diff options
Diffstat (limited to 'drivers/net/wireless/intel/iwlwifi')
186 files changed, 132994 insertions, 0 deletions
diff --git a/drivers/net/wireless/intel/iwlwifi/Kconfig b/drivers/net/wireless/intel/iwlwifi/Kconfig new file mode 100644 index 000000000..b20409f8c --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/Kconfig @@ -0,0 +1,163 @@ +# SPDX-License-Identifier: GPL-2.0-only +config IWLWIFI + tristate "Intel Wireless WiFi Next Gen AGN - Wireless-N/Advanced-N/Ultimate-N (iwlwifi) " + depends on PCI && HAS_IOMEM && CFG80211 + depends on IWLMEI || !IWLMEI + select FW_LOADER + help + Select to build the driver supporting the: + + Intel Wireless WiFi Link Next-Gen AGN + + This option enables support for use with the following hardware: + Intel Wireless WiFi Link 6250AGN Adapter + Intel 6000 Series Wi-Fi Adapters (6200AGN and 6300AGN) + Intel WiFi Link 1000BGN + Intel Wireless WiFi 5150AGN + Intel Wireless WiFi 5100AGN, 5300AGN, and 5350AGN + Intel 6005 Series Wi-Fi Adapters + Intel 6030 Series Wi-Fi Adapters + Intel Wireless WiFi Link 6150BGN 2 Adapter + Intel 100 Series Wi-Fi Adapters (100BGN and 130BGN) + Intel 2000 Series Wi-Fi Adapters + Intel 7260 Wi-Fi Adapter + Intel 3160 Wi-Fi Adapter + Intel 7265 Wi-Fi Adapter + Intel 8260 Wi-Fi Adapter + Intel 3165 Wi-Fi Adapter + + + This driver uses the kernel's mac80211 subsystem. + + In order to use this driver, you will need a firmware + image for it. You can obtain the microcode from: + + <https://wireless.wiki.kernel.org/en/users/Drivers/iwlwifi>. + + The firmware is typically installed in /lib/firmware. You can + look in the hotplug script /etc/hotplug/firmware.agent to + determine which directory FIRMWARE_DIR is set to when the script + runs. + + If you want to compile the driver as a module ( = code which can be + inserted in and removed from the running kernel whenever you want), + say M here and read <file:Documentation/kbuild/modules.rst>. The + module will be called iwlwifi. + +if IWLWIFI + +config IWLWIFI_LEDS + bool + depends on LEDS_CLASS=y || LEDS_CLASS=MAC80211 + depends on IWLMVM || IWLDVM + select LEDS_TRIGGERS + select MAC80211_LEDS + default y + +config IWLDVM + tristate "Intel Wireless WiFi DVM Firmware support" + depends on MAC80211 + help + This is the driver that supports the DVM firmware. The list + of the devices that use this firmware is available here: + https://wireless.wiki.kernel.org/en/users/drivers/iwlwifi#firmware + +config IWLMVM + tristate "Intel Wireless WiFi MVM Firmware support" + select WANT_DEV_COREDUMP + depends on MAC80211 + help + This is the driver that supports the MVM firmware. The list + of the devices that use this firmware is available here: + https://wireless.wiki.kernel.org/en/users/drivers/iwlwifi#firmware + +# don't call it _MODULE -- will confuse Kconfig/fixdep/... +config IWLWIFI_OPMODE_MODULAR + bool + default y if IWLDVM=m + default y if IWLMVM=m + +comment "WARNING: iwlwifi is useless without IWLDVM or IWLMVM" + depends on IWLDVM=n && IWLMVM=n + +menu "Debugging Options" + +config IWLWIFI_DEBUG + bool "Enable full debugging output in the iwlwifi driver" + help + This option will enable debug tracing output for the iwlwifi drivers + + This will result in the kernel module being ~100k larger. You can + control which debug output is sent to the kernel log by setting the + value in + + /sys/module/iwlwifi/parameters/debug + + This entry will only exist if this option is enabled. + + To set a value, simply echo an 8-byte hex value to the same file: + + % echo 0x43fff > /sys/module/iwlwifi/parameters/debug + + You can find the list of debug mask values in: + drivers/net/wireless/iwlwifi/iwl-debug.h + + If this is your first time using this driver, you should say Y here + as the debug information can assist others in helping you resolve + any problems you may encounter. + +config IWLWIFI_DEBUGFS + bool "iwlwifi debugfs support" + depends on MAC80211_DEBUGFS + help + Enable creation of debugfs files for the iwlwifi drivers. This + is a low-impact option that allows getting insight into the + driver's state at runtime. + +config IWLWIFI_DEVICE_TRACING + bool "iwlwifi device access tracing" + depends on EVENT_TRACING + default y + help + Say Y here to trace all commands, including TX frames and IO + accesses, sent to the device. If you say yes, iwlwifi will + register with the ftrace framework for event tracing and dump + all this information to the ringbuffer, you may need to + increase the ringbuffer size. See the ftrace documentation + for more information. + + When tracing is not enabled, this option still has some + (though rather small) overhead. + + If unsure, say Y so we can help you better when problems + occur. +endmenu + +endif + +config IWLMEI + tristate "Intel Management Engine communication over WLAN" + depends on INTEL_MEI + depends on PM + depends on CFG80211 + depends on BROKEN + help + Enables the iwlmei kernel module. + + CSME stands for Converged Security and Management Engine. It is a CPU + on the chipset and runs a dedicated firmware. AMT (Active Management + Technology) is one of the applications that run on that CPU. AMT + allows to control the platform remotely. + + This kernel module allows to communicate with the Intel Management + Engine over Wifi. This is supported starting from Tiger Lake + platforms and has been tested on 9260 devices only. + If AMT is configured not to use the wireless device, this module is + harmless (and useless). + Enabling this option on a platform that has a different device and + has Wireless enabled on AMT can prevent WiFi from working correctly. + + For more information see + <https://software.intel.com/en-us/manageability/> + + If unsure, say N. diff --git a/drivers/net/wireless/intel/iwlwifi/Makefile b/drivers/net/wireless/intel/iwlwifi/Makefile new file mode 100644 index 000000000..75a703eb1 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/Makefile @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0 +# common +obj-$(CONFIG_IWLWIFI) += iwlwifi.o +iwlwifi-objs += iwl-io.o +iwlwifi-objs += iwl-drv.o +iwlwifi-objs += iwl-debug.o +iwlwifi-objs += iwl-eeprom-read.o iwl-eeprom-parse.o +iwlwifi-objs += iwl-phy-db.o iwl-nvm-parse.o +iwlwifi-objs += pcie/drv.o pcie/rx.o pcie/tx.o pcie/trans.o +iwlwifi-objs += pcie/ctxt-info.o pcie/ctxt-info-gen3.o +iwlwifi-objs += pcie/trans-gen2.o pcie/tx-gen2.o +iwlwifi-$(CONFIG_IWLDVM) += cfg/1000.o cfg/2000.o cfg/5000.o cfg/6000.o +iwlwifi-$(CONFIG_IWLMVM) += cfg/7000.o cfg/8000.o cfg/9000.o cfg/22000.o +iwlwifi-objs += iwl-dbg-tlv.o +iwlwifi-objs += iwl-trans.o +iwlwifi-objs += queue/tx.o + +iwlwifi-objs += fw/img.o fw/notif-wait.o fw/rs.o +iwlwifi-objs += fw/dbg.o fw/pnvm.o fw/dump.o +iwlwifi-$(CONFIG_IWLMVM) += fw/paging.o fw/smem.o fw/init.o +iwlwifi-$(CONFIG_ACPI) += fw/acpi.o +iwlwifi-$(CONFIG_EFI) += fw/uefi.o +iwlwifi-$(CONFIG_IWLWIFI_DEBUGFS) += fw/debugfs.o + +iwlwifi-objs += $(iwlwifi-m) + +iwlwifi-$(CONFIG_IWLWIFI_DEVICE_TRACING) += iwl-devtrace.o + +ccflags-y += -I$(src) + +obj-$(CONFIG_IWLDVM) += dvm/ +obj-$(CONFIG_IWLMVM) += mvm/ +obj-$(CONFIG_IWLMEI) += mei/ + +CFLAGS_iwl-devtrace.o := -I$(src) diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/1000.c b/drivers/net/wireless/intel/iwlwifi/cfg/1000.c new file mode 100644 index 000000000..116defb15 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/cfg/1000.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2008 - 2014 Intel Corporation. All rights reserved. + * Copyright(c) 2018 - 2020 Intel Corporation + *****************************************************************************/ + +#include <linux/module.h> +#include <linux/stringify.h> +#include "iwl-config.h" +#include "iwl-agn-hw.h" + +/* Highest firmware API version supported */ +#define IWL1000_UCODE_API_MAX 5 +#define IWL100_UCODE_API_MAX 5 + +/* Lowest firmware API version supported */ +#define IWL1000_UCODE_API_MIN 1 +#define IWL100_UCODE_API_MIN 5 + +/* EEPROM version */ +#define EEPROM_1000_TX_POWER_VERSION (4) +#define EEPROM_1000_EEPROM_VERSION (0x15C) + +#define IWL1000_FW_PRE "iwlwifi-1000-" +#define IWL1000_MODULE_FIRMWARE(api) IWL1000_FW_PRE __stringify(api) ".ucode" + +#define IWL100_FW_PRE "iwlwifi-100-" +#define IWL100_MODULE_FIRMWARE(api) IWL100_FW_PRE __stringify(api) ".ucode" + + +static const struct iwl_base_params iwl1000_base_params = { + .num_of_queues = IWLAGN_NUM_QUEUES, + .max_tfd_queue_size = 256, + .eeprom_size = OTP_LOW_IMAGE_SIZE_2K, + .pll_cfg = true, + .max_ll_items = OTP_MAX_LL_ITEMS_1000, + .shadow_ram_support = false, + .led_compensation = 51, + .wd_timeout = IWL_WATCHDOG_DISABLED, + .max_event_log_size = 128, + .scd_chain_ext_wa = true, +}; + +static const struct iwl_ht_params iwl1000_ht_params = { + .ht_greenfield_support = true, + .use_rts_for_aggregation = true, /* use rts/cts protection */ + .ht40_bands = BIT(NL80211_BAND_2GHZ), +}; + +static const struct iwl_eeprom_params iwl1000_eeprom_params = { + .regulatory_bands = { + EEPROM_REG_BAND_1_CHANNELS, + EEPROM_REG_BAND_2_CHANNELS, + EEPROM_REG_BAND_3_CHANNELS, + EEPROM_REG_BAND_4_CHANNELS, + EEPROM_REG_BAND_5_CHANNELS, + EEPROM_REG_BAND_24_HT40_CHANNELS, + EEPROM_REGULATORY_BAND_NO_HT40, + } +}; + +#define IWL_DEVICE_1000 \ + .fw_name_pre = IWL1000_FW_PRE, \ + .ucode_api_max = IWL1000_UCODE_API_MAX, \ + .ucode_api_min = IWL1000_UCODE_API_MIN, \ + .trans.device_family = IWL_DEVICE_FAMILY_1000, \ + .max_inst_size = IWLAGN_RTC_INST_SIZE, \ + .max_data_size = IWLAGN_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_1000_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_1000_TX_POWER_VERSION, \ + .trans.base_params = &iwl1000_base_params, \ + .eeprom_params = &iwl1000_eeprom_params, \ + .led_mode = IWL_LED_BLINK + +const struct iwl_cfg iwl1000_bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 1000 BGN", + IWL_DEVICE_1000, + .ht_params = &iwl1000_ht_params, +}; + +const struct iwl_cfg iwl1000_bg_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 1000 BG", + IWL_DEVICE_1000, +}; + +#define IWL_DEVICE_100 \ + .fw_name_pre = IWL100_FW_PRE, \ + .ucode_api_max = IWL100_UCODE_API_MAX, \ + .ucode_api_min = IWL100_UCODE_API_MIN, \ + .trans.device_family = IWL_DEVICE_FAMILY_100, \ + .max_inst_size = IWLAGN_RTC_INST_SIZE, \ + .max_data_size = IWLAGN_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_1000_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_1000_TX_POWER_VERSION, \ + .trans.base_params = &iwl1000_base_params, \ + .eeprom_params = &iwl1000_eeprom_params, \ + .led_mode = IWL_LED_RF_STATE, \ + .rx_with_siso_diversity = true + +const struct iwl_cfg iwl100_bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 100 BGN", + IWL_DEVICE_100, + .ht_params = &iwl1000_ht_params, +}; + +const struct iwl_cfg iwl100_bg_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 100 BG", + IWL_DEVICE_100, +}; + +MODULE_FIRMWARE(IWL1000_MODULE_FIRMWARE(IWL1000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL100_MODULE_FIRMWARE(IWL100_UCODE_API_MAX)); diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/2000.c b/drivers/net/wireless/intel/iwlwifi/cfg/2000.c new file mode 100644 index 000000000..ab2038a3f --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/cfg/2000.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2008 - 2014 Intel Corporation. All rights reserved. + * Copyright(c) 2018 - 2020 Intel Corporation + *****************************************************************************/ + +#include <linux/module.h> +#include <linux/stringify.h> +#include "iwl-config.h" +#include "iwl-agn-hw.h" +#include "dvm/commands.h" /* needed for BT for now */ + +/* Highest firmware API version supported */ +#define IWL2030_UCODE_API_MAX 6 +#define IWL2000_UCODE_API_MAX 6 +#define IWL105_UCODE_API_MAX 6 +#define IWL135_UCODE_API_MAX 6 + +/* Lowest firmware API version supported */ +#define IWL2030_UCODE_API_MIN 5 +#define IWL2000_UCODE_API_MIN 5 +#define IWL105_UCODE_API_MIN 5 +#define IWL135_UCODE_API_MIN 5 + +/* EEPROM version */ +#define EEPROM_2000_TX_POWER_VERSION (6) +#define EEPROM_2000_EEPROM_VERSION (0x805) + + +#define IWL2030_FW_PRE "iwlwifi-2030-" +#define IWL2030_MODULE_FIRMWARE(api) IWL2030_FW_PRE __stringify(api) ".ucode" + +#define IWL2000_FW_PRE "iwlwifi-2000-" +#define IWL2000_MODULE_FIRMWARE(api) IWL2000_FW_PRE __stringify(api) ".ucode" + +#define IWL105_FW_PRE "iwlwifi-105-" +#define IWL105_MODULE_FIRMWARE(api) IWL105_FW_PRE __stringify(api) ".ucode" + +#define IWL135_FW_PRE "iwlwifi-135-" +#define IWL135_MODULE_FIRMWARE(api) IWL135_FW_PRE __stringify(api) ".ucode" + +static const struct iwl_base_params iwl2000_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE_2K, + .num_of_queues = IWLAGN_NUM_QUEUES, + .max_tfd_queue_size = 256, + .max_ll_items = OTP_MAX_LL_ITEMS_2x00, + .shadow_ram_support = true, + .led_compensation = 51, + .wd_timeout = IWL_DEF_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = false, /* TODO: fix bugs using this feature */ + .scd_chain_ext_wa = true, +}; + + +static const struct iwl_base_params iwl2030_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE_2K, + .num_of_queues = IWLAGN_NUM_QUEUES, + .max_tfd_queue_size = 256, + .max_ll_items = OTP_MAX_LL_ITEMS_2x00, + .shadow_ram_support = true, + .led_compensation = 57, + .wd_timeout = IWL_LONG_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = false, /* TODO: fix bugs using this feature */ + .scd_chain_ext_wa = true, +}; + +static const struct iwl_ht_params iwl2000_ht_params = { + .ht_greenfield_support = true, + .use_rts_for_aggregation = true, /* use rts/cts protection */ + .ht40_bands = BIT(NL80211_BAND_2GHZ), +}; + +static const struct iwl_eeprom_params iwl20x0_eeprom_params = { + .regulatory_bands = { + EEPROM_REG_BAND_1_CHANNELS, + EEPROM_REG_BAND_2_CHANNELS, + EEPROM_REG_BAND_3_CHANNELS, + EEPROM_REG_BAND_4_CHANNELS, + EEPROM_REG_BAND_5_CHANNELS, + EEPROM_6000_REG_BAND_24_HT40_CHANNELS, + EEPROM_REGULATORY_BAND_NO_HT40, + }, + .enhanced_txpower = true, +}; + +#define IWL_DEVICE_2000 \ + .fw_name_pre = IWL2000_FW_PRE, \ + .ucode_api_max = IWL2000_UCODE_API_MAX, \ + .ucode_api_min = IWL2000_UCODE_API_MIN, \ + .trans.device_family = IWL_DEVICE_FAMILY_2000, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_2000_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_2000_TX_POWER_VERSION, \ + .trans.base_params = &iwl2000_base_params, \ + .eeprom_params = &iwl20x0_eeprom_params, \ + .led_mode = IWL_LED_RF_STATE + + +const struct iwl_cfg iwl2000_2bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 2200 BGN", + IWL_DEVICE_2000, + .ht_params = &iwl2000_ht_params, +}; + +const struct iwl_cfg iwl2000_2bgn_d_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 2200D BGN", + IWL_DEVICE_2000, + .ht_params = &iwl2000_ht_params, +}; + +#define IWL_DEVICE_2030 \ + .fw_name_pre = IWL2030_FW_PRE, \ + .ucode_api_max = IWL2030_UCODE_API_MAX, \ + .ucode_api_min = IWL2030_UCODE_API_MIN, \ + .trans.device_family = IWL_DEVICE_FAMILY_2030, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_2000_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_2000_TX_POWER_VERSION, \ + .trans.base_params = &iwl2030_base_params, \ + .eeprom_params = &iwl20x0_eeprom_params, \ + .led_mode = IWL_LED_RF_STATE + +const struct iwl_cfg iwl2030_2bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 2230 BGN", + IWL_DEVICE_2030, + .ht_params = &iwl2000_ht_params, +}; + +#define IWL_DEVICE_105 \ + .fw_name_pre = IWL105_FW_PRE, \ + .ucode_api_max = IWL105_UCODE_API_MAX, \ + .ucode_api_min = IWL105_UCODE_API_MIN, \ + .trans.device_family = IWL_DEVICE_FAMILY_105, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_2000_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_2000_TX_POWER_VERSION, \ + .trans.base_params = &iwl2000_base_params, \ + .eeprom_params = &iwl20x0_eeprom_params, \ + .led_mode = IWL_LED_RF_STATE, \ + .rx_with_siso_diversity = true + +const struct iwl_cfg iwl105_bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 105 BGN", + IWL_DEVICE_105, + .ht_params = &iwl2000_ht_params, +}; + +const struct iwl_cfg iwl105_bgn_d_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 105D BGN", + IWL_DEVICE_105, + .ht_params = &iwl2000_ht_params, +}; + +#define IWL_DEVICE_135 \ + .fw_name_pre = IWL135_FW_PRE, \ + .ucode_api_max = IWL135_UCODE_API_MAX, \ + .ucode_api_min = IWL135_UCODE_API_MIN, \ + .trans.device_family = IWL_DEVICE_FAMILY_135, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_2000_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_2000_TX_POWER_VERSION, \ + .trans.base_params = &iwl2030_base_params, \ + .eeprom_params = &iwl20x0_eeprom_params, \ + .led_mode = IWL_LED_RF_STATE, \ + .rx_with_siso_diversity = true + +const struct iwl_cfg iwl135_bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 135 BGN", + IWL_DEVICE_135, + .ht_params = &iwl2000_ht_params, +}; + +MODULE_FIRMWARE(IWL2000_MODULE_FIRMWARE(IWL2000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL2030_MODULE_FIRMWARE(IWL2030_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL105_MODULE_FIRMWARE(IWL105_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL135_MODULE_FIRMWARE(IWL135_UCODE_API_MAX)); diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/22000.c b/drivers/net/wireless/intel/iwlwifi/cfg/22000.c new file mode 100644 index 000000000..110fda65b --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/cfg/22000.c @@ -0,0 +1,1039 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2015-2017 Intel Deutschland GmbH + * Copyright (C) 2018-2022 Intel Corporation + */ +#include <linux/module.h> +#include <linux/stringify.h> +#include "iwl-config.h" +#include "iwl-prph.h" +#include "fw/api/txq.h" + +/* Highest firmware API version supported */ +#define IWL_22000_UCODE_API_MAX 72 + +/* Lowest firmware API version supported */ +#define IWL_22000_UCODE_API_MIN 39 + +/* NVM versions */ +#define IWL_22000_NVM_VERSION 0x0a1d + +/* Memory offsets and lengths */ +#define IWL_22000_DCCM_OFFSET 0x800000 /* LMAC1 */ +#define IWL_22000_DCCM_LEN 0x10000 /* LMAC1 */ +#define IWL_22000_DCCM2_OFFSET 0x880000 +#define IWL_22000_DCCM2_LEN 0x8000 +#define IWL_22000_SMEM_OFFSET 0x400000 +#define IWL_22000_SMEM_LEN 0xD0000 + +#define IWL_QU_B_HR_B_FW_PRE "iwlwifi-Qu-b0-hr-b0-" +#define IWL_QNJ_B_HR_B_FW_PRE "iwlwifi-QuQnj-b0-hr-b0-" +#define IWL_QU_C_HR_B_FW_PRE "iwlwifi-Qu-c0-hr-b0-" +#define IWL_QU_B_JF_B_FW_PRE "iwlwifi-Qu-b0-jf-b0-" +#define IWL_QU_C_JF_B_FW_PRE "iwlwifi-Qu-c0-jf-b0-" +#define IWL_QUZ_A_HR_B_FW_PRE "iwlwifi-QuZ-a0-hr-b0-" +#define IWL_QUZ_A_JF_B_FW_PRE "iwlwifi-QuZ-a0-jf-b0-" +#define IWL_QNJ_B_JF_B_FW_PRE "iwlwifi-QuQnj-b0-jf-b0-" +#define IWL_CC_A_FW_PRE "iwlwifi-cc-a0-" +#define IWL_SO_A_JF_B_FW_PRE "iwlwifi-so-a0-jf-b0-" +#define IWL_SO_A_HR_B_FW_PRE "iwlwifi-so-a0-hr-b0-" +#define IWL_SO_A_GF_A_FW_PRE "iwlwifi-so-a0-gf-a0-" +#define IWL_TY_A_GF_A_FW_PRE "iwlwifi-ty-a0-gf-a0-" +#define IWL_SO_A_GF4_A_FW_PRE "iwlwifi-so-a0-gf4-a0-" +#define IWL_SO_A_MR_A_FW_PRE "iwlwifi-so-a0-mr-a0-" +#define IWL_SNJ_A_GF4_A_FW_PRE "iwlwifi-SoSnj-a0-gf4-a0-" +#define IWL_SNJ_A_GF_A_FW_PRE "iwlwifi-SoSnj-a0-gf-a0-" +#define IWL_SNJ_A_HR_B_FW_PRE "iwlwifi-SoSnj-a0-hr-b0-" +#define IWL_SNJ_A_JF_B_FW_PRE "iwlwifi-SoSnj-a0-jf-b0-" +#define IWL_MA_A_HR_B_FW_PRE "iwlwifi-ma-a0-hr-b0-" +#define IWL_MA_A_GF_A_FW_PRE "iwlwifi-ma-a0-gf-a0-" +#define IWL_MA_A_GF4_A_FW_PRE "iwlwifi-ma-a0-gf4-a0-" +#define IWL_MA_A_MR_A_FW_PRE "iwlwifi-ma-a0-mr-a0-" +#define IWL_MA_A_FM_A_FW_PRE "iwlwifi-ma-a0-fm-a0-" +#define IWL_SNJ_A_MR_A_FW_PRE "iwlwifi-SoSnj-a0-mr-a0-" +#define IWL_BZ_A_HR_B_FW_PRE "iwlwifi-bz-a0-hr-b0-" +#define IWL_BZ_A_GF_A_FW_PRE "iwlwifi-bz-a0-gf-a0-" +#define IWL_BZ_A_GF4_A_FW_PRE "iwlwifi-bz-a0-gf4-a0-" +#define IWL_BZ_A_MR_A_FW_PRE "iwlwifi-bz-a0-mr-a0-" +#define IWL_BZ_A_FM_A_FW_PRE "iwlwifi-bz-a0-fm-a0-" +#define IWL_BZ_A_FM4_A_FW_PRE "iwlwifi-bz-a0-fm4-a0-" +#define IWL_GL_A_FM_A_FW_PRE "iwlwifi-gl-a0-fm-a0-" +#define IWL_GL_B_FM_B_FW_PRE "iwlwifi-gl-b0-fm-b0-" +#define IWL_BZ_Z_GF_A_FW_PRE "iwlwifi-bz-z0-gf-a0-" +#define IWL_BNJ_A_FM_A_FW_PRE "iwlwifi-BzBnj-a0-fm-a0-" +#define IWL_BNJ_A_FM4_A_FW_PRE "iwlwifi-BzBnj-a0-fm4-a0-" +#define IWL_BNJ_A_GF_A_FW_PRE "iwlwifi-BzBnj-a0-gf-a0-" +#define IWL_BNJ_A_GF4_A_FW_PRE "iwlwifi-BzBnj-a0-gf4-a0-" +#define IWL_BNJ_A_HR_B_FW_PRE "iwlwifi-BzBnj-a0-hr-b0-" +#define IWL_BNJ_B_FM_B_FW_PRE "iwlwifi-BzBnj-b0-fm-b0-" + + +#define IWL_QU_B_HR_B_MODULE_FIRMWARE(api) \ + IWL_QU_B_HR_B_FW_PRE __stringify(api) ".ucode" +#define IWL_QNJ_B_HR_B_MODULE_FIRMWARE(api) \ + IWL_QNJ_B_HR_B_FW_PRE __stringify(api) ".ucode" +#define IWL_QUZ_A_HR_B_MODULE_FIRMWARE(api) \ + IWL_QUZ_A_HR_B_FW_PRE __stringify(api) ".ucode" +#define IWL_QUZ_A_JF_B_MODULE_FIRMWARE(api) \ + IWL_QUZ_A_JF_B_FW_PRE __stringify(api) ".ucode" +#define IWL_QU_C_HR_B_MODULE_FIRMWARE(api) \ + IWL_QU_C_HR_B_FW_PRE __stringify(api) ".ucode" +#define IWL_QU_B_JF_B_MODULE_FIRMWARE(api) \ + IWL_QU_B_JF_B_FW_PRE __stringify(api) ".ucode" +#define IWL_QNJ_B_JF_B_MODULE_FIRMWARE(api) \ + IWL_QNJ_B_JF_B_FW_PRE __stringify(api) ".ucode" +#define IWL_CC_A_MODULE_FIRMWARE(api) \ + IWL_CC_A_FW_PRE __stringify(api) ".ucode" +#define IWL_SO_A_JF_B_MODULE_FIRMWARE(api) \ + IWL_SO_A_JF_B_FW_PRE __stringify(api) ".ucode" +#define IWL_SO_A_HR_B_MODULE_FIRMWARE(api) \ + IWL_SO_A_HR_B_FW_PRE __stringify(api) ".ucode" +#define IWL_SO_A_GF_A_MODULE_FIRMWARE(api) \ + IWL_SO_A_GF_A_FW_PRE __stringify(api) ".ucode" +#define IWL_TY_A_GF_A_MODULE_FIRMWARE(api) \ + IWL_TY_A_GF_A_FW_PRE __stringify(api) ".ucode" +#define IWL_SNJ_A_GF4_A_MODULE_FIRMWARE(api) \ + IWL_SNJ_A_GF4_A_FW_PRE __stringify(api) ".ucode" +#define IWL_SNJ_A_GF_A_MODULE_FIRMWARE(api) \ + IWL_SNJ_A_GF_A_FW_PRE __stringify(api) ".ucode" +#define IWL_SNJ_A_HR_B_MODULE_FIRMWARE(api) \ + IWL_SNJ_A_HR_B_FW_PRE __stringify(api) ".ucode" +#define IWL_SNJ_A_JF_B_MODULE_FIRMWARE(api) \ + IWL_SNJ_A_JF_B_FW_PRE __stringify(api) ".ucode" +#define IWL_MA_A_HR_B_FW_MODULE_FIRMWARE(api) \ + IWL_MA_A_HR_B_FW_PRE __stringify(api) ".ucode" +#define IWL_MA_A_GF_A_FW_MODULE_FIRMWARE(api) \ + IWL_MA_A_GF_A_FW_PRE __stringify(api) ".ucode" +#define IWL_MA_A_GF4_A_FW_MODULE_FIRMWARE(api) \ + IWL_MA_A_GF4_A_FW_PRE __stringify(api) ".ucode" +#define IWL_MA_A_MR_A_FW_MODULE_FIRMWARE(api) \ + IWL_MA_A_MR_A_FW_PRE __stringify(api) ".ucode" +#define IWL_MA_A_FM_A_FW_MODULE_FIRMWARE(api) \ + IWL_MA_A_FM_A_FW_PRE __stringify(api) ".ucode" +#define IWL_SNJ_A_MR_A_MODULE_FIRMWARE(api) \ + IWL_SNJ_A_MR_A_FW_PRE __stringify(api) ".ucode" +#define IWL_BZ_A_HR_B_MODULE_FIRMWARE(api) \ + IWL_BZ_A_HR_B_FW_PRE __stringify(api) ".ucode" +#define IWL_BZ_A_GF_A_MODULE_FIRMWARE(api) \ + IWL_BZ_A_GF_A_FW_PRE __stringify(api) ".ucode" +#define IWL_BZ_A_GF4_A_MODULE_FIRMWARE(api) \ + IWL_BZ_A_GF4_A_FW_PRE __stringify(api) ".ucode" +#define IWL_BZ_A_MR_A_MODULE_FIRMWARE(api) \ + IWL_BZ_A_MR_A_FW_PRE __stringify(api) ".ucode" +#define IWL_BZ_A_FM_A_MODULE_FIRMWARE(api) \ + IWL_BZ_A_FM_A_FW_PRE __stringify(api) ".ucode" +#define IWL_BZ_A_FM4_A_MODULE_FIRMWARE(api) \ + IWL_BZ_A_FM4_A_FW_PRE __stringify(api) ".ucode" +#define IWL_GL_A_FM_A_MODULE_FIRMWARE(api) \ + IWL_GL_A_FM_A_FW_PRE __stringify(api) ".ucode" +#define IWL_GL_B_FM_B_MODULE_FIRMWARE(api) \ + IWL_GL_B_FM_B_FW_PRE __stringify(api) ".ucode" +#define IWL_BNJ_A_FM_A_MODULE_FIRMWARE(api) \ + IWL_BNJ_A_FM_A_FW_PRE __stringify(api) ".ucode" +#define IWL_BNJ_A_FM4_A_MODULE_FIRMWARE(api) \ + IWL_BNJ_A_FM4_A_FW_PRE __stringify(api) ".ucode" +#define IWL_BNJ_A_GF_A_MODULE_FIRMWARE(api) \ + IWL_BNJ_A_GF_A_FW_PRE __stringify(api) ".ucode" +#define IWL_BNJ_A_GF4_A_MODULE_FIRMWARE(api) \ + IWL_BNJ_A_GF4_A_FW_PRE __stringify(api) ".ucode" +#define IWL_BNJ_A_HR_B_MODULE_FIRMWARE(api) \ + IWL_BNJ_A_HR_B_FW_PRE __stringify(api) ".ucode" +#define IWL_BNJ_B_FM_B_MODULE_FIRMWARE(api) \ + IWL_BNJ_B_FM_B_FW_PRE __stringify(api) ".ucode" + +static const struct iwl_base_params iwl_22000_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE_32K, + .num_of_queues = 512, + .max_tfd_queue_size = 256, + .shadow_ram_support = true, + .led_compensation = 57, + .wd_timeout = IWL_LONG_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = true, + .pcie_l1_allowed = true, +}; + +static const struct iwl_base_params iwl_ax210_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE_32K, + .num_of_queues = 512, + .max_tfd_queue_size = 65536, + .shadow_ram_support = true, + .led_compensation = 57, + .wd_timeout = IWL_LONG_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = true, + .pcie_l1_allowed = true, +}; + +static const struct iwl_ht_params iwl_22000_ht_params = { + .stbc = true, + .ldpc = true, + .ht40_bands = BIT(NL80211_BAND_2GHZ) | BIT(NL80211_BAND_5GHZ) | + BIT(NL80211_BAND_6GHZ), +}; + +#define IWL_DEVICE_22000_COMMON \ + .ucode_api_max = IWL_22000_UCODE_API_MAX, \ + .ucode_api_min = IWL_22000_UCODE_API_MIN, \ + .led_mode = IWL_LED_RF_STATE, \ + .nvm_hw_section_num = 10, \ + .non_shared_ant = ANT_B, \ + .dccm_offset = IWL_22000_DCCM_OFFSET, \ + .dccm_len = IWL_22000_DCCM_LEN, \ + .dccm2_offset = IWL_22000_DCCM2_OFFSET, \ + .dccm2_len = IWL_22000_DCCM2_LEN, \ + .smem_offset = IWL_22000_SMEM_OFFSET, \ + .smem_len = IWL_22000_SMEM_LEN, \ + .features = IWL_TX_CSUM_NETIF_FLAGS | NETIF_F_RXCSUM, \ + .apmg_not_supported = true, \ + .trans.mq_rx_supported = true, \ + .vht_mu_mimo_supported = true, \ + .mac_addr_from_csr = 0x380, \ + .ht_params = &iwl_22000_ht_params, \ + .nvm_ver = IWL_22000_NVM_VERSION, \ + .trans.use_tfh = true, \ + .trans.rf_id = true, \ + .trans.gen2 = true, \ + .nvm_type = IWL_NVM_EXT, \ + .dbgc_supported = true, \ + .min_umac_error_event_table = 0x400000, \ + .d3_debug_data_base_addr = 0x401000, \ + .d3_debug_data_length = 60 * 1024, \ + .mon_smem_regs = { \ + .write_ptr = { \ + .addr = LDBG_M2S_BUF_WPTR, \ + .mask = LDBG_M2S_BUF_WPTR_VAL_MSK, \ + }, \ + .cycle_cnt = { \ + .addr = LDBG_M2S_BUF_WRAP_CNT, \ + .mask = LDBG_M2S_BUF_WRAP_CNT_VAL_MSK, \ + }, \ + } + +#define IWL_DEVICE_22500 \ + IWL_DEVICE_22000_COMMON, \ + .trans.device_family = IWL_DEVICE_FAMILY_22000, \ + .trans.base_params = &iwl_22000_base_params, \ + .gp2_reg_addr = 0xa02c68, \ + .mon_dram_regs = { \ + .write_ptr = { \ + .addr = MON_BUFF_WRPTR_VER2, \ + .mask = 0xffffffff, \ + }, \ + .cycle_cnt = { \ + .addr = MON_BUFF_CYCLE_CNT_VER2, \ + .mask = 0xffffffff, \ + }, \ + } + +#define IWL_DEVICE_AX210 \ + IWL_DEVICE_22000_COMMON, \ + .trans.umac_prph_offset = 0x300000, \ + .trans.device_family = IWL_DEVICE_FAMILY_AX210, \ + .trans.base_params = &iwl_ax210_base_params, \ + .min_txq_size = 128, \ + .gp2_reg_addr = 0xd02c68, \ + .min_ba_txq_size = IWL_DEFAULT_QUEUE_SIZE_HE, \ + .mon_dram_regs = { \ + .write_ptr = { \ + .addr = DBGC_CUR_DBGBUF_STATUS, \ + .mask = DBGC_CUR_DBGBUF_STATUS_OFFSET_MSK, \ + }, \ + .cycle_cnt = { \ + .addr = DBGC_DBGBUF_WRAP_AROUND, \ + .mask = 0xffffffff, \ + }, \ + .cur_frag = { \ + .addr = DBGC_CUR_DBGBUF_STATUS, \ + .mask = DBGC_CUR_DBGBUF_STATUS_IDX_MSK, \ + }, \ + } + +#define IWL_DEVICE_BZ \ + .ucode_api_max = IWL_22000_UCODE_API_MAX, \ + .ucode_api_min = IWL_22000_UCODE_API_MIN, \ + .led_mode = IWL_LED_RF_STATE, \ + .nvm_hw_section_num = 10, \ + .non_shared_ant = ANT_B, \ + .dccm_offset = IWL_22000_DCCM_OFFSET, \ + .dccm_len = IWL_22000_DCCM_LEN, \ + .dccm2_offset = IWL_22000_DCCM2_OFFSET, \ + .dccm2_len = IWL_22000_DCCM2_LEN, \ + .smem_offset = IWL_22000_SMEM_OFFSET, \ + .smem_len = IWL_22000_SMEM_LEN, \ + .features = IWL_TX_CSUM_NETIF_FLAGS_BZ | NETIF_F_RXCSUM, \ + .apmg_not_supported = true, \ + .trans.mq_rx_supported = true, \ + .vht_mu_mimo_supported = true, \ + .mac_addr_from_csr = 0x30, \ + .ht_params = &iwl_22000_ht_params, \ + .nvm_ver = IWL_22000_NVM_VERSION, \ + .trans.use_tfh = true, \ + .trans.rf_id = true, \ + .trans.gen2 = true, \ + .nvm_type = IWL_NVM_EXT, \ + .dbgc_supported = true, \ + .min_umac_error_event_table = 0x400000, \ + .d3_debug_data_base_addr = 0x401000, \ + .d3_debug_data_length = 60 * 1024, \ + .mon_smem_regs = { \ + .write_ptr = { \ + .addr = LDBG_M2S_BUF_WPTR, \ + .mask = LDBG_M2S_BUF_WPTR_VAL_MSK, \ + }, \ + .cycle_cnt = { \ + .addr = LDBG_M2S_BUF_WRAP_CNT, \ + .mask = LDBG_M2S_BUF_WRAP_CNT_VAL_MSK, \ + }, \ + }, \ + .trans.umac_prph_offset = 0x300000, \ + .trans.device_family = IWL_DEVICE_FAMILY_BZ, \ + .trans.base_params = &iwl_ax210_base_params, \ + .min_txq_size = 128, \ + .gp2_reg_addr = 0xd02c68, \ + .min_ba_txq_size = IWL_DEFAULT_QUEUE_SIZE_EHT, \ + .mon_dram_regs = { \ + .write_ptr = { \ + .addr = DBGC_CUR_DBGBUF_STATUS, \ + .mask = DBGC_CUR_DBGBUF_STATUS_OFFSET_MSK, \ + }, \ + .cycle_cnt = { \ + .addr = DBGC_DBGBUF_WRAP_AROUND, \ + .mask = 0xffffffff, \ + }, \ + .cur_frag = { \ + .addr = DBGC_CUR_DBGBUF_STATUS, \ + .mask = DBGC_CUR_DBGBUF_STATUS_IDX_MSK, \ + }, \ + }, \ + .mon_dbgi_regs = { \ + .write_ptr = { \ + .addr = DBGI_SRAM_FIFO_POINTERS, \ + .mask = DBGI_SRAM_FIFO_POINTERS_WR_PTR_MSK, \ + }, \ + } + +const struct iwl_cfg_trans_params iwl_qnj_trans_cfg = { + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .device_family = IWL_DEVICE_FAMILY_22000, + .base_params = &iwl_22000_base_params, +}; + +const struct iwl_cfg_trans_params iwl_qu_trans_cfg = { + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .device_family = IWL_DEVICE_FAMILY_22000, + .base_params = &iwl_22000_base_params, + .integrated = true, + .xtal_latency = 500, + .ltr_delay = IWL_CFG_TRANS_LTR_DELAY_200US, +}; + +const struct iwl_cfg_trans_params iwl_qu_medium_latency_trans_cfg = { + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .device_family = IWL_DEVICE_FAMILY_22000, + .base_params = &iwl_22000_base_params, + .integrated = true, + .xtal_latency = 1820, + .ltr_delay = IWL_CFG_TRANS_LTR_DELAY_1820US, +}; + +const struct iwl_cfg_trans_params iwl_qu_long_latency_trans_cfg = { + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .device_family = IWL_DEVICE_FAMILY_22000, + .base_params = &iwl_22000_base_params, + .integrated = true, + .xtal_latency = 12000, + .low_latency_xtal = true, + .ltr_delay = IWL_CFG_TRANS_LTR_DELAY_2500US, +}; + +const struct iwl_cfg_trans_params iwl_snj_trans_cfg = { + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .device_family = IWL_DEVICE_FAMILY_AX210, + .base_params = &iwl_ax210_base_params, + .umac_prph_offset = 0x300000, +}; + +const struct iwl_cfg_trans_params iwl_so_trans_cfg = { + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .device_family = IWL_DEVICE_FAMILY_AX210, + .base_params = &iwl_ax210_base_params, + .umac_prph_offset = 0x300000, + .integrated = true, + /* TODO: the following values need to be checked */ + .xtal_latency = 500, + .ltr_delay = IWL_CFG_TRANS_LTR_DELAY_200US, +}; + +const struct iwl_cfg_trans_params iwl_so_long_latency_trans_cfg = { + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .device_family = IWL_DEVICE_FAMILY_AX210, + .base_params = &iwl_ax210_base_params, + .umac_prph_offset = 0x300000, + .integrated = true, + .low_latency_xtal = true, + .xtal_latency = 12000, + .ltr_delay = IWL_CFG_TRANS_LTR_DELAY_2500US, +}; + +const struct iwl_cfg_trans_params iwl_so_long_latency_imr_trans_cfg = { + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .device_family = IWL_DEVICE_FAMILY_AX210, + .base_params = &iwl_ax210_base_params, + .umac_prph_offset = 0x300000, + .integrated = true, + .low_latency_xtal = true, + .xtal_latency = 12000, + .ltr_delay = IWL_CFG_TRANS_LTR_DELAY_2500US, + .imr_enabled = true, +}; + +/* + * If the device doesn't support HE, no need to have that many buffers. + * 22000 devices can split multiple frames into a single RB, so fewer are + * needed; AX210 cannot (but use smaller RBs by default) - these sizes + * were picked according to 8 MSDUs inside 256 A-MSDUs in an A-MPDU, with + * additional overhead to account for processing time. + */ +#define IWL_NUM_RBDS_NON_HE 512 +#define IWL_NUM_RBDS_22000_HE 2048 +#define IWL_NUM_RBDS_AX210_HE 4096 + +/* + * All JF radio modules are part of the 9000 series, but the MAC part + * looks more like 22000. That's why this device is here, but called + * 9560 nevertheless. + */ +const struct iwl_cfg iwl9560_qu_b0_jf_b0_cfg = { + .fw_name_pre = IWL_QU_B_JF_B_FW_PRE, + IWL_DEVICE_22500, + .num_rbds = IWL_NUM_RBDS_NON_HE, +}; + +const struct iwl_cfg iwl9560_qu_c0_jf_b0_cfg = { + .fw_name_pre = IWL_QU_C_JF_B_FW_PRE, + IWL_DEVICE_22500, + .num_rbds = IWL_NUM_RBDS_NON_HE, +}; + +const struct iwl_cfg iwl9560_quz_a0_jf_b0_cfg = { + .fw_name_pre = IWL_QUZ_A_JF_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_NON_HE, +}; + +const struct iwl_cfg iwl9560_qnj_b0_jf_b0_cfg = { + .fw_name_pre = IWL_QNJ_B_JF_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_NON_HE, +}; + +const struct iwl_cfg_trans_params iwl_ax200_trans_cfg = { + .device_family = IWL_DEVICE_FAMILY_22000, + .base_params = &iwl_22000_base_params, + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .bisr_workaround = 1, +}; + +const struct iwl_cfg_trans_params iwl_ma_trans_cfg = { + .device_family = IWL_DEVICE_FAMILY_AX210, + .base_params = &iwl_ax210_base_params, + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .integrated = true, + .umac_prph_offset = 0x300000 +}; + +const struct iwl_cfg_trans_params iwl_bz_trans_cfg = { + .device_family = IWL_DEVICE_FAMILY_BZ, + .base_params = &iwl_ax210_base_params, + .mq_rx_supported = true, + .use_tfh = true, + .rf_id = true, + .gen2 = true, + .integrated = true, + .umac_prph_offset = 0x300000, + .xtal_latency = 12000, + .low_latency_xtal = true, + .ltr_delay = IWL_CFG_TRANS_LTR_DELAY_2500US, +}; + +const char iwl_ax101_name[] = "Intel(R) Wi-Fi 6 AX101"; +const char iwl_ax200_name[] = "Intel(R) Wi-Fi 6 AX200 160MHz"; +const char iwl_ax201_name[] = "Intel(R) Wi-Fi 6 AX201 160MHz"; +const char iwl_ax203_name[] = "Intel(R) Wi-Fi 6 AX203"; +const char iwl_ax204_name[] = "Intel(R) Wi-Fi 6 AX204 160MHz"; +const char iwl_ax211_name[] = "Intel(R) Wi-Fi 6E AX211 160MHz"; +const char iwl_ax221_name[] = "Intel(R) Wi-Fi 6E AX221 160MHz"; +const char iwl_ax231_name[] = "Intel(R) Wi-Fi 6E AX231 160MHz"; +const char iwl_ax411_name[] = "Intel(R) Wi-Fi 6E AX411 160MHz"; +const char iwl_bz_name[] = "Intel(R) TBD Bz device"; + +const char iwl_ax200_killer_1650w_name[] = + "Killer(R) Wi-Fi 6 AX1650w 160MHz Wireless Network Adapter (200D2W)"; +const char iwl_ax200_killer_1650x_name[] = + "Killer(R) Wi-Fi 6 AX1650x 160MHz Wireless Network Adapter (200NGW)"; +const char iwl_ax201_killer_1650s_name[] = + "Killer(R) Wi-Fi 6 AX1650s 160MHz Wireless Network Adapter (201D2W)"; +const char iwl_ax201_killer_1650i_name[] = + "Killer(R) Wi-Fi 6 AX1650i 160MHz Wireless Network Adapter (201NGW)"; +const char iwl_ax210_killer_1675w_name[] = + "Killer(R) Wi-Fi 6E AX1675w 160MHz Wireless Network Adapter (210D2W)"; +const char iwl_ax210_killer_1675x_name[] = + "Killer(R) Wi-Fi 6E AX1675x 160MHz Wireless Network Adapter (210NGW)"; +const char iwl_ax211_killer_1675s_name[] = + "Killer(R) Wi-Fi 6E AX1675s 160MHz Wireless Network Adapter (211NGW)"; +const char iwl_ax211_killer_1675i_name[] = + "Killer(R) Wi-Fi 6E AX1675i 160MHz Wireless Network Adapter (211NGW)"; +const char iwl_ax411_killer_1690s_name[] = + "Killer(R) Wi-Fi 6E AX1690s 160MHz Wireless Network Adapter (411D2W)"; +const char iwl_ax411_killer_1690i_name[] = + "Killer(R) Wi-Fi 6E AX1690i 160MHz Wireless Network Adapter (411NGW)"; + +const struct iwl_cfg iwl_qu_b0_hr1_b0 = { + .fw_name_pre = IWL_QU_B_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .tx_with_siso_diversity = true, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_qu_b0_hr_b0 = { + .fw_name_pre = IWL_QU_B_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_ax201_cfg_qu_hr = { + .name = "Intel(R) Wi-Fi 6 AX201 160MHz", + .fw_name_pre = IWL_QU_B_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_qu_c0_hr1_b0 = { + .fw_name_pre = IWL_QU_C_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .tx_with_siso_diversity = true, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_qu_c0_hr_b0 = { + .fw_name_pre = IWL_QU_C_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_ax201_cfg_qu_c0_hr_b0 = { + .name = "Intel(R) Wi-Fi 6 AX201 160MHz", + .fw_name_pre = IWL_QU_C_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_quz_a0_hr1_b0 = { + .fw_name_pre = IWL_QUZ_A_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .tx_with_siso_diversity = true, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_ax201_cfg_quz_hr = { + .name = "Intel(R) Wi-Fi 6 AX201 160MHz", + .fw_name_pre = IWL_QUZ_A_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_ax1650s_cfg_quz_hr = { + .name = "Killer(R) Wi-Fi 6 AX1650s 160MHz Wireless Network Adapter (201D2W)", + .fw_name_pre = IWL_QUZ_A_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_ax1650i_cfg_quz_hr = { + .name = "Killer(R) Wi-Fi 6 AX1650i 160MHz Wireless Network Adapter (201NGW)", + .fw_name_pre = IWL_QUZ_A_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_ax200_cfg_cc = { + .fw_name_pre = IWL_CC_A_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg killer1650s_2ax_cfg_qu_b0_hr_b0 = { + .name = "Killer(R) Wi-Fi 6 AX1650s 160MHz Wireless Network Adapter (201NGW)", + .fw_name_pre = IWL_QU_B_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg killer1650i_2ax_cfg_qu_b0_hr_b0 = { + .name = "Killer(R) Wi-Fi 6 AX1650i 160MHz Wireless Network Adapter (201D2W)", + .fw_name_pre = IWL_QU_B_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg killer1650s_2ax_cfg_qu_c0_hr_b0 = { + .name = "Killer(R) Wi-Fi 6 AX1650s 160MHz Wireless Network Adapter (201NGW)", + .fw_name_pre = IWL_QU_C_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg killer1650i_2ax_cfg_qu_c0_hr_b0 = { + .name = "Killer(R) Wi-Fi 6 AX1650i 160MHz Wireless Network Adapter (201D2W)", + .fw_name_pre = IWL_QU_C_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_qnj_b0_hr_b0_cfg = { + .fw_name_pre = IWL_QNJ_B_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwlax210_2ax_cfg_so_jf_b0 = { + .name = "Intel(R) Wireless-AC 9560 160MHz", + .fw_name_pre = IWL_SO_A_JF_B_FW_PRE, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_NON_HE, +}; + +const struct iwl_cfg iwlax211_2ax_cfg_so_gf_a0 = { + .name = iwl_ax211_name, + .fw_name_pre = IWL_SO_A_GF_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwlax211_2ax_cfg_so_gf_a0_long = { + .name = iwl_ax211_name, + .fw_name_pre = IWL_SO_A_GF_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, + .trans.xtal_latency = 12000, + .trans.low_latency_xtal = true, +}; + +const struct iwl_cfg iwlax210_2ax_cfg_ty_gf_a0 = { + .name = "Intel(R) Wi-Fi 6 AX210 160MHz", + .fw_name_pre = IWL_TY_A_GF_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwlax411_2ax_cfg_so_gf4_a0 = { + .name = iwl_ax411_name, + .fw_name_pre = IWL_SO_A_GF4_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwlax411_2ax_cfg_so_gf4_a0_long = { + .name = iwl_ax411_name, + .fw_name_pre = IWL_SO_A_GF4_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, + .trans.xtal_latency = 12000, + .trans.low_latency_xtal = true, +}; + +const struct iwl_cfg iwlax411_2ax_cfg_sosnj_gf4_a0 = { + .name = iwl_ax411_name, + .fw_name_pre = IWL_SNJ_A_GF4_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwlax211_cfg_snj_gf_a0 = { + .name = iwl_ax211_name, + .fw_name_pre = IWL_SNJ_A_GF_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_snj_hr_b0 = { + .fw_name_pre = IWL_SNJ_A_HR_B_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_snj_a0_jf_b0 = { + .fw_name_pre = IWL_SNJ_A_JF_B_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_ma_a0_hr_b0 = { + .fw_name_pre = IWL_MA_A_HR_B_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_ma_a0_gf_a0 = { + .fw_name_pre = IWL_MA_A_GF_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_ma_a0_gf4_a0 = { + .fw_name_pre = IWL_MA_A_GF4_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_ma_a0_mr_a0 = { + .fw_name_pre = IWL_MA_A_MR_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_ma_a0_ms_a0 = { + .fw_name_pre = IWL_MA_A_MR_A_FW_PRE, + .uhb_supported = false, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_so_a0_ms_a0 = { + .fw_name_pre = IWL_SO_A_MR_A_FW_PRE, + .uhb_supported = false, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_ma_a0_fm_a0 = { + .fw_name_pre = IWL_MA_A_FM_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_snj_a0_mr_a0 = { + .fw_name_pre = IWL_SNJ_A_MR_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_snj_a0_ms_a0 = { + .fw_name_pre = IWL_SNJ_A_MR_A_FW_PRE, + .uhb_supported = false, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_so_a0_hr_a0 = { + .fw_name_pre = IWL_SO_A_HR_B_FW_PRE, + IWL_DEVICE_AX210, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_quz_a0_hr_b0 = { + .fw_name_pre = IWL_QUZ_A_HR_B_FW_PRE, + IWL_DEVICE_22500, + /* + * This device doesn't support receiving BlockAck with a large bitmap + * so we need to restrict the size of transmitted aggregation to the + * HT size; mac80211 would otherwise pick the HE max (256) by default. + */ + .max_tx_agg_size = IEEE80211_MAX_AMPDU_BUF_HT, + .num_rbds = IWL_NUM_RBDS_22000_HE, +}; + +const struct iwl_cfg iwl_cfg_bz_a0_hr_b0 = { + .fw_name_pre = IWL_BZ_A_HR_B_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_bz_a0_gf_a0 = { + .fw_name_pre = IWL_BZ_A_GF_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_bz_a0_gf4_a0 = { + .fw_name_pre = IWL_BZ_A_GF4_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_bz_a0_mr_a0 = { + .fw_name_pre = IWL_BZ_A_MR_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_bz_a0_fm_a0 = { + .fw_name_pre = IWL_BZ_A_FM_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_bz_a0_fm4_a0 = { + .fw_name_pre = IWL_BZ_A_FM4_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_gl_a0_fm_a0 = { + .fw_name_pre = IWL_GL_A_FM_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_gl_b0_fm_b0 = { + .fw_name_pre = IWL_GL_B_FM_B_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_bz_z0_gf_a0 = { + .fw_name_pre = IWL_BZ_Z_GF_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_bnj_a0_fm_a0 = { + .fw_name_pre = IWL_BNJ_A_FM_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_bnj_a0_fm4_a0 = { + .fw_name_pre = IWL_BNJ_A_FM4_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_bnj_a0_gf_a0 = { + .fw_name_pre = IWL_BNJ_A_GF_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_bnj_a0_gf4_a0 = { + .fw_name_pre = IWL_BNJ_A_GF4_A_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_bnj_a0_hr_b0 = { + .fw_name_pre = IWL_BNJ_A_HR_B_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; + +const struct iwl_cfg iwl_cfg_bnj_b0_fm_b0 = { + .fw_name_pre = IWL_BNJ_B_FM_B_FW_PRE, + .uhb_supported = true, + IWL_DEVICE_BZ, + .num_rbds = IWL_NUM_RBDS_AX210_HE, +}; +MODULE_FIRMWARE(IWL_QU_B_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_QNJ_B_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_QU_C_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_QU_B_JF_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_QUZ_A_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_QUZ_A_JF_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_QNJ_B_JF_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_CC_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_SO_A_JF_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_SO_A_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_SO_A_GF_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_TY_A_GF_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_SNJ_A_GF4_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_SNJ_A_GF_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_SNJ_A_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_SNJ_A_JF_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_MA_A_HR_B_FW_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_MA_A_GF_A_FW_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_MA_A_GF4_A_FW_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_MA_A_MR_A_FW_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_MA_A_FM_A_FW_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_SNJ_A_MR_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_BZ_A_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_BZ_A_GF_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_BZ_A_GF4_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_BZ_A_MR_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_BZ_A_FM_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_GL_A_FM_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_BNJ_A_FM_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_BNJ_A_FM4_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_BNJ_A_GF_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_BNJ_A_GF4_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_BNJ_A_HR_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_BZ_A_FM4_A_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_GL_B_FM_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL_BNJ_B_FM_B_MODULE_FIRMWARE(IWL_22000_UCODE_API_MAX)); diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/5000.c b/drivers/net/wireless/intel/iwlwifi/cfg/5000.c new file mode 100644 index 000000000..e2e23d2bc --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/cfg/5000.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2007 - 2014 Intel Corporation. All rights reserved. + * Copyright(c) 2018 - 2020 Intel Corporation + *****************************************************************************/ + +#include <linux/module.h> +#include <linux/stringify.h> +#include "iwl-config.h" +#include "iwl-agn-hw.h" + +/* Highest firmware API version supported */ +#define IWL5000_UCODE_API_MAX 5 +#define IWL5150_UCODE_API_MAX 2 + +/* Lowest firmware API version supported */ +#define IWL5000_UCODE_API_MIN 1 +#define IWL5150_UCODE_API_MIN 1 + +/* EEPROM versions */ +#define EEPROM_5000_TX_POWER_VERSION (4) +#define EEPROM_5000_EEPROM_VERSION (0x11A) +#define EEPROM_5050_TX_POWER_VERSION (4) +#define EEPROM_5050_EEPROM_VERSION (0x21E) + +#define IWL5000_FW_PRE "iwlwifi-5000-" +#define IWL5000_MODULE_FIRMWARE(api) IWL5000_FW_PRE __stringify(api) ".ucode" + +#define IWL5150_FW_PRE "iwlwifi-5150-" +#define IWL5150_MODULE_FIRMWARE(api) IWL5150_FW_PRE __stringify(api) ".ucode" + +static const struct iwl_base_params iwl5000_base_params = { + .eeprom_size = IWLAGN_EEPROM_IMG_SIZE, + .num_of_queues = IWLAGN_NUM_QUEUES, + .max_tfd_queue_size = 256, + .pll_cfg = true, + .led_compensation = 51, + .wd_timeout = IWL_WATCHDOG_DISABLED, + .max_event_log_size = 512, + .scd_chain_ext_wa = true, +}; + +static const struct iwl_ht_params iwl5000_ht_params = { + .ht_greenfield_support = true, + .ht40_bands = BIT(NL80211_BAND_2GHZ) | BIT(NL80211_BAND_5GHZ), +}; + +static const struct iwl_eeprom_params iwl5000_eeprom_params = { + .regulatory_bands = { + EEPROM_REG_BAND_1_CHANNELS, + EEPROM_REG_BAND_2_CHANNELS, + EEPROM_REG_BAND_3_CHANNELS, + EEPROM_REG_BAND_4_CHANNELS, + EEPROM_REG_BAND_5_CHANNELS, + EEPROM_REG_BAND_24_HT40_CHANNELS, + EEPROM_REG_BAND_52_HT40_CHANNELS + }, +}; + +#define IWL_DEVICE_5000 \ + .fw_name_pre = IWL5000_FW_PRE, \ + .ucode_api_max = IWL5000_UCODE_API_MAX, \ + .ucode_api_min = IWL5000_UCODE_API_MIN, \ + .trans.device_family = IWL_DEVICE_FAMILY_5000, \ + .max_inst_size = IWLAGN_RTC_INST_SIZE, \ + .max_data_size = IWLAGN_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_5000_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_5000_TX_POWER_VERSION, \ + .trans.base_params = &iwl5000_base_params, \ + .eeprom_params = &iwl5000_eeprom_params, \ + .led_mode = IWL_LED_BLINK + +const struct iwl_cfg iwl5300_agn_cfg = { + .name = "Intel(R) Ultimate N WiFi Link 5300 AGN", + IWL_DEVICE_5000, + /* at least EEPROM 0x11A has wrong info */ + .valid_tx_ant = ANT_ABC, /* .cfg overwrite */ + .valid_rx_ant = ANT_ABC, /* .cfg overwrite */ + .ht_params = &iwl5000_ht_params, +}; + +const struct iwl_cfg iwl5100_bgn_cfg = { + .name = "Intel(R) WiFi Link 5100 BGN", + IWL_DEVICE_5000, + .valid_tx_ant = ANT_B, /* .cfg overwrite */ + .valid_rx_ant = ANT_AB, /* .cfg overwrite */ + .ht_params = &iwl5000_ht_params, +}; + +const struct iwl_cfg iwl5100_abg_cfg = { + .name = "Intel(R) WiFi Link 5100 ABG", + IWL_DEVICE_5000, + .valid_tx_ant = ANT_B, /* .cfg overwrite */ + .valid_rx_ant = ANT_AB, /* .cfg overwrite */ +}; + +const struct iwl_cfg iwl5100_agn_cfg = { + .name = "Intel(R) WiFi Link 5100 AGN", + IWL_DEVICE_5000, + .valid_tx_ant = ANT_B, /* .cfg overwrite */ + .valid_rx_ant = ANT_AB, /* .cfg overwrite */ + .ht_params = &iwl5000_ht_params, +}; + +const struct iwl_cfg iwl5350_agn_cfg = { + .name = "Intel(R) WiMAX/WiFi Link 5350 AGN", + .fw_name_pre = IWL5000_FW_PRE, + .ucode_api_max = IWL5000_UCODE_API_MAX, + .ucode_api_min = IWL5000_UCODE_API_MIN, + .trans.device_family = IWL_DEVICE_FAMILY_5000, + .max_inst_size = IWLAGN_RTC_INST_SIZE, + .max_data_size = IWLAGN_RTC_DATA_SIZE, + .nvm_ver = EEPROM_5050_EEPROM_VERSION, + .nvm_calib_ver = EEPROM_5050_TX_POWER_VERSION, + .trans.base_params = &iwl5000_base_params, + .eeprom_params = &iwl5000_eeprom_params, + .ht_params = &iwl5000_ht_params, + .led_mode = IWL_LED_BLINK, + .internal_wimax_coex = true, +}; + +#define IWL_DEVICE_5150 \ + .fw_name_pre = IWL5150_FW_PRE, \ + .ucode_api_max = IWL5150_UCODE_API_MAX, \ + .ucode_api_min = IWL5150_UCODE_API_MIN, \ + .trans.device_family = IWL_DEVICE_FAMILY_5150, \ + .max_inst_size = IWLAGN_RTC_INST_SIZE, \ + .max_data_size = IWLAGN_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_5050_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_5050_TX_POWER_VERSION, \ + .trans.base_params = &iwl5000_base_params, \ + .eeprom_params = &iwl5000_eeprom_params, \ + .led_mode = IWL_LED_BLINK, \ + .internal_wimax_coex = true + +const struct iwl_cfg iwl5150_agn_cfg = { + .name = "Intel(R) WiMAX/WiFi Link 5150 AGN", + IWL_DEVICE_5150, + .ht_params = &iwl5000_ht_params, + +}; + +const struct iwl_cfg iwl5150_abg_cfg = { + .name = "Intel(R) WiMAX/WiFi Link 5150 ABG", + IWL_DEVICE_5150, +}; + +MODULE_FIRMWARE(IWL5000_MODULE_FIRMWARE(IWL5000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL5150_MODULE_FIRMWARE(IWL5150_UCODE_API_MAX)); diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/6000.c b/drivers/net/wireless/intel/iwlwifi/cfg/6000.c new file mode 100644 index 000000000..20929e59c --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/cfg/6000.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2008 - 2014 Intel Corporation. All rights reserved. + * Copyright(c) 2018 - 2020 Intel Corporation + *****************************************************************************/ + +#include <linux/module.h> +#include <linux/stringify.h> +#include "iwl-config.h" +#include "iwl-agn-hw.h" +#include "dvm/commands.h" /* needed for BT for now */ + +/* Highest firmware API version supported */ +#define IWL6000_UCODE_API_MAX 6 +#define IWL6050_UCODE_API_MAX 5 +#define IWL6000G2_UCODE_API_MAX 6 +#define IWL6035_UCODE_API_MAX 6 + +/* Lowest firmware API version supported */ +#define IWL6000_UCODE_API_MIN 4 +#define IWL6050_UCODE_API_MIN 4 +#define IWL6000G2_UCODE_API_MIN 5 +#define IWL6035_UCODE_API_MIN 6 + +/* EEPROM versions */ +#define EEPROM_6000_TX_POWER_VERSION (4) +#define EEPROM_6000_EEPROM_VERSION (0x423) +#define EEPROM_6050_TX_POWER_VERSION (4) +#define EEPROM_6050_EEPROM_VERSION (0x532) +#define EEPROM_6150_TX_POWER_VERSION (6) +#define EEPROM_6150_EEPROM_VERSION (0x553) +#define EEPROM_6005_TX_POWER_VERSION (6) +#define EEPROM_6005_EEPROM_VERSION (0x709) +#define EEPROM_6030_TX_POWER_VERSION (6) +#define EEPROM_6030_EEPROM_VERSION (0x709) +#define EEPROM_6035_TX_POWER_VERSION (6) +#define EEPROM_6035_EEPROM_VERSION (0x753) + +#define IWL6000_FW_PRE "iwlwifi-6000-" +#define IWL6000_MODULE_FIRMWARE(api) IWL6000_FW_PRE __stringify(api) ".ucode" + +#define IWL6050_FW_PRE "iwlwifi-6050-" +#define IWL6050_MODULE_FIRMWARE(api) IWL6050_FW_PRE __stringify(api) ".ucode" + +#define IWL6005_FW_PRE "iwlwifi-6000g2a-" +#define IWL6005_MODULE_FIRMWARE(api) IWL6005_FW_PRE __stringify(api) ".ucode" + +#define IWL6030_FW_PRE "iwlwifi-6000g2b-" +#define IWL6030_MODULE_FIRMWARE(api) IWL6030_FW_PRE __stringify(api) ".ucode" + +static const struct iwl_base_params iwl6000_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE_2K, + .num_of_queues = IWLAGN_NUM_QUEUES, + .max_tfd_queue_size = 256, + .max_ll_items = OTP_MAX_LL_ITEMS_6x00, + .shadow_ram_support = true, + .led_compensation = 51, + .wd_timeout = IWL_DEF_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = false, /* TODO: fix bugs using this feature */ + .scd_chain_ext_wa = true, +}; + +static const struct iwl_base_params iwl6050_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE_2K, + .num_of_queues = IWLAGN_NUM_QUEUES, + .max_tfd_queue_size = 256, + .max_ll_items = OTP_MAX_LL_ITEMS_6x50, + .shadow_ram_support = true, + .led_compensation = 51, + .wd_timeout = IWL_DEF_WD_TIMEOUT, + .max_event_log_size = 1024, + .shadow_reg_enable = false, /* TODO: fix bugs using this feature */ + .scd_chain_ext_wa = true, +}; + +static const struct iwl_base_params iwl6000_g2_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE_2K, + .num_of_queues = IWLAGN_NUM_QUEUES, + .max_tfd_queue_size = 256, + .max_ll_items = OTP_MAX_LL_ITEMS_6x00, + .shadow_ram_support = true, + .led_compensation = 57, + .wd_timeout = IWL_LONG_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = false, /* TODO: fix bugs using this feature */ + .scd_chain_ext_wa = true, +}; + +static const struct iwl_ht_params iwl6000_ht_params = { + .ht_greenfield_support = true, + .use_rts_for_aggregation = true, /* use rts/cts protection */ + .ht40_bands = BIT(NL80211_BAND_2GHZ) | BIT(NL80211_BAND_5GHZ), +}; + +static const struct iwl_eeprom_params iwl6000_eeprom_params = { + .regulatory_bands = { + EEPROM_REG_BAND_1_CHANNELS, + EEPROM_REG_BAND_2_CHANNELS, + EEPROM_REG_BAND_3_CHANNELS, + EEPROM_REG_BAND_4_CHANNELS, + EEPROM_REG_BAND_5_CHANNELS, + EEPROM_6000_REG_BAND_24_HT40_CHANNELS, + EEPROM_REG_BAND_52_HT40_CHANNELS + }, + .enhanced_txpower = true, +}; + +#define IWL_DEVICE_6005 \ + .fw_name_pre = IWL6005_FW_PRE, \ + .ucode_api_max = IWL6000G2_UCODE_API_MAX, \ + .ucode_api_min = IWL6000G2_UCODE_API_MIN, \ + .trans.device_family = IWL_DEVICE_FAMILY_6005, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_6005_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_6005_TX_POWER_VERSION, \ + .trans.base_params = &iwl6000_g2_base_params, \ + .eeprom_params = &iwl6000_eeprom_params, \ + .led_mode = IWL_LED_RF_STATE + +const struct iwl_cfg iwl6005_2agn_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6205 AGN", + IWL_DEVICE_6005, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6005_2abg_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6205 ABG", + IWL_DEVICE_6005, +}; + +const struct iwl_cfg iwl6005_2bg_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6205 BG", + IWL_DEVICE_6005, +}; + +const struct iwl_cfg iwl6005_2agn_sff_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6205S AGN", + IWL_DEVICE_6005, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6005_2agn_d_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6205D AGN", + IWL_DEVICE_6005, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6005_2agn_mow1_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6206 AGN", + IWL_DEVICE_6005, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6005_2agn_mow2_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6207 AGN", + IWL_DEVICE_6005, + .ht_params = &iwl6000_ht_params, +}; + +#define IWL_DEVICE_6030 \ + .fw_name_pre = IWL6030_FW_PRE, \ + .ucode_api_max = IWL6000G2_UCODE_API_MAX, \ + .ucode_api_min = IWL6000G2_UCODE_API_MIN, \ + .trans.device_family = IWL_DEVICE_FAMILY_6030, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_6030_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_6030_TX_POWER_VERSION, \ + .trans.base_params = &iwl6000_g2_base_params, \ + .eeprom_params = &iwl6000_eeprom_params, \ + .led_mode = IWL_LED_RF_STATE + +const struct iwl_cfg iwl6030_2agn_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6230 AGN", + IWL_DEVICE_6030, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6030_2abg_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6230 ABG", + IWL_DEVICE_6030, +}; + +const struct iwl_cfg iwl6030_2bgn_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6230 BGN", + IWL_DEVICE_6030, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6030_2bg_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6230 BG", + IWL_DEVICE_6030, +}; + +#define IWL_DEVICE_6035 \ + .fw_name_pre = IWL6030_FW_PRE, \ + .ucode_api_max = IWL6035_UCODE_API_MAX, \ + .ucode_api_min = IWL6035_UCODE_API_MIN, \ + .trans.device_family = IWL_DEVICE_FAMILY_6030, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_6030_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_6030_TX_POWER_VERSION, \ + .trans.base_params = &iwl6000_g2_base_params, \ + .eeprom_params = &iwl6000_eeprom_params, \ + .led_mode = IWL_LED_RF_STATE + +const struct iwl_cfg iwl6035_2agn_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6235 AGN", + IWL_DEVICE_6035, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6035_2agn_sff_cfg = { + .name = "Intel(R) Centrino(R) Ultimate-N 6235 AGN", + IWL_DEVICE_6035, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl1030_bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 1030 BGN", + IWL_DEVICE_6030, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl1030_bg_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 1030 BG", + IWL_DEVICE_6030, +}; + +const struct iwl_cfg iwl130_bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 130 BGN", + IWL_DEVICE_6030, + .ht_params = &iwl6000_ht_params, + .rx_with_siso_diversity = true, +}; + +const struct iwl_cfg iwl130_bg_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 130 BG", + IWL_DEVICE_6030, + .rx_with_siso_diversity = true, +}; + +/* + * "i": Internal configuration, use internal Power Amplifier + */ +#define IWL_DEVICE_6000i \ + .fw_name_pre = IWL6000_FW_PRE, \ + .ucode_api_max = IWL6000_UCODE_API_MAX, \ + .ucode_api_min = IWL6000_UCODE_API_MIN, \ + .trans.device_family = IWL_DEVICE_FAMILY_6000i, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .valid_tx_ant = ANT_BC, /* .cfg overwrite */ \ + .valid_rx_ant = ANT_BC, /* .cfg overwrite */ \ + .nvm_ver = EEPROM_6000_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_6000_TX_POWER_VERSION, \ + .trans.base_params = &iwl6000_base_params, \ + .eeprom_params = &iwl6000_eeprom_params, \ + .led_mode = IWL_LED_BLINK + +const struct iwl_cfg iwl6000i_2agn_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6200 AGN", + IWL_DEVICE_6000i, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6000i_2abg_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6200 ABG", + IWL_DEVICE_6000i, +}; + +const struct iwl_cfg iwl6000i_2bg_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6200 BG", + IWL_DEVICE_6000i, +}; + +#define IWL_DEVICE_6050 \ + .fw_name_pre = IWL6050_FW_PRE, \ + .ucode_api_max = IWL6050_UCODE_API_MAX, \ + .ucode_api_min = IWL6050_UCODE_API_MIN, \ + .trans.device_family = IWL_DEVICE_FAMILY_6050, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .valid_tx_ant = ANT_AB, /* .cfg overwrite */ \ + .valid_rx_ant = ANT_AB, /* .cfg overwrite */ \ + .nvm_ver = EEPROM_6050_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_6050_TX_POWER_VERSION, \ + .trans.base_params = &iwl6050_base_params, \ + .eeprom_params = &iwl6000_eeprom_params, \ + .led_mode = IWL_LED_BLINK, \ + .internal_wimax_coex = true + +const struct iwl_cfg iwl6050_2agn_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N + WiMAX 6250 AGN", + IWL_DEVICE_6050, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6050_2abg_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N + WiMAX 6250 ABG", + IWL_DEVICE_6050, +}; + +#define IWL_DEVICE_6150 \ + .fw_name_pre = IWL6050_FW_PRE, \ + .ucode_api_max = IWL6050_UCODE_API_MAX, \ + .ucode_api_min = IWL6050_UCODE_API_MIN, \ + .trans.device_family = IWL_DEVICE_FAMILY_6150, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_6150_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_6150_TX_POWER_VERSION, \ + .trans.base_params = &iwl6050_base_params, \ + .eeprom_params = &iwl6000_eeprom_params, \ + .led_mode = IWL_LED_BLINK, \ + .internal_wimax_coex = true + +const struct iwl_cfg iwl6150_bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N + WiMAX 6150 BGN", + IWL_DEVICE_6150, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6150_bg_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N + WiMAX 6150 BG", + IWL_DEVICE_6150, +}; + +const struct iwl_cfg iwl6000_3agn_cfg = { + .name = "Intel(R) Centrino(R) Ultimate-N 6300 AGN", + .fw_name_pre = IWL6000_FW_PRE, + .ucode_api_max = IWL6000_UCODE_API_MAX, + .ucode_api_min = IWL6000_UCODE_API_MIN, + .trans.device_family = IWL_DEVICE_FAMILY_6000, + .max_inst_size = IWL60_RTC_INST_SIZE, + .max_data_size = IWL60_RTC_DATA_SIZE, + .nvm_ver = EEPROM_6000_EEPROM_VERSION, + .nvm_calib_ver = EEPROM_6000_TX_POWER_VERSION, + .trans.base_params = &iwl6000_base_params, + .eeprom_params = &iwl6000_eeprom_params, + .ht_params = &iwl6000_ht_params, + .led_mode = IWL_LED_BLINK, +}; + +MODULE_FIRMWARE(IWL6000_MODULE_FIRMWARE(IWL6000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL6050_MODULE_FIRMWARE(IWL6050_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL6005_MODULE_FIRMWARE(IWL6000G2_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL6030_MODULE_FIRMWARE(IWL6000G2_UCODE_API_MAX)); diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/7000.c b/drivers/net/wireless/intel/iwlwifi/cfg/7000.c new file mode 100644 index 000000000..b24dc5523 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/cfg/7000.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2020 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015 Intel Deutschland GmbH + */ +#include <linux/module.h> +#include <linux/stringify.h> +#include "iwl-config.h" + +/* Highest firmware API version supported */ +#define IWL7260_UCODE_API_MAX 17 +#define IWL7265_UCODE_API_MAX 17 +#define IWL7265D_UCODE_API_MAX 29 +#define IWL3168_UCODE_API_MAX 29 + +/* Lowest firmware API version supported */ +#define IWL7260_UCODE_API_MIN 17 +#define IWL7265_UCODE_API_MIN 17 +#define IWL7265D_UCODE_API_MIN 22 +#define IWL3168_UCODE_API_MIN 22 + +/* NVM versions */ +#define IWL7260_NVM_VERSION 0x0a1d +#define IWL3160_NVM_VERSION 0x709 +#define IWL3165_NVM_VERSION 0x709 +#define IWL3168_NVM_VERSION 0xd01 +#define IWL7265_NVM_VERSION 0x0a1d +#define IWL7265D_NVM_VERSION 0x0c11 + +/* DCCM offsets and lengths */ +#define IWL7000_DCCM_OFFSET 0x800000 +#define IWL7260_DCCM_LEN 0x14000 +#define IWL3160_DCCM_LEN 0x10000 +#define IWL7265_DCCM_LEN 0x17A00 + +#define IWL7260_FW_PRE "iwlwifi-7260-" +#define IWL7260_MODULE_FIRMWARE(api) IWL7260_FW_PRE __stringify(api) ".ucode" + +#define IWL3160_FW_PRE "iwlwifi-3160-" +#define IWL3160_MODULE_FIRMWARE(api) IWL3160_FW_PRE __stringify(api) ".ucode" + +#define IWL3168_FW_PRE "iwlwifi-3168-" +#define IWL3168_MODULE_FIRMWARE(api) IWL3168_FW_PRE __stringify(api) ".ucode" + +#define IWL7265_FW_PRE "iwlwifi-7265-" +#define IWL7265_MODULE_FIRMWARE(api) IWL7265_FW_PRE __stringify(api) ".ucode" + +#define IWL7265D_FW_PRE "iwlwifi-7265D-" +#define IWL7265D_MODULE_FIRMWARE(api) IWL7265D_FW_PRE __stringify(api) ".ucode" + +static const struct iwl_base_params iwl7000_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE_16K, + .num_of_queues = 31, + .max_tfd_queue_size = 256, + .shadow_ram_support = true, + .led_compensation = 57, + .wd_timeout = IWL_LONG_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = true, + .pcie_l1_allowed = true, + .apmg_wake_up_wa = true, +}; + +static const struct iwl_tt_params iwl7000_high_temp_tt_params = { + .ct_kill_entry = 118, + .ct_kill_exit = 96, + .ct_kill_duration = 5, + .dynamic_smps_entry = 114, + .dynamic_smps_exit = 110, + .tx_protection_entry = 114, + .tx_protection_exit = 108, + .tx_backoff = { + {.temperature = 112, .backoff = 300}, + {.temperature = 113, .backoff = 800}, + {.temperature = 114, .backoff = 1500}, + {.temperature = 115, .backoff = 3000}, + {.temperature = 116, .backoff = 5000}, + {.temperature = 117, .backoff = 10000}, + }, + .support_ct_kill = true, + .support_dynamic_smps = true, + .support_tx_protection = true, + .support_tx_backoff = true, +}; + +static const struct iwl_ht_params iwl7000_ht_params = { + .stbc = true, + .ht40_bands = BIT(NL80211_BAND_2GHZ) | BIT(NL80211_BAND_5GHZ), +}; + +#define IWL_DEVICE_7000_COMMON \ + .trans.device_family = IWL_DEVICE_FAMILY_7000, \ + .trans.base_params = &iwl7000_base_params, \ + .led_mode = IWL_LED_RF_STATE, \ + .nvm_hw_section_num = 0, \ + .non_shared_ant = ANT_A, \ + .dccm_offset = IWL7000_DCCM_OFFSET + +#define IWL_DEVICE_7000 \ + IWL_DEVICE_7000_COMMON, \ + .ucode_api_max = IWL7260_UCODE_API_MAX, \ + .ucode_api_min = IWL7260_UCODE_API_MIN + +#define IWL_DEVICE_7005 \ + IWL_DEVICE_7000_COMMON, \ + .ucode_api_max = IWL7265_UCODE_API_MAX, \ + .ucode_api_min = IWL7265_UCODE_API_MIN + +#define IWL_DEVICE_3008 \ + IWL_DEVICE_7000_COMMON, \ + .ucode_api_max = IWL3168_UCODE_API_MAX, \ + .ucode_api_min = IWL3168_UCODE_API_MIN + +#define IWL_DEVICE_7005D \ + IWL_DEVICE_7000_COMMON, \ + .ucode_api_max = IWL7265D_UCODE_API_MAX, \ + .ucode_api_min = IWL7265D_UCODE_API_MIN + +const struct iwl_cfg iwl7260_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 7260", + .fw_name_pre = IWL7260_FW_PRE, + IWL_DEVICE_7000, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL7260_NVM_VERSION, + .host_interrupt_operation_mode = true, + .lp_xtal_workaround = true, + .dccm_len = IWL7260_DCCM_LEN, +}; + +const struct iwl_cfg iwl7260_2ac_cfg_high_temp = { + .name = "Intel(R) Dual Band Wireless AC 7260", + .fw_name_pre = IWL7260_FW_PRE, + IWL_DEVICE_7000, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL7260_NVM_VERSION, + .high_temp = true, + .host_interrupt_operation_mode = true, + .lp_xtal_workaround = true, + .dccm_len = IWL7260_DCCM_LEN, + .thermal_params = &iwl7000_high_temp_tt_params, +}; + +const struct iwl_cfg iwl7260_2n_cfg = { + .name = "Intel(R) Dual Band Wireless N 7260", + .fw_name_pre = IWL7260_FW_PRE, + IWL_DEVICE_7000, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL7260_NVM_VERSION, + .host_interrupt_operation_mode = true, + .lp_xtal_workaround = true, + .dccm_len = IWL7260_DCCM_LEN, +}; + +const struct iwl_cfg iwl7260_n_cfg = { + .name = "Intel(R) Wireless N 7260", + .fw_name_pre = IWL7260_FW_PRE, + IWL_DEVICE_7000, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL7260_NVM_VERSION, + .host_interrupt_operation_mode = true, + .lp_xtal_workaround = true, + .dccm_len = IWL7260_DCCM_LEN, +}; + +const struct iwl_cfg iwl3160_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 3160", + .fw_name_pre = IWL3160_FW_PRE, + IWL_DEVICE_7000, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL3160_NVM_VERSION, + .host_interrupt_operation_mode = true, + .dccm_len = IWL3160_DCCM_LEN, +}; + +const struct iwl_cfg iwl3160_2n_cfg = { + .name = "Intel(R) Dual Band Wireless N 3160", + .fw_name_pre = IWL3160_FW_PRE, + IWL_DEVICE_7000, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL3160_NVM_VERSION, + .host_interrupt_operation_mode = true, + .dccm_len = IWL3160_DCCM_LEN, +}; + +const struct iwl_cfg iwl3160_n_cfg = { + .name = "Intel(R) Wireless N 3160", + .fw_name_pre = IWL3160_FW_PRE, + IWL_DEVICE_7000, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL3160_NVM_VERSION, + .host_interrupt_operation_mode = true, + .dccm_len = IWL3160_DCCM_LEN, +}; + +static const struct iwl_pwr_tx_backoff iwl7265_pwr_tx_backoffs[] = { + {.pwr = 1600, .backoff = 0}, + {.pwr = 1300, .backoff = 467}, + {.pwr = 900, .backoff = 1900}, + {.pwr = 800, .backoff = 2630}, + {.pwr = 700, .backoff = 3720}, + {.pwr = 600, .backoff = 5550}, + {.pwr = 500, .backoff = 9350}, + {0}, +}; + +static const struct iwl_ht_params iwl7265_ht_params = { + .stbc = true, + .ldpc = true, + .ht40_bands = BIT(NL80211_BAND_2GHZ) | BIT(NL80211_BAND_5GHZ), +}; + +const struct iwl_cfg iwl3165_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 3165", + .fw_name_pre = IWL7265D_FW_PRE, + IWL_DEVICE_7005D, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL3165_NVM_VERSION, + .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs, + .dccm_len = IWL7265_DCCM_LEN, +}; + +const struct iwl_cfg iwl3168_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 3168", + .fw_name_pre = IWL3168_FW_PRE, + IWL_DEVICE_3008, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL3168_NVM_VERSION, + .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs, + .dccm_len = IWL7265_DCCM_LEN, + .nvm_type = IWL_NVM_SDP, +}; + +const struct iwl_cfg iwl7265_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 7265", + .fw_name_pre = IWL7265_FW_PRE, + IWL_DEVICE_7005, + .ht_params = &iwl7265_ht_params, + .nvm_ver = IWL7265_NVM_VERSION, + .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs, + .dccm_len = IWL7265_DCCM_LEN, +}; + +const struct iwl_cfg iwl7265_2n_cfg = { + .name = "Intel(R) Dual Band Wireless N 7265", + .fw_name_pre = IWL7265_FW_PRE, + IWL_DEVICE_7005, + .ht_params = &iwl7265_ht_params, + .nvm_ver = IWL7265_NVM_VERSION, + .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs, + .dccm_len = IWL7265_DCCM_LEN, +}; + +const struct iwl_cfg iwl7265_n_cfg = { + .name = "Intel(R) Wireless N 7265", + .fw_name_pre = IWL7265_FW_PRE, + IWL_DEVICE_7005, + .ht_params = &iwl7265_ht_params, + .nvm_ver = IWL7265_NVM_VERSION, + .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs, + .dccm_len = IWL7265_DCCM_LEN, +}; + +const struct iwl_cfg iwl7265d_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 7265", + .fw_name_pre = IWL7265D_FW_PRE, + IWL_DEVICE_7005D, + .ht_params = &iwl7265_ht_params, + .nvm_ver = IWL7265D_NVM_VERSION, + .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs, + .dccm_len = IWL7265_DCCM_LEN, +}; + +const struct iwl_cfg iwl7265d_2n_cfg = { + .name = "Intel(R) Dual Band Wireless N 7265", + .fw_name_pre = IWL7265D_FW_PRE, + IWL_DEVICE_7005D, + .ht_params = &iwl7265_ht_params, + .nvm_ver = IWL7265D_NVM_VERSION, + .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs, + .dccm_len = IWL7265_DCCM_LEN, +}; + +const struct iwl_cfg iwl7265d_n_cfg = { + .name = "Intel(R) Wireless N 7265", + .fw_name_pre = IWL7265D_FW_PRE, + IWL_DEVICE_7005D, + .ht_params = &iwl7265_ht_params, + .nvm_ver = IWL7265D_NVM_VERSION, + .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs, + .dccm_len = IWL7265_DCCM_LEN, +}; + +MODULE_FIRMWARE(IWL7260_MODULE_FIRMWARE(IWL7260_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL3160_MODULE_FIRMWARE(IWL7260_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL3168_MODULE_FIRMWARE(IWL3168_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL7265_MODULE_FIRMWARE(IWL7265_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL7265D_MODULE_FIRMWARE(IWL7265D_UCODE_API_MAX)); diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/8000.c b/drivers/net/wireless/intel/iwlwifi/cfg/8000.c new file mode 100644 index 000000000..a6454287d --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/cfg/8000.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2014, 2018-2020 Intel Corporation + * Copyright (C) 2014-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016 Intel Deutschland GmbH + */ +#include <linux/module.h> +#include <linux/stringify.h> +#include "iwl-config.h" + +/* Highest firmware API version supported */ +#define IWL8000_UCODE_API_MAX 36 +#define IWL8265_UCODE_API_MAX 36 + +/* Lowest firmware API version supported */ +#define IWL8000_UCODE_API_MIN 22 +#define IWL8265_UCODE_API_MIN 22 + +/* NVM versions */ +#define IWL8000_NVM_VERSION 0x0a1d + +/* Memory offsets and lengths */ +#define IWL8260_DCCM_OFFSET 0x800000 +#define IWL8260_DCCM_LEN 0x18000 +#define IWL8260_DCCM2_OFFSET 0x880000 +#define IWL8260_DCCM2_LEN 0x8000 +#define IWL8260_SMEM_OFFSET 0x400000 +#define IWL8260_SMEM_LEN 0x68000 + +#define IWL8000_FW_PRE "iwlwifi-8000C-" +#define IWL8000_MODULE_FIRMWARE(api) \ + IWL8000_FW_PRE __stringify(api) ".ucode" + +#define IWL8265_FW_PRE "iwlwifi-8265-" +#define IWL8265_MODULE_FIRMWARE(api) \ + IWL8265_FW_PRE __stringify(api) ".ucode" + +#define DEFAULT_NVM_FILE_FAMILY_8000C "nvmData-8000C" + +static const struct iwl_base_params iwl8000_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE_32K, + .num_of_queues = 31, + .max_tfd_queue_size = 256, + .shadow_ram_support = true, + .led_compensation = 57, + .wd_timeout = IWL_LONG_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = true, + .pcie_l1_allowed = true, +}; + +static const struct iwl_ht_params iwl8000_ht_params = { + .stbc = true, + .ldpc = true, + .ht40_bands = BIT(NL80211_BAND_2GHZ) | BIT(NL80211_BAND_5GHZ), +}; + +static const struct iwl_tt_params iwl8000_tt_params = { + .ct_kill_entry = 115, + .ct_kill_exit = 93, + .ct_kill_duration = 5, + .dynamic_smps_entry = 111, + .dynamic_smps_exit = 107, + .tx_protection_entry = 112, + .tx_protection_exit = 105, + .tx_backoff = { + {.temperature = 110, .backoff = 200}, + {.temperature = 111, .backoff = 600}, + {.temperature = 112, .backoff = 1200}, + {.temperature = 113, .backoff = 2000}, + {.temperature = 114, .backoff = 4000}, + }, + .support_ct_kill = true, + .support_dynamic_smps = true, + .support_tx_protection = true, + .support_tx_backoff = true, +}; + +#define IWL_DEVICE_8000_COMMON \ + .trans.device_family = IWL_DEVICE_FAMILY_8000, \ + .trans.base_params = &iwl8000_base_params, \ + .led_mode = IWL_LED_RF_STATE, \ + .nvm_hw_section_num = 10, \ + .features = NETIF_F_RXCSUM, \ + .non_shared_ant = ANT_A, \ + .dccm_offset = IWL8260_DCCM_OFFSET, \ + .dccm_len = IWL8260_DCCM_LEN, \ + .dccm2_offset = IWL8260_DCCM2_OFFSET, \ + .dccm2_len = IWL8260_DCCM2_LEN, \ + .smem_offset = IWL8260_SMEM_OFFSET, \ + .smem_len = IWL8260_SMEM_LEN, \ + .default_nvm_file_C_step = DEFAULT_NVM_FILE_FAMILY_8000C, \ + .thermal_params = &iwl8000_tt_params, \ + .apmg_not_supported = true, \ + .nvm_type = IWL_NVM_EXT, \ + .dbgc_supported = true, \ + .min_umac_error_event_table = 0x800000 + +#define IWL_DEVICE_8000 \ + IWL_DEVICE_8000_COMMON, \ + .ucode_api_max = IWL8000_UCODE_API_MAX, \ + .ucode_api_min = IWL8000_UCODE_API_MIN \ + +#define IWL_DEVICE_8260 \ + IWL_DEVICE_8000_COMMON, \ + .ucode_api_max = IWL8000_UCODE_API_MAX, \ + .ucode_api_min = IWL8000_UCODE_API_MIN \ + +#define IWL_DEVICE_8265 \ + IWL_DEVICE_8000_COMMON, \ + .ucode_api_max = IWL8265_UCODE_API_MAX, \ + .ucode_api_min = IWL8265_UCODE_API_MIN \ + +const struct iwl_cfg iwl8260_2n_cfg = { + .name = "Intel(R) Dual Band Wireless N 8260", + .fw_name_pre = IWL8000_FW_PRE, + IWL_DEVICE_8260, + .ht_params = &iwl8000_ht_params, + .nvm_ver = IWL8000_NVM_VERSION, +}; + +const struct iwl_cfg iwl8260_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 8260", + .fw_name_pre = IWL8000_FW_PRE, + IWL_DEVICE_8260, + .ht_params = &iwl8000_ht_params, + .nvm_ver = IWL8000_NVM_VERSION, +}; + +const struct iwl_cfg iwl8265_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 8265", + .fw_name_pre = IWL8265_FW_PRE, + IWL_DEVICE_8265, + .ht_params = &iwl8000_ht_params, + .nvm_ver = IWL8000_NVM_VERSION, + .vht_mu_mimo_supported = true, +}; + +const struct iwl_cfg iwl8275_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 8275", + .fw_name_pre = IWL8265_FW_PRE, + IWL_DEVICE_8265, + .ht_params = &iwl8000_ht_params, + .nvm_ver = IWL8000_NVM_VERSION, + .vht_mu_mimo_supported = true, +}; + +const struct iwl_cfg iwl4165_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC 4165", + .fw_name_pre = IWL8000_FW_PRE, + IWL_DEVICE_8000, + .ht_params = &iwl8000_ht_params, + .nvm_ver = IWL8000_NVM_VERSION, +}; + +MODULE_FIRMWARE(IWL8000_MODULE_FIRMWARE(IWL8000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL8265_MODULE_FIRMWARE(IWL8265_UCODE_API_MAX)); diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/9000.c b/drivers/net/wireless/intel/iwlwifi/cfg/9000.c new file mode 100644 index 000000000..7a7ca06d4 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/cfg/9000.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2015-2017 Intel Deutschland GmbH + * Copyright (C) 2018-2021 Intel Corporation + */ +#include <linux/module.h> +#include <linux/stringify.h> +#include "iwl-config.h" +#include "fw/file.h" +#include "iwl-prph.h" + +/* Highest firmware API version supported */ +#define IWL9000_UCODE_API_MAX 46 + +/* Lowest firmware API version supported */ +#define IWL9000_UCODE_API_MIN 30 + +/* NVM versions */ +#define IWL9000_NVM_VERSION 0x0a1d + +/* Memory offsets and lengths */ +#define IWL9000_DCCM_OFFSET 0x800000 +#define IWL9000_DCCM_LEN 0x18000 +#define IWL9000_DCCM2_OFFSET 0x880000 +#define IWL9000_DCCM2_LEN 0x8000 +#define IWL9000_SMEM_OFFSET 0x400000 +#define IWL9000_SMEM_LEN 0x68000 + +#define IWL9000_FW_PRE "iwlwifi-9000-pu-b0-jf-b0-" +#define IWL9260_FW_PRE "iwlwifi-9260-th-b0-jf-b0-" +#define IWL9000_MODULE_FIRMWARE(api) \ + IWL9000_FW_PRE __stringify(api) ".ucode" +#define IWL9260_MODULE_FIRMWARE(api) \ + IWL9260_FW_PRE __stringify(api) ".ucode" + +static const struct iwl_base_params iwl9000_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE_32K, + .num_of_queues = 31, + .max_tfd_queue_size = 256, + .shadow_ram_support = true, + .led_compensation = 57, + .wd_timeout = IWL_LONG_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = true, + .pcie_l1_allowed = true, +}; + +static const struct iwl_ht_params iwl9000_ht_params = { + .stbc = true, + .ldpc = true, + .ht40_bands = BIT(NL80211_BAND_2GHZ) | BIT(NL80211_BAND_5GHZ), +}; + +static const struct iwl_tt_params iwl9000_tt_params = { + .ct_kill_entry = 115, + .ct_kill_exit = 93, + .ct_kill_duration = 5, + .dynamic_smps_entry = 111, + .dynamic_smps_exit = 107, + .tx_protection_entry = 112, + .tx_protection_exit = 105, + .tx_backoff = { + {.temperature = 110, .backoff = 200}, + {.temperature = 111, .backoff = 600}, + {.temperature = 112, .backoff = 1200}, + {.temperature = 113, .backoff = 2000}, + {.temperature = 114, .backoff = 4000}, + }, + .support_ct_kill = true, + .support_dynamic_smps = true, + .support_tx_protection = true, + .support_tx_backoff = true, +}; + +#define IWL_DEVICE_9000 \ + .ucode_api_max = IWL9000_UCODE_API_MAX, \ + .ucode_api_min = IWL9000_UCODE_API_MIN, \ + .led_mode = IWL_LED_RF_STATE, \ + .nvm_hw_section_num = 10, \ + .non_shared_ant = ANT_B, \ + .dccm_offset = IWL9000_DCCM_OFFSET, \ + .dccm_len = IWL9000_DCCM_LEN, \ + .dccm2_offset = IWL9000_DCCM2_OFFSET, \ + .dccm2_len = IWL9000_DCCM2_LEN, \ + .smem_offset = IWL9000_SMEM_OFFSET, \ + .smem_len = IWL9000_SMEM_LEN, \ + .features = IWL_TX_CSUM_NETIF_FLAGS | NETIF_F_RXCSUM, \ + .thermal_params = &iwl9000_tt_params, \ + .apmg_not_supported = true, \ + .num_rbds = 512, \ + .vht_mu_mimo_supported = true, \ + .mac_addr_from_csr = 0x380, \ + .nvm_type = IWL_NVM_EXT, \ + .dbgc_supported = true, \ + .min_umac_error_event_table = 0x800000, \ + .d3_debug_data_base_addr = 0x401000, \ + .d3_debug_data_length = 92 * 1024, \ + .ht_params = &iwl9000_ht_params, \ + .nvm_ver = IWL9000_NVM_VERSION, \ + .mon_smem_regs = { \ + .write_ptr = { \ + .addr = LDBG_M2S_BUF_WPTR, \ + .mask = LDBG_M2S_BUF_WPTR_VAL_MSK, \ + }, \ + .cycle_cnt = { \ + .addr = LDBG_M2S_BUF_WRAP_CNT, \ + .mask = LDBG_M2S_BUF_WRAP_CNT_VAL_MSK, \ + }, \ + }, \ + .mon_dram_regs = { \ + .write_ptr = { \ + .addr = MON_BUFF_WRPTR_VER2, \ + .mask = 0xffffffff, \ + }, \ + .cycle_cnt = { \ + .addr = MON_BUFF_CYCLE_CNT_VER2, \ + .mask = 0xffffffff, \ + }, \ + } + +const struct iwl_cfg_trans_params iwl9000_trans_cfg = { + .device_family = IWL_DEVICE_FAMILY_9000, + .base_params = &iwl9000_base_params, + .mq_rx_supported = true, + .rf_id = true, +}; + +const struct iwl_cfg_trans_params iwl9560_trans_cfg = { + .device_family = IWL_DEVICE_FAMILY_9000, + .base_params = &iwl9000_base_params, + .mq_rx_supported = true, + .rf_id = true, + .integrated = true, + .xtal_latency = 650, +}; + +const struct iwl_cfg_trans_params iwl9560_long_latency_trans_cfg = { + .device_family = IWL_DEVICE_FAMILY_9000, + .base_params = &iwl9000_base_params, + .mq_rx_supported = true, + .rf_id = true, + .integrated = true, + .xtal_latency = 2820, +}; + +const struct iwl_cfg_trans_params iwl9560_shared_clk_trans_cfg = { + .device_family = IWL_DEVICE_FAMILY_9000, + .base_params = &iwl9000_base_params, + .mq_rx_supported = true, + .rf_id = true, + .integrated = true, + .xtal_latency = 670, + .extra_phy_cfg_flags = FW_PHY_CFG_SHARED_CLK +}; + +const char iwl9162_name[] = "Intel(R) Wireless-AC 9162"; +const char iwl9260_name[] = "Intel(R) Wireless-AC 9260"; +const char iwl9260_1_name[] = "Intel(R) Wireless-AC 9260-1"; +const char iwl9270_name[] = "Intel(R) Wireless-AC 9270"; +const char iwl9461_name[] = "Intel(R) Wireless-AC 9461"; +const char iwl9462_name[] = "Intel(R) Wireless-AC 9462"; +const char iwl9560_name[] = "Intel(R) Wireless-AC 9560"; +const char iwl9162_160_name[] = "Intel(R) Wireless-AC 9162 160MHz"; +const char iwl9260_160_name[] = "Intel(R) Wireless-AC 9260 160MHz"; +const char iwl9270_160_name[] = "Intel(R) Wireless-AC 9270 160MHz"; +const char iwl9461_160_name[] = "Intel(R) Wireless-AC 9461 160MHz"; +const char iwl9462_160_name[] = "Intel(R) Wireless-AC 9462 160MHz"; +const char iwl9560_160_name[] = "Intel(R) Wireless-AC 9560 160MHz"; + +const char iwl9260_killer_1550_name[] = + "Killer (R) Wireless-AC 1550 Wireless Network Adapter (9260NGW) 160MHz"; +const char iwl9560_killer_1550i_name[] = + "Killer (R) Wireless-AC 1550i Wireless Network Adapter (9560NGW)"; +const char iwl9560_killer_1550i_160_name[] = + "Killer(R) Wireless-AC 1550i Wireless Network Adapter (9560NGW) 160MHz"; +const char iwl9560_killer_1550s_name[] = + "Killer (R) Wireless-AC 1550s Wireless Network Adapter (9560NGW)"; +const char iwl9560_killer_1550s_160_name[] = + "Killer(R) Wireless-AC 1550s Wireless Network Adapter (9560D2W) 160MHz"; + +const struct iwl_cfg iwl9260_2ac_cfg = { + .fw_name_pre = IWL9260_FW_PRE, + IWL_DEVICE_9000, +}; + +const struct iwl_cfg iwl9560_2ac_cfg_soc = { + .fw_name_pre = IWL9000_FW_PRE, + IWL_DEVICE_9000, +}; + +MODULE_FIRMWARE(IWL9000_MODULE_FIRMWARE(IWL9000_UCODE_API_MAX)); +MODULE_FIRMWARE(IWL9260_MODULE_FIRMWARE(IWL9000_UCODE_API_MAX)); diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/Makefile b/drivers/net/wireless/intel/iwlwifi/dvm/Makefile new file mode 100644 index 000000000..0486b17d7 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# DVM +obj-$(CONFIG_IWLDVM) += iwldvm.o +iwldvm-objs += main.o rs.o mac80211.o ucode.o tx.o +iwldvm-objs += lib.o calib.o tt.o sta.o rx.o + +iwldvm-objs += power.o +iwldvm-objs += scan.o +iwldvm-objs += rxon.o devices.o + +iwldvm-$(CONFIG_IWLWIFI_LEDS) += led.o +iwldvm-$(CONFIG_IWLWIFI_DEBUGFS) += debugfs.o + +ccflags-y += -I $(srctree)/$(src)/../ diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/agn.h b/drivers/net/wireless/intel/iwlwifi/dvm/agn.h new file mode 100644 index 000000000..fefaa4142 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/agn.h @@ -0,0 +1,418 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2021 Intel Corporation + */ +#ifndef __iwl_agn_h__ +#define __iwl_agn_h__ + +#include "iwl-config.h" + +#include "dev.h" + +/* The first 11 queues (0-10) are used otherwise */ +#define IWLAGN_FIRST_AMPDU_QUEUE 11 + +/* AUX (TX during scan dwell) queue */ +#define IWL_AUX_QUEUE 10 + +#define IWL_INVALID_STATION 255 + +/* device operations */ +extern const struct iwl_dvm_cfg iwl_dvm_1000_cfg; +extern const struct iwl_dvm_cfg iwl_dvm_2000_cfg; +extern const struct iwl_dvm_cfg iwl_dvm_105_cfg; +extern const struct iwl_dvm_cfg iwl_dvm_2030_cfg; +extern const struct iwl_dvm_cfg iwl_dvm_5000_cfg; +extern const struct iwl_dvm_cfg iwl_dvm_5150_cfg; +extern const struct iwl_dvm_cfg iwl_dvm_6000_cfg; +extern const struct iwl_dvm_cfg iwl_dvm_6005_cfg; +extern const struct iwl_dvm_cfg iwl_dvm_6050_cfg; +extern const struct iwl_dvm_cfg iwl_dvm_6030_cfg; + + +#define TIME_UNIT 1024 + +/***************************************************** +* DRIVER STATUS FUNCTIONS +******************************************************/ +#define STATUS_RF_KILL_HW 0 +#define STATUS_CT_KILL 1 +#define STATUS_ALIVE 2 +#define STATUS_READY 3 +#define STATUS_EXIT_PENDING 5 +#define STATUS_STATISTICS 6 +#define STATUS_SCANNING 7 +#define STATUS_SCAN_ABORTING 8 +#define STATUS_SCAN_HW 9 +#define STATUS_FW_ERROR 10 +#define STATUS_CHANNEL_SWITCH_PENDING 11 +#define STATUS_SCAN_COMPLETE 12 +#define STATUS_POWER_PMI 13 + +struct iwl_ucode_capabilities; + +extern const struct ieee80211_ops iwlagn_hw_ops; + +static inline void iwl_set_calib_hdr(struct iwl_calib_hdr *hdr, u8 cmd) +{ + hdr->op_code = cmd; + hdr->first_group = 0; + hdr->groups_num = 1; + hdr->data_valid = 1; +} + +void iwl_down(struct iwl_priv *priv); +void iwl_cancel_deferred_work(struct iwl_priv *priv); +void iwlagn_prepare_restart(struct iwl_priv *priv); +void iwl_rx_dispatch(struct iwl_op_mode *op_mode, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb); + +bool iwl_check_for_ct_kill(struct iwl_priv *priv); + +void iwlagn_lift_passive_no_rx(struct iwl_priv *priv); + +/* MAC80211 */ +struct ieee80211_hw *iwl_alloc_all(void); +int iwlagn_mac_setup_register(struct iwl_priv *priv, + const struct iwl_ucode_capabilities *capa); +void iwlagn_mac_unregister(struct iwl_priv *priv); + +/* commands */ +int iwl_dvm_send_cmd(struct iwl_priv *priv, struct iwl_host_cmd *cmd); +int iwl_dvm_send_cmd_pdu(struct iwl_priv *priv, u8 id, + u32 flags, u16 len, const void *data); + +/* RXON */ +void iwl_connection_init_rx_config(struct iwl_priv *priv, + struct iwl_rxon_context *ctx); +int iwlagn_set_pan_params(struct iwl_priv *priv); +int iwlagn_commit_rxon(struct iwl_priv *priv, struct iwl_rxon_context *ctx); +void iwlagn_set_rxon_chain(struct iwl_priv *priv, struct iwl_rxon_context *ctx); +int iwlagn_mac_config(struct ieee80211_hw *hw, u32 changed); +void iwlagn_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u64 changes); +void iwlagn_config_ht40(struct ieee80211_conf *conf, + struct iwl_rxon_context *ctx); +void iwl_set_rxon_ht(struct iwl_priv *priv, struct iwl_ht_config *ht_conf); +void iwl_set_rxon_channel(struct iwl_priv *priv, struct ieee80211_channel *ch, + struct iwl_rxon_context *ctx); +void iwl_set_flags_for_band(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + enum nl80211_band band, + struct ieee80211_vif *vif); + +/* uCode */ +int iwl_send_bt_env(struct iwl_priv *priv, u8 action, u8 type); +void iwl_send_prio_tbl(struct iwl_priv *priv); +int iwl_init_alive_start(struct iwl_priv *priv); +int iwl_run_init_ucode(struct iwl_priv *priv); +int iwl_load_ucode_wait_alive(struct iwl_priv *priv, + enum iwl_ucode_type ucode_type); +int iwl_send_calib_results(struct iwl_priv *priv); +int iwl_calib_set(struct iwl_priv *priv, + const struct iwl_calib_cmd *cmd, size_t len); +void iwl_calib_free_results(struct iwl_priv *priv); +int iwl_dump_nic_event_log(struct iwl_priv *priv, bool full_log, + char **buf); +int iwlagn_hw_valid_rtc_data_addr(u32 addr); + +/* lib */ +int iwlagn_send_tx_power(struct iwl_priv *priv); +void iwlagn_temperature(struct iwl_priv *priv); +int iwlagn_txfifo_flush(struct iwl_priv *priv, u32 scd_q_msk); +void iwlagn_dev_txfifo_flush(struct iwl_priv *priv); +int iwlagn_send_beacon_cmd(struct iwl_priv *priv); +int iwl_send_statistics_request(struct iwl_priv *priv, + u8 flags, bool clear); + +static inline const struct ieee80211_supported_band *iwl_get_hw_mode( + struct iwl_priv *priv, enum nl80211_band band) +{ + return priv->hw->wiphy->bands[band]; +} + +#ifdef CONFIG_PM_SLEEP +int iwlagn_send_patterns(struct iwl_priv *priv, + struct cfg80211_wowlan *wowlan); +int iwlagn_suspend(struct iwl_priv *priv, struct cfg80211_wowlan *wowlan); +#endif + +/* rx */ +int iwlagn_hwrate_to_mac80211_idx(u32 rate_n_flags, enum nl80211_band band); +void iwl_setup_rx_handlers(struct iwl_priv *priv); +void iwl_chswitch_done(struct iwl_priv *priv, bool is_success); + + +/* tx */ +int iwlagn_tx_skb(struct iwl_priv *priv, + struct ieee80211_sta *sta, + struct sk_buff *skb); +int iwlagn_tx_agg_start(struct iwl_priv *priv, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 *ssn); +int iwlagn_tx_agg_oper(struct iwl_priv *priv, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u8 buf_size); +int iwlagn_tx_agg_stop(struct iwl_priv *priv, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid); +int iwlagn_tx_agg_flush(struct iwl_priv *priv, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid); +void iwlagn_rx_reply_compressed_ba(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb); +void iwlagn_rx_reply_tx(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb); + +static inline u32 iwl_tx_status_to_mac80211(u32 status) +{ + status &= TX_STATUS_MSK; + + switch (status) { + case TX_STATUS_SUCCESS: + case TX_STATUS_DIRECT_DONE: + return IEEE80211_TX_STAT_ACK; + case TX_STATUS_FAIL_DEST_PS: + case TX_STATUS_FAIL_PASSIVE_NO_RX: + return IEEE80211_TX_STAT_TX_FILTERED; + default: + return 0; + } +} + +static inline bool iwl_is_tx_success(u32 status) +{ + status &= TX_STATUS_MSK; + return (status == TX_STATUS_SUCCESS) || + (status == TX_STATUS_DIRECT_DONE); +} + +u8 iwl_toggle_tx_ant(struct iwl_priv *priv, u8 ant_idx, u8 valid); + +/* scan */ +void iwlagn_post_scan(struct iwl_priv *priv); +int iwl_force_rf_reset(struct iwl_priv *priv, bool external); +void iwl_init_scan_params(struct iwl_priv *priv); +int iwl_scan_cancel(struct iwl_priv *priv); +void iwl_scan_cancel_timeout(struct iwl_priv *priv, unsigned long ms); +void iwl_force_scan_end(struct iwl_priv *priv); +void iwl_internal_short_hw_scan(struct iwl_priv *priv); +void iwl_setup_rx_scan_handlers(struct iwl_priv *priv); +void iwl_setup_scan_deferred_work(struct iwl_priv *priv); +void iwl_cancel_scan_deferred_work(struct iwl_priv *priv); +int __must_check iwl_scan_initiate(struct iwl_priv *priv, + struct ieee80211_vif *vif, + enum iwl_scan_type scan_type, + enum nl80211_band band); + +/* For faster active scanning, scan will move to the next channel if fewer than + * PLCP_QUIET_THRESH packets are heard on this channel within + * ACTIVE_QUIET_TIME after sending probe request. This shortens the dwell + * time if it's a quiet channel (nothing responded to our probe, and there's + * no other traffic). + * Disable "quiet" feature by setting PLCP_QUIET_THRESH to 0. */ +#define IWL_ACTIVE_QUIET_TIME cpu_to_le16(10) /* msec */ +#define IWL_PLCP_QUIET_THRESH cpu_to_le16(1) /* packets */ + +#define IWL_SCAN_CHECK_WATCHDOG (HZ * 15) + + +/* bt coex */ +void iwlagn_send_advance_bt_config(struct iwl_priv *priv); +void iwlagn_bt_rx_handler_setup(struct iwl_priv *priv); +void iwlagn_bt_setup_deferred_work(struct iwl_priv *priv); +void iwlagn_bt_cancel_deferred_work(struct iwl_priv *priv); +void iwlagn_bt_coex_rssi_monitor(struct iwl_priv *priv); +void iwlagn_bt_adjust_rssi_monitor(struct iwl_priv *priv, bool rssi_ena); + +static inline bool iwl_advanced_bt_coexist(struct iwl_priv *priv) +{ + return priv->lib->bt_params && + priv->lib->bt_params->advanced_bt_coexist; +} + +#ifdef CONFIG_IWLWIFI_DEBUG +const char *iwl_get_tx_fail_reason(u32 status); +const char *iwl_get_agg_tx_fail_reason(u16 status); +#else +static inline const char *iwl_get_tx_fail_reason(u32 status) { return ""; } +static inline const char *iwl_get_agg_tx_fail_reason(u16 status) { return ""; } +#endif + + +/* station management */ +int iwlagn_manage_ibss_station(struct iwl_priv *priv, + struct ieee80211_vif *vif, bool add); +#define IWL_STA_DRIVER_ACTIVE BIT(0) /* driver entry is active */ +#define IWL_STA_UCODE_ACTIVE BIT(1) /* ucode entry is active */ +#define IWL_STA_UCODE_INPROGRESS BIT(2) /* ucode entry is in process of + being activated */ +#define IWL_STA_LOCAL BIT(3) /* station state not directed by mac80211; + (this is for the IBSS BSSID stations) */ +#define IWL_STA_BCAST BIT(4) /* this station is the special bcast station */ + + +void iwl_restore_stations(struct iwl_priv *priv, struct iwl_rxon_context *ctx); +void iwl_clear_ucode_stations(struct iwl_priv *priv, + struct iwl_rxon_context *ctx); +void iwl_dealloc_bcast_stations(struct iwl_priv *priv); +int iwl_get_free_ucode_key_offset(struct iwl_priv *priv); +int iwl_send_add_sta(struct iwl_priv *priv, + struct iwl_addsta_cmd *sta, u8 flags); +int iwl_add_station_common(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + const u8 *addr, bool is_ap, + struct ieee80211_sta *sta, u8 *sta_id_r); +int iwl_remove_station(struct iwl_priv *priv, const u8 sta_id, + const u8 *addr); +void iwl_deactivate_station(struct iwl_priv *priv, const u8 sta_id, + const u8 *addr); +u8 iwl_prep_station(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + const u8 *addr, bool is_ap, struct ieee80211_sta *sta); + +int iwl_send_lq_cmd(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + struct iwl_link_quality_cmd *lq, u8 flags, bool init); +void iwl_add_sta_callback(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb); +int iwl_sta_update_ht(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + struct ieee80211_sta *sta); + +bool iwl_is_ht40_tx_allowed(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_sta *sta); + +static inline int iwl_sta_id(struct ieee80211_sta *sta) +{ + if (WARN_ON(!sta)) + return IWL_INVALID_STATION; + + return ((struct iwl_station_priv *)sta->drv_priv)->sta_id; +} + +int iwlagn_alloc_bcast_station(struct iwl_priv *priv, + struct iwl_rxon_context *ctx); +int iwlagn_add_bssid_station(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + const u8 *addr, u8 *sta_id_r); +int iwl_remove_default_wep_key(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_key_conf *key); +int iwl_set_default_wep_key(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_key_conf *key); +int iwl_restore_default_wep_keys(struct iwl_priv *priv, + struct iwl_rxon_context *ctx); +int iwl_set_dynamic_key(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + struct ieee80211_key_conf *key, + struct ieee80211_sta *sta); +int iwl_remove_dynamic_key(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + struct ieee80211_key_conf *key, + struct ieee80211_sta *sta); +void iwl_update_tkip_key(struct iwl_priv *priv, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, u32 iv32, u16 *phase1key); +int iwl_sta_tx_modify_enable_tid(struct iwl_priv *priv, int sta_id, int tid); +int iwl_sta_rx_agg_start(struct iwl_priv *priv, struct ieee80211_sta *sta, + int tid, u16 ssn); +int iwl_sta_rx_agg_stop(struct iwl_priv *priv, struct ieee80211_sta *sta, + int tid); +void iwl_sta_modify_sleep_tx_count(struct iwl_priv *priv, int sta_id, int cnt); +int iwl_update_bcast_station(struct iwl_priv *priv, + struct iwl_rxon_context *ctx); +int iwl_update_bcast_stations(struct iwl_priv *priv); + +/* rate */ +static inline u32 iwl_ant_idx_to_flags(u8 ant_idx) +{ + return BIT(ant_idx) << RATE_MCS_ANT_POS; +} + +static inline u8 iwl_hw_get_rate(__le32 rate_n_flags) +{ + return le32_to_cpu(rate_n_flags) & RATE_MCS_RATE_MSK; +} + +static inline __le32 iwl_hw_set_rate_n_flags(u8 rate, u32 flags) +{ + return cpu_to_le32(flags|(u32)rate); +} + +int iwl_alive_start(struct iwl_priv *priv); + +#ifdef CONFIG_IWLWIFI_DEBUG +void iwl_print_rx_config_cmd(struct iwl_priv *priv, + enum iwl_rxon_context_id ctxid); +#else +static inline void iwl_print_rx_config_cmd(struct iwl_priv *priv, + enum iwl_rxon_context_id ctxid) +{ +} +#endif + +/* status checks */ + +static inline int iwl_is_ready(struct iwl_priv *priv) +{ + /* The adapter is 'ready' if READY EXIT_PENDING is not set */ + return test_bit(STATUS_READY, &priv->status) && + !test_bit(STATUS_EXIT_PENDING, &priv->status); +} + +static inline int iwl_is_alive(struct iwl_priv *priv) +{ + return test_bit(STATUS_ALIVE, &priv->status); +} + +static inline int iwl_is_rfkill(struct iwl_priv *priv) +{ + return test_bit(STATUS_RF_KILL_HW, &priv->status); +} + +static inline int iwl_is_ctkill(struct iwl_priv *priv) +{ + return test_bit(STATUS_CT_KILL, &priv->status); +} + +static inline int iwl_is_ready_rf(struct iwl_priv *priv) +{ + if (iwl_is_rfkill(priv)) + return 0; + + return iwl_is_ready(priv); +} + +static inline void iwl_dvm_set_pmi(struct iwl_priv *priv, bool state) +{ + if (state) + set_bit(STATUS_POWER_PMI, &priv->status); + else + clear_bit(STATUS_POWER_PMI, &priv->status); + iwl_trans_set_pmi(priv->trans, state); +} + +#ifdef CONFIG_IWLWIFI_DEBUGFS +void iwl_dbgfs_register(struct iwl_priv *priv, struct dentry *dbgfs_dir); +#else +static inline void iwl_dbgfs_register(struct iwl_priv *priv, + struct dentry *dbgfs_dir) { } +#endif /* CONFIG_IWLWIFI_DEBUGFS */ + +#ifdef CONFIG_IWLWIFI_DEBUG +#define IWL_DEBUG_QUIET_RFKILL(m, fmt, args...) \ +do { \ + if (!iwl_is_rfkill((m))) \ + IWL_ERR(m, fmt, ##args); \ + else \ + __iwl_err((m)->dev, \ + iwl_have_debug_level(IWL_DL_RADIO) ? \ + IWL_ERR_MODE_RFKILL : \ + IWL_ERR_MODE_TRACE_ONLY, \ + fmt, ##args); \ +} while (0) +#else +#define IWL_DEBUG_QUIET_RFKILL(m, fmt, args...) \ +do { \ + if (!iwl_is_rfkill((m))) \ + IWL_ERR(m, fmt, ##args); \ + else \ + __iwl_err((m)->dev, IWL_ERR_MODE_TRACE_ONLY, \ + fmt, ##args); \ +} while (0) +#endif /* CONFIG_IWLWIFI_DEBUG */ + +#endif /* __iwl_agn_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/calib.c b/drivers/net/wireless/intel/iwlwifi/dvm/calib.c new file mode 100644 index 000000000..f488620d2 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/calib.c @@ -0,0 +1,1056 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014 Intel Corporation + */ +#include <linux/slab.h> +#include <net/mac80211.h> + +#include "iwl-trans.h" + +#include "dev.h" +#include "calib.h" +#include "agn.h" + +/***************************************************************************** + * INIT calibrations framework + *****************************************************************************/ + +/* Opaque calibration results */ +struct iwl_calib_result { + struct list_head list; + size_t cmd_len; + struct iwl_calib_cmd cmd; +}; + +struct statistics_general_data { + u32 beacon_silence_rssi_a; + u32 beacon_silence_rssi_b; + u32 beacon_silence_rssi_c; + u32 beacon_energy_a; + u32 beacon_energy_b; + u32 beacon_energy_c; +}; + +int iwl_send_calib_results(struct iwl_priv *priv) +{ + struct iwl_host_cmd hcmd = { + .id = REPLY_PHY_CALIBRATION_CMD, + }; + struct iwl_calib_result *res; + + list_for_each_entry(res, &priv->calib_results, list) { + int ret; + + hcmd.len[0] = res->cmd_len; + hcmd.data[0] = &res->cmd; + hcmd.dataflags[0] = IWL_HCMD_DFL_NOCOPY; + ret = iwl_dvm_send_cmd(priv, &hcmd); + if (ret) { + IWL_ERR(priv, "Error %d on calib cmd %d\n", + ret, res->cmd.hdr.op_code); + return ret; + } + } + + return 0; +} + +int iwl_calib_set(struct iwl_priv *priv, + const struct iwl_calib_cmd *cmd, size_t len) +{ + struct iwl_calib_result *res, *tmp; + + if (check_sub_overflow(len, sizeof(*cmd), &len)) + return -ENOMEM; + + res = kmalloc(struct_size(res, cmd.data, len), GFP_ATOMIC); + if (!res) + return -ENOMEM; + res->cmd = *cmd; + memcpy(res->cmd.data, cmd->data, len); + res->cmd_len = struct_size(cmd, data, len); + + list_for_each_entry(tmp, &priv->calib_results, list) { + if (tmp->cmd.hdr.op_code == res->cmd.hdr.op_code) { + list_replace(&tmp->list, &res->list); + kfree(tmp); + return 0; + } + } + + /* wasn't in list already */ + list_add_tail(&res->list, &priv->calib_results); + + return 0; +} + +void iwl_calib_free_results(struct iwl_priv *priv) +{ + struct iwl_calib_result *res, *tmp; + + list_for_each_entry_safe(res, tmp, &priv->calib_results, list) { + list_del(&res->list); + kfree(res); + } +} + +/***************************************************************************** + * RUNTIME calibrations framework + *****************************************************************************/ + +/* "false alarms" are signals that our DSP tries to lock onto, + * but then determines that they are either noise, or transmissions + * from a distant wireless network (also "noise", really) that get + * "stepped on" by stronger transmissions within our own network. + * This algorithm attempts to set a sensitivity level that is high + * enough to receive all of our own network traffic, but not so + * high that our DSP gets too busy trying to lock onto non-network + * activity/noise. */ +static int iwl_sens_energy_cck(struct iwl_priv *priv, + u32 norm_fa, + u32 rx_enable_time, + struct statistics_general_data *rx_info) +{ + u32 max_nrg_cck = 0; + int i = 0; + u8 max_silence_rssi = 0; + u32 silence_ref = 0; + u8 silence_rssi_a = 0; + u8 silence_rssi_b = 0; + u8 silence_rssi_c = 0; + u32 val; + + /* "false_alarms" values below are cross-multiplications to assess the + * numbers of false alarms within the measured period of actual Rx + * (Rx is off when we're txing), vs the min/max expected false alarms + * (some should be expected if rx is sensitive enough) in a + * hypothetical listening period of 200 time units (TU), 204.8 msec: + * + * MIN_FA/fixed-time < false_alarms/actual-rx-time < MAX_FA/beacon-time + * + * */ + u32 false_alarms = norm_fa * 200 * 1024; + u32 max_false_alarms = MAX_FA_CCK * rx_enable_time; + u32 min_false_alarms = MIN_FA_CCK * rx_enable_time; + struct iwl_sensitivity_data *data = NULL; + const struct iwl_sensitivity_ranges *ranges = priv->hw_params.sens; + + data = &(priv->sensitivity_data); + + data->nrg_auto_corr_silence_diff = 0; + + /* Find max silence rssi among all 3 receivers. + * This is background noise, which may include transmissions from other + * networks, measured during silence before our network's beacon */ + silence_rssi_a = (u8)((rx_info->beacon_silence_rssi_a & + ALL_BAND_FILTER) >> 8); + silence_rssi_b = (u8)((rx_info->beacon_silence_rssi_b & + ALL_BAND_FILTER) >> 8); + silence_rssi_c = (u8)((rx_info->beacon_silence_rssi_c & + ALL_BAND_FILTER) >> 8); + + val = max(silence_rssi_b, silence_rssi_c); + max_silence_rssi = max(silence_rssi_a, (u8) val); + + /* Store silence rssi in 20-beacon history table */ + data->nrg_silence_rssi[data->nrg_silence_idx] = max_silence_rssi; + data->nrg_silence_idx++; + if (data->nrg_silence_idx >= NRG_NUM_PREV_STAT_L) + data->nrg_silence_idx = 0; + + /* Find max silence rssi across 20 beacon history */ + for (i = 0; i < NRG_NUM_PREV_STAT_L; i++) { + val = data->nrg_silence_rssi[i]; + silence_ref = max(silence_ref, val); + } + IWL_DEBUG_CALIB(priv, "silence a %u, b %u, c %u, 20-bcn max %u\n", + silence_rssi_a, silence_rssi_b, silence_rssi_c, + silence_ref); + + /* Find max rx energy (min value!) among all 3 receivers, + * measured during beacon frame. + * Save it in 10-beacon history table. */ + i = data->nrg_energy_idx; + val = min(rx_info->beacon_energy_b, rx_info->beacon_energy_c); + data->nrg_value[i] = min(rx_info->beacon_energy_a, val); + + data->nrg_energy_idx++; + if (data->nrg_energy_idx >= 10) + data->nrg_energy_idx = 0; + + /* Find min rx energy (max value) across 10 beacon history. + * This is the minimum signal level that we want to receive well. + * Add backoff (margin so we don't miss slightly lower energy frames). + * This establishes an upper bound (min value) for energy threshold. */ + max_nrg_cck = data->nrg_value[0]; + for (i = 1; i < 10; i++) + max_nrg_cck = (u32) max(max_nrg_cck, (data->nrg_value[i])); + max_nrg_cck += 6; + + IWL_DEBUG_CALIB(priv, "rx energy a %u, b %u, c %u, 10-bcn max/min %u\n", + rx_info->beacon_energy_a, rx_info->beacon_energy_b, + rx_info->beacon_energy_c, max_nrg_cck - 6); + + /* Count number of consecutive beacons with fewer-than-desired + * false alarms. */ + if (false_alarms < min_false_alarms) + data->num_in_cck_no_fa++; + else + data->num_in_cck_no_fa = 0; + IWL_DEBUG_CALIB(priv, "consecutive bcns with few false alarms = %u\n", + data->num_in_cck_no_fa); + + /* If we got too many false alarms this time, reduce sensitivity */ + if ((false_alarms > max_false_alarms) && + (data->auto_corr_cck > AUTO_CORR_MAX_TH_CCK)) { + IWL_DEBUG_CALIB(priv, "norm FA %u > max FA %u\n", + false_alarms, max_false_alarms); + IWL_DEBUG_CALIB(priv, "... reducing sensitivity\n"); + data->nrg_curr_state = IWL_FA_TOO_MANY; + /* Store for "fewer than desired" on later beacon */ + data->nrg_silence_ref = silence_ref; + + /* increase energy threshold (reduce nrg value) + * to decrease sensitivity */ + data->nrg_th_cck = data->nrg_th_cck - NRG_STEP_CCK; + /* Else if we got fewer than desired, increase sensitivity */ + } else if (false_alarms < min_false_alarms) { + data->nrg_curr_state = IWL_FA_TOO_FEW; + + /* Compare silence level with silence level for most recent + * healthy number or too many false alarms */ + data->nrg_auto_corr_silence_diff = (s32)data->nrg_silence_ref - + (s32)silence_ref; + + IWL_DEBUG_CALIB(priv, "norm FA %u < min FA %u, silence diff %d\n", + false_alarms, min_false_alarms, + data->nrg_auto_corr_silence_diff); + + /* Increase value to increase sensitivity, but only if: + * 1a) previous beacon did *not* have *too many* false alarms + * 1b) AND there's a significant difference in Rx levels + * from a previous beacon with too many, or healthy # FAs + * OR 2) We've seen a lot of beacons (100) with too few + * false alarms */ + if ((data->nrg_prev_state != IWL_FA_TOO_MANY) && + ((data->nrg_auto_corr_silence_diff > NRG_DIFF) || + (data->num_in_cck_no_fa > MAX_NUMBER_CCK_NO_FA))) { + + IWL_DEBUG_CALIB(priv, "... increasing sensitivity\n"); + /* Increase nrg value to increase sensitivity */ + val = data->nrg_th_cck + NRG_STEP_CCK; + data->nrg_th_cck = min((u32)ranges->min_nrg_cck, val); + } else { + IWL_DEBUG_CALIB(priv, "... but not changing sensitivity\n"); + } + + /* Else we got a healthy number of false alarms, keep status quo */ + } else { + IWL_DEBUG_CALIB(priv, " FA in safe zone\n"); + data->nrg_curr_state = IWL_FA_GOOD_RANGE; + + /* Store for use in "fewer than desired" with later beacon */ + data->nrg_silence_ref = silence_ref; + + /* If previous beacon had too many false alarms, + * give it some extra margin by reducing sensitivity again + * (but don't go below measured energy of desired Rx) */ + if (data->nrg_prev_state == IWL_FA_TOO_MANY) { + IWL_DEBUG_CALIB(priv, "... increasing margin\n"); + if (data->nrg_th_cck > (max_nrg_cck + NRG_MARGIN)) + data->nrg_th_cck -= NRG_MARGIN; + else + data->nrg_th_cck = max_nrg_cck; + } + } + + /* Make sure the energy threshold does not go above the measured + * energy of the desired Rx signals (reduced by backoff margin), + * or else we might start missing Rx frames. + * Lower value is higher energy, so we use max()! + */ + data->nrg_th_cck = max(max_nrg_cck, data->nrg_th_cck); + IWL_DEBUG_CALIB(priv, "new nrg_th_cck %u\n", data->nrg_th_cck); + + data->nrg_prev_state = data->nrg_curr_state; + + /* Auto-correlation CCK algorithm */ + if (false_alarms > min_false_alarms) { + + /* increase auto_corr values to decrease sensitivity + * so the DSP won't be disturbed by the noise + */ + if (data->auto_corr_cck < AUTO_CORR_MAX_TH_CCK) + data->auto_corr_cck = AUTO_CORR_MAX_TH_CCK + 1; + else { + val = data->auto_corr_cck + AUTO_CORR_STEP_CCK; + data->auto_corr_cck = + min((u32)ranges->auto_corr_max_cck, val); + } + val = data->auto_corr_cck_mrc + AUTO_CORR_STEP_CCK; + data->auto_corr_cck_mrc = + min((u32)ranges->auto_corr_max_cck_mrc, val); + } else if ((false_alarms < min_false_alarms) && + ((data->nrg_auto_corr_silence_diff > NRG_DIFF) || + (data->num_in_cck_no_fa > MAX_NUMBER_CCK_NO_FA))) { + + /* Decrease auto_corr values to increase sensitivity */ + val = data->auto_corr_cck - AUTO_CORR_STEP_CCK; + data->auto_corr_cck = + max((u32)ranges->auto_corr_min_cck, val); + val = data->auto_corr_cck_mrc - AUTO_CORR_STEP_CCK; + data->auto_corr_cck_mrc = + max((u32)ranges->auto_corr_min_cck_mrc, val); + } + + return 0; +} + + +static int iwl_sens_auto_corr_ofdm(struct iwl_priv *priv, + u32 norm_fa, + u32 rx_enable_time) +{ + u32 val; + u32 false_alarms = norm_fa * 200 * 1024; + u32 max_false_alarms = MAX_FA_OFDM * rx_enable_time; + u32 min_false_alarms = MIN_FA_OFDM * rx_enable_time; + struct iwl_sensitivity_data *data = NULL; + const struct iwl_sensitivity_ranges *ranges = priv->hw_params.sens; + + data = &(priv->sensitivity_data); + + /* If we got too many false alarms this time, reduce sensitivity */ + if (false_alarms > max_false_alarms) { + + IWL_DEBUG_CALIB(priv, "norm FA %u > max FA %u)\n", + false_alarms, max_false_alarms); + + val = data->auto_corr_ofdm + AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm = + min((u32)ranges->auto_corr_max_ofdm, val); + + val = data->auto_corr_ofdm_mrc + AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_mrc = + min((u32)ranges->auto_corr_max_ofdm_mrc, val); + + val = data->auto_corr_ofdm_x1 + AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_x1 = + min((u32)ranges->auto_corr_max_ofdm_x1, val); + + val = data->auto_corr_ofdm_mrc_x1 + AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_mrc_x1 = + min((u32)ranges->auto_corr_max_ofdm_mrc_x1, val); + } + + /* Else if we got fewer than desired, increase sensitivity */ + else if (false_alarms < min_false_alarms) { + + IWL_DEBUG_CALIB(priv, "norm FA %u < min FA %u\n", + false_alarms, min_false_alarms); + + val = data->auto_corr_ofdm - AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm = + max((u32)ranges->auto_corr_min_ofdm, val); + + val = data->auto_corr_ofdm_mrc - AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_mrc = + max((u32)ranges->auto_corr_min_ofdm_mrc, val); + + val = data->auto_corr_ofdm_x1 - AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_x1 = + max((u32)ranges->auto_corr_min_ofdm_x1, val); + + val = data->auto_corr_ofdm_mrc_x1 - AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_mrc_x1 = + max((u32)ranges->auto_corr_min_ofdm_mrc_x1, val); + } else { + IWL_DEBUG_CALIB(priv, "min FA %u < norm FA %u < max FA %u OK\n", + min_false_alarms, false_alarms, max_false_alarms); + } + return 0; +} + +static void iwl_prepare_legacy_sensitivity_tbl(struct iwl_priv *priv, + struct iwl_sensitivity_data *data, + __le16 *tbl) +{ + tbl[HD_AUTO_CORR32_X4_TH_ADD_MIN_INDEX] = + cpu_to_le16((u16)data->auto_corr_ofdm); + tbl[HD_AUTO_CORR32_X4_TH_ADD_MIN_MRC_INDEX] = + cpu_to_le16((u16)data->auto_corr_ofdm_mrc); + tbl[HD_AUTO_CORR32_X1_TH_ADD_MIN_INDEX] = + cpu_to_le16((u16)data->auto_corr_ofdm_x1); + tbl[HD_AUTO_CORR32_X1_TH_ADD_MIN_MRC_INDEX] = + cpu_to_le16((u16)data->auto_corr_ofdm_mrc_x1); + + tbl[HD_AUTO_CORR40_X4_TH_ADD_MIN_INDEX] = + cpu_to_le16((u16)data->auto_corr_cck); + tbl[HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_INDEX] = + cpu_to_le16((u16)data->auto_corr_cck_mrc); + + tbl[HD_MIN_ENERGY_CCK_DET_INDEX] = + cpu_to_le16((u16)data->nrg_th_cck); + tbl[HD_MIN_ENERGY_OFDM_DET_INDEX] = + cpu_to_le16((u16)data->nrg_th_ofdm); + + tbl[HD_BARKER_CORR_TH_ADD_MIN_INDEX] = + cpu_to_le16(data->barker_corr_th_min); + tbl[HD_BARKER_CORR_TH_ADD_MIN_MRC_INDEX] = + cpu_to_le16(data->barker_corr_th_min_mrc); + tbl[HD_OFDM_ENERGY_TH_IN_INDEX] = + cpu_to_le16(data->nrg_th_cca); + + IWL_DEBUG_CALIB(priv, "ofdm: ac %u mrc %u x1 %u mrc_x1 %u thresh %u\n", + data->auto_corr_ofdm, data->auto_corr_ofdm_mrc, + data->auto_corr_ofdm_x1, data->auto_corr_ofdm_mrc_x1, + data->nrg_th_ofdm); + + IWL_DEBUG_CALIB(priv, "cck: ac %u mrc %u thresh %u\n", + data->auto_corr_cck, data->auto_corr_cck_mrc, + data->nrg_th_cck); +} + +/* Prepare a SENSITIVITY_CMD, send to uCode if values have changed */ +static int iwl_sensitivity_write(struct iwl_priv *priv) +{ + struct iwl_sensitivity_cmd cmd; + struct iwl_sensitivity_data *data = NULL; + struct iwl_host_cmd cmd_out = { + .id = SENSITIVITY_CMD, + .len = { sizeof(struct iwl_sensitivity_cmd), }, + .flags = CMD_ASYNC, + .data = { &cmd, }, + }; + + data = &(priv->sensitivity_data); + + memset(&cmd, 0, sizeof(cmd)); + + iwl_prepare_legacy_sensitivity_tbl(priv, data, &cmd.table[0]); + + /* Update uCode's "work" table, and copy it to DSP */ + cmd.control = SENSITIVITY_CMD_CONTROL_WORK_TABLE; + + /* Don't send command to uCode if nothing has changed */ + if (!memcmp(&cmd.table[0], &(priv->sensitivity_tbl[0]), + sizeof(u16)*HD_TABLE_SIZE)) { + IWL_DEBUG_CALIB(priv, "No change in SENSITIVITY_CMD\n"); + return 0; + } + + /* Copy table for comparison next time */ + memcpy(&(priv->sensitivity_tbl[0]), &(cmd.table[0]), + sizeof(u16)*HD_TABLE_SIZE); + + return iwl_dvm_send_cmd(priv, &cmd_out); +} + +/* Prepare a SENSITIVITY_CMD, send to uCode if values have changed */ +static int iwl_enhance_sensitivity_write(struct iwl_priv *priv) +{ + struct iwl_enhance_sensitivity_cmd cmd; + struct iwl_sensitivity_data *data = NULL; + struct iwl_host_cmd cmd_out = { + .id = SENSITIVITY_CMD, + .len = { sizeof(struct iwl_enhance_sensitivity_cmd), }, + .flags = CMD_ASYNC, + .data = { &cmd, }, + }; + + data = &(priv->sensitivity_data); + + memset(&cmd, 0, sizeof(cmd)); + + iwl_prepare_legacy_sensitivity_tbl(priv, data, &cmd.enhance_table[0]); + + if (priv->lib->hd_v2) { + cmd.enhance_table[HD_INA_NON_SQUARE_DET_OFDM_INDEX] = + HD_INA_NON_SQUARE_DET_OFDM_DATA_V2; + cmd.enhance_table[HD_INA_NON_SQUARE_DET_CCK_INDEX] = + HD_INA_NON_SQUARE_DET_CCK_DATA_V2; + cmd.enhance_table[HD_CORR_11_INSTEAD_OF_CORR_9_EN_INDEX] = + HD_CORR_11_INSTEAD_OF_CORR_9_EN_DATA_V2; + cmd.enhance_table[HD_OFDM_NON_SQUARE_DET_SLOPE_MRC_INDEX] = + HD_OFDM_NON_SQUARE_DET_SLOPE_MRC_DATA_V2; + cmd.enhance_table[HD_OFDM_NON_SQUARE_DET_INTERCEPT_MRC_INDEX] = + HD_OFDM_NON_SQUARE_DET_INTERCEPT_MRC_DATA_V2; + cmd.enhance_table[HD_OFDM_NON_SQUARE_DET_SLOPE_INDEX] = + HD_OFDM_NON_SQUARE_DET_SLOPE_DATA_V2; + cmd.enhance_table[HD_OFDM_NON_SQUARE_DET_INTERCEPT_INDEX] = + HD_OFDM_NON_SQUARE_DET_INTERCEPT_DATA_V2; + cmd.enhance_table[HD_CCK_NON_SQUARE_DET_SLOPE_MRC_INDEX] = + HD_CCK_NON_SQUARE_DET_SLOPE_MRC_DATA_V2; + cmd.enhance_table[HD_CCK_NON_SQUARE_DET_INTERCEPT_MRC_INDEX] = + HD_CCK_NON_SQUARE_DET_INTERCEPT_MRC_DATA_V2; + cmd.enhance_table[HD_CCK_NON_SQUARE_DET_SLOPE_INDEX] = + HD_CCK_NON_SQUARE_DET_SLOPE_DATA_V2; + cmd.enhance_table[HD_CCK_NON_SQUARE_DET_INTERCEPT_INDEX] = + HD_CCK_NON_SQUARE_DET_INTERCEPT_DATA_V2; + } else { + cmd.enhance_table[HD_INA_NON_SQUARE_DET_OFDM_INDEX] = + HD_INA_NON_SQUARE_DET_OFDM_DATA_V1; + cmd.enhance_table[HD_INA_NON_SQUARE_DET_CCK_INDEX] = + HD_INA_NON_SQUARE_DET_CCK_DATA_V1; + cmd.enhance_table[HD_CORR_11_INSTEAD_OF_CORR_9_EN_INDEX] = + HD_CORR_11_INSTEAD_OF_CORR_9_EN_DATA_V1; + cmd.enhance_table[HD_OFDM_NON_SQUARE_DET_SLOPE_MRC_INDEX] = + HD_OFDM_NON_SQUARE_DET_SLOPE_MRC_DATA_V1; + cmd.enhance_table[HD_OFDM_NON_SQUARE_DET_INTERCEPT_MRC_INDEX] = + HD_OFDM_NON_SQUARE_DET_INTERCEPT_MRC_DATA_V1; + cmd.enhance_table[HD_OFDM_NON_SQUARE_DET_SLOPE_INDEX] = + HD_OFDM_NON_SQUARE_DET_SLOPE_DATA_V1; + cmd.enhance_table[HD_OFDM_NON_SQUARE_DET_INTERCEPT_INDEX] = + HD_OFDM_NON_SQUARE_DET_INTERCEPT_DATA_V1; + cmd.enhance_table[HD_CCK_NON_SQUARE_DET_SLOPE_MRC_INDEX] = + HD_CCK_NON_SQUARE_DET_SLOPE_MRC_DATA_V1; + cmd.enhance_table[HD_CCK_NON_SQUARE_DET_INTERCEPT_MRC_INDEX] = + HD_CCK_NON_SQUARE_DET_INTERCEPT_MRC_DATA_V1; + cmd.enhance_table[HD_CCK_NON_SQUARE_DET_SLOPE_INDEX] = + HD_CCK_NON_SQUARE_DET_SLOPE_DATA_V1; + cmd.enhance_table[HD_CCK_NON_SQUARE_DET_INTERCEPT_INDEX] = + HD_CCK_NON_SQUARE_DET_INTERCEPT_DATA_V1; + } + + /* Update uCode's "work" table, and copy it to DSP */ + cmd.control = SENSITIVITY_CMD_CONTROL_WORK_TABLE; + + /* Don't send command to uCode if nothing has changed */ + if (!memcmp(&cmd.enhance_table[0], &(priv->sensitivity_tbl[0]), + sizeof(u16)*HD_TABLE_SIZE) && + !memcmp(&cmd.enhance_table[HD_INA_NON_SQUARE_DET_OFDM_INDEX], + &(priv->enhance_sensitivity_tbl[0]), + sizeof(u16)*ENHANCE_HD_TABLE_ENTRIES)) { + IWL_DEBUG_CALIB(priv, "No change in SENSITIVITY_CMD\n"); + return 0; + } + + /* Copy table for comparison next time */ + memcpy(&(priv->sensitivity_tbl[0]), &(cmd.enhance_table[0]), + sizeof(u16)*HD_TABLE_SIZE); + memcpy(&(priv->enhance_sensitivity_tbl[0]), + &(cmd.enhance_table[HD_INA_NON_SQUARE_DET_OFDM_INDEX]), + sizeof(u16)*ENHANCE_HD_TABLE_ENTRIES); + + return iwl_dvm_send_cmd(priv, &cmd_out); +} + +void iwl_init_sensitivity(struct iwl_priv *priv) +{ + int ret = 0; + int i; + struct iwl_sensitivity_data *data = NULL; + const struct iwl_sensitivity_ranges *ranges = priv->hw_params.sens; + + if (priv->calib_disabled & IWL_SENSITIVITY_CALIB_DISABLED) + return; + + IWL_DEBUG_CALIB(priv, "Start iwl_init_sensitivity\n"); + + /* Clear driver's sensitivity algo data */ + data = &(priv->sensitivity_data); + + if (ranges == NULL) + return; + + memset(data, 0, sizeof(struct iwl_sensitivity_data)); + + data->num_in_cck_no_fa = 0; + data->nrg_curr_state = IWL_FA_TOO_MANY; + data->nrg_prev_state = IWL_FA_TOO_MANY; + data->nrg_silence_ref = 0; + data->nrg_silence_idx = 0; + data->nrg_energy_idx = 0; + + for (i = 0; i < 10; i++) + data->nrg_value[i] = 0; + + for (i = 0; i < NRG_NUM_PREV_STAT_L; i++) + data->nrg_silence_rssi[i] = 0; + + data->auto_corr_ofdm = ranges->auto_corr_min_ofdm; + data->auto_corr_ofdm_mrc = ranges->auto_corr_min_ofdm_mrc; + data->auto_corr_ofdm_x1 = ranges->auto_corr_min_ofdm_x1; + data->auto_corr_ofdm_mrc_x1 = ranges->auto_corr_min_ofdm_mrc_x1; + data->auto_corr_cck = AUTO_CORR_CCK_MIN_VAL_DEF; + data->auto_corr_cck_mrc = ranges->auto_corr_min_cck_mrc; + data->nrg_th_cck = ranges->nrg_th_cck; + data->nrg_th_ofdm = ranges->nrg_th_ofdm; + data->barker_corr_th_min = ranges->barker_corr_th_min; + data->barker_corr_th_min_mrc = ranges->barker_corr_th_min_mrc; + data->nrg_th_cca = ranges->nrg_th_cca; + + data->last_bad_plcp_cnt_ofdm = 0; + data->last_fa_cnt_ofdm = 0; + data->last_bad_plcp_cnt_cck = 0; + data->last_fa_cnt_cck = 0; + + if (priv->fw->enhance_sensitivity_table) + ret |= iwl_enhance_sensitivity_write(priv); + else + ret |= iwl_sensitivity_write(priv); + IWL_DEBUG_CALIB(priv, "<<return 0x%X\n", ret); +} + +void iwl_sensitivity_calibration(struct iwl_priv *priv) +{ + u32 rx_enable_time; + u32 fa_cck; + u32 fa_ofdm; + u32 bad_plcp_cck; + u32 bad_plcp_ofdm; + u32 norm_fa_ofdm; + u32 norm_fa_cck; + struct iwl_sensitivity_data *data = NULL; + struct statistics_rx_non_phy *rx_info; + struct statistics_rx_phy *ofdm, *cck; + struct statistics_general_data statis; + + if (priv->calib_disabled & IWL_SENSITIVITY_CALIB_DISABLED) + return; + + data = &(priv->sensitivity_data); + + if (!iwl_is_any_associated(priv)) { + IWL_DEBUG_CALIB(priv, "<< - not associated\n"); + return; + } + + spin_lock_bh(&priv->statistics.lock); + rx_info = &priv->statistics.rx_non_phy; + ofdm = &priv->statistics.rx_ofdm; + cck = &priv->statistics.rx_cck; + if (rx_info->interference_data_flag != INTERFERENCE_DATA_AVAILABLE) { + IWL_DEBUG_CALIB(priv, "<< invalid data.\n"); + spin_unlock_bh(&priv->statistics.lock); + return; + } + + /* Extract Statistics: */ + rx_enable_time = le32_to_cpu(rx_info->channel_load); + fa_cck = le32_to_cpu(cck->false_alarm_cnt); + fa_ofdm = le32_to_cpu(ofdm->false_alarm_cnt); + bad_plcp_cck = le32_to_cpu(cck->plcp_err); + bad_plcp_ofdm = le32_to_cpu(ofdm->plcp_err); + + statis.beacon_silence_rssi_a = + le32_to_cpu(rx_info->beacon_silence_rssi_a); + statis.beacon_silence_rssi_b = + le32_to_cpu(rx_info->beacon_silence_rssi_b); + statis.beacon_silence_rssi_c = + le32_to_cpu(rx_info->beacon_silence_rssi_c); + statis.beacon_energy_a = + le32_to_cpu(rx_info->beacon_energy_a); + statis.beacon_energy_b = + le32_to_cpu(rx_info->beacon_energy_b); + statis.beacon_energy_c = + le32_to_cpu(rx_info->beacon_energy_c); + + spin_unlock_bh(&priv->statistics.lock); + + IWL_DEBUG_CALIB(priv, "rx_enable_time = %u usecs\n", rx_enable_time); + + if (!rx_enable_time) { + IWL_DEBUG_CALIB(priv, "<< RX Enable Time == 0!\n"); + return; + } + + /* These statistics increase monotonically, and do not reset + * at each beacon. Calculate difference from last value, or just + * use the new statistics value if it has reset or wrapped around. */ + if (data->last_bad_plcp_cnt_cck > bad_plcp_cck) + data->last_bad_plcp_cnt_cck = bad_plcp_cck; + else { + bad_plcp_cck -= data->last_bad_plcp_cnt_cck; + data->last_bad_plcp_cnt_cck += bad_plcp_cck; + } + + if (data->last_bad_plcp_cnt_ofdm > bad_plcp_ofdm) + data->last_bad_plcp_cnt_ofdm = bad_plcp_ofdm; + else { + bad_plcp_ofdm -= data->last_bad_plcp_cnt_ofdm; + data->last_bad_plcp_cnt_ofdm += bad_plcp_ofdm; + } + + if (data->last_fa_cnt_ofdm > fa_ofdm) + data->last_fa_cnt_ofdm = fa_ofdm; + else { + fa_ofdm -= data->last_fa_cnt_ofdm; + data->last_fa_cnt_ofdm += fa_ofdm; + } + + if (data->last_fa_cnt_cck > fa_cck) + data->last_fa_cnt_cck = fa_cck; + else { + fa_cck -= data->last_fa_cnt_cck; + data->last_fa_cnt_cck += fa_cck; + } + + /* Total aborted signal locks */ + norm_fa_ofdm = fa_ofdm + bad_plcp_ofdm; + norm_fa_cck = fa_cck + bad_plcp_cck; + + IWL_DEBUG_CALIB(priv, "cck: fa %u badp %u ofdm: fa %u badp %u\n", fa_cck, + bad_plcp_cck, fa_ofdm, bad_plcp_ofdm); + + iwl_sens_auto_corr_ofdm(priv, norm_fa_ofdm, rx_enable_time); + iwl_sens_energy_cck(priv, norm_fa_cck, rx_enable_time, &statis); + if (priv->fw->enhance_sensitivity_table) + iwl_enhance_sensitivity_write(priv); + else + iwl_sensitivity_write(priv); +} + +static inline u8 find_first_chain(u8 mask) +{ + if (mask & ANT_A) + return CHAIN_A; + if (mask & ANT_B) + return CHAIN_B; + return CHAIN_C; +} + +/* + * Run disconnected antenna algorithm to find out which antennas are + * disconnected. + */ +static void iwl_find_disconn_antenna(struct iwl_priv *priv, u32* average_sig, + struct iwl_chain_noise_data *data) +{ + u32 active_chains = 0; + u32 max_average_sig; + u16 max_average_sig_antenna_i; + u8 num_tx_chains; + u8 first_chain; + u16 i = 0; + + average_sig[0] = data->chain_signal_a / IWL_CAL_NUM_BEACONS; + average_sig[1] = data->chain_signal_b / IWL_CAL_NUM_BEACONS; + average_sig[2] = data->chain_signal_c / IWL_CAL_NUM_BEACONS; + + if (average_sig[0] >= average_sig[1]) { + max_average_sig = average_sig[0]; + max_average_sig_antenna_i = 0; + active_chains = (1 << max_average_sig_antenna_i); + } else { + max_average_sig = average_sig[1]; + max_average_sig_antenna_i = 1; + active_chains = (1 << max_average_sig_antenna_i); + } + + if (average_sig[2] >= max_average_sig) { + max_average_sig = average_sig[2]; + max_average_sig_antenna_i = 2; + active_chains = (1 << max_average_sig_antenna_i); + } + + IWL_DEBUG_CALIB(priv, "average_sig: a %d b %d c %d\n", + average_sig[0], average_sig[1], average_sig[2]); + IWL_DEBUG_CALIB(priv, "max_average_sig = %d, antenna %d\n", + max_average_sig, max_average_sig_antenna_i); + + /* Compare signal strengths for all 3 receivers. */ + for (i = 0; i < NUM_RX_CHAINS; i++) { + if (i != max_average_sig_antenna_i) { + s32 rssi_delta = (max_average_sig - average_sig[i]); + + /* If signal is very weak, compared with + * strongest, mark it as disconnected. */ + if (rssi_delta > MAXIMUM_ALLOWED_PATHLOSS) + data->disconn_array[i] = 1; + else + active_chains |= (1 << i); + IWL_DEBUG_CALIB(priv, "i = %d rssiDelta = %d " + "disconn_array[i] = %d\n", + i, rssi_delta, data->disconn_array[i]); + } + } + + /* + * The above algorithm sometimes fails when the ucode + * reports 0 for all chains. It's not clear why that + * happens to start with, but it is then causing trouble + * because this can make us enable more chains than the + * hardware really has. + * + * To be safe, simply mask out any chains that we know + * are not on the device. + */ + active_chains &= priv->nvm_data->valid_rx_ant; + + num_tx_chains = 0; + for (i = 0; i < NUM_RX_CHAINS; i++) { + /* loops on all the bits of + * priv->hw_setting.valid_tx_ant */ + u8 ant_msk = (1 << i); + if (!(priv->nvm_data->valid_tx_ant & ant_msk)) + continue; + + num_tx_chains++; + if (data->disconn_array[i] == 0) + /* there is a Tx antenna connected */ + break; + if (num_tx_chains == priv->hw_params.tx_chains_num && + data->disconn_array[i]) { + /* + * If all chains are disconnected + * connect the first valid tx chain + */ + first_chain = + find_first_chain(priv->nvm_data->valid_tx_ant); + data->disconn_array[first_chain] = 0; + active_chains |= BIT(first_chain); + IWL_DEBUG_CALIB(priv, + "All Tx chains are disconnected W/A - declare %d as connected\n", + first_chain); + break; + } + } + + if (active_chains != priv->nvm_data->valid_rx_ant && + active_chains != priv->chain_noise_data.active_chains) + IWL_DEBUG_CALIB(priv, + "Detected that not all antennas are connected! " + "Connected: %#x, valid: %#x.\n", + active_chains, + priv->nvm_data->valid_rx_ant); + + /* Save for use within RXON, TX, SCAN commands, etc. */ + data->active_chains = active_chains; + IWL_DEBUG_CALIB(priv, "active_chains (bitwise) = 0x%x\n", + active_chains); +} + +static void iwlagn_gain_computation(struct iwl_priv *priv, + u32 average_noise[NUM_RX_CHAINS], + u8 default_chain) +{ + int i; + s32 delta_g; + struct iwl_chain_noise_data *data = &priv->chain_noise_data; + + /* + * Find Gain Code for the chains based on "default chain" + */ + for (i = default_chain + 1; i < NUM_RX_CHAINS; i++) { + if ((data->disconn_array[i])) { + data->delta_gain_code[i] = 0; + continue; + } + + delta_g = (priv->lib->chain_noise_scale * + ((s32)average_noise[default_chain] - + (s32)average_noise[i])) / 1500; + + /* bound gain by 2 bits value max, 3rd bit is sign */ + data->delta_gain_code[i] = + min(abs(delta_g), CHAIN_NOISE_MAX_DELTA_GAIN_CODE); + + if (delta_g < 0) + /* + * set negative sign ... + * note to Intel developers: This is uCode API format, + * not the format of any internal device registers. + * Do not change this format for e.g. 6050 or similar + * devices. Change format only if more resolution + * (i.e. more than 2 bits magnitude) is needed. + */ + data->delta_gain_code[i] |= (1 << 2); + } + + IWL_DEBUG_CALIB(priv, "Delta gains: ANT_B = %d ANT_C = %d\n", + data->delta_gain_code[1], data->delta_gain_code[2]); + + if (!data->radio_write) { + struct iwl_calib_chain_noise_gain_cmd cmd; + + memset(&cmd, 0, sizeof(cmd)); + + iwl_set_calib_hdr(&cmd.hdr, + priv->phy_calib_chain_noise_gain_cmd); + cmd.delta_gain_1 = data->delta_gain_code[1]; + cmd.delta_gain_2 = data->delta_gain_code[2]; + iwl_dvm_send_cmd_pdu(priv, REPLY_PHY_CALIBRATION_CMD, + CMD_ASYNC, sizeof(cmd), &cmd); + + data->radio_write = 1; + data->state = IWL_CHAIN_NOISE_CALIBRATED; + } +} + +/* + * Accumulate 16 beacons of signal and noise statistics for each of + * 3 receivers/antennas/rx-chains, then figure out: + * 1) Which antennas are connected. + * 2) Differential rx gain settings to balance the 3 receivers. + */ +void iwl_chain_noise_calibration(struct iwl_priv *priv) +{ + struct iwl_chain_noise_data *data = NULL; + + u32 chain_noise_a; + u32 chain_noise_b; + u32 chain_noise_c; + u32 chain_sig_a; + u32 chain_sig_b; + u32 chain_sig_c; + u32 average_sig[NUM_RX_CHAINS] = {INITIALIZATION_VALUE}; + u32 average_noise[NUM_RX_CHAINS] = {INITIALIZATION_VALUE}; + u32 min_average_noise = MIN_AVERAGE_NOISE_MAX_VALUE; + u16 min_average_noise_antenna_i = INITIALIZATION_VALUE; + u16 i = 0; + u16 rxon_chnum = INITIALIZATION_VALUE; + u16 stat_chnum = INITIALIZATION_VALUE; + u8 rxon_band24; + u8 stat_band24; + struct statistics_rx_non_phy *rx_info; + + /* + * MULTI-FIXME: + * When we support multiple interfaces on different channels, + * this must be modified/fixed. + */ + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + + if (priv->calib_disabled & IWL_CHAIN_NOISE_CALIB_DISABLED) + return; + + data = &(priv->chain_noise_data); + + /* + * Accumulate just the first "chain_noise_num_beacons" after + * the first association, then we're done forever. + */ + if (data->state != IWL_CHAIN_NOISE_ACCUMULATE) { + if (data->state == IWL_CHAIN_NOISE_ALIVE) + IWL_DEBUG_CALIB(priv, "Wait for noise calib reset\n"); + return; + } + + spin_lock_bh(&priv->statistics.lock); + + rx_info = &priv->statistics.rx_non_phy; + + if (rx_info->interference_data_flag != INTERFERENCE_DATA_AVAILABLE) { + IWL_DEBUG_CALIB(priv, " << Interference data unavailable\n"); + spin_unlock_bh(&priv->statistics.lock); + return; + } + + rxon_band24 = !!(ctx->staging.flags & RXON_FLG_BAND_24G_MSK); + rxon_chnum = le16_to_cpu(ctx->staging.channel); + stat_band24 = + !!(priv->statistics.flag & STATISTICS_REPLY_FLG_BAND_24G_MSK); + stat_chnum = le32_to_cpu(priv->statistics.flag) >> 16; + + /* Make sure we accumulate data for just the associated channel + * (even if scanning). */ + if ((rxon_chnum != stat_chnum) || (rxon_band24 != stat_band24)) { + IWL_DEBUG_CALIB(priv, "Stats not from chan=%d, band24=%d\n", + rxon_chnum, rxon_band24); + spin_unlock_bh(&priv->statistics.lock); + return; + } + + /* + * Accumulate beacon statistics values across + * "chain_noise_num_beacons" + */ + chain_noise_a = le32_to_cpu(rx_info->beacon_silence_rssi_a) & + IN_BAND_FILTER; + chain_noise_b = le32_to_cpu(rx_info->beacon_silence_rssi_b) & + IN_BAND_FILTER; + chain_noise_c = le32_to_cpu(rx_info->beacon_silence_rssi_c) & + IN_BAND_FILTER; + + chain_sig_a = le32_to_cpu(rx_info->beacon_rssi_a) & IN_BAND_FILTER; + chain_sig_b = le32_to_cpu(rx_info->beacon_rssi_b) & IN_BAND_FILTER; + chain_sig_c = le32_to_cpu(rx_info->beacon_rssi_c) & IN_BAND_FILTER; + + spin_unlock_bh(&priv->statistics.lock); + + data->beacon_count++; + + data->chain_noise_a = (chain_noise_a + data->chain_noise_a); + data->chain_noise_b = (chain_noise_b + data->chain_noise_b); + data->chain_noise_c = (chain_noise_c + data->chain_noise_c); + + data->chain_signal_a = (chain_sig_a + data->chain_signal_a); + data->chain_signal_b = (chain_sig_b + data->chain_signal_b); + data->chain_signal_c = (chain_sig_c + data->chain_signal_c); + + IWL_DEBUG_CALIB(priv, "chan=%d, band24=%d, beacon=%d\n", + rxon_chnum, rxon_band24, data->beacon_count); + IWL_DEBUG_CALIB(priv, "chain_sig: a %d b %d c %d\n", + chain_sig_a, chain_sig_b, chain_sig_c); + IWL_DEBUG_CALIB(priv, "chain_noise: a %d b %d c %d\n", + chain_noise_a, chain_noise_b, chain_noise_c); + + /* If this is the "chain_noise_num_beacons", determine: + * 1) Disconnected antennas (using signal strengths) + * 2) Differential gain (using silence noise) to balance receivers */ + if (data->beacon_count != IWL_CAL_NUM_BEACONS) + return; + + /* Analyze signal for disconnected antenna */ + if (priv->lib->bt_params && + priv->lib->bt_params->advanced_bt_coexist) { + /* Disable disconnected antenna algorithm for advanced + bt coex, assuming valid antennas are connected */ + data->active_chains = priv->nvm_data->valid_rx_ant; + for (i = 0; i < NUM_RX_CHAINS; i++) + if (!(data->active_chains & (1<<i))) + data->disconn_array[i] = 1; + } else + iwl_find_disconn_antenna(priv, average_sig, data); + + /* Analyze noise for rx balance */ + average_noise[0] = data->chain_noise_a / IWL_CAL_NUM_BEACONS; + average_noise[1] = data->chain_noise_b / IWL_CAL_NUM_BEACONS; + average_noise[2] = data->chain_noise_c / IWL_CAL_NUM_BEACONS; + + for (i = 0; i < NUM_RX_CHAINS; i++) { + if (!(data->disconn_array[i]) && + (average_noise[i] <= min_average_noise)) { + /* This means that chain i is active and has + * lower noise values so far: */ + min_average_noise = average_noise[i]; + min_average_noise_antenna_i = i; + } + } + + IWL_DEBUG_CALIB(priv, "average_noise: a %d b %d c %d\n", + average_noise[0], average_noise[1], + average_noise[2]); + + IWL_DEBUG_CALIB(priv, "min_average_noise = %d, antenna %d\n", + min_average_noise, min_average_noise_antenna_i); + + iwlagn_gain_computation( + priv, average_noise, + find_first_chain(priv->nvm_data->valid_rx_ant)); + + /* Some power changes may have been made during the calibration. + * Update and commit the RXON + */ + iwl_update_chain_flags(priv); + + data->state = IWL_CHAIN_NOISE_DONE; + iwl_power_update_mode(priv, false); +} + +void iwl_reset_run_time_calib(struct iwl_priv *priv) +{ + int i; + memset(&(priv->sensitivity_data), 0, + sizeof(struct iwl_sensitivity_data)); + memset(&(priv->chain_noise_data), 0, + sizeof(struct iwl_chain_noise_data)); + for (i = 0; i < NUM_RX_CHAINS; i++) + priv->chain_noise_data.delta_gain_code[i] = + CHAIN_NOISE_DELTA_GAIN_INIT_VAL; + + /* Ask for statistics now, the uCode will send notification + * periodically after association */ + iwl_send_statistics_request(priv, CMD_ASYNC, true); +} diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/calib.h b/drivers/net/wireless/intel/iwlwifi/dvm/calib.h new file mode 100644 index 000000000..a89b5bad7 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/calib.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014 Intel Corporation + */ +#ifndef __iwl_calib_h__ +#define __iwl_calib_h__ + +#include "dev.h" +#include "commands.h" + +void iwl_chain_noise_calibration(struct iwl_priv *priv); +void iwl_sensitivity_calibration(struct iwl_priv *priv); + +void iwl_init_sensitivity(struct iwl_priv *priv); +void iwl_reset_run_time_calib(struct iwl_priv *priv); + +#endif /* __iwl_calib_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/commands.h b/drivers/net/wireless/intel/iwlwifi/dvm/commands.h new file mode 100644 index 000000000..75a4b8e26 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/commands.h @@ -0,0 +1,3930 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014 Intel Corporation + */ +/* + * Please use this file (commands.h) only for uCode API definitions. + * Please use iwl-xxxx-hw.h for hardware-related definitions. + * Please use dev.h for driver implementation definitions. + */ + +#ifndef __iwl_commands_h__ +#define __iwl_commands_h__ + +#include <linux/ieee80211.h> +#include <linux/types.h> + + +enum { + REPLY_ALIVE = 0x1, + REPLY_ERROR = 0x2, + REPLY_ECHO = 0x3, /* test command */ + + /* RXON and QOS commands */ + REPLY_RXON = 0x10, + REPLY_RXON_ASSOC = 0x11, + REPLY_QOS_PARAM = 0x13, + REPLY_RXON_TIMING = 0x14, + + /* Multi-Station support */ + REPLY_ADD_STA = 0x18, + REPLY_REMOVE_STA = 0x19, + REPLY_REMOVE_ALL_STA = 0x1a, /* not used */ + REPLY_TXFIFO_FLUSH = 0x1e, + + /* Security */ + REPLY_WEPKEY = 0x20, + + /* RX, TX, LEDs */ + REPLY_TX = 0x1c, + REPLY_LEDS_CMD = 0x48, + REPLY_TX_LINK_QUALITY_CMD = 0x4e, + + /* WiMAX coexistence */ + COEX_PRIORITY_TABLE_CMD = 0x5a, + COEX_MEDIUM_NOTIFICATION = 0x5b, + COEX_EVENT_CMD = 0x5c, + + /* Calibration */ + TEMPERATURE_NOTIFICATION = 0x62, + CALIBRATION_CFG_CMD = 0x65, + CALIBRATION_RES_NOTIFICATION = 0x66, + CALIBRATION_COMPLETE_NOTIFICATION = 0x67, + + /* 802.11h related */ + REPLY_QUIET_CMD = 0x71, /* not used */ + REPLY_CHANNEL_SWITCH = 0x72, + CHANNEL_SWITCH_NOTIFICATION = 0x73, + REPLY_SPECTRUM_MEASUREMENT_CMD = 0x74, + SPECTRUM_MEASURE_NOTIFICATION = 0x75, + + /* Power Management */ + POWER_TABLE_CMD = 0x77, + PM_SLEEP_NOTIFICATION = 0x7A, + PM_DEBUG_STATISTIC_NOTIFIC = 0x7B, + + /* Scan commands and notifications */ + REPLY_SCAN_CMD = 0x80, + REPLY_SCAN_ABORT_CMD = 0x81, + SCAN_START_NOTIFICATION = 0x82, + SCAN_RESULTS_NOTIFICATION = 0x83, + SCAN_COMPLETE_NOTIFICATION = 0x84, + + /* IBSS/AP commands */ + BEACON_NOTIFICATION = 0x90, + REPLY_TX_BEACON = 0x91, + WHO_IS_AWAKE_NOTIFICATION = 0x94, /* not used */ + + /* Miscellaneous commands */ + REPLY_TX_POWER_DBM_CMD = 0x95, + QUIET_NOTIFICATION = 0x96, /* not used */ + REPLY_TX_PWR_TABLE_CMD = 0x97, + REPLY_TX_POWER_DBM_CMD_V1 = 0x98, /* old version of API */ + TX_ANT_CONFIGURATION_CMD = 0x98, + MEASURE_ABORT_NOTIFICATION = 0x99, /* not used */ + + /* Bluetooth device coexistence config command */ + REPLY_BT_CONFIG = 0x9b, + + /* Statistics */ + REPLY_STATISTICS_CMD = 0x9c, + STATISTICS_NOTIFICATION = 0x9d, + + /* RF-KILL commands and notifications */ + REPLY_CARD_STATE_CMD = 0xa0, + CARD_STATE_NOTIFICATION = 0xa1, + + /* Missed beacons notification */ + MISSED_BEACONS_NOTIFICATION = 0xa2, + + REPLY_CT_KILL_CONFIG_CMD = 0xa4, + SENSITIVITY_CMD = 0xa8, + REPLY_PHY_CALIBRATION_CMD = 0xb0, + REPLY_RX_PHY_CMD = 0xc0, + REPLY_RX_MPDU_CMD = 0xc1, + REPLY_RX = 0xc3, + REPLY_COMPRESSED_BA = 0xc5, + + /* BT Coex */ + REPLY_BT_COEX_PRIO_TABLE = 0xcc, + REPLY_BT_COEX_PROT_ENV = 0xcd, + REPLY_BT_COEX_PROFILE_NOTIF = 0xce, + + /* PAN commands */ + REPLY_WIPAN_PARAMS = 0xb2, + REPLY_WIPAN_RXON = 0xb3, /* use REPLY_RXON structure */ + REPLY_WIPAN_RXON_TIMING = 0xb4, /* use REPLY_RXON_TIMING structure */ + REPLY_WIPAN_RXON_ASSOC = 0xb6, /* use REPLY_RXON_ASSOC structure */ + REPLY_WIPAN_QOS_PARAM = 0xb7, /* use REPLY_QOS_PARAM structure */ + REPLY_WIPAN_WEPKEY = 0xb8, /* use REPLY_WEPKEY structure */ + REPLY_WIPAN_P2P_CHANNEL_SWITCH = 0xb9, + REPLY_WIPAN_NOA_NOTIFICATION = 0xbc, + REPLY_WIPAN_DEACTIVATION_COMPLETE = 0xbd, + + REPLY_WOWLAN_PATTERNS = 0xe0, + REPLY_WOWLAN_WAKEUP_FILTER = 0xe1, + REPLY_WOWLAN_TSC_RSC_PARAMS = 0xe2, + REPLY_WOWLAN_TKIP_PARAMS = 0xe3, + REPLY_WOWLAN_KEK_KCK_MATERIAL = 0xe4, + REPLY_WOWLAN_GET_STATUS = 0xe5, + REPLY_D3_CONFIG = 0xd3, + + REPLY_MAX = 0xff +}; + +/* + * Minimum number of queues. MAX_NUM is defined in hw specific files. + * Set the minimum to accommodate + * - 4 standard TX queues + * - the command queue + * - 4 PAN TX queues + * - the PAN multicast queue, and + * - the AUX (TX during scan dwell) queue. + */ +#define IWL_MIN_NUM_QUEUES 11 + +/* + * Command queue depends on iPAN support. + */ +#define IWL_DEFAULT_CMD_QUEUE_NUM 4 +#define IWL_IPAN_CMD_QUEUE_NUM 9 + +#define IWL_TX_FIFO_BK 0 /* shared */ +#define IWL_TX_FIFO_BE 1 +#define IWL_TX_FIFO_VI 2 /* shared */ +#define IWL_TX_FIFO_VO 3 +#define IWL_TX_FIFO_BK_IPAN IWL_TX_FIFO_BK +#define IWL_TX_FIFO_BE_IPAN 4 +#define IWL_TX_FIFO_VI_IPAN IWL_TX_FIFO_VI +#define IWL_TX_FIFO_VO_IPAN 5 +/* re-uses the VO FIFO, uCode will properly flush/schedule */ +#define IWL_TX_FIFO_AUX 5 +#define IWL_TX_FIFO_UNUSED 255 + +#define IWLAGN_CMD_FIFO_NUM 7 + +/* + * This queue number is required for proper operation + * because the ucode will stop/start the scheduler as + * required. + */ +#define IWL_IPAN_MCAST_QUEUE 8 + +/****************************************************************************** + * (0) + * Commonly used structures and definitions: + * Command header, rate_n_flags, txpower + * + *****************************************************************************/ + +/** + * iwlagn rate_n_flags bit fields + * + * rate_n_flags format is used in following iwlagn commands: + * REPLY_RX (response only) + * REPLY_RX_MPDU (response only) + * REPLY_TX (both command and response) + * REPLY_TX_LINK_QUALITY_CMD + * + * High-throughput (HT) rate format for bits 7:0 (bit 8 must be "1"): + * 2-0: 0) 6 Mbps + * 1) 12 Mbps + * 2) 18 Mbps + * 3) 24 Mbps + * 4) 36 Mbps + * 5) 48 Mbps + * 6) 54 Mbps + * 7) 60 Mbps + * + * 4-3: 0) Single stream (SISO) + * 1) Dual stream (MIMO) + * 2) Triple stream (MIMO) + * + * 5: Value of 0x20 in bits 7:0 indicates 6 Mbps HT40 duplicate data + * + * Legacy OFDM rate format for bits 7:0 (bit 8 must be "0", bit 9 "0"): + * 3-0: 0xD) 6 Mbps + * 0xF) 9 Mbps + * 0x5) 12 Mbps + * 0x7) 18 Mbps + * 0x9) 24 Mbps + * 0xB) 36 Mbps + * 0x1) 48 Mbps + * 0x3) 54 Mbps + * + * Legacy CCK rate format for bits 7:0 (bit 8 must be "0", bit 9 "1"): + * 6-0: 10) 1 Mbps + * 20) 2 Mbps + * 55) 5.5 Mbps + * 110) 11 Mbps + */ +#define RATE_MCS_CODE_MSK 0x7 +#define RATE_MCS_SPATIAL_POS 3 +#define RATE_MCS_SPATIAL_MSK 0x18 +#define RATE_MCS_HT_DUP_POS 5 +#define RATE_MCS_HT_DUP_MSK 0x20 +/* Both legacy and HT use bits 7:0 as the CCK/OFDM rate or HT MCS */ +#define RATE_MCS_RATE_MSK 0xff + +/* Bit 8: (1) HT format, (0) legacy format in bits 7:0 */ +#define RATE_MCS_FLAGS_POS 8 +#define RATE_MCS_HT_POS 8 +#define RATE_MCS_HT_MSK 0x100 + +/* Bit 9: (1) CCK, (0) OFDM. HT (bit 8) must be "0" for this bit to be valid */ +#define RATE_MCS_CCK_POS 9 +#define RATE_MCS_CCK_MSK 0x200 + +/* Bit 10: (1) Use Green Field preamble */ +#define RATE_MCS_GF_POS 10 +#define RATE_MCS_GF_MSK 0x400 + +/* Bit 11: (1) Use 40Mhz HT40 chnl width, (0) use 20 MHz legacy chnl width */ +#define RATE_MCS_HT40_POS 11 +#define RATE_MCS_HT40_MSK 0x800 + +/* Bit 12: (1) Duplicate data on both 20MHz chnls. HT40 (bit 11) must be set. */ +#define RATE_MCS_DUP_POS 12 +#define RATE_MCS_DUP_MSK 0x1000 + +/* Bit 13: (1) Short guard interval (0.4 usec), (0) normal GI (0.8 usec) */ +#define RATE_MCS_SGI_POS 13 +#define RATE_MCS_SGI_MSK 0x2000 + +/** + * rate_n_flags Tx antenna masks + * bit14:16 + */ +#define RATE_MCS_ANT_POS 14 +#define RATE_MCS_ANT_A_MSK 0x04000 +#define RATE_MCS_ANT_B_MSK 0x08000 +#define RATE_MCS_ANT_C_MSK 0x10000 +#define RATE_MCS_ANT_AB_MSK (RATE_MCS_ANT_A_MSK | RATE_MCS_ANT_B_MSK) +#define RATE_MCS_ANT_ABC_MSK (RATE_MCS_ANT_AB_MSK | RATE_MCS_ANT_C_MSK) +#define RATE_ANT_NUM 3 + +#define POWER_TABLE_NUM_ENTRIES 33 +#define POWER_TABLE_NUM_HT_OFDM_ENTRIES 32 +#define POWER_TABLE_CCK_ENTRY 32 + +#define IWL_PWR_NUM_HT_OFDM_ENTRIES 24 +#define IWL_PWR_CCK_ENTRIES 2 + +/** + * struct tx_power_dual_stream + * + * Table entries in REPLY_TX_PWR_TABLE_CMD, REPLY_CHANNEL_SWITCH + * + * Same format as iwl_tx_power_dual_stream, but __le32 + */ +struct tx_power_dual_stream { + __le32 dw; +} __packed; + +/** + * Command REPLY_TX_POWER_DBM_CMD = 0x98 + * struct iwlagn_tx_power_dbm_cmd + */ +#define IWLAGN_TX_POWER_AUTO 0x7f +#define IWLAGN_TX_POWER_NO_CLOSED (0x1 << 6) + +struct iwlagn_tx_power_dbm_cmd { + s8 global_lmt; /*in half-dBm (e.g. 30 = 15 dBm) */ + u8 flags; + s8 srv_chan_lmt; /*in half-dBm (e.g. 30 = 15 dBm) */ + u8 reserved; +} __packed; + +/** + * Command TX_ANT_CONFIGURATION_CMD = 0x98 + * This command is used to configure valid Tx antenna. + * By default uCode concludes the valid antenna according to the radio flavor. + * This command enables the driver to override/modify this conclusion. + */ +struct iwl_tx_ant_config_cmd { + __le32 valid; +} __packed; + +/****************************************************************************** + * (0a) + * Alive and Error Commands & Responses: + * + *****************************************************************************/ + +#define UCODE_VALID_OK cpu_to_le32(0x1) + +/** + * REPLY_ALIVE = 0x1 (response only, not a command) + * + * uCode issues this "alive" notification once the runtime image is ready + * to receive commands from the driver. This is the *second* "alive" + * notification that the driver will receive after rebooting uCode; + * this "alive" is indicated by subtype field != 9. + * + * See comments documenting "BSM" (bootstrap state machine). + * + * This response includes two pointers to structures within the device's + * data SRAM (access via HBUS_TARG_MEM_* regs) that are useful for debugging: + * + * 1) log_event_table_ptr indicates base of the event log. This traces + * a 256-entry history of uCode execution within a circular buffer. + * Its header format is: + * + * __le32 log_size; log capacity (in number of entries) + * __le32 type; (1) timestamp with each entry, (0) no timestamp + * __le32 wraps; # times uCode has wrapped to top of circular buffer + * __le32 write_index; next circular buffer entry that uCode would fill + * + * The header is followed by the circular buffer of log entries. Entries + * with timestamps have the following format: + * + * __le32 event_id; range 0 - 1500 + * __le32 timestamp; low 32 bits of TSF (of network, if associated) + * __le32 data; event_id-specific data value + * + * Entries without timestamps contain only event_id and data. + * + * + * 2) error_event_table_ptr indicates base of the error log. This contains + * information about any uCode error that occurs. For agn, the format + * of the error log is defined by struct iwl_error_event_table. + * + * The Linux driver can print both logs to the system log when a uCode error + * occurs. + */ + +/* + * Note: This structure is read from the device with IO accesses, + * and the reading already does the endian conversion. As it is + * read with u32-sized accesses, any members with a different size + * need to be ordered correctly though! + */ +struct iwl_error_event_table { + u32 valid; /* (nonzero) valid, (0) log is empty */ + u32 error_id; /* type of error */ + u32 pc; /* program counter */ + u32 blink1; /* branch link */ + u32 blink2; /* branch link */ + u32 ilink1; /* interrupt link */ + u32 ilink2; /* interrupt link */ + u32 data1; /* error-specific data */ + u32 data2; /* error-specific data */ + u32 line; /* source code line of error */ + u32 bcon_time; /* beacon timer */ + u32 tsf_low; /* network timestamp function timer */ + u32 tsf_hi; /* network timestamp function timer */ + u32 gp1; /* GP1 timer register */ + u32 gp2; /* GP2 timer register */ + u32 gp3; /* GP3 timer register */ + u32 ucode_ver; /* uCode version */ + u32 hw_ver; /* HW Silicon version */ + u32 brd_ver; /* HW board version */ + u32 log_pc; /* log program counter */ + u32 frame_ptr; /* frame pointer */ + u32 stack_ptr; /* stack pointer */ + u32 hcmd; /* last host command header */ + u32 isr0; /* isr status register LMPM_NIC_ISR0: + * rxtx_flag */ + u32 isr1; /* isr status register LMPM_NIC_ISR1: + * host_flag */ + u32 isr2; /* isr status register LMPM_NIC_ISR2: + * enc_flag */ + u32 isr3; /* isr status register LMPM_NIC_ISR3: + * time_flag */ + u32 isr4; /* isr status register LMPM_NIC_ISR4: + * wico interrupt */ + u32 isr_pref; /* isr status register LMPM_NIC_PREF_STAT */ + u32 wait_event; /* wait event() caller address */ + u32 l2p_control; /* L2pControlField */ + u32 l2p_duration; /* L2pDurationField */ + u32 l2p_mhvalid; /* L2pMhValidBits */ + u32 l2p_addr_match; /* L2pAddrMatchStat */ + u32 lmpm_pmg_sel; /* indicate which clocks are turned on + * (LMPM_PMG_SEL) */ + u32 u_timestamp; /* indicate when the date and time of the + * compilation */ + u32 flow_handler; /* FH read/write pointers, RX credit */ +} __packed; + +struct iwl_alive_resp { + u8 ucode_minor; + u8 ucode_major; + __le16 reserved1; + u8 sw_rev[8]; + u8 ver_type; + u8 ver_subtype; /* not "9" for runtime alive */ + __le16 reserved2; + __le32 log_event_table_ptr; /* SRAM address for event log */ + __le32 error_event_table_ptr; /* SRAM address for error log */ + __le32 timestamp; + __le32 is_valid; +} __packed; + +/* + * REPLY_ERROR = 0x2 (response only, not a command) + */ +struct iwl_error_resp { + __le32 error_type; + u8 cmd_id; + u8 reserved1; + __le16 bad_cmd_seq_num; + __le32 error_info; + __le64 timestamp; +} __packed; + +/****************************************************************************** + * (1) + * RXON Commands & Responses: + * + *****************************************************************************/ + +/* + * Rx config defines & structure + */ +/* rx_config device types */ +enum { + RXON_DEV_TYPE_AP = 1, + RXON_DEV_TYPE_ESS = 3, + RXON_DEV_TYPE_IBSS = 4, + RXON_DEV_TYPE_SNIFFER = 6, + RXON_DEV_TYPE_CP = 7, + RXON_DEV_TYPE_2STA = 8, + RXON_DEV_TYPE_P2P = 9, +}; + + +#define RXON_RX_CHAIN_DRIVER_FORCE_MSK cpu_to_le16(0x1 << 0) +#define RXON_RX_CHAIN_DRIVER_FORCE_POS (0) +#define RXON_RX_CHAIN_VALID_MSK cpu_to_le16(0x7 << 1) +#define RXON_RX_CHAIN_VALID_POS (1) +#define RXON_RX_CHAIN_FORCE_SEL_MSK cpu_to_le16(0x7 << 4) +#define RXON_RX_CHAIN_FORCE_SEL_POS (4) +#define RXON_RX_CHAIN_FORCE_MIMO_SEL_MSK cpu_to_le16(0x7 << 7) +#define RXON_RX_CHAIN_FORCE_MIMO_SEL_POS (7) +#define RXON_RX_CHAIN_CNT_MSK cpu_to_le16(0x3 << 10) +#define RXON_RX_CHAIN_CNT_POS (10) +#define RXON_RX_CHAIN_MIMO_CNT_MSK cpu_to_le16(0x3 << 12) +#define RXON_RX_CHAIN_MIMO_CNT_POS (12) +#define RXON_RX_CHAIN_MIMO_FORCE_MSK cpu_to_le16(0x1 << 14) +#define RXON_RX_CHAIN_MIMO_FORCE_POS (14) + +/* rx_config flags */ +/* band & modulation selection */ +#define RXON_FLG_BAND_24G_MSK cpu_to_le32(1 << 0) +#define RXON_FLG_CCK_MSK cpu_to_le32(1 << 1) +/* auto detection enable */ +#define RXON_FLG_AUTO_DETECT_MSK cpu_to_le32(1 << 2) +/* TGg protection when tx */ +#define RXON_FLG_TGG_PROTECT_MSK cpu_to_le32(1 << 3) +/* cck short slot & preamble */ +#define RXON_FLG_SHORT_SLOT_MSK cpu_to_le32(1 << 4) +#define RXON_FLG_SHORT_PREAMBLE_MSK cpu_to_le32(1 << 5) +/* antenna selection */ +#define RXON_FLG_DIS_DIV_MSK cpu_to_le32(1 << 7) +#define RXON_FLG_ANT_SEL_MSK cpu_to_le32(0x0f00) +#define RXON_FLG_ANT_A_MSK cpu_to_le32(1 << 8) +#define RXON_FLG_ANT_B_MSK cpu_to_le32(1 << 9) +/* radar detection enable */ +#define RXON_FLG_RADAR_DETECT_MSK cpu_to_le32(1 << 12) +#define RXON_FLG_TGJ_NARROW_BAND_MSK cpu_to_le32(1 << 13) +/* rx response to host with 8-byte TSF +* (according to ON_AIR deassertion) */ +#define RXON_FLG_TSF2HOST_MSK cpu_to_le32(1 << 15) + + +/* HT flags */ +#define RXON_FLG_CTRL_CHANNEL_LOC_POS (22) +#define RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK cpu_to_le32(0x1 << 22) + +#define RXON_FLG_HT_OPERATING_MODE_POS (23) + +#define RXON_FLG_HT_PROT_MSK cpu_to_le32(0x1 << 23) +#define RXON_FLG_HT40_PROT_MSK cpu_to_le32(0x2 << 23) + +#define RXON_FLG_CHANNEL_MODE_POS (25) +#define RXON_FLG_CHANNEL_MODE_MSK cpu_to_le32(0x3 << 25) + +/* channel mode */ +enum { + CHANNEL_MODE_LEGACY = 0, + CHANNEL_MODE_PURE_40 = 1, + CHANNEL_MODE_MIXED = 2, + CHANNEL_MODE_RESERVED = 3, +}; +#define RXON_FLG_CHANNEL_MODE_LEGACY cpu_to_le32(CHANNEL_MODE_LEGACY << RXON_FLG_CHANNEL_MODE_POS) +#define RXON_FLG_CHANNEL_MODE_PURE_40 cpu_to_le32(CHANNEL_MODE_PURE_40 << RXON_FLG_CHANNEL_MODE_POS) +#define RXON_FLG_CHANNEL_MODE_MIXED cpu_to_le32(CHANNEL_MODE_MIXED << RXON_FLG_CHANNEL_MODE_POS) + +/* CTS to self (if spec allows) flag */ +#define RXON_FLG_SELF_CTS_EN cpu_to_le32(0x1<<30) + +/* rx_config filter flags */ +/* accept all data frames */ +#define RXON_FILTER_PROMISC_MSK cpu_to_le32(1 << 0) +/* pass control & management to host */ +#define RXON_FILTER_CTL2HOST_MSK cpu_to_le32(1 << 1) +/* accept multi-cast */ +#define RXON_FILTER_ACCEPT_GRP_MSK cpu_to_le32(1 << 2) +/* don't decrypt uni-cast frames */ +#define RXON_FILTER_DIS_DECRYPT_MSK cpu_to_le32(1 << 3) +/* don't decrypt multi-cast frames */ +#define RXON_FILTER_DIS_GRP_DECRYPT_MSK cpu_to_le32(1 << 4) +/* STA is associated */ +#define RXON_FILTER_ASSOC_MSK cpu_to_le32(1 << 5) +/* transfer to host non bssid beacons in associated state */ +#define RXON_FILTER_BCON_AWARE_MSK cpu_to_le32(1 << 6) + +/** + * REPLY_RXON = 0x10 (command, has simple generic response) + * + * RXON tunes the radio tuner to a service channel, and sets up a number + * of parameters that are used primarily for Rx, but also for Tx operations. + * + * NOTE: When tuning to a new channel, driver must set the + * RXON_FILTER_ASSOC_MSK to 0. This will clear station-dependent + * info within the device, including the station tables, tx retry + * rate tables, and txpower tables. Driver must build a new station + * table and txpower table before transmitting anything on the RXON + * channel. + * + * NOTE: All RXONs wipe clean the internal txpower table. Driver must + * issue a new REPLY_TX_PWR_TABLE_CMD after each REPLY_RXON (0x10), + * regardless of whether RXON_FILTER_ASSOC_MSK is set. + */ + +struct iwl_rxon_cmd { + u8 node_addr[6]; + __le16 reserved1; + u8 bssid_addr[6]; + __le16 reserved2; + u8 wlap_bssid_addr[6]; + __le16 reserved3; + u8 dev_type; + u8 air_propagation; + __le16 rx_chain; + u8 ofdm_basic_rates; + u8 cck_basic_rates; + __le16 assoc_id; + __le32 flags; + __le32 filter_flags; + __le16 channel; + u8 ofdm_ht_single_stream_basic_rates; + u8 ofdm_ht_dual_stream_basic_rates; + u8 ofdm_ht_triple_stream_basic_rates; + u8 reserved5; + __le16 acquisition_data; + __le16 reserved6; +} __packed; + +/* + * REPLY_RXON_ASSOC = 0x11 (command, has simple generic response) + */ +struct iwl_rxon_assoc_cmd { + __le32 flags; + __le32 filter_flags; + u8 ofdm_basic_rates; + u8 cck_basic_rates; + __le16 reserved1; + u8 ofdm_ht_single_stream_basic_rates; + u8 ofdm_ht_dual_stream_basic_rates; + u8 ofdm_ht_triple_stream_basic_rates; + u8 reserved2; + __le16 rx_chain_select_flags; + __le16 acquisition_data; + __le32 reserved3; +} __packed; + +#define IWL_CONN_MAX_LISTEN_INTERVAL 10 +#define IWL_MAX_UCODE_BEACON_INTERVAL 4 /* 4096 */ + +/* + * REPLY_RXON_TIMING = 0x14 (command, has simple generic response) + */ +struct iwl_rxon_time_cmd { + __le64 timestamp; + __le16 beacon_interval; + __le16 atim_window; + __le32 beacon_init_val; + __le16 listen_interval; + u8 dtim_period; + u8 delta_cp_bss_tbtts; +} __packed; + +/* + * REPLY_CHANNEL_SWITCH = 0x72 (command, has simple generic response) + */ +/** + * struct iwl5000_channel_switch_cmd + * @band: 0- 5.2GHz, 1- 2.4GHz + * @expect_beacon: 0- resume transmits after channel switch + * 1- wait for beacon to resume transmits + * @channel: new channel number + * @rxon_flags: Rx on flags + * @rxon_filter_flags: filtering parameters + * @switch_time: switch time in extended beacon format + * @reserved: reserved bytes + */ +struct iwl5000_channel_switch_cmd { + u8 band; + u8 expect_beacon; + __le16 channel; + __le32 rxon_flags; + __le32 rxon_filter_flags; + __le32 switch_time; + __le32 reserved[2][IWL_PWR_NUM_HT_OFDM_ENTRIES + IWL_PWR_CCK_ENTRIES]; +} __packed; + +/** + * struct iwl6000_channel_switch_cmd + * @band: 0- 5.2GHz, 1- 2.4GHz + * @expect_beacon: 0- resume transmits after channel switch + * 1- wait for beacon to resume transmits + * @channel: new channel number + * @rxon_flags: Rx on flags + * @rxon_filter_flags: filtering parameters + * @switch_time: switch time in extended beacon format + * @reserved: reserved bytes + */ +struct iwl6000_channel_switch_cmd { + u8 band; + u8 expect_beacon; + __le16 channel; + __le32 rxon_flags; + __le32 rxon_filter_flags; + __le32 switch_time; + __le32 reserved[3][IWL_PWR_NUM_HT_OFDM_ENTRIES + IWL_PWR_CCK_ENTRIES]; +} __packed; + +/* + * CHANNEL_SWITCH_NOTIFICATION = 0x73 (notification only, not a command) + */ +struct iwl_csa_notification { + __le16 band; + __le16 channel; + __le32 status; /* 0 - OK, 1 - fail */ +} __packed; + +/****************************************************************************** + * (2) + * Quality-of-Service (QOS) Commands & Responses: + * + *****************************************************************************/ + +/** + * struct iwl_ac_qos -- QOS timing params for REPLY_QOS_PARAM + * One for each of 4 EDCA access categories in struct iwl_qosparam_cmd + * + * @cw_min: Contention window, start value in numbers of slots. + * Should be a power-of-2, minus 1. Device's default is 0x0f. + * @cw_max: Contention window, max value in numbers of slots. + * Should be a power-of-2, minus 1. Device's default is 0x3f. + * @aifsn: Number of slots in Arbitration Interframe Space (before + * performing random backoff timing prior to Tx). Device default 1. + * @edca_txop: Length of Tx opportunity, in uSecs. Device default is 0. + * + * Device will automatically increase contention window by (2*CW) + 1 for each + * transmission retry. Device uses cw_max as a bit mask, ANDed with new CW + * value, to cap the CW value. + */ +struct iwl_ac_qos { + __le16 cw_min; + __le16 cw_max; + u8 aifsn; + u8 reserved1; + __le16 edca_txop; +} __packed; + +/* QoS flags defines */ +#define QOS_PARAM_FLG_UPDATE_EDCA_MSK cpu_to_le32(0x01) +#define QOS_PARAM_FLG_TGN_MSK cpu_to_le32(0x02) +#define QOS_PARAM_FLG_TXOP_TYPE_MSK cpu_to_le32(0x10) + +/* Number of Access Categories (AC) (EDCA), queues 0..3 */ +#define AC_NUM 4 + +/* + * REPLY_QOS_PARAM = 0x13 (command, has simple generic response) + * + * This command sets up timings for each of the 4 prioritized EDCA Tx FIFOs + * 0: Background, 1: Best Effort, 2: Video, 3: Voice. + */ +struct iwl_qosparam_cmd { + __le32 qos_flags; + struct iwl_ac_qos ac[AC_NUM]; +} __packed; + +/****************************************************************************** + * (3) + * Add/Modify Stations Commands & Responses: + * + *****************************************************************************/ +/* + * Multi station support + */ + +/* Special, dedicated locations within device's station table */ +#define IWL_AP_ID 0 +#define IWL_AP_ID_PAN 1 +#define IWL_STA_ID 2 +#define IWLAGN_PAN_BCAST_ID 14 +#define IWLAGN_BROADCAST_ID 15 +#define IWLAGN_STATION_COUNT 16 + +#define IWL_TID_NON_QOS IWL_MAX_TID_COUNT + +#define STA_FLG_TX_RATE_MSK cpu_to_le32(1 << 2) +#define STA_FLG_PWR_SAVE_MSK cpu_to_le32(1 << 8) +#define STA_FLG_PAN_STATION cpu_to_le32(1 << 13) +#define STA_FLG_RTS_MIMO_PROT_MSK cpu_to_le32(1 << 17) +#define STA_FLG_AGG_MPDU_8US_MSK cpu_to_le32(1 << 18) +#define STA_FLG_MAX_AGG_SIZE_POS (19) +#define STA_FLG_MAX_AGG_SIZE_MSK cpu_to_le32(3 << 19) +#define STA_FLG_HT40_EN_MSK cpu_to_le32(1 << 21) +#define STA_FLG_MIMO_DIS_MSK cpu_to_le32(1 << 22) +#define STA_FLG_AGG_MPDU_DENSITY_POS (23) +#define STA_FLG_AGG_MPDU_DENSITY_MSK cpu_to_le32(7 << 23) + +/* Use in mode field. 1: modify existing entry, 0: add new station entry */ +#define STA_CONTROL_MODIFY_MSK 0x01 + +/* key flags __le16*/ +#define STA_KEY_FLG_ENCRYPT_MSK cpu_to_le16(0x0007) +#define STA_KEY_FLG_NO_ENC cpu_to_le16(0x0000) +#define STA_KEY_FLG_WEP cpu_to_le16(0x0001) +#define STA_KEY_FLG_CCMP cpu_to_le16(0x0002) +#define STA_KEY_FLG_TKIP cpu_to_le16(0x0003) + +#define STA_KEY_FLG_KEYID_POS 8 +#define STA_KEY_FLG_INVALID cpu_to_le16(0x0800) +/* wep key is either from global key (0) or from station info array (1) */ +#define STA_KEY_FLG_MAP_KEY_MSK cpu_to_le16(0x0008) + +/* wep key in STA: 5-bytes (0) or 13-bytes (1) */ +#define STA_KEY_FLG_KEY_SIZE_MSK cpu_to_le16(0x1000) +#define STA_KEY_MULTICAST_MSK cpu_to_le16(0x4000) +#define STA_KEY_MAX_NUM 8 +#define STA_KEY_MAX_NUM_PAN 16 +/* must not match WEP_INVALID_OFFSET */ +#define IWLAGN_HW_KEY_DEFAULT 0xfe + +/* Flags indicate whether to modify vs. don't change various station params */ +#define STA_MODIFY_KEY_MASK 0x01 +#define STA_MODIFY_TID_DISABLE_TX 0x02 +#define STA_MODIFY_TX_RATE_MSK 0x04 +#define STA_MODIFY_ADDBA_TID_MSK 0x08 +#define STA_MODIFY_DELBA_TID_MSK 0x10 +#define STA_MODIFY_SLEEP_TX_COUNT_MSK 0x20 + +/* agn */ +struct iwl_keyinfo { + __le16 key_flags; + u8 tkip_rx_tsc_byte2; /* TSC[2] for key mix ph1 detection */ + u8 reserved1; + __le16 tkip_rx_ttak[5]; /* 10-byte unicast TKIP TTAK */ + u8 key_offset; + u8 reserved2; + u8 key[16]; /* 16-byte unicast decryption key */ + __le64 tx_secur_seq_cnt; + __le64 hw_tkip_mic_rx_key; + __le64 hw_tkip_mic_tx_key; +} __packed; + +/** + * struct sta_id_modify + * @addr[ETH_ALEN]: station's MAC address + * @sta_id: index of station in uCode's station table + * @modify_mask: STA_MODIFY_*, 1: modify, 0: don't change + * + * Driver selects unused table index when adding new station, + * or the index to a pre-existing station entry when modifying that station. + * Some indexes have special purposes (IWL_AP_ID, index 0, is for AP). + * + * modify_mask flags select which parameters to modify vs. leave alone. + */ +struct sta_id_modify { + u8 addr[ETH_ALEN]; + __le16 reserved1; + u8 sta_id; + u8 modify_mask; + __le16 reserved2; +} __packed; + +/* + * REPLY_ADD_STA = 0x18 (command) + * + * The device contains an internal table of per-station information, + * with info on security keys, aggregation parameters, and Tx rates for + * initial Tx attempt and any retries (agn devices uses + * REPLY_TX_LINK_QUALITY_CMD, + * + * REPLY_ADD_STA sets up the table entry for one station, either creating + * a new entry, or modifying a pre-existing one. + * + * NOTE: RXON command (without "associated" bit set) wipes the station table + * clean. Moving into RF_KILL state does this also. Driver must set up + * new station table before transmitting anything on the RXON channel + * (except active scans or active measurements; those commands carry + * their own txpower/rate setup data). + * + * When getting started on a new channel, driver must set up the + * IWL_BROADCAST_ID entry (last entry in the table). For a client + * station in a BSS, once an AP is selected, driver sets up the AP STA + * in the IWL_AP_ID entry (1st entry in the table). BROADCAST and AP + * are all that are needed for a BSS client station. If the device is + * used as AP, or in an IBSS network, driver must set up station table + * entries for all STAs in network, starting with index IWL_STA_ID. + */ + +struct iwl_addsta_cmd { + u8 mode; /* 1: modify existing, 0: add new station */ + u8 reserved[3]; + struct sta_id_modify sta; + struct iwl_keyinfo key; + __le32 station_flags; /* STA_FLG_* */ + __le32 station_flags_msk; /* STA_FLG_* */ + + /* bit field to disable (1) or enable (0) Tx for Traffic ID (TID) + * corresponding to bit (e.g. bit 5 controls TID 5). + * Set modify_mask bit STA_MODIFY_TID_DISABLE_TX to use this field. */ + __le16 tid_disable_tx; + __le16 legacy_reserved; + + /* TID for which to add block-ack support. + * Set modify_mask bit STA_MODIFY_ADDBA_TID_MSK to use this field. */ + u8 add_immediate_ba_tid; + + /* TID for which to remove block-ack support. + * Set modify_mask bit STA_MODIFY_DELBA_TID_MSK to use this field. */ + u8 remove_immediate_ba_tid; + + /* Starting Sequence Number for added block-ack support. + * Set modify_mask bit STA_MODIFY_ADDBA_TID_MSK to use this field. */ + __le16 add_immediate_ba_ssn; + + /* + * Number of packets OK to transmit to station even though + * it is asleep -- used to synchronise PS-poll and u-APSD + * responses while ucode keeps track of STA sleep state. + */ + __le16 sleep_tx_count; + + __le16 reserved2; +} __packed; + + +#define ADD_STA_SUCCESS_MSK 0x1 +#define ADD_STA_NO_ROOM_IN_TABLE 0x2 +#define ADD_STA_NO_BLOCK_ACK_RESOURCE 0x4 +#define ADD_STA_MODIFY_NON_EXIST_STA 0x8 +/* + * REPLY_ADD_STA = 0x18 (response) + */ +struct iwl_add_sta_resp { + u8 status; /* ADD_STA_* */ +} __packed; + +#define REM_STA_SUCCESS_MSK 0x1 +/* + * REPLY_REM_STA = 0x19 (response) + */ +struct iwl_rem_sta_resp { + u8 status; +} __packed; + +/* + * REPLY_REM_STA = 0x19 (command) + */ +struct iwl_rem_sta_cmd { + u8 num_sta; /* number of removed stations */ + u8 reserved[3]; + u8 addr[ETH_ALEN]; /* MAC addr of the first station */ + u8 reserved2[2]; +} __packed; + + +/* WiFi queues mask */ +#define IWL_SCD_BK_MSK BIT(0) +#define IWL_SCD_BE_MSK BIT(1) +#define IWL_SCD_VI_MSK BIT(2) +#define IWL_SCD_VO_MSK BIT(3) +#define IWL_SCD_MGMT_MSK BIT(3) + +/* PAN queues mask */ +#define IWL_PAN_SCD_BK_MSK BIT(4) +#define IWL_PAN_SCD_BE_MSK BIT(5) +#define IWL_PAN_SCD_VI_MSK BIT(6) +#define IWL_PAN_SCD_VO_MSK BIT(7) +#define IWL_PAN_SCD_MGMT_MSK BIT(7) +#define IWL_PAN_SCD_MULTICAST_MSK BIT(8) + +#define IWL_AGG_TX_QUEUE_MSK 0xffc00 + +#define IWL_DROP_ALL BIT(1) + +/* + * REPLY_TXFIFO_FLUSH = 0x1e(command and response) + * + * When using full FIFO flush this command checks the scheduler HW block WR/RD + * pointers to check if all the frames were transferred by DMA into the + * relevant TX FIFO queue. Only when the DMA is finished and the queue is + * empty the command can finish. + * This command is used to flush the TXFIFO from transmit commands, it may + * operate on single or multiple queues, the command queue can't be flushed by + * this command. The command response is returned when all the queue flush + * operations are done. Each TX command flushed return response with the FLUSH + * status set in the TX response status. When FIFO flush operation is used, + * the flush operation ends when both the scheduler DMA done and TXFIFO empty + * are set. + * + * @queue_control: bit mask for which queues to flush + * @flush_control: flush controls + * 0: Dump single MSDU + * 1: Dump multiple MSDU according to PS, INVALID STA, TTL, TID disable. + * 2: Dump all FIFO + */ +struct iwl_txfifo_flush_cmd_v3 { + __le32 queue_control; + __le16 flush_control; + __le16 reserved; +} __packed; + +struct iwl_txfifo_flush_cmd_v2 { + __le16 queue_control; + __le16 flush_control; +} __packed; + +/* + * REPLY_WEP_KEY = 0x20 + */ +struct iwl_wep_key { + u8 key_index; + u8 key_offset; + u8 reserved1[2]; + u8 key_size; + u8 reserved2[3]; + u8 key[16]; +} __packed; + +struct iwl_wep_cmd { + u8 num_keys; + u8 global_key_type; + u8 flags; + u8 reserved; + struct iwl_wep_key key[]; +} __packed; + +#define WEP_KEY_WEP_TYPE 1 +#define WEP_KEYS_MAX 4 +#define WEP_INVALID_OFFSET 0xff +#define WEP_KEY_LEN_64 5 +#define WEP_KEY_LEN_128 13 + +/****************************************************************************** + * (4) + * Rx Responses: + * + *****************************************************************************/ + +#define RX_RES_STATUS_NO_CRC32_ERROR cpu_to_le32(1 << 0) +#define RX_RES_STATUS_NO_RXE_OVERFLOW cpu_to_le32(1 << 1) + +#define RX_RES_PHY_FLAGS_BAND_24_MSK cpu_to_le16(1 << 0) +#define RX_RES_PHY_FLAGS_MOD_CCK_MSK cpu_to_le16(1 << 1) +#define RX_RES_PHY_FLAGS_SHORT_PREAMBLE_MSK cpu_to_le16(1 << 2) +#define RX_RES_PHY_FLAGS_NARROW_BAND_MSK cpu_to_le16(1 << 3) +#define RX_RES_PHY_FLAGS_ANTENNA_MSK 0x70 +#define RX_RES_PHY_FLAGS_ANTENNA_POS 4 +#define RX_RES_PHY_FLAGS_AGG_MSK cpu_to_le16(1 << 7) + +#define RX_RES_STATUS_SEC_TYPE_MSK (0x7 << 8) +#define RX_RES_STATUS_SEC_TYPE_NONE (0x0 << 8) +#define RX_RES_STATUS_SEC_TYPE_WEP (0x1 << 8) +#define RX_RES_STATUS_SEC_TYPE_CCMP (0x2 << 8) +#define RX_RES_STATUS_SEC_TYPE_TKIP (0x3 << 8) +#define RX_RES_STATUS_SEC_TYPE_ERR (0x7 << 8) + +#define RX_RES_STATUS_STATION_FOUND (1<<6) +#define RX_RES_STATUS_NO_STATION_INFO_MISMATCH (1<<7) + +#define RX_RES_STATUS_DECRYPT_TYPE_MSK (0x3 << 11) +#define RX_RES_STATUS_NOT_DECRYPT (0x0 << 11) +#define RX_RES_STATUS_DECRYPT_OK (0x3 << 11) +#define RX_RES_STATUS_BAD_ICV_MIC (0x1 << 11) +#define RX_RES_STATUS_BAD_KEY_TTAK (0x2 << 11) + +#define RX_MPDU_RES_STATUS_ICV_OK (0x20) +#define RX_MPDU_RES_STATUS_MIC_OK (0x40) +#define RX_MPDU_RES_STATUS_TTAK_OK (1 << 7) +#define RX_MPDU_RES_STATUS_DEC_DONE_MSK (0x800) + + +#define IWLAGN_RX_RES_PHY_CNT 8 +#define IWLAGN_RX_RES_AGC_IDX 1 +#define IWLAGN_RX_RES_RSSI_AB_IDX 2 +#define IWLAGN_RX_RES_RSSI_C_IDX 3 +#define IWLAGN_OFDM_AGC_MSK 0xfe00 +#define IWLAGN_OFDM_AGC_BIT_POS 9 +#define IWLAGN_OFDM_RSSI_INBAND_A_BITMSK 0x00ff +#define IWLAGN_OFDM_RSSI_ALLBAND_A_BITMSK 0xff00 +#define IWLAGN_OFDM_RSSI_A_BIT_POS 0 +#define IWLAGN_OFDM_RSSI_INBAND_B_BITMSK 0xff0000 +#define IWLAGN_OFDM_RSSI_ALLBAND_B_BITMSK 0xff000000 +#define IWLAGN_OFDM_RSSI_B_BIT_POS 16 +#define IWLAGN_OFDM_RSSI_INBAND_C_BITMSK 0x00ff +#define IWLAGN_OFDM_RSSI_ALLBAND_C_BITMSK 0xff00 +#define IWLAGN_OFDM_RSSI_C_BIT_POS 0 + +struct iwlagn_non_cfg_phy { + __le32 non_cfg_phy[IWLAGN_RX_RES_PHY_CNT]; /* up to 8 phy entries */ +} __packed; + + +/* + * REPLY_RX = 0xc3 (response only, not a command) + * Used only for legacy (non 11n) frames. + */ +struct iwl_rx_phy_res { + u8 non_cfg_phy_cnt; /* non configurable DSP phy data byte count */ + u8 cfg_phy_cnt; /* configurable DSP phy data byte count */ + u8 stat_id; /* configurable DSP phy data set ID */ + u8 reserved1; + __le64 timestamp; /* TSF at on air rise */ + __le32 beacon_time_stamp; /* beacon at on-air rise */ + __le16 phy_flags; /* general phy flags: band, modulation, ... */ + __le16 channel; /* channel number */ + u8 non_cfg_phy_buf[32]; /* for various implementations of non_cfg_phy */ + __le32 rate_n_flags; /* RATE_MCS_* */ + __le16 byte_count; /* frame's byte-count */ + __le16 frame_time; /* frame's time on the air */ +} __packed; + +struct iwl_rx_mpdu_res_start { + __le16 byte_count; + __le16 reserved; +} __packed; + + +/****************************************************************************** + * (5) + * Tx Commands & Responses: + * + * Driver must place each REPLY_TX command into one of the prioritized Tx + * queues in host DRAM, shared between driver and device (see comments for + * SCD registers and Tx/Rx Queues). When the device's Tx scheduler and uCode + * are preparing to transmit, the device pulls the Tx command over the PCI + * bus via one of the device's Tx DMA channels, to fill an internal FIFO + * from which data will be transmitted. + * + * uCode handles all timing and protocol related to control frames + * (RTS/CTS/ACK), based on flags in the Tx command. uCode and Tx scheduler + * handle reception of block-acks; uCode updates the host driver via + * REPLY_COMPRESSED_BA. + * + * uCode handles retrying Tx when an ACK is expected but not received. + * This includes trying lower data rates than the one requested in the Tx + * command, as set up by the REPLY_TX_LINK_QUALITY_CMD (agn). + * + * Driver sets up transmit power for various rates via REPLY_TX_PWR_TABLE_CMD. + * This command must be executed after every RXON command, before Tx can occur. + *****************************************************************************/ + +/* REPLY_TX Tx flags field */ + +/* + * 1: Use RTS/CTS protocol or CTS-to-self if spec allows it + * before this frame. if CTS-to-self required check + * RXON_FLG_SELF_CTS_EN status. + */ +#define TX_CMD_FLG_PROT_REQUIRE_MSK cpu_to_le32(1 << 0) + +/* 1: Expect ACK from receiving station + * 0: Don't expect ACK (MAC header's duration field s/b 0) + * Set this for unicast frames, but not broadcast/multicast. */ +#define TX_CMD_FLG_ACK_MSK cpu_to_le32(1 << 3) + +/* For agn devices: + * 1: Use rate scale table (see REPLY_TX_LINK_QUALITY_CMD). + * Tx command's initial_rate_index indicates first rate to try; + * uCode walks through table for additional Tx attempts. + * 0: Use Tx rate/MCS from Tx command's rate_n_flags field. + * This rate will be used for all Tx attempts; it will not be scaled. */ +#define TX_CMD_FLG_STA_RATE_MSK cpu_to_le32(1 << 4) + +/* 1: Expect immediate block-ack. + * Set when Txing a block-ack request frame. Also set TX_CMD_FLG_ACK_MSK. */ +#define TX_CMD_FLG_IMM_BA_RSP_MASK cpu_to_le32(1 << 6) + +/* Tx antenna selection field; reserved (0) for agn devices. */ +#define TX_CMD_FLG_ANT_SEL_MSK cpu_to_le32(0xf00) + +/* 1: Ignore Bluetooth priority for this frame. + * 0: Delay Tx until Bluetooth device is done (normal usage). */ +#define TX_CMD_FLG_IGNORE_BT cpu_to_le32(1 << 12) + +/* 1: uCode overrides sequence control field in MAC header. + * 0: Driver provides sequence control field in MAC header. + * Set this for management frames, non-QOS data frames, non-unicast frames, + * and also in Tx command embedded in REPLY_SCAN_CMD for active scans. */ +#define TX_CMD_FLG_SEQ_CTL_MSK cpu_to_le32(1 << 13) + +/* 1: This frame is non-last MPDU; more fragments are coming. + * 0: Last fragment, or not using fragmentation. */ +#define TX_CMD_FLG_MORE_FRAG_MSK cpu_to_le32(1 << 14) + +/* 1: uCode calculates and inserts Timestamp Function (TSF) in outgoing frame. + * 0: No TSF required in outgoing frame. + * Set this for transmitting beacons and probe responses. */ +#define TX_CMD_FLG_TSF_MSK cpu_to_le32(1 << 16) + +/* 1: Driver inserted 2 bytes pad after the MAC header, for (required) dword + * alignment of frame's payload data field. + * 0: No pad + * Set this for MAC headers with 26 or 30 bytes, i.e. those with QOS or ADDR4 + * field (but not both). Driver must align frame data (i.e. data following + * MAC header) to DWORD boundary. */ +#define TX_CMD_FLG_MH_PAD_MSK cpu_to_le32(1 << 20) + +/* accelerate aggregation support + * 0 - no CCMP encryption; 1 - CCMP encryption */ +#define TX_CMD_FLG_AGG_CCMP_MSK cpu_to_le32(1 << 22) + +/* HCCA-AP - disable duration overwriting. */ +#define TX_CMD_FLG_DUR_MSK cpu_to_le32(1 << 25) + + +/* + * TX command security control + */ +#define TX_CMD_SEC_WEP 0x01 +#define TX_CMD_SEC_CCM 0x02 +#define TX_CMD_SEC_TKIP 0x03 +#define TX_CMD_SEC_MSK 0x03 +#define TX_CMD_SEC_SHIFT 6 +#define TX_CMD_SEC_KEY128 0x08 + +/* + * REPLY_TX = 0x1c (command) + */ + +/* + * Used for managing Tx retries when expecting block-acks. + * Driver should set these fields to 0. + */ +struct iwl_dram_scratch { + u8 try_cnt; /* Tx attempts */ + u8 bt_kill_cnt; /* Tx attempts blocked by Bluetooth device */ + __le16 reserved; +} __packed; + +struct iwl_tx_cmd { + /* + * MPDU byte count: + * MAC header (24/26/30/32 bytes) + 2 bytes pad if 26/30 header size, + * + 8 byte IV for CCM or TKIP (not used for WEP) + * + Data payload + * + 8-byte MIC (not used for CCM/WEP) + * NOTE: Does not include Tx command bytes, post-MAC pad bytes, + * MIC (CCM) 8 bytes, ICV (WEP/TKIP/CKIP) 4 bytes, CRC 4 bytes.i + * Range: 14-2342 bytes. + */ + __le16 len; + + /* + * MPDU or MSDU byte count for next frame. + * Used for fragmentation and bursting, but not 11n aggregation. + * Same as "len", but for next frame. Set to 0 if not applicable. + */ + __le16 next_frame_len; + + __le32 tx_flags; /* TX_CMD_FLG_* */ + + /* uCode may modify this field of the Tx command (in host DRAM!). + * Driver must also set dram_lsb_ptr and dram_msb_ptr in this cmd. */ + struct iwl_dram_scratch scratch; + + /* Rate for *all* Tx attempts, if TX_CMD_FLG_STA_RATE_MSK is cleared. */ + __le32 rate_n_flags; /* RATE_MCS_* */ + + /* Index of destination station in uCode's station table */ + u8 sta_id; + + /* Type of security encryption: CCM or TKIP */ + u8 sec_ctl; /* TX_CMD_SEC_* */ + + /* + * Index into rate table (see REPLY_TX_LINK_QUALITY_CMD) for initial + * Tx attempt, if TX_CMD_FLG_STA_RATE_MSK is set. Normally "0" for + * data frames, this field may be used to selectively reduce initial + * rate (via non-0 value) for special frames (e.g. management), while + * still supporting rate scaling for all frames. + */ + u8 initial_rate_index; + u8 reserved; + u8 key[16]; + __le16 next_frame_flags; + __le16 reserved2; + union { + __le32 life_time; + __le32 attempt; + } stop_time; + + /* Host DRAM physical address pointer to "scratch" in this command. + * Must be dword aligned. "0" in dram_lsb_ptr disables usage. */ + __le32 dram_lsb_ptr; + u8 dram_msb_ptr; + + u8 rts_retry_limit; /*byte 50 */ + u8 data_retry_limit; /*byte 51 */ + u8 tid_tspec; + union { + __le16 pm_frame_timeout; + __le16 attempt_duration; + } timeout; + + /* + * Duration of EDCA burst Tx Opportunity, in 32-usec units. + * Set this if txop time is not specified by HCCA protocol (e.g. by AP). + */ + __le16 driver_txop; + + /* + * MAC header goes here, followed by 2 bytes padding if MAC header + * length is 26 or 30 bytes, followed by payload data + */ + union { + DECLARE_FLEX_ARRAY(u8, payload); + DECLARE_FLEX_ARRAY(struct ieee80211_hdr, hdr); + }; +} __packed; + +/* + * TX command response is sent after *agn* transmission attempts. + * + * both postpone and abort status are expected behavior from uCode. there is + * no special operation required from driver; except for RFKILL_FLUSH, + * which required tx flush host command to flush all the tx frames in queues + */ +enum { + TX_STATUS_SUCCESS = 0x01, + TX_STATUS_DIRECT_DONE = 0x02, + /* postpone TX */ + TX_STATUS_POSTPONE_DELAY = 0x40, + TX_STATUS_POSTPONE_FEW_BYTES = 0x41, + TX_STATUS_POSTPONE_BT_PRIO = 0x42, + TX_STATUS_POSTPONE_QUIET_PERIOD = 0x43, + TX_STATUS_POSTPONE_CALC_TTAK = 0x44, + /* abort TX */ + TX_STATUS_FAIL_INTERNAL_CROSSED_RETRY = 0x81, + TX_STATUS_FAIL_SHORT_LIMIT = 0x82, + TX_STATUS_FAIL_LONG_LIMIT = 0x83, + TX_STATUS_FAIL_FIFO_UNDERRUN = 0x84, + TX_STATUS_FAIL_DRAIN_FLOW = 0x85, + TX_STATUS_FAIL_RFKILL_FLUSH = 0x86, + TX_STATUS_FAIL_LIFE_EXPIRE = 0x87, + TX_STATUS_FAIL_DEST_PS = 0x88, + TX_STATUS_FAIL_HOST_ABORTED = 0x89, + TX_STATUS_FAIL_BT_RETRY = 0x8a, + TX_STATUS_FAIL_STA_INVALID = 0x8b, + TX_STATUS_FAIL_FRAG_DROPPED = 0x8c, + TX_STATUS_FAIL_TID_DISABLE = 0x8d, + TX_STATUS_FAIL_FIFO_FLUSHED = 0x8e, + TX_STATUS_FAIL_INSUFFICIENT_CF_POLL = 0x8f, + TX_STATUS_FAIL_PASSIVE_NO_RX = 0x90, + TX_STATUS_FAIL_NO_BEACON_ON_RADAR = 0x91, +}; + +#define TX_PACKET_MODE_REGULAR 0x0000 +#define TX_PACKET_MODE_BURST_SEQ 0x0100 +#define TX_PACKET_MODE_BURST_FIRST 0x0200 + +enum { + TX_POWER_PA_NOT_ACTIVE = 0x0, +}; + +enum { + TX_STATUS_MSK = 0x000000ff, /* bits 0:7 */ + TX_STATUS_DELAY_MSK = 0x00000040, + TX_STATUS_ABORT_MSK = 0x00000080, + TX_PACKET_MODE_MSK = 0x0000ff00, /* bits 8:15 */ + TX_FIFO_NUMBER_MSK = 0x00070000, /* bits 16:18 */ + TX_RESERVED = 0x00780000, /* bits 19:22 */ + TX_POWER_PA_DETECT_MSK = 0x7f800000, /* bits 23:30 */ + TX_ABORT_REQUIRED_MSK = 0x80000000, /* bits 31:31 */ +}; + +/* ******************************* + * TX aggregation status + ******************************* */ + +enum { + AGG_TX_STATE_TRANSMITTED = 0x00, + AGG_TX_STATE_UNDERRUN_MSK = 0x01, + AGG_TX_STATE_BT_PRIO_MSK = 0x02, + AGG_TX_STATE_FEW_BYTES_MSK = 0x04, + AGG_TX_STATE_ABORT_MSK = 0x08, + AGG_TX_STATE_LAST_SENT_TTL_MSK = 0x10, + AGG_TX_STATE_LAST_SENT_TRY_CNT_MSK = 0x20, + AGG_TX_STATE_LAST_SENT_BT_KILL_MSK = 0x40, + AGG_TX_STATE_SCD_QUERY_MSK = 0x80, + AGG_TX_STATE_TEST_BAD_CRC32_MSK = 0x100, + AGG_TX_STATE_RESPONSE_MSK = 0x1ff, + AGG_TX_STATE_DUMP_TX_MSK = 0x200, + AGG_TX_STATE_DELAY_TX_MSK = 0x400 +}; + +#define AGG_TX_STATUS_MSK 0x00000fff /* bits 0:11 */ +#define AGG_TX_TRY_MSK 0x0000f000 /* bits 12:15 */ +#define AGG_TX_TRY_POS 12 + +#define AGG_TX_STATE_LAST_SENT_MSK (AGG_TX_STATE_LAST_SENT_TTL_MSK | \ + AGG_TX_STATE_LAST_SENT_TRY_CNT_MSK | \ + AGG_TX_STATE_LAST_SENT_BT_KILL_MSK) + +/* # tx attempts for first frame in aggregation */ +#define AGG_TX_STATE_TRY_CNT_POS 12 +#define AGG_TX_STATE_TRY_CNT_MSK 0xf000 + +/* Command ID and sequence number of Tx command for this frame */ +#define AGG_TX_STATE_SEQ_NUM_POS 16 +#define AGG_TX_STATE_SEQ_NUM_MSK 0xffff0000 + +/* + * REPLY_TX = 0x1c (response) + * + * This response may be in one of two slightly different formats, indicated + * by the frame_count field: + * + * 1) No aggregation (frame_count == 1). This reports Tx results for + * a single frame. Multiple attempts, at various bit rates, may have + * been made for this frame. + * + * 2) Aggregation (frame_count > 1). This reports Tx results for + * 2 or more frames that used block-acknowledge. All frames were + * transmitted at same rate. Rate scaling may have been used if first + * frame in this new agg block failed in previous agg block(s). + * + * Note that, for aggregation, ACK (block-ack) status is not delivered here; + * block-ack has not been received by the time the agn device records + * this status. + * This status relates to reasons the tx might have been blocked or aborted + * within the sending station (this agn device), rather than whether it was + * received successfully by the destination station. + */ +struct agg_tx_status { + __le16 status; + __le16 sequence; +} __packed; + +/* refer to ra_tid */ +#define IWLAGN_TX_RES_TID_POS 0 +#define IWLAGN_TX_RES_TID_MSK 0x0f +#define IWLAGN_TX_RES_RA_POS 4 +#define IWLAGN_TX_RES_RA_MSK 0xf0 + +struct iwlagn_tx_resp { + u8 frame_count; /* 1 no aggregation, >1 aggregation */ + u8 bt_kill_count; /* # blocked by bluetooth (unused for agg) */ + u8 failure_rts; /* # failures due to unsuccessful RTS */ + u8 failure_frame; /* # failures due to no ACK (unused for agg) */ + + /* For non-agg: Rate at which frame was successful. + * For agg: Rate at which all frames were transmitted. */ + __le32 rate_n_flags; /* RATE_MCS_* */ + + /* For non-agg: RTS + CTS + frame tx attempts time + ACK. + * For agg: RTS + CTS + aggregation tx time + block-ack time. */ + __le16 wireless_media_time; /* uSecs */ + + u8 pa_status; /* RF power amplifier measurement (not used) */ + u8 pa_integ_res_a[3]; + u8 pa_integ_res_b[3]; + u8 pa_integ_res_C[3]; + + __le32 tfd_info; + __le16 seq_ctl; + __le16 byte_cnt; + u8 tlc_info; + u8 ra_tid; /* tid (0:3), sta_id (4:7) */ + __le16 frame_ctrl; + /* + * For non-agg: frame status TX_STATUS_* + * For agg: status of 1st frame, AGG_TX_STATE_*; other frame status + * fields follow this one, up to frame_count. + * Bit fields: + * 11- 0: AGG_TX_STATE_* status code + * 15-12: Retry count for 1st frame in aggregation (retries + * occur if tx failed for this frame when it was a + * member of a previous aggregation block). If rate + * scaling is used, retry count indicates the rate + * table entry used for all frames in the new agg. + * 31-16: Sequence # for this frame's Tx cmd (not SSN!) + */ + struct agg_tx_status status; /* TX status (in aggregation - + * status of 1st frame) */ +} __packed; +/* + * REPLY_COMPRESSED_BA = 0xc5 (response only, not a command) + * + * Reports Block-Acknowledge from recipient station + */ +struct iwl_compressed_ba_resp { + __le32 sta_addr_lo32; + __le16 sta_addr_hi16; + __le16 reserved; + + /* Index of recipient (BA-sending) station in uCode's station table */ + u8 sta_id; + u8 tid; + __le16 seq_ctl; + __le64 bitmap; + __le16 scd_flow; + __le16 scd_ssn; + u8 txed; /* number of frames sent */ + u8 txed_2_done; /* number of frames acked */ + __le16 reserved1; +} __packed; + +/* + * REPLY_TX_PWR_TABLE_CMD = 0x97 (command, has simple generic response) + * + */ + +/*RS_NEW_API: only TLC_RTS remains and moved to bit 0 */ +#define LINK_QUAL_FLAGS_SET_STA_TLC_RTS_MSK (1 << 0) + +/* # of EDCA prioritized tx fifos */ +#define LINK_QUAL_AC_NUM AC_NUM + +/* # entries in rate scale table to support Tx retries */ +#define LINK_QUAL_MAX_RETRY_NUM 16 + +/* Tx antenna selection values */ +#define LINK_QUAL_ANT_A_MSK (1 << 0) +#define LINK_QUAL_ANT_B_MSK (1 << 1) +#define LINK_QUAL_ANT_MSK (LINK_QUAL_ANT_A_MSK|LINK_QUAL_ANT_B_MSK) + + +/** + * struct iwl_link_qual_general_params + * + * Used in REPLY_TX_LINK_QUALITY_CMD + */ +struct iwl_link_qual_general_params { + u8 flags; + + /* No entries at or above this (driver chosen) index contain MIMO */ + u8 mimo_delimiter; + + /* Best single antenna to use for single stream (legacy, SISO). */ + u8 single_stream_ant_msk; /* LINK_QUAL_ANT_* */ + + /* Best antennas to use for MIMO */ + u8 dual_stream_ant_msk; /* LINK_QUAL_ANT_* */ + + /* + * If driver needs to use different initial rates for different + * EDCA QOS access categories (as implemented by tx fifos 0-3), + * this table will set that up, by indicating the indexes in the + * rs_table[LINK_QUAL_MAX_RETRY_NUM] rate table at which to start. + * Otherwise, driver should set all entries to 0. + * + * Entry usage: + * 0 = Background, 1 = Best Effort (normal), 2 = Video, 3 = Voice + * TX FIFOs above 3 use same value (typically 0) as TX FIFO 3. + */ + u8 start_rate_index[LINK_QUAL_AC_NUM]; +} __packed; + +#define LINK_QUAL_AGG_TIME_LIMIT_DEF (4000) /* 4 milliseconds */ +#define LINK_QUAL_AGG_TIME_LIMIT_MAX (8000) +#define LINK_QUAL_AGG_TIME_LIMIT_MIN (100) + +#define LINK_QUAL_AGG_DISABLE_START_DEF (3) +#define LINK_QUAL_AGG_DISABLE_START_MAX (255) +#define LINK_QUAL_AGG_DISABLE_START_MIN (0) + +#define LINK_QUAL_AGG_FRAME_LIMIT_DEF (63) +#define LINK_QUAL_AGG_FRAME_LIMIT_MAX (63) +#define LINK_QUAL_AGG_FRAME_LIMIT_MIN (0) + +/** + * struct iwl_link_qual_agg_params + * + * Used in REPLY_TX_LINK_QUALITY_CMD + */ +struct iwl_link_qual_agg_params { + + /* + *Maximum number of uSec in aggregation. + * default set to 4000 (4 milliseconds) if not configured in .cfg + */ + __le16 agg_time_limit; + + /* + * Number of Tx retries allowed for a frame, before that frame will + * no longer be considered for the start of an aggregation sequence + * (scheduler will then try to tx it as single frame). + * Driver should set this to 3. + */ + u8 agg_dis_start_th; + + /* + * Maximum number of frames in aggregation. + * 0 = no limit (default). 1 = no aggregation. + * Other values = max # frames in aggregation. + */ + u8 agg_frame_cnt_limit; + + __le32 reserved; +} __packed; + +/* + * REPLY_TX_LINK_QUALITY_CMD = 0x4e (command, has simple generic response) + * + * For agn devices + * + * Each station in the agn device's internal station table has its own table + * of 16 + * Tx rates and modulation modes (e.g. legacy/SISO/MIMO) for retrying Tx when + * an ACK is not received. This command replaces the entire table for + * one station. + * + * NOTE: Station must already be in agn device's station table. + * Use REPLY_ADD_STA. + * + * The rate scaling procedures described below work well. Of course, other + * procedures are possible, and may work better for particular environments. + * + * + * FILLING THE RATE TABLE + * + * Given a particular initial rate and mode, as determined by the rate + * scaling algorithm described below, the Linux driver uses the following + * formula to fill the rs_table[LINK_QUAL_MAX_RETRY_NUM] rate table in the + * Link Quality command: + * + * + * 1) If using High-throughput (HT) (SISO or MIMO) initial rate: + * a) Use this same initial rate for first 3 entries. + * b) Find next lower available rate using same mode (SISO or MIMO), + * use for next 3 entries. If no lower rate available, switch to + * legacy mode (no HT40 channel, no MIMO, no short guard interval). + * c) If using MIMO, set command's mimo_delimiter to number of entries + * using MIMO (3 or 6). + * d) After trying 2 HT rates, switch to legacy mode (no HT40 channel, + * no MIMO, no short guard interval), at the next lower bit rate + * (e.g. if second HT bit rate was 54, try 48 legacy), and follow + * legacy procedure for remaining table entries. + * + * 2) If using legacy initial rate: + * a) Use the initial rate for only one entry. + * b) For each following entry, reduce the rate to next lower available + * rate, until reaching the lowest available rate. + * c) When reducing rate, also switch antenna selection. + * d) Once lowest available rate is reached, repeat this rate until + * rate table is filled (16 entries), switching antenna each entry. + * + * + * ACCUMULATING HISTORY + * + * The rate scaling algorithm for agn devices, as implemented in Linux driver, + * uses two sets of frame Tx success history: One for the current/active + * modulation mode, and one for a speculative/search mode that is being + * attempted. If the speculative mode turns out to be more effective (i.e. + * actual transfer rate is better), then the driver continues to use the + * speculative mode as the new current active mode. + * + * Each history set contains, separately for each possible rate, data for a + * sliding window of the 62 most recent tx attempts at that rate. The data + * includes a shifting bitmap of success(1)/failure(0), and sums of successful + * and attempted frames, from which the driver can additionally calculate a + * success ratio (success / attempted) and number of failures + * (attempted - success), and control the size of the window (attempted). + * The driver uses the bit map to remove successes from the success sum, as + * the oldest tx attempts fall out of the window. + * + * When the agn device makes multiple tx attempts for a given frame, each + * attempt might be at a different rate, and have different modulation + * characteristics (e.g. antenna, fat channel, short guard interval), as set + * up in the rate scaling table in the Link Quality command. The driver must + * determine which rate table entry was used for each tx attempt, to determine + * which rate-specific history to update, and record only those attempts that + * match the modulation characteristics of the history set. + * + * When using block-ack (aggregation), all frames are transmitted at the same + * rate, since there is no per-attempt acknowledgment from the destination + * station. The Tx response struct iwl_tx_resp indicates the Tx rate in + * rate_n_flags field. After receiving a block-ack, the driver can update + * history for the entire block all at once. + * + * + * FINDING BEST STARTING RATE: + * + * When working with a selected initial modulation mode (see below), the + * driver attempts to find a best initial rate. The initial rate is the + * first entry in the Link Quality command's rate table. + * + * 1) Calculate actual throughput (success ratio * expected throughput, see + * table below) for current initial rate. Do this only if enough frames + * have been attempted to make the value meaningful: at least 6 failed + * tx attempts, or at least 8 successes. If not enough, don't try rate + * scaling yet. + * + * 2) Find available rates adjacent to current initial rate. Available means: + * a) supported by hardware && + * b) supported by association && + * c) within any constraints selected by user + * + * 3) Gather measured throughputs for adjacent rates. These might not have + * enough history to calculate a throughput. That's okay, we might try + * using one of them anyway! + * + * 4) Try decreasing rate if, for current rate: + * a) success ratio is < 15% || + * b) lower adjacent rate has better measured throughput || + * c) higher adjacent rate has worse throughput, and lower is unmeasured + * + * As a sanity check, if decrease was determined above, leave rate + * unchanged if: + * a) lower rate unavailable + * b) success ratio at current rate > 85% (very good) + * c) current measured throughput is better than expected throughput + * of lower rate (under perfect 100% tx conditions, see table below) + * + * 5) Try increasing rate if, for current rate: + * a) success ratio is < 15% || + * b) both adjacent rates' throughputs are unmeasured (try it!) || + * b) higher adjacent rate has better measured throughput || + * c) lower adjacent rate has worse throughput, and higher is unmeasured + * + * As a sanity check, if increase was determined above, leave rate + * unchanged if: + * a) success ratio at current rate < 70%. This is not particularly + * good performance; higher rate is sure to have poorer success. + * + * 6) Re-evaluate the rate after each tx frame. If working with block- + * acknowledge, history and statistics may be calculated for the entire + * block (including prior history that fits within the history windows), + * before re-evaluation. + * + * FINDING BEST STARTING MODULATION MODE: + * + * After working with a modulation mode for a "while" (and doing rate scaling), + * the driver searches for a new initial mode in an attempt to improve + * throughput. The "while" is measured by numbers of attempted frames: + * + * For legacy mode, search for new mode after: + * 480 successful frames, or 160 failed frames + * For high-throughput modes (SISO or MIMO), search for new mode after: + * 4500 successful frames, or 400 failed frames + * + * Mode switch possibilities are (3 for each mode): + * + * For legacy: + * Change antenna, try SISO (if HT association), try MIMO (if HT association) + * For SISO: + * Change antenna, try MIMO, try shortened guard interval (SGI) + * For MIMO: + * Try SISO antenna A, SISO antenna B, try shortened guard interval (SGI) + * + * When trying a new mode, use the same bit rate as the old/current mode when + * trying antenna switches and shortened guard interval. When switching to + * SISO from MIMO or legacy, or to MIMO from SISO or legacy, use a rate + * for which the expected throughput (under perfect conditions) is about the + * same or slightly better than the actual measured throughput delivered by + * the old/current mode. + * + * Actual throughput can be estimated by multiplying the expected throughput + * by the success ratio (successful / attempted tx frames). Frame size is + * not considered in this calculation; it assumes that frame size will average + * out to be fairly consistent over several samples. The following are + * metric values for expected throughput assuming 100% success ratio. + * Only G band has support for CCK rates: + * + * RATE: 1 2 5 11 6 9 12 18 24 36 48 54 60 + * + * G: 7 13 35 58 40 57 72 98 121 154 177 186 186 + * A: 0 0 0 0 40 57 72 98 121 154 177 186 186 + * SISO 20MHz: 0 0 0 0 42 42 76 102 124 159 183 193 202 + * SGI SISO 20MHz: 0 0 0 0 46 46 82 110 132 168 192 202 211 + * MIMO 20MHz: 0 0 0 0 74 74 123 155 179 214 236 244 251 + * SGI MIMO 20MHz: 0 0 0 0 81 81 131 164 188 222 243 251 257 + * SISO 40MHz: 0 0 0 0 77 77 127 160 184 220 242 250 257 + * SGI SISO 40MHz: 0 0 0 0 83 83 135 169 193 229 250 257 264 + * MIMO 40MHz: 0 0 0 0 123 123 182 214 235 264 279 285 289 + * SGI MIMO 40MHz: 0 0 0 0 131 131 191 222 242 270 284 289 293 + * + * After the new mode has been tried for a short while (minimum of 6 failed + * frames or 8 successful frames), compare success ratio and actual throughput + * estimate of the new mode with the old. If either is better with the new + * mode, continue to use the new mode. + * + * Continue comparing modes until all 3 possibilities have been tried. + * If moving from legacy to HT, try all 3 possibilities from the new HT + * mode. After trying all 3, a best mode is found. Continue to use this mode + * for the longer "while" described above (e.g. 480 successful frames for + * legacy), and then repeat the search process. + * + */ +struct iwl_link_quality_cmd { + + /* Index of destination/recipient station in uCode's station table */ + u8 sta_id; + u8 reserved1; + __le16 control; /* not used */ + struct iwl_link_qual_general_params general_params; + struct iwl_link_qual_agg_params agg_params; + + /* + * Rate info; when using rate-scaling, Tx command's initial_rate_index + * specifies 1st Tx rate attempted, via index into this table. + * agn devices works its way through table when retrying Tx. + */ + struct { + __le32 rate_n_flags; /* RATE_MCS_*, IWL_RATE_* */ + } rs_table[LINK_QUAL_MAX_RETRY_NUM]; + __le32 reserved2; +} __packed; + +/* + * BT configuration enable flags: + * bit 0 - 1: BT channel announcement enabled + * 0: disable + * bit 1 - 1: priority of BT device enabled + * 0: disable + * bit 2 - 1: BT 2 wire support enabled + * 0: disable + */ +#define BT_COEX_DISABLE (0x0) +#define BT_ENABLE_CHANNEL_ANNOUNCE BIT(0) +#define BT_ENABLE_PRIORITY BIT(1) +#define BT_ENABLE_2_WIRE BIT(2) + +#define BT_COEX_DISABLE (0x0) +#define BT_COEX_ENABLE (BT_ENABLE_CHANNEL_ANNOUNCE | BT_ENABLE_PRIORITY) + +#define BT_LEAD_TIME_MIN (0x0) +#define BT_LEAD_TIME_DEF (0x1E) +#define BT_LEAD_TIME_MAX (0xFF) + +#define BT_MAX_KILL_MIN (0x1) +#define BT_MAX_KILL_DEF (0x5) +#define BT_MAX_KILL_MAX (0xFF) + +#define BT_DURATION_LIMIT_DEF 625 +#define BT_DURATION_LIMIT_MAX 1250 +#define BT_DURATION_LIMIT_MIN 625 + +#define BT_ON_THRESHOLD_DEF 4 +#define BT_ON_THRESHOLD_MAX 1000 +#define BT_ON_THRESHOLD_MIN 1 + +#define BT_FRAG_THRESHOLD_DEF 0 +#define BT_FRAG_THRESHOLD_MAX 0 +#define BT_FRAG_THRESHOLD_MIN 0 + +#define BT_AGG_THRESHOLD_DEF 1200 +#define BT_AGG_THRESHOLD_MAX 8000 +#define BT_AGG_THRESHOLD_MIN 400 + +/* + * REPLY_BT_CONFIG = 0x9b (command, has simple generic response) + * + * agn devices support hardware handshake with Bluetooth device on + * same platform. Bluetooth device alerts wireless device when it will Tx; + * wireless device can delay or kill its own Tx to accommodate. + */ +struct iwl_bt_cmd { + u8 flags; + u8 lead_time; + u8 max_kill; + u8 reserved; + __le32 kill_ack_mask; + __le32 kill_cts_mask; +} __packed; + +#define IWLAGN_BT_FLAG_CHANNEL_INHIBITION BIT(0) + +#define IWLAGN_BT_FLAG_COEX_MODE_MASK (BIT(3)|BIT(4)|BIT(5)) +#define IWLAGN_BT_FLAG_COEX_MODE_SHIFT 3 +#define IWLAGN_BT_FLAG_COEX_MODE_DISABLED 0 +#define IWLAGN_BT_FLAG_COEX_MODE_LEGACY_2W 1 +#define IWLAGN_BT_FLAG_COEX_MODE_3W 2 +#define IWLAGN_BT_FLAG_COEX_MODE_4W 3 + +#define IWLAGN_BT_FLAG_UCODE_DEFAULT BIT(6) +/* Disable Sync PSPoll on SCO/eSCO */ +#define IWLAGN_BT_FLAG_SYNC_2_BT_DISABLE BIT(7) + +#define IWLAGN_BT_PSP_MIN_RSSI_THRESHOLD -75 /* dBm */ +#define IWLAGN_BT_PSP_MAX_RSSI_THRESHOLD -65 /* dBm */ + +#define IWLAGN_BT_PRIO_BOOST_MAX 0xFF +#define IWLAGN_BT_PRIO_BOOST_MIN 0x00 +#define IWLAGN_BT_PRIO_BOOST_DEFAULT 0xF0 +#define IWLAGN_BT_PRIO_BOOST_DEFAULT32 0xF0F0F0F0 + +#define IWLAGN_BT_MAX_KILL_DEFAULT 5 + +#define IWLAGN_BT3_T7_DEFAULT 1 + +enum iwl_bt_kill_idx { + IWL_BT_KILL_DEFAULT = 0, + IWL_BT_KILL_OVERRIDE = 1, + IWL_BT_KILL_REDUCE = 2, +}; + +#define IWLAGN_BT_KILL_ACK_MASK_DEFAULT cpu_to_le32(0xffff0000) +#define IWLAGN_BT_KILL_CTS_MASK_DEFAULT cpu_to_le32(0xffff0000) +#define IWLAGN_BT_KILL_ACK_CTS_MASK_SCO cpu_to_le32(0xffffffff) +#define IWLAGN_BT_KILL_ACK_CTS_MASK_REDUCE cpu_to_le32(0) + +#define IWLAGN_BT3_PRIO_SAMPLE_DEFAULT 2 + +#define IWLAGN_BT3_T2_DEFAULT 0xc + +#define IWLAGN_BT_VALID_ENABLE_FLAGS cpu_to_le16(BIT(0)) +#define IWLAGN_BT_VALID_BOOST cpu_to_le16(BIT(1)) +#define IWLAGN_BT_VALID_MAX_KILL cpu_to_le16(BIT(2)) +#define IWLAGN_BT_VALID_3W_TIMERS cpu_to_le16(BIT(3)) +#define IWLAGN_BT_VALID_KILL_ACK_MASK cpu_to_le16(BIT(4)) +#define IWLAGN_BT_VALID_KILL_CTS_MASK cpu_to_le16(BIT(5)) +#define IWLAGN_BT_VALID_REDUCED_TX_PWR cpu_to_le16(BIT(6)) +#define IWLAGN_BT_VALID_3W_LUT cpu_to_le16(BIT(7)) + +#define IWLAGN_BT_ALL_VALID_MSK (IWLAGN_BT_VALID_ENABLE_FLAGS | \ + IWLAGN_BT_VALID_BOOST | \ + IWLAGN_BT_VALID_MAX_KILL | \ + IWLAGN_BT_VALID_3W_TIMERS | \ + IWLAGN_BT_VALID_KILL_ACK_MASK | \ + IWLAGN_BT_VALID_KILL_CTS_MASK | \ + IWLAGN_BT_VALID_REDUCED_TX_PWR | \ + IWLAGN_BT_VALID_3W_LUT) + +#define IWLAGN_BT_REDUCED_TX_PWR BIT(0) + +#define IWLAGN_BT_DECISION_LUT_SIZE 12 + +struct iwl_basic_bt_cmd { + u8 flags; + u8 ledtime; /* unused */ + u8 max_kill; + u8 bt3_timer_t7_value; + __le32 kill_ack_mask; + __le32 kill_cts_mask; + u8 bt3_prio_sample_time; + u8 bt3_timer_t2_value; + __le16 bt4_reaction_time; /* unused */ + __le32 bt3_lookup_table[IWLAGN_BT_DECISION_LUT_SIZE]; + /* + * bit 0: use reduced tx power for control frame + * bit 1 - 7: reserved + */ + u8 reduce_txpower; + u8 reserved; + __le16 valid; +}; + +struct iwl_bt_cmd_v1 { + struct iwl_basic_bt_cmd basic; + u8 prio_boost; + /* + * set IWLAGN_BT_VALID_BOOST to "1" in "valid" bitmask + * if configure the following patterns + */ + u8 tx_prio_boost; /* SW boost of WiFi tx priority */ + __le16 rx_prio_boost; /* SW boost of WiFi rx priority */ +}; + +struct iwl_bt_cmd_v2 { + struct iwl_basic_bt_cmd basic; + __le32 prio_boost; + /* + * set IWLAGN_BT_VALID_BOOST to "1" in "valid" bitmask + * if configure the following patterns + */ + u8 reserved; + u8 tx_prio_boost; /* SW boost of WiFi tx priority */ + __le16 rx_prio_boost; /* SW boost of WiFi rx priority */ +}; + +#define IWLAGN_BT_SCO_ACTIVE cpu_to_le32(BIT(0)) + +struct iwlagn_bt_sco_cmd { + __le32 flags; +}; + +/****************************************************************************** + * (6) + * Spectrum Management (802.11h) Commands, Responses, Notifications: + * + *****************************************************************************/ + +/* + * Spectrum Management + */ +#define MEASUREMENT_FILTER_FLAG (RXON_FILTER_PROMISC_MSK | \ + RXON_FILTER_CTL2HOST_MSK | \ + RXON_FILTER_ACCEPT_GRP_MSK | \ + RXON_FILTER_DIS_DECRYPT_MSK | \ + RXON_FILTER_DIS_GRP_DECRYPT_MSK | \ + RXON_FILTER_ASSOC_MSK | \ + RXON_FILTER_BCON_AWARE_MSK) + +struct iwl_measure_channel { + __le32 duration; /* measurement duration in extended beacon + * format */ + u8 channel; /* channel to measure */ + u8 type; /* see enum iwl_measure_type */ + __le16 reserved; +} __packed; + +/* + * REPLY_SPECTRUM_MEASUREMENT_CMD = 0x74 (command) + */ +struct iwl_spectrum_cmd { + __le16 len; /* number of bytes starting from token */ + u8 token; /* token id */ + u8 id; /* measurement id -- 0 or 1 */ + u8 origin; /* 0 = TGh, 1 = other, 2 = TGk */ + u8 periodic; /* 1 = periodic */ + __le16 path_loss_timeout; + __le32 start_time; /* start time in extended beacon format */ + __le32 reserved2; + __le32 flags; /* rxon flags */ + __le32 filter_flags; /* rxon filter flags */ + __le16 channel_count; /* minimum 1, maximum 10 */ + __le16 reserved3; + struct iwl_measure_channel channels[10]; +} __packed; + +/* + * REPLY_SPECTRUM_MEASUREMENT_CMD = 0x74 (response) + */ +struct iwl_spectrum_resp { + u8 token; + u8 id; /* id of the prior command replaced, or 0xff */ + __le16 status; /* 0 - command will be handled + * 1 - cannot handle (conflicts with another + * measurement) */ +} __packed; + +enum iwl_measurement_state { + IWL_MEASUREMENT_START = 0, + IWL_MEASUREMENT_STOP = 1, +}; + +enum iwl_measurement_status { + IWL_MEASUREMENT_OK = 0, + IWL_MEASUREMENT_CONCURRENT = 1, + IWL_MEASUREMENT_CSA_CONFLICT = 2, + IWL_MEASUREMENT_TGH_CONFLICT = 3, + /* 4-5 reserved */ + IWL_MEASUREMENT_STOPPED = 6, + IWL_MEASUREMENT_TIMEOUT = 7, + IWL_MEASUREMENT_PERIODIC_FAILED = 8, +}; + +#define NUM_ELEMENTS_IN_HISTOGRAM 8 + +struct iwl_measurement_histogram { + __le32 ofdm[NUM_ELEMENTS_IN_HISTOGRAM]; /* in 0.8usec counts */ + __le32 cck[NUM_ELEMENTS_IN_HISTOGRAM]; /* in 1usec counts */ +} __packed; + +/* clear channel availability counters */ +struct iwl_measurement_cca_counters { + __le32 ofdm; + __le32 cck; +} __packed; + +enum iwl_measure_type { + IWL_MEASURE_BASIC = (1 << 0), + IWL_MEASURE_CHANNEL_LOAD = (1 << 1), + IWL_MEASURE_HISTOGRAM_RPI = (1 << 2), + IWL_MEASURE_HISTOGRAM_NOISE = (1 << 3), + IWL_MEASURE_FRAME = (1 << 4), + /* bits 5:6 are reserved */ + IWL_MEASURE_IDLE = (1 << 7), +}; + +/* + * SPECTRUM_MEASURE_NOTIFICATION = 0x75 (notification only, not a command) + */ +struct iwl_spectrum_notification { + u8 id; /* measurement id -- 0 or 1 */ + u8 token; + u8 channel_index; /* index in measurement channel list */ + u8 state; /* 0 - start, 1 - stop */ + __le32 start_time; /* lower 32-bits of TSF */ + u8 band; /* 0 - 5.2GHz, 1 - 2.4GHz */ + u8 channel; + u8 type; /* see enum iwl_measurement_type */ + u8 reserved1; + /* NOTE: cca_ofdm, cca_cck, basic_type, and histogram are only only + * valid if applicable for measurement type requested. */ + __le32 cca_ofdm; /* cca fraction time in 40Mhz clock periods */ + __le32 cca_cck; /* cca fraction time in 44Mhz clock periods */ + __le32 cca_time; /* channel load time in usecs */ + u8 basic_type; /* 0 - bss, 1 - ofdm preamble, 2 - + * unidentified */ + u8 reserved2[3]; + struct iwl_measurement_histogram histogram; + __le32 stop_time; /* lower 32-bits of TSF */ + __le32 status; /* see iwl_measurement_status */ +} __packed; + +/****************************************************************************** + * (7) + * Power Management Commands, Responses, Notifications: + * + *****************************************************************************/ + +/** + * struct iwl_powertable_cmd - Power Table Command + * @flags: See below: + * + * POWER_TABLE_CMD = 0x77 (command, has simple generic response) + * + * PM allow: + * bit 0 - '0' Driver not allow power management + * '1' Driver allow PM (use rest of parameters) + * + * uCode send sleep notifications: + * bit 1 - '0' Don't send sleep notification + * '1' send sleep notification (SEND_PM_NOTIFICATION) + * + * Sleep over DTIM + * bit 2 - '0' PM have to walk up every DTIM + * '1' PM could sleep over DTIM till listen Interval. + * + * PCI power managed + * bit 3 - '0' (PCI_CFG_LINK_CTRL & 0x1) + * '1' !(PCI_CFG_LINK_CTRL & 0x1) + * + * Fast PD + * bit 4 - '1' Put radio to sleep when receiving frame for others + * + * Force sleep Modes + * bit 31/30- '00' use both mac/xtal sleeps + * '01' force Mac sleep + * '10' force xtal sleep + * '11' Illegal set + * + * NOTE: if sleep_interval[SLEEP_INTRVL_TABLE_SIZE-1] > DTIM period then + * ucode assume sleep over DTIM is allowed and we don't need to wake up + * for every DTIM. + */ +#define IWL_POWER_VEC_SIZE 5 + +#define IWL_POWER_DRIVER_ALLOW_SLEEP_MSK cpu_to_le16(BIT(0)) +#define IWL_POWER_POWER_SAVE_ENA_MSK cpu_to_le16(BIT(0)) +#define IWL_POWER_POWER_MANAGEMENT_ENA_MSK cpu_to_le16(BIT(1)) +#define IWL_POWER_SLEEP_OVER_DTIM_MSK cpu_to_le16(BIT(2)) +#define IWL_POWER_PCI_PM_MSK cpu_to_le16(BIT(3)) +#define IWL_POWER_FAST_PD cpu_to_le16(BIT(4)) +#define IWL_POWER_BEACON_FILTERING cpu_to_le16(BIT(5)) +#define IWL_POWER_SHADOW_REG_ENA cpu_to_le16(BIT(6)) +#define IWL_POWER_CT_KILL_SET cpu_to_le16(BIT(7)) +#define IWL_POWER_BT_SCO_ENA cpu_to_le16(BIT(8)) +#define IWL_POWER_ADVANCE_PM_ENA_MSK cpu_to_le16(BIT(9)) + +struct iwl_powertable_cmd { + __le16 flags; + u8 keep_alive_seconds; + u8 debug_flags; + __le32 rx_data_timeout; + __le32 tx_data_timeout; + __le32 sleep_interval[IWL_POWER_VEC_SIZE]; + __le32 keep_alive_beacons; +} __packed; + +/* + * PM_SLEEP_NOTIFICATION = 0x7A (notification only, not a command) + * all devices identical. + */ +struct iwl_sleep_notification { + u8 pm_sleep_mode; + u8 pm_wakeup_src; + __le16 reserved; + __le32 sleep_time; + __le32 tsf_low; + __le32 bcon_timer; +} __packed; + +/* Sleep states. all devices identical. */ +enum { + IWL_PM_NO_SLEEP = 0, + IWL_PM_SLP_MAC = 1, + IWL_PM_SLP_FULL_MAC_UNASSOCIATE = 2, + IWL_PM_SLP_FULL_MAC_CARD_STATE = 3, + IWL_PM_SLP_PHY = 4, + IWL_PM_SLP_REPENT = 5, + IWL_PM_WAKEUP_BY_TIMER = 6, + IWL_PM_WAKEUP_BY_DRIVER = 7, + IWL_PM_WAKEUP_BY_RFKILL = 8, + /* 3 reserved */ + IWL_PM_NUM_OF_MODES = 12, +}; + +/* + * REPLY_CARD_STATE_CMD = 0xa0 (command, has simple generic response) + */ +#define CARD_STATE_CMD_DISABLE 0x00 /* Put card to sleep */ +#define CARD_STATE_CMD_ENABLE 0x01 /* Wake up card */ +#define CARD_STATE_CMD_HALT 0x02 /* Power down permanently */ +struct iwl_card_state_cmd { + __le32 status; /* CARD_STATE_CMD_* request new power state */ +} __packed; + +/* + * CARD_STATE_NOTIFICATION = 0xa1 (notification only, not a command) + */ +struct iwl_card_state_notif { + __le32 flags; +} __packed; + +#define HW_CARD_DISABLED 0x01 +#define SW_CARD_DISABLED 0x02 +#define CT_CARD_DISABLED 0x04 +#define RXON_CARD_DISABLED 0x10 + +struct iwl_ct_kill_config { + __le32 reserved; + __le32 critical_temperature_M; + __le32 critical_temperature_R; +} __packed; + +/* 1000, and 6x00 */ +struct iwl_ct_kill_throttling_config { + __le32 critical_temperature_exit; + __le32 reserved; + __le32 critical_temperature_enter; +} __packed; + +/****************************************************************************** + * (8) + * Scan Commands, Responses, Notifications: + * + *****************************************************************************/ + +#define SCAN_CHANNEL_TYPE_PASSIVE cpu_to_le32(0) +#define SCAN_CHANNEL_TYPE_ACTIVE cpu_to_le32(1) + +/** + * struct iwl_scan_channel - entry in REPLY_SCAN_CMD channel table + * + * One for each channel in the scan list. + * Each channel can independently select: + * 1) SSID for directed active scans + * 2) Txpower setting (for rate specified within Tx command) + * 3) How long to stay on-channel (behavior may be modified by quiet_time, + * quiet_plcp_th, good_CRC_th) + * + * To avoid uCode errors, make sure the following are true (see comments + * under struct iwl_scan_cmd about max_out_time and quiet_time): + * 1) If using passive_dwell (i.e. passive_dwell != 0): + * active_dwell <= passive_dwell (< max_out_time if max_out_time != 0) + * 2) quiet_time <= active_dwell + * 3) If restricting off-channel time (i.e. max_out_time !=0): + * passive_dwell < max_out_time + * active_dwell < max_out_time + */ + +struct iwl_scan_channel { + /* + * type is defined as: + * 0:0 1 = active, 0 = passive + * 1:20 SSID direct bit map; if a bit is set, then corresponding + * SSID IE is transmitted in probe request. + * 21:31 reserved + */ + __le32 type; + __le16 channel; /* band is selected by iwl_scan_cmd "flags" field */ + u8 tx_gain; /* gain for analog radio */ + u8 dsp_atten; /* gain for DSP */ + __le16 active_dwell; /* in 1024-uSec TU (time units), typ 5-50 */ + __le16 passive_dwell; /* in 1024-uSec TU (time units), typ 20-500 */ +} __packed; + +/* set number of direct probes __le32 type */ +#define IWL_SCAN_PROBE_MASK(n) cpu_to_le32((BIT(n) | (BIT(n) - BIT(1)))) + +/** + * struct iwl_ssid_ie - directed scan network information element + * + * Up to 20 of these may appear in REPLY_SCAN_CMD, + * selected by "type" bit field in struct iwl_scan_channel; + * each channel may select different ssids from among the 20 entries. + * SSID IEs get transmitted in reverse order of entry. + */ +struct iwl_ssid_ie { + u8 id; + u8 len; + u8 ssid[32]; +} __packed; + +#define PROBE_OPTION_MAX 20 +#define TX_CMD_LIFE_TIME_INFINITE cpu_to_le32(0xFFFFFFFF) +#define IWL_GOOD_CRC_TH_DISABLED 0 +#define IWL_GOOD_CRC_TH_DEFAULT cpu_to_le16(1) +#define IWL_GOOD_CRC_TH_NEVER cpu_to_le16(0xffff) +#define IWL_MAX_CMD_SIZE 4096 + +/* + * REPLY_SCAN_CMD = 0x80 (command) + * + * The hardware scan command is very powerful; the driver can set it up to + * maintain (relatively) normal network traffic while doing a scan in the + * background. The max_out_time and suspend_time control the ratio of how + * long the device stays on an associated network channel ("service channel") + * vs. how long it's away from the service channel, i.e. tuned to other channels + * for scanning. + * + * max_out_time is the max time off-channel (in usec), and suspend_time + * is how long (in "extended beacon" format) that the scan is "suspended" + * after returning to the service channel. That is, suspend_time is the + * time that we stay on the service channel, doing normal work, between + * scan segments. The driver may set these parameters differently to support + * scanning when associated vs. not associated, and light vs. heavy traffic + * loads when associated. + * + * After receiving this command, the device's scan engine does the following; + * + * 1) Sends SCAN_START notification to driver + * 2) Checks to see if it has time to do scan for one channel + * 3) Sends NULL packet, with power-save (PS) bit set to 1, + * to tell AP that we're going off-channel + * 4) Tunes to first channel in scan list, does active or passive scan + * 5) Sends SCAN_RESULT notification to driver + * 6) Checks to see if it has time to do scan on *next* channel in list + * 7) Repeats 4-6 until it no longer has time to scan the next channel + * before max_out_time expires + * 8) Returns to service channel + * 9) Sends NULL packet with PS=0 to tell AP that we're back + * 10) Stays on service channel until suspend_time expires + * 11) Repeats entire process 2-10 until list is complete + * 12) Sends SCAN_COMPLETE notification + * + * For fast, efficient scans, the scan command also has support for staying on + * a channel for just a short time, if doing active scanning and getting no + * responses to the transmitted probe request. This time is controlled by + * quiet_time, and the number of received packets below which a channel is + * considered "quiet" is controlled by quiet_plcp_threshold. + * + * For active scanning on channels that have regulatory restrictions against + * blindly transmitting, the scan can listen before transmitting, to make sure + * that there is already legitimate activity on the channel. If enough + * packets are cleanly received on the channel (controlled by good_CRC_th, + * typical value 1), the scan engine starts transmitting probe requests. + * + * Driver must use separate scan commands for 2.4 vs. 5 GHz bands. + * + * To avoid uCode errors, see timing restrictions described under + * struct iwl_scan_channel. + */ + +enum iwl_scan_flags { + /* BIT(0) currently unused */ + IWL_SCAN_FLAGS_ACTION_FRAME_TX = BIT(1), + /* bits 2-7 reserved */ +}; + +struct iwl_scan_cmd { + __le16 len; + u8 scan_flags; /* scan flags: see enum iwl_scan_flags */ + u8 channel_count; /* # channels in channel list */ + __le16 quiet_time; /* dwell only this # millisecs on quiet channel + * (only for active scan) */ + __le16 quiet_plcp_th; /* quiet chnl is < this # pkts (typ. 1) */ + __le16 good_CRC_th; /* passive -> active promotion threshold */ + __le16 rx_chain; /* RXON_RX_CHAIN_* */ + __le32 max_out_time; /* max usec to be away from associated (service) + * channel */ + __le32 suspend_time; /* pause scan this long (in "extended beacon + * format") when returning to service chnl: + */ + __le32 flags; /* RXON_FLG_* */ + __le32 filter_flags; /* RXON_FILTER_* */ + + /* For active scans (set to all-0s for passive scans). + * Does not include payload. Must specify Tx rate; no rate scaling. */ + struct iwl_tx_cmd tx_cmd; + + /* For directed active scans (set to all-0s otherwise) */ + struct iwl_ssid_ie direct_scan[PROBE_OPTION_MAX]; + + /* + * Probe request frame, followed by channel list. + * + * Size of probe request frame is specified by byte count in tx_cmd. + * Channel list follows immediately after probe request frame. + * Number of channels in list is specified by channel_count. + * Each channel in list is of type: + * + * struct iwl_scan_channel channels[0]; + * + * NOTE: Only one band of channels can be scanned per pass. You + * must not mix 2.4GHz channels and 5.2GHz channels, and you must wait + * for one scan to complete (i.e. receive SCAN_COMPLETE_NOTIFICATION) + * before requesting another scan. + */ + u8 data[]; +} __packed; + +/* Can abort will notify by complete notification with abort status. */ +#define CAN_ABORT_STATUS cpu_to_le32(0x1) +/* complete notification statuses */ +#define ABORT_STATUS 0x2 + +/* + * REPLY_SCAN_CMD = 0x80 (response) + */ +struct iwl_scanreq_notification { + __le32 status; /* 1: okay, 2: cannot fulfill request */ +} __packed; + +/* + * SCAN_START_NOTIFICATION = 0x82 (notification only, not a command) + */ +struct iwl_scanstart_notification { + __le32 tsf_low; + __le32 tsf_high; + __le32 beacon_timer; + u8 channel; + u8 band; + u8 reserved[2]; + __le32 status; +} __packed; + +#define SCAN_OWNER_STATUS 0x1 +#define MEASURE_OWNER_STATUS 0x2 + +#define IWL_PROBE_STATUS_OK 0 +#define IWL_PROBE_STATUS_TX_FAILED BIT(0) +/* error statuses combined with TX_FAILED */ +#define IWL_PROBE_STATUS_FAIL_TTL BIT(1) +#define IWL_PROBE_STATUS_FAIL_BT BIT(2) + +#define NUMBER_OF_STATISTICS 1 /* first __le32 is good CRC */ +/* + * SCAN_RESULTS_NOTIFICATION = 0x83 (notification only, not a command) + */ +struct iwl_scanresults_notification { + u8 channel; + u8 band; + u8 probe_status; + u8 num_probe_not_sent; /* not enough time to send */ + __le32 tsf_low; + __le32 tsf_high; + __le32 statistics[NUMBER_OF_STATISTICS]; +} __packed; + +/* + * SCAN_COMPLETE_NOTIFICATION = 0x84 (notification only, not a command) + */ +struct iwl_scancomplete_notification { + u8 scanned_channels; + u8 status; + u8 bt_status; /* BT On/Off status */ + u8 last_channel; + __le32 tsf_low; + __le32 tsf_high; +} __packed; + + +/****************************************************************************** + * (9) + * IBSS/AP Commands and Notifications: + * + *****************************************************************************/ + +enum iwl_ibss_manager { + IWL_NOT_IBSS_MANAGER = 0, + IWL_IBSS_MANAGER = 1, +}; + +/* + * BEACON_NOTIFICATION = 0x90 (notification only, not a command) + */ + +struct iwlagn_beacon_notif { + struct iwlagn_tx_resp beacon_notify_hdr; + __le32 low_tsf; + __le32 high_tsf; + __le32 ibss_mgr_status; +} __packed; + +/* + * REPLY_TX_BEACON = 0x91 (command, has simple generic response) + */ + +struct iwl_tx_beacon_cmd { + struct iwl_tx_cmd tx; + __le16 tim_idx; + u8 tim_size; + u8 reserved1; + struct ieee80211_hdr frame[]; /* beacon frame */ +} __packed; + +/****************************************************************************** + * (10) + * Statistics Commands and Notifications: + * + *****************************************************************************/ + +#define IWL_TEMP_CONVERT 260 + +#define SUP_RATE_11A_MAX_NUM_CHANNELS 8 +#define SUP_RATE_11B_MAX_NUM_CHANNELS 4 +#define SUP_RATE_11G_MAX_NUM_CHANNELS 12 + +/* Used for passing to driver number of successes and failures per rate */ +struct rate_histogram { + union { + __le32 a[SUP_RATE_11A_MAX_NUM_CHANNELS]; + __le32 b[SUP_RATE_11B_MAX_NUM_CHANNELS]; + __le32 g[SUP_RATE_11G_MAX_NUM_CHANNELS]; + } success; + union { + __le32 a[SUP_RATE_11A_MAX_NUM_CHANNELS]; + __le32 b[SUP_RATE_11B_MAX_NUM_CHANNELS]; + __le32 g[SUP_RATE_11G_MAX_NUM_CHANNELS]; + } failed; +} __packed; + +/* statistics command response */ + +struct statistics_dbg { + __le32 burst_check; + __le32 burst_count; + __le32 wait_for_silence_timeout_cnt; + __le32 reserved[3]; +} __packed; + +struct statistics_rx_phy { + __le32 ina_cnt; + __le32 fina_cnt; + __le32 plcp_err; + __le32 crc32_err; + __le32 overrun_err; + __le32 early_overrun_err; + __le32 crc32_good; + __le32 false_alarm_cnt; + __le32 fina_sync_err_cnt; + __le32 sfd_timeout; + __le32 fina_timeout; + __le32 unresponded_rts; + __le32 rxe_frame_limit_overrun; + __le32 sent_ack_cnt; + __le32 sent_cts_cnt; + __le32 sent_ba_rsp_cnt; + __le32 dsp_self_kill; + __le32 mh_format_err; + __le32 re_acq_main_rssi_sum; + __le32 reserved3; +} __packed; + +struct statistics_rx_ht_phy { + __le32 plcp_err; + __le32 overrun_err; + __le32 early_overrun_err; + __le32 crc32_good; + __le32 crc32_err; + __le32 mh_format_err; + __le32 agg_crc32_good; + __le32 agg_mpdu_cnt; + __le32 agg_cnt; + __le32 unsupport_mcs; +} __packed; + +#define INTERFERENCE_DATA_AVAILABLE cpu_to_le32(1) + +struct statistics_rx_non_phy { + __le32 bogus_cts; /* CTS received when not expecting CTS */ + __le32 bogus_ack; /* ACK received when not expecting ACK */ + __le32 non_bssid_frames; /* number of frames with BSSID that + * doesn't belong to the STA BSSID */ + __le32 filtered_frames; /* count frames that were dumped in the + * filtering process */ + __le32 non_channel_beacons; /* beacons with our bss id but not on + * our serving channel */ + __le32 channel_beacons; /* beacons with our bss id and in our + * serving channel */ + __le32 num_missed_bcon; /* number of missed beacons */ + __le32 adc_rx_saturation_time; /* count in 0.8us units the time the + * ADC was in saturation */ + __le32 ina_detection_search_time;/* total time (in 0.8us) searched + * for INA */ + __le32 beacon_silence_rssi_a; /* RSSI silence after beacon frame */ + __le32 beacon_silence_rssi_b; /* RSSI silence after beacon frame */ + __le32 beacon_silence_rssi_c; /* RSSI silence after beacon frame */ + __le32 interference_data_flag; /* flag for interference data + * availability. 1 when data is + * available. */ + __le32 channel_load; /* counts RX Enable time in uSec */ + __le32 dsp_false_alarms; /* DSP false alarm (both OFDM + * and CCK) counter */ + __le32 beacon_rssi_a; + __le32 beacon_rssi_b; + __le32 beacon_rssi_c; + __le32 beacon_energy_a; + __le32 beacon_energy_b; + __le32 beacon_energy_c; +} __packed; + +struct statistics_rx_non_phy_bt { + struct statistics_rx_non_phy common; + /* additional stats for bt */ + __le32 num_bt_kills; + __le32 reserved[2]; +} __packed; + +struct statistics_rx { + struct statistics_rx_phy ofdm; + struct statistics_rx_phy cck; + struct statistics_rx_non_phy general; + struct statistics_rx_ht_phy ofdm_ht; +} __packed; + +struct statistics_rx_bt { + struct statistics_rx_phy ofdm; + struct statistics_rx_phy cck; + struct statistics_rx_non_phy_bt general; + struct statistics_rx_ht_phy ofdm_ht; +} __packed; + +/** + * struct statistics_tx_power - current tx power + * + * @ant_a: current tx power on chain a in 1/2 dB step + * @ant_b: current tx power on chain b in 1/2 dB step + * @ant_c: current tx power on chain c in 1/2 dB step + */ +struct statistics_tx_power { + u8 ant_a; + u8 ant_b; + u8 ant_c; + u8 reserved; +} __packed; + +struct statistics_tx_non_phy_agg { + __le32 ba_timeout; + __le32 ba_reschedule_frames; + __le32 scd_query_agg_frame_cnt; + __le32 scd_query_no_agg; + __le32 scd_query_agg; + __le32 scd_query_mismatch; + __le32 frame_not_ready; + __le32 underrun; + __le32 bt_prio_kill; + __le32 rx_ba_rsp_cnt; +} __packed; + +struct statistics_tx { + __le32 preamble_cnt; + __le32 rx_detected_cnt; + __le32 bt_prio_defer_cnt; + __le32 bt_prio_kill_cnt; + __le32 few_bytes_cnt; + __le32 cts_timeout; + __le32 ack_timeout; + __le32 expected_ack_cnt; + __le32 actual_ack_cnt; + __le32 dump_msdu_cnt; + __le32 burst_abort_next_frame_mismatch_cnt; + __le32 burst_abort_missing_next_frame_cnt; + __le32 cts_timeout_collision; + __le32 ack_or_ba_timeout_collision; + struct statistics_tx_non_phy_agg agg; + /* + * "tx_power" are optional parameters provided by uCode, + * 6000 series is the only device provide the information, + * Those are reserved fields for all the other devices + */ + struct statistics_tx_power tx_power; + __le32 reserved1; +} __packed; + + +struct statistics_div { + __le32 tx_on_a; + __le32 tx_on_b; + __le32 exec_time; + __le32 probe_time; + __le32 reserved1; + __le32 reserved2; +} __packed; + +struct statistics_general_common { + __le32 temperature; /* radio temperature */ + __le32 temperature_m; /* radio voltage */ + struct statistics_dbg dbg; + __le32 sleep_time; + __le32 slots_out; + __le32 slots_idle; + __le32 ttl_timestamp; + struct statistics_div div; + __le32 rx_enable_counter; + /* + * num_of_sos_states: + * count the number of times we have to re-tune + * in order to get out of bad PHY status + */ + __le32 num_of_sos_states; +} __packed; + +struct statistics_bt_activity { + /* Tx statistics */ + __le32 hi_priority_tx_req_cnt; + __le32 hi_priority_tx_denied_cnt; + __le32 lo_priority_tx_req_cnt; + __le32 lo_priority_tx_denied_cnt; + /* Rx statistics */ + __le32 hi_priority_rx_req_cnt; + __le32 hi_priority_rx_denied_cnt; + __le32 lo_priority_rx_req_cnt; + __le32 lo_priority_rx_denied_cnt; +} __packed; + +struct statistics_general { + struct statistics_general_common common; + __le32 reserved2; + __le32 reserved3; +} __packed; + +struct statistics_general_bt { + struct statistics_general_common common; + struct statistics_bt_activity activity; + __le32 reserved2; + __le32 reserved3; +} __packed; + +#define UCODE_STATISTICS_CLEAR_MSK (0x1 << 0) +#define UCODE_STATISTICS_FREQUENCY_MSK (0x1 << 1) +#define UCODE_STATISTICS_NARROW_BAND_MSK (0x1 << 2) + +/* + * REPLY_STATISTICS_CMD = 0x9c, + * all devices identical. + * + * This command triggers an immediate response containing uCode statistics. + * The response is in the same format as STATISTICS_NOTIFICATION 0x9d, below. + * + * If the CLEAR_STATS configuration flag is set, uCode will clear its + * internal copy of the statistics (counters) after issuing the response. + * This flag does not affect STATISTICS_NOTIFICATIONs after beacons (see below). + * + * If the DISABLE_NOTIF configuration flag is set, uCode will not issue + * STATISTICS_NOTIFICATIONs after received beacons (see below). This flag + * does not affect the response to the REPLY_STATISTICS_CMD 0x9c itself. + */ +#define IWL_STATS_CONF_CLEAR_STATS cpu_to_le32(0x1) /* see above */ +#define IWL_STATS_CONF_DISABLE_NOTIF cpu_to_le32(0x2)/* see above */ +struct iwl_statistics_cmd { + __le32 configuration_flags; /* IWL_STATS_CONF_* */ +} __packed; + +/* + * STATISTICS_NOTIFICATION = 0x9d (notification only, not a command) + * + * By default, uCode issues this notification after receiving a beacon + * while associated. To disable this behavior, set DISABLE_NOTIF flag in the + * REPLY_STATISTICS_CMD 0x9c, above. + * + * Statistics counters continue to increment beacon after beacon, but are + * cleared when changing channels or when driver issues REPLY_STATISTICS_CMD + * 0x9c with CLEAR_STATS bit set (see above). + * + * uCode also issues this notification during scans. uCode clears statistics + * appropriately so that each notification contains statistics for only the + * one channel that has just been scanned. + */ +#define STATISTICS_REPLY_FLG_BAND_24G_MSK cpu_to_le32(0x2) +#define STATISTICS_REPLY_FLG_HT40_MODE_MSK cpu_to_le32(0x8) + +struct iwl_notif_statistics { + __le32 flag; + struct statistics_rx rx; + struct statistics_tx tx; + struct statistics_general general; +} __packed; + +struct iwl_bt_notif_statistics { + __le32 flag; + struct statistics_rx_bt rx; + struct statistics_tx tx; + struct statistics_general_bt general; +} __packed; + +/* + * MISSED_BEACONS_NOTIFICATION = 0xa2 (notification only, not a command) + * + * uCode send MISSED_BEACONS_NOTIFICATION to driver when detect beacon missed + * in regardless of how many missed beacons, which mean when driver receive the + * notification, inside the command, it can find all the beacons information + * which include number of total missed beacons, number of consecutive missed + * beacons, number of beacons received and number of beacons expected to + * receive. + * + * If uCode detected consecutive_missed_beacons > 5, it will reset the radio + * in order to bring the radio/PHY back to working state; which has no relation + * to when driver will perform sensitivity calibration. + * + * Driver should set it own missed_beacon_threshold to decide when to perform + * sensitivity calibration based on number of consecutive missed beacons in + * order to improve overall performance, especially in noisy environment. + * + */ + +#define IWL_MISSED_BEACON_THRESHOLD_MIN (1) +#define IWL_MISSED_BEACON_THRESHOLD_DEF (5) +#define IWL_MISSED_BEACON_THRESHOLD_MAX IWL_MISSED_BEACON_THRESHOLD_DEF + +struct iwl_missed_beacon_notif { + __le32 consecutive_missed_beacons; + __le32 total_missed_becons; + __le32 num_expected_beacons; + __le32 num_recvd_beacons; +} __packed; + + +/****************************************************************************** + * (11) + * Rx Calibration Commands: + * + * With the uCode used for open source drivers, most Tx calibration (except + * for Tx Power) and most Rx calibration is done by uCode during the + * "initialize" phase of uCode boot. Driver must calibrate only: + * + * 1) Tx power (depends on temperature), described elsewhere + * 2) Receiver gain balance (optimize MIMO, and detect disconnected antennas) + * 3) Receiver sensitivity (to optimize signal detection) + * + *****************************************************************************/ + +/** + * SENSITIVITY_CMD = 0xa8 (command, has simple generic response) + * + * This command sets up the Rx signal detector for a sensitivity level that + * is high enough to lock onto all signals within the associated network, + * but low enough to ignore signals that are below a certain threshold, so as + * not to have too many "false alarms". False alarms are signals that the + * Rx DSP tries to lock onto, but then discards after determining that they + * are noise. + * + * The optimum number of false alarms is between 5 and 50 per 200 TUs + * (200 * 1024 uSecs, i.e. 204.8 milliseconds) of actual Rx time (i.e. + * time listening, not transmitting). Driver must adjust sensitivity so that + * the ratio of actual false alarms to actual Rx time falls within this range. + * + * While associated, uCode delivers STATISTICS_NOTIFICATIONs after each + * received beacon. These provide information to the driver to analyze the + * sensitivity. Don't analyze statistics that come in from scanning, or any + * other non-associated-network source. Pertinent statistics include: + * + * From "general" statistics (struct statistics_rx_non_phy): + * + * (beacon_energy_[abc] & 0x0FF00) >> 8 (unsigned, higher value is lower level) + * Measure of energy of desired signal. Used for establishing a level + * below which the device does not detect signals. + * + * (beacon_silence_rssi_[abc] & 0x0FF00) >> 8 (unsigned, units in dB) + * Measure of background noise in silent period after beacon. + * + * channel_load + * uSecs of actual Rx time during beacon period (varies according to + * how much time was spent transmitting). + * + * From "cck" and "ofdm" statistics (struct statistics_rx_phy), separately: + * + * false_alarm_cnt + * Signal locks abandoned early (before phy-level header). + * + * plcp_err + * Signal locks abandoned late (during phy-level header). + * + * NOTE: Both false_alarm_cnt and plcp_err increment monotonically from + * beacon to beacon, i.e. each value is an accumulation of all errors + * before and including the latest beacon. Values will wrap around to 0 + * after counting up to 2^32 - 1. Driver must differentiate vs. + * previous beacon's values to determine # false alarms in the current + * beacon period. + * + * Total number of false alarms = false_alarms + plcp_errs + * + * For OFDM, adjust the following table entries in struct iwl_sensitivity_cmd + * (notice that the start points for OFDM are at or close to settings for + * maximum sensitivity): + * + * START / MIN / MAX + * HD_AUTO_CORR32_X1_TH_ADD_MIN_INDEX 90 / 85 / 120 + * HD_AUTO_CORR32_X1_TH_ADD_MIN_MRC_INDEX 170 / 170 / 210 + * HD_AUTO_CORR32_X4_TH_ADD_MIN_INDEX 105 / 105 / 140 + * HD_AUTO_CORR32_X4_TH_ADD_MIN_MRC_INDEX 220 / 220 / 270 + * + * If actual rate of OFDM false alarms (+ plcp_errors) is too high + * (greater than 50 for each 204.8 msecs listening), reduce sensitivity + * by *adding* 1 to all 4 of the table entries above, up to the max for + * each entry. Conversely, if false alarm rate is too low (less than 5 + * for each 204.8 msecs listening), *subtract* 1 from each entry to + * increase sensitivity. + * + * For CCK sensitivity, keep track of the following: + * + * 1). 20-beacon history of maximum background noise, indicated by + * (beacon_silence_rssi_[abc] & 0x0FF00), units in dB, across the + * 3 receivers. For any given beacon, the "silence reference" is + * the maximum of last 60 samples (20 beacons * 3 receivers). + * + * 2). 10-beacon history of strongest signal level, as indicated + * by (beacon_energy_[abc] & 0x0FF00) >> 8, across the 3 receivers, + * i.e. the strength of the signal through the best receiver at the + * moment. These measurements are "upside down", with lower values + * for stronger signals, so max energy will be *minimum* value. + * + * Then for any given beacon, the driver must determine the *weakest* + * of the strongest signals; this is the minimum level that needs to be + * successfully detected, when using the best receiver at the moment. + * "Max cck energy" is the maximum (higher value means lower energy!) + * of the last 10 minima. Once this is determined, driver must add + * a little margin by adding "6" to it. + * + * 3). Number of consecutive beacon periods with too few false alarms. + * Reset this to 0 at the first beacon period that falls within the + * "good" range (5 to 50 false alarms per 204.8 milliseconds rx). + * + * Then, adjust the following CCK table entries in struct iwl_sensitivity_cmd + * (notice that the start points for CCK are at maximum sensitivity): + * + * START / MIN / MAX + * HD_AUTO_CORR40_X4_TH_ADD_MIN_INDEX 125 / 125 / 200 + * HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_INDEX 200 / 200 / 400 + * HD_MIN_ENERGY_CCK_DET_INDEX 100 / 0 / 100 + * + * If actual rate of CCK false alarms (+ plcp_errors) is too high + * (greater than 50 for each 204.8 msecs listening), method for reducing + * sensitivity is: + * + * 1) *Add* 3 to value in HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_INDEX, + * up to max 400. + * + * 2) If current value in HD_AUTO_CORR40_X4_TH_ADD_MIN_INDEX is < 160, + * sensitivity has been reduced a significant amount; bring it up to + * a moderate 161. Otherwise, *add* 3, up to max 200. + * + * 3) a) If current value in HD_AUTO_CORR40_X4_TH_ADD_MIN_INDEX is > 160, + * sensitivity has been reduced only a moderate or small amount; + * *subtract* 2 from value in HD_MIN_ENERGY_CCK_DET_INDEX, + * down to min 0. Otherwise (if gain has been significantly reduced), + * don't change the HD_MIN_ENERGY_CCK_DET_INDEX value. + * + * b) Save a snapshot of the "silence reference". + * + * If actual rate of CCK false alarms (+ plcp_errors) is too low + * (less than 5 for each 204.8 msecs listening), method for increasing + * sensitivity is used only if: + * + * 1a) Previous beacon did not have too many false alarms + * 1b) AND difference between previous "silence reference" and current + * "silence reference" (prev - current) is 2 or more, + * OR 2) 100 or more consecutive beacon periods have had rate of + * less than 5 false alarms per 204.8 milliseconds rx time. + * + * Method for increasing sensitivity: + * + * 1) *Subtract* 3 from value in HD_AUTO_CORR40_X4_TH_ADD_MIN_INDEX, + * down to min 125. + * + * 2) *Subtract* 3 from value in HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_INDEX, + * down to min 200. + * + * 3) *Add* 2 to value in HD_MIN_ENERGY_CCK_DET_INDEX, up to max 100. + * + * If actual rate of CCK false alarms (+ plcp_errors) is within good range + * (between 5 and 50 for each 204.8 msecs listening): + * + * 1) Save a snapshot of the silence reference. + * + * 2) If previous beacon had too many CCK false alarms (+ plcp_errors), + * give some extra margin to energy threshold by *subtracting* 8 + * from value in HD_MIN_ENERGY_CCK_DET_INDEX. + * + * For all cases (too few, too many, good range), make sure that the CCK + * detection threshold (energy) is below the energy level for robust + * detection over the past 10 beacon periods, the "Max cck energy". + * Lower values mean higher energy; this means making sure that the value + * in HD_MIN_ENERGY_CCK_DET_INDEX is at or *above* "Max cck energy". + * + */ + +/* + * Table entries in SENSITIVITY_CMD (struct iwl_sensitivity_cmd) + */ +#define HD_TABLE_SIZE (11) /* number of entries */ +#define HD_MIN_ENERGY_CCK_DET_INDEX (0) /* table indexes */ +#define HD_MIN_ENERGY_OFDM_DET_INDEX (1) +#define HD_AUTO_CORR32_X1_TH_ADD_MIN_INDEX (2) +#define HD_AUTO_CORR32_X1_TH_ADD_MIN_MRC_INDEX (3) +#define HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_INDEX (4) +#define HD_AUTO_CORR32_X4_TH_ADD_MIN_INDEX (5) +#define HD_AUTO_CORR32_X4_TH_ADD_MIN_MRC_INDEX (6) +#define HD_BARKER_CORR_TH_ADD_MIN_INDEX (7) +#define HD_BARKER_CORR_TH_ADD_MIN_MRC_INDEX (8) +#define HD_AUTO_CORR40_X4_TH_ADD_MIN_INDEX (9) +#define HD_OFDM_ENERGY_TH_IN_INDEX (10) + +/* + * Additional table entries in enhance SENSITIVITY_CMD + */ +#define HD_INA_NON_SQUARE_DET_OFDM_INDEX (11) +#define HD_INA_NON_SQUARE_DET_CCK_INDEX (12) +#define HD_CORR_11_INSTEAD_OF_CORR_9_EN_INDEX (13) +#define HD_OFDM_NON_SQUARE_DET_SLOPE_MRC_INDEX (14) +#define HD_OFDM_NON_SQUARE_DET_INTERCEPT_MRC_INDEX (15) +#define HD_OFDM_NON_SQUARE_DET_SLOPE_INDEX (16) +#define HD_OFDM_NON_SQUARE_DET_INTERCEPT_INDEX (17) +#define HD_CCK_NON_SQUARE_DET_SLOPE_MRC_INDEX (18) +#define HD_CCK_NON_SQUARE_DET_INTERCEPT_MRC_INDEX (19) +#define HD_CCK_NON_SQUARE_DET_SLOPE_INDEX (20) +#define HD_CCK_NON_SQUARE_DET_INTERCEPT_INDEX (21) +#define HD_RESERVED (22) + +/* number of entries for enhanced tbl */ +#define ENHANCE_HD_TABLE_SIZE (23) + +/* number of additional entries for enhanced tbl */ +#define ENHANCE_HD_TABLE_ENTRIES (ENHANCE_HD_TABLE_SIZE - HD_TABLE_SIZE) + +#define HD_INA_NON_SQUARE_DET_OFDM_DATA_V1 cpu_to_le16(0) +#define HD_INA_NON_SQUARE_DET_CCK_DATA_V1 cpu_to_le16(0) +#define HD_CORR_11_INSTEAD_OF_CORR_9_EN_DATA_V1 cpu_to_le16(0) +#define HD_OFDM_NON_SQUARE_DET_SLOPE_MRC_DATA_V1 cpu_to_le16(668) +#define HD_OFDM_NON_SQUARE_DET_INTERCEPT_MRC_DATA_V1 cpu_to_le16(4) +#define HD_OFDM_NON_SQUARE_DET_SLOPE_DATA_V1 cpu_to_le16(486) +#define HD_OFDM_NON_SQUARE_DET_INTERCEPT_DATA_V1 cpu_to_le16(37) +#define HD_CCK_NON_SQUARE_DET_SLOPE_MRC_DATA_V1 cpu_to_le16(853) +#define HD_CCK_NON_SQUARE_DET_INTERCEPT_MRC_DATA_V1 cpu_to_le16(4) +#define HD_CCK_NON_SQUARE_DET_SLOPE_DATA_V1 cpu_to_le16(476) +#define HD_CCK_NON_SQUARE_DET_INTERCEPT_DATA_V1 cpu_to_le16(99) + +#define HD_INA_NON_SQUARE_DET_OFDM_DATA_V2 cpu_to_le16(1) +#define HD_INA_NON_SQUARE_DET_CCK_DATA_V2 cpu_to_le16(1) +#define HD_CORR_11_INSTEAD_OF_CORR_9_EN_DATA_V2 cpu_to_le16(1) +#define HD_OFDM_NON_SQUARE_DET_SLOPE_MRC_DATA_V2 cpu_to_le16(600) +#define HD_OFDM_NON_SQUARE_DET_INTERCEPT_MRC_DATA_V2 cpu_to_le16(40) +#define HD_OFDM_NON_SQUARE_DET_SLOPE_DATA_V2 cpu_to_le16(486) +#define HD_OFDM_NON_SQUARE_DET_INTERCEPT_DATA_V2 cpu_to_le16(45) +#define HD_CCK_NON_SQUARE_DET_SLOPE_MRC_DATA_V2 cpu_to_le16(853) +#define HD_CCK_NON_SQUARE_DET_INTERCEPT_MRC_DATA_V2 cpu_to_le16(60) +#define HD_CCK_NON_SQUARE_DET_SLOPE_DATA_V2 cpu_to_le16(476) +#define HD_CCK_NON_SQUARE_DET_INTERCEPT_DATA_V2 cpu_to_le16(99) + + +/* Control field in struct iwl_sensitivity_cmd */ +#define SENSITIVITY_CMD_CONTROL_DEFAULT_TABLE cpu_to_le16(0) +#define SENSITIVITY_CMD_CONTROL_WORK_TABLE cpu_to_le16(1) + +/** + * struct iwl_sensitivity_cmd + * @control: (1) updates working table, (0) updates default table + * @table: energy threshold values, use HD_* as index into table + * + * Always use "1" in "control" to update uCode's working table and DSP. + */ +struct iwl_sensitivity_cmd { + __le16 control; /* always use "1" */ + __le16 table[HD_TABLE_SIZE]; /* use HD_* as index */ +} __packed; + +/* + * + */ +struct iwl_enhance_sensitivity_cmd { + __le16 control; /* always use "1" */ + __le16 enhance_table[ENHANCE_HD_TABLE_SIZE]; /* use HD_* as index */ +} __packed; + + +/** + * REPLY_PHY_CALIBRATION_CMD = 0xb0 (command, has simple generic response) + * + * This command sets the relative gains of agn device's 3 radio receiver chains. + * + * After the first association, driver should accumulate signal and noise + * statistics from the STATISTICS_NOTIFICATIONs that follow the first 20 + * beacons from the associated network (don't collect statistics that come + * in from scanning, or any other non-network source). + * + * DISCONNECTED ANTENNA: + * + * Driver should determine which antennas are actually connected, by comparing + * average beacon signal levels for the 3 Rx chains. Accumulate (add) the + * following values over 20 beacons, one accumulator for each of the chains + * a/b/c, from struct statistics_rx_non_phy: + * + * beacon_rssi_[abc] & 0x0FF (unsigned, units in dB) + * + * Find the strongest signal from among a/b/c. Compare the other two to the + * strongest. If any signal is more than 15 dB (times 20, unless you + * divide the accumulated values by 20) below the strongest, the driver + * considers that antenna to be disconnected, and should not try to use that + * antenna/chain for Rx or Tx. If both A and B seem to be disconnected, + * driver should declare the stronger one as connected, and attempt to use it + * (A and B are the only 2 Tx chains!). + * + * + * RX BALANCE: + * + * Driver should balance the 3 receivers (but just the ones that are connected + * to antennas, see above) for gain, by comparing the average signal levels + * detected during the silence after each beacon (background noise). + * Accumulate (add) the following values over 20 beacons, one accumulator for + * each of the chains a/b/c, from struct statistics_rx_non_phy: + * + * beacon_silence_rssi_[abc] & 0x0FF (unsigned, units in dB) + * + * Find the weakest background noise level from among a/b/c. This Rx chain + * will be the reference, with 0 gain adjustment. Attenuate other channels by + * finding noise difference: + * + * (accum_noise[i] - accum_noise[reference]) / 30 + * + * The "30" adjusts the dB in the 20 accumulated samples to units of 1.5 dB. + * For use in diff_gain_[abc] fields of struct iwl_calibration_cmd, the + * driver should limit the difference results to a range of 0-3 (0-4.5 dB), + * and set bit 2 to indicate "reduce gain". The value for the reference + * (weakest) chain should be "0". + * + * diff_gain_[abc] bit fields: + * 2: (1) reduce gain, (0) increase gain + * 1-0: amount of gain, units of 1.5 dB + */ + +/* Phy calibration command for series */ +enum { + IWL_PHY_CALIBRATE_DC_CMD = 8, + IWL_PHY_CALIBRATE_LO_CMD = 9, + IWL_PHY_CALIBRATE_TX_IQ_CMD = 11, + IWL_PHY_CALIBRATE_CRYSTAL_FRQ_CMD = 15, + IWL_PHY_CALIBRATE_BASE_BAND_CMD = 16, + IWL_PHY_CALIBRATE_TX_IQ_PERD_CMD = 17, + IWL_PHY_CALIBRATE_TEMP_OFFSET_CMD = 18, +}; + +/* This enum defines the bitmap of various calibrations to enable in both + * init ucode and runtime ucode through CALIBRATION_CFG_CMD. + */ +enum iwl_ucode_calib_cfg { + IWL_CALIB_CFG_RX_BB_IDX = BIT(0), + IWL_CALIB_CFG_DC_IDX = BIT(1), + IWL_CALIB_CFG_LO_IDX = BIT(2), + IWL_CALIB_CFG_TX_IQ_IDX = BIT(3), + IWL_CALIB_CFG_RX_IQ_IDX = BIT(4), + IWL_CALIB_CFG_NOISE_IDX = BIT(5), + IWL_CALIB_CFG_CRYSTAL_IDX = BIT(6), + IWL_CALIB_CFG_TEMPERATURE_IDX = BIT(7), + IWL_CALIB_CFG_PAPD_IDX = BIT(8), + IWL_CALIB_CFG_SENSITIVITY_IDX = BIT(9), + IWL_CALIB_CFG_TX_PWR_IDX = BIT(10), +}; + +#define IWL_CALIB_INIT_CFG_ALL cpu_to_le32(IWL_CALIB_CFG_RX_BB_IDX | \ + IWL_CALIB_CFG_DC_IDX | \ + IWL_CALIB_CFG_LO_IDX | \ + IWL_CALIB_CFG_TX_IQ_IDX | \ + IWL_CALIB_CFG_RX_IQ_IDX | \ + IWL_CALIB_CFG_CRYSTAL_IDX) + +#define IWL_CALIB_RT_CFG_ALL cpu_to_le32(IWL_CALIB_CFG_RX_BB_IDX | \ + IWL_CALIB_CFG_DC_IDX | \ + IWL_CALIB_CFG_LO_IDX | \ + IWL_CALIB_CFG_TX_IQ_IDX | \ + IWL_CALIB_CFG_RX_IQ_IDX | \ + IWL_CALIB_CFG_TEMPERATURE_IDX | \ + IWL_CALIB_CFG_PAPD_IDX | \ + IWL_CALIB_CFG_TX_PWR_IDX | \ + IWL_CALIB_CFG_CRYSTAL_IDX) + +#define IWL_CALIB_CFG_FLAG_SEND_COMPLETE_NTFY_MSK cpu_to_le32(BIT(0)) + +struct iwl_calib_cfg_elmnt_s { + __le32 is_enable; + __le32 start; + __le32 send_res; + __le32 apply_res; + __le32 reserved; +} __packed; + +struct iwl_calib_cfg_status_s { + struct iwl_calib_cfg_elmnt_s once; + struct iwl_calib_cfg_elmnt_s perd; + __le32 flags; +} __packed; + +struct iwl_calib_cfg_cmd { + struct iwl_calib_cfg_status_s ucd_calib_cfg; + struct iwl_calib_cfg_status_s drv_calib_cfg; + __le32 reserved1; +} __packed; + +struct iwl_calib_hdr { + u8 op_code; + u8 first_group; + u8 groups_num; + u8 data_valid; +} __packed; + +struct iwl_calib_cmd { + struct iwl_calib_hdr hdr; + u8 data[]; +} __packed; + +struct iwl_calib_xtal_freq_cmd { + struct iwl_calib_hdr hdr; + u8 cap_pin1; + u8 cap_pin2; + u8 pad[2]; +} __packed; + +#define DEFAULT_RADIO_SENSOR_OFFSET cpu_to_le16(2700) +struct iwl_calib_temperature_offset_cmd { + struct iwl_calib_hdr hdr; + __le16 radio_sensor_offset; + __le16 reserved; +} __packed; + +struct iwl_calib_temperature_offset_v2_cmd { + struct iwl_calib_hdr hdr; + __le16 radio_sensor_offset_high; + __le16 radio_sensor_offset_low; + __le16 burntVoltageRef; + __le16 reserved; +} __packed; + +/* IWL_PHY_CALIBRATE_CHAIN_NOISE_RESET_CMD */ +struct iwl_calib_chain_noise_reset_cmd { + struct iwl_calib_hdr hdr; + u8 data[]; +}; + +/* IWL_PHY_CALIBRATE_CHAIN_NOISE_GAIN_CMD */ +struct iwl_calib_chain_noise_gain_cmd { + struct iwl_calib_hdr hdr; + u8 delta_gain_1; + u8 delta_gain_2; + u8 pad[2]; +} __packed; + +/****************************************************************************** + * (12) + * Miscellaneous Commands: + * + *****************************************************************************/ + +/* + * LEDs Command & Response + * REPLY_LEDS_CMD = 0x48 (command, has simple generic response) + * + * For each of 3 possible LEDs (Activity/Link/Tech, selected by "id" field), + * this command turns it on or off, or sets up a periodic blinking cycle. + */ +struct iwl_led_cmd { + __le32 interval; /* "interval" in uSec */ + u8 id; /* 1: Activity, 2: Link, 3: Tech */ + u8 off; /* # intervals off while blinking; + * "0", with >0 "on" value, turns LED on */ + u8 on; /* # intervals on while blinking; + * "0", regardless of "off", turns LED off */ + u8 reserved; +} __packed; + +/* + * station priority table entries + * also used as potential "events" value for both + * COEX_MEDIUM_NOTIFICATION and COEX_EVENT_CMD + */ + +/* + * COEX events entry flag masks + * RP - Requested Priority + * WP - Win Medium Priority: priority assigned when the contention has been won + */ +#define COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG (0x1) +#define COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG (0x2) +#define COEX_EVT_FLAG_DELAY_MEDIUM_FREE_NTFY_FLG (0x4) + +#define COEX_CU_UNASSOC_IDLE_RP 4 +#define COEX_CU_UNASSOC_MANUAL_SCAN_RP 4 +#define COEX_CU_UNASSOC_AUTO_SCAN_RP 4 +#define COEX_CU_CALIBRATION_RP 4 +#define COEX_CU_PERIODIC_CALIBRATION_RP 4 +#define COEX_CU_CONNECTION_ESTAB_RP 4 +#define COEX_CU_ASSOCIATED_IDLE_RP 4 +#define COEX_CU_ASSOC_MANUAL_SCAN_RP 4 +#define COEX_CU_ASSOC_AUTO_SCAN_RP 4 +#define COEX_CU_ASSOC_ACTIVE_LEVEL_RP 4 +#define COEX_CU_RF_ON_RP 6 +#define COEX_CU_RF_OFF_RP 4 +#define COEX_CU_STAND_ALONE_DEBUG_RP 6 +#define COEX_CU_IPAN_ASSOC_LEVEL_RP 4 +#define COEX_CU_RSRVD1_RP 4 +#define COEX_CU_RSRVD2_RP 4 + +#define COEX_CU_UNASSOC_IDLE_WP 3 +#define COEX_CU_UNASSOC_MANUAL_SCAN_WP 3 +#define COEX_CU_UNASSOC_AUTO_SCAN_WP 3 +#define COEX_CU_CALIBRATION_WP 3 +#define COEX_CU_PERIODIC_CALIBRATION_WP 3 +#define COEX_CU_CONNECTION_ESTAB_WP 3 +#define COEX_CU_ASSOCIATED_IDLE_WP 3 +#define COEX_CU_ASSOC_MANUAL_SCAN_WP 3 +#define COEX_CU_ASSOC_AUTO_SCAN_WP 3 +#define COEX_CU_ASSOC_ACTIVE_LEVEL_WP 3 +#define COEX_CU_RF_ON_WP 3 +#define COEX_CU_RF_OFF_WP 3 +#define COEX_CU_STAND_ALONE_DEBUG_WP 6 +#define COEX_CU_IPAN_ASSOC_LEVEL_WP 3 +#define COEX_CU_RSRVD1_WP 3 +#define COEX_CU_RSRVD2_WP 3 + +#define COEX_UNASSOC_IDLE_FLAGS 0 +#define COEX_UNASSOC_MANUAL_SCAN_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG) +#define COEX_UNASSOC_AUTO_SCAN_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG) +#define COEX_CALIBRATION_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG) +#define COEX_PERIODIC_CALIBRATION_FLAGS 0 +/* + * COEX_CONNECTION_ESTAB: + * we need DELAY_MEDIUM_FREE_NTFY to let WiMAX disconnect from network. + */ +#define COEX_CONNECTION_ESTAB_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG | \ + COEX_EVT_FLAG_DELAY_MEDIUM_FREE_NTFY_FLG) +#define COEX_ASSOCIATED_IDLE_FLAGS 0 +#define COEX_ASSOC_MANUAL_SCAN_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG) +#define COEX_ASSOC_AUTO_SCAN_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG) +#define COEX_ASSOC_ACTIVE_LEVEL_FLAGS 0 +#define COEX_RF_ON_FLAGS 0 +#define COEX_RF_OFF_FLAGS 0 +#define COEX_STAND_ALONE_DEBUG_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG) +#define COEX_IPAN_ASSOC_LEVEL_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG | \ + COEX_EVT_FLAG_DELAY_MEDIUM_FREE_NTFY_FLG) +#define COEX_RSRVD1_FLAGS 0 +#define COEX_RSRVD2_FLAGS 0 +/* + * COEX_CU_RF_ON is the event wrapping all radio ownership. + * We need DELAY_MEDIUM_FREE_NTFY to let WiMAX disconnect from network. + */ +#define COEX_CU_RF_ON_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG | \ + COEX_EVT_FLAG_DELAY_MEDIUM_FREE_NTFY_FLG) + + +enum { + /* un-association part */ + COEX_UNASSOC_IDLE = 0, + COEX_UNASSOC_MANUAL_SCAN = 1, + COEX_UNASSOC_AUTO_SCAN = 2, + /* calibration */ + COEX_CALIBRATION = 3, + COEX_PERIODIC_CALIBRATION = 4, + /* connection */ + COEX_CONNECTION_ESTAB = 5, + /* association part */ + COEX_ASSOCIATED_IDLE = 6, + COEX_ASSOC_MANUAL_SCAN = 7, + COEX_ASSOC_AUTO_SCAN = 8, + COEX_ASSOC_ACTIVE_LEVEL = 9, + /* RF ON/OFF */ + COEX_RF_ON = 10, + COEX_RF_OFF = 11, + COEX_STAND_ALONE_DEBUG = 12, + /* IPAN */ + COEX_IPAN_ASSOC_LEVEL = 13, + /* reserved */ + COEX_RSRVD1 = 14, + COEX_RSRVD2 = 15, + COEX_NUM_OF_EVENTS = 16 +}; + +/* + * Coexistence WIFI/WIMAX Command + * COEX_PRIORITY_TABLE_CMD = 0x5a + * + */ +struct iwl_wimax_coex_event_entry { + u8 request_prio; + u8 win_medium_prio; + u8 reserved; + u8 flags; +} __packed; + +/* COEX flag masks */ + +/* Station table is valid */ +#define COEX_FLAGS_STA_TABLE_VALID_MSK (0x1) +/* UnMask wake up src at unassociated sleep */ +#define COEX_FLAGS_UNASSOC_WA_UNMASK_MSK (0x4) +/* UnMask wake up src at associated sleep */ +#define COEX_FLAGS_ASSOC_WA_UNMASK_MSK (0x8) +/* Enable CoEx feature. */ +#define COEX_FLAGS_COEX_ENABLE_MSK (0x80) + +struct iwl_wimax_coex_cmd { + u8 flags; + u8 reserved[3]; + struct iwl_wimax_coex_event_entry sta_prio[COEX_NUM_OF_EVENTS]; +} __packed; + +/* + * Coexistence MEDIUM NOTIFICATION + * COEX_MEDIUM_NOTIFICATION = 0x5b + * + * notification from uCode to host to indicate medium changes + * + */ +/* + * status field + * bit 0 - 2: medium status + * bit 3: medium change indication + * bit 4 - 31: reserved + */ +/* status option values, (0 - 2 bits) */ +#define COEX_MEDIUM_BUSY (0x0) /* radio belongs to WiMAX */ +#define COEX_MEDIUM_ACTIVE (0x1) /* radio belongs to WiFi */ +#define COEX_MEDIUM_PRE_RELEASE (0x2) /* received radio release */ +#define COEX_MEDIUM_MSK (0x7) + +/* send notification status (1 bit) */ +#define COEX_MEDIUM_CHANGED (0x8) +#define COEX_MEDIUM_CHANGED_MSK (0x8) +#define COEX_MEDIUM_SHIFT (3) + +struct iwl_coex_medium_notification { + __le32 status; + __le32 events; +} __packed; + +/* + * Coexistence EVENT Command + * COEX_EVENT_CMD = 0x5c + * + * send from host to uCode for coex event request. + */ +/* flags options */ +#define COEX_EVENT_REQUEST_MSK (0x1) + +struct iwl_coex_event_cmd { + u8 flags; + u8 event; + __le16 reserved; +} __packed; + +struct iwl_coex_event_resp { + __le32 status; +} __packed; + + +/****************************************************************************** + * Bluetooth Coexistence commands + * + *****************************************************************************/ + +/* + * BT Status notification + * REPLY_BT_COEX_PROFILE_NOTIF = 0xce + */ +enum iwl_bt_coex_profile_traffic_load { + IWL_BT_COEX_TRAFFIC_LOAD_NONE = 0, + IWL_BT_COEX_TRAFFIC_LOAD_LOW = 1, + IWL_BT_COEX_TRAFFIC_LOAD_HIGH = 2, + IWL_BT_COEX_TRAFFIC_LOAD_CONTINUOUS = 3, +/* + * There are no more even though below is a u8, the + * indication from the BT device only has two bits. + */ +}; + +#define BT_SESSION_ACTIVITY_1_UART_MSG 0x1 +#define BT_SESSION_ACTIVITY_2_UART_MSG 0x2 + +/* BT UART message - Share Part (BT -> WiFi) */ +#define BT_UART_MSG_FRAME1MSGTYPE_POS (0) +#define BT_UART_MSG_FRAME1MSGTYPE_MSK \ + (0x7 << BT_UART_MSG_FRAME1MSGTYPE_POS) +#define BT_UART_MSG_FRAME1SSN_POS (3) +#define BT_UART_MSG_FRAME1SSN_MSK \ + (0x3 << BT_UART_MSG_FRAME1SSN_POS) +#define BT_UART_MSG_FRAME1UPDATEREQ_POS (5) +#define BT_UART_MSG_FRAME1UPDATEREQ_MSK \ + (0x1 << BT_UART_MSG_FRAME1UPDATEREQ_POS) +#define BT_UART_MSG_FRAME1RESERVED_POS (6) +#define BT_UART_MSG_FRAME1RESERVED_MSK \ + (0x3 << BT_UART_MSG_FRAME1RESERVED_POS) + +#define BT_UART_MSG_FRAME2OPENCONNECTIONS_POS (0) +#define BT_UART_MSG_FRAME2OPENCONNECTIONS_MSK \ + (0x3 << BT_UART_MSG_FRAME2OPENCONNECTIONS_POS) +#define BT_UART_MSG_FRAME2TRAFFICLOAD_POS (2) +#define BT_UART_MSG_FRAME2TRAFFICLOAD_MSK \ + (0x3 << BT_UART_MSG_FRAME2TRAFFICLOAD_POS) +#define BT_UART_MSG_FRAME2CHLSEQN_POS (4) +#define BT_UART_MSG_FRAME2CHLSEQN_MSK \ + (0x1 << BT_UART_MSG_FRAME2CHLSEQN_POS) +#define BT_UART_MSG_FRAME2INBAND_POS (5) +#define BT_UART_MSG_FRAME2INBAND_MSK \ + (0x1 << BT_UART_MSG_FRAME2INBAND_POS) +#define BT_UART_MSG_FRAME2RESERVED_POS (6) +#define BT_UART_MSG_FRAME2RESERVED_MSK \ + (0x3 << BT_UART_MSG_FRAME2RESERVED_POS) + +#define BT_UART_MSG_FRAME3SCOESCO_POS (0) +#define BT_UART_MSG_FRAME3SCOESCO_MSK \ + (0x1 << BT_UART_MSG_FRAME3SCOESCO_POS) +#define BT_UART_MSG_FRAME3SNIFF_POS (1) +#define BT_UART_MSG_FRAME3SNIFF_MSK \ + (0x1 << BT_UART_MSG_FRAME3SNIFF_POS) +#define BT_UART_MSG_FRAME3A2DP_POS (2) +#define BT_UART_MSG_FRAME3A2DP_MSK \ + (0x1 << BT_UART_MSG_FRAME3A2DP_POS) +#define BT_UART_MSG_FRAME3ACL_POS (3) +#define BT_UART_MSG_FRAME3ACL_MSK \ + (0x1 << BT_UART_MSG_FRAME3ACL_POS) +#define BT_UART_MSG_FRAME3MASTER_POS (4) +#define BT_UART_MSG_FRAME3MASTER_MSK \ + (0x1 << BT_UART_MSG_FRAME3MASTER_POS) +#define BT_UART_MSG_FRAME3OBEX_POS (5) +#define BT_UART_MSG_FRAME3OBEX_MSK \ + (0x1 << BT_UART_MSG_FRAME3OBEX_POS) +#define BT_UART_MSG_FRAME3RESERVED_POS (6) +#define BT_UART_MSG_FRAME3RESERVED_MSK \ + (0x3 << BT_UART_MSG_FRAME3RESERVED_POS) + +#define BT_UART_MSG_FRAME4IDLEDURATION_POS (0) +#define BT_UART_MSG_FRAME4IDLEDURATION_MSK \ + (0x3F << BT_UART_MSG_FRAME4IDLEDURATION_POS) +#define BT_UART_MSG_FRAME4RESERVED_POS (6) +#define BT_UART_MSG_FRAME4RESERVED_MSK \ + (0x3 << BT_UART_MSG_FRAME4RESERVED_POS) + +#define BT_UART_MSG_FRAME5TXACTIVITY_POS (0) +#define BT_UART_MSG_FRAME5TXACTIVITY_MSK \ + (0x3 << BT_UART_MSG_FRAME5TXACTIVITY_POS) +#define BT_UART_MSG_FRAME5RXACTIVITY_POS (2) +#define BT_UART_MSG_FRAME5RXACTIVITY_MSK \ + (0x3 << BT_UART_MSG_FRAME5RXACTIVITY_POS) +#define BT_UART_MSG_FRAME5ESCORETRANSMIT_POS (4) +#define BT_UART_MSG_FRAME5ESCORETRANSMIT_MSK \ + (0x3 << BT_UART_MSG_FRAME5ESCORETRANSMIT_POS) +#define BT_UART_MSG_FRAME5RESERVED_POS (6) +#define BT_UART_MSG_FRAME5RESERVED_MSK \ + (0x3 << BT_UART_MSG_FRAME5RESERVED_POS) + +#define BT_UART_MSG_FRAME6SNIFFINTERVAL_POS (0) +#define BT_UART_MSG_FRAME6SNIFFINTERVAL_MSK \ + (0x1F << BT_UART_MSG_FRAME6SNIFFINTERVAL_POS) +#define BT_UART_MSG_FRAME6DISCOVERABLE_POS (5) +#define BT_UART_MSG_FRAME6DISCOVERABLE_MSK \ + (0x1 << BT_UART_MSG_FRAME6DISCOVERABLE_POS) +#define BT_UART_MSG_FRAME6RESERVED_POS (6) +#define BT_UART_MSG_FRAME6RESERVED_MSK \ + (0x3 << BT_UART_MSG_FRAME6RESERVED_POS) + +#define BT_UART_MSG_FRAME7SNIFFACTIVITY_POS (0) +#define BT_UART_MSG_FRAME7SNIFFACTIVITY_MSK \ + (0x7 << BT_UART_MSG_FRAME7SNIFFACTIVITY_POS) +#define BT_UART_MSG_FRAME7PAGE_POS (3) +#define BT_UART_MSG_FRAME7PAGE_MSK \ + (0x1 << BT_UART_MSG_FRAME7PAGE_POS) +#define BT_UART_MSG_FRAME7INQUIRY_POS (4) +#define BT_UART_MSG_FRAME7INQUIRY_MSK \ + (0x1 << BT_UART_MSG_FRAME7INQUIRY_POS) +#define BT_UART_MSG_FRAME7CONNECTABLE_POS (5) +#define BT_UART_MSG_FRAME7CONNECTABLE_MSK \ + (0x1 << BT_UART_MSG_FRAME7CONNECTABLE_POS) +#define BT_UART_MSG_FRAME7RESERVED_POS (6) +#define BT_UART_MSG_FRAME7RESERVED_MSK \ + (0x3 << BT_UART_MSG_FRAME7RESERVED_POS) + +/* BT Session Activity 2 UART message (BT -> WiFi) */ +#define BT_UART_MSG_2_FRAME1RESERVED1_POS (5) +#define BT_UART_MSG_2_FRAME1RESERVED1_MSK \ + (0x1<<BT_UART_MSG_2_FRAME1RESERVED1_POS) +#define BT_UART_MSG_2_FRAME1RESERVED2_POS (6) +#define BT_UART_MSG_2_FRAME1RESERVED2_MSK \ + (0x3<<BT_UART_MSG_2_FRAME1RESERVED2_POS) + +#define BT_UART_MSG_2_FRAME2AGGTRAFFICLOAD_POS (0) +#define BT_UART_MSG_2_FRAME2AGGTRAFFICLOAD_MSK \ + (0x3F<<BT_UART_MSG_2_FRAME2AGGTRAFFICLOAD_POS) +#define BT_UART_MSG_2_FRAME2RESERVED_POS (6) +#define BT_UART_MSG_2_FRAME2RESERVED_MSK \ + (0x3<<BT_UART_MSG_2_FRAME2RESERVED_POS) + +#define BT_UART_MSG_2_FRAME3BRLASTTXPOWER_POS (0) +#define BT_UART_MSG_2_FRAME3BRLASTTXPOWER_MSK \ + (0xF<<BT_UART_MSG_2_FRAME3BRLASTTXPOWER_POS) +#define BT_UART_MSG_2_FRAME3INQPAGESRMODE_POS (4) +#define BT_UART_MSG_2_FRAME3INQPAGESRMODE_MSK \ + (0x1<<BT_UART_MSG_2_FRAME3INQPAGESRMODE_POS) +#define BT_UART_MSG_2_FRAME3LEMASTER_POS (5) +#define BT_UART_MSG_2_FRAME3LEMASTER_MSK \ + (0x1<<BT_UART_MSG_2_FRAME3LEMASTER_POS) +#define BT_UART_MSG_2_FRAME3RESERVED_POS (6) +#define BT_UART_MSG_2_FRAME3RESERVED_MSK \ + (0x3<<BT_UART_MSG_2_FRAME3RESERVED_POS) + +#define BT_UART_MSG_2_FRAME4LELASTTXPOWER_POS (0) +#define BT_UART_MSG_2_FRAME4LELASTTXPOWER_MSK \ + (0xF<<BT_UART_MSG_2_FRAME4LELASTTXPOWER_POS) +#define BT_UART_MSG_2_FRAME4NUMLECONN_POS (4) +#define BT_UART_MSG_2_FRAME4NUMLECONN_MSK \ + (0x3<<BT_UART_MSG_2_FRAME4NUMLECONN_POS) +#define BT_UART_MSG_2_FRAME4RESERVED_POS (6) +#define BT_UART_MSG_2_FRAME4RESERVED_MSK \ + (0x3<<BT_UART_MSG_2_FRAME4RESERVED_POS) + +#define BT_UART_MSG_2_FRAME5BTMINRSSI_POS (0) +#define BT_UART_MSG_2_FRAME5BTMINRSSI_MSK \ + (0xF<<BT_UART_MSG_2_FRAME5BTMINRSSI_POS) +#define BT_UART_MSG_2_FRAME5LESCANINITMODE_POS (4) +#define BT_UART_MSG_2_FRAME5LESCANINITMODE_MSK \ + (0x1<<BT_UART_MSG_2_FRAME5LESCANINITMODE_POS) +#define BT_UART_MSG_2_FRAME5LEADVERMODE_POS (5) +#define BT_UART_MSG_2_FRAME5LEADVERMODE_MSK \ + (0x1<<BT_UART_MSG_2_FRAME5LEADVERMODE_POS) +#define BT_UART_MSG_2_FRAME5RESERVED_POS (6) +#define BT_UART_MSG_2_FRAME5RESERVED_MSK \ + (0x3<<BT_UART_MSG_2_FRAME5RESERVED_POS) + +#define BT_UART_MSG_2_FRAME6LECONNINTERVAL_POS (0) +#define BT_UART_MSG_2_FRAME6LECONNINTERVAL_MSK \ + (0x1F<<BT_UART_MSG_2_FRAME6LECONNINTERVAL_POS) +#define BT_UART_MSG_2_FRAME6RFU_POS (5) +#define BT_UART_MSG_2_FRAME6RFU_MSK \ + (0x1<<BT_UART_MSG_2_FRAME6RFU_POS) +#define BT_UART_MSG_2_FRAME6RESERVED_POS (6) +#define BT_UART_MSG_2_FRAME6RESERVED_MSK \ + (0x3<<BT_UART_MSG_2_FRAME6RESERVED_POS) + +#define BT_UART_MSG_2_FRAME7LECONNSLAVELAT_POS (0) +#define BT_UART_MSG_2_FRAME7LECONNSLAVELAT_MSK \ + (0x7<<BT_UART_MSG_2_FRAME7LECONNSLAVELAT_POS) +#define BT_UART_MSG_2_FRAME7LEPROFILE1_POS (3) +#define BT_UART_MSG_2_FRAME7LEPROFILE1_MSK \ + (0x1<<BT_UART_MSG_2_FRAME7LEPROFILE1_POS) +#define BT_UART_MSG_2_FRAME7LEPROFILE2_POS (4) +#define BT_UART_MSG_2_FRAME7LEPROFILE2_MSK \ + (0x1<<BT_UART_MSG_2_FRAME7LEPROFILE2_POS) +#define BT_UART_MSG_2_FRAME7LEPROFILEOTHER_POS (5) +#define BT_UART_MSG_2_FRAME7LEPROFILEOTHER_MSK \ + (0x1<<BT_UART_MSG_2_FRAME7LEPROFILEOTHER_POS) +#define BT_UART_MSG_2_FRAME7RESERVED_POS (6) +#define BT_UART_MSG_2_FRAME7RESERVED_MSK \ + (0x3<<BT_UART_MSG_2_FRAME7RESERVED_POS) + + +#define BT_ENABLE_REDUCED_TXPOWER_THRESHOLD (-62) +#define BT_DISABLE_REDUCED_TXPOWER_THRESHOLD (-65) + +struct iwl_bt_uart_msg { + u8 header; + u8 frame1; + u8 frame2; + u8 frame3; + u8 frame4; + u8 frame5; + u8 frame6; + u8 frame7; +} __packed; + +struct iwl_bt_coex_profile_notif { + struct iwl_bt_uart_msg last_bt_uart_msg; + u8 bt_status; /* 0 - off, 1 - on */ + u8 bt_traffic_load; /* 0 .. 3? */ + u8 bt_ci_compliance; /* 0 - not complied, 1 - complied */ + u8 reserved; +} __packed; + +#define IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS 0 +#define IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_MSK 0x1 +#define IWL_BT_COEX_PRIO_TBL_PRIO_POS 1 +#define IWL_BT_COEX_PRIO_TBL_PRIO_MASK 0x0e +#define IWL_BT_COEX_PRIO_TBL_RESERVED_POS 4 +#define IWL_BT_COEX_PRIO_TBL_RESERVED_MASK 0xf0 +#define IWL_BT_COEX_PRIO_TBL_PRIO_SHIFT 1 + +/* + * BT Coexistence Priority table + * REPLY_BT_COEX_PRIO_TABLE = 0xcc + */ +enum bt_coex_prio_table_events { + BT_COEX_PRIO_TBL_EVT_INIT_CALIB1 = 0, + BT_COEX_PRIO_TBL_EVT_INIT_CALIB2 = 1, + BT_COEX_PRIO_TBL_EVT_PERIODIC_CALIB_LOW1 = 2, + BT_COEX_PRIO_TBL_EVT_PERIODIC_CALIB_LOW2 = 3, /* DC calib */ + BT_COEX_PRIO_TBL_EVT_PERIODIC_CALIB_HIGH1 = 4, + BT_COEX_PRIO_TBL_EVT_PERIODIC_CALIB_HIGH2 = 5, + BT_COEX_PRIO_TBL_EVT_DTIM = 6, + BT_COEX_PRIO_TBL_EVT_SCAN52 = 7, + BT_COEX_PRIO_TBL_EVT_SCAN24 = 8, + BT_COEX_PRIO_TBL_EVT_RESERVED0 = 9, + BT_COEX_PRIO_TBL_EVT_RESERVED1 = 10, + BT_COEX_PRIO_TBL_EVT_RESERVED2 = 11, + BT_COEX_PRIO_TBL_EVT_RESERVED3 = 12, + BT_COEX_PRIO_TBL_EVT_RESERVED4 = 13, + BT_COEX_PRIO_TBL_EVT_RESERVED5 = 14, + BT_COEX_PRIO_TBL_EVT_RESERVED6 = 15, + /* BT_COEX_PRIO_TBL_EVT_MAX should always be last */ + BT_COEX_PRIO_TBL_EVT_MAX, +}; + +enum bt_coex_prio_table_priorities { + BT_COEX_PRIO_TBL_DISABLED = 0, + BT_COEX_PRIO_TBL_PRIO_LOW = 1, + BT_COEX_PRIO_TBL_PRIO_HIGH = 2, + BT_COEX_PRIO_TBL_PRIO_BYPASS = 3, + BT_COEX_PRIO_TBL_PRIO_COEX_OFF = 4, + BT_COEX_PRIO_TBL_PRIO_COEX_ON = 5, + BT_COEX_PRIO_TBL_PRIO_RSRVD1 = 6, + BT_COEX_PRIO_TBL_PRIO_RSRVD2 = 7, + BT_COEX_PRIO_TBL_MAX, +}; + +struct iwl_bt_coex_prio_table_cmd { + u8 prio_tbl[BT_COEX_PRIO_TBL_EVT_MAX]; +} __packed; + +#define IWL_BT_COEX_ENV_CLOSE 0 +#define IWL_BT_COEX_ENV_OPEN 1 +/* + * BT Protection Envelope + * REPLY_BT_COEX_PROT_ENV = 0xcd + */ +struct iwl_bt_coex_prot_env_cmd { + u8 action; /* 0 = closed, 1 = open */ + u8 type; /* 0 .. 15 */ + u8 reserved[2]; +} __packed; + +/* + * REPLY_D3_CONFIG + */ +enum iwlagn_d3_wakeup_filters { + IWLAGN_D3_WAKEUP_RFKILL = BIT(0), + IWLAGN_D3_WAKEUP_SYSASSERT = BIT(1), +}; + +struct iwlagn_d3_config_cmd { + __le32 min_sleep_time; + __le32 wakeup_flags; +} __packed; + +/* + * REPLY_WOWLAN_PATTERNS + */ +#define IWLAGN_WOWLAN_MIN_PATTERN_LEN 16 +#define IWLAGN_WOWLAN_MAX_PATTERN_LEN 128 + +struct iwlagn_wowlan_pattern { + u8 mask[IWLAGN_WOWLAN_MAX_PATTERN_LEN / 8]; + u8 pattern[IWLAGN_WOWLAN_MAX_PATTERN_LEN]; + u8 mask_size; + u8 pattern_size; + __le16 reserved; +} __packed; + +#define IWLAGN_WOWLAN_MAX_PATTERNS 20 + +struct iwlagn_wowlan_patterns_cmd { + __le32 n_patterns; + struct iwlagn_wowlan_pattern patterns[]; +} __packed; + +/* + * REPLY_WOWLAN_WAKEUP_FILTER + */ +enum iwlagn_wowlan_wakeup_filters { + IWLAGN_WOWLAN_WAKEUP_MAGIC_PACKET = BIT(0), + IWLAGN_WOWLAN_WAKEUP_PATTERN_MATCH = BIT(1), + IWLAGN_WOWLAN_WAKEUP_BEACON_MISS = BIT(2), + IWLAGN_WOWLAN_WAKEUP_LINK_CHANGE = BIT(3), + IWLAGN_WOWLAN_WAKEUP_GTK_REKEY_FAIL = BIT(4), + IWLAGN_WOWLAN_WAKEUP_EAP_IDENT_REQ = BIT(5), + IWLAGN_WOWLAN_WAKEUP_4WAY_HANDSHAKE = BIT(6), + IWLAGN_WOWLAN_WAKEUP_ALWAYS = BIT(7), + IWLAGN_WOWLAN_WAKEUP_ENABLE_NET_DETECT = BIT(8), +}; + +struct iwlagn_wowlan_wakeup_filter_cmd { + __le32 enabled; + __le16 non_qos_seq; + __le16 reserved; + __le16 qos_seq[8]; +}; + +/* + * REPLY_WOWLAN_TSC_RSC_PARAMS + */ +#define IWLAGN_NUM_RSC 16 + +struct tkip_sc { + __le16 iv16; + __le16 pad; + __le32 iv32; +} __packed; + +struct iwlagn_tkip_rsc_tsc { + struct tkip_sc unicast_rsc[IWLAGN_NUM_RSC]; + struct tkip_sc multicast_rsc[IWLAGN_NUM_RSC]; + struct tkip_sc tsc; +} __packed; + +struct aes_sc { + __le64 pn; +} __packed; + +struct iwlagn_aes_rsc_tsc { + struct aes_sc unicast_rsc[IWLAGN_NUM_RSC]; + struct aes_sc multicast_rsc[IWLAGN_NUM_RSC]; + struct aes_sc tsc; +} __packed; + +union iwlagn_all_tsc_rsc { + struct iwlagn_tkip_rsc_tsc tkip; + struct iwlagn_aes_rsc_tsc aes; +}; + +struct iwlagn_wowlan_rsc_tsc_params_cmd { + union iwlagn_all_tsc_rsc all_tsc_rsc; +} __packed; + +/* + * REPLY_WOWLAN_TKIP_PARAMS + */ +#define IWLAGN_MIC_KEY_SIZE 8 +#define IWLAGN_P1K_SIZE 5 +struct iwlagn_mic_keys { + u8 tx[IWLAGN_MIC_KEY_SIZE]; + u8 rx_unicast[IWLAGN_MIC_KEY_SIZE]; + u8 rx_mcast[IWLAGN_MIC_KEY_SIZE]; +} __packed; + +struct iwlagn_p1k_cache { + __le16 p1k[IWLAGN_P1K_SIZE]; +} __packed; + +#define IWLAGN_NUM_RX_P1K_CACHE 2 + +struct iwlagn_wowlan_tkip_params_cmd { + struct iwlagn_mic_keys mic_keys; + struct iwlagn_p1k_cache tx; + struct iwlagn_p1k_cache rx_uni[IWLAGN_NUM_RX_P1K_CACHE]; + struct iwlagn_p1k_cache rx_multi[IWLAGN_NUM_RX_P1K_CACHE]; +} __packed; + +/* + * REPLY_WOWLAN_KEK_KCK_MATERIAL + */ + +#define IWLAGN_KCK_MAX_SIZE 32 +#define IWLAGN_KEK_MAX_SIZE 32 + +struct iwlagn_wowlan_kek_kck_material_cmd { + u8 kck[IWLAGN_KCK_MAX_SIZE]; + u8 kek[IWLAGN_KEK_MAX_SIZE]; + __le16 kck_len; + __le16 kek_len; + __le64 replay_ctr; +} __packed; + +#define RF_KILL_INDICATOR_FOR_WOWLAN 0x87 + +/* + * REPLY_WOWLAN_GET_STATUS = 0xe5 + */ +struct iwlagn_wowlan_status { + __le64 replay_ctr; + __le32 rekey_status; + __le32 wakeup_reason; + u8 pattern_number; + u8 reserved1; + __le16 qos_seq_ctr[8]; + __le16 non_qos_seq_ctr; + __le16 reserved2; + union iwlagn_all_tsc_rsc tsc_rsc; + __le16 reserved3; +} __packed; + +/* + * REPLY_WIPAN_PARAMS = 0xb2 (Commands and Notification) + */ + +/* + * Minimum slot time in TU + */ +#define IWL_MIN_SLOT_TIME 20 + +/** + * struct iwl_wipan_slot + * @width: Time in TU + * @type: + * 0 - BSS + * 1 - PAN + */ +struct iwl_wipan_slot { + __le16 width; + u8 type; + u8 reserved; +} __packed; + +#define IWL_WIPAN_PARAMS_FLG_LEAVE_CHANNEL_CTS BIT(1) /* reserved */ +#define IWL_WIPAN_PARAMS_FLG_LEAVE_CHANNEL_QUIET BIT(2) /* reserved */ +#define IWL_WIPAN_PARAMS_FLG_SLOTTED_MODE BIT(3) /* reserved */ +#define IWL_WIPAN_PARAMS_FLG_FILTER_BEACON_NOTIF BIT(4) +#define IWL_WIPAN_PARAMS_FLG_FULL_SLOTTED_MODE BIT(5) + +/** + * struct iwl_wipan_params_cmd + * @flags: + * bit0: reserved + * bit1: CP leave channel with CTS + * bit2: CP leave channel qith Quiet + * bit3: slotted mode + * 1 - work in slotted mode + * 0 - work in non slotted mode + * bit4: filter beacon notification + * bit5: full tx slotted mode. if this flag is set, + * uCode will perform leaving channel methods in context switch + * also when working in same channel mode + * @num_slots: 1 - 10 + */ +struct iwl_wipan_params_cmd { + __le16 flags; + u8 reserved; + u8 num_slots; + struct iwl_wipan_slot slots[10]; +} __packed; + +/* + * REPLY_WIPAN_P2P_CHANNEL_SWITCH = 0xb9 + * + * TODO: Figure out what this is used for, + * it can only switch between 2.4 GHz + * channels!! + */ + +struct iwl_wipan_p2p_channel_switch_cmd { + __le16 channel; + __le16 reserved; +}; + +/* + * REPLY_WIPAN_NOA_NOTIFICATION = 0xbc + * + * This is used by the device to notify us of the + * NoA schedule it determined so we can forward it + * to userspace for inclusion in probe responses. + * + * In beacons, the NoA schedule is simply appended + * to the frame we give the device. + */ + +struct iwl_wipan_noa_descriptor { + u8 count; + __le32 duration; + __le32 interval; + __le32 starttime; +} __packed; + +struct iwl_wipan_noa_attribute { + u8 id; + __le16 length; + u8 index; + u8 ct_window; + struct iwl_wipan_noa_descriptor descr0, descr1; + u8 reserved; +} __packed; + +struct iwl_wipan_noa_notification { + u32 noa_active; + struct iwl_wipan_noa_attribute noa_attribute; +} __packed; + +#endif /* __iwl_commands_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/debugfs.c b/drivers/net/wireless/intel/iwlwifi/dvm/debugfs.c new file mode 100644 index 000000000..b246dbd37 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/debugfs.c @@ -0,0 +1,2381 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2008 - 2014 Intel Corporation. All rights reserved. + * Copyright (C) 2018 Intel Corporation + *****************************************************************************/ + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/debugfs.h> +#include <linux/ieee80211.h> +#include <net/mac80211.h> + +#include "iwl-debug.h" +#include "iwl-trans.h" +#include "iwl-io.h" +#include "dev.h" +#include "agn.h" + +/* create and remove of files */ +#define DEBUGFS_ADD_FILE(name, parent, mode) do { \ + debugfs_create_file(#name, mode, parent, priv, \ + &iwl_dbgfs_##name##_ops); \ +} while (0) + +/* file operation */ +#define DEBUGFS_READ_FILE_OPS(name) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .read = iwl_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_WRITE_FILE_OPS(name) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .write = iwl_dbgfs_##name##_write, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + + +#define DEBUGFS_READ_WRITE_FILE_OPS(name) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .write = iwl_dbgfs_##name##_write, \ + .read = iwl_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +static ssize_t iwl_dbgfs_sram_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + u32 val = 0; + char *buf; + ssize_t ret; + int i = 0; + bool device_format = false; + int offset = 0; + int len = 0; + int pos = 0; + int sram; + struct iwl_priv *priv = file->private_data; + const struct fw_img *img; + size_t bufsz; + + if (!iwl_is_ready_rf(priv)) + return -EAGAIN; + + /* default is to dump the entire data segment */ + if (!priv->dbgfs_sram_offset && !priv->dbgfs_sram_len) { + priv->dbgfs_sram_offset = 0x800000; + if (!priv->ucode_loaded) + return -EINVAL; + img = &priv->fw->img[priv->cur_ucode]; + priv->dbgfs_sram_len = img->sec[IWL_UCODE_SECTION_DATA].len; + } + len = priv->dbgfs_sram_len; + + if (len == -4) { + device_format = true; + len = 4; + } + + bufsz = 50 + len * 4; + buf = kmalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pos += scnprintf(buf + pos, bufsz - pos, "sram_len: 0x%x\n", + len); + pos += scnprintf(buf + pos, bufsz - pos, "sram_offset: 0x%x\n", + priv->dbgfs_sram_offset); + + /* adjust sram address since reads are only on even u32 boundaries */ + offset = priv->dbgfs_sram_offset & 0x3; + sram = priv->dbgfs_sram_offset & ~0x3; + + /* read the first u32 from sram */ + val = iwl_trans_read_mem32(priv->trans, sram); + + for (; len; len--) { + /* put the address at the start of every line */ + if (i == 0) + pos += scnprintf(buf + pos, bufsz - pos, + "%08X: ", sram + offset); + + if (device_format) + pos += scnprintf(buf + pos, bufsz - pos, + "%02x", (val >> (8 * (3 - offset))) & 0xff); + else + pos += scnprintf(buf + pos, bufsz - pos, + "%02x ", (val >> (8 * offset)) & 0xff); + + /* if all bytes processed, read the next u32 from sram */ + if (++offset == 4) { + sram += 4; + offset = 0; + val = iwl_trans_read_mem32(priv->trans, sram); + } + + /* put in extra spaces and split lines for human readability */ + if (++i == 16) { + i = 0; + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + } else if (!(i & 7)) { + pos += scnprintf(buf + pos, bufsz - pos, " "); + } else if (!(i & 3)) { + pos += scnprintf(buf + pos, bufsz - pos, " "); + } + } + if (i) + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_sram_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[64]; + int buf_size; + u32 offset, len; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (sscanf(buf, "%x,%x", &offset, &len) == 2) { + priv->dbgfs_sram_offset = offset; + priv->dbgfs_sram_len = len; + } else if (sscanf(buf, "%x", &offset) == 1) { + priv->dbgfs_sram_offset = offset; + priv->dbgfs_sram_len = -4; + } else { + priv->dbgfs_sram_offset = 0; + priv->dbgfs_sram_len = 0; + } + + return count; +} + +static ssize_t iwl_dbgfs_wowlan_sram_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + const struct fw_img *img = &priv->fw->img[IWL_UCODE_WOWLAN]; + + if (!priv->wowlan_sram) + return -ENODATA; + + return simple_read_from_buffer(user_buf, count, ppos, + priv->wowlan_sram, + img->sec[IWL_UCODE_SECTION_DATA].len); +} +static ssize_t iwl_dbgfs_stations_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + struct iwl_station_entry *station; + struct iwl_tid_data *tid_data; + char *buf; + int i, j, pos = 0; + ssize_t ret; + /* Add 30 for initial string */ + const size_t bufsz = 30 + sizeof(char) * 500 * (priv->num_stations); + + buf = kmalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pos += scnprintf(buf + pos, bufsz - pos, "num of stations: %d\n\n", + priv->num_stations); + + for (i = 0; i < IWLAGN_STATION_COUNT; i++) { + station = &priv->stations[i]; + if (!station->used) + continue; + pos += scnprintf(buf + pos, bufsz - pos, + "station %d - addr: %pM, flags: %#x\n", + i, station->sta.sta.addr, + station->sta.station_flags_msk); + pos += scnprintf(buf + pos, bufsz - pos, + "TID seqno next_rclmd " + "rate_n_flags state txq\n"); + + for (j = 0; j < IWL_MAX_TID_COUNT; j++) { + tid_data = &priv->tid_data[i][j]; + pos += scnprintf(buf + pos, bufsz - pos, + "%d: 0x%.4x 0x%.4x 0x%.8x " + "%d %.2d", + j, tid_data->seq_number, + tid_data->next_reclaimed, + tid_data->agg.rate_n_flags, + tid_data->agg.state, + tid_data->agg.txq_id); + + if (tid_data->agg.wait_for_ba) + pos += scnprintf(buf + pos, bufsz - pos, + " - waitforba"); + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + } + + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_nvm_read(struct file *file, + char __user *user_buf, + size_t count, + loff_t *ppos) +{ + ssize_t ret; + struct iwl_priv *priv = file->private_data; + int pos = 0, ofs = 0, buf_size = 0; + const u8 *ptr; + char *buf; + u16 nvm_ver; + size_t eeprom_len = priv->eeprom_blob_size; + buf_size = 4 * eeprom_len + 256; + + if (eeprom_len % 16) + return -ENODATA; + + ptr = priv->eeprom_blob; + if (!ptr) + return -ENOMEM; + + /* 4 characters for byte 0xYY */ + buf = kzalloc(buf_size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + nvm_ver = priv->nvm_data->nvm_version; + pos += scnprintf(buf + pos, buf_size - pos, + "NVM version: 0x%x\n", nvm_ver); + for (ofs = 0 ; ofs < eeprom_len ; ofs += 16) { + pos += scnprintf(buf + pos, buf_size - pos, "0x%.4x %16ph\n", + ofs, ptr + ofs); + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_channels_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + struct ieee80211_channel *channels = NULL; + const struct ieee80211_supported_band *supp_band = NULL; + int pos = 0, i, bufsz = PAGE_SIZE; + char *buf; + ssize_t ret; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + supp_band = iwl_get_hw_mode(priv, NL80211_BAND_2GHZ); + if (supp_band) { + channels = supp_band->channels; + + pos += scnprintf(buf + pos, bufsz - pos, + "Displaying %d channels in 2.4GHz band 802.11bg):\n", + supp_band->n_channels); + + for (i = 0; i < supp_band->n_channels; i++) + pos += scnprintf(buf + pos, bufsz - pos, + "%d: %ddBm: BSS%s%s, %s.\n", + channels[i].hw_value, + channels[i].max_power, + channels[i].flags & IEEE80211_CHAN_RADAR ? + " (IEEE 802.11h required)" : "", + ((channels[i].flags & IEEE80211_CHAN_NO_IR) + || (channels[i].flags & + IEEE80211_CHAN_RADAR)) ? "" : + ", IBSS", + channels[i].flags & + IEEE80211_CHAN_NO_IR ? + "passive only" : "active/passive"); + } + supp_band = iwl_get_hw_mode(priv, NL80211_BAND_5GHZ); + if (supp_band) { + channels = supp_band->channels; + + pos += scnprintf(buf + pos, bufsz - pos, + "Displaying %d channels in 5.2GHz band (802.11a)\n", + supp_band->n_channels); + + for (i = 0; i < supp_band->n_channels; i++) + pos += scnprintf(buf + pos, bufsz - pos, + "%d: %ddBm: BSS%s%s, %s.\n", + channels[i].hw_value, + channels[i].max_power, + channels[i].flags & IEEE80211_CHAN_RADAR ? + " (IEEE 802.11h required)" : "", + ((channels[i].flags & IEEE80211_CHAN_NO_IR) + || (channels[i].flags & + IEEE80211_CHAN_RADAR)) ? "" : + ", IBSS", + channels[i].flags & + IEEE80211_CHAN_NO_IR ? + "passive only" : "active/passive"); + } + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_status_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + char buf[512]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_RF_KILL_HW:\t %d\n", + test_bit(STATUS_RF_KILL_HW, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_CT_KILL:\t\t %d\n", + test_bit(STATUS_CT_KILL, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_ALIVE:\t\t %d\n", + test_bit(STATUS_ALIVE, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_READY:\t\t %d\n", + test_bit(STATUS_READY, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_EXIT_PENDING:\t %d\n", + test_bit(STATUS_EXIT_PENDING, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_STATISTICS:\t %d\n", + test_bit(STATUS_STATISTICS, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_SCANNING:\t %d\n", + test_bit(STATUS_SCANNING, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_SCAN_ABORTING:\t %d\n", + test_bit(STATUS_SCAN_ABORTING, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_SCAN_HW:\t\t %d\n", + test_bit(STATUS_SCAN_HW, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_POWER_PMI:\t %d\n", + test_bit(STATUS_POWER_PMI, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_FW_ERROR:\t %d\n", + test_bit(STATUS_FW_ERROR, &priv->status)); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_rx_handlers_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + + int pos = 0; + int cnt = 0; + char *buf; + int bufsz = 24 * 64; /* 24 items * 64 char per item */ + ssize_t ret; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (cnt = 0; cnt < REPLY_MAX; cnt++) { + if (priv->rx_handlers_stats[cnt] > 0) + pos += scnprintf(buf + pos, bufsz - pos, + "\tRx handler[%36s]:\t\t %u\n", + iwl_get_cmd_string(priv->trans, (u32)cnt), + priv->rx_handlers_stats[cnt]); + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_rx_handlers_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + + char buf[8]; + int buf_size; + u32 reset_flag; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%x", &reset_flag) != 1) + return -EFAULT; + if (reset_flag == 0) + memset(&priv->rx_handlers_stats[0], 0, + sizeof(priv->rx_handlers_stats)); + + return count; +} + +static ssize_t iwl_dbgfs_qos_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + struct iwl_rxon_context *ctx; + int pos = 0, i; + char buf[256 * NUM_IWL_RXON_CTX]; + const size_t bufsz = sizeof(buf); + + for_each_context(priv, ctx) { + pos += scnprintf(buf + pos, bufsz - pos, "context %d:\n", + ctx->ctxid); + for (i = 0; i < AC_NUM; i++) { + pos += scnprintf(buf + pos, bufsz - pos, + "\tcw_min\tcw_max\taifsn\ttxop\n"); + pos += scnprintf(buf + pos, bufsz - pos, + "AC[%d]\t%u\t%u\t%u\t%u\n", i, + ctx->qos_data.def_qos_parm.ac[i].cw_min, + ctx->qos_data.def_qos_parm.ac[i].cw_max, + ctx->qos_data.def_qos_parm.ac[i].aifsn, + ctx->qos_data.def_qos_parm.ac[i].edca_txop); + } + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + } + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_thermal_throttling_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + struct iwl_tt_restriction *restriction; + char buf[100]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, + "Thermal Throttling Mode: %s\n", + tt->advanced_tt ? "Advance" : "Legacy"); + pos += scnprintf(buf + pos, bufsz - pos, + "Thermal Throttling State: %d\n", + tt->state); + if (tt->advanced_tt) { + restriction = tt->restriction + tt->state; + pos += scnprintf(buf + pos, bufsz - pos, + "Tx mode: %d\n", + restriction->tx_stream); + pos += scnprintf(buf + pos, bufsz - pos, + "Rx mode: %d\n", + restriction->rx_stream); + pos += scnprintf(buf + pos, bufsz - pos, + "HT mode: %d\n", + restriction->is_ht); + } + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_disable_ht40_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + int ht40; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &ht40) != 1) + return -EFAULT; + if (!iwl_is_any_associated(priv)) + priv->disable_ht40 = ht40 ? true : false; + else + return -EINVAL; + + return count; +} + +static ssize_t iwl_dbgfs_disable_ht40_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[100]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, + "11n 40MHz Mode: %s\n", + priv->disable_ht40 ? "Disabled" : "Enabled"); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_temperature_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[8]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, "%d\n", priv->temperature); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + + +static ssize_t iwl_dbgfs_sleep_level_override_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + int value; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (sscanf(buf, "%d", &value) != 1) + return -EINVAL; + + /* + * Our users expect 0 to be "CAM", but 0 isn't actually + * valid here. However, let's not confuse them and present + * IWL_POWER_INDEX_1 as "1", not "0". + */ + if (value == 0) + return -EINVAL; + else if (value > 0) + value -= 1; + + if (value != -1 && (value < 0 || value >= IWL_POWER_NUM)) + return -EINVAL; + + if (!iwl_is_ready_rf(priv)) + return -EAGAIN; + + priv->power_data.debug_sleep_level_override = value; + + mutex_lock(&priv->mutex); + iwl_power_update_mode(priv, true); + mutex_unlock(&priv->mutex); + + return count; +} + +static ssize_t iwl_dbgfs_sleep_level_override_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[10]; + int pos, value; + const size_t bufsz = sizeof(buf); + + /* see the write function */ + value = priv->power_data.debug_sleep_level_override; + if (value >= 0) + value += 1; + + pos = scnprintf(buf, bufsz, "%d\n", value); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_current_sleep_command_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[200]; + int pos = 0, i; + const size_t bufsz = sizeof(buf); + struct iwl_powertable_cmd *cmd = &priv->power_data.sleep_cmd; + + pos += scnprintf(buf + pos, bufsz - pos, + "flags: %#.2x\n", le16_to_cpu(cmd->flags)); + pos += scnprintf(buf + pos, bufsz - pos, + "RX/TX timeout: %d/%d usec\n", + le32_to_cpu(cmd->rx_data_timeout), + le32_to_cpu(cmd->tx_data_timeout)); + for (i = 0; i < IWL_POWER_VEC_SIZE; i++) + pos += scnprintf(buf + pos, bufsz - pos, + "sleep_interval[%d]: %d\n", i, + le32_to_cpu(cmd->sleep_interval[i])); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +DEBUGFS_READ_WRITE_FILE_OPS(sram); +DEBUGFS_READ_FILE_OPS(wowlan_sram); +DEBUGFS_READ_FILE_OPS(nvm); +DEBUGFS_READ_FILE_OPS(stations); +DEBUGFS_READ_FILE_OPS(channels); +DEBUGFS_READ_FILE_OPS(status); +DEBUGFS_READ_WRITE_FILE_OPS(rx_handlers); +DEBUGFS_READ_FILE_OPS(qos); +DEBUGFS_READ_FILE_OPS(thermal_throttling); +DEBUGFS_READ_WRITE_FILE_OPS(disable_ht40); +DEBUGFS_READ_FILE_OPS(temperature); +DEBUGFS_READ_WRITE_FILE_OPS(sleep_level_override); +DEBUGFS_READ_FILE_OPS(current_sleep_command); + +#define fmt_value " %-30s %10u\n" +#define fmt_hex " %-30s 0x%02X\n" +#define fmt_table " %-30s %10u %10u %10u %10u\n" +#define fmt_header "%-32s current cumulative delta max\n" + +static int iwl_statistics_flag(struct iwl_priv *priv, char *buf, int bufsz) +{ + int p = 0; + u32 flag; + + lockdep_assert_held(&priv->statistics.lock); + + flag = le32_to_cpu(priv->statistics.flag); + + p += scnprintf(buf + p, bufsz - p, "Statistics Flag(0x%X):\n", flag); + if (flag & UCODE_STATISTICS_CLEAR_MSK) + p += scnprintf(buf + p, bufsz - p, + "\tStatistics have been cleared\n"); + p += scnprintf(buf + p, bufsz - p, "\tOperational Frequency: %s\n", + (flag & UCODE_STATISTICS_FREQUENCY_MSK) + ? "2.4 GHz" : "5.2 GHz"); + p += scnprintf(buf + p, bufsz - p, "\tTGj Narrow Band: %s\n", + (flag & UCODE_STATISTICS_NARROW_BAND_MSK) + ? "enabled" : "disabled"); + + return p; +} + +static ssize_t iwl_dbgfs_ucode_rx_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + int pos = 0; + char *buf; + int bufsz = sizeof(struct statistics_rx_phy) * 40 + + sizeof(struct statistics_rx_non_phy) * 40 + + sizeof(struct statistics_rx_ht_phy) * 40 + 400; + ssize_t ret; + struct statistics_rx_phy *ofdm, *accum_ofdm, *delta_ofdm, *max_ofdm; + struct statistics_rx_phy *cck, *accum_cck, *delta_cck, *max_cck; + struct statistics_rx_non_phy *general, *accum_general; + struct statistics_rx_non_phy *delta_general, *max_general; + struct statistics_rx_ht_phy *ht, *accum_ht, *delta_ht, *max_ht; + + if (!iwl_is_alive(priv)) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* + * the statistic information display here is based on + * the last statistics notification from uCode + * might not reflect the current uCode activity + */ + spin_lock_bh(&priv->statistics.lock); + ofdm = &priv->statistics.rx_ofdm; + cck = &priv->statistics.rx_cck; + general = &priv->statistics.rx_non_phy; + ht = &priv->statistics.rx_ofdm_ht; + accum_ofdm = &priv->accum_stats.rx_ofdm; + accum_cck = &priv->accum_stats.rx_cck; + accum_general = &priv->accum_stats.rx_non_phy; + accum_ht = &priv->accum_stats.rx_ofdm_ht; + delta_ofdm = &priv->delta_stats.rx_ofdm; + delta_cck = &priv->delta_stats.rx_cck; + delta_general = &priv->delta_stats.rx_non_phy; + delta_ht = &priv->delta_stats.rx_ofdm_ht; + max_ofdm = &priv->max_delta_stats.rx_ofdm; + max_cck = &priv->max_delta_stats.rx_cck; + max_general = &priv->max_delta_stats.rx_non_phy; + max_ht = &priv->max_delta_stats.rx_ofdm_ht; + + pos += iwl_statistics_flag(priv, buf, bufsz); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_header, "Statistics_Rx - OFDM:"); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "ina_cnt:", + le32_to_cpu(ofdm->ina_cnt), + accum_ofdm->ina_cnt, + delta_ofdm->ina_cnt, max_ofdm->ina_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "fina_cnt:", + le32_to_cpu(ofdm->fina_cnt), accum_ofdm->fina_cnt, + delta_ofdm->fina_cnt, max_ofdm->fina_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "plcp_err:", + le32_to_cpu(ofdm->plcp_err), accum_ofdm->plcp_err, + delta_ofdm->plcp_err, max_ofdm->plcp_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "crc32_err:", + le32_to_cpu(ofdm->crc32_err), accum_ofdm->crc32_err, + delta_ofdm->crc32_err, max_ofdm->crc32_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "overrun_err:", + le32_to_cpu(ofdm->overrun_err), + accum_ofdm->overrun_err, delta_ofdm->overrun_err, + max_ofdm->overrun_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "early_overrun_err:", + le32_to_cpu(ofdm->early_overrun_err), + accum_ofdm->early_overrun_err, + delta_ofdm->early_overrun_err, + max_ofdm->early_overrun_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "crc32_good:", + le32_to_cpu(ofdm->crc32_good), + accum_ofdm->crc32_good, delta_ofdm->crc32_good, + max_ofdm->crc32_good); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "false_alarm_cnt:", + le32_to_cpu(ofdm->false_alarm_cnt), + accum_ofdm->false_alarm_cnt, + delta_ofdm->false_alarm_cnt, + max_ofdm->false_alarm_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "fina_sync_err_cnt:", + le32_to_cpu(ofdm->fina_sync_err_cnt), + accum_ofdm->fina_sync_err_cnt, + delta_ofdm->fina_sync_err_cnt, + max_ofdm->fina_sync_err_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sfd_timeout:", + le32_to_cpu(ofdm->sfd_timeout), + accum_ofdm->sfd_timeout, delta_ofdm->sfd_timeout, + max_ofdm->sfd_timeout); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "fina_timeout:", + le32_to_cpu(ofdm->fina_timeout), + accum_ofdm->fina_timeout, delta_ofdm->fina_timeout, + max_ofdm->fina_timeout); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "unresponded_rts:", + le32_to_cpu(ofdm->unresponded_rts), + accum_ofdm->unresponded_rts, + delta_ofdm->unresponded_rts, + max_ofdm->unresponded_rts); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "rxe_frame_lmt_ovrun:", + le32_to_cpu(ofdm->rxe_frame_limit_overrun), + accum_ofdm->rxe_frame_limit_overrun, + delta_ofdm->rxe_frame_limit_overrun, + max_ofdm->rxe_frame_limit_overrun); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sent_ack_cnt:", + le32_to_cpu(ofdm->sent_ack_cnt), + accum_ofdm->sent_ack_cnt, delta_ofdm->sent_ack_cnt, + max_ofdm->sent_ack_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sent_cts_cnt:", + le32_to_cpu(ofdm->sent_cts_cnt), + accum_ofdm->sent_cts_cnt, delta_ofdm->sent_cts_cnt, + max_ofdm->sent_cts_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sent_ba_rsp_cnt:", + le32_to_cpu(ofdm->sent_ba_rsp_cnt), + accum_ofdm->sent_ba_rsp_cnt, + delta_ofdm->sent_ba_rsp_cnt, + max_ofdm->sent_ba_rsp_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "dsp_self_kill:", + le32_to_cpu(ofdm->dsp_self_kill), + accum_ofdm->dsp_self_kill, + delta_ofdm->dsp_self_kill, + max_ofdm->dsp_self_kill); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "mh_format_err:", + le32_to_cpu(ofdm->mh_format_err), + accum_ofdm->mh_format_err, + delta_ofdm->mh_format_err, + max_ofdm->mh_format_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "re_acq_main_rssi_sum:", + le32_to_cpu(ofdm->re_acq_main_rssi_sum), + accum_ofdm->re_acq_main_rssi_sum, + delta_ofdm->re_acq_main_rssi_sum, + max_ofdm->re_acq_main_rssi_sum); + + pos += scnprintf(buf + pos, bufsz - pos, + fmt_header, "Statistics_Rx - CCK:"); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "ina_cnt:", + le32_to_cpu(cck->ina_cnt), accum_cck->ina_cnt, + delta_cck->ina_cnt, max_cck->ina_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "fina_cnt:", + le32_to_cpu(cck->fina_cnt), accum_cck->fina_cnt, + delta_cck->fina_cnt, max_cck->fina_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "plcp_err:", + le32_to_cpu(cck->plcp_err), accum_cck->plcp_err, + delta_cck->plcp_err, max_cck->plcp_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "crc32_err:", + le32_to_cpu(cck->crc32_err), accum_cck->crc32_err, + delta_cck->crc32_err, max_cck->crc32_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "overrun_err:", + le32_to_cpu(cck->overrun_err), + accum_cck->overrun_err, delta_cck->overrun_err, + max_cck->overrun_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "early_overrun_err:", + le32_to_cpu(cck->early_overrun_err), + accum_cck->early_overrun_err, + delta_cck->early_overrun_err, + max_cck->early_overrun_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "crc32_good:", + le32_to_cpu(cck->crc32_good), accum_cck->crc32_good, + delta_cck->crc32_good, max_cck->crc32_good); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "false_alarm_cnt:", + le32_to_cpu(cck->false_alarm_cnt), + accum_cck->false_alarm_cnt, + delta_cck->false_alarm_cnt, max_cck->false_alarm_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "fina_sync_err_cnt:", + le32_to_cpu(cck->fina_sync_err_cnt), + accum_cck->fina_sync_err_cnt, + delta_cck->fina_sync_err_cnt, + max_cck->fina_sync_err_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sfd_timeout:", + le32_to_cpu(cck->sfd_timeout), + accum_cck->sfd_timeout, delta_cck->sfd_timeout, + max_cck->sfd_timeout); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "fina_timeout:", + le32_to_cpu(cck->fina_timeout), + accum_cck->fina_timeout, delta_cck->fina_timeout, + max_cck->fina_timeout); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "unresponded_rts:", + le32_to_cpu(cck->unresponded_rts), + accum_cck->unresponded_rts, delta_cck->unresponded_rts, + max_cck->unresponded_rts); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "rxe_frame_lmt_ovrun:", + le32_to_cpu(cck->rxe_frame_limit_overrun), + accum_cck->rxe_frame_limit_overrun, + delta_cck->rxe_frame_limit_overrun, + max_cck->rxe_frame_limit_overrun); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sent_ack_cnt:", + le32_to_cpu(cck->sent_ack_cnt), + accum_cck->sent_ack_cnt, delta_cck->sent_ack_cnt, + max_cck->sent_ack_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sent_cts_cnt:", + le32_to_cpu(cck->sent_cts_cnt), + accum_cck->sent_cts_cnt, delta_cck->sent_cts_cnt, + max_cck->sent_cts_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sent_ba_rsp_cnt:", + le32_to_cpu(cck->sent_ba_rsp_cnt), + accum_cck->sent_ba_rsp_cnt, + delta_cck->sent_ba_rsp_cnt, + max_cck->sent_ba_rsp_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "dsp_self_kill:", + le32_to_cpu(cck->dsp_self_kill), + accum_cck->dsp_self_kill, delta_cck->dsp_self_kill, + max_cck->dsp_self_kill); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "mh_format_err:", + le32_to_cpu(cck->mh_format_err), + accum_cck->mh_format_err, delta_cck->mh_format_err, + max_cck->mh_format_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "re_acq_main_rssi_sum:", + le32_to_cpu(cck->re_acq_main_rssi_sum), + accum_cck->re_acq_main_rssi_sum, + delta_cck->re_acq_main_rssi_sum, + max_cck->re_acq_main_rssi_sum); + + pos += scnprintf(buf + pos, bufsz - pos, + fmt_header, "Statistics_Rx - GENERAL:"); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "bogus_cts:", + le32_to_cpu(general->bogus_cts), + accum_general->bogus_cts, delta_general->bogus_cts, + max_general->bogus_cts); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "bogus_ack:", + le32_to_cpu(general->bogus_ack), + accum_general->bogus_ack, delta_general->bogus_ack, + max_general->bogus_ack); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "non_bssid_frames:", + le32_to_cpu(general->non_bssid_frames), + accum_general->non_bssid_frames, + delta_general->non_bssid_frames, + max_general->non_bssid_frames); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "filtered_frames:", + le32_to_cpu(general->filtered_frames), + accum_general->filtered_frames, + delta_general->filtered_frames, + max_general->filtered_frames); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "non_channel_beacons:", + le32_to_cpu(general->non_channel_beacons), + accum_general->non_channel_beacons, + delta_general->non_channel_beacons, + max_general->non_channel_beacons); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "channel_beacons:", + le32_to_cpu(general->channel_beacons), + accum_general->channel_beacons, + delta_general->channel_beacons, + max_general->channel_beacons); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "num_missed_bcon:", + le32_to_cpu(general->num_missed_bcon), + accum_general->num_missed_bcon, + delta_general->num_missed_bcon, + max_general->num_missed_bcon); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "adc_rx_saturation_time:", + le32_to_cpu(general->adc_rx_saturation_time), + accum_general->adc_rx_saturation_time, + delta_general->adc_rx_saturation_time, + max_general->adc_rx_saturation_time); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "ina_detect_search_tm:", + le32_to_cpu(general->ina_detection_search_time), + accum_general->ina_detection_search_time, + delta_general->ina_detection_search_time, + max_general->ina_detection_search_time); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_silence_rssi_a:", + le32_to_cpu(general->beacon_silence_rssi_a), + accum_general->beacon_silence_rssi_a, + delta_general->beacon_silence_rssi_a, + max_general->beacon_silence_rssi_a); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_silence_rssi_b:", + le32_to_cpu(general->beacon_silence_rssi_b), + accum_general->beacon_silence_rssi_b, + delta_general->beacon_silence_rssi_b, + max_general->beacon_silence_rssi_b); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_silence_rssi_c:", + le32_to_cpu(general->beacon_silence_rssi_c), + accum_general->beacon_silence_rssi_c, + delta_general->beacon_silence_rssi_c, + max_general->beacon_silence_rssi_c); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "interference_data_flag:", + le32_to_cpu(general->interference_data_flag), + accum_general->interference_data_flag, + delta_general->interference_data_flag, + max_general->interference_data_flag); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "channel_load:", + le32_to_cpu(general->channel_load), + accum_general->channel_load, + delta_general->channel_load, + max_general->channel_load); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "dsp_false_alarms:", + le32_to_cpu(general->dsp_false_alarms), + accum_general->dsp_false_alarms, + delta_general->dsp_false_alarms, + max_general->dsp_false_alarms); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_rssi_a:", + le32_to_cpu(general->beacon_rssi_a), + accum_general->beacon_rssi_a, + delta_general->beacon_rssi_a, + max_general->beacon_rssi_a); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_rssi_b:", + le32_to_cpu(general->beacon_rssi_b), + accum_general->beacon_rssi_b, + delta_general->beacon_rssi_b, + max_general->beacon_rssi_b); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_rssi_c:", + le32_to_cpu(general->beacon_rssi_c), + accum_general->beacon_rssi_c, + delta_general->beacon_rssi_c, + max_general->beacon_rssi_c); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_energy_a:", + le32_to_cpu(general->beacon_energy_a), + accum_general->beacon_energy_a, + delta_general->beacon_energy_a, + max_general->beacon_energy_a); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_energy_b:", + le32_to_cpu(general->beacon_energy_b), + accum_general->beacon_energy_b, + delta_general->beacon_energy_b, + max_general->beacon_energy_b); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_energy_c:", + le32_to_cpu(general->beacon_energy_c), + accum_general->beacon_energy_c, + delta_general->beacon_energy_c, + max_general->beacon_energy_c); + + pos += scnprintf(buf + pos, bufsz - pos, + fmt_header, "Statistics_Rx - OFDM_HT:"); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "plcp_err:", + le32_to_cpu(ht->plcp_err), accum_ht->plcp_err, + delta_ht->plcp_err, max_ht->plcp_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "overrun_err:", + le32_to_cpu(ht->overrun_err), accum_ht->overrun_err, + delta_ht->overrun_err, max_ht->overrun_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "early_overrun_err:", + le32_to_cpu(ht->early_overrun_err), + accum_ht->early_overrun_err, + delta_ht->early_overrun_err, + max_ht->early_overrun_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "crc32_good:", + le32_to_cpu(ht->crc32_good), accum_ht->crc32_good, + delta_ht->crc32_good, max_ht->crc32_good); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "crc32_err:", + le32_to_cpu(ht->crc32_err), accum_ht->crc32_err, + delta_ht->crc32_err, max_ht->crc32_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "mh_format_err:", + le32_to_cpu(ht->mh_format_err), + accum_ht->mh_format_err, + delta_ht->mh_format_err, max_ht->mh_format_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg_crc32_good:", + le32_to_cpu(ht->agg_crc32_good), + accum_ht->agg_crc32_good, + delta_ht->agg_crc32_good, max_ht->agg_crc32_good); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg_mpdu_cnt:", + le32_to_cpu(ht->agg_mpdu_cnt), + accum_ht->agg_mpdu_cnt, + delta_ht->agg_mpdu_cnt, max_ht->agg_mpdu_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg_cnt:", + le32_to_cpu(ht->agg_cnt), accum_ht->agg_cnt, + delta_ht->agg_cnt, max_ht->agg_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "unsupport_mcs:", + le32_to_cpu(ht->unsupport_mcs), + accum_ht->unsupport_mcs, + delta_ht->unsupport_mcs, max_ht->unsupport_mcs); + + spin_unlock_bh(&priv->statistics.lock); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_ucode_tx_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + int pos = 0; + char *buf; + int bufsz = (sizeof(struct statistics_tx) * 48) + 250; + ssize_t ret; + struct statistics_tx *tx, *accum_tx, *delta_tx, *max_tx; + + if (!iwl_is_alive(priv)) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* the statistic information display here is based on + * the last statistics notification from uCode + * might not reflect the current uCode activity + */ + spin_lock_bh(&priv->statistics.lock); + + tx = &priv->statistics.tx; + accum_tx = &priv->accum_stats.tx; + delta_tx = &priv->delta_stats.tx; + max_tx = &priv->max_delta_stats.tx; + + pos += iwl_statistics_flag(priv, buf, bufsz); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_header, "Statistics_Tx:"); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "preamble:", + le32_to_cpu(tx->preamble_cnt), + accum_tx->preamble_cnt, + delta_tx->preamble_cnt, max_tx->preamble_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "rx_detected_cnt:", + le32_to_cpu(tx->rx_detected_cnt), + accum_tx->rx_detected_cnt, + delta_tx->rx_detected_cnt, max_tx->rx_detected_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "bt_prio_defer_cnt:", + le32_to_cpu(tx->bt_prio_defer_cnt), + accum_tx->bt_prio_defer_cnt, + delta_tx->bt_prio_defer_cnt, + max_tx->bt_prio_defer_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "bt_prio_kill_cnt:", + le32_to_cpu(tx->bt_prio_kill_cnt), + accum_tx->bt_prio_kill_cnt, + delta_tx->bt_prio_kill_cnt, + max_tx->bt_prio_kill_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "few_bytes_cnt:", + le32_to_cpu(tx->few_bytes_cnt), + accum_tx->few_bytes_cnt, + delta_tx->few_bytes_cnt, max_tx->few_bytes_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "cts_timeout:", + le32_to_cpu(tx->cts_timeout), accum_tx->cts_timeout, + delta_tx->cts_timeout, max_tx->cts_timeout); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "ack_timeout:", + le32_to_cpu(tx->ack_timeout), + accum_tx->ack_timeout, + delta_tx->ack_timeout, max_tx->ack_timeout); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "expected_ack_cnt:", + le32_to_cpu(tx->expected_ack_cnt), + accum_tx->expected_ack_cnt, + delta_tx->expected_ack_cnt, + max_tx->expected_ack_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "actual_ack_cnt:", + le32_to_cpu(tx->actual_ack_cnt), + accum_tx->actual_ack_cnt, + delta_tx->actual_ack_cnt, + max_tx->actual_ack_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "dump_msdu_cnt:", + le32_to_cpu(tx->dump_msdu_cnt), + accum_tx->dump_msdu_cnt, + delta_tx->dump_msdu_cnt, + max_tx->dump_msdu_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "abort_nxt_frame_mismatch:", + le32_to_cpu(tx->burst_abort_next_frame_mismatch_cnt), + accum_tx->burst_abort_next_frame_mismatch_cnt, + delta_tx->burst_abort_next_frame_mismatch_cnt, + max_tx->burst_abort_next_frame_mismatch_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "abort_missing_nxt_frame:", + le32_to_cpu(tx->burst_abort_missing_next_frame_cnt), + accum_tx->burst_abort_missing_next_frame_cnt, + delta_tx->burst_abort_missing_next_frame_cnt, + max_tx->burst_abort_missing_next_frame_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "cts_timeout_collision:", + le32_to_cpu(tx->cts_timeout_collision), + accum_tx->cts_timeout_collision, + delta_tx->cts_timeout_collision, + max_tx->cts_timeout_collision); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "ack_ba_timeout_collision:", + le32_to_cpu(tx->ack_or_ba_timeout_collision), + accum_tx->ack_or_ba_timeout_collision, + delta_tx->ack_or_ba_timeout_collision, + max_tx->ack_or_ba_timeout_collision); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg ba_timeout:", + le32_to_cpu(tx->agg.ba_timeout), + accum_tx->agg.ba_timeout, + delta_tx->agg.ba_timeout, + max_tx->agg.ba_timeout); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg ba_resched_frames:", + le32_to_cpu(tx->agg.ba_reschedule_frames), + accum_tx->agg.ba_reschedule_frames, + delta_tx->agg.ba_reschedule_frames, + max_tx->agg.ba_reschedule_frames); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg scd_query_agg_frame:", + le32_to_cpu(tx->agg.scd_query_agg_frame_cnt), + accum_tx->agg.scd_query_agg_frame_cnt, + delta_tx->agg.scd_query_agg_frame_cnt, + max_tx->agg.scd_query_agg_frame_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg scd_query_no_agg:", + le32_to_cpu(tx->agg.scd_query_no_agg), + accum_tx->agg.scd_query_no_agg, + delta_tx->agg.scd_query_no_agg, + max_tx->agg.scd_query_no_agg); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg scd_query_agg:", + le32_to_cpu(tx->agg.scd_query_agg), + accum_tx->agg.scd_query_agg, + delta_tx->agg.scd_query_agg, + max_tx->agg.scd_query_agg); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg scd_query_mismatch:", + le32_to_cpu(tx->agg.scd_query_mismatch), + accum_tx->agg.scd_query_mismatch, + delta_tx->agg.scd_query_mismatch, + max_tx->agg.scd_query_mismatch); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg frame_not_ready:", + le32_to_cpu(tx->agg.frame_not_ready), + accum_tx->agg.frame_not_ready, + delta_tx->agg.frame_not_ready, + max_tx->agg.frame_not_ready); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg underrun:", + le32_to_cpu(tx->agg.underrun), + accum_tx->agg.underrun, + delta_tx->agg.underrun, max_tx->agg.underrun); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg bt_prio_kill:", + le32_to_cpu(tx->agg.bt_prio_kill), + accum_tx->agg.bt_prio_kill, + delta_tx->agg.bt_prio_kill, + max_tx->agg.bt_prio_kill); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg rx_ba_rsp_cnt:", + le32_to_cpu(tx->agg.rx_ba_rsp_cnt), + accum_tx->agg.rx_ba_rsp_cnt, + delta_tx->agg.rx_ba_rsp_cnt, + max_tx->agg.rx_ba_rsp_cnt); + + if (tx->tx_power.ant_a || tx->tx_power.ant_b || tx->tx_power.ant_c) { + pos += scnprintf(buf + pos, bufsz - pos, + "tx power: (1/2 dB step)\n"); + if ((priv->nvm_data->valid_tx_ant & ANT_A) && + tx->tx_power.ant_a) + pos += scnprintf(buf + pos, bufsz - pos, + fmt_hex, "antenna A:", + tx->tx_power.ant_a); + if ((priv->nvm_data->valid_tx_ant & ANT_B) && + tx->tx_power.ant_b) + pos += scnprintf(buf + pos, bufsz - pos, + fmt_hex, "antenna B:", + tx->tx_power.ant_b); + if ((priv->nvm_data->valid_tx_ant & ANT_C) && + tx->tx_power.ant_c) + pos += scnprintf(buf + pos, bufsz - pos, + fmt_hex, "antenna C:", + tx->tx_power.ant_c); + } + + spin_unlock_bh(&priv->statistics.lock); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_ucode_general_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + int pos = 0; + char *buf; + int bufsz = sizeof(struct statistics_general) * 10 + 300; + ssize_t ret; + struct statistics_general_common *general, *accum_general; + struct statistics_general_common *delta_general, *max_general; + struct statistics_dbg *dbg, *accum_dbg, *delta_dbg, *max_dbg; + struct statistics_div *div, *accum_div, *delta_div, *max_div; + + if (!iwl_is_alive(priv)) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* the statistic information display here is based on + * the last statistics notification from uCode + * might not reflect the current uCode activity + */ + + spin_lock_bh(&priv->statistics.lock); + + general = &priv->statistics.common; + dbg = &priv->statistics.common.dbg; + div = &priv->statistics.common.div; + accum_general = &priv->accum_stats.common; + accum_dbg = &priv->accum_stats.common.dbg; + accum_div = &priv->accum_stats.common.div; + delta_general = &priv->delta_stats.common; + max_general = &priv->max_delta_stats.common; + delta_dbg = &priv->delta_stats.common.dbg; + max_dbg = &priv->max_delta_stats.common.dbg; + delta_div = &priv->delta_stats.common.div; + max_div = &priv->max_delta_stats.common.div; + + pos += iwl_statistics_flag(priv, buf, bufsz); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_header, "Statistics_General:"); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_value, "temperature:", + le32_to_cpu(general->temperature)); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_value, "temperature_m:", + le32_to_cpu(general->temperature_m)); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_value, "ttl_timestamp:", + le32_to_cpu(general->ttl_timestamp)); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "burst_check:", + le32_to_cpu(dbg->burst_check), + accum_dbg->burst_check, + delta_dbg->burst_check, max_dbg->burst_check); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "burst_count:", + le32_to_cpu(dbg->burst_count), + accum_dbg->burst_count, + delta_dbg->burst_count, max_dbg->burst_count); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "wait_for_silence_timeout_count:", + le32_to_cpu(dbg->wait_for_silence_timeout_cnt), + accum_dbg->wait_for_silence_timeout_cnt, + delta_dbg->wait_for_silence_timeout_cnt, + max_dbg->wait_for_silence_timeout_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sleep_time:", + le32_to_cpu(general->sleep_time), + accum_general->sleep_time, + delta_general->sleep_time, max_general->sleep_time); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "slots_out:", + le32_to_cpu(general->slots_out), + accum_general->slots_out, + delta_general->slots_out, max_general->slots_out); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "slots_idle:", + le32_to_cpu(general->slots_idle), + accum_general->slots_idle, + delta_general->slots_idle, max_general->slots_idle); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "tx_on_a:", + le32_to_cpu(div->tx_on_a), accum_div->tx_on_a, + delta_div->tx_on_a, max_div->tx_on_a); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "tx_on_b:", + le32_to_cpu(div->tx_on_b), accum_div->tx_on_b, + delta_div->tx_on_b, max_div->tx_on_b); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "exec_time:", + le32_to_cpu(div->exec_time), accum_div->exec_time, + delta_div->exec_time, max_div->exec_time); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "probe_time:", + le32_to_cpu(div->probe_time), accum_div->probe_time, + delta_div->probe_time, max_div->probe_time); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "rx_enable_counter:", + le32_to_cpu(general->rx_enable_counter), + accum_general->rx_enable_counter, + delta_general->rx_enable_counter, + max_general->rx_enable_counter); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "num_of_sos_states:", + le32_to_cpu(general->num_of_sos_states), + accum_general->num_of_sos_states, + delta_general->num_of_sos_states, + max_general->num_of_sos_states); + + spin_unlock_bh(&priv->statistics.lock); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_ucode_bt_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = (struct iwl_priv *)file->private_data; + int pos = 0; + char *buf; + int bufsz = (sizeof(struct statistics_bt_activity) * 24) + 200; + ssize_t ret; + struct statistics_bt_activity *bt, *accum_bt; + + if (!iwl_is_alive(priv)) + return -EAGAIN; + + if (!priv->bt_enable_flag) + return -EINVAL; + + /* make request to uCode to retrieve statistics information */ + mutex_lock(&priv->mutex); + ret = iwl_send_statistics_request(priv, 0, false); + mutex_unlock(&priv->mutex); + + if (ret) + return -EAGAIN; + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* + * the statistic information display here is based on + * the last statistics notification from uCode + * might not reflect the current uCode activity + */ + + spin_lock_bh(&priv->statistics.lock); + + bt = &priv->statistics.bt_activity; + accum_bt = &priv->accum_stats.bt_activity; + + pos += iwl_statistics_flag(priv, buf, bufsz); + pos += scnprintf(buf + pos, bufsz - pos, "Statistics_BT:\n"); + pos += scnprintf(buf + pos, bufsz - pos, + "\t\t\tcurrent\t\t\taccumulative\n"); + pos += scnprintf(buf + pos, bufsz - pos, + "hi_priority_tx_req_cnt:\t\t%u\t\t\t%u\n", + le32_to_cpu(bt->hi_priority_tx_req_cnt), + accum_bt->hi_priority_tx_req_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + "hi_priority_tx_denied_cnt:\t%u\t\t\t%u\n", + le32_to_cpu(bt->hi_priority_tx_denied_cnt), + accum_bt->hi_priority_tx_denied_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + "lo_priority_tx_req_cnt:\t\t%u\t\t\t%u\n", + le32_to_cpu(bt->lo_priority_tx_req_cnt), + accum_bt->lo_priority_tx_req_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + "lo_priority_tx_denied_cnt:\t%u\t\t\t%u\n", + le32_to_cpu(bt->lo_priority_tx_denied_cnt), + accum_bt->lo_priority_tx_denied_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + "hi_priority_rx_req_cnt:\t\t%u\t\t\t%u\n", + le32_to_cpu(bt->hi_priority_rx_req_cnt), + accum_bt->hi_priority_rx_req_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + "hi_priority_rx_denied_cnt:\t%u\t\t\t%u\n", + le32_to_cpu(bt->hi_priority_rx_denied_cnt), + accum_bt->hi_priority_rx_denied_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + "lo_priority_rx_req_cnt:\t\t%u\t\t\t%u\n", + le32_to_cpu(bt->lo_priority_rx_req_cnt), + accum_bt->lo_priority_rx_req_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + "lo_priority_rx_denied_cnt:\t%u\t\t\t%u\n", + le32_to_cpu(bt->lo_priority_rx_denied_cnt), + accum_bt->lo_priority_rx_denied_cnt); + + pos += scnprintf(buf + pos, bufsz - pos, + "(rx)num_bt_kills:\t\t%u\t\t\t%u\n", + le32_to_cpu(priv->statistics.num_bt_kills), + priv->statistics.accum_num_bt_kills); + + spin_unlock_bh(&priv->statistics.lock); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_reply_tx_error_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = (struct iwl_priv *)file->private_data; + int pos = 0; + char *buf; + int bufsz = (sizeof(struct reply_tx_error_statistics) * 24) + + (sizeof(struct reply_agg_tx_error_statistics) * 24) + 200; + ssize_t ret; + + if (!iwl_is_alive(priv)) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pos += scnprintf(buf + pos, bufsz - pos, "Statistics_TX_Error:\n"); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_POSTPONE_DELAY), + priv->reply_tx_stats.pp_delay); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_POSTPONE_FEW_BYTES), + priv->reply_tx_stats.pp_few_bytes); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_POSTPONE_BT_PRIO), + priv->reply_tx_stats.pp_bt_prio); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_POSTPONE_QUIET_PERIOD), + priv->reply_tx_stats.pp_quiet_period); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_POSTPONE_CALC_TTAK), + priv->reply_tx_stats.pp_calc_ttak); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t%u\n", + iwl_get_tx_fail_reason( + TX_STATUS_FAIL_INTERNAL_CROSSED_RETRY), + priv->reply_tx_stats.int_crossed_retry); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_SHORT_LIMIT), + priv->reply_tx_stats.short_limit); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_LONG_LIMIT), + priv->reply_tx_stats.long_limit); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_FIFO_UNDERRUN), + priv->reply_tx_stats.fifo_underrun); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_DRAIN_FLOW), + priv->reply_tx_stats.drain_flow); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_RFKILL_FLUSH), + priv->reply_tx_stats.rfkill_flush); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_LIFE_EXPIRE), + priv->reply_tx_stats.life_expire); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_DEST_PS), + priv->reply_tx_stats.dest_ps); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_HOST_ABORTED), + priv->reply_tx_stats.host_abort); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_BT_RETRY), + priv->reply_tx_stats.pp_delay); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_STA_INVALID), + priv->reply_tx_stats.sta_invalid); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_FRAG_DROPPED), + priv->reply_tx_stats.frag_drop); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_TID_DISABLE), + priv->reply_tx_stats.tid_disable); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_FIFO_FLUSHED), + priv->reply_tx_stats.fifo_flush); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t%u\n", + iwl_get_tx_fail_reason( + TX_STATUS_FAIL_INSUFFICIENT_CF_POLL), + priv->reply_tx_stats.insuff_cf_poll); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_PASSIVE_NO_RX), + priv->reply_tx_stats.fail_hw_drop); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t%u\n", + iwl_get_tx_fail_reason( + TX_STATUS_FAIL_NO_BEACON_ON_RADAR), + priv->reply_tx_stats.sta_color_mismatch); + pos += scnprintf(buf + pos, bufsz - pos, "UNKNOWN:\t\t\t%u\n", + priv->reply_tx_stats.unknown); + + pos += scnprintf(buf + pos, bufsz - pos, + "\nStatistics_Agg_TX_Error:\n"); + + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_agg_tx_fail_reason(AGG_TX_STATE_UNDERRUN_MSK), + priv->reply_agg_tx_stats.underrun); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_agg_tx_fail_reason(AGG_TX_STATE_BT_PRIO_MSK), + priv->reply_agg_tx_stats.bt_prio); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_agg_tx_fail_reason(AGG_TX_STATE_FEW_BYTES_MSK), + priv->reply_agg_tx_stats.few_bytes); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_agg_tx_fail_reason(AGG_TX_STATE_ABORT_MSK), + priv->reply_agg_tx_stats.abort); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t%u\n", + iwl_get_agg_tx_fail_reason( + AGG_TX_STATE_LAST_SENT_TTL_MSK), + priv->reply_agg_tx_stats.last_sent_ttl); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t%u\n", + iwl_get_agg_tx_fail_reason( + AGG_TX_STATE_LAST_SENT_TRY_CNT_MSK), + priv->reply_agg_tx_stats.last_sent_try); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t%u\n", + iwl_get_agg_tx_fail_reason( + AGG_TX_STATE_LAST_SENT_BT_KILL_MSK), + priv->reply_agg_tx_stats.last_sent_bt_kill); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_agg_tx_fail_reason(AGG_TX_STATE_SCD_QUERY_MSK), + priv->reply_agg_tx_stats.scd_query); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t%u\n", + iwl_get_agg_tx_fail_reason( + AGG_TX_STATE_TEST_BAD_CRC32_MSK), + priv->reply_agg_tx_stats.bad_crc32); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_agg_tx_fail_reason(AGG_TX_STATE_RESPONSE_MSK), + priv->reply_agg_tx_stats.response); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_agg_tx_fail_reason(AGG_TX_STATE_DUMP_TX_MSK), + priv->reply_agg_tx_stats.dump_tx); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_agg_tx_fail_reason(AGG_TX_STATE_DELAY_TX_MSK), + priv->reply_agg_tx_stats.delay_tx); + pos += scnprintf(buf + pos, bufsz - pos, "UNKNOWN:\t\t\t%u\n", + priv->reply_agg_tx_stats.unknown); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_sensitivity_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + int pos = 0; + int cnt = 0; + char *buf; + int bufsz = sizeof(struct iwl_sensitivity_data) * 4 + 100; + ssize_t ret; + struct iwl_sensitivity_data *data; + + data = &priv->sensitivity_data; + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pos += scnprintf(buf + pos, bufsz - pos, "auto_corr_ofdm:\t\t\t %u\n", + data->auto_corr_ofdm); + pos += scnprintf(buf + pos, bufsz - pos, + "auto_corr_ofdm_mrc:\t\t %u\n", + data->auto_corr_ofdm_mrc); + pos += scnprintf(buf + pos, bufsz - pos, "auto_corr_ofdm_x1:\t\t %u\n", + data->auto_corr_ofdm_x1); + pos += scnprintf(buf + pos, bufsz - pos, + "auto_corr_ofdm_mrc_x1:\t\t %u\n", + data->auto_corr_ofdm_mrc_x1); + pos += scnprintf(buf + pos, bufsz - pos, "auto_corr_cck:\t\t\t %u\n", + data->auto_corr_cck); + pos += scnprintf(buf + pos, bufsz - pos, "auto_corr_cck_mrc:\t\t %u\n", + data->auto_corr_cck_mrc); + pos += scnprintf(buf + pos, bufsz - pos, + "last_bad_plcp_cnt_ofdm:\t\t %u\n", + data->last_bad_plcp_cnt_ofdm); + pos += scnprintf(buf + pos, bufsz - pos, "last_fa_cnt_ofdm:\t\t %u\n", + data->last_fa_cnt_ofdm); + pos += scnprintf(buf + pos, bufsz - pos, + "last_bad_plcp_cnt_cck:\t\t %u\n", + data->last_bad_plcp_cnt_cck); + pos += scnprintf(buf + pos, bufsz - pos, "last_fa_cnt_cck:\t\t %u\n", + data->last_fa_cnt_cck); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_curr_state:\t\t\t %u\n", + data->nrg_curr_state); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_prev_state:\t\t\t %u\n", + data->nrg_prev_state); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_value:\t\t\t"); + for (cnt = 0; cnt < 10; cnt++) { + pos += scnprintf(buf + pos, bufsz - pos, " %u", + data->nrg_value[cnt]); + } + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_silence_rssi:\t\t"); + for (cnt = 0; cnt < NRG_NUM_PREV_STAT_L; cnt++) { + pos += scnprintf(buf + pos, bufsz - pos, " %u", + data->nrg_silence_rssi[cnt]); + } + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_silence_ref:\t\t %u\n", + data->nrg_silence_ref); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_energy_idx:\t\t\t %u\n", + data->nrg_energy_idx); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_silence_idx:\t\t %u\n", + data->nrg_silence_idx); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_th_cck:\t\t\t %u\n", + data->nrg_th_cck); + pos += scnprintf(buf + pos, bufsz - pos, + "nrg_auto_corr_silence_diff:\t %u\n", + data->nrg_auto_corr_silence_diff); + pos += scnprintf(buf + pos, bufsz - pos, "num_in_cck_no_fa:\t\t %u\n", + data->num_in_cck_no_fa); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_th_ofdm:\t\t\t %u\n", + data->nrg_th_ofdm); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + + +static ssize_t iwl_dbgfs_chain_noise_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + int pos = 0; + int cnt = 0; + char *buf; + int bufsz = sizeof(struct iwl_chain_noise_data) * 4 + 100; + ssize_t ret; + struct iwl_chain_noise_data *data; + + data = &priv->chain_noise_data; + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pos += scnprintf(buf + pos, bufsz - pos, "active_chains:\t\t\t %u\n", + data->active_chains); + pos += scnprintf(buf + pos, bufsz - pos, "chain_noise_a:\t\t\t %u\n", + data->chain_noise_a); + pos += scnprintf(buf + pos, bufsz - pos, "chain_noise_b:\t\t\t %u\n", + data->chain_noise_b); + pos += scnprintf(buf + pos, bufsz - pos, "chain_noise_c:\t\t\t %u\n", + data->chain_noise_c); + pos += scnprintf(buf + pos, bufsz - pos, "chain_signal_a:\t\t\t %u\n", + data->chain_signal_a); + pos += scnprintf(buf + pos, bufsz - pos, "chain_signal_b:\t\t\t %u\n", + data->chain_signal_b); + pos += scnprintf(buf + pos, bufsz - pos, "chain_signal_c:\t\t\t %u\n", + data->chain_signal_c); + pos += scnprintf(buf + pos, bufsz - pos, "beacon_count:\t\t\t %u\n", + data->beacon_count); + + pos += scnprintf(buf + pos, bufsz - pos, "disconn_array:\t\t\t"); + for (cnt = 0; cnt < NUM_RX_CHAINS; cnt++) { + pos += scnprintf(buf + pos, bufsz - pos, " %u", + data->disconn_array[cnt]); + } + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + pos += scnprintf(buf + pos, bufsz - pos, "delta_gain_code:\t\t"); + for (cnt = 0; cnt < NUM_RX_CHAINS; cnt++) { + pos += scnprintf(buf + pos, bufsz - pos, " %u", + data->delta_gain_code[cnt]); + } + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + pos += scnprintf(buf + pos, bufsz - pos, "radio_write:\t\t\t %u\n", + data->radio_write); + pos += scnprintf(buf + pos, bufsz - pos, "state:\t\t\t\t %u\n", + data->state); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_power_save_status_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[60]; + int pos = 0; + const size_t bufsz = sizeof(buf); + u32 pwrsave_status; + + pwrsave_status = iwl_read32(priv->trans, CSR_GP_CNTRL) & + CSR_GP_REG_POWER_SAVE_STATUS_MSK; + + pos += scnprintf(buf + pos, bufsz - pos, "Power Save Status: "); + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", + (pwrsave_status == CSR_GP_REG_NO_POWER_SAVE) ? "none" : + (pwrsave_status == CSR_GP_REG_MAC_POWER_SAVE) ? "MAC" : + (pwrsave_status == CSR_GP_REG_PHY_POWER_SAVE) ? "PHY" : + "error"); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_clear_ucode_statistics_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + int clear; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &clear) != 1) + return -EFAULT; + + /* make request to uCode to retrieve statistics information */ + mutex_lock(&priv->mutex); + iwl_send_statistics_request(priv, 0, true); + mutex_unlock(&priv->mutex); + + return count; +} + +static ssize_t iwl_dbgfs_ucode_tracing_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + int pos = 0; + char buf[128]; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, "ucode trace timer is %s\n", + priv->event_log.ucode_trace ? "On" : "Off"); + pos += scnprintf(buf + pos, bufsz - pos, "non_wraps_count:\t\t %u\n", + priv->event_log.non_wraps_count); + pos += scnprintf(buf + pos, bufsz - pos, "wraps_once_count:\t\t %u\n", + priv->event_log.wraps_once_count); + pos += scnprintf(buf + pos, bufsz - pos, "wraps_more_count:\t\t %u\n", + priv->event_log.wraps_more_count); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_ucode_tracing_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + int trace; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &trace) != 1) + return -EFAULT; + + if (trace) { + priv->event_log.ucode_trace = true; + if (iwl_is_alive(priv)) { + /* start collecting data now */ + mod_timer(&priv->ucode_trace, jiffies); + } + } else { + priv->event_log.ucode_trace = false; + del_timer_sync(&priv->ucode_trace); + } + + return count; +} + +static ssize_t iwl_dbgfs_rxon_flags_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + int len = 0; + char buf[20]; + + len = sprintf(buf, "0x%04X\n", + le32_to_cpu(priv->contexts[IWL_RXON_CTX_BSS].active.flags)); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t iwl_dbgfs_rxon_filter_flags_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + int len = 0; + char buf[20]; + + len = sprintf(buf, "0x%04X\n", + le32_to_cpu(priv->contexts[IWL_RXON_CTX_BSS].active.filter_flags)); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t iwl_dbgfs_missed_beacon_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + int pos = 0; + char buf[12]; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, "%d\n", + priv->missed_beacon_threshold); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_missed_beacon_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + int missed; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &missed) != 1) + return -EINVAL; + + if (missed < IWL_MISSED_BEACON_THRESHOLD_MIN || + missed > IWL_MISSED_BEACON_THRESHOLD_MAX) + priv->missed_beacon_threshold = + IWL_MISSED_BEACON_THRESHOLD_DEF; + else + priv->missed_beacon_threshold = missed; + + return count; +} + +static ssize_t iwl_dbgfs_plcp_delta_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + int pos = 0; + char buf[12]; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, "%u\n", + priv->plcp_delta_threshold); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_plcp_delta_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + int plcp; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &plcp) != 1) + return -EINVAL; + if ((plcp < IWL_MAX_PLCP_ERR_THRESHOLD_MIN) || + (plcp > IWL_MAX_PLCP_ERR_THRESHOLD_MAX)) + priv->plcp_delta_threshold = + IWL_MAX_PLCP_ERR_THRESHOLD_DISABLE; + else + priv->plcp_delta_threshold = plcp; + return count; +} + +static ssize_t iwl_dbgfs_rf_reset_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + int pos = 0; + char buf[300]; + const size_t bufsz = sizeof(buf); + struct iwl_rf_reset *rf_reset = &priv->rf_reset; + + pos += scnprintf(buf + pos, bufsz - pos, + "RF reset statistics\n"); + pos += scnprintf(buf + pos, bufsz - pos, + "\tnumber of reset request: %d\n", + rf_reset->reset_request_count); + pos += scnprintf(buf + pos, bufsz - pos, + "\tnumber of reset request success: %d\n", + rf_reset->reset_success_count); + pos += scnprintf(buf + pos, bufsz - pos, + "\tnumber of reset request reject: %d\n", + rf_reset->reset_reject_count); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_rf_reset_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + int ret; + + ret = iwl_force_rf_reset(priv, true); + return ret ? ret : count; +} + +static ssize_t iwl_dbgfs_txfifo_flush_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + int flush; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &flush) != 1) + return -EINVAL; + + if (iwl_is_rfkill(priv)) + return -EFAULT; + + iwlagn_dev_txfifo_flush(priv); + + return count; +} + +static ssize_t iwl_dbgfs_bt_traffic_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = (struct iwl_priv *)file->private_data; + int pos = 0; + char buf[200]; + const size_t bufsz = sizeof(buf); + + if (!priv->bt_enable_flag) { + pos += scnprintf(buf + pos, bufsz - pos, "BT coex disabled\n"); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); + } + pos += scnprintf(buf + pos, bufsz - pos, "BT enable flag: 0x%x\n", + priv->bt_enable_flag); + pos += scnprintf(buf + pos, bufsz - pos, "BT in %s mode\n", + priv->bt_full_concurrent ? "full concurrency" : "3-wire"); + pos += scnprintf(buf + pos, bufsz - pos, "BT status: %s, " + "last traffic notif: %d\n", + priv->bt_status ? "On" : "Off", priv->last_bt_traffic_load); + pos += scnprintf(buf + pos, bufsz - pos, "ch_announcement: %d, " + "kill_ack_mask: %x, kill_cts_mask: %x\n", + priv->bt_ch_announce, priv->kill_ack_mask, + priv->kill_cts_mask); + + pos += scnprintf(buf + pos, bufsz - pos, "bluetooth traffic load: "); + switch (priv->bt_traffic_load) { + case IWL_BT_COEX_TRAFFIC_LOAD_CONTINUOUS: + pos += scnprintf(buf + pos, bufsz - pos, "Continuous\n"); + break; + case IWL_BT_COEX_TRAFFIC_LOAD_HIGH: + pos += scnprintf(buf + pos, bufsz - pos, "High\n"); + break; + case IWL_BT_COEX_TRAFFIC_LOAD_LOW: + pos += scnprintf(buf + pos, bufsz - pos, "Low\n"); + break; + case IWL_BT_COEX_TRAFFIC_LOAD_NONE: + default: + pos += scnprintf(buf + pos, bufsz - pos, "None\n"); + break; + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_protection_mode_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = (struct iwl_priv *)file->private_data; + + int pos = 0; + char buf[40]; + const size_t bufsz = sizeof(buf); + + if (priv->cfg->ht_params) + pos += scnprintf(buf + pos, bufsz - pos, + "use %s for aggregation\n", + (priv->hw_params.use_rts_for_aggregation) ? + "rts/cts" : "cts-to-self"); + else + pos += scnprintf(buf + pos, bufsz - pos, "N/A"); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_protection_mode_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + int rts; + + if (!priv->cfg->ht_params) + return -EINVAL; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &rts) != 1) + return -EINVAL; + if (rts) + priv->hw_params.use_rts_for_aggregation = true; + else + priv->hw_params.use_rts_for_aggregation = false; + return count; +} + +static int iwl_cmd_echo_test(struct iwl_priv *priv) +{ + int ret; + struct iwl_host_cmd cmd = { + .id = REPLY_ECHO, + .len = { 0 }, + }; + + ret = iwl_dvm_send_cmd(priv, &cmd); + if (ret) + IWL_ERR(priv, "echo testing fail: 0X%x\n", ret); + else + IWL_DEBUG_INFO(priv, "echo testing pass\n"); + return ret; +} + +static ssize_t iwl_dbgfs_echo_test_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + iwl_cmd_echo_test(priv); + return count; +} + +#ifdef CONFIG_IWLWIFI_DEBUG +static ssize_t iwl_dbgfs_log_event_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char *buf = NULL; + ssize_t ret; + + ret = iwl_dump_nic_event_log(priv, true, &buf); + if (ret > 0) + ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_log_event_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + u32 event_log_flag; + char buf[8]; + int buf_size; + + /* check that the interface is up */ + if (!iwl_is_ready(priv)) + return -EAGAIN; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%u", &event_log_flag) != 1) + return -EFAULT; + if (event_log_flag == 1) + iwl_dump_nic_event_log(priv, true, NULL); + + return count; +} +#endif + +static ssize_t iwl_dbgfs_calib_disabled_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[120]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, + "Sensitivity calibrations %s\n", + (priv->calib_disabled & + IWL_SENSITIVITY_CALIB_DISABLED) ? + "DISABLED" : "ENABLED"); + pos += scnprintf(buf + pos, bufsz - pos, + "Chain noise calibrations %s\n", + (priv->calib_disabled & + IWL_CHAIN_NOISE_CALIB_DISABLED) ? + "DISABLED" : "ENABLED"); + pos += scnprintf(buf + pos, bufsz - pos, + "Tx power calibrations %s\n", + (priv->calib_disabled & + IWL_TX_POWER_CALIB_DISABLED) ? + "DISABLED" : "ENABLED"); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_calib_disabled_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[8]; + u32 calib_disabled; + int buf_size; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%x", &calib_disabled) != 1) + return -EFAULT; + + priv->calib_disabled = calib_disabled; + + return count; +} + +static ssize_t iwl_dbgfs_fw_restart_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + bool fw_restart = iwlwifi_mod_params.fw_restart; + int __maybe_unused ret; + + iwlwifi_mod_params.fw_restart = true; + + mutex_lock(&priv->mutex); + + /* take the return value to make compiler happy - it will fail anyway */ + ret = iwl_dvm_send_cmd_pdu(priv, REPLY_ERROR, 0, 0, NULL); + + mutex_unlock(&priv->mutex); + + iwlwifi_mod_params.fw_restart = fw_restart; + + return count; +} + +DEBUGFS_READ_FILE_OPS(ucode_rx_stats); +DEBUGFS_READ_FILE_OPS(ucode_tx_stats); +DEBUGFS_READ_FILE_OPS(ucode_general_stats); +DEBUGFS_READ_FILE_OPS(sensitivity); +DEBUGFS_READ_FILE_OPS(chain_noise); +DEBUGFS_READ_FILE_OPS(power_save_status); +DEBUGFS_WRITE_FILE_OPS(clear_ucode_statistics); +DEBUGFS_READ_WRITE_FILE_OPS(ucode_tracing); +DEBUGFS_READ_WRITE_FILE_OPS(missed_beacon); +DEBUGFS_READ_WRITE_FILE_OPS(plcp_delta); +DEBUGFS_READ_WRITE_FILE_OPS(rf_reset); +DEBUGFS_READ_FILE_OPS(rxon_flags); +DEBUGFS_READ_FILE_OPS(rxon_filter_flags); +DEBUGFS_WRITE_FILE_OPS(txfifo_flush); +DEBUGFS_READ_FILE_OPS(ucode_bt_stats); +DEBUGFS_READ_FILE_OPS(bt_traffic); +DEBUGFS_READ_WRITE_FILE_OPS(protection_mode); +DEBUGFS_READ_FILE_OPS(reply_tx_error); +DEBUGFS_WRITE_FILE_OPS(echo_test); +DEBUGFS_WRITE_FILE_OPS(fw_restart); +#ifdef CONFIG_IWLWIFI_DEBUG +DEBUGFS_READ_WRITE_FILE_OPS(log_event); +#endif +DEBUGFS_READ_WRITE_FILE_OPS(calib_disabled); + +/* + * Create the debugfs files and directories + * + */ +void iwl_dbgfs_register(struct iwl_priv *priv, struct dentry *dbgfs_dir) +{ + struct dentry *dir_data, *dir_rf, *dir_debug; + + priv->debugfs_dir = dbgfs_dir; + + dir_data = debugfs_create_dir("data", dbgfs_dir); + dir_rf = debugfs_create_dir("rf", dbgfs_dir); + dir_debug = debugfs_create_dir("debug", dbgfs_dir); + + DEBUGFS_ADD_FILE(nvm, dir_data, 0400); + DEBUGFS_ADD_FILE(sram, dir_data, 0600); + DEBUGFS_ADD_FILE(wowlan_sram, dir_data, 0400); + DEBUGFS_ADD_FILE(stations, dir_data, 0400); + DEBUGFS_ADD_FILE(channels, dir_data, 0400); + DEBUGFS_ADD_FILE(status, dir_data, 0400); + DEBUGFS_ADD_FILE(rx_handlers, dir_data, 0600); + DEBUGFS_ADD_FILE(qos, dir_data, 0400); + DEBUGFS_ADD_FILE(sleep_level_override, dir_data, 0600); + DEBUGFS_ADD_FILE(current_sleep_command, dir_data, 0400); + DEBUGFS_ADD_FILE(thermal_throttling, dir_data, 0400); + DEBUGFS_ADD_FILE(disable_ht40, dir_data, 0600); + DEBUGFS_ADD_FILE(temperature, dir_data, 0400); + + DEBUGFS_ADD_FILE(power_save_status, dir_debug, 0400); + DEBUGFS_ADD_FILE(clear_ucode_statistics, dir_debug, 0200); + DEBUGFS_ADD_FILE(missed_beacon, dir_debug, 0200); + DEBUGFS_ADD_FILE(plcp_delta, dir_debug, 0600); + DEBUGFS_ADD_FILE(rf_reset, dir_debug, 0600); + DEBUGFS_ADD_FILE(ucode_rx_stats, dir_debug, 0400); + DEBUGFS_ADD_FILE(ucode_tx_stats, dir_debug, 0400); + DEBUGFS_ADD_FILE(ucode_general_stats, dir_debug, 0400); + DEBUGFS_ADD_FILE(txfifo_flush, dir_debug, 0200); + DEBUGFS_ADD_FILE(protection_mode, dir_debug, 0600); + DEBUGFS_ADD_FILE(sensitivity, dir_debug, 0400); + DEBUGFS_ADD_FILE(chain_noise, dir_debug, 0400); + DEBUGFS_ADD_FILE(ucode_tracing, dir_debug, 0600); + DEBUGFS_ADD_FILE(ucode_bt_stats, dir_debug, 0400); + DEBUGFS_ADD_FILE(reply_tx_error, dir_debug, 0400); + DEBUGFS_ADD_FILE(rxon_flags, dir_debug, 0200); + DEBUGFS_ADD_FILE(rxon_filter_flags, dir_debug, 0200); + DEBUGFS_ADD_FILE(echo_test, dir_debug, 0200); + DEBUGFS_ADD_FILE(fw_restart, dir_debug, 0200); +#ifdef CONFIG_IWLWIFI_DEBUG + DEBUGFS_ADD_FILE(log_event, dir_debug, 0600); +#endif + + if (iwl_advanced_bt_coexist(priv)) + DEBUGFS_ADD_FILE(bt_traffic, dir_debug, 0400); + + /* Calibrations disabled/enabled status*/ + DEBUGFS_ADD_FILE(calib_disabled, dir_rf, 0600); + + /* + * Create a symlink with mac80211. This is not very robust, as it does + * not remove the symlink created. The implicit assumption is that + * when the opmode exits, mac80211 will also exit, and will remove + * this symlink as part of its cleanup. + */ + if (priv->mac80211_registered) { + char buf[100]; + struct dentry *mac80211_dir, *dev_dir; + + dev_dir = dbgfs_dir->d_parent; + mac80211_dir = priv->hw->wiphy->debugfsdir; + + snprintf(buf, 100, "../../%pd2", dev_dir); + + debugfs_create_symlink("iwlwifi", mac80211_dir, buf); + } +} diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/dev.h b/drivers/net/wireless/intel/iwlwifi/dvm/dev.h new file mode 100644 index 000000000..1a9eadace --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/dev.h @@ -0,0 +1,924 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + * + * Copyright(c) 2003 - 2014, 2020 Intel Corporation. All rights reserved. + *****************************************************************************/ +/* + * Please use this file (dev.h) for driver implementation definitions. + * Please use commands.h for uCode API definitions. + */ + +#ifndef __iwl_dev_h__ +#define __iwl_dev_h__ + +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/wait.h> +#include <linux/leds.h> +#include <linux/slab.h> +#include <linux/mutex.h> + +#include "fw/img.h" +#include "iwl-eeprom-parse.h" +#include "iwl-csr.h" +#include "iwl-debug.h" +#include "iwl-agn-hw.h" +#include "iwl-op-mode.h" +#include "fw/notif-wait.h" +#include "iwl-trans.h" + +#include "led.h" +#include "power.h" +#include "rs.h" +#include "tt.h" + +/* CT-KILL constants */ +#define CT_KILL_THRESHOLD_LEGACY 110 /* in Celsius */ +#define CT_KILL_THRESHOLD 114 /* in Celsius */ +#define CT_KILL_EXIT_THRESHOLD 95 /* in Celsius */ + +/* Default noise level to report when noise measurement is not available. + * This may be because we're: + * 1) Not associated no beacon statistics being sent to driver) + * 2) Scanning (noise measurement does not apply to associated channel) + * Use default noise value of -127 ... this is below the range of measurable + * Rx dBm for all agn devices, so it can indicate "unmeasurable" to user. + * Also, -127 works better than 0 when averaging frames with/without + * noise info (e.g. averaging might be done in app); measured dBm values are + * always negative ... using a negative value as the default keeps all + * averages within an s8's (used in some apps) range of negative values. */ +#define IWL_NOISE_MEAS_NOT_AVAILABLE (-127) + +/* + * RTS threshold here is total size [2347] minus 4 FCS bytes + * Per spec: + * a value of 0 means RTS on all data/management packets + * a value > max MSDU size means no RTS + * else RTS for data/management frames where MPDU is larger + * than RTS value. + */ +#define DEFAULT_RTS_THRESHOLD 2347U +#define MIN_RTS_THRESHOLD 0U +#define MAX_RTS_THRESHOLD 2347U +#define MAX_MSDU_SIZE 2304U +#define MAX_MPDU_SIZE 2346U +#define DEFAULT_BEACON_INTERVAL 200U +#define DEFAULT_SHORT_RETRY_LIMIT 7U +#define DEFAULT_LONG_RETRY_LIMIT 4U + +#define IWL_NUM_SCAN_RATES (2) + + +#define IEEE80211_DATA_LEN 2304 +#define IEEE80211_4ADDR_LEN 30 +#define IEEE80211_HLEN (IEEE80211_4ADDR_LEN) +#define IEEE80211_FRAME_LEN (IEEE80211_DATA_LEN + IEEE80211_HLEN) + +#define SUP_RATE_11A_MAX_NUM_CHANNELS 8 +#define SUP_RATE_11B_MAX_NUM_CHANNELS 4 +#define SUP_RATE_11G_MAX_NUM_CHANNELS 12 + +#define IWL_SUPPORTED_RATES_IE_LEN 8 + +#define IWL_INVALID_RATE 0xFF +#define IWL_INVALID_VALUE -1 + +union iwl_ht_rate_supp { + u16 rates; + struct { + u8 siso_rate; + u8 mimo_rate; + }; +}; + +struct iwl_ht_config { + bool single_chain_sufficient; + enum ieee80211_smps_mode smps; /* current smps mode */ +}; + +/* QoS structures */ +struct iwl_qos_info { + int qos_active; + struct iwl_qosparam_cmd def_qos_parm; +}; + +/** + * enum iwl_agg_state + * + * The state machine of the BA agreement establishment / tear down. + * These states relate to a specific RA / TID. + * + * @IWL_AGG_OFF: aggregation is not used + * @IWL_AGG_STARTING: aggregation are starting (between start and oper) + * @IWL_AGG_ON: aggregation session is up + * @IWL_EMPTYING_HW_QUEUE_ADDBA: establishing a BA session - waiting for the + * HW queue to be empty from packets for this RA /TID. + * @IWL_EMPTYING_HW_QUEUE_DELBA: tearing down a BA session - waiting for the + * HW queue to be empty from packets for this RA /TID. + */ +enum iwl_agg_state { + IWL_AGG_OFF = 0, + IWL_AGG_STARTING, + IWL_AGG_ON, + IWL_EMPTYING_HW_QUEUE_ADDBA, + IWL_EMPTYING_HW_QUEUE_DELBA, +}; + +/** + * struct iwl_ht_agg - aggregation state machine + + * This structs holds the states for the BA agreement establishment and tear + * down. It also holds the state during the BA session itself. This struct is + * duplicated for each RA / TID. + + * @rate_n_flags: Rate at which Tx was attempted. Holds the data between the + * Tx response (REPLY_TX), and the block ack notification + * (REPLY_COMPRESSED_BA). + * @state: state of the BA agreement establishment / tear down. + * @txq_id: Tx queue used by the BA session + * @ssn: the first packet to be sent in AGG HW queue in Tx AGG start flow, or + * the first packet to be sent in legacy HW queue in Tx AGG stop flow. + * Basically when next_reclaimed reaches ssn, we can tell mac80211 that + * we are ready to finish the Tx AGG stop / start flow. + * @wait_for_ba: Expect block-ack before next Tx reply + */ +struct iwl_ht_agg { + u32 rate_n_flags; + enum iwl_agg_state state; + u16 txq_id; + u16 ssn; + bool wait_for_ba; +}; + +/** + * struct iwl_tid_data - one for each RA / TID + + * This structs holds the states for each RA / TID. + + * @seq_number: the next WiFi sequence number to use + * @next_reclaimed: the WiFi sequence number of the next packet to be acked. + * This is basically (last acked packet++). + * @agg: aggregation state machine + */ +struct iwl_tid_data { + u16 seq_number; + u16 next_reclaimed; + struct iwl_ht_agg agg; +}; + +/* + * Structure should be accessed with sta_lock held. When station addition + * is in progress (IWL_STA_UCODE_INPROGRESS) it is possible to access only + * the commands (iwl_addsta_cmd and iwl_link_quality_cmd) without sta_lock + * held. + */ +struct iwl_station_entry { + struct iwl_addsta_cmd sta; + u8 used, ctxid; + struct iwl_link_quality_cmd *lq; +}; + +/* + * iwl_station_priv: Driver's private station information + * + * When mac80211 creates a station it reserves some space (hw->sta_data_size) + * in the structure for use by driver. This structure is places in that + * space. + */ +struct iwl_station_priv { + struct iwl_rxon_context *ctx; + struct iwl_lq_sta lq_sta; + atomic_t pending_frames; + bool client; + bool asleep; + u8 max_agg_bufsize; + u8 sta_id; +}; + +/** + * struct iwl_vif_priv - driver's private per-interface information + * + * When mac80211 allocates a virtual interface, it can allocate + * space for us to put data into. + */ +struct iwl_vif_priv { + struct iwl_rxon_context *ctx; + u8 ibss_bssid_sta_id; +}; + +struct iwl_sensitivity_ranges { + u16 min_nrg_cck; + + u16 nrg_th_cck; + u16 nrg_th_ofdm; + + u16 auto_corr_min_ofdm; + u16 auto_corr_min_ofdm_mrc; + u16 auto_corr_min_ofdm_x1; + u16 auto_corr_min_ofdm_mrc_x1; + + u16 auto_corr_max_ofdm; + u16 auto_corr_max_ofdm_mrc; + u16 auto_corr_max_ofdm_x1; + u16 auto_corr_max_ofdm_mrc_x1; + + u16 auto_corr_max_cck; + u16 auto_corr_max_cck_mrc; + u16 auto_corr_min_cck; + u16 auto_corr_min_cck_mrc; + + u16 barker_corr_th_min; + u16 barker_corr_th_min_mrc; + u16 nrg_th_cca; +}; + +/****************************************************************************** + * + * Functions implemented in core module which are forward declared here + * for use by iwl-[4-5].c + * + * NOTE: The implementation of these functions are not hardware specific + * which is why they are in the core module files. + * + * Naming convention -- + * iwl_ <-- Is part of iwlwifi + * iwlXXXX_ <-- Hardware specific (implemented in iwl-XXXX.c for XXXX) + * + ****************************************************************************/ +void iwl_update_chain_flags(struct iwl_priv *priv); +extern const u8 iwl_bcast_addr[ETH_ALEN]; + +#define IWL_OPERATION_MODE_AUTO 0 +#define IWL_OPERATION_MODE_HT_ONLY 1 +#define IWL_OPERATION_MODE_MIXED 2 +#define IWL_OPERATION_MODE_20MHZ 3 + +#define TX_POWER_IWL_ILLEGAL_VOLTAGE -10000 + +/* Sensitivity and chain noise calibration */ +#define INITIALIZATION_VALUE 0xFFFF +#define IWL_CAL_NUM_BEACONS 16 +#define MAXIMUM_ALLOWED_PATHLOSS 15 + +#define CHAIN_NOISE_MAX_DELTA_GAIN_CODE 3 + +#define MAX_FA_OFDM 50 +#define MIN_FA_OFDM 5 +#define MAX_FA_CCK 50 +#define MIN_FA_CCK 5 + +#define AUTO_CORR_STEP_OFDM 1 + +#define AUTO_CORR_STEP_CCK 3 +#define AUTO_CORR_MAX_TH_CCK 160 + +#define NRG_DIFF 2 +#define NRG_STEP_CCK 2 +#define NRG_MARGIN 8 +#define MAX_NUMBER_CCK_NO_FA 100 + +#define AUTO_CORR_CCK_MIN_VAL_DEF (125) + +#define CHAIN_A 0 +#define CHAIN_B 1 +#define CHAIN_C 2 +#define CHAIN_NOISE_DELTA_GAIN_INIT_VAL 4 +#define ALL_BAND_FILTER 0xFF00 +#define IN_BAND_FILTER 0xFF +#define MIN_AVERAGE_NOISE_MAX_VALUE 0xFFFFFFFF + +#define NRG_NUM_PREV_STAT_L 20 +#define NUM_RX_CHAINS 3 + +enum iwlagn_false_alarm_state { + IWL_FA_TOO_MANY = 0, + IWL_FA_TOO_FEW = 1, + IWL_FA_GOOD_RANGE = 2, +}; + +enum iwlagn_chain_noise_state { + IWL_CHAIN_NOISE_ALIVE = 0, /* must be 0 */ + IWL_CHAIN_NOISE_ACCUMULATE, + IWL_CHAIN_NOISE_CALIBRATED, + IWL_CHAIN_NOISE_DONE, +}; + +/* Sensitivity calib data */ +struct iwl_sensitivity_data { + u32 auto_corr_ofdm; + u32 auto_corr_ofdm_mrc; + u32 auto_corr_ofdm_x1; + u32 auto_corr_ofdm_mrc_x1; + u32 auto_corr_cck; + u32 auto_corr_cck_mrc; + + u32 last_bad_plcp_cnt_ofdm; + u32 last_fa_cnt_ofdm; + u32 last_bad_plcp_cnt_cck; + u32 last_fa_cnt_cck; + + u32 nrg_curr_state; + u32 nrg_prev_state; + u32 nrg_value[10]; + u8 nrg_silence_rssi[NRG_NUM_PREV_STAT_L]; + u32 nrg_silence_ref; + u32 nrg_energy_idx; + u32 nrg_silence_idx; + u32 nrg_th_cck; + s32 nrg_auto_corr_silence_diff; + u32 num_in_cck_no_fa; + u32 nrg_th_ofdm; + + u16 barker_corr_th_min; + u16 barker_corr_th_min_mrc; + u16 nrg_th_cca; +}; + +/* Chain noise (differential Rx gain) calib data */ +struct iwl_chain_noise_data { + u32 active_chains; + u32 chain_noise_a; + u32 chain_noise_b; + u32 chain_noise_c; + u32 chain_signal_a; + u32 chain_signal_b; + u32 chain_signal_c; + u16 beacon_count; + u8 disconn_array[NUM_RX_CHAINS]; + u8 delta_gain_code[NUM_RX_CHAINS]; + u8 radio_write; + u8 state; +}; + +enum { + MEASUREMENT_READY = (1 << 0), + MEASUREMENT_ACTIVE = (1 << 1), +}; + +/* reply_tx_statistics (for _agn devices) */ +struct reply_tx_error_statistics { + u32 pp_delay; + u32 pp_few_bytes; + u32 pp_bt_prio; + u32 pp_quiet_period; + u32 pp_calc_ttak; + u32 int_crossed_retry; + u32 short_limit; + u32 long_limit; + u32 fifo_underrun; + u32 drain_flow; + u32 rfkill_flush; + u32 life_expire; + u32 dest_ps; + u32 host_abort; + u32 bt_retry; + u32 sta_invalid; + u32 frag_drop; + u32 tid_disable; + u32 fifo_flush; + u32 insuff_cf_poll; + u32 fail_hw_drop; + u32 sta_color_mismatch; + u32 unknown; +}; + +/* reply_agg_tx_statistics (for _agn devices) */ +struct reply_agg_tx_error_statistics { + u32 underrun; + u32 bt_prio; + u32 few_bytes; + u32 abort; + u32 last_sent_ttl; + u32 last_sent_try; + u32 last_sent_bt_kill; + u32 scd_query; + u32 bad_crc32; + u32 response; + u32 dump_tx; + u32 delay_tx; + u32 unknown; +}; + +/* + * schedule the timer to wake up every UCODE_TRACE_PERIOD milliseconds + * to perform continuous uCode event logging operation if enabled + */ +#define UCODE_TRACE_PERIOD (10) + +/* + * iwl_event_log: current uCode event log position + * + * @ucode_trace: enable/disable ucode continuous trace timer + * @num_wraps: how many times the event buffer wraps + * @next_entry: the entry just before the next one that uCode would fill + * @non_wraps_count: counter for no wrap detected when dump ucode events + * @wraps_once_count: counter for wrap once detected when dump ucode events + * @wraps_more_count: counter for wrap more than once detected + * when dump ucode events + */ +struct iwl_event_log { + bool ucode_trace; + u32 num_wraps; + u32 next_entry; + int non_wraps_count; + int wraps_once_count; + int wraps_more_count; +}; + +#define IWL_DELAY_NEXT_FORCE_RF_RESET (HZ*3) + +/* BT Antenna Coupling Threshold (dB) */ +#define IWL_BT_ANTENNA_COUPLING_THRESHOLD (35) + +/* Firmware reload counter and Timestamp */ +#define IWL_MIN_RELOAD_DURATION 1000 /* 1000 ms */ +#define IWL_MAX_CONTINUE_RELOAD_CNT 4 + + +struct iwl_rf_reset { + int reset_request_count; + int reset_success_count; + int reset_reject_count; + unsigned long last_reset_jiffies; +}; + +enum iwl_rxon_context_id { + IWL_RXON_CTX_BSS, + IWL_RXON_CTX_PAN, + + NUM_IWL_RXON_CTX +}; + +/* extend beacon time format bit shifting */ +/* + * for _agn devices + * bits 31:22 - extended + * bits 21:0 - interval + */ +#define IWLAGN_EXT_BEACON_TIME_POS 22 + +struct iwl_rxon_context { + struct ieee80211_vif *vif; + + u8 mcast_queue; + u8 ac_to_queue[IEEE80211_NUM_ACS]; + u8 ac_to_fifo[IEEE80211_NUM_ACS]; + + /* + * We could use the vif to indicate active, but we + * also need it to be active during disabling when + * we already removed the vif for type setting. + */ + bool always_active, is_active; + + bool ht_need_multiple_chains; + + enum iwl_rxon_context_id ctxid; + + u32 interface_modes, exclusive_interface_modes; + u8 unused_devtype, ap_devtype, ibss_devtype, station_devtype; + + /* + * We declare this const so it can only be + * changed via explicit cast within the + * routines that actually update the physical + * hardware. + */ + const struct iwl_rxon_cmd active; + struct iwl_rxon_cmd staging; + + struct iwl_rxon_time_cmd timing; + + struct iwl_qos_info qos_data; + + u8 bcast_sta_id, ap_sta_id; + + u8 rxon_cmd, rxon_assoc_cmd, rxon_timing_cmd; + u8 qos_cmd; + u8 wep_key_cmd; + + struct iwl_wep_key wep_keys[WEP_KEYS_MAX]; + u8 key_mapping_keys; + + __le32 station_flags; + + int beacon_int; + + struct { + bool non_gf_sta_present; + u8 protection; + bool enabled, is_40mhz; + u8 extension_chan_offset; + } ht; +}; + +enum iwl_scan_type { + IWL_SCAN_NORMAL, + IWL_SCAN_RADIO_RESET, +}; + +/** + * struct iwl_hw_params + * + * Holds the module parameters + * + * @tx_chains_num: Number of TX chains + * @rx_chains_num: Number of RX chains + * @ct_kill_threshold: temperature threshold - in hw dependent unit + * @ct_kill_exit_threshold: when to reeable the device - in hw dependent unit + * relevant for 1000, 6000 and up + * @struct iwl_sensitivity_ranges: range of sensitivity values + * @use_rts_for_aggregation: use rts/cts protection for HT traffic + */ +struct iwl_hw_params { + u8 tx_chains_num; + u8 rx_chains_num; + bool use_rts_for_aggregation; + u32 ct_kill_threshold; + u32 ct_kill_exit_threshold; + + const struct iwl_sensitivity_ranges *sens; +}; + +/** + * struct iwl_dvm_bt_params - DVM specific BT (coex) parameters + * @advanced_bt_coexist: support advanced bt coexist + * @bt_init_traffic_load: specify initial bt traffic load + * @bt_prio_boost: default bt priority boost value + * @agg_time_limit: maximum number of uSec in aggregation + * @bt_sco_disable: uCode should not response to BT in SCO/ESCO mode + */ +struct iwl_dvm_bt_params { + bool advanced_bt_coexist; + u8 bt_init_traffic_load; + u32 bt_prio_boost; + u16 agg_time_limit; + bool bt_sco_disable; + bool bt_session_2; +}; + +/** + * struct iwl_dvm_cfg - DVM firmware specific device configuration + * @set_hw_params: set hardware parameters + * @set_channel_switch: send channel switch command + * @nic_config: apply device specific configuration + * @temperature: read temperature + * @adv_thermal_throttle: support advance thermal throttle + * @support_ct_kill_exit: support ct kill exit condition + * @plcp_delta_threshold: plcp error rate threshold used to trigger + * radio tuning when there is a high receiving plcp error rate + * @chain_noise_scale: default chain noise scale used for gain computation + * @hd_v2: v2 of enhanced sensitivity value, used for 2000 series and up + * @no_idle_support: do not support idle mode + * @bt_params: pointer to BT parameters + * @need_temp_offset_calib: need to perform temperature offset calibration + * @no_xtal_calib: some devices do not need crystal calibration data, + * don't send it to those + * @temp_offset_v2: support v2 of temperature offset calibration + * @adv_pm: advanced power management + */ +struct iwl_dvm_cfg { + void (*set_hw_params)(struct iwl_priv *priv); + int (*set_channel_switch)(struct iwl_priv *priv, + struct ieee80211_channel_switch *ch_switch); + void (*nic_config)(struct iwl_priv *priv); + void (*temperature)(struct iwl_priv *priv); + + const struct iwl_dvm_bt_params *bt_params; + s32 chain_noise_scale; + u8 plcp_delta_threshold; + bool adv_thermal_throttle; + bool support_ct_kill_exit; + bool hd_v2; + bool no_idle_support; + bool need_temp_offset_calib; + bool no_xtal_calib; + bool temp_offset_v2; + bool adv_pm; +}; + +struct iwl_wipan_noa_data { + struct rcu_head rcu_head; + u32 length; + u8 data[]; +}; + +/* Calibration disabling bit mask */ +enum { + IWL_CALIB_ENABLE_ALL = 0, + + IWL_SENSITIVITY_CALIB_DISABLED = BIT(0), + IWL_CHAIN_NOISE_CALIB_DISABLED = BIT(1), + IWL_TX_POWER_CALIB_DISABLED = BIT(2), + + IWL_CALIB_DISABLE_ALL = 0xFFFFFFFF, +}; + +#define IWL_OP_MODE_GET_DVM(_iwl_op_mode) \ + ((struct iwl_priv *) ((_iwl_op_mode)->op_mode_specific)) + +#define IWL_MAC80211_GET_DVM(_hw) \ + ((struct iwl_priv *) ((struct iwl_op_mode *) \ + (_hw)->priv)->op_mode_specific) + +struct iwl_priv { + + struct iwl_trans *trans; + struct device *dev; /* for debug prints only */ + const struct iwl_cfg *cfg; + const struct iwl_fw *fw; + const struct iwl_dvm_cfg *lib; + unsigned long status; + + spinlock_t sta_lock; + struct mutex mutex; + + unsigned long transport_queue_stop; + bool passive_no_rx; +#define IWL_INVALID_MAC80211_QUEUE 0xff + u8 queue_to_mac80211[IWL_MAX_HW_QUEUES]; + atomic_t queue_stop_count[IWL_MAX_HW_QUEUES]; + + unsigned long agg_q_alloc[BITS_TO_LONGS(IWL_MAX_HW_QUEUES)]; + + /* ieee device used by generic ieee processing code */ + struct ieee80211_hw *hw; + + struct napi_struct *napi; + + struct list_head calib_results; + + struct workqueue_struct *workqueue; + + struct iwl_hw_params hw_params; + + enum nl80211_band band; + u8 valid_contexts; + + void (*rx_handlers[REPLY_MAX])(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb); + + struct iwl_notif_wait_data notif_wait; + + /* spectrum measurement report caching */ + struct iwl_spectrum_notification measure_report; + u8 measurement_status; + + /* ucode beacon time */ + u32 ucode_beacon_time; + int missed_beacon_threshold; + + /* track IBSS manager (last beacon) status */ + u32 ibss_manager; + + /* jiffies when last recovery from statistics was performed */ + unsigned long rx_statistics_jiffies; + + /*counters */ + u32 rx_handlers_stats[REPLY_MAX]; + + /* rf reset */ + struct iwl_rf_reset rf_reset; + + /* firmware reload counter and timestamp */ + unsigned long reload_jiffies; + int reload_count; + bool ucode_loaded; + + u8 plcp_delta_threshold; + + /* thermal calibration */ + s32 temperature; /* Celsius */ + s32 last_temperature; + + struct iwl_wipan_noa_data __rcu *noa_data; + + /* Scan related variables */ + unsigned long scan_start; + unsigned long scan_start_tsf; + size_t scan_cmd_size; + void *scan_cmd; + enum nl80211_band scan_band; + struct cfg80211_scan_request *scan_request; + struct ieee80211_vif *scan_vif; + enum iwl_scan_type scan_type; + u8 scan_tx_ant[NUM_NL80211_BANDS]; + u8 mgmt_tx_ant; + + /* max number of station keys */ + u8 sta_key_max_num; + + bool new_scan_threshold_behaviour; + + bool wowlan; + + /* EEPROM MAC addresses */ + struct mac_address addresses[2]; + + struct iwl_rxon_context contexts[NUM_IWL_RXON_CTX]; + + __le16 switch_channel; + + u8 start_calib; + struct iwl_sensitivity_data sensitivity_data; + struct iwl_chain_noise_data chain_noise_data; + __le16 sensitivity_tbl[HD_TABLE_SIZE]; + __le16 enhance_sensitivity_tbl[ENHANCE_HD_TABLE_ENTRIES]; + + struct iwl_ht_config current_ht_config; + + /* Rate scaling data */ + u8 retry_rate; + + int activity_timer_active; + + struct iwl_power_mgr power_data; + struct iwl_tt_mgmt thermal_throttle; + + /* station table variables */ + int num_stations; + struct iwl_station_entry stations[IWLAGN_STATION_COUNT]; + unsigned long ucode_key_table; + struct iwl_tid_data tid_data[IWLAGN_STATION_COUNT][IWL_MAX_TID_COUNT]; + atomic_t num_aux_in_flight; + + u8 mac80211_registered; + + /* Indication if ieee80211_ops->open has been called */ + u8 is_open; + + enum nl80211_iftype iw_mode; + + /* Last Rx'd beacon timestamp */ + u64 timestamp; + + struct { + __le32 flag; + struct statistics_general_common common; + struct statistics_rx_non_phy rx_non_phy; + struct statistics_rx_phy rx_ofdm; + struct statistics_rx_ht_phy rx_ofdm_ht; + struct statistics_rx_phy rx_cck; + struct statistics_tx tx; +#ifdef CONFIG_IWLWIFI_DEBUGFS + struct statistics_bt_activity bt_activity; + __le32 num_bt_kills, accum_num_bt_kills; +#endif + spinlock_t lock; + } statistics; +#ifdef CONFIG_IWLWIFI_DEBUGFS + struct { + struct statistics_general_common common; + struct statistics_rx_non_phy rx_non_phy; + struct statistics_rx_phy rx_ofdm; + struct statistics_rx_ht_phy rx_ofdm_ht; + struct statistics_rx_phy rx_cck; + struct statistics_tx tx; + struct statistics_bt_activity bt_activity; + } accum_stats, delta_stats, max_delta_stats; +#endif + + /* + * reporting the number of tids has AGG on. 0 means + * no AGGREGATION + */ + u8 agg_tids_count; + + struct iwl_rx_phy_res last_phy_res; + u32 ampdu_ref; + bool last_phy_res_valid; + + /* + * chain noise reset and gain commands are the + * two extra calibration commands follows the standard + * phy calibration commands + */ + u8 phy_calib_chain_noise_reset_cmd; + u8 phy_calib_chain_noise_gain_cmd; + + /* counts reply_tx error */ + struct reply_tx_error_statistics reply_tx_stats; + struct reply_agg_tx_error_statistics reply_agg_tx_stats; + + /* bt coex */ + u8 bt_enable_flag; + u8 bt_status; + u8 bt_traffic_load, last_bt_traffic_load; + bool bt_ch_announce; + bool bt_full_concurrent; + __le32 kill_ack_mask; + __le32 kill_cts_mask; + __le16 bt_valid; + bool reduced_txpower; + u16 bt_on_thresh; + u16 bt_duration; + u16 dynamic_frag_thresh; + u8 bt_ci_compliance; + struct work_struct bt_traffic_change_work; + bool bt_enable_pspoll; + struct iwl_rxon_context *cur_rssi_ctx; + bool bt_is_sco; + + struct work_struct restart; + struct work_struct scan_completed; + struct work_struct abort_scan; + + struct work_struct beacon_update; + struct iwl_rxon_context *beacon_ctx; + struct sk_buff *beacon_skb; + void *beacon_cmd; + + struct work_struct tt_work; + struct work_struct ct_enter; + struct work_struct ct_exit; + struct work_struct start_internal_scan; + struct work_struct tx_flush; + struct work_struct bt_full_concurrency; + struct work_struct bt_runtime_config; + + struct delayed_work scan_check; + + /* TX Power settings */ + s8 tx_power_user_lmt; + s8 tx_power_next; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + /* debugfs */ + struct dentry *debugfs_dir; + u32 dbgfs_sram_offset, dbgfs_sram_len; + bool disable_ht40; + void *wowlan_sram; +#endif /* CONFIG_IWLWIFI_DEBUGFS */ + + struct iwl_nvm_data *nvm_data; + /* eeprom blob for debugfs */ + u8 *eeprom_blob; + size_t eeprom_blob_size; + + struct work_struct txpower_work; + u32 calib_disabled; + struct work_struct run_time_calib_work; + struct timer_list statistics_periodic; + struct timer_list ucode_trace; + + struct iwl_event_log event_log; + +#ifdef CONFIG_IWLWIFI_LEDS + struct led_classdev led; + unsigned long blink_on, blink_off; + bool led_registered; +#endif + + /* WoWLAN GTK rekey data */ + u8 kck[NL80211_KCK_LEN], kek[NL80211_KEK_LEN]; + __le64 replay_ctr; + __le16 last_seq_ctl; + bool have_rekey_data; +#ifdef CONFIG_PM_SLEEP + struct wiphy_wowlan_support wowlan_support; +#endif + + /* device_pointers: pointers to ucode event tables */ + struct { + u32 error_event_table; + u32 log_event_table; + } device_pointers; + + /* indicator of loaded ucode image */ + enum iwl_ucode_type cur_ucode; +}; /*iwl_priv */ + +static inline struct iwl_rxon_context * +iwl_rxon_ctx_from_vif(struct ieee80211_vif *vif) +{ + struct iwl_vif_priv *vif_priv = (void *)vif->drv_priv; + + return vif_priv->ctx; +} + +#define for_each_context(priv, ctx) \ + for (ctx = &priv->contexts[IWL_RXON_CTX_BSS]; \ + ctx < &priv->contexts[NUM_IWL_RXON_CTX]; ctx++) \ + if (priv->valid_contexts & BIT(ctx->ctxid)) + +static inline int iwl_is_associated_ctx(struct iwl_rxon_context *ctx) +{ + return (ctx->active.filter_flags & RXON_FILTER_ASSOC_MSK) ? 1 : 0; +} + +static inline int iwl_is_associated(struct iwl_priv *priv, + enum iwl_rxon_context_id ctxid) +{ + return iwl_is_associated_ctx(&priv->contexts[ctxid]); +} + +static inline int iwl_is_any_associated(struct iwl_priv *priv) +{ + struct iwl_rxon_context *ctx; + for_each_context(priv, ctx) + if (iwl_is_associated_ctx(ctx)) + return true; + return false; +} + +#endif /* __iwl_dev_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/devices.c b/drivers/net/wireless/intel/iwlwifi/dvm/devices.c new file mode 100644 index 000000000..39e40901f --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/devices.c @@ -0,0 +1,673 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2008 - 2014 Intel Corporation. All rights reserved. + * Copyright (C) 2019 Intel Corporation + *****************************************************************************/ + +#include <linux/units.h> + +/* + * DVM device-specific data & functions + */ +#include "iwl-io.h" +#include "iwl-prph.h" +#include "iwl-eeprom-parse.h" + +#include "agn.h" +#include "dev.h" +#include "commands.h" + + +/* + * 1000 series + * =========== + */ + +/* + * For 1000, use advance thermal throttling critical temperature threshold, + * but legacy thermal management implementation for now. + * This is for the reason of 1000 uCode using advance thermal throttling API + * but not implement ct_kill_exit based on ct_kill exit temperature + * so the thermal throttling will still based on legacy thermal throttling + * management. + * The code here need to be modified once 1000 uCode has the advanced thermal + * throttling algorithm in place + */ +static void iwl1000_set_ct_threshold(struct iwl_priv *priv) +{ + /* want Celsius */ + priv->hw_params.ct_kill_threshold = CT_KILL_THRESHOLD_LEGACY; + priv->hw_params.ct_kill_exit_threshold = CT_KILL_EXIT_THRESHOLD; +} + +/* NIC configuration for 1000 series */ +static void iwl1000_nic_config(struct iwl_priv *priv) +{ + /* Setting digital SVR for 1000 card to 1.32V */ + /* locking is acquired in iwl_set_bits_mask_prph() function */ + iwl_set_bits_mask_prph(priv->trans, APMG_DIGITAL_SVR_REG, + APMG_SVR_DIGITAL_VOLTAGE_1_32, + ~APMG_SVR_VOLTAGE_CONFIG_BIT_MSK); +} + +/** + * iwl_beacon_time_mask_low - mask of lower 32 bit of beacon time + * @priv: pointer to iwl_priv data structure + * @tsf_bits: number of bits need to shift for masking) + */ +static inline u32 iwl_beacon_time_mask_low(struct iwl_priv *priv, + u16 tsf_bits) +{ + return (1 << tsf_bits) - 1; +} + +/** + * iwl_beacon_time_mask_high - mask of higher 32 bit of beacon time + * @priv: pointer to iwl_priv data structure + * @tsf_bits: number of bits need to shift for masking) + */ +static inline u32 iwl_beacon_time_mask_high(struct iwl_priv *priv, + u16 tsf_bits) +{ + return ((1 << (32 - tsf_bits)) - 1) << tsf_bits; +} + +/* + * extended beacon time format + * time in usec will be changed into a 32-bit value in extended:internal format + * the extended part is the beacon counts + * the internal part is the time in usec within one beacon interval + */ +static u32 iwl_usecs_to_beacons(struct iwl_priv *priv, u32 usec, + u32 beacon_interval) +{ + u32 quot; + u32 rem; + u32 interval = beacon_interval * TIME_UNIT; + + if (!interval || !usec) + return 0; + + quot = (usec / interval) & + (iwl_beacon_time_mask_high(priv, IWLAGN_EXT_BEACON_TIME_POS) >> + IWLAGN_EXT_BEACON_TIME_POS); + rem = (usec % interval) & iwl_beacon_time_mask_low(priv, + IWLAGN_EXT_BEACON_TIME_POS); + + return (quot << IWLAGN_EXT_BEACON_TIME_POS) + rem; +} + +/* base is usually what we get from ucode with each received frame, + * the same as HW timer counter counting down + */ +static __le32 iwl_add_beacon_time(struct iwl_priv *priv, u32 base, + u32 addon, u32 beacon_interval) +{ + u32 base_low = base & iwl_beacon_time_mask_low(priv, + IWLAGN_EXT_BEACON_TIME_POS); + u32 addon_low = addon & iwl_beacon_time_mask_low(priv, + IWLAGN_EXT_BEACON_TIME_POS); + u32 interval = beacon_interval * TIME_UNIT; + u32 res = (base & iwl_beacon_time_mask_high(priv, + IWLAGN_EXT_BEACON_TIME_POS)) + + (addon & iwl_beacon_time_mask_high(priv, + IWLAGN_EXT_BEACON_TIME_POS)); + + if (base_low > addon_low) + res += base_low - addon_low; + else if (base_low < addon_low) { + res += interval + base_low - addon_low; + res += (1 << IWLAGN_EXT_BEACON_TIME_POS); + } else + res += (1 << IWLAGN_EXT_BEACON_TIME_POS); + + return cpu_to_le32(res); +} + +static const struct iwl_sensitivity_ranges iwl1000_sensitivity = { + .min_nrg_cck = 95, + .auto_corr_min_ofdm = 90, + .auto_corr_min_ofdm_mrc = 170, + .auto_corr_min_ofdm_x1 = 120, + .auto_corr_min_ofdm_mrc_x1 = 240, + + .auto_corr_max_ofdm = 120, + .auto_corr_max_ofdm_mrc = 210, + .auto_corr_max_ofdm_x1 = 155, + .auto_corr_max_ofdm_mrc_x1 = 290, + + .auto_corr_min_cck = 125, + .auto_corr_max_cck = 200, + .auto_corr_min_cck_mrc = 170, + .auto_corr_max_cck_mrc = 400, + .nrg_th_cck = 95, + .nrg_th_ofdm = 95, + + .barker_corr_th_min = 190, + .barker_corr_th_min_mrc = 390, + .nrg_th_cca = 62, +}; + +static void iwl1000_hw_set_hw_params(struct iwl_priv *priv) +{ + iwl1000_set_ct_threshold(priv); + + /* Set initial sensitivity parameters */ + priv->hw_params.sens = &iwl1000_sensitivity; +} + +const struct iwl_dvm_cfg iwl_dvm_1000_cfg = { + .set_hw_params = iwl1000_hw_set_hw_params, + .nic_config = iwl1000_nic_config, + .temperature = iwlagn_temperature, + .support_ct_kill_exit = true, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_EXT_LONG_THRESHOLD_DEF, + .chain_noise_scale = 1000, +}; + + +/* + * 2000 series + * =========== + */ + +static void iwl2000_set_ct_threshold(struct iwl_priv *priv) +{ + /* want Celsius */ + priv->hw_params.ct_kill_threshold = CT_KILL_THRESHOLD; + priv->hw_params.ct_kill_exit_threshold = CT_KILL_EXIT_THRESHOLD; +} + +/* NIC configuration for 2000 series */ +static void iwl2000_nic_config(struct iwl_priv *priv) +{ + iwl_set_bit(priv->trans, CSR_GP_DRIVER_REG, + CSR_GP_DRIVER_REG_BIT_RADIO_IQ_INVER); +} + +static const struct iwl_sensitivity_ranges iwl2000_sensitivity = { + .min_nrg_cck = 97, + .auto_corr_min_ofdm = 80, + .auto_corr_min_ofdm_mrc = 128, + .auto_corr_min_ofdm_x1 = 105, + .auto_corr_min_ofdm_mrc_x1 = 192, + + .auto_corr_max_ofdm = 145, + .auto_corr_max_ofdm_mrc = 232, + .auto_corr_max_ofdm_x1 = 110, + .auto_corr_max_ofdm_mrc_x1 = 232, + + .auto_corr_min_cck = 125, + .auto_corr_max_cck = 175, + .auto_corr_min_cck_mrc = 160, + .auto_corr_max_cck_mrc = 310, + .nrg_th_cck = 97, + .nrg_th_ofdm = 100, + + .barker_corr_th_min = 190, + .barker_corr_th_min_mrc = 390, + .nrg_th_cca = 62, +}; + +static void iwl2000_hw_set_hw_params(struct iwl_priv *priv) +{ + iwl2000_set_ct_threshold(priv); + + /* Set initial sensitivity parameters */ + priv->hw_params.sens = &iwl2000_sensitivity; +} + +const struct iwl_dvm_cfg iwl_dvm_2000_cfg = { + .set_hw_params = iwl2000_hw_set_hw_params, + .nic_config = iwl2000_nic_config, + .temperature = iwlagn_temperature, + .adv_thermal_throttle = true, + .support_ct_kill_exit = true, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF, + .chain_noise_scale = 1000, + .hd_v2 = true, + .need_temp_offset_calib = true, + .temp_offset_v2 = true, +}; + +const struct iwl_dvm_cfg iwl_dvm_105_cfg = { + .set_hw_params = iwl2000_hw_set_hw_params, + .nic_config = iwl2000_nic_config, + .temperature = iwlagn_temperature, + .adv_thermal_throttle = true, + .support_ct_kill_exit = true, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF, + .chain_noise_scale = 1000, + .hd_v2 = true, + .need_temp_offset_calib = true, + .temp_offset_v2 = true, + .adv_pm = true, +}; + +static const struct iwl_dvm_bt_params iwl2030_bt_params = { + /* Due to bluetooth, we transmit 2.4 GHz probes only on antenna A */ + .advanced_bt_coexist = true, + .agg_time_limit = BT_AGG_THRESHOLD_DEF, + .bt_init_traffic_load = IWL_BT_COEX_TRAFFIC_LOAD_NONE, + .bt_prio_boost = IWLAGN_BT_PRIO_BOOST_DEFAULT32, + .bt_sco_disable = true, + .bt_session_2 = true, +}; + +const struct iwl_dvm_cfg iwl_dvm_2030_cfg = { + .set_hw_params = iwl2000_hw_set_hw_params, + .nic_config = iwl2000_nic_config, + .temperature = iwlagn_temperature, + .adv_thermal_throttle = true, + .support_ct_kill_exit = true, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF, + .chain_noise_scale = 1000, + .hd_v2 = true, + .bt_params = &iwl2030_bt_params, + .need_temp_offset_calib = true, + .temp_offset_v2 = true, + .adv_pm = true, +}; + +/* + * 5000 series + * =========== + */ + +/* NIC configuration for 5000 series */ +static const struct iwl_sensitivity_ranges iwl5000_sensitivity = { + .min_nrg_cck = 100, + .auto_corr_min_ofdm = 90, + .auto_corr_min_ofdm_mrc = 170, + .auto_corr_min_ofdm_x1 = 105, + .auto_corr_min_ofdm_mrc_x1 = 220, + + .auto_corr_max_ofdm = 120, + .auto_corr_max_ofdm_mrc = 210, + .auto_corr_max_ofdm_x1 = 120, + .auto_corr_max_ofdm_mrc_x1 = 240, + + .auto_corr_min_cck = 125, + .auto_corr_max_cck = 200, + .auto_corr_min_cck_mrc = 200, + .auto_corr_max_cck_mrc = 400, + .nrg_th_cck = 100, + .nrg_th_ofdm = 100, + + .barker_corr_th_min = 190, + .barker_corr_th_min_mrc = 390, + .nrg_th_cca = 62, +}; + +static const struct iwl_sensitivity_ranges iwl5150_sensitivity = { + .min_nrg_cck = 95, + .auto_corr_min_ofdm = 90, + .auto_corr_min_ofdm_mrc = 170, + .auto_corr_min_ofdm_x1 = 105, + .auto_corr_min_ofdm_mrc_x1 = 220, + + .auto_corr_max_ofdm = 120, + .auto_corr_max_ofdm_mrc = 210, + /* max = min for performance bug in 5150 DSP */ + .auto_corr_max_ofdm_x1 = 105, + .auto_corr_max_ofdm_mrc_x1 = 220, + + .auto_corr_min_cck = 125, + .auto_corr_max_cck = 200, + .auto_corr_min_cck_mrc = 170, + .auto_corr_max_cck_mrc = 400, + .nrg_th_cck = 95, + .nrg_th_ofdm = 95, + + .barker_corr_th_min = 190, + .barker_corr_th_min_mrc = 390, + .nrg_th_cca = 62, +}; + +#define IWL_5150_VOLTAGE_TO_TEMPERATURE_COEFF (-5) + +static s32 iwl_temp_calib_to_offset(struct iwl_priv *priv) +{ + u16 temperature, voltage; + + temperature = le16_to_cpu(priv->nvm_data->kelvin_temperature); + voltage = le16_to_cpu(priv->nvm_data->kelvin_voltage); + + /* offset = temp - volt / coeff */ + return (s32)(temperature - + voltage / IWL_5150_VOLTAGE_TO_TEMPERATURE_COEFF); +} + +static void iwl5150_set_ct_threshold(struct iwl_priv *priv) +{ + const s32 volt2temp_coef = IWL_5150_VOLTAGE_TO_TEMPERATURE_COEFF; + s32 threshold = (s32)celsius_to_kelvin(CT_KILL_THRESHOLD_LEGACY) - + iwl_temp_calib_to_offset(priv); + + priv->hw_params.ct_kill_threshold = threshold * volt2temp_coef; +} + +static void iwl5000_set_ct_threshold(struct iwl_priv *priv) +{ + /* want Celsius */ + priv->hw_params.ct_kill_threshold = CT_KILL_THRESHOLD_LEGACY; +} + +static void iwl5000_hw_set_hw_params(struct iwl_priv *priv) +{ + iwl5000_set_ct_threshold(priv); + + /* Set initial sensitivity parameters */ + priv->hw_params.sens = &iwl5000_sensitivity; +} + +static void iwl5150_hw_set_hw_params(struct iwl_priv *priv) +{ + iwl5150_set_ct_threshold(priv); + + /* Set initial sensitivity parameters */ + priv->hw_params.sens = &iwl5150_sensitivity; +} + +static void iwl5150_temperature(struct iwl_priv *priv) +{ + u32 vt = 0; + s32 offset = iwl_temp_calib_to_offset(priv); + + vt = le32_to_cpu(priv->statistics.common.temperature); + vt = vt / IWL_5150_VOLTAGE_TO_TEMPERATURE_COEFF + offset; + /* now vt hold the temperature in Kelvin */ + priv->temperature = kelvin_to_celsius(vt); + iwl_tt_handler(priv); +} + +static int iwl5000_hw_channel_switch(struct iwl_priv *priv, + struct ieee80211_channel_switch *ch_switch) +{ + /* + * MULTI-FIXME + * See iwlagn_mac_channel_switch. + */ + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + struct iwl5000_channel_switch_cmd cmd; + u32 switch_time_in_usec, ucode_switch_time; + u16 ch; + u32 tsf_low; + u8 switch_count; + u16 beacon_interval = le16_to_cpu(ctx->timing.beacon_interval); + struct ieee80211_vif *vif = ctx->vif; + struct iwl_host_cmd hcmd = { + .id = REPLY_CHANNEL_SWITCH, + .len = { sizeof(cmd), }, + .data = { &cmd, }, + }; + + cmd.band = priv->band == NL80211_BAND_2GHZ; + ch = ch_switch->chandef.chan->hw_value; + IWL_DEBUG_11H(priv, "channel switch from %d to %d\n", + ctx->active.channel, ch); + cmd.channel = cpu_to_le16(ch); + cmd.rxon_flags = ctx->staging.flags; + cmd.rxon_filter_flags = ctx->staging.filter_flags; + switch_count = ch_switch->count; + tsf_low = ch_switch->timestamp & 0x0ffffffff; + /* + * calculate the ucode channel switch time + * adding TSF as one of the factor for when to switch + */ + if ((priv->ucode_beacon_time > tsf_low) && beacon_interval) { + if (switch_count > ((priv->ucode_beacon_time - tsf_low) / + beacon_interval)) { + switch_count -= (priv->ucode_beacon_time - + tsf_low) / beacon_interval; + } else + switch_count = 0; + } + if (switch_count <= 1) + cmd.switch_time = cpu_to_le32(priv->ucode_beacon_time); + else { + switch_time_in_usec = + vif->bss_conf.beacon_int * switch_count * TIME_UNIT; + ucode_switch_time = iwl_usecs_to_beacons(priv, + switch_time_in_usec, + beacon_interval); + cmd.switch_time = iwl_add_beacon_time(priv, + priv->ucode_beacon_time, + ucode_switch_time, + beacon_interval); + } + IWL_DEBUG_11H(priv, "uCode time for the switch is 0x%x\n", + cmd.switch_time); + cmd.expect_beacon = + ch_switch->chandef.chan->flags & IEEE80211_CHAN_RADAR; + + return iwl_dvm_send_cmd(priv, &hcmd); +} + +const struct iwl_dvm_cfg iwl_dvm_5000_cfg = { + .set_hw_params = iwl5000_hw_set_hw_params, + .set_channel_switch = iwl5000_hw_channel_switch, + .temperature = iwlagn_temperature, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_LONG_THRESHOLD_DEF, + .chain_noise_scale = 1000, + .no_idle_support = true, +}; + +const struct iwl_dvm_cfg iwl_dvm_5150_cfg = { + .set_hw_params = iwl5150_hw_set_hw_params, + .set_channel_switch = iwl5000_hw_channel_switch, + .temperature = iwl5150_temperature, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_LONG_THRESHOLD_DEF, + .chain_noise_scale = 1000, + .no_idle_support = true, + .no_xtal_calib = true, +}; + + + +/* + * 6000 series + * =========== + */ + +static void iwl6000_set_ct_threshold(struct iwl_priv *priv) +{ + /* want Celsius */ + priv->hw_params.ct_kill_threshold = CT_KILL_THRESHOLD; + priv->hw_params.ct_kill_exit_threshold = CT_KILL_EXIT_THRESHOLD; +} + +/* NIC configuration for 6000 series */ +static void iwl6000_nic_config(struct iwl_priv *priv) +{ + switch (priv->trans->trans_cfg->device_family) { + case IWL_DEVICE_FAMILY_6005: + case IWL_DEVICE_FAMILY_6030: + case IWL_DEVICE_FAMILY_6000: + break; + case IWL_DEVICE_FAMILY_6000i: + /* 2x2 IPA phy type */ + iwl_write32(priv->trans, CSR_GP_DRIVER_REG, + CSR_GP_DRIVER_REG_BIT_RADIO_SKU_2x2_IPA); + break; + case IWL_DEVICE_FAMILY_6050: + /* Indicate calibration version to uCode. */ + if (priv->nvm_data->calib_version >= 6) + iwl_set_bit(priv->trans, CSR_GP_DRIVER_REG, + CSR_GP_DRIVER_REG_BIT_CALIB_VERSION6); + break; + case IWL_DEVICE_FAMILY_6150: + /* Indicate calibration version to uCode. */ + if (priv->nvm_data->calib_version >= 6) + iwl_set_bit(priv->trans, CSR_GP_DRIVER_REG, + CSR_GP_DRIVER_REG_BIT_CALIB_VERSION6); + iwl_set_bit(priv->trans, CSR_GP_DRIVER_REG, + CSR_GP_DRIVER_REG_BIT_6050_1x2); + break; + default: + WARN_ON(1); + } +} + +static const struct iwl_sensitivity_ranges iwl6000_sensitivity = { + .min_nrg_cck = 110, + .auto_corr_min_ofdm = 80, + .auto_corr_min_ofdm_mrc = 128, + .auto_corr_min_ofdm_x1 = 105, + .auto_corr_min_ofdm_mrc_x1 = 192, + + .auto_corr_max_ofdm = 145, + .auto_corr_max_ofdm_mrc = 232, + .auto_corr_max_ofdm_x1 = 110, + .auto_corr_max_ofdm_mrc_x1 = 232, + + .auto_corr_min_cck = 125, + .auto_corr_max_cck = 175, + .auto_corr_min_cck_mrc = 160, + .auto_corr_max_cck_mrc = 310, + .nrg_th_cck = 110, + .nrg_th_ofdm = 110, + + .barker_corr_th_min = 190, + .barker_corr_th_min_mrc = 336, + .nrg_th_cca = 62, +}; + +static void iwl6000_hw_set_hw_params(struct iwl_priv *priv) +{ + iwl6000_set_ct_threshold(priv); + + /* Set initial sensitivity parameters */ + priv->hw_params.sens = &iwl6000_sensitivity; + +} + +static int iwl6000_hw_channel_switch(struct iwl_priv *priv, + struct ieee80211_channel_switch *ch_switch) +{ + /* + * MULTI-FIXME + * See iwlagn_mac_channel_switch. + */ + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + struct iwl6000_channel_switch_cmd *cmd; + u32 switch_time_in_usec, ucode_switch_time; + u16 ch; + u32 tsf_low; + u8 switch_count; + u16 beacon_interval = le16_to_cpu(ctx->timing.beacon_interval); + struct ieee80211_vif *vif = ctx->vif; + struct iwl_host_cmd hcmd = { + .id = REPLY_CHANNEL_SWITCH, + .len = { sizeof(*cmd), }, + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + }; + int err; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + hcmd.data[0] = cmd; + + cmd->band = priv->band == NL80211_BAND_2GHZ; + ch = ch_switch->chandef.chan->hw_value; + IWL_DEBUG_11H(priv, "channel switch from %u to %u\n", + ctx->active.channel, ch); + cmd->channel = cpu_to_le16(ch); + cmd->rxon_flags = ctx->staging.flags; + cmd->rxon_filter_flags = ctx->staging.filter_flags; + switch_count = ch_switch->count; + tsf_low = ch_switch->timestamp & 0x0ffffffff; + /* + * calculate the ucode channel switch time + * adding TSF as one of the factor for when to switch + */ + if ((priv->ucode_beacon_time > tsf_low) && beacon_interval) { + if (switch_count > ((priv->ucode_beacon_time - tsf_low) / + beacon_interval)) { + switch_count -= (priv->ucode_beacon_time - + tsf_low) / beacon_interval; + } else + switch_count = 0; + } + if (switch_count <= 1) + cmd->switch_time = cpu_to_le32(priv->ucode_beacon_time); + else { + switch_time_in_usec = + vif->bss_conf.beacon_int * switch_count * TIME_UNIT; + ucode_switch_time = iwl_usecs_to_beacons(priv, + switch_time_in_usec, + beacon_interval); + cmd->switch_time = iwl_add_beacon_time(priv, + priv->ucode_beacon_time, + ucode_switch_time, + beacon_interval); + } + IWL_DEBUG_11H(priv, "uCode time for the switch is 0x%x\n", + cmd->switch_time); + cmd->expect_beacon = + ch_switch->chandef.chan->flags & IEEE80211_CHAN_RADAR; + + err = iwl_dvm_send_cmd(priv, &hcmd); + kfree(cmd); + return err; +} + +const struct iwl_dvm_cfg iwl_dvm_6000_cfg = { + .set_hw_params = iwl6000_hw_set_hw_params, + .set_channel_switch = iwl6000_hw_channel_switch, + .nic_config = iwl6000_nic_config, + .temperature = iwlagn_temperature, + .adv_thermal_throttle = true, + .support_ct_kill_exit = true, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF, + .chain_noise_scale = 1000, +}; + +const struct iwl_dvm_cfg iwl_dvm_6005_cfg = { + .set_hw_params = iwl6000_hw_set_hw_params, + .set_channel_switch = iwl6000_hw_channel_switch, + .nic_config = iwl6000_nic_config, + .temperature = iwlagn_temperature, + .adv_thermal_throttle = true, + .support_ct_kill_exit = true, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF, + .chain_noise_scale = 1000, + .need_temp_offset_calib = true, +}; + +const struct iwl_dvm_cfg iwl_dvm_6050_cfg = { + .set_hw_params = iwl6000_hw_set_hw_params, + .set_channel_switch = iwl6000_hw_channel_switch, + .nic_config = iwl6000_nic_config, + .temperature = iwlagn_temperature, + .adv_thermal_throttle = true, + .support_ct_kill_exit = true, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF, + .chain_noise_scale = 1500, +}; + +static const struct iwl_dvm_bt_params iwl6000_bt_params = { + /* Due to bluetooth, we transmit 2.4 GHz probes only on antenna A */ + .advanced_bt_coexist = true, + .agg_time_limit = BT_AGG_THRESHOLD_DEF, + .bt_init_traffic_load = IWL_BT_COEX_TRAFFIC_LOAD_NONE, + .bt_prio_boost = IWLAGN_BT_PRIO_BOOST_DEFAULT, + .bt_sco_disable = true, +}; + +const struct iwl_dvm_cfg iwl_dvm_6030_cfg = { + .set_hw_params = iwl6000_hw_set_hw_params, + .set_channel_switch = iwl6000_hw_channel_switch, + .nic_config = iwl6000_nic_config, + .temperature = iwlagn_temperature, + .adv_thermal_throttle = true, + .support_ct_kill_exit = true, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF, + .chain_noise_scale = 1000, + .bt_params = &iwl6000_bt_params, + .need_temp_offset_calib = true, + .adv_pm = true, +}; diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/led.c b/drivers/net/wireless/intel/iwlwifi/dvm/led.c new file mode 100644 index 000000000..71f67a019 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/led.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2003 - 2014 Intel Corporation. All rights reserved. + * Copyright (C) 2019 Intel Corporation + *****************************************************************************/ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <net/mac80211.h> +#include <linux/etherdevice.h> +#include <asm/unaligned.h> +#include "iwl-io.h" +#include "iwl-trans.h" +#include "iwl-modparams.h" +#include "dev.h" +#include "agn.h" + +/* Throughput OFF time(ms) ON time (ms) + * >300 25 25 + * >200 to 300 40 40 + * >100 to 200 55 55 + * >70 to 100 65 65 + * >50 to 70 75 75 + * >20 to 50 85 85 + * >10 to 20 95 95 + * >5 to 10 110 110 + * >1 to 5 130 130 + * >0 to 1 167 167 + * <=0 SOLID ON + */ +static const struct ieee80211_tpt_blink iwl_blink[] = { + { .throughput = 0, .blink_time = 334 }, + { .throughput = 1 * 1024 - 1, .blink_time = 260 }, + { .throughput = 5 * 1024 - 1, .blink_time = 220 }, + { .throughput = 10 * 1024 - 1, .blink_time = 190 }, + { .throughput = 20 * 1024 - 1, .blink_time = 170 }, + { .throughput = 50 * 1024 - 1, .blink_time = 150 }, + { .throughput = 70 * 1024 - 1, .blink_time = 130 }, + { .throughput = 100 * 1024 - 1, .blink_time = 110 }, + { .throughput = 200 * 1024 - 1, .blink_time = 80 }, + { .throughput = 300 * 1024 - 1, .blink_time = 50 }, +}; + +/* Set led register off */ +void iwlagn_led_enable(struct iwl_priv *priv) +{ + iwl_write32(priv->trans, CSR_LED_REG, CSR_LED_REG_TURN_ON); +} + +/* + * Adjust led blink rate to compensate on a MAC Clock difference on every HW + * Led blink rate analysis showed an average deviation of 20% on 5000 series + * and up. + * Need to compensate on the led on/off time per HW according to the deviation + * to achieve the desired led frequency + * The calculation is: (100-averageDeviation)/100 * blinkTime + * For code efficiency the calculation will be: + * compensation = (100 - averageDeviation) * 64 / 100 + * NewBlinkTime = (compensation * BlinkTime) / 64 + */ +static inline u8 iwl_blink_compensation(struct iwl_priv *priv, + u8 time, u16 compensation) +{ + if (!compensation) { + IWL_ERR(priv, "undefined blink compensation: " + "use pre-defined blinking time\n"); + return time; + } + + return (u8)((time * compensation) >> 6); +} + +static int iwl_send_led_cmd(struct iwl_priv *priv, struct iwl_led_cmd *led_cmd) +{ + struct iwl_host_cmd cmd = { + .id = REPLY_LEDS_CMD, + .len = { sizeof(struct iwl_led_cmd), }, + .data = { led_cmd, }, + .flags = CMD_ASYNC, + }; + u32 reg; + + reg = iwl_read32(priv->trans, CSR_LED_REG); + if (reg != (reg & CSR_LED_BSM_CTRL_MSK)) + iwl_write32(priv->trans, CSR_LED_REG, + reg & CSR_LED_BSM_CTRL_MSK); + + return iwl_dvm_send_cmd(priv, &cmd); +} + +/* Set led pattern command */ +static int iwl_led_cmd(struct iwl_priv *priv, + unsigned long on, + unsigned long off) +{ + struct iwl_led_cmd led_cmd = { + .id = IWL_LED_LINK, + .interval = IWL_DEF_LED_INTRVL + }; + int ret; + + if (!test_bit(STATUS_READY, &priv->status)) + return -EBUSY; + + if (priv->blink_on == on && priv->blink_off == off) + return 0; + + if (off == 0) { + /* led is SOLID_ON */ + on = IWL_LED_SOLID; + } + + led_cmd.on = iwl_blink_compensation(priv, on, + priv->trans->trans_cfg->base_params->led_compensation); + led_cmd.off = iwl_blink_compensation(priv, off, + priv->trans->trans_cfg->base_params->led_compensation); + + ret = iwl_send_led_cmd(priv, &led_cmd); + if (!ret) { + priv->blink_on = on; + priv->blink_off = off; + } + return ret; +} + +static void iwl_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct iwl_priv *priv = container_of(led_cdev, struct iwl_priv, led); + unsigned long on = 0; + unsigned long off = 0; + + if (brightness > 0) + on = IWL_LED_SOLID; + else + off = IWL_LED_SOLID; + + iwl_led_cmd(priv, on, off); +} + +static int iwl_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct iwl_priv *priv = container_of(led_cdev, struct iwl_priv, led); + + return iwl_led_cmd(priv, *delay_on, *delay_off); +} + +void iwl_leds_init(struct iwl_priv *priv) +{ + int mode = iwlwifi_mod_params.led_mode; + int ret; + + if (mode == IWL_LED_DISABLE) { + IWL_INFO(priv, "Led disabled\n"); + return; + } + if (mode == IWL_LED_DEFAULT) + mode = priv->cfg->led_mode; + + priv->led.name = kasprintf(GFP_KERNEL, "%s-led", + wiphy_name(priv->hw->wiphy)); + if (!priv->led.name) + return; + + priv->led.brightness_set = iwl_led_brightness_set; + priv->led.blink_set = iwl_led_blink_set; + priv->led.max_brightness = 1; + + switch (mode) { + case IWL_LED_DEFAULT: + WARN_ON(1); + break; + case IWL_LED_BLINK: + priv->led.default_trigger = + ieee80211_create_tpt_led_trigger(priv->hw, + IEEE80211_TPT_LEDTRIG_FL_CONNECTED, + iwl_blink, ARRAY_SIZE(iwl_blink)); + break; + case IWL_LED_RF_STATE: + priv->led.default_trigger = + ieee80211_get_radio_led_name(priv->hw); + break; + } + + ret = led_classdev_register(priv->trans->dev, &priv->led); + if (ret) { + kfree(priv->led.name); + return; + } + + priv->led_registered = true; +} + +void iwl_leds_exit(struct iwl_priv *priv) +{ + if (!priv->led_registered) + return; + + led_classdev_unregister(&priv->led); + kfree(priv->led.name); +} diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/led.h b/drivers/net/wireless/intel/iwlwifi/dvm/led.h new file mode 100644 index 000000000..5038fc378 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/led.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + * + * Copyright(c) 2003 - 2014 Intel Corporation. All rights reserved. + *****************************************************************************/ + +#ifndef __iwl_leds_h__ +#define __iwl_leds_h__ + + +struct iwl_priv; + +#define IWL_LED_SOLID 11 +#define IWL_DEF_LED_INTRVL cpu_to_le32(1000) + +#define IWL_LED_ACTIVITY (0<<1) +#define IWL_LED_LINK (1<<1) + +#ifdef CONFIG_IWLWIFI_LEDS +void iwlagn_led_enable(struct iwl_priv *priv); +void iwl_leds_init(struct iwl_priv *priv); +void iwl_leds_exit(struct iwl_priv *priv); +#else +static inline void iwlagn_led_enable(struct iwl_priv *priv) +{ +} +static inline void iwl_leds_init(struct iwl_priv *priv) +{ +} +static inline void iwl_leds_exit(struct iwl_priv *priv) +{ +} +#endif + +#endif /* __iwl_leds_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/lib.c b/drivers/net/wireless/intel/iwlwifi/dvm/lib.c new file mode 100644 index 000000000..1dc974e2c --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/lib.c @@ -0,0 +1,1258 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2008 - 2014, 2022 Intel Corporation. All rights reserved. + *****************************************************************************/ +#include <linux/etherdevice.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <net/mac80211.h> + +#include "iwl-io.h" +#include "iwl-agn-hw.h" +#include "iwl-trans.h" +#include "iwl-modparams.h" + +#include "dev.h" +#include "agn.h" + +int iwlagn_hw_valid_rtc_data_addr(u32 addr) +{ + return (addr >= IWLAGN_RTC_DATA_LOWER_BOUND) && + (addr < IWLAGN_RTC_DATA_UPPER_BOUND); +} + +int iwlagn_send_tx_power(struct iwl_priv *priv) +{ + struct iwlagn_tx_power_dbm_cmd tx_power_cmd; + u8 tx_ant_cfg_cmd; + + if (WARN_ONCE(test_bit(STATUS_SCAN_HW, &priv->status), + "TX Power requested while scanning!\n")) + return -EAGAIN; + + /* half dBm need to multiply */ + tx_power_cmd.global_lmt = (s8)(2 * priv->tx_power_user_lmt); + + if (tx_power_cmd.global_lmt > priv->nvm_data->max_tx_pwr_half_dbm) { + /* + * For the newer devices which using enhanced/extend tx power + * table in EEPROM, the format is in half dBm. driver need to + * convert to dBm format before report to mac80211. + * By doing so, there is a possibility of 1/2 dBm resolution + * lost. driver will perform "round-up" operation before + * reporting, but it will cause 1/2 dBm tx power over the + * regulatory limit. Perform the checking here, if the + * "tx_power_user_lmt" is higher than EEPROM value (in + * half-dBm format), lower the tx power based on EEPROM + */ + tx_power_cmd.global_lmt = + priv->nvm_data->max_tx_pwr_half_dbm; + } + tx_power_cmd.flags = IWLAGN_TX_POWER_NO_CLOSED; + tx_power_cmd.srv_chan_lmt = IWLAGN_TX_POWER_AUTO; + + if (IWL_UCODE_API(priv->fw->ucode_ver) == 1) + tx_ant_cfg_cmd = REPLY_TX_POWER_DBM_CMD_V1; + else + tx_ant_cfg_cmd = REPLY_TX_POWER_DBM_CMD; + + return iwl_dvm_send_cmd_pdu(priv, tx_ant_cfg_cmd, 0, + sizeof(tx_power_cmd), &tx_power_cmd); +} + +void iwlagn_temperature(struct iwl_priv *priv) +{ + lockdep_assert_held(&priv->statistics.lock); + + /* store temperature from correct statistics (in Celsius) */ + priv->temperature = le32_to_cpu(priv->statistics.common.temperature); + iwl_tt_handler(priv); +} + +int iwlagn_hwrate_to_mac80211_idx(u32 rate_n_flags, enum nl80211_band band) +{ + int idx = 0; + int band_offset = 0; + + /* HT rate format: mac80211 wants an MCS number, which is just LSB */ + if (rate_n_flags & RATE_MCS_HT_MSK) { + idx = (rate_n_flags & 0xff); + return idx; + /* Legacy rate format, search for match in table */ + } else { + if (band == NL80211_BAND_5GHZ) + band_offset = IWL_FIRST_OFDM_RATE; + for (idx = band_offset; idx < IWL_RATE_COUNT_LEGACY; idx++) + if (iwl_rates[idx].plcp == (rate_n_flags & 0xFF)) + return idx - band_offset; + } + + return -1; +} + +int iwlagn_manage_ibss_station(struct iwl_priv *priv, + struct ieee80211_vif *vif, bool add) +{ + struct iwl_vif_priv *vif_priv = (void *)vif->drv_priv; + + if (add) + return iwlagn_add_bssid_station(priv, vif_priv->ctx, + vif->bss_conf.bssid, + &vif_priv->ibss_bssid_sta_id); + return iwl_remove_station(priv, vif_priv->ibss_bssid_sta_id, + vif->bss_conf.bssid); +} + +/* + * iwlagn_txfifo_flush: send REPLY_TXFIFO_FLUSH command to uCode + * + * pre-requirements: + * 1. acquire mutex before calling + * 2. make sure rf is on and not in exit state + */ +int iwlagn_txfifo_flush(struct iwl_priv *priv, u32 scd_q_msk) +{ + struct iwl_txfifo_flush_cmd_v3 flush_cmd_v3 = { + .flush_control = cpu_to_le16(IWL_DROP_ALL), + }; + struct iwl_txfifo_flush_cmd_v2 flush_cmd_v2 = { + .flush_control = cpu_to_le16(IWL_DROP_ALL), + }; + + u32 queue_control = IWL_SCD_VO_MSK | IWL_SCD_VI_MSK | + IWL_SCD_BE_MSK | IWL_SCD_BK_MSK | IWL_SCD_MGMT_MSK; + + if ((priv->valid_contexts != BIT(IWL_RXON_CTX_BSS))) + queue_control |= IWL_PAN_SCD_VO_MSK | IWL_PAN_SCD_VI_MSK | + IWL_PAN_SCD_BE_MSK | IWL_PAN_SCD_BK_MSK | + IWL_PAN_SCD_MGMT_MSK | + IWL_PAN_SCD_MULTICAST_MSK; + + if (priv->nvm_data->sku_cap_11n_enable) + queue_control |= IWL_AGG_TX_QUEUE_MSK; + + if (scd_q_msk) + queue_control = scd_q_msk; + + IWL_DEBUG_INFO(priv, "queue control: 0x%x\n", queue_control); + flush_cmd_v3.queue_control = cpu_to_le32(queue_control); + flush_cmd_v2.queue_control = cpu_to_le16((u16)queue_control); + + if (IWL_UCODE_API(priv->fw->ucode_ver) > 2) + return iwl_dvm_send_cmd_pdu(priv, REPLY_TXFIFO_FLUSH, 0, + sizeof(flush_cmd_v3), + &flush_cmd_v3); + return iwl_dvm_send_cmd_pdu(priv, REPLY_TXFIFO_FLUSH, 0, + sizeof(flush_cmd_v2), &flush_cmd_v2); +} + +void iwlagn_dev_txfifo_flush(struct iwl_priv *priv) +{ + mutex_lock(&priv->mutex); + ieee80211_stop_queues(priv->hw); + if (iwlagn_txfifo_flush(priv, 0)) { + IWL_ERR(priv, "flush request fail\n"); + goto done; + } + IWL_DEBUG_INFO(priv, "wait transmit/flush all frames\n"); + iwl_trans_wait_tx_queues_empty(priv->trans, 0xffffffff); +done: + ieee80211_wake_queues(priv->hw); + mutex_unlock(&priv->mutex); +} + +/* + * BT coex + */ +/* Notmal TDM */ +static const __le32 iwlagn_def_3w_lookup[IWLAGN_BT_DECISION_LUT_SIZE] = { + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaeaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xcc00ff28), + cpu_to_le32(0x0000aaaa), + cpu_to_le32(0xcc00aaaa), + cpu_to_le32(0x0000aaaa), + cpu_to_le32(0xc0004000), + cpu_to_le32(0x00004000), + cpu_to_le32(0xf0005000), + cpu_to_le32(0xf0005000), +}; + +/* Full concurrency */ +static const __le32 iwlagn_concurrent_lookup[IWLAGN_BT_DECISION_LUT_SIZE] = { + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0x00000000), + cpu_to_le32(0x00000000), + cpu_to_le32(0x00000000), + cpu_to_le32(0x00000000), +}; + +void iwlagn_send_advance_bt_config(struct iwl_priv *priv) +{ + struct iwl_basic_bt_cmd basic = { + .max_kill = IWLAGN_BT_MAX_KILL_DEFAULT, + .bt3_timer_t7_value = IWLAGN_BT3_T7_DEFAULT, + .bt3_prio_sample_time = IWLAGN_BT3_PRIO_SAMPLE_DEFAULT, + .bt3_timer_t2_value = IWLAGN_BT3_T2_DEFAULT, + }; + struct iwl_bt_cmd_v1 bt_cmd_v1; + struct iwl_bt_cmd_v2 bt_cmd_v2; + int ret; + + BUILD_BUG_ON(sizeof(iwlagn_def_3w_lookup) != + sizeof(basic.bt3_lookup_table)); + + if (priv->lib->bt_params) { + /* + * newer generation of devices (2000 series and newer) + * use the version 2 of the bt command + * we need to make sure sending the host command + * with correct data structure to avoid uCode assert + */ + if (priv->lib->bt_params->bt_session_2) { + bt_cmd_v2.prio_boost = cpu_to_le32( + priv->lib->bt_params->bt_prio_boost); + bt_cmd_v2.tx_prio_boost = 0; + bt_cmd_v2.rx_prio_boost = 0; + } else { + /* older version only has 8 bits */ + WARN_ON(priv->lib->bt_params->bt_prio_boost & ~0xFF); + bt_cmd_v1.prio_boost = + priv->lib->bt_params->bt_prio_boost; + bt_cmd_v1.tx_prio_boost = 0; + bt_cmd_v1.rx_prio_boost = 0; + } + } else { + IWL_ERR(priv, "failed to construct BT Coex Config\n"); + return; + } + + /* + * Possible situations when BT needs to take over for receive, + * at the same time where STA needs to response to AP's frame(s), + * reduce the tx power of the required response frames, by that, + * allow the concurrent BT receive & WiFi transmit + * (BT - ANT A, WiFi -ANT B), without interference to one another + * + * Reduced tx power apply to control frames only (ACK/Back/CTS) + * when indicated by the BT config command + */ + basic.kill_ack_mask = priv->kill_ack_mask; + basic.kill_cts_mask = priv->kill_cts_mask; + if (priv->reduced_txpower) + basic.reduce_txpower = IWLAGN_BT_REDUCED_TX_PWR; + basic.valid = priv->bt_valid; + + /* + * Configure BT coex mode to "no coexistence" when the + * user disabled BT coexistence, we have no interface + * (might be in monitor mode), or the interface is in + * IBSS mode (no proper uCode support for coex then). + */ + if (!iwlwifi_mod_params.bt_coex_active || + priv->iw_mode == NL80211_IFTYPE_ADHOC) { + basic.flags = IWLAGN_BT_FLAG_COEX_MODE_DISABLED; + } else { + basic.flags = IWLAGN_BT_FLAG_COEX_MODE_3W << + IWLAGN_BT_FLAG_COEX_MODE_SHIFT; + + if (!priv->bt_enable_pspoll) + basic.flags |= IWLAGN_BT_FLAG_SYNC_2_BT_DISABLE; + else + basic.flags &= ~IWLAGN_BT_FLAG_SYNC_2_BT_DISABLE; + + if (priv->bt_ch_announce) + basic.flags |= IWLAGN_BT_FLAG_CHANNEL_INHIBITION; + IWL_DEBUG_COEX(priv, "BT coex flag: 0X%x\n", basic.flags); + } + priv->bt_enable_flag = basic.flags; + if (priv->bt_full_concurrent) + memcpy(basic.bt3_lookup_table, iwlagn_concurrent_lookup, + sizeof(iwlagn_concurrent_lookup)); + else + memcpy(basic.bt3_lookup_table, iwlagn_def_3w_lookup, + sizeof(iwlagn_def_3w_lookup)); + + IWL_DEBUG_COEX(priv, "BT coex %s in %s mode\n", + basic.flags ? "active" : "disabled", + priv->bt_full_concurrent ? + "full concurrency" : "3-wire"); + + if (priv->lib->bt_params->bt_session_2) { + memcpy(&bt_cmd_v2.basic, &basic, + sizeof(basic)); + ret = iwl_dvm_send_cmd_pdu(priv, REPLY_BT_CONFIG, + 0, sizeof(bt_cmd_v2), &bt_cmd_v2); + } else { + memcpy(&bt_cmd_v1.basic, &basic, + sizeof(basic)); + ret = iwl_dvm_send_cmd_pdu(priv, REPLY_BT_CONFIG, + 0, sizeof(bt_cmd_v1), &bt_cmd_v1); + } + if (ret) + IWL_ERR(priv, "failed to send BT Coex Config\n"); + +} + +void iwlagn_bt_adjust_rssi_monitor(struct iwl_priv *priv, bool rssi_ena) +{ + struct iwl_rxon_context *ctx, *found_ctx = NULL; + bool found_ap = false; + + lockdep_assert_held(&priv->mutex); + + /* Check whether AP or GO mode is active. */ + if (rssi_ena) { + for_each_context(priv, ctx) { + if (ctx->vif && ctx->vif->type == NL80211_IFTYPE_AP && + iwl_is_associated_ctx(ctx)) { + found_ap = true; + break; + } + } + } + + /* + * If disable was received or If GO/AP mode, disable RSSI + * measurements. + */ + if (!rssi_ena || found_ap) { + if (priv->cur_rssi_ctx) { + ctx = priv->cur_rssi_ctx; + ieee80211_disable_rssi_reports(ctx->vif); + priv->cur_rssi_ctx = NULL; + } + return; + } + + /* + * If rssi measurements need to be enabled, consider all cases now. + * Figure out how many contexts are active. + */ + for_each_context(priv, ctx) { + if (ctx->vif && ctx->vif->type == NL80211_IFTYPE_STATION && + iwl_is_associated_ctx(ctx)) { + found_ctx = ctx; + break; + } + } + + /* + * rssi monitor already enabled for the correct interface...nothing + * to do. + */ + if (found_ctx == priv->cur_rssi_ctx) + return; + + /* + * Figure out if rssi monitor is currently enabled, and needs + * to be changed. If rssi monitor is already enabled, disable + * it first else just enable rssi measurements on the + * interface found above. + */ + if (priv->cur_rssi_ctx) { + ctx = priv->cur_rssi_ctx; + if (ctx->vif) + ieee80211_disable_rssi_reports(ctx->vif); + } + + priv->cur_rssi_ctx = found_ctx; + + if (!found_ctx) + return; + + ieee80211_enable_rssi_reports(found_ctx->vif, + IWLAGN_BT_PSP_MIN_RSSI_THRESHOLD, + IWLAGN_BT_PSP_MAX_RSSI_THRESHOLD); +} + +static bool iwlagn_bt_traffic_is_sco(struct iwl_bt_uart_msg *uart_msg) +{ + return (BT_UART_MSG_FRAME3SCOESCO_MSK & uart_msg->frame3) >> + BT_UART_MSG_FRAME3SCOESCO_POS; +} + +static void iwlagn_bt_traffic_change_work(struct work_struct *work) +{ + struct iwl_priv *priv = + container_of(work, struct iwl_priv, bt_traffic_change_work); + struct iwl_rxon_context *ctx; + int smps_request = -1; + + if (priv->bt_enable_flag == IWLAGN_BT_FLAG_COEX_MODE_DISABLED) { + /* bt coex disabled */ + return; + } + + /* + * Note: bt_traffic_load can be overridden by scan complete and + * coex profile notifications. Ignore that since only bad consequence + * can be not matching debug print with actual state. + */ + IWL_DEBUG_COEX(priv, "BT traffic load changes: %d\n", + priv->bt_traffic_load); + + switch (priv->bt_traffic_load) { + case IWL_BT_COEX_TRAFFIC_LOAD_NONE: + if (priv->bt_status) + smps_request = IEEE80211_SMPS_DYNAMIC; + else + smps_request = IEEE80211_SMPS_AUTOMATIC; + break; + case IWL_BT_COEX_TRAFFIC_LOAD_LOW: + smps_request = IEEE80211_SMPS_DYNAMIC; + break; + case IWL_BT_COEX_TRAFFIC_LOAD_HIGH: + case IWL_BT_COEX_TRAFFIC_LOAD_CONTINUOUS: + smps_request = IEEE80211_SMPS_STATIC; + break; + default: + IWL_ERR(priv, "Invalid BT traffic load: %d\n", + priv->bt_traffic_load); + break; + } + + mutex_lock(&priv->mutex); + + /* + * We can not send command to firmware while scanning. When the scan + * complete we will schedule this work again. We do check with mutex + * locked to prevent new scan request to arrive. We do not check + * STATUS_SCANNING to avoid race when queue_work two times from + * different notifications, but quit and not perform any work at all. + */ + if (test_bit(STATUS_SCAN_HW, &priv->status)) + goto out; + + iwl_update_chain_flags(priv); + + if (smps_request != -1) { + priv->current_ht_config.smps = smps_request; + for_each_context(priv, ctx) { + if (ctx->vif && ctx->vif->type == NL80211_IFTYPE_STATION) + ieee80211_request_smps(ctx->vif, 0, smps_request); + } + } + + /* + * Dynamic PS poll related functionality. Adjust RSSI measurements if + * necessary. + */ + iwlagn_bt_coex_rssi_monitor(priv); +out: + mutex_unlock(&priv->mutex); +} + +/* + * If BT sco traffic, and RSSI monitor is enabled, move measurements to the + * correct interface or disable it if this is the last interface to be + * removed. + */ +void iwlagn_bt_coex_rssi_monitor(struct iwl_priv *priv) +{ + if (priv->bt_is_sco && + priv->bt_traffic_load == IWL_BT_COEX_TRAFFIC_LOAD_CONTINUOUS) + iwlagn_bt_adjust_rssi_monitor(priv, true); + else + iwlagn_bt_adjust_rssi_monitor(priv, false); +} + +static void iwlagn_print_uartmsg(struct iwl_priv *priv, + struct iwl_bt_uart_msg *uart_msg) +{ + IWL_DEBUG_COEX(priv, "Message Type = 0x%X, SSN = 0x%X, " + "Update Req = 0x%X\n", + (BT_UART_MSG_FRAME1MSGTYPE_MSK & uart_msg->frame1) >> + BT_UART_MSG_FRAME1MSGTYPE_POS, + (BT_UART_MSG_FRAME1SSN_MSK & uart_msg->frame1) >> + BT_UART_MSG_FRAME1SSN_POS, + (BT_UART_MSG_FRAME1UPDATEREQ_MSK & uart_msg->frame1) >> + BT_UART_MSG_FRAME1UPDATEREQ_POS); + + IWL_DEBUG_COEX(priv, "Open connections = 0x%X, Traffic load = 0x%X, " + "Chl_SeqN = 0x%X, In band = 0x%X\n", + (BT_UART_MSG_FRAME2OPENCONNECTIONS_MSK & uart_msg->frame2) >> + BT_UART_MSG_FRAME2OPENCONNECTIONS_POS, + (BT_UART_MSG_FRAME2TRAFFICLOAD_MSK & uart_msg->frame2) >> + BT_UART_MSG_FRAME2TRAFFICLOAD_POS, + (BT_UART_MSG_FRAME2CHLSEQN_MSK & uart_msg->frame2) >> + BT_UART_MSG_FRAME2CHLSEQN_POS, + (BT_UART_MSG_FRAME2INBAND_MSK & uart_msg->frame2) >> + BT_UART_MSG_FRAME2INBAND_POS); + + IWL_DEBUG_COEX(priv, "SCO/eSCO = 0x%X, Sniff = 0x%X, A2DP = 0x%X, " + "ACL = 0x%X, Master = 0x%X, OBEX = 0x%X\n", + (BT_UART_MSG_FRAME3SCOESCO_MSK & uart_msg->frame3) >> + BT_UART_MSG_FRAME3SCOESCO_POS, + (BT_UART_MSG_FRAME3SNIFF_MSK & uart_msg->frame3) >> + BT_UART_MSG_FRAME3SNIFF_POS, + (BT_UART_MSG_FRAME3A2DP_MSK & uart_msg->frame3) >> + BT_UART_MSG_FRAME3A2DP_POS, + (BT_UART_MSG_FRAME3ACL_MSK & uart_msg->frame3) >> + BT_UART_MSG_FRAME3ACL_POS, + (BT_UART_MSG_FRAME3MASTER_MSK & uart_msg->frame3) >> + BT_UART_MSG_FRAME3MASTER_POS, + (BT_UART_MSG_FRAME3OBEX_MSK & uart_msg->frame3) >> + BT_UART_MSG_FRAME3OBEX_POS); + + IWL_DEBUG_COEX(priv, "Idle duration = 0x%X\n", + (BT_UART_MSG_FRAME4IDLEDURATION_MSK & uart_msg->frame4) >> + BT_UART_MSG_FRAME4IDLEDURATION_POS); + + IWL_DEBUG_COEX(priv, "Tx Activity = 0x%X, Rx Activity = 0x%X, " + "eSCO Retransmissions = 0x%X\n", + (BT_UART_MSG_FRAME5TXACTIVITY_MSK & uart_msg->frame5) >> + BT_UART_MSG_FRAME5TXACTIVITY_POS, + (BT_UART_MSG_FRAME5RXACTIVITY_MSK & uart_msg->frame5) >> + BT_UART_MSG_FRAME5RXACTIVITY_POS, + (BT_UART_MSG_FRAME5ESCORETRANSMIT_MSK & uart_msg->frame5) >> + BT_UART_MSG_FRAME5ESCORETRANSMIT_POS); + + IWL_DEBUG_COEX(priv, "Sniff Interval = 0x%X, Discoverable = 0x%X\n", + (BT_UART_MSG_FRAME6SNIFFINTERVAL_MSK & uart_msg->frame6) >> + BT_UART_MSG_FRAME6SNIFFINTERVAL_POS, + (BT_UART_MSG_FRAME6DISCOVERABLE_MSK & uart_msg->frame6) >> + BT_UART_MSG_FRAME6DISCOVERABLE_POS); + + IWL_DEBUG_COEX(priv, "Sniff Activity = 0x%X, Page = " + "0x%X, Inquiry = 0x%X, Connectable = 0x%X\n", + (BT_UART_MSG_FRAME7SNIFFACTIVITY_MSK & uart_msg->frame7) >> + BT_UART_MSG_FRAME7SNIFFACTIVITY_POS, + (BT_UART_MSG_FRAME7PAGE_MSK & uart_msg->frame7) >> + BT_UART_MSG_FRAME7PAGE_POS, + (BT_UART_MSG_FRAME7INQUIRY_MSK & uart_msg->frame7) >> + BT_UART_MSG_FRAME7INQUIRY_POS, + (BT_UART_MSG_FRAME7CONNECTABLE_MSK & uart_msg->frame7) >> + BT_UART_MSG_FRAME7CONNECTABLE_POS); +} + +static bool iwlagn_set_kill_msk(struct iwl_priv *priv, + struct iwl_bt_uart_msg *uart_msg) +{ + bool need_update = false; + u8 kill_msk = IWL_BT_KILL_REDUCE; + static const __le32 bt_kill_ack_msg[3] = { + IWLAGN_BT_KILL_ACK_MASK_DEFAULT, + IWLAGN_BT_KILL_ACK_CTS_MASK_SCO, + IWLAGN_BT_KILL_ACK_CTS_MASK_REDUCE}; + static const __le32 bt_kill_cts_msg[3] = { + IWLAGN_BT_KILL_CTS_MASK_DEFAULT, + IWLAGN_BT_KILL_ACK_CTS_MASK_SCO, + IWLAGN_BT_KILL_ACK_CTS_MASK_REDUCE}; + + if (!priv->reduced_txpower) + kill_msk = (BT_UART_MSG_FRAME3SCOESCO_MSK & uart_msg->frame3) + ? IWL_BT_KILL_OVERRIDE : IWL_BT_KILL_DEFAULT; + if (priv->kill_ack_mask != bt_kill_ack_msg[kill_msk] || + priv->kill_cts_mask != bt_kill_cts_msg[kill_msk]) { + priv->bt_valid |= IWLAGN_BT_VALID_KILL_ACK_MASK; + priv->kill_ack_mask = bt_kill_ack_msg[kill_msk]; + priv->bt_valid |= IWLAGN_BT_VALID_KILL_CTS_MASK; + priv->kill_cts_mask = bt_kill_cts_msg[kill_msk]; + need_update = true; + } + return need_update; +} + +/* + * Upon RSSI changes, sends a bt config command with following changes + * 1. enable/disable "reduced control frames tx power + * 2. update the "kill)ack_mask" and "kill_cts_mask" + * + * If "reduced tx power" is enabled, uCode shall + * 1. ACK/Back/CTS rate shall reduced to 6Mbps + * 2. not use duplciate 20/40MHz mode + */ +static bool iwlagn_fill_txpower_mode(struct iwl_priv *priv, + struct iwl_bt_uart_msg *uart_msg) +{ + bool need_update = false; + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + int ave_rssi; + + if (!ctx->vif || (ctx->vif->type != NL80211_IFTYPE_STATION)) { + IWL_DEBUG_INFO(priv, "BSS ctx not active or not in sta mode\n"); + return false; + } + + ave_rssi = ieee80211_ave_rssi(ctx->vif); + if (!ave_rssi) { + /* no rssi data, no changes to reduce tx power */ + IWL_DEBUG_COEX(priv, "no rssi data available\n"); + return need_update; + } + if (!priv->reduced_txpower && + !iwl_is_associated(priv, IWL_RXON_CTX_PAN) && + (ave_rssi > BT_ENABLE_REDUCED_TXPOWER_THRESHOLD) && + (uart_msg->frame3 & (BT_UART_MSG_FRAME3ACL_MSK | + BT_UART_MSG_FRAME3OBEX_MSK)) && + !(uart_msg->frame3 & (BT_UART_MSG_FRAME3SCOESCO_MSK | + BT_UART_MSG_FRAME3SNIFF_MSK | BT_UART_MSG_FRAME3A2DP_MSK))) { + /* enabling reduced tx power */ + priv->reduced_txpower = true; + priv->bt_valid |= IWLAGN_BT_VALID_REDUCED_TX_PWR; + need_update = true; + } else if (priv->reduced_txpower && + (iwl_is_associated(priv, IWL_RXON_CTX_PAN) || + (ave_rssi < BT_DISABLE_REDUCED_TXPOWER_THRESHOLD) || + (uart_msg->frame3 & (BT_UART_MSG_FRAME3SCOESCO_MSK | + BT_UART_MSG_FRAME3SNIFF_MSK | BT_UART_MSG_FRAME3A2DP_MSK)) || + !(uart_msg->frame3 & (BT_UART_MSG_FRAME3ACL_MSK | + BT_UART_MSG_FRAME3OBEX_MSK)))) { + /* disable reduced tx power */ + priv->reduced_txpower = false; + priv->bt_valid |= IWLAGN_BT_VALID_REDUCED_TX_PWR; + need_update = true; + } + + return need_update; +} + +static void iwlagn_bt_coex_profile_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_bt_coex_profile_notif *coex = (void *)pkt->data; + struct iwl_bt_uart_msg *uart_msg = &coex->last_bt_uart_msg; + + if (priv->bt_enable_flag == IWLAGN_BT_FLAG_COEX_MODE_DISABLED) { + /* bt coex disabled */ + return; + } + + IWL_DEBUG_COEX(priv, "BT Coex notification:\n"); + IWL_DEBUG_COEX(priv, " status: %d\n", coex->bt_status); + IWL_DEBUG_COEX(priv, " traffic load: %d\n", coex->bt_traffic_load); + IWL_DEBUG_COEX(priv, " CI compliance: %d\n", + coex->bt_ci_compliance); + iwlagn_print_uartmsg(priv, uart_msg); + + priv->last_bt_traffic_load = priv->bt_traffic_load; + priv->bt_is_sco = iwlagn_bt_traffic_is_sco(uart_msg); + + if (priv->iw_mode != NL80211_IFTYPE_ADHOC) { + if (priv->bt_status != coex->bt_status || + priv->last_bt_traffic_load != coex->bt_traffic_load) { + if (coex->bt_status) { + /* BT on */ + if (!priv->bt_ch_announce) + priv->bt_traffic_load = + IWL_BT_COEX_TRAFFIC_LOAD_HIGH; + else + priv->bt_traffic_load = + coex->bt_traffic_load; + } else { + /* BT off */ + priv->bt_traffic_load = + IWL_BT_COEX_TRAFFIC_LOAD_NONE; + } + priv->bt_status = coex->bt_status; + queue_work(priv->workqueue, + &priv->bt_traffic_change_work); + } + } + + /* schedule to send runtime bt_config */ + /* check reduce power before change ack/cts kill mask */ + if (iwlagn_fill_txpower_mode(priv, uart_msg) || + iwlagn_set_kill_msk(priv, uart_msg)) + queue_work(priv->workqueue, &priv->bt_runtime_config); + + + /* FIXME: based on notification, adjust the prio_boost */ + + priv->bt_ci_compliance = coex->bt_ci_compliance; +} + +void iwlagn_bt_rx_handler_setup(struct iwl_priv *priv) +{ + priv->rx_handlers[REPLY_BT_COEX_PROFILE_NOTIF] = + iwlagn_bt_coex_profile_notif; +} + +void iwlagn_bt_setup_deferred_work(struct iwl_priv *priv) +{ + INIT_WORK(&priv->bt_traffic_change_work, + iwlagn_bt_traffic_change_work); +} + +void iwlagn_bt_cancel_deferred_work(struct iwl_priv *priv) +{ + cancel_work_sync(&priv->bt_traffic_change_work); +} + +static bool is_single_rx_stream(struct iwl_priv *priv) +{ + return priv->current_ht_config.smps == IEEE80211_SMPS_STATIC || + priv->current_ht_config.single_chain_sufficient; +} + +#define IWL_NUM_RX_CHAINS_MULTIPLE 3 +#define IWL_NUM_RX_CHAINS_SINGLE 2 +#define IWL_NUM_IDLE_CHAINS_DUAL 2 +#define IWL_NUM_IDLE_CHAINS_SINGLE 1 + +/* + * Determine how many receiver/antenna chains to use. + * + * More provides better reception via diversity. Fewer saves power + * at the expense of throughput, but only when not in powersave to + * start with. + * + * MIMO (dual stream) requires at least 2, but works better with 3. + * This does not determine *which* chains to use, just how many. + */ +static int iwl_get_active_rx_chain_count(struct iwl_priv *priv) +{ + if (priv->lib->bt_params && + priv->lib->bt_params->advanced_bt_coexist && + (priv->bt_full_concurrent || + priv->bt_traffic_load >= IWL_BT_COEX_TRAFFIC_LOAD_HIGH)) { + /* + * only use chain 'A' in bt high traffic load or + * full concurrency mode + */ + return IWL_NUM_RX_CHAINS_SINGLE; + } + /* # of Rx chains to use when expecting MIMO. */ + if (is_single_rx_stream(priv)) + return IWL_NUM_RX_CHAINS_SINGLE; + else + return IWL_NUM_RX_CHAINS_MULTIPLE; +} + +/* + * When we are in power saving mode, unless device support spatial + * multiplexing power save, use the active count for rx chain count. + */ +static int iwl_get_idle_rx_chain_count(struct iwl_priv *priv, int active_cnt) +{ + /* # Rx chains when idling, depending on SMPS mode */ + switch (priv->current_ht_config.smps) { + case IEEE80211_SMPS_STATIC: + case IEEE80211_SMPS_DYNAMIC: + return IWL_NUM_IDLE_CHAINS_SINGLE; + case IEEE80211_SMPS_AUTOMATIC: + case IEEE80211_SMPS_OFF: + return active_cnt; + default: + WARN(1, "invalid SMPS mode %d", + priv->current_ht_config.smps); + return active_cnt; + } +} + +/* up to 4 chains */ +static u8 iwl_count_chain_bitmap(u32 chain_bitmap) +{ + u8 res; + res = (chain_bitmap & BIT(0)) >> 0; + res += (chain_bitmap & BIT(1)) >> 1; + res += (chain_bitmap & BIT(2)) >> 2; + res += (chain_bitmap & BIT(3)) >> 3; + return res; +} + +/* + * iwlagn_set_rxon_chain - Set up Rx chain usage in "staging" RXON image + * + * Selects how many and which Rx receivers/antennas/chains to use. + * This should not be used for scan command ... it puts data in wrong place. + */ +void iwlagn_set_rxon_chain(struct iwl_priv *priv, struct iwl_rxon_context *ctx) +{ + bool is_single = is_single_rx_stream(priv); + bool is_cam = !test_bit(STATUS_POWER_PMI, &priv->status); + u8 idle_rx_cnt, active_rx_cnt, valid_rx_cnt; + u32 active_chains; + u16 rx_chain; + + /* Tell uCode which antennas are actually connected. + * Before first association, we assume all antennas are connected. + * Just after first association, iwl_chain_noise_calibration() + * checks which antennas actually *are* connected. */ + if (priv->chain_noise_data.active_chains) + active_chains = priv->chain_noise_data.active_chains; + else + active_chains = priv->nvm_data->valid_rx_ant; + + if (priv->lib->bt_params && + priv->lib->bt_params->advanced_bt_coexist && + (priv->bt_full_concurrent || + priv->bt_traffic_load >= IWL_BT_COEX_TRAFFIC_LOAD_HIGH)) { + /* + * only use chain 'A' in bt high traffic load or + * full concurrency mode + */ + active_chains = first_antenna(active_chains); + } + + rx_chain = active_chains << RXON_RX_CHAIN_VALID_POS; + + /* How many receivers should we use? */ + active_rx_cnt = iwl_get_active_rx_chain_count(priv); + idle_rx_cnt = iwl_get_idle_rx_chain_count(priv, active_rx_cnt); + + + /* correct rx chain count according hw settings + * and chain noise calibration + */ + valid_rx_cnt = iwl_count_chain_bitmap(active_chains); + if (valid_rx_cnt < active_rx_cnt) + active_rx_cnt = valid_rx_cnt; + + if (valid_rx_cnt < idle_rx_cnt) + idle_rx_cnt = valid_rx_cnt; + + rx_chain |= active_rx_cnt << RXON_RX_CHAIN_MIMO_CNT_POS; + rx_chain |= idle_rx_cnt << RXON_RX_CHAIN_CNT_POS; + + ctx->staging.rx_chain = cpu_to_le16(rx_chain); + + if (!is_single && (active_rx_cnt >= IWL_NUM_RX_CHAINS_SINGLE) && is_cam) + ctx->staging.rx_chain |= RXON_RX_CHAIN_MIMO_FORCE_MSK; + else + ctx->staging.rx_chain &= ~RXON_RX_CHAIN_MIMO_FORCE_MSK; + + IWL_DEBUG_ASSOC(priv, "rx_chain=0x%X active=%d idle=%d\n", + ctx->staging.rx_chain, + active_rx_cnt, idle_rx_cnt); + + WARN_ON(active_rx_cnt == 0 || idle_rx_cnt == 0 || + active_rx_cnt < idle_rx_cnt); +} + +u8 iwl_toggle_tx_ant(struct iwl_priv *priv, u8 ant, u8 valid) +{ + int i; + u8 ind = ant; + + if (priv->band == NL80211_BAND_2GHZ && + priv->bt_traffic_load >= IWL_BT_COEX_TRAFFIC_LOAD_HIGH) + return 0; + + for (i = 0; i < RATE_ANT_NUM - 1; i++) { + ind = (ind + 1) < RATE_ANT_NUM ? ind + 1 : 0; + if (valid & BIT(ind)) + return ind; + } + return ant; +} + +#ifdef CONFIG_PM_SLEEP +static void iwlagn_convert_p1k(u16 *p1k, __le16 *out) +{ + int i; + + for (i = 0; i < IWLAGN_P1K_SIZE; i++) + out[i] = cpu_to_le16(p1k[i]); +} + +struct wowlan_key_data { + struct iwl_rxon_context *ctx; + struct iwlagn_wowlan_rsc_tsc_params_cmd *rsc_tsc; + struct iwlagn_wowlan_tkip_params_cmd *tkip; + const u8 *bssid; + bool error, use_rsc_tsc, use_tkip; +}; + + +static void iwlagn_wowlan_program_keys(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, + void *_data) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct wowlan_key_data *data = _data; + struct iwl_rxon_context *ctx = data->ctx; + struct aes_sc *aes_sc, *aes_tx_sc = NULL; + struct tkip_sc *tkip_sc, *tkip_tx_sc = NULL; + struct iwlagn_p1k_cache *rx_p1ks; + u8 *rx_mic_key; + struct ieee80211_key_seq seq; + u32 cur_rx_iv32 = 0; + u16 p1k[IWLAGN_P1K_SIZE]; + int ret, i; + + mutex_lock(&priv->mutex); + + if ((key->cipher == WLAN_CIPHER_SUITE_WEP40 || + key->cipher == WLAN_CIPHER_SUITE_WEP104) && + !sta && !ctx->key_mapping_keys) + ret = iwl_set_default_wep_key(priv, ctx, key); + else + ret = iwl_set_dynamic_key(priv, ctx, key, sta); + + if (ret) { + IWL_ERR(priv, "Error setting key during suspend!\n"); + data->error = true; + } + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_TKIP: + if (sta) { + u64 pn64; + + tkip_sc = data->rsc_tsc->all_tsc_rsc.tkip.unicast_rsc; + tkip_tx_sc = &data->rsc_tsc->all_tsc_rsc.tkip.tsc; + + rx_p1ks = data->tkip->rx_uni; + + pn64 = atomic64_read(&key->tx_pn); + tkip_tx_sc->iv16 = cpu_to_le16(TKIP_PN_TO_IV16(pn64)); + tkip_tx_sc->iv32 = cpu_to_le32(TKIP_PN_TO_IV32(pn64)); + + ieee80211_get_tkip_p1k_iv(key, seq.tkip.iv32, p1k); + iwlagn_convert_p1k(p1k, data->tkip->tx.p1k); + + memcpy(data->tkip->mic_keys.tx, + &key->key[NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY], + IWLAGN_MIC_KEY_SIZE); + + rx_mic_key = data->tkip->mic_keys.rx_unicast; + } else { + tkip_sc = + data->rsc_tsc->all_tsc_rsc.tkip.multicast_rsc; + rx_p1ks = data->tkip->rx_multi; + rx_mic_key = data->tkip->mic_keys.rx_mcast; + } + + /* + * For non-QoS this relies on the fact that both the uCode and + * mac80211 use TID 0 (as they need to to avoid replay attacks) + * for checking the IV in the frames. + */ + for (i = 0; i < IWLAGN_NUM_RSC; i++) { + ieee80211_get_key_rx_seq(key, i, &seq); + tkip_sc[i].iv16 = cpu_to_le16(seq.tkip.iv16); + tkip_sc[i].iv32 = cpu_to_le32(seq.tkip.iv32); + /* wrapping isn't allowed, AP must rekey */ + if (seq.tkip.iv32 > cur_rx_iv32) + cur_rx_iv32 = seq.tkip.iv32; + } + + ieee80211_get_tkip_rx_p1k(key, data->bssid, cur_rx_iv32, p1k); + iwlagn_convert_p1k(p1k, rx_p1ks[0].p1k); + ieee80211_get_tkip_rx_p1k(key, data->bssid, + cur_rx_iv32 + 1, p1k); + iwlagn_convert_p1k(p1k, rx_p1ks[1].p1k); + + memcpy(rx_mic_key, + &key->key[NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY], + IWLAGN_MIC_KEY_SIZE); + + data->use_tkip = true; + data->use_rsc_tsc = true; + break; + case WLAN_CIPHER_SUITE_CCMP: + if (sta) { + u64 pn64; + + aes_sc = data->rsc_tsc->all_tsc_rsc.aes.unicast_rsc; + aes_tx_sc = &data->rsc_tsc->all_tsc_rsc.aes.tsc; + + pn64 = atomic64_read(&key->tx_pn); + aes_tx_sc->pn = cpu_to_le64(pn64); + } else + aes_sc = data->rsc_tsc->all_tsc_rsc.aes.multicast_rsc; + + /* + * For non-QoS this relies on the fact that both the uCode and + * mac80211 use TID 0 for checking the IV in the frames. + */ + for (i = 0; i < IWLAGN_NUM_RSC; i++) { + u8 *pn = seq.ccmp.pn; + + ieee80211_get_key_rx_seq(key, i, &seq); + aes_sc[i].pn = cpu_to_le64( + (u64)pn[5] | + ((u64)pn[4] << 8) | + ((u64)pn[3] << 16) | + ((u64)pn[2] << 24) | + ((u64)pn[1] << 32) | + ((u64)pn[0] << 40)); + } + data->use_rsc_tsc = true; + break; + } + + mutex_unlock(&priv->mutex); +} + +int iwlagn_send_patterns(struct iwl_priv *priv, + struct cfg80211_wowlan *wowlan) +{ + struct iwlagn_wowlan_patterns_cmd *pattern_cmd; + struct iwl_host_cmd cmd = { + .id = REPLY_WOWLAN_PATTERNS, + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + }; + int i, err; + + if (!wowlan->n_patterns) + return 0; + + cmd.len[0] = struct_size(pattern_cmd, patterns, wowlan->n_patterns); + + pattern_cmd = kmalloc(cmd.len[0], GFP_KERNEL); + if (!pattern_cmd) + return -ENOMEM; + + pattern_cmd->n_patterns = cpu_to_le32(wowlan->n_patterns); + + for (i = 0; i < wowlan->n_patterns; i++) { + int mask_len = DIV_ROUND_UP(wowlan->patterns[i].pattern_len, 8); + + memcpy(&pattern_cmd->patterns[i].mask, + wowlan->patterns[i].mask, mask_len); + memcpy(&pattern_cmd->patterns[i].pattern, + wowlan->patterns[i].pattern, + wowlan->patterns[i].pattern_len); + pattern_cmd->patterns[i].mask_size = mask_len; + pattern_cmd->patterns[i].pattern_size = + wowlan->patterns[i].pattern_len; + } + + cmd.data[0] = pattern_cmd; + err = iwl_dvm_send_cmd(priv, &cmd); + kfree(pattern_cmd); + return err; +} + +int iwlagn_suspend(struct iwl_priv *priv, struct cfg80211_wowlan *wowlan) +{ + struct iwlagn_wowlan_wakeup_filter_cmd wakeup_filter_cmd; + struct iwl_rxon_cmd rxon; + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + struct iwlagn_wowlan_kek_kck_material_cmd kek_kck_cmd; + struct iwlagn_wowlan_tkip_params_cmd tkip_cmd = {}; + struct iwlagn_d3_config_cmd d3_cfg_cmd = { + /* + * Program the minimum sleep time to 10 seconds, as many + * platforms have issues processing a wakeup signal while + * still being in the process of suspending. + */ + .min_sleep_time = cpu_to_le32(10 * 1000 * 1000), + }; + struct wowlan_key_data key_data = { + .ctx = ctx, + .bssid = ctx->active.bssid_addr, + .use_rsc_tsc = false, + .tkip = &tkip_cmd, + .use_tkip = false, + }; + int ret, i; + u16 seq; + + key_data.rsc_tsc = kzalloc(sizeof(*key_data.rsc_tsc), GFP_KERNEL); + if (!key_data.rsc_tsc) + return -ENOMEM; + + memset(&wakeup_filter_cmd, 0, sizeof(wakeup_filter_cmd)); + + /* + * We know the last used seqno, and the uCode expects to know that + * one, it will increment before TX. + */ + seq = le16_to_cpu(priv->last_seq_ctl) & IEEE80211_SCTL_SEQ; + wakeup_filter_cmd.non_qos_seq = cpu_to_le16(seq); + + /* + * For QoS counters, we store the one to use next, so subtract 0x10 + * since the uCode will add 0x10 before using the value. + */ + for (i = 0; i < IWL_MAX_TID_COUNT; i++) { + seq = priv->tid_data[IWL_AP_ID][i].seq_number; + seq -= 0x10; + wakeup_filter_cmd.qos_seq[i] = cpu_to_le16(seq); + } + + if (wowlan->disconnect) + wakeup_filter_cmd.enabled |= + cpu_to_le32(IWLAGN_WOWLAN_WAKEUP_BEACON_MISS | + IWLAGN_WOWLAN_WAKEUP_LINK_CHANGE); + if (wowlan->magic_pkt) + wakeup_filter_cmd.enabled |= + cpu_to_le32(IWLAGN_WOWLAN_WAKEUP_MAGIC_PACKET); + if (wowlan->gtk_rekey_failure) + wakeup_filter_cmd.enabled |= + cpu_to_le32(IWLAGN_WOWLAN_WAKEUP_GTK_REKEY_FAIL); + if (wowlan->eap_identity_req) + wakeup_filter_cmd.enabled |= + cpu_to_le32(IWLAGN_WOWLAN_WAKEUP_EAP_IDENT_REQ); + if (wowlan->four_way_handshake) + wakeup_filter_cmd.enabled |= + cpu_to_le32(IWLAGN_WOWLAN_WAKEUP_4WAY_HANDSHAKE); + if (wowlan->n_patterns) + wakeup_filter_cmd.enabled |= + cpu_to_le32(IWLAGN_WOWLAN_WAKEUP_PATTERN_MATCH); + + if (wowlan->rfkill_release) + d3_cfg_cmd.wakeup_flags |= + cpu_to_le32(IWLAGN_D3_WAKEUP_RFKILL); + + iwl_scan_cancel_timeout(priv, 200); + + memcpy(&rxon, &ctx->active, sizeof(rxon)); + + priv->ucode_loaded = false; + iwl_trans_stop_device(priv->trans); + ret = iwl_trans_start_hw(priv->trans); + if (ret) + goto out; + + priv->wowlan = true; + + ret = iwl_load_ucode_wait_alive(priv, IWL_UCODE_WOWLAN); + if (ret) + goto out; + + /* now configure WoWLAN ucode */ + ret = iwl_alive_start(priv); + if (ret) + goto out; + + memcpy(&ctx->staging, &rxon, sizeof(rxon)); + ret = iwlagn_commit_rxon(priv, ctx); + if (ret) + goto out; + + ret = iwl_power_update_mode(priv, true); + if (ret) + goto out; + + if (!iwlwifi_mod_params.swcrypto) { + /* mark all keys clear */ + priv->ucode_key_table = 0; + ctx->key_mapping_keys = 0; + + /* + * This needs to be unlocked due to lock ordering + * constraints. Since we're in the suspend path + * that isn't really a problem though. + */ + mutex_unlock(&priv->mutex); + ieee80211_iter_keys(priv->hw, ctx->vif, + iwlagn_wowlan_program_keys, + &key_data); + mutex_lock(&priv->mutex); + if (key_data.error) { + ret = -EIO; + goto out; + } + + if (key_data.use_rsc_tsc) { + struct iwl_host_cmd rsc_tsc_cmd = { + .id = REPLY_WOWLAN_TSC_RSC_PARAMS, + .data[0] = key_data.rsc_tsc, + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + .len[0] = sizeof(*key_data.rsc_tsc), + }; + + ret = iwl_dvm_send_cmd(priv, &rsc_tsc_cmd); + if (ret) + goto out; + } + + if (key_data.use_tkip) { + ret = iwl_dvm_send_cmd_pdu(priv, + REPLY_WOWLAN_TKIP_PARAMS, + 0, sizeof(tkip_cmd), + &tkip_cmd); + if (ret) + goto out; + } + + if (priv->have_rekey_data) { + memset(&kek_kck_cmd, 0, sizeof(kek_kck_cmd)); + memcpy(kek_kck_cmd.kck, priv->kck, NL80211_KCK_LEN); + kek_kck_cmd.kck_len = cpu_to_le16(NL80211_KCK_LEN); + memcpy(kek_kck_cmd.kek, priv->kek, NL80211_KEK_LEN); + kek_kck_cmd.kek_len = cpu_to_le16(NL80211_KEK_LEN); + kek_kck_cmd.replay_ctr = priv->replay_ctr; + + ret = iwl_dvm_send_cmd_pdu(priv, + REPLY_WOWLAN_KEK_KCK_MATERIAL, + 0, sizeof(kek_kck_cmd), + &kek_kck_cmd); + if (ret) + goto out; + } + } + + ret = iwl_dvm_send_cmd_pdu(priv, REPLY_D3_CONFIG, 0, + sizeof(d3_cfg_cmd), &d3_cfg_cmd); + if (ret) + goto out; + + ret = iwl_dvm_send_cmd_pdu(priv, REPLY_WOWLAN_WAKEUP_FILTER, + 0, sizeof(wakeup_filter_cmd), + &wakeup_filter_cmd); + if (ret) + goto out; + + ret = iwlagn_send_patterns(priv, wowlan); + out: + kfree(key_data.rsc_tsc); + return ret; +} +#endif + +int iwl_dvm_send_cmd(struct iwl_priv *priv, struct iwl_host_cmd *cmd) +{ + if (iwl_is_rfkill(priv) || iwl_is_ctkill(priv)) { + IWL_WARN(priv, "Not sending command - %s KILL\n", + iwl_is_rfkill(priv) ? "RF" : "CT"); + return -EIO; + } + + if (test_bit(STATUS_FW_ERROR, &priv->status)) { + IWL_ERR(priv, "Command %s failed: FW Error\n", + iwl_get_cmd_string(priv->trans, cmd->id)); + return -EIO; + } + + /* + * This can happen upon FW ASSERT: we clear the STATUS_FW_ERROR flag + * in iwl_down but cancel the workers only later. + */ + if (!priv->ucode_loaded) { + IWL_ERR(priv, "Fw not loaded - dropping CMD: %x\n", cmd->id); + return -EIO; + } + + /* + * Synchronous commands from this op-mode must hold + * the mutex, this ensures we don't try to send two + * (or more) synchronous commands at a time. + */ + if (!(cmd->flags & CMD_ASYNC)) + lockdep_assert_held(&priv->mutex); + + return iwl_trans_send_cmd(priv->trans, cmd); +} + +int iwl_dvm_send_cmd_pdu(struct iwl_priv *priv, u8 id, + u32 flags, u16 len, const void *data) +{ + struct iwl_host_cmd cmd = { + .id = id, + .len = { len, }, + .data = { data, }, + .flags = flags, + }; + + return iwl_dvm_send_cmd(priv, &cmd); +} diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/mac80211.c b/drivers/net/wireless/intel/iwlwifi/dvm/mac80211.c new file mode 100644 index 000000000..f4070fddc --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/mac80211.c @@ -0,0 +1,1622 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2003 - 2014 Intel Corporation. All rights reserved. + * Copyright (C) 2018 - 2019, 2022 Intel Corporation + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + *****************************************************************************/ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/if_arp.h> + +#include <net/ieee80211_radiotap.h> +#include <net/mac80211.h> + +#include <asm/div64.h> + +#include "iwl-io.h" +#include "iwl-trans.h" +#include "iwl-op-mode.h" +#include "iwl-modparams.h" + +#include "dev.h" +#include "calib.h" +#include "agn.h" + +/***************************************************************************** + * + * mac80211 entry point functions + * + *****************************************************************************/ + +static const struct ieee80211_iface_limit iwlagn_sta_ap_limits[] = { + { + .max = 1, + .types = BIT(NL80211_IFTYPE_STATION), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_AP), + }, +}; + +static const struct ieee80211_iface_limit iwlagn_2sta_limits[] = { + { + .max = 2, + .types = BIT(NL80211_IFTYPE_STATION), + }, +}; + +static const struct ieee80211_iface_combination +iwlagn_iface_combinations_dualmode[] = { + { .num_different_channels = 1, + .max_interfaces = 2, + .beacon_int_infra_match = true, + .limits = iwlagn_sta_ap_limits, + .n_limits = ARRAY_SIZE(iwlagn_sta_ap_limits), + }, + { .num_different_channels = 1, + .max_interfaces = 2, + .limits = iwlagn_2sta_limits, + .n_limits = ARRAY_SIZE(iwlagn_2sta_limits), + }, +}; + +/* + * Not a mac80211 entry point function, but it fits in with all the + * other mac80211 functions grouped here. + */ +int iwlagn_mac_setup_register(struct iwl_priv *priv, + const struct iwl_ucode_capabilities *capa) +{ + int ret; + struct ieee80211_hw *hw = priv->hw; + struct iwl_rxon_context *ctx; + + hw->rate_control_algorithm = "iwl-agn-rs"; + + /* Tell mac80211 our characteristics */ + ieee80211_hw_set(hw, SIGNAL_DBM); + ieee80211_hw_set(hw, AMPDU_AGGREGATION); + ieee80211_hw_set(hw, NEED_DTIM_BEFORE_ASSOC); + ieee80211_hw_set(hw, SPECTRUM_MGMT); + ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS); + ieee80211_hw_set(hw, QUEUE_CONTROL); + ieee80211_hw_set(hw, SUPPORTS_PS); + ieee80211_hw_set(hw, SUPPORTS_DYNAMIC_PS); + ieee80211_hw_set(hw, SUPPORT_FAST_XMIT); + ieee80211_hw_set(hw, WANT_MONITOR_VIF); + + if (priv->trans->max_skb_frags) + hw->netdev_features = NETIF_F_HIGHDMA | NETIF_F_SG; + + hw->offchannel_tx_hw_queue = IWL_AUX_QUEUE; + hw->radiotap_mcs_details |= IEEE80211_RADIOTAP_MCS_HAVE_FMT; + + /* + * Including the following line will crash some AP's. This + * workaround removes the stimulus which causes the crash until + * the AP software can be fixed. + hw->max_tx_aggregation_subframes = LINK_QUAL_AGG_FRAME_LIMIT_DEF; + */ + + if (priv->nvm_data->sku_cap_11n_enable) + hw->wiphy->features |= NL80211_FEATURE_DYNAMIC_SMPS | + NL80211_FEATURE_STATIC_SMPS; + + /* + * Enable 11w if advertised by firmware and software crypto + * is not enabled (as the firmware will interpret some mgmt + * packets, so enabling it with software crypto isn't safe) + */ + if (priv->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_MFP && + !iwlwifi_mod_params.swcrypto) + ieee80211_hw_set(hw, MFP_CAPABLE); + + hw->sta_data_size = sizeof(struct iwl_station_priv); + hw->vif_data_size = sizeof(struct iwl_vif_priv); + + for_each_context(priv, ctx) { + hw->wiphy->interface_modes |= ctx->interface_modes; + hw->wiphy->interface_modes |= ctx->exclusive_interface_modes; + } + + BUILD_BUG_ON(NUM_IWL_RXON_CTX != 2); + + if (hw->wiphy->interface_modes & BIT(NL80211_IFTYPE_AP)) { + hw->wiphy->iface_combinations = + iwlagn_iface_combinations_dualmode; + hw->wiphy->n_iface_combinations = + ARRAY_SIZE(iwlagn_iface_combinations_dualmode); + } + + hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN; + hw->wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG | + REGULATORY_DISABLE_BEACON_HINTS; + +#ifdef CONFIG_PM_SLEEP + if (priv->fw->img[IWL_UCODE_WOWLAN].num_sec && + priv->trans->ops->d3_suspend && + priv->trans->ops->d3_resume && + device_can_wakeup(priv->trans->dev)) { + priv->wowlan_support.flags = WIPHY_WOWLAN_MAGIC_PKT | + WIPHY_WOWLAN_DISCONNECT | + WIPHY_WOWLAN_EAP_IDENTITY_REQ | + WIPHY_WOWLAN_RFKILL_RELEASE; + if (!iwlwifi_mod_params.swcrypto) + priv->wowlan_support.flags |= + WIPHY_WOWLAN_SUPPORTS_GTK_REKEY | + WIPHY_WOWLAN_GTK_REKEY_FAILURE; + + priv->wowlan_support.n_patterns = IWLAGN_WOWLAN_MAX_PATTERNS; + priv->wowlan_support.pattern_min_len = + IWLAGN_WOWLAN_MIN_PATTERN_LEN; + priv->wowlan_support.pattern_max_len = + IWLAGN_WOWLAN_MAX_PATTERN_LEN; + hw->wiphy->wowlan = &priv->wowlan_support; + } +#endif + + if (iwlwifi_mod_params.power_save) + hw->wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT; + else + hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + + hw->wiphy->max_scan_ssids = PROBE_OPTION_MAX; + /* we create the 802.11 header and a max-length SSID element */ + hw->wiphy->max_scan_ie_len = capa->max_probe_length - 24 - 34; + + /* + * We don't use all queues: 4 and 9 are unused and any + * aggregation queue gets mapped down to the AC queue. + */ + hw->queues = IWLAGN_FIRST_AMPDU_QUEUE; + + hw->max_listen_interval = IWL_CONN_MAX_LISTEN_INTERVAL; + + if (priv->nvm_data->bands[NL80211_BAND_2GHZ].n_channels) + priv->hw->wiphy->bands[NL80211_BAND_2GHZ] = + &priv->nvm_data->bands[NL80211_BAND_2GHZ]; + if (priv->nvm_data->bands[NL80211_BAND_5GHZ].n_channels) + priv->hw->wiphy->bands[NL80211_BAND_5GHZ] = + &priv->nvm_data->bands[NL80211_BAND_5GHZ]; + + hw->wiphy->hw_version = priv->trans->hw_id; + + iwl_leds_init(priv); + + wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST); + wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_EXT_KEY_ID); + + ret = ieee80211_register_hw(priv->hw); + if (ret) { + IWL_ERR(priv, "Failed to register hw (error %d)\n", ret); + iwl_leds_exit(priv); + return ret; + } + priv->mac80211_registered = 1; + + return 0; +} + +void iwlagn_mac_unregister(struct iwl_priv *priv) +{ + if (!priv->mac80211_registered) + return; + iwl_leds_exit(priv); + ieee80211_unregister_hw(priv->hw); + priv->mac80211_registered = 0; +} + +static int __iwl_up(struct iwl_priv *priv) +{ + struct iwl_rxon_context *ctx; + int ret; + + lockdep_assert_held(&priv->mutex); + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) { + IWL_WARN(priv, "Exit pending; will not bring the NIC up\n"); + return -EIO; + } + + for_each_context(priv, ctx) { + ret = iwlagn_alloc_bcast_station(priv, ctx); + if (ret) { + iwl_dealloc_bcast_stations(priv); + return ret; + } + } + + ret = iwl_trans_start_hw(priv->trans); + if (ret) { + IWL_ERR(priv, "Failed to start HW: %d\n", ret); + goto error; + } + + ret = iwl_run_init_ucode(priv); + if (ret) { + IWL_ERR(priv, "Failed to run INIT ucode: %d\n", ret); + goto error; + } + + ret = iwl_trans_start_hw(priv->trans); + if (ret) { + IWL_ERR(priv, "Failed to start HW: %d\n", ret); + goto error; + } + + ret = iwl_load_ucode_wait_alive(priv, IWL_UCODE_REGULAR); + if (ret) { + IWL_ERR(priv, "Failed to start RT ucode: %d\n", ret); + goto error; + } + + ret = iwl_alive_start(priv); + if (ret) + goto error; + return 0; + + error: + set_bit(STATUS_EXIT_PENDING, &priv->status); + iwl_down(priv); + clear_bit(STATUS_EXIT_PENDING, &priv->status); + + IWL_ERR(priv, "Unable to initialize device.\n"); + return ret; +} + +static int iwlagn_mac_start(struct ieee80211_hw *hw) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + int ret; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + /* we should be verifying the device is ready to be opened */ + mutex_lock(&priv->mutex); + ret = __iwl_up(priv); + mutex_unlock(&priv->mutex); + if (ret) + return ret; + + IWL_DEBUG_INFO(priv, "Start UP work done.\n"); + + /* Now we should be done, and the READY bit should be set. */ + if (WARN_ON(!test_bit(STATUS_READY, &priv->status))) + ret = -EIO; + + iwlagn_led_enable(priv); + + priv->is_open = 1; + IWL_DEBUG_MAC80211(priv, "leave\n"); + return ret; +} + +static void iwlagn_mac_stop(struct ieee80211_hw *hw) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + if (!priv->is_open) + return; + + priv->is_open = 0; + + mutex_lock(&priv->mutex); + iwl_down(priv); + mutex_unlock(&priv->mutex); + + iwl_cancel_deferred_work(priv); + + flush_workqueue(priv->workqueue); + + IWL_DEBUG_MAC80211(priv, "leave\n"); +} + +static void iwlagn_mac_set_rekey_data(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_gtk_rekey_data *data) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + if (iwlwifi_mod_params.swcrypto) + return; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + mutex_lock(&priv->mutex); + + if (priv->contexts[IWL_RXON_CTX_BSS].vif != vif) + goto out; + + memcpy(priv->kek, data->kek, NL80211_KEK_LEN); + memcpy(priv->kck, data->kck, NL80211_KCK_LEN); + priv->replay_ctr = + cpu_to_le64(be64_to_cpup((__be64 *)&data->replay_ctr)); + priv->have_rekey_data = true; + + out: + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); +} + +#ifdef CONFIG_PM_SLEEP + +static int iwlagn_mac_suspend(struct ieee80211_hw *hw, + struct cfg80211_wowlan *wowlan) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + int ret; + + if (WARN_ON(!wowlan)) + return -EINVAL; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + mutex_lock(&priv->mutex); + + /* Don't attempt WoWLAN when not associated, tear down instead. */ + if (!ctx->vif || ctx->vif->type != NL80211_IFTYPE_STATION || + !iwl_is_associated_ctx(ctx)) { + ret = 1; + goto out; + } + + ret = iwlagn_suspend(priv, wowlan); + if (ret) + goto error; + + /* let the ucode operate on its own */ + iwl_write32(priv->trans, CSR_UCODE_DRV_GP1_SET, + CSR_UCODE_DRV_GP1_BIT_D3_CFG_COMPLETE); + + iwl_trans_d3_suspend(priv->trans, false, true); + + goto out; + + error: + priv->wowlan = false; + iwlagn_prepare_restart(priv); + ieee80211_restart_hw(priv->hw); + out: + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); + + return ret; +} + +struct iwl_resume_data { + struct iwl_priv *priv; + struct iwlagn_wowlan_status *cmd; + bool valid; +}; + +static bool iwl_resume_status_fn(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_resume_data *resume_data = data; + struct iwl_priv *priv = resume_data->priv; + + if (iwl_rx_packet_payload_len(pkt) != sizeof(*resume_data->cmd)) { + IWL_ERR(priv, "rx wrong size data\n"); + return true; + } + memcpy(resume_data->cmd, pkt->data, sizeof(*resume_data->cmd)); + resume_data->valid = true; + + return true; +} + +static int iwlagn_mac_resume(struct ieee80211_hw *hw) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + struct ieee80211_vif *vif; + u32 base; + int ret; + enum iwl_d3_status d3_status; + struct error_table_start { + /* cf. struct iwl_error_event_table */ + u32 valid; + u32 error_id; + } err_info; + struct iwl_notification_wait status_wait; + static const u16 status_cmd[] = { + REPLY_WOWLAN_GET_STATUS, + }; + struct iwlagn_wowlan_status status_data = {}; + struct iwl_resume_data resume_data = { + .priv = priv, + .cmd = &status_data, + .valid = false, + }; + struct cfg80211_wowlan_wakeup wakeup = { + .pattern_idx = -1, + }; +#ifdef CONFIG_IWLWIFI_DEBUGFS + const struct fw_img *img; +#endif + + IWL_DEBUG_MAC80211(priv, "enter\n"); + mutex_lock(&priv->mutex); + + /* we'll clear ctx->vif during iwlagn_prepare_restart() */ + vif = ctx->vif; + + ret = iwl_trans_d3_resume(priv->trans, &d3_status, false, true); + if (ret) + goto out_unlock; + + if (d3_status != IWL_D3_STATUS_ALIVE) { + IWL_INFO(priv, "Device was reset during suspend\n"); + goto out_unlock; + } + + /* uCode is no longer operating by itself */ + iwl_write32(priv->trans, CSR_UCODE_DRV_GP1_CLR, + CSR_UCODE_DRV_GP1_BIT_D3_CFG_COMPLETE); + + base = priv->device_pointers.error_event_table; + if (!iwlagn_hw_valid_rtc_data_addr(base)) { + IWL_WARN(priv, "Invalid error table during resume!\n"); + goto out_unlock; + } + + iwl_trans_read_mem_bytes(priv->trans, base, + &err_info, sizeof(err_info)); + + if (err_info.valid) { + IWL_INFO(priv, "error table is valid (%d, 0x%x)\n", + err_info.valid, err_info.error_id); + if (err_info.error_id == RF_KILL_INDICATOR_FOR_WOWLAN) { + wakeup.rfkill_release = true; + ieee80211_report_wowlan_wakeup(vif, &wakeup, + GFP_KERNEL); + } + goto out_unlock; + } + +#ifdef CONFIG_IWLWIFI_DEBUGFS + img = &priv->fw->img[IWL_UCODE_WOWLAN]; + if (!priv->wowlan_sram) + priv->wowlan_sram = + kzalloc(img->sec[IWL_UCODE_SECTION_DATA].len, + GFP_KERNEL); + + if (priv->wowlan_sram) + iwl_trans_read_mem(priv->trans, 0x800000, + priv->wowlan_sram, + img->sec[IWL_UCODE_SECTION_DATA].len / 4); +#endif + + /* + * This is very strange. The GET_STATUS command is sent but the device + * doesn't reply properly, it seems it doesn't close the RBD so one is + * always left open ... As a result, we need to send another command + * and have to reset the driver afterwards. As we need to switch to + * runtime firmware again that'll happen. + */ + + iwl_init_notification_wait(&priv->notif_wait, &status_wait, status_cmd, + ARRAY_SIZE(status_cmd), iwl_resume_status_fn, + &resume_data); + + iwl_dvm_send_cmd_pdu(priv, REPLY_WOWLAN_GET_STATUS, CMD_ASYNC, 0, NULL); + iwl_dvm_send_cmd_pdu(priv, REPLY_ECHO, CMD_ASYNC, 0, NULL); + /* an RBD is left open in the firmware now! */ + + ret = iwl_wait_notification(&priv->notif_wait, &status_wait, HZ/5); + if (ret) + goto out_unlock; + + if (resume_data.valid && priv->contexts[IWL_RXON_CTX_BSS].vif) { + u32 reasons = le32_to_cpu(status_data.wakeup_reason); + struct cfg80211_wowlan_wakeup *wakeup_report; + + IWL_INFO(priv, "WoWLAN wakeup reason(s): 0x%.8x\n", reasons); + + if (reasons) { + if (reasons & IWLAGN_WOWLAN_WAKEUP_MAGIC_PACKET) + wakeup.magic_pkt = true; + if (reasons & IWLAGN_WOWLAN_WAKEUP_PATTERN_MATCH) + wakeup.pattern_idx = status_data.pattern_number; + if (reasons & (IWLAGN_WOWLAN_WAKEUP_BEACON_MISS | + IWLAGN_WOWLAN_WAKEUP_LINK_CHANGE)) + wakeup.disconnect = true; + if (reasons & IWLAGN_WOWLAN_WAKEUP_GTK_REKEY_FAIL) + wakeup.gtk_rekey_failure = true; + if (reasons & IWLAGN_WOWLAN_WAKEUP_EAP_IDENT_REQ) + wakeup.eap_identity_req = true; + if (reasons & IWLAGN_WOWLAN_WAKEUP_4WAY_HANDSHAKE) + wakeup.four_way_handshake = true; + wakeup_report = &wakeup; + } else { + wakeup_report = NULL; + } + + ieee80211_report_wowlan_wakeup(vif, wakeup_report, GFP_KERNEL); + } + + priv->wowlan = false; + + iwlagn_prepare_restart(priv); + + memset((void *)&ctx->active, 0, sizeof(ctx->active)); + iwl_connection_init_rx_config(priv, ctx); + iwlagn_set_rxon_chain(priv, ctx); + + out_unlock: + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); + + ieee80211_resume_disconnect(vif); + + return 1; +} + +static void iwlagn_mac_set_wakeup(struct ieee80211_hw *hw, bool enabled) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + device_set_wakeup_enable(priv->trans->dev, enabled); +} +#endif + +static void iwlagn_mac_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + if (iwlagn_tx_skb(priv, control->sta, skb)) + ieee80211_free_txskb(hw, skb); +} + +static void iwlagn_mac_update_tkip_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, + u32 iv32, u16 *phase1key) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + iwl_update_tkip_key(priv, vif, keyconf, sta, iv32, phase1key); +} + +static int iwlagn_mac_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_vif_priv *vif_priv = (void *)vif->drv_priv; + struct iwl_rxon_context *ctx = vif_priv->ctx; + int ret; + bool is_default_wep_key = false; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + if (iwlwifi_mod_params.swcrypto) { + IWL_DEBUG_MAC80211(priv, "leave - hwcrypto disabled\n"); + return -EOPNOTSUPP; + } + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_TKIP: + key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC; + fallthrough; + case WLAN_CIPHER_SUITE_CCMP: + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + break; + default: + break; + } + + /* + * We could program these keys into the hardware as well, but we + * don't expect much multicast traffic in IBSS and having keys + * for more stations is probably more useful. + * + * Mark key TX-only and return 0. + */ + if (vif->type == NL80211_IFTYPE_ADHOC && + !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) { + key->hw_key_idx = WEP_INVALID_OFFSET; + return 0; + } + + /* If they key was TX-only, accept deletion */ + if (cmd == DISABLE_KEY && key->hw_key_idx == WEP_INVALID_OFFSET) + return 0; + + mutex_lock(&priv->mutex); + iwl_scan_cancel_timeout(priv, 100); + + BUILD_BUG_ON(WEP_INVALID_OFFSET == IWLAGN_HW_KEY_DEFAULT); + + /* + * If we are getting WEP group key and we didn't receive any key mapping + * so far, we are in legacy wep mode (group key only), otherwise we are + * in 1X mode. + * In legacy wep mode, we use another host command to the uCode. + */ + if ((key->cipher == WLAN_CIPHER_SUITE_WEP40 || + key->cipher == WLAN_CIPHER_SUITE_WEP104) && !sta) { + if (cmd == SET_KEY) + is_default_wep_key = !ctx->key_mapping_keys; + else + is_default_wep_key = + key->hw_key_idx == IWLAGN_HW_KEY_DEFAULT; + } + + + switch (cmd) { + case SET_KEY: + if (is_default_wep_key) { + ret = iwl_set_default_wep_key(priv, vif_priv->ctx, key); + break; + } + ret = iwl_set_dynamic_key(priv, vif_priv->ctx, key, sta); + if (ret) { + /* + * can't add key for RX, but we don't need it + * in the device for TX so still return 0 + */ + ret = 0; + key->hw_key_idx = WEP_INVALID_OFFSET; + } + + IWL_DEBUG_MAC80211(priv, "enable hwcrypto key\n"); + break; + case DISABLE_KEY: + if (is_default_wep_key) + ret = iwl_remove_default_wep_key(priv, ctx, key); + else + ret = iwl_remove_dynamic_key(priv, ctx, key, sta); + + IWL_DEBUG_MAC80211(priv, "disable hwcrypto key\n"); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); + + return ret; +} + +static int iwlagn_mac_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_ampdu_params *params) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + int ret = -EINVAL; + struct ieee80211_sta *sta = params->sta; + enum ieee80211_ampdu_mlme_action action = params->action; + u16 tid = params->tid; + u16 *ssn = ¶ms->ssn; + u8 buf_size = params->buf_size; + struct iwl_station_priv *sta_priv = (void *) sta->drv_priv; + + IWL_DEBUG_HT(priv, "A-MPDU action on addr %pM tid %d\n", + sta->addr, tid); + + if (!(priv->nvm_data->sku_cap_11n_enable)) + return -EACCES; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + mutex_lock(&priv->mutex); + + switch (action) { + case IEEE80211_AMPDU_RX_START: + if (!iwl_enable_rx_ampdu()) + break; + IWL_DEBUG_HT(priv, "start Rx\n"); + ret = iwl_sta_rx_agg_start(priv, sta, tid, *ssn); + break; + case IEEE80211_AMPDU_RX_STOP: + IWL_DEBUG_HT(priv, "stop Rx\n"); + ret = iwl_sta_rx_agg_stop(priv, sta, tid); + break; + case IEEE80211_AMPDU_TX_START: + if (!priv->trans->ops->txq_enable) + break; + if (!iwl_enable_tx_ampdu()) + break; + IWL_DEBUG_HT(priv, "start Tx\n"); + ret = iwlagn_tx_agg_start(priv, vif, sta, tid, ssn); + break; + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + IWL_DEBUG_HT(priv, "Flush Tx\n"); + ret = iwlagn_tx_agg_flush(priv, vif, sta, tid); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + IWL_DEBUG_HT(priv, "stop Tx\n"); + ret = iwlagn_tx_agg_stop(priv, vif, sta, tid); + if ((ret == 0) && (priv->agg_tids_count > 0)) { + priv->agg_tids_count--; + IWL_DEBUG_HT(priv, "priv->agg_tids_count = %u\n", + priv->agg_tids_count); + } + if (!priv->agg_tids_count && + priv->hw_params.use_rts_for_aggregation) { + /* + * switch off RTS/CTS if it was previously enabled + */ + sta_priv->lq_sta.lq.general_params.flags &= + ~LINK_QUAL_FLAGS_SET_STA_TLC_RTS_MSK; + iwl_send_lq_cmd(priv, iwl_rxon_ctx_from_vif(vif), + &sta_priv->lq_sta.lq, CMD_ASYNC, false); + } + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + ret = iwlagn_tx_agg_oper(priv, vif, sta, tid, buf_size); + break; + } + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); + return ret; +} + +static int iwlagn_mac_sta_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_station_priv *sta_priv = (void *)sta->drv_priv; + struct iwl_vif_priv *vif_priv = (void *)vif->drv_priv; + bool is_ap = vif->type == NL80211_IFTYPE_STATION; + int ret; + u8 sta_id; + + IWL_DEBUG_INFO(priv, "proceeding to add station %pM\n", + sta->addr); + sta_priv->sta_id = IWL_INVALID_STATION; + + atomic_set(&sta_priv->pending_frames, 0); + if (vif->type == NL80211_IFTYPE_AP) + sta_priv->client = true; + + ret = iwl_add_station_common(priv, vif_priv->ctx, sta->addr, + is_ap, sta, &sta_id); + if (ret) { + IWL_ERR(priv, "Unable to add station %pM (%d)\n", + sta->addr, ret); + /* Should we return success if return code is EEXIST ? */ + return ret; + } + + sta_priv->sta_id = sta_id; + + return 0; +} + +static int iwlagn_mac_sta_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_station_priv *sta_priv = (void *)sta->drv_priv; + int ret; + + IWL_DEBUG_INFO(priv, "proceeding to remove station %pM\n", sta->addr); + + if (vif->type == NL80211_IFTYPE_STATION) { + /* + * Station will be removed from device when the RXON + * is set to unassociated -- just deactivate it here + * to avoid re-programming it. + */ + ret = 0; + iwl_deactivate_station(priv, sta_priv->sta_id, sta->addr); + } else { + ret = iwl_remove_station(priv, sta_priv->sta_id, sta->addr); + if (ret) + IWL_DEBUG_QUIET_RFKILL(priv, + "Error removing station %pM\n", sta->addr); + } + return ret; +} + +static int iwlagn_mac_sta_state(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + enum ieee80211_sta_state old_state, + enum ieee80211_sta_state new_state) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_vif_priv *vif_priv = (void *)vif->drv_priv; + enum { + NONE, ADD, REMOVE, HT_RATE_INIT, ADD_RATE_INIT, + } op = NONE; + int ret; + + IWL_DEBUG_MAC80211(priv, "station %pM state change %d->%d\n", + sta->addr, old_state, new_state); + + mutex_lock(&priv->mutex); + if (vif->type == NL80211_IFTYPE_STATION) { + if (old_state == IEEE80211_STA_NOTEXIST && + new_state == IEEE80211_STA_NONE) + op = ADD; + else if (old_state == IEEE80211_STA_NONE && + new_state == IEEE80211_STA_NOTEXIST) + op = REMOVE; + else if (old_state == IEEE80211_STA_AUTH && + new_state == IEEE80211_STA_ASSOC) + op = HT_RATE_INIT; + } else { + if (old_state == IEEE80211_STA_AUTH && + new_state == IEEE80211_STA_ASSOC) + op = ADD_RATE_INIT; + else if (old_state == IEEE80211_STA_ASSOC && + new_state == IEEE80211_STA_AUTH) + op = REMOVE; + } + + switch (op) { + case ADD: + ret = iwlagn_mac_sta_add(hw, vif, sta); + if (ret) + break; + /* + * Clear the in-progress flag, the AP station entry was added + * but we'll initialize LQ only when we've associated (which + * would also clear the in-progress flag). This is necessary + * in case we never initialize LQ because association fails. + */ + spin_lock_bh(&priv->sta_lock); + priv->stations[iwl_sta_id(sta)].used &= + ~IWL_STA_UCODE_INPROGRESS; + spin_unlock_bh(&priv->sta_lock); + break; + case REMOVE: + ret = iwlagn_mac_sta_remove(hw, vif, sta); + break; + case ADD_RATE_INIT: + ret = iwlagn_mac_sta_add(hw, vif, sta); + if (ret) + break; + /* Initialize rate scaling */ + IWL_DEBUG_INFO(priv, + "Initializing rate scaling for station %pM\n", + sta->addr); + iwl_rs_rate_init(priv, sta, iwl_sta_id(sta)); + ret = 0; + break; + case HT_RATE_INIT: + /* Initialize rate scaling */ + ret = iwl_sta_update_ht(priv, vif_priv->ctx, sta); + if (ret) + break; + IWL_DEBUG_INFO(priv, + "Initializing rate scaling for station %pM\n", + sta->addr); + iwl_rs_rate_init(priv, sta, iwl_sta_id(sta)); + ret = 0; + break; + default: + ret = 0; + break; + } + + /* + * mac80211 might WARN if we fail, but due the way we + * (badly) handle hard rfkill, we might fail here + */ + if (iwl_is_rfkill(priv)) + ret = 0; + + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); + + return ret; +} + +static void iwlagn_mac_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel_switch *ch_switch) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct ieee80211_conf *conf = &hw->conf; + struct ieee80211_channel *channel = ch_switch->chandef.chan; + struct iwl_ht_config *ht_conf = &priv->current_ht_config; + /* + * MULTI-FIXME + * When we add support for multiple interfaces, we need to + * revisit this. The channel switch command in the device + * only affects the BSS context, but what does that really + * mean? And what if we get a CSA on the second interface? + * This needs a lot of work. + */ + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + u16 ch; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + mutex_lock(&priv->mutex); + + if (iwl_is_rfkill(priv)) + goto out; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status) || + test_bit(STATUS_SCANNING, &priv->status) || + test_bit(STATUS_CHANNEL_SWITCH_PENDING, &priv->status)) + goto out; + + if (!iwl_is_associated_ctx(ctx)) + goto out; + + if (!priv->lib->set_channel_switch) + goto out; + + ch = channel->hw_value; + if (le16_to_cpu(ctx->active.channel) == ch) + goto out; + + priv->current_ht_config.smps = conf->smps_mode; + + /* Configure HT40 channels */ + switch (cfg80211_get_chandef_type(&ch_switch->chandef)) { + case NL80211_CHAN_NO_HT: + case NL80211_CHAN_HT20: + ctx->ht.is_40mhz = false; + ctx->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE; + break; + case NL80211_CHAN_HT40MINUS: + ctx->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW; + ctx->ht.is_40mhz = true; + break; + case NL80211_CHAN_HT40PLUS: + ctx->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + ctx->ht.is_40mhz = true; + break; + } + + if ((le16_to_cpu(ctx->staging.channel) != ch)) + ctx->staging.flags = 0; + + iwl_set_rxon_channel(priv, channel, ctx); + iwl_set_rxon_ht(priv, ht_conf); + iwl_set_flags_for_band(priv, ctx, channel->band, ctx->vif); + + /* + * at this point, staging_rxon has the + * configuration for channel switch + */ + set_bit(STATUS_CHANNEL_SWITCH_PENDING, &priv->status); + priv->switch_channel = cpu_to_le16(ch); + if (priv->lib->set_channel_switch(priv, ch_switch)) { + clear_bit(STATUS_CHANNEL_SWITCH_PENDING, &priv->status); + priv->switch_channel = 0; + ieee80211_chswitch_done(ctx->vif, false); + } + +out: + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); +} + +void iwl_chswitch_done(struct iwl_priv *priv, bool is_success) +{ + /* + * MULTI-FIXME + * See iwlagn_mac_channel_switch. + */ + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + if (!test_and_clear_bit(STATUS_CHANNEL_SWITCH_PENDING, &priv->status)) + return; + + if (ctx->vif) + ieee80211_chswitch_done(ctx->vif, is_success); +} + +static void iwlagn_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + __le32 filter_or = 0, filter_nand = 0; + struct iwl_rxon_context *ctx; + +#define CHK(test, flag) do { \ + if (*total_flags & (test)) \ + filter_or |= (flag); \ + else \ + filter_nand |= (flag); \ + } while (0) + + IWL_DEBUG_MAC80211(priv, "Enter: changed: 0x%x, total: 0x%x\n", + changed_flags, *total_flags); + + CHK(FIF_OTHER_BSS, RXON_FILTER_PROMISC_MSK); + /* Setting _just_ RXON_FILTER_CTL2HOST_MSK causes FH errors */ + CHK(FIF_CONTROL, RXON_FILTER_CTL2HOST_MSK | RXON_FILTER_PROMISC_MSK); + CHK(FIF_BCN_PRBRESP_PROMISC, RXON_FILTER_BCON_AWARE_MSK); + +#undef CHK + + mutex_lock(&priv->mutex); + + for_each_context(priv, ctx) { + ctx->staging.filter_flags &= ~filter_nand; + ctx->staging.filter_flags |= filter_or; + + /* + * Not committing directly because hardware can perform a scan, + * but we'll eventually commit the filter flags change anyway. + */ + } + + mutex_unlock(&priv->mutex); + + /* + * Receiving all multicast frames is always enabled by the + * default flags setup in iwl_connection_init_rx_config() + * since we currently do not support programming multicast + * filters into the device. + */ + *total_flags &= FIF_OTHER_BSS | FIF_ALLMULTI | + FIF_BCN_PRBRESP_PROMISC | FIF_CONTROL; +} + +static void iwlagn_mac_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + u32 queues, bool drop) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + u32 scd_queues; + + mutex_lock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "enter\n"); + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) { + IWL_DEBUG_TX(priv, "Aborting flush due to device shutdown\n"); + goto done; + } + if (iwl_is_rfkill(priv)) { + IWL_DEBUG_TX(priv, "Aborting flush due to RF Kill\n"); + goto done; + } + + scd_queues = BIT(priv->trans->trans_cfg->base_params->num_of_queues) - 1; + scd_queues &= ~(BIT(IWL_IPAN_CMD_QUEUE_NUM) | + BIT(IWL_DEFAULT_CMD_QUEUE_NUM)); + + if (drop) { + IWL_DEBUG_TX_QUEUES(priv, "Flushing SCD queues: 0x%x\n", + scd_queues); + if (iwlagn_txfifo_flush(priv, scd_queues)) { + IWL_ERR(priv, "flush request fail\n"); + goto done; + } + } + + IWL_DEBUG_TX_QUEUES(priv, "wait transmit/flush all frames\n"); + iwl_trans_wait_tx_queues_empty(priv->trans, scd_queues); +done: + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); +} + +static void iwlagn_mac_event_callback(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const struct ieee80211_event *event) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + if (event->type != RSSI_EVENT) + return; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + if (priv->lib->bt_params && + priv->lib->bt_params->advanced_bt_coexist) { + if (event->u.rssi.data == RSSI_EVENT_LOW) + priv->bt_enable_pspoll = true; + else if (event->u.rssi.data == RSSI_EVENT_HIGH) + priv->bt_enable_pspoll = false; + + queue_work(priv->workqueue, &priv->bt_runtime_config); + } else { + IWL_DEBUG_MAC80211(priv, "Advanced BT coex disabled," + "ignoring RSSI callback\n"); + } + + IWL_DEBUG_MAC80211(priv, "leave\n"); +} + +static int iwlagn_mac_set_tim(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, bool set) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + queue_work(priv->workqueue, &priv->beacon_update); + + return 0; +} + +static int iwlagn_mac_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + unsigned int link_id, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_vif_priv *vif_priv = (void *)vif->drv_priv; + struct iwl_rxon_context *ctx = vif_priv->ctx; + int q; + + if (WARN_ON(!ctx)) + return -EINVAL; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + if (!iwl_is_ready_rf(priv)) { + IWL_DEBUG_MAC80211(priv, "leave - RF not ready\n"); + return -EIO; + } + + if (queue >= AC_NUM) { + IWL_DEBUG_MAC80211(priv, "leave - queue >= AC_NUM %d\n", queue); + return 0; + } + + q = AC_NUM - 1 - queue; + + mutex_lock(&priv->mutex); + + ctx->qos_data.def_qos_parm.ac[q].cw_min = + cpu_to_le16(params->cw_min); + ctx->qos_data.def_qos_parm.ac[q].cw_max = + cpu_to_le16(params->cw_max); + ctx->qos_data.def_qos_parm.ac[q].aifsn = params->aifs; + ctx->qos_data.def_qos_parm.ac[q].edca_txop = + cpu_to_le16((params->txop * 32)); + + ctx->qos_data.def_qos_parm.ac[q].reserved1 = 0; + + mutex_unlock(&priv->mutex); + + IWL_DEBUG_MAC80211(priv, "leave\n"); + return 0; +} + +static int iwlagn_mac_tx_last_beacon(struct ieee80211_hw *hw) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + return priv->ibss_manager == IWL_IBSS_MANAGER; +} + +static int iwl_set_mode(struct iwl_priv *priv, struct iwl_rxon_context *ctx) +{ + iwl_connection_init_rx_config(priv, ctx); + + iwlagn_set_rxon_chain(priv, ctx); + + return iwlagn_commit_rxon(priv, ctx); +} + +static int iwl_setup_interface(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + struct ieee80211_vif *vif = ctx->vif; + int err, ac; + + lockdep_assert_held(&priv->mutex); + + /* + * This variable will be correct only when there's just + * a single context, but all code using it is for hardware + * that supports only one context. + */ + priv->iw_mode = vif->type; + + ctx->is_active = true; + + err = iwl_set_mode(priv, ctx); + if (err) { + if (!ctx->always_active) + ctx->is_active = false; + return err; + } + + if (priv->lib->bt_params && priv->lib->bt_params->advanced_bt_coexist && + vif->type == NL80211_IFTYPE_ADHOC) { + /* + * pretend to have high BT traffic as long as we + * are operating in IBSS mode, as this will cause + * the rate scaling etc. to behave as intended. + */ + priv->bt_traffic_load = IWL_BT_COEX_TRAFFIC_LOAD_HIGH; + } + + /* set up queue mappings */ + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) + vif->hw_queue[ac] = ctx->ac_to_queue[ac]; + + if (vif->type == NL80211_IFTYPE_AP) + vif->cab_queue = ctx->mcast_queue; + else + vif->cab_queue = IEEE80211_INVAL_HW_QUEUE; + + return 0; +} + +static int iwlagn_mac_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_vif_priv *vif_priv = (void *)vif->drv_priv; + struct iwl_rxon_context *tmp, *ctx = NULL; + int err; + enum nl80211_iftype viftype = ieee80211_vif_type_p2p(vif); + bool reset = false; + + IWL_DEBUG_MAC80211(priv, "enter: type %d, addr %pM\n", + viftype, vif->addr); + + mutex_lock(&priv->mutex); + + if (!iwl_is_ready_rf(priv)) { + IWL_WARN(priv, "Try to add interface when device not ready\n"); + err = -EINVAL; + goto out; + } + + for_each_context(priv, tmp) { + u32 possible_modes = + tmp->interface_modes | tmp->exclusive_interface_modes; + + if (tmp->vif) { + /* On reset we need to add the same interface again */ + if (tmp->vif == vif) { + reset = true; + ctx = tmp; + break; + } + + /* check if this busy context is exclusive */ + if (tmp->exclusive_interface_modes & + BIT(tmp->vif->type)) { + err = -EINVAL; + goto out; + } + continue; + } + + if (!(possible_modes & BIT(viftype))) + continue; + + /* have maybe usable context w/o interface */ + ctx = tmp; + break; + } + + if (!ctx) { + err = -EOPNOTSUPP; + goto out; + } + + vif_priv->ctx = ctx; + ctx->vif = vif; + + /* + * In SNIFFER device type, the firmware reports the FCS to + * the host, rather than snipping it off. Unfortunately, + * mac80211 doesn't (yet) provide a per-packet flag for + * this, so that we have to set the hardware flag based + * on the interfaces added. As the monitor interface can + * only be present by itself, and will be removed before + * other interfaces are added, this is safe. + */ + if (vif->type == NL80211_IFTYPE_MONITOR) + ieee80211_hw_set(priv->hw, RX_INCLUDES_FCS); + else + __clear_bit(IEEE80211_HW_RX_INCLUDES_FCS, priv->hw->flags); + + err = iwl_setup_interface(priv, ctx); + if (!err || reset) + goto out; + + ctx->vif = NULL; + priv->iw_mode = NL80211_IFTYPE_STATION; + out: + mutex_unlock(&priv->mutex); + + IWL_DEBUG_MAC80211(priv, "leave\n"); + return err; +} + +static void iwl_teardown_interface(struct iwl_priv *priv, + struct ieee80211_vif *vif, + bool mode_change) +{ + struct iwl_rxon_context *ctx = iwl_rxon_ctx_from_vif(vif); + + lockdep_assert_held(&priv->mutex); + + if (priv->scan_vif == vif) { + iwl_scan_cancel_timeout(priv, 200); + iwl_force_scan_end(priv); + } + + if (!mode_change) { + iwl_set_mode(priv, ctx); + if (!ctx->always_active) + ctx->is_active = false; + } + + /* + * When removing the IBSS interface, overwrite the + * BT traffic load with the stored one from the last + * notification, if any. If this is a device that + * doesn't implement this, this has no effect since + * both values are the same and zero. + */ + if (vif->type == NL80211_IFTYPE_ADHOC) + priv->bt_traffic_load = priv->last_bt_traffic_load; +} + +static void iwlagn_mac_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_rxon_context *ctx = iwl_rxon_ctx_from_vif(vif); + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + mutex_lock(&priv->mutex); + + WARN_ON(ctx->vif != vif); + ctx->vif = NULL; + + iwl_teardown_interface(priv, vif, false); + + mutex_unlock(&priv->mutex); + + IWL_DEBUG_MAC80211(priv, "leave\n"); + +} + +static int iwlagn_mac_change_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum nl80211_iftype newtype, bool newp2p) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_rxon_context *ctx, *tmp; + enum nl80211_iftype newviftype = newtype; + u32 interface_modes; + int err; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + newtype = ieee80211_iftype_p2p(newtype, newp2p); + + mutex_lock(&priv->mutex); + + ctx = iwl_rxon_ctx_from_vif(vif); + + /* + * To simplify this code, only support changes on the + * BSS context. The PAN context is usually reassigned + * by creating/removing P2P interfaces anyway. + */ + if (ctx->ctxid != IWL_RXON_CTX_BSS) { + err = -EBUSY; + goto out; + } + + if (!ctx->vif || !iwl_is_ready_rf(priv)) { + /* + * Huh? But wait ... this can maybe happen when + * we're in the middle of a firmware restart! + */ + err = -EBUSY; + goto out; + } + + /* Check if the switch is supported in the same context */ + interface_modes = ctx->interface_modes | ctx->exclusive_interface_modes; + if (!(interface_modes & BIT(newtype))) { + err = -EBUSY; + goto out; + } + + if (ctx->exclusive_interface_modes & BIT(newtype)) { + for_each_context(priv, tmp) { + if (ctx == tmp) + continue; + + if (!tmp->is_active) + continue; + + /* + * The current mode switch would be exclusive, but + * another context is active ... refuse the switch. + */ + err = -EBUSY; + goto out; + } + } + + /* success */ + iwl_teardown_interface(priv, vif, true); + vif->type = newviftype; + vif->p2p = newp2p; + err = iwl_setup_interface(priv, ctx); + WARN_ON(err); + /* + * We've switched internally, but submitting to the + * device may have failed for some reason. Mask this + * error, because otherwise mac80211 will not switch + * (and set the interface type back) and we'll be + * out of sync with it. + */ + err = 0; + + out: + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); + + return err; +} + +static int iwlagn_mac_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_scan_request *hw_req) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct cfg80211_scan_request *req = &hw_req->req; + int ret; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + if (req->n_channels == 0) + return -EINVAL; + + mutex_lock(&priv->mutex); + + /* + * If an internal scan is in progress, just set + * up the scan_request as per above. + */ + if (priv->scan_type != IWL_SCAN_NORMAL) { + IWL_DEBUG_SCAN(priv, + "SCAN request during internal scan - defer\n"); + priv->scan_request = req; + priv->scan_vif = vif; + ret = 0; + } else { + priv->scan_request = req; + priv->scan_vif = vif; + /* + * mac80211 will only ask for one band at a time + * so using channels[0] here is ok + */ + ret = iwl_scan_initiate(priv, vif, IWL_SCAN_NORMAL, + req->channels[0]->band); + if (ret) { + priv->scan_request = NULL; + priv->scan_vif = NULL; + } + } + + IWL_DEBUG_MAC80211(priv, "leave\n"); + + mutex_unlock(&priv->mutex); + + return ret; +} + +static void iwl_sta_modify_ps_wake(struct iwl_priv *priv, int sta_id) +{ + struct iwl_addsta_cmd cmd = { + .mode = STA_CONTROL_MODIFY_MSK, + .station_flags_msk = STA_FLG_PWR_SAVE_MSK, + .sta.sta_id = sta_id, + }; + + iwl_send_add_sta(priv, &cmd, CMD_ASYNC); +} + +static void iwlagn_mac_sta_notify(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum sta_notify_cmd cmd, + struct ieee80211_sta *sta) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_station_priv *sta_priv = (void *)sta->drv_priv; + int sta_id; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + switch (cmd) { + case STA_NOTIFY_SLEEP: + WARN_ON(!sta_priv->client); + sta_priv->asleep = true; + if (atomic_read(&sta_priv->pending_frames) > 0) + ieee80211_sta_block_awake(hw, sta, true); + break; + case STA_NOTIFY_AWAKE: + WARN_ON(!sta_priv->client); + if (!sta_priv->asleep) + break; + sta_priv->asleep = false; + sta_id = iwl_sta_id(sta); + if (sta_id != IWL_INVALID_STATION) + iwl_sta_modify_ps_wake(priv, sta_id); + break; + default: + break; + } + IWL_DEBUG_MAC80211(priv, "leave\n"); +} + +const struct ieee80211_ops iwlagn_hw_ops = { + .tx = iwlagn_mac_tx, + .start = iwlagn_mac_start, + .stop = iwlagn_mac_stop, +#ifdef CONFIG_PM_SLEEP + .suspend = iwlagn_mac_suspend, + .resume = iwlagn_mac_resume, + .set_wakeup = iwlagn_mac_set_wakeup, +#endif + .add_interface = iwlagn_mac_add_interface, + .remove_interface = iwlagn_mac_remove_interface, + .change_interface = iwlagn_mac_change_interface, + .config = iwlagn_mac_config, + .configure_filter = iwlagn_configure_filter, + .set_key = iwlagn_mac_set_key, + .update_tkip_key = iwlagn_mac_update_tkip_key, + .set_rekey_data = iwlagn_mac_set_rekey_data, + .conf_tx = iwlagn_mac_conf_tx, + .bss_info_changed = iwlagn_bss_info_changed, + .ampdu_action = iwlagn_mac_ampdu_action, + .hw_scan = iwlagn_mac_hw_scan, + .sta_notify = iwlagn_mac_sta_notify, + .sta_state = iwlagn_mac_sta_state, + .channel_switch = iwlagn_mac_channel_switch, + .flush = iwlagn_mac_flush, + .tx_last_beacon = iwlagn_mac_tx_last_beacon, + .event_callback = iwlagn_mac_event_callback, + .set_tim = iwlagn_mac_set_tim, +}; + +/* This function both allocates and initializes hw and priv. */ +struct ieee80211_hw *iwl_alloc_all(void) +{ + struct iwl_priv *priv; + struct iwl_op_mode *op_mode; + /* mac80211 allocates memory for this device instance, including + * space for this driver's private structure */ + struct ieee80211_hw *hw; + + hw = ieee80211_alloc_hw(sizeof(struct iwl_priv) + + sizeof(struct iwl_op_mode), &iwlagn_hw_ops); + if (!hw) + goto out; + + op_mode = hw->priv; + priv = IWL_OP_MODE_GET_DVM(op_mode); + priv->hw = hw; + +out: + return hw; +} diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/main.c b/drivers/net/wireless/intel/iwlwifi/dvm/main.c new file mode 100644 index 000000000..a873be109 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/main.c @@ -0,0 +1,2158 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2003 - 2014, 2018 - 2022 Intel Corporation. All rights reserved. + * Copyright(c) 2015 Intel Deutschland GmbH + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + *****************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/if_arp.h> + +#include <net/mac80211.h> + +#include <asm/div64.h> + +#include "iwl-eeprom-read.h" +#include "iwl-eeprom-parse.h" +#include "iwl-io.h" +#include "iwl-trans.h" +#include "iwl-op-mode.h" +#include "iwl-drv.h" +#include "iwl-modparams.h" +#include "iwl-prph.h" + +#include "dev.h" +#include "calib.h" +#include "agn.h" + + +/****************************************************************************** + * + * module boiler plate + * + ******************************************************************************/ + +#define DRV_DESCRIPTION "Intel(R) Wireless WiFi Link AGN driver for Linux" +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(IWLWIFI); + +/* Please keep this array *SORTED* by hex value. + * Access is done through binary search. + * A warning will be triggered on violation. + */ +static const struct iwl_hcmd_names iwl_dvm_cmd_names[] = { + HCMD_NAME(REPLY_ALIVE), + HCMD_NAME(REPLY_ERROR), + HCMD_NAME(REPLY_ECHO), + HCMD_NAME(REPLY_RXON), + HCMD_NAME(REPLY_RXON_ASSOC), + HCMD_NAME(REPLY_QOS_PARAM), + HCMD_NAME(REPLY_RXON_TIMING), + HCMD_NAME(REPLY_ADD_STA), + HCMD_NAME(REPLY_REMOVE_STA), + HCMD_NAME(REPLY_REMOVE_ALL_STA), + HCMD_NAME(REPLY_TX), + HCMD_NAME(REPLY_TXFIFO_FLUSH), + HCMD_NAME(REPLY_WEPKEY), + HCMD_NAME(REPLY_LEDS_CMD), + HCMD_NAME(REPLY_TX_LINK_QUALITY_CMD), + HCMD_NAME(COEX_PRIORITY_TABLE_CMD), + HCMD_NAME(COEX_MEDIUM_NOTIFICATION), + HCMD_NAME(COEX_EVENT_CMD), + HCMD_NAME(TEMPERATURE_NOTIFICATION), + HCMD_NAME(CALIBRATION_CFG_CMD), + HCMD_NAME(CALIBRATION_RES_NOTIFICATION), + HCMD_NAME(CALIBRATION_COMPLETE_NOTIFICATION), + HCMD_NAME(REPLY_QUIET_CMD), + HCMD_NAME(REPLY_CHANNEL_SWITCH), + HCMD_NAME(CHANNEL_SWITCH_NOTIFICATION), + HCMD_NAME(REPLY_SPECTRUM_MEASUREMENT_CMD), + HCMD_NAME(SPECTRUM_MEASURE_NOTIFICATION), + HCMD_NAME(POWER_TABLE_CMD), + HCMD_NAME(PM_SLEEP_NOTIFICATION), + HCMD_NAME(PM_DEBUG_STATISTIC_NOTIFIC), + HCMD_NAME(REPLY_SCAN_CMD), + HCMD_NAME(REPLY_SCAN_ABORT_CMD), + HCMD_NAME(SCAN_START_NOTIFICATION), + HCMD_NAME(SCAN_RESULTS_NOTIFICATION), + HCMD_NAME(SCAN_COMPLETE_NOTIFICATION), + HCMD_NAME(BEACON_NOTIFICATION), + HCMD_NAME(REPLY_TX_BEACON), + HCMD_NAME(WHO_IS_AWAKE_NOTIFICATION), + HCMD_NAME(REPLY_TX_POWER_DBM_CMD), + HCMD_NAME(QUIET_NOTIFICATION), + HCMD_NAME(REPLY_TX_PWR_TABLE_CMD), + HCMD_NAME(REPLY_TX_POWER_DBM_CMD_V1), + HCMD_NAME(TX_ANT_CONFIGURATION_CMD), + HCMD_NAME(MEASURE_ABORT_NOTIFICATION), + HCMD_NAME(REPLY_BT_CONFIG), + HCMD_NAME(REPLY_STATISTICS_CMD), + HCMD_NAME(STATISTICS_NOTIFICATION), + HCMD_NAME(REPLY_CARD_STATE_CMD), + HCMD_NAME(CARD_STATE_NOTIFICATION), + HCMD_NAME(MISSED_BEACONS_NOTIFICATION), + HCMD_NAME(REPLY_CT_KILL_CONFIG_CMD), + HCMD_NAME(SENSITIVITY_CMD), + HCMD_NAME(REPLY_PHY_CALIBRATION_CMD), + HCMD_NAME(REPLY_WIPAN_PARAMS), + HCMD_NAME(REPLY_WIPAN_RXON), + HCMD_NAME(REPLY_WIPAN_RXON_TIMING), + HCMD_NAME(REPLY_WIPAN_RXON_ASSOC), + HCMD_NAME(REPLY_WIPAN_QOS_PARAM), + HCMD_NAME(REPLY_WIPAN_WEPKEY), + HCMD_NAME(REPLY_WIPAN_P2P_CHANNEL_SWITCH), + HCMD_NAME(REPLY_WIPAN_NOA_NOTIFICATION), + HCMD_NAME(REPLY_WIPAN_DEACTIVATION_COMPLETE), + HCMD_NAME(REPLY_RX_PHY_CMD), + HCMD_NAME(REPLY_RX_MPDU_CMD), + HCMD_NAME(REPLY_RX), + HCMD_NAME(REPLY_COMPRESSED_BA), + HCMD_NAME(REPLY_BT_COEX_PRIO_TABLE), + HCMD_NAME(REPLY_BT_COEX_PROT_ENV), + HCMD_NAME(REPLY_BT_COEX_PROFILE_NOTIF), + HCMD_NAME(REPLY_D3_CONFIG), + HCMD_NAME(REPLY_WOWLAN_PATTERNS), + HCMD_NAME(REPLY_WOWLAN_WAKEUP_FILTER), + HCMD_NAME(REPLY_WOWLAN_TSC_RSC_PARAMS), + HCMD_NAME(REPLY_WOWLAN_TKIP_PARAMS), + HCMD_NAME(REPLY_WOWLAN_KEK_KCK_MATERIAL), + HCMD_NAME(REPLY_WOWLAN_GET_STATUS), +}; + +static const struct iwl_hcmd_arr iwl_dvm_groups[] = { + [0x0] = HCMD_ARR(iwl_dvm_cmd_names), +}; + +static const struct iwl_op_mode_ops iwl_dvm_ops; + +void iwl_update_chain_flags(struct iwl_priv *priv) +{ + struct iwl_rxon_context *ctx; + + for_each_context(priv, ctx) { + iwlagn_set_rxon_chain(priv, ctx); + if (ctx->active.rx_chain != ctx->staging.rx_chain) + iwlagn_commit_rxon(priv, ctx); + } +} + +/* Parse the beacon frame to find the TIM element and set tim_idx & tim_size */ +static void iwl_set_beacon_tim(struct iwl_priv *priv, + struct iwl_tx_beacon_cmd *tx_beacon_cmd, + u8 *beacon, u32 frame_size) +{ + u16 tim_idx; + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)beacon; + + /* + * The index is relative to frame start but we start looking at the + * variable-length part of the beacon. + */ + tim_idx = mgmt->u.beacon.variable - beacon; + + /* Parse variable-length elements of beacon to find WLAN_EID_TIM */ + while ((tim_idx < (frame_size - 2)) && + (beacon[tim_idx] != WLAN_EID_TIM)) + tim_idx += beacon[tim_idx+1] + 2; + + /* If TIM field was found, set variables */ + if ((tim_idx < (frame_size - 1)) && (beacon[tim_idx] == WLAN_EID_TIM)) { + tx_beacon_cmd->tim_idx = cpu_to_le16(tim_idx); + tx_beacon_cmd->tim_size = beacon[tim_idx+1]; + } else + IWL_WARN(priv, "Unable to find TIM Element in beacon\n"); +} + +int iwlagn_send_beacon_cmd(struct iwl_priv *priv) +{ + struct iwl_tx_beacon_cmd *tx_beacon_cmd; + struct iwl_host_cmd cmd = { + .id = REPLY_TX_BEACON, + }; + struct ieee80211_tx_info *info; + u32 frame_size; + u32 rate_flags; + u32 rate; + + /* + * We have to set up the TX command, the TX Beacon command, and the + * beacon contents. + */ + + lockdep_assert_held(&priv->mutex); + + if (!priv->beacon_ctx) { + IWL_ERR(priv, "trying to build beacon w/o beacon context!\n"); + return 0; + } + + if (WARN_ON(!priv->beacon_skb)) + return -EINVAL; + + /* Allocate beacon command */ + if (!priv->beacon_cmd) + priv->beacon_cmd = kzalloc(sizeof(*tx_beacon_cmd), GFP_KERNEL); + tx_beacon_cmd = priv->beacon_cmd; + if (!tx_beacon_cmd) + return -ENOMEM; + + frame_size = priv->beacon_skb->len; + + /* Set up TX command fields */ + tx_beacon_cmd->tx.len = cpu_to_le16((u16)frame_size); + tx_beacon_cmd->tx.sta_id = priv->beacon_ctx->bcast_sta_id; + tx_beacon_cmd->tx.stop_time.life_time = TX_CMD_LIFE_TIME_INFINITE; + tx_beacon_cmd->tx.tx_flags = TX_CMD_FLG_SEQ_CTL_MSK | + TX_CMD_FLG_TSF_MSK | TX_CMD_FLG_STA_RATE_MSK; + + /* Set up TX beacon command fields */ + iwl_set_beacon_tim(priv, tx_beacon_cmd, priv->beacon_skb->data, + frame_size); + + /* Set up packet rate and flags */ + info = IEEE80211_SKB_CB(priv->beacon_skb); + + /* + * Let's set up the rate at least somewhat correctly; + * it will currently not actually be used by the uCode, + * it uses the broadcast station's rate instead. + */ + if (info->control.rates[0].idx < 0 || + info->control.rates[0].flags & IEEE80211_TX_RC_MCS) + rate = 0; + else + rate = info->control.rates[0].idx; + + priv->mgmt_tx_ant = iwl_toggle_tx_ant(priv, priv->mgmt_tx_ant, + priv->nvm_data->valid_tx_ant); + rate_flags = iwl_ant_idx_to_flags(priv->mgmt_tx_ant); + + /* In mac80211, rates for 5 GHz start at 0 */ + if (info->band == NL80211_BAND_5GHZ) + rate += IWL_FIRST_OFDM_RATE; + else if (rate >= IWL_FIRST_CCK_RATE && rate <= IWL_LAST_CCK_RATE) + rate_flags |= RATE_MCS_CCK_MSK; + + tx_beacon_cmd->tx.rate_n_flags = + iwl_hw_set_rate_n_flags(rate, rate_flags); + + /* Submit command */ + cmd.len[0] = sizeof(*tx_beacon_cmd); + cmd.data[0] = tx_beacon_cmd; + cmd.dataflags[0] = IWL_HCMD_DFL_NOCOPY; + cmd.len[1] = frame_size; + cmd.data[1] = priv->beacon_skb->data; + cmd.dataflags[1] = IWL_HCMD_DFL_NOCOPY; + + return iwl_dvm_send_cmd(priv, &cmd); +} + +static void iwl_bg_beacon_update(struct work_struct *work) +{ + struct iwl_priv *priv = + container_of(work, struct iwl_priv, beacon_update); + struct sk_buff *beacon; + + mutex_lock(&priv->mutex); + if (!priv->beacon_ctx) { + IWL_ERR(priv, "updating beacon w/o beacon context!\n"); + goto out; + } + + if (priv->beacon_ctx->vif->type != NL80211_IFTYPE_AP) { + /* + * The ucode will send beacon notifications even in + * IBSS mode, but we don't want to process them. But + * we need to defer the type check to here due to + * requiring locking around the beacon_ctx access. + */ + goto out; + } + + /* Pull updated AP beacon from mac80211. will fail if not in AP mode */ + beacon = ieee80211_beacon_get(priv->hw, priv->beacon_ctx->vif, 0); + if (!beacon) { + IWL_ERR(priv, "update beacon failed -- keeping old\n"); + goto out; + } + + /* new beacon skb is allocated every time; dispose previous.*/ + dev_kfree_skb(priv->beacon_skb); + + priv->beacon_skb = beacon; + + iwlagn_send_beacon_cmd(priv); + out: + mutex_unlock(&priv->mutex); +} + +static void iwl_bg_bt_runtime_config(struct work_struct *work) +{ + struct iwl_priv *priv = + container_of(work, struct iwl_priv, bt_runtime_config); + + mutex_lock(&priv->mutex); + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + goto out; + + /* dont send host command if rf-kill is on */ + if (!iwl_is_ready_rf(priv)) + goto out; + + iwlagn_send_advance_bt_config(priv); +out: + mutex_unlock(&priv->mutex); +} + +static void iwl_bg_bt_full_concurrency(struct work_struct *work) +{ + struct iwl_priv *priv = + container_of(work, struct iwl_priv, bt_full_concurrency); + struct iwl_rxon_context *ctx; + + mutex_lock(&priv->mutex); + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + goto out; + + /* dont send host command if rf-kill is on */ + if (!iwl_is_ready_rf(priv)) + goto out; + + IWL_DEBUG_INFO(priv, "BT coex in %s mode\n", + priv->bt_full_concurrent ? + "full concurrency" : "3-wire"); + + /* + * LQ & RXON updated cmds must be sent before BT Config cmd + * to avoid 3-wire collisions + */ + for_each_context(priv, ctx) { + iwlagn_set_rxon_chain(priv, ctx); + iwlagn_commit_rxon(priv, ctx); + } + + iwlagn_send_advance_bt_config(priv); +out: + mutex_unlock(&priv->mutex); +} + +int iwl_send_statistics_request(struct iwl_priv *priv, u8 flags, bool clear) +{ + struct iwl_statistics_cmd statistics_cmd = { + .configuration_flags = + clear ? IWL_STATS_CONF_CLEAR_STATS : 0, + }; + + if (flags & CMD_ASYNC) + return iwl_dvm_send_cmd_pdu(priv, REPLY_STATISTICS_CMD, + CMD_ASYNC, + sizeof(struct iwl_statistics_cmd), + &statistics_cmd); + else + return iwl_dvm_send_cmd_pdu(priv, REPLY_STATISTICS_CMD, 0, + sizeof(struct iwl_statistics_cmd), + &statistics_cmd); +} + +/* + * iwl_bg_statistics_periodic - Timer callback to queue statistics + * + * This callback is provided in order to send a statistics request. + * + * This timer function is continually reset to execute within + * REG_RECALIB_PERIOD seconds since the last STATISTICS_NOTIFICATION + * was received. We need to ensure we receive the statistics in order + * to update the temperature used for calibrating the TXPOWER. + */ +static void iwl_bg_statistics_periodic(struct timer_list *t) +{ + struct iwl_priv *priv = from_timer(priv, t, statistics_periodic); + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + /* dont send host command if rf-kill is on */ + if (!iwl_is_ready_rf(priv)) + return; + + iwl_send_statistics_request(priv, CMD_ASYNC, false); +} + + +static void iwl_print_cont_event_trace(struct iwl_priv *priv, u32 base, + u32 start_idx, u32 num_events, + u32 capacity, u32 mode) +{ + u32 i; + u32 ptr; /* SRAM byte address of log data */ + u32 ev, time, data; /* event log data */ + + if (mode == 0) + ptr = base + (4 * sizeof(u32)) + (start_idx * 2 * sizeof(u32)); + else + ptr = base + (4 * sizeof(u32)) + (start_idx * 3 * sizeof(u32)); + + /* Make sure device is powered up for SRAM reads */ + if (!iwl_trans_grab_nic_access(priv->trans)) + return; + + /* Set starting address; reads will auto-increment */ + iwl_write32(priv->trans, HBUS_TARG_MEM_RADDR, ptr); + + /* + * Refuse to read more than would have fit into the log from + * the current start_idx. This used to happen due to the race + * described below, but now WARN because the code below should + * prevent it from happening here. + */ + if (WARN_ON(num_events > capacity - start_idx)) + num_events = capacity - start_idx; + + /* + * "time" is actually "data" for mode 0 (no timestamp). + * place event id # at far right for easier visual parsing. + */ + for (i = 0; i < num_events; i++) { + ev = iwl_read32(priv->trans, HBUS_TARG_MEM_RDAT); + time = iwl_read32(priv->trans, HBUS_TARG_MEM_RDAT); + if (mode == 0) { + trace_iwlwifi_dev_ucode_cont_event( + priv->trans->dev, 0, time, ev); + } else { + data = iwl_read32(priv->trans, HBUS_TARG_MEM_RDAT); + trace_iwlwifi_dev_ucode_cont_event( + priv->trans->dev, time, data, ev); + } + } + /* Allow device to power down */ + iwl_trans_release_nic_access(priv->trans); +} + +static void iwl_continuous_event_trace(struct iwl_priv *priv) +{ + u32 capacity; /* event log capacity in # entries */ + struct { + u32 capacity; + u32 mode; + u32 wrap_counter; + u32 write_counter; + } __packed read; + u32 base; /* SRAM byte address of event log header */ + u32 mode; /* 0 - no timestamp, 1 - timestamp recorded */ + u32 num_wraps; /* # times uCode wrapped to top of log */ + u32 next_entry; /* index of next entry to be written by uCode */ + + base = priv->device_pointers.log_event_table; + if (iwlagn_hw_valid_rtc_data_addr(base)) { + iwl_trans_read_mem_bytes(priv->trans, base, + &read, sizeof(read)); + capacity = read.capacity; + mode = read.mode; + num_wraps = read.wrap_counter; + next_entry = read.write_counter; + } else + return; + + /* + * Unfortunately, the uCode doesn't use temporary variables. + * Therefore, it can happen that we read next_entry == capacity, + * which really means next_entry == 0. + */ + if (unlikely(next_entry == capacity)) + next_entry = 0; + /* + * Additionally, the uCode increases the write pointer before + * the wraps counter, so if the write pointer is smaller than + * the old write pointer (wrap occurred) but we read that no + * wrap occurred, we actually read between the next_entry and + * num_wraps update (this does happen in practice!!) -- take + * that into account by increasing num_wraps. + */ + if (unlikely(next_entry < priv->event_log.next_entry && + num_wraps == priv->event_log.num_wraps)) + num_wraps++; + + if (num_wraps == priv->event_log.num_wraps) { + iwl_print_cont_event_trace( + priv, base, priv->event_log.next_entry, + next_entry - priv->event_log.next_entry, + capacity, mode); + + priv->event_log.non_wraps_count++; + } else { + if (num_wraps - priv->event_log.num_wraps > 1) + priv->event_log.wraps_more_count++; + else + priv->event_log.wraps_once_count++; + + trace_iwlwifi_dev_ucode_wrap_event(priv->trans->dev, + num_wraps - priv->event_log.num_wraps, + next_entry, priv->event_log.next_entry); + + if (next_entry < priv->event_log.next_entry) { + iwl_print_cont_event_trace( + priv, base, priv->event_log.next_entry, + capacity - priv->event_log.next_entry, + capacity, mode); + + iwl_print_cont_event_trace( + priv, base, 0, next_entry, capacity, mode); + } else { + iwl_print_cont_event_trace( + priv, base, next_entry, + capacity - next_entry, + capacity, mode); + + iwl_print_cont_event_trace( + priv, base, 0, next_entry, capacity, mode); + } + } + + priv->event_log.num_wraps = num_wraps; + priv->event_log.next_entry = next_entry; +} + +/* + * iwl_bg_ucode_trace - Timer callback to log ucode event + * + * The timer is continually set to execute every + * UCODE_TRACE_PERIOD milliseconds after the last timer expired + * this function is to perform continuous uCode event logging operation + * if enabled + */ +static void iwl_bg_ucode_trace(struct timer_list *t) +{ + struct iwl_priv *priv = from_timer(priv, t, ucode_trace); + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + if (priv->event_log.ucode_trace) { + iwl_continuous_event_trace(priv); + /* Reschedule the timer to occur in UCODE_TRACE_PERIOD */ + mod_timer(&priv->ucode_trace, + jiffies + msecs_to_jiffies(UCODE_TRACE_PERIOD)); + } +} + +static void iwl_bg_tx_flush(struct work_struct *work) +{ + struct iwl_priv *priv = + container_of(work, struct iwl_priv, tx_flush); + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + /* do nothing if rf-kill is on */ + if (!iwl_is_ready_rf(priv)) + return; + + IWL_DEBUG_INFO(priv, "device request: flush all tx frames\n"); + iwlagn_dev_txfifo_flush(priv); +} + +/* + * queue/FIFO/AC mapping definitions + */ + +static const u8 iwlagn_bss_ac_to_fifo[] = { + IWL_TX_FIFO_VO, + IWL_TX_FIFO_VI, + IWL_TX_FIFO_BE, + IWL_TX_FIFO_BK, +}; + +static const u8 iwlagn_bss_ac_to_queue[] = { + 0, 1, 2, 3, +}; + +static const u8 iwlagn_pan_ac_to_fifo[] = { + IWL_TX_FIFO_VO_IPAN, + IWL_TX_FIFO_VI_IPAN, + IWL_TX_FIFO_BE_IPAN, + IWL_TX_FIFO_BK_IPAN, +}; + +static const u8 iwlagn_pan_ac_to_queue[] = { + 7, 6, 5, 4, +}; + +static void iwl_init_context(struct iwl_priv *priv, u32 ucode_flags) +{ + int i; + + /* + * The default context is always valid, + * the PAN context depends on uCode. + */ + priv->valid_contexts = BIT(IWL_RXON_CTX_BSS); + if (ucode_flags & IWL_UCODE_TLV_FLAGS_PAN) + priv->valid_contexts |= BIT(IWL_RXON_CTX_PAN); + + for (i = 0; i < NUM_IWL_RXON_CTX; i++) + priv->contexts[i].ctxid = i; + + priv->contexts[IWL_RXON_CTX_BSS].always_active = true; + priv->contexts[IWL_RXON_CTX_BSS].is_active = true; + priv->contexts[IWL_RXON_CTX_BSS].rxon_cmd = REPLY_RXON; + priv->contexts[IWL_RXON_CTX_BSS].rxon_timing_cmd = REPLY_RXON_TIMING; + priv->contexts[IWL_RXON_CTX_BSS].rxon_assoc_cmd = REPLY_RXON_ASSOC; + priv->contexts[IWL_RXON_CTX_BSS].qos_cmd = REPLY_QOS_PARAM; + priv->contexts[IWL_RXON_CTX_BSS].ap_sta_id = IWL_AP_ID; + priv->contexts[IWL_RXON_CTX_BSS].wep_key_cmd = REPLY_WEPKEY; + priv->contexts[IWL_RXON_CTX_BSS].bcast_sta_id = IWLAGN_BROADCAST_ID; + priv->contexts[IWL_RXON_CTX_BSS].exclusive_interface_modes = + BIT(NL80211_IFTYPE_ADHOC) | BIT(NL80211_IFTYPE_MONITOR); + priv->contexts[IWL_RXON_CTX_BSS].interface_modes = + BIT(NL80211_IFTYPE_STATION); + priv->contexts[IWL_RXON_CTX_BSS].ap_devtype = RXON_DEV_TYPE_AP; + priv->contexts[IWL_RXON_CTX_BSS].ibss_devtype = RXON_DEV_TYPE_IBSS; + priv->contexts[IWL_RXON_CTX_BSS].station_devtype = RXON_DEV_TYPE_ESS; + priv->contexts[IWL_RXON_CTX_BSS].unused_devtype = RXON_DEV_TYPE_ESS; + memcpy(priv->contexts[IWL_RXON_CTX_BSS].ac_to_queue, + iwlagn_bss_ac_to_queue, sizeof(iwlagn_bss_ac_to_queue)); + memcpy(priv->contexts[IWL_RXON_CTX_BSS].ac_to_fifo, + iwlagn_bss_ac_to_fifo, sizeof(iwlagn_bss_ac_to_fifo)); + + priv->contexts[IWL_RXON_CTX_PAN].rxon_cmd = REPLY_WIPAN_RXON; + priv->contexts[IWL_RXON_CTX_PAN].rxon_timing_cmd = + REPLY_WIPAN_RXON_TIMING; + priv->contexts[IWL_RXON_CTX_PAN].rxon_assoc_cmd = + REPLY_WIPAN_RXON_ASSOC; + priv->contexts[IWL_RXON_CTX_PAN].qos_cmd = REPLY_WIPAN_QOS_PARAM; + priv->contexts[IWL_RXON_CTX_PAN].ap_sta_id = IWL_AP_ID_PAN; + priv->contexts[IWL_RXON_CTX_PAN].wep_key_cmd = REPLY_WIPAN_WEPKEY; + priv->contexts[IWL_RXON_CTX_PAN].bcast_sta_id = IWLAGN_PAN_BCAST_ID; + priv->contexts[IWL_RXON_CTX_PAN].station_flags = STA_FLG_PAN_STATION; + priv->contexts[IWL_RXON_CTX_PAN].interface_modes = + BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP); + + priv->contexts[IWL_RXON_CTX_PAN].ap_devtype = RXON_DEV_TYPE_CP; + priv->contexts[IWL_RXON_CTX_PAN].station_devtype = RXON_DEV_TYPE_2STA; + priv->contexts[IWL_RXON_CTX_PAN].unused_devtype = RXON_DEV_TYPE_P2P; + memcpy(priv->contexts[IWL_RXON_CTX_PAN].ac_to_queue, + iwlagn_pan_ac_to_queue, sizeof(iwlagn_pan_ac_to_queue)); + memcpy(priv->contexts[IWL_RXON_CTX_PAN].ac_to_fifo, + iwlagn_pan_ac_to_fifo, sizeof(iwlagn_pan_ac_to_fifo)); + priv->contexts[IWL_RXON_CTX_PAN].mcast_queue = IWL_IPAN_MCAST_QUEUE; + + BUILD_BUG_ON(NUM_IWL_RXON_CTX != 2); +} + +static void iwl_rf_kill_ct_config(struct iwl_priv *priv) +{ + struct iwl_ct_kill_config cmd; + struct iwl_ct_kill_throttling_config adv_cmd; + int ret = 0; + + iwl_write32(priv->trans, CSR_UCODE_DRV_GP1_CLR, + CSR_UCODE_DRV_GP1_REG_BIT_CT_KILL_EXIT); + + priv->thermal_throttle.ct_kill_toggle = false; + + if (priv->lib->support_ct_kill_exit) { + adv_cmd.critical_temperature_enter = + cpu_to_le32(priv->hw_params.ct_kill_threshold); + adv_cmd.critical_temperature_exit = + cpu_to_le32(priv->hw_params.ct_kill_exit_threshold); + + ret = iwl_dvm_send_cmd_pdu(priv, + REPLY_CT_KILL_CONFIG_CMD, + 0, sizeof(adv_cmd), &adv_cmd); + if (ret) + IWL_ERR(priv, "REPLY_CT_KILL_CONFIG_CMD failed\n"); + else + IWL_DEBUG_INFO(priv, "REPLY_CT_KILL_CONFIG_CMD " + "succeeded, critical temperature enter is %d," + "exit is %d\n", + priv->hw_params.ct_kill_threshold, + priv->hw_params.ct_kill_exit_threshold); + } else { + cmd.critical_temperature_R = + cpu_to_le32(priv->hw_params.ct_kill_threshold); + + ret = iwl_dvm_send_cmd_pdu(priv, + REPLY_CT_KILL_CONFIG_CMD, + 0, sizeof(cmd), &cmd); + if (ret) + IWL_ERR(priv, "REPLY_CT_KILL_CONFIG_CMD failed\n"); + else + IWL_DEBUG_INFO(priv, "REPLY_CT_KILL_CONFIG_CMD " + "succeeded, " + "critical temperature is %d\n", + priv->hw_params.ct_kill_threshold); + } +} + +static int iwlagn_send_calib_cfg_rt(struct iwl_priv *priv, u32 cfg) +{ + struct iwl_calib_cfg_cmd calib_cfg_cmd; + struct iwl_host_cmd cmd = { + .id = CALIBRATION_CFG_CMD, + .len = { sizeof(struct iwl_calib_cfg_cmd), }, + .data = { &calib_cfg_cmd, }, + }; + + memset(&calib_cfg_cmd, 0, sizeof(calib_cfg_cmd)); + calib_cfg_cmd.ucd_calib_cfg.once.is_enable = IWL_CALIB_RT_CFG_ALL; + calib_cfg_cmd.ucd_calib_cfg.once.start = cpu_to_le32(cfg); + + return iwl_dvm_send_cmd(priv, &cmd); +} + + +static int iwlagn_send_tx_ant_config(struct iwl_priv *priv, u8 valid_tx_ant) +{ + struct iwl_tx_ant_config_cmd tx_ant_cmd = { + .valid = cpu_to_le32(valid_tx_ant), + }; + + if (IWL_UCODE_API(priv->fw->ucode_ver) > 1) { + IWL_DEBUG_HC(priv, "select valid tx ant: %u\n", valid_tx_ant); + return iwl_dvm_send_cmd_pdu(priv, TX_ANT_CONFIGURATION_CMD, 0, + sizeof(struct iwl_tx_ant_config_cmd), + &tx_ant_cmd); + } else { + IWL_DEBUG_HC(priv, "TX_ANT_CONFIGURATION_CMD not supported\n"); + return -EOPNOTSUPP; + } +} + +static void iwl_send_bt_config(struct iwl_priv *priv) +{ + struct iwl_bt_cmd bt_cmd = { + .lead_time = BT_LEAD_TIME_DEF, + .max_kill = BT_MAX_KILL_DEF, + .kill_ack_mask = 0, + .kill_cts_mask = 0, + }; + + if (!iwlwifi_mod_params.bt_coex_active) + bt_cmd.flags = BT_COEX_DISABLE; + else + bt_cmd.flags = BT_COEX_ENABLE; + + priv->bt_enable_flag = bt_cmd.flags; + IWL_DEBUG_INFO(priv, "BT coex %s\n", + (bt_cmd.flags == BT_COEX_DISABLE) ? "disable" : "active"); + + if (iwl_dvm_send_cmd_pdu(priv, REPLY_BT_CONFIG, + 0, sizeof(struct iwl_bt_cmd), &bt_cmd)) + IWL_ERR(priv, "failed to send BT Coex Config\n"); +} + +/* + * iwl_alive_start - called after REPLY_ALIVE notification received + * from protocol/runtime uCode (initialization uCode's + * Alive gets handled by iwl_init_alive_start()). + */ +int iwl_alive_start(struct iwl_priv *priv) +{ + int ret = 0; + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + + IWL_DEBUG_INFO(priv, "Runtime Alive received.\n"); + + /* After the ALIVE response, we can send host commands to the uCode */ + set_bit(STATUS_ALIVE, &priv->status); + + if (iwl_is_rfkill(priv)) + return -ERFKILL; + + if (priv->event_log.ucode_trace) { + /* start collecting data now */ + mod_timer(&priv->ucode_trace, jiffies); + } + + /* download priority table before any calibration request */ + if (priv->lib->bt_params && + priv->lib->bt_params->advanced_bt_coexist) { + /* Configure Bluetooth device coexistence support */ + if (priv->lib->bt_params->bt_sco_disable) + priv->bt_enable_pspoll = false; + else + priv->bt_enable_pspoll = true; + + priv->bt_valid = IWLAGN_BT_ALL_VALID_MSK; + priv->kill_ack_mask = IWLAGN_BT_KILL_ACK_MASK_DEFAULT; + priv->kill_cts_mask = IWLAGN_BT_KILL_CTS_MASK_DEFAULT; + iwlagn_send_advance_bt_config(priv); + priv->bt_valid = IWLAGN_BT_VALID_ENABLE_FLAGS; + priv->cur_rssi_ctx = NULL; + + iwl_send_prio_tbl(priv); + + /* FIXME: w/a to force change uCode BT state machine */ + ret = iwl_send_bt_env(priv, IWL_BT_COEX_ENV_OPEN, + BT_COEX_PRIO_TBL_EVT_INIT_CALIB2); + if (ret) + return ret; + ret = iwl_send_bt_env(priv, IWL_BT_COEX_ENV_CLOSE, + BT_COEX_PRIO_TBL_EVT_INIT_CALIB2); + if (ret) + return ret; + } else if (priv->lib->bt_params) { + /* + * default is 2-wire BT coexexistence support + */ + iwl_send_bt_config(priv); + } + + /* + * Perform runtime calibrations, including DC calibration. + */ + iwlagn_send_calib_cfg_rt(priv, IWL_CALIB_CFG_DC_IDX); + + ieee80211_wake_queues(priv->hw); + + /* Configure Tx antenna selection based on H/W config */ + iwlagn_send_tx_ant_config(priv, priv->nvm_data->valid_tx_ant); + + if (iwl_is_associated_ctx(ctx) && !priv->wowlan) { + struct iwl_rxon_cmd *active_rxon = + (struct iwl_rxon_cmd *)&ctx->active; + /* apply any changes in staging */ + ctx->staging.filter_flags |= RXON_FILTER_ASSOC_MSK; + active_rxon->filter_flags &= ~RXON_FILTER_ASSOC_MSK; + } else { + struct iwl_rxon_context *tmp; + /* Initialize our rx_config data */ + for_each_context(priv, tmp) + iwl_connection_init_rx_config(priv, tmp); + + iwlagn_set_rxon_chain(priv, ctx); + } + + if (!priv->wowlan) { + /* WoWLAN ucode will not reply in the same way, skip it */ + iwl_reset_run_time_calib(priv); + } + + set_bit(STATUS_READY, &priv->status); + + /* Configure the adapter for unassociated operation */ + ret = iwlagn_commit_rxon(priv, ctx); + if (ret) + return ret; + + /* At this point, the NIC is initialized and operational */ + iwl_rf_kill_ct_config(priv); + + IWL_DEBUG_INFO(priv, "ALIVE processing complete.\n"); + + return iwl_power_update_mode(priv, true); +} + +/** + * iwl_clear_driver_stations - clear knowledge of all stations from driver + * @priv: iwl priv struct + * + * This is called during iwl_down() to make sure that in the case + * we're coming there from a hardware restart mac80211 will be + * able to reconfigure stations -- if we're getting there in the + * normal down flow then the stations will already be cleared. + */ +static void iwl_clear_driver_stations(struct iwl_priv *priv) +{ + struct iwl_rxon_context *ctx; + + spin_lock_bh(&priv->sta_lock); + memset(priv->stations, 0, sizeof(priv->stations)); + priv->num_stations = 0; + + priv->ucode_key_table = 0; + + for_each_context(priv, ctx) { + /* + * Remove all key information that is not stored as part + * of station information since mac80211 may not have had + * a chance to remove all the keys. When device is + * reconfigured by mac80211 after an error all keys will + * be reconfigured. + */ + memset(ctx->wep_keys, 0, sizeof(ctx->wep_keys)); + ctx->key_mapping_keys = 0; + } + + spin_unlock_bh(&priv->sta_lock); +} + +void iwl_down(struct iwl_priv *priv) +{ + int exit_pending; + + IWL_DEBUG_INFO(priv, DRV_NAME " is going down\n"); + + lockdep_assert_held(&priv->mutex); + + iwl_scan_cancel_timeout(priv, 200); + + exit_pending = + test_and_set_bit(STATUS_EXIT_PENDING, &priv->status); + + iwl_clear_ucode_stations(priv, NULL); + iwl_dealloc_bcast_stations(priv); + iwl_clear_driver_stations(priv); + + /* reset BT coex data */ + priv->bt_status = 0; + priv->cur_rssi_ctx = NULL; + priv->bt_is_sco = 0; + if (priv->lib->bt_params) + priv->bt_traffic_load = + priv->lib->bt_params->bt_init_traffic_load; + else + priv->bt_traffic_load = 0; + priv->bt_full_concurrent = false; + priv->bt_ci_compliance = 0; + + /* Wipe out the EXIT_PENDING status bit if we are not actually + * exiting the module */ + if (!exit_pending) + clear_bit(STATUS_EXIT_PENDING, &priv->status); + + if (priv->mac80211_registered) + ieee80211_stop_queues(priv->hw); + + priv->ucode_loaded = false; + iwl_trans_stop_device(priv->trans); + + /* Set num_aux_in_flight must be done after the transport is stopped */ + atomic_set(&priv->num_aux_in_flight, 0); + + /* Clear out all status bits but a few that are stable across reset */ + priv->status &= test_bit(STATUS_RF_KILL_HW, &priv->status) << + STATUS_RF_KILL_HW | + test_bit(STATUS_FW_ERROR, &priv->status) << + STATUS_FW_ERROR | + test_bit(STATUS_EXIT_PENDING, &priv->status) << + STATUS_EXIT_PENDING; + + dev_kfree_skb(priv->beacon_skb); + priv->beacon_skb = NULL; +} + +/***************************************************************************** + * + * Workqueue callbacks + * + *****************************************************************************/ + +static void iwl_bg_run_time_calib_work(struct work_struct *work) +{ + struct iwl_priv *priv = container_of(work, struct iwl_priv, + run_time_calib_work); + + mutex_lock(&priv->mutex); + + if (test_bit(STATUS_EXIT_PENDING, &priv->status) || + test_bit(STATUS_SCANNING, &priv->status)) { + mutex_unlock(&priv->mutex); + return; + } + + if (priv->start_calib) { + iwl_chain_noise_calibration(priv); + iwl_sensitivity_calibration(priv); + } + + mutex_unlock(&priv->mutex); +} + +void iwlagn_prepare_restart(struct iwl_priv *priv) +{ + bool bt_full_concurrent; + u8 bt_ci_compliance; + u8 bt_load; + u8 bt_status; + bool bt_is_sco; + int i; + + lockdep_assert_held(&priv->mutex); + + priv->is_open = 0; + + /* + * __iwl_down() will clear the BT status variables, + * which is correct, but when we restart we really + * want to keep them so restore them afterwards. + * + * The restart process will later pick them up and + * re-configure the hw when we reconfigure the BT + * command. + */ + bt_full_concurrent = priv->bt_full_concurrent; + bt_ci_compliance = priv->bt_ci_compliance; + bt_load = priv->bt_traffic_load; + bt_status = priv->bt_status; + bt_is_sco = priv->bt_is_sco; + + iwl_down(priv); + + priv->bt_full_concurrent = bt_full_concurrent; + priv->bt_ci_compliance = bt_ci_compliance; + priv->bt_traffic_load = bt_load; + priv->bt_status = bt_status; + priv->bt_is_sco = bt_is_sco; + + /* reset aggregation queues */ + for (i = IWLAGN_FIRST_AMPDU_QUEUE; i < IWL_MAX_HW_QUEUES; i++) + priv->queue_to_mac80211[i] = IWL_INVALID_MAC80211_QUEUE; + /* and stop counts */ + for (i = 0; i < IWL_MAX_HW_QUEUES; i++) + atomic_set(&priv->queue_stop_count[i], 0); + + memset(priv->agg_q_alloc, 0, sizeof(priv->agg_q_alloc)); +} + +static void iwl_bg_restart(struct work_struct *data) +{ + struct iwl_priv *priv = container_of(data, struct iwl_priv, restart); + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + if (test_and_clear_bit(STATUS_FW_ERROR, &priv->status)) { + mutex_lock(&priv->mutex); + iwlagn_prepare_restart(priv); + mutex_unlock(&priv->mutex); + iwl_cancel_deferred_work(priv); + if (priv->mac80211_registered) + ieee80211_restart_hw(priv->hw); + else + IWL_ERR(priv, + "Cannot request restart before registering with mac80211\n"); + } else { + WARN_ON(1); + } +} + +/***************************************************************************** + * + * driver setup and teardown + * + *****************************************************************************/ + +static void iwl_setup_deferred_work(struct iwl_priv *priv) +{ + priv->workqueue = alloc_ordered_workqueue(DRV_NAME, 0); + + INIT_WORK(&priv->restart, iwl_bg_restart); + INIT_WORK(&priv->beacon_update, iwl_bg_beacon_update); + INIT_WORK(&priv->run_time_calib_work, iwl_bg_run_time_calib_work); + INIT_WORK(&priv->tx_flush, iwl_bg_tx_flush); + INIT_WORK(&priv->bt_full_concurrency, iwl_bg_bt_full_concurrency); + INIT_WORK(&priv->bt_runtime_config, iwl_bg_bt_runtime_config); + + iwl_setup_scan_deferred_work(priv); + + if (priv->lib->bt_params) + iwlagn_bt_setup_deferred_work(priv); + + timer_setup(&priv->statistics_periodic, iwl_bg_statistics_periodic, 0); + + timer_setup(&priv->ucode_trace, iwl_bg_ucode_trace, 0); +} + +void iwl_cancel_deferred_work(struct iwl_priv *priv) +{ + if (priv->lib->bt_params) + iwlagn_bt_cancel_deferred_work(priv); + + cancel_work_sync(&priv->run_time_calib_work); + cancel_work_sync(&priv->beacon_update); + + iwl_cancel_scan_deferred_work(priv); + + cancel_work_sync(&priv->bt_full_concurrency); + cancel_work_sync(&priv->bt_runtime_config); + + del_timer_sync(&priv->statistics_periodic); + del_timer_sync(&priv->ucode_trace); +} + +static int iwl_init_drv(struct iwl_priv *priv) +{ + spin_lock_init(&priv->sta_lock); + + mutex_init(&priv->mutex); + + INIT_LIST_HEAD(&priv->calib_results); + + priv->band = NL80211_BAND_2GHZ; + + priv->plcp_delta_threshold = priv->lib->plcp_delta_threshold; + + priv->iw_mode = NL80211_IFTYPE_STATION; + priv->current_ht_config.smps = IEEE80211_SMPS_STATIC; + priv->missed_beacon_threshold = IWL_MISSED_BEACON_THRESHOLD_DEF; + priv->agg_tids_count = 0; + + priv->rx_statistics_jiffies = jiffies; + + /* Choose which receivers/antennas to use */ + iwlagn_set_rxon_chain(priv, &priv->contexts[IWL_RXON_CTX_BSS]); + + iwl_init_scan_params(priv); + + /* init bt coex */ + if (priv->lib->bt_params && + priv->lib->bt_params->advanced_bt_coexist) { + priv->kill_ack_mask = IWLAGN_BT_KILL_ACK_MASK_DEFAULT; + priv->kill_cts_mask = IWLAGN_BT_KILL_CTS_MASK_DEFAULT; + priv->bt_valid = IWLAGN_BT_ALL_VALID_MSK; + priv->bt_on_thresh = BT_ON_THRESHOLD_DEF; + priv->bt_duration = BT_DURATION_LIMIT_DEF; + priv->dynamic_frag_thresh = BT_FRAG_THRESHOLD_DEF; + } + + return 0; +} + +static void iwl_uninit_drv(struct iwl_priv *priv) +{ + kfree(priv->scan_cmd); + kfree(priv->beacon_cmd); + kfree(rcu_dereference_raw(priv->noa_data)); + iwl_calib_free_results(priv); +#ifdef CONFIG_IWLWIFI_DEBUGFS + kfree(priv->wowlan_sram); +#endif +} + +static void iwl_set_hw_params(struct iwl_priv *priv) +{ + if (priv->cfg->ht_params) + priv->hw_params.use_rts_for_aggregation = + priv->cfg->ht_params->use_rts_for_aggregation; + + /* Device-specific setup */ + priv->lib->set_hw_params(priv); +} + + + +/* show what optional capabilities we have */ +static void iwl_option_config(struct iwl_priv *priv) +{ +#ifdef CONFIG_IWLWIFI_DEBUG + IWL_INFO(priv, "CONFIG_IWLWIFI_DEBUG enabled\n"); +#else + IWL_INFO(priv, "CONFIG_IWLWIFI_DEBUG disabled\n"); +#endif + +#ifdef CONFIG_IWLWIFI_DEBUGFS + IWL_INFO(priv, "CONFIG_IWLWIFI_DEBUGFS enabled\n"); +#else + IWL_INFO(priv, "CONFIG_IWLWIFI_DEBUGFS disabled\n"); +#endif + +#ifdef CONFIG_IWLWIFI_DEVICE_TRACING + IWL_INFO(priv, "CONFIG_IWLWIFI_DEVICE_TRACING enabled\n"); +#else + IWL_INFO(priv, "CONFIG_IWLWIFI_DEVICE_TRACING disabled\n"); +#endif +} + +static int iwl_eeprom_init_hw_params(struct iwl_priv *priv) +{ + struct iwl_nvm_data *data = priv->nvm_data; + + if (data->sku_cap_11n_enable && + !priv->cfg->ht_params) { + IWL_ERR(priv, "Invalid 11n configuration\n"); + return -EINVAL; + } + + if (!data->sku_cap_11n_enable && !data->sku_cap_band_24ghz_enable && + !data->sku_cap_band_52ghz_enable) { + IWL_ERR(priv, "Invalid device sku\n"); + return -EINVAL; + } + + IWL_DEBUG_INFO(priv, + "Device SKU: 24GHz %s %s, 52GHz %s %s, 11.n %s %s\n", + data->sku_cap_band_24ghz_enable ? "" : "NOT", "enabled", + data->sku_cap_band_52ghz_enable ? "" : "NOT", "enabled", + data->sku_cap_11n_enable ? "" : "NOT", "enabled"); + + priv->hw_params.tx_chains_num = + num_of_ant(data->valid_tx_ant); + if (priv->cfg->rx_with_siso_diversity) + priv->hw_params.rx_chains_num = 1; + else + priv->hw_params.rx_chains_num = + num_of_ant(data->valid_rx_ant); + + IWL_DEBUG_INFO(priv, "Valid Tx ant: 0x%X, Valid Rx ant: 0x%X\n", + data->valid_tx_ant, + data->valid_rx_ant); + + return 0; +} + +static int iwl_nvm_check_version(struct iwl_nvm_data *data, + struct iwl_trans *trans) +{ + if (data->nvm_version >= trans->cfg->nvm_ver || + data->calib_version >= trans->cfg->nvm_calib_ver) { + IWL_DEBUG_INFO(trans, "device EEPROM VER=0x%x, CALIB=0x%x\n", + data->nvm_version, data->calib_version); + return 0; + } + + IWL_ERR(trans, + "Unsupported (too old) EEPROM VER=0x%x < 0x%x CALIB=0x%x < 0x%x\n", + data->nvm_version, trans->cfg->nvm_ver, + data->calib_version, trans->cfg->nvm_calib_ver); + return -EINVAL; +} + +static struct iwl_op_mode *iwl_op_mode_dvm_start(struct iwl_trans *trans, + const struct iwl_cfg *cfg, + const struct iwl_fw *fw, + struct dentry *dbgfs_dir) +{ + struct iwl_priv *priv; + struct ieee80211_hw *hw; + struct iwl_op_mode *op_mode; + u16 num_mac; + u32 ucode_flags; + struct iwl_trans_config trans_cfg = {}; + static const u8 no_reclaim_cmds[] = { + REPLY_RX_PHY_CMD, + REPLY_RX_MPDU_CMD, + REPLY_COMPRESSED_BA, + STATISTICS_NOTIFICATION, + REPLY_TX, + }; + int i; + + /************************ + * 1. Allocating HW data + ************************/ + hw = iwl_alloc_all(); + if (!hw) { + pr_err("%s: Cannot allocate network device\n", trans->name); + goto out; + } + + op_mode = hw->priv; + op_mode->ops = &iwl_dvm_ops; + priv = IWL_OP_MODE_GET_DVM(op_mode); + priv->trans = trans; + priv->dev = trans->dev; + priv->cfg = cfg; + priv->fw = fw; + + switch (priv->trans->trans_cfg->device_family) { + case IWL_DEVICE_FAMILY_1000: + case IWL_DEVICE_FAMILY_100: + priv->lib = &iwl_dvm_1000_cfg; + break; + case IWL_DEVICE_FAMILY_2000: + priv->lib = &iwl_dvm_2000_cfg; + break; + case IWL_DEVICE_FAMILY_105: + priv->lib = &iwl_dvm_105_cfg; + break; + case IWL_DEVICE_FAMILY_2030: + case IWL_DEVICE_FAMILY_135: + priv->lib = &iwl_dvm_2030_cfg; + break; + case IWL_DEVICE_FAMILY_5000: + priv->lib = &iwl_dvm_5000_cfg; + break; + case IWL_DEVICE_FAMILY_5150: + priv->lib = &iwl_dvm_5150_cfg; + break; + case IWL_DEVICE_FAMILY_6000: + case IWL_DEVICE_FAMILY_6000i: + priv->lib = &iwl_dvm_6000_cfg; + break; + case IWL_DEVICE_FAMILY_6005: + priv->lib = &iwl_dvm_6005_cfg; + break; + case IWL_DEVICE_FAMILY_6050: + case IWL_DEVICE_FAMILY_6150: + priv->lib = &iwl_dvm_6050_cfg; + break; + case IWL_DEVICE_FAMILY_6030: + priv->lib = &iwl_dvm_6030_cfg; + break; + default: + break; + } + + if (WARN_ON(!priv->lib)) + goto out_free_hw; + + /* + * Populate the state variables that the transport layer needs + * to know about. + */ + trans_cfg.op_mode = op_mode; + trans_cfg.no_reclaim_cmds = no_reclaim_cmds; + trans_cfg.n_no_reclaim_cmds = ARRAY_SIZE(no_reclaim_cmds); + + switch (iwlwifi_mod_params.amsdu_size) { + case IWL_AMSDU_DEF: + case IWL_AMSDU_4K: + trans_cfg.rx_buf_size = IWL_AMSDU_4K; + break; + case IWL_AMSDU_8K: + trans_cfg.rx_buf_size = IWL_AMSDU_8K; + break; + case IWL_AMSDU_12K: + default: + trans_cfg.rx_buf_size = IWL_AMSDU_4K; + pr_err("Unsupported amsdu_size: %d\n", + iwlwifi_mod_params.amsdu_size); + } + + trans_cfg.cmd_q_wdg_timeout = IWL_WATCHDOG_DISABLED; + + trans_cfg.command_groups = iwl_dvm_groups; + trans_cfg.command_groups_size = ARRAY_SIZE(iwl_dvm_groups); + + trans_cfg.cmd_fifo = IWLAGN_CMD_FIFO_NUM; + trans_cfg.cb_data_offs = offsetof(struct ieee80211_tx_info, + driver_data[2]); + + WARN_ON(sizeof(priv->transport_queue_stop) * BITS_PER_BYTE < + priv->trans->trans_cfg->base_params->num_of_queues); + + ucode_flags = fw->ucode_capa.flags; + + if (ucode_flags & IWL_UCODE_TLV_FLAGS_PAN) { + priv->sta_key_max_num = STA_KEY_MAX_NUM_PAN; + trans_cfg.cmd_queue = IWL_IPAN_CMD_QUEUE_NUM; + } else { + priv->sta_key_max_num = STA_KEY_MAX_NUM; + trans_cfg.cmd_queue = IWL_DEFAULT_CMD_QUEUE_NUM; + } + + /* Configure transport layer */ + iwl_trans_configure(priv->trans, &trans_cfg); + + trans->rx_mpdu_cmd = REPLY_RX_MPDU_CMD; + trans->rx_mpdu_cmd_hdr_size = sizeof(struct iwl_rx_mpdu_res_start); + trans->command_groups = trans_cfg.command_groups; + trans->command_groups_size = trans_cfg.command_groups_size; + + /* At this point both hw and priv are allocated. */ + + SET_IEEE80211_DEV(priv->hw, priv->trans->dev); + + iwl_option_config(priv); + + IWL_DEBUG_INFO(priv, "*** LOAD DRIVER ***\n"); + + /* bt channel inhibition enabled*/ + priv->bt_ch_announce = true; + IWL_DEBUG_INFO(priv, "BT channel inhibition is %s\n", + (priv->bt_ch_announce) ? "On" : "Off"); + + /* these spin locks will be used in apm_ops.init and EEPROM access + * we should init now + */ + spin_lock_init(&priv->statistics.lock); + + /*********************** + * 2. Read REV register + ***********************/ + IWL_INFO(priv, "Detected %s, REV=0x%X\n", + priv->trans->name, priv->trans->hw_rev); + + if (iwl_trans_start_hw(priv->trans)) + goto out_free_hw; + + /* Read the EEPROM */ + if (iwl_read_eeprom(priv->trans, &priv->eeprom_blob, + &priv->eeprom_blob_size)) { + IWL_ERR(priv, "Unable to init EEPROM\n"); + goto out_free_hw; + } + + /* Reset chip to save power until we load uCode during "up". */ + iwl_trans_stop_device(priv->trans); + + priv->nvm_data = iwl_parse_eeprom_data(priv->trans, priv->cfg, + priv->eeprom_blob, + priv->eeprom_blob_size); + if (!priv->nvm_data) + goto out_free_eeprom_blob; + + if (iwl_nvm_check_version(priv->nvm_data, priv->trans)) + goto out_free_eeprom; + + if (iwl_eeprom_init_hw_params(priv)) + goto out_free_eeprom; + + /* extract MAC Address */ + memcpy(priv->addresses[0].addr, priv->nvm_data->hw_addr, ETH_ALEN); + IWL_DEBUG_INFO(priv, "MAC address: %pM\n", priv->addresses[0].addr); + priv->hw->wiphy->addresses = priv->addresses; + priv->hw->wiphy->n_addresses = 1; + num_mac = priv->nvm_data->n_hw_addrs; + if (num_mac > 1) { + memcpy(priv->addresses[1].addr, priv->addresses[0].addr, + ETH_ALEN); + priv->addresses[1].addr[5]++; + priv->hw->wiphy->n_addresses++; + } + + /************************ + * 4. Setup HW constants + ************************/ + iwl_set_hw_params(priv); + + if (!(priv->nvm_data->sku_cap_ipan_enable)) { + IWL_DEBUG_INFO(priv, "Your EEPROM disabled PAN\n"); + ucode_flags &= ~IWL_UCODE_TLV_FLAGS_PAN; + /* + * if not PAN, then don't support P2P -- might be a uCode + * packaging bug or due to the eeprom check above + */ + priv->sta_key_max_num = STA_KEY_MAX_NUM; + trans_cfg.cmd_queue = IWL_DEFAULT_CMD_QUEUE_NUM; + + /* Configure transport layer again*/ + iwl_trans_configure(priv->trans, &trans_cfg); + } + + /******************* + * 5. Setup priv + *******************/ + for (i = 0; i < IWL_MAX_HW_QUEUES; i++) { + priv->queue_to_mac80211[i] = IWL_INVALID_MAC80211_QUEUE; + if (i < IWLAGN_FIRST_AMPDU_QUEUE && + i != IWL_DEFAULT_CMD_QUEUE_NUM && + i != IWL_IPAN_CMD_QUEUE_NUM) + priv->queue_to_mac80211[i] = i; + atomic_set(&priv->queue_stop_count[i], 0); + } + + if (iwl_init_drv(priv)) + goto out_free_eeprom; + + /* At this point both hw and priv are initialized. */ + + /******************** + * 6. Setup services + ********************/ + iwl_setup_deferred_work(priv); + iwl_setup_rx_handlers(priv); + + iwl_power_initialize(priv); + iwl_tt_initialize(priv); + + snprintf(priv->hw->wiphy->fw_version, + sizeof(priv->hw->wiphy->fw_version), + "%s", fw->fw_version); + + priv->new_scan_threshold_behaviour = + !!(ucode_flags & IWL_UCODE_TLV_FLAGS_NEWSCAN); + + priv->phy_calib_chain_noise_reset_cmd = + fw->ucode_capa.standard_phy_calibration_size; + priv->phy_calib_chain_noise_gain_cmd = + fw->ucode_capa.standard_phy_calibration_size + 1; + + /* initialize all valid contexts */ + iwl_init_context(priv, ucode_flags); + + /************************************************** + * This is still part of probe() in a sense... + * + * 7. Setup and register with mac80211 and debugfs + **************************************************/ + if (iwlagn_mac_setup_register(priv, &fw->ucode_capa)) + goto out_destroy_workqueue; + + iwl_dbgfs_register(priv, dbgfs_dir); + + return op_mode; + +out_destroy_workqueue: + iwl_tt_exit(priv); + iwl_cancel_deferred_work(priv); + destroy_workqueue(priv->workqueue); + priv->workqueue = NULL; + iwl_uninit_drv(priv); +out_free_eeprom_blob: + kfree(priv->eeprom_blob); +out_free_eeprom: + kfree(priv->nvm_data); +out_free_hw: + ieee80211_free_hw(priv->hw); +out: + op_mode = NULL; + return op_mode; +} + +static void iwl_op_mode_dvm_stop(struct iwl_op_mode *op_mode) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + + IWL_DEBUG_INFO(priv, "*** UNLOAD DRIVER ***\n"); + + iwlagn_mac_unregister(priv); + + iwl_tt_exit(priv); + + kfree(priv->eeprom_blob); + kfree(priv->nvm_data); + + /*netif_stop_queue(dev); */ + + /* ieee80211_unregister_hw calls iwlagn_mac_stop, which flushes + * priv->workqueue... so we can't take down the workqueue + * until now... */ + destroy_workqueue(priv->workqueue); + priv->workqueue = NULL; + + iwl_uninit_drv(priv); + + dev_kfree_skb(priv->beacon_skb); + + iwl_trans_op_mode_leave(priv->trans); + ieee80211_free_hw(priv->hw); +} + +static const char * const desc_lookup_text[] = { + "OK", + "FAIL", + "BAD_PARAM", + "BAD_CHECKSUM", + "NMI_INTERRUPT_WDG", + "SYSASSERT", + "FATAL_ERROR", + "BAD_COMMAND", + "HW_ERROR_TUNE_LOCK", + "HW_ERROR_TEMPERATURE", + "ILLEGAL_CHAN_FREQ", + "VCC_NOT_STABLE", + "FH_ERROR", + "NMI_INTERRUPT_HOST", + "NMI_INTERRUPT_ACTION_PT", + "NMI_INTERRUPT_UNKNOWN", + "UCODE_VERSION_MISMATCH", + "HW_ERROR_ABS_LOCK", + "HW_ERROR_CAL_LOCK_FAIL", + "NMI_INTERRUPT_INST_ACTION_PT", + "NMI_INTERRUPT_DATA_ACTION_PT", + "NMI_TRM_HW_ER", + "NMI_INTERRUPT_TRM", + "NMI_INTERRUPT_BREAK_POINT", + "DEBUG_0", + "DEBUG_1", + "DEBUG_2", + "DEBUG_3", +}; + +static struct { char *name; u8 num; } advanced_lookup[] = { + { "NMI_INTERRUPT_WDG", 0x34 }, + { "SYSASSERT", 0x35 }, + { "UCODE_VERSION_MISMATCH", 0x37 }, + { "BAD_COMMAND", 0x38 }, + { "NMI_INTERRUPT_DATA_ACTION_PT", 0x3C }, + { "FATAL_ERROR", 0x3D }, + { "NMI_TRM_HW_ERR", 0x46 }, + { "NMI_INTERRUPT_TRM", 0x4C }, + { "NMI_INTERRUPT_BREAK_POINT", 0x54 }, + { "NMI_INTERRUPT_WDG_RXF_FULL", 0x5C }, + { "NMI_INTERRUPT_WDG_NO_RBD_RXF_FULL", 0x64 }, + { "NMI_INTERRUPT_HOST", 0x66 }, + { "NMI_INTERRUPT_ACTION_PT", 0x7C }, + { "NMI_INTERRUPT_UNKNOWN", 0x84 }, + { "NMI_INTERRUPT_INST_ACTION_PT", 0x86 }, + { "ADVANCED_SYSASSERT", 0 }, +}; + +static const char *desc_lookup(u32 num) +{ + int i; + int max = ARRAY_SIZE(desc_lookup_text); + + if (num < max) + return desc_lookup_text[num]; + + max = ARRAY_SIZE(advanced_lookup) - 1; + for (i = 0; i < max; i++) { + if (advanced_lookup[i].num == num) + break; + } + return advanced_lookup[i].name; +} + +#define ERROR_START_OFFSET (1 * sizeof(u32)) +#define ERROR_ELEM_SIZE (7 * sizeof(u32)) + +static void iwl_dump_nic_error_log(struct iwl_priv *priv) +{ + struct iwl_trans *trans = priv->trans; + u32 base; + struct iwl_error_event_table table; + + base = priv->device_pointers.error_event_table; + if (priv->cur_ucode == IWL_UCODE_INIT) { + if (!base) + base = priv->fw->init_errlog_ptr; + } else { + if (!base) + base = priv->fw->inst_errlog_ptr; + } + + if (!iwlagn_hw_valid_rtc_data_addr(base)) { + IWL_ERR(priv, + "Not valid error log pointer 0x%08X for %s uCode\n", + base, + (priv->cur_ucode == IWL_UCODE_INIT) + ? "Init" : "RT"); + return; + } + + /*TODO: Update dbgfs with ISR error stats obtained below */ + iwl_trans_read_mem_bytes(trans, base, &table, sizeof(table)); + + if (ERROR_START_OFFSET <= table.valid * ERROR_ELEM_SIZE) { + IWL_ERR(trans, "Start IWL Error Log Dump:\n"); + IWL_ERR(trans, "Status: 0x%08lX, count: %d\n", + priv->status, table.valid); + } + + IWL_ERR(priv, "0x%08X | %-28s\n", table.error_id, + desc_lookup(table.error_id)); + IWL_ERR(priv, "0x%08X | uPc\n", table.pc); + IWL_ERR(priv, "0x%08X | branchlink1\n", table.blink1); + IWL_ERR(priv, "0x%08X | branchlink2\n", table.blink2); + IWL_ERR(priv, "0x%08X | interruptlink1\n", table.ilink1); + IWL_ERR(priv, "0x%08X | interruptlink2\n", table.ilink2); + IWL_ERR(priv, "0x%08X | data1\n", table.data1); + IWL_ERR(priv, "0x%08X | data2\n", table.data2); + IWL_ERR(priv, "0x%08X | line\n", table.line); + IWL_ERR(priv, "0x%08X | beacon time\n", table.bcon_time); + IWL_ERR(priv, "0x%08X | tsf low\n", table.tsf_low); + IWL_ERR(priv, "0x%08X | tsf hi\n", table.tsf_hi); + IWL_ERR(priv, "0x%08X | time gp1\n", table.gp1); + IWL_ERR(priv, "0x%08X | time gp2\n", table.gp2); + IWL_ERR(priv, "0x%08X | time gp3\n", table.gp3); + IWL_ERR(priv, "0x%08X | uCode version\n", table.ucode_ver); + IWL_ERR(priv, "0x%08X | hw version\n", table.hw_ver); + IWL_ERR(priv, "0x%08X | board version\n", table.brd_ver); + IWL_ERR(priv, "0x%08X | hcmd\n", table.hcmd); + IWL_ERR(priv, "0x%08X | isr0\n", table.isr0); + IWL_ERR(priv, "0x%08X | isr1\n", table.isr1); + IWL_ERR(priv, "0x%08X | isr2\n", table.isr2); + IWL_ERR(priv, "0x%08X | isr3\n", table.isr3); + IWL_ERR(priv, "0x%08X | isr4\n", table.isr4); + IWL_ERR(priv, "0x%08X | isr_pref\n", table.isr_pref); + IWL_ERR(priv, "0x%08X | wait_event\n", table.wait_event); + IWL_ERR(priv, "0x%08X | l2p_control\n", table.l2p_control); + IWL_ERR(priv, "0x%08X | l2p_duration\n", table.l2p_duration); + IWL_ERR(priv, "0x%08X | l2p_mhvalid\n", table.l2p_mhvalid); + IWL_ERR(priv, "0x%08X | l2p_addr_match\n", table.l2p_addr_match); + IWL_ERR(priv, "0x%08X | lmpm_pmg_sel\n", table.lmpm_pmg_sel); + IWL_ERR(priv, "0x%08X | timestamp\n", table.u_timestamp); + IWL_ERR(priv, "0x%08X | flow_handler\n", table.flow_handler); +} + +#define EVENT_START_OFFSET (4 * sizeof(u32)) + +/* + * iwl_print_event_log - Dump error event log to syslog + */ +static int iwl_print_event_log(struct iwl_priv *priv, u32 start_idx, + u32 num_events, u32 mode, + int pos, char **buf, size_t bufsz) +{ + u32 i; + u32 base; /* SRAM byte address of event log header */ + u32 event_size; /* 2 u32s, or 3 u32s if timestamp recorded */ + u32 ptr; /* SRAM byte address of log data */ + u32 ev, time, data; /* event log data */ + + struct iwl_trans *trans = priv->trans; + + if (num_events == 0) + return pos; + + base = priv->device_pointers.log_event_table; + if (priv->cur_ucode == IWL_UCODE_INIT) { + if (!base) + base = priv->fw->init_evtlog_ptr; + } else { + if (!base) + base = priv->fw->inst_evtlog_ptr; + } + + if (mode == 0) + event_size = 2 * sizeof(u32); + else + event_size = 3 * sizeof(u32); + + ptr = base + EVENT_START_OFFSET + (start_idx * event_size); + + /* Make sure device is powered up for SRAM reads */ + if (!iwl_trans_grab_nic_access(trans)) + return pos; + + /* Set starting address; reads will auto-increment */ + iwl_write32(trans, HBUS_TARG_MEM_RADDR, ptr); + + /* "time" is actually "data" for mode 0 (no timestamp). + * place event id # at far right for easier visual parsing. */ + for (i = 0; i < num_events; i++) { + ev = iwl_read32(trans, HBUS_TARG_MEM_RDAT); + time = iwl_read32(trans, HBUS_TARG_MEM_RDAT); + if (mode == 0) { + /* data, ev */ + if (bufsz) { + pos += scnprintf(*buf + pos, bufsz - pos, + "EVT_LOG:0x%08x:%04u\n", + time, ev); + } else { + trace_iwlwifi_dev_ucode_event(trans->dev, 0, + time, ev); + IWL_ERR(priv, "EVT_LOG:0x%08x:%04u\n", + time, ev); + } + } else { + data = iwl_read32(trans, HBUS_TARG_MEM_RDAT); + if (bufsz) { + pos += scnprintf(*buf + pos, bufsz - pos, + "EVT_LOGT:%010u:0x%08x:%04u\n", + time, data, ev); + } else { + IWL_ERR(priv, "EVT_LOGT:%010u:0x%08x:%04u\n", + time, data, ev); + trace_iwlwifi_dev_ucode_event(trans->dev, time, + data, ev); + } + } + } + + /* Allow device to power down */ + iwl_trans_release_nic_access(trans); + return pos; +} + +/* + * iwl_print_last_event_logs - Dump the newest # of event log to syslog + */ +static int iwl_print_last_event_logs(struct iwl_priv *priv, u32 capacity, + u32 num_wraps, u32 next_entry, + u32 size, u32 mode, + int pos, char **buf, size_t bufsz) +{ + /* + * display the newest DEFAULT_LOG_ENTRIES entries + * i.e the entries just before the next ont that uCode would fill. + */ + if (num_wraps) { + if (next_entry < size) { + pos = iwl_print_event_log(priv, + capacity - (size - next_entry), + size - next_entry, mode, + pos, buf, bufsz); + pos = iwl_print_event_log(priv, 0, + next_entry, mode, + pos, buf, bufsz); + } else + pos = iwl_print_event_log(priv, next_entry - size, + size, mode, pos, buf, bufsz); + } else { + if (next_entry < size) { + pos = iwl_print_event_log(priv, 0, next_entry, + mode, pos, buf, bufsz); + } else { + pos = iwl_print_event_log(priv, next_entry - size, + size, mode, pos, buf, bufsz); + } + } + return pos; +} + +#define DEFAULT_DUMP_EVENT_LOG_ENTRIES (20) + +int iwl_dump_nic_event_log(struct iwl_priv *priv, bool full_log, + char **buf) +{ + u32 base; /* SRAM byte address of event log header */ + u32 capacity; /* event log capacity in # entries */ + u32 mode; /* 0 - no timestamp, 1 - timestamp recorded */ + u32 num_wraps; /* # times uCode wrapped to top of log */ + u32 next_entry; /* index of next entry to be written by uCode */ + u32 size; /* # entries that we'll print */ + u32 logsize; + int pos = 0; + size_t bufsz = 0; + struct iwl_trans *trans = priv->trans; + + base = priv->device_pointers.log_event_table; + if (priv->cur_ucode == IWL_UCODE_INIT) { + logsize = priv->fw->init_evtlog_size; + if (!base) + base = priv->fw->init_evtlog_ptr; + } else { + logsize = priv->fw->inst_evtlog_size; + if (!base) + base = priv->fw->inst_evtlog_ptr; + } + + if (!iwlagn_hw_valid_rtc_data_addr(base)) { + IWL_ERR(priv, + "Invalid event log pointer 0x%08X for %s uCode\n", + base, + (priv->cur_ucode == IWL_UCODE_INIT) + ? "Init" : "RT"); + return -EINVAL; + } + + /* event log header */ + capacity = iwl_trans_read_mem32(trans, base); + mode = iwl_trans_read_mem32(trans, base + (1 * sizeof(u32))); + num_wraps = iwl_trans_read_mem32(trans, base + (2 * sizeof(u32))); + next_entry = iwl_trans_read_mem32(trans, base + (3 * sizeof(u32))); + + if (capacity > logsize) { + IWL_ERR(priv, "Log capacity %d is bogus, limit to %d " + "entries\n", capacity, logsize); + capacity = logsize; + } + + if (next_entry > logsize) { + IWL_ERR(priv, "Log write index %d is bogus, limit to %d\n", + next_entry, logsize); + next_entry = logsize; + } + + size = num_wraps ? capacity : next_entry; + + /* bail out if nothing in log */ + if (size == 0) { + IWL_ERR(trans, "Start IWL Event Log Dump: nothing in log\n"); + return pos; + } + + if (!(iwl_have_debug_level(IWL_DL_FW)) && !full_log) + size = (size > DEFAULT_DUMP_EVENT_LOG_ENTRIES) + ? DEFAULT_DUMP_EVENT_LOG_ENTRIES : size; + IWL_ERR(priv, "Start IWL Event Log Dump: display last %u entries\n", + size); + +#ifdef CONFIG_IWLWIFI_DEBUG + if (buf) { + if (full_log) + bufsz = capacity * 48; + else + bufsz = size * 48; + *buf = kmalloc(bufsz, GFP_KERNEL); + if (!*buf) + return -ENOMEM; + } + if (iwl_have_debug_level(IWL_DL_FW) || full_log) { + /* + * if uCode has wrapped back to top of log, + * start at the oldest entry, + * i.e the next one that uCode would fill. + */ + if (num_wraps) + pos = iwl_print_event_log(priv, next_entry, + capacity - next_entry, mode, + pos, buf, bufsz); + /* (then/else) start at top of log */ + pos = iwl_print_event_log(priv, 0, + next_entry, mode, pos, buf, bufsz); + } else + pos = iwl_print_last_event_logs(priv, capacity, num_wraps, + next_entry, size, mode, + pos, buf, bufsz); +#else + pos = iwl_print_last_event_logs(priv, capacity, num_wraps, + next_entry, size, mode, + pos, buf, bufsz); +#endif + return pos; +} + +static void iwlagn_fw_error(struct iwl_priv *priv, bool ondemand) +{ + unsigned int reload_msec; + unsigned long reload_jiffies; + + if (iwl_have_debug_level(IWL_DL_FW)) + iwl_print_rx_config_cmd(priv, IWL_RXON_CTX_BSS); + + /* uCode is no longer loaded. */ + priv->ucode_loaded = false; + + /* Set the FW error flag -- cleared on iwl_down */ + set_bit(STATUS_FW_ERROR, &priv->status); + + iwl_abort_notification_waits(&priv->notif_wait); + + /* Keep the restart process from trying to send host + * commands by clearing the ready bit */ + clear_bit(STATUS_READY, &priv->status); + + if (!ondemand) { + /* + * If firmware keep reloading, then it indicate something + * serious wrong and firmware having problem to recover + * from it. Instead of keep trying which will fill the syslog + * and hang the system, let's just stop it + */ + reload_jiffies = jiffies; + reload_msec = jiffies_to_msecs((long) reload_jiffies - + (long) priv->reload_jiffies); + priv->reload_jiffies = reload_jiffies; + if (reload_msec <= IWL_MIN_RELOAD_DURATION) { + priv->reload_count++; + if (priv->reload_count >= IWL_MAX_CONTINUE_RELOAD_CNT) { + IWL_ERR(priv, "BUG_ON, Stop restarting\n"); + return; + } + } else + priv->reload_count = 0; + } + + if (!test_bit(STATUS_EXIT_PENDING, &priv->status)) { + if (iwlwifi_mod_params.fw_restart) { + IWL_DEBUG_FW(priv, + "Restarting adapter due to uCode error.\n"); + queue_work(priv->workqueue, &priv->restart); + } else + IWL_DEBUG_FW(priv, + "Detected FW error, but not restarting\n"); + } +} + +static void iwl_nic_error(struct iwl_op_mode *op_mode, bool sync) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + + IWL_ERR(priv, "Loaded firmware version: %s\n", + priv->fw->fw_version); + + iwl_dump_nic_error_log(priv); + iwl_dump_nic_event_log(priv, false, NULL); + + iwlagn_fw_error(priv, false); +} + +static void iwl_cmd_queue_full(struct iwl_op_mode *op_mode) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + + if (!iwl_check_for_ct_kill(priv)) { + IWL_ERR(priv, "Restarting adapter queue is full\n"); + iwlagn_fw_error(priv, false); + } +} + +#define EEPROM_RF_CONFIG_TYPE_MAX 0x3 + +static void iwl_nic_config(struct iwl_op_mode *op_mode) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + + /* SKU Control */ + iwl_trans_set_bits_mask(priv->trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_MSK_MAC_STEP_DASH, + CSR_HW_REV_STEP_DASH(priv->trans->hw_rev)); + + /* write radio config values to register */ + if (priv->nvm_data->radio_cfg_type <= EEPROM_RF_CONFIG_TYPE_MAX) { + u32 reg_val = + priv->nvm_data->radio_cfg_type << + CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE | + priv->nvm_data->radio_cfg_step << + CSR_HW_IF_CONFIG_REG_POS_PHY_STEP | + priv->nvm_data->radio_cfg_dash << + CSR_HW_IF_CONFIG_REG_POS_PHY_DASH; + + iwl_trans_set_bits_mask(priv->trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE | + CSR_HW_IF_CONFIG_REG_MSK_PHY_STEP | + CSR_HW_IF_CONFIG_REG_MSK_PHY_DASH, + reg_val); + + IWL_INFO(priv, "Radio type=0x%x-0x%x-0x%x\n", + priv->nvm_data->radio_cfg_type, + priv->nvm_data->radio_cfg_step, + priv->nvm_data->radio_cfg_dash); + } else { + WARN_ON(1); + } + + /* set CSR_HW_CONFIG_REG for uCode use */ + iwl_set_bit(priv->trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI | + CSR_HW_IF_CONFIG_REG_BIT_MAC_SI); + + /* W/A : NIC is stuck in a reset state after Early PCIe power off + * (PCIe power is lost before PERST# is asserted), + * causing ME FW to lose ownership and not being able to obtain it back. + */ + iwl_set_bits_mask_prph(priv->trans, APMG_PS_CTRL_REG, + APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS, + ~APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS); + + if (priv->lib->nic_config) + priv->lib->nic_config(priv); +} + +static void iwl_wimax_active(struct iwl_op_mode *op_mode) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + + clear_bit(STATUS_READY, &priv->status); + IWL_ERR(priv, "RF is used by WiMAX\n"); +} + +static void iwl_stop_sw_queue(struct iwl_op_mode *op_mode, int queue) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + int mq = priv->queue_to_mac80211[queue]; + + if (WARN_ON_ONCE(mq == IWL_INVALID_MAC80211_QUEUE)) + return; + + if (atomic_inc_return(&priv->queue_stop_count[mq]) > 1) { + IWL_DEBUG_TX_QUEUES(priv, + "queue %d (mac80211 %d) already stopped\n", + queue, mq); + return; + } + + set_bit(mq, &priv->transport_queue_stop); + ieee80211_stop_queue(priv->hw, mq); +} + +static void iwl_wake_sw_queue(struct iwl_op_mode *op_mode, int queue) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + int mq = priv->queue_to_mac80211[queue]; + + if (WARN_ON_ONCE(mq == IWL_INVALID_MAC80211_QUEUE)) + return; + + if (atomic_dec_return(&priv->queue_stop_count[mq]) > 0) { + IWL_DEBUG_TX_QUEUES(priv, + "queue %d (mac80211 %d) already awake\n", + queue, mq); + return; + } + + clear_bit(mq, &priv->transport_queue_stop); + + if (!priv->passive_no_rx) + ieee80211_wake_queue(priv->hw, mq); +} + +void iwlagn_lift_passive_no_rx(struct iwl_priv *priv) +{ + int mq; + + if (!priv->passive_no_rx) + return; + + for (mq = 0; mq < IWLAGN_FIRST_AMPDU_QUEUE; mq++) { + if (!test_bit(mq, &priv->transport_queue_stop)) { + IWL_DEBUG_TX_QUEUES(priv, "Wake queue %d\n", mq); + ieee80211_wake_queue(priv->hw, mq); + } else { + IWL_DEBUG_TX_QUEUES(priv, "Don't wake queue %d\n", mq); + } + } + + priv->passive_no_rx = false; +} + +static void iwl_free_skb(struct iwl_op_mode *op_mode, struct sk_buff *skb) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + struct ieee80211_tx_info *info; + + info = IEEE80211_SKB_CB(skb); + iwl_trans_free_tx_cmd(priv->trans, info->driver_data[1]); + ieee80211_free_txskb(priv->hw, skb); +} + +static bool iwl_set_hw_rfkill_state(struct iwl_op_mode *op_mode, bool state) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + + if (state) + set_bit(STATUS_RF_KILL_HW, &priv->status); + else + clear_bit(STATUS_RF_KILL_HW, &priv->status); + + wiphy_rfkill_set_hw_state(priv->hw->wiphy, state); + + return false; +} + +static const struct iwl_op_mode_ops iwl_dvm_ops = { + .start = iwl_op_mode_dvm_start, + .stop = iwl_op_mode_dvm_stop, + .rx = iwl_rx_dispatch, + .queue_full = iwl_stop_sw_queue, + .queue_not_full = iwl_wake_sw_queue, + .hw_rf_kill = iwl_set_hw_rfkill_state, + .free_skb = iwl_free_skb, + .nic_error = iwl_nic_error, + .cmd_queue_full = iwl_cmd_queue_full, + .nic_config = iwl_nic_config, + .wimax_active = iwl_wimax_active, +}; + +/***************************************************************************** + * + * driver and module entry point + * + *****************************************************************************/ +static int __init iwl_init(void) +{ + + int ret; + + ret = iwlagn_rate_control_register(); + if (ret) { + pr_err("Unable to register rate control algorithm: %d\n", ret); + return ret; + } + + ret = iwl_opmode_register("iwldvm", &iwl_dvm_ops); + if (ret) { + pr_err("Unable to register op_mode: %d\n", ret); + iwlagn_rate_control_unregister(); + } + + return ret; +} +module_init(iwl_init); + +static void __exit iwl_exit(void) +{ + iwl_opmode_deregister("iwldvm"); + iwlagn_rate_control_unregister(); +} +module_exit(iwl_exit); diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/power.c b/drivers/net/wireless/intel/iwlwifi/dvm/power.c new file mode 100644 index 000000000..6d16a7105 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/power.c @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2007 - 2014 Intel Corporation. All rights reserved. + * Copyright (C) 2019 Intel Corporation + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + *****************************************************************************/ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <net/mac80211.h> +#include "iwl-io.h" +#include "iwl-debug.h" +#include "iwl-trans.h" +#include "iwl-modparams.h" +#include "dev.h" +#include "agn.h" +#include "commands.h" +#include "power.h" + +static bool force_cam = true; +module_param(force_cam, bool, 0644); +MODULE_PARM_DESC(force_cam, "force continuously aware mode (no power saving at all)"); + +/* + * Setting power level allows the card to go to sleep when not busy. + * + * We calculate a sleep command based on the required latency, which + * we get from mac80211. In order to handle thermal throttling, we can + * also use pre-defined power levels. + */ + +/* + * This defines the old power levels. They are still used by default + * (level 1) and for thermal throttle (levels 3 through 5) + */ + +struct iwl_power_vec_entry { + struct iwl_powertable_cmd cmd; + u8 no_dtim; /* number of skip dtim */ +}; + +#define IWL_DTIM_RANGE_0_MAX 2 +#define IWL_DTIM_RANGE_1_MAX 10 + +#define NOSLP cpu_to_le16(0), 0, 0 +#define SLP IWL_POWER_DRIVER_ALLOW_SLEEP_MSK, 0, 0 +#define ASLP (IWL_POWER_POWER_SAVE_ENA_MSK | \ + IWL_POWER_POWER_MANAGEMENT_ENA_MSK | \ + IWL_POWER_ADVANCE_PM_ENA_MSK) +#define ASLP_TOUT(T) cpu_to_le32(T) +#define TU_TO_USEC 1024 +#define SLP_TOUT(T) cpu_to_le32((T) * TU_TO_USEC) +#define SLP_VEC(X0, X1, X2, X3, X4) {cpu_to_le32(X0), \ + cpu_to_le32(X1), \ + cpu_to_le32(X2), \ + cpu_to_le32(X3), \ + cpu_to_le32(X4)} +/* default power management (not Tx power) table values */ +/* for DTIM period 0 through IWL_DTIM_RANGE_0_MAX */ +/* DTIM 0 - 2 */ +static const struct iwl_power_vec_entry range_0[IWL_POWER_NUM] = { + {{SLP, SLP_TOUT(200), SLP_TOUT(500), SLP_VEC(1, 1, 2, 2, 0xFF)}, 0}, + {{SLP, SLP_TOUT(200), SLP_TOUT(300), SLP_VEC(1, 2, 2, 2, 0xFF)}, 0}, + {{SLP, SLP_TOUT(50), SLP_TOUT(100), SLP_VEC(2, 2, 2, 2, 0xFF)}, 0}, + {{SLP, SLP_TOUT(50), SLP_TOUT(25), SLP_VEC(2, 2, 4, 4, 0xFF)}, 1}, + {{SLP, SLP_TOUT(25), SLP_TOUT(25), SLP_VEC(2, 2, 4, 6, 0xFF)}, 2} +}; + + +/* for DTIM period IWL_DTIM_RANGE_0_MAX + 1 through IWL_DTIM_RANGE_1_MAX */ +/* DTIM 3 - 10 */ +static const struct iwl_power_vec_entry range_1[IWL_POWER_NUM] = { + {{SLP, SLP_TOUT(200), SLP_TOUT(500), SLP_VEC(1, 2, 3, 4, 4)}, 0}, + {{SLP, SLP_TOUT(200), SLP_TOUT(300), SLP_VEC(1, 2, 3, 4, 7)}, 0}, + {{SLP, SLP_TOUT(50), SLP_TOUT(100), SLP_VEC(2, 4, 6, 7, 9)}, 0}, + {{SLP, SLP_TOUT(50), SLP_TOUT(25), SLP_VEC(2, 4, 6, 9, 10)}, 1}, + {{SLP, SLP_TOUT(25), SLP_TOUT(25), SLP_VEC(2, 4, 6, 10, 10)}, 2} +}; + +/* for DTIM period > IWL_DTIM_RANGE_1_MAX */ +/* DTIM 11 - */ +static const struct iwl_power_vec_entry range_2[IWL_POWER_NUM] = { + {{SLP, SLP_TOUT(200), SLP_TOUT(500), SLP_VEC(1, 2, 3, 4, 0xFF)}, 0}, + {{SLP, SLP_TOUT(200), SLP_TOUT(300), SLP_VEC(2, 4, 6, 7, 0xFF)}, 0}, + {{SLP, SLP_TOUT(50), SLP_TOUT(100), SLP_VEC(2, 7, 9, 9, 0xFF)}, 0}, + {{SLP, SLP_TOUT(50), SLP_TOUT(25), SLP_VEC(2, 7, 9, 9, 0xFF)}, 0}, + {{SLP, SLP_TOUT(25), SLP_TOUT(25), SLP_VEC(4, 7, 10, 10, 0xFF)}, 0} +}; + +/* advance power management */ +/* DTIM 0 - 2 */ +static const struct iwl_power_vec_entry apm_range_0[IWL_POWER_NUM] = { + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 6, 8, 0xFF), ASLP_TOUT(2)}, 2} +}; + + +/* for DTIM period IWL_DTIM_RANGE_0_MAX + 1 through IWL_DTIM_RANGE_1_MAX */ +/* DTIM 3 - 10 */ +static const struct iwl_power_vec_entry apm_range_1[IWL_POWER_NUM] = { + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 6, 8, 0xFF), 0}, 2} +}; + +/* for DTIM period > IWL_DTIM_RANGE_1_MAX */ +/* DTIM 11 - */ +static const struct iwl_power_vec_entry apm_range_2[IWL_POWER_NUM] = { + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 6, 8, 0xFF), ASLP_TOUT(2)}, 2} +}; + +static void iwl_static_sleep_cmd(struct iwl_priv *priv, + struct iwl_powertable_cmd *cmd, + enum iwl_power_level lvl, int period) +{ + const struct iwl_power_vec_entry *table; + int max_sleep[IWL_POWER_VEC_SIZE] = { 0 }; + int i; + u8 skip; + u32 slp_itrvl; + + if (priv->lib->adv_pm) { + table = apm_range_2; + if (period <= IWL_DTIM_RANGE_1_MAX) + table = apm_range_1; + if (period <= IWL_DTIM_RANGE_0_MAX) + table = apm_range_0; + } else { + table = range_2; + if (period <= IWL_DTIM_RANGE_1_MAX) + table = range_1; + if (period <= IWL_DTIM_RANGE_0_MAX) + table = range_0; + } + + if (WARN_ON(lvl < 0 || lvl >= IWL_POWER_NUM)) + memset(cmd, 0, sizeof(*cmd)); + else + *cmd = table[lvl].cmd; + + if (period == 0) { + skip = 0; + period = 1; + for (i = 0; i < IWL_POWER_VEC_SIZE; i++) + max_sleep[i] = 1; + + } else { + skip = table[lvl].no_dtim; + for (i = 0; i < IWL_POWER_VEC_SIZE; i++) + max_sleep[i] = le32_to_cpu(cmd->sleep_interval[i]); + max_sleep[IWL_POWER_VEC_SIZE - 1] = skip + 1; + } + + slp_itrvl = le32_to_cpu(cmd->sleep_interval[IWL_POWER_VEC_SIZE - 1]); + /* figure out the listen interval based on dtim period and skip */ + if (slp_itrvl == 0xFF) + cmd->sleep_interval[IWL_POWER_VEC_SIZE - 1] = + cpu_to_le32(period * (skip + 1)); + + slp_itrvl = le32_to_cpu(cmd->sleep_interval[IWL_POWER_VEC_SIZE - 1]); + if (slp_itrvl > period) + cmd->sleep_interval[IWL_POWER_VEC_SIZE - 1] = + cpu_to_le32((slp_itrvl / period) * period); + + if (skip) + cmd->flags |= IWL_POWER_SLEEP_OVER_DTIM_MSK; + else + cmd->flags &= ~IWL_POWER_SLEEP_OVER_DTIM_MSK; + + if (priv->trans->trans_cfg->base_params->shadow_reg_enable) + cmd->flags |= IWL_POWER_SHADOW_REG_ENA; + else + cmd->flags &= ~IWL_POWER_SHADOW_REG_ENA; + + if (iwl_advanced_bt_coexist(priv)) { + if (!priv->lib->bt_params->bt_sco_disable) + cmd->flags |= IWL_POWER_BT_SCO_ENA; + else + cmd->flags &= ~IWL_POWER_BT_SCO_ENA; + } + + + slp_itrvl = le32_to_cpu(cmd->sleep_interval[IWL_POWER_VEC_SIZE - 1]); + if (slp_itrvl > IWL_CONN_MAX_LISTEN_INTERVAL) + cmd->sleep_interval[IWL_POWER_VEC_SIZE - 1] = + cpu_to_le32(IWL_CONN_MAX_LISTEN_INTERVAL); + + /* enforce max sleep interval */ + for (i = IWL_POWER_VEC_SIZE - 1; i >= 0 ; i--) { + if (le32_to_cpu(cmd->sleep_interval[i]) > + (max_sleep[i] * period)) + cmd->sleep_interval[i] = + cpu_to_le32(max_sleep[i] * period); + if (i != (IWL_POWER_VEC_SIZE - 1)) { + if (le32_to_cpu(cmd->sleep_interval[i]) > + le32_to_cpu(cmd->sleep_interval[i+1])) + cmd->sleep_interval[i] = + cmd->sleep_interval[i+1]; + } + } + + if (priv->power_data.bus_pm) + cmd->flags |= IWL_POWER_PCI_PM_MSK; + else + cmd->flags &= ~IWL_POWER_PCI_PM_MSK; + + IWL_DEBUG_POWER(priv, "numSkipDtim = %u, dtimPeriod = %d\n", + skip, period); + /* The power level here is 0-4 (used as array index), but user expects + to see 1-5 (according to spec). */ + IWL_DEBUG_POWER(priv, "Sleep command for index %d\n", lvl + 1); +} + +static void iwl_power_sleep_cam_cmd(struct iwl_priv *priv, + struct iwl_powertable_cmd *cmd) +{ + memset(cmd, 0, sizeof(*cmd)); + + if (priv->power_data.bus_pm) + cmd->flags |= IWL_POWER_PCI_PM_MSK; + + IWL_DEBUG_POWER(priv, "Sleep command for CAM\n"); +} + +static int iwl_set_power(struct iwl_priv *priv, struct iwl_powertable_cmd *cmd) +{ + IWL_DEBUG_POWER(priv, "Sending power/sleep command\n"); + IWL_DEBUG_POWER(priv, "Flags value = 0x%08X\n", cmd->flags); + IWL_DEBUG_POWER(priv, "Tx timeout = %u\n", le32_to_cpu(cmd->tx_data_timeout)); + IWL_DEBUG_POWER(priv, "Rx timeout = %u\n", le32_to_cpu(cmd->rx_data_timeout)); + IWL_DEBUG_POWER(priv, "Sleep interval vector = { %d , %d , %d , %d , %d }\n", + le32_to_cpu(cmd->sleep_interval[0]), + le32_to_cpu(cmd->sleep_interval[1]), + le32_to_cpu(cmd->sleep_interval[2]), + le32_to_cpu(cmd->sleep_interval[3]), + le32_to_cpu(cmd->sleep_interval[4])); + + return iwl_dvm_send_cmd_pdu(priv, POWER_TABLE_CMD, 0, + sizeof(struct iwl_powertable_cmd), cmd); +} + +static void iwl_power_build_cmd(struct iwl_priv *priv, + struct iwl_powertable_cmd *cmd) +{ + bool enabled = priv->hw->conf.flags & IEEE80211_CONF_PS; + int dtimper; + + if (force_cam) { + iwl_power_sleep_cam_cmd(priv, cmd); + return; + } + + dtimper = priv->hw->conf.ps_dtim_period ?: 1; + + if (priv->wowlan) + iwl_static_sleep_cmd(priv, cmd, IWL_POWER_INDEX_5, dtimper); + else if (!priv->lib->no_idle_support && + priv->hw->conf.flags & IEEE80211_CONF_IDLE) + iwl_static_sleep_cmd(priv, cmd, IWL_POWER_INDEX_5, 20); + else if (iwl_tt_is_low_power_state(priv)) { + /* in thermal throttling low power state */ + iwl_static_sleep_cmd(priv, cmd, + iwl_tt_current_power_mode(priv), dtimper); + } else if (!enabled) + iwl_power_sleep_cam_cmd(priv, cmd); + else if (priv->power_data.debug_sleep_level_override >= 0) + iwl_static_sleep_cmd(priv, cmd, + priv->power_data.debug_sleep_level_override, + dtimper); + else { + /* Note that the user parameter is 1-5 (according to spec), + but we pass 0-4 because it acts as an array index. */ + if (iwlwifi_mod_params.power_level > IWL_POWER_INDEX_1 && + iwlwifi_mod_params.power_level <= IWL_POWER_NUM) + iwl_static_sleep_cmd(priv, cmd, + iwlwifi_mod_params.power_level - 1, dtimper); + else + iwl_static_sleep_cmd(priv, cmd, + IWL_POWER_INDEX_1, dtimper); + } +} + +int iwl_power_set_mode(struct iwl_priv *priv, struct iwl_powertable_cmd *cmd, + bool force) +{ + int ret; + bool update_chains; + + lockdep_assert_held(&priv->mutex); + + /* Don't update the RX chain when chain noise calibration is running */ + update_chains = priv->chain_noise_data.state == IWL_CHAIN_NOISE_DONE || + priv->chain_noise_data.state == IWL_CHAIN_NOISE_ALIVE; + + if (!memcmp(&priv->power_data.sleep_cmd, cmd, sizeof(*cmd)) && !force) + return 0; + + if (!iwl_is_ready_rf(priv)) + return -EIO; + + /* scan complete use sleep_power_next, need to be updated */ + memcpy(&priv->power_data.sleep_cmd_next, cmd, sizeof(*cmd)); + if (test_bit(STATUS_SCANNING, &priv->status) && !force) { + IWL_DEBUG_INFO(priv, "Defer power set mode while scanning\n"); + return 0; + } + + if (cmd->flags & IWL_POWER_DRIVER_ALLOW_SLEEP_MSK) + iwl_dvm_set_pmi(priv, true); + + ret = iwl_set_power(priv, cmd); + if (!ret) { + if (!(cmd->flags & IWL_POWER_DRIVER_ALLOW_SLEEP_MSK)) + iwl_dvm_set_pmi(priv, false); + + if (update_chains) + iwl_update_chain_flags(priv); + else + IWL_DEBUG_POWER(priv, + "Cannot update the power, chain noise " + "calibration running: %d\n", + priv->chain_noise_data.state); + + memcpy(&priv->power_data.sleep_cmd, cmd, sizeof(*cmd)); + } else + IWL_ERR(priv, "set power fail, ret = %d\n", ret); + + return ret; +} + +int iwl_power_update_mode(struct iwl_priv *priv, bool force) +{ + struct iwl_powertable_cmd cmd; + + iwl_power_build_cmd(priv, &cmd); + return iwl_power_set_mode(priv, &cmd, force); +} + +/* initialize to default */ +void iwl_power_initialize(struct iwl_priv *priv) +{ + priv->power_data.bus_pm = priv->trans->pm_support; + + priv->power_data.debug_sleep_level_override = -1; + + memset(&priv->power_data.sleep_cmd, 0, + sizeof(priv->power_data.sleep_cmd)); +} diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/power.h b/drivers/net/wireless/intel/iwlwifi/dvm/power.h new file mode 100644 index 000000000..f38201ce1 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/power.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + * + * Copyright(c) 2007 - 2014 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + *****************************************************************************/ +#ifndef __iwl_power_setting_h__ +#define __iwl_power_setting_h__ + +#include "commands.h" + +struct iwl_power_mgr { + struct iwl_powertable_cmd sleep_cmd; + struct iwl_powertable_cmd sleep_cmd_next; + int debug_sleep_level_override; + bool bus_pm; +}; + +int iwl_power_set_mode(struct iwl_priv *priv, struct iwl_powertable_cmd *cmd, + bool force); +int iwl_power_update_mode(struct iwl_priv *priv, bool force); +void iwl_power_initialize(struct iwl_priv *priv); + +extern bool no_sleep_autoadjust; + +#endif /* __iwl_power_setting_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/rs.c b/drivers/net/wireless/intel/iwlwifi/dvm/rs.c new file mode 100644 index 000000000..687c906a9 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/rs.c @@ -0,0 +1,3292 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2005 - 2014 Intel Corporation. All rights reserved. + * Copyright (C) 2019 - 2020, 2022 Intel Corporation + *****************************************************************************/ +#include <linux/kernel.h> +#include <linux/skbuff.h> +#include <linux/slab.h> +#include <net/mac80211.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/delay.h> + +#include <linux/workqueue.h> + +#include "dev.h" +#include "agn.h" + +#define RS_NAME "iwl-agn-rs" + +#define NUM_TRY_BEFORE_ANT_TOGGLE 1 +#define IWL_NUMBER_TRY 1 +#define IWL_HT_NUMBER_TRY 3 + +#define IWL_RATE_MAX_WINDOW 62 /* # tx in history window */ +#define IWL_RATE_MIN_FAILURE_TH 6 /* min failures to calc tpt */ +#define IWL_RATE_MIN_SUCCESS_TH 8 /* min successes to calc tpt */ + +/* max allowed rate miss before sync LQ cmd */ +#define IWL_MISSED_RATE_MAX 15 +/* max time to accum history 2 seconds */ +#define IWL_RATE_SCALE_FLUSH_INTVL (3*HZ) + +static u8 rs_ht_to_legacy[] = { + IWL_RATE_6M_INDEX, IWL_RATE_6M_INDEX, + IWL_RATE_6M_INDEX, IWL_RATE_6M_INDEX, + IWL_RATE_6M_INDEX, + IWL_RATE_6M_INDEX, IWL_RATE_9M_INDEX, + IWL_RATE_12M_INDEX, IWL_RATE_18M_INDEX, + IWL_RATE_24M_INDEX, IWL_RATE_36M_INDEX, + IWL_RATE_48M_INDEX, IWL_RATE_54M_INDEX +}; + +static const u8 ant_toggle_lookup[] = { + /*ANT_NONE -> */ ANT_NONE, + /*ANT_A -> */ ANT_B, + /*ANT_B -> */ ANT_C, + /*ANT_AB -> */ ANT_BC, + /*ANT_C -> */ ANT_A, + /*ANT_AC -> */ ANT_AB, + /*ANT_BC -> */ ANT_AC, + /*ANT_ABC -> */ ANT_ABC, +}; + +#define IWL_DECLARE_RATE_INFO(r, s, ip, in, rp, rn, pp, np) \ + [IWL_RATE_##r##M_INDEX] = { IWL_RATE_##r##M_PLCP, \ + IWL_RATE_SISO_##s##M_PLCP, \ + IWL_RATE_MIMO2_##s##M_PLCP,\ + IWL_RATE_MIMO3_##s##M_PLCP,\ + IWL_RATE_##r##M_IEEE, \ + IWL_RATE_##ip##M_INDEX, \ + IWL_RATE_##in##M_INDEX, \ + IWL_RATE_##rp##M_INDEX, \ + IWL_RATE_##rn##M_INDEX, \ + IWL_RATE_##pp##M_INDEX, \ + IWL_RATE_##np##M_INDEX } + +/* + * Parameter order: + * rate, ht rate, prev rate, next rate, prev tgg rate, next tgg rate + * + * If there isn't a valid next or previous rate then INV is used which + * maps to IWL_RATE_INVALID + * + */ +const struct iwl_rate_info iwl_rates[IWL_RATE_COUNT] = { + IWL_DECLARE_RATE_INFO(1, INV, INV, 2, INV, 2, INV, 2), /* 1mbps */ + IWL_DECLARE_RATE_INFO(2, INV, 1, 5, 1, 5, 1, 5), /* 2mbps */ + IWL_DECLARE_RATE_INFO(5, INV, 2, 6, 2, 11, 2, 11), /*5.5mbps */ + IWL_DECLARE_RATE_INFO(11, INV, 9, 12, 9, 12, 5, 18), /* 11mbps */ + IWL_DECLARE_RATE_INFO(6, 6, 5, 9, 5, 11, 5, 11), /* 6mbps */ + IWL_DECLARE_RATE_INFO(9, 6, 6, 11, 6, 11, 5, 11), /* 9mbps */ + IWL_DECLARE_RATE_INFO(12, 12, 11, 18, 11, 18, 11, 18), /* 12mbps */ + IWL_DECLARE_RATE_INFO(18, 18, 12, 24, 12, 24, 11, 24), /* 18mbps */ + IWL_DECLARE_RATE_INFO(24, 24, 18, 36, 18, 36, 18, 36), /* 24mbps */ + IWL_DECLARE_RATE_INFO(36, 36, 24, 48, 24, 48, 24, 48), /* 36mbps */ + IWL_DECLARE_RATE_INFO(48, 48, 36, 54, 36, 54, 36, 54), /* 48mbps */ + IWL_DECLARE_RATE_INFO(54, 54, 48, INV, 48, INV, 48, INV),/* 54mbps */ + IWL_DECLARE_RATE_INFO(60, 60, 48, INV, 48, INV, 48, INV),/* 60mbps */ + /* FIXME:RS: ^^ should be INV (legacy) */ +}; + +static inline u8 rs_extract_rate(u32 rate_n_flags) +{ + return (u8)(rate_n_flags & RATE_MCS_RATE_MSK); +} + +static int iwl_hwrate_to_plcp_idx(u32 rate_n_flags) +{ + int idx = 0; + + /* HT rate format */ + if (rate_n_flags & RATE_MCS_HT_MSK) { + idx = rs_extract_rate(rate_n_flags); + + if (idx >= IWL_RATE_MIMO3_6M_PLCP) + idx = idx - IWL_RATE_MIMO3_6M_PLCP; + else if (idx >= IWL_RATE_MIMO2_6M_PLCP) + idx = idx - IWL_RATE_MIMO2_6M_PLCP; + + idx += IWL_FIRST_OFDM_RATE; + /* skip 9M not supported in ht*/ + if (idx >= IWL_RATE_9M_INDEX) + idx += 1; + if ((idx >= IWL_FIRST_OFDM_RATE) && (idx <= IWL_LAST_OFDM_RATE)) + return idx; + + /* legacy rate format, search for match in table */ + } else { + for (idx = 0; idx < ARRAY_SIZE(iwl_rates); idx++) + if (iwl_rates[idx].plcp == + rs_extract_rate(rate_n_flags)) + return idx; + } + + return -1; +} + +static void rs_rate_scale_perform(struct iwl_priv *priv, + struct sk_buff *skb, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta); +static void rs_fill_link_cmd(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, u32 rate_n_flags); +static void rs_stay_in_table(struct iwl_lq_sta *lq_sta, bool force_search); + + +#ifdef CONFIG_MAC80211_DEBUGFS +static void rs_dbgfs_set_mcs(struct iwl_lq_sta *lq_sta, + u32 *rate_n_flags, int index); +#else +static void rs_dbgfs_set_mcs(struct iwl_lq_sta *lq_sta, + u32 *rate_n_flags, int index) +{} +#endif + +/* + * The following tables contain the expected throughput metrics for all rates + * + * 1, 2, 5.5, 11, 6, 9, 12, 18, 24, 36, 48, 54, 60 MBits + * + * where invalid entries are zeros. + * + * CCK rates are only valid in legacy table and will only be used in G + * (2.4 GHz) band. + */ + +static const u16 expected_tpt_legacy[IWL_RATE_COUNT] = { + 7, 13, 35, 58, 40, 57, 72, 98, 121, 154, 177, 186, 0 +}; + +static const u16 expected_tpt_siso20MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 42, 0, 76, 102, 124, 159, 183, 193, 202}, /* Norm */ + {0, 0, 0, 0, 46, 0, 82, 110, 132, 168, 192, 202, 210}, /* SGI */ + {0, 0, 0, 0, 47, 0, 91, 133, 171, 242, 305, 334, 362}, /* AGG */ + {0, 0, 0, 0, 52, 0, 101, 145, 187, 264, 330, 361, 390}, /* AGG+SGI */ +}; + +static const u16 expected_tpt_siso40MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 77, 0, 127, 160, 184, 220, 242, 250, 257}, /* Norm */ + {0, 0, 0, 0, 83, 0, 135, 169, 193, 229, 250, 257, 264}, /* SGI */ + {0, 0, 0, 0, 94, 0, 177, 249, 313, 423, 512, 550, 586}, /* AGG */ + {0, 0, 0, 0, 104, 0, 193, 270, 338, 454, 545, 584, 620}, /* AGG+SGI */ +}; + +static const u16 expected_tpt_mimo2_20MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 74, 0, 123, 155, 179, 214, 236, 244, 251}, /* Norm */ + {0, 0, 0, 0, 81, 0, 131, 164, 188, 223, 243, 251, 257}, /* SGI */ + {0, 0, 0, 0, 89, 0, 167, 235, 296, 402, 488, 526, 560}, /* AGG */ + {0, 0, 0, 0, 97, 0, 182, 255, 320, 431, 520, 558, 593}, /* AGG+SGI*/ +}; + +static const u16 expected_tpt_mimo2_40MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 123, 0, 182, 214, 235, 264, 279, 285, 289}, /* Norm */ + {0, 0, 0, 0, 131, 0, 191, 222, 242, 270, 284, 289, 293}, /* SGI */ + {0, 0, 0, 0, 171, 0, 305, 410, 496, 634, 731, 771, 805}, /* AGG */ + {0, 0, 0, 0, 186, 0, 329, 439, 527, 667, 764, 803, 838}, /* AGG+SGI */ +}; + +static const u16 expected_tpt_mimo3_20MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 99, 0, 153, 186, 208, 239, 256, 263, 268}, /* Norm */ + {0, 0, 0, 0, 106, 0, 162, 194, 215, 246, 262, 268, 273}, /* SGI */ + {0, 0, 0, 0, 134, 0, 249, 346, 431, 574, 685, 732, 775}, /* AGG */ + {0, 0, 0, 0, 148, 0, 272, 376, 465, 614, 727, 775, 818}, /* AGG+SGI */ +}; + +static const u16 expected_tpt_mimo3_40MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 152, 0, 211, 239, 255, 279, 290, 294, 297}, /* Norm */ + {0, 0, 0, 0, 160, 0, 219, 245, 261, 284, 294, 297, 300}, /* SGI */ + {0, 0, 0, 0, 254, 0, 443, 584, 695, 868, 984, 1030, 1070}, /* AGG */ + {0, 0, 0, 0, 277, 0, 478, 624, 737, 911, 1026, 1070, 1109}, /* AGG+SGI */ +}; + +/* mbps, mcs */ +static const struct iwl_rate_mcs_info iwl_rate_mcs[IWL_RATE_COUNT] = { + { "1", "BPSK DSSS"}, + { "2", "QPSK DSSS"}, + {"5.5", "BPSK CCK"}, + { "11", "QPSK CCK"}, + { "6", "BPSK 1/2"}, + { "9", "BPSK 1/2"}, + { "12", "QPSK 1/2"}, + { "18", "QPSK 3/4"}, + { "24", "16QAM 1/2"}, + { "36", "16QAM 3/4"}, + { "48", "64QAM 2/3"}, + { "54", "64QAM 3/4"}, + { "60", "64QAM 5/6"}, +}; + +#define MCS_INDEX_PER_STREAM (8) + +static void rs_rate_scale_clear_window(struct iwl_rate_scale_data *window) +{ + window->data = 0; + window->success_counter = 0; + window->success_ratio = IWL_INVALID_VALUE; + window->counter = 0; + window->average_tpt = IWL_INVALID_VALUE; + window->stamp = 0; +} + +static inline u8 rs_is_valid_ant(u8 valid_antenna, u8 ant_type) +{ + return (ant_type & valid_antenna) == ant_type; +} + +/* + * removes the old data from the statistics. All data that is older than + * TID_MAX_TIME_DIFF, will be deleted. + */ +static void rs_tl_rm_old_stats(struct iwl_traffic_load *tl, u32 curr_time) +{ + /* The oldest age we want to keep */ + u32 oldest_time = curr_time - TID_MAX_TIME_DIFF; + + while (tl->queue_count && + (tl->time_stamp < oldest_time)) { + tl->total -= tl->packet_count[tl->head]; + tl->packet_count[tl->head] = 0; + tl->time_stamp += TID_QUEUE_CELL_SPACING; + tl->queue_count--; + tl->head++; + if (tl->head >= TID_QUEUE_MAX_SIZE) + tl->head = 0; + } +} + +/* + * increment traffic load value for tid and also remove + * any old values if passed the certain time period + */ +static u8 rs_tl_add_packet(struct iwl_lq_sta *lq_data, + struct ieee80211_hdr *hdr) +{ + u32 curr_time = jiffies_to_msecs(jiffies); + u32 time_diff; + s32 index; + struct iwl_traffic_load *tl = NULL; + u8 tid; + + if (ieee80211_is_data_qos(hdr->frame_control)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + tid = qc[0] & 0xf; + } else + return IWL_MAX_TID_COUNT; + + if (unlikely(tid >= IWL_MAX_TID_COUNT)) + return IWL_MAX_TID_COUNT; + + tl = &lq_data->load[tid]; + + curr_time -= curr_time % TID_ROUND_VALUE; + + /* Happens only for the first packet. Initialize the data */ + if (!(tl->queue_count)) { + tl->total = 1; + tl->time_stamp = curr_time; + tl->queue_count = 1; + tl->head = 0; + tl->packet_count[0] = 1; + return IWL_MAX_TID_COUNT; + } + + time_diff = TIME_WRAP_AROUND(tl->time_stamp, curr_time); + index = time_diff / TID_QUEUE_CELL_SPACING; + + /* The history is too long: remove data that is older than */ + /* TID_MAX_TIME_DIFF */ + if (index >= TID_QUEUE_MAX_SIZE) + rs_tl_rm_old_stats(tl, curr_time); + + index = (tl->head + index) % TID_QUEUE_MAX_SIZE; + tl->packet_count[index] = tl->packet_count[index] + 1; + tl->total = tl->total + 1; + + if ((index + 1) > tl->queue_count) + tl->queue_count = index + 1; + + return tid; +} + +#ifdef CONFIG_MAC80211_DEBUGFS +/* + * Program the device to use fixed rate for frame transmit + * This is for debugging/testing only + * once the device start use fixed rate, we need to reload the module + * to being back the normal operation. + */ +static void rs_program_fix_rate(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta) +{ + struct iwl_station_priv *sta_priv = + container_of(lq_sta, struct iwl_station_priv, lq_sta); + struct iwl_rxon_context *ctx = sta_priv->ctx; + + lq_sta->active_legacy_rate = 0x0FFF; /* 1 - 54 MBits, includes CCK */ + lq_sta->active_siso_rate = 0x1FD0; /* 6 - 60 MBits, no 9, no CCK */ + lq_sta->active_mimo2_rate = 0x1FD0; /* 6 - 60 MBits, no 9, no CCK */ + lq_sta->active_mimo3_rate = 0x1FD0; /* 6 - 60 MBits, no 9, no CCK */ + + IWL_DEBUG_RATE(priv, "sta_id %d rate 0x%X\n", + lq_sta->lq.sta_id, lq_sta->dbg_fixed_rate); + + if (lq_sta->dbg_fixed_rate) { + rs_fill_link_cmd(NULL, lq_sta, lq_sta->dbg_fixed_rate); + iwl_send_lq_cmd(lq_sta->drv, ctx, &lq_sta->lq, CMD_ASYNC, + false); + } +} +#endif + +/* + get the traffic load value for tid +*/ +static void rs_tl_get_load(struct iwl_lq_sta *lq_data, u8 tid) +{ + u32 curr_time = jiffies_to_msecs(jiffies); + u32 time_diff; + s32 index; + struct iwl_traffic_load *tl = NULL; + + if (tid >= IWL_MAX_TID_COUNT) + return; + + tl = &(lq_data->load[tid]); + + curr_time -= curr_time % TID_ROUND_VALUE; + + if (!(tl->queue_count)) + return; + + time_diff = TIME_WRAP_AROUND(tl->time_stamp, curr_time); + index = time_diff / TID_QUEUE_CELL_SPACING; + + /* The history is too long: remove data that is older than */ + /* TID_MAX_TIME_DIFF */ + if (index >= TID_QUEUE_MAX_SIZE) + rs_tl_rm_old_stats(tl, curr_time); +} + +static int rs_tl_turn_on_agg_for_tid(struct iwl_priv *priv, + struct iwl_lq_sta *lq_data, u8 tid, + struct ieee80211_sta *sta) +{ + int ret = -EAGAIN; + + /* + * Don't create TX aggregation sessions when in high + * BT traffic, as they would just be disrupted by BT. + */ + if (priv->bt_traffic_load >= IWL_BT_COEX_TRAFFIC_LOAD_HIGH) { + IWL_DEBUG_COEX(priv, + "BT traffic (%d), no aggregation allowed\n", + priv->bt_traffic_load); + return ret; + } + + rs_tl_get_load(lq_data, tid); + + IWL_DEBUG_HT(priv, "Starting Tx agg: STA: %pM tid: %d\n", + sta->addr, tid); + ret = ieee80211_start_tx_ba_session(sta, tid, 5000); + if (ret == -EAGAIN) { + /* + * driver and mac80211 is out of sync + * this might be cause by reloading firmware + * stop the tx ba session here + */ + IWL_ERR(priv, "Fail start Tx agg on tid: %d\n", + tid); + ieee80211_stop_tx_ba_session(sta, tid); + } + return ret; +} + +static void rs_tl_turn_on_agg(struct iwl_priv *priv, u8 tid, + struct iwl_lq_sta *lq_data, + struct ieee80211_sta *sta) +{ + if (tid < IWL_MAX_TID_COUNT) + rs_tl_turn_on_agg_for_tid(priv, lq_data, tid, sta); + else + IWL_ERR(priv, "tid exceeds max TID count: %d/%d\n", + tid, IWL_MAX_TID_COUNT); +} + +static inline int get_num_of_ant_from_rate(u32 rate_n_flags) +{ + return !!(rate_n_flags & RATE_MCS_ANT_A_MSK) + + !!(rate_n_flags & RATE_MCS_ANT_B_MSK) + + !!(rate_n_flags & RATE_MCS_ANT_C_MSK); +} + +/* + * Static function to get the expected throughput from an iwl_scale_tbl_info + * that wraps a NULL pointer check + */ +static s32 get_expected_tpt(struct iwl_scale_tbl_info *tbl, int rs_index) +{ + if (tbl->expected_tpt) + return tbl->expected_tpt[rs_index]; + return 0; +} + +/* + * rs_collect_tx_data - Update the success/failure sliding window + * + * We keep a sliding window of the last 62 packets transmitted + * at this rate. window->data contains the bitmask of successful + * packets. + */ +static int rs_collect_tx_data(struct iwl_scale_tbl_info *tbl, + int scale_index, int attempts, int successes) +{ + struct iwl_rate_scale_data *window = NULL; + static const u64 mask = (((u64)1) << (IWL_RATE_MAX_WINDOW - 1)); + s32 fail_count, tpt; + + if (scale_index < 0 || scale_index >= IWL_RATE_COUNT) + return -EINVAL; + + /* Select window for current tx bit rate */ + window = &(tbl->win[scale_index]); + + /* Get expected throughput */ + tpt = get_expected_tpt(tbl, scale_index); + + /* + * Keep track of only the latest 62 tx frame attempts in this rate's + * history window; anything older isn't really relevant any more. + * If we have filled up the sliding window, drop the oldest attempt; + * if the oldest attempt (highest bit in bitmap) shows "success", + * subtract "1" from the success counter (this is the main reason + * we keep these bitmaps!). + */ + while (attempts > 0) { + if (window->counter >= IWL_RATE_MAX_WINDOW) { + + /* remove earliest */ + window->counter = IWL_RATE_MAX_WINDOW - 1; + + if (window->data & mask) { + window->data &= ~mask; + window->success_counter--; + } + } + + /* Increment frames-attempted counter */ + window->counter++; + + /* Shift bitmap by one frame to throw away oldest history */ + window->data <<= 1; + + /* Mark the most recent #successes attempts as successful */ + if (successes > 0) { + window->success_counter++; + window->data |= 0x1; + successes--; + } + + attempts--; + } + + /* Calculate current success ratio, avoid divide-by-0! */ + if (window->counter > 0) + window->success_ratio = 128 * (100 * window->success_counter) + / window->counter; + else + window->success_ratio = IWL_INVALID_VALUE; + + fail_count = window->counter - window->success_counter; + + /* Calculate average throughput, if we have enough history. */ + if ((fail_count >= IWL_RATE_MIN_FAILURE_TH) || + (window->success_counter >= IWL_RATE_MIN_SUCCESS_TH)) + window->average_tpt = (window->success_ratio * tpt + 64) / 128; + else + window->average_tpt = IWL_INVALID_VALUE; + + /* Tag this window as having been updated */ + window->stamp = jiffies; + + return 0; +} + +/* + * Fill uCode API rate_n_flags field, based on "search" or "active" table. + */ +/* FIXME:RS:remove this function and put the flags statically in the table */ +static u32 rate_n_flags_from_tbl(struct iwl_priv *priv, + struct iwl_scale_tbl_info *tbl, + int index, u8 use_green) +{ + u32 rate_n_flags = 0; + + if (is_legacy(tbl->lq_type)) { + rate_n_flags = iwl_rates[index].plcp; + if (index >= IWL_FIRST_CCK_RATE && index <= IWL_LAST_CCK_RATE) + rate_n_flags |= RATE_MCS_CCK_MSK; + + } else if (is_Ht(tbl->lq_type)) { + if (index > IWL_LAST_OFDM_RATE) { + IWL_ERR(priv, "Invalid HT rate index %d\n", index); + index = IWL_LAST_OFDM_RATE; + } + rate_n_flags = RATE_MCS_HT_MSK; + + if (is_siso(tbl->lq_type)) + rate_n_flags |= iwl_rates[index].plcp_siso; + else if (is_mimo2(tbl->lq_type)) + rate_n_flags |= iwl_rates[index].plcp_mimo2; + else + rate_n_flags |= iwl_rates[index].plcp_mimo3; + } else { + IWL_ERR(priv, "Invalid tbl->lq_type %d\n", tbl->lq_type); + } + + rate_n_flags |= ((tbl->ant_type << RATE_MCS_ANT_POS) & + RATE_MCS_ANT_ABC_MSK); + + if (is_Ht(tbl->lq_type)) { + if (tbl->is_ht40) { + if (tbl->is_dup) + rate_n_flags |= RATE_MCS_DUP_MSK; + else + rate_n_flags |= RATE_MCS_HT40_MSK; + } + if (tbl->is_SGI) + rate_n_flags |= RATE_MCS_SGI_MSK; + + if (use_green) { + rate_n_flags |= RATE_MCS_GF_MSK; + if (is_siso(tbl->lq_type) && tbl->is_SGI) { + rate_n_flags &= ~RATE_MCS_SGI_MSK; + IWL_ERR(priv, "GF was set with SGI:SISO\n"); + } + } + } + return rate_n_flags; +} + +/* + * Interpret uCode API's rate_n_flags format, + * fill "search" or "active" tx mode table. + */ +static int rs_get_tbl_info_from_mcs(const u32 rate_n_flags, + enum nl80211_band band, + struct iwl_scale_tbl_info *tbl, + int *rate_idx) +{ + u32 ant_msk = (rate_n_flags & RATE_MCS_ANT_ABC_MSK); + u8 num_of_ant = get_num_of_ant_from_rate(rate_n_flags); + u8 mcs; + + memset(tbl, 0, sizeof(struct iwl_scale_tbl_info)); + *rate_idx = iwl_hwrate_to_plcp_idx(rate_n_flags); + + if (*rate_idx == IWL_RATE_INVALID) { + *rate_idx = -1; + return -EINVAL; + } + tbl->is_SGI = 0; /* default legacy setup */ + tbl->is_ht40 = 0; + tbl->is_dup = 0; + tbl->ant_type = (ant_msk >> RATE_MCS_ANT_POS); + tbl->lq_type = LQ_NONE; + tbl->max_search = IWL_MAX_SEARCH; + + /* legacy rate format */ + if (!(rate_n_flags & RATE_MCS_HT_MSK)) { + if (num_of_ant == 1) { + if (band == NL80211_BAND_5GHZ) + tbl->lq_type = LQ_A; + else + tbl->lq_type = LQ_G; + } + /* HT rate format */ + } else { + if (rate_n_flags & RATE_MCS_SGI_MSK) + tbl->is_SGI = 1; + + if ((rate_n_flags & RATE_MCS_HT40_MSK) || + (rate_n_flags & RATE_MCS_DUP_MSK)) + tbl->is_ht40 = 1; + + if (rate_n_flags & RATE_MCS_DUP_MSK) + tbl->is_dup = 1; + + mcs = rs_extract_rate(rate_n_flags); + + /* SISO */ + if (mcs <= IWL_RATE_SISO_60M_PLCP) { + if (num_of_ant == 1) + tbl->lq_type = LQ_SISO; /*else NONE*/ + /* MIMO2 */ + } else if (mcs <= IWL_RATE_MIMO2_60M_PLCP) { + if (num_of_ant == 2) + tbl->lq_type = LQ_MIMO2; + /* MIMO3 */ + } else { + if (num_of_ant == 3) { + tbl->max_search = IWL_MAX_11N_MIMO3_SEARCH; + tbl->lq_type = LQ_MIMO3; + } + } + } + return 0; +} + +/* switch to another antenna/antennas and return 1 */ +/* if no other valid antenna found, return 0 */ +static int rs_toggle_antenna(u32 valid_ant, u32 *rate_n_flags, + struct iwl_scale_tbl_info *tbl) +{ + u8 new_ant_type; + + if (!tbl->ant_type || tbl->ant_type > ANT_ABC) + return 0; + + if (!rs_is_valid_ant(valid_ant, tbl->ant_type)) + return 0; + + new_ant_type = ant_toggle_lookup[tbl->ant_type]; + + while ((new_ant_type != tbl->ant_type) && + !rs_is_valid_ant(valid_ant, new_ant_type)) + new_ant_type = ant_toggle_lookup[new_ant_type]; + + if (new_ant_type == tbl->ant_type) + return 0; + + tbl->ant_type = new_ant_type; + *rate_n_flags &= ~RATE_MCS_ANT_ABC_MSK; + *rate_n_flags |= new_ant_type << RATE_MCS_ANT_POS; + return 1; +} + +/* + * Green-field mode is valid if the station supports it and + * there are no non-GF stations present in the BSS. + */ +static bool rs_use_green(struct ieee80211_sta *sta) +{ + /* + * There's a bug somewhere in this code that causes the + * scaling to get stuck because GF+SGI can't be combined + * in SISO rates. Until we find that bug, disable GF, it + * has only limited benefit and we still interoperate with + * GF APs since we can always receive GF transmissions. + */ + return false; +} + +/* + * rs_get_supported_rates - get the available rates + * + * if management frame or broadcast frame only return + * basic available rates. + * + */ +static u16 rs_get_supported_rates(struct iwl_lq_sta *lq_sta, + struct ieee80211_hdr *hdr, + enum iwl_table_type rate_type) +{ + if (is_legacy(rate_type)) { + return lq_sta->active_legacy_rate; + } else { + if (is_siso(rate_type)) + return lq_sta->active_siso_rate; + else if (is_mimo2(rate_type)) + return lq_sta->active_mimo2_rate; + else + return lq_sta->active_mimo3_rate; + } +} + +static u16 rs_get_adjacent_rate(struct iwl_priv *priv, u8 index, u16 rate_mask, + int rate_type) +{ + u8 high = IWL_RATE_INVALID; + u8 low = IWL_RATE_INVALID; + + /* 802.11A or ht walks to the next literal adjacent rate in + * the rate table */ + if (is_a_band(rate_type) || !is_legacy(rate_type)) { + int i; + u32 mask; + + /* Find the previous rate that is in the rate mask */ + i = index - 1; + if (i >= 0) + mask = BIT(i); + + for (; i >= 0; i--, mask >>= 1) { + if (rate_mask & mask) { + low = i; + break; + } + } + + /* Find the next rate that is in the rate mask */ + i = index + 1; + for (mask = (1 << i); i < IWL_RATE_COUNT; i++, mask <<= 1) { + if (rate_mask & mask) { + high = i; + break; + } + } + + return (high << 8) | low; + } + + low = index; + while (low != IWL_RATE_INVALID) { + low = iwl_rates[low].prev_rs; + if (low == IWL_RATE_INVALID) + break; + if (rate_mask & (1 << low)) + break; + IWL_DEBUG_RATE(priv, "Skipping masked lower rate: %d\n", low); + } + + high = index; + while (high != IWL_RATE_INVALID) { + high = iwl_rates[high].next_rs; + if (high == IWL_RATE_INVALID) + break; + if (rate_mask & (1 << high)) + break; + IWL_DEBUG_RATE(priv, "Skipping masked higher rate: %d\n", high); + } + + return (high << 8) | low; +} + +static u32 rs_get_lower_rate(struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl, + u8 scale_index, u8 ht_possible) +{ + s32 low; + u16 rate_mask; + u16 high_low; + u8 switch_to_legacy = 0; + u8 is_green = lq_sta->is_green; + struct iwl_priv *priv = lq_sta->drv; + + /* check if we need to switch from HT to legacy rates. + * assumption is that mandatory rates (1Mbps or 6Mbps) + * are always supported (spec demand) */ + if (!is_legacy(tbl->lq_type) && (!ht_possible || !scale_index)) { + switch_to_legacy = 1; + scale_index = rs_ht_to_legacy[scale_index]; + if (lq_sta->band == NL80211_BAND_5GHZ) + tbl->lq_type = LQ_A; + else + tbl->lq_type = LQ_G; + + if (num_of_ant(tbl->ant_type) > 1) + tbl->ant_type = + first_antenna(priv->nvm_data->valid_tx_ant); + + tbl->is_ht40 = 0; + tbl->is_SGI = 0; + tbl->max_search = IWL_MAX_SEARCH; + } + + rate_mask = rs_get_supported_rates(lq_sta, NULL, tbl->lq_type); + + /* Mask with station rate restriction */ + if (is_legacy(tbl->lq_type)) { + /* supp_rates has no CCK bits in A mode */ + if (lq_sta->band == NL80211_BAND_5GHZ) + rate_mask = (u16)(rate_mask & + (lq_sta->supp_rates << IWL_FIRST_OFDM_RATE)); + else + rate_mask = (u16)(rate_mask & lq_sta->supp_rates); + } + + /* If we switched from HT to legacy, check current rate */ + if (switch_to_legacy && (rate_mask & (1 << scale_index))) { + low = scale_index; + goto out; + } + + high_low = rs_get_adjacent_rate(lq_sta->drv, scale_index, rate_mask, + tbl->lq_type); + low = high_low & 0xff; + + if (low == IWL_RATE_INVALID) + low = scale_index; + +out: + return rate_n_flags_from_tbl(lq_sta->drv, tbl, low, is_green); +} + +/* + * Simple function to compare two rate scale table types + */ +static bool table_type_matches(struct iwl_scale_tbl_info *a, + struct iwl_scale_tbl_info *b) +{ + return (a->lq_type == b->lq_type) && (a->ant_type == b->ant_type) && + (a->is_SGI == b->is_SGI); +} + +static void rs_bt_update_lq(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + struct iwl_lq_sta *lq_sta) +{ + struct iwl_scale_tbl_info *tbl; + bool full_concurrent = priv->bt_full_concurrent; + + if ((priv->bt_traffic_load != priv->last_bt_traffic_load) || + (priv->bt_full_concurrent != full_concurrent)) { + priv->bt_full_concurrent = full_concurrent; + priv->last_bt_traffic_load = priv->bt_traffic_load; + + /* Update uCode's rate table. */ + tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + rs_fill_link_cmd(priv, lq_sta, tbl->current_rate); + iwl_send_lq_cmd(priv, ctx, &lq_sta->lq, CMD_ASYNC, false); + + queue_work(priv->workqueue, &priv->bt_full_concurrency); + } +} + +/* + * mac80211 sends us Tx status + */ +static void rs_tx_status(void *priv_r, struct ieee80211_supported_band *sband, + struct ieee80211_sta *sta, void *priv_sta, + struct sk_buff *skb) +{ + int legacy_success; + int retries; + int rs_index, mac_index, i; + struct iwl_lq_sta *lq_sta = priv_sta; + struct iwl_link_quality_cmd *table; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct iwl_op_mode *op_mode = (struct iwl_op_mode *)priv_r; + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + enum mac80211_rate_control_flags mac_flags; + u32 tx_rate; + struct iwl_scale_tbl_info tbl_type; + struct iwl_scale_tbl_info *curr_tbl, *other_tbl, *tmp_tbl; + struct iwl_station_priv *sta_priv = (void *)sta->drv_priv; + struct iwl_rxon_context *ctx = sta_priv->ctx; + + IWL_DEBUG_RATE_LIMIT(priv, "get frame ack response, update rate scale window\n"); + + /* Treat uninitialized rate scaling data same as non-existing. */ + if (!lq_sta) { + IWL_DEBUG_RATE(priv, "Station rate scaling not created yet.\n"); + return; + } else if (!lq_sta->drv) { + IWL_DEBUG_RATE(priv, "Rate scaling not initialized yet.\n"); + return; + } + + if (!ieee80211_is_data(hdr->frame_control) || + info->flags & IEEE80211_TX_CTL_NO_ACK) + return; + + /* This packet was aggregated but doesn't carry status info */ + if ((info->flags & IEEE80211_TX_CTL_AMPDU) && + !(info->flags & IEEE80211_TX_STAT_AMPDU)) + return; + + /* + * Ignore this Tx frame response if its initial rate doesn't match + * that of latest Link Quality command. There may be stragglers + * from a previous Link Quality command, but we're no longer interested + * in those; they're either from the "active" mode while we're trying + * to check "search" mode, or a prior "search" mode after we've moved + * to a new "search" mode (which might become the new "active" mode). + */ + table = &lq_sta->lq; + tx_rate = le32_to_cpu(table->rs_table[0].rate_n_flags); + rs_get_tbl_info_from_mcs(tx_rate, priv->band, &tbl_type, &rs_index); + if (priv->band == NL80211_BAND_5GHZ) + rs_index -= IWL_FIRST_OFDM_RATE; + mac_flags = info->status.rates[0].flags; + mac_index = info->status.rates[0].idx; + /* For HT packets, map MCS to PLCP */ + if (mac_flags & IEEE80211_TX_RC_MCS) { + mac_index &= RATE_MCS_CODE_MSK; /* Remove # of streams */ + if (mac_index >= (IWL_RATE_9M_INDEX - IWL_FIRST_OFDM_RATE)) + mac_index++; + /* + * mac80211 HT index is always zero-indexed; we need to move + * HT OFDM rates after CCK rates in 2.4 GHz band + */ + if (priv->band == NL80211_BAND_2GHZ) + mac_index += IWL_FIRST_OFDM_RATE; + } + /* Here we actually compare this rate to the latest LQ command */ + if ((mac_index < 0) || + (tbl_type.is_SGI != !!(mac_flags & IEEE80211_TX_RC_SHORT_GI)) || + (tbl_type.is_ht40 != !!(mac_flags & IEEE80211_TX_RC_40_MHZ_WIDTH)) || + (tbl_type.is_dup != !!(mac_flags & IEEE80211_TX_RC_DUP_DATA)) || + (tbl_type.ant_type != info->status.antenna) || + (!!(tx_rate & RATE_MCS_HT_MSK) != !!(mac_flags & IEEE80211_TX_RC_MCS)) || + (!!(tx_rate & RATE_MCS_GF_MSK) != !!(mac_flags & IEEE80211_TX_RC_GREEN_FIELD)) || + (rs_index != mac_index)) { + IWL_DEBUG_RATE(priv, "initial rate %d does not match %d (0x%x)\n", mac_index, rs_index, tx_rate); + /* + * Since rates mis-match, the last LQ command may have failed. + * After IWL_MISSED_RATE_MAX mis-matches, resync the uCode with + * ... driver. + */ + lq_sta->missed_rate_counter++; + if (lq_sta->missed_rate_counter > IWL_MISSED_RATE_MAX) { + lq_sta->missed_rate_counter = 0; + iwl_send_lq_cmd(priv, ctx, &lq_sta->lq, CMD_ASYNC, false); + } + /* Regardless, ignore this status info for outdated rate */ + return; + } else + /* Rate did match, so reset the missed_rate_counter */ + lq_sta->missed_rate_counter = 0; + + /* Figure out if rate scale algorithm is in active or search table */ + if (table_type_matches(&tbl_type, + &(lq_sta->lq_info[lq_sta->active_tbl]))) { + curr_tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + other_tbl = &(lq_sta->lq_info[1 - lq_sta->active_tbl]); + } else if (table_type_matches(&tbl_type, + &lq_sta->lq_info[1 - lq_sta->active_tbl])) { + curr_tbl = &(lq_sta->lq_info[1 - lq_sta->active_tbl]); + other_tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + } else { + IWL_DEBUG_RATE(priv, "Neither active nor search matches tx rate\n"); + tmp_tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + IWL_DEBUG_RATE(priv, "active- lq:%x, ant:%x, SGI:%d\n", + tmp_tbl->lq_type, tmp_tbl->ant_type, tmp_tbl->is_SGI); + tmp_tbl = &(lq_sta->lq_info[1 - lq_sta->active_tbl]); + IWL_DEBUG_RATE(priv, "search- lq:%x, ant:%x, SGI:%d\n", + tmp_tbl->lq_type, tmp_tbl->ant_type, tmp_tbl->is_SGI); + IWL_DEBUG_RATE(priv, "actual- lq:%x, ant:%x, SGI:%d\n", + tbl_type.lq_type, tbl_type.ant_type, tbl_type.is_SGI); + /* + * no matching table found, let's by-pass the data collection + * and continue to perform rate scale to find the rate table + */ + rs_stay_in_table(lq_sta, true); + goto done; + } + + /* + * Updating the frame history depends on whether packets were + * aggregated. + * + * For aggregation, all packets were transmitted at the same rate, the + * first index into rate scale table. + */ + if (info->flags & IEEE80211_TX_STAT_AMPDU) { + tx_rate = le32_to_cpu(table->rs_table[0].rate_n_flags); + rs_get_tbl_info_from_mcs(tx_rate, priv->band, &tbl_type, + &rs_index); + rs_collect_tx_data(curr_tbl, rs_index, + info->status.ampdu_len, + info->status.ampdu_ack_len); + + /* Update success/fail counts if not searching for new mode */ + if (lq_sta->stay_in_tbl) { + lq_sta->total_success += info->status.ampdu_ack_len; + lq_sta->total_failed += (info->status.ampdu_len - + info->status.ampdu_ack_len); + } + } else { + /* + * For legacy, update frame history with for each Tx retry. + */ + retries = info->status.rates[0].count - 1; + /* HW doesn't send more than 15 retries */ + retries = min(retries, 15); + + /* The last transmission may have been successful */ + legacy_success = !!(info->flags & IEEE80211_TX_STAT_ACK); + /* Collect data for each rate used during failed TX attempts */ + for (i = 0; i <= retries; ++i) { + tx_rate = le32_to_cpu(table->rs_table[i].rate_n_flags); + rs_get_tbl_info_from_mcs(tx_rate, priv->band, + &tbl_type, &rs_index); + /* + * Only collect stats if retried rate is in the same RS + * table as active/search. + */ + if (table_type_matches(&tbl_type, curr_tbl)) + tmp_tbl = curr_tbl; + else if (table_type_matches(&tbl_type, other_tbl)) + tmp_tbl = other_tbl; + else + continue; + rs_collect_tx_data(tmp_tbl, rs_index, 1, + i < retries ? 0 : legacy_success); + } + + /* Update success/fail counts if not searching for new mode */ + if (lq_sta->stay_in_tbl) { + lq_sta->total_success += legacy_success; + lq_sta->total_failed += retries + (1 - legacy_success); + } + } + /* The last TX rate is cached in lq_sta; it's set in if/else above */ + lq_sta->last_rate_n_flags = tx_rate; +done: + /* See if there's a better rate or modulation mode to try. */ + if (sta && sta->deflink.supp_rates[sband->band]) + rs_rate_scale_perform(priv, skb, sta, lq_sta); + + if (priv->lib->bt_params && priv->lib->bt_params->advanced_bt_coexist) + rs_bt_update_lq(priv, ctx, lq_sta); +} + +/* + * Begin a period of staying with a selected modulation mode. + * Set "stay_in_tbl" flag to prevent any mode switches. + * Set frame tx success limits according to legacy vs. high-throughput, + * and reset overall (spanning all rates) tx success history statistics. + * These control how long we stay using same modulation mode before + * searching for a new mode. + */ +static void rs_set_stay_in_table(struct iwl_priv *priv, u8 is_legacy, + struct iwl_lq_sta *lq_sta) +{ + IWL_DEBUG_RATE(priv, "we are staying in the same table\n"); + lq_sta->stay_in_tbl = 1; /* only place this gets set */ + if (is_legacy) { + lq_sta->table_count_limit = IWL_LEGACY_TABLE_COUNT; + lq_sta->max_failure_limit = IWL_LEGACY_FAILURE_LIMIT; + lq_sta->max_success_limit = IWL_LEGACY_SUCCESS_LIMIT; + } else { + lq_sta->table_count_limit = IWL_NONE_LEGACY_TABLE_COUNT; + lq_sta->max_failure_limit = IWL_NONE_LEGACY_FAILURE_LIMIT; + lq_sta->max_success_limit = IWL_NONE_LEGACY_SUCCESS_LIMIT; + } + lq_sta->table_count = 0; + lq_sta->total_failed = 0; + lq_sta->total_success = 0; + lq_sta->flush_timer = jiffies; + lq_sta->action_counter = 0; +} + +/* + * Find correct throughput table for given mode of modulation + */ +static void rs_set_expected_tpt_table(struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl) +{ + /* Used to choose among HT tables */ + const u16 (*ht_tbl_pointer)[IWL_RATE_COUNT]; + + /* Check for invalid LQ type */ + if (WARN_ON_ONCE(!is_legacy(tbl->lq_type) && !is_Ht(tbl->lq_type))) { + tbl->expected_tpt = expected_tpt_legacy; + return; + } + + /* Legacy rates have only one table */ + if (is_legacy(tbl->lq_type)) { + tbl->expected_tpt = expected_tpt_legacy; + return; + } + + /* Choose among many HT tables depending on number of streams + * (SISO/MIMO2/MIMO3), channel width (20/40), SGI, and aggregation + * status */ + if (is_siso(tbl->lq_type) && (!tbl->is_ht40 || lq_sta->is_dup)) + ht_tbl_pointer = expected_tpt_siso20MHz; + else if (is_siso(tbl->lq_type)) + ht_tbl_pointer = expected_tpt_siso40MHz; + else if (is_mimo2(tbl->lq_type) && (!tbl->is_ht40 || lq_sta->is_dup)) + ht_tbl_pointer = expected_tpt_mimo2_20MHz; + else if (is_mimo2(tbl->lq_type)) + ht_tbl_pointer = expected_tpt_mimo2_40MHz; + else if (is_mimo3(tbl->lq_type) && (!tbl->is_ht40 || lq_sta->is_dup)) + ht_tbl_pointer = expected_tpt_mimo3_20MHz; + else /* if (is_mimo3(tbl->lq_type)) <-- must be true */ + ht_tbl_pointer = expected_tpt_mimo3_40MHz; + + if (!tbl->is_SGI && !lq_sta->is_agg) /* Normal */ + tbl->expected_tpt = ht_tbl_pointer[0]; + else if (tbl->is_SGI && !lq_sta->is_agg) /* SGI */ + tbl->expected_tpt = ht_tbl_pointer[1]; + else if (!tbl->is_SGI && lq_sta->is_agg) /* AGG */ + tbl->expected_tpt = ht_tbl_pointer[2]; + else /* AGG+SGI */ + tbl->expected_tpt = ht_tbl_pointer[3]; +} + +/* + * Find starting rate for new "search" high-throughput mode of modulation. + * Goal is to find lowest expected rate (under perfect conditions) that is + * above the current measured throughput of "active" mode, to give new mode + * a fair chance to prove itself without too many challenges. + * + * This gets called when transitioning to more aggressive modulation + * (i.e. legacy to SISO or MIMO, or SISO to MIMO), as well as less aggressive + * (i.e. MIMO to SISO). When moving to MIMO, bit rate will typically need + * to decrease to match "active" throughput. When moving from MIMO to SISO, + * bit rate will typically need to increase, but not if performance was bad. + */ +static s32 rs_get_best_rate(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl, /* "search" */ + u16 rate_mask, s8 index) +{ + /* "active" values */ + struct iwl_scale_tbl_info *active_tbl = + &(lq_sta->lq_info[lq_sta->active_tbl]); + s32 active_sr = active_tbl->win[index].success_ratio; + s32 active_tpt = active_tbl->expected_tpt[index]; + /* expected "search" throughput */ + const u16 *tpt_tbl = tbl->expected_tpt; + + s32 new_rate, high, low, start_hi; + u16 high_low; + s8 rate = index; + + new_rate = high = low = start_hi = IWL_RATE_INVALID; + + for (; ;) { + high_low = rs_get_adjacent_rate(priv, rate, rate_mask, + tbl->lq_type); + + low = high_low & 0xff; + high = (high_low >> 8) & 0xff; + + /* + * Lower the "search" bit rate, to give new "search" mode + * approximately the same throughput as "active" if: + * + * 1) "Active" mode has been working modestly well (but not + * great), and expected "search" throughput (under perfect + * conditions) at candidate rate is above the actual + * measured "active" throughput (but less than expected + * "active" throughput under perfect conditions). + * OR + * 2) "Active" mode has been working perfectly or very well + * and expected "search" throughput (under perfect + * conditions) at candidate rate is above expected + * "active" throughput (under perfect conditions). + */ + if ((((100 * tpt_tbl[rate]) > lq_sta->last_tpt) && + ((active_sr > IWL_RATE_DECREASE_TH) && + (active_sr <= IWL_RATE_HIGH_TH) && + (tpt_tbl[rate] <= active_tpt))) || + ((active_sr >= IWL_RATE_SCALE_SWITCH) && + (tpt_tbl[rate] > active_tpt))) { + + /* (2nd or later pass) + * If we've already tried to raise the rate, and are + * now trying to lower it, use the higher rate. */ + if (start_hi != IWL_RATE_INVALID) { + new_rate = start_hi; + break; + } + + new_rate = rate; + + /* Loop again with lower rate */ + if (low != IWL_RATE_INVALID) + rate = low; + + /* Lower rate not available, use the original */ + else + break; + + /* Else try to raise the "search" rate to match "active" */ + } else { + /* (2nd or later pass) + * If we've already tried to lower the rate, and are + * now trying to raise it, use the lower rate. */ + if (new_rate != IWL_RATE_INVALID) + break; + + /* Loop again with higher rate */ + else if (high != IWL_RATE_INVALID) { + start_hi = high; + rate = high; + + /* Higher rate not available, use the original */ + } else { + new_rate = rate; + break; + } + } + } + + return new_rate; +} + +/* + * Set up search table for MIMO2 + */ +static int rs_switch_to_mimo2(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, + struct iwl_scale_tbl_info *tbl, int index) +{ + u16 rate_mask; + s32 rate; + s8 is_green = lq_sta->is_green; + struct iwl_station_priv *sta_priv = (void *)sta->drv_priv; + struct iwl_rxon_context *ctx = sta_priv->ctx; + + if (!conf_is_ht(conf) || !sta->deflink.ht_cap.ht_supported) + return -1; + + if (sta->deflink.smps_mode == IEEE80211_SMPS_STATIC) + return -1; + + /* Need both Tx chains/antennas to support MIMO */ + if (priv->hw_params.tx_chains_num < 2) + return -1; + + IWL_DEBUG_RATE(priv, "LQ: try to switch to MIMO2\n"); + + tbl->lq_type = LQ_MIMO2; + tbl->is_dup = lq_sta->is_dup; + tbl->action = 0; + tbl->max_search = IWL_MAX_SEARCH; + rate_mask = lq_sta->active_mimo2_rate; + + if (iwl_is_ht40_tx_allowed(priv, ctx, sta)) + tbl->is_ht40 = 1; + else + tbl->is_ht40 = 0; + + rs_set_expected_tpt_table(lq_sta, tbl); + + rate = rs_get_best_rate(priv, lq_sta, tbl, rate_mask, index); + + IWL_DEBUG_RATE(priv, "LQ: MIMO2 best rate %d mask %X\n", rate, rate_mask); + if ((rate == IWL_RATE_INVALID) || !((1 << rate) & rate_mask)) { + IWL_DEBUG_RATE(priv, "Can't switch with index %d rate mask %x\n", + rate, rate_mask); + return -1; + } + tbl->current_rate = rate_n_flags_from_tbl(priv, tbl, rate, is_green); + + IWL_DEBUG_RATE(priv, "LQ: Switch to new mcs %X index is green %X\n", + tbl->current_rate, is_green); + return 0; +} + +/* + * Set up search table for MIMO3 + */ +static int rs_switch_to_mimo3(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, + struct iwl_scale_tbl_info *tbl, int index) +{ + u16 rate_mask; + s32 rate; + s8 is_green = lq_sta->is_green; + struct iwl_station_priv *sta_priv = (void *)sta->drv_priv; + struct iwl_rxon_context *ctx = sta_priv->ctx; + + if (!conf_is_ht(conf) || !sta->deflink.ht_cap.ht_supported) + return -1; + + if (sta->deflink.smps_mode == IEEE80211_SMPS_STATIC) + return -1; + + /* Need both Tx chains/antennas to support MIMO */ + if (priv->hw_params.tx_chains_num < 3) + return -1; + + IWL_DEBUG_RATE(priv, "LQ: try to switch to MIMO3\n"); + + tbl->lq_type = LQ_MIMO3; + tbl->is_dup = lq_sta->is_dup; + tbl->action = 0; + tbl->max_search = IWL_MAX_11N_MIMO3_SEARCH; + rate_mask = lq_sta->active_mimo3_rate; + + if (iwl_is_ht40_tx_allowed(priv, ctx, sta)) + tbl->is_ht40 = 1; + else + tbl->is_ht40 = 0; + + rs_set_expected_tpt_table(lq_sta, tbl); + + rate = rs_get_best_rate(priv, lq_sta, tbl, rate_mask, index); + + IWL_DEBUG_RATE(priv, "LQ: MIMO3 best rate %d mask %X\n", + rate, rate_mask); + if ((rate == IWL_RATE_INVALID) || !((1 << rate) & rate_mask)) { + IWL_DEBUG_RATE(priv, "Can't switch with index %d rate mask %x\n", + rate, rate_mask); + return -1; + } + tbl->current_rate = rate_n_flags_from_tbl(priv, tbl, rate, is_green); + + IWL_DEBUG_RATE(priv, "LQ: Switch to new mcs %X index is green %X\n", + tbl->current_rate, is_green); + return 0; +} + +/* + * Set up search table for SISO + */ +static int rs_switch_to_siso(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, + struct iwl_scale_tbl_info *tbl, int index) +{ + u16 rate_mask; + u8 is_green = lq_sta->is_green; + s32 rate; + struct iwl_station_priv *sta_priv = (void *)sta->drv_priv; + struct iwl_rxon_context *ctx = sta_priv->ctx; + + if (!conf_is_ht(conf) || !sta->deflink.ht_cap.ht_supported) + return -1; + + IWL_DEBUG_RATE(priv, "LQ: try to switch to SISO\n"); + + tbl->is_dup = lq_sta->is_dup; + tbl->lq_type = LQ_SISO; + tbl->action = 0; + tbl->max_search = IWL_MAX_SEARCH; + rate_mask = lq_sta->active_siso_rate; + + if (iwl_is_ht40_tx_allowed(priv, ctx, sta)) + tbl->is_ht40 = 1; + else + tbl->is_ht40 = 0; + + if (is_green) + tbl->is_SGI = 0; /*11n spec: no SGI in SISO+Greenfield*/ + + rs_set_expected_tpt_table(lq_sta, tbl); + rate = rs_get_best_rate(priv, lq_sta, tbl, rate_mask, index); + + IWL_DEBUG_RATE(priv, "LQ: get best rate %d mask %X\n", rate, rate_mask); + if ((rate == IWL_RATE_INVALID) || !((1 << rate) & rate_mask)) { + IWL_DEBUG_RATE(priv, "can not switch with index %d rate mask %x\n", + rate, rate_mask); + return -1; + } + tbl->current_rate = rate_n_flags_from_tbl(priv, tbl, rate, is_green); + IWL_DEBUG_RATE(priv, "LQ: Switch to new mcs %X index is green %X\n", + tbl->current_rate, is_green); + return 0; +} + +/* + * Try to switch to new modulation mode from legacy + */ +static void rs_move_legacy_other(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, + int index) +{ + struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + struct iwl_scale_tbl_info *search_tbl = + &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + struct iwl_rate_scale_data *window = &(tbl->win[index]); + u32 sz = (sizeof(struct iwl_scale_tbl_info) - + (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT)); + u8 start_action; + u8 valid_tx_ant = priv->nvm_data->valid_tx_ant; + u8 tx_chains_num = priv->hw_params.tx_chains_num; + int ret = 0; + u8 update_search_tbl_counter = 0; + + switch (priv->bt_traffic_load) { + case IWL_BT_COEX_TRAFFIC_LOAD_NONE: + /* nothing */ + break; + case IWL_BT_COEX_TRAFFIC_LOAD_LOW: + /* avoid antenna B unless MIMO */ + if (tbl->action == IWL_LEGACY_SWITCH_ANTENNA2) + tbl->action = IWL_LEGACY_SWITCH_SISO; + break; + case IWL_BT_COEX_TRAFFIC_LOAD_HIGH: + case IWL_BT_COEX_TRAFFIC_LOAD_CONTINUOUS: + /* avoid antenna B and MIMO */ + valid_tx_ant = + first_antenna(priv->nvm_data->valid_tx_ant); + if (tbl->action >= IWL_LEGACY_SWITCH_ANTENNA2 && + tbl->action != IWL_LEGACY_SWITCH_SISO) + tbl->action = IWL_LEGACY_SWITCH_SISO; + break; + default: + IWL_ERR(priv, "Invalid BT load %d\n", priv->bt_traffic_load); + break; + } + + if (!iwl_ht_enabled(priv)) + /* stay in Legacy */ + tbl->action = IWL_LEGACY_SWITCH_ANTENNA1; + else if (iwl_tx_ant_restriction(priv) == IWL_ANT_OK_SINGLE && + tbl->action > IWL_LEGACY_SWITCH_SISO) + tbl->action = IWL_LEGACY_SWITCH_SISO; + + /* configure as 1x1 if bt full concurrency */ + if (priv->bt_full_concurrent) { + if (!iwl_ht_enabled(priv)) + tbl->action = IWL_LEGACY_SWITCH_ANTENNA1; + else if (tbl->action >= IWL_LEGACY_SWITCH_ANTENNA2) + tbl->action = IWL_LEGACY_SWITCH_SISO; + valid_tx_ant = + first_antenna(priv->nvm_data->valid_tx_ant); + } + + start_action = tbl->action; + for (; ;) { + lq_sta->action_counter++; + switch (tbl->action) { + case IWL_LEGACY_SWITCH_ANTENNA1: + case IWL_LEGACY_SWITCH_ANTENNA2: + IWL_DEBUG_RATE(priv, "LQ: Legacy toggle Antenna\n"); + + if ((tbl->action == IWL_LEGACY_SWITCH_ANTENNA1 && + tx_chains_num <= 1) || + (tbl->action == IWL_LEGACY_SWITCH_ANTENNA2 && + tx_chains_num <= 2)) + break; + + /* Don't change antenna if success has been great */ + if (window->success_ratio >= IWL_RS_GOOD_RATIO && + !priv->bt_full_concurrent && + priv->bt_traffic_load == + IWL_BT_COEX_TRAFFIC_LOAD_NONE) + break; + + /* Set up search table to try other antenna */ + memcpy(search_tbl, tbl, sz); + + if (rs_toggle_antenna(valid_tx_ant, + &search_tbl->current_rate, search_tbl)) { + update_search_tbl_counter = 1; + rs_set_expected_tpt_table(lq_sta, search_tbl); + goto out; + } + break; + case IWL_LEGACY_SWITCH_SISO: + IWL_DEBUG_RATE(priv, "LQ: Legacy switch to SISO\n"); + + /* Set up search table to try SISO */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + ret = rs_switch_to_siso(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) { + lq_sta->action_counter = 0; + goto out; + } + + break; + case IWL_LEGACY_SWITCH_MIMO2_AB: + case IWL_LEGACY_SWITCH_MIMO2_AC: + case IWL_LEGACY_SWITCH_MIMO2_BC: + IWL_DEBUG_RATE(priv, "LQ: Legacy switch to MIMO2\n"); + + /* Set up search table to try MIMO */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + + if (tbl->action == IWL_LEGACY_SWITCH_MIMO2_AB) + search_tbl->ant_type = ANT_AB; + else if (tbl->action == IWL_LEGACY_SWITCH_MIMO2_AC) + search_tbl->ant_type = ANT_AC; + else + search_tbl->ant_type = ANT_BC; + + if (!rs_is_valid_ant(valid_tx_ant, search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo2(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) { + lq_sta->action_counter = 0; + goto out; + } + break; + + case IWL_LEGACY_SWITCH_MIMO3_ABC: + IWL_DEBUG_RATE(priv, "LQ: Legacy switch to MIMO3\n"); + + /* Set up search table to try MIMO3 */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + + search_tbl->ant_type = ANT_ABC; + + if (!rs_is_valid_ant(valid_tx_ant, search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo3(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) { + lq_sta->action_counter = 0; + goto out; + } + break; + } + tbl->action++; + if (tbl->action > IWL_LEGACY_SWITCH_MIMO3_ABC) + tbl->action = IWL_LEGACY_SWITCH_ANTENNA1; + + if (tbl->action == start_action) + break; + + } + search_tbl->lq_type = LQ_NONE; + return; + +out: + lq_sta->search_better_tbl = 1; + tbl->action++; + if (tbl->action > IWL_LEGACY_SWITCH_MIMO3_ABC) + tbl->action = IWL_LEGACY_SWITCH_ANTENNA1; + if (update_search_tbl_counter) + search_tbl->action = tbl->action; +} + +/* + * Try to switch to new modulation mode from SISO + */ +static void rs_move_siso_to_other(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, int index) +{ + u8 is_green = lq_sta->is_green; + struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + struct iwl_scale_tbl_info *search_tbl = + &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + struct iwl_rate_scale_data *window = &(tbl->win[index]); + struct ieee80211_sta_ht_cap *ht_cap = &sta->deflink.ht_cap; + u32 sz = (sizeof(struct iwl_scale_tbl_info) - + (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT)); + u8 start_action; + u8 valid_tx_ant = priv->nvm_data->valid_tx_ant; + u8 tx_chains_num = priv->hw_params.tx_chains_num; + u8 update_search_tbl_counter = 0; + int ret; + + switch (priv->bt_traffic_load) { + case IWL_BT_COEX_TRAFFIC_LOAD_NONE: + /* nothing */ + break; + case IWL_BT_COEX_TRAFFIC_LOAD_LOW: + /* avoid antenna B unless MIMO */ + if (tbl->action == IWL_SISO_SWITCH_ANTENNA2) + tbl->action = IWL_SISO_SWITCH_MIMO2_AB; + break; + case IWL_BT_COEX_TRAFFIC_LOAD_HIGH: + case IWL_BT_COEX_TRAFFIC_LOAD_CONTINUOUS: + /* avoid antenna B and MIMO */ + valid_tx_ant = + first_antenna(priv->nvm_data->valid_tx_ant); + if (tbl->action != IWL_SISO_SWITCH_ANTENNA1) + tbl->action = IWL_SISO_SWITCH_ANTENNA1; + break; + default: + IWL_ERR(priv, "Invalid BT load %d\n", priv->bt_traffic_load); + break; + } + + if (iwl_tx_ant_restriction(priv) == IWL_ANT_OK_SINGLE && + tbl->action > IWL_SISO_SWITCH_ANTENNA2) { + /* stay in SISO */ + tbl->action = IWL_SISO_SWITCH_ANTENNA1; + } + + /* configure as 1x1 if bt full concurrency */ + if (priv->bt_full_concurrent) { + valid_tx_ant = + first_antenna(priv->nvm_data->valid_tx_ant); + if (tbl->action >= IWL_LEGACY_SWITCH_ANTENNA2) + tbl->action = IWL_SISO_SWITCH_ANTENNA1; + } + + start_action = tbl->action; + for (;;) { + lq_sta->action_counter++; + switch (tbl->action) { + case IWL_SISO_SWITCH_ANTENNA1: + case IWL_SISO_SWITCH_ANTENNA2: + IWL_DEBUG_RATE(priv, "LQ: SISO toggle Antenna\n"); + if ((tbl->action == IWL_SISO_SWITCH_ANTENNA1 && + tx_chains_num <= 1) || + (tbl->action == IWL_SISO_SWITCH_ANTENNA2 && + tx_chains_num <= 2)) + break; + + if (window->success_ratio >= IWL_RS_GOOD_RATIO && + !priv->bt_full_concurrent && + priv->bt_traffic_load == + IWL_BT_COEX_TRAFFIC_LOAD_NONE) + break; + + memcpy(search_tbl, tbl, sz); + if (rs_toggle_antenna(valid_tx_ant, + &search_tbl->current_rate, search_tbl)) { + update_search_tbl_counter = 1; + goto out; + } + break; + case IWL_SISO_SWITCH_MIMO2_AB: + case IWL_SISO_SWITCH_MIMO2_AC: + case IWL_SISO_SWITCH_MIMO2_BC: + IWL_DEBUG_RATE(priv, "LQ: SISO switch to MIMO2\n"); + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + + if (tbl->action == IWL_SISO_SWITCH_MIMO2_AB) + search_tbl->ant_type = ANT_AB; + else if (tbl->action == IWL_SISO_SWITCH_MIMO2_AC) + search_tbl->ant_type = ANT_AC; + else + search_tbl->ant_type = ANT_BC; + + if (!rs_is_valid_ant(valid_tx_ant, search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo2(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) + goto out; + break; + case IWL_SISO_SWITCH_GI: + if (!tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_20)) + break; + if (tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_40)) + break; + + IWL_DEBUG_RATE(priv, "LQ: SISO toggle SGI/NGI\n"); + + memcpy(search_tbl, tbl, sz); + if (is_green) { + if (!tbl->is_SGI) + break; + else + IWL_ERR(priv, + "SGI was set in GF+SISO\n"); + } + search_tbl->is_SGI = !tbl->is_SGI; + rs_set_expected_tpt_table(lq_sta, search_tbl); + if (tbl->is_SGI) { + s32 tpt = lq_sta->last_tpt / 100; + if (tpt >= search_tbl->expected_tpt[index]) + break; + } + search_tbl->current_rate = + rate_n_flags_from_tbl(priv, search_tbl, + index, is_green); + update_search_tbl_counter = 1; + goto out; + case IWL_SISO_SWITCH_MIMO3_ABC: + IWL_DEBUG_RATE(priv, "LQ: SISO switch to MIMO3\n"); + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + search_tbl->ant_type = ANT_ABC; + + if (!rs_is_valid_ant(valid_tx_ant, search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo3(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) + goto out; + break; + } + tbl->action++; + if (tbl->action > IWL_LEGACY_SWITCH_MIMO3_ABC) + tbl->action = IWL_SISO_SWITCH_ANTENNA1; + + if (tbl->action == start_action) + break; + } + search_tbl->lq_type = LQ_NONE; + return; + + out: + lq_sta->search_better_tbl = 1; + tbl->action++; + if (tbl->action > IWL_SISO_SWITCH_MIMO3_ABC) + tbl->action = IWL_SISO_SWITCH_ANTENNA1; + if (update_search_tbl_counter) + search_tbl->action = tbl->action; +} + +/* + * Try to switch to new modulation mode from MIMO2 + */ +static void rs_move_mimo2_to_other(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, int index) +{ + s8 is_green = lq_sta->is_green; + struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + struct iwl_scale_tbl_info *search_tbl = + &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + struct iwl_rate_scale_data *window = &(tbl->win[index]); + struct ieee80211_sta_ht_cap *ht_cap = &sta->deflink.ht_cap; + u32 sz = (sizeof(struct iwl_scale_tbl_info) - + (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT)); + u8 start_action; + u8 valid_tx_ant = priv->nvm_data->valid_tx_ant; + u8 tx_chains_num = priv->hw_params.tx_chains_num; + u8 update_search_tbl_counter = 0; + int ret; + + switch (priv->bt_traffic_load) { + case IWL_BT_COEX_TRAFFIC_LOAD_NONE: + /* nothing */ + break; + case IWL_BT_COEX_TRAFFIC_LOAD_HIGH: + case IWL_BT_COEX_TRAFFIC_LOAD_CONTINUOUS: + /* avoid antenna B and MIMO */ + if (tbl->action != IWL_MIMO2_SWITCH_SISO_A) + tbl->action = IWL_MIMO2_SWITCH_SISO_A; + break; + case IWL_BT_COEX_TRAFFIC_LOAD_LOW: + /* avoid antenna B unless MIMO */ + if (tbl->action == IWL_MIMO2_SWITCH_SISO_B || + tbl->action == IWL_MIMO2_SWITCH_SISO_C) + tbl->action = IWL_MIMO2_SWITCH_SISO_A; + break; + default: + IWL_ERR(priv, "Invalid BT load %d\n", priv->bt_traffic_load); + break; + } + + if ((iwl_tx_ant_restriction(priv) == IWL_ANT_OK_SINGLE) && + (tbl->action < IWL_MIMO2_SWITCH_SISO_A || + tbl->action > IWL_MIMO2_SWITCH_SISO_C)) { + /* switch in SISO */ + tbl->action = IWL_MIMO2_SWITCH_SISO_A; + } + + /* configure as 1x1 if bt full concurrency */ + if (priv->bt_full_concurrent && + (tbl->action < IWL_MIMO2_SWITCH_SISO_A || + tbl->action > IWL_MIMO2_SWITCH_SISO_C)) + tbl->action = IWL_MIMO2_SWITCH_SISO_A; + + start_action = tbl->action; + for (;;) { + lq_sta->action_counter++; + switch (tbl->action) { + case IWL_MIMO2_SWITCH_ANTENNA1: + case IWL_MIMO2_SWITCH_ANTENNA2: + IWL_DEBUG_RATE(priv, "LQ: MIMO2 toggle Antennas\n"); + + if (tx_chains_num <= 2) + break; + + if (window->success_ratio >= IWL_RS_GOOD_RATIO) + break; + + memcpy(search_tbl, tbl, sz); + if (rs_toggle_antenna(valid_tx_ant, + &search_tbl->current_rate, search_tbl)) { + update_search_tbl_counter = 1; + goto out; + } + break; + case IWL_MIMO2_SWITCH_SISO_A: + case IWL_MIMO2_SWITCH_SISO_B: + case IWL_MIMO2_SWITCH_SISO_C: + IWL_DEBUG_RATE(priv, "LQ: MIMO2 switch to SISO\n"); + + /* Set up new search table for SISO */ + memcpy(search_tbl, tbl, sz); + + if (tbl->action == IWL_MIMO2_SWITCH_SISO_A) + search_tbl->ant_type = ANT_A; + else if (tbl->action == IWL_MIMO2_SWITCH_SISO_B) + search_tbl->ant_type = ANT_B; + else + search_tbl->ant_type = ANT_C; + + if (!rs_is_valid_ant(valid_tx_ant, search_tbl->ant_type)) + break; + + ret = rs_switch_to_siso(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) + goto out; + + break; + + case IWL_MIMO2_SWITCH_GI: + if (!tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_20)) + break; + if (tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_40)) + break; + + IWL_DEBUG_RATE(priv, "LQ: MIMO2 toggle SGI/NGI\n"); + + /* Set up new search table for MIMO2 */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = !tbl->is_SGI; + rs_set_expected_tpt_table(lq_sta, search_tbl); + /* + * If active table already uses the fastest possible + * modulation (dual stream with short guard interval), + * and it's working well, there's no need to look + * for a better type of modulation! + */ + if (tbl->is_SGI) { + s32 tpt = lq_sta->last_tpt / 100; + if (tpt >= search_tbl->expected_tpt[index]) + break; + } + search_tbl->current_rate = + rate_n_flags_from_tbl(priv, search_tbl, + index, is_green); + update_search_tbl_counter = 1; + goto out; + + case IWL_MIMO2_SWITCH_MIMO3_ABC: + IWL_DEBUG_RATE(priv, "LQ: MIMO2 switch to MIMO3\n"); + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + search_tbl->ant_type = ANT_ABC; + + if (!rs_is_valid_ant(valid_tx_ant, search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo3(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) + goto out; + + break; + } + tbl->action++; + if (tbl->action > IWL_MIMO2_SWITCH_MIMO3_ABC) + tbl->action = IWL_MIMO2_SWITCH_ANTENNA1; + + if (tbl->action == start_action) + break; + } + search_tbl->lq_type = LQ_NONE; + return; + out: + lq_sta->search_better_tbl = 1; + tbl->action++; + if (tbl->action > IWL_MIMO2_SWITCH_MIMO3_ABC) + tbl->action = IWL_MIMO2_SWITCH_ANTENNA1; + if (update_search_tbl_counter) + search_tbl->action = tbl->action; + +} + +/* + * Try to switch to new modulation mode from MIMO3 + */ +static void rs_move_mimo3_to_other(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, int index) +{ + s8 is_green = lq_sta->is_green; + struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + struct iwl_scale_tbl_info *search_tbl = + &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + struct iwl_rate_scale_data *window = &(tbl->win[index]); + struct ieee80211_sta_ht_cap *ht_cap = &sta->deflink.ht_cap; + u32 sz = (sizeof(struct iwl_scale_tbl_info) - + (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT)); + u8 start_action; + u8 valid_tx_ant = priv->nvm_data->valid_tx_ant; + u8 tx_chains_num = priv->hw_params.tx_chains_num; + int ret; + u8 update_search_tbl_counter = 0; + + switch (priv->bt_traffic_load) { + case IWL_BT_COEX_TRAFFIC_LOAD_NONE: + /* nothing */ + break; + case IWL_BT_COEX_TRAFFIC_LOAD_HIGH: + case IWL_BT_COEX_TRAFFIC_LOAD_CONTINUOUS: + /* avoid antenna B and MIMO */ + if (tbl->action != IWL_MIMO3_SWITCH_SISO_A) + tbl->action = IWL_MIMO3_SWITCH_SISO_A; + break; + case IWL_BT_COEX_TRAFFIC_LOAD_LOW: + /* avoid antenna B unless MIMO */ + if (tbl->action == IWL_MIMO3_SWITCH_SISO_B || + tbl->action == IWL_MIMO3_SWITCH_SISO_C) + tbl->action = IWL_MIMO3_SWITCH_SISO_A; + break; + default: + IWL_ERR(priv, "Invalid BT load %d\n", priv->bt_traffic_load); + break; + } + + if ((iwl_tx_ant_restriction(priv) == IWL_ANT_OK_SINGLE) && + (tbl->action < IWL_MIMO3_SWITCH_SISO_A || + tbl->action > IWL_MIMO3_SWITCH_SISO_C)) { + /* switch in SISO */ + tbl->action = IWL_MIMO3_SWITCH_SISO_A; + } + + /* configure as 1x1 if bt full concurrency */ + if (priv->bt_full_concurrent && + (tbl->action < IWL_MIMO3_SWITCH_SISO_A || + tbl->action > IWL_MIMO3_SWITCH_SISO_C)) + tbl->action = IWL_MIMO3_SWITCH_SISO_A; + + start_action = tbl->action; + for (;;) { + lq_sta->action_counter++; + switch (tbl->action) { + case IWL_MIMO3_SWITCH_ANTENNA1: + case IWL_MIMO3_SWITCH_ANTENNA2: + IWL_DEBUG_RATE(priv, "LQ: MIMO3 toggle Antennas\n"); + + if (tx_chains_num <= 3) + break; + + if (window->success_ratio >= IWL_RS_GOOD_RATIO) + break; + + memcpy(search_tbl, tbl, sz); + if (rs_toggle_antenna(valid_tx_ant, + &search_tbl->current_rate, search_tbl)) + goto out; + break; + case IWL_MIMO3_SWITCH_SISO_A: + case IWL_MIMO3_SWITCH_SISO_B: + case IWL_MIMO3_SWITCH_SISO_C: + IWL_DEBUG_RATE(priv, "LQ: MIMO3 switch to SISO\n"); + + /* Set up new search table for SISO */ + memcpy(search_tbl, tbl, sz); + + if (tbl->action == IWL_MIMO3_SWITCH_SISO_A) + search_tbl->ant_type = ANT_A; + else if (tbl->action == IWL_MIMO3_SWITCH_SISO_B) + search_tbl->ant_type = ANT_B; + else + search_tbl->ant_type = ANT_C; + + if (!rs_is_valid_ant(valid_tx_ant, search_tbl->ant_type)) + break; + + ret = rs_switch_to_siso(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) + goto out; + + break; + + case IWL_MIMO3_SWITCH_MIMO2_AB: + case IWL_MIMO3_SWITCH_MIMO2_AC: + case IWL_MIMO3_SWITCH_MIMO2_BC: + IWL_DEBUG_RATE(priv, "LQ: MIMO3 switch to MIMO2\n"); + + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + if (tbl->action == IWL_MIMO3_SWITCH_MIMO2_AB) + search_tbl->ant_type = ANT_AB; + else if (tbl->action == IWL_MIMO3_SWITCH_MIMO2_AC) + search_tbl->ant_type = ANT_AC; + else + search_tbl->ant_type = ANT_BC; + + if (!rs_is_valid_ant(valid_tx_ant, search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo2(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) + goto out; + + break; + + case IWL_MIMO3_SWITCH_GI: + if (!tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_20)) + break; + if (tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_40)) + break; + + IWL_DEBUG_RATE(priv, "LQ: MIMO3 toggle SGI/NGI\n"); + + /* Set up new search table for MIMO */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = !tbl->is_SGI; + rs_set_expected_tpt_table(lq_sta, search_tbl); + /* + * If active table already uses the fastest possible + * modulation (dual stream with short guard interval), + * and it's working well, there's no need to look + * for a better type of modulation! + */ + if (tbl->is_SGI) { + s32 tpt = lq_sta->last_tpt / 100; + if (tpt >= search_tbl->expected_tpt[index]) + break; + } + search_tbl->current_rate = + rate_n_flags_from_tbl(priv, search_tbl, + index, is_green); + update_search_tbl_counter = 1; + goto out; + } + tbl->action++; + if (tbl->action > IWL_MIMO3_SWITCH_GI) + tbl->action = IWL_MIMO3_SWITCH_ANTENNA1; + + if (tbl->action == start_action) + break; + } + search_tbl->lq_type = LQ_NONE; + return; + out: + lq_sta->search_better_tbl = 1; + tbl->action++; + if (tbl->action > IWL_MIMO3_SWITCH_GI) + tbl->action = IWL_MIMO3_SWITCH_ANTENNA1; + if (update_search_tbl_counter) + search_tbl->action = tbl->action; +} + +/* + * Check whether we should continue using same modulation mode, or + * begin search for a new mode, based on: + * 1) # tx successes or failures while using this mode + * 2) # times calling this function + * 3) elapsed time in this mode (not used, for now) + */ +static void rs_stay_in_table(struct iwl_lq_sta *lq_sta, bool force_search) +{ + struct iwl_scale_tbl_info *tbl; + int i; + int active_tbl; + int flush_interval_passed = 0; + struct iwl_priv *priv; + + priv = lq_sta->drv; + active_tbl = lq_sta->active_tbl; + + tbl = &(lq_sta->lq_info[active_tbl]); + + /* If we've been disallowing search, see if we should now allow it */ + if (lq_sta->stay_in_tbl) { + + /* Elapsed time using current modulation mode */ + if (lq_sta->flush_timer) + flush_interval_passed = + time_after(jiffies, + (unsigned long)(lq_sta->flush_timer + + IWL_RATE_SCALE_FLUSH_INTVL)); + + /* + * Check if we should allow search for new modulation mode. + * If many frames have failed or succeeded, or we've used + * this same modulation for a long time, allow search, and + * reset history stats that keep track of whether we should + * allow a new search. Also (below) reset all bitmaps and + * stats in active history. + */ + if (force_search || + (lq_sta->total_failed > lq_sta->max_failure_limit) || + (lq_sta->total_success > lq_sta->max_success_limit) || + ((!lq_sta->search_better_tbl) && (lq_sta->flush_timer) + && (flush_interval_passed))) { + IWL_DEBUG_RATE(priv, "LQ: stay is expired %d %d %d\n", + lq_sta->total_failed, + lq_sta->total_success, + flush_interval_passed); + + /* Allow search for new mode */ + lq_sta->stay_in_tbl = 0; /* only place reset */ + lq_sta->total_failed = 0; + lq_sta->total_success = 0; + lq_sta->flush_timer = 0; + + /* + * Else if we've used this modulation mode enough repetitions + * (regardless of elapsed time or success/failure), reset + * history bitmaps and rate-specific stats for all rates in + * active table. + */ + } else { + lq_sta->table_count++; + if (lq_sta->table_count >= + lq_sta->table_count_limit) { + lq_sta->table_count = 0; + + IWL_DEBUG_RATE(priv, "LQ: stay in table clear win\n"); + for (i = 0; i < IWL_RATE_COUNT; i++) + rs_rate_scale_clear_window( + &(tbl->win[i])); + } + } + + /* If transitioning to allow "search", reset all history + * bitmaps and stats in active table (this will become the new + * "search" table). */ + if (!lq_sta->stay_in_tbl) { + for (i = 0; i < IWL_RATE_COUNT; i++) + rs_rate_scale_clear_window(&(tbl->win[i])); + } + } +} + +/* + * setup rate table in uCode + */ +static void rs_update_rate_tbl(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl, + int index, u8 is_green) +{ + u32 rate; + + /* Update uCode's rate table. */ + rate = rate_n_flags_from_tbl(priv, tbl, index, is_green); + rs_fill_link_cmd(priv, lq_sta, rate); + iwl_send_lq_cmd(priv, ctx, &lq_sta->lq, CMD_ASYNC, false); +} + +/* + * Do rate scaling and search for new modulation mode. + */ +static void rs_rate_scale_perform(struct iwl_priv *priv, + struct sk_buff *skb, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta) +{ + struct ieee80211_hw *hw = priv->hw; + struct ieee80211_conf *conf = &hw->conf; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + int low = IWL_RATE_INVALID; + int high = IWL_RATE_INVALID; + int index; + int i; + struct iwl_rate_scale_data *window = NULL; + int current_tpt = IWL_INVALID_VALUE; + int low_tpt = IWL_INVALID_VALUE; + int high_tpt = IWL_INVALID_VALUE; + u32 fail_count; + s8 scale_action = 0; + u16 rate_mask; + u8 update_lq = 0; + struct iwl_scale_tbl_info *tbl, *tbl1; + u16 rate_scale_index_msk = 0; + u8 is_green = 0; + u8 active_tbl = 0; + u8 done_search = 0; + u16 high_low; + s32 sr; + u8 tid = IWL_MAX_TID_COUNT; + struct iwl_tid_data *tid_data; + struct iwl_station_priv *sta_priv = (void *)sta->drv_priv; + struct iwl_rxon_context *ctx = sta_priv->ctx; + + IWL_DEBUG_RATE(priv, "rate scale calculate new rate for skb\n"); + + /* Send management frames and NO_ACK data using lowest rate. */ + /* TODO: this could probably be improved.. */ + if (!ieee80211_is_data(hdr->frame_control) || + info->flags & IEEE80211_TX_CTL_NO_ACK) + return; + + lq_sta->supp_rates = sta->deflink.supp_rates[lq_sta->band]; + + tid = rs_tl_add_packet(lq_sta, hdr); + if ((tid != IWL_MAX_TID_COUNT) && + (lq_sta->tx_agg_tid_en & (1 << tid))) { + tid_data = &priv->tid_data[lq_sta->lq.sta_id][tid]; + if (tid_data->agg.state == IWL_AGG_OFF) + lq_sta->is_agg = 0; + else + lq_sta->is_agg = 1; + } else + lq_sta->is_agg = 0; + + /* + * Select rate-scale / modulation-mode table to work with in + * the rest of this function: "search" if searching for better + * modulation mode, or "active" if doing rate scaling within a mode. + */ + if (!lq_sta->search_better_tbl) + active_tbl = lq_sta->active_tbl; + else + active_tbl = 1 - lq_sta->active_tbl; + + tbl = &(lq_sta->lq_info[active_tbl]); + if (is_legacy(tbl->lq_type)) + lq_sta->is_green = 0; + else + lq_sta->is_green = rs_use_green(sta); + is_green = lq_sta->is_green; + + /* current tx rate */ + index = lq_sta->last_txrate_idx; + + IWL_DEBUG_RATE(priv, "Rate scale index %d for type %d\n", index, + tbl->lq_type); + + /* rates available for this association, and for modulation mode */ + rate_mask = rs_get_supported_rates(lq_sta, hdr, tbl->lq_type); + + IWL_DEBUG_RATE(priv, "mask 0x%04X\n", rate_mask); + + /* mask with station rate restriction */ + if (is_legacy(tbl->lq_type)) { + if (lq_sta->band == NL80211_BAND_5GHZ) + /* supp_rates has no CCK bits in A mode */ + rate_scale_index_msk = (u16) (rate_mask & + (lq_sta->supp_rates << IWL_FIRST_OFDM_RATE)); + else + rate_scale_index_msk = (u16) (rate_mask & + lq_sta->supp_rates); + + } else + rate_scale_index_msk = rate_mask; + + if (!rate_scale_index_msk) + rate_scale_index_msk = rate_mask; + + if (!((1 << index) & rate_scale_index_msk)) { + IWL_ERR(priv, "Current Rate is not valid\n"); + if (lq_sta->search_better_tbl) { + /* revert to active table if search table is not valid*/ + tbl->lq_type = LQ_NONE; + lq_sta->search_better_tbl = 0; + tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + /* get "active" rate info */ + index = iwl_hwrate_to_plcp_idx(tbl->current_rate); + rs_update_rate_tbl(priv, ctx, lq_sta, tbl, + index, is_green); + } + return; + } + + /* Get expected throughput table and history window for current rate */ + if (!tbl->expected_tpt) { + IWL_ERR(priv, "tbl->expected_tpt is NULL\n"); + return; + } + + /* force user max rate if set by user */ + if ((lq_sta->max_rate_idx != -1) && + (lq_sta->max_rate_idx < index)) { + index = lq_sta->max_rate_idx; + update_lq = 1; + window = &(tbl->win[index]); + goto lq_update; + } + + window = &(tbl->win[index]); + + /* + * If there is not enough history to calculate actual average + * throughput, keep analyzing results of more tx frames, without + * changing rate or mode (bypass most of the rest of this function). + * Set up new rate table in uCode only if old rate is not supported + * in current association (use new rate found above). + */ + fail_count = window->counter - window->success_counter; + if ((fail_count < IWL_RATE_MIN_FAILURE_TH) && + (window->success_counter < IWL_RATE_MIN_SUCCESS_TH)) { + IWL_DEBUG_RATE(priv, "LQ: still below TH. succ=%d total=%d " + "for index %d\n", + window->success_counter, window->counter, index); + + /* Can't calculate this yet; not enough history */ + window->average_tpt = IWL_INVALID_VALUE; + + /* Should we stay with this modulation mode, + * or search for a new one? */ + rs_stay_in_table(lq_sta, false); + + goto out; + } + /* Else we have enough samples; calculate estimate of + * actual average throughput */ + if (window->average_tpt != ((window->success_ratio * + tbl->expected_tpt[index] + 64) / 128)) { + IWL_ERR(priv, "expected_tpt should have been calculated by now\n"); + window->average_tpt = ((window->success_ratio * + tbl->expected_tpt[index] + 64) / 128); + } + + /* If we are searching for better modulation mode, check success. */ + if (lq_sta->search_better_tbl && + (iwl_tx_ant_restriction(priv) == IWL_ANT_OK_MULTI)) { + /* If good success, continue using the "search" mode; + * no need to send new link quality command, since we're + * continuing to use the setup that we've been trying. */ + if (window->average_tpt > lq_sta->last_tpt) { + + IWL_DEBUG_RATE(priv, "LQ: SWITCHING TO NEW TABLE " + "suc=%d cur-tpt=%d old-tpt=%d\n", + window->success_ratio, + window->average_tpt, + lq_sta->last_tpt); + + if (!is_legacy(tbl->lq_type)) + lq_sta->enable_counter = 1; + + /* Swap tables; "search" becomes "active" */ + lq_sta->active_tbl = active_tbl; + current_tpt = window->average_tpt; + + /* Else poor success; go back to mode in "active" table */ + } else { + + IWL_DEBUG_RATE(priv, "LQ: GOING BACK TO THE OLD TABLE " + "suc=%d cur-tpt=%d old-tpt=%d\n", + window->success_ratio, + window->average_tpt, + lq_sta->last_tpt); + + /* Nullify "search" table */ + tbl->lq_type = LQ_NONE; + + /* Revert to "active" table */ + active_tbl = lq_sta->active_tbl; + tbl = &(lq_sta->lq_info[active_tbl]); + + /* Revert to "active" rate and throughput info */ + index = iwl_hwrate_to_plcp_idx(tbl->current_rate); + current_tpt = lq_sta->last_tpt; + + /* Need to set up a new rate table in uCode */ + update_lq = 1; + } + + /* Either way, we've made a decision; modulation mode + * search is done, allow rate adjustment next time. */ + lq_sta->search_better_tbl = 0; + done_search = 1; /* Don't switch modes below! */ + goto lq_update; + } + + /* (Else) not in search of better modulation mode, try for better + * starting rate, while staying in this mode. */ + high_low = rs_get_adjacent_rate(priv, index, rate_scale_index_msk, + tbl->lq_type); + low = high_low & 0xff; + high = (high_low >> 8) & 0xff; + + /* If user set max rate, dont allow higher than user constrain */ + if ((lq_sta->max_rate_idx != -1) && + (lq_sta->max_rate_idx < high)) + high = IWL_RATE_INVALID; + + sr = window->success_ratio; + + /* Collect measured throughputs for current and adjacent rates */ + current_tpt = window->average_tpt; + if (low != IWL_RATE_INVALID) + low_tpt = tbl->win[low].average_tpt; + if (high != IWL_RATE_INVALID) + high_tpt = tbl->win[high].average_tpt; + + scale_action = 0; + + /* Too many failures, decrease rate */ + if ((sr <= IWL_RATE_DECREASE_TH) || (current_tpt == 0)) { + IWL_DEBUG_RATE(priv, "decrease rate because of low success_ratio\n"); + scale_action = -1; + + /* No throughput measured yet for adjacent rates; try increase. */ + } else if ((low_tpt == IWL_INVALID_VALUE) && + (high_tpt == IWL_INVALID_VALUE)) { + + if (high != IWL_RATE_INVALID && sr >= IWL_RATE_INCREASE_TH) + scale_action = 1; + else if (low != IWL_RATE_INVALID) + scale_action = 0; + } + + /* Both adjacent throughputs are measured, but neither one has better + * throughput; we're using the best rate, don't change it! */ + else if ((low_tpt != IWL_INVALID_VALUE) && + (high_tpt != IWL_INVALID_VALUE) && + (low_tpt < current_tpt) && + (high_tpt < current_tpt)) + scale_action = 0; + + /* At least one adjacent rate's throughput is measured, + * and may have better performance. */ + else { + /* Higher adjacent rate's throughput is measured */ + if (high_tpt != IWL_INVALID_VALUE) { + /* Higher rate has better throughput */ + if (high_tpt > current_tpt && + sr >= IWL_RATE_INCREASE_TH) { + scale_action = 1; + } else { + scale_action = 0; + } + + /* Lower adjacent rate's throughput is measured */ + } else if (low_tpt != IWL_INVALID_VALUE) { + /* Lower rate has better throughput */ + if (low_tpt > current_tpt) { + IWL_DEBUG_RATE(priv, + "decrease rate because of low tpt\n"); + scale_action = -1; + } else if (sr >= IWL_RATE_INCREASE_TH) { + scale_action = 1; + } + } + } + + /* Sanity check; asked for decrease, but success rate or throughput + * has been good at old rate. Don't change it. */ + if ((scale_action == -1) && (low != IWL_RATE_INVALID) && + ((sr > IWL_RATE_HIGH_TH) || + (current_tpt > (100 * tbl->expected_tpt[low])))) + scale_action = 0; + if (!iwl_ht_enabled(priv) && !is_legacy(tbl->lq_type)) + scale_action = -1; + if (iwl_tx_ant_restriction(priv) != IWL_ANT_OK_MULTI && + (is_mimo2(tbl->lq_type) || is_mimo3(tbl->lq_type))) + scale_action = -1; + + if ((priv->bt_traffic_load >= IWL_BT_COEX_TRAFFIC_LOAD_HIGH) && + (is_mimo2(tbl->lq_type) || is_mimo3(tbl->lq_type))) { + if (lq_sta->last_bt_traffic > priv->bt_traffic_load) { + /* + * don't set scale_action, don't want to scale up if + * the rate scale doesn't otherwise think that is a + * good idea. + */ + } else if (lq_sta->last_bt_traffic <= priv->bt_traffic_load) { + scale_action = -1; + } + } + lq_sta->last_bt_traffic = priv->bt_traffic_load; + + if ((priv->bt_traffic_load >= IWL_BT_COEX_TRAFFIC_LOAD_HIGH) && + (is_mimo2(tbl->lq_type) || is_mimo3(tbl->lq_type))) { + /* search for a new modulation */ + rs_stay_in_table(lq_sta, true); + goto lq_update; + } + + switch (scale_action) { + case -1: + /* Decrease starting rate, update uCode's rate table */ + if (low != IWL_RATE_INVALID) { + update_lq = 1; + index = low; + } + + break; + case 1: + /* Increase starting rate, update uCode's rate table */ + if (high != IWL_RATE_INVALID) { + update_lq = 1; + index = high; + } + + break; + case 0: + /* No change */ + default: + break; + } + + IWL_DEBUG_RATE(priv, "choose rate scale index %d action %d low %d " + "high %d type %d\n", + index, scale_action, low, high, tbl->lq_type); + +lq_update: + /* Replace uCode's rate table for the destination station. */ + if (update_lq) + rs_update_rate_tbl(priv, ctx, lq_sta, tbl, index, is_green); + + if (iwl_tx_ant_restriction(priv) == IWL_ANT_OK_MULTI) { + /* Should we stay with this modulation mode, + * or search for a new one? */ + rs_stay_in_table(lq_sta, false); + } + /* + * Search for new modulation mode if we're: + * 1) Not changing rates right now + * 2) Not just finishing up a search + * 3) Allowing a new search + */ + if (!update_lq && !done_search && !lq_sta->stay_in_tbl && window->counter) { + /* Save current throughput to compare with "search" throughput*/ + lq_sta->last_tpt = current_tpt; + + /* Select a new "search" modulation mode to try. + * If one is found, set up the new "search" table. */ + if (is_legacy(tbl->lq_type)) + rs_move_legacy_other(priv, lq_sta, conf, sta, index); + else if (is_siso(tbl->lq_type)) + rs_move_siso_to_other(priv, lq_sta, conf, sta, index); + else if (is_mimo2(tbl->lq_type)) + rs_move_mimo2_to_other(priv, lq_sta, conf, sta, index); + else + rs_move_mimo3_to_other(priv, lq_sta, conf, sta, index); + + /* If new "search" mode was selected, set up in uCode table */ + if (lq_sta->search_better_tbl) { + /* Access the "search" table, clear its history. */ + tbl = &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + for (i = 0; i < IWL_RATE_COUNT; i++) + rs_rate_scale_clear_window(&(tbl->win[i])); + + /* Use new "search" start rate */ + index = iwl_hwrate_to_plcp_idx(tbl->current_rate); + + IWL_DEBUG_RATE(priv, "Switch current mcs: %X index: %d\n", + tbl->current_rate, index); + rs_fill_link_cmd(priv, lq_sta, tbl->current_rate); + iwl_send_lq_cmd(priv, ctx, &lq_sta->lq, CMD_ASYNC, false); + } else + done_search = 1; + } + + if (done_search && !lq_sta->stay_in_tbl) { + /* If the "active" (non-search) mode was legacy, + * and we've tried switching antennas, + * but we haven't been able to try HT modes (not available), + * stay with best antenna legacy modulation for a while + * before next round of mode comparisons. */ + tbl1 = &(lq_sta->lq_info[lq_sta->active_tbl]); + if (is_legacy(tbl1->lq_type) && !conf_is_ht(conf) && + lq_sta->action_counter > tbl1->max_search) { + IWL_DEBUG_RATE(priv, "LQ: STAY in legacy table\n"); + rs_set_stay_in_table(priv, 1, lq_sta); + } + + /* If we're in an HT mode, and all 3 mode switch actions + * have been tried and compared, stay in this best modulation + * mode for a while before next round of mode comparisons. */ + if (lq_sta->enable_counter && + (lq_sta->action_counter >= tbl1->max_search) && + iwl_ht_enabled(priv)) { + if ((lq_sta->last_tpt > IWL_AGG_TPT_THREHOLD) && + (lq_sta->tx_agg_tid_en & (1 << tid)) && + (tid != IWL_MAX_TID_COUNT)) { + u8 sta_id = lq_sta->lq.sta_id; + tid_data = &priv->tid_data[sta_id][tid]; + if (tid_data->agg.state == IWL_AGG_OFF) { + IWL_DEBUG_RATE(priv, + "try to aggregate tid %d\n", + tid); + rs_tl_turn_on_agg(priv, tid, + lq_sta, sta); + } + } + rs_set_stay_in_table(priv, 0, lq_sta); + } + } + +out: + tbl->current_rate = rate_n_flags_from_tbl(priv, tbl, index, is_green); + lq_sta->last_txrate_idx = index; +} + +/* + * rs_initialize_lq - Initialize a station's hardware rate table + * + * The uCode's station table contains a table of fallback rates + * for automatic fallback during transmission. + * + * NOTE: This sets up a default set of values. These will be replaced later + * if the driver's iwl-agn-rs rate scaling algorithm is used, instead of + * rc80211_simple. + * + * NOTE: Run REPLY_ADD_STA command to set up station table entry, before + * calling this function (which runs REPLY_TX_LINK_QUALITY_CMD, + * which requires station table entry to exist). + */ +static void rs_initialize_lq(struct iwl_priv *priv, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta) +{ + struct iwl_scale_tbl_info *tbl; + int rate_idx; + int i; + u32 rate; + u8 use_green = rs_use_green(sta); + u8 active_tbl = 0; + u8 valid_tx_ant; + struct iwl_station_priv *sta_priv; + struct iwl_rxon_context *ctx; + + if (!sta || !lq_sta) + return; + + sta_priv = (void *)sta->drv_priv; + ctx = sta_priv->ctx; + + i = lq_sta->last_txrate_idx; + + valid_tx_ant = priv->nvm_data->valid_tx_ant; + + if (!lq_sta->search_better_tbl) + active_tbl = lq_sta->active_tbl; + else + active_tbl = 1 - lq_sta->active_tbl; + + tbl = &(lq_sta->lq_info[active_tbl]); + + if ((i < 0) || (i >= IWL_RATE_COUNT)) + i = 0; + + rate = iwl_rates[i].plcp; + tbl->ant_type = first_antenna(valid_tx_ant); + rate |= tbl->ant_type << RATE_MCS_ANT_POS; + + if (i >= IWL_FIRST_CCK_RATE && i <= IWL_LAST_CCK_RATE) + rate |= RATE_MCS_CCK_MSK; + + rs_get_tbl_info_from_mcs(rate, priv->band, tbl, &rate_idx); + if (!rs_is_valid_ant(valid_tx_ant, tbl->ant_type)) + rs_toggle_antenna(valid_tx_ant, &rate, tbl); + + rate = rate_n_flags_from_tbl(priv, tbl, rate_idx, use_green); + tbl->current_rate = rate; + rs_set_expected_tpt_table(lq_sta, tbl); + rs_fill_link_cmd(NULL, lq_sta, rate); + priv->stations[lq_sta->lq.sta_id].lq = &lq_sta->lq; + iwl_send_lq_cmd(priv, ctx, &lq_sta->lq, 0, true); +} + +static void rs_get_rate(void *priv_r, struct ieee80211_sta *sta, void *priv_sta, + struct ieee80211_tx_rate_control *txrc) +{ + + struct sk_buff *skb = txrc->skb; + struct ieee80211_supported_band *sband = txrc->sband; + struct iwl_op_mode *op_mode __maybe_unused = + (struct iwl_op_mode *)priv_r; + struct iwl_priv *priv __maybe_unused = IWL_OP_MODE_GET_DVM(op_mode); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct iwl_lq_sta *lq_sta = priv_sta; + int rate_idx; + + IWL_DEBUG_RATE_LIMIT(priv, "rate scale calculate new rate for skb\n"); + + /* Get max rate if user set max rate */ + if (lq_sta) { + lq_sta->max_rate_idx = fls(txrc->rate_idx_mask) - 1; + if ((sband->band == NL80211_BAND_5GHZ) && + (lq_sta->max_rate_idx != -1)) + lq_sta->max_rate_idx += IWL_FIRST_OFDM_RATE; + if ((lq_sta->max_rate_idx < 0) || + (lq_sta->max_rate_idx >= IWL_RATE_COUNT)) + lq_sta->max_rate_idx = -1; + } + + /* Treat uninitialized rate scaling data same as non-existing. */ + if (lq_sta && !lq_sta->drv) { + IWL_DEBUG_RATE(priv, "Rate scaling not initialized yet.\n"); + priv_sta = NULL; + } + + rate_idx = lq_sta->last_txrate_idx; + + if (lq_sta->last_rate_n_flags & RATE_MCS_HT_MSK) { + rate_idx -= IWL_FIRST_OFDM_RATE; + /* 6M and 9M shared same MCS index */ + rate_idx = (rate_idx > 0) ? (rate_idx - 1) : 0; + if (rs_extract_rate(lq_sta->last_rate_n_flags) >= + IWL_RATE_MIMO3_6M_PLCP) + rate_idx = rate_idx + (2 * MCS_INDEX_PER_STREAM); + else if (rs_extract_rate(lq_sta->last_rate_n_flags) >= + IWL_RATE_MIMO2_6M_PLCP) + rate_idx = rate_idx + MCS_INDEX_PER_STREAM; + info->control.rates[0].flags = IEEE80211_TX_RC_MCS; + if (lq_sta->last_rate_n_flags & RATE_MCS_SGI_MSK) + info->control.rates[0].flags |= IEEE80211_TX_RC_SHORT_GI; + if (lq_sta->last_rate_n_flags & RATE_MCS_DUP_MSK) + info->control.rates[0].flags |= IEEE80211_TX_RC_DUP_DATA; + if (lq_sta->last_rate_n_flags & RATE_MCS_HT40_MSK) + info->control.rates[0].flags |= IEEE80211_TX_RC_40_MHZ_WIDTH; + if (lq_sta->last_rate_n_flags & RATE_MCS_GF_MSK) + info->control.rates[0].flags |= IEEE80211_TX_RC_GREEN_FIELD; + } else { + /* Check for invalid rates */ + if ((rate_idx < 0) || (rate_idx >= IWL_RATE_COUNT_LEGACY) || + ((sband->band == NL80211_BAND_5GHZ) && + (rate_idx < IWL_FIRST_OFDM_RATE))) + rate_idx = rate_lowest_index(sband, sta); + /* On valid 5 GHz rate, adjust index */ + else if (sband->band == NL80211_BAND_5GHZ) + rate_idx -= IWL_FIRST_OFDM_RATE; + info->control.rates[0].flags = 0; + } + info->control.rates[0].idx = rate_idx; + info->control.rates[0].count = 1; +} + +static void *rs_alloc_sta(void *priv_rate, struct ieee80211_sta *sta, + gfp_t gfp) +{ + struct iwl_station_priv *sta_priv = (struct iwl_station_priv *) sta->drv_priv; + struct iwl_op_mode *op_mode __maybe_unused = + (struct iwl_op_mode *)priv_rate; + struct iwl_priv *priv __maybe_unused = IWL_OP_MODE_GET_DVM(op_mode); + + IWL_DEBUG_RATE(priv, "create station rate scale window\n"); + + return &sta_priv->lq_sta; +} + +/* + * Called after adding a new station to initialize rate scaling + */ +void iwl_rs_rate_init(struct iwl_priv *priv, struct ieee80211_sta *sta, u8 sta_id) +{ + int i, j; + struct ieee80211_hw *hw = priv->hw; + struct ieee80211_conf *conf = &priv->hw->conf; + struct ieee80211_sta_ht_cap *ht_cap = &sta->deflink.ht_cap; + struct iwl_station_priv *sta_priv; + struct iwl_lq_sta *lq_sta; + struct ieee80211_supported_band *sband; + unsigned long supp; /* must be unsigned long for for_each_set_bit */ + + sta_priv = (struct iwl_station_priv *) sta->drv_priv; + lq_sta = &sta_priv->lq_sta; + sband = hw->wiphy->bands[conf->chandef.chan->band]; + + + lq_sta->lq.sta_id = sta_id; + + for (j = 0; j < LQ_SIZE; j++) + for (i = 0; i < IWL_RATE_COUNT; i++) + rs_rate_scale_clear_window(&lq_sta->lq_info[j].win[i]); + + lq_sta->flush_timer = 0; + lq_sta->supp_rates = sta->deflink.supp_rates[sband->band]; + + IWL_DEBUG_RATE(priv, "LQ: *** rate scale station global init for station %d ***\n", + sta_id); + /* TODO: what is a good starting rate for STA? About middle? Maybe not + * the lowest or the highest rate.. Could consider using RSSI from + * previous packets? Need to have IEEE 802.1X auth succeed immediately + * after assoc.. */ + + lq_sta->is_dup = 0; + lq_sta->max_rate_idx = -1; + lq_sta->missed_rate_counter = IWL_MISSED_RATE_MAX; + lq_sta->is_green = rs_use_green(sta); + lq_sta->band = sband->band; + /* + * active legacy rates as per supported rates bitmap + */ + supp = sta->deflink.supp_rates[sband->band]; + lq_sta->active_legacy_rate = 0; + for_each_set_bit(i, &supp, BITS_PER_LONG) + lq_sta->active_legacy_rate |= BIT(sband->bitrates[i].hw_value); + + /* + * active_siso_rate mask includes 9 MBits (bit 5), and CCK (bits 0-3), + * supp_rates[] does not; shift to convert format, force 9 MBits off. + */ + lq_sta->active_siso_rate = ht_cap->mcs.rx_mask[0] << 1; + lq_sta->active_siso_rate |= ht_cap->mcs.rx_mask[0] & 0x1; + lq_sta->active_siso_rate &= ~((u16)0x2); + lq_sta->active_siso_rate <<= IWL_FIRST_OFDM_RATE; + + /* Same here */ + lq_sta->active_mimo2_rate = ht_cap->mcs.rx_mask[1] << 1; + lq_sta->active_mimo2_rate |= ht_cap->mcs.rx_mask[1] & 0x1; + lq_sta->active_mimo2_rate &= ~((u16)0x2); + lq_sta->active_mimo2_rate <<= IWL_FIRST_OFDM_RATE; + + lq_sta->active_mimo3_rate = ht_cap->mcs.rx_mask[2] << 1; + lq_sta->active_mimo3_rate |= ht_cap->mcs.rx_mask[2] & 0x1; + lq_sta->active_mimo3_rate &= ~((u16)0x2); + lq_sta->active_mimo3_rate <<= IWL_FIRST_OFDM_RATE; + + IWL_DEBUG_RATE(priv, "SISO-RATE=%X MIMO2-RATE=%X MIMO3-RATE=%X\n", + lq_sta->active_siso_rate, + lq_sta->active_mimo2_rate, + lq_sta->active_mimo3_rate); + + /* These values will be overridden later */ + lq_sta->lq.general_params.single_stream_ant_msk = + first_antenna(priv->nvm_data->valid_tx_ant); + lq_sta->lq.general_params.dual_stream_ant_msk = + priv->nvm_data->valid_tx_ant & + ~first_antenna(priv->nvm_data->valid_tx_ant); + if (!lq_sta->lq.general_params.dual_stream_ant_msk) { + lq_sta->lq.general_params.dual_stream_ant_msk = ANT_AB; + } else if (num_of_ant(priv->nvm_data->valid_tx_ant) == 2) { + lq_sta->lq.general_params.dual_stream_ant_msk = + priv->nvm_data->valid_tx_ant; + } + + /* as default allow aggregation for all tids */ + lq_sta->tx_agg_tid_en = IWL_AGG_ALL_TID; + lq_sta->drv = priv; + + /* Set last_txrate_idx to lowest rate */ + lq_sta->last_txrate_idx = rate_lowest_index(sband, sta); + if (sband->band == NL80211_BAND_5GHZ) + lq_sta->last_txrate_idx += IWL_FIRST_OFDM_RATE; + lq_sta->is_agg = 0; +#ifdef CONFIG_MAC80211_DEBUGFS + lq_sta->dbg_fixed_rate = 0; +#endif + + rs_initialize_lq(priv, sta, lq_sta); +} + +static void rs_fill_link_cmd(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, u32 new_rate) +{ + struct iwl_scale_tbl_info tbl_type; + int index = 0; + int rate_idx; + int repeat_rate = 0; + u8 ant_toggle_cnt = 0; + u8 use_ht_possible = 1; + u8 valid_tx_ant = 0; + struct iwl_station_priv *sta_priv = + container_of(lq_sta, struct iwl_station_priv, lq_sta); + struct iwl_link_quality_cmd *lq_cmd = &lq_sta->lq; + + /* Override starting rate (index 0) if needed for debug purposes */ + rs_dbgfs_set_mcs(lq_sta, &new_rate, index); + + /* Interpret new_rate (rate_n_flags) */ + rs_get_tbl_info_from_mcs(new_rate, lq_sta->band, + &tbl_type, &rate_idx); + + if (priv && priv->bt_full_concurrent) { + /* 1x1 only */ + tbl_type.ant_type = + first_antenna(priv->nvm_data->valid_tx_ant); + } + + /* How many times should we repeat the initial rate? */ + if (is_legacy(tbl_type.lq_type)) { + ant_toggle_cnt = 1; + repeat_rate = IWL_NUMBER_TRY; + } else { + repeat_rate = min(IWL_HT_NUMBER_TRY, + LINK_QUAL_AGG_DISABLE_START_DEF - 1); + } + + lq_cmd->general_params.mimo_delimiter = + is_mimo(tbl_type.lq_type) ? 1 : 0; + + /* Fill 1st table entry (index 0) */ + lq_cmd->rs_table[index].rate_n_flags = cpu_to_le32(new_rate); + + if (num_of_ant(tbl_type.ant_type) == 1) { + lq_cmd->general_params.single_stream_ant_msk = + tbl_type.ant_type; + } else if (num_of_ant(tbl_type.ant_type) == 2) { + lq_cmd->general_params.dual_stream_ant_msk = + tbl_type.ant_type; + } /* otherwise we don't modify the existing value */ + + index++; + repeat_rate--; + if (priv) { + if (priv->bt_full_concurrent) + valid_tx_ant = ANT_A; + else + valid_tx_ant = priv->nvm_data->valid_tx_ant; + } + + /* Fill rest of rate table */ + while (index < LINK_QUAL_MAX_RETRY_NUM) { + /* Repeat initial/next rate. + * For legacy IWL_NUMBER_TRY == 1, this loop will not execute. + * For HT IWL_HT_NUMBER_TRY == 3, this executes twice. */ + while (repeat_rate > 0 && (index < LINK_QUAL_MAX_RETRY_NUM)) { + if (is_legacy(tbl_type.lq_type)) { + if (ant_toggle_cnt < NUM_TRY_BEFORE_ANT_TOGGLE) + ant_toggle_cnt++; + else if (priv && + rs_toggle_antenna(valid_tx_ant, + &new_rate, &tbl_type)) + ant_toggle_cnt = 1; + } + + /* Override next rate if needed for debug purposes */ + rs_dbgfs_set_mcs(lq_sta, &new_rate, index); + + /* Fill next table entry */ + lq_cmd->rs_table[index].rate_n_flags = + cpu_to_le32(new_rate); + repeat_rate--; + index++; + } + + rs_get_tbl_info_from_mcs(new_rate, lq_sta->band, &tbl_type, + &rate_idx); + + if (priv && priv->bt_full_concurrent) { + /* 1x1 only */ + tbl_type.ant_type = + first_antenna(priv->nvm_data->valid_tx_ant); + } + + /* Indicate to uCode which entries might be MIMO. + * If initial rate was MIMO, this will finally end up + * as (IWL_HT_NUMBER_TRY * 2), after 2nd pass, otherwise 0. */ + if (is_mimo(tbl_type.lq_type)) + lq_cmd->general_params.mimo_delimiter = index; + + /* Get next rate */ + new_rate = rs_get_lower_rate(lq_sta, &tbl_type, rate_idx, + use_ht_possible); + + /* How many times should we repeat the next rate? */ + if (is_legacy(tbl_type.lq_type)) { + if (ant_toggle_cnt < NUM_TRY_BEFORE_ANT_TOGGLE) + ant_toggle_cnt++; + else if (priv && + rs_toggle_antenna(valid_tx_ant, + &new_rate, &tbl_type)) + ant_toggle_cnt = 1; + + repeat_rate = IWL_NUMBER_TRY; + } else { + repeat_rate = IWL_HT_NUMBER_TRY; + } + + /* Don't allow HT rates after next pass. + * rs_get_lower_rate() will change type to LQ_A or LQ_G. */ + use_ht_possible = 0; + + /* Override next rate if needed for debug purposes */ + rs_dbgfs_set_mcs(lq_sta, &new_rate, index); + + /* Fill next table entry */ + lq_cmd->rs_table[index].rate_n_flags = cpu_to_le32(new_rate); + + index++; + repeat_rate--; + } + + lq_cmd->agg_params.agg_frame_cnt_limit = + sta_priv->max_agg_bufsize ?: LINK_QUAL_AGG_FRAME_LIMIT_DEF; + lq_cmd->agg_params.agg_dis_start_th = LINK_QUAL_AGG_DISABLE_START_DEF; + + lq_cmd->agg_params.agg_time_limit = + cpu_to_le16(LINK_QUAL_AGG_TIME_LIMIT_DEF); + /* + * overwrite if needed, pass aggregation time limit + * to uCode in uSec + */ + if (priv && priv->lib->bt_params && + priv->lib->bt_params->agg_time_limit && + priv->bt_traffic_load >= IWL_BT_COEX_TRAFFIC_LOAD_HIGH) + lq_cmd->agg_params.agg_time_limit = + cpu_to_le16(priv->lib->bt_params->agg_time_limit); +} + +static void *rs_alloc(struct ieee80211_hw *hw) +{ + return hw->priv; +} +/* rate scale requires free function to be implemented */ +static void rs_free(void *priv_rate) +{ + return; +} + +static void rs_free_sta(void *priv_r, struct ieee80211_sta *sta, + void *priv_sta) +{ + struct iwl_op_mode *op_mode __maybe_unused = priv_r; + struct iwl_priv *priv __maybe_unused = IWL_OP_MODE_GET_DVM(op_mode); + + IWL_DEBUG_RATE(priv, "enter\n"); + IWL_DEBUG_RATE(priv, "leave\n"); +} + +#ifdef CONFIG_MAC80211_DEBUGFS +static void rs_dbgfs_set_mcs(struct iwl_lq_sta *lq_sta, + u32 *rate_n_flags, int index) +{ + struct iwl_priv *priv; + u8 valid_tx_ant; + u8 ant_sel_tx; + + priv = lq_sta->drv; + valid_tx_ant = priv->nvm_data->valid_tx_ant; + if (lq_sta->dbg_fixed_rate) { + ant_sel_tx = + ((lq_sta->dbg_fixed_rate & RATE_MCS_ANT_ABC_MSK) + >> RATE_MCS_ANT_POS); + if ((valid_tx_ant & ant_sel_tx) == ant_sel_tx) { + *rate_n_flags = lq_sta->dbg_fixed_rate; + IWL_DEBUG_RATE(priv, "Fixed rate ON\n"); + } else { + lq_sta->dbg_fixed_rate = 0; + IWL_ERR(priv, + "Invalid antenna selection 0x%X, Valid is 0x%X\n", + ant_sel_tx, valid_tx_ant); + IWL_DEBUG_RATE(priv, "Fixed rate OFF\n"); + } + } else { + IWL_DEBUG_RATE(priv, "Fixed rate OFF\n"); + } +} + +static ssize_t rs_sta_dbgfs_scale_table_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct iwl_lq_sta *lq_sta = file->private_data; + struct iwl_priv *priv; + char buf[64]; + size_t buf_size; + u32 parsed_rate; + + + priv = lq_sta->drv; + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (sscanf(buf, "%x", &parsed_rate) == 1) + lq_sta->dbg_fixed_rate = parsed_rate; + else + lq_sta->dbg_fixed_rate = 0; + + rs_program_fix_rate(priv, lq_sta); + + return count; +} + +static ssize_t rs_sta_dbgfs_scale_table_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + char *buff; + int desc = 0; + int i = 0; + int index = 0; + ssize_t ret; + + struct iwl_lq_sta *lq_sta = file->private_data; + struct iwl_priv *priv; + struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + + priv = lq_sta->drv; + buff = kmalloc(1024, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + desc += sprintf(buff+desc, "sta_id %d\n", lq_sta->lq.sta_id); + desc += sprintf(buff+desc, "failed=%d success=%d rate=0%X\n", + lq_sta->total_failed, lq_sta->total_success, + lq_sta->active_legacy_rate); + desc += sprintf(buff+desc, "fixed rate 0x%X\n", + lq_sta->dbg_fixed_rate); + desc += sprintf(buff+desc, "valid_tx_ant %s%s%s\n", + (priv->nvm_data->valid_tx_ant & ANT_A) ? "ANT_A," : "", + (priv->nvm_data->valid_tx_ant & ANT_B) ? "ANT_B," : "", + (priv->nvm_data->valid_tx_ant & ANT_C) ? "ANT_C" : ""); + desc += sprintf(buff+desc, "lq type %s\n", + (is_legacy(tbl->lq_type)) ? "legacy" : "HT"); + if (is_Ht(tbl->lq_type)) { + desc += sprintf(buff + desc, " %s", + (is_siso(tbl->lq_type)) ? "SISO" : + ((is_mimo2(tbl->lq_type)) ? "MIMO2" : "MIMO3")); + desc += sprintf(buff + desc, " %s", + (tbl->is_ht40) ? "40MHz" : "20MHz"); + desc += sprintf(buff + desc, " %s %s %s\n", + (tbl->is_SGI) ? "SGI" : "", + (lq_sta->is_green) ? "GF enabled" : "", + (lq_sta->is_agg) ? "AGG on" : ""); + } + desc += sprintf(buff+desc, "last tx rate=0x%X\n", + lq_sta->last_rate_n_flags); + desc += sprintf(buff+desc, "general:" + "flags=0x%X mimo-d=%d s-ant0x%x d-ant=0x%x\n", + lq_sta->lq.general_params.flags, + lq_sta->lq.general_params.mimo_delimiter, + lq_sta->lq.general_params.single_stream_ant_msk, + lq_sta->lq.general_params.dual_stream_ant_msk); + + desc += sprintf(buff+desc, "agg:" + "time_limit=%d dist_start_th=%d frame_cnt_limit=%d\n", + le16_to_cpu(lq_sta->lq.agg_params.agg_time_limit), + lq_sta->lq.agg_params.agg_dis_start_th, + lq_sta->lq.agg_params.agg_frame_cnt_limit); + + desc += sprintf(buff+desc, + "Start idx [0]=0x%x [1]=0x%x [2]=0x%x [3]=0x%x\n", + lq_sta->lq.general_params.start_rate_index[0], + lq_sta->lq.general_params.start_rate_index[1], + lq_sta->lq.general_params.start_rate_index[2], + lq_sta->lq.general_params.start_rate_index[3]); + + for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++) { + index = iwl_hwrate_to_plcp_idx( + le32_to_cpu(lq_sta->lq.rs_table[i].rate_n_flags)); + if (is_legacy(tbl->lq_type)) { + desc += sprintf(buff+desc, " rate[%d] 0x%X %smbps\n", + i, le32_to_cpu(lq_sta->lq.rs_table[i].rate_n_flags), + iwl_rate_mcs[index].mbps); + } else { + desc += sprintf(buff+desc, " rate[%d] 0x%X %smbps (%s)\n", + i, le32_to_cpu(lq_sta->lq.rs_table[i].rate_n_flags), + iwl_rate_mcs[index].mbps, iwl_rate_mcs[index].mcs); + } + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buff, desc); + kfree(buff); + return ret; +} + +static const struct file_operations rs_sta_dbgfs_scale_table_ops = { + .write = rs_sta_dbgfs_scale_table_write, + .read = rs_sta_dbgfs_scale_table_read, + .open = simple_open, + .llseek = default_llseek, +}; +static ssize_t rs_sta_dbgfs_stats_table_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + char *buff; + int desc = 0; + int i, j; + ssize_t ret; + + struct iwl_lq_sta *lq_sta = file->private_data; + + buff = kmalloc(1024, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + for (i = 0; i < LQ_SIZE; i++) { + desc += sprintf(buff+desc, + "%s type=%d SGI=%d HT40=%d DUP=%d GF=%d\n" + "rate=0x%X\n", + lq_sta->active_tbl == i ? "*" : "x", + lq_sta->lq_info[i].lq_type, + lq_sta->lq_info[i].is_SGI, + lq_sta->lq_info[i].is_ht40, + lq_sta->lq_info[i].is_dup, + lq_sta->is_green, + lq_sta->lq_info[i].current_rate); + for (j = 0; j < IWL_RATE_COUNT; j++) { + desc += sprintf(buff+desc, + "counter=%d success=%d %%=%d\n", + lq_sta->lq_info[i].win[j].counter, + lq_sta->lq_info[i].win[j].success_counter, + lq_sta->lq_info[i].win[j].success_ratio); + } + } + ret = simple_read_from_buffer(user_buf, count, ppos, buff, desc); + kfree(buff); + return ret; +} + +static const struct file_operations rs_sta_dbgfs_stats_table_ops = { + .read = rs_sta_dbgfs_stats_table_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t rs_sta_dbgfs_rate_scale_data_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + struct iwl_lq_sta *lq_sta = file->private_data; + struct iwl_scale_tbl_info *tbl = &lq_sta->lq_info[lq_sta->active_tbl]; + char buff[120]; + int desc = 0; + + if (is_Ht(tbl->lq_type)) + desc += sprintf(buff+desc, + "Bit Rate= %d Mb/s\n", + tbl->expected_tpt[lq_sta->last_txrate_idx]); + else + desc += sprintf(buff+desc, + "Bit Rate= %d Mb/s\n", + iwl_rates[lq_sta->last_txrate_idx].ieee >> 1); + + return simple_read_from_buffer(user_buf, count, ppos, buff, desc); +} + +static const struct file_operations rs_sta_dbgfs_rate_scale_data_ops = { + .read = rs_sta_dbgfs_rate_scale_data_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static void rs_add_debugfs(void *priv, void *priv_sta, + struct dentry *dir) +{ + struct iwl_lq_sta *lq_sta = priv_sta; + + debugfs_create_file("rate_scale_table", 0600, dir, lq_sta, + &rs_sta_dbgfs_scale_table_ops); + debugfs_create_file("rate_stats_table", 0400, dir, lq_sta, + &rs_sta_dbgfs_stats_table_ops); + debugfs_create_file("rate_scale_data", 0400, dir, lq_sta, + &rs_sta_dbgfs_rate_scale_data_ops); + debugfs_create_u8("tx_agg_tid_enable", 0600, dir, + &lq_sta->tx_agg_tid_en); + +} +#endif + +/* + * Initialization of rate scaling information is done by driver after + * the station is added. Since mac80211 calls this function before a + * station is added we ignore it. + */ +static void rs_rate_init_stub(void *priv_r, struct ieee80211_supported_band *sband, + struct cfg80211_chan_def *chandef, + struct ieee80211_sta *sta, void *priv_sta) +{ +} + +static const struct rate_control_ops rs_ops = { + .name = RS_NAME, + .tx_status = rs_tx_status, + .get_rate = rs_get_rate, + .rate_init = rs_rate_init_stub, + .alloc = rs_alloc, + .free = rs_free, + .alloc_sta = rs_alloc_sta, + .free_sta = rs_free_sta, +#ifdef CONFIG_MAC80211_DEBUGFS + .add_sta_debugfs = rs_add_debugfs, +#endif +}; + +int iwlagn_rate_control_register(void) +{ + return ieee80211_rate_control_register(&rs_ops); +} + +void iwlagn_rate_control_unregister(void) +{ + ieee80211_rate_control_unregister(&rs_ops); +} + diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/rs.h b/drivers/net/wireless/intel/iwlwifi/dvm/rs.h new file mode 100644 index 000000000..0b47f1993 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/rs.h @@ -0,0 +1,402 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + * + * Copyright(c) 2003 - 2014 Intel Corporation. All rights reserved. + *****************************************************************************/ + +#ifndef __iwl_agn_rs_h__ +#define __iwl_agn_rs_h__ + +#include <net/mac80211.h> + +#include "iwl-config.h" + +#include "commands.h" + +struct iwl_rate_info { + u8 plcp; /* uCode API: IWL_RATE_6M_PLCP, etc. */ + u8 plcp_siso; /* uCode API: IWL_RATE_SISO_6M_PLCP, etc. */ + u8 plcp_mimo2; /* uCode API: IWL_RATE_MIMO2_6M_PLCP, etc. */ + u8 plcp_mimo3; /* uCode API: IWL_RATE_MIMO3_6M_PLCP, etc. */ + u8 ieee; /* MAC header: IWL_RATE_6M_IEEE, etc. */ + u8 prev_ieee; /* previous rate in IEEE speeds */ + u8 next_ieee; /* next rate in IEEE speeds */ + u8 prev_rs; /* previous rate used in rs algo */ + u8 next_rs; /* next rate used in rs algo */ + u8 prev_rs_tgg; /* previous rate used in TGG rs algo */ + u8 next_rs_tgg; /* next rate used in TGG rs algo */ +}; + +/* + * These serve as indexes into + * struct iwl_rate_info iwl_rates[IWL_RATE_COUNT]; + */ +enum { + IWL_RATE_1M_INDEX = 0, + IWL_RATE_2M_INDEX, + IWL_RATE_5M_INDEX, + IWL_RATE_11M_INDEX, + IWL_RATE_6M_INDEX, + IWL_RATE_9M_INDEX, + IWL_RATE_12M_INDEX, + IWL_RATE_18M_INDEX, + IWL_RATE_24M_INDEX, + IWL_RATE_36M_INDEX, + IWL_RATE_48M_INDEX, + IWL_RATE_54M_INDEX, + IWL_RATE_60M_INDEX, + IWL_RATE_COUNT, /*FIXME:RS:change to IWL_RATE_INDEX_COUNT,*/ + IWL_RATE_COUNT_LEGACY = IWL_RATE_COUNT - 1, /* Excluding 60M */ + IWL_RATE_INVM_INDEX = IWL_RATE_COUNT, + IWL_RATE_INVALID = IWL_RATE_COUNT, +}; + +enum { + IWL_RATE_6M_INDEX_TABLE = 0, + IWL_RATE_9M_INDEX_TABLE, + IWL_RATE_12M_INDEX_TABLE, + IWL_RATE_18M_INDEX_TABLE, + IWL_RATE_24M_INDEX_TABLE, + IWL_RATE_36M_INDEX_TABLE, + IWL_RATE_48M_INDEX_TABLE, + IWL_RATE_54M_INDEX_TABLE, + IWL_RATE_1M_INDEX_TABLE, + IWL_RATE_2M_INDEX_TABLE, + IWL_RATE_5M_INDEX_TABLE, + IWL_RATE_11M_INDEX_TABLE, + IWL_RATE_INVM_INDEX_TABLE = IWL_RATE_INVM_INDEX - 1, +}; + +enum { + IWL_FIRST_OFDM_RATE = IWL_RATE_6M_INDEX, + IWL_LAST_OFDM_RATE = IWL_RATE_60M_INDEX, + IWL_FIRST_CCK_RATE = IWL_RATE_1M_INDEX, + IWL_LAST_CCK_RATE = IWL_RATE_11M_INDEX, +}; + +/* #define vs. enum to keep from defaulting to 'large integer' */ +#define IWL_RATE_6M_MASK (1 << IWL_RATE_6M_INDEX) +#define IWL_RATE_9M_MASK (1 << IWL_RATE_9M_INDEX) +#define IWL_RATE_12M_MASK (1 << IWL_RATE_12M_INDEX) +#define IWL_RATE_18M_MASK (1 << IWL_RATE_18M_INDEX) +#define IWL_RATE_24M_MASK (1 << IWL_RATE_24M_INDEX) +#define IWL_RATE_36M_MASK (1 << IWL_RATE_36M_INDEX) +#define IWL_RATE_48M_MASK (1 << IWL_RATE_48M_INDEX) +#define IWL_RATE_54M_MASK (1 << IWL_RATE_54M_INDEX) +#define IWL_RATE_60M_MASK (1 << IWL_RATE_60M_INDEX) +#define IWL_RATE_1M_MASK (1 << IWL_RATE_1M_INDEX) +#define IWL_RATE_2M_MASK (1 << IWL_RATE_2M_INDEX) +#define IWL_RATE_5M_MASK (1 << IWL_RATE_5M_INDEX) +#define IWL_RATE_11M_MASK (1 << IWL_RATE_11M_INDEX) + +/* uCode API values for legacy bit rates, both OFDM and CCK */ +enum { + IWL_RATE_6M_PLCP = 13, + IWL_RATE_9M_PLCP = 15, + IWL_RATE_12M_PLCP = 5, + IWL_RATE_18M_PLCP = 7, + IWL_RATE_24M_PLCP = 9, + IWL_RATE_36M_PLCP = 11, + IWL_RATE_48M_PLCP = 1, + IWL_RATE_54M_PLCP = 3, + IWL_RATE_60M_PLCP = 3,/*FIXME:RS:should be removed*/ + IWL_RATE_1M_PLCP = 10, + IWL_RATE_2M_PLCP = 20, + IWL_RATE_5M_PLCP = 55, + IWL_RATE_11M_PLCP = 110, + /*FIXME:RS:change to IWL_RATE_LEGACY_??M_PLCP */ + /*FIXME:RS:add IWL_RATE_LEGACY_INVM_PLCP = 0,*/ +}; + +/* uCode API values for OFDM high-throughput (HT) bit rates */ +enum { + IWL_RATE_SISO_6M_PLCP = 0, + IWL_RATE_SISO_12M_PLCP = 1, + IWL_RATE_SISO_18M_PLCP = 2, + IWL_RATE_SISO_24M_PLCP = 3, + IWL_RATE_SISO_36M_PLCP = 4, + IWL_RATE_SISO_48M_PLCP = 5, + IWL_RATE_SISO_54M_PLCP = 6, + IWL_RATE_SISO_60M_PLCP = 7, + IWL_RATE_MIMO2_6M_PLCP = 0x8, + IWL_RATE_MIMO2_12M_PLCP = 0x9, + IWL_RATE_MIMO2_18M_PLCP = 0xa, + IWL_RATE_MIMO2_24M_PLCP = 0xb, + IWL_RATE_MIMO2_36M_PLCP = 0xc, + IWL_RATE_MIMO2_48M_PLCP = 0xd, + IWL_RATE_MIMO2_54M_PLCP = 0xe, + IWL_RATE_MIMO2_60M_PLCP = 0xf, + IWL_RATE_MIMO3_6M_PLCP = 0x10, + IWL_RATE_MIMO3_12M_PLCP = 0x11, + IWL_RATE_MIMO3_18M_PLCP = 0x12, + IWL_RATE_MIMO3_24M_PLCP = 0x13, + IWL_RATE_MIMO3_36M_PLCP = 0x14, + IWL_RATE_MIMO3_48M_PLCP = 0x15, + IWL_RATE_MIMO3_54M_PLCP = 0x16, + IWL_RATE_MIMO3_60M_PLCP = 0x17, + IWL_RATE_SISO_INVM_PLCP, + IWL_RATE_MIMO2_INVM_PLCP = IWL_RATE_SISO_INVM_PLCP, + IWL_RATE_MIMO3_INVM_PLCP = IWL_RATE_SISO_INVM_PLCP, +}; + +/* MAC header values for bit rates */ +enum { + IWL_RATE_6M_IEEE = 12, + IWL_RATE_9M_IEEE = 18, + IWL_RATE_12M_IEEE = 24, + IWL_RATE_18M_IEEE = 36, + IWL_RATE_24M_IEEE = 48, + IWL_RATE_36M_IEEE = 72, + IWL_RATE_48M_IEEE = 96, + IWL_RATE_54M_IEEE = 108, + IWL_RATE_60M_IEEE = 120, + IWL_RATE_1M_IEEE = 2, + IWL_RATE_2M_IEEE = 4, + IWL_RATE_5M_IEEE = 11, + IWL_RATE_11M_IEEE = 22, +}; + +#define IWL_RATES_MASK ((1 << IWL_RATE_COUNT) - 1) + +#define IWL_INVALID_VALUE -1 + +#define IWL_MIN_RSSI_VAL -100 +#define IWL_MAX_RSSI_VAL 0 + +/* These values specify how many Tx frame attempts before + * searching for a new modulation mode */ +#define IWL_LEGACY_FAILURE_LIMIT 160 +#define IWL_LEGACY_SUCCESS_LIMIT 480 +#define IWL_LEGACY_TABLE_COUNT 160 + +#define IWL_NONE_LEGACY_FAILURE_LIMIT 400 +#define IWL_NONE_LEGACY_SUCCESS_LIMIT 4500 +#define IWL_NONE_LEGACY_TABLE_COUNT 1500 + +/* Success ratio (ACKed / attempted tx frames) values (perfect is 128 * 100) */ +#define IWL_RS_GOOD_RATIO 12800 /* 100% */ +#define IWL_RATE_SCALE_SWITCH 10880 /* 85% */ +#define IWL_RATE_HIGH_TH 10880 /* 85% */ +#define IWL_RATE_INCREASE_TH 6400 /* 50% */ +#define IWL_RATE_DECREASE_TH 1920 /* 15% */ + +/* possible actions when in legacy mode */ +#define IWL_LEGACY_SWITCH_ANTENNA1 0 +#define IWL_LEGACY_SWITCH_ANTENNA2 1 +#define IWL_LEGACY_SWITCH_SISO 2 +#define IWL_LEGACY_SWITCH_MIMO2_AB 3 +#define IWL_LEGACY_SWITCH_MIMO2_AC 4 +#define IWL_LEGACY_SWITCH_MIMO2_BC 5 +#define IWL_LEGACY_SWITCH_MIMO3_ABC 6 + +/* possible actions when in siso mode */ +#define IWL_SISO_SWITCH_ANTENNA1 0 +#define IWL_SISO_SWITCH_ANTENNA2 1 +#define IWL_SISO_SWITCH_MIMO2_AB 2 +#define IWL_SISO_SWITCH_MIMO2_AC 3 +#define IWL_SISO_SWITCH_MIMO2_BC 4 +#define IWL_SISO_SWITCH_GI 5 +#define IWL_SISO_SWITCH_MIMO3_ABC 6 + + +/* possible actions when in mimo mode */ +#define IWL_MIMO2_SWITCH_ANTENNA1 0 +#define IWL_MIMO2_SWITCH_ANTENNA2 1 +#define IWL_MIMO2_SWITCH_SISO_A 2 +#define IWL_MIMO2_SWITCH_SISO_B 3 +#define IWL_MIMO2_SWITCH_SISO_C 4 +#define IWL_MIMO2_SWITCH_GI 5 +#define IWL_MIMO2_SWITCH_MIMO3_ABC 6 + + +/* possible actions when in mimo3 mode */ +#define IWL_MIMO3_SWITCH_ANTENNA1 0 +#define IWL_MIMO3_SWITCH_ANTENNA2 1 +#define IWL_MIMO3_SWITCH_SISO_A 2 +#define IWL_MIMO3_SWITCH_SISO_B 3 +#define IWL_MIMO3_SWITCH_SISO_C 4 +#define IWL_MIMO3_SWITCH_MIMO2_AB 5 +#define IWL_MIMO3_SWITCH_MIMO2_AC 6 +#define IWL_MIMO3_SWITCH_MIMO2_BC 7 +#define IWL_MIMO3_SWITCH_GI 8 + + +#define IWL_MAX_11N_MIMO3_SEARCH IWL_MIMO3_SWITCH_GI +#define IWL_MAX_SEARCH IWL_MIMO2_SWITCH_MIMO3_ABC + +/*FIXME:RS:add possible actions for MIMO3*/ + +#define IWL_ACTION_LIMIT 3 /* # possible actions */ + +#define LQ_SIZE 2 /* 2 mode tables: "Active" and "Search" */ + +/* load per tid defines for A-MPDU activation */ +#define IWL_AGG_TPT_THREHOLD 0 +#define IWL_AGG_LOAD_THRESHOLD 10 +#define IWL_AGG_ALL_TID 0xff +#define TID_QUEUE_CELL_SPACING 50 /*mS */ +#define TID_QUEUE_MAX_SIZE 20 +#define TID_ROUND_VALUE 5 /* mS */ + +#define TID_MAX_TIME_DIFF ((TID_QUEUE_MAX_SIZE - 1) * TID_QUEUE_CELL_SPACING) +#define TIME_WRAP_AROUND(x, y) (((y) > (x)) ? (y) - (x) : (0-(x)) + (y)) + +extern const struct iwl_rate_info iwl_rates[IWL_RATE_COUNT]; + +enum iwl_table_type { + LQ_NONE, + LQ_G, /* legacy types */ + LQ_A, + LQ_SISO, /* high-throughput types */ + LQ_MIMO2, + LQ_MIMO3, + LQ_MAX, +}; + +#define is_legacy(tbl) (((tbl) == LQ_G) || ((tbl) == LQ_A)) +#define is_siso(tbl) ((tbl) == LQ_SISO) +#define is_mimo2(tbl) ((tbl) == LQ_MIMO2) +#define is_mimo3(tbl) ((tbl) == LQ_MIMO3) +#define is_mimo(tbl) (is_mimo2(tbl) || is_mimo3(tbl)) +#define is_Ht(tbl) (is_siso(tbl) || is_mimo(tbl)) +#define is_a_band(tbl) ((tbl) == LQ_A) +#define is_g_and(tbl) ((tbl) == LQ_G) + +#define IWL_MAX_MCS_DISPLAY_SIZE 12 + +struct iwl_rate_mcs_info { + char mbps[IWL_MAX_MCS_DISPLAY_SIZE]; + char mcs[IWL_MAX_MCS_DISPLAY_SIZE]; +}; + +/** + * struct iwl_rate_scale_data -- tx success history for one rate + */ +struct iwl_rate_scale_data { + u64 data; /* bitmap of successful frames */ + s32 success_counter; /* number of frames successful */ + s32 success_ratio; /* per-cent * 128 */ + s32 counter; /* number of frames attempted */ + s32 average_tpt; /* success ratio * expected throughput */ + unsigned long stamp; +}; + +/** + * struct iwl_scale_tbl_info -- tx params and success history for all rates + * + * There are two of these in struct iwl_lq_sta, + * one for "active", and one for "search". + */ +struct iwl_scale_tbl_info { + enum iwl_table_type lq_type; + u8 ant_type; + u8 is_SGI; /* 1 = short guard interval */ + u8 is_ht40; /* 1 = 40 MHz channel width */ + u8 is_dup; /* 1 = duplicated data streams */ + u8 action; /* change modulation; IWL_[LEGACY/SISO/MIMO]_SWITCH_* */ + u8 max_search; /* maximun number of tables we can search */ + const u16 *expected_tpt; /* throughput metrics; expected_tpt_G, etc. */ + u32 current_rate; /* rate_n_flags, uCode API format */ + struct iwl_rate_scale_data win[IWL_RATE_COUNT]; /* rate histories */ +}; + +struct iwl_traffic_load { + unsigned long time_stamp; /* age of the oldest statistics */ + u32 packet_count[TID_QUEUE_MAX_SIZE]; /* packet count in this time + * slice */ + u32 total; /* total num of packets during the + * last TID_MAX_TIME_DIFF */ + u8 queue_count; /* number of queues that has + * been used since the last cleanup */ + u8 head; /* start of the circular buffer */ +}; + +/** + * struct iwl_lq_sta -- driver's rate scaling private structure + * + * Pointer to this gets passed back and forth between driver and mac80211. + */ +struct iwl_lq_sta { + u8 active_tbl; /* index of active table, range 0-1 */ + u8 enable_counter; /* indicates HT mode */ + u8 stay_in_tbl; /* 1: disallow, 0: allow search for new mode */ + u8 search_better_tbl; /* 1: currently trying alternate mode */ + s32 last_tpt; + + /* The following determine when to search for a new mode */ + u32 table_count_limit; + u32 max_failure_limit; /* # failed frames before new search */ + u32 max_success_limit; /* # successful frames before new search */ + u32 table_count; + u32 total_failed; /* total failed frames, any/all rates */ + u32 total_success; /* total successful frames, any/all rates */ + u64 flush_timer; /* time staying in mode before new search */ + + u8 action_counter; /* # mode-switch actions tried */ + u8 is_green; + u8 is_dup; + enum nl80211_band band; + + /* The following are bitmaps of rates; IWL_RATE_6M_MASK, etc. */ + u32 supp_rates; + u16 active_legacy_rate; + u16 active_siso_rate; + u16 active_mimo2_rate; + u16 active_mimo3_rate; + s8 max_rate_idx; /* Max rate set by user */ + u8 missed_rate_counter; + + struct iwl_link_quality_cmd lq; + struct iwl_scale_tbl_info lq_info[LQ_SIZE]; /* "active", "search" */ + struct iwl_traffic_load load[IWL_MAX_TID_COUNT]; + u8 tx_agg_tid_en; +#ifdef CONFIG_MAC80211_DEBUGFS + u32 dbg_fixed_rate; +#endif + struct iwl_priv *drv; + + /* used to be in sta_info */ + int last_txrate_idx; + /* last tx rate_n_flags */ + u32 last_rate_n_flags; + /* packets destined for this STA are aggregated */ + u8 is_agg; + /* BT traffic this sta was last updated in */ + u8 last_bt_traffic; +}; + +static inline u8 first_antenna(u8 mask) +{ + if (mask & ANT_A) + return ANT_A; + if (mask & ANT_B) + return ANT_B; + return ANT_C; +} + + +/* Initialize station's rate scaling information after adding station */ +void iwl_rs_rate_init(struct iwl_priv *priv, struct ieee80211_sta *sta, + u8 sta_id); + +/** + * iwl_rate_control_register - Register the rate control algorithm callbacks + * + * Since the rate control algorithm is hardware specific, there is no need + * or reason to place it as a stand alone module. The driver can call + * iwl_rate_control_register in order to register the rate control callbacks + * with the mac80211 subsystem. This should be performed prior to calling + * ieee80211_register_hw + * + */ +int iwlagn_rate_control_register(void); + +/** + * iwl_rate_control_unregister - Unregister the rate control callbacks + * + * This should be called after calling ieee80211_unregister_hw, but before + * the driver is unloaded. + */ +void iwlagn_rate_control_unregister(void); + +#endif /* __iwl_agn__rs__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/rx.c b/drivers/net/wireless/intel/iwlwifi/dvm/rx.c new file mode 100644 index 000000000..e9d271736 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/rx.c @@ -0,0 +1,1021 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2003 - 2014 Intel Corporation. All rights reserved. + * Copyright(c) 2015 Intel Deutschland GmbH + * Copyright(c) 2018, 2020-2021 Intel Corporation + * + * Portions of this file are derived from the ipw3945 project, as well + * as portionhelp of the ieee80211 subsystem header files. + *****************************************************************************/ + +#include <linux/etherdevice.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <net/mac80211.h> +#include <asm/unaligned.h> + +#include "iwl-trans.h" +#include "iwl-io.h" +#include "dev.h" +#include "calib.h" +#include "agn.h" + +/****************************************************************************** + * + * Generic RX handler implementations + * + ******************************************************************************/ + +static void iwlagn_rx_reply_error(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_error_resp *err_resp = (void *)pkt->data; + + IWL_ERR(priv, "Error Reply type 0x%08X cmd REPLY_ERROR (0x%02X) " + "seq 0x%04X ser 0x%08X\n", + le32_to_cpu(err_resp->error_type), + err_resp->cmd_id, + le16_to_cpu(err_resp->bad_cmd_seq_num), + le32_to_cpu(err_resp->error_info)); +} + +static void iwlagn_rx_csa(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_csa_notification *csa = (void *)pkt->data; + /* + * MULTI-FIXME + * See iwlagn_mac_channel_switch. + */ + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + struct iwl_rxon_cmd *rxon = (void *)&ctx->active; + + if (!test_bit(STATUS_CHANNEL_SWITCH_PENDING, &priv->status)) + return; + + if (!le32_to_cpu(csa->status) && csa->channel == priv->switch_channel) { + rxon->channel = csa->channel; + ctx->staging.channel = csa->channel; + IWL_DEBUG_11H(priv, "CSA notif: channel %d\n", + le16_to_cpu(csa->channel)); + iwl_chswitch_done(priv, true); + } else { + IWL_ERR(priv, "CSA notif (fail) : channel %d\n", + le16_to_cpu(csa->channel)); + iwl_chswitch_done(priv, false); + } +} + + +static void iwlagn_rx_spectrum_measure_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_spectrum_notification *report = (void *)pkt->data; + + if (!report->state) { + IWL_DEBUG_11H(priv, + "Spectrum Measure Notification: Start\n"); + return; + } + + memcpy(&priv->measure_report, report, sizeof(*report)); + priv->measurement_status |= MEASUREMENT_READY; +} + +static void iwlagn_rx_pm_sleep_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) +{ +#ifdef CONFIG_IWLWIFI_DEBUG + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_sleep_notification *sleep = (void *)pkt->data; + IWL_DEBUG_RX(priv, "sleep mode: %d, src: %d\n", + sleep->pm_sleep_mode, sleep->pm_wakeup_src); +#endif +} + +static void iwlagn_rx_pm_debug_statistics_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + u32 __maybe_unused len = iwl_rx_packet_len(pkt); + IWL_DEBUG_RADIO(priv, "Dumping %d bytes of unhandled " + "notification for PM_DEBUG_STATISTIC_NOTIFIC:\n", len); + iwl_print_hex_dump(priv, IWL_DL_RADIO, pkt->data, len); +} + +static void iwlagn_rx_beacon_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwlagn_beacon_notif *beacon = (void *)pkt->data; +#ifdef CONFIG_IWLWIFI_DEBUG + u16 status = le16_to_cpu(beacon->beacon_notify_hdr.status.status); + u8 rate = iwl_hw_get_rate(beacon->beacon_notify_hdr.rate_n_flags); + + IWL_DEBUG_RX(priv, "beacon status %#x, retries:%d ibssmgr:%d " + "tsf:0x%.8x%.8x rate:%d\n", + status & TX_STATUS_MSK, + beacon->beacon_notify_hdr.failure_frame, + le32_to_cpu(beacon->ibss_mgr_status), + le32_to_cpu(beacon->high_tsf), + le32_to_cpu(beacon->low_tsf), rate); +#endif + + priv->ibss_manager = le32_to_cpu(beacon->ibss_mgr_status); +} + +/* + * iwl_good_plcp_health - checks for plcp error. + * + * When the plcp error is exceeding the thresholds, reset the radio + * to improve the throughput. + */ +static bool iwlagn_good_plcp_health(struct iwl_priv *priv, + struct statistics_rx_phy *cur_ofdm, + struct statistics_rx_ht_phy *cur_ofdm_ht, + unsigned int msecs) +{ + int delta; + int threshold = priv->plcp_delta_threshold; + + if (threshold == IWL_MAX_PLCP_ERR_THRESHOLD_DISABLE) { + IWL_DEBUG_RADIO(priv, "plcp_err check disabled\n"); + return true; + } + + delta = le32_to_cpu(cur_ofdm->plcp_err) - + le32_to_cpu(priv->statistics.rx_ofdm.plcp_err) + + le32_to_cpu(cur_ofdm_ht->plcp_err) - + le32_to_cpu(priv->statistics.rx_ofdm_ht.plcp_err); + + /* Can be negative if firmware reset statistics */ + if (delta <= 0) + return true; + + if ((delta * 100 / msecs) > threshold) { + IWL_DEBUG_RADIO(priv, + "plcp health threshold %u delta %d msecs %u\n", + threshold, delta, msecs); + return false; + } + + return true; +} + +int iwl_force_rf_reset(struct iwl_priv *priv, bool external) +{ + struct iwl_rf_reset *rf_reset; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return -EAGAIN; + + if (!iwl_is_any_associated(priv)) { + IWL_DEBUG_SCAN(priv, "force reset rejected: not associated\n"); + return -ENOLINK; + } + + rf_reset = &priv->rf_reset; + rf_reset->reset_request_count++; + if (!external && rf_reset->last_reset_jiffies && + time_after(rf_reset->last_reset_jiffies + + IWL_DELAY_NEXT_FORCE_RF_RESET, jiffies)) { + IWL_DEBUG_INFO(priv, "RF reset rejected\n"); + rf_reset->reset_reject_count++; + return -EAGAIN; + } + rf_reset->reset_success_count++; + rf_reset->last_reset_jiffies = jiffies; + + /* + * There is no easy and better way to force reset the radio, + * the only known method is switching channel which will force to + * reset and tune the radio. + * Use internal short scan (single channel) operation to should + * achieve this objective. + * Driver should reset the radio when number of consecutive missed + * beacon, or any other uCode error condition detected. + */ + IWL_DEBUG_INFO(priv, "perform radio reset.\n"); + iwl_internal_short_hw_scan(priv); + return 0; +} + + +static void iwlagn_recover_from_statistics(struct iwl_priv *priv, + struct statistics_rx_phy *cur_ofdm, + struct statistics_rx_ht_phy *cur_ofdm_ht, + struct statistics_tx *tx, + unsigned long stamp) +{ + unsigned int msecs; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + msecs = jiffies_to_msecs(stamp - priv->rx_statistics_jiffies); + + /* Only gather statistics and update time stamp when not associated */ + if (!iwl_is_any_associated(priv)) + return; + + /* Do not check/recover when do not have enough statistics data */ + if (msecs < 99) + return; + + if (!iwlagn_good_plcp_health(priv, cur_ofdm, cur_ofdm_ht, msecs)) + iwl_force_rf_reset(priv, false); +} + +/* Calculate noise level, based on measurements during network silence just + * before arriving beacon. This measurement can be done only if we know + * exactly when to expect beacons, therefore only when we're associated. */ +static void iwlagn_rx_calc_noise(struct iwl_priv *priv) +{ + struct statistics_rx_non_phy *rx_info; + int num_active_rx = 0; + int total_silence = 0; + int bcn_silence_a, bcn_silence_b, bcn_silence_c; + int last_rx_noise; + + rx_info = &priv->statistics.rx_non_phy; + + bcn_silence_a = + le32_to_cpu(rx_info->beacon_silence_rssi_a) & IN_BAND_FILTER; + bcn_silence_b = + le32_to_cpu(rx_info->beacon_silence_rssi_b) & IN_BAND_FILTER; + bcn_silence_c = + le32_to_cpu(rx_info->beacon_silence_rssi_c) & IN_BAND_FILTER; + + if (bcn_silence_a) { + total_silence += bcn_silence_a; + num_active_rx++; + } + if (bcn_silence_b) { + total_silence += bcn_silence_b; + num_active_rx++; + } + if (bcn_silence_c) { + total_silence += bcn_silence_c; + num_active_rx++; + } + + /* Average among active antennas */ + if (num_active_rx) + last_rx_noise = (total_silence / num_active_rx) - 107; + else + last_rx_noise = IWL_NOISE_MEAS_NOT_AVAILABLE; + + IWL_DEBUG_CALIB(priv, "inband silence a %u, b %u, c %u, dBm %d\n", + bcn_silence_a, bcn_silence_b, bcn_silence_c, + last_rx_noise); +} + +#ifdef CONFIG_IWLWIFI_DEBUGFS +/* + * based on the assumption of all statistics counter are in DWORD + * FIXME: This function is for debugging, do not deal with + * the case of counters roll-over. + */ +static void accum_stats(__le32 *prev, __le32 *cur, __le32 *delta, + __le32 *max_delta, __le32 *accum, int size) +{ + int i; + + for (i = 0; + i < size / sizeof(__le32); + i++, prev++, cur++, delta++, max_delta++, accum++) { + if (le32_to_cpu(*cur) > le32_to_cpu(*prev)) { + *delta = cpu_to_le32( + le32_to_cpu(*cur) - le32_to_cpu(*prev)); + le32_add_cpu(accum, le32_to_cpu(*delta)); + if (le32_to_cpu(*delta) > le32_to_cpu(*max_delta)) + *max_delta = *delta; + } + } +} + +static void +iwlagn_accumulative_statistics(struct iwl_priv *priv, + struct statistics_general_common *common, + struct statistics_rx_non_phy *rx_non_phy, + struct statistics_rx_phy *rx_ofdm, + struct statistics_rx_ht_phy *rx_ofdm_ht, + struct statistics_rx_phy *rx_cck, + struct statistics_tx *tx, + struct statistics_bt_activity *bt_activity) +{ +#define ACCUM(_name) \ + accum_stats((__le32 *)&priv->statistics._name, \ + (__le32 *)_name, \ + (__le32 *)&priv->delta_stats._name, \ + (__le32 *)&priv->max_delta_stats._name, \ + (__le32 *)&priv->accum_stats._name, \ + sizeof(*_name)) + + ACCUM(common); + ACCUM(rx_non_phy); + ACCUM(rx_ofdm); + ACCUM(rx_ofdm_ht); + ACCUM(rx_cck); + ACCUM(tx); + if (bt_activity) + ACCUM(bt_activity); +#undef ACCUM +} +#else +static inline void +iwlagn_accumulative_statistics(struct iwl_priv *priv, + struct statistics_general_common *common, + struct statistics_rx_non_phy *rx_non_phy, + struct statistics_rx_phy *rx_ofdm, + struct statistics_rx_ht_phy *rx_ofdm_ht, + struct statistics_rx_phy *rx_cck, + struct statistics_tx *tx, + struct statistics_bt_activity *bt_activity) +{ +} +#endif + +static void iwlagn_rx_statistics(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) +{ + unsigned long stamp = jiffies; + const int reg_recalib_period = 60; + int change; + struct iwl_rx_packet *pkt = rxb_addr(rxb); + u32 len = iwl_rx_packet_payload_len(pkt); + __le32 *flag; + struct statistics_general_common *common; + struct statistics_rx_non_phy *rx_non_phy; + struct statistics_rx_phy *rx_ofdm; + struct statistics_rx_ht_phy *rx_ofdm_ht; + struct statistics_rx_phy *rx_cck; + struct statistics_tx *tx; + struct statistics_bt_activity *bt_activity; + + IWL_DEBUG_RX(priv, "Statistics notification received (%d bytes).\n", + len); + + spin_lock(&priv->statistics.lock); + + if (len == sizeof(struct iwl_bt_notif_statistics)) { + struct iwl_bt_notif_statistics *stats; + stats = (void *)&pkt->data; + flag = &stats->flag; + common = &stats->general.common; + rx_non_phy = &stats->rx.general.common; + rx_ofdm = &stats->rx.ofdm; + rx_ofdm_ht = &stats->rx.ofdm_ht; + rx_cck = &stats->rx.cck; + tx = &stats->tx; + bt_activity = &stats->general.activity; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + /* handle this exception directly */ + priv->statistics.num_bt_kills = stats->rx.general.num_bt_kills; + le32_add_cpu(&priv->statistics.accum_num_bt_kills, + le32_to_cpu(stats->rx.general.num_bt_kills)); +#endif + } else if (len == sizeof(struct iwl_notif_statistics)) { + struct iwl_notif_statistics *stats; + stats = (void *)&pkt->data; + flag = &stats->flag; + common = &stats->general.common; + rx_non_phy = &stats->rx.general; + rx_ofdm = &stats->rx.ofdm; + rx_ofdm_ht = &stats->rx.ofdm_ht; + rx_cck = &stats->rx.cck; + tx = &stats->tx; + bt_activity = NULL; + } else { + WARN_ONCE(1, "len %d doesn't match BT (%zu) or normal (%zu)\n", + len, sizeof(struct iwl_bt_notif_statistics), + sizeof(struct iwl_notif_statistics)); + spin_unlock(&priv->statistics.lock); + return; + } + + change = common->temperature != priv->statistics.common.temperature || + (*flag & STATISTICS_REPLY_FLG_HT40_MODE_MSK) != + (priv->statistics.flag & STATISTICS_REPLY_FLG_HT40_MODE_MSK); + + iwlagn_accumulative_statistics(priv, common, rx_non_phy, rx_ofdm, + rx_ofdm_ht, rx_cck, tx, bt_activity); + + iwlagn_recover_from_statistics(priv, rx_ofdm, rx_ofdm_ht, tx, stamp); + + priv->statistics.flag = *flag; + memcpy(&priv->statistics.common, common, sizeof(*common)); + memcpy(&priv->statistics.rx_non_phy, rx_non_phy, sizeof(*rx_non_phy)); + memcpy(&priv->statistics.rx_ofdm, rx_ofdm, sizeof(*rx_ofdm)); + memcpy(&priv->statistics.rx_ofdm_ht, rx_ofdm_ht, sizeof(*rx_ofdm_ht)); + memcpy(&priv->statistics.rx_cck, rx_cck, sizeof(*rx_cck)); + memcpy(&priv->statistics.tx, tx, sizeof(*tx)); +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (bt_activity) + memcpy(&priv->statistics.bt_activity, bt_activity, + sizeof(*bt_activity)); +#endif + + priv->rx_statistics_jiffies = stamp; + + set_bit(STATUS_STATISTICS, &priv->status); + + /* Reschedule the statistics timer to occur in + * reg_recalib_period seconds to ensure we get a + * thermal update even if the uCode doesn't give + * us one */ + mod_timer(&priv->statistics_periodic, jiffies + + msecs_to_jiffies(reg_recalib_period * 1000)); + + if (unlikely(!test_bit(STATUS_SCANNING, &priv->status)) && + (pkt->hdr.cmd == STATISTICS_NOTIFICATION)) { + iwlagn_rx_calc_noise(priv); + queue_work(priv->workqueue, &priv->run_time_calib_work); + } + if (priv->lib->temperature && change) + priv->lib->temperature(priv); + + spin_unlock(&priv->statistics.lock); +} + +static void iwlagn_rx_reply_statistics(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_notif_statistics *stats = (void *)pkt->data; + + if (le32_to_cpu(stats->flag) & UCODE_STATISTICS_CLEAR_MSK) { +#ifdef CONFIG_IWLWIFI_DEBUGFS + memset(&priv->accum_stats, 0, + sizeof(priv->accum_stats)); + memset(&priv->delta_stats, 0, + sizeof(priv->delta_stats)); + memset(&priv->max_delta_stats, 0, + sizeof(priv->max_delta_stats)); +#endif + IWL_DEBUG_RX(priv, "Statistics have been cleared\n"); + } + + iwlagn_rx_statistics(priv, rxb); +} + +/* Handle notification from uCode that card's power state is changing + * due to software, hardware, or critical temperature RFKILL */ +static void iwlagn_rx_card_state_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_card_state_notif *card_state_notif = (void *)pkt->data; + u32 flags = le32_to_cpu(card_state_notif->flags); + unsigned long status = priv->status; + + IWL_DEBUG_RF_KILL(priv, "Card state received: HW:%s SW:%s CT:%s\n", + (flags & HW_CARD_DISABLED) ? "Kill" : "On", + (flags & SW_CARD_DISABLED) ? "Kill" : "On", + (flags & CT_CARD_DISABLED) ? + "Reached" : "Not reached"); + + if (flags & (SW_CARD_DISABLED | HW_CARD_DISABLED | + CT_CARD_DISABLED)) { + + iwl_write32(priv->trans, CSR_UCODE_DRV_GP1_SET, + CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED); + + iwl_write_direct32(priv->trans, HBUS_TARG_MBX_C, + HBUS_TARG_MBX_C_REG_BIT_CMD_BLOCKED); + + if (!(flags & RXON_CARD_DISABLED)) { + iwl_write32(priv->trans, CSR_UCODE_DRV_GP1_CLR, + CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED); + iwl_write_direct32(priv->trans, HBUS_TARG_MBX_C, + HBUS_TARG_MBX_C_REG_BIT_CMD_BLOCKED); + } + if (flags & CT_CARD_DISABLED) + iwl_tt_enter_ct_kill(priv); + } + if (!(flags & CT_CARD_DISABLED)) + iwl_tt_exit_ct_kill(priv); + + if (flags & HW_CARD_DISABLED) + set_bit(STATUS_RF_KILL_HW, &priv->status); + else + clear_bit(STATUS_RF_KILL_HW, &priv->status); + + + if (!(flags & RXON_CARD_DISABLED)) + iwl_scan_cancel(priv); + + if ((test_bit(STATUS_RF_KILL_HW, &status) != + test_bit(STATUS_RF_KILL_HW, &priv->status))) + wiphy_rfkill_set_hw_state(priv->hw->wiphy, + test_bit(STATUS_RF_KILL_HW, &priv->status)); +} + +static void iwlagn_rx_missed_beacon_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) + +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_missed_beacon_notif *missed_beacon = (void *)pkt->data; + + if (le32_to_cpu(missed_beacon->consecutive_missed_beacons) > + priv->missed_beacon_threshold) { + IWL_DEBUG_CALIB(priv, + "missed bcn cnsq %d totl %d rcd %d expctd %d\n", + le32_to_cpu(missed_beacon->consecutive_missed_beacons), + le32_to_cpu(missed_beacon->total_missed_becons), + le32_to_cpu(missed_beacon->num_recvd_beacons), + le32_to_cpu(missed_beacon->num_expected_beacons)); + if (!test_bit(STATUS_SCANNING, &priv->status)) + iwl_init_sensitivity(priv); + } +} + +/* Cache phy data (Rx signal strength, etc) for HT frame (REPLY_RX_PHY_CMD). + * This will be used later in iwl_rx_reply_rx() for REPLY_RX_MPDU_CMD. */ +static void iwlagn_rx_reply_rx_phy(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + + priv->last_phy_res_valid = true; + priv->ampdu_ref++; + memcpy(&priv->last_phy_res, pkt->data, + sizeof(struct iwl_rx_phy_res)); +} + +/* + * returns non-zero if packet should be dropped + */ +static int iwlagn_set_decrypted_flag(struct iwl_priv *priv, + struct ieee80211_hdr *hdr, + u32 decrypt_res, + struct ieee80211_rx_status *stats) +{ + u16 fc = le16_to_cpu(hdr->frame_control); + + /* + * All contexts have the same setting here due to it being + * a module parameter, so OK to check any context. + */ + if (priv->contexts[IWL_RXON_CTX_BSS].active.filter_flags & + RXON_FILTER_DIS_DECRYPT_MSK) + return 0; + + if (!(fc & IEEE80211_FCTL_PROTECTED)) + return 0; + + IWL_DEBUG_RX(priv, "decrypt_res:0x%x\n", decrypt_res); + switch (decrypt_res & RX_RES_STATUS_SEC_TYPE_MSK) { + case RX_RES_STATUS_SEC_TYPE_TKIP: + /* The uCode has got a bad phase 1 Key, pushes the packet. + * Decryption will be done in SW. */ + if ((decrypt_res & RX_RES_STATUS_DECRYPT_TYPE_MSK) == + RX_RES_STATUS_BAD_KEY_TTAK) + break; + fallthrough; + case RX_RES_STATUS_SEC_TYPE_WEP: + if ((decrypt_res & RX_RES_STATUS_DECRYPT_TYPE_MSK) == + RX_RES_STATUS_BAD_ICV_MIC) { + /* bad ICV, the packet is destroyed since the + * decryption is inplace, drop it */ + IWL_DEBUG_RX(priv, "Packet destroyed\n"); + return -1; + } + fallthrough; + case RX_RES_STATUS_SEC_TYPE_CCMP: + if ((decrypt_res & RX_RES_STATUS_DECRYPT_TYPE_MSK) == + RX_RES_STATUS_DECRYPT_OK) { + IWL_DEBUG_RX(priv, "hw decrypt successfully!!!\n"); + stats->flag |= RX_FLAG_DECRYPTED; + } + break; + + default: + break; + } + return 0; +} + +static void iwlagn_pass_packet_to_mac80211(struct iwl_priv *priv, + struct ieee80211_hdr *hdr, + u16 len, + u32 ampdu_status, + struct iwl_rx_cmd_buffer *rxb, + struct ieee80211_rx_status *stats) +{ + struct sk_buff *skb; + __le16 fc = hdr->frame_control; + struct iwl_rxon_context *ctx; + unsigned int hdrlen, fraglen; + + /* We only process data packets if the interface is open */ + if (unlikely(!priv->is_open)) { + IWL_DEBUG_DROP_LIMIT(priv, + "Dropping packet while interface is not open.\n"); + return; + } + + /* In case of HW accelerated crypto and bad decryption, drop */ + if (!iwlwifi_mod_params.swcrypto && + iwlagn_set_decrypted_flag(priv, hdr, ampdu_status, stats)) + return; + + /* Dont use dev_alloc_skb(), we'll have enough headroom once + * ieee80211_hdr pulled. + */ + skb = alloc_skb(128, GFP_ATOMIC); + if (!skb) { + IWL_ERR(priv, "alloc_skb failed\n"); + return; + } + /* If frame is small enough to fit in skb->head, pull it completely. + * If not, only pull ieee80211_hdr so that splice() or TCP coalesce + * are more efficient. + */ + hdrlen = (len <= skb_tailroom(skb)) ? len : sizeof(*hdr); + + skb_put_data(skb, hdr, hdrlen); + fraglen = len - hdrlen; + + if (fraglen) { + int offset = (void *)hdr + hdrlen - + rxb_addr(rxb) + rxb_offset(rxb); + + skb_add_rx_frag(skb, 0, rxb_steal_page(rxb), offset, + fraglen, rxb->truesize); + } + + /* + * Wake any queues that were stopped due to a passive channel tx + * failure. This can happen because the regulatory enforcement in + * the device waits for a beacon before allowing transmission, + * sometimes even after already having transmitted frames for the + * association because the new RXON may reset the information. + */ + if (unlikely(ieee80211_is_beacon(fc) && priv->passive_no_rx)) { + for_each_context(priv, ctx) { + if (!ether_addr_equal(hdr->addr3, + ctx->active.bssid_addr)) + continue; + iwlagn_lift_passive_no_rx(priv); + } + } + + memcpy(IEEE80211_SKB_RXCB(skb), stats, sizeof(*stats)); + + ieee80211_rx_napi(priv->hw, NULL, skb, priv->napi); +} + +static u32 iwlagn_translate_rx_status(struct iwl_priv *priv, u32 decrypt_in) +{ + u32 decrypt_out = 0; + + if ((decrypt_in & RX_RES_STATUS_STATION_FOUND) == + RX_RES_STATUS_STATION_FOUND) + decrypt_out |= (RX_RES_STATUS_STATION_FOUND | + RX_RES_STATUS_NO_STATION_INFO_MISMATCH); + + decrypt_out |= (decrypt_in & RX_RES_STATUS_SEC_TYPE_MSK); + + /* packet was not encrypted */ + if ((decrypt_in & RX_RES_STATUS_SEC_TYPE_MSK) == + RX_RES_STATUS_SEC_TYPE_NONE) + return decrypt_out; + + /* packet was encrypted with unknown alg */ + if ((decrypt_in & RX_RES_STATUS_SEC_TYPE_MSK) == + RX_RES_STATUS_SEC_TYPE_ERR) + return decrypt_out; + + /* decryption was not done in HW */ + if ((decrypt_in & RX_MPDU_RES_STATUS_DEC_DONE_MSK) != + RX_MPDU_RES_STATUS_DEC_DONE_MSK) + return decrypt_out; + + switch (decrypt_in & RX_RES_STATUS_SEC_TYPE_MSK) { + + case RX_RES_STATUS_SEC_TYPE_CCMP: + /* alg is CCM: check MIC only */ + if (!(decrypt_in & RX_MPDU_RES_STATUS_MIC_OK)) + /* Bad MIC */ + decrypt_out |= RX_RES_STATUS_BAD_ICV_MIC; + else + decrypt_out |= RX_RES_STATUS_DECRYPT_OK; + + break; + + case RX_RES_STATUS_SEC_TYPE_TKIP: + if (!(decrypt_in & RX_MPDU_RES_STATUS_TTAK_OK)) { + /* Bad TTAK */ + decrypt_out |= RX_RES_STATUS_BAD_KEY_TTAK; + break; + } + fallthrough; + default: + if (!(decrypt_in & RX_MPDU_RES_STATUS_ICV_OK)) + decrypt_out |= RX_RES_STATUS_BAD_ICV_MIC; + else + decrypt_out |= RX_RES_STATUS_DECRYPT_OK; + break; + } + + IWL_DEBUG_RX(priv, "decrypt_in:0x%x decrypt_out = 0x%x\n", + decrypt_in, decrypt_out); + + return decrypt_out; +} + +/* Calc max signal level (dBm) among 3 possible receivers */ +static int iwlagn_calc_rssi(struct iwl_priv *priv, + struct iwl_rx_phy_res *rx_resp) +{ + /* data from PHY/DSP regarding signal strength, etc., + * contents are always there, not configurable by host + */ + struct iwlagn_non_cfg_phy *ncphy = + (struct iwlagn_non_cfg_phy *)rx_resp->non_cfg_phy_buf; + u32 val, rssi_a, rssi_b, rssi_c, max_rssi; + u8 agc; + + val = le32_to_cpu(ncphy->non_cfg_phy[IWLAGN_RX_RES_AGC_IDX]); + agc = (val & IWLAGN_OFDM_AGC_MSK) >> IWLAGN_OFDM_AGC_BIT_POS; + + /* Find max rssi among 3 possible receivers. + * These values are measured by the digital signal processor (DSP). + * They should stay fairly constant even as the signal strength varies, + * if the radio's automatic gain control (AGC) is working right. + * AGC value (see below) will provide the "interesting" info. + */ + val = le32_to_cpu(ncphy->non_cfg_phy[IWLAGN_RX_RES_RSSI_AB_IDX]); + rssi_a = (val & IWLAGN_OFDM_RSSI_INBAND_A_BITMSK) >> + IWLAGN_OFDM_RSSI_A_BIT_POS; + rssi_b = (val & IWLAGN_OFDM_RSSI_INBAND_B_BITMSK) >> + IWLAGN_OFDM_RSSI_B_BIT_POS; + val = le32_to_cpu(ncphy->non_cfg_phy[IWLAGN_RX_RES_RSSI_C_IDX]); + rssi_c = (val & IWLAGN_OFDM_RSSI_INBAND_C_BITMSK) >> + IWLAGN_OFDM_RSSI_C_BIT_POS; + + max_rssi = max_t(u32, rssi_a, rssi_b); + max_rssi = max_t(u32, max_rssi, rssi_c); + + IWL_DEBUG_STATS(priv, "Rssi In A %d B %d C %d Max %d AGC dB %d\n", + rssi_a, rssi_b, rssi_c, max_rssi, agc); + + /* dBm = max_rssi dB - agc dB - constant. + * Higher AGC (higher radio gain) means lower signal. */ + return max_rssi - agc - IWLAGN_RSSI_OFFSET; +} + +/* Called for REPLY_RX_MPDU_CMD */ +static void iwlagn_rx_reply_rx(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) +{ + struct ieee80211_hdr *header; + struct ieee80211_rx_status rx_status = {}; + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_rx_phy_res *phy_res; + __le32 rx_pkt_status; + struct iwl_rx_mpdu_res_start *amsdu; + u32 len, pkt_len = iwl_rx_packet_len(pkt); + u32 ampdu_status; + u32 rate_n_flags; + + if (!priv->last_phy_res_valid) { + IWL_ERR(priv, "MPDU frame without cached PHY data\n"); + return; + } + + if (unlikely(pkt_len < sizeof(*amsdu))) { + IWL_DEBUG_DROP(priv, "Bad REPLY_RX_MPDU_CMD size\n"); + return; + } + + phy_res = &priv->last_phy_res; + amsdu = (struct iwl_rx_mpdu_res_start *)pkt->data; + header = (struct ieee80211_hdr *)(pkt->data + sizeof(*amsdu)); + len = le16_to_cpu(amsdu->byte_count); + + if (unlikely(len + sizeof(*amsdu) + sizeof(__le32) > pkt_len)) { + IWL_DEBUG_DROP(priv, "FW lied about packet len\n"); + return; + } + + rx_pkt_status = *(__le32 *)(pkt->data + sizeof(*amsdu) + len); + ampdu_status = iwlagn_translate_rx_status(priv, + le32_to_cpu(rx_pkt_status)); + + if ((unlikely(phy_res->cfg_phy_cnt > 20))) { + IWL_DEBUG_DROP(priv, "dsp size out of range [0,20]: %d\n", + phy_res->cfg_phy_cnt); + return; + } + + if (!(rx_pkt_status & RX_RES_STATUS_NO_CRC32_ERROR) || + !(rx_pkt_status & RX_RES_STATUS_NO_RXE_OVERFLOW)) { + IWL_DEBUG_RX(priv, "Bad CRC or FIFO: 0x%08X.\n", + le32_to_cpu(rx_pkt_status)); + return; + } + + /* This will be used in several places later */ + rate_n_flags = le32_to_cpu(phy_res->rate_n_flags); + + /* rx_status carries information about the packet to mac80211 */ + rx_status.mactime = le64_to_cpu(phy_res->timestamp); + rx_status.band = (phy_res->phy_flags & RX_RES_PHY_FLAGS_BAND_24_MSK) ? + NL80211_BAND_2GHZ : NL80211_BAND_5GHZ; + rx_status.freq = + ieee80211_channel_to_frequency(le16_to_cpu(phy_res->channel), + rx_status.band); + rx_status.rate_idx = + iwlagn_hwrate_to_mac80211_idx(rate_n_flags, rx_status.band); + rx_status.flag = 0; + + /* TSF isn't reliable. In order to allow smooth user experience, + * this W/A doesn't propagate it to the mac80211 */ + /*rx_status.flag |= RX_FLAG_MACTIME_START;*/ + + priv->ucode_beacon_time = le32_to_cpu(phy_res->beacon_time_stamp); + + /* Find max signal strength (dBm) among 3 antenna/receiver chains */ + rx_status.signal = iwlagn_calc_rssi(priv, phy_res); + + IWL_DEBUG_STATS_LIMIT(priv, "Rssi %d, TSF %llu\n", + rx_status.signal, (unsigned long long)rx_status.mactime); + + /* + * "antenna number" + * + * It seems that the antenna field in the phy flags value + * is actually a bit field. This is undefined by radiotap, + * it wants an actual antenna number but I always get "7" + * for most legacy frames I receive indicating that the + * same frame was received on all three RX chains. + * + * I think this field should be removed in favor of a + * new 802.11n radiotap field "RX chains" that is defined + * as a bitmask. + */ + rx_status.antenna = + (le16_to_cpu(phy_res->phy_flags) & RX_RES_PHY_FLAGS_ANTENNA_MSK) + >> RX_RES_PHY_FLAGS_ANTENNA_POS; + + /* set the preamble flag if appropriate */ + if (phy_res->phy_flags & RX_RES_PHY_FLAGS_SHORT_PREAMBLE_MSK) + rx_status.enc_flags |= RX_ENC_FLAG_SHORTPRE; + + if (phy_res->phy_flags & RX_RES_PHY_FLAGS_AGG_MSK) { + /* + * We know which subframes of an A-MPDU belong + * together since we get a single PHY response + * from the firmware for all of them + */ + rx_status.flag |= RX_FLAG_AMPDU_DETAILS; + rx_status.ampdu_reference = priv->ampdu_ref; + } + + /* Set up the HT phy flags */ + if (rate_n_flags & RATE_MCS_HT_MSK) + rx_status.encoding = RX_ENC_HT; + if (rate_n_flags & RATE_MCS_HT40_MSK) + rx_status.bw = RATE_INFO_BW_40; + else + rx_status.bw = RATE_INFO_BW_20; + if (rate_n_flags & RATE_MCS_SGI_MSK) + rx_status.enc_flags |= RX_ENC_FLAG_SHORT_GI; + if (rate_n_flags & RATE_MCS_GF_MSK) + rx_status.enc_flags |= RX_ENC_FLAG_HT_GF; + + iwlagn_pass_packet_to_mac80211(priv, header, len, ampdu_status, + rxb, &rx_status); +} + +static void iwlagn_rx_noa_notification(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_wipan_noa_data *new_data, *old_data; + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_wipan_noa_notification *noa_notif = (void *)pkt->data; + + /* no condition -- we're in softirq */ + old_data = rcu_dereference_protected(priv->noa_data, true); + + if (noa_notif->noa_active) { + u32 len = le16_to_cpu(noa_notif->noa_attribute.length); + u32 copylen = len; + + /* EID, len, OUI, subtype */ + len += 1 + 1 + 3 + 1; + /* P2P id, P2P length */ + len += 1 + 2; + copylen += 1 + 2; + + new_data = kmalloc(struct_size(new_data, data, len), GFP_ATOMIC); + if (new_data) { + new_data->length = len; + new_data->data[0] = WLAN_EID_VENDOR_SPECIFIC; + new_data->data[1] = len - 2; /* not counting EID, len */ + new_data->data[2] = (WLAN_OUI_WFA >> 16) & 0xff; + new_data->data[3] = (WLAN_OUI_WFA >> 8) & 0xff; + new_data->data[4] = (WLAN_OUI_WFA >> 0) & 0xff; + new_data->data[5] = WLAN_OUI_TYPE_WFA_P2P; + memcpy(&new_data->data[6], &noa_notif->noa_attribute, + copylen); + } + } else + new_data = NULL; + + rcu_assign_pointer(priv->noa_data, new_data); + + if (old_data) + kfree_rcu(old_data, rcu_head); +} + +/* + * iwl_setup_rx_handlers - Initialize Rx handler callbacks + * + * Setup the RX handlers for each of the reply types sent from the uCode + * to the host. + */ +void iwl_setup_rx_handlers(struct iwl_priv *priv) +{ + void (**handlers)(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb); + + handlers = priv->rx_handlers; + + handlers[REPLY_ERROR] = iwlagn_rx_reply_error; + handlers[CHANNEL_SWITCH_NOTIFICATION] = iwlagn_rx_csa; + handlers[SPECTRUM_MEASURE_NOTIFICATION] = + iwlagn_rx_spectrum_measure_notif; + handlers[PM_SLEEP_NOTIFICATION] = iwlagn_rx_pm_sleep_notif; + handlers[PM_DEBUG_STATISTIC_NOTIFIC] = + iwlagn_rx_pm_debug_statistics_notif; + handlers[BEACON_NOTIFICATION] = iwlagn_rx_beacon_notif; + handlers[REPLY_ADD_STA] = iwl_add_sta_callback; + + handlers[REPLY_WIPAN_NOA_NOTIFICATION] = iwlagn_rx_noa_notification; + + /* + * The same handler is used for both the REPLY to a discrete + * statistics request from the host as well as for the periodic + * statistics notifications (after received beacons) from the uCode. + */ + handlers[REPLY_STATISTICS_CMD] = iwlagn_rx_reply_statistics; + handlers[STATISTICS_NOTIFICATION] = iwlagn_rx_statistics; + + iwl_setup_rx_scan_handlers(priv); + + handlers[CARD_STATE_NOTIFICATION] = iwlagn_rx_card_state_notif; + handlers[MISSED_BEACONS_NOTIFICATION] = + iwlagn_rx_missed_beacon_notif; + + /* Rx handlers */ + handlers[REPLY_RX_PHY_CMD] = iwlagn_rx_reply_rx_phy; + handlers[REPLY_RX_MPDU_CMD] = iwlagn_rx_reply_rx; + + /* block ack */ + handlers[REPLY_COMPRESSED_BA] = + iwlagn_rx_reply_compressed_ba; + + priv->rx_handlers[REPLY_TX] = iwlagn_rx_reply_tx; + + /* set up notification wait support */ + iwl_notification_wait_init(&priv->notif_wait); + + /* Set up BT Rx handlers */ + if (priv->lib->bt_params) + iwlagn_bt_rx_handler_setup(priv); +} + +void iwl_rx_dispatch(struct iwl_op_mode *op_mode, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + + /* + * Do the notification wait before RX handlers so + * even if the RX handler consumes the RXB we have + * access to it in the notification wait entry. + */ + iwl_notification_wait_notify(&priv->notif_wait, pkt); + + /* Based on type of command response or notification, + * handle those that need handling via function in + * rx_handlers table. See iwl_setup_rx_handlers() */ + if (priv->rx_handlers[pkt->hdr.cmd]) { + priv->rx_handlers_stats[pkt->hdr.cmd]++; + priv->rx_handlers[pkt->hdr.cmd](priv, rxb); + } else { + /* No handling needed */ + IWL_DEBUG_RX(priv, "No handler needed for %s, 0x%02x\n", + iwl_get_cmd_string(priv->trans, + WIDE_ID(0, pkt->hdr.cmd)), + pkt->hdr.cmd); + } +} diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/rxon.c b/drivers/net/wireless/intel/iwlwifi/dvm/rxon.c new file mode 100644 index 000000000..f80cce37e --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/rxon.c @@ -0,0 +1,1547 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2003 - 2014 Intel Corporation. All rights reserved. + * Copyright(c) 2015 Intel Deutschland GmbH + *****************************************************************************/ + +#include <linux/etherdevice.h> +#include "iwl-trans.h" +#include "iwl-modparams.h" +#include "dev.h" +#include "agn.h" +#include "calib.h" + +/* + * initialize rxon structure with default values from eeprom + */ +void iwl_connection_init_rx_config(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + memset(&ctx->staging, 0, sizeof(ctx->staging)); + + if (!ctx->vif) { + ctx->staging.dev_type = ctx->unused_devtype; + } else + switch (ctx->vif->type) { + case NL80211_IFTYPE_AP: + ctx->staging.dev_type = ctx->ap_devtype; + break; + + case NL80211_IFTYPE_STATION: + ctx->staging.dev_type = ctx->station_devtype; + ctx->staging.filter_flags = RXON_FILTER_ACCEPT_GRP_MSK; + break; + + case NL80211_IFTYPE_ADHOC: + ctx->staging.dev_type = ctx->ibss_devtype; + ctx->staging.flags = RXON_FLG_SHORT_PREAMBLE_MSK; + ctx->staging.filter_flags = RXON_FILTER_BCON_AWARE_MSK | + RXON_FILTER_ACCEPT_GRP_MSK; + break; + + case NL80211_IFTYPE_MONITOR: + ctx->staging.dev_type = RXON_DEV_TYPE_SNIFFER; + break; + + default: + IWL_ERR(priv, "Unsupported interface type %d\n", + ctx->vif->type); + break; + } + +#if 0 + /* TODO: Figure out when short_preamble would be set and cache from + * that */ + if (!hw_to_local(priv->hw)->short_preamble) + ctx->staging.flags &= ~RXON_FLG_SHORT_PREAMBLE_MSK; + else + ctx->staging.flags |= RXON_FLG_SHORT_PREAMBLE_MSK; +#endif + + ctx->staging.channel = + cpu_to_le16(priv->hw->conf.chandef.chan->hw_value); + priv->band = priv->hw->conf.chandef.chan->band; + + iwl_set_flags_for_band(priv, ctx, priv->band, ctx->vif); + + /* clear both MIX and PURE40 mode flag */ + ctx->staging.flags &= ~(RXON_FLG_CHANNEL_MODE_MIXED | + RXON_FLG_CHANNEL_MODE_PURE_40); + if (ctx->vif) + memcpy(ctx->staging.node_addr, ctx->vif->addr, ETH_ALEN); + + ctx->staging.ofdm_ht_single_stream_basic_rates = 0xff; + ctx->staging.ofdm_ht_dual_stream_basic_rates = 0xff; + ctx->staging.ofdm_ht_triple_stream_basic_rates = 0xff; +} + +static int iwlagn_disable_bss(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct iwl_rxon_cmd *send) +{ + __le32 old_filter = send->filter_flags; + int ret; + + send->filter_flags &= ~RXON_FILTER_ASSOC_MSK; + ret = iwl_dvm_send_cmd_pdu(priv, ctx->rxon_cmd, + 0, sizeof(*send), send); + + send->filter_flags = old_filter; + + if (ret) + IWL_DEBUG_QUIET_RFKILL(priv, + "Error clearing ASSOC_MSK on BSS (%d)\n", ret); + + return ret; +} + +static int iwlagn_disable_pan(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct iwl_rxon_cmd *send) +{ + struct iwl_notification_wait disable_wait; + __le32 old_filter = send->filter_flags; + u8 old_dev_type = send->dev_type; + int ret; + static const u16 deactivate_cmd[] = { + REPLY_WIPAN_DEACTIVATION_COMPLETE + }; + + iwl_init_notification_wait(&priv->notif_wait, &disable_wait, + deactivate_cmd, ARRAY_SIZE(deactivate_cmd), + NULL, NULL); + + send->filter_flags &= ~RXON_FILTER_ASSOC_MSK; + send->dev_type = RXON_DEV_TYPE_P2P; + ret = iwl_dvm_send_cmd_pdu(priv, ctx->rxon_cmd, + 0, sizeof(*send), send); + + send->filter_flags = old_filter; + send->dev_type = old_dev_type; + + if (ret) { + IWL_ERR(priv, "Error disabling PAN (%d)\n", ret); + iwl_remove_notification(&priv->notif_wait, &disable_wait); + } else { + ret = iwl_wait_notification(&priv->notif_wait, + &disable_wait, HZ); + if (ret) + IWL_ERR(priv, "Timed out waiting for PAN disable\n"); + } + + return ret; +} + +static int iwlagn_disconn_pan(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct iwl_rxon_cmd *send) +{ + __le32 old_filter = send->filter_flags; + int ret; + + send->filter_flags &= ~RXON_FILTER_ASSOC_MSK; + ret = iwl_dvm_send_cmd_pdu(priv, ctx->rxon_cmd, 0, + sizeof(*send), send); + + send->filter_flags = old_filter; + + return ret; +} + +static void iwlagn_update_qos(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + int ret; + + if (!ctx->is_active) + return; + + ctx->qos_data.def_qos_parm.qos_flags = 0; + + if (ctx->qos_data.qos_active) + ctx->qos_data.def_qos_parm.qos_flags |= + QOS_PARAM_FLG_UPDATE_EDCA_MSK; + + if (ctx->ht.enabled) + ctx->qos_data.def_qos_parm.qos_flags |= QOS_PARAM_FLG_TGN_MSK; + + IWL_DEBUG_INFO(priv, "send QoS cmd with Qos active=%d FLAGS=0x%X\n", + ctx->qos_data.qos_active, + ctx->qos_data.def_qos_parm.qos_flags); + + ret = iwl_dvm_send_cmd_pdu(priv, ctx->qos_cmd, 0, + sizeof(struct iwl_qosparam_cmd), + &ctx->qos_data.def_qos_parm); + if (ret) + IWL_DEBUG_QUIET_RFKILL(priv, "Failed to update QoS\n"); +} + +static int iwlagn_update_beacon(struct iwl_priv *priv, + struct ieee80211_vif *vif) +{ + lockdep_assert_held(&priv->mutex); + + dev_kfree_skb(priv->beacon_skb); + priv->beacon_skb = ieee80211_beacon_get(priv->hw, vif, 0); + if (!priv->beacon_skb) + return -ENOMEM; + return iwlagn_send_beacon_cmd(priv); +} + +static int iwlagn_send_rxon_assoc(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + int ret = 0; + struct iwl_rxon_assoc_cmd rxon_assoc; + const struct iwl_rxon_cmd *rxon1 = &ctx->staging; + const struct iwl_rxon_cmd *rxon2 = &ctx->active; + + if ((rxon1->flags == rxon2->flags) && + (rxon1->filter_flags == rxon2->filter_flags) && + (rxon1->cck_basic_rates == rxon2->cck_basic_rates) && + (rxon1->ofdm_ht_single_stream_basic_rates == + rxon2->ofdm_ht_single_stream_basic_rates) && + (rxon1->ofdm_ht_dual_stream_basic_rates == + rxon2->ofdm_ht_dual_stream_basic_rates) && + (rxon1->ofdm_ht_triple_stream_basic_rates == + rxon2->ofdm_ht_triple_stream_basic_rates) && + (rxon1->acquisition_data == rxon2->acquisition_data) && + (rxon1->rx_chain == rxon2->rx_chain) && + (rxon1->ofdm_basic_rates == rxon2->ofdm_basic_rates)) { + IWL_DEBUG_INFO(priv, "Using current RXON_ASSOC. Not resending.\n"); + return 0; + } + + rxon_assoc.flags = ctx->staging.flags; + rxon_assoc.filter_flags = ctx->staging.filter_flags; + rxon_assoc.ofdm_basic_rates = ctx->staging.ofdm_basic_rates; + rxon_assoc.cck_basic_rates = ctx->staging.cck_basic_rates; + rxon_assoc.reserved1 = 0; + rxon_assoc.reserved2 = 0; + rxon_assoc.reserved3 = 0; + rxon_assoc.ofdm_ht_single_stream_basic_rates = + ctx->staging.ofdm_ht_single_stream_basic_rates; + rxon_assoc.ofdm_ht_dual_stream_basic_rates = + ctx->staging.ofdm_ht_dual_stream_basic_rates; + rxon_assoc.rx_chain_select_flags = ctx->staging.rx_chain; + rxon_assoc.ofdm_ht_triple_stream_basic_rates = + ctx->staging.ofdm_ht_triple_stream_basic_rates; + rxon_assoc.acquisition_data = ctx->staging.acquisition_data; + + ret = iwl_dvm_send_cmd_pdu(priv, ctx->rxon_assoc_cmd, + CMD_ASYNC, sizeof(rxon_assoc), &rxon_assoc); + return ret; +} + +static u16 iwl_adjust_beacon_interval(u16 beacon_val, u16 max_beacon_val) +{ + u16 new_val; + u16 beacon_factor; + + /* + * If mac80211 hasn't given us a beacon interval, program + * the default into the device (not checking this here + * would cause the adjustment below to return the maximum + * value, which may break PAN.) + */ + if (!beacon_val) + return DEFAULT_BEACON_INTERVAL; + + /* + * If the beacon interval we obtained from the peer + * is too large, we'll have to wake up more often + * (and in IBSS case, we'll beacon too much) + * + * For example, if max_beacon_val is 4096, and the + * requested beacon interval is 7000, we'll have to + * use 3500 to be able to wake up on the beacons. + * + * This could badly influence beacon detection stats. + */ + + beacon_factor = (beacon_val + max_beacon_val) / max_beacon_val; + new_val = beacon_val / beacon_factor; + + if (!new_val) + new_val = max_beacon_val; + + return new_val; +} + +static int iwl_send_rxon_timing(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + u64 tsf; + s32 interval_tm, rem; + struct ieee80211_conf *conf = NULL; + u16 beacon_int; + struct ieee80211_vif *vif = ctx->vif; + + conf = &priv->hw->conf; + + lockdep_assert_held(&priv->mutex); + + memset(&ctx->timing, 0, sizeof(struct iwl_rxon_time_cmd)); + + ctx->timing.timestamp = cpu_to_le64(priv->timestamp); + ctx->timing.listen_interval = cpu_to_le16(conf->listen_interval); + + beacon_int = vif ? vif->bss_conf.beacon_int : 0; + + /* + * TODO: For IBSS we need to get atim_window from mac80211, + * for now just always use 0 + */ + ctx->timing.atim_window = 0; + + if (ctx->ctxid == IWL_RXON_CTX_PAN && + (!ctx->vif || ctx->vif->type != NL80211_IFTYPE_STATION) && + iwl_is_associated(priv, IWL_RXON_CTX_BSS) && + priv->contexts[IWL_RXON_CTX_BSS].vif && + priv->contexts[IWL_RXON_CTX_BSS].vif->bss_conf.beacon_int) { + ctx->timing.beacon_interval = + priv->contexts[IWL_RXON_CTX_BSS].timing.beacon_interval; + beacon_int = le16_to_cpu(ctx->timing.beacon_interval); + } else if (ctx->ctxid == IWL_RXON_CTX_BSS && + iwl_is_associated(priv, IWL_RXON_CTX_PAN) && + priv->contexts[IWL_RXON_CTX_PAN].vif && + priv->contexts[IWL_RXON_CTX_PAN].vif->bss_conf.beacon_int && + (!iwl_is_associated_ctx(ctx) || !ctx->vif || + !ctx->vif->bss_conf.beacon_int)) { + ctx->timing.beacon_interval = + priv->contexts[IWL_RXON_CTX_PAN].timing.beacon_interval; + beacon_int = le16_to_cpu(ctx->timing.beacon_interval); + } else { + beacon_int = iwl_adjust_beacon_interval(beacon_int, + IWL_MAX_UCODE_BEACON_INTERVAL * TIME_UNIT); + ctx->timing.beacon_interval = cpu_to_le16(beacon_int); + } + + ctx->beacon_int = beacon_int; + + tsf = priv->timestamp; /* tsf is modifed by do_div: copy it */ + interval_tm = beacon_int * TIME_UNIT; + rem = do_div(tsf, interval_tm); + ctx->timing.beacon_init_val = cpu_to_le32(interval_tm - rem); + + ctx->timing.dtim_period = vif ? (vif->bss_conf.dtim_period ?: 1) : 1; + + IWL_DEBUG_ASSOC(priv, + "beacon interval %d beacon timer %d beacon tim %d\n", + le16_to_cpu(ctx->timing.beacon_interval), + le32_to_cpu(ctx->timing.beacon_init_val), + le16_to_cpu(ctx->timing.atim_window)); + + return iwl_dvm_send_cmd_pdu(priv, ctx->rxon_timing_cmd, + 0, sizeof(ctx->timing), &ctx->timing); +} + +static int iwlagn_rxon_disconn(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + int ret; + struct iwl_rxon_cmd *active = (void *)&ctx->active; + + if (ctx->ctxid == IWL_RXON_CTX_BSS) { + ret = iwlagn_disable_bss(priv, ctx, &ctx->staging); + } else { + ret = iwlagn_disable_pan(priv, ctx, &ctx->staging); + if (ret) + return ret; + if (ctx->vif) { + ret = iwl_send_rxon_timing(priv, ctx); + if (ret) { + IWL_ERR(priv, "Failed to send timing (%d)!\n", ret); + return ret; + } + ret = iwlagn_disconn_pan(priv, ctx, &ctx->staging); + } + } + if (ret) + return ret; + + /* + * Un-assoc RXON clears the station table and WEP + * keys, so we have to restore those afterwards. + */ + iwl_clear_ucode_stations(priv, ctx); + /* update -- might need P2P now */ + iwl_update_bcast_station(priv, ctx); + iwl_restore_stations(priv, ctx); + ret = iwl_restore_default_wep_keys(priv, ctx); + if (ret) { + IWL_ERR(priv, "Failed to restore WEP keys (%d)\n", ret); + return ret; + } + + memcpy(active, &ctx->staging, sizeof(*active)); + return 0; +} + +static int iwl_set_tx_power(struct iwl_priv *priv, s8 tx_power, bool force) +{ + int ret; + s8 prev_tx_power; + bool defer; + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + + if (priv->calib_disabled & IWL_TX_POWER_CALIB_DISABLED) + return 0; + + lockdep_assert_held(&priv->mutex); + + if (priv->tx_power_user_lmt == tx_power && !force) + return 0; + + if (tx_power < IWLAGN_TX_POWER_TARGET_POWER_MIN) { + IWL_WARN(priv, + "Requested user TXPOWER %d below lower limit %d.\n", + tx_power, + IWLAGN_TX_POWER_TARGET_POWER_MIN); + return -EINVAL; + } + + if (tx_power > DIV_ROUND_UP(priv->nvm_data->max_tx_pwr_half_dbm, 2)) { + IWL_WARN(priv, + "Requested user TXPOWER %d above upper limit %d.\n", + tx_power, priv->nvm_data->max_tx_pwr_half_dbm); + return -EINVAL; + } + + if (!iwl_is_ready_rf(priv)) + return -EIO; + + /* scan complete and commit_rxon use tx_power_next value, + * it always need to be updated for newest request */ + priv->tx_power_next = tx_power; + + /* do not set tx power when scanning or channel changing */ + defer = test_bit(STATUS_SCANNING, &priv->status) || + memcmp(&ctx->active, &ctx->staging, sizeof(ctx->staging)); + if (defer && !force) { + IWL_DEBUG_INFO(priv, "Deferring tx power set\n"); + return 0; + } + + prev_tx_power = priv->tx_power_user_lmt; + priv->tx_power_user_lmt = tx_power; + + ret = iwlagn_send_tx_power(priv); + + /* if fail to set tx_power, restore the orig. tx power */ + if (ret) { + priv->tx_power_user_lmt = prev_tx_power; + priv->tx_power_next = prev_tx_power; + } + return ret; +} + +static int iwlagn_rxon_connect(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + int ret; + struct iwl_rxon_cmd *active = (void *)&ctx->active; + + /* RXON timing must be before associated RXON */ + if (ctx->ctxid == IWL_RXON_CTX_BSS) { + ret = iwl_send_rxon_timing(priv, ctx); + if (ret) { + IWL_ERR(priv, "Failed to send timing (%d)!\n", ret); + return ret; + } + } + /* QoS info may be cleared by previous un-assoc RXON */ + iwlagn_update_qos(priv, ctx); + + /* + * We'll run into this code path when beaconing is + * enabled, but then we also need to send the beacon + * to the device. + */ + if (ctx->vif && (ctx->vif->type == NL80211_IFTYPE_AP)) { + ret = iwlagn_update_beacon(priv, ctx->vif); + if (ret) { + IWL_ERR(priv, + "Error sending required beacon (%d)!\n", + ret); + return ret; + } + } + + priv->start_calib = 0; + /* + * Apply the new configuration. + * + * Associated RXON doesn't clear the station table in uCode, + * so we don't need to restore stations etc. after this. + */ + ret = iwl_dvm_send_cmd_pdu(priv, ctx->rxon_cmd, 0, + sizeof(struct iwl_rxon_cmd), &ctx->staging); + if (ret) { + IWL_ERR(priv, "Error setting new RXON (%d)\n", ret); + return ret; + } + memcpy(active, &ctx->staging, sizeof(*active)); + + /* IBSS beacon needs to be sent after setting assoc */ + if (ctx->vif && (ctx->vif->type == NL80211_IFTYPE_ADHOC)) + if (iwlagn_update_beacon(priv, ctx->vif)) + IWL_ERR(priv, "Error sending IBSS beacon\n"); + iwl_init_sensitivity(priv); + + /* + * If we issue a new RXON command which required a tune then + * we must send a new TXPOWER command or we won't be able to + * Tx any frames. + * + * It's expected we set power here if channel is changing. + */ + ret = iwl_set_tx_power(priv, priv->tx_power_next, true); + if (ret) { + IWL_ERR(priv, "Error sending TX power (%d)\n", ret); + return ret; + } + + return 0; +} + +int iwlagn_set_pan_params(struct iwl_priv *priv) +{ + struct iwl_wipan_params_cmd cmd; + struct iwl_rxon_context *ctx_bss, *ctx_pan; + int slot0 = 300, slot1 = 0; + int ret; + + if (priv->valid_contexts == BIT(IWL_RXON_CTX_BSS)) + return 0; + + BUILD_BUG_ON(NUM_IWL_RXON_CTX != 2); + + lockdep_assert_held(&priv->mutex); + + ctx_bss = &priv->contexts[IWL_RXON_CTX_BSS]; + ctx_pan = &priv->contexts[IWL_RXON_CTX_PAN]; + + /* + * If the PAN context is inactive, then we don't need + * to update the PAN parameters, the last thing we'll + * have done before it goes inactive is making the PAN + * parameters be WLAN-only. + */ + if (!ctx_pan->is_active) + return 0; + + memset(&cmd, 0, sizeof(cmd)); + + /* only 2 slots are currently allowed */ + cmd.num_slots = 2; + + cmd.slots[0].type = 0; /* BSS */ + cmd.slots[1].type = 1; /* PAN */ + + if (ctx_bss->vif && ctx_pan->vif) { + int bcnint = ctx_pan->beacon_int; + int dtim = ctx_pan->vif->bss_conf.dtim_period ?: 1; + + /* should be set, but seems unused?? */ + cmd.flags |= cpu_to_le16(IWL_WIPAN_PARAMS_FLG_SLOTTED_MODE); + + if (ctx_pan->vif->type == NL80211_IFTYPE_AP && + bcnint && + bcnint != ctx_bss->beacon_int) { + IWL_ERR(priv, + "beacon intervals don't match (%d, %d)\n", + ctx_bss->beacon_int, ctx_pan->beacon_int); + } else + bcnint = max_t(int, bcnint, + ctx_bss->beacon_int); + if (!bcnint) + bcnint = DEFAULT_BEACON_INTERVAL; + slot0 = bcnint / 2; + slot1 = bcnint - slot0; + + if (test_bit(STATUS_SCAN_HW, &priv->status) || + (!ctx_bss->vif->cfg.idle && + !ctx_bss->vif->cfg.assoc)) { + slot0 = dtim * bcnint * 3 - IWL_MIN_SLOT_TIME; + slot1 = IWL_MIN_SLOT_TIME; + } else if (!ctx_pan->vif->cfg.idle && + !ctx_pan->vif->cfg.assoc) { + slot1 = dtim * bcnint * 3 - IWL_MIN_SLOT_TIME; + slot0 = IWL_MIN_SLOT_TIME; + } + } else if (ctx_pan->vif) { + slot0 = 0; + slot1 = max_t(int, 1, ctx_pan->vif->bss_conf.dtim_period) * + ctx_pan->beacon_int; + slot1 = max_t(int, DEFAULT_BEACON_INTERVAL, slot1); + + if (test_bit(STATUS_SCAN_HW, &priv->status)) { + slot0 = slot1 * 3 - IWL_MIN_SLOT_TIME; + slot1 = IWL_MIN_SLOT_TIME; + } + } + + cmd.slots[0].width = cpu_to_le16(slot0); + cmd.slots[1].width = cpu_to_le16(slot1); + + ret = iwl_dvm_send_cmd_pdu(priv, REPLY_WIPAN_PARAMS, 0, + sizeof(cmd), &cmd); + if (ret) + IWL_ERR(priv, "Error setting PAN parameters (%d)\n", ret); + + return ret; +} + +static void _iwl_set_rxon_ht(struct iwl_priv *priv, + struct iwl_ht_config *ht_conf, + struct iwl_rxon_context *ctx) +{ + struct iwl_rxon_cmd *rxon = &ctx->staging; + + if (!ctx->ht.enabled) { + rxon->flags &= ~(RXON_FLG_CHANNEL_MODE_MSK | + RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK | + RXON_FLG_HT40_PROT_MSK | + RXON_FLG_HT_PROT_MSK); + return; + } + + /* FIXME: if the definition of ht.protection changed, the "translation" + * will be needed for rxon->flags + */ + rxon->flags |= cpu_to_le32(ctx->ht.protection << + RXON_FLG_HT_OPERATING_MODE_POS); + + /* Set up channel bandwidth: + * 20 MHz only, 20/40 mixed or pure 40 if ht40 ok */ + /* clear the HT channel mode before set the mode */ + rxon->flags &= ~(RXON_FLG_CHANNEL_MODE_MSK | + RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK); + if (iwl_is_ht40_tx_allowed(priv, ctx, NULL)) { + /* pure ht40 */ + if (ctx->ht.protection == + IEEE80211_HT_OP_MODE_PROTECTION_20MHZ) { + rxon->flags |= RXON_FLG_CHANNEL_MODE_PURE_40; + /* + * Note: control channel is opposite of extension + * channel + */ + switch (ctx->ht.extension_chan_offset) { + case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: + rxon->flags &= + ~RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK; + break; + case IEEE80211_HT_PARAM_CHA_SEC_BELOW: + rxon->flags |= + RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK; + break; + } + } else { + /* + * Note: control channel is opposite of extension + * channel + */ + switch (ctx->ht.extension_chan_offset) { + case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: + rxon->flags &= + ~(RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK); + rxon->flags |= RXON_FLG_CHANNEL_MODE_MIXED; + break; + case IEEE80211_HT_PARAM_CHA_SEC_BELOW: + rxon->flags |= RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK; + rxon->flags |= RXON_FLG_CHANNEL_MODE_MIXED; + break; + case IEEE80211_HT_PARAM_CHA_SEC_NONE: + default: + /* + * channel location only valid if in Mixed + * mode + */ + IWL_ERR(priv, + "invalid extension channel offset\n"); + break; + } + } + } else { + rxon->flags |= RXON_FLG_CHANNEL_MODE_LEGACY; + } + + iwlagn_set_rxon_chain(priv, ctx); + + IWL_DEBUG_ASSOC(priv, "rxon flags 0x%X operation mode :0x%X " + "extension channel offset 0x%x\n", + le32_to_cpu(rxon->flags), ctx->ht.protection, + ctx->ht.extension_chan_offset); +} + +void iwl_set_rxon_ht(struct iwl_priv *priv, struct iwl_ht_config *ht_conf) +{ + struct iwl_rxon_context *ctx; + + for_each_context(priv, ctx) + _iwl_set_rxon_ht(priv, ht_conf, ctx); +} + +/* + * iwl_set_rxon_channel - Set the band and channel values in staging RXON + * @ch: requested channel as a pointer to struct ieee80211_channel + + * NOTE: Does not commit to the hardware; it sets appropriate bit fields + * in the staging RXON flag structure based on the ch->band + */ +void iwl_set_rxon_channel(struct iwl_priv *priv, struct ieee80211_channel *ch, + struct iwl_rxon_context *ctx) +{ + enum nl80211_band band = ch->band; + u16 channel = ch->hw_value; + + if ((le16_to_cpu(ctx->staging.channel) == channel) && + (priv->band == band)) + return; + + ctx->staging.channel = cpu_to_le16(channel); + if (band == NL80211_BAND_5GHZ) + ctx->staging.flags &= ~RXON_FLG_BAND_24G_MSK; + else + ctx->staging.flags |= RXON_FLG_BAND_24G_MSK; + + priv->band = band; + + IWL_DEBUG_INFO(priv, "Staging channel set to %d [%d]\n", channel, band); + +} + +void iwl_set_flags_for_band(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + enum nl80211_band band, + struct ieee80211_vif *vif) +{ + if (band == NL80211_BAND_5GHZ) { + ctx->staging.flags &= + ~(RXON_FLG_BAND_24G_MSK | RXON_FLG_AUTO_DETECT_MSK + | RXON_FLG_CCK_MSK); + ctx->staging.flags |= RXON_FLG_SHORT_SLOT_MSK; + } else { + /* Copied from iwl_post_associate() */ + if (vif && vif->bss_conf.use_short_slot) + ctx->staging.flags |= RXON_FLG_SHORT_SLOT_MSK; + else + ctx->staging.flags &= ~RXON_FLG_SHORT_SLOT_MSK; + + ctx->staging.flags |= RXON_FLG_BAND_24G_MSK; + ctx->staging.flags |= RXON_FLG_AUTO_DETECT_MSK; + ctx->staging.flags &= ~RXON_FLG_CCK_MSK; + } +} + +static void iwl_set_rxon_hwcrypto(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, int hw_decrypt) +{ + struct iwl_rxon_cmd *rxon = &ctx->staging; + + if (hw_decrypt) + rxon->filter_flags &= ~RXON_FILTER_DIS_DECRYPT_MSK; + else + rxon->filter_flags |= RXON_FILTER_DIS_DECRYPT_MSK; + +} + +/* validate RXON structure is valid */ +static int iwl_check_rxon_cmd(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + struct iwl_rxon_cmd *rxon = &ctx->staging; + u32 errors = 0; + + if (rxon->flags & RXON_FLG_BAND_24G_MSK) { + if (rxon->flags & RXON_FLG_TGJ_NARROW_BAND_MSK) { + IWL_WARN(priv, "check 2.4G: wrong narrow\n"); + errors |= BIT(0); + } + if (rxon->flags & RXON_FLG_RADAR_DETECT_MSK) { + IWL_WARN(priv, "check 2.4G: wrong radar\n"); + errors |= BIT(1); + } + } else { + if (!(rxon->flags & RXON_FLG_SHORT_SLOT_MSK)) { + IWL_WARN(priv, "check 5.2G: not short slot!\n"); + errors |= BIT(2); + } + if (rxon->flags & RXON_FLG_CCK_MSK) { + IWL_WARN(priv, "check 5.2G: CCK!\n"); + errors |= BIT(3); + } + } + if ((rxon->node_addr[0] | rxon->bssid_addr[0]) & 0x1) { + IWL_WARN(priv, "mac/bssid mcast!\n"); + errors |= BIT(4); + } + + /* make sure basic rates 6Mbps and 1Mbps are supported */ + if ((rxon->ofdm_basic_rates & IWL_RATE_6M_MASK) == 0 && + (rxon->cck_basic_rates & IWL_RATE_1M_MASK) == 0) { + IWL_WARN(priv, "neither 1 nor 6 are basic\n"); + errors |= BIT(5); + } + + if (le16_to_cpu(rxon->assoc_id) > 2007) { + IWL_WARN(priv, "aid > 2007\n"); + errors |= BIT(6); + } + + if ((rxon->flags & (RXON_FLG_CCK_MSK | RXON_FLG_SHORT_SLOT_MSK)) + == (RXON_FLG_CCK_MSK | RXON_FLG_SHORT_SLOT_MSK)) { + IWL_WARN(priv, "CCK and short slot\n"); + errors |= BIT(7); + } + + if ((rxon->flags & (RXON_FLG_CCK_MSK | RXON_FLG_AUTO_DETECT_MSK)) + == (RXON_FLG_CCK_MSK | RXON_FLG_AUTO_DETECT_MSK)) { + IWL_WARN(priv, "CCK and auto detect\n"); + errors |= BIT(8); + } + + if ((rxon->flags & (RXON_FLG_AUTO_DETECT_MSK | + RXON_FLG_TGG_PROTECT_MSK)) == + RXON_FLG_TGG_PROTECT_MSK) { + IWL_WARN(priv, "TGg but no auto-detect\n"); + errors |= BIT(9); + } + + if (rxon->channel == 0) { + IWL_WARN(priv, "zero channel is invalid\n"); + errors |= BIT(10); + } + + WARN(errors, "Invalid RXON (%#x), channel %d", + errors, le16_to_cpu(rxon->channel)); + + return errors ? -EINVAL : 0; +} + +/* + * iwl_full_rxon_required - check if full RXON (vs RXON_ASSOC) cmd is needed + * @priv: staging_rxon is compared to active_rxon + * + * If the RXON structure is changing enough to require a new tune, + * or is clearing the RXON_FILTER_ASSOC_MSK, then return 1 to indicate that + * a new tune (full RXON command, rather than RXON_ASSOC cmd) is required. + */ +static int iwl_full_rxon_required(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + const struct iwl_rxon_cmd *staging = &ctx->staging; + const struct iwl_rxon_cmd *active = &ctx->active; + +#define CHK(cond) \ + if ((cond)) { \ + IWL_DEBUG_INFO(priv, "need full RXON - " #cond "\n"); \ + return 1; \ + } + +#define CHK_NEQ(c1, c2) \ + if ((c1) != (c2)) { \ + IWL_DEBUG_INFO(priv, "need full RXON - " \ + #c1 " != " #c2 " - %d != %d\n", \ + (c1), (c2)); \ + return 1; \ + } + + /* These items are only settable from the full RXON command */ + CHK(!iwl_is_associated_ctx(ctx)); + CHK(!ether_addr_equal(staging->bssid_addr, active->bssid_addr)); + CHK(!ether_addr_equal(staging->node_addr, active->node_addr)); + CHK(!ether_addr_equal(staging->wlap_bssid_addr, + active->wlap_bssid_addr)); + CHK_NEQ(staging->dev_type, active->dev_type); + CHK_NEQ(staging->channel, active->channel); + CHK_NEQ(staging->air_propagation, active->air_propagation); + CHK_NEQ(staging->ofdm_ht_single_stream_basic_rates, + active->ofdm_ht_single_stream_basic_rates); + CHK_NEQ(staging->ofdm_ht_dual_stream_basic_rates, + active->ofdm_ht_dual_stream_basic_rates); + CHK_NEQ(staging->ofdm_ht_triple_stream_basic_rates, + active->ofdm_ht_triple_stream_basic_rates); + CHK_NEQ(staging->assoc_id, active->assoc_id); + + /* flags, filter_flags, ofdm_basic_rates, and cck_basic_rates can + * be updated with the RXON_ASSOC command -- however only some + * flag transitions are allowed using RXON_ASSOC */ + + /* Check if we are not switching bands */ + CHK_NEQ(staging->flags & RXON_FLG_BAND_24G_MSK, + active->flags & RXON_FLG_BAND_24G_MSK); + + /* Check if we are switching association toggle */ + CHK_NEQ(staging->filter_flags & RXON_FILTER_ASSOC_MSK, + active->filter_flags & RXON_FILTER_ASSOC_MSK); + +#undef CHK +#undef CHK_NEQ + + return 0; +} + +#ifdef CONFIG_IWLWIFI_DEBUG +void iwl_print_rx_config_cmd(struct iwl_priv *priv, + enum iwl_rxon_context_id ctxid) +{ + struct iwl_rxon_context *ctx = &priv->contexts[ctxid]; + struct iwl_rxon_cmd *rxon = &ctx->staging; + + IWL_DEBUG_RADIO(priv, "RX CONFIG:\n"); + iwl_print_hex_dump(priv, IWL_DL_RADIO, (u8 *) rxon, sizeof(*rxon)); + IWL_DEBUG_RADIO(priv, "u16 channel: 0x%x\n", + le16_to_cpu(rxon->channel)); + IWL_DEBUG_RADIO(priv, "u32 flags: 0x%08X\n", + le32_to_cpu(rxon->flags)); + IWL_DEBUG_RADIO(priv, "u32 filter_flags: 0x%08x\n", + le32_to_cpu(rxon->filter_flags)); + IWL_DEBUG_RADIO(priv, "u8 dev_type: 0x%x\n", rxon->dev_type); + IWL_DEBUG_RADIO(priv, "u8 ofdm_basic_rates: 0x%02x\n", + rxon->ofdm_basic_rates); + IWL_DEBUG_RADIO(priv, "u8 cck_basic_rates: 0x%02x\n", + rxon->cck_basic_rates); + IWL_DEBUG_RADIO(priv, "u8[6] node_addr: %pM\n", rxon->node_addr); + IWL_DEBUG_RADIO(priv, "u8[6] bssid_addr: %pM\n", rxon->bssid_addr); + IWL_DEBUG_RADIO(priv, "u16 assoc_id: 0x%x\n", + le16_to_cpu(rxon->assoc_id)); +} +#endif + +static void iwl_calc_basic_rates(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + int lowest_present_ofdm = 100; + int lowest_present_cck = 100; + u8 cck = 0; + u8 ofdm = 0; + + if (ctx->vif) { + struct ieee80211_supported_band *sband; + unsigned long basic = ctx->vif->bss_conf.basic_rates; + int i; + + sband = priv->hw->wiphy->bands[priv->hw->conf.chandef.chan->band]; + + for_each_set_bit(i, &basic, BITS_PER_LONG) { + int hw = sband->bitrates[i].hw_value; + if (hw >= IWL_FIRST_OFDM_RATE) { + ofdm |= BIT(hw - IWL_FIRST_OFDM_RATE); + if (lowest_present_ofdm > hw) + lowest_present_ofdm = hw; + } else { + BUILD_BUG_ON(IWL_FIRST_CCK_RATE != 0); + + cck |= BIT(hw); + if (lowest_present_cck > hw) + lowest_present_cck = hw; + } + } + } + + /* + * Now we've got the basic rates as bitmaps in the ofdm and cck + * variables. This isn't sufficient though, as there might not + * be all the right rates in the bitmap. E.g. if the only basic + * rates are 5.5 Mbps and 11 Mbps, we still need to add 1 Mbps + * and 6 Mbps because the 802.11-2007 standard says in 9.6: + * + * [...] a STA responding to a received frame shall transmit + * its Control Response frame [...] at the highest rate in the + * BSSBasicRateSet parameter that is less than or equal to the + * rate of the immediately previous frame in the frame exchange + * sequence ([...]) and that is of the same modulation class + * ([...]) as the received frame. If no rate contained in the + * BSSBasicRateSet parameter meets these conditions, then the + * control frame sent in response to a received frame shall be + * transmitted at the highest mandatory rate of the PHY that is + * less than or equal to the rate of the received frame, and + * that is of the same modulation class as the received frame. + * + * As a consequence, we need to add all mandatory rates that are + * lower than all of the basic rates to these bitmaps. + */ + + if (IWL_RATE_24M_INDEX < lowest_present_ofdm) + ofdm |= IWL_RATE_24M_MASK >> IWL_FIRST_OFDM_RATE; + if (IWL_RATE_12M_INDEX < lowest_present_ofdm) + ofdm |= IWL_RATE_12M_MASK >> IWL_FIRST_OFDM_RATE; + /* 6M already there or needed so always add */ + ofdm |= IWL_RATE_6M_MASK >> IWL_FIRST_OFDM_RATE; + + /* + * CCK is a bit more complex with DSSS vs. HR/DSSS vs. ERP. + * Note, however: + * - if no CCK rates are basic, it must be ERP since there must + * be some basic rates at all, so they're OFDM => ERP PHY + * (or we're in 5 GHz, and the cck bitmap will never be used) + * - if 11M is a basic rate, it must be ERP as well, so add 5.5M + * - if 5.5M is basic, 1M and 2M are mandatory + * - if 2M is basic, 1M is mandatory + * - if 1M is basic, that's the only valid ACK rate. + * As a consequence, it's not as complicated as it sounds, just add + * any lower rates to the ACK rate bitmap. + */ + if (IWL_RATE_11M_INDEX < lowest_present_cck) + cck |= IWL_RATE_11M_MASK >> IWL_FIRST_CCK_RATE; + if (IWL_RATE_5M_INDEX < lowest_present_cck) + cck |= IWL_RATE_5M_MASK >> IWL_FIRST_CCK_RATE; + if (IWL_RATE_2M_INDEX < lowest_present_cck) + cck |= IWL_RATE_2M_MASK >> IWL_FIRST_CCK_RATE; + /* 1M already there or needed so always add */ + cck |= IWL_RATE_1M_MASK >> IWL_FIRST_CCK_RATE; + + IWL_DEBUG_RATE(priv, "Set basic rates cck:0x%.2x ofdm:0x%.2x\n", + cck, ofdm); + + /* "basic_rates" is a misnomer here -- should be called ACK rates */ + ctx->staging.cck_basic_rates = cck; + ctx->staging.ofdm_basic_rates = ofdm; +} + +/* + * iwlagn_commit_rxon - commit staging_rxon to hardware + * + * The RXON command in staging_rxon is committed to the hardware and + * the active_rxon structure is updated with the new data. This + * function correctly transitions out of the RXON_ASSOC_MSK state if + * a HW tune is required based on the RXON structure changes. + * + * The connect/disconnect flow should be as the following: + * + * 1. make sure send RXON command with association bit unset if not connect + * this should include the channel and the band for the candidate + * to be connected to + * 2. Add Station before RXON association with the AP + * 3. RXON_timing has to send before RXON for connection + * 4. full RXON command - associated bit set + * 5. use RXON_ASSOC command to update any flags changes + */ +int iwlagn_commit_rxon(struct iwl_priv *priv, struct iwl_rxon_context *ctx) +{ + /* cast away the const for active_rxon in this function */ + struct iwl_rxon_cmd *active = (void *)&ctx->active; + bool new_assoc = !!(ctx->staging.filter_flags & RXON_FILTER_ASSOC_MSK); + int ret; + + lockdep_assert_held(&priv->mutex); + + if (!iwl_is_alive(priv)) + return -EBUSY; + + /* This function hardcodes a bunch of dual-mode assumptions */ + BUILD_BUG_ON(NUM_IWL_RXON_CTX != 2); + + if (!ctx->is_active) + return 0; + + /* always get timestamp with Rx frame */ + ctx->staging.flags |= RXON_FLG_TSF2HOST_MSK; + + /* recalculate basic rates */ + iwl_calc_basic_rates(priv, ctx); + + /* + * force CTS-to-self frames protection if RTS-CTS is not preferred + * one aggregation protection method + */ + if (!priv->hw_params.use_rts_for_aggregation) + ctx->staging.flags |= RXON_FLG_SELF_CTS_EN; + + if ((ctx->vif && ctx->vif->bss_conf.use_short_slot) || + !(ctx->staging.flags & RXON_FLG_BAND_24G_MSK)) + ctx->staging.flags |= RXON_FLG_SHORT_SLOT_MSK; + else + ctx->staging.flags &= ~RXON_FLG_SHORT_SLOT_MSK; + + iwl_print_rx_config_cmd(priv, ctx->ctxid); + ret = iwl_check_rxon_cmd(priv, ctx); + if (ret) { + IWL_ERR(priv, "Invalid RXON configuration. Not committing.\n"); + return -EINVAL; + } + + /* + * receive commit_rxon request + * abort any previous channel switch if still in process + */ + if (test_bit(STATUS_CHANNEL_SWITCH_PENDING, &priv->status) && + (priv->switch_channel != ctx->staging.channel)) { + IWL_DEBUG_11H(priv, "abort channel switch on %d\n", + le16_to_cpu(priv->switch_channel)); + iwl_chswitch_done(priv, false); + } + + /* + * If we don't need to send a full RXON, we can use + * iwl_rxon_assoc_cmd which is used to reconfigure filter + * and other flags for the current radio configuration. + */ + if (!iwl_full_rxon_required(priv, ctx)) { + ret = iwlagn_send_rxon_assoc(priv, ctx); + if (ret) { + IWL_ERR(priv, "Error setting RXON_ASSOC (%d)\n", ret); + return ret; + } + + memcpy(active, &ctx->staging, sizeof(*active)); + /* + * We do not commit tx power settings while channel changing, + * do it now if after settings changed. + */ + iwl_set_tx_power(priv, priv->tx_power_next, false); + + /* make sure we are in the right PS state */ + iwl_power_update_mode(priv, true); + + return 0; + } + + iwl_set_rxon_hwcrypto(priv, ctx, !iwlwifi_mod_params.swcrypto); + + IWL_DEBUG_INFO(priv, + "Going to commit RXON\n" + " * with%s RXON_FILTER_ASSOC_MSK\n" + " * channel = %d\n" + " * bssid = %pM\n", + (new_assoc ? "" : "out"), + le16_to_cpu(ctx->staging.channel), + ctx->staging.bssid_addr); + + /* + * Always clear associated first, but with the correct config. + * This is required as for example station addition for the + * AP station must be done after the BSSID is set to correctly + * set up filters in the device. + */ + ret = iwlagn_rxon_disconn(priv, ctx); + if (ret) + return ret; + + ret = iwlagn_set_pan_params(priv); + if (ret) + return ret; + + if (new_assoc) + return iwlagn_rxon_connect(priv, ctx); + + return 0; +} + +void iwlagn_config_ht40(struct ieee80211_conf *conf, + struct iwl_rxon_context *ctx) +{ + if (conf_is_ht40_minus(conf)) { + ctx->ht.extension_chan_offset = + IEEE80211_HT_PARAM_CHA_SEC_BELOW; + ctx->ht.is_40mhz = true; + } else if (conf_is_ht40_plus(conf)) { + ctx->ht.extension_chan_offset = + IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + ctx->ht.is_40mhz = true; + } else { + ctx->ht.extension_chan_offset = + IEEE80211_HT_PARAM_CHA_SEC_NONE; + ctx->ht.is_40mhz = false; + } +} + +int iwlagn_mac_config(struct ieee80211_hw *hw, u32 changed) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_rxon_context *ctx; + struct ieee80211_conf *conf = &hw->conf; + struct ieee80211_channel *channel = conf->chandef.chan; + int ret = 0; + + IWL_DEBUG_MAC80211(priv, "enter: changed %#x\n", changed); + + mutex_lock(&priv->mutex); + + if (unlikely(test_bit(STATUS_SCANNING, &priv->status))) { + IWL_DEBUG_MAC80211(priv, "leave - scanning\n"); + goto out; + } + + if (!iwl_is_ready(priv)) { + IWL_DEBUG_MAC80211(priv, "leave - not ready\n"); + goto out; + } + + if (changed & (IEEE80211_CONF_CHANGE_SMPS | + IEEE80211_CONF_CHANGE_CHANNEL)) { + /* mac80211 uses static for non-HT which is what we want */ + priv->current_ht_config.smps = conf->smps_mode; + + /* + * Recalculate chain counts. + * + * If monitor mode is enabled then mac80211 will + * set up the SM PS mode to OFF if an HT channel is + * configured. + */ + for_each_context(priv, ctx) + iwlagn_set_rxon_chain(priv, ctx); + } + + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + for_each_context(priv, ctx) { + /* Configure HT40 channels */ + if (ctx->ht.enabled != conf_is_ht(conf)) + ctx->ht.enabled = conf_is_ht(conf); + + if (ctx->ht.enabled) { + /* if HT40 is used, it should not change + * after associated except channel switch */ + if (!ctx->ht.is_40mhz || + !iwl_is_associated_ctx(ctx)) + iwlagn_config_ht40(conf, ctx); + } else + ctx->ht.is_40mhz = false; + + /* + * Default to no protection. Protection mode will + * later be set from BSS config in iwl_ht_conf + */ + ctx->ht.protection = IEEE80211_HT_OP_MODE_PROTECTION_NONE; + + /* if we are switching from ht to 2.4 clear flags + * from any ht related info since 2.4 does not + * support ht */ + if (le16_to_cpu(ctx->staging.channel) != + channel->hw_value) + ctx->staging.flags = 0; + + iwl_set_rxon_channel(priv, channel, ctx); + iwl_set_rxon_ht(priv, &priv->current_ht_config); + + iwl_set_flags_for_band(priv, ctx, channel->band, + ctx->vif); + } + + iwl_update_bcast_stations(priv); + } + + if (changed & (IEEE80211_CONF_CHANGE_PS | + IEEE80211_CONF_CHANGE_IDLE)) { + ret = iwl_power_update_mode(priv, false); + if (ret) + IWL_DEBUG_MAC80211(priv, "Error setting sleep level\n"); + } + + if (changed & IEEE80211_CONF_CHANGE_POWER) { + IWL_DEBUG_MAC80211(priv, "TX Power old=%d new=%d\n", + priv->tx_power_user_lmt, conf->power_level); + + iwl_set_tx_power(priv, conf->power_level, false); + } + + for_each_context(priv, ctx) { + if (!memcmp(&ctx->staging, &ctx->active, sizeof(ctx->staging))) + continue; + iwlagn_commit_rxon(priv, ctx); + } + out: + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); + + return ret; +} + +static void iwlagn_check_needed_chains(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_bss_conf *bss_conf) +{ + struct ieee80211_vif *vif = ctx->vif; + struct iwl_rxon_context *tmp; + struct ieee80211_sta *sta; + struct iwl_ht_config *ht_conf = &priv->current_ht_config; + struct ieee80211_sta_ht_cap *ht_cap; + bool need_multiple; + + lockdep_assert_held(&priv->mutex); + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + rcu_read_lock(); + sta = ieee80211_find_sta(vif, bss_conf->bssid); + if (!sta) { + /* + * If at all, this can only happen through a race + * when the AP disconnects us while we're still + * setting up the connection, in that case mac80211 + * will soon tell us about that. + */ + need_multiple = false; + rcu_read_unlock(); + break; + } + + ht_cap = &sta->deflink.ht_cap; + + need_multiple = true; + + /* + * If the peer advertises no support for receiving 2 and 3 + * stream MCS rates, it can't be transmitting them either. + */ + if (ht_cap->mcs.rx_mask[1] == 0 && + ht_cap->mcs.rx_mask[2] == 0) { + need_multiple = false; + } else if (!(ht_cap->mcs.tx_params & + IEEE80211_HT_MCS_TX_DEFINED)) { + /* If it can't TX MCS at all ... */ + need_multiple = false; + } else if (ht_cap->mcs.tx_params & + IEEE80211_HT_MCS_TX_RX_DIFF) { + int maxstreams; + + /* + * But if it can receive them, it might still not + * be able to transmit them, which is what we need + * to check here -- so check the number of streams + * it advertises for TX (if different from RX). + */ + + maxstreams = (ht_cap->mcs.tx_params & + IEEE80211_HT_MCS_TX_MAX_STREAMS_MASK); + maxstreams >>= + IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT; + maxstreams += 1; + + if (maxstreams <= 1) + need_multiple = false; + } + + rcu_read_unlock(); + break; + case NL80211_IFTYPE_ADHOC: + /* currently */ + need_multiple = false; + break; + default: + /* only AP really */ + need_multiple = true; + break; + } + + ctx->ht_need_multiple_chains = need_multiple; + + if (!need_multiple) { + /* check all contexts */ + for_each_context(priv, tmp) { + if (!tmp->vif) + continue; + if (tmp->ht_need_multiple_chains) { + need_multiple = true; + break; + } + } + } + + ht_conf->single_chain_sufficient = !need_multiple; +} + +static void iwlagn_chain_noise_reset(struct iwl_priv *priv) +{ + struct iwl_chain_noise_data *data = &priv->chain_noise_data; + int ret; + + if (priv->calib_disabled & IWL_CHAIN_NOISE_CALIB_DISABLED) + return; + + if ((data->state == IWL_CHAIN_NOISE_ALIVE) && + iwl_is_any_associated(priv)) { + struct iwl_calib_chain_noise_reset_cmd cmd; + + /* clear data for chain noise calibration algorithm */ + data->chain_noise_a = 0; + data->chain_noise_b = 0; + data->chain_noise_c = 0; + data->chain_signal_a = 0; + data->chain_signal_b = 0; + data->chain_signal_c = 0; + data->beacon_count = 0; + + memset(&cmd, 0, sizeof(cmd)); + iwl_set_calib_hdr(&cmd.hdr, + priv->phy_calib_chain_noise_reset_cmd); + ret = iwl_dvm_send_cmd_pdu(priv, + REPLY_PHY_CALIBRATION_CMD, + 0, sizeof(cmd), &cmd); + if (ret) + IWL_ERR(priv, + "Could not send REPLY_PHY_CALIBRATION_CMD\n"); + data->state = IWL_CHAIN_NOISE_ACCUMULATE; + IWL_DEBUG_CALIB(priv, "Run chain_noise_calibrate\n"); + } +} + +void iwlagn_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u64 changes) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_rxon_context *ctx = iwl_rxon_ctx_from_vif(vif); + int ret; + bool force = false; + + mutex_lock(&priv->mutex); + + if (changes & BSS_CHANGED_IDLE && vif->cfg.idle) { + /* + * If we go idle, then clearly no "passive-no-rx" + * workaround is needed any more, this is a reset. + */ + iwlagn_lift_passive_no_rx(priv); + } + + if (unlikely(!iwl_is_ready(priv))) { + IWL_DEBUG_MAC80211(priv, "leave - not ready\n"); + mutex_unlock(&priv->mutex); + return; + } + + if (unlikely(!ctx->vif)) { + IWL_DEBUG_MAC80211(priv, "leave - vif is NULL\n"); + mutex_unlock(&priv->mutex); + return; + } + + if (changes & BSS_CHANGED_BEACON_INT) + force = true; + + if (changes & BSS_CHANGED_QOS) { + ctx->qos_data.qos_active = bss_conf->qos; + iwlagn_update_qos(priv, ctx); + } + + ctx->staging.assoc_id = cpu_to_le16(vif->cfg.aid); + if (vif->bss_conf.use_short_preamble) + ctx->staging.flags |= RXON_FLG_SHORT_PREAMBLE_MSK; + else + ctx->staging.flags &= ~RXON_FLG_SHORT_PREAMBLE_MSK; + + if (changes & BSS_CHANGED_ASSOC) { + if (vif->cfg.assoc) { + priv->timestamp = bss_conf->sync_tsf; + ctx->staging.filter_flags |= RXON_FILTER_ASSOC_MSK; + } else { + ctx->staging.filter_flags &= ~RXON_FILTER_ASSOC_MSK; + + if (ctx->ctxid == IWL_RXON_CTX_BSS) + priv->have_rekey_data = false; + } + + iwlagn_bt_coex_rssi_monitor(priv); + } + + if (ctx->ht.enabled) { + ctx->ht.protection = bss_conf->ht_operation_mode & + IEEE80211_HT_OP_MODE_PROTECTION; + ctx->ht.non_gf_sta_present = !!(bss_conf->ht_operation_mode & + IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT); + iwlagn_check_needed_chains(priv, ctx, bss_conf); + iwl_set_rxon_ht(priv, &priv->current_ht_config); + } + + iwlagn_set_rxon_chain(priv, ctx); + + if (bss_conf->use_cts_prot && (priv->band != NL80211_BAND_5GHZ)) + ctx->staging.flags |= RXON_FLG_TGG_PROTECT_MSK; + else + ctx->staging.flags &= ~RXON_FLG_TGG_PROTECT_MSK; + + if (bss_conf->use_cts_prot) + ctx->staging.flags |= RXON_FLG_SELF_CTS_EN; + else + ctx->staging.flags &= ~RXON_FLG_SELF_CTS_EN; + + memcpy(ctx->staging.bssid_addr, bss_conf->bssid, ETH_ALEN); + + if (vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_ADHOC) { + if (vif->bss_conf.enable_beacon) { + ctx->staging.filter_flags |= RXON_FILTER_ASSOC_MSK; + priv->beacon_ctx = ctx; + } else { + ctx->staging.filter_flags &= ~RXON_FILTER_ASSOC_MSK; + priv->beacon_ctx = NULL; + } + } + + /* + * If the ucode decides to do beacon filtering before + * association, it will lose beacons that are needed + * before sending frames out on passive channels. This + * causes association failures on those channels. Enable + * receiving beacons in such cases. + */ + + if (vif->type == NL80211_IFTYPE_STATION) { + if (!vif->cfg.assoc) + ctx->staging.filter_flags |= RXON_FILTER_BCON_AWARE_MSK; + else + ctx->staging.filter_flags &= + ~RXON_FILTER_BCON_AWARE_MSK; + } + + if (force || memcmp(&ctx->staging, &ctx->active, sizeof(ctx->staging))) + iwlagn_commit_rxon(priv, ctx); + + if (changes & BSS_CHANGED_ASSOC && vif->cfg.assoc) { + /* + * The chain noise calibration will enable PM upon + * completion. If calibration has already been run + * then we need to enable power management here. + */ + if (priv->chain_noise_data.state == IWL_CHAIN_NOISE_DONE) + iwl_power_update_mode(priv, false); + + /* Enable RX differential gain and sensitivity calibrations */ + iwlagn_chain_noise_reset(priv); + priv->start_calib = 1; + } + + if (changes & BSS_CHANGED_IBSS) { + ret = iwlagn_manage_ibss_station(priv, vif, + vif->cfg.ibss_joined); + if (ret) + IWL_ERR(priv, "failed to %s IBSS station %pM\n", + vif->cfg.ibss_joined ? "add" : "remove", + bss_conf->bssid); + } + + if (changes & BSS_CHANGED_BEACON && priv->beacon_ctx == ctx) { + if (iwlagn_update_beacon(priv, vif)) + IWL_ERR(priv, "Error updating beacon\n"); + } + + mutex_unlock(&priv->mutex); +} + +void iwlagn_post_scan(struct iwl_priv *priv) +{ + struct iwl_rxon_context *ctx; + + /* + * We do not commit power settings while scan is pending, + * do it now if the settings changed. + */ + iwl_power_set_mode(priv, &priv->power_data.sleep_cmd_next, false); + iwl_set_tx_power(priv, priv->tx_power_next, false); + + /* + * Since setting the RXON may have been deferred while + * performing the scan, fire one off if needed + */ + for_each_context(priv, ctx) + if (memcmp(&ctx->staging, &ctx->active, sizeof(ctx->staging))) + iwlagn_commit_rxon(priv, ctx); + + iwlagn_set_pan_params(priv); +} diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/scan.c b/drivers/net/wireless/intel/iwlwifi/dvm/scan.c new file mode 100644 index 000000000..a7e85c5c8 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/scan.c @@ -0,0 +1,1062 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2008 - 2014 Intel Corporation. All rights reserved. + * Copyright(c) 2018 Intel Corporation + *****************************************************************************/ +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/etherdevice.h> +#include <net/mac80211.h> + +#include "dev.h" +#include "agn.h" + +/* For active scan, listen ACTIVE_DWELL_TIME (msec) on each channel after + * sending probe req. This should be set long enough to hear probe responses + * from more than one AP. */ +#define IWL_ACTIVE_DWELL_TIME_24 (30) /* all times in msec */ +#define IWL_ACTIVE_DWELL_TIME_52 (20) + +#define IWL_ACTIVE_DWELL_FACTOR_24GHZ (3) +#define IWL_ACTIVE_DWELL_FACTOR_52GHZ (2) + +/* For passive scan, listen PASSIVE_DWELL_TIME (msec) on each channel. + * Must be set longer than active dwell time. + * For the most reliable scan, set > AP beacon interval (typically 100msec). */ +#define IWL_PASSIVE_DWELL_TIME_24 (20) /* all times in msec */ +#define IWL_PASSIVE_DWELL_TIME_52 (10) +#define IWL_PASSIVE_DWELL_BASE (100) +#define IWL_CHANNEL_TUNE_TIME 5 +#define MAX_SCAN_CHANNEL 50 + +/* For reset radio, need minimal dwell time only */ +#define IWL_RADIO_RESET_DWELL_TIME 5 + +static int iwl_send_scan_abort(struct iwl_priv *priv) +{ + int ret; + struct iwl_host_cmd cmd = { + .id = REPLY_SCAN_ABORT_CMD, + .flags = CMD_WANT_SKB, + }; + __le32 *status; + + /* Exit instantly with error when device is not ready + * to receive scan abort command or it does not perform + * hardware scan currently */ + if (!test_bit(STATUS_READY, &priv->status) || + !test_bit(STATUS_SCAN_HW, &priv->status) || + test_bit(STATUS_FW_ERROR, &priv->status)) + return -EIO; + + ret = iwl_dvm_send_cmd(priv, &cmd); + if (ret) + return ret; + + status = (void *)cmd.resp_pkt->data; + if (*status != CAN_ABORT_STATUS) { + /* The scan abort will return 1 for success or + * 2 for "failure". A failure condition can be + * due to simply not being in an active scan which + * can occur if we send the scan abort before we + * the microcode has notified us that a scan is + * completed. */ + IWL_DEBUG_SCAN(priv, "SCAN_ABORT ret %d.\n", + le32_to_cpu(*status)); + ret = -EIO; + } + + iwl_free_resp(&cmd); + return ret; +} + +static void iwl_complete_scan(struct iwl_priv *priv, bool aborted) +{ + struct cfg80211_scan_info info = { + .aborted = aborted, + }; + + /* check if scan was requested from mac80211 */ + if (priv->scan_request) { + IWL_DEBUG_SCAN(priv, "Complete scan in mac80211\n"); + ieee80211_scan_completed(priv->hw, &info); + } + + priv->scan_type = IWL_SCAN_NORMAL; + priv->scan_vif = NULL; + priv->scan_request = NULL; +} + +static void iwl_process_scan_complete(struct iwl_priv *priv) +{ + bool aborted; + + lockdep_assert_held(&priv->mutex); + + if (!test_and_clear_bit(STATUS_SCAN_COMPLETE, &priv->status)) + return; + + IWL_DEBUG_SCAN(priv, "Completed scan.\n"); + + cancel_delayed_work(&priv->scan_check); + + aborted = test_and_clear_bit(STATUS_SCAN_ABORTING, &priv->status); + if (aborted) + IWL_DEBUG_SCAN(priv, "Aborted scan completed.\n"); + + if (!test_and_clear_bit(STATUS_SCANNING, &priv->status)) { + IWL_DEBUG_SCAN(priv, "Scan already completed.\n"); + goto out_settings; + } + + if (priv->scan_type != IWL_SCAN_NORMAL && !aborted) { + int err; + + /* Check if mac80211 requested scan during our internal scan */ + if (priv->scan_request == NULL) + goto out_complete; + + /* If so request a new scan */ + err = iwl_scan_initiate(priv, priv->scan_vif, IWL_SCAN_NORMAL, + priv->scan_request->channels[0]->band); + if (err) { + IWL_DEBUG_SCAN(priv, + "failed to initiate pending scan: %d\n", err); + aborted = true; + goto out_complete; + } + + return; + } + +out_complete: + iwl_complete_scan(priv, aborted); + +out_settings: + /* Can we still talk to firmware ? */ + if (!iwl_is_ready_rf(priv)) + return; + + iwlagn_post_scan(priv); +} + +void iwl_force_scan_end(struct iwl_priv *priv) +{ + lockdep_assert_held(&priv->mutex); + + if (!test_bit(STATUS_SCANNING, &priv->status)) { + IWL_DEBUG_SCAN(priv, "Forcing scan end while not scanning\n"); + return; + } + + IWL_DEBUG_SCAN(priv, "Forcing scan end\n"); + clear_bit(STATUS_SCANNING, &priv->status); + clear_bit(STATUS_SCAN_HW, &priv->status); + clear_bit(STATUS_SCAN_ABORTING, &priv->status); + clear_bit(STATUS_SCAN_COMPLETE, &priv->status); + iwl_complete_scan(priv, true); +} + +static void iwl_do_scan_abort(struct iwl_priv *priv) +{ + int ret; + + lockdep_assert_held(&priv->mutex); + + if (!test_bit(STATUS_SCANNING, &priv->status)) { + IWL_DEBUG_SCAN(priv, "Not performing scan to abort\n"); + return; + } + + if (test_and_set_bit(STATUS_SCAN_ABORTING, &priv->status)) { + IWL_DEBUG_SCAN(priv, "Scan abort in progress\n"); + return; + } + + ret = iwl_send_scan_abort(priv); + if (ret) { + IWL_DEBUG_SCAN(priv, "Send scan abort failed %d\n", ret); + iwl_force_scan_end(priv); + } else + IWL_DEBUG_SCAN(priv, "Successfully send scan abort\n"); +} + +/* + * iwl_scan_cancel - Cancel any currently executing HW scan + */ +int iwl_scan_cancel(struct iwl_priv *priv) +{ + IWL_DEBUG_SCAN(priv, "Queuing abort scan\n"); + queue_work(priv->workqueue, &priv->abort_scan); + return 0; +} + +/* + * iwl_scan_cancel_timeout - Cancel any currently executing HW scan + * @ms: amount of time to wait (in milliseconds) for scan to abort + */ +void iwl_scan_cancel_timeout(struct iwl_priv *priv, unsigned long ms) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(ms); + + lockdep_assert_held(&priv->mutex); + + IWL_DEBUG_SCAN(priv, "Scan cancel timeout\n"); + + iwl_do_scan_abort(priv); + + while (time_before_eq(jiffies, timeout)) { + if (!test_bit(STATUS_SCAN_HW, &priv->status)) + goto finished; + msleep(20); + } + + return; + + finished: + /* + * Now STATUS_SCAN_HW is clear. This means that the + * device finished, but the background work is going + * to execute at best as soon as we release the mutex. + * Since we need to be able to issue a new scan right + * after this function returns, run the complete here. + * The STATUS_SCAN_COMPLETE bit will then be cleared + * and prevent the background work from "completing" + * a possible new scan. + */ + iwl_process_scan_complete(priv); +} + +/* Service response to REPLY_SCAN_CMD (0x80) */ +static void iwl_rx_reply_scan(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) +{ +#ifdef CONFIG_IWLWIFI_DEBUG + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_scanreq_notification *notif = (void *)pkt->data; + + IWL_DEBUG_SCAN(priv, "Scan request status = 0x%x\n", notif->status); +#endif +} + +/* Service SCAN_START_NOTIFICATION (0x82) */ +static void iwl_rx_scan_start_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_scanstart_notification *notif = (void *)pkt->data; + + priv->scan_start_tsf = le32_to_cpu(notif->tsf_low); + IWL_DEBUG_SCAN(priv, "Scan start: " + "%d [802.11%s] " + "(TSF: 0x%08X:%08X) - %d (beacon timer %u)\n", + notif->channel, + notif->band ? "bg" : "a", + le32_to_cpu(notif->tsf_high), + le32_to_cpu(notif->tsf_low), + notif->status, notif->beacon_timer); +} + +/* Service SCAN_RESULTS_NOTIFICATION (0x83) */ +static void iwl_rx_scan_results_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) +{ +#ifdef CONFIG_IWLWIFI_DEBUG + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_scanresults_notification *notif = (void *)pkt->data; + + IWL_DEBUG_SCAN(priv, "Scan ch.res: " + "%d [802.11%s] " + "probe status: %u:%u " + "(TSF: 0x%08X:%08X) - %d " + "elapsed=%lu usec\n", + notif->channel, + notif->band ? "bg" : "a", + notif->probe_status, notif->num_probe_not_sent, + le32_to_cpu(notif->tsf_high), + le32_to_cpu(notif->tsf_low), + le32_to_cpu(notif->statistics[0]), + le32_to_cpu(notif->tsf_low) - priv->scan_start_tsf); +#endif +} + +/* Service SCAN_COMPLETE_NOTIFICATION (0x84) */ +static void iwl_rx_scan_complete_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_scancomplete_notification *scan_notif = (void *)pkt->data; + + IWL_DEBUG_SCAN(priv, "Scan complete: %d channels (TSF 0x%08X:%08X) - %d\n", + scan_notif->scanned_channels, + scan_notif->tsf_low, + scan_notif->tsf_high, scan_notif->status); + + IWL_DEBUG_SCAN(priv, "Scan on %sGHz took %dms\n", + (priv->scan_band == NL80211_BAND_2GHZ) ? "2.4" : "5.2", + jiffies_to_msecs(jiffies - priv->scan_start)); + + /* + * When aborting, we run the scan completed background work inline + * and the background work must then do nothing. The SCAN_COMPLETE + * bit helps implement that logic and thus needs to be set before + * queueing the work. Also, since the scan abort waits for SCAN_HW + * to clear, we need to set SCAN_COMPLETE before clearing SCAN_HW + * to avoid a race there. + */ + set_bit(STATUS_SCAN_COMPLETE, &priv->status); + clear_bit(STATUS_SCAN_HW, &priv->status); + queue_work(priv->workqueue, &priv->scan_completed); + + if (priv->iw_mode != NL80211_IFTYPE_ADHOC && + iwl_advanced_bt_coexist(priv) && + priv->bt_status != scan_notif->bt_status) { + if (scan_notif->bt_status) { + /* BT on */ + if (!priv->bt_ch_announce) + priv->bt_traffic_load = + IWL_BT_COEX_TRAFFIC_LOAD_HIGH; + /* + * otherwise, no traffic load information provided + * no changes made + */ + } else { + /* BT off */ + priv->bt_traffic_load = + IWL_BT_COEX_TRAFFIC_LOAD_NONE; + } + priv->bt_status = scan_notif->bt_status; + queue_work(priv->workqueue, + &priv->bt_traffic_change_work); + } +} + +void iwl_setup_rx_scan_handlers(struct iwl_priv *priv) +{ + /* scan handlers */ + priv->rx_handlers[REPLY_SCAN_CMD] = iwl_rx_reply_scan; + priv->rx_handlers[SCAN_START_NOTIFICATION] = iwl_rx_scan_start_notif; + priv->rx_handlers[SCAN_RESULTS_NOTIFICATION] = + iwl_rx_scan_results_notif; + priv->rx_handlers[SCAN_COMPLETE_NOTIFICATION] = + iwl_rx_scan_complete_notif; +} + +static u16 iwl_get_active_dwell_time(struct iwl_priv *priv, + enum nl80211_band band, u8 n_probes) +{ + if (band == NL80211_BAND_5GHZ) + return IWL_ACTIVE_DWELL_TIME_52 + + IWL_ACTIVE_DWELL_FACTOR_52GHZ * (n_probes + 1); + else + return IWL_ACTIVE_DWELL_TIME_24 + + IWL_ACTIVE_DWELL_FACTOR_24GHZ * (n_probes + 1); +} + +static u16 iwl_limit_dwell(struct iwl_priv *priv, u16 dwell_time) +{ + struct iwl_rxon_context *ctx; + int limits[NUM_IWL_RXON_CTX] = {}; + int n_active = 0; + u16 limit; + + BUILD_BUG_ON(NUM_IWL_RXON_CTX != 2); + + /* + * If we're associated, we clamp the dwell time 98% + * of the beacon interval (minus 2 * channel tune time) + * If both contexts are active, we have to restrict to + * 1/2 of the minimum of them, because they might be in + * lock-step with the time inbetween only half of what + * time we'd have in each of them. + */ + for_each_context(priv, ctx) { + switch (ctx->staging.dev_type) { + case RXON_DEV_TYPE_P2P: + /* no timing constraints */ + continue; + case RXON_DEV_TYPE_ESS: + default: + /* timing constraints if associated */ + if (!iwl_is_associated_ctx(ctx)) + continue; + break; + case RXON_DEV_TYPE_CP: + case RXON_DEV_TYPE_2STA: + /* + * These seem to always have timers for TBTT + * active in uCode even when not associated yet. + */ + break; + } + + limits[n_active++] = ctx->beacon_int ?: IWL_PASSIVE_DWELL_BASE; + } + + switch (n_active) { + case 0: + return dwell_time; + case 2: + limit = (limits[1] * 98) / 100 - IWL_CHANNEL_TUNE_TIME * 2; + limit /= 2; + dwell_time = min(limit, dwell_time); + fallthrough; + case 1: + limit = (limits[0] * 98) / 100 - IWL_CHANNEL_TUNE_TIME * 2; + limit /= n_active; + return min(limit, dwell_time); + default: + WARN_ON_ONCE(1); + return dwell_time; + } +} + +static u16 iwl_get_passive_dwell_time(struct iwl_priv *priv, + enum nl80211_band band) +{ + u16 passive = (band == NL80211_BAND_2GHZ) ? + IWL_PASSIVE_DWELL_BASE + IWL_PASSIVE_DWELL_TIME_24 : + IWL_PASSIVE_DWELL_BASE + IWL_PASSIVE_DWELL_TIME_52; + + return iwl_limit_dwell(priv, passive); +} + +/* Return valid, unused, channel for a passive scan to reset the RF */ +static u8 iwl_get_single_channel_number(struct iwl_priv *priv, + enum nl80211_band band) +{ + struct ieee80211_supported_band *sband = priv->hw->wiphy->bands[band]; + struct iwl_rxon_context *ctx; + int i; + + for (i = 0; i < sband->n_channels; i++) { + bool busy = false; + + for_each_context(priv, ctx) { + busy = sband->channels[i].hw_value == + le16_to_cpu(ctx->staging.channel); + if (busy) + break; + } + + if (busy) + continue; + + if (!(sband->channels[i].flags & IEEE80211_CHAN_DISABLED)) + return sband->channels[i].hw_value; + } + + return 0; +} + +static int iwl_get_channel_for_reset_scan(struct iwl_priv *priv, + struct ieee80211_vif *vif, + enum nl80211_band band, + struct iwl_scan_channel *scan_ch) +{ + const struct ieee80211_supported_band *sband; + u16 channel; + + sband = iwl_get_hw_mode(priv, band); + if (!sband) { + IWL_ERR(priv, "invalid band\n"); + return 0; + } + + channel = iwl_get_single_channel_number(priv, band); + if (channel) { + scan_ch->channel = cpu_to_le16(channel); + scan_ch->type = SCAN_CHANNEL_TYPE_PASSIVE; + scan_ch->active_dwell = + cpu_to_le16(IWL_RADIO_RESET_DWELL_TIME); + scan_ch->passive_dwell = + cpu_to_le16(IWL_RADIO_RESET_DWELL_TIME); + /* Set txpower levels to defaults */ + scan_ch->dsp_atten = 110; + if (band == NL80211_BAND_5GHZ) + scan_ch->tx_gain = ((1 << 5) | (3 << 3)) | 3; + else + scan_ch->tx_gain = ((1 << 5) | (5 << 3)); + return 1; + } + + IWL_ERR(priv, "no valid channel found\n"); + return 0; +} + +static int iwl_get_channels_for_scan(struct iwl_priv *priv, + struct ieee80211_vif *vif, + enum nl80211_band band, + u8 is_active, u8 n_probes, + struct iwl_scan_channel *scan_ch) +{ + struct ieee80211_channel *chan; + const struct ieee80211_supported_band *sband; + u16 passive_dwell = 0; + u16 active_dwell = 0; + int added, i; + u16 channel; + + sband = iwl_get_hw_mode(priv, band); + if (!sband) + return 0; + + active_dwell = iwl_get_active_dwell_time(priv, band, n_probes); + passive_dwell = iwl_get_passive_dwell_time(priv, band); + + if (passive_dwell <= active_dwell) + passive_dwell = active_dwell + 1; + + for (i = 0, added = 0; i < priv->scan_request->n_channels; i++) { + chan = priv->scan_request->channels[i]; + + if (chan->band != band) + continue; + + channel = chan->hw_value; + scan_ch->channel = cpu_to_le16(channel); + + if (!is_active || (chan->flags & IEEE80211_CHAN_NO_IR)) + scan_ch->type = SCAN_CHANNEL_TYPE_PASSIVE; + else + scan_ch->type = SCAN_CHANNEL_TYPE_ACTIVE; + + if (n_probes) + scan_ch->type |= IWL_SCAN_PROBE_MASK(n_probes); + + scan_ch->active_dwell = cpu_to_le16(active_dwell); + scan_ch->passive_dwell = cpu_to_le16(passive_dwell); + + /* Set txpower levels to defaults */ + scan_ch->dsp_atten = 110; + + /* NOTE: if we were doing 6Mb OFDM for scans we'd use + * power level: + * scan_ch->tx_gain = ((1 << 5) | (2 << 3)) | 3; + */ + if (band == NL80211_BAND_5GHZ) + scan_ch->tx_gain = ((1 << 5) | (3 << 3)) | 3; + else + scan_ch->tx_gain = ((1 << 5) | (5 << 3)); + + IWL_DEBUG_SCAN(priv, "Scanning ch=%d prob=0x%X [%s %d]\n", + channel, le32_to_cpu(scan_ch->type), + (scan_ch->type & SCAN_CHANNEL_TYPE_ACTIVE) ? + "ACTIVE" : "PASSIVE", + (scan_ch->type & SCAN_CHANNEL_TYPE_ACTIVE) ? + active_dwell : passive_dwell); + + scan_ch++; + added++; + } + + IWL_DEBUG_SCAN(priv, "total channels to scan %d\n", added); + return added; +} + +/* + * iwl_fill_probe_req - fill in all required fields and IE for probe request + */ +static u16 iwl_fill_probe_req(struct ieee80211_mgmt *frame, const u8 *ta, + const u8 *ies, int ie_len, const u8 *ssid, + u8 ssid_len, int left) +{ + int len = 0; + u8 *pos = NULL; + + /* Make sure there is enough space for the probe request, + * two mandatory IEs and the data */ + left -= 24; + if (left < 0) + return 0; + + frame->frame_control = cpu_to_le16(IEEE80211_STYPE_PROBE_REQ); + eth_broadcast_addr(frame->da); + memcpy(frame->sa, ta, ETH_ALEN); + eth_broadcast_addr(frame->bssid); + frame->seq_ctrl = 0; + + len += 24; + + /* ...next IE... */ + pos = &frame->u.probe_req.variable[0]; + + /* fill in our SSID IE */ + left -= ssid_len + 2; + if (left < 0) + return 0; + *pos++ = WLAN_EID_SSID; + *pos++ = ssid_len; + if (ssid && ssid_len) { + memcpy(pos, ssid, ssid_len); + pos += ssid_len; + } + + len += ssid_len + 2; + + if (WARN_ON(left < ie_len)) + return len; + + if (ies && ie_len) { + memcpy(pos, ies, ie_len); + len += ie_len; + } + + return (u16)len; +} + +static int iwlagn_request_scan(struct iwl_priv *priv, struct ieee80211_vif *vif) +{ + struct iwl_host_cmd cmd = { + .id = REPLY_SCAN_CMD, + .len = { sizeof(struct iwl_scan_cmd), }, + }; + struct iwl_scan_cmd *scan; + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + u32 rate_flags = 0; + u16 cmd_len = 0; + u16 rx_chain = 0; + enum nl80211_band band; + u8 n_probes = 0; + u8 rx_ant = priv->nvm_data->valid_rx_ant; + u8 rate; + bool is_active = false; + int chan_mod; + u8 active_chains; + u8 scan_tx_antennas = priv->nvm_data->valid_tx_ant; + int ret; + size_t scan_cmd_size = sizeof(struct iwl_scan_cmd) + + MAX_SCAN_CHANNEL * sizeof(struct iwl_scan_channel) + + priv->fw->ucode_capa.max_probe_length; + const u8 *ssid = NULL; + u8 ssid_len = 0; + + if (WARN_ON(priv->scan_type == IWL_SCAN_NORMAL && + (!priv->scan_request || + priv->scan_request->n_channels > MAX_SCAN_CHANNEL))) + return -EINVAL; + + lockdep_assert_held(&priv->mutex); + + if (vif) + ctx = iwl_rxon_ctx_from_vif(vif); + + if (!priv->scan_cmd) { + priv->scan_cmd = kmalloc(scan_cmd_size, GFP_KERNEL); + if (!priv->scan_cmd) { + IWL_DEBUG_SCAN(priv, + "fail to allocate memory for scan\n"); + return -ENOMEM; + } + priv->scan_cmd_size = scan_cmd_size; + } + if (priv->scan_cmd_size < scan_cmd_size) { + IWL_DEBUG_SCAN(priv, + "memory needed for scan grew unexpectedly\n"); + return -ENOMEM; + } + scan = priv->scan_cmd; + memset(scan, 0, priv->scan_cmd_size); + + scan->quiet_plcp_th = IWL_PLCP_QUIET_THRESH; + scan->quiet_time = IWL_ACTIVE_QUIET_TIME; + + if (iwl_is_any_associated(priv)) { + u16 interval = 0; + u32 extra; + u32 suspend_time = 100; + u32 scan_suspend_time = 100; + + IWL_DEBUG_INFO(priv, "Scanning while associated...\n"); + switch (priv->scan_type) { + case IWL_SCAN_RADIO_RESET: + interval = 0; + break; + case IWL_SCAN_NORMAL: + interval = vif->bss_conf.beacon_int; + break; + } + + scan->suspend_time = 0; + scan->max_out_time = cpu_to_le32(200 * 1024); + if (!interval) + interval = suspend_time; + + extra = (suspend_time / interval) << 22; + scan_suspend_time = (extra | + ((suspend_time % interval) * 1024)); + scan->suspend_time = cpu_to_le32(scan_suspend_time); + IWL_DEBUG_SCAN(priv, "suspend_time 0x%X beacon interval %d\n", + scan_suspend_time, interval); + } + + switch (priv->scan_type) { + case IWL_SCAN_RADIO_RESET: + IWL_DEBUG_SCAN(priv, "Start internal passive scan.\n"); + /* + * Override quiet time as firmware checks that active + * dwell is >= quiet; since we use passive scan it'll + * not actually be used. + */ + scan->quiet_time = cpu_to_le16(IWL_RADIO_RESET_DWELL_TIME); + break; + case IWL_SCAN_NORMAL: + if (priv->scan_request->n_ssids) { + int i, p = 0; + IWL_DEBUG_SCAN(priv, "Kicking off active scan\n"); + /* + * The highest priority SSID is inserted to the + * probe request template. + */ + ssid_len = priv->scan_request->ssids[0].ssid_len; + ssid = priv->scan_request->ssids[0].ssid; + + /* + * Invert the order of ssids, the firmware will invert + * it back. + */ + for (i = priv->scan_request->n_ssids - 1; i >= 1; i--) { + scan->direct_scan[p].id = WLAN_EID_SSID; + scan->direct_scan[p].len = + priv->scan_request->ssids[i].ssid_len; + memcpy(scan->direct_scan[p].ssid, + priv->scan_request->ssids[i].ssid, + priv->scan_request->ssids[i].ssid_len); + n_probes++; + p++; + } + is_active = true; + } else + IWL_DEBUG_SCAN(priv, "Start passive scan.\n"); + break; + } + + scan->tx_cmd.tx_flags = TX_CMD_FLG_SEQ_CTL_MSK; + scan->tx_cmd.sta_id = ctx->bcast_sta_id; + scan->tx_cmd.stop_time.life_time = TX_CMD_LIFE_TIME_INFINITE; + + switch (priv->scan_band) { + case NL80211_BAND_2GHZ: + scan->flags = RXON_FLG_BAND_24G_MSK | RXON_FLG_AUTO_DETECT_MSK; + chan_mod = le32_to_cpu( + priv->contexts[IWL_RXON_CTX_BSS].active.flags & + RXON_FLG_CHANNEL_MODE_MSK) + >> RXON_FLG_CHANNEL_MODE_POS; + if ((priv->scan_request && priv->scan_request->no_cck) || + chan_mod == CHANNEL_MODE_PURE_40) { + rate = IWL_RATE_6M_PLCP; + } else { + rate = IWL_RATE_1M_PLCP; + rate_flags = RATE_MCS_CCK_MSK; + } + /* + * Internal scans are passive, so we can indiscriminately set + * the BT ignore flag on 2.4 GHz since it applies to TX only. + */ + if (priv->lib->bt_params && + priv->lib->bt_params->advanced_bt_coexist) + scan->tx_cmd.tx_flags |= TX_CMD_FLG_IGNORE_BT; + break; + case NL80211_BAND_5GHZ: + rate = IWL_RATE_6M_PLCP; + break; + default: + IWL_WARN(priv, "Invalid scan band\n"); + return -EIO; + } + + /* + * If active scanning is requested but a certain channel is + * marked passive, we can do active scanning if we detect + * transmissions. + * + * There is an issue with some firmware versions that triggers + * a sysassert on a "good CRC threshold" of zero (== disabled), + * on a radar channel even though this means that we should NOT + * send probes. + * + * The "good CRC threshold" is the number of frames that we + * need to receive during our dwell time on a channel before + * sending out probes -- setting this to a huge value will + * mean we never reach it, but at the same time work around + * the aforementioned issue. Thus use IWL_GOOD_CRC_TH_NEVER + * here instead of IWL_GOOD_CRC_TH_DISABLED. + * + * This was fixed in later versions along with some other + * scan changes, and the threshold behaves as a flag in those + * versions. + */ + if (priv->new_scan_threshold_behaviour) + scan->good_CRC_th = is_active ? IWL_GOOD_CRC_TH_DEFAULT : + IWL_GOOD_CRC_TH_DISABLED; + else + scan->good_CRC_th = is_active ? IWL_GOOD_CRC_TH_DEFAULT : + IWL_GOOD_CRC_TH_NEVER; + + band = priv->scan_band; + + if (band == NL80211_BAND_2GHZ && + priv->lib->bt_params && + priv->lib->bt_params->advanced_bt_coexist) { + /* transmit 2.4 GHz probes only on first antenna */ + scan_tx_antennas = first_antenna(scan_tx_antennas); + } + + priv->scan_tx_ant[band] = iwl_toggle_tx_ant(priv, + priv->scan_tx_ant[band], + scan_tx_antennas); + rate_flags |= iwl_ant_idx_to_flags(priv->scan_tx_ant[band]); + scan->tx_cmd.rate_n_flags = iwl_hw_set_rate_n_flags(rate, rate_flags); + + /* + * In power save mode while associated use one chain, + * otherwise use all chains + */ + if (test_bit(STATUS_POWER_PMI, &priv->status) && + !(priv->hw->conf.flags & IEEE80211_CONF_IDLE)) { + /* rx_ant has been set to all valid chains previously */ + active_chains = rx_ant & + ((u8)(priv->chain_noise_data.active_chains)); + if (!active_chains) + active_chains = rx_ant; + + IWL_DEBUG_SCAN(priv, "chain_noise_data.active_chains: %u\n", + priv->chain_noise_data.active_chains); + + rx_ant = first_antenna(active_chains); + } + if (priv->lib->bt_params && + priv->lib->bt_params->advanced_bt_coexist && + priv->bt_full_concurrent) { + /* operated as 1x1 in full concurrency mode */ + rx_ant = first_antenna(rx_ant); + } + + /* MIMO is not used here, but value is required */ + rx_chain |= + priv->nvm_data->valid_rx_ant << RXON_RX_CHAIN_VALID_POS; + rx_chain |= rx_ant << RXON_RX_CHAIN_FORCE_MIMO_SEL_POS; + rx_chain |= rx_ant << RXON_RX_CHAIN_FORCE_SEL_POS; + rx_chain |= 0x1 << RXON_RX_CHAIN_DRIVER_FORCE_POS; + scan->rx_chain = cpu_to_le16(rx_chain); + switch (priv->scan_type) { + case IWL_SCAN_NORMAL: + cmd_len = iwl_fill_probe_req( + (struct ieee80211_mgmt *)scan->data, + vif->addr, + priv->scan_request->ie, + priv->scan_request->ie_len, + ssid, ssid_len, + scan_cmd_size - sizeof(*scan)); + break; + case IWL_SCAN_RADIO_RESET: + /* use bcast addr, will not be transmitted but must be valid */ + cmd_len = iwl_fill_probe_req( + (struct ieee80211_mgmt *)scan->data, + iwl_bcast_addr, NULL, 0, + NULL, 0, + scan_cmd_size - sizeof(*scan)); + break; + default: + BUG(); + } + scan->tx_cmd.len = cpu_to_le16(cmd_len); + + scan->filter_flags |= (RXON_FILTER_ACCEPT_GRP_MSK | + RXON_FILTER_BCON_AWARE_MSK); + + switch (priv->scan_type) { + case IWL_SCAN_RADIO_RESET: + scan->channel_count = + iwl_get_channel_for_reset_scan(priv, vif, band, + (void *)&scan->data[cmd_len]); + break; + case IWL_SCAN_NORMAL: + scan->channel_count = + iwl_get_channels_for_scan(priv, vif, band, + is_active, n_probes, + (void *)&scan->data[cmd_len]); + break; + } + + if (scan->channel_count == 0) { + IWL_DEBUG_SCAN(priv, "channel count %d\n", scan->channel_count); + return -EIO; + } + + cmd.len[0] += le16_to_cpu(scan->tx_cmd.len) + + scan->channel_count * sizeof(struct iwl_scan_channel); + cmd.data[0] = scan; + cmd.dataflags[0] = IWL_HCMD_DFL_NOCOPY; + scan->len = cpu_to_le16(cmd.len[0]); + + /* set scan bit here for PAN params */ + set_bit(STATUS_SCAN_HW, &priv->status); + + ret = iwlagn_set_pan_params(priv); + if (ret) { + clear_bit(STATUS_SCAN_HW, &priv->status); + return ret; + } + + ret = iwl_dvm_send_cmd(priv, &cmd); + if (ret) { + clear_bit(STATUS_SCAN_HW, &priv->status); + iwlagn_set_pan_params(priv); + } + + return ret; +} + +void iwl_init_scan_params(struct iwl_priv *priv) +{ + u8 ant_idx = fls(priv->nvm_data->valid_tx_ant) - 1; + if (!priv->scan_tx_ant[NL80211_BAND_5GHZ]) + priv->scan_tx_ant[NL80211_BAND_5GHZ] = ant_idx; + if (!priv->scan_tx_ant[NL80211_BAND_2GHZ]) + priv->scan_tx_ant[NL80211_BAND_2GHZ] = ant_idx; +} + +int __must_check iwl_scan_initiate(struct iwl_priv *priv, + struct ieee80211_vif *vif, + enum iwl_scan_type scan_type, + enum nl80211_band band) +{ + int ret; + + lockdep_assert_held(&priv->mutex); + + cancel_delayed_work(&priv->scan_check); + + if (!iwl_is_ready_rf(priv)) { + IWL_WARN(priv, "Request scan called when driver not ready.\n"); + return -EIO; + } + + if (test_bit(STATUS_SCAN_HW, &priv->status)) { + IWL_DEBUG_SCAN(priv, + "Multiple concurrent scan requests in parallel.\n"); + return -EBUSY; + } + + if (test_bit(STATUS_SCAN_ABORTING, &priv->status)) { + IWL_DEBUG_SCAN(priv, "Scan request while abort pending.\n"); + return -EBUSY; + } + + IWL_DEBUG_SCAN(priv, "Starting %sscan...\n", + scan_type == IWL_SCAN_NORMAL ? "" : + "internal short "); + + set_bit(STATUS_SCANNING, &priv->status); + priv->scan_type = scan_type; + priv->scan_start = jiffies; + priv->scan_band = band; + + ret = iwlagn_request_scan(priv, vif); + if (ret) { + clear_bit(STATUS_SCANNING, &priv->status); + priv->scan_type = IWL_SCAN_NORMAL; + return ret; + } + + queue_delayed_work(priv->workqueue, &priv->scan_check, + IWL_SCAN_CHECK_WATCHDOG); + + return 0; +} + + +/* + * internal short scan, this function should only been called while associated. + * It will reset and tune the radio to prevent possible RF related problem + */ +void iwl_internal_short_hw_scan(struct iwl_priv *priv) +{ + queue_work(priv->workqueue, &priv->start_internal_scan); +} + +static void iwl_bg_start_internal_scan(struct work_struct *work) +{ + struct iwl_priv *priv = + container_of(work, struct iwl_priv, start_internal_scan); + + IWL_DEBUG_SCAN(priv, "Start internal scan\n"); + + mutex_lock(&priv->mutex); + + if (priv->scan_type == IWL_SCAN_RADIO_RESET) { + IWL_DEBUG_SCAN(priv, "Internal scan already in progress\n"); + goto unlock; + } + + if (test_bit(STATUS_SCANNING, &priv->status)) { + IWL_DEBUG_SCAN(priv, "Scan already in progress.\n"); + goto unlock; + } + + if (iwl_scan_initiate(priv, NULL, IWL_SCAN_RADIO_RESET, priv->band)) + IWL_DEBUG_SCAN(priv, "failed to start internal short scan\n"); + unlock: + mutex_unlock(&priv->mutex); +} + +static void iwl_bg_scan_check(struct work_struct *data) +{ + struct iwl_priv *priv = + container_of(data, struct iwl_priv, scan_check.work); + + IWL_DEBUG_SCAN(priv, "Scan check work\n"); + + /* Since we are here firmware does not finish scan and + * most likely is in bad shape, so we don't bother to + * send abort command, just force scan complete to mac80211 */ + mutex_lock(&priv->mutex); + iwl_force_scan_end(priv); + mutex_unlock(&priv->mutex); +} + +static void iwl_bg_abort_scan(struct work_struct *work) +{ + struct iwl_priv *priv = container_of(work, struct iwl_priv, abort_scan); + + IWL_DEBUG_SCAN(priv, "Abort scan work\n"); + + /* We keep scan_check work queued in case when firmware will not + * report back scan completed notification */ + mutex_lock(&priv->mutex); + iwl_scan_cancel_timeout(priv, 200); + mutex_unlock(&priv->mutex); +} + +static void iwl_bg_scan_completed(struct work_struct *work) +{ + struct iwl_priv *priv = + container_of(work, struct iwl_priv, scan_completed); + + mutex_lock(&priv->mutex); + iwl_process_scan_complete(priv); + mutex_unlock(&priv->mutex); +} + +void iwl_setup_scan_deferred_work(struct iwl_priv *priv) +{ + INIT_WORK(&priv->scan_completed, iwl_bg_scan_completed); + INIT_WORK(&priv->abort_scan, iwl_bg_abort_scan); + INIT_WORK(&priv->start_internal_scan, iwl_bg_start_internal_scan); + INIT_DELAYED_WORK(&priv->scan_check, iwl_bg_scan_check); +} + +void iwl_cancel_scan_deferred_work(struct iwl_priv *priv) +{ + cancel_work_sync(&priv->start_internal_scan); + cancel_work_sync(&priv->abort_scan); + cancel_work_sync(&priv->scan_completed); + + if (cancel_delayed_work_sync(&priv->scan_check)) { + mutex_lock(&priv->mutex); + iwl_force_scan_end(priv); + mutex_unlock(&priv->mutex); + } +} diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/sta.c b/drivers/net/wireless/intel/iwlwifi/dvm/sta.c new file mode 100644 index 000000000..8b01ab986 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/sta.c @@ -0,0 +1,1425 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2003 - 2014, 2022 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + *****************************************************************************/ +#include <linux/etherdevice.h> +#include <net/mac80211.h> +#include "iwl-trans.h" +#include "dev.h" +#include "agn.h" + +const u8 iwl_bcast_addr[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + +static int iwl_sta_ucode_activate(struct iwl_priv *priv, u8 sta_id) +{ + lockdep_assert_held(&priv->sta_lock); + + if (sta_id >= IWLAGN_STATION_COUNT) { + IWL_ERR(priv, "invalid sta_id %u\n", sta_id); + return -EINVAL; + } + if (!(priv->stations[sta_id].used & IWL_STA_DRIVER_ACTIVE)) + IWL_ERR(priv, "ACTIVATE a non DRIVER active station id %u " + "addr %pM\n", + sta_id, priv->stations[sta_id].sta.sta.addr); + + if (priv->stations[sta_id].used & IWL_STA_UCODE_ACTIVE) { + IWL_DEBUG_ASSOC(priv, + "STA id %u addr %pM already present in uCode " + "(according to driver)\n", + sta_id, priv->stations[sta_id].sta.sta.addr); + } else { + priv->stations[sta_id].used |= IWL_STA_UCODE_ACTIVE; + IWL_DEBUG_ASSOC(priv, "Added STA id %u addr %pM to uCode\n", + sta_id, priv->stations[sta_id].sta.sta.addr); + } + return 0; +} + +static void iwl_process_add_sta_resp(struct iwl_priv *priv, + struct iwl_rx_packet *pkt) +{ + struct iwl_add_sta_resp *add_sta_resp = (void *)pkt->data; + + IWL_DEBUG_INFO(priv, "Processing response for adding station\n"); + + spin_lock_bh(&priv->sta_lock); + + switch (add_sta_resp->status) { + case ADD_STA_SUCCESS_MSK: + IWL_DEBUG_INFO(priv, "REPLY_ADD_STA PASSED\n"); + break; + case ADD_STA_NO_ROOM_IN_TABLE: + IWL_ERR(priv, "Adding station failed, no room in table.\n"); + break; + case ADD_STA_NO_BLOCK_ACK_RESOURCE: + IWL_ERR(priv, + "Adding station failed, no block ack resource.\n"); + break; + case ADD_STA_MODIFY_NON_EXIST_STA: + IWL_ERR(priv, "Attempting to modify non-existing station\n"); + break; + default: + IWL_DEBUG_ASSOC(priv, "Received REPLY_ADD_STA:(0x%08X)\n", + add_sta_resp->status); + break; + } + + spin_unlock_bh(&priv->sta_lock); +} + +void iwl_add_sta_callback(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + + iwl_process_add_sta_resp(priv, pkt); +} + +int iwl_send_add_sta(struct iwl_priv *priv, + struct iwl_addsta_cmd *sta, u8 flags) +{ + int ret = 0; + struct iwl_host_cmd cmd = { + .id = REPLY_ADD_STA, + .flags = flags, + .data = { sta, }, + .len = { sizeof(*sta), }, + }; + u8 sta_id __maybe_unused = sta->sta.sta_id; + struct iwl_rx_packet *pkt; + struct iwl_add_sta_resp *add_sta_resp; + + IWL_DEBUG_INFO(priv, "Adding sta %u (%pM) %ssynchronously\n", + sta_id, sta->sta.addr, flags & CMD_ASYNC ? "a" : ""); + + if (!(flags & CMD_ASYNC)) { + cmd.flags |= CMD_WANT_SKB; + might_sleep(); + } + + ret = iwl_dvm_send_cmd(priv, &cmd); + + if (ret || (flags & CMD_ASYNC)) + return ret; + + pkt = cmd.resp_pkt; + add_sta_resp = (void *)pkt->data; + + /* debug messages are printed in the handler */ + if (add_sta_resp->status == ADD_STA_SUCCESS_MSK) { + spin_lock_bh(&priv->sta_lock); + ret = iwl_sta_ucode_activate(priv, sta_id); + spin_unlock_bh(&priv->sta_lock); + } else { + ret = -EIO; + } + + iwl_free_resp(&cmd); + + return ret; +} + +bool iwl_is_ht40_tx_allowed(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_sta *sta) +{ + if (!ctx->ht.enabled || !ctx->ht.is_40mhz) + return false; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (priv->disable_ht40) + return false; +#endif + + /* special case for RXON */ + if (!sta) + return true; + + return sta->deflink.bandwidth >= IEEE80211_STA_RX_BW_40; +} + +static void iwl_sta_calc_ht_flags(struct iwl_priv *priv, + struct ieee80211_sta *sta, + struct iwl_rxon_context *ctx, + __le32 *flags, __le32 *mask) +{ + struct ieee80211_sta_ht_cap *sta_ht_inf = &sta->deflink.ht_cap; + + *mask = STA_FLG_RTS_MIMO_PROT_MSK | + STA_FLG_MIMO_DIS_MSK | + STA_FLG_HT40_EN_MSK | + STA_FLG_MAX_AGG_SIZE_MSK | + STA_FLG_AGG_MPDU_DENSITY_MSK; + *flags = 0; + + if (!sta || !sta_ht_inf->ht_supported) + return; + + IWL_DEBUG_INFO(priv, "STA %pM SM PS mode: %s\n", + sta->addr, + (sta->deflink.smps_mode == IEEE80211_SMPS_STATIC) ? + "static" : + (sta->deflink.smps_mode == IEEE80211_SMPS_DYNAMIC) ? + "dynamic" : "disabled"); + + switch (sta->deflink.smps_mode) { + case IEEE80211_SMPS_STATIC: + *flags |= STA_FLG_MIMO_DIS_MSK; + break; + case IEEE80211_SMPS_DYNAMIC: + *flags |= STA_FLG_RTS_MIMO_PROT_MSK; + break; + case IEEE80211_SMPS_OFF: + break; + default: + IWL_WARN(priv, "Invalid MIMO PS mode %d\n", sta->deflink.smps_mode); + break; + } + + *flags |= cpu_to_le32( + (u32)sta_ht_inf->ampdu_factor << STA_FLG_MAX_AGG_SIZE_POS); + + *flags |= cpu_to_le32( + (u32)sta_ht_inf->ampdu_density << STA_FLG_AGG_MPDU_DENSITY_POS); + + if (iwl_is_ht40_tx_allowed(priv, ctx, sta)) + *flags |= STA_FLG_HT40_EN_MSK; +} + +int iwl_sta_update_ht(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + struct ieee80211_sta *sta) +{ + u8 sta_id = iwl_sta_id(sta); + __le32 flags, mask; + struct iwl_addsta_cmd cmd; + + if (WARN_ON_ONCE(sta_id == IWL_INVALID_STATION)) + return -EINVAL; + + iwl_sta_calc_ht_flags(priv, sta, ctx, &flags, &mask); + + spin_lock_bh(&priv->sta_lock); + priv->stations[sta_id].sta.station_flags &= ~mask; + priv->stations[sta_id].sta.station_flags |= flags; + spin_unlock_bh(&priv->sta_lock); + + memset(&cmd, 0, sizeof(cmd)); + cmd.mode = STA_CONTROL_MODIFY_MSK; + cmd.station_flags_msk = mask; + cmd.station_flags = flags; + cmd.sta.sta_id = sta_id; + + return iwl_send_add_sta(priv, &cmd, 0); +} + +static void iwl_set_ht_add_station(struct iwl_priv *priv, u8 index, + struct ieee80211_sta *sta, + struct iwl_rxon_context *ctx) +{ + __le32 flags, mask; + + iwl_sta_calc_ht_flags(priv, sta, ctx, &flags, &mask); + + lockdep_assert_held(&priv->sta_lock); + priv->stations[index].sta.station_flags &= ~mask; + priv->stations[index].sta.station_flags |= flags; +} + +/* + * iwl_prep_station - Prepare station information for addition + * + * should be called with sta_lock held + */ +u8 iwl_prep_station(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + const u8 *addr, bool is_ap, struct ieee80211_sta *sta) +{ + struct iwl_station_entry *station; + int i; + u8 sta_id = IWL_INVALID_STATION; + + if (is_ap) + sta_id = ctx->ap_sta_id; + else if (is_broadcast_ether_addr(addr)) + sta_id = ctx->bcast_sta_id; + else + for (i = IWL_STA_ID; i < IWLAGN_STATION_COUNT; i++) { + if (ether_addr_equal(priv->stations[i].sta.sta.addr, + addr)) { + sta_id = i; + break; + } + + if (!priv->stations[i].used && + sta_id == IWL_INVALID_STATION) + sta_id = i; + } + + /* + * These two conditions have the same outcome, but keep them + * separate + */ + if (unlikely(sta_id == IWL_INVALID_STATION)) + return sta_id; + + /* + * uCode is not able to deal with multiple requests to add a + * station. Keep track if one is in progress so that we do not send + * another. + */ + if (priv->stations[sta_id].used & IWL_STA_UCODE_INPROGRESS) { + IWL_DEBUG_INFO(priv, "STA %d already in process of being " + "added.\n", sta_id); + return sta_id; + } + + if ((priv->stations[sta_id].used & IWL_STA_DRIVER_ACTIVE) && + (priv->stations[sta_id].used & IWL_STA_UCODE_ACTIVE) && + ether_addr_equal(priv->stations[sta_id].sta.sta.addr, addr)) { + IWL_DEBUG_ASSOC(priv, "STA %d (%pM) already added, not " + "adding again.\n", sta_id, addr); + return sta_id; + } + + station = &priv->stations[sta_id]; + station->used = IWL_STA_DRIVER_ACTIVE; + IWL_DEBUG_ASSOC(priv, "Add STA to driver ID %d: %pM\n", + sta_id, addr); + priv->num_stations++; + + /* Set up the REPLY_ADD_STA command to send to device */ + memset(&station->sta, 0, sizeof(struct iwl_addsta_cmd)); + memcpy(station->sta.sta.addr, addr, ETH_ALEN); + station->sta.mode = 0; + station->sta.sta.sta_id = sta_id; + station->sta.station_flags = ctx->station_flags; + station->ctxid = ctx->ctxid; + + if (sta) { + struct iwl_station_priv *sta_priv; + + sta_priv = (void *)sta->drv_priv; + sta_priv->ctx = ctx; + } + + /* + * OK to call unconditionally, since local stations (IBSS BSSID + * STA and broadcast STA) pass in a NULL sta, and mac80211 + * doesn't allow HT IBSS. + */ + iwl_set_ht_add_station(priv, sta_id, sta, ctx); + + return sta_id; + +} + +#define STA_WAIT_TIMEOUT (HZ/2) + +/* + * iwl_add_station_common - + */ +int iwl_add_station_common(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + const u8 *addr, bool is_ap, + struct ieee80211_sta *sta, u8 *sta_id_r) +{ + int ret = 0; + u8 sta_id; + struct iwl_addsta_cmd sta_cmd; + + *sta_id_r = 0; + spin_lock_bh(&priv->sta_lock); + sta_id = iwl_prep_station(priv, ctx, addr, is_ap, sta); + if (sta_id == IWL_INVALID_STATION) { + IWL_ERR(priv, "Unable to prepare station %pM for addition\n", + addr); + spin_unlock_bh(&priv->sta_lock); + return -EINVAL; + } + + /* + * uCode is not able to deal with multiple requests to add a + * station. Keep track if one is in progress so that we do not send + * another. + */ + if (priv->stations[sta_id].used & IWL_STA_UCODE_INPROGRESS) { + IWL_DEBUG_INFO(priv, "STA %d already in process of being " + "added.\n", sta_id); + spin_unlock_bh(&priv->sta_lock); + return -EEXIST; + } + + if ((priv->stations[sta_id].used & IWL_STA_DRIVER_ACTIVE) && + (priv->stations[sta_id].used & IWL_STA_UCODE_ACTIVE)) { + IWL_DEBUG_ASSOC(priv, "STA %d (%pM) already added, not " + "adding again.\n", sta_id, addr); + spin_unlock_bh(&priv->sta_lock); + return -EEXIST; + } + + priv->stations[sta_id].used |= IWL_STA_UCODE_INPROGRESS; + memcpy(&sta_cmd, &priv->stations[sta_id].sta, + sizeof(struct iwl_addsta_cmd)); + spin_unlock_bh(&priv->sta_lock); + + /* Add station to device's station table */ + ret = iwl_send_add_sta(priv, &sta_cmd, 0); + if (ret) { + spin_lock_bh(&priv->sta_lock); + IWL_ERR(priv, "Adding station %pM failed.\n", + priv->stations[sta_id].sta.sta.addr); + priv->stations[sta_id].used &= ~IWL_STA_DRIVER_ACTIVE; + priv->stations[sta_id].used &= ~IWL_STA_UCODE_INPROGRESS; + spin_unlock_bh(&priv->sta_lock); + } + *sta_id_r = sta_id; + return ret; +} + +/* + * iwl_sta_ucode_deactivate - deactivate ucode status for a station + */ +static void iwl_sta_ucode_deactivate(struct iwl_priv *priv, u8 sta_id) +{ + lockdep_assert_held(&priv->sta_lock); + + /* Ucode must be active and driver must be non active */ + if ((priv->stations[sta_id].used & + (IWL_STA_UCODE_ACTIVE | IWL_STA_DRIVER_ACTIVE)) != + IWL_STA_UCODE_ACTIVE) + IWL_ERR(priv, "removed non active STA %u\n", sta_id); + + priv->stations[sta_id].used &= ~IWL_STA_UCODE_ACTIVE; + + memset(&priv->stations[sta_id], 0, sizeof(struct iwl_station_entry)); + IWL_DEBUG_ASSOC(priv, "Removed STA %u\n", sta_id); +} + +static int iwl_send_remove_station(struct iwl_priv *priv, + const u8 *addr, int sta_id, + bool temporary) +{ + struct iwl_rx_packet *pkt; + int ret; + struct iwl_rem_sta_cmd rm_sta_cmd; + struct iwl_rem_sta_resp *rem_sta_resp; + + struct iwl_host_cmd cmd = { + .id = REPLY_REMOVE_STA, + .len = { sizeof(struct iwl_rem_sta_cmd), }, + .data = { &rm_sta_cmd, }, + }; + + memset(&rm_sta_cmd, 0, sizeof(rm_sta_cmd)); + rm_sta_cmd.num_sta = 1; + memcpy(&rm_sta_cmd.addr, addr, ETH_ALEN); + + cmd.flags |= CMD_WANT_SKB; + + ret = iwl_dvm_send_cmd(priv, &cmd); + + if (ret) + return ret; + + pkt = cmd.resp_pkt; + rem_sta_resp = (void *)pkt->data; + + switch (rem_sta_resp->status) { + case REM_STA_SUCCESS_MSK: + if (!temporary) { + spin_lock_bh(&priv->sta_lock); + iwl_sta_ucode_deactivate(priv, sta_id); + spin_unlock_bh(&priv->sta_lock); + } + IWL_DEBUG_ASSOC(priv, "REPLY_REMOVE_STA PASSED\n"); + break; + default: + ret = -EIO; + IWL_ERR(priv, "REPLY_REMOVE_STA failed\n"); + break; + } + + iwl_free_resp(&cmd); + + return ret; +} + +/* + * iwl_remove_station - Remove driver's knowledge of station. + */ +int iwl_remove_station(struct iwl_priv *priv, const u8 sta_id, + const u8 *addr) +{ + u8 tid; + + if (!iwl_is_ready(priv)) { + IWL_DEBUG_INFO(priv, + "Unable to remove station %pM, device not ready.\n", + addr); + /* + * It is typical for stations to be removed when we are + * going down. Return success since device will be down + * soon anyway + */ + return 0; + } + + IWL_DEBUG_ASSOC(priv, "Removing STA from driver:%d %pM\n", + sta_id, addr); + + if (WARN_ON(sta_id == IWL_INVALID_STATION)) + return -EINVAL; + + spin_lock_bh(&priv->sta_lock); + + if (!(priv->stations[sta_id].used & IWL_STA_DRIVER_ACTIVE)) { + IWL_DEBUG_INFO(priv, "Removing %pM but non DRIVER active\n", + addr); + goto out_err; + } + + if (!(priv->stations[sta_id].used & IWL_STA_UCODE_ACTIVE)) { + IWL_DEBUG_INFO(priv, "Removing %pM but non UCODE active\n", + addr); + goto out_err; + } + + if (priv->stations[sta_id].used & IWL_STA_LOCAL) { + kfree(priv->stations[sta_id].lq); + priv->stations[sta_id].lq = NULL; + } + + for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++) + memset(&priv->tid_data[sta_id][tid], 0, + sizeof(priv->tid_data[sta_id][tid])); + + priv->stations[sta_id].used &= ~IWL_STA_DRIVER_ACTIVE; + + priv->num_stations--; + + if (WARN_ON(priv->num_stations < 0)) + priv->num_stations = 0; + + spin_unlock_bh(&priv->sta_lock); + + return iwl_send_remove_station(priv, addr, sta_id, false); +out_err: + spin_unlock_bh(&priv->sta_lock); + return -EINVAL; +} + +void iwl_deactivate_station(struct iwl_priv *priv, const u8 sta_id, + const u8 *addr) +{ + u8 tid; + + if (!iwl_is_ready(priv)) { + IWL_DEBUG_INFO(priv, + "Unable to remove station %pM, device not ready.\n", + addr); + return; + } + + IWL_DEBUG_ASSOC(priv, "Deactivating STA: %pM (%d)\n", addr, sta_id); + + if (WARN_ON_ONCE(sta_id == IWL_INVALID_STATION)) + return; + + spin_lock_bh(&priv->sta_lock); + + WARN_ON_ONCE(!(priv->stations[sta_id].used & IWL_STA_DRIVER_ACTIVE)); + + for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++) + memset(&priv->tid_data[sta_id][tid], 0, + sizeof(priv->tid_data[sta_id][tid])); + + priv->stations[sta_id].used &= ~IWL_STA_DRIVER_ACTIVE; + priv->stations[sta_id].used &= ~IWL_STA_UCODE_INPROGRESS; + + priv->num_stations--; + + if (WARN_ON_ONCE(priv->num_stations < 0)) + priv->num_stations = 0; + + spin_unlock_bh(&priv->sta_lock); +} + +static void iwl_sta_fill_lq(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + u8 sta_id, struct iwl_link_quality_cmd *link_cmd) +{ + int i, r; + u32 rate_flags = 0; + __le32 rate_n_flags; + + lockdep_assert_held(&priv->mutex); + + memset(link_cmd, 0, sizeof(*link_cmd)); + + /* Set up the rate scaling to start at selected rate, fall back + * all the way down to 1M in IEEE order, and then spin on 1M */ + if (priv->band == NL80211_BAND_5GHZ) + r = IWL_RATE_6M_INDEX; + else if (ctx && ctx->vif && ctx->vif->p2p) + r = IWL_RATE_6M_INDEX; + else + r = IWL_RATE_1M_INDEX; + + if (r >= IWL_FIRST_CCK_RATE && r <= IWL_LAST_CCK_RATE) + rate_flags |= RATE_MCS_CCK_MSK; + + rate_flags |= first_antenna(priv->nvm_data->valid_tx_ant) << + RATE_MCS_ANT_POS; + rate_n_flags = iwl_hw_set_rate_n_flags(iwl_rates[r].plcp, rate_flags); + for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++) + link_cmd->rs_table[i].rate_n_flags = rate_n_flags; + + link_cmd->general_params.single_stream_ant_msk = + first_antenna(priv->nvm_data->valid_tx_ant); + + link_cmd->general_params.dual_stream_ant_msk = + priv->nvm_data->valid_tx_ant & + ~first_antenna(priv->nvm_data->valid_tx_ant); + if (!link_cmd->general_params.dual_stream_ant_msk) { + link_cmd->general_params.dual_stream_ant_msk = ANT_AB; + } else if (num_of_ant(priv->nvm_data->valid_tx_ant) == 2) { + link_cmd->general_params.dual_stream_ant_msk = + priv->nvm_data->valid_tx_ant; + } + + link_cmd->agg_params.agg_dis_start_th = + LINK_QUAL_AGG_DISABLE_START_DEF; + link_cmd->agg_params.agg_time_limit = + cpu_to_le16(LINK_QUAL_AGG_TIME_LIMIT_DEF); + + link_cmd->sta_id = sta_id; +} + +/* + * iwl_clear_ucode_stations - clear ucode station table bits + * + * This function clears all the bits in the driver indicating + * which stations are active in the ucode. Call when something + * other than explicit station management would cause this in + * the ucode, e.g. unassociated RXON. + */ +void iwl_clear_ucode_stations(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + int i; + bool cleared = false; + + IWL_DEBUG_INFO(priv, "Clearing ucode stations in driver\n"); + + spin_lock_bh(&priv->sta_lock); + for (i = 0; i < IWLAGN_STATION_COUNT; i++) { + if (ctx && ctx->ctxid != priv->stations[i].ctxid) + continue; + + if (priv->stations[i].used & IWL_STA_UCODE_ACTIVE) { + IWL_DEBUG_INFO(priv, + "Clearing ucode active for station %d\n", i); + priv->stations[i].used &= ~IWL_STA_UCODE_ACTIVE; + cleared = true; + } + } + spin_unlock_bh(&priv->sta_lock); + + if (!cleared) + IWL_DEBUG_INFO(priv, + "No active stations found to be cleared\n"); +} + +/* + * iwl_restore_stations() - Restore driver known stations to device + * + * All stations considered active by driver, but not present in ucode, is + * restored. + * + * Function sleeps. + */ +void iwl_restore_stations(struct iwl_priv *priv, struct iwl_rxon_context *ctx) +{ + struct iwl_addsta_cmd sta_cmd; + static const struct iwl_link_quality_cmd zero_lq = {}; + struct iwl_link_quality_cmd lq; + int i; + bool found = false; + int ret; + bool send_lq; + + if (!iwl_is_ready(priv)) { + IWL_DEBUG_INFO(priv, + "Not ready yet, not restoring any stations.\n"); + return; + } + + IWL_DEBUG_ASSOC(priv, "Restoring all known stations ... start.\n"); + spin_lock_bh(&priv->sta_lock); + for (i = 0; i < IWLAGN_STATION_COUNT; i++) { + if (ctx->ctxid != priv->stations[i].ctxid) + continue; + if ((priv->stations[i].used & IWL_STA_DRIVER_ACTIVE) && + !(priv->stations[i].used & IWL_STA_UCODE_ACTIVE)) { + IWL_DEBUG_ASSOC(priv, "Restoring sta %pM\n", + priv->stations[i].sta.sta.addr); + priv->stations[i].sta.mode = 0; + priv->stations[i].used |= IWL_STA_UCODE_INPROGRESS; + found = true; + } + } + + for (i = 0; i < IWLAGN_STATION_COUNT; i++) { + if ((priv->stations[i].used & IWL_STA_UCODE_INPROGRESS)) { + memcpy(&sta_cmd, &priv->stations[i].sta, + sizeof(struct iwl_addsta_cmd)); + send_lq = false; + if (priv->stations[i].lq) { + if (priv->wowlan) + iwl_sta_fill_lq(priv, ctx, i, &lq); + else + memcpy(&lq, priv->stations[i].lq, + sizeof(struct iwl_link_quality_cmd)); + + if (memcmp(&lq, &zero_lq, sizeof(lq))) + send_lq = true; + } + spin_unlock_bh(&priv->sta_lock); + ret = iwl_send_add_sta(priv, &sta_cmd, 0); + if (ret) { + spin_lock_bh(&priv->sta_lock); + IWL_ERR(priv, "Adding station %pM failed.\n", + priv->stations[i].sta.sta.addr); + priv->stations[i].used &= + ~IWL_STA_DRIVER_ACTIVE; + priv->stations[i].used &= + ~IWL_STA_UCODE_INPROGRESS; + continue; + } + /* + * Rate scaling has already been initialized, send + * current LQ command + */ + if (send_lq) + iwl_send_lq_cmd(priv, ctx, &lq, 0, true); + spin_lock_bh(&priv->sta_lock); + priv->stations[i].used &= ~IWL_STA_UCODE_INPROGRESS; + } + } + + spin_unlock_bh(&priv->sta_lock); + if (!found) + IWL_DEBUG_INFO(priv, "Restoring all known stations .... " + "no stations to be restored.\n"); + else + IWL_DEBUG_INFO(priv, "Restoring all known stations .... " + "complete.\n"); +} + +int iwl_get_free_ucode_key_offset(struct iwl_priv *priv) +{ + int i; + + for (i = 0; i < priv->sta_key_max_num; i++) + if (!test_and_set_bit(i, &priv->ucode_key_table)) + return i; + + return WEP_INVALID_OFFSET; +} + +void iwl_dealloc_bcast_stations(struct iwl_priv *priv) +{ + int i; + + spin_lock_bh(&priv->sta_lock); + for (i = 0; i < IWLAGN_STATION_COUNT; i++) { + if (!(priv->stations[i].used & IWL_STA_BCAST)) + continue; + + priv->stations[i].used &= ~IWL_STA_UCODE_ACTIVE; + priv->num_stations--; + if (WARN_ON(priv->num_stations < 0)) + priv->num_stations = 0; + kfree(priv->stations[i].lq); + priv->stations[i].lq = NULL; + } + spin_unlock_bh(&priv->sta_lock); +} + +#ifdef CONFIG_IWLWIFI_DEBUG +static void iwl_dump_lq_cmd(struct iwl_priv *priv, + struct iwl_link_quality_cmd *lq) +{ + int i; + IWL_DEBUG_RATE(priv, "lq station id 0x%x\n", lq->sta_id); + IWL_DEBUG_RATE(priv, "lq ant 0x%X 0x%X\n", + lq->general_params.single_stream_ant_msk, + lq->general_params.dual_stream_ant_msk); + + for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++) + IWL_DEBUG_RATE(priv, "lq index %d 0x%X\n", + i, lq->rs_table[i].rate_n_flags); +} +#else +static inline void iwl_dump_lq_cmd(struct iwl_priv *priv, + struct iwl_link_quality_cmd *lq) +{ +} +#endif + +/* + * is_lq_table_valid() - Test one aspect of LQ cmd for validity + * + * It sometimes happens when a HT rate has been in use and we + * loose connectivity with AP then mac80211 will first tell us that the + * current channel is not HT anymore before removing the station. In such a + * scenario the RXON flags will be updated to indicate we are not + * communicating HT anymore, but the LQ command may still contain HT rates. + * Test for this to prevent driver from sending LQ command between the time + * RXON flags are updated and when LQ command is updated. + */ +static bool is_lq_table_valid(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct iwl_link_quality_cmd *lq) +{ + int i; + + if (ctx->ht.enabled) + return true; + + IWL_DEBUG_INFO(priv, "Channel %u is not an HT channel\n", + ctx->active.channel); + for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++) { + if (le32_to_cpu(lq->rs_table[i].rate_n_flags) & + RATE_MCS_HT_MSK) { + IWL_DEBUG_INFO(priv, + "index %d of LQ expects HT channel\n", + i); + return false; + } + } + return true; +} + +/* + * iwl_send_lq_cmd() - Send link quality command + * @init: This command is sent as part of station initialization right + * after station has been added. + * + * The link quality command is sent as the last step of station creation. + * This is the special case in which init is set and we call a callback in + * this case to clear the state indicating that station creation is in + * progress. + */ +int iwl_send_lq_cmd(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + struct iwl_link_quality_cmd *lq, u8 flags, bool init) +{ + int ret = 0; + struct iwl_host_cmd cmd = { + .id = REPLY_TX_LINK_QUALITY_CMD, + .len = { sizeof(struct iwl_link_quality_cmd), }, + .flags = flags, + .data = { lq, }, + }; + + if (WARN_ON(lq->sta_id == IWL_INVALID_STATION)) + return -EINVAL; + + + spin_lock_bh(&priv->sta_lock); + if (!(priv->stations[lq->sta_id].used & IWL_STA_DRIVER_ACTIVE)) { + spin_unlock_bh(&priv->sta_lock); + return -EINVAL; + } + spin_unlock_bh(&priv->sta_lock); + + iwl_dump_lq_cmd(priv, lq); + if (WARN_ON(init && (cmd.flags & CMD_ASYNC))) + return -EINVAL; + + if (is_lq_table_valid(priv, ctx, lq)) + ret = iwl_dvm_send_cmd(priv, &cmd); + else + ret = -EINVAL; + + if (cmd.flags & CMD_ASYNC) + return ret; + + if (init) { + IWL_DEBUG_INFO(priv, "init LQ command complete, " + "clearing sta addition status for sta %d\n", + lq->sta_id); + spin_lock_bh(&priv->sta_lock); + priv->stations[lq->sta_id].used &= ~IWL_STA_UCODE_INPROGRESS; + spin_unlock_bh(&priv->sta_lock); + } + return ret; +} + + +static struct iwl_link_quality_cmd * +iwl_sta_alloc_lq(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + u8 sta_id) +{ + struct iwl_link_quality_cmd *link_cmd; + + link_cmd = kzalloc(sizeof(struct iwl_link_quality_cmd), GFP_KERNEL); + if (!link_cmd) { + IWL_ERR(priv, "Unable to allocate memory for LQ cmd.\n"); + return NULL; + } + + iwl_sta_fill_lq(priv, ctx, sta_id, link_cmd); + + return link_cmd; +} + +/* + * iwlagn_add_bssid_station - Add the special IBSS BSSID station + * + * Function sleeps. + */ +int iwlagn_add_bssid_station(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + const u8 *addr, u8 *sta_id_r) +{ + int ret; + u8 sta_id; + struct iwl_link_quality_cmd *link_cmd; + + if (sta_id_r) + *sta_id_r = IWL_INVALID_STATION; + + ret = iwl_add_station_common(priv, ctx, addr, 0, NULL, &sta_id); + if (ret) { + IWL_ERR(priv, "Unable to add station %pM\n", addr); + return ret; + } + + if (sta_id_r) + *sta_id_r = sta_id; + + spin_lock_bh(&priv->sta_lock); + priv->stations[sta_id].used |= IWL_STA_LOCAL; + spin_unlock_bh(&priv->sta_lock); + + /* Set up default rate scaling table in device's station table */ + link_cmd = iwl_sta_alloc_lq(priv, ctx, sta_id); + if (!link_cmd) { + IWL_ERR(priv, + "Unable to initialize rate scaling for station %pM.\n", + addr); + return -ENOMEM; + } + + ret = iwl_send_lq_cmd(priv, ctx, link_cmd, 0, true); + if (ret) + IWL_ERR(priv, "Link quality command failed (%d)\n", ret); + + spin_lock_bh(&priv->sta_lock); + priv->stations[sta_id].lq = link_cmd; + spin_unlock_bh(&priv->sta_lock); + + return 0; +} + +/* + * static WEP keys + * + * For each context, the device has a table of 4 static WEP keys + * (one for each key index) that is updated with the following + * commands. + */ + +static int iwl_send_static_wepkey_cmd(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + bool send_if_empty) +{ + int i, not_empty = 0; + u8 buff[sizeof(struct iwl_wep_cmd) + + sizeof(struct iwl_wep_key) * WEP_KEYS_MAX]; + struct iwl_wep_cmd *wep_cmd = (struct iwl_wep_cmd *)buff; + size_t cmd_size = sizeof(struct iwl_wep_cmd); + struct iwl_host_cmd cmd = { + .id = ctx->wep_key_cmd, + .data = { wep_cmd, }, + }; + + might_sleep(); + + memset(wep_cmd, 0, cmd_size + + (sizeof(struct iwl_wep_key) * WEP_KEYS_MAX)); + + for (i = 0; i < WEP_KEYS_MAX ; i++) { + wep_cmd->key[i].key_index = i; + if (ctx->wep_keys[i].key_size) { + wep_cmd->key[i].key_offset = i; + not_empty = 1; + } else { + wep_cmd->key[i].key_offset = WEP_INVALID_OFFSET; + } + + wep_cmd->key[i].key_size = ctx->wep_keys[i].key_size; + memcpy(&wep_cmd->key[i].key[3], ctx->wep_keys[i].key, + ctx->wep_keys[i].key_size); + } + + wep_cmd->global_key_type = WEP_KEY_WEP_TYPE; + wep_cmd->num_keys = WEP_KEYS_MAX; + + cmd_size += sizeof(struct iwl_wep_key) * WEP_KEYS_MAX; + + cmd.len[0] = cmd_size; + + if (not_empty || send_if_empty) + return iwl_dvm_send_cmd(priv, &cmd); + else + return 0; +} + +int iwl_restore_default_wep_keys(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + lockdep_assert_held(&priv->mutex); + + return iwl_send_static_wepkey_cmd(priv, ctx, false); +} + +int iwl_remove_default_wep_key(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_key_conf *keyconf) +{ + int ret; + + lockdep_assert_held(&priv->mutex); + + IWL_DEBUG_WEP(priv, "Removing default WEP key: idx=%d\n", + keyconf->keyidx); + + memset(&ctx->wep_keys[keyconf->keyidx], 0, sizeof(ctx->wep_keys[0])); + if (iwl_is_rfkill(priv)) { + IWL_DEBUG_WEP(priv, + "Not sending REPLY_WEPKEY command due to RFKILL.\n"); + /* but keys in device are clear anyway so return success */ + return 0; + } + ret = iwl_send_static_wepkey_cmd(priv, ctx, 1); + IWL_DEBUG_WEP(priv, "Remove default WEP key: idx=%d ret=%d\n", + keyconf->keyidx, ret); + + return ret; +} + +int iwl_set_default_wep_key(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_key_conf *keyconf) +{ + int ret; + + lockdep_assert_held(&priv->mutex); + + if (keyconf->keylen != WEP_KEY_LEN_128 && + keyconf->keylen != WEP_KEY_LEN_64) { + IWL_DEBUG_WEP(priv, + "Bad WEP key length %d\n", keyconf->keylen); + return -EINVAL; + } + + keyconf->hw_key_idx = IWLAGN_HW_KEY_DEFAULT; + + ctx->wep_keys[keyconf->keyidx].key_size = keyconf->keylen; + memcpy(&ctx->wep_keys[keyconf->keyidx].key, &keyconf->key, + keyconf->keylen); + + ret = iwl_send_static_wepkey_cmd(priv, ctx, false); + IWL_DEBUG_WEP(priv, "Set default WEP key: len=%d idx=%d ret=%d\n", + keyconf->keylen, keyconf->keyidx, ret); + + return ret; +} + +/* + * dynamic (per-station) keys + * + * The dynamic keys are a little more complicated. The device has + * a key cache of up to STA_KEY_MAX_NUM/STA_KEY_MAX_NUM_PAN keys. + * These are linked to stations by a table that contains an index + * into the key table for each station/key index/{mcast,unicast}, + * i.e. it's basically an array of pointers like this: + * key_offset_t key_mapping[NUM_STATIONS][4][2]; + * (it really works differently, but you can think of it as such) + * + * The key uploading and linking happens in the same command, the + * add station command with STA_MODIFY_KEY_MASK. + */ + +static u8 iwlagn_key_sta_id(struct iwl_priv *priv, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_vif_priv *vif_priv = (void *)vif->drv_priv; + + if (sta) + return iwl_sta_id(sta); + + /* + * The device expects GTKs for station interfaces to be + * installed as GTKs for the AP station. If we have no + * station ID, then use the ap_sta_id in that case. + */ + if (vif->type == NL80211_IFTYPE_STATION && vif_priv->ctx) + return vif_priv->ctx->ap_sta_id; + + return IWL_INVALID_STATION; +} + +static int iwlagn_send_sta_key(struct iwl_priv *priv, + struct ieee80211_key_conf *keyconf, + u8 sta_id, u32 tkip_iv32, u16 *tkip_p1k, + u32 cmd_flags) +{ + __le16 key_flags; + struct iwl_addsta_cmd sta_cmd; + size_t to_copy; + int i; + + spin_lock_bh(&priv->sta_lock); + memcpy(&sta_cmd, &priv->stations[sta_id].sta, sizeof(sta_cmd)); + spin_unlock_bh(&priv->sta_lock); + + key_flags = cpu_to_le16(keyconf->keyidx << STA_KEY_FLG_KEYID_POS); + key_flags |= STA_KEY_FLG_MAP_KEY_MSK; + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + key_flags |= STA_KEY_FLG_CCMP; + memcpy(sta_cmd.key.key, keyconf->key, keyconf->keylen); + break; + case WLAN_CIPHER_SUITE_TKIP: + key_flags |= STA_KEY_FLG_TKIP; + sta_cmd.key.tkip_rx_tsc_byte2 = tkip_iv32; + for (i = 0; i < 5; i++) + sta_cmd.key.tkip_rx_ttak[i] = cpu_to_le16(tkip_p1k[i]); + /* keyconf may contain MIC rx/tx keys which iwl does not use */ + to_copy = min_t(size_t, sizeof(sta_cmd.key.key), keyconf->keylen); + memcpy(sta_cmd.key.key, keyconf->key, to_copy); + break; + case WLAN_CIPHER_SUITE_WEP104: + key_flags |= STA_KEY_FLG_KEY_SIZE_MSK; + fallthrough; + case WLAN_CIPHER_SUITE_WEP40: + key_flags |= STA_KEY_FLG_WEP; + memcpy(&sta_cmd.key.key[3], keyconf->key, keyconf->keylen); + break; + default: + WARN_ON(1); + return -EINVAL; + } + + if (!(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE)) + key_flags |= STA_KEY_MULTICAST_MSK; + + /* key pointer (offset) */ + sta_cmd.key.key_offset = keyconf->hw_key_idx; + + sta_cmd.key.key_flags = key_flags; + sta_cmd.mode = STA_CONTROL_MODIFY_MSK; + sta_cmd.sta.modify_mask = STA_MODIFY_KEY_MASK; + + return iwl_send_add_sta(priv, &sta_cmd, cmd_flags); +} + +void iwl_update_tkip_key(struct iwl_priv *priv, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, u32 iv32, u16 *phase1key) +{ + u8 sta_id = iwlagn_key_sta_id(priv, vif, sta); + + if (sta_id == IWL_INVALID_STATION) + return; + + if (iwl_scan_cancel(priv)) { + /* cancel scan failed, just live w/ bad key and rely + briefly on SW decryption */ + return; + } + + iwlagn_send_sta_key(priv, keyconf, sta_id, + iv32, phase1key, CMD_ASYNC); +} + +int iwl_remove_dynamic_key(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta) +{ + struct iwl_addsta_cmd sta_cmd; + u8 sta_id = iwlagn_key_sta_id(priv, ctx->vif, sta); + __le16 key_flags; + + /* if station isn't there, neither is the key */ + if (sta_id == IWL_INVALID_STATION) + return -ENOENT; + + spin_lock_bh(&priv->sta_lock); + memcpy(&sta_cmd, &priv->stations[sta_id].sta, sizeof(sta_cmd)); + if (!(priv->stations[sta_id].used & IWL_STA_UCODE_ACTIVE)) + sta_id = IWL_INVALID_STATION; + spin_unlock_bh(&priv->sta_lock); + + if (sta_id == IWL_INVALID_STATION) + return 0; + + lockdep_assert_held(&priv->mutex); + + ctx->key_mapping_keys--; + + IWL_DEBUG_WEP(priv, "Remove dynamic key: idx=%d sta=%d\n", + keyconf->keyidx, sta_id); + + if (!test_and_clear_bit(keyconf->hw_key_idx, &priv->ucode_key_table)) + IWL_ERR(priv, "offset %d not used in uCode key table.\n", + keyconf->hw_key_idx); + + key_flags = cpu_to_le16(keyconf->keyidx << STA_KEY_FLG_KEYID_POS); + key_flags |= STA_KEY_FLG_MAP_KEY_MSK | STA_KEY_FLG_NO_ENC | + STA_KEY_FLG_INVALID; + + if (!(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE)) + key_flags |= STA_KEY_MULTICAST_MSK; + + sta_cmd.key.key_flags = key_flags; + sta_cmd.key.key_offset = keyconf->hw_key_idx; + sta_cmd.sta.modify_mask = STA_MODIFY_KEY_MASK; + sta_cmd.mode = STA_CONTROL_MODIFY_MSK; + + return iwl_send_add_sta(priv, &sta_cmd, 0); +} + +int iwl_set_dynamic_key(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta) +{ + struct ieee80211_key_seq seq; + u16 p1k[5]; + int ret; + u8 sta_id = iwlagn_key_sta_id(priv, ctx->vif, sta); + const u8 *addr; + + if (sta_id == IWL_INVALID_STATION) + return -EINVAL; + + lockdep_assert_held(&priv->mutex); + + keyconf->hw_key_idx = iwl_get_free_ucode_key_offset(priv); + if (keyconf->hw_key_idx == WEP_INVALID_OFFSET) + return -ENOSPC; + + ctx->key_mapping_keys++; + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_TKIP: + if (sta) + addr = sta->addr; + else /* station mode case only */ + addr = ctx->active.bssid_addr; + + /* pre-fill phase 1 key into device cache */ + ieee80211_get_key_rx_seq(keyconf, 0, &seq); + ieee80211_get_tkip_rx_p1k(keyconf, addr, seq.tkip.iv32, p1k); + ret = iwlagn_send_sta_key(priv, keyconf, sta_id, + seq.tkip.iv32, p1k, 0); + break; + case WLAN_CIPHER_SUITE_CCMP: + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + ret = iwlagn_send_sta_key(priv, keyconf, sta_id, + 0, NULL, 0); + break; + default: + IWL_ERR(priv, "Unknown cipher %x\n", keyconf->cipher); + ret = -EINVAL; + } + + if (ret) { + ctx->key_mapping_keys--; + clear_bit(keyconf->hw_key_idx, &priv->ucode_key_table); + } + + IWL_DEBUG_WEP(priv, "Set dynamic key: cipher=%x len=%d idx=%d sta=%pM ret=%d\n", + keyconf->cipher, keyconf->keylen, keyconf->keyidx, + sta ? sta->addr : NULL, ret); + + return ret; +} + +/* + * iwlagn_alloc_bcast_station - add broadcast station into driver's station table. + * + * This adds the broadcast station into the driver's station table + * and marks it driver active, so that it will be restored to the + * device at the next best time. + */ +int iwlagn_alloc_bcast_station(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + struct iwl_link_quality_cmd *link_cmd; + u8 sta_id; + + spin_lock_bh(&priv->sta_lock); + sta_id = iwl_prep_station(priv, ctx, iwl_bcast_addr, false, NULL); + if (sta_id == IWL_INVALID_STATION) { + IWL_ERR(priv, "Unable to prepare broadcast station\n"); + spin_unlock_bh(&priv->sta_lock); + + return -EINVAL; + } + + priv->stations[sta_id].used |= IWL_STA_DRIVER_ACTIVE; + priv->stations[sta_id].used |= IWL_STA_BCAST; + spin_unlock_bh(&priv->sta_lock); + + link_cmd = iwl_sta_alloc_lq(priv, ctx, sta_id); + if (!link_cmd) { + IWL_ERR(priv, + "Unable to initialize rate scaling for bcast station.\n"); + return -ENOMEM; + } + + spin_lock_bh(&priv->sta_lock); + priv->stations[sta_id].lq = link_cmd; + spin_unlock_bh(&priv->sta_lock); + + return 0; +} + +/* + * iwl_update_bcast_station - update broadcast station's LQ command + * + * Only used by iwlagn. Placed here to have all bcast station management + * code together. + */ +int iwl_update_bcast_station(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + struct iwl_link_quality_cmd *link_cmd; + u8 sta_id = ctx->bcast_sta_id; + + link_cmd = iwl_sta_alloc_lq(priv, ctx, sta_id); + if (!link_cmd) { + IWL_ERR(priv, "Unable to initialize rate scaling for bcast station.\n"); + return -ENOMEM; + } + + spin_lock_bh(&priv->sta_lock); + if (priv->stations[sta_id].lq) + kfree(priv->stations[sta_id].lq); + else + IWL_DEBUG_INFO(priv, "Bcast station rate scaling has not been initialized yet.\n"); + priv->stations[sta_id].lq = link_cmd; + spin_unlock_bh(&priv->sta_lock); + + return 0; +} + +int iwl_update_bcast_stations(struct iwl_priv *priv) +{ + struct iwl_rxon_context *ctx; + int ret = 0; + + for_each_context(priv, ctx) { + ret = iwl_update_bcast_station(priv, ctx); + if (ret) + break; + } + + return ret; +} + +/* + * iwl_sta_tx_modify_enable_tid - Enable Tx for this TID in station table + */ +int iwl_sta_tx_modify_enable_tid(struct iwl_priv *priv, int sta_id, int tid) +{ + struct iwl_addsta_cmd sta_cmd; + + lockdep_assert_held(&priv->mutex); + + /* Remove "disable" flag, to enable Tx for this TID */ + spin_lock_bh(&priv->sta_lock); + priv->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_TID_DISABLE_TX; + priv->stations[sta_id].sta.tid_disable_tx &= cpu_to_le16(~(1 << tid)); + priv->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + memcpy(&sta_cmd, &priv->stations[sta_id].sta, sizeof(struct iwl_addsta_cmd)); + spin_unlock_bh(&priv->sta_lock); + + return iwl_send_add_sta(priv, &sta_cmd, 0); +} + +int iwl_sta_rx_agg_start(struct iwl_priv *priv, struct ieee80211_sta *sta, + int tid, u16 ssn) +{ + int sta_id; + struct iwl_addsta_cmd sta_cmd; + + lockdep_assert_held(&priv->mutex); + + sta_id = iwl_sta_id(sta); + if (sta_id == IWL_INVALID_STATION) + return -ENXIO; + + spin_lock_bh(&priv->sta_lock); + priv->stations[sta_id].sta.station_flags_msk = 0; + priv->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_ADDBA_TID_MSK; + priv->stations[sta_id].sta.add_immediate_ba_tid = (u8)tid; + priv->stations[sta_id].sta.add_immediate_ba_ssn = cpu_to_le16(ssn); + priv->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + memcpy(&sta_cmd, &priv->stations[sta_id].sta, sizeof(struct iwl_addsta_cmd)); + spin_unlock_bh(&priv->sta_lock); + + return iwl_send_add_sta(priv, &sta_cmd, 0); +} + +int iwl_sta_rx_agg_stop(struct iwl_priv *priv, struct ieee80211_sta *sta, + int tid) +{ + int sta_id; + struct iwl_addsta_cmd sta_cmd; + + lockdep_assert_held(&priv->mutex); + + sta_id = iwl_sta_id(sta); + if (sta_id == IWL_INVALID_STATION) { + IWL_ERR(priv, "Invalid station for AGG tid %d\n", tid); + return -ENXIO; + } + + spin_lock_bh(&priv->sta_lock); + priv->stations[sta_id].sta.station_flags_msk = 0; + priv->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_DELBA_TID_MSK; + priv->stations[sta_id].sta.remove_immediate_ba_tid = (u8)tid; + priv->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + memcpy(&sta_cmd, &priv->stations[sta_id].sta, sizeof(struct iwl_addsta_cmd)); + spin_unlock_bh(&priv->sta_lock); + + return iwl_send_add_sta(priv, &sta_cmd, 0); +} + + + +void iwl_sta_modify_sleep_tx_count(struct iwl_priv *priv, int sta_id, int cnt) +{ + struct iwl_addsta_cmd cmd = { + .mode = STA_CONTROL_MODIFY_MSK, + .station_flags = STA_FLG_PWR_SAVE_MSK, + .station_flags_msk = STA_FLG_PWR_SAVE_MSK, + .sta.sta_id = sta_id, + .sta.modify_mask = STA_MODIFY_SLEEP_TX_COUNT_MSK, + .sleep_tx_count = cpu_to_le16(cnt), + }; + + iwl_send_add_sta(priv, &cmd, CMD_ASYNC); +} diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/tt.c b/drivers/net/wireless/intel/iwlwifi/dvm/tt.c new file mode 100644 index 000000000..43e8d04d5 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/tt.c @@ -0,0 +1,668 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2007 - 2014 Intel Corporation. All rights reserved. + * Copyright (C) 2018, 2020 Intel Corporation + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + *****************************************************************************/ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <net/mac80211.h> +#include "iwl-io.h" +#include "iwl-modparams.h" +#include "iwl-debug.h" +#include "agn.h" +#include "dev.h" +#include "commands.h" +#include "tt.h" + +/* default Thermal Throttling transaction table + * Current state | Throttling Down | Throttling Up + *============================================================================= + * Condition Nxt State Condition Nxt State Condition Nxt State + *----------------------------------------------------------------------------- + * IWL_TI_0 T >= 114 CT_KILL 114>T>=105 TI_1 N/A N/A + * IWL_TI_1 T >= 114 CT_KILL 114>T>=110 TI_2 T<=95 TI_0 + * IWL_TI_2 T >= 114 CT_KILL T<=100 TI_1 + * IWL_CT_KILL N/A N/A N/A N/A T<=95 TI_0 + *============================================================================= + */ +static const struct iwl_tt_trans tt_range_0[IWL_TI_STATE_MAX - 1] = { + {IWL_TI_0, IWL_ABSOLUTE_ZERO, 104}, + {IWL_TI_1, 105, CT_KILL_THRESHOLD - 1}, + {IWL_TI_CT_KILL, CT_KILL_THRESHOLD, IWL_ABSOLUTE_MAX} +}; +static const struct iwl_tt_trans tt_range_1[IWL_TI_STATE_MAX - 1] = { + {IWL_TI_0, IWL_ABSOLUTE_ZERO, 95}, + {IWL_TI_2, 110, CT_KILL_THRESHOLD - 1}, + {IWL_TI_CT_KILL, CT_KILL_THRESHOLD, IWL_ABSOLUTE_MAX} +}; +static const struct iwl_tt_trans tt_range_2[IWL_TI_STATE_MAX - 1] = { + {IWL_TI_1, IWL_ABSOLUTE_ZERO, 100}, + {IWL_TI_CT_KILL, CT_KILL_THRESHOLD, IWL_ABSOLUTE_MAX}, + {IWL_TI_CT_KILL, CT_KILL_THRESHOLD, IWL_ABSOLUTE_MAX} +}; +static const struct iwl_tt_trans tt_range_3[IWL_TI_STATE_MAX - 1] = { + {IWL_TI_0, IWL_ABSOLUTE_ZERO, CT_KILL_EXIT_THRESHOLD}, + {IWL_TI_CT_KILL, CT_KILL_EXIT_THRESHOLD + 1, IWL_ABSOLUTE_MAX}, + {IWL_TI_CT_KILL, CT_KILL_EXIT_THRESHOLD + 1, IWL_ABSOLUTE_MAX} +}; + +/* Advance Thermal Throttling default restriction table */ +static const struct iwl_tt_restriction restriction_range[IWL_TI_STATE_MAX] = { + {IWL_ANT_OK_MULTI, IWL_ANT_OK_MULTI, true }, + {IWL_ANT_OK_SINGLE, IWL_ANT_OK_MULTI, true }, + {IWL_ANT_OK_SINGLE, IWL_ANT_OK_SINGLE, false }, + {IWL_ANT_OK_NONE, IWL_ANT_OK_NONE, false } +}; + +bool iwl_tt_is_low_power_state(struct iwl_priv *priv) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + + if (tt->state >= IWL_TI_1) + return true; + return false; +} + +u8 iwl_tt_current_power_mode(struct iwl_priv *priv) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + + return tt->tt_power_mode; +} + +bool iwl_ht_enabled(struct iwl_priv *priv) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + struct iwl_tt_restriction *restriction; + + if (!priv->thermal_throttle.advanced_tt) + return true; + restriction = tt->restriction + tt->state; + return restriction->is_ht; +} + +static bool iwl_within_ct_kill_margin(struct iwl_priv *priv) +{ + s32 temp = priv->temperature; /* degrees CELSIUS except specified */ + bool within_margin = false; + + if (!priv->thermal_throttle.advanced_tt) + within_margin = ((temp + IWL_TT_CT_KILL_MARGIN) >= + CT_KILL_THRESHOLD_LEGACY) ? true : false; + else + within_margin = ((temp + IWL_TT_CT_KILL_MARGIN) >= + CT_KILL_THRESHOLD) ? true : false; + return within_margin; +} + +bool iwl_check_for_ct_kill(struct iwl_priv *priv) +{ + bool is_ct_kill = false; + + if (iwl_within_ct_kill_margin(priv)) { + iwl_tt_enter_ct_kill(priv); + is_ct_kill = true; + } + return is_ct_kill; +} + +enum iwl_antenna_ok iwl_tx_ant_restriction(struct iwl_priv *priv) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + struct iwl_tt_restriction *restriction; + + if (!priv->thermal_throttle.advanced_tt) + return IWL_ANT_OK_MULTI; + restriction = tt->restriction + tt->state; + return restriction->tx_stream; +} + +enum iwl_antenna_ok iwl_rx_ant_restriction(struct iwl_priv *priv) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + struct iwl_tt_restriction *restriction; + + if (!priv->thermal_throttle.advanced_tt) + return IWL_ANT_OK_MULTI; + restriction = tt->restriction + tt->state; + return restriction->rx_stream; +} + +#define CT_KILL_EXIT_DURATION (5) /* 5 seconds duration */ +#define CT_KILL_WAITING_DURATION (300) /* 300ms duration */ + +/* + * toggle the bit to wake up uCode and check the temperature + * if the temperature is below CT, uCode will stay awake and send card + * state notification with CT_KILL bit clear to inform Thermal Throttling + * Management to change state. Otherwise, uCode will go back to sleep + * without doing anything, driver should continue the 5 seconds timer + * to wake up uCode for temperature check until temperature drop below CT + */ +static void iwl_tt_check_exit_ct_kill(struct timer_list *t) +{ + struct iwl_priv *priv = from_timer(priv, t, + thermal_throttle.ct_kill_exit_tm); + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + if (tt->state == IWL_TI_CT_KILL) { + if (priv->thermal_throttle.ct_kill_toggle) { + iwl_write32(priv->trans, CSR_UCODE_DRV_GP1_CLR, + CSR_UCODE_DRV_GP1_REG_BIT_CT_KILL_EXIT); + priv->thermal_throttle.ct_kill_toggle = false; + } else { + iwl_write32(priv->trans, CSR_UCODE_DRV_GP1_SET, + CSR_UCODE_DRV_GP1_REG_BIT_CT_KILL_EXIT); + priv->thermal_throttle.ct_kill_toggle = true; + } + iwl_read32(priv->trans, CSR_UCODE_DRV_GP1); + if (iwl_trans_grab_nic_access(priv->trans)) + iwl_trans_release_nic_access(priv->trans); + + /* Reschedule the ct_kill timer to occur in + * CT_KILL_EXIT_DURATION seconds to ensure we get a + * thermal update */ + IWL_DEBUG_TEMP(priv, "schedule ct_kill exit timer\n"); + mod_timer(&priv->thermal_throttle.ct_kill_exit_tm, + jiffies + CT_KILL_EXIT_DURATION * HZ); + } +} + +static void iwl_perform_ct_kill_task(struct iwl_priv *priv, + bool stop) +{ + if (stop) { + IWL_DEBUG_TEMP(priv, "Stop all queues\n"); + if (priv->mac80211_registered) + ieee80211_stop_queues(priv->hw); + IWL_DEBUG_TEMP(priv, + "Schedule 5 seconds CT_KILL Timer\n"); + mod_timer(&priv->thermal_throttle.ct_kill_exit_tm, + jiffies + CT_KILL_EXIT_DURATION * HZ); + } else { + IWL_DEBUG_TEMP(priv, "Wake all queues\n"); + if (priv->mac80211_registered) + ieee80211_wake_queues(priv->hw); + } +} + +static void iwl_tt_ready_for_ct_kill(struct timer_list *t) +{ + struct iwl_priv *priv = from_timer(priv, t, + thermal_throttle.ct_kill_waiting_tm); + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + /* temperature timer expired, ready to go into CT_KILL state */ + if (tt->state != IWL_TI_CT_KILL) { + IWL_DEBUG_TEMP(priv, "entering CT_KILL state when " + "temperature timer expired\n"); + tt->state = IWL_TI_CT_KILL; + set_bit(STATUS_CT_KILL, &priv->status); + iwl_perform_ct_kill_task(priv, true); + } +} + +static void iwl_prepare_ct_kill_task(struct iwl_priv *priv) +{ + IWL_DEBUG_TEMP(priv, "Prepare to enter IWL_TI_CT_KILL\n"); + /* make request to retrieve statistics information */ + iwl_send_statistics_request(priv, 0, false); + /* Reschedule the ct_kill wait timer */ + mod_timer(&priv->thermal_throttle.ct_kill_waiting_tm, + jiffies + msecs_to_jiffies(CT_KILL_WAITING_DURATION)); +} + +#define IWL_MINIMAL_POWER_THRESHOLD (CT_KILL_THRESHOLD_LEGACY) +#define IWL_REDUCED_PERFORMANCE_THRESHOLD_2 (100) +#define IWL_REDUCED_PERFORMANCE_THRESHOLD_1 (90) + +/* + * Legacy thermal throttling + * 1) Avoid NIC destruction due to high temperatures + * Chip will identify dangerously high temperatures that can + * harm the device and will power down + * 2) Avoid the NIC power down due to high temperature + * Throttle early enough to lower the power consumption before + * drastic steps are needed + */ +static void iwl_legacy_tt_handler(struct iwl_priv *priv, s32 temp, bool force) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + enum iwl_tt_state old_state; + +#ifdef CONFIG_IWLWIFI_DEBUG + if ((tt->tt_previous_temp) && + (temp > tt->tt_previous_temp) && + ((temp - tt->tt_previous_temp) > + IWL_TT_INCREASE_MARGIN)) { + IWL_DEBUG_TEMP(priv, + "Temperature increase %d degree Celsius\n", + (temp - tt->tt_previous_temp)); + } +#endif + old_state = tt->state; + /* in Celsius */ + if (temp >= IWL_MINIMAL_POWER_THRESHOLD) + tt->state = IWL_TI_CT_KILL; + else if (temp >= IWL_REDUCED_PERFORMANCE_THRESHOLD_2) + tt->state = IWL_TI_2; + else if (temp >= IWL_REDUCED_PERFORMANCE_THRESHOLD_1) + tt->state = IWL_TI_1; + else + tt->state = IWL_TI_0; + +#ifdef CONFIG_IWLWIFI_DEBUG + tt->tt_previous_temp = temp; +#endif + /* stop ct_kill_waiting_tm timer */ + del_timer_sync(&priv->thermal_throttle.ct_kill_waiting_tm); + if (tt->state != old_state) { + switch (tt->state) { + case IWL_TI_0: + /* + * When the system is ready to go back to IWL_TI_0 + * we only have to call iwl_power_update_mode() to + * do so. + */ + break; + case IWL_TI_1: + tt->tt_power_mode = IWL_POWER_INDEX_3; + break; + case IWL_TI_2: + tt->tt_power_mode = IWL_POWER_INDEX_4; + break; + default: + tt->tt_power_mode = IWL_POWER_INDEX_5; + break; + } + mutex_lock(&priv->mutex); + if (old_state == IWL_TI_CT_KILL) + clear_bit(STATUS_CT_KILL, &priv->status); + if (tt->state != IWL_TI_CT_KILL && + iwl_power_update_mode(priv, true)) { + /* TT state not updated + * try again during next temperature read + */ + if (old_state == IWL_TI_CT_KILL) + set_bit(STATUS_CT_KILL, &priv->status); + tt->state = old_state; + IWL_ERR(priv, "Cannot update power mode, " + "TT state not updated\n"); + } else { + if (tt->state == IWL_TI_CT_KILL) { + if (force) { + set_bit(STATUS_CT_KILL, &priv->status); + iwl_perform_ct_kill_task(priv, true); + } else { + iwl_prepare_ct_kill_task(priv); + tt->state = old_state; + } + } else if (old_state == IWL_TI_CT_KILL) { + iwl_perform_ct_kill_task(priv, false); + } + IWL_DEBUG_TEMP(priv, "Temperature state changed %u\n", + tt->state); + IWL_DEBUG_TEMP(priv, "Power Index change to %u\n", + tt->tt_power_mode); + } + mutex_unlock(&priv->mutex); + } +} + +/* + * Advance thermal throttling + * 1) Avoid NIC destruction due to high temperatures + * Chip will identify dangerously high temperatures that can + * harm the device and will power down + * 2) Avoid the NIC power down due to high temperature + * Throttle early enough to lower the power consumption before + * drastic steps are needed + * Actions include relaxing the power down sleep thresholds and + * decreasing the number of TX streams + * 3) Avoid throughput performance impact as much as possible + * + *============================================================================= + * Condition Nxt State Condition Nxt State Condition Nxt State + *----------------------------------------------------------------------------- + * IWL_TI_0 T >= 114 CT_KILL 114>T>=105 TI_1 N/A N/A + * IWL_TI_1 T >= 114 CT_KILL 114>T>=110 TI_2 T<=95 TI_0 + * IWL_TI_2 T >= 114 CT_KILL T<=100 TI_1 + * IWL_CT_KILL N/A N/A N/A N/A T<=95 TI_0 + *============================================================================= + */ +static void iwl_advance_tt_handler(struct iwl_priv *priv, s32 temp, bool force) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + int i; + bool changed = false; + enum iwl_tt_state old_state; + struct iwl_tt_trans *transaction; + + old_state = tt->state; + for (i = 0; i < IWL_TI_STATE_MAX - 1; i++) { + /* based on the current TT state, + * find the curresponding transaction table + * each table has (IWL_TI_STATE_MAX - 1) entries + * tt->transaction + ((old_state * (IWL_TI_STATE_MAX - 1)) + * will advance to the correct table. + * then based on the current temperature + * find the next state need to transaction to + * go through all the possible (IWL_TI_STATE_MAX - 1) entries + * in the current table to see if transaction is needed + */ + transaction = tt->transaction + + ((old_state * (IWL_TI_STATE_MAX - 1)) + i); + if (temp >= transaction->tt_low && + temp <= transaction->tt_high) { +#ifdef CONFIG_IWLWIFI_DEBUG + if ((tt->tt_previous_temp) && + (temp > tt->tt_previous_temp) && + ((temp - tt->tt_previous_temp) > + IWL_TT_INCREASE_MARGIN)) { + IWL_DEBUG_TEMP(priv, + "Temperature increase %d " + "degree Celsius\n", + (temp - tt->tt_previous_temp)); + } + tt->tt_previous_temp = temp; +#endif + if (old_state != + transaction->next_state) { + changed = true; + tt->state = + transaction->next_state; + } + break; + } + } + /* stop ct_kill_waiting_tm timer */ + del_timer_sync(&priv->thermal_throttle.ct_kill_waiting_tm); + if (changed) { + if (tt->state >= IWL_TI_1) { + /* force PI = IWL_POWER_INDEX_5 in the case of TI > 0 */ + tt->tt_power_mode = IWL_POWER_INDEX_5; + + if (!iwl_ht_enabled(priv)) { + struct iwl_rxon_context *ctx; + + for_each_context(priv, ctx) { + struct iwl_rxon_cmd *rxon; + + rxon = &ctx->staging; + + /* disable HT */ + rxon->flags &= ~( + RXON_FLG_CHANNEL_MODE_MSK | + RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK | + RXON_FLG_HT40_PROT_MSK | + RXON_FLG_HT_PROT_MSK); + } + } else { + /* check HT capability and set + * according to the system HT capability + * in case get disabled before */ + iwl_set_rxon_ht(priv, &priv->current_ht_config); + } + + } else { + /* + * restore system power setting -- it will be + * recalculated automatically. + */ + + /* check HT capability and set + * according to the system HT capability + * in case get disabled before */ + iwl_set_rxon_ht(priv, &priv->current_ht_config); + } + mutex_lock(&priv->mutex); + if (old_state == IWL_TI_CT_KILL) + clear_bit(STATUS_CT_KILL, &priv->status); + if (tt->state != IWL_TI_CT_KILL && + iwl_power_update_mode(priv, true)) { + /* TT state not updated + * try again during next temperature read + */ + IWL_ERR(priv, "Cannot update power mode, " + "TT state not updated\n"); + if (old_state == IWL_TI_CT_KILL) + set_bit(STATUS_CT_KILL, &priv->status); + tt->state = old_state; + } else { + IWL_DEBUG_TEMP(priv, + "Thermal Throttling to new state: %u\n", + tt->state); + if (old_state != IWL_TI_CT_KILL && + tt->state == IWL_TI_CT_KILL) { + if (force) { + IWL_DEBUG_TEMP(priv, + "Enter IWL_TI_CT_KILL\n"); + set_bit(STATUS_CT_KILL, &priv->status); + iwl_perform_ct_kill_task(priv, true); + } else { + tt->state = old_state; + iwl_prepare_ct_kill_task(priv); + } + } else if (old_state == IWL_TI_CT_KILL && + tt->state != IWL_TI_CT_KILL) { + IWL_DEBUG_TEMP(priv, "Exit IWL_TI_CT_KILL\n"); + iwl_perform_ct_kill_task(priv, false); + } + } + mutex_unlock(&priv->mutex); + } +} + +/* Card State Notification indicated reach critical temperature + * if PSP not enable, no Thermal Throttling function will be performed + * just set the GP1 bit to acknowledge the event + * otherwise, go into IWL_TI_CT_KILL state + * since Card State Notification will not provide any temperature reading + * for Legacy mode + * so just pass the CT_KILL temperature to iwl_legacy_tt_handler() + * for advance mode + * pass CT_KILL_THRESHOLD+1 to make sure move into IWL_TI_CT_KILL state + */ +static void iwl_bg_ct_enter(struct work_struct *work) +{ + struct iwl_priv *priv = container_of(work, struct iwl_priv, ct_enter); + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + if (!iwl_is_ready(priv)) + return; + + if (tt->state != IWL_TI_CT_KILL) { + IWL_ERR(priv, "Device reached critical temperature " + "- ucode going to sleep!\n"); + if (!priv->thermal_throttle.advanced_tt) + iwl_legacy_tt_handler(priv, + IWL_MINIMAL_POWER_THRESHOLD, + true); + else + iwl_advance_tt_handler(priv, + CT_KILL_THRESHOLD + 1, true); + } +} + +/* Card State Notification indicated out of critical temperature + * since Card State Notification will not provide any temperature reading + * so pass the IWL_REDUCED_PERFORMANCE_THRESHOLD_2 temperature + * to iwl_legacy_tt_handler() to get out of IWL_CT_KILL state + */ +static void iwl_bg_ct_exit(struct work_struct *work) +{ + struct iwl_priv *priv = container_of(work, struct iwl_priv, ct_exit); + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + if (!iwl_is_ready(priv)) + return; + + /* stop ct_kill_exit_tm timer */ + del_timer_sync(&priv->thermal_throttle.ct_kill_exit_tm); + + if (tt->state == IWL_TI_CT_KILL) { + IWL_ERR(priv, + "Device temperature below critical" + "- ucode awake!\n"); + /* + * exit from CT_KILL state + * reset the current temperature reading + */ + priv->temperature = 0; + if (!priv->thermal_throttle.advanced_tt) + iwl_legacy_tt_handler(priv, + IWL_REDUCED_PERFORMANCE_THRESHOLD_2, + true); + else + iwl_advance_tt_handler(priv, CT_KILL_EXIT_THRESHOLD, + true); + } +} + +void iwl_tt_enter_ct_kill(struct iwl_priv *priv) +{ + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + IWL_DEBUG_TEMP(priv, "Queueing critical temperature enter.\n"); + queue_work(priv->workqueue, &priv->ct_enter); +} + +void iwl_tt_exit_ct_kill(struct iwl_priv *priv) +{ + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + IWL_DEBUG_TEMP(priv, "Queueing critical temperature exit.\n"); + queue_work(priv->workqueue, &priv->ct_exit); +} + +static void iwl_bg_tt_work(struct work_struct *work) +{ + struct iwl_priv *priv = container_of(work, struct iwl_priv, tt_work); + s32 temp = priv->temperature; /* degrees CELSIUS except specified */ + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + if (!priv->thermal_throttle.advanced_tt) + iwl_legacy_tt_handler(priv, temp, false); + else + iwl_advance_tt_handler(priv, temp, false); +} + +void iwl_tt_handler(struct iwl_priv *priv) +{ + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + IWL_DEBUG_TEMP(priv, "Queueing thermal throttling work.\n"); + queue_work(priv->workqueue, &priv->tt_work); +} + +/* Thermal throttling initialization + * For advance thermal throttling: + * Initialize Thermal Index and temperature threshold table + * Initialize thermal throttling restriction table + */ +void iwl_tt_initialize(struct iwl_priv *priv) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + int size = sizeof(struct iwl_tt_trans) * (IWL_TI_STATE_MAX - 1); + struct iwl_tt_trans *transaction; + + IWL_DEBUG_TEMP(priv, "Initialize Thermal Throttling\n"); + + memset(tt, 0, sizeof(struct iwl_tt_mgmt)); + + tt->state = IWL_TI_0; + timer_setup(&priv->thermal_throttle.ct_kill_exit_tm, + iwl_tt_check_exit_ct_kill, 0); + timer_setup(&priv->thermal_throttle.ct_kill_waiting_tm, + iwl_tt_ready_for_ct_kill, 0); + /* setup deferred ct kill work */ + INIT_WORK(&priv->tt_work, iwl_bg_tt_work); + INIT_WORK(&priv->ct_enter, iwl_bg_ct_enter); + INIT_WORK(&priv->ct_exit, iwl_bg_ct_exit); + + if (priv->lib->adv_thermal_throttle) { + IWL_DEBUG_TEMP(priv, "Advanced Thermal Throttling\n"); + tt->restriction = kcalloc(IWL_TI_STATE_MAX, + sizeof(struct iwl_tt_restriction), + GFP_KERNEL); + tt->transaction = kcalloc(IWL_TI_STATE_MAX * + (IWL_TI_STATE_MAX - 1), + sizeof(struct iwl_tt_trans), + GFP_KERNEL); + if (!tt->restriction || !tt->transaction) { + IWL_ERR(priv, "Fallback to Legacy Throttling\n"); + priv->thermal_throttle.advanced_tt = false; + kfree(tt->restriction); + tt->restriction = NULL; + kfree(tt->transaction); + tt->transaction = NULL; + } else { + transaction = tt->transaction + + (IWL_TI_0 * (IWL_TI_STATE_MAX - 1)); + memcpy(transaction, &tt_range_0[0], size); + transaction = tt->transaction + + (IWL_TI_1 * (IWL_TI_STATE_MAX - 1)); + memcpy(transaction, &tt_range_1[0], size); + transaction = tt->transaction + + (IWL_TI_2 * (IWL_TI_STATE_MAX - 1)); + memcpy(transaction, &tt_range_2[0], size); + transaction = tt->transaction + + (IWL_TI_CT_KILL * (IWL_TI_STATE_MAX - 1)); + memcpy(transaction, &tt_range_3[0], size); + size = sizeof(struct iwl_tt_restriction) * + IWL_TI_STATE_MAX; + memcpy(tt->restriction, + &restriction_range[0], size); + priv->thermal_throttle.advanced_tt = true; + } + } else { + IWL_DEBUG_TEMP(priv, "Legacy Thermal Throttling\n"); + priv->thermal_throttle.advanced_tt = false; + } +} + +/* cleanup thermal throttling management related memory and timer */ +void iwl_tt_exit(struct iwl_priv *priv) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + + /* stop ct_kill_exit_tm timer if activated */ + del_timer_sync(&priv->thermal_throttle.ct_kill_exit_tm); + /* stop ct_kill_waiting_tm timer if activated */ + del_timer_sync(&priv->thermal_throttle.ct_kill_waiting_tm); + cancel_work_sync(&priv->tt_work); + cancel_work_sync(&priv->ct_enter); + cancel_work_sync(&priv->ct_exit); + + if (priv->thermal_throttle.advanced_tt) { + /* free advance thermal throttling memory */ + kfree(tt->restriction); + tt->restriction = NULL; + kfree(tt->transaction); + tt->transaction = NULL; + } +} diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/tt.h b/drivers/net/wireless/intel/iwlwifi/dvm/tt.h new file mode 100644 index 000000000..7ace052fc --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/tt.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + * + * Copyright(c) 2007 - 2014 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + *****************************************************************************/ +#ifndef __iwl_tt_setting_h__ +#define __iwl_tt_setting_h__ + +#include "commands.h" + +#define IWL_ABSOLUTE_ZERO 0 +#define IWL_ABSOLUTE_MAX 0xFFFFFFFF +#define IWL_TT_INCREASE_MARGIN 5 +#define IWL_TT_CT_KILL_MARGIN 3 + +enum iwl_antenna_ok { + IWL_ANT_OK_NONE, + IWL_ANT_OK_SINGLE, + IWL_ANT_OK_MULTI, +}; + +/* Thermal Throttling State Machine states */ +enum iwl_tt_state { + IWL_TI_0, /* normal temperature, system power state */ + IWL_TI_1, /* high temperature detect, low power state */ + IWL_TI_2, /* higher temperature detected, lower power state */ + IWL_TI_CT_KILL, /* critical temperature detected, lowest power state */ + IWL_TI_STATE_MAX +}; + +/** + * struct iwl_tt_restriction - Thermal Throttling restriction table + * @tx_stream: number of tx stream allowed + * @is_ht: ht enable/disable + * @rx_stream: number of rx stream allowed + * + * This table is used by advance thermal throttling management + * based on the current thermal throttling state, and determines + * the number of tx/rx streams and the status of HT operation. + */ +struct iwl_tt_restriction { + enum iwl_antenna_ok tx_stream; + enum iwl_antenna_ok rx_stream; + bool is_ht; +}; + +/** + * struct iwl_tt_trans - Thermal Throttling transaction table + * @next_state: next thermal throttling mode + * @tt_low: low temperature threshold to change state + * @tt_high: high temperature threshold to change state + * + * This is used by the advanced thermal throttling algorithm + * to determine the next thermal state to go based on the + * current temperature. + */ +struct iwl_tt_trans { + enum iwl_tt_state next_state; + u32 tt_low; + u32 tt_high; +}; + +/** + * struct iwl_tt_mgnt - Thermal Throttling Management structure + * @advanced_tt: advanced thermal throttle required + * @state: current Thermal Throttling state + * @tt_power_mode: Thermal Throttling power mode index + * being used to set power level when + * when thermal throttling state != IWL_TI_0 + * the tt_power_mode should set to different + * power mode based on the current tt state + * @tt_previous_temperature: last measured temperature + * @iwl_tt_restriction: ptr to restriction tbl, used by advance + * thermal throttling to determine how many tx/rx streams + * should be used in tt state; and can HT be enabled or not + * @iwl_tt_trans: ptr to adv trans table, used by advance thermal throttling + * state transaction + * @ct_kill_toggle: used to toggle the CSR bit when checking uCode temperature + * @ct_kill_exit_tm: timer to exit thermal kill + */ +struct iwl_tt_mgmt { + enum iwl_tt_state state; + bool advanced_tt; + u8 tt_power_mode; + bool ct_kill_toggle; +#ifdef CONFIG_IWLWIFI_DEBUG + s32 tt_previous_temp; +#endif + struct iwl_tt_restriction *restriction; + struct iwl_tt_trans *transaction; + struct timer_list ct_kill_exit_tm; + struct timer_list ct_kill_waiting_tm; +}; + +u8 iwl_tt_current_power_mode(struct iwl_priv *priv); +bool iwl_tt_is_low_power_state(struct iwl_priv *priv); +bool iwl_ht_enabled(struct iwl_priv *priv); +enum iwl_antenna_ok iwl_tx_ant_restriction(struct iwl_priv *priv); +enum iwl_antenna_ok iwl_rx_ant_restriction(struct iwl_priv *priv); +void iwl_tt_enter_ct_kill(struct iwl_priv *priv); +void iwl_tt_exit_ct_kill(struct iwl_priv *priv); +void iwl_tt_handler(struct iwl_priv *priv); +void iwl_tt_initialize(struct iwl_priv *priv); +void iwl_tt_exit(struct iwl_priv *priv); + +#endif /* __iwl_tt_setting_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/tx.c b/drivers/net/wireless/intel/iwlwifi/dvm/tx.c new file mode 100644 index 000000000..ca1daec64 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/tx.c @@ -0,0 +1,1390 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2008 - 2014 Intel Corporation. All rights reserved. + * Copyright (C) 2019 Intel Corporation + * Copyright (C) 2023 Intel Corporation + *****************************************************************************/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/ieee80211.h> +#include "iwl-io.h" +#include "iwl-trans.h" +#include "iwl-agn-hw.h" +#include "dev.h" +#include "agn.h" + +static const u8 tid_to_ac[] = { + IEEE80211_AC_BE, + IEEE80211_AC_BK, + IEEE80211_AC_BK, + IEEE80211_AC_BE, + IEEE80211_AC_VI, + IEEE80211_AC_VI, + IEEE80211_AC_VO, + IEEE80211_AC_VO, +}; + +static void iwlagn_tx_cmd_protection(struct iwl_priv *priv, + struct ieee80211_tx_info *info, + __le16 fc, __le32 *tx_flags) +{ + if (info->control.rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS || + info->control.rates[0].flags & IEEE80211_TX_RC_USE_CTS_PROTECT || + info->flags & IEEE80211_TX_CTL_AMPDU) + *tx_flags |= TX_CMD_FLG_PROT_REQUIRE_MSK; +} + +/* + * handle build REPLY_TX command notification. + */ +static void iwlagn_tx_cmd_build_basic(struct iwl_priv *priv, + struct sk_buff *skb, + struct iwl_tx_cmd *tx_cmd, + struct ieee80211_tx_info *info, + struct ieee80211_hdr *hdr, u8 sta_id) +{ + __le16 fc = hdr->frame_control; + __le32 tx_flags = tx_cmd->tx_flags; + + tx_cmd->stop_time.life_time = TX_CMD_LIFE_TIME_INFINITE; + + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK)) + tx_flags |= TX_CMD_FLG_ACK_MSK; + else + tx_flags &= ~TX_CMD_FLG_ACK_MSK; + + if (ieee80211_is_probe_resp(fc)) + tx_flags |= TX_CMD_FLG_TSF_MSK; + else if (ieee80211_is_back_req(fc)) + tx_flags |= TX_CMD_FLG_ACK_MSK | TX_CMD_FLG_IMM_BA_RSP_MASK; + else if (info->band == NL80211_BAND_2GHZ && + priv->lib->bt_params && + priv->lib->bt_params->advanced_bt_coexist && + (ieee80211_is_auth(fc) || ieee80211_is_assoc_req(fc) || + ieee80211_is_reassoc_req(fc) || + info->control.flags & IEEE80211_TX_CTRL_PORT_CTRL_PROTO)) + tx_flags |= TX_CMD_FLG_IGNORE_BT; + + + tx_cmd->sta_id = sta_id; + if (ieee80211_has_morefrags(fc)) + tx_flags |= TX_CMD_FLG_MORE_FRAG_MSK; + + if (ieee80211_is_data_qos(fc)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + tx_cmd->tid_tspec = qc[0] & 0xf; + tx_flags &= ~TX_CMD_FLG_SEQ_CTL_MSK; + } else { + tx_cmd->tid_tspec = IWL_TID_NON_QOS; + if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) + tx_flags |= TX_CMD_FLG_SEQ_CTL_MSK; + else + tx_flags &= ~TX_CMD_FLG_SEQ_CTL_MSK; + } + + iwlagn_tx_cmd_protection(priv, info, fc, &tx_flags); + + tx_flags &= ~(TX_CMD_FLG_ANT_SEL_MSK); + if (ieee80211_is_mgmt(fc)) { + if (ieee80211_is_assoc_req(fc) || ieee80211_is_reassoc_req(fc)) + tx_cmd->timeout.pm_frame_timeout = cpu_to_le16(3); + else + tx_cmd->timeout.pm_frame_timeout = cpu_to_le16(2); + } else { + tx_cmd->timeout.pm_frame_timeout = 0; + } + + tx_cmd->driver_txop = 0; + tx_cmd->tx_flags = tx_flags; + tx_cmd->next_frame_len = 0; +} + +static void iwlagn_tx_cmd_build_rate(struct iwl_priv *priv, + struct iwl_tx_cmd *tx_cmd, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + __le16 fc) +{ + u32 rate_flags; + int rate_idx; + u8 rts_retry_limit; + u8 data_retry_limit; + u8 rate_plcp; + + if (priv->wowlan) { + rts_retry_limit = IWLAGN_LOW_RETRY_LIMIT; + data_retry_limit = IWLAGN_LOW_RETRY_LIMIT; + } else { + /* Set retry limit on RTS packets */ + rts_retry_limit = IWLAGN_RTS_DFAULT_RETRY_LIMIT; + + /* Set retry limit on DATA packets and Probe Responses*/ + if (ieee80211_is_probe_resp(fc)) { + data_retry_limit = IWLAGN_MGMT_DFAULT_RETRY_LIMIT; + rts_retry_limit = + min(data_retry_limit, rts_retry_limit); + } else if (ieee80211_is_back_req(fc)) + data_retry_limit = IWLAGN_BAR_DFAULT_RETRY_LIMIT; + else + data_retry_limit = IWLAGN_DEFAULT_TX_RETRY; + } + + tx_cmd->data_retry_limit = data_retry_limit; + tx_cmd->rts_retry_limit = rts_retry_limit; + + /* DATA packets will use the uCode station table for rate/antenna + * selection */ + if (ieee80211_is_data(fc)) { + tx_cmd->initial_rate_index = 0; + tx_cmd->tx_flags |= TX_CMD_FLG_STA_RATE_MSK; + return; + } else if (ieee80211_is_back_req(fc)) + tx_cmd->tx_flags |= TX_CMD_FLG_STA_RATE_MSK; + + /** + * If the current TX rate stored in mac80211 has the MCS bit set, it's + * not really a TX rate. Thus, we use the lowest supported rate for + * this band. Also use the lowest supported rate if the stored rate + * index is invalid. + */ + rate_idx = info->control.rates[0].idx; + if (info->control.rates[0].flags & IEEE80211_TX_RC_MCS || + (rate_idx < 0) || (rate_idx > IWL_RATE_COUNT_LEGACY)) + rate_idx = rate_lowest_index( + &priv->nvm_data->bands[info->band], sta); + /* For 5 GHZ band, remap mac80211 rate indices into driver indices */ + if (info->band == NL80211_BAND_5GHZ) + rate_idx += IWL_FIRST_OFDM_RATE; + /* Get PLCP rate for tx_cmd->rate_n_flags */ + rate_plcp = iwl_rates[rate_idx].plcp; + /* Zero out flags for this packet */ + rate_flags = 0; + + /* Set CCK flag as needed */ + if ((rate_idx >= IWL_FIRST_CCK_RATE) && (rate_idx <= IWL_LAST_CCK_RATE)) + rate_flags |= RATE_MCS_CCK_MSK; + + /* Set up antennas */ + if (priv->lib->bt_params && + priv->lib->bt_params->advanced_bt_coexist && + priv->bt_full_concurrent) { + /* operated as 1x1 in full concurrency mode */ + priv->mgmt_tx_ant = iwl_toggle_tx_ant(priv, priv->mgmt_tx_ant, + first_antenna(priv->nvm_data->valid_tx_ant)); + } else + priv->mgmt_tx_ant = iwl_toggle_tx_ant( + priv, priv->mgmt_tx_ant, + priv->nvm_data->valid_tx_ant); + rate_flags |= iwl_ant_idx_to_flags(priv->mgmt_tx_ant); + + /* Set the rate in the TX cmd */ + tx_cmd->rate_n_flags = iwl_hw_set_rate_n_flags(rate_plcp, rate_flags); +} + +static void iwlagn_tx_cmd_build_hwcrypto(struct iwl_priv *priv, + struct ieee80211_tx_info *info, + struct iwl_tx_cmd *tx_cmd, + struct sk_buff *skb_frag) +{ + struct ieee80211_key_conf *keyconf = info->control.hw_key; + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + tx_cmd->sec_ctl = TX_CMD_SEC_CCM; + memcpy(tx_cmd->key, keyconf->key, keyconf->keylen); + if (info->flags & IEEE80211_TX_CTL_AMPDU) + tx_cmd->tx_flags |= TX_CMD_FLG_AGG_CCMP_MSK; + break; + + case WLAN_CIPHER_SUITE_TKIP: + tx_cmd->sec_ctl = TX_CMD_SEC_TKIP; + ieee80211_get_tkip_p2k(keyconf, skb_frag, tx_cmd->key); + break; + + case WLAN_CIPHER_SUITE_WEP104: + tx_cmd->sec_ctl |= TX_CMD_SEC_KEY128; + fallthrough; + case WLAN_CIPHER_SUITE_WEP40: + tx_cmd->sec_ctl |= (TX_CMD_SEC_WEP | + (keyconf->keyidx & TX_CMD_SEC_MSK) << TX_CMD_SEC_SHIFT); + + memcpy(&tx_cmd->key[3], keyconf->key, keyconf->keylen); + + IWL_DEBUG_TX(priv, "Configuring packet for WEP encryption " + "with key %d\n", keyconf->keyidx); + break; + + default: + IWL_ERR(priv, "Unknown encode cipher %x\n", keyconf->cipher); + break; + } +} + +/** + * iwl_sta_id_or_broadcast - return sta_id or broadcast sta + * @context: the current context + * @sta: mac80211 station + * + * In certain circumstances mac80211 passes a station pointer + * that may be %NULL, for example during TX or key setup. In + * that case, we need to use the broadcast station, so this + * inline wraps that pattern. + */ +static int iwl_sta_id_or_broadcast(struct iwl_rxon_context *context, + struct ieee80211_sta *sta) +{ + int sta_id; + + if (!sta) + return context->bcast_sta_id; + + sta_id = iwl_sta_id(sta); + + /* + * mac80211 should not be passing a partially + * initialised station! + */ + WARN_ON(sta_id == IWL_INVALID_STATION); + + return sta_id; +} + +/* + * start REPLY_TX command process + */ +int iwlagn_tx_skb(struct iwl_priv *priv, + struct ieee80211_sta *sta, + struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct iwl_station_priv *sta_priv = NULL; + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + struct iwl_device_tx_cmd *dev_cmd; + struct iwl_tx_cmd *tx_cmd; + __le16 fc; + u8 hdr_len; + u16 len, seq_number = 0; + u8 sta_id, tid = IWL_MAX_TID_COUNT; + bool is_agg = false, is_data_qos = false; + int txq_id; + + if (info->control.vif) + ctx = iwl_rxon_ctx_from_vif(info->control.vif); + + if (iwl_is_rfkill(priv)) { + IWL_DEBUG_DROP(priv, "Dropping - RF KILL\n"); + goto drop_unlock_priv; + } + + fc = hdr->frame_control; + +#ifdef CONFIG_IWLWIFI_DEBUG + if (ieee80211_is_auth(fc)) + IWL_DEBUG_TX(priv, "Sending AUTH frame\n"); + else if (ieee80211_is_assoc_req(fc)) + IWL_DEBUG_TX(priv, "Sending ASSOC frame\n"); + else if (ieee80211_is_reassoc_req(fc)) + IWL_DEBUG_TX(priv, "Sending REASSOC frame\n"); +#endif + + if (unlikely(ieee80211_is_probe_resp(fc))) { + struct iwl_wipan_noa_data *noa_data = + rcu_dereference(priv->noa_data); + + if (noa_data && + pskb_expand_head(skb, 0, noa_data->length, + GFP_ATOMIC) == 0) { + skb_put_data(skb, noa_data->data, noa_data->length); + hdr = (struct ieee80211_hdr *)skb->data; + } + } + + hdr_len = ieee80211_hdrlen(fc); + + /* For management frames use broadcast id to do not break aggregation */ + if (!ieee80211_is_data(fc)) + sta_id = ctx->bcast_sta_id; + else { + /* Find index into station table for destination station */ + sta_id = iwl_sta_id_or_broadcast(ctx, sta); + if (sta_id == IWL_INVALID_STATION) { + IWL_DEBUG_DROP(priv, "Dropping - INVALID STATION: %pM\n", + hdr->addr1); + goto drop_unlock_priv; + } + } + + if (sta) + sta_priv = (void *)sta->drv_priv; + + if (sta_priv && sta_priv->asleep && + (info->flags & IEEE80211_TX_CTL_NO_PS_BUFFER)) { + /* + * This sends an asynchronous command to the device, + * but we can rely on it being processed before the + * next frame is processed -- and the next frame to + * this station is the one that will consume this + * counter. + * For now set the counter to just 1 since we do not + * support uAPSD yet. + * + * FIXME: If we get two non-bufferable frames one + * after the other, we might only send out one of + * them because this is racy. + */ + iwl_sta_modify_sleep_tx_count(priv, sta_id, 1); + } + + dev_cmd = iwl_trans_alloc_tx_cmd(priv->trans); + + if (unlikely(!dev_cmd)) + goto drop_unlock_priv; + + dev_cmd->hdr.cmd = REPLY_TX; + tx_cmd = (struct iwl_tx_cmd *) dev_cmd->payload; + + /* Total # bytes to be transmitted */ + len = (u16)skb->len; + tx_cmd->len = cpu_to_le16(len); + + if (info->control.hw_key) + iwlagn_tx_cmd_build_hwcrypto(priv, info, tx_cmd, skb); + + /* TODO need this for burst mode later on */ + iwlagn_tx_cmd_build_basic(priv, skb, tx_cmd, info, hdr, sta_id); + + iwlagn_tx_cmd_build_rate(priv, tx_cmd, info, sta, fc); + + memset(&info->status, 0, sizeof(info->status)); + memset(info->driver_data, 0, sizeof(info->driver_data)); + + info->driver_data[0] = ctx; + info->driver_data[1] = dev_cmd; + /* From now on, we cannot access info->control */ + + spin_lock(&priv->sta_lock); + + if (ieee80211_is_data_qos(fc) && !ieee80211_is_qos_nullfunc(fc)) { + u8 *qc = NULL; + struct iwl_tid_data *tid_data; + qc = ieee80211_get_qos_ctl(hdr); + tid = qc[0] & IEEE80211_QOS_CTL_TID_MASK; + if (WARN_ON_ONCE(tid >= IWL_MAX_TID_COUNT)) + goto drop_unlock_sta; + tid_data = &priv->tid_data[sta_id][tid]; + + /* aggregation is on for this <sta,tid> */ + if (info->flags & IEEE80211_TX_CTL_AMPDU && + tid_data->agg.state != IWL_AGG_ON) { + IWL_ERR(priv, + "TX_CTL_AMPDU while not in AGG: Tx flags = 0x%08x, agg.state = %d\n", + info->flags, tid_data->agg.state); + IWL_ERR(priv, "sta_id = %d, tid = %d seq_num = %d\n", + sta_id, tid, + IEEE80211_SEQ_TO_SN(tid_data->seq_number)); + goto drop_unlock_sta; + } + + /* We can receive packets from the stack in IWL_AGG_{ON,OFF} + * only. Check this here. + */ + if (WARN_ONCE(tid_data->agg.state != IWL_AGG_ON && + tid_data->agg.state != IWL_AGG_OFF, + "Tx while agg.state = %d\n", tid_data->agg.state)) + goto drop_unlock_sta; + + seq_number = tid_data->seq_number; + seq_number &= IEEE80211_SCTL_SEQ; + hdr->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG); + hdr->seq_ctrl |= cpu_to_le16(seq_number); + seq_number += 0x10; + + if (info->flags & IEEE80211_TX_CTL_AMPDU) + is_agg = true; + is_data_qos = true; + } + + /* Copy MAC header from skb into command buffer */ + memcpy(tx_cmd->hdr, hdr, hdr_len); + + txq_id = info->hw_queue; + + if (is_agg) + txq_id = priv->tid_data[sta_id][tid].agg.txq_id; + else if (info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM) { + /* + * The microcode will clear the more data + * bit in the last frame it transmits. + */ + hdr->frame_control |= + cpu_to_le16(IEEE80211_FCTL_MOREDATA); + } + + WARN_ON_ONCE(is_agg && + priv->queue_to_mac80211[txq_id] != info->hw_queue); + + IWL_DEBUG_TX(priv, "TX to [%d|%d] Q:%d - seq: 0x%x\n", sta_id, tid, + txq_id, seq_number); + + if (iwl_trans_tx(priv->trans, skb, dev_cmd, txq_id)) + goto drop_unlock_sta; + + if (is_data_qos && !ieee80211_has_morefrags(fc)) + priv->tid_data[sta_id][tid].seq_number = seq_number; + + spin_unlock(&priv->sta_lock); + + /* + * Avoid atomic ops if it isn't an associated client. + * Also, if this is a packet for aggregation, don't + * increase the counter because the ucode will stop + * aggregation queues when their respective station + * goes to sleep. + */ + if (sta_priv && sta_priv->client && !is_agg) + atomic_inc(&sta_priv->pending_frames); + + return 0; + +drop_unlock_sta: + if (dev_cmd) + iwl_trans_free_tx_cmd(priv->trans, dev_cmd); + spin_unlock(&priv->sta_lock); +drop_unlock_priv: + return -1; +} + +static int iwlagn_alloc_agg_txq(struct iwl_priv *priv, int mq) +{ + int q; + + for (q = IWLAGN_FIRST_AMPDU_QUEUE; + q < priv->trans->trans_cfg->base_params->num_of_queues; q++) { + if (!test_and_set_bit(q, priv->agg_q_alloc)) { + priv->queue_to_mac80211[q] = mq; + return q; + } + } + + return -ENOSPC; +} + +static void iwlagn_dealloc_agg_txq(struct iwl_priv *priv, int q) +{ + clear_bit(q, priv->agg_q_alloc); + priv->queue_to_mac80211[q] = IWL_INVALID_MAC80211_QUEUE; +} + +int iwlagn_tx_agg_stop(struct iwl_priv *priv, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid) +{ + struct iwl_tid_data *tid_data; + int sta_id, txq_id; + enum iwl_agg_state agg_state; + + sta_id = iwl_sta_id(sta); + + if (sta_id == IWL_INVALID_STATION) { + IWL_ERR(priv, "Invalid station for AGG tid %d\n", tid); + return -ENXIO; + } + + spin_lock_bh(&priv->sta_lock); + + tid_data = &priv->tid_data[sta_id][tid]; + txq_id = tid_data->agg.txq_id; + + switch (tid_data->agg.state) { + case IWL_EMPTYING_HW_QUEUE_ADDBA: + /* + * This can happen if the peer stops aggregation + * again before we've had a chance to drain the + * queue we selected previously, i.e. before the + * session was really started completely. + */ + IWL_DEBUG_HT(priv, "AGG stop before setup done\n"); + goto turn_off; + case IWL_AGG_STARTING: + /* + * This can happen when the session is stopped before + * we receive ADDBA response + */ + IWL_DEBUG_HT(priv, "AGG stop before AGG became operational\n"); + goto turn_off; + case IWL_AGG_ON: + break; + default: + IWL_WARN(priv, + "Stopping AGG while state not ON or starting for %d on %d (%d)\n", + sta_id, tid, tid_data->agg.state); + spin_unlock_bh(&priv->sta_lock); + return 0; + } + + tid_data->agg.ssn = IEEE80211_SEQ_TO_SN(tid_data->seq_number); + + /* There are still packets for this RA / TID in the HW */ + if (!test_bit(txq_id, priv->agg_q_alloc)) { + IWL_DEBUG_TX_QUEUES(priv, + "stopping AGG on STA/TID %d/%d but hwq %d not used\n", + sta_id, tid, txq_id); + } else if (tid_data->agg.ssn != tid_data->next_reclaimed) { + IWL_DEBUG_TX_QUEUES(priv, + "Can't proceed: ssn %d, next_recl = %d\n", + tid_data->agg.ssn, + tid_data->next_reclaimed); + tid_data->agg.state = IWL_EMPTYING_HW_QUEUE_DELBA; + spin_unlock_bh(&priv->sta_lock); + return 0; + } + + IWL_DEBUG_TX_QUEUES(priv, "Can proceed: ssn = next_recl = %d\n", + tid_data->agg.ssn); +turn_off: + agg_state = tid_data->agg.state; + tid_data->agg.state = IWL_AGG_OFF; + + spin_unlock_bh(&priv->sta_lock); + + if (test_bit(txq_id, priv->agg_q_alloc)) { + /* + * If the transport didn't know that we wanted to start + * agreggation, don't tell it that we want to stop them. + * This can happen when we don't get the addBA response on + * time, or we hadn't time to drain the AC queues. + */ + if (agg_state == IWL_AGG_ON) + iwl_trans_txq_disable(priv->trans, txq_id, true); + else + IWL_DEBUG_TX_QUEUES(priv, "Don't disable tx agg: %d\n", + agg_state); + iwlagn_dealloc_agg_txq(priv, txq_id); + } + + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + + return 0; +} + +int iwlagn_tx_agg_start(struct iwl_priv *priv, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 *ssn) +{ + struct iwl_rxon_context *ctx = iwl_rxon_ctx_from_vif(vif); + struct iwl_tid_data *tid_data; + int sta_id, txq_id, ret; + + IWL_DEBUG_HT(priv, "TX AGG request on ra = %pM tid = %d\n", + sta->addr, tid); + + sta_id = iwl_sta_id(sta); + if (sta_id == IWL_INVALID_STATION) { + IWL_ERR(priv, "Start AGG on invalid station\n"); + return -ENXIO; + } + if (unlikely(tid >= IWL_MAX_TID_COUNT)) + return -EINVAL; + + if (priv->tid_data[sta_id][tid].agg.state != IWL_AGG_OFF) { + IWL_ERR(priv, "Start AGG when state is not IWL_AGG_OFF !\n"); + return -ENXIO; + } + + txq_id = iwlagn_alloc_agg_txq(priv, ctx->ac_to_queue[tid_to_ac[tid]]); + if (txq_id < 0) { + IWL_DEBUG_TX_QUEUES(priv, + "No free aggregation queue for %pM/%d\n", + sta->addr, tid); + return txq_id; + } + + ret = iwl_sta_tx_modify_enable_tid(priv, sta_id, tid); + if (ret) + return ret; + + spin_lock_bh(&priv->sta_lock); + tid_data = &priv->tid_data[sta_id][tid]; + tid_data->agg.ssn = IEEE80211_SEQ_TO_SN(tid_data->seq_number); + tid_data->agg.txq_id = txq_id; + + *ssn = tid_data->agg.ssn; + + if (*ssn == tid_data->next_reclaimed) { + IWL_DEBUG_TX_QUEUES(priv, "Can proceed: ssn = next_recl = %d\n", + tid_data->agg.ssn); + tid_data->agg.state = IWL_AGG_STARTING; + ret = IEEE80211_AMPDU_TX_START_IMMEDIATE; + } else { + IWL_DEBUG_TX_QUEUES(priv, "Can't proceed: ssn %d, " + "next_reclaimed = %d\n", + tid_data->agg.ssn, + tid_data->next_reclaimed); + tid_data->agg.state = IWL_EMPTYING_HW_QUEUE_ADDBA; + } + spin_unlock_bh(&priv->sta_lock); + + return ret; +} + +int iwlagn_tx_agg_flush(struct iwl_priv *priv, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid) +{ + struct iwl_tid_data *tid_data; + enum iwl_agg_state agg_state; + int sta_id, txq_id; + sta_id = iwl_sta_id(sta); + + /* + * First set the agg state to OFF to avoid calling + * ieee80211_stop_tx_ba_cb in iwlagn_check_ratid_empty. + */ + spin_lock_bh(&priv->sta_lock); + + tid_data = &priv->tid_data[sta_id][tid]; + txq_id = tid_data->agg.txq_id; + agg_state = tid_data->agg.state; + IWL_DEBUG_TX_QUEUES(priv, "Flush AGG: sta %d tid %d q %d state %d\n", + sta_id, tid, txq_id, tid_data->agg.state); + + tid_data->agg.state = IWL_AGG_OFF; + + spin_unlock_bh(&priv->sta_lock); + + if (iwlagn_txfifo_flush(priv, BIT(txq_id))) + IWL_ERR(priv, "Couldn't flush the AGG queue\n"); + + if (test_bit(txq_id, priv->agg_q_alloc)) { + /* + * If the transport didn't know that we wanted to start + * agreggation, don't tell it that we want to stop them. + * This can happen when we don't get the addBA response on + * time, or we hadn't time to drain the AC queues. + */ + if (agg_state == IWL_AGG_ON) + iwl_trans_txq_disable(priv->trans, txq_id, true); + else + IWL_DEBUG_TX_QUEUES(priv, "Don't disable tx agg: %d\n", + agg_state); + iwlagn_dealloc_agg_txq(priv, txq_id); + } + + return 0; +} + +int iwlagn_tx_agg_oper(struct iwl_priv *priv, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u8 buf_size) +{ + struct iwl_station_priv *sta_priv = (void *) sta->drv_priv; + struct iwl_rxon_context *ctx = iwl_rxon_ctx_from_vif(vif); + int q, fifo; + u16 ssn; + + buf_size = min_t(int, buf_size, LINK_QUAL_AGG_FRAME_LIMIT_DEF); + + spin_lock_bh(&priv->sta_lock); + ssn = priv->tid_data[sta_priv->sta_id][tid].agg.ssn; + q = priv->tid_data[sta_priv->sta_id][tid].agg.txq_id; + priv->tid_data[sta_priv->sta_id][tid].agg.state = IWL_AGG_ON; + spin_unlock_bh(&priv->sta_lock); + + fifo = ctx->ac_to_fifo[tid_to_ac[tid]]; + + iwl_trans_txq_enable(priv->trans, q, fifo, sta_priv->sta_id, tid, + buf_size, ssn, 0); + + /* + * If the limit is 0, then it wasn't initialised yet, + * use the default. We can do that since we take the + * minimum below, and we don't want to go above our + * default due to hardware restrictions. + */ + if (sta_priv->max_agg_bufsize == 0) + sta_priv->max_agg_bufsize = + LINK_QUAL_AGG_FRAME_LIMIT_DEF; + + /* + * Even though in theory the peer could have different + * aggregation reorder buffer sizes for different sessions, + * our ucode doesn't allow for that and has a global limit + * for each station. Therefore, use the minimum of all the + * aggregation sessions and our default value. + */ + sta_priv->max_agg_bufsize = + min(sta_priv->max_agg_bufsize, buf_size); + + if (priv->hw_params.use_rts_for_aggregation) { + /* + * switch to RTS/CTS if it is the prefer protection + * method for HT traffic + */ + + sta_priv->lq_sta.lq.general_params.flags |= + LINK_QUAL_FLAGS_SET_STA_TLC_RTS_MSK; + } + priv->agg_tids_count++; + IWL_DEBUG_HT(priv, "priv->agg_tids_count = %u\n", + priv->agg_tids_count); + + sta_priv->lq_sta.lq.agg_params.agg_frame_cnt_limit = + sta_priv->max_agg_bufsize; + + IWL_DEBUG_HT(priv, "Tx aggregation enabled on ra = %pM tid = %d\n", + sta->addr, tid); + + return iwl_send_lq_cmd(priv, ctx, + &sta_priv->lq_sta.lq, CMD_ASYNC, false); +} + +static void iwlagn_check_ratid_empty(struct iwl_priv *priv, int sta_id, u8 tid) +{ + struct iwl_tid_data *tid_data = &priv->tid_data[sta_id][tid]; + enum iwl_rxon_context_id ctx; + struct ieee80211_vif *vif; + u8 *addr; + + lockdep_assert_held(&priv->sta_lock); + + addr = priv->stations[sta_id].sta.sta.addr; + ctx = priv->stations[sta_id].ctxid; + vif = priv->contexts[ctx].vif; + + switch (priv->tid_data[sta_id][tid].agg.state) { + case IWL_EMPTYING_HW_QUEUE_DELBA: + /* There are no packets for this RA / TID in the HW any more */ + if (tid_data->agg.ssn == tid_data->next_reclaimed) { + IWL_DEBUG_TX_QUEUES(priv, + "Can continue DELBA flow ssn = next_recl = %d\n", + tid_data->next_reclaimed); + iwl_trans_txq_disable(priv->trans, + tid_data->agg.txq_id, true); + iwlagn_dealloc_agg_txq(priv, tid_data->agg.txq_id); + tid_data->agg.state = IWL_AGG_OFF; + ieee80211_stop_tx_ba_cb_irqsafe(vif, addr, tid); + } + break; + case IWL_EMPTYING_HW_QUEUE_ADDBA: + /* There are no packets for this RA / TID in the HW any more */ + if (tid_data->agg.ssn == tid_data->next_reclaimed) { + IWL_DEBUG_TX_QUEUES(priv, + "Can continue ADDBA flow ssn = next_recl = %d\n", + tid_data->next_reclaimed); + tid_data->agg.state = IWL_AGG_STARTING; + ieee80211_start_tx_ba_cb_irqsafe(vif, addr, tid); + } + break; + default: + break; + } +} + +static void iwlagn_non_agg_tx_status(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + const u8 *addr1) +{ + struct ieee80211_sta *sta; + struct iwl_station_priv *sta_priv; + + rcu_read_lock(); + sta = ieee80211_find_sta(ctx->vif, addr1); + if (sta) { + sta_priv = (void *)sta->drv_priv; + /* avoid atomic ops if this isn't a client */ + if (sta_priv->client && + atomic_dec_return(&sta_priv->pending_frames) == 0) + ieee80211_sta_block_awake(priv->hw, sta, false); + } + rcu_read_unlock(); +} + +/* + * translate ucode response to mac80211 tx status control values + */ +static void iwlagn_hwrate_to_tx_control(struct iwl_priv *priv, u32 rate_n_flags, + struct ieee80211_tx_info *info) +{ + struct ieee80211_tx_rate *r = &info->status.rates[0]; + + info->status.antenna = + ((rate_n_flags & RATE_MCS_ANT_ABC_MSK) >> RATE_MCS_ANT_POS); + if (rate_n_flags & RATE_MCS_HT_MSK) + r->flags |= IEEE80211_TX_RC_MCS; + if (rate_n_flags & RATE_MCS_GF_MSK) + r->flags |= IEEE80211_TX_RC_GREEN_FIELD; + if (rate_n_flags & RATE_MCS_HT40_MSK) + r->flags |= IEEE80211_TX_RC_40_MHZ_WIDTH; + if (rate_n_flags & RATE_MCS_DUP_MSK) + r->flags |= IEEE80211_TX_RC_DUP_DATA; + if (rate_n_flags & RATE_MCS_SGI_MSK) + r->flags |= IEEE80211_TX_RC_SHORT_GI; + r->idx = iwlagn_hwrate_to_mac80211_idx(rate_n_flags, info->band); +} + +#ifdef CONFIG_IWLWIFI_DEBUG +const char *iwl_get_tx_fail_reason(u32 status) +{ +#define TX_STATUS_FAIL(x) case TX_STATUS_FAIL_ ## x: return #x +#define TX_STATUS_POSTPONE(x) case TX_STATUS_POSTPONE_ ## x: return #x + + switch (status & TX_STATUS_MSK) { + case TX_STATUS_SUCCESS: + return "SUCCESS"; + TX_STATUS_POSTPONE(DELAY); + TX_STATUS_POSTPONE(FEW_BYTES); + TX_STATUS_POSTPONE(BT_PRIO); + TX_STATUS_POSTPONE(QUIET_PERIOD); + TX_STATUS_POSTPONE(CALC_TTAK); + TX_STATUS_FAIL(INTERNAL_CROSSED_RETRY); + TX_STATUS_FAIL(SHORT_LIMIT); + TX_STATUS_FAIL(LONG_LIMIT); + TX_STATUS_FAIL(FIFO_UNDERRUN); + TX_STATUS_FAIL(DRAIN_FLOW); + TX_STATUS_FAIL(RFKILL_FLUSH); + TX_STATUS_FAIL(LIFE_EXPIRE); + TX_STATUS_FAIL(DEST_PS); + TX_STATUS_FAIL(HOST_ABORTED); + TX_STATUS_FAIL(BT_RETRY); + TX_STATUS_FAIL(STA_INVALID); + TX_STATUS_FAIL(FRAG_DROPPED); + TX_STATUS_FAIL(TID_DISABLE); + TX_STATUS_FAIL(FIFO_FLUSHED); + TX_STATUS_FAIL(INSUFFICIENT_CF_POLL); + TX_STATUS_FAIL(PASSIVE_NO_RX); + TX_STATUS_FAIL(NO_BEACON_ON_RADAR); + } + + return "UNKNOWN"; + +#undef TX_STATUS_FAIL +#undef TX_STATUS_POSTPONE +} +#endif /* CONFIG_IWLWIFI_DEBUG */ + +static void iwlagn_count_agg_tx_err_status(struct iwl_priv *priv, u16 status) +{ + status &= AGG_TX_STATUS_MSK; + + switch (status) { + case AGG_TX_STATE_UNDERRUN_MSK: + priv->reply_agg_tx_stats.underrun++; + break; + case AGG_TX_STATE_BT_PRIO_MSK: + priv->reply_agg_tx_stats.bt_prio++; + break; + case AGG_TX_STATE_FEW_BYTES_MSK: + priv->reply_agg_tx_stats.few_bytes++; + break; + case AGG_TX_STATE_ABORT_MSK: + priv->reply_agg_tx_stats.abort++; + break; + case AGG_TX_STATE_LAST_SENT_TTL_MSK: + priv->reply_agg_tx_stats.last_sent_ttl++; + break; + case AGG_TX_STATE_LAST_SENT_TRY_CNT_MSK: + priv->reply_agg_tx_stats.last_sent_try++; + break; + case AGG_TX_STATE_LAST_SENT_BT_KILL_MSK: + priv->reply_agg_tx_stats.last_sent_bt_kill++; + break; + case AGG_TX_STATE_SCD_QUERY_MSK: + priv->reply_agg_tx_stats.scd_query++; + break; + case AGG_TX_STATE_TEST_BAD_CRC32_MSK: + priv->reply_agg_tx_stats.bad_crc32++; + break; + case AGG_TX_STATE_RESPONSE_MSK: + priv->reply_agg_tx_stats.response++; + break; + case AGG_TX_STATE_DUMP_TX_MSK: + priv->reply_agg_tx_stats.dump_tx++; + break; + case AGG_TX_STATE_DELAY_TX_MSK: + priv->reply_agg_tx_stats.delay_tx++; + break; + default: + priv->reply_agg_tx_stats.unknown++; + break; + } +} + +static inline u32 iwlagn_get_scd_ssn(struct iwlagn_tx_resp *tx_resp) +{ + return le32_to_cpup((__le32 *)&tx_resp->status + + tx_resp->frame_count) & IEEE80211_MAX_SN; +} + +static void iwl_rx_reply_tx_agg(struct iwl_priv *priv, + struct iwlagn_tx_resp *tx_resp) +{ + struct agg_tx_status *frame_status = &tx_resp->status; + int tid = (tx_resp->ra_tid & IWLAGN_TX_RES_TID_MSK) >> + IWLAGN_TX_RES_TID_POS; + int sta_id = (tx_resp->ra_tid & IWLAGN_TX_RES_RA_MSK) >> + IWLAGN_TX_RES_RA_POS; + struct iwl_ht_agg *agg = &priv->tid_data[sta_id][tid].agg; + u32 status = le16_to_cpu(tx_resp->status.status); + int i; + + WARN_ON(tid == IWL_TID_NON_QOS); + + if (agg->wait_for_ba) + IWL_DEBUG_TX_REPLY(priv, + "got tx response w/o block-ack\n"); + + agg->rate_n_flags = le32_to_cpu(tx_resp->rate_n_flags); + agg->wait_for_ba = (tx_resp->frame_count > 1); + + /* + * If the BT kill count is non-zero, we'll get this + * notification again. + */ + if (tx_resp->bt_kill_count && tx_resp->frame_count == 1 && + priv->lib->bt_params && + priv->lib->bt_params->advanced_bt_coexist) { + IWL_DEBUG_COEX(priv, "receive reply tx w/ bt_kill\n"); + } + + if (tx_resp->frame_count == 1) + return; + + IWL_DEBUG_TX_REPLY(priv, "TXQ %d initial_rate 0x%x ssn %d frm_cnt %d\n", + agg->txq_id, + le32_to_cpu(tx_resp->rate_n_flags), + iwlagn_get_scd_ssn(tx_resp), tx_resp->frame_count); + + /* Construct bit-map of pending frames within Tx window */ + for (i = 0; i < tx_resp->frame_count; i++) { + u16 fstatus = le16_to_cpu(frame_status[i].status); + u8 retry_cnt = (fstatus & AGG_TX_TRY_MSK) >> AGG_TX_TRY_POS; + + if (status & AGG_TX_STATUS_MSK) + iwlagn_count_agg_tx_err_status(priv, fstatus); + + if (status & (AGG_TX_STATE_FEW_BYTES_MSK | + AGG_TX_STATE_ABORT_MSK)) + continue; + + if (status & AGG_TX_STATUS_MSK || retry_cnt > 1) + IWL_DEBUG_TX_REPLY(priv, + "%d: status %s (0x%04x), try-count (0x%01x)\n", + i, + iwl_get_agg_tx_fail_reason(fstatus), + fstatus & AGG_TX_STATUS_MSK, + retry_cnt); + } +} + +#ifdef CONFIG_IWLWIFI_DEBUG +#define AGG_TX_STATE_FAIL(x) case AGG_TX_STATE_ ## x: return #x + +const char *iwl_get_agg_tx_fail_reason(u16 status) +{ + status &= AGG_TX_STATUS_MSK; + switch (status) { + case AGG_TX_STATE_TRANSMITTED: + return "SUCCESS"; + AGG_TX_STATE_FAIL(UNDERRUN_MSK); + AGG_TX_STATE_FAIL(BT_PRIO_MSK); + AGG_TX_STATE_FAIL(FEW_BYTES_MSK); + AGG_TX_STATE_FAIL(ABORT_MSK); + AGG_TX_STATE_FAIL(LAST_SENT_TTL_MSK); + AGG_TX_STATE_FAIL(LAST_SENT_TRY_CNT_MSK); + AGG_TX_STATE_FAIL(LAST_SENT_BT_KILL_MSK); + AGG_TX_STATE_FAIL(SCD_QUERY_MSK); + AGG_TX_STATE_FAIL(TEST_BAD_CRC32_MSK); + AGG_TX_STATE_FAIL(RESPONSE_MSK); + AGG_TX_STATE_FAIL(DUMP_TX_MSK); + AGG_TX_STATE_FAIL(DELAY_TX_MSK); + } + + return "UNKNOWN"; +} +#endif /* CONFIG_IWLWIFI_DEBUG */ + +static void iwlagn_count_tx_err_status(struct iwl_priv *priv, u16 status) +{ + status &= TX_STATUS_MSK; + + switch (status) { + case TX_STATUS_POSTPONE_DELAY: + priv->reply_tx_stats.pp_delay++; + break; + case TX_STATUS_POSTPONE_FEW_BYTES: + priv->reply_tx_stats.pp_few_bytes++; + break; + case TX_STATUS_POSTPONE_BT_PRIO: + priv->reply_tx_stats.pp_bt_prio++; + break; + case TX_STATUS_POSTPONE_QUIET_PERIOD: + priv->reply_tx_stats.pp_quiet_period++; + break; + case TX_STATUS_POSTPONE_CALC_TTAK: + priv->reply_tx_stats.pp_calc_ttak++; + break; + case TX_STATUS_FAIL_INTERNAL_CROSSED_RETRY: + priv->reply_tx_stats.int_crossed_retry++; + break; + case TX_STATUS_FAIL_SHORT_LIMIT: + priv->reply_tx_stats.short_limit++; + break; + case TX_STATUS_FAIL_LONG_LIMIT: + priv->reply_tx_stats.long_limit++; + break; + case TX_STATUS_FAIL_FIFO_UNDERRUN: + priv->reply_tx_stats.fifo_underrun++; + break; + case TX_STATUS_FAIL_DRAIN_FLOW: + priv->reply_tx_stats.drain_flow++; + break; + case TX_STATUS_FAIL_RFKILL_FLUSH: + priv->reply_tx_stats.rfkill_flush++; + break; + case TX_STATUS_FAIL_LIFE_EXPIRE: + priv->reply_tx_stats.life_expire++; + break; + case TX_STATUS_FAIL_DEST_PS: + priv->reply_tx_stats.dest_ps++; + break; + case TX_STATUS_FAIL_HOST_ABORTED: + priv->reply_tx_stats.host_abort++; + break; + case TX_STATUS_FAIL_BT_RETRY: + priv->reply_tx_stats.bt_retry++; + break; + case TX_STATUS_FAIL_STA_INVALID: + priv->reply_tx_stats.sta_invalid++; + break; + case TX_STATUS_FAIL_FRAG_DROPPED: + priv->reply_tx_stats.frag_drop++; + break; + case TX_STATUS_FAIL_TID_DISABLE: + priv->reply_tx_stats.tid_disable++; + break; + case TX_STATUS_FAIL_FIFO_FLUSHED: + priv->reply_tx_stats.fifo_flush++; + break; + case TX_STATUS_FAIL_INSUFFICIENT_CF_POLL: + priv->reply_tx_stats.insuff_cf_poll++; + break; + case TX_STATUS_FAIL_PASSIVE_NO_RX: + priv->reply_tx_stats.fail_hw_drop++; + break; + case TX_STATUS_FAIL_NO_BEACON_ON_RADAR: + priv->reply_tx_stats.sta_color_mismatch++; + break; + default: + priv->reply_tx_stats.unknown++; + break; + } +} + +static void iwlagn_set_tx_status(struct iwl_priv *priv, + struct ieee80211_tx_info *info, + struct iwlagn_tx_resp *tx_resp) +{ + u16 status = le16_to_cpu(tx_resp->status.status); + + info->flags &= ~IEEE80211_TX_CTL_AMPDU; + + info->status.rates[0].count = tx_resp->failure_frame + 1; + info->flags |= iwl_tx_status_to_mac80211(status); + iwlagn_hwrate_to_tx_control(priv, le32_to_cpu(tx_resp->rate_n_flags), + info); + if (!iwl_is_tx_success(status)) + iwlagn_count_tx_err_status(priv, status); +} + +static void iwl_check_abort_status(struct iwl_priv *priv, + u8 frame_count, u32 status) +{ + if (frame_count == 1 && status == TX_STATUS_FAIL_RFKILL_FLUSH) { + IWL_ERR(priv, "Tx flush command to flush out all frames\n"); + if (!test_bit(STATUS_EXIT_PENDING, &priv->status)) + queue_work(priv->workqueue, &priv->tx_flush); + } +} + +void iwlagn_rx_reply_tx(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + u16 sequence = le16_to_cpu(pkt->hdr.sequence); + int txq_id = SEQ_TO_QUEUE(sequence); + int cmd_index __maybe_unused = SEQ_TO_INDEX(sequence); + struct iwlagn_tx_resp *tx_resp = (void *)pkt->data; + struct ieee80211_hdr *hdr; + u32 status = le16_to_cpu(tx_resp->status.status); + u16 ssn = iwlagn_get_scd_ssn(tx_resp); + int tid; + int sta_id; + int freed; + struct ieee80211_tx_info *info; + struct sk_buff_head skbs; + struct sk_buff *skb; + struct iwl_rxon_context *ctx; + bool is_agg = (txq_id >= IWLAGN_FIRST_AMPDU_QUEUE); + + tid = (tx_resp->ra_tid & IWLAGN_TX_RES_TID_MSK) >> + IWLAGN_TX_RES_TID_POS; + sta_id = (tx_resp->ra_tid & IWLAGN_TX_RES_RA_MSK) >> + IWLAGN_TX_RES_RA_POS; + + spin_lock_bh(&priv->sta_lock); + + if (is_agg) { + WARN_ON_ONCE(sta_id >= IWLAGN_STATION_COUNT || + tid >= IWL_MAX_TID_COUNT); + if (txq_id != priv->tid_data[sta_id][tid].agg.txq_id) + IWL_ERR(priv, "txq_id mismatch: %d %d\n", txq_id, + priv->tid_data[sta_id][tid].agg.txq_id); + iwl_rx_reply_tx_agg(priv, tx_resp); + } + + __skb_queue_head_init(&skbs); + + if (tx_resp->frame_count == 1) { + u16 next_reclaimed = le16_to_cpu(tx_resp->seq_ctl); + next_reclaimed = IEEE80211_SEQ_TO_SN(next_reclaimed + 0x10); + + if (is_agg) { + /* If this is an aggregation queue, we can rely on the + * ssn since the wifi sequence number corresponds to + * the index in the TFD ring (%256). + * The seq_ctl is the sequence control of the packet + * to which this Tx response relates. But if there is a + * hole in the bitmap of the BA we received, this Tx + * response may allow to reclaim the hole and all the + * subsequent packets that were already acked. + * In that case, seq_ctl != ssn, and the next packet + * to be reclaimed will be ssn and not seq_ctl. + */ + next_reclaimed = ssn; + } + + if (tid != IWL_TID_NON_QOS) { + priv->tid_data[sta_id][tid].next_reclaimed = + next_reclaimed; + IWL_DEBUG_TX_REPLY(priv, "Next reclaimed packet:%d\n", + next_reclaimed); + iwlagn_check_ratid_empty(priv, sta_id, tid); + } + + iwl_trans_reclaim(priv->trans, txq_id, ssn, &skbs, false); + + freed = 0; + + /* process frames */ + skb_queue_walk(&skbs, skb) { + hdr = (struct ieee80211_hdr *)skb->data; + + if (!ieee80211_is_data_qos(hdr->frame_control)) + priv->last_seq_ctl = tx_resp->seq_ctl; + + info = IEEE80211_SKB_CB(skb); + ctx = info->driver_data[0]; + iwl_trans_free_tx_cmd(priv->trans, + info->driver_data[1]); + + memset(&info->status, 0, sizeof(info->status)); + + if (status == TX_STATUS_FAIL_PASSIVE_NO_RX && + ctx->vif && + ctx->vif->type == NL80211_IFTYPE_STATION) { + /* block and stop all queues */ + priv->passive_no_rx = true; + IWL_DEBUG_TX_QUEUES(priv, + "stop all queues: passive channel\n"); + ieee80211_stop_queues(priv->hw); + + IWL_DEBUG_TX_REPLY(priv, + "TXQ %d status %s (0x%08x) " + "rate_n_flags 0x%x retries %d\n", + txq_id, + iwl_get_tx_fail_reason(status), + status, + le32_to_cpu(tx_resp->rate_n_flags), + tx_resp->failure_frame); + + IWL_DEBUG_TX_REPLY(priv, + "FrameCnt = %d, idx=%d\n", + tx_resp->frame_count, cmd_index); + } + + /* check if BAR is needed */ + if (is_agg && !iwl_is_tx_success(status)) + info->flags |= IEEE80211_TX_STAT_AMPDU_NO_BACK; + iwlagn_set_tx_status(priv, IEEE80211_SKB_CB(skb), + tx_resp); + if (!is_agg) + iwlagn_non_agg_tx_status(priv, ctx, hdr->addr1); + + freed++; + } + + if (tid != IWL_TID_NON_QOS) { + priv->tid_data[sta_id][tid].next_reclaimed = + next_reclaimed; + IWL_DEBUG_TX_REPLY(priv, "Next reclaimed packet:%d\n", + next_reclaimed); + } + + if (!is_agg && freed != 1) + IWL_ERR(priv, "Q: %d, freed %d\n", txq_id, freed); + + IWL_DEBUG_TX_REPLY(priv, "TXQ %d status %s (0x%08x)\n", txq_id, + iwl_get_tx_fail_reason(status), status); + + IWL_DEBUG_TX_REPLY(priv, + "\t\t\t\tinitial_rate 0x%x retries %d, idx=%d ssn=%d seq_ctl=0x%x\n", + le32_to_cpu(tx_resp->rate_n_flags), + tx_resp->failure_frame, + SEQ_TO_INDEX(sequence), ssn, + le16_to_cpu(tx_resp->seq_ctl)); + } + + iwl_check_abort_status(priv, tx_resp->frame_count, status); + spin_unlock_bh(&priv->sta_lock); + + while (!skb_queue_empty(&skbs)) { + skb = __skb_dequeue(&skbs); + ieee80211_tx_status(priv->hw, skb); + } +} + +/* + * iwlagn_rx_reply_compressed_ba - Handler for REPLY_COMPRESSED_BA + * + * Handles block-acknowledge notification from device, which reports success + * of frames sent via aggregation. + */ +void iwlagn_rx_reply_compressed_ba(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_compressed_ba_resp *ba_resp = (void *)pkt->data; + struct iwl_ht_agg *agg; + struct sk_buff_head reclaimed_skbs; + struct sk_buff *skb; + int sta_id; + int tid; + int freed; + + /* "flow" corresponds to Tx queue */ + u16 scd_flow = le16_to_cpu(ba_resp->scd_flow); + + /* "ssn" is start of block-ack Tx window, corresponds to index + * (in Tx queue's circular buffer) of first TFD/frame in window */ + u16 ba_resp_scd_ssn = le16_to_cpu(ba_resp->scd_ssn); + + if (scd_flow >= priv->trans->trans_cfg->base_params->num_of_queues) { + IWL_ERR(priv, + "BUG_ON scd_flow is bigger than number of queues\n"); + return; + } + + sta_id = ba_resp->sta_id; + tid = ba_resp->tid; + agg = &priv->tid_data[sta_id][tid].agg; + + spin_lock_bh(&priv->sta_lock); + + if (unlikely(!agg->wait_for_ba)) { + if (unlikely(ba_resp->bitmap)) + IWL_ERR(priv, "Received BA when not expected\n"); + spin_unlock_bh(&priv->sta_lock); + return; + } + + if (unlikely(scd_flow != agg->txq_id)) { + /* + * FIXME: this is a uCode bug which need to be addressed, + * log the information and return for now. + * Since it is can possibly happen very often and in order + * not to fill the syslog, don't use IWL_ERR or IWL_WARN + */ + IWL_DEBUG_TX_QUEUES(priv, + "Bad queue mapping txq_id=%d, agg_txq[sta:%d,tid:%d]=%d\n", + scd_flow, sta_id, tid, agg->txq_id); + spin_unlock_bh(&priv->sta_lock); + return; + } + + __skb_queue_head_init(&reclaimed_skbs); + + /* Release all TFDs before the SSN, i.e. all TFDs in front of + * block-ack window (we assume that they've been successfully + * transmitted ... if not, it's too late anyway). */ + iwl_trans_reclaim(priv->trans, scd_flow, ba_resp_scd_ssn, + &reclaimed_skbs, false); + + IWL_DEBUG_TX_REPLY(priv, "REPLY_COMPRESSED_BA [%d] Received from %pM, " + "sta_id = %d\n", + agg->wait_for_ba, + (u8 *) &ba_resp->sta_addr_lo32, + ba_resp->sta_id); + IWL_DEBUG_TX_REPLY(priv, "TID = %d, SeqCtl = %d, bitmap = 0x%llx, " + "scd_flow = %d, scd_ssn = %d sent:%d, acked:%d\n", + ba_resp->tid, le16_to_cpu(ba_resp->seq_ctl), + (unsigned long long)le64_to_cpu(ba_resp->bitmap), + scd_flow, ba_resp_scd_ssn, ba_resp->txed, + ba_resp->txed_2_done); + + /* Mark that the expected block-ack response arrived */ + agg->wait_for_ba = false; + + /* Sanity check values reported by uCode */ + if (ba_resp->txed_2_done > ba_resp->txed) { + IWL_DEBUG_TX_REPLY(priv, + "bogus sent(%d) and ack(%d) count\n", + ba_resp->txed, ba_resp->txed_2_done); + /* + * set txed_2_done = txed, + * so it won't impact rate scale + */ + ba_resp->txed = ba_resp->txed_2_done; + } + + priv->tid_data[sta_id][tid].next_reclaimed = ba_resp_scd_ssn; + + iwlagn_check_ratid_empty(priv, sta_id, tid); + freed = 0; + + skb_queue_walk(&reclaimed_skbs, skb) { + struct ieee80211_hdr *hdr = (void *)skb->data; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + if (ieee80211_is_data_qos(hdr->frame_control)) + freed++; + else + WARN_ON_ONCE(1); + + iwl_trans_free_tx_cmd(priv->trans, info->driver_data[1]); + + memset(&info->status, 0, sizeof(info->status)); + /* Packet was transmitted successfully, failures come as single + * frames because before failing a frame the firmware transmits + * it without aggregation at least once. + */ + info->flags |= IEEE80211_TX_STAT_ACK; + + if (freed == 1) { + /* this is the first skb we deliver in this batch */ + /* put the rate scaling data there */ + info = IEEE80211_SKB_CB(skb); + memset(&info->status, 0, sizeof(info->status)); + info->flags |= IEEE80211_TX_STAT_AMPDU; + info->status.ampdu_ack_len = ba_resp->txed_2_done; + info->status.ampdu_len = ba_resp->txed; + iwlagn_hwrate_to_tx_control(priv, agg->rate_n_flags, + info); + } + } + + spin_unlock_bh(&priv->sta_lock); + + while (!skb_queue_empty(&reclaimed_skbs)) { + skb = __skb_dequeue(&reclaimed_skbs); + ieee80211_tx_status(priv->hw, skb); + } +} diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/ucode.c b/drivers/net/wireless/intel/iwlwifi/dvm/ucode.c new file mode 100644 index 000000000..bb13ca5d6 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/dvm/ucode.c @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2008 - 2014 Intel Corporation. All rights reserved. + * Copyright(c) 2015 Intel Deutschland GmbH + *****************************************************************************/ + +#include <linux/kernel.h> + +#include "iwl-io.h" +#include "iwl-agn-hw.h" +#include "iwl-trans.h" +#include "iwl-fh.h" +#include "iwl-op-mode.h" + +#include "dev.h" +#include "agn.h" +#include "calib.h" + +/****************************************************************************** + * + * uCode download functions + * + ******************************************************************************/ + +/* + * Calibration + */ +static int iwl_set_Xtal_calib(struct iwl_priv *priv) +{ + struct iwl_calib_xtal_freq_cmd cmd; + __le16 *xtal_calib = priv->nvm_data->xtal_calib; + + iwl_set_calib_hdr(&cmd.hdr, IWL_PHY_CALIBRATE_CRYSTAL_FRQ_CMD); + cmd.cap_pin1 = le16_to_cpu(xtal_calib[0]); + cmd.cap_pin2 = le16_to_cpu(xtal_calib[1]); + return iwl_calib_set(priv, (void *)&cmd, sizeof(cmd)); +} + +static int iwl_set_temperature_offset_calib(struct iwl_priv *priv) +{ + struct iwl_calib_temperature_offset_cmd cmd; + + memset(&cmd, 0, sizeof(cmd)); + iwl_set_calib_hdr(&cmd.hdr, IWL_PHY_CALIBRATE_TEMP_OFFSET_CMD); + cmd.radio_sensor_offset = priv->nvm_data->raw_temperature; + if (!(cmd.radio_sensor_offset)) + cmd.radio_sensor_offset = DEFAULT_RADIO_SENSOR_OFFSET; + + IWL_DEBUG_CALIB(priv, "Radio sensor offset: %d\n", + le16_to_cpu(cmd.radio_sensor_offset)); + return iwl_calib_set(priv, (void *)&cmd, sizeof(cmd)); +} + +static int iwl_set_temperature_offset_calib_v2(struct iwl_priv *priv) +{ + struct iwl_calib_temperature_offset_v2_cmd cmd; + + memset(&cmd, 0, sizeof(cmd)); + iwl_set_calib_hdr(&cmd.hdr, IWL_PHY_CALIBRATE_TEMP_OFFSET_CMD); + cmd.radio_sensor_offset_high = priv->nvm_data->kelvin_temperature; + cmd.radio_sensor_offset_low = priv->nvm_data->raw_temperature; + if (!cmd.radio_sensor_offset_low) { + IWL_DEBUG_CALIB(priv, "no info in EEPROM, use default\n"); + cmd.radio_sensor_offset_low = DEFAULT_RADIO_SENSOR_OFFSET; + cmd.radio_sensor_offset_high = DEFAULT_RADIO_SENSOR_OFFSET; + } + cmd.burntVoltageRef = priv->nvm_data->calib_voltage; + + IWL_DEBUG_CALIB(priv, "Radio sensor offset high: %d\n", + le16_to_cpu(cmd.radio_sensor_offset_high)); + IWL_DEBUG_CALIB(priv, "Radio sensor offset low: %d\n", + le16_to_cpu(cmd.radio_sensor_offset_low)); + IWL_DEBUG_CALIB(priv, "Voltage Ref: %d\n", + le16_to_cpu(cmd.burntVoltageRef)); + + return iwl_calib_set(priv, (void *)&cmd, sizeof(cmd)); +} + +static int iwl_send_calib_cfg(struct iwl_priv *priv) +{ + struct iwl_calib_cfg_cmd calib_cfg_cmd; + struct iwl_host_cmd cmd = { + .id = CALIBRATION_CFG_CMD, + .len = { sizeof(struct iwl_calib_cfg_cmd), }, + .data = { &calib_cfg_cmd, }, + }; + + memset(&calib_cfg_cmd, 0, sizeof(calib_cfg_cmd)); + calib_cfg_cmd.ucd_calib_cfg.once.is_enable = IWL_CALIB_INIT_CFG_ALL; + calib_cfg_cmd.ucd_calib_cfg.once.start = IWL_CALIB_INIT_CFG_ALL; + calib_cfg_cmd.ucd_calib_cfg.once.send_res = IWL_CALIB_INIT_CFG_ALL; + calib_cfg_cmd.ucd_calib_cfg.flags = + IWL_CALIB_CFG_FLAG_SEND_COMPLETE_NTFY_MSK; + + return iwl_dvm_send_cmd(priv, &cmd); +} + +int iwl_init_alive_start(struct iwl_priv *priv) +{ + int ret; + + if (priv->lib->bt_params && + priv->lib->bt_params->advanced_bt_coexist) { + /* + * Tell uCode we are ready to perform calibration + * need to perform this before any calibration + * no need to close the envlope since we are going + * to load the runtime uCode later. + */ + ret = iwl_send_bt_env(priv, IWL_BT_COEX_ENV_OPEN, + BT_COEX_PRIO_TBL_EVT_INIT_CALIB2); + if (ret) + return ret; + + } + + ret = iwl_send_calib_cfg(priv); + if (ret) + return ret; + + /** + * temperature offset calibration is only needed for runtime ucode, + * so prepare the value now. + */ + if (priv->lib->need_temp_offset_calib) { + if (priv->lib->temp_offset_v2) + return iwl_set_temperature_offset_calib_v2(priv); + else + return iwl_set_temperature_offset_calib(priv); + } + + return 0; +} + +static int iwl_send_wimax_coex(struct iwl_priv *priv) +{ + struct iwl_wimax_coex_cmd coex_cmd; + + /* coexistence is disabled */ + memset(&coex_cmd, 0, sizeof(coex_cmd)); + + return iwl_dvm_send_cmd_pdu(priv, + COEX_PRIORITY_TABLE_CMD, 0, + sizeof(coex_cmd), &coex_cmd); +} + +static const u8 iwl_bt_prio_tbl[BT_COEX_PRIO_TBL_EVT_MAX] = { + ((BT_COEX_PRIO_TBL_PRIO_BYPASS << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (0 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + ((BT_COEX_PRIO_TBL_PRIO_BYPASS << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (1 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + ((BT_COEX_PRIO_TBL_PRIO_LOW << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (0 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + ((BT_COEX_PRIO_TBL_PRIO_LOW << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (1 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + ((BT_COEX_PRIO_TBL_PRIO_HIGH << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (0 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + ((BT_COEX_PRIO_TBL_PRIO_HIGH << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (1 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + ((BT_COEX_PRIO_TBL_PRIO_BYPASS << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (0 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + ((BT_COEX_PRIO_TBL_PRIO_COEX_OFF << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (0 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + ((BT_COEX_PRIO_TBL_PRIO_COEX_ON << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (0 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + 0, 0, 0, 0, 0, 0, 0 +}; + +void iwl_send_prio_tbl(struct iwl_priv *priv) +{ + struct iwl_bt_coex_prio_table_cmd prio_tbl_cmd; + + memcpy(prio_tbl_cmd.prio_tbl, iwl_bt_prio_tbl, + sizeof(iwl_bt_prio_tbl)); + if (iwl_dvm_send_cmd_pdu(priv, + REPLY_BT_COEX_PRIO_TABLE, 0, + sizeof(prio_tbl_cmd), &prio_tbl_cmd)) + IWL_ERR(priv, "failed to send BT prio tbl command\n"); +} + +int iwl_send_bt_env(struct iwl_priv *priv, u8 action, u8 type) +{ + struct iwl_bt_coex_prot_env_cmd env_cmd; + int ret; + + env_cmd.action = action; + env_cmd.type = type; + ret = iwl_dvm_send_cmd_pdu(priv, + REPLY_BT_COEX_PROT_ENV, 0, + sizeof(env_cmd), &env_cmd); + if (ret) + IWL_ERR(priv, "failed to send BT env command\n"); + return ret; +} + +static const u8 iwlagn_default_queue_to_tx_fifo[] = { + IWL_TX_FIFO_VO, + IWL_TX_FIFO_VI, + IWL_TX_FIFO_BE, + IWL_TX_FIFO_BK, +}; + +static const u8 iwlagn_ipan_queue_to_tx_fifo[] = { + IWL_TX_FIFO_VO, + IWL_TX_FIFO_VI, + IWL_TX_FIFO_BE, + IWL_TX_FIFO_BK, + IWL_TX_FIFO_BK_IPAN, + IWL_TX_FIFO_BE_IPAN, + IWL_TX_FIFO_VI_IPAN, + IWL_TX_FIFO_VO_IPAN, + IWL_TX_FIFO_BE_IPAN, + IWL_TX_FIFO_UNUSED, + IWL_TX_FIFO_AUX, +}; + +static int iwl_alive_notify(struct iwl_priv *priv) +{ + const u8 *queue_to_txf; + u8 n_queues; + int ret; + int i; + + iwl_trans_fw_alive(priv->trans, 0); + + if (priv->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_PAN && + priv->nvm_data->sku_cap_ipan_enable) { + n_queues = ARRAY_SIZE(iwlagn_ipan_queue_to_tx_fifo); + queue_to_txf = iwlagn_ipan_queue_to_tx_fifo; + } else { + n_queues = ARRAY_SIZE(iwlagn_default_queue_to_tx_fifo); + queue_to_txf = iwlagn_default_queue_to_tx_fifo; + } + + for (i = 0; i < n_queues; i++) + if (queue_to_txf[i] != IWL_TX_FIFO_UNUSED) + iwl_trans_ac_txq_enable(priv->trans, i, + queue_to_txf[i], 0); + + priv->passive_no_rx = false; + priv->transport_queue_stop = 0; + + ret = iwl_send_wimax_coex(priv); + if (ret) + return ret; + + if (!priv->lib->no_xtal_calib) { + ret = iwl_set_Xtal_calib(priv); + if (ret) + return ret; + } + + return iwl_send_calib_results(priv); +} + +struct iwl_alive_data { + bool valid; + u8 subtype; +}; + +static bool iwl_alive_fn(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_priv *priv = + container_of(notif_wait, struct iwl_priv, notif_wait); + struct iwl_alive_data *alive_data = data; + struct iwl_alive_resp *palive; + + palive = (void *)pkt->data; + + IWL_DEBUG_FW(priv, "Alive ucode status 0x%08X revision " + "0x%01X 0x%01X\n", + palive->is_valid, palive->ver_type, + palive->ver_subtype); + + priv->device_pointers.error_event_table = + le32_to_cpu(palive->error_event_table_ptr); + priv->device_pointers.log_event_table = + le32_to_cpu(palive->log_event_table_ptr); + + alive_data->subtype = palive->ver_subtype; + alive_data->valid = palive->is_valid == UCODE_VALID_OK; + + return true; +} + +#define UCODE_ALIVE_TIMEOUT HZ +#define UCODE_CALIB_TIMEOUT (2*HZ) + +int iwl_load_ucode_wait_alive(struct iwl_priv *priv, + enum iwl_ucode_type ucode_type) +{ + struct iwl_notification_wait alive_wait; + struct iwl_alive_data alive_data; + const struct fw_img *fw; + int ret; + enum iwl_ucode_type old_type; + static const u16 alive_cmd[] = { REPLY_ALIVE }; + + fw = iwl_get_ucode_image(priv->fw, ucode_type); + if (WARN_ON(!fw)) + return -EINVAL; + + old_type = priv->cur_ucode; + priv->cur_ucode = ucode_type; + priv->ucode_loaded = false; + + iwl_init_notification_wait(&priv->notif_wait, &alive_wait, + alive_cmd, ARRAY_SIZE(alive_cmd), + iwl_alive_fn, &alive_data); + + ret = iwl_trans_start_fw(priv->trans, fw, false); + if (ret) { + priv->cur_ucode = old_type; + iwl_remove_notification(&priv->notif_wait, &alive_wait); + return ret; + } + + /* + * Some things may run in the background now, but we + * just wait for the ALIVE notification here. + */ + ret = iwl_wait_notification(&priv->notif_wait, &alive_wait, + UCODE_ALIVE_TIMEOUT); + if (ret) { + priv->cur_ucode = old_type; + return ret; + } + + if (!alive_data.valid) { + IWL_ERR(priv, "Loaded ucode is not valid!\n"); + priv->cur_ucode = old_type; + return -EIO; + } + + priv->ucode_loaded = true; + + if (ucode_type != IWL_UCODE_WOWLAN) { + /* delay a bit to give rfkill time to run */ + msleep(5); + } + + ret = iwl_alive_notify(priv); + if (ret) { + IWL_WARN(priv, + "Could not complete ALIVE transition: %d\n", ret); + priv->cur_ucode = old_type; + return ret; + } + + return 0; +} + +static bool iwlagn_wait_calib(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_priv *priv = data; + struct iwl_calib_cmd *cmd; + + if (pkt->hdr.cmd != CALIBRATION_RES_NOTIFICATION) { + WARN_ON(pkt->hdr.cmd != CALIBRATION_COMPLETE_NOTIFICATION); + return true; + } + + cmd = (struct iwl_calib_cmd *)pkt->data; + + if (iwl_calib_set(priv, cmd, iwl_rx_packet_payload_len(pkt))) + IWL_ERR(priv, "Failed to record calibration data %d\n", + cmd->hdr.op_code); + + return false; +} + +int iwl_run_init_ucode(struct iwl_priv *priv) +{ + struct iwl_notification_wait calib_wait; + static const u16 calib_complete[] = { + CALIBRATION_RES_NOTIFICATION, + CALIBRATION_COMPLETE_NOTIFICATION + }; + int ret; + + lockdep_assert_held(&priv->mutex); + + /* No init ucode required? Curious, but maybe ok */ + if (!priv->fw->img[IWL_UCODE_INIT].num_sec) + return 0; + + iwl_init_notification_wait(&priv->notif_wait, &calib_wait, + calib_complete, ARRAY_SIZE(calib_complete), + iwlagn_wait_calib, priv); + + /* Will also start the device */ + ret = iwl_load_ucode_wait_alive(priv, IWL_UCODE_INIT); + if (ret) + goto error; + + ret = iwl_init_alive_start(priv); + if (ret) + goto error; + + /* + * Some things may run in the background now, but we + * just wait for the calibration complete notification. + */ + ret = iwl_wait_notification(&priv->notif_wait, &calib_wait, + UCODE_CALIB_TIMEOUT); + + goto out; + + error: + iwl_remove_notification(&priv->notif_wait, &calib_wait); + out: + /* Whatever happened, stop the device */ + iwl_trans_stop_device(priv->trans); + priv->ucode_loaded = false; + + return ret; +} diff --git a/drivers/net/wireless/intel/iwlwifi/fw/acpi.c b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c new file mode 100644 index 000000000..585e8cd2d --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c @@ -0,0 +1,1185 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2019-2022 Intel Corporation + */ +#include <linux/uuid.h> +#include <linux/dmi.h> +#include "iwl-drv.h" +#include "iwl-debug.h" +#include "acpi.h" +#include "fw/runtime.h" + +const guid_t iwl_guid = GUID_INIT(0xF21202BF, 0x8F78, 0x4DC6, + 0xA5, 0xB3, 0x1F, 0x73, + 0x8E, 0x28, 0x5A, 0xDE); +IWL_EXPORT_SYMBOL(iwl_guid); + +const guid_t iwl_rfi_guid = GUID_INIT(0x7266172C, 0x220B, 0x4B29, + 0x81, 0x4F, 0x75, 0xE4, + 0xDD, 0x26, 0xB5, 0xFD); +IWL_EXPORT_SYMBOL(iwl_rfi_guid); + +static const struct dmi_system_id dmi_ppag_approved_list[] = { + { .ident = "HP", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + }, + }, + { .ident = "SAMSUNG", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD"), + }, + }, + { .ident = "MSFT", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + }, + }, + { .ident = "ASUS", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + }, + }, + {} +}; + +static int iwl_acpi_get_handle(struct device *dev, acpi_string method, + acpi_handle *ret_handle) +{ + acpi_handle root_handle; + acpi_status status; + + root_handle = ACPI_HANDLE(dev); + if (!root_handle) { + IWL_DEBUG_DEV_RADIO(dev, + "ACPI: Could not retrieve root port handle\n"); + return -ENOENT; + } + + status = acpi_get_handle(root_handle, method, ret_handle); + if (ACPI_FAILURE(status)) { + IWL_DEBUG_DEV_RADIO(dev, + "ACPI: %s method not found\n", method); + return -ENOENT; + } + return 0; +} + +void *iwl_acpi_get_object(struct device *dev, acpi_string method) +{ + struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; + acpi_handle handle; + acpi_status status; + int ret; + + ret = iwl_acpi_get_handle(dev, method, &handle); + if (ret) + return ERR_PTR(-ENOENT); + + /* Call the method with no arguments */ + status = acpi_evaluate_object(handle, NULL, NULL, &buf); + if (ACPI_FAILURE(status)) { + IWL_DEBUG_DEV_RADIO(dev, + "ACPI: %s method invocation failed (status: 0x%x)\n", + method, status); + return ERR_PTR(-ENOENT); + } + return buf.pointer; +} +IWL_EXPORT_SYMBOL(iwl_acpi_get_object); + +/* + * Generic function for evaluating a method defined in the device specific + * method (DSM) interface. The returned acpi object must be freed by calling + * function. + */ +static void *iwl_acpi_get_dsm_object(struct device *dev, int rev, int func, + union acpi_object *args, + const guid_t *guid) +{ + union acpi_object *obj; + + obj = acpi_evaluate_dsm(ACPI_HANDLE(dev), guid, rev, func, + args); + if (!obj) { + IWL_DEBUG_DEV_RADIO(dev, + "ACPI: DSM method invocation failed (rev: %d, func:%d)\n", + rev, func); + return ERR_PTR(-ENOENT); + } + return obj; +} + +/* + * Generic function to evaluate a DSM with no arguments + * and an integer return value, + * (as an integer object or inside a buffer object), + * verify and assign the value in the "value" parameter. + * return 0 in success and the appropriate errno otherwise. + */ +static int iwl_acpi_get_dsm_integer(struct device *dev, int rev, int func, + const guid_t *guid, u64 *value, + size_t expected_size) +{ + union acpi_object *obj; + int ret = 0; + + obj = iwl_acpi_get_dsm_object(dev, rev, func, NULL, guid); + if (IS_ERR(obj)) { + IWL_DEBUG_DEV_RADIO(dev, + "Failed to get DSM object. func= %d\n", + func); + return -ENOENT; + } + + if (obj->type == ACPI_TYPE_INTEGER) { + *value = obj->integer.value; + } else if (obj->type == ACPI_TYPE_BUFFER) { + __le64 le_value = 0; + + if (WARN_ON_ONCE(expected_size > sizeof(le_value))) + return -EINVAL; + + /* if the buffer size doesn't match the expected size */ + if (obj->buffer.length != expected_size) + IWL_DEBUG_DEV_RADIO(dev, + "ACPI: DSM invalid buffer size, padding or truncating (%d)\n", + obj->buffer.length); + + /* assuming LE from Intel BIOS spec */ + memcpy(&le_value, obj->buffer.pointer, + min_t(size_t, expected_size, (size_t)obj->buffer.length)); + *value = le64_to_cpu(le_value); + } else { + IWL_DEBUG_DEV_RADIO(dev, + "ACPI: DSM method did not return a valid object, type=%d\n", + obj->type); + ret = -EINVAL; + goto out; + } + + IWL_DEBUG_DEV_RADIO(dev, + "ACPI: DSM method evaluated: func=%d, ret=%d\n", + func, ret); +out: + ACPI_FREE(obj); + return ret; +} + +/* + * Evaluate a DSM with no arguments and a u8 return value, + */ +int iwl_acpi_get_dsm_u8(struct device *dev, int rev, int func, + const guid_t *guid, u8 *value) +{ + int ret; + u64 val; + + ret = iwl_acpi_get_dsm_integer(dev, rev, func, + guid, &val, sizeof(u8)); + + if (ret < 0) + return ret; + + /* cast val (u64) to be u8 */ + *value = (u8)val; + return 0; +} +IWL_EXPORT_SYMBOL(iwl_acpi_get_dsm_u8); + +/* + * Evaluate a DSM with no arguments and a u32 return value, + */ +int iwl_acpi_get_dsm_u32(struct device *dev, int rev, int func, + const guid_t *guid, u32 *value) +{ + int ret; + u64 val; + + ret = iwl_acpi_get_dsm_integer(dev, rev, func, + guid, &val, sizeof(u32)); + + if (ret < 0) + return ret; + + /* cast val (u64) to be u32 */ + *value = (u32)val; + return 0; +} +IWL_EXPORT_SYMBOL(iwl_acpi_get_dsm_u32); + +union acpi_object *iwl_acpi_get_wifi_pkg_range(struct device *dev, + union acpi_object *data, + int min_data_size, + int max_data_size, + int *tbl_rev) +{ + int i; + union acpi_object *wifi_pkg; + + /* + * We need at least one entry in the wifi package that + * describes the domain, and one more entry, otherwise there's + * no point in reading it. + */ + if (WARN_ON_ONCE(min_data_size < 2 || min_data_size > max_data_size)) + return ERR_PTR(-EINVAL); + + /* + * We need at least two packages, one for the revision and one + * for the data itself. Also check that the revision is valid + * (i.e. it is an integer (each caller has to check by itself + * if the returned revision is supported)). + */ + if (data->type != ACPI_TYPE_PACKAGE || + data->package.count < 2 || + data->package.elements[0].type != ACPI_TYPE_INTEGER) { + IWL_DEBUG_DEV_RADIO(dev, "Invalid packages structure\n"); + return ERR_PTR(-EINVAL); + } + + *tbl_rev = data->package.elements[0].integer.value; + + /* loop through all the packages to find the one for WiFi */ + for (i = 1; i < data->package.count; i++) { + union acpi_object *domain; + + wifi_pkg = &data->package.elements[i]; + + /* skip entries that are not a package with the right size */ + if (wifi_pkg->type != ACPI_TYPE_PACKAGE || + wifi_pkg->package.count < min_data_size || + wifi_pkg->package.count > max_data_size) + continue; + + domain = &wifi_pkg->package.elements[0]; + if (domain->type == ACPI_TYPE_INTEGER && + domain->integer.value == ACPI_WIFI_DOMAIN) + goto found; + } + + return ERR_PTR(-ENOENT); + +found: + return wifi_pkg; +} +IWL_EXPORT_SYMBOL(iwl_acpi_get_wifi_pkg_range); + +int iwl_acpi_get_tas(struct iwl_fw_runtime *fwrt, + union iwl_tas_config_cmd *cmd, int fw_ver) +{ + union acpi_object *wifi_pkg, *data; + int ret, tbl_rev, i, block_list_size, enabled; + + data = iwl_acpi_get_object(fwrt->dev, ACPI_WTAS_METHOD); + if (IS_ERR(data)) + return PTR_ERR(data); + + /* try to read wtas table revision 1 or revision 0*/ + wifi_pkg = iwl_acpi_get_wifi_pkg(fwrt->dev, data, + ACPI_WTAS_WIFI_DATA_SIZE, + &tbl_rev); + if (IS_ERR(wifi_pkg)) { + ret = PTR_ERR(wifi_pkg); + goto out_free; + } + + if (tbl_rev == 1 && wifi_pkg->package.elements[1].type == + ACPI_TYPE_INTEGER) { + u32 tas_selection = + (u32)wifi_pkg->package.elements[1].integer.value; + u16 override_iec = + (tas_selection & ACPI_WTAS_OVERRIDE_IEC_MSK) >> ACPI_WTAS_OVERRIDE_IEC_POS; + u16 enabled_iec = (tas_selection & ACPI_WTAS_ENABLE_IEC_MSK) >> + ACPI_WTAS_ENABLE_IEC_POS; + u8 usa_tas_uhb = (tas_selection & ACPI_WTAS_USA_UHB_MSK) >> ACPI_WTAS_USA_UHB_POS; + + + enabled = tas_selection & ACPI_WTAS_ENABLED_MSK; + if (fw_ver <= 3) { + cmd->v3.override_tas_iec = cpu_to_le16(override_iec); + cmd->v3.enable_tas_iec = cpu_to_le16(enabled_iec); + } else { + cmd->v4.usa_tas_uhb_allowed = usa_tas_uhb; + cmd->v4.override_tas_iec = (u8)override_iec; + cmd->v4.enable_tas_iec = (u8)enabled_iec; + } + + } else if (tbl_rev == 0 && + wifi_pkg->package.elements[1].type == ACPI_TYPE_INTEGER) { + enabled = !!wifi_pkg->package.elements[1].integer.value; + } else { + ret = -EINVAL; + goto out_free; + } + + if (!enabled) { + IWL_DEBUG_RADIO(fwrt, "TAS not enabled\n"); + ret = 0; + goto out_free; + } + + IWL_DEBUG_RADIO(fwrt, "Reading TAS table revision %d\n", tbl_rev); + if (wifi_pkg->package.elements[2].type != ACPI_TYPE_INTEGER || + wifi_pkg->package.elements[2].integer.value > + APCI_WTAS_BLACK_LIST_MAX) { + IWL_DEBUG_RADIO(fwrt, "TAS invalid array size %llu\n", + wifi_pkg->package.elements[2].integer.value); + ret = -EINVAL; + goto out_free; + } + block_list_size = wifi_pkg->package.elements[2].integer.value; + cmd->v4.block_list_size = cpu_to_le32(block_list_size); + + IWL_DEBUG_RADIO(fwrt, "TAS array size %u\n", block_list_size); + if (block_list_size > APCI_WTAS_BLACK_LIST_MAX) { + IWL_DEBUG_RADIO(fwrt, "TAS invalid array size value %u\n", + block_list_size); + ret = -EINVAL; + goto out_free; + } + + for (i = 0; i < block_list_size; i++) { + u32 country; + + if (wifi_pkg->package.elements[3 + i].type != + ACPI_TYPE_INTEGER) { + IWL_DEBUG_RADIO(fwrt, + "TAS invalid array elem %d\n", 3 + i); + ret = -EINVAL; + goto out_free; + } + + country = wifi_pkg->package.elements[3 + i].integer.value; + cmd->v4.block_list_array[i] = cpu_to_le32(country); + IWL_DEBUG_RADIO(fwrt, "TAS block list country %d\n", country); + } + + ret = 1; +out_free: + kfree(data); + return ret; +} +IWL_EXPORT_SYMBOL(iwl_acpi_get_tas); + +int iwl_acpi_get_mcc(struct device *dev, char *mcc) +{ + union acpi_object *wifi_pkg, *data; + u32 mcc_val; + int ret, tbl_rev; + + data = iwl_acpi_get_object(dev, ACPI_WRDD_METHOD); + if (IS_ERR(data)) + return PTR_ERR(data); + + wifi_pkg = iwl_acpi_get_wifi_pkg(dev, data, ACPI_WRDD_WIFI_DATA_SIZE, + &tbl_rev); + if (IS_ERR(wifi_pkg)) { + ret = PTR_ERR(wifi_pkg); + goto out_free; + } + + if (wifi_pkg->package.elements[1].type != ACPI_TYPE_INTEGER || + tbl_rev != 0) { + ret = -EINVAL; + goto out_free; + } + + mcc_val = wifi_pkg->package.elements[1].integer.value; + + mcc[0] = (mcc_val >> 8) & 0xff; + mcc[1] = mcc_val & 0xff; + mcc[2] = '\0'; + + ret = 0; +out_free: + kfree(data); + return ret; +} +IWL_EXPORT_SYMBOL(iwl_acpi_get_mcc); + +u64 iwl_acpi_get_pwr_limit(struct device *dev) +{ + union acpi_object *data, *wifi_pkg; + u64 dflt_pwr_limit; + int tbl_rev; + + data = iwl_acpi_get_object(dev, ACPI_SPLC_METHOD); + if (IS_ERR(data)) { + dflt_pwr_limit = 0; + goto out; + } + + wifi_pkg = iwl_acpi_get_wifi_pkg(dev, data, + ACPI_SPLC_WIFI_DATA_SIZE, &tbl_rev); + if (IS_ERR(wifi_pkg) || tbl_rev != 0 || + wifi_pkg->package.elements[1].integer.value != ACPI_TYPE_INTEGER) { + dflt_pwr_limit = 0; + goto out_free; + } + + dflt_pwr_limit = wifi_pkg->package.elements[1].integer.value; +out_free: + kfree(data); +out: + return dflt_pwr_limit; +} +IWL_EXPORT_SYMBOL(iwl_acpi_get_pwr_limit); + +int iwl_acpi_get_eckv(struct device *dev, u32 *extl_clk) +{ + union acpi_object *wifi_pkg, *data; + int ret, tbl_rev; + + data = iwl_acpi_get_object(dev, ACPI_ECKV_METHOD); + if (IS_ERR(data)) + return PTR_ERR(data); + + wifi_pkg = iwl_acpi_get_wifi_pkg(dev, data, ACPI_ECKV_WIFI_DATA_SIZE, + &tbl_rev); + if (IS_ERR(wifi_pkg)) { + ret = PTR_ERR(wifi_pkg); + goto out_free; + } + + if (wifi_pkg->package.elements[1].type != ACPI_TYPE_INTEGER || + tbl_rev != 0) { + ret = -EINVAL; + goto out_free; + } + + *extl_clk = wifi_pkg->package.elements[1].integer.value; + + ret = 0; + +out_free: + kfree(data); + return ret; +} +IWL_EXPORT_SYMBOL(iwl_acpi_get_eckv); + +static int iwl_sar_set_profile(union acpi_object *table, + struct iwl_sar_profile *profile, + bool enabled, u8 num_chains, u8 num_sub_bands) +{ + int i, j, idx = 0; + + /* + * The table from ACPI is flat, but we store it in a + * structured array. + */ + for (i = 0; i < ACPI_SAR_NUM_CHAINS_REV2; i++) { + for (j = 0; j < ACPI_SAR_NUM_SUB_BANDS_REV2; j++) { + /* if we don't have the values, use the default */ + if (i >= num_chains || j >= num_sub_bands) { + profile->chains[i].subbands[j] = 0; + } else { + if (table[idx].type != ACPI_TYPE_INTEGER || + table[idx].integer.value > U8_MAX) + return -EINVAL; + + profile->chains[i].subbands[j] = + table[idx].integer.value; + + idx++; + } + } + } + + /* Only if all values were valid can the profile be enabled */ + profile->enabled = enabled; + + return 0; +} + +static int iwl_sar_fill_table(struct iwl_fw_runtime *fwrt, + __le16 *per_chain, u32 n_subbands, + int prof_a, int prof_b) +{ + int profs[ACPI_SAR_NUM_CHAINS_REV0] = { prof_a, prof_b }; + int i, j; + + for (i = 0; i < ACPI_SAR_NUM_CHAINS_REV0; i++) { + struct iwl_sar_profile *prof; + + /* don't allow SAR to be disabled (profile 0 means disable) */ + if (profs[i] == 0) + return -EPERM; + + /* we are off by one, so allow up to ACPI_SAR_PROFILE_NUM */ + if (profs[i] > ACPI_SAR_PROFILE_NUM) + return -EINVAL; + + /* profiles go from 1 to 4, so decrement to access the array */ + prof = &fwrt->sar_profiles[profs[i] - 1]; + + /* if the profile is disabled, do nothing */ + if (!prof->enabled) { + IWL_DEBUG_RADIO(fwrt, "SAR profile %d is disabled.\n", + profs[i]); + /* + * if one of the profiles is disabled, we + * ignore all of them and return 1 to + * differentiate disabled from other failures. + */ + return 1; + } + + IWL_DEBUG_INFO(fwrt, + "SAR EWRD: chain %d profile index %d\n", + i, profs[i]); + IWL_DEBUG_RADIO(fwrt, " Chain[%d]:\n", i); + for (j = 0; j < n_subbands; j++) { + per_chain[i * n_subbands + j] = + cpu_to_le16(prof->chains[i].subbands[j]); + IWL_DEBUG_RADIO(fwrt, " Band[%d] = %d * .125dBm\n", + j, prof->chains[i].subbands[j]); + } + } + + return 0; +} + +int iwl_sar_select_profile(struct iwl_fw_runtime *fwrt, + __le16 *per_chain, u32 n_tables, u32 n_subbands, + int prof_a, int prof_b) +{ + int i, ret = 0; + + for (i = 0; i < n_tables; i++) { + ret = iwl_sar_fill_table(fwrt, + &per_chain[i * n_subbands * ACPI_SAR_NUM_CHAINS_REV0], + n_subbands, prof_a, prof_b); + if (ret) + break; + } + + return ret; +} +IWL_EXPORT_SYMBOL(iwl_sar_select_profile); + +int iwl_sar_get_wrds_table(struct iwl_fw_runtime *fwrt) +{ + union acpi_object *wifi_pkg, *table, *data; + int ret, tbl_rev; + u32 flags; + u8 num_chains, num_sub_bands; + + data = iwl_acpi_get_object(fwrt->dev, ACPI_WRDS_METHOD); + if (IS_ERR(data)) + return PTR_ERR(data); + + /* start by trying to read revision 2 */ + wifi_pkg = iwl_acpi_get_wifi_pkg(fwrt->dev, data, + ACPI_WRDS_WIFI_DATA_SIZE_REV2, + &tbl_rev); + if (!IS_ERR(wifi_pkg)) { + if (tbl_rev != 2) { + ret = PTR_ERR(wifi_pkg); + goto out_free; + } + + num_chains = ACPI_SAR_NUM_CHAINS_REV2; + num_sub_bands = ACPI_SAR_NUM_SUB_BANDS_REV2; + + goto read_table; + } + + /* then try revision 1 */ + wifi_pkg = iwl_acpi_get_wifi_pkg(fwrt->dev, data, + ACPI_WRDS_WIFI_DATA_SIZE_REV1, + &tbl_rev); + if (!IS_ERR(wifi_pkg)) { + if (tbl_rev != 1) { + ret = PTR_ERR(wifi_pkg); + goto out_free; + } + + num_chains = ACPI_SAR_NUM_CHAINS_REV1; + num_sub_bands = ACPI_SAR_NUM_SUB_BANDS_REV1; + + goto read_table; + } + + /* then finally revision 0 */ + wifi_pkg = iwl_acpi_get_wifi_pkg(fwrt->dev, data, + ACPI_WRDS_WIFI_DATA_SIZE_REV0, + &tbl_rev); + if (!IS_ERR(wifi_pkg)) { + if (tbl_rev != 0) { + ret = PTR_ERR(wifi_pkg); + goto out_free; + } + + num_chains = ACPI_SAR_NUM_CHAINS_REV0; + num_sub_bands = ACPI_SAR_NUM_SUB_BANDS_REV0; + + goto read_table; + } + + ret = PTR_ERR(wifi_pkg); + goto out_free; + +read_table: + if (wifi_pkg->package.elements[1].type != ACPI_TYPE_INTEGER) { + ret = -EINVAL; + goto out_free; + } + + IWL_DEBUG_RADIO(fwrt, "Reading WRDS tbl_rev=%d\n", tbl_rev); + + flags = wifi_pkg->package.elements[1].integer.value; + fwrt->reduced_power_flags = flags >> IWL_REDUCE_POWER_FLAGS_POS; + + /* position of the actual table */ + table = &wifi_pkg->package.elements[2]; + + /* The profile from WRDS is officially profile 1, but goes + * into sar_profiles[0] (because we don't have a profile 0). + */ + ret = iwl_sar_set_profile(table, &fwrt->sar_profiles[0], + flags & IWL_SAR_ENABLE_MSK, + num_chains, num_sub_bands); +out_free: + kfree(data); + return ret; +} +IWL_EXPORT_SYMBOL(iwl_sar_get_wrds_table); + +int iwl_sar_get_ewrd_table(struct iwl_fw_runtime *fwrt) +{ + union acpi_object *wifi_pkg, *data; + bool enabled; + int i, n_profiles, tbl_rev, pos; + int ret = 0; + u8 num_chains, num_sub_bands; + + data = iwl_acpi_get_object(fwrt->dev, ACPI_EWRD_METHOD); + if (IS_ERR(data)) + return PTR_ERR(data); + + /* start by trying to read revision 2 */ + wifi_pkg = iwl_acpi_get_wifi_pkg(fwrt->dev, data, + ACPI_EWRD_WIFI_DATA_SIZE_REV2, + &tbl_rev); + if (!IS_ERR(wifi_pkg)) { + if (tbl_rev != 2) { + ret = PTR_ERR(wifi_pkg); + goto out_free; + } + + num_chains = ACPI_SAR_NUM_CHAINS_REV2; + num_sub_bands = ACPI_SAR_NUM_SUB_BANDS_REV2; + + goto read_table; + } + + /* then try revision 1 */ + wifi_pkg = iwl_acpi_get_wifi_pkg(fwrt->dev, data, + ACPI_EWRD_WIFI_DATA_SIZE_REV1, + &tbl_rev); + if (!IS_ERR(wifi_pkg)) { + if (tbl_rev != 1) { + ret = PTR_ERR(wifi_pkg); + goto out_free; + } + + num_chains = ACPI_SAR_NUM_CHAINS_REV1; + num_sub_bands = ACPI_SAR_NUM_SUB_BANDS_REV1; + + goto read_table; + } + + /* then finally revision 0 */ + wifi_pkg = iwl_acpi_get_wifi_pkg(fwrt->dev, data, + ACPI_EWRD_WIFI_DATA_SIZE_REV0, + &tbl_rev); + if (!IS_ERR(wifi_pkg)) { + if (tbl_rev != 0) { + ret = PTR_ERR(wifi_pkg); + goto out_free; + } + + num_chains = ACPI_SAR_NUM_CHAINS_REV0; + num_sub_bands = ACPI_SAR_NUM_SUB_BANDS_REV0; + + goto read_table; + } + + ret = PTR_ERR(wifi_pkg); + goto out_free; + +read_table: + if (wifi_pkg->package.elements[1].type != ACPI_TYPE_INTEGER || + wifi_pkg->package.elements[2].type != ACPI_TYPE_INTEGER) { + ret = -EINVAL; + goto out_free; + } + + enabled = !!(wifi_pkg->package.elements[1].integer.value); + n_profiles = wifi_pkg->package.elements[2].integer.value; + + /* + * Check the validity of n_profiles. The EWRD profiles start + * from index 1, so the maximum value allowed here is + * ACPI_SAR_PROFILES_NUM - 1. + */ + if (n_profiles <= 0 || n_profiles >= ACPI_SAR_PROFILE_NUM) { + ret = -EINVAL; + goto out_free; + } + + /* the tables start at element 3 */ + pos = 3; + + for (i = 0; i < n_profiles; i++) { + /* The EWRD profiles officially go from 2 to 4, but we + * save them in sar_profiles[1-3] (because we don't + * have profile 0). So in the array we start from 1. + */ + ret = iwl_sar_set_profile(&wifi_pkg->package.elements[pos], + &fwrt->sar_profiles[i + 1], enabled, + num_chains, num_sub_bands); + if (ret < 0) + break; + + /* go to the next table */ + pos += num_chains * num_sub_bands; + } + +out_free: + kfree(data); + return ret; +} +IWL_EXPORT_SYMBOL(iwl_sar_get_ewrd_table); + +int iwl_sar_get_wgds_table(struct iwl_fw_runtime *fwrt) +{ + union acpi_object *wifi_pkg, *data; + int i, j, k, ret, tbl_rev; + u8 num_bands, num_profiles; + static const struct { + u8 revisions; + u8 bands; + u8 profiles; + u8 min_profiles; + } rev_data[] = { + { + .revisions = BIT(3), + .bands = ACPI_GEO_NUM_BANDS_REV2, + .profiles = ACPI_NUM_GEO_PROFILES_REV3, + .min_profiles = 3, + }, + { + .revisions = BIT(2), + .bands = ACPI_GEO_NUM_BANDS_REV2, + .profiles = ACPI_NUM_GEO_PROFILES, + }, + { + .revisions = BIT(0) | BIT(1), + .bands = ACPI_GEO_NUM_BANDS_REV0, + .profiles = ACPI_NUM_GEO_PROFILES, + }, + }; + int idx; + /* start from one to skip the domain */ + int entry_idx = 1; + + BUILD_BUG_ON(ACPI_NUM_GEO_PROFILES_REV3 != IWL_NUM_GEO_PROFILES_V3); + BUILD_BUG_ON(ACPI_NUM_GEO_PROFILES != IWL_NUM_GEO_PROFILES); + + data = iwl_acpi_get_object(fwrt->dev, ACPI_WGDS_METHOD); + if (IS_ERR(data)) + return PTR_ERR(data); + + /* read the highest revision we understand first */ + for (idx = 0; idx < ARRAY_SIZE(rev_data); idx++) { + /* min_profiles != 0 requires num_profiles header */ + u32 hdr_size = 1 + !!rev_data[idx].min_profiles; + u32 profile_size = ACPI_GEO_PER_CHAIN_SIZE * + rev_data[idx].bands; + u32 max_size = hdr_size + profile_size * rev_data[idx].profiles; + u32 min_size; + + if (!rev_data[idx].min_profiles) + min_size = max_size; + else + min_size = hdr_size + + profile_size * rev_data[idx].min_profiles; + + wifi_pkg = iwl_acpi_get_wifi_pkg_range(fwrt->dev, data, + min_size, max_size, + &tbl_rev); + if (!IS_ERR(wifi_pkg)) { + if (!(BIT(tbl_rev) & rev_data[idx].revisions)) + continue; + + num_bands = rev_data[idx].bands; + num_profiles = rev_data[idx].profiles; + + if (rev_data[idx].min_profiles) { + /* read header that says # of profiles */ + union acpi_object *entry; + + entry = &wifi_pkg->package.elements[entry_idx]; + entry_idx++; + if (entry->type != ACPI_TYPE_INTEGER || + entry->integer.value > num_profiles) { + ret = -EINVAL; + goto out_free; + } + num_profiles = entry->integer.value; + + /* + * this also validates >= min_profiles since we + * otherwise wouldn't have gotten the data when + * looking up in ACPI + */ + if (wifi_pkg->package.count != + hdr_size + profile_size * num_profiles) { + ret = -EINVAL; + goto out_free; + } + } + goto read_table; + } + } + + if (idx < ARRAY_SIZE(rev_data)) + ret = PTR_ERR(wifi_pkg); + else + ret = -ENOENT; + goto out_free; + +read_table: + fwrt->geo_rev = tbl_rev; + for (i = 0; i < num_profiles; i++) { + for (j = 0; j < ACPI_GEO_NUM_BANDS_REV2; j++) { + union acpi_object *entry; + + /* + * num_bands is either 2 or 3, if it's only 2 then + * fill the third band (6 GHz) with the values from + * 5 GHz (second band) + */ + if (j >= num_bands) { + fwrt->geo_profiles[i].bands[j].max = + fwrt->geo_profiles[i].bands[1].max; + } else { + entry = &wifi_pkg->package.elements[entry_idx]; + entry_idx++; + if (entry->type != ACPI_TYPE_INTEGER || + entry->integer.value > U8_MAX) { + ret = -EINVAL; + goto out_free; + } + + fwrt->geo_profiles[i].bands[j].max = + entry->integer.value; + } + + for (k = 0; k < ACPI_GEO_NUM_CHAINS; k++) { + /* same here as above */ + if (j >= num_bands) { + fwrt->geo_profiles[i].bands[j].chains[k] = + fwrt->geo_profiles[i].bands[1].chains[k]; + } else { + entry = &wifi_pkg->package.elements[entry_idx]; + entry_idx++; + if (entry->type != ACPI_TYPE_INTEGER || + entry->integer.value > U8_MAX) { + ret = -EINVAL; + goto out_free; + } + + fwrt->geo_profiles[i].bands[j].chains[k] = + entry->integer.value; + } + } + } + } + + fwrt->geo_num_profiles = num_profiles; + fwrt->geo_enabled = true; + ret = 0; +out_free: + kfree(data); + return ret; +} +IWL_EXPORT_SYMBOL(iwl_sar_get_wgds_table); + +bool iwl_sar_geo_support(struct iwl_fw_runtime *fwrt) +{ + /* + * The PER_CHAIN_LIMIT_OFFSET_CMD command is not supported on + * earlier firmware versions. Unfortunately, we don't have a + * TLV API flag to rely on, so rely on the major version which + * is in the first byte of ucode_ver. This was implemented + * initially on version 38 and then backported to 17. It was + * also backported to 29, but only for 7265D devices. The + * intention was to have it in 36 as well, but not all 8000 + * family got this feature enabled. The 8000 family is the + * only one using version 36, so skip this version entirely. + */ + return IWL_UCODE_SERIAL(fwrt->fw->ucode_ver) >= 38 || + (IWL_UCODE_SERIAL(fwrt->fw->ucode_ver) == 17 && + fwrt->trans->hw_rev != CSR_HW_REV_TYPE_3160) || + (IWL_UCODE_SERIAL(fwrt->fw->ucode_ver) == 29 && + ((fwrt->trans->hw_rev & CSR_HW_REV_TYPE_MSK) == + CSR_HW_REV_TYPE_7265D)); +} +IWL_EXPORT_SYMBOL(iwl_sar_geo_support); + +int iwl_sar_geo_init(struct iwl_fw_runtime *fwrt, + struct iwl_per_chain_offset *table, + u32 n_bands, u32 n_profiles) +{ + int i, j; + + if (!fwrt->geo_enabled) + return -ENODATA; + + if (!iwl_sar_geo_support(fwrt)) + return -EOPNOTSUPP; + + for (i = 0; i < n_profiles; i++) { + for (j = 0; j < n_bands; j++) { + struct iwl_per_chain_offset *chain = + &table[i * n_bands + j]; + + chain->max_tx_power = + cpu_to_le16(fwrt->geo_profiles[i].bands[j].max); + chain->chain_a = fwrt->geo_profiles[i].bands[j].chains[0]; + chain->chain_b = fwrt->geo_profiles[i].bands[j].chains[1]; + IWL_DEBUG_RADIO(fwrt, + "SAR geographic profile[%d] Band[%d]: chain A = %d chain B = %d max_tx_power = %d\n", + i, j, + fwrt->geo_profiles[i].bands[j].chains[0], + fwrt->geo_profiles[i].bands[j].chains[1], + fwrt->geo_profiles[i].bands[j].max); + } + } + + return 0; +} +IWL_EXPORT_SYMBOL(iwl_sar_geo_init); + +__le32 iwl_acpi_get_lari_config_bitmap(struct iwl_fw_runtime *fwrt) +{ + int ret; + u8 value; + __le32 config_bitmap = 0; + + /* + ** Evaluate func 'DSM_FUNC_ENABLE_INDONESIA_5G2' + */ + ret = iwl_acpi_get_dsm_u8(fwrt->dev, 0, + DSM_FUNC_ENABLE_INDONESIA_5G2, + &iwl_guid, &value); + + if (!ret && value == DSM_VALUE_INDONESIA_ENABLE) + config_bitmap |= + cpu_to_le32(LARI_CONFIG_ENABLE_5G2_IN_INDONESIA_MSK); + + /* + ** Evaluate func 'DSM_FUNC_DISABLE_SRD' + */ + ret = iwl_acpi_get_dsm_u8(fwrt->dev, 0, + DSM_FUNC_DISABLE_SRD, + &iwl_guid, &value); + if (!ret) { + if (value == DSM_VALUE_SRD_PASSIVE) + config_bitmap |= + cpu_to_le32(LARI_CONFIG_CHANGE_ETSI_TO_PASSIVE_MSK); + else if (value == DSM_VALUE_SRD_DISABLE) + config_bitmap |= + cpu_to_le32(LARI_CONFIG_CHANGE_ETSI_TO_DISABLED_MSK); + } + + return config_bitmap; +} +IWL_EXPORT_SYMBOL(iwl_acpi_get_lari_config_bitmap); + +int iwl_acpi_get_ppag_table(struct iwl_fw_runtime *fwrt) +{ + union acpi_object *wifi_pkg, *data, *flags; + int i, j, ret, tbl_rev, num_sub_bands = 0; + int idx = 2; + + fwrt->ppag_flags = 0; + + data = iwl_acpi_get_object(fwrt->dev, ACPI_PPAG_METHOD); + if (IS_ERR(data)) + return PTR_ERR(data); + + /* try to read ppag table rev 2 or 1 (both have the same data size) */ + wifi_pkg = iwl_acpi_get_wifi_pkg(fwrt->dev, data, + ACPI_PPAG_WIFI_DATA_SIZE_V2, &tbl_rev); + + if (!IS_ERR(wifi_pkg)) { + if (tbl_rev == 1 || tbl_rev == 2) { + num_sub_bands = IWL_NUM_SUB_BANDS_V2; + IWL_DEBUG_RADIO(fwrt, + "Reading PPAG table v2 (tbl_rev=%d)\n", + tbl_rev); + goto read_table; + } else { + ret = -EINVAL; + goto out_free; + } + } + + /* try to read ppag table revision 0 */ + wifi_pkg = iwl_acpi_get_wifi_pkg(fwrt->dev, data, + ACPI_PPAG_WIFI_DATA_SIZE_V1, &tbl_rev); + + if (!IS_ERR(wifi_pkg)) { + if (tbl_rev != 0) { + ret = -EINVAL; + goto out_free; + } + num_sub_bands = IWL_NUM_SUB_BANDS_V1; + IWL_DEBUG_RADIO(fwrt, "Reading PPAG table v1 (tbl_rev=0)\n"); + goto read_table; + } + +read_table: + fwrt->ppag_ver = tbl_rev; + flags = &wifi_pkg->package.elements[1]; + + if (flags->type != ACPI_TYPE_INTEGER) { + ret = -EINVAL; + goto out_free; + } + + fwrt->ppag_flags = flags->integer.value & ACPI_PPAG_MASK; + + if (!fwrt->ppag_flags) { + ret = 0; + goto out_free; + } + + /* + * read, verify gain values and save them into the PPAG table. + * first sub-band (j=0) corresponds to Low-Band (2.4GHz), and the + * following sub-bands to High-Band (5GHz). + */ + for (i = 0; i < IWL_NUM_CHAIN_LIMITS; i++) { + for (j = 0; j < num_sub_bands; j++) { + union acpi_object *ent; + + ent = &wifi_pkg->package.elements[idx++]; + if (ent->type != ACPI_TYPE_INTEGER) { + ret = -EINVAL; + goto out_free; + } + + fwrt->ppag_chains[i].subbands[j] = ent->integer.value; + + if ((j == 0 && + (fwrt->ppag_chains[i].subbands[j] > ACPI_PPAG_MAX_LB || + fwrt->ppag_chains[i].subbands[j] < ACPI_PPAG_MIN_LB)) || + (j != 0 && + (fwrt->ppag_chains[i].subbands[j] > ACPI_PPAG_MAX_HB || + fwrt->ppag_chains[i].subbands[j] < ACPI_PPAG_MIN_HB))) { + fwrt->ppag_flags = 0; + ret = -EINVAL; + goto out_free; + } + } + } + + + ret = 0; + +out_free: + kfree(data); + return ret; +} +IWL_EXPORT_SYMBOL(iwl_acpi_get_ppag_table); + +int iwl_read_ppag_table(struct iwl_fw_runtime *fwrt, union iwl_ppag_table_cmd *cmd, + int *cmd_size) +{ + u8 cmd_ver; + int i, j, num_sub_bands; + s8 *gain; + + /* many firmware images for JF lie about this */ + if (CSR_HW_RFID_TYPE(fwrt->trans->hw_rf_id) == + CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_JF)) + return -EOPNOTSUPP; + + if (!fw_has_capa(&fwrt->fw->ucode_capa, IWL_UCODE_TLV_CAPA_SET_PPAG)) { + IWL_DEBUG_RADIO(fwrt, + "PPAG capability not supported by FW, command not sent.\n"); + return -EINVAL; + } + if (!fwrt->ppag_flags) { + IWL_DEBUG_RADIO(fwrt, "PPAG not enabled, command not sent.\n"); + return -EINVAL; + } + + /* The 'flags' field is the same in v1 and in v2 so we can just + * use v1 to access it. + */ + cmd->v1.flags = cpu_to_le32(fwrt->ppag_flags); + cmd_ver = iwl_fw_lookup_cmd_ver(fwrt->fw, + WIDE_ID(PHY_OPS_GROUP, PER_PLATFORM_ANT_GAIN_CMD), + IWL_FW_CMD_VER_UNKNOWN); + if (cmd_ver == 1) { + num_sub_bands = IWL_NUM_SUB_BANDS_V1; + gain = cmd->v1.gain[0]; + *cmd_size = sizeof(cmd->v1); + if (fwrt->ppag_ver == 1 || fwrt->ppag_ver == 2) { + IWL_DEBUG_RADIO(fwrt, + "PPAG table rev is %d but FW supports v1, sending truncated table\n", + fwrt->ppag_ver); + cmd->v1.flags &= cpu_to_le32(IWL_PPAG_ETSI_MASK); + } + } else if (cmd_ver == 2 || cmd_ver == 3) { + num_sub_bands = IWL_NUM_SUB_BANDS_V2; + gain = cmd->v2.gain[0]; + *cmd_size = sizeof(cmd->v2); + if (fwrt->ppag_ver == 0) { + IWL_DEBUG_RADIO(fwrt, + "PPAG table is v1 but FW supports v2, sending padded table\n"); + } else if (cmd_ver == 2 && fwrt->ppag_ver == 2) { + IWL_DEBUG_RADIO(fwrt, + "PPAG table is v3 but FW supports v2, sending partial bitmap.\n"); + cmd->v1.flags &= cpu_to_le32(IWL_PPAG_ETSI_MASK); + } + } else { + IWL_DEBUG_RADIO(fwrt, "Unsupported PPAG command version\n"); + return -EINVAL; + } + + for (i = 0; i < IWL_NUM_CHAIN_LIMITS; i++) { + for (j = 0; j < num_sub_bands; j++) { + gain[i * num_sub_bands + j] = + fwrt->ppag_chains[i].subbands[j]; + IWL_DEBUG_RADIO(fwrt, + "PPAG table: chain[%d] band[%d]: gain = %d\n", + i, j, gain[i * num_sub_bands + j]); + } + } + + return 0; +} +IWL_EXPORT_SYMBOL(iwl_read_ppag_table); + +bool iwl_acpi_is_ppag_approved(struct iwl_fw_runtime *fwrt) +{ + + if (!dmi_check_system(dmi_ppag_approved_list)) { + IWL_DEBUG_RADIO(fwrt, + "System vendor '%s' is not in the approved list, disabling PPAG.\n", + dmi_get_system_info(DMI_SYS_VENDOR)); + fwrt->ppag_flags = 0; + return false; + } + + return true; +} +IWL_EXPORT_SYMBOL(iwl_acpi_is_ppag_approved); diff --git a/drivers/net/wireless/intel/iwlwifi/fw/acpi.h b/drivers/net/wireless/intel/iwlwifi/fw/acpi.h new file mode 100644 index 000000000..6f361c591 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/acpi.h @@ -0,0 +1,349 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2018-2022 Intel Corporation + */ +#ifndef __iwl_fw_acpi__ +#define __iwl_fw_acpi__ + +#include <linux/acpi.h> +#include "fw/api/commands.h" +#include "fw/api/power.h" +#include "fw/api/phy.h" +#include "fw/api/nvm-reg.h" +#include "fw/img.h" +#include "iwl-trans.h" + + +#define ACPI_WRDS_METHOD "WRDS" +#define ACPI_EWRD_METHOD "EWRD" +#define ACPI_WGDS_METHOD "WGDS" +#define ACPI_WRDD_METHOD "WRDD" +#define ACPI_SPLC_METHOD "SPLC" +#define ACPI_ECKV_METHOD "ECKV" +#define ACPI_PPAG_METHOD "PPAG" +#define ACPI_WTAS_METHOD "WTAS" + +#define ACPI_WIFI_DOMAIN (0x07) + +#define ACPI_SAR_PROFILE_NUM 4 + +#define ACPI_NUM_GEO_PROFILES 3 +#define ACPI_NUM_GEO_PROFILES_REV3 8 +#define ACPI_GEO_PER_CHAIN_SIZE 3 + +#define ACPI_SAR_NUM_CHAINS_REV0 2 +#define ACPI_SAR_NUM_CHAINS_REV1 2 +#define ACPI_SAR_NUM_CHAINS_REV2 4 +#define ACPI_SAR_NUM_SUB_BANDS_REV0 5 +#define ACPI_SAR_NUM_SUB_BANDS_REV1 11 +#define ACPI_SAR_NUM_SUB_BANDS_REV2 11 + +#define ACPI_WRDS_WIFI_DATA_SIZE_REV0 (ACPI_SAR_NUM_CHAINS_REV0 * \ + ACPI_SAR_NUM_SUB_BANDS_REV0 + 2) +#define ACPI_WRDS_WIFI_DATA_SIZE_REV1 (ACPI_SAR_NUM_CHAINS_REV1 * \ + ACPI_SAR_NUM_SUB_BANDS_REV1 + 2) +#define ACPI_WRDS_WIFI_DATA_SIZE_REV2 (ACPI_SAR_NUM_CHAINS_REV2 * \ + ACPI_SAR_NUM_SUB_BANDS_REV2 + 2) +#define ACPI_EWRD_WIFI_DATA_SIZE_REV0 ((ACPI_SAR_PROFILE_NUM - 1) * \ + ACPI_SAR_NUM_CHAINS_REV0 * \ + ACPI_SAR_NUM_SUB_BANDS_REV0 + 3) +#define ACPI_EWRD_WIFI_DATA_SIZE_REV1 ((ACPI_SAR_PROFILE_NUM - 1) * \ + ACPI_SAR_NUM_CHAINS_REV1 * \ + ACPI_SAR_NUM_SUB_BANDS_REV1 + 3) +#define ACPI_EWRD_WIFI_DATA_SIZE_REV2 ((ACPI_SAR_PROFILE_NUM - 1) * \ + ACPI_SAR_NUM_CHAINS_REV2 * \ + ACPI_SAR_NUM_SUB_BANDS_REV2 + 3) + +/* revision 0 and 1 are identical, except for the semantics in the FW */ +#define ACPI_GEO_NUM_BANDS_REV0 2 +#define ACPI_GEO_NUM_BANDS_REV2 3 +#define ACPI_GEO_NUM_CHAINS 2 + +#define ACPI_WRDD_WIFI_DATA_SIZE 2 +#define ACPI_SPLC_WIFI_DATA_SIZE 2 +#define ACPI_ECKV_WIFI_DATA_SIZE 2 + +/* + * TAS size: 1 elelment for type, + * 1 element for enabled field, + * 1 element for block list size, + * 16 elements for block list array + */ +#define APCI_WTAS_BLACK_LIST_MAX 16 +#define ACPI_WTAS_WIFI_DATA_SIZE (3 + APCI_WTAS_BLACK_LIST_MAX) +#define ACPI_WTAS_ENABLED_MSK 0x1 +#define ACPI_WTAS_OVERRIDE_IEC_MSK 0x2 +#define ACPI_WTAS_ENABLE_IEC_MSK 0x4 +#define ACPI_WTAS_OVERRIDE_IEC_POS 0x1 +#define ACPI_WTAS_ENABLE_IEC_POS 0x2 +#define ACPI_WTAS_USA_UHB_MSK BIT(16) +#define ACPI_WTAS_USA_UHB_POS 16 + + +#define ACPI_PPAG_WIFI_DATA_SIZE_V1 ((IWL_NUM_CHAIN_LIMITS * \ + IWL_NUM_SUB_BANDS_V1) + 2) +#define ACPI_PPAG_WIFI_DATA_SIZE_V2 ((IWL_NUM_CHAIN_LIMITS * \ + IWL_NUM_SUB_BANDS_V2) + 2) + +/* PPAG gain value bounds in 1/8 dBm */ +#define ACPI_PPAG_MIN_LB -16 +#define ACPI_PPAG_MAX_LB 24 +#define ACPI_PPAG_MIN_HB -16 +#define ACPI_PPAG_MAX_HB 40 +#define ACPI_PPAG_MASK 3 +#define IWL_PPAG_ETSI_MASK BIT(0) + +#define IWL_SAR_ENABLE_MSK BIT(0) +#define IWL_REDUCE_POWER_FLAGS_POS 1 + +/* + * The profile for revision 2 is a superset of revision 1, which is in + * turn a superset of revision 0. So we can store all revisions + * inside revision 2, which is what we represent here. + */ +struct iwl_sar_profile_chain { + u8 subbands[ACPI_SAR_NUM_SUB_BANDS_REV2]; +}; + +struct iwl_sar_profile { + bool enabled; + struct iwl_sar_profile_chain chains[ACPI_SAR_NUM_CHAINS_REV2]; +}; + +/* Same thing as with SAR, all revisions fit in revision 2 */ +struct iwl_geo_profile_band { + u8 max; + u8 chains[ACPI_GEO_NUM_CHAINS]; +}; + +struct iwl_geo_profile { + struct iwl_geo_profile_band bands[ACPI_GEO_NUM_BANDS_REV2]; +}; + +/* Same thing as with SAR, all revisions fit in revision 2 */ +struct iwl_ppag_chain { + s8 subbands[ACPI_SAR_NUM_SUB_BANDS_REV2]; +}; + +enum iwl_dsm_funcs_rev_0 { + DSM_FUNC_QUERY = 0, + DSM_FUNC_DISABLE_SRD = 1, + DSM_FUNC_ENABLE_INDONESIA_5G2 = 2, + DSM_FUNC_ENABLE_6E = 3, + DSM_FUNC_11AX_ENABLEMENT = 6, + DSM_FUNC_ENABLE_UNII4_CHAN = 7, + DSM_FUNC_ACTIVATE_CHANNEL = 8, + DSM_FUNC_FORCE_DISABLE_CHANNELS = 9 +}; + +enum iwl_dsm_values_srd { + DSM_VALUE_SRD_ACTIVE, + DSM_VALUE_SRD_PASSIVE, + DSM_VALUE_SRD_DISABLE, + DSM_VALUE_SRD_MAX +}; + +enum iwl_dsm_values_indonesia { + DSM_VALUE_INDONESIA_DISABLE, + DSM_VALUE_INDONESIA_ENABLE, + DSM_VALUE_INDONESIA_RESERVED, + DSM_VALUE_INDONESIA_MAX +}; + +/* DSM RFI uses a different GUID, so need separate definitions */ + +#define DSM_RFI_FUNC_ENABLE 3 + +enum iwl_dsm_values_rfi { + DSM_VALUE_RFI_ENABLE, + DSM_VALUE_RFI_DISABLE, + DSM_VALUE_RFI_MAX +}; + +#ifdef CONFIG_ACPI + +struct iwl_fw_runtime; + +extern const guid_t iwl_guid; +extern const guid_t iwl_rfi_guid; + +void *iwl_acpi_get_object(struct device *dev, acpi_string method); + +int iwl_acpi_get_dsm_u8(struct device *dev, int rev, int func, + const guid_t *guid, u8 *value); + +int iwl_acpi_get_dsm_u32(struct device *dev, int rev, int func, + const guid_t *guid, u32 *value); + +union acpi_object *iwl_acpi_get_wifi_pkg_range(struct device *dev, + union acpi_object *data, + int min_data_size, + int max_data_size, + int *tbl_rev); +/** + * iwl_acpi_get_mcc - read MCC from ACPI, if available + * + * @dev: the struct device + * @mcc: output buffer (3 bytes) that will get the MCC + * + * This function tries to read the current MCC from ACPI if available. + */ +int iwl_acpi_get_mcc(struct device *dev, char *mcc); + +u64 iwl_acpi_get_pwr_limit(struct device *dev); + +/* + * iwl_acpi_get_eckv - read external clock validation from ACPI, if available + * + * @dev: the struct device + * @extl_clk: output var (2 bytes) that will get the clk indication. + * + * This function tries to read the external clock indication + * from ACPI if available. + */ +int iwl_acpi_get_eckv(struct device *dev, u32 *extl_clk); + +int iwl_sar_select_profile(struct iwl_fw_runtime *fwrt, + __le16 *per_chain, u32 n_tables, u32 n_subbands, + int prof_a, int prof_b); + +int iwl_sar_get_wrds_table(struct iwl_fw_runtime *fwrt); + +int iwl_sar_get_ewrd_table(struct iwl_fw_runtime *fwrt); + +int iwl_sar_get_wgds_table(struct iwl_fw_runtime *fwrt); + +bool iwl_sar_geo_support(struct iwl_fw_runtime *fwrt); + +int iwl_sar_geo_init(struct iwl_fw_runtime *fwrt, + struct iwl_per_chain_offset *table, + u32 n_bands, u32 n_profiles); + +int iwl_acpi_get_tas(struct iwl_fw_runtime *fwrt, + union iwl_tas_config_cmd *cmd, int fw_ver); + +__le32 iwl_acpi_get_lari_config_bitmap(struct iwl_fw_runtime *fwrt); + +int iwl_acpi_get_ppag_table(struct iwl_fw_runtime *fwrt); + +int iwl_read_ppag_table(struct iwl_fw_runtime *fwrt, union iwl_ppag_table_cmd *cmd, + int *cmd_size); + +bool iwl_acpi_is_ppag_approved(struct iwl_fw_runtime *fwrt); + +#else /* CONFIG_ACPI */ + +static inline void *iwl_acpi_get_object(struct device *dev, acpi_string method) +{ + return ERR_PTR(-ENOENT); +} + +static inline void *iwl_acpi_get_dsm_object(struct device *dev, int rev, + int func, union acpi_object *args) +{ + return ERR_PTR(-ENOENT); +} + +static inline int iwl_acpi_get_dsm_u8(struct device *dev, int rev, int func, + const guid_t *guid, u8 *value) +{ + return -ENOENT; +} + +static inline int iwl_acpi_get_dsm_u32(struct device *dev, int rev, int func, + const guid_t *guid, u32 *value) +{ + return -ENOENT; +} + +static inline union acpi_object * +iwl_acpi_get_wifi_pkg_range(struct device *dev, + union acpi_object *data, + int min_data_size, int max_data_size, + int *tbl_rev) +{ + return ERR_PTR(-ENOENT); +} + +static inline int iwl_acpi_get_mcc(struct device *dev, char *mcc) +{ + return -ENOENT; +} + +static inline u64 iwl_acpi_get_pwr_limit(struct device *dev) +{ + return 0; +} + +static inline int iwl_acpi_get_eckv(struct device *dev, u32 *extl_clk) +{ + return -ENOENT; +} + +static inline int iwl_sar_select_profile(struct iwl_fw_runtime *fwrt, + __le16 *per_chain, u32 n_tables, u32 n_subbands, + int prof_a, int prof_b) +{ + return -ENOENT; +} + +static inline int iwl_sar_get_wrds_table(struct iwl_fw_runtime *fwrt) +{ + return -ENOENT; +} + +static inline int iwl_sar_get_ewrd_table(struct iwl_fw_runtime *fwrt) +{ + return -ENOENT; +} + +static inline int iwl_sar_get_wgds_table(struct iwl_fw_runtime *fwrt) +{ + return 1; +} + +static inline bool iwl_sar_geo_support(struct iwl_fw_runtime *fwrt) +{ + return false; +} + +static inline int iwl_acpi_get_tas(struct iwl_fw_runtime *fwrt, + union iwl_tas_config_cmd *cmd, int fw_ver) +{ + return -ENOENT; +} + +static inline __le32 iwl_acpi_get_lari_config_bitmap(struct iwl_fw_runtime *fwrt) +{ + return 0; +} + +static inline int iwl_acpi_get_ppag_table(struct iwl_fw_runtime *fwrt) +{ + return -ENOENT; +} + +static inline int iwl_read_ppag_table(struct iwl_fw_runtime *fwrt, + union iwl_ppag_table_cmd *cmd, int *cmd_size) +{ + return -ENOENT; +} + +static inline bool iwl_acpi_is_ppag_approved(struct iwl_fw_runtime *fwrt) +{ + return false; +} + +#endif /* CONFIG_ACPI */ + +static inline union acpi_object * +iwl_acpi_get_wifi_pkg(struct device *dev, + union acpi_object *data, + int data_size, int *tbl_rev) +{ + return iwl_acpi_get_wifi_pkg_range(dev, data, data_size, data_size, + tbl_rev); +} + +#endif /* __iwl_fw_acpi__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/alive.h b/drivers/net/wireless/intel/iwlwifi/fw/api/alive.h new file mode 100644 index 000000000..e00ab21e7 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/alive.h @@ -0,0 +1,180 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018, 2020-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_alive_h__ +#define __iwl_fw_api_alive_h__ + +/* alive response is_valid values */ +#define ALIVE_RESP_UCODE_OK BIT(0) +#define ALIVE_RESP_RFKILL BIT(1) + +/* alive response ver_type values */ +enum { + FW_TYPE_HW = 0, + FW_TYPE_PROT = 1, + FW_TYPE_AP = 2, + FW_TYPE_WOWLAN = 3, + FW_TYPE_TIMING = 4, + FW_TYPE_WIPAN = 5 +}; + +/* alive response ver_subtype values */ +enum { + FW_SUBTYPE_FULL_FEATURE = 0, + FW_SUBTYPE_BOOTSRAP = 1, /* Not valid */ + FW_SUBTYPE_REDUCED = 2, + FW_SUBTYPE_ALIVE_ONLY = 3, + FW_SUBTYPE_WOWLAN = 4, + FW_SUBTYPE_AP_SUBTYPE = 5, + FW_SUBTYPE_WIPAN = 6, + FW_SUBTYPE_INITIALIZE = 9 +}; + +#define IWL_ALIVE_STATUS_ERR 0xDEAD +#define IWL_ALIVE_STATUS_OK 0xCAFE + +#define IWL_ALIVE_FLG_RFKILL BIT(0) + +struct iwl_lmac_debug_addrs { + __le32 error_event_table_ptr; /* SRAM address for error log */ + __le32 log_event_table_ptr; /* SRAM address for LMAC event log */ + __le32 cpu_register_ptr; + __le32 dbgm_config_ptr; + __le32 alive_counter_ptr; + __le32 scd_base_ptr; /* SRAM address for SCD */ + __le32 st_fwrd_addr; /* pointer to Store and forward */ + __le32 st_fwrd_size; +} __packed; /* UCODE_DEBUG_ADDRS_API_S_VER_2 */ + +struct iwl_lmac_alive { + __le32 ucode_major; + __le32 ucode_minor; + u8 ver_subtype; + u8 ver_type; + u8 mac; + u8 opt; + __le32 timestamp; + struct iwl_lmac_debug_addrs dbg_ptrs; +} __packed; /* UCODE_ALIVE_NTFY_API_S_VER_3 */ + +struct iwl_umac_debug_addrs { + __le32 error_info_addr; /* SRAM address for UMAC error log */ + __le32 dbg_print_buff_addr; +} __packed; /* UMAC_DEBUG_ADDRS_API_S_VER_1 */ + +struct iwl_umac_alive { + __le32 umac_major; /* UMAC version: major */ + __le32 umac_minor; /* UMAC version: minor */ + struct iwl_umac_debug_addrs dbg_ptrs; +} __packed; /* UMAC_ALIVE_DATA_API_S_VER_2 */ + +struct iwl_sku_id { + __le32 data[3]; +} __packed; /* SKU_ID_API_S_VER_1 */ + +struct iwl_alive_ntf_v3 { + __le16 status; + __le16 flags; + struct iwl_lmac_alive lmac_data; + struct iwl_umac_alive umac_data; +} __packed; /* UCODE_ALIVE_NTFY_API_S_VER_3 */ + +struct iwl_alive_ntf_v4 { + __le16 status; + __le16 flags; + struct iwl_lmac_alive lmac_data[2]; + struct iwl_umac_alive umac_data; +} __packed; /* UCODE_ALIVE_NTFY_API_S_VER_4 */ + +struct iwl_alive_ntf_v5 { + __le16 status; + __le16 flags; + struct iwl_lmac_alive lmac_data[2]; + struct iwl_umac_alive umac_data; + struct iwl_sku_id sku_id; +} __packed; /* UCODE_ALIVE_NTFY_API_S_VER_5 */ + +struct iwl_imr_alive_info { + __le64 base_addr; + __le32 size; + __le32 enabled; +} __packed; /* IMR_ALIVE_INFO_API_S_VER_1 */ + +struct iwl_alive_ntf_v6 { + __le16 status; + __le16 flags; + struct iwl_lmac_alive lmac_data[2]; + struct iwl_umac_alive umac_data; + struct iwl_sku_id sku_id; + struct iwl_imr_alive_info imr; +} __packed; /* UCODE_ALIVE_NTFY_API_S_VER_6 */ + +/** + * enum iwl_extended_cfg_flag - commands driver may send before + * finishing init flow + * @IWL_INIT_DEBUG_CFG: driver is going to send debug config command + * @IWL_INIT_NVM: driver is going to send NVM_ACCESS commands + * @IWL_INIT_PHY: driver is going to send PHY_DB commands + */ +enum iwl_extended_cfg_flags { + IWL_INIT_DEBUG_CFG, + IWL_INIT_NVM, + IWL_INIT_PHY, +}; + +/** + * struct iwl_extended_cfg_cmd - mark what commands ucode should wait for + * before finishing init flows + * @init_flags: values from iwl_extended_cfg_flags + */ +struct iwl_init_extended_cfg_cmd { + __le32 init_flags; +} __packed; /* INIT_EXTENDED_CFG_CMD_API_S_VER_1 */ + +/** + * struct iwl_radio_version_notif - information on the radio version + * ( RADIO_VERSION_NOTIFICATION = 0x68 ) + * @radio_flavor: radio flavor + * @radio_step: radio version step + * @radio_dash: radio version dash + */ +struct iwl_radio_version_notif { + __le32 radio_flavor; + __le32 radio_step; + __le32 radio_dash; +} __packed; /* RADIO_VERSION_NOTOFICATION_S_VER_1 */ + +enum iwl_card_state_flags { + CARD_ENABLED = 0x00, + HW_CARD_DISABLED = 0x01, + SW_CARD_DISABLED = 0x02, + CT_KILL_CARD_DISABLED = 0x04, + HALT_CARD_DISABLED = 0x08, + CARD_DISABLED_MSK = 0x0f, + CARD_IS_RX_ON = 0x10, +}; + +/** + * enum iwl_error_recovery_flags - flags for error recovery cmd + * @ERROR_RECOVERY_UPDATE_DB: update db from blob sent + * @ERROR_RECOVERY_END_OF_RECOVERY: end of recovery + */ +enum iwl_error_recovery_flags { + ERROR_RECOVERY_UPDATE_DB = BIT(0), + ERROR_RECOVERY_END_OF_RECOVERY = BIT(1), +}; + +/** + * struct iwl_fw_error_recovery_cmd - recovery cmd sent upon assert + * @flags: &enum iwl_error_recovery_flags + * @buf_size: db buffer size in bytes + */ +struct iwl_fw_error_recovery_cmd { + __le32 flags; + __le32 buf_size; +} __packed; /* ERROR_RECOVERY_CMD_HDR_API_S_VER_1 */ + +#endif /* __iwl_fw_api_alive_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/binding.h b/drivers/net/wireless/intel/iwlwifi/fw/api/binding.h new file mode 100644 index 000000000..29e2816e7 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/binding.h @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_binding_h__ +#define __iwl_fw_api_binding_h__ + +#include <fw/file.h> +#include <fw/img.h> + +#define MAX_MACS_IN_BINDING (3) +#define MAX_BINDINGS (4) + +/** + * struct iwl_binding_cmd_v1 - configuring bindings + * ( BINDING_CONTEXT_CMD = 0x2b ) + * @id_and_color: ID and color of the relevant Binding, + * &enum iwl_ctxt_id_and_color + * @action: action to perform, one of FW_CTXT_ACTION_* + * @macs: array of MAC id and colors which belong to the binding, + * &enum iwl_ctxt_id_and_color + * @phy: PHY id and color which belongs to the binding, + * &enum iwl_ctxt_id_and_color + */ +struct iwl_binding_cmd_v1 { + /* COMMON_INDEX_HDR_API_S_VER_1 */ + __le32 id_and_color; + __le32 action; + /* BINDING_DATA_API_S_VER_1 */ + __le32 macs[MAX_MACS_IN_BINDING]; + __le32 phy; +} __packed; /* BINDING_CMD_API_S_VER_1 */ + +/** + * struct iwl_binding_cmd - configuring bindings + * ( BINDING_CONTEXT_CMD = 0x2b ) + * @id_and_color: ID and color of the relevant Binding, + * &enum iwl_ctxt_id_and_color + * @action: action to perform, one of FW_CTXT_ACTION_* + * @macs: array of MAC id and colors which belong to the binding + * &enum iwl_ctxt_id_and_color + * @phy: PHY id and color which belongs to the binding + * &enum iwl_ctxt_id_and_color + * @lmac_id: the lmac id the binding belongs to + */ +struct iwl_binding_cmd { + /* COMMON_INDEX_HDR_API_S_VER_1 */ + __le32 id_and_color; + __le32 action; + /* BINDING_DATA_API_S_VER_1 */ + __le32 macs[MAX_MACS_IN_BINDING]; + __le32 phy; + __le32 lmac_id; +} __packed; /* BINDING_CMD_API_S_VER_2 */ + +#define IWL_BINDING_CMD_SIZE_V1 sizeof(struct iwl_binding_cmd_v1) +#define IWL_LMAC_24G_INDEX 0 +#define IWL_LMAC_5G_INDEX 1 + +static inline u32 iwl_mvm_get_lmac_id(const struct iwl_fw *fw, + enum nl80211_band band){ + if (!fw_has_capa(&fw->ucode_capa, IWL_UCODE_TLV_CAPA_CDB_SUPPORT) || + band == NL80211_BAND_2GHZ) + return IWL_LMAC_24G_INDEX; + return IWL_LMAC_5G_INDEX; +} + +/* The maximal number of fragments in the FW's schedule session */ +#define IWL_MVM_MAX_QUOTA 128 + +/** + * struct iwl_time_quota_data_v1 - configuration of time quota per binding + * @id_and_color: ID and color of the relevant Binding, + * &enum iwl_ctxt_id_and_color + * @quota: absolute time quota in TU. The scheduler will try to divide the + * remainig quota (after Time Events) according to this quota. + * @max_duration: max uninterrupted context duration in TU + */ +struct iwl_time_quota_data_v1 { + __le32 id_and_color; + __le32 quota; + __le32 max_duration; +} __packed; /* TIME_QUOTA_DATA_API_S_VER_1 */ + +/** + * struct iwl_time_quota_cmd - configuration of time quota between bindings + * ( TIME_QUOTA_CMD = 0x2c ) + * @quotas: allocations per binding + * Note: on non-CDB the fourth one is the auxilary mac and is + * essentially zero. + * On CDB the fourth one is a regular binding. + */ +struct iwl_time_quota_cmd_v1 { + struct iwl_time_quota_data_v1 quotas[MAX_BINDINGS]; +} __packed; /* TIME_QUOTA_ALLOCATION_CMD_API_S_VER_1 */ + +enum iwl_quota_low_latency { + IWL_QUOTA_LOW_LATENCY_NONE = 0, + IWL_QUOTA_LOW_LATENCY_TX = BIT(0), + IWL_QUOTA_LOW_LATENCY_RX = BIT(1), + IWL_QUOTA_LOW_LATENCY_TX_RX = + IWL_QUOTA_LOW_LATENCY_TX | IWL_QUOTA_LOW_LATENCY_RX, +}; + +/** + * struct iwl_time_quota_data - configuration of time quota per binding + * @id_and_color: ID and color of the relevant Binding. + * @quota: absolute time quota in TU. The scheduler will try to divide the + * remainig quota (after Time Events) according to this quota. + * @max_duration: max uninterrupted context duration in TU + * @low_latency: low latency status, &enum iwl_quota_low_latency + */ +struct iwl_time_quota_data { + __le32 id_and_color; + __le32 quota; + __le32 max_duration; + __le32 low_latency; +} __packed; /* TIME_QUOTA_DATA_API_S_VER_2 */ + +/** + * struct iwl_time_quota_cmd - configuration of time quota between bindings + * ( TIME_QUOTA_CMD = 0x2c ) + * Note: on non-CDB the fourth one is the auxilary mac and is essentially zero. + * On CDB the fourth one is a regular binding. + * + * @quotas: allocations per binding + */ +struct iwl_time_quota_cmd { + struct iwl_time_quota_data quotas[MAX_BINDINGS]; +} __packed; /* TIME_QUOTA_ALLOCATION_CMD_API_S_VER_2 */ + +#endif /* __iwl_fw_api_binding_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/cmdhdr.h b/drivers/net/wireless/intel/iwlwifi/fw/api/cmdhdr.h new file mode 100644 index 000000000..d130d4f85 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/cmdhdr.h @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_cmdhdr_h__ +#define __iwl_fw_api_cmdhdr_h__ + +/** + * DOC: Host command section + * + * A host command is a command issued by the upper layer to the fw. There are + * several versions of fw that have several APIs. The transport layer is + * completely agnostic to these differences. + * The transport does provide helper functionality (i.e. SYNC / ASYNC mode), + */ +#define SEQ_TO_QUEUE(s) (((s) >> 8) & 0x1f) +#define QUEUE_TO_SEQ(q) (((q) & 0x1f) << 8) +#define SEQ_TO_INDEX(s) ((s) & 0xff) +#define INDEX_TO_SEQ(i) ((i) & 0xff) +#define SEQ_RX_FRAME cpu_to_le16(0x8000) + +/* + * those functions retrieve specific information from + * the id field in the iwl_host_cmd struct which contains + * the command id, the group id and the version of the command + * and vice versa +*/ +static inline u8 iwl_cmd_opcode(u32 cmdid) +{ + return cmdid & 0xFF; +} + +static inline u8 iwl_cmd_groupid(u32 cmdid) +{ + return ((cmdid & 0xFF00) >> 8); +} + +static inline u8 iwl_cmd_version(u32 cmdid) +{ + return ((cmdid & 0xFF0000) >> 16); +} + +static inline u32 iwl_cmd_id(u8 opcode, u8 groupid, u8 version) +{ + return opcode + (groupid << 8) + (version << 16); +} + +/* make u16 wide id out of u8 group and opcode */ +#define WIDE_ID(grp, opcode) (((grp) << 8) | (opcode)) +#define DEF_ID(opcode) ((1 << 8) | (opcode)) + +/* due to the conversion, this group is special; new groups + * should be defined in the appropriate fw-api header files + */ +#define IWL_ALWAYS_LONG_GROUP 1 + +/** + * struct iwl_cmd_header - (short) command header format + * + * This header format appears in the beginning of each command sent from the + * driver, and each response/notification received from uCode. + */ +struct iwl_cmd_header { + /** + * @cmd: Command ID: REPLY_RXON, etc. + */ + u8 cmd; + /** + * @group_id: group ID, for commands with groups + */ + u8 group_id; + /** + * @sequence: + * Sequence number for the command. + * + * The driver sets up the sequence number to values of its choosing. + * uCode does not use this value, but passes it back to the driver + * when sending the response to each driver-originated command, so + * the driver can match the response to the command. Since the values + * don't get used by uCode, the driver may set up an arbitrary format. + * + * There is one exception: uCode sets bit 15 when it originates + * the response/notification, i.e. when the response/notification + * is not a direct response to a command sent by the driver. For + * example, uCode issues REPLY_RX when it sends a received frame + * to the driver; it is not a direct response to any driver command. + * + * The Linux driver uses the following format: + * + * 0:7 tfd index - position within TX queue + * 8:12 TX queue id + * 13:14 reserved + * 15 unsolicited RX or uCode-originated notification + */ + __le16 sequence; +} __packed; + +/** + * struct iwl_cmd_header_wide + * + * This header format appears in the beginning of each command sent from the + * driver, and each response/notification received from uCode. + * this is the wide version that contains more information about the command + * like length, version and command type + * + * @cmd: command ID, like in &struct iwl_cmd_header + * @group_id: group ID, like in &struct iwl_cmd_header + * @sequence: sequence, like in &struct iwl_cmd_header + * @length: length of the command + * @reserved: reserved + * @version: command version + */ +struct iwl_cmd_header_wide { + u8 cmd; + u8 group_id; + __le16 sequence; + __le16 length; + u8 reserved; + u8 version; +} __packed; + +/** + * struct iwl_calib_res_notif_phy_db - Receive phy db chunk after calibrations + * @type: type of the result - mostly ignored + * @length: length of the data + * @data: data, length in @length + */ +struct iwl_calib_res_notif_phy_db { + __le16 type; + __le16 length; + u8 data[]; +} __packed; + +/** + * struct iwl_phy_db_cmd - configure operational ucode + * @type: type of the data + * @length: length of the data + * @data: data, length in @length + */ +struct iwl_phy_db_cmd { + __le16 type; + __le16 length; + u8 data[]; +} __packed; + +/** + * struct iwl_cmd_response - generic response struct for most commands + * @status: status of the command asked, changes for each one + */ +struct iwl_cmd_response { + __le32 status; +}; + +#endif /* __iwl_fw_api_cmdhdr_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/coex.h b/drivers/net/wireless/intel/iwlwifi/fw/api/coex.h new file mode 100644 index 000000000..3e81e9369 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/coex.h @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2013-2014, 2018-2019 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_coex_h__ +#define __iwl_fw_api_coex_h__ + +#include <linux/types.h> +#include <linux/bitops.h> + +#define BITS(nb) (BIT(nb) - 1) + +enum iwl_bt_coex_lut_type { + BT_COEX_TIGHT_LUT = 0, + BT_COEX_LOOSE_LUT, + BT_COEX_TX_DIS_LUT, + + BT_COEX_MAX_LUT, + BT_COEX_INVALID_LUT = 0xff, +}; /* BT_COEX_DECISION_LUT_INDEX_API_E_VER_1 */ + +#define BT_REDUCED_TX_POWER_BIT BIT(7) + +enum iwl_bt_coex_mode { + BT_COEX_DISABLE = 0x0, + BT_COEX_NW = 0x1, + BT_COEX_BT = 0x2, + BT_COEX_WIFI = 0x3, +}; /* BT_COEX_MODES_E */ + +enum iwl_bt_coex_enabled_modules { + BT_COEX_MPLUT_ENABLED = BIT(0), + BT_COEX_MPLUT_BOOST_ENABLED = BIT(1), + BT_COEX_SYNC2SCO_ENABLED = BIT(2), + BT_COEX_CORUN_ENABLED = BIT(3), + BT_COEX_HIGH_BAND_RET = BIT(4), +}; /* BT_COEX_MODULES_ENABLE_E_VER_1 */ + +/** + * struct iwl_bt_coex_cmd - bt coex configuration command + * @mode: &enum iwl_bt_coex_mode + * @enabled_modules: &enum iwl_bt_coex_enabled_modules + * + * The structure is used for the BT_COEX command. + */ +struct iwl_bt_coex_cmd { + __le32 mode; + __le32 enabled_modules; +} __packed; /* BT_COEX_CMD_API_S_VER_6 */ + +/** + * struct iwl_bt_coex_reduced_txp_update_cmd + * @reduced_txp: bit BT_REDUCED_TX_POWER_BIT to enable / disable, rest of the + * bits are the sta_id (value) + */ +struct iwl_bt_coex_reduced_txp_update_cmd { + __le32 reduced_txp; +} __packed; /* BT_COEX_UPDATE_REDUCED_TX_POWER_API_S_VER_1 */ + +/** + * struct iwl_bt_coex_ci_cmd - bt coex channel inhibition command + * @bt_primary_ci: primary channel inhibition bitmap + * @primary_ch_phy_id: primary channel PHY ID + * @bt_secondary_ci: secondary channel inhibition bitmap + * @secondary_ch_phy_id: secondary channel PHY ID + * + * Used for BT_COEX_CI command + */ +struct iwl_bt_coex_ci_cmd { + __le64 bt_primary_ci; + __le32 primary_ch_phy_id; + + __le64 bt_secondary_ci; + __le32 secondary_ch_phy_id; +} __packed; /* BT_CI_MSG_API_S_VER_2 */ + +#define BT_MBOX(n_dw, _msg, _pos, _nbits) \ + BT_MBOX##n_dw##_##_msg##_POS = (_pos), \ + BT_MBOX##n_dw##_##_msg = BITS(_nbits) << BT_MBOX##n_dw##_##_msg##_POS + +enum iwl_bt_mxbox_dw0 { + BT_MBOX(0, LE_SLAVE_LAT, 0, 3), + BT_MBOX(0, LE_PROF1, 3, 1), + BT_MBOX(0, LE_PROF2, 4, 1), + BT_MBOX(0, LE_PROF_OTHER, 5, 1), + BT_MBOX(0, CHL_SEQ_N, 8, 4), + BT_MBOX(0, INBAND_S, 13, 1), + BT_MBOX(0, LE_MIN_RSSI, 16, 4), + BT_MBOX(0, LE_SCAN, 20, 1), + BT_MBOX(0, LE_ADV, 21, 1), + BT_MBOX(0, LE_MAX_TX_POWER, 24, 4), + BT_MBOX(0, OPEN_CON_1, 28, 2), +}; + +enum iwl_bt_mxbox_dw1 { + BT_MBOX(1, BR_MAX_TX_POWER, 0, 4), + BT_MBOX(1, IP_SR, 4, 1), + BT_MBOX(1, LE_MSTR, 5, 1), + BT_MBOX(1, AGGR_TRFC_LD, 8, 6), + BT_MBOX(1, MSG_TYPE, 16, 3), + BT_MBOX(1, SSN, 19, 2), +}; + +enum iwl_bt_mxbox_dw2 { + BT_MBOX(2, SNIFF_ACT, 0, 3), + BT_MBOX(2, PAG, 3, 1), + BT_MBOX(2, INQUIRY, 4, 1), + BT_MBOX(2, CONN, 5, 1), + BT_MBOX(2, SNIFF_INTERVAL, 8, 5), + BT_MBOX(2, DISC, 13, 1), + BT_MBOX(2, SCO_TX_ACT, 16, 2), + BT_MBOX(2, SCO_RX_ACT, 18, 2), + BT_MBOX(2, ESCO_RE_TX, 20, 2), + BT_MBOX(2, SCO_DURATION, 24, 6), +}; + +enum iwl_bt_mxbox_dw3 { + BT_MBOX(3, SCO_STATE, 0, 1), + BT_MBOX(3, SNIFF_STATE, 1, 1), + BT_MBOX(3, A2DP_STATE, 2, 1), + BT_MBOX(3, ACL_STATE, 3, 1), + BT_MBOX(3, MSTR_STATE, 4, 1), + BT_MBOX(3, OBX_STATE, 5, 1), + BT_MBOX(3, A2DP_SRC, 6, 1), + BT_MBOX(3, OPEN_CON_2, 8, 2), + BT_MBOX(3, TRAFFIC_LOAD, 10, 2), + BT_MBOX(3, CHL_SEQN_LSB, 12, 1), + BT_MBOX(3, INBAND_P, 13, 1), + BT_MBOX(3, MSG_TYPE_2, 16, 3), + BT_MBOX(3, SSN_2, 19, 2), + BT_MBOX(3, UPDATE_REQUEST, 21, 1), +}; + +#define BT_MBOX_MSG(_notif, _num, _field) \ + ((le32_to_cpu((_notif)->mbox_msg[(_num)]) & BT_MBOX##_num##_##_field)\ + >> BT_MBOX##_num##_##_field##_POS) + +#define BT_MBOX_PRINT(_num, _field, _end) \ + pos += scnprintf(buf + pos, bufsz - pos, \ + "\t%s: %d%s", \ + #_field, \ + BT_MBOX_MSG(notif, _num, _field), \ + true ? "\n" : ", ") +enum iwl_bt_activity_grading { + BT_OFF = 0, + BT_ON_NO_CONNECTION = 1, + BT_LOW_TRAFFIC = 2, + BT_HIGH_TRAFFIC = 3, + BT_VERY_HIGH_TRAFFIC = 4, + + BT_MAX_AG, +}; /* BT_COEX_BT_ACTIVITY_GRADING_API_E_VER_1 */ + +enum iwl_bt_ci_compliance { + BT_CI_COMPLIANCE_NONE = 0, + BT_CI_COMPLIANCE_PRIMARY = 1, + BT_CI_COMPLIANCE_SECONDARY = 2, + BT_CI_COMPLIANCE_BOTH = 3, +}; /* BT_COEX_CI_COMPLIENCE_E_VER_1 */ + +/** + * struct iwl_bt_coex_profile_notif - notification about BT coex + * @mbox_msg: message from BT to WiFi + * @msg_idx: the index of the message + * @bt_ci_compliance: enum %iwl_bt_ci_compliance + * @primary_ch_lut: LUT used for primary channel &enum iwl_bt_coex_lut_type + * @secondary_ch_lut: LUT used for secondary channel &enum iwl_bt_coex_lut_type + * @bt_activity_grading: the activity of BT &enum iwl_bt_activity_grading + * @ttc_status: is TTC enabled - one bit per PHY + * @rrc_status: is RRC enabled - one bit per PHY + * @reserved: reserved + */ +struct iwl_bt_coex_profile_notif { + __le32 mbox_msg[4]; + __le32 msg_idx; + __le32 bt_ci_compliance; + + __le32 primary_ch_lut; + __le32 secondary_ch_lut; + __le32 bt_activity_grading; + u8 ttc_status; + u8 rrc_status; + __le16 reserved; +} __packed; /* BT_COEX_PROFILE_NTFY_API_S_VER_4 */ + +#endif /* __iwl_fw_api_coex_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h b/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h new file mode 100644 index 000000000..0b052c2e5 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/commands.h @@ -0,0 +1,613 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + * Copyright (C) 2018-2022 Intel Corporation + */ +#ifndef __iwl_fw_api_commands_h__ +#define __iwl_fw_api_commands_h__ + +/** + * enum iwl_mvm_command_groups - command groups for the firmware + * @LEGACY_GROUP: legacy group, uses command IDs from &enum iwl_legacy_cmds + * @LONG_GROUP: legacy group with long header, also uses command IDs + * from &enum iwl_legacy_cmds + * @SYSTEM_GROUP: system group, uses command IDs from + * &enum iwl_system_subcmd_ids + * @MAC_CONF_GROUP: MAC configuration group, uses command IDs from + * &enum iwl_mac_conf_subcmd_ids + * @PHY_OPS_GROUP: PHY operations group, uses command IDs from + * &enum iwl_phy_ops_subcmd_ids + * @DATA_PATH_GROUP: data path group, uses command IDs from + * &enum iwl_data_path_subcmd_ids + * @SCAN_GROUP: scan group, uses command IDs from + * &enum iwl_scan_subcmd_ids + * @NAN_GROUP: NAN group, uses command IDs from &enum iwl_nan_subcmd_ids + * @LOCATION_GROUP: location group, uses command IDs from + * &enum iwl_location_subcmd_ids + * @PROT_OFFLOAD_GROUP: protocol offload group, uses command IDs from + * &enum iwl_prot_offload_subcmd_ids + * @REGULATORY_AND_NVM_GROUP: regulatory/NVM group, uses command IDs from + * &enum iwl_regulatory_and_nvm_subcmd_ids + * @DEBUG_GROUP: Debug group, uses command IDs from &enum iwl_debug_cmds + */ +enum iwl_mvm_command_groups { + LEGACY_GROUP = 0x0, + LONG_GROUP = 0x1, + SYSTEM_GROUP = 0x2, + MAC_CONF_GROUP = 0x3, + PHY_OPS_GROUP = 0x4, + DATA_PATH_GROUP = 0x5, + SCAN_GROUP = 0x6, + NAN_GROUP = 0x7, + LOCATION_GROUP = 0x8, + PROT_OFFLOAD_GROUP = 0xb, + REGULATORY_AND_NVM_GROUP = 0xc, + DEBUG_GROUP = 0xf, +}; + +/** + * enum iwl_legacy_cmds - legacy group command IDs + */ +enum iwl_legacy_cmds { + /** + * @UCODE_ALIVE_NTFY: + * Alive data from the firmware, as described in + * &struct iwl_alive_ntf_v3 or &struct iwl_alive_ntf_v4 or + * &struct iwl_alive_ntf_v5 or &struct iwl_alive_ntf_v6. + */ + UCODE_ALIVE_NTFY = 0x1, + + /** + * @REPLY_ERROR: Cause an error in the firmware, for testing purposes. + */ + REPLY_ERROR = 0x2, + + /** + * @ECHO_CMD: Send data to the device to have it returned immediately. + */ + ECHO_CMD = 0x3, + + /** + * @INIT_COMPLETE_NOTIF: Notification that initialization is complete. + */ + INIT_COMPLETE_NOTIF = 0x4, + + /** + * @PHY_CONTEXT_CMD: + * Add/modify/remove a PHY context, using &struct iwl_phy_context_cmd + * or &struct iwl_phy_context_cmd_v1. + */ + PHY_CONTEXT_CMD = 0x8, + + /** + * @DBG_CFG: Debug configuration command. + */ + DBG_CFG = 0x9, + + /** + * @SCAN_ITERATION_COMPLETE_UMAC: + * Firmware indicates a scan iteration completed, using + * &struct iwl_umac_scan_iter_complete_notif. + */ + SCAN_ITERATION_COMPLETE_UMAC = 0xb5, + + /** + * @SCAN_CFG_CMD: + * uses &struct iwl_scan_config_v1, &struct iwl_scan_config_v2 + * or &struct iwl_scan_config + */ + SCAN_CFG_CMD = 0xc, + + /** + * @SCAN_REQ_UMAC: uses &struct iwl_scan_req_umac + */ + SCAN_REQ_UMAC = 0xd, + + /** + * @SCAN_ABORT_UMAC: uses &struct iwl_umac_scan_abort + */ + SCAN_ABORT_UMAC = 0xe, + + /** + * @SCAN_COMPLETE_UMAC: uses &struct iwl_umac_scan_complete + */ + SCAN_COMPLETE_UMAC = 0xf, + + /** + * @BA_WINDOW_STATUS_NOTIFICATION_ID: + * uses &struct iwl_ba_window_status_notif + */ + BA_WINDOW_STATUS_NOTIFICATION_ID = 0x13, + + /** + * @ADD_STA_KEY: + * &struct iwl_mvm_add_sta_key_cmd_v1 or + * &struct iwl_mvm_add_sta_key_cmd. + */ + ADD_STA_KEY = 0x17, + + /** + * @ADD_STA: + * &struct iwl_mvm_add_sta_cmd or &struct iwl_mvm_add_sta_cmd_v7. + */ + ADD_STA = 0x18, + + /** + * @REMOVE_STA: &struct iwl_mvm_rm_sta_cmd + */ + REMOVE_STA = 0x19, + + /** + * @FW_GET_ITEM_CMD: uses &struct iwl_fw_get_item_cmd + */ + FW_GET_ITEM_CMD = 0x1a, + + /** + * @TX_CMD: uses &struct iwl_tx_cmd or &struct iwl_tx_cmd_gen2 or + * &struct iwl_tx_cmd_gen3, + * response in &struct iwl_mvm_tx_resp or + * &struct iwl_mvm_tx_resp_v3 + */ + TX_CMD = 0x1c, + + /** + * @TXPATH_FLUSH: &struct iwl_tx_path_flush_cmd + */ + TXPATH_FLUSH = 0x1e, + + /** + * @MGMT_MCAST_KEY: + * &struct iwl_mvm_mgmt_mcast_key_cmd or + * &struct iwl_mvm_mgmt_mcast_key_cmd_v1 + */ + MGMT_MCAST_KEY = 0x1f, + + /* scheduler config */ + /** + * @SCD_QUEUE_CFG: &struct iwl_scd_txq_cfg_cmd for older hardware, + * &struct iwl_tx_queue_cfg_cmd with &struct iwl_tx_queue_cfg_rsp + * for newer (22000) hardware. + */ + SCD_QUEUE_CFG = 0x1d, + + /** + * @WEP_KEY: uses &struct iwl_mvm_wep_key_cmd + */ + WEP_KEY = 0x20, + + /** + * @SHARED_MEM_CFG: + * retrieve shared memory configuration - response in + * &struct iwl_shared_mem_cfg + */ + SHARED_MEM_CFG = 0x25, + + /** + * @TDLS_CHANNEL_SWITCH_CMD: uses &struct iwl_tdls_channel_switch_cmd + */ + TDLS_CHANNEL_SWITCH_CMD = 0x27, + + /** + * @TDLS_CHANNEL_SWITCH_NOTIFICATION: + * uses &struct iwl_tdls_channel_switch_notif + */ + TDLS_CHANNEL_SWITCH_NOTIFICATION = 0xaa, + + /** + * @TDLS_CONFIG_CMD: + * &struct iwl_tdls_config_cmd, response in &struct iwl_tdls_config_res + */ + TDLS_CONFIG_CMD = 0xa7, + + /** + * @MAC_CONTEXT_CMD: &struct iwl_mac_ctx_cmd + */ + MAC_CONTEXT_CMD = 0x28, + + /** + * @TIME_EVENT_CMD: + * &struct iwl_time_event_cmd, response in &struct iwl_time_event_resp + */ + TIME_EVENT_CMD = 0x29, /* both CMD and response */ + + /** + * @TIME_EVENT_NOTIFICATION: &struct iwl_time_event_notif + */ + TIME_EVENT_NOTIFICATION = 0x2a, + + /** + * @BINDING_CONTEXT_CMD: + * &struct iwl_binding_cmd or &struct iwl_binding_cmd_v1 + */ + BINDING_CONTEXT_CMD = 0x2b, + + /** + * @TIME_QUOTA_CMD: &struct iwl_time_quota_cmd + */ + TIME_QUOTA_CMD = 0x2c, + + /** + * @NON_QOS_TX_COUNTER_CMD: + * command is &struct iwl_nonqos_seq_query_cmd + */ + NON_QOS_TX_COUNTER_CMD = 0x2d, + + /** + * @LEDS_CMD: command is &struct iwl_led_cmd + */ + LEDS_CMD = 0x48, + + /** + * @LQ_CMD: using &struct iwl_lq_cmd + */ + LQ_CMD = 0x4e, + + /** + * @FW_PAGING_BLOCK_CMD: + * &struct iwl_fw_paging_cmd + */ + FW_PAGING_BLOCK_CMD = 0x4f, + + /** + * @SCAN_OFFLOAD_REQUEST_CMD: uses &struct iwl_scan_req_lmac + */ + SCAN_OFFLOAD_REQUEST_CMD = 0x51, + + /** + * @SCAN_OFFLOAD_ABORT_CMD: abort the scan - no further contents + */ + SCAN_OFFLOAD_ABORT_CMD = 0x52, + + /** + * @HOT_SPOT_CMD: uses &struct iwl_hs20_roc_req + */ + HOT_SPOT_CMD = 0x53, + + /** + * @SCAN_OFFLOAD_COMPLETE: + * notification, &struct iwl_periodic_scan_complete + */ + SCAN_OFFLOAD_COMPLETE = 0x6D, + + /** + * @SCAN_OFFLOAD_UPDATE_PROFILES_CMD: + * update scan offload (scheduled scan) profiles/blocklist/etc. + */ + SCAN_OFFLOAD_UPDATE_PROFILES_CMD = 0x6E, + + /** + * @MATCH_FOUND_NOTIFICATION: scan match found + */ + MATCH_FOUND_NOTIFICATION = 0xd9, + + /** + * @SCAN_ITERATION_COMPLETE: + * uses &struct iwl_lmac_scan_complete_notif + */ + SCAN_ITERATION_COMPLETE = 0xe7, + + /* Phy */ + /** + * @PHY_CONFIGURATION_CMD: &struct iwl_phy_cfg_cmd_v1 or &struct iwl_phy_cfg_cmd_v3 + */ + PHY_CONFIGURATION_CMD = 0x6a, + + /** + * @CALIB_RES_NOTIF_PHY_DB: &struct iwl_calib_res_notif_phy_db + */ + CALIB_RES_NOTIF_PHY_DB = 0x6b, + + /** + * @PHY_DB_CMD: &struct iwl_phy_db_cmd + */ + PHY_DB_CMD = 0x6c, + + /** + * @POWER_TABLE_CMD: &struct iwl_device_power_cmd + */ + POWER_TABLE_CMD = 0x77, + + /** + * @PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION: + * &struct iwl_uapsd_misbehaving_ap_notif + */ + PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION = 0x78, + + /** + * @LTR_CONFIG: &struct iwl_ltr_config_cmd + */ + LTR_CONFIG = 0xee, + + /** + * @REPLY_THERMAL_MNG_BACKOFF: + * Thermal throttling command + */ + REPLY_THERMAL_MNG_BACKOFF = 0x7e, + + /** + * @NVM_ACCESS_CMD: using &struct iwl_nvm_access_cmd + */ + NVM_ACCESS_CMD = 0x88, + + /** + * @BEACON_NOTIFICATION: &struct iwl_extended_beacon_notif + */ + BEACON_NOTIFICATION = 0x90, + + /** + * @BEACON_TEMPLATE_CMD: + * Uses one of &struct iwl_mac_beacon_cmd_v6, + * &struct iwl_mac_beacon_cmd_v7 or &struct iwl_mac_beacon_cmd + * depending on the device version. + */ + BEACON_TEMPLATE_CMD = 0x91, + /** + * @TX_ANT_CONFIGURATION_CMD: &struct iwl_tx_ant_cfg_cmd + */ + TX_ANT_CONFIGURATION_CMD = 0x98, + + /** + * @STATISTICS_CMD: + * one of &struct iwl_statistics_cmd, + * &struct iwl_notif_statistics_v11, + * &struct iwl_notif_statistics_v10, + * &struct iwl_notif_statistics, + * &struct iwl_statistics_operational_ntfy_ver_14 + */ + STATISTICS_CMD = 0x9c, + + /** + * @STATISTICS_NOTIFICATION: + * one of &struct iwl_notif_statistics_v10, + * &struct iwl_notif_statistics_v11, + * &struct iwl_notif_statistic, + * &struct iwl_statistics_operational_ntfy_ver_14 + * &struct iwl_statistics_operational_ntfy + */ + STATISTICS_NOTIFICATION = 0x9d, + + /** + * @EOSP_NOTIFICATION: + * Notify that a service period ended, + * &struct iwl_mvm_eosp_notification + */ + EOSP_NOTIFICATION = 0x9e, + + /** + * @REDUCE_TX_POWER_CMD: + * &struct iwl_dev_tx_power_cmd + */ + REDUCE_TX_POWER_CMD = 0x9f, + + /** + * @MISSED_BEACONS_NOTIFICATION: &struct iwl_missed_beacons_notif + */ + MISSED_BEACONS_NOTIFICATION = 0xa2, + + /** + * @MAC_PM_POWER_TABLE: using &struct iwl_mac_power_cmd + */ + MAC_PM_POWER_TABLE = 0xa9, + + /** + * @MFUART_LOAD_NOTIFICATION: &struct iwl_mfuart_load_notif + */ + MFUART_LOAD_NOTIFICATION = 0xb1, + + /** + * @RSS_CONFIG_CMD: &struct iwl_rss_config_cmd + */ + RSS_CONFIG_CMD = 0xb3, + + /** + * @REPLY_RX_PHY_CMD: &struct iwl_rx_phy_info + */ + REPLY_RX_PHY_CMD = 0xc0, + + /** + * @REPLY_RX_MPDU_CMD: + * &struct iwl_rx_mpdu_res_start or &struct iwl_rx_mpdu_desc + */ + REPLY_RX_MPDU_CMD = 0xc1, + + /** + * @BAR_FRAME_RELEASE: Frame release from BAR notification, used for + * multi-TID BAR (previously, the BAR frame itself was reported + * instead). Uses &struct iwl_bar_frame_release. + */ + BAR_FRAME_RELEASE = 0xc2, + + /** + * @FRAME_RELEASE: + * Frame release (reorder helper) notification, uses + * &struct iwl_frame_release + */ + FRAME_RELEASE = 0xc3, + + /** + * @BA_NOTIF: + * BlockAck notification, uses &struct iwl_mvm_compressed_ba_notif + * or &struct iwl_mvm_ba_notif depending on the HW + */ + BA_NOTIF = 0xc5, + + /* Location Aware Regulatory */ + /** + * @MCC_UPDATE_CMD: using &struct iwl_mcc_update_cmd + */ + MCC_UPDATE_CMD = 0xc8, + + /** + * @MCC_CHUB_UPDATE_CMD: using &struct iwl_mcc_chub_notif + */ + MCC_CHUB_UPDATE_CMD = 0xc9, + + /** + * @MARKER_CMD: trace marker command, uses &struct iwl_mvm_marker + * with &struct iwl_mvm_marker_rsp + */ + MARKER_CMD = 0xcb, + + /** + * @BT_PROFILE_NOTIFICATION: &struct iwl_bt_coex_profile_notif + */ + BT_PROFILE_NOTIFICATION = 0xce, + + /** + * @BT_CONFIG: &struct iwl_bt_coex_cmd + */ + BT_CONFIG = 0x9b, + + /** + * @BT_COEX_UPDATE_REDUCED_TXP: + * &struct iwl_bt_coex_reduced_txp_update_cmd + */ + BT_COEX_UPDATE_REDUCED_TXP = 0x5c, + + /** + * @BT_COEX_CI: &struct iwl_bt_coex_ci_cmd + */ + BT_COEX_CI = 0x5d, + + /** + * @REPLY_SF_CFG_CMD: &struct iwl_sf_cfg_cmd + */ + REPLY_SF_CFG_CMD = 0xd1, + /** + * @REPLY_BEACON_FILTERING_CMD: &struct iwl_beacon_filter_cmd + */ + REPLY_BEACON_FILTERING_CMD = 0xd2, + + /** + * @DTS_MEASUREMENT_NOTIFICATION: + * &struct iwl_dts_measurement_notif_v1 or + * &struct iwl_dts_measurement_notif_v2 + */ + DTS_MEASUREMENT_NOTIFICATION = 0xdd, + + /** + * @LDBG_CONFIG_CMD: configure continuous trace recording + */ + LDBG_CONFIG_CMD = 0xf6, + + /** + * @DEBUG_LOG_MSG: Debugging log data from firmware + */ + DEBUG_LOG_MSG = 0xf7, + + /** + * @MCAST_FILTER_CMD: &struct iwl_mcast_filter_cmd + */ + MCAST_FILTER_CMD = 0xd0, + + /** + * @D3_CONFIG_CMD: &struct iwl_d3_manager_config + */ + D3_CONFIG_CMD = 0xd3, + + /** + * @PROT_OFFLOAD_CONFIG_CMD: Depending on firmware, uses one of + * &struct iwl_proto_offload_cmd_v1, &struct iwl_proto_offload_cmd_v2, + * &struct iwl_proto_offload_cmd_v3_small, + * &struct iwl_proto_offload_cmd_v3_large + */ + PROT_OFFLOAD_CONFIG_CMD = 0xd4, + + /** + * @OFFLOADS_QUERY_CMD: + * No data in command, response in &struct iwl_wowlan_status + */ + OFFLOADS_QUERY_CMD = 0xd5, + + /** + * @D0I3_END_CMD: End D0i3/D3 state, no command data + */ + D0I3_END_CMD = 0xed, + + /** + * @WOWLAN_PATTERNS: &struct iwl_wowlan_patterns_cmd + */ + WOWLAN_PATTERNS = 0xe0, + + /** + * @WOWLAN_CONFIGURATION: &struct iwl_wowlan_config_cmd + */ + WOWLAN_CONFIGURATION = 0xe1, + + /** + * @WOWLAN_TSC_RSC_PARAM: &struct iwl_wowlan_rsc_tsc_params_cmd_v4, + * &struct iwl_wowlan_rsc_tsc_params_cmd + */ + WOWLAN_TSC_RSC_PARAM = 0xe2, + + /** + * @WOWLAN_TKIP_PARAM: &struct iwl_wowlan_tkip_params_cmd + */ + WOWLAN_TKIP_PARAM = 0xe3, + + /** + * @WOWLAN_KEK_KCK_MATERIAL: &struct iwl_wowlan_kek_kck_material_cmd + */ + WOWLAN_KEK_KCK_MATERIAL = 0xe4, + + /** + * @WOWLAN_GET_STATUSES: response in &struct iwl_wowlan_status + */ + WOWLAN_GET_STATUSES = 0xe5, + + /** + * @SCAN_OFFLOAD_PROFILES_QUERY_CMD: + * No command data, response is &struct iwl_scan_offload_profiles_query + */ + SCAN_OFFLOAD_PROFILES_QUERY_CMD = 0x56, +}; + +/** + * enum iwl_system_subcmd_ids - system group command IDs + */ +enum iwl_system_subcmd_ids { + /** + * @SHARED_MEM_CFG_CMD: + * response in &struct iwl_shared_mem_cfg or + * &struct iwl_shared_mem_cfg_v2 + */ + SHARED_MEM_CFG_CMD = 0x0, + + /** + * @SOC_CONFIGURATION_CMD: &struct iwl_soc_configuration_cmd + */ + SOC_CONFIGURATION_CMD = 0x01, + + /** + * @INIT_EXTENDED_CFG_CMD: &struct iwl_init_extended_cfg_cmd + */ + INIT_EXTENDED_CFG_CMD = 0x03, + + /** + * @FW_ERROR_RECOVERY_CMD: &struct iwl_fw_error_recovery_cmd + */ + FW_ERROR_RECOVERY_CMD = 0x7, + + /** + * @RFI_CONFIG_CMD: &struct iwl_rfi_config_cmd + */ + RFI_CONFIG_CMD = 0xb, + + /** + * @RFI_GET_FREQ_TABLE_CMD: &struct iwl_rfi_config_cmd + */ + RFI_GET_FREQ_TABLE_CMD = 0xc, + + /** + * @SYSTEM_FEATURES_CONTROL_CMD: &struct iwl_system_features_control_cmd + */ + SYSTEM_FEATURES_CONTROL_CMD = 0xd, + + /** + * @RFI_DEACTIVATE_NOTIF: &struct iwl_rfi_deactivate_notif + */ + RFI_DEACTIVATE_NOTIF = 0xff, +}; + +#endif /* __iwl_fw_api_commands_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/config.h b/drivers/net/wireless/intel/iwlwifi/fw/api/config.h new file mode 100644 index 000000000..087354b3c --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/config.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2019 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_config_h__ +#define __iwl_fw_api_config_h__ + +/* + * struct iwl_dqa_enable_cmd + * @cmd_queue: the TXQ number of the command queue + */ +struct iwl_dqa_enable_cmd { + __le32 cmd_queue; +} __packed; /* DQA_CONTROL_CMD_API_S_VER_1 */ + +/* + * struct iwl_tx_ant_cfg_cmd + * @valid: valid antenna configuration + */ +struct iwl_tx_ant_cfg_cmd { + __le32 valid; +} __packed; + +/** + * struct iwl_calib_ctrl - Calibration control struct. + * Sent as part of the phy configuration command. + * @flow_trigger: bitmap for which calibrations to perform according to + * flow triggers, using &enum iwl_calib_cfg + * @event_trigger: bitmap for which calibrations to perform according to + * event triggers, using &enum iwl_calib_cfg + */ +struct iwl_calib_ctrl { + __le32 flow_trigger; + __le32 event_trigger; +} __packed; + +/* This enum defines the bitmap of various calibrations to enable in both + * init ucode and runtime ucode through CALIBRATION_CFG_CMD. + */ +enum iwl_calib_cfg { + IWL_CALIB_CFG_XTAL_IDX = BIT(0), + IWL_CALIB_CFG_TEMPERATURE_IDX = BIT(1), + IWL_CALIB_CFG_VOLTAGE_READ_IDX = BIT(2), + IWL_CALIB_CFG_PAPD_IDX = BIT(3), + IWL_CALIB_CFG_TX_PWR_IDX = BIT(4), + IWL_CALIB_CFG_DC_IDX = BIT(5), + IWL_CALIB_CFG_BB_FILTER_IDX = BIT(6), + IWL_CALIB_CFG_LO_LEAKAGE_IDX = BIT(7), + IWL_CALIB_CFG_TX_IQ_IDX = BIT(8), + IWL_CALIB_CFG_TX_IQ_SKEW_IDX = BIT(9), + IWL_CALIB_CFG_RX_IQ_IDX = BIT(10), + IWL_CALIB_CFG_RX_IQ_SKEW_IDX = BIT(11), + IWL_CALIB_CFG_SENSITIVITY_IDX = BIT(12), + IWL_CALIB_CFG_CHAIN_NOISE_IDX = BIT(13), + IWL_CALIB_CFG_DISCONNECTED_ANT_IDX = BIT(14), + IWL_CALIB_CFG_ANT_COUPLING_IDX = BIT(15), + IWL_CALIB_CFG_DAC_IDX = BIT(16), + IWL_CALIB_CFG_ABS_IDX = BIT(17), + IWL_CALIB_CFG_AGC_IDX = BIT(18), +}; + +/** + * struct iwl_phy_specific_cfg - specific PHY filter configuration + * + * Sent as part of the phy configuration command (v3) to configure specific FW + * defined PHY filters that can be applied to each antenna. + * + * @filter_cfg_chain_a: filter config id for LMAC1 chain A + * @filter_cfg_chain_b: filter config id for LMAC1 chain B + * @filter_cfg_chain_c: filter config id for LMAC2 chain A + * @filter_cfg_chain_d: filter config id for LMAC2 chain B + * values: 0 - no filter; 0xffffffff - reserved; otherwise - filter id + */ +struct iwl_phy_specific_cfg { + __le32 filter_cfg_chain_a; + __le32 filter_cfg_chain_b; + __le32 filter_cfg_chain_c; + __le32 filter_cfg_chain_d; +} __packed; /* PHY_SPECIFIC_CONFIGURATION_API_VER_1*/ + +/** + * struct iwl_phy_cfg_cmd - Phy configuration command + * + * @phy_cfg: PHY configuration value, uses &enum iwl_fw_phy_cfg + * @calib_control: calibration control data + */ +struct iwl_phy_cfg_cmd_v1 { + __le32 phy_cfg; + struct iwl_calib_ctrl calib_control; +} __packed; + +/** + * struct iwl_phy_cfg_cmd_v3 - Phy configuration command (v3) + * + * @phy_cfg: PHY configuration value, uses &enum iwl_fw_phy_cfg + * @calib_control: calibration control data + * @phy_specific_cfg: configure predefined PHY filters + */ +struct iwl_phy_cfg_cmd_v3 { + __le32 phy_cfg; + struct iwl_calib_ctrl calib_control; + struct iwl_phy_specific_cfg phy_specific_cfg; +} __packed; /* PHY_CONFIGURATION_CMD_API_S_VER_3 */ + +/* + * enum iwl_dc2dc_config_id - flag ids + * + * Ids of dc2dc configuration flags + */ +enum iwl_dc2dc_config_id { + DCDC_LOW_POWER_MODE_MSK_SET = 0x1, /* not used */ + DCDC_FREQ_TUNE_SET = 0x2, +}; /* MARKER_ID_API_E_VER_1 */ + +#endif /* __iwl_fw_api_config_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/context.h b/drivers/net/wireless/intel/iwlwifi/fw/api/context.h new file mode 100644 index 000000000..105ba7170 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/context.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_context_h__ +#define __iwl_fw_api_context_h__ + +/** + * enum iwl_ctxt_id_and_color - ID and color fields in context dword + * @FW_CTXT_ID_POS: position of the ID + * @FW_CTXT_ID_MSK: mask of the ID + * @FW_CTXT_COLOR_POS: position of the color + * @FW_CTXT_COLOR_MSK: mask of the color + * @FW_CTXT_INVALID: value used to indicate unused/invalid + */ +enum iwl_ctxt_id_and_color { + FW_CTXT_ID_POS = 0, + FW_CTXT_ID_MSK = 0xff << FW_CTXT_ID_POS, + FW_CTXT_COLOR_POS = 8, + FW_CTXT_COLOR_MSK = 0xff << FW_CTXT_COLOR_POS, + FW_CTXT_INVALID = 0xffffffff, +}; + +#define FW_CMD_ID_AND_COLOR(_id, _color) (((_id) << FW_CTXT_ID_POS) |\ + ((_color) << FW_CTXT_COLOR_POS)) + +/* Possible actions on PHYs, MACs and Bindings */ +enum iwl_ctxt_action { + FW_CTXT_ACTION_STUB = 0, + FW_CTXT_ACTION_ADD, + FW_CTXT_ACTION_MODIFY, + FW_CTXT_ACTION_REMOVE, + FW_CTXT_ACTION_NUM +}; /* COMMON_CONTEXT_ACTION_API_E_VER_1 */ + +#endif /* __iwl_fw_api_context_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/d3.h b/drivers/net/wireless/intel/iwlwifi/fw/api/d3.h new file mode 100644 index 000000000..8a613e150 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/d3.h @@ -0,0 +1,863 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_d3_h__ +#define __iwl_fw_api_d3_h__ +#include <iwl-trans.h> + +/** + * enum iwl_d0i3_flags - d0i3 flags + * @IWL_D0I3_RESET_REQUIRE: FW require reset upon resume + */ +enum iwl_d0i3_flags { + IWL_D0I3_RESET_REQUIRE = BIT(0), +}; + +/** + * enum iwl_d3_wakeup_flags - D3 manager wakeup flags + * @IWL_WAKEUP_D3_CONFIG_FW_ERROR: wake up on firmware sysassert + */ +enum iwl_d3_wakeup_flags { + IWL_WAKEUP_D3_CONFIG_FW_ERROR = BIT(0), +}; /* D3_MANAGER_WAKEUP_CONFIG_API_E_VER_3 */ + +/** + * struct iwl_d3_manager_config - D3 manager configuration command + * @min_sleep_time: minimum sleep time (in usec) + * @wakeup_flags: wakeup flags, see &enum iwl_d3_wakeup_flags + * @wakeup_host_timer: force wakeup after this many seconds + * + * The structure is used for the D3_CONFIG_CMD command. + */ +struct iwl_d3_manager_config { + __le32 min_sleep_time; + __le32 wakeup_flags; + __le32 wakeup_host_timer; +} __packed; /* D3_MANAGER_CONFIG_CMD_S_VER_4 */ + + +/* TODO: OFFLOADS_QUERY_API_S_VER_1 */ + +/** + * enum iwl_d3_proto_offloads - enabled protocol offloads + * @IWL_D3_PROTO_OFFLOAD_ARP: ARP data is enabled + * @IWL_D3_PROTO_OFFLOAD_NS: NS (Neighbor Solicitation) is enabled + * @IWL_D3_PROTO_IPV4_VALID: IPv4 data is valid + * @IWL_D3_PROTO_IPV6_VALID: IPv6 data is valid + */ +enum iwl_proto_offloads { + IWL_D3_PROTO_OFFLOAD_ARP = BIT(0), + IWL_D3_PROTO_OFFLOAD_NS = BIT(1), + IWL_D3_PROTO_IPV4_VALID = BIT(2), + IWL_D3_PROTO_IPV6_VALID = BIT(3), +}; + +#define IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V1 2 +#define IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V2 6 +#define IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3L 12 +#define IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3S 4 +#define IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_MAX 12 + +#define IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3L 4 +#define IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3S 2 + +/** + * struct iwl_proto_offload_cmd_common - ARP/NS offload common part + * @enabled: enable flags + * @remote_ipv4_addr: remote address to answer to (or zero if all) + * @host_ipv4_addr: our IPv4 address to respond to queries for + * @arp_mac_addr: our MAC address for ARP responses + * @reserved: unused + */ +struct iwl_proto_offload_cmd_common { + __le32 enabled; + __be32 remote_ipv4_addr; + __be32 host_ipv4_addr; + u8 arp_mac_addr[ETH_ALEN]; + __le16 reserved; +} __packed; + +/** + * struct iwl_proto_offload_cmd_v1 - ARP/NS offload configuration + * @common: common/IPv4 configuration + * @remote_ipv6_addr: remote address to answer to (or zero if all) + * @solicited_node_ipv6_addr: broken -- solicited node address exists + * for each target address + * @target_ipv6_addr: our target addresses + * @ndp_mac_addr: neighbor solicitation response MAC address + * @reserved2: reserved + */ +struct iwl_proto_offload_cmd_v1 { + struct iwl_proto_offload_cmd_common common; + u8 remote_ipv6_addr[16]; + u8 solicited_node_ipv6_addr[16]; + u8 target_ipv6_addr[IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V1][16]; + u8 ndp_mac_addr[ETH_ALEN]; + __le16 reserved2; +} __packed; /* PROT_OFFLOAD_CONFIG_CMD_DB_S_VER_1 */ + +/** + * struct iwl_proto_offload_cmd_v2 - ARP/NS offload configuration + * @common: common/IPv4 configuration + * @remote_ipv6_addr: remote address to answer to (or zero if all) + * @solicited_node_ipv6_addr: broken -- solicited node address exists + * for each target address + * @target_ipv6_addr: our target addresses + * @ndp_mac_addr: neighbor solicitation response MAC address + * @num_valid_ipv6_addrs: number of valid IPv6 addresses + * @reserved2: reserved + */ +struct iwl_proto_offload_cmd_v2 { + struct iwl_proto_offload_cmd_common common; + u8 remote_ipv6_addr[16]; + u8 solicited_node_ipv6_addr[16]; + u8 target_ipv6_addr[IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V2][16]; + u8 ndp_mac_addr[ETH_ALEN]; + u8 num_valid_ipv6_addrs; + u8 reserved2[3]; +} __packed; /* PROT_OFFLOAD_CONFIG_CMD_DB_S_VER_2 */ + +struct iwl_ns_config { + struct in6_addr source_ipv6_addr; + struct in6_addr dest_ipv6_addr; + u8 target_mac_addr[ETH_ALEN]; + __le16 reserved; +} __packed; /* NS_OFFLOAD_CONFIG */ + +struct iwl_targ_addr { + struct in6_addr addr; + __le32 config_num; +} __packed; /* TARGET_IPV6_ADDRESS */ + +/** + * struct iwl_proto_offload_cmd_v3_small - ARP/NS offload configuration + * @common: common/IPv4 configuration + * @num_valid_ipv6_addrs: number of valid IPv6 addresses + * @targ_addrs: target IPv6 addresses + * @ns_config: NS offload configurations + */ +struct iwl_proto_offload_cmd_v3_small { + struct iwl_proto_offload_cmd_common common; + __le32 num_valid_ipv6_addrs; + struct iwl_targ_addr targ_addrs[IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3S]; + struct iwl_ns_config ns_config[IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3S]; +} __packed; /* PROT_OFFLOAD_CONFIG_CMD_DB_S_VER_3 */ + +/** + * struct iwl_proto_offload_cmd_v3_large - ARP/NS offload configuration + * @common: common/IPv4 configuration + * @num_valid_ipv6_addrs: number of valid IPv6 addresses + * @targ_addrs: target IPv6 addresses + * @ns_config: NS offload configurations + */ +struct iwl_proto_offload_cmd_v3_large { + struct iwl_proto_offload_cmd_common common; + __le32 num_valid_ipv6_addrs; + struct iwl_targ_addr targ_addrs[IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3L]; + struct iwl_ns_config ns_config[IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3L]; +} __packed; /* PROT_OFFLOAD_CONFIG_CMD_DB_S_VER_3 */ + +/** + * struct iwl_proto_offload_cmd_v4 - ARP/NS offload configuration + * @sta_id: station id + * @common: common/IPv4 configuration + * @num_valid_ipv6_addrs: number of valid IPv6 addresses + * @targ_addrs: target IPv6 addresses + * @ns_config: NS offload configurations + */ +struct iwl_proto_offload_cmd_v4 { + __le32 sta_id; + struct iwl_proto_offload_cmd_common common; + __le32 num_valid_ipv6_addrs; + struct iwl_targ_addr targ_addrs[IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3L]; + struct iwl_ns_config ns_config[IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3L]; +} __packed; /* PROT_OFFLOAD_CONFIG_CMD_DB_S_VER_4 */ + +/* + * WOWLAN_PATTERNS + */ +#define IWL_WOWLAN_MIN_PATTERN_LEN 16 +#define IWL_WOWLAN_MAX_PATTERN_LEN 128 + +struct iwl_wowlan_pattern_v1 { + u8 mask[IWL_WOWLAN_MAX_PATTERN_LEN / 8]; + u8 pattern[IWL_WOWLAN_MAX_PATTERN_LEN]; + u8 mask_size; + u8 pattern_size; + __le16 reserved; +} __packed; /* WOWLAN_PATTERN_API_S_VER_1 */ + +#define IWL_WOWLAN_MAX_PATTERNS 20 + +/** + * struct iwl_wowlan_patterns_cmd - WoWLAN wakeup patterns + */ +struct iwl_wowlan_patterns_cmd_v1 { + /** + * @n_patterns: number of patterns + */ + __le32 n_patterns; + + /** + * @patterns: the patterns, array length in @n_patterns + */ + struct iwl_wowlan_pattern_v1 patterns[]; +} __packed; /* WOWLAN_PATTERN_ARRAY_API_S_VER_1 */ + +#define IPV4_ADDR_SIZE 4 +#define IPV6_ADDR_SIZE 16 + +enum iwl_wowlan_pattern_type { + WOWLAN_PATTERN_TYPE_BITMASK, + WOWLAN_PATTERN_TYPE_IPV4_TCP_SYN, + WOWLAN_PATTERN_TYPE_IPV6_TCP_SYN, + WOWLAN_PATTERN_TYPE_IPV4_TCP_SYN_WILDCARD, + WOWLAN_PATTERN_TYPE_IPV6_TCP_SYN_WILDCARD, +}; /* WOWLAN_PATTERN_TYPE_API_E_VER_1 */ + +/** + * struct iwl_wowlan_ipv4_tcp_syn - WoWLAN IPv4 TCP SYN pattern data + */ +struct iwl_wowlan_ipv4_tcp_syn { + /** + * @src_addr: source IP address to match + */ + u8 src_addr[IPV4_ADDR_SIZE]; + + /** + * @dst_addr: destination IP address to match + */ + u8 dst_addr[IPV4_ADDR_SIZE]; + + /** + * @src_port: source TCP port to match + */ + __le16 src_port; + + /** + * @dst_port: destination TCP port to match + */ + __le16 dst_port; +} __packed; /* WOWLAN_IPV4_TCP_SYN_API_S_VER_1 */ + +/** + * struct iwl_wowlan_ipv6_tcp_syn - WoWLAN Ipv6 TCP SYN pattern data + */ +struct iwl_wowlan_ipv6_tcp_syn { + /** + * @src_addr: source IP address to match + */ + u8 src_addr[IPV6_ADDR_SIZE]; + + /** + * @dst_addr: destination IP address to match + */ + u8 dst_addr[IPV6_ADDR_SIZE]; + + /** + * @src_port: source TCP port to match + */ + __le16 src_port; + + /** + * @dst_port: destination TCP port to match + */ + __le16 dst_port; +} __packed; /* WOWLAN_IPV6_TCP_SYN_API_S_VER_1 */ + +/** + * union iwl_wowlan_pattern_data - Data for the different pattern types + * + * If wildcard addresses/ports are to be used, the union can be left + * undefined. + */ +union iwl_wowlan_pattern_data { + /** + * @bitmask: bitmask pattern data + */ + struct iwl_wowlan_pattern_v1 bitmask; + + /** + * @ipv4_tcp_syn: IPv4 TCP SYN pattern data + */ + struct iwl_wowlan_ipv4_tcp_syn ipv4_tcp_syn; + + /** + * @ipv6_tcp_syn: IPv6 TCP SYN pattern data + */ + struct iwl_wowlan_ipv6_tcp_syn ipv6_tcp_syn; +}; /* WOWLAN_PATTERN_API_U_VER_1 */ + +/** + * struct iwl_wowlan_pattern_v2 - Pattern entry for the WoWLAN wakeup patterns + */ +struct iwl_wowlan_pattern_v2 { + /** + * @pattern_type: defines the struct type to be used in the union + */ + u8 pattern_type; + + /** + * @reserved: reserved for alignment + */ + u8 reserved[3]; + + /** + * @u: the union containing the match data, or undefined for + * wildcard matches + */ + union iwl_wowlan_pattern_data u; +} __packed; /* WOWLAN_PATTERN_API_S_VER_2 */ + +/** + * struct iwl_wowlan_patterns_cmd - WoWLAN wakeup patterns command + */ +struct iwl_wowlan_patterns_cmd { + /** + * @n_patterns: number of patterns + */ + u8 n_patterns; + + /** + * @n_patterns: sta_id + */ + u8 sta_id; + + /** + * @reserved: reserved for alignment + */ + __le16 reserved; + + /** + * @patterns: the patterns, array length in @n_patterns + */ + struct iwl_wowlan_pattern_v2 patterns[]; +} __packed; /* WOWLAN_PATTERN_ARRAY_API_S_VER_3 */ + +enum iwl_wowlan_wakeup_filters { + IWL_WOWLAN_WAKEUP_MAGIC_PACKET = BIT(0), + IWL_WOWLAN_WAKEUP_PATTERN_MATCH = BIT(1), + IWL_WOWLAN_WAKEUP_BEACON_MISS = BIT(2), + IWL_WOWLAN_WAKEUP_LINK_CHANGE = BIT(3), + IWL_WOWLAN_WAKEUP_GTK_REKEY_FAIL = BIT(4), + IWL_WOWLAN_WAKEUP_EAP_IDENT_REQ = BIT(5), + IWL_WOWLAN_WAKEUP_4WAY_HANDSHAKE = BIT(6), + IWL_WOWLAN_WAKEUP_ENABLE_NET_DETECT = BIT(7), + IWL_WOWLAN_WAKEUP_RF_KILL_DEASSERT = BIT(8), + IWL_WOWLAN_WAKEUP_REMOTE_LINK_LOSS = BIT(9), + IWL_WOWLAN_WAKEUP_REMOTE_SIGNATURE_TABLE = BIT(10), + IWL_WOWLAN_WAKEUP_REMOTE_TCP_EXTERNAL = BIT(11), + IWL_WOWLAN_WAKEUP_REMOTE_WAKEUP_PACKET = BIT(12), + IWL_WOWLAN_WAKEUP_IOAC_MAGIC_PACKET = BIT(13), + IWL_WOWLAN_WAKEUP_HOST_TIMER = BIT(14), + IWL_WOWLAN_WAKEUP_RX_FRAME = BIT(15), + IWL_WOWLAN_WAKEUP_BCN_FILTERING = BIT(16), +}; /* WOWLAN_WAKEUP_FILTER_API_E_VER_4 */ + +enum iwl_wowlan_flags { + IS_11W_ASSOC = BIT(0), + ENABLE_L3_FILTERING = BIT(1), + ENABLE_NBNS_FILTERING = BIT(2), + ENABLE_DHCP_FILTERING = BIT(3), + ENABLE_STORE_BEACON = BIT(4), +}; + +/** + * struct iwl_wowlan_config_cmd - WoWLAN configuration (versions 5 and 6) + * @wakeup_filter: filter from &enum iwl_wowlan_wakeup_filters + * @non_qos_seq: non-QoS sequence counter to use next. + * Reserved if the struct has version >= 6. + * @qos_seq: QoS sequence counters to use next + * @wowlan_ba_teardown_tids: bitmap of BA sessions to tear down + * @is_11n_connection: indicates HT connection + * @offloading_tid: TID reserved for firmware use + * @flags: extra flags, see &enum iwl_wowlan_flags + * @sta_id: station ID for wowlan. + * @reserved: reserved + */ +struct iwl_wowlan_config_cmd { + __le32 wakeup_filter; + __le16 non_qos_seq; + __le16 qos_seq[8]; + u8 wowlan_ba_teardown_tids; + u8 is_11n_connection; + u8 offloading_tid; + u8 flags; + u8 sta_id; + u8 reserved; +} __packed; /* WOWLAN_CONFIG_API_S_VER_5 */ + +#define IWL_NUM_RSC 16 +#define WOWLAN_KEY_MAX_SIZE 32 +#define WOWLAN_GTK_KEYS_NUM 2 +#define WOWLAN_IGTK_KEYS_NUM 2 + +/* + * WOWLAN_TSC_RSC_PARAMS + */ +struct tkip_sc { + __le16 iv16; + __le16 pad; + __le32 iv32; +} __packed; /* TKIP_SC_API_U_VER_1 */ + +struct iwl_tkip_rsc_tsc { + struct tkip_sc unicast_rsc[IWL_NUM_RSC]; + struct tkip_sc multicast_rsc[IWL_NUM_RSC]; + struct tkip_sc tsc; +} __packed; /* TKIP_TSC_RSC_API_S_VER_1 */ + +struct aes_sc { + __le64 pn; +} __packed; /* TKIP_AES_SC_API_U_VER_1 */ + +struct iwl_aes_rsc_tsc { + struct aes_sc unicast_rsc[IWL_NUM_RSC]; + struct aes_sc multicast_rsc[IWL_NUM_RSC]; + struct aes_sc tsc; +} __packed; /* AES_TSC_RSC_API_S_VER_1 */ + +union iwl_all_tsc_rsc { + struct iwl_tkip_rsc_tsc tkip; + struct iwl_aes_rsc_tsc aes; +}; /* ALL_TSC_RSC_API_S_VER_2 */ + +struct iwl_wowlan_rsc_tsc_params_cmd_ver_2 { + union iwl_all_tsc_rsc all_tsc_rsc; +} __packed; /* ALL_TSC_RSC_API_S_VER_2 */ + +struct iwl_wowlan_rsc_tsc_params_cmd_v4 { + struct iwl_wowlan_rsc_tsc_params_cmd_ver_2 params; + __le32 sta_id; +} __packed; /* ALL_TSC_RSC_API_S_VER_4 */ + +struct iwl_wowlan_rsc_tsc_params_cmd { + __le64 ucast_rsc[IWL_MAX_TID_COUNT]; + __le64 mcast_rsc[WOWLAN_GTK_KEYS_NUM][IWL_MAX_TID_COUNT]; + __le32 sta_id; +#define IWL_MCAST_KEY_MAP_INVALID 0xff + u8 mcast_key_id_map[4]; +} __packed; /* ALL_TSC_RSC_API_S_VER_5 */ + +#define IWL_MIC_KEY_SIZE 8 +struct iwl_mic_keys { + u8 tx[IWL_MIC_KEY_SIZE]; + u8 rx_unicast[IWL_MIC_KEY_SIZE]; + u8 rx_mcast[IWL_MIC_KEY_SIZE]; +} __packed; /* MIC_KEYS_API_S_VER_1 */ + +#define IWL_P1K_SIZE 5 +struct iwl_p1k_cache { + __le16 p1k[IWL_P1K_SIZE]; +} __packed; + +#define IWL_NUM_RX_P1K_CACHE 2 + +struct iwl_wowlan_tkip_params_cmd_ver_1 { + struct iwl_mic_keys mic_keys; + struct iwl_p1k_cache tx; + struct iwl_p1k_cache rx_uni[IWL_NUM_RX_P1K_CACHE]; + struct iwl_p1k_cache rx_multi[IWL_NUM_RX_P1K_CACHE]; +} __packed; /* WOWLAN_TKIP_SETTING_API_S_VER_1 */ + +struct iwl_wowlan_tkip_params_cmd { + struct iwl_mic_keys mic_keys; + struct iwl_p1k_cache tx; + struct iwl_p1k_cache rx_uni[IWL_NUM_RX_P1K_CACHE]; + struct iwl_p1k_cache rx_multi[IWL_NUM_RX_P1K_CACHE]; + u8 reversed[2]; + __le32 sta_id; +} __packed; /* WOWLAN_TKIP_SETTING_API_S_VER_2 */ + +#define IWL_KCK_MAX_SIZE 32 +#define IWL_KEK_MAX_SIZE 32 + +struct iwl_wowlan_kek_kck_material_cmd_v2 { + u8 kck[IWL_KCK_MAX_SIZE]; + u8 kek[IWL_KEK_MAX_SIZE]; + __le16 kck_len; + __le16 kek_len; + __le64 replay_ctr; +} __packed; /* KEK_KCK_MATERIAL_API_S_VER_2 */ + +struct iwl_wowlan_kek_kck_material_cmd_v3 { + u8 kck[IWL_KCK_MAX_SIZE]; + u8 kek[IWL_KEK_MAX_SIZE]; + __le16 kck_len; + __le16 kek_len; + __le64 replay_ctr; + __le32 akm; + __le32 gtk_cipher; + __le32 igtk_cipher; + __le32 bigtk_cipher; +} __packed; /* KEK_KCK_MATERIAL_API_S_VER_3 */ + +struct iwl_wowlan_kek_kck_material_cmd_v4 { + __le32 sta_id; + u8 kck[IWL_KCK_MAX_SIZE]; + u8 kek[IWL_KEK_MAX_SIZE]; + __le16 kck_len; + __le16 kek_len; + __le64 replay_ctr; + __le32 akm; + __le32 gtk_cipher; + __le32 igtk_cipher; + __le32 bigtk_cipher; +} __packed; /* KEK_KCK_MATERIAL_API_S_VER_4 */ + +struct iwl_wowlan_get_status_cmd { + __le32 sta_id; +} __packed; /* WOWLAN_GET_STATUSES_CMD_API_S_VER_1 */ + +#define RF_KILL_INDICATOR_FOR_WOWLAN 0x87 + +enum iwl_wowlan_rekey_status { + IWL_WOWLAN_REKEY_POST_REKEY = 0, + IWL_WOWLAN_REKEY_WHILE_REKEY = 1, +}; /* WOWLAN_REKEY_STATUS_API_E_VER_1 */ + +enum iwl_wowlan_wakeup_reason { + IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS = 0, + IWL_WOWLAN_WAKEUP_BY_MAGIC_PACKET = BIT(0), + IWL_WOWLAN_WAKEUP_BY_PATTERN = BIT(1), + IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_MISSED_BEACON = BIT(2), + IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_DEAUTH = BIT(3), + IWL_WOWLAN_WAKEUP_BY_GTK_REKEY_FAILURE = BIT(4), + IWL_WOWLAN_WAKEUP_BY_RFKILL_DEASSERTED = BIT(5), + IWL_WOWLAN_WAKEUP_BY_UCODE_ERROR = BIT(6), + IWL_WOWLAN_WAKEUP_BY_EAPOL_REQUEST = BIT(7), + IWL_WOWLAN_WAKEUP_BY_FOUR_WAY_HANDSHAKE = BIT(8), + IWL_WOWLAN_WAKEUP_BY_REM_WAKE_LINK_LOSS = BIT(9), + IWL_WOWLAN_WAKEUP_BY_REM_WAKE_SIGNATURE_TABLE = BIT(10), + IWL_WOWLAN_WAKEUP_BY_REM_WAKE_TCP_EXTERNAL = BIT(11), + IWL_WOWLAN_WAKEUP_BY_REM_WAKE_WAKEUP_PACKET = BIT(12), + IWL_WOWLAN_WAKEUP_BY_IOAC_MAGIC_PACKET = BIT(13), + IWL_WOWLAN_WAKEUP_BY_D3_WAKEUP_HOST_TIMER = BIT(14), + IWL_WOWLAN_WAKEUP_BY_RXFRAME_FILTERED_IN = BIT(15), + IWL_WOWLAN_WAKEUP_BY_BEACON_FILTERED_IN = BIT(16), + IWL_WAKEUP_BY_11W_UNPROTECTED_DEAUTH_OR_DISASSOC = BIT(17), + IWL_WAKEUP_BY_PATTERN_IPV4_TCP_SYN = BIT(18), + IWL_WAKEUP_BY_PATTERN_IPV4_TCP_SYN_WILDCARD = BIT(19), + IWL_WAKEUP_BY_PATTERN_IPV6_TCP_SYN = BIT(20), + IWL_WAKEUP_BY_PATTERN_IPV6_TCP_SYN_WILDCARD = BIT(21), +}; /* WOWLAN_WAKE_UP_REASON_API_E_VER_2 */ + +struct iwl_wowlan_gtk_status_v1 { + u8 key_index; + u8 reserved[3]; + u8 decrypt_key[16]; + u8 tkip_mic_key[8]; + struct iwl_wowlan_rsc_tsc_params_cmd_ver_2 rsc; +} __packed; /* WOWLAN_GTK_MATERIAL_VER_1 */ + +/** + * struct iwl_wowlan_gtk_status_v2 - GTK status + * @key: GTK material + * @key_len: GTK legth, if set to 0, the key is not available + * @key_flags: information about the key: + * bits[0:1]: key index assigned by the AP + * bits[2:6]: GTK index of the key in the internal DB + * bit[7]: Set iff this is the currently used GTK + * @reserved: padding + * @tkip_mic_key: TKIP RX MIC key + * @rsc: TSC RSC counters + */ +struct iwl_wowlan_gtk_status_v2 { + u8 key[WOWLAN_KEY_MAX_SIZE]; + u8 key_len; + u8 key_flags; + u8 reserved[2]; + u8 tkip_mic_key[8]; + struct iwl_wowlan_rsc_tsc_params_cmd_ver_2 rsc; +} __packed; /* WOWLAN_GTK_MATERIAL_VER_2 */ + +/** + * struct iwl_wowlan_all_rsc_tsc_v5 - key counters + * @ucast_rsc: unicast RSC values + * @mcast_rsc: multicast RSC values (per key map value) + * @sta_id: station ID + * @mcast_key_id_map: map of key id to @mcast_rsc entry + */ +struct iwl_wowlan_all_rsc_tsc_v5 { + __le64 ucast_rsc[IWL_MAX_TID_COUNT]; + __le64 mcast_rsc[2][IWL_MAX_TID_COUNT]; + __le32 sta_id; + u8 mcast_key_id_map[4]; +} __packed; /* ALL_TSC_RSC_API_S_VER_5 */ + +/** + * struct iwl_wowlan_gtk_status_v3 - GTK status + * @key: GTK material + * @key_len: GTK length, if set to 0, the key is not available + * @key_flags: information about the key: + * bits[0:1]: key index assigned by the AP + * bits[2:6]: GTK index of the key in the internal DB + * bit[7]: Set iff this is the currently used GTK + * @reserved: padding + * @tkip_mic_key: TKIP RX MIC key + * @sc: RSC/TSC counters + */ +struct iwl_wowlan_gtk_status_v3 { + u8 key[WOWLAN_KEY_MAX_SIZE]; + u8 key_len; + u8 key_flags; + u8 reserved[2]; + u8 tkip_mic_key[IWL_MIC_KEY_SIZE]; + struct iwl_wowlan_all_rsc_tsc_v5 sc; +} __packed; /* WOWLAN_GTK_MATERIAL_VER_3 */ + +#define IWL_WOWLAN_GTK_IDX_MASK (BIT(0) | BIT(1)) + +/** + * struct iwl_wowlan_igtk_status - IGTK status + * @key: IGTK material + * @ipn: the IGTK packet number (replay counter) + * @key_len: IGTK length, if set to 0, the key is not available + * @key_flags: information about the key: + * bits[0]: key index assigned by the AP (0: index 4, 1: index 5) + * bits[1:5]: IGTK index of the key in the internal DB + * bit[6]: Set iff this is the currently used IGTK + */ +struct iwl_wowlan_igtk_status { + u8 key[WOWLAN_KEY_MAX_SIZE]; + u8 ipn[6]; + u8 key_len; + u8 key_flags; +} __packed; /* WOWLAN_IGTK_MATERIAL_VER_1 */ + +/** + * struct iwl_wowlan_status_v6 - WoWLAN status + * @gtk: GTK data + * @replay_ctr: GTK rekey replay counter + * @pattern_number: number of the matched pattern + * @non_qos_seq_ctr: non-QoS sequence counter to use next + * @qos_seq_ctr: QoS sequence counters to use next + * @wakeup_reasons: wakeup reasons, see &enum iwl_wowlan_wakeup_reason + * @num_of_gtk_rekeys: number of GTK rekeys + * @transmitted_ndps: number of transmitted neighbor discovery packets + * @received_beacons: number of received beacons + * @wake_packet_length: wakeup packet length + * @wake_packet_bufsize: wakeup packet buffer size + * @wake_packet: wakeup packet + */ +struct iwl_wowlan_status_v6 { + struct iwl_wowlan_gtk_status_v1 gtk; + __le64 replay_ctr; + __le16 pattern_number; + __le16 non_qos_seq_ctr; + __le16 qos_seq_ctr[8]; + __le32 wakeup_reasons; + __le32 num_of_gtk_rekeys; + __le32 transmitted_ndps; + __le32 received_beacons; + __le32 wake_packet_length; + __le32 wake_packet_bufsize; + u8 wake_packet[]; /* can be truncated from _length to _bufsize */ +} __packed; /* WOWLAN_STATUSES_API_S_VER_6 */ + +/** + * struct iwl_wowlan_status_v7 - WoWLAN status + * @gtk: GTK data + * @igtk: IGTK data + * @replay_ctr: GTK rekey replay counter + * @pattern_number: number of the matched pattern + * @non_qos_seq_ctr: non-QoS sequence counter to use next + * @qos_seq_ctr: QoS sequence counters to use next + * @wakeup_reasons: wakeup reasons, see &enum iwl_wowlan_wakeup_reason + * @num_of_gtk_rekeys: number of GTK rekeys + * @transmitted_ndps: number of transmitted neighbor discovery packets + * @received_beacons: number of received beacons + * @wake_packet_length: wakeup packet length + * @wake_packet_bufsize: wakeup packet buffer size + * @wake_packet: wakeup packet + */ +struct iwl_wowlan_status_v7 { + struct iwl_wowlan_gtk_status_v2 gtk[WOWLAN_GTK_KEYS_NUM]; + struct iwl_wowlan_igtk_status igtk[WOWLAN_IGTK_KEYS_NUM]; + __le64 replay_ctr; + __le16 pattern_number; + __le16 non_qos_seq_ctr; + __le16 qos_seq_ctr[8]; + __le32 wakeup_reasons; + __le32 num_of_gtk_rekeys; + __le32 transmitted_ndps; + __le32 received_beacons; + __le32 wake_packet_length; + __le32 wake_packet_bufsize; + u8 wake_packet[]; /* can be truncated from _length to _bufsize */ +} __packed; /* WOWLAN_STATUSES_API_S_VER_7 */ + +/** + * struct iwl_wowlan_status_v9 - WoWLAN status (versions 9 and 10) + * @gtk: GTK data + * @igtk: IGTK data + * @replay_ctr: GTK rekey replay counter + * @pattern_number: number of the matched pattern + * @non_qos_seq_ctr: non-QoS sequence counter to use next. + * Reserved if the struct has version >= 10. + * @qos_seq_ctr: QoS sequence counters to use next + * @wakeup_reasons: wakeup reasons, see &enum iwl_wowlan_wakeup_reason + * @num_of_gtk_rekeys: number of GTK rekeys + * @transmitted_ndps: number of transmitted neighbor discovery packets + * @received_beacons: number of received beacons + * @wake_packet_length: wakeup packet length + * @wake_packet_bufsize: wakeup packet buffer size + * @tid_tear_down: bit mask of tids whose BA sessions were closed + * in suspend state + * @reserved: unused + * @wake_packet: wakeup packet + */ +struct iwl_wowlan_status_v9 { + struct iwl_wowlan_gtk_status_v2 gtk[WOWLAN_GTK_KEYS_NUM]; + struct iwl_wowlan_igtk_status igtk[WOWLAN_IGTK_KEYS_NUM]; + __le64 replay_ctr; + __le16 pattern_number; + __le16 non_qos_seq_ctr; + __le16 qos_seq_ctr[8]; + __le32 wakeup_reasons; + __le32 num_of_gtk_rekeys; + __le32 transmitted_ndps; + __le32 received_beacons; + __le32 wake_packet_length; + __le32 wake_packet_bufsize; + u8 tid_tear_down; + u8 reserved[3]; + u8 wake_packet[]; /* can be truncated from _length to _bufsize */ +} __packed; /* WOWLAN_STATUSES_RSP_API_S_VER_9 */ + +/** + * struct iwl_wowlan_status_v12 - WoWLAN status + * @gtk: GTK data + * @igtk: IGTK data + * @replay_ctr: GTK rekey replay counter + * @pattern_number: number of the matched pattern + * @non_qos_seq_ctr: non-QoS sequence counter to use next. + * Reserved if the struct has version >= 10. + * @qos_seq_ctr: QoS sequence counters to use next + * @wakeup_reasons: wakeup reasons, see &enum iwl_wowlan_wakeup_reason + * @num_of_gtk_rekeys: number of GTK rekeys + * @transmitted_ndps: number of transmitted neighbor discovery packets + * @received_beacons: number of received beacons + * @wake_packet_length: wakeup packet length + * @wake_packet_bufsize: wakeup packet buffer size + * @tid_tear_down: bit mask of tids whose BA sessions were closed + * in suspend state + * @reserved: unused + * @wake_packet: wakeup packet + */ +struct iwl_wowlan_status_v12 { + struct iwl_wowlan_gtk_status_v3 gtk[WOWLAN_GTK_KEYS_NUM]; + struct iwl_wowlan_igtk_status igtk[WOWLAN_IGTK_KEYS_NUM]; + __le64 replay_ctr; + __le16 pattern_number; + __le16 non_qos_seq_ctr; + __le16 qos_seq_ctr[8]; + __le32 wakeup_reasons; + __le32 num_of_gtk_rekeys; + __le32 transmitted_ndps; + __le32 received_beacons; + __le32 wake_packet_length; + __le32 wake_packet_bufsize; + u8 tid_tear_down; + u8 reserved[3]; + u8 wake_packet[]; /* can be truncated from _length to _bufsize */ +} __packed; /* WOWLAN_STATUSES_RSP_API_S_VER_12 */ + +/** + * struct iwl_wowlan_info_notif_v1 - WoWLAN information notification + * @gtk: GTK data + * @igtk: IGTK data + * @replay_ctr: GTK rekey replay counter + * @pattern_number: number of the matched patterns + * @reserved1: reserved + * @qos_seq_ctr: QoS sequence counters to use next + * @wakeup_reasons: wakeup reasons, see &enum iwl_wowlan_wakeup_reason + * @num_of_gtk_rekeys: number of GTK rekeys + * @transmitted_ndps: number of transmitted neighbor discovery packets + * @received_beacons: number of received beacons + * @wake_packet_length: wakeup packet length + * @wake_packet_bufsize: wakeup packet buffer size + * @tid_tear_down: bit mask of tids whose BA sessions were closed + * in suspend state + * @station_id: station id + * @reserved2: reserved + */ +struct iwl_wowlan_info_notif_v1 { + struct iwl_wowlan_gtk_status_v3 gtk[WOWLAN_GTK_KEYS_NUM]; + struct iwl_wowlan_igtk_status igtk[WOWLAN_IGTK_KEYS_NUM]; + __le64 replay_ctr; + __le16 pattern_number; + __le16 reserved1; + __le16 qos_seq_ctr[8]; + __le32 wakeup_reasons; + __le32 num_of_gtk_rekeys; + __le32 transmitted_ndps; + __le32 received_beacons; + __le32 wake_packet_length; + __le32 wake_packet_bufsize; + u8 tid_tear_down; + u8 station_id; + u8 reserved2[2]; +} __packed; /* WOWLAN_INFO_NTFY_API_S_VER_1 */ + +/** + * struct iwl_wowlan_info_notif - WoWLAN information notification + * @gtk: GTK data + * @igtk: IGTK data + * @replay_ctr: GTK rekey replay counter + * @pattern_number: number of the matched patterns + * @reserved1: reserved + * @qos_seq_ctr: QoS sequence counters to use next + * @wakeup_reasons: wakeup reasons, see &enum iwl_wowlan_wakeup_reason + * @num_of_gtk_rekeys: number of GTK rekeys + * @transmitted_ndps: number of transmitted neighbor discovery packets + * @received_beacons: number of received beacons + * @tid_tear_down: bit mask of tids whose BA sessions were closed + * in suspend state + * @station_id: station id + * @reserved2: reserved + */ +struct iwl_wowlan_info_notif { + struct iwl_wowlan_gtk_status_v3 gtk[WOWLAN_GTK_KEYS_NUM]; + struct iwl_wowlan_igtk_status igtk[WOWLAN_IGTK_KEYS_NUM]; + __le64 replay_ctr; + __le16 pattern_number; + __le16 reserved1; + __le16 qos_seq_ctr[8]; + __le32 wakeup_reasons; + __le32 num_of_gtk_rekeys; + __le32 transmitted_ndps; + __le32 received_beacons; + u8 tid_tear_down; + u8 station_id; + u8 reserved2[2]; +} __packed; /* WOWLAN_INFO_NTFY_API_S_VER_2 */ + +/** + * struct iwl_wowlan_wake_pkt_notif - WoWLAN wake packet notification + * @wake_packet_length: wakeup packet length + * @station_id: station id + * @reserved: unused + * @wake_packet: wakeup packet + */ +struct iwl_wowlan_wake_pkt_notif { + __le32 wake_packet_length; + u8 station_id; + u8 reserved[3]; + u8 wake_packet[1]; +} __packed; /* WOWLAN_WAKE_PKT_NTFY_API_S_VER_1 */ + +/** + * struct iwl_mvm_d3_end_notif - d3 end notification + * @flags: See &enum iwl_d0i3_flags + */ +struct iwl_mvm_d3_end_notif { + __le32 flags; +} __packed; + +/* TODO: NetDetect API */ + +#endif /* __iwl_fw_api_d3_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h b/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h new file mode 100644 index 000000000..43619acc2 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h @@ -0,0 +1,406 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_datapath_h__ +#define __iwl_fw_api_datapath_h__ + +/** + * enum iwl_data_path_subcmd_ids - data path group commands + */ +enum iwl_data_path_subcmd_ids { + /** + * @DQA_ENABLE_CMD: &struct iwl_dqa_enable_cmd + */ + DQA_ENABLE_CMD = 0x0, + + /** + * @UPDATE_MU_GROUPS_CMD: &struct iwl_mu_group_mgmt_cmd + */ + UPDATE_MU_GROUPS_CMD = 0x1, + + /** + * @TRIGGER_RX_QUEUES_NOTIF_CMD: &struct iwl_rxq_sync_cmd + */ + TRIGGER_RX_QUEUES_NOTIF_CMD = 0x2, + + /** + * @STA_HE_CTXT_CMD: &struct iwl_he_sta_context_cmd + */ + STA_HE_CTXT_CMD = 0x7, + + /** + * @RLC_CONFIG_CMD: &struct iwl_rlc_config_cmd + */ + RLC_CONFIG_CMD = 0x8, + + /** + * @RFH_QUEUE_CONFIG_CMD: &struct iwl_rfh_queue_config + */ + RFH_QUEUE_CONFIG_CMD = 0xD, + + /** + * @TLC_MNG_CONFIG_CMD: &struct iwl_tlc_config_cmd_v4 + */ + TLC_MNG_CONFIG_CMD = 0xF, + + /** + * @HE_AIR_SNIFFER_CONFIG_CMD: &struct iwl_he_monitor_cmd + */ + HE_AIR_SNIFFER_CONFIG_CMD = 0x13, + + /** + * @CHEST_COLLECTOR_FILTER_CONFIG_CMD: Configure the CSI + * matrix collection, uses &struct iwl_channel_estimation_cfg + */ + CHEST_COLLECTOR_FILTER_CONFIG_CMD = 0x14, + + /** + * @RX_BAID_ALLOCATION_CONFIG_CMD: Allocate/deallocate a BAID for an RX + * blockack session, uses &struct iwl_rx_baid_cfg_cmd for the + * command, and &struct iwl_rx_baid_cfg_resp as a response. + */ + RX_BAID_ALLOCATION_CONFIG_CMD = 0x16, + + /** + * @SCD_QUEUE_CONFIG_CMD: new scheduler queue allocation/config/removal + * command, uses &struct iwl_scd_queue_cfg_cmd and the response + * is (same as before) &struct iwl_tx_queue_cfg_rsp. + */ + SCD_QUEUE_CONFIG_CMD = 0x17, + + /** + * @MONITOR_NOTIF: Datapath monitoring notification, using + * &struct iwl_datapath_monitor_notif + */ + MONITOR_NOTIF = 0xF4, + + /** + * @RX_NO_DATA_NOTIF: &struct iwl_rx_no_data + */ + RX_NO_DATA_NOTIF = 0xF5, + + /** + * @THERMAL_DUAL_CHAIN_DISABLE_REQ: firmware request for SMPS mode, + * &struct iwl_thermal_dual_chain_request + */ + THERMAL_DUAL_CHAIN_REQUEST = 0xF6, + + /** + * @TLC_MNG_UPDATE_NOTIF: &struct iwl_tlc_update_notif + */ + TLC_MNG_UPDATE_NOTIF = 0xF7, + + /** + * @STA_PM_NOTIF: &struct iwl_mvm_pm_state_notification + */ + STA_PM_NOTIF = 0xFD, + + /** + * @MU_GROUP_MGMT_NOTIF: &struct iwl_mu_group_mgmt_notif + */ + MU_GROUP_MGMT_NOTIF = 0xFE, + + /** + * @RX_QUEUES_NOTIFICATION: &struct iwl_rxq_sync_notification + */ + RX_QUEUES_NOTIFICATION = 0xFF, +}; + +/** + * struct iwl_mu_group_mgmt_cmd - VHT MU-MIMO group configuration + * + * @reserved: reserved + * @membership_status: a bitmap of MU groups + * @user_position:the position of station in a group. If the station is in the + * group then bits (group * 2) is the position -1 + */ +struct iwl_mu_group_mgmt_cmd { + __le32 reserved; + __le32 membership_status[2]; + __le32 user_position[4]; +} __packed; /* MU_GROUP_ID_MNG_TABLE_API_S_VER_1 */ + +/** + * struct iwl_mu_group_mgmt_notif - VHT MU-MIMO group id notification + * + * @membership_status: a bitmap of MU groups + * @user_position: the position of station in a group. If the station is in the + * group then bits (group * 2) is the position -1 + */ +struct iwl_mu_group_mgmt_notif { + __le32 membership_status[2]; + __le32 user_position[4]; +} __packed; /* MU_GROUP_MNG_NTFY_API_S_VER_1 */ + +enum iwl_channel_estimation_flags { + IWL_CHANNEL_ESTIMATION_ENABLE = BIT(0), + IWL_CHANNEL_ESTIMATION_TIMER = BIT(1), + IWL_CHANNEL_ESTIMATION_COUNTER = BIT(2), +}; + +/** + * struct iwl_channel_estimation_cfg - channel estimation reporting config + */ +struct iwl_channel_estimation_cfg { + /** + * @flags: flags, see &enum iwl_channel_estimation_flags + */ + __le32 flags; + /** + * @timer: if enabled via flags, automatically disable after this many + * microseconds + */ + __le32 timer; + /** + * @count: if enabled via flags, automatically disable after this many + * frames with channel estimation matrix were captured + */ + __le32 count; + /** + * @rate_n_flags_mask: only try to record the channel estimation matrix + * if the rate_n_flags value for the received frame (let's call + * that rx_rnf) matches the mask/value given here like this: + * (rx_rnf & rate_n_flags_mask) == rate_n_flags_val. + */ + __le32 rate_n_flags_mask; + /** + * @rate_n_flags_val: see @rate_n_flags_mask + */ + __le32 rate_n_flags_val; + /** + * @reserved: reserved (for alignment) + */ + __le32 reserved; + /** + * @frame_types: bitmap of frame types to capture, the received frame's + * subtype|type takes 6 bits in the frame and the corresponding bit + * in this field must be set to 1 to capture channel estimation for + * that frame type. Set to all-ones to enable capturing for all + * frame types. + */ + __le64 frame_types; +} __packed; /* CHEST_COLLECTOR_FILTER_CMD_API_S_VER_1 */ + +enum iwl_datapath_monitor_notif_type { + IWL_DP_MON_NOTIF_TYPE_EXT_CCA, +}; + +struct iwl_datapath_monitor_notif { + __le32 type; + u8 mac_id; + u8 reserved[3]; +} __packed; /* MONITOR_NTF_API_S_VER_1 */ + +/** + * enum iwl_thermal_dual_chain_req_events - firmware SMPS request event + * @THERMAL_DUAL_CHAIN_REQ_ENABLE: (re-)enable dual-chain operation + * (subject to other constraints) + * @THERMAL_DUAL_CHAIN_REQ_DISABLE: disable dual-chain operation + * (static SMPS) + */ +enum iwl_thermal_dual_chain_req_events { + THERMAL_DUAL_CHAIN_REQ_ENABLE, + THERMAL_DUAL_CHAIN_REQ_DISABLE, +}; /* THERMAL_DUAL_CHAIN_DISABLE_STATE_API_E_VER_1 */ + +/** + * struct iwl_thermal_dual_chain_request - SMPS request + * @event: the type of request, see &enum iwl_thermal_dual_chain_req_events + */ +struct iwl_thermal_dual_chain_request { + __le32 event; +} __packed; /* THERMAL_DUAL_CHAIN_DISABLE_REQ_NTFY_API_S_VER_1 */ + +enum iwl_rlc_chain_info { + IWL_RLC_CHAIN_INFO_DRIVER_FORCE = BIT(0), + IWL_RLC_CHAIN_INFO_VALID = 0x000e, + IWL_RLC_CHAIN_INFO_FORCE = 0x0070, + IWL_RLC_CHAIN_INFO_FORCE_MIMO = 0x0380, + IWL_RLC_CHAIN_INFO_COUNT = 0x0c00, + IWL_RLC_CHAIN_INFO_MIMO_COUNT = 0x3000, +}; + +/** + * struct iwl_rlc_properties - RLC properties + * @rx_chain_info: RX chain info, &enum iwl_rlc_chain_info + * @reserved: reserved + */ +struct iwl_rlc_properties { + __le32 rx_chain_info; + __le32 reserved; +} __packed; /* RLC_PROPERTIES_S_VER_1 */ + +enum iwl_sad_mode { + IWL_SAD_MODE_ENABLED = BIT(0), + IWL_SAD_MODE_DEFAULT_ANT_MSK = 0x6, + IWL_SAD_MODE_DEFAULT_ANT_FW = 0x0, + IWL_SAD_MODE_DEFAULT_ANT_A = 0x2, + IWL_SAD_MODE_DEFAULT_ANT_B = 0x4, +}; + +/** + * struct iwl_sad_properties - SAD properties + * @chain_a_sad_mode: chain A SAD mode, &enum iwl_sad_mode + * @chain_b_sad_mode: chain B SAD mode, &enum iwl_sad_mode + * @mac_id: MAC index + * @reserved: reserved + */ +struct iwl_sad_properties { + __le32 chain_a_sad_mode; + __le32 chain_b_sad_mode; + __le32 mac_id; + __le32 reserved; +} __packed; + +/** + * struct iwl_rlc_config_cmd - RLC configuration + * @phy_id: PHY index + * @rlc: RLC properties, &struct iwl_rlc_properties + * @sad: SAD (single antenna diversity) options, &struct iwl_sad_properties + * @flags: flags, &enum iwl_rlc_flags + * @reserved: reserved + */ +struct iwl_rlc_config_cmd { + __le32 phy_id; + struct iwl_rlc_properties rlc; + struct iwl_sad_properties sad; + u8 flags; + u8 reserved[3]; +} __packed; /* RLC_CONFIG_CMD_API_S_VER_2 */ + +#define IWL_MAX_BAID_OLD 16 /* MAX_IMMEDIATE_BA_API_D_VER_2 */ +#define IWL_MAX_BAID 32 /* MAX_IMMEDIATE_BA_API_D_VER_3 */ + +/** + * enum iwl_rx_baid_action - BAID allocation/config action + * @IWL_RX_BAID_ACTION_ADD: add a new BAID session + * @IWL_RX_BAID_ACTION_MODIFY: modify the BAID session + * @IWL_RX_BAID_ACTION_REMOVE: remove the BAID session + */ +enum iwl_rx_baid_action { + IWL_RX_BAID_ACTION_ADD, + IWL_RX_BAID_ACTION_MODIFY, + IWL_RX_BAID_ACTION_REMOVE, +}; /* RX_BAID_ALLOCATION_ACTION_E_VER_1 */ + +/** + * struct iwl_rx_baid_cfg_cmd_alloc - BAID allocation data + * @sta_id_mask: station ID mask + * @tid: the TID for this session + * @reserved: reserved + * @ssn: the starting sequence number + * @win_size: RX BA session window size + */ +struct iwl_rx_baid_cfg_cmd_alloc { + __le32 sta_id_mask; + u8 tid; + u8 reserved[3]; + __le16 ssn; + __le16 win_size; +} __packed; /* RX_BAID_ALLOCATION_ADD_CMD_API_S_VER_1 */ + +/** + * struct iwl_rx_baid_cfg_cmd_modify - BAID modification data + * @old_sta_id_mask: old station ID mask + * @new_sta_id_mask: new station ID mask + * @tid: TID of the BAID + */ +struct iwl_rx_baid_cfg_cmd_modify { + __le32 old_sta_id_mask; + __le32 new_sta_id_mask; + __le32 tid; +} __packed; /* RX_BAID_ALLOCATION_MODIFY_CMD_API_S_VER_2 */ + +/** + * struct iwl_rx_baid_cfg_cmd_remove_v1 - BAID removal data + * @baid: the BAID to remove + */ +struct iwl_rx_baid_cfg_cmd_remove_v1 { + __le32 baid; +} __packed; /* RX_BAID_ALLOCATION_REMOVE_CMD_API_S_VER_1 */ + +/** + * struct iwl_rx_baid_cfg_cmd_remove - BAID removal data + * @sta_id_mask: the station mask of the BAID to remove + * @tid: the TID of the BAID to remove + */ +struct iwl_rx_baid_cfg_cmd_remove { + __le32 sta_id_mask; + __le32 tid; +} __packed; /* RX_BAID_ALLOCATION_REMOVE_CMD_API_S_VER_2 */ + +/** + * struct iwl_rx_baid_cfg_cmd - BAID allocation/config command + * @action: the action, from &enum iwl_rx_baid_action + */ +struct iwl_rx_baid_cfg_cmd { + __le32 action; + union { + struct iwl_rx_baid_cfg_cmd_alloc alloc; + struct iwl_rx_baid_cfg_cmd_modify modify; + struct iwl_rx_baid_cfg_cmd_remove_v1 remove_v1; + struct iwl_rx_baid_cfg_cmd_remove remove; + }; /* RX_BAID_ALLOCATION_OPERATION_API_U_VER_2 */ +} __packed; /* RX_BAID_ALLOCATION_CONFIG_CMD_API_S_VER_2 */ + +/** + * struct iwl_rx_baid_cfg_resp - BAID allocation response + * @baid: the allocated BAID + */ +struct iwl_rx_baid_cfg_resp { + __le32 baid; +}; /* RX_BAID_ALLOCATION_RESPONSE_API_S_VER_1 */ + +/** + * enum iwl_scd_queue_cfg_operation - scheduler queue operation + * @IWL_SCD_QUEUE_ADD: allocate a new queue + * @IWL_SCD_QUEUE_REMOVE: remove a queue + * @IWL_SCD_QUEUE_MODIFY: modify a queue + */ +enum iwl_scd_queue_cfg_operation { + IWL_SCD_QUEUE_ADD = 0, + IWL_SCD_QUEUE_REMOVE = 1, + IWL_SCD_QUEUE_MODIFY = 2, +}; + +/** + * struct iwl_scd_queue_cfg_cmd - scheduler queue allocation command + * @operation: the operation, see &enum iwl_scd_queue_cfg_operation + * @u.add.sta_mask: station mask + * @u.add.tid: TID + * @u.add.reserved: reserved + * @u.add.flags: flags from &enum iwl_tx_queue_cfg_actions, except + * %TX_QUEUE_CFG_ENABLE_QUEUE is not valid + * @u.add.cb_size: size code + * @u.add.bc_dram_addr: byte-count table IOVA + * @u.add.tfdq_dram_addr: TFD queue IOVA + * @u.remove.queue: queue ID for removal + * @u.modify.sta_mask: new station mask for modify + * @u.modify.queue: queue ID to modify + */ +struct iwl_scd_queue_cfg_cmd { + __le32 operation; + union { + struct { + __le32 sta_mask; + u8 tid; + u8 reserved[3]; + __le32 flags; + __le32 cb_size; + __le64 bc_dram_addr; + __le64 tfdq_dram_addr; + } __packed add; /* TX_QUEUE_CFG_CMD_ADD_API_S_VER_1 */ + struct { + __le32 queue; + } __packed remove; /* TX_QUEUE_CFG_CMD_REMOVE_API_S_VER_1 */ + struct { + __le32 sta_mask; + __le32 queue; + } __packed modify; /* TX_QUEUE_CFG_CMD_MODIFY_API_S_VER_1 */ + } __packed u; /* TX_QUEUE_CFG_CMD_OPERATION_API_U_VER_1 */ +} __packed; /* TX_QUEUE_CFG_CMD_API_S_VER_3 */ + +#endif /* __iwl_fw_api_datapath_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/dbg-tlv.h b/drivers/net/wireless/intel/iwlwifi/fw/api/dbg-tlv.h new file mode 100644 index 000000000..39bee9c00 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/dbg-tlv.h @@ -0,0 +1,529 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2018-2022 Intel Corporation + */ +#ifndef __iwl_fw_dbg_tlv_h__ +#define __iwl_fw_dbg_tlv_h__ + +#include <linux/bitops.h> + +#define IWL_FW_INI_MAX_REGION_ID 64 +#define IWL_FW_INI_MAX_NAME 32 +#define IWL_FW_INI_MAX_CFG_NAME 64 +#define IWL_FW_INI_DOMAIN_ALWAYS_ON 0 +#define IWL_FW_INI_REGION_ID_MASK GENMASK(15, 0) +#define IWL_FW_INI_REGION_DUMP_POLICY_MASK GENMASK(31, 16) +#define IWL_FW_INI_PRESET_DISABLE 0xff + +/** + * struct iwl_fw_ini_hcmd + * + * @id: the debug configuration command type for instance: 0xf6 / 0xf5 / DHC + * @group: the desired cmd group + * @reserved: to align to FW struct + * @data: all of the relevant command data to be sent + */ +struct iwl_fw_ini_hcmd { + u8 id; + u8 group; + __le16 reserved; + u8 data[]; +} __packed; /* FW_DEBUG_TLV_HCMD_DATA_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_header - Common Header for all ini debug TLV's structures + * + * @version: TLV version + * @domain: domain of the TLV. One of &enum iwl_fw_ini_dbg_domain + */ +struct iwl_fw_ini_header { + __le32 version; + __le32 domain; + /* followed by the data */ +} __packed; /* FW_TLV_DEBUG_HEADER_S_VER_1 */ + +/** + * struct iwl_fw_ini_region_dev_addr - Configuration to read device addresses + * + * @size: size of each memory chunk + * @offset: offset to add to the base address of each chunk + */ +struct iwl_fw_ini_region_dev_addr { + __le32 size; + __le32 offset; +} __packed; /* FW_TLV_DEBUG_DEVICE_ADDR_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_region_fifos - Configuration to read Tx/Rx fifos + * + * @fid: fifos ids array. Used to determine what fifos to collect + * @hdr_only: if non zero, collect only the registers + * @offset: offset to add to the registers addresses + */ +struct iwl_fw_ini_region_fifos { + __le32 fid[2]; + __le32 hdr_only; + __le32 offset; +} __packed; /* FW_TLV_DEBUG_REGION_FIFOS_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_region_err_table - error table region data + * + * Configuration to read Umac/Lmac error table + * + * @version: version of the error table + * @base_addr: base address of the error table + * @size: size of the error table + * @offset: offset to add to &base_addr + */ +struct iwl_fw_ini_region_err_table { + __le32 version; + __le32 base_addr; + __le32 size; + __le32 offset; +} __packed; /* FW_TLV_DEBUG_REGION_ERROR_TABLE_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_region_special_device_memory - special device memory + * + * Configuration to read a special memory + * + * @type: type of the special memory + * @version: version of the special memory + * @base_addr: base address of the error table + * @size: size of the error table + * @offset: offset to add to &base_addr + */ +struct iwl_fw_ini_region_special_device_memory { + __le16 type; + __le16 version; + __le32 base_addr; + __le32 size; + __le32 offset; +} __packed; /* FW_TLV_DEBUG_REGION_SPECIAL_DEVICE_ADDR_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_region_internal_buffer - internal buffer region data + * + * Configuration to read internal monitor buffer + * + * @alloc_id: allocation id one of &enum iwl_fw_ini_allocation_id + * @base_addr: internal buffer base address + * @size: size internal buffer size + */ +struct iwl_fw_ini_region_internal_buffer { + __le32 alloc_id; + __le32 base_addr; + __le32 size; +} __packed; /* FW_TLV_DEBUG_REGION_INTERNAL_BUFFER_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_region_tlv - region TLV + * + * Configures parameters for region data collection + * + * @hdr: debug header + * @id: region id. Max id is &IWL_FW_INI_MAX_REGION_ID + * @type: region type. One of &enum iwl_fw_ini_region_type + * @sub_type: region sub type + * @sub_type_ver: region sub type version + * @reserved: not in use + * @name: region name + * @dev_addr: device address configuration. Used by + * &IWL_FW_INI_REGION_DEVICE_MEMORY, &IWL_FW_INI_REGION_PERIPHERY_MAC, + * &IWL_FW_INI_REGION_PERIPHERY_PHY, &IWL_FW_INI_REGION_PERIPHERY_AUX, + * &IWL_FW_INI_REGION_PAGING, &IWL_FW_INI_REGION_CSR, + * &IWL_FW_INI_REGION_DRAM_IMR and &IWL_FW_INI_REGION_PCI_IOSF_CONFIG + * &IWL_FW_INI_REGION_DBGI_SRAM, &FW_TLV_DEBUG_REGION_TYPE_DBGI_SRAM, + * @fifos: fifos configuration. Used by &IWL_FW_INI_REGION_TXF and + * &IWL_FW_INI_REGION_RXF + * @err_table: error table configuration. Used by + * IWL_FW_INI_REGION_LMAC_ERROR_TABLE and + * IWL_FW_INI_REGION_UMAC_ERROR_TABLE + * @internal_buffer: internal monitor buffer configuration. Used by + * &IWL_FW_INI_REGION_INTERNAL_BUFFER + * @dram_alloc_id: dram allocation id. One of &enum iwl_fw_ini_allocation_id. + * Used by &IWL_FW_INI_REGION_DRAM_BUFFER + * @tlv_mask: tlv collection mask. Used by &IWL_FW_INI_REGION_TLV + * @addrs: array of addresses attached to the end of the region tlv + */ +struct iwl_fw_ini_region_tlv { + struct iwl_fw_ini_header hdr; + __le32 id; + u8 type; + u8 sub_type; + u8 sub_type_ver; + u8 reserved; + u8 name[IWL_FW_INI_MAX_NAME]; + union { + struct iwl_fw_ini_region_dev_addr dev_addr; + struct iwl_fw_ini_region_fifos fifos; + struct iwl_fw_ini_region_err_table err_table; + struct iwl_fw_ini_region_internal_buffer internal_buffer; + struct iwl_fw_ini_region_special_device_memory special_mem; + __le32 dram_alloc_id; + __le32 tlv_mask; + }; /* FW_TLV_DEBUG_REGION_CONF_PARAMS_API_U_VER_1 */ + __le32 addrs[]; +} __packed; /* FW_TLV_DEBUG_REGION_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_debug_info_tlv + * + * debug configuration name for a specific image + * + * @hdr: debug header + * @image_type: image type + * @debug_cfg_name: debug configuration name + */ +struct iwl_fw_ini_debug_info_tlv { + struct iwl_fw_ini_header hdr; + __le32 image_type; + u8 debug_cfg_name[IWL_FW_INI_MAX_CFG_NAME]; +} __packed; /* FW_TLV_DEBUG_INFO_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_allocation_tlv - Allocates DRAM buffers + * + * @hdr: debug header + * @alloc_id: allocation id. One of &enum iwl_fw_ini_allocation_id + * @buf_location: buffer location. One of &enum iwl_fw_ini_buffer_location + * @req_size: requested buffer size + * @max_frags_num: maximum number of fragments + * @min_size: minimum buffer size + */ +struct iwl_fw_ini_allocation_tlv { + struct iwl_fw_ini_header hdr; + __le32 alloc_id; + __le32 buf_location; + __le32 req_size; + __le32 max_frags_num; + __le32 min_size; +} __packed; /* FW_TLV_DEBUG_BUFFER_ALLOCATION_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_trigger_tlv - trigger TLV + * + * Trigger that upon firing, determines what regions to collect + * + * @hdr: debug header + * @time_point: time point. One of &enum iwl_fw_ini_time_point + * @trigger_reason: trigger reason + * @apply_policy: uses &enum iwl_fw_ini_trigger_apply_policy + * @dump_delay: delay from trigger fire to dump, in usec + * @occurrences: max trigger fire occurrences allowed + * @reserved: unused + * @ignore_consec: ignore consecutive triggers, in usec + * @reset_fw: if non zero, will reset and reload the FW + * @multi_dut: initiate debug dump data on several DUTs + * @regions_mask: mask of regions to collect + * @data: trigger data + */ +struct iwl_fw_ini_trigger_tlv { + struct iwl_fw_ini_header hdr; + __le32 time_point; + __le32 trigger_reason; + __le32 apply_policy; + __le32 dump_delay; + __le32 occurrences; + __le32 reserved; + __le32 ignore_consec; + __le32 reset_fw; + __le32 multi_dut; + __le64 regions_mask; + __le32 data[]; +} __packed; /* FW_TLV_DEBUG_TRIGGER_API_S_VER_1 */ + +/** + * struct iwl_fw_ini_hcmd_tlv - Generic Host command pass through TLV + * + * @hdr: debug header + * @time_point: time point. One of &enum iwl_fw_ini_time_point + * @period_msec: interval at which the hcmd will be sent to the FW. + * Measured in msec (0 = one time command) + * @hcmd: a variable length host-command to be sent to apply the configuration + */ +struct iwl_fw_ini_hcmd_tlv { + struct iwl_fw_ini_header hdr; + __le32 time_point; + __le32 period_msec; + struct iwl_fw_ini_hcmd hcmd; +} __packed; /* FW_TLV_DEBUG_HCMD_API_S_VER_1 */ + +/** +* struct iwl_fw_ini_addr_val - Address and value to set it to +* +* @address: the base address +* @value: value to set at address +*/ +struct iwl_fw_ini_addr_val { + __le32 address; + __le32 value; +} __packed; /* FW_TLV_DEBUG_ADDR_VALUE_VER_1 */ + +/** + * struct iwl_fw_ini_conf_tlv - configuration TLV to set register/memory. + * + * @hdr: debug header + * @time_point: time point to apply config. One of &enum iwl_fw_ini_time_point + * @set_type: write access type preset token for time point. + * one of &enum iwl_fw_ini_config_set_type + * @addr_offset: the offset to add to any item in address[0] field + * @addr_val: address value pair + */ +struct iwl_fw_ini_conf_set_tlv { + struct iwl_fw_ini_header hdr; + __le32 time_point; + __le32 set_type; + __le32 addr_offset; + struct iwl_fw_ini_addr_val addr_val[]; +} __packed; /* FW_TLV_DEBUG_CONFIG_SET_API_S_VER_1 */ + +/** + * enum iwl_fw_ini_config_set_type + * + * @IWL_FW_INI_CONFIG_SET_TYPE_INVALID: invalid config set + * @IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_PERIPHERY_MAC: for PERIPHERY MAC configuration + * @IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_PERIPHERY_PHY: for PERIPHERY PHY configuration + * @IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_PERIPHERY_AUX: for PERIPHERY AUX configuration + * @IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_MEMORY: for DEVICE MEMORY configuration + * @IWL_FW_INI_CONFIG_SET_TYPE_CSR: for CSR configuration + * @IWL_FW_INI_CONFIG_SET_TYPE_DBGC_DRAM_ADDR: for DBGC_DRAM_ADDR configuration + * @IWL_FW_INI_CONFIG_SET_TYPE_PERIPH_SCRATCH_HWM: for PERIPH SCRATCH HWM configuration + * @IWL_FW_INI_ALLOCATION_NUM: max number of configuration supported +*/ + +enum iwl_fw_ini_config_set_type { + IWL_FW_INI_CONFIG_SET_TYPE_INVALID = 0, + IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_PERIPHERY_MAC, + IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_PERIPHERY_PHY, + IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_PERIPHERY_AUX, + IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_MEMORY, + IWL_FW_INI_CONFIG_SET_TYPE_CSR, + IWL_FW_INI_CONFIG_SET_TYPE_DBGC_DRAM_ADDR, + IWL_FW_INI_CONFIG_SET_TYPE_PERIPH_SCRATCH_HWM, + IWL_FW_INI_CONFIG_SET_TYPE_MAX_NUM, +} __packed; + +/** + * enum iwl_fw_ini_allocation_id + * + * @IWL_FW_INI_ALLOCATION_INVALID: invalid + * @IWL_FW_INI_ALLOCATION_ID_DBGC1: allocation meant for DBGC1 configuration + * @IWL_FW_INI_ALLOCATION_ID_DBGC2: allocation meant for DBGC2 configuration + * @IWL_FW_INI_ALLOCATION_ID_DBGC3: allocation meant for DBGC3 configuration + * @IWL_FW_INI_ALLOCATION_ID_DBGC4: allocation meant for DBGC4 configuration + * @IWL_FW_INI_ALLOCATION_NUM: number of allocation ids +*/ +enum iwl_fw_ini_allocation_id { + IWL_FW_INI_ALLOCATION_INVALID, + IWL_FW_INI_ALLOCATION_ID_DBGC1, + IWL_FW_INI_ALLOCATION_ID_DBGC2, + IWL_FW_INI_ALLOCATION_ID_DBGC3, + IWL_FW_INI_ALLOCATION_ID_DBGC4, + IWL_FW_INI_ALLOCATION_NUM, +}; /* FW_DEBUG_TLV_ALLOCATION_ID_E_VER_1 */ + +/** + * enum iwl_fw_ini_buffer_location + * + * @IWL_FW_INI_LOCATION_INVALID: invalid + * @IWL_FW_INI_LOCATION_SRAM_PATH: SRAM location + * @IWL_FW_INI_LOCATION_DRAM_PATH: DRAM location + * @IWL_FW_INI_LOCATION_NPK_PATH: NPK location + */ +enum iwl_fw_ini_buffer_location { + IWL_FW_INI_LOCATION_INVALID, + IWL_FW_INI_LOCATION_SRAM_PATH, + IWL_FW_INI_LOCATION_DRAM_PATH, + IWL_FW_INI_LOCATION_NPK_PATH, + IWL_FW_INI_LOCATION_NUM, +}; /* FW_DEBUG_TLV_BUFFER_LOCATION_E_VER_1 */ + +/** + * enum iwl_fw_ini_region_type + * + * @IWL_FW_INI_REGION_INVALID: invalid + * @IWL_FW_INI_REGION_TLV: uCode and debug TLVs + * @IWL_FW_INI_REGION_INTERNAL_BUFFER: monitor SMEM buffer + * @IWL_FW_INI_REGION_DRAM_BUFFER: monitor DRAM buffer + * @IWL_FW_INI_REGION_TXF: TX fifos + * @IWL_FW_INI_REGION_RXF: RX fifo + * @IWL_FW_INI_REGION_LMAC_ERROR_TABLE: lmac error table + * @IWL_FW_INI_REGION_UMAC_ERROR_TABLE: umac error table + * @IWL_FW_INI_REGION_RSP_OR_NOTIF: FW response or notification data + * @IWL_FW_INI_REGION_DEVICE_MEMORY: device internal memory + * @IWL_FW_INI_REGION_PERIPHERY_MAC: periphery registers of MAC + * @IWL_FW_INI_REGION_PERIPHERY_PHY: periphery registers of PHY + * @IWL_FW_INI_REGION_PERIPHERY_AUX: periphery registers of AUX + * @IWL_FW_INI_REGION_PAGING: paging memory + * @IWL_FW_INI_REGION_CSR: CSR registers + * @IWL_FW_INI_REGION_DRAM_IMR: IMR memory + * @IWL_FW_INI_REGION_PCI_IOSF_CONFIG: PCI/IOSF config + * @IWL_FW_INI_REGION_SPECIAL_DEVICE_MEMORY: special device memory + * @IWL_FW_INI_REGION_DBGI_SRAM: periphery registers of DBGI SRAM + * @IWL_FW_INI_REGION_NUM: number of region types + */ +enum iwl_fw_ini_region_type { + IWL_FW_INI_REGION_INVALID, + IWL_FW_INI_REGION_TLV, + IWL_FW_INI_REGION_INTERNAL_BUFFER, + IWL_FW_INI_REGION_DRAM_BUFFER, + IWL_FW_INI_REGION_TXF, + IWL_FW_INI_REGION_RXF, + IWL_FW_INI_REGION_LMAC_ERROR_TABLE, + IWL_FW_INI_REGION_UMAC_ERROR_TABLE, + IWL_FW_INI_REGION_RSP_OR_NOTIF, + IWL_FW_INI_REGION_DEVICE_MEMORY, + IWL_FW_INI_REGION_PERIPHERY_MAC, + IWL_FW_INI_REGION_PERIPHERY_PHY, + IWL_FW_INI_REGION_PERIPHERY_AUX, + IWL_FW_INI_REGION_PAGING, + IWL_FW_INI_REGION_CSR, + IWL_FW_INI_REGION_DRAM_IMR, + IWL_FW_INI_REGION_PCI_IOSF_CONFIG, + IWL_FW_INI_REGION_SPECIAL_DEVICE_MEMORY, + IWL_FW_INI_REGION_DBGI_SRAM, + IWL_FW_INI_REGION_NUM +}; /* FW_TLV_DEBUG_REGION_TYPE_API_E */ + +enum iwl_fw_ini_region_device_memory_subtype { + IWL_FW_INI_REGION_DEVICE_MEMORY_SUBTYPE_HW_SMEM = 1, + IWL_FW_INI_REGION_DEVICE_MEMORY_SUBTYPE_UMAC_ERROR_TABLE = 5, + IWL_FW_INI_REGION_DEVICE_MEMORY_SUBTYPE_LMAC_1_ERROR_TABLE = 7, + IWL_FW_INI_REGION_DEVICE_MEMORY_SUBTYPE_LMAC_2_ERROR_TABLE = 10, + IWL_FW_INI_REGION_DEVICE_MEMORY_SUBTYPE_TCM_1_ERROR_TABLE = 14, + IWL_FW_INI_REGION_DEVICE_MEMORY_SUBTYPE_TCM_2_ERROR_TABLE = 16, + IWL_FW_INI_REGION_DEVICE_MEMORY_SUBTYPE_RCM_1_ERROR_TABLE = 18, + IWL_FW_INI_REGION_DEVICE_MEMORY_SUBTYPE_RCM_2_ERROR_TABLE = 20, +}; /* FW_TLV_DEBUG_REGION_DEVICE_MEMORY_SUBTYPE_API_E */ + +/** + * enum iwl_fw_ini_time_point + * + * Hard coded time points in which the driver can send hcmd or perform dump + * collection + * + * @IWL_FW_INI_TIME_POINT_EARLY: pre loading the FW + * @IWL_FW_INI_TIME_POINT_AFTER_ALIVE: first cmd from host after alive notif + * @IWL_FW_INI_TIME_POINT_POST_INIT: last cmd in series of init sequence + * @IWL_FW_INI_TIME_POINT_FW_ASSERT: FW assert + * @IWL_FW_INI_TIME_POINT_FW_HW_ERROR: FW HW error + * @IWL_FW_INI_TIME_POINT_FW_TFD_Q_HANG: TFD queue hang + * @IWL_FW_INI_TIME_POINT_FW_DHC_NOTIFICATION: DHC cmd response and notif + * @IWL_FW_INI_TIME_POINT_FW_RSP_OR_NOTIF: FW response or notification. + * data field holds id and group + * @IWL_FW_INI_TIME_POINT_USER_TRIGGER: user trigger time point + * @IWL_FW_INI_TIME_POINT_PERIODIC: periodic timepoint that fires in constant + * intervals. data field holds the interval time in msec + * @IWL_FW_INI_TIME_POINT_RESERVED: reserved + * @IWL_FW_INI_TIME_POINT_HOST_ASSERT: Unused + * @IWL_FW_INI_TIME_POINT_HOST_ALIVE_TIMEOUT: alive timeout + * @IWL_FW_INI_TIME_POINT_HOST_DEVICE_ENABLE: device enable + * @IWL_FW_INI_TIME_POINT_HOST_DEVICE_DISABLE: device disable + * @IWL_FW_INI_TIME_POINT_HOST_D3_START: D3 start + * @IWL_FW_INI_TIME_POINT_HOST_D3_END: D3 end + * @IWL_FW_INI_TIME_POINT_MISSED_BEACONS: missed beacons + * @IWL_FW_INI_TIME_POINT_ASSOC_FAILED: association failure + * @IWL_FW_INI_TIME_POINT_TX_FAILED: Tx frame failed + * @IWL_FW_INI_TIME_POINT_TX_WFD_ACTION_FRAME_FAILED: wifi direct action + * frame failed + * @IWL_FW_INI_TIME_POINT_TX_LATENCY_THRESHOLD: Tx latency threshold + * @IWL_FW_INI_TIME_POINT_HANG_OCCURRED: hang occurred + * @IWL_FW_INI_TIME_POINT_EAPOL_FAILED: EAPOL failed + * @IWL_FW_INI_TIME_POINT_FAKE_TX: fake Tx + * @IWL_FW_INI_TIME_POINT_DEASSOC: de association + * @IWL_FW_INI_TIME_POINT_NUM: number of time points + */ +enum iwl_fw_ini_time_point { + IWL_FW_INI_TIME_POINT_INVALID, + IWL_FW_INI_TIME_POINT_EARLY, + IWL_FW_INI_TIME_POINT_AFTER_ALIVE, + IWL_FW_INI_TIME_POINT_POST_INIT, + IWL_FW_INI_TIME_POINT_FW_ASSERT, + IWL_FW_INI_TIME_POINT_FW_HW_ERROR, + IWL_FW_INI_TIME_POINT_FW_TFD_Q_HANG, + IWL_FW_INI_TIME_POINT_FW_DHC_NOTIFICATION, + IWL_FW_INI_TIME_POINT_FW_RSP_OR_NOTIF, + IWL_FW_INI_TIME_POINT_USER_TRIGGER, + IWL_FW_INI_TIME_POINT_PERIODIC, + IWL_FW_INI_TIME_POINT_RESERVED, + IWL_FW_INI_TIME_POINT_HOST_ASSERT, + IWL_FW_INI_TIME_POINT_HOST_ALIVE_TIMEOUT, + IWL_FW_INI_TIME_POINT_HOST_DEVICE_ENABLE, + IWL_FW_INI_TIME_POINT_HOST_DEVICE_DISABLE, + IWL_FW_INI_TIME_POINT_HOST_D3_START, + IWL_FW_INI_TIME_POINT_HOST_D3_END, + IWL_FW_INI_TIME_POINT_MISSED_BEACONS, + IWL_FW_INI_TIME_POINT_ASSOC_FAILED, + IWL_FW_INI_TIME_POINT_TX_FAILED, + IWL_FW_INI_TIME_POINT_TX_WFD_ACTION_FRAME_FAILED, + IWL_FW_INI_TIME_POINT_TX_LATENCY_THRESHOLD, + IWL_FW_INI_TIME_POINT_HANG_OCCURRED, + IWL_FW_INI_TIME_POINT_EAPOL_FAILED, + IWL_FW_INI_TIME_POINT_FAKE_TX, + IWL_FW_INI_TIME_POINT_DEASSOC, + IWL_FW_INI_TIME_POINT_NUM, +}; /* FW_TLV_DEBUG_TIME_POINT_API_E */ + +/** + * enum iwl_fw_ini_trigger_apply_policy - Determines how to apply triggers + * + * @IWL_FW_INI_APPLY_POLICY_MATCH_TIME_POINT: match by time point + * @IWL_FW_INI_APPLY_POLICY_MATCH_DATA: match by trigger data + * @IWL_FW_INI_APPLY_POLICY_OVERRIDE_REGIONS: override regions mask. + * Append otherwise + * @IWL_FW_INI_APPLY_POLICY_OVERRIDE_CFG: override trigger configuration + * @IWL_FW_INI_APPLY_POLICY_OVERRIDE_DATA: override trigger data. + * Append otherwise + * @IWL_FW_INI_APPLY_POLICY_DUMP_COMPLETE_CMD: send cmd once dump collected + */ +enum iwl_fw_ini_trigger_apply_policy { + IWL_FW_INI_APPLY_POLICY_MATCH_TIME_POINT = BIT(0), + IWL_FW_INI_APPLY_POLICY_MATCH_DATA = BIT(1), + IWL_FW_INI_APPLY_POLICY_OVERRIDE_REGIONS = BIT(8), + IWL_FW_INI_APPLY_POLICY_OVERRIDE_CFG = BIT(9), + IWL_FW_INI_APPLY_POLICY_OVERRIDE_DATA = BIT(10), + IWL_FW_INI_APPLY_POLICY_DUMP_COMPLETE_CMD = BIT(16), +}; + +/** + * enum iwl_fw_ini_trigger_reset_fw_policy - Determines how to handle reset + * + * @IWL_FW_INI_RESET_FW_MODE_NOTHING: do not stop FW and reload (default) + * @IWL_FW_INI_RESET_FW_MODE_STOP_FW_ONLY: stop FW without reload FW + * @IWL_FW_INI_RESET_FW_MODE_STOP_AND_RELOAD_FW: stop FW with reload FW + */ +enum iwl_fw_ini_trigger_reset_fw_policy { + IWL_FW_INI_RESET_FW_MODE_NOTHING = 0, + IWL_FW_INI_RESET_FW_MODE_STOP_FW_ONLY, + IWL_FW_INI_RESET_FW_MODE_STOP_AND_RELOAD_FW +}; + +/** + * enum iwl_fw_ini_dump_policy - Determines how to handle dump based on enabled flags + * + * @IWL_FW_INI_DEBUG_DUMP_POLICY_NO_LIMIT: OS has no limit of dump size + * @IWL_FW_INI_DEBUG_DUMP_POLICY_MAX_LIMIT_600KB: mini dump only 600KB region dump + * @IWL_FW_IWL_DEBUG_DUMP_POLICY_MAX_LIMIT_5MB: mini dump 5MB size dump + */ +enum iwl_fw_ini_dump_policy { + IWL_FW_INI_DEBUG_DUMP_POLICY_NO_LIMIT = BIT(0), + IWL_FW_INI_DEBUG_DUMP_POLICY_MAX_LIMIT_600KB = BIT(1), + IWL_FW_IWL_DEBUG_DUMP_POLICY_MAX_LIMIT_5MB = BIT(2), + +}; + +/** + * enum iwl_fw_ini_dump_type - Determines dump type based on size defined by FW. + * + * @IWL_FW_INI_DUMP_BRIEF : only dump the most important regions + * @IWL_FW_INI_DEBUG_MEDIUM: dump more regions than "brief", but not all regions + * @IWL_FW_INI_DUMP_VERBOSE : dump all regions + */ +enum iwl_fw_ini_dump_type { + IWL_FW_INI_DUMP_BRIEF, + IWL_FW_INI_DUMP_MEDIUM, + IWL_FW_INI_DUMP_VERBOSE, +}; +#endif diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/debug.h b/drivers/net/wireless/intel/iwlwifi/fw/api/debug.h new file mode 100644 index 000000000..0c555089e --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/debug.h @@ -0,0 +1,424 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_debug_h__ +#define __iwl_fw_api_debug_h__ + +/** + * enum iwl_debug_cmds - debug commands + */ +enum iwl_debug_cmds { + /** + * @LMAC_RD_WR: + * LMAC memory read/write, using &struct iwl_dbg_mem_access_cmd and + * &struct iwl_dbg_mem_access_rsp + */ + LMAC_RD_WR = 0x0, + /** + * @UMAC_RD_WR: + * UMAC memory read/write, using &struct iwl_dbg_mem_access_cmd and + * &struct iwl_dbg_mem_access_rsp + */ + UMAC_RD_WR = 0x1, + /** + * @HOST_EVENT_CFG: + * updates the enabled event severities + * &struct iwl_dbg_host_event_cfg_cmd + */ + HOST_EVENT_CFG = 0x3, + /** + * @DBGC_SUSPEND_RESUME: + * DBGC suspend/resume commad. Uses a single dword as data: + * 0 - resume DBGC recording + * 1 - suspend DBGC recording + */ + DBGC_SUSPEND_RESUME = 0x7, + /** + * @BUFFER_ALLOCATION: + * passes DRAM buffers to a DBGC + * &struct iwl_buf_alloc_cmd + */ + BUFFER_ALLOCATION = 0x8, + /** + * @FW_DUMP_COMPLETE_CMD: + * sends command to fw once dump collection completed + * &struct iwl_dbg_dump_complete_cmd + */ + FW_DUMP_COMPLETE_CMD = 0xB, + /** + * @MFU_ASSERT_DUMP_NTF: + * &struct iwl_mfu_assert_dump_notif + */ + MFU_ASSERT_DUMP_NTF = 0xFE, +}; + +/* Error response/notification */ +enum { + FW_ERR_UNKNOWN_CMD = 0x0, + FW_ERR_INVALID_CMD_PARAM = 0x1, + FW_ERR_SERVICE = 0x2, + FW_ERR_ARC_MEMORY = 0x3, + FW_ERR_ARC_CODE = 0x4, + FW_ERR_WATCH_DOG = 0x5, + FW_ERR_WEP_GRP_KEY_INDX = 0x10, + FW_ERR_WEP_KEY_SIZE = 0x11, + FW_ERR_OBSOLETE_FUNC = 0x12, + FW_ERR_UNEXPECTED = 0xFE, + FW_ERR_FATAL = 0xFF +}; + +/** enum iwl_dbg_suspend_resume_cmds - dbgc suspend resume operations + * dbgc suspend resume command operations + * @DBGC_RESUME_CMD: resume dbgc recording + * @DBGC_SUSPEND_CMD: stop dbgc recording + */ +enum iwl_dbg_suspend_resume_cmds { + DBGC_RESUME_CMD, + DBGC_SUSPEND_CMD, +}; + +/** + * struct iwl_error_resp - FW error indication + * ( REPLY_ERROR = 0x2 ) + * @error_type: one of FW_ERR_* + * @cmd_id: the command ID for which the error occurred + * @reserved1: reserved + * @bad_cmd_seq_num: sequence number of the erroneous command + * @error_service: which service created the error, applicable only if + * error_type = 2, otherwise 0 + * @timestamp: TSF in usecs. + */ +struct iwl_error_resp { + __le32 error_type; + u8 cmd_id; + u8 reserved1; + __le16 bad_cmd_seq_num; + __le32 error_service; + __le64 timestamp; +} __packed; + +#define TX_FIFO_MAX_NUM_9000 8 +#define TX_FIFO_MAX_NUM 15 +#define RX_FIFO_MAX_NUM 2 +#define TX_FIFO_INTERNAL_MAX_NUM 6 + +/** + * struct iwl_shared_mem_cfg_v2 - Shared memory configuration information + * + * @shared_mem_addr: shared memory addr (pre 8000 HW set to 0x0 as MARBH is not + * accessible) + * @shared_mem_size: shared memory size + * @sample_buff_addr: internal sample (mon/adc) buff addr (pre 8000 HW set to + * 0x0 as accessible only via DBGM RDAT) + * @sample_buff_size: internal sample buff size + * @txfifo_addr: start addr of TXF0 (excluding the context table 0.5KB), (pre + * 8000 HW set to 0x0 as not accessible) + * @txfifo_size: size of TXF0 ... TXF7 + * @rxfifo_size: RXF1, RXF2 sizes. If there is no RXF2, it'll have a value of 0 + * @page_buff_addr: used by UMAC and performance debug (page miss analysis), + * when paging is not supported this should be 0 + * @page_buff_size: size of %page_buff_addr + * @rxfifo_addr: Start address of rxFifo + * @internal_txfifo_addr: start address of internalFifo + * @internal_txfifo_size: internal fifos' size + * + * NOTE: on firmware that don't have IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG + * set, the last 3 members don't exist. + */ +struct iwl_shared_mem_cfg_v2 { + __le32 shared_mem_addr; + __le32 shared_mem_size; + __le32 sample_buff_addr; + __le32 sample_buff_size; + __le32 txfifo_addr; + __le32 txfifo_size[TX_FIFO_MAX_NUM_9000]; + __le32 rxfifo_size[RX_FIFO_MAX_NUM]; + __le32 page_buff_addr; + __le32 page_buff_size; + __le32 rxfifo_addr; + __le32 internal_txfifo_addr; + __le32 internal_txfifo_size[TX_FIFO_INTERNAL_MAX_NUM]; +} __packed; /* SHARED_MEM_ALLOC_API_S_VER_2 */ + +/** + * struct iwl_shared_mem_lmac_cfg - LMAC shared memory configuration + * + * @txfifo_addr: start addr of TXF0 (excluding the context table 0.5KB) + * @txfifo_size: size of TX FIFOs + * @rxfifo1_addr: RXF1 addr + * @rxfifo1_size: RXF1 size + */ +struct iwl_shared_mem_lmac_cfg { + __le32 txfifo_addr; + __le32 txfifo_size[TX_FIFO_MAX_NUM]; + __le32 rxfifo1_addr; + __le32 rxfifo1_size; + +} __packed; /* SHARED_MEM_ALLOC_LMAC_API_S_VER_1 */ + +/** + * struct iwl_shared_mem_cfg - Shared memory configuration information + * + * @shared_mem_addr: shared memory address + * @shared_mem_size: shared memory size + * @sample_buff_addr: internal sample (mon/adc) buff addr + * @sample_buff_size: internal sample buff size + * @rxfifo2_addr: start addr of RXF2 + * @rxfifo2_size: size of RXF2 + * @page_buff_addr: used by UMAC and performance debug (page miss analysis), + * when paging is not supported this should be 0 + * @page_buff_size: size of %page_buff_addr + * @lmac_num: number of LMACs (1 or 2) + * @lmac_smem: per - LMAC smem data + * @rxfifo2_control_addr: start addr of RXF2C + * @rxfifo2_control_size: size of RXF2C + */ +struct iwl_shared_mem_cfg { + __le32 shared_mem_addr; + __le32 shared_mem_size; + __le32 sample_buff_addr; + __le32 sample_buff_size; + __le32 rxfifo2_addr; + __le32 rxfifo2_size; + __le32 page_buff_addr; + __le32 page_buff_size; + __le32 lmac_num; + struct iwl_shared_mem_lmac_cfg lmac_smem[3]; + __le32 rxfifo2_control_addr; + __le32 rxfifo2_control_size; +} __packed; /* SHARED_MEM_ALLOC_API_S_VER_4 */ + +/** + * struct iwl_mfuart_load_notif_v1 - mfuart image version & status + * ( MFUART_LOAD_NOTIFICATION = 0xb1 ) + * @installed_ver: installed image version + * @external_ver: external image version + * @status: MFUART loading status + * @duration: MFUART loading time +*/ +struct iwl_mfuart_load_notif_v1 { + __le32 installed_ver; + __le32 external_ver; + __le32 status; + __le32 duration; +} __packed; /* MFU_LOADER_NTFY_API_S_VER_1 */ + +/** + * struct iwl_mfuart_load_notif - mfuart image version & status + * ( MFUART_LOAD_NOTIFICATION = 0xb1 ) + * @installed_ver: installed image version + * @external_ver: external image version + * @status: MFUART loading status + * @duration: MFUART loading time + * @image_size: MFUART image size in bytes +*/ +struct iwl_mfuart_load_notif { + __le32 installed_ver; + __le32 external_ver; + __le32 status; + __le32 duration; + /* image size valid only in v2 of the command */ + __le32 image_size; +} __packed; /* MFU_LOADER_NTFY_API_S_VER_2 */ + +/** + * struct iwl_mfu_assert_dump_notif - mfuart dump logs + * ( MFU_ASSERT_DUMP_NTF = 0xfe ) + * @assert_id: mfuart assert id that cause the notif + * @curr_reset_num: number of asserts since uptime + * @index_num: current chunk id + * @parts_num: total number of chunks + * @data_size: number of data bytes sent + * @data: data buffer + */ +struct iwl_mfu_assert_dump_notif { + __le32 assert_id; + __le32 curr_reset_num; + __le16 index_num; + __le16 parts_num; + __le32 data_size; + __le32 data[]; +} __packed; /* MFU_DUMP_ASSERT_API_S_VER_1 */ + +/** + * enum iwl_mvm_marker_id - marker ids + * + * The ids for different type of markers to insert into the usniffer logs + * + * @MARKER_ID_TX_FRAME_LATENCY: TX latency marker + * @MARKER_ID_SYNC_CLOCK: sync FW time and systime + */ +enum iwl_mvm_marker_id { + MARKER_ID_TX_FRAME_LATENCY = 1, + MARKER_ID_SYNC_CLOCK = 2, +}; /* MARKER_ID_API_E_VER_2 */ + +/** + * struct iwl_mvm_marker - mark info into the usniffer logs + * + * (MARKER_CMD = 0xcb) + * + * Mark the UTC time stamp into the usniffer logs together with additional + * metadata, so the usniffer output can be parsed. + * In the command response the ucode will return the GP2 time. + * + * @dw_len: The amount of dwords following this byte including this byte. + * @marker_id: A unique marker id (iwl_mvm_marker_id). + * @reserved: reserved. + * @timestamp: in milliseconds since 1970-01-01 00:00:00 UTC + * @metadata: additional meta data that will be written to the unsiffer log + */ +struct iwl_mvm_marker { + u8 dw_len; + u8 marker_id; + __le16 reserved; + __le64 timestamp; + __le32 metadata[]; +} __packed; /* MARKER_API_S_VER_1 */ + +/** + * struct iwl_mvm_marker_rsp - Response to marker cmd + * + * @gp2: The gp2 clock value in the FW + */ +struct iwl_mvm_marker_rsp { + __le32 gp2; +} __packed; + +/* Operation types for the debug mem access */ +enum { + DEBUG_MEM_OP_READ = 0, + DEBUG_MEM_OP_WRITE = 1, + DEBUG_MEM_OP_WRITE_BYTES = 2, +}; + +#define DEBUG_MEM_MAX_SIZE_DWORDS 32 + +/** + * struct iwl_dbg_mem_access_cmd - Request the device to read/write memory + * @op: DEBUG_MEM_OP_* + * @addr: address to read/write from/to + * @len: in dwords, to read/write + * @data: for write opeations, contains the source buffer + */ +struct iwl_dbg_mem_access_cmd { + __le32 op; + __le32 addr; + __le32 len; + __le32 data[]; +} __packed; /* DEBUG_(U|L)MAC_RD_WR_CMD_API_S_VER_1 */ + +/* Status responses for the debug mem access */ +enum { + DEBUG_MEM_STATUS_SUCCESS = 0x0, + DEBUG_MEM_STATUS_FAILED = 0x1, + DEBUG_MEM_STATUS_LOCKED = 0x2, + DEBUG_MEM_STATUS_HIDDEN = 0x3, + DEBUG_MEM_STATUS_LENGTH = 0x4, +}; + +/** + * struct iwl_dbg_mem_access_rsp - Response to debug mem commands + * @status: DEBUG_MEM_STATUS_* + * @len: read dwords (0 for write operations) + * @data: contains the read DWs + */ +struct iwl_dbg_mem_access_rsp { + __le32 status; + __le32 len; + __le32 data[]; +} __packed; /* DEBUG_(U|L)MAC_RD_WR_RSP_API_S_VER_1 */ + +/** + * struct iwl_dbg_suspend_resume_cmd - dbgc suspend resume command + * @operation: suspend or resume operation, uses + * &enum iwl_dbg_suspend_resume_cmds + */ +struct iwl_dbg_suspend_resume_cmd { + __le32 operation; +} __packed; + +#define BUF_ALLOC_MAX_NUM_FRAGS 16 + +/** + * struct iwl_buf_alloc_frag - a DBGC fragment + * @addr: base address of the fragment + * @size: size of the fragment + */ +struct iwl_buf_alloc_frag { + __le64 addr; + __le32 size; +} __packed; /* FRAGMENT_STRUCTURE_API_S_VER_1 */ + +/** + * struct iwl_buf_alloc_cmd - buffer allocation command + * @alloc_id: &enum iwl_fw_ini_allocation_id + * @buf_location: &enum iwl_fw_ini_buffer_location + * @num_frags: number of fragments + * @frags: fragments array + */ +struct iwl_buf_alloc_cmd { + __le32 alloc_id; + __le32 buf_location; + __le32 num_frags; + struct iwl_buf_alloc_frag frags[BUF_ALLOC_MAX_NUM_FRAGS]; +} __packed; /* BUFFER_ALLOCATION_CMD_API_S_VER_2 */ + +#define DRAM_INFO_FIRST_MAGIC_WORD 0x76543210 +#define DRAM_INFO_SECOND_MAGIC_WORD 0x89ABCDEF + +/** + * struct iwL_dram_info - DRAM fragments allocation struct + * + * Driver will fill in the first 1K(+) of the pointed DRAM fragment + * + * @first_word: magic word value + * @second_word: magic word value + * @framfrags: DRAM fragmentaion detail +*/ +struct iwl_dram_info { + __le32 first_word; + __le32 second_word; + struct iwl_buf_alloc_cmd dram_frags[IWL_FW_INI_ALLOCATION_NUM - 1]; +} __packed; /* INIT_DRAM_FRAGS_ALLOCATIONS_S_VER_1 */ + +/** + * struct iwl_dbgc1_info - DBGC1 address and size + * + * Driver will fill the dbcg1 address and size at address based on config TLV. + * + * @first_word: all 0 set as identifier + * @dbgc1_add_lsb: LSB bits of DBGC1 physical address + * @dbgc1_add_msb: MSB bits of DBGC1 physical address + * @dbgc1_size: DBGC1 size +*/ +struct iwl_dbgc1_info { + __le32 first_word; + __le32 dbgc1_add_lsb; + __le32 dbgc1_add_msb; + __le32 dbgc1_size; +} __packed; /* INIT_DRAM_FRAGS_ALLOCATIONS_S_VER_1 */ + +/** + * struct iwl_dbg_host_event_cfg_cmd + * @enabled_severities: enabled severities + */ +struct iwl_dbg_host_event_cfg_cmd { + __le32 enabled_severities; +} __packed; /* DEBUG_HOST_EVENT_CFG_CMD_API_S_VER_1 */ + +/** + * struct iwl_dbg_dump_complete_cmd - dump complete cmd + * + * @tp: timepoint whose dump has completed + * @tp_data: timepoint data + */ +struct iwl_dbg_dump_complete_cmd { + __le32 tp; + __le32 tp_data; +} __packed; /* FW_DUMP_COMPLETE_CMD_API_S_VER_1 */ + +#endif /* __iwl_fw_api_debug_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/filter.h b/drivers/net/wireless/intel/iwlwifi/fw/api/filter.h new file mode 100644 index 000000000..88fe61d14 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/filter.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_filter_h__ +#define __iwl_fw_api_filter_h__ + +#include "fw/api/mac.h" + +#define MAX_PORT_ID_NUM 2 +#define MAX_MCAST_FILTERING_ADDRESSES 256 + +/** + * struct iwl_mcast_filter_cmd - configure multicast filter. + * @filter_own: Set 1 to filter out multicast packets sent by station itself + * @port_id: Multicast MAC addresses array specifier. This is a strange way + * to identify network interface adopted in host-device IF. + * It is used by FW as index in array of addresses. This array has + * MAX_PORT_ID_NUM members. + * @count: Number of MAC addresses in the array + * @pass_all: Set 1 to pass all multicast packets. + * @bssid: current association BSSID. + * @reserved: reserved + * @addr_list: Place holder for array of MAC addresses. + * IMPORTANT: add padding if necessary to ensure DWORD alignment. + */ +struct iwl_mcast_filter_cmd { + u8 filter_own; + u8 port_id; + u8 count; + u8 pass_all; + u8 bssid[6]; + u8 reserved[2]; + u8 addr_list[]; +} __packed; /* MCAST_FILTERING_CMD_API_S_VER_1 */ + +#endif /* __iwl_fw_api_filter_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/led.h b/drivers/net/wireless/intel/iwlwifi/fw/api/led.h new file mode 100644 index 000000000..475bb4640 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/led.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_led_h__ +#define __iwl_fw_api_led_h__ + +/** + * struct iwl_led_cmd - LED switching command + * + * @status: LED status (on/off) + */ +struct iwl_led_cmd { + __le32 status; +} __packed; /* LEDS_CMD_API_S_VER_2 */ + +#endif /* __iwl_fw_api_led_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/location.h b/drivers/net/wireless/intel/iwlwifi/fw/api/location.h new file mode 100644 index 000000000..12af94e16 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/location.h @@ -0,0 +1,1694 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2015-2017 Intel Deutschland GmbH + * Copyright (C) 2018-2021 Intel Corporation + */ +#ifndef __iwl_fw_api_location_h__ +#define __iwl_fw_api_location_h__ + +/** + * enum iwl_location_subcmd_ids - location group command IDs + */ +enum iwl_location_subcmd_ids { + /** + * @TOF_RANGE_REQ_CMD: TOF ranging request, + * uses one of &struct iwl_tof_range_req_cmd_v5, + * &struct iwl_tof_range_req_cmd_v7, + * &struct iwl_tof_range_req_cmd_v8, + * &struct iwl_tof_range_req_cmd_v9, + * &struct iwl_tof_range_req_cmd_v11, + * &struct iwl_tof_range_req_cmd_v7 + */ + TOF_RANGE_REQ_CMD = 0x0, + /** + * @TOF_CONFIG_CMD: TOF configuration, uses &struct iwl_tof_config_cmd + */ + TOF_CONFIG_CMD = 0x1, + /** + * @TOF_RANGE_ABORT_CMD: abort ongoing ranging, uses + * &struct iwl_tof_range_abort_cmd + */ + TOF_RANGE_ABORT_CMD = 0x2, + /** + * @TOF_RANGE_REQ_EXT_CMD: TOF extended ranging config, + * uses &struct iwl_tof_range_req_ext_cmd + */ + TOF_RANGE_REQ_EXT_CMD = 0x3, + /** + * @TOF_RESPONDER_CONFIG_CMD: FTM responder configuration, + * uses &struct iwl_tof_responder_config_cmd + */ + TOF_RESPONDER_CONFIG_CMD = 0x4, + /** + * @TOF_RESPONDER_DYN_CONFIG_CMD: FTM dynamic configuration, + * uses &struct iwl_tof_responder_dyn_config_cmd + */ + TOF_RESPONDER_DYN_CONFIG_CMD = 0x5, + /** + * @CSI_HEADER_NOTIFICATION: CSI header + */ + CSI_HEADER_NOTIFICATION = 0xFA, + /** + * @CSI_CHUNKS_NOTIFICATION: CSI chunk, + * uses &struct iwl_csi_chunk_notification + */ + CSI_CHUNKS_NOTIFICATION = 0xFB, + /** + * @TOF_LC_NOTIF: used for LCI/civic location, contains just + * the action frame + */ + TOF_LC_NOTIF = 0xFC, + /** + * @TOF_RESPONDER_STATS: FTM responder statistics notification, + * uses &struct iwl_ftm_responder_stats + */ + TOF_RESPONDER_STATS = 0xFD, + /** + * @TOF_MCSI_DEBUG_NOTIF: MCSI debug notification, uses + * &struct iwl_tof_mcsi_notif + */ + TOF_MCSI_DEBUG_NOTIF = 0xFE, + /** + * @TOF_RANGE_RESPONSE_NOTIF: ranging response, using + * &struct iwl_tof_range_rsp_ntfy + */ + TOF_RANGE_RESPONSE_NOTIF = 0xFF, +}; + +/** + * struct iwl_tof_config_cmd - ToF configuration + * @tof_disabled: indicates if ToF is disabled (or not) + * @one_sided_disabled: indicates if one-sided is disabled (or not) + * @is_debug_mode: indiciates if debug mode is active + * @is_buf_required: indicates if channel estimation buffer is required + */ +struct iwl_tof_config_cmd { + u8 tof_disabled; + u8 one_sided_disabled; + u8 is_debug_mode; + u8 is_buf_required; +} __packed; + +/** + * enum iwl_tof_bandwidth - values for iwl_tof_range_req_ap_entry.bandwidth + * @IWL_TOF_BW_20_LEGACY: 20 MHz non-HT + * @IWL_TOF_BW_20_HT: 20 MHz HT + * @IWL_TOF_BW_40: 40 MHz + * @IWL_TOF_BW_80: 80 MHz + * @IWL_TOF_BW_160: 160 MHz + * @IWL_TOF_BW_NUM: number of tof bandwidths + */ +enum iwl_tof_bandwidth { + IWL_TOF_BW_20_LEGACY, + IWL_TOF_BW_20_HT, + IWL_TOF_BW_40, + IWL_TOF_BW_80, + IWL_TOF_BW_160, + IWL_TOF_BW_NUM, +}; /* LOCAT_BW_TYPE_E */ + +/* + * enum iwl_tof_algo_type - Algorithym type for range measurement request + */ +enum iwl_tof_algo_type { + IWL_TOF_ALGO_TYPE_MAX_LIKE = 0, + IWL_TOF_ALGO_TYPE_LINEAR_REG = 1, + IWL_TOF_ALGO_TYPE_FFT = 2, + + /* Keep last */ + IWL_TOF_ALGO_TYPE_INVALID, +}; /* ALGO_TYPE_E */ + +/* + * enum iwl_tof_mcsi_ntfy - Enable/Disable MCSI notifications + */ +enum iwl_tof_mcsi_enable { + IWL_TOF_MCSI_DISABLED = 0, + IWL_TOF_MCSI_ENABLED = 1, +}; /* MCSI_ENABLE_E */ + +/** + * enum iwl_tof_responder_cmd_valid_field - valid fields in the responder cfg + * @IWL_TOF_RESPONDER_CMD_VALID_CHAN_INFO: channel info is valid + * @IWL_TOF_RESPONDER_CMD_VALID_TOA_OFFSET: ToA offset is valid + * @IWL_TOF_RESPONDER_CMD_VALID_COMMON_CALIB: common calibration mode is valid + * @IWL_TOF_RESPONDER_CMD_VALID_SPECIFIC_CALIB: spefici calibration mode is + * valid + * @IWL_TOF_RESPONDER_CMD_VALID_BSSID: BSSID is valid + * @IWL_TOF_RESPONDER_CMD_VALID_TX_ANT: TX antenna is valid + * @IWL_TOF_RESPONDER_CMD_VALID_ALGO_TYPE: algorithm type is valid + * @IWL_TOF_RESPONDER_CMD_VALID_NON_ASAP_SUPPORT: non-ASAP support is valid + * @IWL_TOF_RESPONDER_CMD_VALID_STATISTICS_REPORT_SUPPORT: statistics report + * support is valid + * @IWL_TOF_RESPONDER_CMD_VALID_MCSI_NOTIF_SUPPORT: MCSI notification support + * is valid + * @IWL_TOF_RESPONDER_CMD_VALID_FAST_ALGO_SUPPORT: fast algorithm support + * is valid + * @IWL_TOF_RESPONDER_CMD_VALID_RETRY_ON_ALGO_FAIL: retry on algorithm failure + * is valid + * @IWL_TOF_RESPONDER_CMD_VALID_STA_ID: station ID is valid + * @IWL_TOF_RESPONDER_CMD_VALID_NDP_SUPPORT: enable/disable NDP ranging support + * is valid + * @IWL_TOF_RESPONDER_CMD_VALID_NDP_PARAMS: NDP parameters are valid + * @IWL_TOF_RESPONDER_CMD_VALID_LMR_FEEDBACK: LMR feedback support is valid + * @IWL_TOF_RESPONDER_CMD_VALID_SESSION_ID: session id flag is valid + * @IWL_TOF_RESPONDER_CMD_VALID_BSS_COLOR: the bss_color field is valid + * @IWL_TOF_RESPONDER_CMD_VALID_MIN_MAX_TIME_BETWEEN_MSR: the + * min_time_between_msr and max_time_between_msr fields are valid + */ +enum iwl_tof_responder_cmd_valid_field { + IWL_TOF_RESPONDER_CMD_VALID_CHAN_INFO = BIT(0), + IWL_TOF_RESPONDER_CMD_VALID_TOA_OFFSET = BIT(1), + IWL_TOF_RESPONDER_CMD_VALID_COMMON_CALIB = BIT(2), + IWL_TOF_RESPONDER_CMD_VALID_SPECIFIC_CALIB = BIT(3), + IWL_TOF_RESPONDER_CMD_VALID_BSSID = BIT(4), + IWL_TOF_RESPONDER_CMD_VALID_TX_ANT = BIT(5), + IWL_TOF_RESPONDER_CMD_VALID_ALGO_TYPE = BIT(6), + IWL_TOF_RESPONDER_CMD_VALID_NON_ASAP_SUPPORT = BIT(7), + IWL_TOF_RESPONDER_CMD_VALID_STATISTICS_REPORT_SUPPORT = BIT(8), + IWL_TOF_RESPONDER_CMD_VALID_MCSI_NOTIF_SUPPORT = BIT(9), + IWL_TOF_RESPONDER_CMD_VALID_FAST_ALGO_SUPPORT = BIT(10), + IWL_TOF_RESPONDER_CMD_VALID_RETRY_ON_ALGO_FAIL = BIT(11), + IWL_TOF_RESPONDER_CMD_VALID_STA_ID = BIT(12), + IWL_TOF_RESPONDER_CMD_VALID_NDP_SUPPORT = BIT(22), + IWL_TOF_RESPONDER_CMD_VALID_NDP_PARAMS = BIT(23), + IWL_TOF_RESPONDER_CMD_VALID_LMR_FEEDBACK = BIT(24), + IWL_TOF_RESPONDER_CMD_VALID_SESSION_ID = BIT(25), + IWL_TOF_RESPONDER_CMD_VALID_BSS_COLOR = BIT(26), + IWL_TOF_RESPONDER_CMD_VALID_MIN_MAX_TIME_BETWEEN_MSR = BIT(27), +}; + +/** + * enum iwl_tof_responder_cfg_flags - responder configuration flags + * @IWL_TOF_RESPONDER_FLAGS_NON_ASAP_SUPPORT: non-ASAP support + * @IWL_TOF_RESPONDER_FLAGS_REPORT_STATISTICS: report statistics + * @IWL_TOF_RESPONDER_FLAGS_REPORT_MCSI: report MCSI + * @IWL_TOF_RESPONDER_FLAGS_ALGO_TYPE: algorithm type + * @IWL_TOF_RESPONDER_FLAGS_TOA_OFFSET_MODE: ToA offset mode + * @IWL_TOF_RESPONDER_FLAGS_COMMON_CALIB_MODE: common calibration mode + * @IWL_TOF_RESPONDER_FLAGS_SPECIFIC_CALIB_MODE: specific calibration mode + * @IWL_TOF_RESPONDER_FLAGS_FAST_ALGO_SUPPORT: fast algorithm support + * @IWL_TOF_RESPONDER_FLAGS_RETRY_ON_ALGO_FAIL: retry on algorithm fail + * @IWL_TOF_RESPONDER_FLAGS_FTM_TX_ANT: TX antenna mask + * @IWL_TOF_RESPONDER_FLAGS_NDP_SUPPORT: support NDP ranging + * @IWL_TOF_RESPONDER_FLAGS_LMR_FEEDBACK: request for LMR feedback if the + * initiator supports it + * @IWL_TOF_RESPONDER_FLAGS_SESSION_ID: send the session id in the initial FTM + * frame. + */ +enum iwl_tof_responder_cfg_flags { + IWL_TOF_RESPONDER_FLAGS_NON_ASAP_SUPPORT = BIT(0), + IWL_TOF_RESPONDER_FLAGS_REPORT_STATISTICS = BIT(1), + IWL_TOF_RESPONDER_FLAGS_REPORT_MCSI = BIT(2), + IWL_TOF_RESPONDER_FLAGS_ALGO_TYPE = BIT(3) | BIT(4) | BIT(5), + IWL_TOF_RESPONDER_FLAGS_TOA_OFFSET_MODE = BIT(6), + IWL_TOF_RESPONDER_FLAGS_COMMON_CALIB_MODE = BIT(7), + IWL_TOF_RESPONDER_FLAGS_SPECIFIC_CALIB_MODE = BIT(8), + IWL_TOF_RESPONDER_FLAGS_FAST_ALGO_SUPPORT = BIT(9), + IWL_TOF_RESPONDER_FLAGS_RETRY_ON_ALGO_FAIL = BIT(10), + IWL_TOF_RESPONDER_FLAGS_FTM_TX_ANT = RATE_MCS_ANT_AB_MSK, + IWL_TOF_RESPONDER_FLAGS_NDP_SUPPORT = BIT(24), + IWL_TOF_RESPONDER_FLAGS_LMR_FEEDBACK = BIT(25), + IWL_TOF_RESPONDER_FLAGS_SESSION_ID = BIT(27), +}; + +/** + * struct iwl_tof_responder_config_cmd_v6 - ToF AP mode (for debug) + * @cmd_valid_fields: &iwl_tof_responder_cmd_valid_field + * @responder_cfg_flags: &iwl_tof_responder_cfg_flags + * @bandwidth: current AP Bandwidth: &enum iwl_tof_bandwidth + * @rate: current AP rate + * @channel_num: current AP Channel + * @ctrl_ch_position: coding of the control channel position relative to + * the center frequency, see iwl_mvm_get_ctrl_pos() + * @sta_id: index of the AP STA when in AP mode + * @reserved1: reserved + * @toa_offset: Artificial addition [pSec] for the ToA - to be used for debug + * purposes, simulating station movement by adding various values + * to this field + * @common_calib: XVT: common calibration value + * @specific_calib: XVT: specific calibration value + * @bssid: Current AP BSSID + * @reserved2: reserved + */ +struct iwl_tof_responder_config_cmd_v6 { + __le32 cmd_valid_fields; + __le32 responder_cfg_flags; + u8 bandwidth; + u8 rate; + u8 channel_num; + u8 ctrl_ch_position; + u8 sta_id; + u8 reserved1; + __le16 toa_offset; + __le16 common_calib; + __le16 specific_calib; + u8 bssid[ETH_ALEN]; + __le16 reserved2; +} __packed; /* TOF_RESPONDER_CONFIG_CMD_API_S_VER_6 */ + +/** + * struct iwl_tof_responder_config_cmd_v7 - ToF AP mode (for debug) + * @cmd_valid_fields: &iwl_tof_responder_cmd_valid_field + * @responder_cfg_flags: &iwl_tof_responder_cfg_flags + * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. + * bits 4 - 7: &enum iwl_location_bw. + * @rate: current AP rate + * @channel_num: current AP Channel + * @ctrl_ch_position: coding of the control channel position relative to + * the center frequency, see iwl_mvm_get_ctrl_pos() + * @sta_id: index of the AP STA when in AP mode + * @reserved1: reserved + * @toa_offset: Artificial addition [pSec] for the ToA - to be used for debug + * purposes, simulating station movement by adding various values + * to this field + * @common_calib: XVT: common calibration value + * @specific_calib: XVT: specific calibration value + * @bssid: Current AP BSSID + * @reserved2: reserved + */ +struct iwl_tof_responder_config_cmd_v7 { + __le32 cmd_valid_fields; + __le32 responder_cfg_flags; + u8 format_bw; + u8 rate; + u8 channel_num; + u8 ctrl_ch_position; + u8 sta_id; + u8 reserved1; + __le16 toa_offset; + __le16 common_calib; + __le16 specific_calib; + u8 bssid[ETH_ALEN]; + __le16 reserved2; +} __packed; /* TOF_RESPONDER_CONFIG_CMD_API_S_VER_7 */ + +#define IWL_RESPONDER_STS_POS 3 +#define IWL_RESPONDER_TOTAL_LTF_POS 6 + +/** + * struct iwl_tof_responder_config_cmd_v8 - ToF AP mode (for debug) + * @cmd_valid_fields: &iwl_tof_responder_cmd_valid_field + * @responder_cfg_flags: &iwl_tof_responder_cfg_flags + * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. + * bits 4 - 7: &enum iwl_location_bw. + * @rate: current AP rate + * @channel_num: current AP Channel + * @ctrl_ch_position: coding of the control channel position relative to + * the center frequency, see iwl_mvm_get_ctrl_pos() + * @sta_id: index of the AP STA when in AP mode + * @reserved1: reserved + * @toa_offset: Artificial addition [pSec] for the ToA - to be used for debug + * purposes, simulating station movement by adding various values + * to this field + * @common_calib: XVT: common calibration value + * @specific_calib: XVT: specific calibration value + * @bssid: Current AP BSSID + * @r2i_ndp_params: parameters for R2I NDP. + * bits 0 - 2: max number of LTF repetitions + * bits 3 - 5: max number of spatial streams (supported values are < 2) + * bits 6 - 7: max number of total LTFs see + * &enum ieee80211_range_params_max_total_ltf + * @i2r_ndp_params: parameters for I2R NDP. + * bits 0 - 2: max number of LTF repetitions + * bits 3 - 5: max number of spatial streams + * bits 6 - 7: max number of total LTFs see + * &enum ieee80211_range_params_max_total_ltf + */ +struct iwl_tof_responder_config_cmd_v8 { + __le32 cmd_valid_fields; + __le32 responder_cfg_flags; + u8 format_bw; + u8 rate; + u8 channel_num; + u8 ctrl_ch_position; + u8 sta_id; + u8 reserved1; + __le16 toa_offset; + __le16 common_calib; + __le16 specific_calib; + u8 bssid[ETH_ALEN]; + u8 r2i_ndp_params; + u8 i2r_ndp_params; +} __packed; /* TOF_RESPONDER_CONFIG_CMD_API_S_VER_8 */ + +/** + * struct iwl_tof_responder_config_cmd_v9 - ToF AP mode (for debug) + * @cmd_valid_fields: &iwl_tof_responder_cmd_valid_field + * @responder_cfg_flags: &iwl_tof_responder_cfg_flags + * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. + * bits 4 - 7: &enum iwl_location_bw. + * @bss_color: current AP bss_color + * @channel_num: current AP Channel + * @ctrl_ch_position: coding of the control channel position relative to + * the center frequency, see iwl_mvm_get_ctrl_pos() + * @sta_id: index of the AP STA when in AP mode + * @reserved1: reserved + * @toa_offset: Artificial addition [pSec] for the ToA - to be used for debug + * purposes, simulating station movement by adding various values + * to this field + * @common_calib: XVT: common calibration value + * @specific_calib: XVT: specific calibration value + * @bssid: Current AP BSSID + * @r2i_ndp_params: parameters for R2I NDP. + * bits 0 - 2: max number of LTF repetitions + * bits 3 - 5: max number of spatial streams (supported values are < 2) + * bits 6 - 7: max number of total LTFs see + * &enum ieee80211_range_params_max_total_ltf + * @i2r_ndp_params: parameters for I2R NDP. + * bits 0 - 2: max number of LTF repetitions + * bits 3 - 5: max number of spatial streams + * bits 6 - 7: max number of total LTFs see + * &enum ieee80211_range_params_max_total_ltf + * @min_time_between_msr: for non trigger based NDP ranging, minimum time + * between measurements in milliseconds. + * @max_time_between_msr: for non trigger based NDP ranging, maximum time + * between measurements in milliseconds. + */ +struct iwl_tof_responder_config_cmd_v9 { + __le32 cmd_valid_fields; + __le32 responder_cfg_flags; + u8 format_bw; + u8 bss_color; + u8 channel_num; + u8 ctrl_ch_position; + u8 sta_id; + u8 reserved1; + __le16 toa_offset; + __le16 common_calib; + __le16 specific_calib; + u8 bssid[ETH_ALEN]; + u8 r2i_ndp_params; + u8 i2r_ndp_params; + __le16 min_time_between_msr; + __le16 max_time_between_msr; +} __packed; /* TOF_RESPONDER_CONFIG_CMD_API_S_VER_8 */ + +#define IWL_LCI_CIVIC_IE_MAX_SIZE 400 + +/** + * struct iwl_tof_responder_dyn_config_cmd - Dynamic responder settings + * @lci_len: The length of the 1st (LCI) part in the @lci_civic buffer + * @civic_len: The length of the 2nd (CIVIC) part in the @lci_civic buffer + * @lci_civic: The LCI/CIVIC buffer. LCI data (if exists) comes first, then, if + * needed, 0-padding such that the next part is dword-aligned, then CIVIC + * data (if exists) follows, and then 0-padding again to complete a + * 4-multiple long buffer. + */ +struct iwl_tof_responder_dyn_config_cmd_v2 { + __le32 lci_len; + __le32 civic_len; + u8 lci_civic[]; +} __packed; /* TOF_RESPONDER_DYN_CONFIG_CMD_API_S_VER_2 */ + +#define IWL_LCI_MAX_SIZE 160 +#define IWL_CIVIC_MAX_SIZE 160 +#define HLTK_11AZ_LEN 32 + +/** + * enum iwl_responder_dyn_cfg_valid_flags - valid flags for dyn_config_cmd + * @IWL_RESPONDER_DYN_CFG_VALID_LCI: LCI data is valid + * @IWL_RESPONDER_DYN_CFG_VALID_CIVIC: Civic data is valid + * @IWL_RESPONDER_DYN_CFG_VALID_PASN_STA: the pasn_addr, HLTK and cipher fields + * are valid. + */ +enum iwl_responder_dyn_cfg_valid_flags { + IWL_RESPONDER_DYN_CFG_VALID_LCI = BIT(0), + IWL_RESPONDER_DYN_CFG_VALID_CIVIC = BIT(1), + IWL_RESPONDER_DYN_CFG_VALID_PASN_STA = BIT(2), +}; + +/** + * struct iwl_tof_responder_dyn_config_cmd - Dynamic responder settings + * @cipher: The negotiated cipher. see &enum iwl_location_cipher. + * @valid_flags: flags indicating which fields in the command are valid. see + * &enum iwl_responder_dyn_cfg_valid_flags. + * @lci_len: length of the LCI data in bytes + * @civic_len: length of the Civic data in bytes + * @lci_buf: the LCI buffer + * @civic_buf: the Civic buffer + * @hltk_buf: HLTK for secure LTF bits generation for the specified station + * @addr: mac address of the station for which to use the HLTK + * @reserved: for alignment + */ +struct iwl_tof_responder_dyn_config_cmd { + u8 cipher; + u8 valid_flags; + u8 lci_len; + u8 civic_len; + u8 lci_buf[IWL_LCI_MAX_SIZE]; + u8 civic_buf[IWL_LCI_MAX_SIZE]; + u8 hltk_buf[HLTK_11AZ_LEN]; + u8 addr[ETH_ALEN]; + u8 reserved[2]; +} __packed; /* TOF_RESPONDER_DYN_CONFIG_CMD_API_S_VER_3 */ + +/** + * struct iwl_tof_range_req_ext_cmd - extended range req for WLS + * @tsf_timer_offset_msec: the recommended time offset (mSec) from the AP's TSF + * @reserved: reserved + * @min_delta_ftm: Minimal time between two consecutive measurements, + * in units of 100us. 0 means no preference by station + * @ftm_format_and_bw20M: FTM Channel Spacing/Format for 20MHz: recommended + * value be sent to the AP + * @ftm_format_and_bw40M: FTM Channel Spacing/Format for 40MHz: recommended + * value to be sent to the AP + * @ftm_format_and_bw80M: FTM Channel Spacing/Format for 80MHz: recommended + * value to be sent to the AP + */ +struct iwl_tof_range_req_ext_cmd { + __le16 tsf_timer_offset_msec; + __le16 reserved; + u8 min_delta_ftm; + u8 ftm_format_and_bw20M; + u8 ftm_format_and_bw40M; + u8 ftm_format_and_bw80M; +} __packed; + +/** + * enum iwl_tof_location_query - values for query bitmap + * @IWL_TOF_LOC_LCI: query LCI + * @IWL_TOF_LOC_CIVIC: query civic + */ +enum iwl_tof_location_query { + IWL_TOF_LOC_LCI = 0x01, + IWL_TOF_LOC_CIVIC = 0x02, +}; + + /** + * struct iwl_tof_range_req_ap_entry_v2 - AP configuration parameters + * @channel_num: Current AP Channel + * @bandwidth: Current AP Bandwidth. One of iwl_tof_bandwidth. + * @tsf_delta_direction: TSF relatively to the subject AP + * @ctrl_ch_position: Coding of the control channel position relative to the + * center frequency, see iwl_mvm_get_ctrl_pos(). + * @bssid: AP's BSSID + * @measure_type: Measurement type: 0 - two sided, 1 - One sided + * @num_of_bursts: Recommended value to be sent to the AP. 2s Exponent of the + * number of measurement iterations (min 2^0 = 1, max 2^14) + * @burst_period: Recommended value to be sent to the AP. Measurement + * periodicity In units of 100ms. ignored if num_of_bursts = 0 + * @samples_per_burst: 2-sided: the number of FTMs pairs in single Burst (1-31); + * 1-sided: how many rts/cts pairs should be used per burst. + * @retries_per_sample: Max number of retries that the LMAC should send + * in case of no replies by the AP. + * @tsf_delta: TSF Delta in units of microseconds. + * The difference between the AP TSF and the device local clock. + * @location_req: Location Request Bit[0] LCI should be sent in the FTMR; + * Bit[1] Civic should be sent in the FTMR + * @asap_mode: 0 - non asap mode, 1 - asap mode (not relevant for one sided) + * @enable_dyn_ack: Enable Dynamic ACK BW. + * 0: Initiator interact with regular AP; + * 1: Initiator interact with Responder machine: need to send the + * Initiator Acks with HT 40MHz / 80MHz, since the Responder should + * use it for its ch est measurement (this flag will be set when we + * configure the opposite machine to be Responder). + * @rssi: Last received value + * legal values: -128-0 (0x7f). above 0x0 indicating an invalid value. + * @algo_type: &enum iwl_tof_algo_type + * @notify_mcsi: &enum iwl_tof_mcsi_ntfy. + * @reserved: For alignment and future use + */ +struct iwl_tof_range_req_ap_entry_v2 { + u8 channel_num; + u8 bandwidth; + u8 tsf_delta_direction; + u8 ctrl_ch_position; + u8 bssid[ETH_ALEN]; + u8 measure_type; + u8 num_of_bursts; + __le16 burst_period; + u8 samples_per_burst; + u8 retries_per_sample; + __le32 tsf_delta; + u8 location_req; + u8 asap_mode; + u8 enable_dyn_ack; + s8 rssi; + u8 algo_type; + u8 notify_mcsi; + __le16 reserved; +} __packed; /* LOCATION_RANGE_REQ_AP_ENTRY_CMD_API_S_VER_2 */ + +/** + * enum iwl_initiator_ap_flags - per responder FTM configuration flags + * @IWL_INITIATOR_AP_FLAGS_ASAP: Request for ASAP measurement. + * @IWL_INITIATOR_AP_FLAGS_LCI_REQUEST: Request for LCI information + * @IWL_INITIATOR_AP_FLAGS_CIVIC_REQUEST: Request for CIVIC information + * @IWL_INITIATOR_AP_FLAGS_DYN_ACK: Send HT/VHT ack for FTM frames. If not set, + * 20Mhz dup acks will be sent. + * @IWL_INITIATOR_AP_FLAGS_ALGO_LR: Use LR algo type for rtt calculation. + * Default algo type is ML. + * @IWL_INITIATOR_AP_FLAGS_ALGO_FFT: Use FFT algo type for rtt calculation. + * Default algo type is ML. + * @IWL_INITIATOR_AP_FLAGS_MCSI_REPORT: Send the MCSI for each FTM frame to the + * driver. + * @IWL_INITIATOR_AP_FLAGS_NON_TB: Use non trigger based flow + * @IWL_INITIATOR_AP_FLAGS_TB: Use trigger based flow + * @IWL_INITIATOR_AP_FLAGS_SECURED: request secure LTF measurement + * @IWL_INITIATOR_AP_FLAGS_LMR_FEEDBACK: Send LMR feedback + * @IWL_INITIATOR_AP_FLAGS_USE_CALIB: Use calibration values from the request + * instead of fw internal values. + * @IWL_INITIATOR_AP_FLAGS_PMF: request to protect the negotiation and LMR + * frames with protected management frames. + * @IWL_INITIATOR_AP_FLAGS_TERMINATE_ON_LMR_FEEDBACK: terminate the session if + * the responder asked for LMR feedback although the initiator did not set + * the LMR feedback bit in the FTM request. If not set, the initiator will + * continue with the session and will provide the LMR feedback. + */ +enum iwl_initiator_ap_flags { + IWL_INITIATOR_AP_FLAGS_ASAP = BIT(1), + IWL_INITIATOR_AP_FLAGS_LCI_REQUEST = BIT(2), + IWL_INITIATOR_AP_FLAGS_CIVIC_REQUEST = BIT(3), + IWL_INITIATOR_AP_FLAGS_DYN_ACK = BIT(4), + IWL_INITIATOR_AP_FLAGS_ALGO_LR = BIT(5), + IWL_INITIATOR_AP_FLAGS_ALGO_FFT = BIT(6), + IWL_INITIATOR_AP_FLAGS_MCSI_REPORT = BIT(8), + IWL_INITIATOR_AP_FLAGS_NON_TB = BIT(9), + IWL_INITIATOR_AP_FLAGS_TB = BIT(10), + IWL_INITIATOR_AP_FLAGS_SECURED = BIT(11), + IWL_INITIATOR_AP_FLAGS_LMR_FEEDBACK = BIT(12), + IWL_INITIATOR_AP_FLAGS_USE_CALIB = BIT(13), + IWL_INITIATOR_AP_FLAGS_PMF = BIT(14), + IWL_INITIATOR_AP_FLAGS_TERMINATE_ON_LMR_FEEDBACK = BIT(15), +}; + +/** + * struct iwl_tof_range_req_ap_entry_v3 - AP configuration parameters + * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. + * @channel_num: AP Channel number + * @bandwidth: AP bandwidth. One of iwl_tof_bandwidth. + * @ctrl_ch_position: Coding of the control channel position relative to the + * center frequency, see iwl_mvm_get_ctrl_pos(). + * @ftmr_max_retries: Max number of retries to send the FTMR in case of no + * reply from the AP. + * @bssid: AP's BSSID + * @burst_period: Recommended value to be sent to the AP. Measurement + * periodicity In units of 100ms. ignored if num_of_bursts_exp = 0 + * @samples_per_burst: the number of FTMs pairs in single Burst (1-31); + * @num_of_bursts: Recommended value to be sent to the AP. 2s Exponent of + * the number of measurement iterations (min 2^0 = 1, max 2^14) + * @reserved: For alignment and future use + * @tsf_delta: not in use + */ +struct iwl_tof_range_req_ap_entry_v3 { + __le32 initiator_ap_flags; + u8 channel_num; + u8 bandwidth; + u8 ctrl_ch_position; + u8 ftmr_max_retries; + u8 bssid[ETH_ALEN]; + __le16 burst_period; + u8 samples_per_burst; + u8 num_of_bursts; + __le16 reserved; + __le32 tsf_delta; +} __packed; /* LOCATION_RANGE_REQ_AP_ENTRY_CMD_API_S_VER_3 */ + +/** + * enum iwl_location_frame_format - location frame formats + * @IWL_LOCATION_FRAME_FORMAT_LEGACY: legacy + * @IWL_LOCATION_FRAME_FORMAT_HT: HT + * @IWL_LOCATION_FRAME_FORMAT_VHT: VHT + * @IWL_LOCATION_FRAME_FORMAT_HE: HE + */ +enum iwl_location_frame_format { + IWL_LOCATION_FRAME_FORMAT_LEGACY, + IWL_LOCATION_FRAME_FORMAT_HT, + IWL_LOCATION_FRAME_FORMAT_VHT, + IWL_LOCATION_FRAME_FORMAT_HE, +}; + +/** + * enum iwl_location_bw - location bandwidth selection + * @IWL_LOCATION_BW_20MHZ: 20MHz + * @IWL_LOCATION_BW_40MHZ: 40MHz + * @IWL_LOCATION_BW_80MHZ: 80MHz + */ +enum iwl_location_bw { + IWL_LOCATION_BW_20MHZ, + IWL_LOCATION_BW_40MHZ, + IWL_LOCATION_BW_80MHZ, + IWL_LOCATION_BW_160MHZ, +}; + +#define TK_11AZ_LEN 32 + +#define LOCATION_BW_POS 4 + +/** + * struct iwl_tof_range_req_ap_entry_v4 - AP configuration parameters + * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. + * @channel_num: AP Channel number + * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. + * bits 4 - 7: &enum iwl_location_bw. + * @ctrl_ch_position: Coding of the control channel position relative to the + * center frequency, see iwl_mvm_get_ctrl_pos(). + * @ftmr_max_retries: Max number of retries to send the FTMR in case of no + * reply from the AP. + * @bssid: AP's BSSID + * @burst_period: Recommended value to be sent to the AP. Measurement + * periodicity In units of 100ms. ignored if num_of_bursts_exp = 0 + * @samples_per_burst: the number of FTMs pairs in single Burst (1-31); + * @num_of_bursts: Recommended value to be sent to the AP. 2s Exponent of + * the number of measurement iterations (min 2^0 = 1, max 2^14) + * @reserved: For alignment and future use + * @hltk: HLTK to be used for secured 11az measurement + * @tk: TK to be used for secured 11az measurement + */ +struct iwl_tof_range_req_ap_entry_v4 { + __le32 initiator_ap_flags; + u8 channel_num; + u8 format_bw; + u8 ctrl_ch_position; + u8 ftmr_max_retries; + u8 bssid[ETH_ALEN]; + __le16 burst_period; + u8 samples_per_burst; + u8 num_of_bursts; + __le16 reserved; + u8 hltk[HLTK_11AZ_LEN]; + u8 tk[TK_11AZ_LEN]; +} __packed; /* LOCATION_RANGE_REQ_AP_ENTRY_CMD_API_S_VER_4 */ + +/** + * enum iwl_location_cipher - location cipher selection + * @IWL_LOCATION_CIPHER_CCMP_128: CCMP 128 + * @IWL_LOCATION_CIPHER_GCMP_128: GCMP 128 + * @IWL_LOCATION_CIPHER_GCMP_256: GCMP 256 + * @IWL_LOCATION_CIPHER_INVALID: security is not used. + * @IWL_LOCATION_CIPHER_MAX: maximum value for this enum. + */ +enum iwl_location_cipher { + IWL_LOCATION_CIPHER_CCMP_128, + IWL_LOCATION_CIPHER_GCMP_128, + IWL_LOCATION_CIPHER_GCMP_256, + IWL_LOCATION_CIPHER_INVALID, + IWL_LOCATION_CIPHER_MAX, +}; + +/** + * struct iwl_tof_range_req_ap_entry_v6 - AP configuration parameters + * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. + * @channel_num: AP Channel number + * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. + * bits 4 - 7: &enum iwl_location_bw. + * @ctrl_ch_position: Coding of the control channel position relative to the + * center frequency, see iwl_mvm_get_ctrl_pos(). + * @ftmr_max_retries: Max number of retries to send the FTMR in case of no + * reply from the AP. + * @bssid: AP's BSSID + * @burst_period: Recommended value to be sent to the AP. Measurement + * periodicity In units of 100ms. ignored if num_of_bursts_exp = 0 + * @samples_per_burst: the number of FTMs pairs in single Burst (1-31); + * @num_of_bursts: Recommended value to be sent to the AP. 2s Exponent of + * the number of measurement iterations (min 2^0 = 1, max 2^14) + * @sta_id: the station id of the AP. Only relevant when associated to the AP, + * otherwise should be set to &IWL_MVM_INVALID_STA. + * @cipher: pairwise cipher suite for secured measurement. + * &enum iwl_location_cipher. + * @hltk: HLTK to be used for secured 11az measurement + * @tk: TK to be used for secured 11az measurement + * @calib: An array of calibration values per FTM rx bandwidth. + * If &IWL_INITIATOR_AP_FLAGS_USE_CALIB is set, the fw will use the + * calibration value that corresponds to the rx bandwidth of the FTM + * frame. + * @beacon_interval: beacon interval of the AP in TUs. Only required if + * &IWL_INITIATOR_AP_FLAGS_TB is set. + */ +struct iwl_tof_range_req_ap_entry_v6 { + __le32 initiator_ap_flags; + u8 channel_num; + u8 format_bw; + u8 ctrl_ch_position; + u8 ftmr_max_retries; + u8 bssid[ETH_ALEN]; + __le16 burst_period; + u8 samples_per_burst; + u8 num_of_bursts; + u8 sta_id; + u8 cipher; + u8 hltk[HLTK_11AZ_LEN]; + u8 tk[TK_11AZ_LEN]; + __le16 calib[IWL_TOF_BW_NUM]; + __le16 beacon_interval; +} __packed; /* LOCATION_RANGE_REQ_AP_ENTRY_CMD_API_S_VER_6 */ + +/** + * struct iwl_tof_range_req_ap_entry_v7 - AP configuration parameters + * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. + * @channel_num: AP Channel number + * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. + * bits 4 - 7: &enum iwl_location_bw. + * @ctrl_ch_position: Coding of the control channel position relative to the + * center frequency, see iwl_mvm_get_ctrl_pos(). + * @ftmr_max_retries: Max number of retries to send the FTMR in case of no + * reply from the AP. + * @bssid: AP's BSSID + * @burst_period: Recommended value to be sent to the AP. Measurement + * periodicity In units of 100ms. ignored if num_of_bursts_exp = 0 + * @samples_per_burst: the number of FTMs pairs in single Burst (1-31); + * @num_of_bursts: Recommended value to be sent to the AP. 2s Exponent of + * the number of measurement iterations (min 2^0 = 1, max 2^14) + * @sta_id: the station id of the AP. Only relevant when associated to the AP, + * otherwise should be set to &IWL_MVM_INVALID_STA. + * @cipher: pairwise cipher suite for secured measurement. + * &enum iwl_location_cipher. + * @hltk: HLTK to be used for secured 11az measurement + * @tk: TK to be used for secured 11az measurement + * @calib: An array of calibration values per FTM rx bandwidth. + * If &IWL_INITIATOR_AP_FLAGS_USE_CALIB is set, the fw will use the + * calibration value that corresponds to the rx bandwidth of the FTM + * frame. + * @beacon_interval: beacon interval of the AP in TUs. Only required if + * &IWL_INITIATOR_AP_FLAGS_TB is set. + * @rx_pn: the next expected PN for protected management frames Rx. LE byte + * order. Only valid if &IWL_INITIATOR_AP_FLAGS_SECURED is set and sta_id + * is set to &IWL_MVM_INVALID_STA. + * @tx_pn: the next PN to use for protected management frames Tx. LE byte + * order. Only valid if &IWL_INITIATOR_AP_FLAGS_SECURED is set and sta_id + * is set to &IWL_MVM_INVALID_STA. + */ +struct iwl_tof_range_req_ap_entry_v7 { + __le32 initiator_ap_flags; + u8 channel_num; + u8 format_bw; + u8 ctrl_ch_position; + u8 ftmr_max_retries; + u8 bssid[ETH_ALEN]; + __le16 burst_period; + u8 samples_per_burst; + u8 num_of_bursts; + u8 sta_id; + u8 cipher; + u8 hltk[HLTK_11AZ_LEN]; + u8 tk[TK_11AZ_LEN]; + __le16 calib[IWL_TOF_BW_NUM]; + __le16 beacon_interval; + u8 rx_pn[IEEE80211_CCMP_PN_LEN]; + u8 tx_pn[IEEE80211_CCMP_PN_LEN]; +} __packed; /* LOCATION_RANGE_REQ_AP_ENTRY_CMD_API_S_VER_7 */ + +#define IWL_LOCATION_MAX_STS_POS 3 + +/** + * struct iwl_tof_range_req_ap_entry_v8 - AP configuration parameters + * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. + * @channel_num: AP Channel number + * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. + * bits 4 - 7: &enum iwl_location_bw. + * @ctrl_ch_position: Coding of the control channel position relative to the + * center frequency, see iwl_mvm_get_ctrl_pos(). + * @ftmr_max_retries: Max number of retries to send the FTMR in case of no + * reply from the AP. + * @bssid: AP's BSSID + * @burst_period: Recommended value to be sent to the AP. Measurement + * periodicity In units of 100ms. ignored if num_of_bursts_exp = 0 + * @samples_per_burst: the number of FTMs pairs in single Burst (1-31); + * @num_of_bursts: Recommended value to be sent to the AP. 2s Exponent of + * the number of measurement iterations (min 2^0 = 1, max 2^14) + * @sta_id: the station id of the AP. Only relevant when associated to the AP, + * otherwise should be set to &IWL_MVM_INVALID_STA. + * @cipher: pairwise cipher suite for secured measurement. + * &enum iwl_location_cipher. + * @hltk: HLTK to be used for secured 11az measurement + * @tk: TK to be used for secured 11az measurement + * @calib: An array of calibration values per FTM rx bandwidth. + * If &IWL_INITIATOR_AP_FLAGS_USE_CALIB is set, the fw will use the + * calibration value that corresponds to the rx bandwidth of the FTM + * frame. + * @beacon_interval: beacon interval of the AP in TUs. Only required if + * &IWL_INITIATOR_AP_FLAGS_TB is set. + * @rx_pn: the next expected PN for protected management frames Rx. LE byte + * order. Only valid if &IWL_INITIATOR_AP_FLAGS_SECURED is set and sta_id + * is set to &IWL_MVM_INVALID_STA. + * @tx_pn: the next PN to use for protected management frames Tx. LE byte + * order. Only valid if &IWL_INITIATOR_AP_FLAGS_SECURED is set and sta_id + * is set to &IWL_MVM_INVALID_STA. + * @r2i_ndp_params: parameters for R2I NDP ranging negotiation. + * bits 0 - 2: max LTF repetitions + * bits 3 - 5: max number of spatial streams + * bits 6 - 7: reserved + * @i2r_ndp_params: parameters for I2R NDP ranging negotiation. + * bits 0 - 2: max LTF repetitions + * bits 3 - 5: max number of spatial streams (supported values are < 2) + * bits 6 - 7: reserved + * @r2i_max_total_ltf: R2I Max Total LTFs for NDP ranging negotiation. + * One of &enum ieee80211_range_params_max_total_ltf. + * @i2r_max_total_ltf: I2R Max Total LTFs for NDP ranging negotiation. + * One of &enum ieee80211_range_params_max_total_ltf. + */ +struct iwl_tof_range_req_ap_entry_v8 { + __le32 initiator_ap_flags; + u8 channel_num; + u8 format_bw; + u8 ctrl_ch_position; + u8 ftmr_max_retries; + u8 bssid[ETH_ALEN]; + __le16 burst_period; + u8 samples_per_burst; + u8 num_of_bursts; + u8 sta_id; + u8 cipher; + u8 hltk[HLTK_11AZ_LEN]; + u8 tk[TK_11AZ_LEN]; + __le16 calib[IWL_TOF_BW_NUM]; + __le16 beacon_interval; + u8 rx_pn[IEEE80211_CCMP_PN_LEN]; + u8 tx_pn[IEEE80211_CCMP_PN_LEN]; + u8 r2i_ndp_params; + u8 i2r_ndp_params; + u8 r2i_max_total_ltf; + u8 i2r_max_total_ltf; +} __packed; /* LOCATION_RANGE_REQ_AP_ENTRY_CMD_API_S_VER_8 */ + +/** + * struct iwl_tof_range_req_ap_entry_v9 - AP configuration parameters + * @initiator_ap_flags: see &enum iwl_initiator_ap_flags. + * @channel_num: AP Channel number + * @format_bw: bits 0 - 3: &enum iwl_location_frame_format. + * bits 4 - 7: &enum iwl_location_bw. + * @ctrl_ch_position: Coding of the control channel position relative to the + * center frequency, see iwl_mvm_get_ctrl_pos(). + * @ftmr_max_retries: Max number of retries to send the FTMR in case of no + * reply from the AP. + * @bssid: AP's BSSID + * @burst_period: For EDCA based ranging: Recommended value to be sent to the + * AP. Measurement periodicity In units of 100ms. ignored if + * num_of_bursts_exp = 0. + * For non trigger based NDP ranging, the maximum time between + * measurements in units of milliseconds. + * @samples_per_burst: the number of FTMs pairs in single Burst (1-31); + * @num_of_bursts: Recommended value to be sent to the AP. 2s Exponent of + * the number of measurement iterations (min 2^0 = 1, max 2^14) + * @sta_id: the station id of the AP. Only relevant when associated to the AP, + * otherwise should be set to &IWL_MVM_INVALID_STA. + * @cipher: pairwise cipher suite for secured measurement. + * &enum iwl_location_cipher. + * @hltk: HLTK to be used for secured 11az measurement + * @tk: TK to be used for secured 11az measurement + * @calib: An array of calibration values per FTM rx bandwidth. + * If &IWL_INITIATOR_AP_FLAGS_USE_CALIB is set, the fw will use the + * calibration value that corresponds to the rx bandwidth of the FTM + * frame. + * @beacon_interval: beacon interval of the AP in TUs. Only required if + * &IWL_INITIATOR_AP_FLAGS_TB is set. + * @bss_color: the BSS color of the responder. Only valid if + * &IWL_INITIATOR_AP_FLAGS_TB or &IWL_INITIATOR_AP_FLAGS_NON_TB is set. + * @rx_pn: the next expected PN for protected management frames Rx. LE byte + * order. Only valid if &IWL_INITIATOR_AP_FLAGS_SECURED is set and sta_id + * is set to &IWL_MVM_INVALID_STA. + * @tx_pn: the next PN to use for protected management frames Tx. LE byte + * order. Only valid if &IWL_INITIATOR_AP_FLAGS_SECURED is set and sta_id + * is set to &IWL_MVM_INVALID_STA. + * @r2i_ndp_params: parameters for R2I NDP ranging negotiation. + * bits 0 - 2: max LTF repetitions + * bits 3 - 5: max number of spatial streams + * bits 6 - 7: reserved + * @i2r_ndp_params: parameters for I2R NDP ranging negotiation. + * bits 0 - 2: max LTF repetitions + * bits 3 - 5: max number of spatial streams (supported values are < 2) + * bits 6 - 7: reserved + * @r2i_max_total_ltf: R2I Max Total LTFs for NDP ranging negotiation. + * One of &enum ieee80211_range_params_max_total_ltf. + * @i2r_max_total_ltf: I2R Max Total LTFs for NDP ranging negotiation. + * One of &enum ieee80211_range_params_max_total_ltf. + * @bss_color: the BSS color of the responder. Only valid if + * &IWL_INITIATOR_AP_FLAGS_NON_TB or &IWL_INITIATOR_AP_FLAGS_TB is set. + * @band: 0 for 5.2 GHz, 1 for 2.4 GHz, 2 for 6GHz + * @min_time_between_msr: For non trigger based NDP ranging, the minimum time + * between measurements in units of milliseconds + */ +struct iwl_tof_range_req_ap_entry_v9 { + __le32 initiator_ap_flags; + u8 channel_num; + u8 format_bw; + u8 ctrl_ch_position; + u8 ftmr_max_retries; + u8 bssid[ETH_ALEN]; + __le16 burst_period; + u8 samples_per_burst; + u8 num_of_bursts; + u8 sta_id; + u8 cipher; + u8 hltk[HLTK_11AZ_LEN]; + u8 tk[TK_11AZ_LEN]; + __le16 calib[IWL_TOF_BW_NUM]; + u16 beacon_interval; + u8 rx_pn[IEEE80211_CCMP_PN_LEN]; + u8 tx_pn[IEEE80211_CCMP_PN_LEN]; + u8 r2i_ndp_params; + u8 i2r_ndp_params; + u8 r2i_max_total_ltf; + u8 i2r_max_total_ltf; + u8 bss_color; + u8 band; + __le16 min_time_between_msr; +} __packed; /* LOCATION_RANGE_REQ_AP_ENTRY_CMD_API_S_VER_9 */ + +/** + * enum iwl_tof_response_mode + * @IWL_MVM_TOF_RESPONSE_ASAP: report each AP measurement separately as soon as + * possible (not supported for this release) + * @IWL_MVM_TOF_RESPONSE_TIMEOUT: report all AP measurements as a batch upon + * timeout expiration + * @IWL_MVM_TOF_RESPONSE_COMPLETE: report all AP measurements as a batch at the + * earlier of: measurements completion / timeout + * expiration. + */ +enum iwl_tof_response_mode { + IWL_MVM_TOF_RESPONSE_ASAP, + IWL_MVM_TOF_RESPONSE_TIMEOUT, + IWL_MVM_TOF_RESPONSE_COMPLETE, +}; + +/** + * enum iwl_tof_initiator_flags + * + * @IWL_TOF_INITIATOR_FLAGS_FAST_ALGO_DISABLED: disable fast algo, meaning run + * the algo on ant A+B, instead of only one of them. + * @IWL_TOF_INITIATOR_FLAGS_RX_CHAIN_SEL_A: open RX antenna A for FTMs RX + * @IWL_TOF_INITIATOR_FLAGS_RX_CHAIN_SEL_B: open RX antenna B for FTMs RX + * @IWL_TOF_INITIATOR_FLAGS_RX_CHAIN_SEL_C: open RX antenna C for FTMs RX + * @IWL_TOF_INITIATOR_FLAGS_TX_CHAIN_SEL_A: use antenna A fo TX ACKs during FTM + * @IWL_TOF_INITIATOR_FLAGS_TX_CHAIN_SEL_B: use antenna B fo TX ACKs during FTM + * @IWL_TOF_INITIATOR_FLAGS_TX_CHAIN_SEL_C: use antenna C fo TX ACKs during FTM + * @IWL_TOF_INITIATOR_FLAGS_MACADDR_RANDOM: use random mac address for FTM + * @IWL_TOF_INITIATOR_FLAGS_SPECIFIC_CALIB: use the specific calib value from + * the range request command + * @IWL_TOF_INITIATOR_FLAGS_COMMON_CALIB: use the common calib value from the + * ragne request command + * @IWL_TOF_INITIATOR_FLAGS_NON_ASAP_SUPPORT: support non-asap measurements + */ +enum iwl_tof_initiator_flags { + IWL_TOF_INITIATOR_FLAGS_FAST_ALGO_DISABLED = BIT(0), + IWL_TOF_INITIATOR_FLAGS_RX_CHAIN_SEL_A = BIT(1), + IWL_TOF_INITIATOR_FLAGS_RX_CHAIN_SEL_B = BIT(2), + IWL_TOF_INITIATOR_FLAGS_RX_CHAIN_SEL_C = BIT(3), + IWL_TOF_INITIATOR_FLAGS_TX_CHAIN_SEL_A = BIT(4), + IWL_TOF_INITIATOR_FLAGS_TX_CHAIN_SEL_B = BIT(5), + IWL_TOF_INITIATOR_FLAGS_TX_CHAIN_SEL_C = BIT(6), + IWL_TOF_INITIATOR_FLAGS_MACADDR_RANDOM = BIT(7), + IWL_TOF_INITIATOR_FLAGS_SPECIFIC_CALIB = BIT(15), + IWL_TOF_INITIATOR_FLAGS_COMMON_CALIB = BIT(16), + IWL_TOF_INITIATOR_FLAGS_NON_ASAP_SUPPORT = BIT(20), +}; /* LOCATION_RANGE_REQ_CMD_API_S_VER_5 */ + +#define IWL_MVM_TOF_MAX_APS 5 +#define IWL_MVM_TOF_MAX_TWO_SIDED_APS 5 + +/** + * struct iwl_tof_range_req_cmd_v5 - start measurement cmd + * @initiator_flags: see flags @ iwl_tof_initiator_flags + * @request_id: A Token incremented per request. The same Token will be + * sent back in the range response + * @initiator: 0- NW initiated, 1 - Client Initiated + * @one_sided_los_disable: '0'- run ML-Algo for both ToF/OneSided, + * '1' - run ML-Algo for ToF only + * @req_timeout: Requested timeout of the response in units of 100ms. + * This is equivalent to the session time configured to the + * LMAC in Initiator Request + * @report_policy: Supported partially for this release: For current release - + * the range report will be uploaded as a batch when ready or + * when the session is done (successfully / partially). + * one of iwl_tof_response_mode. + * @reserved0: reserved + * @num_of_ap: Number of APs to measure (error if > IWL_MVM_TOF_MAX_APS) + * @macaddr_random: '0' Use default source MAC address (i.e. p2_p), + * '1' Use MAC Address randomization according to the below + * @range_req_bssid: ranging request BSSID + * @macaddr_template: MAC address template to use for non-randomized bits + * @macaddr_mask: Bits set to 0 shall be copied from the MAC address template. + * Bits set to 1 shall be randomized by the UMAC + * @ftm_rx_chains: Rx chain to open to receive Responder's FTMs (XVT) + * @ftm_tx_chains: Tx chain to send the ack to the Responder FTM (XVT) + * @common_calib: The common calib value to inject to this measurement calc + * @specific_calib: The specific calib value to inject to this measurement calc + * @ap: per-AP request data + */ +struct iwl_tof_range_req_cmd_v5 { + __le32 initiator_flags; + u8 request_id; + u8 initiator; + u8 one_sided_los_disable; + u8 req_timeout; + u8 report_policy; + u8 reserved0; + u8 num_of_ap; + u8 macaddr_random; + u8 range_req_bssid[ETH_ALEN]; + u8 macaddr_template[ETH_ALEN]; + u8 macaddr_mask[ETH_ALEN]; + u8 ftm_rx_chains; + u8 ftm_tx_chains; + __le16 common_calib; + __le16 specific_calib; + struct iwl_tof_range_req_ap_entry_v2 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; +/* LOCATION_RANGE_REQ_CMD_API_S_VER_5 */ + +/** + * struct iwl_tof_range_req_cmd_v7 - start measurement cmd + * @initiator_flags: see flags @ iwl_tof_initiator_flags + * @request_id: A Token incremented per request. The same Token will be + * sent back in the range response + * @num_of_ap: Number of APs to measure (error if > IWL_MVM_TOF_MAX_APS) + * @range_req_bssid: ranging request BSSID + * @macaddr_mask: Bits set to 0 shall be copied from the MAC address template. + * Bits set to 1 shall be randomized by the UMAC + * @macaddr_template: MAC address template to use for non-randomized bits + * @req_timeout_ms: Requested timeout of the response in units of milliseconds. + * This is the session time for completing the measurement. + * @tsf_mac_id: report the measurement start time for each ap in terms of the + * TSF of this mac id. 0xff to disable TSF reporting. + * @common_calib: The common calib value to inject to this measurement calc + * @specific_calib: The specific calib value to inject to this measurement calc + * @ap: per-AP request data, see &struct iwl_tof_range_req_ap_entry_v2. + */ +struct iwl_tof_range_req_cmd_v7 { + __le32 initiator_flags; + u8 request_id; + u8 num_of_ap; + u8 range_req_bssid[ETH_ALEN]; + u8 macaddr_mask[ETH_ALEN]; + u8 macaddr_template[ETH_ALEN]; + __le32 req_timeout_ms; + __le32 tsf_mac_id; + __le16 common_calib; + __le16 specific_calib; + struct iwl_tof_range_req_ap_entry_v3 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_REQ_CMD_API_S_VER_7 */ + +/** + * struct iwl_tof_range_req_cmd_v8 - start measurement cmd + * @initiator_flags: see flags @ iwl_tof_initiator_flags + * @request_id: A Token incremented per request. The same Token will be + * sent back in the range response + * @num_of_ap: Number of APs to measure (error if > IWL_MVM_TOF_MAX_APS) + * @range_req_bssid: ranging request BSSID + * @macaddr_mask: Bits set to 0 shall be copied from the MAC address template. + * Bits set to 1 shall be randomized by the UMAC + * @macaddr_template: MAC address template to use for non-randomized bits + * @req_timeout_ms: Requested timeout of the response in units of milliseconds. + * This is the session time for completing the measurement. + * @tsf_mac_id: report the measurement start time for each ap in terms of the + * TSF of this mac id. 0xff to disable TSF reporting. + * @common_calib: The common calib value to inject to this measurement calc + * @specific_calib: The specific calib value to inject to this measurement calc + * @ap: per-AP request data, see &struct iwl_tof_range_req_ap_entry_v2. + */ +struct iwl_tof_range_req_cmd_v8 { + __le32 initiator_flags; + u8 request_id; + u8 num_of_ap; + u8 range_req_bssid[ETH_ALEN]; + u8 macaddr_mask[ETH_ALEN]; + u8 macaddr_template[ETH_ALEN]; + __le32 req_timeout_ms; + __le32 tsf_mac_id; + __le16 common_calib; + __le16 specific_calib; + struct iwl_tof_range_req_ap_entry_v4 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_REQ_CMD_API_S_VER_8 */ + +/** + * struct iwl_tof_range_req_cmd_v9 - start measurement cmd + * @initiator_flags: see flags @ iwl_tof_initiator_flags + * @request_id: A Token incremented per request. The same Token will be + * sent back in the range response + * @num_of_ap: Number of APs to measure (error if > IWL_MVM_TOF_MAX_APS) + * @range_req_bssid: ranging request BSSID + * @macaddr_mask: Bits set to 0 shall be copied from the MAC address template. + * Bits set to 1 shall be randomized by the UMAC + * @macaddr_template: MAC address template to use for non-randomized bits + * @req_timeout_ms: Requested timeout of the response in units of milliseconds. + * This is the session time for completing the measurement. + * @tsf_mac_id: report the measurement start time for each ap in terms of the + * TSF of this mac id. 0xff to disable TSF reporting. + * @ap: per-AP request data, see &struct iwl_tof_range_req_ap_entry_v2. + */ +struct iwl_tof_range_req_cmd_v9 { + __le32 initiator_flags; + u8 request_id; + u8 num_of_ap; + u8 range_req_bssid[ETH_ALEN]; + u8 macaddr_mask[ETH_ALEN]; + u8 macaddr_template[ETH_ALEN]; + __le32 req_timeout_ms; + __le32 tsf_mac_id; + struct iwl_tof_range_req_ap_entry_v6 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_REQ_CMD_API_S_VER_9 */ + +/** + * struct iwl_tof_range_req_cmd_v11 - start measurement cmd + * @initiator_flags: see flags @ iwl_tof_initiator_flags + * @request_id: A Token incremented per request. The same Token will be + * sent back in the range response + * @num_of_ap: Number of APs to measure (error if > IWL_MVM_TOF_MAX_APS) + * @range_req_bssid: ranging request BSSID + * @macaddr_mask: Bits set to 0 shall be copied from the MAC address template. + * Bits set to 1 shall be randomized by the UMAC + * @macaddr_template: MAC address template to use for non-randomized bits + * @req_timeout_ms: Requested timeout of the response in units of milliseconds. + * This is the session time for completing the measurement. + * @tsf_mac_id: report the measurement start time for each ap in terms of the + * TSF of this mac id. 0xff to disable TSF reporting. + * @ap: per-AP request data, see &struct iwl_tof_range_req_ap_entry_v2. + */ +struct iwl_tof_range_req_cmd_v11 { + __le32 initiator_flags; + u8 request_id; + u8 num_of_ap; + u8 range_req_bssid[ETH_ALEN]; + u8 macaddr_mask[ETH_ALEN]; + u8 macaddr_template[ETH_ALEN]; + __le32 req_timeout_ms; + __le32 tsf_mac_id; + struct iwl_tof_range_req_ap_entry_v7 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_REQ_CMD_API_S_VER_11 */ + +/** + * struct iwl_tof_range_req_cmd_v12 - start measurement cmd + * @initiator_flags: see flags @ iwl_tof_initiator_flags + * @request_id: A Token incremented per request. The same Token will be + * sent back in the range response + * @num_of_ap: Number of APs to measure (error if > IWL_MVM_TOF_MAX_APS) + * @range_req_bssid: ranging request BSSID + * @macaddr_mask: Bits set to 0 shall be copied from the MAC address template. + * Bits set to 1 shall be randomized by the UMAC + * @macaddr_template: MAC address template to use for non-randomized bits + * @req_timeout_ms: Requested timeout of the response in units of milliseconds. + * This is the session time for completing the measurement. + * @tsf_mac_id: report the measurement start time for each ap in terms of the + * TSF of this mac id. 0xff to disable TSF reporting. + * @ap: per-AP request data, see &struct iwl_tof_range_req_ap_entry_v2. + */ +struct iwl_tof_range_req_cmd_v12 { + __le32 initiator_flags; + u8 request_id; + u8 num_of_ap; + u8 range_req_bssid[ETH_ALEN]; + u8 macaddr_mask[ETH_ALEN]; + u8 macaddr_template[ETH_ALEN]; + __le32 req_timeout_ms; + __le32 tsf_mac_id; + struct iwl_tof_range_req_ap_entry_v8 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_REQ_CMD_API_S_VER_12 */ + +/** + * struct iwl_tof_range_req_cmd_v13 - start measurement cmd + * @initiator_flags: see flags @ iwl_tof_initiator_flags + * @request_id: A Token incremented per request. The same Token will be + * sent back in the range response + * @num_of_ap: Number of APs to measure (error if > IWL_MVM_TOF_MAX_APS) + * @range_req_bssid: ranging request BSSID + * @macaddr_mask: Bits set to 0 shall be copied from the MAC address template. + * Bits set to 1 shall be randomized by the UMAC + * @macaddr_template: MAC address template to use for non-randomized bits + * @req_timeout_ms: Requested timeout of the response in units of milliseconds. + * This is the session time for completing the measurement. + * @tsf_mac_id: report the measurement start time for each ap in terms of the + * TSF of this mac id. 0xff to disable TSF reporting. + * @ap: per-AP request data, see &struct iwl_tof_range_req_ap_entry_v9. + */ +struct iwl_tof_range_req_cmd_v13 { + __le32 initiator_flags; + u8 request_id; + u8 num_of_ap; + u8 range_req_bssid[ETH_ALEN]; + u8 macaddr_mask[ETH_ALEN]; + u8 macaddr_template[ETH_ALEN]; + __le32 req_timeout_ms; + __le32 tsf_mac_id; + struct iwl_tof_range_req_ap_entry_v9 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_REQ_CMD_API_S_VER_13 */ + +/* + * enum iwl_tof_range_request_status - status of the sent request + * @IWL_TOF_RANGE_REQUEST_STATUS_SUCCESSFUL - FW successfully received the + * request + * @IWL_TOF_RANGE_REQUEST_STATUS_BUSY - FW is busy with a previous request, the + * sent request will not be handled + */ +enum iwl_tof_range_request_status { + IWL_TOF_RANGE_REQUEST_STATUS_SUCCESS, + IWL_TOF_RANGE_REQUEST_STATUS_BUSY, +}; + +/** + * enum iwl_tof_entry_status + * + * @IWL_TOF_ENTRY_SUCCESS: successful measurement. + * @IWL_TOF_ENTRY_GENERAL_FAILURE: General failure. + * @IWL_TOF_ENTRY_NO_RESPONSE: Responder didn't reply to the request. + * @IWL_TOF_ENTRY_REQUEST_REJECTED: Responder rejected the request. + * @IWL_TOF_ENTRY_NOT_SCHEDULED: Time event was scheduled but not called yet. + * @IWL_TOF_ENTRY_TIMING_MEASURE_TIMEOUT: Time event triggered but no + * measurement was completed. + * @IWL_TOF_ENTRY_TARGET_DIFF_CH_CANNOT_CHANGE: No range due inability to switch + * from the primary channel. + * @IWL_TOF_ENTRY_RANGE_NOT_SUPPORTED: Device doesn't support FTM. + * @IWL_TOF_ENTRY_REQUEST_ABORT_UNKNOWN_REASON: Request aborted due to unknown + * reason. + * @IWL_TOF_ENTRY_LOCATION_INVALID_T1_T4_TIME_STAMP: Failure due to invalid + * T1/T4. + * @IWL_TOF_ENTRY_11MC_PROTOCOL_FAILURE: Failure due to invalid FTM frame + * structure. + * @IWL_TOF_ENTRY_REQUEST_CANNOT_SCHED: Request cannot be scheduled. + * @IWL_TOF_ENTRY_RESPONDER_CANNOT_COLABORATE: Responder cannot serve the + * initiator for some period, period supplied in @refusal_period. + * @IWL_TOF_ENTRY_BAD_REQUEST_ARGS: Bad request arguments. + * @IWL_TOF_ENTRY_WIFI_NOT_ENABLED: Wifi not enabled. + * @IWL_TOF_ENTRY_RESPONDER_OVERRIDE_PARAMS: Responder override the original + * parameters within the current session. + */ +enum iwl_tof_entry_status { + IWL_TOF_ENTRY_SUCCESS = 0, + IWL_TOF_ENTRY_GENERAL_FAILURE = 1, + IWL_TOF_ENTRY_NO_RESPONSE = 2, + IWL_TOF_ENTRY_REQUEST_REJECTED = 3, + IWL_TOF_ENTRY_NOT_SCHEDULED = 4, + IWL_TOF_ENTRY_TIMING_MEASURE_TIMEOUT = 5, + IWL_TOF_ENTRY_TARGET_DIFF_CH_CANNOT_CHANGE = 6, + IWL_TOF_ENTRY_RANGE_NOT_SUPPORTED = 7, + IWL_TOF_ENTRY_REQUEST_ABORT_UNKNOWN_REASON = 8, + IWL_TOF_ENTRY_LOCATION_INVALID_T1_T4_TIME_STAMP = 9, + IWL_TOF_ENTRY_11MC_PROTOCOL_FAILURE = 10, + IWL_TOF_ENTRY_REQUEST_CANNOT_SCHED = 11, + IWL_TOF_ENTRY_RESPONDER_CANNOT_COLABORATE = 12, + IWL_TOF_ENTRY_BAD_REQUEST_ARGS = 13, + IWL_TOF_ENTRY_WIFI_NOT_ENABLED = 14, + IWL_TOF_ENTRY_RESPONDER_OVERRIDE_PARAMS = 15, +}; /* LOCATION_RANGE_RSP_AP_ENTRY_NTFY_API_S_VER_2 */ + +/** + * struct iwl_tof_range_rsp_ap_entry_ntfy_v3 - AP parameters (response) + * @bssid: BSSID of the AP + * @measure_status: current APs measurement status, one of + * &enum iwl_tof_entry_status. + * @measure_bw: Current AP Bandwidth: 0 20MHz, 1 40MHz, 2 80MHz + * @rtt: The Round Trip Time that took for the last measurement for + * current AP [pSec] + * @rtt_variance: The Variance of the RTT values measured for current AP + * @rtt_spread: The Difference between the maximum and the minimum RTT + * values measured for current AP in the current session [pSec] + * @rssi: RSSI as uploaded in the Channel Estimation notification + * @rssi_spread: The Difference between the maximum and the minimum RSSI values + * measured for current AP in the current session + * @reserved: reserved + * @refusal_period: refusal period in case of + * @IWL_TOF_ENTRY_RESPONDER_CANNOT_COLABORATE [sec] + * @range: Measured range [cm] + * @range_variance: Measured range variance [cm] + * @timestamp: The GP2 Clock [usec] where Channel Estimation notification was + * uploaded by the LMAC + * @t2t3_initiator: as calculated from the algo in the initiator + * @t1t4_responder: as calculated from the algo in the responder + * @common_calib: Calib val that was used in for this AP measurement + * @specific_calib: val that was used in for this AP measurement + * @papd_calib_output: The result of the tof papd calibration that was injected + * into the algorithm. + */ +struct iwl_tof_range_rsp_ap_entry_ntfy_v3 { + u8 bssid[ETH_ALEN]; + u8 measure_status; + u8 measure_bw; + __le32 rtt; + __le32 rtt_variance; + __le32 rtt_spread; + s8 rssi; + u8 rssi_spread; + u8 reserved; + u8 refusal_period; + __le32 range; + __le32 range_variance; + __le32 timestamp; + __le32 t2t3_initiator; + __le32 t1t4_responder; + __le16 common_calib; + __le16 specific_calib; + __le32 papd_calib_output; +} __packed; /* LOCATION_RANGE_RSP_AP_ETRY_NTFY_API_S_VER_3 */ + +/** + * struct iwl_tof_range_rsp_ap_entry_ntfy_v4 - AP parameters (response) + * @bssid: BSSID of the AP + * @measure_status: current APs measurement status, one of + * &enum iwl_tof_entry_status. + * @measure_bw: Current AP Bandwidth: 0 20MHz, 1 40MHz, 2 80MHz + * @rtt: The Round Trip Time that took for the last measurement for + * current AP [pSec] + * @rtt_variance: The Variance of the RTT values measured for current AP + * @rtt_spread: The Difference between the maximum and the minimum RTT + * values measured for current AP in the current session [pSec] + * @rssi: RSSI as uploaded in the Channel Estimation notification + * @rssi_spread: The Difference between the maximum and the minimum RSSI values + * measured for current AP in the current session + * @last_burst: 1 if no more FTM sessions are scheduled for this responder + * @refusal_period: refusal period in case of + * @IWL_TOF_ENTRY_RESPONDER_CANNOT_COLABORATE [sec] + * @timestamp: The GP2 Clock [usec] where Channel Estimation notification was + * uploaded by the LMAC + * @start_tsf: measurement start time in TSF of the mac specified in the range + * request + * @rx_rate_n_flags: rate and flags of the last FTM frame received from this + * responder + * @tx_rate_n_flags: rate and flags of the last ack sent to this responder + * @t2t3_initiator: as calculated from the algo in the initiator + * @t1t4_responder: as calculated from the algo in the responder + * @common_calib: Calib val that was used in for this AP measurement + * @specific_calib: val that was used in for this AP measurement + * @papd_calib_output: The result of the tof papd calibration that was injected + * into the algorithm. + */ +struct iwl_tof_range_rsp_ap_entry_ntfy_v4 { + u8 bssid[ETH_ALEN]; + u8 measure_status; + u8 measure_bw; + __le32 rtt; + __le32 rtt_variance; + __le32 rtt_spread; + s8 rssi; + u8 rssi_spread; + u8 last_burst; + u8 refusal_period; + __le32 timestamp; + __le32 start_tsf; + __le32 rx_rate_n_flags; + __le32 tx_rate_n_flags; + __le32 t2t3_initiator; + __le32 t1t4_responder; + __le16 common_calib; + __le16 specific_calib; + __le32 papd_calib_output; +} __packed; /* LOCATION_RANGE_RSP_AP_ETRY_NTFY_API_S_VER_4 */ + +/** + * struct iwl_tof_range_rsp_ap_entry_ntfy_v5 - AP parameters (response) + * @bssid: BSSID of the AP + * @measure_status: current APs measurement status, one of + * &enum iwl_tof_entry_status. + * @measure_bw: Current AP Bandwidth: 0 20MHz, 1 40MHz, 2 80MHz + * @rtt: The Round Trip Time that took for the last measurement for + * current AP [pSec] + * @rtt_variance: The Variance of the RTT values measured for current AP + * @rtt_spread: The Difference between the maximum and the minimum RTT + * values measured for current AP in the current session [pSec] + * @rssi: RSSI as uploaded in the Channel Estimation notification + * @rssi_spread: The Difference between the maximum and the minimum RSSI values + * measured for current AP in the current session + * @last_burst: 1 if no more FTM sessions are scheduled for this responder + * @refusal_period: refusal period in case of + * @IWL_TOF_ENTRY_RESPONDER_CANNOT_COLABORATE [sec] + * @timestamp: The GP2 Clock [usec] where Channel Estimation notification was + * uploaded by the LMAC + * @start_tsf: measurement start time in TSF of the mac specified in the range + * request + * @rx_rate_n_flags: rate and flags of the last FTM frame received from this + * responder + * @tx_rate_n_flags: rate and flags of the last ack sent to this responder + * @t2t3_initiator: as calculated from the algo in the initiator + * @t1t4_responder: as calculated from the algo in the responder + * @common_calib: Calib val that was used in for this AP measurement + * @specific_calib: val that was used in for this AP measurement + * @papd_calib_output: The result of the tof papd calibration that was injected + * into the algorithm. + * @rttConfidence: a value between 0 - 31 that represents the rtt accuracy. + * @reserved: for alignment + */ +struct iwl_tof_range_rsp_ap_entry_ntfy_v5 { + u8 bssid[ETH_ALEN]; + u8 measure_status; + u8 measure_bw; + __le32 rtt; + __le32 rtt_variance; + __le32 rtt_spread; + s8 rssi; + u8 rssi_spread; + u8 last_burst; + u8 refusal_period; + __le32 timestamp; + __le32 start_tsf; + __le32 rx_rate_n_flags; + __le32 tx_rate_n_flags; + __le32 t2t3_initiator; + __le32 t1t4_responder; + __le16 common_calib; + __le16 specific_calib; + __le32 papd_calib_output; + u8 rttConfidence; + u8 reserved[3]; +} __packed; /* LOCATION_RANGE_RSP_AP_ETRY_NTFY_API_S_VER_5 */ + +/** + * struct iwl_tof_range_rsp_ap_entry_ntfy_v6 - AP parameters (response) + * @bssid: BSSID of the AP + * @measure_status: current APs measurement status, one of + * &enum iwl_tof_entry_status. + * @measure_bw: Current AP Bandwidth: 0 20MHz, 1 40MHz, 2 80MHz + * @rtt: The Round Trip Time that took for the last measurement for + * current AP [pSec] + * @rtt_variance: The Variance of the RTT values measured for current AP + * @rtt_spread: The Difference between the maximum and the minimum RTT + * values measured for current AP in the current session [pSec] + * @rssi: RSSI as uploaded in the Channel Estimation notification + * @rssi_spread: The Difference between the maximum and the minimum RSSI values + * measured for current AP in the current session + * @last_burst: 1 if no more FTM sessions are scheduled for this responder + * @refusal_period: refusal period in case of + * @IWL_TOF_ENTRY_RESPONDER_CANNOT_COLABORATE [sec] + * @timestamp: The GP2 Clock [usec] where Channel Estimation notification was + * uploaded by the LMAC + * @start_tsf: measurement start time in TSF of the mac specified in the range + * request + * @rx_rate_n_flags: rate and flags of the last FTM frame received from this + * responder + * @tx_rate_n_flags: rate and flags of the last ack sent to this responder + * @t2t3_initiator: as calculated from the algo in the initiator + * @t1t4_responder: as calculated from the algo in the responder + * @common_calib: Calib val that was used in for this AP measurement + * @specific_calib: val that was used in for this AP measurement + * @papd_calib_output: The result of the tof papd calibration that was injected + * into the algorithm. + * @rttConfidence: a value between 0 - 31 that represents the rtt accuracy. + * @reserved: for alignment + * @rx_pn: the last PN used for this responder Rx in case PMF is configured in + * LE byte order. + * @tx_pn: the last PN used for this responder Tx in case PMF is configured in + * LE byte order. + */ +struct iwl_tof_range_rsp_ap_entry_ntfy_v6 { + u8 bssid[ETH_ALEN]; + u8 measure_status; + u8 measure_bw; + __le32 rtt; + __le32 rtt_variance; + __le32 rtt_spread; + s8 rssi; + u8 rssi_spread; + u8 last_burst; + u8 refusal_period; + __le32 timestamp; + __le32 start_tsf; + __le32 rx_rate_n_flags; + __le32 tx_rate_n_flags; + __le32 t2t3_initiator; + __le32 t1t4_responder; + __le16 common_calib; + __le16 specific_calib; + __le32 papd_calib_output; + u8 rttConfidence; + u8 reserved[3]; + u8 rx_pn[IEEE80211_CCMP_PN_LEN]; + u8 tx_pn[IEEE80211_CCMP_PN_LEN]; +} __packed; /* LOCATION_RANGE_RSP_AP_ETRY_NTFY_API_S_VER_6, + LOCATION_RANGE_RSP_AP_ETRY_NTFY_API_S_VER_7 */ + + +/** + * enum iwl_tof_response_status - tof response status + * + * @IWL_TOF_RESPONSE_SUCCESS: successful range. + * @IWL_TOF_RESPONSE_TIMEOUT: request aborted due to timeout expiration. + * partial result of ranges done so far is included in the response. + * @IWL_TOF_RESPONSE_ABORTED: Measurement aborted by command. + * @IWL_TOF_RESPONSE_FAILED: Measurement request command failed. + */ +enum iwl_tof_response_status { + IWL_TOF_RESPONSE_SUCCESS = 0, + IWL_TOF_RESPONSE_TIMEOUT = 1, + IWL_TOF_RESPONSE_ABORTED = 4, + IWL_TOF_RESPONSE_FAILED = 5, +}; /* LOCATION_RNG_RSP_STATUS */ + +/** + * struct iwl_tof_range_rsp_ntfy_v5 - ranging response notification + * @request_id: A Token ID of the corresponding Range request + * @request_status: status of current measurement session, one of + * &enum iwl_tof_response_status. + * @last_in_batch: reprot policy (when not all responses are uploaded at once) + * @num_of_aps: Number of APs to measure (error if > IWL_MVM_TOF_MAX_APS) + * @ap: per-AP data + */ +struct iwl_tof_range_rsp_ntfy_v5 { + u8 request_id; + u8 request_status; + u8 last_in_batch; + u8 num_of_aps; + struct iwl_tof_range_rsp_ap_entry_ntfy_v3 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_RSP_NTFY_API_S_VER_5 */ + +/** + * struct iwl_tof_range_rsp_ntfy_v6 - ranging response notification + * @request_id: A Token ID of the corresponding Range request + * @num_of_aps: Number of APs results + * @last_report: 1 if no more FTM sessions are scheduled, 0 otherwise. + * @reserved: reserved + * @ap: per-AP data + */ +struct iwl_tof_range_rsp_ntfy_v6 { + u8 request_id; + u8 num_of_aps; + u8 last_report; + u8 reserved; + struct iwl_tof_range_rsp_ap_entry_ntfy_v4 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_RSP_NTFY_API_S_VER_6 */ + +/** + * struct iwl_tof_range_rsp_ntfy_v7 - ranging response notification + * @request_id: A Token ID of the corresponding Range request + * @num_of_aps: Number of APs results + * @last_report: 1 if no more FTM sessions are scheduled, 0 otherwise. + * @reserved: reserved + * @ap: per-AP data + */ +struct iwl_tof_range_rsp_ntfy_v7 { + u8 request_id; + u8 num_of_aps; + u8 last_report; + u8 reserved; + struct iwl_tof_range_rsp_ap_entry_ntfy_v5 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_RSP_NTFY_API_S_VER_7 */ + +/** + * struct iwl_tof_range_rsp_ntfy_v8 - ranging response notification + * @request_id: A Token ID of the corresponding Range request + * @num_of_aps: Number of APs results + * @last_report: 1 if no more FTM sessions are scheduled, 0 otherwise. + * @reserved: reserved + * @ap: per-AP data + */ +struct iwl_tof_range_rsp_ntfy_v8 { + u8 request_id; + u8 num_of_aps; + u8 last_report; + u8 reserved; + struct iwl_tof_range_rsp_ap_entry_ntfy_v6 ap[IWL_MVM_TOF_MAX_APS]; +} __packed; /* LOCATION_RANGE_RSP_NTFY_API_S_VER_8, + LOCATION_RANGE_RSP_NTFY_API_S_VER_9 */ + +#define IWL_MVM_TOF_MCSI_BUF_SIZE (245) +/** + * struct iwl_tof_mcsi_notif - used for debug + * @token: token ID for the current session + * @role: '0' - initiator, '1' - responder + * @reserved: reserved + * @initiator_bssid: initiator machine + * @responder_bssid: responder machine + * @mcsi_buffer: debug data + */ +struct iwl_tof_mcsi_notif { + u8 token; + u8 role; + __le16 reserved; + u8 initiator_bssid[ETH_ALEN]; + u8 responder_bssid[ETH_ALEN]; + u8 mcsi_buffer[IWL_MVM_TOF_MCSI_BUF_SIZE * 4]; +} __packed; + +/** + * struct iwl_tof_range_abort_cmd + * @request_id: corresponds to a range request + * @reserved: reserved + */ +struct iwl_tof_range_abort_cmd { + u8 request_id; + u8 reserved[3]; +} __packed; + +enum ftm_responder_stats_flags { + FTM_RESP_STAT_NON_ASAP_STARTED = BIT(0), + FTM_RESP_STAT_NON_ASAP_IN_WIN = BIT(1), + FTM_RESP_STAT_NON_ASAP_OUT_WIN = BIT(2), + FTM_RESP_STAT_TRIGGER_DUP = BIT(3), + FTM_RESP_STAT_DUP = BIT(4), + FTM_RESP_STAT_DUP_IN_WIN = BIT(5), + FTM_RESP_STAT_DUP_OUT_WIN = BIT(6), + FTM_RESP_STAT_SCHED_SUCCESS = BIT(7), + FTM_RESP_STAT_ASAP_REQ = BIT(8), + FTM_RESP_STAT_NON_ASAP_REQ = BIT(9), + FTM_RESP_STAT_ASAP_RESP = BIT(10), + FTM_RESP_STAT_NON_ASAP_RESP = BIT(11), + FTM_RESP_STAT_FAIL_INITIATOR_INACTIVE = BIT(12), + FTM_RESP_STAT_FAIL_INITIATOR_OUT_WIN = BIT(13), + FTM_RESP_STAT_FAIL_INITIATOR_RETRY_LIM = BIT(14), + FTM_RESP_STAT_FAIL_NEXT_SERVED = BIT(15), + FTM_RESP_STAT_FAIL_TRIGGER_ERR = BIT(16), + FTM_RESP_STAT_FAIL_GC = BIT(17), + FTM_RESP_STAT_SUCCESS = BIT(18), + FTM_RESP_STAT_INTEL_IE = BIT(19), + FTM_RESP_STAT_INITIATOR_ACTIVE = BIT(20), + FTM_RESP_STAT_MEASUREMENTS_AVAILABLE = BIT(21), + FTM_RESP_STAT_TRIGGER_UNKNOWN = BIT(22), + FTM_RESP_STAT_PROCESS_FAIL = BIT(23), + FTM_RESP_STAT_ACK = BIT(24), + FTM_RESP_STAT_NACK = BIT(25), + FTM_RESP_STAT_INVALID_INITIATOR_ID = BIT(26), + FTM_RESP_STAT_TIMER_MIN_DELTA = BIT(27), + FTM_RESP_STAT_INITIATOR_REMOVED = BIT(28), + FTM_RESP_STAT_INITIATOR_ADDED = BIT(29), + FTM_RESP_STAT_ERR_LIST_FULL = BIT(30), + FTM_RESP_STAT_INITIATOR_SCHED_NOW = BIT(31), +}; /* RESP_IND_E */ + +/** + * struct iwl_ftm_responder_stats - FTM responder statistics + * @addr: initiator address + * @success_ftm: number of successful ftm frames + * @ftm_per_burst: num of FTM frames that were received + * @flags: &enum ftm_responder_stats_flags + * @duration: actual duration of FTM + * @allocated_duration: time that was allocated for this FTM session + * @bw: FTM request bandwidth + * @rate: FTM request rate + * @reserved: for alingment and future use + */ +struct iwl_ftm_responder_stats { + u8 addr[ETH_ALEN]; + u8 success_ftm; + u8 ftm_per_burst; + __le32 flags; + __le32 duration; + __le32 allocated_duration; + u8 bw; + u8 rate; + __le16 reserved; +} __packed; /* TOF_RESPONDER_STATISTICS_NTFY_S_VER_2 */ + +#define IWL_CSI_MAX_EXPECTED_CHUNKS 16 + +#define IWL_CSI_CHUNK_CTL_NUM_MASK_VER_1 0x0003 +#define IWL_CSI_CHUNK_CTL_IDX_MASK_VER_1 0x000c + +#define IWL_CSI_CHUNK_CTL_NUM_MASK_VER_2 0x00ff +#define IWL_CSI_CHUNK_CTL_IDX_MASK_VER_2 0xff00 + +struct iwl_csi_chunk_notification { + __le32 token; + __le16 seq; + __le16 ctl; + __le32 size; + u8 data[]; +} __packed; /* CSI_CHUNKS_HDR_NTFY_API_S_VER_1/VER_2 */ + +#endif /* __iwl_fw_api_location_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h new file mode 100644 index 000000000..712532f17 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2019, 2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_mac_cfg_h__ +#define __iwl_fw_api_mac_cfg_h__ + +/** + * enum iwl_mac_conf_subcmd_ids - mac configuration command IDs + */ +enum iwl_mac_conf_subcmd_ids { + /** + * @LOW_LATENCY_CMD: &struct iwl_mac_low_latency_cmd + */ + LOW_LATENCY_CMD = 0x3, + /** + * @CHANNEL_SWITCH_TIME_EVENT_CMD: &struct iwl_chan_switch_te_cmd + */ + CHANNEL_SWITCH_TIME_EVENT_CMD = 0x4, + /** + * @MISSED_VAP_NOTIF: &struct iwl_missed_vap_notif + */ + MISSED_VAP_NOTIF = 0xFA, + /** + * @SESSION_PROTECTION_CMD: &struct iwl_mvm_session_prot_cmd + */ + SESSION_PROTECTION_CMD = 0x5, + /** + * @CANCEL_CHANNEL_SWITCH_CMD: &struct iwl_cancel_channel_switch_cmd + */ + CANCEL_CHANNEL_SWITCH_CMD = 0x6, + + /** + * @SESSION_PROTECTION_NOTIF: &struct iwl_mvm_session_prot_notif + */ + SESSION_PROTECTION_NOTIF = 0xFB, + + /** + * @PROBE_RESPONSE_DATA_NOTIF: &struct iwl_probe_resp_data_notif + */ + PROBE_RESPONSE_DATA_NOTIF = 0xFC, + + /** + * @CHANNEL_SWITCH_START_NOTIF: &struct iwl_channel_switch_start_notif + */ + CHANNEL_SWITCH_START_NOTIF = 0xFF, + + /** + *@CHANNEL_SWITCH_ERROR_NOTIF: &struct iwl_channel_switch_error_notif + */ + CHANNEL_SWITCH_ERROR_NOTIF = 0xF9, +}; + +#define IWL_P2P_NOA_DESC_COUNT (2) + +/** + * struct iwl_p2p_noa_attr - NOA attr contained in probe resp FW notification + * + * @id: attribute id + * @len_low: length low half + * @len_high: length high half + * @idx: instance of NoA timing + * @ctwin: GO's ct window and pwer save capability + * @desc: NoA descriptor + * @reserved: reserved for alignment purposes + */ +struct iwl_p2p_noa_attr { + u8 id; + u8 len_low; + u8 len_high; + u8 idx; + u8 ctwin; + struct ieee80211_p2p_noa_desc desc[IWL_P2P_NOA_DESC_COUNT]; + u8 reserved; +} __packed; + +#define IWL_PROBE_RESP_DATA_NO_CSA (0xff) + +/** + * struct iwl_probe_resp_data_notif - notification with NOA and CSA counter + * + * @mac_id: the mac which should send the probe response + * @noa_active: notifies if the noa attribute should be handled + * @noa_attr: P2P NOA attribute + * @csa_counter: current csa counter + * @reserved: reserved for alignment purposes + */ +struct iwl_probe_resp_data_notif { + __le32 mac_id; + __le32 noa_active; + struct iwl_p2p_noa_attr noa_attr; + u8 csa_counter; + u8 reserved[3]; +} __packed; /* PROBE_RESPONSE_DATA_NTFY_API_S_VER_1 */ + +/** + * struct iwl_missed_vap_notif - notification of missing vap detection + * + * @mac_id: the mac for which the ucode sends the notification for + * @num_beacon_intervals_elapsed: beacons elpased with no vap profile inside + * @profile_periodicity: beacons period to have our profile inside + * @reserved: reserved for alignment purposes + */ +struct iwl_missed_vap_notif { + __le32 mac_id; + u8 num_beacon_intervals_elapsed; + u8 profile_periodicity; + u8 reserved[2]; +} __packed; /* MISSED_VAP_NTFY_API_S_VER_1 */ + +/** + * struct iwl_channel_switch_start_notif - Channel switch start notification + * + * @id_and_color: ID and color of the MAC + */ +struct iwl_channel_switch_start_notif { + __le32 id_and_color; +} __packed; /* CHANNEL_SWITCH_START_NTFY_API_S_VER_1 */ + +#define CS_ERR_COUNT_ERROR BIT(0) +#define CS_ERR_LONG_DELAY_AFTER_CS BIT(1) +#define CS_ERR_LONG_TX_BLOCK BIT(2) +#define CS_ERR_TX_BLOCK_TIMER_EXPIRED BIT(3) + +/** + * struct iwl_channel_switch_error_notif - Channel switch error notification + * + * @mac_id: the mac for which the ucode sends the notification for + * @csa_err_mask: mask of channel switch error that can occur + */ +struct iwl_channel_switch_error_notif { + __le32 mac_id; + __le32 csa_err_mask; +} __packed; /* CHANNEL_SWITCH_ERROR_NTFY_API_S_VER_1 */ + +/** + * struct iwl_cancel_channel_switch_cmd - Cancel Channel Switch command + * + * @mac_id: the mac that should cancel the channel switch + */ +struct iwl_cancel_channel_switch_cmd { + __le32 mac_id; +} __packed; /* MAC_CANCEL_CHANNEL_SWITCH_S_VER_1 */ + +/** + * struct iwl_chan_switch_te_cmd - Channel Switch Time Event command + * + * @mac_id: MAC ID for channel switch + * @action: action to perform, one of FW_CTXT_ACTION_* + * @tsf: beacon tsf + * @cs_count: channel switch count from CSA/eCSA IE + * @cs_delayed_bcn_count: if set to N (!= 0) GO/AP can delay N beacon intervals + * at the new channel after the channel switch, otherwise (N == 0) expect + * beacon right after the channel switch. + * @cs_mode: 1 - quiet, 0 - otherwise + * @reserved: reserved for alignment purposes + */ +struct iwl_chan_switch_te_cmd { + __le32 mac_id; + __le32 action; + __le32 tsf; + u8 cs_count; + u8 cs_delayed_bcn_count; + u8 cs_mode; + u8 reserved; +} __packed; /* MAC_CHANNEL_SWITCH_TIME_EVENT_S_VER_2 */ + +/** + * struct iwl_mac_low_latency_cmd - set/clear mac to 'low-latency mode' + * + * @mac_id: MAC ID to whom to apply the low-latency configurations + * @low_latency_rx: 1/0 to set/clear Rx low latency direction + * @low_latency_tx: 1/0 to set/clear Tx low latency direction + * @reserved: reserved for alignment purposes + */ +struct iwl_mac_low_latency_cmd { + __le32 mac_id; + u8 low_latency_rx; + u8 low_latency_tx; + __le16 reserved; +} __packed; /* MAC_LOW_LATENCY_API_S_VER_1 */ + +#endif /* __iwl_fw_api_mac_cfg_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/mac.h b/drivers/net/wireless/intel/iwlwifi/fw/api/mac.h new file mode 100644 index 000000000..9b7caf968 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/mac.h @@ -0,0 +1,768 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_mac_h__ +#define __iwl_fw_api_mac_h__ + +/* + * The first MAC indices (starting from 0) are available to the driver, + * AUX indices follows - 1 for non-CDB, 2 for CDB. + */ +#define MAC_INDEX_AUX 4 +#define MAC_INDEX_MIN_DRIVER 0 +#define NUM_MAC_INDEX_DRIVER MAC_INDEX_AUX +#define NUM_MAC_INDEX (NUM_MAC_INDEX_DRIVER + 1) +#define NUM_MAC_INDEX_CDB (NUM_MAC_INDEX_DRIVER + 2) + +#define IWL_MVM_STATION_COUNT_MAX 16 +#define IWL_MVM_INVALID_STA 0xFF + +enum iwl_ac { + AC_BK, + AC_BE, + AC_VI, + AC_VO, + AC_NUM, +}; + +/** + * enum iwl_mac_protection_flags - MAC context flags + * @MAC_PROT_FLG_TGG_PROTECT: 11g protection when transmitting OFDM frames, + * this will require CCK RTS/CTS2self. + * RTS/CTS will protect full burst time. + * @MAC_PROT_FLG_HT_PROT: enable HT protection + * @MAC_PROT_FLG_FAT_PROT: protect 40 MHz transmissions + * @MAC_PROT_FLG_SELF_CTS_EN: allow CTS2self + */ +enum iwl_mac_protection_flags { + MAC_PROT_FLG_TGG_PROTECT = BIT(3), + MAC_PROT_FLG_HT_PROT = BIT(23), + MAC_PROT_FLG_FAT_PROT = BIT(24), + MAC_PROT_FLG_SELF_CTS_EN = BIT(30), +}; + +#define MAC_FLG_SHORT_SLOT BIT(4) +#define MAC_FLG_SHORT_PREAMBLE BIT(5) + +/** + * enum iwl_mac_types - Supported MAC types + * @FW_MAC_TYPE_FIRST: lowest supported MAC type + * @FW_MAC_TYPE_AUX: Auxiliary MAC (internal) + * @FW_MAC_TYPE_LISTENER: monitor MAC type (?) + * @FW_MAC_TYPE_PIBSS: Pseudo-IBSS + * @FW_MAC_TYPE_IBSS: IBSS + * @FW_MAC_TYPE_BSS_STA: BSS (managed) station + * @FW_MAC_TYPE_P2P_DEVICE: P2P Device + * @FW_MAC_TYPE_P2P_STA: P2P client + * @FW_MAC_TYPE_GO: P2P GO + * @FW_MAC_TYPE_TEST: ? + * @FW_MAC_TYPE_MAX: highest support MAC type + */ +enum iwl_mac_types { + FW_MAC_TYPE_FIRST = 1, + FW_MAC_TYPE_AUX = FW_MAC_TYPE_FIRST, + FW_MAC_TYPE_LISTENER, + FW_MAC_TYPE_PIBSS, + FW_MAC_TYPE_IBSS, + FW_MAC_TYPE_BSS_STA, + FW_MAC_TYPE_P2P_DEVICE, + FW_MAC_TYPE_P2P_STA, + FW_MAC_TYPE_GO, + FW_MAC_TYPE_TEST, + FW_MAC_TYPE_MAX = FW_MAC_TYPE_TEST +}; /* MAC_CONTEXT_TYPE_API_E_VER_1 */ + +/** + * enum iwl_tsf_id - TSF hw timer ID + * @TSF_ID_A: use TSF A + * @TSF_ID_B: use TSF B + * @TSF_ID_C: use TSF C + * @TSF_ID_D: use TSF D + * @NUM_TSF_IDS: number of TSF timers available + */ +enum iwl_tsf_id { + TSF_ID_A = 0, + TSF_ID_B = 1, + TSF_ID_C = 2, + TSF_ID_D = 3, + NUM_TSF_IDS = 4, +}; /* TSF_ID_API_E_VER_1 */ + +/** + * struct iwl_mac_data_ap - configuration data for AP MAC context + * @beacon_time: beacon transmit time in system time + * @beacon_tsf: beacon transmit time in TSF + * @bi: beacon interval in TU + * @reserved1: reserved + * @dtim_interval: dtim transmit time in TU + * @reserved2: reserved + * @mcast_qid: queue ID for multicast traffic. + * NOTE: obsolete from VER2 and on + * @beacon_template: beacon template ID + */ +struct iwl_mac_data_ap { + __le32 beacon_time; + __le64 beacon_tsf; + __le32 bi; + __le32 reserved1; + __le32 dtim_interval; + __le32 reserved2; + __le32 mcast_qid; + __le32 beacon_template; +} __packed; /* AP_MAC_DATA_API_S_VER_2 */ + +/** + * struct iwl_mac_data_ibss - configuration data for IBSS MAC context + * @beacon_time: beacon transmit time in system time + * @beacon_tsf: beacon transmit time in TSF + * @bi: beacon interval in TU + * @reserved: reserved + * @beacon_template: beacon template ID + */ +struct iwl_mac_data_ibss { + __le32 beacon_time; + __le64 beacon_tsf; + __le32 bi; + __le32 reserved; + __le32 beacon_template; +} __packed; /* IBSS_MAC_DATA_API_S_VER_1 */ + +/** + * enum iwl_mac_data_policy - policy of the data path for this MAC + * @TWT_SUPPORTED: twt is supported + * @MORE_DATA_ACK_SUPPORTED: AP supports More Data Ack according to + * paragraph 9.4.1.17 in P802.11ax_D4 specification. Used for TWT + * early termination detection. + * @FLEXIBLE_TWT_SUPPORTED: AP supports flexible TWT schedule + * @PROTECTED_TWT_SUPPORTED: AP supports protected TWT frames (with 11w) + * @BROADCAST_TWT_SUPPORTED: AP and STA support broadcast TWT + * @COEX_HIGH_PRIORITY_ENABLE: high priority mode for BT coex, to be used + * during 802.1X negotiation (and allowed during 4-way-HS) + */ +enum iwl_mac_data_policy { + TWT_SUPPORTED = BIT(0), + MORE_DATA_ACK_SUPPORTED = BIT(1), + FLEXIBLE_TWT_SUPPORTED = BIT(2), + PROTECTED_TWT_SUPPORTED = BIT(3), + BROADCAST_TWT_SUPPORTED = BIT(4), + COEX_HIGH_PRIORITY_ENABLE = BIT(5), +}; + +/** + * struct iwl_mac_data_sta - configuration data for station MAC context + * @is_assoc: 1 for associated state, 0 otherwise + * @dtim_time: DTIM arrival time in system time + * @dtim_tsf: DTIM arrival time in TSF + * @bi: beacon interval in TU, applicable only when associated + * @reserved1: reserved + * @dtim_interval: DTIM interval in TU, applicable only when associated + * @data_policy: see &enum iwl_mac_data_policy + * @listen_interval: in beacon intervals, applicable only when associated + * @assoc_id: unique ID assigned by the AP during association + * @assoc_beacon_arrive_time: TSF of first beacon after association + */ +struct iwl_mac_data_sta { + __le32 is_assoc; + __le32 dtim_time; + __le64 dtim_tsf; + __le32 bi; + __le32 reserved1; + __le32 dtim_interval; + __le32 data_policy; + __le32 listen_interval; + __le32 assoc_id; + __le32 assoc_beacon_arrive_time; +} __packed; /* STA_MAC_DATA_API_S_VER_2 */ + +/** + * struct iwl_mac_data_go - configuration data for P2P GO MAC context + * @ap: iwl_mac_data_ap struct with most config data + * @ctwin: client traffic window in TU (period after TBTT when GO is present). + * 0 indicates that there is no CT window. + * @opp_ps_enabled: indicate that opportunistic PS allowed + */ +struct iwl_mac_data_go { + struct iwl_mac_data_ap ap; + __le32 ctwin; + __le32 opp_ps_enabled; +} __packed; /* GO_MAC_DATA_API_S_VER_1 */ + +/** + * struct iwl_mac_data_p2p_sta - configuration data for P2P client MAC context + * @sta: iwl_mac_data_sta struct with most config data + * @ctwin: client traffic window in TU (period after TBTT when GO is present). + * 0 indicates that there is no CT window. + */ +struct iwl_mac_data_p2p_sta { + struct iwl_mac_data_sta sta; + __le32 ctwin; +} __packed; /* P2P_STA_MAC_DATA_API_S_VER_2 */ + +/** + * struct iwl_mac_data_pibss - Pseudo IBSS config data + * @stats_interval: interval in TU between statistics notifications to host. + */ +struct iwl_mac_data_pibss { + __le32 stats_interval; +} __packed; /* PIBSS_MAC_DATA_API_S_VER_1 */ + +/* + * struct iwl_mac_data_p2p_dev - configuration data for the P2P Device MAC + * context. + * @is_disc_extended: if set to true, P2P Device discoverability is enabled on + * other channels as well. This should be to true only in case that the + * device is discoverable and there is an active GO. Note that setting this + * field when not needed, will increase the number of interrupts and have + * effect on the platform power, as this setting opens the Rx filters on + * all macs. + */ +struct iwl_mac_data_p2p_dev { + __le32 is_disc_extended; +} __packed; /* _P2P_DEV_MAC_DATA_API_S_VER_1 */ + +/** + * enum iwl_mac_filter_flags - MAC context filter flags + * @MAC_FILTER_IN_PROMISC: accept all data frames + * @MAC_FILTER_IN_CONTROL_AND_MGMT: pass all management and + * control frames to the host + * @MAC_FILTER_ACCEPT_GRP: accept multicast frames + * @MAC_FILTER_DIS_DECRYPT: don't decrypt unicast frames + * @MAC_FILTER_DIS_GRP_DECRYPT: don't decrypt multicast frames + * @MAC_FILTER_IN_BEACON: transfer foreign BSS's beacons to host + * (in station mode when associated) + * @MAC_FILTER_OUT_BCAST: filter out all broadcast frames + * @MAC_FILTER_IN_CRC32: extract FCS and append it to frames + * @MAC_FILTER_IN_PROBE_REQUEST: pass probe requests to host + */ +enum iwl_mac_filter_flags { + MAC_FILTER_IN_PROMISC = BIT(0), + MAC_FILTER_IN_CONTROL_AND_MGMT = BIT(1), + MAC_FILTER_ACCEPT_GRP = BIT(2), + MAC_FILTER_DIS_DECRYPT = BIT(3), + MAC_FILTER_DIS_GRP_DECRYPT = BIT(4), + MAC_FILTER_IN_BEACON = BIT(6), + MAC_FILTER_OUT_BCAST = BIT(8), + MAC_FILTER_IN_CRC32 = BIT(11), + MAC_FILTER_IN_PROBE_REQUEST = BIT(12), + /** + * @MAC_FILTER_IN_11AX: mark BSS as supporting 802.11ax + */ + MAC_FILTER_IN_11AX = BIT(14), +}; + +/** + * enum iwl_mac_qos_flags - QoS flags + * @MAC_QOS_FLG_UPDATE_EDCA: ? + * @MAC_QOS_FLG_TGN: HT is enabled + * @MAC_QOS_FLG_TXOP_TYPE: ? + * + */ +enum iwl_mac_qos_flags { + MAC_QOS_FLG_UPDATE_EDCA = BIT(0), + MAC_QOS_FLG_TGN = BIT(1), + MAC_QOS_FLG_TXOP_TYPE = BIT(4), +}; + +/** + * struct iwl_ac_qos - QOS timing params for MAC_CONTEXT_CMD + * @cw_min: Contention window, start value in numbers of slots. + * Should be a power-of-2, minus 1. Device's default is 0x0f. + * @cw_max: Contention window, max value in numbers of slots. + * Should be a power-of-2, minus 1. Device's default is 0x3f. + * @aifsn: Number of slots in Arbitration Interframe Space (before + * performing random backoff timing prior to Tx). Device default 1. + * @fifos_mask: FIFOs used by this MAC for this AC + * @edca_txop: Length of Tx opportunity, in uSecs. Device default is 0. + * + * One instance of this config struct for each of 4 EDCA access categories + * in struct iwl_qosparam_cmd. + * + * Device will automatically increase contention window by (2*CW) + 1 for each + * transmission retry. Device uses cw_max as a bit mask, ANDed with new CW + * value, to cap the CW value. + */ +struct iwl_ac_qos { + __le16 cw_min; + __le16 cw_max; + u8 aifsn; + u8 fifos_mask; + __le16 edca_txop; +} __packed; /* AC_QOS_API_S_VER_2 */ + +/** + * struct iwl_mac_ctx_cmd - command structure to configure MAC contexts + * ( MAC_CONTEXT_CMD = 0x28 ) + * @id_and_color: ID and color of the MAC + * @action: action to perform, one of FW_CTXT_ACTION_* + * @mac_type: one of &enum iwl_mac_types + * @tsf_id: TSF HW timer, one of &enum iwl_tsf_id + * @node_addr: MAC address + * @reserved_for_node_addr: reserved + * @bssid_addr: BSSID + * @reserved_for_bssid_addr: reserved + * @cck_rates: basic rates available for CCK + * @ofdm_rates: basic rates available for OFDM + * @protection_flags: combination of &enum iwl_mac_protection_flags + * @cck_short_preamble: 0x20 for enabling short preamble, 0 otherwise + * @short_slot: 0x10 for enabling short slots, 0 otherwise + * @filter_flags: combination of &enum iwl_mac_filter_flags + * @qos_flags: from &enum iwl_mac_qos_flags + * @ac: one iwl_mac_qos configuration for each AC + */ +struct iwl_mac_ctx_cmd { + /* COMMON_INDEX_HDR_API_S_VER_1 */ + __le32 id_and_color; + __le32 action; + /* MAC_CONTEXT_COMMON_DATA_API_S_VER_1 */ + __le32 mac_type; + __le32 tsf_id; + u8 node_addr[6]; + __le16 reserved_for_node_addr; + u8 bssid_addr[6]; + __le16 reserved_for_bssid_addr; + __le32 cck_rates; + __le32 ofdm_rates; + __le32 protection_flags; + __le32 cck_short_preamble; + __le32 short_slot; + __le32 filter_flags; + /* MAC_QOS_PARAM_API_S_VER_1 */ + __le32 qos_flags; + struct iwl_ac_qos ac[AC_NUM+1]; + /* MAC_CONTEXT_COMMON_DATA_API_S */ + union { + struct iwl_mac_data_ap ap; + struct iwl_mac_data_go go; + struct iwl_mac_data_sta sta; + struct iwl_mac_data_p2p_sta p2p_sta; + struct iwl_mac_data_p2p_dev p2p_dev; + struct iwl_mac_data_pibss pibss; + struct iwl_mac_data_ibss ibss; + }; +} __packed; /* MAC_CONTEXT_CMD_API_S_VER_1 */ + +#define IWL_NONQOS_SEQ_GET 0x1 +#define IWL_NONQOS_SEQ_SET 0x2 +struct iwl_nonqos_seq_query_cmd { + __le32 get_set_flag; + __le32 mac_id_n_color; + __le16 value; + __le16 reserved; +} __packed; /* NON_QOS_TX_COUNTER_GET_SET_API_S_VER_1 */ + +/** + * struct iwl_missed_beacons_notif - information on missed beacons + * ( MISSED_BEACONS_NOTIFICATION = 0xa2 ) + * @mac_id: interface ID + * @consec_missed_beacons_since_last_rx: number of consecutive missed + * beacons since last RX. + * @consec_missed_beacons: number of consecutive missed beacons + * @num_expected_beacons: number of expected beacons + * @num_recvd_beacons: number of received beacons + */ +struct iwl_missed_beacons_notif { + __le32 mac_id; + __le32 consec_missed_beacons_since_last_rx; + __le32 consec_missed_beacons; + __le32 num_expected_beacons; + __le32 num_recvd_beacons; +} __packed; /* MISSED_BEACON_NTFY_API_S_VER_3 */ + +/** + * struct iwl_he_backoff_conf - used for backoff configuration + * Per each trigger-based AC, (set by MU EDCA Parameter set info-element) + * used for backoff configuration of TXF5..TXF8 trigger based. + * The MU-TIMER is reloaded w/ MU_TIME each time a frame from the AC is sent via + * trigger-based TX. + * @cwmin: CW min + * @cwmax: CW max + * @aifsn: AIFSN + * AIFSN=0, means that no backoff from the specified TRIG-BASED AC is + * allowed till the MU-TIMER is 0 + * @mu_time: MU time in 8TU units + */ +struct iwl_he_backoff_conf { + __le16 cwmin; + __le16 cwmax; + __le16 aifsn; + __le16 mu_time; +} __packed; /* AC_QOS_DOT11AX_API_S */ + +/** + * enum iwl_he_pkt_ext_constellations - PPE constellation indices + * @IWL_HE_PKT_EXT_BPSK: BPSK + * @IWL_HE_PKT_EXT_QPSK: QPSK + * @IWL_HE_PKT_EXT_16QAM: 16-QAM + * @IWL_HE_PKT_EXT_64QAM: 64-QAM + * @IWL_HE_PKT_EXT_256QAM: 256-QAM + * @IWL_HE_PKT_EXT_1024QAM: 1024-QAM + * @IWL_HE_PKT_EXT_RESERVED: reserved value + * @IWL_HE_PKT_EXT_NONE: not defined + */ +enum iwl_he_pkt_ext_constellations { + IWL_HE_PKT_EXT_BPSK = 0, + IWL_HE_PKT_EXT_QPSK, + IWL_HE_PKT_EXT_16QAM, + IWL_HE_PKT_EXT_64QAM, + IWL_HE_PKT_EXT_256QAM, + IWL_HE_PKT_EXT_1024QAM, + IWL_HE_PKT_EXT_RESERVED, + IWL_HE_PKT_EXT_NONE, +}; + +#define MAX_HE_SUPP_NSS 2 +#define MAX_CHANNEL_BW_INDX_API_D_VER_2 4 +#define MAX_CHANNEL_BW_INDX_API_D_VER_3 5 + +/** + * struct iwl_he_pkt_ext_v1 - QAM thresholds + * The required PPE is set via HE Capabilities IE, per Nss x BW x MCS + * The IE is organized in the following way: + * Support for Nss x BW (or RU) matrix: + * (0=SISO, 1=MIMO2) x (0-20MHz, 1-40MHz, 2-80MHz, 3-160MHz) + * Each entry contains 2 QAM thresholds for 8us and 16us: + * 0=BPSK, 1=QPSK, 2=16QAM, 3=64QAM, 4=256QAM, 5=1024QAM, 6=RES, 7=NONE + * i.e. QAM_th1 < QAM_th2 such if TX uses QAM_tx: + * QAM_tx < QAM_th1 --> PPE=0us + * QAM_th1 <= QAM_tx < QAM_th2 --> PPE=8us + * QAM_th2 <= QAM_tx --> PPE=16us + * @pkt_ext_qam_th: QAM thresholds + * For each Nss/Bw define 2 QAM thrsholds (0..5) + * For rates below the low_th, no need for PPE + * For rates between low_th and high_th, need 8us PPE + * For rates equal or higher then the high_th, need 16us PPE + * Nss (0-siso, 1-mimo2) x BW (0-20MHz, 1-40MHz, 2-80MHz, 3-160MHz) x + * (0-low_th, 1-high_th) + */ +struct iwl_he_pkt_ext_v1 { + u8 pkt_ext_qam_th[MAX_HE_SUPP_NSS][MAX_CHANNEL_BW_INDX_API_D_VER_2][2]; +} __packed; /* PKT_EXT_DOT11AX_API_S_VER_1 */ + +/** + * struct iwl_he_pkt_ext_v2 - QAM thresholds + * The required PPE is set via HE Capabilities IE, per Nss x BW x MCS + * The IE is organized in the following way: + * Support for Nss x BW (or RU) matrix: + * (0=SISO, 1=MIMO2) x (0-20MHz, 1-40MHz, 2-80MHz, 3-160MHz) + * Each entry contains 2 QAM thresholds for 8us and 16us: + * 0=BPSK, 1=QPSK, 2=16QAM, 3=64QAM, 4=256QAM, 5=1024QAM, 6=RES, 7=NONE + * i.e. QAM_th1 < QAM_th2 such if TX uses QAM_tx: + * QAM_tx < QAM_th1 --> PPE=0us + * QAM_th1 <= QAM_tx < QAM_th2 --> PPE=8us + * QAM_th2 <= QAM_tx --> PPE=16us + * @pkt_ext_qam_th: QAM thresholds + * For each Nss/Bw define 2 QAM thrsholds (0..5) + * For rates below the low_th, no need for PPE + * For rates between low_th and high_th, need 8us PPE + * For rates equal or higher then the high_th, need 16us PPE + * Nss (0-siso, 1-mimo2) x + * BW (0-20MHz, 1-40MHz, 2-80MHz, 3-160MHz, 4-320MHz) x + * (0-low_th, 1-high_th) + */ +struct iwl_he_pkt_ext_v2 { + u8 pkt_ext_qam_th[MAX_HE_SUPP_NSS][MAX_CHANNEL_BW_INDX_API_D_VER_3][2]; +} __packed; /* PKT_EXT_DOT11AX_API_S_VER_2 */ + +/** + * enum iwl_he_sta_ctxt_flags - HE STA context flags + * @STA_CTXT_HE_REF_BSSID_VALID: ref bssid addr valid (for receiving specific + * control frames such as TRIG, NDPA, BACK) + * @STA_CTXT_HE_BSS_COLOR_DIS: BSS color disable, don't use the BSS + * color for RX filter but use MAC header + * @STA_CTXT_HE_PARTIAL_BSS_COLOR: partial BSS color allocation + * @STA_CTXT_HE_32BIT_BA_BITMAP: indicates the receiver supports BA bitmap + * of 32-bits + * @STA_CTXT_HE_PACKET_EXT: indicates that the packet-extension info is valid + * and should be used + * @STA_CTXT_HE_TRIG_RND_ALLOC: indicates that trigger based random allocation + * is enabled according to UORA element existence + * @STA_CTXT_HE_CONST_TRIG_RND_ALLOC: used for AV testing + * @STA_CTXT_HE_ACK_ENABLED: indicates that the AP supports receiving ACK- + * enabled AGG, i.e. both BACK and non-BACK frames in a single AGG + * @STA_CTXT_HE_MU_EDCA_CW: indicates that there is an element of MU EDCA + * parameter set, i.e. the backoff counters for trig-based ACs + * @STA_CTXT_HE_NIC_NOT_ACK_ENABLED: mark that the NIC doesn't support receiving + * ACK-enabled AGG, (i.e. both BACK and non-BACK frames in single AGG). + * If the NIC is not ACK_ENABLED it may use the EOF-bit in first non-0 + * len delim to determine if AGG or single. + * @STA_CTXT_HE_RU_2MHZ_BLOCK: indicates that 26-tone RU OFDMA transmission are + * not allowed (as there are OBSS that might classify such transmissions as + * radar pulses). + * @STA_CTXT_HE_NDP_FEEDBACK_ENABLED: mark support for NDP feedback and change + * of threshold + * @STA_CTXT_EHT_PUNCTURE_MASK_VALID: indicates the puncture_mask field is valid + * @STA_CTXT_EHT_LONG_PPE_ENABLED: indicates the PPE requirement should be + * extended to 20us for BW > 160Mhz or for MCS w/ 4096-QAM. + */ +enum iwl_he_sta_ctxt_flags { + STA_CTXT_HE_REF_BSSID_VALID = BIT(4), + STA_CTXT_HE_BSS_COLOR_DIS = BIT(5), + STA_CTXT_HE_PARTIAL_BSS_COLOR = BIT(6), + STA_CTXT_HE_32BIT_BA_BITMAP = BIT(7), + STA_CTXT_HE_PACKET_EXT = BIT(8), + STA_CTXT_HE_TRIG_RND_ALLOC = BIT(9), + STA_CTXT_HE_CONST_TRIG_RND_ALLOC = BIT(10), + STA_CTXT_HE_ACK_ENABLED = BIT(11), + STA_CTXT_HE_MU_EDCA_CW = BIT(12), + STA_CTXT_HE_NIC_NOT_ACK_ENABLED = BIT(13), + STA_CTXT_HE_RU_2MHZ_BLOCK = BIT(14), + STA_CTXT_HE_NDP_FEEDBACK_ENABLED = BIT(15), + STA_CTXT_EHT_PUNCTURE_MASK_VALID = BIT(16), + STA_CTXT_EHT_LONG_PPE_ENABLED = BIT(17), +}; + +/** + * enum iwl_he_htc_flags - HE HTC support flags + * @IWL_HE_HTC_SUPPORT: HE-HTC support + * @IWL_HE_HTC_UL_MU_RESP_SCHED: HE UL MU response schedule + * support via A-control field + * @IWL_HE_HTC_BSR_SUPP: BSR support in A-control field + * @IWL_HE_HTC_OMI_SUPP: A-OMI support in A-control field + * @IWL_HE_HTC_BQR_SUPP: A-BQR support in A-control field + */ +enum iwl_he_htc_flags { + IWL_HE_HTC_SUPPORT = BIT(0), + IWL_HE_HTC_UL_MU_RESP_SCHED = BIT(3), + IWL_HE_HTC_BSR_SUPP = BIT(4), + IWL_HE_HTC_OMI_SUPP = BIT(5), + IWL_HE_HTC_BQR_SUPP = BIT(6), +}; + +/* + * @IWL_HE_HTC_LINK_ADAP_NO_FEEDBACK: the STA does not provide HE MFB + * @IWL_HE_HTC_LINK_ADAP_UNSOLICITED: the STA provides only unsolicited HE MFB + * @IWL_HE_HTC_LINK_ADAP_BOTH: the STA is capable of providing HE MFB in + * response to HE MRQ and if the STA provides unsolicited HE MFB + */ +#define IWL_HE_HTC_LINK_ADAP_POS (1) +#define IWL_HE_HTC_LINK_ADAP_NO_FEEDBACK (0) +#define IWL_HE_HTC_LINK_ADAP_UNSOLICITED (2 << IWL_HE_HTC_LINK_ADAP_POS) +#define IWL_HE_HTC_LINK_ADAP_BOTH (3 << IWL_HE_HTC_LINK_ADAP_POS) + +/** + * struct iwl_he_sta_context_cmd_v1 - configure FW to work with HE AP + * @sta_id: STA id + * @tid_limit: max num of TIDs in TX HE-SU multi-TID agg + * 0 - bad value, 1 - multi-tid not supported, 2..8 - tid limit + * @reserved1: reserved byte for future use + * @reserved2: reserved byte for future use + * @flags: see %iwl_11ax_sta_ctxt_flags + * @ref_bssid_addr: reference BSSID used by the AP + * @reserved0: reserved 2 bytes for aligning the ref_bssid_addr field to 8 bytes + * @htc_flags: which features are supported in HTC + * @frag_flags: frag support in A-MSDU + * @frag_level: frag support level + * @frag_max_num: max num of "open" MSDUs in the receiver (in power of 2) + * @frag_min_size: min frag size (except last frag) + * @pkt_ext: optional, exists according to PPE-present bit in the HE-PHY capa + * @bss_color: 11ax AP ID that is used in the HE SIG-A to mark inter BSS frame + * @htc_trig_based_pkt_ext: default PE in 4us units + * @frame_time_rts_th: HE duration RTS threshold, in units of 32us + * @rand_alloc_ecwmin: random CWmin = 2**ECWmin-1 + * @rand_alloc_ecwmax: random CWmax = 2**ECWmax-1 + * @reserved3: reserved byte for future use + * @trig_based_txf: MU EDCA Parameter set for the trigger based traffic queues + */ +struct iwl_he_sta_context_cmd_v1 { + u8 sta_id; + u8 tid_limit; + u8 reserved1; + u8 reserved2; + __le32 flags; + + /* The below fields are set via Multiple BSSID IE */ + u8 ref_bssid_addr[6]; + __le16 reserved0; + + /* The below fields are set via HE-capabilities IE */ + __le32 htc_flags; + + u8 frag_flags; + u8 frag_level; + u8 frag_max_num; + u8 frag_min_size; + + /* The below fields are set via PPE thresholds element */ + struct iwl_he_pkt_ext_v1 pkt_ext; + + /* The below fields are set via HE-Operation IE */ + u8 bss_color; + u8 htc_trig_based_pkt_ext; + __le16 frame_time_rts_th; + + /* Random access parameter set (i.e. RAPS) */ + u8 rand_alloc_ecwmin; + u8 rand_alloc_ecwmax; + __le16 reserved3; + + /* The below fields are set via MU EDCA parameter set element */ + struct iwl_he_backoff_conf trig_based_txf[AC_NUM]; +} __packed; /* STA_CONTEXT_DOT11AX_API_S_VER_1 */ + +/** + * struct iwl_he_sta_context_cmd_v2 - configure FW to work with HE AP + * @sta_id: STA id + * @tid_limit: max num of TIDs in TX HE-SU multi-TID agg + * 0 - bad value, 1 - multi-tid not supported, 2..8 - tid limit + * @reserved1: reserved byte for future use + * @reserved2: reserved byte for future use + * @flags: see %iwl_11ax_sta_ctxt_flags + * @ref_bssid_addr: reference BSSID used by the AP + * @reserved0: reserved 2 bytes for aligning the ref_bssid_addr field to 8 bytes + * @htc_flags: which features are supported in HTC + * @frag_flags: frag support in A-MSDU + * @frag_level: frag support level + * @frag_max_num: max num of "open" MSDUs in the receiver (in power of 2) + * @frag_min_size: min frag size (except last frag) + * @pkt_ext: optional, exists according to PPE-present bit in the HE-PHY capa + * @bss_color: 11ax AP ID that is used in the HE SIG-A to mark inter BSS frame + * @htc_trig_based_pkt_ext: default PE in 4us units + * @frame_time_rts_th: HE duration RTS threshold, in units of 32us + * @rand_alloc_ecwmin: random CWmin = 2**ECWmin-1 + * @rand_alloc_ecwmax: random CWmax = 2**ECWmax-1 + * @reserved3: reserved byte for future use + * @trig_based_txf: MU EDCA Parameter set for the trigger based traffic queues + * @max_bssid_indicator: indicator of the max bssid supported on the associated + * bss + * @bssid_index: index of the associated VAP + * @ema_ap: AP supports enhanced Multi BSSID advertisement + * @profile_periodicity: number of Beacon periods that are needed to receive the + * complete VAPs info + * @bssid_count: actual number of VAPs in the MultiBSS Set + * @reserved4: alignment + */ +struct iwl_he_sta_context_cmd_v2 { + u8 sta_id; + u8 tid_limit; + u8 reserved1; + u8 reserved2; + __le32 flags; + + /* The below fields are set via Multiple BSSID IE */ + u8 ref_bssid_addr[6]; + __le16 reserved0; + + /* The below fields are set via HE-capabilities IE */ + __le32 htc_flags; + + u8 frag_flags; + u8 frag_level; + u8 frag_max_num; + u8 frag_min_size; + + /* The below fields are set via PPE thresholds element */ + struct iwl_he_pkt_ext_v1 pkt_ext; + + /* The below fields are set via HE-Operation IE */ + u8 bss_color; + u8 htc_trig_based_pkt_ext; + __le16 frame_time_rts_th; + + /* Random access parameter set (i.e. RAPS) */ + u8 rand_alloc_ecwmin; + u8 rand_alloc_ecwmax; + __le16 reserved3; + + /* The below fields are set via MU EDCA parameter set element */ + struct iwl_he_backoff_conf trig_based_txf[AC_NUM]; + + u8 max_bssid_indicator; + u8 bssid_index; + u8 ema_ap; + u8 profile_periodicity; + u8 bssid_count; + u8 reserved4[3]; +} __packed; /* STA_CONTEXT_DOT11AX_API_S_VER_2 */ + +/** + * struct iwl_he_sta_context_cmd_v3 - configure FW to work with HE AP + * @sta_id: STA id + * @tid_limit: max num of TIDs in TX HE-SU multi-TID agg + * 0 - bad value, 1 - multi-tid not supported, 2..8 - tid limit + * @reserved1: reserved byte for future use + * @reserved2: reserved byte for future use + * @flags: see %iwl_11ax_sta_ctxt_flags + * @ref_bssid_addr: reference BSSID used by the AP + * @reserved0: reserved 2 bytes for aligning the ref_bssid_addr field to 8 bytes + * @htc_flags: which features are supported in HTC + * @frag_flags: frag support in A-MSDU + * @frag_level: frag support level + * @frag_max_num: max num of "open" MSDUs in the receiver (in power of 2) + * @frag_min_size: min frag size (except last frag) + * @pkt_ext: optional, exists according to PPE-present bit in the HE-PHY capa + * @bss_color: 11ax AP ID that is used in the HE SIG-A to mark inter BSS frame + * @htc_trig_based_pkt_ext: default PE in 4us units + * @frame_time_rts_th: HE duration RTS threshold, in units of 32us + * @rand_alloc_ecwmin: random CWmin = 2**ECWmin-1 + * @rand_alloc_ecwmax: random CWmax = 2**ECWmax-1 + * @puncture_mask: puncture mask for EHT + * @trig_based_txf: MU EDCA Parameter set for the trigger based traffic queues + * @max_bssid_indicator: indicator of the max bssid supported on the associated + * bss + * @bssid_index: index of the associated VAP + * @ema_ap: AP supports enhanced Multi BSSID advertisement + * @profile_periodicity: number of Beacon periods that are needed to receive the + * complete VAPs info + * @bssid_count: actual number of VAPs in the MultiBSS Set + * @reserved4: alignment + */ +struct iwl_he_sta_context_cmd_v3 { + u8 sta_id; + u8 tid_limit; + u8 reserved1; + u8 reserved2; + __le32 flags; + + /* The below fields are set via Multiple BSSID IE */ + u8 ref_bssid_addr[6]; + __le16 reserved0; + + /* The below fields are set via HE-capabilities IE */ + __le32 htc_flags; + + u8 frag_flags; + u8 frag_level; + u8 frag_max_num; + u8 frag_min_size; + + /* The below fields are set via PPE thresholds element */ + struct iwl_he_pkt_ext_v2 pkt_ext; + + /* The below fields are set via HE-Operation IE */ + u8 bss_color; + u8 htc_trig_based_pkt_ext; + __le16 frame_time_rts_th; + + /* Random access parameter set (i.e. RAPS) */ + u8 rand_alloc_ecwmin; + u8 rand_alloc_ecwmax; + __le16 puncture_mask; + + /* The below fields are set via MU EDCA parameter set element */ + struct iwl_he_backoff_conf trig_based_txf[AC_NUM]; + + u8 max_bssid_indicator; + u8 bssid_index; + u8 ema_ap; + u8 profile_periodicity; + u8 bssid_count; + u8 reserved4[3]; +} __packed; /* STA_CONTEXT_DOT11AX_API_S_VER_2 */ + +/** + * struct iwl_he_monitor_cmd - configure air sniffer for HE + * @bssid: the BSSID to sniff for + * @reserved1: reserved for dword alignment + * @aid: the AID to track on for HE MU + * @reserved2: reserved for future use + */ +struct iwl_he_monitor_cmd { + u8 bssid[6]; + __le16 reserved1; + __le16 aid; + u8 reserved2[6]; +} __packed; /* HE_AIR_SNIFFER_CONFIG_CMD_API_S_VER_1 */ + +#endif /* __iwl_fw_api_mac_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h b/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h new file mode 100644 index 000000000..91bfde6d5 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h @@ -0,0 +1,575 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_nvm_reg_h__ +#define __iwl_fw_api_nvm_reg_h__ + +/** + * enum iwl_regulatory_and_nvm_subcmd_ids - regulatory/NVM commands + */ +enum iwl_regulatory_and_nvm_subcmd_ids { + /** + * @NVM_ACCESS_COMPLETE: &struct iwl_nvm_access_complete_cmd + */ + NVM_ACCESS_COMPLETE = 0x0, + + /** + * @LARI_CONFIG_CHANGE: &struct iwl_lari_config_change_cmd + */ + LARI_CONFIG_CHANGE = 0x1, + + /** + * @NVM_GET_INFO: + * Command is &struct iwl_nvm_get_info, + * response is &struct iwl_nvm_get_info_rsp + */ + NVM_GET_INFO = 0x2, + + /** + * @TAS_CONFIG: &struct iwl_tas_config_cmd + */ + TAS_CONFIG = 0x3, + + /** + * @SAR_OFFSET_MAPPING_TABLE_CMD: &iwl_sar_offset_mapping_cmd + */ + SAR_OFFSET_MAPPING_TABLE_CMD = 0x4, + + /** + * @PNVM_INIT_COMPLETE_NTFY: &struct iwl_pnvm_init_complete_ntfy + */ + PNVM_INIT_COMPLETE_NTFY = 0xFE, +}; + +/** + * enum iwl_nvm_access_op - NVM access opcode + * @IWL_NVM_READ: read NVM + * @IWL_NVM_WRITE: write NVM + */ +enum iwl_nvm_access_op { + IWL_NVM_READ = 0, + IWL_NVM_WRITE = 1, +}; + +/** + * enum iwl_nvm_access_target - target of the NVM_ACCESS_CMD + * @NVM_ACCESS_TARGET_CACHE: access the cache + * @NVM_ACCESS_TARGET_OTP: access the OTP + * @NVM_ACCESS_TARGET_EEPROM: access the EEPROM + */ +enum iwl_nvm_access_target { + NVM_ACCESS_TARGET_CACHE = 0, + NVM_ACCESS_TARGET_OTP = 1, + NVM_ACCESS_TARGET_EEPROM = 2, +}; + +/** + * enum iwl_nvm_section_type - section types for NVM_ACCESS_CMD + * @NVM_SECTION_TYPE_SW: software section + * @NVM_SECTION_TYPE_REGULATORY: regulatory section + * @NVM_SECTION_TYPE_CALIBRATION: calibration section + * @NVM_SECTION_TYPE_PRODUCTION: production section + * @NVM_SECTION_TYPE_REGULATORY_SDP: regulatory section used by 3168 series + * @NVM_SECTION_TYPE_MAC_OVERRIDE: MAC override section + * @NVM_SECTION_TYPE_PHY_SKU: PHY SKU section + * @NVM_MAX_NUM_SECTIONS: number of sections + */ +enum iwl_nvm_section_type { + NVM_SECTION_TYPE_SW = 1, + NVM_SECTION_TYPE_REGULATORY = 3, + NVM_SECTION_TYPE_CALIBRATION = 4, + NVM_SECTION_TYPE_PRODUCTION = 5, + NVM_SECTION_TYPE_REGULATORY_SDP = 8, + NVM_SECTION_TYPE_MAC_OVERRIDE = 11, + NVM_SECTION_TYPE_PHY_SKU = 12, + NVM_MAX_NUM_SECTIONS = 13, +}; + +/** + * struct iwl_nvm_access_cmd - Request the device to send an NVM section + * @op_code: &enum iwl_nvm_access_op + * @target: &enum iwl_nvm_access_target + * @type: &enum iwl_nvm_section_type + * @offset: offset in bytes into the section + * @length: in bytes, to read/write + * @data: if write operation, the data to write. On read its empty + */ +struct iwl_nvm_access_cmd { + u8 op_code; + u8 target; + __le16 type; + __le16 offset; + __le16 length; + u8 data[]; +} __packed; /* NVM_ACCESS_CMD_API_S_VER_2 */ + +/** + * struct iwl_nvm_access_resp_ver2 - response to NVM_ACCESS_CMD + * @offset: offset in bytes into the section + * @length: in bytes, either how much was written or read + * @type: NVM_SECTION_TYPE_* + * @status: 0 for success, fail otherwise + * @data: if read operation, the data returned. Empty on write. + */ +struct iwl_nvm_access_resp { + __le16 offset; + __le16 length; + __le16 type; + __le16 status; + u8 data[]; +} __packed; /* NVM_ACCESS_CMD_RESP_API_S_VER_2 */ + +/* + * struct iwl_nvm_get_info - request to get NVM data + */ +struct iwl_nvm_get_info { + __le32 reserved; +} __packed; /* REGULATORY_NVM_GET_INFO_CMD_API_S_VER_1 */ + +/** + * enum iwl_nvm_info_general_flags - flags in NVM_GET_INFO resp + * @NVM_GENERAL_FLAGS_EMPTY_OTP: 1 if OTP is empty + */ +enum iwl_nvm_info_general_flags { + NVM_GENERAL_FLAGS_EMPTY_OTP = BIT(0), +}; + +/** + * struct iwl_nvm_get_info_general - general NVM data + * @flags: bit 0: 1 - empty, 0 - non-empty + * @nvm_version: nvm version + * @board_type: board type + * @n_hw_addrs: number of reserved MAC addresses + */ +struct iwl_nvm_get_info_general { + __le32 flags; + __le16 nvm_version; + u8 board_type; + u8 n_hw_addrs; +} __packed; /* REGULATORY_NVM_GET_INFO_GENERAL_S_VER_2 */ + +/** + * enum iwl_nvm_mac_sku_flags - flags in &iwl_nvm_get_info_sku + * @NVM_MAC_SKU_FLAGS_BAND_2_4_ENABLED: true if 2.4 band enabled + * @NVM_MAC_SKU_FLAGS_BAND_5_2_ENABLED: true if 5.2 band enabled + * @NVM_MAC_SKU_FLAGS_802_11N_ENABLED: true if 11n enabled + * @NVM_MAC_SKU_FLAGS_802_11AC_ENABLED: true if 11ac enabled + * @NVM_MAC_SKU_FLAGS_MIMO_DISABLED: true if MIMO disabled + * @NVM_MAC_SKU_FLAGS_WAPI_ENABLED: true if WAPI enabled + * @NVM_MAC_SKU_FLAGS_REG_CHECK_ENABLED: true if regulatory checker enabled + * @NVM_MAC_SKU_FLAGS_API_LOCK_ENABLED: true if API lock enabled + */ +enum iwl_nvm_mac_sku_flags { + NVM_MAC_SKU_FLAGS_BAND_2_4_ENABLED = BIT(0), + NVM_MAC_SKU_FLAGS_BAND_5_2_ENABLED = BIT(1), + NVM_MAC_SKU_FLAGS_802_11N_ENABLED = BIT(2), + NVM_MAC_SKU_FLAGS_802_11AC_ENABLED = BIT(3), + /** + * @NVM_MAC_SKU_FLAGS_802_11AX_ENABLED: true if 11ax enabled + */ + NVM_MAC_SKU_FLAGS_802_11AX_ENABLED = BIT(4), + NVM_MAC_SKU_FLAGS_MIMO_DISABLED = BIT(5), + NVM_MAC_SKU_FLAGS_WAPI_ENABLED = BIT(8), + NVM_MAC_SKU_FLAGS_REG_CHECK_ENABLED = BIT(14), + NVM_MAC_SKU_FLAGS_API_LOCK_ENABLED = BIT(15), +}; + +/** + * struct iwl_nvm_get_info_sku - mac information + * @mac_sku_flags: flags for SKU, see &enum iwl_nvm_mac_sku_flags + */ +struct iwl_nvm_get_info_sku { + __le32 mac_sku_flags; +} __packed; /* REGULATORY_NVM_GET_INFO_MAC_SKU_SECTION_S_VER_2 */ + +/** + * struct iwl_nvm_get_info_phy - phy information + * @tx_chains: BIT 0 chain A, BIT 1 chain B + * @rx_chains: BIT 0 chain A, BIT 1 chain B + */ +struct iwl_nvm_get_info_phy { + __le32 tx_chains; + __le32 rx_chains; +} __packed; /* REGULATORY_NVM_GET_INFO_PHY_SKU_SECTION_S_VER_1 */ + +#define IWL_NUM_CHANNELS_V1 51 +#define IWL_NUM_CHANNELS 110 + +/** + * struct iwl_nvm_get_info_regulatory - regulatory information + * @lar_enabled: is LAR enabled + * @channel_profile: regulatory data of this channel + * @reserved: reserved + */ +struct iwl_nvm_get_info_regulatory_v1 { + __le32 lar_enabled; + __le16 channel_profile[IWL_NUM_CHANNELS_V1]; + __le16 reserved; +} __packed; /* REGULATORY_NVM_GET_INFO_REGULATORY_S_VER_1 */ + +/** + * struct iwl_nvm_get_info_regulatory - regulatory information + * @lar_enabled: is LAR enabled + * @n_channels: number of valid channels in the array + * @channel_profile: regulatory data of this channel + */ +struct iwl_nvm_get_info_regulatory { + __le32 lar_enabled; + __le32 n_channels; + __le32 channel_profile[IWL_NUM_CHANNELS]; +} __packed; /* REGULATORY_NVM_GET_INFO_REGULATORY_S_VER_2 */ + +/** + * struct iwl_nvm_get_info_rsp_v3 - response to get NVM data + * @general: general NVM data + * @mac_sku: data relating to MAC sku + * @phy_sku: data relating to PHY sku + * @regulatory: regulatory data + */ +struct iwl_nvm_get_info_rsp_v3 { + struct iwl_nvm_get_info_general general; + struct iwl_nvm_get_info_sku mac_sku; + struct iwl_nvm_get_info_phy phy_sku; + struct iwl_nvm_get_info_regulatory_v1 regulatory; +} __packed; /* REGULATORY_NVM_GET_INFO_RSP_API_S_VER_3 */ + +/** + * struct iwl_nvm_get_info_rsp - response to get NVM data + * @general: general NVM data + * @mac_sku: data relating to MAC sku + * @phy_sku: data relating to PHY sku + * @regulatory: regulatory data + */ +struct iwl_nvm_get_info_rsp { + struct iwl_nvm_get_info_general general; + struct iwl_nvm_get_info_sku mac_sku; + struct iwl_nvm_get_info_phy phy_sku; + struct iwl_nvm_get_info_regulatory regulatory; +} __packed; /* REGULATORY_NVM_GET_INFO_RSP_API_S_VER_4 */ + +/** + * struct iwl_nvm_access_complete_cmd - NVM_ACCESS commands are completed + * @reserved: reserved + */ +struct iwl_nvm_access_complete_cmd { + __le32 reserved; +} __packed; /* NVM_ACCESS_COMPLETE_CMD_API_S_VER_1 */ + +/** + * struct iwl_mcc_update_cmd - Request the device to update geographic + * regulatory profile according to the given MCC (Mobile Country Code). + * The MCC is two letter-code, ascii upper case[A-Z] or '00' for world domain. + * 'ZZ' MCC will be used to switch to NVM default profile; in this case, the + * MCC in the cmd response will be the relevant MCC in the NVM. + * @mcc: given mobile country code + * @source_id: the source from where we got the MCC, see iwl_mcc_source + * @reserved: reserved for alignment + * @key: integrity key for MCC API OEM testing + * @reserved2: reserved + */ +struct iwl_mcc_update_cmd { + __le16 mcc; + u8 source_id; + u8 reserved; + __le32 key; + u8 reserved2[20]; +} __packed; /* LAR_UPDATE_MCC_CMD_API_S_VER_2 */ + +/** + * enum iwl_geo_information - geographic information. + * @GEO_NO_INFO: no special info for this geo profile. + * @GEO_WMM_ETSI_5GHZ_INFO: this geo profile limits the WMM params + * for the 5 GHz band. + */ +enum iwl_geo_information { + GEO_NO_INFO = 0, + GEO_WMM_ETSI_5GHZ_INFO = BIT(0), +}; + +/** + * struct iwl_mcc_update_resp_v3 - response to MCC_UPDATE_CMD. + * Contains the new channel control profile map, if changed, and the new MCC + * (mobile country code). + * The new MCC may be different than what was requested in MCC_UPDATE_CMD. + * @status: see &enum iwl_mcc_update_status + * @mcc: the new applied MCC + * @cap: capabilities for all channels which matches the MCC + * @source_id: the MCC source, see iwl_mcc_source + * @time: time elapsed from the MCC test start (in units of 30 seconds) + * @geo_info: geographic specific profile information + * see &enum iwl_geo_information. + * @n_channels: number of channels in @channels_data. + * @channels: channel control data map, DWORD for each channel. Only the first + * 16bits are used. + */ +struct iwl_mcc_update_resp_v3 { + __le32 status; + __le16 mcc; + u8 cap; + u8 source_id; + __le16 time; + __le16 geo_info; + __le32 n_channels; + __le32 channels[]; +} __packed; /* LAR_UPDATE_MCC_CMD_RESP_S_VER_3 */ + +/** + * struct iwl_mcc_update_resp - response to MCC_UPDATE_CMD. + * Contains the new channel control profile map, if changed, and the new MCC + * (mobile country code). + * The new MCC may be different than what was requested in MCC_UPDATE_CMD. + * @status: see &enum iwl_mcc_update_status + * @mcc: the new applied MCC + * @cap: capabilities for all channels which matches the MCC + * @time: time elapsed from the MCC test start (in units of 30 seconds) + * @geo_info: geographic specific profile information + * see &enum iwl_geo_information. + * @source_id: the MCC source, see iwl_mcc_source + * @reserved: for four bytes alignment. + * @n_channels: number of channels in @channels_data. + * @channels: channel control data map, DWORD for each channel. Only the first + * 16bits are used. + */ +struct iwl_mcc_update_resp { + __le32 status; + __le16 mcc; + __le16 cap; + __le16 time; + __le16 geo_info; + u8 source_id; + u8 reserved[3]; + __le32 n_channels; + __le32 channels[]; +} __packed; /* LAR_UPDATE_MCC_CMD_RESP_S_VER_4 */ + +/** + * struct iwl_mcc_chub_notif - chub notifies of mcc change + * (MCC_CHUB_UPDATE_CMD = 0xc9) + * The Chub (Communication Hub, CommsHUB) is a HW component that connects to + * the cellular and connectivity cores that gets updates of the mcc, and + * notifies the ucode directly of any mcc change. + * The ucode requests the driver to request the device to update geographic + * regulatory profile according to the given MCC (Mobile Country Code). + * The MCC is two letter-code, ascii upper case[A-Z] or '00' for world domain. + * 'ZZ' MCC will be used to switch to NVM default profile; in this case, the + * MCC in the cmd response will be the relevant MCC in the NVM. + * @mcc: given mobile country code + * @source_id: identity of the change originator, see iwl_mcc_source + * @reserved1: reserved for alignment + */ +struct iwl_mcc_chub_notif { + __le16 mcc; + u8 source_id; + u8 reserved1; +} __packed; /* LAR_MCC_NOTIFY_S */ + +enum iwl_mcc_update_status { + MCC_RESP_NEW_CHAN_PROFILE, + MCC_RESP_SAME_CHAN_PROFILE, + MCC_RESP_INVALID, + MCC_RESP_NVM_DISABLED, + MCC_RESP_ILLEGAL, + MCC_RESP_LOW_PRIORITY, + MCC_RESP_TEST_MODE_ACTIVE, + MCC_RESP_TEST_MODE_NOT_ACTIVE, + MCC_RESP_TEST_MODE_DENIAL_OF_SERVICE, +}; + +enum iwl_mcc_source { + MCC_SOURCE_OLD_FW = 0, + MCC_SOURCE_ME = 1, + MCC_SOURCE_BIOS = 2, + MCC_SOURCE_3G_LTE_HOST = 3, + MCC_SOURCE_3G_LTE_DEVICE = 4, + MCC_SOURCE_WIFI = 5, + MCC_SOURCE_RESERVED = 6, + MCC_SOURCE_DEFAULT = 7, + MCC_SOURCE_UNINITIALIZED = 8, + MCC_SOURCE_MCC_API = 9, + MCC_SOURCE_GET_CURRENT = 0x10, + MCC_SOURCE_GETTING_MCC_TEST_MODE = 0x11, +}; + +#define IWL_TAS_BLOCK_LIST_MAX 16 +/** + * struct iwl_tas_config_cmd_v2 - configures the TAS + * @block_list_size: size of relevant field in block_list_array + * @block_list_array: list of countries where TAS must be disabled + */ +struct iwl_tas_config_cmd_v2 { + __le32 block_list_size; + __le32 block_list_array[IWL_TAS_BLOCK_LIST_MAX]; +} __packed; /* TAS_CONFIG_CMD_API_S_VER_2 */ + +/** + * struct iwl_tas_config_cmd_v3 - configures the TAS + * @block_list_size: size of relevant field in block_list_array + * @block_list_array: list of countries where TAS must be disabled + * @override_tas_iec: indicates whether to override default value of IEC regulatory + * @enable_tas_iec: in case override_tas_iec is set - + * indicates whether IEC regulatory is enabled or disabled + */ +struct iwl_tas_config_cmd_v3 { + __le32 block_list_size; + __le32 block_list_array[IWL_TAS_BLOCK_LIST_MAX]; + __le16 override_tas_iec; + __le16 enable_tas_iec; +} __packed; /* TAS_CONFIG_CMD_API_S_VER_3 */ + +/** + * struct iwl_tas_config_cmd_v3 - configures the TAS + * @block_list_size: size of relevant field in block_list_array + * @block_list_array: list of countries where TAS must be disabled + * @override_tas_iec: indicates whether to override default value of IEC regulatory + * @enable_tas_iec: in case override_tas_iec is set - + * indicates whether IEC regulatory is enabled or disabled + * @usa_tas_uhb_allowed: if set, allow TAS UHB in the USA + * @reserved: reserved +*/ +struct iwl_tas_config_cmd_v4 { + __le32 block_list_size; + __le32 block_list_array[IWL_TAS_BLOCK_LIST_MAX]; + u8 override_tas_iec; + u8 enable_tas_iec; + u8 usa_tas_uhb_allowed; + u8 reserved; +} __packed; /* TAS_CONFIG_CMD_API_S_VER_4 */ + +union iwl_tas_config_cmd { + struct iwl_tas_config_cmd_v2 v2; + struct iwl_tas_config_cmd_v3 v3; + struct iwl_tas_config_cmd_v4 v4; +}; +/** + * enum iwl_lari_configs - bit masks for the various LARI config operations + * @LARI_CONFIG_DISABLE_11AC_UKRAINE_MSK: disable 11ac in ukraine + * @LARI_CONFIG_CHANGE_ETSI_TO_PASSIVE_MSK: ETSI 5.8GHz SRD passive scan + * @LARI_CONFIG_CHANGE_ETSI_TO_DISABLED_MSK: ETSI 5.8GHz SRD disabled + * @LARI_CONFIG_ENABLE_5G2_IN_INDONESIA_MSK: enable 5.15/5.35GHz bands in + * Indonesia + */ +enum iwl_lari_config_masks { + LARI_CONFIG_DISABLE_11AC_UKRAINE_MSK = BIT(0), + LARI_CONFIG_CHANGE_ETSI_TO_PASSIVE_MSK = BIT(1), + LARI_CONFIG_CHANGE_ETSI_TO_DISABLED_MSK = BIT(2), + LARI_CONFIG_ENABLE_5G2_IN_INDONESIA_MSK = BIT(3), +}; + +#define IWL_11AX_UKRAINE_MASK 3 +#define IWL_11AX_UKRAINE_SHIFT 8 + +/** + * struct iwl_lari_config_change_cmd_v1 - change LARI configuration + * @config_bitmap: bit map of the config commands. each bit will trigger a + * different predefined FW config operation + */ +struct iwl_lari_config_change_cmd_v1 { + __le32 config_bitmap; +} __packed; /* LARI_CHANGE_CONF_CMD_S_VER_1 */ + +/** + * struct iwl_lari_config_change_cmd_v2 - change LARI configuration + * @config_bitmap: bit map of the config commands. each bit will trigger a + * different predefined FW config operation + * @oem_uhb_allow_bitmap: bitmap of UHB enabled MCC sets + */ +struct iwl_lari_config_change_cmd_v2 { + __le32 config_bitmap; + __le32 oem_uhb_allow_bitmap; +} __packed; /* LARI_CHANGE_CONF_CMD_S_VER_2 */ + +/** + * struct iwl_lari_config_change_cmd_v3 - change LARI configuration + * @config_bitmap: bit map of the config commands. each bit will trigger a + * different predefined FW config operation + * @oem_uhb_allow_bitmap: bitmap of UHB enabled MCC sets + * @oem_11ax_allow_bitmap: bitmap of 11ax allowed MCCs. + * For each supported country, a pair of regulatory override bit and 11ax mode exist + * in the bit field. + */ +struct iwl_lari_config_change_cmd_v3 { + __le32 config_bitmap; + __le32 oem_uhb_allow_bitmap; + __le32 oem_11ax_allow_bitmap; +} __packed; /* LARI_CHANGE_CONF_CMD_S_VER_3 */ + +/** + * struct iwl_lari_config_change_cmd_v4 - change LARI configuration + * @config_bitmap: Bitmap of the config commands. Each bit will trigger a + * different predefined FW config operation. + * @oem_uhb_allow_bitmap: Bitmap of UHB enabled MCC sets. + * @oem_11ax_allow_bitmap: Bitmap of 11ax allowed MCCs. There are two bits + * per country, one to indicate whether to override and the other to + * indicate the value to use. + * @oem_unii4_allow_bitmap: Bitmap of unii4 allowed MCCs.There are two bits + * per country, one to indicate whether to override and the other to + * indicate allow/disallow unii4 channels. + */ +struct iwl_lari_config_change_cmd_v4 { + __le32 config_bitmap; + __le32 oem_uhb_allow_bitmap; + __le32 oem_11ax_allow_bitmap; + __le32 oem_unii4_allow_bitmap; +} __packed; /* LARI_CHANGE_CONF_CMD_S_VER_4 */ + +/** + * struct iwl_lari_config_change_cmd_v5 - change LARI configuration + * @config_bitmap: Bitmap of the config commands. Each bit will trigger a + * different predefined FW config operation. + * @oem_uhb_allow_bitmap: Bitmap of UHB enabled MCC sets. + * @oem_11ax_allow_bitmap: Bitmap of 11ax allowed MCCs. There are two bits + * per country, one to indicate whether to override and the other to + * indicate the value to use. + * @oem_unii4_allow_bitmap: Bitmap of unii4 allowed MCCs.There are two bits + * per country, one to indicate whether to override and the other to + * indicate allow/disallow unii4 channels. + * @chan_state_active_bitmap: Bitmap for overriding channel state to active. + * Each bit represents a country or region to activate, according to the BIOS + * definitions. + */ +struct iwl_lari_config_change_cmd_v5 { + __le32 config_bitmap; + __le32 oem_uhb_allow_bitmap; + __le32 oem_11ax_allow_bitmap; + __le32 oem_unii4_allow_bitmap; + __le32 chan_state_active_bitmap; +} __packed; /* LARI_CHANGE_CONF_CMD_S_VER_5 */ + +/** + * struct iwl_lari_config_change_cmd_v6 - change LARI configuration + * @config_bitmap: Bitmap of the config commands. Each bit will trigger a + * different predefined FW config operation. + * @oem_uhb_allow_bitmap: Bitmap of UHB enabled MCC sets. + * @oem_11ax_allow_bitmap: Bitmap of 11ax allowed MCCs. There are two bits + * per country, one to indicate whether to override and the other to + * indicate the value to use. + * @oem_unii4_allow_bitmap: Bitmap of unii4 allowed MCCs.There are two bits + * per country, one to indicate whether to override and the other to + * indicate allow/disallow unii4 channels. + * @chan_state_active_bitmap: Bitmap for overriding channel state to active. + * Each bit represents a country or region to activate, according to the BIOS + * definitions. + * @force_disable_channels_bitmap: Bitmap of disabled bands/channels. + * Each bit represents a set of channels in a specific band that should be disabled + */ +struct iwl_lari_config_change_cmd_v6 { + __le32 config_bitmap; + __le32 oem_uhb_allow_bitmap; + __le32 oem_11ax_allow_bitmap; + __le32 oem_unii4_allow_bitmap; + __le32 chan_state_active_bitmap; + __le32 force_disable_channels_bitmap; +} __packed; /* LARI_CHANGE_CONF_CMD_S_VER_6 */ + +/** + * struct iwl_pnvm_init_complete_ntfy - PNVM initialization complete + * @status: PNVM image loading status + */ +struct iwl_pnvm_init_complete_ntfy { + __le32 status; +} __packed; /* PNVM_INIT_COMPLETE_NTFY_S_VER_1 */ + +#endif /* __iwl_fw_api_nvm_reg_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/offload.h b/drivers/net/wireless/intel/iwlwifi/fw/api/offload.h new file mode 100644 index 000000000..a0123f81f --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/offload.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + * Copyright (C) 2021-2022 Intel Corporation + */ +#ifndef __iwl_fw_api_offload_h__ +#define __iwl_fw_api_offload_h__ + +/** + * enum iwl_prot_offload_subcmd_ids - protocol offload commands + */ +enum iwl_prot_offload_subcmd_ids { + /** + * @WOWLAN_WAKE_PKT_NOTIFICATION: Notification in &struct iwl_wowlan_wake_pkt_notif + */ + WOWLAN_WAKE_PKT_NOTIFICATION = 0xFC, + + /** + * @WOWLAN_INFO_NOTIFICATION: Notification in &struct iwl_wowlan_info_notif + */ + WOWLAN_INFO_NOTIFICATION = 0xFD, + + /** + * @D3_END_NOTIFICATION: End D3 state notification + */ + D3_END_NOTIFICATION = 0xFE, + + /** + * @STORED_BEACON_NTF: &struct iwl_stored_beacon_notif + */ + STORED_BEACON_NTF = 0xFF, +}; + +#define MAX_STORED_BEACON_SIZE 600 + +/** + * struct iwl_stored_beacon_notif_common - Stored beacon notif common fields + * + * @system_time: system time on air rise + * @tsf: TSF on air rise + * @beacon_timestamp: beacon on air rise + * @band: band, matches &RX_RES_PHY_FLAGS_BAND_24 definition + * @channel: channel this beacon was received on + * @rates: rate in ucode internal format + * @byte_count: frame's byte count + */ +struct iwl_stored_beacon_notif_common { + __le32 system_time; + __le64 tsf; + __le32 beacon_timestamp; + __le16 band; + __le16 channel; + __le32 rates; + __le32 byte_count; +} __packed; + +/** + * struct iwl_stored_beacon_notif - Stored beacon notification + * + * @common: fields common for all versions + * @data: beacon data, length in @byte_count + */ +struct iwl_stored_beacon_notif_v2 { + struct iwl_stored_beacon_notif_common common; + u8 data[MAX_STORED_BEACON_SIZE]; +} __packed; /* WOWLAN_STROED_BEACON_INFO_S_VER_2 */ + +/** + * struct iwl_stored_beacon_notif_v3 - Stored beacon notification + * + * @common: fields common for all versions + * @sta_id: station for which the beacon was received + * @reserved: reserved for alignment + * @data: beacon data, length in @byte_count + */ +struct iwl_stored_beacon_notif_v3 { + struct iwl_stored_beacon_notif_common common; + u8 sta_id; + u8 reserved[3]; + u8 data[MAX_STORED_BEACON_SIZE]; +} __packed; /* WOWLAN_STROED_BEACON_INFO_S_VER_3 */ + +#endif /* __iwl_fw_api_offload_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/paging.h b/drivers/net/wireless/intel/iwlwifi/fw/api/paging.h new file mode 100644 index 000000000..db59a1897 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/paging.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_paging_h__ +#define __iwl_fw_api_paging_h__ + +#define NUM_OF_FW_PAGING_BLOCKS 33 /* 32 for data and 1 block for CSS */ + +/** + * struct iwl_fw_paging_cmd - paging layout + * + * Send to FW the paging layout in the driver. + * + * @flags: various flags for the command + * @block_size: the block size in powers of 2 + * @block_num: number of blocks specified in the command. + * @device_phy_addr: virtual addresses from device side + */ +struct iwl_fw_paging_cmd { + __le32 flags; + __le32 block_size; + __le32 block_num; + __le32 device_phy_addr[NUM_OF_FW_PAGING_BLOCKS]; +} __packed; /* FW_PAGING_BLOCK_CMD_API_S_VER_1 */ + +#endif /* __iwl_fw_api_paging_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/phy-ctxt.h b/drivers/net/wireless/intel/iwlwifi/fw/api/phy-ctxt.h new file mode 100644 index 000000000..e66f77924 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/phy-ctxt.h @@ -0,0 +1,161 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018, 2020-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_phy_ctxt_h__ +#define __iwl_fw_api_phy_ctxt_h__ + +/* Supported bands */ +#define PHY_BAND_5 (0) +#define PHY_BAND_24 (1) +#define PHY_BAND_6 (2) + +/* Supported channel width, vary if there is VHT support */ +#define PHY_VHT_CHANNEL_MODE20 (0x0) +#define PHY_VHT_CHANNEL_MODE40 (0x1) +#define PHY_VHT_CHANNEL_MODE80 (0x2) +#define PHY_VHT_CHANNEL_MODE160 (0x3) + +/* + * Control channel position: + * For legacy set bit means upper channel, otherwise lower. + * For VHT - bit-2 marks if the control is lower/upper relative to center-freq + * bits-1:0 mark the distance from the center freq. for 20Mhz, offset is 0. + * center_freq + * | + * 40Mhz |_______|_______| + * 80Mhz |_______|_______|_______|_______| + * 160Mhz |_______|_______|_______|_______|_______|_______|_______|_______| + * code 011 010 001 000 | 100 101 110 111 + */ +#define PHY_VHT_CTRL_POS_1_BELOW (0x0) +#define PHY_VHT_CTRL_POS_2_BELOW (0x1) +#define PHY_VHT_CTRL_POS_3_BELOW (0x2) +#define PHY_VHT_CTRL_POS_4_BELOW (0x3) +#define PHY_VHT_CTRL_POS_1_ABOVE (0x4) +#define PHY_VHT_CTRL_POS_2_ABOVE (0x5) +#define PHY_VHT_CTRL_POS_3_ABOVE (0x6) +#define PHY_VHT_CTRL_POS_4_ABOVE (0x7) + +/* + * struct iwl_fw_channel_info_v1 - channel information + * + * @band: PHY_BAND_* + * @channel: channel number + * @width: PHY_[VHT|LEGACY]_CHANNEL_* + * @ctrl channel: PHY_[VHT|LEGACY]_CTRL_* + */ +struct iwl_fw_channel_info_v1 { + u8 band; + u8 channel; + u8 width; + u8 ctrl_pos; +} __packed; /* CHANNEL_CONFIG_API_S_VER_1 */ + +/* + * struct iwl_fw_channel_info - channel information + * + * @channel: channel number + * @band: PHY_BAND_* + * @width: PHY_[VHT|LEGACY]_CHANNEL_* + * @ctrl channel: PHY_[VHT|LEGACY]_CTRL_* + * @reserved: for future use and alignment + */ +struct iwl_fw_channel_info { + __le32 channel; + u8 band; + u8 width; + u8 ctrl_pos; + u8 reserved; +} __packed; /*CHANNEL_CONFIG_API_S_VER_2 */ + +#define PHY_RX_CHAIN_DRIVER_FORCE_POS (0) +#define PHY_RX_CHAIN_DRIVER_FORCE_MSK \ + (0x1 << PHY_RX_CHAIN_DRIVER_FORCE_POS) +#define PHY_RX_CHAIN_VALID_POS (1) +#define PHY_RX_CHAIN_VALID_MSK \ + (0x7 << PHY_RX_CHAIN_VALID_POS) +#define PHY_RX_CHAIN_FORCE_SEL_POS (4) +#define PHY_RX_CHAIN_FORCE_SEL_MSK \ + (0x7 << PHY_RX_CHAIN_FORCE_SEL_POS) +#define PHY_RX_CHAIN_FORCE_MIMO_SEL_POS (7) +#define PHY_RX_CHAIN_FORCE_MIMO_SEL_MSK \ + (0x7 << PHY_RX_CHAIN_FORCE_MIMO_SEL_POS) +#define PHY_RX_CHAIN_CNT_POS (10) +#define PHY_RX_CHAIN_CNT_MSK \ + (0x3 << PHY_RX_CHAIN_CNT_POS) +#define PHY_RX_CHAIN_MIMO_CNT_POS (12) +#define PHY_RX_CHAIN_MIMO_CNT_MSK \ + (0x3 << PHY_RX_CHAIN_MIMO_CNT_POS) +#define PHY_RX_CHAIN_MIMO_FORCE_POS (14) +#define PHY_RX_CHAIN_MIMO_FORCE_MSK \ + (0x1 << PHY_RX_CHAIN_MIMO_FORCE_POS) + +/* TODO: fix the value, make it depend on firmware at runtime? */ +#define NUM_PHY_CTX 3 + +/* TODO: complete missing documentation */ +/** + * struct iwl_phy_context_cmd_tail - tail of iwl_phy_ctx_cmd for alignment with + * various channel structures. + * + * @txchain_info: ??? + * @rxchain_info: ??? + * @acquisition_data: ??? + * @dsp_cfg_flags: set to 0 + */ +struct iwl_phy_context_cmd_tail { + __le32 txchain_info; + __le32 rxchain_info; + __le32 acquisition_data; + __le32 dsp_cfg_flags; +} __packed; + +/** + * struct iwl_phy_context_cmd - config of the PHY context + * ( PHY_CONTEXT_CMD = 0x8 ) + * @id_and_color: ID and color of the relevant Binding + * @action: action to perform, one of FW_CTXT_ACTION_* + * @apply_time: 0 means immediate apply and context switch. + * other value means apply new params after X usecs + * @tx_param_color: ??? + * @ci: channel info + * @tail: command tail + */ +struct iwl_phy_context_cmd_v1 { + /* COMMON_INDEX_HDR_API_S_VER_1 */ + __le32 id_and_color; + __le32 action; + /* PHY_CONTEXT_DATA_API_S_VER_3 */ + __le32 apply_time; + __le32 tx_param_color; + struct iwl_fw_channel_info ci; + struct iwl_phy_context_cmd_tail tail; +} __packed; /* PHY_CONTEXT_CMD_API_VER_1 */ + +/** + * struct iwl_phy_context_cmd - config of the PHY context + * ( PHY_CONTEXT_CMD = 0x8 ) + * @id_and_color: ID and color of the relevant Binding + * @action: action to perform, one of FW_CTXT_ACTION_* + * @lmac_id: the lmac id the phy context belongs to + * @ci: channel info + * @rxchain_info: ??? + * @dsp_cfg_flags: set to 0 + * @reserved: reserved to align to 64 bit + */ +struct iwl_phy_context_cmd { + /* COMMON_INDEX_HDR_API_S_VER_1 */ + __le32 id_and_color; + __le32 action; + /* PHY_CONTEXT_DATA_API_S_VER_3, PHY_CONTEXT_DATA_API_S_VER_4 */ + struct iwl_fw_channel_info ci; + __le32 lmac_id; + __le32 rxchain_info; /* reserved in _VER_4 */ + __le32 dsp_cfg_flags; + __le32 reserved; +} __packed; /* PHY_CONTEXT_CMD_API_VER_3, PHY_CONTEXT_CMD_API_VER_4 */ + +#endif /* __iwl_fw_api_phy_ctxt_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/phy.h b/drivers/net/wireless/intel/iwlwifi/fw/api/phy.h new file mode 100644 index 000000000..b1b9c2985 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/phy.h @@ -0,0 +1,226 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2019-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_phy_h__ +#define __iwl_fw_api_phy_h__ + +/** + * enum iwl_phy_ops_subcmd_ids - PHY group commands + */ +enum iwl_phy_ops_subcmd_ids { + /** + * @CMD_DTS_MEASUREMENT_TRIGGER_WIDE: + * Uses either &struct iwl_dts_measurement_cmd or + * &struct iwl_ext_dts_measurement_cmd + */ + CMD_DTS_MEASUREMENT_TRIGGER_WIDE = 0x0, + + /** + * @CTDP_CONFIG_CMD: &struct iwl_mvm_ctdp_cmd + */ + CTDP_CONFIG_CMD = 0x03, + + /** + * @TEMP_REPORTING_THRESHOLDS_CMD: &struct temp_report_ths_cmd + */ + TEMP_REPORTING_THRESHOLDS_CMD = 0x04, + + /** + * @PER_CHAIN_LIMIT_OFFSET_CMD: &struct iwl_geo_tx_power_profiles_cmd + */ + PER_CHAIN_LIMIT_OFFSET_CMD = 0x05, + + /** + * @PER_PLATFORM_ANT_GAIN_CMD: &struct iwl_ppag_table_cmd + */ + PER_PLATFORM_ANT_GAIN_CMD = 0x07, + + /** + * @CT_KILL_NOTIFICATION: &struct ct_kill_notif + */ + CT_KILL_NOTIFICATION = 0xFE, + + /** + * @DTS_MEASUREMENT_NOTIF_WIDE: + * &struct iwl_dts_measurement_notif_v1 or + * &struct iwl_dts_measurement_notif_v2 + */ + DTS_MEASUREMENT_NOTIF_WIDE = 0xFF, +}; + +/* DTS measurements */ + +enum iwl_dts_measurement_flags { + DTS_TRIGGER_CMD_FLAGS_TEMP = BIT(0), + DTS_TRIGGER_CMD_FLAGS_VOLT = BIT(1), +}; + +/** + * struct iwl_dts_measurement_cmd - request DTS temp and/or voltage measurements + * + * @flags: indicates which measurements we want as specified in + * &enum iwl_dts_measurement_flags + */ +struct iwl_dts_measurement_cmd { + __le32 flags; +} __packed; /* TEMPERATURE_MEASUREMENT_TRIGGER_CMD_S */ + +/** +* enum iwl_dts_control_measurement_mode - DTS measurement type +* @DTS_AUTOMATIC: Automatic mode (full SW control). Provide temperature read +* back (latest value. Not waiting for new value). Use automatic +* SW DTS configuration. +* @DTS_REQUEST_READ: Request DTS read. Configure DTS with manual settings, +* trigger DTS reading and provide read back temperature read +* when available. +* @DTS_OVER_WRITE: over-write the DTS temperatures in the SW until next read +* @DTS_DIRECT_WITHOUT_MEASURE: DTS returns its latest temperature result, +* without measurement trigger. +*/ +enum iwl_dts_control_measurement_mode { + DTS_AUTOMATIC = 0, + DTS_REQUEST_READ = 1, + DTS_OVER_WRITE = 2, + DTS_DIRECT_WITHOUT_MEASURE = 3, +}; + +/** +* enum iwl_dts_used - DTS to use or used for measurement in the DTS request +* @DTS_USE_TOP: Top +* @DTS_USE_CHAIN_A: chain A +* @DTS_USE_CHAIN_B: chain B +* @DTS_USE_CHAIN_C: chain C +* @XTAL_TEMPERATURE: read temperature from xtal +*/ +enum iwl_dts_used { + DTS_USE_TOP = 0, + DTS_USE_CHAIN_A = 1, + DTS_USE_CHAIN_B = 2, + DTS_USE_CHAIN_C = 3, + XTAL_TEMPERATURE = 4, +}; + +/** +* enum iwl_dts_bit_mode - bit-mode to use in DTS request read mode +* @DTS_BIT6_MODE: bit 6 mode +* @DTS_BIT8_MODE: bit 8 mode +*/ +enum iwl_dts_bit_mode { + DTS_BIT6_MODE = 0, + DTS_BIT8_MODE = 1, +}; + +/** + * struct iwl_ext_dts_measurement_cmd - request extended DTS temp measurements + * @control_mode: see &enum iwl_dts_control_measurement_mode + * @temperature: used when over write DTS mode is selected + * @sensor: set temperature sensor to use. See &enum iwl_dts_used + * @avg_factor: average factor to DTS in request DTS read mode + * @bit_mode: value defines the DTS bit mode to use. See &enum iwl_dts_bit_mode + * @step_duration: step duration for the DTS + */ +struct iwl_ext_dts_measurement_cmd { + __le32 control_mode; + __le32 temperature; + __le32 sensor; + __le32 avg_factor; + __le32 bit_mode; + __le32 step_duration; +} __packed; /* XVT_FW_DTS_CONTROL_MEASUREMENT_REQUEST_API_S */ + +/** + * struct iwl_dts_measurement_notif_v1 - measurements notification + * + * @temp: the measured temperature + * @voltage: the measured voltage + */ +struct iwl_dts_measurement_notif_v1 { + __le32 temp; + __le32 voltage; +} __packed; /* TEMPERATURE_MEASUREMENT_TRIGGER_NTFY_S_VER_1*/ + +/** + * struct iwl_dts_measurement_notif_v2 - measurements notification + * + * @temp: the measured temperature + * @voltage: the measured voltage + * @threshold_idx: the trip index that was crossed + */ +struct iwl_dts_measurement_notif_v2 { + __le32 temp; + __le32 voltage; + __le32 threshold_idx; +} __packed; /* TEMPERATURE_MEASUREMENT_TRIGGER_NTFY_S_VER_2 */ + +/** + * struct iwl_dts_measurement_resp - measurements response + * + * @temp: the measured temperature + */ +struct iwl_dts_measurement_resp { + __le32 temp; +} __packed; /* CMD_DTS_MEASUREMENT_RSP_API_S_VER_1 */ + +/** + * struct ct_kill_notif - CT-kill entry notification + * This structure represent both versions of this notification. + * + * @temperature: the current temperature in celsius + * @dts: only in v2: DTS that trigger the CT Kill bitmap: + * bit 0: ToP master + * bit 1: PA chain A master + * bit 2: PA chain B master + * bit 3: ToP slave + * bit 4: PA chain A slave + * bit 5: PA chain B slave) + * bits 6,7: reserved (set to 0) + * @scheme: only for v2: scheme that trigger the CT Kill (0-SW, 1-HW) + */ +struct ct_kill_notif { + __le16 temperature; + u8 dts; + u8 scheme; +} __packed; /* CT_KILL_NOTIFICATION_API_S_VER_1, CT_KILL_NOTIFICATION_API_S_VER_2 */ + +/** +* enum ctdp_cmd_operation - CTDP command operations +* @CTDP_CMD_OPERATION_START: update the current budget +* @CTDP_CMD_OPERATION_STOP: stop ctdp +* @CTDP_CMD_OPERATION_REPORT: get the average budget +*/ +enum iwl_mvm_ctdp_cmd_operation { + CTDP_CMD_OPERATION_START = 0x1, + CTDP_CMD_OPERATION_STOP = 0x2, + CTDP_CMD_OPERATION_REPORT = 0x4, +};/* CTDP_CMD_OPERATION_TYPE_E */ + +/** + * struct iwl_mvm_ctdp_cmd - track and manage the FW power consumption budget + * + * @operation: see &enum iwl_mvm_ctdp_cmd_operation + * @budget: the budget in milliwatt + * @window_size: defined in API but not used + */ +struct iwl_mvm_ctdp_cmd { + __le32 operation; + __le32 budget; + __le32 window_size; +} __packed; + +#define IWL_MAX_DTS_TRIPS 8 + +/** + * struct temp_report_ths_cmd - set temperature thresholds + * + * @num_temps: number of temperature thresholds passed + * @thresholds: array with the thresholds to be configured + */ +struct temp_report_ths_cmd { + __le32 num_temps; + __le16 thresholds[IWL_MAX_DTS_TRIPS]; +} __packed; /* GRP_PHY_TEMP_REPORTING_THRESHOLDS_CMD */ + +#endif /* __iwl_fw_api_phy_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/power.h b/drivers/net/wireless/intel/iwlwifi/fw/api/power.h new file mode 100644 index 000000000..f92cac1da --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/power.h @@ -0,0 +1,670 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_power_h__ +#define __iwl_fw_api_power_h__ + +/* Power Management Commands, Responses, Notifications */ + +/** + * enum iwl_ltr_config_flags - masks for LTR config command flags + * @LTR_CFG_FLAG_FEATURE_ENABLE: Feature operational status + * @LTR_CFG_FLAG_HW_DIS_ON_SHADOW_REG_ACCESS: allow LTR change on shadow + * memory access + * @LTR_CFG_FLAG_HW_EN_SHRT_WR_THROUGH: allow LTR msg send on ANY LTR + * reg change + * @LTR_CFG_FLAG_HW_DIS_ON_D0_2_D3: allow LTR msg send on transition from + * D0 to D3 + * @LTR_CFG_FLAG_SW_SET_SHORT: fixed static short LTR register + * @LTR_CFG_FLAG_SW_SET_LONG: fixed static short LONG register + * @LTR_CFG_FLAG_DENIE_C10_ON_PD: allow going into C10 on PD + * @LTR_CFG_FLAG_UPDATE_VALUES: update config values and short + * idle timeout + */ +enum iwl_ltr_config_flags { + LTR_CFG_FLAG_FEATURE_ENABLE = BIT(0), + LTR_CFG_FLAG_HW_DIS_ON_SHADOW_REG_ACCESS = BIT(1), + LTR_CFG_FLAG_HW_EN_SHRT_WR_THROUGH = BIT(2), + LTR_CFG_FLAG_HW_DIS_ON_D0_2_D3 = BIT(3), + LTR_CFG_FLAG_SW_SET_SHORT = BIT(4), + LTR_CFG_FLAG_SW_SET_LONG = BIT(5), + LTR_CFG_FLAG_DENIE_C10_ON_PD = BIT(6), + LTR_CFG_FLAG_UPDATE_VALUES = BIT(7), +}; + +/** + * struct iwl_ltr_config_cmd_v1 - configures the LTR + * @flags: See &enum iwl_ltr_config_flags + * @static_long: static LTR Long register value. + * @static_short: static LTR Short register value. + */ +struct iwl_ltr_config_cmd_v1 { + __le32 flags; + __le32 static_long; + __le32 static_short; +} __packed; /* LTR_CAPABLE_API_S_VER_1 */ + +#define LTR_VALID_STATES_NUM 4 + +/** + * struct iwl_ltr_config_cmd - configures the LTR + * @flags: See &enum iwl_ltr_config_flags + * @static_long: static LTR Long register value. + * @static_short: static LTR Short register value. + * @ltr_cfg_values: LTR parameters table values (in usec) in folowing order: + * TX, RX, Short Idle, Long Idle. Used only if %LTR_CFG_FLAG_UPDATE_VALUES + * is set. + * @ltr_short_idle_timeout: LTR Short Idle timeout (in usec). Used only if + * %LTR_CFG_FLAG_UPDATE_VALUES is set. + */ +struct iwl_ltr_config_cmd { + __le32 flags; + __le32 static_long; + __le32 static_short; + __le32 ltr_cfg_values[LTR_VALID_STATES_NUM]; + __le32 ltr_short_idle_timeout; +} __packed; /* LTR_CAPABLE_API_S_VER_2 */ + +/* Radio LP RX Energy Threshold measured in dBm */ +#define POWER_LPRX_RSSI_THRESHOLD 75 +#define POWER_LPRX_RSSI_THRESHOLD_MAX 94 +#define POWER_LPRX_RSSI_THRESHOLD_MIN 30 + +/** + * enum iwl_power_flags - masks for power table command flags + * @POWER_FLAGS_POWER_SAVE_ENA_MSK: '1' Allow to save power by turning off + * receiver and transmitter. '0' - does not allow. + * @POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK: '0' Driver disables power management, + * '1' Driver enables PM (use rest of parameters) + * @POWER_FLAGS_SKIP_OVER_DTIM_MSK: '0' PM have to walk up every DTIM, + * '1' PM could sleep over DTIM till listen Interval. + * @POWER_FLAGS_SNOOZE_ENA_MSK: Enable snoozing only if uAPSD is enabled and all + * access categories are both delivery and trigger enabled. + * @POWER_FLAGS_BT_SCO_ENA: Enable BT SCO coex only if uAPSD and + * PBW Snoozing enabled + * @POWER_FLAGS_ADVANCE_PM_ENA_MSK: Advanced PM (uAPSD) enable mask + * @POWER_FLAGS_LPRX_ENA_MSK: Low Power RX enable. + * @POWER_FLAGS_UAPSD_MISBEHAVING_ENA_MSK: AP/GO's uAPSD misbehaving + * detection enablement +*/ +enum iwl_power_flags { + POWER_FLAGS_POWER_SAVE_ENA_MSK = BIT(0), + POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK = BIT(1), + POWER_FLAGS_SKIP_OVER_DTIM_MSK = BIT(2), + POWER_FLAGS_SNOOZE_ENA_MSK = BIT(5), + POWER_FLAGS_BT_SCO_ENA = BIT(8), + POWER_FLAGS_ADVANCE_PM_ENA_MSK = BIT(9), + POWER_FLAGS_LPRX_ENA_MSK = BIT(11), + POWER_FLAGS_UAPSD_MISBEHAVING_ENA_MSK = BIT(12), +}; + +#define IWL_POWER_VEC_SIZE 5 + +/** + * struct iwl_powertable_cmd - legacy power command. Beside old API support this + * is used also with a new power API for device wide power settings. + * POWER_TABLE_CMD = 0x77 (command, has simple generic response) + * + * @flags: Power table command flags from POWER_FLAGS_* + * @keep_alive_seconds: Keep alive period in seconds. Default - 25 sec. + * Minimum allowed:- 3 * DTIM. Keep alive period must be + * set regardless of power scheme or current power state. + * FW use this value also when PM is disabled. + * @debug_flags: debug flags + * @rx_data_timeout: Minimum time (usec) from last Rx packet for AM to + * PSM transition - legacy PM + * @tx_data_timeout: Minimum time (usec) from last Tx packet for AM to + * PSM transition - legacy PM + * @sleep_interval: not in use + * @skip_dtim_periods: Number of DTIM periods to skip if Skip over DTIM flag + * is set. For example, if it is required to skip over + * one DTIM, this value need to be set to 2 (DTIM periods). + * @lprx_rssi_threshold: Signal strength up to which LP RX can be enabled. + * Default: 80dbm + */ +struct iwl_powertable_cmd { + /* PM_POWER_TABLE_CMD_API_S_VER_6 */ + __le16 flags; + u8 keep_alive_seconds; + u8 debug_flags; + __le32 rx_data_timeout; + __le32 tx_data_timeout; + __le32 sleep_interval[IWL_POWER_VEC_SIZE]; + __le32 skip_dtim_periods; + __le32 lprx_rssi_threshold; +} __packed; + +/** + * enum iwl_device_power_flags - masks for device power command flags + * @DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK: + * '1' Allow to save power by turning off + * receiver and transmitter. '0' - does not allow. + * @DEVICE_POWER_FLAGS_ALLOW_MEM_RETENTION_MSK: + * Device Retention indication, '1' indicate retention is enabled. + * @DEVICE_POWER_FLAGS_32K_CLK_VALID_MSK: + * 32Khz external slow clock valid indication, '1' indicate cloack is + * valid. +*/ +enum iwl_device_power_flags { + DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK = BIT(0), + DEVICE_POWER_FLAGS_ALLOW_MEM_RETENTION_MSK = BIT(1), + DEVICE_POWER_FLAGS_32K_CLK_VALID_MSK = BIT(12), +}; + +/** + * struct iwl_device_power_cmd - device wide power command. + * DEVICE_POWER_CMD = 0x77 (command, has simple generic response) + * + * @flags: Power table command flags from &enum iwl_device_power_flags + * @reserved: reserved (padding) + */ +struct iwl_device_power_cmd { + /* PM_POWER_TABLE_CMD_API_S_VER_6 */ + __le16 flags; + __le16 reserved; +} __packed; + +/** + * struct iwl_mac_power_cmd - New power command containing uAPSD support + * MAC_PM_POWER_TABLE = 0xA9 (command, has simple generic response) + * @id_and_color: MAC contex identifier, &enum iwl_ctxt_id_and_color + * @flags: Power table command flags from POWER_FLAGS_* + * @keep_alive_seconds: Keep alive period in seconds. Default - 25 sec. + * Minimum allowed:- 3 * DTIM. Keep alive period must be + * set regardless of power scheme or current power state. + * FW use this value also when PM is disabled. + * @rx_data_timeout: Minimum time (usec) from last Rx packet for AM to + * PSM transition - legacy PM + * @tx_data_timeout: Minimum time (usec) from last Tx packet for AM to + * PSM transition - legacy PM + * @skip_dtim_periods: Number of DTIM periods to skip if Skip over DTIM flag + * is set. For example, if it is required to skip over + * one DTIM, this value need to be set to 2 (DTIM periods). + * @rx_data_timeout_uapsd: Minimum time (usec) from last Rx packet for AM to + * PSM transition - uAPSD + * @tx_data_timeout_uapsd: Minimum time (usec) from last Tx packet for AM to + * PSM transition - uAPSD + * @lprx_rssi_threshold: Signal strength up to which LP RX can be enabled. + * Default: 80dbm + * @snooze_interval: Maximum time between attempts to retrieve buffered data + * from the AP [msec] + * @snooze_window: A window of time in which PBW snoozing insures that all + * packets received. It is also the minimum time from last + * received unicast RX packet, before client stops snoozing + * for data. [msec] + * @snooze_step: TBD + * @qndp_tid: TID client shall use for uAPSD QNDP triggers + * @uapsd_ac_flags: Set trigger-enabled and delivery-enabled indication for + * each corresponding AC. + * Use IEEE80211_WMM_IE_STA_QOSINFO_AC* for correct values. + * @uapsd_max_sp: Use IEEE80211_WMM_IE_STA_QOSINFO_SP_* for correct + * values. + * @heavy_tx_thld_packets: TX threshold measured in number of packets + * @heavy_rx_thld_packets: RX threshold measured in number of packets + * @heavy_tx_thld_percentage: TX threshold measured in load's percentage + * @heavy_rx_thld_percentage: RX threshold measured in load's percentage + * @limited_ps_threshold: (unused) + * @reserved: reserved (padding) + */ +struct iwl_mac_power_cmd { + /* CONTEXT_DESC_API_T_VER_1 */ + __le32 id_and_color; + + /* CLIENT_PM_POWER_TABLE_S_VER_1 */ + __le16 flags; + __le16 keep_alive_seconds; + __le32 rx_data_timeout; + __le32 tx_data_timeout; + __le32 rx_data_timeout_uapsd; + __le32 tx_data_timeout_uapsd; + u8 lprx_rssi_threshold; + u8 skip_dtim_periods; + __le16 snooze_interval; + __le16 snooze_window; + u8 snooze_step; + u8 qndp_tid; + u8 uapsd_ac_flags; + u8 uapsd_max_sp; + u8 heavy_tx_thld_packets; + u8 heavy_rx_thld_packets; + u8 heavy_tx_thld_percentage; + u8 heavy_rx_thld_percentage; + u8 limited_ps_threshold; + u8 reserved; +} __packed; + +/* + * struct iwl_uapsd_misbehaving_ap_notif - FW sends this notification when + * associated AP is identified as improperly implementing uAPSD protocol. + * PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION = 0x78 + * @sta_id: index of station in uCode's station table - associated AP ID in + * this context. + */ +struct iwl_uapsd_misbehaving_ap_notif { + __le32 sta_id; + u8 mac_id; + u8 reserved[3]; +} __packed; + +/** + * struct iwl_reduce_tx_power_cmd - TX power reduction command + * REDUCE_TX_POWER_CMD = 0x9f + * @flags: (reserved for future implementation) + * @mac_context_id: id of the mac ctx for which we are reducing TX power. + * @pwr_restriction: TX power restriction in dBms. + */ +struct iwl_reduce_tx_power_cmd { + u8 flags; + u8 mac_context_id; + __le16 pwr_restriction; +} __packed; /* TX_REDUCED_POWER_API_S_VER_1 */ + +enum iwl_dev_tx_power_cmd_mode { + IWL_TX_POWER_MODE_SET_MAC = 0, + IWL_TX_POWER_MODE_SET_DEVICE = 1, + IWL_TX_POWER_MODE_SET_CHAINS = 2, + IWL_TX_POWER_MODE_SET_ACK = 3, + IWL_TX_POWER_MODE_SET_SAR_TIMER = 4, + IWL_TX_POWER_MODE_SET_SAR_TIMER_DEFAULT_TABLE = 5, +}; /* TX_POWER_REDUCED_FLAGS_TYPE_API_E_VER_5 */; + +#define IWL_NUM_CHAIN_TABLES 1 +#define IWL_NUM_CHAIN_TABLES_V2 2 +#define IWL_NUM_CHAIN_LIMITS 2 +#define IWL_NUM_SUB_BANDS_V1 5 +#define IWL_NUM_SUB_BANDS_V2 11 + +/** + * struct iwl_dev_tx_power_common - Common part of the TX power reduction cmd + * @set_mode: see &enum iwl_dev_tx_power_cmd_mode + * @mac_context_id: id of the mac ctx for which we are reducing TX power. + * @pwr_restriction: TX power restriction in 1/8 dBms. + * @dev_24: device TX power restriction in 1/8 dBms + * @dev_52_low: device TX power restriction upper band - low + * @dev_52_high: device TX power restriction upper band - high + */ +struct iwl_dev_tx_power_common { + __le32 set_mode; + __le32 mac_context_id; + __le16 pwr_restriction; + __le16 dev_24; + __le16 dev_52_low; + __le16 dev_52_high; +}; + +/** + * struct iwl_dev_tx_power_cmd_v3 - TX power reduction command version 3 + * @per_chain: per chain restrictions + */ +struct iwl_dev_tx_power_cmd_v3 { + __le16 per_chain[IWL_NUM_CHAIN_TABLES][IWL_NUM_CHAIN_LIMITS][IWL_NUM_SUB_BANDS_V1]; +} __packed; /* TX_REDUCED_POWER_API_S_VER_3 */ + +#define IWL_DEV_MAX_TX_POWER 0x7FFF + +/** + * struct iwl_dev_tx_power_cmd_v4 - TX power reduction command version 4 + * @per_chain: per chain restrictions + * @enable_ack_reduction: enable or disable close range ack TX power + * reduction. + * @reserved: reserved (padding) + */ +struct iwl_dev_tx_power_cmd_v4 { + __le16 per_chain[IWL_NUM_CHAIN_TABLES][IWL_NUM_CHAIN_LIMITS][IWL_NUM_SUB_BANDS_V1]; + u8 enable_ack_reduction; + u8 reserved[3]; +} __packed; /* TX_REDUCED_POWER_API_S_VER_4 */ + +/** + * struct iwl_dev_tx_power_cmd_v5 - TX power reduction command version 5 + * @per_chain: per chain restrictions + * @enable_ack_reduction: enable or disable close range ack TX power + * reduction. + * @per_chain_restriction_changed: is per_chain_restriction has changed + * from last command. used if set_mode is + * IWL_TX_POWER_MODE_SET_SAR_TIMER. + * note: if not changed, the command is used for keep alive only. + * @reserved: reserved (padding) + * @timer_period: timer in milliseconds. if expires FW will change to default + * BIOS values. relevant if setMode is IWL_TX_POWER_MODE_SET_SAR_TIMER + */ +struct iwl_dev_tx_power_cmd_v5 { + __le16 per_chain[IWL_NUM_CHAIN_TABLES][IWL_NUM_CHAIN_LIMITS][IWL_NUM_SUB_BANDS_V1]; + u8 enable_ack_reduction; + u8 per_chain_restriction_changed; + u8 reserved[2]; + __le32 timer_period; +} __packed; /* TX_REDUCED_POWER_API_S_VER_5 */ + +/** + * struct iwl_dev_tx_power_cmd_v6 - TX power reduction command version 6 + * @per_chain: per chain restrictions + * @enable_ack_reduction: enable or disable close range ack TX power + * reduction. + * @per_chain_restriction_changed: is per_chain_restriction has changed + * from last command. used if set_mode is + * IWL_TX_POWER_MODE_SET_SAR_TIMER. + * note: if not changed, the command is used for keep alive only. + * @reserved: reserved (padding) + * @timer_period: timer in milliseconds. if expires FW will change to default + * BIOS values. relevant if setMode is IWL_TX_POWER_MODE_SET_SAR_TIMER + */ +struct iwl_dev_tx_power_cmd_v6 { + __le16 per_chain[IWL_NUM_CHAIN_TABLES_V2][IWL_NUM_CHAIN_LIMITS][IWL_NUM_SUB_BANDS_V2]; + u8 enable_ack_reduction; + u8 per_chain_restriction_changed; + u8 reserved[2]; + __le32 timer_period; +} __packed; /* TX_REDUCED_POWER_API_S_VER_6 */ + +/** + * struct iwl_dev_tx_power_cmd_v7 - TX power reduction command version 7 + * @per_chain: per chain restrictions + * @enable_ack_reduction: enable or disable close range ack TX power + * reduction. + * @per_chain_restriction_changed: is per_chain_restriction has changed + * from last command. used if set_mode is + * IWL_TX_POWER_MODE_SET_SAR_TIMER. + * note: if not changed, the command is used for keep alive only. + * @reserved: reserved (padding) + * @timer_period: timer in milliseconds. if expires FW will change to default + * BIOS values. relevant if setMode is IWL_TX_POWER_MODE_SET_SAR_TIMER + * @flags: reduce power flags. + */ +struct iwl_dev_tx_power_cmd_v7 { + __le16 per_chain[IWL_NUM_CHAIN_TABLES_V2][IWL_NUM_CHAIN_LIMITS][IWL_NUM_SUB_BANDS_V2]; + u8 enable_ack_reduction; + u8 per_chain_restriction_changed; + u8 reserved[2]; + __le32 timer_period; + __le32 flags; +} __packed; /* TX_REDUCED_POWER_API_S_VER_7 */ +/** + * struct iwl_dev_tx_power_cmd - TX power reduction command (multiversion) + * @common: common part of the command + * @v3: version 3 part of the command + * @v4: version 4 part of the command + * @v5: version 5 part of the command + * @v6: version 6 part of the command + */ +struct iwl_dev_tx_power_cmd { + struct iwl_dev_tx_power_common common; + union { + struct iwl_dev_tx_power_cmd_v3 v3; + struct iwl_dev_tx_power_cmd_v4 v4; + struct iwl_dev_tx_power_cmd_v5 v5; + struct iwl_dev_tx_power_cmd_v6 v6; + struct iwl_dev_tx_power_cmd_v7 v7; + }; +}; + +#define IWL_NUM_GEO_PROFILES 3 +#define IWL_NUM_GEO_PROFILES_V3 8 +#define IWL_NUM_BANDS_PER_CHAIN_V1 2 +#define IWL_NUM_BANDS_PER_CHAIN_V2 3 + +/** + * enum iwl_geo_per_chain_offset_operation - type of operation + * @IWL_PER_CHAIN_OFFSET_SET_TABLES: send the tables from the host to the FW. + * @IWL_PER_CHAIN_OFFSET_GET_CURRENT_TABLE: retrieve the last configured table. + */ +enum iwl_geo_per_chain_offset_operation { + IWL_PER_CHAIN_OFFSET_SET_TABLES, + IWL_PER_CHAIN_OFFSET_GET_CURRENT_TABLE, +}; /* PER_CHAIN_OFFSET_OPERATION_E */ + +/** + * struct iwl_per_chain_offset - embedded struct for PER_CHAIN_LIMIT_OFFSET_CMD. + * @max_tx_power: maximum allowed tx power. + * @chain_a: tx power offset for chain a. + * @chain_b: tx power offset for chain b. + */ +struct iwl_per_chain_offset { + __le16 max_tx_power; + u8 chain_a; + u8 chain_b; +} __packed; /* PER_CHAIN_LIMIT_OFFSET_PER_CHAIN_S_VER_1 */ + +/** + * struct iwl_geo_tx_power_profile_cmd_v1 - struct for PER_CHAIN_LIMIT_OFFSET_CMD cmd. + * @ops: operations, value from &enum iwl_geo_per_chain_offset_operation + * @table: offset profile per band. + */ +struct iwl_geo_tx_power_profiles_cmd_v1 { + __le32 ops; + struct iwl_per_chain_offset table[IWL_NUM_GEO_PROFILES][IWL_NUM_BANDS_PER_CHAIN_V1]; +} __packed; /* PER_CHAIN_LIMIT_OFFSET_CMD_VER_1 */ + +/** + * struct iwl_geo_tx_power_profile_cmd_v2 - struct for PER_CHAIN_LIMIT_OFFSET_CMD cmd. + * @ops: operations, value from &enum iwl_geo_per_chain_offset_operation + * @table: offset profile per band. + * @table_revision: 0 for not-South Korea, 1 for South Korea (the name is misleading) + */ +struct iwl_geo_tx_power_profiles_cmd_v2 { + __le32 ops; + struct iwl_per_chain_offset table[IWL_NUM_GEO_PROFILES][IWL_NUM_BANDS_PER_CHAIN_V1]; + __le32 table_revision; +} __packed; /* PER_CHAIN_LIMIT_OFFSET_CMD_VER_2 */ + +/** + * struct iwl_geo_tx_power_profile_cmd_v3 - struct for PER_CHAIN_LIMIT_OFFSET_CMD cmd. + * @ops: operations, value from &enum iwl_geo_per_chain_offset_operation + * @table: offset profile per band. + * @table_revision: 0 for not-South Korea, 1 for South Korea (the name is misleading) + */ +struct iwl_geo_tx_power_profiles_cmd_v3 { + __le32 ops; + struct iwl_per_chain_offset table[IWL_NUM_GEO_PROFILES][IWL_NUM_BANDS_PER_CHAIN_V2]; + __le32 table_revision; +} __packed; /* PER_CHAIN_LIMIT_OFFSET_CMD_VER_3 */ + +/** + * struct iwl_geo_tx_power_profile_cmd_v4 - struct for PER_CHAIN_LIMIT_OFFSET_CMD cmd. + * @ops: operations, value from &enum iwl_geo_per_chain_offset_operation + * @table: offset profile per band. + * @table_revision: 0 for not-South Korea, 1 for South Korea (the name is misleading) + */ +struct iwl_geo_tx_power_profiles_cmd_v4 { + __le32 ops; + struct iwl_per_chain_offset table[IWL_NUM_GEO_PROFILES_V3][IWL_NUM_BANDS_PER_CHAIN_V1]; + __le32 table_revision; +} __packed; /* PER_CHAIN_LIMIT_OFFSET_CMD_VER_4 */ + +/** + * struct iwl_geo_tx_power_profile_cmd_v5 - struct for PER_CHAIN_LIMIT_OFFSET_CMD cmd. + * @ops: operations, value from &enum iwl_geo_per_chain_offset_operation + * @table: offset profile per band. + * @table_revision: 0 for not-South Korea, 1 for South Korea (the name is misleading) + */ +struct iwl_geo_tx_power_profiles_cmd_v5 { + __le32 ops; + struct iwl_per_chain_offset table[IWL_NUM_GEO_PROFILES_V3][IWL_NUM_BANDS_PER_CHAIN_V2]; + __le32 table_revision; +} __packed; /* PER_CHAIN_LIMIT_OFFSET_CMD_VER_5 */ + +union iwl_geo_tx_power_profiles_cmd { + struct iwl_geo_tx_power_profiles_cmd_v1 v1; + struct iwl_geo_tx_power_profiles_cmd_v2 v2; + struct iwl_geo_tx_power_profiles_cmd_v3 v3; + struct iwl_geo_tx_power_profiles_cmd_v4 v4; + struct iwl_geo_tx_power_profiles_cmd_v5 v5; +}; + +/** + * struct iwl_geo_tx_power_profiles_resp - response to PER_CHAIN_LIMIT_OFFSET_CMD cmd + * @profile_idx: current geo profile in use + */ +struct iwl_geo_tx_power_profiles_resp { + __le32 profile_idx; +} __packed; /* PER_CHAIN_LIMIT_OFFSET_RSP */ + +/** + * union iwl_ppag_table_cmd - union for all versions of PPAG command + * @v1: version 1 + * @v2: version 2 + * + * @flags: bit 0 - indicates enablement of PPAG for ETSI + * bit 1 - indicates enablement of PPAG for CHINA BIOS + * bit 1 can be used only in v3 (identical to v2) + * @gain: table of antenna gain values per chain and sub-band + * @reserved: reserved + */ +union iwl_ppag_table_cmd { + struct { + __le32 flags; + s8 gain[IWL_NUM_CHAIN_LIMITS][IWL_NUM_SUB_BANDS_V1]; + s8 reserved[2]; + } v1; + struct { + __le32 flags; + s8 gain[IWL_NUM_CHAIN_LIMITS][IWL_NUM_SUB_BANDS_V2]; + s8 reserved[2]; + } v2; +} __packed; + +#define MCC_TO_SAR_OFFSET_TABLE_ROW_SIZE 26 +#define MCC_TO_SAR_OFFSET_TABLE_COL_SIZE 13 + +/** + * struct iwl_sar_offset_mapping_cmd - struct for SAR_OFFSET_MAPPING_TABLE_CMD + * @offset_map: mapping a mcc to a geo sar group + * @reserved: reserved + */ +struct iwl_sar_offset_mapping_cmd { + u8 offset_map[MCC_TO_SAR_OFFSET_TABLE_ROW_SIZE] + [MCC_TO_SAR_OFFSET_TABLE_COL_SIZE]; + u16 reserved; +} __packed; /*SAR_OFFSET_MAPPING_TABLE_CMD_API_S*/ + +/** + * struct iwl_beacon_filter_cmd + * REPLY_BEACON_FILTERING_CMD = 0xd2 (command) + * @bf_energy_delta: Used for RSSI filtering, if in 'normal' state. Send beacon + * to driver if delta in Energy values calculated for this and last + * passed beacon is greater than this threshold. Zero value means that + * the Energy change is ignored for beacon filtering, and beacon will + * not be forced to be sent to driver regardless of this delta. Typical + * energy delta 5dB. + * @bf_roaming_energy_delta: Used for RSSI filtering, if in 'roaming' state. + * Send beacon to driver if delta in Energy values calculated for this + * and last passed beacon is greater than this threshold. Zero value + * means that the Energy change is ignored for beacon filtering while in + * Roaming state, typical energy delta 1dB. + * @bf_roaming_state: Used for RSSI filtering. If absolute Energy values + * calculated for current beacon is less than the threshold, use + * Roaming Energy Delta Threshold, otherwise use normal Energy Delta + * Threshold. Typical energy threshold is -72dBm. + * @bf_temp_threshold: This threshold determines the type of temperature + * filtering (Slow or Fast) that is selected (Units are in Celsuis): + * If the current temperature is above this threshold - Fast filter + * will be used, If the current temperature is below this threshold - + * Slow filter will be used. + * @bf_temp_fast_filter: Send Beacon to driver if delta in temperature values + * calculated for this and the last passed beacon is greater than this + * threshold. Zero value means that the temperature change is ignored for + * beacon filtering; beacons will not be forced to be sent to driver + * regardless of whether its temerature has been changed. + * @bf_temp_slow_filter: Send Beacon to driver if delta in temperature values + * calculated for this and the last passed beacon is greater than this + * threshold. Zero value means that the temperature change is ignored for + * beacon filtering; beacons will not be forced to be sent to driver + * regardless of whether its temerature has been changed. + * @bf_enable_beacon_filter: 1, beacon filtering is enabled; 0, disabled. + * @bf_debug_flag: beacon filtering debug configuration + * @bf_escape_timer: Send beacons to to driver if no beacons were passed + * for a specific period of time. Units: Beacons. + * @ba_escape_timer: Fully receive and parse beacon if no beacons were passed + * for a longer period of time then this escape-timeout. Units: Beacons. + * @ba_enable_beacon_abort: 1, beacon abort is enabled; 0, disabled. + * @bf_threshold_absolute_low: See below. + * @bf_threshold_absolute_high: Send Beacon to driver if Energy value calculated + * for this beacon crossed this absolute threshold. For the 'Increase' + * direction the bf_energy_absolute_low[i] is used. For the 'Decrease' + * direction the bf_energy_absolute_high[i] is used. Zero value means + * that this specific threshold is ignored for beacon filtering, and + * beacon will not be forced to be sent to driver due to this setting. + */ +struct iwl_beacon_filter_cmd { + __le32 bf_energy_delta; + __le32 bf_roaming_energy_delta; + __le32 bf_roaming_state; + __le32 bf_temp_threshold; + __le32 bf_temp_fast_filter; + __le32 bf_temp_slow_filter; + __le32 bf_enable_beacon_filter; + __le32 bf_debug_flag; + __le32 bf_escape_timer; + __le32 ba_escape_timer; + __le32 ba_enable_beacon_abort; + __le32 bf_threshold_absolute_low[2]; + __le32 bf_threshold_absolute_high[2]; +} __packed; /* BEACON_FILTER_CONFIG_API_S_VER_4 */ + +/* Beacon filtering and beacon abort */ +#define IWL_BF_ENERGY_DELTA_DEFAULT 5 +#define IWL_BF_ENERGY_DELTA_D0I3 20 +#define IWL_BF_ENERGY_DELTA_MAX 255 +#define IWL_BF_ENERGY_DELTA_MIN 0 + +#define IWL_BF_ROAMING_ENERGY_DELTA_DEFAULT 1 +#define IWL_BF_ROAMING_ENERGY_DELTA_D0I3 20 +#define IWL_BF_ROAMING_ENERGY_DELTA_MAX 255 +#define IWL_BF_ROAMING_ENERGY_DELTA_MIN 0 + +#define IWL_BF_ROAMING_STATE_DEFAULT 72 +#define IWL_BF_ROAMING_STATE_D0I3 72 +#define IWL_BF_ROAMING_STATE_MAX 255 +#define IWL_BF_ROAMING_STATE_MIN 0 + +#define IWL_BF_TEMP_THRESHOLD_DEFAULT 112 +#define IWL_BF_TEMP_THRESHOLD_D0I3 112 +#define IWL_BF_TEMP_THRESHOLD_MAX 255 +#define IWL_BF_TEMP_THRESHOLD_MIN 0 + +#define IWL_BF_TEMP_FAST_FILTER_DEFAULT 1 +#define IWL_BF_TEMP_FAST_FILTER_D0I3 1 +#define IWL_BF_TEMP_FAST_FILTER_MAX 255 +#define IWL_BF_TEMP_FAST_FILTER_MIN 0 + +#define IWL_BF_TEMP_SLOW_FILTER_DEFAULT 5 +#define IWL_BF_TEMP_SLOW_FILTER_D0I3 20 +#define IWL_BF_TEMP_SLOW_FILTER_MAX 255 +#define IWL_BF_TEMP_SLOW_FILTER_MIN 0 + +#define IWL_BF_ENABLE_BEACON_FILTER_DEFAULT 1 + +#define IWL_BF_DEBUG_FLAG_DEFAULT 0 +#define IWL_BF_DEBUG_FLAG_D0I3 0 + +#define IWL_BF_ESCAPE_TIMER_DEFAULT 0 +#define IWL_BF_ESCAPE_TIMER_D0I3 0 +#define IWL_BF_ESCAPE_TIMER_MAX 1024 +#define IWL_BF_ESCAPE_TIMER_MIN 0 + +#define IWL_BA_ESCAPE_TIMER_DEFAULT 6 +#define IWL_BA_ESCAPE_TIMER_D0I3 6 +#define IWL_BA_ESCAPE_TIMER_D3 9 +#define IWL_BA_ESCAPE_TIMER_MAX 1024 +#define IWL_BA_ESCAPE_TIMER_MIN 0 + +#define IWL_BA_ENABLE_BEACON_ABORT_DEFAULT 1 + +#define IWL_BF_CMD_CONFIG(mode) \ + .bf_energy_delta = cpu_to_le32(IWL_BF_ENERGY_DELTA ## mode), \ + .bf_roaming_energy_delta = \ + cpu_to_le32(IWL_BF_ROAMING_ENERGY_DELTA ## mode), \ + .bf_roaming_state = cpu_to_le32(IWL_BF_ROAMING_STATE ## mode), \ + .bf_temp_threshold = cpu_to_le32(IWL_BF_TEMP_THRESHOLD ## mode), \ + .bf_temp_fast_filter = cpu_to_le32(IWL_BF_TEMP_FAST_FILTER ## mode), \ + .bf_temp_slow_filter = cpu_to_le32(IWL_BF_TEMP_SLOW_FILTER ## mode), \ + .bf_debug_flag = cpu_to_le32(IWL_BF_DEBUG_FLAG ## mode), \ + .bf_escape_timer = cpu_to_le32(IWL_BF_ESCAPE_TIMER ## mode), \ + .ba_escape_timer = cpu_to_le32(IWL_BA_ESCAPE_TIMER ## mode) + +#define IWL_BF_CMD_CONFIG_DEFAULTS IWL_BF_CMD_CONFIG(_DEFAULT) +#define IWL_BF_CMD_CONFIG_D0I3 IWL_BF_CMD_CONFIG(_D0I3) +#endif /* __iwl_fw_api_power_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/rfi.h b/drivers/net/wireless/intel/iwlwifi/fw/api/rfi.h new file mode 100644 index 000000000..1a84a4081 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/rfi.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2020-2021 Intel Corporation + */ +#ifndef __iwl_fw_api_rfi_h__ +#define __iwl_fw_api_rfi_h__ + +#define IWL_RFI_LUT_ENTRY_CHANNELS_NUM 15 +#define IWL_RFI_LUT_SIZE 24 +#define IWL_RFI_LUT_INSTALLED_SIZE 4 + +/** + * struct iwl_rfi_lut_entry - an entry in the RFI frequency LUT. + * + * @freq: frequency + * @channels: channels that can be interfered at frequency freq (at most 15) + * @bands: the corresponding bands + */ +struct iwl_rfi_lut_entry { + __le16 freq; + u8 channels[IWL_RFI_LUT_ENTRY_CHANNELS_NUM]; + u8 bands[IWL_RFI_LUT_ENTRY_CHANNELS_NUM]; +} __packed; + +/** + * struct iwl_rfi_config_cmd - RFI configuration table + * + * @entry: a table can have 24 frequency/channel mappings + * @oem: specifies if this is the default table or set by OEM + */ +struct iwl_rfi_config_cmd { + struct iwl_rfi_lut_entry table[IWL_RFI_LUT_SIZE]; + u8 oem; + u8 reserved[3]; +} __packed; /* RFI_CONFIG_CMD_API_S_VER_1 */ + +/** + * iwl_rfi_freq_table_status - status of the frequency table query + * @RFI_FREQ_TABLE_OK: can be used + * @RFI_FREQ_TABLE_DVFS_NOT_READY: DVFS is not ready yet, should try later + * @RFI_FREQ_TABLE_DISABLED: the feature is disabled in FW + */ +enum iwl_rfi_freq_table_status { + RFI_FREQ_TABLE_OK, + RFI_FREQ_TABLE_DVFS_NOT_READY, + RFI_FREQ_TABLE_DISABLED, +}; + +/** + * struct iwl_rfi_freq_table_resp_cmd - get the rfi freq table used by FW + * + * @table: table used by FW + * @status: see &iwl_rfi_freq_table_status + */ +struct iwl_rfi_freq_table_resp_cmd { + struct iwl_rfi_lut_entry table[IWL_RFI_LUT_INSTALLED_SIZE]; + __le32 status; +} __packed; /* RFI_CONFIG_CMD_API_S_VER_1 */ + +/** + * struct iwl_rfi_deactivate_notif - notifcation that FW disaled RFIm + * + * @reason: used only for a log message + */ +struct iwl_rfi_deactivate_notif { + __le32 reason; +} __packed; /* RFI_DEACTIVATE_NTF_S_VER_1 */ +#endif /* __iwl_fw_api_rfi_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/rs.h b/drivers/net/wireless/intel/iwlwifi/fw/api/rs.h new file mode 100644 index 000000000..687f804c4 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/rs.h @@ -0,0 +1,761 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2022 Intel Corporation + * Copyright (C) 2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_rs_h__ +#define __iwl_fw_api_rs_h__ + +#include "mac.h" + +/** + * enum iwl_tlc_mng_cfg_flags_enum - options for TLC config flags + * @IWL_TLC_MNG_CFG_FLAGS_STBC_MSK: enable STBC. For HE this enables STBC for + * bandwidths <= 80MHz + * @IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK: enable LDPC + * @IWL_TLC_MNG_CFG_FLAGS_HE_STBC_160MHZ_MSK: enable STBC in HE at 160MHz + * bandwidth + * @IWL_TLC_MNG_CFG_FLAGS_HE_DCM_NSS_1_MSK: enable HE Dual Carrier Modulation + * for BPSK (MCS 0) with 1 spatial + * stream + * @IWL_TLC_MNG_CFG_FLAGS_HE_DCM_NSS_2_MSK: enable HE Dual Carrier Modulation + * for BPSK (MCS 0) with 2 spatial + * streams + */ +enum iwl_tlc_mng_cfg_flags { + IWL_TLC_MNG_CFG_FLAGS_STBC_MSK = BIT(0), + IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK = BIT(1), + IWL_TLC_MNG_CFG_FLAGS_HE_STBC_160MHZ_MSK = BIT(2), + IWL_TLC_MNG_CFG_FLAGS_HE_DCM_NSS_1_MSK = BIT(3), + IWL_TLC_MNG_CFG_FLAGS_HE_DCM_NSS_2_MSK = BIT(4), +}; + +/** + * enum iwl_tlc_mng_cfg_cw - channel width options + * @IWL_TLC_MNG_CH_WIDTH_20MHZ: 20MHZ channel + * @IWL_TLC_MNG_CH_WIDTH_40MHZ: 40MHZ channel + * @IWL_TLC_MNG_CH_WIDTH_80MHZ: 80MHZ channel + * @IWL_TLC_MNG_CH_WIDTH_160MHZ: 160MHZ channel + * @IWL_TLC_MNG_CH_WIDTH_LAST: maximum value + */ +enum iwl_tlc_mng_cfg_cw { + IWL_TLC_MNG_CH_WIDTH_20MHZ, + IWL_TLC_MNG_CH_WIDTH_40MHZ, + IWL_TLC_MNG_CH_WIDTH_80MHZ, + IWL_TLC_MNG_CH_WIDTH_160MHZ, + IWL_TLC_MNG_CH_WIDTH_LAST = IWL_TLC_MNG_CH_WIDTH_160MHZ, +}; + +/** + * enum iwl_tlc_mng_cfg_chains - possible chains + * @IWL_TLC_MNG_CHAIN_A_MSK: chain A + * @IWL_TLC_MNG_CHAIN_B_MSK: chain B + */ +enum iwl_tlc_mng_cfg_chains { + IWL_TLC_MNG_CHAIN_A_MSK = BIT(0), + IWL_TLC_MNG_CHAIN_B_MSK = BIT(1), +}; + +/** + * enum iwl_tlc_mng_cfg_mode - supported modes + * @IWL_TLC_MNG_MODE_CCK: enable CCK + * @IWL_TLC_MNG_MODE_OFDM_NON_HT: enable OFDM (non HT) + * @IWL_TLC_MNG_MODE_NON_HT: enable non HT + * @IWL_TLC_MNG_MODE_HT: enable HT + * @IWL_TLC_MNG_MODE_VHT: enable VHT + * @IWL_TLC_MNG_MODE_HE: enable HE + * @IWL_TLC_MNG_MODE_INVALID: invalid value + * @IWL_TLC_MNG_MODE_NUM: a count of possible modes + */ +enum iwl_tlc_mng_cfg_mode { + IWL_TLC_MNG_MODE_CCK = 0, + IWL_TLC_MNG_MODE_OFDM_NON_HT = IWL_TLC_MNG_MODE_CCK, + IWL_TLC_MNG_MODE_NON_HT = IWL_TLC_MNG_MODE_CCK, + IWL_TLC_MNG_MODE_HT, + IWL_TLC_MNG_MODE_VHT, + IWL_TLC_MNG_MODE_HE, + IWL_TLC_MNG_MODE_INVALID, + IWL_TLC_MNG_MODE_NUM = IWL_TLC_MNG_MODE_INVALID, +}; + +/** + * enum iwl_tlc_mng_ht_rates - HT/VHT/HE rates + * @IWL_TLC_MNG_HT_RATE_MCS0: index of MCS0 + * @IWL_TLC_MNG_HT_RATE_MCS1: index of MCS1 + * @IWL_TLC_MNG_HT_RATE_MCS2: index of MCS2 + * @IWL_TLC_MNG_HT_RATE_MCS3: index of MCS3 + * @IWL_TLC_MNG_HT_RATE_MCS4: index of MCS4 + * @IWL_TLC_MNG_HT_RATE_MCS5: index of MCS5 + * @IWL_TLC_MNG_HT_RATE_MCS6: index of MCS6 + * @IWL_TLC_MNG_HT_RATE_MCS7: index of MCS7 + * @IWL_TLC_MNG_HT_RATE_MCS8: index of MCS8 + * @IWL_TLC_MNG_HT_RATE_MCS9: index of MCS9 + * @IWL_TLC_MNG_HT_RATE_MCS10: index of MCS10 + * @IWL_TLC_MNG_HT_RATE_MCS11: index of MCS11 + * @IWL_TLC_MNG_HT_RATE_MAX: maximal rate for HT/VHT + */ +enum iwl_tlc_mng_ht_rates { + IWL_TLC_MNG_HT_RATE_MCS0 = 0, + IWL_TLC_MNG_HT_RATE_MCS1, + IWL_TLC_MNG_HT_RATE_MCS2, + IWL_TLC_MNG_HT_RATE_MCS3, + IWL_TLC_MNG_HT_RATE_MCS4, + IWL_TLC_MNG_HT_RATE_MCS5, + IWL_TLC_MNG_HT_RATE_MCS6, + IWL_TLC_MNG_HT_RATE_MCS7, + IWL_TLC_MNG_HT_RATE_MCS8, + IWL_TLC_MNG_HT_RATE_MCS9, + IWL_TLC_MNG_HT_RATE_MCS10, + IWL_TLC_MNG_HT_RATE_MCS11, + IWL_TLC_MNG_HT_RATE_MAX = IWL_TLC_MNG_HT_RATE_MCS11, +}; + +enum IWL_TLC_MNG_NSS { + IWL_TLC_NSS_1, + IWL_TLC_NSS_2, + IWL_TLC_NSS_MAX +}; + +/** + * enum IWL_TLC_MCS_PER_BW - mcs index per BW + * @IWL_TLC_MCS_PER_BW_80: mcs for bw - 20Hhz, 40Hhz, 80Hhz + * @IWL_TLC_MCS_PER_BW_160: mcs for bw - 160Mhz + * @IWL_TLC_MCS_PER_BW_320: mcs for bw - 320Mhz + * @IWL_TLC_MCS_PER_BW_NUM_V3: number of entries up to version 3 + * @IWL_TLC_MCS_PER_BW_NUM_V4: number of entries from version 4 + */ +enum IWL_TLC_MCS_PER_BW { + IWL_TLC_MCS_PER_BW_80, + IWL_TLC_MCS_PER_BW_160, + IWL_TLC_MCS_PER_BW_320, + IWL_TLC_MCS_PER_BW_NUM_V3 = IWL_TLC_MCS_PER_BW_160 + 1, + IWL_TLC_MCS_PER_BW_NUM_V4 = IWL_TLC_MCS_PER_BW_320 + 1, +}; + +/** + * struct iwl_tlc_config_cmd_v3 - TLC configuration + * @sta_id: station id + * @reserved1: reserved + * @max_ch_width: max supported channel width from @enum iwl_tlc_mng_cfg_cw + * @mode: &enum iwl_tlc_mng_cfg_mode + * @chains: bitmask of &enum iwl_tlc_mng_cfg_chains + * @amsdu: TX amsdu is supported + * @flags: bitmask of &enum iwl_tlc_mng_cfg_flags + * @non_ht_rates: bitmap of supported legacy rates + * @ht_rates: bitmap of &enum iwl_tlc_mng_ht_rates, per &enum IWL_TLC_MCS_PER_BW + * <nss, channel-width> pair (0 - 80mhz width and below, 1 - 160mhz). + * @max_mpdu_len: max MPDU length, in bytes + * @sgi_ch_width_supp: bitmap of SGI support per channel width + * use BIT(@enum iwl_tlc_mng_cfg_cw) + * @reserved2: reserved + * @max_tx_op: max TXOP in uSecs for all AC (BK, BE, VO, VI), + * set zero for no limit. + */ +struct iwl_tlc_config_cmd_v3 { + u8 sta_id; + u8 reserved1[3]; + u8 max_ch_width; + u8 mode; + u8 chains; + u8 amsdu; + __le16 flags; + __le16 non_ht_rates; + __le16 ht_rates[IWL_TLC_NSS_MAX][IWL_TLC_MCS_PER_BW_NUM_V3]; + __le16 max_mpdu_len; + u8 sgi_ch_width_supp; + u8 reserved2; + __le32 max_tx_op; +} __packed; /* TLC_MNG_CONFIG_CMD_API_S_VER_3 */ + +/** + * struct iwl_tlc_config_cmd_v4 - TLC configuration + * @sta_id: station id + * @reserved1: reserved + * @max_ch_width: max supported channel width from &enum iwl_tlc_mng_cfg_cw + * @mode: &enum iwl_tlc_mng_cfg_mode + * @chains: bitmask of &enum iwl_tlc_mng_cfg_chains + * @sgi_ch_width_supp: bitmap of SGI support per channel width + * use BIT(&enum iwl_tlc_mng_cfg_cw) + * @flags: bitmask of &enum iwl_tlc_mng_cfg_flags + * @non_ht_rates: bitmap of supported legacy rates + * @ht_rates: bitmap of &enum iwl_tlc_mng_ht_rates, per <nss, channel-width> + * pair (0 - 80mhz width and below, 1 - 160mhz, 2 - 320mhz). + * @max_mpdu_len: max MPDU length, in bytes + * @max_tx_op: max TXOP in uSecs for all AC (BK, BE, VO, VI), + * set zero for no limit. + */ +struct iwl_tlc_config_cmd_v4 { + u8 sta_id; + u8 reserved1[3]; + u8 max_ch_width; + u8 mode; + u8 chains; + u8 sgi_ch_width_supp; + __le16 flags; + __le16 non_ht_rates; + __le16 ht_rates[IWL_TLC_NSS_MAX][IWL_TLC_MCS_PER_BW_NUM_V4]; + __le16 max_mpdu_len; + __le16 max_tx_op; +} __packed; /* TLC_MNG_CONFIG_CMD_API_S_VER_4 */ + +/** + * enum iwl_tlc_update_flags - updated fields + * @IWL_TLC_NOTIF_FLAG_RATE: last initial rate update + * @IWL_TLC_NOTIF_FLAG_AMSDU: umsdu parameters update + */ +enum iwl_tlc_update_flags { + IWL_TLC_NOTIF_FLAG_RATE = BIT(0), + IWL_TLC_NOTIF_FLAG_AMSDU = BIT(1), +}; + +/** + * struct iwl_tlc_update_notif - TLC notification from FW + * @sta_id: station id + * @reserved: reserved + * @flags: bitmap of notifications reported + * @rate: current initial rate + * @amsdu_size: Max AMSDU size, in bytes + * @amsdu_enabled: bitmap for per-TID AMSDU enablement + */ +struct iwl_tlc_update_notif { + u8 sta_id; + u8 reserved[3]; + __le32 flags; + __le32 rate; + __le32 amsdu_size; + __le32 amsdu_enabled; +} __packed; /* TLC_MNG_UPDATE_NTFY_API_S_VER_2 */ + + +#define IWL_MAX_MCS_DISPLAY_SIZE 12 + +struct iwl_rate_mcs_info { + char mbps[IWL_MAX_MCS_DISPLAY_SIZE]; + char mcs[IWL_MAX_MCS_DISPLAY_SIZE]; +}; + +/* + * These serve as indexes into + * struct iwl_rate_info fw_rate_idx_to_plcp[IWL_RATE_COUNT]; + * TODO: avoid overlap between legacy and HT rates + */ +enum { + IWL_RATE_1M_INDEX = 0, + IWL_FIRST_CCK_RATE = IWL_RATE_1M_INDEX, + IWL_RATE_2M_INDEX, + IWL_RATE_5M_INDEX, + IWL_RATE_11M_INDEX, + IWL_LAST_CCK_RATE = IWL_RATE_11M_INDEX, + IWL_RATE_6M_INDEX, + IWL_FIRST_OFDM_RATE = IWL_RATE_6M_INDEX, + IWL_RATE_MCS_0_INDEX = IWL_RATE_6M_INDEX, + IWL_FIRST_HT_RATE = IWL_RATE_MCS_0_INDEX, + IWL_FIRST_VHT_RATE = IWL_RATE_MCS_0_INDEX, + IWL_RATE_9M_INDEX, + IWL_RATE_12M_INDEX, + IWL_RATE_MCS_1_INDEX = IWL_RATE_12M_INDEX, + IWL_RATE_18M_INDEX, + IWL_RATE_MCS_2_INDEX = IWL_RATE_18M_INDEX, + IWL_RATE_24M_INDEX, + IWL_RATE_MCS_3_INDEX = IWL_RATE_24M_INDEX, + IWL_RATE_36M_INDEX, + IWL_RATE_MCS_4_INDEX = IWL_RATE_36M_INDEX, + IWL_RATE_48M_INDEX, + IWL_RATE_MCS_5_INDEX = IWL_RATE_48M_INDEX, + IWL_RATE_54M_INDEX, + IWL_RATE_MCS_6_INDEX = IWL_RATE_54M_INDEX, + IWL_LAST_NON_HT_RATE = IWL_RATE_54M_INDEX, + IWL_RATE_60M_INDEX, + IWL_RATE_MCS_7_INDEX = IWL_RATE_60M_INDEX, + IWL_LAST_HT_RATE = IWL_RATE_MCS_7_INDEX, + IWL_RATE_MCS_8_INDEX, + IWL_RATE_MCS_9_INDEX, + IWL_LAST_VHT_RATE = IWL_RATE_MCS_9_INDEX, + IWL_RATE_MCS_10_INDEX, + IWL_RATE_MCS_11_INDEX, + IWL_LAST_HE_RATE = IWL_RATE_MCS_11_INDEX, + IWL_RATE_COUNT_LEGACY = IWL_LAST_NON_HT_RATE + 1, + IWL_RATE_COUNT = IWL_LAST_HE_RATE + 1, + IWL_RATE_INVM_INDEX = IWL_RATE_COUNT, + IWL_RATE_INVALID = IWL_RATE_COUNT, +}; + +#define IWL_RATE_BIT_MSK(r) BIT(IWL_RATE_##r##M_INDEX) + +/* fw API values for legacy bit rates, both OFDM and CCK */ +enum { + IWL_RATE_6M_PLCP = 13, + IWL_RATE_9M_PLCP = 15, + IWL_RATE_12M_PLCP = 5, + IWL_RATE_18M_PLCP = 7, + IWL_RATE_24M_PLCP = 9, + IWL_RATE_36M_PLCP = 11, + IWL_RATE_48M_PLCP = 1, + IWL_RATE_54M_PLCP = 3, + IWL_RATE_1M_PLCP = 10, + IWL_RATE_2M_PLCP = 20, + IWL_RATE_5M_PLCP = 55, + IWL_RATE_11M_PLCP = 110, + IWL_RATE_INVM_PLCP = -1, +}; + +/* + * rate_n_flags bit fields version 1 + * + * The 32-bit value has different layouts in the low 8 bites depending on the + * format. There are three formats, HT, VHT and legacy (11abg, with subformats + * for CCK and OFDM). + * + * High-throughput (HT) rate format + * bit 8 is 1, bit 26 is 0, bit 9 is 0 (OFDM) + * Very High-throughput (VHT) rate format + * bit 8 is 0, bit 26 is 1, bit 9 is 0 (OFDM) + * Legacy OFDM rate format for bits 7:0 + * bit 8 is 0, bit 26 is 0, bit 9 is 0 (OFDM) + * Legacy CCK rate format for bits 7:0: + * bit 8 is 0, bit 26 is 0, bit 9 is 1 (CCK) + */ + +/* Bit 8: (1) HT format, (0) legacy or VHT format */ +#define RATE_MCS_HT_POS 8 +#define RATE_MCS_HT_MSK_V1 BIT(RATE_MCS_HT_POS) + +/* Bit 9: (1) CCK, (0) OFDM. HT (bit 8) must be "0" for this bit to be valid */ +#define RATE_MCS_CCK_POS_V1 9 +#define RATE_MCS_CCK_MSK_V1 BIT(RATE_MCS_CCK_POS_V1) + +/* Bit 26: (1) VHT format, (0) legacy format in bits 8:0 */ +#define RATE_MCS_VHT_POS_V1 26 +#define RATE_MCS_VHT_MSK_V1 BIT(RATE_MCS_VHT_POS_V1) + + +/* + * High-throughput (HT) rate format for bits 7:0 + * + * 2-0: MCS rate base + * 0) 6 Mbps + * 1) 12 Mbps + * 2) 18 Mbps + * 3) 24 Mbps + * 4) 36 Mbps + * 5) 48 Mbps + * 6) 54 Mbps + * 7) 60 Mbps + * 4-3: 0) Single stream (SISO) + * 1) Dual stream (MIMO) + * 2) Triple stream (MIMO) + * 5: Value of 0x20 in bits 7:0 indicates 6 Mbps HT40 duplicate data + * (bits 7-6 are zero) + * + * Together the low 5 bits work out to the MCS index because we don't + * support MCSes above 15/23, and 0-7 have one stream, 8-15 have two + * streams and 16-23 have three streams. We could also support MCS 32 + * which is the duplicate 20 MHz MCS (bit 5 set, all others zero.) + */ +#define RATE_HT_MCS_RATE_CODE_MSK_V1 0x7 +#define RATE_HT_MCS_NSS_POS_V1 3 +#define RATE_HT_MCS_NSS_MSK_V1 (3 << RATE_HT_MCS_NSS_POS_V1) +#define RATE_HT_MCS_MIMO2_MSK BIT(RATE_HT_MCS_NSS_POS_V1) + +/* Bit 10: (1) Use Green Field preamble */ +#define RATE_HT_MCS_GF_POS 10 +#define RATE_HT_MCS_GF_MSK (1 << RATE_HT_MCS_GF_POS) + +#define RATE_HT_MCS_INDEX_MSK_V1 0x3f + +/* + * Very High-throughput (VHT) rate format for bits 7:0 + * + * 3-0: VHT MCS (0-9) + * 5-4: number of streams - 1: + * 0) Single stream (SISO) + * 1) Dual stream (MIMO) + * 2) Triple stream (MIMO) + */ + +/* Bit 4-5: (0) SISO, (1) MIMO2 (2) MIMO3 */ +#define RATE_VHT_MCS_RATE_CODE_MSK 0xf +#define RATE_VHT_MCS_NSS_POS 4 +#define RATE_VHT_MCS_NSS_MSK (3 << RATE_VHT_MCS_NSS_POS) +#define RATE_VHT_MCS_MIMO2_MSK BIT(RATE_VHT_MCS_NSS_POS) + +/* + * Legacy OFDM rate format for bits 7:0 + * + * 3-0: 0xD) 6 Mbps + * 0xF) 9 Mbps + * 0x5) 12 Mbps + * 0x7) 18 Mbps + * 0x9) 24 Mbps + * 0xB) 36 Mbps + * 0x1) 48 Mbps + * 0x3) 54 Mbps + * (bits 7-4 are 0) + * + * Legacy CCK rate format for bits 7:0: + * bit 8 is 0, bit 26 is 0, bit 9 is 1 (CCK): + * + * 6-0: 10) 1 Mbps + * 20) 2 Mbps + * 55) 5.5 Mbps + * 110) 11 Mbps + * (bit 7 is 0) + */ +#define RATE_LEGACY_RATE_MSK_V1 0xff + +/* Bit 10 - OFDM HE */ +#define RATE_MCS_HE_POS_V1 10 +#define RATE_MCS_HE_MSK_V1 BIT(RATE_MCS_HE_POS_V1) + +/* + * Bit 11-12: (0) 20MHz, (1) 40MHz, (2) 80MHz, (3) 160MHz + * 0 and 1 are valid for HT and VHT, 2 and 3 only for VHT + */ +#define RATE_MCS_CHAN_WIDTH_POS 11 +#define RATE_MCS_CHAN_WIDTH_MSK_V1 (3 << RATE_MCS_CHAN_WIDTH_POS) + +/* Bit 13: (1) Short guard interval (0.4 usec), (0) normal GI (0.8 usec) */ +#define RATE_MCS_SGI_POS_V1 13 +#define RATE_MCS_SGI_MSK_V1 BIT(RATE_MCS_SGI_POS_V1) + +/* Bit 14-16: Antenna selection (1) Ant A, (2) Ant B, (4) Ant C */ +#define RATE_MCS_ANT_POS 14 +#define RATE_MCS_ANT_A_MSK (1 << RATE_MCS_ANT_POS) +#define RATE_MCS_ANT_B_MSK (2 << RATE_MCS_ANT_POS) +#define RATE_MCS_ANT_AB_MSK (RATE_MCS_ANT_A_MSK | \ + RATE_MCS_ANT_B_MSK) +#define RATE_MCS_ANT_MSK RATE_MCS_ANT_AB_MSK + +/* Bit 17: (0) SS, (1) SS*2 */ +#define RATE_MCS_STBC_POS 17 +#define RATE_MCS_STBC_MSK BIT(RATE_MCS_STBC_POS) + +/* Bit 18: OFDM-HE dual carrier mode */ +#define RATE_HE_DUAL_CARRIER_MODE 18 +#define RATE_HE_DUAL_CARRIER_MODE_MSK BIT(RATE_HE_DUAL_CARRIER_MODE) + +/* Bit 19: (0) Beamforming is off, (1) Beamforming is on */ +#define RATE_MCS_BF_POS 19 +#define RATE_MCS_BF_MSK (1 << RATE_MCS_BF_POS) + +/* + * Bit 20-21: HE LTF type and guard interval + * HE (ext) SU: + * 0 1xLTF+0.8us + * 1 2xLTF+0.8us + * 2 2xLTF+1.6us + * 3 & SGI (bit 13) clear 4xLTF+3.2us + * 3 & SGI (bit 13) set 4xLTF+0.8us + * HE MU: + * 0 4xLTF+0.8us + * 1 2xLTF+0.8us + * 2 2xLTF+1.6us + * 3 4xLTF+3.2us + * HE TRIG: + * 0 1xLTF+1.6us + * 1 2xLTF+1.6us + * 2 4xLTF+3.2us + * 3 (does not occur) + */ +#define RATE_MCS_HE_GI_LTF_POS 20 +#define RATE_MCS_HE_GI_LTF_MSK_V1 (3 << RATE_MCS_HE_GI_LTF_POS) + +/* Bit 22-23: HE type. (0) SU, (1) SU_EXT, (2) MU, (3) trigger based */ +#define RATE_MCS_HE_TYPE_POS_V1 22 +#define RATE_MCS_HE_TYPE_SU_V1 (0 << RATE_MCS_HE_TYPE_POS_V1) +#define RATE_MCS_HE_TYPE_EXT_SU_V1 BIT(RATE_MCS_HE_TYPE_POS_V1) +#define RATE_MCS_HE_TYPE_MU_V1 (2 << RATE_MCS_HE_TYPE_POS_V1) +#define RATE_MCS_HE_TYPE_TRIG_V1 (3 << RATE_MCS_HE_TYPE_POS_V1) +#define RATE_MCS_HE_TYPE_MSK_V1 (3 << RATE_MCS_HE_TYPE_POS_V1) + +/* Bit 24-25: (0) 20MHz (no dup), (1) 2x20MHz, (2) 4x20MHz, 3 8x20MHz */ +#define RATE_MCS_DUP_POS_V1 24 +#define RATE_MCS_DUP_MSK_V1 (3 << RATE_MCS_DUP_POS_V1) + +/* Bit 27: (1) LDPC enabled, (0) LDPC disabled */ +#define RATE_MCS_LDPC_POS_V1 27 +#define RATE_MCS_LDPC_MSK_V1 BIT(RATE_MCS_LDPC_POS_V1) + +/* Bit 28: (1) 106-tone RX (8 MHz RU), (0) normal bandwidth */ +#define RATE_MCS_HE_106T_POS_V1 28 +#define RATE_MCS_HE_106T_MSK_V1 BIT(RATE_MCS_HE_106T_POS_V1) + +/* Bit 30-31: (1) RTS, (2) CTS */ +#define RATE_MCS_RTS_REQUIRED_POS (30) +#define RATE_MCS_RTS_REQUIRED_MSK (0x1 << RATE_MCS_RTS_REQUIRED_POS) + +#define RATE_MCS_CTS_REQUIRED_POS (31) +#define RATE_MCS_CTS_REQUIRED_MSK (0x1 << RATE_MCS_CTS_REQUIRED_POS) + +/* rate_n_flags bit field version 2 + * + * The 32-bit value has different layouts in the low 8 bits depending on the + * format. There are three formats, HT, VHT and legacy (11abg, with subformats + * for CCK and OFDM). + * + */ + +/* Bits 10-8: rate format + * (0) Legacy CCK (1) Legacy OFDM (2) High-throughput (HT) + * (3) Very High-throughput (VHT) (4) High-efficiency (HE) + * (5) Extremely High-throughput (EHT) + */ +#define RATE_MCS_MOD_TYPE_POS 8 +#define RATE_MCS_MOD_TYPE_MSK (0x7 << RATE_MCS_MOD_TYPE_POS) +#define RATE_MCS_CCK_MSK (0 << RATE_MCS_MOD_TYPE_POS) +#define RATE_MCS_LEGACY_OFDM_MSK (1 << RATE_MCS_MOD_TYPE_POS) +#define RATE_MCS_HT_MSK (2 << RATE_MCS_MOD_TYPE_POS) +#define RATE_MCS_VHT_MSK (3 << RATE_MCS_MOD_TYPE_POS) +#define RATE_MCS_HE_MSK (4 << RATE_MCS_MOD_TYPE_POS) +#define RATE_MCS_EHT_MSK (5 << RATE_MCS_MOD_TYPE_POS) + +/* + * Legacy CCK rate format for bits 0:3: + * + * (0) 0xa - 1 Mbps + * (1) 0x14 - 2 Mbps + * (2) 0x37 - 5.5 Mbps + * (3) 0x6e - 11 nbps + * + * Legacy OFDM rate format for bis 3:0: + * + * (0) 6 Mbps + * (1) 9 Mbps + * (2) 12 Mbps + * (3) 18 Mbps + * (4) 24 Mbps + * (5) 36 Mbps + * (6) 48 Mbps + * (7) 54 Mbps + * + */ +#define RATE_LEGACY_RATE_MSK 0x7 + +/* + * HT, VHT, HE, EHT rate format for bits 3:0 + * 3-0: MCS + * + */ +#define RATE_HT_MCS_CODE_MSK 0x7 +#define RATE_MCS_NSS_POS 4 +#define RATE_MCS_NSS_MSK (1 << RATE_MCS_NSS_POS) +#define RATE_MCS_CODE_MSK 0xf +#define RATE_HT_MCS_INDEX(r) ((((r) & RATE_MCS_NSS_MSK) >> 1) | \ + ((r) & RATE_HT_MCS_CODE_MSK)) + +/* Bits 7-5: reserved */ + +/* + * Bits 13-11: (0) 20MHz, (1) 40MHz, (2) 80MHz, (3) 160MHz, (4) 320MHz + */ +#define RATE_MCS_CHAN_WIDTH_MSK (0x7 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_20 (0 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_40 (1 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_80 (2 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_160 (3 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_320 (4 << RATE_MCS_CHAN_WIDTH_POS) + +/* Bit 15-14: Antenna selection: + * Bit 14: Ant A active + * Bit 15: Ant B active + * + * All relevant definitions are same as in v1 + */ + +/* Bit 16 (1) LDPC enables, (0) LDPC disabled */ +#define RATE_MCS_LDPC_POS 16 +#define RATE_MCS_LDPC_MSK (1 << RATE_MCS_LDPC_POS) + +/* Bit 17: (0) SS, (1) SS*2 (same as v1) */ + +/* Bit 18: OFDM-HE dual carrier mode (same as v1) */ + +/* Bit 19: (0) Beamforming is off, (1) Beamforming is on (same as v1) */ + +/* + * Bit 22-20: HE LTF type and guard interval + * CCK: + * 0 long preamble + * 1 short preamble + * HT/VHT: + * 0 0.8us + * 1 0.4us + * HE (ext) SU: + * 0 1xLTF+0.8us + * 1 2xLTF+0.8us + * 2 2xLTF+1.6us + * 3 4xLTF+3.2us + * 4 4xLTF+0.8us + * HE MU: + * 0 4xLTF+0.8us + * 1 2xLTF+0.8us + * 2 2xLTF+1.6us + * 3 4xLTF+3.2us + * HE TRIG: + * 0 1xLTF+1.6us + * 1 2xLTF+1.6us + * 2 4xLTF+3.2us + * */ +#define RATE_MCS_HE_GI_LTF_MSK (0x7 << RATE_MCS_HE_GI_LTF_POS) +#define RATE_MCS_SGI_POS RATE_MCS_HE_GI_LTF_POS +#define RATE_MCS_SGI_MSK (1 << RATE_MCS_SGI_POS) +#define RATE_MCS_HE_SU_4_LTF 3 +#define RATE_MCS_HE_SU_4_LTF_08_GI 4 + +/* Bit 24-23: HE type. (0) SU, (1) SU_EXT, (2) MU, (3) trigger based */ +#define RATE_MCS_HE_TYPE_POS 23 +#define RATE_MCS_HE_TYPE_SU (0 << RATE_MCS_HE_TYPE_POS) +#define RATE_MCS_HE_TYPE_EXT_SU (1 << RATE_MCS_HE_TYPE_POS) +#define RATE_MCS_HE_TYPE_MU (2 << RATE_MCS_HE_TYPE_POS) +#define RATE_MCS_HE_TYPE_TRIG (3 << RATE_MCS_HE_TYPE_POS) +#define RATE_MCS_HE_TYPE_MSK (3 << RATE_MCS_HE_TYPE_POS) + +/* Bit 25: duplicate channel enabled + * + * if this bit is set, duplicate is according to BW (bits 11-13): + * + * CCK: 2x 20MHz + * OFDM Legacy: N x 20Mhz, (N = BW \ 2 , either 2, 4, 8, 16) + * EHT: 2 x BW/2, (80 - 2x40, 160 - 2x80, 320 - 2x160) + * */ +#define RATE_MCS_DUP_POS 25 +#define RATE_MCS_DUP_MSK (1 << RATE_MCS_DUP_POS) + +/* Bit 26: (1) 106-tone RX (8 MHz RU), (0) normal bandwidth */ +#define RATE_MCS_HE_106T_POS 26 +#define RATE_MCS_HE_106T_MSK (1 << RATE_MCS_HE_106T_POS) + +/* Bit 27: EHT extra LTF: + * instead of 1 LTF for SISO use 2 LTFs, + * instead of 2 LTFs for NSTS=2 use 4 LTFs*/ +#define RATE_MCS_EHT_EXTRA_LTF_POS 27 +#define RATE_MCS_EHT_EXTRA_LTF_MSK (1 << RATE_MCS_EHT_EXTRA_LTF_POS) + +/* Bit 31-28: reserved */ + +/* Link Quality definitions */ + +/* # entries in rate scale table to support Tx retries */ +#define LQ_MAX_RETRY_NUM 16 + +/* Link quality command flags bit fields */ + +/* Bit 0: (0) Don't use RTS (1) Use RTS */ +#define LQ_FLAG_USE_RTS_POS 0 +#define LQ_FLAG_USE_RTS_MSK (1 << LQ_FLAG_USE_RTS_POS) + +/* Bit 1-3: LQ command color. Used to match responses to LQ commands */ +#define LQ_FLAG_COLOR_POS 1 +#define LQ_FLAG_COLOR_MSK (7 << LQ_FLAG_COLOR_POS) +#define LQ_FLAG_COLOR_GET(_f) (((_f) & LQ_FLAG_COLOR_MSK) >>\ + LQ_FLAG_COLOR_POS) +#define LQ_FLAGS_COLOR_INC(_c) ((((_c) + 1) << LQ_FLAG_COLOR_POS) &\ + LQ_FLAG_COLOR_MSK) +#define LQ_FLAG_COLOR_SET(_f, _c) ((_c) | ((_f) & ~LQ_FLAG_COLOR_MSK)) + +/* Bit 4-5: Tx RTS BW Signalling + * (0) No RTS BW signalling + * (1) Static BW signalling + * (2) Dynamic BW signalling + */ +#define LQ_FLAG_RTS_BW_SIG_POS 4 +#define LQ_FLAG_RTS_BW_SIG_NONE (0 << LQ_FLAG_RTS_BW_SIG_POS) +#define LQ_FLAG_RTS_BW_SIG_STATIC (1 << LQ_FLAG_RTS_BW_SIG_POS) +#define LQ_FLAG_RTS_BW_SIG_DYNAMIC (2 << LQ_FLAG_RTS_BW_SIG_POS) + +/* Bit 6: (0) No dynamic BW selection (1) Allow dynamic BW selection + * Dyanmic BW selection allows Tx with narrower BW then requested in rates + */ +#define LQ_FLAG_DYNAMIC_BW_POS 6 +#define LQ_FLAG_DYNAMIC_BW_MSK (1 << LQ_FLAG_DYNAMIC_BW_POS) + +/* Single Stream Tx Parameters (lq_cmd->ss_params) + * Flags to control a smart FW decision about whether BFER/STBC/SISO will be + * used for single stream Tx. + */ + +/* Bit 0-1: Max STBC streams allowed. Can be 0-3. + * (0) - No STBC allowed + * (1) - 2x1 STBC allowed (HT/VHT) + * (2) - 4x2 STBC allowed (HT/VHT) + * (3) - 3x2 STBC allowed (HT only) + * All our chips are at most 2 antennas so only (1) is valid for now. + */ +#define LQ_SS_STBC_ALLOWED_POS 0 +#define LQ_SS_STBC_ALLOWED_MSK (3 << LQ_SS_STBC_ALLOWED_MSK) + +/* 2x1 STBC is allowed */ +#define LQ_SS_STBC_1SS_ALLOWED (1 << LQ_SS_STBC_ALLOWED_POS) + +/* Bit 2: Beamformer (VHT only) is allowed */ +#define LQ_SS_BFER_ALLOWED_POS 2 +#define LQ_SS_BFER_ALLOWED (1 << LQ_SS_BFER_ALLOWED_POS) + +/* Bit 3: Force BFER or STBC for testing + * If this is set: + * If BFER is allowed then force the ucode to choose BFER else + * If STBC is allowed then force the ucode to choose STBC over SISO + */ +#define LQ_SS_FORCE_POS 3 +#define LQ_SS_FORCE (1 << LQ_SS_FORCE_POS) + +/* Bit 31: ss_params field is valid. Used for FW backward compatibility + * with other drivers which don't support the ss_params API yet + */ +#define LQ_SS_PARAMS_VALID_POS 31 +#define LQ_SS_PARAMS_VALID (1 << LQ_SS_PARAMS_VALID_POS) + +/** + * struct iwl_lq_cmd - link quality command + * @sta_id: station to update + * @reduced_tpc: reduced transmit power control value + * @control: not used + * @flags: combination of LQ_FLAG_* + * @mimo_delim: the first SISO index in rs_table, which separates MIMO + * and SISO rates + * @single_stream_ant_msk: best antenna for SISO (can be dual in CDD). + * Should be ANT_[ABC] + * @dual_stream_ant_msk: best antennas for MIMO, combination of ANT_[ABC] + * @initial_rate_index: first index from rs_table per AC category + * @agg_time_limit: aggregation max time threshold in usec/100, meaning + * value of 100 is one usec. Range is 100 to 8000 + * @agg_disable_start_th: try-count threshold for starting aggregation. + * If a frame has higher try-count, it should not be selected for + * starting an aggregation sequence. + * @agg_frame_cnt_limit: max frame count in an aggregation. + * 0: no limit + * 1: no aggregation (one frame per aggregation) + * 2 - 0x3f: maximal number of frames (up to 3f == 63) + * @reserved2: reserved + * @rs_table: array of rates for each TX try, each is rate_n_flags, + * meaning it is a combination of RATE_MCS_* and IWL_RATE_*_PLCP + * @ss_params: single stream features. declare whether STBC or BFER are allowed. + */ +struct iwl_lq_cmd { + u8 sta_id; + u8 reduced_tpc; + __le16 control; + /* LINK_QUAL_GENERAL_PARAMS_API_S_VER_1 */ + u8 flags; + u8 mimo_delim; + u8 single_stream_ant_msk; + u8 dual_stream_ant_msk; + u8 initial_rate_index[AC_NUM]; + /* LINK_QUAL_AGG_PARAMS_API_S_VER_1 */ + __le16 agg_time_limit; + u8 agg_disable_start_th; + u8 agg_frame_cnt_limit; + __le32 reserved2; + __le32 rs_table[LQ_MAX_RETRY_NUM]; + __le32 ss_params; +}; /* LINK_QUALITY_CMD_API_S_VER_1 */ + +u8 iwl_fw_rate_idx_to_plcp(int idx); +u32 iwl_new_rate_from_v1(u32 rate_v1); +const struct iwl_rate_mcs_info *iwl_rate_mcs(int idx); +const char *iwl_rs_pretty_ant(u8 ant); +const char *iwl_rs_pretty_bw(int bw); +int rs_pretty_print_rate(char *buf, int bufsz, const u32 rate); +bool iwl_he_is_sgi(u32 rate_n_flags); + +#endif /* __iwl_fw_api_rs_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h b/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h new file mode 100644 index 000000000..74a018887 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/rx.h @@ -0,0 +1,849 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_rx_h__ +#define __iwl_fw_api_rx_h__ + +/* API for pre-9000 hardware */ + +#define IWL_RX_INFO_PHY_CNT 8 +#define IWL_RX_INFO_ENERGY_ANT_ABC_IDX 1 +#define IWL_RX_INFO_ENERGY_ANT_A_MSK 0x000000ff +#define IWL_RX_INFO_ENERGY_ANT_B_MSK 0x0000ff00 +#define IWL_RX_INFO_ENERGY_ANT_A_POS 0 +#define IWL_RX_INFO_ENERGY_ANT_B_POS 8 +#define IWL_RX_INFO_ENERGY_ANT_C_POS 16 + +enum iwl_mac_context_info { + MAC_CONTEXT_INFO_NONE, + MAC_CONTEXT_INFO_GSCAN, +}; + +/** + * struct iwl_rx_phy_info - phy info + * (REPLY_RX_PHY_CMD = 0xc0) + * @non_cfg_phy_cnt: non configurable DSP phy data byte count + * @cfg_phy_cnt: configurable DSP phy data byte count + * @stat_id: configurable DSP phy data set ID + * @reserved1: reserved + * @system_timestamp: GP2 at on air rise + * @timestamp: TSF at on air rise + * @beacon_time_stamp: beacon at on-air rise + * @phy_flags: general phy flags: band, modulation, ... + * @channel: channel number + * @non_cfg_phy: for various implementations of non_cfg_phy + * @rate_n_flags: RATE_MCS_* + * @byte_count: frame's byte-count + * @frame_time: frame's time on the air, based on byte count and frame rate + * calculation + * @mac_active_msk: what MACs were active when the frame was received + * @mac_context_info: additional info on the context in which the frame was + * received as defined in &enum iwl_mac_context_info + * + * Before each Rx, the device sends this data. It contains PHY information + * about the reception of the packet. + */ +struct iwl_rx_phy_info { + u8 non_cfg_phy_cnt; + u8 cfg_phy_cnt; + u8 stat_id; + u8 reserved1; + __le32 system_timestamp; + __le64 timestamp; + __le32 beacon_time_stamp; + __le16 phy_flags; + __le16 channel; + __le32 non_cfg_phy[IWL_RX_INFO_PHY_CNT]; + __le32 rate_n_flags; + __le32 byte_count; + u8 mac_active_msk; + u8 mac_context_info; + __le16 frame_time; +} __packed; + +/* + * TCP offload Rx assist info + * + * bits 0:3 - reserved + * bits 4:7 - MIC CRC length + * bits 8:12 - MAC header length + * bit 13 - Padding indication + * bit 14 - A-AMSDU indication + * bit 15 - Offload enabled + */ +enum iwl_csum_rx_assist_info { + CSUM_RXA_RESERVED_MASK = 0x000f, + CSUM_RXA_MICSIZE_MASK = 0x00f0, + CSUM_RXA_HEADERLEN_MASK = 0x1f00, + CSUM_RXA_PADD = BIT(13), + CSUM_RXA_AMSDU = BIT(14), + CSUM_RXA_ENA = BIT(15) +}; + +/** + * struct iwl_rx_mpdu_res_start - phy info + * @byte_count: byte count of the frame + * @assist: see &enum iwl_csum_rx_assist_info + */ +struct iwl_rx_mpdu_res_start { + __le16 byte_count; + __le16 assist; +} __packed; /* _RX_MPDU_RES_START_API_S_VER_2 */ + +/** + * enum iwl_rx_phy_flags - to parse %iwl_rx_phy_info phy_flags + * @RX_RES_PHY_FLAGS_BAND_24: true if the packet was received on 2.4 band + * @RX_RES_PHY_FLAGS_MOD_CCK: modulation is CCK + * @RX_RES_PHY_FLAGS_SHORT_PREAMBLE: true if packet's preamble was short + * @RX_RES_PHY_FLAGS_NARROW_BAND: narrow band (<20 MHz) receive + * @RX_RES_PHY_FLAGS_ANTENNA: antenna on which the packet was received + * @RX_RES_PHY_FLAGS_ANTENNA_POS: antenna bit position + * @RX_RES_PHY_FLAGS_AGG: set if the packet was part of an A-MPDU + * @RX_RES_PHY_FLAGS_OFDM_HT: The frame was an HT frame + * @RX_RES_PHY_FLAGS_OFDM_GF: The frame used GF preamble + * @RX_RES_PHY_FLAGS_OFDM_VHT: The frame was a VHT frame + */ +enum iwl_rx_phy_flags { + RX_RES_PHY_FLAGS_BAND_24 = BIT(0), + RX_RES_PHY_FLAGS_MOD_CCK = BIT(1), + RX_RES_PHY_FLAGS_SHORT_PREAMBLE = BIT(2), + RX_RES_PHY_FLAGS_NARROW_BAND = BIT(3), + RX_RES_PHY_FLAGS_ANTENNA = (0x7 << 4), + RX_RES_PHY_FLAGS_ANTENNA_POS = 4, + RX_RES_PHY_FLAGS_AGG = BIT(7), + RX_RES_PHY_FLAGS_OFDM_HT = BIT(8), + RX_RES_PHY_FLAGS_OFDM_GF = BIT(9), + RX_RES_PHY_FLAGS_OFDM_VHT = BIT(10), +}; + +/** + * enum iwl_mvm_rx_status - written by fw for each Rx packet + * @RX_MPDU_RES_STATUS_CRC_OK: CRC is fine + * @RX_MPDU_RES_STATUS_OVERRUN_OK: there was no RXE overflow + * @RX_MPDU_RES_STATUS_SRC_STA_FOUND: station was found + * @RX_MPDU_RES_STATUS_KEY_VALID: key was valid + * @RX_MPDU_RES_STATUS_ICV_OK: ICV is fine, if not, the packet is destroyed + * @RX_MPDU_RES_STATUS_MIC_OK: used for CCM alg only. TKIP MIC is checked + * in the driver. + * @RX_MPDU_RES_STATUS_TTAK_OK: TTAK is fine + * @RX_MPDU_RES_STATUS_MNG_FRAME_REPLAY_ERR: valid for alg = CCM_CMAC or + * alg = CCM only. Checks replay attack for 11w frames. + * @RX_MPDU_RES_STATUS_SEC_NO_ENC: this frame is not encrypted + * @RX_MPDU_RES_STATUS_SEC_WEP_ENC: this frame is encrypted using WEP + * @RX_MPDU_RES_STATUS_SEC_CCM_ENC: this frame is encrypted using CCM + * @RX_MPDU_RES_STATUS_SEC_TKIP_ENC: this frame is encrypted using TKIP + * @RX_MPDU_RES_STATUS_SEC_EXT_ENC: this frame is encrypted using extension + * algorithm + * @RX_MPDU_RES_STATUS_SEC_CMAC_GMAC_ENC: this frame is protected using + * CMAC or GMAC + * @RX_MPDU_RES_STATUS_SEC_ENC_ERR: this frame couldn't be decrypted + * @RX_MPDU_RES_STATUS_SEC_ENC_MSK: bitmask of the encryption algorithm + * @RX_MPDU_RES_STATUS_DEC_DONE: this frame has been successfully decrypted + * @RX_MPDU_RES_STATUS_CSUM_DONE: checksum was done by the hw + * @RX_MPDU_RES_STATUS_CSUM_OK: checksum found no errors + * @RX_MPDU_RES_STATUS_STA_ID_MSK: station ID mask + * @RX_MDPU_RES_STATUS_STA_ID_SHIFT: station ID bit shift + */ +enum iwl_mvm_rx_status { + RX_MPDU_RES_STATUS_CRC_OK = BIT(0), + RX_MPDU_RES_STATUS_OVERRUN_OK = BIT(1), + RX_MPDU_RES_STATUS_SRC_STA_FOUND = BIT(2), + RX_MPDU_RES_STATUS_KEY_VALID = BIT(3), + RX_MPDU_RES_STATUS_ICV_OK = BIT(5), + RX_MPDU_RES_STATUS_MIC_OK = BIT(6), + RX_MPDU_RES_STATUS_TTAK_OK = BIT(7), + RX_MPDU_RES_STATUS_MNG_FRAME_REPLAY_ERR = BIT(7), + RX_MPDU_RES_STATUS_SEC_NO_ENC = (0 << 8), + RX_MPDU_RES_STATUS_SEC_WEP_ENC = (1 << 8), + RX_MPDU_RES_STATUS_SEC_CCM_ENC = (2 << 8), + RX_MPDU_RES_STATUS_SEC_TKIP_ENC = (3 << 8), + RX_MPDU_RES_STATUS_SEC_EXT_ENC = (4 << 8), + RX_MPDU_RES_STATUS_SEC_CMAC_GMAC_ENC = (6 << 8), + RX_MPDU_RES_STATUS_SEC_ENC_ERR = (7 << 8), + RX_MPDU_RES_STATUS_SEC_ENC_MSK = (7 << 8), + RX_MPDU_RES_STATUS_DEC_DONE = BIT(11), + RX_MPDU_RES_STATUS_CSUM_DONE = BIT(16), + RX_MPDU_RES_STATUS_CSUM_OK = BIT(17), + RX_MDPU_RES_STATUS_STA_ID_SHIFT = 24, + RX_MPDU_RES_STATUS_STA_ID_MSK = 0x1f << RX_MDPU_RES_STATUS_STA_ID_SHIFT, +}; + +/* 9000 series API */ +enum iwl_rx_mpdu_mac_flags1 { + IWL_RX_MDPU_MFLG1_ADDRTYPE_MASK = 0x03, + IWL_RX_MPDU_MFLG1_MIC_CRC_LEN_MASK = 0xf0, + /* shift should be 4, but the length is measured in 2-byte + * words, so shifting only by 3 gives a byte result + */ + IWL_RX_MPDU_MFLG1_MIC_CRC_LEN_SHIFT = 3, +}; + +enum iwl_rx_mpdu_mac_flags2 { + /* in 2-byte words */ + IWL_RX_MPDU_MFLG2_HDR_LEN_MASK = 0x1f, + IWL_RX_MPDU_MFLG2_PAD = 0x20, + IWL_RX_MPDU_MFLG2_AMSDU = 0x40, +}; + +enum iwl_rx_mpdu_amsdu_info { + IWL_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK = 0x7f, + IWL_RX_MPDU_AMSDU_LAST_SUBFRAME = 0x80, +}; + +#define RX_MPDU_BAND_POS 6 +#define RX_MPDU_BAND_MASK 0xC0 +#define BAND_IN_RX_STATUS(_val) \ + (((_val) & RX_MPDU_BAND_MASK) >> RX_MPDU_BAND_POS) + +enum iwl_rx_l3_proto_values { + IWL_RX_L3_TYPE_NONE, + IWL_RX_L3_TYPE_IPV4, + IWL_RX_L3_TYPE_IPV4_FRAG, + IWL_RX_L3_TYPE_IPV6_FRAG, + IWL_RX_L3_TYPE_IPV6, + IWL_RX_L3_TYPE_IPV6_IN_IPV4, + IWL_RX_L3_TYPE_ARP, + IWL_RX_L3_TYPE_EAPOL, +}; + +#define IWL_RX_L3_PROTO_POS 4 + +enum iwl_rx_l3l4_flags { + IWL_RX_L3L4_IP_HDR_CSUM_OK = BIT(0), + IWL_RX_L3L4_TCP_UDP_CSUM_OK = BIT(1), + IWL_RX_L3L4_TCP_FIN_SYN_RST_PSH = BIT(2), + IWL_RX_L3L4_TCP_ACK = BIT(3), + IWL_RX_L3L4_L3_PROTO_MASK = 0xf << IWL_RX_L3_PROTO_POS, + IWL_RX_L3L4_L4_PROTO_MASK = 0xf << 8, + IWL_RX_L3L4_RSS_HASH_MASK = 0xf << 12, +}; + +enum iwl_rx_mpdu_status { + IWL_RX_MPDU_STATUS_CRC_OK = BIT(0), + IWL_RX_MPDU_STATUS_OVERRUN_OK = BIT(1), + IWL_RX_MPDU_STATUS_SRC_STA_FOUND = BIT(2), + IWL_RX_MPDU_STATUS_KEY_VALID = BIT(3), + IWL_RX_MPDU_STATUS_ICV_OK = BIT(5), + IWL_RX_MPDU_STATUS_MIC_OK = BIT(6), + IWL_RX_MPDU_RES_STATUS_TTAK_OK = BIT(7), + /* overlayed since IWL_UCODE_TLV_API_DEPRECATE_TTAK */ + IWL_RX_MPDU_STATUS_REPLAY_ERROR = BIT(7), + IWL_RX_MPDU_STATUS_SEC_MASK = 0x7 << 8, + IWL_RX_MPDU_STATUS_SEC_UNKNOWN = IWL_RX_MPDU_STATUS_SEC_MASK, + IWL_RX_MPDU_STATUS_SEC_NONE = 0x0 << 8, + IWL_RX_MPDU_STATUS_SEC_WEP = 0x1 << 8, + IWL_RX_MPDU_STATUS_SEC_CCM = 0x2 << 8, + IWL_RX_MPDU_STATUS_SEC_TKIP = 0x3 << 8, + IWL_RX_MPDU_STATUS_SEC_EXT_ENC = 0x4 << 8, + IWL_RX_MPDU_STATUS_SEC_GCM = 0x5 << 8, + IWL_RX_MPDU_STATUS_DECRYPTED = BIT(11), + IWL_RX_MPDU_STATUS_ROBUST_MNG_FRAME = BIT(15), + + IWL_RX_MPDU_STATUS_DUPLICATE = BIT(22), + + IWL_RX_MPDU_STATUS_STA_ID = 0x1f000000, +}; + +#define IWL_RX_REORDER_DATA_INVALID_BAID 0x7f + +enum iwl_rx_mpdu_reorder_data { + IWL_RX_MPDU_REORDER_NSSN_MASK = 0x00000fff, + IWL_RX_MPDU_REORDER_SN_MASK = 0x00fff000, + IWL_RX_MPDU_REORDER_SN_SHIFT = 12, + IWL_RX_MPDU_REORDER_BAID_MASK = 0x7f000000, + IWL_RX_MPDU_REORDER_BAID_SHIFT = 24, + IWL_RX_MPDU_REORDER_BA_OLD_SN = 0x80000000, +}; + +enum iwl_rx_mpdu_phy_info { + IWL_RX_MPDU_PHY_AMPDU = BIT(5), + IWL_RX_MPDU_PHY_AMPDU_TOGGLE = BIT(6), + IWL_RX_MPDU_PHY_SHORT_PREAMBLE = BIT(7), + /* short preamble is only for CCK, for non-CCK overridden by this */ + IWL_RX_MPDU_PHY_NCCK_ADDTL_NTFY = BIT(7), + IWL_RX_MPDU_PHY_TSF_OVERLOAD = BIT(8), +}; + +enum iwl_rx_mpdu_mac_info { + IWL_RX_MPDU_PHY_MAC_INDEX_MASK = 0x0f, + IWL_RX_MPDU_PHY_PHY_INDEX_MASK = 0xf0, +}; + +/* TSF overload low dword */ +enum iwl_rx_phy_data0 { + /* info type: HE any */ + IWL_RX_PHY_DATA0_HE_BEAM_CHNG = 0x00000001, + IWL_RX_PHY_DATA0_HE_UPLINK = 0x00000002, + IWL_RX_PHY_DATA0_HE_BSS_COLOR_MASK = 0x000000fc, + IWL_RX_PHY_DATA0_HE_SPATIAL_REUSE_MASK = 0x00000f00, + /* 1 bit reserved */ + IWL_RX_PHY_DATA0_HE_TXOP_DUR_MASK = 0x000fe000, + IWL_RX_PHY_DATA0_HE_LDPC_EXT_SYM = 0x00100000, + IWL_RX_PHY_DATA0_HE_PRE_FEC_PAD_MASK = 0x00600000, + IWL_RX_PHY_DATA0_HE_PE_DISAMBIG = 0x00800000, + IWL_RX_PHY_DATA0_HE_DOPPLER = 0x01000000, + /* 6 bits reserved */ + IWL_RX_PHY_DATA0_HE_DELIM_EOF = 0x80000000, +}; + +enum iwl_rx_phy_info_type { + IWL_RX_PHY_INFO_TYPE_NONE = 0, + IWL_RX_PHY_INFO_TYPE_CCK = 1, + IWL_RX_PHY_INFO_TYPE_OFDM_LGCY = 2, + IWL_RX_PHY_INFO_TYPE_HT = 3, + IWL_RX_PHY_INFO_TYPE_VHT_SU = 4, + IWL_RX_PHY_INFO_TYPE_VHT_MU = 5, + IWL_RX_PHY_INFO_TYPE_HE_SU = 6, + IWL_RX_PHY_INFO_TYPE_HE_MU = 7, + IWL_RX_PHY_INFO_TYPE_HE_TB = 8, + IWL_RX_PHY_INFO_TYPE_HE_MU_EXT = 9, + IWL_RX_PHY_INFO_TYPE_HE_TB_EXT = 10, +}; + +/* TSF overload high dword */ +enum iwl_rx_phy_data1 { + /* + * check this first - if TSF overload is set, + * see &enum iwl_rx_phy_info_type + */ + IWL_RX_PHY_DATA1_INFO_TYPE_MASK = 0xf0000000, + + /* info type: HT/VHT/HE any */ + IWL_RX_PHY_DATA1_LSIG_LEN_MASK = 0x0fff0000, + + /* info type: HE MU/MU-EXT */ + IWL_RX_PHY_DATA1_HE_MU_SIGB_COMPRESSION = 0x00000001, + IWL_RX_PHY_DATA1_HE_MU_SIBG_SYM_OR_USER_NUM_MASK = 0x0000001e, + + /* info type: HE any */ + IWL_RX_PHY_DATA1_HE_LTF_NUM_MASK = 0x000000e0, + IWL_RX_PHY_DATA1_HE_RU_ALLOC_SEC80 = 0x00000100, + /* trigger encoded */ + IWL_RX_PHY_DATA1_HE_RU_ALLOC_MASK = 0x0000fe00, + + /* info type: HE TB/TX-EXT */ + IWL_RX_PHY_DATA1_HE_TB_PILOT_TYPE = 0x00000001, + IWL_RX_PHY_DATA1_HE_TB_LOW_SS_MASK = 0x0000000e, +}; + +/* goes into Metadata DW 7 */ +enum iwl_rx_phy_data2 { + /* info type: HE MU-EXT */ + /* the a1/a2/... is what the PHY/firmware calls the values */ + IWL_RX_PHY_DATA2_HE_MU_EXT_CH1_RU0 = 0x000000ff, /* a1 */ + IWL_RX_PHY_DATA2_HE_MU_EXT_CH1_RU2 = 0x0000ff00, /* a2 */ + IWL_RX_PHY_DATA2_HE_MU_EXT_CH2_RU0 = 0x00ff0000, /* b1 */ + IWL_RX_PHY_DATA2_HE_MU_EXT_CH2_RU2 = 0xff000000, /* b2 */ + + /* info type: HE TB-EXT */ + IWL_RX_PHY_DATA2_HE_TB_EXT_SPTL_REUSE1 = 0x0000000f, + IWL_RX_PHY_DATA2_HE_TB_EXT_SPTL_REUSE2 = 0x000000f0, + IWL_RX_PHY_DATA2_HE_TB_EXT_SPTL_REUSE3 = 0x00000f00, + IWL_RX_PHY_DATA2_HE_TB_EXT_SPTL_REUSE4 = 0x0000f000, +}; + +/* goes into Metadata DW 8 */ +enum iwl_rx_phy_data3 { + /* info type: HE MU-EXT */ + IWL_RX_PHY_DATA3_HE_MU_EXT_CH1_RU1 = 0x000000ff, /* c1 */ + IWL_RX_PHY_DATA3_HE_MU_EXT_CH1_RU3 = 0x0000ff00, /* c2 */ + IWL_RX_PHY_DATA3_HE_MU_EXT_CH2_RU1 = 0x00ff0000, /* d1 */ + IWL_RX_PHY_DATA3_HE_MU_EXT_CH2_RU3 = 0xff000000, /* d2 */ +}; + +/* goes into Metadata DW 4 high 16 bits */ +enum iwl_rx_phy_data4 { + /* info type: HE MU-EXT */ + IWL_RX_PHY_DATA4_HE_MU_EXT_CH1_CTR_RU = 0x0001, + IWL_RX_PHY_DATA4_HE_MU_EXT_CH2_CTR_RU = 0x0002, + IWL_RX_PHY_DATA4_HE_MU_EXT_CH1_CRC_OK = 0x0004, + IWL_RX_PHY_DATA4_HE_MU_EXT_CH2_CRC_OK = 0x0008, + IWL_RX_PHY_DATA4_HE_MU_EXT_SIGB_MCS_MASK = 0x00f0, + IWL_RX_PHY_DATA4_HE_MU_EXT_SIGB_DCM = 0x0100, + IWL_RX_PHY_DATA4_HE_MU_EXT_PREAMBLE_PUNC_TYPE_MASK = 0x0600, +}; + +/** + * struct iwl_rx_mpdu_desc_v1 - RX MPDU descriptor + */ +struct iwl_rx_mpdu_desc_v1 { + /* DW7 - carries rss_hash only when rpa_en == 1 */ + union { + /** + * @rss_hash: RSS hash value + */ + __le32 rss_hash; + + /** + * @phy_data2: depends on info type (see @phy_data1) + */ + __le32 phy_data2; + }; + + /* DW8 - carries filter_match only when rpa_en == 1 */ + union { + /** + * @filter_match: filter match value + */ + __le32 filter_match; + + /** + * @phy_data3: depends on info type (see @phy_data1) + */ + __le32 phy_data3; + }; + + /* DW9 */ + /** + * @rate_n_flags: RX rate/flags encoding + */ + __le32 rate_n_flags; + /* DW10 */ + /** + * @energy_a: energy chain A + */ + u8 energy_a; + /** + * @energy_b: energy chain B + */ + u8 energy_b; + /** + * @channel: channel number + */ + u8 channel; + /** + * @mac_context: MAC context mask + */ + u8 mac_context; + /* DW11 */ + /** + * @gp2_on_air_rise: GP2 timer value on air rise (INA) + */ + __le32 gp2_on_air_rise; + /* DW12 & DW13 */ + union { + /** + * @tsf_on_air_rise: + * TSF value on air rise (INA), only valid if + * %IWL_RX_MPDU_PHY_TSF_OVERLOAD isn't set + */ + __le64 tsf_on_air_rise; + + struct { + /** + * @phy_data0: depends on info_type, see @phy_data1 + */ + __le32 phy_data0; + /** + * @phy_data1: valid only if + * %IWL_RX_MPDU_PHY_TSF_OVERLOAD is set, + * see &enum iwl_rx_phy_data1. + */ + __le32 phy_data1; + }; + }; +} __packed; /* RX_MPDU_RES_START_API_S_VER_4 */ + +/** + * struct iwl_rx_mpdu_desc_v3 - RX MPDU descriptor + */ +struct iwl_rx_mpdu_desc_v3 { + /* DW7 - carries filter_match only when rpa_en == 1 */ + union { + /** + * @filter_match: filter match value + */ + __le32 filter_match; + + /** + * @phy_data3: depends on info type (see @phy_data1) + */ + __le32 phy_data3; + }; + + /* DW8 - carries rss_hash only when rpa_en == 1 */ + union { + /** + * @rss_hash: RSS hash value + */ + __le32 rss_hash; + + /** + * @phy_data2: depends on info type (see @phy_data1) + */ + __le32 phy_data2; + }; + /* DW9 */ + /** + * @partial_hash: 31:0 ip/tcp header hash + * w/o some fields (such as IP SRC addr) + */ + __le32 partial_hash; + /* DW10 */ + /** + * @raw_xsum: raw xsum value + */ + __be16 raw_xsum; + /** + * @reserved_xsum: reserved high bits in the raw checksum + */ + __le16 reserved_xsum; + /* DW11 */ + /** + * @rate_n_flags: RX rate/flags encoding + */ + __le32 rate_n_flags; + /* DW12 */ + /** + * @energy_a: energy chain A + */ + u8 energy_a; + /** + * @energy_b: energy chain B + */ + u8 energy_b; + /** + * @channel: channel number + */ + u8 channel; + /** + * @mac_context: MAC context mask + */ + u8 mac_context; + /* DW13 */ + /** + * @gp2_on_air_rise: GP2 timer value on air rise (INA) + */ + __le32 gp2_on_air_rise; + /* DW14 & DW15 */ + union { + /** + * @tsf_on_air_rise: + * TSF value on air rise (INA), only valid if + * %IWL_RX_MPDU_PHY_TSF_OVERLOAD isn't set + */ + __le64 tsf_on_air_rise; + + struct { + /** + * @phy_data0: depends on info_type, see @phy_data1 + */ + __le32 phy_data0; + /** + * @phy_data1: valid only if + * %IWL_RX_MPDU_PHY_TSF_OVERLOAD is set, + * see &enum iwl_rx_phy_data1. + */ + __le32 phy_data1; + }; + }; + /* DW16 & DW17 */ + /** + * @reserved: reserved + */ + __le32 reserved[2]; +} __packed; /* RX_MPDU_RES_START_API_S_VER_3, + RX_MPDU_RES_START_API_S_VER_5 */ + +/** + * struct iwl_rx_mpdu_desc - RX MPDU descriptor + */ +struct iwl_rx_mpdu_desc { + /* DW2 */ + /** + * @mpdu_len: MPDU length + */ + __le16 mpdu_len; + /** + * @mac_flags1: &enum iwl_rx_mpdu_mac_flags1 + */ + u8 mac_flags1; + /** + * @mac_flags2: &enum iwl_rx_mpdu_mac_flags2 + */ + u8 mac_flags2; + /* DW3 */ + /** + * @amsdu_info: &enum iwl_rx_mpdu_amsdu_info + */ + u8 amsdu_info; + /** + * @phy_info: &enum iwl_rx_mpdu_phy_info + */ + __le16 phy_info; + /** + * @mac_phy_idx: MAC/PHY index + */ + u8 mac_phy_idx; + /* DW4 - carries csum data only when rpa_en == 1 */ + /** + * @raw_csum: raw checksum (alledgedly unreliable) + */ + __le16 raw_csum; + + union { + /** + * @l3l4_flags: &enum iwl_rx_l3l4_flags + */ + __le16 l3l4_flags; + + /** + * @phy_data4: depends on info type, see phy_data1 + */ + __le16 phy_data4; + }; + /* DW5 */ + /** + * @status: &enum iwl_rx_mpdu_status + */ + __le32 status; + + /* DW6 */ + /** + * @reorder_data: &enum iwl_rx_mpdu_reorder_data + */ + __le32 reorder_data; + + union { + struct iwl_rx_mpdu_desc_v1 v1; + struct iwl_rx_mpdu_desc_v3 v3; + }; +} __packed; /* RX_MPDU_RES_START_API_S_VER_3, + RX_MPDU_RES_START_API_S_VER_4, + RX_MPDU_RES_START_API_S_VER_5 */ + +#define IWL_RX_DESC_SIZE_V1 offsetofend(struct iwl_rx_mpdu_desc, v1) + +#define RX_NO_DATA_CHAIN_A_POS 0 +#define RX_NO_DATA_CHAIN_A_MSK (0xff << RX_NO_DATA_CHAIN_A_POS) +#define RX_NO_DATA_CHAIN_B_POS 8 +#define RX_NO_DATA_CHAIN_B_MSK (0xff << RX_NO_DATA_CHAIN_B_POS) +#define RX_NO_DATA_CHANNEL_POS 16 +#define RX_NO_DATA_CHANNEL_MSK (0xff << RX_NO_DATA_CHANNEL_POS) + +#define RX_NO_DATA_INFO_TYPE_POS 0 +#define RX_NO_DATA_INFO_TYPE_MSK (0xff << RX_NO_DATA_INFO_TYPE_POS) +#define RX_NO_DATA_INFO_TYPE_NONE 0 +#define RX_NO_DATA_INFO_TYPE_RX_ERR 1 +#define RX_NO_DATA_INFO_TYPE_NDP 2 +#define RX_NO_DATA_INFO_TYPE_MU_UNMATCHED 3 +#define RX_NO_DATA_INFO_TYPE_HE_TB_UNMATCHED 4 + +#define RX_NO_DATA_INFO_ERR_POS 8 +#define RX_NO_DATA_INFO_ERR_MSK (0xff << RX_NO_DATA_INFO_ERR_POS) +#define RX_NO_DATA_INFO_ERR_NONE 0 +#define RX_NO_DATA_INFO_ERR_BAD_PLCP 1 +#define RX_NO_DATA_INFO_ERR_UNSUPPORTED_RATE 2 +#define RX_NO_DATA_INFO_ERR_NO_DELIM 3 +#define RX_NO_DATA_INFO_ERR_BAD_MAC_HDR 4 + +#define RX_NO_DATA_FRAME_TIME_POS 0 +#define RX_NO_DATA_FRAME_TIME_MSK (0xfffff << RX_NO_DATA_FRAME_TIME_POS) + +#define RX_NO_DATA_RX_VEC0_HE_NSTS_MSK 0x03800000 +#define RX_NO_DATA_RX_VEC0_VHT_NSTS_MSK 0x38000000 + +/** + * struct iwl_rx_no_data - RX no data descriptor + * @info: 7:0 frame type, 15:8 RX error type + * @rssi: 7:0 energy chain-A, + * 15:8 chain-B, measured at FINA time (FINA_ENERGY), 16:23 channel + * @on_air_rise_time: GP2 during on air rise + * @fr_time: frame time + * @rate: rate/mcs of frame + * @phy_info: &enum iwl_rx_phy_data0 and &enum iwl_rx_phy_info_type + * @rx_vec: DW-12:9 raw RX vectors from DSP according to modulation type. + * for VHT: OFDM_RX_VECTOR_SIGA1_OUT, OFDM_RX_VECTOR_SIGA2_OUT + * for HE: OFDM_RX_VECTOR_HE_SIGA1_OUT, OFDM_RX_VECTOR_HE_SIGA2_OUT + */ +struct iwl_rx_no_data { + __le32 info; + __le32 rssi; + __le32 on_air_rise_time; + __le32 fr_time; + __le32 rate; + __le32 phy_info[2]; + __le32 rx_vec[2]; +} __packed; /* RX_NO_DATA_NTFY_API_S_VER_1, + RX_NO_DATA_NTFY_API_S_VER_2 */ + +struct iwl_frame_release { + u8 baid; + u8 reserved; + __le16 nssn; +}; + +/** + * enum iwl_bar_frame_release_sta_tid - STA/TID information for BAR release + * @IWL_BAR_FRAME_RELEASE_TID_MASK: TID mask + * @IWL_BAR_FRAME_RELEASE_STA_MASK: STA mask + */ +enum iwl_bar_frame_release_sta_tid { + IWL_BAR_FRAME_RELEASE_TID_MASK = 0x0000000f, + IWL_BAR_FRAME_RELEASE_STA_MASK = 0x000001f0, +}; + +/** + * enum iwl_bar_frame_release_ba_info - BA information for BAR release + * @IWL_BAR_FRAME_RELEASE_NSSN_MASK: NSSN mask + * @IWL_BAR_FRAME_RELEASE_SN_MASK: SN mask (ignored by driver) + * @IWL_BAR_FRAME_RELEASE_BAID_MASK: BAID mask + */ +enum iwl_bar_frame_release_ba_info { + IWL_BAR_FRAME_RELEASE_NSSN_MASK = 0x00000fff, + IWL_BAR_FRAME_RELEASE_SN_MASK = 0x00fff000, + IWL_BAR_FRAME_RELEASE_BAID_MASK = 0x3f000000, +}; + +/** + * struct iwl_bar_frame_release - frame release from BAR info + * @sta_tid: STA & TID information, see &enum iwl_bar_frame_release_sta_tid. + * @ba_info: BA information, see &enum iwl_bar_frame_release_ba_info. + */ +struct iwl_bar_frame_release { + __le32 sta_tid; + __le32 ba_info; +} __packed; /* RX_BAR_TO_FRAME_RELEASE_API_S_VER_1 */ + +enum iwl_rss_hash_func_en { + IWL_RSS_HASH_TYPE_IPV4_TCP, + IWL_RSS_HASH_TYPE_IPV4_UDP, + IWL_RSS_HASH_TYPE_IPV4_PAYLOAD, + IWL_RSS_HASH_TYPE_IPV6_TCP, + IWL_RSS_HASH_TYPE_IPV6_UDP, + IWL_RSS_HASH_TYPE_IPV6_PAYLOAD, +}; + +#define IWL_RSS_HASH_KEY_CNT 10 +#define IWL_RSS_INDIRECTION_TABLE_SIZE 128 +#define IWL_RSS_ENABLE 1 + +/** + * struct iwl_rss_config_cmd - RSS (Receive Side Scaling) configuration + * + * @flags: 1 - enable, 0 - disable + * @hash_mask: Type of RSS to use. Values are from %iwl_rss_hash_func_en + * @reserved: reserved + * @secret_key: 320 bit input of random key configuration from driver + * @indirection_table: indirection table + */ +struct iwl_rss_config_cmd { + __le32 flags; + u8 hash_mask; + u8 reserved[3]; + __le32 secret_key[IWL_RSS_HASH_KEY_CNT]; + u8 indirection_table[IWL_RSS_INDIRECTION_TABLE_SIZE]; +} __packed; /* RSS_CONFIG_CMD_API_S_VER_1 */ + +#define IWL_MULTI_QUEUE_SYNC_SENDER_POS 0 +#define IWL_MULTI_QUEUE_SYNC_SENDER_MSK 0xf + +/** + * struct iwl_rxq_sync_cmd - RXQ notification trigger + * + * @flags: flags of the notification. bit 0:3 are the sender queue + * @rxq_mask: rx queues to send the notification on + * @count: number of bytes in payload, should be DWORD aligned + * @payload: data to send to rx queues + */ +struct iwl_rxq_sync_cmd { + __le32 flags; + __le32 rxq_mask; + __le32 count; + u8 payload[]; +} __packed; /* MULTI_QUEUE_DRV_SYNC_HDR_CMD_API_S_VER_1 */ + +/** + * struct iwl_rxq_sync_notification - Notification triggered by RXQ + * sync command + * + * @count: number of bytes in payload + * @payload: data to send to rx queues + */ +struct iwl_rxq_sync_notification { + __le32 count; + u8 payload[]; +} __packed; /* MULTI_QUEUE_DRV_SYNC_HDR_CMD_API_S_VER_1 */ + +/** + * enum iwl_mvm_pm_event - type of station PM event + * @IWL_MVM_PM_EVENT_AWAKE: station woke up + * @IWL_MVM_PM_EVENT_ASLEEP: station went to sleep + * @IWL_MVM_PM_EVENT_UAPSD: station sent uAPSD trigger + * @IWL_MVM_PM_EVENT_PS_POLL: station sent PS-Poll + */ +enum iwl_mvm_pm_event { + IWL_MVM_PM_EVENT_AWAKE, + IWL_MVM_PM_EVENT_ASLEEP, + IWL_MVM_PM_EVENT_UAPSD, + IWL_MVM_PM_EVENT_PS_POLL, +}; /* PEER_PM_NTFY_API_E_VER_1 */ + +/** + * struct iwl_mvm_pm_state_notification - station PM state notification + * @sta_id: station ID of the station changing state + * @type: the new powersave state, see &enum iwl_mvm_pm_event + */ +struct iwl_mvm_pm_state_notification { + u8 sta_id; + u8 type; + /* private: */ + __le16 reserved; +} __packed; /* PEER_PM_NTFY_API_S_VER_1 */ + +#define BA_WINDOW_STREAMS_MAX 16 +#define BA_WINDOW_STATUS_TID_MSK 0x000F +#define BA_WINDOW_STATUS_STA_ID_POS 4 +#define BA_WINDOW_STATUS_STA_ID_MSK 0x01F0 +#define BA_WINDOW_STATUS_VALID_MSK BIT(9) + +/** + * struct iwl_ba_window_status_notif - reordering window's status notification + * @bitmap: bitmap of received frames [start_seq_num + 0]..[start_seq_num + 63] + * @ra_tid: bit 3:0 - TID, bit 8:4 - STA_ID, bit 9 - valid + * @start_seq_num: the start sequence number of the bitmap + * @mpdu_rx_count: the number of received MPDUs since entering D0i3 + */ +struct iwl_ba_window_status_notif { + __le64 bitmap[BA_WINDOW_STREAMS_MAX]; + __le16 ra_tid[BA_WINDOW_STREAMS_MAX]; + __le32 start_seq_num[BA_WINDOW_STREAMS_MAX]; + __le16 mpdu_rx_count[BA_WINDOW_STREAMS_MAX]; +} __packed; /* BA_WINDOW_STATUS_NTFY_API_S_VER_1 */ + +/** + * struct iwl_rfh_queue_config - RX queue configuration + * @q_num: Q num + * @enable: enable queue + * @reserved: alignment + * @urbd_stts_wrptr: DMA address of urbd_stts_wrptr + * @fr_bd_cb: DMA address of freeRB table + * @ur_bd_cb: DMA address of used RB table + * @fr_bd_wid: Initial index of the free table + */ +struct iwl_rfh_queue_data { + u8 q_num; + u8 enable; + __le16 reserved; + __le64 urbd_stts_wrptr; + __le64 fr_bd_cb; + __le64 ur_bd_cb; + __le32 fr_bd_wid; +} __packed; /* RFH_QUEUE_CONFIG_S_VER_1 */ + +/** + * struct iwl_rfh_queue_config - RX queue configuration + * @num_queues: number of queues configured + * @reserved: alignment + * @data: DMA addresses per-queue + */ +struct iwl_rfh_queue_config { + u8 num_queues; + u8 reserved[3]; + struct iwl_rfh_queue_data data[]; +} __packed; /* RFH_QUEUE_CONFIG_API_S_VER_1 */ + +#endif /* __iwl_fw_api_rx_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/scan.h b/drivers/net/wireless/intel/iwlwifi/fw/api/scan.h new file mode 100644 index 000000000..7ba0e3409 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/scan.h @@ -0,0 +1,1249 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_scan_h__ +#define __iwl_fw_api_scan_h__ + +/* Scan Commands, Responses, Notifications */ + +/** + * enum iwl_scan_subcmd_ids - scan commands + */ +enum iwl_scan_subcmd_ids { + /** + * @OFFLOAD_MATCH_INFO_NOTIF: &struct iwl_scan_offload_match_info + */ + OFFLOAD_MATCH_INFO_NOTIF = 0xFC, +}; + +/* Max number of IEs for direct SSID scans in a command */ +#define PROBE_OPTION_MAX 20 + +#define SCAN_SHORT_SSID_MAX_SIZE 8 +#define SCAN_BSSID_MAX_SIZE 16 + +/** + * struct iwl_ssid_ie - directed scan network information element + * + * Up to 20 of these may appear in REPLY_SCAN_CMD, + * selected by "type" bit field in struct iwl_scan_channel; + * each channel may select different ssids from among the 20 entries. + * SSID IEs get transmitted in reverse order of entry. + * + * @id: element ID + * @len: element length + * @ssid: element (SSID) data + */ +struct iwl_ssid_ie { + u8 id; + u8 len; + u8 ssid[IEEE80211_MAX_SSID_LEN]; +} __packed; /* SCAN_DIRECT_SSID_IE_API_S_VER_1 */ + +/* scan offload */ +#define IWL_SCAN_MAX_BLACKLIST_LEN 64 +#define IWL_SCAN_SHORT_BLACKLIST_LEN 16 +#define IWL_SCAN_MAX_PROFILES 11 +#define IWL_SCAN_MAX_PROFILES_V2 8 +#define SCAN_OFFLOAD_PROBE_REQ_SIZE 512 +#define SCAN_NUM_BAND_PROBE_DATA_V_1 2 +#define SCAN_NUM_BAND_PROBE_DATA_V_2 3 + +/* Default watchdog (in MS) for scheduled scan iteration */ +#define IWL_SCHED_SCAN_WATCHDOG cpu_to_le16(15000) + +#define IWL_GOOD_CRC_TH_DEFAULT cpu_to_le16(1) +#define CAN_ABORT_STATUS 1 + +#define IWL_FULL_SCAN_MULTIPLIER 5 +#define IWL_FAST_SCHED_SCAN_ITERATIONS 3 +#define IWL_MAX_SCHED_SCAN_PLANS 2 + +enum scan_framework_client { + SCAN_CLIENT_SCHED_SCAN = BIT(0), + SCAN_CLIENT_NETDETECT = BIT(1), + SCAN_CLIENT_ASSET_TRACKING = BIT(2), +}; + +/** + * struct iwl_scan_offload_blocklist - SCAN_OFFLOAD_BLACKLIST_S + * @ssid: MAC address to filter out + * @reported_rssi: AP rssi reported to the host + * @client_bitmap: clients ignore this entry - enum scan_framework_client + */ +struct iwl_scan_offload_blocklist { + u8 ssid[ETH_ALEN]; + u8 reported_rssi; + u8 client_bitmap; +} __packed; + +enum iwl_scan_offload_network_type { + IWL_NETWORK_TYPE_BSS = 1, + IWL_NETWORK_TYPE_IBSS = 2, + IWL_NETWORK_TYPE_ANY = 3, +}; + +enum iwl_scan_offload_band_selection { + IWL_SCAN_OFFLOAD_SELECT_2_4 = 0x4, + IWL_SCAN_OFFLOAD_SELECT_5_2 = 0x8, + IWL_SCAN_OFFLOAD_SELECT_ANY = 0xc, +}; + +enum iwl_scan_offload_auth_alg { + IWL_AUTH_ALGO_UNSUPPORTED = 0x00, + IWL_AUTH_ALGO_NONE = 0x01, + IWL_AUTH_ALGO_PSK = 0x02, + IWL_AUTH_ALGO_8021X = 0x04, + IWL_AUTH_ALGO_SAE = 0x08, + IWL_AUTH_ALGO_8021X_SHA384 = 0x10, + IWL_AUTH_ALGO_OWE = 0x20, +}; + +/** + * struct iwl_scan_offload_profile - SCAN_OFFLOAD_PROFILE_S + * @ssid_index: index to ssid list in fixed part + * @unicast_cipher: encryption algorithm to match - bitmap + * @auth_alg: authentication algorithm to match - bitmap + * @network_type: enum iwl_scan_offload_network_type + * @band_selection: enum iwl_scan_offload_band_selection + * @client_bitmap: clients waiting for match - enum scan_framework_client + * @reserved: reserved + */ +struct iwl_scan_offload_profile { + u8 ssid_index; + u8 unicast_cipher; + u8 auth_alg; + u8 network_type; + u8 band_selection; + u8 client_bitmap; + u8 reserved[2]; +} __packed; + +/** + * struct iwl_scan_offload_profile_cfg_data + * @blocklist_len: length of blocklist + * @num_profiles: num of profiles in the list + * @match_notify: clients waiting for match found notification + * @pass_match: clients waiting for the results + * @active_clients: active clients bitmap - enum scan_framework_client + * @any_beacon_notify: clients waiting for match notification without match + * @reserved: reserved + */ +struct iwl_scan_offload_profile_cfg_data { + u8 blocklist_len; + u8 num_profiles; + u8 match_notify; + u8 pass_match; + u8 active_clients; + u8 any_beacon_notify; + u8 reserved[2]; +} __packed; + +/** + * struct iwl_scan_offload_profile_cfg + * @profiles: profiles to search for match + * @data: the rest of the data for profile_cfg + */ +struct iwl_scan_offload_profile_cfg_v1 { + struct iwl_scan_offload_profile profiles[IWL_SCAN_MAX_PROFILES]; + struct iwl_scan_offload_profile_cfg_data data; +} __packed; /* SCAN_OFFLOAD_PROFILES_CFG_API_S_VER_1-2*/ + +/** + * struct iwl_scan_offload_profile_cfg + * @profiles: profiles to search for match + * @data: the rest of the data for profile_cfg + */ +struct iwl_scan_offload_profile_cfg { + struct iwl_scan_offload_profile profiles[IWL_SCAN_MAX_PROFILES_V2]; + struct iwl_scan_offload_profile_cfg_data data; +} __packed; /* SCAN_OFFLOAD_PROFILES_CFG_API_S_VER_3*/ + +/** + * struct iwl_scan_schedule_lmac - schedule of scan offload + * @delay: delay between iterations, in seconds. + * @iterations: num of scan iterations + * @full_scan_mul: number of partial scans before each full scan + */ +struct iwl_scan_schedule_lmac { + __le16 delay; + u8 iterations; + u8 full_scan_mul; +} __packed; /* SCAN_SCHEDULE_API_S */ + +enum iwl_scan_offload_complete_status { + IWL_SCAN_OFFLOAD_COMPLETED = 1, + IWL_SCAN_OFFLOAD_ABORTED = 2, +}; + +enum iwl_scan_ebs_status { + IWL_SCAN_EBS_SUCCESS, + IWL_SCAN_EBS_FAILED, + IWL_SCAN_EBS_CHAN_NOT_FOUND, + IWL_SCAN_EBS_INACTIVE, +}; + +/** + * struct iwl_scan_req_tx_cmd - SCAN_REQ_TX_CMD_API_S + * @tx_flags: combination of TX_CMD_FLG_* + * @rate_n_flags: rate for *all* Tx attempts, if TX_CMD_FLG_STA_RATE_MSK is + * cleared. Combination of RATE_MCS_* + * @sta_id: index of destination station in FW station table + * @reserved: for alignment and future use + */ +struct iwl_scan_req_tx_cmd { + __le32 tx_flags; + __le32 rate_n_flags; + u8 sta_id; + u8 reserved[3]; +} __packed; + +enum iwl_scan_channel_flags_lmac { + IWL_UNIFIED_SCAN_CHANNEL_FULL = BIT(27), + IWL_UNIFIED_SCAN_CHANNEL_PARTIAL = BIT(28), +}; + +/** + * struct iwl_scan_channel_cfg_lmac - SCAN_CHANNEL_CFG_S_VER2 + * @flags: bits 1-20: directed scan to i'th ssid + * other bits &enum iwl_scan_channel_flags_lmac + * @channel_num: channel number 1-13 etc + * @iter_count: scan iteration on this channel + * @iter_interval: interval in seconds between iterations on one channel + */ +struct iwl_scan_channel_cfg_lmac { + __le32 flags; + __le16 channel_num; + __le16 iter_count; + __le32 iter_interval; +} __packed; + +/** + * struct iwl_scan_probe_segment - PROBE_SEGMENT_API_S_VER_1 + * @offset: offset in the data block + * @len: length of the segment + */ +struct iwl_scan_probe_segment { + __le16 offset; + __le16 len; +} __packed; + +/** + * struct iwl_scan_probe_req_v1 - PROBE_REQUEST_FRAME_API_S_VER_2 + * @mac_header: first (and common) part of the probe + * @band_data: band specific data + * @common_data: last (and common) part of the probe + * @buf: raw data block + */ +struct iwl_scan_probe_req_v1 { + struct iwl_scan_probe_segment mac_header; + struct iwl_scan_probe_segment band_data[SCAN_NUM_BAND_PROBE_DATA_V_1]; + struct iwl_scan_probe_segment common_data; + u8 buf[SCAN_OFFLOAD_PROBE_REQ_SIZE]; +} __packed; + +/** + * struct iwl_scan_probe_req - PROBE_REQUEST_FRAME_API_S_VER_v2 + * @mac_header: first (and common) part of the probe + * @band_data: band specific data + * @common_data: last (and common) part of the probe + * @buf: raw data block + */ +struct iwl_scan_probe_req { + struct iwl_scan_probe_segment mac_header; + struct iwl_scan_probe_segment band_data[SCAN_NUM_BAND_PROBE_DATA_V_2]; + struct iwl_scan_probe_segment common_data; + u8 buf[SCAN_OFFLOAD_PROBE_REQ_SIZE]; +} __packed; + +enum iwl_scan_channel_flags { + IWL_SCAN_CHANNEL_FLAG_EBS = BIT(0), + IWL_SCAN_CHANNEL_FLAG_EBS_ACCURATE = BIT(1), + IWL_SCAN_CHANNEL_FLAG_CACHE_ADD = BIT(2), + IWL_SCAN_CHANNEL_FLAG_EBS_FRAG = BIT(3), + IWL_SCAN_CHANNEL_FLAG_FORCE_EBS = BIT(4), + IWL_SCAN_CHANNEL_FLAG_ENABLE_CHAN_ORDER = BIT(5), + IWL_SCAN_CHANNEL_FLAG_6G_PSC_NO_FILTER = BIT(6), +}; + +/** + * struct iwl_scan_channel_opt - CHANNEL_OPTIMIZATION_API_S + * @flags: enum iwl_scan_channel_flags + * @non_ebs_ratio: defines the ratio of number of scan iterations where EBS is + * involved. + * 1 - EBS is disabled. + * 2 - every second scan will be full scan(and so on). + */ +struct iwl_scan_channel_opt { + __le16 flags; + __le16 non_ebs_ratio; +} __packed; + +/** + * enum iwl_mvm_lmac_scan_flags - LMAC scan flags + * @IWL_MVM_LMAC_SCAN_FLAG_PASS_ALL: pass all beacons and probe responses + * without filtering. + * @IWL_MVM_LMAC_SCAN_FLAG_PASSIVE: force passive scan on all channels + * @IWL_MVM_LMAC_SCAN_FLAG_PRE_CONNECTION: single channel scan + * @IWL_MVM_LMAC_SCAN_FLAG_ITER_COMPLETE: send iteration complete notification + * @IWL_MVM_LMAC_SCAN_FLAG_MULTIPLE_SSIDS: multiple SSID matching + * @IWL_MVM_LMAC_SCAN_FLAG_FRAGMENTED: all passive scans will be fragmented + * @IWL_MVM_LMAC_SCAN_FLAGS_RRM_ENABLED: insert WFA vendor-specific TPC report + * and DS parameter set IEs into probe requests. + * @IWL_MVM_LMAC_SCAN_FLAG_EXTENDED_DWELL: use extended dwell time on channels + * 1, 6 and 11. + * @IWL_MVM_LMAC_SCAN_FLAG_MATCH: Send match found notification on matches + */ +enum iwl_mvm_lmac_scan_flags { + IWL_MVM_LMAC_SCAN_FLAG_PASS_ALL = BIT(0), + IWL_MVM_LMAC_SCAN_FLAG_PASSIVE = BIT(1), + IWL_MVM_LMAC_SCAN_FLAG_PRE_CONNECTION = BIT(2), + IWL_MVM_LMAC_SCAN_FLAG_ITER_COMPLETE = BIT(3), + IWL_MVM_LMAC_SCAN_FLAG_MULTIPLE_SSIDS = BIT(4), + IWL_MVM_LMAC_SCAN_FLAG_FRAGMENTED = BIT(5), + IWL_MVM_LMAC_SCAN_FLAGS_RRM_ENABLED = BIT(6), + IWL_MVM_LMAC_SCAN_FLAG_EXTENDED_DWELL = BIT(7), + IWL_MVM_LMAC_SCAN_FLAG_MATCH = BIT(9), +}; + +enum iwl_scan_priority { + IWL_SCAN_PRIORITY_LOW, + IWL_SCAN_PRIORITY_MEDIUM, + IWL_SCAN_PRIORITY_HIGH, +}; + +enum iwl_scan_priority_ext { + IWL_SCAN_PRIORITY_EXT_0_LOWEST, + IWL_SCAN_PRIORITY_EXT_1, + IWL_SCAN_PRIORITY_EXT_2, + IWL_SCAN_PRIORITY_EXT_3, + IWL_SCAN_PRIORITY_EXT_4, + IWL_SCAN_PRIORITY_EXT_5, + IWL_SCAN_PRIORITY_EXT_6, + IWL_SCAN_PRIORITY_EXT_7_HIGHEST, +}; + +/** + * struct iwl_scan_req_lmac - SCAN_REQUEST_CMD_API_S_VER_1 + * @reserved1: for alignment and future use + * @n_channels: num of channels to scan + * @active_dwell: dwell time for active channels + * @passive_dwell: dwell time for passive channels + * @fragmented_dwell: dwell time for fragmented passive scan + * @extended_dwell: dwell time for channels 1, 6 and 11 (in certain cases) + * @reserved2: for alignment and future use + * @rx_chain_select: PHY_RX_CHAIN_* flags + * @scan_flags: &enum iwl_mvm_lmac_scan_flags + * @max_out_time: max time (in TU) to be out of associated channel + * @suspend_time: pause scan this long (TUs) when returning to service channel + * @flags: RXON flags + * @filter_flags: RXON filter + * @tx_cmd: tx command for active scan; for 2GHz and for 5GHz + * @direct_scan: list of SSIDs for directed active scan + * @scan_prio: enum iwl_scan_priority + * @iter_num: number of scan iterations + * @delay: delay in seconds before first iteration + * @schedule: two scheduling plans. The first one is finite, the second one can + * be infinite. + * @channel_opt: channel optimization options, for full and partial scan + * @data: channel configuration and probe request packet. + */ +struct iwl_scan_req_lmac { + /* SCAN_REQUEST_FIXED_PART_API_S_VER_7 */ + __le32 reserved1; + u8 n_channels; + u8 active_dwell; + u8 passive_dwell; + u8 fragmented_dwell; + u8 extended_dwell; + u8 reserved2; + __le16 rx_chain_select; + __le32 scan_flags; + __le32 max_out_time; + __le32 suspend_time; + /* RX_ON_FLAGS_API_S_VER_1 */ + __le32 flags; + __le32 filter_flags; + struct iwl_scan_req_tx_cmd tx_cmd[2]; + struct iwl_ssid_ie direct_scan[PROBE_OPTION_MAX]; + __le32 scan_prio; + /* SCAN_REQ_PERIODIC_PARAMS_API_S */ + __le32 iter_num; + __le32 delay; + struct iwl_scan_schedule_lmac schedule[IWL_MAX_SCHED_SCAN_PLANS]; + struct iwl_scan_channel_opt channel_opt[2]; + u8 data[]; +} __packed; + +/** + * struct iwl_scan_results_notif - scan results for one channel - + * SCAN_RESULT_NTF_API_S_VER_3 + * @channel: which channel the results are from + * @band: 0 for 5.2 GHz, 1 for 2.4 GHz + * @probe_status: SCAN_PROBE_STATUS_*, indicates success of probe request + * @num_probe_not_sent: # of request that weren't sent due to not enough time + * @duration: duration spent in channel, in usecs + */ +struct iwl_scan_results_notif { + u8 channel; + u8 band; + u8 probe_status; + u8 num_probe_not_sent; + __le32 duration; +} __packed; + +/** + * struct iwl_lmac_scan_complete_notif - notifies end of scanning (all channels) + * SCAN_COMPLETE_NTF_API_S_VER_3 + * @scanned_channels: number of channels scanned (and number of valid results) + * @status: one of SCAN_COMP_STATUS_* + * @bt_status: BT on/off status + * @last_channel: last channel that was scanned + * @tsf_low: TSF timer (lower half) in usecs + * @tsf_high: TSF timer (higher half) in usecs + * @results: an array of scan results, only "scanned_channels" of them are valid + */ +struct iwl_lmac_scan_complete_notif { + u8 scanned_channels; + u8 status; + u8 bt_status; + u8 last_channel; + __le32 tsf_low; + __le32 tsf_high; + struct iwl_scan_results_notif results[]; +} __packed; + +/** + * struct iwl_scan_offload_complete - PERIODIC_SCAN_COMPLETE_NTF_API_S_VER_2 + * @last_schedule_line: last schedule line executed (fast or regular) + * @last_schedule_iteration: last scan iteration executed before scan abort + * @status: &enum iwl_scan_offload_complete_status + * @ebs_status: EBS success status &enum iwl_scan_ebs_status + * @time_after_last_iter: time in seconds elapsed after last iteration + * @reserved: reserved + */ +struct iwl_periodic_scan_complete { + u8 last_schedule_line; + u8 last_schedule_iteration; + u8 status; + u8 ebs_status; + __le32 time_after_last_iter; + __le32 reserved; +} __packed; + +/* UMAC Scan API */ + +/* The maximum of either of these cannot exceed 8, because we use an + * 8-bit mask (see IWL_MVM_SCAN_MASK in mvm.h). + */ +#define IWL_MVM_MAX_UMAC_SCANS 4 +#define IWL_MVM_MAX_LMAC_SCANS 1 + +enum scan_config_flags { + SCAN_CONFIG_FLAG_ACTIVATE = BIT(0), + SCAN_CONFIG_FLAG_DEACTIVATE = BIT(1), + SCAN_CONFIG_FLAG_FORBID_CHUB_REQS = BIT(2), + SCAN_CONFIG_FLAG_ALLOW_CHUB_REQS = BIT(3), + SCAN_CONFIG_FLAG_SET_TX_CHAINS = BIT(8), + SCAN_CONFIG_FLAG_SET_RX_CHAINS = BIT(9), + SCAN_CONFIG_FLAG_SET_AUX_STA_ID = BIT(10), + SCAN_CONFIG_FLAG_SET_ALL_TIMES = BIT(11), + SCAN_CONFIG_FLAG_SET_EFFECTIVE_TIMES = BIT(12), + SCAN_CONFIG_FLAG_SET_CHANNEL_FLAGS = BIT(13), + SCAN_CONFIG_FLAG_SET_LEGACY_RATES = BIT(14), + SCAN_CONFIG_FLAG_SET_MAC_ADDR = BIT(15), + SCAN_CONFIG_FLAG_SET_FRAGMENTED = BIT(16), + SCAN_CONFIG_FLAG_CLEAR_FRAGMENTED = BIT(17), + SCAN_CONFIG_FLAG_SET_CAM_MODE = BIT(18), + SCAN_CONFIG_FLAG_CLEAR_CAM_MODE = BIT(19), + SCAN_CONFIG_FLAG_SET_PROMISC_MODE = BIT(20), + SCAN_CONFIG_FLAG_CLEAR_PROMISC_MODE = BIT(21), + SCAN_CONFIG_FLAG_SET_LMAC2_FRAGMENTED = BIT(22), + SCAN_CONFIG_FLAG_CLEAR_LMAC2_FRAGMENTED = BIT(23), + + /* Bits 26-31 are for num of channels in channel_array */ +#define SCAN_CONFIG_N_CHANNELS(n) ((n) << 26) +}; + +enum scan_config_rates { + /* OFDM basic rates */ + SCAN_CONFIG_RATE_6M = BIT(0), + SCAN_CONFIG_RATE_9M = BIT(1), + SCAN_CONFIG_RATE_12M = BIT(2), + SCAN_CONFIG_RATE_18M = BIT(3), + SCAN_CONFIG_RATE_24M = BIT(4), + SCAN_CONFIG_RATE_36M = BIT(5), + SCAN_CONFIG_RATE_48M = BIT(6), + SCAN_CONFIG_RATE_54M = BIT(7), + /* CCK basic rates */ + SCAN_CONFIG_RATE_1M = BIT(8), + SCAN_CONFIG_RATE_2M = BIT(9), + SCAN_CONFIG_RATE_5M = BIT(10), + SCAN_CONFIG_RATE_11M = BIT(11), + + /* Bits 16-27 are for supported rates */ +#define SCAN_CONFIG_SUPPORTED_RATE(rate) ((rate) << 16) +}; + +enum iwl_channel_flags { + IWL_CHANNEL_FLAG_EBS = BIT(0), + IWL_CHANNEL_FLAG_ACCURATE_EBS = BIT(1), + IWL_CHANNEL_FLAG_EBS_ADD = BIT(2), + IWL_CHANNEL_FLAG_PRE_SCAN_PASSIVE2ACTIVE = BIT(3), +}; + +enum iwl_uhb_chan_cfg_flags { + IWL_UHB_CHAN_CFG_FLAG_UNSOLICITED_PROBE_RES = BIT(24), + IWL_UHB_CHAN_CFG_FLAG_PSC_CHAN_NO_LISTEN = BIT(25), + IWL_UHB_CHAN_CFG_FLAG_FORCE_PASSIVE = BIT(26), +}; +/** + * struct iwl_scan_dwell + * @active: default dwell time for active scan + * @passive: default dwell time for passive scan + * @fragmented: default dwell time for fragmented scan + * @extended: default dwell time for channels 1, 6 and 11 + */ +struct iwl_scan_dwell { + u8 active; + u8 passive; + u8 fragmented; + u8 extended; +} __packed; + +/** + * struct iwl_scan_config_v1 - scan configuration command + * @flags: enum scan_config_flags + * @tx_chains: valid_tx antenna - ANT_* definitions + * @rx_chains: valid_rx antenna - ANT_* definitions + * @legacy_rates: default legacy rates - enum scan_config_rates + * @out_of_channel_time: default max out of serving channel time + * @suspend_time: default max suspend time + * @dwell: dwells for the scan + * @mac_addr: default mac address to be used in probes + * @bcast_sta_id: the index of the station in the fw + * @channel_flags: default channel flags - enum iwl_channel_flags + * scan_config_channel_flag + * @channel_array: default supported channels + */ +struct iwl_scan_config_v1 { + __le32 flags; + __le32 tx_chains; + __le32 rx_chains; + __le32 legacy_rates; + __le32 out_of_channel_time; + __le32 suspend_time; + struct iwl_scan_dwell dwell; + u8 mac_addr[ETH_ALEN]; + u8 bcast_sta_id; + u8 channel_flags; + u8 channel_array[]; +} __packed; /* SCAN_CONFIG_DB_CMD_API_S */ + +#define SCAN_TWO_LMACS 2 +#define SCAN_LB_LMAC_IDX 0 +#define SCAN_HB_LMAC_IDX 1 + +/** + * struct iwl_scan_config_v2 - scan configuration command + * @flags: enum scan_config_flags + * @tx_chains: valid_tx antenna - ANT_* definitions + * @rx_chains: valid_rx antenna - ANT_* definitions + * @legacy_rates: default legacy rates - enum scan_config_rates + * @out_of_channel_time: default max out of serving channel time + * @suspend_time: default max suspend time + * @dwell: dwells for the scan + * @mac_addr: default mac address to be used in probes + * @bcast_sta_id: the index of the station in the fw + * @channel_flags: default channel flags - enum iwl_channel_flags + * scan_config_channel_flag + * @channel_array: default supported channels + */ +struct iwl_scan_config_v2 { + __le32 flags; + __le32 tx_chains; + __le32 rx_chains; + __le32 legacy_rates; + __le32 out_of_channel_time[SCAN_TWO_LMACS]; + __le32 suspend_time[SCAN_TWO_LMACS]; + struct iwl_scan_dwell dwell; + u8 mac_addr[ETH_ALEN]; + u8 bcast_sta_id; + u8 channel_flags; + u8 channel_array[]; +} __packed; /* SCAN_CONFIG_DB_CMD_API_S_2 */ + +/** + * struct iwl_scan_config - scan configuration command + * @enable_cam_mode: whether to enable CAM mode. + * @enable_promiscouos_mode: whether to enable promiscouos mode + * @bcast_sta_id: the index of the station in the fw. Deprecated starting with + * API version 5. + * @reserved: reserved + * @tx_chains: valid_tx antenna - ANT_* definitions + * @rx_chains: valid_rx antenna - ANT_* definitions + */ +struct iwl_scan_config { + u8 enable_cam_mode; + u8 enable_promiscouos_mode; + u8 bcast_sta_id; + u8 reserved; + __le32 tx_chains; + __le32 rx_chains; +} __packed; /* SCAN_CONFIG_DB_CMD_API_S_5 */ + +/** + * enum iwl_umac_scan_flags - UMAC scan flags + * @IWL_UMAC_SCAN_FLAG_PREEMPTIVE: scan process triggered by this scan request + * can be preempted by other scan requests with higher priority. + * The low priority scan will be resumed when the higher proirity scan is + * completed. + * @IWL_UMAC_SCAN_FLAG_START_NOTIF: notification will be sent to the driver + * when scan starts. + */ +enum iwl_umac_scan_flags { + IWL_UMAC_SCAN_FLAG_PREEMPTIVE = BIT(0), + IWL_UMAC_SCAN_FLAG_START_NOTIF = BIT(1), +}; + +enum iwl_umac_scan_uid_offsets { + IWL_UMAC_SCAN_UID_TYPE_OFFSET = 0, + IWL_UMAC_SCAN_UID_SEQ_OFFSET = 8, +}; + +enum iwl_umac_scan_general_flags { + IWL_UMAC_SCAN_GEN_FLAGS_PERIODIC = BIT(0), + IWL_UMAC_SCAN_GEN_FLAGS_OVER_BT = BIT(1), + IWL_UMAC_SCAN_GEN_FLAGS_PASS_ALL = BIT(2), + IWL_UMAC_SCAN_GEN_FLAGS_PASSIVE = BIT(3), + IWL_UMAC_SCAN_GEN_FLAGS_PRE_CONNECT = BIT(4), + IWL_UMAC_SCAN_GEN_FLAGS_ITER_COMPLETE = BIT(5), + IWL_UMAC_SCAN_GEN_FLAGS_MULTIPLE_SSID = BIT(6), + IWL_UMAC_SCAN_GEN_FLAGS_FRAGMENTED = BIT(7), + IWL_UMAC_SCAN_GEN_FLAGS_RRM_ENABLED = BIT(8), + IWL_UMAC_SCAN_GEN_FLAGS_MATCH = BIT(9), + IWL_UMAC_SCAN_GEN_FLAGS_EXTENDED_DWELL = BIT(10), + /* Extended dwell is obselete when adaptive dwell is used, making this + * bit reusable. Hence, probe request defer is used only when adaptive + * dwell is supported. */ + IWL_UMAC_SCAN_GEN_FLAGS_PROB_REQ_DEFER_SUPP = BIT(10), + IWL_UMAC_SCAN_GEN_FLAGS_LMAC2_FRAGMENTED = BIT(11), + IWL_UMAC_SCAN_GEN_FLAGS_ADAPTIVE_DWELL = BIT(13), + IWL_UMAC_SCAN_GEN_FLAGS_MAX_CHNL_TIME = BIT(14), + IWL_UMAC_SCAN_GEN_FLAGS_PROB_REQ_HIGH_TX_RATE = BIT(15), +}; + +/** + * enum iwl_umac_scan_general_flags2 - UMAC scan general flags #2 + * @IWL_UMAC_SCAN_GEN_FLAGS2_NOTIF_PER_CHNL: Whether to send a complete + * notification per channel or not. + * @IWL_UMAC_SCAN_GEN_FLAGS2_ALLOW_CHNL_REORDER: Whether to allow channel + * reorder optimization or not. + */ +enum iwl_umac_scan_general_flags2 { + IWL_UMAC_SCAN_GEN_FLAGS2_NOTIF_PER_CHNL = BIT(0), + IWL_UMAC_SCAN_GEN_FLAGS2_ALLOW_CHNL_REORDER = BIT(1), +}; + +/** + * enum iwl_umac_scan_general_flags_v2 - UMAC scan general flags version 2 + * + * The FW flags were reordered and hence the driver introduce version 2 + * + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_PERIODIC: periodic or scheduled + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_PASS_ALL: pass all probe responses and beacons + * during scan iterations + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_NTFY_ITER_COMPLETE: send complete notification + * on every iteration instead of only once after the last iteration + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC1: fragmented scan LMAC1 + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC2: fragmented scan LMAC2 + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_MATCH: does this scan check for profile matching + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_USE_ALL_RX_CHAINS: use all valid chains for RX + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_ADAPTIVE_DWELL: works with adaptive dwell + * for active channel + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_PREEMPTIVE: can be preempted by other requests + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_NTF_START: send notification of scan start + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_MULTI_SSID: matching on multiple SSIDs + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_FORCE_PASSIVE: all the channels scanned + * as passive + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_TRIGGER_UHB_SCAN: at the end of 2.4GHz and + * 5.2Ghz bands scan, trigger scan on 6GHz band to discover + * the reported collocated APs + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_6GHZ_PASSIVE_SCAN: at the end of 2.4GHz and 5GHz + * bands scan, if not APs were discovered, allow scan to conitnue and scan + * 6GHz PSC channels in order to discover country information. + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_6GHZ_PASSIVE_SCAN_FILTER_IN: in case + * &IWL_UMAC_SCAN_GEN_FLAGS_V2_6GHZ_PASSIVE_SCAN is enabled and scan is + * activated over 6GHz PSC channels, filter in beacons and probe responses. + * @IWL_UMAC_SCAN_GEN_FLAGS_V2_OCE: if set, send probe requests in a minimum + * rate of 5.5Mpbs, filter in broadcast probe responses and set the max + * channel time indication field in the FILS request parameters element + * (if included by the driver in the probe request IEs). + */ +enum iwl_umac_scan_general_flags_v2 { + IWL_UMAC_SCAN_GEN_FLAGS_V2_PERIODIC = BIT(0), + IWL_UMAC_SCAN_GEN_FLAGS_V2_PASS_ALL = BIT(1), + IWL_UMAC_SCAN_GEN_FLAGS_V2_NTFY_ITER_COMPLETE = BIT(2), + IWL_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC1 = BIT(3), + IWL_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC2 = BIT(4), + IWL_UMAC_SCAN_GEN_FLAGS_V2_MATCH = BIT(5), + IWL_UMAC_SCAN_GEN_FLAGS_V2_USE_ALL_RX_CHAINS = BIT(6), + IWL_UMAC_SCAN_GEN_FLAGS_V2_ADAPTIVE_DWELL = BIT(7), + IWL_UMAC_SCAN_GEN_FLAGS_V2_PREEMPTIVE = BIT(8), + IWL_UMAC_SCAN_GEN_FLAGS_V2_NTF_START = BIT(9), + IWL_UMAC_SCAN_GEN_FLAGS_V2_MULTI_SSID = BIT(10), + IWL_UMAC_SCAN_GEN_FLAGS_V2_FORCE_PASSIVE = BIT(11), + IWL_UMAC_SCAN_GEN_FLAGS_V2_TRIGGER_UHB_SCAN = BIT(12), + IWL_UMAC_SCAN_GEN_FLAGS_V2_6GHZ_PASSIVE_SCAN = BIT(13), + IWL_UMAC_SCAN_GEN_FLAGS_V2_6GHZ_PASSIVE_SCAN_FILTER_IN = BIT(14), + IWL_UMAC_SCAN_GEN_FLAGS_V2_OCE = BIT(15), +}; + +/** + * enum iwl_umac_scan_general_params_flags2 - UMAC scan general flags2 + * + * @IWL_UMAC_SCAN_GEN_PARAMS_FLAGS2_RESPECT_P2P_GO_LB: scan event scheduling + * should be aware of a P2P GO operation on the 2GHz band. + * @IWL_UMAC_SCAN_GEN_PARAMS_FLAGS2_RESPECT_P2P_GO_HB: scan event scheduling + * should be aware of a P2P GO operation on the 5GHz or 6GHz band. + */ +enum iwl_umac_scan_general_params_flags2 { + IWL_UMAC_SCAN_GEN_PARAMS_FLAGS2_RESPECT_P2P_GO_LB = BIT(0), + IWL_UMAC_SCAN_GEN_PARAMS_FLAGS2_RESPECT_P2P_GO_HB = BIT(1), +}; + +/** + * struct iwl_scan_channel_cfg_umac + * @flags: bitmap - 0-19: directed scan to i'th ssid. + * @channel_num: channel number 1-13 etc. + * @band: band of channel: 0 for 2GHz, 1 for 5GHz + * @iter_count: repetition count for the channel. + * @iter_interval: interval between two scan iterations on one channel. + */ +struct iwl_scan_channel_cfg_umac { + __le32 flags; + /* Both versions are of the same size, so use a union without adjusting + * the command size later + */ + union { + struct { + u8 channel_num; + u8 iter_count; + __le16 iter_interval; + } v1; /* SCAN_CHANNEL_CONFIG_API_S_VER_1 */ + struct { + u8 channel_num; + u8 band; + u8 iter_count; + u8 iter_interval; + } v2; /* SCAN_CHANNEL_CONFIG_API_S_VER_2 + * SCAN_CHANNEL_CONFIG_API_S_VER_3 + * SCAN_CHANNEL_CONFIG_API_S_VER_4 + */ + }; +} __packed; + +/** + * struct iwl_scan_umac_schedule + * @interval: interval in seconds between scan iterations + * @iter_count: num of scan iterations for schedule plan, 0xff for infinite loop + * @reserved: for alignment and future use + */ +struct iwl_scan_umac_schedule { + __le16 interval; + u8 iter_count; + u8 reserved; +} __packed; /* SCAN_SCHED_PARAM_API_S_VER_1 */ + +struct iwl_scan_req_umac_tail_v1 { + /* SCAN_PERIODIC_PARAMS_API_S_VER_1 */ + struct iwl_scan_umac_schedule schedule[IWL_MAX_SCHED_SCAN_PLANS]; + __le16 delay; + __le16 reserved; + /* SCAN_PROBE_PARAMS_API_S_VER_1 */ + struct iwl_scan_probe_req_v1 preq; + struct iwl_ssid_ie direct_scan[PROBE_OPTION_MAX]; +} __packed; + +/** + * struct iwl_scan_req_umac_tail - the rest of the UMAC scan request command + * parameters following channels configuration array. + * @schedule: two scheduling plans. + * @delay: delay in TUs before starting the first scan iteration + * @reserved: for future use and alignment + * @preq: probe request with IEs blocks + * @direct_scan: list of SSIDs for directed active scan + */ +struct iwl_scan_req_umac_tail_v2 { + /* SCAN_PERIODIC_PARAMS_API_S_VER_1 */ + struct iwl_scan_umac_schedule schedule[IWL_MAX_SCHED_SCAN_PLANS]; + __le16 delay; + __le16 reserved; + /* SCAN_PROBE_PARAMS_API_S_VER_2 */ + struct iwl_scan_probe_req preq; + struct iwl_ssid_ie direct_scan[PROBE_OPTION_MAX]; +} __packed; + +/** + * struct iwl_scan_umac_chan_param + * @flags: channel flags &enum iwl_scan_channel_flags + * @count: num of channels in scan request + * @reserved: for future use and alignment + */ +struct iwl_scan_umac_chan_param { + u8 flags; + u8 count; + __le16 reserved; +} __packed; /*SCAN_CHANNEL_PARAMS_API_S_VER_1 */ + +/** + * struct iwl_scan_req_umac + * @flags: &enum iwl_umac_scan_flags + * @uid: scan id, &enum iwl_umac_scan_uid_offsets + * @ooc_priority: out of channel priority - &enum iwl_scan_priority + * @general_flags: &enum iwl_umac_scan_general_flags + * @scan_start_mac_id: report the scan start TSF time according to this mac TSF + * @extended_dwell: dwell time for channels 1, 6 and 11 + * @active_dwell: dwell time for active scan per LMAC + * @passive_dwell: dwell time for passive scan per LMAC + * @fragmented_dwell: dwell time for fragmented passive scan + * @adwell_default_n_aps: for adaptive dwell the default number of APs + * per channel + * @adwell_default_n_aps_social: for adaptive dwell the default + * number of APs per social (1,6,11) channel + * @general_flags2: &enum iwl_umac_scan_general_flags2 + * @adwell_max_budget: for adaptive dwell the maximal budget of TU to be added + * to total scan time + * @max_out_time: max out of serving channel time, per LMAC - for CDB there + * are 2 LMACs + * @suspend_time: max suspend time, per LMAC - for CDB there are 2 LMACs + * @scan_priority: scan internal prioritization &enum iwl_scan_priority + * @num_of_fragments: Number of fragments needed for full coverage per band. + * Relevant only for fragmented scan. + * @channel: &struct iwl_scan_umac_chan_param + * @reserved: for future use and alignment + * @reserved3: for future use and alignment + * @data: &struct iwl_scan_channel_cfg_umac and + * &struct iwl_scan_req_umac_tail + */ +struct iwl_scan_req_umac { + __le32 flags; + __le32 uid; + __le32 ooc_priority; + __le16 general_flags; + u8 reserved; + u8 scan_start_mac_id; + union { + struct { + u8 extended_dwell; + u8 active_dwell; + u8 passive_dwell; + u8 fragmented_dwell; + __le32 max_out_time; + __le32 suspend_time; + __le32 scan_priority; + struct iwl_scan_umac_chan_param channel; + u8 data[]; + } v1; /* SCAN_REQUEST_CMD_UMAC_API_S_VER_1 */ + struct { + u8 extended_dwell; + u8 active_dwell; + u8 passive_dwell; + u8 fragmented_dwell; + __le32 max_out_time[SCAN_TWO_LMACS]; + __le32 suspend_time[SCAN_TWO_LMACS]; + __le32 scan_priority; + struct iwl_scan_umac_chan_param channel; + u8 data[]; + } v6; /* SCAN_REQUEST_CMD_UMAC_API_S_VER_6 */ + struct { + u8 active_dwell; + u8 passive_dwell; + u8 fragmented_dwell; + u8 adwell_default_n_aps; + u8 adwell_default_n_aps_social; + u8 reserved3; + __le16 adwell_max_budget; + __le32 max_out_time[SCAN_TWO_LMACS]; + __le32 suspend_time[SCAN_TWO_LMACS]; + __le32 scan_priority; + struct iwl_scan_umac_chan_param channel; + u8 data[]; + } v7; /* SCAN_REQUEST_CMD_UMAC_API_S_VER_7 */ + struct { + u8 active_dwell[SCAN_TWO_LMACS]; + u8 reserved2; + u8 adwell_default_n_aps; + u8 adwell_default_n_aps_social; + u8 general_flags2; + __le16 adwell_max_budget; + __le32 max_out_time[SCAN_TWO_LMACS]; + __le32 suspend_time[SCAN_TWO_LMACS]; + __le32 scan_priority; + u8 passive_dwell[SCAN_TWO_LMACS]; + u8 num_of_fragments[SCAN_TWO_LMACS]; + struct iwl_scan_umac_chan_param channel; + u8 data[]; + } v8; /* SCAN_REQUEST_CMD_UMAC_API_S_VER_8 */ + struct { + u8 active_dwell[SCAN_TWO_LMACS]; + u8 adwell_default_hb_n_aps; + u8 adwell_default_lb_n_aps; + u8 adwell_default_n_aps_social; + u8 general_flags2; + __le16 adwell_max_budget; + __le32 max_out_time[SCAN_TWO_LMACS]; + __le32 suspend_time[SCAN_TWO_LMACS]; + __le32 scan_priority; + u8 passive_dwell[SCAN_TWO_LMACS]; + u8 num_of_fragments[SCAN_TWO_LMACS]; + struct iwl_scan_umac_chan_param channel; + u8 data[]; + } v9; /* SCAN_REQUEST_CMD_UMAC_API_S_VER_9 */ + }; +} __packed; + +#define IWL_SCAN_REQ_UMAC_SIZE_V8 sizeof(struct iwl_scan_req_umac) +#define IWL_SCAN_REQ_UMAC_SIZE_V7 48 +#define IWL_SCAN_REQ_UMAC_SIZE_V6 44 +#define IWL_SCAN_REQ_UMAC_SIZE_V1 36 + +/** + * struct iwl_scan_probe_params_v3 + * @preq: scan probe request params + * @ssid_num: number of valid SSIDs in direct scan array + * @short_ssid_num: number of valid short SSIDs in short ssid array + * @bssid_num: number of valid bssid in bssids array + * @reserved: reserved + * @direct_scan: list of ssids + * @short_ssid: array of short ssids + * @bssid_array: array of bssids + */ +struct iwl_scan_probe_params_v3 { + struct iwl_scan_probe_req preq; + u8 ssid_num; + u8 short_ssid_num; + u8 bssid_num; + u8 reserved; + struct iwl_ssid_ie direct_scan[PROBE_OPTION_MAX]; + __le32 short_ssid[SCAN_SHORT_SSID_MAX_SIZE]; + u8 bssid_array[SCAN_BSSID_MAX_SIZE][ETH_ALEN]; +} __packed; /* SCAN_PROBE_PARAMS_API_S_VER_3 */ + +/** + * struct iwl_scan_probe_params_v4 + * @preq: scan probe request params + * @short_ssid_num: number of valid short SSIDs in short ssid array + * @bssid_num: number of valid bssid in bssids array + * @reserved: reserved + * @direct_scan: list of ssids + * @short_ssid: array of short ssids + * @bssid_array: array of bssids + */ +struct iwl_scan_probe_params_v4 { + struct iwl_scan_probe_req preq; + u8 short_ssid_num; + u8 bssid_num; + __le16 reserved; + struct iwl_ssid_ie direct_scan[PROBE_OPTION_MAX]; + __le32 short_ssid[SCAN_SHORT_SSID_MAX_SIZE]; + u8 bssid_array[SCAN_BSSID_MAX_SIZE][ETH_ALEN]; +} __packed; /* SCAN_PROBE_PARAMS_API_S_VER_4 */ + +#define SCAN_MAX_NUM_CHANS_V3 67 + +/** + * struct iwl_scan_channel_params_v4 + * @flags: channel flags &enum iwl_scan_channel_flags + * @count: num of channels in scan request + * @num_of_aps_override: override the number of APs the FW uses to calculate + * dwell time when adaptive dwell is used + * @reserved: for future use and alignment + * @channel_config: array of explicit channel configurations + * for 2.4Ghz and 5.2Ghz bands + * @adwell_ch_override_bitmap: when using adaptive dwell, override the number + * of APs value with &num_of_aps_override for the channel. + * To cast channel to index, use &iwl_mvm_scan_ch_and_band_to_idx + */ +struct iwl_scan_channel_params_v4 { + u8 flags; + u8 count; + u8 num_of_aps_override; + u8 reserved; + struct iwl_scan_channel_cfg_umac channel_config[SCAN_MAX_NUM_CHANS_V3]; + u8 adwell_ch_override_bitmap[16]; +} __packed; /* SCAN_CHANNEL_PARAMS_API_S_VER_4 also + SCAN_CHANNEL_PARAMS_API_S_VER_5 */ + +/** + * struct iwl_scan_channel_params_v6 + * @flags: channel flags &enum iwl_scan_channel_flags + * @count: num of channels in scan request + * @n_aps_override: override the number of APs the FW uses to calculate dwell + * time when adaptive dwell is used. + * Channel k will use n_aps_override[i] when BIT(20 + i) is set in + * channel_config[k].flags + * @channel_config: array of explicit channel configurations + * for 2.4Ghz and 5.2Ghz bands + */ +struct iwl_scan_channel_params_v6 { + u8 flags; + u8 count; + u8 n_aps_override[2]; + struct iwl_scan_channel_cfg_umac channel_config[SCAN_MAX_NUM_CHANS_V3]; +} __packed; /* SCAN_CHANNEL_PARAMS_API_S_VER_6 */ + +/** + * struct iwl_scan_general_params_v11 + * @flags: &enum iwl_umac_scan_general_flags_v2 + * @reserved: reserved for future + * @scan_start_mac_id: report the scan start TSF time according to this mac TSF + * @active_dwell: dwell time for active scan per LMAC + * @adwell_default_2g: adaptive dwell default number of APs + * for 2.4GHz channel + * @adwell_default_5g: adaptive dwell default number of APs + * for 5GHz channels + * @adwell_default_social_chn: adaptive dwell default number of + * APs per social channel + * @flags2: for version 11 see &enum iwl_umac_scan_general_params_flags2. + * Otherwise reserved. + * @adwell_max_budget: the maximal number of TUs that adaptive dwell + * can add to the total scan time + * @max_out_of_time: max out of serving channel time, per LMAC + * @suspend_time: max suspend time, per LMAC + * @scan_priority: priority of the request + * @passive_dwell: continues dwell time for passive channel + * (without adaptive dwell) + * @num_of_fragments: number of fragments needed for full fragmented + * scan coverage. + */ +struct iwl_scan_general_params_v11 { + __le16 flags; + u8 reserved; + u8 scan_start_mac_id; + u8 active_dwell[SCAN_TWO_LMACS]; + u8 adwell_default_2g; + u8 adwell_default_5g; + u8 adwell_default_social_chn; + u8 flags2; + __le16 adwell_max_budget; + __le32 max_out_of_time[SCAN_TWO_LMACS]; + __le32 suspend_time[SCAN_TWO_LMACS]; + __le32 scan_priority; + u8 passive_dwell[SCAN_TWO_LMACS]; + u8 num_of_fragments[SCAN_TWO_LMACS]; +} __packed; /* SCAN_GENERAL_PARAMS_API_S_VER_11 and *_VER_10 */ + +/** + * struct iwl_scan_periodic_parms_v1 + * @schedule: can scheduling parameter + * @delay: initial delay of the periodic scan in seconds + * @reserved: reserved for future + */ +struct iwl_scan_periodic_parms_v1 { + struct iwl_scan_umac_schedule schedule[IWL_MAX_SCHED_SCAN_PLANS]; + __le16 delay; + __le16 reserved; +} __packed; /* SCAN_PERIODIC_PARAMS_API_S_VER_1 */ + +/** + * struct iwl_scan_req_params_v12 + * @general_params: &struct iwl_scan_general_params_v11 + * @channel_params: &struct iwl_scan_channel_params_v4 + * @periodic_params: &struct iwl_scan_periodic_parms_v1 + * @probe_params: &struct iwl_scan_probe_params_v3 + */ +struct iwl_scan_req_params_v12 { + struct iwl_scan_general_params_v11 general_params; + struct iwl_scan_channel_params_v4 channel_params; + struct iwl_scan_periodic_parms_v1 periodic_params; + struct iwl_scan_probe_params_v3 probe_params; +} __packed; /* SCAN_REQUEST_PARAMS_API_S_VER_12 */ + +/** + * struct iwl_scan_req_params_v15 + * @general_params: &struct iwl_scan_general_params_v11 + * @channel_params: &struct iwl_scan_channel_params_v6 + * @periodic_params: &struct iwl_scan_periodic_parms_v1 + * @probe_params: &struct iwl_scan_probe_params_v4 + */ +struct iwl_scan_req_params_v15 { + struct iwl_scan_general_params_v11 general_params; + struct iwl_scan_channel_params_v6 channel_params; + struct iwl_scan_periodic_parms_v1 periodic_params; + struct iwl_scan_probe_params_v4 probe_params; +} __packed; /* SCAN_REQUEST_PARAMS_API_S_VER_15 and *_VER_14 */ + +/** + * struct iwl_scan_req_umac_v12 + * @uid: scan id, &enum iwl_umac_scan_uid_offsets + * @ooc_priority: out of channel priority - &enum iwl_scan_priority + * @scan_params: scan parameters + */ +struct iwl_scan_req_umac_v12 { + __le32 uid; + __le32 ooc_priority; + struct iwl_scan_req_params_v12 scan_params; +} __packed; /* SCAN_REQUEST_CMD_UMAC_API_S_VER_12 */ + +/** + * struct iwl_scan_req_umac_v15 + * @uid: scan id, &enum iwl_umac_scan_uid_offsets + * @ooc_priority: out of channel priority - &enum iwl_scan_priority + * @scan_params: scan parameters + */ +struct iwl_scan_req_umac_v15 { + __le32 uid; + __le32 ooc_priority; + struct iwl_scan_req_params_v15 scan_params; +} __packed; /* SCAN_REQUEST_CMD_UMAC_API_S_VER_15 and *_VER_14 */ + +/** + * struct iwl_umac_scan_abort + * @uid: scan id, &enum iwl_umac_scan_uid_offsets + * @flags: reserved + */ +struct iwl_umac_scan_abort { + __le32 uid; + __le32 flags; +} __packed; /* SCAN_ABORT_CMD_UMAC_API_S_VER_1 */ + +/** + * struct iwl_umac_scan_complete + * @uid: scan id, &enum iwl_umac_scan_uid_offsets + * @last_schedule: last scheduling line + * @last_iter: last scan iteration number + * @status: &enum iwl_scan_offload_complete_status + * @ebs_status: &enum iwl_scan_ebs_status + * @time_from_last_iter: time elapsed from last iteration + * @reserved: for future use + */ +struct iwl_umac_scan_complete { + __le32 uid; + u8 last_schedule; + u8 last_iter; + u8 status; + u8 ebs_status; + __le32 time_from_last_iter; + __le32 reserved; +} __packed; /* SCAN_COMPLETE_NTF_UMAC_API_S_VER_1 */ + +#define SCAN_OFFLOAD_MATCHING_CHANNELS_LEN_V1 5 +#define SCAN_OFFLOAD_MATCHING_CHANNELS_LEN 7 + +/** + * struct iwl_scan_offload_profile_match_v1 - match information + * @bssid: matched bssid + * @reserved: reserved + * @channel: channel where the match occurred + * @energy: energy + * @matching_feature: feature matches + * @matching_channels: bitmap of channels that matched, referencing + * the channels passed in the scan offload request. + */ +struct iwl_scan_offload_profile_match_v1 { + u8 bssid[ETH_ALEN]; + __le16 reserved; + u8 channel; + u8 energy; + u8 matching_feature; + u8 matching_channels[SCAN_OFFLOAD_MATCHING_CHANNELS_LEN_V1]; +} __packed; /* SCAN_OFFLOAD_PROFILE_MATCH_RESULTS_S_VER_1 */ + +/** + * struct iwl_scan_offload_profiles_query_v1 - match results query response + * @matched_profiles: bitmap of matched profiles, referencing the + * matches passed in the scan offload request + * @last_scan_age: age of the last offloaded scan + * @n_scans_done: number of offloaded scans done + * @gp2_d0u: GP2 when D0U occurred + * @gp2_invoked: GP2 when scan offload was invoked + * @resume_while_scanning: not used + * @self_recovery: obsolete + * @reserved: reserved + * @matches: array of match information, one for each match + */ +struct iwl_scan_offload_profiles_query_v1 { + __le32 matched_profiles; + __le32 last_scan_age; + __le32 n_scans_done; + __le32 gp2_d0u; + __le32 gp2_invoked; + u8 resume_while_scanning; + u8 self_recovery; + __le16 reserved; + struct iwl_scan_offload_profile_match_v1 matches[]; +} __packed; /* SCAN_OFFLOAD_PROFILES_QUERY_RSP_S_VER_2 */ + +/** + * struct iwl_scan_offload_profile_match - match information + * @bssid: matched bssid + * @reserved: reserved + * @channel: channel where the match occurred + * @energy: energy + * @matching_feature: feature matches + * @matching_channels: bitmap of channels that matched, referencing + * the channels passed in the scan offload request. + */ +struct iwl_scan_offload_profile_match { + u8 bssid[ETH_ALEN]; + __le16 reserved; + u8 channel; + u8 energy; + u8 matching_feature; + u8 matching_channels[SCAN_OFFLOAD_MATCHING_CHANNELS_LEN]; +} __packed; /* SCAN_OFFLOAD_PROFILE_MATCH_RESULTS_S_VER_2 */ + +/** + * struct iwl_scan_offload_match_info - match results information + * @matched_profiles: bitmap of matched profiles, referencing the + * matches passed in the scan offload request + * @last_scan_age: age of the last offloaded scan + * @n_scans_done: number of offloaded scans done + * @gp2_d0u: GP2 when D0U occurred + * @gp2_invoked: GP2 when scan offload was invoked + * @resume_while_scanning: not used + * @self_recovery: obsolete + * @reserved: reserved + * @matches: array of match information, one for each match + */ +struct iwl_scan_offload_match_info { + __le32 matched_profiles; + __le32 last_scan_age; + __le32 n_scans_done; + __le32 gp2_d0u; + __le32 gp2_invoked; + u8 resume_while_scanning; + u8 self_recovery; + __le16 reserved; + struct iwl_scan_offload_profile_match matches[]; +} __packed; /* SCAN_OFFLOAD_PROFILES_QUERY_RSP_S_VER_3 and + * SCAN_OFFLOAD_MATCH_INFO_NOTIFICATION_S_VER_1 + */ + +/** + * struct iwl_umac_scan_iter_complete_notif - notifies end of scanning iteration + * @uid: scan id, &enum iwl_umac_scan_uid_offsets + * @scanned_channels: number of channels scanned and number of valid elements in + * results array + * @status: one of SCAN_COMP_STATUS_* + * @bt_status: BT on/off status + * @last_channel: last channel that was scanned + * @start_tsf: TSF timer in usecs of the scan start time for the mac specified + * in &struct iwl_scan_req_umac. + * @results: array of scan results, length in @scanned_channels + */ +struct iwl_umac_scan_iter_complete_notif { + __le32 uid; + u8 scanned_channels; + u8 status; + u8 bt_status; + u8 last_channel; + __le64 start_tsf; + struct iwl_scan_results_notif results[]; +} __packed; /* SCAN_ITER_COMPLETE_NTF_UMAC_API_S_VER_2 */ + +#endif /* __iwl_fw_api_scan_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/sf.h b/drivers/net/wireless/intel/iwlwifi/fw/api/sf.h new file mode 100644 index 000000000..be4ff79fa --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/sf.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_sf_h__ +#define __iwl_fw_api_sf_h__ + +/* Smart Fifo state */ +enum iwl_sf_state { + SF_LONG_DELAY_ON = 0, /* should never be called by driver */ + SF_FULL_ON, + SF_UNINIT, + SF_INIT_OFF, + SF_HW_NUM_STATES +}; + +/* Smart Fifo possible scenario */ +enum iwl_sf_scenario { + SF_SCENARIO_SINGLE_UNICAST, + SF_SCENARIO_AGG_UNICAST, + SF_SCENARIO_MULTICAST, + SF_SCENARIO_BA_RESP, + SF_SCENARIO_TX_RESP, + SF_NUM_SCENARIO +}; + +#define SF_TRANSIENT_STATES_NUMBER 2 /* SF_LONG_DELAY_ON and SF_FULL_ON */ +#define SF_NUM_TIMEOUT_TYPES 2 /* Aging timer and Idle timer */ + +/* smart FIFO default values */ +#define SF_W_MARK_SISO 6144 +#define SF_W_MARK_MIMO2 8192 +#define SF_W_MARK_MIMO3 6144 +#define SF_W_MARK_LEGACY 4096 +#define SF_W_MARK_SCAN 4096 + +/* SF Scenarios timers for default configuration (aligned to 32 uSec) */ +#define SF_SINGLE_UNICAST_IDLE_TIMER_DEF 160 /* 150 uSec */ +#define SF_SINGLE_UNICAST_AGING_TIMER_DEF 400 /* 0.4 mSec */ +#define SF_AGG_UNICAST_IDLE_TIMER_DEF 160 /* 150 uSec */ +#define SF_AGG_UNICAST_AGING_TIMER_DEF 400 /* 0.4 mSec */ +#define SF_MCAST_IDLE_TIMER_DEF 160 /* 150 mSec */ +#define SF_MCAST_AGING_TIMER_DEF 400 /* 0.4 mSec */ +#define SF_BA_IDLE_TIMER_DEF 160 /* 150 uSec */ +#define SF_BA_AGING_TIMER_DEF 400 /* 0.4 mSec */ +#define SF_TX_RE_IDLE_TIMER_DEF 160 /* 150 uSec */ +#define SF_TX_RE_AGING_TIMER_DEF 400 /* 0.4 mSec */ + +/* SF Scenarios timers for BSS MAC configuration (aligned to 32 uSec) */ +#define SF_SINGLE_UNICAST_IDLE_TIMER 320 /* 300 uSec */ +#define SF_SINGLE_UNICAST_AGING_TIMER 2016 /* 2 mSec */ +#define SF_AGG_UNICAST_IDLE_TIMER 320 /* 300 uSec */ +#define SF_AGG_UNICAST_AGING_TIMER 2016 /* 2 mSec */ +#define SF_MCAST_IDLE_TIMER 2016 /* 2 mSec */ +#define SF_MCAST_AGING_TIMER 10016 /* 10 mSec */ +#define SF_BA_IDLE_TIMER 320 /* 300 uSec */ +#define SF_BA_AGING_TIMER 2016 /* 2 mSec */ +#define SF_TX_RE_IDLE_TIMER 320 /* 300 uSec */ +#define SF_TX_RE_AGING_TIMER 2016 /* 2 mSec */ + +#define SF_LONG_DELAY_AGING_TIMER 1000000 /* 1 Sec */ + +#define SF_CFG_DUMMY_NOTIF_OFF BIT(16) + +/** + * struct iwl_sf_cfg_cmd - Smart Fifo configuration command. + * @state: smart fifo state, types listed in &enum iwl_sf_state. + * @watermark: Minimum allowed available free space in RXF for transient state. + * @long_delay_timeouts: aging and idle timer values for each scenario + * in long delay state. + * @full_on_timeouts: timer values for each scenario in full on state. + */ +struct iwl_sf_cfg_cmd { + __le32 state; + __le32 watermark[SF_TRANSIENT_STATES_NUMBER]; + __le32 long_delay_timeouts[SF_NUM_SCENARIO][SF_NUM_TIMEOUT_TYPES]; + __le32 full_on_timeouts[SF_NUM_SCENARIO][SF_NUM_TIMEOUT_TYPES]; +} __packed; /* SF_CFG_API_S_VER_2 */ + +#endif /* __iwl_fw_api_sf_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/sta.h b/drivers/net/wireless/intel/iwlwifi/fw/api/sta.h new file mode 100644 index 000000000..d62fed543 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/sta.h @@ -0,0 +1,494 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_sta_h__ +#define __iwl_fw_api_sta_h__ + +/** + * enum iwl_sta_flags - flags for the ADD_STA host command + * @STA_FLG_REDUCED_TX_PWR_CTRL: reduced TX power (control frames) + * @STA_FLG_REDUCED_TX_PWR_DATA: reduced TX power (data frames) + * @STA_FLG_DISABLE_TX: set if TX should be disabled + * @STA_FLG_PS: set if STA is in Power Save + * @STA_FLG_DRAIN_FLOW: drain flow + * @STA_FLG_PAN: STA is for PAN interface + * @STA_FLG_CLASS_AUTH: station is authenticated + * @STA_FLG_CLASS_ASSOC: station is associated + * @STA_FLG_RTS_MIMO_PROT: station requires RTS MIMO protection (dynamic SMPS) + * @STA_FLG_MAX_AGG_SIZE_MSK: maximal size for A-MPDU (mask) + * @STA_FLG_MAX_AGG_SIZE_SHIFT: maximal size for A-MPDU (bit shift) + * @STA_FLG_MAX_AGG_SIZE_8K: maximal size for A-MPDU (8k supported) + * @STA_FLG_MAX_AGG_SIZE_16K: maximal size for A-MPDU (16k supported) + * @STA_FLG_MAX_AGG_SIZE_32K: maximal size for A-MPDU (32k supported) + * @STA_FLG_MAX_AGG_SIZE_64K: maximal size for A-MPDU (64k supported) + * @STA_FLG_MAX_AGG_SIZE_128K: maximal size for A-MPDU (128k supported) + * @STA_FLG_MAX_AGG_SIZE_256K: maximal size for A-MPDU (256k supported) + * @STA_FLG_MAX_AGG_SIZE_512K: maximal size for A-MPDU (512k supported) + * @STA_FLG_MAX_AGG_SIZE_1024K: maximal size for A-MPDU (1024k supported) + * @STA_FLG_MAX_AGG_SIZE_2M: maximal size for A-MPDU (2M supported) + * @STA_FLG_MAX_AGG_SIZE_4M: maximal size for A-MPDU (4M supported) + * @STA_FLG_AGG_MPDU_DENS_MSK: maximal MPDU density for Tx aggregation + * @STA_FLG_FAT_EN_MSK: support for channel width (for Tx). This flag is + * initialised by driver and can be updated by fw upon reception of + * action frames that can change the channel width. When cleared the fw + * will send all the frames in 20MHz even when FAT channel is requested. + * @STA_FLG_FAT_EN_20MHZ: no wide channels are supported, only 20 MHz + * @STA_FLG_FAT_EN_40MHZ: wide channels up to 40 MHz supported + * @STA_FLG_FAT_EN_80MHZ: wide channels up to 80 MHz supported + * @STA_FLG_FAT_EN_160MHZ: wide channels up to 160 MHz supported + * @STA_FLG_MIMO_EN_MSK: support for MIMO. This flag is initialised by the + * driver and can be updated by fw upon reception of action frames. + * @STA_FLG_MIMO_EN_SISO: no support for MIMO + * @STA_FLG_MIMO_EN_MIMO2: 2 streams supported + * @STA_FLG_MIMO_EN_MIMO3: 3 streams supported + * @STA_FLG_AGG_MPDU_DENS_MSK: A-MPDU density (mask) + * @STA_FLG_AGG_MPDU_DENS_SHIFT: A-MPDU density (bit shift) + * @STA_FLG_AGG_MPDU_DENS_2US: A-MPDU density (2 usec gap) + * @STA_FLG_AGG_MPDU_DENS_4US: A-MPDU density (4 usec gap) + * @STA_FLG_AGG_MPDU_DENS_8US: A-MPDU density (8 usec gap) + * @STA_FLG_AGG_MPDU_DENS_16US: A-MPDU density (16 usec gap) + */ +enum iwl_sta_flags { + STA_FLG_REDUCED_TX_PWR_CTRL = BIT(3), + STA_FLG_REDUCED_TX_PWR_DATA = BIT(6), + + STA_FLG_DISABLE_TX = BIT(4), + + STA_FLG_PS = BIT(8), + STA_FLG_DRAIN_FLOW = BIT(12), + STA_FLG_PAN = BIT(13), + STA_FLG_CLASS_AUTH = BIT(14), + STA_FLG_CLASS_ASSOC = BIT(15), + STA_FLG_RTS_MIMO_PROT = BIT(17), + + STA_FLG_MAX_AGG_SIZE_SHIFT = 19, + STA_FLG_MAX_AGG_SIZE_8K = (0 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_16K = (1 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_32K = (2 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_64K = (3 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_128K = (4 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_256K = (5 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_512K = (6 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_1024K = (7 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_2M = (8 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_4M = (9 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_MSK = (0xf << STA_FLG_MAX_AGG_SIZE_SHIFT), + + STA_FLG_AGG_MPDU_DENS_SHIFT = 23, + STA_FLG_AGG_MPDU_DENS_2US = (4 << STA_FLG_AGG_MPDU_DENS_SHIFT), + STA_FLG_AGG_MPDU_DENS_4US = (5 << STA_FLG_AGG_MPDU_DENS_SHIFT), + STA_FLG_AGG_MPDU_DENS_8US = (6 << STA_FLG_AGG_MPDU_DENS_SHIFT), + STA_FLG_AGG_MPDU_DENS_16US = (7 << STA_FLG_AGG_MPDU_DENS_SHIFT), + STA_FLG_AGG_MPDU_DENS_MSK = (7 << STA_FLG_AGG_MPDU_DENS_SHIFT), + + STA_FLG_FAT_EN_20MHZ = (0 << 26), + STA_FLG_FAT_EN_40MHZ = (1 << 26), + STA_FLG_FAT_EN_80MHZ = (2 << 26), + STA_FLG_FAT_EN_160MHZ = (3 << 26), + STA_FLG_FAT_EN_MSK = (3 << 26), + + STA_FLG_MIMO_EN_SISO = (0 << 28), + STA_FLG_MIMO_EN_MIMO2 = (1 << 28), + STA_FLG_MIMO_EN_MIMO3 = (2 << 28), + STA_FLG_MIMO_EN_MSK = (3 << 28), +}; + +/** + * enum iwl_sta_key_flag - key flags for the ADD_STA host command + * @STA_KEY_FLG_NO_ENC: no encryption + * @STA_KEY_FLG_WEP: WEP encryption algorithm + * @STA_KEY_FLG_CCM: CCMP encryption algorithm + * @STA_KEY_FLG_TKIP: TKIP encryption algorithm + * @STA_KEY_FLG_EXT: extended cipher algorithm (depends on the FW support) + * @STA_KEY_FLG_GCMP: GCMP encryption algorithm + * @STA_KEY_FLG_CMAC: CMAC encryption algorithm + * @STA_KEY_FLG_ENC_UNKNOWN: unknown encryption algorithm + * @STA_KEY_FLG_EN_MSK: mask for encryption algorithmi value + * @STA_KEY_FLG_WEP_KEY_MAP: wep is either a group key (0 - legacy WEP) or from + * station info array (1 - n 1X mode) + * @STA_KEY_FLG_KEYID_MSK: the index of the key + * @STA_KEY_FLG_KEYID_POS: key index bit position + * @STA_KEY_NOT_VALID: key is invalid + * @STA_KEY_FLG_WEP_13BYTES: set for 13 bytes WEP key + * @STA_KEY_FLG_KEY_32BYTES: for non-wep key set for 32 bytes key + * @STA_KEY_MULTICAST: set for multical key + * @STA_KEY_MFP: key is used for Management Frame Protection + */ +enum iwl_sta_key_flag { + STA_KEY_FLG_NO_ENC = (0 << 0), + STA_KEY_FLG_WEP = (1 << 0), + STA_KEY_FLG_CCM = (2 << 0), + STA_KEY_FLG_TKIP = (3 << 0), + STA_KEY_FLG_EXT = (4 << 0), + STA_KEY_FLG_GCMP = (5 << 0), + STA_KEY_FLG_CMAC = (6 << 0), + STA_KEY_FLG_ENC_UNKNOWN = (7 << 0), + STA_KEY_FLG_EN_MSK = (7 << 0), + + STA_KEY_FLG_WEP_KEY_MAP = BIT(3), + STA_KEY_FLG_KEYID_POS = 8, + STA_KEY_FLG_KEYID_MSK = (3 << STA_KEY_FLG_KEYID_POS), + STA_KEY_NOT_VALID = BIT(11), + STA_KEY_FLG_WEP_13BYTES = BIT(12), + STA_KEY_FLG_KEY_32BYTES = BIT(12), + STA_KEY_MULTICAST = BIT(14), + STA_KEY_MFP = BIT(15), +}; + +/** + * enum iwl_sta_modify_flag - indicate to the fw what flag are being changed + * @STA_MODIFY_QUEUE_REMOVAL: this command removes a queue + * @STA_MODIFY_TID_DISABLE_TX: this command modifies %tid_disable_tx + * @STA_MODIFY_UAPSD_ACS: this command modifies %uapsd_acs + * @STA_MODIFY_ADD_BA_TID: this command modifies %add_immediate_ba_tid + * @STA_MODIFY_REMOVE_BA_TID: this command modifies %remove_immediate_ba_tid + * @STA_MODIFY_SLEEPING_STA_TX_COUNT: this command modifies %sleep_tx_count + * @STA_MODIFY_PROT_TH: modify RTS threshold + * @STA_MODIFY_QUEUES: modify the queues used by this station + */ +enum iwl_sta_modify_flag { + STA_MODIFY_QUEUE_REMOVAL = BIT(0), + STA_MODIFY_TID_DISABLE_TX = BIT(1), + STA_MODIFY_UAPSD_ACS = BIT(2), + STA_MODIFY_ADD_BA_TID = BIT(3), + STA_MODIFY_REMOVE_BA_TID = BIT(4), + STA_MODIFY_SLEEPING_STA_TX_COUNT = BIT(5), + STA_MODIFY_PROT_TH = BIT(6), + STA_MODIFY_QUEUES = BIT(7), +}; + +/** + * enum iwl_sta_mode - station command mode + * @STA_MODE_ADD: add new station + * @STA_MODE_MODIFY: modify the station + */ +enum iwl_sta_mode { + STA_MODE_ADD = 0, + STA_MODE_MODIFY = 1, +}; + +/** + * enum iwl_sta_sleep_flag - type of sleep of the station + * @STA_SLEEP_STATE_AWAKE: station is awake + * @STA_SLEEP_STATE_PS_POLL: station is PS-polling + * @STA_SLEEP_STATE_UAPSD: station uses U-APSD + * @STA_SLEEP_STATE_MOREDATA: set more-data bit on + * (last) released frame + */ +enum iwl_sta_sleep_flag { + STA_SLEEP_STATE_AWAKE = 0, + STA_SLEEP_STATE_PS_POLL = BIT(0), + STA_SLEEP_STATE_UAPSD = BIT(1), + STA_SLEEP_STATE_MOREDATA = BIT(2), +}; + +#define STA_KEY_MAX_NUM (16) +#define STA_KEY_IDX_INVALID (0xff) +#define STA_KEY_MAX_DATA_KEY_NUM (4) +#define IWL_MAX_GLOBAL_KEYS (4) +#define STA_KEY_LEN_WEP40 (5) +#define STA_KEY_LEN_WEP104 (13) + +#define IWL_ADD_STA_STATUS_MASK 0xFF +#define IWL_ADD_STA_BAID_VALID_MASK 0x8000 +#define IWL_ADD_STA_BAID_MASK 0x7F00 +#define IWL_ADD_STA_BAID_SHIFT 8 + +/** + * struct iwl_mvm_add_sta_cmd_v7 - Add/modify a station in the fw's sta table. + * ( REPLY_ADD_STA = 0x18 ) + * @add_modify: see &enum iwl_sta_mode + * @awake_acs: ACs to transmit data on while station is sleeping (for U-APSD) + * @tid_disable_tx: is tid BIT(tid) enabled for Tx. Clear BIT(x) to enable + * AMPDU for tid x. Set %STA_MODIFY_TID_DISABLE_TX to change this field. + * @mac_id_n_color: the Mac context this station belongs to, + * see &enum iwl_ctxt_id_and_color + * @addr: station's MAC address + * @reserved2: reserved + * @sta_id: index of station in uCode's station table + * @modify_mask: from &enum iwl_sta_modify_flag, selects what to change + * @reserved3: reserved + * @station_flags: look at &enum iwl_sta_flags + * @station_flags_msk: what of %station_flags have changed, + * also &enum iwl_sta_flags + * @add_immediate_ba_tid: tid for which to add block-ack support (Rx) + * Set %STA_MODIFY_ADD_BA_TID to use this field, and also set + * add_immediate_ba_ssn. + * @remove_immediate_ba_tid: tid for which to remove block-ack support (Rx) + * Set %STA_MODIFY_REMOVE_BA_TID to use this field + * @add_immediate_ba_ssn: ssn for the Rx block-ack session. Used together with + * add_immediate_ba_tid. + * @sleep_tx_count: number of packets to transmit to station even though it is + * asleep. Used to synchronise PS-poll and u-APSD responses while ucode + * keeps track of STA sleep state. + * @sleep_state_flags: Look at &enum iwl_sta_sleep_flag. + * @assoc_id: assoc_id to be sent in VHT PLCP (9-bit), for grp use 0, for AP + * mac-addr. + * @beamform_flags: beam forming controls + * @tfd_queue_msk: tfd queues used by this station + * + * The device contains an internal table of per-station information, with info + * on security keys, aggregation parameters, and Tx rates for initial Tx + * attempt and any retries (set by REPLY_TX_LINK_QUALITY_CMD). + * + * ADD_STA sets up the table entry for one station, either creating a new + * entry, or modifying a pre-existing one. + */ +struct iwl_mvm_add_sta_cmd_v7 { + u8 add_modify; + u8 awake_acs; + __le16 tid_disable_tx; + __le32 mac_id_n_color; + u8 addr[ETH_ALEN]; /* _STA_ID_MODIFY_INFO_API_S_VER_1 */ + __le16 reserved2; + u8 sta_id; + u8 modify_mask; + __le16 reserved3; + __le32 station_flags; + __le32 station_flags_msk; + u8 add_immediate_ba_tid; + u8 remove_immediate_ba_tid; + __le16 add_immediate_ba_ssn; + __le16 sleep_tx_count; + __le16 sleep_state_flags; + __le16 assoc_id; + __le16 beamform_flags; + __le32 tfd_queue_msk; +} __packed; /* ADD_STA_CMD_API_S_VER_7 */ + +/** + * enum iwl_sta_type - FW station types + * ( REPLY_ADD_STA = 0x18 ) + * @IWL_STA_LINK: Link station - normal RX and TX traffic. + * @IWL_STA_GENERAL_PURPOSE: General purpose. In AP mode used for beacons + * and probe responses. + * @IWL_STA_MULTICAST: multicast traffic, + * @IWL_STA_TDLS_LINK: TDLS link station + * @IWL_STA_AUX_ACTIVITY: auxilary station (scan, ROC and so on). + */ +enum iwl_sta_type { + IWL_STA_LINK, + IWL_STA_GENERAL_PURPOSE, + IWL_STA_MULTICAST, + IWL_STA_TDLS_LINK, + IWL_STA_AUX_ACTIVITY, +}; + +/** + * struct iwl_mvm_add_sta_cmd - Add/modify a station in the fw's sta table. + * ( REPLY_ADD_STA = 0x18 ) + * @add_modify: see &enum iwl_sta_mode + * @awake_acs: ACs to transmit data on while station is sleeping (for U-APSD) + * @tid_disable_tx: is tid BIT(tid) enabled for Tx. Clear BIT(x) to enable + * AMPDU for tid x. Set %STA_MODIFY_TID_DISABLE_TX to change this field. + * @mac_id_n_color: the Mac context this station belongs to, + * see &enum iwl_ctxt_id_and_color + * @addr: station's MAC address + * @reserved2: reserved + * @sta_id: index of station in uCode's station table + * @modify_mask: from &enum iwl_sta_modify_flag, selects what to change + * @reserved3: reserved + * @station_flags: look at &enum iwl_sta_flags + * @station_flags_msk: what of %station_flags have changed, + * also &enum iwl_sta_flags + * @add_immediate_ba_tid: tid for which to add block-ack support (Rx) + * Set %STA_MODIFY_ADD_BA_TID to use this field, and also set + * add_immediate_ba_ssn. + * @remove_immediate_ba_tid: tid for which to remove block-ack support (Rx) + * Set %STA_MODIFY_REMOVE_BA_TID to use this field + * @add_immediate_ba_ssn: ssn for the Rx block-ack session. Used together with + * add_immediate_ba_tid. + * @sleep_tx_count: number of packets to transmit to station even though it is + * asleep. Used to synchronise PS-poll and u-APSD responses while ucode + * keeps track of STA sleep state. + * @station_type: type of this station. See &enum iwl_sta_type. + * @sleep_state_flags: Look at &enum iwl_sta_sleep_flag. + * @assoc_id: assoc_id to be sent in VHT PLCP (9-bit), for grp use 0, for AP + * mac-addr. + * @beamform_flags: beam forming controls + * @tfd_queue_msk: tfd queues used by this station. + * Obselete for new TX API (9 and above). + * @rx_ba_window: aggregation window size + * @sp_length: the size of the SP in actual number of frames + * @uapsd_acs: 4 LS bits are trigger enabled ACs, 4 MS bits are the deliver + * enabled ACs. + * + * The device contains an internal table of per-station information, with info + * on security keys, aggregation parameters, and Tx rates for initial Tx + * attempt and any retries (set by REPLY_TX_LINK_QUALITY_CMD). + * + * ADD_STA sets up the table entry for one station, either creating a new + * entry, or modifying a pre-existing one. + */ +struct iwl_mvm_add_sta_cmd { + u8 add_modify; + u8 awake_acs; + __le16 tid_disable_tx; + __le32 mac_id_n_color; /* can be used for lmac id when using cmd v12 */ + u8 addr[ETH_ALEN]; /* _STA_ID_MODIFY_INFO_API_S_VER_1 */ + __le16 reserved2; + u8 sta_id; + u8 modify_mask; + __le16 reserved3; + __le32 station_flags; + __le32 station_flags_msk; + u8 add_immediate_ba_tid; + u8 remove_immediate_ba_tid; + __le16 add_immediate_ba_ssn; + __le16 sleep_tx_count; + u8 sleep_state_flags; + u8 station_type; + __le16 assoc_id; + __le16 beamform_flags; + __le32 tfd_queue_msk; + __le16 rx_ba_window; + u8 sp_length; + u8 uapsd_acs; +} __packed; /* ADD_STA_CMD_API_S_VER_10 */ + +/** + * struct iwl_mvm_add_sta_key_common - add/modify sta key common part + * ( REPLY_ADD_STA_KEY = 0x17 ) + * @sta_id: index of station in uCode's station table + * @key_offset: key offset in key storage + * @key_flags: type &enum iwl_sta_key_flag + * @key: key material data + * @rx_secur_seq_cnt: RX security sequence counter for the key + */ +struct iwl_mvm_add_sta_key_common { + u8 sta_id; + u8 key_offset; + __le16 key_flags; + u8 key[32]; + u8 rx_secur_seq_cnt[16]; +} __packed; + +/** + * struct iwl_mvm_add_sta_key_cmd_v1 - add/modify sta key + * @common: see &struct iwl_mvm_add_sta_key_common + * @tkip_rx_tsc_byte2: TSC[2] for key mix ph1 detection + * @reserved: reserved + * @tkip_rx_ttak: 10-byte unicast TKIP TTAK for Rx + */ +struct iwl_mvm_add_sta_key_cmd_v1 { + struct iwl_mvm_add_sta_key_common common; + u8 tkip_rx_tsc_byte2; + u8 reserved; + __le16 tkip_rx_ttak[5]; +} __packed; /* ADD_MODIFY_STA_KEY_API_S_VER_1 */ + +/** + * struct iwl_mvm_add_sta_key_cmd - add/modify sta key + * @common: see &struct iwl_mvm_add_sta_key_common + * @rx_mic_key: TKIP RX unicast or multicast key + * @tx_mic_key: TKIP TX key + * @transmit_seq_cnt: TSC, transmit packet number + * + * Note: This is used for both v2 and v3, the difference being + * in the way the common.rx_secur_seq_cnt is used, in v2 that's + * the strange hole format, in v3 it's just a u64. + */ +struct iwl_mvm_add_sta_key_cmd { + struct iwl_mvm_add_sta_key_common common; + __le64 rx_mic_key; + __le64 tx_mic_key; + __le64 transmit_seq_cnt; +} __packed; /* ADD_MODIFY_STA_KEY_API_S_VER_2, ADD_MODIFY_STA_KEY_API_S_VER_3 */ + +/** + * enum iwl_mvm_add_sta_rsp_status - status in the response to ADD_STA command + * @ADD_STA_SUCCESS: operation was executed successfully + * @ADD_STA_STATIONS_OVERLOAD: no room left in the fw's station table + * @ADD_STA_IMMEDIATE_BA_FAILURE: can't add Rx block ack session + * @ADD_STA_MODIFY_NON_EXISTING_STA: driver requested to modify a station that + * doesn't exist. + */ +enum iwl_mvm_add_sta_rsp_status { + ADD_STA_SUCCESS = 0x1, + ADD_STA_STATIONS_OVERLOAD = 0x2, + ADD_STA_IMMEDIATE_BA_FAILURE = 0x4, + ADD_STA_MODIFY_NON_EXISTING_STA = 0x8, +}; + +/** + * struct iwl_mvm_rm_sta_cmd - Add / modify a station in the fw's station table + * ( REMOVE_STA = 0x19 ) + * @sta_id: the station id of the station to be removed + * @reserved: reserved + */ +struct iwl_mvm_rm_sta_cmd { + u8 sta_id; + u8 reserved[3]; +} __packed; /* REMOVE_STA_CMD_API_S_VER_2 */ + +/** + * struct iwl_mvm_mgmt_mcast_key_cmd_v1 + * ( MGMT_MCAST_KEY = 0x1f ) + * @ctrl_flags: &enum iwl_sta_key_flag + * @igtk: IGTK key material + * @k1: unused + * @k2: unused + * @sta_id: station ID that support IGTK + * @key_id: key ID + * @receive_seq_cnt: initial RSC/PN needed for replay check + */ +struct iwl_mvm_mgmt_mcast_key_cmd_v1 { + __le32 ctrl_flags; + u8 igtk[16]; + u8 k1[16]; + u8 k2[16]; + __le32 key_id; + __le32 sta_id; + __le64 receive_seq_cnt; +} __packed; /* SEC_MGMT_MULTICAST_KEY_CMD_API_S_VER_1 */ + +/** + * struct iwl_mvm_mgmt_mcast_key_cmd + * ( MGMT_MCAST_KEY = 0x1f ) + * @ctrl_flags: &enum iwl_sta_key_flag + * @igtk: IGTK master key + * @sta_id: station ID that support IGTK + * @key_id: key ID + * @receive_seq_cnt: initial RSC/PN needed for replay check + */ +struct iwl_mvm_mgmt_mcast_key_cmd { + __le32 ctrl_flags; + u8 igtk[32]; + __le32 key_id; + __le32 sta_id; + __le64 receive_seq_cnt; +} __packed; /* SEC_MGMT_MULTICAST_KEY_CMD_API_S_VER_2 */ + +struct iwl_mvm_wep_key { + u8 key_index; + u8 key_offset; + __le16 reserved1; + u8 key_size; + u8 reserved2[3]; + u8 key[16]; +} __packed; + +struct iwl_mvm_wep_key_cmd { + __le32 mac_id_n_color; + u8 num_keys; + u8 decryption_type; + u8 flags; + u8 reserved; + struct iwl_mvm_wep_key wep_key[]; +} __packed; /* SEC_CURR_WEP_KEY_CMD_API_S_VER_2 */ + +/** + * struct iwl_mvm_eosp_notification - EOSP notification from firmware + * @remain_frame_count: # of frames remaining, non-zero if SP was cut + * short by GO absence + * @sta_id: station ID + */ +struct iwl_mvm_eosp_notification { + __le32 remain_frame_count; + __le32 sta_id; +} __packed; /* UAPSD_EOSP_NTFY_API_S_VER_1 */ + +#endif /* __iwl_fw_api_sta_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/stats.h b/drivers/net/wireless/intel/iwlwifi/fw/api/stats.h new file mode 100644 index 000000000..898e62326 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/stats.h @@ -0,0 +1,961 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018, 2020 - 2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_stats_h__ +#define __iwl_fw_api_stats_h__ +#include "mac.h" + +struct mvm_statistics_dbg { + __le32 burst_check; + __le32 burst_count; + __le32 wait_for_silence_timeout_cnt; + u8 reserved[12]; +} __packed; /* STATISTICS_DEBUG_API_S_VER_2 */ + +struct mvm_statistics_div { + __le32 tx_on_a; + __le32 tx_on_b; + __le32 exec_time; + __le32 probe_time; + __le32 rssi_ant; + __le32 reserved2; +} __packed; /* STATISTICS_SLOW_DIV_API_S_VER_2 */ + +/** + * struct mvm_statistics_rx_non_phy + * @bogus_cts: CTS received when not expecting CTS + * @bogus_ack: ACK received when not expecting ACK + * @non_channel_beacons: beacons with our bss id but not on our serving channel + * @channel_beacons: beacons with our bss id and in our serving channel + * @num_missed_bcon: number of missed beacons + * @adc_rx_saturation_time: count in 0.8us units the time the ADC was in + * saturation + * @ina_detection_search_time: total time (in 0.8us) searched for INA + * @beacon_silence_rssi_a: RSSI silence after beacon frame + * @beacon_silence_rssi_b: RSSI silence after beacon frame + * @beacon_silence_rssi_c: RSSI silence after beacon frame + * @interference_data_flag: flag for interference data availability. 1 when data + * is available. + * @channel_load: counts RX Enable time in uSec + * @beacon_rssi_a: beacon RSSI on antenna A + * @beacon_rssi_b: beacon RSSI on antenna B + * @beacon_rssi_c: beacon RSSI on antenna C + * @beacon_energy_a: beacon energy on antenna A + * @beacon_energy_b: beacon energy on antenna B + * @beacon_energy_c: beacon energy on antenna C + * @num_bt_kills: number of BT "kills" (frame TX aborts) + * @mac_id: mac ID + */ +struct mvm_statistics_rx_non_phy { + __le32 bogus_cts; + __le32 bogus_ack; + __le32 non_channel_beacons; + __le32 channel_beacons; + __le32 num_missed_bcon; + __le32 adc_rx_saturation_time; + __le32 ina_detection_search_time; + __le32 beacon_silence_rssi_a; + __le32 beacon_silence_rssi_b; + __le32 beacon_silence_rssi_c; + __le32 interference_data_flag; + __le32 channel_load; + __le32 beacon_rssi_a; + __le32 beacon_rssi_b; + __le32 beacon_rssi_c; + __le32 beacon_energy_a; + __le32 beacon_energy_b; + __le32 beacon_energy_c; + __le32 num_bt_kills; + __le32 mac_id; +} __packed; /* STATISTICS_RX_NON_PHY_API_S_VER_4 */ + +struct mvm_statistics_rx_non_phy_v3 { + __le32 bogus_cts; /* CTS received when not expecting CTS */ + __le32 bogus_ack; /* ACK received when not expecting ACK */ + __le32 non_bssid_frames; /* number of frames with BSSID that + * doesn't belong to the STA BSSID */ + __le32 filtered_frames; /* count frames that were dumped in the + * filtering process */ + __le32 non_channel_beacons; /* beacons with our bss id but not on + * our serving channel */ + __le32 channel_beacons; /* beacons with our bss id and in our + * serving channel */ + __le32 num_missed_bcon; /* number of missed beacons */ + __le32 adc_rx_saturation_time; /* count in 0.8us units the time the + * ADC was in saturation */ + __le32 ina_detection_search_time;/* total time (in 0.8us) searched + * for INA */ + __le32 beacon_silence_rssi_a; /* RSSI silence after beacon frame */ + __le32 beacon_silence_rssi_b; /* RSSI silence after beacon frame */ + __le32 beacon_silence_rssi_c; /* RSSI silence after beacon frame */ + __le32 interference_data_flag; /* flag for interference data + * availability. 1 when data is + * available. */ + __le32 channel_load; /* counts RX Enable time in uSec */ + __le32 dsp_false_alarms; /* DSP false alarm (both OFDM + * and CCK) counter */ + __le32 beacon_rssi_a; + __le32 beacon_rssi_b; + __le32 beacon_rssi_c; + __le32 beacon_energy_a; + __le32 beacon_energy_b; + __le32 beacon_energy_c; + __le32 num_bt_kills; + __le32 mac_id; + __le32 directed_data_mpdu; +} __packed; /* STATISTICS_RX_NON_PHY_API_S_VER_3 */ + +struct mvm_statistics_rx_phy { + __le32 unresponded_rts; + __le32 rxe_frame_lmt_overrun; + __le32 sent_ba_rsp_cnt; + __le32 dsp_self_kill; + __le32 reserved; +} __packed; /* STATISTICS_RX_PHY_API_S_VER_3 */ + +struct mvm_statistics_rx_phy_v2 { + __le32 ina_cnt; + __le32 fina_cnt; + __le32 plcp_err; + __le32 crc32_err; + __le32 overrun_err; + __le32 early_overrun_err; + __le32 crc32_good; + __le32 false_alarm_cnt; + __le32 fina_sync_err_cnt; + __le32 sfd_timeout; + __le32 fina_timeout; + __le32 unresponded_rts; + __le32 rxe_frame_lmt_overrun; + __le32 sent_ack_cnt; + __le32 sent_cts_cnt; + __le32 sent_ba_rsp_cnt; + __le32 dsp_self_kill; + __le32 mh_format_err; + __le32 re_acq_main_rssi_sum; + __le32 reserved; +} __packed; /* STATISTICS_RX_PHY_API_S_VER_2 */ + +struct mvm_statistics_rx_ht_phy_v1 { + __le32 plcp_err; + __le32 overrun_err; + __le32 early_overrun_err; + __le32 crc32_good; + __le32 crc32_err; + __le32 mh_format_err; + __le32 agg_crc32_good; + __le32 agg_mpdu_cnt; + __le32 agg_cnt; + __le32 unsupport_mcs; +} __packed; /* STATISTICS_HT_RX_PHY_API_S_VER_1 */ + +struct mvm_statistics_rx_ht_phy { + __le32 mh_format_err; + __le32 agg_mpdu_cnt; + __le32 agg_cnt; + __le32 unsupport_mcs; +} __packed; /* STATISTICS_HT_RX_PHY_API_S_VER_2 */ + +struct mvm_statistics_tx_non_phy_v3 { + __le32 preamble_cnt; + __le32 rx_detected_cnt; + __le32 bt_prio_defer_cnt; + __le32 bt_prio_kill_cnt; + __le32 few_bytes_cnt; + __le32 cts_timeout; + __le32 ack_timeout; + __le32 expected_ack_cnt; + __le32 actual_ack_cnt; + __le32 dump_msdu_cnt; + __le32 burst_abort_next_frame_mismatch_cnt; + __le32 burst_abort_missing_next_frame_cnt; + __le32 cts_timeout_collision; + __le32 ack_or_ba_timeout_collision; +} __packed; /* STATISTICS_TX_NON_PHY_API_S_VER_3 */ + +struct mvm_statistics_tx_non_phy { + __le32 bt_prio_defer_cnt; + __le32 bt_prio_kill_cnt; + __le32 few_bytes_cnt; + __le32 cts_timeout; + __le32 ack_timeout; + __le32 dump_msdu_cnt; + __le32 burst_abort_next_frame_mismatch_cnt; + __le32 burst_abort_missing_next_frame_cnt; + __le32 cts_timeout_collision; + __le32 ack_or_ba_timeout_collision; +} __packed; /* STATISTICS_TX_NON_PHY_API_S_VER_4 */ + +#define MAX_CHAINS 3 + +struct mvm_statistics_tx_non_phy_agg { + __le32 ba_timeout; + __le32 ba_reschedule_frames; + __le32 scd_query_agg_frame_cnt; + __le32 scd_query_no_agg; + __le32 scd_query_agg; + __le32 scd_query_mismatch; + __le32 frame_not_ready; + __le32 underrun; + __le32 bt_prio_kill; + __le32 rx_ba_rsp_cnt; + __s8 txpower[MAX_CHAINS]; + __s8 reserved; + __le32 reserved2; +} __packed; /* STATISTICS_TX_NON_PHY_AGG_API_S_VER_1 */ + +struct mvm_statistics_tx_channel_width { + __le32 ext_cca_narrow_ch20[1]; + __le32 ext_cca_narrow_ch40[2]; + __le32 ext_cca_narrow_ch80[3]; + __le32 ext_cca_narrow_ch160[4]; + __le32 last_tx_ch_width_indx; + __le32 rx_detected_per_ch_width[4]; + __le32 success_per_ch_width[4]; + __le32 fail_per_ch_width[4]; +}; /* STATISTICS_TX_CHANNEL_WIDTH_API_S_VER_1 */ + +struct mvm_statistics_tx_v4 { + struct mvm_statistics_tx_non_phy_v3 general; + struct mvm_statistics_tx_non_phy_agg agg; + struct mvm_statistics_tx_channel_width channel_width; +} __packed; /* STATISTICS_TX_API_S_VER_4 */ + +struct mvm_statistics_tx { + struct mvm_statistics_tx_non_phy general; + struct mvm_statistics_tx_non_phy_agg agg; + struct mvm_statistics_tx_channel_width channel_width; +} __packed; /* STATISTICS_TX_API_S_VER_5 */ + + +struct mvm_statistics_bt_activity { + __le32 hi_priority_tx_req_cnt; + __le32 hi_priority_tx_denied_cnt; + __le32 lo_priority_tx_req_cnt; + __le32 lo_priority_tx_denied_cnt; + __le32 hi_priority_rx_req_cnt; + __le32 hi_priority_rx_denied_cnt; + __le32 lo_priority_rx_req_cnt; + __le32 lo_priority_rx_denied_cnt; +} __packed; /* STATISTICS_BT_ACTIVITY_API_S_VER_1 */ + +struct mvm_statistics_general_common_v19 { + __le32 radio_temperature; + __le32 radio_voltage; + struct mvm_statistics_dbg dbg; + __le32 sleep_time; + __le32 slots_out; + __le32 slots_idle; + __le32 ttl_timestamp; + struct mvm_statistics_div slow_div; + __le32 rx_enable_counter; + /* + * num_of_sos_states: + * count the number of times we have to re-tune + * in order to get out of bad PHY status + */ + __le32 num_of_sos_states; + __le32 beacon_filtered; + __le32 missed_beacons; + u8 beacon_filter_average_energy; + u8 beacon_filter_reason; + u8 beacon_filter_current_energy; + u8 beacon_filter_reserved; + __le32 beacon_filter_delta_time; + struct mvm_statistics_bt_activity bt_activity; + __le64 rx_time; + __le64 on_time_rf; + __le64 on_time_scan; + __le64 tx_time; +} __packed; + +struct mvm_statistics_general_common { + __le32 radio_temperature; + struct mvm_statistics_dbg dbg; + __le32 sleep_time; + __le32 slots_out; + __le32 slots_idle; + __le32 ttl_timestamp; + struct mvm_statistics_div slow_div; + __le32 rx_enable_counter; + /* + * num_of_sos_states: + * count the number of times we have to re-tune + * in order to get out of bad PHY status + */ + __le32 num_of_sos_states; + __le32 beacon_filtered; + __le32 missed_beacons; + u8 beacon_filter_average_energy; + u8 beacon_filter_reason; + u8 beacon_filter_current_energy; + u8 beacon_filter_reserved; + __le32 beacon_filter_delta_time; + struct mvm_statistics_bt_activity bt_activity; + __le64 rx_time; + __le64 on_time_rf; + __le64 on_time_scan; + __le64 tx_time; +} __packed; /* STATISTICS_GENERAL_API_S_VER_10 */ + +struct mvm_statistics_general_v8 { + struct mvm_statistics_general_common_v19 common; + __le32 beacon_counter[NUM_MAC_INDEX]; + u8 beacon_average_energy[NUM_MAC_INDEX]; + u8 reserved[4 - (NUM_MAC_INDEX % 4)]; +} __packed; /* STATISTICS_GENERAL_API_S_VER_8 */ + +struct mvm_statistics_general { + struct mvm_statistics_general_common common; + __le32 beacon_counter[MAC_INDEX_AUX]; + u8 beacon_average_energy[MAC_INDEX_AUX]; + u8 reserved[8 - MAC_INDEX_AUX]; +} __packed; /* STATISTICS_GENERAL_API_S_VER_10 */ + +/** + * struct mvm_statistics_load - RX statistics for multi-queue devices + * @air_time: accumulated air time, per mac + * @byte_count: accumulated byte count, per mac + * @pkt_count: accumulated packet count, per mac + * @avg_energy: average RSSI, per station + */ +struct mvm_statistics_load { + __le32 air_time[MAC_INDEX_AUX]; + __le32 byte_count[MAC_INDEX_AUX]; + __le32 pkt_count[MAC_INDEX_AUX]; + u8 avg_energy[IWL_MVM_STATION_COUNT_MAX]; +} __packed; /* STATISTICS_RX_MAC_STATION_S_VER_3 */ + +struct mvm_statistics_load_v1 { + __le32 air_time[NUM_MAC_INDEX]; + __le32 byte_count[NUM_MAC_INDEX]; + __le32 pkt_count[NUM_MAC_INDEX]; + u8 avg_energy[IWL_MVM_STATION_COUNT_MAX]; +} __packed; /* STATISTICS_RX_MAC_STATION_S_VER_1 */ + +struct mvm_statistics_rx { + struct mvm_statistics_rx_phy ofdm; + struct mvm_statistics_rx_phy cck; + struct mvm_statistics_rx_non_phy general; + struct mvm_statistics_rx_ht_phy ofdm_ht; +} __packed; /* STATISTICS_RX_API_S_VER_4 */ + +struct mvm_statistics_rx_v3 { + struct mvm_statistics_rx_phy_v2 ofdm; + struct mvm_statistics_rx_phy_v2 cck; + struct mvm_statistics_rx_non_phy_v3 general; + struct mvm_statistics_rx_ht_phy_v1 ofdm_ht; +} __packed; /* STATISTICS_RX_API_S_VER_3 */ + +/* + * STATISTICS_NOTIFICATION = 0x9d (notification only, not a command) + * + * By default, uCode issues this notification after receiving a beacon + * while associated. To disable this behavior, set DISABLE_NOTIF flag in the + * STATISTICS_CMD (0x9c), below. + */ + +struct iwl_notif_statistics_v10 { + __le32 flag; + struct mvm_statistics_rx_v3 rx; + struct mvm_statistics_tx_v4 tx; + struct mvm_statistics_general_v8 general; +} __packed; /* STATISTICS_NTFY_API_S_VER_10 */ + +struct iwl_notif_statistics_v11 { + __le32 flag; + struct mvm_statistics_rx_v3 rx; + struct mvm_statistics_tx_v4 tx; + struct mvm_statistics_general_v8 general; + struct mvm_statistics_load_v1 load_stats; +} __packed; /* STATISTICS_NTFY_API_S_VER_11 */ + +struct iwl_notif_statistics { + __le32 flag; + struct mvm_statistics_rx rx; + struct mvm_statistics_tx tx; + struct mvm_statistics_general general; + struct mvm_statistics_load load_stats; +} __packed; /* STATISTICS_NTFY_API_S_VER_13 */ + +/** + * enum iwl_statistics_notif_flags - flags used in statistics notification + * @IWL_STATISTICS_REPLY_FLG_CLEAR: statistics were cleared after this report + */ +enum iwl_statistics_notif_flags { + IWL_STATISTICS_REPLY_FLG_CLEAR = 0x1, +}; + +/** + * enum iwl_statistics_cmd_flags - flags used in statistics command + * @IWL_STATISTICS_FLG_CLEAR: request to clear statistics after the report + * that's sent after this command + * @IWL_STATISTICS_FLG_DISABLE_NOTIF: disable unilateral statistics + * notifications + */ +enum iwl_statistics_cmd_flags { + IWL_STATISTICS_FLG_CLEAR = 0x1, + IWL_STATISTICS_FLG_DISABLE_NOTIF = 0x2, +}; + +/** + * struct iwl_statistics_cmd - statistics config command + * @flags: flags from &enum iwl_statistics_cmd_flags + */ +struct iwl_statistics_cmd { + __le32 flags; +} __packed; /* STATISTICS_CMD_API_S_VER_1 */ + +#define MAX_BCAST_FILTER_NUM 8 + +/** + * enum iwl_fw_statistics_type + * + * @FW_STATISTICS_OPERATIONAL: operational statistics + * @FW_STATISTICS_PHY: phy statistics + * @FW_STATISTICS_MAC: mac statistics + * @FW_STATISTICS_RX: rx statistics + * @FW_STATISTICS_TX: tx statistics + * @FW_STATISTICS_DURATION: duration statistics + * @FW_STATISTICS_HE: he statistics + */ +enum iwl_fw_statistics_type { + FW_STATISTICS_OPERATIONAL, + FW_STATISTICS_PHY, + FW_STATISTICS_MAC, + FW_STATISTICS_RX, + FW_STATISTICS_TX, + FW_STATISTICS_DURATION, + FW_STATISTICS_HE, +}; /* FW_STATISTICS_TYPE_API_E_VER_1 */ + +#define IWL_STATISTICS_TYPE_MSK 0x7f +/** + * struct iwl_statistics_ntfy_hdr + * + * @type: struct type + * @version: version of the struct + * @size: size in bytes + */ +struct iwl_statistics_ntfy_hdr { + u8 type; + u8 version; + __le16 size; +}; /* STATISTICS_NTFY_HDR_API_S_VER_1 */ + +/** + * struct iwl_statistics_ntfy_per_mac + * + * @beacon_filter_average_energy: Average energy [-dBm] of the 2 + * antennas. + * @air_time: air time + * @beacon_counter: all beacons (both filtered and not filtered) + * @beacon_average_energy: all beacons (both filtered and not + * filtered) + * @beacon_rssi_a: beacon RSSI on antenna A + * @beacon_rssi_b: beacon RSSI on antenna B + * @rx_bytes: RX byte count + */ +struct iwl_statistics_ntfy_per_mac { + __le32 beacon_filter_average_energy; + __le32 air_time; + __le32 beacon_counter; + __le32 beacon_average_energy; + __le32 beacon_rssi_a; + __le32 beacon_rssi_b; + __le32 rx_bytes; +} __packed; /* STATISTICS_NTFY_PER_MAC_API_S_VER_1 */ + +#define IWL_STATS_MAX_BW_INDEX 5 +/** struct iwl_statistics_ntfy_per_phy + * @channel_load: channel load + * @channel_load_by_us: device contribution to MCLM + * @channel_load_not_by_us: other devices' contribution to MCLM + * @clt: CLT HW timer (TIM_CH_LOAD2) + * @act: active accumulator SW + * @elp: elapsed time accumulator SW + * @rx_detected_per_ch_width: number of deferred TX per channel width, + * 0 - 20, 1/2/3 - 40/80/160 + * @success_per_ch_width: number of frames that got ACK/BACK/CTS + * per channel BW. note, BACK counted as 1 + * @fail_per_ch_width: number of frames that didn't get ACK/BACK/CTS + * per channel BW. note BACK counted as 1 + * @last_tx_ch_width_indx: last txed frame channel width index + */ +struct iwl_statistics_ntfy_per_phy { + __le32 channel_load; + __le32 channel_load_by_us; + __le32 channel_load_not_by_us; + __le32 clt; + __le32 act; + __le32 elp; + __le32 rx_detected_per_ch_width[IWL_STATS_MAX_BW_INDEX]; + __le32 success_per_ch_width[IWL_STATS_MAX_BW_INDEX]; + __le32 fail_per_ch_width[IWL_STATS_MAX_BW_INDEX]; + __le32 last_tx_ch_width_indx; +} __packed; /* STATISTICS_NTFY_PER_PHY_API_S_VER_1 */ + +/** + * struct iwl_statistics_ntfy_per_sta + * + * @average_energy: in fact it is minus the energy.. + */ +struct iwl_statistics_ntfy_per_sta { + __le32 average_energy; +} __packed; /* STATISTICS_NTFY_PER_STA_API_S_VER_1 */ + +#define IWL_STATS_MAX_PHY_OPERTINAL 3 +/** + * struct iwl_statistics_operational_ntfy + * + * @hdr: general statistics header + * @flags: bitmap of possible notification structures + * @per_mac_stats: per mac statistics, &struct iwl_statistics_ntfy_per_mac + * @per_phy_stats: per phy statistics, &struct iwl_statistics_ntfy_per_phy + * @per_sta_stats: per sta statistics, &struct iwl_statistics_ntfy_per_sta + * @rx_time: rx time + * @tx_time: usec the radio is transmitting. + * @on_time_rf: The total time in usec the RF is awake. + * @on_time_scan: usec the radio is awake due to scan. + */ +struct iwl_statistics_operational_ntfy { + struct iwl_statistics_ntfy_hdr hdr; + __le32 flags; + struct iwl_statistics_ntfy_per_mac per_mac_stats[MAC_INDEX_AUX]; + struct iwl_statistics_ntfy_per_phy per_phy_stats[IWL_STATS_MAX_PHY_OPERTINAL]; + struct iwl_statistics_ntfy_per_sta per_sta_stats[IWL_MVM_STATION_COUNT_MAX]; + __le64 rx_time; + __le64 tx_time; + __le64 on_time_rf; + __le64 on_time_scan; +} __packed; /* STATISTICS_OPERATIONAL_NTFY_API_S_VER_15 */ + +/** + * struct iwl_statistics_operational_ntfy_ver_14 + * + * @hdr: general statistics header + * @flags: bitmap of possible notification structures + * @mac_id: mac on which the beacon was received + * @beacon_filter_average_energy: Average energy [-dBm] of the 2 + * antennas. + * @beacon_filter_reason: beacon filter reason + * @radio_temperature: radio temperature + * @air_time: air time + * @beacon_counter: all beacons (both filtered and not filtered) + * @beacon_average_energy: all beacons (both filtered and not + * filtered) + * @beacon_rssi_a: beacon RSSI on antenna A + * @beacon_rssi_b: beacon RSSI on antenna B + * @rx_bytes: per MAC RX byte count + * @rx_time: rx time + * @tx_time: usec the radio is transmitting. + * @on_time_rf: The total time in usec the RF is awake. + * @on_time_scan: usec the radio is awake due to scan. + * @average_energy: in fact it is minus the energy.. + * @reserved: reserved + */ +struct iwl_statistics_operational_ntfy_ver_14 { + struct iwl_statistics_ntfy_hdr hdr; + __le32 flags; + __le32 mac_id; + __le32 beacon_filter_average_energy; + __le32 beacon_filter_reason; + __le32 radio_temperature; + __le32 air_time[MAC_INDEX_AUX]; + __le32 beacon_counter[MAC_INDEX_AUX]; + __le32 beacon_average_energy[MAC_INDEX_AUX]; + __le32 beacon_rssi_a; + __le32 beacon_rssi_b; + __le32 rx_bytes[MAC_INDEX_AUX]; + __le64 rx_time; + __le64 tx_time; + __le64 on_time_rf; + __le64 on_time_scan; + __le32 average_energy[IWL_MVM_STATION_COUNT_MAX]; + __le32 reserved; +} __packed; /* STATISTICS_OPERATIONAL_NTFY_API_S_VER_14 */ + +/** + * struct iwl_statistics_phy_ntfy + * + * @hdr: general statistics header + * RX PHY related statistics + * @energy_and_config: ??? + * @rssi_band: @31:24 rssiAllBand_B, 23:16 rssiInBand_B, 15:8 + * rssiAllBand_A, 7:0 rssiInBand_A + * @agc_word: @31:16 agcWord_B, 15:0 agcWord_A + * @agc_gain: @19:10 agcGain_B, 9:0 agcGain_A + * @dfe_gain: @19:10 dfeGain_B, 9:0 dfeGain_A + * @snr_calc_main: @18:0 snrCalcMain + * @energy_calc_main: @18:0 energyCalcMain + * @snr_calc_aux: @18:0 snrCalcAux + * @dsp_dc_estim_a: @27:14 dspDcEstimQA, 13:0 dspDcEstimIA + * @dsp_dc_estim_b: @27:14 dspDcEstimQB, 13:0 dspDcEstimIB + * @ina_detec_type_and_ofdm_corr_comb: @31:31 inaDetectCckMrc, + * 30:27 inaDetectType, 26:0 ofdmCorrComb + * @cw_corr_comb: @26:0 cwCorrComb + * @rssi_comb: @25:0 rssiComb + * @auto_corr_cck: @23:12 autoCck, 11:00 crossCck + * @ofdm_fine_freq_and_pina_freq_err: @18:7 ofdmFineFreq, 6:0 + * ofdmPinaFreqErr + * @snrm_evm_main: @31:0 snrmEvmMain + * @snrm_evm_aux: @31:0 snrmEvmAux + * @rx_rate: @31:0 rate + * TX PHY related statistics + * @per_chain_enums_and_dsp_atten_a: @perChainEnumsAndDspAtten + * (per version) + * @target_power_and_power_meas_a: @31:16 targetPower_A, 15:0 + * powerMeasuredCalc_A + * @tx_config_as_i_and_ac_a: @31:16 txConfigAsI_A, 15:0 + * txConfigAc_A + * @predist_dcq_and_dci_a: @31:16 predist_dci_A, 15:0 + * predist_dcq_A + * @per_chain_enums_and_dsp_atten_b: @perChainEnumsAndDspAtten + * (per version) + * @target_power_and_power_meas_b: @31:16 targetPower_B, 15:0 + * powerMeasuredCalc_B + * @tx_config_as_i_and_ac_b: @31:16 txConfigAsI_B, 15:0 + * txConfigAc_B + * @predist_dcq_and_dci_b: @31:16 predist_dci_B, 15:0 + * predist_dcq_B + * @tx_rate: @31:0 rate + * @tlc_backoff: @31:0 tlcBackoff + * @mpapd_calib_mode_mpapd_calib_type_a: @31:16 + * mpapdCalibMode_A, 15:0 mpapdCalibType_A + * @psat_and_phy_power_limit_a: @31:16 psat_A, 15:0 + * phyPowerLimit_A + * @sar_and_regulatory_power_limit_a: @31:16 sarPowerLimit_A, + * 15:0 regulatoryPowerLimit_A + * @mpapd_calib_mode_mpapd_calib_type_b: @31:16 + * mpapdCalibMode_B, 15:0 mpapdCalibType_B + * @psat_and_phy_power_limit_b: @31:16 psat_B, 15:0 + * phyPowerLimit_B + * @sar_and_regulatory_power_limit_b: @31:16 sarPowerLimit_B, + * 15:0 regulatoryPowerLimit_B + * @srd_and_driver_power_limits: @31:16 srdPowerLimit, 15:0 + * driverPowerLimit + * @reserved: reserved + */ +struct iwl_statistics_phy_ntfy { + struct iwl_statistics_ntfy_hdr hdr; + __le32 energy_and_config; + __le32 rssi_band; + __le32 agc_word; + __le32 agc_gain; + __le32 dfe_gain; + __le32 snr_calc_main; + __le32 energy_calc_main; + __le32 snr_calc_aux; + __le32 dsp_dc_estim_a; + __le32 dsp_dc_estim_b; + __le32 ina_detec_type_and_ofdm_corr_comb; + __le32 cw_corr_comb; + __le32 rssi_comb; + __le32 auto_corr_cck; + __le32 ofdm_fine_freq_and_pina_freq_err; + __le32 snrm_evm_main; + __le32 snrm_evm_aux; + __le32 rx_rate; + __le32 per_chain_enums_and_dsp_atten_a; + __le32 target_power_and_power_meas_a; + __le32 tx_config_as_i_and_ac_a; + __le32 predist_dcq_and_dci_a; + __le32 per_chain_enums_and_dsp_atten_b; + __le32 target_power_and_power_meas_b; + __le32 tx_config_as_i_and_ac_b; + __le32 predist_dcq_and_dci_b; + __le32 tx_rate; + __le32 tlc_backoff; + __le32 mpapd_calib_mode_mpapd_calib_type_a; + __le32 psat_and_phy_power_limit_a; + __le32 sar_and_regulatory_power_limit_a; + __le32 mpapd_calib_mode_mpapd_calib_type_b; + __le32 psat_and_phy_power_limit_b; + __le32 sar_and_regulatory_power_limit_b; + __le32 srd_and_driver_power_limits; + __le32 reserved; +} __packed; /* STATISTICS_PHY_NTFY_API_S_VER_1 */ + +/** + * struct iwl_statistics_mac_ntfy + * + * @hdr: general statistics header + * @bcast_filter_passed_per_mac: bcast filter passed per mac + * @bcast_filter_dropped_per_mac: bcast filter dropped per mac + * @bcast_filter_passed_per_filter: bcast filter passed per filter + * @bcast_filter_dropped_per_filter: bcast filter dropped per filter + * @reserved: reserved + */ +struct iwl_statistics_mac_ntfy { + struct iwl_statistics_ntfy_hdr hdr; + __le32 bcast_filter_passed_per_mac[NUM_MAC_INDEX_CDB]; + __le32 bcast_filter_dropped_per_mac[NUM_MAC_INDEX_CDB]; + __le32 bcast_filter_passed_per_filter[MAX_BCAST_FILTER_NUM]; + __le32 bcast_filter_dropped_per_filter[MAX_BCAST_FILTER_NUM]; + __le32 reserved; +} __packed; /* STATISTICS_MAC_NTFY_API_S_VER_1 */ + +/** + * struct iwl_statistics_rx_ntfy + * + * @hdr: general statistics header + * @rx_agg_mpdu_cnt: aggregation frame count (number of + * delimiters) + * @rx_agg_cnt: number of RX Aggregations + * @unsupported_mcs: number of PLCP headers that have rate which + * is unsupported by DSP + * @bogus_cts: CTS received when not expecting CTS + * @bogus_ack: ACK received when not expecting ACK + * @rx_byte_count: ??? + * @rx_packet_count: ??? + * @missed_beacons: ??? + * @unresponded_rts: un-responded RTS, due to NAV not zero + * @rxe_frame_limit_overrun: RXE got frame limit overrun + * @sent_ba_rsp_cnt: BA response TX count + * @late_rx_handle: count the number of times the RX path was + * aborted due to late entry + * @num_bt_kills: ??? + * @reserved: reserved + */ +struct iwl_statistics_rx_ntfy { + struct iwl_statistics_ntfy_hdr hdr; + __le32 rx_agg_mpdu_cnt; + __le32 rx_agg_cnt; + __le32 unsupported_mcs; + __le32 bogus_cts; + __le32 bogus_ack; + __le32 rx_byte_count[MAC_INDEX_AUX]; + __le32 rx_packet_count[MAC_INDEX_AUX]; + __le32 missed_beacons; + __le32 unresponded_rts; + __le32 rxe_frame_limit_overrun; + __le32 sent_ba_rsp_cnt; + __le32 late_rx_handle; + __le32 num_bt_kills; + __le32 reserved; +} __packed; /* STATISTICS_RX_NTFY_API_S_VER_1 */ + +/** + * struct iwl_statistics_tx_ntfy + * + * @hdr: general statistics header + * @cts_timeout: timeout when waiting for CTS + * @ack_timeout: timeout when waiting for ACK + * @dump_msdu_cnt: number of MSDUs that were dumped due to any + * reason + * @burst_abort_missing_next_frame_cnt: number of times a burst + * was aborted due to missing next frame bytes in txfifo + * number of times got timeout when waiting for CTS/ACK/BA and energy was + * detected just after sending the RTS/DATA. this statistics may help getting + * interesting indicators, like the likelihood of collision (so the benefit of + * protection may be estimated Vs. its cost). Or how many of the failures are + * due to collision and how many due to SNR. + * For Link-quality the CTS collision indication is more reliable then the ACK + * collision indication as the RTS frame is short and has more chance that the + * frame/s which caused the collision continue after the RTS was sent. + * @cts_timeout_collision: ??? + * ACK/BA failed and energy as detected after DATA + * Note: to get the collision ratio need to: + * ackOrBaTimeoutCollision / (ack_timeout + ba_timeout) + * @ack_or_ba_timeout_collision: ??? + * @ba_timeout: timeout when waiting for immediate BA response + * @ba_reschedule_frames: failed to get BA response and + * rescheduled all the non-ACKed frames + * gives the avarage number of frames inside aggregation + * @scd_query_agg_frame_cnt: ??? + * @scd_query_no_agg: scheduler query prevented aggregation + * @scd_query_agg: scheduler query allowed aggregation + * @scd_query_mismatch: scheduler query inaccurate, either too + * short or too long + * @agg_terminated_underrun: aggregation was terminated due to + * underrun + * @agg_terminated_bt_prio_kill: aggregation was terminated due + * to BT + * @tx_kill_on_long_retry: count the tx frames dropped due to + * long retry limit (DATA frame failed) + * @tx_kill_on_short_retry: count the tx frames dropped due to + * short retry limit (RTS frame failed) + * TX deffer on energy. This counter is reset on each successful transmit. + * When timer exceed TX deffer limit than will be uCode assert. + * @tx_deffer_counter: ??? + * @tx_deffer_base_time: Keep the time of the last successful + * transmit + * @tx_underrun: TX killed due to underrun + * @bt_defer: TX deferred due to BT priority, so probably TX was + * not started. + * @tx_kill_on_dsp_timeout: TX killed on DSP problem detected + * @tx_kill_on_immediate_quiet: TX killed due to immediate quiet + * @kill_ba_cnt: number of times sending BA failed + * @kill_ack_cnt: number of times sending ACK failed + * @kill_cts_cnt: number of times sending CTS failed + * @burst_terminated: Count burst or fragmentation termination + * occurrence + * @late_tx_vec_wr_cnt: ??? + * TX is not sent because ucode failed to notify the TRM in SIFS-delta from + * ON_AIR deassertion. + * @late_rx2_tx_cnt: ??? + * @scd_query_cnt: count the times SCD query was done to check + * for TX AGG + * @tx_frames_acked_in_agg: count the number of frames + * transmitted inside AGG and were successful + * @last_tx_ch_width_indx: ??? + * number of deferred TX per channel width, 0 - 20, 1/2/3 - 40/80/160 + * @rx_detected_per_ch_width: ??? + * @success_per_ch_width: ??? + * @fail_per_ch_width: ??? + * @reserved: reserved + */ +struct iwl_statistics_tx_ntfy { + struct iwl_statistics_ntfy_hdr hdr; + __le32 cts_timeout; + __le32 ack_timeout; + __le32 dump_msdu_cnt; + __le32 burst_abort_missing_next_frame_cnt; + __le32 cts_timeout_collision; + __le32 ack_or_ba_timeout_collision; + __le32 ba_timeout; + __le32 ba_reschedule_frames; + __le32 scd_query_agg_frame_cnt; + __le32 scd_query_no_agg; + __le32 scd_query_agg; + __le32 scd_query_mismatch; + __le32 agg_terminated_underrun; + __le32 agg_terminated_bt_prio_kill; + __le32 tx_kill_on_long_retry; + __le32 tx_kill_on_short_retry; + __le32 tx_deffer_counter; + __le32 tx_deffer_base_time; + __le32 tx_underrun; + __le32 bt_defer; + __le32 tx_kill_on_dsp_timeout; + __le32 tx_kill_on_immediate_quiet; + __le32 kill_ba_cnt; + __le32 kill_ack_cnt; + __le32 kill_cts_cnt; + __le32 burst_terminated; + __le32 late_tx_vec_wr_cnt; + __le32 late_rx2_tx_cnt; + __le32 scd_query_cnt; + __le32 tx_frames_acked_in_agg; + __le32 last_tx_ch_width_indx; + __le32 rx_detected_per_ch_width[4]; + __le32 success_per_ch_width[4]; + __le32 fail_per_ch_width[4]; + __le32 reserved; +} __packed; /* STATISTICS_TX_NTFY_API_S_VER_1 */ + +/** + * struct iwl_statistics_duration_ntfy + * + * @hdr: general statistics header + * @cont_burst_chk_cnt: number of times continuation or + * fragmentation or bursting was checked + * @cont_burst_cnt: number of times continuation or fragmentation + * or bursting was successful + * @wait_for_silence_timeout_cnt: ??? + * @reserved: reserved + */ +struct iwl_statistics_duration_ntfy { + struct iwl_statistics_ntfy_hdr hdr; + __le32 cont_burst_chk_cnt; + __le32 cont_burst_cnt; + __le32 wait_for_silence_timeout_cnt; + __le32 reserved; +} __packed; /* STATISTICS_DURATION_NTFY_API_S_VER_1 */ + +/** + * struct iwl_statistics_he_ntfy + * + * @hdr: general statistics header + * received HE frames + * @rx_siga_valid_cnt: rx HE SIG-A valid + * @rx_siga_invalid_cnt: rx HE SIG-A invalid + * received HE frames w/ valid Sig-A + * @rx_trig_based_frame_cnt: rx HE-TB (trig-based) + * @rx_su_frame_cnt: rx HE-SU + * @rx_sigb_invalid_cnt: rx (suspected) HE-MU w/ bad SIG-B + * @rx_our_bss_color_cnt: rx valid HE SIG-A w/ our BSS color + * @rx_other_bss_color_cnt: rx valid HE SIG-A w/ other BSS color + * @rx_zero_bss_color_cnt: ??? + * received HE-MU frames w/ good Sig-B + * @rx_mu_for_us_cnt: match AID + * @rx_mu_not_for_us_cnt: no matched AID + * received HE-MU frames for us (w/ our AID) + * @rx_mu_nss_ar: 0 - SISO, 1 - MIMO2 + * @rx_mu_mimo_cnt: full BW RU, compressed SIG-B + * @rx_mu_ru_bw_ar: MU alloc, MHz: 0 - 2, 1 - 5, 2 - 10, 3 - 20, + * 4 - 40, 5 - 80, 6 - 160 + * received trigger frames + * @rx_trig_for_us_cnt: ??? + * @rx_trig_not_for_us_cnt: ??? + * trigger for us + * @rx_trig_with_cs_req_cnt: ??? + * @rx_trig_type_ar: ??? + * @rx_trig_in_agg_cnt: ??? + * basic trigger for us allocations + * @rx_basic_trig_alloc_nss_ar: ??? + * @rx_basic_trig_alloc_mu_mimo_cnt: ??? + * @rx_basic_trig_alloc_ru_bw_ar: ??? + * @rx_basic_trig_total_byte_cnt: ??? + * trig-based TX + * @tx_trig_based_cs_req_fail_cnt: ??? + * @tx_trig_based_sifs_ok_cnt: ??? + * @tx_trig_based_sifs_fail_cnt: ??? + * @tx_trig_based_byte_cnt: ??? + * @tx_trig_based_pad_byte_cnt: ??? + * @tx_trig_based_frame_cnt: ??? + * @tx_trig_based_acked_frame_cnt: ??? + * @tx_trig_based_ack_timeout_cnt: ??? + * HE-SU TX + * @tx_su_frame_cnt: ??? + * EDCA <--> MU-EDCA transitions + * @tx_edca_to_mu_edca_cnt: ??? + * @tx_mu_edca_to_edca_by_timeout_cnt: ??? + * @tx_mu_edca_to_edca_by_ack_fail_cnt: ??? + * @tx_mu_edca_to_edca_by_small_alloc_cnt: ??? + * @reserved: reserved + */ +struct iwl_statistics_he_ntfy { + struct iwl_statistics_ntfy_hdr hdr; + __le32 rx_siga_valid_cnt; + __le32 rx_siga_invalid_cnt; + __le32 rx_trig_based_frame_cnt; + __le32 rx_su_frame_cnt; + __le32 rx_sigb_invalid_cnt; + __le32 rx_our_bss_color_cnt; + __le32 rx_other_bss_color_cnt; + __le32 rx_zero_bss_color_cnt; + __le32 rx_mu_for_us_cnt; + __le32 rx_mu_not_for_us_cnt; + __le32 rx_mu_nss_ar[2]; + __le32 rx_mu_mimo_cnt; + __le32 rx_mu_ru_bw_ar[7]; + __le32 rx_trig_for_us_cnt; + __le32 rx_trig_not_for_us_cnt; + __le32 rx_trig_with_cs_req_cnt; + __le32 rx_trig_type_ar[8 + 1]; + __le32 rx_trig_in_agg_cnt; + __le32 rx_basic_trig_alloc_nss_ar[2]; + __le32 rx_basic_trig_alloc_mu_mimo_cnt; + __le32 rx_basic_trig_alloc_ru_bw_ar[7]; + __le32 rx_basic_trig_total_byte_cnt; + __le32 tx_trig_based_cs_req_fail_cnt; + __le32 tx_trig_based_sifs_ok_cnt; + __le32 tx_trig_based_sifs_fail_cnt; + __le32 tx_trig_based_byte_cnt; + __le32 tx_trig_based_pad_byte_cnt; + __le32 tx_trig_based_frame_cnt; + __le32 tx_trig_based_acked_frame_cnt; + __le32 tx_trig_based_ack_timeout_cnt; + __le32 tx_su_frame_cnt; + __le32 tx_edca_to_mu_edca_cnt; + __le32 tx_mu_edca_to_edca_by_timeout_cnt; + __le32 tx_mu_edca_to_edca_by_ack_fail_cnt; + __le32 tx_mu_edca_to_edca_by_small_alloc_cnt; + __le32 reserved; +} __packed; /* STATISTICS_HE_NTFY_API_S_VER_1 */ + +#endif /* __iwl_fw_api_stats_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/system.h b/drivers/net/wireless/intel/iwlwifi/fw/api/system.h new file mode 100644 index 000000000..acf5d4b9a --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/system.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2019-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_system_h__ +#define __iwl_fw_api_system_h__ + +#define SOC_CONFIG_CMD_FLAGS_DISCRETE BIT(0) +#define SOC_CONFIG_CMD_FLAGS_LOW_LATENCY BIT(1) + +#define SOC_FLAGS_LTR_APPLY_DELAY_MASK 0xc +#define SOC_FLAGS_LTR_APPLY_DELAY_NONE 0 +#define SOC_FLAGS_LTR_APPLY_DELAY_200 1 +#define SOC_FLAGS_LTR_APPLY_DELAY_2500 2 +#define SOC_FLAGS_LTR_APPLY_DELAY_1820 3 + +/** + * struct iwl_soc_configuration_cmd - Set device stabilization latency + * + * @flags: soc settings flags. In VER_1, we can only set the DISCRETE + * flag, because the FW treats the whole value as an integer. In + * VER_2, we can set the bits independently. + * @latency: time for SOC to ensure stable power & XTAL + */ +struct iwl_soc_configuration_cmd { + __le32 flags; + __le32 latency; +} __packed; /* + * SOC_CONFIGURATION_CMD_S_VER_1 (see description above) + * SOC_CONFIGURATION_CMD_S_VER_2 + */ + +/** + * struct iwl_system_features_control_cmd - system features control command + * @features: bitmap of features to disable + */ +struct iwl_system_features_control_cmd { + __le32 features[4]; +} __packed; /* SYSTEM_FEATURES_CONTROL_CMD_API_S_VER_1 */ + +#endif /* __iwl_fw_api_system_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/tdls.h b/drivers/net/wireless/intel/iwlwifi/fw/api/tdls.h new file mode 100644 index 000000000..893438aad --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/tdls.h @@ -0,0 +1,161 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_tdls_h__ +#define __iwl_fw_api_tdls_h__ + +#include "fw/api/tx.h" +#include "fw/api/phy-ctxt.h" + +#define IWL_MVM_TDLS_STA_COUNT 4 + +/* Type of TDLS request */ +enum iwl_tdls_channel_switch_type { + TDLS_SEND_CHAN_SW_REQ = 0, + TDLS_SEND_CHAN_SW_RESP_AND_MOVE_CH, + TDLS_MOVE_CH, +}; /* TDLS_STA_CHANNEL_SWITCH_CMD_TYPE_API_E_VER_1 */ + +/** + * struct iwl_tdls_channel_switch_timing - Switch timing in TDLS channel-switch + * @frame_timestamp: GP2 timestamp of channel-switch request/response packet + * received from peer + * @max_offchan_duration: What amount of microseconds out of a DTIM is given + * to the TDLS off-channel communication. For instance if the DTIM is + * 200TU and the TDLS peer is to be given 25% of the time, the value + * given will be 50TU, or 50 * 1024 if translated into microseconds. + * @switch_time: switch time the peer sent in its channel switch timing IE + * @switch_timeout: switch timeout the peer sent in its channel switch timing IE + */ +struct iwl_tdls_channel_switch_timing { + __le32 frame_timestamp; /* GP2 time of peer packet Rx */ + __le32 max_offchan_duration; /* given in micro-seconds */ + __le32 switch_time; /* given in micro-seconds */ + __le32 switch_timeout; /* given in micro-seconds */ +} __packed; /* TDLS_STA_CHANNEL_SWITCH_TIMING_DATA_API_S_VER_1 */ + +#define IWL_TDLS_CH_SW_FRAME_MAX_SIZE 200 + +/** + * struct iwl_tdls_channel_switch_frame - TDLS channel switch frame template + * + * A template representing a TDLS channel-switch request or response frame + * + * @switch_time_offset: offset to the channel switch timing IE in the template + * @tx_cmd: Tx parameters for the frame + * @data: frame data + */ +struct iwl_tdls_channel_switch_frame { + __le32 switch_time_offset; + struct iwl_tx_cmd tx_cmd; + u8 data[IWL_TDLS_CH_SW_FRAME_MAX_SIZE]; +} __packed; /* TDLS_STA_CHANNEL_SWITCH_FRAME_API_S_VER_1 */ + +/** + * struct iwl_tdls_channel_switch_cmd_tail - tail of iwl_tdls_channel_switch_cmd + * + * @timing: timing related data for command + * @frame: channel-switch request/response template, depending to switch_type + */ +struct iwl_tdls_channel_switch_cmd_tail { + struct iwl_tdls_channel_switch_timing timing; + struct iwl_tdls_channel_switch_frame frame; +} __packed; + +/** + * struct iwl_tdls_channel_switch_cmd - TDLS channel switch command + * + * The command is sent to initiate a channel switch and also in response to + * incoming TDLS channel-switch request/response packets from remote peers. + * + * @switch_type: see &enum iwl_tdls_channel_switch_type + * @peer_sta_id: station id of TDLS peer + * @ci: channel we switch to + * @tail: command tail + */ +struct iwl_tdls_channel_switch_cmd { + u8 switch_type; + __le32 peer_sta_id; + struct iwl_fw_channel_info ci; + struct iwl_tdls_channel_switch_cmd_tail tail; +} __packed; /* TDLS_STA_CHANNEL_SWITCH_CMD_API_S_VER_1 */ + +/** + * struct iwl_tdls_channel_switch_notif - TDLS channel switch start notification + * + * @status: non-zero on success + * @offchannel_duration: duration given in microseconds + * @sta_id: peer currently performing the channel-switch with + */ +struct iwl_tdls_channel_switch_notif { + __le32 status; + __le32 offchannel_duration; + __le32 sta_id; +} __packed; /* TDLS_STA_CHANNEL_SWITCH_NTFY_API_S_VER_1 */ + +/** + * struct iwl_tdls_sta_info - TDLS station info + * + * @sta_id: station id of the TDLS peer + * @tx_to_peer_tid: TID reserved vs. the peer for FW based Tx + * @tx_to_peer_ssn: initial SSN the FW should use for Tx on its TID vs the peer + * @is_initiator: 1 if the peer is the TDLS link initiator, 0 otherwise + */ +struct iwl_tdls_sta_info { + u8 sta_id; + u8 tx_to_peer_tid; + __le16 tx_to_peer_ssn; + __le32 is_initiator; +} __packed; /* TDLS_STA_INFO_VER_1 */ + +/** + * struct iwl_tdls_config_cmd - TDLS basic config command + * + * @id_and_color: MAC id and color being configured + * @tdls_peer_count: amount of currently connected TDLS peers + * @tx_to_ap_tid: TID reverved vs. the AP for FW based Tx + * @tx_to_ap_ssn: initial SSN the FW should use for Tx on its TID vs. the AP + * @sta_info: per-station info. Only the first tdls_peer_count entries are set + * @pti_req_data_offset: offset of network-level data for the PTI template + * @pti_req_tx_cmd: Tx parameters for PTI request template + * @pti_req_template: PTI request template data + */ +struct iwl_tdls_config_cmd { + __le32 id_and_color; /* mac id and color */ + u8 tdls_peer_count; + u8 tx_to_ap_tid; + __le16 tx_to_ap_ssn; + struct iwl_tdls_sta_info sta_info[IWL_MVM_TDLS_STA_COUNT]; + + __le32 pti_req_data_offset; + struct iwl_tx_cmd pti_req_tx_cmd; + u8 pti_req_template[]; +} __packed; /* TDLS_CONFIG_CMD_API_S_VER_1 */ + +/** + * struct iwl_tdls_config_sta_info_res - TDLS per-station config information + * + * @sta_id: station id of the TDLS peer + * @tx_to_peer_last_seq: last sequence number used by FW during FW-based Tx to + * the peer + */ +struct iwl_tdls_config_sta_info_res { + __le16 sta_id; + __le16 tx_to_peer_last_seq; +} __packed; /* TDLS_STA_INFO_RSP_VER_1 */ + +/** + * struct iwl_tdls_config_res - TDLS config information from FW + * + * @tx_to_ap_last_seq: last sequence number used by FW during FW-based Tx to AP + * @sta_info: per-station TDLS config information + */ +struct iwl_tdls_config_res { + __le32 tx_to_ap_last_seq; + struct iwl_tdls_config_sta_info_res sta_info[IWL_MVM_TDLS_STA_COUNT]; +} __packed; /* TDLS_CONFIG_RSP_API_S_VER_1 */ + +#endif /* __iwl_fw_api_tdls_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/time-event.h b/drivers/net/wireless/intel/iwlwifi/fw/api/time-event.h new file mode 100644 index 000000000..904cd78a9 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/time-event.h @@ -0,0 +1,418 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_time_event_h__ +#define __iwl_fw_api_time_event_h__ + +#include "fw/api/phy-ctxt.h" + +/* Time Event types, according to MAC type */ +enum iwl_time_event_type { + /* BSS Station Events */ + TE_BSS_STA_AGGRESSIVE_ASSOC, + TE_BSS_STA_ASSOC, + TE_BSS_EAP_DHCP_PROT, + TE_BSS_QUIET_PERIOD, + + /* P2P Device Events */ + TE_P2P_DEVICE_DISCOVERABLE, + TE_P2P_DEVICE_LISTEN, + TE_P2P_DEVICE_ACTION_SCAN, + TE_P2P_DEVICE_FULL_SCAN, + + /* P2P Client Events */ + TE_P2P_CLIENT_AGGRESSIVE_ASSOC, + TE_P2P_CLIENT_ASSOC, + TE_P2P_CLIENT_QUIET_PERIOD, + + /* P2P GO Events */ + TE_P2P_GO_ASSOC_PROT, + TE_P2P_GO_REPETITIVET_NOA, + TE_P2P_GO_CT_WINDOW, + + /* WiDi Sync Events */ + TE_WIDI_TX_SYNC, + + /* Channel Switch NoA */ + TE_CHANNEL_SWITCH_PERIOD, + + TE_MAX +}; /* MAC_EVENT_TYPE_API_E_VER_1 */ + +/* Time event - defines for command API v1 */ + +/* + * @TE_V1_FRAG_NONE: fragmentation of the time event is NOT allowed. + * @TE_V1_FRAG_SINGLE: fragmentation of the time event is allowed, but only + * the first fragment is scheduled. + * @TE_V1_FRAG_DUAL: fragmentation of the time event is allowed, but only + * the first 2 fragments are scheduled. + * @TE_V1_FRAG_ENDLESS: fragmentation of the time event is allowed, and any + * number of fragments are valid. + * + * Other than the constant defined above, specifying a fragmentation value 'x' + * means that the event can be fragmented but only the first 'x' will be + * scheduled. + */ +enum { + TE_V1_FRAG_NONE = 0, + TE_V1_FRAG_SINGLE = 1, + TE_V1_FRAG_DUAL = 2, + TE_V1_FRAG_ENDLESS = 0xffffffff +}; + +/* If a Time Event can be fragmented, this is the max number of fragments */ +#define TE_V1_FRAG_MAX_MSK 0x0fffffff +/* Repeat the time event endlessly (until removed) */ +#define TE_V1_REPEAT_ENDLESS 0xffffffff +/* If a Time Event has bounded repetitions, this is the maximal value */ +#define TE_V1_REPEAT_MAX_MSK_V1 0x0fffffff + +/* Time Event dependencies: none, on another TE, or in a specific time */ +enum { + TE_V1_INDEPENDENT = 0, + TE_V1_DEP_OTHER = BIT(0), + TE_V1_DEP_TSF = BIT(1), + TE_V1_EVENT_SOCIOPATHIC = BIT(2), +}; /* MAC_EVENT_DEPENDENCY_POLICY_API_E_VER_2 */ + +/* + * @TE_V1_NOTIF_NONE: no notifications + * @TE_V1_NOTIF_HOST_EVENT_START: request/receive notification on event start + * @TE_V1_NOTIF_HOST_EVENT_END:request/receive notification on event end + * @TE_V1_NOTIF_INTERNAL_EVENT_START: internal FW use + * @TE_V1_NOTIF_INTERNAL_EVENT_END: internal FW use. + * @TE_V1_NOTIF_HOST_FRAG_START: request/receive notification on frag start + * @TE_V1_NOTIF_HOST_FRAG_END:request/receive notification on frag end + * @TE_V1_NOTIF_INTERNAL_FRAG_START: internal FW use. + * @TE_V1_NOTIF_INTERNAL_FRAG_END: internal FW use. + * + * Supported Time event notifications configuration. + * A notification (both event and fragment) includes a status indicating weather + * the FW was able to schedule the event or not. For fragment start/end + * notification the status is always success. There is no start/end fragment + * notification for monolithic events. + */ +enum { + TE_V1_NOTIF_NONE = 0, + TE_V1_NOTIF_HOST_EVENT_START = BIT(0), + TE_V1_NOTIF_HOST_EVENT_END = BIT(1), + TE_V1_NOTIF_INTERNAL_EVENT_START = BIT(2), + TE_V1_NOTIF_INTERNAL_EVENT_END = BIT(3), + TE_V1_NOTIF_HOST_FRAG_START = BIT(4), + TE_V1_NOTIF_HOST_FRAG_END = BIT(5), + TE_V1_NOTIF_INTERNAL_FRAG_START = BIT(6), + TE_V1_NOTIF_INTERNAL_FRAG_END = BIT(7), +}; /* MAC_EVENT_ACTION_API_E_VER_2 */ + +/* Time event - defines for command API */ + +/* + * @TE_V2_FRAG_NONE: fragmentation of the time event is NOT allowed. + * @TE_V2_FRAG_SINGLE: fragmentation of the time event is allowed, but only + * the first fragment is scheduled. + * @TE_V2_FRAG_DUAL: fragmentation of the time event is allowed, but only + * the first 2 fragments are scheduled. + * @TE_V2_FRAG_ENDLESS: fragmentation of the time event is allowed, and any + * number of fragments are valid. + * + * Other than the constant defined above, specifying a fragmentation value 'x' + * means that the event can be fragmented but only the first 'x' will be + * scheduled. + */ +enum { + TE_V2_FRAG_NONE = 0, + TE_V2_FRAG_SINGLE = 1, + TE_V2_FRAG_DUAL = 2, + TE_V2_FRAG_MAX = 0xfe, + TE_V2_FRAG_ENDLESS = 0xff +}; + +/* Repeat the time event endlessly (until removed) */ +#define TE_V2_REPEAT_ENDLESS 0xff +/* If a Time Event has bounded repetitions, this is the maximal value */ +#define TE_V2_REPEAT_MAX 0xfe + +#define TE_V2_PLACEMENT_POS 12 +#define TE_V2_ABSENCE_POS 15 + +/** + * enum iwl_time_event_policy - Time event policy values + * A notification (both event and fragment) includes a status indicating weather + * the FW was able to schedule the event or not. For fragment start/end + * notification the status is always success. There is no start/end fragment + * notification for monolithic events. + * + * @TE_V2_DEFAULT_POLICY: independent, social, present, unoticable + * @TE_V2_NOTIF_HOST_EVENT_START: request/receive notification on event start + * @TE_V2_NOTIF_HOST_EVENT_END:request/receive notification on event end + * @TE_V2_NOTIF_INTERNAL_EVENT_START: internal FW use + * @TE_V2_NOTIF_INTERNAL_EVENT_END: internal FW use. + * @TE_V2_NOTIF_HOST_FRAG_START: request/receive notification on frag start + * @TE_V2_NOTIF_HOST_FRAG_END:request/receive notification on frag end + * @TE_V2_NOTIF_INTERNAL_FRAG_START: internal FW use. + * @TE_V2_NOTIF_INTERNAL_FRAG_END: internal FW use. + * @TE_V2_START_IMMEDIATELY: start time event immediately + * @TE_V2_DEP_OTHER: depends on another time event + * @TE_V2_DEP_TSF: depends on a specific time + * @TE_V2_EVENT_SOCIOPATHIC: can't co-exist with other events of tha same MAC + * @TE_V2_ABSENCE: are we present or absent during the Time Event. + */ +enum iwl_time_event_policy { + TE_V2_DEFAULT_POLICY = 0x0, + + /* notifications (event start/stop, fragment start/stop) */ + TE_V2_NOTIF_HOST_EVENT_START = BIT(0), + TE_V2_NOTIF_HOST_EVENT_END = BIT(1), + TE_V2_NOTIF_INTERNAL_EVENT_START = BIT(2), + TE_V2_NOTIF_INTERNAL_EVENT_END = BIT(3), + + TE_V2_NOTIF_HOST_FRAG_START = BIT(4), + TE_V2_NOTIF_HOST_FRAG_END = BIT(5), + TE_V2_NOTIF_INTERNAL_FRAG_START = BIT(6), + TE_V2_NOTIF_INTERNAL_FRAG_END = BIT(7), + TE_V2_START_IMMEDIATELY = BIT(11), + + /* placement characteristics */ + TE_V2_DEP_OTHER = BIT(TE_V2_PLACEMENT_POS), + TE_V2_DEP_TSF = BIT(TE_V2_PLACEMENT_POS + 1), + TE_V2_EVENT_SOCIOPATHIC = BIT(TE_V2_PLACEMENT_POS + 2), + + /* are we present or absent during the Time Event. */ + TE_V2_ABSENCE = BIT(TE_V2_ABSENCE_POS), +}; + +/** + * struct iwl_time_event_cmd - configuring Time Events + * with struct MAC_TIME_EVENT_DATA_API_S_VER_2 (see also + * with version 1. determined by IWL_UCODE_TLV_FLAGS) + * ( TIME_EVENT_CMD = 0x29 ) + * @id_and_color: ID and color of the relevant MAC, + * &enum iwl_ctxt_id_and_color + * @action: action to perform, one of &enum iwl_ctxt_action + * @id: this field has two meanings, depending on the action: + * If the action is ADD, then it means the type of event to add. + * For all other actions it is the unique event ID assigned when the + * event was added by the FW. + * @apply_time: When to start the Time Event (in GP2) + * @max_delay: maximum delay to event's start (apply time), in TU + * @depends_on: the unique ID of the event we depend on (if any) + * @interval: interval between repetitions, in TU + * @duration: duration of event in TU + * @repeat: how many repetitions to do, can be TE_REPEAT_ENDLESS + * @max_frags: maximal number of fragments the Time Event can be divided to + * @policy: defines whether uCode shall notify the host or other uCode modules + * on event and/or fragment start and/or end + * using one of TE_INDEPENDENT, TE_DEP_OTHER, TE_DEP_TSF + * TE_EVENT_SOCIOPATHIC + * using TE_ABSENCE and using TE_NOTIF_*, + * &enum iwl_time_event_policy + */ +struct iwl_time_event_cmd { + /* COMMON_INDEX_HDR_API_S_VER_1 */ + __le32 id_and_color; + __le32 action; + __le32 id; + /* MAC_TIME_EVENT_DATA_API_S_VER_2 */ + __le32 apply_time; + __le32 max_delay; + __le32 depends_on; + __le32 interval; + __le32 duration; + u8 repeat; + u8 max_frags; + __le16 policy; +} __packed; /* MAC_TIME_EVENT_CMD_API_S_VER_2 */ + +/** + * struct iwl_time_event_resp - response structure to iwl_time_event_cmd + * @status: bit 0 indicates success, all others specify errors + * @id: the Time Event type + * @unique_id: the unique ID assigned (in ADD) or given (others) to the TE + * @id_and_color: ID and color of the relevant MAC, + * &enum iwl_ctxt_id_and_color + */ +struct iwl_time_event_resp { + __le32 status; + __le32 id; + __le32 unique_id; + __le32 id_and_color; +} __packed; /* MAC_TIME_EVENT_RSP_API_S_VER_1 */ + +/** + * struct iwl_time_event_notif - notifications of time event start/stop + * ( TIME_EVENT_NOTIFICATION = 0x2a ) + * @timestamp: action timestamp in GP2 + * @session_id: session's unique id + * @unique_id: unique id of the Time Event itself + * @id_and_color: ID and color of the relevant MAC + * @action: &enum iwl_time_event_policy + * @status: true if scheduled, false otherwise (not executed) + */ +struct iwl_time_event_notif { + __le32 timestamp; + __le32 session_id; + __le32 unique_id; + __le32 id_and_color; + __le32 action; + __le32 status; +} __packed; /* MAC_TIME_EVENT_NTFY_API_S_VER_1 */ + +/* + * struct iwl_hs20_roc_req_tail - tail of iwl_hs20_roc_req + * + * @node_addr: Our MAC Address + * @reserved: reserved for alignment + * @apply_time: GP2 value to start (should always be the current GP2 value) + * @apply_time_max_delay: Maximum apply time delay value in TU. Defines max + * time by which start of the event is allowed to be postponed. + * @duration: event duration in TU To calculate event duration: + * timeEventDuration = min(duration, remainingQuota) + */ +struct iwl_hs20_roc_req_tail { + u8 node_addr[ETH_ALEN]; + __le16 reserved; + __le32 apply_time; + __le32 apply_time_max_delay; + __le32 duration; +} __packed; + +/* + * Aux ROC command + * + * Command requests the firmware to create a time event for a certain duration + * and remain on the given channel. This is done by using the Aux framework in + * the FW. + * The command was first used for Hot Spot issues - but can be used regardless + * to Hot Spot. + * + * ( HOT_SPOT_CMD 0x53 ) + * + * @id_and_color: ID and color of the MAC + * @action: action to perform, one of FW_CTXT_ACTION_* + * @event_unique_id: If the action FW_CTXT_ACTION_REMOVE then the + * event_unique_id should be the id of the time event assigned by ucode. + * Otherwise ignore the event_unique_id. + * @sta_id_and_color: station id and color, resumed during "Remain On Channel" + * activity. + * @channel_info: channel info + */ +struct iwl_hs20_roc_req { + /* COMMON_INDEX_HDR_API_S_VER_1 hdr */ + __le32 id_and_color; + __le32 action; + __le32 event_unique_id; + __le32 sta_id_and_color; + struct iwl_fw_channel_info channel_info; + struct iwl_hs20_roc_req_tail tail; +} __packed; /* HOT_SPOT_CMD_API_S_VER_1 */ + +/* + * values for AUX ROC result values + */ +enum iwl_mvm_hot_spot { + HOT_SPOT_RSP_STATUS_OK, + HOT_SPOT_RSP_STATUS_TOO_MANY_EVENTS, + HOT_SPOT_MAX_NUM_OF_SESSIONS, +}; + +/* + * Aux ROC command response + * + * In response to iwl_hs20_roc_req the FW sends this command to notify the + * driver the uid of the timevent. + * + * ( HOT_SPOT_CMD 0x53 ) + * + * @event_unique_id: Unique ID of time event assigned by ucode + * @status: Return status 0 is success, all the rest used for specific errors + */ +struct iwl_hs20_roc_res { + __le32 event_unique_id; + __le32 status; +} __packed; /* HOT_SPOT_RSP_API_S_VER_1 */ + +/** + * enum iwl_mvm_session_prot_conf_id - session protection's configurations + * @SESSION_PROTECT_CONF_ASSOC: Start a session protection for association. + * The firmware will allocate two events. + * Valid for BSS_STA and P2P_STA. + * * A rather short event that can't be fragmented and with a very + * high priority. If every goes well (99% of the cases) the + * association should complete within this first event. During + * that event, no other activity will happen in the firmware, + * which is why it can't be too long. + * The length of this event is hard-coded in the firmware: 300TUs. + * * Another event which can be much longer (it's duration is + * configurable by the driver) which has a slightly lower + * priority and that can be fragmented allowing other activities + * to run while this event is running. + * The firmware will automatically remove both events once the driver sets + * the BSS MAC as associated. Neither of the events will be removed + * for the P2P_STA MAC. + * Only the duration is configurable for this protection. + * @SESSION_PROTECT_CONF_GO_CLIENT_ASSOC: not used + * @SESSION_PROTECT_CONF_P2P_DEVICE_DISCOV: Schedule the P2P Device to be in + * listen mode. Will be fragmented. Valid only on the P2P Device MAC. + * Valid only on the P2P Device MAC. The firmware will take into account + * the duration, the interval and the repetition count. + * @SESSION_PROTECT_CONF_P2P_GO_NEGOTIATION: Schedule the P2P Device to be be + * able to run the GO Negotiation. Will not be fragmented and not + * repetitive. Valid only on the P2P Device MAC. Only the duration will + * be taken into account. + * @SESSION_PROTECT_CONF_MAX_ID: not used + */ +enum iwl_mvm_session_prot_conf_id { + SESSION_PROTECT_CONF_ASSOC, + SESSION_PROTECT_CONF_GO_CLIENT_ASSOC, + SESSION_PROTECT_CONF_P2P_DEVICE_DISCOV, + SESSION_PROTECT_CONF_P2P_GO_NEGOTIATION, + SESSION_PROTECT_CONF_MAX_ID, +}; /* SESSION_PROTECTION_CONF_ID_E_VER_1 */ + +/** + * struct iwl_mvm_session_prot_cmd - configure a session protection + * @id_and_color: the id and color of the mac for which this session protection + * is sent + * @action: can be either FW_CTXT_ACTION_ADD or FW_CTXT_ACTION_REMOVE + * @conf_id: see &enum iwl_mvm_session_prot_conf_id + * @duration_tu: the duration of the whole protection in TUs. + * @repetition_count: not used + * @interval: not used + * + * Note: the session protection will always be scheduled to start as + * early as possible, but the maximum delay is configuration dependent. + * The firmware supports only one concurrent session protection per vif. + * Adding a new session protection will remove any currently running session. + */ +struct iwl_mvm_session_prot_cmd { + /* COMMON_INDEX_HDR_API_S_VER_1 hdr */ + __le32 id_and_color; + __le32 action; + __le32 conf_id; + __le32 duration_tu; + __le32 repetition_count; + __le32 interval; +} __packed; /* SESSION_PROTECTION_CMD_API_S_VER_1 */ + +/** + * struct iwl_mvm_session_prot_notif - session protection started / ended + * @mac_id: the mac id for which the session protection started / ended + * @status: 1 means success, 0 means failure + * @start: 1 means the session protection started, 0 means it ended + * @conf_id: see &enum iwl_mvm_session_prot_conf_id + * + * Note that any session protection will always get two notifications: start + * and end even the firmware could not schedule it. + */ +struct iwl_mvm_session_prot_notif { + __le32 mac_id; + __le32 status; + __le32 start; + __le32 conf_id; +} __packed; /* SESSION_PROTECTION_NOTIFICATION_API_S_VER_2 */ + +#endif /* __iwl_fw_api_time_event_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/tx.h b/drivers/net/wireless/intel/iwlwifi/fw/api/tx.h new file mode 100644 index 000000000..ecc6706f6 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/tx.h @@ -0,0 +1,966 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2022 Intel Corporation + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_tx_h__ +#define __iwl_fw_api_tx_h__ +#include <linux/ieee80211.h> + +/** + * enum iwl_tx_flags - bitmasks for tx_flags in TX command + * @TX_CMD_FLG_PROT_REQUIRE: use RTS or CTS-to-self to protect the frame + * @TX_CMD_FLG_WRITE_TX_POWER: update current tx power value in the mgmt frame + * @TX_CMD_FLG_ACK: expect ACK from receiving station + * @TX_CMD_FLG_STA_RATE: use RS table with initial index from the TX command. + * Otherwise, use rate_n_flags from the TX command + * @TX_CMD_FLG_BAR: this frame is a BA request, immediate BAR is expected + * Must set TX_CMD_FLG_ACK with this flag. + * @TX_CMD_FLG_TXOP_PROT: TXOP protection requested + * @TX_CMD_FLG_VHT_NDPA: mark frame is NDPA for VHT beamformer sequence + * @TX_CMD_FLG_HT_NDPA: mark frame is NDPA for HT beamformer sequence + * @TX_CMD_FLG_CSI_FDBK2HOST: mark to send feedback to host (only if good CRC) + * @TX_CMD_FLG_BT_PRIO_MASK: BT priority value + * @TX_CMD_FLG_BT_PRIO_POS: the position of the BT priority (bit 11 is ignored + * on old firmwares). + * @TX_CMD_FLG_BT_DIS: disable BT priority for this frame + * @TX_CMD_FLG_SEQ_CTL: set if FW should override the sequence control. + * Should be set for mgmt, non-QOS data, mcast, bcast and in scan command + * @TX_CMD_FLG_MORE_FRAG: this frame is non-last MPDU + * @TX_CMD_FLG_TSF: FW should calculate and insert TSF in the frame + * Should be set for beacons and probe responses + * @TX_CMD_FLG_CALIB: activate PA TX power calibrations + * @TX_CMD_FLG_KEEP_SEQ_CTL: if seq_ctl is set, don't increase inner seq count + * @TX_CMD_FLG_MH_PAD: driver inserted 2 byte padding after MAC header. + * Should be set for 26/30 length MAC headers + * @TX_CMD_FLG_RESP_TO_DRV: zero this if the response should go only to FW + * @TX_CMD_FLG_TKIP_MIC_DONE: FW already performed TKIP MIC calculation + * @TX_CMD_FLG_DUR: disable duration overwriting used in PS-Poll Assoc-id + * @TX_CMD_FLG_FW_DROP: FW should mark frame to be dropped + * @TX_CMD_FLG_EXEC_PAPD: execute PAPD + * @TX_CMD_FLG_PAPD_TYPE: 0 for reference power, 1 for nominal power + * @TX_CMD_FLG_HCCA_CHUNK: mark start of TSPEC chunk + */ +enum iwl_tx_flags { + TX_CMD_FLG_PROT_REQUIRE = BIT(0), + TX_CMD_FLG_WRITE_TX_POWER = BIT(1), + TX_CMD_FLG_ACK = BIT(3), + TX_CMD_FLG_STA_RATE = BIT(4), + TX_CMD_FLG_BAR = BIT(6), + TX_CMD_FLG_TXOP_PROT = BIT(7), + TX_CMD_FLG_VHT_NDPA = BIT(8), + TX_CMD_FLG_HT_NDPA = BIT(9), + TX_CMD_FLG_CSI_FDBK2HOST = BIT(10), + TX_CMD_FLG_BT_PRIO_POS = 11, + TX_CMD_FLG_BT_PRIO_MASK = BIT(11) | BIT(12), + TX_CMD_FLG_BT_DIS = BIT(12), + TX_CMD_FLG_SEQ_CTL = BIT(13), + TX_CMD_FLG_MORE_FRAG = BIT(14), + TX_CMD_FLG_TSF = BIT(16), + TX_CMD_FLG_CALIB = BIT(17), + TX_CMD_FLG_KEEP_SEQ_CTL = BIT(18), + TX_CMD_FLG_MH_PAD = BIT(20), + TX_CMD_FLG_RESP_TO_DRV = BIT(21), + TX_CMD_FLG_TKIP_MIC_DONE = BIT(23), + TX_CMD_FLG_DUR = BIT(25), + TX_CMD_FLG_FW_DROP = BIT(26), + TX_CMD_FLG_EXEC_PAPD = BIT(27), + TX_CMD_FLG_PAPD_TYPE = BIT(28), + TX_CMD_FLG_HCCA_CHUNK = BIT(31) +}; /* TX_FLAGS_BITS_API_S_VER_1 */ + +/** + * enum iwl_tx_cmd_flags - bitmasks for tx_flags in TX command for 22000 + * @IWL_TX_FLAGS_CMD_RATE: use rate from the TX command + * @IWL_TX_FLAGS_ENCRYPT_DIS: frame should not be encrypted, even if it belongs + * to a secured STA + * @IWL_TX_FLAGS_HIGH_PRI: high priority frame (like EAPOL) - can affect rate + * selection, retry limits and BT kill + */ +enum iwl_tx_cmd_flags { + IWL_TX_FLAGS_CMD_RATE = BIT(0), + IWL_TX_FLAGS_ENCRYPT_DIS = BIT(1), + IWL_TX_FLAGS_HIGH_PRI = BIT(2), + /* Use these flags only from + * TX_FLAGS_BITS_API_S_VER_4 and above */ + IWL_TX_FLAGS_RTS = BIT(3), + IWL_TX_FLAGS_CTS = BIT(4), +}; /* TX_FLAGS_BITS_API_S_VER_3 */ + +/** + * enum iwl_tx_pm_timeouts - pm timeout values in TX command + * @PM_FRAME_NONE: no need to suspend sleep mode + * @PM_FRAME_MGMT: fw suspend sleep mode for 100TU + * @PM_FRAME_ASSOC: fw suspend sleep mode for 10sec + */ +enum iwl_tx_pm_timeouts { + PM_FRAME_NONE = 0, + PM_FRAME_MGMT = 2, + PM_FRAME_ASSOC = 3, +}; + +#define TX_CMD_SEC_MSK 0x07 +#define TX_CMD_SEC_WEP_KEY_IDX_POS 6 +#define TX_CMD_SEC_WEP_KEY_IDX_MSK 0xc0 + +/** + * enum iwl_tx_cmd_sec_ctrl - bitmasks for security control in TX command + * @TX_CMD_SEC_WEP: WEP encryption algorithm. + * @TX_CMD_SEC_CCM: CCM encryption algorithm. + * @TX_CMD_SEC_TKIP: TKIP encryption algorithm. + * @TX_CMD_SEC_EXT: extended cipher algorithm. + * @TX_CMD_SEC_GCMP: GCMP encryption algorithm. + * @TX_CMD_SEC_KEY128: set for 104 bits WEP key. + * @TX_CMD_SEC_KEY_FROM_TABLE: for a non-WEP key, set if the key should be taken + * from the table instead of from the TX command. + * If the key is taken from the key table its index should be given by the + * first byte of the TX command key field. + */ +enum iwl_tx_cmd_sec_ctrl { + TX_CMD_SEC_WEP = 0x01, + TX_CMD_SEC_CCM = 0x02, + TX_CMD_SEC_TKIP = 0x03, + TX_CMD_SEC_EXT = 0x04, + TX_CMD_SEC_GCMP = 0x05, + TX_CMD_SEC_KEY128 = 0x08, + TX_CMD_SEC_KEY_FROM_TABLE = 0x10, +}; + +/* + * TX command Frame life time in us - to be written in pm_frame_timeout + */ +#define TX_CMD_LIFE_TIME_INFINITE 0xFFFFFFFF +#define TX_CMD_LIFE_TIME_DEFAULT 2000000 /* 2000 ms*/ +#define TX_CMD_LIFE_TIME_PROBE_RESP 40000 /* 40 ms */ +#define TX_CMD_LIFE_TIME_EXPIRED_FRAME 0 + +/* + * TID for non QoS frames - to be written in tid_tspec + */ +#define IWL_TID_NON_QOS 0 + +/* + * Limits on the retransmissions - to be written in {data,rts}_retry_limit + */ +#define IWL_DEFAULT_TX_RETRY 15 +#define IWL_MGMT_DFAULT_RETRY_LIMIT 3 +#define IWL_RTS_DFAULT_RETRY_LIMIT 60 +#define IWL_BAR_DFAULT_RETRY_LIMIT 60 +#define IWL_LOW_RETRY_LIMIT 7 + +/** + * enum iwl_tx_offload_assist_flags_pos - set %iwl_tx_cmd offload_assist values + * @TX_CMD_OFFLD_IP_HDR: offset to start of IP header (in words) + * from mac header end. For normal case it is 4 words for SNAP. + * note: tx_cmd, mac header and pad are not counted in the offset. + * This is used to help the offload in case there is tunneling such as + * IPv6 in IPv4, in such case the ip header offset should point to the + * inner ip header and IPv4 checksum of the external header should be + * calculated by driver. + * @TX_CMD_OFFLD_L4_EN: enable TCP/UDP checksum + * @TX_CMD_OFFLD_L3_EN: enable IP header checksum + * @TX_CMD_OFFLD_MH_SIZE: size of the mac header in words. Includes the IV + * field. Doesn't include the pad. + * @TX_CMD_OFFLD_PAD: mark 2-byte pad was inserted after the mac header for + * alignment + * @TX_CMD_OFFLD_AMSDU: mark TX command is A-MSDU + */ +enum iwl_tx_offload_assist_flags_pos { + TX_CMD_OFFLD_IP_HDR = 0, + TX_CMD_OFFLD_L4_EN = 6, + TX_CMD_OFFLD_L3_EN = 7, + TX_CMD_OFFLD_MH_SIZE = 8, + TX_CMD_OFFLD_PAD = 13, + TX_CMD_OFFLD_AMSDU = 14, +}; + +#define IWL_TX_CMD_OFFLD_MH_MASK 0x1f +#define IWL_TX_CMD_OFFLD_IP_HDR_MASK 0x3f + +enum iwl_tx_offload_assist_bz { + IWL_TX_CMD_OFFLD_BZ_RESULT_OFFS = 0x000003ff, + IWL_TX_CMD_OFFLD_BZ_START_OFFS = 0x001ff800, + IWL_TX_CMD_OFFLD_BZ_MH_LEN = 0x07c00000, + IWL_TX_CMD_OFFLD_BZ_MH_PAD = 0x08000000, + IWL_TX_CMD_OFFLD_BZ_AMSDU = 0x10000000, + IWL_TX_CMD_OFFLD_BZ_ZERO2ONES = 0x20000000, + IWL_TX_CMD_OFFLD_BZ_ENABLE_CSUM = 0x40000000, + IWL_TX_CMD_OFFLD_BZ_PARTIAL_CSUM = 0x80000000, +}; + +/* TODO: complete documentation for try_cnt and btkill_cnt */ +/** + * struct iwl_tx_cmd - TX command struct to FW + * ( TX_CMD = 0x1c ) + * @len: in bytes of the payload, see below for details + * @offload_assist: TX offload configuration + * @tx_flags: combination of TX_CMD_FLG_*, see &enum iwl_tx_flags + * @scratch: scratch buffer used by the device + * @rate_n_flags: rate for *all* Tx attempts, if TX_CMD_FLG_STA_RATE_MSK is + * cleared. Combination of RATE_MCS_* + * @sta_id: index of destination station in FW station table + * @sec_ctl: security control, TX_CMD_SEC_* + * @initial_rate_index: index into the the rate table for initial TX attempt. + * Applied if TX_CMD_FLG_STA_RATE_MSK is set, normally 0 for data frames. + * @reserved2: reserved + * @key: security key + * @reserved3: reserved + * @life_time: frame life time (usecs??) + * @dram_lsb_ptr: Physical address of scratch area in the command (try_cnt + + * btkill_cnd + reserved), first 32 bits. "0" disables usage. + * @dram_msb_ptr: upper bits of the scratch physical address + * @rts_retry_limit: max attempts for RTS + * @data_retry_limit: max attempts to send the data packet + * @tid_tspec: TID/tspec + * @pm_frame_timeout: PM TX frame timeout + * @reserved4: reserved + * @payload: payload (same as @hdr) + * @hdr: 802.11 header (same as @payload) + * + * The byte count (both len and next_frame_len) includes MAC header + * (24/26/30/32 bytes) + * + 2 bytes pad if 26/30 header size + * + 8 byte IV for CCM or TKIP (not used for WEP) + * + Data payload + * + 8-byte MIC (not used for CCM/WEP) + * It does not include post-MAC padding, i.e., + * MIC (CCM) 8 bytes, ICV (WEP/TKIP/CKIP) 4 bytes, CRC 4 bytes. + * Range of len: 14-2342 bytes. + * + * After the struct fields the MAC header is placed, plus any padding, + * and then the actial payload. + */ +struct iwl_tx_cmd { + __le16 len; + __le16 offload_assist; + __le32 tx_flags; + struct { + u8 try_cnt; + u8 btkill_cnt; + __le16 reserved; + } scratch; /* DRAM_SCRATCH_API_U_VER_1 */ + __le32 rate_n_flags; + u8 sta_id; + u8 sec_ctl; + u8 initial_rate_index; + u8 reserved2; + u8 key[16]; + __le32 reserved3; + __le32 life_time; + __le32 dram_lsb_ptr; + u8 dram_msb_ptr; + u8 rts_retry_limit; + u8 data_retry_limit; + u8 tid_tspec; + __le16 pm_frame_timeout; + __le16 reserved4; + union { + DECLARE_FLEX_ARRAY(u8, payload); + DECLARE_FLEX_ARRAY(struct ieee80211_hdr, hdr); + }; +} __packed; /* TX_CMD_API_S_VER_6 */ + +struct iwl_dram_sec_info { + __le32 pn_low; + __le16 pn_high; + __le16 aux_info; +} __packed; /* DRAM_SEC_INFO_API_S_VER_1 */ + +/** + * struct iwl_tx_cmd_gen2 - TX command struct to FW for 22000 devices + * ( TX_CMD = 0x1c ) + * @len: in bytes of the payload, see below for details + * @offload_assist: TX offload configuration + * @flags: combination of &enum iwl_tx_cmd_flags + * @dram_info: FW internal DRAM storage + * @rate_n_flags: rate for *all* Tx attempts, if TX_CMD_FLG_STA_RATE_MSK is + * cleared. Combination of RATE_MCS_* + * @hdr: 802.11 header + */ +struct iwl_tx_cmd_gen2 { + __le16 len; + __le16 offload_assist; + __le32 flags; + struct iwl_dram_sec_info dram_info; + __le32 rate_n_flags; + struct ieee80211_hdr hdr[]; +} __packed; /* TX_CMD_API_S_VER_7, + TX_CMD_API_S_VER_9 */ + +/** + * struct iwl_tx_cmd_gen3 - TX command struct to FW for AX210+ devices + * ( TX_CMD = 0x1c ) + * @len: in bytes of the payload, see below for details + * @flags: combination of &enum iwl_tx_cmd_flags + * @offload_assist: TX offload configuration + * @dram_info: FW internal DRAM storage + * @rate_n_flags: rate for *all* Tx attempts, if TX_CMD_FLG_STA_RATE_MSK is + * cleared. Combination of RATE_MCS_* + * @reserved: reserved + * @hdr: 802.11 header + */ +struct iwl_tx_cmd_gen3 { + __le16 len; + __le16 flags; + __le32 offload_assist; + struct iwl_dram_sec_info dram_info; + __le32 rate_n_flags; + u8 reserved[8]; + struct ieee80211_hdr hdr[]; +} __packed; /* TX_CMD_API_S_VER_8, + TX_CMD_API_S_VER_10 */ + +/* + * TX response related data + */ + +/* + * enum iwl_tx_status - status that is returned by the fw after attempts to Tx + * @TX_STATUS_SUCCESS: + * @TX_STATUS_DIRECT_DONE: + * @TX_STATUS_POSTPONE_DELAY: + * @TX_STATUS_POSTPONE_FEW_BYTES: + * @TX_STATUS_POSTPONE_BT_PRIO: + * @TX_STATUS_POSTPONE_QUIET_PERIOD: + * @TX_STATUS_POSTPONE_CALC_TTAK: + * @TX_STATUS_FAIL_INTERNAL_CROSSED_RETRY: + * @TX_STATUS_FAIL_SHORT_LIMIT: + * @TX_STATUS_FAIL_LONG_LIMIT: + * @TX_STATUS_FAIL_UNDERRUN: + * @TX_STATUS_FAIL_DRAIN_FLOW: + * @TX_STATUS_FAIL_RFKILL_FLUSH: + * @TX_STATUS_FAIL_LIFE_EXPIRE: + * @TX_STATUS_FAIL_DEST_PS: + * @TX_STATUS_FAIL_HOST_ABORTED: + * @TX_STATUS_FAIL_BT_RETRY: + * @TX_STATUS_FAIL_STA_INVALID: + * @TX_TATUS_FAIL_FRAG_DROPPED: + * @TX_STATUS_FAIL_TID_DISABLE: + * @TX_STATUS_FAIL_FIFO_FLUSHED: + * @TX_STATUS_FAIL_SMALL_CF_POLL: + * @TX_STATUS_FAIL_FW_DROP: + * @TX_STATUS_FAIL_STA_COLOR_MISMATCH: mismatch between color of Tx cmd and + * STA table + * @TX_FRAME_STATUS_INTERNAL_ABORT: + * @TX_MODE_MSK: + * @TX_MODE_NO_BURST: + * @TX_MODE_IN_BURST_SEQ: + * @TX_MODE_FIRST_IN_BURST: + * @TX_QUEUE_NUM_MSK: + * + * Valid only if frame_count =1 + * TODO: complete documentation + */ +enum iwl_tx_status { + TX_STATUS_MSK = 0x000000ff, + TX_STATUS_SUCCESS = 0x01, + TX_STATUS_DIRECT_DONE = 0x02, + /* postpone TX */ + TX_STATUS_POSTPONE_DELAY = 0x40, + TX_STATUS_POSTPONE_FEW_BYTES = 0x41, + TX_STATUS_POSTPONE_BT_PRIO = 0x42, + TX_STATUS_POSTPONE_QUIET_PERIOD = 0x43, + TX_STATUS_POSTPONE_CALC_TTAK = 0x44, + /* abort TX */ + TX_STATUS_FAIL_INTERNAL_CROSSED_RETRY = 0x81, + TX_STATUS_FAIL_SHORT_LIMIT = 0x82, + TX_STATUS_FAIL_LONG_LIMIT = 0x83, + TX_STATUS_FAIL_UNDERRUN = 0x84, + TX_STATUS_FAIL_DRAIN_FLOW = 0x85, + TX_STATUS_FAIL_RFKILL_FLUSH = 0x86, + TX_STATUS_FAIL_LIFE_EXPIRE = 0x87, + TX_STATUS_FAIL_DEST_PS = 0x88, + TX_STATUS_FAIL_HOST_ABORTED = 0x89, + TX_STATUS_FAIL_BT_RETRY = 0x8a, + TX_STATUS_FAIL_STA_INVALID = 0x8b, + TX_STATUS_FAIL_FRAG_DROPPED = 0x8c, + TX_STATUS_FAIL_TID_DISABLE = 0x8d, + TX_STATUS_FAIL_FIFO_FLUSHED = 0x8e, + TX_STATUS_FAIL_SMALL_CF_POLL = 0x8f, + TX_STATUS_FAIL_FW_DROP = 0x90, + TX_STATUS_FAIL_STA_COLOR_MISMATCH = 0x91, + TX_STATUS_INTERNAL_ABORT = 0x92, + TX_MODE_MSK = 0x00000f00, + TX_MODE_NO_BURST = 0x00000000, + TX_MODE_IN_BURST_SEQ = 0x00000100, + TX_MODE_FIRST_IN_BURST = 0x00000200, + TX_QUEUE_NUM_MSK = 0x0001f000, + TX_NARROW_BW_MSK = 0x00060000, + TX_NARROW_BW_1DIV2 = 0x00020000, + TX_NARROW_BW_1DIV4 = 0x00040000, + TX_NARROW_BW_1DIV8 = 0x00060000, +}; + +/* + * enum iwl_tx_agg_status - TX aggregation status + * @AGG_TX_STATE_STATUS_MSK: + * @AGG_TX_STATE_TRANSMITTED: + * @AGG_TX_STATE_UNDERRUN: + * @AGG_TX_STATE_BT_PRIO: + * @AGG_TX_STATE_FEW_BYTES: + * @AGG_TX_STATE_ABORT: + * @AGG_TX_STATE_TX_ON_AIR_DROP: TX_ON_AIR signal drop without underrun or + * BT detection + * @AGG_TX_STATE_LAST_SENT_TRY_CNT: + * @AGG_TX_STATE_LAST_SENT_BT_KILL: + * @AGG_TX_STATE_SCD_QUERY: + * @AGG_TX_STATE_TEST_BAD_CRC32: + * @AGG_TX_STATE_RESPONSE: + * @AGG_TX_STATE_DUMP_TX: + * @AGG_TX_STATE_DELAY_TX: + * @AGG_TX_STATE_TRY_CNT_MSK: Retry count for 1st frame in aggregation (retries + * occur if tx failed for this frame when it was a member of a previous + * aggregation block). If rate scaling is used, retry count indicates the + * rate table entry used for all frames in the new agg. + * @AGG_TX_STATE_SEQ_NUM_MSK: Command ID and sequence number of Tx command for + * this frame + * + * TODO: complete documentation + */ +enum iwl_tx_agg_status { + AGG_TX_STATE_STATUS_MSK = 0x00fff, + AGG_TX_STATE_TRANSMITTED = 0x000, + AGG_TX_STATE_UNDERRUN = 0x001, + AGG_TX_STATE_BT_PRIO = 0x002, + AGG_TX_STATE_FEW_BYTES = 0x004, + AGG_TX_STATE_ABORT = 0x008, + AGG_TX_STATE_TX_ON_AIR_DROP = 0x010, + AGG_TX_STATE_LAST_SENT_TRY_CNT = 0x020, + AGG_TX_STATE_LAST_SENT_BT_KILL = 0x040, + AGG_TX_STATE_SCD_QUERY = 0x080, + AGG_TX_STATE_TEST_BAD_CRC32 = 0x0100, + AGG_TX_STATE_RESPONSE = 0x1ff, + AGG_TX_STATE_DUMP_TX = 0x200, + AGG_TX_STATE_DELAY_TX = 0x400, + AGG_TX_STATE_TRY_CNT_POS = 12, + AGG_TX_STATE_TRY_CNT_MSK = 0xf << AGG_TX_STATE_TRY_CNT_POS, +}; + +/* + * The mask below describes a status where we are absolutely sure that the MPDU + * wasn't sent. For BA/Underrun we cannot be that sure. All we know that we've + * written the bytes to the TXE, but we know nothing about what the DSP did. + */ +#define AGG_TX_STAT_FRAME_NOT_SENT (AGG_TX_STATE_FEW_BYTES | \ + AGG_TX_STATE_ABORT | \ + AGG_TX_STATE_SCD_QUERY) + +/* + * REPLY_TX = 0x1c (response) + * + * This response may be in one of two slightly different formats, indicated + * by the frame_count field: + * + * 1) No aggregation (frame_count == 1). This reports Tx results for a single + * frame. Multiple attempts, at various bit rates, may have been made for + * this frame. + * + * 2) Aggregation (frame_count > 1). This reports Tx results for two or more + * frames that used block-acknowledge. All frames were transmitted at + * same rate. Rate scaling may have been used if first frame in this new + * agg block failed in previous agg block(s). + * + * Note that, for aggregation, ACK (block-ack) status is not delivered + * here; block-ack has not been received by the time the device records + * this status. + * This status relates to reasons the tx might have been blocked or aborted + * within the device, rather than whether it was received successfully by + * the destination station. + */ + +/** + * struct agg_tx_status - per packet TX aggregation status + * @status: See &enum iwl_tx_agg_status + * @sequence: Sequence # for this frame's Tx cmd (not SSN!) + */ +struct agg_tx_status { + __le16 status; + __le16 sequence; +} __packed; + +/* + * definitions for initial rate index field + * bits [3:0] initial rate index + * bits [6:4] rate table color, used for the initial rate + * bit-7 invalid rate indication + */ +#define TX_RES_INIT_RATE_INDEX_MSK 0x0f +#define TX_RES_RATE_TABLE_COLOR_POS 4 +#define TX_RES_RATE_TABLE_COLOR_MSK 0x70 +#define TX_RES_INV_RATE_INDEX_MSK 0x80 +#define TX_RES_RATE_TABLE_COL_GET(_f) (((_f) & TX_RES_RATE_TABLE_COLOR_MSK) >>\ + TX_RES_RATE_TABLE_COLOR_POS) + +#define IWL_MVM_TX_RES_GET_TID(_ra_tid) ((_ra_tid) & 0x0f) +#define IWL_MVM_TX_RES_GET_RA(_ra_tid) ((_ra_tid) >> 4) + +/** + * struct iwl_mvm_tx_resp_v3 - notifies that fw is TXing a packet + * ( REPLY_TX = 0x1c ) + * @frame_count: 1 no aggregation, >1 aggregation + * @bt_kill_count: num of times blocked by bluetooth (unused for agg) + * @failure_rts: num of failures due to unsuccessful RTS + * @failure_frame: num failures due to no ACK (unused for agg) + * @initial_rate: for non-agg: rate of the successful Tx. For agg: rate of the + * Tx of all the batch. RATE_MCS_* + * @wireless_media_time: for non-agg: RTS + CTS + frame tx attempts time + ACK. + * for agg: RTS + CTS + aggregation tx time + block-ack time. + * in usec. + * @pa_status: tx power info + * @pa_integ_res_a: tx power info + * @pa_integ_res_b: tx power info + * @pa_integ_res_c: tx power info + * @measurement_req_id: tx power info + * @reduced_tpc: transmit power reduction used + * @reserved: reserved + * @tfd_info: TFD information set by the FH + * @seq_ctl: sequence control from the Tx cmd + * @byte_cnt: byte count from the Tx cmd + * @tlc_info: TLC rate info + * @ra_tid: bits [3:0] = ra, bits [7:4] = tid + * @frame_ctrl: frame control + * @status: for non-agg: frame status TX_STATUS_* + * for agg: status of 1st frame, AGG_TX_STATE_*; other frame status fields + * follow this one, up to frame_count. Length in @frame_count. + * + * After the array of statuses comes the SSN of the SCD. Look at + * %iwl_mvm_get_scd_ssn for more details. + */ +struct iwl_mvm_tx_resp_v3 { + u8 frame_count; + u8 bt_kill_count; + u8 failure_rts; + u8 failure_frame; + __le32 initial_rate; + __le16 wireless_media_time; + + u8 pa_status; + u8 pa_integ_res_a[3]; + u8 pa_integ_res_b[3]; + u8 pa_integ_res_c[3]; + __le16 measurement_req_id; + u8 reduced_tpc; + u8 reserved; + + __le32 tfd_info; + __le16 seq_ctl; + __le16 byte_cnt; + u8 tlc_info; + u8 ra_tid; + __le16 frame_ctrl; + struct agg_tx_status status[]; +} __packed; /* TX_RSP_API_S_VER_3 */ + +/** + * struct iwl_mvm_tx_resp - notifies that fw is TXing a packet + * ( REPLY_TX = 0x1c ) + * @frame_count: 1 no aggregation, >1 aggregation + * @bt_kill_count: num of times blocked by bluetooth (unused for agg) + * @failure_rts: num of failures due to unsuccessful RTS + * @failure_frame: num failures due to no ACK (unused for agg) + * @initial_rate: for non-agg: rate of the successful Tx. For agg: rate of the + * Tx of all the batch. RATE_MCS_* + * @wireless_media_time: for non-agg: RTS + CTS + frame tx attempts time + ACK. + * for agg: RTS + CTS + aggregation tx time + block-ack time. + * in usec. + * @pa_status: tx power info + * @pa_integ_res_a: tx power info + * @pa_integ_res_b: tx power info + * @pa_integ_res_c: tx power info + * @measurement_req_id: tx power info + * @reduced_tpc: transmit power reduction used + * @reserved: reserved + * @tfd_info: TFD information set by the FH + * @seq_ctl: sequence control from the Tx cmd + * @byte_cnt: byte count from the Tx cmd + * @tlc_info: TLC rate info + * @ra_tid: bits [3:0] = ra, bits [7:4] = tid + * @frame_ctrl: frame control + * @tx_queue: TX queue for this response + * @reserved2: reserved for padding/alignment + * @status: for non-agg: frame status TX_STATUS_* + * For version 6 TX response isn't received for aggregation at all. + * + * After the array of statuses comes the SSN of the SCD. Look at + * %iwl_mvm_get_scd_ssn for more details. + */ +struct iwl_mvm_tx_resp { + u8 frame_count; + u8 bt_kill_count; + u8 failure_rts; + u8 failure_frame; + __le32 initial_rate; + __le16 wireless_media_time; + + u8 pa_status; + u8 pa_integ_res_a[3]; + u8 pa_integ_res_b[3]; + u8 pa_integ_res_c[3]; + __le16 measurement_req_id; + u8 reduced_tpc; + u8 reserved; + + __le32 tfd_info; + __le16 seq_ctl; + __le16 byte_cnt; + u8 tlc_info; + u8 ra_tid; + __le16 frame_ctrl; + __le16 tx_queue; + __le16 reserved2; + struct agg_tx_status status; +} __packed; /* TX_RSP_API_S_VER_6, + TX_RSP_API_S_VER_7 */ + +/** + * struct iwl_mvm_ba_notif - notifies about reception of BA + * ( BA_NOTIF = 0xc5 ) + * @sta_addr: MAC address + * @reserved: reserved + * @sta_id: Index of recipient (BA-sending) station in fw's station table + * @tid: tid of the session + * @seq_ctl: sequence control field + * @bitmap: the bitmap of the BA notification as seen in the air + * @scd_flow: the tx queue this BA relates to + * @scd_ssn: the index of the last contiguously sent packet + * @txed: number of Txed frames in this batch + * @txed_2_done: number of Acked frames in this batch + * @reduced_txp: power reduced according to TPC. This is the actual value and + * not a copy from the LQ command. Thus, if not the first rate was used + * for Tx-ing then this value will be set to 0 by FW. + * @reserved1: reserved + */ +struct iwl_mvm_ba_notif { + u8 sta_addr[ETH_ALEN]; + __le16 reserved; + + u8 sta_id; + u8 tid; + __le16 seq_ctl; + __le64 bitmap; + __le16 scd_flow; + __le16 scd_ssn; + u8 txed; + u8 txed_2_done; + u8 reduced_txp; + u8 reserved1; +} __packed; + +/** + * struct iwl_mvm_compressed_ba_tfd - progress of a TFD queue + * @q_num: TFD queue number + * @tfd_index: Index of first un-acked frame in the TFD queue + * @scd_queue: For debug only - the physical queue the TFD queue is bound to + * @tid: TID of the queue (0-7) + * @reserved: reserved for alignment + */ +struct iwl_mvm_compressed_ba_tfd { + __le16 q_num; + __le16 tfd_index; + u8 scd_queue; + u8 tid; + u8 reserved[2]; +} __packed; /* COMPRESSED_BA_TFD_API_S_VER_1 */ + +/** + * struct iwl_mvm_compressed_ba_ratid - progress of a RA TID queue + * @q_num: RA TID queue number + * @tid: TID of the queue + * @ssn: BA window current SSN + */ +struct iwl_mvm_compressed_ba_ratid { + u8 q_num; + u8 tid; + __le16 ssn; +} __packed; /* COMPRESSED_BA_RATID_API_S_VER_1 */ + +/* + * enum iwl_mvm_ba_resp_flags - TX aggregation status + * @IWL_MVM_BA_RESP_TX_AGG: generated due to BA + * @IWL_MVM_BA_RESP_TX_BAR: generated due to BA after BAR + * @IWL_MVM_BA_RESP_TX_AGG_FAIL: aggregation didn't receive BA + * @IWL_MVM_BA_RESP_TX_UNDERRUN: aggregation got underrun + * @IWL_MVM_BA_RESP_TX_BT_KILL: aggregation got BT-kill + * @IWL_MVM_BA_RESP_TX_DSP_TIMEOUT: aggregation didn't finish within the + * expected time + */ +enum iwl_mvm_ba_resp_flags { + IWL_MVM_BA_RESP_TX_AGG, + IWL_MVM_BA_RESP_TX_BAR, + IWL_MVM_BA_RESP_TX_AGG_FAIL, + IWL_MVM_BA_RESP_TX_UNDERRUN, + IWL_MVM_BA_RESP_TX_BT_KILL, + IWL_MVM_BA_RESP_TX_DSP_TIMEOUT +}; + +/** + * struct iwl_mvm_compressed_ba_notif - notifies about reception of BA + * ( BA_NOTIF = 0xc5 ) + * @flags: status flag, see the &iwl_mvm_ba_resp_flags + * @sta_id: Index of recipient (BA-sending) station in fw's station table + * @reduced_txp: power reduced according to TPC. This is the actual value and + * not a copy from the LQ command. Thus, if not the first rate was used + * for Tx-ing then this value will be set to 0 by FW. + * @tlc_rate_info: TLC rate info, initial rate index, TLC table color + * @retry_cnt: retry count + * @query_byte_cnt: SCD query byte count + * @query_frame_cnt: SCD query frame count + * @txed: number of frames sent in the aggregation (all-TIDs) + * @done: number of frames that were Acked by the BA (all-TIDs) + * @reserved: reserved (for alignment) + * @wireless_time: Wireless-media time + * @tx_rate: the rate the aggregation was sent at + * @tfd_cnt: number of TFD-Q elements + * @ra_tid_cnt: number of RATID-Q elements + * @tfd: array of TFD queue status updates. See &iwl_mvm_compressed_ba_tfd + * for details. Length in @tfd_cnt. + * @ra_tid: array of RA-TID queue status updates. For debug purposes only. See + * &iwl_mvm_compressed_ba_ratid for more details. Length in @ra_tid_cnt. + */ +struct iwl_mvm_compressed_ba_notif { + __le32 flags; + u8 sta_id; + u8 reduced_txp; + u8 tlc_rate_info; + u8 retry_cnt; + __le32 query_byte_cnt; + __le16 query_frame_cnt; + __le16 txed; + __le16 done; + __le16 reserved; + __le32 wireless_time; + __le32 tx_rate; + __le16 tfd_cnt; + __le16 ra_tid_cnt; + union { + DECLARE_FLEX_ARRAY(struct iwl_mvm_compressed_ba_ratid, ra_tid); + DECLARE_FLEX_ARRAY(struct iwl_mvm_compressed_ba_tfd, tfd); + }; +} __packed; /* COMPRESSED_BA_RES_API_S_VER_4, + COMPRESSED_BA_RES_API_S_VER_5 */ + +/** + * struct iwl_mac_beacon_cmd_v6 - beacon template command + * @tx: the tx commands associated with the beacon frame + * @template_id: currently equal to the mac context id of the coresponding + * mac. + * @tim_idx: the offset of the tim IE in the beacon + * @tim_size: the length of the tim IE + * @frame: the template of the beacon frame + */ +struct iwl_mac_beacon_cmd_v6 { + struct iwl_tx_cmd tx; + __le32 template_id; + __le32 tim_idx; + __le32 tim_size; + struct ieee80211_hdr frame[]; +} __packed; /* BEACON_TEMPLATE_CMD_API_S_VER_6 */ + +/** + * struct iwl_mac_beacon_cmd_v7 - beacon template command with offloaded CSA + * @tx: the tx commands associated with the beacon frame + * @template_id: currently equal to the mac context id of the coresponding + * mac. + * @tim_idx: the offset of the tim IE in the beacon + * @tim_size: the length of the tim IE + * @ecsa_offset: offset to the ECSA IE if present + * @csa_offset: offset to the CSA IE if present + * @frame: the template of the beacon frame + */ +struct iwl_mac_beacon_cmd_v7 { + struct iwl_tx_cmd tx; + __le32 template_id; + __le32 tim_idx; + __le32 tim_size; + __le32 ecsa_offset; + __le32 csa_offset; + struct ieee80211_hdr frame[]; +} __packed; /* BEACON_TEMPLATE_CMD_API_S_VER_7 */ + +/* Bit flags for BEACON_TEMPLATE_CMD_API until version 10 */ +enum iwl_mac_beacon_flags_v1 { + IWL_MAC_BEACON_CCK_V1 = BIT(8), + IWL_MAC_BEACON_ANT_A_V1 = BIT(9), + IWL_MAC_BEACON_ANT_B_V1 = BIT(10), + IWL_MAC_BEACON_FILS_V1 = BIT(12), +}; + +/* Bit flags for BEACON_TEMPLATE_CMD_API version 11 and above */ +enum iwl_mac_beacon_flags { + IWL_MAC_BEACON_CCK = BIT(5), + IWL_MAC_BEACON_ANT_A = BIT(6), + IWL_MAC_BEACON_ANT_B = BIT(7), + IWL_MAC_BEACON_FILS = BIT(8), +}; + +/** + * struct iwl_mac_beacon_cmd - beacon template command with offloaded CSA + * @byte_cnt: byte count of the beacon frame. + * @flags: least significant byte for rate code. The most significant byte + * is &enum iwl_mac_beacon_flags. + * @short_ssid: Short SSID + * @reserved: reserved + * @template_id: currently equal to the mac context id of the coresponding mac. + * @tim_idx: the offset of the tim IE in the beacon + * @tim_size: the length of the tim IE + * @ecsa_offset: offset to the ECSA IE if present + * @csa_offset: offset to the CSA IE if present + * @frame: the template of the beacon frame + */ +struct iwl_mac_beacon_cmd { + __le16 byte_cnt; + __le16 flags; + __le32 short_ssid; + __le32 reserved; + __le32 template_id; + __le32 tim_idx; + __le32 tim_size; + __le32 ecsa_offset; + __le32 csa_offset; + struct ieee80211_hdr frame[]; +} __packed; /* BEACON_TEMPLATE_CMD_API_S_VER_10, + BEACON_TEMPLATE_CMD_API_S_VER_11, + BEACON_TEMPLATE_CMD_API_S_VER_12 */ + +struct iwl_beacon_notif { + struct iwl_mvm_tx_resp beacon_notify_hdr; + __le64 tsf; + __le32 ibss_mgr_status; +} __packed; + +/** + * struct iwl_extended_beacon_notif_v5 - notifies about beacon transmission + * @beacon_notify_hdr: tx response command associated with the beacon + * @tsf: last beacon tsf + * @ibss_mgr_status: whether IBSS is manager + * @gp2: last beacon time in gp2 + */ +struct iwl_extended_beacon_notif_v5 { + struct iwl_mvm_tx_resp beacon_notify_hdr; + __le64 tsf; + __le32 ibss_mgr_status; + __le32 gp2; +} __packed; /* BEACON_NTFY_API_S_VER_5 */ + +/** + * struct iwl_extended_beacon_notif - notifies about beacon transmission + * @status: the status of the Tx response of the beacon + * @tsf: last beacon tsf + * @ibss_mgr_status: whether IBSS is manager + * @gp2: last beacon time in gp2 + */ +struct iwl_extended_beacon_notif { + __le32 status; + __le64 tsf; + __le32 ibss_mgr_status; + __le32 gp2; +} __packed; /* BEACON_NTFY_API_S_VER_6_ */ + +/** + * enum iwl_dump_control - dump (flush) control flags + * @DUMP_TX_FIFO_FLUSH: Dump MSDUs until the the FIFO is empty + * and the TFD queues are empty. + */ +enum iwl_dump_control { + DUMP_TX_FIFO_FLUSH = BIT(1), +}; + +/** + * struct iwl_tx_path_flush_cmd -- queue/FIFO flush command + * @queues_ctl: bitmap of queues to flush + * @flush_ctl: control flags + * @reserved: reserved + */ +struct iwl_tx_path_flush_cmd_v1 { + __le32 queues_ctl; + __le16 flush_ctl; + __le16 reserved; +} __packed; /* TX_PATH_FLUSH_CMD_API_S_VER_1 */ + +/** + * struct iwl_tx_path_flush_cmd -- queue/FIFO flush command + * @sta_id: station ID to flush + * @tid_mask: TID mask to flush + * @reserved: reserved + */ +struct iwl_tx_path_flush_cmd { + __le32 sta_id; + __le16 tid_mask; + __le16 reserved; +} __packed; /* TX_PATH_FLUSH_CMD_API_S_VER_2 */ + +#define IWL_TX_FLUSH_QUEUE_RSP 16 + +/** + * struct iwl_flush_queue_info - virtual flush queue info + * @queue_num: virtual queue id + * @read_before_flush: read pointer before flush + * @read_after_flush: read pointer after flush + */ +struct iwl_flush_queue_info { + __le16 tid; + __le16 queue_num; + __le16 read_before_flush; + __le16 read_after_flush; +} __packed; /* TFDQ_FLUSH_INFO_API_S_VER_1 */ + +/** + * struct iwl_tx_path_flush_cmd_rsp -- queue/FIFO flush command response + * @num_flushed_queues: number of queues in queues array + * @queues: all flushed queues + */ +struct iwl_tx_path_flush_cmd_rsp { + __le16 sta_id; + __le16 num_flushed_queues; + struct iwl_flush_queue_info queues[IWL_TX_FLUSH_QUEUE_RSP]; +} __packed; /* TX_PATH_FLUSH_CMD_RSP_API_S_VER_1 */ + +/* Available options for the SCD_QUEUE_CFG HCMD */ +enum iwl_scd_cfg_actions { + SCD_CFG_DISABLE_QUEUE = 0x0, + SCD_CFG_ENABLE_QUEUE = 0x1, + SCD_CFG_UPDATE_QUEUE_TID = 0x2, +}; + +/** + * struct iwl_scd_txq_cfg_cmd - New txq hw scheduler config command + * @token: unused + * @sta_id: station id + * @tid: TID + * @scd_queue: scheduler queue to confiug + * @action: 1 queue enable, 0 queue disable, 2 change txq's tid owner + * Value is one of &enum iwl_scd_cfg_actions options + * @aggregate: 1 aggregated queue, 0 otherwise + * @tx_fifo: &enum iwl_mvm_tx_fifo + * @window: BA window size + * @ssn: SSN for the BA agreement + * @reserved: reserved + */ +struct iwl_scd_txq_cfg_cmd { + u8 token; + u8 sta_id; + u8 tid; + u8 scd_queue; + u8 action; + u8 aggregate; + u8 tx_fifo; + u8 window; + __le16 ssn; + __le16 reserved; +} __packed; /* SCD_QUEUE_CFG_CMD_API_S_VER_1 */ + +/** + * struct iwl_scd_txq_cfg_rsp + * @token: taken from the command + * @sta_id: station id from the command + * @tid: tid from the command + * @scd_queue: scd_queue from the command + */ +struct iwl_scd_txq_cfg_rsp { + u8 token; + u8 sta_id; + u8 tid; + u8 scd_queue; +} __packed; /* SCD_QUEUE_CFG_RSP_API_S_VER_1 */ + +#endif /* __iwl_fw_api_tx_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/txq.h b/drivers/net/wireless/intel/iwlwifi/fw/api/txq.h new file mode 100644 index 000000000..e01894631 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/txq.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2019-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_api_txq_h__ +#define __iwl_fw_api_txq_h__ + +/* + * DQA queue numbers + * + * @IWL_MVM_DQA_CMD_QUEUE: a queue reserved for sending HCMDs to the FW + * @IWL_MVM_DQA_AUX_QUEUE: a queue reserved for aux frames + * @IWL_MVM_DQA_P2P_DEVICE_QUEUE: a queue reserved for P2P device frames + * @IWL_MVM_DQA_INJECT_MONITOR_QUEUE: a queue reserved for injection using + * monitor mode. Note this queue is the same as the queue for P2P device + * but we can't have active monitor mode along with P2P device anyway. + * @IWL_MVM_DQA_GCAST_QUEUE: a queue reserved for P2P GO/SoftAP GCAST frames + * @IWL_MVM_DQA_BSS_CLIENT_QUEUE: a queue reserved for BSS activity, to ensure + * that we are never left without the possibility to connect to an AP. + * @IWL_MVM_DQA_MIN_MGMT_QUEUE: first TXQ in pool for MGMT and non-QOS frames. + * Each MGMT queue is mapped to a single STA + * MGMT frames are frames that return true on ieee80211_is_mgmt() + * @IWL_MVM_DQA_MAX_MGMT_QUEUE: last TXQ in pool for MGMT frames + * @IWL_MVM_DQA_AP_PROBE_RESP_QUEUE: a queue reserved for P2P GO/SoftAP probe + * responses + * @IWL_MVM_DQA_MIN_DATA_QUEUE: first TXQ in pool for DATA frames. + * DATA frames are intended for !ieee80211_is_mgmt() frames, but if + * the MGMT TXQ pool is exhausted, mgmt frames can be sent on DATA queues + * as well + * @IWL_MVM_DQA_MAX_DATA_QUEUE: last TXQ in pool for DATA frames + */ +enum iwl_mvm_dqa_txq { + IWL_MVM_DQA_CMD_QUEUE = 0, + IWL_MVM_DQA_AUX_QUEUE = 1, + IWL_MVM_DQA_P2P_DEVICE_QUEUE = 2, + IWL_MVM_DQA_INJECT_MONITOR_QUEUE = 2, + IWL_MVM_DQA_GCAST_QUEUE = 3, + IWL_MVM_DQA_BSS_CLIENT_QUEUE = 4, + IWL_MVM_DQA_MIN_MGMT_QUEUE = 5, + IWL_MVM_DQA_MAX_MGMT_QUEUE = 8, + IWL_MVM_DQA_AP_PROBE_RESP_QUEUE = 9, + IWL_MVM_DQA_MIN_DATA_QUEUE = 10, + IWL_MVM_DQA_MAX_DATA_QUEUE = 30, +}; + +enum iwl_mvm_tx_fifo { + IWL_MVM_TX_FIFO_BK = 0, + IWL_MVM_TX_FIFO_BE, + IWL_MVM_TX_FIFO_VI, + IWL_MVM_TX_FIFO_VO, + IWL_MVM_TX_FIFO_MCAST = 5, + IWL_MVM_TX_FIFO_CMD = 7, +}; + +enum iwl_gen2_tx_fifo { + IWL_GEN2_TX_FIFO_CMD = 0, + IWL_GEN2_EDCA_TX_FIFO_BK, + IWL_GEN2_EDCA_TX_FIFO_BE, + IWL_GEN2_EDCA_TX_FIFO_VI, + IWL_GEN2_EDCA_TX_FIFO_VO, + IWL_GEN2_TRIG_TX_FIFO_BK, + IWL_GEN2_TRIG_TX_FIFO_BE, + IWL_GEN2_TRIG_TX_FIFO_VI, + IWL_GEN2_TRIG_TX_FIFO_VO, +}; + +/** + * enum iwl_tx_queue_cfg_actions - TXQ config options + * @TX_QUEUE_CFG_ENABLE_QUEUE: enable a queue + * @TX_QUEUE_CFG_TFD_SHORT_FORMAT: use short TFD format + */ +enum iwl_tx_queue_cfg_actions { + TX_QUEUE_CFG_ENABLE_QUEUE = BIT(0), + TX_QUEUE_CFG_TFD_SHORT_FORMAT = BIT(1), +}; + +#define IWL_DEFAULT_QUEUE_SIZE_EHT (1024 * 4) +#define IWL_DEFAULT_QUEUE_SIZE_HE 1024 +#define IWL_DEFAULT_QUEUE_SIZE 256 +#define IWL_MGMT_QUEUE_SIZE 16 +#define IWL_CMD_QUEUE_SIZE 32 +/** + * struct iwl_tx_queue_cfg_cmd - txq hw scheduler config command + * @sta_id: station id + * @tid: tid of the queue + * @flags: see &enum iwl_tx_queue_cfg_actions + * @cb_size: size of TFD cyclic buffer. Value is exponent - 3. + * Minimum value 0 (8 TFDs), maximum value 5 (256 TFDs) + * @byte_cnt_addr: address of byte count table + * @tfdq_addr: address of TFD circular buffer + */ +struct iwl_tx_queue_cfg_cmd { + u8 sta_id; + u8 tid; + __le16 flags; + __le32 cb_size; + __le64 byte_cnt_addr; + __le64 tfdq_addr; +} __packed; /* TX_QUEUE_CFG_CMD_API_S_VER_2 */ + +/** + * struct iwl_tx_queue_cfg_rsp - response to txq hw scheduler config + * @queue_number: queue number assigned to this RA -TID + * @flags: set on failure + * @write_pointer: initial value for write pointer + * @reserved: reserved + */ +struct iwl_tx_queue_cfg_rsp { + __le16 queue_number; + __le16 flags; + __le16 write_pointer; + __le16 reserved; +} __packed; /* TX_QUEUE_CFG_RSP_API_S_VER_2 */ + +#endif /* __iwl_fw_api_txq_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dbg.c b/drivers/net/wireless/intel/iwlwifi/fw/dbg.c new file mode 100644 index 000000000..3ef0b776b --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/dbg.c @@ -0,0 +1,3152 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#include <linux/devcoredump.h> +#include "iwl-drv.h" +#include "runtime.h" +#include "dbg.h" +#include "debugfs.h" +#include "iwl-io.h" +#include "iwl-prph.h" +#include "iwl-csr.h" +#include "iwl-fh.h" +/** + * struct iwl_fw_dump_ptrs - set of pointers needed for the fw-error-dump + * + * @fwrt_ptr: pointer to the buffer coming from fwrt + * @trans_ptr: pointer to struct %iwl_trans_dump_data which contains the + * transport's data. + * @trans_len: length of the valid data in trans_ptr + * @fwrt_len: length of the valid data in fwrt_ptr + */ +struct iwl_fw_dump_ptrs { + struct iwl_trans_dump_data *trans_ptr; + void *fwrt_ptr; + u32 fwrt_len; +}; + +#define RADIO_REG_MAX_READ 0x2ad +static void iwl_read_radio_regs(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data) +{ + u8 *pos = (void *)(*dump_data)->data; + int i; + + IWL_DEBUG_INFO(fwrt, "WRT radio registers dump\n"); + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return; + + (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RADIO_REG); + (*dump_data)->len = cpu_to_le32(RADIO_REG_MAX_READ); + + for (i = 0; i < RADIO_REG_MAX_READ; i++) { + u32 rd_cmd = RADIO_RSP_RD_CMD; + + rd_cmd |= i << RADIO_RSP_ADDR_POS; + iwl_write_prph_no_grab(fwrt->trans, RSP_RADIO_CMD, rd_cmd); + *pos = (u8)iwl_read_prph_no_grab(fwrt->trans, RSP_RADIO_RDDAT); + + pos++; + } + + *dump_data = iwl_fw_error_next_data(*dump_data); + + iwl_trans_release_nic_access(fwrt->trans); +} + +static void iwl_fwrt_dump_rxf(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data, + int size, u32 offset, int fifo_num) +{ + struct iwl_fw_error_dump_fifo *fifo_hdr; + u32 *fifo_data; + u32 fifo_len; + int i; + + fifo_hdr = (void *)(*dump_data)->data; + fifo_data = (void *)fifo_hdr->data; + fifo_len = size; + + /* No need to try to read the data if the length is 0 */ + if (fifo_len == 0) + return; + + /* Add a TLV for the RXF */ + (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RXF); + (*dump_data)->len = cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); + + fifo_hdr->fifo_num = cpu_to_le32(fifo_num); + fifo_hdr->available_bytes = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_RD_D_SPACE + offset)); + fifo_hdr->wr_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_RD_WR_PTR + offset)); + fifo_hdr->rd_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_RD_RD_PTR + offset)); + fifo_hdr->fence_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_RD_FENCE_PTR + offset)); + fifo_hdr->fence_mode = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + RXF_SET_FENCE_MODE + offset)); + + /* Lock fence */ + iwl_trans_write_prph(fwrt->trans, RXF_SET_FENCE_MODE + offset, 0x1); + /* Set fence pointer to the same place like WR pointer */ + iwl_trans_write_prph(fwrt->trans, RXF_LD_WR2FENCE + offset, 0x1); + /* Set fence offset */ + iwl_trans_write_prph(fwrt->trans, + RXF_LD_FENCE_OFFSET_ADDR + offset, 0x0); + + /* Read FIFO */ + fifo_len /= sizeof(u32); /* Size in DWORDS */ + for (i = 0; i < fifo_len; i++) + fifo_data[i] = iwl_trans_read_prph(fwrt->trans, + RXF_FIFO_RD_FENCE_INC + + offset); + *dump_data = iwl_fw_error_next_data(*dump_data); +} + +static void iwl_fwrt_dump_txf(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data, + int size, u32 offset, int fifo_num) +{ + struct iwl_fw_error_dump_fifo *fifo_hdr; + u32 *fifo_data; + u32 fifo_len; + int i; + + fifo_hdr = (void *)(*dump_data)->data; + fifo_data = (void *)fifo_hdr->data; + fifo_len = size; + + /* No need to try to read the data if the length is 0 */ + if (fifo_len == 0) + return; + + /* Add a TLV for the FIFO */ + (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_TXF); + (*dump_data)->len = cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); + + fifo_hdr->fifo_num = cpu_to_le32(fifo_num); + fifo_hdr->available_bytes = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_FIFO_ITEM_CNT + offset)); + fifo_hdr->wr_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_WR_PTR + offset)); + fifo_hdr->rd_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_RD_PTR + offset)); + fifo_hdr->fence_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_FENCE_PTR + offset)); + fifo_hdr->fence_mode = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_LOCK_FENCE + offset)); + + /* Set the TXF_READ_MODIFY_ADDR to TXF_WR_PTR */ + iwl_trans_write_prph(fwrt->trans, TXF_READ_MODIFY_ADDR + offset, + TXF_WR_PTR + offset); + + /* Dummy-read to advance the read pointer to the head */ + iwl_trans_read_prph(fwrt->trans, TXF_READ_MODIFY_DATA + offset); + + /* Read FIFO */ + for (i = 0; i < fifo_len / sizeof(u32); i++) + fifo_data[i] = iwl_trans_read_prph(fwrt->trans, + TXF_READ_MODIFY_DATA + + offset); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_txf) + fwrt->sanitize_ops->frob_txf(fwrt->sanitize_ctx, + fifo_data, fifo_len); + + *dump_data = iwl_fw_error_next_data(*dump_data); +} + +static void iwl_fw_dump_rxf(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data) +{ + struct iwl_fwrt_shared_mem_cfg *cfg = &fwrt->smem_cfg; + + IWL_DEBUG_INFO(fwrt, "WRT RX FIFO dump\n"); + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RXF)) { + /* Pull RXF1 */ + iwl_fwrt_dump_rxf(fwrt, dump_data, + cfg->lmac[0].rxfifo1_size, 0, 0); + /* Pull RXF2 */ + iwl_fwrt_dump_rxf(fwrt, dump_data, cfg->rxfifo2_size, + RXF_DIFF_FROM_PREV + + fwrt->trans->trans_cfg->umac_prph_offset, 1); + /* Pull LMAC2 RXF1 */ + if (fwrt->smem_cfg.num_lmacs > 1) + iwl_fwrt_dump_rxf(fwrt, dump_data, + cfg->lmac[1].rxfifo1_size, + LMAC2_PRPH_OFFSET, 2); + } + + iwl_trans_release_nic_access(fwrt->trans); +} + +static void iwl_fw_dump_txf(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data) +{ + struct iwl_fw_error_dump_fifo *fifo_hdr; + struct iwl_fwrt_shared_mem_cfg *cfg = &fwrt->smem_cfg; + u32 *fifo_data; + u32 fifo_len; + int i, j; + + IWL_DEBUG_INFO(fwrt, "WRT TX FIFO dump\n"); + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_TXF)) { + /* Pull TXF data from LMAC1 */ + for (i = 0; i < fwrt->smem_cfg.num_txfifo_entries; i++) { + /* Mark the number of TXF we're pulling now */ + iwl_trans_write_prph(fwrt->trans, TXF_LARC_NUM, i); + iwl_fwrt_dump_txf(fwrt, dump_data, + cfg->lmac[0].txfifo_size[i], 0, i); + } + + /* Pull TXF data from LMAC2 */ + if (fwrt->smem_cfg.num_lmacs > 1) { + for (i = 0; i < fwrt->smem_cfg.num_txfifo_entries; + i++) { + /* Mark the number of TXF we're pulling now */ + iwl_trans_write_prph(fwrt->trans, + TXF_LARC_NUM + + LMAC2_PRPH_OFFSET, i); + iwl_fwrt_dump_txf(fwrt, dump_data, + cfg->lmac[1].txfifo_size[i], + LMAC2_PRPH_OFFSET, + i + cfg->num_txfifo_entries); + } + } + } + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_INTERNAL_TXF) && + fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG)) { + /* Pull UMAC internal TXF data from all TXFs */ + for (i = 0; + i < ARRAY_SIZE(fwrt->smem_cfg.internal_txfifo_size); + i++) { + fifo_hdr = (void *)(*dump_data)->data; + fifo_data = (void *)fifo_hdr->data; + fifo_len = fwrt->smem_cfg.internal_txfifo_size[i]; + + /* No need to try to read the data if the length is 0 */ + if (fifo_len == 0) + continue; + + /* Add a TLV for the internal FIFOs */ + (*dump_data)->type = + cpu_to_le32(IWL_FW_ERROR_DUMP_INTERNAL_TXF); + (*dump_data)->len = + cpu_to_le32(fifo_len + sizeof(*fifo_hdr)); + + fifo_hdr->fifo_num = cpu_to_le32(i); + + /* Mark the number of TXF we're pulling now */ + iwl_trans_write_prph(fwrt->trans, TXF_CPU2_NUM, i + + fwrt->smem_cfg.num_txfifo_entries); + + fifo_hdr->available_bytes = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_FIFO_ITEM_CNT)); + fifo_hdr->wr_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_WR_PTR)); + fifo_hdr->rd_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_RD_PTR)); + fifo_hdr->fence_ptr = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_FENCE_PTR)); + fifo_hdr->fence_mode = + cpu_to_le32(iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_LOCK_FENCE)); + + /* Set TXF_CPU2_READ_MODIFY_ADDR to TXF_CPU2_WR_PTR */ + iwl_trans_write_prph(fwrt->trans, + TXF_CPU2_READ_MODIFY_ADDR, + TXF_CPU2_WR_PTR); + + /* Dummy-read to advance the read pointer to head */ + iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_READ_MODIFY_DATA); + + /* Read FIFO */ + fifo_len /= sizeof(u32); /* Size in DWORDS */ + for (j = 0; j < fifo_len; j++) + fifo_data[j] = + iwl_trans_read_prph(fwrt->trans, + TXF_CPU2_READ_MODIFY_DATA); + *dump_data = iwl_fw_error_next_data(*dump_data); + } + } + + iwl_trans_release_nic_access(fwrt->trans); +} + +struct iwl_prph_range { + u32 start, end; +}; + +static const struct iwl_prph_range iwl_prph_dump_addr_comm[] = { + { .start = 0x00a00000, .end = 0x00a00000 }, + { .start = 0x00a0000c, .end = 0x00a00024 }, + { .start = 0x00a0002c, .end = 0x00a0003c }, + { .start = 0x00a00410, .end = 0x00a00418 }, + { .start = 0x00a00420, .end = 0x00a00420 }, + { .start = 0x00a00428, .end = 0x00a00428 }, + { .start = 0x00a00430, .end = 0x00a0043c }, + { .start = 0x00a00444, .end = 0x00a00444 }, + { .start = 0x00a004c0, .end = 0x00a004cc }, + { .start = 0x00a004d8, .end = 0x00a004d8 }, + { .start = 0x00a004e0, .end = 0x00a004f0 }, + { .start = 0x00a00840, .end = 0x00a00840 }, + { .start = 0x00a00850, .end = 0x00a00858 }, + { .start = 0x00a01004, .end = 0x00a01008 }, + { .start = 0x00a01010, .end = 0x00a01010 }, + { .start = 0x00a01018, .end = 0x00a01018 }, + { .start = 0x00a01024, .end = 0x00a01024 }, + { .start = 0x00a0102c, .end = 0x00a01034 }, + { .start = 0x00a0103c, .end = 0x00a01040 }, + { .start = 0x00a01048, .end = 0x00a01094 }, + { .start = 0x00a01c00, .end = 0x00a01c20 }, + { .start = 0x00a01c58, .end = 0x00a01c58 }, + { .start = 0x00a01c7c, .end = 0x00a01c7c }, + { .start = 0x00a01c28, .end = 0x00a01c54 }, + { .start = 0x00a01c5c, .end = 0x00a01c5c }, + { .start = 0x00a01c60, .end = 0x00a01cdc }, + { .start = 0x00a01ce0, .end = 0x00a01d0c }, + { .start = 0x00a01d18, .end = 0x00a01d20 }, + { .start = 0x00a01d2c, .end = 0x00a01d30 }, + { .start = 0x00a01d40, .end = 0x00a01d5c }, + { .start = 0x00a01d80, .end = 0x00a01d80 }, + { .start = 0x00a01d98, .end = 0x00a01d9c }, + { .start = 0x00a01da8, .end = 0x00a01da8 }, + { .start = 0x00a01db8, .end = 0x00a01df4 }, + { .start = 0x00a01dc0, .end = 0x00a01dfc }, + { .start = 0x00a01e00, .end = 0x00a01e2c }, + { .start = 0x00a01e40, .end = 0x00a01e60 }, + { .start = 0x00a01e68, .end = 0x00a01e6c }, + { .start = 0x00a01e74, .end = 0x00a01e74 }, + { .start = 0x00a01e84, .end = 0x00a01e90 }, + { .start = 0x00a01e9c, .end = 0x00a01ec4 }, + { .start = 0x00a01ed0, .end = 0x00a01ee0 }, + { .start = 0x00a01f00, .end = 0x00a01f1c }, + { .start = 0x00a01f44, .end = 0x00a01ffc }, + { .start = 0x00a02000, .end = 0x00a02048 }, + { .start = 0x00a02068, .end = 0x00a020f0 }, + { .start = 0x00a02100, .end = 0x00a02118 }, + { .start = 0x00a02140, .end = 0x00a0214c }, + { .start = 0x00a02168, .end = 0x00a0218c }, + { .start = 0x00a021c0, .end = 0x00a021c0 }, + { .start = 0x00a02400, .end = 0x00a02410 }, + { .start = 0x00a02418, .end = 0x00a02420 }, + { .start = 0x00a02428, .end = 0x00a0242c }, + { .start = 0x00a02434, .end = 0x00a02434 }, + { .start = 0x00a02440, .end = 0x00a02460 }, + { .start = 0x00a02468, .end = 0x00a024b0 }, + { .start = 0x00a024c8, .end = 0x00a024cc }, + { .start = 0x00a02500, .end = 0x00a02504 }, + { .start = 0x00a0250c, .end = 0x00a02510 }, + { .start = 0x00a02540, .end = 0x00a02554 }, + { .start = 0x00a02580, .end = 0x00a025f4 }, + { .start = 0x00a02600, .end = 0x00a0260c }, + { .start = 0x00a02648, .end = 0x00a02650 }, + { .start = 0x00a02680, .end = 0x00a02680 }, + { .start = 0x00a026c0, .end = 0x00a026d0 }, + { .start = 0x00a02700, .end = 0x00a0270c }, + { .start = 0x00a02804, .end = 0x00a02804 }, + { .start = 0x00a02818, .end = 0x00a0281c }, + { .start = 0x00a02c00, .end = 0x00a02db4 }, + { .start = 0x00a02df4, .end = 0x00a02fb0 }, + { .start = 0x00a03000, .end = 0x00a03014 }, + { .start = 0x00a0301c, .end = 0x00a0302c }, + { .start = 0x00a03034, .end = 0x00a03038 }, + { .start = 0x00a03040, .end = 0x00a03048 }, + { .start = 0x00a03060, .end = 0x00a03068 }, + { .start = 0x00a03070, .end = 0x00a03074 }, + { .start = 0x00a0307c, .end = 0x00a0307c }, + { .start = 0x00a03080, .end = 0x00a03084 }, + { .start = 0x00a0308c, .end = 0x00a03090 }, + { .start = 0x00a03098, .end = 0x00a03098 }, + { .start = 0x00a030a0, .end = 0x00a030a0 }, + { .start = 0x00a030a8, .end = 0x00a030b4 }, + { .start = 0x00a030bc, .end = 0x00a030bc }, + { .start = 0x00a030c0, .end = 0x00a0312c }, + { .start = 0x00a03c00, .end = 0x00a03c5c }, + { .start = 0x00a04400, .end = 0x00a04454 }, + { .start = 0x00a04460, .end = 0x00a04474 }, + { .start = 0x00a044c0, .end = 0x00a044ec }, + { .start = 0x00a04500, .end = 0x00a04504 }, + { .start = 0x00a04510, .end = 0x00a04538 }, + { .start = 0x00a04540, .end = 0x00a04548 }, + { .start = 0x00a04560, .end = 0x00a0457c }, + { .start = 0x00a04590, .end = 0x00a04598 }, + { .start = 0x00a045c0, .end = 0x00a045f4 }, +}; + +static const struct iwl_prph_range iwl_prph_dump_addr_9000[] = { + { .start = 0x00a05c00, .end = 0x00a05c18 }, + { .start = 0x00a05400, .end = 0x00a056e8 }, + { .start = 0x00a08000, .end = 0x00a098bc }, + { .start = 0x00a02400, .end = 0x00a02758 }, + { .start = 0x00a04764, .end = 0x00a0476c }, + { .start = 0x00a04770, .end = 0x00a04774 }, + { .start = 0x00a04620, .end = 0x00a04624 }, +}; + +static const struct iwl_prph_range iwl_prph_dump_addr_22000[] = { + { .start = 0x00a00000, .end = 0x00a00000 }, + { .start = 0x00a0000c, .end = 0x00a00024 }, + { .start = 0x00a0002c, .end = 0x00a00034 }, + { .start = 0x00a0003c, .end = 0x00a0003c }, + { .start = 0x00a00410, .end = 0x00a00418 }, + { .start = 0x00a00420, .end = 0x00a00420 }, + { .start = 0x00a00428, .end = 0x00a00428 }, + { .start = 0x00a00430, .end = 0x00a0043c }, + { .start = 0x00a00444, .end = 0x00a00444 }, + { .start = 0x00a00840, .end = 0x00a00840 }, + { .start = 0x00a00850, .end = 0x00a00858 }, + { .start = 0x00a01004, .end = 0x00a01008 }, + { .start = 0x00a01010, .end = 0x00a01010 }, + { .start = 0x00a01018, .end = 0x00a01018 }, + { .start = 0x00a01024, .end = 0x00a01024 }, + { .start = 0x00a0102c, .end = 0x00a01034 }, + { .start = 0x00a0103c, .end = 0x00a01040 }, + { .start = 0x00a01048, .end = 0x00a01050 }, + { .start = 0x00a01058, .end = 0x00a01058 }, + { .start = 0x00a01060, .end = 0x00a01070 }, + { .start = 0x00a0108c, .end = 0x00a0108c }, + { .start = 0x00a01c20, .end = 0x00a01c28 }, + { .start = 0x00a01d10, .end = 0x00a01d10 }, + { .start = 0x00a01e28, .end = 0x00a01e2c }, + { .start = 0x00a01e60, .end = 0x00a01e60 }, + { .start = 0x00a01e80, .end = 0x00a01e80 }, + { .start = 0x00a01ea0, .end = 0x00a01ea0 }, + { .start = 0x00a02000, .end = 0x00a0201c }, + { .start = 0x00a02024, .end = 0x00a02024 }, + { .start = 0x00a02040, .end = 0x00a02048 }, + { .start = 0x00a020c0, .end = 0x00a020e0 }, + { .start = 0x00a02400, .end = 0x00a02404 }, + { .start = 0x00a0240c, .end = 0x00a02414 }, + { .start = 0x00a0241c, .end = 0x00a0243c }, + { .start = 0x00a02448, .end = 0x00a024bc }, + { .start = 0x00a024c4, .end = 0x00a024cc }, + { .start = 0x00a02508, .end = 0x00a02508 }, + { .start = 0x00a02510, .end = 0x00a02514 }, + { .start = 0x00a0251c, .end = 0x00a0251c }, + { .start = 0x00a0252c, .end = 0x00a0255c }, + { .start = 0x00a02564, .end = 0x00a025a0 }, + { .start = 0x00a025a8, .end = 0x00a025b4 }, + { .start = 0x00a025c0, .end = 0x00a025c0 }, + { .start = 0x00a025e8, .end = 0x00a025f4 }, + { .start = 0x00a02c08, .end = 0x00a02c18 }, + { .start = 0x00a02c2c, .end = 0x00a02c38 }, + { .start = 0x00a02c68, .end = 0x00a02c78 }, + { .start = 0x00a03000, .end = 0x00a03000 }, + { .start = 0x00a03010, .end = 0x00a03014 }, + { .start = 0x00a0301c, .end = 0x00a0302c }, + { .start = 0x00a03034, .end = 0x00a03038 }, + { .start = 0x00a03040, .end = 0x00a03044 }, + { .start = 0x00a03060, .end = 0x00a03068 }, + { .start = 0x00a03070, .end = 0x00a03070 }, + { .start = 0x00a0307c, .end = 0x00a03084 }, + { .start = 0x00a0308c, .end = 0x00a03090 }, + { .start = 0x00a03098, .end = 0x00a03098 }, + { .start = 0x00a030a0, .end = 0x00a030a0 }, + { .start = 0x00a030a8, .end = 0x00a030b4 }, + { .start = 0x00a030bc, .end = 0x00a030c0 }, + { .start = 0x00a030c8, .end = 0x00a030f4 }, + { .start = 0x00a03100, .end = 0x00a0312c }, + { .start = 0x00a03c00, .end = 0x00a03c5c }, + { .start = 0x00a04400, .end = 0x00a04454 }, + { .start = 0x00a04460, .end = 0x00a04474 }, + { .start = 0x00a044c0, .end = 0x00a044ec }, + { .start = 0x00a04500, .end = 0x00a04504 }, + { .start = 0x00a04510, .end = 0x00a04538 }, + { .start = 0x00a04540, .end = 0x00a04548 }, + { .start = 0x00a04560, .end = 0x00a04560 }, + { .start = 0x00a04570, .end = 0x00a0457c }, + { .start = 0x00a04590, .end = 0x00a04590 }, + { .start = 0x00a04598, .end = 0x00a04598 }, + { .start = 0x00a045c0, .end = 0x00a045f4 }, + { .start = 0x00a05c18, .end = 0x00a05c1c }, + { .start = 0x00a0c000, .end = 0x00a0c018 }, + { .start = 0x00a0c020, .end = 0x00a0c028 }, + { .start = 0x00a0c038, .end = 0x00a0c094 }, + { .start = 0x00a0c0c0, .end = 0x00a0c104 }, + { .start = 0x00a0c10c, .end = 0x00a0c118 }, + { .start = 0x00a0c150, .end = 0x00a0c174 }, + { .start = 0x00a0c17c, .end = 0x00a0c188 }, + { .start = 0x00a0c190, .end = 0x00a0c198 }, + { .start = 0x00a0c1a0, .end = 0x00a0c1a8 }, + { .start = 0x00a0c1b0, .end = 0x00a0c1b8 }, +}; + +static const struct iwl_prph_range iwl_prph_dump_addr_ax210[] = { + { .start = 0x00d03c00, .end = 0x00d03c64 }, + { .start = 0x00d05c18, .end = 0x00d05c1c }, + { .start = 0x00d0c000, .end = 0x00d0c174 }, +}; + +static void iwl_read_prph_block(struct iwl_trans *trans, u32 start, + u32 len_bytes, __le32 *data) +{ + u32 i; + + for (i = 0; i < len_bytes; i += 4) + *data++ = cpu_to_le32(iwl_read_prph_no_grab(trans, start + i)); +} + +static void iwl_dump_prph(struct iwl_fw_runtime *fwrt, + const struct iwl_prph_range *iwl_prph_dump_addr, + u32 range_len, void *ptr) +{ + struct iwl_fw_error_dump_prph *prph; + struct iwl_trans *trans = fwrt->trans; + struct iwl_fw_error_dump_data **data = + (struct iwl_fw_error_dump_data **)ptr; + u32 i; + + if (!data) + return; + + IWL_DEBUG_INFO(trans, "WRT PRPH dump\n"); + + if (!iwl_trans_grab_nic_access(trans)) + return; + + for (i = 0; i < range_len; i++) { + /* The range includes both boundaries */ + int num_bytes_in_chunk = iwl_prph_dump_addr[i].end - + iwl_prph_dump_addr[i].start + 4; + + (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_PRPH); + (*data)->len = cpu_to_le32(sizeof(*prph) + + num_bytes_in_chunk); + prph = (void *)(*data)->data; + prph->prph_start = cpu_to_le32(iwl_prph_dump_addr[i].start); + + iwl_read_prph_block(trans, iwl_prph_dump_addr[i].start, + /* our range is inclusive, hence + 4 */ + iwl_prph_dump_addr[i].end - + iwl_prph_dump_addr[i].start + 4, + (void *)prph->data); + + *data = iwl_fw_error_next_data(*data); + } + + iwl_trans_release_nic_access(trans); +} + +/* + * alloc_sgtable - allocates scallerlist table in the given size, + * fills it with pages and returns it + * @size: the size (in bytes) of the table +*/ +static struct scatterlist *alloc_sgtable(int size) +{ + int alloc_size, nents, i; + struct page *new_page; + struct scatterlist *iter; + struct scatterlist *table; + + nents = DIV_ROUND_UP(size, PAGE_SIZE); + table = kcalloc(nents, sizeof(*table), GFP_KERNEL); + if (!table) + return NULL; + sg_init_table(table, nents); + iter = table; + for_each_sg(table, iter, sg_nents(table), i) { + new_page = alloc_page(GFP_KERNEL); + if (!new_page) { + /* release all previous allocated pages in the table */ + iter = table; + for_each_sg(table, iter, sg_nents(table), i) { + new_page = sg_page(iter); + if (new_page) + __free_page(new_page); + } + kfree(table); + return NULL; + } + alloc_size = min_t(int, size, PAGE_SIZE); + size -= PAGE_SIZE; + sg_set_page(iter, new_page, alloc_size, 0); + } + return table; +} + +static void iwl_fw_get_prph_len(struct iwl_fw_runtime *fwrt, + const struct iwl_prph_range *iwl_prph_dump_addr, + u32 range_len, void *ptr) +{ + u32 *prph_len = (u32 *)ptr; + int i, num_bytes_in_chunk; + + if (!prph_len) + return; + + for (i = 0; i < range_len; i++) { + /* The range includes both boundaries */ + num_bytes_in_chunk = + iwl_prph_dump_addr[i].end - + iwl_prph_dump_addr[i].start + 4; + + *prph_len += sizeof(struct iwl_fw_error_dump_data) + + sizeof(struct iwl_fw_error_dump_prph) + + num_bytes_in_chunk; + } +} + +static void iwl_fw_prph_handler(struct iwl_fw_runtime *fwrt, void *ptr, + void (*handler)(struct iwl_fw_runtime *, + const struct iwl_prph_range *, + u32, void *)) +{ + u32 range_len; + + if (fwrt->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + range_len = ARRAY_SIZE(iwl_prph_dump_addr_ax210); + handler(fwrt, iwl_prph_dump_addr_ax210, range_len, ptr); + } else if (fwrt->trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_22000) { + range_len = ARRAY_SIZE(iwl_prph_dump_addr_22000); + handler(fwrt, iwl_prph_dump_addr_22000, range_len, ptr); + } else { + range_len = ARRAY_SIZE(iwl_prph_dump_addr_comm); + handler(fwrt, iwl_prph_dump_addr_comm, range_len, ptr); + + if (fwrt->trans->trans_cfg->mq_rx_supported) { + range_len = ARRAY_SIZE(iwl_prph_dump_addr_9000); + handler(fwrt, iwl_prph_dump_addr_9000, range_len, ptr); + } + } +} + +static void iwl_fw_dump_mem(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **dump_data, + u32 len, u32 ofs, u32 type) +{ + struct iwl_fw_error_dump_mem *dump_mem; + + if (!len) + return; + + (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_MEM); + (*dump_data)->len = cpu_to_le32(len + sizeof(*dump_mem)); + dump_mem = (void *)(*dump_data)->data; + dump_mem->type = cpu_to_le32(type); + dump_mem->offset = cpu_to_le32(ofs); + iwl_trans_read_mem_bytes(fwrt->trans, ofs, dump_mem->data, len); + *dump_data = iwl_fw_error_next_data(*dump_data); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) + fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, ofs, + dump_mem->data, len); + + IWL_DEBUG_INFO(fwrt, "WRT memory dump. Type=%u\n", dump_mem->type); +} + +#define ADD_LEN(len, item_len, const_len) \ + do {size_t item = item_len; len += (!!item) * const_len + item; } \ + while (0) + +static int iwl_fw_rxf_len(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_shared_mem_cfg *mem_cfg) +{ + size_t hdr_len = sizeof(struct iwl_fw_error_dump_data) + + sizeof(struct iwl_fw_error_dump_fifo); + u32 fifo_len = 0; + int i; + + if (!iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RXF)) + return 0; + + /* Count RXF2 size */ + ADD_LEN(fifo_len, mem_cfg->rxfifo2_size, hdr_len); + + /* Count RXF1 sizes */ + if (WARN_ON(mem_cfg->num_lmacs > MAX_NUM_LMAC)) + mem_cfg->num_lmacs = MAX_NUM_LMAC; + + for (i = 0; i < mem_cfg->num_lmacs; i++) + ADD_LEN(fifo_len, mem_cfg->lmac[i].rxfifo1_size, hdr_len); + + return fifo_len; +} + +static int iwl_fw_txf_len(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_shared_mem_cfg *mem_cfg) +{ + size_t hdr_len = sizeof(struct iwl_fw_error_dump_data) + + sizeof(struct iwl_fw_error_dump_fifo); + u32 fifo_len = 0; + int i; + + if (!iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_TXF)) + goto dump_internal_txf; + + /* Count TXF sizes */ + if (WARN_ON(mem_cfg->num_lmacs > MAX_NUM_LMAC)) + mem_cfg->num_lmacs = MAX_NUM_LMAC; + + for (i = 0; i < mem_cfg->num_lmacs; i++) { + int j; + + for (j = 0; j < mem_cfg->num_txfifo_entries; j++) + ADD_LEN(fifo_len, mem_cfg->lmac[i].txfifo_size[j], + hdr_len); + } + +dump_internal_txf: + if (!(iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_INTERNAL_TXF) && + fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG))) + goto out; + + for (i = 0; i < ARRAY_SIZE(mem_cfg->internal_txfifo_size); i++) + ADD_LEN(fifo_len, mem_cfg->internal_txfifo_size[i], hdr_len); + +out: + return fifo_len; +} + +static void iwl_dump_paging(struct iwl_fw_runtime *fwrt, + struct iwl_fw_error_dump_data **data) +{ + int i; + + IWL_DEBUG_INFO(fwrt, "WRT paging dump\n"); + for (i = 1; i < fwrt->num_of_paging_blk + 1; i++) { + struct iwl_fw_error_dump_paging *paging; + struct page *pages = + fwrt->fw_paging_db[i].fw_paging_block; + dma_addr_t addr = fwrt->fw_paging_db[i].fw_paging_phys; + + (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_PAGING); + (*data)->len = cpu_to_le32(sizeof(*paging) + + PAGING_BLOCK_SIZE); + paging = (void *)(*data)->data; + paging->index = cpu_to_le32(i); + dma_sync_single_for_cpu(fwrt->trans->dev, addr, + PAGING_BLOCK_SIZE, + DMA_BIDIRECTIONAL); + memcpy(paging->data, page_address(pages), + PAGING_BLOCK_SIZE); + dma_sync_single_for_device(fwrt->trans->dev, addr, + PAGING_BLOCK_SIZE, + DMA_BIDIRECTIONAL); + (*data) = iwl_fw_error_next_data(*data); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) + fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, + fwrt->fw_paging_db[i].fw_offs, + paging->data, + PAGING_BLOCK_SIZE); + } +} + +static struct iwl_fw_error_dump_file * +iwl_fw_error_dump_file(struct iwl_fw_runtime *fwrt, + struct iwl_fw_dump_ptrs *fw_error_dump, + struct iwl_fwrt_dump_data *data) +{ + 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_fw_error_dump_smem_cfg *dump_smem_cfg; + struct iwl_fw_error_dump_trigger_desc *dump_trig; + u32 sram_len, sram_ofs; + const struct iwl_fw_dbg_mem_seg_tlv *fw_mem = fwrt->fw->dbg.mem_tlv; + struct iwl_fwrt_shared_mem_cfg *mem_cfg = &fwrt->smem_cfg; + u32 file_len, fifo_len = 0, prph_len = 0, radio_len = 0; + u32 smem_len = fwrt->fw->dbg.n_mem_tlv ? 0 : fwrt->trans->cfg->smem_len; + u32 sram2_len = fwrt->fw->dbg.n_mem_tlv ? + 0 : fwrt->trans->cfg->dccm2_len; + int i; + + /* SRAM - include stack CCM if driver knows the values for it */ + if (!fwrt->trans->cfg->dccm_offset || !fwrt->trans->cfg->dccm_len) { + const struct fw_img *img; + + if (fwrt->cur_fw_img >= IWL_UCODE_TYPE_MAX) + return NULL; + img = &fwrt->fw->img[fwrt->cur_fw_img]; + sram_ofs = img->sec[IWL_UCODE_SECTION_DATA].offset; + sram_len = img->sec[IWL_UCODE_SECTION_DATA].len; + } else { + sram_ofs = fwrt->trans->cfg->dccm_offset; + sram_len = fwrt->trans->cfg->dccm_len; + } + + /* reading RXF/TXF sizes */ + if (test_bit(STATUS_FW_ERROR, &fwrt->trans->status)) { + fifo_len = iwl_fw_rxf_len(fwrt, mem_cfg); + fifo_len += iwl_fw_txf_len(fwrt, mem_cfg); + + /* Make room for PRPH registers */ + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_PRPH)) + iwl_fw_prph_handler(fwrt, &prph_len, + iwl_fw_get_prph_len); + + if (fwrt->trans->trans_cfg->device_family == + IWL_DEVICE_FAMILY_7000 && + iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_RADIO_REG)) + radio_len = sizeof(*dump_data) + RADIO_REG_MAX_READ; + } + + file_len = sizeof(*dump_file) + fifo_len + prph_len + radio_len; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_DEV_FW_INFO)) + file_len += sizeof(*dump_data) + sizeof(*dump_info); + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM_CFG)) + file_len += sizeof(*dump_data) + sizeof(*dump_smem_cfg); + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM)) { + size_t hdr_len = sizeof(*dump_data) + + sizeof(struct iwl_fw_error_dump_mem); + + /* Dump SRAM only if no mem_tlvs */ + if (!fwrt->fw->dbg.n_mem_tlv) + ADD_LEN(file_len, sram_len, hdr_len); + + /* Make room for all mem types that exist */ + ADD_LEN(file_len, smem_len, hdr_len); + ADD_LEN(file_len, sram2_len, hdr_len); + + for (i = 0; i < fwrt->fw->dbg.n_mem_tlv; i++) + ADD_LEN(file_len, le32_to_cpu(fw_mem[i].len), hdr_len); + } + + /* Make room for fw's virtual image pages, if it exists */ + if (iwl_fw_dbg_is_paging_enabled(fwrt)) + file_len += fwrt->num_of_paging_blk * + (sizeof(*dump_data) + + sizeof(struct iwl_fw_error_dump_paging) + + PAGING_BLOCK_SIZE); + + if (iwl_fw_dbg_is_d3_debug_enabled(fwrt) && fwrt->dump.d3_debug_data) { + file_len += sizeof(*dump_data) + + fwrt->trans->cfg->d3_debug_data_length * 2; + } + + /* If we only want a monitor dump, reset the file length */ + if (data->monitor_only) { + file_len = sizeof(*dump_file) + sizeof(*dump_data) * 2 + + sizeof(*dump_info) + sizeof(*dump_smem_cfg); + } + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_ERROR_INFO) && + data->desc) + file_len += sizeof(*dump_data) + sizeof(*dump_trig) + + data->desc->len; + + dump_file = vzalloc(file_len); + if (!dump_file) + return NULL; + + fw_error_dump->fwrt_ptr = dump_file; + + dump_file->barker = cpu_to_le32(IWL_FW_ERROR_DUMP_BARKER); + dump_data = (void *)dump_file->data; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_DEV_FW_INFO)) { + 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->hw_type = + cpu_to_le32(CSR_HW_REV_TYPE(fwrt->trans->hw_rev)); + dump_info->hw_step = + cpu_to_le32(fwrt->trans->hw_rev_step); + memcpy(dump_info->fw_human_readable, fwrt->fw->human_readable, + sizeof(dump_info->fw_human_readable)); + strncpy(dump_info->dev_human_readable, fwrt->trans->name, + sizeof(dump_info->dev_human_readable) - 1); + strncpy(dump_info->bus_human_readable, fwrt->dev->bus->name, + sizeof(dump_info->bus_human_readable) - 1); + dump_info->num_of_lmacs = fwrt->smem_cfg.num_lmacs; + dump_info->lmac_err_id[0] = + cpu_to_le32(fwrt->dump.lmac_err_id[0]); + if (fwrt->smem_cfg.num_lmacs > 1) + dump_info->lmac_err_id[1] = + cpu_to_le32(fwrt->dump.lmac_err_id[1]); + dump_info->umac_err_id = cpu_to_le32(fwrt->dump.umac_err_id); + + dump_data = iwl_fw_error_next_data(dump_data); + } + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM_CFG)) { + /* Dump shared memory configuration */ + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_MEM_CFG); + dump_data->len = cpu_to_le32(sizeof(*dump_smem_cfg)); + dump_smem_cfg = (void *)dump_data->data; + dump_smem_cfg->num_lmacs = cpu_to_le32(mem_cfg->num_lmacs); + dump_smem_cfg->num_txfifo_entries = + cpu_to_le32(mem_cfg->num_txfifo_entries); + for (i = 0; i < MAX_NUM_LMAC; i++) { + int j; + u32 *txf_size = mem_cfg->lmac[i].txfifo_size; + + for (j = 0; j < TX_FIFO_MAX_NUM; j++) + dump_smem_cfg->lmac[i].txfifo_size[j] = + cpu_to_le32(txf_size[j]); + dump_smem_cfg->lmac[i].rxfifo1_size = + cpu_to_le32(mem_cfg->lmac[i].rxfifo1_size); + } + dump_smem_cfg->rxfifo2_size = + cpu_to_le32(mem_cfg->rxfifo2_size); + dump_smem_cfg->internal_txfifo_addr = + cpu_to_le32(mem_cfg->internal_txfifo_addr); + for (i = 0; i < TX_FIFO_INTERNAL_MAX_NUM; i++) { + dump_smem_cfg->internal_txfifo_size[i] = + cpu_to_le32(mem_cfg->internal_txfifo_size[i]); + } + + dump_data = iwl_fw_error_next_data(dump_data); + } + + /* We only dump the FIFOs if the FW is in error state */ + if (fifo_len) { + iwl_fw_dump_rxf(fwrt, &dump_data); + iwl_fw_dump_txf(fwrt, &dump_data); + } + + if (radio_len) + iwl_read_radio_regs(fwrt, &dump_data); + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_ERROR_INFO) && + data->desc) { + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_ERROR_INFO); + dump_data->len = cpu_to_le32(sizeof(*dump_trig) + + data->desc->len); + dump_trig = (void *)dump_data->data; + memcpy(dump_trig, &data->desc->trig_desc, + sizeof(*dump_trig) + data->desc->len); + + dump_data = iwl_fw_error_next_data(dump_data); + } + + /* In case we only want monitor dump, skip to dump trasport data */ + if (data->monitor_only) + goto out; + + if (iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_MEM)) { + const struct iwl_fw_dbg_mem_seg_tlv *fw_dbg_mem = + fwrt->fw->dbg.mem_tlv; + + if (!fwrt->fw->dbg.n_mem_tlv) + iwl_fw_dump_mem(fwrt, &dump_data, sram_len, sram_ofs, + IWL_FW_ERROR_DUMP_MEM_SRAM); + + for (i = 0; i < fwrt->fw->dbg.n_mem_tlv; i++) { + u32 len = le32_to_cpu(fw_dbg_mem[i].len); + u32 ofs = le32_to_cpu(fw_dbg_mem[i].ofs); + + iwl_fw_dump_mem(fwrt, &dump_data, len, ofs, + le32_to_cpu(fw_dbg_mem[i].data_type)); + } + + iwl_fw_dump_mem(fwrt, &dump_data, smem_len, + fwrt->trans->cfg->smem_offset, + IWL_FW_ERROR_DUMP_MEM_SMEM); + + iwl_fw_dump_mem(fwrt, &dump_data, sram2_len, + fwrt->trans->cfg->dccm2_offset, + IWL_FW_ERROR_DUMP_MEM_SRAM); + } + + if (iwl_fw_dbg_is_d3_debug_enabled(fwrt) && fwrt->dump.d3_debug_data) { + u32 addr = fwrt->trans->cfg->d3_debug_data_base_addr; + size_t data_size = fwrt->trans->cfg->d3_debug_data_length; + + dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_D3_DEBUG_DATA); + dump_data->len = cpu_to_le32(data_size * 2); + + memcpy(dump_data->data, fwrt->dump.d3_debug_data, data_size); + + kfree(fwrt->dump.d3_debug_data); + fwrt->dump.d3_debug_data = NULL; + + iwl_trans_read_mem_bytes(fwrt->trans, addr, + dump_data->data + data_size, + data_size); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) + fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, addr, + dump_data->data + data_size, + data_size); + + dump_data = iwl_fw_error_next_data(dump_data); + } + + /* Dump fw's virtual image */ + if (iwl_fw_dbg_is_paging_enabled(fwrt)) + iwl_dump_paging(fwrt, &dump_data); + + if (prph_len) + iwl_fw_prph_handler(fwrt, &dump_data, iwl_dump_prph); + +out: + dump_file->file_len = cpu_to_le32(file_len); + return dump_file; +} + +/** + * struct iwl_dump_ini_region_data - region data + * @reg_tlv: region TLV + * @dump_data: dump data + */ +struct iwl_dump_ini_region_data { + struct iwl_ucode_tlv *reg_tlv; + struct iwl_fwrt_dump_data *dump_data; +}; + +static int +iwl_dump_ini_prph_mac_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, u32 range_len, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + __le32 *val = range->data; + u32 prph_val; + u32 addr = le32_to_cpu(reg->addrs[idx]) + + le32_to_cpu(reg->dev_addr.offset); + int i; + + range->internal_base_addr = cpu_to_le32(addr); + range->range_data_size = reg->dev_addr.size; + for (i = 0; i < le32_to_cpu(reg->dev_addr.size); i += 4) { + prph_val = iwl_read_prph(fwrt->trans, addr + i); + if ((prph_val & ~0xf) == 0xa5a5a5a0) + return -EBUSY; + *val++ = cpu_to_le32(prph_val); + } + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int +iwl_dump_ini_prph_phy_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, u32 range_len, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + __le32 *val = range->data; + u32 indirect_wr_addr = WMAL_INDRCT_RD_CMD1; + u32 indirect_rd_addr = WMAL_MRSPF_1; + u32 prph_val; + u32 addr = le32_to_cpu(reg->addrs[idx]); + u32 dphy_state; + u32 dphy_addr; + int i; + + range->internal_base_addr = cpu_to_le32(addr); + range->range_data_size = reg->dev_addr.size; + + if (fwrt->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) + indirect_wr_addr = WMAL_INDRCT_CMD1; + + indirect_wr_addr += le32_to_cpu(reg->dev_addr.offset); + indirect_rd_addr += le32_to_cpu(reg->dev_addr.offset); + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return -EBUSY; + + dphy_addr = (reg->dev_addr.offset) ? WFPM_LMAC2_PS_CTL_RW : + WFPM_LMAC1_PS_CTL_RW; + dphy_state = iwl_read_umac_prph_no_grab(fwrt->trans, dphy_addr); + + for (i = 0; i < le32_to_cpu(reg->dev_addr.size); i += 4) { + if (dphy_state == HBUS_TIMEOUT || + (dphy_state & WFPM_PS_CTL_RW_PHYRF_PD_FSM_CURSTATE_MSK) != + WFPM_PHYRF_STATE_ON) { + *val++ = cpu_to_le32(WFPM_DPHY_OFF); + continue; + } + + iwl_write_prph_no_grab(fwrt->trans, indirect_wr_addr, + WMAL_INDRCT_CMD(addr + i)); + prph_val = iwl_read_prph_no_grab(fwrt->trans, + indirect_rd_addr); + *val++ = cpu_to_le32(prph_val); + } + + iwl_trans_release_nic_access(fwrt->trans); + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int iwl_dump_ini_csr_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, u32 range_len, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + __le32 *val = range->data; + u32 addr = le32_to_cpu(reg->addrs[idx]) + + le32_to_cpu(reg->dev_addr.offset); + int i; + + range->internal_base_addr = cpu_to_le32(addr); + range->range_data_size = reg->dev_addr.size; + for (i = 0; i < le32_to_cpu(reg->dev_addr.size); i += 4) + *val++ = cpu_to_le32(iwl_trans_read32(fwrt->trans, addr + i)); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int iwl_dump_ini_config_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, u32 range_len, int idx) +{ + struct iwl_trans *trans = fwrt->trans; + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + __le32 *val = range->data; + u32 addr = le32_to_cpu(reg->addrs[idx]) + + le32_to_cpu(reg->dev_addr.offset); + int i; + + /* we shouldn't get here if the trans doesn't have read_config32 */ + if (WARN_ON_ONCE(!trans->ops->read_config32)) + return -EOPNOTSUPP; + + range->internal_base_addr = cpu_to_le32(addr); + range->range_data_size = reg->dev_addr.size; + for (i = 0; i < le32_to_cpu(reg->dev_addr.size); i += 4) { + int ret; + u32 tmp; + + ret = trans->ops->read_config32(trans, addr + i, &tmp); + if (ret < 0) + return ret; + + *val++ = cpu_to_le32(tmp); + } + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int iwl_dump_ini_dev_mem_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, u32 range_len, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + u32 addr = le32_to_cpu(reg->addrs[idx]) + + le32_to_cpu(reg->dev_addr.offset); + + range->internal_base_addr = cpu_to_le32(addr); + range->range_data_size = reg->dev_addr.size; + iwl_trans_read_mem_bytes(fwrt->trans, addr, range->data, + le32_to_cpu(reg->dev_addr.size)); + + if (reg->sub_type == IWL_FW_INI_REGION_DEVICE_MEMORY_SUBTYPE_HW_SMEM && + fwrt->sanitize_ops && fwrt->sanitize_ops->frob_txf) + fwrt->sanitize_ops->frob_txf(fwrt->sanitize_ctx, + range->data, + le32_to_cpu(reg->dev_addr.size)); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int _iwl_dump_ini_paging_iter(struct iwl_fw_runtime *fwrt, + void *range_ptr, u32 range_len, int idx) +{ + struct page *page = fwrt->fw_paging_db[idx].fw_paging_block; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + dma_addr_t addr = fwrt->fw_paging_db[idx].fw_paging_phys; + u32 page_size = fwrt->fw_paging_db[idx].fw_paging_size; + + range->page_num = cpu_to_le32(idx); + range->range_data_size = cpu_to_le32(page_size); + dma_sync_single_for_cpu(fwrt->trans->dev, addr, page_size, + DMA_BIDIRECTIONAL); + memcpy(range->data, page_address(page), page_size); + dma_sync_single_for_device(fwrt->trans->dev, addr, page_size, + DMA_BIDIRECTIONAL); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int iwl_dump_ini_paging_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, u32 range_len, int idx) +{ + struct iwl_fw_ini_error_dump_range *range; + u32 page_size; + + /* all paged index start from 1 to skip CSS section */ + idx++; + + if (!fwrt->trans->trans_cfg->gen2) + return _iwl_dump_ini_paging_iter(fwrt, range_ptr, range_len, idx); + + range = range_ptr; + page_size = fwrt->trans->init_dram.paging[idx].size; + + range->page_num = cpu_to_le32(idx); + range->range_data_size = cpu_to_le32(page_size); + memcpy(range->data, fwrt->trans->init_dram.paging[idx].block, + page_size); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int +iwl_dump_ini_mon_dram_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, u32 range_len, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + struct iwl_dram_data *frag; + u32 alloc_id = le32_to_cpu(reg->dram_alloc_id); + + frag = &fwrt->trans->dbg.fw_mon_ini[alloc_id].frags[idx]; + + range->dram_base_addr = cpu_to_le64(frag->physical); + range->range_data_size = cpu_to_le32(frag->size); + + memcpy(range->data, frag->block, frag->size); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int iwl_dump_ini_mon_smem_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, u32 range_len, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + u32 addr = le32_to_cpu(reg->internal_buffer.base_addr); + + range->internal_base_addr = cpu_to_le32(addr); + range->range_data_size = reg->internal_buffer.size; + iwl_trans_read_mem_bytes(fwrt->trans, addr, range->data, + le32_to_cpu(reg->internal_buffer.size)); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static bool iwl_ini_txf_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_txf_iter_data *iter = &fwrt->dump.txf_iter_data; + struct iwl_fwrt_shared_mem_cfg *cfg = &fwrt->smem_cfg; + int txf_num = cfg->num_txfifo_entries; + int int_txf_num = ARRAY_SIZE(cfg->internal_txfifo_size); + u32 lmac_bitmap = le32_to_cpu(reg->fifos.fid[0]); + + if (!idx) { + if (le32_to_cpu(reg->fifos.offset) && cfg->num_lmacs == 1) { + IWL_ERR(fwrt, "WRT: Invalid lmac offset 0x%x\n", + le32_to_cpu(reg->fifos.offset)); + return false; + } + + iter->internal_txf = 0; + iter->fifo_size = 0; + iter->fifo = -1; + if (le32_to_cpu(reg->fifos.offset)) + iter->lmac = 1; + else + iter->lmac = 0; + } + + if (!iter->internal_txf) { + for (iter->fifo++; iter->fifo < txf_num; iter->fifo++) { + iter->fifo_size = + cfg->lmac[iter->lmac].txfifo_size[iter->fifo]; + if (iter->fifo_size && (lmac_bitmap & BIT(iter->fifo))) + return true; + } + iter->fifo--; + } + + iter->internal_txf = 1; + + if (!fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG)) + return false; + + for (iter->fifo++; iter->fifo < int_txf_num + txf_num; iter->fifo++) { + iter->fifo_size = + cfg->internal_txfifo_size[iter->fifo - txf_num]; + if (iter->fifo_size && (lmac_bitmap & BIT(iter->fifo))) + return true; + } + + return false; +} + +static int iwl_dump_ini_txf_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, u32 range_len, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + struct iwl_txf_iter_data *iter = &fwrt->dump.txf_iter_data; + struct iwl_fw_ini_error_dump_register *reg_dump = (void *)range->data; + u32 offs = le32_to_cpu(reg->fifos.offset), addr; + u32 registers_num = iwl_tlv_array_len(reg_data->reg_tlv, reg, addrs); + u32 registers_size = registers_num * sizeof(*reg_dump); + __le32 *data; + int i; + + if (!iwl_ini_txf_iter(fwrt, reg_data, idx)) + return -EIO; + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return -EBUSY; + + range->fifo_hdr.fifo_num = cpu_to_le32(iter->fifo); + range->fifo_hdr.num_of_registers = cpu_to_le32(registers_num); + range->range_data_size = cpu_to_le32(iter->fifo_size + registers_size); + + iwl_write_prph_no_grab(fwrt->trans, TXF_LARC_NUM + offs, iter->fifo); + + /* + * read txf registers. for each register, write to the dump the + * register address and its value + */ + for (i = 0; i < registers_num; i++) { + addr = le32_to_cpu(reg->addrs[i]) + offs; + + reg_dump->addr = cpu_to_le32(addr); + reg_dump->data = cpu_to_le32(iwl_read_prph_no_grab(fwrt->trans, + addr)); + + reg_dump++; + } + + if (reg->fifos.hdr_only) { + range->range_data_size = cpu_to_le32(registers_size); + goto out; + } + + /* Set the TXF_READ_MODIFY_ADDR to TXF_WR_PTR */ + iwl_write_prph_no_grab(fwrt->trans, TXF_READ_MODIFY_ADDR + offs, + TXF_WR_PTR + offs); + + /* Dummy-read to advance the read pointer to the head */ + iwl_read_prph_no_grab(fwrt->trans, TXF_READ_MODIFY_DATA + offs); + + /* Read FIFO */ + addr = TXF_READ_MODIFY_DATA + offs; + data = (void *)reg_dump; + for (i = 0; i < iter->fifo_size; i += sizeof(*data)) + *data++ = cpu_to_le32(iwl_read_prph_no_grab(fwrt->trans, addr)); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_txf) + fwrt->sanitize_ops->frob_txf(fwrt->sanitize_ctx, + reg_dump, iter->fifo_size); + +out: + iwl_trans_release_nic_access(fwrt->trans); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +struct iwl_ini_rxf_data { + u32 fifo_num; + u32 size; + u32 offset; +}; + +static void iwl_ini_get_rxf_data(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + struct iwl_ini_rxf_data *data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + u32 fid1 = le32_to_cpu(reg->fifos.fid[0]); + u32 fid2 = le32_to_cpu(reg->fifos.fid[1]); + u8 fifo_idx; + + if (!data) + return; + + memset(data, 0, sizeof(*data)); + + /* make sure only one bit is set in only one fid */ + if (WARN_ONCE(hweight_long(fid1) + hweight_long(fid2) != 1, + "fid1=%x, fid2=%x\n", fid1, fid2)) + return; + + if (fid1) { + fifo_idx = ffs(fid1) - 1; + if (WARN_ONCE(fifo_idx >= MAX_NUM_LMAC, "fifo_idx=%d\n", + fifo_idx)) + return; + + data->size = fwrt->smem_cfg.lmac[fifo_idx].rxfifo1_size; + data->fifo_num = fifo_idx; + } else { + u8 max_idx; + + fifo_idx = ffs(fid2) - 1; + if (iwl_fw_lookup_notif_ver(fwrt->fw, SYSTEM_GROUP, + SHARED_MEM_CFG_CMD, 0) <= 3) + max_idx = 0; + else + max_idx = 1; + + if (WARN_ONCE(fifo_idx > max_idx, + "invalid umac fifo idx %d", fifo_idx)) + return; + + /* use bit 31 to distinguish between umac and lmac rxf while + * parsing the dump + */ + data->fifo_num = fifo_idx | IWL_RXF_UMAC_BIT; + + switch (fifo_idx) { + case 0: + data->size = fwrt->smem_cfg.rxfifo2_size; + data->offset = iwl_umac_prph(fwrt->trans, + RXF_DIFF_FROM_PREV); + break; + case 1: + data->size = fwrt->smem_cfg.rxfifo2_control_size; + data->offset = iwl_umac_prph(fwrt->trans, + RXF2C_DIFF_FROM_PREV); + break; + } + } +} + +static int iwl_dump_ini_rxf_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, u32 range_len, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + struct iwl_ini_rxf_data rxf_data; + struct iwl_fw_ini_error_dump_register *reg_dump = (void *)range->data; + u32 offs = le32_to_cpu(reg->fifos.offset), addr; + u32 registers_num = iwl_tlv_array_len(reg_data->reg_tlv, reg, addrs); + u32 registers_size = registers_num * sizeof(*reg_dump); + __le32 *data; + int i; + + iwl_ini_get_rxf_data(fwrt, reg_data, &rxf_data); + if (!rxf_data.size) + return -EIO; + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return -EBUSY; + + range->fifo_hdr.fifo_num = cpu_to_le32(rxf_data.fifo_num); + range->fifo_hdr.num_of_registers = cpu_to_le32(registers_num); + range->range_data_size = cpu_to_le32(rxf_data.size + registers_size); + + /* + * read rxf registers. for each register, write to the dump the + * register address and its value + */ + for (i = 0; i < registers_num; i++) { + addr = le32_to_cpu(reg->addrs[i]) + offs; + + reg_dump->addr = cpu_to_le32(addr); + reg_dump->data = cpu_to_le32(iwl_read_prph_no_grab(fwrt->trans, + addr)); + + reg_dump++; + } + + if (reg->fifos.hdr_only) { + range->range_data_size = cpu_to_le32(registers_size); + goto out; + } + + offs = rxf_data.offset; + + /* Lock fence */ + iwl_write_prph_no_grab(fwrt->trans, RXF_SET_FENCE_MODE + offs, 0x1); + /* Set fence pointer to the same place like WR pointer */ + iwl_write_prph_no_grab(fwrt->trans, RXF_LD_WR2FENCE + offs, 0x1); + /* Set fence offset */ + iwl_write_prph_no_grab(fwrt->trans, RXF_LD_FENCE_OFFSET_ADDR + offs, + 0x0); + + /* Read FIFO */ + addr = RXF_FIFO_RD_FENCE_INC + offs; + data = (void *)reg_dump; + for (i = 0; i < rxf_data.size; i += sizeof(*data)) + *data++ = cpu_to_le32(iwl_read_prph_no_grab(fwrt->trans, addr)); + +out: + iwl_trans_release_nic_access(fwrt->trans); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int +iwl_dump_ini_err_table_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, u32 range_len, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_region_err_table *err_table = ®->err_table; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + u32 addr = le32_to_cpu(err_table->base_addr) + + le32_to_cpu(err_table->offset); + + range->internal_base_addr = cpu_to_le32(addr); + range->range_data_size = err_table->size; + iwl_trans_read_mem_bytes(fwrt->trans, addr, range->data, + le32_to_cpu(err_table->size)); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int +iwl_dump_ini_special_mem_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, u32 range_len, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_region_special_device_memory *special_mem = + ®->special_mem; + + struct iwl_fw_ini_error_dump_range *range = range_ptr; + u32 addr = le32_to_cpu(special_mem->base_addr) + + le32_to_cpu(special_mem->offset); + + range->internal_base_addr = cpu_to_le32(addr); + range->range_data_size = special_mem->size; + iwl_trans_read_mem_bytes(fwrt->trans, addr, range->data, + le32_to_cpu(special_mem->size)); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int +iwl_dump_ini_dbgi_sram_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, u32 range_len, int idx) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_error_dump_range *range = range_ptr; + __le32 *val = range->data; + u32 prph_data; + int i; + + if (!iwl_trans_grab_nic_access(fwrt->trans)) + return -EBUSY; + + range->range_data_size = reg->dev_addr.size; + for (i = 0; i < (le32_to_cpu(reg->dev_addr.size) / 4); i++) { + prph_data = iwl_read_prph_no_grab(fwrt->trans, (i % 2) ? + DBGI_SRAM_TARGET_ACCESS_RDATA_MSB : + DBGI_SRAM_TARGET_ACCESS_RDATA_LSB); + if ((prph_data & ~0xf) == 0xa5a5a5a0) { + iwl_trans_release_nic_access(fwrt->trans); + return -EBUSY; + } + *val++ = cpu_to_le32(prph_data); + } + iwl_trans_release_nic_access(fwrt->trans); + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int iwl_dump_ini_fw_pkt_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, u32 range_len, int idx) +{ + struct iwl_fw_ini_error_dump_range *range = range_ptr; + struct iwl_rx_packet *pkt = reg_data->dump_data->fw_pkt; + u32 pkt_len; + + if (!pkt) + return -EIO; + + pkt_len = iwl_rx_packet_payload_len(pkt); + + memcpy(&range->fw_pkt_hdr, &pkt->hdr, sizeof(range->fw_pkt_hdr)); + range->range_data_size = cpu_to_le32(pkt_len); + + memcpy(range->data, pkt->data, pkt_len); + + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static int iwl_dump_ini_imr_iter(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range_ptr, u32 range_len, int idx) +{ + /* read the IMR memory and DMA it to SRAM */ + struct iwl_fw_ini_error_dump_range *range = range_ptr; + u64 imr_curr_addr = fwrt->trans->dbg.imr_data.imr_curr_addr; + u32 imr_rem_bytes = fwrt->trans->dbg.imr_data.imr2sram_remainbyte; + u32 sram_addr = fwrt->trans->dbg.imr_data.sram_addr; + u32 sram_size = fwrt->trans->dbg.imr_data.sram_size; + u32 size_to_dump = (imr_rem_bytes > sram_size) ? sram_size : imr_rem_bytes; + + range->range_data_size = cpu_to_le32(size_to_dump); + if (iwl_trans_write_imr_mem(fwrt->trans, sram_addr, + imr_curr_addr, size_to_dump)) { + IWL_ERR(fwrt, "WRT_DEBUG: IMR Memory transfer failed\n"); + return -1; + } + + fwrt->trans->dbg.imr_data.imr_curr_addr = imr_curr_addr + size_to_dump; + fwrt->trans->dbg.imr_data.imr2sram_remainbyte -= size_to_dump; + + iwl_trans_read_mem_bytes(fwrt->trans, sram_addr, range->data, + size_to_dump); + return sizeof(*range) + le32_to_cpu(range->range_data_size); +} + +static void * +iwl_dump_ini_mem_fill_header(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *data, u32 data_len) +{ + struct iwl_fw_ini_error_dump *dump = data; + + dump->header.version = cpu_to_le32(IWL_INI_DUMP_VER); + + return dump->data; +} + +/** + * mask_apply_and_normalize - applies mask on val and normalize the result + * + * The normalization is based on the first set bit in the mask + * + * @val: value + * @mask: mask to apply and to normalize with + */ +static u32 mask_apply_and_normalize(u32 val, u32 mask) +{ + return (val & mask) >> (ffs(mask) - 1); +} + +static __le32 iwl_get_mon_reg(struct iwl_fw_runtime *fwrt, u32 alloc_id, + const struct iwl_fw_mon_reg *reg_info) +{ + u32 val, offs; + + /* The header addresses of DBGCi is calculate as follows: + * DBGC1 address + (0x100 * i) + */ + offs = (alloc_id - IWL_FW_INI_ALLOCATION_ID_DBGC1) * 0x100; + + if (!reg_info || !reg_info->addr || !reg_info->mask) + return 0; + + val = iwl_read_prph_no_grab(fwrt->trans, reg_info->addr + offs); + + return cpu_to_le32(mask_apply_and_normalize(val, reg_info->mask)); +} + +static void * +iwl_dump_ini_mon_fill_header(struct iwl_fw_runtime *fwrt, u32 alloc_id, + struct iwl_fw_ini_monitor_dump *data, + const struct iwl_fw_mon_regs *addrs) +{ + if (!iwl_trans_grab_nic_access(fwrt->trans)) { + IWL_ERR(fwrt, "Failed to get monitor header\n"); + return NULL; + } + + data->write_ptr = iwl_get_mon_reg(fwrt, alloc_id, + &addrs->write_ptr); + if (fwrt->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + u32 wrt_ptr = le32_to_cpu(data->write_ptr); + + data->write_ptr = cpu_to_le32(wrt_ptr >> 2); + } + data->cycle_cnt = iwl_get_mon_reg(fwrt, alloc_id, + &addrs->cycle_cnt); + data->cur_frag = iwl_get_mon_reg(fwrt, alloc_id, + &addrs->cur_frag); + + iwl_trans_release_nic_access(fwrt->trans); + + data->header.version = cpu_to_le32(IWL_INI_DUMP_VER); + + return data->data; +} + +static void * +iwl_dump_ini_mon_dram_fill_header(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *data, u32 data_len) +{ + struct iwl_fw_ini_monitor_dump *mon_dump = (void *)data; + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + u32 alloc_id = le32_to_cpu(reg->dram_alloc_id); + + return iwl_dump_ini_mon_fill_header(fwrt, alloc_id, mon_dump, + &fwrt->trans->cfg->mon_dram_regs); +} + +static void * +iwl_dump_ini_mon_smem_fill_header(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *data, u32 data_len) +{ + struct iwl_fw_ini_monitor_dump *mon_dump = (void *)data; + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + u32 alloc_id = le32_to_cpu(reg->internal_buffer.alloc_id); + + return iwl_dump_ini_mon_fill_header(fwrt, alloc_id, mon_dump, + &fwrt->trans->cfg->mon_smem_regs); +} + +static void * +iwl_dump_ini_mon_dbgi_fill_header(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *data, u32 data_len) +{ + struct iwl_fw_ini_monitor_dump *mon_dump = (void *)data; + + return iwl_dump_ini_mon_fill_header(fwrt, + /* no offset calculation later */ + IWL_FW_INI_ALLOCATION_ID_DBGC1, + mon_dump, + &fwrt->trans->cfg->mon_dbgi_regs); +} + +static void * +iwl_dump_ini_err_table_fill_header(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *data, u32 data_len) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_err_table_dump *dump = data; + + dump->header.version = cpu_to_le32(IWL_INI_DUMP_VER); + dump->version = reg->err_table.version; + + return dump->data; +} + +static void * +iwl_dump_ini_special_mem_fill_header(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *data, u32 data_len) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_special_device_memory *dump = data; + + dump->header.version = cpu_to_le32(IWL_INI_DUMP_VER); + dump->type = reg->special_mem.type; + dump->version = reg->special_mem.version; + + return dump->data; +} + +static void * +iwl_dump_ini_imr_fill_header(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *data, u32 data_len) +{ + struct iwl_fw_ini_error_dump *dump = data; + + dump->header.version = cpu_to_le32(IWL_INI_DUMP_VER); + + return dump->data; +} + +static u32 iwl_dump_ini_mem_ranges(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + + return iwl_tlv_array_len(reg_data->reg_tlv, reg, addrs); +} + +static u32 iwl_dump_ini_paging_ranges(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + if (fwrt->trans->trans_cfg->gen2) { + if (fwrt->trans->init_dram.paging_cnt) + return fwrt->trans->init_dram.paging_cnt - 1; + else + return 0; + } + + return fwrt->num_of_paging_blk; +} + +static u32 +iwl_dump_ini_mon_dram_ranges(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_mon *fw_mon; + u32 ranges = 0, alloc_id = le32_to_cpu(reg->dram_alloc_id); + int i; + + fw_mon = &fwrt->trans->dbg.fw_mon_ini[alloc_id]; + + for (i = 0; i < fw_mon->num_frags; i++) { + if (!fw_mon->frags[i].size) + break; + + ranges++; + } + + return ranges; +} + +static u32 iwl_dump_ini_txf_ranges(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + u32 num_of_fifos = 0; + + while (iwl_ini_txf_iter(fwrt, reg_data, num_of_fifos)) + num_of_fifos++; + + return num_of_fifos; +} + +static u32 iwl_dump_ini_single_range(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + return 1; +} + +static u32 iwl_dump_ini_imr_ranges(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + /* range is total number of pages need to copied from + *IMR memory to SRAM and later from SRAM to DRAM + */ + u32 imr_enable = fwrt->trans->dbg.imr_data.imr_enable; + u32 imr_size = fwrt->trans->dbg.imr_data.imr_size; + u32 sram_size = fwrt->trans->dbg.imr_data.sram_size; + + if (imr_enable == 0 || imr_size == 0 || sram_size == 0) { + IWL_DEBUG_INFO(fwrt, + "WRT: Invalid imr data enable: %d, imr_size: %d, sram_size: %d\n", + imr_enable, imr_size, sram_size); + return 0; + } + + return((imr_size % sram_size) ? (imr_size / sram_size + 1) : (imr_size / sram_size)); +} + +static u32 iwl_dump_ini_mem_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + u32 size = le32_to_cpu(reg->dev_addr.size); + u32 ranges = iwl_dump_ini_mem_ranges(fwrt, reg_data); + + if (!size || !ranges) + return 0; + + return sizeof(struct iwl_fw_ini_error_dump) + ranges * + (size + sizeof(struct iwl_fw_ini_error_dump_range)); +} + +static u32 +iwl_dump_ini_paging_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + int i; + u32 range_header_len = sizeof(struct iwl_fw_ini_error_dump_range); + u32 size = sizeof(struct iwl_fw_ini_error_dump); + + /* start from 1 to skip CSS section */ + for (i = 1; i <= iwl_dump_ini_paging_ranges(fwrt, reg_data); i++) { + size += range_header_len; + if (fwrt->trans->trans_cfg->gen2) + size += fwrt->trans->init_dram.paging[i].size; + else + size += fwrt->fw_paging_db[i].fw_paging_size; + } + + return size; +} + +static u32 +iwl_dump_ini_mon_dram_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_mon *fw_mon; + u32 size = 0, alloc_id = le32_to_cpu(reg->dram_alloc_id); + int i; + + fw_mon = &fwrt->trans->dbg.fw_mon_ini[alloc_id]; + + for (i = 0; i < fw_mon->num_frags; i++) { + struct iwl_dram_data *frag = &fw_mon->frags[i]; + + if (!frag->size) + break; + + size += sizeof(struct iwl_fw_ini_error_dump_range) + frag->size; + } + + if (size) + size += sizeof(struct iwl_fw_ini_monitor_dump); + + return size; +} + +static u32 +iwl_dump_ini_mon_smem_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + u32 size; + + size = le32_to_cpu(reg->internal_buffer.size); + if (!size) + return 0; + + size += sizeof(struct iwl_fw_ini_monitor_dump) + + sizeof(struct iwl_fw_ini_error_dump_range); + + return size; +} + +static u32 iwl_dump_ini_mon_dbgi_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + u32 size = le32_to_cpu(reg->dev_addr.size); + u32 ranges = iwl_dump_ini_mem_ranges(fwrt, reg_data); + + if (!size || !ranges) + return 0; + + return sizeof(struct iwl_fw_ini_monitor_dump) + ranges * + (size + sizeof(struct iwl_fw_ini_error_dump_range)); +} + +static u32 iwl_dump_ini_txf_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_txf_iter_data *iter = &fwrt->dump.txf_iter_data; + u32 registers_num = iwl_tlv_array_len(reg_data->reg_tlv, reg, addrs); + u32 size = 0; + u32 fifo_hdr = sizeof(struct iwl_fw_ini_error_dump_range) + + registers_num * + sizeof(struct iwl_fw_ini_error_dump_register); + + while (iwl_ini_txf_iter(fwrt, reg_data, size)) { + size += fifo_hdr; + if (!reg->fifos.hdr_only) + size += iter->fifo_size; + } + + if (!size) + return 0; + + return size + sizeof(struct iwl_fw_ini_error_dump); +} + +static u32 iwl_dump_ini_rxf_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_ini_rxf_data rx_data; + u32 registers_num = iwl_tlv_array_len(reg_data->reg_tlv, reg, addrs); + u32 size = sizeof(struct iwl_fw_ini_error_dump) + + sizeof(struct iwl_fw_ini_error_dump_range) + + registers_num * sizeof(struct iwl_fw_ini_error_dump_register); + + if (reg->fifos.hdr_only) + return size; + + iwl_ini_get_rxf_data(fwrt, reg_data, &rx_data); + size += rx_data.size; + + return size; +} + +static u32 +iwl_dump_ini_err_table_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + u32 size = le32_to_cpu(reg->err_table.size); + + if (size) + size += sizeof(struct iwl_fw_ini_err_table_dump) + + sizeof(struct iwl_fw_ini_error_dump_range); + + return size; +} + +static u32 +iwl_dump_ini_special_mem_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + u32 size = le32_to_cpu(reg->special_mem.size); + + if (size) + size += sizeof(struct iwl_fw_ini_special_device_memory) + + sizeof(struct iwl_fw_ini_error_dump_range); + + return size; +} + +static u32 +iwl_dump_ini_fw_pkt_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + u32 size = 0; + + if (!reg_data->dump_data->fw_pkt) + return 0; + + size += iwl_rx_packet_payload_len(reg_data->dump_data->fw_pkt); + if (size) + size += sizeof(struct iwl_fw_ini_error_dump) + + sizeof(struct iwl_fw_ini_error_dump_range); + + return size; +} + +static u32 +iwl_dump_ini_imr_get_size(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data) +{ + u32 size = 0; + u32 ranges = 0; + u32 imr_enable = fwrt->trans->dbg.imr_data.imr_enable; + u32 imr_size = fwrt->trans->dbg.imr_data.imr_size; + u32 sram_size = fwrt->trans->dbg.imr_data.sram_size; + + if (imr_enable == 0 || imr_size == 0 || sram_size == 0) { + IWL_DEBUG_INFO(fwrt, + "WRT: Invalid imr data enable: %d, imr_size: %d, sram_size: %d\n", + imr_enable, imr_size, sram_size); + return size; + } + size = imr_size; + ranges = iwl_dump_ini_imr_ranges(fwrt, reg_data); + if (!size && !ranges) { + IWL_ERR(fwrt, "WRT: imr_size :=%d, ranges :=%d\n", size, ranges); + return 0; + } + size += sizeof(struct iwl_fw_ini_error_dump) + + ranges * sizeof(struct iwl_fw_ini_error_dump_range); + return size; +} + +/** + * struct iwl_dump_ini_mem_ops - ini memory dump operations + * @get_num_of_ranges: returns the number of memory ranges in the region. + * @get_size: returns the total size of the region. + * @fill_mem_hdr: fills region type specific headers and returns pointer to + * the first range or NULL if failed to fill headers. + * @fill_range: copies a given memory range into the dump. + * Returns the size of the range or negative error value otherwise. + */ +struct iwl_dump_ini_mem_ops { + u32 (*get_num_of_ranges)(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data); + u32 (*get_size)(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data); + void *(*fill_mem_hdr)(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *data, u32 data_len); + int (*fill_range)(struct iwl_fw_runtime *fwrt, + struct iwl_dump_ini_region_data *reg_data, + void *range, u32 range_len, int idx); +}; + +/** + * iwl_dump_ini_mem + * + * Creates a dump tlv and copy a memory region into it. + * Returns the size of the current dump tlv or 0 if failed + * + * @fwrt: fw runtime struct + * @list: list to add the dump tlv to + * @reg_data: memory region + * @ops: memory dump operations + */ +static u32 iwl_dump_ini_mem(struct iwl_fw_runtime *fwrt, struct list_head *list, + struct iwl_dump_ini_region_data *reg_data, + const struct iwl_dump_ini_mem_ops *ops) +{ + struct iwl_fw_ini_region_tlv *reg = (void *)reg_data->reg_tlv->data; + struct iwl_fw_ini_dump_entry *entry; + struct iwl_fw_ini_error_dump_data *tlv; + struct iwl_fw_ini_error_dump_header *header; + u32 type = reg->type; + u32 id = le32_get_bits(reg->id, IWL_FW_INI_REGION_ID_MASK); + u32 num_of_ranges, i, size; + u8 *range; + u32 free_size; + u64 header_size; + u32 dump_policy = IWL_FW_INI_DUMP_VERBOSE; + + IWL_DEBUG_FW(fwrt, "WRT: Collecting region: dump type=%d, id=%d, type=%d\n", + dump_policy, id, type); + + if (le32_to_cpu(reg->hdr.version) >= 2) { + u32 dp = le32_get_bits(reg->id, + IWL_FW_INI_REGION_DUMP_POLICY_MASK); + + if (dump_policy == IWL_FW_INI_DUMP_VERBOSE && + !(dp & IWL_FW_INI_DEBUG_DUMP_POLICY_NO_LIMIT)) { + IWL_DEBUG_FW(fwrt, + "WRT: no dump - type %d and policy mismatch=%d\n", + dump_policy, dp); + return 0; + } else if (dump_policy == IWL_FW_INI_DUMP_MEDIUM && + !(dp & IWL_FW_IWL_DEBUG_DUMP_POLICY_MAX_LIMIT_5MB)) { + IWL_DEBUG_FW(fwrt, + "WRT: no dump - type %d and policy mismatch=%d\n", + dump_policy, dp); + return 0; + } else if (dump_policy == IWL_FW_INI_DUMP_BRIEF && + !(dp & IWL_FW_INI_DEBUG_DUMP_POLICY_MAX_LIMIT_600KB)) { + IWL_DEBUG_FW(fwrt, + "WRT: no dump - type %d and policy mismatch=%d\n", + dump_policy, dp); + return 0; + } + } + + if (!ops->get_num_of_ranges || !ops->get_size || !ops->fill_mem_hdr || + !ops->fill_range) { + IWL_DEBUG_FW(fwrt, "WRT: no ops for collecting data\n"); + return 0; + } + + size = ops->get_size(fwrt, reg_data); + + if (size < sizeof(*header)) { + IWL_DEBUG_FW(fwrt, "WRT: size didn't include space for header\n"); + return 0; + } + + entry = vzalloc(sizeof(*entry) + sizeof(*tlv) + size); + if (!entry) + return 0; + + entry->size = sizeof(*tlv) + size; + + tlv = (void *)entry->data; + tlv->type = reg->type; + tlv->sub_type = reg->sub_type; + tlv->sub_type_ver = reg->sub_type_ver; + tlv->reserved = reg->reserved; + tlv->len = cpu_to_le32(size); + + num_of_ranges = ops->get_num_of_ranges(fwrt, reg_data); + + header = (void *)tlv->data; + header->region_id = cpu_to_le32(id); + header->num_of_ranges = cpu_to_le32(num_of_ranges); + header->name_len = cpu_to_le32(IWL_FW_INI_MAX_NAME); + memcpy(header->name, reg->name, IWL_FW_INI_MAX_NAME); + + free_size = size; + range = ops->fill_mem_hdr(fwrt, reg_data, header, free_size); + if (!range) { + IWL_ERR(fwrt, + "WRT: Failed to fill region header: id=%d, type=%d\n", + id, type); + goto out_err; + } + + header_size = range - (u8 *)header; + + if (WARN(header_size > free_size, + "header size %llu > free_size %d", + header_size, free_size)) { + IWL_ERR(fwrt, + "WRT: fill_mem_hdr used more than given free_size\n"); + goto out_err; + } + + free_size -= header_size; + + for (i = 0; i < num_of_ranges; i++) { + int range_size = ops->fill_range(fwrt, reg_data, range, + free_size, i); + + if (range_size < 0) { + IWL_ERR(fwrt, + "WRT: Failed to dump region: id=%d, type=%d\n", + id, type); + goto out_err; + } + + if (WARN(range_size > free_size, "range_size %d > free_size %d", + range_size, free_size)) { + IWL_ERR(fwrt, + "WRT: fill_raged used more than given free_size\n"); + goto out_err; + } + + free_size -= range_size; + range = range + range_size; + } + + list_add_tail(&entry->list, list); + + return entry->size; + +out_err: + vfree(entry); + + return 0; +} + +static u32 iwl_dump_ini_info(struct iwl_fw_runtime *fwrt, + struct iwl_fw_ini_trigger_tlv *trigger, + struct list_head *list) +{ + struct iwl_fw_ini_dump_entry *entry; + struct iwl_fw_error_dump_data *tlv; + struct iwl_fw_ini_dump_info *dump; + struct iwl_dbg_tlv_node *node; + struct iwl_fw_ini_dump_cfg_name *cfg_name; + u32 size = sizeof(*tlv) + sizeof(*dump); + u32 num_of_cfg_names = 0; + u32 hw_type; + + list_for_each_entry(node, &fwrt->trans->dbg.debug_info_tlv_list, list) { + size += sizeof(*cfg_name); + num_of_cfg_names++; + } + + entry = vzalloc(sizeof(*entry) + size); + if (!entry) + return 0; + + entry->size = size; + + tlv = (void *)entry->data; + tlv->type = cpu_to_le32(IWL_INI_DUMP_INFO_TYPE); + tlv->len = cpu_to_le32(size - sizeof(*tlv)); + + dump = (void *)tlv->data; + + dump->version = cpu_to_le32(IWL_INI_DUMP_VER); + dump->time_point = trigger->time_point; + dump->trigger_reason = trigger->trigger_reason; + dump->external_cfg_state = + cpu_to_le32(fwrt->trans->dbg.external_ini_cfg); + + dump->ver_type = cpu_to_le32(fwrt->dump.fw_ver.type); + dump->ver_subtype = cpu_to_le32(fwrt->dump.fw_ver.subtype); + + dump->hw_step = cpu_to_le32(fwrt->trans->hw_rev_step); + + /* + * Several HWs all have type == 0x42, so we'll override this value + * according to the detected HW + */ + hw_type = CSR_HW_REV_TYPE(fwrt->trans->hw_rev); + if (hw_type == IWL_AX210_HW_TYPE) { + u32 prph_val = iwl_read_umac_prph(fwrt->trans, WFPM_OTP_CFG1_ADDR); + u32 is_jacket = !!(prph_val & WFPM_OTP_CFG1_IS_JACKET_BIT); + u32 is_cdb = !!(prph_val & WFPM_OTP_CFG1_IS_CDB_BIT); + u32 masked_bits = is_jacket | (is_cdb << 1); + + /* + * The HW type depends on certain bits in this case, so add + * these bits to the HW type. We won't have collisions since we + * add these bits after the highest possible bit in the mask. + */ + hw_type |= masked_bits << IWL_AX210_HW_TYPE_ADDITION_SHIFT; + } + dump->hw_type = cpu_to_le32(hw_type); + + dump->rf_id_flavor = + cpu_to_le32(CSR_HW_RFID_FLAVOR(fwrt->trans->hw_rf_id)); + dump->rf_id_dash = cpu_to_le32(CSR_HW_RFID_DASH(fwrt->trans->hw_rf_id)); + dump->rf_id_step = cpu_to_le32(CSR_HW_RFID_STEP(fwrt->trans->hw_rf_id)); + dump->rf_id_type = cpu_to_le32(CSR_HW_RFID_TYPE(fwrt->trans->hw_rf_id)); + + dump->lmac_major = cpu_to_le32(fwrt->dump.fw_ver.lmac_major); + dump->lmac_minor = cpu_to_le32(fwrt->dump.fw_ver.lmac_minor); + dump->umac_major = cpu_to_le32(fwrt->dump.fw_ver.umac_major); + dump->umac_minor = cpu_to_le32(fwrt->dump.fw_ver.umac_minor); + + dump->fw_mon_mode = cpu_to_le32(fwrt->trans->dbg.ini_dest); + dump->regions_mask = trigger->regions_mask & + ~cpu_to_le64(fwrt->trans->dbg.unsupported_region_msk); + + dump->build_tag_len = cpu_to_le32(sizeof(dump->build_tag)); + memcpy(dump->build_tag, fwrt->fw->human_readable, + sizeof(dump->build_tag)); + + cfg_name = dump->cfg_names; + dump->num_of_cfg_names = cpu_to_le32(num_of_cfg_names); + list_for_each_entry(node, &fwrt->trans->dbg.debug_info_tlv_list, list) { + struct iwl_fw_ini_debug_info_tlv *debug_info = + (void *)node->tlv.data; + + cfg_name->image_type = debug_info->image_type; + cfg_name->cfg_name_len = + cpu_to_le32(IWL_FW_INI_MAX_CFG_NAME); + memcpy(cfg_name->cfg_name, debug_info->debug_cfg_name, + sizeof(cfg_name->cfg_name)); + cfg_name++; + } + + /* add dump info TLV to the beginning of the list since it needs to be + * the first TLV in the dump + */ + list_add(&entry->list, list); + + return entry->size; +} + +static const struct iwl_dump_ini_mem_ops iwl_dump_ini_region_ops[] = { + [IWL_FW_INI_REGION_INVALID] = {}, + [IWL_FW_INI_REGION_INTERNAL_BUFFER] = { + .get_num_of_ranges = iwl_dump_ini_single_range, + .get_size = iwl_dump_ini_mon_smem_get_size, + .fill_mem_hdr = iwl_dump_ini_mon_smem_fill_header, + .fill_range = iwl_dump_ini_mon_smem_iter, + }, + [IWL_FW_INI_REGION_DRAM_BUFFER] = { + .get_num_of_ranges = iwl_dump_ini_mon_dram_ranges, + .get_size = iwl_dump_ini_mon_dram_get_size, + .fill_mem_hdr = iwl_dump_ini_mon_dram_fill_header, + .fill_range = iwl_dump_ini_mon_dram_iter, + }, + [IWL_FW_INI_REGION_TXF] = { + .get_num_of_ranges = iwl_dump_ini_txf_ranges, + .get_size = iwl_dump_ini_txf_get_size, + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .fill_range = iwl_dump_ini_txf_iter, + }, + [IWL_FW_INI_REGION_RXF] = { + .get_num_of_ranges = iwl_dump_ini_single_range, + .get_size = iwl_dump_ini_rxf_get_size, + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .fill_range = iwl_dump_ini_rxf_iter, + }, + [IWL_FW_INI_REGION_LMAC_ERROR_TABLE] = { + .get_num_of_ranges = iwl_dump_ini_single_range, + .get_size = iwl_dump_ini_err_table_get_size, + .fill_mem_hdr = iwl_dump_ini_err_table_fill_header, + .fill_range = iwl_dump_ini_err_table_iter, + }, + [IWL_FW_INI_REGION_UMAC_ERROR_TABLE] = { + .get_num_of_ranges = iwl_dump_ini_single_range, + .get_size = iwl_dump_ini_err_table_get_size, + .fill_mem_hdr = iwl_dump_ini_err_table_fill_header, + .fill_range = iwl_dump_ini_err_table_iter, + }, + [IWL_FW_INI_REGION_RSP_OR_NOTIF] = { + .get_num_of_ranges = iwl_dump_ini_single_range, + .get_size = iwl_dump_ini_fw_pkt_get_size, + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .fill_range = iwl_dump_ini_fw_pkt_iter, + }, + [IWL_FW_INI_REGION_DEVICE_MEMORY] = { + .get_num_of_ranges = iwl_dump_ini_mem_ranges, + .get_size = iwl_dump_ini_mem_get_size, + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .fill_range = iwl_dump_ini_dev_mem_iter, + }, + [IWL_FW_INI_REGION_PERIPHERY_MAC] = { + .get_num_of_ranges = iwl_dump_ini_mem_ranges, + .get_size = iwl_dump_ini_mem_get_size, + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .fill_range = iwl_dump_ini_prph_mac_iter, + }, + [IWL_FW_INI_REGION_PERIPHERY_PHY] = { + .get_num_of_ranges = iwl_dump_ini_mem_ranges, + .get_size = iwl_dump_ini_mem_get_size, + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .fill_range = iwl_dump_ini_prph_phy_iter, + }, + [IWL_FW_INI_REGION_PERIPHERY_AUX] = {}, + [IWL_FW_INI_REGION_PAGING] = { + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .get_num_of_ranges = iwl_dump_ini_paging_ranges, + .get_size = iwl_dump_ini_paging_get_size, + .fill_range = iwl_dump_ini_paging_iter, + }, + [IWL_FW_INI_REGION_CSR] = { + .get_num_of_ranges = iwl_dump_ini_mem_ranges, + .get_size = iwl_dump_ini_mem_get_size, + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .fill_range = iwl_dump_ini_csr_iter, + }, + [IWL_FW_INI_REGION_DRAM_IMR] = { + .get_num_of_ranges = iwl_dump_ini_imr_ranges, + .get_size = iwl_dump_ini_imr_get_size, + .fill_mem_hdr = iwl_dump_ini_imr_fill_header, + .fill_range = iwl_dump_ini_imr_iter, + }, + [IWL_FW_INI_REGION_PCI_IOSF_CONFIG] = { + .get_num_of_ranges = iwl_dump_ini_mem_ranges, + .get_size = iwl_dump_ini_mem_get_size, + .fill_mem_hdr = iwl_dump_ini_mem_fill_header, + .fill_range = iwl_dump_ini_config_iter, + }, + [IWL_FW_INI_REGION_SPECIAL_DEVICE_MEMORY] = { + .get_num_of_ranges = iwl_dump_ini_single_range, + .get_size = iwl_dump_ini_special_mem_get_size, + .fill_mem_hdr = iwl_dump_ini_special_mem_fill_header, + .fill_range = iwl_dump_ini_special_mem_iter, + }, + [IWL_FW_INI_REGION_DBGI_SRAM] = { + .get_num_of_ranges = iwl_dump_ini_mem_ranges, + .get_size = iwl_dump_ini_mon_dbgi_get_size, + .fill_mem_hdr = iwl_dump_ini_mon_dbgi_fill_header, + .fill_range = iwl_dump_ini_dbgi_sram_iter, + }, +}; + +static u32 iwl_dump_ini_trigger(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data, + struct list_head *list) +{ + struct iwl_fw_ini_trigger_tlv *trigger = dump_data->trig; + enum iwl_fw_ini_time_point tp_id = le32_to_cpu(trigger->time_point); + struct iwl_dump_ini_region_data reg_data = { + .dump_data = dump_data, + }; + struct iwl_dump_ini_region_data imr_reg_data = { + .dump_data = dump_data, + }; + int i; + u32 size = 0; + u64 regions_mask = le64_to_cpu(trigger->regions_mask) & + ~(fwrt->trans->dbg.unsupported_region_msk); + + BUILD_BUG_ON(sizeof(trigger->regions_mask) != sizeof(regions_mask)); + BUILD_BUG_ON((sizeof(trigger->regions_mask) * BITS_PER_BYTE) < + ARRAY_SIZE(fwrt->trans->dbg.active_regions)); + + for (i = 0; i < ARRAY_SIZE(fwrt->trans->dbg.active_regions); i++) { + u32 reg_type; + struct iwl_fw_ini_region_tlv *reg; + + if (!(BIT_ULL(i) & regions_mask)) + continue; + + reg_data.reg_tlv = fwrt->trans->dbg.active_regions[i]; + if (!reg_data.reg_tlv) { + IWL_WARN(fwrt, + "WRT: Unassigned region id %d, skipping\n", i); + continue; + } + + reg = (void *)reg_data.reg_tlv->data; + reg_type = reg->type; + if (reg_type >= ARRAY_SIZE(iwl_dump_ini_region_ops)) + continue; + + if (reg_type == IWL_FW_INI_REGION_PERIPHERY_PHY && + tp_id != IWL_FW_INI_TIME_POINT_FW_ASSERT) { + IWL_WARN(fwrt, + "WRT: trying to collect phy prph at time point: %d, skipping\n", + tp_id); + continue; + } + /* + * DRAM_IMR can be collected only for FW/HW error timepoint + * when fw is not alive. In addition, it must be collected + * lastly as it overwrites SRAM that can possibly contain + * debug data which also need to be collected. + */ + if (reg_type == IWL_FW_INI_REGION_DRAM_IMR) { + if (tp_id == IWL_FW_INI_TIME_POINT_FW_ASSERT || + tp_id == IWL_FW_INI_TIME_POINT_FW_HW_ERROR) + imr_reg_data.reg_tlv = fwrt->trans->dbg.active_regions[i]; + else + IWL_INFO(fwrt, + "WRT: trying to collect DRAM_IMR at time point: %d, skipping\n", + tp_id); + /* continue to next region */ + continue; + } + + + size += iwl_dump_ini_mem(fwrt, list, ®_data, + &iwl_dump_ini_region_ops[reg_type]); + } + /* collect DRAM_IMR region in the last */ + if (imr_reg_data.reg_tlv) + size += iwl_dump_ini_mem(fwrt, list, ®_data, + &iwl_dump_ini_region_ops[IWL_FW_INI_REGION_DRAM_IMR]); + + if (size) + size += iwl_dump_ini_info(fwrt, trigger, list); + + return size; +} + +static bool iwl_fw_ini_trigger_on(struct iwl_fw_runtime *fwrt, + struct iwl_fw_ini_trigger_tlv *trig) +{ + enum iwl_fw_ini_time_point tp_id = le32_to_cpu(trig->time_point); + u32 usec = le32_to_cpu(trig->ignore_consec); + + if (!iwl_trans_dbg_ini_valid(fwrt->trans) || + tp_id == IWL_FW_INI_TIME_POINT_INVALID || + tp_id >= IWL_FW_INI_TIME_POINT_NUM || + iwl_fw_dbg_no_trig_window(fwrt, tp_id, usec)) + return false; + + return true; +} + +static u32 iwl_dump_ini_file_gen(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data, + struct list_head *list) +{ + struct iwl_fw_ini_trigger_tlv *trigger = dump_data->trig; + struct iwl_fw_ini_dump_entry *entry; + struct iwl_fw_ini_dump_file_hdr *hdr; + u32 size; + + if (!trigger || !iwl_fw_ini_trigger_on(fwrt, trigger) || + !le64_to_cpu(trigger->regions_mask)) + return 0; + + entry = vzalloc(sizeof(*entry) + sizeof(*hdr)); + if (!entry) + return 0; + + entry->size = sizeof(*hdr); + + size = iwl_dump_ini_trigger(fwrt, dump_data, list); + if (!size) { + vfree(entry); + return 0; + } + + hdr = (void *)entry->data; + hdr->barker = cpu_to_le32(IWL_FW_INI_ERROR_DUMP_BARKER); + hdr->file_len = cpu_to_le32(size + entry->size); + + list_add(&entry->list, list); + + return le32_to_cpu(hdr->file_len); +} + +static inline void iwl_fw_free_dump_desc(struct iwl_fw_runtime *fwrt, + const struct iwl_fw_dump_desc *desc) +{ + if (desc && desc != &iwl_dump_desc_assert) + kfree(desc); + + fwrt->dump.lmac_err_id[0] = 0; + if (fwrt->smem_cfg.num_lmacs > 1) + fwrt->dump.lmac_err_id[1] = 0; + fwrt->dump.umac_err_id = 0; +} + +static void iwl_fw_error_dump(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data) +{ + struct iwl_fw_dump_ptrs fw_error_dump = {}; + struct iwl_fw_error_dump_file *dump_file; + struct scatterlist *sg_dump_data; + u32 file_len; + u32 dump_mask = fwrt->fw->dbg.dump_mask; + + dump_file = iwl_fw_error_dump_file(fwrt, &fw_error_dump, dump_data); + if (!dump_file) + return; + + if (dump_data->monitor_only) + dump_mask &= BIT(IWL_FW_ERROR_DUMP_FW_MONITOR); + + fw_error_dump.trans_ptr = iwl_trans_dump_data(fwrt->trans, dump_mask, + fwrt->sanitize_ops, + fwrt->sanitize_ctx); + file_len = le32_to_cpu(dump_file->file_len); + fw_error_dump.fwrt_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); + } + + sg_dump_data = alloc_sgtable(file_len); + if (sg_dump_data) { + sg_pcopy_from_buffer(sg_dump_data, + sg_nents(sg_dump_data), + fw_error_dump.fwrt_ptr, + fw_error_dump.fwrt_len, 0); + if (fw_error_dump.trans_ptr) + sg_pcopy_from_buffer(sg_dump_data, + sg_nents(sg_dump_data), + fw_error_dump.trans_ptr->data, + fw_error_dump.trans_ptr->len, + fw_error_dump.fwrt_len); + dev_coredumpsg(fwrt->trans->dev, sg_dump_data, file_len, + GFP_KERNEL); + } + vfree(fw_error_dump.fwrt_ptr); + vfree(fw_error_dump.trans_ptr); +} + +static void iwl_dump_ini_list_free(struct list_head *list) +{ + while (!list_empty(list)) { + struct iwl_fw_ini_dump_entry *entry = + list_entry(list->next, typeof(*entry), list); + + list_del(&entry->list); + vfree(entry); + } +} + +static void iwl_fw_error_dump_data_free(struct iwl_fwrt_dump_data *dump_data) +{ + dump_data->trig = NULL; + kfree(dump_data->fw_pkt); + dump_data->fw_pkt = NULL; +} + +static void iwl_fw_error_ini_dump(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data) +{ + LIST_HEAD(dump_list); + struct scatterlist *sg_dump_data; + u32 file_len = iwl_dump_ini_file_gen(fwrt, dump_data, &dump_list); + + if (!file_len) + return; + + sg_dump_data = alloc_sgtable(file_len); + if (sg_dump_data) { + struct iwl_fw_ini_dump_entry *entry; + int sg_entries = sg_nents(sg_dump_data); + u32 offs = 0; + + list_for_each_entry(entry, &dump_list, list) { + sg_pcopy_from_buffer(sg_dump_data, sg_entries, + entry->data, entry->size, offs); + offs += entry->size; + } + dev_coredumpsg(fwrt->trans->dev, sg_dump_data, file_len, + GFP_KERNEL); + } + iwl_dump_ini_list_free(&dump_list); +} + +const struct iwl_fw_dump_desc iwl_dump_desc_assert = { + .trig_desc = { + .type = cpu_to_le32(FW_DBG_TRIGGER_FW_ASSERT), + }, +}; +IWL_EXPORT_SYMBOL(iwl_dump_desc_assert); + +int iwl_fw_dbg_collect_desc(struct iwl_fw_runtime *fwrt, + const struct iwl_fw_dump_desc *desc, + bool monitor_only, + unsigned int delay) +{ + struct iwl_fwrt_wk_data *wk_data; + unsigned long idx; + + if (iwl_trans_dbg_ini_valid(fwrt->trans)) { + iwl_fw_free_dump_desc(fwrt, desc); + return 0; + } + + /* + * Check there is an available worker. + * ffz return value is undefined if no zero exists, + * so check against ~0UL first. + */ + if (fwrt->dump.active_wks == ~0UL) + return -EBUSY; + + idx = ffz(fwrt->dump.active_wks); + + if (idx >= IWL_FW_RUNTIME_DUMP_WK_NUM || + test_and_set_bit(fwrt->dump.wks[idx].idx, &fwrt->dump.active_wks)) + return -EBUSY; + + wk_data = &fwrt->dump.wks[idx]; + + if (WARN_ON(wk_data->dump_data.desc)) + iwl_fw_free_dump_desc(fwrt, wk_data->dump_data.desc); + + wk_data->dump_data.desc = desc; + wk_data->dump_data.monitor_only = monitor_only; + + IWL_WARN(fwrt, "Collecting data: trigger %d fired.\n", + le32_to_cpu(desc->trig_desc.type)); + + schedule_delayed_work(&wk_data->wk, usecs_to_jiffies(delay)); + + return 0; +} +IWL_EXPORT_SYMBOL(iwl_fw_dbg_collect_desc); + +int iwl_fw_dbg_error_collect(struct iwl_fw_runtime *fwrt, + enum iwl_fw_dbg_trigger trig_type) +{ + if (!test_bit(STATUS_DEVICE_ENABLED, &fwrt->trans->status)) + return -EIO; + + if (iwl_trans_dbg_ini_valid(fwrt->trans)) { + if (trig_type != FW_DBG_TRIGGER_ALIVE_TIMEOUT && + trig_type != FW_DBG_TRIGGER_DRIVER) + return -EIO; + + iwl_dbg_tlv_time_point(fwrt, + IWL_FW_INI_TIME_POINT_HOST_ALIVE_TIMEOUT, + NULL); + } else { + struct iwl_fw_dump_desc *iwl_dump_error_desc; + int ret; + + iwl_dump_error_desc = + kmalloc(sizeof(*iwl_dump_error_desc), GFP_KERNEL); + + if (!iwl_dump_error_desc) + return -ENOMEM; + + iwl_dump_error_desc->trig_desc.type = cpu_to_le32(trig_type); + iwl_dump_error_desc->len = 0; + + ret = iwl_fw_dbg_collect_desc(fwrt, iwl_dump_error_desc, + false, 0); + if (ret) { + kfree(iwl_dump_error_desc); + return ret; + } + } + + iwl_trans_sync_nmi(fwrt->trans); + + return 0; +} +IWL_EXPORT_SYMBOL(iwl_fw_dbg_error_collect); + +int iwl_fw_dbg_collect(struct iwl_fw_runtime *fwrt, + enum iwl_fw_dbg_trigger trig, + const char *str, size_t len, + struct iwl_fw_dbg_trigger_tlv *trigger) +{ + struct iwl_fw_dump_desc *desc; + unsigned int delay = 0; + bool monitor_only = false; + + if (trigger) { + u16 occurrences = le16_to_cpu(trigger->occurrences) - 1; + + if (!le16_to_cpu(trigger->occurrences)) + return 0; + + if (trigger->flags & IWL_FW_DBG_FORCE_RESTART) { + IWL_WARN(fwrt, "Force restart: trigger %d fired.\n", + trig); + iwl_force_nmi(fwrt->trans); + return 0; + } + + trigger->occurrences = cpu_to_le16(occurrences); + monitor_only = trigger->mode & IWL_FW_DBG_TRIGGER_MONITOR_ONLY; + + /* convert msec to usec */ + delay = le32_to_cpu(trigger->stop_delay) * USEC_PER_MSEC; + } + + desc = kzalloc(struct_size(desc, trig_desc.data, len), GFP_ATOMIC); + if (!desc) + return -ENOMEM; + + + desc->len = len; + desc->trig_desc.type = cpu_to_le32(trig); + memcpy(desc->trig_desc.data, str, len); + + return iwl_fw_dbg_collect_desc(fwrt, desc, monitor_only, delay); +} +IWL_EXPORT_SYMBOL(iwl_fw_dbg_collect); + +int iwl_fw_dbg_collect_trig(struct iwl_fw_runtime *fwrt, + struct iwl_fw_dbg_trigger_tlv *trigger, + const char *fmt, ...) +{ + int ret, len = 0; + char buf[64]; + + if (iwl_trans_dbg_ini_valid(fwrt->trans)) + return 0; + + if (fmt) { + va_list ap; + + buf[sizeof(buf) - 1] = '\0'; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + /* check for truncation */ + if (WARN_ON_ONCE(buf[sizeof(buf) - 1])) + buf[sizeof(buf) - 1] = '\0'; + + len = strlen(buf) + 1; + } + + ret = iwl_fw_dbg_collect(fwrt, le32_to_cpu(trigger->id), buf, len, + trigger); + + if (ret) + return ret; + + return 0; +} +IWL_EXPORT_SYMBOL(iwl_fw_dbg_collect_trig); + +int iwl_fw_start_dbg_conf(struct iwl_fw_runtime *fwrt, u8 conf_id) +{ + u8 *ptr; + int ret; + int i; + + if (WARN_ONCE(conf_id >= ARRAY_SIZE(fwrt->fw->dbg.conf_tlv), + "Invalid configuration %d\n", conf_id)) + return -EINVAL; + + /* EARLY START - firmware's configuration is hard coded */ + if ((!fwrt->fw->dbg.conf_tlv[conf_id] || + !fwrt->fw->dbg.conf_tlv[conf_id]->num_of_hcmds) && + conf_id == FW_DBG_START_FROM_ALIVE) + return 0; + + if (!fwrt->fw->dbg.conf_tlv[conf_id]) + return -EINVAL; + + if (fwrt->dump.conf != FW_DBG_INVALID) + IWL_INFO(fwrt, "FW already configured (%d) - re-configuring\n", + fwrt->dump.conf); + + /* Send all HCMDs for configuring the FW debug */ + ptr = (void *)&fwrt->fw->dbg.conf_tlv[conf_id]->hcmd; + for (i = 0; i < fwrt->fw->dbg.conf_tlv[conf_id]->num_of_hcmds; i++) { + struct iwl_fw_dbg_conf_hcmd *cmd = (void *)ptr; + struct iwl_host_cmd hcmd = { + .id = cmd->id, + .len = { le16_to_cpu(cmd->len), }, + .data = { cmd->data, }, + }; + + ret = iwl_trans_send_cmd(fwrt->trans, &hcmd); + if (ret) + return ret; + + ptr += sizeof(*cmd); + ptr += le16_to_cpu(cmd->len); + } + + fwrt->dump.conf = conf_id; + + return 0; +} +IWL_EXPORT_SYMBOL(iwl_fw_start_dbg_conf); + +void iwl_send_dbg_dump_complete_cmd(struct iwl_fw_runtime *fwrt, + u32 timepoint, + u32 timepoint_data) +{ + struct iwl_dbg_dump_complete_cmd hcmd_data; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(DEBUG_GROUP, FW_DUMP_COMPLETE_CMD), + .data[0] = &hcmd_data, + .len[0] = sizeof(hcmd_data), + }; + + if (test_bit(STATUS_FW_ERROR, &fwrt->trans->status)) + return; + + if (fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DUMP_COMPLETE_SUPPORT)) { + hcmd_data.tp = cpu_to_le32(timepoint); + hcmd_data.tp_data = cpu_to_le32(timepoint_data); + iwl_trans_send_cmd(fwrt->trans, &hcmd); + } +} + +/* this function assumes dump_start was called beforehand and dump_end will be + * called afterwards + */ +static void iwl_fw_dbg_collect_sync(struct iwl_fw_runtime *fwrt, u8 wk_idx) +{ + struct iwl_fw_dbg_params params = {0}; + struct iwl_fwrt_dump_data *dump_data = + &fwrt->dump.wks[wk_idx].dump_data; + u32 policy; + u32 time_point; + if (!test_bit(wk_idx, &fwrt->dump.active_wks)) + return; + + if (!dump_data->trig) { + IWL_ERR(fwrt, "dump trigger data is not set\n"); + goto out; + } + + if (!test_bit(STATUS_DEVICE_ENABLED, &fwrt->trans->status)) { + IWL_ERR(fwrt, "Device is not enabled - cannot dump error\n"); + goto out; + } + + /* there's no point in fw dump if the bus is dead */ + if (test_bit(STATUS_TRANS_DEAD, &fwrt->trans->status)) { + IWL_ERR(fwrt, "Skip fw error dump since bus is dead\n"); + goto out; + } + + iwl_fw_dbg_stop_restart_recording(fwrt, ¶ms, true); + + IWL_DEBUG_FW_INFO(fwrt, "WRT: Data collection start\n"); + if (iwl_trans_dbg_ini_valid(fwrt->trans)) + iwl_fw_error_ini_dump(fwrt, &fwrt->dump.wks[wk_idx].dump_data); + else + iwl_fw_error_dump(fwrt, &fwrt->dump.wks[wk_idx].dump_data); + IWL_DEBUG_FW_INFO(fwrt, "WRT: Data collection done\n"); + + iwl_fw_dbg_stop_restart_recording(fwrt, ¶ms, false); + + policy = le32_to_cpu(dump_data->trig->apply_policy); + time_point = le32_to_cpu(dump_data->trig->time_point); + + if (policy & IWL_FW_INI_APPLY_POLICY_DUMP_COMPLETE_CMD) { + IWL_DEBUG_FW_INFO(fwrt, "WRT: sending dump complete\n"); + iwl_send_dbg_dump_complete_cmd(fwrt, time_point, 0); + } + if (fwrt->trans->dbg.last_tp_resetfw == IWL_FW_INI_RESET_FW_MODE_STOP_FW_ONLY) + iwl_force_nmi(fwrt->trans); + +out: + if (iwl_trans_dbg_ini_valid(fwrt->trans)) { + iwl_fw_error_dump_data_free(dump_data); + } else { + iwl_fw_free_dump_desc(fwrt, dump_data->desc); + dump_data->desc = NULL; + } + + clear_bit(wk_idx, &fwrt->dump.active_wks); +} + +int iwl_fw_dbg_ini_collect(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data, + bool sync) +{ + struct iwl_fw_ini_trigger_tlv *trig = dump_data->trig; + enum iwl_fw_ini_time_point tp_id = le32_to_cpu(trig->time_point); + u32 occur, delay; + unsigned long idx; + + if (!iwl_fw_ini_trigger_on(fwrt, trig)) { + IWL_WARN(fwrt, "WRT: Trigger %d is not active, aborting dump\n", + tp_id); + return -EINVAL; + } + + delay = le32_to_cpu(trig->dump_delay); + occur = le32_to_cpu(trig->occurrences); + if (!occur) + return 0; + + trig->occurrences = cpu_to_le32(--occur); + + /* Check there is an available worker. + * ffz return value is undefined if no zero exists, + * so check against ~0UL first. + */ + if (fwrt->dump.active_wks == ~0UL) + return -EBUSY; + + idx = ffz(fwrt->dump.active_wks); + + if (idx >= IWL_FW_RUNTIME_DUMP_WK_NUM || + test_and_set_bit(fwrt->dump.wks[idx].idx, &fwrt->dump.active_wks)) + return -EBUSY; + + fwrt->dump.wks[idx].dump_data = *dump_data; + + if (sync) + delay = 0; + + IWL_WARN(fwrt, + "WRT: Collecting data: ini trigger %d fired (delay=%dms).\n", + tp_id, (u32)(delay / USEC_PER_MSEC)); + + if (sync) + iwl_fw_dbg_collect_sync(fwrt, idx); + else + schedule_delayed_work(&fwrt->dump.wks[idx].wk, usecs_to_jiffies(delay)); + + return 0; +} + +void iwl_fw_error_dump_wk(struct work_struct *work) +{ + struct iwl_fwrt_wk_data *wks = + container_of(work, typeof(*wks), wk.work); + struct iwl_fw_runtime *fwrt = + container_of(wks, typeof(*fwrt), dump.wks[wks->idx]); + + /* assumes the op mode mutex is locked in dump_start since + * iwl_fw_dbg_collect_sync can't run in parallel + */ + if (fwrt->ops && fwrt->ops->dump_start) + fwrt->ops->dump_start(fwrt->ops_ctx); + + iwl_fw_dbg_collect_sync(fwrt, wks->idx); + + if (fwrt->ops && fwrt->ops->dump_end) + fwrt->ops->dump_end(fwrt->ops_ctx); +} + +void iwl_fw_dbg_read_d3_debug_data(struct iwl_fw_runtime *fwrt) +{ + const struct iwl_cfg *cfg = fwrt->trans->cfg; + + if (!iwl_fw_dbg_is_d3_debug_enabled(fwrt)) + return; + + if (!fwrt->dump.d3_debug_data) { + fwrt->dump.d3_debug_data = kmalloc(cfg->d3_debug_data_length, + GFP_KERNEL); + if (!fwrt->dump.d3_debug_data) { + IWL_ERR(fwrt, + "failed to allocate memory for D3 debug data\n"); + return; + } + } + + /* if the buffer holds previous debug data it is overwritten */ + iwl_trans_read_mem_bytes(fwrt->trans, cfg->d3_debug_data_base_addr, + fwrt->dump.d3_debug_data, + cfg->d3_debug_data_length); + + if (fwrt->sanitize_ops && fwrt->sanitize_ops->frob_mem) + fwrt->sanitize_ops->frob_mem(fwrt->sanitize_ctx, + cfg->d3_debug_data_base_addr, + fwrt->dump.d3_debug_data, + cfg->d3_debug_data_length); +} +IWL_EXPORT_SYMBOL(iwl_fw_dbg_read_d3_debug_data); + +void iwl_fw_dbg_stop_sync(struct iwl_fw_runtime *fwrt) +{ + int i; + + iwl_dbg_tlv_del_timers(fwrt->trans); + for (i = 0; i < IWL_FW_RUNTIME_DUMP_WK_NUM; i++) + iwl_fw_dbg_collect_sync(fwrt, i); + + iwl_fw_dbg_stop_restart_recording(fwrt, NULL, true); +} +IWL_EXPORT_SYMBOL(iwl_fw_dbg_stop_sync); + +static int iwl_fw_dbg_suspend_resume_hcmd(struct iwl_trans *trans, bool suspend) +{ + struct iwl_dbg_suspend_resume_cmd cmd = { + .operation = suspend ? + cpu_to_le32(DBGC_SUSPEND_CMD) : + cpu_to_le32(DBGC_RESUME_CMD), + }; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(DEBUG_GROUP, DBGC_SUSPEND_RESUME), + .data[0] = &cmd, + .len[0] = sizeof(cmd), + }; + + return iwl_trans_send_cmd(trans, &hcmd); +} + +static void iwl_fw_dbg_stop_recording(struct iwl_trans *trans, + struct iwl_fw_dbg_params *params) +{ + if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_7000) { + iwl_set_bits_prph(trans, MON_BUFF_SAMPLE_CTL, 0x100); + return; + } + + if (params) { + params->in_sample = iwl_read_umac_prph(trans, DBGC_IN_SAMPLE); + params->out_ctrl = iwl_read_umac_prph(trans, DBGC_OUT_CTRL); + } + + iwl_write_umac_prph(trans, DBGC_IN_SAMPLE, 0); + /* wait for the DBGC to finish writing the internal buffer to DRAM to + * avoid halting the HW while writing + */ + usleep_range(700, 1000); + iwl_write_umac_prph(trans, DBGC_OUT_CTRL, 0); +} + +static int iwl_fw_dbg_restart_recording(struct iwl_trans *trans, + struct iwl_fw_dbg_params *params) +{ + if (!params) + return -EIO; + + if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_7000) { + iwl_clear_bits_prph(trans, MON_BUFF_SAMPLE_CTL, 0x100); + iwl_clear_bits_prph(trans, MON_BUFF_SAMPLE_CTL, 0x1); + iwl_set_bits_prph(trans, MON_BUFF_SAMPLE_CTL, 0x1); + } else { + iwl_write_umac_prph(trans, DBGC_IN_SAMPLE, params->in_sample); + iwl_write_umac_prph(trans, DBGC_OUT_CTRL, params->out_ctrl); + } + + return 0; +} + +void iwl_fw_dbg_stop_restart_recording(struct iwl_fw_runtime *fwrt, + struct iwl_fw_dbg_params *params, + bool stop) +{ + int ret __maybe_unused = 0; + + if (test_bit(STATUS_FW_ERROR, &fwrt->trans->status)) + return; + + if (fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DBG_SUSPEND_RESUME_CMD_SUPP)) + ret = iwl_fw_dbg_suspend_resume_hcmd(fwrt->trans, stop); + else if (stop) + iwl_fw_dbg_stop_recording(fwrt->trans, params); + else + ret = iwl_fw_dbg_restart_recording(fwrt->trans, params); +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (!ret) { + if (stop) + fwrt->trans->dbg.rec_on = false; + else + iwl_fw_set_dbg_rec_on(fwrt); + } +#endif +} +IWL_EXPORT_SYMBOL(iwl_fw_dbg_stop_restart_recording); diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dbg.h b/drivers/net/wireless/intel/iwlwifi/fw/dbg.h new file mode 100644 index 000000000..be7806407 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/dbg.h @@ -0,0 +1,330 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2019, 2021-2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_dbg_h__ +#define __iwl_fw_dbg_h__ +#include <linux/workqueue.h> +#include <net/cfg80211.h> +#include "runtime.h" +#include "iwl-prph.h" +#include "iwl-io.h" +#include "file.h" +#include "error-dump.h" +#include "api/commands.h" +#include "api/dbg-tlv.h" +#include "api/alive.h" + +/** + * struct iwl_fw_dump_desc - describes the dump + * @len: length of trig_desc->data + * @trig_desc: the description of the dump + */ +struct iwl_fw_dump_desc { + size_t len; + /* must be last */ + struct iwl_fw_error_dump_trigger_desc trig_desc; +}; + +/** + * struct iwl_fw_dbg_params - register values to restore + * @in_sample: DBGC_IN_SAMPLE value + * @out_ctrl: DBGC_OUT_CTRL value + */ +struct iwl_fw_dbg_params { + u32 in_sample; + u32 out_ctrl; +}; + +extern const struct iwl_fw_dump_desc iwl_dump_desc_assert; + +int iwl_fw_dbg_collect_desc(struct iwl_fw_runtime *fwrt, + const struct iwl_fw_dump_desc *desc, + bool monitor_only, unsigned int delay); +int iwl_fw_dbg_error_collect(struct iwl_fw_runtime *fwrt, + enum iwl_fw_dbg_trigger trig_type); +int iwl_fw_dbg_ini_collect(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data, + bool sync); +int iwl_fw_dbg_collect(struct iwl_fw_runtime *fwrt, + enum iwl_fw_dbg_trigger trig, const char *str, + size_t len, struct iwl_fw_dbg_trigger_tlv *trigger); +int iwl_fw_dbg_collect_trig(struct iwl_fw_runtime *fwrt, + struct iwl_fw_dbg_trigger_tlv *trigger, + const char *fmt, ...) __printf(3, 4); +int iwl_fw_start_dbg_conf(struct iwl_fw_runtime *fwrt, u8 id); + +#define iwl_fw_dbg_trigger_enabled(fw, id) ({ \ + void *__dbg_trigger = (fw)->dbg.trigger_tlv[(id)]; \ + unlikely(__dbg_trigger); \ +}) + +static inline struct iwl_fw_dbg_trigger_tlv* +_iwl_fw_dbg_get_trigger(const struct iwl_fw *fw, enum iwl_fw_dbg_trigger id) +{ + return fw->dbg.trigger_tlv[id]; +} + +#define iwl_fw_dbg_get_trigger(fw, id) ({ \ + BUILD_BUG_ON(!__builtin_constant_p(id)); \ + BUILD_BUG_ON((id) >= FW_DBG_TRIGGER_MAX); \ + _iwl_fw_dbg_get_trigger((fw), (id)); \ +}) + +static inline bool +iwl_fw_dbg_trigger_vif_match(struct iwl_fw_dbg_trigger_tlv *trig, + struct wireless_dev *wdev) +{ + u32 trig_vif = le32_to_cpu(trig->vif_type); + + return trig_vif == IWL_FW_DBG_CONF_VIF_ANY || + wdev->iftype == trig_vif; +} + +static inline bool +iwl_fw_dbg_trigger_stop_conf_match(struct iwl_fw_runtime *fwrt, + struct iwl_fw_dbg_trigger_tlv *trig) +{ + return ((trig->mode & IWL_FW_DBG_TRIGGER_STOP) && + (fwrt->dump.conf == FW_DBG_INVALID || + (BIT(fwrt->dump.conf) & le32_to_cpu(trig->stop_conf_ids)))); +} + +static inline bool +iwl_fw_dbg_no_trig_window(struct iwl_fw_runtime *fwrt, u32 id, u32 dis_usec) +{ + unsigned long wind_jiff = usecs_to_jiffies(dis_usec); + + /* If this is the first event checked, jump to update start ts */ + if (fwrt->dump.non_collect_ts_start[id] && + (time_after(fwrt->dump.non_collect_ts_start[id] + wind_jiff, + jiffies))) + return true; + + fwrt->dump.non_collect_ts_start[id] = jiffies; + return false; +} + +static inline bool +iwl_fw_dbg_trigger_check_stop(struct iwl_fw_runtime *fwrt, + struct wireless_dev *wdev, + struct iwl_fw_dbg_trigger_tlv *trig) +{ + u32 usec = le16_to_cpu(trig->trig_dis_ms) * USEC_PER_MSEC; + + if (wdev && !iwl_fw_dbg_trigger_vif_match(trig, wdev)) + return false; + + if (iwl_fw_dbg_no_trig_window(fwrt, le32_to_cpu(trig->id), usec)) { + IWL_WARN(fwrt, "Trigger %d occurred while no-collect window.\n", + trig->id); + return false; + } + + return iwl_fw_dbg_trigger_stop_conf_match(fwrt, trig); +} + +static inline struct iwl_fw_dbg_trigger_tlv* +_iwl_fw_dbg_trigger_on(struct iwl_fw_runtime *fwrt, + struct wireless_dev *wdev, + const enum iwl_fw_dbg_trigger id) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + + if (iwl_trans_dbg_ini_valid(fwrt->trans)) + return NULL; + + if (!iwl_fw_dbg_trigger_enabled(fwrt->fw, id)) + return NULL; + + trig = _iwl_fw_dbg_get_trigger(fwrt->fw, id); + + if (!iwl_fw_dbg_trigger_check_stop(fwrt, wdev, trig)) + return NULL; + + return trig; +} + +#define iwl_fw_dbg_trigger_on(fwrt, wdev, id) ({ \ + BUILD_BUG_ON(!__builtin_constant_p(id)); \ + BUILD_BUG_ON((id) >= FW_DBG_TRIGGER_MAX); \ + _iwl_fw_dbg_trigger_on((fwrt), (wdev), (id)); \ +}) + +static inline void +_iwl_fw_dbg_trigger_simple_stop(struct iwl_fw_runtime *fwrt, + struct wireless_dev *wdev, + struct iwl_fw_dbg_trigger_tlv *trigger) +{ + if (!trigger) + return; + + if (!iwl_fw_dbg_trigger_check_stop(fwrt, wdev, trigger)) + return; + + iwl_fw_dbg_collect_trig(fwrt, trigger, NULL); +} + +#define iwl_fw_dbg_trigger_simple_stop(fwrt, wdev, trig) \ + _iwl_fw_dbg_trigger_simple_stop((fwrt), (wdev), \ + iwl_fw_dbg_get_trigger((fwrt)->fw,\ + (trig))) +void iwl_fw_dbg_stop_restart_recording(struct iwl_fw_runtime *fwrt, + struct iwl_fw_dbg_params *params, + bool stop); + +#ifdef CONFIG_IWLWIFI_DEBUGFS +static inline void iwl_fw_set_dbg_rec_on(struct iwl_fw_runtime *fwrt) +{ + if (fwrt->cur_fw_img == IWL_UCODE_REGULAR && + (fwrt->fw->dbg.dest_tlv || + fwrt->trans->dbg.ini_dest != IWL_FW_INI_LOCATION_INVALID)) + fwrt->trans->dbg.rec_on = true; +} +#endif + +static inline void iwl_fw_dump_conf_clear(struct iwl_fw_runtime *fwrt) +{ + fwrt->dump.conf = FW_DBG_INVALID; +} + +void iwl_fw_error_dump_wk(struct work_struct *work); + +static inline bool iwl_fw_dbg_type_on(struct iwl_fw_runtime *fwrt, u32 type) +{ + return (fwrt->fw->dbg.dump_mask & BIT(type)); +} + +static inline bool iwl_fw_dbg_is_d3_debug_enabled(struct iwl_fw_runtime *fwrt) +{ + return fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_D3_DEBUG) && + fwrt->trans->cfg->d3_debug_data_length && fwrt->ops && + fwrt->ops->d3_debug_enable && + fwrt->ops->d3_debug_enable(fwrt->ops_ctx) && + iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_D3_DEBUG_DATA); +} + +static inline bool iwl_fw_dbg_is_paging_enabled(struct iwl_fw_runtime *fwrt) +{ + return iwl_fw_dbg_type_on(fwrt, IWL_FW_ERROR_DUMP_PAGING) && + !fwrt->trans->trans_cfg->gen2 && + fwrt->cur_fw_img < IWL_UCODE_TYPE_MAX && + fwrt->fw->img[fwrt->cur_fw_img].paging_mem_size && + fwrt->fw_paging_db[0].fw_paging_block; +} + +void iwl_fw_dbg_read_d3_debug_data(struct iwl_fw_runtime *fwrt); + +static inline void iwl_fw_flush_dumps(struct iwl_fw_runtime *fwrt) +{ + int i; + + iwl_dbg_tlv_del_timers(fwrt->trans); + for (i = 0; i < IWL_FW_RUNTIME_DUMP_WK_NUM; i++) + flush_delayed_work(&fwrt->dump.wks[i].wk); +} + +#ifdef CONFIG_IWLWIFI_DEBUGFS +static inline void iwl_fw_cancel_timestamp(struct iwl_fw_runtime *fwrt) +{ + fwrt->timestamp.delay = 0; + cancel_delayed_work_sync(&fwrt->timestamp.wk); +} + +void iwl_fw_trigger_timestamp(struct iwl_fw_runtime *fwrt, u32 delay); + +static inline void iwl_fw_suspend_timestamp(struct iwl_fw_runtime *fwrt) +{ + cancel_delayed_work_sync(&fwrt->timestamp.wk); +} + +static inline void iwl_fw_resume_timestamp(struct iwl_fw_runtime *fwrt) +{ + if (!fwrt->timestamp.delay) + return; + + schedule_delayed_work(&fwrt->timestamp.wk, + round_jiffies_relative(fwrt->timestamp.delay)); +} + +#else + +static inline void iwl_fw_cancel_timestamp(struct iwl_fw_runtime *fwrt) {} + +static inline void iwl_fw_trigger_timestamp(struct iwl_fw_runtime *fwrt, + u32 delay) {} + +static inline void iwl_fw_suspend_timestamp(struct iwl_fw_runtime *fwrt) {} + +static inline void iwl_fw_resume_timestamp(struct iwl_fw_runtime *fwrt) {} + +#endif /* CONFIG_IWLWIFI_DEBUGFS */ + +void iwl_fw_dbg_stop_sync(struct iwl_fw_runtime *fwrt); + +static inline void iwl_fw_lmac1_set_alive_err_table(struct iwl_trans *trans, + u32 lmac_error_event_table) +{ + if (!(trans->dbg.error_event_table_tlv_status & + IWL_ERROR_EVENT_TABLE_LMAC1) || + WARN_ON(trans->dbg.lmac_error_event_table[0] != + lmac_error_event_table)) + trans->dbg.lmac_error_event_table[0] = lmac_error_event_table; +} + +static inline void iwl_fw_umac_set_alive_err_table(struct iwl_trans *trans, + u32 umac_error_event_table) +{ + if (!(trans->dbg.error_event_table_tlv_status & + IWL_ERROR_EVENT_TABLE_UMAC) || + WARN_ON(trans->dbg.umac_error_event_table != + umac_error_event_table)) + trans->dbg.umac_error_event_table = umac_error_event_table; +} + +static inline void iwl_fw_error_collect(struct iwl_fw_runtime *fwrt, bool sync) +{ + enum iwl_fw_ini_time_point tp_id; + + if (!iwl_trans_dbg_ini_valid(fwrt->trans)) { + iwl_fw_dbg_collect_desc(fwrt, &iwl_dump_desc_assert, false, 0); + return; + } + + if (fwrt->trans->dbg.hw_error) { + tp_id = IWL_FW_INI_TIME_POINT_FW_HW_ERROR; + fwrt->trans->dbg.hw_error = false; + } else { + tp_id = IWL_FW_INI_TIME_POINT_FW_ASSERT; + } + + _iwl_dbg_tlv_time_point(fwrt, tp_id, NULL, sync); +} + +void iwl_fw_error_print_fseq_regs(struct iwl_fw_runtime *fwrt); + +static inline void iwl_fwrt_update_fw_versions(struct iwl_fw_runtime *fwrt, + struct iwl_lmac_alive *lmac, + struct iwl_umac_alive *umac) +{ + if (lmac) { + fwrt->dump.fw_ver.type = lmac->ver_type; + fwrt->dump.fw_ver.subtype = lmac->ver_subtype; + fwrt->dump.fw_ver.lmac_major = le32_to_cpu(lmac->ucode_major); + fwrt->dump.fw_ver.lmac_minor = le32_to_cpu(lmac->ucode_minor); + } + + if (umac) { + fwrt->dump.fw_ver.umac_major = le32_to_cpu(umac->umac_major); + fwrt->dump.fw_ver.umac_minor = le32_to_cpu(umac->umac_minor); + } +} + +void iwl_fwrt_dump_error_logs(struct iwl_fw_runtime *fwrt); +void iwl_send_dbg_dump_complete_cmd(struct iwl_fw_runtime *fwrt, + u32 timepoint, + u32 timepoint_data); +#endif /* __iwl_fw_dbg_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/debugfs.c b/drivers/net/wireless/intel/iwlwifi/fw/debugfs.c new file mode 100644 index 000000000..607e07ed2 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/debugfs.c @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include "api/commands.h" +#include "debugfs.h" +#include "dbg.h" +#include <linux/seq_file.h> + +#define FWRT_DEBUGFS_OPEN_WRAPPER(name, buflen, argtype) \ +struct dbgfs_##name##_data { \ + argtype *arg; \ + bool read_done; \ + ssize_t rlen; \ + char rbuf[buflen]; \ +}; \ +static int _iwl_dbgfs_##name##_open(struct inode *inode, \ + struct file *file) \ +{ \ + struct dbgfs_##name##_data *data; \ + \ + data = kzalloc(sizeof(*data), GFP_KERNEL); \ + if (!data) \ + return -ENOMEM; \ + \ + data->read_done = false; \ + data->arg = inode->i_private; \ + file->private_data = data; \ + \ + return 0; \ +} + +#define FWRT_DEBUGFS_READ_WRAPPER(name) \ +static ssize_t _iwl_dbgfs_##name##_read(struct file *file, \ + char __user *user_buf, \ + size_t count, loff_t *ppos) \ +{ \ + struct dbgfs_##name##_data *data = file->private_data; \ + \ + if (!data->read_done) { \ + data->read_done = true; \ + data->rlen = iwl_dbgfs_##name##_read(data->arg, \ + sizeof(data->rbuf),\ + data->rbuf); \ + } \ + \ + if (data->rlen < 0) \ + return data->rlen; \ + return simple_read_from_buffer(user_buf, count, ppos, \ + data->rbuf, data->rlen); \ +} + +static int _iwl_dbgfs_release(struct inode *inode, struct file *file) +{ + kfree(file->private_data); + + return 0; +} + +#define _FWRT_DEBUGFS_READ_FILE_OPS(name, buflen, argtype) \ +FWRT_DEBUGFS_OPEN_WRAPPER(name, buflen, argtype) \ +FWRT_DEBUGFS_READ_WRAPPER(name) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .read = _iwl_dbgfs_##name##_read, \ + .open = _iwl_dbgfs_##name##_open, \ + .llseek = generic_file_llseek, \ + .release = _iwl_dbgfs_release, \ +} + +#define FWRT_DEBUGFS_WRITE_WRAPPER(name, buflen, argtype) \ +static ssize_t _iwl_dbgfs_##name##_write(struct file *file, \ + const char __user *user_buf, \ + size_t count, loff_t *ppos) \ +{ \ + argtype *arg = \ + ((struct dbgfs_##name##_data *)file->private_data)->arg;\ + char buf[buflen] = {}; \ + size_t buf_size = min(count, sizeof(buf) - 1); \ + \ + if (copy_from_user(buf, user_buf, buf_size)) \ + return -EFAULT; \ + \ + return iwl_dbgfs_##name##_write(arg, buf, buf_size); \ +} + +#define _FWRT_DEBUGFS_READ_WRITE_FILE_OPS(name, buflen, argtype) \ +FWRT_DEBUGFS_OPEN_WRAPPER(name, buflen, argtype) \ +FWRT_DEBUGFS_WRITE_WRAPPER(name, buflen, argtype) \ +FWRT_DEBUGFS_READ_WRAPPER(name) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .write = _iwl_dbgfs_##name##_write, \ + .read = _iwl_dbgfs_##name##_read, \ + .open = _iwl_dbgfs_##name##_open, \ + .llseek = generic_file_llseek, \ + .release = _iwl_dbgfs_release, \ +} + +#define _FWRT_DEBUGFS_WRITE_FILE_OPS(name, buflen, argtype) \ +FWRT_DEBUGFS_OPEN_WRAPPER(name, buflen, argtype) \ +FWRT_DEBUGFS_WRITE_WRAPPER(name, buflen, argtype) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .write = _iwl_dbgfs_##name##_write, \ + .open = _iwl_dbgfs_##name##_open, \ + .llseek = generic_file_llseek, \ + .release = _iwl_dbgfs_release, \ +} + +#define FWRT_DEBUGFS_READ_FILE_OPS(name, bufsz) \ + _FWRT_DEBUGFS_READ_FILE_OPS(name, bufsz, struct iwl_fw_runtime) + +#define FWRT_DEBUGFS_WRITE_FILE_OPS(name, bufsz) \ + _FWRT_DEBUGFS_WRITE_FILE_OPS(name, bufsz, struct iwl_fw_runtime) + +#define FWRT_DEBUGFS_READ_WRITE_FILE_OPS(name, bufsz) \ + _FWRT_DEBUGFS_READ_WRITE_FILE_OPS(name, bufsz, struct iwl_fw_runtime) + +#define FWRT_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode) do { \ + debugfs_create_file(alias, mode, parent, fwrt, \ + &iwl_dbgfs_##name##_ops); \ + } while (0) +#define FWRT_DEBUGFS_ADD_FILE(name, parent, mode) \ + FWRT_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode) + +static int iwl_fw_send_timestamp_marker_cmd(struct iwl_fw_runtime *fwrt) +{ + struct iwl_mvm_marker marker = { + .dw_len = sizeof(struct iwl_mvm_marker) / 4, + .marker_id = MARKER_ID_SYNC_CLOCK, + + /* the real timestamp is taken from the ftrace clock + * this is for finding the match between fw and kernel logs + */ + .timestamp = cpu_to_le64(fwrt->timestamp.seq++), + }; + + struct iwl_host_cmd hcmd = { + .id = MARKER_CMD, + .flags = CMD_ASYNC, + .data[0] = &marker, + .len[0] = sizeof(marker), + }; + + return iwl_trans_send_cmd(fwrt->trans, &hcmd); +} + +static int iwl_dbgfs_enabled_severities_write(struct iwl_fw_runtime *fwrt, + char *buf, size_t count) +{ + struct iwl_dbg_host_event_cfg_cmd event_cfg; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(DEBUG_GROUP, HOST_EVENT_CFG), + .flags = CMD_ASYNC, + .data[0] = &event_cfg, + .len[0] = sizeof(event_cfg), + }; + u32 enabled_severities; + int ret = kstrtou32(buf, 10, &enabled_severities); + + if (ret < 0) + return ret; + + event_cfg.enabled_severities = cpu_to_le32(enabled_severities); + + ret = iwl_trans_send_cmd(fwrt->trans, &hcmd); + IWL_INFO(fwrt, + "sent host event cfg with enabled_severities: %u, ret: %d\n", + enabled_severities, ret); + + return ret ?: count; +} + +FWRT_DEBUGFS_WRITE_FILE_OPS(enabled_severities, 16); + +static void iwl_fw_timestamp_marker_wk(struct work_struct *work) +{ + int ret; + struct iwl_fw_runtime *fwrt = + container_of(work, struct iwl_fw_runtime, timestamp.wk.work); + unsigned long delay = fwrt->timestamp.delay; + + ret = iwl_fw_send_timestamp_marker_cmd(fwrt); + if (!ret && delay) + schedule_delayed_work(&fwrt->timestamp.wk, + round_jiffies_relative(delay)); + else + IWL_INFO(fwrt, + "stopping timestamp_marker, ret: %d, delay: %u\n", + ret, jiffies_to_msecs(delay) / 1000); +} + +void iwl_fw_trigger_timestamp(struct iwl_fw_runtime *fwrt, u32 delay) +{ + IWL_INFO(fwrt, + "starting timestamp_marker trigger with delay: %us\n", + delay); + + iwl_fw_cancel_timestamp(fwrt); + + fwrt->timestamp.delay = msecs_to_jiffies(delay * 1000); + + schedule_delayed_work(&fwrt->timestamp.wk, + round_jiffies_relative(fwrt->timestamp.delay)); +} + +static ssize_t iwl_dbgfs_timestamp_marker_write(struct iwl_fw_runtime *fwrt, + char *buf, size_t count) +{ + int ret; + u32 delay; + + ret = kstrtou32(buf, 10, &delay); + if (ret < 0) + return ret; + + iwl_fw_trigger_timestamp(fwrt, delay); + + return count; +} + +static ssize_t iwl_dbgfs_timestamp_marker_read(struct iwl_fw_runtime *fwrt, + size_t size, char *buf) +{ + u32 delay_secs = jiffies_to_msecs(fwrt->timestamp.delay) / 1000; + + return scnprintf(buf, size, "%d\n", delay_secs); +} + +FWRT_DEBUGFS_READ_WRITE_FILE_OPS(timestamp_marker, 16); + +struct hcmd_write_data { + __be32 cmd_id; + __be32 flags; + __be16 length; + u8 data[]; +} __packed; + +static ssize_t iwl_dbgfs_send_hcmd_write(struct iwl_fw_runtime *fwrt, char *buf, + size_t count) +{ + size_t header_size = (sizeof(u32) * 2 + sizeof(u16)) * 2; + size_t data_size = (count - 1) / 2; + int ret; + struct hcmd_write_data *data; + struct iwl_host_cmd hcmd = { + .len = { 0, }, + .data = { NULL, }, + }; + + if (fwrt->ops && fwrt->ops->fw_running && + !fwrt->ops->fw_running(fwrt->ops_ctx)) + return -EIO; + + if (count < header_size + 1 || count > 1024 * 4) + return -EINVAL; + + data = kmalloc(data_size, GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret = hex2bin((u8 *)data, buf, data_size); + if (ret) + goto out; + + hcmd.id = be32_to_cpu(data->cmd_id); + hcmd.flags = be32_to_cpu(data->flags); + hcmd.len[0] = be16_to_cpu(data->length); + hcmd.data[0] = data->data; + + if (count != header_size + hcmd.len[0] * 2 + 1) { + IWL_ERR(fwrt, + "host command data size does not match header length\n"); + ret = -EINVAL; + goto out; + } + + if (fwrt->ops && fwrt->ops->send_hcmd) + ret = fwrt->ops->send_hcmd(fwrt->ops_ctx, &hcmd); + else + ret = -EPERM; + + if (ret < 0) + goto out; + + if (hcmd.flags & CMD_WANT_SKB) + iwl_free_resp(&hcmd); +out: + kfree(data); + return ret ?: count; +} + +FWRT_DEBUGFS_WRITE_FILE_OPS(send_hcmd, 512); + +static ssize_t iwl_dbgfs_fw_dbg_domain_read(struct iwl_fw_runtime *fwrt, + size_t size, char *buf) +{ + return scnprintf(buf, size, "0x%08x\n", + fwrt->trans->dbg.domains_bitmap); +} + +FWRT_DEBUGFS_READ_FILE_OPS(fw_dbg_domain, 20); + +struct iwl_dbgfs_fw_info_priv { + struct iwl_fw_runtime *fwrt; +}; + +struct iwl_dbgfs_fw_info_state { + loff_t pos; +}; + +static void *iwl_dbgfs_fw_info_seq_next(struct seq_file *seq, + void *v, loff_t *pos) +{ + struct iwl_dbgfs_fw_info_state *state = v; + struct iwl_dbgfs_fw_info_priv *priv = seq->private; + const struct iwl_fw *fw = priv->fwrt->fw; + + *pos = ++state->pos; + if (*pos >= fw->ucode_capa.n_cmd_versions) { + kfree(state); + return NULL; + } + + return state; +} + +static void iwl_dbgfs_fw_info_seq_stop(struct seq_file *seq, + void *v) +{ + kfree(v); +} + +static void *iwl_dbgfs_fw_info_seq_start(struct seq_file *seq, loff_t *pos) +{ + struct iwl_dbgfs_fw_info_priv *priv = seq->private; + const struct iwl_fw *fw = priv->fwrt->fw; + struct iwl_dbgfs_fw_info_state *state; + + if (*pos >= fw->ucode_capa.n_cmd_versions) + return NULL; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + state->pos = *pos; + return state; +}; + +static int iwl_dbgfs_fw_info_seq_show(struct seq_file *seq, void *v) +{ + struct iwl_dbgfs_fw_info_state *state = v; + struct iwl_dbgfs_fw_info_priv *priv = seq->private; + const struct iwl_fw *fw = priv->fwrt->fw; + const struct iwl_fw_cmd_version *ver; + u32 cmd_id; + + if (!state->pos) + seq_puts(seq, "fw_api_ver:\n"); + + ver = &fw->ucode_capa.cmd_versions[state->pos]; + + cmd_id = WIDE_ID(ver->group, ver->cmd); + + seq_printf(seq, " 0x%04x:\n", cmd_id); + seq_printf(seq, " name: %s\n", + iwl_get_cmd_string(priv->fwrt->trans, cmd_id)); + seq_printf(seq, " cmd_ver: %d\n", ver->cmd_ver); + seq_printf(seq, " notif_ver: %d\n", ver->notif_ver); + return 0; +} + +static const struct seq_operations iwl_dbgfs_info_seq_ops = { + .start = iwl_dbgfs_fw_info_seq_start, + .next = iwl_dbgfs_fw_info_seq_next, + .stop = iwl_dbgfs_fw_info_seq_stop, + .show = iwl_dbgfs_fw_info_seq_show, +}; + +static int iwl_dbgfs_fw_info_open(struct inode *inode, struct file *filp) +{ + struct iwl_dbgfs_fw_info_priv *priv; + + priv = __seq_open_private(filp, &iwl_dbgfs_info_seq_ops, + sizeof(*priv)); + + if (!priv) + return -ENOMEM; + + priv->fwrt = inode->i_private; + return 0; +} + +static const struct file_operations iwl_dbgfs_fw_info_ops = { + .owner = THIS_MODULE, + .open = iwl_dbgfs_fw_info_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release_private, +}; + +void iwl_fwrt_dbgfs_register(struct iwl_fw_runtime *fwrt, + struct dentry *dbgfs_dir) +{ + INIT_DELAYED_WORK(&fwrt->timestamp.wk, iwl_fw_timestamp_marker_wk); + FWRT_DEBUGFS_ADD_FILE(timestamp_marker, dbgfs_dir, 0200); + FWRT_DEBUGFS_ADD_FILE(fw_info, dbgfs_dir, 0200); + FWRT_DEBUGFS_ADD_FILE(send_hcmd, dbgfs_dir, 0200); + FWRT_DEBUGFS_ADD_FILE(enabled_severities, dbgfs_dir, 0200); + FWRT_DEBUGFS_ADD_FILE(fw_dbg_domain, dbgfs_dir, 0400); +} diff --git a/drivers/net/wireless/intel/iwlwifi/fw/debugfs.h b/drivers/net/wireless/intel/iwlwifi/fw/debugfs.h new file mode 100644 index 000000000..0248d40bc --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/debugfs.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include "runtime.h" + +#ifdef CONFIG_IWLWIFI_DEBUGFS +void iwl_fwrt_dbgfs_register(struct iwl_fw_runtime *fwrt, + struct dentry *dbgfs_dir); + +#else +static inline void iwl_fwrt_dbgfs_register(struct iwl_fw_runtime *fwrt, + struct dentry *dbgfs_dir) { } + +#endif /* CONFIG_IWLWIFI_DEBUGFS */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dump.c b/drivers/net/wireless/intel/iwlwifi/fw/dump.c new file mode 100644 index 000000000..b90f1e9ce --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/dump.c @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#include <linux/devcoredump.h> +#include "iwl-drv.h" +#include "runtime.h" +#include "dbg.h" +#include "debugfs.h" +#include "iwl-io.h" +#include "iwl-prph.h" +#include "iwl-csr.h" +#include "pnvm.h" + +/* + * Note: This structure is read from the device with IO accesses, + * and the reading already does the endian conversion. As it is + * read with u32-sized accesses, any members with a different size + * need to be ordered correctly though! + */ +struct iwl_error_event_table { + u32 valid; /* (nonzero) valid, (0) log is empty */ + u32 error_id; /* type of error */ + u32 trm_hw_status0; /* TRM HW status */ + u32 trm_hw_status1; /* TRM HW status */ + u32 blink2; /* branch link */ + u32 ilink1; /* interrupt link */ + u32 ilink2; /* interrupt link */ + u32 data1; /* error-specific data */ + u32 data2; /* error-specific data */ + u32 data3; /* error-specific data */ + u32 bcon_time; /* beacon timer */ + u32 tsf_low; /* network timestamp function timer */ + u32 tsf_hi; /* network timestamp function timer */ + u32 gp1; /* GP1 timer register */ + u32 gp2; /* GP2 timer register */ + u32 fw_rev_type; /* firmware revision type */ + u32 major; /* uCode version major */ + u32 minor; /* uCode version minor */ + u32 hw_ver; /* HW Silicon version */ + u32 brd_ver; /* HW board version */ + u32 log_pc; /* log program counter */ + u32 frame_ptr; /* frame pointer */ + u32 stack_ptr; /* stack pointer */ + u32 hcmd; /* last host command header */ + u32 isr0; /* isr status register LMPM_NIC_ISR0: + * rxtx_flag */ + u32 isr1; /* isr status register LMPM_NIC_ISR1: + * host_flag */ + u32 isr2; /* isr status register LMPM_NIC_ISR2: + * enc_flag */ + u32 isr3; /* isr status register LMPM_NIC_ISR3: + * time_flag */ + u32 isr4; /* isr status register LMPM_NIC_ISR4: + * wico interrupt */ + u32 last_cmd_id; /* last HCMD id handled by the firmware */ + u32 wait_event; /* wait event() caller address */ + u32 l2p_control; /* L2pControlField */ + u32 l2p_duration; /* L2pDurationField */ + u32 l2p_mhvalid; /* L2pMhValidBits */ + u32 l2p_addr_match; /* L2pAddrMatchStat */ + u32 lmpm_pmg_sel; /* indicate which clocks are turned on + * (LMPM_PMG_SEL) */ + u32 u_timestamp; /* indicate when the date and time of the + * compilation */ + u32 flow_handler; /* FH read/write pointers, RX credit */ +} __packed /* LOG_ERROR_TABLE_API_S_VER_3 */; + +/* + * UMAC error struct - relevant starting from family 8000 chip. + * Note: This structure is read from the device with IO accesses, + * and the reading already does the endian conversion. As it is + * read with u32-sized accesses, any members with a different size + * need to be ordered correctly though! + */ +struct iwl_umac_error_event_table { + u32 valid; /* (nonzero) valid, (0) log is empty */ + u32 error_id; /* type of error */ + u32 blink1; /* branch link */ + u32 blink2; /* branch link */ + u32 ilink1; /* interrupt link */ + u32 ilink2; /* interrupt link */ + u32 data1; /* error-specific data */ + u32 data2; /* error-specific data */ + u32 data3; /* error-specific data */ + u32 umac_major; + u32 umac_minor; + u32 frame_pointer; /* core register 27*/ + u32 stack_pointer; /* core register 28 */ + u32 cmd_header; /* latest host cmd sent to UMAC */ + u32 nic_isr_pref; /* ISR status register */ +} __packed; + +#define ERROR_START_OFFSET (1 * sizeof(u32)) +#define ERROR_ELEM_SIZE (7 * sizeof(u32)) + +static void iwl_fwrt_dump_umac_error_log(struct iwl_fw_runtime *fwrt) +{ + struct iwl_trans *trans = fwrt->trans; + struct iwl_umac_error_event_table table = {}; + u32 base = fwrt->trans->dbg.umac_error_event_table; + char pnvm_name[MAX_PNVM_NAME]; + + if (!base && + !(fwrt->trans->dbg.error_event_table_tlv_status & + IWL_ERROR_EVENT_TABLE_UMAC)) + return; + + iwl_trans_read_mem_bytes(trans, base, &table, sizeof(table)); + + if (table.valid) + fwrt->dump.umac_err_id = table.error_id; + + if (ERROR_START_OFFSET <= table.valid * ERROR_ELEM_SIZE) { + IWL_ERR(trans, "Start IWL Error Log Dump:\n"); + IWL_ERR(trans, "Transport status: 0x%08lX, valid: %d\n", + fwrt->trans->status, table.valid); + } + + if ((table.error_id & ~FW_SYSASSERT_CPU_MASK) == + FW_SYSASSERT_PNVM_MISSING) { + iwl_pnvm_get_fs_name(trans, pnvm_name, sizeof(pnvm_name)); + IWL_ERR(fwrt, "PNVM data is missing, please install %s\n", + pnvm_name); + } + + IWL_ERR(fwrt, "0x%08X | %s\n", table.error_id, + iwl_fw_lookup_assert_desc(table.error_id)); + IWL_ERR(fwrt, "0x%08X | umac branchlink1\n", table.blink1); + IWL_ERR(fwrt, "0x%08X | umac branchlink2\n", table.blink2); + IWL_ERR(fwrt, "0x%08X | umac interruptlink1\n", table.ilink1); + IWL_ERR(fwrt, "0x%08X | umac interruptlink2\n", table.ilink2); + IWL_ERR(fwrt, "0x%08X | umac data1\n", table.data1); + IWL_ERR(fwrt, "0x%08X | umac data2\n", table.data2); + IWL_ERR(fwrt, "0x%08X | umac data3\n", table.data3); + IWL_ERR(fwrt, "0x%08X | umac major\n", table.umac_major); + IWL_ERR(fwrt, "0x%08X | umac minor\n", table.umac_minor); + IWL_ERR(fwrt, "0x%08X | frame pointer\n", table.frame_pointer); + IWL_ERR(fwrt, "0x%08X | stack pointer\n", table.stack_pointer); + IWL_ERR(fwrt, "0x%08X | last host cmd\n", table.cmd_header); + IWL_ERR(fwrt, "0x%08X | isr status reg\n", table.nic_isr_pref); +} + +static void iwl_fwrt_dump_lmac_error_log(struct iwl_fw_runtime *fwrt, u8 lmac_num) +{ + struct iwl_trans *trans = fwrt->trans; + struct iwl_error_event_table table = {}; + u32 val, base = fwrt->trans->dbg.lmac_error_event_table[lmac_num]; + + if (fwrt->cur_fw_img == IWL_UCODE_INIT) { + if (!base) + base = fwrt->fw->init_errlog_ptr; + } else { + if (!base) + base = fwrt->fw->inst_errlog_ptr; + } + + if (base < 0x400000) { + IWL_ERR(fwrt, + "Not valid error log pointer 0x%08X for %s uCode\n", + base, + (fwrt->cur_fw_img == IWL_UCODE_INIT) + ? "Init" : "RT"); + return; + } + + /* check if there is a HW error */ + val = iwl_trans_read_mem32(trans, base); + if (((val & ~0xf) == 0xa5a5a5a0) || ((val & ~0xf) == 0x5a5a5a50)) { + int err; + + IWL_ERR(trans, "HW error, resetting before reading\n"); + + /* reset the device */ + err = iwl_trans_sw_reset(trans, true); + if (err) + return; + + err = iwl_finish_nic_init(trans); + if (err) + return; + } + + iwl_trans_read_mem_bytes(trans, base, &table, sizeof(table)); + + if (table.valid) + fwrt->dump.lmac_err_id[lmac_num] = table.error_id; + + if (ERROR_START_OFFSET <= table.valid * ERROR_ELEM_SIZE) { + IWL_ERR(trans, "Start IWL Error Log Dump:\n"); + IWL_ERR(trans, "Transport status: 0x%08lX, valid: %d\n", + fwrt->trans->status, table.valid); + } + + /* Do not change this output - scripts rely on it */ + + IWL_ERR(fwrt, "Loaded firmware version: %s\n", fwrt->fw->fw_version); + + IWL_ERR(fwrt, "0x%08X | %-28s\n", table.error_id, + iwl_fw_lookup_assert_desc(table.error_id)); + IWL_ERR(fwrt, "0x%08X | trm_hw_status0\n", table.trm_hw_status0); + IWL_ERR(fwrt, "0x%08X | trm_hw_status1\n", table.trm_hw_status1); + IWL_ERR(fwrt, "0x%08X | branchlink2\n", table.blink2); + IWL_ERR(fwrt, "0x%08X | interruptlink1\n", table.ilink1); + IWL_ERR(fwrt, "0x%08X | interruptlink2\n", table.ilink2); + IWL_ERR(fwrt, "0x%08X | data1\n", table.data1); + IWL_ERR(fwrt, "0x%08X | data2\n", table.data2); + IWL_ERR(fwrt, "0x%08X | data3\n", table.data3); + IWL_ERR(fwrt, "0x%08X | beacon time\n", table.bcon_time); + IWL_ERR(fwrt, "0x%08X | tsf low\n", table.tsf_low); + IWL_ERR(fwrt, "0x%08X | tsf hi\n", table.tsf_hi); + IWL_ERR(fwrt, "0x%08X | time gp1\n", table.gp1); + IWL_ERR(fwrt, "0x%08X | time gp2\n", table.gp2); + IWL_ERR(fwrt, "0x%08X | uCode revision type\n", table.fw_rev_type); + IWL_ERR(fwrt, "0x%08X | uCode version major\n", table.major); + IWL_ERR(fwrt, "0x%08X | uCode version minor\n", table.minor); + IWL_ERR(fwrt, "0x%08X | hw version\n", table.hw_ver); + IWL_ERR(fwrt, "0x%08X | board version\n", table.brd_ver); + IWL_ERR(fwrt, "0x%08X | hcmd\n", table.hcmd); + IWL_ERR(fwrt, "0x%08X | isr0\n", table.isr0); + IWL_ERR(fwrt, "0x%08X | isr1\n", table.isr1); + IWL_ERR(fwrt, "0x%08X | isr2\n", table.isr2); + IWL_ERR(fwrt, "0x%08X | isr3\n", table.isr3); + IWL_ERR(fwrt, "0x%08X | isr4\n", table.isr4); + IWL_ERR(fwrt, "0x%08X | last cmd Id\n", table.last_cmd_id); + IWL_ERR(fwrt, "0x%08X | wait_event\n", table.wait_event); + IWL_ERR(fwrt, "0x%08X | l2p_control\n", table.l2p_control); + IWL_ERR(fwrt, "0x%08X | l2p_duration\n", table.l2p_duration); + IWL_ERR(fwrt, "0x%08X | l2p_mhvalid\n", table.l2p_mhvalid); + IWL_ERR(fwrt, "0x%08X | l2p_addr_match\n", table.l2p_addr_match); + IWL_ERR(fwrt, "0x%08X | lmpm_pmg_sel\n", table.lmpm_pmg_sel); + IWL_ERR(fwrt, "0x%08X | timestamp\n", table.u_timestamp); + IWL_ERR(fwrt, "0x%08X | flow_handler\n", table.flow_handler); +} + +/* + * TCM error struct. + * Note: This structure is read from the device with IO accesses, + * and the reading already does the endian conversion. As it is + * read with u32-sized accesses, any members with a different size + * need to be ordered correctly though! + */ +struct iwl_tcm_error_event_table { + u32 valid; + u32 error_id; + u32 blink2; + u32 ilink1; + u32 ilink2; + u32 data1, data2, data3; + u32 logpc; + u32 frame_pointer; + u32 stack_pointer; + u32 msgid; + u32 isr; + u32 hw_status[5]; + u32 sw_status[1]; + u32 reserved[4]; +} __packed; /* TCM_LOG_ERROR_TABLE_API_S_VER_1 */ + +static void iwl_fwrt_dump_tcm_error_log(struct iwl_fw_runtime *fwrt, int idx) +{ + struct iwl_trans *trans = fwrt->trans; + struct iwl_tcm_error_event_table table = {}; + u32 base = fwrt->trans->dbg.tcm_error_event_table[idx]; + int i; + u32 flag = idx ? IWL_ERROR_EVENT_TABLE_TCM2 : + IWL_ERROR_EVENT_TABLE_TCM1; + + if (!base || !(fwrt->trans->dbg.error_event_table_tlv_status & flag)) + return; + + iwl_trans_read_mem_bytes(trans, base, &table, sizeof(table)); + + IWL_ERR(fwrt, "TCM%d status:\n", idx + 1); + IWL_ERR(fwrt, "0x%08X | error ID\n", table.error_id); + IWL_ERR(fwrt, "0x%08X | tcm branchlink2\n", table.blink2); + IWL_ERR(fwrt, "0x%08X | tcm interruptlink1\n", table.ilink1); + IWL_ERR(fwrt, "0x%08X | tcm interruptlink2\n", table.ilink2); + IWL_ERR(fwrt, "0x%08X | tcm data1\n", table.data1); + IWL_ERR(fwrt, "0x%08X | tcm data2\n", table.data2); + IWL_ERR(fwrt, "0x%08X | tcm data3\n", table.data3); + IWL_ERR(fwrt, "0x%08X | tcm log PC\n", table.logpc); + IWL_ERR(fwrt, "0x%08X | tcm frame pointer\n", table.frame_pointer); + IWL_ERR(fwrt, "0x%08X | tcm stack pointer\n", table.stack_pointer); + IWL_ERR(fwrt, "0x%08X | tcm msg ID\n", table.msgid); + IWL_ERR(fwrt, "0x%08X | tcm ISR status\n", table.isr); + for (i = 0; i < ARRAY_SIZE(table.hw_status); i++) + IWL_ERR(fwrt, "0x%08X | tcm HW status[%d]\n", + table.hw_status[i], i); + for (i = 0; i < ARRAY_SIZE(table.sw_status); i++) + IWL_ERR(fwrt, "0x%08X | tcm SW status[%d]\n", + table.sw_status[i], i); +} + +/* + * RCM error struct. + * Note: This structure is read from the device with IO accesses, + * and the reading already does the endian conversion. As it is + * read with u32-sized accesses, any members with a different size + * need to be ordered correctly though! + */ +struct iwl_rcm_error_event_table { + u32 valid; + u32 error_id; + u32 blink2; + u32 ilink1; + u32 ilink2; + u32 data1, data2, data3; + u32 logpc; + u32 frame_pointer; + u32 stack_pointer; + u32 msgid; + u32 isr; + u32 frame_hw_status; + u32 mbx_lmac_to_rcm_req; + u32 mbx_rcm_to_lmac_req; + u32 mh_ctl; + u32 mh_addr1_lo; + u32 mh_info; + u32 mh_err; + u32 reserved[3]; +} __packed; /* RCM_LOG_ERROR_TABLE_API_S_VER_1 */ + +static void iwl_fwrt_dump_rcm_error_log(struct iwl_fw_runtime *fwrt, int idx) +{ + struct iwl_trans *trans = fwrt->trans; + struct iwl_rcm_error_event_table table = {}; + u32 base = fwrt->trans->dbg.rcm_error_event_table[idx]; + u32 flag = idx ? IWL_ERROR_EVENT_TABLE_RCM2 : + IWL_ERROR_EVENT_TABLE_RCM1; + + if (!base || !(fwrt->trans->dbg.error_event_table_tlv_status & flag)) + return; + + iwl_trans_read_mem_bytes(trans, base, &table, sizeof(table)); + + IWL_ERR(fwrt, "RCM%d status:\n", idx + 1); + IWL_ERR(fwrt, "0x%08X | error ID\n", table.error_id); + IWL_ERR(fwrt, "0x%08X | rcm branchlink2\n", table.blink2); + IWL_ERR(fwrt, "0x%08X | rcm interruptlink1\n", table.ilink1); + IWL_ERR(fwrt, "0x%08X | rcm interruptlink2\n", table.ilink2); + IWL_ERR(fwrt, "0x%08X | rcm data1\n", table.data1); + IWL_ERR(fwrt, "0x%08X | rcm data2\n", table.data2); + IWL_ERR(fwrt, "0x%08X | rcm data3\n", table.data3); + IWL_ERR(fwrt, "0x%08X | rcm log PC\n", table.logpc); + IWL_ERR(fwrt, "0x%08X | rcm frame pointer\n", table.frame_pointer); + IWL_ERR(fwrt, "0x%08X | rcm stack pointer\n", table.stack_pointer); + IWL_ERR(fwrt, "0x%08X | rcm msg ID\n", table.msgid); + IWL_ERR(fwrt, "0x%08X | rcm ISR status\n", table.isr); + IWL_ERR(fwrt, "0x%08X | frame HW status\n", table.frame_hw_status); + IWL_ERR(fwrt, "0x%08X | LMAC-to-RCM request mbox\n", + table.mbx_lmac_to_rcm_req); + IWL_ERR(fwrt, "0x%08X | RCM-to-LMAC request mbox\n", + table.mbx_rcm_to_lmac_req); + IWL_ERR(fwrt, "0x%08X | MAC header control\n", table.mh_ctl); + IWL_ERR(fwrt, "0x%08X | MAC header addr1 low\n", table.mh_addr1_lo); + IWL_ERR(fwrt, "0x%08X | MAC header info\n", table.mh_info); + IWL_ERR(fwrt, "0x%08X | MAC header error\n", table.mh_err); +} + +static void iwl_fwrt_dump_iml_error_log(struct iwl_fw_runtime *fwrt) +{ + struct iwl_trans *trans = fwrt->trans; + u32 error, data1; + + if (fwrt->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_22000) { + error = UMAG_SB_CPU_2_STATUS; + data1 = UMAG_SB_CPU_1_STATUS; + } else if (fwrt->trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_8000) { + error = SB_CPU_2_STATUS; + data1 = SB_CPU_1_STATUS; + } else { + return; + } + + error = iwl_read_umac_prph(trans, UMAG_SB_CPU_2_STATUS); + + IWL_ERR(trans, "IML/ROM dump:\n"); + + if (error & 0xFFFF0000) + IWL_ERR(trans, "0x%04X | IML/ROM SYSASSERT\n", error >> 16); + + IWL_ERR(fwrt, "0x%08X | IML/ROM error/state\n", error); + IWL_ERR(fwrt, "0x%08X | IML/ROM data1\n", + iwl_read_umac_prph(trans, data1)); + + if (fwrt->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_22000) + IWL_ERR(fwrt, "0x%08X | IML/ROM WFPM_AUTH_KEY_0\n", + iwl_read_umac_prph(trans, SB_MODIFY_CFG_FLAG)); +} + +#define FSEQ_REG(x) { .addr = (x), .str = #x, } + +static void iwl_fwrt_dump_fseq_regs(struct iwl_fw_runtime *fwrt) +{ + struct iwl_trans *trans = fwrt->trans; + int i; + struct { + u32 addr; + const char *str; + } fseq_regs[] = { + FSEQ_REG(FSEQ_ERROR_CODE), + FSEQ_REG(FSEQ_TOP_INIT_VERSION), + FSEQ_REG(FSEQ_CNVIO_INIT_VERSION), + FSEQ_REG(FSEQ_OTP_VERSION), + FSEQ_REG(FSEQ_TOP_CONTENT_VERSION), + FSEQ_REG(FSEQ_ALIVE_TOKEN), + FSEQ_REG(FSEQ_CNVI_ID), + FSEQ_REG(FSEQ_CNVR_ID), + FSEQ_REG(CNVI_AUX_MISC_CHIP), + FSEQ_REG(CNVR_AUX_MISC_CHIP), + FSEQ_REG(CNVR_SCU_SD_REGS_SD_REG_DIG_DCDC_VTRIM), + FSEQ_REG(CNVR_SCU_SD_REGS_SD_REG_ACTIVE_VDIG_MIRROR), + }; + + if (!iwl_trans_grab_nic_access(trans)) + return; + + IWL_ERR(fwrt, "Fseq Registers:\n"); + + for (i = 0; i < ARRAY_SIZE(fseq_regs); i++) + IWL_ERR(fwrt, "0x%08X | %s\n", + iwl_read_prph_no_grab(trans, fseq_regs[i].addr), + fseq_regs[i].str); + + iwl_trans_release_nic_access(trans); +} + +void iwl_fwrt_dump_error_logs(struct iwl_fw_runtime *fwrt) +{ + if (!test_bit(STATUS_DEVICE_ENABLED, &fwrt->trans->status)) { + IWL_ERR(fwrt, + "DEVICE_ENABLED bit is not set. Aborting dump.\n"); + return; + } + + iwl_fwrt_dump_lmac_error_log(fwrt, 0); + if (fwrt->trans->dbg.lmac_error_event_table[1]) + iwl_fwrt_dump_lmac_error_log(fwrt, 1); + iwl_fwrt_dump_umac_error_log(fwrt); + iwl_fwrt_dump_tcm_error_log(fwrt, 0); + iwl_fwrt_dump_rcm_error_log(fwrt, 0); + iwl_fwrt_dump_tcm_error_log(fwrt, 1); + iwl_fwrt_dump_rcm_error_log(fwrt, 1); + iwl_fwrt_dump_iml_error_log(fwrt); + iwl_fwrt_dump_fseq_regs(fwrt); + + if (fwrt->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) { + u32 scratch = iwl_read32(fwrt->trans, CSR_FUNC_SCRATCH); + + IWL_ERR(fwrt, "Function Scratch status:\n"); + IWL_ERR(fwrt, "0x%08X | Func Scratch\n", scratch); + } +} +IWL_EXPORT_SYMBOL(iwl_fwrt_dump_error_logs); diff --git a/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h b/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h new file mode 100644 index 000000000..2d481849a --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/error-dump.h @@ -0,0 +1,560 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2014, 2018-2021 Intel Corporation + * Copyright (C) 2014-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __fw_error_dump_h__ +#define __fw_error_dump_h__ + +#include <linux/types.h> +#include "fw/api/cmdhdr.h" + +#define IWL_FW_ERROR_DUMP_BARKER 0x14789632 +#define IWL_FW_INI_ERROR_DUMP_BARKER 0x14789633 + +/** + * enum iwl_fw_error_dump_type - types of data in the dump file + * @IWL_FW_ERROR_DUMP_CSR: Control Status Registers - from offset 0 + * @IWL_FW_ERROR_DUMP_RXF: + * @IWL_FW_ERROR_DUMP_TXCMD: last TX command data, structured as + * &struct iwl_fw_error_dump_txcmd packets + * @IWL_FW_ERROR_DUMP_DEV_FW_INFO: struct %iwl_fw_error_dump_info + * info on the device / firmware. + * @IWL_FW_ERROR_DUMP_FW_MONITOR: firmware monitor + * @IWL_FW_ERROR_DUMP_PRPH: range of periphery registers - there can be several + * sections like this in a single file. + * @IWL_FW_ERROR_DUMP_FH_REGS: range of FH registers + * @IWL_FW_ERROR_DUMP_MEM: chunk of memory + * @IWL_FW_ERROR_DUMP_ERROR_INFO: description of what triggered this dump. + * Structured as &struct iwl_fw_error_dump_trigger_desc. + * @IWL_FW_ERROR_DUMP_RB: the content of an RB structured as + * &struct iwl_fw_error_dump_rb + * @IWL_FW_ERROR_PAGING: UMAC's image memory segments which were + * paged to the DRAM. + * @IWL_FW_ERROR_DUMP_RADIO_REG: Dump the radio registers. + * @IWL_FW_ERROR_DUMP_EXTERNAL: used only by external code utilities, and + * for that reason is not in use in any other place in the Linux Wi-Fi + * stack. + * @IWL_FW_ERROR_DUMP_MEM_CFG: the addresses and sizes of fifos in the smem, + * which we get from the fw after ALIVE. The content is structured as + * &struct iwl_fw_error_dump_smem_cfg. + */ +enum iwl_fw_error_dump_type { + /* 0 is deprecated */ + IWL_FW_ERROR_DUMP_CSR = 1, + IWL_FW_ERROR_DUMP_RXF = 2, + IWL_FW_ERROR_DUMP_TXCMD = 3, + IWL_FW_ERROR_DUMP_DEV_FW_INFO = 4, + IWL_FW_ERROR_DUMP_FW_MONITOR = 5, + IWL_FW_ERROR_DUMP_PRPH = 6, + IWL_FW_ERROR_DUMP_TXF = 7, + IWL_FW_ERROR_DUMP_FH_REGS = 8, + IWL_FW_ERROR_DUMP_MEM = 9, + IWL_FW_ERROR_DUMP_ERROR_INFO = 10, + IWL_FW_ERROR_DUMP_RB = 11, + IWL_FW_ERROR_DUMP_PAGING = 12, + IWL_FW_ERROR_DUMP_RADIO_REG = 13, + IWL_FW_ERROR_DUMP_INTERNAL_TXF = 14, + IWL_FW_ERROR_DUMP_EXTERNAL = 15, /* Do not move */ + IWL_FW_ERROR_DUMP_MEM_CFG = 16, + IWL_FW_ERROR_DUMP_D3_DEBUG_DATA = 17, + + IWL_FW_ERROR_DUMP_MAX, +}; + +/** + * struct iwl_fw_error_dump_data - data for one type + * @type: &enum iwl_fw_error_dump_type + * @len: the length starting from %data + * @data: the data itself + */ +struct iwl_fw_error_dump_data { + __le32 type; + __le32 len; + __u8 data[]; +} __packed; + +/** + * struct iwl_fw_error_dump_file - the layout of the header of the file + * @barker: must be %IWL_FW_ERROR_DUMP_BARKER + * @file_len: the length of all the file starting from %barker + * @data: array of &struct iwl_fw_error_dump_data + */ +struct iwl_fw_error_dump_file { + __le32 barker; + __le32 file_len; + u8 data[]; +} __packed; + +/** + * struct iwl_fw_error_dump_txcmd - TX command data + * @cmdlen: original length of command + * @caplen: captured length of command (may be less) + * @data: captured command data, @caplen bytes + */ +struct iwl_fw_error_dump_txcmd { + __le32 cmdlen; + __le32 caplen; + u8 data[]; +} __packed; + +/** + * struct iwl_fw_error_dump_fifo - RX/TX FIFO data + * @fifo_num: number of FIFO (starting from 0) + * @available_bytes: num of bytes available in FIFO (may be less than FIFO size) + * @wr_ptr: position of write pointer + * @rd_ptr: position of read pointer + * @fence_ptr: position of fence pointer + * @fence_mode: the current mode of the fence (before locking) - + * 0=follow RD pointer ; 1 = freeze + * @data: all of the FIFO's data + */ +struct iwl_fw_error_dump_fifo { + __le32 fifo_num; + __le32 available_bytes; + __le32 wr_ptr; + __le32 rd_ptr; + __le32 fence_ptr; + __le32 fence_mode; + u8 data[]; +} __packed; + +enum iwl_fw_error_dump_family { + IWL_FW_ERROR_DUMP_FAMILY_7 = 7, + IWL_FW_ERROR_DUMP_FAMILY_8 = 8, +}; + +#define MAX_NUM_LMAC 2 + +/** + * struct iwl_fw_error_dump_info - info on the device / firmware + * @hw_type: the type of the device + * @hw_step: the step of the device + * @fw_human_readable: human readable FW version + * @dev_human_readable: name of the device + * @bus_human_readable: name of the bus used + * @num_of_lmacs: the number of lmacs + * @lmac_err_id: the lmac 0/1 error_id/rt_status that triggered the latest dump + * if the dump collection was not initiated by an assert, the value is 0 + * @umac_err_id: the umac error_id/rt_status that triggered the latest dump + * if the dump collection was not initiated by an assert, the value is 0 + */ +struct iwl_fw_error_dump_info { + __le32 hw_type; + __le32 hw_step; + u8 fw_human_readable[FW_VER_HUMAN_READABLE_SZ]; + u8 dev_human_readable[64]; + u8 bus_human_readable[8]; + u8 num_of_lmacs; + __le32 umac_err_id; + __le32 lmac_err_id[MAX_NUM_LMAC]; +} __packed; + +/** + * struct iwl_fw_error_dump_fw_mon - FW monitor data + * @fw_mon_wr_ptr: the position of the write pointer in the cyclic buffer + * @fw_mon_base_ptr: base pointer of the data + * @fw_mon_cycle_cnt: number of wraparounds + * @fw_mon_base_high_ptr: used in AX210 devices, the base adderss is 64 bit + * so fw_mon_base_ptr holds LSB 32 bits and fw_mon_base_high_ptr hold + * MSB 32 bits + * @reserved: for future use + * @data: captured data + */ +struct iwl_fw_error_dump_fw_mon { + __le32 fw_mon_wr_ptr; + __le32 fw_mon_base_ptr; + __le32 fw_mon_cycle_cnt; + __le32 fw_mon_base_high_ptr; + __le32 reserved[2]; + u8 data[]; +} __packed; + +#define MAX_NUM_LMAC 2 +#define TX_FIFO_INTERNAL_MAX_NUM 6 +#define TX_FIFO_MAX_NUM 15 +/** + * struct iwl_fw_error_dump_smem_cfg - Dump SMEM configuration + * This must follow &struct iwl_fwrt_shared_mem_cfg. + * @num_lmacs: number of lmacs + * @num_txfifo_entries: number of tx fifos + * @lmac: sizes of lmacs txfifos and rxfifo1 + * @rxfifo2_size: size of rxfifo2 + * @internal_txfifo_addr: address of internal tx fifo + * @internal_txfifo_size: size of internal tx fifo + */ +struct iwl_fw_error_dump_smem_cfg { + __le32 num_lmacs; + __le32 num_txfifo_entries; + struct { + __le32 txfifo_size[TX_FIFO_MAX_NUM]; + __le32 rxfifo1_size; + } lmac[MAX_NUM_LMAC]; + __le32 rxfifo2_size; + __le32 internal_txfifo_addr; + __le32 internal_txfifo_size[TX_FIFO_INTERNAL_MAX_NUM]; +} __packed; +/** + * struct iwl_fw_error_dump_prph - periphery registers data + * @prph_start: address of the first register in this chunk + * @data: the content of the registers + */ +struct iwl_fw_error_dump_prph { + __le32 prph_start; + __le32 data[]; +}; + +enum iwl_fw_error_dump_mem_type { + IWL_FW_ERROR_DUMP_MEM_SRAM, + IWL_FW_ERROR_DUMP_MEM_SMEM, + IWL_FW_ERROR_DUMP_MEM_NAMED_MEM = 10, +}; + +/** + * struct iwl_fw_error_dump_mem - chunk of memory + * @type: &enum iwl_fw_error_dump_mem_type + * @offset: the offset from which the memory was read + * @data: the content of the memory + */ +struct iwl_fw_error_dump_mem { + __le32 type; + __le32 offset; + u8 data[]; +}; + +/* Dump version, used by the dump parser to differentiate between + * different dump formats + */ +#define IWL_INI_DUMP_VER 1 + +/* Use bit 31 as dump info type to avoid colliding with region types */ +#define IWL_INI_DUMP_INFO_TYPE BIT(31) + +/** + * struct iwl_fw_error_dump_data - data for one type + * @type: &enum iwl_fw_ini_region_type + * @sub_type: sub type id + * @sub_type_ver: sub type version + * @reserved: not in use + * @len: the length starting from %data + * @data: the data itself + */ +struct iwl_fw_ini_error_dump_data { + u8 type; + u8 sub_type; + u8 sub_type_ver; + u8 reserved; + __le32 len; + __u8 data[]; +} __packed; + +/** + * struct iwl_fw_ini_dump_entry + * @list: list of dump entries + * @size: size of the data + * @data: entry data + */ +struct iwl_fw_ini_dump_entry { + struct list_head list; + u32 size; + u8 data[]; +} __packed; + +/** + * struct iwl_fw_error_dump_file - header of dump file + * @barker: must be %IWL_FW_INI_ERROR_DUMP_BARKER + * @file_len: the length of all the file including the header + */ +struct iwl_fw_ini_dump_file_hdr { + __le32 barker; + __le32 file_len; +} __packed; + +/** + * struct iwl_fw_ini_fifo_hdr - fifo range header + * @fifo_num: the fifo number. In case of umac rx fifo, set BIT(31) to + * distinguish between lmac and umac rx fifos + * @num_of_registers: num of registers to dump, dword size each + */ +struct iwl_fw_ini_fifo_hdr { + __le32 fifo_num; + __le32 num_of_registers; +} __packed; + +/** + * struct iwl_fw_ini_error_dump_range - range of memory + * @range_data_size: the size of this range, in bytes + * @internal_base_addr: base address of internal memory range + * @dram_base_addr: base address of dram monitor range + * @page_num: page number of memory range + * @fifo_hdr: fifo header of memory range + * @fw_pkt: FW packet header of memory range + * @data: the actual memory + */ +struct iwl_fw_ini_error_dump_range { + __le32 range_data_size; + union { + __le32 internal_base_addr __packed; + __le64 dram_base_addr __packed; + __le32 page_num __packed; + struct iwl_fw_ini_fifo_hdr fifo_hdr; + struct iwl_cmd_header fw_pkt_hdr; + }; + __le32 data[]; +} __packed; + +/** + * struct iwl_fw_ini_error_dump_header - ini region dump header + * @version: dump version + * @region_id: id of the region + * @num_of_ranges: number of ranges in this region + * @name_len: number of bytes allocated to the name string of this region + * @name: name of the region + */ +struct iwl_fw_ini_error_dump_header { + __le32 version; + __le32 region_id; + __le32 num_of_ranges; + __le32 name_len; + u8 name[IWL_FW_INI_MAX_NAME]; +}; + +/** + * struct iwl_fw_ini_error_dump - ini region dump + * @header: the header of this region + * @data: data of memory ranges in this region, + * see &struct iwl_fw_ini_error_dump_range + */ +struct iwl_fw_ini_error_dump { + struct iwl_fw_ini_error_dump_header header; + u8 data[]; +} __packed; + +/* This bit is used to differentiate between lmac and umac rxf */ +#define IWL_RXF_UMAC_BIT BIT(31) + +/** + * struct iwl_fw_ini_error_dump_register - ini register dump + * @addr: address of the register + * @data: data of the register + */ +struct iwl_fw_ini_error_dump_register { + __le32 addr; + __le32 data; +} __packed; + +/** + * struct iwl_fw_ini_dump_cfg_name - configuration name + * @image_type: image type the configuration is related to + * @cfg_name_len: length of the configuration name + * @cfg_name: name of the configuraiton + */ +struct iwl_fw_ini_dump_cfg_name { + __le32 image_type; + __le32 cfg_name_len; + u8 cfg_name[IWL_FW_INI_MAX_CFG_NAME]; +} __packed; + +/* AX210's HW type */ +#define IWL_AX210_HW_TYPE 0x42 +/* How many bits to roll when adding to the HW type of AX210 HW */ +#define IWL_AX210_HW_TYPE_ADDITION_SHIFT 12 + +/* struct iwl_fw_ini_dump_info - ini dump information + * @version: dump version + * @time_point: time point that caused the dump collection + * @trigger_reason: reason of the trigger + * @external_cfg_state: &enum iwl_ini_cfg_state + * @ver_type: FW version type + * @ver_subtype: FW version subype + * @hw_step: HW step + * @hw_type: HW type + * @rf_id_flavor: HW RF id flavor + * @rf_id_dash: HW RF id dash + * @rf_id_step: HW RF id step + * @rf_id_type: HW RF id type + * @lmac_major: lmac major version + * @lmac_minor: lmac minor version + * @umac_major: umac major version + * @umac_minor: umac minor version + * @fw_mon_mode: FW monitor mode &enum iwl_fw_ini_buffer_location + * @regions_mask: bitmap mask of regions ids in the dump + * @build_tag_len: length of the build tag + * @build_tag: build tag string + * @num_of_cfg_names: number of configuration name structs + * @cfg_names: configuration names + */ +struct iwl_fw_ini_dump_info { + __le32 version; + __le32 time_point; + __le32 trigger_reason; + __le32 external_cfg_state; + __le32 ver_type; + __le32 ver_subtype; + __le32 hw_step; + __le32 hw_type; + __le32 rf_id_flavor; + __le32 rf_id_dash; + __le32 rf_id_step; + __le32 rf_id_type; + __le32 lmac_major; + __le32 lmac_minor; + __le32 umac_major; + __le32 umac_minor; + __le32 fw_mon_mode; + __le64 regions_mask; + __le32 build_tag_len; + u8 build_tag[FW_VER_HUMAN_READABLE_SZ]; + __le32 num_of_cfg_names; + struct iwl_fw_ini_dump_cfg_name cfg_names[]; +} __packed; + +/** + * struct iwl_fw_ini_err_table_dump - ini error table dump + * @header: header of the region + * @version: error table version + * @data: data of memory ranges in this region, + * see &struct iwl_fw_ini_error_dump_range + */ +struct iwl_fw_ini_err_table_dump { + struct iwl_fw_ini_error_dump_header header; + __le32 version; + u8 data[]; +} __packed; + +/** + * struct iwl_fw_error_dump_rb - content of an Receive Buffer + * @index: the index of the Receive Buffer in the Rx queue + * @rxq: the RB's Rx queue + * @reserved: + * @data: the content of the Receive Buffer + */ +struct iwl_fw_error_dump_rb { + __le32 index; + __le32 rxq; + __le32 reserved; + u8 data[]; +}; + +/** + * struct iwl_fw_ini_monitor_dump - ini monitor dump + * @header: header of the region + * @write_ptr: write pointer position in the buffer + * @cycle_cnt: cycles count + * @cur_frag: current fragment in use + * @data: data of memory ranges in this region, + * see &struct iwl_fw_ini_error_dump_range + */ +struct iwl_fw_ini_monitor_dump { + struct iwl_fw_ini_error_dump_header header; + __le32 write_ptr; + __le32 cycle_cnt; + __le32 cur_frag; + u8 data[]; +} __packed; + +/** + * struct iwl_fw_ini_special_device_memory - special device memory + * @header: header of the region + * @type: type of special memory + * @version: struct special memory version + * @data: data of memory ranges in this region, + * see &struct iwl_fw_ini_error_dump_range + */ +struct iwl_fw_ini_special_device_memory { + struct iwl_fw_ini_error_dump_header header; + __le16 type; + __le16 version; + u8 data[]; +} __packed; + +/** + * struct iwl_fw_error_dump_paging - content of the UMAC's image page + * block on DRAM + * @index: the index of the page block + * @reserved: + * @data: the content of the page block + */ +struct iwl_fw_error_dump_paging { + __le32 index; + __le32 reserved; + u8 data[]; +}; + +/** + * iwl_fw_error_next_data - advance fw error dump data pointer + * @data: previous data block + * Returns: next data block + */ +static inline struct iwl_fw_error_dump_data * +iwl_fw_error_next_data(struct iwl_fw_error_dump_data *data) +{ + return (void *)(data->data + le32_to_cpu(data->len)); +} + +/** + * enum iwl_fw_dbg_trigger - triggers available + * + * @FW_DBG_TRIGGER_USER: trigger log collection by user + * This should not be defined as a trigger to the driver, but a value the + * driver should set to indicate that the trigger was initiated by the + * user. + * @FW_DBG_TRIGGER_FW_ASSERT: trigger log collection when the firmware asserts + * @FW_DBG_TRIGGER_MISSED_BEACONS: trigger log collection when beacons are + * missed. + * @FW_DBG_TRIGGER_CHANNEL_SWITCH: trigger log collection upon channel switch. + * @FW_DBG_TRIGGER_FW_NOTIF: trigger log collection when the firmware sends a + * command response or a notification. + * @FW_DBG_TRIGGER_MLME: trigger log collection upon MLME event. + * @FW_DBG_TRIGGER_STATS: trigger log collection upon statistics threshold. + * @FW_DBG_TRIGGER_RSSI: trigger log collection when the rssi of the beacon + * goes below a threshold. + * @FW_DBG_TRIGGER_TXQ_TIMERS: configures the timers for the Tx queue hang + * detection. + * @FW_DBG_TRIGGER_TIME_EVENT: trigger log collection upon time events related + * events. + * @FW_DBG_TRIGGER_BA: trigger log collection upon BlockAck related events. + * @FW_DBG_TX_LATENCY: trigger log collection when the tx latency goes above a + * threshold. + * @FW_DBG_TDLS: trigger log collection upon TDLS related events. + * @FW_DBG_TRIGGER_TX_STATUS: trigger log collection upon tx status when + * the firmware sends a tx reply. + * @FW_DBG_TRIGGER_ALIVE_TIMEOUT: trigger log collection if alive flow timeouts + * @FW_DBG_TRIGGER_DRIVER: trigger log collection upon a flow failure + * in the driver. + */ +enum iwl_fw_dbg_trigger { + FW_DBG_TRIGGER_INVALID = 0, + FW_DBG_TRIGGER_USER, + FW_DBG_TRIGGER_FW_ASSERT, + FW_DBG_TRIGGER_MISSED_BEACONS, + FW_DBG_TRIGGER_CHANNEL_SWITCH, + FW_DBG_TRIGGER_FW_NOTIF, + FW_DBG_TRIGGER_MLME, + FW_DBG_TRIGGER_STATS, + FW_DBG_TRIGGER_RSSI, + FW_DBG_TRIGGER_TXQ_TIMERS, + FW_DBG_TRIGGER_TIME_EVENT, + FW_DBG_TRIGGER_BA, + FW_DBG_TRIGGER_TX_LATENCY, + FW_DBG_TRIGGER_TDLS, + FW_DBG_TRIGGER_TX_STATUS, + FW_DBG_TRIGGER_ALIVE_TIMEOUT, + FW_DBG_TRIGGER_DRIVER, + + /* must be last */ + FW_DBG_TRIGGER_MAX, +}; + +/** + * struct iwl_fw_error_dump_trigger_desc - describes the trigger condition + * @type: &enum iwl_fw_dbg_trigger + * @data: raw data about what happened + */ +struct iwl_fw_error_dump_trigger_desc { + __le32 type; + u8 data[]; +}; + +#endif /* __fw_error_dump_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/file.h b/drivers/net/wireless/intel/iwlwifi/fw/file.h new file mode 100644 index 000000000..a7817d952 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/file.h @@ -0,0 +1,955 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2008-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_file_h__ +#define __iwl_fw_file_h__ + +#include <linux/netdevice.h> +#include <linux/nl80211.h> + +/* v1/v2 uCode file layout */ +struct iwl_ucode_header { + __le32 ver; /* major/minor/API/serial */ + union { + struct { + __le32 inst_size; /* bytes of runtime code */ + __le32 data_size; /* bytes of runtime data */ + __le32 init_size; /* bytes of init code */ + __le32 init_data_size; /* bytes of init data */ + __le32 boot_size; /* bytes of bootstrap code */ + u8 data[0]; /* in same order as sizes */ + } v1; + struct { + __le32 build; /* build number */ + __le32 inst_size; /* bytes of runtime code */ + __le32 data_size; /* bytes of runtime data */ + __le32 init_size; /* bytes of init code */ + __le32 init_data_size; /* bytes of init data */ + __le32 boot_size; /* bytes of bootstrap code */ + u8 data[0]; /* in same order as sizes */ + } v2; + } u; +}; + +#define IWL_UCODE_TLV_DEBUG_BASE 0x1000005 +#define IWL_UCODE_TLV_CONST_BASE 0x100 + +/* + * new TLV uCode file layout + * + * The new TLV file format contains TLVs, that each specify + * some piece of data. + */ + +enum iwl_ucode_tlv_type { + IWL_UCODE_TLV_INVALID = 0, /* unused */ + IWL_UCODE_TLV_INST = 1, + IWL_UCODE_TLV_DATA = 2, + IWL_UCODE_TLV_INIT = 3, + IWL_UCODE_TLV_INIT_DATA = 4, + IWL_UCODE_TLV_BOOT = 5, + IWL_UCODE_TLV_PROBE_MAX_LEN = 6, /* a u32 value */ + IWL_UCODE_TLV_PAN = 7, /* deprecated -- only used in DVM */ + IWL_UCODE_TLV_MEM_DESC = 7, /* replaces PAN in non-DVM */ + IWL_UCODE_TLV_RUNT_EVTLOG_PTR = 8, + IWL_UCODE_TLV_RUNT_EVTLOG_SIZE = 9, + IWL_UCODE_TLV_RUNT_ERRLOG_PTR = 10, + IWL_UCODE_TLV_INIT_EVTLOG_PTR = 11, + IWL_UCODE_TLV_INIT_EVTLOG_SIZE = 12, + IWL_UCODE_TLV_INIT_ERRLOG_PTR = 13, + IWL_UCODE_TLV_ENHANCE_SENS_TBL = 14, + IWL_UCODE_TLV_PHY_CALIBRATION_SIZE = 15, + IWL_UCODE_TLV_WOWLAN_INST = 16, + IWL_UCODE_TLV_WOWLAN_DATA = 17, + IWL_UCODE_TLV_FLAGS = 18, + IWL_UCODE_TLV_SEC_RT = 19, + IWL_UCODE_TLV_SEC_INIT = 20, + IWL_UCODE_TLV_SEC_WOWLAN = 21, + IWL_UCODE_TLV_DEF_CALIB = 22, + IWL_UCODE_TLV_PHY_SKU = 23, + IWL_UCODE_TLV_SECURE_SEC_RT = 24, + IWL_UCODE_TLV_SECURE_SEC_INIT = 25, + IWL_UCODE_TLV_SECURE_SEC_WOWLAN = 26, + IWL_UCODE_TLV_NUM_OF_CPU = 27, + IWL_UCODE_TLV_CSCHEME = 28, + IWL_UCODE_TLV_API_CHANGES_SET = 29, + IWL_UCODE_TLV_ENABLED_CAPABILITIES = 30, + IWL_UCODE_TLV_N_SCAN_CHANNELS = 31, + IWL_UCODE_TLV_PAGING = 32, + IWL_UCODE_TLV_SEC_RT_USNIFFER = 34, + /* 35 is unused */ + IWL_UCODE_TLV_FW_VERSION = 36, + IWL_UCODE_TLV_FW_DBG_DEST = 38, + IWL_UCODE_TLV_FW_DBG_CONF = 39, + IWL_UCODE_TLV_FW_DBG_TRIGGER = 40, + IWL_UCODE_TLV_CMD_VERSIONS = 48, + IWL_UCODE_TLV_FW_GSCAN_CAPA = 50, + IWL_UCODE_TLV_FW_MEM_SEG = 51, + IWL_UCODE_TLV_IML = 52, + IWL_UCODE_TLV_UMAC_DEBUG_ADDRS = 54, + IWL_UCODE_TLV_LMAC_DEBUG_ADDRS = 55, + IWL_UCODE_TLV_FW_RECOVERY_INFO = 57, + IWL_UCODE_TLV_HW_TYPE = 58, + IWL_UCODE_TLV_FW_FSEQ_VERSION = 60, + IWL_UCODE_TLV_PHY_INTEGRATION_VERSION = 61, + + IWL_UCODE_TLV_PNVM_VERSION = 62, + IWL_UCODE_TLV_PNVM_SKU = 64, + + IWL_UCODE_TLV_SEC_TABLE_ADDR = 66, + IWL_UCODE_TLV_D3_KEK_KCK_ADDR = 67, + + IWL_UCODE_TLV_FW_NUM_STATIONS = IWL_UCODE_TLV_CONST_BASE + 0, + + IWL_UCODE_TLV_TYPE_DEBUG_INFO = IWL_UCODE_TLV_DEBUG_BASE + 0, + IWL_UCODE_TLV_TYPE_BUFFER_ALLOCATION = IWL_UCODE_TLV_DEBUG_BASE + 1, + IWL_UCODE_TLV_TYPE_HCMD = IWL_UCODE_TLV_DEBUG_BASE + 2, + IWL_UCODE_TLV_TYPE_REGIONS = IWL_UCODE_TLV_DEBUG_BASE + 3, + IWL_UCODE_TLV_TYPE_TRIGGERS = IWL_UCODE_TLV_DEBUG_BASE + 4, + IWL_UCODE_TLV_TYPE_CONF_SET = IWL_UCODE_TLV_DEBUG_BASE + 5, + IWL_UCODE_TLV_DEBUG_MAX = IWL_UCODE_TLV_TYPE_TRIGGERS, + + /* TLVs 0x1000-0x2000 are for internal driver usage */ + IWL_UCODE_TLV_FW_DBG_DUMP_LST = 0x1000, +}; + +struct iwl_ucode_tlv { + __le32 type; /* see above */ + __le32 length; /* not including type/length fields */ + u8 data[]; +}; + +#define IWL_TLV_UCODE_MAGIC 0x0a4c5749 +#define FW_VER_HUMAN_READABLE_SZ 64 + +struct iwl_tlv_ucode_header { + /* + * The TLV style ucode header is distinguished from + * the v1/v2 style header by first four bytes being + * zero, as such is an invalid combination of + * major/minor/API/serial versions. + */ + __le32 zero; + __le32 magic; + u8 human_readable[FW_VER_HUMAN_READABLE_SZ]; + /* major/minor/API/serial or major in new format */ + __le32 ver; + __le32 build; + __le64 ignore; + /* + * The data contained herein has a TLV layout, + * see above for the TLV header and types. + * Note that each TLV is padded to a length + * that is a multiple of 4 for alignment. + */ + u8 data[]; +}; + +/* + * ucode TLVs + * + * ability to get extension for: flags & capabilities from ucode binaries files + */ +struct iwl_ucode_api { + __le32 api_index; + __le32 api_flags; +} __packed; + +struct iwl_ucode_capa { + __le32 api_index; + __le32 api_capa; +} __packed; + +/** + * enum iwl_ucode_tlv_flag - ucode API flags + * @IWL_UCODE_TLV_FLAGS_PAN: This is PAN capable microcode; this previously + * was a separate TLV but moved here to save space. + * @IWL_UCODE_TLV_FLAGS_NEWSCAN: new uCode scan behavior on hidden SSID, + * treats good CRC threshold as a boolean + * @IWL_UCODE_TLV_FLAGS_MFP: This uCode image supports MFP (802.11w). + * @IWL_UCODE_TLV_FLAGS_UAPSD_SUPPORT: This uCode image supports uAPSD + * @IWL_UCODE_TLV_FLAGS_SHORT_BL: 16 entries of block list instead of 64 in scan + * offload profile config command. + * @IWL_UCODE_TLV_FLAGS_D3_6_IPV6_ADDRS: D3 image supports up to six + * (rather than two) IPv6 addresses + * @IWL_UCODE_TLV_FLAGS_NO_BASIC_SSID: not sending a probe with the SSID element + * from the probe request template. + * @IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL: new NS offload (small version) + * @IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_LARGE: new NS offload (large version) + * @IWL_UCODE_TLV_FLAGS_UAPSD_SUPPORT: General support for uAPSD + * @IWL_UCODE_TLV_FLAGS_P2P_PS_UAPSD: P2P client supports uAPSD power save + * @IWL_UCODE_TLV_FLAGS_EBS_SUPPORT: this uCode image supports EBS. + */ +enum iwl_ucode_tlv_flag { + IWL_UCODE_TLV_FLAGS_PAN = BIT(0), + IWL_UCODE_TLV_FLAGS_NEWSCAN = BIT(1), + IWL_UCODE_TLV_FLAGS_MFP = BIT(2), + IWL_UCODE_TLV_FLAGS_SHORT_BL = BIT(7), + IWL_UCODE_TLV_FLAGS_D3_6_IPV6_ADDRS = BIT(10), + IWL_UCODE_TLV_FLAGS_NO_BASIC_SSID = BIT(12), + IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL = BIT(15), + IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_LARGE = BIT(16), + IWL_UCODE_TLV_FLAGS_UAPSD_SUPPORT = BIT(24), + IWL_UCODE_TLV_FLAGS_EBS_SUPPORT = BIT(25), + IWL_UCODE_TLV_FLAGS_P2P_PS_UAPSD = BIT(26), +}; + +typedef unsigned int __bitwise iwl_ucode_tlv_api_t; + +/** + * enum iwl_ucode_tlv_api - ucode api + * @IWL_UCODE_TLV_API_FRAGMENTED_SCAN: This ucode supports active dwell time + * longer than the passive one, which is essential for fragmented scan. + * @IWL_UCODE_TLV_API_WIFI_MCC_UPDATE: ucode supports MCC updates with source. + * @IWL_UCODE_TLV_API_LQ_SS_PARAMS: Configure STBC/BFER via LQ CMD ss_params + * @IWL_UCODE_TLV_API_NEW_VERSION: new versioning format + * @IWL_UCODE_TLV_API_SCAN_TSF_REPORT: Scan start time reported in scan + * iteration complete notification, and the timestamp reported for RX + * received during scan, are reported in TSF of the mac specified in the + * scan request. + * @IWL_UCODE_TLV_API_TKIP_MIC_KEYS: This ucode supports version 2 of + * ADD_MODIFY_STA_KEY_API_S_VER_2. + * @IWL_UCODE_TLV_API_STA_TYPE: This ucode supports station type assignement. + * @IWL_UCODE_TLV_API_NAN2_VER2: This ucode supports NAN API version 2 + * @IWL_UCODE_TLV_API_NEW_RX_STATS: should new RX STATISTICS API be used + * @IWL_UCODE_TLV_API_QUOTA_LOW_LATENCY: Quota command includes a field + * indicating low latency direction. + * @IWL_UCODE_TLV_API_DEPRECATE_TTAK: RX status flag TTAK ok (bit 7) is + * deprecated. + * @IWL_UCODE_TLV_API_ADAPTIVE_DWELL_V2: This ucode supports version 8 + * of scan request: SCAN_REQUEST_CMD_UMAC_API_S_VER_8 + * @IWL_UCODE_TLV_API_FRAG_EBS: This ucode supports fragmented EBS + * @IWL_UCODE_TLV_API_REDUCE_TX_POWER: This ucode supports v5 of + * the REDUCE_TX_POWER_CMD. + * @IWL_UCODE_TLV_API_SHORT_BEACON_NOTIF: This ucode supports the short + * version of the beacon notification. + * @IWL_UCODE_TLV_API_BEACON_FILTER_V4: This ucode supports v4 of + * BEACON_FILTER_CONFIG_API_S_VER_4. + * @IWL_UCODE_TLV_API_REGULATORY_NVM_INFO: This ucode supports v4 of + * REGULATORY_NVM_GET_INFO_RSP_API_S. + * @IWL_UCODE_TLV_API_FTM_NEW_RANGE_REQ: This ucode supports v7 of + * LOCATION_RANGE_REQ_CMD_API_S and v6 of LOCATION_RANGE_RESP_NTFY_API_S. + * @IWL_UCODE_TLV_API_SCAN_OFFLOAD_CHANS: This ucode supports v2 of + * SCAN_OFFLOAD_PROFILE_MATCH_RESULTS_S and v3 of + * SCAN_OFFLOAD_PROFILES_QUERY_RSP_S. + * @IWL_UCODE_TLV_API_MBSSID_HE: This ucode supports v2 of + * STA_CONTEXT_DOT11AX_API_S + * @IWL_UCODE_TLV_API_SAR_TABLE_VER: This ucode supports different sar + * version tables. + * @IWL_UCODE_TLV_API_REDUCED_SCAN_CONFIG: This ucode supports v3 of + * SCAN_CONFIG_DB_CMD_API_S. + * + * @NUM_IWL_UCODE_TLV_API: number of bits used + */ +enum iwl_ucode_tlv_api { + /* API Set 0 */ + IWL_UCODE_TLV_API_FRAGMENTED_SCAN = (__force iwl_ucode_tlv_api_t)8, + IWL_UCODE_TLV_API_WIFI_MCC_UPDATE = (__force iwl_ucode_tlv_api_t)9, + IWL_UCODE_TLV_API_LQ_SS_PARAMS = (__force iwl_ucode_tlv_api_t)18, + IWL_UCODE_TLV_API_NEW_VERSION = (__force iwl_ucode_tlv_api_t)20, + IWL_UCODE_TLV_API_SCAN_TSF_REPORT = (__force iwl_ucode_tlv_api_t)28, + IWL_UCODE_TLV_API_TKIP_MIC_KEYS = (__force iwl_ucode_tlv_api_t)29, + IWL_UCODE_TLV_API_STA_TYPE = (__force iwl_ucode_tlv_api_t)30, + IWL_UCODE_TLV_API_NAN2_VER2 = (__force iwl_ucode_tlv_api_t)31, + /* API Set 1 */ + IWL_UCODE_TLV_API_ADAPTIVE_DWELL = (__force iwl_ucode_tlv_api_t)32, + IWL_UCODE_TLV_API_OCE = (__force iwl_ucode_tlv_api_t)33, + IWL_UCODE_TLV_API_NEW_BEACON_TEMPLATE = (__force iwl_ucode_tlv_api_t)34, + IWL_UCODE_TLV_API_NEW_RX_STATS = (__force iwl_ucode_tlv_api_t)35, + IWL_UCODE_TLV_API_WOWLAN_KEY_MATERIAL = (__force iwl_ucode_tlv_api_t)36, + IWL_UCODE_TLV_API_QUOTA_LOW_LATENCY = (__force iwl_ucode_tlv_api_t)38, + IWL_UCODE_TLV_API_DEPRECATE_TTAK = (__force iwl_ucode_tlv_api_t)41, + IWL_UCODE_TLV_API_ADAPTIVE_DWELL_V2 = (__force iwl_ucode_tlv_api_t)42, + IWL_UCODE_TLV_API_FRAG_EBS = (__force iwl_ucode_tlv_api_t)44, + IWL_UCODE_TLV_API_REDUCE_TX_POWER = (__force iwl_ucode_tlv_api_t)45, + IWL_UCODE_TLV_API_SHORT_BEACON_NOTIF = (__force iwl_ucode_tlv_api_t)46, + IWL_UCODE_TLV_API_BEACON_FILTER_V4 = (__force iwl_ucode_tlv_api_t)47, + IWL_UCODE_TLV_API_REGULATORY_NVM_INFO = (__force iwl_ucode_tlv_api_t)48, + IWL_UCODE_TLV_API_FTM_NEW_RANGE_REQ = (__force iwl_ucode_tlv_api_t)49, + IWL_UCODE_TLV_API_SCAN_OFFLOAD_CHANS = (__force iwl_ucode_tlv_api_t)50, + IWL_UCODE_TLV_API_MBSSID_HE = (__force iwl_ucode_tlv_api_t)52, + IWL_UCODE_TLV_API_WOWLAN_TCP_SYN_WAKE = (__force iwl_ucode_tlv_api_t)53, + IWL_UCODE_TLV_API_FTM_RTT_ACCURACY = (__force iwl_ucode_tlv_api_t)54, + IWL_UCODE_TLV_API_SAR_TABLE_VER = (__force iwl_ucode_tlv_api_t)55, + IWL_UCODE_TLV_API_REDUCED_SCAN_CONFIG = (__force iwl_ucode_tlv_api_t)56, + IWL_UCODE_TLV_API_ADWELL_HB_DEF_N_AP = (__force iwl_ucode_tlv_api_t)57, + IWL_UCODE_TLV_API_SCAN_EXT_CHAN_VER = (__force iwl_ucode_tlv_api_t)58, + IWL_UCODE_TLV_API_BAND_IN_RX_DATA = (__force iwl_ucode_tlv_api_t)59, + + +#ifdef __CHECKER__ + /* sparse says it cannot increment the previous enum member */ +#define NUM_IWL_UCODE_TLV_API 128 +#else + NUM_IWL_UCODE_TLV_API +#endif +}; + +typedef unsigned int __bitwise iwl_ucode_tlv_capa_t; + +/** + * enum iwl_ucode_tlv_capa - ucode capabilities + * @IWL_UCODE_TLV_CAPA_D0I3_SUPPORT: supports D0i3 + * @IWL_UCODE_TLV_CAPA_LAR_SUPPORT: supports Location Aware Regulatory + * @IWL_UCODE_TLV_CAPA_UMAC_SCAN: supports UMAC scan. + * @IWL_UCODE_TLV_CAPA_BEAMFORMER: supports Beamformer + * @IWL_UCODE_TLV_CAPA_TDLS_SUPPORT: support basic TDLS functionality + * @IWL_UCODE_TLV_CAPA_TXPOWER_INSERTION_SUPPORT: supports insertion of current + * tx power value into TPC Report action frame and Link Measurement Report + * action frame + * @IWL_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT: supports updating current + * channel in DS parameter set element in probe requests. + * @IWL_UCODE_TLV_CAPA_WFA_TPC_REP_IE_SUPPORT: supports adding TPC Report IE in + * probe requests. + * @IWL_UCODE_TLV_CAPA_QUIET_PERIOD_SUPPORT: supports Quiet Period requests + * @IWL_UCODE_TLV_CAPA_DQA_SUPPORT: supports dynamic queue allocation (DQA), + * which also implies support for the scheduler configuration command + * @IWL_UCODE_TLV_CAPA_TDLS_CHANNEL_SWITCH: supports TDLS channel switching + * @IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG: Consolidated D3-D0 image + * @IWL_UCODE_TLV_CAPA_HOTSPOT_SUPPORT: supports Hot Spot Command + * @IWL_UCODE_TLV_CAPA_CSUM_SUPPORT: supports TCP Checksum Offload + * @IWL_UCODE_TLV_CAPA_RADIO_BEACON_STATS: support radio and beacon statistics + * @IWL_UCODE_TLV_CAPA_P2P_SCM_UAPSD: supports U-APSD on p2p interface when it + * is standalone or with a BSS station interface in the same binding. + * @IWL_UCODE_TLV_CAPA_BT_COEX_PLCR: enabled BT Coex packet level co-running + * @IWL_UCODE_TLV_CAPA_LAR_MULTI_MCC: ucode supports LAR updates with different + * sources for the MCC. This TLV bit is a future replacement to + * IWL_UCODE_TLV_API_WIFI_MCC_UPDATE. When either is set, multi-source LAR + * is supported. + * @IWL_UCODE_TLV_CAPA_BT_COEX_RRC: supports BT Coex RRC + * @IWL_UCODE_TLV_CAPA_GSCAN_SUPPORT: supports gscan (no longer used) + * @IWL_UCODE_TLV_CAPA_SOC_LATENCY_SUPPORT: the firmware supports setting + * stabilization latency for SoCs. + * @IWL_UCODE_TLV_CAPA_STA_PM_NOTIF: firmware will send STA PM notification + * @IWL_UCODE_TLV_CAPA_TLC_OFFLOAD: firmware implements rate scaling algorithm + * @IWL_UCODE_TLV_CAPA_DYNAMIC_QUOTA: firmware implements quota related + * @IWL_UCODE_TLV_CAPA_COEX_SCHEMA_2: firmware implements Coex Schema 2 + * IWL_UCODE_TLV_CAPA_CHANNEL_SWITCH_CMD: firmware supports CSA command + * @IWL_UCODE_TLV_CAPA_ULTRA_HB_CHANNELS: firmware supports ultra high band + * (6 GHz). + * @IWL_UCODE_TLV_CAPA_CS_MODIFY: firmware supports modify action CSA command + * @IWL_UCODE_TLV_CAPA_EXTENDED_DTS_MEASURE: extended DTS measurement + * @IWL_UCODE_TLV_CAPA_SHORT_PM_TIMEOUTS: supports short PM timeouts + * @IWL_UCODE_TLV_CAPA_BT_MPLUT_SUPPORT: supports bt-coex Multi-priority LUT + * @IWL_UCODE_TLV_CAPA_CSA_AND_TBTT_OFFLOAD: the firmware supports CSA + * countdown offloading. Beacon notifications are not sent to the host. + * The fw also offloads TBTT alignment. + * @IWL_UCODE_TLV_CAPA_BEACON_ANT_SELECTION: firmware will decide on what + * antenna the beacon should be transmitted + * @IWL_UCODE_TLV_CAPA_BEACON_STORING: firmware will store the latest beacon + * from AP and will send it upon d0i3 exit. + * @IWL_UCODE_TLV_CAPA_LAR_SUPPORT_V3: support LAR API V3 + * @IWL_UCODE_TLV_CAPA_CT_KILL_BY_FW: firmware responsible for CT-kill + * @IWL_UCODE_TLV_CAPA_TEMP_THS_REPORT_SUPPORT: supports temperature + * thresholds reporting + * @IWL_UCODE_TLV_CAPA_CTDP_SUPPORT: supports cTDP command + * @IWL_UCODE_TLV_CAPA_USNIFFER_UNIFIED: supports usniffer enabled in + * regular image. + * @IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG: support getting more shared + * memory addresses from the firmware. + * @IWL_UCODE_TLV_CAPA_LQM_SUPPORT: supports Link Quality Measurement + * @IWL_UCODE_TLV_CAPA_TX_POWER_ACK: reduced TX power API has larger + * command size (command version 4) that supports toggling ACK TX + * power reduction. + * @IWL_UCODE_TLV_CAPA_D3_DEBUG: supports debug recording during D3 + * @IWL_UCODE_TLV_CAPA_MCC_UPDATE_11AX_SUPPORT: MCC response support 11ax + * capability. + * @IWL_UCODE_TLV_CAPA_CSI_REPORTING: firmware is capable of being configured + * to report the CSI information with (certain) RX frames + * @IWL_UCODE_TLV_CAPA_FTM_CALIBRATED: has FTM calibrated and thus supports both + * initiator and responder + * @IWL_UCODE_TLV_CAPA_MLME_OFFLOAD: supports MLME offload + * @IWL_UCODE_TLV_CAPA_PROTECTED_TWT: Supports protection of TWT action frames + * @IWL_UCODE_TLV_CAPA_FW_RESET_HANDSHAKE: Supports the firmware handshake in + * reset flow + * @IWL_UCODE_TLV_CAPA_PASSIVE_6GHZ_SCAN: Support for passive scan on 6GHz PSC + * channels even when these are not enabled. + * @IWL_UCODE_TLV_CAPA_DUMP_COMPLETE_SUPPORT: Support for indicating dump collection + * complete to FW. + * + * @NUM_IWL_UCODE_TLV_CAPA: number of bits used + */ +enum iwl_ucode_tlv_capa { + /* set 0 */ + IWL_UCODE_TLV_CAPA_D0I3_SUPPORT = (__force iwl_ucode_tlv_capa_t)0, + IWL_UCODE_TLV_CAPA_LAR_SUPPORT = (__force iwl_ucode_tlv_capa_t)1, + IWL_UCODE_TLV_CAPA_UMAC_SCAN = (__force iwl_ucode_tlv_capa_t)2, + IWL_UCODE_TLV_CAPA_BEAMFORMER = (__force iwl_ucode_tlv_capa_t)3, + IWL_UCODE_TLV_CAPA_TDLS_SUPPORT = (__force iwl_ucode_tlv_capa_t)6, + IWL_UCODE_TLV_CAPA_TXPOWER_INSERTION_SUPPORT = (__force iwl_ucode_tlv_capa_t)8, + IWL_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT = (__force iwl_ucode_tlv_capa_t)9, + IWL_UCODE_TLV_CAPA_WFA_TPC_REP_IE_SUPPORT = (__force iwl_ucode_tlv_capa_t)10, + IWL_UCODE_TLV_CAPA_QUIET_PERIOD_SUPPORT = (__force iwl_ucode_tlv_capa_t)11, + IWL_UCODE_TLV_CAPA_DQA_SUPPORT = (__force iwl_ucode_tlv_capa_t)12, + IWL_UCODE_TLV_CAPA_TDLS_CHANNEL_SWITCH = (__force iwl_ucode_tlv_capa_t)13, + IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG = (__force iwl_ucode_tlv_capa_t)17, + IWL_UCODE_TLV_CAPA_HOTSPOT_SUPPORT = (__force iwl_ucode_tlv_capa_t)18, + IWL_UCODE_TLV_CAPA_CSUM_SUPPORT = (__force iwl_ucode_tlv_capa_t)21, + IWL_UCODE_TLV_CAPA_RADIO_BEACON_STATS = (__force iwl_ucode_tlv_capa_t)22, + IWL_UCODE_TLV_CAPA_P2P_SCM_UAPSD = (__force iwl_ucode_tlv_capa_t)26, + IWL_UCODE_TLV_CAPA_BT_COEX_PLCR = (__force iwl_ucode_tlv_capa_t)28, + IWL_UCODE_TLV_CAPA_LAR_MULTI_MCC = (__force iwl_ucode_tlv_capa_t)29, + IWL_UCODE_TLV_CAPA_BT_COEX_RRC = (__force iwl_ucode_tlv_capa_t)30, + IWL_UCODE_TLV_CAPA_GSCAN_SUPPORT = (__force iwl_ucode_tlv_capa_t)31, + + /* set 1 */ + IWL_UCODE_TLV_CAPA_SOC_LATENCY_SUPPORT = (__force iwl_ucode_tlv_capa_t)37, + IWL_UCODE_TLV_CAPA_STA_PM_NOTIF = (__force iwl_ucode_tlv_capa_t)38, + IWL_UCODE_TLV_CAPA_BINDING_CDB_SUPPORT = (__force iwl_ucode_tlv_capa_t)39, + IWL_UCODE_TLV_CAPA_CDB_SUPPORT = (__force iwl_ucode_tlv_capa_t)40, + IWL_UCODE_TLV_CAPA_D0I3_END_FIRST = (__force iwl_ucode_tlv_capa_t)41, + IWL_UCODE_TLV_CAPA_TLC_OFFLOAD = (__force iwl_ucode_tlv_capa_t)43, + IWL_UCODE_TLV_CAPA_DYNAMIC_QUOTA = (__force iwl_ucode_tlv_capa_t)44, + IWL_UCODE_TLV_CAPA_COEX_SCHEMA_2 = (__force iwl_ucode_tlv_capa_t)45, + IWL_UCODE_TLV_CAPA_CHANNEL_SWITCH_CMD = (__force iwl_ucode_tlv_capa_t)46, + IWL_UCODE_TLV_CAPA_FTM_CALIBRATED = (__force iwl_ucode_tlv_capa_t)47, + IWL_UCODE_TLV_CAPA_ULTRA_HB_CHANNELS = (__force iwl_ucode_tlv_capa_t)48, + IWL_UCODE_TLV_CAPA_CS_MODIFY = (__force iwl_ucode_tlv_capa_t)49, + IWL_UCODE_TLV_CAPA_SET_LTR_GEN2 = (__force iwl_ucode_tlv_capa_t)50, + IWL_UCODE_TLV_CAPA_SET_PPAG = (__force iwl_ucode_tlv_capa_t)52, + IWL_UCODE_TLV_CAPA_TAS_CFG = (__force iwl_ucode_tlv_capa_t)53, + IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD = (__force iwl_ucode_tlv_capa_t)54, + IWL_UCODE_TLV_CAPA_PROTECTED_TWT = (__force iwl_ucode_tlv_capa_t)56, + IWL_UCODE_TLV_CAPA_FW_RESET_HANDSHAKE = (__force iwl_ucode_tlv_capa_t)57, + IWL_UCODE_TLV_CAPA_PASSIVE_6GHZ_SCAN = (__force iwl_ucode_tlv_capa_t)58, + IWL_UCODE_TLV_CAPA_HIDDEN_6GHZ_SCAN = (__force iwl_ucode_tlv_capa_t)59, + IWL_UCODE_TLV_CAPA_BROADCAST_TWT = (__force iwl_ucode_tlv_capa_t)60, + IWL_UCODE_TLV_CAPA_COEX_HIGH_PRIO = (__force iwl_ucode_tlv_capa_t)61, + IWL_UCODE_TLV_CAPA_RFIM_SUPPORT = (__force iwl_ucode_tlv_capa_t)62, + IWL_UCODE_TLV_CAPA_BAID_ML_SUPPORT = (__force iwl_ucode_tlv_capa_t)63, + + /* set 2 */ + IWL_UCODE_TLV_CAPA_EXTENDED_DTS_MEASURE = (__force iwl_ucode_tlv_capa_t)64, + IWL_UCODE_TLV_CAPA_SHORT_PM_TIMEOUTS = (__force iwl_ucode_tlv_capa_t)65, + IWL_UCODE_TLV_CAPA_BT_MPLUT_SUPPORT = (__force iwl_ucode_tlv_capa_t)67, + IWL_UCODE_TLV_CAPA_MULTI_QUEUE_RX_SUPPORT = (__force iwl_ucode_tlv_capa_t)68, + IWL_UCODE_TLV_CAPA_CSA_AND_TBTT_OFFLOAD = (__force iwl_ucode_tlv_capa_t)70, + IWL_UCODE_TLV_CAPA_BEACON_ANT_SELECTION = (__force iwl_ucode_tlv_capa_t)71, + IWL_UCODE_TLV_CAPA_BEACON_STORING = (__force iwl_ucode_tlv_capa_t)72, + IWL_UCODE_TLV_CAPA_LAR_SUPPORT_V3 = (__force iwl_ucode_tlv_capa_t)73, + IWL_UCODE_TLV_CAPA_CT_KILL_BY_FW = (__force iwl_ucode_tlv_capa_t)74, + IWL_UCODE_TLV_CAPA_TEMP_THS_REPORT_SUPPORT = (__force iwl_ucode_tlv_capa_t)75, + IWL_UCODE_TLV_CAPA_CTDP_SUPPORT = (__force iwl_ucode_tlv_capa_t)76, + IWL_UCODE_TLV_CAPA_USNIFFER_UNIFIED = (__force iwl_ucode_tlv_capa_t)77, + IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG = (__force iwl_ucode_tlv_capa_t)80, + IWL_UCODE_TLV_CAPA_LQM_SUPPORT = (__force iwl_ucode_tlv_capa_t)81, + IWL_UCODE_TLV_CAPA_TX_POWER_ACK = (__force iwl_ucode_tlv_capa_t)84, + IWL_UCODE_TLV_CAPA_D3_DEBUG = (__force iwl_ucode_tlv_capa_t)87, + IWL_UCODE_TLV_CAPA_LED_CMD_SUPPORT = (__force iwl_ucode_tlv_capa_t)88, + IWL_UCODE_TLV_CAPA_MCC_UPDATE_11AX_SUPPORT = (__force iwl_ucode_tlv_capa_t)89, + IWL_UCODE_TLV_CAPA_CSI_REPORTING = (__force iwl_ucode_tlv_capa_t)90, + IWL_UCODE_TLV_CAPA_DBG_SUSPEND_RESUME_CMD_SUPP = (__force iwl_ucode_tlv_capa_t)92, + IWL_UCODE_TLV_CAPA_DBG_BUF_ALLOC_CMD_SUPP = (__force iwl_ucode_tlv_capa_t)93, + + /* set 3 */ + IWL_UCODE_TLV_CAPA_MLME_OFFLOAD = (__force iwl_ucode_tlv_capa_t)96, + + /* + * @IWL_UCODE_TLV_CAPA_PSC_CHAN_SUPPORT: supports PSC channels + */ + IWL_UCODE_TLV_CAPA_PSC_CHAN_SUPPORT = (__force iwl_ucode_tlv_capa_t)98, + + IWL_UCODE_TLV_CAPA_BIGTK_SUPPORT = (__force iwl_ucode_tlv_capa_t)100, + IWL_UCODE_TLV_CAPA_DRAM_FRAG_SUPPORT = (__force iwl_ucode_tlv_capa_t)104, + IWL_UCODE_TLV_CAPA_DUMP_COMPLETE_SUPPORT = (__force iwl_ucode_tlv_capa_t)105, + +#ifdef __CHECKER__ + /* sparse says it cannot increment the previous enum member */ +#define NUM_IWL_UCODE_TLV_CAPA 128 +#else + NUM_IWL_UCODE_TLV_CAPA +#endif +}; + +/* The default calibrate table size if not specified by firmware file */ +#define IWL_DEFAULT_STANDARD_PHY_CALIBRATE_TBL_SIZE 18 +#define IWL_MAX_STANDARD_PHY_CALIBRATE_TBL_SIZE 19 +#define IWL_MAX_PHY_CALIBRATE_TBL_SIZE 253 + +/* The default max probe length if not specified by the firmware file */ +#define IWL_DEFAULT_MAX_PROBE_LENGTH 200 + +/* + * For 16.0 uCode and above, there is no differentiation between sections, + * just an offset to the HW address. + */ +#define CPU1_CPU2_SEPARATOR_SECTION 0xFFFFCCCC +#define PAGING_SEPARATOR_SECTION 0xAAAABBBB + +/* uCode version contains 4 values: Major/Minor/API/Serial */ +#define IWL_UCODE_MAJOR(ver) (((ver) & 0xFF000000) >> 24) +#define IWL_UCODE_MINOR(ver) (((ver) & 0x00FF0000) >> 16) +#define IWL_UCODE_API(ver) (((ver) & 0x0000FF00) >> 8) +#define IWL_UCODE_SERIAL(ver) ((ver) & 0x000000FF) + +/** + * struct iwl_tlv_calib_ctrl - Calibration control struct. + * Sent as part of the phy configuration command. + * @flow_trigger: bitmap for which calibrations to perform according to + * flow triggers. + * @event_trigger: bitmap for which calibrations to perform according to + * event triggers. + */ +struct iwl_tlv_calib_ctrl { + __le32 flow_trigger; + __le32 event_trigger; +} __packed; + +enum iwl_fw_phy_cfg { + FW_PHY_CFG_RADIO_TYPE_POS = 0, + FW_PHY_CFG_RADIO_TYPE = 0x3 << FW_PHY_CFG_RADIO_TYPE_POS, + FW_PHY_CFG_RADIO_STEP_POS = 2, + FW_PHY_CFG_RADIO_STEP = 0x3 << FW_PHY_CFG_RADIO_STEP_POS, + FW_PHY_CFG_RADIO_DASH_POS = 4, + FW_PHY_CFG_RADIO_DASH = 0x3 << FW_PHY_CFG_RADIO_DASH_POS, + FW_PHY_CFG_TX_CHAIN_POS = 16, + FW_PHY_CFG_TX_CHAIN = 0xf << FW_PHY_CFG_TX_CHAIN_POS, + FW_PHY_CFG_RX_CHAIN_POS = 20, + FW_PHY_CFG_RX_CHAIN = 0xf << FW_PHY_CFG_RX_CHAIN_POS, + FW_PHY_CFG_CHAIN_SAD_POS = 23, + FW_PHY_CFG_CHAIN_SAD_ENABLED = 0x1 << FW_PHY_CFG_CHAIN_SAD_POS, + FW_PHY_CFG_CHAIN_SAD_ANT_A = 0x2 << FW_PHY_CFG_CHAIN_SAD_POS, + FW_PHY_CFG_CHAIN_SAD_ANT_B = 0x4 << FW_PHY_CFG_CHAIN_SAD_POS, + FW_PHY_CFG_SHARED_CLK = BIT(31), +}; + +enum iwl_fw_dbg_reg_operator { + CSR_ASSIGN, + CSR_SETBIT, + CSR_CLEARBIT, + + PRPH_ASSIGN, + PRPH_SETBIT, + PRPH_CLEARBIT, + + INDIRECT_ASSIGN, + INDIRECT_SETBIT, + INDIRECT_CLEARBIT, + + PRPH_BLOCKBIT, +}; + +/** + * struct iwl_fw_dbg_reg_op - an operation on a register + * + * @op: &enum iwl_fw_dbg_reg_operator + * @addr: offset of the register + * @val: value + */ +struct iwl_fw_dbg_reg_op { + u8 op; + u8 reserved[3]; + __le32 addr; + __le32 val; +} __packed; + +/** + * enum iwl_fw_dbg_monitor_mode - available monitor recording modes + * + * @SMEM_MODE: monitor stores the data in SMEM + * @EXTERNAL_MODE: monitor stores the data in allocated DRAM + * @MARBH_MODE: monitor stores the data in MARBH buffer + * @MIPI_MODE: monitor outputs the data through the MIPI interface + */ +enum iwl_fw_dbg_monitor_mode { + SMEM_MODE = 0, + EXTERNAL_MODE = 1, + MARBH_MODE = 2, + MIPI_MODE = 3, +}; + +/** + * struct iwl_fw_dbg_mem_seg_tlv - configures the debug data memory segments + * + * @data_type: the memory segment type to record + * @ofs: the memory segment offset + * @len: the memory segment length, in bytes + * + * This parses IWL_UCODE_TLV_FW_MEM_SEG + */ +struct iwl_fw_dbg_mem_seg_tlv { + __le32 data_type; + __le32 ofs; + __le32 len; +} __packed; + +/** + * struct iwl_fw_dbg_dest_tlv_v1 - configures the destination of the debug data + * + * @version: version of the TLV - currently 0 + * @monitor_mode: &enum iwl_fw_dbg_monitor_mode + * @size_power: buffer size will be 2^(size_power + 11) + * @base_reg: addr of the base addr register (PRPH) + * @end_reg: addr of the end addr register (PRPH) + * @write_ptr_reg: the addr of the reg of the write pointer + * @wrap_count: the addr of the reg of the wrap_count + * @base_shift: shift right of the base addr reg + * @end_shift: shift right of the end addr reg + * @reg_ops: array of registers operations + * + * This parses IWL_UCODE_TLV_FW_DBG_DEST + */ +struct iwl_fw_dbg_dest_tlv_v1 { + u8 version; + u8 monitor_mode; + u8 size_power; + u8 reserved; + __le32 base_reg; + __le32 end_reg; + __le32 write_ptr_reg; + __le32 wrap_count; + u8 base_shift; + u8 end_shift; + struct iwl_fw_dbg_reg_op reg_ops[]; +} __packed; + +/* Mask of the register for defining the LDBG MAC2SMEM buffer SMEM size */ +#define IWL_LDBG_M2S_BUF_SIZE_MSK 0x0fff0000 +/* Mask of the register for defining the LDBG MAC2SMEM SMEM base address */ +#define IWL_LDBG_M2S_BUF_BA_MSK 0x00000fff +/* The smem buffer chunks are in units of 256 bits */ +#define IWL_M2S_UNIT_SIZE 0x100 + +struct iwl_fw_dbg_dest_tlv { + u8 version; + u8 monitor_mode; + u8 size_power; + u8 reserved; + __le32 cfg_reg; + __le32 write_ptr_reg; + __le32 wrap_count; + u8 base_shift; + u8 size_shift; + struct iwl_fw_dbg_reg_op reg_ops[]; +} __packed; + +struct iwl_fw_dbg_conf_hcmd { + u8 id; + u8 reserved; + __le16 len; + u8 data[]; +} __packed; + +/** + * enum iwl_fw_dbg_trigger_mode - triggers functionalities + * + * @IWL_FW_DBG_TRIGGER_START: when trigger occurs re-conf the dbg mechanism + * @IWL_FW_DBG_TRIGGER_STOP: when trigger occurs pull the dbg data + * @IWL_FW_DBG_TRIGGER_MONITOR_ONLY: when trigger occurs trigger is set to + * collect only monitor data + */ +enum iwl_fw_dbg_trigger_mode { + IWL_FW_DBG_TRIGGER_START = BIT(0), + IWL_FW_DBG_TRIGGER_STOP = BIT(1), + IWL_FW_DBG_TRIGGER_MONITOR_ONLY = BIT(2), +}; + +/** + * enum iwl_fw_dbg_trigger_flags - the flags supported by wrt triggers + * @IWL_FW_DBG_FORCE_RESTART: force a firmware restart + */ +enum iwl_fw_dbg_trigger_flags { + IWL_FW_DBG_FORCE_RESTART = BIT(0), +}; + +/** + * enum iwl_fw_dbg_trigger_vif_type - define the VIF type for a trigger + * @IWL_FW_DBG_CONF_VIF_ANY: any vif type + * @IWL_FW_DBG_CONF_VIF_IBSS: IBSS mode + * @IWL_FW_DBG_CONF_VIF_STATION: BSS mode + * @IWL_FW_DBG_CONF_VIF_AP: AP mode + * @IWL_FW_DBG_CONF_VIF_P2P_CLIENT: P2P Client mode + * @IWL_FW_DBG_CONF_VIF_P2P_GO: P2P GO mode + * @IWL_FW_DBG_CONF_VIF_P2P_DEVICE: P2P device + */ +enum iwl_fw_dbg_trigger_vif_type { + IWL_FW_DBG_CONF_VIF_ANY = NL80211_IFTYPE_UNSPECIFIED, + IWL_FW_DBG_CONF_VIF_IBSS = NL80211_IFTYPE_ADHOC, + IWL_FW_DBG_CONF_VIF_STATION = NL80211_IFTYPE_STATION, + IWL_FW_DBG_CONF_VIF_AP = NL80211_IFTYPE_AP, + IWL_FW_DBG_CONF_VIF_P2P_CLIENT = NL80211_IFTYPE_P2P_CLIENT, + IWL_FW_DBG_CONF_VIF_P2P_GO = NL80211_IFTYPE_P2P_GO, + IWL_FW_DBG_CONF_VIF_P2P_DEVICE = NL80211_IFTYPE_P2P_DEVICE, +}; + +/** + * struct iwl_fw_dbg_trigger_tlv - a TLV that describes the trigger + * @id: &enum iwl_fw_dbg_trigger + * @vif_type: &enum iwl_fw_dbg_trigger_vif_type + * @stop_conf_ids: bitmap of configurations this trigger relates to. + * if the mode is %IWL_FW_DBG_TRIGGER_STOP, then if the bit corresponding + * to the currently running configuration is set, the data should be + * collected. + * @stop_delay: how many milliseconds to wait before collecting the data + * after the STOP trigger fires. + * @mode: &enum iwl_fw_dbg_trigger_mode - can be stop / start of both + * @start_conf_id: if mode is %IWL_FW_DBG_TRIGGER_START, this defines what + * configuration should be applied when the triggers kicks in. + * @occurrences: number of occurrences. 0 means the trigger will never fire. + * @trig_dis_ms: the time, in milliseconds, after an occurrence of this + * trigger in which another occurrence should be ignored. + * @flags: &enum iwl_fw_dbg_trigger_flags + */ +struct iwl_fw_dbg_trigger_tlv { + __le32 id; + __le32 vif_type; + __le32 stop_conf_ids; + __le32 stop_delay; + u8 mode; + u8 start_conf_id; + __le16 occurrences; + __le16 trig_dis_ms; + u8 flags; + u8 reserved[5]; + + u8 data[]; +} __packed; + +#define FW_DBG_START_FROM_ALIVE 0 +#define FW_DBG_CONF_MAX 32 +#define FW_DBG_INVALID 0xff + +/** + * struct iwl_fw_dbg_trigger_missed_bcon - configures trigger for missed beacons + * @stop_consec_missed_bcon: stop recording if threshold is crossed. + * @stop_consec_missed_bcon_since_rx: stop recording if threshold is crossed. + * @start_consec_missed_bcon: start recording if threshold is crossed. + * @start_consec_missed_bcon_since_rx: start recording if threshold is crossed. + * @reserved1: reserved + * @reserved2: reserved + */ +struct iwl_fw_dbg_trigger_missed_bcon { + __le32 stop_consec_missed_bcon; + __le32 stop_consec_missed_bcon_since_rx; + __le32 reserved2[2]; + __le32 start_consec_missed_bcon; + __le32 start_consec_missed_bcon_since_rx; + __le32 reserved1[2]; +} __packed; + +/** + * struct iwl_fw_dbg_trigger_cmd - configures trigger for messages from FW. + * cmds: the list of commands to trigger the collection on + */ +struct iwl_fw_dbg_trigger_cmd { + struct cmd { + u8 cmd_id; + u8 group_id; + } __packed cmds[16]; +} __packed; + +/** + * iwl_fw_dbg_trigger_stats - configures trigger for statistics + * @stop_offset: the offset of the value to be monitored + * @stop_threshold: the threshold above which to collect + * @start_offset: the offset of the value to be monitored + * @start_threshold: the threshold above which to start recording + */ +struct iwl_fw_dbg_trigger_stats { + __le32 stop_offset; + __le32 stop_threshold; + __le32 start_offset; + __le32 start_threshold; +} __packed; + +/** + * struct iwl_fw_dbg_trigger_low_rssi - trigger for low beacon RSSI + * @rssi: RSSI value to trigger at + */ +struct iwl_fw_dbg_trigger_low_rssi { + __le32 rssi; +} __packed; + +/** + * struct iwl_fw_dbg_trigger_mlme - configures trigger for mlme events + * @stop_auth_denied: number of denied authentication to collect + * @stop_auth_timeout: number of authentication timeout to collect + * @stop_rx_deauth: number of Rx deauth before to collect + * @stop_tx_deauth: number of Tx deauth before to collect + * @stop_assoc_denied: number of denied association to collect + * @stop_assoc_timeout: number of association timeout to collect + * @stop_connection_loss: number of connection loss to collect + * @start_auth_denied: number of denied authentication to start recording + * @start_auth_timeout: number of authentication timeout to start recording + * @start_rx_deauth: number of Rx deauth to start recording + * @start_tx_deauth: number of Tx deauth to start recording + * @start_assoc_denied: number of denied association to start recording + * @start_assoc_timeout: number of association timeout to start recording + * @start_connection_loss: number of connection loss to start recording + */ +struct iwl_fw_dbg_trigger_mlme { + u8 stop_auth_denied; + u8 stop_auth_timeout; + u8 stop_rx_deauth; + u8 stop_tx_deauth; + + u8 stop_assoc_denied; + u8 stop_assoc_timeout; + u8 stop_connection_loss; + u8 reserved; + + u8 start_auth_denied; + u8 start_auth_timeout; + u8 start_rx_deauth; + u8 start_tx_deauth; + + u8 start_assoc_denied; + u8 start_assoc_timeout; + u8 start_connection_loss; + u8 reserved2; +} __packed; + +/** + * struct iwl_fw_dbg_trigger_txq_timer - configures the Tx queue's timer + * @command_queue: timeout for the command queue in ms + * @bss: timeout for the queues of a BSS (except for TDLS queues) in ms + * @softap: timeout for the queues of a softAP in ms + * @p2p_go: timeout for the queues of a P2P GO in ms + * @p2p_client: timeout for the queues of a P2P client in ms + * @p2p_device: timeout for the queues of a P2P device in ms + * @ibss: timeout for the queues of an IBSS in ms + * @tdls: timeout for the queues of a TDLS station in ms + */ +struct iwl_fw_dbg_trigger_txq_timer { + __le32 command_queue; + __le32 bss; + __le32 softap; + __le32 p2p_go; + __le32 p2p_client; + __le32 p2p_device; + __le32 ibss; + __le32 tdls; + __le32 reserved[4]; +} __packed; + +/** + * struct iwl_fw_dbg_trigger_time_event - configures a time event trigger + * time_Events: a list of tuples <id, action_bitmap>. The driver will issue a + * trigger each time a time event notification that relates to time event + * id with one of the actions in the bitmap is received and + * BIT(notif->status) is set in status_bitmap. + * + */ +struct iwl_fw_dbg_trigger_time_event { + struct { + __le32 id; + __le32 action_bitmap; + __le32 status_bitmap; + } __packed time_events[16]; +} __packed; + +/** + * struct iwl_fw_dbg_trigger_ba - configures BlockAck related trigger + * rx_ba_start: tid bitmap to configure on what tid the trigger should occur + * when an Rx BlockAck session is started. + * rx_ba_stop: tid bitmap to configure on what tid the trigger should occur + * when an Rx BlockAck session is stopped. + * tx_ba_start: tid bitmap to configure on what tid the trigger should occur + * when a Tx BlockAck session is started. + * tx_ba_stop: tid bitmap to configure on what tid the trigger should occur + * when a Tx BlockAck session is stopped. + * rx_bar: tid bitmap to configure on what tid the trigger should occur + * when a BAR is received (for a Tx BlockAck session). + * tx_bar: tid bitmap to configure on what tid the trigger should occur + * when a BAR is send (for an Rx BlocAck session). + * frame_timeout: tid bitmap to configure on what tid the trigger should occur + * when a frame times out in the reordering buffer. + */ +struct iwl_fw_dbg_trigger_ba { + __le16 rx_ba_start; + __le16 rx_ba_stop; + __le16 tx_ba_start; + __le16 tx_ba_stop; + __le16 rx_bar; + __le16 tx_bar; + __le16 frame_timeout; +} __packed; + +/** + * struct iwl_fw_dbg_trigger_tdls - configures trigger for TDLS events. + * @action_bitmap: the TDLS action to trigger the collection upon + * @peer_mode: trigger on specific peer or all + * @peer: the TDLS peer to trigger the collection on + */ +struct iwl_fw_dbg_trigger_tdls { + u8 action_bitmap; + u8 peer_mode; + u8 peer[ETH_ALEN]; + u8 reserved[4]; +} __packed; + +/** + * struct iwl_fw_dbg_trigger_tx_status - configures trigger for tx response + * status. + * @statuses: the list of statuses to trigger the collection on + */ +struct iwl_fw_dbg_trigger_tx_status { + struct tx_status { + u8 status; + u8 reserved[3]; + } __packed statuses[16]; + __le32 reserved[2]; +} __packed; + +/** + * struct iwl_fw_dbg_conf_tlv - a TLV that describes a debug configuration. + * @id: conf id + * @usniffer: should the uSniffer image be used + * @num_of_hcmds: how many HCMDs to send are present here + * @hcmd: a variable length host command to be sent to apply the configuration. + * If there is more than one HCMD to send, they will appear one after the + * other and be sent in the order that they appear in. + * This parses IWL_UCODE_TLV_FW_DBG_CONF. The user can add up-to + * %FW_DBG_CONF_MAX configuration per run. + */ +struct iwl_fw_dbg_conf_tlv { + u8 id; + u8 usniffer; + u8 reserved; + u8 num_of_hcmds; + struct iwl_fw_dbg_conf_hcmd hcmd; +} __packed; + +#define IWL_FW_CMD_VER_UNKNOWN 99 + +/** + * struct iwl_fw_cmd_version - firmware command version entry + * @cmd: command ID + * @group: group ID + * @cmd_ver: command version + * @notif_ver: notification version + */ +struct iwl_fw_cmd_version { + u8 cmd; + u8 group; + u8 cmd_ver; + u8 notif_ver; +} __packed; + +struct iwl_fw_tcm_error_addr { + __le32 addr; +}; /* FW_TLV_TCM_ERROR_INFO_ADDRS_S */ + +struct iwl_fw_dump_exclude { + __le32 addr, size; +}; + +static inline size_t _iwl_tlv_array_len(const struct iwl_ucode_tlv *tlv, + size_t fixed_size, size_t var_size) +{ + size_t var_len = le32_to_cpu(tlv->length) - fixed_size; + + if (WARN_ON(var_len % var_size)) + return 0; + + return var_len / var_size; +} + +#define iwl_tlv_array_len(_tlv_ptr, _struct_ptr, _memb) \ + _iwl_tlv_array_len((_tlv_ptr), sizeof(*(_struct_ptr)), \ + sizeof(_struct_ptr->_memb[0])) + +#endif /* __iwl_fw_file_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/img.c b/drivers/net/wireless/intel/iwlwifi/fw/img.c new file mode 100644 index 000000000..b7deca05a --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/img.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright(c) 2019 - 2021 Intel Corporation + */ +#include <fw/api/commands.h> +#include "img.h" + +u8 iwl_fw_lookup_cmd_ver(const struct iwl_fw *fw, u32 cmd_id, u8 def) +{ + const struct iwl_fw_cmd_version *entry; + unsigned int i; + /* prior to LONG_GROUP, we never used this CMD version API */ + u8 grp = iwl_cmd_groupid(cmd_id) ?: LONG_GROUP; + u8 cmd = iwl_cmd_opcode(cmd_id); + + if (!fw->ucode_capa.cmd_versions || + !fw->ucode_capa.n_cmd_versions) + return def; + + entry = fw->ucode_capa.cmd_versions; + for (i = 0; i < fw->ucode_capa.n_cmd_versions; i++, entry++) { + if (entry->group == grp && entry->cmd == cmd) { + if (entry->cmd_ver == IWL_FW_CMD_VER_UNKNOWN) + return def; + return entry->cmd_ver; + } + } + + return def; +} +EXPORT_SYMBOL_GPL(iwl_fw_lookup_cmd_ver); + +u8 iwl_fw_lookup_notif_ver(const struct iwl_fw *fw, u8 grp, u8 cmd, u8 def) +{ + const struct iwl_fw_cmd_version *entry; + unsigned int i; + + if (!fw->ucode_capa.cmd_versions || + !fw->ucode_capa.n_cmd_versions) + return def; + + entry = fw->ucode_capa.cmd_versions; + for (i = 0; i < fw->ucode_capa.n_cmd_versions; i++, entry++) { + if (entry->group == grp && entry->cmd == cmd) { + if (entry->notif_ver == IWL_FW_CMD_VER_UNKNOWN) + return def; + return entry->notif_ver; + } + } + + return def; +} +EXPORT_SYMBOL_GPL(iwl_fw_lookup_notif_ver); + +static const struct { + const char *name; + u32 num; +} advanced_lookup[] = { + { "NMI_INTERRUPT_WDG", 0x34 }, + { "SYSASSERT", 0x35 }, + { "UCODE_VERSION_MISMATCH", 0x37 }, + { "BAD_COMMAND", 0x38 }, + { "BAD_COMMAND", 0x39 }, + { "NMI_INTERRUPT_DATA_ACTION_PT", 0x3C }, + { "FATAL_ERROR", 0x3D }, + { "NMI_TRM_HW_ERR", 0x46 }, + { "NMI_INTERRUPT_TRM", 0x4C }, + { "NMI_INTERRUPT_BREAK_POINT", 0x54 }, + { "NMI_INTERRUPT_WDG_RXF_FULL", 0x5C }, + { "NMI_INTERRUPT_WDG_NO_RBD_RXF_FULL", 0x64 }, + { "NMI_INTERRUPT_HOST", 0x66 }, + { "NMI_INTERRUPT_LMAC_FATAL", 0x70 }, + { "NMI_INTERRUPT_UMAC_FATAL", 0x71 }, + { "NMI_INTERRUPT_OTHER_LMAC_FATAL", 0x73 }, + { "NMI_INTERRUPT_ACTION_PT", 0x7C }, + { "NMI_INTERRUPT_UNKNOWN", 0x84 }, + { "NMI_INTERRUPT_INST_ACTION_PT", 0x86 }, + { "PNVM_MISSING", FW_SYSASSERT_PNVM_MISSING }, + { "ADVANCED_SYSASSERT", 0 }, +}; + +const char *iwl_fw_lookup_assert_desc(u32 num) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(advanced_lookup) - 1; i++) + if (advanced_lookup[i].num == (num & ~FW_SYSASSERT_CPU_MASK)) + return advanced_lookup[i].name; + + /* No entry matches 'num', so it is the last: ADVANCED_SYSASSERT */ + return advanced_lookup[i].name; +} +EXPORT_SYMBOL_GPL(iwl_fw_lookup_assert_desc); diff --git a/drivers/net/wireless/intel/iwlwifi/fw/img.h b/drivers/net/wireless/intel/iwlwifi/fw/img.h new file mode 100644 index 000000000..f878ac508 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/img.h @@ -0,0 +1,274 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016 Intel Deutschland GmbH + */ +#ifndef __iwl_fw_img_h__ +#define __iwl_fw_img_h__ +#include <linux/types.h> + +#include "api/dbg-tlv.h" + +#include "file.h" +#include "error-dump.h" + +/** + * enum iwl_ucode_type + * + * The type of ucode. + * + * @IWL_UCODE_REGULAR: Normal runtime ucode + * @IWL_UCODE_INIT: Initial ucode + * @IWL_UCODE_WOWLAN: Wake on Wireless enabled ucode + * @IWL_UCODE_REGULAR_USNIFFER: Normal runtime ucode when using usniffer image + */ +enum iwl_ucode_type { + IWL_UCODE_REGULAR, + IWL_UCODE_INIT, + IWL_UCODE_WOWLAN, + IWL_UCODE_REGULAR_USNIFFER, + IWL_UCODE_TYPE_MAX, +}; + +/* + * enumeration of ucode section. + * This enumeration is used directly for older firmware (before 16.0). + * For new firmware, there can be up to 4 sections (see below) but the + * first one packaged into the firmware file is the DATA section and + * some debugging code accesses that. + */ +enum iwl_ucode_sec { + IWL_UCODE_SECTION_DATA, + IWL_UCODE_SECTION_INST, +}; + +struct iwl_ucode_capabilities { + u32 max_probe_length; + u32 n_scan_channels; + u32 standard_phy_calibration_size; + u32 flags; + u32 error_log_addr; + u32 error_log_size; + u32 num_stations; + unsigned long _api[BITS_TO_LONGS(NUM_IWL_UCODE_TLV_API)]; + unsigned long _capa[BITS_TO_LONGS(NUM_IWL_UCODE_TLV_CAPA)]; + + const struct iwl_fw_cmd_version *cmd_versions; + u32 n_cmd_versions; +}; + +static inline bool +fw_has_api(const struct iwl_ucode_capabilities *capabilities, + iwl_ucode_tlv_api_t api) +{ + return test_bit((__force long)api, capabilities->_api); +} + +static inline bool +fw_has_capa(const struct iwl_ucode_capabilities *capabilities, + iwl_ucode_tlv_capa_t capa) +{ + return test_bit((__force long)capa, capabilities->_capa); +} + +/* one for each uCode image (inst/data, init/runtime/wowlan) */ +struct fw_desc { + const void *data; /* vmalloc'ed data */ + u32 len; /* size in bytes */ + u32 offset; /* offset in the device */ +}; + +struct fw_img { + struct fw_desc *sec; + int num_sec; + bool is_dual_cpus; + u32 paging_mem_size; +}; + +/* + * Block paging calculations + */ +#define PAGE_2_EXP_SIZE 12 /* 4K == 2^12 */ +#define FW_PAGING_SIZE BIT(PAGE_2_EXP_SIZE) /* page size is 4KB */ +#define PAGE_PER_GROUP_2_EXP_SIZE 3 +/* 8 pages per group */ +#define NUM_OF_PAGE_PER_GROUP BIT(PAGE_PER_GROUP_2_EXP_SIZE) +/* don't change, support only 32KB size */ +#define PAGING_BLOCK_SIZE (NUM_OF_PAGE_PER_GROUP * FW_PAGING_SIZE) +/* 32K == 2^15 */ +#define BLOCK_2_EXP_SIZE (PAGE_2_EXP_SIZE + PAGE_PER_GROUP_2_EXP_SIZE) + +/* + * Image paging calculations + */ +#define BLOCK_PER_IMAGE_2_EXP_SIZE 5 +/* 2^5 == 32 blocks per image */ +#define NUM_OF_BLOCK_PER_IMAGE BIT(BLOCK_PER_IMAGE_2_EXP_SIZE) +/* maximum image size 1024KB */ +#define MAX_PAGING_IMAGE_SIZE (NUM_OF_BLOCK_PER_IMAGE * PAGING_BLOCK_SIZE) + +/* Virtual address signature */ +#define PAGING_ADDR_SIG 0xAA000000 + +#define PAGING_CMD_IS_SECURED BIT(9) +#define PAGING_CMD_IS_ENABLED BIT(8) +#define PAGING_CMD_NUM_OF_PAGES_IN_LAST_GRP_POS 0 +#define PAGING_TLV_SECURE_MASK 1 + +/* FW MSB Mask for regions/cache_control */ +#define FW_ADDR_CACHE_CONTROL 0xC0000000UL + +/** + * struct iwl_fw_paging + * @fw_paging_phys: page phy pointer + * @fw_paging_block: pointer to the allocated block + * @fw_paging_size: page size + * @fw_offs: offset in the device + */ +struct iwl_fw_paging { + dma_addr_t fw_paging_phys; + struct page *fw_paging_block; + u32 fw_paging_size; + u32 fw_offs; +}; + +/** + * enum iwl_fw_type - iwlwifi firmware type + * @IWL_FW_DVM: DVM firmware + * @IWL_FW_MVM: MVM firmware + */ +enum iwl_fw_type { + IWL_FW_DVM, + IWL_FW_MVM, +}; + +/** + * struct iwl_fw_dbg - debug data + * + * @dest_tlv: points to debug destination TLV (typically SRAM or DRAM) + * @n_dest_reg: num of reg_ops in dest_tlv + * @conf_tlv: array of pointers to configuration HCMDs + * @trigger_tlv: array of pointers to triggers TLVs + * @trigger_tlv_len: lengths of the @dbg_trigger_tlv entries + * @mem_tlv: Runtime addresses to dump + * @n_mem_tlv: number of runtime addresses + * @dump_mask: bitmask of dump regions +*/ +struct iwl_fw_dbg { + struct iwl_fw_dbg_dest_tlv_v1 *dest_tlv; + u8 n_dest_reg; + struct iwl_fw_dbg_conf_tlv *conf_tlv[FW_DBG_CONF_MAX]; + struct iwl_fw_dbg_trigger_tlv *trigger_tlv[FW_DBG_TRIGGER_MAX]; + size_t trigger_tlv_len[FW_DBG_TRIGGER_MAX]; + struct iwl_fw_dbg_mem_seg_tlv *mem_tlv; + size_t n_mem_tlv; + u32 dump_mask; +}; + +struct iwl_dump_exclude { + u32 addr, size; +}; + +/** + * struct iwl_fw - variables associated with the firmware + * + * @ucode_ver: ucode version from the ucode file + * @fw_version: firmware version string + * @img: ucode image like ucode_rt, ucode_init, ucode_wowlan. + * @iml_len: length of the image loader image + * @iml: image loader fw image + * @ucode_capa: capabilities parsed from the ucode file. + * @enhance_sensitivity_table: device can do enhanced sensitivity. + * @init_evtlog_ptr: event log offset for init ucode. + * @init_evtlog_size: event log size for init ucode. + * @init_errlog_ptr: error log offfset for init ucode. + * @inst_evtlog_ptr: event log offset for runtime ucode. + * @inst_evtlog_size: event log size for runtime ucode. + * @inst_errlog_ptr: error log offfset for runtime ucode. + * @type: firmware type (&enum iwl_fw_type) + * @human_readable: human readable version + * we get the ALIVE from the uCode + * @phy_integration_ver: PHY integration version string + * @phy_integration_ver_len: length of @phy_integration_ver + * @dump_excl: image dump exclusion areas for RT image + * @dump_excl_wowlan: image dump exclusion areas for WoWLAN image + */ +struct iwl_fw { + u32 ucode_ver; + + char fw_version[64]; + + /* ucode images */ + struct fw_img img[IWL_UCODE_TYPE_MAX]; + size_t iml_len; + u8 *iml; + + struct iwl_ucode_capabilities ucode_capa; + bool enhance_sensitivity_table; + + u32 init_evtlog_ptr, init_evtlog_size, init_errlog_ptr; + u32 inst_evtlog_ptr, inst_evtlog_size, inst_errlog_ptr; + + struct iwl_tlv_calib_ctrl default_calib[IWL_UCODE_TYPE_MAX]; + u32 phy_config; + u8 valid_tx_ant; + u8 valid_rx_ant; + + enum iwl_fw_type type; + + u8 human_readable[FW_VER_HUMAN_READABLE_SZ]; + + struct iwl_fw_dbg dbg; + + u8 *phy_integration_ver; + u32 phy_integration_ver_len; + + struct iwl_dump_exclude dump_excl[2], dump_excl_wowlan[2]; +}; + +static inline const char *get_fw_dbg_mode_string(int mode) +{ + switch (mode) { + case SMEM_MODE: + return "SMEM"; + case EXTERNAL_MODE: + return "EXTERNAL_DRAM"; + case MARBH_MODE: + return "MARBH"; + case MIPI_MODE: + return "MIPI"; + default: + return "UNKNOWN"; + } +} + +static inline bool +iwl_fw_dbg_conf_usniffer(const struct iwl_fw *fw, u8 id) +{ + const struct iwl_fw_dbg_conf_tlv *conf_tlv = fw->dbg.conf_tlv[id]; + + if (!conf_tlv) + return false; + + return conf_tlv->usniffer; +} + +static inline const struct fw_img * +iwl_get_ucode_image(const struct iwl_fw *fw, enum iwl_ucode_type ucode_type) +{ + if (ucode_type >= IWL_UCODE_TYPE_MAX) + return NULL; + + return &fw->img[ucode_type]; +} + +u8 iwl_fw_lookup_cmd_ver(const struct iwl_fw *fw, u32 cmd_id, u8 def); + +u8 iwl_fw_lookup_notif_ver(const struct iwl_fw *fw, u8 grp, u8 cmd, u8 def); +const char *iwl_fw_lookup_assert_desc(u32 num); + +#define FW_SYSASSERT_CPU_MASK 0xf0000000 +#define FW_SYSASSERT_PNVM_MISSING 0x0010070d + +#endif /* __iwl_fw_img_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/init.c b/drivers/net/wireless/intel/iwlwifi/fw/init.c new file mode 100644 index 000000000..135bd48bf --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/init.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2019-2021 Intel Corporation + */ +#include "iwl-drv.h" +#include "runtime.h" +#include "dbg.h" +#include "debugfs.h" + +#include "fw/api/system.h" +#include "fw/api/commands.h" +#include "fw/api/rx.h" +#include "fw/api/datapath.h" + +void iwl_fw_runtime_init(struct iwl_fw_runtime *fwrt, struct iwl_trans *trans, + const struct iwl_fw *fw, + const struct iwl_fw_runtime_ops *ops, void *ops_ctx, + const struct iwl_dump_sanitize_ops *sanitize_ops, + void *sanitize_ctx, + struct dentry *dbgfs_dir) +{ + int i; + + memset(fwrt, 0, sizeof(*fwrt)); + fwrt->trans = trans; + fwrt->fw = fw; + fwrt->dev = trans->dev; + fwrt->dump.conf = FW_DBG_INVALID; + fwrt->ops = ops; + fwrt->sanitize_ops = sanitize_ops; + fwrt->sanitize_ctx = sanitize_ctx; + fwrt->ops_ctx = ops_ctx; + for (i = 0; i < IWL_FW_RUNTIME_DUMP_WK_NUM; i++) { + fwrt->dump.wks[i].idx = i; + INIT_DELAYED_WORK(&fwrt->dump.wks[i].wk, iwl_fw_error_dump_wk); + } + iwl_fwrt_dbgfs_register(fwrt, dbgfs_dir); +} +IWL_EXPORT_SYMBOL(iwl_fw_runtime_init); + +void iwl_fw_runtime_suspend(struct iwl_fw_runtime *fwrt) +{ + iwl_fw_suspend_timestamp(fwrt); + iwl_dbg_tlv_time_point(fwrt, IWL_FW_INI_TIME_POINT_HOST_D3_START, NULL); +} +IWL_EXPORT_SYMBOL(iwl_fw_runtime_suspend); + +void iwl_fw_runtime_resume(struct iwl_fw_runtime *fwrt) +{ + iwl_dbg_tlv_time_point(fwrt, IWL_FW_INI_TIME_POINT_HOST_D3_END, NULL); + iwl_fw_resume_timestamp(fwrt); +} +IWL_EXPORT_SYMBOL(iwl_fw_runtime_resume); + +/* set device type and latency */ +int iwl_set_soc_latency(struct iwl_fw_runtime *fwrt) +{ + struct iwl_soc_configuration_cmd cmd = {}; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(SYSTEM_GROUP, SOC_CONFIGURATION_CMD), + .data[0] = &cmd, + .len[0] = sizeof(cmd), + }; + int ret; + + /* + * In VER_1 of this command, the discrete value is considered + * an integer; In VER_2, it's a bitmask. Since we have only 2 + * values in VER_1, this is backwards-compatible with VER_2, + * as long as we don't set any other bits. + */ + if (!fwrt->trans->trans_cfg->integrated) + cmd.flags = cpu_to_le32(SOC_CONFIG_CMD_FLAGS_DISCRETE); + + BUILD_BUG_ON(IWL_CFG_TRANS_LTR_DELAY_NONE != + SOC_FLAGS_LTR_APPLY_DELAY_NONE); + BUILD_BUG_ON(IWL_CFG_TRANS_LTR_DELAY_200US != + SOC_FLAGS_LTR_APPLY_DELAY_200); + BUILD_BUG_ON(IWL_CFG_TRANS_LTR_DELAY_2500US != + SOC_FLAGS_LTR_APPLY_DELAY_2500); + BUILD_BUG_ON(IWL_CFG_TRANS_LTR_DELAY_1820US != + SOC_FLAGS_LTR_APPLY_DELAY_1820); + + if (fwrt->trans->trans_cfg->ltr_delay != IWL_CFG_TRANS_LTR_DELAY_NONE && + !WARN_ON(!fwrt->trans->trans_cfg->integrated)) + cmd.flags |= le32_encode_bits(fwrt->trans->trans_cfg->ltr_delay, + SOC_FLAGS_LTR_APPLY_DELAY_MASK); + + if (iwl_fw_lookup_cmd_ver(fwrt->fw, SCAN_REQ_UMAC, + IWL_FW_CMD_VER_UNKNOWN) >= 2 && + fwrt->trans->trans_cfg->low_latency_xtal) + cmd.flags |= cpu_to_le32(SOC_CONFIG_CMD_FLAGS_LOW_LATENCY); + + cmd.latency = cpu_to_le32(fwrt->trans->trans_cfg->xtal_latency); + + ret = iwl_trans_send_cmd(fwrt->trans, &hcmd); + if (ret) + IWL_ERR(fwrt, "Failed to set soc latency: %d\n", ret); + return ret; +} +IWL_EXPORT_SYMBOL(iwl_set_soc_latency); + +int iwl_configure_rxq(struct iwl_fw_runtime *fwrt) +{ + int i, num_queues, size, ret; + struct iwl_rfh_queue_config *cmd; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(DATA_PATH_GROUP, RFH_QUEUE_CONFIG_CMD), + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + }; + + /* + * The default queue is configured via context info, so if we + * have a single queue, there's nothing to do here. + */ + if (fwrt->trans->num_rx_queues == 1) + return 0; + + if (fwrt->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_22000) + return 0; + + /* skip the default queue */ + num_queues = fwrt->trans->num_rx_queues - 1; + + size = struct_size(cmd, data, num_queues); + + cmd = kzalloc(size, GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + cmd->num_queues = num_queues; + + for (i = 0; i < num_queues; i++) { + struct iwl_trans_rxq_dma_data data; + + cmd->data[i].q_num = i + 1; + iwl_trans_get_rxq_dma_data(fwrt->trans, i + 1, &data); + + cmd->data[i].fr_bd_cb = cpu_to_le64(data.fr_bd_cb); + cmd->data[i].urbd_stts_wrptr = + cpu_to_le64(data.urbd_stts_wrptr); + cmd->data[i].ur_bd_cb = cpu_to_le64(data.ur_bd_cb); + cmd->data[i].fr_bd_wid = cpu_to_le32(data.fr_bd_wid); + } + + hcmd.data[0] = cmd; + hcmd.len[0] = size; + + ret = iwl_trans_send_cmd(fwrt->trans, &hcmd); + + kfree(cmd); + + if (ret) + IWL_ERR(fwrt, "Failed to configure RX queues: %d\n", ret); + + return ret; +} +IWL_EXPORT_SYMBOL(iwl_configure_rxq); diff --git a/drivers/net/wireless/intel/iwlwifi/fw/notif-wait.c b/drivers/net/wireless/intel/iwlwifi/fw/notif-wait.c new file mode 100644 index 000000000..231d2517f --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/notif-wait.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2021 Intel Corporation + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#include <linux/sched.h> +#include <linux/export.h> + +#include "iwl-drv.h" +#include "notif-wait.h" + + +void iwl_notification_wait_init(struct iwl_notif_wait_data *notif_wait) +{ + spin_lock_init(¬if_wait->notif_wait_lock); + INIT_LIST_HEAD(¬if_wait->notif_waits); + init_waitqueue_head(¬if_wait->notif_waitq); +} +IWL_EXPORT_SYMBOL(iwl_notification_wait_init); + +bool iwl_notification_wait(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt) +{ + bool triggered = false; + + if (!list_empty(¬if_wait->notif_waits)) { + struct iwl_notification_wait *w; + + spin_lock_bh(¬if_wait->notif_wait_lock); + list_for_each_entry(w, ¬if_wait->notif_waits, list) { + int i; + bool found = false; + + /* + * If it already finished (triggered) or has been + * aborted then don't evaluate it again to avoid races, + * Otherwise the function could be called again even + * though it returned true before + */ + if (w->triggered || w->aborted) + continue; + + for (i = 0; i < w->n_cmds; i++) { + u16 rec_id = WIDE_ID(pkt->hdr.group_id, + pkt->hdr.cmd); + + if (w->cmds[i] == rec_id || + (!iwl_cmd_groupid(w->cmds[i]) && + DEF_ID(w->cmds[i]) == rec_id)) { + found = true; + break; + } + } + if (!found) + continue; + + if (!w->fn || w->fn(notif_wait, pkt, w->fn_data)) { + w->triggered = true; + triggered = true; + } + } + spin_unlock_bh(¬if_wait->notif_wait_lock); + } + + return triggered; +} +IWL_EXPORT_SYMBOL(iwl_notification_wait); + +void iwl_abort_notification_waits(struct iwl_notif_wait_data *notif_wait) +{ + struct iwl_notification_wait *wait_entry; + + spin_lock_bh(¬if_wait->notif_wait_lock); + list_for_each_entry(wait_entry, ¬if_wait->notif_waits, list) + wait_entry->aborted = true; + spin_unlock_bh(¬if_wait->notif_wait_lock); + + wake_up_all(¬if_wait->notif_waitq); +} +IWL_EXPORT_SYMBOL(iwl_abort_notification_waits); + +void +iwl_init_notification_wait(struct iwl_notif_wait_data *notif_wait, + struct iwl_notification_wait *wait_entry, + const u16 *cmds, int n_cmds, + bool (*fn)(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data), + void *fn_data) +{ + if (WARN_ON(n_cmds > MAX_NOTIF_CMDS)) + n_cmds = MAX_NOTIF_CMDS; + + wait_entry->fn = fn; + wait_entry->fn_data = fn_data; + wait_entry->n_cmds = n_cmds; + memcpy(wait_entry->cmds, cmds, n_cmds * sizeof(u16)); + wait_entry->triggered = false; + wait_entry->aborted = false; + + spin_lock_bh(¬if_wait->notif_wait_lock); + list_add(&wait_entry->list, ¬if_wait->notif_waits); + spin_unlock_bh(¬if_wait->notif_wait_lock); +} +IWL_EXPORT_SYMBOL(iwl_init_notification_wait); + +void iwl_remove_notification(struct iwl_notif_wait_data *notif_wait, + struct iwl_notification_wait *wait_entry) +{ + spin_lock_bh(¬if_wait->notif_wait_lock); + list_del(&wait_entry->list); + spin_unlock_bh(¬if_wait->notif_wait_lock); +} +IWL_EXPORT_SYMBOL(iwl_remove_notification); + +int iwl_wait_notification(struct iwl_notif_wait_data *notif_wait, + struct iwl_notification_wait *wait_entry, + unsigned long timeout) +{ + int ret; + + ret = wait_event_timeout(notif_wait->notif_waitq, + wait_entry->triggered || wait_entry->aborted, + timeout); + + iwl_remove_notification(notif_wait, wait_entry); + + if (wait_entry->aborted) + return -EIO; + + /* return value is always >= 0 */ + if (ret <= 0) + return -ETIMEDOUT; + return 0; +} +IWL_EXPORT_SYMBOL(iwl_wait_notification); diff --git a/drivers/net/wireless/intel/iwlwifi/fw/notif-wait.h b/drivers/net/wireless/intel/iwlwifi/fw/notif-wait.h new file mode 100644 index 000000000..49e8ba11b --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/notif-wait.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014 Intel Corporation + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_notif_wait_h__ +#define __iwl_notif_wait_h__ + +#include <linux/wait.h> + +#include "iwl-trans.h" + +struct iwl_notif_wait_data { + struct list_head notif_waits; + spinlock_t notif_wait_lock; + wait_queue_head_t notif_waitq; +}; + +#define MAX_NOTIF_CMDS 5 + +/** + * struct iwl_notification_wait - notification wait entry + * @list: list head for global list + * @fn: Function called with the notification. If the function + * returns true, the wait is over, if it returns false then + * the waiter stays blocked. If no function is given, any + * of the listed commands will unblock the waiter. + * @cmds: command IDs + * @n_cmds: number of command IDs + * @triggered: waiter should be woken up + * @aborted: wait was aborted + * + * This structure is not used directly, to wait for a + * notification declare it on the stack, and call + * iwl_init_notification_wait() with appropriate + * parameters. Then do whatever will cause the ucode + * to notify the driver, and to wait for that then + * call iwl_wait_notification(). + * + * Each notification is one-shot. If at some point we + * need to support multi-shot notifications (which + * can't be allocated on the stack) we need to modify + * the code for them. + */ +struct iwl_notification_wait { + struct list_head list; + + bool (*fn)(struct iwl_notif_wait_data *notif_data, + struct iwl_rx_packet *pkt, void *data); + void *fn_data; + + u16 cmds[MAX_NOTIF_CMDS]; + u8 n_cmds; + bool triggered, aborted; +}; + + +/* caller functions */ +void iwl_notification_wait_init(struct iwl_notif_wait_data *notif_data); +bool iwl_notification_wait(struct iwl_notif_wait_data *notif_data, + struct iwl_rx_packet *pkt); +void iwl_abort_notification_waits(struct iwl_notif_wait_data *notif_data); + +static inline void +iwl_notification_notify(struct iwl_notif_wait_data *notif_data) +{ + wake_up_all(¬if_data->notif_waitq); +} + +static inline void +iwl_notification_wait_notify(struct iwl_notif_wait_data *notif_data, + struct iwl_rx_packet *pkt) +{ + if (iwl_notification_wait(notif_data, pkt)) + iwl_notification_notify(notif_data); +} + +/* user functions */ +void __acquires(wait_entry) +iwl_init_notification_wait(struct iwl_notif_wait_data *notif_data, + struct iwl_notification_wait *wait_entry, + const u16 *cmds, int n_cmds, + bool (*fn)(struct iwl_notif_wait_data *notif_data, + struct iwl_rx_packet *pkt, void *data), + void *fn_data); + +int __must_check __releases(wait_entry) +iwl_wait_notification(struct iwl_notif_wait_data *notif_data, + struct iwl_notification_wait *wait_entry, + unsigned long timeout); + +void __releases(wait_entry) +iwl_remove_notification(struct iwl_notif_wait_data *notif_data, + struct iwl_notification_wait *wait_entry); + +#endif /* __iwl_notif_wait_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/paging.c b/drivers/net/wireless/intel/iwlwifi/fw/paging.c new file mode 100644 index 000000000..945bc4160 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/paging.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2019, 2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include "iwl-drv.h" +#include "runtime.h" +#include "fw/api/commands.h" + +void iwl_free_fw_paging(struct iwl_fw_runtime *fwrt) +{ + int i; + + if (!fwrt->fw_paging_db[0].fw_paging_block) + return; + + for (i = 0; i < NUM_OF_FW_PAGING_BLOCKS; i++) { + struct iwl_fw_paging *paging = &fwrt->fw_paging_db[i]; + + if (!paging->fw_paging_block) { + IWL_DEBUG_FW(fwrt, + "Paging: block %d already freed, continue to next page\n", + i); + + continue; + } + dma_unmap_page(fwrt->trans->dev, paging->fw_paging_phys, + paging->fw_paging_size, DMA_BIDIRECTIONAL); + + __free_pages(paging->fw_paging_block, + get_order(paging->fw_paging_size)); + paging->fw_paging_block = NULL; + } + + memset(fwrt->fw_paging_db, 0, sizeof(fwrt->fw_paging_db)); +} +IWL_EXPORT_SYMBOL(iwl_free_fw_paging); + +static int iwl_alloc_fw_paging_mem(struct iwl_fw_runtime *fwrt, + const struct fw_img *image) +{ + struct page *block; + dma_addr_t phys = 0; + int blk_idx, order, num_of_pages, size; + + if (fwrt->fw_paging_db[0].fw_paging_block) + return 0; + + /* ensure BLOCK_2_EXP_SIZE is power of 2 of PAGING_BLOCK_SIZE */ + BUILD_BUG_ON(BIT(BLOCK_2_EXP_SIZE) != PAGING_BLOCK_SIZE); + + num_of_pages = image->paging_mem_size / FW_PAGING_SIZE; + fwrt->num_of_paging_blk = + DIV_ROUND_UP(num_of_pages, NUM_OF_PAGE_PER_GROUP); + fwrt->num_of_pages_in_last_blk = + num_of_pages - + NUM_OF_PAGE_PER_GROUP * (fwrt->num_of_paging_blk - 1); + + IWL_DEBUG_FW(fwrt, + "Paging: allocating mem for %d paging blocks, each block holds 8 pages, last block holds %d pages\n", + fwrt->num_of_paging_blk, + fwrt->num_of_pages_in_last_blk); + + /* + * Allocate CSS and paging blocks in dram. + */ + for (blk_idx = 0; blk_idx < fwrt->num_of_paging_blk + 1; blk_idx++) { + /* For CSS allocate 4KB, for others PAGING_BLOCK_SIZE (32K) */ + size = blk_idx ? PAGING_BLOCK_SIZE : FW_PAGING_SIZE; + order = get_order(size); + block = alloc_pages(GFP_KERNEL, order); + if (!block) { + /* free all the previous pages since we failed */ + iwl_free_fw_paging(fwrt); + return -ENOMEM; + } + + fwrt->fw_paging_db[blk_idx].fw_paging_block = block; + fwrt->fw_paging_db[blk_idx].fw_paging_size = size; + + phys = dma_map_page(fwrt->trans->dev, block, 0, + PAGE_SIZE << order, + DMA_BIDIRECTIONAL); + if (dma_mapping_error(fwrt->trans->dev, phys)) { + /* + * free the previous pages and the current one + * since we failed to map_page. + */ + iwl_free_fw_paging(fwrt); + return -ENOMEM; + } + fwrt->fw_paging_db[blk_idx].fw_paging_phys = phys; + + if (!blk_idx) + IWL_DEBUG_FW(fwrt, + "Paging: allocated 4K(CSS) bytes (order %d) for firmware paging.\n", + order); + else + IWL_DEBUG_FW(fwrt, + "Paging: allocated 32K bytes (order %d) for firmware paging.\n", + order); + } + + return 0; +} + +static int iwl_fill_paging_mem(struct iwl_fw_runtime *fwrt, + const struct fw_img *image) +{ + int sec_idx, idx, ret; + u32 offset = 0; + + /* + * find where is the paging image start point: + * if CPU2 exist and it's in paging format, then the image looks like: + * CPU1 sections (2 or more) + * CPU1_CPU2_SEPARATOR_SECTION delimiter - separate between CPU1 to CPU2 + * CPU2 sections (not paged) + * PAGING_SEPARATOR_SECTION delimiter - separate between CPU2 + * non paged to CPU2 paging sec + * CPU2 paging CSS + * CPU2 paging image (including instruction and data) + */ + for (sec_idx = 0; sec_idx < image->num_sec; sec_idx++) { + if (image->sec[sec_idx].offset == PAGING_SEPARATOR_SECTION) { + sec_idx++; + break; + } + } + + /* + * If paging is enabled there should be at least 2 more sections left + * (one for CSS and one for Paging data) + */ + if (sec_idx >= image->num_sec - 1) { + IWL_ERR(fwrt, "Paging: Missing CSS and/or paging sections\n"); + ret = -EINVAL; + goto err; + } + + /* copy the CSS block to the dram */ + IWL_DEBUG_FW(fwrt, "Paging: load paging CSS to FW, sec = %d\n", + sec_idx); + + if (image->sec[sec_idx].len > fwrt->fw_paging_db[0].fw_paging_size) { + IWL_ERR(fwrt, "CSS block is larger than paging size\n"); + ret = -EINVAL; + goto err; + } + + memcpy(page_address(fwrt->fw_paging_db[0].fw_paging_block), + image->sec[sec_idx].data, + image->sec[sec_idx].len); + fwrt->fw_paging_db[0].fw_offs = image->sec[sec_idx].offset; + dma_sync_single_for_device(fwrt->trans->dev, + fwrt->fw_paging_db[0].fw_paging_phys, + fwrt->fw_paging_db[0].fw_paging_size, + DMA_BIDIRECTIONAL); + + IWL_DEBUG_FW(fwrt, + "Paging: copied %d CSS bytes to first block\n", + fwrt->fw_paging_db[0].fw_paging_size); + + sec_idx++; + + /* + * Copy the paging blocks to the dram. The loop index starts + * from 1 since the CSS block (index 0) was already copied to + * dram. We use num_of_paging_blk + 1 to account for that. + */ + for (idx = 1; idx < fwrt->num_of_paging_blk + 1; idx++) { + struct iwl_fw_paging *block = &fwrt->fw_paging_db[idx]; + int remaining = image->sec[sec_idx].len - offset; + int len = block->fw_paging_size; + + /* + * For the last block, we copy all that is remaining, + * for all other blocks, we copy fw_paging_size at a + * time. */ + if (idx == fwrt->num_of_paging_blk) { + len = remaining; + if (remaining != + fwrt->num_of_pages_in_last_blk * FW_PAGING_SIZE) { + IWL_ERR(fwrt, + "Paging: last block contains more data than expected %d\n", + remaining); + ret = -EINVAL; + goto err; + } + } else if (block->fw_paging_size > remaining) { + IWL_ERR(fwrt, + "Paging: not enough data in other in block %d (%d)\n", + idx, remaining); + ret = -EINVAL; + goto err; + } + + memcpy(page_address(block->fw_paging_block), + (const u8 *)image->sec[sec_idx].data + offset, len); + block->fw_offs = image->sec[sec_idx].offset + offset; + dma_sync_single_for_device(fwrt->trans->dev, + block->fw_paging_phys, + block->fw_paging_size, + DMA_BIDIRECTIONAL); + + IWL_DEBUG_FW(fwrt, + "Paging: copied %d paging bytes to block %d\n", + len, idx); + + offset += block->fw_paging_size; + } + + return 0; + +err: + iwl_free_fw_paging(fwrt); + return ret; +} + +static int iwl_save_fw_paging(struct iwl_fw_runtime *fwrt, + const struct fw_img *fw) +{ + int ret; + + ret = iwl_alloc_fw_paging_mem(fwrt, fw); + if (ret) + return ret; + + return iwl_fill_paging_mem(fwrt, fw); +} + +/* send paging cmd to FW in case CPU2 has paging image */ +static int iwl_send_paging_cmd(struct iwl_fw_runtime *fwrt, + const struct fw_img *fw) +{ + struct iwl_fw_paging_cmd paging_cmd = { + .flags = cpu_to_le32(PAGING_CMD_IS_SECURED | + PAGING_CMD_IS_ENABLED | + (fwrt->num_of_pages_in_last_blk << + PAGING_CMD_NUM_OF_PAGES_IN_LAST_GRP_POS)), + .block_size = cpu_to_le32(BLOCK_2_EXP_SIZE), + .block_num = cpu_to_le32(fwrt->num_of_paging_blk), + }; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(IWL_ALWAYS_LONG_GROUP, FW_PAGING_BLOCK_CMD), + .len = { sizeof(paging_cmd), }, + .data = { &paging_cmd, }, + }; + int blk_idx; + + /* loop for for all paging blocks + CSS block */ + for (blk_idx = 0; blk_idx < fwrt->num_of_paging_blk + 1; blk_idx++) { + dma_addr_t addr = fwrt->fw_paging_db[blk_idx].fw_paging_phys; + __le32 phy_addr; + + addr = addr >> PAGE_2_EXP_SIZE; + phy_addr = cpu_to_le32(addr); + paging_cmd.device_phy_addr[blk_idx] = phy_addr; + } + + return iwl_trans_send_cmd(fwrt->trans, &hcmd); +} + +int iwl_init_paging(struct iwl_fw_runtime *fwrt, enum iwl_ucode_type type) +{ + const struct fw_img *fw = &fwrt->fw->img[type]; + int ret; + + if (fwrt->trans->trans_cfg->gen2) + return 0; + + /* + * Configure and operate fw paging mechanism. + * The driver configures the paging flow only once. + * The CPU2 paging image is included in the IWL_UCODE_INIT image. + */ + if (!fw->paging_mem_size) + return 0; + + ret = iwl_save_fw_paging(fwrt, fw); + if (ret) { + IWL_ERR(fwrt, "failed to save the FW paging image\n"); + return ret; + } + + ret = iwl_send_paging_cmd(fwrt, fw); + if (ret) { + IWL_ERR(fwrt, "failed to send the paging cmd\n"); + iwl_free_fw_paging(fwrt); + return ret; + } + + return 0; +} +IWL_EXPORT_SYMBOL(iwl_init_paging); diff --git a/drivers/net/wireless/intel/iwlwifi/fw/pnvm.c b/drivers/net/wireless/intel/iwlwifi/fw/pnvm.c new file mode 100644 index 000000000..b6d3ac6ed --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/pnvm.c @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright(c) 2020-2021 Intel Corporation + */ + +#include "iwl-drv.h" +#include "pnvm.h" +#include "iwl-prph.h" +#include "iwl-io.h" +#include "fw/api/commands.h" +#include "fw/api/nvm-reg.h" +#include "fw/api/alive.h" +#include "fw/uefi.h" + +struct iwl_pnvm_section { + __le32 offset; + const u8 data[]; +} __packed; + +static bool iwl_pnvm_complete_fn(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_trans *trans = (struct iwl_trans *)data; + struct iwl_pnvm_init_complete_ntfy *pnvm_ntf = (void *)pkt->data; + + IWL_DEBUG_FW(trans, + "PNVM complete notification received with status 0x%0x\n", + le32_to_cpu(pnvm_ntf->status)); + + return true; +} + +static int iwl_pnvm_handle_section(struct iwl_trans *trans, const u8 *data, + size_t len) +{ + const struct iwl_ucode_tlv *tlv; + u32 sha1 = 0; + u16 mac_type = 0, rf_id = 0; + u8 *pnvm_data = NULL, *tmp; + bool hw_match = false; + u32 size = 0; + int ret; + + IWL_DEBUG_FW(trans, "Handling PNVM section\n"); + + while (len >= sizeof(*tlv)) { + u32 tlv_len, tlv_type; + + len -= sizeof(*tlv); + tlv = (const void *)data; + + tlv_len = le32_to_cpu(tlv->length); + tlv_type = le32_to_cpu(tlv->type); + + if (len < tlv_len) { + IWL_ERR(trans, "invalid TLV len: %zd/%u\n", + len, tlv_len); + ret = -EINVAL; + goto out; + } + + data += sizeof(*tlv); + + switch (tlv_type) { + case IWL_UCODE_TLV_PNVM_VERSION: + if (tlv_len < sizeof(__le32)) { + IWL_DEBUG_FW(trans, + "Invalid size for IWL_UCODE_TLV_PNVM_VERSION (expected %zd, got %d)\n", + sizeof(__le32), tlv_len); + break; + } + + sha1 = le32_to_cpup((const __le32 *)data); + + IWL_DEBUG_FW(trans, + "Got IWL_UCODE_TLV_PNVM_VERSION %0x\n", + sha1); + break; + case IWL_UCODE_TLV_HW_TYPE: + if (tlv_len < 2 * sizeof(__le16)) { + IWL_DEBUG_FW(trans, + "Invalid size for IWL_UCODE_TLV_HW_TYPE (expected %zd, got %d)\n", + 2 * sizeof(__le16), tlv_len); + break; + } + + if (hw_match) + break; + + mac_type = le16_to_cpup((const __le16 *)data); + rf_id = le16_to_cpup((const __le16 *)(data + sizeof(__le16))); + + IWL_DEBUG_FW(trans, + "Got IWL_UCODE_TLV_HW_TYPE mac_type 0x%0x rf_id 0x%0x\n", + mac_type, rf_id); + + if (mac_type == CSR_HW_REV_TYPE(trans->hw_rev) && + rf_id == CSR_HW_RFID_TYPE(trans->hw_rf_id)) + hw_match = true; + break; + case IWL_UCODE_TLV_SEC_RT: { + const struct iwl_pnvm_section *section = (const void *)data; + u32 data_len = tlv_len - sizeof(*section); + + IWL_DEBUG_FW(trans, + "Got IWL_UCODE_TLV_SEC_RT len %d\n", + tlv_len); + + /* TODO: remove, this is a deprecated separator */ + if (le32_to_cpup((const __le32 *)data) == 0xddddeeee) { + IWL_DEBUG_FW(trans, "Ignoring separator.\n"); + break; + } + + IWL_DEBUG_FW(trans, "Adding data (size %d)\n", + data_len); + + tmp = krealloc(pnvm_data, size + data_len, GFP_KERNEL); + if (!tmp) { + IWL_DEBUG_FW(trans, + "Couldn't allocate (more) pnvm_data\n"); + + ret = -ENOMEM; + goto out; + } + + pnvm_data = tmp; + + memcpy(pnvm_data + size, section->data, data_len); + + size += data_len; + + break; + } + case IWL_UCODE_TLV_PNVM_SKU: + IWL_DEBUG_FW(trans, + "New PNVM section started, stop parsing.\n"); + goto done; + default: + IWL_DEBUG_FW(trans, "Found TLV 0x%0x, len %d\n", + tlv_type, tlv_len); + break; + } + + len -= ALIGN(tlv_len, 4); + data += ALIGN(tlv_len, 4); + } + +done: + if (!hw_match) { + IWL_DEBUG_FW(trans, + "HW mismatch, skipping PNVM section (need mac_type 0x%x rf_id 0x%x)\n", + CSR_HW_REV_TYPE(trans->hw_rev), + CSR_HW_RFID_TYPE(trans->hw_rf_id)); + ret = -ENOENT; + goto out; + } + + if (!size) { + IWL_DEBUG_FW(trans, "Empty PNVM, skipping.\n"); + ret = -ENOENT; + goto out; + } + + IWL_INFO(trans, "loaded PNVM version %08x\n", sha1); + + ret = iwl_trans_set_pnvm(trans, pnvm_data, size); +out: + kfree(pnvm_data); + return ret; +} + +static int iwl_pnvm_parse(struct iwl_trans *trans, const u8 *data, + size_t len) +{ + const struct iwl_ucode_tlv *tlv; + + IWL_DEBUG_FW(trans, "Parsing PNVM file\n"); + + while (len >= sizeof(*tlv)) { + u32 tlv_len, tlv_type; + + len -= sizeof(*tlv); + tlv = (const void *)data; + + tlv_len = le32_to_cpu(tlv->length); + tlv_type = le32_to_cpu(tlv->type); + + if (len < tlv_len) { + IWL_ERR(trans, "invalid TLV len: %zd/%u\n", + len, tlv_len); + return -EINVAL; + } + + if (tlv_type == IWL_UCODE_TLV_PNVM_SKU) { + const struct iwl_sku_id *sku_id = + (const void *)(data + sizeof(*tlv)); + + IWL_DEBUG_FW(trans, + "Got IWL_UCODE_TLV_PNVM_SKU len %d\n", + tlv_len); + IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n", + le32_to_cpu(sku_id->data[0]), + le32_to_cpu(sku_id->data[1]), + le32_to_cpu(sku_id->data[2])); + + data += sizeof(*tlv) + ALIGN(tlv_len, 4); + len -= ALIGN(tlv_len, 4); + + if (trans->sku_id[0] == le32_to_cpu(sku_id->data[0]) && + trans->sku_id[1] == le32_to_cpu(sku_id->data[1]) && + trans->sku_id[2] == le32_to_cpu(sku_id->data[2])) { + int ret; + + ret = iwl_pnvm_handle_section(trans, data, len); + if (!ret) + return 0; + } else { + IWL_DEBUG_FW(trans, "SKU ID didn't match!\n"); + } + } else { + data += sizeof(*tlv) + ALIGN(tlv_len, 4); + len -= ALIGN(tlv_len, 4); + } + } + + return -ENOENT; +} + +static int iwl_pnvm_get_from_fs(struct iwl_trans *trans, u8 **data, size_t *len) +{ + const struct firmware *pnvm; + char pnvm_name[MAX_PNVM_NAME]; + size_t new_len; + int ret; + + iwl_pnvm_get_fs_name(trans, pnvm_name, sizeof(pnvm_name)); + + ret = firmware_request_nowarn(&pnvm, pnvm_name, trans->dev); + if (ret) { + IWL_DEBUG_FW(trans, "PNVM file %s not found %d\n", + pnvm_name, ret); + return ret; + } + + new_len = pnvm->size; + *data = kmemdup(pnvm->data, pnvm->size, GFP_KERNEL); + release_firmware(pnvm); + + if (!*data) + return -ENOMEM; + + *len = new_len; + + return 0; +} + +int iwl_pnvm_load(struct iwl_trans *trans, + struct iwl_notif_wait_data *notif_wait) +{ + u8 *data; + size_t len; + struct pnvm_sku_package *package; + struct iwl_notification_wait pnvm_wait; + static const u16 ntf_cmds[] = { WIDE_ID(REGULATORY_AND_NVM_GROUP, + PNVM_INIT_COMPLETE_NTFY) }; + int ret; + + /* if the SKU_ID is empty, there's nothing to do */ + if (!trans->sku_id[0] && !trans->sku_id[1] && !trans->sku_id[2]) + return 0; + + /* + * If we already loaded (or tried to load) it before, we just + * need to set it again. + */ + if (trans->pnvm_loaded) { + ret = iwl_trans_set_pnvm(trans, NULL, 0); + if (ret) + return ret; + goto skip_parse; + } + + /* First attempt to get the PNVM from BIOS */ + package = iwl_uefi_get_pnvm(trans, &len); + if (!IS_ERR_OR_NULL(package)) { + if (len >= sizeof(*package)) { + /* we need only the data */ + len -= sizeof(*package); + data = kmemdup(package->data, len, GFP_KERNEL); + } else { + data = NULL; + } + + /* free package regardless of whether kmemdup succeeded */ + kfree(package); + + if (data) + goto parse; + } + + /* If it's not available, try from the filesystem */ + ret = iwl_pnvm_get_from_fs(trans, &data, &len); + if (ret) { + /* + * Pretend we've loaded it - at least we've tried and + * couldn't load it at all, so there's no point in + * trying again over and over. + */ + trans->pnvm_loaded = true; + + goto skip_parse; + } + +parse: + iwl_pnvm_parse(trans, data, len); + + kfree(data); + +skip_parse: + data = NULL; + /* now try to get the reduce power table, if not loaded yet */ + if (!trans->reduce_power_loaded) { + data = iwl_uefi_get_reduced_power(trans, &len); + if (IS_ERR_OR_NULL(data)) { + /* + * Pretend we've loaded it - at least we've tried and + * couldn't load it at all, so there's no point in + * trying again over and over. + */ + trans->reduce_power_loaded = true; + + goto skip_reduce_power; + } + } + + ret = iwl_trans_set_reduce_power(trans, data, len); + if (ret) + IWL_DEBUG_FW(trans, + "Failed to set reduce power table %d\n", + ret); + kfree(data); + +skip_reduce_power: + iwl_init_notification_wait(notif_wait, &pnvm_wait, + ntf_cmds, ARRAY_SIZE(ntf_cmds), + iwl_pnvm_complete_fn, trans); + + /* kick the doorbell */ + iwl_write_umac_prph(trans, UREG_DOORBELL_TO_ISR6, + UREG_DOORBELL_TO_ISR6_PNVM); + + return iwl_wait_notification(notif_wait, &pnvm_wait, + MVM_UCODE_PNVM_TIMEOUT); +} +IWL_EXPORT_SYMBOL(iwl_pnvm_load); diff --git a/drivers/net/wireless/intel/iwlwifi/fw/pnvm.h b/drivers/net/wireless/intel/iwlwifi/fw/pnvm.h new file mode 100644 index 000000000..203c367dd --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/pnvm.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/****************************************************************************** + * + * Copyright(c) 2020-2021 Intel Corporation + * + *****************************************************************************/ + +#ifndef __IWL_PNVM_H__ +#define __IWL_PNVM_H__ + +#include "fw/notif-wait.h" + +#define MVM_UCODE_PNVM_TIMEOUT (HZ / 4) + +#define MAX_PNVM_NAME 64 + +int iwl_pnvm_load(struct iwl_trans *trans, + struct iwl_notif_wait_data *notif_wait); + +static inline +void iwl_pnvm_get_fs_name(struct iwl_trans *trans, + u8 *pnvm_name, size_t max_len) +{ + int pre_len; + + /* + * The prefix unfortunately includes a hyphen at the end, so + * don't add the dot here... + */ + snprintf(pnvm_name, max_len, "%spnvm", trans->cfg->fw_name_pre); + + /* ...but replace the hyphen with the dot here. */ + pre_len = strlen(trans->cfg->fw_name_pre); + if (pre_len < max_len && pre_len > 0) + pnvm_name[pre_len - 1] = '.'; +} + +#endif /* __IWL_PNVM_H__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/rs.c b/drivers/net/wireless/intel/iwlwifi/fw/rs.c new file mode 100644 index 000000000..a83521461 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/rs.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2021 Intel Corporation + */ + +#include <net/mac80211.h> +#include "fw/api/rs.h" +#include "iwl-drv.h" +#include "iwl-config.h" + +#define IWL_DECLARE_RATE_INFO(r) \ + [IWL_RATE_##r##M_INDEX] = IWL_RATE_##r##M_PLCP + +/* + * Translate from fw_rate_index (IWL_RATE_XXM_INDEX) to PLCP + * */ +static const u8 fw_rate_idx_to_plcp[IWL_RATE_COUNT] = { + IWL_DECLARE_RATE_INFO(1), + IWL_DECLARE_RATE_INFO(2), + IWL_DECLARE_RATE_INFO(5), + IWL_DECLARE_RATE_INFO(11), + IWL_DECLARE_RATE_INFO(6), + IWL_DECLARE_RATE_INFO(9), + IWL_DECLARE_RATE_INFO(12), + IWL_DECLARE_RATE_INFO(18), + IWL_DECLARE_RATE_INFO(24), + IWL_DECLARE_RATE_INFO(36), + IWL_DECLARE_RATE_INFO(48), + IWL_DECLARE_RATE_INFO(54), +}; + +/* mbps, mcs */ +static const struct iwl_rate_mcs_info rate_mcs[IWL_RATE_COUNT] = { + { "1", "BPSK DSSS"}, + { "2", "QPSK DSSS"}, + {"5.5", "BPSK CCK"}, + { "11", "QPSK CCK"}, + { "6", "BPSK 1/2"}, + { "9", "BPSK 1/2"}, + { "12", "QPSK 1/2"}, + { "18", "QPSK 3/4"}, + { "24", "16QAM 1/2"}, + { "36", "16QAM 3/4"}, + { "48", "64QAM 2/3"}, + { "54", "64QAM 3/4"}, + { "60", "64QAM 5/6"}, +}; + +static const char * const ant_name[] = { + [ANT_NONE] = "None", + [ANT_A] = "A", + [ANT_B] = "B", + [ANT_AB] = "AB", +}; + +static const char * const pretty_bw[] = { + "20Mhz", + "40Mhz", + "80Mhz", + "160 Mhz", + "320Mhz", +}; + +u8 iwl_fw_rate_idx_to_plcp(int idx) +{ + return fw_rate_idx_to_plcp[idx]; +} +IWL_EXPORT_SYMBOL(iwl_fw_rate_idx_to_plcp); + +const struct iwl_rate_mcs_info *iwl_rate_mcs(int idx) +{ + return &rate_mcs[idx]; +} +IWL_EXPORT_SYMBOL(iwl_rate_mcs); + +const char *iwl_rs_pretty_ant(u8 ant) +{ + if (ant >= ARRAY_SIZE(ant_name)) + return "UNKNOWN"; + + return ant_name[ant]; +} +IWL_EXPORT_SYMBOL(iwl_rs_pretty_ant); + +const char *iwl_rs_pretty_bw(int bw) +{ + if (bw >= ARRAY_SIZE(pretty_bw)) + return "unknown bw"; + + return pretty_bw[bw]; +} +IWL_EXPORT_SYMBOL(iwl_rs_pretty_bw); + +static u32 iwl_legacy_rate_to_fw_idx(u32 rate_n_flags) +{ + int rate = rate_n_flags & RATE_LEGACY_RATE_MSK_V1; + int idx; + bool ofdm = !(rate_n_flags & RATE_MCS_CCK_MSK_V1); + int offset = ofdm ? IWL_FIRST_OFDM_RATE : 0; + int last = ofdm ? IWL_RATE_COUNT_LEGACY : IWL_FIRST_OFDM_RATE; + + for (idx = offset; idx < last; idx++) + if (iwl_fw_rate_idx_to_plcp(idx) == rate) + return idx - offset; + return IWL_RATE_INVALID; +} + +u32 iwl_new_rate_from_v1(u32 rate_v1) +{ + u32 rate_v2 = 0; + u32 dup = 0; + + if (rate_v1 == 0) + return rate_v1; + /* convert rate */ + if (rate_v1 & RATE_MCS_HT_MSK_V1) { + u32 nss = 0; + + rate_v2 |= RATE_MCS_HT_MSK; + rate_v2 |= + rate_v1 & RATE_HT_MCS_RATE_CODE_MSK_V1; + nss = (rate_v1 & RATE_HT_MCS_MIMO2_MSK) >> + RATE_HT_MCS_NSS_POS_V1; + rate_v2 |= nss << RATE_MCS_NSS_POS; + } else if (rate_v1 & RATE_MCS_VHT_MSK_V1 || + rate_v1 & RATE_MCS_HE_MSK_V1) { + rate_v2 |= rate_v1 & RATE_VHT_MCS_RATE_CODE_MSK; + + rate_v2 |= rate_v1 & RATE_VHT_MCS_MIMO2_MSK; + + if (rate_v1 & RATE_MCS_HE_MSK_V1) { + u32 he_type_bits = rate_v1 & RATE_MCS_HE_TYPE_MSK_V1; + u32 he_type = he_type_bits >> RATE_MCS_HE_TYPE_POS_V1; + u32 he_106t = (rate_v1 & RATE_MCS_HE_106T_MSK_V1) >> + RATE_MCS_HE_106T_POS_V1; + u32 he_gi_ltf = (rate_v1 & RATE_MCS_HE_GI_LTF_MSK_V1) >> + RATE_MCS_HE_GI_LTF_POS; + + if ((he_type_bits == RATE_MCS_HE_TYPE_SU || + he_type_bits == RATE_MCS_HE_TYPE_EXT_SU) && + he_gi_ltf == RATE_MCS_HE_SU_4_LTF) + /* the new rate have an additional bit to + * represent the value 4 rather then using SGI + * bit for this purpose - as it was done in the old + * rate */ + he_gi_ltf += (rate_v1 & RATE_MCS_SGI_MSK_V1) >> + RATE_MCS_SGI_POS_V1; + + rate_v2 |= he_gi_ltf << RATE_MCS_HE_GI_LTF_POS; + rate_v2 |= he_type << RATE_MCS_HE_TYPE_POS; + rate_v2 |= he_106t << RATE_MCS_HE_106T_POS; + rate_v2 |= rate_v1 & RATE_HE_DUAL_CARRIER_MODE_MSK; + rate_v2 |= RATE_MCS_HE_MSK; + } else { + rate_v2 |= RATE_MCS_VHT_MSK; + } + /* if legacy format */ + } else { + u32 legacy_rate = iwl_legacy_rate_to_fw_idx(rate_v1); + + if (WARN_ON_ONCE(legacy_rate == IWL_RATE_INVALID)) + legacy_rate = (rate_v1 & RATE_MCS_CCK_MSK_V1) ? + IWL_FIRST_CCK_RATE : IWL_FIRST_OFDM_RATE; + + rate_v2 |= legacy_rate; + if (!(rate_v1 & RATE_MCS_CCK_MSK_V1)) + rate_v2 |= RATE_MCS_LEGACY_OFDM_MSK; + } + + /* convert flags */ + if (rate_v1 & RATE_MCS_LDPC_MSK_V1) + rate_v2 |= RATE_MCS_LDPC_MSK; + rate_v2 |= (rate_v1 & RATE_MCS_CHAN_WIDTH_MSK_V1) | + (rate_v1 & RATE_MCS_ANT_AB_MSK) | + (rate_v1 & RATE_MCS_STBC_MSK) | + (rate_v1 & RATE_MCS_BF_MSK); + + dup = (rate_v1 & RATE_MCS_DUP_MSK_V1) >> RATE_MCS_DUP_POS_V1; + if (dup) { + rate_v2 |= RATE_MCS_DUP_MSK; + rate_v2 |= dup << RATE_MCS_CHAN_WIDTH_POS; + } + + if ((!(rate_v1 & RATE_MCS_HE_MSK_V1)) && + (rate_v1 & RATE_MCS_SGI_MSK_V1)) + rate_v2 |= RATE_MCS_SGI_MSK; + + return rate_v2; +} +IWL_EXPORT_SYMBOL(iwl_new_rate_from_v1); + +int rs_pretty_print_rate(char *buf, int bufsz, const u32 rate) +{ + char *type; + u8 mcs = 0, nss = 0; + u8 ant = (rate & RATE_MCS_ANT_AB_MSK) >> RATE_MCS_ANT_POS; + u32 bw = (rate & RATE_MCS_CHAN_WIDTH_MSK) >> + RATE_MCS_CHAN_WIDTH_POS; + u32 format = rate & RATE_MCS_MOD_TYPE_MSK; + bool sgi; + + if (format == RATE_MCS_CCK_MSK || + format == RATE_MCS_LEGACY_OFDM_MSK) { + int legacy_rate = rate & RATE_LEGACY_RATE_MSK; + int index = format == RATE_MCS_CCK_MSK ? + legacy_rate : + legacy_rate + IWL_FIRST_OFDM_RATE; + + return scnprintf(buf, bufsz, "Legacy | ANT: %s Rate: %s Mbps", + iwl_rs_pretty_ant(ant), + index == IWL_RATE_INVALID ? "BAD" : + iwl_rate_mcs(index)->mbps); + } + + if (format == RATE_MCS_VHT_MSK) + type = "VHT"; + else if (format == RATE_MCS_HT_MSK) + type = "HT"; + else if (format == RATE_MCS_HE_MSK) + type = "HE"; + else + type = "Unknown"; /* shouldn't happen */ + + mcs = format == RATE_MCS_HT_MSK ? + RATE_HT_MCS_INDEX(rate) : + rate & RATE_MCS_CODE_MSK; + nss = ((rate & RATE_MCS_NSS_MSK) + >> RATE_MCS_NSS_POS) + 1; + sgi = format == RATE_MCS_HE_MSK ? + iwl_he_is_sgi(rate) : + rate & RATE_MCS_SGI_MSK; + + return scnprintf(buf, bufsz, + "0x%x: %s | ANT: %s BW: %s MCS: %d NSS: %d %s%s%s%s%s", + rate, type, iwl_rs_pretty_ant(ant), iwl_rs_pretty_bw(bw), mcs, nss, + (sgi) ? "SGI " : "NGI ", + (rate & RATE_MCS_STBC_MSK) ? "STBC " : "", + (rate & RATE_MCS_LDPC_MSK) ? "LDPC " : "", + (rate & RATE_HE_DUAL_CARRIER_MODE_MSK) ? "DCM " : "", + (rate & RATE_MCS_BF_MSK) ? "BF " : ""); +} +IWL_EXPORT_SYMBOL(rs_pretty_print_rate); + +bool iwl_he_is_sgi(u32 rate_n_flags) +{ + u32 type = rate_n_flags & RATE_MCS_HE_TYPE_MSK; + u32 ltf_gi = rate_n_flags & RATE_MCS_HE_GI_LTF_MSK; + + if (type == RATE_MCS_HE_TYPE_SU || + type == RATE_MCS_HE_TYPE_EXT_SU) + return ltf_gi == RATE_MCS_HE_SU_4_LTF_08_GI; + return false; +} +IWL_EXPORT_SYMBOL(iwl_he_is_sgi); + diff --git a/drivers/net/wireless/intel/iwlwifi/fw/runtime.h b/drivers/net/wireless/intel/iwlwifi/fw/runtime.h new file mode 100644 index 000000000..d3cb1ae68 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/runtime.h @@ -0,0 +1,206 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2018-2022 Intel Corporation + */ +#ifndef __iwl_fw_runtime_h__ +#define __iwl_fw_runtime_h__ + +#include "iwl-config.h" +#include "iwl-trans.h" +#include "img.h" +#include "fw/api/debug.h" +#include "fw/api/paging.h" +#include "fw/api/power.h" +#include "iwl-eeprom-parse.h" +#include "fw/acpi.h" + +struct iwl_fw_runtime_ops { + void (*dump_start)(void *ctx); + void (*dump_end)(void *ctx); + bool (*fw_running)(void *ctx); + int (*send_hcmd)(void *ctx, struct iwl_host_cmd *host_cmd); + bool (*d3_debug_enable)(void *ctx); +}; + +#define MAX_NUM_LMAC 2 +struct iwl_fwrt_shared_mem_cfg { + int num_lmacs; + int num_txfifo_entries; + struct { + u32 txfifo_size[TX_FIFO_MAX_NUM]; + u32 rxfifo1_size; + } lmac[MAX_NUM_LMAC]; + u32 rxfifo2_size; + u32 rxfifo2_control_size; + u32 internal_txfifo_addr; + u32 internal_txfifo_size[TX_FIFO_INTERNAL_MAX_NUM]; +}; + +#define IWL_FW_RUNTIME_DUMP_WK_NUM 5 + +/** + * struct iwl_fwrt_dump_data - dump data + * @trig: trigger the worker was scheduled upon + * @fw_pkt: packet received from FW + */ +struct iwl_fwrt_dump_data { + union { + struct { + struct iwl_fw_ini_trigger_tlv *trig; + struct iwl_rx_packet *fw_pkt; + }; + struct { + const struct iwl_fw_dump_desc *desc; + bool monitor_only; + }; + }; +}; + +/** + * struct iwl_fwrt_wk_data - dump worker data struct + * @idx: index of the worker + * @wk: worker + */ +struct iwl_fwrt_wk_data { + u8 idx; + struct delayed_work wk; + struct iwl_fwrt_dump_data dump_data; +}; + +/** + * struct iwl_txf_iter_data - Tx fifo iterator data struct + * @fifo: fifo number + * @lmac: lmac number + * @fifo_size: fifo size + * @internal_txf: non zero if fifo is internal Tx fifo + */ +struct iwl_txf_iter_data { + int fifo; + int lmac; + u32 fifo_size; + u8 internal_txf; +}; + +/** + * struct iwl_fw_runtime - runtime data for firmware + * @fw: firmware image + * @cfg: NIC configuration + * @dev: device pointer + * @ops: user ops + * @ops_ctx: user ops context + * @fw_paging_db: paging database + * @num_of_paging_blk: number of paging blocks + * @num_of_pages_in_last_blk: number of pages in the last block + * @smem_cfg: saved firmware SMEM configuration + * @cur_fw_img: current firmware image, must be maintained by + * the driver by calling &iwl_fw_set_current_image() + * @dump: debug dump data + */ +struct iwl_fw_runtime { + struct iwl_trans *trans; + const struct iwl_fw *fw; + struct device *dev; + + const struct iwl_fw_runtime_ops *ops; + void *ops_ctx; + + const struct iwl_dump_sanitize_ops *sanitize_ops; + void *sanitize_ctx; + + /* Paging */ + struct iwl_fw_paging fw_paging_db[NUM_OF_FW_PAGING_BLOCKS]; + u16 num_of_paging_blk; + u16 num_of_pages_in_last_blk; + + enum iwl_ucode_type cur_fw_img; + + /* memory configuration */ + struct iwl_fwrt_shared_mem_cfg smem_cfg; + + /* debug */ + struct { + struct iwl_fwrt_wk_data wks[IWL_FW_RUNTIME_DUMP_WK_NUM]; + unsigned long active_wks; + + u8 conf; + + /* ts of the beginning of a non-collect fw dbg data period */ + unsigned long non_collect_ts_start[IWL_FW_INI_TIME_POINT_NUM]; + u32 *d3_debug_data; + u32 lmac_err_id[MAX_NUM_LMAC]; + u32 umac_err_id; + + struct iwl_txf_iter_data txf_iter_data; + + struct { + u8 type; + u8 subtype; + u32 lmac_major; + u32 lmac_minor; + u32 umac_major; + u32 umac_minor; + } fw_ver; + } dump; +#ifdef CONFIG_IWLWIFI_DEBUGFS + struct { + struct delayed_work wk; + u32 delay; + u64 seq; + } timestamp; + bool tpc_enabled; +#endif /* CONFIG_IWLWIFI_DEBUGFS */ +#ifdef CONFIG_ACPI + struct iwl_sar_profile sar_profiles[ACPI_SAR_PROFILE_NUM]; + u8 sar_chain_a_profile; + u8 sar_chain_b_profile; + struct iwl_geo_profile geo_profiles[ACPI_NUM_GEO_PROFILES_REV3]; + u32 geo_rev; + u32 geo_num_profiles; + bool geo_enabled; + struct iwl_ppag_chain ppag_chains[IWL_NUM_CHAIN_LIMITS]; + u32 ppag_flags; + u32 ppag_ver; + struct iwl_sar_offset_mapping_cmd sgom_table; + bool sgom_enabled; + u8 reduced_power_flags; +#endif +}; + +void iwl_fw_runtime_init(struct iwl_fw_runtime *fwrt, struct iwl_trans *trans, + const struct iwl_fw *fw, + const struct iwl_fw_runtime_ops *ops, void *ops_ctx, + const struct iwl_dump_sanitize_ops *sanitize_ops, + void *sanitize_ctx, + struct dentry *dbgfs_dir); + +static inline void iwl_fw_runtime_free(struct iwl_fw_runtime *fwrt) +{ + int i; + + kfree(fwrt->dump.d3_debug_data); + fwrt->dump.d3_debug_data = NULL; + + iwl_dbg_tlv_del_timers(fwrt->trans); + for (i = 0; i < IWL_FW_RUNTIME_DUMP_WK_NUM; i++) + cancel_delayed_work_sync(&fwrt->dump.wks[i].wk); +} + +void iwl_fw_runtime_suspend(struct iwl_fw_runtime *fwrt); + +void iwl_fw_runtime_resume(struct iwl_fw_runtime *fwrt); + +static inline void iwl_fw_set_current_image(struct iwl_fw_runtime *fwrt, + enum iwl_ucode_type cur_fw_img) +{ + fwrt->cur_fw_img = cur_fw_img; +} + +int iwl_init_paging(struct iwl_fw_runtime *fwrt, enum iwl_ucode_type type); +void iwl_free_fw_paging(struct iwl_fw_runtime *fwrt); + +void iwl_get_shared_mem_conf(struct iwl_fw_runtime *fwrt); +int iwl_set_soc_latency(struct iwl_fw_runtime *fwrt); +int iwl_configure_rxq(struct iwl_fw_runtime *fwrt); + +#endif /* __iwl_fw_runtime_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/smem.c b/drivers/net/wireless/intel/iwlwifi/fw/smem.c new file mode 100644 index 000000000..3f1272014 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/smem.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include "iwl-drv.h" +#include "runtime.h" +#include "fw/api/commands.h" + +static void iwl_parse_shared_mem_22000(struct iwl_fw_runtime *fwrt, + struct iwl_rx_packet *pkt) +{ + struct iwl_shared_mem_cfg *mem_cfg = (void *)pkt->data; + int i, lmac; + int lmac_num = le32_to_cpu(mem_cfg->lmac_num); + u8 api_ver = iwl_fw_lookup_notif_ver(fwrt->fw, SYSTEM_GROUP, + SHARED_MEM_CFG_CMD, 0); + + if (WARN_ON(lmac_num > ARRAY_SIZE(mem_cfg->lmac_smem))) + return; + + fwrt->smem_cfg.num_lmacs = lmac_num; + fwrt->smem_cfg.num_txfifo_entries = + ARRAY_SIZE(mem_cfg->lmac_smem[0].txfifo_size); + fwrt->smem_cfg.rxfifo2_size = le32_to_cpu(mem_cfg->rxfifo2_size); + + if (api_ver >= 4 && + !WARN_ON_ONCE(iwl_rx_packet_payload_len(pkt) < sizeof(*mem_cfg))) { + fwrt->smem_cfg.rxfifo2_control_size = + le32_to_cpu(mem_cfg->rxfifo2_control_size); + } + + for (lmac = 0; lmac < lmac_num; lmac++) { + struct iwl_shared_mem_lmac_cfg *lmac_cfg = + &mem_cfg->lmac_smem[lmac]; + + for (i = 0; i < ARRAY_SIZE(lmac_cfg->txfifo_size); i++) + fwrt->smem_cfg.lmac[lmac].txfifo_size[i] = + le32_to_cpu(lmac_cfg->txfifo_size[i]); + fwrt->smem_cfg.lmac[lmac].rxfifo1_size = + le32_to_cpu(lmac_cfg->rxfifo1_size); + } +} + +static void iwl_parse_shared_mem(struct iwl_fw_runtime *fwrt, + struct iwl_rx_packet *pkt) +{ + struct iwl_shared_mem_cfg_v2 *mem_cfg = (void *)pkt->data; + int i; + + fwrt->smem_cfg.num_lmacs = 1; + + fwrt->smem_cfg.num_txfifo_entries = ARRAY_SIZE(mem_cfg->txfifo_size); + for (i = 0; i < ARRAY_SIZE(mem_cfg->txfifo_size); i++) + fwrt->smem_cfg.lmac[0].txfifo_size[i] = + le32_to_cpu(mem_cfg->txfifo_size[i]); + + fwrt->smem_cfg.lmac[0].rxfifo1_size = + le32_to_cpu(mem_cfg->rxfifo_size[0]); + fwrt->smem_cfg.rxfifo2_size = le32_to_cpu(mem_cfg->rxfifo_size[1]); + + /* new API has more data, from rxfifo_addr field and on */ + if (fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG)) { + BUILD_BUG_ON(sizeof(fwrt->smem_cfg.internal_txfifo_size) != + sizeof(mem_cfg->internal_txfifo_size)); + + fwrt->smem_cfg.internal_txfifo_addr = + le32_to_cpu(mem_cfg->internal_txfifo_addr); + + for (i = 0; + i < ARRAY_SIZE(fwrt->smem_cfg.internal_txfifo_size); + i++) + fwrt->smem_cfg.internal_txfifo_size[i] = + le32_to_cpu(mem_cfg->internal_txfifo_size[i]); + } +} + +void iwl_get_shared_mem_conf(struct iwl_fw_runtime *fwrt) +{ + struct iwl_host_cmd cmd = { + .flags = CMD_WANT_SKB, + .data = { NULL, }, + .len = { 0, }, + }; + struct iwl_rx_packet *pkt; + int ret; + + if (fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_EXTEND_SHARED_MEM_CFG)) + cmd.id = WIDE_ID(SYSTEM_GROUP, SHARED_MEM_CFG_CMD); + else + cmd.id = SHARED_MEM_CFG; + + ret = iwl_trans_send_cmd(fwrt->trans, &cmd); + + if (ret) { + WARN(ret != -ERFKILL, + "Could not send the SMEM command: %d\n", ret); + return; + } + + pkt = cmd.resp_pkt; + if (fwrt->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_22000) + iwl_parse_shared_mem_22000(fwrt, pkt); + else + iwl_parse_shared_mem(fwrt, pkt); + + IWL_DEBUG_INFO(fwrt, "SHARED MEM CFG: got memory offsets/sizes\n"); + + iwl_free_resp(&cmd); +} +IWL_EXPORT_SYMBOL(iwl_get_shared_mem_conf); diff --git a/drivers/net/wireless/intel/iwlwifi/fw/uefi.c b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c new file mode 100644 index 000000000..6d408cd0f --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright(c) 2021 Intel Corporation + */ + +#include "iwl-drv.h" +#include "pnvm.h" +#include "iwl-prph.h" +#include "iwl-io.h" + +#include "fw/uefi.h" +#include "fw/api/alive.h" +#include <linux/efi.h> +#include "fw/runtime.h" + +#define IWL_EFI_VAR_GUID EFI_GUID(0x92daaf2f, 0xc02b, 0x455b, \ + 0xb2, 0xec, 0xf5, 0xa3, \ + 0x59, 0x4f, 0x4a, 0xea) + +void *iwl_uefi_get_pnvm(struct iwl_trans *trans, size_t *len) +{ + void *data; + unsigned long package_size; + efi_status_t status; + + *len = 0; + + if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) + return ERR_PTR(-ENODEV); + + /* + * TODO: we hardcode a maximum length here, because reading + * from the UEFI is not working. To implement this properly, + * we have to call efivar_entry_size(). + */ + package_size = IWL_HARDCODED_PNVM_SIZE; + + data = kmalloc(package_size, GFP_KERNEL); + if (!data) + return ERR_PTR(-ENOMEM); + + status = efi.get_variable(IWL_UEFI_OEM_PNVM_NAME, &IWL_EFI_VAR_GUID, + NULL, &package_size, data); + if (status != EFI_SUCCESS) { + IWL_DEBUG_FW(trans, + "PNVM UEFI variable not found 0x%lx (len %lu)\n", + status, package_size); + kfree(data); + return ERR_PTR(-ENOENT); + } + + IWL_DEBUG_FW(trans, "Read PNVM from UEFI with size %lu\n", package_size); + *len = package_size; + + return data; +} + +static void *iwl_uefi_reduce_power_section(struct iwl_trans *trans, + const u8 *data, size_t len) +{ + const struct iwl_ucode_tlv *tlv; + u8 *reduce_power_data = NULL, *tmp; + u32 size = 0; + + IWL_DEBUG_FW(trans, "Handling REDUCE_POWER section\n"); + + while (len >= sizeof(*tlv)) { + u32 tlv_len, tlv_type; + + len -= sizeof(*tlv); + tlv = (const void *)data; + + tlv_len = le32_to_cpu(tlv->length); + tlv_type = le32_to_cpu(tlv->type); + + if (len < tlv_len) { + IWL_ERR(trans, "invalid TLV len: %zd/%u\n", + len, tlv_len); + kfree(reduce_power_data); + reduce_power_data = ERR_PTR(-EINVAL); + goto out; + } + + data += sizeof(*tlv); + + switch (tlv_type) { + case IWL_UCODE_TLV_MEM_DESC: { + IWL_DEBUG_FW(trans, + "Got IWL_UCODE_TLV_MEM_DESC len %d\n", + tlv_len); + + IWL_DEBUG_FW(trans, "Adding data (size %d)\n", tlv_len); + + tmp = krealloc(reduce_power_data, size + tlv_len, GFP_KERNEL); + if (!tmp) { + IWL_DEBUG_FW(trans, + "Couldn't allocate (more) reduce_power_data\n"); + + kfree(reduce_power_data); + reduce_power_data = ERR_PTR(-ENOMEM); + goto out; + } + + reduce_power_data = tmp; + + memcpy(reduce_power_data + size, data, tlv_len); + + size += tlv_len; + + break; + } + case IWL_UCODE_TLV_PNVM_SKU: + IWL_DEBUG_FW(trans, + "New REDUCE_POWER section started, stop parsing.\n"); + goto done; + default: + IWL_DEBUG_FW(trans, "Found TLV 0x%0x, len %d\n", + tlv_type, tlv_len); + break; + } + + len -= ALIGN(tlv_len, 4); + data += ALIGN(tlv_len, 4); + } + +done: + if (!size) { + IWL_DEBUG_FW(trans, "Empty REDUCE_POWER, skipping.\n"); + /* Better safe than sorry, but 'reduce_power_data' should + * always be NULL if !size. + */ + kfree(reduce_power_data); + reduce_power_data = ERR_PTR(-ENOENT); + goto out; + } + + IWL_INFO(trans, "loaded REDUCE_POWER\n"); + +out: + return reduce_power_data; +} + +static void *iwl_uefi_reduce_power_parse(struct iwl_trans *trans, + const u8 *data, size_t len) +{ + const struct iwl_ucode_tlv *tlv; + void *sec_data; + + IWL_DEBUG_FW(trans, "Parsing REDUCE_POWER data\n"); + + while (len >= sizeof(*tlv)) { + u32 tlv_len, tlv_type; + + len -= sizeof(*tlv); + tlv = (const void *)data; + + tlv_len = le32_to_cpu(tlv->length); + tlv_type = le32_to_cpu(tlv->type); + + if (len < tlv_len) { + IWL_ERR(trans, "invalid TLV len: %zd/%u\n", + len, tlv_len); + return ERR_PTR(-EINVAL); + } + + if (tlv_type == IWL_UCODE_TLV_PNVM_SKU) { + const struct iwl_sku_id *sku_id = + (const void *)(data + sizeof(*tlv)); + + IWL_DEBUG_FW(trans, + "Got IWL_UCODE_TLV_PNVM_SKU len %d\n", + tlv_len); + IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n", + le32_to_cpu(sku_id->data[0]), + le32_to_cpu(sku_id->data[1]), + le32_to_cpu(sku_id->data[2])); + + data += sizeof(*tlv) + ALIGN(tlv_len, 4); + len -= ALIGN(tlv_len, 4); + + if (trans->sku_id[0] == le32_to_cpu(sku_id->data[0]) && + trans->sku_id[1] == le32_to_cpu(sku_id->data[1]) && + trans->sku_id[2] == le32_to_cpu(sku_id->data[2])) { + sec_data = iwl_uefi_reduce_power_section(trans, + data, + len); + if (!IS_ERR(sec_data)) + return sec_data; + } else { + IWL_DEBUG_FW(trans, "SKU ID didn't match!\n"); + } + } else { + data += sizeof(*tlv) + ALIGN(tlv_len, 4); + len -= ALIGN(tlv_len, 4); + } + } + + return ERR_PTR(-ENOENT); +} + +void *iwl_uefi_get_reduced_power(struct iwl_trans *trans, size_t *len) +{ + struct pnvm_sku_package *package; + void *data = NULL; + unsigned long package_size; + efi_status_t status; + + *len = 0; + + if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) + return ERR_PTR(-ENODEV); + + /* + * TODO: we hardcode a maximum length here, because reading + * from the UEFI is not working. To implement this properly, + * we have to call efivar_entry_size(). + */ + package_size = IWL_HARDCODED_REDUCE_POWER_SIZE; + + package = kmalloc(package_size, GFP_KERNEL); + if (!package) + return ERR_PTR(-ENOMEM); + + status = efi.get_variable(IWL_UEFI_REDUCED_POWER_NAME, &IWL_EFI_VAR_GUID, + NULL, &package_size, data); + if (status != EFI_SUCCESS) { + IWL_DEBUG_FW(trans, + "Reduced Power UEFI variable not found 0x%lx (len %lu)\n", + status, package_size); + kfree(package); + return ERR_PTR(-ENOENT); + } + + IWL_DEBUG_FW(trans, "Read reduced power from UEFI with size %lu\n", + package_size); + *len = package_size; + + IWL_DEBUG_FW(trans, "rev %d, total_size %d, n_skus %d\n", + package->rev, package->total_size, package->n_skus); + + data = iwl_uefi_reduce_power_parse(trans, package->data, + *len - sizeof(*package)); + + kfree(package); + + return data; +} + +#ifdef CONFIG_ACPI +static int iwl_uefi_sgom_parse(struct uefi_cnv_wlan_sgom_data *sgom_data, + struct iwl_fw_runtime *fwrt) +{ + int i, j; + + if (sgom_data->revision != 1) + return -EINVAL; + + memcpy(fwrt->sgom_table.offset_map, sgom_data->offset_map, + sizeof(fwrt->sgom_table.offset_map)); + + for (i = 0; i < MCC_TO_SAR_OFFSET_TABLE_ROW_SIZE; i++) { + for (j = 0; j < MCC_TO_SAR_OFFSET_TABLE_COL_SIZE; j++) { + /* since each byte is composed of to values, */ + /* one for each letter, */ + /* extract and check each of them separately */ + u8 value = fwrt->sgom_table.offset_map[i][j]; + u8 low = value & 0xF; + u8 high = (value & 0xF0) >> 4; + + if (high > fwrt->geo_num_profiles) + high = 0; + if (low > fwrt->geo_num_profiles) + low = 0; + fwrt->sgom_table.offset_map[i][j] = (high << 4) | low; + } + } + + fwrt->sgom_enabled = true; + return 0; +} + +void iwl_uefi_get_sgom_table(struct iwl_trans *trans, + struct iwl_fw_runtime *fwrt) +{ + struct uefi_cnv_wlan_sgom_data *data; + unsigned long package_size; + efi_status_t status; + int ret; + + if (!fwrt->geo_enabled || + !efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) + return; + + /* TODO: we hardcode a maximum length here, because reading + * from the UEFI is not working. To implement this properly, + * we have to call efivar_entry_size(). + */ + package_size = IWL_HARDCODED_SGOM_SIZE; + + data = kmalloc(package_size, GFP_KERNEL); + if (!data) + return; + + status = efi.get_variable(IWL_UEFI_SGOM_NAME, &IWL_EFI_VAR_GUID, + NULL, &package_size, data); + if (status != EFI_SUCCESS) { + IWL_DEBUG_FW(trans, + "SGOM UEFI variable not found 0x%lx\n", status); + goto out_free; + } + + IWL_DEBUG_FW(trans, "Read SGOM from UEFI with size %lu\n", + package_size); + + ret = iwl_uefi_sgom_parse(data, fwrt); + if (ret < 0) + IWL_DEBUG_FW(trans, "Cannot read SGOM tables. rev is invalid\n"); + +out_free: + kfree(data); + +} +IWL_EXPORT_SYMBOL(iwl_uefi_get_sgom_table); +#endif /* CONFIG_ACPI */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/uefi.h b/drivers/net/wireless/intel/iwlwifi/fw/uefi.h new file mode 100644 index 000000000..09d2a971b --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/fw/uefi.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright(c) 2021 Intel Corporation + */ +#ifndef __iwl_fw_uefi__ +#define __iwl_fw_uefi__ + +#define IWL_UEFI_OEM_PNVM_NAME L"UefiCnvWlanOemSignedPnvm" +#define IWL_UEFI_REDUCED_POWER_NAME L"UefiCnvWlanReducedPower" +#define IWL_UEFI_SGOM_NAME L"UefiCnvWlanSarGeoOffsetMapping" + +/* + * TODO: we have these hardcoded values that the caller must pass, + * because reading from the UEFI is not working. To implement this + * properly, we have to change iwl_pnvm_get_from_uefi() to call + * efivar_entry_size() and return the value to the caller instead. + */ +#define IWL_HARDCODED_PNVM_SIZE 4096 +#define IWL_HARDCODED_REDUCE_POWER_SIZE 32768 +#define IWL_HARDCODED_SGOM_SIZE 339 + +struct pnvm_sku_package { + u8 rev; + u32 total_size; + u8 n_skus; + u32 reserved[2]; + u8 data[]; +} __packed; + +struct uefi_cnv_wlan_sgom_data { + u8 revision; + u8 offset_map[IWL_HARDCODED_SGOM_SIZE - 1]; +} __packed; + +/* + * This is known to be broken on v4.19 and to work on v5.4. Until we + * figure out why this is the case and how to make it work, simply + * disable the feature in old kernels. + */ +#ifdef CONFIG_EFI +void *iwl_uefi_get_pnvm(struct iwl_trans *trans, size_t *len); +void *iwl_uefi_get_reduced_power(struct iwl_trans *trans, size_t *len); +#else /* CONFIG_EFI */ +static inline +void *iwl_uefi_get_pnvm(struct iwl_trans *trans, size_t *len) +{ + return ERR_PTR(-EOPNOTSUPP); +} + +static inline +void *iwl_uefi_get_reduced_power(struct iwl_trans *trans, size_t *len) +{ + return ERR_PTR(-EOPNOTSUPP); +} +#endif /* CONFIG_EFI */ + +#if defined(CONFIG_EFI) && defined(CONFIG_ACPI) +void iwl_uefi_get_sgom_table(struct iwl_trans *trans, struct iwl_fw_runtime *fwrt); +#else +static inline +void iwl_uefi_get_sgom_table(struct iwl_trans *trans, struct iwl_fw_runtime *fwrt) +{ +} +#endif +#endif /* __iwl_fw_uefi__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-agn-hw.h b/drivers/net/wireless/intel/iwlwifi/iwl-agn-hw.h new file mode 100644 index 000000000..ee85f460a --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-agn-hw.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014 Intel Corporation + */ +/* + * Please use this file (iwl-agn-hw.h) only for hardware-related definitions. + */ + +#ifndef __iwl_agn_hw_h__ +#define __iwl_agn_hw_h__ + +#define IWLAGN_RTC_INST_LOWER_BOUND (0x000000) +#define IWLAGN_RTC_INST_UPPER_BOUND (0x020000) + +#define IWLAGN_RTC_DATA_LOWER_BOUND (0x800000) +#define IWLAGN_RTC_DATA_UPPER_BOUND (0x80C000) + +#define IWLAGN_RTC_INST_SIZE (IWLAGN_RTC_INST_UPPER_BOUND - \ + IWLAGN_RTC_INST_LOWER_BOUND) +#define IWLAGN_RTC_DATA_SIZE (IWLAGN_RTC_DATA_UPPER_BOUND - \ + IWLAGN_RTC_DATA_LOWER_BOUND) + +#define IWL60_RTC_INST_LOWER_BOUND (0x000000) +#define IWL60_RTC_INST_UPPER_BOUND (0x040000) +#define IWL60_RTC_DATA_LOWER_BOUND (0x800000) +#define IWL60_RTC_DATA_UPPER_BOUND (0x814000) +#define IWL60_RTC_INST_SIZE \ + (IWL60_RTC_INST_UPPER_BOUND - IWL60_RTC_INST_LOWER_BOUND) +#define IWL60_RTC_DATA_SIZE \ + (IWL60_RTC_DATA_UPPER_BOUND - IWL60_RTC_DATA_LOWER_BOUND) + +/* RSSI to dBm */ +#define IWLAGN_RSSI_OFFSET 44 + +#define IWLAGN_DEFAULT_TX_RETRY 15 +#define IWLAGN_MGMT_DFAULT_RETRY_LIMIT 3 +#define IWLAGN_RTS_DFAULT_RETRY_LIMIT 60 +#define IWLAGN_BAR_DFAULT_RETRY_LIMIT 60 +#define IWLAGN_LOW_RETRY_LIMIT 7 + +/* Limit range of txpower output target to be between these values */ +#define IWLAGN_TX_POWER_TARGET_POWER_MIN (0) /* 0 dBm: 1 milliwatt */ +#define IWLAGN_TX_POWER_TARGET_POWER_MAX (16) /* 16 dBm */ + +/* EEPROM */ +#define IWLAGN_EEPROM_IMG_SIZE 2048 + +/* high blocks contain PAPD data */ +#define OTP_HIGH_IMAGE_SIZE_6x00 (6 * 512 * sizeof(u16)) /* 6 KB */ +#define OTP_HIGH_IMAGE_SIZE_1000 (0x200 * sizeof(u16)) /* 1024 bytes */ +#define OTP_MAX_LL_ITEMS_1000 (3) /* OTP blocks for 1000 */ +#define OTP_MAX_LL_ITEMS_6x00 (4) /* OTP blocks for 6x00 */ +#define OTP_MAX_LL_ITEMS_6x50 (7) /* OTP blocks for 6x50 */ +#define OTP_MAX_LL_ITEMS_2x00 (4) /* OTP blocks for 2x00 */ + + +#define IWLAGN_NUM_QUEUES 20 + +#endif /* __iwl_agn_hw_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-config.h b/drivers/net/wireless/intel/iwlwifi/iwl-config.h new file mode 100644 index 000000000..cfa5e1b3c --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-config.h @@ -0,0 +1,664 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __IWL_CONFIG_H__ +#define __IWL_CONFIG_H__ + +#include <linux/types.h> +#include <linux/netdevice.h> +#include <linux/ieee80211.h> +#include <linux/nl80211.h> +#include "iwl-csr.h" + +enum iwl_device_family { + IWL_DEVICE_FAMILY_UNDEFINED, + IWL_DEVICE_FAMILY_1000, + IWL_DEVICE_FAMILY_100, + IWL_DEVICE_FAMILY_2000, + IWL_DEVICE_FAMILY_2030, + IWL_DEVICE_FAMILY_105, + IWL_DEVICE_FAMILY_135, + IWL_DEVICE_FAMILY_5000, + IWL_DEVICE_FAMILY_5150, + IWL_DEVICE_FAMILY_6000, + IWL_DEVICE_FAMILY_6000i, + IWL_DEVICE_FAMILY_6005, + IWL_DEVICE_FAMILY_6030, + IWL_DEVICE_FAMILY_6050, + IWL_DEVICE_FAMILY_6150, + IWL_DEVICE_FAMILY_7000, + IWL_DEVICE_FAMILY_8000, + IWL_DEVICE_FAMILY_9000, + IWL_DEVICE_FAMILY_22000, + IWL_DEVICE_FAMILY_AX210, + IWL_DEVICE_FAMILY_BZ, +}; + +/* + * LED mode + * IWL_LED_DEFAULT: use device default + * IWL_LED_RF_STATE: turn LED on/off based on RF state + * LED ON = RF ON + * LED OFF = RF OFF + * IWL_LED_BLINK: adjust led blink rate based on blink table + * IWL_LED_DISABLE: led disabled + */ +enum iwl_led_mode { + IWL_LED_DEFAULT, + IWL_LED_RF_STATE, + IWL_LED_BLINK, + IWL_LED_DISABLE, +}; + +/** + * enum iwl_nvm_type - nvm formats + * @IWL_NVM: the regular format + * @IWL_NVM_EXT: extended NVM format + * @IWL_NVM_SDP: NVM format used by 3168 series + */ +enum iwl_nvm_type { + IWL_NVM, + IWL_NVM_EXT, + IWL_NVM_SDP, +}; + +/* + * This is the threshold value of plcp error rate per 100mSecs. It is + * used to set and check for the validity of plcp_delta. + */ +#define IWL_MAX_PLCP_ERR_THRESHOLD_MIN 1 +#define IWL_MAX_PLCP_ERR_THRESHOLD_DEF 50 +#define IWL_MAX_PLCP_ERR_LONG_THRESHOLD_DEF 100 +#define IWL_MAX_PLCP_ERR_EXT_LONG_THRESHOLD_DEF 200 +#define IWL_MAX_PLCP_ERR_THRESHOLD_MAX 255 +#define IWL_MAX_PLCP_ERR_THRESHOLD_DISABLE 0 + +/* TX queue watchdog timeouts in mSecs */ +#define IWL_WATCHDOG_DISABLED 0 +#define IWL_DEF_WD_TIMEOUT 2500 +#define IWL_LONG_WD_TIMEOUT 10000 +#define IWL_MAX_WD_TIMEOUT 120000 + +#define IWL_DEFAULT_MAX_TX_POWER 22 +#define IWL_TX_CSUM_NETIF_FLAGS (NETIF_F_IPV6_CSUM | NETIF_F_IP_CSUM |\ + NETIF_F_TSO | NETIF_F_TSO6) +#define IWL_TX_CSUM_NETIF_FLAGS_BZ (NETIF_F_HW_CSUM | NETIF_F_TSO | NETIF_F_TSO6) +#define IWL_CSUM_NETIF_FLAGS_MASK (IWL_TX_CSUM_NETIF_FLAGS | \ + IWL_TX_CSUM_NETIF_FLAGS_BZ | \ + NETIF_F_RXCSUM) + +/* Antenna presence definitions */ +#define ANT_NONE 0x0 +#define ANT_INVALID 0xff +#define ANT_A BIT(0) +#define ANT_B BIT(1) +#define ANT_C BIT(2) +#define ANT_AB (ANT_A | ANT_B) +#define ANT_AC (ANT_A | ANT_C) +#define ANT_BC (ANT_B | ANT_C) +#define ANT_ABC (ANT_A | ANT_B | ANT_C) + + +static inline u8 num_of_ant(u8 mask) +{ + return !!((mask) & ANT_A) + + !!((mask) & ANT_B) + + !!((mask) & ANT_C); +} + +/** + * struct iwl_base_params - params not likely to change within a device family + * @max_ll_items: max number of OTP blocks + * @shadow_ram_support: shadow support for OTP memory + * @led_compensation: compensate on the led on/off time per HW according + * to the deviation to achieve the desired led frequency. + * The detail algorithm is described in iwl-led.c + * @wd_timeout: TX queues watchdog timeout + * @max_event_log_size: size of event log buffer size for ucode event logging + * @shadow_reg_enable: HW shadow register support + * @apmg_wake_up_wa: should the MAC access REQ be asserted when a command + * is in flight. This is due to a HW bug in 7260, 3160 and 7265. + * @scd_chain_ext_wa: should the chain extension feature in SCD be disabled. + * @max_tfd_queue_size: max number of entries in tfd queue. + */ +struct iwl_base_params { + unsigned int wd_timeout; + + u16 eeprom_size; + u16 max_event_log_size; + + u8 pll_cfg:1, /* for iwl_pcie_apm_init() */ + shadow_ram_support:1, + shadow_reg_enable:1, + pcie_l1_allowed:1, + apmg_wake_up_wa:1, + scd_chain_ext_wa:1; + + u16 num_of_queues; /* def: HW dependent */ + u32 max_tfd_queue_size; /* def: HW dependent */ + + u8 max_ll_items; + u8 led_compensation; +}; + +/* + * @stbc: support Tx STBC and 1*SS Rx STBC + * @ldpc: support Tx/Rx with LDPC + * @use_rts_for_aggregation: use rts/cts protection for HT traffic + * @ht40_bands: bitmap of bands (using %NL80211_BAND_*) that support HT40 + */ +struct iwl_ht_params { + u8 ht_greenfield_support:1, + stbc:1, + ldpc:1, + use_rts_for_aggregation:1; + u8 ht40_bands; +}; + +/* + * Tx-backoff threshold + * @temperature: The threshold in Celsius + * @backoff: The tx-backoff in uSec + */ +struct iwl_tt_tx_backoff { + s32 temperature; + u32 backoff; +}; + +#define TT_TX_BACKOFF_SIZE 6 + +/** + * struct iwl_tt_params - thermal throttling parameters + * @ct_kill_entry: CT Kill entry threshold + * @ct_kill_exit: CT Kill exit threshold + * @ct_kill_duration: The time intervals (in uSec) in which the driver needs + * to checks whether to exit CT Kill. + * @dynamic_smps_entry: Dynamic SMPS entry threshold + * @dynamic_smps_exit: Dynamic SMPS exit threshold + * @tx_protection_entry: TX protection entry threshold + * @tx_protection_exit: TX protection exit threshold + * @tx_backoff: Array of thresholds for tx-backoff , in ascending order. + * @support_ct_kill: Support CT Kill? + * @support_dynamic_smps: Support dynamic SMPS? + * @support_tx_protection: Support tx protection? + * @support_tx_backoff: Support tx-backoff? + */ +struct iwl_tt_params { + u32 ct_kill_entry; + u32 ct_kill_exit; + u32 ct_kill_duration; + u32 dynamic_smps_entry; + u32 dynamic_smps_exit; + u32 tx_protection_entry; + u32 tx_protection_exit; + struct iwl_tt_tx_backoff tx_backoff[TT_TX_BACKOFF_SIZE]; + u8 support_ct_kill:1, + support_dynamic_smps:1, + support_tx_protection:1, + support_tx_backoff:1; +}; + +/* + * information on how to parse the EEPROM + */ +#define EEPROM_REG_BAND_1_CHANNELS 0x08 +#define EEPROM_REG_BAND_2_CHANNELS 0x26 +#define EEPROM_REG_BAND_3_CHANNELS 0x42 +#define EEPROM_REG_BAND_4_CHANNELS 0x5C +#define EEPROM_REG_BAND_5_CHANNELS 0x74 +#define EEPROM_REG_BAND_24_HT40_CHANNELS 0x82 +#define EEPROM_REG_BAND_52_HT40_CHANNELS 0x92 +#define EEPROM_6000_REG_BAND_24_HT40_CHANNELS 0x80 +#define EEPROM_REGULATORY_BAND_NO_HT40 0 + +/* lower blocks contain EEPROM image and calibration data */ +#define OTP_LOW_IMAGE_SIZE_2K (2 * 512 * sizeof(u16)) /* 2 KB */ +#define OTP_LOW_IMAGE_SIZE_16K (16 * 512 * sizeof(u16)) /* 16 KB */ +#define OTP_LOW_IMAGE_SIZE_32K (32 * 512 * sizeof(u16)) /* 32 KB */ + +struct iwl_eeprom_params { + const u8 regulatory_bands[7]; + bool enhanced_txpower; +}; + +/* Tx-backoff power threshold + * @pwr: The power limit in mw + * @backoff: The tx-backoff in uSec + */ +struct iwl_pwr_tx_backoff { + u32 pwr; + u32 backoff; +}; + +enum iwl_cfg_trans_ltr_delay { + IWL_CFG_TRANS_LTR_DELAY_NONE = 0, + IWL_CFG_TRANS_LTR_DELAY_200US = 1, + IWL_CFG_TRANS_LTR_DELAY_2500US = 2, + IWL_CFG_TRANS_LTR_DELAY_1820US = 3, +}; + +/** + * struct iwl_cfg_trans - information needed to start the trans + * + * These values are specific to the device ID and do not change when + * multiple configs are used for a single device ID. They values are + * used, among other things, to boot the NIC so that the HW REV or + * RFID can be read before deciding the remaining parameters to use. + * + * @base_params: pointer to basic parameters + * @csr: csr flags and addresses that are different across devices + * @device_family: the device family + * @umac_prph_offset: offset to add to UMAC periphery address + * @xtal_latency: power up latency to get the xtal stabilized + * @extra_phy_cfg_flags: extra configuration flags to pass to the PHY + * @rf_id: need to read rf_id to determine the firmware image + * @use_tfh: use TFH + * @gen2: 22000 and on transport operation + * @mq_rx_supported: multi-queue rx support + * @integrated: discrete or integrated + * @low_latency_xtal: use the low latency xtal if supported + * @ltr_delay: LTR delay parameter, &enum iwl_cfg_trans_ltr_delay. + * @imr_enabled: use the IMR if supported. + */ +struct iwl_cfg_trans_params { + const struct iwl_base_params *base_params; + enum iwl_device_family device_family; + u32 umac_prph_offset; + u32 xtal_latency; + u32 extra_phy_cfg_flags; + u32 rf_id:1, + use_tfh:1, + gen2:1, + mq_rx_supported:1, + integrated:1, + low_latency_xtal:1, + bisr_workaround:1, + ltr_delay:2, + imr_enabled:1; +}; + +/** + * struct iwl_fw_mon_reg - FW monitor register info + * @addr: register address + * @mask: register mask + */ +struct iwl_fw_mon_reg { + u32 addr; + u32 mask; +}; + +/** + * struct iwl_fw_mon_regs - FW monitor registers + * @write_ptr: write pointer register + * @cycle_cnt: cycle count register + * @cur_frag: current fragment in use + */ +struct iwl_fw_mon_regs { + struct iwl_fw_mon_reg write_ptr; + struct iwl_fw_mon_reg cycle_cnt; + struct iwl_fw_mon_reg cur_frag; +}; + +/** + * struct iwl_cfg + * @trans: the trans-specific configuration part + * @name: Official name of the device + * @fw_name_pre: Firmware filename prefix. The api version and extension + * (.ucode) will be added to filename before loading from disk. The + * filename is constructed as fw_name_pre<api>.ucode. + * @ucode_api_max: Highest version of uCode API supported by driver. + * @ucode_api_min: Lowest version of uCode API supported by driver. + * @max_inst_size: The maximal length of the fw inst section (only DVM) + * @max_data_size: The maximal length of the fw data section (only DVM) + * @valid_tx_ant: valid transmit antenna + * @valid_rx_ant: valid receive antenna + * @non_shared_ant: the antenna that is for WiFi only + * @nvm_ver: NVM version + * @nvm_calib_ver: NVM calibration version + * @lib: pointer to the lib ops + * @ht_params: point to ht parameters + * @led_mode: 0=blinking, 1=On(RF On)/Off(RF Off) + * @rx_with_siso_diversity: 1x1 device with rx antenna diversity + * @tx_with_siso_diversity: 1x1 device with tx antenna diversity + * @internal_wimax_coex: internal wifi/wimax combo device + * @high_temp: Is this NIC is designated to be in high temperature. + * @host_interrupt_operation_mode: device needs host interrupt operation + * mode set + * @nvm_hw_section_num: the ID of the HW NVM section + * @mac_addr_from_csr: read HW address from CSR registers at this offset + * @features: hw features, any combination of feature_passlist + * @pwr_tx_backoffs: translation table between power limits and backoffs + * @max_tx_agg_size: max TX aggregation size of the ADDBA request/response + * @dccm_offset: offset from which DCCM begins + * @dccm_len: length of DCCM (including runtime stack CCM) + * @dccm2_offset: offset from which the second DCCM begins + * @dccm2_len: length of the second DCCM + * @smem_offset: offset from which the SMEM begins + * @smem_len: the length of SMEM + * @vht_mu_mimo_supported: VHT MU-MIMO support + * @cdb: CDB support + * @nvm_type: see &enum iwl_nvm_type + * @d3_debug_data_base_addr: base address where D3 debug data is stored + * @d3_debug_data_length: length of the D3 debug data + * @bisr_workaround: BISR hardware workaround (for 22260 series devices) + * @min_txq_size: minimum number of slots required in a TX queue + * @uhb_supported: ultra high band channels supported + * @min_ba_txq_size: minimum number of slots required in a TX queue which + * based on hardware support (HE - 256, EHT - 1K). + * @num_rbds: number of receive buffer descriptors to use + * (only used for multi-queue capable devices) + * @mac_addr_csr_base: CSR base register for MAC address access, if not set + * assume 0x380 + * + * We enable the driver to be backward compatible wrt. hardware features. + * API differences in uCode shouldn't be handled here but through TLVs + * and/or the uCode API version instead. + */ +struct iwl_cfg { + struct iwl_cfg_trans_params trans; + /* params specific to an individual device within a device family */ + const char *name; + const char *fw_name_pre; + /* params likely to change within a device family */ + const struct iwl_ht_params *ht_params; + const struct iwl_eeprom_params *eeprom_params; + const struct iwl_pwr_tx_backoff *pwr_tx_backoffs; + const char *default_nvm_file_C_step; + const struct iwl_tt_params *thermal_params; + enum iwl_led_mode led_mode; + enum iwl_nvm_type nvm_type; + u32 max_data_size; + u32 max_inst_size; + netdev_features_t features; + u32 dccm_offset; + u32 dccm_len; + u32 dccm2_offset; + u32 dccm2_len; + u32 smem_offset; + u32 smem_len; + u16 nvm_ver; + u16 nvm_calib_ver; + u32 rx_with_siso_diversity:1, + tx_with_siso_diversity:1, + bt_shared_single_ant:1, + internal_wimax_coex:1, + host_interrupt_operation_mode:1, + high_temp:1, + mac_addr_from_csr:10, + lp_xtal_workaround:1, + disable_dummy_notification:1, + apmg_not_supported:1, + vht_mu_mimo_supported:1, + cdb:1, + dbgc_supported:1, + uhb_supported:1; + u8 valid_tx_ant; + u8 valid_rx_ant; + u8 non_shared_ant; + u8 nvm_hw_section_num; + u8 max_tx_agg_size; + u8 ucode_api_max; + u8 ucode_api_min; + u16 num_rbds; + u32 min_umac_error_event_table; + u32 d3_debug_data_base_addr; + u32 d3_debug_data_length; + u32 min_txq_size; + u32 gp2_reg_addr; + u32 min_ba_txq_size; + const struct iwl_fw_mon_regs mon_dram_regs; + const struct iwl_fw_mon_regs mon_smem_regs; + const struct iwl_fw_mon_regs mon_dbgi_regs; +}; + +#define IWL_CFG_ANY (~0) + +#define IWL_CFG_MAC_TYPE_PU 0x31 +#define IWL_CFG_MAC_TYPE_PNJ 0x32 +#define IWL_CFG_MAC_TYPE_TH 0x32 +#define IWL_CFG_MAC_TYPE_QU 0x33 +#define IWL_CFG_MAC_TYPE_QUZ 0x35 +#define IWL_CFG_MAC_TYPE_QNJ 0x36 +#define IWL_CFG_MAC_TYPE_SO 0x37 +#define IWL_CFG_MAC_TYPE_SNJ 0x42 +#define IWL_CFG_MAC_TYPE_SOF 0x43 +#define IWL_CFG_MAC_TYPE_MA 0x44 +#define IWL_CFG_MAC_TYPE_BZ 0x46 +#define IWL_CFG_MAC_TYPE_GL 0x47 + +#define IWL_CFG_RF_TYPE_TH 0x105 +#define IWL_CFG_RF_TYPE_TH1 0x108 +#define IWL_CFG_RF_TYPE_JF2 0x105 +#define IWL_CFG_RF_TYPE_JF1 0x108 +#define IWL_CFG_RF_TYPE_HR2 0x10A +#define IWL_CFG_RF_TYPE_HR1 0x10C +#define IWL_CFG_RF_TYPE_GF 0x10D +#define IWL_CFG_RF_TYPE_MR 0x110 +#define IWL_CFG_RF_TYPE_MS 0x111 +#define IWL_CFG_RF_TYPE_FM 0x112 + +#define IWL_CFG_RF_ID_TH 0x1 +#define IWL_CFG_RF_ID_TH1 0x1 +#define IWL_CFG_RF_ID_JF 0x3 +#define IWL_CFG_RF_ID_JF1 0x6 +#define IWL_CFG_RF_ID_JF1_DIV 0xA +#define IWL_CFG_RF_ID_HR 0x7 +#define IWL_CFG_RF_ID_HR1 0x4 + +#define IWL_CFG_NO_160 0x1 +#define IWL_CFG_160 0x0 + +#define IWL_CFG_CORES_BT 0x0 +#define IWL_CFG_CORES_BT_GNSS 0x5 + +#define IWL_CFG_NO_CDB 0x0 +#define IWL_CFG_CDB 0x1 + +#define IWL_CFG_NO_JACKET 0x0 +#define IWL_CFG_IS_JACKET 0x1 + +#define IWL_SUBDEVICE_RF_ID(subdevice) ((u16)((subdevice) & 0x00F0) >> 4) +#define IWL_SUBDEVICE_NO_160(subdevice) ((u16)((subdevice) & 0x0200) >> 9) +#define IWL_SUBDEVICE_CORES(subdevice) ((u16)((subdevice) & 0x1C00) >> 10) + +struct iwl_dev_info { + u16 device; + u16 subdevice; + u16 mac_type; + u16 rf_type; + u8 mac_step; + u8 rf_id; + u8 no_160; + u8 cores; + u8 cdb; + u8 jacket; + const struct iwl_cfg *cfg; + const char *name; +}; + +/* + * This list declares the config structures for all devices. + */ +extern const struct iwl_cfg_trans_params iwl9000_trans_cfg; +extern const struct iwl_cfg_trans_params iwl9560_trans_cfg; +extern const struct iwl_cfg_trans_params iwl9560_long_latency_trans_cfg; +extern const struct iwl_cfg_trans_params iwl9560_shared_clk_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_qnj_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_qu_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_qu_medium_latency_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_qu_long_latency_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_ax200_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_snj_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_so_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_so_long_latency_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_so_long_latency_imr_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_ma_trans_cfg; +extern const struct iwl_cfg_trans_params iwl_bz_trans_cfg; +extern const char iwl9162_name[]; +extern const char iwl9260_name[]; +extern const char iwl9260_1_name[]; +extern const char iwl9270_name[]; +extern const char iwl9461_name[]; +extern const char iwl9462_name[]; +extern const char iwl9560_name[]; +extern const char iwl9162_160_name[]; +extern const char iwl9260_160_name[]; +extern const char iwl9270_160_name[]; +extern const char iwl9461_160_name[]; +extern const char iwl9462_160_name[]; +extern const char iwl9560_160_name[]; +extern const char iwl9260_killer_1550_name[]; +extern const char iwl9560_killer_1550i_name[]; +extern const char iwl9560_killer_1550s_name[]; +extern const char iwl_ax200_name[]; +extern const char iwl_ax203_name[]; +extern const char iwl_ax204_name[]; +extern const char iwl_ax201_name[]; +extern const char iwl_ax101_name[]; +extern const char iwl_ax200_killer_1650w_name[]; +extern const char iwl_ax200_killer_1650x_name[]; +extern const char iwl_ax201_killer_1650s_name[]; +extern const char iwl_ax201_killer_1650i_name[]; +extern const char iwl_ax210_killer_1675w_name[]; +extern const char iwl_ax210_killer_1675x_name[]; +extern const char iwl9560_killer_1550i_160_name[]; +extern const char iwl9560_killer_1550s_160_name[]; +extern const char iwl_ax211_killer_1675s_name[]; +extern const char iwl_ax211_killer_1675i_name[]; +extern const char iwl_ax411_killer_1690s_name[]; +extern const char iwl_ax411_killer_1690i_name[]; +extern const char iwl_ax211_name[]; +extern const char iwl_ax221_name[]; +extern const char iwl_ax231_name[]; +extern const char iwl_ax411_name[]; +extern const char iwl_bz_name[]; +#if IS_ENABLED(CONFIG_IWLDVM) +extern const struct iwl_cfg iwl5300_agn_cfg; +extern const struct iwl_cfg iwl5100_agn_cfg; +extern const struct iwl_cfg iwl5350_agn_cfg; +extern const struct iwl_cfg iwl5100_bgn_cfg; +extern const struct iwl_cfg iwl5100_abg_cfg; +extern const struct iwl_cfg iwl5150_agn_cfg; +extern const struct iwl_cfg iwl5150_abg_cfg; +extern const struct iwl_cfg iwl6005_2agn_cfg; +extern const struct iwl_cfg iwl6005_2abg_cfg; +extern const struct iwl_cfg iwl6005_2bg_cfg; +extern const struct iwl_cfg iwl6005_2agn_sff_cfg; +extern const struct iwl_cfg iwl6005_2agn_d_cfg; +extern const struct iwl_cfg iwl6005_2agn_mow1_cfg; +extern const struct iwl_cfg iwl6005_2agn_mow2_cfg; +extern const struct iwl_cfg iwl1030_bgn_cfg; +extern const struct iwl_cfg iwl1030_bg_cfg; +extern const struct iwl_cfg iwl6030_2agn_cfg; +extern const struct iwl_cfg iwl6030_2abg_cfg; +extern const struct iwl_cfg iwl6030_2bgn_cfg; +extern const struct iwl_cfg iwl6030_2bg_cfg; +extern const struct iwl_cfg iwl6000i_2agn_cfg; +extern const struct iwl_cfg iwl6000i_2abg_cfg; +extern const struct iwl_cfg iwl6000i_2bg_cfg; +extern const struct iwl_cfg iwl6000_3agn_cfg; +extern const struct iwl_cfg iwl6050_2agn_cfg; +extern const struct iwl_cfg iwl6050_2abg_cfg; +extern const struct iwl_cfg iwl6150_bgn_cfg; +extern const struct iwl_cfg iwl6150_bg_cfg; +extern const struct iwl_cfg iwl1000_bgn_cfg; +extern const struct iwl_cfg iwl1000_bg_cfg; +extern const struct iwl_cfg iwl100_bgn_cfg; +extern const struct iwl_cfg iwl100_bg_cfg; +extern const struct iwl_cfg iwl130_bgn_cfg; +extern const struct iwl_cfg iwl130_bg_cfg; +extern const struct iwl_cfg iwl2000_2bgn_cfg; +extern const struct iwl_cfg iwl2000_2bgn_d_cfg; +extern const struct iwl_cfg iwl2030_2bgn_cfg; +extern const struct iwl_cfg iwl6035_2agn_cfg; +extern const struct iwl_cfg iwl6035_2agn_sff_cfg; +extern const struct iwl_cfg iwl105_bgn_cfg; +extern const struct iwl_cfg iwl105_bgn_d_cfg; +extern const struct iwl_cfg iwl135_bgn_cfg; +#endif /* CONFIG_IWLDVM */ +#if IS_ENABLED(CONFIG_IWLMVM) +extern const struct iwl_cfg iwl7260_2ac_cfg; +extern const struct iwl_cfg iwl7260_2ac_cfg_high_temp; +extern const struct iwl_cfg iwl7260_2n_cfg; +extern const struct iwl_cfg iwl7260_n_cfg; +extern const struct iwl_cfg iwl3160_2ac_cfg; +extern const struct iwl_cfg iwl3160_2n_cfg; +extern const struct iwl_cfg iwl3160_n_cfg; +extern const struct iwl_cfg iwl3165_2ac_cfg; +extern const struct iwl_cfg iwl3168_2ac_cfg; +extern const struct iwl_cfg iwl7265_2ac_cfg; +extern const struct iwl_cfg iwl7265_2n_cfg; +extern const struct iwl_cfg iwl7265_n_cfg; +extern const struct iwl_cfg iwl7265d_2ac_cfg; +extern const struct iwl_cfg iwl7265d_2n_cfg; +extern const struct iwl_cfg iwl7265d_n_cfg; +extern const struct iwl_cfg iwl8260_2n_cfg; +extern const struct iwl_cfg iwl8260_2ac_cfg; +extern const struct iwl_cfg iwl8265_2ac_cfg; +extern const struct iwl_cfg iwl8275_2ac_cfg; +extern const struct iwl_cfg iwl4165_2ac_cfg; +extern const struct iwl_cfg iwl9260_2ac_cfg; +extern const struct iwl_cfg iwl9560_qu_b0_jf_b0_cfg; +extern const struct iwl_cfg iwl9560_qu_c0_jf_b0_cfg; +extern const struct iwl_cfg iwl9560_quz_a0_jf_b0_cfg; +extern const struct iwl_cfg iwl9560_qnj_b0_jf_b0_cfg; +extern const struct iwl_cfg iwl9560_2ac_cfg_soc; +extern const struct iwl_cfg iwl_qu_b0_hr1_b0; +extern const struct iwl_cfg iwl_qu_c0_hr1_b0; +extern const struct iwl_cfg iwl_quz_a0_hr1_b0; +extern const struct iwl_cfg iwl_qu_b0_hr_b0; +extern const struct iwl_cfg iwl_qu_c0_hr_b0; +extern const struct iwl_cfg iwl_ax200_cfg_cc; +extern const struct iwl_cfg iwl_ax201_cfg_qu_hr; +extern const struct iwl_cfg iwl_ax201_cfg_qu_c0_hr_b0; +extern const struct iwl_cfg iwl_ax201_cfg_quz_hr; +extern const struct iwl_cfg iwl_ax1650i_cfg_quz_hr; +extern const struct iwl_cfg iwl_ax1650s_cfg_quz_hr; +extern const struct iwl_cfg killer1650s_2ax_cfg_qu_b0_hr_b0; +extern const struct iwl_cfg killer1650i_2ax_cfg_qu_b0_hr_b0; +extern const struct iwl_cfg killer1650s_2ax_cfg_qu_c0_hr_b0; +extern const struct iwl_cfg killer1650i_2ax_cfg_qu_c0_hr_b0; +extern const struct iwl_cfg killer1650x_2ax_cfg; +extern const struct iwl_cfg killer1650w_2ax_cfg; +extern const struct iwl_cfg iwl_qnj_b0_hr_b0_cfg; +extern const struct iwl_cfg iwlax210_2ax_cfg_so_jf_b0; +extern const struct iwl_cfg iwlax211_2ax_cfg_so_gf_a0; +extern const struct iwl_cfg iwlax211_2ax_cfg_so_gf_a0_long; +extern const struct iwl_cfg iwlax210_2ax_cfg_ty_gf_a0; +extern const struct iwl_cfg iwlax411_2ax_cfg_so_gf4_a0; +extern const struct iwl_cfg iwlax411_2ax_cfg_so_gf4_a0_long; +extern const struct iwl_cfg iwlax411_2ax_cfg_sosnj_gf4_a0; +extern const struct iwl_cfg iwlax211_cfg_snj_gf_a0; +extern const struct iwl_cfg iwl_cfg_snj_hr_b0; +extern const struct iwl_cfg iwl_cfg_snj_a0_jf_b0; +extern const struct iwl_cfg iwl_cfg_ma_a0_hr_b0; +extern const struct iwl_cfg iwl_cfg_ma_a0_gf_a0; +extern const struct iwl_cfg iwl_cfg_ma_a0_gf4_a0; +extern const struct iwl_cfg iwl_cfg_ma_a0_mr_a0; +extern const struct iwl_cfg iwl_cfg_ma_a0_ms_a0; +extern const struct iwl_cfg iwl_cfg_ma_a0_fm_a0; +extern const struct iwl_cfg iwl_cfg_snj_a0_mr_a0; +extern const struct iwl_cfg iwl_cfg_snj_a0_ms_a0; +extern const struct iwl_cfg iwl_cfg_so_a0_hr_a0; +extern const struct iwl_cfg iwl_cfg_so_a0_ms_a0; +extern const struct iwl_cfg iwl_cfg_quz_a0_hr_b0; +extern const struct iwl_cfg iwl_cfg_bz_a0_hr_b0; +extern const struct iwl_cfg iwl_cfg_bz_a0_gf_a0; +extern const struct iwl_cfg iwl_cfg_bz_a0_gf4_a0; +extern const struct iwl_cfg iwl_cfg_bz_a0_mr_a0; +extern const struct iwl_cfg iwl_cfg_bz_a0_fm_a0; +extern const struct iwl_cfg iwl_cfg_bz_a0_fm4_a0; +extern const struct iwl_cfg iwl_cfg_gl_a0_fm_a0; +extern const struct iwl_cfg iwl_cfg_gl_b0_fm_b0; +extern const struct iwl_cfg iwl_cfg_bz_z0_gf_a0; +extern const struct iwl_cfg iwl_cfg_bnj_a0_fm_a0; +extern const struct iwl_cfg iwl_cfg_bnj_a0_fm4_a0; +extern const struct iwl_cfg iwl_cfg_bnj_a0_gf_a0; +extern const struct iwl_cfg iwl_cfg_bnj_a0_gf4_a0; +extern const struct iwl_cfg iwl_cfg_bnj_a0_hr_b0; +extern const struct iwl_cfg iwl_cfg_bnj_b0_fm_b0; +#endif /* CONFIG_IWLMVM */ + +#endif /* __IWL_CONFIG_H__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-context-info-gen3.h b/drivers/net/wireless/intel/iwlwifi/iwl-context-info-gen3.h new file mode 100644 index 000000000..b84884034 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-context-info-gen3.h @@ -0,0 +1,269 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2018, 2020-2022 Intel Corporation + */ +#ifndef __iwl_context_info_file_gen3_h__ +#define __iwl_context_info_file_gen3_h__ + +#include "iwl-context-info.h" + +#define CSR_CTXT_INFO_BOOT_CTRL 0x0 +#define CSR_CTXT_INFO_ADDR 0x118 +#define CSR_IML_DATA_ADDR 0x120 +#define CSR_IML_SIZE_ADDR 0x128 +#define CSR_IML_RESP_ADDR 0x12c + +/* Set bit for enabling automatic function boot */ +#define CSR_AUTO_FUNC_BOOT_ENA BIT(1) +/* Set bit for initiating function boot */ +#define CSR_AUTO_FUNC_INIT BIT(7) + +/** + * enum iwl_prph_scratch_mtr_format - tfd size configuration + * @IWL_PRPH_MTR_FORMAT_16B: 16 bit tfd + * @IWL_PRPH_MTR_FORMAT_32B: 32 bit tfd + * @IWL_PRPH_MTR_FORMAT_64B: 64 bit tfd + * @IWL_PRPH_MTR_FORMAT_256B: 256 bit tfd + */ +enum iwl_prph_scratch_mtr_format { + IWL_PRPH_MTR_FORMAT_16B = 0x0, + IWL_PRPH_MTR_FORMAT_32B = 0x40000, + IWL_PRPH_MTR_FORMAT_64B = 0x80000, + IWL_PRPH_MTR_FORMAT_256B = 0xC0000, +}; + +/** + * enum iwl_prph_scratch_flags - PRPH scratch control flags + * @IWL_PRPH_SCRATCH_IMR_DEBUG_EN: IMR support for debug + * @IWL_PRPH_SCRATCH_EARLY_DEBUG_EN: enable early debug conf + * @IWL_PRPH_SCRATCH_EDBG_DEST_DRAM: use DRAM, with size allocated + * in hwm config. + * @IWL_PRPH_SCRATCH_EDBG_DEST_INTERNAL: use buffer on SRAM + * @IWL_PRPH_SCRATCH_EDBG_DEST_ST_ARBITER: use st arbiter, mainly for + * multicomm. + * @IWL_PRPH_SCRATCH_EDBG_DEST_TB22DTF: route debug data to SoC HW + * @IWL_PRPH_SCTATCH_RB_SIZE_4K: Use 4K RB size (the default is 2K) + * @IWL_PRPH_SCRATCH_MTR_MODE: format used for completion - 0: for + * completion descriptor, 1 for responses (legacy) + * @IWL_PRPH_SCRATCH_MTR_FORMAT: a mask for the size of the tfd. + * There are 4 optional values: 0: 16 bit, 1: 32 bit, 2: 64 bit, + * 3: 256 bit. + * @IWL_PRPH_SCRATCH_RB_SIZE_EXT_MASK: RB size full information, ignored + * by older firmware versions, so set IWL_PRPH_SCRATCH_RB_SIZE_4K + * appropriately; use the below values for this. + * @IWL_PRPH_SCRATCH_RB_SIZE_EXT_8K: 8kB RB size + * @IWL_PRPH_SCRATCH_RB_SIZE_EXT_12K: 12kB RB size + * @IWL_PRPH_SCRATCH_RB_SIZE_EXT_16K: 16kB RB size + */ +enum iwl_prph_scratch_flags { + IWL_PRPH_SCRATCH_IMR_DEBUG_EN = BIT(1), + IWL_PRPH_SCRATCH_EARLY_DEBUG_EN = BIT(4), + IWL_PRPH_SCRATCH_EDBG_DEST_DRAM = BIT(8), + IWL_PRPH_SCRATCH_EDBG_DEST_INTERNAL = BIT(9), + IWL_PRPH_SCRATCH_EDBG_DEST_ST_ARBITER = BIT(10), + IWL_PRPH_SCRATCH_EDBG_DEST_TB22DTF = BIT(11), + IWL_PRPH_SCRATCH_RB_SIZE_4K = BIT(16), + IWL_PRPH_SCRATCH_MTR_MODE = BIT(17), + IWL_PRPH_SCRATCH_MTR_FORMAT = BIT(18) | BIT(19), + IWL_PRPH_SCRATCH_RB_SIZE_EXT_MASK = 0xf << 20, + IWL_PRPH_SCRATCH_RB_SIZE_EXT_8K = 8 << 20, + IWL_PRPH_SCRATCH_RB_SIZE_EXT_12K = 9 << 20, + IWL_PRPH_SCRATCH_RB_SIZE_EXT_16K = 10 << 20, +}; + +/* + * struct iwl_prph_scratch_version - version structure + * @mac_id: SKU and revision id + * @version: prph scratch information version id + * @size: the size of the context information in DWs + * @reserved: reserved + */ +struct iwl_prph_scratch_version { + __le16 mac_id; + __le16 version; + __le16 size; + __le16 reserved; +} __packed; /* PERIPH_SCRATCH_VERSION_S */ + +/* + * struct iwl_prph_scratch_control - control structure + * @control_flags: context information flags see &enum iwl_prph_scratch_flags + * @reserved: reserved + */ +struct iwl_prph_scratch_control { + __le32 control_flags; + __le32 reserved; +} __packed; /* PERIPH_SCRATCH_CONTROL_S */ + +/* + * struct iwl_prph_scratch_pnvm_cfg - ror config + * @pnvm_base_addr: PNVM start address + * @pnvm_size: PNVM size in DWs + * @reserved: reserved + */ +struct iwl_prph_scratch_pnvm_cfg { + __le64 pnvm_base_addr; + __le32 pnvm_size; + __le32 reserved; +} __packed; /* PERIPH_SCRATCH_PNVM_CFG_S */ + +/* + * struct iwl_prph_scratch_hwm_cfg - hwm config + * @hwm_base_addr: hwm start address + * @hwm_size: hwm size in DWs + * @debug_token_config: debug preset + */ +struct iwl_prph_scratch_hwm_cfg { + __le64 hwm_base_addr; + __le32 hwm_size; + __le32 debug_token_config; +} __packed; /* PERIPH_SCRATCH_HWM_CFG_S */ + +/* + * struct iwl_prph_scratch_rbd_cfg - RBDs configuration + * @free_rbd_addr: default queue free RB CB base address + * @reserved: reserved + */ +struct iwl_prph_scratch_rbd_cfg { + __le64 free_rbd_addr; + __le32 reserved; +} __packed; /* PERIPH_SCRATCH_RBD_CFG_S */ + +/* + * struct iwl_prph_scratch_uefi_cfg - prph scratch reduce power table + * @base_addr: reduce power table address + * @size: table size in dwords + */ +struct iwl_prph_scratch_uefi_cfg { + __le64 base_addr; + __le32 size; + __le32 reserved; +} __packed; /* PERIPH_SCRATCH_UEFI_CFG_S */ + +/* + * struct iwl_prph_scratch_ctrl_cfg - prph scratch ctrl and config + * @version: version information of context info and HW + * @control: control flags of FH configurations + * @pnvm_cfg: ror configuration + * @hwm_cfg: hwm configuration + * @rbd_cfg: default RX queue configuration + */ +struct iwl_prph_scratch_ctrl_cfg { + struct iwl_prph_scratch_version version; + struct iwl_prph_scratch_control control; + struct iwl_prph_scratch_pnvm_cfg pnvm_cfg; + struct iwl_prph_scratch_hwm_cfg hwm_cfg; + struct iwl_prph_scratch_rbd_cfg rbd_cfg; + struct iwl_prph_scratch_uefi_cfg reduce_power_cfg; +} __packed; /* PERIPH_SCRATCH_CTRL_CFG_S */ + +/* + * struct iwl_prph_scratch - peripheral scratch mapping + * @ctrl_cfg: control and configuration of prph scratch + * @dram: firmware images addresses in DRAM + * @reserved: reserved + */ +struct iwl_prph_scratch { + struct iwl_prph_scratch_ctrl_cfg ctrl_cfg; + __le32 reserved[12]; + struct iwl_context_info_dram dram; +} __packed; /* PERIPH_SCRATCH_S */ + +/* + * struct iwl_prph_info - peripheral information + * @boot_stage_mirror: reflects the value in the Boot Stage CSR register + * @ipc_status_mirror: reflects the value in the IPC Status CSR register + * @sleep_notif: indicates the peripheral sleep status + * @reserved: reserved + */ +struct iwl_prph_info { + __le32 boot_stage_mirror; + __le32 ipc_status_mirror; + __le32 sleep_notif; + __le32 reserved; +} __packed; /* PERIPH_INFO_S */ + +/* + * struct iwl_context_info_gen3 - device INIT configuration + * @version: version of the context information + * @size: size of context information in DWs + * @config: context in which the peripheral would execute - a subset of + * capability csr register published by the peripheral + * @prph_info_base_addr: the peripheral information structure start address + * @cr_head_idx_arr_base_addr: the completion ring head index array + * start address + * @tr_tail_idx_arr_base_addr: the transfer ring tail index array + * start address + * @cr_tail_idx_arr_base_addr: the completion ring tail index array + * start address + * @tr_head_idx_arr_base_addr: the transfer ring head index array + * start address + * @cr_idx_arr_size: number of entries in the completion ring index array + * @tr_idx_arr_size: number of entries in the transfer ring index array + * @mtr_base_addr: the message transfer ring start address + * @mcr_base_addr: the message completion ring start address + * @mtr_size: number of entries which the message transfer ring can hold + * @mcr_size: number of entries which the message completion ring can hold + * @mtr_doorbell_vec: the doorbell vector associated with the message + * transfer ring + * @mcr_doorbell_vec: the doorbell vector associated with the message + * completion ring + * @mtr_msi_vec: the MSI which shall be generated by the peripheral after + * completing a transfer descriptor in the message transfer ring + * @mcr_msi_vec: the MSI which shall be generated by the peripheral after + * completing a completion descriptor in the message completion ring + * @mtr_opt_header_size: the size of the optional header in the transfer + * descriptor associated with the message transfer ring in DWs + * @mtr_opt_footer_size: the size of the optional footer in the transfer + * descriptor associated with the message transfer ring in DWs + * @mcr_opt_header_size: the size of the optional header in the completion + * descriptor associated with the message completion ring in DWs + * @mcr_opt_footer_size: the size of the optional footer in the completion + * descriptor associated with the message completion ring in DWs + * @msg_rings_ctrl_flags: message rings control flags + * @prph_info_msi_vec: the MSI which shall be generated by the peripheral + * after updating the Peripheral Information structure + * @prph_scratch_base_addr: the peripheral scratch structure start address + * @prph_scratch_size: the size of the peripheral scratch structure in DWs + * @reserved: reserved + */ +struct iwl_context_info_gen3 { + __le16 version; + __le16 size; + __le32 config; + __le64 prph_info_base_addr; + __le64 cr_head_idx_arr_base_addr; + __le64 tr_tail_idx_arr_base_addr; + __le64 cr_tail_idx_arr_base_addr; + __le64 tr_head_idx_arr_base_addr; + __le16 cr_idx_arr_size; + __le16 tr_idx_arr_size; + __le64 mtr_base_addr; + __le64 mcr_base_addr; + __le16 mtr_size; + __le16 mcr_size; + __le16 mtr_doorbell_vec; + __le16 mcr_doorbell_vec; + __le16 mtr_msi_vec; + __le16 mcr_msi_vec; + u8 mtr_opt_header_size; + u8 mtr_opt_footer_size; + u8 mcr_opt_header_size; + u8 mcr_opt_footer_size; + __le16 msg_rings_ctrl_flags; + __le16 prph_info_msi_vec; + __le64 prph_scratch_base_addr; + __le32 prph_scratch_size; + __le32 reserved; +} __packed; /* IPC_CONTEXT_INFO_S */ + +int iwl_pcie_ctxt_info_gen3_init(struct iwl_trans *trans, + const struct fw_img *fw); +void iwl_pcie_ctxt_info_gen3_free(struct iwl_trans *trans, bool alive); + +int iwl_trans_pcie_ctx_info_gen3_set_pnvm(struct iwl_trans *trans, + const void *data, u32 len); +int iwl_trans_pcie_ctx_info_gen3_set_reduce_power(struct iwl_trans *trans, + const void *data, u32 len); + +#endif /* __iwl_context_info_file_gen3_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-context-info.h b/drivers/net/wireless/intel/iwlwifi/iwl-context-info.h new file mode 100644 index 000000000..4354d5aca --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-context-info.h @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2018-2020 Intel Corporation + */ +#ifndef __iwl_context_info_file_h__ +#define __iwl_context_info_file_h__ + +/* maximmum number of DRAM map entries supported by FW */ +#define IWL_MAX_DRAM_ENTRY 64 +#define CSR_CTXT_INFO_BA 0x40 + +/** + * enum iwl_context_info_flags - Context information control flags + * @IWL_CTXT_INFO_AUTO_FUNC_INIT: If set, FW will not wait before interrupting + * the init done for driver command that configures several system modes + * @IWL_CTXT_INFO_EARLY_DEBUG: enable early debug + * @IWL_CTXT_INFO_ENABLE_CDMP: enable core dump + * @IWL_CTXT_INFO_RB_CB_SIZE: mask of the RBD Cyclic Buffer Size + * exponent, the actual size is 2**value, valid sizes are 8-2048. + * The value is four bits long. Maximum valid exponent is 12 + * @IWL_CTXT_INFO_TFD_FORMAT_LONG: use long TFD Format (the + * default is short format - not supported by the driver) + * @IWL_CTXT_INFO_RB_SIZE: RB size mask + * (values are IWL_CTXT_INFO_RB_SIZE_*K) + * @IWL_CTXT_INFO_RB_SIZE_1K: Value for 1K RB size + * @IWL_CTXT_INFO_RB_SIZE_2K: Value for 2K RB size + * @IWL_CTXT_INFO_RB_SIZE_4K: Value for 4K RB size + * @IWL_CTXT_INFO_RB_SIZE_8K: Value for 8K RB size + * @IWL_CTXT_INFO_RB_SIZE_12K: Value for 12K RB size + * @IWL_CTXT_INFO_RB_SIZE_16K: Value for 16K RB size + * @IWL_CTXT_INFO_RB_SIZE_20K: Value for 20K RB size + * @IWL_CTXT_INFO_RB_SIZE_24K: Value for 24K RB size + * @IWL_CTXT_INFO_RB_SIZE_28K: Value for 28K RB size + * @IWL_CTXT_INFO_RB_SIZE_32K: Value for 32K RB size + */ +enum iwl_context_info_flags { + IWL_CTXT_INFO_AUTO_FUNC_INIT = 0x0001, + IWL_CTXT_INFO_EARLY_DEBUG = 0x0002, + IWL_CTXT_INFO_ENABLE_CDMP = 0x0004, + IWL_CTXT_INFO_RB_CB_SIZE = 0x00f0, + IWL_CTXT_INFO_TFD_FORMAT_LONG = 0x0100, + IWL_CTXT_INFO_RB_SIZE = 0x1e00, + IWL_CTXT_INFO_RB_SIZE_1K = 0x1, + IWL_CTXT_INFO_RB_SIZE_2K = 0x2, + IWL_CTXT_INFO_RB_SIZE_4K = 0x4, + IWL_CTXT_INFO_RB_SIZE_8K = 0x8, + IWL_CTXT_INFO_RB_SIZE_12K = 0x9, + IWL_CTXT_INFO_RB_SIZE_16K = 0xa, + IWL_CTXT_INFO_RB_SIZE_20K = 0xb, + IWL_CTXT_INFO_RB_SIZE_24K = 0xc, + IWL_CTXT_INFO_RB_SIZE_28K = 0xd, + IWL_CTXT_INFO_RB_SIZE_32K = 0xe, +}; + +/* + * struct iwl_context_info_version - version structure + * @mac_id: SKU and revision id + * @version: context information version id + * @size: the size of the context information in DWs + */ +struct iwl_context_info_version { + __le16 mac_id; + __le16 version; + __le16 size; + __le16 reserved; +} __packed; + +/* + * struct iwl_context_info_control - version structure + * @control_flags: context information flags see &enum iwl_context_info_flags + */ +struct iwl_context_info_control { + __le32 control_flags; + __le32 reserved; +} __packed; + +/* + * struct iwl_context_info_dram - images DRAM map + * each entry in the map represents a DRAM chunk of up to 32 KB + * @umac_img: UMAC image DRAM map + * @lmac_img: LMAC image DRAM map + * @virtual_img: paged image DRAM map + */ +struct iwl_context_info_dram { + __le64 umac_img[IWL_MAX_DRAM_ENTRY]; + __le64 lmac_img[IWL_MAX_DRAM_ENTRY]; + __le64 virtual_img[IWL_MAX_DRAM_ENTRY]; +} __packed; + +/* + * struct iwl_context_info_rbd_cfg - RBDs configuration + * @free_rbd_addr: default queue free RB CB base address + * @used_rbd_addr: default queue used RB CB base address + * @status_wr_ptr: default queue used RB status write pointer + */ +struct iwl_context_info_rbd_cfg { + __le64 free_rbd_addr; + __le64 used_rbd_addr; + __le64 status_wr_ptr; +} __packed; + +/* + * struct iwl_context_info_hcmd_cfg - command queue configuration + * @cmd_queue_addr: address of command queue + * @cmd_queue_size: number of entries + */ +struct iwl_context_info_hcmd_cfg { + __le64 cmd_queue_addr; + u8 cmd_queue_size; + u8 reserved[7]; +} __packed; + +/* + * struct iwl_context_info_dump_cfg - Core Dump configuration + * @core_dump_addr: core dump (debug DRAM address) start address + * @core_dump_size: size, in DWs + */ +struct iwl_context_info_dump_cfg { + __le64 core_dump_addr; + __le32 core_dump_size; + __le32 reserved; +} __packed; + +/* + * struct iwl_context_info_pnvm_cfg - platform NVM data configuration + * @platform_nvm_addr: Platform NVM data start address + * @platform_nvm_size: size in DWs + */ +struct iwl_context_info_pnvm_cfg { + __le64 platform_nvm_addr; + __le32 platform_nvm_size; + __le32 reserved; +} __packed; + +/* + * struct iwl_context_info_early_dbg_cfg - early debug configuration for + * dumping DRAM addresses + * @early_debug_addr: early debug start address + * @early_debug_size: size in DWs + */ +struct iwl_context_info_early_dbg_cfg { + __le64 early_debug_addr; + __le32 early_debug_size; + __le32 reserved; +} __packed; + +/* + * struct iwl_context_info - device INIT configuration + * @version: version information of context info and HW + * @control: control flags of FH configurations + * @rbd_cfg: default RX queue configuration + * @hcmd_cfg: command queue configuration + * @dump_cfg: core dump data + * @edbg_cfg: early debug configuration + * @pnvm_cfg: platform nvm configuration + * @dram: firmware image addresses in DRAM + */ +struct iwl_context_info { + struct iwl_context_info_version version; + struct iwl_context_info_control control; + __le64 reserved0; + struct iwl_context_info_rbd_cfg rbd_cfg; + struct iwl_context_info_hcmd_cfg hcmd_cfg; + __le32 reserved1[4]; + struct iwl_context_info_dump_cfg dump_cfg; + struct iwl_context_info_early_dbg_cfg edbg_cfg; + struct iwl_context_info_pnvm_cfg pnvm_cfg; + __le32 reserved2[16]; + struct iwl_context_info_dram dram; + __le32 reserved3[16]; +} __packed; + +int iwl_pcie_ctxt_info_init(struct iwl_trans *trans, const struct fw_img *fw); +void iwl_pcie_ctxt_info_free(struct iwl_trans *trans); +void iwl_pcie_ctxt_info_free_paging(struct iwl_trans *trans); +int iwl_pcie_init_fw_sec(struct iwl_trans *trans, + const struct fw_img *fw, + struct iwl_context_info_dram *ctxt_dram); +int iwl_pcie_ctxt_info_alloc_dma(struct iwl_trans *trans, + const void *data, u32 len, + struct iwl_dram_data *dram); + +#endif /* __iwl_context_info_file_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-csr.h b/drivers/net/wireless/intel/iwlwifi/iwl-csr.h new file mode 100644 index 000000000..3e1f011e9 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-csr.h @@ -0,0 +1,641 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2016 Intel Deutschland GmbH + */ +#ifndef __iwl_csr_h__ +#define __iwl_csr_h__ +/* + * CSR (control and status registers) + * + * CSR registers are mapped directly into PCI bus space, and are accessible + * whenever platform supplies power to device, even when device is in + * low power states due to driver-invoked device resets + * (e.g. CSR_RESET_REG_FLAG_SW_RESET) or uCode-driven power-saving modes. + * + * Use iwl_write32() and iwl_read32() family to access these registers; + * these provide simple PCI bus access, without waking up the MAC. + * Do not use iwl_write_direct32() family for these registers; + * no need to "grab nic access" via CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ. + * The MAC (uCode processor, etc.) does not need to be powered up for accessing + * the CSR registers. + * + * NOTE: Device does need to be awake in order to read this memory + * via CSR_EEPROM and CSR_OTP registers + */ +#define CSR_BASE (0x000) + +#define CSR_HW_IF_CONFIG_REG (CSR_BASE+0x000) /* hardware interface config */ +#define CSR_INT_COALESCING (CSR_BASE+0x004) /* accum ints, 32-usec units */ +#define CSR_INT (CSR_BASE+0x008) /* host interrupt status/ack */ +#define CSR_INT_MASK (CSR_BASE+0x00c) /* host interrupt enable */ +#define CSR_FH_INT_STATUS (CSR_BASE+0x010) /* busmaster int status/ack*/ +#define CSR_GPIO_IN (CSR_BASE+0x018) /* read external chip pins */ +#define CSR_RESET (CSR_BASE+0x020) /* busmaster enable, NMI, etc*/ +#define CSR_GP_CNTRL (CSR_BASE+0x024) +#define CSR_FUNC_SCRATCH (CSR_BASE+0x02c) /* Scratch register - used for FW dbg */ + +/* 2nd byte of CSR_INT_COALESCING, not accessible via iwl_write32()! */ +#define CSR_INT_PERIODIC_REG (CSR_BASE+0x005) + +/* + * Hardware revision info + * Bit fields: + * 31-16: Reserved + * 15-4: Type of device: see CSR_HW_REV_TYPE_xxx definitions + * 3-2: Revision step: 0 = A, 1 = B, 2 = C, 3 = D + * 1-0: "Dash" (-) value, as in A-1, etc. + */ +#define CSR_HW_REV (CSR_BASE+0x028) + +/* + * RF ID revision info + * Bit fields: + * 31:24: Reserved (set to 0x0) + * 23:12: Type + * 11:8: Step (A - 0x0, B - 0x1, etc) + * 7:4: Dash + * 3:0: Flavor + */ +#define CSR_HW_RF_ID (CSR_BASE+0x09c) + +/* + * EEPROM and OTP (one-time-programmable) memory reads + * + * NOTE: Device must be awake, initialized via apm_ops.init(), + * in order to read. + */ +#define CSR_EEPROM_REG (CSR_BASE+0x02c) +#define CSR_EEPROM_GP (CSR_BASE+0x030) +#define CSR_OTP_GP_REG (CSR_BASE+0x034) + +#define CSR_GIO_REG (CSR_BASE+0x03C) +#define CSR_GP_UCODE_REG (CSR_BASE+0x048) +#define CSR_GP_DRIVER_REG (CSR_BASE+0x050) + +/* + * UCODE-DRIVER GP (general purpose) mailbox registers. + * SET/CLR registers set/clear bit(s) if "1" is written. + */ +#define CSR_UCODE_DRV_GP1 (CSR_BASE+0x054) +#define CSR_UCODE_DRV_GP1_SET (CSR_BASE+0x058) +#define CSR_UCODE_DRV_GP1_CLR (CSR_BASE+0x05c) +#define CSR_UCODE_DRV_GP2 (CSR_BASE+0x060) + +#define CSR_MBOX_SET_REG (CSR_BASE + 0x88) + +#define CSR_LED_REG (CSR_BASE+0x094) +#define CSR_DRAM_INT_TBL_REG (CSR_BASE+0x0A0) +#define CSR_MAC_SHADOW_REG_CTRL (CSR_BASE + 0x0A8) /* 6000 and up */ +#define CSR_MAC_SHADOW_REG_CTRL_RX_WAKE BIT(20) +#define CSR_MAC_SHADOW_REG_CTL2 (CSR_BASE + 0x0AC) +#define CSR_MAC_SHADOW_REG_CTL2_RX_WAKE 0xFFFF + +/* LTR control (since IWL_DEVICE_FAMILY_22000) */ +#define CSR_LTR_LONG_VAL_AD (CSR_BASE + 0x0D4) +#define CSR_LTR_LONG_VAL_AD_NO_SNOOP_REQ 0x80000000 +#define CSR_LTR_LONG_VAL_AD_NO_SNOOP_SCALE 0x1c000000 +#define CSR_LTR_LONG_VAL_AD_NO_SNOOP_VAL 0x03ff0000 +#define CSR_LTR_LONG_VAL_AD_SNOOP_REQ 0x00008000 +#define CSR_LTR_LONG_VAL_AD_SNOOP_SCALE 0x00001c00 +#define CSR_LTR_LONG_VAL_AD_SNOOP_VAL 0x000003ff +#define CSR_LTR_LONG_VAL_AD_SCALE_USEC 2 + +/* GIO Chicken Bits (PCI Express bus link power management) */ +#define CSR_GIO_CHICKEN_BITS (CSR_BASE+0x100) + +#define CSR_IPC_SLEEP_CONTROL (CSR_BASE + 0x114) +#define CSR_IPC_SLEEP_CONTROL_SUSPEND 0x3 +#define CSR_IPC_SLEEP_CONTROL_RESUME 0 + +/* Doorbell - since Bz + * connected to UREG_DOORBELL_TO_ISR6 (lower 16 bits only) + */ +#define CSR_DOORBELL_VECTOR (CSR_BASE + 0x130) + +/* host chicken bits */ +#define CSR_HOST_CHICKEN (CSR_BASE + 0x204) +#define CSR_HOST_CHICKEN_PM_IDLE_SRC_DIS_SB_PME BIT(19) + +/* Analog phase-lock-loop configuration */ +#define CSR_ANA_PLL_CFG (CSR_BASE+0x20c) + +/* + * CSR HW resources monitor registers + */ +#define CSR_MONITOR_CFG_REG (CSR_BASE+0x214) +#define CSR_MONITOR_STATUS_REG (CSR_BASE+0x228) +#define CSR_MONITOR_XTAL_RESOURCES (0x00000010) + +/* + * CSR Hardware Revision Workaround Register. Indicates hardware rev; + * "step" determines CCK backoff for txpower calculation. + * See also CSR_HW_REV register. + * Bit fields: + * 3-2: 0 = A, 1 = B, 2 = C, 3 = D step + * 1-0: "Dash" (-) value, as in C-1, etc. + */ +#define CSR_HW_REV_WA_REG (CSR_BASE+0x22C) + +#define CSR_DBG_HPET_MEM_REG (CSR_BASE+0x240) +#define CSR_DBG_LINK_PWR_MGMT_REG (CSR_BASE+0x250) + +/* + * Scratch register initial configuration - this is set on init, and read + * during a error FW error. + */ +#define CSR_FUNC_SCRATCH_INIT_VALUE (0x01010101) + +/* Bits for CSR_HW_IF_CONFIG_REG */ +#define CSR_HW_IF_CONFIG_REG_MSK_MAC_STEP_DASH (0x0000000F) +#define CSR_HW_IF_CONFIG_REG_BIT_MONITOR_SRAM (0x00000080) +#define CSR_HW_IF_CONFIG_REG_MSK_BOARD_VER (0x000000C0) +#define CSR_HW_IF_CONFIG_REG_BIT_MAC_SI (0x00000100) +#define CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI (0x00000200) +#define CSR_HW_IF_CONFIG_REG_D3_DEBUG (0x00000200) +#define CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE (0x00000C00) +#define CSR_HW_IF_CONFIG_REG_MSK_PHY_DASH (0x00003000) +#define CSR_HW_IF_CONFIG_REG_MSK_PHY_STEP (0x0000C000) + +#define CSR_HW_IF_CONFIG_REG_POS_MAC_DASH (0) +#define CSR_HW_IF_CONFIG_REG_POS_MAC_STEP (2) +#define CSR_HW_IF_CONFIG_REG_POS_BOARD_VER (6) +#define CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE (10) +#define CSR_HW_IF_CONFIG_REG_POS_PHY_DASH (12) +#define CSR_HW_IF_CONFIG_REG_POS_PHY_STEP (14) + +#define CSR_HW_IF_CONFIG_REG_BIT_HAP_WAKE_L1A (0x00080000) +#define CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM (0x00200000) +#define CSR_HW_IF_CONFIG_REG_BIT_NIC_READY (0x00400000) /* PCI_OWN_SEM */ +#define CSR_HW_IF_CONFIG_REG_BIT_NIC_PREPARE_DONE (0x02000000) /* ME_OWN */ +#define CSR_HW_IF_CONFIG_REG_PREPARE (0x08000000) /* WAKE_ME */ +#define CSR_HW_IF_CONFIG_REG_ENABLE_PME (0x10000000) +#define CSR_HW_IF_CONFIG_REG_PERSIST_MODE (0x40000000) /* PERSISTENCE */ + +#define CSR_MBOX_SET_REG_OS_ALIVE BIT(5) + +#define CSR_INT_PERIODIC_DIS (0x00) /* disable periodic int*/ +#define CSR_INT_PERIODIC_ENA (0xFF) /* 255*32 usec ~ 8 msec*/ + +/* interrupt flags in INTA, set by uCode or hardware (e.g. dma), + * acknowledged (reset) by host writing "1" to flagged bits. */ +#define CSR_INT_BIT_FH_RX (1 << 31) /* Rx DMA, cmd responses, FH_INT[17:16] */ +#define CSR_INT_BIT_HW_ERR (1 << 29) /* DMA hardware error FH_INT[31] */ +#define CSR_INT_BIT_RX_PERIODIC (1 << 28) /* Rx periodic */ +#define CSR_INT_BIT_FH_TX (1 << 27) /* Tx DMA FH_INT[1:0] */ +#define CSR_INT_BIT_SCD (1 << 26) /* TXQ pointer advanced */ +#define CSR_INT_BIT_SW_ERR (1 << 25) /* uCode error */ +#define CSR_INT_BIT_RF_KILL (1 << 7) /* HW RFKILL switch GP_CNTRL[27] toggled */ +#define CSR_INT_BIT_CT_KILL (1 << 6) /* Critical temp (chip too hot) rfkill */ +#define CSR_INT_BIT_SW_RX (1 << 3) /* Rx, command responses */ +#define CSR_INT_BIT_WAKEUP (1 << 1) /* NIC controller waking up (pwr mgmt) */ +#define CSR_INT_BIT_ALIVE (1 << 0) /* uCode interrupts once it initializes */ + +#define CSR_INI_SET_MASK (CSR_INT_BIT_FH_RX | \ + CSR_INT_BIT_HW_ERR | \ + CSR_INT_BIT_FH_TX | \ + CSR_INT_BIT_SW_ERR | \ + CSR_INT_BIT_RF_KILL | \ + CSR_INT_BIT_SW_RX | \ + CSR_INT_BIT_WAKEUP | \ + CSR_INT_BIT_ALIVE | \ + CSR_INT_BIT_RX_PERIODIC) + +/* interrupt flags in FH (flow handler) (PCI busmaster DMA) */ +#define CSR_FH_INT_BIT_ERR (1 << 31) /* Error */ +#define CSR_FH_INT_BIT_HI_PRIOR (1 << 30) /* High priority Rx, bypass coalescing */ +#define CSR_FH_INT_BIT_RX_CHNL1 (1 << 17) /* Rx channel 1 */ +#define CSR_FH_INT_BIT_RX_CHNL0 (1 << 16) /* Rx channel 0 */ +#define CSR_FH_INT_BIT_TX_CHNL1 (1 << 1) /* Tx channel 1 */ +#define CSR_FH_INT_BIT_TX_CHNL0 (1 << 0) /* Tx channel 0 */ + +#define CSR_FH_INT_RX_MASK (CSR_FH_INT_BIT_HI_PRIOR | \ + CSR_FH_INT_BIT_RX_CHNL1 | \ + CSR_FH_INT_BIT_RX_CHNL0) + +#define CSR_FH_INT_TX_MASK (CSR_FH_INT_BIT_TX_CHNL1 | \ + CSR_FH_INT_BIT_TX_CHNL0) + +/* GPIO */ +#define CSR_GPIO_IN_BIT_AUX_POWER (0x00000200) +#define CSR_GPIO_IN_VAL_VAUX_PWR_SRC (0x00000000) +#define CSR_GPIO_IN_VAL_VMAIN_PWR_SRC (0x00000200) + +/* RESET */ +#define CSR_RESET_REG_FLAG_NEVO_RESET (0x00000001) +#define CSR_RESET_REG_FLAG_FORCE_NMI (0x00000002) +#define CSR_RESET_REG_FLAG_SW_RESET (0x00000080) +#define CSR_RESET_REG_FLAG_MASTER_DISABLED (0x00000100) +#define CSR_RESET_REG_FLAG_STOP_MASTER (0x00000200) +#define CSR_RESET_LINK_PWR_MGMT_DISABLED (0x80000000) + +/* + * GP (general purpose) CONTROL REGISTER + * Bit fields: + * 27: HW_RF_KILL_SW + * Indicates state of (platform's) hardware RF-Kill switch + * 26-24: POWER_SAVE_TYPE + * Indicates current power-saving mode: + * 000 -- No power saving + * 001 -- MAC power-down + * 010 -- PHY (radio) power-down + * 011 -- Error + * 10: XTAL ON request + * 9-6: SYS_CONFIG + * Indicates current system configuration, reflecting pins on chip + * as forced high/low by device circuit board. + * 4: GOING_TO_SLEEP + * Indicates MAC is entering a power-saving sleep power-down. + * Not a good time to access device-internal resources. + * 3: MAC_ACCESS_REQ + * Host sets this to request and maintain MAC wakeup, to allow host + * access to device-internal resources. Host must wait for + * MAC_CLOCK_READY (and !GOING_TO_SLEEP) before accessing non-CSR + * device registers. + * 2: INIT_DONE + * Host sets this to put device into fully operational D0 power mode. + * Host resets this after SW_RESET to put device into low power mode. + * 0: MAC_CLOCK_READY + * Indicates MAC (ucode processor, etc.) is powered up and can run. + * Internal resources are accessible. + * NOTE: This does not indicate that the processor is actually running. + * NOTE: This does not indicate that device has completed + * init or post-power-down restore of internal SRAM memory. + * Use CSR_UCODE_DRV_GP1_BIT_MAC_SLEEP as indication that + * SRAM is restored and uCode is in normal operation mode. + * Later devices (5xxx/6xxx/1xxx) use non-volatile SRAM, and + * do not need to save/restore it. + * NOTE: After device reset, this bit remains "0" until host sets + * INIT_DONE + */ +#define CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY (0x00000001) +#define CSR_GP_CNTRL_REG_FLAG_INIT_DONE (0x00000004) +#define CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ (0x00000008) +#define CSR_GP_CNTRL_REG_FLAG_GOING_TO_SLEEP (0x00000010) +#define CSR_GP_CNTRL_REG_FLAG_XTAL_ON (0x00000400) + +#define CSR_GP_CNTRL_REG_VAL_MAC_ACCESS_EN (0x00000001) + +#define CSR_GP_CNTRL_REG_MSK_POWER_SAVE_TYPE (0x07000000) +#define CSR_GP_CNTRL_REG_FLAG_RFKILL_WAKE_L1A_EN (0x04000000) +#define CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW (0x08000000) + +/* From Bz we use these instead during init/reset flow */ +#define CSR_GP_CNTRL_REG_FLAG_MAC_INIT BIT(6) +#define CSR_GP_CNTRL_REG_FLAG_ROM_START BIT(7) +#define CSR_GP_CNTRL_REG_FLAG_MAC_STATUS BIT(20) +#define CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ BIT(21) +#define CSR_GP_CNTRL_REG_FLAG_BUS_MASTER_DISABLE_STATUS BIT(28) +#define CSR_GP_CNTRL_REG_FLAG_BUS_MASTER_DISABLE_REQ BIT(29) +#define CSR_GP_CNTRL_REG_FLAG_SW_RESET BIT(31) + +/* HW REV */ +#define CSR_HW_REV_STEP_DASH(_val) ((_val) & CSR_HW_IF_CONFIG_REG_MSK_MAC_STEP_DASH) +#define CSR_HW_REV_TYPE(_val) (((_val) & 0x000FFF0) >> 4) + +/* HW RFID */ +#define CSR_HW_RFID_FLAVOR(_val) (((_val) & 0x000000F) >> 0) +#define CSR_HW_RFID_DASH(_val) (((_val) & 0x00000F0) >> 4) +#define CSR_HW_RFID_STEP(_val) (((_val) & 0x0000F00) >> 8) +#define CSR_HW_RFID_TYPE(_val) (((_val) & 0x0FFF000) >> 12) +#define CSR_HW_RFID_IS_CDB(_val) (((_val) & 0x10000000) >> 28) +#define CSR_HW_RFID_IS_JACKET(_val) (((_val) & 0x20000000) >> 29) + +/** + * hw_rev values + */ +enum { + SILICON_A_STEP = 0, + SILICON_B_STEP, + SILICON_C_STEP, + SILICON_Z_STEP = 0xf, +}; + + +#define CSR_HW_REV_TYPE_MSK (0x000FFF0) +#define CSR_HW_REV_TYPE_5300 (0x0000020) +#define CSR_HW_REV_TYPE_5350 (0x0000030) +#define CSR_HW_REV_TYPE_5100 (0x0000050) +#define CSR_HW_REV_TYPE_5150 (0x0000040) +#define CSR_HW_REV_TYPE_1000 (0x0000060) +#define CSR_HW_REV_TYPE_6x00 (0x0000070) +#define CSR_HW_REV_TYPE_6x50 (0x0000080) +#define CSR_HW_REV_TYPE_6150 (0x0000084) +#define CSR_HW_REV_TYPE_6x05 (0x00000B0) +#define CSR_HW_REV_TYPE_6x30 CSR_HW_REV_TYPE_6x05 +#define CSR_HW_REV_TYPE_6x35 CSR_HW_REV_TYPE_6x05 +#define CSR_HW_REV_TYPE_2x30 (0x00000C0) +#define CSR_HW_REV_TYPE_2x00 (0x0000100) +#define CSR_HW_REV_TYPE_105 (0x0000110) +#define CSR_HW_REV_TYPE_135 (0x0000120) +#define CSR_HW_REV_TYPE_3160 (0x0000164) +#define CSR_HW_REV_TYPE_7265D (0x0000210) +#define CSR_HW_REV_TYPE_NONE (0x00001F0) +#define CSR_HW_REV_TYPE_QNJ (0x0000360) +#define CSR_HW_REV_TYPE_QNJ_B0 (0x0000361) +#define CSR_HW_REV_TYPE_QU_B0 (0x0000331) +#define CSR_HW_REV_TYPE_QU_C0 (0x0000332) +#define CSR_HW_REV_TYPE_QUZ (0x0000351) +#define CSR_HW_REV_TYPE_HR_CDB (0x0000340) +#define CSR_HW_REV_TYPE_SO (0x0000370) +#define CSR_HW_REV_TYPE_TY (0x0000420) + +/* RF_ID value */ +#define CSR_HW_RF_ID_TYPE_JF (0x00105100) +#define CSR_HW_RF_ID_TYPE_HR (0x0010A000) +#define CSR_HW_RF_ID_TYPE_HR1 (0x0010c100) +#define CSR_HW_RF_ID_TYPE_HRCDB (0x00109F00) +#define CSR_HW_RF_ID_TYPE_GF (0x0010D000) +#define CSR_HW_RF_ID_TYPE_GF4 (0x0010E000) + +/* HW_RF CHIP STEP */ +#define CSR_HW_RF_STEP(_val) (((_val) >> 8) & 0xF) + +/* EEPROM REG */ +#define CSR_EEPROM_REG_READ_VALID_MSK (0x00000001) +#define CSR_EEPROM_REG_BIT_CMD (0x00000002) +#define CSR_EEPROM_REG_MSK_ADDR (0x0000FFFC) +#define CSR_EEPROM_REG_MSK_DATA (0xFFFF0000) + +/* EEPROM GP */ +#define CSR_EEPROM_GP_VALID_MSK (0x00000007) /* signature */ +#define CSR_EEPROM_GP_IF_OWNER_MSK (0x00000180) +#define CSR_EEPROM_GP_BAD_SIGNATURE_BOTH_EEP_AND_OTP (0x00000000) +#define CSR_EEPROM_GP_BAD_SIG_EEP_GOOD_SIG_OTP (0x00000001) +#define CSR_EEPROM_GP_GOOD_SIG_EEP_LESS_THAN_4K (0x00000002) +#define CSR_EEPROM_GP_GOOD_SIG_EEP_MORE_THAN_4K (0x00000004) + +/* One-time-programmable memory general purpose reg */ +#define CSR_OTP_GP_REG_DEVICE_SELECT (0x00010000) /* 0 - EEPROM, 1 - OTP */ +#define CSR_OTP_GP_REG_OTP_ACCESS_MODE (0x00020000) /* 0 - absolute, 1 - relative */ +#define CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK (0x00100000) /* bit 20 */ +#define CSR_OTP_GP_REG_ECC_UNCORR_STATUS_MSK (0x00200000) /* bit 21 */ + +/* GP REG */ +#define CSR_GP_REG_POWER_SAVE_STATUS_MSK (0x03000000) /* bit 24/25 */ +#define CSR_GP_REG_NO_POWER_SAVE (0x00000000) +#define CSR_GP_REG_MAC_POWER_SAVE (0x01000000) +#define CSR_GP_REG_PHY_POWER_SAVE (0x02000000) +#define CSR_GP_REG_POWER_SAVE_ERROR (0x03000000) + + +/* CSR GIO */ +#define CSR_GIO_REG_VAL_L0S_DISABLED (0x00000002) + +/* + * UCODE-DRIVER GP (general purpose) mailbox register 1 + * Host driver and uCode write and/or read this register to communicate with + * each other. + * Bit fields: + * 4: UCODE_DISABLE + * Host sets this to request permanent halt of uCode, same as + * sending CARD_STATE command with "halt" bit set. + * 3: CT_KILL_EXIT + * Host sets this to request exit from CT_KILL state, i.e. host thinks + * device temperature is low enough to continue normal operation. + * 2: CMD_BLOCKED + * Host sets this during RF KILL power-down sequence (HW, SW, CT KILL) + * to release uCode to clear all Tx and command queues, enter + * unassociated mode, and power down. + * NOTE: Some devices also use HBUS_TARG_MBX_C register for this bit. + * 1: SW_BIT_RFKILL + * Host sets this when issuing CARD_STATE command to request + * device sleep. + * 0: MAC_SLEEP + * uCode sets this when preparing a power-saving power-down. + * uCode resets this when power-up is complete and SRAM is sane. + * NOTE: device saves internal SRAM data to host when powering down, + * and must restore this data after powering back up. + * MAC_SLEEP is the best indication that restore is complete. + * Later devices (5xxx/6xxx/1xxx) use non-volatile SRAM, and + * do not need to save/restore it. + */ +#define CSR_UCODE_DRV_GP1_BIT_MAC_SLEEP (0x00000001) +#define CSR_UCODE_SW_BIT_RFKILL (0x00000002) +#define CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED (0x00000004) +#define CSR_UCODE_DRV_GP1_REG_BIT_CT_KILL_EXIT (0x00000008) +#define CSR_UCODE_DRV_GP1_BIT_D3_CFG_COMPLETE (0x00000020) + +/* GP Driver */ +#define CSR_GP_DRIVER_REG_BIT_RADIO_SKU_MSK (0x00000003) +#define CSR_GP_DRIVER_REG_BIT_RADIO_SKU_3x3_HYB (0x00000000) +#define CSR_GP_DRIVER_REG_BIT_RADIO_SKU_2x2_HYB (0x00000001) +#define CSR_GP_DRIVER_REG_BIT_RADIO_SKU_2x2_IPA (0x00000002) +#define CSR_GP_DRIVER_REG_BIT_CALIB_VERSION6 (0x00000004) +#define CSR_GP_DRIVER_REG_BIT_6050_1x2 (0x00000008) + +#define CSR_GP_DRIVER_REG_BIT_RADIO_IQ_INVER (0x00000080) + +/* GIO Chicken Bits (PCI Express bus link power management) */ +#define CSR_GIO_CHICKEN_BITS_REG_BIT_L1A_NO_L0S_RX (0x00800000) +#define CSR_GIO_CHICKEN_BITS_REG_BIT_DIS_L0S_EXIT_TIMER (0x20000000) + +/* LED */ +#define CSR_LED_BSM_CTRL_MSK (0xFFFFFFDF) +#define CSR_LED_REG_TURN_ON (0x60) +#define CSR_LED_REG_TURN_OFF (0x20) + +/* ANA_PLL */ +#define CSR50_ANA_PLL_CFG_VAL (0x00880300) + +/* HPET MEM debug */ +#define CSR_DBG_HPET_MEM_REG_VAL (0xFFFF0000) + +/* DRAM INT TABLE */ +#define CSR_DRAM_INT_TBL_ENABLE (1 << 31) +#define CSR_DRAM_INIT_TBL_WRITE_POINTER (1 << 28) +#define CSR_DRAM_INIT_TBL_WRAP_CHECK (1 << 27) + +/* + * SHR target access (Shared block memory space) + * + * Shared internal registers can be accessed directly from PCI bus through SHR + * arbiter without need for the MAC HW to be powered up. This is possible due to + * indirect read/write via HEEP_CTRL_WRD_PCIEX_CTRL (0xEC) and + * HEEP_CTRL_WRD_PCIEX_DATA (0xF4) registers. + * + * Use iwl_write32()/iwl_read32() family to access these registers. The MAC HW + * need not be powered up so no "grab inc access" is required. + */ + +/* + * Registers for accessing shared registers (e.g. SHR_APMG_GP1, + * SHR_APMG_XTAL_CFG). For example, to read from SHR_APMG_GP1 register (0x1DC), + * first, write to the control register: + * HEEP_CTRL_WRD_PCIEX_CTRL[15:0] = 0x1DC (offset of the SHR_APMG_GP1 register) + * HEEP_CTRL_WRD_PCIEX_CTRL[29:28] = 2 (read access) + * second, read from the data register HEEP_CTRL_WRD_PCIEX_DATA[31:0]. + * + * To write the register, first, write to the data register + * HEEP_CTRL_WRD_PCIEX_DATA[31:0] and then: + * HEEP_CTRL_WRD_PCIEX_CTRL[15:0] = 0x1DC (offset of the SHR_APMG_GP1 register) + * HEEP_CTRL_WRD_PCIEX_CTRL[29:28] = 3 (write access) + */ +#define HEEP_CTRL_WRD_PCIEX_CTRL_REG (CSR_BASE+0x0ec) +#define HEEP_CTRL_WRD_PCIEX_DATA_REG (CSR_BASE+0x0f4) + +/* + * HBUS (Host-side Bus) + * + * HBUS registers are mapped directly into PCI bus space, but are used + * to indirectly access device's internal memory or registers that + * may be powered-down. + * + * Use iwl_write_direct32()/iwl_read_direct32() family for these registers; + * host must "grab nic access" via CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ + * to make sure the MAC (uCode processor, etc.) is powered up for accessing + * internal resources. + * + * Do not use iwl_write32()/iwl_read32() family to access these registers; + * these provide only simple PCI bus access, without waking up the MAC. + */ +#define HBUS_BASE (0x400) + +/* + * Registers for accessing device's internal SRAM memory (e.g. SCD SRAM + * structures, error log, event log, verifying uCode load). + * First write to address register, then read from or write to data register + * to complete the job. Once the address register is set up, accesses to + * data registers auto-increment the address by one dword. + * Bit usage for address registers (read or write): + * 0-31: memory address within device + */ +#define HBUS_TARG_MEM_RADDR (HBUS_BASE+0x00c) +#define HBUS_TARG_MEM_WADDR (HBUS_BASE+0x010) +#define HBUS_TARG_MEM_WDAT (HBUS_BASE+0x018) +#define HBUS_TARG_MEM_RDAT (HBUS_BASE+0x01c) + +/* Mailbox C, used as workaround alternative to CSR_UCODE_DRV_GP1 mailbox */ +#define HBUS_TARG_MBX_C (HBUS_BASE+0x030) +#define HBUS_TARG_MBX_C_REG_BIT_CMD_BLOCKED (0x00000004) + +/* + * Registers for accessing device's internal peripheral registers + * (e.g. SCD, BSM, etc.). First write to address register, + * then read from or write to data register to complete the job. + * Bit usage for address registers (read or write): + * 0-15: register address (offset) within device + * 24-25: (# bytes - 1) to read or write (e.g. 3 for dword) + */ +#define HBUS_TARG_PRPH_WADDR (HBUS_BASE+0x044) +#define HBUS_TARG_PRPH_RADDR (HBUS_BASE+0x048) +#define HBUS_TARG_PRPH_WDAT (HBUS_BASE+0x04c) +#define HBUS_TARG_PRPH_RDAT (HBUS_BASE+0x050) + +/* Used to enable DBGM */ +#define HBUS_TARG_TEST_REG (HBUS_BASE+0x05c) + +/* + * Per-Tx-queue write pointer (index, really!) + * Indicates index to next TFD that driver will fill (1 past latest filled). + * Bit usage: + * 0-7: queue write index + * 11-8: queue selector + */ +#define HBUS_TARG_WRPTR (HBUS_BASE+0x060) +/* This register is common for Tx and Rx, Rx queues start from 512 */ +#define HBUS_TARG_WRPTR_Q_SHIFT (16) +#define HBUS_TARG_WRPTR_RX_Q(q) (((q) + 512) << HBUS_TARG_WRPTR_Q_SHIFT) + +/********************************************************** + * CSR values + **********************************************************/ + /* + * host interrupt timeout value + * used with setting interrupt coalescing timer + * the CSR_INT_COALESCING is an 8 bit register in 32-usec unit + * + * default interrupt coalescing timer is 64 x 32 = 2048 usecs + */ +#define IWL_HOST_INT_TIMEOUT_MAX (0xFF) +#define IWL_HOST_INT_TIMEOUT_DEF (0x40) +#define IWL_HOST_INT_TIMEOUT_MIN (0x0) +#define IWL_HOST_INT_OPER_MODE BIT(31) + +/***************************************************************************** + * 7000/3000 series SHR DTS addresses * + *****************************************************************************/ + +/* Diode Results Register Structure: */ +enum dtd_diode_reg { + DTS_DIODE_REG_DIG_VAL = 0x000000FF, /* bits [7:0] */ + DTS_DIODE_REG_VREF_LOW = 0x0000FF00, /* bits [15:8] */ + DTS_DIODE_REG_VREF_HIGH = 0x00FF0000, /* bits [23:16] */ + DTS_DIODE_REG_VREF_ID = 0x03000000, /* bits [25:24] */ + DTS_DIODE_REG_PASS_ONCE = 0x80000000, /* bits [31:31] */ + DTS_DIODE_REG_FLAGS_MSK = 0xFF000000, /* bits [31:24] */ +/* Those are the masks INSIDE the flags bit-field: */ + DTS_DIODE_REG_FLAGS_VREFS_ID_POS = 0, + DTS_DIODE_REG_FLAGS_VREFS_ID = 0x00000003, /* bits [1:0] */ + DTS_DIODE_REG_FLAGS_PASS_ONCE_POS = 7, + DTS_DIODE_REG_FLAGS_PASS_ONCE = 0x00000080, /* bits [7:7] */ +}; + +/***************************************************************************** + * MSIX related registers * + *****************************************************************************/ + +#define CSR_MSIX_BASE (0x2000) +#define CSR_MSIX_FH_INT_CAUSES_AD (CSR_MSIX_BASE + 0x800) +#define CSR_MSIX_FH_INT_MASK_AD (CSR_MSIX_BASE + 0x804) +#define CSR_MSIX_HW_INT_CAUSES_AD (CSR_MSIX_BASE + 0x808) +#define CSR_MSIX_HW_INT_MASK_AD (CSR_MSIX_BASE + 0x80C) +#define CSR_MSIX_AUTOMASK_ST_AD (CSR_MSIX_BASE + 0x810) +#define CSR_MSIX_RX_IVAR_AD_REG (CSR_MSIX_BASE + 0x880) +#define CSR_MSIX_IVAR_AD_REG (CSR_MSIX_BASE + 0x890) +#define CSR_MSIX_PENDING_PBA_AD (CSR_MSIX_BASE + 0x1000) +#define CSR_MSIX_RX_IVAR(cause) (CSR_MSIX_RX_IVAR_AD_REG + (cause)) +#define CSR_MSIX_IVAR(cause) (CSR_MSIX_IVAR_AD_REG + (cause)) + +#define MSIX_FH_INT_CAUSES_Q(q) (q) + +/* + * Causes for the FH register interrupts + */ +enum msix_fh_int_causes { + MSIX_FH_INT_CAUSES_Q0 = BIT(0), + MSIX_FH_INT_CAUSES_Q1 = BIT(1), + MSIX_FH_INT_CAUSES_D2S_CH0_NUM = BIT(16), + MSIX_FH_INT_CAUSES_D2S_CH1_NUM = BIT(17), + MSIX_FH_INT_CAUSES_S2D = BIT(19), + MSIX_FH_INT_CAUSES_FH_ERR = BIT(21), +}; + +/* The low 16 bits are for rx data queue indication */ +#define MSIX_FH_INT_CAUSES_DATA_QUEUE 0xffff + +/* + * Causes for the HW register interrupts + */ +enum msix_hw_int_causes { + MSIX_HW_INT_CAUSES_REG_ALIVE = BIT(0), + MSIX_HW_INT_CAUSES_REG_WAKEUP = BIT(1), + MSIX_HW_INT_CAUSES_REG_IML = BIT(1), + MSIX_HW_INT_CAUSES_REG_RESET_DONE = BIT(2), + MSIX_HW_INT_CAUSES_REG_SW_ERR_BZ = BIT(5), + MSIX_HW_INT_CAUSES_REG_CT_KILL = BIT(6), + MSIX_HW_INT_CAUSES_REG_RF_KILL = BIT(7), + MSIX_HW_INT_CAUSES_REG_PERIODIC = BIT(8), + MSIX_HW_INT_CAUSES_REG_SW_ERR = BIT(25), + MSIX_HW_INT_CAUSES_REG_SCD = BIT(26), + MSIX_HW_INT_CAUSES_REG_FH_TX = BIT(27), + MSIX_HW_INT_CAUSES_REG_HW_ERR = BIT(29), + MSIX_HW_INT_CAUSES_REG_HAP = BIT(30), +}; + +#define MSIX_MIN_INTERRUPT_VECTORS 2 +#define MSIX_AUTO_CLEAR_CAUSE 0 +#define MSIX_NON_AUTO_CLEAR_CAUSE BIT(7) + +/***************************************************************************** + * HW address related registers * + *****************************************************************************/ + +#define CSR_ADDR_BASE(trans) ((trans)->cfg->mac_addr_from_csr) +#define CSR_MAC_ADDR0_OTP(trans) (CSR_ADDR_BASE(trans) + 0x00) +#define CSR_MAC_ADDR1_OTP(trans) (CSR_ADDR_BASE(trans) + 0x04) +#define CSR_MAC_ADDR0_STRAP(trans) (CSR_ADDR_BASE(trans) + 0x08) +#define CSR_MAC_ADDR1_STRAP(trans) (CSR_ADDR_BASE(trans) + 0x0c) + +#endif /* !__iwl_csr_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.c b/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.c new file mode 100644 index 000000000..5979d904b --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.c @@ -0,0 +1,1389 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2018-2022 Intel Corporation + */ +#include <linux/firmware.h> +#include "iwl-drv.h" +#include "iwl-trans.h" +#include "iwl-dbg-tlv.h" +#include "fw/dbg.h" +#include "fw/runtime.h" + +/** + * enum iwl_dbg_tlv_type - debug TLV types + * @IWL_DBG_TLV_TYPE_DEBUG_INFO: debug info TLV + * @IWL_DBG_TLV_TYPE_BUF_ALLOC: buffer allocation TLV + * @IWL_DBG_TLV_TYPE_HCMD: host command TLV + * @IWL_DBG_TLV_TYPE_REGION: region TLV + * @IWL_DBG_TLV_TYPE_TRIGGER: trigger TLV + * @IWL_DBG_TLV_TYPE_CONF_SET: conf set TLV + * @IWL_DBG_TLV_TYPE_NUM: number of debug TLVs + */ +enum iwl_dbg_tlv_type { + IWL_DBG_TLV_TYPE_DEBUG_INFO = + IWL_UCODE_TLV_TYPE_DEBUG_INFO - IWL_UCODE_TLV_DEBUG_BASE, + IWL_DBG_TLV_TYPE_BUF_ALLOC, + IWL_DBG_TLV_TYPE_HCMD, + IWL_DBG_TLV_TYPE_REGION, + IWL_DBG_TLV_TYPE_TRIGGER, + IWL_DBG_TLV_TYPE_CONF_SET, + IWL_DBG_TLV_TYPE_NUM, +}; + +/** + * struct iwl_dbg_tlv_ver_data - debug TLV version struct + * @min_ver: min version supported + * @max_ver: max version supported + */ +struct iwl_dbg_tlv_ver_data { + int min_ver; + int max_ver; +}; + +/** + * struct iwl_dbg_tlv_timer_node - timer node struct + * @list: list of &struct iwl_dbg_tlv_timer_node + * @timer: timer + * @fwrt: &struct iwl_fw_runtime + * @tlv: TLV attach to the timer node + */ +struct iwl_dbg_tlv_timer_node { + struct list_head list; + struct timer_list timer; + struct iwl_fw_runtime *fwrt; + struct iwl_ucode_tlv *tlv; +}; + +static const struct iwl_dbg_tlv_ver_data +dbg_ver_table[IWL_DBG_TLV_TYPE_NUM] = { + [IWL_DBG_TLV_TYPE_DEBUG_INFO] = {.min_ver = 1, .max_ver = 1,}, + [IWL_DBG_TLV_TYPE_BUF_ALLOC] = {.min_ver = 1, .max_ver = 1,}, + [IWL_DBG_TLV_TYPE_HCMD] = {.min_ver = 1, .max_ver = 1,}, + [IWL_DBG_TLV_TYPE_REGION] = {.min_ver = 1, .max_ver = 3,}, + [IWL_DBG_TLV_TYPE_TRIGGER] = {.min_ver = 1, .max_ver = 1,}, + [IWL_DBG_TLV_TYPE_CONF_SET] = {.min_ver = 1, .max_ver = 1,}, +}; + +static int iwl_dbg_tlv_add(const struct iwl_ucode_tlv *tlv, + struct list_head *list) +{ + u32 len = le32_to_cpu(tlv->length); + struct iwl_dbg_tlv_node *node; + + node = kzalloc(sizeof(*node) + len, GFP_KERNEL); + if (!node) + return -ENOMEM; + + memcpy(&node->tlv, tlv, sizeof(node->tlv)); + memcpy(node->tlv.data, tlv->data, len); + list_add_tail(&node->list, list); + + return 0; +} + +static bool iwl_dbg_tlv_ver_support(const struct iwl_ucode_tlv *tlv) +{ + const struct iwl_fw_ini_header *hdr = (const void *)&tlv->data[0]; + u32 type = le32_to_cpu(tlv->type); + u32 tlv_idx = type - IWL_UCODE_TLV_DEBUG_BASE; + u32 ver = le32_to_cpu(hdr->version); + + if (ver < dbg_ver_table[tlv_idx].min_ver || + ver > dbg_ver_table[tlv_idx].max_ver) + return false; + + return true; +} + +static int iwl_dbg_tlv_alloc_debug_info(struct iwl_trans *trans, + const struct iwl_ucode_tlv *tlv) +{ + const struct iwl_fw_ini_debug_info_tlv *debug_info = (const void *)tlv->data; + + if (le32_to_cpu(tlv->length) != sizeof(*debug_info)) + return -EINVAL; + + IWL_DEBUG_FW(trans, "WRT: Loading debug cfg: %s\n", + debug_info->debug_cfg_name); + + return iwl_dbg_tlv_add(tlv, &trans->dbg.debug_info_tlv_list); +} + +static int iwl_dbg_tlv_alloc_buf_alloc(struct iwl_trans *trans, + const struct iwl_ucode_tlv *tlv) +{ + const struct iwl_fw_ini_allocation_tlv *alloc = (const void *)tlv->data; + u32 buf_location; + u32 alloc_id; + + if (le32_to_cpu(tlv->length) != sizeof(*alloc)) + return -EINVAL; + + buf_location = le32_to_cpu(alloc->buf_location); + alloc_id = le32_to_cpu(alloc->alloc_id); + + if (buf_location == IWL_FW_INI_LOCATION_INVALID || + buf_location >= IWL_FW_INI_LOCATION_NUM) + goto err; + + if (alloc_id == IWL_FW_INI_ALLOCATION_INVALID || + alloc_id >= IWL_FW_INI_ALLOCATION_NUM) + goto err; + + if (buf_location == IWL_FW_INI_LOCATION_NPK_PATH && + alloc_id != IWL_FW_INI_ALLOCATION_ID_DBGC1) + goto err; + + if (buf_location == IWL_FW_INI_LOCATION_SRAM_PATH && + alloc_id != IWL_FW_INI_ALLOCATION_ID_DBGC1) + goto err; + + if (buf_location == IWL_FW_INI_LOCATION_DRAM_PATH && + alloc->req_size == 0) { + IWL_ERR(trans, "WRT: Invalid DRAM buffer allocation requested size (0)\n"); + return -EINVAL; + } + + trans->dbg.fw_mon_cfg[alloc_id] = *alloc; + + return 0; +err: + IWL_ERR(trans, + "WRT: Invalid allocation id %u and/or location id %u for allocation TLV\n", + alloc_id, buf_location); + return -EINVAL; +} + +static int iwl_dbg_tlv_alloc_hcmd(struct iwl_trans *trans, + const struct iwl_ucode_tlv *tlv) +{ + const struct iwl_fw_ini_hcmd_tlv *hcmd = (const void *)tlv->data; + u32 tp = le32_to_cpu(hcmd->time_point); + + if (le32_to_cpu(tlv->length) <= sizeof(*hcmd)) + return -EINVAL; + + /* Host commands can not be sent in early time point since the FW + * is not ready + */ + if (tp == IWL_FW_INI_TIME_POINT_INVALID || + tp >= IWL_FW_INI_TIME_POINT_NUM || + tp == IWL_FW_INI_TIME_POINT_EARLY) { + IWL_ERR(trans, + "WRT: Invalid time point %u for host command TLV\n", + tp); + return -EINVAL; + } + + return iwl_dbg_tlv_add(tlv, &trans->dbg.time_point[tp].hcmd_list); +} + +static int iwl_dbg_tlv_alloc_region(struct iwl_trans *trans, + const struct iwl_ucode_tlv *tlv) +{ + const struct iwl_fw_ini_region_tlv *reg = (const void *)tlv->data; + struct iwl_ucode_tlv **active_reg; + u32 id = le32_to_cpu(reg->id); + u8 type = reg->type; + u32 tlv_len = sizeof(*tlv) + le32_to_cpu(tlv->length); + + /* + * The higher part of the ID from version 2 is debug policy. + * The id will be only lsb 16 bits, so mask it out. + */ + if (le32_to_cpu(reg->hdr.version) >= 2) + id &= IWL_FW_INI_REGION_ID_MASK; + + if (le32_to_cpu(tlv->length) < sizeof(*reg)) + return -EINVAL; + + /* for safe use of a string from FW, limit it to IWL_FW_INI_MAX_NAME */ + IWL_DEBUG_FW(trans, "WRT: parsing region: %.*s\n", + IWL_FW_INI_MAX_NAME, reg->name); + + if (id >= IWL_FW_INI_MAX_REGION_ID) { + IWL_ERR(trans, "WRT: Invalid region id %u\n", id); + return -EINVAL; + } + + if (type <= IWL_FW_INI_REGION_INVALID || + type >= IWL_FW_INI_REGION_NUM) { + IWL_ERR(trans, "WRT: Invalid region type %u\n", type); + return -EINVAL; + } + + if (type == IWL_FW_INI_REGION_PCI_IOSF_CONFIG && + !trans->ops->read_config32) { + IWL_ERR(trans, "WRT: Unsupported region type %u\n", type); + return -EOPNOTSUPP; + } + + if (type == IWL_FW_INI_REGION_INTERNAL_BUFFER) { + trans->dbg.imr_data.sram_addr = + le32_to_cpu(reg->internal_buffer.base_addr); + trans->dbg.imr_data.sram_size = + le32_to_cpu(reg->internal_buffer.size); + } + + + active_reg = &trans->dbg.active_regions[id]; + if (*active_reg) { + IWL_WARN(trans, "WRT: Overriding region id %u\n", id); + + kfree(*active_reg); + } + + *active_reg = kmemdup(tlv, tlv_len, GFP_KERNEL); + if (!*active_reg) + return -ENOMEM; + + IWL_DEBUG_FW(trans, "WRT: Enabling region id %u type %u\n", id, type); + + return 0; +} + +static int iwl_dbg_tlv_alloc_trigger(struct iwl_trans *trans, + const struct iwl_ucode_tlv *tlv) +{ + const struct iwl_fw_ini_trigger_tlv *trig = (const void *)tlv->data; + struct iwl_fw_ini_trigger_tlv *dup_trig; + u32 tp = le32_to_cpu(trig->time_point); + u32 rf = le32_to_cpu(trig->reset_fw); + struct iwl_ucode_tlv *dup = NULL; + int ret; + + if (le32_to_cpu(tlv->length) < sizeof(*trig)) + return -EINVAL; + + if (tp <= IWL_FW_INI_TIME_POINT_INVALID || + tp >= IWL_FW_INI_TIME_POINT_NUM) { + IWL_ERR(trans, + "WRT: Invalid time point %u for trigger TLV\n", + tp); + return -EINVAL; + } + + IWL_DEBUG_FW(trans, + "WRT: time point %u for trigger TLV with reset_fw %u\n", + tp, rf); + trans->dbg.last_tp_resetfw = 0xFF; + if (!le32_to_cpu(trig->occurrences)) { + dup = kmemdup(tlv, sizeof(*tlv) + le32_to_cpu(tlv->length), + GFP_KERNEL); + if (!dup) + return -ENOMEM; + dup_trig = (void *)dup->data; + dup_trig->occurrences = cpu_to_le32(-1); + tlv = dup; + } + + ret = iwl_dbg_tlv_add(tlv, &trans->dbg.time_point[tp].trig_list); + kfree(dup); + + return ret; +} + +static int iwl_dbg_tlv_config_set(struct iwl_trans *trans, + const struct iwl_ucode_tlv *tlv) +{ + const struct iwl_fw_ini_conf_set_tlv *conf_set = (const void *)tlv->data; + u32 tp = le32_to_cpu(conf_set->time_point); + u32 type = le32_to_cpu(conf_set->set_type); + + if (tp <= IWL_FW_INI_TIME_POINT_INVALID || + tp >= IWL_FW_INI_TIME_POINT_NUM) { + IWL_DEBUG_FW(trans, + "WRT: Invalid time point %u for config set TLV\n", tp); + return -EINVAL; + } + + if (type <= IWL_FW_INI_CONFIG_SET_TYPE_INVALID || + type >= IWL_FW_INI_CONFIG_SET_TYPE_MAX_NUM) { + IWL_DEBUG_FW(trans, + "WRT: Invalid config set type %u for config set TLV\n", type); + return -EINVAL; + } + + return iwl_dbg_tlv_add(tlv, &trans->dbg.time_point[tp].config_list); +} + +static int (*dbg_tlv_alloc[])(struct iwl_trans *trans, + const struct iwl_ucode_tlv *tlv) = { + [IWL_DBG_TLV_TYPE_DEBUG_INFO] = iwl_dbg_tlv_alloc_debug_info, + [IWL_DBG_TLV_TYPE_BUF_ALLOC] = iwl_dbg_tlv_alloc_buf_alloc, + [IWL_DBG_TLV_TYPE_HCMD] = iwl_dbg_tlv_alloc_hcmd, + [IWL_DBG_TLV_TYPE_REGION] = iwl_dbg_tlv_alloc_region, + [IWL_DBG_TLV_TYPE_TRIGGER] = iwl_dbg_tlv_alloc_trigger, + [IWL_DBG_TLV_TYPE_CONF_SET] = iwl_dbg_tlv_config_set, +}; + +void iwl_dbg_tlv_alloc(struct iwl_trans *trans, const struct iwl_ucode_tlv *tlv, + bool ext) +{ + enum iwl_ini_cfg_state *cfg_state = ext ? + &trans->dbg.external_ini_cfg : &trans->dbg.internal_ini_cfg; + const struct iwl_fw_ini_header *hdr = (const void *)&tlv->data[0]; + u32 type; + u32 tlv_idx; + u32 domain; + int ret; + + if (le32_to_cpu(tlv->length) < sizeof(*hdr)) + return; + + type = le32_to_cpu(tlv->type); + tlv_idx = type - IWL_UCODE_TLV_DEBUG_BASE; + domain = le32_to_cpu(hdr->domain); + + if (domain != IWL_FW_INI_DOMAIN_ALWAYS_ON && + !(domain & trans->dbg.domains_bitmap)) { + IWL_DEBUG_FW(trans, + "WRT: Skipping TLV with disabled domain 0x%0x (0x%0x)\n", + domain, trans->dbg.domains_bitmap); + return; + } + + if (tlv_idx >= ARRAY_SIZE(dbg_tlv_alloc) || !dbg_tlv_alloc[tlv_idx]) { + IWL_ERR(trans, "WRT: Unsupported TLV type 0x%x\n", type); + goto out_err; + } + + if (!iwl_dbg_tlv_ver_support(tlv)) { + IWL_ERR(trans, "WRT: Unsupported TLV 0x%x version %u\n", type, + le32_to_cpu(hdr->version)); + goto out_err; + } + + ret = dbg_tlv_alloc[tlv_idx](trans, tlv); + if (ret) { + IWL_ERR(trans, + "WRT: Failed to allocate TLV 0x%x, ret %d, (ext=%d)\n", + type, ret, ext); + goto out_err; + } + + if (*cfg_state == IWL_INI_CFG_STATE_NOT_LOADED) + *cfg_state = IWL_INI_CFG_STATE_LOADED; + + return; + +out_err: + *cfg_state = IWL_INI_CFG_STATE_CORRUPTED; +} + +void iwl_dbg_tlv_del_timers(struct iwl_trans *trans) +{ + struct list_head *timer_list = &trans->dbg.periodic_trig_list; + struct iwl_dbg_tlv_timer_node *node, *tmp; + + list_for_each_entry_safe(node, tmp, timer_list, list) { + del_timer_sync(&node->timer); + list_del(&node->list); + kfree(node); + } +} +IWL_EXPORT_SYMBOL(iwl_dbg_tlv_del_timers); + +static void iwl_dbg_tlv_fragments_free(struct iwl_trans *trans, + enum iwl_fw_ini_allocation_id alloc_id) +{ + struct iwl_fw_mon *fw_mon; + int i; + + if (alloc_id <= IWL_FW_INI_ALLOCATION_INVALID || + alloc_id >= IWL_FW_INI_ALLOCATION_NUM) + return; + + fw_mon = &trans->dbg.fw_mon_ini[alloc_id]; + + for (i = 0; i < fw_mon->num_frags; i++) { + struct iwl_dram_data *frag = &fw_mon->frags[i]; + + dma_free_coherent(trans->dev, frag->size, frag->block, + frag->physical); + + frag->physical = 0; + frag->block = NULL; + frag->size = 0; + } + + kfree(fw_mon->frags); + fw_mon->frags = NULL; + fw_mon->num_frags = 0; +} + +void iwl_dbg_tlv_free(struct iwl_trans *trans) +{ + struct iwl_dbg_tlv_node *tlv_node, *tlv_node_tmp; + int i; + + iwl_dbg_tlv_del_timers(trans); + + for (i = 0; i < ARRAY_SIZE(trans->dbg.active_regions); i++) { + struct iwl_ucode_tlv **active_reg = + &trans->dbg.active_regions[i]; + + kfree(*active_reg); + *active_reg = NULL; + } + + list_for_each_entry_safe(tlv_node, tlv_node_tmp, + &trans->dbg.debug_info_tlv_list, list) { + list_del(&tlv_node->list); + kfree(tlv_node); + } + + for (i = 0; i < ARRAY_SIZE(trans->dbg.time_point); i++) { + struct iwl_dbg_tlv_time_point_data *tp = + &trans->dbg.time_point[i]; + + list_for_each_entry_safe(tlv_node, tlv_node_tmp, &tp->trig_list, + list) { + list_del(&tlv_node->list); + kfree(tlv_node); + } + + list_for_each_entry_safe(tlv_node, tlv_node_tmp, &tp->hcmd_list, + list) { + list_del(&tlv_node->list); + kfree(tlv_node); + } + + list_for_each_entry_safe(tlv_node, tlv_node_tmp, + &tp->active_trig_list, list) { + list_del(&tlv_node->list); + kfree(tlv_node); + } + + list_for_each_entry_safe(tlv_node, tlv_node_tmp, + &tp->config_list, list) { + list_del(&tlv_node->list); + kfree(tlv_node); + } + + } + + for (i = 0; i < ARRAY_SIZE(trans->dbg.fw_mon_ini); i++) + iwl_dbg_tlv_fragments_free(trans, i); +} + +static int iwl_dbg_tlv_parse_bin(struct iwl_trans *trans, const u8 *data, + size_t len) +{ + const struct iwl_ucode_tlv *tlv; + u32 tlv_len; + + while (len >= sizeof(*tlv)) { + len -= sizeof(*tlv); + tlv = (const void *)data; + + tlv_len = le32_to_cpu(tlv->length); + + if (len < tlv_len) { + IWL_ERR(trans, "invalid TLV len: %zd/%u\n", + len, tlv_len); + return -EINVAL; + } + len -= ALIGN(tlv_len, 4); + data += sizeof(*tlv) + ALIGN(tlv_len, 4); + + iwl_dbg_tlv_alloc(trans, tlv, true); + } + + return 0; +} + +void iwl_dbg_tlv_load_bin(struct device *dev, struct iwl_trans *trans) +{ + const struct firmware *fw; + const char *yoyo_bin = "iwl-debug-yoyo.bin"; + int res; + + if (!iwlwifi_mod_params.enable_ini || + trans->trans_cfg->device_family <= IWL_DEVICE_FAMILY_8000) + return; + + res = firmware_request_nowarn(&fw, yoyo_bin, dev); + IWL_DEBUG_FW(trans, "%s %s\n", res ? "didn't load" : "loaded", yoyo_bin); + + if (res) + return; + + iwl_dbg_tlv_parse_bin(trans, fw->data, fw->size); + + release_firmware(fw); +} + +void iwl_dbg_tlv_init(struct iwl_trans *trans) +{ + int i; + + INIT_LIST_HEAD(&trans->dbg.debug_info_tlv_list); + INIT_LIST_HEAD(&trans->dbg.periodic_trig_list); + + for (i = 0; i < ARRAY_SIZE(trans->dbg.time_point); i++) { + struct iwl_dbg_tlv_time_point_data *tp = + &trans->dbg.time_point[i]; + + INIT_LIST_HEAD(&tp->trig_list); + INIT_LIST_HEAD(&tp->hcmd_list); + INIT_LIST_HEAD(&tp->active_trig_list); + INIT_LIST_HEAD(&tp->config_list); + } +} + +static int iwl_dbg_tlv_alloc_fragment(struct iwl_fw_runtime *fwrt, + struct iwl_dram_data *frag, u32 pages) +{ + void *block = NULL; + dma_addr_t physical; + + if (!frag || frag->size || !pages) + return -EIO; + + /* + * We try to allocate as many pages as we can, starting with + * the requested amount and going down until we can allocate + * something. Because of DIV_ROUND_UP(), pages will never go + * down to 0 and stop the loop, so stop when pages reaches 1, + * which is too small anyway. + */ + while (pages > 1) { + block = dma_alloc_coherent(fwrt->dev, pages * PAGE_SIZE, + &physical, + GFP_KERNEL | __GFP_NOWARN); + if (block) + break; + + IWL_WARN(fwrt, "WRT: Failed to allocate fragment size %lu\n", + pages * PAGE_SIZE); + + pages = DIV_ROUND_UP(pages, 2); + } + + if (!block) + return -ENOMEM; + + frag->physical = physical; + frag->block = block; + frag->size = pages * PAGE_SIZE; + + return pages; +} + +static int iwl_dbg_tlv_alloc_fragments(struct iwl_fw_runtime *fwrt, + enum iwl_fw_ini_allocation_id alloc_id) +{ + struct iwl_fw_mon *fw_mon; + struct iwl_fw_ini_allocation_tlv *fw_mon_cfg; + u32 num_frags, remain_pages, frag_pages; + int i; + + if (alloc_id < IWL_FW_INI_ALLOCATION_INVALID || + alloc_id >= IWL_FW_INI_ALLOCATION_NUM) + return -EIO; + + fw_mon_cfg = &fwrt->trans->dbg.fw_mon_cfg[alloc_id]; + fw_mon = &fwrt->trans->dbg.fw_mon_ini[alloc_id]; + + if (fw_mon->num_frags || + fw_mon_cfg->buf_location != + cpu_to_le32(IWL_FW_INI_LOCATION_DRAM_PATH)) + return 0; + + num_frags = le32_to_cpu(fw_mon_cfg->max_frags_num); + if (fwrt->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) { + if (alloc_id != IWL_FW_INI_ALLOCATION_ID_DBGC1) + return -EIO; + num_frags = 1; + } + + remain_pages = DIV_ROUND_UP(le32_to_cpu(fw_mon_cfg->req_size), + PAGE_SIZE); + num_frags = min_t(u32, num_frags, BUF_ALLOC_MAX_NUM_FRAGS); + num_frags = min_t(u32, num_frags, remain_pages); + frag_pages = DIV_ROUND_UP(remain_pages, num_frags); + + fw_mon->frags = kcalloc(num_frags, sizeof(*fw_mon->frags), GFP_KERNEL); + if (!fw_mon->frags) + return -ENOMEM; + + for (i = 0; i < num_frags; i++) { + int pages = min_t(u32, frag_pages, remain_pages); + + IWL_DEBUG_FW(fwrt, + "WRT: Allocating DRAM buffer (alloc_id=%u, fragment=%u, size=0x%lx)\n", + alloc_id, i, pages * PAGE_SIZE); + + pages = iwl_dbg_tlv_alloc_fragment(fwrt, &fw_mon->frags[i], + pages); + if (pages < 0) { + u32 alloc_size = le32_to_cpu(fw_mon_cfg->req_size) - + (remain_pages * PAGE_SIZE); + + if (alloc_size < le32_to_cpu(fw_mon_cfg->min_size)) { + iwl_dbg_tlv_fragments_free(fwrt->trans, + alloc_id); + return pages; + } + break; + } + + remain_pages -= pages; + fw_mon->num_frags++; + } + + return 0; +} + +static int iwl_dbg_tlv_apply_buffer(struct iwl_fw_runtime *fwrt, + enum iwl_fw_ini_allocation_id alloc_id) +{ + struct iwl_fw_mon *fw_mon; + u32 remain_frags, num_commands; + int i, fw_mon_idx = 0; + + if (!fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DBG_BUF_ALLOC_CMD_SUPP)) + return 0; + + if (alloc_id < IWL_FW_INI_ALLOCATION_INVALID || + alloc_id >= IWL_FW_INI_ALLOCATION_NUM) + return -EIO; + + if (le32_to_cpu(fwrt->trans->dbg.fw_mon_cfg[alloc_id].buf_location) != + IWL_FW_INI_LOCATION_DRAM_PATH) + return 0; + + fw_mon = &fwrt->trans->dbg.fw_mon_ini[alloc_id]; + + /* the first fragment of DBGC1 is given to the FW via register + * or context info + */ + if (alloc_id == IWL_FW_INI_ALLOCATION_ID_DBGC1) + fw_mon_idx++; + + remain_frags = fw_mon->num_frags - fw_mon_idx; + if (!remain_frags) + return 0; + + num_commands = DIV_ROUND_UP(remain_frags, BUF_ALLOC_MAX_NUM_FRAGS); + + IWL_DEBUG_FW(fwrt, "WRT: Applying DRAM destination (alloc_id=%u)\n", + alloc_id); + + for (i = 0; i < num_commands; i++) { + u32 num_frags = min_t(u32, remain_frags, + BUF_ALLOC_MAX_NUM_FRAGS); + struct iwl_buf_alloc_cmd data = { + .alloc_id = cpu_to_le32(alloc_id), + .num_frags = cpu_to_le32(num_frags), + .buf_location = + cpu_to_le32(IWL_FW_INI_LOCATION_DRAM_PATH), + }; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(DEBUG_GROUP, BUFFER_ALLOCATION), + .data[0] = &data, + .len[0] = sizeof(data), + .flags = CMD_SEND_IN_RFKILL, + }; + int ret, j; + + for (j = 0; j < num_frags; j++) { + struct iwl_buf_alloc_frag *frag = &data.frags[j]; + struct iwl_dram_data *fw_mon_frag = + &fw_mon->frags[fw_mon_idx++]; + + frag->addr = cpu_to_le64(fw_mon_frag->physical); + frag->size = cpu_to_le32(fw_mon_frag->size); + } + ret = iwl_trans_send_cmd(fwrt->trans, &hcmd); + if (ret) + return ret; + + remain_frags -= num_frags; + } + + return 0; +} + +static void iwl_dbg_tlv_apply_buffers(struct iwl_fw_runtime *fwrt) +{ + int ret, i; + + if (fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DRAM_FRAG_SUPPORT)) + return; + + for (i = 0; i < IWL_FW_INI_ALLOCATION_NUM; i++) { + ret = iwl_dbg_tlv_apply_buffer(fwrt, i); + if (ret) + IWL_WARN(fwrt, + "WRT: Failed to apply DRAM buffer for allocation id %d, ret=%d\n", + i, ret); + } +} + +static int iwl_dbg_tlv_update_dram(struct iwl_fw_runtime *fwrt, + enum iwl_fw_ini_allocation_id alloc_id, + struct iwl_dram_info *dram_info) +{ + struct iwl_fw_mon *fw_mon; + u32 remain_frags, num_frags; + int j, fw_mon_idx = 0; + struct iwl_buf_alloc_cmd *data; + + if (le32_to_cpu(fwrt->trans->dbg.fw_mon_cfg[alloc_id].buf_location) != + IWL_FW_INI_LOCATION_DRAM_PATH) { + IWL_DEBUG_FW(fwrt, "DRAM_PATH is not supported alloc_id %u\n", alloc_id); + return -1; + } + + fw_mon = &fwrt->trans->dbg.fw_mon_ini[alloc_id]; + + /* the first fragment of DBGC1 is given to the FW via register + * or context info + */ + if (alloc_id == IWL_FW_INI_ALLOCATION_ID_DBGC1) + fw_mon_idx++; + + remain_frags = fw_mon->num_frags - fw_mon_idx; + if (!remain_frags) + return -1; + + num_frags = min_t(u32, remain_frags, BUF_ALLOC_MAX_NUM_FRAGS); + data = &dram_info->dram_frags[alloc_id - 1]; + data->alloc_id = cpu_to_le32(alloc_id); + data->num_frags = cpu_to_le32(num_frags); + data->buf_location = cpu_to_le32(IWL_FW_INI_LOCATION_DRAM_PATH); + + IWL_DEBUG_FW(fwrt, "WRT: DRAM buffer details alloc_id=%u, num_frags=%u\n", + cpu_to_le32(alloc_id), cpu_to_le32(num_frags)); + + for (j = 0; j < num_frags; j++) { + struct iwl_buf_alloc_frag *frag = &data->frags[j]; + struct iwl_dram_data *fw_mon_frag = &fw_mon->frags[fw_mon_idx++]; + + frag->addr = cpu_to_le64(fw_mon_frag->physical); + frag->size = cpu_to_le32(fw_mon_frag->size); + IWL_DEBUG_FW(fwrt, "WRT: DRAM fragment details\n"); + IWL_DEBUG_FW(fwrt, "frag=%u, addr=0x%016llx, size=0x%x)\n", + j, cpu_to_le64(fw_mon_frag->physical), + cpu_to_le32(fw_mon_frag->size)); + } + return 0; +} + +static void iwl_dbg_tlv_update_drams(struct iwl_fw_runtime *fwrt) +{ + int ret, i; + bool dram_alloc = false; + struct iwl_dram_data *frags = + &fwrt->trans->dbg.fw_mon_ini[IWL_FW_INI_ALLOCATION_ID_DBGC1].frags[0]; + struct iwl_dram_info *dram_info; + + if (!frags || !frags->block) + return; + + dram_info = frags->block; + + if (!fw_has_capa(&fwrt->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DRAM_FRAG_SUPPORT)) + return; + + dram_info->first_word = cpu_to_le32(DRAM_INFO_FIRST_MAGIC_WORD); + dram_info->second_word = cpu_to_le32(DRAM_INFO_SECOND_MAGIC_WORD); + + for (i = IWL_FW_INI_ALLOCATION_ID_DBGC1; + i <= IWL_FW_INI_ALLOCATION_ID_DBGC3; i++) { + ret = iwl_dbg_tlv_update_dram(fwrt, i, dram_info); + if (!ret) + dram_alloc = true; + else + IWL_WARN(fwrt, + "WRT: Failed to set DRAM buffer for alloc id %d, ret=%d\n", + i, ret); + } + + if (dram_alloc) + IWL_DEBUG_FW(fwrt, "block data after %08x\n", + dram_info->first_word); + else + memset(frags->block, 0, sizeof(*dram_info)); +} + +static void iwl_dbg_tlv_send_hcmds(struct iwl_fw_runtime *fwrt, + struct list_head *hcmd_list) +{ + struct iwl_dbg_tlv_node *node; + + list_for_each_entry(node, hcmd_list, list) { + struct iwl_fw_ini_hcmd_tlv *hcmd = (void *)node->tlv.data; + struct iwl_fw_ini_hcmd *hcmd_data = &hcmd->hcmd; + u16 hcmd_len = le32_to_cpu(node->tlv.length) - sizeof(*hcmd); + struct iwl_host_cmd cmd = { + .id = WIDE_ID(hcmd_data->group, hcmd_data->id), + .len = { hcmd_len, }, + .data = { hcmd_data->data, }, + }; + + iwl_trans_send_cmd(fwrt->trans, &cmd); + } +} + +static void iwl_dbg_tlv_apply_config(struct iwl_fw_runtime *fwrt, + struct list_head *conf_list) +{ + struct iwl_dbg_tlv_node *node; + + list_for_each_entry(node, conf_list, list) { + struct iwl_fw_ini_conf_set_tlv *config_list = (void *)node->tlv.data; + u32 count, address, value; + u32 len = (le32_to_cpu(node->tlv.length) - sizeof(*config_list)) / 8; + u32 type = le32_to_cpu(config_list->set_type); + u32 offset = le32_to_cpu(config_list->addr_offset); + + switch (type) { + case IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_PERIPHERY_MAC: { + if (!iwl_trans_grab_nic_access(fwrt->trans)) { + IWL_DEBUG_FW(fwrt, "WRT: failed to get nic access\n"); + IWL_DEBUG_FW(fwrt, "WRT: skipping MAC PERIPHERY config\n"); + continue; + } + IWL_DEBUG_FW(fwrt, "WRT: MAC PERIPHERY config len: len %u\n", len); + for (count = 0; count < len; count++) { + address = le32_to_cpu(config_list->addr_val[count].address); + value = le32_to_cpu(config_list->addr_val[count].value); + iwl_trans_write_prph(fwrt->trans, address + offset, value); + } + iwl_trans_release_nic_access(fwrt->trans); + break; + } + case IWL_FW_INI_CONFIG_SET_TYPE_DEVICE_MEMORY: { + for (count = 0; count < len; count++) { + address = le32_to_cpu(config_list->addr_val[count].address); + value = le32_to_cpu(config_list->addr_val[count].value); + iwl_trans_write_mem32(fwrt->trans, address + offset, value); + IWL_DEBUG_FW(fwrt, "WRT: DEV_MEM: count %u, add: %u val: %u\n", + count, address, value); + } + break; + } + case IWL_FW_INI_CONFIG_SET_TYPE_CSR: { + for (count = 0; count < len; count++) { + address = le32_to_cpu(config_list->addr_val[count].address); + value = le32_to_cpu(config_list->addr_val[count].value); + iwl_write32(fwrt->trans, address + offset, value); + IWL_DEBUG_FW(fwrt, "WRT: CSR: count %u, add: %u val: %u\n", + count, address, value); + } + break; + } + case IWL_FW_INI_CONFIG_SET_TYPE_DBGC_DRAM_ADDR: { + struct iwl_dbgc1_info dram_info = {}; + struct iwl_dram_data *frags = &fwrt->trans->dbg.fw_mon_ini[1].frags[0]; + __le64 dram_base_addr; + __le32 dram_size; + u64 dram_addr; + u32 ret; + + if (!frags) + break; + + dram_base_addr = cpu_to_le64(frags->physical); + dram_size = cpu_to_le32(frags->size); + dram_addr = le64_to_cpu(dram_base_addr); + + IWL_DEBUG_FW(fwrt, "WRT: dram_base_addr 0x%016llx, dram_size 0x%x\n", + dram_base_addr, dram_size); + IWL_DEBUG_FW(fwrt, "WRT: config_list->addr_offset: %u\n", + le32_to_cpu(config_list->addr_offset)); + for (count = 0; count < len; count++) { + address = le32_to_cpu(config_list->addr_val[count].address); + dram_info.dbgc1_add_lsb = + cpu_to_le32((dram_addr & 0x00000000FFFFFFFFULL) + 0x400); + dram_info.dbgc1_add_msb = + cpu_to_le32((dram_addr & 0xFFFFFFFF00000000ULL) >> 32); + dram_info.dbgc1_size = cpu_to_le32(le32_to_cpu(dram_size) - 0x400); + ret = iwl_trans_write_mem(fwrt->trans, + address + offset, &dram_info, 4); + if (ret) { + IWL_ERR(fwrt, "Failed to write dram_info to HW_SMEM\n"); + break; + } + } + break; + } + case IWL_FW_INI_CONFIG_SET_TYPE_PERIPH_SCRATCH_HWM: { + u32 debug_token_config = + le32_to_cpu(config_list->addr_val[0].value); + + IWL_DEBUG_FW(fwrt, "WRT: Setting HWM debug token config: %u\n", + debug_token_config); + fwrt->trans->dbg.ucode_preset = debug_token_config; + break; + } + default: + break; + } + } +} + +static void iwl_dbg_tlv_periodic_trig_handler(struct timer_list *t) +{ + struct iwl_dbg_tlv_timer_node *timer_node = + from_timer(timer_node, t, timer); + struct iwl_fwrt_dump_data dump_data = { + .trig = (void *)timer_node->tlv->data, + }; + int ret; + + ret = iwl_fw_dbg_ini_collect(timer_node->fwrt, &dump_data, false); + if (!ret || ret == -EBUSY) { + u32 occur = le32_to_cpu(dump_data.trig->occurrences); + u32 collect_interval = le32_to_cpu(dump_data.trig->data[0]); + + if (!occur) + return; + + mod_timer(t, jiffies + msecs_to_jiffies(collect_interval)); + } +} + +static void iwl_dbg_tlv_set_periodic_trigs(struct iwl_fw_runtime *fwrt) +{ + struct iwl_dbg_tlv_node *node; + struct list_head *trig_list = + &fwrt->trans->dbg.time_point[IWL_FW_INI_TIME_POINT_PERIODIC].active_trig_list; + + list_for_each_entry(node, trig_list, list) { + struct iwl_fw_ini_trigger_tlv *trig = (void *)node->tlv.data; + struct iwl_dbg_tlv_timer_node *timer_node; + u32 occur = le32_to_cpu(trig->occurrences), collect_interval; + u32 min_interval = 100; + + if (!occur) + continue; + + /* make sure there is at least one dword of data for the + * interval value + */ + if (le32_to_cpu(node->tlv.length) < + sizeof(*trig) + sizeof(__le32)) { + IWL_ERR(fwrt, + "WRT: Invalid periodic trigger data was not given\n"); + continue; + } + + if (le32_to_cpu(trig->data[0]) < min_interval) { + IWL_WARN(fwrt, + "WRT: Override min interval from %u to %u msec\n", + le32_to_cpu(trig->data[0]), min_interval); + trig->data[0] = cpu_to_le32(min_interval); + } + + collect_interval = le32_to_cpu(trig->data[0]); + + timer_node = kzalloc(sizeof(*timer_node), GFP_KERNEL); + if (!timer_node) { + IWL_ERR(fwrt, + "WRT: Failed to allocate periodic trigger\n"); + continue; + } + + timer_node->fwrt = fwrt; + timer_node->tlv = &node->tlv; + timer_setup(&timer_node->timer, + iwl_dbg_tlv_periodic_trig_handler, 0); + + list_add_tail(&timer_node->list, + &fwrt->trans->dbg.periodic_trig_list); + + IWL_DEBUG_FW(fwrt, "WRT: Enabling periodic trigger\n"); + + mod_timer(&timer_node->timer, + jiffies + msecs_to_jiffies(collect_interval)); + } +} + +static bool is_trig_data_contained(const struct iwl_ucode_tlv *new, + const struct iwl_ucode_tlv *old) +{ + const struct iwl_fw_ini_trigger_tlv *new_trig = (const void *)new->data; + const struct iwl_fw_ini_trigger_tlv *old_trig = (const void *)old->data; + const __le32 *new_data = new_trig->data, *old_data = old_trig->data; + u32 new_dwords_num = iwl_tlv_array_len(new, new_trig, data); + u32 old_dwords_num = iwl_tlv_array_len(old, old_trig, data); + int i, j; + + for (i = 0; i < new_dwords_num; i++) { + bool match = false; + + for (j = 0; j < old_dwords_num; j++) { + if (new_data[i] == old_data[j]) { + match = true; + break; + } + } + if (!match) + return false; + } + + return true; +} + +static int iwl_dbg_tlv_override_trig_node(struct iwl_fw_runtime *fwrt, + struct iwl_ucode_tlv *trig_tlv, + struct iwl_dbg_tlv_node *node) +{ + struct iwl_ucode_tlv *node_tlv = &node->tlv; + struct iwl_fw_ini_trigger_tlv *node_trig = (void *)node_tlv->data; + struct iwl_fw_ini_trigger_tlv *trig = (void *)trig_tlv->data; + u32 policy = le32_to_cpu(trig->apply_policy); + u32 size = le32_to_cpu(trig_tlv->length); + u32 trig_data_len = size - sizeof(*trig); + u32 offset = 0; + + if (!(policy & IWL_FW_INI_APPLY_POLICY_OVERRIDE_DATA)) { + u32 data_len = le32_to_cpu(node_tlv->length) - + sizeof(*node_trig); + + IWL_DEBUG_FW(fwrt, + "WRT: Appending trigger data (time point %u)\n", + le32_to_cpu(trig->time_point)); + + offset += data_len; + size += data_len; + } else { + IWL_DEBUG_FW(fwrt, + "WRT: Overriding trigger data (time point %u)\n", + le32_to_cpu(trig->time_point)); + } + + if (size != le32_to_cpu(node_tlv->length)) { + struct list_head *prev = node->list.prev; + struct iwl_dbg_tlv_node *tmp; + + list_del(&node->list); + + tmp = krealloc(node, sizeof(*node) + size, GFP_KERNEL); + if (!tmp) { + IWL_WARN(fwrt, + "WRT: No memory to override trigger (time point %u)\n", + le32_to_cpu(trig->time_point)); + + list_add(&node->list, prev); + + return -ENOMEM; + } + + list_add(&tmp->list, prev); + node_tlv = &tmp->tlv; + node_trig = (void *)node_tlv->data; + } + + memcpy((u8 *)node_trig->data + offset, trig->data, trig_data_len); + node_tlv->length = cpu_to_le32(size); + + if (policy & IWL_FW_INI_APPLY_POLICY_OVERRIDE_CFG) { + IWL_DEBUG_FW(fwrt, + "WRT: Overriding trigger configuration (time point %u)\n", + le32_to_cpu(trig->time_point)); + + /* the first 11 dwords are configuration related */ + memcpy(node_trig, trig, sizeof(__le32) * 11); + } + + if (policy & IWL_FW_INI_APPLY_POLICY_OVERRIDE_REGIONS) { + IWL_DEBUG_FW(fwrt, + "WRT: Overriding trigger regions (time point %u)\n", + le32_to_cpu(trig->time_point)); + + node_trig->regions_mask = trig->regions_mask; + } else { + IWL_DEBUG_FW(fwrt, + "WRT: Appending trigger regions (time point %u)\n", + le32_to_cpu(trig->time_point)); + + node_trig->regions_mask |= trig->regions_mask; + } + + return 0; +} + +static int +iwl_dbg_tlv_add_active_trigger(struct iwl_fw_runtime *fwrt, + struct list_head *trig_list, + struct iwl_ucode_tlv *trig_tlv) +{ + struct iwl_fw_ini_trigger_tlv *trig = (void *)trig_tlv->data; + struct iwl_dbg_tlv_node *node, *match = NULL; + u32 policy = le32_to_cpu(trig->apply_policy); + + list_for_each_entry(node, trig_list, list) { + if (!(policy & IWL_FW_INI_APPLY_POLICY_MATCH_TIME_POINT)) + break; + + if (!(policy & IWL_FW_INI_APPLY_POLICY_MATCH_DATA) || + is_trig_data_contained(trig_tlv, &node->tlv)) { + match = node; + break; + } + } + + if (!match) { + IWL_DEBUG_FW(fwrt, "WRT: Enabling trigger (time point %u)\n", + le32_to_cpu(trig->time_point)); + return iwl_dbg_tlv_add(trig_tlv, trig_list); + } + + return iwl_dbg_tlv_override_trig_node(fwrt, trig_tlv, match); +} + +static void +iwl_dbg_tlv_gen_active_trig_list(struct iwl_fw_runtime *fwrt, + struct iwl_dbg_tlv_time_point_data *tp) +{ + struct iwl_dbg_tlv_node *node; + struct list_head *trig_list = &tp->trig_list; + struct list_head *active_trig_list = &tp->active_trig_list; + + list_for_each_entry(node, trig_list, list) { + struct iwl_ucode_tlv *tlv = &node->tlv; + + iwl_dbg_tlv_add_active_trigger(fwrt, active_trig_list, tlv); + } +} + +static bool iwl_dbg_tlv_check_fw_pkt(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data, + union iwl_dbg_tlv_tp_data *tp_data, + u32 trig_data) +{ + struct iwl_rx_packet *pkt = tp_data->fw_pkt; + struct iwl_cmd_header *wanted_hdr = (void *)&trig_data; + + if (pkt && (pkt->hdr.cmd == wanted_hdr->cmd && + pkt->hdr.group_id == wanted_hdr->group_id)) { + struct iwl_rx_packet *fw_pkt = + kmemdup(pkt, + sizeof(*pkt) + iwl_rx_packet_payload_len(pkt), + GFP_ATOMIC); + + if (!fw_pkt) + return false; + + dump_data->fw_pkt = fw_pkt; + + return true; + } + + return false; +} + +static int +iwl_dbg_tlv_tp_trigger(struct iwl_fw_runtime *fwrt, bool sync, + struct list_head *active_trig_list, + union iwl_dbg_tlv_tp_data *tp_data, + bool (*data_check)(struct iwl_fw_runtime *fwrt, + struct iwl_fwrt_dump_data *dump_data, + union iwl_dbg_tlv_tp_data *tp_data, + u32 trig_data)) +{ + struct iwl_dbg_tlv_node *node; + + list_for_each_entry(node, active_trig_list, list) { + struct iwl_fwrt_dump_data dump_data = { + .trig = (void *)node->tlv.data, + }; + u32 num_data = iwl_tlv_array_len(&node->tlv, dump_data.trig, + data); + int ret, i; + u32 tp = le32_to_cpu(dump_data.trig->time_point); + + + if (!num_data) { + ret = iwl_fw_dbg_ini_collect(fwrt, &dump_data, sync); + if (ret) + return ret; + } + + for (i = 0; i < num_data; i++) { + if (!data_check || + data_check(fwrt, &dump_data, tp_data, + le32_to_cpu(dump_data.trig->data[i]))) { + ret = iwl_fw_dbg_ini_collect(fwrt, &dump_data, sync); + if (ret) + return ret; + + break; + } + } + + fwrt->trans->dbg.restart_required = FALSE; + IWL_DEBUG_INFO(fwrt, "WRT: tp %d, reset_fw %d\n", + tp, dump_data.trig->reset_fw); + IWL_DEBUG_INFO(fwrt, "WRT: restart_required %d, last_tp_resetfw %d\n", + fwrt->trans->dbg.restart_required, + fwrt->trans->dbg.last_tp_resetfw); + + if (fwrt->trans->trans_cfg->device_family == + IWL_DEVICE_FAMILY_9000) { + fwrt->trans->dbg.restart_required = TRUE; + } else if (tp == IWL_FW_INI_TIME_POINT_FW_ASSERT && + fwrt->trans->dbg.last_tp_resetfw == + IWL_FW_INI_RESET_FW_MODE_STOP_FW_ONLY) { + fwrt->trans->dbg.restart_required = FALSE; + fwrt->trans->dbg.last_tp_resetfw = 0xFF; + IWL_DEBUG_FW(fwrt, "WRT: FW_ASSERT due to reset_fw_mode-no restart\n"); + } else if (le32_to_cpu(dump_data.trig->reset_fw) == + IWL_FW_INI_RESET_FW_MODE_STOP_AND_RELOAD_FW) { + IWL_DEBUG_INFO(fwrt, "WRT: stop and reload firmware\n"); + fwrt->trans->dbg.restart_required = TRUE; + } else if (le32_to_cpu(dump_data.trig->reset_fw) == + IWL_FW_INI_RESET_FW_MODE_STOP_FW_ONLY) { + IWL_DEBUG_INFO(fwrt, "WRT: stop only and no reload firmware\n"); + fwrt->trans->dbg.restart_required = FALSE; + fwrt->trans->dbg.last_tp_resetfw = + le32_to_cpu(dump_data.trig->reset_fw); + } else if (le32_to_cpu(dump_data.trig->reset_fw) == + IWL_FW_INI_RESET_FW_MODE_NOTHING) { + IWL_DEBUG_INFO(fwrt, + "WRT: nothing need to be done after debug collection\n"); + } else { + IWL_ERR(fwrt, "WRT: wrong resetfw %d\n", + le32_to_cpu(dump_data.trig->reset_fw)); + } + } + return 0; +} + +static void iwl_dbg_tlv_init_cfg(struct iwl_fw_runtime *fwrt) +{ + enum iwl_fw_ini_buffer_location *ini_dest = &fwrt->trans->dbg.ini_dest; + int ret, i; + u32 failed_alloc = 0; + + if (*ini_dest != IWL_FW_INI_LOCATION_INVALID) + return; + + IWL_DEBUG_FW(fwrt, + "WRT: Generating active triggers list, domain 0x%x\n", + fwrt->trans->dbg.domains_bitmap); + + for (i = 0; i < ARRAY_SIZE(fwrt->trans->dbg.time_point); i++) { + struct iwl_dbg_tlv_time_point_data *tp = + &fwrt->trans->dbg.time_point[i]; + + iwl_dbg_tlv_gen_active_trig_list(fwrt, tp); + } + + *ini_dest = IWL_FW_INI_LOCATION_INVALID; + for (i = 0; i < IWL_FW_INI_ALLOCATION_NUM; i++) { + struct iwl_fw_ini_allocation_tlv *fw_mon_cfg = + &fwrt->trans->dbg.fw_mon_cfg[i]; + u32 dest = le32_to_cpu(fw_mon_cfg->buf_location); + + if (dest == IWL_FW_INI_LOCATION_INVALID) { + failed_alloc |= BIT(i); + continue; + } + + if (*ini_dest == IWL_FW_INI_LOCATION_INVALID) + *ini_dest = dest; + + if (dest != *ini_dest) + continue; + + ret = iwl_dbg_tlv_alloc_fragments(fwrt, i); + + if (ret) { + IWL_WARN(fwrt, + "WRT: Failed to allocate DRAM buffer for allocation id %d, ret=%d\n", + i, ret); + failed_alloc |= BIT(i); + } + } + + if (!failed_alloc) + return; + + for (i = 0; i < ARRAY_SIZE(fwrt->trans->dbg.active_regions) && failed_alloc; i++) { + struct iwl_fw_ini_region_tlv *reg; + struct iwl_ucode_tlv **active_reg = + &fwrt->trans->dbg.active_regions[i]; + u32 reg_type; + + if (!*active_reg) { + fwrt->trans->dbg.unsupported_region_msk |= BIT(i); + continue; + } + + reg = (void *)(*active_reg)->data; + reg_type = reg->type; + + if (reg_type != IWL_FW_INI_REGION_DRAM_BUFFER || + !(BIT(le32_to_cpu(reg->dram_alloc_id)) & failed_alloc)) + continue; + + IWL_DEBUG_FW(fwrt, + "WRT: removing allocation id %d from region id %d\n", + le32_to_cpu(reg->dram_alloc_id), i); + + failed_alloc &= ~le32_to_cpu(reg->dram_alloc_id); + fwrt->trans->dbg.unsupported_region_msk |= BIT(i); + + kfree(*active_reg); + *active_reg = NULL; + } +} + +void _iwl_dbg_tlv_time_point(struct iwl_fw_runtime *fwrt, + enum iwl_fw_ini_time_point tp_id, + union iwl_dbg_tlv_tp_data *tp_data, + bool sync) +{ + struct list_head *hcmd_list, *trig_list, *conf_list; + + if (!iwl_trans_dbg_ini_valid(fwrt->trans) || + tp_id == IWL_FW_INI_TIME_POINT_INVALID || + tp_id >= IWL_FW_INI_TIME_POINT_NUM) + return; + + hcmd_list = &fwrt->trans->dbg.time_point[tp_id].hcmd_list; + trig_list = &fwrt->trans->dbg.time_point[tp_id].active_trig_list; + conf_list = &fwrt->trans->dbg.time_point[tp_id].config_list; + + switch (tp_id) { + case IWL_FW_INI_TIME_POINT_EARLY: + iwl_dbg_tlv_init_cfg(fwrt); + iwl_dbg_tlv_apply_config(fwrt, conf_list); + iwl_dbg_tlv_update_drams(fwrt); + iwl_dbg_tlv_tp_trigger(fwrt, sync, trig_list, tp_data, NULL); + break; + case IWL_FW_INI_TIME_POINT_AFTER_ALIVE: + iwl_dbg_tlv_apply_buffers(fwrt); + iwl_dbg_tlv_send_hcmds(fwrt, hcmd_list); + iwl_dbg_tlv_apply_config(fwrt, conf_list); + iwl_dbg_tlv_tp_trigger(fwrt, sync, trig_list, tp_data, NULL); + break; + case IWL_FW_INI_TIME_POINT_PERIODIC: + iwl_dbg_tlv_set_periodic_trigs(fwrt); + iwl_dbg_tlv_send_hcmds(fwrt, hcmd_list); + break; + case IWL_FW_INI_TIME_POINT_FW_RSP_OR_NOTIF: + case IWL_FW_INI_TIME_POINT_MISSED_BEACONS: + case IWL_FW_INI_TIME_POINT_FW_DHC_NOTIFICATION: + iwl_dbg_tlv_send_hcmds(fwrt, hcmd_list); + iwl_dbg_tlv_apply_config(fwrt, conf_list); + iwl_dbg_tlv_tp_trigger(fwrt, sync, trig_list, tp_data, + iwl_dbg_tlv_check_fw_pkt); + break; + default: + iwl_dbg_tlv_send_hcmds(fwrt, hcmd_list); + iwl_dbg_tlv_apply_config(fwrt, conf_list); + iwl_dbg_tlv_tp_trigger(fwrt, sync, trig_list, tp_data, NULL); + break; + } +} +IWL_EXPORT_SYMBOL(_iwl_dbg_tlv_time_point); diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.h b/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.h new file mode 100644 index 000000000..06fb7d665 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-dbg-tlv.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2018-2023 Intel Corporation + */ +#ifndef __iwl_dbg_tlv_h__ +#define __iwl_dbg_tlv_h__ + +#include <linux/device.h> +#include <linux/types.h> +#include <fw/file.h> +#include <fw/api/dbg-tlv.h> + +#define IWL_DBG_TLV_MAX_PRESET 15 +#define ENABLE_INI (IWL_DBG_TLV_MAX_PRESET + 1) + +/** + * struct iwl_dbg_tlv_node - debug TLV node + * @list: list of &struct iwl_dbg_tlv_node + * @tlv: debug TLV + */ +struct iwl_dbg_tlv_node { + struct list_head list; + struct iwl_ucode_tlv tlv; +}; + +/** + * union iwl_dbg_tlv_tp_data - data that is given in a time point + * @fw_pkt: a packet received from the FW + */ +union iwl_dbg_tlv_tp_data { + struct iwl_rx_packet *fw_pkt; +}; + +/** + * struct iwl_dbg_tlv_time_point_data + * @trig_list: list of triggers + * @active_trig_list: list of active triggers + * @hcmd_list: list of host commands + * @config_list: list of configuration + */ +struct iwl_dbg_tlv_time_point_data { + struct list_head trig_list; + struct list_head active_trig_list; + struct list_head hcmd_list; + struct list_head config_list; +}; + +struct iwl_trans; +struct iwl_fw_runtime; + +void iwl_dbg_tlv_load_bin(struct device *dev, struct iwl_trans *trans); +void iwl_dbg_tlv_free(struct iwl_trans *trans); +void iwl_dbg_tlv_alloc(struct iwl_trans *trans, const struct iwl_ucode_tlv *tlv, + bool ext); +void iwl_dbg_tlv_init(struct iwl_trans *trans); +void _iwl_dbg_tlv_time_point(struct iwl_fw_runtime *fwrt, + enum iwl_fw_ini_time_point tp_id, + union iwl_dbg_tlv_tp_data *tp_data, + bool sync); + +static inline void iwl_dbg_tlv_time_point(struct iwl_fw_runtime *fwrt, + enum iwl_fw_ini_time_point tp_id, + union iwl_dbg_tlv_tp_data *tp_data) +{ + _iwl_dbg_tlv_time_point(fwrt, tp_id, tp_data, false); +} + +static inline void iwl_dbg_tlv_time_point_sync(struct iwl_fw_runtime *fwrt, + enum iwl_fw_ini_time_point tp_id, + union iwl_dbg_tlv_tp_data *tp_data) +{ + _iwl_dbg_tlv_time_point(fwrt, tp_id, tp_data, true); +} + +void iwl_dbg_tlv_del_timers(struct iwl_trans *trans); + +#endif /* __iwl_dbg_tlv_h__*/ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-debug.c b/drivers/net/wireless/intel/iwlwifi/iwl-debug.c new file mode 100644 index 000000000..3a3c13a41 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-debug.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2011, 2021-2022 Intel Corporation + */ +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/export.h> +#include "iwl-drv.h" +#include "iwl-debug.h" +#include "iwl-devtrace.h" + +#define __iwl_fn(fn) \ +void __iwl_ ##fn(struct device *dev, const char *fmt, ...) \ +{ \ + struct va_format vaf = { \ + .fmt = fmt, \ + }; \ + va_list args; \ + \ + va_start(args, fmt); \ + vaf.va = &args; \ + dev_ ##fn(dev, "%pV", &vaf); \ + trace_iwlwifi_ ##fn(&vaf); \ + va_end(args); \ +} + +__iwl_fn(warn) +IWL_EXPORT_SYMBOL(__iwl_warn); +__iwl_fn(info) +IWL_EXPORT_SYMBOL(__iwl_info); +__iwl_fn(crit) +IWL_EXPORT_SYMBOL(__iwl_crit); + +void __iwl_err(struct device *dev, enum iwl_err_mode mode, const char *fmt, ...) +{ + struct va_format vaf = { + .fmt = fmt, + }; + va_list args, args2; + + va_start(args, fmt); + switch (mode) { + case IWL_ERR_MODE_RATELIMIT: + if (net_ratelimit()) + break; + fallthrough; + case IWL_ERR_MODE_REGULAR: + case IWL_ERR_MODE_RFKILL: + va_copy(args2, args); + vaf.va = &args2; + if (mode == IWL_ERR_MODE_RFKILL) + dev_err(dev, "(RFKILL) %pV", &vaf); + else + dev_err(dev, "%pV", &vaf); + va_end(args2); + break; + default: + break; + } + vaf.va = &args; + trace_iwlwifi_err(&vaf); + va_end(args); +} +IWL_EXPORT_SYMBOL(__iwl_err); + +#if defined(CONFIG_IWLWIFI_DEBUG) || defined(CONFIG_IWLWIFI_DEVICE_TRACING) +void __iwl_dbg(struct device *dev, + u32 level, bool limit, const char *function, + const char *fmt, ...) +{ + struct va_format vaf = { + .fmt = fmt, + }; + va_list args; + + va_start(args, fmt); + vaf.va = &args; +#ifdef CONFIG_IWLWIFI_DEBUG + if (iwl_have_debug_level(level) && + (!limit || net_ratelimit())) + dev_printk(KERN_DEBUG, dev, "%s %pV", function, &vaf); +#endif + trace_iwlwifi_dbg(level, function, &vaf); + va_end(args); +} +IWL_EXPORT_SYMBOL(__iwl_dbg); +#endif diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-debug.h b/drivers/net/wireless/intel/iwlwifi/iwl-debug.h new file mode 100644 index 000000000..1b9f16a31 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-debug.h @@ -0,0 +1,219 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + * + * Copyright(c) 2003 - 2014 Intel Corporation. All rights reserved. + * Copyright(c) 2018 - 2021 Intel Corporation + * + * Portions of this file are derived from the ipw3945 project. + *****************************************************************************/ + +#ifndef __iwl_debug_h__ +#define __iwl_debug_h__ + +#include "iwl-modparams.h" + + +static inline bool iwl_have_debug_level(u32 level) +{ +#ifdef CONFIG_IWLWIFI_DEBUG + return iwlwifi_mod_params.debug_level & level; +#else + return false; +#endif +} + +enum iwl_err_mode { + IWL_ERR_MODE_REGULAR, + IWL_ERR_MODE_RFKILL, + IWL_ERR_MODE_TRACE_ONLY, + IWL_ERR_MODE_RATELIMIT, +}; + +struct device; +void __iwl_err(struct device *dev, enum iwl_err_mode mode, const char *fmt, ...) + __printf(3, 4); +void __iwl_warn(struct device *dev, const char *fmt, ...) __printf(2, 3); +void __iwl_info(struct device *dev, const char *fmt, ...) __printf(2, 3); +void __iwl_crit(struct device *dev, const char *fmt, ...) __printf(2, 3); + +/* not all compilers can evaluate strlen() at compile time, so use sizeof() */ +#define CHECK_FOR_NEWLINE(f) BUILD_BUG_ON(f[sizeof(f) - 2] != '\n') + +/* No matter what is m (priv, bus, trans), this will work */ +#define __IWL_ERR_DEV(d, mode, f, a...) \ + do { \ + CHECK_FOR_NEWLINE(f); \ + __iwl_err((d), mode, f, ## a); \ + } while (0) +#define IWL_ERR_DEV(d, f, a...) \ + __IWL_ERR_DEV(d, IWL_ERR_MODE_REGULAR, f, ## a) +#define IWL_ERR(m, f, a...) \ + IWL_ERR_DEV((m)->dev, f, ## a) +#define IWL_ERR_LIMIT(m, f, a...) \ + __IWL_ERR_DEV((m)->dev, IWL_ERR_MODE_RATELIMIT, f, ## a) +#define IWL_WARN(m, f, a...) \ + do { \ + CHECK_FOR_NEWLINE(f); \ + __iwl_warn((m)->dev, f, ## a); \ + } while (0) +#define IWL_INFO(m, f, a...) \ + do { \ + CHECK_FOR_NEWLINE(f); \ + __iwl_info((m)->dev, f, ## a); \ + } while (0) +#define IWL_CRIT(m, f, a...) \ + do { \ + CHECK_FOR_NEWLINE(f); \ + __iwl_crit((m)->dev, f, ## a); \ + } while (0) + +#if defined(CONFIG_IWLWIFI_DEBUG) || defined(CONFIG_IWLWIFI_DEVICE_TRACING) +void __iwl_dbg(struct device *dev, + u32 level, bool limit, const char *function, + const char *fmt, ...) __printf(5, 6); +#else +__printf(5, 6) static inline void +__iwl_dbg(struct device *dev, + u32 level, bool limit, const char *function, + const char *fmt, ...) +{} +#endif + +#define iwl_print_hex_error(m, p, len) \ +do { \ + print_hex_dump(KERN_ERR, "iwl data: ", \ + DUMP_PREFIX_OFFSET, 16, 1, p, len, 1); \ +} while (0) + +#define __IWL_DEBUG_DEV(dev, level, limit, fmt, args...) \ + do { \ + CHECK_FOR_NEWLINE(fmt); \ + __iwl_dbg(dev, level, limit, __func__, fmt, ##args); \ + } while (0) +#define IWL_DEBUG(m, level, fmt, args...) \ + __IWL_DEBUG_DEV((m)->dev, level, false, fmt, ##args) +#define IWL_DEBUG_DEV(dev, level, fmt, args...) \ + __IWL_DEBUG_DEV(dev, level, false, fmt, ##args) +#define IWL_DEBUG_LIMIT(m, level, fmt, args...) \ + __IWL_DEBUG_DEV((m)->dev, level, true, fmt, ##args) + +#ifdef CONFIG_IWLWIFI_DEBUG +#define iwl_print_hex_dump(m, level, p, len) \ +do { \ + if (iwl_have_debug_level(level)) \ + print_hex_dump(KERN_DEBUG, "iwl data: ", \ + DUMP_PREFIX_OFFSET, 16, 1, p, len, 1); \ +} while (0) +#else +#define iwl_print_hex_dump(m, level, p, len) +#endif /* CONFIG_IWLWIFI_DEBUG */ + +/* + * To use the debug system: + * + * If you are defining a new debug classification, simply add it to the #define + * list here in the form of + * + * #define IWL_DL_xxxx VALUE + * + * where xxxx should be the name of the classification (for example, WEP). + * + * You then need to either add a IWL_xxxx_DEBUG() macro definition for your + * classification, or use IWL_DEBUG(IWL_DL_xxxx, ...) whenever you want + * to send output to that classification. + * + * The active debug levels can be accessed via files + * + * /sys/module/iwlwifi/parameters/debug + * when CONFIG_IWLWIFI_DEBUG=y. + * + * /sys/kernel/debug/phy0/iwlwifi/debug/debug_level + * when CONFIG_IWLWIFI_DEBUGFS=y. + * + */ + +/* 0x0000000F - 0x00000001 */ +#define IWL_DL_INFO 0x00000001 +#define IWL_DL_MAC80211 0x00000002 +#define IWL_DL_HCMD 0x00000004 +#define IWL_DL_TDLS 0x00000008 +/* 0x000000F0 - 0x00000010 */ +#define IWL_DL_QUOTA 0x00000010 +#define IWL_DL_TE 0x00000020 +#define IWL_DL_EEPROM 0x00000040 +#define IWL_DL_RADIO 0x00000080 +/* 0x00000F00 - 0x00000100 */ +#define IWL_DL_POWER 0x00000100 +#define IWL_DL_TEMP 0x00000200 +#define IWL_DL_WOWLAN 0x00000400 +#define IWL_DL_SCAN 0x00000800 +/* 0x0000F000 - 0x00001000 */ +#define IWL_DL_ASSOC 0x00001000 +#define IWL_DL_DROP 0x00002000 +#define IWL_DL_LAR 0x00004000 +#define IWL_DL_COEX 0x00008000 +/* 0x000F0000 - 0x00010000 */ +#define IWL_DL_FW 0x00010000 +#define IWL_DL_RF_KILL 0x00020000 +#define IWL_DL_TPT 0x00040000 +/* 0x00F00000 - 0x00100000 */ +#define IWL_DL_RATE 0x00100000 +#define IWL_DL_CALIB 0x00200000 +#define IWL_DL_WEP 0x00400000 +#define IWL_DL_TX 0x00800000 +/* 0x0F000000 - 0x01000000 */ +#define IWL_DL_RX 0x01000000 +#define IWL_DL_ISR 0x02000000 +#define IWL_DL_HT 0x04000000 +#define IWL_DL_EXTERNAL 0x08000000 +/* 0xF0000000 - 0x10000000 */ +#define IWL_DL_11H 0x10000000 +#define IWL_DL_STATS 0x20000000 +#define IWL_DL_TX_REPLY 0x40000000 +#define IWL_DL_TX_QUEUES 0x80000000 + +#define IWL_DEBUG_INFO(p, f, a...) IWL_DEBUG(p, IWL_DL_INFO, f, ## a) +#define IWL_DEBUG_TDLS(p, f, a...) IWL_DEBUG(p, IWL_DL_TDLS, f, ## a) +#define IWL_DEBUG_MAC80211(p, f, a...) IWL_DEBUG(p, IWL_DL_MAC80211, f, ## a) +#define IWL_DEBUG_EXTERNAL(p, f, a...) IWL_DEBUG(p, IWL_DL_EXTERNAL, f, ## a) +#define IWL_DEBUG_TEMP(p, f, a...) IWL_DEBUG(p, IWL_DL_TEMP, f, ## a) +#define IWL_DEBUG_SCAN(p, f, a...) IWL_DEBUG(p, IWL_DL_SCAN, f, ## a) +#define IWL_DEBUG_RX(p, f, a...) IWL_DEBUG(p, IWL_DL_RX, f, ## a) +#define IWL_DEBUG_TX(p, f, a...) IWL_DEBUG(p, IWL_DL_TX, f, ## a) +#define IWL_DEBUG_ISR(p, f, a...) IWL_DEBUG(p, IWL_DL_ISR, f, ## a) +#define IWL_DEBUG_WEP(p, f, a...) IWL_DEBUG(p, IWL_DL_WEP, f, ## a) +#define IWL_DEBUG_HC(p, f, a...) IWL_DEBUG(p, IWL_DL_HCMD, f, ## a) +#define IWL_DEBUG_QUOTA(p, f, a...) IWL_DEBUG(p, IWL_DL_QUOTA, f, ## a) +#define IWL_DEBUG_TE(p, f, a...) IWL_DEBUG(p, IWL_DL_TE, f, ## a) +#define IWL_DEBUG_EEPROM(d, f, a...) IWL_DEBUG_DEV(d, IWL_DL_EEPROM, f, ## a) +#define IWL_DEBUG_CALIB(p, f, a...) IWL_DEBUG(p, IWL_DL_CALIB, f, ## a) +#define IWL_DEBUG_FW(p, f, a...) IWL_DEBUG(p, IWL_DL_FW, f, ## a) +#define IWL_DEBUG_RF_KILL(p, f, a...) IWL_DEBUG(p, IWL_DL_RF_KILL, f, ## a) +#define IWL_DEBUG_DROP(p, f, a...) IWL_DEBUG(p, IWL_DL_DROP, f, ## a) +#define IWL_DEBUG_DROP_LIMIT(p, f, a...) \ + IWL_DEBUG_LIMIT(p, IWL_DL_DROP, f, ## a) +#define IWL_DEBUG_COEX(p, f, a...) IWL_DEBUG(p, IWL_DL_COEX, f, ## a) +#define IWL_DEBUG_RATE(p, f, a...) IWL_DEBUG(p, IWL_DL_RATE, f, ## a) +#define IWL_DEBUG_RATE_LIMIT(p, f, a...) \ + IWL_DEBUG_LIMIT(p, IWL_DL_RATE, f, ## a) +#define IWL_DEBUG_ASSOC(p, f, a...) \ + IWL_DEBUG(p, IWL_DL_ASSOC | IWL_DL_INFO, f, ## a) +#define IWL_DEBUG_ASSOC_LIMIT(p, f, a...) \ + IWL_DEBUG_LIMIT(p, IWL_DL_ASSOC | IWL_DL_INFO, f, ## a) +#define IWL_DEBUG_HT(p, f, a...) IWL_DEBUG(p, IWL_DL_HT, f, ## a) +#define IWL_DEBUG_STATS(p, f, a...) IWL_DEBUG(p, IWL_DL_STATS, f, ## a) +#define IWL_DEBUG_STATS_LIMIT(p, f, a...) \ + IWL_DEBUG_LIMIT(p, IWL_DL_STATS, f, ## a) +#define IWL_DEBUG_TX_REPLY(p, f, a...) IWL_DEBUG(p, IWL_DL_TX_REPLY, f, ## a) +#define IWL_DEBUG_TX_QUEUES(p, f, a...) IWL_DEBUG(p, IWL_DL_TX_QUEUES, f, ## a) +#define IWL_DEBUG_RADIO(p, f, a...) IWL_DEBUG(p, IWL_DL_RADIO, f, ## a) +#define IWL_DEBUG_DEV_RADIO(p, f, a...) IWL_DEBUG_DEV(p, IWL_DL_RADIO, f, ## a) +#define IWL_DEBUG_POWER(p, f, a...) IWL_DEBUG(p, IWL_DL_POWER, f, ## a) +#define IWL_DEBUG_11H(p, f, a...) IWL_DEBUG(p, IWL_DL_11H, f, ## a) +#define IWL_DEBUG_TPT(p, f, a...) IWL_DEBUG(p, IWL_DL_TPT, f, ## a) +#define IWL_DEBUG_WOWLAN(p, f, a...) IWL_DEBUG(p, IWL_DL_WOWLAN, f, ## a) +#define IWL_DEBUG_LAR(p, f, a...) IWL_DEBUG(p, IWL_DL_LAR, f, ## a) +#define IWL_DEBUG_FW_INFO(p, f, a...) \ + IWL_DEBUG(p, IWL_DL_INFO | IWL_DL_FW, f, ## a) + +#endif diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-devtrace-data.h b/drivers/net/wireless/intel/iwlwifi/iwl-devtrace-data.h new file mode 100644 index 000000000..347fd95c4 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-devtrace-data.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + * + * Copyright(c) 2009 - 2014 Intel Corporation. All rights reserved. + * Copyright(c) 2015 Intel Deutschland GmbH + * Copyright(c) 2018 - 2019 Intel Corporation + *****************************************************************************/ + +#if !defined(__IWLWIFI_DEVICE_TRACE_DATA) || defined(TRACE_HEADER_MULTI_READ) +#define __IWLWIFI_DEVICE_TRACE_DATA + +#include <linux/tracepoint.h> + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM iwlwifi_data + +TRACE_EVENT(iwlwifi_dev_tx_tb, + TP_PROTO(const struct device *dev, struct sk_buff *skb, + u8 *data_src, dma_addr_t phys, size_t data_len), + TP_ARGS(dev, skb, data_src, phys, data_len), + TP_STRUCT__entry( + DEV_ENTRY + __field(u64, phys) + + __dynamic_array(u8, data, + iwl_trace_data(skb) ? data_len : 0) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->phys = phys; + if (iwl_trace_data(skb)) + memcpy(__get_dynamic_array(data), data_src, data_len); + ), + TP_printk("[%s] TX frame data", __get_str(dev)) +); + +TRACE_EVENT(iwlwifi_dev_rx_data, + TP_PROTO(const struct device *dev, + const struct iwl_trans *trans, + void *rxbuf, size_t len), + TP_ARGS(dev, trans, rxbuf, len), + TP_STRUCT__entry( + DEV_ENTRY + __dynamic_array(u8, data, + len - iwl_rx_trace_len(trans, rxbuf, len, NULL)) + ), + TP_fast_assign( + size_t offs = iwl_rx_trace_len(trans, rxbuf, len, NULL); + DEV_ASSIGN; + if (offs < len) + memcpy(__get_dynamic_array(data), + ((u8 *)rxbuf) + offs, len - offs); + ), + TP_printk("[%s] RX frame data", __get_str(dev)) +); +#endif /* __IWLWIFI_DEVICE_TRACE_DATA */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE iwl-devtrace-data +#include <trace/define_trace.h> diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-devtrace-io.h b/drivers/net/wireless/intel/iwlwifi/iwl-devtrace-io.h new file mode 100644 index 000000000..0af9d8362 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-devtrace-io.h @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + * + * Copyright(c) 2009 - 2014 Intel Corporation. All rights reserved. + * Copyright(c) 2016-2017 Intel Deutschland GmbH + *****************************************************************************/ + +#if !defined(__IWLWIFI_DEVICE_TRACE_IO) || defined(TRACE_HEADER_MULTI_READ) +#define __IWLWIFI_DEVICE_TRACE_IO + +#include <linux/tracepoint.h> +#include <linux/pci.h> + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM iwlwifi_io + +TRACE_EVENT(iwlwifi_dev_ioread32, + TP_PROTO(const struct device *dev, u32 offs, u32 val), + TP_ARGS(dev, offs, val), + TP_STRUCT__entry( + DEV_ENTRY + __field(u32, offs) + __field(u32, val) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->offs = offs; + __entry->val = val; + ), + TP_printk("[%s] read io[%#x] = %#x", + __get_str(dev), __entry->offs, __entry->val) +); + +TRACE_EVENT(iwlwifi_dev_iowrite8, + TP_PROTO(const struct device *dev, u32 offs, u8 val), + TP_ARGS(dev, offs, val), + TP_STRUCT__entry( + DEV_ENTRY + __field(u32, offs) + __field(u8, val) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->offs = offs; + __entry->val = val; + ), + TP_printk("[%s] write io[%#x] = %#x)", + __get_str(dev), __entry->offs, __entry->val) +); + +TRACE_EVENT(iwlwifi_dev_iowrite32, + TP_PROTO(const struct device *dev, u32 offs, u32 val), + TP_ARGS(dev, offs, val), + TP_STRUCT__entry( + DEV_ENTRY + __field(u32, offs) + __field(u32, val) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->offs = offs; + __entry->val = val; + ), + TP_printk("[%s] write io[%#x] = %#x)", + __get_str(dev), __entry->offs, __entry->val) +); + +TRACE_EVENT(iwlwifi_dev_iowrite64, + TP_PROTO(const struct device *dev, u64 offs, u64 val), + TP_ARGS(dev, offs, val), + TP_STRUCT__entry( + DEV_ENTRY + __field(u64, offs) + __field(u64, val) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->offs = offs; + __entry->val = val; + ), + TP_printk("[%s] write io[%llu] = %llu)", + __get_str(dev), __entry->offs, __entry->val) +); + +TRACE_EVENT(iwlwifi_dev_iowrite_prph32, + TP_PROTO(const struct device *dev, u32 offs, u32 val), + TP_ARGS(dev, offs, val), + TP_STRUCT__entry( + DEV_ENTRY + __field(u32, offs) + __field(u32, val) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->offs = offs; + __entry->val = val; + ), + TP_printk("[%s] write PRPH[%#x] = %#x)", + __get_str(dev), __entry->offs, __entry->val) +); + +TRACE_EVENT(iwlwifi_dev_iowrite_prph64, + TP_PROTO(const struct device *dev, u64 offs, u64 val), + TP_ARGS(dev, offs, val), + TP_STRUCT__entry( + DEV_ENTRY + __field(u64, offs) + __field(u64, val) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->offs = offs; + __entry->val = val; + ), + TP_printk("[%s] write PRPH[%llu] = %llu)", + __get_str(dev), __entry->offs, __entry->val) +); + +TRACE_EVENT(iwlwifi_dev_ioread_prph32, + TP_PROTO(const struct device *dev, u32 offs, u32 val), + TP_ARGS(dev, offs, val), + TP_STRUCT__entry( + DEV_ENTRY + __field(u32, offs) + __field(u32, val) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->offs = offs; + __entry->val = val; + ), + TP_printk("[%s] read PRPH[%#x] = %#x", + __get_str(dev), __entry->offs, __entry->val) +); + +TRACE_EVENT(iwlwifi_dev_irq, + TP_PROTO(const struct device *dev), + TP_ARGS(dev), + TP_STRUCT__entry( + DEV_ENTRY + ), + TP_fast_assign( + DEV_ASSIGN; + ), + /* TP_printk("") doesn't compile */ + TP_printk("%d", 0) +); + +TRACE_EVENT(iwlwifi_dev_irq_msix, + TP_PROTO(const struct device *dev, struct msix_entry *msix_entry, + bool defirq, u32 inta_fh, u32 inta_hw), + TP_ARGS(dev, msix_entry, defirq, inta_fh, inta_hw), + TP_STRUCT__entry( + DEV_ENTRY + __field(u32, entry) + __field(u8, defirq) + __field(u32, inta_fh) + __field(u32, inta_hw) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->entry = msix_entry->entry; + __entry->defirq = defirq; + __entry->inta_fh = inta_fh; + __entry->inta_hw = inta_hw; + ), + TP_printk("entry:%d defirq:%d fh:0x%x, hw:0x%x", + __entry->entry, __entry->defirq, + __entry->inta_fh, __entry->inta_hw) +); + +TRACE_EVENT(iwlwifi_dev_ict_read, + TP_PROTO(const struct device *dev, u32 index, u32 value), + TP_ARGS(dev, index, value), + TP_STRUCT__entry( + DEV_ENTRY + __field(u32, index) + __field(u32, value) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->index = index; + __entry->value = value; + ), + TP_printk("[%s] read ict[%d] = %#.8x", + __get_str(dev), __entry->index, __entry->value) +); +#endif /* __IWLWIFI_DEVICE_TRACE_IO */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE iwl-devtrace-io +#include <trace/define_trace.h> diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-devtrace-iwlwifi.h b/drivers/net/wireless/intel/iwlwifi/iwl-devtrace-iwlwifi.h new file mode 100644 index 000000000..46ed723f1 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-devtrace-iwlwifi.h @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + * + * Copyright(c) 2009 - 2014 Intel Corporation. All rights reserved. + * Copyright(c) 2015 Intel Mobile Communications GmbH + * Copyright(c) 2016 - 2017 Intel Deutschland GmbH + * Copyright(c) 2018 Intel Corporation + *****************************************************************************/ + +#if !defined(__IWLWIFI_DEVICE_TRACE_IWLWIFI) || defined(TRACE_HEADER_MULTI_READ) +#define __IWLWIFI_DEVICE_TRACE_IWLWIFI + +#include <linux/tracepoint.h> + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM iwlwifi + +TRACE_EVENT(iwlwifi_dev_hcmd, + TP_PROTO(const struct device *dev, + struct iwl_host_cmd *cmd, u16 total_size, + struct iwl_cmd_header_wide *hdr), + TP_ARGS(dev, cmd, total_size, hdr), + TP_STRUCT__entry( + DEV_ENTRY + __dynamic_array(u8, hcmd, total_size) + __field(u32, flags) + ), + TP_fast_assign( + int i, offset = sizeof(struct iwl_cmd_header); + + if (hdr->group_id) + offset = sizeof(struct iwl_cmd_header_wide); + + DEV_ASSIGN; + __entry->flags = cmd->flags; + memcpy(__get_dynamic_array(hcmd), hdr, offset); + + for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) { + if (!cmd->len[i]) + continue; + memcpy((u8 *)__get_dynamic_array(hcmd) + offset, + cmd->data[i], cmd->len[i]); + offset += cmd->len[i]; + } + ), + TP_printk("[%s] hcmd %#.2x.%#.2x (%ssync)", + __get_str(dev), ((u8 *)__get_dynamic_array(hcmd))[1], + ((u8 *)__get_dynamic_array(hcmd))[0], + __entry->flags & CMD_ASYNC ? "a" : "") +); + +TRACE_EVENT(iwlwifi_dev_rx, + TP_PROTO(const struct device *dev, const struct iwl_trans *trans, + struct iwl_rx_packet *pkt, size_t len), + TP_ARGS(dev, trans, pkt, len), + TP_STRUCT__entry( + DEV_ENTRY + __field(u16, cmd) + __field(u8, hdr_offset) + __dynamic_array(u8, rxbuf, + iwl_rx_trace_len(trans, pkt, len, NULL)) + ), + TP_fast_assign( + size_t hdr_offset = 0; + + DEV_ASSIGN; + __entry->cmd = WIDE_ID(pkt->hdr.group_id, pkt->hdr.cmd); + memcpy(__get_dynamic_array(rxbuf), pkt, + iwl_rx_trace_len(trans, pkt, len, &hdr_offset)); + __entry->hdr_offset = hdr_offset; + ), + TP_printk("[%s] RX cmd %#.2x", + __get_str(dev), __entry->cmd) +); + +TRACE_EVENT(iwlwifi_dev_tx, + TP_PROTO(const struct device *dev, struct sk_buff *skb, + void *tfd, size_t tfdlen, + void *buf0, size_t buf0_len, + int hdr_len), + TP_ARGS(dev, skb, tfd, tfdlen, buf0, buf0_len, hdr_len), + TP_STRUCT__entry( + DEV_ENTRY + __field(void *, skbaddr) + __field(size_t, framelen) + __dynamic_array(u8, tfd, tfdlen) + + /* + * Do not insert between or below these items, + * we want to keep the frame together (except + * for the possible padding). + */ + __dynamic_array(u8, buf0, buf0_len) + __dynamic_array(u8, buf1, hdr_len > 0 && iwl_trace_data(skb) ? + 0 : skb->len - hdr_len) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->skbaddr = skb; + __entry->framelen = buf0_len; + if (hdr_len > 0) + __entry->framelen += skb->len - hdr_len; + memcpy(__get_dynamic_array(tfd), tfd, tfdlen); + memcpy(__get_dynamic_array(buf0), buf0, buf0_len); + if (hdr_len > 0 && !iwl_trace_data(skb)) + skb_copy_bits(skb, hdr_len, + __get_dynamic_array(buf1), + skb->len - hdr_len); + ), + TP_printk("[%s] TX %.2x (%zu bytes) skbaddr=%p", + __get_str(dev), ((u8 *)__get_dynamic_array(buf0))[0], + __entry->framelen, __entry->skbaddr) +); + +TRACE_EVENT(iwlwifi_dev_ucode_event, + TP_PROTO(const struct device *dev, u32 time, u32 data, u32 ev), + TP_ARGS(dev, time, data, ev), + TP_STRUCT__entry( + DEV_ENTRY + + __field(u32, time) + __field(u32, data) + __field(u32, ev) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->time = time; + __entry->data = data; + __entry->ev = ev; + ), + TP_printk("[%s] EVT_LOGT:%010u:0x%08x:%04u", + __get_str(dev), __entry->time, __entry->data, __entry->ev) +); +#endif /* __IWLWIFI_DEVICE_TRACE_IWLWIFI */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE iwl-devtrace-iwlwifi +#include <trace/define_trace.h> diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-devtrace-msg.h b/drivers/net/wireless/intel/iwlwifi/iwl-devtrace-msg.h new file mode 100644 index 000000000..1d6c292cf --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-devtrace-msg.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + * + * Copyright(c) 2009 - 2014 Intel Corporation. All rights reserved. + *****************************************************************************/ + +#if !defined(__IWLWIFI_DEVICE_TRACE_MSG) || defined(TRACE_HEADER_MULTI_READ) +#define __IWLWIFI_DEVICE_TRACE_MSG + +#include <linux/tracepoint.h> + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM iwlwifi_msg + +#define MAX_MSG_LEN 110 + +DECLARE_EVENT_CLASS(iwlwifi_msg_event, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf), + TP_STRUCT__entry( + __vstring(msg, vaf->fmt, vaf->va) + ), + TP_fast_assign( + __assign_vstr(msg, vaf->fmt, vaf->va); + ), + TP_printk("%s", __get_str(msg)) +); + +DEFINE_EVENT(iwlwifi_msg_event, iwlwifi_err, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(iwlwifi_msg_event, iwlwifi_warn, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(iwlwifi_msg_event, iwlwifi_info, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(iwlwifi_msg_event, iwlwifi_crit, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +TRACE_EVENT(iwlwifi_dbg, + TP_PROTO(u32 level, const char *function, + struct va_format *vaf), + TP_ARGS(level, function, vaf), + TP_STRUCT__entry( + __field(u32, level) + __string(function, function) + __vstring(msg, vaf->fmt, vaf->va) + ), + TP_fast_assign( + __entry->level = level; + __assign_str(function, function); + __assign_vstr(msg, vaf->fmt, vaf->va); + ), + TP_printk("%s", __get_str(msg)) +); +#endif /* __IWLWIFI_DEVICE_TRACE_MSG */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE iwl-devtrace-msg +#include <trace/define_trace.h> diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-devtrace-ucode.h b/drivers/net/wireless/intel/iwlwifi/iwl-devtrace-ucode.h new file mode 100644 index 000000000..3ec0205ac --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-devtrace-ucode.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + * + * Copyright(c) 2009 - 2014 Intel Corporation. All rights reserved. + *****************************************************************************/ + +#if !defined(__IWLWIFI_DEVICE_TRACE_UCODE) || defined(TRACE_HEADER_MULTI_READ) +#define __IWLWIFI_DEVICE_TRACE_UCODE + +#include <linux/tracepoint.h> + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM iwlwifi_ucode + +TRACE_EVENT(iwlwifi_dev_ucode_cont_event, + TP_PROTO(const struct device *dev, u32 time, u32 data, u32 ev), + TP_ARGS(dev, time, data, ev), + TP_STRUCT__entry( + DEV_ENTRY + + __field(u32, time) + __field(u32, data) + __field(u32, ev) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->time = time; + __entry->data = data; + __entry->ev = ev; + ), + TP_printk("[%s] EVT_LOGT:%010u:0x%08x:%04u", + __get_str(dev), __entry->time, __entry->data, __entry->ev) +); + +TRACE_EVENT(iwlwifi_dev_ucode_wrap_event, + TP_PROTO(const struct device *dev, u32 wraps, u32 n_entry, u32 p_entry), + TP_ARGS(dev, wraps, n_entry, p_entry), + TP_STRUCT__entry( + DEV_ENTRY + + __field(u32, wraps) + __field(u32, n_entry) + __field(u32, p_entry) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->wraps = wraps; + __entry->n_entry = n_entry; + __entry->p_entry = p_entry; + ), + TP_printk("[%s] wraps=#%02d n=0x%X p=0x%X", + __get_str(dev), __entry->wraps, __entry->n_entry, + __entry->p_entry) +); +#endif /* __IWLWIFI_DEVICE_TRACE_UCODE */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE iwl-devtrace-ucode +#include <trace/define_trace.h> diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-devtrace.c b/drivers/net/wireless/intel/iwlwifi/iwl-devtrace.c new file mode 100644 index 000000000..999b7c652 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-devtrace.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2009 - 2014 Intel Corporation. All rights reserved. + * Copyright (C) 2018 Intel Corporation + *****************************************************************************/ + +#include <linux/module.h> + +/* sparse doesn't like tracepoint macros */ +#ifndef __CHECKER__ +#include "iwl-trans.h" + +#define CREATE_TRACE_POINTS +#include "iwl-devtrace.h" + +EXPORT_TRACEPOINT_SYMBOL(iwlwifi_dev_ucode_event); +EXPORT_TRACEPOINT_SYMBOL(iwlwifi_dev_ucode_cont_event); +EXPORT_TRACEPOINT_SYMBOL(iwlwifi_dev_ucode_wrap_event); +#endif diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-devtrace.h b/drivers/net/wireless/intel/iwlwifi/iwl-devtrace.h new file mode 100644 index 000000000..1455b5783 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-devtrace.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + * + * Copyright(c) 2009 - 2014 Intel Corporation. All rights reserved. + * Copyright(C) 2016 Intel Deutschland GmbH + * Copyright(c) 2018 Intel Corporation + *****************************************************************************/ + +#ifndef __IWLWIFI_DEVICE_TRACE +#include <linux/skbuff.h> +#include <linux/ieee80211.h> +#include <net/cfg80211.h> +#include "iwl-trans.h" +#if !defined(__IWLWIFI_DEVICE_TRACE) +static inline bool iwl_trace_data(struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (void *)skb->data; + __le16 fc = hdr->frame_control; + int offs = 24; /* start with normal header length */ + + if (!ieee80211_is_data(fc)) + return false; + + /* Try to determine if the frame is EAPOL. This might have false + * positives (if there's no RFC 1042 header and we compare to some + * payload instead) but since we're only doing tracing that's not + * a problem. + */ + + if (ieee80211_has_a4(fc)) + offs += 6; + if (ieee80211_is_data_qos(fc)) + offs += 2; + /* don't account for crypto - these are unencrypted */ + + /* also account for the RFC 1042 header, of course */ + offs += 6; + + return skb->len <= offs + 2 || + *(__be16 *)(skb->data + offs) != cpu_to_be16(ETH_P_PAE); +} + +static inline size_t iwl_rx_trace_len(const struct iwl_trans *trans, + void *rxbuf, size_t len, + size_t *out_hdr_offset) +{ + struct iwl_cmd_header *cmd = (void *)((u8 *)rxbuf + sizeof(__le32)); + struct ieee80211_hdr *hdr = NULL; + size_t hdr_offset; + + if (cmd->cmd != trans->rx_mpdu_cmd) + return len; + + hdr_offset = sizeof(struct iwl_cmd_header) + + trans->rx_mpdu_cmd_hdr_size; + + if (out_hdr_offset) + *out_hdr_offset = hdr_offset; + + hdr = (void *)((u8 *)cmd + hdr_offset); + if (!ieee80211_is_data(hdr->frame_control)) + return len; + /* maybe try to identify EAPOL frames? */ + return sizeof(__le32) + sizeof(*cmd) + trans->rx_mpdu_cmd_hdr_size + + ieee80211_hdrlen(hdr->frame_control); +} +#endif + +#define __IWLWIFI_DEVICE_TRACE + +#include <linux/tracepoint.h> +#include <linux/device.h> + + +#if !defined(CONFIG_IWLWIFI_DEVICE_TRACING) || defined(__CHECKER__) +#undef TRACE_EVENT +#define TRACE_EVENT(name, proto, ...) \ +static inline void trace_ ## name(proto) {} +#undef DECLARE_EVENT_CLASS +#define DECLARE_EVENT_CLASS(...) +#undef DEFINE_EVENT +#define DEFINE_EVENT(evt_class, name, proto, ...) \ +static inline void trace_ ## name(proto) {} +#endif + +#define DEV_ENTRY __string(dev, dev_name(dev)) +#define DEV_ASSIGN __assign_str(dev, dev_name(dev)) + +#include "iwl-devtrace-io.h" +#include "iwl-devtrace-ucode.h" +#include "iwl-devtrace-msg.h" +#include "iwl-devtrace-data.h" +#include "iwl-devtrace-iwlwifi.h" + +#endif /* __IWLWIFI_DEVICE_TRACE */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c new file mode 100644 index 000000000..5eba1a355 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c @@ -0,0 +1,1956 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include <linux/completion.h> +#include <linux/dma-mapping.h> +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/vmalloc.h> + +#include "iwl-drv.h" +#include "iwl-csr.h" +#include "iwl-debug.h" +#include "iwl-trans.h" +#include "iwl-op-mode.h" +#include "iwl-agn-hw.h" +#include "fw/img.h" +#include "iwl-dbg-tlv.h" +#include "iwl-config.h" +#include "iwl-modparams.h" +#include "fw/api/alive.h" +#include "fw/api/mac.h" + +/****************************************************************************** + * + * module boiler plate + * + ******************************************************************************/ + +#define DRV_DESCRIPTION "Intel(R) Wireless WiFi driver for Linux" +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_LICENSE("GPL"); + +#ifdef CONFIG_IWLWIFI_DEBUGFS +static struct dentry *iwl_dbgfs_root; +#endif + +/** + * struct iwl_drv - drv common data + * @list: list of drv structures using this opmode + * @fw: the iwl_fw structure + * @op_mode: the running op_mode + * @trans: transport layer + * @dev: for debug prints only + * @fw_index: firmware revision to try loading + * @firmware_name: composite filename of ucode file to load + * @request_firmware_complete: the firmware has been obtained from user space + * @dbgfs_drv: debugfs root directory entry + * @dbgfs_trans: debugfs transport directory entry + * @dbgfs_op_mode: debugfs op_mode directory entry + */ +struct iwl_drv { + struct list_head list; + struct iwl_fw fw; + + struct iwl_op_mode *op_mode; + struct iwl_trans *trans; + struct device *dev; + + int fw_index; /* firmware we're trying to load */ + char firmware_name[64]; /* name of firmware file to load */ + + struct completion request_firmware_complete; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + struct dentry *dbgfs_drv; + struct dentry *dbgfs_trans; + struct dentry *dbgfs_op_mode; +#endif +}; + +enum { + DVM_OP_MODE, + MVM_OP_MODE, +}; + +/* Protects the table contents, i.e. the ops pointer & drv list */ +static DEFINE_MUTEX(iwlwifi_opmode_table_mtx); +static struct iwlwifi_opmode_table { + const char *name; /* name: iwldvm, iwlmvm, etc */ + const struct iwl_op_mode_ops *ops; /* pointer to op_mode ops */ + struct list_head drv; /* list of devices using this op_mode */ +} iwlwifi_opmode_table[] = { /* ops set when driver is initialized */ + [DVM_OP_MODE] = { .name = "iwldvm", .ops = NULL }, + [MVM_OP_MODE] = { .name = "iwlmvm", .ops = NULL }, +}; + +#define IWL_DEFAULT_SCAN_CHANNELS 40 + +/* + * struct fw_sec: Just for the image parsing process. + * For the fw storage we are using struct fw_desc. + */ +struct fw_sec { + const void *data; /* the sec data */ + size_t size; /* section size */ + u32 offset; /* offset of writing in the device */ +}; + +static void iwl_free_fw_desc(struct iwl_drv *drv, struct fw_desc *desc) +{ + vfree(desc->data); + desc->data = NULL; + desc->len = 0; +} + +static void iwl_free_fw_img(struct iwl_drv *drv, struct fw_img *img) +{ + int i; + for (i = 0; i < img->num_sec; i++) + iwl_free_fw_desc(drv, &img->sec[i]); + kfree(img->sec); +} + +static void iwl_dealloc_ucode(struct iwl_drv *drv) +{ + int i; + + kfree(drv->fw.dbg.dest_tlv); + for (i = 0; i < ARRAY_SIZE(drv->fw.dbg.conf_tlv); i++) + kfree(drv->fw.dbg.conf_tlv[i]); + for (i = 0; i < ARRAY_SIZE(drv->fw.dbg.trigger_tlv); i++) + kfree(drv->fw.dbg.trigger_tlv[i]); + kfree(drv->fw.dbg.mem_tlv); + kfree(drv->fw.iml); + kfree(drv->fw.ucode_capa.cmd_versions); + kfree(drv->fw.phy_integration_ver); + + for (i = 0; i < IWL_UCODE_TYPE_MAX; i++) + iwl_free_fw_img(drv, drv->fw.img + i); + + /* clear the data for the aborted load case */ + memset(&drv->fw, 0, sizeof(drv->fw)); +} + +static int iwl_alloc_fw_desc(struct iwl_drv *drv, struct fw_desc *desc, + struct fw_sec *sec) +{ + void *data; + + desc->data = NULL; + + if (!sec || !sec->size) + return -EINVAL; + + data = vmalloc(sec->size); + if (!data) + return -ENOMEM; + + desc->len = sec->size; + desc->offset = sec->offset; + memcpy(data, sec->data, desc->len); + desc->data = data; + + return 0; +} + +static void iwl_req_fw_callback(const struct firmware *ucode_raw, + void *context); + +static int iwl_request_firmware(struct iwl_drv *drv, bool first) +{ + const struct iwl_cfg *cfg = drv->trans->cfg; + char tag[8]; + + if (drv->trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_9000 && + (drv->trans->hw_rev_step != SILICON_B_STEP && + drv->trans->hw_rev_step != SILICON_C_STEP)) { + IWL_ERR(drv, + "Only HW steps B and C are currently supported (0x%0x)\n", + drv->trans->hw_rev); + return -EINVAL; + } + + if (first) { + drv->fw_index = cfg->ucode_api_max; + sprintf(tag, "%d", drv->fw_index); + } else { + drv->fw_index--; + sprintf(tag, "%d", drv->fw_index); + } + + if (drv->fw_index < cfg->ucode_api_min) { + IWL_ERR(drv, "no suitable firmware found!\n"); + + if (cfg->ucode_api_min == cfg->ucode_api_max) { + IWL_ERR(drv, "%s%d is required\n", cfg->fw_name_pre, + cfg->ucode_api_max); + } else { + IWL_ERR(drv, "minimum version required: %s%d\n", + cfg->fw_name_pre, cfg->ucode_api_min); + IWL_ERR(drv, "maximum version supported: %s%d\n", + cfg->fw_name_pre, cfg->ucode_api_max); + } + + IWL_ERR(drv, + "check git://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git\n"); + return -ENOENT; + } + + snprintf(drv->firmware_name, sizeof(drv->firmware_name), "%s%s.ucode", + cfg->fw_name_pre, tag); + + IWL_DEBUG_FW_INFO(drv, "attempting to load firmware '%s'\n", + drv->firmware_name); + + return request_firmware_nowait(THIS_MODULE, 1, drv->firmware_name, + drv->trans->dev, + GFP_KERNEL, drv, iwl_req_fw_callback); +} + +struct fw_img_parsing { + struct fw_sec *sec; + int sec_counter; +}; + +/* + * struct fw_sec_parsing: to extract fw section and it's offset from tlv + */ +struct fw_sec_parsing { + __le32 offset; + const u8 data[]; +} __packed; + +/** + * struct iwl_tlv_calib_data - parse the default calib data from TLV + * + * @ucode_type: the uCode to which the following default calib relates. + * @calib: default calibrations. + */ +struct iwl_tlv_calib_data { + __le32 ucode_type; + struct iwl_tlv_calib_ctrl calib; +} __packed; + +struct iwl_firmware_pieces { + struct fw_img_parsing img[IWL_UCODE_TYPE_MAX]; + + u32 init_evtlog_ptr, init_evtlog_size, init_errlog_ptr; + u32 inst_evtlog_ptr, inst_evtlog_size, inst_errlog_ptr; + + /* FW debug data parsed for driver usage */ + bool dbg_dest_tlv_init; + const u8 *dbg_dest_ver; + union { + const struct iwl_fw_dbg_dest_tlv *dbg_dest_tlv; + const struct iwl_fw_dbg_dest_tlv_v1 *dbg_dest_tlv_v1; + }; + const struct iwl_fw_dbg_conf_tlv *dbg_conf_tlv[FW_DBG_CONF_MAX]; + size_t dbg_conf_tlv_len[FW_DBG_CONF_MAX]; + const struct iwl_fw_dbg_trigger_tlv *dbg_trigger_tlv[FW_DBG_TRIGGER_MAX]; + size_t dbg_trigger_tlv_len[FW_DBG_TRIGGER_MAX]; + struct iwl_fw_dbg_mem_seg_tlv *dbg_mem_tlv; + size_t n_mem_tlv; +}; + +/* + * These functions are just to extract uCode section data from the pieces + * structure. + */ +static struct fw_sec *get_sec(struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type, + int sec) +{ + return &pieces->img[type].sec[sec]; +} + +static void alloc_sec_data(struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type, + int sec) +{ + struct fw_img_parsing *img = &pieces->img[type]; + struct fw_sec *sec_memory; + int size = sec + 1; + size_t alloc_size = sizeof(*img->sec) * size; + + if (img->sec && img->sec_counter >= size) + return; + + sec_memory = krealloc(img->sec, alloc_size, GFP_KERNEL); + if (!sec_memory) + return; + + img->sec = sec_memory; + img->sec_counter = size; +} + +static void set_sec_data(struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type, + int sec, + const void *data) +{ + alloc_sec_data(pieces, type, sec); + + pieces->img[type].sec[sec].data = data; +} + +static void set_sec_size(struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type, + int sec, + size_t size) +{ + alloc_sec_data(pieces, type, sec); + + pieces->img[type].sec[sec].size = size; +} + +static size_t get_sec_size(struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type, + int sec) +{ + return pieces->img[type].sec[sec].size; +} + +static void set_sec_offset(struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type, + int sec, + u32 offset) +{ + alloc_sec_data(pieces, type, sec); + + pieces->img[type].sec[sec].offset = offset; +} + +/* + * Gets uCode section from tlv. + */ +static int iwl_store_ucode_sec(struct iwl_firmware_pieces *pieces, + const void *data, enum iwl_ucode_type type, + int size) +{ + struct fw_img_parsing *img; + struct fw_sec *sec; + const struct fw_sec_parsing *sec_parse; + size_t alloc_size; + + if (WARN_ON(!pieces || !data || type >= IWL_UCODE_TYPE_MAX)) + return -1; + + sec_parse = (const struct fw_sec_parsing *)data; + + img = &pieces->img[type]; + + alloc_size = sizeof(*img->sec) * (img->sec_counter + 1); + sec = krealloc(img->sec, alloc_size, GFP_KERNEL); + if (!sec) + return -ENOMEM; + img->sec = sec; + + sec = &img->sec[img->sec_counter]; + + sec->offset = le32_to_cpu(sec_parse->offset); + sec->data = sec_parse->data; + sec->size = size - sizeof(sec_parse->offset); + + ++img->sec_counter; + + return 0; +} + +static int iwl_set_default_calib(struct iwl_drv *drv, const u8 *data) +{ + const struct iwl_tlv_calib_data *def_calib = + (const struct iwl_tlv_calib_data *)data; + u32 ucode_type = le32_to_cpu(def_calib->ucode_type); + if (ucode_type >= IWL_UCODE_TYPE_MAX) { + IWL_ERR(drv, "Wrong ucode_type %u for default calibration.\n", + ucode_type); + return -EINVAL; + } + drv->fw.default_calib[ucode_type].flow_trigger = + def_calib->calib.flow_trigger; + drv->fw.default_calib[ucode_type].event_trigger = + def_calib->calib.event_trigger; + + return 0; +} + +static void iwl_set_ucode_api_flags(struct iwl_drv *drv, const u8 *data, + struct iwl_ucode_capabilities *capa) +{ + const struct iwl_ucode_api *ucode_api = (const void *)data; + u32 api_index = le32_to_cpu(ucode_api->api_index); + u32 api_flags = le32_to_cpu(ucode_api->api_flags); + int i; + + if (api_index >= DIV_ROUND_UP(NUM_IWL_UCODE_TLV_API, 32)) { + IWL_WARN(drv, + "api flags index %d larger than supported by driver\n", + api_index); + return; + } + + for (i = 0; i < 32; i++) { + if (api_flags & BIT(i)) + __set_bit(i + 32 * api_index, capa->_api); + } +} + +static void iwl_set_ucode_capabilities(struct iwl_drv *drv, const u8 *data, + struct iwl_ucode_capabilities *capa) +{ + const struct iwl_ucode_capa *ucode_capa = (const void *)data; + u32 api_index = le32_to_cpu(ucode_capa->api_index); + u32 api_flags = le32_to_cpu(ucode_capa->api_capa); + int i; + + if (api_index >= DIV_ROUND_UP(NUM_IWL_UCODE_TLV_CAPA, 32)) { + IWL_WARN(drv, + "capa flags index %d larger than supported by driver\n", + api_index); + return; + } + + for (i = 0; i < 32; i++) { + if (api_flags & BIT(i)) + __set_bit(i + 32 * api_index, capa->_capa); + } +} + +static const char *iwl_reduced_fw_name(struct iwl_drv *drv) +{ + const char *name = drv->firmware_name; + + if (strncmp(name, "iwlwifi-", 8) == 0) + name += 8; + + return name; +} + +static int iwl_parse_v1_v2_firmware(struct iwl_drv *drv, + const struct firmware *ucode_raw, + struct iwl_firmware_pieces *pieces) +{ + const struct iwl_ucode_header *ucode = (const void *)ucode_raw->data; + u32 api_ver, hdr_size, build; + char buildstr[25]; + const u8 *src; + + drv->fw.ucode_ver = le32_to_cpu(ucode->ver); + api_ver = IWL_UCODE_API(drv->fw.ucode_ver); + + switch (api_ver) { + default: + hdr_size = 28; + if (ucode_raw->size < hdr_size) { + IWL_ERR(drv, "File size too small!\n"); + return -EINVAL; + } + build = le32_to_cpu(ucode->u.v2.build); + set_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST, + le32_to_cpu(ucode->u.v2.inst_size)); + set_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA, + le32_to_cpu(ucode->u.v2.data_size)); + set_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST, + le32_to_cpu(ucode->u.v2.init_size)); + set_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA, + le32_to_cpu(ucode->u.v2.init_data_size)); + src = ucode->u.v2.data; + break; + case 0: + case 1: + case 2: + hdr_size = 24; + if (ucode_raw->size < hdr_size) { + IWL_ERR(drv, "File size too small!\n"); + return -EINVAL; + } + build = 0; + set_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST, + le32_to_cpu(ucode->u.v1.inst_size)); + set_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA, + le32_to_cpu(ucode->u.v1.data_size)); + set_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST, + le32_to_cpu(ucode->u.v1.init_size)); + set_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA, + le32_to_cpu(ucode->u.v1.init_data_size)); + src = ucode->u.v1.data; + break; + } + + if (build) + sprintf(buildstr, " build %u", build); + else + buildstr[0] = '\0'; + + snprintf(drv->fw.fw_version, + sizeof(drv->fw.fw_version), + "%u.%u.%u.%u%s %s", + IWL_UCODE_MAJOR(drv->fw.ucode_ver), + IWL_UCODE_MINOR(drv->fw.ucode_ver), + IWL_UCODE_API(drv->fw.ucode_ver), + IWL_UCODE_SERIAL(drv->fw.ucode_ver), + buildstr, iwl_reduced_fw_name(drv)); + + /* Verify size of file vs. image size info in file's header */ + + if (ucode_raw->size != hdr_size + + get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST) + + get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA) + + get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST) + + get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA)) { + + IWL_ERR(drv, + "uCode file size %d does not match expected size\n", + (int)ucode_raw->size); + return -EINVAL; + } + + + set_sec_data(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST, src); + src += get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST); + set_sec_offset(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST, + IWLAGN_RTC_INST_LOWER_BOUND); + set_sec_data(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA, src); + src += get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA); + set_sec_offset(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA, + IWLAGN_RTC_DATA_LOWER_BOUND); + set_sec_data(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST, src); + src += get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST); + set_sec_offset(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST, + IWLAGN_RTC_INST_LOWER_BOUND); + set_sec_data(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA, src); + src += get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA); + set_sec_offset(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA, + IWLAGN_RTC_DATA_LOWER_BOUND); + return 0; +} + +static void iwl_drv_set_dump_exclude(struct iwl_drv *drv, + enum iwl_ucode_tlv_type tlv_type, + const void *tlv_data, u32 tlv_len) +{ + const struct iwl_fw_dump_exclude *fw = tlv_data; + struct iwl_dump_exclude *excl; + + if (tlv_len < sizeof(*fw)) + return; + + if (tlv_type == IWL_UCODE_TLV_SEC_TABLE_ADDR) { + excl = &drv->fw.dump_excl[0]; + + /* second time we find this, it's for WoWLAN */ + if (excl->addr) + excl = &drv->fw.dump_excl_wowlan[0]; + } else if (fw_has_capa(&drv->fw.ucode_capa, + IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG)) { + /* IWL_UCODE_TLV_D3_KEK_KCK_ADDR is regular image */ + excl = &drv->fw.dump_excl[0]; + } else { + /* IWL_UCODE_TLV_D3_KEK_KCK_ADDR is WoWLAN image */ + excl = &drv->fw.dump_excl_wowlan[0]; + } + + if (excl->addr) + excl++; + + if (excl->addr) { + IWL_DEBUG_FW_INFO(drv, "found too many excludes in fw file\n"); + return; + } + + excl->addr = le32_to_cpu(fw->addr) & ~FW_ADDR_CACHE_CONTROL; + excl->size = le32_to_cpu(fw->size); +} + +static void iwl_parse_dbg_tlv_assert_tables(struct iwl_drv *drv, + const struct iwl_ucode_tlv *tlv) +{ + const struct iwl_fw_ini_region_tlv *region; + u32 length = le32_to_cpu(tlv->length); + u32 addr; + + if (length < offsetof(typeof(*region), special_mem) + + sizeof(region->special_mem)) + return; + + region = (const void *)tlv->data; + addr = le32_to_cpu(region->special_mem.base_addr); + addr += le32_to_cpu(region->special_mem.offset); + addr &= ~FW_ADDR_CACHE_CONTROL; + + if (region->type != IWL_FW_INI_REGION_SPECIAL_DEVICE_MEMORY) + return; + + switch (region->sub_type) { + case IWL_FW_INI_REGION_DEVICE_MEMORY_SUBTYPE_UMAC_ERROR_TABLE: + drv->trans->dbg.umac_error_event_table = addr; + drv->trans->dbg.error_event_table_tlv_status |= + IWL_ERROR_EVENT_TABLE_UMAC; + break; + case IWL_FW_INI_REGION_DEVICE_MEMORY_SUBTYPE_LMAC_1_ERROR_TABLE: + drv->trans->dbg.lmac_error_event_table[0] = addr; + drv->trans->dbg.error_event_table_tlv_status |= + IWL_ERROR_EVENT_TABLE_LMAC1; + break; + case IWL_FW_INI_REGION_DEVICE_MEMORY_SUBTYPE_LMAC_2_ERROR_TABLE: + drv->trans->dbg.lmac_error_event_table[1] = addr; + drv->trans->dbg.error_event_table_tlv_status |= + IWL_ERROR_EVENT_TABLE_LMAC2; + break; + case IWL_FW_INI_REGION_DEVICE_MEMORY_SUBTYPE_TCM_1_ERROR_TABLE: + drv->trans->dbg.tcm_error_event_table[0] = addr; + drv->trans->dbg.error_event_table_tlv_status |= + IWL_ERROR_EVENT_TABLE_TCM1; + break; + case IWL_FW_INI_REGION_DEVICE_MEMORY_SUBTYPE_TCM_2_ERROR_TABLE: + drv->trans->dbg.tcm_error_event_table[1] = addr; + drv->trans->dbg.error_event_table_tlv_status |= + IWL_ERROR_EVENT_TABLE_TCM2; + break; + case IWL_FW_INI_REGION_DEVICE_MEMORY_SUBTYPE_RCM_1_ERROR_TABLE: + drv->trans->dbg.rcm_error_event_table[0] = addr; + drv->trans->dbg.error_event_table_tlv_status |= + IWL_ERROR_EVENT_TABLE_RCM1; + break; + case IWL_FW_INI_REGION_DEVICE_MEMORY_SUBTYPE_RCM_2_ERROR_TABLE: + drv->trans->dbg.rcm_error_event_table[1] = addr; + drv->trans->dbg.error_event_table_tlv_status |= + IWL_ERROR_EVENT_TABLE_RCM2; + break; + default: + break; + } +} + +static int iwl_parse_tlv_firmware(struct iwl_drv *drv, + const struct firmware *ucode_raw, + struct iwl_firmware_pieces *pieces, + struct iwl_ucode_capabilities *capa, + bool *usniffer_images) +{ + const struct iwl_tlv_ucode_header *ucode = (const void *)ucode_raw->data; + const struct iwl_ucode_tlv *tlv; + size_t len = ucode_raw->size; + const u8 *data; + u32 tlv_len; + u32 usniffer_img; + enum iwl_ucode_tlv_type tlv_type; + const u8 *tlv_data; + char buildstr[25]; + u32 build, paging_mem_size; + int num_of_cpus; + bool usniffer_req = false; + + if (len < sizeof(*ucode)) { + IWL_ERR(drv, "uCode has invalid length: %zd\n", len); + return -EINVAL; + } + + if (ucode->magic != cpu_to_le32(IWL_TLV_UCODE_MAGIC)) { + IWL_ERR(drv, "invalid uCode magic: 0X%x\n", + le32_to_cpu(ucode->magic)); + return -EINVAL; + } + + drv->fw.ucode_ver = le32_to_cpu(ucode->ver); + memcpy(drv->fw.human_readable, ucode->human_readable, + sizeof(drv->fw.human_readable)); + build = le32_to_cpu(ucode->build); + + if (build) + sprintf(buildstr, " build %u", build); + else + buildstr[0] = '\0'; + + snprintf(drv->fw.fw_version, + sizeof(drv->fw.fw_version), + "%u.%u.%u.%u%s %s", + IWL_UCODE_MAJOR(drv->fw.ucode_ver), + IWL_UCODE_MINOR(drv->fw.ucode_ver), + IWL_UCODE_API(drv->fw.ucode_ver), + IWL_UCODE_SERIAL(drv->fw.ucode_ver), + buildstr, iwl_reduced_fw_name(drv)); + + data = ucode->data; + + len -= sizeof(*ucode); + + while (len >= sizeof(*tlv)) { + len -= sizeof(*tlv); + + tlv = (const void *)data; + tlv_len = le32_to_cpu(tlv->length); + tlv_type = le32_to_cpu(tlv->type); + tlv_data = tlv->data; + + if (len < tlv_len) { + IWL_ERR(drv, "invalid TLV len: %zd/%u\n", + len, tlv_len); + return -EINVAL; + } + len -= ALIGN(tlv_len, 4); + data += sizeof(*tlv) + ALIGN(tlv_len, 4); + + switch (tlv_type) { + case IWL_UCODE_TLV_INST: + set_sec_data(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_INST, tlv_data); + set_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_INST, tlv_len); + set_sec_offset(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_INST, + IWLAGN_RTC_INST_LOWER_BOUND); + break; + case IWL_UCODE_TLV_DATA: + set_sec_data(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA, tlv_data); + set_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA, tlv_len); + set_sec_offset(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA, + IWLAGN_RTC_DATA_LOWER_BOUND); + break; + case IWL_UCODE_TLV_INIT: + set_sec_data(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_INST, tlv_data); + set_sec_size(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_INST, tlv_len); + set_sec_offset(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_INST, + IWLAGN_RTC_INST_LOWER_BOUND); + break; + case IWL_UCODE_TLV_INIT_DATA: + set_sec_data(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_DATA, tlv_data); + set_sec_size(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_DATA, tlv_len); + set_sec_offset(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_DATA, + IWLAGN_RTC_DATA_LOWER_BOUND); + break; + case IWL_UCODE_TLV_BOOT: + IWL_ERR(drv, "Found unexpected BOOT ucode\n"); + break; + case IWL_UCODE_TLV_PROBE_MAX_LEN: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + capa->max_probe_length = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_PAN: + if (tlv_len) + goto invalid_tlv_len; + capa->flags |= IWL_UCODE_TLV_FLAGS_PAN; + break; + case IWL_UCODE_TLV_FLAGS: + /* must be at least one u32 */ + if (tlv_len < sizeof(u32)) + goto invalid_tlv_len; + /* and a proper number of u32s */ + if (tlv_len % sizeof(u32)) + goto invalid_tlv_len; + /* + * This driver only reads the first u32 as + * right now no more features are defined, + * if that changes then either the driver + * will not work with the new firmware, or + * it'll not take advantage of new features. + */ + capa->flags = le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_API_CHANGES_SET: + if (tlv_len != sizeof(struct iwl_ucode_api)) + goto invalid_tlv_len; + iwl_set_ucode_api_flags(drv, tlv_data, capa); + break; + case IWL_UCODE_TLV_ENABLED_CAPABILITIES: + if (tlv_len != sizeof(struct iwl_ucode_capa)) + goto invalid_tlv_len; + iwl_set_ucode_capabilities(drv, tlv_data, capa); + break; + case IWL_UCODE_TLV_INIT_EVTLOG_PTR: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->init_evtlog_ptr = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_INIT_EVTLOG_SIZE: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->init_evtlog_size = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_INIT_ERRLOG_PTR: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->init_errlog_ptr = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_RUNT_EVTLOG_PTR: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->inst_evtlog_ptr = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_RUNT_EVTLOG_SIZE: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->inst_evtlog_size = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_RUNT_ERRLOG_PTR: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->inst_errlog_ptr = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_ENHANCE_SENS_TBL: + if (tlv_len) + goto invalid_tlv_len; + drv->fw.enhance_sensitivity_table = true; + break; + case IWL_UCODE_TLV_WOWLAN_INST: + set_sec_data(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_INST, tlv_data); + set_sec_size(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_INST, tlv_len); + set_sec_offset(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_INST, + IWLAGN_RTC_INST_LOWER_BOUND); + break; + case IWL_UCODE_TLV_WOWLAN_DATA: + set_sec_data(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_DATA, tlv_data); + set_sec_size(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_DATA, tlv_len); + set_sec_offset(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_DATA, + IWLAGN_RTC_DATA_LOWER_BOUND); + break; + case IWL_UCODE_TLV_PHY_CALIBRATION_SIZE: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + capa->standard_phy_calibration_size = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_SEC_RT: + iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_REGULAR, + tlv_len); + drv->fw.type = IWL_FW_MVM; + break; + case IWL_UCODE_TLV_SEC_INIT: + iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_INIT, + tlv_len); + drv->fw.type = IWL_FW_MVM; + break; + case IWL_UCODE_TLV_SEC_WOWLAN: + iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_WOWLAN, + tlv_len); + drv->fw.type = IWL_FW_MVM; + break; + case IWL_UCODE_TLV_DEF_CALIB: + if (tlv_len != sizeof(struct iwl_tlv_calib_data)) + goto invalid_tlv_len; + if (iwl_set_default_calib(drv, tlv_data)) + goto tlv_error; + break; + case IWL_UCODE_TLV_PHY_SKU: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + drv->fw.phy_config = le32_to_cpup((const __le32 *)tlv_data); + drv->fw.valid_tx_ant = (drv->fw.phy_config & + FW_PHY_CFG_TX_CHAIN) >> + FW_PHY_CFG_TX_CHAIN_POS; + drv->fw.valid_rx_ant = (drv->fw.phy_config & + FW_PHY_CFG_RX_CHAIN) >> + FW_PHY_CFG_RX_CHAIN_POS; + break; + case IWL_UCODE_TLV_SECURE_SEC_RT: + iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_REGULAR, + tlv_len); + drv->fw.type = IWL_FW_MVM; + break; + case IWL_UCODE_TLV_SECURE_SEC_INIT: + iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_INIT, + tlv_len); + drv->fw.type = IWL_FW_MVM; + break; + case IWL_UCODE_TLV_SECURE_SEC_WOWLAN: + iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_WOWLAN, + tlv_len); + drv->fw.type = IWL_FW_MVM; + break; + case IWL_UCODE_TLV_NUM_OF_CPU: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + num_of_cpus = + le32_to_cpup((const __le32 *)tlv_data); + + if (num_of_cpus == 2) { + drv->fw.img[IWL_UCODE_REGULAR].is_dual_cpus = + true; + drv->fw.img[IWL_UCODE_INIT].is_dual_cpus = + true; + drv->fw.img[IWL_UCODE_WOWLAN].is_dual_cpus = + true; + } else if ((num_of_cpus > 2) || (num_of_cpus < 1)) { + IWL_ERR(drv, "Driver support upto 2 CPUs\n"); + return -EINVAL; + } + break; + case IWL_UCODE_TLV_N_SCAN_CHANNELS: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + capa->n_scan_channels = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_FW_VERSION: { + const __le32 *ptr = (const void *)tlv_data; + u32 major, minor; + u8 local_comp; + + if (tlv_len != sizeof(u32) * 3) + goto invalid_tlv_len; + + major = le32_to_cpup(ptr++); + minor = le32_to_cpup(ptr++); + local_comp = le32_to_cpup(ptr); + + if (major >= 35) + snprintf(drv->fw.fw_version, + sizeof(drv->fw.fw_version), + "%u.%08x.%u %s", major, minor, + local_comp, iwl_reduced_fw_name(drv)); + else + snprintf(drv->fw.fw_version, + sizeof(drv->fw.fw_version), + "%u.%u.%u %s", major, minor, + local_comp, iwl_reduced_fw_name(drv)); + break; + } + case IWL_UCODE_TLV_FW_DBG_DEST: { + const struct iwl_fw_dbg_dest_tlv *dest = NULL; + const struct iwl_fw_dbg_dest_tlv_v1 *dest_v1 = NULL; + u8 mon_mode; + + pieces->dbg_dest_ver = (const u8 *)tlv_data; + if (*pieces->dbg_dest_ver == 1) { + dest = (const void *)tlv_data; + } else if (*pieces->dbg_dest_ver == 0) { + dest_v1 = (const void *)tlv_data; + } else { + IWL_ERR(drv, + "The version is %d, and it is invalid\n", + *pieces->dbg_dest_ver); + break; + } + + if (pieces->dbg_dest_tlv_init) { + IWL_ERR(drv, + "dbg destination ignored, already exists\n"); + break; + } + + pieces->dbg_dest_tlv_init = true; + + if (dest_v1) { + pieces->dbg_dest_tlv_v1 = dest_v1; + mon_mode = dest_v1->monitor_mode; + } else { + pieces->dbg_dest_tlv = dest; + mon_mode = dest->monitor_mode; + } + + IWL_INFO(drv, "Found debug destination: %s\n", + get_fw_dbg_mode_string(mon_mode)); + + drv->fw.dbg.n_dest_reg = (dest_v1) ? + tlv_len - + offsetof(struct iwl_fw_dbg_dest_tlv_v1, + reg_ops) : + tlv_len - + offsetof(struct iwl_fw_dbg_dest_tlv, + reg_ops); + + drv->fw.dbg.n_dest_reg /= + sizeof(drv->fw.dbg.dest_tlv->reg_ops[0]); + + break; + } + case IWL_UCODE_TLV_FW_DBG_CONF: { + const struct iwl_fw_dbg_conf_tlv *conf = + (const void *)tlv_data; + + if (!pieces->dbg_dest_tlv_init) { + IWL_ERR(drv, + "Ignore dbg config %d - no destination configured\n", + conf->id); + break; + } + + if (conf->id >= ARRAY_SIZE(drv->fw.dbg.conf_tlv)) { + IWL_ERR(drv, + "Skip unknown configuration: %d\n", + conf->id); + break; + } + + if (pieces->dbg_conf_tlv[conf->id]) { + IWL_ERR(drv, + "Ignore duplicate dbg config %d\n", + conf->id); + break; + } + + if (conf->usniffer) + usniffer_req = true; + + IWL_INFO(drv, "Found debug configuration: %d\n", + conf->id); + + pieces->dbg_conf_tlv[conf->id] = conf; + pieces->dbg_conf_tlv_len[conf->id] = tlv_len; + break; + } + case IWL_UCODE_TLV_FW_DBG_TRIGGER: { + const struct iwl_fw_dbg_trigger_tlv *trigger = + (const void *)tlv_data; + u32 trigger_id = le32_to_cpu(trigger->id); + + if (trigger_id >= ARRAY_SIZE(drv->fw.dbg.trigger_tlv)) { + IWL_ERR(drv, + "Skip unknown trigger: %u\n", + trigger->id); + break; + } + + if (pieces->dbg_trigger_tlv[trigger_id]) { + IWL_ERR(drv, + "Ignore duplicate dbg trigger %u\n", + trigger->id); + break; + } + + IWL_INFO(drv, "Found debug trigger: %u\n", trigger->id); + + pieces->dbg_trigger_tlv[trigger_id] = trigger; + pieces->dbg_trigger_tlv_len[trigger_id] = tlv_len; + break; + } + case IWL_UCODE_TLV_FW_DBG_DUMP_LST: { + if (tlv_len != sizeof(u32)) { + IWL_ERR(drv, + "dbg lst mask size incorrect, skip\n"); + break; + } + + drv->fw.dbg.dump_mask = + le32_to_cpup((const __le32 *)tlv_data); + break; + } + case IWL_UCODE_TLV_SEC_RT_USNIFFER: + *usniffer_images = true; + iwl_store_ucode_sec(pieces, tlv_data, + IWL_UCODE_REGULAR_USNIFFER, + tlv_len); + break; + case IWL_UCODE_TLV_PAGING: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + paging_mem_size = le32_to_cpup((const __le32 *)tlv_data); + + IWL_DEBUG_FW(drv, + "Paging: paging enabled (size = %u bytes)\n", + paging_mem_size); + + if (paging_mem_size > MAX_PAGING_IMAGE_SIZE) { + IWL_ERR(drv, + "Paging: driver supports up to %lu bytes for paging image\n", + MAX_PAGING_IMAGE_SIZE); + return -EINVAL; + } + + if (paging_mem_size & (FW_PAGING_SIZE - 1)) { + IWL_ERR(drv, + "Paging: image isn't multiple %lu\n", + FW_PAGING_SIZE); + return -EINVAL; + } + + drv->fw.img[IWL_UCODE_REGULAR].paging_mem_size = + paging_mem_size; + usniffer_img = IWL_UCODE_REGULAR_USNIFFER; + drv->fw.img[usniffer_img].paging_mem_size = + paging_mem_size; + break; + case IWL_UCODE_TLV_FW_GSCAN_CAPA: + /* ignored */ + break; + case IWL_UCODE_TLV_FW_MEM_SEG: { + const struct iwl_fw_dbg_mem_seg_tlv *dbg_mem = + (const void *)tlv_data; + size_t size; + struct iwl_fw_dbg_mem_seg_tlv *n; + + if (tlv_len != (sizeof(*dbg_mem))) + goto invalid_tlv_len; + + IWL_DEBUG_INFO(drv, "Found debug memory segment: %u\n", + dbg_mem->data_type); + + size = sizeof(*pieces->dbg_mem_tlv) * + (pieces->n_mem_tlv + 1); + n = krealloc(pieces->dbg_mem_tlv, size, GFP_KERNEL); + if (!n) + return -ENOMEM; + pieces->dbg_mem_tlv = n; + pieces->dbg_mem_tlv[pieces->n_mem_tlv] = *dbg_mem; + pieces->n_mem_tlv++; + break; + } + case IWL_UCODE_TLV_IML: { + drv->fw.iml_len = tlv_len; + drv->fw.iml = kmemdup(tlv_data, tlv_len, GFP_KERNEL); + if (!drv->fw.iml) + return -ENOMEM; + break; + } + case IWL_UCODE_TLV_FW_RECOVERY_INFO: { + const struct { + __le32 buf_addr; + __le32 buf_size; + } *recov_info = (const void *)tlv_data; + + if (tlv_len != sizeof(*recov_info)) + goto invalid_tlv_len; + capa->error_log_addr = + le32_to_cpu(recov_info->buf_addr); + capa->error_log_size = + le32_to_cpu(recov_info->buf_size); + } + break; + case IWL_UCODE_TLV_FW_FSEQ_VERSION: { + const struct { + u8 version[32]; + u8 sha1[20]; + } *fseq_ver = (const void *)tlv_data; + + if (tlv_len != sizeof(*fseq_ver)) + goto invalid_tlv_len; + IWL_INFO(drv, "TLV_FW_FSEQ_VERSION: %s\n", + fseq_ver->version); + } + break; + case IWL_UCODE_TLV_FW_NUM_STATIONS: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + if (le32_to_cpup((const __le32 *)tlv_data) > + IWL_MVM_STATION_COUNT_MAX) { + IWL_ERR(drv, + "%d is an invalid number of station\n", + le32_to_cpup((const __le32 *)tlv_data)); + goto tlv_error; + } + capa->num_stations = + le32_to_cpup((const __le32 *)tlv_data); + break; + case IWL_UCODE_TLV_UMAC_DEBUG_ADDRS: { + const struct iwl_umac_debug_addrs *dbg_ptrs = + (const void *)tlv_data; + + if (tlv_len != sizeof(*dbg_ptrs)) + goto invalid_tlv_len; + if (drv->trans->trans_cfg->device_family < + IWL_DEVICE_FAMILY_22000) + break; + drv->trans->dbg.umac_error_event_table = + le32_to_cpu(dbg_ptrs->error_info_addr) & + ~FW_ADDR_CACHE_CONTROL; + drv->trans->dbg.error_event_table_tlv_status |= + IWL_ERROR_EVENT_TABLE_UMAC; + break; + } + case IWL_UCODE_TLV_LMAC_DEBUG_ADDRS: { + const struct iwl_lmac_debug_addrs *dbg_ptrs = + (const void *)tlv_data; + + if (tlv_len != sizeof(*dbg_ptrs)) + goto invalid_tlv_len; + if (drv->trans->trans_cfg->device_family < + IWL_DEVICE_FAMILY_22000) + break; + drv->trans->dbg.lmac_error_event_table[0] = + le32_to_cpu(dbg_ptrs->error_event_table_ptr) & + ~FW_ADDR_CACHE_CONTROL; + drv->trans->dbg.error_event_table_tlv_status |= + IWL_ERROR_EVENT_TABLE_LMAC1; + break; + } + case IWL_UCODE_TLV_TYPE_REGIONS: + iwl_parse_dbg_tlv_assert_tables(drv, tlv); + fallthrough; + case IWL_UCODE_TLV_TYPE_DEBUG_INFO: + case IWL_UCODE_TLV_TYPE_BUFFER_ALLOCATION: + case IWL_UCODE_TLV_TYPE_HCMD: + case IWL_UCODE_TLV_TYPE_TRIGGERS: + case IWL_UCODE_TLV_TYPE_CONF_SET: + if (iwlwifi_mod_params.enable_ini) + iwl_dbg_tlv_alloc(drv->trans, tlv, false); + break; + case IWL_UCODE_TLV_CMD_VERSIONS: + if (tlv_len % sizeof(struct iwl_fw_cmd_version)) { + IWL_ERR(drv, + "Invalid length for command versions: %u\n", + tlv_len); + tlv_len /= sizeof(struct iwl_fw_cmd_version); + tlv_len *= sizeof(struct iwl_fw_cmd_version); + } + if (WARN_ON(capa->cmd_versions)) + return -EINVAL; + capa->cmd_versions = kmemdup(tlv_data, tlv_len, + GFP_KERNEL); + if (!capa->cmd_versions) + return -ENOMEM; + capa->n_cmd_versions = + tlv_len / sizeof(struct iwl_fw_cmd_version); + break; + case IWL_UCODE_TLV_PHY_INTEGRATION_VERSION: + if (drv->fw.phy_integration_ver) { + IWL_ERR(drv, + "phy integration str ignored, already exists\n"); + break; + } + + drv->fw.phy_integration_ver = + kmemdup(tlv_data, tlv_len, GFP_KERNEL); + if (!drv->fw.phy_integration_ver) + return -ENOMEM; + drv->fw.phy_integration_ver_len = tlv_len; + break; + case IWL_UCODE_TLV_SEC_TABLE_ADDR: + case IWL_UCODE_TLV_D3_KEK_KCK_ADDR: + iwl_drv_set_dump_exclude(drv, tlv_type, + tlv_data, tlv_len); + break; + default: + IWL_DEBUG_INFO(drv, "unknown TLV: %d\n", tlv_type); + break; + } + } + + if (!fw_has_capa(capa, IWL_UCODE_TLV_CAPA_USNIFFER_UNIFIED) && + usniffer_req && !*usniffer_images) { + IWL_ERR(drv, + "user selected to work with usniffer but usniffer image isn't available in ucode package\n"); + return -EINVAL; + } + + if (len) { + IWL_ERR(drv, "invalid TLV after parsing: %zd\n", len); + iwl_print_hex_dump(drv, IWL_DL_FW, data, len); + return -EINVAL; + } + + return 0; + + invalid_tlv_len: + IWL_ERR(drv, "TLV %d has invalid size: %u\n", tlv_type, tlv_len); + tlv_error: + iwl_print_hex_dump(drv, IWL_DL_FW, tlv_data, tlv_len); + + return -EINVAL; +} + +static int iwl_alloc_ucode(struct iwl_drv *drv, + struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type) +{ + int i; + struct fw_desc *sec; + + sec = kcalloc(pieces->img[type].sec_counter, sizeof(*sec), GFP_KERNEL); + if (!sec) + return -ENOMEM; + drv->fw.img[type].sec = sec; + drv->fw.img[type].num_sec = pieces->img[type].sec_counter; + + for (i = 0; i < pieces->img[type].sec_counter; i++) + if (iwl_alloc_fw_desc(drv, &sec[i], get_sec(pieces, type, i))) + return -ENOMEM; + + return 0; +} + +static int validate_sec_sizes(struct iwl_drv *drv, + struct iwl_firmware_pieces *pieces, + const struct iwl_cfg *cfg) +{ + IWL_DEBUG_INFO(drv, "f/w package hdr runtime inst size = %zd\n", + get_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_INST)); + IWL_DEBUG_INFO(drv, "f/w package hdr runtime data size = %zd\n", + get_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA)); + IWL_DEBUG_INFO(drv, "f/w package hdr init inst size = %zd\n", + get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST)); + IWL_DEBUG_INFO(drv, "f/w package hdr init data size = %zd\n", + get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA)); + + /* Verify that uCode images will fit in card's SRAM. */ + if (get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST) > + cfg->max_inst_size) { + IWL_ERR(drv, "uCode instr len %zd too large to fit in\n", + get_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_INST)); + return -1; + } + + if (get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA) > + cfg->max_data_size) { + IWL_ERR(drv, "uCode data len %zd too large to fit in\n", + get_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA)); + return -1; + } + + if (get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST) > + cfg->max_inst_size) { + IWL_ERR(drv, "uCode init instr len %zd too large to fit in\n", + get_sec_size(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_INST)); + return -1; + } + + if (get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA) > + cfg->max_data_size) { + IWL_ERR(drv, "uCode init data len %zd too large to fit in\n", + get_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA)); + return -1; + } + return 0; +} + +static struct iwl_op_mode * +_iwl_op_mode_start(struct iwl_drv *drv, struct iwlwifi_opmode_table *op) +{ + const struct iwl_op_mode_ops *ops = op->ops; + struct dentry *dbgfs_dir = NULL; + struct iwl_op_mode *op_mode = NULL; + int retry, max_retry = !!iwlwifi_mod_params.fw_restart * IWL_MAX_INIT_RETRY; + + for (retry = 0; retry <= max_retry; retry++) { + +#ifdef CONFIG_IWLWIFI_DEBUGFS + drv->dbgfs_op_mode = debugfs_create_dir(op->name, + drv->dbgfs_drv); + dbgfs_dir = drv->dbgfs_op_mode; +#endif + + op_mode = ops->start(drv->trans, drv->trans->cfg, + &drv->fw, dbgfs_dir); + + if (op_mode) + return op_mode; + + IWL_ERR(drv, "retry init count %d\n", retry); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + debugfs_remove_recursive(drv->dbgfs_op_mode); + drv->dbgfs_op_mode = NULL; +#endif + } + + return NULL; +} + +static void _iwl_op_mode_stop(struct iwl_drv *drv) +{ + /* op_mode can be NULL if its start failed */ + if (drv->op_mode) { + iwl_op_mode_stop(drv->op_mode); + drv->op_mode = NULL; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + debugfs_remove_recursive(drv->dbgfs_op_mode); + drv->dbgfs_op_mode = NULL; +#endif + } +} + +/* + * iwl_req_fw_callback - callback when firmware was loaded + * + * If loaded successfully, copies the firmware into buffers + * for the card to fetch (via DMA). + */ +static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context) +{ + struct iwl_drv *drv = context; + struct iwl_fw *fw = &drv->fw; + const struct iwl_ucode_header *ucode; + struct iwlwifi_opmode_table *op; + int err; + struct iwl_firmware_pieces *pieces; + const unsigned int api_max = drv->trans->cfg->ucode_api_max; + const unsigned int api_min = drv->trans->cfg->ucode_api_min; + size_t trigger_tlv_sz[FW_DBG_TRIGGER_MAX]; + u32 api_ver; + int i; + bool load_module = false; + bool usniffer_images = false; + bool failure = true; + + fw->ucode_capa.max_probe_length = IWL_DEFAULT_MAX_PROBE_LENGTH; + fw->ucode_capa.standard_phy_calibration_size = + IWL_DEFAULT_STANDARD_PHY_CALIBRATE_TBL_SIZE; + fw->ucode_capa.n_scan_channels = IWL_DEFAULT_SCAN_CHANNELS; + fw->ucode_capa.num_stations = IWL_MVM_STATION_COUNT_MAX; + /* dump all fw memory areas by default */ + fw->dbg.dump_mask = 0xffffffff; + + pieces = kzalloc(sizeof(*pieces), GFP_KERNEL); + if (!pieces) + goto out_free_fw; + + if (!ucode_raw) + goto try_again; + + IWL_DEBUG_FW_INFO(drv, "Loaded firmware file '%s' (%zd bytes).\n", + drv->firmware_name, ucode_raw->size); + + /* Make sure that we got at least the API version number */ + if (ucode_raw->size < 4) { + IWL_ERR(drv, "File size way too small!\n"); + goto try_again; + } + + /* Data from ucode file: header followed by uCode images */ + ucode = (const struct iwl_ucode_header *)ucode_raw->data; + + if (ucode->ver) + err = iwl_parse_v1_v2_firmware(drv, ucode_raw, pieces); + else + err = iwl_parse_tlv_firmware(drv, ucode_raw, pieces, + &fw->ucode_capa, &usniffer_images); + + if (err) + goto try_again; + + if (fw_has_api(&drv->fw.ucode_capa, IWL_UCODE_TLV_API_NEW_VERSION)) + api_ver = drv->fw.ucode_ver; + else + api_ver = IWL_UCODE_API(drv->fw.ucode_ver); + + /* + * api_ver should match the api version forming part of the + * firmware filename ... but we don't check for that and only rely + * on the API version read from firmware header from here on forward + */ + if (api_ver < api_min || api_ver > api_max) { + IWL_ERR(drv, + "Driver unable to support your firmware API. " + "Driver supports v%u, firmware is v%u.\n", + api_max, api_ver); + goto try_again; + } + + /* + * In mvm uCode there is no difference between data and instructions + * sections. + */ + if (fw->type == IWL_FW_DVM && validate_sec_sizes(drv, pieces, + drv->trans->cfg)) + goto try_again; + + /* Allocate ucode buffers for card's bus-master loading ... */ + + /* Runtime instructions and 2 copies of data: + * 1) unmodified from disk + * 2) backup cache for save/restore during power-downs + */ + for (i = 0; i < IWL_UCODE_TYPE_MAX; i++) + if (iwl_alloc_ucode(drv, pieces, i)) + goto out_free_fw; + + if (pieces->dbg_dest_tlv_init) { + size_t dbg_dest_size = sizeof(*drv->fw.dbg.dest_tlv) + + sizeof(drv->fw.dbg.dest_tlv->reg_ops[0]) * + drv->fw.dbg.n_dest_reg; + + drv->fw.dbg.dest_tlv = kmalloc(dbg_dest_size, GFP_KERNEL); + + if (!drv->fw.dbg.dest_tlv) + goto out_free_fw; + + if (*pieces->dbg_dest_ver == 0) { + memcpy(drv->fw.dbg.dest_tlv, pieces->dbg_dest_tlv_v1, + dbg_dest_size); + } else { + struct iwl_fw_dbg_dest_tlv_v1 *dest_tlv = + drv->fw.dbg.dest_tlv; + + dest_tlv->version = pieces->dbg_dest_tlv->version; + dest_tlv->monitor_mode = + pieces->dbg_dest_tlv->monitor_mode; + dest_tlv->size_power = + pieces->dbg_dest_tlv->size_power; + dest_tlv->wrap_count = + pieces->dbg_dest_tlv->wrap_count; + dest_tlv->write_ptr_reg = + pieces->dbg_dest_tlv->write_ptr_reg; + dest_tlv->base_shift = + pieces->dbg_dest_tlv->base_shift; + memcpy(dest_tlv->reg_ops, + pieces->dbg_dest_tlv->reg_ops, + sizeof(drv->fw.dbg.dest_tlv->reg_ops[0]) * + drv->fw.dbg.n_dest_reg); + + /* In version 1 of the destination tlv, which is + * relevant for internal buffer exclusively, + * the base address is part of given with the length + * of the buffer, and the size shift is give instead of + * end shift. We now store these values in base_reg, + * and end shift, and when dumping the data we'll + * manipulate it for extracting both the length and + * base address */ + dest_tlv->base_reg = pieces->dbg_dest_tlv->cfg_reg; + dest_tlv->end_shift = + pieces->dbg_dest_tlv->size_shift; + } + } + + for (i = 0; i < ARRAY_SIZE(drv->fw.dbg.conf_tlv); i++) { + if (pieces->dbg_conf_tlv[i]) { + drv->fw.dbg.conf_tlv[i] = + kmemdup(pieces->dbg_conf_tlv[i], + pieces->dbg_conf_tlv_len[i], + GFP_KERNEL); + if (!drv->fw.dbg.conf_tlv[i]) + goto out_free_fw; + } + } + + memset(&trigger_tlv_sz, 0xff, sizeof(trigger_tlv_sz)); + + trigger_tlv_sz[FW_DBG_TRIGGER_MISSED_BEACONS] = + sizeof(struct iwl_fw_dbg_trigger_missed_bcon); + trigger_tlv_sz[FW_DBG_TRIGGER_CHANNEL_SWITCH] = 0; + trigger_tlv_sz[FW_DBG_TRIGGER_FW_NOTIF] = + sizeof(struct iwl_fw_dbg_trigger_cmd); + trigger_tlv_sz[FW_DBG_TRIGGER_MLME] = + sizeof(struct iwl_fw_dbg_trigger_mlme); + trigger_tlv_sz[FW_DBG_TRIGGER_STATS] = + sizeof(struct iwl_fw_dbg_trigger_stats); + trigger_tlv_sz[FW_DBG_TRIGGER_RSSI] = + sizeof(struct iwl_fw_dbg_trigger_low_rssi); + trigger_tlv_sz[FW_DBG_TRIGGER_TXQ_TIMERS] = + sizeof(struct iwl_fw_dbg_trigger_txq_timer); + trigger_tlv_sz[FW_DBG_TRIGGER_TIME_EVENT] = + sizeof(struct iwl_fw_dbg_trigger_time_event); + trigger_tlv_sz[FW_DBG_TRIGGER_BA] = + sizeof(struct iwl_fw_dbg_trigger_ba); + trigger_tlv_sz[FW_DBG_TRIGGER_TDLS] = + sizeof(struct iwl_fw_dbg_trigger_tdls); + + for (i = 0; i < ARRAY_SIZE(drv->fw.dbg.trigger_tlv); i++) { + if (pieces->dbg_trigger_tlv[i]) { + /* + * If the trigger isn't long enough, WARN and exit. + * Someone is trying to debug something and he won't + * be able to catch the bug he is trying to chase. + * We'd better be noisy to be sure he knows what's + * going on. + */ + if (WARN_ON(pieces->dbg_trigger_tlv_len[i] < + (trigger_tlv_sz[i] + + sizeof(struct iwl_fw_dbg_trigger_tlv)))) + goto out_free_fw; + drv->fw.dbg.trigger_tlv_len[i] = + pieces->dbg_trigger_tlv_len[i]; + drv->fw.dbg.trigger_tlv[i] = + kmemdup(pieces->dbg_trigger_tlv[i], + drv->fw.dbg.trigger_tlv_len[i], + GFP_KERNEL); + if (!drv->fw.dbg.trigger_tlv[i]) + goto out_free_fw; + } + } + + /* Now that we can no longer fail, copy information */ + + drv->fw.dbg.mem_tlv = pieces->dbg_mem_tlv; + pieces->dbg_mem_tlv = NULL; + drv->fw.dbg.n_mem_tlv = pieces->n_mem_tlv; + + /* + * The (size - 16) / 12 formula is based on the information recorded + * for each event, which is of mode 1 (including timestamp) for all + * new microcodes that include this information. + */ + fw->init_evtlog_ptr = pieces->init_evtlog_ptr; + if (pieces->init_evtlog_size) + fw->init_evtlog_size = (pieces->init_evtlog_size - 16)/12; + else + fw->init_evtlog_size = + drv->trans->trans_cfg->base_params->max_event_log_size; + fw->init_errlog_ptr = pieces->init_errlog_ptr; + fw->inst_evtlog_ptr = pieces->inst_evtlog_ptr; + if (pieces->inst_evtlog_size) + fw->inst_evtlog_size = (pieces->inst_evtlog_size - 16)/12; + else + fw->inst_evtlog_size = + drv->trans->trans_cfg->base_params->max_event_log_size; + fw->inst_errlog_ptr = pieces->inst_errlog_ptr; + + /* + * figure out the offset of chain noise reset and gain commands + * base on the size of standard phy calibration commands table size + */ + if (fw->ucode_capa.standard_phy_calibration_size > + IWL_MAX_PHY_CALIBRATE_TBL_SIZE) + fw->ucode_capa.standard_phy_calibration_size = + IWL_MAX_STANDARD_PHY_CALIBRATE_TBL_SIZE; + + /* We have our copies now, allow OS release its copies */ + release_firmware(ucode_raw); + + iwl_dbg_tlv_load_bin(drv->trans->dev, drv->trans); + + mutex_lock(&iwlwifi_opmode_table_mtx); + switch (fw->type) { + case IWL_FW_DVM: + op = &iwlwifi_opmode_table[DVM_OP_MODE]; + break; + default: + WARN(1, "Invalid fw type %d\n", fw->type); + fallthrough; + case IWL_FW_MVM: + op = &iwlwifi_opmode_table[MVM_OP_MODE]; + break; + } + + IWL_INFO(drv, "loaded firmware version %s op_mode %s\n", + drv->fw.fw_version, op->name); + + /* add this device to the list of devices using this op_mode */ + list_add_tail(&drv->list, &op->drv); + + if (op->ops) { + drv->op_mode = _iwl_op_mode_start(drv, op); + + if (!drv->op_mode) { + mutex_unlock(&iwlwifi_opmode_table_mtx); + goto out_unbind; + } + } else { + load_module = true; + } + mutex_unlock(&iwlwifi_opmode_table_mtx); + + /* + * Complete the firmware request last so that + * a driver unbind (stop) doesn't run while we + * are doing the start() above. + */ + complete(&drv->request_firmware_complete); + + /* + * Load the module last so we don't block anything + * else from proceeding if the module fails to load + * or hangs loading. + */ + if (load_module) + request_module("%s", op->name); + failure = false; + goto free; + + try_again: + /* try next, if any */ + release_firmware(ucode_raw); + if (iwl_request_firmware(drv, false)) + goto out_unbind; + goto free; + + out_free_fw: + release_firmware(ucode_raw); + out_unbind: + complete(&drv->request_firmware_complete); + device_release_driver(drv->trans->dev); + /* drv has just been freed by the release */ + failure = false; + free: + if (failure) + iwl_dealloc_ucode(drv); + + if (pieces) { + for (i = 0; i < ARRAY_SIZE(pieces->img); i++) + kfree(pieces->img[i].sec); + kfree(pieces->dbg_mem_tlv); + kfree(pieces); + } +} + +struct iwl_drv *iwl_drv_start(struct iwl_trans *trans) +{ + struct iwl_drv *drv; + int ret; + + drv = kzalloc(sizeof(*drv), GFP_KERNEL); + if (!drv) { + ret = -ENOMEM; + goto err; + } + + drv->trans = trans; + drv->dev = trans->dev; + + init_completion(&drv->request_firmware_complete); + INIT_LIST_HEAD(&drv->list); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + /* Create the device debugfs entries. */ + drv->dbgfs_drv = debugfs_create_dir(dev_name(trans->dev), + iwl_dbgfs_root); + + /* Create transport layer debugfs dir */ + drv->trans->dbgfs_dir = debugfs_create_dir("trans", drv->dbgfs_drv); +#endif + + drv->trans->dbg.domains_bitmap = IWL_TRANS_FW_DBG_DOMAIN(drv->trans); + if (iwlwifi_mod_params.enable_ini != ENABLE_INI) { + /* We have a non-default value in the module parameter, + * take its value + */ + drv->trans->dbg.domains_bitmap &= 0xffff; + if (iwlwifi_mod_params.enable_ini != IWL_FW_INI_PRESET_DISABLE) { + if (iwlwifi_mod_params.enable_ini > ENABLE_INI) { + IWL_ERR(trans, + "invalid enable_ini module parameter value: max = %d, using 0 instead\n", + ENABLE_INI); + iwlwifi_mod_params.enable_ini = 0; + } + drv->trans->dbg.domains_bitmap = + BIT(IWL_FW_DBG_DOMAIN_POS + iwlwifi_mod_params.enable_ini); + } + } + + ret = iwl_request_firmware(drv, true); + if (ret) { + IWL_ERR(trans, "Couldn't request the fw\n"); + goto err_fw; + } + + return drv; + +err_fw: +#ifdef CONFIG_IWLWIFI_DEBUGFS + debugfs_remove_recursive(drv->dbgfs_drv); + iwl_dbg_tlv_free(drv->trans); +#endif + kfree(drv); +err: + return ERR_PTR(ret); +} + +void iwl_drv_stop(struct iwl_drv *drv) +{ + wait_for_completion(&drv->request_firmware_complete); + + _iwl_op_mode_stop(drv); + + iwl_dealloc_ucode(drv); + + mutex_lock(&iwlwifi_opmode_table_mtx); + /* + * List is empty (this item wasn't added) + * when firmware loading failed -- in that + * case we can't remove it from any list. + */ + if (!list_empty(&drv->list)) + list_del(&drv->list); + mutex_unlock(&iwlwifi_opmode_table_mtx); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + drv->trans->ops->debugfs_cleanup(drv->trans); + + debugfs_remove_recursive(drv->dbgfs_drv); +#endif + + iwl_dbg_tlv_free(drv->trans); + + kfree(drv); +} + +/* shared module parameters */ +struct iwl_mod_params iwlwifi_mod_params = { + .fw_restart = true, + .bt_coex_active = true, + .power_level = IWL_POWER_INDEX_1, + .uapsd_disable = IWL_DISABLE_UAPSD_BSS | IWL_DISABLE_UAPSD_P2P_CLIENT, + .enable_ini = ENABLE_INI, + /* the rest are 0 by default */ +}; +IWL_EXPORT_SYMBOL(iwlwifi_mod_params); + +int iwl_opmode_register(const char *name, const struct iwl_op_mode_ops *ops) +{ + int i; + struct iwl_drv *drv; + struct iwlwifi_opmode_table *op; + + mutex_lock(&iwlwifi_opmode_table_mtx); + for (i = 0; i < ARRAY_SIZE(iwlwifi_opmode_table); i++) { + op = &iwlwifi_opmode_table[i]; + if (strcmp(op->name, name)) + continue; + op->ops = ops; + /* TODO: need to handle exceptional case */ + list_for_each_entry(drv, &op->drv, list) + drv->op_mode = _iwl_op_mode_start(drv, op); + + mutex_unlock(&iwlwifi_opmode_table_mtx); + return 0; + } + mutex_unlock(&iwlwifi_opmode_table_mtx); + return -EIO; +} +IWL_EXPORT_SYMBOL(iwl_opmode_register); + +void iwl_opmode_deregister(const char *name) +{ + int i; + struct iwl_drv *drv; + + mutex_lock(&iwlwifi_opmode_table_mtx); + for (i = 0; i < ARRAY_SIZE(iwlwifi_opmode_table); i++) { + if (strcmp(iwlwifi_opmode_table[i].name, name)) + continue; + iwlwifi_opmode_table[i].ops = NULL; + + /* call the stop routine for all devices */ + list_for_each_entry(drv, &iwlwifi_opmode_table[i].drv, list) + _iwl_op_mode_stop(drv); + + mutex_unlock(&iwlwifi_opmode_table_mtx); + return; + } + mutex_unlock(&iwlwifi_opmode_table_mtx); +} +IWL_EXPORT_SYMBOL(iwl_opmode_deregister); + +static int __init iwl_drv_init(void) +{ + int i, err; + + for (i = 0; i < ARRAY_SIZE(iwlwifi_opmode_table); i++) + INIT_LIST_HEAD(&iwlwifi_opmode_table[i].drv); + + pr_info(DRV_DESCRIPTION "\n"); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + /* Create the root of iwlwifi debugfs subsystem. */ + iwl_dbgfs_root = debugfs_create_dir(DRV_NAME, NULL); +#endif + + err = iwl_pci_register_driver(); + if (err) + goto cleanup_debugfs; + + return 0; + +cleanup_debugfs: +#ifdef CONFIG_IWLWIFI_DEBUGFS + debugfs_remove_recursive(iwl_dbgfs_root); +#endif + return err; +} +module_init(iwl_drv_init); + +static void __exit iwl_drv_exit(void) +{ + iwl_pci_unregister_driver(); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + debugfs_remove_recursive(iwl_dbgfs_root); +#endif +} +module_exit(iwl_drv_exit); + +#ifdef CONFIG_IWLWIFI_DEBUG +module_param_named(debug, iwlwifi_mod_params.debug_level, uint, 0644); +MODULE_PARM_DESC(debug, "debug output mask"); +#endif + +module_param_named(swcrypto, iwlwifi_mod_params.swcrypto, int, 0444); +MODULE_PARM_DESC(swcrypto, "using crypto in software (default 0 [hardware])"); +module_param_named(11n_disable, iwlwifi_mod_params.disable_11n, uint, 0444); +MODULE_PARM_DESC(11n_disable, + "disable 11n functionality, bitmap: 1: full, 2: disable agg TX, 4: disable agg RX, 8 enable agg TX"); +module_param_named(amsdu_size, iwlwifi_mod_params.amsdu_size, int, 0444); +MODULE_PARM_DESC(amsdu_size, + "amsdu size 0: 12K for multi Rx queue devices, 2K for AX210 devices, " + "4K for other devices 1:4K 2:8K 3:12K (16K buffers) 4: 2K (default 0)"); +module_param_named(fw_restart, iwlwifi_mod_params.fw_restart, bool, 0444); +MODULE_PARM_DESC(fw_restart, "restart firmware in case of error (default true)"); + +module_param_named(nvm_file, iwlwifi_mod_params.nvm_file, charp, 0444); +MODULE_PARM_DESC(nvm_file, "NVM file name"); + +module_param_named(uapsd_disable, iwlwifi_mod_params.uapsd_disable, uint, 0644); +MODULE_PARM_DESC(uapsd_disable, + "disable U-APSD functionality bitmap 1: BSS 2: P2P Client (default: 3)"); + +module_param_named(enable_ini, iwlwifi_mod_params.enable_ini, uint, 0444); +MODULE_PARM_DESC(enable_ini, + "0:disable, 1-15:FW_DBG_PRESET Values, 16:enabled without preset value defined," + "Debug INI TLV FW debug infrastructure (default: 16)"); + +/* + * set bt_coex_active to true, uCode will do kill/defer + * every time the priority line is asserted (BT is sending signals on the + * priority line in the PCIx). + * set bt_coex_active to false, uCode will ignore the BT activity and + * perform the normal operation + * + * User might experience transmit issue on some platform due to WiFi/BT + * co-exist problem. The possible behaviors are: + * Able to scan and finding all the available AP + * Not able to associate with any AP + * On those platforms, WiFi communication can be restored by set + * "bt_coex_active" module parameter to "false" + * + * default: bt_coex_active = true (BT_COEX_ENABLE) + */ +module_param_named(bt_coex_active, iwlwifi_mod_params.bt_coex_active, + bool, 0444); +MODULE_PARM_DESC(bt_coex_active, "enable wifi/bt co-exist (default: enable)"); + +module_param_named(led_mode, iwlwifi_mod_params.led_mode, int, 0444); +MODULE_PARM_DESC(led_mode, "0=system default, " + "1=On(RF On)/Off(RF Off), 2=blinking, 3=Off (default: 0)"); + +module_param_named(power_save, iwlwifi_mod_params.power_save, bool, 0444); +MODULE_PARM_DESC(power_save, + "enable WiFi power management (default: disable)"); + +module_param_named(power_level, iwlwifi_mod_params.power_level, int, 0444); +MODULE_PARM_DESC(power_level, + "default power save level (range from 1 - 5, default: 1)"); + +module_param_named(disable_11ac, iwlwifi_mod_params.disable_11ac, bool, 0444); +MODULE_PARM_DESC(disable_11ac, "Disable VHT capabilities (default: false)"); + +module_param_named(remove_when_gone, + iwlwifi_mod_params.remove_when_gone, bool, + 0444); +MODULE_PARM_DESC(remove_when_gone, + "Remove dev from PCIe bus if it is deemed inaccessible (default: false)"); + +module_param_named(disable_11ax, iwlwifi_mod_params.disable_11ax, bool, + S_IRUGO); +MODULE_PARM_DESC(disable_11ax, "Disable HE capabilities (default: false)"); diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-drv.h b/drivers/net/wireless/intel/iwlwifi/iwl-drv.h new file mode 100644 index 000000000..80073f973 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-drv.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2020-2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + */ +#ifndef __iwl_drv_h__ +#define __iwl_drv_h__ +#include <linux/export.h> + +/* for all modules */ +#define DRV_NAME "iwlwifi" + +/* radio config bits (actual values from NVM definition) */ +#define NVM_RF_CFG_DASH_MSK(x) (x & 0x3) /* bits 0-1 */ +#define NVM_RF_CFG_STEP_MSK(x) ((x >> 2) & 0x3) /* bits 2-3 */ +#define NVM_RF_CFG_TYPE_MSK(x) ((x >> 4) & 0x3) /* bits 4-5 */ +#define NVM_RF_CFG_PNUM_MSK(x) ((x >> 6) & 0x3) /* bits 6-7 */ +#define NVM_RF_CFG_TX_ANT_MSK(x) ((x >> 8) & 0xF) /* bits 8-11 */ +#define NVM_RF_CFG_RX_ANT_MSK(x) ((x >> 12) & 0xF) /* bits 12-15 */ + +#define EXT_NVM_RF_CFG_FLAVOR_MSK(x) ((x) & 0xF) +#define EXT_NVM_RF_CFG_DASH_MSK(x) (((x) >> 4) & 0xF) +#define EXT_NVM_RF_CFG_STEP_MSK(x) (((x) >> 8) & 0xF) +#define EXT_NVM_RF_CFG_TYPE_MSK(x) (((x) >> 12) & 0xFFF) +#define EXT_NVM_RF_CFG_TX_ANT_MSK(x) (((x) >> 24) & 0xF) +#define EXT_NVM_RF_CFG_RX_ANT_MSK(x) (((x) >> 28) & 0xF) + +/** + * DOC: Driver system flows - drv component + * + * This component implements the system flows such as bus enumeration, bus + * removal. Bus dependent parts of system flows (such as iwl_pci_probe) are in + * bus specific files (transport files). This is the code that is common among + * different buses. + * + * This component is also in charge of managing the several implementations of + * the wifi flows: it will allow to have several fw API implementation. These + * different implementations will differ in the way they implement mac80211's + * handlers too. + + * The init flow wrt to the drv component looks like this: + * 1) The bus specific component is called from module_init + * 2) The bus specific component registers the bus driver + * 3) The bus driver calls the probe function + * 4) The bus specific component configures the bus + * 5) The bus specific component calls to the drv bus agnostic part + * (iwl_drv_start) + * 6) iwl_drv_start fetches the fw ASYNC, iwl_req_fw_callback + * 7) iwl_req_fw_callback parses the fw file + * 8) iwl_req_fw_callback starts the wifi implementation to matches the fw + */ + +struct iwl_drv; +struct iwl_trans; +struct iwl_cfg; +/** + * iwl_drv_start - start the drv + * + * @trans_ops: the ops of the transport + * + * starts the driver: fetches the firmware. This should be called by bus + * specific system flows implementations. For example, the bus specific probe + * function should do bus related operations only, and then call to this + * function. It returns the driver object or %NULL if an error occurred. + */ +struct iwl_drv *iwl_drv_start(struct iwl_trans *trans); + +/** + * iwl_drv_stop - stop the drv + * + * @drv: + * + * Stop the driver. This should be called by bus specific system flows + * implementations. For example, the bus specific remove function should first + * call this function and then do the bus related operations only. + */ +void iwl_drv_stop(struct iwl_drv *drv); + +/* + * exported symbol management + * + * The driver can be split into multiple modules, in which case some symbols + * must be exported for the sub-modules. However, if it's not split and + * everything is built-in, then we can avoid that. + */ +#ifdef CONFIG_IWLWIFI_OPMODE_MODULAR +#define IWL_EXPORT_SYMBOL(sym) EXPORT_SYMBOL_NS_GPL(sym, IWLWIFI) +#else +#define IWL_EXPORT_SYMBOL(sym) +#endif + +/* max retry for init flow */ +#define IWL_MAX_INIT_RETRY 2 + +#endif /* __iwl_drv_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-eeprom-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-eeprom-parse.c new file mode 100644 index 000000000..d7a7835b9 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-eeprom-parse.c @@ -0,0 +1,879 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2018-2020 Intel Corporation + * Copyright (C) 2015 Intel Mobile Communications GmbH + */ +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/export.h> +#include "iwl-drv.h" +#include "iwl-modparams.h" +#include "iwl-eeprom-parse.h" + +#if IS_ENABLED(CONFIG_IWLDVM) +/* EEPROM offset definitions */ + +/* indirect access definitions */ +#define ADDRESS_MSK 0x0000FFFF +#define INDIRECT_TYPE_MSK 0x000F0000 +#define INDIRECT_HOST 0x00010000 +#define INDIRECT_GENERAL 0x00020000 +#define INDIRECT_REGULATORY 0x00030000 +#define INDIRECT_CALIBRATION 0x00040000 +#define INDIRECT_PROCESS_ADJST 0x00050000 +#define INDIRECT_OTHERS 0x00060000 +#define INDIRECT_TXP_LIMIT 0x00070000 +#define INDIRECT_TXP_LIMIT_SIZE 0x00080000 +#define INDIRECT_ADDRESS 0x00100000 + +/* corresponding link offsets in EEPROM */ +#define EEPROM_LINK_HOST (2*0x64) +#define EEPROM_LINK_GENERAL (2*0x65) +#define EEPROM_LINK_REGULATORY (2*0x66) +#define EEPROM_LINK_CALIBRATION (2*0x67) +#define EEPROM_LINK_PROCESS_ADJST (2*0x68) +#define EEPROM_LINK_OTHERS (2*0x69) +#define EEPROM_LINK_TXP_LIMIT (2*0x6a) +#define EEPROM_LINK_TXP_LIMIT_SIZE (2*0x6b) + +/* General */ +#define EEPROM_DEVICE_ID (2*0x08) /* 2 bytes */ +#define EEPROM_SUBSYSTEM_ID (2*0x0A) /* 2 bytes */ +#define EEPROM_MAC_ADDRESS (2*0x15) /* 6 bytes */ +#define EEPROM_BOARD_REVISION (2*0x35) /* 2 bytes */ +#define EEPROM_BOARD_PBA_NUMBER (2*0x3B+1) /* 9 bytes */ +#define EEPROM_VERSION (2*0x44) /* 2 bytes */ +#define EEPROM_SKU_CAP (2*0x45) /* 2 bytes */ +#define EEPROM_OEM_MODE (2*0x46) /* 2 bytes */ +#define EEPROM_RADIO_CONFIG (2*0x48) /* 2 bytes */ +#define EEPROM_NUM_MAC_ADDRESS (2*0x4C) /* 2 bytes */ + +/* calibration */ +struct iwl_eeprom_calib_hdr { + u8 version; + u8 pa_type; + __le16 voltage; +} __packed; + +#define EEPROM_CALIB_ALL (INDIRECT_ADDRESS | INDIRECT_CALIBRATION) +#define EEPROM_XTAL ((2*0x128) | EEPROM_CALIB_ALL) + +/* temperature */ +#define EEPROM_KELVIN_TEMPERATURE ((2*0x12A) | EEPROM_CALIB_ALL) +#define EEPROM_RAW_TEMPERATURE ((2*0x12B) | EEPROM_CALIB_ALL) + +/* SKU Capabilities (actual values from EEPROM definition) */ +enum eeprom_sku_bits { + EEPROM_SKU_CAP_BAND_24GHZ = BIT(4), + EEPROM_SKU_CAP_BAND_52GHZ = BIT(5), + EEPROM_SKU_CAP_11N_ENABLE = BIT(6), + EEPROM_SKU_CAP_AMT_ENABLE = BIT(7), + EEPROM_SKU_CAP_IPAN_ENABLE = BIT(8) +}; + +/* radio config bits (actual values from EEPROM definition) */ +#define EEPROM_RF_CFG_TYPE_MSK(x) (x & 0x3) /* bits 0-1 */ +#define EEPROM_RF_CFG_STEP_MSK(x) ((x >> 2) & 0x3) /* bits 2-3 */ +#define EEPROM_RF_CFG_DASH_MSK(x) ((x >> 4) & 0x3) /* bits 4-5 */ +#define EEPROM_RF_CFG_PNUM_MSK(x) ((x >> 6) & 0x3) /* bits 6-7 */ +#define EEPROM_RF_CFG_TX_ANT_MSK(x) ((x >> 8) & 0xF) /* bits 8-11 */ +#define EEPROM_RF_CFG_RX_ANT_MSK(x) ((x >> 12) & 0xF) /* bits 12-15 */ + + +/* + * EEPROM bands + * These are the channel numbers from each band in the order + * that they are stored in the EEPROM band information. Note + * that EEPROM bands aren't the same as mac80211 bands, and + * there are even special "ht40 bands" in the EEPROM. + */ +static const u8 iwl_eeprom_band_1[14] = { /* 2.4 GHz */ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 +}; + +static const u8 iwl_eeprom_band_2[] = { /* 4915-5080MHz */ + 183, 184, 185, 187, 188, 189, 192, 196, 7, 8, 11, 12, 16 +}; + +static const u8 iwl_eeprom_band_3[] = { /* 5170-5320MHz */ + 34, 36, 38, 40, 42, 44, 46, 48, 52, 56, 60, 64 +}; + +static const u8 iwl_eeprom_band_4[] = { /* 5500-5700MHz */ + 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140 +}; + +static const u8 iwl_eeprom_band_5[] = { /* 5725-5825MHz */ + 145, 149, 153, 157, 161, 165 +}; + +static const u8 iwl_eeprom_band_6[] = { /* 2.4 ht40 channel */ + 1, 2, 3, 4, 5, 6, 7 +}; + +static const u8 iwl_eeprom_band_7[] = { /* 5.2 ht40 channel */ + 36, 44, 52, 60, 100, 108, 116, 124, 132, 149, 157 +}; + +#define IWL_NUM_CHANNELS (ARRAY_SIZE(iwl_eeprom_band_1) + \ + ARRAY_SIZE(iwl_eeprom_band_2) + \ + ARRAY_SIZE(iwl_eeprom_band_3) + \ + ARRAY_SIZE(iwl_eeprom_band_4) + \ + ARRAY_SIZE(iwl_eeprom_band_5)) + +/* rate data (static) */ +static struct ieee80211_rate iwl_cfg80211_rates[] = { + { .bitrate = 1 * 10, .hw_value = 0, .hw_value_short = 0, }, + { .bitrate = 2 * 10, .hw_value = 1, .hw_value_short = 1, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 5.5 * 10, .hw_value = 2, .hw_value_short = 2, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 11 * 10, .hw_value = 3, .hw_value_short = 3, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 6 * 10, .hw_value = 4, .hw_value_short = 4, }, + { .bitrate = 9 * 10, .hw_value = 5, .hw_value_short = 5, }, + { .bitrate = 12 * 10, .hw_value = 6, .hw_value_short = 6, }, + { .bitrate = 18 * 10, .hw_value = 7, .hw_value_short = 7, }, + { .bitrate = 24 * 10, .hw_value = 8, .hw_value_short = 8, }, + { .bitrate = 36 * 10, .hw_value = 9, .hw_value_short = 9, }, + { .bitrate = 48 * 10, .hw_value = 10, .hw_value_short = 10, }, + { .bitrate = 54 * 10, .hw_value = 11, .hw_value_short = 11, }, +}; +#define RATES_24_OFFS 0 +#define N_RATES_24 ARRAY_SIZE(iwl_cfg80211_rates) +#define RATES_52_OFFS 4 +#define N_RATES_52 (N_RATES_24 - RATES_52_OFFS) + +/* EEPROM reading functions */ + +static u16 iwl_eeprom_query16(const u8 *eeprom, size_t eeprom_size, int offset) +{ + if (WARN_ON(offset + sizeof(u16) > eeprom_size)) + return 0; + return le16_to_cpup((__le16 *)(eeprom + offset)); +} + +static u32 eeprom_indirect_address(const u8 *eeprom, size_t eeprom_size, + u32 address) +{ + u16 offset = 0; + + if ((address & INDIRECT_ADDRESS) == 0) + return address; + + switch (address & INDIRECT_TYPE_MSK) { + case INDIRECT_HOST: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_HOST); + break; + case INDIRECT_GENERAL: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_GENERAL); + break; + case INDIRECT_REGULATORY: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_REGULATORY); + break; + case INDIRECT_TXP_LIMIT: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_TXP_LIMIT); + break; + case INDIRECT_TXP_LIMIT_SIZE: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_TXP_LIMIT_SIZE); + break; + case INDIRECT_CALIBRATION: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_CALIBRATION); + break; + case INDIRECT_PROCESS_ADJST: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_PROCESS_ADJST); + break; + case INDIRECT_OTHERS: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_OTHERS); + break; + default: + WARN_ON(1); + break; + } + + /* translate the offset from words to byte */ + return (address & ADDRESS_MSK) + (offset << 1); +} + +static const u8 *iwl_eeprom_query_addr(const u8 *eeprom, size_t eeprom_size, + u32 offset) +{ + u32 address = eeprom_indirect_address(eeprom, eeprom_size, offset); + + if (WARN_ON(address >= eeprom_size)) + return NULL; + + return &eeprom[address]; +} + +static int iwl_eeprom_read_calib(const u8 *eeprom, size_t eeprom_size, + struct iwl_nvm_data *data) +{ + struct iwl_eeprom_calib_hdr *hdr; + + hdr = (void *)iwl_eeprom_query_addr(eeprom, eeprom_size, + EEPROM_CALIB_ALL); + if (!hdr) + return -ENODATA; + data->calib_version = hdr->version; + data->calib_voltage = hdr->voltage; + + return 0; +} + +/** + * enum iwl_eeprom_channel_flags - channel flags in EEPROM + * @EEPROM_CHANNEL_VALID: channel is usable for this SKU/geo + * @EEPROM_CHANNEL_IBSS: usable as an IBSS channel + * @EEPROM_CHANNEL_ACTIVE: active scanning allowed + * @EEPROM_CHANNEL_RADAR: radar detection required + * @EEPROM_CHANNEL_WIDE: 20 MHz channel okay (?) + * @EEPROM_CHANNEL_DFS: dynamic freq selection candidate + */ +enum iwl_eeprom_channel_flags { + EEPROM_CHANNEL_VALID = BIT(0), + EEPROM_CHANNEL_IBSS = BIT(1), + EEPROM_CHANNEL_ACTIVE = BIT(3), + EEPROM_CHANNEL_RADAR = BIT(4), + EEPROM_CHANNEL_WIDE = BIT(5), + EEPROM_CHANNEL_DFS = BIT(7), +}; + +/** + * struct iwl_eeprom_channel - EEPROM channel data + * @flags: %EEPROM_CHANNEL_* flags + * @max_power_avg: max power (in dBm) on this channel, at most 31 dBm + */ +struct iwl_eeprom_channel { + u8 flags; + s8 max_power_avg; +} __packed; + + +enum iwl_eeprom_enhanced_txpwr_flags { + IWL_EEPROM_ENH_TXP_FL_VALID = BIT(0), + IWL_EEPROM_ENH_TXP_FL_BAND_52G = BIT(1), + IWL_EEPROM_ENH_TXP_FL_OFDM = BIT(2), + IWL_EEPROM_ENH_TXP_FL_40MHZ = BIT(3), + IWL_EEPROM_ENH_TXP_FL_HT_AP = BIT(4), + IWL_EEPROM_ENH_TXP_FL_RES1 = BIT(5), + IWL_EEPROM_ENH_TXP_FL_RES2 = BIT(6), + IWL_EEPROM_ENH_TXP_FL_COMMON_TYPE = BIT(7), +}; + +/** + * struct iwl_eeprom_enhanced_txpwr + * @flags: entry flags + * @channel: channel number + * @chain_a_max: chain a max power in 1/2 dBm + * @chain_b_max: chain b max power in 1/2 dBm + * @chain_c_max: chain c max power in 1/2 dBm + * @delta_20_in_40: 20-in-40 deltas (hi/lo) + * @mimo2_max: mimo2 max power in 1/2 dBm + * @mimo3_max: mimo3 max power in 1/2 dBm + * + * This structure presents the enhanced regulatory tx power limit layout + * in an EEPROM image. + */ +struct iwl_eeprom_enhanced_txpwr { + u8 flags; + u8 channel; + s8 chain_a_max; + s8 chain_b_max; + s8 chain_c_max; + u8 delta_20_in_40; + s8 mimo2_max; + s8 mimo3_max; +} __packed; + +static s8 iwl_get_max_txpwr_half_dbm(const struct iwl_nvm_data *data, + struct iwl_eeprom_enhanced_txpwr *txp) +{ + s8 result = 0; /* (.5 dBm) */ + + /* Take the highest tx power from any valid chains */ + if (data->valid_tx_ant & ANT_A && txp->chain_a_max > result) + result = txp->chain_a_max; + + if (data->valid_tx_ant & ANT_B && txp->chain_b_max > result) + result = txp->chain_b_max; + + if (data->valid_tx_ant & ANT_C && txp->chain_c_max > result) + result = txp->chain_c_max; + + if ((data->valid_tx_ant == ANT_AB || + data->valid_tx_ant == ANT_BC || + data->valid_tx_ant == ANT_AC) && txp->mimo2_max > result) + result = txp->mimo2_max; + + if (data->valid_tx_ant == ANT_ABC && txp->mimo3_max > result) + result = txp->mimo3_max; + + return result; +} + +#define EEPROM_TXP_OFFS (0x00 | INDIRECT_ADDRESS | INDIRECT_TXP_LIMIT) +#define EEPROM_TXP_ENTRY_LEN sizeof(struct iwl_eeprom_enhanced_txpwr) +#define EEPROM_TXP_SZ_OFFS (0x00 | INDIRECT_ADDRESS | INDIRECT_TXP_LIMIT_SIZE) + +#define TXP_CHECK_AND_PRINT(x) \ + ((txp->flags & IWL_EEPROM_ENH_TXP_FL_##x) ? # x " " : "") + +static void +iwl_eeprom_enh_txp_read_element(struct iwl_nvm_data *data, + struct iwl_eeprom_enhanced_txpwr *txp, + int n_channels, s8 max_txpower_avg) +{ + int ch_idx; + enum nl80211_band band; + + band = txp->flags & IWL_EEPROM_ENH_TXP_FL_BAND_52G ? + NL80211_BAND_5GHZ : NL80211_BAND_2GHZ; + + for (ch_idx = 0; ch_idx < n_channels; ch_idx++) { + struct ieee80211_channel *chan = &data->channels[ch_idx]; + + /* update matching channel or from common data only */ + if (txp->channel != 0 && chan->hw_value != txp->channel) + continue; + + /* update matching band only */ + if (band != chan->band) + continue; + + if (chan->max_power < max_txpower_avg && + !(txp->flags & IWL_EEPROM_ENH_TXP_FL_40MHZ)) + chan->max_power = max_txpower_avg; + } +} + +static void iwl_eeprom_enhanced_txpower(struct device *dev, + struct iwl_nvm_data *data, + const u8 *eeprom, size_t eeprom_size, + int n_channels) +{ + struct iwl_eeprom_enhanced_txpwr *txp_array, *txp; + int idx, entries; + __le16 *txp_len; + s8 max_txp_avg_halfdbm; + + BUILD_BUG_ON(sizeof(struct iwl_eeprom_enhanced_txpwr) != 8); + + /* the length is in 16-bit words, but we want entries */ + txp_len = (__le16 *)iwl_eeprom_query_addr(eeprom, eeprom_size, + EEPROM_TXP_SZ_OFFS); + entries = le16_to_cpup(txp_len) * 2 / EEPROM_TXP_ENTRY_LEN; + + txp_array = (void *)iwl_eeprom_query_addr(eeprom, eeprom_size, + EEPROM_TXP_OFFS); + + for (idx = 0; idx < entries; idx++) { + txp = &txp_array[idx]; + /* skip invalid entries */ + if (!(txp->flags & IWL_EEPROM_ENH_TXP_FL_VALID)) + continue; + + IWL_DEBUG_EEPROM(dev, "%s %d:\t %s%s%s%s%s%s%s%s (0x%02x)\n", + (txp->channel && (txp->flags & + IWL_EEPROM_ENH_TXP_FL_COMMON_TYPE)) ? + "Common " : (txp->channel) ? + "Channel" : "Common", + (txp->channel), + TXP_CHECK_AND_PRINT(VALID), + TXP_CHECK_AND_PRINT(BAND_52G), + TXP_CHECK_AND_PRINT(OFDM), + TXP_CHECK_AND_PRINT(40MHZ), + TXP_CHECK_AND_PRINT(HT_AP), + TXP_CHECK_AND_PRINT(RES1), + TXP_CHECK_AND_PRINT(RES2), + TXP_CHECK_AND_PRINT(COMMON_TYPE), + txp->flags); + IWL_DEBUG_EEPROM(dev, + "\t\t chain_A: %d chain_B: %d chain_C: %d\n", + txp->chain_a_max, txp->chain_b_max, + txp->chain_c_max); + IWL_DEBUG_EEPROM(dev, + "\t\t MIMO2: %d MIMO3: %d High 20_on_40: 0x%02x Low 20_on_40: 0x%02x\n", + txp->mimo2_max, txp->mimo3_max, + ((txp->delta_20_in_40 & 0xf0) >> 4), + (txp->delta_20_in_40 & 0x0f)); + + max_txp_avg_halfdbm = iwl_get_max_txpwr_half_dbm(data, txp); + + iwl_eeprom_enh_txp_read_element(data, txp, n_channels, + DIV_ROUND_UP(max_txp_avg_halfdbm, 2)); + + if (max_txp_avg_halfdbm > data->max_tx_pwr_half_dbm) + data->max_tx_pwr_half_dbm = max_txp_avg_halfdbm; + } +} + +static void iwl_init_band_reference(const struct iwl_cfg *cfg, + const u8 *eeprom, size_t eeprom_size, + int eeprom_band, int *eeprom_ch_count, + const struct iwl_eeprom_channel **ch_info, + const u8 **eeprom_ch_array) +{ + u32 offset = cfg->eeprom_params->regulatory_bands[eeprom_band - 1]; + + offset |= INDIRECT_ADDRESS | INDIRECT_REGULATORY; + + *ch_info = (void *)iwl_eeprom_query_addr(eeprom, eeprom_size, offset); + + switch (eeprom_band) { + case 1: /* 2.4GHz band */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_1); + *eeprom_ch_array = iwl_eeprom_band_1; + break; + case 2: /* 4.9GHz band */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_2); + *eeprom_ch_array = iwl_eeprom_band_2; + break; + case 3: /* 5.2GHz band */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_3); + *eeprom_ch_array = iwl_eeprom_band_3; + break; + case 4: /* 5.5GHz band */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_4); + *eeprom_ch_array = iwl_eeprom_band_4; + break; + case 5: /* 5.7GHz band */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_5); + *eeprom_ch_array = iwl_eeprom_band_5; + break; + case 6: /* 2.4GHz ht40 channels */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_6); + *eeprom_ch_array = iwl_eeprom_band_6; + break; + case 7: /* 5 GHz ht40 channels */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_7); + *eeprom_ch_array = iwl_eeprom_band_7; + break; + default: + *eeprom_ch_count = 0; + *eeprom_ch_array = NULL; + WARN_ON(1); + } +} + +#define CHECK_AND_PRINT(x) \ + ((eeprom_ch->flags & EEPROM_CHANNEL_##x) ? # x " " : "") + +static void iwl_mod_ht40_chan_info(struct device *dev, + struct iwl_nvm_data *data, int n_channels, + enum nl80211_band band, u16 channel, + const struct iwl_eeprom_channel *eeprom_ch, + u8 clear_ht40_extension_channel) +{ + struct ieee80211_channel *chan = NULL; + int i; + + for (i = 0; i < n_channels; i++) { + if (data->channels[i].band != band) + continue; + if (data->channels[i].hw_value != channel) + continue; + chan = &data->channels[i]; + break; + } + + if (!chan) + return; + + IWL_DEBUG_EEPROM(dev, + "HT40 Ch. %d [%sGHz] %s%s%s%s%s(0x%02x %ddBm): Ad-Hoc %ssupported\n", + channel, + band == NL80211_BAND_5GHZ ? "5.2" : "2.4", + CHECK_AND_PRINT(IBSS), + CHECK_AND_PRINT(ACTIVE), + CHECK_AND_PRINT(RADAR), + CHECK_AND_PRINT(WIDE), + CHECK_AND_PRINT(DFS), + eeprom_ch->flags, + eeprom_ch->max_power_avg, + ((eeprom_ch->flags & EEPROM_CHANNEL_IBSS) && + !(eeprom_ch->flags & EEPROM_CHANNEL_RADAR)) ? "" + : "not "); + + if (eeprom_ch->flags & EEPROM_CHANNEL_VALID) + chan->flags &= ~clear_ht40_extension_channel; +} + +#define CHECK_AND_PRINT_I(x) \ + ((eeprom_ch_info[ch_idx].flags & EEPROM_CHANNEL_##x) ? # x " " : "") + +static int iwl_init_channel_map(struct device *dev, const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, + const u8 *eeprom, size_t eeprom_size) +{ + int band, ch_idx; + const struct iwl_eeprom_channel *eeprom_ch_info; + const u8 *eeprom_ch_array; + int eeprom_ch_count; + int n_channels = 0; + + /* + * Loop through the 5 EEPROM bands and add them to the parse list + */ + for (band = 1; band <= 5; band++) { + struct ieee80211_channel *channel; + + iwl_init_band_reference(cfg, eeprom, eeprom_size, band, + &eeprom_ch_count, &eeprom_ch_info, + &eeprom_ch_array); + + /* Loop through each band adding each of the channels */ + for (ch_idx = 0; ch_idx < eeprom_ch_count; ch_idx++) { + const struct iwl_eeprom_channel *eeprom_ch; + + eeprom_ch = &eeprom_ch_info[ch_idx]; + + if (!(eeprom_ch->flags & EEPROM_CHANNEL_VALID)) { + IWL_DEBUG_EEPROM(dev, + "Ch. %d Flags %x [%sGHz] - No traffic\n", + eeprom_ch_array[ch_idx], + eeprom_ch_info[ch_idx].flags, + (band != 1) ? "5.2" : "2.4"); + continue; + } + + channel = &data->channels[n_channels]; + n_channels++; + + channel->hw_value = eeprom_ch_array[ch_idx]; + channel->band = (band == 1) ? NL80211_BAND_2GHZ + : NL80211_BAND_5GHZ; + channel->center_freq = + ieee80211_channel_to_frequency( + channel->hw_value, channel->band); + + /* set no-HT40, will enable as appropriate later */ + channel->flags = IEEE80211_CHAN_NO_HT40; + + if (!(eeprom_ch->flags & EEPROM_CHANNEL_IBSS)) + channel->flags |= IEEE80211_CHAN_NO_IR; + + if (!(eeprom_ch->flags & EEPROM_CHANNEL_ACTIVE)) + channel->flags |= IEEE80211_CHAN_NO_IR; + + if (eeprom_ch->flags & EEPROM_CHANNEL_RADAR) + channel->flags |= IEEE80211_CHAN_RADAR; + + /* Initialize regulatory-based run-time data */ + channel->max_power = + eeprom_ch_info[ch_idx].max_power_avg; + IWL_DEBUG_EEPROM(dev, + "Ch. %d [%sGHz] %s%s%s%s%s%s(0x%02x %ddBm): Ad-Hoc %ssupported\n", + channel->hw_value, + (band != 1) ? "5.2" : "2.4", + CHECK_AND_PRINT_I(VALID), + CHECK_AND_PRINT_I(IBSS), + CHECK_AND_PRINT_I(ACTIVE), + CHECK_AND_PRINT_I(RADAR), + CHECK_AND_PRINT_I(WIDE), + CHECK_AND_PRINT_I(DFS), + eeprom_ch_info[ch_idx].flags, + eeprom_ch_info[ch_idx].max_power_avg, + ((eeprom_ch_info[ch_idx].flags & + EEPROM_CHANNEL_IBSS) && + !(eeprom_ch_info[ch_idx].flags & + EEPROM_CHANNEL_RADAR)) + ? "" : "not "); + } + } + + if (cfg->eeprom_params->enhanced_txpower) { + /* + * for newer device (6000 series and up) + * EEPROM contain enhanced tx power information + * driver need to process addition information + * to determine the max channel tx power limits + */ + iwl_eeprom_enhanced_txpower(dev, data, eeprom, eeprom_size, + n_channels); + } else { + /* All others use data from channel map */ + int i; + + data->max_tx_pwr_half_dbm = -128; + + for (i = 0; i < n_channels; i++) + data->max_tx_pwr_half_dbm = + max_t(s8, data->max_tx_pwr_half_dbm, + data->channels[i].max_power * 2); + } + + /* Check if we do have HT40 channels */ + if (cfg->eeprom_params->regulatory_bands[5] == + EEPROM_REGULATORY_BAND_NO_HT40 && + cfg->eeprom_params->regulatory_bands[6] == + EEPROM_REGULATORY_BAND_NO_HT40) + return n_channels; + + /* Two additional EEPROM bands for 2.4 and 5 GHz HT40 channels */ + for (band = 6; band <= 7; band++) { + enum nl80211_band ieeeband; + + iwl_init_band_reference(cfg, eeprom, eeprom_size, band, + &eeprom_ch_count, &eeprom_ch_info, + &eeprom_ch_array); + + /* EEPROM band 6 is 2.4, band 7 is 5 GHz */ + ieeeband = (band == 6) ? NL80211_BAND_2GHZ + : NL80211_BAND_5GHZ; + + /* Loop through each band adding each of the channels */ + for (ch_idx = 0; ch_idx < eeprom_ch_count; ch_idx++) { + /* Set up driver's info for lower half */ + iwl_mod_ht40_chan_info(dev, data, n_channels, ieeeband, + eeprom_ch_array[ch_idx], + &eeprom_ch_info[ch_idx], + IEEE80211_CHAN_NO_HT40PLUS); + + /* Set up driver's info for upper half */ + iwl_mod_ht40_chan_info(dev, data, n_channels, ieeeband, + eeprom_ch_array[ch_idx] + 4, + &eeprom_ch_info[ch_idx], + IEEE80211_CHAN_NO_HT40MINUS); + } + } + + return n_channels; +} +#endif + +int iwl_init_sband_channels(struct iwl_nvm_data *data, + struct ieee80211_supported_band *sband, + int n_channels, enum nl80211_band band) +{ + struct ieee80211_channel *chan = &data->channels[0]; + int n = 0, idx = 0; + + while (idx < n_channels && chan->band != band) + chan = &data->channels[++idx]; + + sband->channels = &data->channels[idx]; + + while (idx < n_channels && chan->band == band) { + chan = &data->channels[++idx]; + n++; + } + + sband->n_channels = n; + + return n; +} + +#define MAX_BIT_RATE_40_MHZ 150 /* Mbps */ +#define MAX_BIT_RATE_20_MHZ 72 /* Mbps */ + +void iwl_init_ht_hw_capab(struct iwl_trans *trans, + struct iwl_nvm_data *data, + struct ieee80211_sta_ht_cap *ht_info, + enum nl80211_band band, + u8 tx_chains, u8 rx_chains) +{ + const struct iwl_cfg *cfg = trans->cfg; + int max_bit_rate = 0; + + tx_chains = hweight8(tx_chains); + if (cfg->rx_with_siso_diversity) + rx_chains = 1; + else + rx_chains = hweight8(rx_chains); + + if (!(data->sku_cap_11n_enable) || + (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_ALL) || + !cfg->ht_params) { + ht_info->ht_supported = false; + return; + } + + if (data->sku_cap_mimo_disabled) + rx_chains = 1; + + ht_info->ht_supported = true; + ht_info->cap = IEEE80211_HT_CAP_DSSSCCK40; + + if (cfg->ht_params->stbc) { + ht_info->cap |= (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT); + + if (tx_chains > 1) + ht_info->cap |= IEEE80211_HT_CAP_TX_STBC; + } + + if (cfg->ht_params->ldpc) + ht_info->cap |= IEEE80211_HT_CAP_LDPC_CODING; + + if (trans->trans_cfg->mq_rx_supported || + iwlwifi_mod_params.amsdu_size >= IWL_AMSDU_8K) + ht_info->cap |= IEEE80211_HT_CAP_MAX_AMSDU; + + ht_info->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + ht_info->ampdu_density = IEEE80211_HT_MPDU_DENSITY_4; + + ht_info->mcs.rx_mask[0] = 0xFF; + if (rx_chains >= 2) + ht_info->mcs.rx_mask[1] = 0xFF; + if (rx_chains >= 3) + ht_info->mcs.rx_mask[2] = 0xFF; + + if (cfg->ht_params->ht_greenfield_support) + ht_info->cap |= IEEE80211_HT_CAP_GRN_FLD; + ht_info->cap |= IEEE80211_HT_CAP_SGI_20; + + max_bit_rate = MAX_BIT_RATE_20_MHZ; + + if (cfg->ht_params->ht40_bands & BIT(band)) { + ht_info->cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + ht_info->cap |= IEEE80211_HT_CAP_SGI_40; + max_bit_rate = MAX_BIT_RATE_40_MHZ; + } + + /* Highest supported Rx data rate */ + max_bit_rate *= rx_chains; + WARN_ON(max_bit_rate & ~IEEE80211_HT_MCS_RX_HIGHEST_MASK); + ht_info->mcs.rx_highest = cpu_to_le16(max_bit_rate); + + /* Tx MCS capabilities */ + ht_info->mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; + if (tx_chains != rx_chains) { + ht_info->mcs.tx_params |= IEEE80211_HT_MCS_TX_RX_DIFF; + ht_info->mcs.tx_params |= ((tx_chains - 1) << + IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT); + } +} + +#if IS_ENABLED(CONFIG_IWLDVM) +static void iwl_init_sbands(struct iwl_trans *trans, const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, + const u8 *eeprom, size_t eeprom_size) +{ + struct device *dev = trans->dev; + int n_channels = iwl_init_channel_map(dev, cfg, data, + eeprom, eeprom_size); + int n_used = 0; + struct ieee80211_supported_band *sband; + + sband = &data->bands[NL80211_BAND_2GHZ]; + sband->band = NL80211_BAND_2GHZ; + sband->bitrates = &iwl_cfg80211_rates[RATES_24_OFFS]; + sband->n_bitrates = N_RATES_24; + n_used += iwl_init_sband_channels(data, sband, n_channels, + NL80211_BAND_2GHZ); + iwl_init_ht_hw_capab(trans, data, &sband->ht_cap, NL80211_BAND_2GHZ, + data->valid_tx_ant, data->valid_rx_ant); + + sband = &data->bands[NL80211_BAND_5GHZ]; + sband->band = NL80211_BAND_5GHZ; + sband->bitrates = &iwl_cfg80211_rates[RATES_52_OFFS]; + sband->n_bitrates = N_RATES_52; + n_used += iwl_init_sband_channels(data, sband, n_channels, + NL80211_BAND_5GHZ); + iwl_init_ht_hw_capab(trans, data, &sband->ht_cap, NL80211_BAND_5GHZ, + data->valid_tx_ant, data->valid_rx_ant); + + if (n_channels != n_used) + IWL_ERR_DEV(dev, "EEPROM: used only %d of %d channels\n", + n_used, n_channels); +} + +/* EEPROM data functions */ + +struct iwl_nvm_data * +iwl_parse_eeprom_data(struct iwl_trans *trans, const struct iwl_cfg *cfg, + const u8 *eeprom, size_t eeprom_size) +{ + struct iwl_nvm_data *data; + struct device *dev = trans->dev; + const void *tmp; + u16 radio_cfg, sku; + + if (WARN_ON(!cfg || !cfg->eeprom_params)) + return NULL; + + data = kzalloc(struct_size(data, channels, IWL_NUM_CHANNELS), + GFP_KERNEL); + if (!data) + return NULL; + + /* get MAC address(es) */ + tmp = iwl_eeprom_query_addr(eeprom, eeprom_size, EEPROM_MAC_ADDRESS); + if (!tmp) + goto err_free; + memcpy(data->hw_addr, tmp, ETH_ALEN); + data->n_hw_addrs = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_NUM_MAC_ADDRESS); + + if (iwl_eeprom_read_calib(eeprom, eeprom_size, data)) + goto err_free; + + tmp = iwl_eeprom_query_addr(eeprom, eeprom_size, EEPROM_XTAL); + if (!tmp) + goto err_free; + memcpy(data->xtal_calib, tmp, sizeof(data->xtal_calib)); + + tmp = iwl_eeprom_query_addr(eeprom, eeprom_size, + EEPROM_RAW_TEMPERATURE); + if (!tmp) + goto err_free; + data->raw_temperature = *(__le16 *)tmp; + + tmp = iwl_eeprom_query_addr(eeprom, eeprom_size, + EEPROM_KELVIN_TEMPERATURE); + if (!tmp) + goto err_free; + data->kelvin_temperature = *(__le16 *)tmp; + data->kelvin_voltage = *((__le16 *)tmp + 1); + + radio_cfg = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_RADIO_CONFIG); + data->radio_cfg_dash = EEPROM_RF_CFG_DASH_MSK(radio_cfg); + data->radio_cfg_pnum = EEPROM_RF_CFG_PNUM_MSK(radio_cfg); + data->radio_cfg_step = EEPROM_RF_CFG_STEP_MSK(radio_cfg); + data->radio_cfg_type = EEPROM_RF_CFG_TYPE_MSK(radio_cfg); + data->valid_rx_ant = EEPROM_RF_CFG_RX_ANT_MSK(radio_cfg); + data->valid_tx_ant = EEPROM_RF_CFG_TX_ANT_MSK(radio_cfg); + + sku = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_SKU_CAP); + data->sku_cap_11n_enable = sku & EEPROM_SKU_CAP_11N_ENABLE; + data->sku_cap_amt_enable = sku & EEPROM_SKU_CAP_AMT_ENABLE; + data->sku_cap_band_24ghz_enable = sku & EEPROM_SKU_CAP_BAND_24GHZ; + data->sku_cap_band_52ghz_enable = sku & EEPROM_SKU_CAP_BAND_52GHZ; + data->sku_cap_ipan_enable = sku & EEPROM_SKU_CAP_IPAN_ENABLE; + if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_ALL) + data->sku_cap_11n_enable = false; + + data->nvm_version = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_VERSION); + + /* check overrides (some devices have wrong EEPROM) */ + if (cfg->valid_tx_ant) + data->valid_tx_ant = cfg->valid_tx_ant; + if (cfg->valid_rx_ant) + data->valid_rx_ant = cfg->valid_rx_ant; + + if (!data->valid_tx_ant || !data->valid_rx_ant) { + IWL_ERR_DEV(dev, "invalid antennas (0x%x, 0x%x)\n", + data->valid_tx_ant, data->valid_rx_ant); + goto err_free; + } + + iwl_init_sbands(trans, cfg, data, eeprom, eeprom_size); + + return data; + err_free: + kfree(data); + return NULL; +} +IWL_EXPORT_SYMBOL(iwl_parse_eeprom_data); +#endif diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-eeprom-parse.h b/drivers/net/wireless/intel/iwlwifi/iwl-eeprom-parse.h new file mode 100644 index 000000000..0efffb6ee --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-eeprom-parse.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018 Intel Corporation + * Copyright (C) 2015 Intel Mobile Communications GmbH + */ +#ifndef __iwl_eeprom_parse_h__ +#define __iwl_eeprom_parse_h__ + +#include <linux/types.h> +#include <linux/if_ether.h> +#include <net/cfg80211.h> +#include "iwl-trans.h" + +struct iwl_nvm_data { + int n_hw_addrs; + u8 hw_addr[ETH_ALEN]; + + u8 calib_version; + __le16 calib_voltage; + + __le16 raw_temperature; + __le16 kelvin_temperature; + __le16 kelvin_voltage; + __le16 xtal_calib[2]; + + bool sku_cap_band_24ghz_enable; + bool sku_cap_band_52ghz_enable; + bool sku_cap_11n_enable; + bool sku_cap_11ac_enable; + bool sku_cap_11ax_enable; + bool sku_cap_amt_enable; + bool sku_cap_ipan_enable; + bool sku_cap_mimo_disabled; + + u16 radio_cfg_type; + u8 radio_cfg_step; + u8 radio_cfg_dash; + u8 radio_cfg_pnum; + u8 valid_tx_ant, valid_rx_ant; + + u32 nvm_version; + s8 max_tx_pwr_half_dbm; + + bool lar_enabled; + bool vht160_supported; + struct ieee80211_supported_band bands[NUM_NL80211_BANDS]; + + /* + * iftype data for low (2.4 GHz) and high (5 and 6 GHz) bands, + * we can use the same for 5 and 6 GHz bands because they have + * the same data + */ + struct { + struct ieee80211_sband_iftype_data low[2]; + struct ieee80211_sband_iftype_data high[2]; + } iftd; + + struct ieee80211_channel channels[]; +}; + +/** + * iwl_parse_eeprom_data - parse EEPROM data and return values + * + * @dev: device pointer we're parsing for, for debug only + * @cfg: device configuration for parsing and overrides + * @eeprom: the EEPROM data + * @eeprom_size: length of the EEPROM data + * + * This function parses all EEPROM values we need and then + * returns a (newly allocated) struct containing all the + * relevant values for driver use. The struct must be freed + * later with iwl_free_nvm_data(). + */ +struct iwl_nvm_data * +iwl_parse_eeprom_data(struct iwl_trans *trans, const struct iwl_cfg *cfg, + const u8 *eeprom, size_t eeprom_size); + +int iwl_init_sband_channels(struct iwl_nvm_data *data, + struct ieee80211_supported_band *sband, + int n_channels, enum nl80211_band band); + +void iwl_init_ht_hw_capab(struct iwl_trans *trans, + struct iwl_nvm_data *data, + struct ieee80211_sta_ht_cap *ht_info, + enum nl80211_band band, + u8 tx_chains, u8 rx_chains); + +#endif /* __iwl_eeprom_parse_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-eeprom-read.c b/drivers/net/wireless/intel/iwlwifi/iwl-eeprom-read.c new file mode 100644 index 000000000..5f386bb1a --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-eeprom-read.c @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2018-2019, 2021 Intel Corporation + */ +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/export.h> + +#include "iwl-drv.h" +#include "iwl-debug.h" +#include "iwl-eeprom-read.h" +#include "iwl-io.h" +#include "iwl-prph.h" +#include "iwl-csr.h" + +/* + * EEPROM access time values: + * + * Driver initiates EEPROM read by writing byte address << 1 to CSR_EEPROM_REG. + * Driver then polls CSR_EEPROM_REG for CSR_EEPROM_REG_READ_VALID_MSK (0x1). + * When polling, wait 10 uSec between polling loops, up to a maximum 5000 uSec. + * Driver reads 16-bit value from bits 31-16 of CSR_EEPROM_REG. + */ +#define IWL_EEPROM_ACCESS_TIMEOUT 5000 /* uSec */ + +/* + * The device's EEPROM semaphore prevents conflicts between driver and uCode + * when accessing the EEPROM; each access is a series of pulses to/from the + * EEPROM chip, not a single event, so even reads could conflict if they + * weren't arbitrated by the semaphore. + */ +#define IWL_EEPROM_SEM_TIMEOUT 10 /* microseconds */ +#define IWL_EEPROM_SEM_RETRY_LIMIT 1000 /* number of attempts (not time) */ + + +static int iwl_eeprom_acquire_semaphore(struct iwl_trans *trans) +{ + u16 count; + int ret; + + for (count = 0; count < IWL_EEPROM_SEM_RETRY_LIMIT; count++) { + /* Request semaphore */ + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM); + + /* See if we got it */ + ret = iwl_poll_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM, + CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM, + IWL_EEPROM_SEM_TIMEOUT); + if (ret >= 0) { + IWL_DEBUG_EEPROM(trans->dev, + "Acquired semaphore after %d tries.\n", + count+1); + return ret; + } + } + + return ret; +} + +static void iwl_eeprom_release_semaphore(struct iwl_trans *trans) +{ + iwl_clear_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM); +} + +static int iwl_eeprom_verify_signature(struct iwl_trans *trans, bool nvm_is_otp) +{ + u32 gp = iwl_read32(trans, CSR_EEPROM_GP) & CSR_EEPROM_GP_VALID_MSK; + + IWL_DEBUG_EEPROM(trans->dev, "EEPROM signature=0x%08x\n", gp); + + switch (gp) { + case CSR_EEPROM_GP_BAD_SIG_EEP_GOOD_SIG_OTP: + if (!nvm_is_otp) { + IWL_ERR(trans, "EEPROM with bad signature: 0x%08x\n", + gp); + return -ENOENT; + } + return 0; + case CSR_EEPROM_GP_GOOD_SIG_EEP_LESS_THAN_4K: + case CSR_EEPROM_GP_GOOD_SIG_EEP_MORE_THAN_4K: + if (nvm_is_otp) { + IWL_ERR(trans, "OTP with bad signature: 0x%08x\n", gp); + return -ENOENT; + } + return 0; + case CSR_EEPROM_GP_BAD_SIGNATURE_BOTH_EEP_AND_OTP: + default: + IWL_ERR(trans, + "bad EEPROM/OTP signature, type=%s, EEPROM_GP=0x%08x\n", + nvm_is_otp ? "OTP" : "EEPROM", gp); + return -ENOENT; + } +} + +/****************************************************************************** + * + * OTP related functions + * +******************************************************************************/ + +static void iwl_set_otp_access_absolute(struct iwl_trans *trans) +{ + iwl_read32(trans, CSR_OTP_GP_REG); + + iwl_clear_bit(trans, CSR_OTP_GP_REG, + CSR_OTP_GP_REG_OTP_ACCESS_MODE); +} + +static int iwl_nvm_is_otp(struct iwl_trans *trans) +{ + u32 otpgp; + + /* OTP only valid for CP/PP and after */ + switch (trans->hw_rev & CSR_HW_REV_TYPE_MSK) { + case CSR_HW_REV_TYPE_NONE: + IWL_ERR(trans, "Unknown hardware type\n"); + return -EIO; + case CSR_HW_REV_TYPE_5300: + case CSR_HW_REV_TYPE_5350: + case CSR_HW_REV_TYPE_5100: + case CSR_HW_REV_TYPE_5150: + return 0; + default: + otpgp = iwl_read32(trans, CSR_OTP_GP_REG); + if (otpgp & CSR_OTP_GP_REG_DEVICE_SELECT) + return 1; + return 0; + } +} + +static int iwl_init_otp_access(struct iwl_trans *trans) +{ + int ret; + + ret = iwl_finish_nic_init(trans); + if (ret) + return ret; + + iwl_set_bits_prph(trans, APMG_PS_CTRL_REG, + APMG_PS_CTRL_VAL_RESET_REQ); + udelay(5); + iwl_clear_bits_prph(trans, APMG_PS_CTRL_REG, + APMG_PS_CTRL_VAL_RESET_REQ); + + /* + * CSR auto clock gate disable bit - + * this is only applicable for HW with OTP shadow RAM + */ + if (trans->trans_cfg->base_params->shadow_ram_support) + iwl_set_bit(trans, CSR_DBG_LINK_PWR_MGMT_REG, + CSR_RESET_LINK_PWR_MGMT_DISABLED); + + return 0; +} + +static int iwl_read_otp_word(struct iwl_trans *trans, u16 addr, + __le16 *eeprom_data) +{ + int ret = 0; + u32 r; + u32 otpgp; + + iwl_write32(trans, CSR_EEPROM_REG, + CSR_EEPROM_REG_MSK_ADDR & (addr << 1)); + ret = iwl_poll_bit(trans, CSR_EEPROM_REG, + CSR_EEPROM_REG_READ_VALID_MSK, + CSR_EEPROM_REG_READ_VALID_MSK, + IWL_EEPROM_ACCESS_TIMEOUT); + if (ret < 0) { + IWL_ERR(trans, "Time out reading OTP[%d]\n", addr); + return ret; + } + r = iwl_read32(trans, CSR_EEPROM_REG); + /* check for ECC errors: */ + otpgp = iwl_read32(trans, CSR_OTP_GP_REG); + if (otpgp & CSR_OTP_GP_REG_ECC_UNCORR_STATUS_MSK) { + /* stop in this case */ + /* set the uncorrectable OTP ECC bit for acknowledgment */ + iwl_set_bit(trans, CSR_OTP_GP_REG, + CSR_OTP_GP_REG_ECC_UNCORR_STATUS_MSK); + IWL_ERR(trans, "Uncorrectable OTP ECC error, abort OTP read\n"); + return -EINVAL; + } + if (otpgp & CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK) { + /* continue in this case */ + /* set the correctable OTP ECC bit for acknowledgment */ + iwl_set_bit(trans, CSR_OTP_GP_REG, + CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK); + IWL_ERR(trans, "Correctable OTP ECC error, continue read\n"); + } + *eeprom_data = cpu_to_le16(r >> 16); + return 0; +} + +/* + * iwl_is_otp_empty: check for empty OTP + */ +static bool iwl_is_otp_empty(struct iwl_trans *trans) +{ + u16 next_link_addr = 0; + __le16 link_value; + bool is_empty = false; + + /* locate the beginning of OTP link list */ + if (!iwl_read_otp_word(trans, next_link_addr, &link_value)) { + if (!link_value) { + IWL_ERR(trans, "OTP is empty\n"); + is_empty = true; + } + } else { + IWL_ERR(trans, "Unable to read first block of OTP list.\n"); + is_empty = true; + } + + return is_empty; +} + + +/* + * iwl_find_otp_image: find EEPROM image in OTP + * finding the OTP block that contains the EEPROM image. + * the last valid block on the link list (the block _before_ the last block) + * is the block we should read and used to configure the device. + * If all the available OTP blocks are full, the last block will be the block + * we should read and used to configure the device. + * only perform this operation if shadow RAM is disabled + */ +static int iwl_find_otp_image(struct iwl_trans *trans, + u16 *validblockaddr) +{ + u16 next_link_addr = 0, valid_addr; + __le16 link_value = 0; + int usedblocks = 0; + + /* set addressing mode to absolute to traverse the link list */ + iwl_set_otp_access_absolute(trans); + + /* checking for empty OTP or error */ + if (iwl_is_otp_empty(trans)) + return -EINVAL; + + /* + * start traverse link list + * until reach the max number of OTP blocks + * different devices have different number of OTP blocks + */ + do { + /* save current valid block address + * check for more block on the link list + */ + valid_addr = next_link_addr; + next_link_addr = le16_to_cpu(link_value) * sizeof(u16); + IWL_DEBUG_EEPROM(trans->dev, "OTP blocks %d addr 0x%x\n", + usedblocks, next_link_addr); + if (iwl_read_otp_word(trans, next_link_addr, &link_value)) + return -EINVAL; + if (!link_value) { + /* + * reach the end of link list, return success and + * set address point to the starting address + * of the image + */ + *validblockaddr = valid_addr; + /* skip first 2 bytes (link list pointer) */ + *validblockaddr += 2; + return 0; + } + /* more in the link list, continue */ + usedblocks++; + } while (usedblocks <= trans->trans_cfg->base_params->max_ll_items); + + /* OTP has no valid blocks */ + IWL_DEBUG_EEPROM(trans->dev, "OTP has no valid blocks\n"); + return -EINVAL; +} + +/* + * iwl_read_eeprom - read EEPROM contents + * + * Load the EEPROM contents from adapter and return it + * and its size. + * + * NOTE: This routine uses the non-debug IO access functions. + */ +int iwl_read_eeprom(struct iwl_trans *trans, u8 **eeprom, size_t *eeprom_size) +{ + __le16 *e; + u32 gp = iwl_read32(trans, CSR_EEPROM_GP); + int sz; + int ret; + u16 addr; + u16 validblockaddr = 0; + u16 cache_addr = 0; + int nvm_is_otp; + + if (!eeprom || !eeprom_size) + return -EINVAL; + + nvm_is_otp = iwl_nvm_is_otp(trans); + if (nvm_is_otp < 0) + return nvm_is_otp; + + sz = trans->trans_cfg->base_params->eeprom_size; + IWL_DEBUG_EEPROM(trans->dev, "NVM size = %d\n", sz); + + e = kmalloc(sz, GFP_KERNEL); + if (!e) + return -ENOMEM; + + ret = iwl_eeprom_verify_signature(trans, nvm_is_otp); + if (ret < 0) { + IWL_ERR(trans, "EEPROM not found, EEPROM_GP=0x%08x\n", gp); + goto err_free; + } + + /* Make sure driver (instead of uCode) is allowed to read EEPROM */ + ret = iwl_eeprom_acquire_semaphore(trans); + if (ret < 0) { + IWL_ERR(trans, "Failed to acquire EEPROM semaphore.\n"); + goto err_free; + } + + if (nvm_is_otp) { + ret = iwl_init_otp_access(trans); + if (ret) { + IWL_ERR(trans, "Failed to initialize OTP access.\n"); + goto err_unlock; + } + + iwl_write32(trans, CSR_EEPROM_GP, + iwl_read32(trans, CSR_EEPROM_GP) & + ~CSR_EEPROM_GP_IF_OWNER_MSK); + + iwl_set_bit(trans, CSR_OTP_GP_REG, + CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK | + CSR_OTP_GP_REG_ECC_UNCORR_STATUS_MSK); + /* traversing the linked list if no shadow ram supported */ + if (!trans->trans_cfg->base_params->shadow_ram_support) { + ret = iwl_find_otp_image(trans, &validblockaddr); + if (ret) + goto err_unlock; + } + for (addr = validblockaddr; addr < validblockaddr + sz; + addr += sizeof(u16)) { + __le16 eeprom_data; + + ret = iwl_read_otp_word(trans, addr, &eeprom_data); + if (ret) + goto err_unlock; + e[cache_addr / 2] = eeprom_data; + cache_addr += sizeof(u16); + } + } else { + /* eeprom is an array of 16bit values */ + for (addr = 0; addr < sz; addr += sizeof(u16)) { + u32 r; + + iwl_write32(trans, CSR_EEPROM_REG, + CSR_EEPROM_REG_MSK_ADDR & (addr << 1)); + + ret = iwl_poll_bit(trans, CSR_EEPROM_REG, + CSR_EEPROM_REG_READ_VALID_MSK, + CSR_EEPROM_REG_READ_VALID_MSK, + IWL_EEPROM_ACCESS_TIMEOUT); + if (ret < 0) { + IWL_ERR(trans, + "Time out reading EEPROM[%d]\n", addr); + goto err_unlock; + } + r = iwl_read32(trans, CSR_EEPROM_REG); + e[addr / 2] = cpu_to_le16(r >> 16); + } + } + + IWL_DEBUG_EEPROM(trans->dev, "NVM Type: %s\n", + nvm_is_otp ? "OTP" : "EEPROM"); + + iwl_eeprom_release_semaphore(trans); + + *eeprom_size = sz; + *eeprom = (u8 *)e; + return 0; + + err_unlock: + iwl_eeprom_release_semaphore(trans); + err_free: + kfree(e); + + return ret; +} +IWL_EXPORT_SYMBOL(iwl_read_eeprom); diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-eeprom-read.h b/drivers/net/wireless/intel/iwlwifi/iwl-eeprom-read.h new file mode 100644 index 000000000..63b8e6c66 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-eeprom-read.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014 Intel Corporation + */ +#ifndef __iwl_eeprom_h__ +#define __iwl_eeprom_h__ + +#include "iwl-trans.h" + +int iwl_read_eeprom(struct iwl_trans *trans, u8 **eeprom, size_t *eeprom_size); + +#endif /* __iwl_eeprom_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-fh.h b/drivers/net/wireless/intel/iwlwifi/iwl-fh.h new file mode 100644 index 000000000..bedd78a47 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-fh.h @@ -0,0 +1,740 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_fh_h__ +#define __iwl_fh_h__ + +#include <linux/types.h> +#include <linux/bitfield.h> + +#include "iwl-trans.h" + +/****************************/ +/* Flow Handler Definitions */ +/****************************/ + +/** + * This I/O area is directly read/writable by driver (e.g. Linux uses writel()) + * Addresses are offsets from device's PCI hardware base address. + */ +#define FH_MEM_LOWER_BOUND (0x1000) +#define FH_MEM_UPPER_BOUND (0x2000) +#define FH_MEM_LOWER_BOUND_GEN2 (0xa06000) +#define FH_MEM_UPPER_BOUND_GEN2 (0xa08000) + +/** + * Keep-Warm (KW) buffer base address. + * + * Driver must allocate a 4KByte buffer that is for keeping the + * host DRAM powered on (via dummy accesses to DRAM) to maintain low-latency + * DRAM access when doing Txing or Rxing. The dummy accesses prevent host + * from going into a power-savings mode that would cause higher DRAM latency, + * and possible data over/under-runs, before all Tx/Rx is complete. + * + * Driver loads FH_KW_MEM_ADDR_REG with the physical address (bits 35:4) + * of the buffer, which must be 4K aligned. Once this is set up, the device + * automatically invokes keep-warm accesses when normal accesses might not + * be sufficient to maintain fast DRAM response. + * + * Bit fields: + * 31-0: Keep-warm buffer physical base address [35:4], must be 4K aligned + */ +#define FH_KW_MEM_ADDR_REG (FH_MEM_LOWER_BOUND + 0x97C) + + +/** + * TFD Circular Buffers Base (CBBC) addresses + * + * Device has 16 base pointer registers, one for each of 16 host-DRAM-resident + * circular buffers (CBs/queues) containing Transmit Frame Descriptors (TFDs) + * (see struct iwl_tfd_frame). These 16 pointer registers are offset by 0x04 + * bytes from one another. Each TFD circular buffer in DRAM must be 256-byte + * aligned (address bits 0-7 must be 0). + * Later devices have 20 (5000 series) or 30 (higher) queues, but the registers + * for them are in different places. + * + * Bit fields in each pointer register: + * 27-0: TFD CB physical base address [35:8], must be 256-byte aligned + */ +#define FH_MEM_CBBC_0_15_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0x9D0) +#define FH_MEM_CBBC_0_15_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xA10) +#define FH_MEM_CBBC_16_19_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xBF0) +#define FH_MEM_CBBC_16_19_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xC00) +#define FH_MEM_CBBC_20_31_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xB20) +#define FH_MEM_CBBC_20_31_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xB80) +/* 22000 TFD table address, 64 bit */ +#define TFH_TFDQ_CBB_TABLE (0x1C00) + +/* Find TFD CB base pointer for given queue */ +static inline unsigned int FH_MEM_CBBC_QUEUE(struct iwl_trans *trans, + unsigned int chnl) +{ + if (trans->trans_cfg->use_tfh) { + WARN_ON_ONCE(chnl >= 64); + return TFH_TFDQ_CBB_TABLE + 8 * chnl; + } + if (chnl < 16) + return FH_MEM_CBBC_0_15_LOWER_BOUND + 4 * chnl; + if (chnl < 20) + return FH_MEM_CBBC_16_19_LOWER_BOUND + 4 * (chnl - 16); + WARN_ON_ONCE(chnl >= 32); + return FH_MEM_CBBC_20_31_LOWER_BOUND + 4 * (chnl - 20); +} + +/* 22000 configuration registers */ + +/* + * TFH Configuration register. + * + * BIT fields: + * + * Bits 3:0: + * Define the maximum number of pending read requests. + * Maximum configuration value allowed is 0xC + * Bits 9:8: + * Define the maximum transfer size. (64 / 128 / 256) + * Bit 10: + * When bit is set and transfer size is set to 128B, the TFH will enable + * reading chunks of more than 64B only if the read address is aligned to 128B. + * In case of DRAM read address which is not aligned to 128B, the TFH will + * enable transfer size which doesn't cross 64B DRAM address boundary. +*/ +#define TFH_TRANSFER_MODE (0x1F40) +#define TFH_TRANSFER_MAX_PENDING_REQ 0xc +#define TFH_CHUNK_SIZE_128 BIT(8) +#define TFH_CHUNK_SPLIT_MODE BIT(10) +/* + * Defines the offset address in dwords referring from the beginning of the + * Tx CMD which will be updated in DRAM. + * Note that the TFH offset address for Tx CMD update is always referring to + * the start of the TFD first TB. + * In case of a DRAM Tx CMD update the TFH will update PN and Key ID + */ +#define TFH_TXCMD_UPDATE_CFG (0x1F48) +/* + * Controls TX DMA operation + * + * BIT fields: + * + * Bits 31:30: Enable the SRAM DMA channel. + * Turning on bit 31 will kick the SRAM2DRAM DMA. + * Note that the sram2dram may be enabled only after configuring the DRAM and + * SRAM addresses registers and the byte count register. + * Bits 25:24: Defines the interrupt target upon dram2sram transfer done. When + * set to 1 - interrupt is sent to the driver + * Bit 0: Indicates the snoop configuration +*/ +#define TFH_SRV_DMA_CHNL0_CTRL (0x1F60) +#define TFH_SRV_DMA_SNOOP BIT(0) +#define TFH_SRV_DMA_TO_DRIVER BIT(24) +#define TFH_SRV_DMA_START BIT(31) + +/* Defines the DMA SRAM write start address to transfer a data block */ +#define TFH_SRV_DMA_CHNL0_SRAM_ADDR (0x1F64) + +/* Defines the 64bits DRAM start address to read the DMA data block from */ +#define TFH_SRV_DMA_CHNL0_DRAM_ADDR (0x1F68) + +/* + * Defines the number of bytes to transfer from DRAM to SRAM. + * Note that this register may be configured with non-dword aligned size. + */ +#define TFH_SRV_DMA_CHNL0_BC (0x1F70) + +/** + * Rx SRAM Control and Status Registers (RSCSR) + * + * These registers provide handshake between driver and device for the Rx queue + * (this queue handles *all* command responses, notifications, Rx data, etc. + * sent from uCode to host driver). Unlike Tx, there is only one Rx + * queue, and only one Rx DMA/FIFO channel. Also unlike Tx, which can + * concatenate up to 20 DRAM buffers to form a Tx frame, each Receive Buffer + * Descriptor (RBD) points to only one Rx Buffer (RB); there is a 1:1 + * mapping between RBDs and RBs. + * + * Driver must allocate host DRAM memory for the following, and set the + * physical address of each into device registers: + * + * 1) Receive Buffer Descriptor (RBD) circular buffer (CB), typically with 256 + * entries (although any power of 2, up to 4096, is selectable by driver). + * Each entry (1 dword) points to a receive buffer (RB) of consistent size + * (typically 4K, although 8K or 16K are also selectable by driver). + * Driver sets up RB size and number of RBDs in the CB via Rx config + * register FH_MEM_RCSR_CHNL0_CONFIG_REG. + * + * Bit fields within one RBD: + * 27-0: Receive Buffer physical address bits [35:8], 256-byte aligned + * + * Driver sets physical address [35:8] of base of RBD circular buffer + * into FH_RSCSR_CHNL0_RBDCB_BASE_REG [27:0]. + * + * 2) Rx status buffer, 8 bytes, in which uCode indicates which Rx Buffers + * (RBs) have been filled, via a "write pointer", actually the index of + * the RB's corresponding RBD within the circular buffer. Driver sets + * physical address [35:4] into FH_RSCSR_CHNL0_STTS_WPTR_REG [31:0]. + * + * Bit fields in lower dword of Rx status buffer (upper dword not used + * by driver: + * 31-12: Not used by driver + * 11- 0: Index of last filled Rx buffer descriptor + * (device writes, driver reads this value) + * + * As the driver prepares Receive Buffers (RBs) for device to fill, driver must + * enter pointers to these RBs into contiguous RBD circular buffer entries, + * and update the device's "write" index register, + * FH_RSCSR_CHNL0_RBDCB_WPTR_REG. + * + * This "write" index corresponds to the *next* RBD that the driver will make + * available, i.e. one RBD past the tail of the ready-to-fill RBDs within + * the circular buffer. This value should initially be 0 (before preparing any + * RBs), should be 8 after preparing the first 8 RBs (for example), and must + * wrap back to 0 at the end of the circular buffer (but don't wrap before + * "read" index has advanced past 1! See below). + * NOTE: DEVICE EXPECTS THE WRITE INDEX TO BE INCREMENTED IN MULTIPLES OF 8. + * + * As the device fills RBs (referenced from contiguous RBDs within the circular + * buffer), it updates the Rx status buffer in host DRAM, 2) described above, + * to tell the driver the index of the latest filled RBD. The driver must + * read this "read" index from DRAM after receiving an Rx interrupt from device + * + * The driver must also internally keep track of a third index, which is the + * next RBD to process. When receiving an Rx interrupt, driver should process + * all filled but unprocessed RBs up to, but not including, the RB + * corresponding to the "read" index. For example, if "read" index becomes "1", + * driver may process the RB pointed to by RBD 0. Depending on volume of + * traffic, there may be many RBs to process. + * + * If read index == write index, device thinks there is no room to put new data. + * Due to this, the maximum number of filled RBs is 255, instead of 256. To + * be safe, make sure that there is a gap of at least 2 RBDs between "write" + * and "read" indexes; that is, make sure that there are no more than 254 + * buffers waiting to be filled. + */ +#define FH_MEM_RSCSR_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xBC0) +#define FH_MEM_RSCSR_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xC00) +#define FH_MEM_RSCSR_CHNL0 (FH_MEM_RSCSR_LOWER_BOUND) + +/** + * Physical base address of 8-byte Rx Status buffer. + * Bit fields: + * 31-0: Rx status buffer physical base address [35:4], must 16-byte aligned. + */ +#define FH_RSCSR_CHNL0_STTS_WPTR_REG (FH_MEM_RSCSR_CHNL0) + +/** + * Physical base address of Rx Buffer Descriptor Circular Buffer. + * Bit fields: + * 27-0: RBD CD physical base address [35:8], must be 256-byte aligned. + */ +#define FH_RSCSR_CHNL0_RBDCB_BASE_REG (FH_MEM_RSCSR_CHNL0 + 0x004) + +/** + * Rx write pointer (index, really!). + * Bit fields: + * 11-0: Index of driver's most recent prepared-to-be-filled RBD, + 1. + * NOTE: For 256-entry circular buffer, use only bits [7:0]. + */ +#define FH_RSCSR_CHNL0_RBDCB_WPTR_REG (FH_MEM_RSCSR_CHNL0 + 0x008) +#define FH_RSCSR_CHNL0_WPTR (FH_RSCSR_CHNL0_RBDCB_WPTR_REG) + +#define FW_RSCSR_CHNL0_RXDCB_RDPTR_REG (FH_MEM_RSCSR_CHNL0 + 0x00c) +#define FH_RSCSR_CHNL0_RDPTR FW_RSCSR_CHNL0_RXDCB_RDPTR_REG + +/** + * Rx Config/Status Registers (RCSR) + * Rx Config Reg for channel 0 (only channel used) + * + * Driver must initialize FH_MEM_RCSR_CHNL0_CONFIG_REG as follows for + * normal operation (see bit fields). + * + * Clearing FH_MEM_RCSR_CHNL0_CONFIG_REG to 0 turns off Rx DMA. + * Driver should poll FH_MEM_RSSR_RX_STATUS_REG for + * FH_RSSR_CHNL0_RX_STATUS_CHNL_IDLE (bit 24) before continuing. + * + * Bit fields: + * 31-30: Rx DMA channel enable: '00' off/pause, '01' pause at end of frame, + * '10' operate normally + * 29-24: reserved + * 23-20: # RBDs in circular buffer = 2^value; use "8" for 256 RBDs (normal), + * min "5" for 32 RBDs, max "12" for 4096 RBDs. + * 19-18: reserved + * 17-16: size of each receive buffer; '00' 4K (normal), '01' 8K, + * '10' 12K, '11' 16K. + * 15-14: reserved + * 13-12: IRQ destination; '00' none, '01' host driver (normal operation) + * 11- 4: timeout for closing Rx buffer and interrupting host (units 32 usec) + * typical value 0x10 (about 1/2 msec) + * 3- 0: reserved + */ +#define FH_MEM_RCSR_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xC00) +#define FH_MEM_RCSR_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xCC0) +#define FH_MEM_RCSR_CHNL0 (FH_MEM_RCSR_LOWER_BOUND) + +#define FH_MEM_RCSR_CHNL0_CONFIG_REG (FH_MEM_RCSR_CHNL0) +#define FH_MEM_RCSR_CHNL0_RBDCB_WPTR (FH_MEM_RCSR_CHNL0 + 0x8) +#define FH_MEM_RCSR_CHNL0_FLUSH_RB_REQ (FH_MEM_RCSR_CHNL0 + 0x10) + +#define FH_RCSR_CHNL0_RX_CONFIG_RB_TIMEOUT_MSK (0x00000FF0) /* bits 4-11 */ +#define FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_MSK (0x00001000) /* bits 12 */ +#define FH_RCSR_CHNL0_RX_CONFIG_SINGLE_FRAME_MSK (0x00008000) /* bit 15 */ +#define FH_RCSR_CHNL0_RX_CONFIG_RB_SIZE_MSK (0x00030000) /* bits 16-17 */ +#define FH_RCSR_CHNL0_RX_CONFIG_RBDBC_SIZE_MSK (0x00F00000) /* bits 20-23 */ +#define FH_RCSR_CHNL0_RX_CONFIG_DMA_CHNL_EN_MSK (0xC0000000) /* bits 30-31*/ + +#define FH_RCSR_RX_CONFIG_RBDCB_SIZE_POS (20) +#define FH_RCSR_RX_CONFIG_REG_IRQ_RBTH_POS (4) +#define RX_RB_TIMEOUT (0x11) + +#define FH_RCSR_RX_CONFIG_CHNL_EN_PAUSE_VAL (0x00000000) +#define FH_RCSR_RX_CONFIG_CHNL_EN_PAUSE_EOF_VAL (0x40000000) +#define FH_RCSR_RX_CONFIG_CHNL_EN_ENABLE_VAL (0x80000000) + +#define FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K (0x00000000) +#define FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_8K (0x00010000) +#define FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_12K (0x00020000) +#define FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_16K (0x00030000) + +#define FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY (0x00000004) +#define FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_NO_INT_VAL (0x00000000) +#define FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_INT_HOST_VAL (0x00001000) + +/** + * Rx Shared Status Registers (RSSR) + * + * After stopping Rx DMA channel (writing 0 to + * FH_MEM_RCSR_CHNL0_CONFIG_REG), driver must poll + * FH_MEM_RSSR_RX_STATUS_REG until Rx channel is idle. + * + * Bit fields: + * 24: 1 = Channel 0 is idle + * + * FH_MEM_RSSR_SHARED_CTRL_REG and FH_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV + * contain default values that should not be altered by the driver. + */ +#define FH_MEM_RSSR_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xC40) +#define FH_MEM_RSSR_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xD00) + +#define FH_MEM_RSSR_SHARED_CTRL_REG (FH_MEM_RSSR_LOWER_BOUND) +#define FH_MEM_RSSR_RX_STATUS_REG (FH_MEM_RSSR_LOWER_BOUND + 0x004) +#define FH_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV\ + (FH_MEM_RSSR_LOWER_BOUND + 0x008) + +#define FH_RSSR_CHNL0_RX_STATUS_CHNL_IDLE (0x01000000) + +#define FH_MEM_TFDIB_REG1_ADDR_BITSHIFT 28 +#define FH_MEM_TB_MAX_LENGTH (0x00020000) + +/* 9000 rx series registers */ + +#define RFH_Q0_FRBDCB_BA_LSB 0xA08000 /* 64 bit address */ +#define RFH_Q_FRBDCB_BA_LSB(q) (RFH_Q0_FRBDCB_BA_LSB + (q) * 8) +/* Write index table */ +#define RFH_Q0_FRBDCB_WIDX 0xA08080 +#define RFH_Q_FRBDCB_WIDX(q) (RFH_Q0_FRBDCB_WIDX + (q) * 4) +/* Write index table - shadow registers */ +#define RFH_Q0_FRBDCB_WIDX_TRG 0x1C80 +#define RFH_Q_FRBDCB_WIDX_TRG(q) (RFH_Q0_FRBDCB_WIDX_TRG + (q) * 4) +/* Read index table */ +#define RFH_Q0_FRBDCB_RIDX 0xA080C0 +#define RFH_Q_FRBDCB_RIDX(q) (RFH_Q0_FRBDCB_RIDX + (q) * 4) +/* Used list table */ +#define RFH_Q0_URBDCB_BA_LSB 0xA08100 /* 64 bit address */ +#define RFH_Q_URBDCB_BA_LSB(q) (RFH_Q0_URBDCB_BA_LSB + (q) * 8) +/* Write index table */ +#define RFH_Q0_URBDCB_WIDX 0xA08180 +#define RFH_Q_URBDCB_WIDX(q) (RFH_Q0_URBDCB_WIDX + (q) * 4) +#define RFH_Q0_URBDCB_VAID 0xA081C0 +#define RFH_Q_URBDCB_VAID(q) (RFH_Q0_URBDCB_VAID + (q) * 4) +/* stts */ +#define RFH_Q0_URBD_STTS_WPTR_LSB 0xA08200 /*64 bits address */ +#define RFH_Q_URBD_STTS_WPTR_LSB(q) (RFH_Q0_URBD_STTS_WPTR_LSB + (q) * 8) + +#define RFH_Q0_ORB_WPTR_LSB 0xA08280 +#define RFH_Q_ORB_WPTR_LSB(q) (RFH_Q0_ORB_WPTR_LSB + (q) * 8) +#define RFH_RBDBUF_RBD0_LSB 0xA08300 +#define RFH_RBDBUF_RBD_LSB(q) (RFH_RBDBUF_RBD0_LSB + (q) * 8) + +/** + * RFH Status Register + * + * Bit fields: + * + * Bit 29: RBD_FETCH_IDLE + * This status flag is set by the RFH when there is no active RBD fetch from + * DRAM. + * Once the RFH RBD controller starts fetching (or when there is a pending + * RBD read response from DRAM), this flag is immediately turned off. + * + * Bit 30: SRAM_DMA_IDLE + * This status flag is set by the RFH when there is no active transaction from + * SRAM to DRAM. + * Once the SRAM to DRAM DMA is active, this flag is immediately turned off. + * + * Bit 31: RXF_DMA_IDLE + * This status flag is set by the RFH when there is no active transaction from + * RXF to DRAM. + * Once the RXF-to-DRAM DMA is active, this flag is immediately turned off. + */ +#define RFH_GEN_STATUS 0xA09808 +#define RFH_GEN_STATUS_GEN3 0xA07824 +#define RBD_FETCH_IDLE BIT(29) +#define SRAM_DMA_IDLE BIT(30) +#define RXF_DMA_IDLE BIT(31) + +/* DMA configuration */ +#define RFH_RXF_DMA_CFG 0xA09820 +#define RFH_RXF_DMA_CFG_GEN3 0xA07880 +/* RB size */ +#define RFH_RXF_DMA_RB_SIZE_MASK (0x000F0000) /* bits 16-19 */ +#define RFH_RXF_DMA_RB_SIZE_POS 16 +#define RFH_RXF_DMA_RB_SIZE_1K (0x1 << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_2K (0x2 << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_4K (0x4 << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_8K (0x8 << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_12K (0x9 << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_16K (0xA << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_20K (0xB << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_24K (0xC << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_28K (0xD << RFH_RXF_DMA_RB_SIZE_POS) +#define RFH_RXF_DMA_RB_SIZE_32K (0xE << RFH_RXF_DMA_RB_SIZE_POS) +/* RB Circular Buffer size:defines the table sizes in RBD units */ +#define RFH_RXF_DMA_RBDCB_SIZE_MASK (0x00F00000) /* bits 20-23 */ +#define RFH_RXF_DMA_RBDCB_SIZE_POS 20 +#define RFH_RXF_DMA_RBDCB_SIZE_8 (0x3 << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_RBDCB_SIZE_16 (0x4 << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_RBDCB_SIZE_32 (0x5 << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_RBDCB_SIZE_64 (0x7 << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_RBDCB_SIZE_128 (0x7 << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_RBDCB_SIZE_256 (0x8 << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_RBDCB_SIZE_512 (0x9 << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_RBDCB_SIZE_1024 (0xA << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_RBDCB_SIZE_2048 (0xB << RFH_RXF_DMA_RBDCB_SIZE_POS) +#define RFH_RXF_DMA_MIN_RB_SIZE_MASK (0x03000000) /* bit 24-25 */ +#define RFH_RXF_DMA_MIN_RB_SIZE_POS 24 +#define RFH_RXF_DMA_MIN_RB_4_8 (3 << RFH_RXF_DMA_MIN_RB_SIZE_POS) +#define RFH_RXF_DMA_DROP_TOO_LARGE_MASK (0x04000000) /* bit 26 */ +#define RFH_RXF_DMA_SINGLE_FRAME_MASK (0x20000000) /* bit 29 */ +#define RFH_DMA_EN_MASK (0xC0000000) /* bits 30-31*/ +#define RFH_DMA_EN_ENABLE_VAL BIT(31) + +#define RFH_RXF_RXQ_ACTIVE 0xA0980C + +#define RFH_GEN_CFG 0xA09800 +#define RFH_GEN_CFG_SERVICE_DMA_SNOOP BIT(0) +#define RFH_GEN_CFG_RFH_DMA_SNOOP BIT(1) +#define RFH_GEN_CFG_RB_CHUNK_SIZE BIT(4) +#define RFH_GEN_CFG_RB_CHUNK_SIZE_128 1 +#define RFH_GEN_CFG_RB_CHUNK_SIZE_64 0 +/* the driver assumes everywhere that the default RXQ is 0 */ +#define RFH_GEN_CFG_DEFAULT_RXQ_NUM 0xF00 +#define RFH_GEN_CFG_VAL(_n, _v) FIELD_PREP(RFH_GEN_CFG_ ## _n, _v) + +/* end of 9000 rx series registers */ + +/* TFDB Area - TFDs buffer table */ +#define FH_MEM_TFDIB_DRAM_ADDR_LSB_MSK (0xFFFFFFFF) +#define FH_TFDIB_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0x900) +#define FH_TFDIB_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0x958) +#define FH_TFDIB_CTRL0_REG(_chnl) (FH_TFDIB_LOWER_BOUND + 0x8 * (_chnl)) +#define FH_TFDIB_CTRL1_REG(_chnl) (FH_TFDIB_LOWER_BOUND + 0x8 * (_chnl) + 0x4) + +/** + * Transmit DMA Channel Control/Status Registers (TCSR) + * + * Device has one configuration register for each of 8 Tx DMA/FIFO channels + * supported in hardware (don't confuse these with the 16 Tx queues in DRAM, + * which feed the DMA/FIFO channels); config regs are separated by 0x20 bytes. + * + * To use a Tx DMA channel, driver must initialize its + * FH_TCSR_CHNL_TX_CONFIG_REG(chnl) with: + * + * FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE | + * FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE_VAL + * + * All other bits should be 0. + * + * Bit fields: + * 31-30: Tx DMA channel enable: '00' off/pause, '01' pause at end of frame, + * '10' operate normally + * 29- 4: Reserved, set to "0" + * 3: Enable internal DMA requests (1, normal operation), disable (0) + * 2- 0: Reserved, set to "0" + */ +#define FH_TCSR_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xD00) +#define FH_TCSR_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xE60) + +/* Find Control/Status reg for given Tx DMA/FIFO channel */ +#define FH_TCSR_CHNL_NUM (8) + +/* TCSR: tx_config register values */ +#define FH_TCSR_CHNL_TX_CONFIG_REG(_chnl) \ + (FH_TCSR_LOWER_BOUND + 0x20 * (_chnl)) +#define FH_TCSR_CHNL_TX_CREDIT_REG(_chnl) \ + (FH_TCSR_LOWER_BOUND + 0x20 * (_chnl) + 0x4) +#define FH_TCSR_CHNL_TX_BUF_STS_REG(_chnl) \ + (FH_TCSR_LOWER_BOUND + 0x20 * (_chnl) + 0x8) + +#define FH_TCSR_TX_CONFIG_REG_VAL_MSG_MODE_TXF (0x00000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_MSG_MODE_DRV (0x00000001) + +#define FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_DISABLE (0x00000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE (0x00000008) + +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_NOINT (0x00000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_ENDTFD (0x00100000) +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_IFTFD (0x00200000) + +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_NOINT (0x00000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_ENDTFD (0x00400000) +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_IFTFD (0x00800000) + +#define FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE (0x00000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE_EOF (0x40000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE (0x80000000) + +#define FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_EMPTY (0x00000000) +#define FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_WAIT (0x00002000) +#define FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_VALID (0x00000003) + +#define FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_NUM (20) +#define FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_IDX (12) + +/** + * Tx Shared Status Registers (TSSR) + * + * After stopping Tx DMA channel (writing 0 to + * FH_TCSR_CHNL_TX_CONFIG_REG(chnl)), driver must poll + * FH_TSSR_TX_STATUS_REG until selected Tx channel is idle + * (channel's buffers empty | no pending requests). + * + * Bit fields: + * 31-24: 1 = Channel buffers empty (channel 7:0) + * 23-16: 1 = No pending requests (channel 7:0) + */ +#define FH_TSSR_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xEA0) +#define FH_TSSR_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xEC0) + +#define FH_TSSR_TX_STATUS_REG (FH_TSSR_LOWER_BOUND + 0x010) + +/** + * Bit fields for TSSR(Tx Shared Status & Control) error status register: + * 31: Indicates an address error when accessed to internal memory + * uCode/driver must write "1" in order to clear this flag + * 30: Indicates that Host did not send the expected number of dwords to FH + * uCode/driver must write "1" in order to clear this flag + * 16-9:Each status bit is for one channel. Indicates that an (Error) ActDMA + * command was received from the scheduler while the TRB was already full + * with previous command + * uCode/driver must write "1" in order to clear this flag + * 7-0: Each status bit indicates a channel's TxCredit error. When an error + * bit is set, it indicates that the FH has received a full indication + * from the RTC TxFIFO and the current value of the TxCredit counter was + * not equal to zero. This mean that the credit mechanism was not + * synchronized to the TxFIFO status + * uCode/driver must write "1" in order to clear this flag + */ +#define FH_TSSR_TX_ERROR_REG (FH_TSSR_LOWER_BOUND + 0x018) +#define FH_TSSR_TX_MSG_CONFIG_REG (FH_TSSR_LOWER_BOUND + 0x008) + +#define FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(_chnl) ((1 << (_chnl)) << 16) + +/* Tx service channels */ +#define FH_SRVC_CHNL (9) +#define FH_SRVC_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0x9C8) +#define FH_SRVC_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0x9D0) +#define FH_SRVC_CHNL_SRAM_ADDR_REG(_chnl) \ + (FH_SRVC_LOWER_BOUND + ((_chnl) - 9) * 0x4) + +#define FH_TX_CHICKEN_BITS_REG (FH_MEM_LOWER_BOUND + 0xE98) +#define FH_TX_TRB_REG(_chan) (FH_MEM_LOWER_BOUND + 0x958 + (_chan) * 4) + +/* Instruct FH to increment the retry count of a packet when + * it is brought from the memory to TX-FIFO + */ +#define FH_TX_CHICKEN_BITS_SCD_AUTO_RETRY_EN (0x00000002) + +#define RX_POOL_SIZE(rbds) ((rbds) - 1 + \ + IWL_MAX_RX_HW_QUEUES * \ + (RX_CLAIM_REQ_ALLOC - RX_POST_REQ_ALLOC)) +/* cb size is the exponent */ +#define RX_QUEUE_CB_SIZE(x) ilog2(x) + +#define RX_QUEUE_SIZE 256 +#define RX_QUEUE_MASK 255 +#define RX_QUEUE_SIZE_LOG 8 + +/** + * struct iwl_rb_status - reserve buffer status + * host memory mapped FH registers + * @closed_rb_num [0:11] - Indicates the index of the RB which was closed + * @closed_fr_num [0:11] - Indicates the index of the RX Frame which was closed + * @finished_rb_num [0:11] - Indicates the index of the current RB + * in which the last frame was written to + * @finished_fr_num [0:11] - Indicates the index of the RX Frame + * which was transferred + */ +struct iwl_rb_status { + __le16 closed_rb_num; + __le16 closed_fr_num; + __le16 finished_rb_num; + __le16 finished_fr_nam; + __le32 __spare; +} __packed; + + +#define TFD_QUEUE_SIZE_MAX (256) +#define TFD_QUEUE_SIZE_MAX_GEN3 (65536) +/* cb size is the exponent - 3 */ +#define TFD_QUEUE_CB_SIZE(x) (ilog2(x) - 3) +#define TFD_QUEUE_SIZE_BC_DUP (64) +#define TFD_QUEUE_BC_SIZE (TFD_QUEUE_SIZE_MAX + TFD_QUEUE_SIZE_BC_DUP) +#define TFD_QUEUE_BC_SIZE_GEN3_AX210 1024 +#define TFD_QUEUE_BC_SIZE_GEN3_BZ (1024 * 4) +#define IWL_TX_DMA_MASK DMA_BIT_MASK(36) +#define IWL_NUM_OF_TBS 20 +#define IWL_TFH_NUM_TBS 25 + +/* IMR DMA registers */ +#define IMR_TFH_SRV_DMA_CHNL0_CTRL 0x00a0a51c +#define IMR_TFH_SRV_DMA_CHNL0_SRAM_ADDR 0x00a0a520 +#define IMR_TFH_SRV_DMA_CHNL0_DRAM_ADDR_LSB 0x00a0a524 +#define IMR_TFH_SRV_DMA_CHNL0_DRAM_ADDR_MSB 0x00a0a528 +#define IMR_TFH_SRV_DMA_CHNL0_BC 0x00a0a52c +#define TFH_SRV_DMA_CHNL0_LEFT_BC 0x00a0a530 + +/* RFH S2D DMA registers */ +#define IMR_RFH_GEN_CFG_SERVICE_DMA_RS_MSK 0x0000000c +#define IMR_RFH_GEN_CFG_SERVICE_DMA_SNOOP_MSK 0x00000002 + +/* TFH D2S DMA registers */ +#define IMR_UREG_CHICK_HALT_UMAC_PERMANENTLY_MSK 0x80000000 +#define IMR_UREG_CHICK 0x00d05c00 +#define IMR_TFH_SRV_DMA_CHNL0_CTRL_D2S_IRQ_TARGET_POS 0x00800000 +#define IMR_TFH_SRV_DMA_CHNL0_CTRL_D2S_RS_MSK 0x00000030 +#define IMR_TFH_SRV_DMA_CHNL0_CTRL_D2S_DMA_EN_POS 0x80000000 + +static inline u8 iwl_get_dma_hi_addr(dma_addr_t addr) +{ + return (sizeof(addr) > sizeof(u32) ? upper_32_bits(addr) : 0) & 0xF; +} + +/** + * enum iwl_tfd_tb_hi_n_len - TB hi_n_len bits + * @TB_HI_N_LEN_ADDR_HI_MSK: high 4 bits (to make it 36) of DMA address + * @TB_HI_N_LEN_LEN_MSK: length of the TB + */ +enum iwl_tfd_tb_hi_n_len { + TB_HI_N_LEN_ADDR_HI_MSK = 0xf, + TB_HI_N_LEN_LEN_MSK = 0xfff0, +}; + +/** + * struct iwl_tfd_tb transmit buffer descriptor within transmit frame descriptor + * + * This structure contains dma address and length of transmission address + * + * @lo: low [31:0] portion of the dma address of TX buffer + * every even is unaligned on 16 bit boundary + * @hi_n_len: &enum iwl_tfd_tb_hi_n_len + */ +struct iwl_tfd_tb { + __le32 lo; + __le16 hi_n_len; +} __packed; + +/** + * struct iwl_tfh_tb transmit buffer descriptor within transmit frame descriptor + * + * This structure contains dma address and length of transmission address + * + * @tb_len length of the tx buffer + * @addr 64 bits dma address + */ +struct iwl_tfh_tb { + __le16 tb_len; + __le64 addr; +} __packed; + +/** + * Each Tx queue uses a circular buffer of 256 TFDs stored in host DRAM. + * Both driver and device share these circular buffers, each of which must be + * contiguous 256 TFDs. + * For pre 22000 HW it is 256 x 128 bytes-per-TFD = 32 KBytes + * For 22000 HW and on it is 256 x 256 bytes-per-TFD = 65 KBytes + * + * Driver must indicate the physical address of the base of each + * circular buffer via the FH_MEM_CBBC_QUEUE registers. + * + * Each TFD contains pointer/size information for up to 20 / 25 data buffers + * in host DRAM. These buffers collectively contain the (one) frame described + * by the TFD. Each buffer must be a single contiguous block of memory within + * itself, but buffers may be scattered in host DRAM. Each buffer has max size + * of (4K - 4). The concatenates all of a TFD's buffers into a single + * Tx frame, up to 8 KBytes in size. + * + * A maximum of 255 (not 256!) TFDs may be on a queue waiting for Tx. + */ + +/** + * struct iwl_tfd - Transmit Frame Descriptor (TFD) + * @ __reserved1[3] reserved + * @ num_tbs 0-4 number of active tbs + * 5 reserved + * 6-7 padding (not used) + * @ tbs[20] transmit frame buffer descriptors + * @ __pad padding + */ +struct iwl_tfd { + u8 __reserved1[3]; + u8 num_tbs; + struct iwl_tfd_tb tbs[IWL_NUM_OF_TBS]; + __le32 __pad; +} __packed; + +/** + * struct iwl_tfh_tfd - Transmit Frame Descriptor (TFD) + * @ num_tbs 0-4 number of active tbs + * 5 -15 reserved + * @ tbs[25] transmit frame buffer descriptors + * @ __pad padding + */ +struct iwl_tfh_tfd { + __le16 num_tbs; + struct iwl_tfh_tb tbs[IWL_TFH_NUM_TBS]; + __le32 __pad; +} __packed; + +/* Keep Warm Size */ +#define IWL_KW_SIZE 0x1000 /* 4k */ + +/* Fixed (non-configurable) rx data from phy */ + +/** + * struct iwlagn_schedq_bc_tbl scheduler byte count table + * base physical address provided by SCD_DRAM_BASE_ADDR + * For devices up to 22000: + * @tfd_offset 0-12 - tx command byte count + * 12-16 - station index + * For 22000: + * @tfd_offset 0-12 - tx command byte count + * 12-13 - number of 64 byte chunks + * 14-16 - reserved + */ +struct iwlagn_scd_bc_tbl { + __le16 tfd_offset[TFD_QUEUE_BC_SIZE]; +} __packed; + +/** + * struct iwl_gen3_bc_tbl_entry scheduler byte count table entry gen3 + * For AX210 and on: + * @tfd_offset: 0-12 - tx command byte count + * 12-13 - number of 64 byte chunks + * 14-16 - reserved + */ +struct iwl_gen3_bc_tbl_entry { + __le16 tfd_offset; +} __packed; + +#endif /* !__iwl_fh_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-io.c b/drivers/net/wireless/intel/iwlwifi/iwl-io.c new file mode 100644 index 000000000..396f2c997 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-io.c @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2003-2014, 2018-2021 Intel Corporation + * Copyright (C) 2015-2016 Intel Deutschland GmbH + */ +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/export.h> + +#include "iwl-drv.h" +#include "iwl-io.h" +#include "iwl-csr.h" +#include "iwl-debug.h" +#include "iwl-prph.h" +#include "iwl-fh.h" + +void iwl_write8(struct iwl_trans *trans, u32 ofs, u8 val) +{ + trace_iwlwifi_dev_iowrite8(trans->dev, ofs, val); + iwl_trans_write8(trans, ofs, val); +} +IWL_EXPORT_SYMBOL(iwl_write8); + +void iwl_write32(struct iwl_trans *trans, u32 ofs, u32 val) +{ + trace_iwlwifi_dev_iowrite32(trans->dev, ofs, val); + iwl_trans_write32(trans, ofs, val); +} +IWL_EXPORT_SYMBOL(iwl_write32); + +void iwl_write64(struct iwl_trans *trans, u64 ofs, u64 val) +{ + trace_iwlwifi_dev_iowrite64(trans->dev, ofs, val); + iwl_trans_write32(trans, ofs, lower_32_bits(val)); + iwl_trans_write32(trans, ofs + 4, upper_32_bits(val)); +} +IWL_EXPORT_SYMBOL(iwl_write64); + +u32 iwl_read32(struct iwl_trans *trans, u32 ofs) +{ + u32 val = iwl_trans_read32(trans, ofs); + + trace_iwlwifi_dev_ioread32(trans->dev, ofs, val); + return val; +} +IWL_EXPORT_SYMBOL(iwl_read32); + +#define IWL_POLL_INTERVAL 10 /* microseconds */ + +int iwl_poll_bit(struct iwl_trans *trans, u32 addr, + u32 bits, u32 mask, int timeout) +{ + int t = 0; + + do { + if ((iwl_read32(trans, addr) & mask) == (bits & mask)) + return t; + udelay(IWL_POLL_INTERVAL); + t += IWL_POLL_INTERVAL; + } while (t < timeout); + + return -ETIMEDOUT; +} +IWL_EXPORT_SYMBOL(iwl_poll_bit); + +u32 iwl_read_direct32(struct iwl_trans *trans, u32 reg) +{ + if (iwl_trans_grab_nic_access(trans)) { + u32 value = iwl_read32(trans, reg); + + iwl_trans_release_nic_access(trans); + return value; + } + + return 0x5a5a5a5a; +} +IWL_EXPORT_SYMBOL(iwl_read_direct32); + +void iwl_write_direct32(struct iwl_trans *trans, u32 reg, u32 value) +{ + if (iwl_trans_grab_nic_access(trans)) { + iwl_write32(trans, reg, value); + iwl_trans_release_nic_access(trans); + } +} +IWL_EXPORT_SYMBOL(iwl_write_direct32); + +void iwl_write_direct64(struct iwl_trans *trans, u64 reg, u64 value) +{ + if (iwl_trans_grab_nic_access(trans)) { + iwl_write64(trans, reg, value); + iwl_trans_release_nic_access(trans); + } +} +IWL_EXPORT_SYMBOL(iwl_write_direct64); + +int iwl_poll_direct_bit(struct iwl_trans *trans, u32 addr, u32 mask, + int timeout) +{ + int t = 0; + + do { + if ((iwl_read_direct32(trans, addr) & mask) == mask) + return t; + udelay(IWL_POLL_INTERVAL); + t += IWL_POLL_INTERVAL; + } while (t < timeout); + + return -ETIMEDOUT; +} +IWL_EXPORT_SYMBOL(iwl_poll_direct_bit); + +u32 iwl_read_prph_no_grab(struct iwl_trans *trans, u32 ofs) +{ + u32 val = iwl_trans_read_prph(trans, ofs); + trace_iwlwifi_dev_ioread_prph32(trans->dev, ofs, val); + return val; +} +IWL_EXPORT_SYMBOL(iwl_read_prph_no_grab); + +void iwl_write_prph_no_grab(struct iwl_trans *trans, u32 ofs, u32 val) +{ + trace_iwlwifi_dev_iowrite_prph32(trans->dev, ofs, val); + iwl_trans_write_prph(trans, ofs, val); +} +IWL_EXPORT_SYMBOL(iwl_write_prph_no_grab); + +void iwl_write_prph64_no_grab(struct iwl_trans *trans, u64 ofs, u64 val) +{ + trace_iwlwifi_dev_iowrite_prph64(trans->dev, ofs, val); + iwl_write_prph_no_grab(trans, ofs, val & 0xffffffff); + iwl_write_prph_no_grab(trans, ofs + 4, val >> 32); +} +IWL_EXPORT_SYMBOL(iwl_write_prph64_no_grab); + +u32 iwl_read_prph(struct iwl_trans *trans, u32 ofs) +{ + if (iwl_trans_grab_nic_access(trans)) { + u32 val = iwl_read_prph_no_grab(trans, ofs); + + iwl_trans_release_nic_access(trans); + + return val; + } + + return 0x5a5a5a5a; +} +IWL_EXPORT_SYMBOL(iwl_read_prph); + +void iwl_write_prph_delay(struct iwl_trans *trans, u32 ofs, u32 val, u32 delay_ms) +{ + if (iwl_trans_grab_nic_access(trans)) { + mdelay(delay_ms); + iwl_write_prph_no_grab(trans, ofs, val); + iwl_trans_release_nic_access(trans); + } +} +IWL_EXPORT_SYMBOL(iwl_write_prph_delay); + +int iwl_poll_prph_bit(struct iwl_trans *trans, u32 addr, + u32 bits, u32 mask, int timeout) +{ + int t = 0; + + do { + if ((iwl_read_prph(trans, addr) & mask) == (bits & mask)) + return t; + udelay(IWL_POLL_INTERVAL); + t += IWL_POLL_INTERVAL; + } while (t < timeout); + + return -ETIMEDOUT; +} + +void iwl_set_bits_prph(struct iwl_trans *trans, u32 ofs, u32 mask) +{ + if (iwl_trans_grab_nic_access(trans)) { + iwl_write_prph_no_grab(trans, ofs, + iwl_read_prph_no_grab(trans, ofs) | + mask); + iwl_trans_release_nic_access(trans); + } +} +IWL_EXPORT_SYMBOL(iwl_set_bits_prph); + +void iwl_set_bits_mask_prph(struct iwl_trans *trans, u32 ofs, + u32 bits, u32 mask) +{ + if (iwl_trans_grab_nic_access(trans)) { + iwl_write_prph_no_grab(trans, ofs, + (iwl_read_prph_no_grab(trans, ofs) & + mask) | bits); + iwl_trans_release_nic_access(trans); + } +} +IWL_EXPORT_SYMBOL(iwl_set_bits_mask_prph); + +void iwl_clear_bits_prph(struct iwl_trans *trans, u32 ofs, u32 mask) +{ + u32 val; + + if (iwl_trans_grab_nic_access(trans)) { + val = iwl_read_prph_no_grab(trans, ofs); + iwl_write_prph_no_grab(trans, ofs, (val & ~mask)); + iwl_trans_release_nic_access(trans); + } +} +IWL_EXPORT_SYMBOL(iwl_clear_bits_prph); + +void iwl_force_nmi(struct iwl_trans *trans) +{ + if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_9000) + iwl_write_prph_delay(trans, DEVICE_SET_NMI_REG, + DEVICE_SET_NMI_VAL_DRV, 1); + else if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) + iwl_write_umac_prph(trans, UREG_NIC_SET_NMI_DRIVER, + UREG_NIC_SET_NMI_DRIVER_NMI_FROM_DRIVER); + else if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_BZ) + iwl_write_umac_prph(trans, UREG_DOORBELL_TO_ISR6, + UREG_DOORBELL_TO_ISR6_NMI_BIT); + else + iwl_write32(trans, CSR_DOORBELL_VECTOR, + UREG_DOORBELL_TO_ISR6_NMI_BIT); +} +IWL_EXPORT_SYMBOL(iwl_force_nmi); + +static const char *get_rfh_string(int cmd) +{ +#define IWL_CMD(x) case x: return #x +#define IWL_CMD_MQ(arg, reg, q) { if (arg == reg(q)) return #reg; } + + int i; + + for (i = 0; i < IWL_MAX_RX_HW_QUEUES; i++) { + IWL_CMD_MQ(cmd, RFH_Q_FRBDCB_BA_LSB, i); + IWL_CMD_MQ(cmd, RFH_Q_FRBDCB_WIDX, i); + IWL_CMD_MQ(cmd, RFH_Q_FRBDCB_RIDX, i); + IWL_CMD_MQ(cmd, RFH_Q_URBD_STTS_WPTR_LSB, i); + } + + switch (cmd) { + IWL_CMD(RFH_RXF_DMA_CFG); + IWL_CMD(RFH_GEN_CFG); + IWL_CMD(RFH_GEN_STATUS); + IWL_CMD(FH_TSSR_TX_STATUS_REG); + IWL_CMD(FH_TSSR_TX_ERROR_REG); + default: + return "UNKNOWN"; + } +#undef IWL_CMD_MQ +} + +struct reg { + u32 addr; + bool is64; +}; + +static int iwl_dump_rfh(struct iwl_trans *trans, char **buf) +{ + int i, q; + int num_q = trans->num_rx_queues; + static const u32 rfh_tbl[] = { + RFH_RXF_DMA_CFG, + RFH_GEN_CFG, + RFH_GEN_STATUS, + FH_TSSR_TX_STATUS_REG, + FH_TSSR_TX_ERROR_REG, + }; + static const struct reg rfh_mq_tbl[] = { + { RFH_Q0_FRBDCB_BA_LSB, true }, + { RFH_Q0_FRBDCB_WIDX, false }, + { RFH_Q0_FRBDCB_RIDX, false }, + { RFH_Q0_URBD_STTS_WPTR_LSB, true }, + }; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (buf) { + int pos = 0; + /* + * Register (up to 34 for name + 8 blank/q for MQ): 40 chars + * Colon + space: 2 characters + * 0X%08x: 10 characters + * New line: 1 character + * Total of 53 characters + */ + size_t bufsz = ARRAY_SIZE(rfh_tbl) * 53 + + ARRAY_SIZE(rfh_mq_tbl) * 53 * num_q + 40; + + *buf = kmalloc(bufsz, GFP_KERNEL); + if (!*buf) + return -ENOMEM; + + pos += scnprintf(*buf + pos, bufsz - pos, + "RFH register values:\n"); + + for (i = 0; i < ARRAY_SIZE(rfh_tbl); i++) + pos += scnprintf(*buf + pos, bufsz - pos, + "%40s: 0X%08x\n", + get_rfh_string(rfh_tbl[i]), + iwl_read_prph(trans, rfh_tbl[i])); + + for (i = 0; i < ARRAY_SIZE(rfh_mq_tbl); i++) + for (q = 0; q < num_q; q++) { + u32 addr = rfh_mq_tbl[i].addr; + + addr += q * (rfh_mq_tbl[i].is64 ? 8 : 4); + pos += scnprintf(*buf + pos, bufsz - pos, + "%34s(q %2d): 0X%08x\n", + get_rfh_string(addr), q, + iwl_read_prph(trans, addr)); + } + + return pos; + } +#endif + + IWL_ERR(trans, "RFH register values:\n"); + for (i = 0; i < ARRAY_SIZE(rfh_tbl); i++) + IWL_ERR(trans, " %34s: 0X%08x\n", + get_rfh_string(rfh_tbl[i]), + iwl_read_prph(trans, rfh_tbl[i])); + + for (i = 0; i < ARRAY_SIZE(rfh_mq_tbl); i++) + for (q = 0; q < num_q; q++) { + u32 addr = rfh_mq_tbl[i].addr; + + addr += q * (rfh_mq_tbl[i].is64 ? 8 : 4); + IWL_ERR(trans, " %34s(q %d): 0X%08x\n", + get_rfh_string(addr), q, + iwl_read_prph(trans, addr)); + } + + return 0; +} + +static const char *get_fh_string(int cmd) +{ + switch (cmd) { + IWL_CMD(FH_RSCSR_CHNL0_STTS_WPTR_REG); + IWL_CMD(FH_RSCSR_CHNL0_RBDCB_BASE_REG); + IWL_CMD(FH_RSCSR_CHNL0_WPTR); + IWL_CMD(FH_MEM_RCSR_CHNL0_CONFIG_REG); + IWL_CMD(FH_MEM_RSSR_SHARED_CTRL_REG); + IWL_CMD(FH_MEM_RSSR_RX_STATUS_REG); + IWL_CMD(FH_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV); + IWL_CMD(FH_TSSR_TX_STATUS_REG); + IWL_CMD(FH_TSSR_TX_ERROR_REG); + default: + return "UNKNOWN"; + } +#undef IWL_CMD +} + +int iwl_dump_fh(struct iwl_trans *trans, char **buf) +{ + int i; + static const u32 fh_tbl[] = { + FH_RSCSR_CHNL0_STTS_WPTR_REG, + FH_RSCSR_CHNL0_RBDCB_BASE_REG, + FH_RSCSR_CHNL0_WPTR, + FH_MEM_RCSR_CHNL0_CONFIG_REG, + FH_MEM_RSSR_SHARED_CTRL_REG, + FH_MEM_RSSR_RX_STATUS_REG, + FH_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV, + FH_TSSR_TX_STATUS_REG, + FH_TSSR_TX_ERROR_REG + }; + + if (trans->trans_cfg->mq_rx_supported) + return iwl_dump_rfh(trans, buf); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (buf) { + int pos = 0; + size_t bufsz = ARRAY_SIZE(fh_tbl) * 48 + 40; + + *buf = kmalloc(bufsz, GFP_KERNEL); + if (!*buf) + return -ENOMEM; + + pos += scnprintf(*buf + pos, bufsz - pos, + "FH register values:\n"); + + for (i = 0; i < ARRAY_SIZE(fh_tbl); i++) + pos += scnprintf(*buf + pos, bufsz - pos, + " %34s: 0X%08x\n", + get_fh_string(fh_tbl[i]), + iwl_read_direct32(trans, fh_tbl[i])); + + return pos; + } +#endif + + IWL_ERR(trans, "FH register values:\n"); + for (i = 0; i < ARRAY_SIZE(fh_tbl); i++) + IWL_ERR(trans, " %34s: 0X%08x\n", + get_fh_string(fh_tbl[i]), + iwl_read_direct32(trans, fh_tbl[i])); + + return 0; +} + +#define IWL_HOST_MON_BLOCK_PEMON 0x00 +#define IWL_HOST_MON_BLOCK_HIPM 0x22 + +#define IWL_HOST_MON_BLOCK_PEMON_VEC0 0x00 +#define IWL_HOST_MON_BLOCK_PEMON_VEC1 0x01 +#define IWL_HOST_MON_BLOCK_PEMON_WFPM 0x06 + +static void iwl_dump_host_monitor_block(struct iwl_trans *trans, + u32 block, u32 vec, u32 iter) +{ + int i; + + IWL_ERR(trans, "Host monitor block 0x%x vector 0x%x\n", block, vec); + iwl_write32(trans, CSR_MONITOR_CFG_REG, (block << 8) | vec); + for (i = 0; i < iter; i++) + IWL_ERR(trans, " value [iter %d]: 0x%08x\n", + i, iwl_read32(trans, CSR_MONITOR_STATUS_REG)); +} + +static void iwl_dump_host_monitor(struct iwl_trans *trans) +{ + switch (trans->trans_cfg->device_family) { + case IWL_DEVICE_FAMILY_22000: + case IWL_DEVICE_FAMILY_AX210: + IWL_ERR(trans, "CSR_RESET = 0x%x\n", + iwl_read32(trans, CSR_RESET)); + iwl_dump_host_monitor_block(trans, IWL_HOST_MON_BLOCK_PEMON, + IWL_HOST_MON_BLOCK_PEMON_VEC0, 15); + iwl_dump_host_monitor_block(trans, IWL_HOST_MON_BLOCK_PEMON, + IWL_HOST_MON_BLOCK_PEMON_VEC1, 15); + iwl_dump_host_monitor_block(trans, IWL_HOST_MON_BLOCK_PEMON, + IWL_HOST_MON_BLOCK_PEMON_WFPM, 15); + iwl_dump_host_monitor_block(trans, IWL_HOST_MON_BLOCK_HIPM, + IWL_HOST_MON_BLOCK_PEMON_VEC0, 1); + break; + default: + /* not supported yet */ + return; + } +} + +int iwl_finish_nic_init(struct iwl_trans *trans) +{ + const struct iwl_cfg_trans_params *cfg_trans = trans->trans_cfg; + u32 poll_ready; + int err; + + if (cfg_trans->bisr_workaround) { + /* ensure the TOP FSM isn't still in previous reset */ + mdelay(2); + } + + /* + * Set "initialization complete" bit to move adapter from + * D0U* --> D0A* (powered-up active) state. + */ + if (cfg_trans->device_family >= IWL_DEVICE_FAMILY_BZ) { + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY | + CSR_GP_CNTRL_REG_FLAG_MAC_INIT); + poll_ready = CSR_GP_CNTRL_REG_FLAG_MAC_STATUS; + } else { + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_INIT_DONE); + poll_ready = CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY; + } + + if (cfg_trans->device_family == IWL_DEVICE_FAMILY_8000) + udelay(2); + + /* + * Wait for clock stabilization; once stabilized, access to + * device-internal resources is supported, e.g. iwl_write_prph() + * and accesses to uCode SRAM. + */ + err = iwl_poll_bit(trans, CSR_GP_CNTRL, poll_ready, poll_ready, 25000); + if (err < 0) { + IWL_DEBUG_INFO(trans, "Failed to wake NIC\n"); + + iwl_dump_host_monitor(trans); + } + + if (cfg_trans->bisr_workaround) { + /* ensure BISR shift has finished */ + udelay(200); + } + + return err < 0 ? err : 0; +} +IWL_EXPORT_SYMBOL(iwl_finish_nic_init); + +void iwl_trans_sync_nmi_with_addr(struct iwl_trans *trans, u32 inta_addr, + u32 sw_err_bit) +{ + unsigned long timeout = jiffies + IWL_TRANS_NMI_TIMEOUT; + bool interrupts_enabled = test_bit(STATUS_INT_ENABLED, &trans->status); + + /* if the interrupts were already disabled, there is no point in + * calling iwl_disable_interrupts + */ + if (interrupts_enabled) + iwl_trans_interrupts(trans, false); + + iwl_force_nmi(trans); + while (time_after(timeout, jiffies)) { + u32 inta_hw = iwl_read32(trans, inta_addr); + + /* Error detected by uCode */ + if (inta_hw & sw_err_bit) { + /* Clear causes register */ + iwl_write32(trans, inta_addr, inta_hw & sw_err_bit); + break; + } + + mdelay(1); + } + + /* enable interrupts only if there were already enabled before this + * function to avoid a case were the driver enable interrupts before + * proper configurations were made + */ + if (interrupts_enabled) + iwl_trans_interrupts(trans, true); + + iwl_trans_fw_error(trans, false); +} diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-io.h b/drivers/net/wireless/intel/iwlwifi/iwl-io.h new file mode 100644 index 000000000..37b3bd628 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-io.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2018-2021 Intel Corporation + */ +#ifndef __iwl_io_h__ +#define __iwl_io_h__ + +#include "iwl-devtrace.h" +#include "iwl-trans.h" + +void iwl_write8(struct iwl_trans *trans, u32 ofs, u8 val); +void iwl_write32(struct iwl_trans *trans, u32 ofs, u32 val); +void iwl_write64(struct iwl_trans *trans, u64 ofs, u64 val); +u32 iwl_read32(struct iwl_trans *trans, u32 ofs); + +static inline void iwl_set_bit(struct iwl_trans *trans, u32 reg, u32 mask) +{ + iwl_trans_set_bits_mask(trans, reg, mask, mask); +} + +static inline void iwl_clear_bit(struct iwl_trans *trans, u32 reg, u32 mask) +{ + iwl_trans_set_bits_mask(trans, reg, mask, 0); +} + +int iwl_poll_bit(struct iwl_trans *trans, u32 addr, + u32 bits, u32 mask, int timeout); +int iwl_poll_direct_bit(struct iwl_trans *trans, u32 addr, u32 mask, + int timeout); + +u32 iwl_read_direct32(struct iwl_trans *trans, u32 reg); +void iwl_write_direct32(struct iwl_trans *trans, u32 reg, u32 value); +void iwl_write_direct64(struct iwl_trans *trans, u64 reg, u64 value); + + +u32 iwl_read_prph_no_grab(struct iwl_trans *trans, u32 ofs); +u32 iwl_read_prph(struct iwl_trans *trans, u32 ofs); +void iwl_write_prph_no_grab(struct iwl_trans *trans, u32 ofs, u32 val); +void iwl_write_prph64_no_grab(struct iwl_trans *trans, u64 ofs, u64 val); +void iwl_write_prph_delay(struct iwl_trans *trans, u32 ofs, + u32 val, u32 delay_ms); +static inline void iwl_write_prph(struct iwl_trans *trans, u32 ofs, u32 val) +{ + iwl_write_prph_delay(trans, ofs, val, 0); +} + +int iwl_poll_prph_bit(struct iwl_trans *trans, u32 addr, + u32 bits, u32 mask, int timeout); +void iwl_set_bits_prph(struct iwl_trans *trans, u32 ofs, u32 mask); +void iwl_set_bits_mask_prph(struct iwl_trans *trans, u32 ofs, + u32 bits, u32 mask); +void iwl_clear_bits_prph(struct iwl_trans *trans, u32 ofs, u32 mask); +void iwl_force_nmi(struct iwl_trans *trans); + +int iwl_finish_nic_init(struct iwl_trans *trans); + +/* Error handling */ +int iwl_dump_fh(struct iwl_trans *trans, char **buf); + +/* + * UMAC periphery address space changed from 0xA00000 to 0xD00000 starting from + * device family AX200. So peripheries used in families above and below AX200 + * should go through iwl_..._umac_..._prph. + */ +static inline u32 iwl_umac_prph(struct iwl_trans *trans, u32 ofs) +{ + return ofs + trans->trans_cfg->umac_prph_offset; +} + +static inline u32 iwl_read_umac_prph_no_grab(struct iwl_trans *trans, u32 ofs) +{ + return iwl_read_prph_no_grab(trans, ofs + + trans->trans_cfg->umac_prph_offset); +} + +static inline u32 iwl_read_umac_prph(struct iwl_trans *trans, u32 ofs) +{ + return iwl_read_prph(trans, ofs + trans->trans_cfg->umac_prph_offset); +} + +static inline void iwl_write_umac_prph_no_grab(struct iwl_trans *trans, u32 ofs, + u32 val) +{ + iwl_write_prph_no_grab(trans, ofs + trans->trans_cfg->umac_prph_offset, + val); +} + +static inline void iwl_write_umac_prph(struct iwl_trans *trans, u32 ofs, + u32 val) +{ + iwl_write_prph(trans, ofs + trans->trans_cfg->umac_prph_offset, val); +} + +static inline int iwl_poll_umac_prph_bit(struct iwl_trans *trans, u32 addr, + u32 bits, u32 mask, int timeout) +{ + return iwl_poll_prph_bit(trans, addr + + trans->trans_cfg->umac_prph_offset, + bits, mask, timeout); +} + +#endif diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-modparams.h b/drivers/net/wireless/intel/iwlwifi/iwl-modparams.h new file mode 100644 index 000000000..d0b4d02bd --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-modparams.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2022 Intel Corporation + */ +#ifndef __iwl_modparams_h__ +#define __iwl_modparams_h__ + +#include <linux/types.h> +#include <linux/spinlock.h> +#include <linux/gfp.h> + +extern struct iwl_mod_params iwlwifi_mod_params; + +enum iwl_power_level { + IWL_POWER_INDEX_1, + IWL_POWER_INDEX_2, + IWL_POWER_INDEX_3, + IWL_POWER_INDEX_4, + IWL_POWER_INDEX_5, + IWL_POWER_NUM +}; + +enum iwl_disable_11n { + IWL_DISABLE_HT_ALL = BIT(0), + IWL_DISABLE_HT_TXAGG = BIT(1), + IWL_DISABLE_HT_RXAGG = BIT(2), + IWL_ENABLE_HT_TXAGG = BIT(3), +}; + +enum iwl_amsdu_size { + IWL_AMSDU_DEF = 0, + IWL_AMSDU_4K = 1, + IWL_AMSDU_8K = 2, + IWL_AMSDU_12K = 3, + /* Add 2K at the end to avoid breaking current API */ + IWL_AMSDU_2K = 4, +}; + +enum iwl_uapsd_disable { + IWL_DISABLE_UAPSD_BSS = BIT(0), + IWL_DISABLE_UAPSD_P2P_CLIENT = BIT(1), +}; + +/** + * struct iwl_mod_params + * + * Holds the module parameters + * + * @swcrypto: using hardware encryption, default = 0 + * @disable_11n: disable 11n capabilities, default = 0, + * use IWL_[DIS,EN]ABLE_HT_* constants + * @amsdu_size: See &enum iwl_amsdu_size. + * @fw_restart: restart firmware, default = 1 + * @bt_coex_active: enable bt coex, default = true + * @led_mode: system default, default = 0 + * @power_save: enable power save, default = false + * @power_level: power level, default = 1 + * @debug_level: levels are IWL_DL_* + * @nvm_file: specifies a external NVM file + * @uapsd_disable: disable U-APSD, see &enum iwl_uapsd_disable, default = + * IWL_DISABLE_UAPSD_BSS | IWL_DISABLE_UAPSD_P2P_CLIENT + * @disable_11ac: disable VHT capabilities, default = false. + * @remove_when_gone: remove an inaccessible device from the PCIe bus. + * @enable_ini: enable new FW debug infratructure (INI TLVs) + */ +struct iwl_mod_params { + int swcrypto; + unsigned int disable_11n; + int amsdu_size; + bool fw_restart; + bool bt_coex_active; + int led_mode; + bool power_save; + int power_level; +#ifdef CONFIG_IWLWIFI_DEBUG + u32 debug_level; +#endif + char *nvm_file; + u32 uapsd_disable; + bool disable_11ac; + /** + * @disable_11ax: disable HE capabilities, default = false + */ + bool disable_11ax; + bool remove_when_gone; + u32 enable_ini; + bool disable_11be; +}; + +static inline bool iwl_enable_rx_ampdu(void) +{ + if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_RXAGG) + return false; + return true; +} + +static inline bool iwl_enable_tx_ampdu(void) +{ + if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_TXAGG) + return false; + if (iwlwifi_mod_params.disable_11n & IWL_ENABLE_HT_TXAGG) + return true; + + /* enabled by default */ + return true; +} + +#endif /* #__iwl_modparams_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c new file mode 100644 index 000000000..9040da3dc --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c @@ -0,0 +1,1817 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/export.h> +#include <linux/etherdevice.h> +#include <linux/pci.h> +#include <linux/firmware.h> + +#include "iwl-drv.h" +#include "iwl-modparams.h" +#include "iwl-nvm-parse.h" +#include "iwl-prph.h" +#include "iwl-io.h" +#include "iwl-csr.h" +#include "fw/acpi.h" +#include "fw/api/nvm-reg.h" +#include "fw/api/commands.h" +#include "fw/api/cmdhdr.h" +#include "fw/img.h" +#include "mei/iwl-mei.h" + +/* NVM offsets (in words) definitions */ +enum nvm_offsets { + /* NVM HW-Section offset (in words) definitions */ + SUBSYSTEM_ID = 0x0A, + HW_ADDR = 0x15, + + /* NVM SW-Section offset (in words) definitions */ + NVM_SW_SECTION = 0x1C0, + NVM_VERSION = 0, + RADIO_CFG = 1, + SKU = 2, + N_HW_ADDRS = 3, + NVM_CHANNELS = 0x1E0 - NVM_SW_SECTION, + + /* NVM calibration section offset (in words) definitions */ + NVM_CALIB_SECTION = 0x2B8, + XTAL_CALIB = 0x316 - NVM_CALIB_SECTION, + + /* NVM REGULATORY -Section offset (in words) definitions */ + NVM_CHANNELS_SDP = 0, +}; + +enum ext_nvm_offsets { + /* NVM HW-Section offset (in words) definitions */ + MAC_ADDRESS_OVERRIDE_EXT_NVM = 1, + + /* NVM SW-Section offset (in words) definitions */ + NVM_VERSION_EXT_NVM = 0, + N_HW_ADDRS_FAMILY_8000 = 3, + + /* NVM PHY_SKU-Section offset (in words) definitions */ + RADIO_CFG_FAMILY_EXT_NVM = 0, + SKU_FAMILY_8000 = 2, + + /* NVM REGULATORY -Section offset (in words) definitions */ + NVM_CHANNELS_EXTENDED = 0, + NVM_LAR_OFFSET_OLD = 0x4C7, + NVM_LAR_OFFSET = 0x507, + NVM_LAR_ENABLED = 0x7, +}; + +/* SKU Capabilities (actual values from NVM definition) */ +enum nvm_sku_bits { + NVM_SKU_CAP_BAND_24GHZ = BIT(0), + NVM_SKU_CAP_BAND_52GHZ = BIT(1), + NVM_SKU_CAP_11N_ENABLE = BIT(2), + NVM_SKU_CAP_11AC_ENABLE = BIT(3), + NVM_SKU_CAP_MIMO_DISABLE = BIT(5), +}; + +/* + * These are the channel numbers in the order that they are stored in the NVM + */ +static const u16 iwl_nvm_channels[] = { + /* 2.4 GHz */ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + /* 5 GHz */ + 36, 40, 44 , 48, 52, 56, 60, 64, + 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, + 149, 153, 157, 161, 165 +}; + +static const u16 iwl_ext_nvm_channels[] = { + /* 2.4 GHz */ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + /* 5 GHz */ + 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, + 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, + 149, 153, 157, 161, 165, 169, 173, 177, 181 +}; + +static const u16 iwl_uhb_nvm_channels[] = { + /* 2.4 GHz */ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + /* 5 GHz */ + 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, + 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, + 149, 153, 157, 161, 165, 169, 173, 177, 181, + /* 6-7 GHz */ + 1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65, 69, + 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 129, + 133, 137, 141, 145, 149, 153, 157, 161, 165, 169, 173, 177, 181, 185, + 189, 193, 197, 201, 205, 209, 213, 217, 221, 225, 229, 233 +}; + +#define IWL_NVM_NUM_CHANNELS ARRAY_SIZE(iwl_nvm_channels) +#define IWL_NVM_NUM_CHANNELS_EXT ARRAY_SIZE(iwl_ext_nvm_channels) +#define IWL_NVM_NUM_CHANNELS_UHB ARRAY_SIZE(iwl_uhb_nvm_channels) +#define NUM_2GHZ_CHANNELS 14 +#define NUM_5GHZ_CHANNELS 37 +#define FIRST_2GHZ_HT_MINUS 5 +#define LAST_2GHZ_HT_PLUS 9 +#define N_HW_ADDR_MASK 0xF + +/* rate data (static) */ +static struct ieee80211_rate iwl_cfg80211_rates[] = { + { .bitrate = 1 * 10, .hw_value = 0, .hw_value_short = 0, }, + { .bitrate = 2 * 10, .hw_value = 1, .hw_value_short = 1, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 5.5 * 10, .hw_value = 2, .hw_value_short = 2, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 11 * 10, .hw_value = 3, .hw_value_short = 3, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 6 * 10, .hw_value = 4, .hw_value_short = 4, }, + { .bitrate = 9 * 10, .hw_value = 5, .hw_value_short = 5, }, + { .bitrate = 12 * 10, .hw_value = 6, .hw_value_short = 6, }, + { .bitrate = 18 * 10, .hw_value = 7, .hw_value_short = 7, }, + { .bitrate = 24 * 10, .hw_value = 8, .hw_value_short = 8, }, + { .bitrate = 36 * 10, .hw_value = 9, .hw_value_short = 9, }, + { .bitrate = 48 * 10, .hw_value = 10, .hw_value_short = 10, }, + { .bitrate = 54 * 10, .hw_value = 11, .hw_value_short = 11, }, +}; +#define RATES_24_OFFS 0 +#define N_RATES_24 ARRAY_SIZE(iwl_cfg80211_rates) +#define RATES_52_OFFS 4 +#define N_RATES_52 (N_RATES_24 - RATES_52_OFFS) + +/** + * enum iwl_nvm_channel_flags - channel flags in NVM + * @NVM_CHANNEL_VALID: channel is usable for this SKU/geo + * @NVM_CHANNEL_IBSS: usable as an IBSS channel + * @NVM_CHANNEL_ACTIVE: active scanning allowed + * @NVM_CHANNEL_RADAR: radar detection required + * @NVM_CHANNEL_INDOOR_ONLY: only indoor use is allowed + * @NVM_CHANNEL_GO_CONCURRENT: GO operation is allowed when connected to BSS + * on same channel on 2.4 or same UNII band on 5.2 + * @NVM_CHANNEL_UNIFORM: uniform spreading required + * @NVM_CHANNEL_20MHZ: 20 MHz channel okay + * @NVM_CHANNEL_40MHZ: 40 MHz channel okay + * @NVM_CHANNEL_80MHZ: 80 MHz channel okay + * @NVM_CHANNEL_160MHZ: 160 MHz channel okay + * @NVM_CHANNEL_DC_HIGH: DC HIGH required/allowed (?) + */ +enum iwl_nvm_channel_flags { + NVM_CHANNEL_VALID = BIT(0), + NVM_CHANNEL_IBSS = BIT(1), + NVM_CHANNEL_ACTIVE = BIT(3), + NVM_CHANNEL_RADAR = BIT(4), + NVM_CHANNEL_INDOOR_ONLY = BIT(5), + NVM_CHANNEL_GO_CONCURRENT = BIT(6), + NVM_CHANNEL_UNIFORM = BIT(7), + NVM_CHANNEL_20MHZ = BIT(8), + NVM_CHANNEL_40MHZ = BIT(9), + NVM_CHANNEL_80MHZ = BIT(10), + NVM_CHANNEL_160MHZ = BIT(11), + NVM_CHANNEL_DC_HIGH = BIT(12), +}; + +/** + * enum iwl_reg_capa_flags - global flags applied for the whole regulatory + * domain. + * @REG_CAPA_BF_CCD_LOW_BAND: Beam-forming or Cyclic Delay Diversity in the + * 2.4Ghz band is allowed. + * @REG_CAPA_BF_CCD_HIGH_BAND: Beam-forming or Cyclic Delay Diversity in the + * 5Ghz band is allowed. + * @REG_CAPA_160MHZ_ALLOWED: 11ac channel with a width of 160Mhz is allowed + * for this regulatory domain (valid only in 5Ghz). + * @REG_CAPA_80MHZ_ALLOWED: 11ac channel with a width of 80Mhz is allowed + * for this regulatory domain (valid only in 5Ghz). + * @REG_CAPA_MCS_8_ALLOWED: 11ac with MCS 8 is allowed. + * @REG_CAPA_MCS_9_ALLOWED: 11ac with MCS 9 is allowed. + * @REG_CAPA_40MHZ_FORBIDDEN: 11n channel with a width of 40Mhz is forbidden + * for this regulatory domain (valid only in 5Ghz). + * @REG_CAPA_DC_HIGH_ENABLED: DC HIGH allowed. + * @REG_CAPA_11AX_DISABLED: 11ax is forbidden for this regulatory domain. + */ +enum iwl_reg_capa_flags { + REG_CAPA_BF_CCD_LOW_BAND = BIT(0), + REG_CAPA_BF_CCD_HIGH_BAND = BIT(1), + REG_CAPA_160MHZ_ALLOWED = BIT(2), + REG_CAPA_80MHZ_ALLOWED = BIT(3), + REG_CAPA_MCS_8_ALLOWED = BIT(4), + REG_CAPA_MCS_9_ALLOWED = BIT(5), + REG_CAPA_40MHZ_FORBIDDEN = BIT(7), + REG_CAPA_DC_HIGH_ENABLED = BIT(9), + REG_CAPA_11AX_DISABLED = BIT(10), +}; + +/** + * enum iwl_reg_capa_flags_v2 - global flags applied for the whole regulatory + * domain (version 2). + * @REG_CAPA_V2_STRADDLE_DISABLED: Straddle channels (144, 142, 138) are + * disabled. + * @REG_CAPA_V2_BF_CCD_LOW_BAND: Beam-forming or Cyclic Delay Diversity in the + * 2.4Ghz band is allowed. + * @REG_CAPA_V2_BF_CCD_HIGH_BAND: Beam-forming or Cyclic Delay Diversity in the + * 5Ghz band is allowed. + * @REG_CAPA_V2_160MHZ_ALLOWED: 11ac channel with a width of 160Mhz is allowed + * for this regulatory domain (valid only in 5Ghz). + * @REG_CAPA_V2_80MHZ_ALLOWED: 11ac channel with a width of 80Mhz is allowed + * for this regulatory domain (valid only in 5Ghz). + * @REG_CAPA_V2_MCS_8_ALLOWED: 11ac with MCS 8 is allowed. + * @REG_CAPA_V2_MCS_9_ALLOWED: 11ac with MCS 9 is allowed. + * @REG_CAPA_V2_WEATHER_DISABLED: Weather radar channels (120, 124, 128, 118, + * 126, 122) are disabled. + * @REG_CAPA_V2_40MHZ_ALLOWED: 11n channel with a width of 40Mhz is allowed + * for this regulatory domain (uvalid only in 5Ghz). + * @REG_CAPA_V2_11AX_DISABLED: 11ax is forbidden for this regulatory domain. + */ +enum iwl_reg_capa_flags_v2 { + REG_CAPA_V2_STRADDLE_DISABLED = BIT(0), + REG_CAPA_V2_BF_CCD_LOW_BAND = BIT(1), + REG_CAPA_V2_BF_CCD_HIGH_BAND = BIT(2), + REG_CAPA_V2_160MHZ_ALLOWED = BIT(3), + REG_CAPA_V2_80MHZ_ALLOWED = BIT(4), + REG_CAPA_V2_MCS_8_ALLOWED = BIT(5), + REG_CAPA_V2_MCS_9_ALLOWED = BIT(6), + REG_CAPA_V2_WEATHER_DISABLED = BIT(7), + REG_CAPA_V2_40MHZ_ALLOWED = BIT(8), + REG_CAPA_V2_11AX_DISABLED = BIT(10), +}; + +/* +* API v2 for reg_capa_flags is relevant from version 6 and onwards of the +* MCC update command response. +*/ +#define REG_CAPA_V2_RESP_VER 6 + +/** + * struct iwl_reg_capa - struct for global regulatory capabilities, Used for + * handling the different APIs of reg_capa_flags. + * + * @allow_40mhz: 11n channel with a width of 40Mhz is allowed + * for this regulatory domain (valid only in 5Ghz). + * @allow_80mhz: 11ac channel with a width of 80Mhz is allowed + * for this regulatory domain (valid only in 5Ghz). + * @allow_160mhz: 11ac channel with a width of 160Mhz is allowed + * for this regulatory domain (valid only in 5Ghz). + * @disable_11ax: 11ax is forbidden for this regulatory domain. + */ +struct iwl_reg_capa { + u16 allow_40mhz; + u16 allow_80mhz; + u16 allow_160mhz; + u16 disable_11ax; +}; + +static inline void iwl_nvm_print_channel_flags(struct device *dev, u32 level, + int chan, u32 flags) +{ +#define CHECK_AND_PRINT_I(x) \ + ((flags & NVM_CHANNEL_##x) ? " " #x : "") + + if (!(flags & NVM_CHANNEL_VALID)) { + IWL_DEBUG_DEV(dev, level, "Ch. %d: 0x%x: No traffic\n", + chan, flags); + return; + } + + /* Note: already can print up to 101 characters, 110 is the limit! */ + IWL_DEBUG_DEV(dev, level, + "Ch. %d: 0x%x:%s%s%s%s%s%s%s%s%s%s%s%s\n", + chan, flags, + CHECK_AND_PRINT_I(VALID), + CHECK_AND_PRINT_I(IBSS), + CHECK_AND_PRINT_I(ACTIVE), + CHECK_AND_PRINT_I(RADAR), + CHECK_AND_PRINT_I(INDOOR_ONLY), + CHECK_AND_PRINT_I(GO_CONCURRENT), + CHECK_AND_PRINT_I(UNIFORM), + CHECK_AND_PRINT_I(20MHZ), + CHECK_AND_PRINT_I(40MHZ), + CHECK_AND_PRINT_I(80MHZ), + CHECK_AND_PRINT_I(160MHZ), + CHECK_AND_PRINT_I(DC_HIGH)); +#undef CHECK_AND_PRINT_I +} + +static u32 iwl_get_channel_flags(u8 ch_num, int ch_idx, enum nl80211_band band, + u32 nvm_flags, const struct iwl_cfg *cfg) +{ + u32 flags = IEEE80211_CHAN_NO_HT40; + + if (band == NL80211_BAND_2GHZ && (nvm_flags & NVM_CHANNEL_40MHZ)) { + if (ch_num <= LAST_2GHZ_HT_PLUS) + flags &= ~IEEE80211_CHAN_NO_HT40PLUS; + if (ch_num >= FIRST_2GHZ_HT_MINUS) + flags &= ~IEEE80211_CHAN_NO_HT40MINUS; + } else if (nvm_flags & NVM_CHANNEL_40MHZ) { + if ((ch_idx - NUM_2GHZ_CHANNELS) % 2 == 0) + flags &= ~IEEE80211_CHAN_NO_HT40PLUS; + else + flags &= ~IEEE80211_CHAN_NO_HT40MINUS; + } + if (!(nvm_flags & NVM_CHANNEL_80MHZ)) + flags |= IEEE80211_CHAN_NO_80MHZ; + if (!(nvm_flags & NVM_CHANNEL_160MHZ)) + flags |= IEEE80211_CHAN_NO_160MHZ; + + if (!(nvm_flags & NVM_CHANNEL_IBSS)) + flags |= IEEE80211_CHAN_NO_IR; + + if (!(nvm_flags & NVM_CHANNEL_ACTIVE)) + flags |= IEEE80211_CHAN_NO_IR; + + if (nvm_flags & NVM_CHANNEL_RADAR) + flags |= IEEE80211_CHAN_RADAR; + + if (nvm_flags & NVM_CHANNEL_INDOOR_ONLY) + flags |= IEEE80211_CHAN_INDOOR_ONLY; + + /* Set the GO concurrent flag only in case that NO_IR is set. + * Otherwise it is meaningless + */ + if ((nvm_flags & NVM_CHANNEL_GO_CONCURRENT) && + (flags & IEEE80211_CHAN_NO_IR)) + flags |= IEEE80211_CHAN_IR_CONCURRENT; + + return flags; +} + +static enum nl80211_band iwl_nl80211_band_from_channel_idx(int ch_idx) +{ + if (ch_idx >= NUM_2GHZ_CHANNELS + NUM_5GHZ_CHANNELS) { + return NL80211_BAND_6GHZ; + } + + if (ch_idx >= NUM_2GHZ_CHANNELS) + return NL80211_BAND_5GHZ; + return NL80211_BAND_2GHZ; +} + +static int iwl_init_channel_map(struct device *dev, const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, + const void * const nvm_ch_flags, + u32 sbands_flags, bool v4) +{ + int ch_idx; + int n_channels = 0; + struct ieee80211_channel *channel; + u32 ch_flags; + int num_of_ch; + const u16 *nvm_chan; + + if (cfg->uhb_supported) { + num_of_ch = IWL_NVM_NUM_CHANNELS_UHB; + nvm_chan = iwl_uhb_nvm_channels; + } else if (cfg->nvm_type == IWL_NVM_EXT) { + num_of_ch = IWL_NVM_NUM_CHANNELS_EXT; + nvm_chan = iwl_ext_nvm_channels; + } else { + num_of_ch = IWL_NVM_NUM_CHANNELS; + nvm_chan = iwl_nvm_channels; + } + + for (ch_idx = 0; ch_idx < num_of_ch; ch_idx++) { + enum nl80211_band band = + iwl_nl80211_band_from_channel_idx(ch_idx); + + if (v4) + ch_flags = + __le32_to_cpup((const __le32 *)nvm_ch_flags + ch_idx); + else + ch_flags = + __le16_to_cpup((const __le16 *)nvm_ch_flags + ch_idx); + + if (band == NL80211_BAND_5GHZ && + !data->sku_cap_band_52ghz_enable) + continue; + + /* workaround to disable wide channels in 5GHz */ + if ((sbands_flags & IWL_NVM_SBANDS_FLAGS_NO_WIDE_IN_5GHZ) && + band == NL80211_BAND_5GHZ) { + ch_flags &= ~(NVM_CHANNEL_40MHZ | + NVM_CHANNEL_80MHZ | + NVM_CHANNEL_160MHZ); + } + + if (ch_flags & NVM_CHANNEL_160MHZ) + data->vht160_supported = true; + + if (!(sbands_flags & IWL_NVM_SBANDS_FLAGS_LAR) && + !(ch_flags & NVM_CHANNEL_VALID)) { + /* + * Channels might become valid later if lar is + * supported, hence we still want to add them to + * the list of supported channels to cfg80211. + */ + iwl_nvm_print_channel_flags(dev, IWL_DL_EEPROM, + nvm_chan[ch_idx], ch_flags); + continue; + } + + channel = &data->channels[n_channels]; + n_channels++; + + channel->hw_value = nvm_chan[ch_idx]; + channel->band = band; + channel->center_freq = + ieee80211_channel_to_frequency( + channel->hw_value, channel->band); + + /* Initialize regulatory-based run-time data */ + + /* + * Default value - highest tx power value. max_power + * is not used in mvm, and is used for backwards compatibility + */ + channel->max_power = IWL_DEFAULT_MAX_TX_POWER; + + /* don't put limitations in case we're using LAR */ + if (!(sbands_flags & IWL_NVM_SBANDS_FLAGS_LAR)) + channel->flags = iwl_get_channel_flags(nvm_chan[ch_idx], + ch_idx, band, + ch_flags, cfg); + else + channel->flags = 0; + + /* TODO: Don't put limitations on UHB devices as we still don't + * have NVM for them + */ + if (cfg->uhb_supported) + channel->flags = 0; + iwl_nvm_print_channel_flags(dev, IWL_DL_EEPROM, + channel->hw_value, ch_flags); + IWL_DEBUG_EEPROM(dev, "Ch. %d: %ddBm\n", + channel->hw_value, channel->max_power); + } + + return n_channels; +} + +static void iwl_init_vht_hw_capab(struct iwl_trans *trans, + struct iwl_nvm_data *data, + struct ieee80211_sta_vht_cap *vht_cap, + u8 tx_chains, u8 rx_chains) +{ + const struct iwl_cfg *cfg = trans->cfg; + int num_rx_ants = num_of_ant(rx_chains); + int num_tx_ants = num_of_ant(tx_chains); + + vht_cap->vht_supported = true; + + vht_cap->cap = IEEE80211_VHT_CAP_SHORT_GI_80 | + IEEE80211_VHT_CAP_RXSTBC_1 | + IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE | + 3 << IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT | + IEEE80211_VHT_MAX_AMPDU_1024K << + IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT; + + if (data->vht160_supported) + vht_cap->cap |= IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ | + IEEE80211_VHT_CAP_SHORT_GI_160; + + if (cfg->vht_mu_mimo_supported) + vht_cap->cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE; + + if (cfg->ht_params->ldpc) + vht_cap->cap |= IEEE80211_VHT_CAP_RXLDPC; + + if (data->sku_cap_mimo_disabled) { + num_rx_ants = 1; + num_tx_ants = 1; + } + + if (num_tx_ants > 1) + vht_cap->cap |= IEEE80211_VHT_CAP_TXSTBC; + else + vht_cap->cap |= IEEE80211_VHT_CAP_TX_ANTENNA_PATTERN; + + switch (iwlwifi_mod_params.amsdu_size) { + case IWL_AMSDU_DEF: + if (trans->trans_cfg->mq_rx_supported) + vht_cap->cap |= + IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454; + else + vht_cap->cap |= IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_3895; + break; + case IWL_AMSDU_2K: + if (trans->trans_cfg->mq_rx_supported) + vht_cap->cap |= + IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454; + else + WARN(1, "RB size of 2K is not supported by this device\n"); + break; + case IWL_AMSDU_4K: + vht_cap->cap |= IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_3895; + break; + case IWL_AMSDU_8K: + vht_cap->cap |= IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_7991; + break; + case IWL_AMSDU_12K: + vht_cap->cap |= IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454; + break; + default: + break; + } + + vht_cap->vht_mcs.rx_mcs_map = + cpu_to_le16(IEEE80211_VHT_MCS_SUPPORT_0_9 << 0 | + IEEE80211_VHT_MCS_SUPPORT_0_9 << 2 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 4 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 6 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 8 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 10 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 12 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 14); + + if (num_rx_ants == 1 || cfg->rx_with_siso_diversity) { + vht_cap->cap |= IEEE80211_VHT_CAP_RX_ANTENNA_PATTERN; + /* this works because NOT_SUPPORTED == 3 */ + vht_cap->vht_mcs.rx_mcs_map |= + cpu_to_le16(IEEE80211_VHT_MCS_NOT_SUPPORTED << 2); + } + + vht_cap->vht_mcs.tx_mcs_map = vht_cap->vht_mcs.rx_mcs_map; + + vht_cap->vht_mcs.tx_highest |= + cpu_to_le16(IEEE80211_VHT_EXT_NSS_BW_CAPABLE); +} + +static const u8 iwl_vendor_caps[] = { + 0xdd, /* vendor element */ + 0x06, /* length */ + 0x00, 0x17, 0x35, /* Intel OUI */ + 0x08, /* type (Intel Capabilities) */ + /* followed by 16 bits of capabilities */ +#define IWL_VENDOR_CAP_IMPROVED_BF_FDBK_HE BIT(0) + IWL_VENDOR_CAP_IMPROVED_BF_FDBK_HE, + 0x00 +}; + +static const struct ieee80211_sband_iftype_data iwl_he_capa[] = { + { + .types_mask = BIT(NL80211_IFTYPE_STATION), + .he_cap = { + .has_he = true, + .he_cap_elem = { + .mac_cap_info[0] = + IEEE80211_HE_MAC_CAP0_HTC_HE, + .mac_cap_info[1] = + IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_16US | + IEEE80211_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_8, + .mac_cap_info[2] = + IEEE80211_HE_MAC_CAP2_32BIT_BA_BITMAP, + .mac_cap_info[3] = + IEEE80211_HE_MAC_CAP3_OMI_CONTROL | + IEEE80211_HE_MAC_CAP3_RX_CTRL_FRAME_TO_MULTIBSS, + .mac_cap_info[4] = + IEEE80211_HE_MAC_CAP4_AMSDU_IN_AMPDU | + IEEE80211_HE_MAC_CAP4_MULTI_TID_AGG_TX_QOS_B39, + .mac_cap_info[5] = + IEEE80211_HE_MAC_CAP5_MULTI_TID_AGG_TX_QOS_B40 | + IEEE80211_HE_MAC_CAP5_MULTI_TID_AGG_TX_QOS_B41 | + IEEE80211_HE_MAC_CAP5_UL_2x996_TONE_RU | + IEEE80211_HE_MAC_CAP5_HE_DYNAMIC_SM_PS | + IEEE80211_HE_MAC_CAP5_HT_VHT_TRIG_FRAME_RX, + .phy_cap_info[0] = + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G | + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G | + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G, + .phy_cap_info[1] = + IEEE80211_HE_PHY_CAP1_PREAMBLE_PUNC_RX_MASK | + IEEE80211_HE_PHY_CAP1_DEVICE_CLASS_A | + IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD, + .phy_cap_info[2] = + IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US | + IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ, + .phy_cap_info[3] = + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_BPSK | + IEEE80211_HE_PHY_CAP3_DCM_MAX_TX_NSS_1 | + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_BPSK | + IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_1, + .phy_cap_info[4] = + IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE | + IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_ABOVE_80MHZ_8 | + IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_8, + .phy_cap_info[6] = + IEEE80211_HE_PHY_CAP6_TRIG_SU_BEAMFORMING_FB | + IEEE80211_HE_PHY_CAP6_TRIG_MU_BEAMFORMING_PARTIAL_BW_FB | + IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT, + .phy_cap_info[7] = + IEEE80211_HE_PHY_CAP7_POWER_BOOST_FACTOR_SUPP | + IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI, + .phy_cap_info[8] = + IEEE80211_HE_PHY_CAP8_HE_ER_SU_PPDU_4XLTF_AND_08_US_GI | + IEEE80211_HE_PHY_CAP8_20MHZ_IN_40MHZ_HE_PPDU_IN_2G | + IEEE80211_HE_PHY_CAP8_20MHZ_IN_160MHZ_HE_PPDU | + IEEE80211_HE_PHY_CAP8_80MHZ_IN_160MHZ_HE_PPDU | + IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_242, + .phy_cap_info[9] = + IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_COMP_SIGB | + IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_NON_COMP_SIGB | + (IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_RESERVED << + IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_POS), + .phy_cap_info[10] = + IEEE80211_HE_PHY_CAP10_HE_MU_M1RU_MAX_LTF, + }, + /* + * Set default Tx/Rx HE MCS NSS Support field. + * Indicate support for up to 2 spatial streams and all + * MCS, without any special cases + */ + .he_mcs_nss_supp = { + .rx_mcs_80 = cpu_to_le16(0xfffa), + .tx_mcs_80 = cpu_to_le16(0xfffa), + .rx_mcs_160 = cpu_to_le16(0xfffa), + .tx_mcs_160 = cpu_to_le16(0xfffa), + .rx_mcs_80p80 = cpu_to_le16(0xffff), + .tx_mcs_80p80 = cpu_to_le16(0xffff), + }, + /* + * Set default PPE thresholds, with PPET16 set to 0, + * PPET8 set to 7 + */ + .ppe_thres = {0x61, 0x1c, 0xc7, 0x71}, + }, + }, + { + .types_mask = BIT(NL80211_IFTYPE_AP), + .he_cap = { + .has_he = true, + .he_cap_elem = { + .mac_cap_info[0] = + IEEE80211_HE_MAC_CAP0_HTC_HE, + .mac_cap_info[1] = + IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_16US | + IEEE80211_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_8, + .mac_cap_info[3] = + IEEE80211_HE_MAC_CAP3_OMI_CONTROL, + .phy_cap_info[0] = + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G | + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G, + .phy_cap_info[1] = + IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD, + .phy_cap_info[2] = + IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ | + IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US, + .phy_cap_info[3] = + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_BPSK | + IEEE80211_HE_PHY_CAP3_DCM_MAX_TX_NSS_1 | + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_BPSK | + IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_1, + .phy_cap_info[6] = + IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT, + .phy_cap_info[7] = + IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI, + .phy_cap_info[8] = + IEEE80211_HE_PHY_CAP8_HE_ER_SU_PPDU_4XLTF_AND_08_US_GI | + IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_242, + .phy_cap_info[9] = + IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_RESERVED + << IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_POS, + }, + /* + * Set default Tx/Rx HE MCS NSS Support field. + * Indicate support for up to 2 spatial streams and all + * MCS, without any special cases + */ + .he_mcs_nss_supp = { + .rx_mcs_80 = cpu_to_le16(0xfffa), + .tx_mcs_80 = cpu_to_le16(0xfffa), + .rx_mcs_160 = cpu_to_le16(0xfffa), + .tx_mcs_160 = cpu_to_le16(0xfffa), + .rx_mcs_80p80 = cpu_to_le16(0xffff), + .tx_mcs_80p80 = cpu_to_le16(0xffff), + }, + /* + * Set default PPE thresholds, with PPET16 set to 0, + * PPET8 set to 7 + */ + .ppe_thres = {0x61, 0x1c, 0xc7, 0x71}, + }, + }, +}; + +static void iwl_init_he_6ghz_capa(struct iwl_trans *trans, + struct iwl_nvm_data *data, + struct ieee80211_supported_band *sband, + u8 tx_chains, u8 rx_chains) +{ + struct ieee80211_sta_ht_cap ht_cap; + struct ieee80211_sta_vht_cap vht_cap = {}; + struct ieee80211_sband_iftype_data *iftype_data; + u16 he_6ghz_capa = 0; + u32 exp; + int i; + + if (sband->band != NL80211_BAND_6GHZ) + return; + + /* grab HT/VHT capabilities and calculate HE 6 GHz capabilities */ + iwl_init_ht_hw_capab(trans, data, &ht_cap, NL80211_BAND_5GHZ, + tx_chains, rx_chains); + WARN_ON(!ht_cap.ht_supported); + iwl_init_vht_hw_capab(trans, data, &vht_cap, tx_chains, rx_chains); + WARN_ON(!vht_cap.vht_supported); + + he_6ghz_capa |= + u16_encode_bits(ht_cap.ampdu_density, + IEEE80211_HE_6GHZ_CAP_MIN_MPDU_START); + exp = u32_get_bits(vht_cap.cap, + IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK); + he_6ghz_capa |= + u16_encode_bits(exp, IEEE80211_HE_6GHZ_CAP_MAX_AMPDU_LEN_EXP); + exp = u32_get_bits(vht_cap.cap, IEEE80211_VHT_CAP_MAX_MPDU_MASK); + he_6ghz_capa |= + u16_encode_bits(exp, IEEE80211_HE_6GHZ_CAP_MAX_MPDU_LEN); + /* we don't support extended_ht_cap_info anywhere, so no RD_RESPONDER */ + if (vht_cap.cap & IEEE80211_VHT_CAP_TX_ANTENNA_PATTERN) + he_6ghz_capa |= IEEE80211_HE_6GHZ_CAP_TX_ANTPAT_CONS; + if (vht_cap.cap & IEEE80211_VHT_CAP_RX_ANTENNA_PATTERN) + he_6ghz_capa |= IEEE80211_HE_6GHZ_CAP_RX_ANTPAT_CONS; + + IWL_DEBUG_EEPROM(trans->dev, "he_6ghz_capa=0x%x\n", he_6ghz_capa); + + /* we know it's writable - we set it before ourselves */ + iftype_data = (void *)(uintptr_t)sband->iftype_data; + for (i = 0; i < sband->n_iftype_data; i++) + iftype_data[i].he_6ghz_capa.capa = cpu_to_le16(he_6ghz_capa); +} + +static void +iwl_nvm_fixup_sband_iftd(struct iwl_trans *trans, + struct ieee80211_supported_band *sband, + struct ieee80211_sband_iftype_data *iftype_data, + u8 tx_chains, u8 rx_chains, + const struct iwl_fw *fw) +{ + bool is_ap = iftype_data->types_mask & BIT(NL80211_IFTYPE_AP); + + /* Advertise an A-MPDU exponent extension based on + * operating band + */ + if (sband->band != NL80211_BAND_2GHZ) + iftype_data->he_cap.he_cap_elem.mac_cap_info[3] |= + IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_1; + else + iftype_data->he_cap.he_cap_elem.mac_cap_info[3] |= + IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_3; + + if (is_ap && iwlwifi_mod_params.nvm_file) + iftype_data->he_cap.he_cap_elem.phy_cap_info[0] |= + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G; + + if ((tx_chains & rx_chains) == ANT_AB) { + iftype_data->he_cap.he_cap_elem.phy_cap_info[2] |= + IEEE80211_HE_PHY_CAP2_STBC_TX_UNDER_80MHZ; + iftype_data->he_cap.he_cap_elem.phy_cap_info[5] |= + IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_2 | + IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_ABOVE_80MHZ_2; + if (!is_ap) + iftype_data->he_cap.he_cap_elem.phy_cap_info[7] |= + IEEE80211_HE_PHY_CAP7_MAX_NC_2; + } else if (!is_ap) { + /* If not 2x2, we need to indicate 1x1 in the + * Midamble RX Max NSTS - but not for AP mode + */ + iftype_data->he_cap.he_cap_elem.phy_cap_info[1] &= + ~IEEE80211_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS; + iftype_data->he_cap.he_cap_elem.phy_cap_info[2] &= + ~IEEE80211_HE_PHY_CAP2_MIDAMBLE_RX_TX_MAX_NSTS; + iftype_data->he_cap.he_cap_elem.phy_cap_info[7] |= + IEEE80211_HE_PHY_CAP7_MAX_NC_1; + } + + switch (CSR_HW_RFID_TYPE(trans->hw_rf_id)) { + case IWL_CFG_RF_TYPE_GF: + case IWL_CFG_RF_TYPE_MR: + case IWL_CFG_RF_TYPE_MS: + iftype_data->he_cap.he_cap_elem.phy_cap_info[9] |= + IEEE80211_HE_PHY_CAP9_TX_1024_QAM_LESS_THAN_242_TONE_RU; + if (!is_ap) + iftype_data->he_cap.he_cap_elem.phy_cap_info[9] |= + IEEE80211_HE_PHY_CAP9_RX_1024_QAM_LESS_THAN_242_TONE_RU; + break; + } + + if (fw_has_capa(&fw->ucode_capa, IWL_UCODE_TLV_CAPA_BROADCAST_TWT)) + iftype_data->he_cap.he_cap_elem.mac_cap_info[2] |= + IEEE80211_HE_MAC_CAP2_BCAST_TWT; + + if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_22000 && + !is_ap) { + iftype_data->vendor_elems.data = iwl_vendor_caps; + iftype_data->vendor_elems.len = ARRAY_SIZE(iwl_vendor_caps); + } +} + +static void iwl_init_he_hw_capab(struct iwl_trans *trans, + struct iwl_nvm_data *data, + struct ieee80211_supported_band *sband, + u8 tx_chains, u8 rx_chains, + const struct iwl_fw *fw) +{ + struct ieee80211_sband_iftype_data *iftype_data; + int i; + + /* should only initialize once */ + if (WARN_ON(sband->iftype_data)) + return; + + BUILD_BUG_ON(sizeof(data->iftd.low) != sizeof(iwl_he_capa)); + BUILD_BUG_ON(sizeof(data->iftd.high) != sizeof(iwl_he_capa)); + + switch (sband->band) { + case NL80211_BAND_2GHZ: + iftype_data = data->iftd.low; + break; + case NL80211_BAND_5GHZ: + case NL80211_BAND_6GHZ: + iftype_data = data->iftd.high; + break; + default: + WARN_ON(1); + return; + } + + memcpy(iftype_data, iwl_he_capa, sizeof(iwl_he_capa)); + + sband->iftype_data = iftype_data; + sband->n_iftype_data = ARRAY_SIZE(iwl_he_capa); + + for (i = 0; i < sband->n_iftype_data; i++) + iwl_nvm_fixup_sband_iftd(trans, sband, &iftype_data[i], + tx_chains, rx_chains, fw); + + iwl_init_he_6ghz_capa(trans, data, sband, tx_chains, rx_chains); +} + +static void iwl_init_sbands(struct iwl_trans *trans, + struct iwl_nvm_data *data, + const void *nvm_ch_flags, u8 tx_chains, + u8 rx_chains, u32 sbands_flags, bool v4, + const struct iwl_fw *fw) +{ + struct device *dev = trans->dev; + const struct iwl_cfg *cfg = trans->cfg; + int n_channels; + int n_used = 0; + struct ieee80211_supported_band *sband; + + n_channels = iwl_init_channel_map(dev, cfg, data, nvm_ch_flags, + sbands_flags, v4); + sband = &data->bands[NL80211_BAND_2GHZ]; + sband->band = NL80211_BAND_2GHZ; + sband->bitrates = &iwl_cfg80211_rates[RATES_24_OFFS]; + sband->n_bitrates = N_RATES_24; + n_used += iwl_init_sband_channels(data, sband, n_channels, + NL80211_BAND_2GHZ); + iwl_init_ht_hw_capab(trans, data, &sband->ht_cap, NL80211_BAND_2GHZ, + tx_chains, rx_chains); + + if (data->sku_cap_11ax_enable && !iwlwifi_mod_params.disable_11ax) + iwl_init_he_hw_capab(trans, data, sband, tx_chains, rx_chains, + fw); + + sband = &data->bands[NL80211_BAND_5GHZ]; + sband->band = NL80211_BAND_5GHZ; + sband->bitrates = &iwl_cfg80211_rates[RATES_52_OFFS]; + sband->n_bitrates = N_RATES_52; + n_used += iwl_init_sband_channels(data, sband, n_channels, + NL80211_BAND_5GHZ); + iwl_init_ht_hw_capab(trans, data, &sband->ht_cap, NL80211_BAND_5GHZ, + tx_chains, rx_chains); + if (data->sku_cap_11ac_enable && !iwlwifi_mod_params.disable_11ac) + iwl_init_vht_hw_capab(trans, data, &sband->vht_cap, + tx_chains, rx_chains); + + if (data->sku_cap_11ax_enable && !iwlwifi_mod_params.disable_11ax) + iwl_init_he_hw_capab(trans, data, sband, tx_chains, rx_chains, + fw); + + /* 6GHz band. */ + sband = &data->bands[NL80211_BAND_6GHZ]; + sband->band = NL80211_BAND_6GHZ; + /* use the same rates as 5GHz band */ + sband->bitrates = &iwl_cfg80211_rates[RATES_52_OFFS]; + sband->n_bitrates = N_RATES_52; + n_used += iwl_init_sband_channels(data, sband, n_channels, + NL80211_BAND_6GHZ); + + if (data->sku_cap_11ax_enable && !iwlwifi_mod_params.disable_11ax) + iwl_init_he_hw_capab(trans, data, sband, tx_chains, rx_chains, + fw); + else + sband->n_channels = 0; + if (n_channels != n_used) + IWL_ERR_DEV(dev, "NVM: used only %d of %d channels\n", + n_used, n_channels); +} + +static int iwl_get_sku(const struct iwl_cfg *cfg, const __le16 *nvm_sw, + const __le16 *phy_sku) +{ + if (cfg->nvm_type != IWL_NVM_EXT) + return le16_to_cpup(nvm_sw + SKU); + + return le32_to_cpup((const __le32 *)(phy_sku + SKU_FAMILY_8000)); +} + +static int iwl_get_nvm_version(const struct iwl_cfg *cfg, const __le16 *nvm_sw) +{ + if (cfg->nvm_type != IWL_NVM_EXT) + return le16_to_cpup(nvm_sw + NVM_VERSION); + else + return le32_to_cpup((const __le32 *)(nvm_sw + + NVM_VERSION_EXT_NVM)); +} + +static int iwl_get_radio_cfg(const struct iwl_cfg *cfg, const __le16 *nvm_sw, + const __le16 *phy_sku) +{ + if (cfg->nvm_type != IWL_NVM_EXT) + return le16_to_cpup(nvm_sw + RADIO_CFG); + + return le32_to_cpup((const __le32 *)(phy_sku + RADIO_CFG_FAMILY_EXT_NVM)); + +} + +static int iwl_get_n_hw_addrs(const struct iwl_cfg *cfg, const __le16 *nvm_sw) +{ + int n_hw_addr; + + if (cfg->nvm_type != IWL_NVM_EXT) + return le16_to_cpup(nvm_sw + N_HW_ADDRS); + + n_hw_addr = le32_to_cpup((const __le32 *)(nvm_sw + N_HW_ADDRS_FAMILY_8000)); + + return n_hw_addr & N_HW_ADDR_MASK; +} + +static void iwl_set_radio_cfg(const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, + u32 radio_cfg) +{ + if (cfg->nvm_type != IWL_NVM_EXT) { + data->radio_cfg_type = NVM_RF_CFG_TYPE_MSK(radio_cfg); + data->radio_cfg_step = NVM_RF_CFG_STEP_MSK(radio_cfg); + data->radio_cfg_dash = NVM_RF_CFG_DASH_MSK(radio_cfg); + data->radio_cfg_pnum = NVM_RF_CFG_PNUM_MSK(radio_cfg); + return; + } + + /* set the radio configuration for family 8000 */ + data->radio_cfg_type = EXT_NVM_RF_CFG_TYPE_MSK(radio_cfg); + data->radio_cfg_step = EXT_NVM_RF_CFG_STEP_MSK(radio_cfg); + data->radio_cfg_dash = EXT_NVM_RF_CFG_DASH_MSK(radio_cfg); + data->radio_cfg_pnum = EXT_NVM_RF_CFG_FLAVOR_MSK(radio_cfg); + data->valid_tx_ant = EXT_NVM_RF_CFG_TX_ANT_MSK(radio_cfg); + data->valid_rx_ant = EXT_NVM_RF_CFG_RX_ANT_MSK(radio_cfg); +} + +static void iwl_flip_hw_address(__le32 mac_addr0, __le32 mac_addr1, u8 *dest) +{ + const u8 *hw_addr; + + hw_addr = (const u8 *)&mac_addr0; + dest[0] = hw_addr[3]; + dest[1] = hw_addr[2]; + dest[2] = hw_addr[1]; + dest[3] = hw_addr[0]; + + hw_addr = (const u8 *)&mac_addr1; + dest[4] = hw_addr[1]; + dest[5] = hw_addr[0]; +} + +static void iwl_set_hw_address_from_csr(struct iwl_trans *trans, + struct iwl_nvm_data *data) +{ + __le32 mac_addr0 = cpu_to_le32(iwl_read32(trans, + CSR_MAC_ADDR0_STRAP(trans))); + __le32 mac_addr1 = cpu_to_le32(iwl_read32(trans, + CSR_MAC_ADDR1_STRAP(trans))); + + iwl_flip_hw_address(mac_addr0, mac_addr1, data->hw_addr); + /* + * If the OEM fused a valid address, use it instead of the one in the + * OTP + */ + if (is_valid_ether_addr(data->hw_addr)) + return; + + mac_addr0 = cpu_to_le32(iwl_read32(trans, CSR_MAC_ADDR0_OTP(trans))); + mac_addr1 = cpu_to_le32(iwl_read32(trans, CSR_MAC_ADDR1_OTP(trans))); + + iwl_flip_hw_address(mac_addr0, mac_addr1, data->hw_addr); +} + +static void iwl_set_hw_address_family_8000(struct iwl_trans *trans, + const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, + const __le16 *mac_override, + const __be16 *nvm_hw) +{ + const u8 *hw_addr; + + if (mac_override) { + static const u8 reserved_mac[] = { + 0x02, 0xcc, 0xaa, 0xff, 0xee, 0x00 + }; + + hw_addr = (const u8 *)(mac_override + + MAC_ADDRESS_OVERRIDE_EXT_NVM); + + /* + * Store the MAC address from MAO section. + * No byte swapping is required in MAO section + */ + memcpy(data->hw_addr, hw_addr, ETH_ALEN); + + /* + * Force the use of the OTP MAC address in case of reserved MAC + * address in the NVM, or if address is given but invalid. + */ + if (is_valid_ether_addr(data->hw_addr) && + memcmp(reserved_mac, hw_addr, ETH_ALEN) != 0) + return; + + IWL_ERR(trans, + "mac address from nvm override section is not valid\n"); + } + + if (nvm_hw) { + /* read the mac address from WFMP registers */ + __le32 mac_addr0 = cpu_to_le32(iwl_trans_read_prph(trans, + WFMP_MAC_ADDR_0)); + __le32 mac_addr1 = cpu_to_le32(iwl_trans_read_prph(trans, + WFMP_MAC_ADDR_1)); + + iwl_flip_hw_address(mac_addr0, mac_addr1, data->hw_addr); + + return; + } + + IWL_ERR(trans, "mac address is not found\n"); +} + +static int iwl_set_hw_address(struct iwl_trans *trans, + const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, const __be16 *nvm_hw, + const __le16 *mac_override) +{ + if (cfg->mac_addr_from_csr) { + iwl_set_hw_address_from_csr(trans, data); + } else if (cfg->nvm_type != IWL_NVM_EXT) { + const u8 *hw_addr = (const u8 *)(nvm_hw + HW_ADDR); + + /* The byte order is little endian 16 bit, meaning 214365 */ + data->hw_addr[0] = hw_addr[1]; + data->hw_addr[1] = hw_addr[0]; + data->hw_addr[2] = hw_addr[3]; + data->hw_addr[3] = hw_addr[2]; + data->hw_addr[4] = hw_addr[5]; + data->hw_addr[5] = hw_addr[4]; + } else { + iwl_set_hw_address_family_8000(trans, cfg, data, + mac_override, nvm_hw); + } + + if (!is_valid_ether_addr(data->hw_addr)) { + IWL_ERR(trans, "no valid mac address was found\n"); + return -EINVAL; + } + + if (!trans->csme_own) + IWL_INFO(trans, "base HW address: %pM, OTP minor version: 0x%x\n", + data->hw_addr, iwl_read_prph(trans, REG_OTP_MINOR)); + + return 0; +} + +static bool +iwl_nvm_no_wide_in_5ghz(struct iwl_trans *trans, const struct iwl_cfg *cfg, + const __be16 *nvm_hw) +{ + /* + * Workaround a bug in Indonesia SKUs where the regulatory in + * some 7000-family OTPs erroneously allow wide channels in + * 5GHz. To check for Indonesia, we take the SKU value from + * bits 1-4 in the subsystem ID and check if it is either 5 or + * 9. In those cases, we need to force-disable wide channels + * in 5GHz otherwise the FW will throw a sysassert when we try + * to use them. + */ + if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_7000) { + /* + * Unlike the other sections in the NVM, the hw + * section uses big-endian. + */ + u16 subsystem_id = be16_to_cpup(nvm_hw + SUBSYSTEM_ID); + u8 sku = (subsystem_id & 0x1e) >> 1; + + if (sku == 5 || sku == 9) { + IWL_DEBUG_EEPROM(trans->dev, + "disabling wide channels in 5GHz (0x%0x %d)\n", + subsystem_id, sku); + return true; + } + } + + return false; +} + +struct iwl_nvm_data * +iwl_parse_mei_nvm_data(struct iwl_trans *trans, const struct iwl_cfg *cfg, + const struct iwl_mei_nvm *mei_nvm, + const struct iwl_fw *fw) +{ + struct iwl_nvm_data *data; + u32 sbands_flags = 0; + u8 rx_chains = fw->valid_rx_ant; + u8 tx_chains = fw->valid_rx_ant; + + if (cfg->uhb_supported) + data = kzalloc(struct_size(data, channels, + IWL_NVM_NUM_CHANNELS_UHB), + GFP_KERNEL); + else + data = kzalloc(struct_size(data, channels, + IWL_NVM_NUM_CHANNELS_EXT), + GFP_KERNEL); + if (!data) + return NULL; + + BUILD_BUG_ON(ARRAY_SIZE(mei_nvm->channels) != + IWL_NVM_NUM_CHANNELS_UHB); + data->nvm_version = mei_nvm->nvm_version; + + iwl_set_radio_cfg(cfg, data, mei_nvm->radio_cfg); + if (data->valid_tx_ant) + tx_chains &= data->valid_tx_ant; + if (data->valid_rx_ant) + rx_chains &= data->valid_rx_ant; + + data->sku_cap_mimo_disabled = false; + data->sku_cap_band_24ghz_enable = true; + data->sku_cap_band_52ghz_enable = true; + data->sku_cap_11n_enable = + !(iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_ALL); + data->sku_cap_11ac_enable = true; + data->sku_cap_11ax_enable = + mei_nvm->caps & MEI_NVM_CAPS_11AX_SUPPORT; + + data->lar_enabled = mei_nvm->caps & MEI_NVM_CAPS_LARI_SUPPORT; + + data->n_hw_addrs = mei_nvm->n_hw_addrs; + /* If no valid mac address was found - bail out */ + if (iwl_set_hw_address(trans, cfg, data, NULL, NULL)) { + kfree(data); + return NULL; + } + + if (data->lar_enabled && + fw_has_capa(&fw->ucode_capa, IWL_UCODE_TLV_CAPA_LAR_SUPPORT)) + sbands_flags |= IWL_NVM_SBANDS_FLAGS_LAR; + + iwl_init_sbands(trans, data, mei_nvm->channels, tx_chains, rx_chains, + sbands_flags, true, fw); + + return data; +} +IWL_EXPORT_SYMBOL(iwl_parse_mei_nvm_data); + +struct iwl_nvm_data * +iwl_parse_nvm_data(struct iwl_trans *trans, const struct iwl_cfg *cfg, + const struct iwl_fw *fw, + const __be16 *nvm_hw, const __le16 *nvm_sw, + const __le16 *nvm_calib, const __le16 *regulatory, + const __le16 *mac_override, const __le16 *phy_sku, + u8 tx_chains, u8 rx_chains) +{ + struct iwl_nvm_data *data; + bool lar_enabled; + u32 sku, radio_cfg; + u32 sbands_flags = 0; + u16 lar_config; + const __le16 *ch_section; + + if (cfg->uhb_supported) + data = kzalloc(struct_size(data, channels, + IWL_NVM_NUM_CHANNELS_UHB), + GFP_KERNEL); + else if (cfg->nvm_type != IWL_NVM_EXT) + data = kzalloc(struct_size(data, channels, + IWL_NVM_NUM_CHANNELS), + GFP_KERNEL); + else + data = kzalloc(struct_size(data, channels, + IWL_NVM_NUM_CHANNELS_EXT), + GFP_KERNEL); + if (!data) + return NULL; + + data->nvm_version = iwl_get_nvm_version(cfg, nvm_sw); + + radio_cfg = iwl_get_radio_cfg(cfg, nvm_sw, phy_sku); + iwl_set_radio_cfg(cfg, data, radio_cfg); + if (data->valid_tx_ant) + tx_chains &= data->valid_tx_ant; + if (data->valid_rx_ant) + rx_chains &= data->valid_rx_ant; + + sku = iwl_get_sku(cfg, nvm_sw, phy_sku); + data->sku_cap_band_24ghz_enable = sku & NVM_SKU_CAP_BAND_24GHZ; + data->sku_cap_band_52ghz_enable = sku & NVM_SKU_CAP_BAND_52GHZ; + data->sku_cap_11n_enable = sku & NVM_SKU_CAP_11N_ENABLE; + if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_ALL) + data->sku_cap_11n_enable = false; + data->sku_cap_11ac_enable = data->sku_cap_11n_enable && + (sku & NVM_SKU_CAP_11AC_ENABLE); + data->sku_cap_mimo_disabled = sku & NVM_SKU_CAP_MIMO_DISABLE; + + data->n_hw_addrs = iwl_get_n_hw_addrs(cfg, nvm_sw); + + if (cfg->nvm_type != IWL_NVM_EXT) { + /* Checking for required sections */ + if (!nvm_calib) { + IWL_ERR(trans, + "Can't parse empty Calib NVM sections\n"); + kfree(data); + return NULL; + } + + ch_section = cfg->nvm_type == IWL_NVM_SDP ? + ®ulatory[NVM_CHANNELS_SDP] : + &nvm_sw[NVM_CHANNELS]; + + /* in family 8000 Xtal calibration values moved to OTP */ + data->xtal_calib[0] = *(nvm_calib + XTAL_CALIB); + data->xtal_calib[1] = *(nvm_calib + XTAL_CALIB + 1); + lar_enabled = true; + } else { + u16 lar_offset = data->nvm_version < 0xE39 ? + NVM_LAR_OFFSET_OLD : + NVM_LAR_OFFSET; + + lar_config = le16_to_cpup(regulatory + lar_offset); + data->lar_enabled = !!(lar_config & + NVM_LAR_ENABLED); + lar_enabled = data->lar_enabled; + ch_section = ®ulatory[NVM_CHANNELS_EXTENDED]; + } + + /* If no valid mac address was found - bail out */ + if (iwl_set_hw_address(trans, cfg, data, nvm_hw, mac_override)) { + kfree(data); + return NULL; + } + + if (lar_enabled && + fw_has_capa(&fw->ucode_capa, IWL_UCODE_TLV_CAPA_LAR_SUPPORT)) + sbands_flags |= IWL_NVM_SBANDS_FLAGS_LAR; + + if (iwl_nvm_no_wide_in_5ghz(trans, cfg, nvm_hw)) + sbands_flags |= IWL_NVM_SBANDS_FLAGS_NO_WIDE_IN_5GHZ; + + iwl_init_sbands(trans, data, ch_section, tx_chains, rx_chains, + sbands_flags, false, fw); + data->calib_version = 255; + + return data; +} +IWL_EXPORT_SYMBOL(iwl_parse_nvm_data); + +static u32 iwl_nvm_get_regdom_bw_flags(const u16 *nvm_chan, + int ch_idx, u16 nvm_flags, + struct iwl_reg_capa reg_capa, + const struct iwl_cfg *cfg) +{ + u32 flags = NL80211_RRF_NO_HT40; + + if (ch_idx < NUM_2GHZ_CHANNELS && + (nvm_flags & NVM_CHANNEL_40MHZ)) { + if (nvm_chan[ch_idx] <= LAST_2GHZ_HT_PLUS) + flags &= ~NL80211_RRF_NO_HT40PLUS; + if (nvm_chan[ch_idx] >= FIRST_2GHZ_HT_MINUS) + flags &= ~NL80211_RRF_NO_HT40MINUS; + } else if (nvm_flags & NVM_CHANNEL_40MHZ) { + if ((ch_idx - NUM_2GHZ_CHANNELS) % 2 == 0) + flags &= ~NL80211_RRF_NO_HT40PLUS; + else + flags &= ~NL80211_RRF_NO_HT40MINUS; + } + + if (!(nvm_flags & NVM_CHANNEL_80MHZ)) + flags |= NL80211_RRF_NO_80MHZ; + if (!(nvm_flags & NVM_CHANNEL_160MHZ)) + flags |= NL80211_RRF_NO_160MHZ; + + if (!(nvm_flags & NVM_CHANNEL_ACTIVE)) + flags |= NL80211_RRF_NO_IR; + + if (nvm_flags & NVM_CHANNEL_RADAR) + flags |= NL80211_RRF_DFS; + + if (nvm_flags & NVM_CHANNEL_INDOOR_ONLY) + flags |= NL80211_RRF_NO_OUTDOOR; + + /* Set the GO concurrent flag only in case that NO_IR is set. + * Otherwise it is meaningless + */ + if ((nvm_flags & NVM_CHANNEL_GO_CONCURRENT) && + (flags & NL80211_RRF_NO_IR)) + flags |= NL80211_RRF_GO_CONCURRENT; + + /* + * reg_capa is per regulatory domain so apply it for every channel + */ + if (ch_idx >= NUM_2GHZ_CHANNELS) { + if (!reg_capa.allow_40mhz) + flags |= NL80211_RRF_NO_HT40; + + if (!reg_capa.allow_80mhz) + flags |= NL80211_RRF_NO_80MHZ; + + if (!reg_capa.allow_160mhz) + flags |= NL80211_RRF_NO_160MHZ; + } + if (reg_capa.disable_11ax) + flags |= NL80211_RRF_NO_HE; + + return flags; +} + +static struct iwl_reg_capa iwl_get_reg_capa(u16 flags, u8 resp_ver) +{ + struct iwl_reg_capa reg_capa; + + if (resp_ver >= REG_CAPA_V2_RESP_VER) { + reg_capa.allow_40mhz = flags & REG_CAPA_V2_40MHZ_ALLOWED; + reg_capa.allow_80mhz = flags & REG_CAPA_V2_80MHZ_ALLOWED; + reg_capa.allow_160mhz = flags & REG_CAPA_V2_160MHZ_ALLOWED; + reg_capa.disable_11ax = flags & REG_CAPA_V2_11AX_DISABLED; + } else { + reg_capa.allow_40mhz = !(flags & REG_CAPA_40MHZ_FORBIDDEN); + reg_capa.allow_80mhz = flags & REG_CAPA_80MHZ_ALLOWED; + reg_capa.allow_160mhz = flags & REG_CAPA_160MHZ_ALLOWED; + reg_capa.disable_11ax = flags & REG_CAPA_11AX_DISABLED; + } + return reg_capa; +} + +struct ieee80211_regdomain * +iwl_parse_nvm_mcc_info(struct device *dev, const struct iwl_cfg *cfg, + int num_of_ch, __le32 *channels, u16 fw_mcc, + u16 geo_info, u16 cap, u8 resp_ver) +{ + int ch_idx; + u16 ch_flags; + u32 reg_rule_flags, prev_reg_rule_flags = 0; + const u16 *nvm_chan; + struct ieee80211_regdomain *regd, *copy_rd; + struct ieee80211_reg_rule *rule; + enum nl80211_band band; + int center_freq, prev_center_freq = 0; + int valid_rules = 0; + bool new_rule; + int max_num_ch; + struct iwl_reg_capa reg_capa; + + if (cfg->uhb_supported) { + max_num_ch = IWL_NVM_NUM_CHANNELS_UHB; + nvm_chan = iwl_uhb_nvm_channels; + } else if (cfg->nvm_type == IWL_NVM_EXT) { + max_num_ch = IWL_NVM_NUM_CHANNELS_EXT; + nvm_chan = iwl_ext_nvm_channels; + } else { + max_num_ch = IWL_NVM_NUM_CHANNELS; + nvm_chan = iwl_nvm_channels; + } + + if (num_of_ch > max_num_ch) { + IWL_DEBUG_DEV(dev, IWL_DL_LAR, + "Num of channels (%d) is greater than expected. Truncating to %d\n", + num_of_ch, max_num_ch); + num_of_ch = max_num_ch; + } + + if (WARN_ON_ONCE(num_of_ch > NL80211_MAX_SUPP_REG_RULES)) + return ERR_PTR(-EINVAL); + + IWL_DEBUG_DEV(dev, IWL_DL_LAR, "building regdom for %d channels\n", + num_of_ch); + + /* build a regdomain rule for every valid channel */ + regd = kzalloc(struct_size(regd, reg_rules, num_of_ch), GFP_KERNEL); + if (!regd) + return ERR_PTR(-ENOMEM); + + /* set alpha2 from FW. */ + regd->alpha2[0] = fw_mcc >> 8; + regd->alpha2[1] = fw_mcc & 0xff; + + /* parse regulatory capability flags */ + reg_capa = iwl_get_reg_capa(cap, resp_ver); + + for (ch_idx = 0; ch_idx < num_of_ch; ch_idx++) { + ch_flags = (u16)__le32_to_cpup(channels + ch_idx); + band = iwl_nl80211_band_from_channel_idx(ch_idx); + center_freq = ieee80211_channel_to_frequency(nvm_chan[ch_idx], + band); + new_rule = false; + + if (!(ch_flags & NVM_CHANNEL_VALID)) { + iwl_nvm_print_channel_flags(dev, IWL_DL_LAR, + nvm_chan[ch_idx], ch_flags); + continue; + } + + reg_rule_flags = iwl_nvm_get_regdom_bw_flags(nvm_chan, ch_idx, + ch_flags, reg_capa, + cfg); + + /* we can't continue the same rule */ + if (ch_idx == 0 || prev_reg_rule_flags != reg_rule_flags || + center_freq - prev_center_freq > 20) { + valid_rules++; + new_rule = true; + } + + rule = ®d->reg_rules[valid_rules - 1]; + + if (new_rule) + rule->freq_range.start_freq_khz = + MHZ_TO_KHZ(center_freq - 10); + + rule->freq_range.end_freq_khz = MHZ_TO_KHZ(center_freq + 10); + + /* this doesn't matter - not used by FW */ + rule->power_rule.max_antenna_gain = DBI_TO_MBI(6); + rule->power_rule.max_eirp = + DBM_TO_MBM(IWL_DEFAULT_MAX_TX_POWER); + + rule->flags = reg_rule_flags; + + /* rely on auto-calculation to merge BW of contiguous chans */ + rule->flags |= NL80211_RRF_AUTO_BW; + rule->freq_range.max_bandwidth_khz = 0; + + prev_center_freq = center_freq; + prev_reg_rule_flags = reg_rule_flags; + + iwl_nvm_print_channel_flags(dev, IWL_DL_LAR, + nvm_chan[ch_idx], ch_flags); + + if (!(geo_info & GEO_WMM_ETSI_5GHZ_INFO) || + band == NL80211_BAND_2GHZ) + continue; + + reg_query_regdb_wmm(regd->alpha2, center_freq, rule); + } + + /* + * Certain firmware versions might report no valid channels + * if booted in RF-kill, i.e. not all calibrations etc. are + * running. We'll get out of this situation later when the + * rfkill is removed and we update the regdomain again, but + * since cfg80211 doesn't accept an empty regdomain, add a + * dummy (unusable) rule here in this case so we can init. + */ + if (!valid_rules) { + valid_rules = 1; + rule = ®d->reg_rules[valid_rules - 1]; + rule->freq_range.start_freq_khz = MHZ_TO_KHZ(2412); + rule->freq_range.end_freq_khz = MHZ_TO_KHZ(2413); + rule->freq_range.max_bandwidth_khz = MHZ_TO_KHZ(1); + rule->power_rule.max_antenna_gain = DBI_TO_MBI(6); + rule->power_rule.max_eirp = + DBM_TO_MBM(IWL_DEFAULT_MAX_TX_POWER); + } + + regd->n_reg_rules = valid_rules; + + /* + * Narrow down regdom for unused regulatory rules to prevent hole + * between reg rules to wmm rules. + */ + copy_rd = kmemdup(regd, struct_size(regd, reg_rules, valid_rules), + GFP_KERNEL); + if (!copy_rd) + copy_rd = ERR_PTR(-ENOMEM); + + kfree(regd); + return copy_rd; +} +IWL_EXPORT_SYMBOL(iwl_parse_nvm_mcc_info); + +#define IWL_MAX_NVM_SECTION_SIZE 0x1b58 +#define IWL_MAX_EXT_NVM_SECTION_SIZE 0x1ffc +#define MAX_NVM_FILE_LEN 16384 + +void iwl_nvm_fixups(u32 hw_id, unsigned int section, u8 *data, + unsigned int len) +{ +#define IWL_4165_DEVICE_ID 0x5501 +#define NVM_SKU_CAP_MIMO_DISABLE BIT(5) + + if (section == NVM_SECTION_TYPE_PHY_SKU && + hw_id == IWL_4165_DEVICE_ID && data && len >= 5 && + (data[4] & NVM_SKU_CAP_MIMO_DISABLE)) + /* OTP 0x52 bug work around: it's a 1x1 device */ + data[3] = ANT_B | (ANT_B << 4); +} +IWL_EXPORT_SYMBOL(iwl_nvm_fixups); + +/* + * Reads external NVM from a file into mvm->nvm_sections + * + * HOW TO CREATE THE NVM FILE FORMAT: + * ------------------------------ + * 1. create hex file, format: + * 3800 -> header + * 0000 -> header + * 5a40 -> data + * + * rev - 6 bit (word1) + * len - 10 bit (word1) + * id - 4 bit (word2) + * rsv - 12 bit (word2) + * + * 2. flip 8bits with 8 bits per line to get the right NVM file format + * + * 3. create binary file from the hex file + * + * 4. save as "iNVM_xxx.bin" under /lib/firmware + */ +int iwl_read_external_nvm(struct iwl_trans *trans, + const char *nvm_file_name, + struct iwl_nvm_section *nvm_sections) +{ + int ret, section_size; + u16 section_id; + const struct firmware *fw_entry; + const struct { + __le16 word1; + __le16 word2; + u8 data[]; + } *file_sec; + const u8 *eof; + u8 *temp; + int max_section_size; + const __le32 *dword_buff; + +#define NVM_WORD1_LEN(x) (8 * (x & 0x03FF)) +#define NVM_WORD2_ID(x) (x >> 12) +#define EXT_NVM_WORD2_LEN(x) (2 * (((x) & 0xFF) << 8 | (x) >> 8)) +#define EXT_NVM_WORD1_ID(x) ((x) >> 4) +#define NVM_HEADER_0 (0x2A504C54) +#define NVM_HEADER_1 (0x4E564D2A) +#define NVM_HEADER_SIZE (4 * sizeof(u32)) + + IWL_DEBUG_EEPROM(trans->dev, "Read from external NVM\n"); + + /* Maximal size depends on NVM version */ + if (trans->cfg->nvm_type != IWL_NVM_EXT) + max_section_size = IWL_MAX_NVM_SECTION_SIZE; + else + max_section_size = IWL_MAX_EXT_NVM_SECTION_SIZE; + + /* + * Obtain NVM image via request_firmware. Since we already used + * request_firmware_nowait() for the firmware binary load and only + * get here after that we assume the NVM request can be satisfied + * synchronously. + */ + ret = request_firmware(&fw_entry, nvm_file_name, trans->dev); + if (ret) { + IWL_ERR(trans, "ERROR: %s isn't available %d\n", + nvm_file_name, ret); + return ret; + } + + IWL_INFO(trans, "Loaded NVM file %s (%zu bytes)\n", + nvm_file_name, fw_entry->size); + + if (fw_entry->size > MAX_NVM_FILE_LEN) { + IWL_ERR(trans, "NVM file too large\n"); + ret = -EINVAL; + goto out; + } + + eof = fw_entry->data + fw_entry->size; + dword_buff = (const __le32 *)fw_entry->data; + + /* some NVM file will contain a header. + * The header is identified by 2 dwords header as follow: + * dword[0] = 0x2A504C54 + * dword[1] = 0x4E564D2A + * + * This header must be skipped when providing the NVM data to the FW. + */ + if (fw_entry->size > NVM_HEADER_SIZE && + dword_buff[0] == cpu_to_le32(NVM_HEADER_0) && + dword_buff[1] == cpu_to_le32(NVM_HEADER_1)) { + file_sec = (const void *)(fw_entry->data + NVM_HEADER_SIZE); + IWL_INFO(trans, "NVM Version %08X\n", le32_to_cpu(dword_buff[2])); + IWL_INFO(trans, "NVM Manufacturing date %08X\n", + le32_to_cpu(dword_buff[3])); + + /* nvm file validation, dword_buff[2] holds the file version */ + if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_8000 && + trans->hw_rev_step == SILICON_C_STEP && + le32_to_cpu(dword_buff[2]) < 0xE4A) { + ret = -EFAULT; + goto out; + } + } else { + file_sec = (const void *)fw_entry->data; + } + + while (true) { + if (file_sec->data > eof) { + IWL_ERR(trans, + "ERROR - NVM file too short for section header\n"); + ret = -EINVAL; + break; + } + + /* check for EOF marker */ + if (!file_sec->word1 && !file_sec->word2) { + ret = 0; + break; + } + + if (trans->cfg->nvm_type != IWL_NVM_EXT) { + section_size = + 2 * NVM_WORD1_LEN(le16_to_cpu(file_sec->word1)); + section_id = NVM_WORD2_ID(le16_to_cpu(file_sec->word2)); + } else { + section_size = 2 * EXT_NVM_WORD2_LEN( + le16_to_cpu(file_sec->word2)); + section_id = EXT_NVM_WORD1_ID( + le16_to_cpu(file_sec->word1)); + } + + if (section_size > max_section_size) { + IWL_ERR(trans, "ERROR - section too large (%d)\n", + section_size); + ret = -EINVAL; + break; + } + + if (!section_size) { + IWL_ERR(trans, "ERROR - section empty\n"); + ret = -EINVAL; + break; + } + + if (file_sec->data + section_size > eof) { + IWL_ERR(trans, + "ERROR - NVM file too short for section (%d bytes)\n", + section_size); + ret = -EINVAL; + break; + } + + if (WARN(section_id >= NVM_MAX_NUM_SECTIONS, + "Invalid NVM section ID %d\n", section_id)) { + ret = -EINVAL; + break; + } + + temp = kmemdup(file_sec->data, section_size, GFP_KERNEL); + if (!temp) { + ret = -ENOMEM; + break; + } + + iwl_nvm_fixups(trans->hw_id, section_id, temp, section_size); + + kfree(nvm_sections[section_id].data); + nvm_sections[section_id].data = temp; + nvm_sections[section_id].length = section_size; + + /* advance to the next section */ + file_sec = (const void *)(file_sec->data + section_size); + } +out: + release_firmware(fw_entry); + return ret; +} +IWL_EXPORT_SYMBOL(iwl_read_external_nvm); + +struct iwl_nvm_data *iwl_get_nvm(struct iwl_trans *trans, + const struct iwl_fw *fw) +{ + struct iwl_nvm_get_info cmd = {}; + struct iwl_nvm_data *nvm; + struct iwl_host_cmd hcmd = { + .flags = CMD_WANT_SKB | CMD_SEND_IN_RFKILL, + .data = { &cmd, }, + .len = { sizeof(cmd) }, + .id = WIDE_ID(REGULATORY_AND_NVM_GROUP, NVM_GET_INFO) + }; + int ret; + bool empty_otp; + u32 mac_flags; + u32 sbands_flags = 0; + /* + * All the values in iwl_nvm_get_info_rsp v4 are the same as + * in v3, except for the channel profile part of the + * regulatory. So we can just access the new struct, with the + * exception of the latter. + */ + struct iwl_nvm_get_info_rsp *rsp; + struct iwl_nvm_get_info_rsp_v3 *rsp_v3; + bool v4 = fw_has_api(&fw->ucode_capa, + IWL_UCODE_TLV_API_REGULATORY_NVM_INFO); + size_t rsp_size = v4 ? sizeof(*rsp) : sizeof(*rsp_v3); + void *channel_profile; + + ret = iwl_trans_send_cmd(trans, &hcmd); + if (ret) + return ERR_PTR(ret); + + if (WARN(iwl_rx_packet_payload_len(hcmd.resp_pkt) != rsp_size, + "Invalid payload len in NVM response from FW %d", + iwl_rx_packet_payload_len(hcmd.resp_pkt))) { + ret = -EINVAL; + goto out; + } + + rsp = (void *)hcmd.resp_pkt->data; + empty_otp = !!(le32_to_cpu(rsp->general.flags) & + NVM_GENERAL_FLAGS_EMPTY_OTP); + if (empty_otp) + IWL_INFO(trans, "OTP is empty\n"); + + nvm = kzalloc(struct_size(nvm, channels, IWL_NUM_CHANNELS), GFP_KERNEL); + if (!nvm) { + ret = -ENOMEM; + goto out; + } + + iwl_set_hw_address_from_csr(trans, nvm); + /* TODO: if platform NVM has MAC address - override it here */ + + if (!is_valid_ether_addr(nvm->hw_addr)) { + IWL_ERR(trans, "no valid mac address was found\n"); + ret = -EINVAL; + goto err_free; + } + + IWL_INFO(trans, "base HW address: %pM\n", nvm->hw_addr); + + /* Initialize general data */ + nvm->nvm_version = le16_to_cpu(rsp->general.nvm_version); + nvm->n_hw_addrs = rsp->general.n_hw_addrs; + if (nvm->n_hw_addrs == 0) + IWL_WARN(trans, + "Firmware declares no reserved mac addresses. OTP is empty: %d\n", + empty_otp); + + /* Initialize MAC sku data */ + mac_flags = le32_to_cpu(rsp->mac_sku.mac_sku_flags); + nvm->sku_cap_11ac_enable = + !!(mac_flags & NVM_MAC_SKU_FLAGS_802_11AC_ENABLED); + nvm->sku_cap_11n_enable = + !!(mac_flags & NVM_MAC_SKU_FLAGS_802_11N_ENABLED); + nvm->sku_cap_11ax_enable = + !!(mac_flags & NVM_MAC_SKU_FLAGS_802_11AX_ENABLED); + nvm->sku_cap_band_24ghz_enable = + !!(mac_flags & NVM_MAC_SKU_FLAGS_BAND_2_4_ENABLED); + nvm->sku_cap_band_52ghz_enable = + !!(mac_flags & NVM_MAC_SKU_FLAGS_BAND_5_2_ENABLED); + nvm->sku_cap_mimo_disabled = + !!(mac_flags & NVM_MAC_SKU_FLAGS_MIMO_DISABLED); + + /* Initialize PHY sku data */ + nvm->valid_tx_ant = (u8)le32_to_cpu(rsp->phy_sku.tx_chains); + nvm->valid_rx_ant = (u8)le32_to_cpu(rsp->phy_sku.rx_chains); + + if (le32_to_cpu(rsp->regulatory.lar_enabled) && + fw_has_capa(&fw->ucode_capa, + IWL_UCODE_TLV_CAPA_LAR_SUPPORT)) { + nvm->lar_enabled = true; + sbands_flags |= IWL_NVM_SBANDS_FLAGS_LAR; + } + + rsp_v3 = (void *)rsp; + channel_profile = v4 ? (void *)rsp->regulatory.channel_profile : + (void *)rsp_v3->regulatory.channel_profile; + + iwl_init_sbands(trans, nvm, + channel_profile, + nvm->valid_tx_ant & fw->valid_tx_ant, + nvm->valid_rx_ant & fw->valid_rx_ant, + sbands_flags, v4, fw); + + iwl_free_resp(&hcmd); + return nvm; + +err_free: + kfree(nvm); +out: + iwl_free_resp(&hcmd); + return ERR_PTR(ret); +} +IWL_EXPORT_SYMBOL(iwl_get_nvm); diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h new file mode 100644 index 000000000..e01f7751c --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2015, 2018-2021 Intel Corporation + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_nvm_parse_h__ +#define __iwl_nvm_parse_h__ + +#include <net/cfg80211.h> +#include "iwl-eeprom-parse.h" +#include "mei/iwl-mei.h" + +/** + * enum iwl_nvm_sbands_flags - modification flags for the channel profiles + * + * @IWL_NVM_SBANDS_FLAGS_LAR: LAR is enabled + * @IWL_NVM_SBANDS_FLAGS_NO_WIDE_IN_5GHZ: disallow 40, 80 and 160MHz on 5GHz + */ +enum iwl_nvm_sbands_flags { + IWL_NVM_SBANDS_FLAGS_LAR = BIT(0), + IWL_NVM_SBANDS_FLAGS_NO_WIDE_IN_5GHZ = BIT(1), +}; + +/** + * iwl_parse_nvm_data - parse NVM data and return values + * + * This function parses all NVM values we need and then + * returns a (newly allocated) struct containing all the + * relevant values for driver use. The struct must be freed + * later with iwl_free_nvm_data(). + */ +struct iwl_nvm_data * +iwl_parse_nvm_data(struct iwl_trans *trans, const struct iwl_cfg *cfg, + const struct iwl_fw *fw, + const __be16 *nvm_hw, const __le16 *nvm_sw, + const __le16 *nvm_calib, const __le16 *regulatory, + const __le16 *mac_override, const __le16 *phy_sku, + u8 tx_chains, u8 rx_chains); + +/** + * iwl_parse_mcc_info - parse MCC (mobile country code) info coming from FW + * + * This function parses the regulatory channel data received as a + * MCC_UPDATE_CMD command. It returns a newly allocation regulatory domain, + * to be fed into the regulatory core. In case the geo_info is set handle + * accordingly. An ERR_PTR is returned on error. + * If not given to the regulatory core, the user is responsible for freeing + * the regdomain returned here with kfree. + */ +struct ieee80211_regdomain * +iwl_parse_nvm_mcc_info(struct device *dev, const struct iwl_cfg *cfg, + int num_of_ch, __le32 *channels, u16 fw_mcc, + u16 geo_info, u16 cap, u8 resp_ver); + +/** + * struct iwl_nvm_section - describes an NVM section in memory. + * + * This struct holds an NVM section read from the NIC using NVM_ACCESS_CMD, + * and saved for later use by the driver. Not all NVM sections are saved + * this way, only the needed ones. + */ +struct iwl_nvm_section { + u16 length; + const u8 *data; +}; + +/** + * iwl_read_external_nvm - Reads external NVM from a file into nvm_sections + */ +int iwl_read_external_nvm(struct iwl_trans *trans, + const char *nvm_file_name, + struct iwl_nvm_section *nvm_sections); +void iwl_nvm_fixups(u32 hw_id, unsigned int section, u8 *data, + unsigned int len); + +/** + * iwl_get_nvm - retrieve NVM data from firmware + * + * Allocates a new iwl_nvm_data structure, fills it with + * NVM data, and returns it to caller. + */ +struct iwl_nvm_data *iwl_get_nvm(struct iwl_trans *trans, + const struct iwl_fw *fw); + +/** + * iwl_parse_mei_nvm_data - parse the mei_nvm_data and get an iwl_nvm_data + */ +struct iwl_nvm_data * +iwl_parse_mei_nvm_data(struct iwl_trans *trans, const struct iwl_cfg *cfg, + const struct iwl_mei_nvm *mei_nvm, + const struct iwl_fw *fw); + +#endif /* __iwl_nvm_parse_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-op-mode.h b/drivers/net/wireless/intel/iwlwifi/iwl-op-mode.h new file mode 100644 index 000000000..af5f9b210 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-op-mode.h @@ -0,0 +1,215 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015 Intel Deutschland GmbH + */ +#ifndef __iwl_op_mode_h__ +#define __iwl_op_mode_h__ + +#include <linux/netdevice.h> +#include <linux/debugfs.h> +#include "iwl-dbg-tlv.h" + +struct iwl_op_mode; +struct iwl_trans; +struct sk_buff; +struct iwl_device_cmd; +struct iwl_rx_cmd_buffer; +struct iwl_fw; +struct iwl_cfg; + +/** + * DOC: Operational mode - what is it ? + * + * The operational mode (a.k.a. op_mode) is the layer that implements + * mac80211's handlers. It knows two APIs: mac80211's and the fw's. It uses + * the transport API to access the HW. The op_mode doesn't need to know how the + * underlying HW works, since the transport layer takes care of that. + * + * There can be several op_mode: i.e. different fw APIs will require two + * different op_modes. This is why the op_mode is virtualized. + */ + +/** + * DOC: Life cycle of the Operational mode + * + * The operational mode has a very simple life cycle. + * + * 1) The driver layer (iwl-drv.c) chooses the op_mode based on the + * capabilities advertised by the fw file (in TLV format). + * 2) The driver layer starts the op_mode (ops->start) + * 3) The op_mode registers mac80211 + * 4) The op_mode is governed by mac80211 + * 5) The driver layer stops the op_mode + */ + +/** + * struct iwl_op_mode_ops - op_mode specific operations + * + * The op_mode exports its ops so that external components can start it and + * interact with it. The driver layer typically calls the start and stop + * handlers, the transport layer calls the others. + * + * All the handlers MUST be implemented, except @rx_rss which can be left + * out *iff* the opmode will never run on hardware with multi-queue capability. + * + * @start: start the op_mode. The transport layer is already allocated. + * May sleep + * @stop: stop the op_mode. Must free all the memory allocated. + * May sleep + * @rx: Rx notification to the op_mode. rxb is the Rx buffer itself. Cmd is the + * HCMD this Rx responds to. Can't sleep. + * @rx_rss: data queue RX notification to the op_mode, for (data) notifications + * received on the RSS queue(s). The queue parameter indicates which of the + * RSS queues received this frame; it will always be non-zero. + * This method must not sleep. + * @async_cb: called when an ASYNC command with CMD_WANT_ASYNC_CALLBACK set + * completes. Must be atomic. + * @queue_full: notifies that a HW queue is full. + * Must be atomic and called with BH disabled. + * @queue_not_full: notifies that a HW queue is not full any more. + * Must be atomic and called with BH disabled. + * @hw_rf_kill:notifies of a change in the HW rf kill switch. True means that + * the radio is killed. Return %true if the device should be stopped by + * the transport immediately after the call. May sleep. + * @free_skb: allows the transport layer to free skbs that haven't been + * reclaimed by the op_mode. This can happen when the driver is freed and + * there are Tx packets pending in the transport layer. + * Must be atomic + * @nic_error: error notification. Must be atomic and must be called with BH + * disabled, unless the sync parameter is true. + * @cmd_queue_full: Called when the command queue gets full. Must be atomic and + * called with BH disabled. + * @nic_config: configure NIC, called before firmware is started. + * May sleep + * @wimax_active: invoked when WiMax becomes active. May sleep + * @time_point: called when transport layer wants to collect debug data + */ +struct iwl_op_mode_ops { + struct iwl_op_mode *(*start)(struct iwl_trans *trans, + const struct iwl_cfg *cfg, + const struct iwl_fw *fw, + struct dentry *dbgfs_dir); + void (*stop)(struct iwl_op_mode *op_mode); + void (*rx)(struct iwl_op_mode *op_mode, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb); + void (*rx_rss)(struct iwl_op_mode *op_mode, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, unsigned int queue); + void (*async_cb)(struct iwl_op_mode *op_mode, + const struct iwl_device_cmd *cmd); + void (*queue_full)(struct iwl_op_mode *op_mode, int queue); + void (*queue_not_full)(struct iwl_op_mode *op_mode, int queue); + bool (*hw_rf_kill)(struct iwl_op_mode *op_mode, bool state); + void (*free_skb)(struct iwl_op_mode *op_mode, struct sk_buff *skb); + void (*nic_error)(struct iwl_op_mode *op_mode, bool sync); + void (*cmd_queue_full)(struct iwl_op_mode *op_mode); + void (*nic_config)(struct iwl_op_mode *op_mode); + void (*wimax_active)(struct iwl_op_mode *op_mode); + void (*time_point)(struct iwl_op_mode *op_mode, + enum iwl_fw_ini_time_point tp_id, + union iwl_dbg_tlv_tp_data *tp_data); +}; + +int iwl_opmode_register(const char *name, const struct iwl_op_mode_ops *ops); +void iwl_opmode_deregister(const char *name); + +/** + * struct iwl_op_mode - operational mode + * @ops: pointer to its own ops + * + * This holds an implementation of the mac80211 / fw API. + */ +struct iwl_op_mode { + const struct iwl_op_mode_ops *ops; + + char op_mode_specific[] __aligned(sizeof(void *)); +}; + +static inline void iwl_op_mode_stop(struct iwl_op_mode *op_mode) +{ + might_sleep(); + op_mode->ops->stop(op_mode); +} + +static inline void iwl_op_mode_rx(struct iwl_op_mode *op_mode, + struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb) +{ + return op_mode->ops->rx(op_mode, napi, rxb); +} + +static inline void iwl_op_mode_rx_rss(struct iwl_op_mode *op_mode, + struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, + unsigned int queue) +{ + op_mode->ops->rx_rss(op_mode, napi, rxb, queue); +} + +static inline void iwl_op_mode_async_cb(struct iwl_op_mode *op_mode, + const struct iwl_device_cmd *cmd) +{ + if (op_mode->ops->async_cb) + op_mode->ops->async_cb(op_mode, cmd); +} + +static inline void iwl_op_mode_queue_full(struct iwl_op_mode *op_mode, + int queue) +{ + op_mode->ops->queue_full(op_mode, queue); +} + +static inline void iwl_op_mode_queue_not_full(struct iwl_op_mode *op_mode, + int queue) +{ + op_mode->ops->queue_not_full(op_mode, queue); +} + +static inline bool __must_check +iwl_op_mode_hw_rf_kill(struct iwl_op_mode *op_mode, bool state) +{ + might_sleep(); + return op_mode->ops->hw_rf_kill(op_mode, state); +} + +static inline void iwl_op_mode_free_skb(struct iwl_op_mode *op_mode, + struct sk_buff *skb) +{ + if (WARN_ON_ONCE(!op_mode)) + return; + op_mode->ops->free_skb(op_mode, skb); +} + +static inline void iwl_op_mode_nic_error(struct iwl_op_mode *op_mode, bool sync) +{ + op_mode->ops->nic_error(op_mode, sync); +} + +static inline void iwl_op_mode_cmd_queue_full(struct iwl_op_mode *op_mode) +{ + op_mode->ops->cmd_queue_full(op_mode); +} + +static inline void iwl_op_mode_nic_config(struct iwl_op_mode *op_mode) +{ + might_sleep(); + op_mode->ops->nic_config(op_mode); +} + +static inline void iwl_op_mode_wimax_active(struct iwl_op_mode *op_mode) +{ + might_sleep(); + op_mode->ops->wimax_active(op_mode); +} + +static inline void iwl_op_mode_time_point(struct iwl_op_mode *op_mode, + enum iwl_fw_ini_time_point tp_id, + union iwl_dbg_tlv_tp_data *tp_data) +{ + if (!op_mode || !op_mode->ops || !op_mode->ops->time_point) + return; + op_mode->ops->time_point(op_mode, tp_id, tp_data); +} + +#endif /* __iwl_op_mode_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-phy-db.c b/drivers/net/wireless/intel/iwlwifi/iwl-phy-db.c new file mode 100644 index 000000000..0a93ac769 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-phy-db.c @@ -0,0 +1,438 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2020-2021 Intel Corporation + * Copyright (C) 2016 Intel Deutschland GmbH + */ +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/export.h> + +#include "iwl-drv.h" +#include "iwl-phy-db.h" +#include "iwl-debug.h" +#include "iwl-op-mode.h" +#include "iwl-trans.h" + +struct iwl_phy_db_entry { + u16 size; + u8 *data; +}; + +/** + * struct iwl_phy_db - stores phy configuration and calibration data. + * + * @cfg: phy configuration. + * @calib_nch: non channel specific calibration data. + * @n_group_papd: number of entries in papd channel group. + * @calib_ch_group_papd: calibration data related to papd channel group. + * @n_group_txp: number of entries in tx power channel group. + * @calib_ch_group_txp: calibration data related to tx power chanel group. + * @trans: transport layer + */ +struct iwl_phy_db { + struct iwl_phy_db_entry cfg; + struct iwl_phy_db_entry calib_nch; + int n_group_papd; + struct iwl_phy_db_entry *calib_ch_group_papd; + int n_group_txp; + struct iwl_phy_db_entry *calib_ch_group_txp; + + struct iwl_trans *trans; +}; + +enum iwl_phy_db_section_type { + IWL_PHY_DB_CFG = 1, + IWL_PHY_DB_CALIB_NCH, + IWL_PHY_DB_UNUSED, + IWL_PHY_DB_CALIB_CHG_PAPD, + IWL_PHY_DB_CALIB_CHG_TXP, + IWL_PHY_DB_MAX +}; + +#define PHY_DB_CMD 0x6c + +/* for parsing of tx power channel group data that comes from the firmware*/ +struct iwl_phy_db_chg_txp { + __le32 space; + __le16 max_channel_idx; +} __packed; + +struct iwl_phy_db *iwl_phy_db_init(struct iwl_trans *trans) +{ + struct iwl_phy_db *phy_db = kzalloc(sizeof(struct iwl_phy_db), + GFP_KERNEL); + + if (!phy_db) + return phy_db; + + phy_db->trans = trans; + + phy_db->n_group_txp = -1; + phy_db->n_group_papd = -1; + + /* TODO: add default values of the phy db. */ + return phy_db; +} +IWL_EXPORT_SYMBOL(iwl_phy_db_init); + +/* + * get phy db section: returns a pointer to a phy db section specified by + * type and channel group id. + */ +static struct iwl_phy_db_entry * +iwl_phy_db_get_section(struct iwl_phy_db *phy_db, + enum iwl_phy_db_section_type type, + u16 chg_id) +{ + if (!phy_db || type >= IWL_PHY_DB_MAX) + return NULL; + + switch (type) { + case IWL_PHY_DB_CFG: + return &phy_db->cfg; + case IWL_PHY_DB_CALIB_NCH: + return &phy_db->calib_nch; + case IWL_PHY_DB_CALIB_CHG_PAPD: + if (chg_id >= phy_db->n_group_papd) + return NULL; + return &phy_db->calib_ch_group_papd[chg_id]; + case IWL_PHY_DB_CALIB_CHG_TXP: + if (chg_id >= phy_db->n_group_txp) + return NULL; + return &phy_db->calib_ch_group_txp[chg_id]; + default: + return NULL; + } + return NULL; +} + +static void iwl_phy_db_free_section(struct iwl_phy_db *phy_db, + enum iwl_phy_db_section_type type, + u16 chg_id) +{ + struct iwl_phy_db_entry *entry = + iwl_phy_db_get_section(phy_db, type, chg_id); + if (!entry) + return; + + kfree(entry->data); + entry->data = NULL; + entry->size = 0; +} + +void iwl_phy_db_free(struct iwl_phy_db *phy_db) +{ + int i; + + if (!phy_db) + return; + + iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CFG, 0); + iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CALIB_NCH, 0); + + for (i = 0; i < phy_db->n_group_papd; i++) + iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CALIB_CHG_PAPD, i); + kfree(phy_db->calib_ch_group_papd); + + for (i = 0; i < phy_db->n_group_txp; i++) + iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CALIB_CHG_TXP, i); + kfree(phy_db->calib_ch_group_txp); + + kfree(phy_db); +} +IWL_EXPORT_SYMBOL(iwl_phy_db_free); + +int iwl_phy_db_set_section(struct iwl_phy_db *phy_db, + struct iwl_rx_packet *pkt) +{ + unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); + struct iwl_calib_res_notif_phy_db *phy_db_notif = + (struct iwl_calib_res_notif_phy_db *)pkt->data; + enum iwl_phy_db_section_type type; + u16 size; + struct iwl_phy_db_entry *entry; + u16 chg_id = 0; + + if (pkt_len < sizeof(*phy_db_notif)) + return -EINVAL; + + type = le16_to_cpu(phy_db_notif->type); + size = le16_to_cpu(phy_db_notif->length); + + if (pkt_len < sizeof(*phy_db_notif) + size) + return -EINVAL; + + if (!phy_db) + return -EINVAL; + + if (type == IWL_PHY_DB_CALIB_CHG_PAPD) { + chg_id = le16_to_cpup((__le16 *)phy_db_notif->data); + if (phy_db && !phy_db->calib_ch_group_papd) { + /* + * Firmware sends the largest index first, so we can use + * it to know how much we should allocate. + */ + phy_db->calib_ch_group_papd = kcalloc(chg_id + 1, + sizeof(struct iwl_phy_db_entry), + GFP_ATOMIC); + if (!phy_db->calib_ch_group_papd) + return -ENOMEM; + phy_db->n_group_papd = chg_id + 1; + } + } else if (type == IWL_PHY_DB_CALIB_CHG_TXP) { + chg_id = le16_to_cpup((__le16 *)phy_db_notif->data); + if (phy_db && !phy_db->calib_ch_group_txp) { + /* + * Firmware sends the largest index first, so we can use + * it to know how much we should allocate. + */ + phy_db->calib_ch_group_txp = kcalloc(chg_id + 1, + sizeof(struct iwl_phy_db_entry), + GFP_ATOMIC); + if (!phy_db->calib_ch_group_txp) + return -ENOMEM; + phy_db->n_group_txp = chg_id + 1; + } + } + + entry = iwl_phy_db_get_section(phy_db, type, chg_id); + if (!entry) + return -EINVAL; + + kfree(entry->data); + entry->data = kmemdup(phy_db_notif->data, size, GFP_ATOMIC); + if (!entry->data) { + entry->size = 0; + return -ENOMEM; + } + + entry->size = size; + + IWL_DEBUG_INFO(phy_db->trans, + "%s(%d): [PHYDB]SET: Type %d , Size: %d\n", + __func__, __LINE__, type, size); + + return 0; +} +IWL_EXPORT_SYMBOL(iwl_phy_db_set_section); + +static int is_valid_channel(u16 ch_id) +{ + if (ch_id <= 14 || + (36 <= ch_id && ch_id <= 64 && ch_id % 4 == 0) || + (100 <= ch_id && ch_id <= 140 && ch_id % 4 == 0) || + (145 <= ch_id && ch_id <= 165 && ch_id % 4 == 1)) + return 1; + return 0; +} + +static u8 ch_id_to_ch_index(u16 ch_id) +{ + if (WARN_ON(!is_valid_channel(ch_id))) + return 0xff; + + if (ch_id <= 14) + return ch_id - 1; + if (ch_id <= 64) + return (ch_id + 20) / 4; + if (ch_id <= 140) + return (ch_id - 12) / 4; + return (ch_id - 13) / 4; +} + + +static u16 channel_id_to_papd(u16 ch_id) +{ + if (WARN_ON(!is_valid_channel(ch_id))) + return 0xff; + + if (1 <= ch_id && ch_id <= 14) + return 0; + if (36 <= ch_id && ch_id <= 64) + return 1; + if (100 <= ch_id && ch_id <= 140) + return 2; + return 3; +} + +static u16 channel_id_to_txp(struct iwl_phy_db *phy_db, u16 ch_id) +{ + struct iwl_phy_db_chg_txp *txp_chg; + int i; + u8 ch_index = ch_id_to_ch_index(ch_id); + if (ch_index == 0xff) + return 0xff; + + for (i = 0; i < phy_db->n_group_txp; i++) { + txp_chg = (void *)phy_db->calib_ch_group_txp[i].data; + if (!txp_chg) + return 0xff; + /* + * Looking for the first channel group that its max channel is + * higher then wanted channel. + */ + if (le16_to_cpu(txp_chg->max_channel_idx) >= ch_index) + return i; + } + return 0xff; +} +static +int iwl_phy_db_get_section_data(struct iwl_phy_db *phy_db, + u32 type, u8 **data, u16 *size, u16 ch_id) +{ + struct iwl_phy_db_entry *entry; + u16 ch_group_id = 0; + + if (!phy_db) + return -EINVAL; + + /* find wanted channel group */ + if (type == IWL_PHY_DB_CALIB_CHG_PAPD) + ch_group_id = channel_id_to_papd(ch_id); + else if (type == IWL_PHY_DB_CALIB_CHG_TXP) + ch_group_id = channel_id_to_txp(phy_db, ch_id); + + entry = iwl_phy_db_get_section(phy_db, type, ch_group_id); + if (!entry) + return -EINVAL; + + *data = entry->data; + *size = entry->size; + + IWL_DEBUG_INFO(phy_db->trans, + "%s(%d): [PHYDB] GET: Type %d , Size: %d\n", + __func__, __LINE__, type, *size); + + return 0; +} + +static int iwl_send_phy_db_cmd(struct iwl_phy_db *phy_db, u16 type, + u16 length, void *data) +{ + struct iwl_phy_db_cmd phy_db_cmd; + struct iwl_host_cmd cmd = { + .id = PHY_DB_CMD, + }; + + IWL_DEBUG_INFO(phy_db->trans, + "Sending PHY-DB hcmd of type %d, of length %d\n", + type, length); + + /* Set phy db cmd variables */ + phy_db_cmd.type = cpu_to_le16(type); + phy_db_cmd.length = cpu_to_le16(length); + + /* Set hcmd variables */ + cmd.data[0] = &phy_db_cmd; + cmd.len[0] = sizeof(struct iwl_phy_db_cmd); + cmd.data[1] = data; + cmd.len[1] = length; + cmd.dataflags[1] = IWL_HCMD_DFL_NOCOPY; + + return iwl_trans_send_cmd(phy_db->trans, &cmd); +} + +static int iwl_phy_db_send_all_channel_groups( + struct iwl_phy_db *phy_db, + enum iwl_phy_db_section_type type, + u8 max_ch_groups) +{ + u16 i; + int err; + struct iwl_phy_db_entry *entry; + + /* Send all the channel specific groups to operational fw */ + for (i = 0; i < max_ch_groups; i++) { + entry = iwl_phy_db_get_section(phy_db, + type, + i); + if (!entry) + return -EINVAL; + + if (!entry->size) + continue; + + /* Send the requested PHY DB section */ + err = iwl_send_phy_db_cmd(phy_db, + type, + entry->size, + entry->data); + if (err) { + IWL_ERR(phy_db->trans, + "Can't SEND phy_db section %d (%d), err %d\n", + type, i, err); + return err; + } + + IWL_DEBUG_INFO(phy_db->trans, + "Sent PHY_DB HCMD, type = %d num = %d\n", + type, i); + } + + return 0; +} + +int iwl_send_phy_db_data(struct iwl_phy_db *phy_db) +{ + u8 *data = NULL; + u16 size = 0; + int err; + + IWL_DEBUG_INFO(phy_db->trans, + "Sending phy db data and configuration to runtime image\n"); + + /* Send PHY DB CFG section */ + err = iwl_phy_db_get_section_data(phy_db, IWL_PHY_DB_CFG, + &data, &size, 0); + if (err) { + IWL_ERR(phy_db->trans, "Cannot get Phy DB cfg section\n"); + return err; + } + + err = iwl_send_phy_db_cmd(phy_db, IWL_PHY_DB_CFG, size, data); + if (err) { + IWL_ERR(phy_db->trans, + "Cannot send HCMD of Phy DB cfg section\n"); + return err; + } + + err = iwl_phy_db_get_section_data(phy_db, IWL_PHY_DB_CALIB_NCH, + &data, &size, 0); + if (err) { + IWL_ERR(phy_db->trans, + "Cannot get Phy DB non specific channel section\n"); + return err; + } + + err = iwl_send_phy_db_cmd(phy_db, IWL_PHY_DB_CALIB_NCH, size, data); + if (err) { + IWL_ERR(phy_db->trans, + "Cannot send HCMD of Phy DB non specific channel section\n"); + return err; + } + + /* Send all the TXP channel specific data */ + err = iwl_phy_db_send_all_channel_groups(phy_db, + IWL_PHY_DB_CALIB_CHG_PAPD, + phy_db->n_group_papd); + if (err) { + IWL_ERR(phy_db->trans, + "Cannot send channel specific PAPD groups\n"); + return err; + } + + /* Send all the TXP channel specific data */ + err = iwl_phy_db_send_all_channel_groups(phy_db, + IWL_PHY_DB_CALIB_CHG_TXP, + phy_db->n_group_txp); + if (err) { + IWL_ERR(phy_db->trans, + "Cannot send channel specific TX power groups\n"); + return err; + } + + IWL_DEBUG_INFO(phy_db->trans, + "Finished sending phy db non channel data\n"); + return 0; +} +IWL_EXPORT_SYMBOL(iwl_send_phy_db_data); diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-phy-db.h b/drivers/net/wireless/intel/iwlwifi/iwl-phy-db.h new file mode 100644 index 000000000..0b2fdfa94 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-phy-db.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014 Intel Corporation + */ +#ifndef __IWL_PHYDB_H__ +#define __IWL_PHYDB_H__ + +#include <linux/types.h> + +#include "iwl-op-mode.h" +#include "iwl-trans.h" + +struct iwl_phy_db *iwl_phy_db_init(struct iwl_trans *trans); + +void iwl_phy_db_free(struct iwl_phy_db *phy_db); + +int iwl_phy_db_set_section(struct iwl_phy_db *phy_db, + struct iwl_rx_packet *pkt); + + +int iwl_send_phy_db_data(struct iwl_phy_db *phy_db); + +#endif /* __IWL_PHYDB_H__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-prph.h b/drivers/net/wireless/intel/iwlwifi/iwl-prph.h new file mode 100644 index 000000000..c5a306b01 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-prph.h @@ -0,0 +1,503 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016 Intel Deutschland GmbH + */ +#ifndef __iwl_prph_h__ +#define __iwl_prph_h__ +#include <linux/bitfield.h> + +/* + * Registers in this file are internal, not PCI bus memory mapped. + * Driver accesses these via HBUS_TARG_PRPH_* registers. + */ +#define PRPH_BASE (0x00000) +#define PRPH_END (0xFFFFF) + +/* APMG (power management) constants */ +#define APMG_BASE (PRPH_BASE + 0x3000) +#define APMG_CLK_CTRL_REG (APMG_BASE + 0x0000) +#define APMG_CLK_EN_REG (APMG_BASE + 0x0004) +#define APMG_CLK_DIS_REG (APMG_BASE + 0x0008) +#define APMG_PS_CTRL_REG (APMG_BASE + 0x000c) +#define APMG_PCIDEV_STT_REG (APMG_BASE + 0x0010) +#define APMG_RFKILL_REG (APMG_BASE + 0x0014) +#define APMG_RTC_INT_STT_REG (APMG_BASE + 0x001c) +#define APMG_RTC_INT_MSK_REG (APMG_BASE + 0x0020) +#define APMG_DIGITAL_SVR_REG (APMG_BASE + 0x0058) +#define APMG_ANALOG_SVR_REG (APMG_BASE + 0x006C) + +#define APMS_CLK_VAL_MRB_FUNC_MODE (0x00000001) +#define APMG_CLK_VAL_DMA_CLK_RQT (0x00000200) +#define APMG_CLK_VAL_BSM_CLK_RQT (0x00000800) + +#define APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS (0x00400000) +#define APMG_PS_CTRL_VAL_RESET_REQ (0x04000000) +#define APMG_PS_CTRL_MSK_PWR_SRC (0x03000000) +#define APMG_PS_CTRL_VAL_PWR_SRC_VMAIN (0x00000000) +#define APMG_PS_CTRL_VAL_PWR_SRC_VAUX (0x02000000) +#define APMG_SVR_VOLTAGE_CONFIG_BIT_MSK (0x000001E0) /* bit 8:5 */ +#define APMG_SVR_DIGITAL_VOLTAGE_1_32 (0x00000060) + +#define APMG_PCIDEV_STT_VAL_PERSIST_DIS (0x00000200) +#define APMG_PCIDEV_STT_VAL_L1_ACT_DIS (0x00000800) +#define APMG_PCIDEV_STT_VAL_WAKE_ME (0x00004000) + +#define APMG_RTC_INT_STT_RFKILL (0x10000000) + +/* Device system time */ +#define DEVICE_SYSTEM_TIME_REG 0xA0206C + +/* Device NMI register and value for 8000 family and lower hw's */ +#define DEVICE_SET_NMI_REG 0x00a01c30 +#define DEVICE_SET_NMI_VAL_DRV BIT(7) +/* Device NMI register and value for 9000 family and above hw's */ +#define UREG_NIC_SET_NMI_DRIVER 0x00a05c10 +#define UREG_NIC_SET_NMI_DRIVER_NMI_FROM_DRIVER BIT(24) +#define UREG_NIC_SET_NMI_DRIVER_RESET_HANDSHAKE (BIT(24) | BIT(25)) + +/* Shared registers (0x0..0x3ff, via target indirect or periphery */ +#define SHR_BASE 0x00a10000 + +/* Shared GP1 register */ +#define SHR_APMG_GP1_REG 0x01dc +#define SHR_APMG_GP1_REG_PRPH (SHR_BASE + SHR_APMG_GP1_REG) +#define SHR_APMG_GP1_WF_XTAL_LP_EN 0x00000004 +#define SHR_APMG_GP1_CHICKEN_BIT_SELECT 0x80000000 + +/* Shared DL_CFG register */ +#define SHR_APMG_DL_CFG_REG 0x01c4 +#define SHR_APMG_DL_CFG_REG_PRPH (SHR_BASE + SHR_APMG_DL_CFG_REG) +#define SHR_APMG_DL_CFG_RTCS_CLK_SELECTOR_MSK 0x000000c0 +#define SHR_APMG_DL_CFG_RTCS_CLK_INTERNAL_XTAL 0x00000080 +#define SHR_APMG_DL_CFG_DL_CLOCK_POWER_UP 0x00000100 + +/* Shared APMG_XTAL_CFG register */ +#define SHR_APMG_XTAL_CFG_REG 0x1c0 +#define SHR_APMG_XTAL_CFG_XTAL_ON_REQ 0x80000000 + +/* + * Device reset for family 8000 + * write to bit 24 in order to reset the CPU +*/ +#define RELEASE_CPU_RESET (0x300C) +#define RELEASE_CPU_RESET_BIT BIT(24) + +/***************************************************************************** + * 7000/3000 series SHR DTS addresses * + *****************************************************************************/ + +#define SHR_MISC_WFM_DTS_EN (0x00a10024) +#define DTSC_CFG_MODE (0x00a10604) +#define DTSC_VREF_AVG (0x00a10648) +#define DTSC_VREF5_AVG (0x00a1064c) +#define DTSC_CFG_MODE_PERIODIC (0x2) +#define DTSC_PTAT_AVG (0x00a10650) + + +/** + * Tx Scheduler + * + * The Tx Scheduler selects the next frame to be transmitted, choosing TFDs + * (Transmit Frame Descriptors) from up to 16 circular Tx queues resident in + * host DRAM. It steers each frame's Tx command (which contains the frame + * data) into one of up to 7 prioritized Tx DMA FIFO channels within the + * device. A queue maps to only one (selectable by driver) Tx DMA channel, + * but one DMA channel may take input from several queues. + * + * Tx DMA FIFOs have dedicated purposes. + * + * For 5000 series and up, they are used differently + * (cf. iwl5000_default_queue_to_tx_fifo in iwl-5000.c): + * + * 0 -- EDCA BK (background) frames, lowest priority + * 1 -- EDCA BE (best effort) frames, normal priority + * 2 -- EDCA VI (video) frames, higher priority + * 3 -- EDCA VO (voice) and management frames, highest priority + * 4 -- unused + * 5 -- unused + * 6 -- unused + * 7 -- Commands + * + * Driver should normally map queues 0-6 to Tx DMA/FIFO channels 0-6. + * In addition, driver can map the remaining queues to Tx DMA/FIFO + * channels 0-3 to support 11n aggregation via EDCA DMA channels. + * + * The driver sets up each queue to work in one of two modes: + * + * 1) Scheduler-Ack, in which the scheduler automatically supports a + * block-ack (BA) window of up to 64 TFDs. In this mode, each queue + * contains TFDs for a unique combination of Recipient Address (RA) + * and Traffic Identifier (TID), that is, traffic of a given + * Quality-Of-Service (QOS) priority, destined for a single station. + * + * In scheduler-ack mode, the scheduler keeps track of the Tx status of + * each frame within the BA window, including whether it's been transmitted, + * and whether it's been acknowledged by the receiving station. The device + * automatically processes block-acks received from the receiving STA, + * and reschedules un-acked frames to be retransmitted (successful + * Tx completion may end up being out-of-order). + * + * The driver must maintain the queue's Byte Count table in host DRAM + * for this mode. + * This mode does not support fragmentation. + * + * 2) FIFO (a.k.a. non-Scheduler-ACK), in which each TFD is processed in order. + * The device may automatically retry Tx, but will retry only one frame + * at a time, until receiving ACK from receiving station, or reaching + * retry limit and giving up. + * + * The command queue (#4/#9) must use this mode! + * This mode does not require use of the Byte Count table in host DRAM. + * + * Driver controls scheduler operation via 3 means: + * 1) Scheduler registers + * 2) Shared scheduler data base in internal SRAM + * 3) Shared data in host DRAM + * + * Initialization: + * + * When loading, driver should allocate memory for: + * 1) 16 TFD circular buffers, each with space for (typically) 256 TFDs. + * 2) 16 Byte Count circular buffers in 16 KBytes contiguous memory + * (1024 bytes for each queue). + * + * After receiving "Alive" response from uCode, driver must initialize + * the scheduler (especially for queue #4/#9, the command queue, otherwise + * the driver can't issue commands!): + */ +#define SCD_MEM_LOWER_BOUND (0x0000) + +/** + * Max Tx window size is the max number of contiguous TFDs that the scheduler + * can keep track of at one time when creating block-ack chains of frames. + * Note that "64" matches the number of ack bits in a block-ack packet. + */ +#define SCD_WIN_SIZE 64 +#define SCD_FRAME_LIMIT 64 + +#define SCD_TXFIFO_POS_TID (0) +#define SCD_TXFIFO_POS_RA (4) +#define SCD_QUEUE_RA_TID_MAP_RATID_MSK (0x01FF) + +/* agn SCD */ +#define SCD_QUEUE_STTS_REG_POS_TXF (0) +#define SCD_QUEUE_STTS_REG_POS_ACTIVE (3) +#define SCD_QUEUE_STTS_REG_POS_WSL (4) +#define SCD_QUEUE_STTS_REG_POS_SCD_ACT_EN (19) +#define SCD_QUEUE_STTS_REG_MSK (0x017F0000) + +#define SCD_QUEUE_CTX_REG1_CREDIT (0x00FFFF00) +#define SCD_QUEUE_CTX_REG1_SUPER_CREDIT (0xFF000000) +#define SCD_QUEUE_CTX_REG1_VAL(_n, _v) FIELD_PREP(SCD_QUEUE_CTX_REG1_ ## _n, _v) + +#define SCD_QUEUE_CTX_REG2_WIN_SIZE (0x0000007F) +#define SCD_QUEUE_CTX_REG2_FRAME_LIMIT (0x007F0000) +#define SCD_QUEUE_CTX_REG2_VAL(_n, _v) FIELD_PREP(SCD_QUEUE_CTX_REG2_ ## _n, _v) + +#define SCD_GP_CTRL_ENABLE_31_QUEUES BIT(0) +#define SCD_GP_CTRL_AUTO_ACTIVE_MODE BIT(18) + +/* Context Data */ +#define SCD_CONTEXT_MEM_LOWER_BOUND (SCD_MEM_LOWER_BOUND + 0x600) +#define SCD_CONTEXT_MEM_UPPER_BOUND (SCD_MEM_LOWER_BOUND + 0x6A0) + +/* Tx status */ +#define SCD_TX_STTS_MEM_LOWER_BOUND (SCD_MEM_LOWER_BOUND + 0x6A0) +#define SCD_TX_STTS_MEM_UPPER_BOUND (SCD_MEM_LOWER_BOUND + 0x7E0) + +/* Translation Data */ +#define SCD_TRANS_TBL_MEM_LOWER_BOUND (SCD_MEM_LOWER_BOUND + 0x7E0) +#define SCD_TRANS_TBL_MEM_UPPER_BOUND (SCD_MEM_LOWER_BOUND + 0x808) + +#define SCD_CONTEXT_QUEUE_OFFSET(x)\ + (SCD_CONTEXT_MEM_LOWER_BOUND + ((x) * 8)) + +#define SCD_TX_STTS_QUEUE_OFFSET(x)\ + (SCD_TX_STTS_MEM_LOWER_BOUND + ((x) * 16)) + +#define SCD_TRANS_TBL_OFFSET_QUEUE(x) \ + ((SCD_TRANS_TBL_MEM_LOWER_BOUND + ((x) * 2)) & 0xfffc) + +#define SCD_BASE (PRPH_BASE + 0xa02c00) + +#define SCD_SRAM_BASE_ADDR (SCD_BASE + 0x0) +#define SCD_DRAM_BASE_ADDR (SCD_BASE + 0x8) +#define SCD_AIT (SCD_BASE + 0x0c) +#define SCD_TXFACT (SCD_BASE + 0x10) +#define SCD_ACTIVE (SCD_BASE + 0x14) +#define SCD_QUEUECHAIN_SEL (SCD_BASE + 0xe8) +#define SCD_CHAINEXT_EN (SCD_BASE + 0x244) +#define SCD_AGGR_SEL (SCD_BASE + 0x248) +#define SCD_INTERRUPT_MASK (SCD_BASE + 0x108) +#define SCD_GP_CTRL (SCD_BASE + 0x1a8) +#define SCD_EN_CTRL (SCD_BASE + 0x254) + +/*********************** END TX SCHEDULER *************************************/ + +/* Oscillator clock */ +#define OSC_CLK (0xa04068) +#define OSC_CLK_FORCE_CONTROL (0x8) + +#define FH_UCODE_LOAD_STATUS (0x1AF0) + +/* + * Replacing FH_UCODE_LOAD_STATUS + * This register is writen by driver and is read by uCode during boot flow. + * Note this address is cleared after MAC reset. + */ +#define UREG_UCODE_LOAD_STATUS (0xa05c40) +#define UREG_CPU_INIT_RUN (0xa05c44) + +#define LMPM_SECURE_UCODE_LOAD_CPU1_HDR_ADDR (0x1E78) +#define LMPM_SECURE_UCODE_LOAD_CPU2_HDR_ADDR (0x1E7C) + +#define LMPM_SECURE_CPU1_HDR_MEM_SPACE (0x420000) +#define LMPM_SECURE_CPU2_HDR_MEM_SPACE (0x420400) + +#define LMAC2_PRPH_OFFSET (0x100000) + +/* Rx FIFO */ +#define RXF_SIZE_ADDR (0xa00c88) +#define RXF_RD_D_SPACE (0xa00c40) +#define RXF_RD_WR_PTR (0xa00c50) +#define RXF_RD_RD_PTR (0xa00c54) +#define RXF_RD_FENCE_PTR (0xa00c4c) +#define RXF_SET_FENCE_MODE (0xa00c14) +#define RXF_LD_WR2FENCE (0xa00c1c) +#define RXF_FIFO_RD_FENCE_INC (0xa00c68) +#define RXF_SIZE_BYTE_CND_POS (7) +#define RXF_SIZE_BYTE_CNT_MSK (0x3ff << RXF_SIZE_BYTE_CND_POS) +#define RXF_DIFF_FROM_PREV (0x200) +#define RXF2C_DIFF_FROM_PREV (0x4e00) + +#define RXF_LD_FENCE_OFFSET_ADDR (0xa00c10) +#define RXF_FIFO_RD_FENCE_ADDR (0xa00c0c) + +/* Tx FIFO */ +#define TXF_FIFO_ITEM_CNT (0xa00438) +#define TXF_WR_PTR (0xa00414) +#define TXF_RD_PTR (0xa00410) +#define TXF_FENCE_PTR (0xa00418) +#define TXF_LOCK_FENCE (0xa00424) +#define TXF_LARC_NUM (0xa0043c) +#define TXF_READ_MODIFY_DATA (0xa00448) +#define TXF_READ_MODIFY_ADDR (0xa0044c) + +/* UMAC Internal Tx Fifo */ +#define TXF_CPU2_FIFO_ITEM_CNT (0xA00538) +#define TXF_CPU2_WR_PTR (0xA00514) +#define TXF_CPU2_RD_PTR (0xA00510) +#define TXF_CPU2_FENCE_PTR (0xA00518) +#define TXF_CPU2_LOCK_FENCE (0xA00524) +#define TXF_CPU2_NUM (0xA0053C) +#define TXF_CPU2_READ_MODIFY_DATA (0xA00548) +#define TXF_CPU2_READ_MODIFY_ADDR (0xA0054C) + +/* Radio registers access */ +#define RSP_RADIO_CMD (0xa02804) +#define RSP_RADIO_RDDAT (0xa02814) +#define RADIO_RSP_ADDR_POS (6) +#define RADIO_RSP_RD_CMD (3) + +/* LTR control (Qu only) */ +#define HPM_MAC_LTR_CSR 0xa0348c +#define HPM_MAC_LRT_ENABLE_ALL 0xf +/* also uses CSR_LTR_* for values */ +#define HPM_UMAC_LTR 0xa03480 + +/* FW monitor */ +#define MON_BUFF_SAMPLE_CTL (0xa03c00) +#define MON_BUFF_BASE_ADDR (0xa03c1c) +#define MON_BUFF_END_ADDR (0xa03c40) +#define MON_BUFF_WRPTR (0xa03c44) +#define MON_BUFF_CYCLE_CNT (0xa03c48) +/* FW monitor family 8000 and on */ +#define MON_BUFF_BASE_ADDR_VER2 (0xa03c1c) +#define MON_BUFF_END_ADDR_VER2 (0xa03c20) +#define MON_BUFF_WRPTR_VER2 (0xa03c24) +#define MON_BUFF_CYCLE_CNT_VER2 (0xa03c28) +#define MON_BUFF_SHIFT_VER2 (0x8) +/* FW monitor familiy AX210 and on */ +#define DBGC_CUR_DBGBUF_BASE_ADDR_LSB (0xd03c20) +#define DBGC_CUR_DBGBUF_BASE_ADDR_MSB (0xd03c24) +#define DBGC_CUR_DBGBUF_STATUS (0xd03c1c) +#define DBGC_DBGBUF_WRAP_AROUND (0xd03c2c) +#define DBGC_CUR_DBGBUF_STATUS_OFFSET_MSK (0x00ffffff) +#define DBGC_CUR_DBGBUF_STATUS_IDX_MSK (0x0f000000) + +#define MON_DMARB_RD_CTL_ADDR (0xa03c60) +#define MON_DMARB_RD_DATA_ADDR (0xa03c5c) + +#define DBGC_IN_SAMPLE (0xa03c00) +#define DBGC_OUT_CTRL (0xa03c0c) + +/* M2S registers */ +#define LDBG_M2S_BUF_WPTR (0xa0476c) +#define LDBG_M2S_BUF_WRAP_CNT (0xa04774) +#define LDBG_M2S_BUF_WPTR_VAL_MSK (0x000fffff) +#define LDBG_M2S_BUF_WRAP_CNT_VAL_MSK (0x000fffff) + +/* enable the ID buf for read */ +#define WFPM_PS_CTL_CLR 0xA0300C +#define WFMP_MAC_ADDR_0 0xA03080 +#define WFMP_MAC_ADDR_1 0xA03084 +#define LMPM_PMG_EN 0xA01CEC +#define RADIO_REG_SYS_MANUAL_DFT_0 0xAD4078 +#define RFIC_REG_RD 0xAD0470 +#define WFPM_CTRL_REG 0xA03030 +#define WFPM_OTP_CFG1_ADDR 0x00a03098 +#define WFPM_OTP_CFG1_IS_JACKET_BIT BIT(5) +#define WFPM_OTP_CFG1_IS_CDB_BIT BIT(4) + +#define WFPM_GP2 0xA030B4 + +/* DBGI SRAM Register details */ +#define DBGI_SRAM_TARGET_ACCESS_RDATA_LSB 0x00A2E154 +#define DBGI_SRAM_TARGET_ACCESS_RDATA_MSB 0x00A2E158 +#define DBGI_SRAM_FIFO_POINTERS 0x00A2E148 +#define DBGI_SRAM_FIFO_POINTERS_WR_PTR_MSK 0x00000FFF + +enum { + ENABLE_WFPM = BIT(31), + WFPM_AUX_CTL_AUX_IF_MAC_OWNER_MSK = 0x80000000, +}; + +#define CNVI_AUX_MISC_CHIP 0xA200B0 +#define CNVR_AUX_MISC_CHIP 0xA2B800 +#define CNVR_SCU_SD_REGS_SD_REG_DIG_DCDC_VTRIM 0xA29890 +#define CNVR_SCU_SD_REGS_SD_REG_ACTIVE_VDIG_MIRROR 0xA29938 + +#define PREG_AUX_BUS_WPROT_0 0xA04CC0 + +/* device family 9000 WPROT register */ +#define PREG_PRPH_WPROT_9000 0xA04CE0 +/* device family 22000 WPROT register */ +#define PREG_PRPH_WPROT_22000 0xA04D00 + +#define SB_MODIFY_CFG_FLAG 0xA03088 +#define SB_CPU_1_STATUS 0xA01E30 +#define SB_CPU_2_STATUS 0xA01E34 +#define UMAG_SB_CPU_1_STATUS 0xA038C0 +#define UMAG_SB_CPU_2_STATUS 0xA038C4 +#define UMAG_GEN_HW_STATUS 0xA038C8 +#define UREG_UMAC_CURRENT_PC 0xa05c18 +#define UREG_LMAC1_CURRENT_PC 0xa05c1c +#define UREG_LMAC2_CURRENT_PC 0xa05c20 + +#define WFPM_LMAC1_PD_NOTIFICATION 0xa0338c +#define WFPM_ARC1_PD_NOTIFICATION 0xa03044 +#define HPM_SECONDARY_DEVICE_STATE 0xa03404 +#define WFPM_MAC_OTP_CFG7_ADDR 0xa03338 +#define WFPM_MAC_OTP_CFG7_DATA 0xa0333c + + +/* For UMAG_GEN_HW_STATUS reg check */ +enum { + UMAG_GEN_HW_IS_FPGA = BIT(1), +}; + +/* FW chicken bits */ +#define LMPM_CHICK 0xA01FF8 +enum { + LMPM_CHICK_EXTENDED_ADDR_SPACE = BIT(0), +}; + +/* FW chicken bits */ +#define LMPM_PAGE_PASS_NOTIF 0xA03824 +enum { + LMPM_PAGE_PASS_NOTIF_POS = BIT(20), +}; + +/* + * CRF ID register + * + * type: bits 0-11 + * reserved: bits 12-18 + * slave_exist: bit 19 + * dash: bits 20-23 + * step: bits 24-26 + * flavor: bits 27-31 + */ +#define REG_CRF_ID_TYPE(val) (((val) & 0x00000FFF) >> 0) +#define REG_CRF_ID_SLAVE(val) (((val) & 0x00080000) >> 19) +#define REG_CRF_ID_DASH(val) (((val) & 0x00F00000) >> 20) +#define REG_CRF_ID_STEP(val) (((val) & 0x07000000) >> 24) +#define REG_CRF_ID_FLAVOR(val) (((val) & 0xF8000000) >> 27) + +#define UREG_CHICK (0xA05C00) +#define UREG_CHICK_MSI_ENABLE BIT(24) +#define UREG_CHICK_MSIX_ENABLE BIT(25) + +#define SD_REG_VER 0xa29600 +#define SD_REG_VER_GEN2 0x00a2b800 + +#define REG_CRF_ID_TYPE_JF_1 0x201 +#define REG_CRF_ID_TYPE_JF_2 0x202 +#define REG_CRF_ID_TYPE_HR_CDB 0x503 +#define REG_CRF_ID_TYPE_HR_NONE_CDB 0x504 +#define REG_CRF_ID_TYPE_HR_NONE_CDB_1X1 0x501 +#define REG_CRF_ID_TYPE_HR_NONE_CDB_CCP 0x532 +#define REG_CRF_ID_TYPE_GF 0x410 +#define REG_CRF_ID_TYPE_GF_TC 0xF08 +#define REG_CRF_ID_TYPE_MR 0x810 +#define REG_CRF_ID_TYPE_FM 0x910 + +#define HPM_DEBUG 0xA03440 +#define PERSISTENCE_BIT BIT(12) +#define PREG_WFPM_ACCESS BIT(12) + +#define HPM_HIPM_GEN_CFG 0xA03458 +#define HPM_HIPM_GEN_CFG_CR_PG_EN BIT(0) +#define HPM_HIPM_GEN_CFG_CR_SLP_EN BIT(1) +#define HPM_HIPM_GEN_CFG_CR_FORCE_ACTIVE BIT(10) + +#define UREG_DOORBELL_TO_ISR6 0xA05C04 +#define UREG_DOORBELL_TO_ISR6_NMI_BIT BIT(0) +#define UREG_DOORBELL_TO_ISR6_RESET_HANDSHAKE (BIT(0) | BIT(1)) +#define UREG_DOORBELL_TO_ISR6_SUSPEND BIT(18) +#define UREG_DOORBELL_TO_ISR6_RESUME BIT(19) +#define UREG_DOORBELL_TO_ISR6_PNVM BIT(20) + +/* + * From BZ family driver triggers this bit for suspend and resume + * The driver should update CSR_IPC_SLEEP_CONTROL before triggering + * this interrupt with suspend/resume value + */ +#define UREG_DOORBELL_TO_ISR6_SLEEP_CTRL BIT(31) + +#define CNVI_MBOX_C 0xA3400C + +#define FSEQ_ERROR_CODE 0xA340C8 +#define FSEQ_TOP_INIT_VERSION 0xA34038 +#define FSEQ_CNVIO_INIT_VERSION 0xA3403C +#define FSEQ_OTP_VERSION 0xA340FC +#define FSEQ_TOP_CONTENT_VERSION 0xA340F4 +#define FSEQ_ALIVE_TOKEN 0xA340F0 +#define FSEQ_CNVI_ID 0xA3408C +#define FSEQ_CNVR_ID 0xA34090 + +#define IWL_D3_SLEEP_STATUS_SUSPEND 0xD3 +#define IWL_D3_SLEEP_STATUS_RESUME 0xD0 + +#define WMAL_INDRCT_RD_CMD1_OPMOD_POS 28 +#define WMAL_INDRCT_RD_CMD1_BYTE_ADDRESS_MSK 0xFFFFF +#define WMAL_CMD_READ_BURST_ACCESS 2 +#define WMAL_MRSPF_1 0xADFC20 +#define WMAL_INDRCT_RD_CMD1 0xADFD44 +#define WMAL_INDRCT_CMD1 0xADFC14 +#define WMAL_INDRCT_CMD(addr) \ + ((WMAL_CMD_READ_BURST_ACCESS << WMAL_INDRCT_RD_CMD1_OPMOD_POS) | \ + ((addr) & WMAL_INDRCT_RD_CMD1_BYTE_ADDRESS_MSK)) + +#define WFPM_LMAC1_PS_CTL_RW 0xA03380 +#define WFPM_LMAC2_PS_CTL_RW 0xA033C0 +#define WFPM_PS_CTL_RW_PHYRF_PD_FSM_CURSTATE_MSK 0x0000000F +#define WFPM_PHYRF_STATE_ON 5 +#define HBUS_TIMEOUT 0xA5A5A5A1 +#define WFPM_DPHY_OFF 0xDF10FF + +#define REG_OTP_MINOR 0xA0333C + +#endif /* __iwl_prph_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-scd.h b/drivers/net/wireless/intel/iwlwifi/iwl-scd.h new file mode 100644 index 000000000..1eaff898c --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-scd.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2014 Intel Mobile Communications GmbH + */ +#ifndef __iwl_scd_h__ +#define __iwl_scd_h__ + +#include "iwl-trans.h" +#include "iwl-io.h" +#include "iwl-prph.h" + + +static inline void iwl_scd_txq_set_chain(struct iwl_trans *trans, + u16 txq_id) +{ + iwl_set_bits_prph(trans, SCD_QUEUECHAIN_SEL, BIT(txq_id)); +} + +static inline void iwl_scd_txq_enable_agg(struct iwl_trans *trans, + u16 txq_id) +{ + iwl_set_bits_prph(trans, SCD_AGGR_SEL, BIT(txq_id)); +} + +static inline void iwl_scd_txq_disable_agg(struct iwl_trans *trans, + u16 txq_id) +{ + iwl_clear_bits_prph(trans, SCD_AGGR_SEL, BIT(txq_id)); +} + +static inline void iwl_scd_disable_agg(struct iwl_trans *trans) +{ + iwl_set_bits_prph(trans, SCD_AGGR_SEL, 0); +} + +static inline void iwl_scd_activate_fifos(struct iwl_trans *trans) +{ + iwl_write_prph(trans, SCD_TXFACT, IWL_MASK(0, 7)); +} + +static inline void iwl_scd_deactivate_fifos(struct iwl_trans *trans) +{ + iwl_write_prph(trans, SCD_TXFACT, 0); +} + +static inline void iwl_scd_enable_set_active(struct iwl_trans *trans, + u32 value) +{ + iwl_write_prph(trans, SCD_EN_CTRL, value); +} + +static inline unsigned int SCD_QUEUE_WRPTR(unsigned int chnl) +{ + if (chnl < 20) + return SCD_BASE + 0x18 + chnl * 4; + WARN_ON_ONCE(chnl >= 32); + return SCD_BASE + 0x284 + (chnl - 20) * 4; +} + +static inline unsigned int SCD_QUEUE_RDPTR(unsigned int chnl) +{ + if (chnl < 20) + return SCD_BASE + 0x68 + chnl * 4; + WARN_ON_ONCE(chnl >= 32); + return SCD_BASE + 0x2B4 + chnl * 4; +} + +static inline unsigned int SCD_QUEUE_STATUS_BITS(unsigned int chnl) +{ + if (chnl < 20) + return SCD_BASE + 0x10c + chnl * 4; + WARN_ON_ONCE(chnl >= 32); + return SCD_BASE + 0x334 + chnl * 4; +} + +static inline void iwl_scd_txq_set_inactive(struct iwl_trans *trans, + u16 txq_id) +{ + iwl_write_prph(trans, SCD_QUEUE_STATUS_BITS(txq_id), + (0 << SCD_QUEUE_STTS_REG_POS_ACTIVE)| + (1 << SCD_QUEUE_STTS_REG_POS_SCD_ACT_EN)); +} + +#endif diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c new file mode 100644 index 000000000..b1af9359c --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + * Copyright (C) 2019-2021 Intel Corporation + */ +#include <linux/kernel.h> +#include <linux/bsearch.h> + +#include "fw/api/tx.h" +#include "iwl-trans.h" +#include "iwl-drv.h" +#include "iwl-fh.h" +#include "queue/tx.h" +#include <linux/dmapool.h> +#include "fw/api/commands.h" + +struct iwl_trans *iwl_trans_alloc(unsigned int priv_size, + struct device *dev, + const struct iwl_trans_ops *ops, + const struct iwl_cfg_trans_params *cfg_trans) +{ + struct iwl_trans *trans; +#ifdef CONFIG_LOCKDEP + static struct lock_class_key __key; +#endif + + trans = devm_kzalloc(dev, sizeof(*trans) + priv_size, GFP_KERNEL); + if (!trans) + return NULL; + + trans->trans_cfg = cfg_trans; + +#ifdef CONFIG_LOCKDEP + lockdep_init_map(&trans->sync_cmd_lockdep_map, "sync_cmd_lockdep_map", + &__key, 0); +#endif + + trans->dev = dev; + trans->ops = ops; + trans->num_rx_queues = 1; + + WARN_ON(!ops->wait_txq_empty && !ops->wait_tx_queues_empty); + + if (trans->trans_cfg->use_tfh) { + trans->txqs.tfd.addr_size = 64; + trans->txqs.tfd.max_tbs = IWL_TFH_NUM_TBS; + trans->txqs.tfd.size = sizeof(struct iwl_tfh_tfd); + } else { + trans->txqs.tfd.addr_size = 36; + trans->txqs.tfd.max_tbs = IWL_NUM_OF_TBS; + trans->txqs.tfd.size = sizeof(struct iwl_tfd); + } + trans->max_skb_frags = IWL_TRANS_MAX_FRAGS(trans); + + return trans; +} + +int iwl_trans_init(struct iwl_trans *trans) +{ + int txcmd_size, txcmd_align; + + if (!trans->trans_cfg->gen2) { + txcmd_size = sizeof(struct iwl_tx_cmd); + txcmd_align = sizeof(void *); + } else if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) { + txcmd_size = sizeof(struct iwl_tx_cmd_gen2); + txcmd_align = 64; + } else { + txcmd_size = sizeof(struct iwl_tx_cmd_gen3); + txcmd_align = 128; + } + + txcmd_size += sizeof(struct iwl_cmd_header); + txcmd_size += 36; /* biggest possible 802.11 header */ + + /* Ensure device TX cmd cannot reach/cross a page boundary in gen2 */ + if (WARN_ON(trans->trans_cfg->gen2 && txcmd_size >= txcmd_align)) + return -EINVAL; + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + trans->txqs.bc_tbl_size = + sizeof(struct iwl_gen3_bc_tbl_entry) * TFD_QUEUE_BC_SIZE_GEN3_BZ; + else if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + trans->txqs.bc_tbl_size = + sizeof(struct iwl_gen3_bc_tbl_entry) * TFD_QUEUE_BC_SIZE_GEN3_AX210; + else + trans->txqs.bc_tbl_size = sizeof(struct iwlagn_scd_bc_tbl); + /* + * For gen2 devices, we use a single allocation for each byte-count + * table, but they're pretty small (1k) so use a DMA pool that we + * allocate here. + */ + if (trans->trans_cfg->gen2) { + trans->txqs.bc_pool = dmam_pool_create("iwlwifi:bc", trans->dev, + trans->txqs.bc_tbl_size, + 256, 0); + if (!trans->txqs.bc_pool) + return -ENOMEM; + } + + /* Some things must not change even if the config does */ + WARN_ON(trans->txqs.tfd.addr_size != + (trans->trans_cfg->use_tfh ? 64 : 36)); + + snprintf(trans->dev_cmd_pool_name, sizeof(trans->dev_cmd_pool_name), + "iwl_cmd_pool:%s", dev_name(trans->dev)); + trans->dev_cmd_pool = + kmem_cache_create(trans->dev_cmd_pool_name, + txcmd_size, txcmd_align, + SLAB_HWCACHE_ALIGN, NULL); + if (!trans->dev_cmd_pool) + return -ENOMEM; + + trans->txqs.tso_hdr_page = alloc_percpu(struct iwl_tso_hdr_page); + if (!trans->txqs.tso_hdr_page) { + kmem_cache_destroy(trans->dev_cmd_pool); + return -ENOMEM; + } + + /* Initialize the wait queue for commands */ + init_waitqueue_head(&trans->wait_command_queue); + + return 0; +} + +void iwl_trans_free(struct iwl_trans *trans) +{ + int i; + + if (trans->txqs.tso_hdr_page) { + for_each_possible_cpu(i) { + struct iwl_tso_hdr_page *p = + per_cpu_ptr(trans->txqs.tso_hdr_page, i); + + if (p && p->page) + __free_page(p->page); + } + + free_percpu(trans->txqs.tso_hdr_page); + } + + kmem_cache_destroy(trans->dev_cmd_pool); +} + +int iwl_trans_send_cmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) +{ + int ret; + + if (unlikely(!(cmd->flags & CMD_SEND_IN_RFKILL) && + test_bit(STATUS_RFKILL_OPMODE, &trans->status))) + return -ERFKILL; + + /* + * We can't test IWL_MVM_STATUS_IN_D3 in mvm->status because this + * bit is set early in the D3 flow, before we send all the commands + * that configure the firmware for D3 operation (power, patterns, ...) + * and we don't want to flag all those with CMD_SEND_IN_D3. + * So use the system_pm_mode instead. The only command sent after + * we set system_pm_mode is D3_CONFIG_CMD, which we now flag with + * CMD_SEND_IN_D3. + */ + if (unlikely(trans->system_pm_mode == IWL_PLAT_PM_MODE_D3 && + !(cmd->flags & CMD_SEND_IN_D3))) + return -EHOSTDOWN; + + if (unlikely(test_bit(STATUS_FW_ERROR, &trans->status))) + return -EIO; + + if (unlikely(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return -EIO; + } + + if (WARN_ON((cmd->flags & CMD_WANT_ASYNC_CALLBACK) && + !(cmd->flags & CMD_ASYNC))) + return -EINVAL; + + if (!(cmd->flags & CMD_ASYNC)) + lock_map_acquire_read(&trans->sync_cmd_lockdep_map); + + if (trans->wide_cmd_header && !iwl_cmd_groupid(cmd->id)) { + if (cmd->id != REPLY_ERROR) + cmd->id = DEF_ID(cmd->id); + } + + ret = iwl_trans_txq_send_hcmd(trans, cmd); + + if (!(cmd->flags & CMD_ASYNC)) + lock_map_release(&trans->sync_cmd_lockdep_map); + + if (WARN_ON((cmd->flags & CMD_WANT_SKB) && !ret && !cmd->resp_pkt)) + return -EIO; + + return ret; +} +IWL_EXPORT_SYMBOL(iwl_trans_send_cmd); + +/* Comparator for struct iwl_hcmd_names. + * Used in the binary search over a list of host commands. + * + * @key: command_id that we're looking for. + * @elt: struct iwl_hcmd_names candidate for match. + * + * @return 0 iff equal. + */ +static int iwl_hcmd_names_cmp(const void *key, const void *elt) +{ + const struct iwl_hcmd_names *name = elt; + const u8 *cmd1 = key; + u8 cmd2 = name->cmd_id; + + return (*cmd1 - cmd2); +} + +const char *iwl_get_cmd_string(struct iwl_trans *trans, u32 id) +{ + u8 grp, cmd; + struct iwl_hcmd_names *ret; + const struct iwl_hcmd_arr *arr; + size_t size = sizeof(struct iwl_hcmd_names); + + grp = iwl_cmd_groupid(id); + cmd = iwl_cmd_opcode(id); + + if (!trans->command_groups || grp >= trans->command_groups_size || + !trans->command_groups[grp].arr) + return "UNKNOWN"; + + arr = &trans->command_groups[grp]; + ret = bsearch(&cmd, arr->arr, arr->size, size, iwl_hcmd_names_cmp); + if (!ret) + return "UNKNOWN"; + return ret->cmd_name; +} +IWL_EXPORT_SYMBOL(iwl_get_cmd_string); + +int iwl_cmd_groups_verify_sorted(const struct iwl_trans_config *trans) +{ + int i, j; + const struct iwl_hcmd_arr *arr; + + for (i = 0; i < trans->command_groups_size; i++) { + arr = &trans->command_groups[i]; + if (!arr->arr) + continue; + for (j = 0; j < arr->size - 1; j++) + if (arr->arr[j].cmd_id > arr->arr[j + 1].cmd_id) + return -1; + } + return 0; +} +IWL_EXPORT_SYMBOL(iwl_cmd_groups_verify_sorted); diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h new file mode 100644 index 000000000..70022cade --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h @@ -0,0 +1,1551 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2005-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_trans_h__ +#define __iwl_trans_h__ + +#include <linux/ieee80211.h> +#include <linux/mm.h> /* for page_address */ +#include <linux/lockdep.h> +#include <linux/kernel.h> + +#include "iwl-debug.h" +#include "iwl-config.h" +#include "fw/img.h" +#include "iwl-op-mode.h" +#include <linux/firmware.h> +#include "fw/api/cmdhdr.h" +#include "fw/api/txq.h" +#include "fw/api/dbg-tlv.h" +#include "iwl-dbg-tlv.h" + +/** + * DOC: Transport layer - what is it ? + * + * The transport layer is the layer that deals with the HW directly. It provides + * an abstraction of the underlying HW to the upper layer. The transport layer + * doesn't provide any policy, algorithm or anything of this kind, but only + * mechanisms to make the HW do something. It is not completely stateless but + * close to it. + * We will have an implementation for each different supported bus. + */ + +/** + * DOC: Life cycle of the transport layer + * + * The transport layer has a very precise life cycle. + * + * 1) A helper function is called during the module initialization and + * registers the bus driver's ops with the transport's alloc function. + * 2) Bus's probe calls to the transport layer's allocation functions. + * Of course this function is bus specific. + * 3) This allocation functions will spawn the upper layer which will + * register mac80211. + * + * 4) At some point (i.e. mac80211's start call), the op_mode will call + * the following sequence: + * start_hw + * start_fw + * + * 5) Then when finished (or reset): + * stop_device + * + * 6) Eventually, the free function will be called. + */ + +/* default preset 0 (start from bit 16)*/ +#define IWL_FW_DBG_DOMAIN_POS 16 +#define IWL_FW_DBG_DOMAIN BIT(IWL_FW_DBG_DOMAIN_POS) + +#define IWL_TRANS_FW_DBG_DOMAIN(trans) IWL_FW_INI_DOMAIN_ALWAYS_ON + +#define FH_RSCSR_FRAME_SIZE_MSK 0x00003FFF /* bits 0-13 */ +#define FH_RSCSR_FRAME_INVALID 0x55550000 +#define FH_RSCSR_FRAME_ALIGN 0x40 +#define FH_RSCSR_RPA_EN BIT(25) +#define FH_RSCSR_RADA_EN BIT(26) +#define FH_RSCSR_RXQ_POS 16 +#define FH_RSCSR_RXQ_MASK 0x3F0000 + +struct iwl_rx_packet { + /* + * The first 4 bytes of the RX frame header contain both the RX frame + * size and some flags. + * Bit fields: + * 31: flag flush RB request + * 30: flag ignore TC (terminal counter) request + * 29: flag fast IRQ request + * 28-27: Reserved + * 26: RADA enabled + * 25: Offload enabled + * 24: RPF enabled + * 23: RSS enabled + * 22: Checksum enabled + * 21-16: RX queue + * 15-14: Reserved + * 13-00: RX frame size + */ + __le32 len_n_flags; + struct iwl_cmd_header hdr; + u8 data[]; +} __packed; + +static inline u32 iwl_rx_packet_len(const struct iwl_rx_packet *pkt) +{ + return le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; +} + +static inline u32 iwl_rx_packet_payload_len(const struct iwl_rx_packet *pkt) +{ + return iwl_rx_packet_len(pkt) - sizeof(pkt->hdr); +} + +/** + * enum CMD_MODE - how to send the host commands ? + * + * @CMD_ASYNC: Return right away and don't wait for the response + * @CMD_WANT_SKB: Not valid with CMD_ASYNC. The caller needs the buffer of + * the response. The caller needs to call iwl_free_resp when done. + * @CMD_WANT_ASYNC_CALLBACK: the op_mode's async callback function must be + * called after this command completes. Valid only with CMD_ASYNC. + * @CMD_SEND_IN_D3: Allow the command to be sent in D3 mode, relevant to + * SUSPEND and RESUME commands. We are in D3 mode when we set + * trans->system_pm_mode to IWL_PLAT_PM_MODE_D3. + */ +enum CMD_MODE { + CMD_ASYNC = BIT(0), + CMD_WANT_SKB = BIT(1), + CMD_SEND_IN_RFKILL = BIT(2), + CMD_WANT_ASYNC_CALLBACK = BIT(3), + CMD_SEND_IN_D3 = BIT(4), +}; + +#define DEF_CMD_PAYLOAD_SIZE 320 + +/** + * struct iwl_device_cmd + * + * For allocation of the command and tx queues, this establishes the overall + * size of the largest command we send to uCode, except for commands that + * aren't fully copied and use other TFD space. + */ +struct iwl_device_cmd { + union { + struct { + struct iwl_cmd_header hdr; /* uCode API */ + u8 payload[DEF_CMD_PAYLOAD_SIZE]; + }; + struct { + struct iwl_cmd_header_wide hdr_wide; + u8 payload_wide[DEF_CMD_PAYLOAD_SIZE - + sizeof(struct iwl_cmd_header_wide) + + sizeof(struct iwl_cmd_header)]; + }; + }; +} __packed; + +/** + * struct iwl_device_tx_cmd - buffer for TX command + * @hdr: the header + * @payload: the payload placeholder + * + * The actual structure is sized dynamically according to need. + */ +struct iwl_device_tx_cmd { + struct iwl_cmd_header hdr; + u8 payload[]; +} __packed; + +#define TFD_MAX_PAYLOAD_SIZE (sizeof(struct iwl_device_cmd)) + +/* + * number of transfer buffers (fragments) per transmit frame descriptor; + * this is just the driver's idea, the hardware supports 20 + */ +#define IWL_MAX_CMD_TBS_PER_TFD 2 + +/* We need 2 entries for the TX command and header, and another one might + * be needed for potential data in the SKB's head. The remaining ones can + * be used for frags. + */ +#define IWL_TRANS_MAX_FRAGS(trans) ((trans)->txqs.tfd.max_tbs - 3) + +/** + * enum iwl_hcmd_dataflag - flag for each one of the chunks of the command + * + * @IWL_HCMD_DFL_NOCOPY: By default, the command is copied to the host command's + * ring. The transport layer doesn't map the command's buffer to DMA, but + * rather copies it to a previously allocated DMA buffer. This flag tells + * the transport layer not to copy the command, but to map the existing + * buffer (that is passed in) instead. This saves the memcpy and allows + * commands that are bigger than the fixed buffer to be submitted. + * Note that a TFD entry after a NOCOPY one cannot be a normal copied one. + * @IWL_HCMD_DFL_DUP: Only valid without NOCOPY, duplicate the memory for this + * chunk internally and free it again after the command completes. This + * can (currently) be used only once per command. + * Note that a TFD entry after a DUP one cannot be a normal copied one. + */ +enum iwl_hcmd_dataflag { + IWL_HCMD_DFL_NOCOPY = BIT(0), + IWL_HCMD_DFL_DUP = BIT(1), +}; + +enum iwl_error_event_table_status { + IWL_ERROR_EVENT_TABLE_LMAC1 = BIT(0), + IWL_ERROR_EVENT_TABLE_LMAC2 = BIT(1), + IWL_ERROR_EVENT_TABLE_UMAC = BIT(2), + IWL_ERROR_EVENT_TABLE_TCM1 = BIT(3), + IWL_ERROR_EVENT_TABLE_TCM2 = BIT(4), + IWL_ERROR_EVENT_TABLE_RCM1 = BIT(5), + IWL_ERROR_EVENT_TABLE_RCM2 = BIT(6), +}; + +/** + * struct iwl_host_cmd - Host command to the uCode + * + * @data: array of chunks that composes the data of the host command + * @resp_pkt: response packet, if %CMD_WANT_SKB was set + * @_rx_page_order: (internally used to free response packet) + * @_rx_page_addr: (internally used to free response packet) + * @flags: can be CMD_* + * @len: array of the lengths of the chunks in data + * @dataflags: IWL_HCMD_DFL_* + * @id: command id of the host command, for wide commands encoding the + * version and group as well + */ +struct iwl_host_cmd { + const void *data[IWL_MAX_CMD_TBS_PER_TFD]; + struct iwl_rx_packet *resp_pkt; + unsigned long _rx_page_addr; + u32 _rx_page_order; + + u32 flags; + u32 id; + u16 len[IWL_MAX_CMD_TBS_PER_TFD]; + u8 dataflags[IWL_MAX_CMD_TBS_PER_TFD]; +}; + +static inline void iwl_free_resp(struct iwl_host_cmd *cmd) +{ + free_pages(cmd->_rx_page_addr, cmd->_rx_page_order); +} + +struct iwl_rx_cmd_buffer { + struct page *_page; + int _offset; + bool _page_stolen; + u32 _rx_page_order; + unsigned int truesize; +}; + +static inline void *rxb_addr(struct iwl_rx_cmd_buffer *r) +{ + return (void *)((unsigned long)page_address(r->_page) + r->_offset); +} + +static inline int rxb_offset(struct iwl_rx_cmd_buffer *r) +{ + return r->_offset; +} + +static inline struct page *rxb_steal_page(struct iwl_rx_cmd_buffer *r) +{ + r->_page_stolen = true; + get_page(r->_page); + return r->_page; +} + +static inline void iwl_free_rxb(struct iwl_rx_cmd_buffer *r) +{ + __free_pages(r->_page, r->_rx_page_order); +} + +#define MAX_NO_RECLAIM_CMDS 6 + +#define IWL_MASK(lo, hi) ((1 << (hi)) | ((1 << (hi)) - (1 << (lo)))) + +/* + * Maximum number of HW queues the transport layer + * currently supports + */ +#define IWL_MAX_HW_QUEUES 32 +#define IWL_MAX_TVQM_QUEUES 512 + +#define IWL_MAX_TID_COUNT 8 +#define IWL_MGMT_TID 15 +#define IWL_FRAME_LIMIT 64 +#define IWL_MAX_RX_HW_QUEUES 16 +#define IWL_9000_MAX_RX_HW_QUEUES 6 + +/** + * enum iwl_wowlan_status - WoWLAN image/device status + * @IWL_D3_STATUS_ALIVE: firmware is still running after resume + * @IWL_D3_STATUS_RESET: device was reset while suspended + */ +enum iwl_d3_status { + IWL_D3_STATUS_ALIVE, + IWL_D3_STATUS_RESET, +}; + +/** + * enum iwl_trans_status: transport status flags + * @STATUS_SYNC_HCMD_ACTIVE: a SYNC command is being processed + * @STATUS_DEVICE_ENABLED: APM is enabled + * @STATUS_TPOWER_PMI: the device might be asleep (need to wake it up) + * @STATUS_INT_ENABLED: interrupts are enabled + * @STATUS_RFKILL_HW: the actual HW state of the RF-kill switch + * @STATUS_RFKILL_OPMODE: RF-kill state reported to opmode + * @STATUS_FW_ERROR: the fw is in error state + * @STATUS_TRANS_GOING_IDLE: shutting down the trans, only special commands + * are sent + * @STATUS_TRANS_IDLE: the trans is idle - general commands are not to be sent + * @STATUS_TRANS_DEAD: trans is dead - avoid any read/write operation + * @STATUS_SUPPRESS_CMD_ERROR_ONCE: suppress "FW error in SYNC CMD" once, + * e.g. for testing + */ +enum iwl_trans_status { + STATUS_SYNC_HCMD_ACTIVE, + STATUS_DEVICE_ENABLED, + STATUS_TPOWER_PMI, + STATUS_INT_ENABLED, + STATUS_RFKILL_HW, + STATUS_RFKILL_OPMODE, + STATUS_FW_ERROR, + STATUS_TRANS_GOING_IDLE, + STATUS_TRANS_IDLE, + STATUS_TRANS_DEAD, + STATUS_SUPPRESS_CMD_ERROR_ONCE, +}; + +static inline int +iwl_trans_get_rb_size_order(enum iwl_amsdu_size rb_size) +{ + switch (rb_size) { + case IWL_AMSDU_2K: + return get_order(2 * 1024); + case IWL_AMSDU_4K: + return get_order(4 * 1024); + case IWL_AMSDU_8K: + return get_order(8 * 1024); + case IWL_AMSDU_12K: + return get_order(16 * 1024); + default: + WARN_ON(1); + return -1; + } +} + +static inline int +iwl_trans_get_rb_size(enum iwl_amsdu_size rb_size) +{ + switch (rb_size) { + case IWL_AMSDU_2K: + return 2 * 1024; + case IWL_AMSDU_4K: + return 4 * 1024; + case IWL_AMSDU_8K: + return 8 * 1024; + case IWL_AMSDU_12K: + return 16 * 1024; + default: + WARN_ON(1); + return 0; + } +} + +struct iwl_hcmd_names { + u8 cmd_id; + const char *const cmd_name; +}; + +#define HCMD_NAME(x) \ + { .cmd_id = x, .cmd_name = #x } + +struct iwl_hcmd_arr { + const struct iwl_hcmd_names *arr; + int size; +}; + +#define HCMD_ARR(x) \ + { .arr = x, .size = ARRAY_SIZE(x) } + +/** + * struct iwl_dump_sanitize_ops - dump sanitization operations + * @frob_txf: Scrub the TX FIFO data + * @frob_hcmd: Scrub a host command, the %hcmd pointer is to the header + * but that might be short or long (&struct iwl_cmd_header or + * &struct iwl_cmd_header_wide) + * @frob_mem: Scrub memory data + */ +struct iwl_dump_sanitize_ops { + void (*frob_txf)(void *ctx, void *buf, size_t buflen); + void (*frob_hcmd)(void *ctx, void *hcmd, size_t buflen); + void (*frob_mem)(void *ctx, u32 mem_addr, void *mem, size_t buflen); +}; + +/** + * struct iwl_trans_config - transport configuration + * + * @op_mode: pointer to the upper layer. + * @cmd_queue: the index of the command queue. + * Must be set before start_fw. + * @cmd_fifo: the fifo for host commands + * @cmd_q_wdg_timeout: the timeout of the watchdog timer for the command queue. + * @no_reclaim_cmds: Some devices erroneously don't set the + * SEQ_RX_FRAME bit on some notifications, this is the + * list of such notifications to filter. Max length is + * %MAX_NO_RECLAIM_CMDS. + * @n_no_reclaim_cmds: # of commands in list + * @rx_buf_size: RX buffer size needed for A-MSDUs + * if unset 4k will be the RX buffer size + * @bc_table_dword: set to true if the BC table expects the byte count to be + * in DWORD (as opposed to bytes) + * @scd_set_active: should the transport configure the SCD for HCMD queue + * @command_groups: array of command groups, each member is an array of the + * commands in the group; for debugging only + * @command_groups_size: number of command groups, to avoid illegal access + * @cb_data_offs: offset inside skb->cb to store transport data at, must have + * space for at least two pointers + * @fw_reset_handshake: firmware supports reset flow handshake + * @queue_alloc_cmd_ver: queue allocation command version, set to 0 + * for using the older SCD_QUEUE_CFG, set to the version of + * SCD_QUEUE_CONFIG_CMD otherwise. + */ +struct iwl_trans_config { + struct iwl_op_mode *op_mode; + + u8 cmd_queue; + u8 cmd_fifo; + unsigned int cmd_q_wdg_timeout; + const u8 *no_reclaim_cmds; + unsigned int n_no_reclaim_cmds; + + enum iwl_amsdu_size rx_buf_size; + bool bc_table_dword; + bool scd_set_active; + const struct iwl_hcmd_arr *command_groups; + int command_groups_size; + + u8 cb_data_offs; + bool fw_reset_handshake; + u8 queue_alloc_cmd_ver; +}; + +struct iwl_trans_dump_data { + u32 len; + u8 data[]; +}; + +struct iwl_trans; + +struct iwl_trans_txq_scd_cfg { + u8 fifo; + u8 sta_id; + u8 tid; + bool aggregate; + int frame_limit; +}; + +/** + * struct iwl_trans_rxq_dma_data - RX queue DMA data + * @fr_bd_cb: DMA address of free BD cyclic buffer + * @fr_bd_wid: Initial write index of the free BD cyclic buffer + * @urbd_stts_wrptr: DMA address of urbd_stts_wrptr + * @ur_bd_cb: DMA address of used BD cyclic buffer + */ +struct iwl_trans_rxq_dma_data { + u64 fr_bd_cb; + u32 fr_bd_wid; + u64 urbd_stts_wrptr; + u64 ur_bd_cb; +}; + +/** + * struct iwl_trans_ops - transport specific operations + * + * All the handlers MUST be implemented + * + * @start_hw: starts the HW. From that point on, the HW can send interrupts. + * May sleep. + * @op_mode_leave: Turn off the HW RF kill indication if on + * May sleep + * @start_fw: allocates and inits all the resources for the transport + * layer. Also kick a fw image. + * May sleep + * @fw_alive: called when the fw sends alive notification. If the fw provides + * the SCD base address in SRAM, then provide it here, or 0 otherwise. + * May sleep + * @stop_device: stops the whole device (embedded CPU put to reset) and stops + * the HW. From that point on, the HW will be stopped but will still issue + * an interrupt if the HW RF kill switch is triggered. + * This callback must do the right thing and not crash even if %start_hw() + * was called but not &start_fw(). May sleep. + * @d3_suspend: put the device into the correct mode for WoWLAN during + * suspend. This is optional, if not implemented WoWLAN will not be + * supported. This callback may sleep. + * @d3_resume: resume the device after WoWLAN, enabling the opmode to + * talk to the WoWLAN image to get its status. This is optional, if not + * implemented WoWLAN will not be supported. This callback may sleep. + * @send_cmd:send a host command. Must return -ERFKILL if RFkill is asserted. + * If RFkill is asserted in the middle of a SYNC host command, it must + * return -ERFKILL straight away. + * May sleep only if CMD_ASYNC is not set + * @tx: send an skb. The transport relies on the op_mode to zero the + * the ieee80211_tx_info->driver_data. If the MPDU is an A-MSDU, all + * the CSUM will be taken care of (TCP CSUM and IP header in case of + * IPv4). If the MPDU is a single MSDU, the op_mode must compute the IP + * header if it is IPv4. + * Must be atomic + * @reclaim: free packet until ssn. Returns a list of freed packets. + * Must be atomic + * @txq_enable: setup a queue. To setup an AC queue, use the + * iwl_trans_ac_txq_enable wrapper. fw_alive must have been called before + * this one. The op_mode must not configure the HCMD queue. The scheduler + * configuration may be %NULL, in which case the hardware will not be + * configured. If true is returned, the operation mode needs to increment + * the sequence number of the packets routed to this queue because of a + * hardware scheduler bug. May sleep. + * @txq_disable: de-configure a Tx queue to send AMPDUs + * Must be atomic + * @txq_set_shared_mode: change Tx queue shared/unshared marking + * @wait_tx_queues_empty: wait until tx queues are empty. May sleep. + * @wait_txq_empty: wait until specific tx queue is empty. May sleep. + * @freeze_txq_timer: prevents the timer of the queue from firing until the + * queue is set to awake. Must be atomic. + * @block_txq_ptrs: stop updating the write pointers of the Tx queues. Note + * that the transport needs to refcount the calls since this function + * will be called several times with block = true, and then the queues + * need to be unblocked only after the same number of calls with + * block = false. + * @write8: write a u8 to a register at offset ofs from the BAR + * @write32: write a u32 to a register at offset ofs from the BAR + * @read32: read a u32 register at offset ofs from the BAR + * @read_prph: read a DWORD from a periphery register + * @write_prph: write a DWORD to a periphery register + * @read_mem: read device's SRAM in DWORD + * @write_mem: write device's SRAM in DWORD. If %buf is %NULL, then the memory + * will be zeroed. + * @read_config32: read a u32 value from the device's config space at + * the given offset. + * @configure: configure parameters required by the transport layer from + * the op_mode. May be called several times before start_fw, can't be + * called after that. + * @set_pmi: set the power pmi state + * @grab_nic_access: wake the NIC to be able to access non-HBUS regs. + * Sleeping is not allowed between grab_nic_access and + * release_nic_access. + * @release_nic_access: let the NIC go to sleep. The "flags" parameter + * must be the same one that was sent before to the grab_nic_access. + * @set_bits_mask - set SRAM register according to value and mask. + * @dump_data: return a vmalloc'ed buffer with debug data, maybe containing last + * TX'ed commands and similar. The buffer will be vfree'd by the caller. + * Note that the transport must fill in the proper file headers. + * @debugfs_cleanup: used in the driver unload flow to make a proper cleanup + * of the trans debugfs + * @set_pnvm: set the pnvm data in the prph scratch buffer, inside the + * context info. + * @interrupts: disable/enable interrupts to transport + */ +struct iwl_trans_ops { + + int (*start_hw)(struct iwl_trans *iwl_trans); + void (*op_mode_leave)(struct iwl_trans *iwl_trans); + int (*start_fw)(struct iwl_trans *trans, const struct fw_img *fw, + bool run_in_rfkill); + void (*fw_alive)(struct iwl_trans *trans, u32 scd_addr); + void (*stop_device)(struct iwl_trans *trans); + + int (*d3_suspend)(struct iwl_trans *trans, bool test, bool reset); + int (*d3_resume)(struct iwl_trans *trans, enum iwl_d3_status *status, + bool test, bool reset); + + int (*send_cmd)(struct iwl_trans *trans, struct iwl_host_cmd *cmd); + + int (*tx)(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_device_tx_cmd *dev_cmd, int queue); + void (*reclaim)(struct iwl_trans *trans, int queue, int ssn, + struct sk_buff_head *skbs, bool is_flush); + + void (*set_q_ptrs)(struct iwl_trans *trans, int queue, int ptr); + + bool (*txq_enable)(struct iwl_trans *trans, int queue, u16 ssn, + const struct iwl_trans_txq_scd_cfg *cfg, + unsigned int queue_wdg_timeout); + void (*txq_disable)(struct iwl_trans *trans, int queue, + bool configure_scd); + /* 22000 functions */ + int (*txq_alloc)(struct iwl_trans *trans, u32 flags, + u32 sta_mask, u8 tid, + int size, unsigned int queue_wdg_timeout); + void (*txq_free)(struct iwl_trans *trans, int queue); + int (*rxq_dma_data)(struct iwl_trans *trans, int queue, + struct iwl_trans_rxq_dma_data *data); + + void (*txq_set_shared_mode)(struct iwl_trans *trans, u32 txq_id, + bool shared); + + int (*wait_tx_queues_empty)(struct iwl_trans *trans, u32 txq_bm); + int (*wait_txq_empty)(struct iwl_trans *trans, int queue); + void (*freeze_txq_timer)(struct iwl_trans *trans, unsigned long txqs, + bool freeze); + void (*block_txq_ptrs)(struct iwl_trans *trans, bool block); + + void (*write8)(struct iwl_trans *trans, u32 ofs, u8 val); + void (*write32)(struct iwl_trans *trans, u32 ofs, u32 val); + u32 (*read32)(struct iwl_trans *trans, u32 ofs); + u32 (*read_prph)(struct iwl_trans *trans, u32 ofs); + void (*write_prph)(struct iwl_trans *trans, u32 ofs, u32 val); + int (*read_mem)(struct iwl_trans *trans, u32 addr, + void *buf, int dwords); + int (*write_mem)(struct iwl_trans *trans, u32 addr, + const void *buf, int dwords); + int (*read_config32)(struct iwl_trans *trans, u32 ofs, u32 *val); + void (*configure)(struct iwl_trans *trans, + const struct iwl_trans_config *trans_cfg); + void (*set_pmi)(struct iwl_trans *trans, bool state); + int (*sw_reset)(struct iwl_trans *trans, bool retake_ownership); + bool (*grab_nic_access)(struct iwl_trans *trans); + void (*release_nic_access)(struct iwl_trans *trans); + void (*set_bits_mask)(struct iwl_trans *trans, u32 reg, u32 mask, + u32 value); + + struct iwl_trans_dump_data *(*dump_data)(struct iwl_trans *trans, + u32 dump_mask, + const struct iwl_dump_sanitize_ops *sanitize_ops, + void *sanitize_ctx); + void (*debugfs_cleanup)(struct iwl_trans *trans); + void (*sync_nmi)(struct iwl_trans *trans); + int (*set_pnvm)(struct iwl_trans *trans, const void *data, u32 len); + int (*set_reduce_power)(struct iwl_trans *trans, + const void *data, u32 len); + void (*interrupts)(struct iwl_trans *trans, bool enable); + int (*imr_dma_data)(struct iwl_trans *trans, + u32 dst_addr, u64 src_addr, + u32 byte_cnt); + +}; + +/** + * enum iwl_trans_state - state of the transport layer + * + * @IWL_TRANS_NO_FW: firmware wasn't started yet, or crashed + * @IWL_TRANS_FW_STARTED: FW was started, but not alive yet + * @IWL_TRANS_FW_ALIVE: FW has sent an alive response + */ +enum iwl_trans_state { + IWL_TRANS_NO_FW, + IWL_TRANS_FW_STARTED, + IWL_TRANS_FW_ALIVE, +}; + +/** + * DOC: Platform power management + * + * In system-wide power management the entire platform goes into a low + * power state (e.g. idle or suspend to RAM) at the same time and the + * device is configured as a wakeup source for the entire platform. + * This is usually triggered by userspace activity (e.g. the user + * presses the suspend button or a power management daemon decides to + * put the platform in low power mode). The device's behavior in this + * mode is dictated by the wake-on-WLAN configuration. + * + * The terms used for the device's behavior are as follows: + * + * - D0: the device is fully powered and the host is awake; + * - D3: the device is in low power mode and only reacts to + * specific events (e.g. magic-packet received or scan + * results found); + * + * These terms reflect the power modes in the firmware and are not to + * be confused with the physical device power state. + */ + +/** + * enum iwl_plat_pm_mode - platform power management mode + * + * This enumeration describes the device's platform power management + * behavior when in system-wide suspend (i.e WoWLAN). + * + * @IWL_PLAT_PM_MODE_DISABLED: power management is disabled for this + * device. In system-wide suspend mode, it means that the all + * connections will be closed automatically by mac80211 before + * the platform is suspended. + * @IWL_PLAT_PM_MODE_D3: the device goes into D3 mode (i.e. WoWLAN). + */ +enum iwl_plat_pm_mode { + IWL_PLAT_PM_MODE_DISABLED, + IWL_PLAT_PM_MODE_D3, +}; + +/** + * enum iwl_ini_cfg_state + * @IWL_INI_CFG_STATE_NOT_LOADED: no debug cfg was given + * @IWL_INI_CFG_STATE_LOADED: debug cfg was found and loaded + * @IWL_INI_CFG_STATE_CORRUPTED: debug cfg was found and some of the TLVs + * are corrupted. The rest of the debug TLVs will still be used + */ +enum iwl_ini_cfg_state { + IWL_INI_CFG_STATE_NOT_LOADED, + IWL_INI_CFG_STATE_LOADED, + IWL_INI_CFG_STATE_CORRUPTED, +}; + +/* Max time to wait for nmi interrupt */ +#define IWL_TRANS_NMI_TIMEOUT (HZ / 4) + +/** + * struct iwl_dram_data + * @physical: page phy pointer + * @block: pointer to the allocated block/page + * @size: size of the block/page + */ +struct iwl_dram_data { + dma_addr_t physical; + void *block; + int size; +}; + +/** + * struct iwl_fw_mon - fw monitor per allocation id + * @num_frags: number of fragments + * @frags: an array of DRAM buffer fragments + */ +struct iwl_fw_mon { + u32 num_frags; + struct iwl_dram_data *frags; +}; + +/** + * struct iwl_self_init_dram - dram data used by self init process + * @fw: lmac and umac dram data + * @fw_cnt: total number of items in array + * @paging: paging dram data + * @paging_cnt: total number of items in array + */ +struct iwl_self_init_dram { + struct iwl_dram_data *fw; + int fw_cnt; + struct iwl_dram_data *paging; + int paging_cnt; +}; + +/** + * struct iwl_imr_data - imr dram data used during debug process + * @imr_enable: imr enable status received from fw + * @imr_size: imr dram size received from fw + * @sram_addr: sram address from debug tlv + * @sram_size: sram size from debug tlv + * @imr2sram_remainbyte`: size remained after each dma transfer + * @imr_curr_addr: current dst address used during dma transfer + * @imr_base_addr: imr address received from fw + */ +struct iwl_imr_data { + u32 imr_enable; + u32 imr_size; + u32 sram_addr; + u32 sram_size; + u32 imr2sram_remainbyte; + u64 imr_curr_addr; + __le64 imr_base_addr; +}; + +/** + * struct iwl_trans_debug - transport debug related data + * + * @n_dest_reg: num of reg_ops in %dbg_dest_tlv + * @rec_on: true iff there is a fw debug recording currently active + * @dest_tlv: points to the destination TLV for debug + * @conf_tlv: array of pointers to configuration TLVs for debug + * @trigger_tlv: array of pointers to triggers TLVs for debug + * @lmac_error_event_table: addrs of lmacs error tables + * @umac_error_event_table: addr of umac error table + * @tcm_error_event_table: address(es) of TCM error table(s) + * @rcm_error_event_table: address(es) of RCM error table(s) + * @error_event_table_tlv_status: bitmap that indicates what error table + * pointers was recevied via TLV. uses enum &iwl_error_event_table_status + * @internal_ini_cfg: internal debug cfg state. Uses &enum iwl_ini_cfg_state + * @external_ini_cfg: external debug cfg state. Uses &enum iwl_ini_cfg_state + * @fw_mon_cfg: debug buffer allocation configuration + * @fw_mon_ini: DRAM buffer fragments per allocation id + * @fw_mon: DRAM buffer for firmware monitor + * @hw_error: equals true if hw error interrupt was received from the FW + * @ini_dest: debug monitor destination uses &enum iwl_fw_ini_buffer_location + * @active_regions: active regions + * @debug_info_tlv_list: list of debug info TLVs + * @time_point: array of debug time points + * @periodic_trig_list: periodic triggers list + * @domains_bitmap: bitmap of active domains other than &IWL_FW_INI_DOMAIN_ALWAYS_ON + * @ucode_preset: preset based on ucode + */ +struct iwl_trans_debug { + u8 n_dest_reg; + bool rec_on; + + const struct iwl_fw_dbg_dest_tlv_v1 *dest_tlv; + const struct iwl_fw_dbg_conf_tlv *conf_tlv[FW_DBG_CONF_MAX]; + struct iwl_fw_dbg_trigger_tlv * const *trigger_tlv; + + u32 lmac_error_event_table[2]; + u32 umac_error_event_table; + u32 tcm_error_event_table[2]; + u32 rcm_error_event_table[2]; + unsigned int error_event_table_tlv_status; + + enum iwl_ini_cfg_state internal_ini_cfg; + enum iwl_ini_cfg_state external_ini_cfg; + + struct iwl_fw_ini_allocation_tlv fw_mon_cfg[IWL_FW_INI_ALLOCATION_NUM]; + struct iwl_fw_mon fw_mon_ini[IWL_FW_INI_ALLOCATION_NUM]; + + struct iwl_dram_data fw_mon; + + bool hw_error; + enum iwl_fw_ini_buffer_location ini_dest; + + u64 unsupported_region_msk; + struct iwl_ucode_tlv *active_regions[IWL_FW_INI_MAX_REGION_ID]; + struct list_head debug_info_tlv_list; + struct iwl_dbg_tlv_time_point_data + time_point[IWL_FW_INI_TIME_POINT_NUM]; + struct list_head periodic_trig_list; + + u32 domains_bitmap; + u32 ucode_preset; + bool restart_required; + u32 last_tp_resetfw; + struct iwl_imr_data imr_data; +}; + +struct iwl_dma_ptr { + dma_addr_t dma; + void *addr; + size_t size; +}; + +struct iwl_cmd_meta { + /* only for SYNC commands, iff the reply skb is wanted */ + struct iwl_host_cmd *source; + u32 flags; + u32 tbs; +}; + +/* + * The FH will write back to the first TB only, so we need to copy some data + * into the buffer regardless of whether it should be mapped or not. + * This indicates how big the first TB must be to include the scratch buffer + * and the assigned PN. + * Since PN location is 8 bytes at offset 12, it's 20 now. + * If we make it bigger then allocations will be bigger and copy slower, so + * that's probably not useful. + */ +#define IWL_FIRST_TB_SIZE 20 +#define IWL_FIRST_TB_SIZE_ALIGN ALIGN(IWL_FIRST_TB_SIZE, 64) + +struct iwl_pcie_txq_entry { + void *cmd; + struct sk_buff *skb; + /* buffer to free after command completes */ + const void *free_buf; + struct iwl_cmd_meta meta; +}; + +struct iwl_pcie_first_tb_buf { + u8 buf[IWL_FIRST_TB_SIZE_ALIGN]; +}; + +/** + * struct iwl_txq - Tx Queue for DMA + * @q: generic Rx/Tx queue descriptor + * @tfds: transmit frame descriptors (DMA memory) + * @first_tb_bufs: start of command headers, including scratch buffers, for + * the writeback -- this is DMA memory and an array holding one buffer + * for each command on the queue + * @first_tb_dma: DMA address for the first_tb_bufs start + * @entries: transmit entries (driver state) + * @lock: queue lock + * @stuck_timer: timer that fires if queue gets stuck + * @trans: pointer back to transport (for timer) + * @need_update: indicates need to update read/write index + * @ampdu: true if this queue is an ampdu queue for an specific RA/TID + * @wd_timeout: queue watchdog timeout (jiffies) - per queue + * @frozen: tx stuck queue timer is frozen + * @frozen_expiry_remainder: remember how long until the timer fires + * @bc_tbl: byte count table of the queue (relevant only for gen2 transport) + * @write_ptr: 1-st empty entry (index) host_w + * @read_ptr: last used entry (index) host_r + * @dma_addr: physical addr for BD's + * @n_window: safe queue window + * @id: queue id + * @low_mark: low watermark, resume queue if free space more than this + * @high_mark: high watermark, stop queue if free space less than this + * + * A Tx queue consists of circular buffer of BDs (a.k.a. TFDs, transmit frame + * descriptors) and required locking structures. + * + * Note the difference between TFD_QUEUE_SIZE_MAX and n_window: the hardware + * always assumes 256 descriptors, so TFD_QUEUE_SIZE_MAX is always 256 (unless + * there might be HW changes in the future). For the normal TX + * queues, n_window, which is the size of the software queue data + * is also 256; however, for the command queue, n_window is only + * 32 since we don't need so many commands pending. Since the HW + * still uses 256 BDs for DMA though, TFD_QUEUE_SIZE_MAX stays 256. + * This means that we end up with the following: + * HW entries: | 0 | ... | N * 32 | ... | N * 32 + 31 | ... | 255 | + * SW entries: | 0 | ... | 31 | + * where N is a number between 0 and 7. This means that the SW + * data is a window overlayed over the HW queue. + */ +struct iwl_txq { + void *tfds; + struct iwl_pcie_first_tb_buf *first_tb_bufs; + dma_addr_t first_tb_dma; + struct iwl_pcie_txq_entry *entries; + /* lock for syncing changes on the queue */ + spinlock_t lock; + unsigned long frozen_expiry_remainder; + struct timer_list stuck_timer; + struct iwl_trans *trans; + bool need_update; + bool frozen; + bool ampdu; + int block; + unsigned long wd_timeout; + struct sk_buff_head overflow_q; + struct iwl_dma_ptr bc_tbl; + + int write_ptr; + int read_ptr; + dma_addr_t dma_addr; + int n_window; + u32 id; + int low_mark; + int high_mark; + + bool overflow_tx; +}; + +/** + * struct iwl_trans_txqs - transport tx queues data + * + * @bc_table_dword: true if the BC table expects DWORD (as opposed to bytes) + * @page_offs: offset from skb->cb to mac header page pointer + * @dev_cmd_offs: offset from skb->cb to iwl_device_tx_cmd pointer + * @queue_used - bit mask of used queues + * @queue_stopped - bit mask of stopped queues + * @scd_bc_tbls: gen1 pointer to the byte count table of the scheduler + * @queue_alloc_cmd_ver: queue allocation command version + */ +struct iwl_trans_txqs { + unsigned long queue_used[BITS_TO_LONGS(IWL_MAX_TVQM_QUEUES)]; + unsigned long queue_stopped[BITS_TO_LONGS(IWL_MAX_TVQM_QUEUES)]; + struct iwl_txq *txq[IWL_MAX_TVQM_QUEUES]; + struct dma_pool *bc_pool; + size_t bc_tbl_size; + bool bc_table_dword; + u8 page_offs; + u8 dev_cmd_offs; + struct iwl_tso_hdr_page __percpu *tso_hdr_page; + + struct { + u8 fifo; + u8 q_id; + unsigned int wdg_timeout; + } cmd; + + struct { + u8 max_tbs; + u16 size; + u8 addr_size; + } tfd; + + struct iwl_dma_ptr scd_bc_tbls; + + u8 queue_alloc_cmd_ver; +}; + +/** + * struct iwl_trans - transport common data + * + * @csme_own - true if we couldn't get ownership on the device + * @ops - pointer to iwl_trans_ops + * @op_mode - pointer to the op_mode + * @trans_cfg: the trans-specific configuration part + * @cfg - pointer to the configuration + * @drv - pointer to iwl_drv + * @status: a bit-mask of transport status flags + * @dev - pointer to struct device * that represents the device + * @max_skb_frags: maximum number of fragments an SKB can have when transmitted. + * 0 indicates that frag SKBs (NETIF_F_SG) aren't supported. + * @hw_rf_id a u32 with the device RF ID + * @hw_id: a u32 with the ID of the device / sub-device. + * Set during transport allocation. + * @hw_id_str: a string with info about HW ID. Set during transport allocation. + * @hw_rev_step: The mac step of the HW + * @pm_support: set to true in start_hw if link pm is supported + * @ltr_enabled: set to true if the LTR is enabled + * @wide_cmd_header: true when ucode supports wide command header format + * @wait_command_queue: wait queue for sync commands + * @num_rx_queues: number of RX queues allocated by the transport; + * the transport must set this before calling iwl_drv_start() + * @iml_len: the length of the image loader + * @iml: a pointer to the image loader itself + * @dev_cmd_pool: pool for Tx cmd allocation - for internal use only. + * The user should use iwl_trans_{alloc,free}_tx_cmd. + * @rx_mpdu_cmd: MPDU RX command ID, must be assigned by opmode before + * starting the firmware, used for tracing + * @rx_mpdu_cmd_hdr_size: used for tracing, amount of data before the + * start of the 802.11 header in the @rx_mpdu_cmd + * @dflt_pwr_limit: default power limit fetched from the platform (ACPI) + * @system_pm_mode: the system-wide power management mode in use. + * This mode is set dynamically, depending on the WoWLAN values + * configured from the userspace at runtime. + * @iwl_trans_txqs: transport tx queues data. + */ +struct iwl_trans { + bool csme_own; + const struct iwl_trans_ops *ops; + struct iwl_op_mode *op_mode; + const struct iwl_cfg_trans_params *trans_cfg; + const struct iwl_cfg *cfg; + struct iwl_drv *drv; + enum iwl_trans_state state; + unsigned long status; + + struct device *dev; + u32 max_skb_frags; + u32 hw_rev; + u32 hw_rev_step; + u32 hw_rf_id; + u32 hw_id; + char hw_id_str[52]; + u32 sku_id[3]; + + u8 rx_mpdu_cmd, rx_mpdu_cmd_hdr_size; + + bool pm_support; + bool ltr_enabled; + u8 pnvm_loaded:1; + u8 reduce_power_loaded:1; + + const struct iwl_hcmd_arr *command_groups; + int command_groups_size; + bool wide_cmd_header; + + wait_queue_head_t wait_command_queue; + u8 num_rx_queues; + + size_t iml_len; + u8 *iml; + + /* The following fields are internal only */ + struct kmem_cache *dev_cmd_pool; + char dev_cmd_pool_name[50]; + + struct dentry *dbgfs_dir; + +#ifdef CONFIG_LOCKDEP + struct lockdep_map sync_cmd_lockdep_map; +#endif + + struct iwl_trans_debug dbg; + struct iwl_self_init_dram init_dram; + + enum iwl_plat_pm_mode system_pm_mode; + + const char *name; + struct iwl_trans_txqs txqs; + + /* pointer to trans specific struct */ + /*Ensure that this pointer will always be aligned to sizeof pointer */ + char trans_specific[] __aligned(sizeof(void *)); +}; + +const char *iwl_get_cmd_string(struct iwl_trans *trans, u32 id); +int iwl_cmd_groups_verify_sorted(const struct iwl_trans_config *trans); + +static inline void iwl_trans_configure(struct iwl_trans *trans, + const struct iwl_trans_config *trans_cfg) +{ + trans->op_mode = trans_cfg->op_mode; + + trans->ops->configure(trans, trans_cfg); + WARN_ON(iwl_cmd_groups_verify_sorted(trans_cfg)); +} + +static inline int iwl_trans_start_hw(struct iwl_trans *trans) +{ + might_sleep(); + + return trans->ops->start_hw(trans); +} + +static inline void iwl_trans_op_mode_leave(struct iwl_trans *trans) +{ + might_sleep(); + + if (trans->ops->op_mode_leave) + trans->ops->op_mode_leave(trans); + + trans->op_mode = NULL; + + trans->state = IWL_TRANS_NO_FW; +} + +static inline void iwl_trans_fw_alive(struct iwl_trans *trans, u32 scd_addr) +{ + might_sleep(); + + trans->state = IWL_TRANS_FW_ALIVE; + + trans->ops->fw_alive(trans, scd_addr); +} + +static inline int iwl_trans_start_fw(struct iwl_trans *trans, + const struct fw_img *fw, + bool run_in_rfkill) +{ + int ret; + + might_sleep(); + + WARN_ON_ONCE(!trans->rx_mpdu_cmd); + + clear_bit(STATUS_FW_ERROR, &trans->status); + ret = trans->ops->start_fw(trans, fw, run_in_rfkill); + if (ret == 0) + trans->state = IWL_TRANS_FW_STARTED; + + return ret; +} + +static inline void iwl_trans_stop_device(struct iwl_trans *trans) +{ + might_sleep(); + + trans->ops->stop_device(trans); + + trans->state = IWL_TRANS_NO_FW; +} + +static inline int iwl_trans_d3_suspend(struct iwl_trans *trans, bool test, + bool reset) +{ + might_sleep(); + if (!trans->ops->d3_suspend) + return 0; + + return trans->ops->d3_suspend(trans, test, reset); +} + +static inline int iwl_trans_d3_resume(struct iwl_trans *trans, + enum iwl_d3_status *status, + bool test, bool reset) +{ + might_sleep(); + if (!trans->ops->d3_resume) + return 0; + + return trans->ops->d3_resume(trans, status, test, reset); +} + +static inline struct iwl_trans_dump_data * +iwl_trans_dump_data(struct iwl_trans *trans, u32 dump_mask, + const struct iwl_dump_sanitize_ops *sanitize_ops, + void *sanitize_ctx) +{ + if (!trans->ops->dump_data) + return NULL; + return trans->ops->dump_data(trans, dump_mask, + sanitize_ops, sanitize_ctx); +} + +static inline struct iwl_device_tx_cmd * +iwl_trans_alloc_tx_cmd(struct iwl_trans *trans) +{ + return kmem_cache_zalloc(trans->dev_cmd_pool, GFP_ATOMIC); +} + +int iwl_trans_send_cmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd); + +static inline void iwl_trans_free_tx_cmd(struct iwl_trans *trans, + struct iwl_device_tx_cmd *dev_cmd) +{ + kmem_cache_free(trans->dev_cmd_pool, dev_cmd); +} + +static inline int iwl_trans_tx(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_device_tx_cmd *dev_cmd, int queue) +{ + if (unlikely(test_bit(STATUS_FW_ERROR, &trans->status))) + return -EIO; + + if (WARN_ON_ONCE(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return -EIO; + } + + return trans->ops->tx(trans, skb, dev_cmd, queue); +} + +static inline void iwl_trans_reclaim(struct iwl_trans *trans, int queue, + int ssn, struct sk_buff_head *skbs, + bool is_flush) +{ + if (WARN_ON_ONCE(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return; + } + + trans->ops->reclaim(trans, queue, ssn, skbs, is_flush); +} + +static inline void iwl_trans_set_q_ptrs(struct iwl_trans *trans, int queue, + int ptr) +{ + if (WARN_ON_ONCE(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return; + } + + trans->ops->set_q_ptrs(trans, queue, ptr); +} + +static inline void iwl_trans_txq_disable(struct iwl_trans *trans, int queue, + bool configure_scd) +{ + trans->ops->txq_disable(trans, queue, configure_scd); +} + +static inline bool +iwl_trans_txq_enable_cfg(struct iwl_trans *trans, int queue, u16 ssn, + const struct iwl_trans_txq_scd_cfg *cfg, + unsigned int queue_wdg_timeout) +{ + might_sleep(); + + if (WARN_ON_ONCE(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return false; + } + + return trans->ops->txq_enable(trans, queue, ssn, + cfg, queue_wdg_timeout); +} + +static inline int +iwl_trans_get_rxq_dma_data(struct iwl_trans *trans, int queue, + struct iwl_trans_rxq_dma_data *data) +{ + if (WARN_ON_ONCE(!trans->ops->rxq_dma_data)) + return -ENOTSUPP; + + return trans->ops->rxq_dma_data(trans, queue, data); +} + +static inline void +iwl_trans_txq_free(struct iwl_trans *trans, int queue) +{ + if (WARN_ON_ONCE(!trans->ops->txq_free)) + return; + + trans->ops->txq_free(trans, queue); +} + +static inline int +iwl_trans_txq_alloc(struct iwl_trans *trans, + u32 flags, u32 sta_mask, u8 tid, + int size, unsigned int wdg_timeout) +{ + might_sleep(); + + if (WARN_ON_ONCE(!trans->ops->txq_alloc)) + return -ENOTSUPP; + + if (WARN_ON_ONCE(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return -EIO; + } + + return trans->ops->txq_alloc(trans, flags, sta_mask, tid, + size, wdg_timeout); +} + +static inline void iwl_trans_txq_set_shared_mode(struct iwl_trans *trans, + int queue, bool shared_mode) +{ + if (trans->ops->txq_set_shared_mode) + trans->ops->txq_set_shared_mode(trans, queue, shared_mode); +} + +static inline void iwl_trans_txq_enable(struct iwl_trans *trans, int queue, + int fifo, int sta_id, int tid, + int frame_limit, u16 ssn, + unsigned int queue_wdg_timeout) +{ + struct iwl_trans_txq_scd_cfg cfg = { + .fifo = fifo, + .sta_id = sta_id, + .tid = tid, + .frame_limit = frame_limit, + .aggregate = sta_id >= 0, + }; + + iwl_trans_txq_enable_cfg(trans, queue, ssn, &cfg, queue_wdg_timeout); +} + +static inline +void iwl_trans_ac_txq_enable(struct iwl_trans *trans, int queue, int fifo, + unsigned int queue_wdg_timeout) +{ + struct iwl_trans_txq_scd_cfg cfg = { + .fifo = fifo, + .sta_id = -1, + .tid = IWL_MAX_TID_COUNT, + .frame_limit = IWL_FRAME_LIMIT, + .aggregate = false, + }; + + iwl_trans_txq_enable_cfg(trans, queue, 0, &cfg, queue_wdg_timeout); +} + +static inline void iwl_trans_freeze_txq_timer(struct iwl_trans *trans, + unsigned long txqs, + bool freeze) +{ + if (WARN_ON_ONCE(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return; + } + + if (trans->ops->freeze_txq_timer) + trans->ops->freeze_txq_timer(trans, txqs, freeze); +} + +static inline void iwl_trans_block_txq_ptrs(struct iwl_trans *trans, + bool block) +{ + if (WARN_ON_ONCE(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return; + } + + if (trans->ops->block_txq_ptrs) + trans->ops->block_txq_ptrs(trans, block); +} + +static inline int iwl_trans_wait_tx_queues_empty(struct iwl_trans *trans, + u32 txqs) +{ + if (WARN_ON_ONCE(!trans->ops->wait_tx_queues_empty)) + return -ENOTSUPP; + + /* No need to wait if the firmware is not alive */ + if (trans->state != IWL_TRANS_FW_ALIVE) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return -EIO; + } + + return trans->ops->wait_tx_queues_empty(trans, txqs); +} + +static inline int iwl_trans_wait_txq_empty(struct iwl_trans *trans, int queue) +{ + if (WARN_ON_ONCE(!trans->ops->wait_txq_empty)) + return -ENOTSUPP; + + if (WARN_ON_ONCE(trans->state != IWL_TRANS_FW_ALIVE)) { + IWL_ERR(trans, "%s bad state = %d\n", __func__, trans->state); + return -EIO; + } + + return trans->ops->wait_txq_empty(trans, queue); +} + +static inline void iwl_trans_write8(struct iwl_trans *trans, u32 ofs, u8 val) +{ + trans->ops->write8(trans, ofs, val); +} + +static inline void iwl_trans_write32(struct iwl_trans *trans, u32 ofs, u32 val) +{ + trans->ops->write32(trans, ofs, val); +} + +static inline u32 iwl_trans_read32(struct iwl_trans *trans, u32 ofs) +{ + return trans->ops->read32(trans, ofs); +} + +static inline u32 iwl_trans_read_prph(struct iwl_trans *trans, u32 ofs) +{ + return trans->ops->read_prph(trans, ofs); +} + +static inline void iwl_trans_write_prph(struct iwl_trans *trans, u32 ofs, + u32 val) +{ + return trans->ops->write_prph(trans, ofs, val); +} + +static inline int iwl_trans_read_mem(struct iwl_trans *trans, u32 addr, + void *buf, int dwords) +{ + return trans->ops->read_mem(trans, addr, buf, dwords); +} + +#define iwl_trans_read_mem_bytes(trans, addr, buf, bufsize) \ + do { \ + if (__builtin_constant_p(bufsize)) \ + BUILD_BUG_ON((bufsize) % sizeof(u32)); \ + iwl_trans_read_mem(trans, addr, buf, (bufsize) / sizeof(u32));\ + } while (0) + +static inline int iwl_trans_write_imr_mem(struct iwl_trans *trans, + u32 dst_addr, u64 src_addr, + u32 byte_cnt) +{ + if (trans->ops->imr_dma_data) + return trans->ops->imr_dma_data(trans, dst_addr, src_addr, byte_cnt); + return 0; +} + +static inline u32 iwl_trans_read_mem32(struct iwl_trans *trans, u32 addr) +{ + u32 value; + + if (WARN_ON(iwl_trans_read_mem(trans, addr, &value, 1))) + return 0xa5a5a5a5; + + return value; +} + +static inline int iwl_trans_write_mem(struct iwl_trans *trans, u32 addr, + const void *buf, int dwords) +{ + return trans->ops->write_mem(trans, addr, buf, dwords); +} + +static inline u32 iwl_trans_write_mem32(struct iwl_trans *trans, u32 addr, + u32 val) +{ + return iwl_trans_write_mem(trans, addr, &val, 1); +} + +static inline void iwl_trans_set_pmi(struct iwl_trans *trans, bool state) +{ + if (trans->ops->set_pmi) + trans->ops->set_pmi(trans, state); +} + +static inline int iwl_trans_sw_reset(struct iwl_trans *trans, + bool retake_ownership) +{ + if (trans->ops->sw_reset) + return trans->ops->sw_reset(trans, retake_ownership); + return 0; +} + +static inline void +iwl_trans_set_bits_mask(struct iwl_trans *trans, u32 reg, u32 mask, u32 value) +{ + trans->ops->set_bits_mask(trans, reg, mask, value); +} + +#define iwl_trans_grab_nic_access(trans) \ + __cond_lock(nic_access, \ + likely((trans)->ops->grab_nic_access(trans))) + +static inline void __releases(nic_access) +iwl_trans_release_nic_access(struct iwl_trans *trans) +{ + trans->ops->release_nic_access(trans); + __release(nic_access); +} + +static inline void iwl_trans_fw_error(struct iwl_trans *trans, bool sync) +{ + if (WARN_ON_ONCE(!trans->op_mode)) + return; + + /* prevent double restarts due to the same erroneous FW */ + if (!test_and_set_bit(STATUS_FW_ERROR, &trans->status)) { + iwl_op_mode_nic_error(trans->op_mode, sync); + trans->state = IWL_TRANS_NO_FW; + } +} + +static inline bool iwl_trans_fw_running(struct iwl_trans *trans) +{ + return trans->state == IWL_TRANS_FW_ALIVE; +} + +static inline void iwl_trans_sync_nmi(struct iwl_trans *trans) +{ + if (trans->ops->sync_nmi) + trans->ops->sync_nmi(trans); +} + +void iwl_trans_sync_nmi_with_addr(struct iwl_trans *trans, u32 inta_addr, + u32 sw_err_bit); + +static inline int iwl_trans_set_pnvm(struct iwl_trans *trans, + const void *data, u32 len) +{ + if (trans->ops->set_pnvm) { + int ret = trans->ops->set_pnvm(trans, data, len); + + if (ret) + return ret; + } + + trans->pnvm_loaded = true; + + return 0; +} + +static inline int iwl_trans_set_reduce_power(struct iwl_trans *trans, + const void *data, u32 len) +{ + if (trans->ops->set_reduce_power) { + int ret = trans->ops->set_reduce_power(trans, data, len); + + if (ret) + return ret; + } + + trans->reduce_power_loaded = true; + return 0; +} + +static inline bool iwl_trans_dbg_ini_valid(struct iwl_trans *trans) +{ + return trans->dbg.internal_ini_cfg != IWL_INI_CFG_STATE_NOT_LOADED || + trans->dbg.external_ini_cfg != IWL_INI_CFG_STATE_NOT_LOADED; +} + +static inline void iwl_trans_interrupts(struct iwl_trans *trans, bool enable) +{ + if (trans->ops->interrupts) + trans->ops->interrupts(trans, enable); +} + +/***************************************************** + * transport helper functions + *****************************************************/ +struct iwl_trans *iwl_trans_alloc(unsigned int priv_size, + struct device *dev, + const struct iwl_trans_ops *ops, + const struct iwl_cfg_trans_params *cfg_trans); +int iwl_trans_init(struct iwl_trans *trans); +void iwl_trans_free(struct iwl_trans *trans); + +/***************************************************** +* driver (transport) register/unregister functions +******************************************************/ +int __must_check iwl_pci_register_driver(void); +void iwl_pci_unregister_driver(void); + +#endif /* __iwl_trans_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mei/Makefile b/drivers/net/wireless/intel/iwlwifi/mei/Makefile new file mode 100644 index 000000000..8e3ef0347 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mei/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_IWLMEI) += iwlmei.o +iwlmei-y += main.o +iwlmei-y += net.o +iwlmei-$(CONFIG_IWLWIFI_DEVICE_TRACING) += trace.o +CFLAGS_trace.o := -I$(src) + +ccflags-y += -I $(srctree)/$(src)/../ diff --git a/drivers/net/wireless/intel/iwlwifi/mei/internal.h b/drivers/net/wireless/intel/iwlwifi/mei/internal.h new file mode 100644 index 000000000..92fea7dd7 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mei/internal.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021 Intel Corporation + */ + +#ifndef __IWLMEI_INTERNAL_H_ +#define __IWLMEI_INTERNAL_H_ + +#include <uapi/linux/if_ether.h> +#include <linux/netdevice.h> + +#include "sap.h" + +rx_handler_result_t iwl_mei_rx_filter(struct sk_buff *skb, + const struct iwl_sap_oob_filters *filters, + bool *pass_to_csme); + +void iwl_mei_add_data_to_ring(struct sk_buff *skb, bool cb_tx); + +#endif /* __IWLMEI_INTERNAL_H_ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mei/iwl-mei.h b/drivers/net/wireless/intel/iwlwifi/mei/iwl-mei.h new file mode 100644 index 000000000..5409699c9 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mei/iwl-mei.h @@ -0,0 +1,506 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021 Intel Corporation + */ + +#ifndef __iwl_mei_h__ +#define __iwl_mei_h__ + +#include <linux/if_ether.h> +#include <linux/skbuff.h> +#include <linux/ieee80211.h> + +/** + * DOC: Introduction + * + * iwlmei is the kernel module that is in charge of the commnunication between + * the iwlwifi driver and the CSME firmware's WLAN driver. This communication + * uses the SAP protocol defined in another file. + * iwlwifi can request or release ownership on the WiFi device through iwlmei. + * iwlmei may notify iwlwifi about certain events: what filter iwlwifi should + * use to passthrough inbound packets to the CSME firmware for example. iwlmei + * may also use iwlwifi to send traffic. This means that we need communication + * from iwlmei to iwlwifi and the other way around. + */ + +/** + * DOC: Life cycle + * + * iwlmei exports symbols that are needed by iwlwifi so that iwlmei will always + * be loaded when iwlwifi is alive. iwlwifi registers itself to iwlmei and + * provides the pointers to the functions that iwlmei calls whenever needed. + * iwlwifi calls iwlmei through direct and context-free function calls. + * It is assumed that only one device is accessible to the CSME firmware and + * under the scope of iwlmei so that it is valid not to have any context passed + * to iwlmei's functions. + * + * There are cases in which iwlmei can't access the CSME firmware, because the + * CSME firmware is undergoing a reset, or the mei bus decided to unbind the + * device. In those cases, iwlmei will need not to send requests over the mei + * bus. Instead, it needs to cache the requests from iwlwifi and fulfill them + * when the mei bus is available again. + * + * iwlmei can call iwlwifi as long as iwlwifi is registered to iwlmei. When + * iwlwifi goes down (the PCI device is unbound, or the iwlwifi is unloaded) + * iwlwifi needs to unregister from iwlmei. + */ + +/** + * DOC: Memory layout + * + * Since iwlwifi calls iwlmei without any context, iwlmei needs to hold a + * global pointer to its data (which is in the mei client device's private + * data area). If there was no bind on the mei bus, this pointer is NULL and + * iwlmei knows not access to the CSME firmware upon requests from iwlwifi. + * + * iwlmei needs to cache requests from iwlwifi when there is no mei client + * device available (when iwlmei has been removed from the mei bus). In this + * case, all iwlmei's data that resides in the mei client device's private data + * area is unavailable. For this specific case, a separate caching area is + * needed. + */ + +/** + * DOC: Concurrency + * + * iwlwifi can call iwlmei at any time. iwlmei will take care to synchronize + * the calls from iwlwifi with its internal flows. iwlwifi must not call iwlmei + * in flows that cannot sleep. Moreover, iwlwifi must not call iwlmei in flows + * that originated from iwlmei. + */ + +/** + * DOC: Probe and remove from mei bus driver + * + * When the mei bus driver enumerates its devices, it calls the iwlmei's probe + * function which will send the %SAP_ME_MSG_START message. The probe completes + * before the response (%SAP_ME_MSG_START_OK) is received. This response will + * be handle by the Rx path. Once it arrives, the connection to the CSME + * firmware is considered established and iwlwifi's requests can be treated + * against the CSME firmware. + * + * When the mei bus driver removes the device, iwlmei loses all the data that + * was attached to the mei client device. It clears the global pointer to the + * mei client device since it is not available anymore. This will cause all the + * requests coming from iwlwifi to be cached. This flow takes the global mutex + * to be synchronized with all the requests coming from iwlwifi. + */ + +/** + * DOC: Driver load when CSME owns the device + * + * When the driver (iwlwifi) is loaded while CSME owns the device, + * it'll ask CSME to release the device through HW registers. CSME + * will release the device only in the case that there is no connection + * through the mei bus. If there is a mei bus connection, CSME will refuse + * to release the ownership on the device through the HW registers. In that + * case, iwlwifi must first request ownership using the SAP protocol. + * + * Once iwlwifi will request ownership through the SAP protocol, CSME will + * grant the ownership on the device through the HW registers as well. + * In order to request ownership over SAP, we first need to have an interface + * which means that we need to register to mac80211. + * This can't happen before we get the NVM that contains all the capabilities + * of the device. Reading the NVM usually requires the load the firmware, but + * this is impossible as long as we don't have ownership on the device. + * In order to solve this chicken and egg problem, the host driver can get + * the NVM through CSME which owns the device. It can send + * %SAP_MSG_NOTIF_GET_NVM, which will be replied by %SAP_MSG_NOTIF_NVM with + * the NVM's content that the host driver needs. + */ + +/** + * DOC: CSME behavior regarding the ownership requests + * + * The ownership requests from the host can come in two different ways: + * - the HW registers in iwl_pcie_set_hw_ready + * - using the Software Arbitration Protocol (SAP) + * + * The host can ask CSME who owns the device with %SAP_MSG_NOTIF_WHO_OWNS_NIC, + * and it can request ownership with %SAP_MSG_NOTIF_HOST_ASKS_FOR_NIC_OWNERSHIP. + * The host will first use %SAP_MSG_NOTIF_WHO_OWNS_NIC to know what state + * CSME is in. In case CSME thinks it owns the device, the host can ask for + * ownership with %SAP_MSG_NOTIF_HOST_ASKS_FOR_NIC_OWNERSHIP. + * + * Here the table that describes CSME's behavior upon ownership request: + * + * +-------------------+------------+--------------+-----------------------------+------------+ + * | State | HW reg bit | Reply for | Event | HW reg bit | + * | | before | WHO_OWNS_NIC | | after | + * +===================+============+==============+=============================+============+ + * | WiAMT not | 0 | Host | HW register or | 0 | + * | operational | Host owner | | HOST_ASKS_FOR_NIC_OWNERSHIP | Host owner | + * +-------------------+------------+--------------+-----------------------------+------------+ + * | Operational & | 1 | N/A | HW register | 0 | + * | SAP down & | CSME owner | | | Host owner | + * | no session active | | | | | + * +-------------------+------------+--------------+-----------------------------+------------+ + * | Operational & | 1 | CSME | HW register | 1 | + * | SAP up | CSME owner | | | CSME owner | + * +-------------------+------------+--------------+-----------------------------+------------+ + * | Operational & | 1 | CSME | HOST_ASKS_FOR_NIC_OWNERSHIP | 0 | + * | SAP up | CSME owner | | | Host owner | + * +-------------------+------------+--------------+-----------------------------+------------+ + */ + +/** + * DOC: Driver load when CSME is associated and a session is active + * + * A "session" is active when CSME is associated to an access point and the + * link is used to attach a remote driver or to control the system remotely. + * When a session is active, we want to make sure it won't disconnect when we + * take ownership on the device. + * In this case, the driver can get the device, but it'll need to make + * sure that it'll connect to the exact same AP (same BSSID). + * In order to do so, CSME will send the connection parameters through + * SAP and then the host can check if it can connect to this same AP. + * If yes, it can request ownership through SAP and connect quickly without + * scanning all the channels, but just probing the AP on the channel that + * CSME was connected to. + * In order to signal this specific scenario to iwlwifi, iwlmei will + * immediately require iwlwifi to report RF-Kill to the network stack. This + * RF-Kill will prevent the stack from getting the device, and it has a reason + * that tells the userspace that the device is in RF-Kill because it is not + * owned by the host. Once the userspace has configured the right profile, + * it'll be able to let iwlmei know that it can request ownership over SAP + * which will remove the RF-Kill, and finally allow the host to connect. + * The host has then 3 seconds to connect (including DHCP). Had the host + * failed to connect within those 3 seconds, CSME will take the device back. + */ + +/** + * DOC: Datapath + * + * CSME can transmit packets, through the netdev that it gets from the wifi + * driver. It'll send packet in the 802.3 format and simply call + * dev_queue_xmit. + * + * For Rx, iwlmei registers a Rx handler that it attaches to the netdev. iwlmei + * may catch packets and send them to CSME, it can then either drop them so + * that they are invisible to user space, or let them go the user space. + * + * Packets transmitted by the user space do not need to be forwarded to CSME + * with the exception of the DHCP request. In order to know what IP is used + * by the user space, CSME needs to get the DHCP request. See + * iwl_mei_tx_copy_to_csme(). + */ + +/** + * enum iwl_mei_nvm_caps - capabilities for MEI NVM + * @MEI_NVM_CAPS_LARI_SUPPORT: Lari is supported + * @MEI_NVM_CAPS_11AX_SUPPORT: 11AX is supported + */ +enum iwl_mei_nvm_caps { + MEI_NVM_CAPS_LARI_SUPPORT = BIT(0), + MEI_NVM_CAPS_11AX_SUPPORT = BIT(1), +}; + +/** + * struct iwl_mei_nvm - used to pass the NVM from CSME + * @hw_addr: The MAC address + * @n_hw_addrs: The number of MAC addresses + * @reserved: For alignment. + * @radio_cfg: The radio configuration. + * @caps: See &enum iwl_mei_nvm_caps. + * @nvm_version: The version of the NVM. + * @channels: The data for each channel. + * + * If a field is added, it must correspond to the SAP structure. + */ +struct iwl_mei_nvm { + u8 hw_addr[ETH_ALEN]; + u8 n_hw_addrs; + u8 reserved; + u32 radio_cfg; + u32 caps; + u32 nvm_version; + u32 channels[110]; +}; + +/** + * enum iwl_mei_pairwise_cipher - cipher for UCAST key + * @IWL_MEI_CIPHER_NONE: none + * @IWL_MEI_CIPHER_CCMP: ccmp + * @IWL_MEI_CIPHER_GCMP: gcmp + * @IWL_MEI_CIPHER_GCMP_256: gcmp 256 + * + * Note that those values are dictated by the CSME firmware API (see sap.h) + */ +enum iwl_mei_pairwise_cipher { + IWL_MEI_CIPHER_NONE = 0, + IWL_MEI_CIPHER_CCMP = 4, + IWL_MEI_CIPHER_GCMP = 8, + IWL_MEI_CIPHER_GCMP_256 = 9, +}; + +/** + * enum iwl_mei_akm_auth - a combination of AKM and AUTH method + * @IWL_MEI_AKM_AUTH_OPEN: No encryption + * @IWL_MEI_AKM_AUTH_RSNA: 1X profile + * @IWL_MEI_AKM_AUTH_RSNA_PSK: PSK profile + * @IWL_MEI_AKM_AUTH_SAE: SAE profile + * + * Note that those values are dictated by the CSME firmware API (see sap.h) + */ +enum iwl_mei_akm_auth { + IWL_MEI_AKM_AUTH_OPEN = 0, + IWL_MEI_AKM_AUTH_RSNA = 6, + IWL_MEI_AKM_AUTH_RSNA_PSK = 7, + IWL_MEI_AKM_AUTH_SAE = 9, +}; + +/** + * struct iwl_mei_conn_info - connection info + * @lp_state: link protection state + * @auth_mode: authentication mode + * @ssid_len: the length of SSID + * @ssid: the SSID + * @pairwise_cipher: the cipher used for unicast packets + * @channel: the associated channel + * @band: the associated band + * @bssid: the BSSID + */ +struct iwl_mei_conn_info { + u8 lp_state; + u8 auth_mode; + u8 ssid_len; + u8 channel; + u8 band; + u8 pairwise_cipher; + u8 bssid[ETH_ALEN]; + u8 ssid[IEEE80211_MAX_SSID_LEN]; +}; + +/** + * struct iwl_mei_colloc_info - collocated AP info + * @channel: the channel of the collocated AP + * @bssid: the BSSID of the collocated AP + */ +struct iwl_mei_colloc_info { + u8 channel; + u8 bssid[ETH_ALEN]; +}; + +/* + * struct iwl_mei_ops - driver's operations called by iwlmei + * Operations will not be called more than once concurrently. + * It's not allowed to call iwlmei functions from this context. + * + * @me_conn_status: provide information about CSME's current connection. + * @rfkill: called when the wifi driver should report a change in the rfkill + * status. + * @roaming_forbidden: indicates whether roaming is forbidden. + * @sap_connected: indicate that SAP is now connected. Will be called in case + * the wifi driver registered to iwlmei before SAP connection succeeded or + * when the SAP connection is re-established. + * @nic_stolen: this means that device is no longer available. The device can + * still be used until the callback returns. + */ +struct iwl_mei_ops { + void (*me_conn_status)(void *priv, + const struct iwl_mei_conn_info *conn_info); + void (*rfkill)(void *priv, bool blocked); + void (*roaming_forbidden)(void *priv, bool forbidden); + void (*sap_connected)(void *priv); + void (*nic_stolen)(void *priv); +}; + +#if IS_ENABLED(CONFIG_IWLMEI) + +/** + * iwl_mei_is_connected() - is the connection to the CSME firmware established? + * + * Return: true if we have a SAP connection + */ +bool iwl_mei_is_connected(void); + +/** + * iwl_mei_get_nvm() - returns the NVM for the device + * + * It is the caller's responsibility to free the memory returned + * by this function. + * This function blocks (sleeps) until the NVM is ready. + * + * Return: the NVM as received from CSME + */ +struct iwl_mei_nvm *iwl_mei_get_nvm(void); + +/** + * iwl_mei_get_ownership() - request ownership + * + * This function blocks until ownership is granted or timeout expired. + * + * Return: 0 in case we could get ownership on the device + */ +int iwl_mei_get_ownership(void); + +/** + * iwl_mei_set_rfkill_state() - set SW and HW RF kill states + * @hw_rfkill: HW RF kill state. + * @sw_rfkill: SW RF kill state. + * + * This function must be called when SW RF kill is issued by the user. + */ +void iwl_mei_set_rfkill_state(bool hw_rfkill, bool sw_rfkill); + +/** + * iwl_mei_set_nic_info() - set mac address + * @mac_address: mac address to set + * @nvm_address: NVM mac adsress to set + * + * This function must be called upon mac address change. + */ +void iwl_mei_set_nic_info(const u8 *mac_address, const u8 *nvm_address); + +/** + * iwl_mei_set_country_code() - set new country code + * @mcc: the new applied MCC + * + * This function must be called upon country code update + */ +void iwl_mei_set_country_code(u16 mcc); + +/** + * iwl_mei_set_power_limit() - set TX power limit + * @power_limit: pointer to an array of 10 elements (le16) represents the power + * restrictions per chain. + * + * This function must be called upon power restrictions change + */ +void iwl_mei_set_power_limit(const __le16 *power_limit); + +/** + * iwl_mei_register() - register the wifi driver to iwlmei + * @priv: a pointer to the wifi driver's context. Cannot be NULL. + * @ops: the ops structure. + * + * Return: 0 unless something went wrong. It is illegal to call any + * other API function before this function is called and succeeds. + * + * Only one wifi driver instance (wifi device instance really) + * can register at a time. + */ +int iwl_mei_register(void *priv, const struct iwl_mei_ops *ops); + +/** + * iwl_mei_start_unregister() - unregister the wifi driver from iwlmei + * + * From this point on, iwlmei will not used the callbacks provided by + * the driver, but the device is still usable. + */ +void iwl_mei_start_unregister(void); + +/** + * iwl_mei_unregister_complete() - complete the unregistration + * + * Must be called after iwl_mei_start_unregister. When this function returns, + * the device is owned by CSME. + */ +void iwl_mei_unregister_complete(void); + +/** + * iwl_mei_set_netdev() - sets the netdev for Tx / Rx. + * @netdev: the net_device + * + * The caller should set the netdev to a non-NULL value when the + * interface is added. Packets might be sent to the driver immediately + * afterwards. + * The caller should set the netdev to NULL when the interface is removed. + * This function will call synchronize_net() after setting the netdev to NULL. + * Only when this function returns, can the caller assume that iwlmei will + * no longer inject packets into the netdev's Tx path. + * + * Context: This function can sleep and assumes rtnl_lock is taken. + * The netdev must be set to NULL before iwl_mei_start_unregister() is called. + */ +void iwl_mei_set_netdev(struct net_device *netdev); + +/** + * iwl_mei_tx_copy_to_csme() - must be called for each packet sent by + * the wifi driver. + * @skb: the skb sent + * @ivlen: the size of the IV that needs to be skipped after the MAC and + * before the SNAP header. + * + * This function doesn't take any lock, it simply tries to catch DHCP + * packets sent by the wifi driver. If the packet is a DHCP packet, it + * will send it to CSME. This function must not be called for virtual + * interfaces that are not monitored by CSME, meaning it must be called + * only for packets transmitted by the netdevice that was registered + * with iwl_mei_set_netdev(). + */ +void iwl_mei_tx_copy_to_csme(struct sk_buff *skb, unsigned int ivlen); + +/** + * iwl_mei_host_associated() - must be called when iwlwifi associated. + * @conn_info: pointer to the connection info structure. + * @colloc_info: pointer to the collocated AP info. This is relevant only in + * case of UHB associated AP, otherwise set to NULL. + */ +void iwl_mei_host_associated(const struct iwl_mei_conn_info *conn_info, + const struct iwl_mei_colloc_info *colloc_info); + +/** + * iwl_mei_host_disassociated() - must be called when iwlwifi disassociated. + */ +void iwl_mei_host_disassociated(void); + +/** + * iwl_mei_device_state() - must be called when the device changes up/down state + * @up: true if the device is up, false otherwise. + */ +void iwl_mei_device_state(bool up); + +#else + +static inline bool iwl_mei_is_connected(void) +{ return false; } + +static inline struct iwl_mei_nvm *iwl_mei_get_nvm(void) +{ return NULL; } + +static inline int iwl_mei_get_ownership(void) +{ return 0; } + +static inline void iwl_mei_set_rfkill_state(bool hw_rfkill, bool sw_rfkill) +{} + +static inline void iwl_mei_set_nic_info(const u8 *mac_address, const u8 *nvm_address) +{} + +static inline void iwl_mei_set_country_code(u16 mcc) +{} + +static inline void iwl_mei_set_power_limit(__le16 *power_limit) +{} + +static inline int iwl_mei_register(void *priv, + const struct iwl_mei_ops *ops) +{ return 0; } + +static inline void iwl_mei_start_unregister(void) +{} + +static inline void iwl_mei_unregister_complete(void) +{} + +static inline void iwl_mei_set_netdev(struct net_device *netdev) +{} + +static inline void iwl_mei_tx_copy_to_csme(struct sk_buff *skb, + unsigned int ivlen) +{} + +static inline void iwl_mei_host_associated(const struct iwl_mei_conn_info *conn_info, + const struct iwl_mei_colloc_info *colloc_info) +{} + +static inline void iwl_mei_host_disassociated(void) +{} + +static inline void iwl_mei_device_state(bool up) +{} + +#endif /* CONFIG_IWLMEI */ + +#endif /* __iwl_mei_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mei/main.c b/drivers/net/wireless/intel/iwlwifi/mei/main.c new file mode 100644 index 000000000..27eb28290 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mei/main.c @@ -0,0 +1,2061 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2022 Intel Corporation + */ + +#include <linux/etherdevice.h> +#include <linux/netdevice.h> +#include <linux/ieee80211.h> +#include <linux/rtnetlink.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/mei_cl_bus.h> +#include <linux/rcupdate.h> +#include <linux/debugfs.h> +#include <linux/skbuff.h> +#include <linux/wait.h> +#include <linux/slab.h> +#include <linux/mm.h> + +#include <net/cfg80211.h> + +#include "internal.h" +#include "iwl-mei.h" +#include "trace.h" +#include "trace-data.h" +#include "sap.h" + +MODULE_DESCRIPTION("The Intel(R) wireless / CSME firmware interface"); +MODULE_LICENSE("GPL"); + +#define MEI_WLAN_UUID UUID_LE(0x13280904, 0x7792, 0x4fcb, \ + 0xa1, 0xaa, 0x5e, 0x70, 0xcb, 0xb1, 0xe8, 0x65) + +/* + * Since iwlwifi calls iwlmei without any context, hold a pointer to the + * mei_cl_device structure here. + * Define a mutex that will synchronize all the flows between iwlwifi and + * iwlmei. + * Note that iwlmei can't have several instances, so it ok to have static + * variables here. + */ +static struct mei_cl_device *iwl_mei_global_cldev; +static DEFINE_MUTEX(iwl_mei_mutex); +static unsigned long iwl_mei_status; + +enum iwl_mei_status_bits { + IWL_MEI_STATUS_SAP_CONNECTED, +}; + +bool iwl_mei_is_connected(void) +{ + return test_bit(IWL_MEI_STATUS_SAP_CONNECTED, &iwl_mei_status); +} +EXPORT_SYMBOL_GPL(iwl_mei_is_connected); + +#define SAP_VERSION 3 +#define SAP_CONTROL_BLOCK_ID 0x21504153 /* SAP! in ASCII */ + +struct iwl_sap_q_ctrl_blk { + __le32 wr_ptr; + __le32 rd_ptr; + __le32 size; +}; + +enum iwl_sap_q_idx { + SAP_QUEUE_IDX_NOTIF = 0, + SAP_QUEUE_IDX_DATA, + SAP_QUEUE_IDX_MAX, +}; + +struct iwl_sap_dir { + __le32 reserved; + struct iwl_sap_q_ctrl_blk q_ctrl_blk[SAP_QUEUE_IDX_MAX]; +}; + +enum iwl_sap_dir_idx { + SAP_DIRECTION_HOST_TO_ME = 0, + SAP_DIRECTION_ME_TO_HOST, + SAP_DIRECTION_MAX, +}; + +struct iwl_sap_shared_mem_ctrl_blk { + __le32 sap_id; + __le32 size; + struct iwl_sap_dir dir[SAP_DIRECTION_MAX]; +}; + +/* + * The shared area has the following layout: + * + * +-----------------------------------+ + * |struct iwl_sap_shared_mem_ctrl_blk | + * +-----------------------------------+ + * |Host -> ME data queue | + * +-----------------------------------+ + * |Host -> ME notif queue | + * +-----------------------------------+ + * |ME -> Host data queue | + * +-----------------------------------+ + * |ME -> host notif queue | + * +-----------------------------------+ + * |SAP control block id (SAP!) | + * +-----------------------------------+ + */ + +#define SAP_H2M_DATA_Q_SZ 48256 +#define SAP_M2H_DATA_Q_SZ 24128 +#define SAP_H2M_NOTIF_Q_SZ 2240 +#define SAP_M2H_NOTIF_Q_SZ 62720 + +#define _IWL_MEI_SAP_SHARED_MEM_SZ \ + (sizeof(struct iwl_sap_shared_mem_ctrl_blk) + \ + SAP_H2M_DATA_Q_SZ + SAP_H2M_NOTIF_Q_SZ + \ + SAP_M2H_DATA_Q_SZ + SAP_M2H_NOTIF_Q_SZ + 4) + +#define IWL_MEI_SAP_SHARED_MEM_SZ \ + (roundup(_IWL_MEI_SAP_SHARED_MEM_SZ, PAGE_SIZE)) + +struct iwl_mei_shared_mem_ptrs { + struct iwl_sap_shared_mem_ctrl_blk *ctrl; + void *q_head[SAP_DIRECTION_MAX][SAP_QUEUE_IDX_MAX]; + size_t q_size[SAP_DIRECTION_MAX][SAP_QUEUE_IDX_MAX]; +}; + +struct iwl_mei_filters { + struct rcu_head rcu_head; + struct iwl_sap_oob_filters filters; +}; + +/** + * struct iwl_mei - holds the private date for iwl_mei + * + * @get_nvm_wq: the wait queue for the get_nvm flow + * @send_csa_msg_wk: used to defer the transmission of the CHECK_SHARED_AREA + * message. Used so that we can send CHECK_SHARED_AREA from atomic + * contexts. + * @get_ownership_wq: the wait queue for the get_ownership_flow + * @shared_mem: the memory that is shared between CSME and the host + * @cldev: the pointer to the MEI client device + * @nvm: the data returned by the CSME for the NVM + * @filters: the filters sent by CSME + * @got_ownership: true if we own the device + * @amt_enabled: true if CSME has wireless enabled + * @csa_throttled: when true, we can't send CHECK_SHARED_AREA over the MEI + * bus, but rather need to wait until send_csa_msg_wk runs + * @csme_taking_ownership: true when CSME is taking ownership. Used to remember + * to send CSME_OWNERSHIP_CONFIRMED when the driver completes its down + * flow. + * @link_prot_state: true when we are in link protection PASSIVE + * @device_down: true if the device is down. Used to remember to send + * CSME_OWNERSHIP_CONFIRMED when the driver is already down. + * @csa_throttle_end_wk: used when &csa_throttled is true + * @data_q_lock: protects the access to the data queues which are + * accessed without the mutex. + * @netdev_work: used to defer registering and unregistering of the netdev to + * avoid taking the rtnl lock in the SAP messages handlers. + * @sap_seq_no: the sequence number for the SAP messages + * @seq_no: the sequence number for the SAP messages + * @dbgfs_dir: the debugfs dir entry + */ +struct iwl_mei { + wait_queue_head_t get_nvm_wq; + struct work_struct send_csa_msg_wk; + wait_queue_head_t get_ownership_wq; + struct iwl_mei_shared_mem_ptrs shared_mem; + struct mei_cl_device *cldev; + struct iwl_mei_nvm *nvm; + struct iwl_mei_filters __rcu *filters; + bool got_ownership; + bool amt_enabled; + bool csa_throttled; + bool csme_taking_ownership; + bool link_prot_state; + bool device_down; + struct delayed_work csa_throttle_end_wk; + spinlock_t data_q_lock; + struct work_struct netdev_work; + + atomic_t sap_seq_no; + atomic_t seq_no; + + struct dentry *dbgfs_dir; +}; + +/** + * struct iwl_mei_cache - cache for the parameters from iwlwifi + * @ops: Callbacks to iwlwifi. + * @netdev: The netdev that will be used to transmit / receive packets. + * @conn_info: The connection info message triggered by iwlwifi's association. + * @power_limit: pointer to an array of 10 elements (le16) represents the power + * restrictions per chain. + * @rf_kill: rf kill state. + * @mcc: MCC info + * @mac_address: interface MAC address. + * @nvm_address: NVM MAC address. + * @priv: A pointer to iwlwifi. + * + * This used to cache the configurations coming from iwlwifi's way. The data + * is cached here so that we can buffer the configuration even if we don't have + * a bind from the mei bus and hence, on iwl_mei structure. + */ +struct iwl_mei_cache { + const struct iwl_mei_ops *ops; + struct net_device __rcu *netdev; + const struct iwl_sap_notif_connection_info *conn_info; + const __le16 *power_limit; + u32 rf_kill; + u16 mcc; + u8 mac_address[6]; + u8 nvm_address[6]; + void *priv; +}; + +static struct iwl_mei_cache iwl_mei_cache = { + .rf_kill = SAP_HW_RFKILL_DEASSERTED | SAP_SW_RFKILL_DEASSERTED +}; + +static void iwl_mei_free_shared_mem(struct mei_cl_device *cldev) +{ + struct iwl_mei *mei = mei_cldev_get_drvdata(cldev); + + if (mei_cldev_dma_unmap(cldev)) + dev_err(&cldev->dev, "Couldn't unmap the shared mem properly\n"); + memset(&mei->shared_mem, 0, sizeof(mei->shared_mem)); +} + +#define HBM_DMA_BUF_ID_WLAN 1 + +static int iwl_mei_alloc_shared_mem(struct mei_cl_device *cldev) +{ + struct iwl_mei *mei = mei_cldev_get_drvdata(cldev); + struct iwl_mei_shared_mem_ptrs *mem = &mei->shared_mem; + + mem->ctrl = mei_cldev_dma_map(cldev, HBM_DMA_BUF_ID_WLAN, + IWL_MEI_SAP_SHARED_MEM_SZ); + + if (IS_ERR(mem->ctrl)) { + int ret = PTR_ERR(mem->ctrl); + + mem->ctrl = NULL; + + return ret; + } + + memset(mem->ctrl, 0, IWL_MEI_SAP_SHARED_MEM_SZ); + + return 0; +} + +static void iwl_mei_init_shared_mem(struct iwl_mei *mei) +{ + struct iwl_mei_shared_mem_ptrs *mem = &mei->shared_mem; + struct iwl_sap_dir *h2m; + struct iwl_sap_dir *m2h; + int dir, queue; + u8 *q_head; + + mem->ctrl->sap_id = cpu_to_le32(SAP_CONTROL_BLOCK_ID); + + mem->ctrl->size = cpu_to_le32(sizeof(*mem->ctrl)); + + h2m = &mem->ctrl->dir[SAP_DIRECTION_HOST_TO_ME]; + m2h = &mem->ctrl->dir[SAP_DIRECTION_ME_TO_HOST]; + + h2m->q_ctrl_blk[SAP_QUEUE_IDX_DATA].size = + cpu_to_le32(SAP_H2M_DATA_Q_SZ); + h2m->q_ctrl_blk[SAP_QUEUE_IDX_NOTIF].size = + cpu_to_le32(SAP_H2M_NOTIF_Q_SZ); + m2h->q_ctrl_blk[SAP_QUEUE_IDX_DATA].size = + cpu_to_le32(SAP_M2H_DATA_Q_SZ); + m2h->q_ctrl_blk[SAP_QUEUE_IDX_NOTIF].size = + cpu_to_le32(SAP_M2H_NOTIF_Q_SZ); + + /* q_head points to the start of the first queue */ + q_head = (void *)(mem->ctrl + 1); + + /* Initialize the queue heads */ + for (dir = 0; dir < SAP_DIRECTION_MAX; dir++) { + for (queue = 0; queue < SAP_QUEUE_IDX_MAX; queue++) { + mem->q_head[dir][queue] = q_head; + q_head += + le32_to_cpu(mem->ctrl->dir[dir].q_ctrl_blk[queue].size); + mem->q_size[dir][queue] = + le32_to_cpu(mem->ctrl->dir[dir].q_ctrl_blk[queue].size); + } + } + + *(__le32 *)q_head = cpu_to_le32(SAP_CONTROL_BLOCK_ID); +} + +static ssize_t iwl_mei_write_cyclic_buf(struct mei_cl_device *cldev, + struct iwl_sap_q_ctrl_blk *notif_q, + u8 *q_head, + const struct iwl_sap_hdr *hdr, + u32 q_sz) +{ + u32 rd = le32_to_cpu(READ_ONCE(notif_q->rd_ptr)); + u32 wr = le32_to_cpu(READ_ONCE(notif_q->wr_ptr)); + size_t room_in_buf; + size_t tx_sz = sizeof(*hdr) + le16_to_cpu(hdr->len); + + if (rd > q_sz || wr > q_sz) { + dev_err(&cldev->dev, + "Pointers are past the end of the buffer\n"); + return -EINVAL; + } + + room_in_buf = wr >= rd ? q_sz - wr + rd : rd - wr; + + /* we don't have enough room for the data to write */ + if (room_in_buf < tx_sz) { + dev_err(&cldev->dev, + "Not enough room in the buffer\n"); + return -ENOSPC; + } + + if (wr + tx_sz <= q_sz) { + memcpy(q_head + wr, hdr, tx_sz); + } else { + memcpy(q_head + wr, hdr, q_sz - wr); + memcpy(q_head, (const u8 *)hdr + q_sz - wr, tx_sz - (q_sz - wr)); + } + + WRITE_ONCE(notif_q->wr_ptr, cpu_to_le32((wr + tx_sz) % q_sz)); + return 0; +} + +static bool iwl_mei_host_to_me_data_pending(const struct iwl_mei *mei) +{ + struct iwl_sap_q_ctrl_blk *notif_q; + struct iwl_sap_dir *dir; + + dir = &mei->shared_mem.ctrl->dir[SAP_DIRECTION_HOST_TO_ME]; + notif_q = &dir->q_ctrl_blk[SAP_QUEUE_IDX_DATA]; + + if (READ_ONCE(notif_q->wr_ptr) != READ_ONCE(notif_q->rd_ptr)) + return true; + + notif_q = &dir->q_ctrl_blk[SAP_QUEUE_IDX_NOTIF]; + return READ_ONCE(notif_q->wr_ptr) != READ_ONCE(notif_q->rd_ptr); +} + +static int iwl_mei_send_check_shared_area(struct mei_cl_device *cldev) +{ + struct iwl_mei *mei = mei_cldev_get_drvdata(cldev); + struct iwl_sap_me_msg_start msg = { + .hdr.type = cpu_to_le32(SAP_ME_MSG_CHECK_SHARED_AREA), + .hdr.seq_num = cpu_to_le32(atomic_inc_return(&mei->seq_no)), + }; + int ret; + + lockdep_assert_held(&iwl_mei_mutex); + + if (mei->csa_throttled) + return 0; + + trace_iwlmei_me_msg(&msg.hdr, true); + ret = mei_cldev_send(cldev, (void *)&msg, sizeof(msg)); + if (ret != sizeof(msg)) { + dev_err(&cldev->dev, + "failed to send the SAP_ME_MSG_CHECK_SHARED_AREA message %d\n", + ret); + return ret; + } + + mei->csa_throttled = true; + + schedule_delayed_work(&mei->csa_throttle_end_wk, + msecs_to_jiffies(100)); + + return 0; +} + +static void iwl_mei_csa_throttle_end_wk(struct work_struct *wk) +{ + struct iwl_mei *mei = + container_of(wk, struct iwl_mei, csa_throttle_end_wk.work); + + mutex_lock(&iwl_mei_mutex); + + mei->csa_throttled = false; + + if (iwl_mei_host_to_me_data_pending(mei)) + iwl_mei_send_check_shared_area(mei->cldev); + + mutex_unlock(&iwl_mei_mutex); +} + +static int iwl_mei_send_sap_msg_payload(struct mei_cl_device *cldev, + struct iwl_sap_hdr *hdr) +{ + struct iwl_mei *mei = mei_cldev_get_drvdata(cldev); + struct iwl_sap_q_ctrl_blk *notif_q; + struct iwl_sap_dir *dir; + void *q_head; + u32 q_sz; + int ret; + + lockdep_assert_held(&iwl_mei_mutex); + + if (!mei->shared_mem.ctrl) { + dev_err(&cldev->dev, + "No shared memory, can't send any SAP message\n"); + return -EINVAL; + } + + if (!iwl_mei_is_connected()) { + dev_err(&cldev->dev, + "Can't send a SAP message if we're not connected\n"); + return -ENODEV; + } + + hdr->seq_num = cpu_to_le32(atomic_inc_return(&mei->sap_seq_no)); + dev_dbg(&cldev->dev, "Sending %d\n", hdr->type); + + dir = &mei->shared_mem.ctrl->dir[SAP_DIRECTION_HOST_TO_ME]; + notif_q = &dir->q_ctrl_blk[SAP_QUEUE_IDX_NOTIF]; + q_head = mei->shared_mem.q_head[SAP_DIRECTION_HOST_TO_ME][SAP_QUEUE_IDX_NOTIF]; + q_sz = mei->shared_mem.q_size[SAP_DIRECTION_HOST_TO_ME][SAP_QUEUE_IDX_NOTIF]; + ret = iwl_mei_write_cyclic_buf(q_head, notif_q, q_head, hdr, q_sz); + + if (ret < 0) + return ret; + + trace_iwlmei_sap_cmd(hdr, true); + + return iwl_mei_send_check_shared_area(cldev); +} + +void iwl_mei_add_data_to_ring(struct sk_buff *skb, bool cb_tx) +{ + struct iwl_sap_q_ctrl_blk *notif_q; + struct iwl_sap_dir *dir; + struct iwl_mei *mei; + size_t room_in_buf; + size_t tx_sz; + size_t hdr_sz; + u32 q_sz; + u32 rd; + u32 wr; + u8 *q_head; + + if (!iwl_mei_global_cldev) + return; + + mei = mei_cldev_get_drvdata(iwl_mei_global_cldev); + + /* + * We access this path for Rx packets (the more common case) + * and from Tx path when we send DHCP packets, the latter is + * very unlikely. + * Take the lock already here to make sure we see that remove() + * might have cleared the IWL_MEI_STATUS_SAP_CONNECTED bit. + */ + spin_lock_bh(&mei->data_q_lock); + + if (!iwl_mei_is_connected()) { + spin_unlock_bh(&mei->data_q_lock); + return; + } + + /* + * We are in a RCU critical section and the remove from the CSME bus + * which would free this memory waits for the readers to complete (this + * is done in netdev_rx_handler_unregister). + */ + dir = &mei->shared_mem.ctrl->dir[SAP_DIRECTION_HOST_TO_ME]; + notif_q = &dir->q_ctrl_blk[SAP_QUEUE_IDX_DATA]; + q_head = mei->shared_mem.q_head[SAP_DIRECTION_HOST_TO_ME][SAP_QUEUE_IDX_DATA]; + q_sz = mei->shared_mem.q_size[SAP_DIRECTION_HOST_TO_ME][SAP_QUEUE_IDX_DATA]; + + rd = le32_to_cpu(READ_ONCE(notif_q->rd_ptr)); + wr = le32_to_cpu(READ_ONCE(notif_q->wr_ptr)); + hdr_sz = cb_tx ? sizeof(struct iwl_sap_cb_data) : + sizeof(struct iwl_sap_hdr); + tx_sz = skb->len + hdr_sz; + + if (rd > q_sz || wr > q_sz) { + dev_err(&mei->cldev->dev, + "can't write the data: pointers are past the end of the buffer\n"); + goto out; + } + + room_in_buf = wr >= rd ? q_sz - wr + rd : rd - wr; + + /* we don't have enough room for the data to write */ + if (room_in_buf < tx_sz) { + dev_err(&mei->cldev->dev, + "Not enough room in the buffer for this data\n"); + goto out; + } + + if (skb_headroom(skb) < hdr_sz) { + dev_err(&mei->cldev->dev, + "Not enough headroom in the skb to write the SAP header\n"); + goto out; + } + + if (cb_tx) { + struct iwl_sap_cb_data *cb_hdr = skb_push(skb, sizeof(*cb_hdr)); + + memset(cb_hdr, 0, sizeof(*cb_hdr)); + cb_hdr->hdr.type = cpu_to_le16(SAP_MSG_CB_DATA_PACKET); + cb_hdr->hdr.len = cpu_to_le16(skb->len - sizeof(cb_hdr->hdr)); + cb_hdr->hdr.seq_num = cpu_to_le32(atomic_inc_return(&mei->sap_seq_no)); + cb_hdr->to_me_filt_status = cpu_to_le32(BIT(CB_TX_DHCP_FILT_IDX)); + cb_hdr->data_len = cpu_to_le32(skb->len - sizeof(*cb_hdr)); + trace_iwlmei_sap_data(skb, IWL_SAP_TX_DHCP); + } else { + struct iwl_sap_hdr *hdr = skb_push(skb, sizeof(*hdr)); + + hdr->type = cpu_to_le16(SAP_MSG_DATA_PACKET); + hdr->len = cpu_to_le16(skb->len - sizeof(*hdr)); + hdr->seq_num = cpu_to_le32(atomic_inc_return(&mei->sap_seq_no)); + trace_iwlmei_sap_data(skb, IWL_SAP_TX_DATA_FROM_AIR); + } + + if (wr + tx_sz <= q_sz) { + skb_copy_bits(skb, 0, q_head + wr, tx_sz); + } else { + skb_copy_bits(skb, 0, q_head + wr, q_sz - wr); + skb_copy_bits(skb, q_sz - wr, q_head, tx_sz - (q_sz - wr)); + } + + WRITE_ONCE(notif_q->wr_ptr, cpu_to_le32((wr + tx_sz) % q_sz)); + +out: + spin_unlock_bh(&mei->data_q_lock); +} + +static int +iwl_mei_send_sap_msg(struct mei_cl_device *cldev, u16 type) +{ + struct iwl_sap_hdr msg = { + .type = cpu_to_le16(type), + }; + + return iwl_mei_send_sap_msg_payload(cldev, &msg); +} + +static void iwl_mei_send_csa_msg_wk(struct work_struct *wk) +{ + struct iwl_mei *mei = + container_of(wk, struct iwl_mei, send_csa_msg_wk); + + if (!iwl_mei_is_connected()) + return; + + mutex_lock(&iwl_mei_mutex); + + iwl_mei_send_check_shared_area(mei->cldev); + + mutex_unlock(&iwl_mei_mutex); +} + +/* Called in a RCU read critical section from netif_receive_skb */ +static rx_handler_result_t iwl_mei_rx_handler(struct sk_buff **pskb) +{ + struct sk_buff *skb = *pskb; + struct iwl_mei *mei = + rcu_dereference(skb->dev->rx_handler_data); + struct iwl_mei_filters *filters = rcu_dereference(mei->filters); + bool rx_for_csme = false; + rx_handler_result_t res; + + /* + * remove() unregisters this handler and synchronize_net, so this + * should never happen. + */ + if (!iwl_mei_is_connected()) { + dev_err(&mei->cldev->dev, + "Got an Rx packet, but we're not connected to SAP?\n"); + return RX_HANDLER_PASS; + } + + if (filters) + res = iwl_mei_rx_filter(skb, &filters->filters, &rx_for_csme); + else + res = RX_HANDLER_PASS; + + /* + * The data is already on the ring of the shared area, all we + * need to do is to tell the CSME firmware to check what we have + * there. + */ + if (rx_for_csme) + schedule_work(&mei->send_csa_msg_wk); + + if (res != RX_HANDLER_PASS) { + trace_iwlmei_sap_data(skb, IWL_SAP_RX_DATA_DROPPED_FROM_AIR); + dev_kfree_skb(skb); + } + + return res; +} + +static void iwl_mei_netdev_work(struct work_struct *wk) +{ + struct iwl_mei *mei = + container_of(wk, struct iwl_mei, netdev_work); + struct net_device *netdev; + + /* + * First take rtnl and only then the mutex to avoid an ABBA + * with iwl_mei_set_netdev() + */ + rtnl_lock(); + mutex_lock(&iwl_mei_mutex); + + netdev = rcu_dereference_protected(iwl_mei_cache.netdev, + lockdep_is_held(&iwl_mei_mutex)); + if (netdev) { + if (mei->amt_enabled) + netdev_rx_handler_register(netdev, iwl_mei_rx_handler, + mei); + else + netdev_rx_handler_unregister(netdev); + } + + mutex_unlock(&iwl_mei_mutex); + rtnl_unlock(); +} + +static void +iwl_mei_handle_rx_start_ok(struct mei_cl_device *cldev, + const struct iwl_sap_me_msg_start_ok *rsp, + ssize_t len) +{ + if (len != sizeof(*rsp)) { + dev_err(&cldev->dev, + "got invalid SAP_ME_MSG_START_OK from CSME firmware\n"); + dev_err(&cldev->dev, + "size is incorrect: %zd instead of %zu\n", + len, sizeof(*rsp)); + return; + } + + if (rsp->supported_version != SAP_VERSION) { + dev_err(&cldev->dev, + "didn't get the expected version: got %d\n", + rsp->supported_version); + return; + } + + mutex_lock(&iwl_mei_mutex); + set_bit(IWL_MEI_STATUS_SAP_CONNECTED, &iwl_mei_status); + /* + * We'll receive AMT_STATE SAP message in a bit and + * that will continue the flow + */ + mutex_unlock(&iwl_mei_mutex); +} + +static void iwl_mei_handle_csme_filters(struct mei_cl_device *cldev, + const struct iwl_sap_csme_filters *filters) +{ + struct iwl_mei *mei = mei_cldev_get_drvdata(iwl_mei_global_cldev); + struct iwl_mei_filters *new_filters; + struct iwl_mei_filters *old_filters; + + old_filters = + rcu_dereference_protected(mei->filters, + lockdep_is_held(&iwl_mei_mutex)); + + new_filters = kzalloc(sizeof(*new_filters), GFP_KERNEL); + if (!new_filters) + return; + + /* Copy the OOB filters */ + new_filters->filters = filters->filters; + + rcu_assign_pointer(mei->filters, new_filters); + + if (old_filters) + kfree_rcu(old_filters, rcu_head); +} + +static void +iwl_mei_handle_conn_status(struct mei_cl_device *cldev, + const struct iwl_sap_notif_conn_status *status) +{ + struct iwl_mei *mei = mei_cldev_get_drvdata(cldev); + struct iwl_mei_conn_info conn_info = { + .lp_state = le32_to_cpu(status->link_prot_state), + .ssid_len = le32_to_cpu(status->conn_info.ssid_len), + .channel = status->conn_info.channel, + .band = status->conn_info.band, + .auth_mode = le32_to_cpu(status->conn_info.auth_mode), + .pairwise_cipher = le32_to_cpu(status->conn_info.pairwise_cipher), + }; + + if (!iwl_mei_cache.ops || + conn_info.ssid_len > ARRAY_SIZE(conn_info.ssid)) + return; + + memcpy(conn_info.ssid, status->conn_info.ssid, conn_info.ssid_len); + ether_addr_copy(conn_info.bssid, status->conn_info.bssid); + + iwl_mei_cache.ops->me_conn_status(iwl_mei_cache.priv, &conn_info); + + mei->link_prot_state = status->link_prot_state; + + /* + * Update the Rfkill state in case the host does not own the device: + * if we are in Link Protection, ask to not touch the device, else, + * unblock rfkill. + * If the host owns the device, inform the user space whether it can + * roam. + */ + if (mei->got_ownership) + iwl_mei_cache.ops->roaming_forbidden(iwl_mei_cache.priv, + status->link_prot_state); + else + iwl_mei_cache.ops->rfkill(iwl_mei_cache.priv, + status->link_prot_state); +} + +static void iwl_mei_set_init_conf(struct iwl_mei *mei) +{ + struct iwl_sap_notif_host_link_up link_msg = { + .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_HOST_LINK_UP), + .hdr.len = cpu_to_le16(sizeof(link_msg) - sizeof(link_msg.hdr)), + }; + struct iwl_sap_notif_country_code mcc_msg = { + .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_COUNTRY_CODE), + .hdr.len = cpu_to_le16(sizeof(mcc_msg) - sizeof(mcc_msg.hdr)), + .mcc = cpu_to_le16(iwl_mei_cache.mcc), + }; + struct iwl_sap_notif_sar_limits sar_msg = { + .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_SAR_LIMITS), + .hdr.len = cpu_to_le16(sizeof(sar_msg) - sizeof(sar_msg.hdr)), + }; + struct iwl_sap_notif_host_nic_info nic_info_msg = { + .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_NIC_INFO), + .hdr.len = cpu_to_le16(sizeof(nic_info_msg) - sizeof(nic_info_msg.hdr)), + }; + struct iwl_sap_msg_dw rfkill_msg = { + .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_RADIO_STATE), + .hdr.len = cpu_to_le16(sizeof(rfkill_msg) - sizeof(rfkill_msg.hdr)), + .val = cpu_to_le32(iwl_mei_cache.rf_kill), + }; + + /* wifi driver has registered already */ + if (iwl_mei_cache.ops) { + iwl_mei_send_sap_msg(mei->cldev, + SAP_MSG_NOTIF_WIFIDR_UP); + iwl_mei_cache.ops->sap_connected(iwl_mei_cache.priv); + } + + iwl_mei_send_sap_msg(mei->cldev, SAP_MSG_NOTIF_WHO_OWNS_NIC); + + if (iwl_mei_cache.conn_info) { + link_msg.conn_info = *iwl_mei_cache.conn_info; + iwl_mei_send_sap_msg_payload(mei->cldev, &link_msg.hdr); + } + + iwl_mei_send_sap_msg_payload(mei->cldev, &mcc_msg.hdr); + + if (iwl_mei_cache.power_limit) { + memcpy(sar_msg.sar_chain_info_table, iwl_mei_cache.power_limit, + sizeof(sar_msg.sar_chain_info_table)); + iwl_mei_send_sap_msg_payload(mei->cldev, &sar_msg.hdr); + } + + ether_addr_copy(nic_info_msg.mac_address, iwl_mei_cache.mac_address); + ether_addr_copy(nic_info_msg.nvm_address, iwl_mei_cache.nvm_address); + iwl_mei_send_sap_msg_payload(mei->cldev, &nic_info_msg.hdr); + + iwl_mei_send_sap_msg_payload(mei->cldev, &rfkill_msg.hdr); +} + +static void iwl_mei_handle_amt_state(struct mei_cl_device *cldev, + const struct iwl_sap_msg_dw *dw) +{ + struct iwl_mei *mei = mei_cldev_get_drvdata(cldev); + + mutex_lock(&iwl_mei_mutex); + + if (mei->amt_enabled == !!le32_to_cpu(dw->val)) + goto out; + + mei->amt_enabled = dw->val; + + if (mei->amt_enabled) + iwl_mei_set_init_conf(mei); + else if (iwl_mei_cache.ops) + iwl_mei_cache.ops->rfkill(iwl_mei_cache.priv, false); + + schedule_work(&mei->netdev_work); + +out: + mutex_unlock(&iwl_mei_mutex); +} + +static void iwl_mei_handle_nic_owner(struct mei_cl_device *cldev, + const struct iwl_sap_msg_dw *dw) +{ + struct iwl_mei *mei = mei_cldev_get_drvdata(cldev); + + mei->got_ownership = dw->val != cpu_to_le32(SAP_NIC_OWNER_ME); +} + +static void iwl_mei_handle_can_release_ownership(struct mei_cl_device *cldev, + const void *payload) +{ + /* We can get ownership and driver is registered, go ahead */ + if (iwl_mei_cache.ops) + iwl_mei_send_sap_msg(cldev, + SAP_MSG_NOTIF_HOST_ASKS_FOR_NIC_OWNERSHIP); +} + +static void iwl_mei_handle_csme_taking_ownership(struct mei_cl_device *cldev, + const void *payload) +{ + struct iwl_mei *mei = mei_cldev_get_drvdata(cldev); + + dev_info(&cldev->dev, "CSME takes ownership\n"); + + mei->got_ownership = false; + + if (iwl_mei_cache.ops && !mei->device_down) { + /* + * Remember to send CSME_OWNERSHIP_CONFIRMED when the wifi + * driver is finished taking the device down. + */ + mei->csme_taking_ownership = true; + + iwl_mei_cache.ops->rfkill(iwl_mei_cache.priv, true); + } else { + iwl_mei_send_sap_msg(cldev, + SAP_MSG_NOTIF_CSME_OWNERSHIP_CONFIRMED); + } +} + +static void iwl_mei_handle_nvm(struct mei_cl_device *cldev, + const struct iwl_sap_nvm *sap_nvm) +{ + struct iwl_mei *mei = mei_cldev_get_drvdata(cldev); + const struct iwl_mei_nvm *mei_nvm = (const void *)sap_nvm; + int i; + + kfree(mei->nvm); + mei->nvm = kzalloc(sizeof(*mei_nvm), GFP_KERNEL); + if (!mei->nvm) + return; + + ether_addr_copy(mei->nvm->hw_addr, sap_nvm->hw_addr); + mei->nvm->n_hw_addrs = sap_nvm->n_hw_addrs; + mei->nvm->radio_cfg = le32_to_cpu(sap_nvm->radio_cfg); + mei->nvm->caps = le32_to_cpu(sap_nvm->caps); + mei->nvm->nvm_version = le32_to_cpu(sap_nvm->nvm_version); + + for (i = 0; i < ARRAY_SIZE(mei->nvm->channels); i++) + mei->nvm->channels[i] = le32_to_cpu(sap_nvm->channels[i]); + + wake_up_all(&mei->get_nvm_wq); +} + +static void iwl_mei_handle_rx_host_own_req(struct mei_cl_device *cldev, + const struct iwl_sap_msg_dw *dw) +{ + struct iwl_mei *mei = mei_cldev_get_drvdata(cldev); + + /* + * This means that we can't use the wifi device right now, CSME is not + * ready to let us use it. + */ + if (!dw->val) { + dev_info(&cldev->dev, "Ownership req denied\n"); + return; + } + + mei->got_ownership = true; + wake_up_all(&mei->get_ownership_wq); + + iwl_mei_send_sap_msg(cldev, + SAP_MSG_NOTIF_HOST_OWNERSHIP_CONFIRMED); + + /* We can now start the connection, unblock rfkill */ + if (iwl_mei_cache.ops) + iwl_mei_cache.ops->rfkill(iwl_mei_cache.priv, false); +} + +static void iwl_mei_handle_ping(struct mei_cl_device *cldev, + const struct iwl_sap_hdr *hdr) +{ + iwl_mei_send_sap_msg(cldev, SAP_MSG_NOTIF_PONG); +} + +static void iwl_mei_handle_sap_msg(struct mei_cl_device *cldev, + const struct iwl_sap_hdr *hdr) +{ + u16 len = le16_to_cpu(hdr->len) + sizeof(*hdr); + u16 type = le16_to_cpu(hdr->type); + + dev_dbg(&cldev->dev, + "Got a new SAP message: type %d, len %d, seq %d\n", + le16_to_cpu(hdr->type), len, + le32_to_cpu(hdr->seq_num)); + +#define SAP_MSG_HANDLER(_cmd, _handler, _sz) \ + case SAP_MSG_NOTIF_ ## _cmd: \ + if (len < _sz) { \ + dev_err(&cldev->dev, \ + "Bad size for %d: %u < %u\n", \ + le16_to_cpu(hdr->type), \ + (unsigned int)len, \ + (unsigned int)_sz); \ + break; \ + } \ + mutex_lock(&iwl_mei_mutex); \ + _handler(cldev, (const void *)hdr); \ + mutex_unlock(&iwl_mei_mutex); \ + break + +#define SAP_MSG_HANDLER_NO_LOCK(_cmd, _handler, _sz) \ + case SAP_MSG_NOTIF_ ## _cmd: \ + if (len < _sz) { \ + dev_err(&cldev->dev, \ + "Bad size for %d: %u < %u\n", \ + le16_to_cpu(hdr->type), \ + (unsigned int)len, \ + (unsigned int)_sz); \ + break; \ + } \ + _handler(cldev, (const void *)hdr); \ + break + +#define SAP_MSG_HANDLER_NO_HANDLER(_cmd, _sz) \ + case SAP_MSG_NOTIF_ ## _cmd: \ + if (len < _sz) { \ + dev_err(&cldev->dev, \ + "Bad size for %d: %u < %u\n", \ + le16_to_cpu(hdr->type), \ + (unsigned int)len, \ + (unsigned int)_sz); \ + break; \ + } \ + break + + switch (type) { + SAP_MSG_HANDLER(PING, iwl_mei_handle_ping, 0); + SAP_MSG_HANDLER(CSME_FILTERS, + iwl_mei_handle_csme_filters, + sizeof(struct iwl_sap_csme_filters)); + SAP_MSG_HANDLER(CSME_CONN_STATUS, + iwl_mei_handle_conn_status, + sizeof(struct iwl_sap_notif_conn_status)); + SAP_MSG_HANDLER_NO_LOCK(AMT_STATE, + iwl_mei_handle_amt_state, + sizeof(struct iwl_sap_msg_dw)); + SAP_MSG_HANDLER_NO_HANDLER(PONG, 0); + SAP_MSG_HANDLER(NVM, iwl_mei_handle_nvm, + sizeof(struct iwl_sap_nvm)); + SAP_MSG_HANDLER(CSME_REPLY_TO_HOST_OWNERSHIP_REQ, + iwl_mei_handle_rx_host_own_req, + sizeof(struct iwl_sap_msg_dw)); + SAP_MSG_HANDLER(NIC_OWNER, iwl_mei_handle_nic_owner, + sizeof(struct iwl_sap_msg_dw)); + SAP_MSG_HANDLER(CSME_CAN_RELEASE_OWNERSHIP, + iwl_mei_handle_can_release_ownership, 0); + SAP_MSG_HANDLER(CSME_TAKING_OWNERSHIP, + iwl_mei_handle_csme_taking_ownership, 0); + default: + /* + * This is not really an error, there are message that we decided + * to ignore, yet, it is useful to be able to leave a note if debug + * is enabled. + */ + dev_dbg(&cldev->dev, "Unsupported message: type %d, len %d\n", + le16_to_cpu(hdr->type), len); + } + +#undef SAP_MSG_HANDLER +#undef SAP_MSG_HANDLER_NO_LOCK +} + +static void iwl_mei_read_from_q(const u8 *q_head, u32 q_sz, + u32 *_rd, u32 wr, + void *_buf, u32 len) +{ + u8 *buf = _buf; + u32 rd = *_rd; + + if (rd + len <= q_sz) { + memcpy(buf, q_head + rd, len); + rd += len; + } else { + memcpy(buf, q_head + rd, q_sz - rd); + memcpy(buf + q_sz - rd, q_head, len - (q_sz - rd)); + rd = len - (q_sz - rd); + } + + *_rd = rd; +} + +#define QOS_HDR_IV_SNAP_LEN (sizeof(struct ieee80211_qos_hdr) + \ + IEEE80211_TKIP_IV_LEN + \ + sizeof(rfc1042_header) + ETH_TLEN) + +static void iwl_mei_handle_sap_data(struct mei_cl_device *cldev, + const u8 *q_head, u32 q_sz, + u32 rd, u32 wr, ssize_t valid_rx_sz, + struct sk_buff_head *tx_skbs) +{ + struct iwl_sap_hdr hdr; + struct net_device *netdev = + rcu_dereference_protected(iwl_mei_cache.netdev, + lockdep_is_held(&iwl_mei_mutex)); + + if (!netdev) + return; + + while (valid_rx_sz >= sizeof(hdr)) { + struct ethhdr *ethhdr; + unsigned char *data; + struct sk_buff *skb; + u16 len; + + iwl_mei_read_from_q(q_head, q_sz, &rd, wr, &hdr, sizeof(hdr)); + valid_rx_sz -= sizeof(hdr); + len = le16_to_cpu(hdr.len); + + if (valid_rx_sz < len) { + dev_err(&cldev->dev, + "Data queue is corrupted: valid data len %zd, len %d\n", + valid_rx_sz, len); + break; + } + + if (len < sizeof(*ethhdr)) { + dev_err(&cldev->dev, + "Data len is smaller than an ethernet header? len = %d\n", + len); + } + + valid_rx_sz -= len; + + if (le16_to_cpu(hdr.type) != SAP_MSG_DATA_PACKET) { + dev_err(&cldev->dev, "Unsupported Rx data: type %d, len %d\n", + le16_to_cpu(hdr.type), len); + continue; + } + + /* We need enough room for the WiFi header + SNAP + IV */ + skb = netdev_alloc_skb(netdev, len + QOS_HDR_IV_SNAP_LEN); + if (!skb) + continue; + + skb_reserve(skb, QOS_HDR_IV_SNAP_LEN); + ethhdr = skb_push(skb, sizeof(*ethhdr)); + + iwl_mei_read_from_q(q_head, q_sz, &rd, wr, + ethhdr, sizeof(*ethhdr)); + len -= sizeof(*ethhdr); + + skb_reset_mac_header(skb); + skb_reset_network_header(skb); + skb->protocol = ethhdr->h_proto; + + data = skb_put(skb, len); + iwl_mei_read_from_q(q_head, q_sz, &rd, wr, data, len); + + /* + * Enqueue the skb here so that it can be sent later when we + * do not hold the mutex. TX'ing a packet with a mutex held is + * possible, but it wouldn't be nice to forbid the TX path to + * call any of iwlmei's functions, since every API from iwlmei + * needs the mutex. + */ + __skb_queue_tail(tx_skbs, skb); + } +} + +static void iwl_mei_handle_sap_rx_cmd(struct mei_cl_device *cldev, + const u8 *q_head, u32 q_sz, + u32 rd, u32 wr, ssize_t valid_rx_sz) +{ + struct page *p = alloc_page(GFP_KERNEL); + struct iwl_sap_hdr *hdr; + + if (!p) + return; + + hdr = page_address(p); + + while (valid_rx_sz >= sizeof(*hdr)) { + u16 len; + + iwl_mei_read_from_q(q_head, q_sz, &rd, wr, hdr, sizeof(*hdr)); + valid_rx_sz -= sizeof(*hdr); + len = le16_to_cpu(hdr->len); + + if (valid_rx_sz < len) + break; + + iwl_mei_read_from_q(q_head, q_sz, &rd, wr, hdr + 1, len); + + trace_iwlmei_sap_cmd(hdr, false); + iwl_mei_handle_sap_msg(cldev, hdr); + valid_rx_sz -= len; + } + + /* valid_rx_sz must be 0 now... */ + if (valid_rx_sz) + dev_err(&cldev->dev, + "More data in the buffer although we read it all\n"); + + __free_page(p); +} + +static void iwl_mei_handle_sap_rx(struct mei_cl_device *cldev, + struct iwl_sap_q_ctrl_blk *notif_q, + const u8 *q_head, + struct sk_buff_head *skbs, + u32 q_sz) +{ + u32 rd = le32_to_cpu(READ_ONCE(notif_q->rd_ptr)); + u32 wr = le32_to_cpu(READ_ONCE(notif_q->wr_ptr)); + ssize_t valid_rx_sz; + + if (rd > q_sz || wr > q_sz) { + dev_err(&cldev->dev, + "Pointers are past the buffer limit\n"); + return; + } + + if (rd == wr) + return; + + valid_rx_sz = wr > rd ? wr - rd : q_sz - rd + wr; + + if (skbs) + iwl_mei_handle_sap_data(cldev, q_head, q_sz, rd, wr, + valid_rx_sz, skbs); + else + iwl_mei_handle_sap_rx_cmd(cldev, q_head, q_sz, rd, wr, + valid_rx_sz); + + /* Increment the read pointer to point to the write pointer */ + WRITE_ONCE(notif_q->rd_ptr, cpu_to_le32(wr)); +} + +static void iwl_mei_handle_check_shared_area(struct mei_cl_device *cldev) +{ + struct iwl_mei *mei = mei_cldev_get_drvdata(cldev); + struct iwl_sap_q_ctrl_blk *notif_q; + struct sk_buff_head tx_skbs; + struct iwl_sap_dir *dir; + void *q_head; + u32 q_sz; + + if (!mei->shared_mem.ctrl) + return; + + dir = &mei->shared_mem.ctrl->dir[SAP_DIRECTION_ME_TO_HOST]; + notif_q = &dir->q_ctrl_blk[SAP_QUEUE_IDX_NOTIF]; + q_head = mei->shared_mem.q_head[SAP_DIRECTION_ME_TO_HOST][SAP_QUEUE_IDX_NOTIF]; + q_sz = mei->shared_mem.q_size[SAP_DIRECTION_ME_TO_HOST][SAP_QUEUE_IDX_NOTIF]; + + /* + * Do not hold the mutex here, but rather each and every message + * handler takes it. + * This allows message handlers to take it at a certain time. + */ + iwl_mei_handle_sap_rx(cldev, notif_q, q_head, NULL, q_sz); + + mutex_lock(&iwl_mei_mutex); + dir = &mei->shared_mem.ctrl->dir[SAP_DIRECTION_ME_TO_HOST]; + notif_q = &dir->q_ctrl_blk[SAP_QUEUE_IDX_DATA]; + q_head = mei->shared_mem.q_head[SAP_DIRECTION_ME_TO_HOST][SAP_QUEUE_IDX_DATA]; + q_sz = mei->shared_mem.q_size[SAP_DIRECTION_ME_TO_HOST][SAP_QUEUE_IDX_DATA]; + + __skb_queue_head_init(&tx_skbs); + + iwl_mei_handle_sap_rx(cldev, notif_q, q_head, &tx_skbs, q_sz); + + if (skb_queue_empty(&tx_skbs)) { + mutex_unlock(&iwl_mei_mutex); + return; + } + + /* + * Take the RCU read lock before we unlock the mutex to make sure that + * even if the netdev is replaced by another non-NULL netdev right after + * we unlock the mutex, the old netdev will still be valid when we + * transmit the frames. We can't allow to replace the netdev here because + * the skbs hold a pointer to the netdev. + */ + rcu_read_lock(); + + mutex_unlock(&iwl_mei_mutex); + + if (!rcu_access_pointer(iwl_mei_cache.netdev)) { + dev_err(&cldev->dev, "Can't Tx without a netdev\n"); + skb_queue_purge(&tx_skbs); + goto out; + } + + while (!skb_queue_empty(&tx_skbs)) { + struct sk_buff *skb = __skb_dequeue(&tx_skbs); + + trace_iwlmei_sap_data(skb, IWL_SAP_RX_DATA_TO_AIR); + dev_queue_xmit(skb); + } + +out: + rcu_read_unlock(); +} + +static void iwl_mei_rx(struct mei_cl_device *cldev) +{ + struct iwl_sap_me_msg_hdr *hdr; + u8 msg[100]; + ssize_t ret; + + ret = mei_cldev_recv(cldev, (u8 *)&msg, sizeof(msg)); + if (ret < 0) { + dev_err(&cldev->dev, "failed to receive data: %zd\n", ret); + return; + } + + if (ret == 0) { + dev_err(&cldev->dev, "got an empty response\n"); + return; + } + + hdr = (void *)msg; + trace_iwlmei_me_msg(hdr, false); + + switch (le32_to_cpu(hdr->type)) { + case SAP_ME_MSG_START_OK: + BUILD_BUG_ON(sizeof(struct iwl_sap_me_msg_start_ok) > + sizeof(msg)); + + iwl_mei_handle_rx_start_ok(cldev, (void *)msg, ret); + break; + case SAP_ME_MSG_CHECK_SHARED_AREA: + iwl_mei_handle_check_shared_area(cldev); + break; + default: + dev_err(&cldev->dev, "got a RX notification: %d\n", + le32_to_cpu(hdr->type)); + break; + } +} + +static int iwl_mei_send_start(struct mei_cl_device *cldev) +{ + struct iwl_mei *mei = mei_cldev_get_drvdata(cldev); + struct iwl_sap_me_msg_start msg = { + .hdr.type = cpu_to_le32(SAP_ME_MSG_START), + .hdr.seq_num = cpu_to_le32(atomic_inc_return(&mei->seq_no)), + .hdr.len = cpu_to_le32(sizeof(msg)), + .supported_versions[0] = SAP_VERSION, + .init_data_seq_num = cpu_to_le16(0x100), + .init_notif_seq_num = cpu_to_le16(0x800), + }; + int ret; + + trace_iwlmei_me_msg(&msg.hdr, true); + ret = mei_cldev_send(cldev, (void *)&msg, sizeof(msg)); + if (ret != sizeof(msg)) { + dev_err(&cldev->dev, + "failed to send the SAP_ME_MSG_START message %d\n", + ret); + return ret; + } + + return 0; +} + +static int iwl_mei_enable(struct mei_cl_device *cldev) +{ + int ret; + + ret = mei_cldev_enable(cldev); + if (ret < 0) { + dev_err(&cldev->dev, "failed to enable the device: %d\n", ret); + return ret; + } + + ret = mei_cldev_register_rx_cb(cldev, iwl_mei_rx); + if (ret) { + dev_err(&cldev->dev, + "failed to register to the rx cb: %d\n", ret); + mei_cldev_disable(cldev); + return ret; + } + + return 0; +} + +struct iwl_mei_nvm *iwl_mei_get_nvm(void) +{ + struct iwl_mei_nvm *nvm = NULL; + struct iwl_mei *mei; + int ret; + + mutex_lock(&iwl_mei_mutex); + + if (!iwl_mei_is_connected()) + goto out; + + mei = mei_cldev_get_drvdata(iwl_mei_global_cldev); + + if (!mei) + goto out; + + ret = iwl_mei_send_sap_msg(iwl_mei_global_cldev, + SAP_MSG_NOTIF_GET_NVM); + if (ret) + goto out; + + mutex_unlock(&iwl_mei_mutex); + + ret = wait_event_timeout(mei->get_nvm_wq, mei->nvm, 2 * HZ); + if (!ret) + return NULL; + + mutex_lock(&iwl_mei_mutex); + + if (!iwl_mei_is_connected()) + goto out; + + mei = mei_cldev_get_drvdata(iwl_mei_global_cldev); + + if (!mei) + goto out; + + if (mei->nvm) + nvm = kmemdup(mei->nvm, sizeof(*mei->nvm), GFP_KERNEL); + +out: + mutex_unlock(&iwl_mei_mutex); + return nvm; +} +EXPORT_SYMBOL_GPL(iwl_mei_get_nvm); + +int iwl_mei_get_ownership(void) +{ + struct iwl_mei *mei; + int ret; + + mutex_lock(&iwl_mei_mutex); + + /* In case we didn't have a bind */ + if (!iwl_mei_is_connected()) { + ret = 0; + goto out; + } + + mei = mei_cldev_get_drvdata(iwl_mei_global_cldev); + + if (!mei) { + ret = -ENODEV; + goto out; + } + + if (!mei->amt_enabled) { + ret = 0; + goto out; + } + + if (mei->got_ownership) { + ret = 0; + goto out; + } + + ret = iwl_mei_send_sap_msg(mei->cldev, + SAP_MSG_NOTIF_HOST_ASKS_FOR_NIC_OWNERSHIP); + if (ret) + goto out; + + mutex_unlock(&iwl_mei_mutex); + + ret = wait_event_timeout(mei->get_ownership_wq, + mei->got_ownership, HZ / 2); + if (!ret) + return -ETIMEDOUT; + + mutex_lock(&iwl_mei_mutex); + + /* In case we didn't have a bind */ + if (!iwl_mei_is_connected()) { + ret = 0; + goto out; + } + + mei = mei_cldev_get_drvdata(iwl_mei_global_cldev); + + if (!mei) { + ret = -ENODEV; + goto out; + } + + ret = !mei->got_ownership; + +out: + mutex_unlock(&iwl_mei_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(iwl_mei_get_ownership); + +void iwl_mei_host_associated(const struct iwl_mei_conn_info *conn_info, + const struct iwl_mei_colloc_info *colloc_info) +{ + struct iwl_sap_notif_host_link_up msg = { + .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_HOST_LINK_UP), + .hdr.len = cpu_to_le16(sizeof(msg) - sizeof(msg.hdr)), + .conn_info = { + .ssid_len = cpu_to_le32(conn_info->ssid_len), + .channel = conn_info->channel, + .band = conn_info->band, + .pairwise_cipher = cpu_to_le32(conn_info->pairwise_cipher), + .auth_mode = cpu_to_le32(conn_info->auth_mode), + }, + }; + struct iwl_mei *mei; + + if (conn_info->ssid_len > ARRAY_SIZE(msg.conn_info.ssid)) + return; + + memcpy(msg.conn_info.ssid, conn_info->ssid, conn_info->ssid_len); + memcpy(msg.conn_info.bssid, conn_info->bssid, ETH_ALEN); + + if (colloc_info) { + msg.colloc_channel = colloc_info->channel; + msg.colloc_band = colloc_info->channel <= 14 ? 0 : 1; + memcpy(msg.colloc_bssid, colloc_info->bssid, ETH_ALEN); + } + + mutex_lock(&iwl_mei_mutex); + + if (!iwl_mei_is_connected()) + goto out; + + mei = mei_cldev_get_drvdata(iwl_mei_global_cldev); + + if (!mei && !mei->amt_enabled) + goto out; + + iwl_mei_send_sap_msg_payload(mei->cldev, &msg.hdr); + +out: + kfree(iwl_mei_cache.conn_info); + iwl_mei_cache.conn_info = + kmemdup(&msg.conn_info, sizeof(msg.conn_info), GFP_KERNEL); + mutex_unlock(&iwl_mei_mutex); +} +EXPORT_SYMBOL_GPL(iwl_mei_host_associated); + +void iwl_mei_host_disassociated(void) +{ + struct iwl_mei *mei; + struct iwl_sap_notif_host_link_down msg = { + .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_HOST_LINK_DOWN), + .hdr.len = cpu_to_le16(sizeof(msg) - sizeof(msg.hdr)), + .type = HOST_LINK_DOWN_TYPE_LONG, + }; + + mutex_lock(&iwl_mei_mutex); + + if (!iwl_mei_is_connected()) + goto out; + + mei = mei_cldev_get_drvdata(iwl_mei_global_cldev); + + if (!mei && !mei->amt_enabled) + goto out; + + iwl_mei_send_sap_msg_payload(mei->cldev, &msg.hdr); + +out: + kfree(iwl_mei_cache.conn_info); + iwl_mei_cache.conn_info = NULL; + mutex_unlock(&iwl_mei_mutex); +} +EXPORT_SYMBOL_GPL(iwl_mei_host_disassociated); + +void iwl_mei_set_rfkill_state(bool hw_rfkill, bool sw_rfkill) +{ + struct iwl_mei *mei; + u32 rfkill_state = 0; + struct iwl_sap_msg_dw msg = { + .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_RADIO_STATE), + .hdr.len = cpu_to_le16(sizeof(msg) - sizeof(msg.hdr)), + }; + + if (!sw_rfkill) + rfkill_state |= SAP_SW_RFKILL_DEASSERTED; + + if (!hw_rfkill) + rfkill_state |= SAP_HW_RFKILL_DEASSERTED; + + mutex_lock(&iwl_mei_mutex); + + if (!iwl_mei_is_connected()) + goto out; + + msg.val = cpu_to_le32(rfkill_state); + + mei = mei_cldev_get_drvdata(iwl_mei_global_cldev); + + if (!mei && !mei->amt_enabled) + goto out; + + iwl_mei_send_sap_msg_payload(mei->cldev, &msg.hdr); + +out: + iwl_mei_cache.rf_kill = rfkill_state; + mutex_unlock(&iwl_mei_mutex); +} +EXPORT_SYMBOL_GPL(iwl_mei_set_rfkill_state); + +void iwl_mei_set_nic_info(const u8 *mac_address, const u8 *nvm_address) +{ + struct iwl_mei *mei; + struct iwl_sap_notif_host_nic_info msg = { + .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_NIC_INFO), + .hdr.len = cpu_to_le16(sizeof(msg) - sizeof(msg.hdr)), + }; + + mutex_lock(&iwl_mei_mutex); + + if (!iwl_mei_is_connected()) + goto out; + + ether_addr_copy(msg.mac_address, mac_address); + ether_addr_copy(msg.nvm_address, nvm_address); + + mei = mei_cldev_get_drvdata(iwl_mei_global_cldev); + + if (!mei && !mei->amt_enabled) + goto out; + + iwl_mei_send_sap_msg_payload(mei->cldev, &msg.hdr); + +out: + ether_addr_copy(iwl_mei_cache.mac_address, mac_address); + ether_addr_copy(iwl_mei_cache.nvm_address, nvm_address); + mutex_unlock(&iwl_mei_mutex); +} +EXPORT_SYMBOL_GPL(iwl_mei_set_nic_info); + +void iwl_mei_set_country_code(u16 mcc) +{ + struct iwl_mei *mei; + struct iwl_sap_notif_country_code msg = { + .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_COUNTRY_CODE), + .hdr.len = cpu_to_le16(sizeof(msg) - sizeof(msg.hdr)), + .mcc = cpu_to_le16(mcc), + }; + + mutex_lock(&iwl_mei_mutex); + + if (!iwl_mei_is_connected()) + goto out; + + mei = mei_cldev_get_drvdata(iwl_mei_global_cldev); + + if (!mei && !mei->amt_enabled) + goto out; + + iwl_mei_send_sap_msg_payload(mei->cldev, &msg.hdr); + +out: + iwl_mei_cache.mcc = mcc; + mutex_unlock(&iwl_mei_mutex); +} +EXPORT_SYMBOL_GPL(iwl_mei_set_country_code); + +void iwl_mei_set_power_limit(const __le16 *power_limit) +{ + struct iwl_mei *mei; + struct iwl_sap_notif_sar_limits msg = { + .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_SAR_LIMITS), + .hdr.len = cpu_to_le16(sizeof(msg) - sizeof(msg.hdr)), + }; + + mutex_lock(&iwl_mei_mutex); + + if (!iwl_mei_is_connected()) + goto out; + + mei = mei_cldev_get_drvdata(iwl_mei_global_cldev); + + if (!mei && !mei->amt_enabled) + goto out; + + memcpy(msg.sar_chain_info_table, power_limit, sizeof(msg.sar_chain_info_table)); + + iwl_mei_send_sap_msg_payload(mei->cldev, &msg.hdr); + +out: + kfree(iwl_mei_cache.power_limit); + iwl_mei_cache.power_limit = kmemdup(power_limit, + sizeof(msg.sar_chain_info_table), GFP_KERNEL); + mutex_unlock(&iwl_mei_mutex); +} +EXPORT_SYMBOL_GPL(iwl_mei_set_power_limit); + +void iwl_mei_set_netdev(struct net_device *netdev) +{ + struct iwl_mei *mei; + + mutex_lock(&iwl_mei_mutex); + + if (!iwl_mei_is_connected()) { + rcu_assign_pointer(iwl_mei_cache.netdev, netdev); + goto out; + } + + mei = mei_cldev_get_drvdata(iwl_mei_global_cldev); + + if (!mei) + goto out; + + if (!netdev) { + struct net_device *dev = + rcu_dereference_protected(iwl_mei_cache.netdev, + lockdep_is_held(&iwl_mei_mutex)); + + if (!dev) + goto out; + + netdev_rx_handler_unregister(dev); + } + + rcu_assign_pointer(iwl_mei_cache.netdev, netdev); + + if (netdev && mei->amt_enabled) + netdev_rx_handler_register(netdev, iwl_mei_rx_handler, mei); + +out: + mutex_unlock(&iwl_mei_mutex); +} +EXPORT_SYMBOL_GPL(iwl_mei_set_netdev); + +void iwl_mei_device_state(bool up) +{ + struct iwl_mei *mei; + + mutex_lock(&iwl_mei_mutex); + + if (!iwl_mei_is_connected()) + goto out; + + mei = mei_cldev_get_drvdata(iwl_mei_global_cldev); + + if (!mei) + goto out; + + mei->device_down = !up; + + if (up || !mei->csme_taking_ownership) + goto out; + + iwl_mei_send_sap_msg(mei->cldev, + SAP_MSG_NOTIF_CSME_OWNERSHIP_CONFIRMED); + mei->csme_taking_ownership = false; +out: + mutex_unlock(&iwl_mei_mutex); +} +EXPORT_SYMBOL_GPL(iwl_mei_device_state); + +int iwl_mei_register(void *priv, const struct iwl_mei_ops *ops) +{ + int ret; + + /* + * We must have a non-NULL priv pointer to not crash when there are + * multiple WiFi devices. + */ + if (!priv) + return -EINVAL; + + mutex_lock(&iwl_mei_mutex); + + /* do not allow registration if someone else already registered */ + if (iwl_mei_cache.priv || iwl_mei_cache.ops) { + ret = -EBUSY; + goto out; + } + + iwl_mei_cache.priv = priv; + iwl_mei_cache.ops = ops; + + if (iwl_mei_global_cldev) { + struct iwl_mei *mei = + mei_cldev_get_drvdata(iwl_mei_global_cldev); + + /* we have already a SAP connection */ + if (iwl_mei_is_connected()) { + if (mei->amt_enabled) + iwl_mei_send_sap_msg(mei->cldev, + SAP_MSG_NOTIF_WIFIDR_UP); + ops->rfkill(priv, mei->link_prot_state); + } + } + ret = 0; + +out: + mutex_unlock(&iwl_mei_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(iwl_mei_register); + +void iwl_mei_start_unregister(void) +{ + mutex_lock(&iwl_mei_mutex); + + /* At this point, the wifi driver should have removed the netdev */ + if (rcu_access_pointer(iwl_mei_cache.netdev)) + pr_err("Still had a netdev pointer set upon unregister\n"); + + kfree(iwl_mei_cache.conn_info); + iwl_mei_cache.conn_info = NULL; + kfree(iwl_mei_cache.power_limit); + iwl_mei_cache.power_limit = NULL; + iwl_mei_cache.ops = NULL; + /* leave iwl_mei_cache.priv non-NULL to prevent any new registration */ + + mutex_unlock(&iwl_mei_mutex); +} +EXPORT_SYMBOL_GPL(iwl_mei_start_unregister); + +void iwl_mei_unregister_complete(void) +{ + mutex_lock(&iwl_mei_mutex); + + iwl_mei_cache.priv = NULL; + + if (iwl_mei_global_cldev) { + struct iwl_mei *mei = + mei_cldev_get_drvdata(iwl_mei_global_cldev); + + iwl_mei_send_sap_msg(mei->cldev, SAP_MSG_NOTIF_WIFIDR_DOWN); + mei->got_ownership = false; + } + + mutex_unlock(&iwl_mei_mutex); +} +EXPORT_SYMBOL_GPL(iwl_mei_unregister_complete); + +#if IS_ENABLED(CONFIG_DEBUG_FS) + +static ssize_t +iwl_mei_dbgfs_send_start_message_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + int ret; + + mutex_lock(&iwl_mei_mutex); + + if (!iwl_mei_global_cldev) { + ret = -ENODEV; + goto out; + } + + ret = iwl_mei_send_start(iwl_mei_global_cldev); + +out: + mutex_unlock(&iwl_mei_mutex); + return ret ?: count; +} + +static const struct file_operations iwl_mei_dbgfs_send_start_message_ops = { + .write = iwl_mei_dbgfs_send_start_message_write, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t iwl_mei_dbgfs_req_ownership_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + iwl_mei_get_ownership(); + + return count; +} + +static const struct file_operations iwl_mei_dbgfs_req_ownership_ops = { + .write = iwl_mei_dbgfs_req_ownership_write, + .open = simple_open, + .llseek = default_llseek, +}; + +static void iwl_mei_dbgfs_register(struct iwl_mei *mei) +{ + mei->dbgfs_dir = debugfs_create_dir(KBUILD_MODNAME, NULL); + + if (!mei->dbgfs_dir) + return; + + debugfs_create_ulong("status", S_IRUSR, + mei->dbgfs_dir, &iwl_mei_status); + debugfs_create_file("send_start_message", S_IWUSR, mei->dbgfs_dir, + mei, &iwl_mei_dbgfs_send_start_message_ops); + debugfs_create_file("req_ownership", S_IWUSR, mei->dbgfs_dir, + mei, &iwl_mei_dbgfs_req_ownership_ops); +} + +static void iwl_mei_dbgfs_unregister(struct iwl_mei *mei) +{ + debugfs_remove_recursive(mei->dbgfs_dir); + mei->dbgfs_dir = NULL; +} + +#else + +static void iwl_mei_dbgfs_register(struct iwl_mei *mei) {} +static void iwl_mei_dbgfs_unregister(struct iwl_mei *mei) {} + +#endif /* CONFIG_DEBUG_FS */ + +#define ALLOC_SHARED_MEM_RETRY_MAX_NUM 3 + +/* + * iwl_mei_probe - the probe function called by the mei bus enumeration + * + * This allocates the data needed by iwlmei and sets a pointer to this data + * into the mei_cl_device's drvdata. + * It starts the SAP protocol by sending the SAP_ME_MSG_START without + * waiting for the answer. The answer will be caught later by the Rx callback. + */ +static int iwl_mei_probe(struct mei_cl_device *cldev, + const struct mei_cl_device_id *id) +{ + int alloc_retry = ALLOC_SHARED_MEM_RETRY_MAX_NUM; + struct iwl_mei *mei; + int ret; + + mei = devm_kzalloc(&cldev->dev, sizeof(*mei), GFP_KERNEL); + if (!mei) + return -ENOMEM; + + init_waitqueue_head(&mei->get_nvm_wq); + INIT_WORK(&mei->send_csa_msg_wk, iwl_mei_send_csa_msg_wk); + INIT_DELAYED_WORK(&mei->csa_throttle_end_wk, + iwl_mei_csa_throttle_end_wk); + init_waitqueue_head(&mei->get_ownership_wq); + spin_lock_init(&mei->data_q_lock); + INIT_WORK(&mei->netdev_work, iwl_mei_netdev_work); + + mei_cldev_set_drvdata(cldev, mei); + mei->cldev = cldev; + mei->device_down = true; + + do { + ret = iwl_mei_alloc_shared_mem(cldev); + if (!ret) + break; + /* + * The CSME firmware needs to boot the internal WLAN client. + * This can take time in certain configurations (usually + * upon resume and when the whole CSME firmware is shut down + * during suspend). + * + * Wait a bit before retrying and hope we'll succeed next time. + */ + + dev_dbg(&cldev->dev, + "Couldn't allocate the shared memory: %d, attempt %d / %d\n", + ret, alloc_retry, ALLOC_SHARED_MEM_RETRY_MAX_NUM); + msleep(100); + alloc_retry--; + } while (alloc_retry); + + if (ret) { + dev_err(&cldev->dev, "Couldn't allocate the shared memory: %d\n", + ret); + goto free; + } + + iwl_mei_init_shared_mem(mei); + + ret = iwl_mei_enable(cldev); + if (ret) + goto free_shared_mem; + + iwl_mei_dbgfs_register(mei); + + /* + * We now have a Rx function in place, start the SAP procotol + * we expect to get the SAP_ME_MSG_START_OK response later on. + */ + mutex_lock(&iwl_mei_mutex); + ret = iwl_mei_send_start(cldev); + mutex_unlock(&iwl_mei_mutex); + if (ret) + goto debugfs_unregister; + + /* must be last */ + iwl_mei_global_cldev = cldev; + + return 0; + +debugfs_unregister: + iwl_mei_dbgfs_unregister(mei); + mei_cldev_disable(cldev); +free_shared_mem: + iwl_mei_free_shared_mem(cldev); +free: + mei_cldev_set_drvdata(cldev, NULL); + devm_kfree(&cldev->dev, mei); + + return ret; +} + +#define SEND_SAP_MAX_WAIT_ITERATION 10 + +static void iwl_mei_remove(struct mei_cl_device *cldev) +{ + struct iwl_mei *mei = mei_cldev_get_drvdata(cldev); + int i; + + /* + * We are being removed while the bus is active, it means we are + * going to suspend/ shutdown, so the NIC will disappear. + */ + if (mei_cldev_enabled(cldev) && iwl_mei_cache.ops) + iwl_mei_cache.ops->nic_stolen(iwl_mei_cache.priv); + + if (rcu_access_pointer(iwl_mei_cache.netdev)) { + struct net_device *dev; + + /* + * First take rtnl and only then the mutex to avoid an ABBA + * with iwl_mei_set_netdev() + */ + rtnl_lock(); + mutex_lock(&iwl_mei_mutex); + + /* + * If we are suspending and the wifi driver hasn't removed it's netdev + * yet, do it now. In any case, don't change the cache.netdev pointer. + */ + dev = rcu_dereference_protected(iwl_mei_cache.netdev, + lockdep_is_held(&iwl_mei_mutex)); + + netdev_rx_handler_unregister(dev); + mutex_unlock(&iwl_mei_mutex); + rtnl_unlock(); + } + + mutex_lock(&iwl_mei_mutex); + + if (mei->amt_enabled) { + /* + * Tell CSME that we are going down so that it won't access the + * memory anymore, make sure this message goes through immediately. + */ + mei->csa_throttled = false; + iwl_mei_send_sap_msg(mei->cldev, + SAP_MSG_NOTIF_HOST_GOES_DOWN); + + for (i = 0; i < SEND_SAP_MAX_WAIT_ITERATION; i++) { + if (!iwl_mei_host_to_me_data_pending(mei)) + break; + + msleep(20); + } + + /* + * If we couldn't make sure that CSME saw the HOST_GOES_DOWN + * message, it means that it will probably keep reading memory + * that we are going to unmap and free, expect IOMMU error + * messages. + */ + if (i == SEND_SAP_MAX_WAIT_ITERATION) + dev_err(&mei->cldev->dev, + "Couldn't get ACK from CSME on HOST_GOES_DOWN message\n"); + } + + mutex_unlock(&iwl_mei_mutex); + + /* + * This looks strange, but this lock is taken here to make sure that + * iwl_mei_add_data_to_ring called from the Tx path sees that we + * clear the IWL_MEI_STATUS_SAP_CONNECTED bit. + * Rx isn't a problem because the rx_handler can't be called after + * having been unregistered. + */ + spin_lock_bh(&mei->data_q_lock); + clear_bit(IWL_MEI_STATUS_SAP_CONNECTED, &iwl_mei_status); + spin_unlock_bh(&mei->data_q_lock); + + if (iwl_mei_cache.ops) + iwl_mei_cache.ops->rfkill(iwl_mei_cache.priv, false); + + /* + * mei_cldev_disable will return only after all the MEI Rx is done. + * It must be called when iwl_mei_mutex is *not* held, since it waits + * for our Rx handler to complete. + * After it returns, no new Rx will start. + */ + mei_cldev_disable(cldev); + + /* + * Since the netdev was already removed and the netdev's removal + * includes a call to synchronize_net() so that we know there won't be + * any new Rx that will trigger the following workers. + */ + cancel_work_sync(&mei->send_csa_msg_wk); + cancel_delayed_work_sync(&mei->csa_throttle_end_wk); + cancel_work_sync(&mei->netdev_work); + + /* + * If someone waits for the ownership, let him know that we are going + * down and that we are not connected anymore. He'll be able to take + * the device. + */ + wake_up_all(&mei->get_ownership_wq); + + mutex_lock(&iwl_mei_mutex); + + iwl_mei_global_cldev = NULL; + + wake_up_all(&mei->get_nvm_wq); + + iwl_mei_free_shared_mem(cldev); + + iwl_mei_dbgfs_unregister(mei); + + mei_cldev_set_drvdata(cldev, NULL); + + kfree(mei->nvm); + + kfree(rcu_access_pointer(mei->filters)); + + devm_kfree(&cldev->dev, mei); + + mutex_unlock(&iwl_mei_mutex); +} + +static const struct mei_cl_device_id iwl_mei_tbl[] = { + { + .name = KBUILD_MODNAME, + .uuid = MEI_WLAN_UUID, + .version = MEI_CL_VERSION_ANY, + }, + + /* required last entry */ + { } +}; + +/* + * Do not export the device table because this module is loaded by + * iwlwifi's dependency. + */ + +static struct mei_cl_driver iwl_mei_cl_driver = { + .id_table = iwl_mei_tbl, + .name = KBUILD_MODNAME, + .probe = iwl_mei_probe, + .remove = iwl_mei_remove, +}; + +module_mei_cl_driver(iwl_mei_cl_driver); diff --git a/drivers/net/wireless/intel/iwlwifi/mei/net.c b/drivers/net/wireless/intel/iwlwifi/mei/net.c new file mode 100644 index 000000000..eac46d1a3 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mei/net.c @@ -0,0 +1,412 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021-2022 Intel Corporation + */ + +#include <uapi/linux/if_ether.h> +#include <uapi/linux/if_arp.h> +#include <uapi/linux/icmp.h> + +#include <linux/etherdevice.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <linux/ieee80211.h> + +#include <net/cfg80211.h> +#include <net/ip.h> + +#include <linux/if_arp.h> +#include <linux/icmp.h> +#include <linux/udp.h> +#include <linux/ip.h> +#include <linux/mm.h> + +#include "internal.h" +#include "sap.h" +#include "iwl-mei.h" + +/* + * Returns true if further filtering should be stopped. Only in that case + * pass_to_csme and rx_handler_res are set. Otherwise, next level of filters + * should be checked. + */ +static bool iwl_mei_rx_filter_eth(const struct ethhdr *ethhdr, + const struct iwl_sap_oob_filters *filters, + bool *pass_to_csme, + rx_handler_result_t *rx_handler_res) +{ + const struct iwl_sap_eth_filter *filt; + + /* This filter is not relevant for UCAST packet */ + if (!is_multicast_ether_addr(ethhdr->h_dest) || + is_broadcast_ether_addr(ethhdr->h_dest)) + return false; + + for (filt = &filters->eth_filters[0]; + filt < &filters->eth_filters[0] + ARRAY_SIZE(filters->eth_filters); + filt++) { + /* Assume there are no enabled filter after a disabled one */ + if (!(filt->flags & SAP_ETH_FILTER_ENABLED)) + break; + + if (compare_ether_header(filt->mac_address, ethhdr->h_dest)) + continue; + + /* Packet needs to reach the host's stack */ + if (filt->flags & SAP_ETH_FILTER_COPY) + *rx_handler_res = RX_HANDLER_PASS; + else + *rx_handler_res = RX_HANDLER_CONSUMED; + + /* We have an authoritative answer, stop filtering */ + if (filt->flags & SAP_ETH_FILTER_STOP) { + *pass_to_csme = true; + return true; + } + + return false; + } + + /* MCAST frames that don't match layer 2 filters are not sent to ME */ + *pass_to_csme = false; + + return true; +} + +/* + * Returns true iff the frame should be passed to CSME in which case + * rx_handler_res is set. + */ +static bool iwl_mei_rx_filter_arp(struct sk_buff *skb, + const struct iwl_sap_oob_filters *filters, + rx_handler_result_t *rx_handler_res) +{ + const struct iwl_sap_ipv4_filter *filt = &filters->ipv4_filter; + const struct arphdr *arp; + const __be32 *target_ip; + u32 flags = le32_to_cpu(filt->flags); + + if (!pskb_may_pull(skb, arp_hdr_len(skb->dev))) + return false; + + arp = arp_hdr(skb); + + /* Handle only IPv4 over ethernet ARP frames */ + if (arp->ar_hrd != htons(ARPHRD_ETHER) || + arp->ar_pro != htons(ETH_P_IP)) + return false; + + /* + * After the ARP header, we have: + * src MAC address - 6 bytes + * src IP address - 4 bytes + * target MAC addess - 6 bytes + */ + target_ip = (const void *)((const u8 *)(arp + 1) + + ETH_ALEN + sizeof(__be32) + ETH_ALEN); + + /* + * ARP request is forwarded to ME only if IP address match in the + * ARP request's target ip field. + */ + if (arp->ar_op == htons(ARPOP_REQUEST) && + (filt->flags & cpu_to_le32(SAP_IPV4_FILTER_ARP_REQ_PASS)) && + (filt->ipv4_addr == 0 || filt->ipv4_addr == *target_ip)) { + if (flags & SAP_IPV4_FILTER_ARP_REQ_COPY) + *rx_handler_res = RX_HANDLER_PASS; + else + *rx_handler_res = RX_HANDLER_CONSUMED; + + return true; + } + + /* ARP reply is always forwarded to ME regardless of the IP */ + if (flags & SAP_IPV4_FILTER_ARP_RESP_PASS && + arp->ar_op == htons(ARPOP_REPLY)) { + if (flags & SAP_IPV4_FILTER_ARP_RESP_COPY) + *rx_handler_res = RX_HANDLER_PASS; + else + *rx_handler_res = RX_HANDLER_CONSUMED; + + return true; + } + + return false; +} + +static bool +iwl_mei_rx_filter_tcp_udp(struct sk_buff *skb, bool ip_match, + const struct iwl_sap_oob_filters *filters, + rx_handler_result_t *rx_handler_res) +{ + const struct iwl_sap_flex_filter *filt; + + for (filt = &filters->flex_filters[0]; + filt < &filters->flex_filters[0] + ARRAY_SIZE(filters->flex_filters); + filt++) { + if (!(filt->flags & SAP_FLEX_FILTER_ENABLED)) + break; + + /* + * We are required to have a match on the IP level and we didn't + * have such match. + */ + if ((filt->flags & + (SAP_FLEX_FILTER_IPV4 | SAP_FLEX_FILTER_IPV6)) && + !ip_match) + continue; + + if ((filt->flags & SAP_FLEX_FILTER_UDP) && + ip_hdr(skb)->protocol != IPPROTO_UDP) + continue; + + if ((filt->flags & SAP_FLEX_FILTER_TCP) && + ip_hdr(skb)->protocol != IPPROTO_TCP) + continue; + + /* + * We must have either a TCP header or a UDP header, both + * starts with a source port and then a destination port. + * Both are big endian words. + * Use a UDP header and that will work for TCP as well. + */ + if ((filt->src_port && filt->src_port != udp_hdr(skb)->source) || + (filt->dst_port && filt->dst_port != udp_hdr(skb)->dest)) + continue; + + if (filt->flags & SAP_FLEX_FILTER_COPY) + *rx_handler_res = RX_HANDLER_PASS; + else + *rx_handler_res = RX_HANDLER_CONSUMED; + + return true; + } + + return false; +} + +static bool iwl_mei_rx_filter_ipv4(struct sk_buff *skb, + const struct iwl_sap_oob_filters *filters, + rx_handler_result_t *rx_handler_res) +{ + const struct iwl_sap_ipv4_filter *filt = &filters->ipv4_filter; + const struct iphdr *iphdr; + unsigned int iphdrlen; + bool match; + + if (!pskb_may_pull(skb, skb_network_offset(skb) + sizeof(*iphdr)) || + !pskb_may_pull(skb, skb_network_offset(skb) + ip_hdrlen(skb))) + return false; + + iphdrlen = ip_hdrlen(skb); + iphdr = ip_hdr(skb); + match = !filters->ipv4_filter.ipv4_addr || + filters->ipv4_filter.ipv4_addr == iphdr->daddr; + + skb_set_transport_header(skb, skb_network_offset(skb) + iphdrlen); + + switch (ip_hdr(skb)->protocol) { + case IPPROTO_UDP: + case IPPROTO_TCP: + /* + * UDP header is shorter than TCP header and we look at the first bytes + * of the header anyway (see below). + * If we have a truncated TCP packet, let CSME handle this. + */ + if (!pskb_may_pull(skb, skb_transport_offset(skb) + + sizeof(struct udphdr))) + return false; + + return iwl_mei_rx_filter_tcp_udp(skb, match, + filters, rx_handler_res); + + case IPPROTO_ICMP: { + struct icmphdr *icmp; + + if (!pskb_may_pull(skb, skb_transport_offset(skb) + sizeof(*icmp))) + return false; + + icmp = icmp_hdr(skb); + + /* + * Don't pass echo requests to ME even if it wants it as we + * want the host to answer. + */ + if ((filt->flags & cpu_to_le32(SAP_IPV4_FILTER_ICMP_PASS)) && + match && (icmp->type != ICMP_ECHO || icmp->code != 0)) { + if (filt->flags & cpu_to_le32(SAP_IPV4_FILTER_ICMP_COPY)) + *rx_handler_res = RX_HANDLER_PASS; + else + *rx_handler_res = RX_HANDLER_CONSUMED; + + return true; + } + break; + } + case IPPROTO_ICMPV6: + /* TODO: Should we have the same ICMP request logic here too? */ + if ((filters->icmpv6_flags & cpu_to_le32(SAP_ICMPV6_FILTER_ENABLED) && + match)) { + if (filters->icmpv6_flags & + cpu_to_le32(SAP_ICMPV6_FILTER_COPY)) + *rx_handler_res = RX_HANDLER_PASS; + else + *rx_handler_res = RX_HANDLER_CONSUMED; + + return true; + } + break; + default: + return false; + } + + return false; +} + +static bool iwl_mei_rx_filter_ipv6(struct sk_buff *skb, + const struct iwl_sap_oob_filters *filters, + rx_handler_result_t *rx_handler_res) +{ + *rx_handler_res = RX_HANDLER_PASS; + + /* TODO */ + + return false; +} + +static rx_handler_result_t +iwl_mei_rx_pass_to_csme(struct sk_buff *skb, + const struct iwl_sap_oob_filters *filters, + bool *pass_to_csme) +{ + const struct ethhdr *ethhdr = (void *)skb_mac_header(skb); + rx_handler_result_t rx_handler_res = RX_HANDLER_PASS; + bool (*filt_handler)(struct sk_buff *skb, + const struct iwl_sap_oob_filters *filters, + rx_handler_result_t *rx_handler_res); + + /* + * skb->data points the IP header / ARP header and the ETH header + * is in the headroom. + */ + skb_reset_network_header(skb); + + /* + * MCAST IP packets sent by us are received again here without + * an ETH header. Drop them here. + */ + if (!skb_mac_offset(skb)) + return RX_HANDLER_PASS; + + if (skb_headroom(skb) < sizeof(*ethhdr)) + return RX_HANDLER_PASS; + + if (iwl_mei_rx_filter_eth(ethhdr, filters, + pass_to_csme, &rx_handler_res)) + return rx_handler_res; + + switch (skb->protocol) { + case htons(ETH_P_IP): + filt_handler = iwl_mei_rx_filter_ipv4; + break; + case htons(ETH_P_ARP): + filt_handler = iwl_mei_rx_filter_arp; + break; + case htons(ETH_P_IPV6): + filt_handler = iwl_mei_rx_filter_ipv6; + break; + default: + *pass_to_csme = false; + return rx_handler_res; + } + + *pass_to_csme = filt_handler(skb, filters, &rx_handler_res); + + return rx_handler_res; +} + +rx_handler_result_t iwl_mei_rx_filter(struct sk_buff *orig_skb, + const struct iwl_sap_oob_filters *filters, + bool *pass_to_csme) +{ + rx_handler_result_t ret; + struct sk_buff *skb; + + ret = iwl_mei_rx_pass_to_csme(orig_skb, filters, pass_to_csme); + + if (!*pass_to_csme) + return RX_HANDLER_PASS; + + if (ret == RX_HANDLER_PASS) { + skb = skb_copy(orig_skb, GFP_ATOMIC); + + if (!skb) + return RX_HANDLER_PASS; + } else { + skb = orig_skb; + } + + /* CSME wants the MAC header as well, push it back */ + skb_push(skb, skb->data - skb_mac_header(skb)); + + /* + * Add the packet that CSME wants to get to the ring. Don't send the + * Check Shared Area HECI message since this is not possible from the + * Rx context. The caller will schedule a worker to do just that. + */ + iwl_mei_add_data_to_ring(skb, false); + + /* + * In case we drop the packet, don't free it, the caller will do that + * for us + */ + if (ret == RX_HANDLER_PASS) + dev_kfree_skb(skb); + + return ret; +} + +#define DHCP_SERVER_PORT 67 +#define DHCP_CLIENT_PORT 68 +void iwl_mei_tx_copy_to_csme(struct sk_buff *origskb, unsigned int ivlen) +{ + struct ieee80211_hdr *hdr; + struct sk_buff *skb; + struct ethhdr ethhdr; + struct ethhdr *eth; + + /* Catch DHCP packets */ + if (origskb->protocol != htons(ETH_P_IP) || + ip_hdr(origskb)->protocol != IPPROTO_UDP || + udp_hdr(origskb)->source != htons(DHCP_CLIENT_PORT) || + udp_hdr(origskb)->dest != htons(DHCP_SERVER_PORT)) + return; + + /* + * We could be a bit less aggressive here and not copy everything, but + * this is very rare anyway, do don't bother much. + */ + skb = skb_copy(origskb, GFP_ATOMIC); + if (!skb) + return; + + skb->protocol = origskb->protocol; + + hdr = (void *)skb->data; + + memcpy(ethhdr.h_dest, ieee80211_get_DA(hdr), ETH_ALEN); + memcpy(ethhdr.h_source, ieee80211_get_SA(hdr), ETH_ALEN); + + /* + * Remove the ieee80211 header + IV + SNAP but leave the ethertype + * We still have enough headroom for the sap header. + */ + pskb_pull(skb, ieee80211_hdrlen(hdr->frame_control) + ivlen + 6); + eth = skb_push(skb, sizeof(ethhdr.h_dest) + sizeof(ethhdr.h_source)); + memcpy(eth, ðhdr, sizeof(ethhdr.h_dest) + sizeof(ethhdr.h_source)); + + iwl_mei_add_data_to_ring(skb, true); + + dev_kfree_skb(skb); +} +EXPORT_SYMBOL_GPL(iwl_mei_tx_copy_to_csme); diff --git a/drivers/net/wireless/intel/iwlwifi/mei/sap.h b/drivers/net/wireless/intel/iwlwifi/mei/sap.h new file mode 100644 index 000000000..be1456dea --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mei/sap.h @@ -0,0 +1,733 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021 Intel Corporation + */ + +#ifndef __sap_h__ +#define __sap_h__ + +#include "mei/iwl-mei.h" + +/** + * DOC: Introduction + * + * SAP is the protocol used by the Intel Wireless driver (iwlwifi) + * and the wireless driver implemented in the CSME firmware. + * It allows to do several things: + * 1) Decide who is the owner of the device: CSME or the host + * 2) When the host is the owner of the device, CSME can still + * send and receive packets through iwlwifi. + * + * The protocol uses the ME interface (mei driver) to send + * messages to the CSME firmware. Those messages have a header + * &struct iwl_sap_me_msg_hdr and this header is followed + * by a payload. + * + * Since this messaging system cannot support high amounts of + * traffic, iwlwifi and the CSME firmware's WLAN driver have an + * addtional communication pipe to exchange information. The body + * of the message is copied to a shared area and the message that + * goes over the ME interface just signals the other side + * that a new message is waiting in the shared area. The ME + * interface is used only for signaling and not to transfer + * the payload. + * + * This shared area of memory is DMA'able mapped to be + * writable by both the CSME firmware and iwlwifi. It is + * mapped to address space of the device that controls the ME + * interface's DMA engine. Any data that iwlwifi needs to + * send to the CSME firmware needs to be copied to there. + */ + +/** + * DOC: Initial Handshake + * + * Once we get a link to the CMSE's WLAN driver we start the handshake + * to establish the shared memory that will allow the communication between + * the CSME's WLAN driver and the host. + * + * 1) Host sends %SAP_ME_MSG_START message with the physical address + * of the shared area. + * 2) CSME replies with %SAP_ME_MSG_START_OK which includes the versions + * protocol versions supported by CSME. + */ + +/** + * DOC: Host and driver state messages + * + * In order to let CSME konw about the host state and the host driver state, + * the host sends messages that let CSME know about the host's state. + * When the host driver is loaded, the host sends %SAP_MSG_NOTIF_WIFIDR_UP. + * When the host driver is unloaded, the host sends %SAP_MSG_NOTIF_WIFIDR_DOWN. + * When the iwlmei is unloaded, %SAP_MSG_NOTIF_HOST_GOES_DOWN is sent to let + * CSME know not to access the shared memory anymore since it'll be freed. + * + * CSME will reply to SAP_MSG_NOTIF_WIFIDR_UP by + * %SAP_MSG_NOTIF_AMT_STATE to let the host driver whether CSME can use the + * WiFi device or not followed by %SAP_MSG_NOTIF_CSME_CONN_STATUS to inform + * the host driver on the connection state of CSME. + * + * When host is associated to an AP, it must send %SAP_MSG_NOTIF_HOST_LINK_UP + * and when it disconnect from the AP, it must send + * %SAP_MSG_NOTIF_HOST_LINK_DOWN. + */ + +/** + * DOC: Ownership + * + * The device can be controlled either by the CSME firmware or + * by the host driver: iwlwifi. There is a negotiaion between + * those two entities to determine who controls (or owns) the + * device. Since the CSME can control the device even when the + * OS is not working or even missing, the CSME can request the + * device if it comes to the conclusion that the OS's host driver + * is not operational. This is why the host driver needs to + * signal CSME that it is up and running. If the driver is + * unloaded, it'll signal CSME that it is going down so that + * CSME can take ownership. + */ + +/** + * DOC: Ownership transfer + * + * When the host driver needs the device, it'll send the + * %SAP_MSG_NOTIF_HOST_ASKS_FOR_NIC_OWNERSHIP that will be replied by + * %SAP_MSG_NOTIF_CSME_REPLY_TO_HOST_OWNERSHIP_REQ which will let the + * host know whether the ownership is granted or no. If the ownership is + * granted, the hosts sends %SAP_MSG_NOTIF_HOST_OWNERSHIP_CONFIRMED. + * + * When CSME requests ownership, it'll send the + * %SAP_MSG_NOTIF_CSME_TAKING_OWNERSHIP and give some time to host to stop + * accessing the device. The host needs to send + * %SAP_MSG_NOTIF_CSME_OWNERSHIP_CONFIRMED to confirm that it won't access + * the device anymore. If the host failed to send this message fast enough, + * CSME will take ownership on the device anyway. + * When CSME is willing to release the ownership, it'll send + * %SAP_MSG_NOTIF_CSME_CAN_RELEASE_OWNERSHIP. + */ + +/** + * DOC: Data messages + * + * Data messages must be sent and receives on a separate queue in the shared + * memory. Almost all the data messages use the %SAP_MSG_DATA_PACKET for both + * packets sent by CSME to the host to be sent to the AP or for packets + * received from the AP and sent by the host to CSME. + * CSME sends filters to the host to let the host what inbound packets it must + * send to CSME. Those filters are received by the host as a + * %SAP_MSG_NOTIF_CSME_FILTERS command. + * The only outbound packets that must be sent to CSME are the DHCP packets. + * Those packets must use the %SAP_MSG_CB_DATA_PACKET message. + */ + +/** + * enum iwl_sap_me_msg_id - the ID of the ME message + * @SAP_ME_MSG_START: See &struct iwl_sap_me_msg_start. + * @SAP_ME_MSG_START_OK: See &struct iwl_sap_me_msg_start_ok. + * @SAP_ME_MSG_CHECK_SHARED_AREA: This message has no payload. + */ +enum iwl_sap_me_msg_id { + SAP_ME_MSG_START = 1, + SAP_ME_MSG_START_OK, + SAP_ME_MSG_CHECK_SHARED_AREA, +}; + +/** + * struct iwl_sap_me_msg_hdr - the header of the ME message + * @type: the type of the message, see &enum iwl_sap_me_msg_id. + * @seq_num: a sequence number used for debug only. + * @len: the length of the mssage. + */ +struct iwl_sap_me_msg_hdr { + __le32 type; + __le32 seq_num; + __le32 len; +} __packed; + +/** + * struct iwl_sap_me_msg_start - used for the %SAP_ME_MSG_START message + * @hdr: See &struct iwl_sap_me_msg_hdr. + * @shared_mem: physical address of SAP shared memory area. + * @init_data_seq_num: seq_num of the first data packet HOST -> CSME. + * @init_notif_seq_num: seq_num of the first notification HOST -> CSME. + * @supported_versions: The host sends to the CSME a zero-terminated array + * of versions its supports. + * + * This message is sent by the host to CSME and will responded by the + * %SAP_ME_MSG_START_OK message. + */ +struct iwl_sap_me_msg_start { + struct iwl_sap_me_msg_hdr hdr; + __le64 shared_mem; + __le16 init_data_seq_num; + __le16 init_notif_seq_num; + u8 supported_versions[64]; +} __packed; + +/** + * struct iwl_sap_me_msg_start_ok - used for the %SAP_ME_MSG_START_OK + * @hdr: See &struct iwl_sap_me_msg_hdr + * @init_data_seq_num: Not used. + * @init_notif_seq_num: Not used + * @supported_version: The version that will be used. + * @reserved: For alignment. + * + * This message is sent by CSME to the host in response to the + * %SAP_ME_MSG_START message. + */ +struct iwl_sap_me_msg_start_ok { + struct iwl_sap_me_msg_hdr hdr; + __le16 init_data_seq_num; + __le16 init_notif_seq_num; + u8 supported_version; + u8 reserved[3]; +} __packed; + +/** + * enum iwl_sap_msg - SAP messages + * @SAP_MSG_NOTIF_BOTH_WAYS_MIN: Not used. + * @SAP_MSG_NOTIF_PING: No payload. Solicitate a response message (check-alive). + * @SAP_MSG_NOTIF_PONG: No payload. The response message. + * @SAP_MSG_NOTIF_BOTH_WAYS_MAX: Not used. + * + * @SAP_MSG_NOTIF_FROM_CSME_MIN: Not used. + * @SAP_MSG_NOTIF_CSME_FILTERS: TODO + * @SAP_MSG_NOTIF_AMT_STATE: Payload is a DW. Any non-zero value means + * that CSME is enabled. + * @SAP_MSG_NOTIF_CSME_REPLY_TO_HOST_OWNERSHIP_REQ: Payload is a DW. 0 means + * the host will not get ownership. Any other value means the host is + * the owner. + * @SAP_MSG_NOTIF_CSME_TAKING_OWNERSHIP: No payload. + * @SAP_MSG_NOTIF_TRIGGER_IP_REFRESH: No payload. + * @SAP_MSG_NOTIF_CSME_CAN_RELEASE_OWNERSHIP: No payload. + * @SAP_MSG_NOTIF_NIC_OWNER: Payload is a DW. See &enum iwl_sap_nic_owner. + * @SAP_MSG_NOTIF_CSME_CONN_STATUS: See &struct iwl_sap_notif_conn_status. + * @SAP_MSG_NOTIF_NVM: See &struct iwl_sap_nvm. + * @SAP_MSG_NOTIF_FROM_CSME_MAX: Not used. + * + * @SAP_MSG_NOTIF_FROM_HOST_MIN: Not used. + * @SAP_MSG_NOTIF_BAND_SELECTION: TODO + * @SAP_MSG_NOTIF_RADIO_STATE: Payload is a DW. + * See &enum iwl_sap_radio_state_bitmap. + * @SAP_MSG_NOTIF_NIC_INFO: See &struct iwl_sap_notif_host_nic_info. + * @SAP_MSG_NOTIF_HOST_ASKS_FOR_NIC_OWNERSHIP: No payload. + * @SAP_MSG_NOTIF_HOST_SUSPENDS: Payload is a DW. Bitmap described in + * &enum iwl_sap_notif_host_suspends_bitmap. + * @SAP_MSG_NOTIF_HOST_RESUMES: Payload is a DW. 0 or 1. 1 says that + * the CSME should re-initialize the init control block. + * @SAP_MSG_NOTIF_HOST_GOES_DOWN: No payload. + * @SAP_MSG_NOTIF_CSME_OWNERSHIP_CONFIRMED: No payload. + * @SAP_MSG_NOTIF_COUNTRY_CODE: See &struct iwl_sap_notif_country_code. + * @SAP_MSG_NOTIF_HOST_LINK_UP: See &struct iwl_sap_notif_host_link_up. + * @SAP_MSG_NOTIF_HOST_LINK_DOWN: See &struct iwl_sap_notif_host_link_down. + * @SAP_MSG_NOTIF_WHO_OWNS_NIC: No payload. + * @SAP_MSG_NOTIF_WIFIDR_DOWN: No payload. + * @SAP_MSG_NOTIF_WIFIDR_UP: No payload. + * @SAP_MSG_NOTIF_HOST_OWNERSHIP_CONFIRMED: No payload. + * @SAP_MSG_NOTIF_SAR_LIMITS: See &struct iwl_sap_notif_sar_limits. + * @SAP_MSG_NOTIF_GET_NVM: No payload. Triggers %SAP_MSG_NOTIF_NVM. + * @SAP_MSG_NOTIF_FROM_HOST_MAX: Not used. + * + * @SAP_MSG_DATA_MIN: Not used. + * @SAP_MSG_DATA_PACKET: Packets that passed the filters defined by + * %SAP_MSG_NOTIF_CSME_FILTERS. The payload is &struct iwl_sap_hdr with + * the payload of the packet immediately afterwards. + * @SAP_MSG_CB_DATA_PACKET: Indicates to CSME that we transmitted a specific + * packet. Used only for DHCP transmitted packets. See + * &struct iwl_sap_cb_data. + * @SAP_MSG_DATA_MAX: Not used. + */ +enum iwl_sap_msg { + SAP_MSG_NOTIF_BOTH_WAYS_MIN = 0, + SAP_MSG_NOTIF_PING = 1, + SAP_MSG_NOTIF_PONG = 2, + SAP_MSG_NOTIF_BOTH_WAYS_MAX, + + SAP_MSG_NOTIF_FROM_CSME_MIN = 500, + SAP_MSG_NOTIF_CSME_FILTERS = SAP_MSG_NOTIF_FROM_CSME_MIN, + /* 501 is deprecated */ + SAP_MSG_NOTIF_AMT_STATE = 502, + SAP_MSG_NOTIF_CSME_REPLY_TO_HOST_OWNERSHIP_REQ = 503, + SAP_MSG_NOTIF_CSME_TAKING_OWNERSHIP = 504, + SAP_MSG_NOTIF_TRIGGER_IP_REFRESH = 505, + SAP_MSG_NOTIF_CSME_CAN_RELEASE_OWNERSHIP = 506, + /* 507 is deprecated */ + /* 508 is deprecated */ + /* 509 is deprecated */ + /* 510 is deprecated */ + SAP_MSG_NOTIF_NIC_OWNER = 511, + SAP_MSG_NOTIF_CSME_CONN_STATUS = 512, + SAP_MSG_NOTIF_NVM = 513, + SAP_MSG_NOTIF_FROM_CSME_MAX, + + SAP_MSG_NOTIF_FROM_HOST_MIN = 1000, + SAP_MSG_NOTIF_BAND_SELECTION = SAP_MSG_NOTIF_FROM_HOST_MIN, + SAP_MSG_NOTIF_RADIO_STATE = 1001, + SAP_MSG_NOTIF_NIC_INFO = 1002, + SAP_MSG_NOTIF_HOST_ASKS_FOR_NIC_OWNERSHIP = 1003, + SAP_MSG_NOTIF_HOST_SUSPENDS = 1004, + SAP_MSG_NOTIF_HOST_RESUMES = 1005, + SAP_MSG_NOTIF_HOST_GOES_DOWN = 1006, + SAP_MSG_NOTIF_CSME_OWNERSHIP_CONFIRMED = 1007, + SAP_MSG_NOTIF_COUNTRY_CODE = 1008, + SAP_MSG_NOTIF_HOST_LINK_UP = 1009, + SAP_MSG_NOTIF_HOST_LINK_DOWN = 1010, + SAP_MSG_NOTIF_WHO_OWNS_NIC = 1011, + SAP_MSG_NOTIF_WIFIDR_DOWN = 1012, + SAP_MSG_NOTIF_WIFIDR_UP = 1013, + /* 1014 is deprecated */ + SAP_MSG_NOTIF_HOST_OWNERSHIP_CONFIRMED = 1015, + SAP_MSG_NOTIF_SAR_LIMITS = 1016, + SAP_MSG_NOTIF_GET_NVM = 1017, + SAP_MSG_NOTIF_FROM_HOST_MAX, + + SAP_MSG_DATA_MIN = 2000, + SAP_MSG_DATA_PACKET = SAP_MSG_DATA_MIN, + SAP_MSG_CB_DATA_PACKET = 2001, + SAP_MSG_DATA_MAX, +}; + +/** + * struct iwl_sap_hdr - prefixes any SAP message + * @type: See &enum iwl_sap_msg. + * @len: The length of the message (header not included). + * @seq_num: For debug. + * @payload: The payload of the message. + */ +struct iwl_sap_hdr { + __le16 type; + __le16 len; + __le32 seq_num; + u8 payload[]; +}; + +/** + * struct iwl_sap_msg_dw - suits any DW long SAP message + * @hdr: The SAP header + * @val: The value of the DW. + */ +struct iwl_sap_msg_dw { + struct iwl_sap_hdr hdr; + __le32 val; +}; + +/** + * enum iwl_sap_nic_owner - used by %SAP_MSG_NOTIF_NIC_OWNER + * @SAP_NIC_OWNER_UNKNOWN: Not used. + * @SAP_NIC_OWNER_HOST: The host owns the NIC. + * @SAP_NIC_OWNER_ME: CSME owns the NIC. + */ +enum iwl_sap_nic_owner { + SAP_NIC_OWNER_UNKNOWN, + SAP_NIC_OWNER_HOST, + SAP_NIC_OWNER_ME, +}; + +enum iwl_sap_wifi_auth_type { + SAP_WIFI_AUTH_TYPE_OPEN = IWL_MEI_AKM_AUTH_OPEN, + SAP_WIFI_AUTH_TYPE_RSNA = IWL_MEI_AKM_AUTH_RSNA, + SAP_WIFI_AUTH_TYPE_RSNA_PSK = IWL_MEI_AKM_AUTH_RSNA_PSK, + SAP_WIFI_AUTH_TYPE_SAE = IWL_MEI_AKM_AUTH_SAE, + SAP_WIFI_AUTH_TYPE_MAX, +}; + +/** + * enum iwl_sap_wifi_cipher_alg + * @SAP_WIFI_CIPHER_ALG_NONE: TBD + * @SAP_WIFI_CIPHER_ALG_CCMP: TBD + * @SAP_WIFI_CIPHER_ALG_GCMP: TBD + * @SAP_WIFI_CIPHER_ALG_GCMP_256: TBD + */ +enum iwl_sap_wifi_cipher_alg { + SAP_WIFI_CIPHER_ALG_NONE = IWL_MEI_CIPHER_NONE, + SAP_WIFI_CIPHER_ALG_CCMP = IWL_MEI_CIPHER_CCMP, + SAP_WIFI_CIPHER_ALG_GCMP = IWL_MEI_CIPHER_GCMP, + SAP_WIFI_CIPHER_ALG_GCMP_256 = IWL_MEI_CIPHER_GCMP_256, +}; + +/** + * struct iwl_sap_notif_connection_info - nested in other structures + * @ssid_len: The length of the SSID. + * @ssid: The SSID. + * @auth_mode: The authentication mode. See &enum iwl_sap_wifi_auth_type. + * @pairwise_cipher: The cipher used for unicast packets. + * See &enum iwl_sap_wifi_cipher_alg. + * @channel: The channel on which we are associated. + * @band: The band on which we are associated. + * @reserved: For alignment. + * @bssid: The BSSID. + * @reserved1: For alignment. + */ +struct iwl_sap_notif_connection_info { + __le32 ssid_len; + u8 ssid[32]; + __le32 auth_mode; + __le32 pairwise_cipher; + u8 channel; + u8 band; + __le16 reserved; + u8 bssid[6]; + __le16 reserved1; +} __packed; + +/** + * enum iwl_sap_scan_request - for the scan_request field + * @SCAN_REQUEST_FILTERING: Filtering is requested. + * @SCAN_REQUEST_FAST: Fast scan is requested. + */ +enum iwl_sap_scan_request { + SCAN_REQUEST_FILTERING = 1 << 0, + SCAN_REQUEST_FAST = 1 << 1, +}; + +/** + * struct iwl_sap_notif_conn_status - payload of %SAP_MSG_NOTIF_CSME_CONN_STATUS + * @hdr: The SAP header + * @link_prot_state: Non-zero if link protection is active. + * @scan_request: See &enum iwl_sap_scan_request. + * @conn_info: Information about the connection. + */ +struct iwl_sap_notif_conn_status { + struct iwl_sap_hdr hdr; + __le32 link_prot_state; + __le32 scan_request; + struct iwl_sap_notif_connection_info conn_info; +} __packed; + +/** + * enum iwl_sap_radio_state_bitmap - used for %SAP_MSG_NOTIF_RADIO_STATE + * @SAP_SW_RFKILL_DEASSERTED: If set, SW RfKill is de-asserted + * @SAP_HW_RFKILL_DEASSERTED: If set, HW RfKill is de-asserted + * + * If both bits are set, then the radio is on. + */ +enum iwl_sap_radio_state_bitmap { + SAP_SW_RFKILL_DEASSERTED = 1 << 0, + SAP_HW_RFKILL_DEASSERTED = 1 << 1, +}; + +/** + * enum iwl_sap_notif_host_suspends_bitmap - used for %SAP_MSG_NOTIF_HOST_SUSPENDS + * @SAP_OFFER_NIC: TBD + * @SAP_FILTER_CONFIGURED: TBD + * @SAP_NLO_CONFIGURED: TBD + * @SAP_HOST_OWNS_NIC: TBD + * @SAP_LINK_PROTECTED: TBD + */ +enum iwl_sap_notif_host_suspends_bitmap { + SAP_OFFER_NIC = 1 << 0, + SAP_FILTER_CONFIGURED = 1 << 1, + SAP_NLO_CONFIGURED = 1 << 2, + SAP_HOST_OWNS_NIC = 1 << 3, + SAP_LINK_PROTECTED = 1 << 4, +}; + +/** + * struct iwl_sap_notif_country_code - payload of %SAP_MSG_NOTIF_COUNTRY_CODE + * @hdr: The SAP header + * @mcc: The country code. + * @source_id: TBD + * @reserved: For alignment. + * @diff_time: TBD + */ +struct iwl_sap_notif_country_code { + struct iwl_sap_hdr hdr; + __le16 mcc; + u8 source_id; + u8 reserved; + __le32 diff_time; +} __packed; + +/** + * struct iwl_sap_notif_host_link_up - payload of %SAP_MSG_NOTIF_HOST_LINK_UP + * @hdr: The SAP header + * @conn_info: Information about the connection. + * @colloc_channel: The collocated channel + * @colloc_band: The band of the collocated channel. + * @reserved: For alignment. + * @colloc_bssid: The collocated BSSID. + * @reserved1: For alignment. + */ +struct iwl_sap_notif_host_link_up { + struct iwl_sap_hdr hdr; + struct iwl_sap_notif_connection_info conn_info; + u8 colloc_channel; + u8 colloc_band; + __le16 reserved; + u8 colloc_bssid[6]; + __le16 reserved1; +} __packed; + +/** + * enum iwl_sap_notif_link_down_type - used in &struct iwl_sap_notif_host_link_down + * @HOST_LINK_DOWN_TYPE_NONE: TBD + * @HOST_LINK_DOWN_TYPE_TEMPORARY: TBD + * @HOST_LINK_DOWN_TYPE_LONG: TBD + */ +enum iwl_sap_notif_link_down_type { + HOST_LINK_DOWN_TYPE_NONE, + HOST_LINK_DOWN_TYPE_TEMPORARY, + HOST_LINK_DOWN_TYPE_LONG, +}; + +/** + * struct iwl_sap_notif_host_link_down - payload for %SAP_MSG_NOTIF_HOST_LINK_DOWN + * @hdr: The SAP header + * @type: See &enum iwl_sap_notif_link_down_type. + * @reserved: For alignment. + * @reason_valid: If 0, ignore the next field. + * @reason: The reason of the disconnection. + */ +struct iwl_sap_notif_host_link_down { + struct iwl_sap_hdr hdr; + u8 type; + u8 reserved[2]; + u8 reason_valid; + __le32 reason; +} __packed; + +/** + * struct iwl_sap_notif_host_nic_info - payload for %SAP_MSG_NOTIF_NIC_INFO + * @hdr: The SAP header + * @mac_address: The MAC address as configured to the interface. + * @nvm_address: The MAC address as configured in the NVM. + */ +struct iwl_sap_notif_host_nic_info { + struct iwl_sap_hdr hdr; + u8 mac_address[6]; + u8 nvm_address[6]; +} __packed; + +/** + * struct iwl_sap_notif_dw - payload is a dw + * @hdr: The SAP header. + * @dw: The payload. + */ +struct iwl_sap_notif_dw { + struct iwl_sap_hdr hdr; + __le32 dw; +} __packed; + +/** + * struct iwl_sap_notif_sar_limits - payload for %SAP_MSG_NOTIF_SAR_LIMITS + * @hdr: The SAP header + * @sar_chain_info_table: Tx power limits. + */ +struct iwl_sap_notif_sar_limits { + struct iwl_sap_hdr hdr; + __le16 sar_chain_info_table[2][5]; +} __packed; + +/** + * enum iwl_sap_nvm_caps - capabilities for NVM SAP + * @SAP_NVM_CAPS_LARI_SUPPORT: Lari is supported + * @SAP_NVM_CAPS_11AX_SUPPORT: 11AX is supported + */ +enum iwl_sap_nvm_caps { + SAP_NVM_CAPS_LARI_SUPPORT = BIT(0), + SAP_NVM_CAPS_11AX_SUPPORT = BIT(1), +}; + +/** + * struct iwl_sap_nvm - payload for %SAP_MSG_NOTIF_NVM + * @hdr: The SAP header. + * @hw_addr: The MAC address + * @n_hw_addrs: The number of MAC addresses + * @reserved: For alignment. + * @radio_cfg: The radio configuration. + * @caps: See &enum iwl_sap_nvm_caps. + * @nvm_version: The version of the NVM. + * @channels: The data for each channel. + */ +struct iwl_sap_nvm { + struct iwl_sap_hdr hdr; + u8 hw_addr[6]; + u8 n_hw_addrs; + u8 reserved; + __le32 radio_cfg; + __le32 caps; + __le32 nvm_version; + __le32 channels[110]; +} __packed; + +/** + * enum iwl_sap_eth_filter_flags - used in &struct iwl_sap_eth_filter + * @SAP_ETH_FILTER_STOP: Do not process further filters. + * @SAP_ETH_FILTER_COPY: Copy the packet to the CSME. + * @SAP_ETH_FILTER_ENABLED: If false, the filter should be ignored. + */ +enum iwl_sap_eth_filter_flags { + SAP_ETH_FILTER_STOP = BIT(0), + SAP_ETH_FILTER_COPY = BIT(1), + SAP_ETH_FILTER_ENABLED = BIT(2), +}; + +/** + * struct iwl_sap_eth_filter - a L2 filter + * @mac_address: Address to filter. + * @flags: See &enum iwl_sap_eth_filter_flags. + */ +struct iwl_sap_eth_filter { + u8 mac_address[6]; + u8 flags; +} __packed; + +/** + * enum iwl_sap_flex_filter_flags - used in &struct iwl_sap_flex_filter + * @SAP_FLEX_FILTER_COPY: Pass UDP / TCP packets to CSME. + * @SAP_FLEX_FILTER_ENABLED: If false, the filter should be ignored. + * @SAP_FLEX_FILTER_IPV4: Filter requires match on the IP address as well. + * @SAP_FLEX_FILTER_IPV6: Filter requires match on the IP address as well. + * @SAP_FLEX_FILTER_TCP: Filter should be applied on TCP packets. + * @SAP_FLEX_FILTER_UDP: Filter should be applied on UDP packets. + */ +enum iwl_sap_flex_filter_flags { + SAP_FLEX_FILTER_COPY = BIT(0), + SAP_FLEX_FILTER_ENABLED = BIT(1), + SAP_FLEX_FILTER_IPV6 = BIT(2), + SAP_FLEX_FILTER_IPV4 = BIT(3), + SAP_FLEX_FILTER_TCP = BIT(4), + SAP_FLEX_FILTER_UDP = BIT(5), +}; + +/** + * struct iwl_sap_flex_filter - + * @src_port: Source port in network format. + * @dst_port: Destination port in network format. + * @flags: Flags and protocol, see &enum iwl_sap_flex_filter_flags. + * @reserved: For alignment. + */ +struct iwl_sap_flex_filter { + __be16 src_port; + __be16 dst_port; + u8 flags; + u8 reserved; +} __packed; + +/** + * enum iwl_sap_ipv4_filter_flags - used in &struct iwl_sap_ipv4_filter + * @SAP_IPV4_FILTER_ICMP_PASS: Pass ICMP packets to CSME. + * @SAP_IPV4_FILTER_ICMP_COPY: Pass ICMP packets to host. + * @SAP_IPV4_FILTER_ARP_REQ_PASS: Pass ARP requests to CSME. + * @SAP_IPV4_FILTER_ARP_REQ_COPY: Pass ARP requests to host. + * @SAP_IPV4_FILTER_ARP_RESP_PASS: Pass ARP responses to CSME. + * @SAP_IPV4_FILTER_ARP_RESP_COPY: Pass ARP responses to host. + */ +enum iwl_sap_ipv4_filter_flags { + SAP_IPV4_FILTER_ICMP_PASS = BIT(0), + SAP_IPV4_FILTER_ICMP_COPY = BIT(1), + SAP_IPV4_FILTER_ARP_REQ_PASS = BIT(2), + SAP_IPV4_FILTER_ARP_REQ_COPY = BIT(3), + SAP_IPV4_FILTER_ARP_RESP_PASS = BIT(4), + SAP_IPV4_FILTER_ARP_RESP_COPY = BIT(5), +}; + +/** + * struct iwl_sap_ipv4_filter- + * @ipv4_addr: The IP address to filer. + * @flags: See &enum iwl_sap_ipv4_filter_flags. + */ +struct iwl_sap_ipv4_filter { + __be32 ipv4_addr; + __le32 flags; +} __packed; + +/** + * enum iwl_sap_ipv6_filter_flags - + * @SAP_IPV6_ADDR_FILTER_COPY: Pass packets to the host. + * @SAP_IPV6_ADDR_FILTER_ENABLED: If false, the filter should be ignored. + */ +enum iwl_sap_ipv6_filter_flags { + SAP_IPV6_ADDR_FILTER_COPY = BIT(0), + SAP_IPV6_ADDR_FILTER_ENABLED = BIT(1), +}; + +/** + * struct iwl_sap_ipv6_filter - + * @addr_lo24: Lowest 24 bits of the IPv6 address. + * @flags: See &enum iwl_sap_ipv6_filter_flags. + */ +struct iwl_sap_ipv6_filter { + u8 addr_lo24[3]; + u8 flags; +} __packed; + +/** + * enum iwl_sap_icmpv6_filter_flags - + * @SAP_ICMPV6_FILTER_ENABLED: If false, the filter should be ignored. + * @SAP_ICMPV6_FILTER_COPY: Pass packets to the host. + */ +enum iwl_sap_icmpv6_filter_flags { + SAP_ICMPV6_FILTER_ENABLED = BIT(0), + SAP_ICMPV6_FILTER_COPY = BIT(1), +}; + +/** + * enum iwl_sap_vlan_filter_flags - + * @SAP_VLAN_FILTER_VLAN_ID_MSK: TBD + * @SAP_VLAN_FILTER_ENABLED: If false, the filter should be ignored. + */ +enum iwl_sap_vlan_filter_flags { + SAP_VLAN_FILTER_VLAN_ID_MSK = 0x0FFF, + SAP_VLAN_FILTER_ENABLED = BIT(15), +}; + +/** + * struct iwl_sap_oob_filters - Out of band filters (for RX only) + * @flex_filters: Array of &struct iwl_sap_flex_filter. + * @icmpv6_flags: See &enum iwl_sap_icmpv6_filter_flags. + * @ipv6_filters: Array of &struct iwl_sap_ipv6_filter. + * @eth_filters: Array of &struct iwl_sap_eth_filter. + * @reserved: For alignment. + * @ipv4_filter: &struct iwl_sap_ipv4_filter. + * @vlan: See &enum iwl_sap_vlan_filter_flags. + */ +struct iwl_sap_oob_filters { + struct iwl_sap_flex_filter flex_filters[14]; + __le32 icmpv6_flags; + struct iwl_sap_ipv6_filter ipv6_filters[4]; + struct iwl_sap_eth_filter eth_filters[5]; + u8 reserved; + struct iwl_sap_ipv4_filter ipv4_filter; + __le16 vlan[4]; +} __packed; + +/** + * struct iwl_sap_csme_filters - payload of %SAP_MSG_NOTIF_CSME_FILTERS + * @hdr: The SAP header. + * @mode: Not used. + * @mac_address: Not used. + * @reserved: For alignment. + * @cbfilters: Not used. + * @filters: Out of band filters. + */ +struct iwl_sap_csme_filters { + struct iwl_sap_hdr hdr; + __le32 mode; + u8 mac_address[6]; + __le16 reserved; + u8 cbfilters[1728]; + struct iwl_sap_oob_filters filters; +} __packed; + +#define CB_TX_DHCP_FILT_IDX 30 +/** + * struct iwl_sap_cb_data - header to be added for transmitted packets. + * @hdr: The SAP header. + * @reserved: Not used. + * @to_me_filt_status: The filter that matches. Bit %CB_TX_DHCP_FILT_IDX should + * be set for DHCP (the only packet that uses this header). + * @reserved2: Not used. + * @data_len: The length of the payload. + * @payload: The payload of the transmitted packet. + */ +struct iwl_sap_cb_data { + struct iwl_sap_hdr hdr; + __le32 reserved[7]; + __le32 to_me_filt_status; + __le32 reserved2; + __le32 data_len; + u8 payload[]; +}; + +#endif /* __sap_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mei/trace-data.h b/drivers/net/wireless/intel/iwlwifi/mei/trace-data.h new file mode 100644 index 000000000..83639c622 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mei/trace-data.h @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright(c) 2021 Intel Corporation + */ + +#if !defined(CONFIG_IWLWIFI_DEVICE_TRACING) + +#define trace_iwlmei_sap_data(...) + +#else + +#if !defined(__IWLWIFI_DEVICE_TRACE_IWLWIFI_SAP_DATA) || defined(TRACE_HEADER_MULTI_READ) + +#ifndef __IWLWIFI_DEVICE_TRACE_IWLWIFI_SAP_DATA +enum iwl_sap_data_trace_type { + IWL_SAP_RX_DATA_TO_AIR, + IWL_SAP_TX_DATA_FROM_AIR, + IWL_SAP_RX_DATA_DROPPED_FROM_AIR, + IWL_SAP_TX_DHCP, +}; + +static inline size_t +iwlmei_sap_data_offset(enum iwl_sap_data_trace_type trace_type) +{ + switch (trace_type) { + case IWL_SAP_RX_DATA_TO_AIR: + return 0; + case IWL_SAP_TX_DATA_FROM_AIR: + case IWL_SAP_RX_DATA_DROPPED_FROM_AIR: + return sizeof(struct iwl_sap_hdr); + case IWL_SAP_TX_DHCP: + return sizeof(struct iwl_sap_cb_data); + default: + WARN_ON_ONCE(1); + } + + return 0; +} +#endif + +#define __IWLWIFI_DEVICE_TRACE_IWLWIFI_SAP_DATA + +#include <linux/tracepoint.h> +#include <linux/skbuff.h> +#include "sap.h" + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM iwlmei_sap_data + +TRACE_EVENT(iwlmei_sap_data, + TP_PROTO(const struct sk_buff *skb, + enum iwl_sap_data_trace_type trace_type), + TP_ARGS(skb, trace_type), + TP_STRUCT__entry( + __dynamic_array(u8, data, + skb->len - iwlmei_sap_data_offset(trace_type)) + __field(u32, trace_type) + ), + TP_fast_assign( + size_t offset = iwlmei_sap_data_offset(trace_type); + __entry->trace_type = trace_type; + skb_copy_bits(skb, offset, __get_dynamic_array(data), + skb->len - offset); + ), + TP_printk("sap_data:trace_type %d len %d", + __entry->trace_type, __get_dynamic_array_len(data)) +); + +/* + * If you add something here, add a stub in case + * !defined(CONFIG_IWLWIFI_DEVICE_TRACING) + */ + +#endif /* __IWLWIFI_DEVICE_TRACE_IWLWIFI_SAP_DATA */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace-data +#include <trace/define_trace.h> + +#endif /* CONFIG_IWLWIFI_DEVICE_TRACING */ diff --git a/drivers/net/wireless/intel/iwlwifi/mei/trace.c b/drivers/net/wireless/intel/iwlwifi/mei/trace.c new file mode 100644 index 000000000..47ac32ef9 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mei/trace.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021 Intel Corporation + */ + +#include <linux/module.h> + +/* sparse doesn't like tracepoint macros */ +#ifndef __CHECKER__ + +#define CREATE_TRACE_POINTS +#include "trace.h" +#include "trace-data.h" + +#endif /* __CHECKER__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mei/trace.h b/drivers/net/wireless/intel/iwlwifi/mei/trace.h new file mode 100644 index 000000000..45ecb22ec --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mei/trace.h @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright(c) 2021 Intel Corporation + */ + +#if !defined(CONFIG_IWLWIFI_DEVICE_TRACING) + +#define trace_iwlmei_sap_cmd(...) +#define trace_iwlmei_me_msg(...) + +#else + +#if !defined(__IWLWIFI_DEVICE_TRACE_IWLWIFI_SAP_CMD) || defined(TRACE_HEADER_MULTI_READ) +#define __IWLWIFI_DEVICE_TRACE_IWLWIFI_SAP_CMD + +#include <linux/tracepoint.h> + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM iwlmei_sap_cmd + +#include "mei/sap.h" + +TRACE_EVENT(iwlmei_sap_cmd, + TP_PROTO(const struct iwl_sap_hdr *sap_cmd, bool tx), + TP_ARGS(sap_cmd, tx), + TP_STRUCT__entry( + __dynamic_array(u8, cmd, + le16_to_cpu(sap_cmd->len) + sizeof(*sap_cmd)) + __field(u8, tx) + __field(u16, type) + __field(u16, len) + __field(u32, seq) + ), + TP_fast_assign( + memcpy(__get_dynamic_array(cmd), sap_cmd, + le16_to_cpu(sap_cmd->len) + sizeof(*sap_cmd)); + __entry->tx = tx; + __entry->type = le16_to_cpu(sap_cmd->type); + __entry->len = le16_to_cpu(sap_cmd->len); + __entry->seq = le32_to_cpu(sap_cmd->seq_num); + ), + TP_printk("sap_cmd %s: type %d len %d seq %d", __entry->tx ? "Tx" : "Rx", + __entry->type, __entry->len, __entry->seq) +); + +TRACE_EVENT(iwlmei_me_msg, + TP_PROTO(const struct iwl_sap_me_msg_hdr *hdr, bool tx), + TP_ARGS(hdr, tx), + TP_STRUCT__entry( + __field(u8, type) + __field(u8, tx) + __field(u32, seq_num) + ), + TP_fast_assign( + __entry->type = le32_to_cpu(hdr->type); + __entry->seq_num = le32_to_cpu(hdr->seq_num); + __entry->tx = tx; + ), + TP_printk("ME message: %s: type %d seq %d", __entry->tx ? "Tx" : "Rx", + __entry->type, __entry->seq_num) +); + +/* + * If you add something here, add a stub in case + * !defined(CONFIG_IWLWIFI_DEVICE_TRACING) + */ + +#endif /* __IWLWIFI_DEVICE_TRACE_IWLWIFI_SAP_CMD */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace +#include <trace/define_trace.h> + +#endif /* CONFIG_IWLWIFI_DEVICE_TRACING */ diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/Makefile b/drivers/net/wireless/intel/iwlwifi/mvm/Makefile new file mode 100644 index 000000000..11e814b7c --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_IWLMVM) += iwlmvm.o +iwlmvm-y += fw.o mac80211.o nvm.o ops.o phy-ctxt.o mac-ctxt.o +iwlmvm-y += utils.o rx.o rxmq.o tx.o binding.o quota.o sta.o sf.o +iwlmvm-y += scan.o time-event.o rs.o rs-fw.o +iwlmvm-y += power.o coex.o +iwlmvm-y += tt.o offloading.o tdls.o +iwlmvm-y += ftm-responder.o ftm-initiator.o +iwlmvm-y += rfi.o +iwlmvm-$(CONFIG_IWLWIFI_DEBUGFS) += debugfs.o debugfs-vif.o +iwlmvm-$(CONFIG_IWLWIFI_LEDS) += led.o +iwlmvm-$(CONFIG_PM) += d3.o +iwlmvm-$(CONFIG_IWLMEI) += vendor-cmd.o + +ccflags-y += -I $(srctree)/$(src)/../ diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/binding.c b/drivers/net/wireless/intel/iwlwifi/mvm/binding.c new file mode 100644 index 000000000..0aac30630 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/binding.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2020 Intel Corporation + * Copyright (C) 2016 Intel Deutschland GmbH + */ +#include <net/mac80211.h> +#include "fw-api.h" +#include "mvm.h" + +struct iwl_mvm_iface_iterator_data { + struct ieee80211_vif *ignore_vif; + int idx; + + struct iwl_mvm_phy_ctxt *phyctxt; + + u16 ids[MAX_MACS_IN_BINDING]; + u16 colors[MAX_MACS_IN_BINDING]; +}; + +static int iwl_mvm_binding_cmd(struct iwl_mvm *mvm, u32 action, + struct iwl_mvm_iface_iterator_data *data) +{ + struct iwl_binding_cmd cmd; + struct iwl_mvm_phy_ctxt *phyctxt = data->phyctxt; + int i, ret; + u32 status; + int size; + + memset(&cmd, 0, sizeof(cmd)); + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_BINDING_CDB_SUPPORT)) { + size = sizeof(cmd); + cmd.lmac_id = cpu_to_le32(iwl_mvm_get_lmac_id(mvm->fw, + phyctxt->channel->band)); + } else { + size = IWL_BINDING_CMD_SIZE_V1; + } + + cmd.id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(phyctxt->id, + phyctxt->color)); + cmd.action = cpu_to_le32(action); + cmd.phy = cpu_to_le32(FW_CMD_ID_AND_COLOR(phyctxt->id, + phyctxt->color)); + + for (i = 0; i < MAX_MACS_IN_BINDING; i++) + cmd.macs[i] = cpu_to_le32(FW_CTXT_INVALID); + for (i = 0; i < data->idx; i++) + cmd.macs[i] = cpu_to_le32(FW_CMD_ID_AND_COLOR(data->ids[i], + data->colors[i])); + + status = 0; + ret = iwl_mvm_send_cmd_pdu_status(mvm, BINDING_CONTEXT_CMD, + size, &cmd, &status); + if (ret) { + IWL_ERR(mvm, "Failed to send binding (action:%d): %d\n", + action, ret); + return ret; + } + + if (status) { + IWL_ERR(mvm, "Binding command failed: %u\n", status); + ret = -EIO; + } + + return ret; +} + +static void iwl_mvm_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_iface_iterator_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif == data->ignore_vif) + return; + + if (mvmvif->phy_ctxt != data->phyctxt) + return; + + if (WARN_ON_ONCE(data->idx >= MAX_MACS_IN_BINDING)) + return; + + data->ids[data->idx] = mvmvif->id; + data->colors[data->idx] = mvmvif->color; + data->idx++; +} + +static int iwl_mvm_binding_update(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mvm_phy_ctxt *phyctxt, + bool add) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_iface_iterator_data data = { + .ignore_vif = vif, + .phyctxt = phyctxt, + }; + u32 action = FW_CTXT_ACTION_MODIFY; + + lockdep_assert_held(&mvm->mutex); + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_iface_iterator, + &data); + + /* + * If there are no other interfaces yet we + * need to create a new binding. + */ + if (data.idx == 0) { + if (add) + action = FW_CTXT_ACTION_ADD; + else + action = FW_CTXT_ACTION_REMOVE; + } + + if (add) { + if (WARN_ON_ONCE(data.idx >= MAX_MACS_IN_BINDING)) + return -EINVAL; + + data.ids[data.idx] = mvmvif->id; + data.colors[data.idx] = mvmvif->color; + data.idx++; + } + + return iwl_mvm_binding_cmd(mvm, action, &data); +} + +int iwl_mvm_binding_add_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (WARN_ON_ONCE(!mvmvif->phy_ctxt)) + return -EINVAL; + + /* + * Update SF - Disable if needed. if this fails, SF might still be on + * while many macs are bound, which is forbidden - so fail the binding. + */ + if (iwl_mvm_sf_update(mvm, vif, false)) + return -EINVAL; + + return iwl_mvm_binding_update(mvm, vif, mvmvif->phy_ctxt, true); +} + +int iwl_mvm_binding_remove_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + if (WARN_ON_ONCE(!mvmvif->phy_ctxt)) + return -EINVAL; + + ret = iwl_mvm_binding_update(mvm, vif, mvmvif->phy_ctxt, false); + + if (!ret) + if (iwl_mvm_sf_update(mvm, vif, true)) + IWL_ERR(mvm, "Failed to update SF state\n"); + + return ret; +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/coex.c b/drivers/net/wireless/intel/iwlwifi/mvm/coex.c new file mode 100644 index 000000000..ee3c8a786 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/coex.c @@ -0,0 +1,685 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2013-2014, 2018-2020, 2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + */ +#include <linux/ieee80211.h> +#include <linux/etherdevice.h> +#include <net/mac80211.h> + +#include "fw/api/coex.h" +#include "iwl-modparams.h" +#include "mvm.h" +#include "iwl-debug.h" + +/* 20MHz / 40MHz below / 40Mhz above*/ +static const __le64 iwl_ci_mask[][3] = { + /* dummy entry for channel 0 */ + {cpu_to_le64(0), cpu_to_le64(0), cpu_to_le64(0)}, + { + cpu_to_le64(0x0000001FFFULL), + cpu_to_le64(0x0ULL), + cpu_to_le64(0x00007FFFFFULL), + }, + { + cpu_to_le64(0x000000FFFFULL), + cpu_to_le64(0x0ULL), + cpu_to_le64(0x0003FFFFFFULL), + }, + { + cpu_to_le64(0x000003FFFCULL), + cpu_to_le64(0x0ULL), + cpu_to_le64(0x000FFFFFFCULL), + }, + { + cpu_to_le64(0x00001FFFE0ULL), + cpu_to_le64(0x0ULL), + cpu_to_le64(0x007FFFFFE0ULL), + }, + { + cpu_to_le64(0x00007FFF80ULL), + cpu_to_le64(0x00007FFFFFULL), + cpu_to_le64(0x01FFFFFF80ULL), + }, + { + cpu_to_le64(0x0003FFFC00ULL), + cpu_to_le64(0x0003FFFFFFULL), + cpu_to_le64(0x0FFFFFFC00ULL), + }, + { + cpu_to_le64(0x000FFFF000ULL), + cpu_to_le64(0x000FFFFFFCULL), + cpu_to_le64(0x3FFFFFF000ULL), + }, + { + cpu_to_le64(0x007FFF8000ULL), + cpu_to_le64(0x007FFFFFE0ULL), + cpu_to_le64(0xFFFFFF8000ULL), + }, + { + cpu_to_le64(0x01FFFE0000ULL), + cpu_to_le64(0x01FFFFFF80ULL), + cpu_to_le64(0xFFFFFE0000ULL), + }, + { + cpu_to_le64(0x0FFFF00000ULL), + cpu_to_le64(0x0FFFFFFC00ULL), + cpu_to_le64(0x0ULL), + }, + { + cpu_to_le64(0x3FFFC00000ULL), + cpu_to_le64(0x3FFFFFF000ULL), + cpu_to_le64(0x0) + }, + { + cpu_to_le64(0xFFFE000000ULL), + cpu_to_le64(0xFFFFFF8000ULL), + cpu_to_le64(0x0) + }, + { + cpu_to_le64(0xFFF8000000ULL), + cpu_to_le64(0xFFFFFE0000ULL), + cpu_to_le64(0x0) + }, + { + cpu_to_le64(0xFE00000000ULL), + cpu_to_le64(0x0ULL), + cpu_to_le64(0x0ULL) + }, +}; + +static enum iwl_bt_coex_lut_type +iwl_get_coex_type(struct iwl_mvm *mvm, const struct ieee80211_vif *vif) +{ + struct ieee80211_chanctx_conf *chanctx_conf; + enum iwl_bt_coex_lut_type ret; + u16 phy_ctx_id; + u32 primary_ch_phy_id, secondary_ch_phy_id; + + /* + * Checking that we hold mvm->mutex is a good idea, but the rate + * control can't acquire the mutex since it runs in Tx path. + * So this is racy in that case, but in the worst case, the AMPDU + * size limit will be wrong for a short time which is not a big + * issue. + */ + + rcu_read_lock(); + + chanctx_conf = rcu_dereference(vif->bss_conf.chanctx_conf); + + if (!chanctx_conf || + chanctx_conf->def.chan->band != NL80211_BAND_2GHZ) { + rcu_read_unlock(); + return BT_COEX_INVALID_LUT; + } + + ret = BT_COEX_TX_DIS_LUT; + + if (mvm->cfg->bt_shared_single_ant) { + rcu_read_unlock(); + return ret; + } + + phy_ctx_id = *((u16 *)chanctx_conf->drv_priv); + primary_ch_phy_id = le32_to_cpu(mvm->last_bt_ci_cmd.primary_ch_phy_id); + secondary_ch_phy_id = + le32_to_cpu(mvm->last_bt_ci_cmd.secondary_ch_phy_id); + + if (primary_ch_phy_id == phy_ctx_id) + ret = le32_to_cpu(mvm->last_bt_notif.primary_ch_lut); + else if (secondary_ch_phy_id == phy_ctx_id) + ret = le32_to_cpu(mvm->last_bt_notif.secondary_ch_lut); + /* else - default = TX TX disallowed */ + + rcu_read_unlock(); + + return ret; +} + +int iwl_mvm_send_bt_init_conf(struct iwl_mvm *mvm) +{ + struct iwl_bt_coex_cmd bt_cmd = {}; + u32 mode; + + lockdep_assert_held(&mvm->mutex); + + if (unlikely(mvm->bt_force_ant_mode != BT_FORCE_ANT_DIS)) { + switch (mvm->bt_force_ant_mode) { + case BT_FORCE_ANT_BT: + mode = BT_COEX_BT; + break; + case BT_FORCE_ANT_WIFI: + mode = BT_COEX_WIFI; + break; + default: + WARN_ON(1); + mode = 0; + } + + bt_cmd.mode = cpu_to_le32(mode); + goto send_cmd; + } + + bt_cmd.mode = cpu_to_le32(BT_COEX_NW); + + if (IWL_MVM_BT_COEX_SYNC2SCO) + bt_cmd.enabled_modules |= + cpu_to_le32(BT_COEX_SYNC2SCO_ENABLED); + + if (iwl_mvm_is_mplut_supported(mvm)) + bt_cmd.enabled_modules |= cpu_to_le32(BT_COEX_MPLUT_ENABLED); + + bt_cmd.enabled_modules |= cpu_to_le32(BT_COEX_HIGH_BAND_RET); + +send_cmd: + memset(&mvm->last_bt_notif, 0, sizeof(mvm->last_bt_notif)); + memset(&mvm->last_bt_ci_cmd, 0, sizeof(mvm->last_bt_ci_cmd)); + + return iwl_mvm_send_cmd_pdu(mvm, BT_CONFIG, 0, sizeof(bt_cmd), &bt_cmd); +} + +static int iwl_mvm_bt_coex_reduced_txp(struct iwl_mvm *mvm, u8 sta_id, + bool enable) +{ + struct iwl_bt_coex_reduced_txp_update_cmd cmd = {}; + struct iwl_mvm_sta *mvmsta; + u32 value; + + mvmsta = iwl_mvm_sta_from_staid_protected(mvm, sta_id); + if (!mvmsta) + return 0; + + /* nothing to do */ + if (mvmsta->bt_reduced_txpower == enable) + return 0; + + value = mvmsta->sta_id; + + if (enable) + value |= BT_REDUCED_TX_POWER_BIT; + + IWL_DEBUG_COEX(mvm, "%sable reduced Tx Power for sta %d\n", + enable ? "en" : "dis", sta_id); + + cmd.reduced_txp = cpu_to_le32(value); + mvmsta->bt_reduced_txpower = enable; + + return iwl_mvm_send_cmd_pdu(mvm, BT_COEX_UPDATE_REDUCED_TXP, + CMD_ASYNC, sizeof(cmd), &cmd); +} + +struct iwl_bt_iterator_data { + struct iwl_bt_coex_profile_notif *notif; + struct iwl_mvm *mvm; + struct ieee80211_chanctx_conf *primary; + struct ieee80211_chanctx_conf *secondary; + bool primary_ll; + u8 primary_load; + u8 secondary_load; +}; + +static inline +void iwl_mvm_bt_coex_enable_rssi_event(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + bool enable, int rssi) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + mvmvif->bf_data.last_bt_coex_event = rssi; + mvmvif->bf_data.bt_coex_max_thold = + enable ? -IWL_MVM_BT_COEX_EN_RED_TXP_THRESH : 0; + mvmvif->bf_data.bt_coex_min_thold = + enable ? -IWL_MVM_BT_COEX_DIS_RED_TXP_THRESH : 0; +} + +#define MVM_COEX_TCM_PERIOD (HZ * 10) + +static void iwl_mvm_bt_coex_tcm_based_ci(struct iwl_mvm *mvm, + struct iwl_bt_iterator_data *data) +{ + unsigned long now = jiffies; + + if (!time_after(now, mvm->bt_coex_last_tcm_ts + MVM_COEX_TCM_PERIOD)) + return; + + mvm->bt_coex_last_tcm_ts = now; + + /* We assume here that we don't have more than 2 vifs on 2.4GHz */ + + /* if the primary is low latency, it will stay primary */ + if (data->primary_ll) + return; + + if (data->primary_load >= data->secondary_load) + return; + + swap(data->primary, data->secondary); +} + +/* must be called under rcu_read_lock */ +static void iwl_mvm_bt_notif_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_bt_iterator_data *data = _data; + struct iwl_mvm *mvm = data->mvm; + struct ieee80211_chanctx_conf *chanctx_conf; + /* default smps_mode is AUTOMATIC - only used for client modes */ + enum ieee80211_smps_mode smps_mode = IEEE80211_SMPS_AUTOMATIC; + u32 bt_activity_grading, min_ag_for_static_smps; + int ave_rssi; + + lockdep_assert_held(&mvm->mutex); + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + break; + case NL80211_IFTYPE_AP: + if (!mvmvif->ap_ibss_active) + return; + break; + default: + return; + } + + chanctx_conf = rcu_dereference(vif->bss_conf.chanctx_conf); + + /* If channel context is invalid or not on 2.4GHz .. */ + if ((!chanctx_conf || + chanctx_conf->def.chan->band != NL80211_BAND_2GHZ)) { + if (vif->type == NL80211_IFTYPE_STATION) { + /* ... relax constraints and disable rssi events */ + iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_BT_COEX, + smps_mode); + iwl_mvm_bt_coex_reduced_txp(mvm, mvmvif->ap_sta_id, + false); + iwl_mvm_bt_coex_enable_rssi_event(mvm, vif, false, 0); + } + return; + } + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_COEX_SCHEMA_2)) + min_ag_for_static_smps = BT_VERY_HIGH_TRAFFIC; + else + min_ag_for_static_smps = BT_HIGH_TRAFFIC; + + bt_activity_grading = le32_to_cpu(data->notif->bt_activity_grading); + if (bt_activity_grading >= min_ag_for_static_smps) + smps_mode = IEEE80211_SMPS_STATIC; + else if (bt_activity_grading >= BT_LOW_TRAFFIC) + smps_mode = IEEE80211_SMPS_DYNAMIC; + + /* relax SMPS constraints for next association */ + if (!vif->cfg.assoc) + smps_mode = IEEE80211_SMPS_AUTOMATIC; + + if (mvmvif->phy_ctxt && + (mvm->last_bt_notif.rrc_status & BIT(mvmvif->phy_ctxt->id))) + smps_mode = IEEE80211_SMPS_AUTOMATIC; + + IWL_DEBUG_COEX(data->mvm, + "mac %d: bt_activity_grading %d smps_req %d\n", + mvmvif->id, bt_activity_grading, smps_mode); + + if (vif->type == NL80211_IFTYPE_STATION) + iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_BT_COEX, + smps_mode); + + /* low latency is always primary */ + if (iwl_mvm_vif_low_latency(mvmvif)) { + data->primary_ll = true; + + data->secondary = data->primary; + data->primary = chanctx_conf; + } + + if (vif->type == NL80211_IFTYPE_AP) { + if (!mvmvif->ap_ibss_active) + return; + + if (chanctx_conf == data->primary) + return; + + if (!data->primary_ll) { + /* + * downgrade the current primary no matter what its + * type is. + */ + data->secondary = data->primary; + data->primary = chanctx_conf; + } else { + /* there is low latency vif - we will be secondary */ + data->secondary = chanctx_conf; + } + + if (data->primary == chanctx_conf) + data->primary_load = mvm->tcm.result.load[mvmvif->id]; + else if (data->secondary == chanctx_conf) + data->secondary_load = mvm->tcm.result.load[mvmvif->id]; + return; + } + + /* + * STA / P2P Client, try to be primary if first vif. If we are in low + * latency mode, we are already in primary and just don't do much + */ + if (!data->primary || data->primary == chanctx_conf) + data->primary = chanctx_conf; + else if (!data->secondary) + /* if secondary is not NULL, it might be a GO */ + data->secondary = chanctx_conf; + + if (data->primary == chanctx_conf) + data->primary_load = mvm->tcm.result.load[mvmvif->id]; + else if (data->secondary == chanctx_conf) + data->secondary_load = mvm->tcm.result.load[mvmvif->id]; + /* + * don't reduce the Tx power if one of these is true: + * we are in LOOSE + * single share antenna product + * BT is inactive + * we are not associated + */ + if (iwl_get_coex_type(mvm, vif) == BT_COEX_LOOSE_LUT || + mvm->cfg->bt_shared_single_ant || !vif->cfg.assoc || + le32_to_cpu(mvm->last_bt_notif.bt_activity_grading) == BT_OFF) { + iwl_mvm_bt_coex_reduced_txp(mvm, mvmvif->ap_sta_id, false); + iwl_mvm_bt_coex_enable_rssi_event(mvm, vif, false, 0); + return; + } + + /* try to get the avg rssi from fw */ + ave_rssi = mvmvif->bf_data.ave_beacon_signal; + + /* if the RSSI isn't valid, fake it is very low */ + if (!ave_rssi) + ave_rssi = -100; + if (ave_rssi > -IWL_MVM_BT_COEX_EN_RED_TXP_THRESH) { + if (iwl_mvm_bt_coex_reduced_txp(mvm, mvmvif->ap_sta_id, true)) + IWL_ERR(mvm, "Couldn't send BT_CONFIG cmd\n"); + } else if (ave_rssi < -IWL_MVM_BT_COEX_DIS_RED_TXP_THRESH) { + if (iwl_mvm_bt_coex_reduced_txp(mvm, mvmvif->ap_sta_id, false)) + IWL_ERR(mvm, "Couldn't send BT_CONFIG cmd\n"); + } + + /* Begin to monitor the RSSI: it may influence the reduced Tx power */ + iwl_mvm_bt_coex_enable_rssi_event(mvm, vif, true, ave_rssi); +} + +static void iwl_mvm_bt_coex_notif_handle(struct iwl_mvm *mvm) +{ + struct iwl_bt_iterator_data data = { + .mvm = mvm, + .notif = &mvm->last_bt_notif, + }; + struct iwl_bt_coex_ci_cmd cmd = {}; + u8 ci_bw_idx; + + /* Ignore updates if we are in force mode */ + if (unlikely(mvm->bt_force_ant_mode != BT_FORCE_ANT_DIS)) + return; + + rcu_read_lock(); + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_bt_notif_iterator, &data); + + iwl_mvm_bt_coex_tcm_based_ci(mvm, &data); + + if (data.primary) { + struct ieee80211_chanctx_conf *chan = data.primary; + if (WARN_ON(!chan->def.chan)) { + rcu_read_unlock(); + return; + } + + if (chan->def.width < NL80211_CHAN_WIDTH_40) { + ci_bw_idx = 0; + } else { + if (chan->def.center_freq1 > + chan->def.chan->center_freq) + ci_bw_idx = 2; + else + ci_bw_idx = 1; + } + + cmd.bt_primary_ci = + iwl_ci_mask[chan->def.chan->hw_value][ci_bw_idx]; + cmd.primary_ch_phy_id = + cpu_to_le32(*((u16 *)data.primary->drv_priv)); + } + + if (data.secondary) { + struct ieee80211_chanctx_conf *chan = data.secondary; + if (WARN_ON(!data.secondary->def.chan)) { + rcu_read_unlock(); + return; + } + + if (chan->def.width < NL80211_CHAN_WIDTH_40) { + ci_bw_idx = 0; + } else { + if (chan->def.center_freq1 > + chan->def.chan->center_freq) + ci_bw_idx = 2; + else + ci_bw_idx = 1; + } + + cmd.bt_secondary_ci = + iwl_ci_mask[chan->def.chan->hw_value][ci_bw_idx]; + cmd.secondary_ch_phy_id = + cpu_to_le32(*((u16 *)data.secondary->drv_priv)); + } + + rcu_read_unlock(); + + /* Don't spam the fw with the same command over and over */ + if (memcmp(&cmd, &mvm->last_bt_ci_cmd, sizeof(cmd))) { + if (iwl_mvm_send_cmd_pdu(mvm, BT_COEX_CI, 0, + sizeof(cmd), &cmd)) + IWL_ERR(mvm, "Failed to send BT_CI cmd\n"); + memcpy(&mvm->last_bt_ci_cmd, &cmd, sizeof(cmd)); + } +} + +void iwl_mvm_rx_bt_coex_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_bt_coex_profile_notif *notif = (void *)pkt->data; + + IWL_DEBUG_COEX(mvm, "BT Coex Notification received\n"); + IWL_DEBUG_COEX(mvm, "\tBT ci compliance %d\n", notif->bt_ci_compliance); + IWL_DEBUG_COEX(mvm, "\tBT primary_ch_lut %d\n", + le32_to_cpu(notif->primary_ch_lut)); + IWL_DEBUG_COEX(mvm, "\tBT secondary_ch_lut %d\n", + le32_to_cpu(notif->secondary_ch_lut)); + IWL_DEBUG_COEX(mvm, "\tBT activity grading %d\n", + le32_to_cpu(notif->bt_activity_grading)); + + /* remember this notification for future use: rssi fluctuations */ + memcpy(&mvm->last_bt_notif, notif, sizeof(mvm->last_bt_notif)); + + iwl_mvm_bt_coex_notif_handle(mvm); +} + +void iwl_mvm_bt_rssi_event(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + enum ieee80211_rssi_event_data rssi_event) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + lockdep_assert_held(&mvm->mutex); + + /* Ignore updates if we are in force mode */ + if (unlikely(mvm->bt_force_ant_mode != BT_FORCE_ANT_DIS)) + return; + + /* + * Rssi update while not associated - can happen since the statistics + * are handled asynchronously + */ + if (mvmvif->ap_sta_id == IWL_MVM_INVALID_STA) + return; + + /* No BT - reports should be disabled */ + if (le32_to_cpu(mvm->last_bt_notif.bt_activity_grading) == BT_OFF) + return; + + IWL_DEBUG_COEX(mvm, "RSSI for %pM is now %s\n", vif->bss_conf.bssid, + rssi_event == RSSI_EVENT_HIGH ? "HIGH" : "LOW"); + + /* + * Check if rssi is good enough for reduced Tx power, but not in loose + * scheme. + */ + if (rssi_event == RSSI_EVENT_LOW || mvm->cfg->bt_shared_single_ant || + iwl_get_coex_type(mvm, vif) == BT_COEX_LOOSE_LUT) + ret = iwl_mvm_bt_coex_reduced_txp(mvm, mvmvif->ap_sta_id, + false); + else + ret = iwl_mvm_bt_coex_reduced_txp(mvm, mvmvif->ap_sta_id, true); + + if (ret) + IWL_ERR(mvm, "couldn't send BT_CONFIG HCMD upon RSSI event\n"); +} + +#define LINK_QUAL_AGG_TIME_LIMIT_DEF (4000) +#define LINK_QUAL_AGG_TIME_LIMIT_BT_ACT (1200) + +u16 iwl_mvm_coex_agg_time_limit(struct iwl_mvm *mvm, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(mvmsta->vif); + struct iwl_mvm_phy_ctxt *phy_ctxt = mvmvif->phy_ctxt; + enum iwl_bt_coex_lut_type lut_type; + + if (mvm->last_bt_notif.ttc_status & BIT(phy_ctxt->id)) + return LINK_QUAL_AGG_TIME_LIMIT_DEF; + + if (le32_to_cpu(mvm->last_bt_notif.bt_activity_grading) < + BT_HIGH_TRAFFIC) + return LINK_QUAL_AGG_TIME_LIMIT_DEF; + + lut_type = iwl_get_coex_type(mvm, mvmsta->vif); + + if (lut_type == BT_COEX_LOOSE_LUT || lut_type == BT_COEX_INVALID_LUT) + return LINK_QUAL_AGG_TIME_LIMIT_DEF; + + /* tight coex, high bt traffic, reduce AGG time limit */ + return LINK_QUAL_AGG_TIME_LIMIT_BT_ACT; +} + +bool iwl_mvm_bt_coex_is_mimo_allowed(struct iwl_mvm *mvm, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(mvmsta->vif); + struct iwl_mvm_phy_ctxt *phy_ctxt = mvmvif->phy_ctxt; + enum iwl_bt_coex_lut_type lut_type; + + if (mvm->last_bt_notif.ttc_status & BIT(phy_ctxt->id)) + return true; + + if (le32_to_cpu(mvm->last_bt_notif.bt_activity_grading) < + BT_HIGH_TRAFFIC) + return true; + + /* + * In Tight / TxTxDis, BT can't Rx while we Tx, so use both antennas + * since BT is already killed. + * In Loose, BT can Rx while we Tx, so forbid MIMO to let BT Rx while + * we Tx. + * When we are in 5GHz, we'll get BT_COEX_INVALID_LUT allowing MIMO. + */ + lut_type = iwl_get_coex_type(mvm, mvmsta->vif); + return lut_type != BT_COEX_LOOSE_LUT; +} + +bool iwl_mvm_bt_coex_is_ant_avail(struct iwl_mvm *mvm, u8 ant) +{ + /* there is no other antenna, shared antenna is always available */ + if (mvm->cfg->bt_shared_single_ant) + return true; + + if (ant & mvm->cfg->non_shared_ant) + return true; + + return le32_to_cpu(mvm->last_bt_notif.bt_activity_grading) < + BT_HIGH_TRAFFIC; +} + +bool iwl_mvm_bt_coex_is_shared_ant_avail(struct iwl_mvm *mvm) +{ + /* there is no other antenna, shared antenna is always available */ + if (mvm->cfg->bt_shared_single_ant) + return true; + + return le32_to_cpu(mvm->last_bt_notif.bt_activity_grading) < BT_HIGH_TRAFFIC; +} + +bool iwl_mvm_bt_coex_is_tpc_allowed(struct iwl_mvm *mvm, + enum nl80211_band band) +{ + u32 bt_activity = le32_to_cpu(mvm->last_bt_notif.bt_activity_grading); + + if (band != NL80211_BAND_2GHZ) + return false; + + return bt_activity >= BT_LOW_TRAFFIC; +} + +u8 iwl_mvm_bt_coex_get_single_ant_msk(struct iwl_mvm *mvm, u8 enabled_ants) +{ + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_COEX_SCHEMA_2) && + (mvm->cfg->non_shared_ant & enabled_ants)) + return mvm->cfg->non_shared_ant; + + return first_antenna(enabled_ants); +} + +u8 iwl_mvm_bt_coex_tx_prio(struct iwl_mvm *mvm, struct ieee80211_hdr *hdr, + struct ieee80211_tx_info *info, u8 ac) +{ + __le16 fc = hdr->frame_control; + bool mplut_enabled = iwl_mvm_is_mplut_supported(mvm); + + if (info->band != NL80211_BAND_2GHZ) + return 0; + + if (unlikely(mvm->bt_tx_prio)) + return mvm->bt_tx_prio - 1; + + if (likely(ieee80211_is_data(fc))) { + if (likely(ieee80211_is_data_qos(fc))) { + switch (ac) { + case IEEE80211_AC_BE: + return mplut_enabled ? 1 : 0; + case IEEE80211_AC_VI: + return mplut_enabled ? 2 : 3; + case IEEE80211_AC_VO: + return 3; + default: + return 0; + } + } else if (is_multicast_ether_addr(hdr->addr1)) { + return 3; + } else + return 0; + } else if (ieee80211_is_mgmt(fc)) { + return ieee80211_is_disassoc(fc) ? 0 : 3; + } else if (ieee80211_is_ctl(fc)) { + /* ignore cfend and cfendack frames as we never send those */ + return 3; + } + + return 0; +} + +void iwl_mvm_bt_coex_vif_change(struct iwl_mvm *mvm) +{ + iwl_mvm_bt_coex_notif_handle(mvm); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/constants.h b/drivers/net/wireless/intel/iwlwifi/mvm/constants.h new file mode 100644 index 000000000..c604f9f39 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/constants.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2013-2014, 2018-2021 Intel Corporation + * Copyright (C) 2015 Intel Deutschland GmbH + */ +#ifndef __MVM_CONSTANTS_H +#define __MVM_CONSTANTS_H + +#include <linux/ieee80211.h> +#include "fw-api.h" + +#define IWL_MVM_UAPSD_NOAGG_BSSIDS_NUM 20 + +#define IWL_MVM_DEFAULT_PS_TX_DATA_TIMEOUT (100 * USEC_PER_MSEC) +#define IWL_MVM_DEFAULT_PS_RX_DATA_TIMEOUT (100 * USEC_PER_MSEC) +#define IWL_MVM_WOWLAN_PS_TX_DATA_TIMEOUT (10 * USEC_PER_MSEC) +#define IWL_MVM_WOWLAN_PS_RX_DATA_TIMEOUT (10 * USEC_PER_MSEC) +#define IWL_MVM_SHORT_PS_TX_DATA_TIMEOUT (2 * 1024) /* defined in TU */ +#define IWL_MVM_SHORT_PS_RX_DATA_TIMEOUT (40 * 1024) /* defined in TU */ +#define IWL_MVM_P2P_LOWLATENCY_PS_ENABLE 0 +#define IWL_MVM_UAPSD_RX_DATA_TIMEOUT (50 * USEC_PER_MSEC) +#define IWL_MVM_UAPSD_TX_DATA_TIMEOUT (50 * USEC_PER_MSEC) +#define IWL_MVM_UAPSD_QUEUES (IEEE80211_WMM_IE_STA_QOSINFO_AC_VO |\ + IEEE80211_WMM_IE_STA_QOSINFO_AC_VI |\ + IEEE80211_WMM_IE_STA_QOSINFO_AC_BK |\ + IEEE80211_WMM_IE_STA_QOSINFO_AC_BE) +#define IWL_MVM_PS_HEAVY_TX_THLD_PACKETS 20 +#define IWL_MVM_PS_HEAVY_RX_THLD_PACKETS 8 +#define IWL_MVM_PS_SNOOZE_HEAVY_TX_THLD_PACKETS 30 +#define IWL_MVM_PS_SNOOZE_HEAVY_RX_THLD_PACKETS 20 +#define IWL_MVM_PS_HEAVY_TX_THLD_PERCENT 50 +#define IWL_MVM_PS_HEAVY_RX_THLD_PERCENT 50 +#define IWL_MVM_PS_SNOOZE_INTERVAL 25 +#define IWL_MVM_PS_SNOOZE_WINDOW 50 +#define IWL_MVM_WOWLAN_PS_SNOOZE_WINDOW 25 +#define IWL_MVM_LOWLAT_QUOTA_MIN_PERCENT 64 +#define IWL_MVM_BT_COEX_EN_RED_TXP_THRESH 62 +#define IWL_MVM_BT_COEX_DIS_RED_TXP_THRESH 65 +#define IWL_MVM_BT_COEX_SYNC2SCO 1 +#define IWL_MVM_BT_COEX_MPLUT 1 +#define IWL_MVM_BT_COEX_RRC 1 +#define IWL_MVM_BT_COEX_TTC 1 +#define IWL_MVM_BT_COEX_MPLUT_REG0 0x22002200 +#define IWL_MVM_BT_COEX_MPLUT_REG1 0x11118451 +#define IWL_MVM_BT_COEX_ANTENNA_COUPLING_THRS 30 +#define IWL_MVM_FW_MCAST_FILTER_PASS_ALL 0 +#define IWL_MVM_FW_BCAST_FILTER_PASS_ALL 0 +#define IWL_MVM_QUOTA_THRESHOLD 4 +#define IWL_MVM_RS_RSSI_BASED_INIT_RATE 0 +#define IWL_MVM_RS_80_20_FAR_RANGE_TWEAK 1 +#define IWL_MVM_TOF_IS_RESPONDER 0 +#define IWL_MVM_HW_CSUM_DISABLE 0 +#define IWL_MVM_PARSE_NVM 0 +#define IWL_MVM_ADWELL_ENABLE 1 +#define IWL_MVM_ADWELL_MAX_BUDGET 0 +#define IWL_MVM_TCM_LOAD_MEDIUM_THRESH 10 /* percentage */ +#define IWL_MVM_TCM_LOAD_HIGH_THRESH 50 /* percentage */ +#define IWL_MVM_TCM_LOWLAT_ENABLE_THRESH 100 /* packets/10 seconds */ +#define IWL_MVM_UAPSD_NONAGG_PERIOD 5000 /* msecs */ +#define IWL_MVM_UAPSD_NOAGG_LIST_LEN IWL_MVM_UAPSD_NOAGG_BSSIDS_NUM +#define IWL_MVM_NON_TRANSMITTING_AP 0 +#define IWL_MVM_RS_NUM_TRY_BEFORE_ANT_TOGGLE 1 +#define IWL_MVM_RS_HT_VHT_RETRIES_PER_RATE 2 +#define IWL_MVM_RS_HT_VHT_RETRIES_PER_RATE_TW 1 +#define IWL_MVM_RS_INITIAL_MIMO_NUM_RATES 3 +#define IWL_MVM_RS_INITIAL_SISO_NUM_RATES 3 +#define IWL_MVM_RS_INITIAL_LEGACY_NUM_RATES 2 +#define IWL_MVM_RS_INITIAL_LEGACY_RETRIES 2 +#define IWL_MVM_RS_SECONDARY_LEGACY_RETRIES 1 +#define IWL_MVM_RS_SECONDARY_LEGACY_NUM_RATES 16 +#define IWL_MVM_RS_SECONDARY_SISO_NUM_RATES 3 +#define IWL_MVM_RS_SECONDARY_SISO_RETRIES 1 +#define IWL_MVM_RS_RATE_MIN_FAILURE_TH 3 +#define IWL_MVM_RS_RATE_MIN_SUCCESS_TH 8 +#define IWL_MVM_RS_STAY_IN_COLUMN_TIMEOUT 5 /* Seconds */ +#define IWL_MVM_RS_IDLE_TIMEOUT 5 /* Seconds */ +#define IWL_MVM_RS_MISSED_RATE_MAX 15 +#define IWL_MVM_RS_LEGACY_FAILURE_LIMIT 160 +#define IWL_MVM_RS_LEGACY_SUCCESS_LIMIT 480 +#define IWL_MVM_RS_LEGACY_TABLE_COUNT 160 +#define IWL_MVM_RS_NON_LEGACY_FAILURE_LIMIT 400 +#define IWL_MVM_RS_NON_LEGACY_SUCCESS_LIMIT 4500 +#define IWL_MVM_RS_NON_LEGACY_TABLE_COUNT 1500 +#define IWL_MVM_RS_SR_FORCE_DECREASE 15 /* percent */ +#define IWL_MVM_RS_SR_NO_DECREASE 85 /* percent */ +#define IWL_MVM_RS_AGG_TIME_LIMIT 4000 /* 4 msecs. valid 100-8000 */ +#define IWL_MVM_RS_AGG_DISABLE_START 3 +#define IWL_MVM_RS_AGG_START_THRESHOLD 10 /* num frames per second */ +#define IWL_MVM_RS_TPC_SR_FORCE_INCREASE 75 /* percent */ +#define IWL_MVM_RS_TPC_SR_NO_INCREASE 85 /* percent */ +#define IWL_MVM_RS_TPC_TX_POWER_STEP 3 +#define IWL_MVM_ENABLE_EBS 1 +#define IWL_MVM_FTM_INITIATOR_ALGO IWL_TOF_ALGO_TYPE_MAX_LIKE +#define IWL_MVM_FTM_INITIATOR_DYNACK true +#define IWL_MVM_FTM_LMR_FEEDBACK_TERMINATE false +#define IWL_MVM_FTM_R2I_MAX_REP 7 +#define IWL_MVM_FTM_I2R_MAX_REP 7 +#define IWL_MVM_FTM_R2I_MAX_STS 1 +#define IWL_MVM_FTM_I2R_MAX_STS 1 +#define IWL_MVM_FTM_R2I_MAX_TOTAL_LTF 3 +#define IWL_MVM_FTM_I2R_MAX_TOTAL_LTF 3 +#define IWL_MVM_FTM_INITIATOR_SECURE_LTF false +#define IWL_MVM_FTM_RESP_NDP_SUPPORT true +#define IWL_MVM_FTM_RESP_LMR_FEEDBACK_SUPPORT true +#define IWL_MVM_FTM_NON_TB_MIN_TIME_BETWEEN_MSR 5 +#define IWL_MVM_FTM_NON_TB_MAX_TIME_BETWEEN_MSR 1000 +#define IWL_MVM_D3_DEBUG false +#define IWL_MVM_USE_TWT true +#define IWL_MVM_AMPDU_CONSEC_DROPS_DELBA 20 +#define IWL_MVM_USE_NSSN_SYNC 0 +#define IWL_MVM_PHY_FILTER_CHAIN_A 0 +#define IWL_MVM_PHY_FILTER_CHAIN_B 0 +#define IWL_MVM_PHY_FILTER_CHAIN_C 0 +#define IWL_MVM_PHY_FILTER_CHAIN_D 0 +#define IWL_MVM_FTM_INITIATOR_ENABLE_SMOOTH false +#define IWL_MVM_FTM_INITIATOR_SMOOTH_ALPHA 40 +/* 20016 pSec is 6 meter RTT, meaning 3 meter range */ +#define IWL_MVM_FTM_INITIATOR_SMOOTH_UNDERSHOOT 20016 +#define IWL_MVM_FTM_INITIATOR_SMOOTH_OVERSHOOT 20016 +#define IWL_MVM_FTM_INITIATOR_SMOOTH_AGE_SEC 2 +#define IWL_MVM_DISABLE_AP_FILS false +#define IWL_MVM_6GHZ_PASSIVE_SCAN_TIMEOUT 3000 /* in seconds */ +#define IWL_MVM_6GHZ_PASSIVE_SCAN_ASSOC_TIMEOUT 60 /* in seconds */ + +#endif /* __MVM_CONSTANTS_H */ diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c new file mode 100644 index 000000000..2748459d1 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c @@ -0,0 +1,3155 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include <linux/etherdevice.h> +#include <linux/ip.h> +#include <linux/fs.h> +#include <net/cfg80211.h> +#include <net/ipv6.h> +#include <net/tcp.h> +#include <net/addrconf.h> +#include "iwl-modparams.h" +#include "fw-api.h" +#include "mvm.h" +#include "fw/img.h" + +void iwl_mvm_set_rekey_data(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_gtk_rekey_data *data) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + mutex_lock(&mvm->mutex); + + mvmvif->rekey_data.kek_len = data->kek_len; + mvmvif->rekey_data.kck_len = data->kck_len; + memcpy(mvmvif->rekey_data.kek, data->kek, data->kek_len); + memcpy(mvmvif->rekey_data.kck, data->kck, data->kck_len); + mvmvif->rekey_data.akm = data->akm & 0xFF; + mvmvif->rekey_data.replay_ctr = + cpu_to_le64(be64_to_cpup((const __be64 *)data->replay_ctr)); + mvmvif->rekey_data.valid = true; + + mutex_unlock(&mvm->mutex); +} + +#if IS_ENABLED(CONFIG_IPV6) +void iwl_mvm_ipv6_addr_change(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct inet6_dev *idev) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct inet6_ifaddr *ifa; + int idx = 0; + + memset(mvmvif->tentative_addrs, 0, sizeof(mvmvif->tentative_addrs)); + + read_lock_bh(&idev->lock); + list_for_each_entry(ifa, &idev->addr_list, if_list) { + mvmvif->target_ipv6_addrs[idx] = ifa->addr; + if (ifa->flags & IFA_F_TENTATIVE) + __set_bit(idx, mvmvif->tentative_addrs); + idx++; + if (idx >= IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_MAX) + break; + } + read_unlock_bh(&idev->lock); + + mvmvif->num_target_ipv6_addrs = idx; +} +#endif + +void iwl_mvm_set_default_unicast_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, int idx) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + mvmvif->tx_key_idx = idx; +} + +static void iwl_mvm_convert_p1k(u16 *p1k, __le16 *out) +{ + int i; + + for (i = 0; i < IWL_P1K_SIZE; i++) + out[i] = cpu_to_le16(p1k[i]); +} + +static const u8 *iwl_mvm_find_max_pn(struct ieee80211_key_conf *key, + struct iwl_mvm_key_pn *ptk_pn, + struct ieee80211_key_seq *seq, + int tid, int queues) +{ + const u8 *ret = seq->ccmp.pn; + int i; + + /* get the PN from mac80211, used on the default queue */ + ieee80211_get_key_rx_seq(key, tid, seq); + + /* and use the internal data for the other queues */ + for (i = 1; i < queues; i++) { + const u8 *tmp = ptk_pn->q[i].pn[tid]; + + if (memcmp(ret, tmp, IEEE80211_CCMP_PN_LEN) <= 0) + ret = tmp; + } + + return ret; +} + +struct wowlan_key_reprogram_data { + bool error; + int wep_key_idx; +}; + +static void iwl_mvm_wowlan_program_keys(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, + void *_data) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct wowlan_key_reprogram_data *data = _data; + int ret; + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: { /* hack it for now */ + struct { + struct iwl_mvm_wep_key_cmd wep_key_cmd; + struct iwl_mvm_wep_key wep_key; + } __packed wkc = { + .wep_key_cmd.mac_id_n_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)), + .wep_key_cmd.num_keys = 1, + /* firmware sets STA_KEY_FLG_WEP_13BYTES */ + .wep_key_cmd.decryption_type = STA_KEY_FLG_WEP, + .wep_key.key_index = key->keyidx, + .wep_key.key_size = key->keylen, + }; + + /* + * This will fail -- the key functions don't set support + * pairwise WEP keys. However, that's better than silently + * failing WoWLAN. Or maybe not? + */ + if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) + break; + + memcpy(&wkc.wep_key.key[3], key->key, key->keylen); + if (key->keyidx == mvmvif->tx_key_idx) { + /* TX key must be at offset 0 */ + wkc.wep_key.key_offset = 0; + } else { + /* others start at 1 */ + data->wep_key_idx++; + wkc.wep_key.key_offset = data->wep_key_idx; + } + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_send_cmd_pdu(mvm, WEP_KEY, 0, sizeof(wkc), &wkc); + data->error = ret != 0; + + mvm->ptk_ivlen = key->iv_len; + mvm->ptk_icvlen = key->icv_len; + mvm->gtk_ivlen = key->iv_len; + mvm->gtk_icvlen = key->icv_len; + mutex_unlock(&mvm->mutex); + + /* don't upload key again */ + return; + } + default: + data->error = true; + return; + case WLAN_CIPHER_SUITE_BIP_GMAC_256: + case WLAN_CIPHER_SUITE_BIP_GMAC_128: + return; + case WLAN_CIPHER_SUITE_AES_CMAC: + /* + * Ignore CMAC keys -- the WoWLAN firmware doesn't support them + * but we also shouldn't abort suspend due to that. It does have + * support for the IGTK key renewal, but doesn't really use the + * IGTK for anything. This means we could spuriously wake up or + * be deauthenticated, but that was considered acceptable. + */ + return; + case WLAN_CIPHER_SUITE_TKIP: + case WLAN_CIPHER_SUITE_CCMP: + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_GCMP_256: + break; + } + + mutex_lock(&mvm->mutex); + /* + * The D3 firmware hardcodes the key offset 0 as the key it + * uses to transmit packets to the AP, i.e. the PTK. + */ + if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) { + mvm->ptk_ivlen = key->iv_len; + mvm->ptk_icvlen = key->icv_len; + ret = iwl_mvm_set_sta_key(mvm, vif, sta, key, 0); + } else { + /* + * firmware only supports TSC/RSC for a single key, + * so if there are multiple keep overwriting them + * with new ones -- this relies on mac80211 doing + * list_add_tail(). + */ + mvm->gtk_ivlen = key->iv_len; + mvm->gtk_icvlen = key->icv_len; + ret = iwl_mvm_set_sta_key(mvm, vif, sta, key, 1); + } + mutex_unlock(&mvm->mutex); + data->error = ret != 0; +} + +struct wowlan_key_rsc_tsc_data { + struct iwl_wowlan_rsc_tsc_params_cmd_v4 *rsc_tsc; + bool have_rsc_tsc; +}; + +static void iwl_mvm_wowlan_get_rsc_tsc_data(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, + void *_data) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct wowlan_key_rsc_tsc_data *data = _data; + struct aes_sc *aes_sc; + struct tkip_sc *tkip_sc, *tkip_tx_sc = NULL; + struct ieee80211_key_seq seq; + int i; + + switch (key->cipher) { + default: + break; + case WLAN_CIPHER_SUITE_TKIP: + if (sta) { + u64 pn64; + + tkip_sc = + data->rsc_tsc->params.all_tsc_rsc.tkip.unicast_rsc; + tkip_tx_sc = + &data->rsc_tsc->params.all_tsc_rsc.tkip.tsc; + + pn64 = atomic64_read(&key->tx_pn); + tkip_tx_sc->iv16 = cpu_to_le16(TKIP_PN_TO_IV16(pn64)); + tkip_tx_sc->iv32 = cpu_to_le32(TKIP_PN_TO_IV32(pn64)); + } else { + tkip_sc = + data->rsc_tsc->params.all_tsc_rsc.tkip.multicast_rsc; + } + + /* + * For non-QoS this relies on the fact that both the uCode and + * mac80211 use TID 0 (as they need to to avoid replay attacks) + * for checking the IV in the frames. + */ + for (i = 0; i < IWL_NUM_RSC; i++) { + ieee80211_get_key_rx_seq(key, i, &seq); + tkip_sc[i].iv16 = cpu_to_le16(seq.tkip.iv16); + tkip_sc[i].iv32 = cpu_to_le32(seq.tkip.iv32); + } + + data->have_rsc_tsc = true; + break; + case WLAN_CIPHER_SUITE_CCMP: + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_GCMP_256: + if (sta) { + struct aes_sc *aes_tx_sc; + u64 pn64; + + aes_sc = + data->rsc_tsc->params.all_tsc_rsc.aes.unicast_rsc; + aes_tx_sc = + &data->rsc_tsc->params.all_tsc_rsc.aes.tsc; + + pn64 = atomic64_read(&key->tx_pn); + aes_tx_sc->pn = cpu_to_le64(pn64); + } else { + aes_sc = + data->rsc_tsc->params.all_tsc_rsc.aes.multicast_rsc; + } + + /* + * For non-QoS this relies on the fact that both the uCode and + * mac80211/our RX code use TID 0 for checking the PN. + */ + if (sta && iwl_mvm_has_new_rx_api(mvm)) { + struct iwl_mvm_sta *mvmsta; + struct iwl_mvm_key_pn *ptk_pn; + const u8 *pn; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + rcu_read_lock(); + ptk_pn = rcu_dereference(mvmsta->ptk_pn[key->keyidx]); + if (WARN_ON(!ptk_pn)) { + rcu_read_unlock(); + break; + } + + for (i = 0; i < IWL_MAX_TID_COUNT; i++) { + pn = iwl_mvm_find_max_pn(key, ptk_pn, &seq, i, + mvm->trans->num_rx_queues); + aes_sc[i].pn = cpu_to_le64((u64)pn[5] | + ((u64)pn[4] << 8) | + ((u64)pn[3] << 16) | + ((u64)pn[2] << 24) | + ((u64)pn[1] << 32) | + ((u64)pn[0] << 40)); + } + + rcu_read_unlock(); + } else { + for (i = 0; i < IWL_NUM_RSC; i++) { + u8 *pn = seq.ccmp.pn; + + ieee80211_get_key_rx_seq(key, i, &seq); + aes_sc[i].pn = cpu_to_le64((u64)pn[5] | + ((u64)pn[4] << 8) | + ((u64)pn[3] << 16) | + ((u64)pn[2] << 24) | + ((u64)pn[1] << 32) | + ((u64)pn[0] << 40)); + } + } + data->have_rsc_tsc = true; + break; + } +} + +struct wowlan_key_rsc_v5_data { + struct iwl_wowlan_rsc_tsc_params_cmd *rsc; + bool have_rsc; + int gtks; + int gtk_ids[4]; +}; + +static void iwl_mvm_wowlan_get_rsc_v5_data(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, + void *_data) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct wowlan_key_rsc_v5_data *data = _data; + struct ieee80211_key_seq seq; + __le64 *rsc; + int i; + + /* only for ciphers that can be PTK/GTK */ + switch (key->cipher) { + default: + return; + case WLAN_CIPHER_SUITE_TKIP: + case WLAN_CIPHER_SUITE_CCMP: + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_GCMP_256: + break; + } + + if (sta) { + rsc = data->rsc->ucast_rsc; + } else { + if (WARN_ON(data->gtks >= ARRAY_SIZE(data->gtk_ids))) + return; + data->gtk_ids[data->gtks] = key->keyidx; + rsc = data->rsc->mcast_rsc[data->gtks % 2]; + if (WARN_ON(key->keyidx >= + ARRAY_SIZE(data->rsc->mcast_key_id_map))) + return; + data->rsc->mcast_key_id_map[key->keyidx] = data->gtks % 2; + if (data->gtks >= 2) { + int prev = data->gtks - 2; + int prev_idx = data->gtk_ids[prev]; + + data->rsc->mcast_key_id_map[prev_idx] = + IWL_MCAST_KEY_MAP_INVALID; + } + data->gtks++; + } + + switch (key->cipher) { + default: + WARN_ON(1); + break; + case WLAN_CIPHER_SUITE_TKIP: + + /* + * For non-QoS this relies on the fact that both the uCode and + * mac80211 use TID 0 (as they need to to avoid replay attacks) + * for checking the IV in the frames. + */ + for (i = 0; i < IWL_MAX_TID_COUNT; i++) { + ieee80211_get_key_rx_seq(key, i, &seq); + + rsc[i] = cpu_to_le64(((u64)seq.tkip.iv32 << 16) | + seq.tkip.iv16); + } + + data->have_rsc = true; + break; + case WLAN_CIPHER_SUITE_CCMP: + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_GCMP_256: + /* + * For non-QoS this relies on the fact that both the uCode and + * mac80211/our RX code use TID 0 for checking the PN. + */ + if (sta) { + struct iwl_mvm_sta *mvmsta; + struct iwl_mvm_key_pn *ptk_pn; + const u8 *pn; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + rcu_read_lock(); + ptk_pn = rcu_dereference(mvmsta->ptk_pn[key->keyidx]); + if (WARN_ON(!ptk_pn)) { + rcu_read_unlock(); + break; + } + + for (i = 0; i < IWL_MAX_TID_COUNT; i++) { + pn = iwl_mvm_find_max_pn(key, ptk_pn, &seq, i, + mvm->trans->num_rx_queues); + rsc[i] = cpu_to_le64((u64)pn[5] | + ((u64)pn[4] << 8) | + ((u64)pn[3] << 16) | + ((u64)pn[2] << 24) | + ((u64)pn[1] << 32) | + ((u64)pn[0] << 40)); + } + + rcu_read_unlock(); + } else { + for (i = 0; i < IWL_MAX_TID_COUNT; i++) { + u8 *pn = seq.ccmp.pn; + + ieee80211_get_key_rx_seq(key, i, &seq); + rsc[i] = cpu_to_le64((u64)pn[5] | + ((u64)pn[4] << 8) | + ((u64)pn[3] << 16) | + ((u64)pn[2] << 24) | + ((u64)pn[1] << 32) | + ((u64)pn[0] << 40)); + } + } + data->have_rsc = true; + break; + } +} + +static int iwl_mvm_wowlan_config_rsc_tsc(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ver = iwl_fw_lookup_cmd_ver(mvm->fw, WOWLAN_TSC_RSC_PARAM, + IWL_FW_CMD_VER_UNKNOWN); + int ret; + + if (ver == 5) { + struct wowlan_key_rsc_v5_data data = {}; + int i; + + data.rsc = kmalloc(sizeof(*data.rsc), GFP_KERNEL); + if (!data.rsc) + return -ENOMEM; + + memset(data.rsc, 0xff, sizeof(*data.rsc)); + + for (i = 0; i < ARRAY_SIZE(data.rsc->mcast_key_id_map); i++) + data.rsc->mcast_key_id_map[i] = + IWL_MCAST_KEY_MAP_INVALID; + data.rsc->sta_id = cpu_to_le32(mvmvif->ap_sta_id); + + ieee80211_iter_keys(mvm->hw, vif, + iwl_mvm_wowlan_get_rsc_v5_data, + &data); + + if (data.have_rsc) + ret = iwl_mvm_send_cmd_pdu(mvm, WOWLAN_TSC_RSC_PARAM, + CMD_ASYNC, sizeof(*data.rsc), + data.rsc); + else + ret = 0; + kfree(data.rsc); + } else if (ver == 4 || ver == 2 || ver == IWL_FW_CMD_VER_UNKNOWN) { + struct wowlan_key_rsc_tsc_data data = {}; + int size; + + data.rsc_tsc = kzalloc(sizeof(*data.rsc_tsc), GFP_KERNEL); + if (!data.rsc_tsc) + return -ENOMEM; + + if (ver == 4) { + size = sizeof(*data.rsc_tsc); + data.rsc_tsc->sta_id = cpu_to_le32(mvmvif->ap_sta_id); + } else { + /* ver == 2 || ver == IWL_FW_CMD_VER_UNKNOWN */ + size = sizeof(data.rsc_tsc->params); + } + + ieee80211_iter_keys(mvm->hw, vif, + iwl_mvm_wowlan_get_rsc_tsc_data, + &data); + + if (data.have_rsc_tsc) + ret = iwl_mvm_send_cmd_pdu(mvm, WOWLAN_TSC_RSC_PARAM, + CMD_ASYNC, size, + data.rsc_tsc); + else + ret = 0; + kfree(data.rsc_tsc); + } else { + ret = 0; + WARN_ON_ONCE(1); + } + + return ret; +} + +struct wowlan_key_tkip_data { + struct iwl_wowlan_tkip_params_cmd tkip; + bool have_tkip_keys; +}; + +static void iwl_mvm_wowlan_get_tkip_data(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, + void *_data) +{ + struct wowlan_key_tkip_data *data = _data; + struct iwl_p1k_cache *rx_p1ks; + u8 *rx_mic_key; + struct ieee80211_key_seq seq; + u32 cur_rx_iv32 = 0; + u16 p1k[IWL_P1K_SIZE]; + int i; + + switch (key->cipher) { + default: + break; + case WLAN_CIPHER_SUITE_TKIP: + if (sta) { + u64 pn64; + + rx_p1ks = data->tkip.rx_uni; + + pn64 = atomic64_read(&key->tx_pn); + + ieee80211_get_tkip_p1k_iv(key, TKIP_PN_TO_IV32(pn64), + p1k); + iwl_mvm_convert_p1k(p1k, data->tkip.tx.p1k); + + memcpy(data->tkip.mic_keys.tx, + &key->key[NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY], + IWL_MIC_KEY_SIZE); + + rx_mic_key = data->tkip.mic_keys.rx_unicast; + } else { + rx_p1ks = data->tkip.rx_multi; + rx_mic_key = data->tkip.mic_keys.rx_mcast; + } + + for (i = 0; i < IWL_NUM_RSC; i++) { + ieee80211_get_key_rx_seq(key, i, &seq); + /* wrapping isn't allowed, AP must rekey */ + if (seq.tkip.iv32 > cur_rx_iv32) + cur_rx_iv32 = seq.tkip.iv32; + } + + ieee80211_get_tkip_rx_p1k(key, vif->bss_conf.bssid, + cur_rx_iv32, p1k); + iwl_mvm_convert_p1k(p1k, rx_p1ks[0].p1k); + ieee80211_get_tkip_rx_p1k(key, vif->bss_conf.bssid, + cur_rx_iv32 + 1, p1k); + iwl_mvm_convert_p1k(p1k, rx_p1ks[1].p1k); + + memcpy(rx_mic_key, + &key->key[NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY], + IWL_MIC_KEY_SIZE); + + data->have_tkip_keys = true; + break; + } +} + +struct wowlan_key_gtk_type_iter { + struct iwl_wowlan_kek_kck_material_cmd_v4 *kek_kck_cmd; +}; + +static void iwl_mvm_wowlan_gtk_type_iter(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, + void *_data) +{ + struct wowlan_key_gtk_type_iter *data = _data; + + switch (key->cipher) { + default: + return; + case WLAN_CIPHER_SUITE_BIP_GMAC_256: + case WLAN_CIPHER_SUITE_BIP_GMAC_128: + data->kek_kck_cmd->igtk_cipher = cpu_to_le32(STA_KEY_FLG_GCMP); + return; + case WLAN_CIPHER_SUITE_AES_CMAC: + data->kek_kck_cmd->igtk_cipher = cpu_to_le32(STA_KEY_FLG_CCM); + return; + case WLAN_CIPHER_SUITE_CCMP: + if (!sta) + data->kek_kck_cmd->gtk_cipher = + cpu_to_le32(STA_KEY_FLG_CCM); + break; + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_GCMP_256: + if (!sta) + data->kek_kck_cmd->gtk_cipher = + cpu_to_le32(STA_KEY_FLG_GCMP); + break; + } +} + +static int iwl_mvm_send_patterns_v1(struct iwl_mvm *mvm, + struct cfg80211_wowlan *wowlan) +{ + struct iwl_wowlan_patterns_cmd_v1 *pattern_cmd; + struct iwl_host_cmd cmd = { + .id = WOWLAN_PATTERNS, + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + }; + int i, err; + + if (!wowlan->n_patterns) + return 0; + + cmd.len[0] = struct_size(pattern_cmd, patterns, wowlan->n_patterns); + + pattern_cmd = kmalloc(cmd.len[0], GFP_KERNEL); + if (!pattern_cmd) + return -ENOMEM; + + pattern_cmd->n_patterns = cpu_to_le32(wowlan->n_patterns); + + for (i = 0; i < wowlan->n_patterns; i++) { + int mask_len = DIV_ROUND_UP(wowlan->patterns[i].pattern_len, 8); + + memcpy(&pattern_cmd->patterns[i].mask, + wowlan->patterns[i].mask, mask_len); + memcpy(&pattern_cmd->patterns[i].pattern, + wowlan->patterns[i].pattern, + wowlan->patterns[i].pattern_len); + pattern_cmd->patterns[i].mask_size = mask_len; + pattern_cmd->patterns[i].pattern_size = + wowlan->patterns[i].pattern_len; + } + + cmd.data[0] = pattern_cmd; + err = iwl_mvm_send_cmd(mvm, &cmd); + kfree(pattern_cmd); + return err; +} + +static int iwl_mvm_send_patterns(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct cfg80211_wowlan *wowlan) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_wowlan_patterns_cmd *pattern_cmd; + struct iwl_host_cmd cmd = { + .id = WOWLAN_PATTERNS, + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + }; + int i, err; + int ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd.id, + IWL_FW_CMD_VER_UNKNOWN); + + if (!wowlan->n_patterns) + return 0; + + cmd.len[0] = sizeof(*pattern_cmd) + + wowlan->n_patterns * sizeof(struct iwl_wowlan_pattern_v2); + + pattern_cmd = kzalloc(cmd.len[0], GFP_KERNEL); + if (!pattern_cmd) + return -ENOMEM; + + pattern_cmd->n_patterns = wowlan->n_patterns; + if (ver >= 3) + pattern_cmd->sta_id = mvmvif->ap_sta_id; + + for (i = 0; i < wowlan->n_patterns; i++) { + int mask_len = DIV_ROUND_UP(wowlan->patterns[i].pattern_len, 8); + + pattern_cmd->patterns[i].pattern_type = + WOWLAN_PATTERN_TYPE_BITMASK; + + memcpy(&pattern_cmd->patterns[i].u.bitmask.mask, + wowlan->patterns[i].mask, mask_len); + memcpy(&pattern_cmd->patterns[i].u.bitmask.pattern, + wowlan->patterns[i].pattern, + wowlan->patterns[i].pattern_len); + pattern_cmd->patterns[i].u.bitmask.mask_size = mask_len; + pattern_cmd->patterns[i].u.bitmask.pattern_size = + wowlan->patterns[i].pattern_len; + } + + cmd.data[0] = pattern_cmd; + err = iwl_mvm_send_cmd(mvm, &cmd); + kfree(pattern_cmd); + return err; +} + +static int iwl_mvm_d3_reprogram(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *ap_sta) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_chanctx_conf *ctx; + u8 chains_static, chains_dynamic; + struct cfg80211_chan_def chandef; + int ret, i; + struct iwl_binding_cmd_v1 binding_cmd = {}; + struct iwl_time_quota_cmd quota_cmd = {}; + struct iwl_time_quota_data *quota; + u32 status; + + if (WARN_ON_ONCE(iwl_mvm_is_cdb_supported(mvm))) + return -EINVAL; + + /* add back the PHY */ + if (WARN_ON(!mvmvif->phy_ctxt)) + return -EINVAL; + + rcu_read_lock(); + ctx = rcu_dereference(vif->bss_conf.chanctx_conf); + if (WARN_ON(!ctx)) { + rcu_read_unlock(); + return -EINVAL; + } + chandef = ctx->def; + chains_static = ctx->rx_chains_static; + chains_dynamic = ctx->rx_chains_dynamic; + rcu_read_unlock(); + + ret = iwl_mvm_phy_ctxt_add(mvm, mvmvif->phy_ctxt, &chandef, + chains_static, chains_dynamic); + if (ret) + return ret; + + /* add back the MAC */ + mvmvif->uploaded = false; + + if (WARN_ON(!vif->cfg.assoc)) + return -EINVAL; + + ret = iwl_mvm_mac_ctxt_add(mvm, vif); + if (ret) + return ret; + + /* add back binding - XXX refactor? */ + binding_cmd.id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->phy_ctxt->id, + mvmvif->phy_ctxt->color)); + binding_cmd.action = cpu_to_le32(FW_CTXT_ACTION_ADD); + binding_cmd.phy = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->phy_ctxt->id, + mvmvif->phy_ctxt->color)); + binding_cmd.macs[0] = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)); + for (i = 1; i < MAX_MACS_IN_BINDING; i++) + binding_cmd.macs[i] = cpu_to_le32(FW_CTXT_INVALID); + + status = 0; + ret = iwl_mvm_send_cmd_pdu_status(mvm, BINDING_CONTEXT_CMD, + IWL_BINDING_CMD_SIZE_V1, &binding_cmd, + &status); + if (ret) { + IWL_ERR(mvm, "Failed to add binding: %d\n", ret); + return ret; + } + + if (status) { + IWL_ERR(mvm, "Binding command failed: %u\n", status); + return -EIO; + } + + ret = iwl_mvm_sta_send_to_fw(mvm, ap_sta, false, 0); + if (ret) + return ret; + rcu_assign_pointer(mvm->fw_id_to_mac_id[mvmvif->ap_sta_id], ap_sta); + + ret = iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + if (ret) + return ret; + + /* and some quota */ + quota = iwl_mvm_quota_cmd_get_quota(mvm, "a_cmd, 0); + quota->id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->phy_ctxt->id, + mvmvif->phy_ctxt->color)); + quota->quota = cpu_to_le32(IWL_MVM_MAX_QUOTA); + quota->max_duration = cpu_to_le32(IWL_MVM_MAX_QUOTA); + + for (i = 1; i < MAX_BINDINGS; i++) { + quota = iwl_mvm_quota_cmd_get_quota(mvm, "a_cmd, i); + quota->id_and_color = cpu_to_le32(FW_CTXT_INVALID); + } + + ret = iwl_mvm_send_cmd_pdu(mvm, TIME_QUOTA_CMD, 0, + iwl_mvm_quota_cmd_size(mvm), "a_cmd); + if (ret) + IWL_ERR(mvm, "Failed to send quota: %d\n", ret); + + if (iwl_mvm_is_lar_supported(mvm) && iwl_mvm_init_fw_regd(mvm)) + IWL_ERR(mvm, "Failed to initialize D3 LAR information\n"); + + return 0; +} + +static int iwl_mvm_get_last_nonqos_seq(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_nonqos_seq_query_cmd query_cmd = { + .get_set_flag = cpu_to_le32(IWL_NONQOS_SEQ_GET), + .mac_id_n_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)), + }; + struct iwl_host_cmd cmd = { + .id = NON_QOS_TX_COUNTER_CMD, + .flags = CMD_WANT_SKB, + }; + int err; + u32 size; + + cmd.data[0] = &query_cmd; + cmd.len[0] = sizeof(query_cmd); + + err = iwl_mvm_send_cmd(mvm, &cmd); + if (err) + return err; + + size = iwl_rx_packet_payload_len(cmd.resp_pkt); + if (size < sizeof(__le16)) { + err = -EINVAL; + } else { + err = le16_to_cpup((__le16 *)cmd.resp_pkt->data); + /* firmware returns next, not last-used seqno */ + err = (u16) (err - 0x10); + } + + iwl_free_resp(&cmd); + return err; +} + +void iwl_mvm_set_last_nonqos_seq(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_nonqos_seq_query_cmd query_cmd = { + .get_set_flag = cpu_to_le32(IWL_NONQOS_SEQ_SET), + .mac_id_n_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)), + .value = cpu_to_le16(mvmvif->seqno), + }; + + /* return if called during restart, not resume from D3 */ + if (!mvmvif->seqno_valid) + return; + + mvmvif->seqno_valid = false; + + if (iwl_mvm_send_cmd_pdu(mvm, NON_QOS_TX_COUNTER_CMD, 0, + sizeof(query_cmd), &query_cmd)) + IWL_ERR(mvm, "failed to set non-QoS seqno\n"); +} + +static int iwl_mvm_switch_to_d3(struct iwl_mvm *mvm) +{ + iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_REGULAR, true); + + iwl_mvm_stop_device(mvm); + /* + * Set the HW restart bit -- this is mostly true as we're + * going to load new firmware and reprogram that, though + * the reprogramming is going to be manual to avoid adding + * all the MACs that aren't support. + * We don't have to clear up everything though because the + * reprogramming is manual. When we resume, we'll actually + * go through a proper restart sequence again to switch + * back to the runtime firmware image. + */ + set_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status); + + /* the fw is reset, so all the keys are cleared */ + memset(mvm->fw_key_table, 0, sizeof(mvm->fw_key_table)); + + mvm->ptk_ivlen = 0; + mvm->ptk_icvlen = 0; + mvm->ptk_ivlen = 0; + mvm->ptk_icvlen = 0; + + return iwl_mvm_load_d3_fw(mvm); +} + +static int +iwl_mvm_get_wowlan_config(struct iwl_mvm *mvm, + struct cfg80211_wowlan *wowlan, + struct iwl_wowlan_config_cmd *wowlan_config_cmd, + struct ieee80211_vif *vif, struct iwl_mvm_vif *mvmvif, + struct ieee80211_sta *ap_sta) +{ + struct iwl_mvm_sta *mvm_ap_sta = iwl_mvm_sta_from_mac80211(ap_sta); + + /* TODO: wowlan_config_cmd->wowlan_ba_teardown_tids */ + + wowlan_config_cmd->is_11n_connection = + ap_sta->deflink.ht_cap.ht_supported; + wowlan_config_cmd->flags = ENABLE_L3_FILTERING | + ENABLE_NBNS_FILTERING | ENABLE_DHCP_FILTERING; + + if (iwl_fw_lookup_cmd_ver(mvm->fw, WOWLAN_CONFIGURATION, 0) < 6) { + /* Query the last used seqno and set it */ + int ret = iwl_mvm_get_last_nonqos_seq(mvm, vif); + + if (ret < 0) + return ret; + + wowlan_config_cmd->non_qos_seq = cpu_to_le16(ret); + } + + iwl_mvm_set_wowlan_qos_seq(mvm_ap_sta, wowlan_config_cmd); + + if (wowlan->disconnect) + wowlan_config_cmd->wakeup_filter |= + cpu_to_le32(IWL_WOWLAN_WAKEUP_BEACON_MISS | + IWL_WOWLAN_WAKEUP_LINK_CHANGE); + if (wowlan->magic_pkt) + wowlan_config_cmd->wakeup_filter |= + cpu_to_le32(IWL_WOWLAN_WAKEUP_MAGIC_PACKET); + if (wowlan->gtk_rekey_failure) + wowlan_config_cmd->wakeup_filter |= + cpu_to_le32(IWL_WOWLAN_WAKEUP_GTK_REKEY_FAIL); + if (wowlan->eap_identity_req) + wowlan_config_cmd->wakeup_filter |= + cpu_to_le32(IWL_WOWLAN_WAKEUP_EAP_IDENT_REQ); + if (wowlan->four_way_handshake) + wowlan_config_cmd->wakeup_filter |= + cpu_to_le32(IWL_WOWLAN_WAKEUP_4WAY_HANDSHAKE); + if (wowlan->n_patterns) + wowlan_config_cmd->wakeup_filter |= + cpu_to_le32(IWL_WOWLAN_WAKEUP_PATTERN_MATCH); + + if (wowlan->rfkill_release) + wowlan_config_cmd->wakeup_filter |= + cpu_to_le32(IWL_WOWLAN_WAKEUP_RF_KILL_DEASSERT); + + if (wowlan->tcp) { + /* + * Set the "link change" (really "link lost") flag as well + * since that implies losing the TCP connection. + */ + wowlan_config_cmd->wakeup_filter |= + cpu_to_le32(IWL_WOWLAN_WAKEUP_REMOTE_LINK_LOSS | + IWL_WOWLAN_WAKEUP_REMOTE_SIGNATURE_TABLE | + IWL_WOWLAN_WAKEUP_REMOTE_WAKEUP_PACKET | + IWL_WOWLAN_WAKEUP_LINK_CHANGE); + } + + if (wowlan->any) { + wowlan_config_cmd->wakeup_filter |= + cpu_to_le32(IWL_WOWLAN_WAKEUP_BEACON_MISS | + IWL_WOWLAN_WAKEUP_LINK_CHANGE | + IWL_WOWLAN_WAKEUP_RX_FRAME | + IWL_WOWLAN_WAKEUP_BCN_FILTERING); + } + + return 0; +} + +static int iwl_mvm_wowlan_config_key_params(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + bool unified = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG); + struct wowlan_key_reprogram_data key_data = {}; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + u8 cmd_ver; + size_t cmd_size; + + if (!unified) { + /* + * if we have to configure keys, call ieee80211_iter_keys(), + * as we need non-atomic context in order to take the + * required locks. + */ + /* + * Note that currently we don't use CMD_ASYNC in the iterator. + * In case of key_data.configure_keys, all the configured + * commands are SYNC, and iwl_mvm_wowlan_program_keys() will + * take care of locking/unlocking mvm->mutex. + */ + ieee80211_iter_keys(mvm->hw, vif, iwl_mvm_wowlan_program_keys, + &key_data); + + if (key_data.error) + return -EIO; + } + + ret = iwl_mvm_wowlan_config_rsc_tsc(mvm, vif); + if (ret) + return ret; + + if (!fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_TKIP_MIC_KEYS)) { + int ver = iwl_fw_lookup_cmd_ver(mvm->fw, WOWLAN_TKIP_PARAM, + IWL_FW_CMD_VER_UNKNOWN); + struct wowlan_key_tkip_data tkip_data = {}; + int size; + + if (ver == 2) { + size = sizeof(tkip_data.tkip); + tkip_data.tkip.sta_id = + cpu_to_le32(mvmvif->ap_sta_id); + } else if (ver == 1 || ver == IWL_FW_CMD_VER_UNKNOWN) { + size = sizeof(struct iwl_wowlan_tkip_params_cmd_ver_1); + } else { + WARN_ON_ONCE(1); + return -EINVAL; + } + + ieee80211_iter_keys(mvm->hw, vif, iwl_mvm_wowlan_get_tkip_data, + &tkip_data); + + if (tkip_data.have_tkip_keys) { + /* send relevant data according to CMD version */ + ret = iwl_mvm_send_cmd_pdu(mvm, + WOWLAN_TKIP_PARAM, + CMD_ASYNC, size, + &tkip_data.tkip); + if (ret) + return ret; + } + } + + /* configure rekey data only if offloaded rekey is supported (d3) */ + if (mvmvif->rekey_data.valid) { + struct iwl_wowlan_kek_kck_material_cmd_v4 kek_kck_cmd = {}; + struct iwl_wowlan_kek_kck_material_cmd_v4 *_kek_kck_cmd = + &kek_kck_cmd; + struct wowlan_key_gtk_type_iter gtk_type_data = { + .kek_kck_cmd = _kek_kck_cmd, + }; + + cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, + WOWLAN_KEK_KCK_MATERIAL, + IWL_FW_CMD_VER_UNKNOWN); + if (WARN_ON(cmd_ver != 2 && cmd_ver != 3 && cmd_ver != 4 && + cmd_ver != IWL_FW_CMD_VER_UNKNOWN)) + return -EINVAL; + + ieee80211_iter_keys(mvm->hw, vif, iwl_mvm_wowlan_gtk_type_iter, + >k_type_data); + + memcpy(kek_kck_cmd.kck, mvmvif->rekey_data.kck, + mvmvif->rekey_data.kck_len); + kek_kck_cmd.kck_len = cpu_to_le16(mvmvif->rekey_data.kck_len); + memcpy(kek_kck_cmd.kek, mvmvif->rekey_data.kek, + mvmvif->rekey_data.kek_len); + kek_kck_cmd.kek_len = cpu_to_le16(mvmvif->rekey_data.kek_len); + kek_kck_cmd.replay_ctr = mvmvif->rekey_data.replay_ctr; + kek_kck_cmd.akm = cpu_to_le32(mvmvif->rekey_data.akm); + kek_kck_cmd.sta_id = cpu_to_le32(mvmvif->ap_sta_id); + + if (cmd_ver == 4) { + cmd_size = sizeof(struct iwl_wowlan_kek_kck_material_cmd_v4); + } else { + if (cmd_ver == 3) + cmd_size = + sizeof(struct iwl_wowlan_kek_kck_material_cmd_v3); + else + cmd_size = + sizeof(struct iwl_wowlan_kek_kck_material_cmd_v2); + /* skip the sta_id at the beginning */ + _kek_kck_cmd = (void *) + ((u8 *)_kek_kck_cmd + sizeof(kek_kck_cmd.sta_id)); + } + + IWL_DEBUG_WOWLAN(mvm, "setting akm %d\n", + mvmvif->rekey_data.akm); + + ret = iwl_mvm_send_cmd_pdu(mvm, WOWLAN_KEK_KCK_MATERIAL, + CMD_ASYNC, cmd_size, _kek_kck_cmd); + if (ret) + return ret; + } + + return 0; +} + +static int +iwl_mvm_wowlan_config(struct iwl_mvm *mvm, + struct cfg80211_wowlan *wowlan, + struct iwl_wowlan_config_cmd *wowlan_config_cmd, + struct ieee80211_vif *vif, struct iwl_mvm_vif *mvmvif, + struct ieee80211_sta *ap_sta) +{ + int ret; + bool unified_image = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG); + + mvm->offload_tid = wowlan_config_cmd->offloading_tid; + + if (!unified_image) { + ret = iwl_mvm_switch_to_d3(mvm); + if (ret) + return ret; + + ret = iwl_mvm_d3_reprogram(mvm, vif, ap_sta); + if (ret) + return ret; + } + + /* + * This needs to be unlocked due to lock ordering + * constraints. Since we're in the suspend path + * that isn't really a problem though. + */ + mutex_unlock(&mvm->mutex); + ret = iwl_mvm_wowlan_config_key_params(mvm, vif); + mutex_lock(&mvm->mutex); + if (ret) + return ret; + + ret = iwl_mvm_send_cmd_pdu(mvm, WOWLAN_CONFIGURATION, 0, + sizeof(*wowlan_config_cmd), + wowlan_config_cmd); + if (ret) + return ret; + + if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_WOWLAN_TCP_SYN_WAKE)) + ret = iwl_mvm_send_patterns(mvm, vif, wowlan); + else + ret = iwl_mvm_send_patterns_v1(mvm, wowlan); + if (ret) + return ret; + + return iwl_mvm_send_proto_offload(mvm, vif, false, true, 0); +} + +static int +iwl_mvm_netdetect_config(struct iwl_mvm *mvm, + struct cfg80211_wowlan *wowlan, + struct cfg80211_sched_scan_request *nd_config, + struct ieee80211_vif *vif) +{ + int ret; + bool unified_image = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG); + + if (!unified_image) { + ret = iwl_mvm_switch_to_d3(mvm); + if (ret) + return ret; + } else { + /* In theory, we wouldn't have to stop a running sched + * scan in order to start another one (for + * net-detect). But in practice this doesn't seem to + * work properly, so stop any running sched_scan now. + */ + ret = iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_SCHED, true); + if (ret) + return ret; + } + + ret = iwl_mvm_sched_scan_start(mvm, vif, nd_config, &mvm->nd_ies, + IWL_MVM_SCAN_NETDETECT); + if (ret) + return ret; + + if (WARN_ON(mvm->nd_match_sets || mvm->nd_channels)) + return -EBUSY; + + /* save the sched scan matchsets... */ + if (nd_config->n_match_sets) { + mvm->nd_match_sets = kmemdup(nd_config->match_sets, + sizeof(*nd_config->match_sets) * + nd_config->n_match_sets, + GFP_KERNEL); + if (mvm->nd_match_sets) + mvm->n_nd_match_sets = nd_config->n_match_sets; + } + + /* ...and the sched scan channels for later reporting */ + mvm->nd_channels = kmemdup(nd_config->channels, + sizeof(*nd_config->channels) * + nd_config->n_channels, + GFP_KERNEL); + if (mvm->nd_channels) + mvm->n_nd_channels = nd_config->n_channels; + + return 0; +} + +static void iwl_mvm_free_nd(struct iwl_mvm *mvm) +{ + kfree(mvm->nd_match_sets); + mvm->nd_match_sets = NULL; + mvm->n_nd_match_sets = 0; + kfree(mvm->nd_channels); + mvm->nd_channels = NULL; + mvm->n_nd_channels = 0; +} + +static int __iwl_mvm_suspend(struct ieee80211_hw *hw, + struct cfg80211_wowlan *wowlan, + bool test) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct ieee80211_vif *vif = NULL; + struct iwl_mvm_vif *mvmvif = NULL; + struct ieee80211_sta *ap_sta = NULL; + struct iwl_d3_manager_config d3_cfg_cmd_data = { + /* + * Program the minimum sleep time to 10 seconds, as many + * platforms have issues processing a wakeup signal while + * still being in the process of suspending. + */ + .min_sleep_time = cpu_to_le32(10 * 1000 * 1000), + }; + struct iwl_host_cmd d3_cfg_cmd = { + .id = D3_CONFIG_CMD, + .flags = CMD_WANT_SKB | CMD_SEND_IN_D3, + .data[0] = &d3_cfg_cmd_data, + .len[0] = sizeof(d3_cfg_cmd_data), + }; + int ret; + int len __maybe_unused; + bool unified_image = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG); + + if (!wowlan) { + /* + * mac80211 shouldn't get here, but for D3 test + * it doesn't warrant a warning + */ + WARN_ON(!test); + return -EINVAL; + } + + mutex_lock(&mvm->mutex); + + set_bit(IWL_MVM_STATUS_IN_D3, &mvm->status); + + synchronize_net(); + + vif = iwl_mvm_get_bss_vif(mvm); + if (IS_ERR_OR_NULL(vif)) { + ret = 1; + goto out_noreset; + } + + mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (mvmvif->ap_sta_id == IWL_MVM_INVALID_STA) { + /* if we're not associated, this must be netdetect */ + if (!wowlan->nd_config) { + ret = 1; + goto out_noreset; + } + + ret = iwl_mvm_netdetect_config( + mvm, wowlan, wowlan->nd_config, vif); + if (ret) + goto out; + + mvm->net_detect = true; + } else { + struct iwl_wowlan_config_cmd wowlan_config_cmd = {}; + + wowlan_config_cmd.sta_id = mvmvif->ap_sta_id; + + ap_sta = rcu_dereference_protected( + mvm->fw_id_to_mac_id[mvmvif->ap_sta_id], + lockdep_is_held(&mvm->mutex)); + if (IS_ERR_OR_NULL(ap_sta)) { + ret = -EINVAL; + goto out_noreset; + } + + ret = iwl_mvm_get_wowlan_config(mvm, wowlan, &wowlan_config_cmd, + vif, mvmvif, ap_sta); + if (ret) + goto out_noreset; + ret = iwl_mvm_wowlan_config(mvm, wowlan, &wowlan_config_cmd, + vif, mvmvif, ap_sta); + if (ret) + goto out; + + mvm->net_detect = false; + } + + ret = iwl_mvm_power_update_device(mvm); + if (ret) + goto out; + + ret = iwl_mvm_power_update_mac(mvm); + if (ret) + goto out; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (mvm->d3_wake_sysassert) + d3_cfg_cmd_data.wakeup_flags |= + cpu_to_le32(IWL_WAKEUP_D3_CONFIG_FW_ERROR); +#endif + + /* + * Prior to 9000 device family the driver needs to stop the dbg + * recording before entering D3. In later devices the FW stops the + * recording automatically. + */ + if (mvm->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_9000) + iwl_fw_dbg_stop_restart_recording(&mvm->fwrt, NULL, true); + + mvm->trans->system_pm_mode = IWL_PLAT_PM_MODE_D3; + + /* must be last -- this switches firmware state */ + ret = iwl_mvm_send_cmd(mvm, &d3_cfg_cmd); + if (ret) + goto out; +#ifdef CONFIG_IWLWIFI_DEBUGFS + len = iwl_rx_packet_payload_len(d3_cfg_cmd.resp_pkt); + if (len >= sizeof(u32)) { + mvm->d3_test_pme_ptr = + le32_to_cpup((__le32 *)d3_cfg_cmd.resp_pkt->data); + } +#endif + iwl_free_resp(&d3_cfg_cmd); + + clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status); + + ret = iwl_trans_d3_suspend(mvm->trans, test, !unified_image); + out: + if (ret < 0) { + iwl_mvm_free_nd(mvm); + + if (!unified_image) { + if (mvm->fw_restart > 0) { + mvm->fw_restart--; + ieee80211_restart_hw(mvm->hw); + } + } + + clear_bit(IWL_MVM_STATUS_IN_D3, &mvm->status); + } + out_noreset: + mutex_unlock(&mvm->mutex); + + return ret; +} + +int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + iwl_mvm_pause_tcm(mvm, true); + + iwl_fw_runtime_suspend(&mvm->fwrt); + + return __iwl_mvm_suspend(hw, wowlan, false); +} + +/* converted data from the different status responses */ +struct iwl_wowlan_status_data { + u64 replay_ctr; + u32 num_of_gtk_rekeys; + u32 received_beacons; + u32 wakeup_reasons; + u32 wake_packet_length; + u32 wake_packet_bufsize; + u16 pattern_number; + u16 non_qos_seq_ctr; + u16 qos_seq_ctr[8]; + u8 tid_tear_down; + + struct { + /* including RX MIC key for TKIP */ + u8 key[WOWLAN_KEY_MAX_SIZE]; + u8 len; + u8 flags; + } gtk; + + struct { + /* + * We store both the TKIP and AES representations + * coming from the firmware because we decode the + * data from there before we iterate the keys and + * know which one we need. + */ + struct { + struct ieee80211_key_seq seq[IWL_MAX_TID_COUNT]; + } tkip, aes; + + /* + * We use -1 for when we have valid data but don't know + * the key ID from firmware, and thus it needs to be + * installed with the last key (depending on rekeying). + */ + s8 key_id; + bool valid; + } gtk_seq[2]; + + struct { + /* Same as above */ + struct { + struct ieee80211_key_seq seq[IWL_MAX_TID_COUNT]; + u64 tx_pn; + } tkip, aes; + } ptk; + + struct { + u64 ipn; + u8 key[WOWLAN_KEY_MAX_SIZE]; + u8 len; + u8 flags; + } igtk; + + u8 *wake_packet; +}; + +static void iwl_mvm_report_wakeup_reasons(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_wowlan_status_data *status) +{ + struct sk_buff *pkt = NULL; + struct cfg80211_wowlan_wakeup wakeup = { + .pattern_idx = -1, + }; + struct cfg80211_wowlan_wakeup *wakeup_report = &wakeup; + u32 reasons = status->wakeup_reasons; + + if (reasons == IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS) { + wakeup_report = NULL; + goto report; + } + + pm_wakeup_event(mvm->dev, 0); + + if (reasons & IWL_WOWLAN_WAKEUP_BY_MAGIC_PACKET) + wakeup.magic_pkt = true; + + if (reasons & IWL_WOWLAN_WAKEUP_BY_PATTERN) + wakeup.pattern_idx = + status->pattern_number; + + if (reasons & (IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_MISSED_BEACON | + IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_DEAUTH)) + wakeup.disconnect = true; + + if (reasons & IWL_WOWLAN_WAKEUP_BY_GTK_REKEY_FAILURE) + wakeup.gtk_rekey_failure = true; + + if (reasons & IWL_WOWLAN_WAKEUP_BY_RFKILL_DEASSERTED) + wakeup.rfkill_release = true; + + if (reasons & IWL_WOWLAN_WAKEUP_BY_EAPOL_REQUEST) + wakeup.eap_identity_req = true; + + if (reasons & IWL_WOWLAN_WAKEUP_BY_FOUR_WAY_HANDSHAKE) + wakeup.four_way_handshake = true; + + if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_LINK_LOSS) + wakeup.tcp_connlost = true; + + if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_SIGNATURE_TABLE) + wakeup.tcp_nomoretokens = true; + + if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_WAKEUP_PACKET) + wakeup.tcp_match = true; + + if (status->wake_packet) { + int pktsize = status->wake_packet_bufsize; + int pktlen = status->wake_packet_length; + const u8 *pktdata = status->wake_packet; + const struct ieee80211_hdr *hdr = (const void *)pktdata; + int truncated = pktlen - pktsize; + + /* this would be a firmware bug */ + if (WARN_ON_ONCE(truncated < 0)) + truncated = 0; + + if (ieee80211_is_data(hdr->frame_control)) { + int hdrlen = ieee80211_hdrlen(hdr->frame_control); + int ivlen = 0, icvlen = 4; /* also FCS */ + + pkt = alloc_skb(pktsize, GFP_KERNEL); + if (!pkt) + goto report; + + skb_put_data(pkt, pktdata, hdrlen); + pktdata += hdrlen; + pktsize -= hdrlen; + + if (ieee80211_has_protected(hdr->frame_control)) { + /* + * This is unlocked and using gtk_i(c)vlen, + * but since everything is under RTNL still + * that's not really a problem - changing + * it would be difficult. + */ + if (is_multicast_ether_addr(hdr->addr1)) { + ivlen = mvm->gtk_ivlen; + icvlen += mvm->gtk_icvlen; + } else { + ivlen = mvm->ptk_ivlen; + icvlen += mvm->ptk_icvlen; + } + } + + /* if truncated, FCS/ICV is (partially) gone */ + if (truncated >= icvlen) { + icvlen = 0; + truncated -= icvlen; + } else { + icvlen -= truncated; + truncated = 0; + } + + pktsize -= ivlen + icvlen; + pktdata += ivlen; + + skb_put_data(pkt, pktdata, pktsize); + + if (ieee80211_data_to_8023(pkt, vif->addr, vif->type)) + goto report; + wakeup.packet = pkt->data; + wakeup.packet_present_len = pkt->len; + wakeup.packet_len = pkt->len - truncated; + wakeup.packet_80211 = false; + } else { + int fcslen = 4; + + if (truncated >= 4) { + truncated -= 4; + fcslen = 0; + } else { + fcslen -= truncated; + truncated = 0; + } + pktsize -= fcslen; + wakeup.packet = status->wake_packet; + wakeup.packet_present_len = pktsize; + wakeup.packet_len = pktlen - truncated; + wakeup.packet_80211 = true; + } + } + + report: + ieee80211_report_wowlan_wakeup(vif, wakeup_report, GFP_KERNEL); + kfree_skb(pkt); +} + +static void iwl_mvm_le64_to_aes_seq(__le64 le_pn, struct ieee80211_key_seq *seq) +{ + u64 pn = le64_to_cpu(le_pn); + + seq->ccmp.pn[0] = pn >> 40; + seq->ccmp.pn[1] = pn >> 32; + seq->ccmp.pn[2] = pn >> 24; + seq->ccmp.pn[3] = pn >> 16; + seq->ccmp.pn[4] = pn >> 8; + seq->ccmp.pn[5] = pn; +} + +static void iwl_mvm_aes_sc_to_seq(struct aes_sc *sc, + struct ieee80211_key_seq *seq) +{ + iwl_mvm_le64_to_aes_seq(sc->pn, seq); +} + +static void iwl_mvm_le64_to_tkip_seq(__le64 le_pn, struct ieee80211_key_seq *seq) +{ + u64 pn = le64_to_cpu(le_pn); + + seq->tkip.iv16 = (u16)pn; + seq->tkip.iv32 = (u32)(pn >> 16); +} + +static void iwl_mvm_tkip_sc_to_seq(struct tkip_sc *sc, + struct ieee80211_key_seq *seq) +{ + seq->tkip.iv32 = le32_to_cpu(sc->iv32); + seq->tkip.iv16 = le16_to_cpu(sc->iv16); +} + +static void iwl_mvm_set_key_rx_seq_tids(struct ieee80211_key_conf *key, + struct ieee80211_key_seq *seq) +{ + int tid; + + for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++) + ieee80211_set_key_rx_seq(key, tid, &seq[tid]); +} + +static void iwl_mvm_set_aes_ptk_rx_seq(struct iwl_mvm *mvm, + struct iwl_wowlan_status_data *status, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_key_pn *ptk_pn; + int tid; + + iwl_mvm_set_key_rx_seq_tids(key, status->ptk.aes.seq); + + if (!iwl_mvm_has_new_rx_api(mvm)) + return; + + + rcu_read_lock(); + ptk_pn = rcu_dereference(mvmsta->ptk_pn[key->keyidx]); + if (WARN_ON(!ptk_pn)) { + rcu_read_unlock(); + return; + } + + for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++) { + int i; + + for (i = 1; i < mvm->trans->num_rx_queues; i++) + memcpy(ptk_pn->q[i].pn[tid], + status->ptk.aes.seq[tid].ccmp.pn, + IEEE80211_CCMP_PN_LEN); + } + rcu_read_unlock(); +} + +static void iwl_mvm_convert_key_counters(struct iwl_wowlan_status_data *status, + union iwl_all_tsc_rsc *sc) +{ + int i; + + BUILD_BUG_ON(IWL_MAX_TID_COUNT > IWL_MAX_TID_COUNT); + BUILD_BUG_ON(IWL_MAX_TID_COUNT > IWL_NUM_RSC); + + /* GTK RX counters */ + for (i = 0; i < IWL_MAX_TID_COUNT; i++) { + iwl_mvm_tkip_sc_to_seq(&sc->tkip.multicast_rsc[i], + &status->gtk_seq[0].tkip.seq[i]); + iwl_mvm_aes_sc_to_seq(&sc->aes.multicast_rsc[i], + &status->gtk_seq[0].aes.seq[i]); + } + status->gtk_seq[0].valid = true; + status->gtk_seq[0].key_id = -1; + + /* PTK TX counter */ + status->ptk.tkip.tx_pn = (u64)le16_to_cpu(sc->tkip.tsc.iv16) | + ((u64)le32_to_cpu(sc->tkip.tsc.iv32) << 16); + status->ptk.aes.tx_pn = le64_to_cpu(sc->aes.tsc.pn); + + /* PTK RX counters */ + for (i = 0; i < IWL_MAX_TID_COUNT; i++) { + iwl_mvm_tkip_sc_to_seq(&sc->tkip.unicast_rsc[i], + &status->ptk.tkip.seq[i]); + iwl_mvm_aes_sc_to_seq(&sc->aes.unicast_rsc[i], + &status->ptk.aes.seq[i]); + } +} + +static void +iwl_mvm_convert_key_counters_v5_gtk_seq(struct iwl_wowlan_status_data *status, + struct iwl_wowlan_all_rsc_tsc_v5 *sc, + unsigned int idx, unsigned int key_id) +{ + int tid; + + for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++) { + iwl_mvm_le64_to_tkip_seq(sc->mcast_rsc[idx][tid], + &status->gtk_seq[idx].tkip.seq[tid]); + iwl_mvm_le64_to_aes_seq(sc->mcast_rsc[idx][tid], + &status->gtk_seq[idx].aes.seq[tid]); + } + + status->gtk_seq[idx].valid = true; + status->gtk_seq[idx].key_id = key_id; +} + +static void +iwl_mvm_convert_key_counters_v5(struct iwl_wowlan_status_data *status, + struct iwl_wowlan_all_rsc_tsc_v5 *sc) +{ + int i, tid; + + BUILD_BUG_ON(IWL_MAX_TID_COUNT > IWL_MAX_TID_COUNT); + BUILD_BUG_ON(IWL_MAX_TID_COUNT > IWL_NUM_RSC); + BUILD_BUG_ON(ARRAY_SIZE(sc->mcast_rsc) != ARRAY_SIZE(status->gtk_seq)); + + /* GTK RX counters */ + for (i = 0; i < ARRAY_SIZE(sc->mcast_key_id_map); i++) { + u8 entry = sc->mcast_key_id_map[i]; + + if (entry < ARRAY_SIZE(sc->mcast_rsc)) + iwl_mvm_convert_key_counters_v5_gtk_seq(status, sc, + entry, i); + } + + /* PTK TX counters not needed, assigned in device */ + + /* PTK RX counters */ + for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++) { + iwl_mvm_le64_to_tkip_seq(sc->ucast_rsc[tid], + &status->ptk.tkip.seq[tid]); + iwl_mvm_le64_to_aes_seq(sc->ucast_rsc[tid], + &status->ptk.aes.seq[tid]); + } +} + +static void iwl_mvm_set_key_rx_seq_idx(struct ieee80211_key_conf *key, + struct iwl_wowlan_status_data *status, + int idx) +{ + switch (key->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_GCMP_256: + iwl_mvm_set_key_rx_seq_tids(key, status->gtk_seq[idx].aes.seq); + break; + case WLAN_CIPHER_SUITE_TKIP: + iwl_mvm_set_key_rx_seq_tids(key, status->gtk_seq[idx].tkip.seq); + break; + default: + WARN_ON(1); + } +} + +static void iwl_mvm_set_key_rx_seq(struct ieee80211_key_conf *key, + struct iwl_wowlan_status_data *status, + bool installed) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(status->gtk_seq); i++) { + if (!status->gtk_seq[i].valid) + continue; + + /* Handle the case where we know the key ID */ + if (status->gtk_seq[i].key_id == key->keyidx) { + s8 new_key_id = -1; + + if (status->num_of_gtk_rekeys) + new_key_id = status->gtk.flags & + IWL_WOWLAN_GTK_IDX_MASK; + + /* Don't install a new key's value to an old key */ + if (new_key_id != key->keyidx) + iwl_mvm_set_key_rx_seq_idx(key, status, i); + continue; + } + + /* handle the case where we didn't, last key only */ + if (status->gtk_seq[i].key_id == -1 && + (!status->num_of_gtk_rekeys || installed)) + iwl_mvm_set_key_rx_seq_idx(key, status, i); + } +} + +struct iwl_mvm_d3_gtk_iter_data { + struct iwl_mvm *mvm; + struct iwl_wowlan_status_data *status; + void *last_gtk; + u32 cipher; + bool find_phase, unhandled_cipher; + int num_keys; +}; + +static void iwl_mvm_d3_update_keys(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, + void *_data) +{ + struct iwl_mvm_d3_gtk_iter_data *data = _data; + struct iwl_wowlan_status_data *status = data->status; + + if (data->unhandled_cipher) + return; + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + /* ignore WEP completely, nothing to do */ + return; + case WLAN_CIPHER_SUITE_CCMP: + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_GCMP_256: + case WLAN_CIPHER_SUITE_TKIP: + /* we support these */ + break; + default: + /* everything else (even CMAC for MFP) - disconnect from AP */ + data->unhandled_cipher = true; + return; + } + + data->num_keys++; + + /* + * pairwise key - update sequence counters only; + * note that this assumes no TDLS sessions are active + */ + if (sta) { + if (data->find_phase) + return; + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_GCMP_256: + atomic64_set(&key->tx_pn, status->ptk.aes.tx_pn); + iwl_mvm_set_aes_ptk_rx_seq(data->mvm, status, sta, key); + break; + case WLAN_CIPHER_SUITE_TKIP: + atomic64_set(&key->tx_pn, status->ptk.tkip.tx_pn); + iwl_mvm_set_key_rx_seq_tids(key, status->ptk.tkip.seq); + break; + } + + /* that's it for this key */ + return; + } + + if (data->find_phase) { + data->last_gtk = key; + data->cipher = key->cipher; + return; + } + + if (data->status->num_of_gtk_rekeys) + ieee80211_remove_key(key); + + if (data->last_gtk == key) + iwl_mvm_set_key_rx_seq(key, data->status, false); +} + +static bool iwl_mvm_setup_connection_keep(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_wowlan_status_data *status) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_d3_gtk_iter_data gtkdata = { + .mvm = mvm, + .status = status, + }; + u32 disconnection_reasons = + IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_MISSED_BEACON | + IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_DEAUTH; + + if (!status || !vif->bss_conf.bssid) + return false; + + if (status->wakeup_reasons & disconnection_reasons) + return false; + + /* find last GTK that we used initially, if any */ + gtkdata.find_phase = true; + ieee80211_iter_keys(mvm->hw, vif, + iwl_mvm_d3_update_keys, >kdata); + /* not trying to keep connections with MFP/unhandled ciphers */ + if (gtkdata.unhandled_cipher) + return false; + if (!gtkdata.num_keys) + goto out; + if (!gtkdata.last_gtk) + return false; + + /* + * invalidate all other GTKs that might still exist and update + * the one that we used + */ + gtkdata.find_phase = false; + ieee80211_iter_keys(mvm->hw, vif, + iwl_mvm_d3_update_keys, >kdata); + + IWL_DEBUG_WOWLAN(mvm, "num of GTK rekeying %d\n", + status->num_of_gtk_rekeys); + if (status->num_of_gtk_rekeys) { + struct ieee80211_key_conf *key; + struct { + struct ieee80211_key_conf conf; + u8 key[32]; + } conf = { + .conf.cipher = gtkdata.cipher, + .conf.keyidx = + status->gtk.flags & IWL_WOWLAN_GTK_IDX_MASK, + }; + __be64 replay_ctr; + + IWL_DEBUG_WOWLAN(mvm, + "Received from FW GTK cipher %d, key index %d\n", + conf.conf.cipher, conf.conf.keyidx); + + BUILD_BUG_ON(WLAN_KEY_LEN_CCMP != WLAN_KEY_LEN_GCMP); + BUILD_BUG_ON(sizeof(conf.key) < WLAN_KEY_LEN_CCMP); + BUILD_BUG_ON(sizeof(conf.key) < WLAN_KEY_LEN_GCMP_256); + BUILD_BUG_ON(sizeof(conf.key) < WLAN_KEY_LEN_TKIP); + BUILD_BUG_ON(sizeof(conf.key) < sizeof(status->gtk.key)); + + memcpy(conf.conf.key, status->gtk.key, sizeof(status->gtk.key)); + + switch (gtkdata.cipher) { + case WLAN_CIPHER_SUITE_CCMP: + case WLAN_CIPHER_SUITE_GCMP: + conf.conf.keylen = WLAN_KEY_LEN_CCMP; + break; + case WLAN_CIPHER_SUITE_GCMP_256: + conf.conf.keylen = WLAN_KEY_LEN_GCMP_256; + break; + case WLAN_CIPHER_SUITE_TKIP: + conf.conf.keylen = WLAN_KEY_LEN_TKIP; + break; + } + + key = ieee80211_gtk_rekey_add(vif, &conf.conf); + if (IS_ERR(key)) + return false; + iwl_mvm_set_key_rx_seq(key, status, true); + + replay_ctr = cpu_to_be64(status->replay_ctr); + + ieee80211_gtk_rekey_notify(vif, vif->bss_conf.bssid, + (void *)&replay_ctr, GFP_KERNEL); + } + +out: + if (iwl_fw_lookup_notif_ver(mvm->fw, LONG_GROUP, + WOWLAN_GET_STATUSES, 0) < 10) { + mvmvif->seqno_valid = true; + /* +0x10 because the set API expects next-to-use, not last-used */ + mvmvif->seqno = status->non_qos_seq_ctr + 0x10; + } + + return true; +} + +static void iwl_mvm_convert_gtk_v2(struct iwl_wowlan_status_data *status, + struct iwl_wowlan_gtk_status_v2 *data) +{ + BUILD_BUG_ON(sizeof(status->gtk.key) < sizeof(data->key)); + BUILD_BUG_ON(NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY + + sizeof(data->tkip_mic_key) > + sizeof(status->gtk.key)); + + status->gtk.len = data->key_len; + status->gtk.flags = data->key_flags; + + memcpy(status->gtk.key, data->key, sizeof(data->key)); + + /* if it's as long as the TKIP encryption key, copy MIC key */ + if (status->gtk.len == NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY) + memcpy(status->gtk.key + NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY, + data->tkip_mic_key, sizeof(data->tkip_mic_key)); +} + +static void iwl_mvm_convert_gtk_v3(struct iwl_wowlan_status_data *status, + struct iwl_wowlan_gtk_status_v3 *data) +{ + /* The parts we need are identical in v2 and v3 */ +#define CHECK(_f) do { \ + BUILD_BUG_ON(offsetof(struct iwl_wowlan_gtk_status_v2, _f) != \ + offsetof(struct iwl_wowlan_gtk_status_v3, _f)); \ + BUILD_BUG_ON(offsetofend(struct iwl_wowlan_gtk_status_v2, _f) !=\ + offsetofend(struct iwl_wowlan_gtk_status_v3, _f)); \ +} while (0) + + CHECK(key); + CHECK(key_len); + CHECK(key_flags); + CHECK(tkip_mic_key); +#undef CHECK + + iwl_mvm_convert_gtk_v2(status, (void *)data); +} + +static void iwl_mvm_convert_igtk(struct iwl_wowlan_status_data *status, + struct iwl_wowlan_igtk_status *data) +{ + const u8 *ipn = data->ipn; + + BUILD_BUG_ON(sizeof(status->igtk.key) < sizeof(data->key)); + + status->igtk.len = data->key_len; + status->igtk.flags = data->key_flags; + + memcpy(status->igtk.key, data->key, sizeof(data->key)); + + status->igtk.ipn = ((u64)ipn[5] << 0) | + ((u64)ipn[4] << 8) | + ((u64)ipn[3] << 16) | + ((u64)ipn[2] << 24) | + ((u64)ipn[1] << 32) | + ((u64)ipn[0] << 40); +} + +static void iwl_mvm_parse_wowlan_info_notif(struct iwl_mvm *mvm, + struct iwl_wowlan_info_notif *data, + struct iwl_wowlan_status_data *status, + u32 len) +{ + u32 i; + + if (!data) { + IWL_ERR(mvm, "iwl_wowlan_info_notif data is NULL\n"); + status = NULL; + return; + } + + if (len < sizeof(*data)) { + IWL_ERR(mvm, "Invalid WoWLAN info notification!\n"); + status = NULL; + return; + } + + iwl_mvm_convert_key_counters_v5(status, &data->gtk[0].sc); + iwl_mvm_convert_gtk_v3(status, &data->gtk[0]); + iwl_mvm_convert_igtk(status, &data->igtk[0]); + + status->replay_ctr = le64_to_cpu(data->replay_ctr); + status->pattern_number = le16_to_cpu(data->pattern_number); + for (i = 0; i < IWL_MAX_TID_COUNT; i++) + status->qos_seq_ctr[i] = + le16_to_cpu(data->qos_seq_ctr[i]); + status->wakeup_reasons = le32_to_cpu(data->wakeup_reasons); + status->num_of_gtk_rekeys = + le32_to_cpu(data->num_of_gtk_rekeys); + status->received_beacons = le32_to_cpu(data->received_beacons); + status->tid_tear_down = data->tid_tear_down; +} + +/* Occasionally, templates would be nice. This is one of those times ... */ +#define iwl_mvm_parse_wowlan_status_common(_ver) \ +static struct iwl_wowlan_status_data * \ +iwl_mvm_parse_wowlan_status_common_ ## _ver(struct iwl_mvm *mvm, \ + struct iwl_wowlan_status_ ##_ver *data,\ + int len) \ +{ \ + struct iwl_wowlan_status_data *status; \ + int data_size, i; \ + \ + if (len < sizeof(*data)) { \ + IWL_ERR(mvm, "Invalid WoWLAN status response!\n"); \ + return NULL; \ + } \ + \ + data_size = ALIGN(le32_to_cpu(data->wake_packet_bufsize), 4); \ + if (len != sizeof(*data) + data_size) { \ + IWL_ERR(mvm, "Invalid WoWLAN status response!\n"); \ + return NULL; \ + } \ + \ + status = kzalloc(sizeof(*status), GFP_KERNEL); \ + if (!status) \ + return NULL; \ + \ + /* copy all the common fields */ \ + status->replay_ctr = le64_to_cpu(data->replay_ctr); \ + status->pattern_number = le16_to_cpu(data->pattern_number); \ + status->non_qos_seq_ctr = le16_to_cpu(data->non_qos_seq_ctr); \ + for (i = 0; i < 8; i++) \ + status->qos_seq_ctr[i] = \ + le16_to_cpu(data->qos_seq_ctr[i]); \ + status->wakeup_reasons = le32_to_cpu(data->wakeup_reasons); \ + status->num_of_gtk_rekeys = \ + le32_to_cpu(data->num_of_gtk_rekeys); \ + status->received_beacons = le32_to_cpu(data->received_beacons); \ + status->wake_packet_length = \ + le32_to_cpu(data->wake_packet_length); \ + status->wake_packet_bufsize = \ + le32_to_cpu(data->wake_packet_bufsize); \ + if (status->wake_packet_bufsize) { \ + status->wake_packet = \ + kmemdup(data->wake_packet, \ + status->wake_packet_bufsize, \ + GFP_KERNEL); \ + if (!status->wake_packet) { \ + kfree(status); \ + return NULL; \ + } \ + } else { \ + status->wake_packet = NULL; \ + } \ + \ + return status; \ +} + +iwl_mvm_parse_wowlan_status_common(v6) +iwl_mvm_parse_wowlan_status_common(v7) +iwl_mvm_parse_wowlan_status_common(v9) +iwl_mvm_parse_wowlan_status_common(v12) + +static struct iwl_wowlan_status_data * +iwl_mvm_send_wowlan_get_status(struct iwl_mvm *mvm, u8 sta_id) +{ + struct iwl_wowlan_status_data *status; + struct iwl_wowlan_get_status_cmd get_status_cmd = { + .sta_id = cpu_to_le32(sta_id), + }; + struct iwl_host_cmd cmd = { + .id = WOWLAN_GET_STATUSES, + .flags = CMD_WANT_SKB, + .data = { &get_status_cmd, }, + .len = { sizeof(get_status_cmd), }, + }; + int ret, len; + u8 notif_ver; + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd.id, + IWL_FW_CMD_VER_UNKNOWN); + + if (cmd_ver == IWL_FW_CMD_VER_UNKNOWN) + cmd.len[0] = 0; + + lockdep_assert_held(&mvm->mutex); + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (ret) { + IWL_ERR(mvm, "failed to query wakeup status (%d)\n", ret); + return ERR_PTR(ret); + } + + len = iwl_rx_packet_payload_len(cmd.resp_pkt); + + /* default to 7 (when we have IWL_UCODE_TLV_API_WOWLAN_KEY_MATERIAL) */ + notif_ver = iwl_fw_lookup_notif_ver(mvm->fw, LONG_GROUP, + WOWLAN_GET_STATUSES, 0); + if (!notif_ver) + notif_ver = iwl_fw_lookup_notif_ver(mvm->fw, LEGACY_GROUP, + WOWLAN_GET_STATUSES, 7); + + if (!fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_WOWLAN_KEY_MATERIAL)) { + struct iwl_wowlan_status_v6 *v6 = (void *)cmd.resp_pkt->data; + + status = iwl_mvm_parse_wowlan_status_common_v6(mvm, v6, len); + if (!status) + goto out_free_resp; + + BUILD_BUG_ON(sizeof(v6->gtk.decrypt_key) > + sizeof(status->gtk.key)); + BUILD_BUG_ON(NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY + + sizeof(v6->gtk.tkip_mic_key) > + sizeof(status->gtk.key)); + + /* copy GTK info to the right place */ + memcpy(status->gtk.key, v6->gtk.decrypt_key, + sizeof(v6->gtk.decrypt_key)); + memcpy(status->gtk.key + NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY, + v6->gtk.tkip_mic_key, + sizeof(v6->gtk.tkip_mic_key)); + + iwl_mvm_convert_key_counters(status, &v6->gtk.rsc.all_tsc_rsc); + + /* hardcode the key length to 16 since v6 only supports 16 */ + status->gtk.len = 16; + + /* + * The key index only uses 2 bits (values 0 to 3) and + * we always set bit 7 which means this is the + * currently used key. + */ + status->gtk.flags = v6->gtk.key_index | BIT(7); + } else if (notif_ver == 7) { + struct iwl_wowlan_status_v7 *v7 = (void *)cmd.resp_pkt->data; + + status = iwl_mvm_parse_wowlan_status_common_v7(mvm, v7, len); + if (!status) + goto out_free_resp; + + iwl_mvm_convert_key_counters(status, &v7->gtk[0].rsc.all_tsc_rsc); + iwl_mvm_convert_gtk_v2(status, &v7->gtk[0]); + iwl_mvm_convert_igtk(status, &v7->igtk[0]); + } else if (notif_ver == 9 || notif_ver == 10 || notif_ver == 11) { + struct iwl_wowlan_status_v9 *v9 = (void *)cmd.resp_pkt->data; + + /* these three command versions have same layout and size, the + * difference is only in a few not used (reserved) fields. + */ + status = iwl_mvm_parse_wowlan_status_common_v9(mvm, v9, len); + if (!status) + goto out_free_resp; + + iwl_mvm_convert_key_counters(status, &v9->gtk[0].rsc.all_tsc_rsc); + iwl_mvm_convert_gtk_v2(status, &v9->gtk[0]); + iwl_mvm_convert_igtk(status, &v9->igtk[0]); + + status->tid_tear_down = v9->tid_tear_down; + } else if (notif_ver == 12) { + struct iwl_wowlan_status_v12 *v12 = (void *)cmd.resp_pkt->data; + + status = iwl_mvm_parse_wowlan_status_common_v12(mvm, v12, len); + if (!status) + goto out_free_resp; + + iwl_mvm_convert_key_counters_v5(status, &v12->gtk[0].sc); + iwl_mvm_convert_gtk_v3(status, &v12->gtk[0]); + iwl_mvm_convert_igtk(status, &v12->igtk[0]); + + status->tid_tear_down = v12->tid_tear_down; + } else { + IWL_ERR(mvm, + "Firmware advertises unknown WoWLAN status response %d!\n", + notif_ver); + status = NULL; + } + +out_free_resp: + iwl_free_resp(&cmd); + return status; +} + +/* releases the MVM mutex */ +static bool iwl_mvm_query_wakeup_reasons(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_wowlan_status_data *status) +{ + int i; + bool keep; + struct iwl_mvm_sta *mvm_ap_sta; + + if (!status) + goto out_unlock; + + IWL_DEBUG_WOWLAN(mvm, "wakeup reason 0x%x\n", + status->wakeup_reasons); + + /* still at hard-coded place 0 for D3 image */ + mvm_ap_sta = iwl_mvm_sta_from_staid_protected(mvm, 0); + if (!mvm_ap_sta) + goto out_unlock; + + for (i = 0; i < IWL_MAX_TID_COUNT; i++) { + u16 seq = status->qos_seq_ctr[i]; + /* firmware stores last-used value, we store next value */ + seq += 0x10; + mvm_ap_sta->tid_data[i].seq_number = seq; + } + + if (mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_22000) { + i = mvm->offload_tid; + iwl_trans_set_q_ptrs(mvm->trans, + mvm_ap_sta->tid_data[i].txq_id, + mvm_ap_sta->tid_data[i].seq_number >> 4); + } + + /* now we have all the data we need, unlock to avoid mac80211 issues */ + mutex_unlock(&mvm->mutex); + + iwl_mvm_report_wakeup_reasons(mvm, vif, status); + + keep = iwl_mvm_setup_connection_keep(mvm, vif, status); + + return keep; + +out_unlock: + mutex_unlock(&mvm->mutex); + return false; +} + +#define ND_QUERY_BUF_LEN (sizeof(struct iwl_scan_offload_profile_match) * \ + IWL_SCAN_MAX_PROFILES) + +struct iwl_mvm_nd_results { + u32 matched_profiles; + u8 matches[ND_QUERY_BUF_LEN]; +}; + +static int +iwl_mvm_netdetect_query_results(struct iwl_mvm *mvm, + struct iwl_mvm_nd_results *results) +{ + struct iwl_scan_offload_match_info *query; + struct iwl_host_cmd cmd = { + .id = SCAN_OFFLOAD_PROFILES_QUERY_CMD, + .flags = CMD_WANT_SKB, + }; + int ret, len; + size_t query_len, matches_len; + int max_profiles = iwl_umac_scan_get_max_profiles(mvm->fw); + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (ret) { + IWL_ERR(mvm, "failed to query matched profiles (%d)\n", ret); + return ret; + } + + if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_SCAN_OFFLOAD_CHANS)) { + query_len = sizeof(struct iwl_scan_offload_match_info); + matches_len = sizeof(struct iwl_scan_offload_profile_match) * + max_profiles; + } else { + query_len = sizeof(struct iwl_scan_offload_profiles_query_v1); + matches_len = sizeof(struct iwl_scan_offload_profile_match_v1) * + max_profiles; + } + + len = iwl_rx_packet_payload_len(cmd.resp_pkt); + if (len < query_len) { + IWL_ERR(mvm, "Invalid scan offload profiles query response!\n"); + ret = -EIO; + goto out_free_resp; + } + + query = (void *)cmd.resp_pkt->data; + + results->matched_profiles = le32_to_cpu(query->matched_profiles); + memcpy(results->matches, query->matches, matches_len); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + mvm->last_netdetect_scans = le32_to_cpu(query->n_scans_done); +#endif + +out_free_resp: + iwl_free_resp(&cmd); + return ret; +} + +static int iwl_mvm_query_num_match_chans(struct iwl_mvm *mvm, + struct iwl_mvm_nd_results *results, + int idx) +{ + int n_chans = 0, i; + + if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_SCAN_OFFLOAD_CHANS)) { + struct iwl_scan_offload_profile_match *matches = + (void *)results->matches; + + for (i = 0; i < SCAN_OFFLOAD_MATCHING_CHANNELS_LEN; i++) + n_chans += hweight8(matches[idx].matching_channels[i]); + } else { + struct iwl_scan_offload_profile_match_v1 *matches = + (void *)results->matches; + + for (i = 0; i < SCAN_OFFLOAD_MATCHING_CHANNELS_LEN_V1; i++) + n_chans += hweight8(matches[idx].matching_channels[i]); + } + + return n_chans; +} + +static void iwl_mvm_query_set_freqs(struct iwl_mvm *mvm, + struct iwl_mvm_nd_results *results, + struct cfg80211_wowlan_nd_match *match, + int idx) +{ + int i; + + if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_SCAN_OFFLOAD_CHANS)) { + struct iwl_scan_offload_profile_match *matches = + (void *)results->matches; + + for (i = 0; i < SCAN_OFFLOAD_MATCHING_CHANNELS_LEN * 8; i++) + if (matches[idx].matching_channels[i / 8] & (BIT(i % 8))) + match->channels[match->n_channels++] = + mvm->nd_channels[i]->center_freq; + } else { + struct iwl_scan_offload_profile_match_v1 *matches = + (void *)results->matches; + + for (i = 0; i < SCAN_OFFLOAD_MATCHING_CHANNELS_LEN_V1 * 8; i++) + if (matches[idx].matching_channels[i / 8] & (BIT(i % 8))) + match->channels[match->n_channels++] = + mvm->nd_channels[i]->center_freq; + } +} + +/** + * enum iwl_d3_notif - d3 notifications + * @IWL_D3_NOTIF_WOWLAN_INFO: WOWLAN_INFO_NOTIF was received + * @IWL_D3_NOTIF_WOWLAN_WAKE_PKT: WOWLAN_WAKE_PKT_NOTIF was received + * @IWL_D3_NOTIF_PROT_OFFLOAD: PROT_OFFLOAD_NOTIF was received + * @IWL_D3_ND_MATCH_INFO: OFFLOAD_MATCH_INFO_NOTIF was received + * @IWL_D3_NOTIF_D3_END_NOTIF: D3_END_NOTIF was received + */ +enum iwl_d3_notif { + IWL_D3_NOTIF_WOWLAN_INFO = BIT(0), + IWL_D3_NOTIF_WOWLAN_WAKE_PKT = BIT(1), + IWL_D3_NOTIF_PROT_OFFLOAD = BIT(2), + IWL_D3_ND_MATCH_INFO = BIT(3), + IWL_D3_NOTIF_D3_END_NOTIF = BIT(4) +}; + +/* manage d3 resume data */ +struct iwl_d3_data { + struct iwl_wowlan_status_data *status; + bool test; + u32 d3_end_flags; + u32 notif_expected; /* bitmap - see &enum iwl_d3_notif */ + u32 notif_received; /* bitmap - see &enum iwl_d3_notif */ + struct iwl_mvm_nd_results *nd_results; + bool nd_results_valid; +}; + +static void iwl_mvm_query_netdetect_reasons(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_d3_data *d3_data) +{ + struct cfg80211_wowlan_nd_info *net_detect = NULL; + struct cfg80211_wowlan_wakeup wakeup = { + .pattern_idx = -1, + }; + struct cfg80211_wowlan_wakeup *wakeup_report = &wakeup; + unsigned long matched_profiles; + u32 reasons = 0; + int i, n_matches, ret; + + if (WARN_ON(!d3_data || !d3_data->status)) + goto out; + + reasons = d3_data->status->wakeup_reasons; + + if (reasons & IWL_WOWLAN_WAKEUP_BY_RFKILL_DEASSERTED) + wakeup.rfkill_release = true; + + if (reasons != IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS) + goto out; + + if (!iwl_fw_lookup_notif_ver(mvm->fw, PROT_OFFLOAD_GROUP, + WOWLAN_INFO_NOTIFICATION, 0)) { + IWL_INFO(mvm, "Query FW for ND results\n"); + ret = iwl_mvm_netdetect_query_results(mvm, d3_data->nd_results); + + } else { + IWL_INFO(mvm, "Notification based ND results\n"); + ret = d3_data->nd_results_valid ? 0 : -1; + } + + if (ret || !d3_data->nd_results->matched_profiles) { + wakeup_report = NULL; + goto out; + } + + matched_profiles = d3_data->nd_results->matched_profiles; + if (mvm->n_nd_match_sets) { + n_matches = hweight_long(matched_profiles); + } else { + IWL_ERR(mvm, "no net detect match information available\n"); + n_matches = 0; + } + + net_detect = kzalloc(struct_size(net_detect, matches, n_matches), + GFP_KERNEL); + if (!net_detect || !n_matches) + goto out_report_nd; + + for_each_set_bit(i, &matched_profiles, mvm->n_nd_match_sets) { + struct cfg80211_wowlan_nd_match *match; + int idx, n_channels = 0; + + n_channels = iwl_mvm_query_num_match_chans(mvm, + d3_data->nd_results, + i); + + match = kzalloc(struct_size(match, channels, n_channels), + GFP_KERNEL); + if (!match) + goto out_report_nd; + + net_detect->matches[net_detect->n_matches++] = match; + + /* We inverted the order of the SSIDs in the scan + * request, so invert the index here. + */ + idx = mvm->n_nd_match_sets - i - 1; + match->ssid.ssid_len = mvm->nd_match_sets[idx].ssid.ssid_len; + memcpy(match->ssid.ssid, mvm->nd_match_sets[idx].ssid.ssid, + match->ssid.ssid_len); + + if (mvm->n_nd_channels < n_channels) + continue; + + iwl_mvm_query_set_freqs(mvm, d3_data->nd_results, match, i); + } + +out_report_nd: + wakeup.net_detect = net_detect; +out: + iwl_mvm_free_nd(mvm); + + mutex_unlock(&mvm->mutex); + ieee80211_report_wowlan_wakeup(vif, wakeup_report, GFP_KERNEL); + + if (net_detect) { + for (i = 0; i < net_detect->n_matches; i++) + kfree(net_detect->matches[i]); + kfree(net_detect); + } +} + +static void iwl_mvm_d3_disconnect_iter(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + /* skip the one we keep connection on */ + if (data == vif) + return; + + if (vif->type == NL80211_IFTYPE_STATION) + ieee80211_resume_disconnect(vif); +} + +static bool iwl_mvm_rt_status(struct iwl_trans *trans, u32 base, u32 *err_id) +{ + struct error_table_start { + /* cf. struct iwl_error_event_table */ + u32 valid; + __le32 err_id; + } err_info; + + if (!base) + return false; + + iwl_trans_read_mem_bytes(trans, base, + &err_info, sizeof(err_info)); + if (err_info.valid && err_id) + *err_id = le32_to_cpu(err_info.err_id); + + return !!err_info.valid; +} + +static bool iwl_mvm_check_rt_status(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + u32 err_id; + + /* check for lmac1 error */ + if (iwl_mvm_rt_status(mvm->trans, + mvm->trans->dbg.lmac_error_event_table[0], + &err_id)) { + if (err_id == RF_KILL_INDICATOR_FOR_WOWLAN) { + struct cfg80211_wowlan_wakeup wakeup = { + .rfkill_release = true, + }; + ieee80211_report_wowlan_wakeup(vif, &wakeup, + GFP_KERNEL); + } + return true; + } + + /* check if we have lmac2 set and check for error */ + if (iwl_mvm_rt_status(mvm->trans, + mvm->trans->dbg.lmac_error_event_table[1], NULL)) + return true; + + /* check for umac error */ + if (iwl_mvm_rt_status(mvm->trans, + mvm->trans->dbg.umac_error_event_table, NULL)) + return true; + + return false; +} + +/* + * This function assumes: + * 1. The mutex is already held. + * 2. The callee functions unlock the mutex. + */ +static bool +iwl_mvm_choose_query_wakeup_reasons(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_d3_data *d3_data) +{ + lockdep_assert_held(&mvm->mutex); + + /* if FW uses status notification, status shouldn't be NULL here */ + if (!d3_data->status) { + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u8 sta_id = mvm->net_detect ? IWL_MVM_INVALID_STA : mvmvif->ap_sta_id; + + d3_data->status = iwl_mvm_send_wowlan_get_status(mvm, sta_id); + } + + if (mvm->net_detect) { + iwl_mvm_query_netdetect_reasons(mvm, vif, d3_data); + } else { + bool keep = iwl_mvm_query_wakeup_reasons(mvm, vif, + d3_data->status); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (keep) + mvm->keep_vif = vif; +#endif + + return keep; + } + return false; +} + +#define IWL_WOWLAN_WAKEUP_REASON_HAS_WAKEUP_PKT (IWL_WOWLAN_WAKEUP_BY_MAGIC_PACKET | \ + IWL_WOWLAN_WAKEUP_BY_PATTERN | \ + IWL_WAKEUP_BY_PATTERN_IPV4_TCP_SYN |\ + IWL_WAKEUP_BY_PATTERN_IPV4_TCP_SYN_WILDCARD |\ + IWL_WAKEUP_BY_PATTERN_IPV6_TCP_SYN |\ + IWL_WAKEUP_BY_PATTERN_IPV6_TCP_SYN_WILDCARD) + +static int iwl_mvm_wowlan_store_wake_pkt(struct iwl_mvm *mvm, + struct iwl_wowlan_wake_pkt_notif *notif, + struct iwl_wowlan_status_data *status, + u32 len) +{ + u32 data_size, packet_len = le32_to_cpu(notif->wake_packet_length); + + if (len < sizeof(*notif)) { + IWL_ERR(mvm, "Invalid WoWLAN wake packet notification!\n"); + return -EIO; + } + + if (WARN_ON(!status)) { + IWL_ERR(mvm, "Got wake packet notification but wowlan status data is NULL\n"); + return -EIO; + } + + if (WARN_ON(!(status->wakeup_reasons & + IWL_WOWLAN_WAKEUP_REASON_HAS_WAKEUP_PKT))) { + IWL_ERR(mvm, "Got wakeup packet but wakeup reason is %x\n", + status->wakeup_reasons); + return -EIO; + } + + data_size = len - offsetof(struct iwl_wowlan_wake_pkt_notif, wake_packet); + + /* data_size got the padding from the notification, remove it. */ + if (packet_len < data_size) + data_size = packet_len; + + status->wake_packet = kmemdup(notif->wake_packet, data_size, + GFP_ATOMIC); + + if (!status->wake_packet) + return -ENOMEM; + + status->wake_packet_length = packet_len; + status->wake_packet_bufsize = data_size; + + return 0; +} + +static void iwl_mvm_nd_match_info_handler(struct iwl_mvm *mvm, + struct iwl_d3_data *d3_data, + struct iwl_scan_offload_match_info *notif, + u32 len) +{ + struct iwl_wowlan_status_data *status = d3_data->status; + struct ieee80211_vif *vif = iwl_mvm_get_bss_vif(mvm); + struct iwl_mvm_nd_results *results = d3_data->nd_results; + size_t i, matches_len = sizeof(struct iwl_scan_offload_profile_match) * + iwl_umac_scan_get_max_profiles(mvm->fw); + + if (IS_ERR_OR_NULL(vif)) + return; + + if (len < sizeof(struct iwl_scan_offload_match_info)) { + IWL_ERR(mvm, "Invalid scan match info notification\n"); + return; + } + + if (!mvm->net_detect) { + IWL_ERR(mvm, "Unexpected scan match info notification\n"); + return; + } + + if (!status || status->wakeup_reasons != IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS) { + IWL_ERR(mvm, + "Ignore scan match info notification: no reason\n"); + return; + } + +#ifdef CONFIG_IWLWIFI_DEBUGFS + mvm->last_netdetect_scans = le32_to_cpu(notif->n_scans_done); +#endif + + results->matched_profiles = le32_to_cpu(notif->matched_profiles); + IWL_INFO(mvm, "number of matched profiles=%u\n", + results->matched_profiles); + + if (results->matched_profiles) { + memcpy(results->matches, notif->matches, matches_len); + d3_data->nd_results_valid = TRUE; + } + + /* no scan should be active at this point */ + mvm->scan_status = 0; + for (i = 0; i < mvm->max_scans; i++) + mvm->scan_uid_status[i] = 0; +} + +static bool iwl_mvm_wait_d3_notif(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_d3_data *d3_data = data; + u32 len; + int ret; + int wowlan_info_ver = iwl_fw_lookup_notif_ver(mvm->fw, + PROT_OFFLOAD_GROUP, + WOWLAN_INFO_NOTIFICATION, + IWL_FW_CMD_VER_UNKNOWN); + + + switch (WIDE_ID(pkt->hdr.group_id, pkt->hdr.cmd)) { + case WIDE_ID(PROT_OFFLOAD_GROUP, WOWLAN_INFO_NOTIFICATION): { + struct iwl_wowlan_info_notif *notif; + + if (d3_data->notif_received & IWL_D3_NOTIF_WOWLAN_INFO) { + /* We might get two notifications due to dual bss */ + IWL_DEBUG_WOWLAN(mvm, + "Got additional wowlan info notification\n"); + break; + } + + if (wowlan_info_ver < 2) { + struct iwl_wowlan_info_notif_v1 *notif_v1 = (void *)pkt->data; + + notif = kmemdup(notif_v1, sizeof(*notif), GFP_ATOMIC); + if (!notif) + return false; + + notif->tid_tear_down = notif_v1->tid_tear_down; + notif->station_id = notif_v1->station_id; + memset_after(notif, 0, station_id); + } else { + notif = (void *)pkt->data; + } + + d3_data->notif_received |= IWL_D3_NOTIF_WOWLAN_INFO; + len = iwl_rx_packet_payload_len(pkt); + iwl_mvm_parse_wowlan_info_notif(mvm, notif, d3_data->status, + len); + + if (wowlan_info_ver < 2) + kfree(notif); + + if (d3_data->status && + d3_data->status->wakeup_reasons & IWL_WOWLAN_WAKEUP_REASON_HAS_WAKEUP_PKT) + /* We are supposed to get also wake packet notif */ + d3_data->notif_expected |= IWL_D3_NOTIF_WOWLAN_WAKE_PKT; + + break; + } + case WIDE_ID(PROT_OFFLOAD_GROUP, WOWLAN_WAKE_PKT_NOTIFICATION): { + struct iwl_wowlan_wake_pkt_notif *notif = (void *)pkt->data; + + if (d3_data->notif_received & IWL_D3_NOTIF_WOWLAN_WAKE_PKT) { + /* We shouldn't get two wake packet notifications */ + IWL_ERR(mvm, + "Got additional wowlan wake packet notification\n"); + } else { + d3_data->notif_received |= IWL_D3_NOTIF_WOWLAN_WAKE_PKT; + len = iwl_rx_packet_payload_len(pkt); + ret = iwl_mvm_wowlan_store_wake_pkt(mvm, notif, + d3_data->status, + len); + if (ret) + IWL_ERR(mvm, + "Can't parse WOWLAN_WAKE_PKT_NOTIFICATION\n"); + } + + break; + } + case WIDE_ID(SCAN_GROUP, OFFLOAD_MATCH_INFO_NOTIF): { + struct iwl_scan_offload_match_info *notif = (void *)pkt->data; + + if (d3_data->notif_received & IWL_D3_ND_MATCH_INFO) { + IWL_ERR(mvm, + "Got additional netdetect match info\n"); + break; + } + + d3_data->notif_received |= IWL_D3_ND_MATCH_INFO; + + /* explicitly set this in the 'expected' as well */ + d3_data->notif_expected |= IWL_D3_ND_MATCH_INFO; + + len = iwl_rx_packet_payload_len(pkt); + iwl_mvm_nd_match_info_handler(mvm, d3_data, notif, len); + break; + } + case WIDE_ID(PROT_OFFLOAD_GROUP, D3_END_NOTIFICATION): { + struct iwl_mvm_d3_end_notif *notif = (void *)pkt->data; + + d3_data->d3_end_flags = __le32_to_cpu(notif->flags); + d3_data->notif_received |= IWL_D3_NOTIF_D3_END_NOTIF; + + break; + } + default: + WARN_ON(1); + } + + return d3_data->notif_received == d3_data->notif_expected; +} + +static int iwl_mvm_resume_firmware(struct iwl_mvm *mvm, bool test) +{ + int ret; + enum iwl_d3_status d3_status; + struct iwl_host_cmd cmd = { + .id = D0I3_END_CMD, + .flags = CMD_WANT_SKB | CMD_SEND_IN_D3, + }; + bool reset = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG); + + ret = iwl_trans_d3_resume(mvm->trans, &d3_status, test, !reset); + if (ret) + return ret; + + if (d3_status != IWL_D3_STATUS_ALIVE) { + IWL_INFO(mvm, "Device was reset during suspend\n"); + return -ENOENT; + } + + /* + * We should trigger resume flow using command only for 22000 family + * AX210 and above don't need the command since they have + * the doorbell interrupt. + */ + if (mvm->trans->trans_cfg->device_family <= IWL_DEVICE_FAMILY_22000 && + fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_D0I3_END_FIRST)) { + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (ret < 0) + IWL_ERR(mvm, "Failed to send D0I3_END_CMD first (%d)\n", + ret); + } + + return ret; +} + +#define IWL_MVM_D3_NOTIF_TIMEOUT (HZ / 5) + +static int iwl_mvm_d3_notif_wait(struct iwl_mvm *mvm, + struct iwl_d3_data *d3_data) +{ + static const u16 d3_resume_notif[] = { + WIDE_ID(PROT_OFFLOAD_GROUP, WOWLAN_INFO_NOTIFICATION), + WIDE_ID(PROT_OFFLOAD_GROUP, WOWLAN_WAKE_PKT_NOTIFICATION), + WIDE_ID(SCAN_GROUP, OFFLOAD_MATCH_INFO_NOTIF), + WIDE_ID(PROT_OFFLOAD_GROUP, D3_END_NOTIFICATION) + }; + struct iwl_notification_wait wait_d3_notif; + int ret; + + iwl_init_notification_wait(&mvm->notif_wait, &wait_d3_notif, + d3_resume_notif, ARRAY_SIZE(d3_resume_notif), + iwl_mvm_wait_d3_notif, d3_data); + + ret = iwl_mvm_resume_firmware(mvm, d3_data->test); + if (ret) { + iwl_remove_notification(&mvm->notif_wait, &wait_d3_notif); + return ret; + } + + return iwl_wait_notification(&mvm->notif_wait, &wait_d3_notif, + IWL_MVM_D3_NOTIF_TIMEOUT); +} + +static inline bool iwl_mvm_d3_resume_notif_based(struct iwl_mvm *mvm) +{ + return iwl_fw_lookup_notif_ver(mvm->fw, PROT_OFFLOAD_GROUP, + WOWLAN_INFO_NOTIFICATION, 0) && + iwl_fw_lookup_notif_ver(mvm->fw, PROT_OFFLOAD_GROUP, + WOWLAN_WAKE_PKT_NOTIFICATION, 0) && + iwl_fw_lookup_notif_ver(mvm->fw, PROT_OFFLOAD_GROUP, + D3_END_NOTIFICATION, 0); +} + +static int __iwl_mvm_resume(struct iwl_mvm *mvm, bool test) +{ + struct ieee80211_vif *vif = NULL; + int ret = 1; + struct iwl_mvm_nd_results results = {}; + struct iwl_d3_data d3_data = { + .test = test, + .notif_expected = + IWL_D3_NOTIF_WOWLAN_INFO | + IWL_D3_NOTIF_D3_END_NOTIF, + .nd_results_valid = false, + .nd_results = &results, + }; + bool unified_image = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG); + bool d0i3_first = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_D0I3_END_FIRST); + bool resume_notif_based = iwl_mvm_d3_resume_notif_based(mvm); + bool keep = false; + + mutex_lock(&mvm->mutex); + + mvm->last_reset_or_resume_time_jiffies = jiffies; + + /* get the BSS vif pointer again */ + vif = iwl_mvm_get_bss_vif(mvm); + if (IS_ERR_OR_NULL(vif)) + goto err; + + iwl_fw_dbg_read_d3_debug_data(&mvm->fwrt); + + if (iwl_mvm_check_rt_status(mvm, vif)) { + set_bit(STATUS_FW_ERROR, &mvm->trans->status); + iwl_mvm_dump_nic_error_log(mvm); + iwl_dbg_tlv_time_point(&mvm->fwrt, + IWL_FW_INI_TIME_POINT_FW_ASSERT, NULL); + iwl_fw_dbg_collect_desc(&mvm->fwrt, &iwl_dump_desc_assert, + false, 0); + ret = 1; + goto err; + } + + if (resume_notif_based) { + d3_data.status = kzalloc(sizeof(*d3_data.status), GFP_KERNEL); + if (!d3_data.status) { + IWL_ERR(mvm, "Failed to allocate wowlan status\n"); + ret = -ENOMEM; + goto err; + } + + ret = iwl_mvm_d3_notif_wait(mvm, &d3_data); + if (ret) + goto err; + } else { + ret = iwl_mvm_resume_firmware(mvm, test); + if (ret < 0) + goto err; + } + + /* after the successful handshake, we're out of D3 */ + mvm->trans->system_pm_mode = IWL_PLAT_PM_MODE_DISABLED; + + /* when reset is required we can't send these following commands */ + if (d3_data.d3_end_flags & IWL_D0I3_RESET_REQUIRE) + goto query_wakeup_reasons; + + /* + * Query the current location and source from the D3 firmware so we + * can play it back when we re-intiailize the D0 firmware + */ + iwl_mvm_update_changed_regdom(mvm); + + /* Re-configure PPAG settings */ + iwl_mvm_ppag_send_cmd(mvm); + + if (!unified_image) + /* Re-configure default SAR profile */ + iwl_mvm_sar_select_profile(mvm, 1, 1); + + if (mvm->net_detect && unified_image) { + /* If this is a non-unified image, we restart the FW, + * so no need to stop the netdetect scan. If that + * fails, continue and try to get the wake-up reasons, + * but trigger a HW restart by keeping a failure code + * in ret. + */ + ret = iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_NETDETECT, + false); + } + +query_wakeup_reasons: + keep = iwl_mvm_choose_query_wakeup_reasons(mvm, vif, &d3_data); + /* has unlocked the mutex, so skip that */ + goto out; + +err: + mutex_unlock(&mvm->mutex); +out: + if (d3_data.status) + kfree(d3_data.status->wake_packet); + kfree(d3_data.status); + iwl_mvm_free_nd(mvm); + + if (!d3_data.test && !mvm->net_detect) + ieee80211_iterate_active_interfaces_mtx(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_d3_disconnect_iter, + keep ? vif : NULL); + + clear_bit(IWL_MVM_STATUS_IN_D3, &mvm->status); + + /* no need to reset the device in unified images, if successful */ + if (unified_image && !ret) { + /* nothing else to do if we already sent D0I3_END_CMD */ + if (d0i3_first) + return 0; + + if (!iwl_fw_lookup_notif_ver(mvm->fw, PROT_OFFLOAD_GROUP, + D3_END_NOTIFICATION, 0)) { + ret = iwl_mvm_send_cmd_pdu(mvm, D0I3_END_CMD, 0, 0, NULL); + if (!ret) + return 0; + } else if (!(d3_data.d3_end_flags & IWL_D0I3_RESET_REQUIRE)) { + return 0; + } + } + + /* + * Reconfigure the device in one of the following cases: + * 1. We are not using a unified image + * 2. We are using a unified image but had an error while exiting D3 + */ + set_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, &mvm->status); + + /* regardless of what happened, we're now out of D3 */ + mvm->trans->system_pm_mode = IWL_PLAT_PM_MODE_DISABLED; + + return 1; +} + +int iwl_mvm_resume(struct ieee80211_hw *hw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + ret = __iwl_mvm_resume(mvm, false); + + iwl_mvm_resume_tcm(mvm); + + iwl_fw_runtime_resume(&mvm->fwrt); + + return ret; +} + +void iwl_mvm_set_wakeup(struct ieee80211_hw *hw, bool enabled) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + device_set_wakeup_enable(mvm->trans->dev, enabled); +} + +#ifdef CONFIG_IWLWIFI_DEBUGFS +static int iwl_mvm_d3_test_open(struct inode *inode, struct file *file) +{ + struct iwl_mvm *mvm = inode->i_private; + int err; + + if (mvm->d3_test_active) + return -EBUSY; + + file->private_data = inode->i_private; + + iwl_mvm_pause_tcm(mvm, true); + + iwl_fw_runtime_suspend(&mvm->fwrt); + + /* start pseudo D3 */ + rtnl_lock(); + wiphy_lock(mvm->hw->wiphy); + err = __iwl_mvm_suspend(mvm->hw, mvm->hw->wiphy->wowlan_config, true); + wiphy_unlock(mvm->hw->wiphy); + rtnl_unlock(); + if (err > 0) + err = -EINVAL; + if (err) + return err; + + mvm->d3_test_active = true; + mvm->keep_vif = NULL; + return 0; +} + +static ssize_t iwl_mvm_d3_test_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + u32 pme_asserted; + + while (true) { + /* read pme_ptr if available */ + if (mvm->d3_test_pme_ptr) { + pme_asserted = iwl_trans_read_mem32(mvm->trans, + mvm->d3_test_pme_ptr); + if (pme_asserted) + break; + } + + if (msleep_interruptible(100)) + break; + } + + return 0; +} + +static void iwl_mvm_d3_test_disconn_work_iter(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + /* skip the one we keep connection on */ + if (_data == vif) + return; + + if (vif->type == NL80211_IFTYPE_STATION) + ieee80211_connection_loss(vif); +} + +static int iwl_mvm_d3_test_release(struct inode *inode, struct file *file) +{ + struct iwl_mvm *mvm = inode->i_private; + bool unified_image = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG); + + mvm->d3_test_active = false; + + iwl_fw_dbg_read_d3_debug_data(&mvm->fwrt); + + rtnl_lock(); + wiphy_lock(mvm->hw->wiphy); + __iwl_mvm_resume(mvm, true); + wiphy_unlock(mvm->hw->wiphy); + rtnl_unlock(); + + iwl_mvm_resume_tcm(mvm); + + iwl_fw_runtime_resume(&mvm->fwrt); + + iwl_abort_notification_waits(&mvm->notif_wait); + if (!unified_image) { + int remaining_time = 10; + + ieee80211_restart_hw(mvm->hw); + + /* wait for restart and disconnect all interfaces */ + while (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) && + remaining_time > 0) { + remaining_time--; + msleep(1000); + } + + if (remaining_time == 0) + IWL_ERR(mvm, "Timed out waiting for HW restart!\n"); + } + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_d3_test_disconn_work_iter, mvm->keep_vif); + + return 0; +} + +const struct file_operations iwl_dbgfs_d3_test_ops = { + .llseek = no_llseek, + .open = iwl_mvm_d3_test_open, + .read = iwl_mvm_d3_test_read, + .release = iwl_mvm_d3_test_release, +}; +#endif diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/debugfs-vif.c b/drivers/net/wireless/intel/iwlwifi/mvm/debugfs-vif.c new file mode 100644 index 000000000..78d8b37eb --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/debugfs-vif.c @@ -0,0 +1,772 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include "mvm.h" +#include "debugfs.h" + +static void iwl_dbgfs_update_pm(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + enum iwl_dbgfs_pm_mask param, int val) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_dbgfs_pm *dbgfs_pm = &mvmvif->dbgfs_pm; + + dbgfs_pm->mask |= param; + + switch (param) { + case MVM_DEBUGFS_PM_KEEP_ALIVE: { + int dtimper = vif->bss_conf.dtim_period ?: 1; + int dtimper_msec = dtimper * vif->bss_conf.beacon_int; + + IWL_DEBUG_POWER(mvm, "debugfs: set keep_alive= %d sec\n", val); + if (val * MSEC_PER_SEC < 3 * dtimper_msec) + IWL_WARN(mvm, + "debugfs: keep alive period (%ld msec) is less than minimum required (%d msec)\n", + val * MSEC_PER_SEC, 3 * dtimper_msec); + dbgfs_pm->keep_alive_seconds = val; + break; + } + case MVM_DEBUGFS_PM_SKIP_OVER_DTIM: + IWL_DEBUG_POWER(mvm, "skip_over_dtim %s\n", + val ? "enabled" : "disabled"); + dbgfs_pm->skip_over_dtim = val; + break; + case MVM_DEBUGFS_PM_SKIP_DTIM_PERIODS: + IWL_DEBUG_POWER(mvm, "skip_dtim_periods=%d\n", val); + dbgfs_pm->skip_dtim_periods = val; + break; + case MVM_DEBUGFS_PM_RX_DATA_TIMEOUT: + IWL_DEBUG_POWER(mvm, "rx_data_timeout=%d\n", val); + dbgfs_pm->rx_data_timeout = val; + break; + case MVM_DEBUGFS_PM_TX_DATA_TIMEOUT: + IWL_DEBUG_POWER(mvm, "tx_data_timeout=%d\n", val); + dbgfs_pm->tx_data_timeout = val; + break; + case MVM_DEBUGFS_PM_LPRX_ENA: + IWL_DEBUG_POWER(mvm, "lprx %s\n", val ? "enabled" : "disabled"); + dbgfs_pm->lprx_ena = val; + break; + case MVM_DEBUGFS_PM_LPRX_RSSI_THRESHOLD: + IWL_DEBUG_POWER(mvm, "lprx_rssi_threshold=%d\n", val); + dbgfs_pm->lprx_rssi_threshold = val; + break; + case MVM_DEBUGFS_PM_SNOOZE_ENABLE: + IWL_DEBUG_POWER(mvm, "snooze_enable=%d\n", val); + dbgfs_pm->snooze_ena = val; + break; + case MVM_DEBUGFS_PM_UAPSD_MISBEHAVING: + IWL_DEBUG_POWER(mvm, "uapsd_misbehaving_enable=%d\n", val); + dbgfs_pm->uapsd_misbehaving = val; + break; + case MVM_DEBUGFS_PM_USE_PS_POLL: + IWL_DEBUG_POWER(mvm, "use_ps_poll=%d\n", val); + dbgfs_pm->use_ps_poll = val; + break; + } +} + +static ssize_t iwl_dbgfs_pm_params_write(struct ieee80211_vif *vif, char *buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = mvmvif->mvm; + enum iwl_dbgfs_pm_mask param; + int val, ret; + + if (!strncmp("keep_alive=", buf, 11)) { + if (sscanf(buf + 11, "%d", &val) != 1) + return -EINVAL; + param = MVM_DEBUGFS_PM_KEEP_ALIVE; + } else if (!strncmp("skip_over_dtim=", buf, 15)) { + if (sscanf(buf + 15, "%d", &val) != 1) + return -EINVAL; + param = MVM_DEBUGFS_PM_SKIP_OVER_DTIM; + } else if (!strncmp("skip_dtim_periods=", buf, 18)) { + if (sscanf(buf + 18, "%d", &val) != 1) + return -EINVAL; + param = MVM_DEBUGFS_PM_SKIP_DTIM_PERIODS; + } else if (!strncmp("rx_data_timeout=", buf, 16)) { + if (sscanf(buf + 16, "%d", &val) != 1) + return -EINVAL; + param = MVM_DEBUGFS_PM_RX_DATA_TIMEOUT; + } else if (!strncmp("tx_data_timeout=", buf, 16)) { + if (sscanf(buf + 16, "%d", &val) != 1) + return -EINVAL; + param = MVM_DEBUGFS_PM_TX_DATA_TIMEOUT; + } else if (!strncmp("lprx=", buf, 5)) { + if (sscanf(buf + 5, "%d", &val) != 1) + return -EINVAL; + param = MVM_DEBUGFS_PM_LPRX_ENA; + } else if (!strncmp("lprx_rssi_threshold=", buf, 20)) { + if (sscanf(buf + 20, "%d", &val) != 1) + return -EINVAL; + if (val > POWER_LPRX_RSSI_THRESHOLD_MAX || val < + POWER_LPRX_RSSI_THRESHOLD_MIN) + return -EINVAL; + param = MVM_DEBUGFS_PM_LPRX_RSSI_THRESHOLD; + } else if (!strncmp("snooze_enable=", buf, 14)) { + if (sscanf(buf + 14, "%d", &val) != 1) + return -EINVAL; + param = MVM_DEBUGFS_PM_SNOOZE_ENABLE; + } else if (!strncmp("uapsd_misbehaving=", buf, 18)) { + if (sscanf(buf + 18, "%d", &val) != 1) + return -EINVAL; + param = MVM_DEBUGFS_PM_UAPSD_MISBEHAVING; + } else if (!strncmp("use_ps_poll=", buf, 12)) { + if (sscanf(buf + 12, "%d", &val) != 1) + return -EINVAL; + param = MVM_DEBUGFS_PM_USE_PS_POLL; + } else { + return -EINVAL; + } + + mutex_lock(&mvm->mutex); + iwl_dbgfs_update_pm(mvm, vif, param, val); + ret = iwl_mvm_power_update_mac(mvm); + mutex_unlock(&mvm->mutex); + + return ret ?: count; +} + +static ssize_t iwl_dbgfs_tx_pwr_lmt_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ieee80211_vif *vif = file->private_data; + char buf[64]; + int bufsz = sizeof(buf); + int pos; + + pos = scnprintf(buf, bufsz, "bss limit = %d\n", + vif->bss_conf.txpower); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_pm_params_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ieee80211_vif *vif = file->private_data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = mvmvif->mvm; + char buf[512]; + int bufsz = sizeof(buf); + int pos; + + pos = iwl_mvm_power_mac_dbgfs_read(mvm, vif, buf, bufsz); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_mac_params_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ieee80211_vif *vif = file->private_data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = mvmvif->mvm; + u8 ap_sta_id; + struct ieee80211_chanctx_conf *chanctx_conf; + char buf[512]; + int bufsz = sizeof(buf); + int pos = 0; + int i; + + mutex_lock(&mvm->mutex); + + ap_sta_id = mvmvif->ap_sta_id; + + switch (ieee80211_vif_type_p2p(vif)) { + case NL80211_IFTYPE_ADHOC: + pos += scnprintf(buf+pos, bufsz-pos, "type: ibss\n"); + break; + case NL80211_IFTYPE_STATION: + pos += scnprintf(buf+pos, bufsz-pos, "type: bss\n"); + break; + case NL80211_IFTYPE_AP: + pos += scnprintf(buf+pos, bufsz-pos, "type: ap\n"); + break; + case NL80211_IFTYPE_P2P_CLIENT: + pos += scnprintf(buf+pos, bufsz-pos, "type: p2p client\n"); + break; + case NL80211_IFTYPE_P2P_GO: + pos += scnprintf(buf+pos, bufsz-pos, "type: p2p go\n"); + break; + case NL80211_IFTYPE_P2P_DEVICE: + pos += scnprintf(buf+pos, bufsz-pos, "type: p2p dev\n"); + break; + default: + break; + } + + pos += scnprintf(buf+pos, bufsz-pos, "mac id/color: %d / %d\n", + mvmvif->id, mvmvif->color); + pos += scnprintf(buf+pos, bufsz-pos, "bssid: %pM\n", + vif->bss_conf.bssid); + pos += scnprintf(buf+pos, bufsz-pos, "Load: %d\n", + mvm->tcm.result.load[mvmvif->id]); + pos += scnprintf(buf+pos, bufsz-pos, "QoS:\n"); + for (i = 0; i < ARRAY_SIZE(mvmvif->queue_params); i++) + pos += scnprintf(buf+pos, bufsz-pos, + "\t%d: txop:%d - cw_min:%d - cw_max = %d - aifs = %d upasd = %d\n", + i, mvmvif->queue_params[i].txop, + mvmvif->queue_params[i].cw_min, + mvmvif->queue_params[i].cw_max, + mvmvif->queue_params[i].aifs, + mvmvif->queue_params[i].uapsd); + + if (vif->type == NL80211_IFTYPE_STATION && + ap_sta_id != IWL_MVM_INVALID_STA) { + struct iwl_mvm_sta *mvm_sta; + + mvm_sta = iwl_mvm_sta_from_staid_protected(mvm, ap_sta_id); + if (mvm_sta) { + pos += scnprintf(buf+pos, bufsz-pos, + "ap_sta_id %d - reduced Tx power %d\n", + ap_sta_id, + mvm_sta->bt_reduced_txpower); + } + } + + rcu_read_lock(); + chanctx_conf = rcu_dereference(vif->bss_conf.chanctx_conf); + if (chanctx_conf) + pos += scnprintf(buf+pos, bufsz-pos, + "idle rx chains %d, active rx chains: %d\n", + chanctx_conf->rx_chains_static, + chanctx_conf->rx_chains_dynamic); + rcu_read_unlock(); + + mutex_unlock(&mvm->mutex); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static void iwl_dbgfs_update_bf(struct ieee80211_vif *vif, + enum iwl_dbgfs_bf_mask param, int value) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_dbgfs_bf *dbgfs_bf = &mvmvif->dbgfs_bf; + + dbgfs_bf->mask |= param; + + switch (param) { + case MVM_DEBUGFS_BF_ENERGY_DELTA: + dbgfs_bf->bf_energy_delta = value; + break; + case MVM_DEBUGFS_BF_ROAMING_ENERGY_DELTA: + dbgfs_bf->bf_roaming_energy_delta = value; + break; + case MVM_DEBUGFS_BF_ROAMING_STATE: + dbgfs_bf->bf_roaming_state = value; + break; + case MVM_DEBUGFS_BF_TEMP_THRESHOLD: + dbgfs_bf->bf_temp_threshold = value; + break; + case MVM_DEBUGFS_BF_TEMP_FAST_FILTER: + dbgfs_bf->bf_temp_fast_filter = value; + break; + case MVM_DEBUGFS_BF_TEMP_SLOW_FILTER: + dbgfs_bf->bf_temp_slow_filter = value; + break; + case MVM_DEBUGFS_BF_ENABLE_BEACON_FILTER: + dbgfs_bf->bf_enable_beacon_filter = value; + break; + case MVM_DEBUGFS_BF_DEBUG_FLAG: + dbgfs_bf->bf_debug_flag = value; + break; + case MVM_DEBUGFS_BF_ESCAPE_TIMER: + dbgfs_bf->bf_escape_timer = value; + break; + case MVM_DEBUGFS_BA_ENABLE_BEACON_ABORT: + dbgfs_bf->ba_enable_beacon_abort = value; + break; + case MVM_DEBUGFS_BA_ESCAPE_TIMER: + dbgfs_bf->ba_escape_timer = value; + break; + } +} + +static ssize_t iwl_dbgfs_bf_params_write(struct ieee80211_vif *vif, char *buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = mvmvif->mvm; + enum iwl_dbgfs_bf_mask param; + int value, ret = 0; + + if (!strncmp("bf_energy_delta=", buf, 16)) { + if (sscanf(buf+16, "%d", &value) != 1) + return -EINVAL; + if (value < IWL_BF_ENERGY_DELTA_MIN || + value > IWL_BF_ENERGY_DELTA_MAX) + return -EINVAL; + param = MVM_DEBUGFS_BF_ENERGY_DELTA; + } else if (!strncmp("bf_roaming_energy_delta=", buf, 24)) { + if (sscanf(buf+24, "%d", &value) != 1) + return -EINVAL; + if (value < IWL_BF_ROAMING_ENERGY_DELTA_MIN || + value > IWL_BF_ROAMING_ENERGY_DELTA_MAX) + return -EINVAL; + param = MVM_DEBUGFS_BF_ROAMING_ENERGY_DELTA; + } else if (!strncmp("bf_roaming_state=", buf, 17)) { + if (sscanf(buf+17, "%d", &value) != 1) + return -EINVAL; + if (value < IWL_BF_ROAMING_STATE_MIN || + value > IWL_BF_ROAMING_STATE_MAX) + return -EINVAL; + param = MVM_DEBUGFS_BF_ROAMING_STATE; + } else if (!strncmp("bf_temp_threshold=", buf, 18)) { + if (sscanf(buf+18, "%d", &value) != 1) + return -EINVAL; + if (value < IWL_BF_TEMP_THRESHOLD_MIN || + value > IWL_BF_TEMP_THRESHOLD_MAX) + return -EINVAL; + param = MVM_DEBUGFS_BF_TEMP_THRESHOLD; + } else if (!strncmp("bf_temp_fast_filter=", buf, 20)) { + if (sscanf(buf+20, "%d", &value) != 1) + return -EINVAL; + if (value < IWL_BF_TEMP_FAST_FILTER_MIN || + value > IWL_BF_TEMP_FAST_FILTER_MAX) + return -EINVAL; + param = MVM_DEBUGFS_BF_TEMP_FAST_FILTER; + } else if (!strncmp("bf_temp_slow_filter=", buf, 20)) { + if (sscanf(buf+20, "%d", &value) != 1) + return -EINVAL; + if (value < IWL_BF_TEMP_SLOW_FILTER_MIN || + value > IWL_BF_TEMP_SLOW_FILTER_MAX) + return -EINVAL; + param = MVM_DEBUGFS_BF_TEMP_SLOW_FILTER; + } else if (!strncmp("bf_enable_beacon_filter=", buf, 24)) { + if (sscanf(buf+24, "%d", &value) != 1) + return -EINVAL; + if (value < 0 || value > 1) + return -EINVAL; + param = MVM_DEBUGFS_BF_ENABLE_BEACON_FILTER; + } else if (!strncmp("bf_debug_flag=", buf, 14)) { + if (sscanf(buf+14, "%d", &value) != 1) + return -EINVAL; + if (value < 0 || value > 1) + return -EINVAL; + param = MVM_DEBUGFS_BF_DEBUG_FLAG; + } else if (!strncmp("bf_escape_timer=", buf, 16)) { + if (sscanf(buf+16, "%d", &value) != 1) + return -EINVAL; + if (value < IWL_BF_ESCAPE_TIMER_MIN || + value > IWL_BF_ESCAPE_TIMER_MAX) + return -EINVAL; + param = MVM_DEBUGFS_BF_ESCAPE_TIMER; + } else if (!strncmp("ba_escape_timer=", buf, 16)) { + if (sscanf(buf+16, "%d", &value) != 1) + return -EINVAL; + if (value < IWL_BA_ESCAPE_TIMER_MIN || + value > IWL_BA_ESCAPE_TIMER_MAX) + return -EINVAL; + param = MVM_DEBUGFS_BA_ESCAPE_TIMER; + } else if (!strncmp("ba_enable_beacon_abort=", buf, 23)) { + if (sscanf(buf+23, "%d", &value) != 1) + return -EINVAL; + if (value < 0 || value > 1) + return -EINVAL; + param = MVM_DEBUGFS_BA_ENABLE_BEACON_ABORT; + } else { + return -EINVAL; + } + + mutex_lock(&mvm->mutex); + iwl_dbgfs_update_bf(vif, param, value); + if (param == MVM_DEBUGFS_BF_ENABLE_BEACON_FILTER && !value) + ret = iwl_mvm_disable_beacon_filter(mvm, vif, 0); + else + ret = iwl_mvm_enable_beacon_filter(mvm, vif, 0); + mutex_unlock(&mvm->mutex); + + return ret ?: count; +} + +static ssize_t iwl_dbgfs_bf_params_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ieee80211_vif *vif = file->private_data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + char buf[256]; + int pos = 0; + const size_t bufsz = sizeof(buf); + struct iwl_beacon_filter_cmd cmd = { + IWL_BF_CMD_CONFIG_DEFAULTS, + .bf_enable_beacon_filter = + cpu_to_le32(IWL_BF_ENABLE_BEACON_FILTER_DEFAULT), + .ba_enable_beacon_abort = + cpu_to_le32(IWL_BA_ENABLE_BEACON_ABORT_DEFAULT), + }; + + iwl_mvm_beacon_filter_debugfs_parameters(vif, &cmd); + if (mvmvif->bf_data.bf_enabled) + cmd.bf_enable_beacon_filter = cpu_to_le32(1); + else + cmd.bf_enable_beacon_filter = 0; + + pos += scnprintf(buf+pos, bufsz-pos, "bf_energy_delta = %d\n", + le32_to_cpu(cmd.bf_energy_delta)); + pos += scnprintf(buf+pos, bufsz-pos, "bf_roaming_energy_delta = %d\n", + le32_to_cpu(cmd.bf_roaming_energy_delta)); + pos += scnprintf(buf+pos, bufsz-pos, "bf_roaming_state = %d\n", + le32_to_cpu(cmd.bf_roaming_state)); + pos += scnprintf(buf+pos, bufsz-pos, "bf_temp_threshold = %d\n", + le32_to_cpu(cmd.bf_temp_threshold)); + pos += scnprintf(buf+pos, bufsz-pos, "bf_temp_fast_filter = %d\n", + le32_to_cpu(cmd.bf_temp_fast_filter)); + pos += scnprintf(buf+pos, bufsz-pos, "bf_temp_slow_filter = %d\n", + le32_to_cpu(cmd.bf_temp_slow_filter)); + pos += scnprintf(buf+pos, bufsz-pos, "bf_enable_beacon_filter = %d\n", + le32_to_cpu(cmd.bf_enable_beacon_filter)); + pos += scnprintf(buf+pos, bufsz-pos, "bf_debug_flag = %d\n", + le32_to_cpu(cmd.bf_debug_flag)); + pos += scnprintf(buf+pos, bufsz-pos, "bf_escape_timer = %d\n", + le32_to_cpu(cmd.bf_escape_timer)); + pos += scnprintf(buf+pos, bufsz-pos, "ba_escape_timer = %d\n", + le32_to_cpu(cmd.ba_escape_timer)); + pos += scnprintf(buf+pos, bufsz-pos, "ba_enable_beacon_abort = %d\n", + le32_to_cpu(cmd.ba_enable_beacon_abort)); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static inline char *iwl_dbgfs_is_match(char *name, char *buf) +{ + int len = strlen(name); + + return !strncmp(name, buf, len) ? buf + len : NULL; +} + +static ssize_t iwl_dbgfs_os_device_timediff_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ieee80211_vif *vif = file->private_data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = mvmvif->mvm; + u32 curr_gp2; + u64 curr_os; + s64 diff; + char buf[64]; + const size_t bufsz = sizeof(buf); + int pos = 0; + + mutex_lock(&mvm->mutex); + iwl_mvm_get_sync_time(mvm, CLOCK_BOOTTIME, &curr_gp2, &curr_os, NULL); + mutex_unlock(&mvm->mutex); + + do_div(curr_os, NSEC_PER_USEC); + diff = curr_os - curr_gp2; + pos += scnprintf(buf + pos, bufsz - pos, "diff=%lld\n", diff); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_low_latency_write(struct ieee80211_vif *vif, char *buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = mvmvif->mvm; + u8 value; + int ret; + + ret = kstrtou8(buf, 0, &value); + if (ret) + return ret; + if (value > 1) + return -EINVAL; + + mutex_lock(&mvm->mutex); + iwl_mvm_update_low_latency(mvm, vif, value, LOW_LATENCY_DEBUGFS); + mutex_unlock(&mvm->mutex); + + return count; +} + +static ssize_t +iwl_dbgfs_low_latency_force_write(struct ieee80211_vif *vif, char *buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = mvmvif->mvm; + u8 value; + int ret; + + ret = kstrtou8(buf, 0, &value); + if (ret) + return ret; + + if (value > NUM_LOW_LATENCY_FORCE) + return -EINVAL; + + mutex_lock(&mvm->mutex); + if (value == LOW_LATENCY_FORCE_UNSET) { + iwl_mvm_update_low_latency(mvm, vif, false, + LOW_LATENCY_DEBUGFS_FORCE); + iwl_mvm_update_low_latency(mvm, vif, false, + LOW_LATENCY_DEBUGFS_FORCE_ENABLE); + } else { + iwl_mvm_update_low_latency(mvm, vif, + value == LOW_LATENCY_FORCE_ON, + LOW_LATENCY_DEBUGFS_FORCE); + iwl_mvm_update_low_latency(mvm, vif, true, + LOW_LATENCY_DEBUGFS_FORCE_ENABLE); + } + mutex_unlock(&mvm->mutex); + return count; +} + +static ssize_t iwl_dbgfs_low_latency_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ieee80211_vif *vif = file->private_data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + char format[] = "traffic=%d\ndbgfs=%d\nvcmd=%d\nvif_type=%d\n" + "dbgfs_force_enable=%d\ndbgfs_force=%d\nactual=%d\n"; + + /* + * all values in format are boolean so the size of format is enough + * for holding the result string + */ + char buf[sizeof(format) + 1] = {}; + int len; + + len = scnprintf(buf, sizeof(buf) - 1, format, + !!(mvmvif->low_latency & LOW_LATENCY_TRAFFIC), + !!(mvmvif->low_latency & LOW_LATENCY_DEBUGFS), + !!(mvmvif->low_latency & LOW_LATENCY_VCMD), + !!(mvmvif->low_latency & LOW_LATENCY_VIF_TYPE), + !!(mvmvif->low_latency & + LOW_LATENCY_DEBUGFS_FORCE_ENABLE), + !!(mvmvif->low_latency & LOW_LATENCY_DEBUGFS_FORCE), + !!(mvmvif->low_latency_actual)); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t iwl_dbgfs_uapsd_misbehaving_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ieee80211_vif *vif = file->private_data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + char buf[20]; + int len; + + len = sprintf(buf, "%pM\n", mvmvif->uapsd_misbehaving_bssid); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t iwl_dbgfs_uapsd_misbehaving_write(struct ieee80211_vif *vif, + char *buf, size_t count, + loff_t *ppos) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = mvmvif->mvm; + bool ret; + + mutex_lock(&mvm->mutex); + ret = mac_pton(buf, mvmvif->uapsd_misbehaving_bssid); + mutex_unlock(&mvm->mutex); + + return ret ? count : -EINVAL; +} + +static ssize_t iwl_dbgfs_rx_phyinfo_write(struct ieee80211_vif *vif, char *buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = mvmvif->mvm; + struct ieee80211_chanctx_conf *chanctx_conf; + struct iwl_mvm_phy_ctxt *phy_ctxt; + u16 value; + int ret; + + ret = kstrtou16(buf, 0, &value); + if (ret) + return ret; + + mutex_lock(&mvm->mutex); + rcu_read_lock(); + + chanctx_conf = rcu_dereference(vif->bss_conf.chanctx_conf); + /* make sure the channel context is assigned */ + if (!chanctx_conf) { + rcu_read_unlock(); + mutex_unlock(&mvm->mutex); + return -EINVAL; + } + + phy_ctxt = &mvm->phy_ctxts[*(u16 *)chanctx_conf->drv_priv]; + rcu_read_unlock(); + + mvm->dbgfs_rx_phyinfo = value; + + ret = iwl_mvm_phy_ctxt_changed(mvm, phy_ctxt, &chanctx_conf->min_def, + chanctx_conf->rx_chains_static, + chanctx_conf->rx_chains_dynamic); + mutex_unlock(&mvm->mutex); + + return ret ?: count; +} + +static ssize_t iwl_dbgfs_rx_phyinfo_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ieee80211_vif *vif = file->private_data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + char buf[8]; + int len; + + len = scnprintf(buf, sizeof(buf), "0x%04x\n", + mvmvif->mvm->dbgfs_rx_phyinfo); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static void iwl_dbgfs_quota_check(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int *ret = data; + + if (mvmvif->dbgfs_quota_min) + *ret = -EINVAL; +} + +static ssize_t iwl_dbgfs_quota_min_write(struct ieee80211_vif *vif, char *buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = mvmvif->mvm; + u16 value; + int ret; + + ret = kstrtou16(buf, 0, &value); + if (ret) + return ret; + + if (value > 95) + return -EINVAL; + + mutex_lock(&mvm->mutex); + + mvmvif->dbgfs_quota_min = 0; + ieee80211_iterate_interfaces(mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_dbgfs_quota_check, &ret); + if (ret == 0) { + mvmvif->dbgfs_quota_min = value; + iwl_mvm_update_quotas(mvm, false, NULL); + } + mutex_unlock(&mvm->mutex); + + return ret ?: count; +} + +static ssize_t iwl_dbgfs_quota_min_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ieee80211_vif *vif = file->private_data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + char buf[10]; + int len; + + len = scnprintf(buf, sizeof(buf), "%d\n", mvmvif->dbgfs_quota_min); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +#define MVM_DEBUGFS_WRITE_FILE_OPS(name, bufsz) \ + _MVM_DEBUGFS_WRITE_FILE_OPS(name, bufsz, struct ieee80211_vif) +#define MVM_DEBUGFS_READ_WRITE_FILE_OPS(name, bufsz) \ + _MVM_DEBUGFS_READ_WRITE_FILE_OPS(name, bufsz, struct ieee80211_vif) +#define MVM_DEBUGFS_ADD_FILE_VIF(name, parent, mode) do { \ + debugfs_create_file(#name, mode, parent, vif, \ + &iwl_dbgfs_##name##_ops); \ + } while (0) + +MVM_DEBUGFS_READ_FILE_OPS(mac_params); +MVM_DEBUGFS_READ_FILE_OPS(tx_pwr_lmt); +MVM_DEBUGFS_READ_WRITE_FILE_OPS(pm_params, 32); +MVM_DEBUGFS_READ_WRITE_FILE_OPS(bf_params, 256); +MVM_DEBUGFS_READ_WRITE_FILE_OPS(low_latency, 10); +MVM_DEBUGFS_WRITE_FILE_OPS(low_latency_force, 10); +MVM_DEBUGFS_READ_WRITE_FILE_OPS(uapsd_misbehaving, 20); +MVM_DEBUGFS_READ_WRITE_FILE_OPS(rx_phyinfo, 10); +MVM_DEBUGFS_READ_WRITE_FILE_OPS(quota_min, 32); +MVM_DEBUGFS_READ_FILE_OPS(os_device_timediff); + + +void iwl_mvm_vif_dbgfs_register(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct dentry *dbgfs_dir = vif->debugfs_dir; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + char buf[100]; + + /* + * Check if debugfs directory already exist before creating it. + * This may happen when, for example, resetting hw or suspend-resume + */ + if (!dbgfs_dir || mvmvif->dbgfs_dir) + return; + + mvmvif->dbgfs_dir = debugfs_create_dir("iwlmvm", dbgfs_dir); + if (IS_ERR_OR_NULL(mvmvif->dbgfs_dir)) { + IWL_ERR(mvm, "Failed to create debugfs directory under %pd\n", + dbgfs_dir); + return; + } + + if (iwlmvm_mod_params.power_scheme != IWL_POWER_SCHEME_CAM && + ((vif->type == NL80211_IFTYPE_STATION && !vif->p2p) || + (vif->type == NL80211_IFTYPE_STATION && vif->p2p))) + MVM_DEBUGFS_ADD_FILE_VIF(pm_params, mvmvif->dbgfs_dir, 0600); + + MVM_DEBUGFS_ADD_FILE_VIF(tx_pwr_lmt, mvmvif->dbgfs_dir, 0400); + MVM_DEBUGFS_ADD_FILE_VIF(mac_params, mvmvif->dbgfs_dir, 0400); + MVM_DEBUGFS_ADD_FILE_VIF(low_latency, mvmvif->dbgfs_dir, 0600); + MVM_DEBUGFS_ADD_FILE_VIF(low_latency_force, mvmvif->dbgfs_dir, 0600); + MVM_DEBUGFS_ADD_FILE_VIF(uapsd_misbehaving, mvmvif->dbgfs_dir, 0600); + MVM_DEBUGFS_ADD_FILE_VIF(rx_phyinfo, mvmvif->dbgfs_dir, 0600); + MVM_DEBUGFS_ADD_FILE_VIF(quota_min, mvmvif->dbgfs_dir, 0600); + MVM_DEBUGFS_ADD_FILE_VIF(os_device_timediff, mvmvif->dbgfs_dir, 0400); + + if (vif->type == NL80211_IFTYPE_STATION && !vif->p2p && + mvmvif == mvm->bf_allowed_vif) + MVM_DEBUGFS_ADD_FILE_VIF(bf_params, mvmvif->dbgfs_dir, 0600); + + /* + * Create symlink for convenience pointing to interface specific + * debugfs entries for the driver. For example, under + * /sys/kernel/debug/iwlwifi/0000\:02\:00.0/iwlmvm/ + * find + * netdev:wlan0 -> ../../../ieee80211/phy0/netdev:wlan0/iwlmvm/ + */ + snprintf(buf, 100, "../../../%pd3/%pd", + dbgfs_dir, + mvmvif->dbgfs_dir); + + mvmvif->dbgfs_slink = debugfs_create_symlink(dbgfs_dir->d_name.name, + mvm->debugfs_dir, buf); +} + +void iwl_mvm_vif_dbgfs_clean(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + debugfs_remove(mvmvif->dbgfs_slink); + mvmvif->dbgfs_slink = NULL; + + debugfs_remove_recursive(mvmvif->dbgfs_dir); + mvmvif->dbgfs_dir = NULL; +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/debugfs.c b/drivers/net/wireless/intel/iwlwifi/mvm/debugfs.c new file mode 100644 index 000000000..022ec7ec0 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/debugfs.c @@ -0,0 +1,1959 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include <linux/vmalloc.h> +#include <linux/err.h> +#include <linux/ieee80211.h> +#include <linux/netdevice.h> + +#include "mvm.h" +#include "sta.h" +#include "iwl-io.h" +#include "debugfs.h" +#include "iwl-modparams.h" +#include "fw/error-dump.h" + +static ssize_t iwl_dbgfs_ctdp_budget_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + char buf[16]; + int pos, budget; + + if (!iwl_mvm_is_ctdp_supported(mvm)) + return -EOPNOTSUPP; + + if (!iwl_mvm_firmware_running(mvm) || + mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) + return -EIO; + + mutex_lock(&mvm->mutex); + budget = iwl_mvm_ctdp_command(mvm, CTDP_CMD_OPERATION_REPORT, 0); + mutex_unlock(&mvm->mutex); + + if (budget < 0) + return budget; + + pos = scnprintf(buf, sizeof(buf), "%d\n", budget); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_stop_ctdp_write(struct iwl_mvm *mvm, char *buf, + size_t count, loff_t *ppos) +{ + int ret; + + if (!iwl_mvm_is_ctdp_supported(mvm)) + return -EOPNOTSUPP; + + if (!iwl_mvm_firmware_running(mvm) || + mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) + return -EIO; + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_ctdp_command(mvm, CTDP_CMD_OPERATION_STOP, 0); + mutex_unlock(&mvm->mutex); + + return ret ?: count; +} + +static ssize_t iwl_dbgfs_force_ctkill_write(struct iwl_mvm *mvm, char *buf, + size_t count, loff_t *ppos) +{ + if (!iwl_mvm_firmware_running(mvm) || + mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) + return -EIO; + + iwl_mvm_enter_ctkill(mvm); + + return count; +} + +static ssize_t iwl_dbgfs_tx_flush_write(struct iwl_mvm *mvm, char *buf, + size_t count, loff_t *ppos) +{ + int ret; + u32 flush_arg; + + if (!iwl_mvm_firmware_running(mvm) || + mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) + return -EIO; + + if (kstrtou32(buf, 0, &flush_arg)) + return -EINVAL; + + if (iwl_mvm_has_new_tx_api(mvm)) { + IWL_DEBUG_TX_QUEUES(mvm, + "FLUSHING all tids queues on sta_id = %d\n", + flush_arg); + mutex_lock(&mvm->mutex); + ret = iwl_mvm_flush_sta_tids(mvm, flush_arg, 0xFFFF) + ? : count; + mutex_unlock(&mvm->mutex); + return ret; + } + + IWL_DEBUG_TX_QUEUES(mvm, "FLUSHING queues mask to flush = 0x%x\n", + flush_arg); + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_flush_tx_path(mvm, flush_arg) ? : count; + mutex_unlock(&mvm->mutex); + + return ret; +} + +static ssize_t iwl_dbgfs_sta_drain_write(struct iwl_mvm *mvm, char *buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm_sta *mvmsta; + int sta_id, drain, ret; + + if (!iwl_mvm_firmware_running(mvm) || + mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) + return -EIO; + + if (sscanf(buf, "%d %d", &sta_id, &drain) != 2) + return -EINVAL; + if (sta_id < 0 || sta_id >= mvm->fw->ucode_capa.num_stations) + return -EINVAL; + if (drain < 0 || drain > 1) + return -EINVAL; + + mutex_lock(&mvm->mutex); + + mvmsta = iwl_mvm_sta_from_staid_protected(mvm, sta_id); + + if (!mvmsta) + ret = -ENOENT; + else + ret = iwl_mvm_drain_sta(mvm, mvmsta, drain) ? : count; + + mutex_unlock(&mvm->mutex); + + return ret; +} + +static ssize_t iwl_dbgfs_sram_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + const struct fw_img *img; + unsigned int ofs, len; + size_t ret; + u8 *ptr; + + if (!iwl_mvm_firmware_running(mvm)) + return -EINVAL; + + /* default is to dump the entire data segment */ + img = &mvm->fw->img[mvm->fwrt.cur_fw_img]; + ofs = img->sec[IWL_UCODE_SECTION_DATA].offset; + len = img->sec[IWL_UCODE_SECTION_DATA].len; + + if (mvm->dbgfs_sram_len) { + ofs = mvm->dbgfs_sram_offset; + len = mvm->dbgfs_sram_len; + } + + ptr = kzalloc(len, GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + iwl_trans_read_mem_bytes(mvm->trans, ofs, ptr, len); + + ret = simple_read_from_buffer(user_buf, count, ppos, ptr, len); + + kfree(ptr); + + return ret; +} + +static ssize_t iwl_dbgfs_sram_write(struct iwl_mvm *mvm, char *buf, + size_t count, loff_t *ppos) +{ + const struct fw_img *img; + u32 offset, len; + u32 img_offset, img_len; + + if (!iwl_mvm_firmware_running(mvm)) + return -EINVAL; + + img = &mvm->fw->img[mvm->fwrt.cur_fw_img]; + img_offset = img->sec[IWL_UCODE_SECTION_DATA].offset; + img_len = img->sec[IWL_UCODE_SECTION_DATA].len; + + if (sscanf(buf, "%x,%x", &offset, &len) == 2) { + if ((offset & 0x3) || (len & 0x3)) + return -EINVAL; + + if (offset + len > img_offset + img_len) + return -EINVAL; + + mvm->dbgfs_sram_offset = offset; + mvm->dbgfs_sram_len = len; + } else { + mvm->dbgfs_sram_offset = 0; + mvm->dbgfs_sram_len = 0; + } + + return count; +} + +static ssize_t iwl_dbgfs_set_nic_temperature_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + char buf[16]; + int pos; + + if (!mvm->temperature_test) + pos = scnprintf(buf , sizeof(buf), "disabled\n"); + else + pos = scnprintf(buf , sizeof(buf), "%d\n", mvm->temperature); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +/* + * Set NIC Temperature + * Cause the driver to ignore the actual NIC temperature reported by the FW + * Enable: any value between IWL_MVM_DEBUG_SET_TEMPERATURE_MIN - + * IWL_MVM_DEBUG_SET_TEMPERATURE_MAX + * Disable: IWL_MVM_DEBUG_SET_TEMPERATURE_DISABLE + */ +static ssize_t iwl_dbgfs_set_nic_temperature_write(struct iwl_mvm *mvm, + char *buf, size_t count, + loff_t *ppos) +{ + int temperature; + + if (!iwl_mvm_firmware_running(mvm) && !mvm->temperature_test) + return -EIO; + + if (kstrtoint(buf, 10, &temperature)) + return -EINVAL; + /* not a legal temperature */ + if ((temperature > IWL_MVM_DEBUG_SET_TEMPERATURE_MAX && + temperature != IWL_MVM_DEBUG_SET_TEMPERATURE_DISABLE) || + temperature < IWL_MVM_DEBUG_SET_TEMPERATURE_MIN) + return -EINVAL; + + mutex_lock(&mvm->mutex); + if (temperature == IWL_MVM_DEBUG_SET_TEMPERATURE_DISABLE) { + if (!mvm->temperature_test) + goto out; + + mvm->temperature_test = false; + /* Since we can't read the temp while awake, just set + * it to zero until we get the next RX stats from the + * firmware. + */ + mvm->temperature = 0; + } else { + mvm->temperature_test = true; + mvm->temperature = temperature; + } + IWL_DEBUG_TEMP(mvm, "%sabling debug set temperature (temp = %d)\n", + mvm->temperature_test ? "En" : "Dis" , + mvm->temperature); + /* handle the temperature change */ + iwl_mvm_tt_handler(mvm); + +out: + mutex_unlock(&mvm->mutex); + + return count; +} + +static ssize_t iwl_dbgfs_nic_temp_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + char buf[16]; + int pos, ret; + s32 temp; + + if (!iwl_mvm_firmware_running(mvm)) + return -EIO; + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_get_temp(mvm, &temp); + mutex_unlock(&mvm->mutex); + + if (ret) + return -EIO; + + pos = scnprintf(buf , sizeof(buf), "%d\n", temp); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +#ifdef CONFIG_ACPI +static ssize_t iwl_dbgfs_sar_geo_profile_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + char buf[256]; + int pos = 0; + int bufsz = sizeof(buf); + int tbl_idx; + + if (!iwl_mvm_firmware_running(mvm)) + return -EIO; + + mutex_lock(&mvm->mutex); + tbl_idx = iwl_mvm_get_sar_geo_profile(mvm); + if (tbl_idx < 0) { + mutex_unlock(&mvm->mutex); + return tbl_idx; + } + + if (!tbl_idx) { + pos = scnprintf(buf, bufsz, + "SAR geographic profile disabled\n"); + } else { + pos += scnprintf(buf + pos, bufsz - pos, + "Use geographic profile %d\n", tbl_idx); + pos += scnprintf(buf + pos, bufsz - pos, + "2.4GHz:\n\tChain A offset: %hhu dBm\n\tChain B offset: %hhu dBm\n\tmax tx power: %hhu dBm\n", + mvm->fwrt.geo_profiles[tbl_idx - 1].bands[0].chains[0], + mvm->fwrt.geo_profiles[tbl_idx - 1].bands[0].chains[1], + mvm->fwrt.geo_profiles[tbl_idx - 1].bands[0].max); + pos += scnprintf(buf + pos, bufsz - pos, + "5.2GHz:\n\tChain A offset: %hhu dBm\n\tChain B offset: %hhu dBm\n\tmax tx power: %hhu dBm\n", + mvm->fwrt.geo_profiles[tbl_idx - 1].bands[1].chains[0], + mvm->fwrt.geo_profiles[tbl_idx - 1].bands[1].chains[1], + mvm->fwrt.geo_profiles[tbl_idx - 1].bands[1].max); + } + mutex_unlock(&mvm->mutex); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} +#endif + +static ssize_t iwl_dbgfs_stations_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + struct ieee80211_sta *sta; + char buf[400]; + int i, pos = 0, bufsz = sizeof(buf); + + mutex_lock(&mvm->mutex); + + for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) { + pos += scnprintf(buf + pos, bufsz - pos, "%.2d: ", i); + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i], + lockdep_is_held(&mvm->mutex)); + if (!sta) + pos += scnprintf(buf + pos, bufsz - pos, "N/A\n"); + else if (IS_ERR(sta)) + pos += scnprintf(buf + pos, bufsz - pos, "%ld\n", + PTR_ERR(sta)); + else + pos += scnprintf(buf + pos, bufsz - pos, "%pM\n", + sta->addr); + } + + mutex_unlock(&mvm->mutex); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_rs_data_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ieee80211_sta *sta = file->private_data; + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_lq_sta_rs_fw *lq_sta = &mvmsta->lq_sta.rs_fw; + struct iwl_mvm *mvm = lq_sta->pers.drv; + static const size_t bufsz = 2048; + char *buff; + int desc = 0; + ssize_t ret; + + buff = kmalloc(bufsz, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + mutex_lock(&mvm->mutex); + + desc += scnprintf(buff + desc, bufsz - desc, "sta_id %d\n", + lq_sta->pers.sta_id); + desc += scnprintf(buff + desc, bufsz - desc, + "fixed rate 0x%X\n", + lq_sta->pers.dbg_fixed_rate); + desc += scnprintf(buff + desc, bufsz - desc, + "A-MPDU size limit %d\n", + lq_sta->pers.dbg_agg_frame_count_lim); + desc += scnprintf(buff + desc, bufsz - desc, + "valid_tx_ant %s%s\n", + (iwl_mvm_get_valid_tx_ant(mvm) & ANT_A) ? "ANT_A," : "", + (iwl_mvm_get_valid_tx_ant(mvm) & ANT_B) ? "ANT_B," : ""); + desc += scnprintf(buff + desc, bufsz - desc, + "last tx rate=0x%X ", + lq_sta->last_rate_n_flags); + + desc += rs_pretty_print_rate(buff + desc, bufsz - desc, + lq_sta->last_rate_n_flags); + if (desc < bufsz - 1) + buff[desc++] = '\n'; + mutex_unlock(&mvm->mutex); + + ret = simple_read_from_buffer(user_buf, count, ppos, buff, desc); + kfree(buff); + return ret; +} + +static ssize_t iwl_dbgfs_amsdu_len_write(struct ieee80211_sta *sta, + char *buf, size_t count, + loff_t *ppos) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + int i; + u16 amsdu_len; + + if (kstrtou16(buf, 0, &amsdu_len)) + return -EINVAL; + + /* only change from debug set <-> debug unset */ + if (amsdu_len && mvmsta->orig_amsdu_len) + return -EBUSY; + + if (amsdu_len) { + mvmsta->orig_amsdu_len = sta->cur->max_amsdu_len; + sta->deflink.agg.max_amsdu_len = amsdu_len; + sta->deflink.agg.max_amsdu_len = amsdu_len; + for (i = 0; i < ARRAY_SIZE(sta->deflink.agg.max_tid_amsdu_len); i++) + sta->deflink.agg.max_tid_amsdu_len[i] = amsdu_len; + } else { + sta->deflink.agg.max_amsdu_len = mvmsta->orig_amsdu_len; + mvmsta->orig_amsdu_len = 0; + } + + return count; +} + +static ssize_t iwl_dbgfs_amsdu_len_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ieee80211_sta *sta = file->private_data; + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + + char buf[32]; + int pos; + + pos = scnprintf(buf, sizeof(buf), "current %d ", sta->cur->max_amsdu_len); + pos += scnprintf(buf + pos, sizeof(buf) - pos, "stored %d\n", + mvmsta->orig_amsdu_len); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_disable_power_off_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + char buf[64]; + int bufsz = sizeof(buf); + int pos = 0; + + pos += scnprintf(buf+pos, bufsz-pos, "disable_power_off_d0=%d\n", + mvm->disable_power_off); + pos += scnprintf(buf+pos, bufsz-pos, "disable_power_off_d3=%d\n", + mvm->disable_power_off_d3); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_disable_power_off_write(struct iwl_mvm *mvm, char *buf, + size_t count, loff_t *ppos) +{ + int ret, val; + + if (!iwl_mvm_firmware_running(mvm)) + return -EIO; + + if (!strncmp("disable_power_off_d0=", buf, 21)) { + if (sscanf(buf + 21, "%d", &val) != 1) + return -EINVAL; + mvm->disable_power_off = val; + } else if (!strncmp("disable_power_off_d3=", buf, 21)) { + if (sscanf(buf + 21, "%d", &val) != 1) + return -EINVAL; + mvm->disable_power_off_d3 = val; + } else { + return -EINVAL; + } + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_power_update_device(mvm); + mutex_unlock(&mvm->mutex); + + return ret ?: count; +} + +static +int iwl_mvm_coex_dump_mbox(struct iwl_bt_coex_profile_notif *notif, char *buf, + int pos, int bufsz) +{ + pos += scnprintf(buf+pos, bufsz-pos, "MBOX dw0:\n"); + + BT_MBOX_PRINT(0, LE_SLAVE_LAT, false); + BT_MBOX_PRINT(0, LE_PROF1, false); + BT_MBOX_PRINT(0, LE_PROF2, false); + BT_MBOX_PRINT(0, LE_PROF_OTHER, false); + BT_MBOX_PRINT(0, CHL_SEQ_N, false); + BT_MBOX_PRINT(0, INBAND_S, false); + BT_MBOX_PRINT(0, LE_MIN_RSSI, false); + BT_MBOX_PRINT(0, LE_SCAN, false); + BT_MBOX_PRINT(0, LE_ADV, false); + BT_MBOX_PRINT(0, LE_MAX_TX_POWER, false); + BT_MBOX_PRINT(0, OPEN_CON_1, true); + + pos += scnprintf(buf+pos, bufsz-pos, "MBOX dw1:\n"); + + BT_MBOX_PRINT(1, BR_MAX_TX_POWER, false); + BT_MBOX_PRINT(1, IP_SR, false); + BT_MBOX_PRINT(1, LE_MSTR, false); + BT_MBOX_PRINT(1, AGGR_TRFC_LD, false); + BT_MBOX_PRINT(1, MSG_TYPE, false); + BT_MBOX_PRINT(1, SSN, true); + + pos += scnprintf(buf+pos, bufsz-pos, "MBOX dw2:\n"); + + BT_MBOX_PRINT(2, SNIFF_ACT, false); + BT_MBOX_PRINT(2, PAG, false); + BT_MBOX_PRINT(2, INQUIRY, false); + BT_MBOX_PRINT(2, CONN, false); + BT_MBOX_PRINT(2, SNIFF_INTERVAL, false); + BT_MBOX_PRINT(2, DISC, false); + BT_MBOX_PRINT(2, SCO_TX_ACT, false); + BT_MBOX_PRINT(2, SCO_RX_ACT, false); + BT_MBOX_PRINT(2, ESCO_RE_TX, false); + BT_MBOX_PRINT(2, SCO_DURATION, true); + + pos += scnprintf(buf+pos, bufsz-pos, "MBOX dw3:\n"); + + BT_MBOX_PRINT(3, SCO_STATE, false); + BT_MBOX_PRINT(3, SNIFF_STATE, false); + BT_MBOX_PRINT(3, A2DP_STATE, false); + BT_MBOX_PRINT(3, A2DP_SRC, false); + BT_MBOX_PRINT(3, ACL_STATE, false); + BT_MBOX_PRINT(3, MSTR_STATE, false); + BT_MBOX_PRINT(3, OBX_STATE, false); + BT_MBOX_PRINT(3, OPEN_CON_2, false); + BT_MBOX_PRINT(3, TRAFFIC_LOAD, false); + BT_MBOX_PRINT(3, CHL_SEQN_LSB, false); + BT_MBOX_PRINT(3, INBAND_P, false); + BT_MBOX_PRINT(3, MSG_TYPE_2, false); + BT_MBOX_PRINT(3, SSN_2, false); + BT_MBOX_PRINT(3, UPDATE_REQUEST, true); + + return pos; +} + +static ssize_t iwl_dbgfs_bt_notif_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + struct iwl_bt_coex_profile_notif *notif = &mvm->last_bt_notif; + char *buf; + int ret, pos = 0, bufsz = sizeof(char) * 1024; + + buf = kmalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + mutex_lock(&mvm->mutex); + + pos += iwl_mvm_coex_dump_mbox(notif, buf, pos, bufsz); + + pos += scnprintf(buf + pos, bufsz - pos, "bt_ci_compliance = %d\n", + notif->bt_ci_compliance); + pos += scnprintf(buf + pos, bufsz - pos, "primary_ch_lut = %d\n", + le32_to_cpu(notif->primary_ch_lut)); + pos += scnprintf(buf + pos, bufsz - pos, "secondary_ch_lut = %d\n", + le32_to_cpu(notif->secondary_ch_lut)); + pos += scnprintf(buf + pos, + bufsz - pos, "bt_activity_grading = %d\n", + le32_to_cpu(notif->bt_activity_grading)); + pos += scnprintf(buf + pos, bufsz - pos, "bt_rrc = %d\n", + notif->rrc_status & 0xF); + pos += scnprintf(buf + pos, bufsz - pos, "bt_ttc = %d\n", + notif->ttc_status & 0xF); + + pos += scnprintf(buf + pos, bufsz - pos, "sync_sco = %d\n", + IWL_MVM_BT_COEX_SYNC2SCO); + pos += scnprintf(buf + pos, bufsz - pos, "mplut = %d\n", + IWL_MVM_BT_COEX_MPLUT); + + mutex_unlock(&mvm->mutex); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + + return ret; +} +#undef BT_MBOX_PRINT + +static ssize_t iwl_dbgfs_bt_cmd_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + struct iwl_bt_coex_ci_cmd *cmd = &mvm->last_bt_ci_cmd; + char buf[256]; + int bufsz = sizeof(buf); + int pos = 0; + + mutex_lock(&mvm->mutex); + + pos += scnprintf(buf + pos, bufsz - pos, "Channel inhibition CMD\n"); + pos += scnprintf(buf + pos, bufsz - pos, + "\tPrimary Channel Bitmap 0x%016llx\n", + le64_to_cpu(cmd->bt_primary_ci)); + pos += scnprintf(buf + pos, bufsz - pos, + "\tSecondary Channel Bitmap 0x%016llx\n", + le64_to_cpu(cmd->bt_secondary_ci)); + + mutex_unlock(&mvm->mutex); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t +iwl_dbgfs_bt_tx_prio_write(struct iwl_mvm *mvm, char *buf, + size_t count, loff_t *ppos) +{ + u32 bt_tx_prio; + + if (sscanf(buf, "%u", &bt_tx_prio) != 1) + return -EINVAL; + if (bt_tx_prio > 4) + return -EINVAL; + + mvm->bt_tx_prio = bt_tx_prio; + + return count; +} + +static ssize_t +iwl_dbgfs_bt_force_ant_write(struct iwl_mvm *mvm, char *buf, + size_t count, loff_t *ppos) +{ + static const char * const modes_str[BT_FORCE_ANT_MAX] = { + [BT_FORCE_ANT_DIS] = "dis", + [BT_FORCE_ANT_AUTO] = "auto", + [BT_FORCE_ANT_BT] = "bt", + [BT_FORCE_ANT_WIFI] = "wifi", + }; + int ret, bt_force_ant_mode; + + ret = match_string(modes_str, ARRAY_SIZE(modes_str), buf); + if (ret < 0) + return ret; + + bt_force_ant_mode = ret; + ret = 0; + mutex_lock(&mvm->mutex); + if (mvm->bt_force_ant_mode == bt_force_ant_mode) + goto out; + + mvm->bt_force_ant_mode = bt_force_ant_mode; + IWL_DEBUG_COEX(mvm, "Force mode: %s\n", + modes_str[mvm->bt_force_ant_mode]); + + if (iwl_mvm_firmware_running(mvm)) + ret = iwl_mvm_send_bt_init_conf(mvm); + else + ret = 0; + +out: + mutex_unlock(&mvm->mutex); + return ret ?: count; +} + +static ssize_t iwl_dbgfs_fw_ver_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + char *buff, *pos, *endpos; + static const size_t bufsz = 1024; + int ret; + + buff = kmalloc(bufsz, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + pos = buff; + endpos = pos + bufsz; + + pos += scnprintf(pos, endpos - pos, "FW prefix: %s\n", + mvm->trans->cfg->fw_name_pre); + pos += scnprintf(pos, endpos - pos, "FW: %s\n", + mvm->fwrt.fw->human_readable); + pos += scnprintf(pos, endpos - pos, "Device: %s\n", + mvm->fwrt.trans->name); + pos += scnprintf(pos, endpos - pos, "Bus: %s\n", + mvm->fwrt.dev->bus->name); + + ret = simple_read_from_buffer(user_buf, count, ppos, buff, pos - buff); + kfree(buff); + + return ret; +} + +static ssize_t iwl_dbgfs_phy_integration_ver_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + char *buf; + size_t bufsz; + int pos; + ssize_t ret; + + bufsz = mvm->fw->phy_integration_ver_len + 2; + buf = kmalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pos = scnprintf(buf, bufsz, "%.*s\n", mvm->fw->phy_integration_ver_len, + mvm->fw->phy_integration_ver); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + + kfree(buf); + return ret; +} + +#define PRINT_STATS_LE32(_struct, _memb) \ + pos += scnprintf(buf + pos, bufsz - pos, \ + fmt_table, #_memb, \ + le32_to_cpu(_struct->_memb)) + +static ssize_t iwl_dbgfs_fw_rx_stats_read(struct file *file, + char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + static const char *fmt_table = "\t%-30s %10u\n"; + static const char *fmt_header = "%-32s\n"; + int pos = 0; + char *buf; + int ret; + size_t bufsz; + + if (iwl_mvm_has_new_rx_stats_api(mvm)) + bufsz = ((sizeof(struct mvm_statistics_rx) / + sizeof(__le32)) * 43) + (4 * 33) + 1; + else + /* 43 = size of each data line; 33 = size of each header */ + bufsz = ((sizeof(struct mvm_statistics_rx_v3) / + sizeof(__le32)) * 43) + (4 * 33) + 1; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + mutex_lock(&mvm->mutex); + + if (iwl_mvm_firmware_running(mvm)) + iwl_mvm_request_statistics(mvm, false); + + pos += scnprintf(buf + pos, bufsz - pos, fmt_header, + "Statistics_Rx - OFDM"); + if (!iwl_mvm_has_new_rx_stats_api(mvm)) { + struct mvm_statistics_rx_phy_v2 *ofdm = &mvm->rx_stats_v3.ofdm; + + PRINT_STATS_LE32(ofdm, ina_cnt); + PRINT_STATS_LE32(ofdm, fina_cnt); + PRINT_STATS_LE32(ofdm, plcp_err); + PRINT_STATS_LE32(ofdm, crc32_err); + PRINT_STATS_LE32(ofdm, overrun_err); + PRINT_STATS_LE32(ofdm, early_overrun_err); + PRINT_STATS_LE32(ofdm, crc32_good); + PRINT_STATS_LE32(ofdm, false_alarm_cnt); + PRINT_STATS_LE32(ofdm, fina_sync_err_cnt); + PRINT_STATS_LE32(ofdm, sfd_timeout); + PRINT_STATS_LE32(ofdm, fina_timeout); + PRINT_STATS_LE32(ofdm, unresponded_rts); + PRINT_STATS_LE32(ofdm, rxe_frame_lmt_overrun); + PRINT_STATS_LE32(ofdm, sent_ack_cnt); + PRINT_STATS_LE32(ofdm, sent_cts_cnt); + PRINT_STATS_LE32(ofdm, sent_ba_rsp_cnt); + PRINT_STATS_LE32(ofdm, dsp_self_kill); + PRINT_STATS_LE32(ofdm, mh_format_err); + PRINT_STATS_LE32(ofdm, re_acq_main_rssi_sum); + PRINT_STATS_LE32(ofdm, reserved); + } else { + struct mvm_statistics_rx_phy *ofdm = &mvm->rx_stats.ofdm; + + PRINT_STATS_LE32(ofdm, unresponded_rts); + PRINT_STATS_LE32(ofdm, rxe_frame_lmt_overrun); + PRINT_STATS_LE32(ofdm, sent_ba_rsp_cnt); + PRINT_STATS_LE32(ofdm, dsp_self_kill); + PRINT_STATS_LE32(ofdm, reserved); + } + + pos += scnprintf(buf + pos, bufsz - pos, fmt_header, + "Statistics_Rx - CCK"); + if (!iwl_mvm_has_new_rx_stats_api(mvm)) { + struct mvm_statistics_rx_phy_v2 *cck = &mvm->rx_stats_v3.cck; + + PRINT_STATS_LE32(cck, ina_cnt); + PRINT_STATS_LE32(cck, fina_cnt); + PRINT_STATS_LE32(cck, plcp_err); + PRINT_STATS_LE32(cck, crc32_err); + PRINT_STATS_LE32(cck, overrun_err); + PRINT_STATS_LE32(cck, early_overrun_err); + PRINT_STATS_LE32(cck, crc32_good); + PRINT_STATS_LE32(cck, false_alarm_cnt); + PRINT_STATS_LE32(cck, fina_sync_err_cnt); + PRINT_STATS_LE32(cck, sfd_timeout); + PRINT_STATS_LE32(cck, fina_timeout); + PRINT_STATS_LE32(cck, unresponded_rts); + PRINT_STATS_LE32(cck, rxe_frame_lmt_overrun); + PRINT_STATS_LE32(cck, sent_ack_cnt); + PRINT_STATS_LE32(cck, sent_cts_cnt); + PRINT_STATS_LE32(cck, sent_ba_rsp_cnt); + PRINT_STATS_LE32(cck, dsp_self_kill); + PRINT_STATS_LE32(cck, mh_format_err); + PRINT_STATS_LE32(cck, re_acq_main_rssi_sum); + PRINT_STATS_LE32(cck, reserved); + } else { + struct mvm_statistics_rx_phy *cck = &mvm->rx_stats.cck; + + PRINT_STATS_LE32(cck, unresponded_rts); + PRINT_STATS_LE32(cck, rxe_frame_lmt_overrun); + PRINT_STATS_LE32(cck, sent_ba_rsp_cnt); + PRINT_STATS_LE32(cck, dsp_self_kill); + PRINT_STATS_LE32(cck, reserved); + } + + pos += scnprintf(buf + pos, bufsz - pos, fmt_header, + "Statistics_Rx - GENERAL"); + if (!iwl_mvm_has_new_rx_stats_api(mvm)) { + struct mvm_statistics_rx_non_phy_v3 *general = + &mvm->rx_stats_v3.general; + + PRINT_STATS_LE32(general, bogus_cts); + PRINT_STATS_LE32(general, bogus_ack); + PRINT_STATS_LE32(general, non_bssid_frames); + PRINT_STATS_LE32(general, filtered_frames); + PRINT_STATS_LE32(general, non_channel_beacons); + PRINT_STATS_LE32(general, channel_beacons); + PRINT_STATS_LE32(general, num_missed_bcon); + PRINT_STATS_LE32(general, adc_rx_saturation_time); + PRINT_STATS_LE32(general, ina_detection_search_time); + PRINT_STATS_LE32(general, beacon_silence_rssi_a); + PRINT_STATS_LE32(general, beacon_silence_rssi_b); + PRINT_STATS_LE32(general, beacon_silence_rssi_c); + PRINT_STATS_LE32(general, interference_data_flag); + PRINT_STATS_LE32(general, channel_load); + PRINT_STATS_LE32(general, dsp_false_alarms); + PRINT_STATS_LE32(general, beacon_rssi_a); + PRINT_STATS_LE32(general, beacon_rssi_b); + PRINT_STATS_LE32(general, beacon_rssi_c); + PRINT_STATS_LE32(general, beacon_energy_a); + PRINT_STATS_LE32(general, beacon_energy_b); + PRINT_STATS_LE32(general, beacon_energy_c); + PRINT_STATS_LE32(general, num_bt_kills); + PRINT_STATS_LE32(general, mac_id); + PRINT_STATS_LE32(general, directed_data_mpdu); + } else { + struct mvm_statistics_rx_non_phy *general = + &mvm->rx_stats.general; + + PRINT_STATS_LE32(general, bogus_cts); + PRINT_STATS_LE32(general, bogus_ack); + PRINT_STATS_LE32(general, non_channel_beacons); + PRINT_STATS_LE32(general, channel_beacons); + PRINT_STATS_LE32(general, num_missed_bcon); + PRINT_STATS_LE32(general, adc_rx_saturation_time); + PRINT_STATS_LE32(general, ina_detection_search_time); + PRINT_STATS_LE32(general, beacon_silence_rssi_a); + PRINT_STATS_LE32(general, beacon_silence_rssi_b); + PRINT_STATS_LE32(general, beacon_silence_rssi_c); + PRINT_STATS_LE32(general, interference_data_flag); + PRINT_STATS_LE32(general, channel_load); + PRINT_STATS_LE32(general, beacon_rssi_a); + PRINT_STATS_LE32(general, beacon_rssi_b); + PRINT_STATS_LE32(general, beacon_rssi_c); + PRINT_STATS_LE32(general, beacon_energy_a); + PRINT_STATS_LE32(general, beacon_energy_b); + PRINT_STATS_LE32(general, beacon_energy_c); + PRINT_STATS_LE32(general, num_bt_kills); + PRINT_STATS_LE32(general, mac_id); + } + + pos += scnprintf(buf + pos, bufsz - pos, fmt_header, + "Statistics_Rx - HT"); + if (!iwl_mvm_has_new_rx_stats_api(mvm)) { + struct mvm_statistics_rx_ht_phy_v1 *ht = + &mvm->rx_stats_v3.ofdm_ht; + + PRINT_STATS_LE32(ht, plcp_err); + PRINT_STATS_LE32(ht, overrun_err); + PRINT_STATS_LE32(ht, early_overrun_err); + PRINT_STATS_LE32(ht, crc32_good); + PRINT_STATS_LE32(ht, crc32_err); + PRINT_STATS_LE32(ht, mh_format_err); + PRINT_STATS_LE32(ht, agg_crc32_good); + PRINT_STATS_LE32(ht, agg_mpdu_cnt); + PRINT_STATS_LE32(ht, agg_cnt); + PRINT_STATS_LE32(ht, unsupport_mcs); + } else { + struct mvm_statistics_rx_ht_phy *ht = + &mvm->rx_stats.ofdm_ht; + + PRINT_STATS_LE32(ht, mh_format_err); + PRINT_STATS_LE32(ht, agg_mpdu_cnt); + PRINT_STATS_LE32(ht, agg_cnt); + PRINT_STATS_LE32(ht, unsupport_mcs); + } + + mutex_unlock(&mvm->mutex); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + + return ret; +} +#undef PRINT_STAT_LE32 + +static ssize_t iwl_dbgfs_frame_stats_read(struct iwl_mvm *mvm, + char __user *user_buf, size_t count, + loff_t *ppos, + struct iwl_mvm_frame_stats *stats) +{ + char *buff, *pos, *endpos; + int idx, i; + int ret; + static const size_t bufsz = 1024; + + buff = kmalloc(bufsz, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + spin_lock_bh(&mvm->drv_stats_lock); + + pos = buff; + endpos = pos + bufsz; + + pos += scnprintf(pos, endpos - pos, + "Legacy/HT/VHT\t:\t%d/%d/%d\n", + stats->legacy_frames, + stats->ht_frames, + stats->vht_frames); + pos += scnprintf(pos, endpos - pos, "20/40/80\t:\t%d/%d/%d\n", + stats->bw_20_frames, + stats->bw_40_frames, + stats->bw_80_frames); + pos += scnprintf(pos, endpos - pos, "NGI/SGI\t\t:\t%d/%d\n", + stats->ngi_frames, + stats->sgi_frames); + pos += scnprintf(pos, endpos - pos, "SISO/MIMO2\t:\t%d/%d\n", + stats->siso_frames, + stats->mimo2_frames); + pos += scnprintf(pos, endpos - pos, "FAIL/SCSS\t:\t%d/%d\n", + stats->fail_frames, + stats->success_frames); + pos += scnprintf(pos, endpos - pos, "MPDUs agg\t:\t%d\n", + stats->agg_frames); + pos += scnprintf(pos, endpos - pos, "A-MPDUs\t\t:\t%d\n", + stats->ampdu_count); + pos += scnprintf(pos, endpos - pos, "Avg MPDUs/A-MPDU:\t%d\n", + stats->ampdu_count > 0 ? + (stats->agg_frames / stats->ampdu_count) : 0); + + pos += scnprintf(pos, endpos - pos, "Last Rates\n"); + + idx = stats->last_frame_idx - 1; + for (i = 0; i < ARRAY_SIZE(stats->last_rates); i++) { + idx = (idx + 1) % ARRAY_SIZE(stats->last_rates); + if (stats->last_rates[idx] == 0) + continue; + pos += scnprintf(pos, endpos - pos, "Rate[%d]: ", + (int)(ARRAY_SIZE(stats->last_rates) - i)); + pos += rs_pretty_print_rate_v1(pos, endpos - pos, + stats->last_rates[idx]); + if (pos < endpos - 1) + *pos++ = '\n'; + } + spin_unlock_bh(&mvm->drv_stats_lock); + + ret = simple_read_from_buffer(user_buf, count, ppos, buff, pos - buff); + kfree(buff); + + return ret; +} + +static ssize_t iwl_dbgfs_drv_rx_stats_read(struct file *file, + char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + + return iwl_dbgfs_frame_stats_read(mvm, user_buf, count, ppos, + &mvm->drv_rx_stats); +} + +static ssize_t iwl_dbgfs_fw_restart_write(struct iwl_mvm *mvm, char *buf, + size_t count, loff_t *ppos) +{ + int __maybe_unused ret; + + if (!iwl_mvm_firmware_running(mvm)) + return -EIO; + + mutex_lock(&mvm->mutex); + + /* allow one more restart that we're provoking here */ + if (mvm->fw_restart >= 0) + mvm->fw_restart++; + + if (count == 6 && !strcmp(buf, "nolog\n")) { + set_bit(IWL_MVM_STATUS_SUPPRESS_ERROR_LOG_ONCE, &mvm->status); + set_bit(STATUS_SUPPRESS_CMD_ERROR_ONCE, &mvm->trans->status); + } + + /* take the return value to make compiler happy - it will fail anyway */ + ret = iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(LONG_GROUP, REPLY_ERROR), + 0, 0, NULL); + + mutex_unlock(&mvm->mutex); + + return count; +} + +static ssize_t iwl_dbgfs_fw_nmi_write(struct iwl_mvm *mvm, char *buf, + size_t count, loff_t *ppos) +{ + if (!iwl_mvm_firmware_running(mvm)) + return -EIO; + + if (count == 6 && !strcmp(buf, "nolog\n")) + set_bit(IWL_MVM_STATUS_SUPPRESS_ERROR_LOG_ONCE, &mvm->status); + + iwl_force_nmi(mvm->trans); + + return count; +} + +static ssize_t +iwl_dbgfs_scan_ant_rxchain_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + int pos = 0; + char buf[32]; + const size_t bufsz = sizeof(buf); + + /* print which antennas were set for the scan command by the user */ + pos += scnprintf(buf + pos, bufsz - pos, "Antennas for scan: "); + if (mvm->scan_rx_ant & ANT_A) + pos += scnprintf(buf + pos, bufsz - pos, "A"); + if (mvm->scan_rx_ant & ANT_B) + pos += scnprintf(buf + pos, bufsz - pos, "B"); + pos += scnprintf(buf + pos, bufsz - pos, " (%hhx)\n", mvm->scan_rx_ant); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t +iwl_dbgfs_scan_ant_rxchain_write(struct iwl_mvm *mvm, char *buf, + size_t count, loff_t *ppos) +{ + u8 scan_rx_ant; + + if (!iwl_mvm_firmware_running(mvm)) + return -EIO; + + if (sscanf(buf, "%hhx", &scan_rx_ant) != 1) + return -EINVAL; + if (scan_rx_ant > ANT_ABC) + return -EINVAL; + if (scan_rx_ant & ~(iwl_mvm_get_valid_rx_ant(mvm))) + return -EINVAL; + + if (mvm->scan_rx_ant != scan_rx_ant) { + mvm->scan_rx_ant = scan_rx_ant; + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_UMAC_SCAN)) + iwl_mvm_config_scan(mvm); + } + + return count; +} + +static ssize_t iwl_dbgfs_indirection_tbl_write(struct iwl_mvm *mvm, + char *buf, size_t count, + loff_t *ppos) +{ + struct iwl_rss_config_cmd cmd = { + .flags = cpu_to_le32(IWL_RSS_ENABLE), + .hash_mask = IWL_RSS_HASH_TYPE_IPV4_TCP | + IWL_RSS_HASH_TYPE_IPV4_UDP | + IWL_RSS_HASH_TYPE_IPV4_PAYLOAD | + IWL_RSS_HASH_TYPE_IPV6_TCP | + IWL_RSS_HASH_TYPE_IPV6_UDP | + IWL_RSS_HASH_TYPE_IPV6_PAYLOAD, + }; + int ret, i, num_repeats, nbytes = count / 2; + + ret = hex2bin(cmd.indirection_table, buf, nbytes); + if (ret) + return ret; + + /* + * The input is the redirection table, partial or full. + * Repeat the pattern if needed. + * For example, input of 01020F will be repeated 42 times, + * indirecting RSS hash results to queues 1, 2, 15 (skipping + * queues 3 - 14). + */ + num_repeats = ARRAY_SIZE(cmd.indirection_table) / nbytes; + for (i = 1; i < num_repeats; i++) + memcpy(&cmd.indirection_table[i * nbytes], + cmd.indirection_table, nbytes); + /* handle cut in the middle pattern for the last places */ + memcpy(&cmd.indirection_table[i * nbytes], cmd.indirection_table, + ARRAY_SIZE(cmd.indirection_table) % nbytes); + + netdev_rss_key_fill(cmd.secret_key, sizeof(cmd.secret_key)); + + mutex_lock(&mvm->mutex); + if (iwl_mvm_firmware_running(mvm)) + ret = iwl_mvm_send_cmd_pdu(mvm, RSS_CONFIG_CMD, 0, + sizeof(cmd), &cmd); + else + ret = 0; + mutex_unlock(&mvm->mutex); + + return ret ?: count; +} + +static ssize_t iwl_dbgfs_inject_packet_write(struct iwl_mvm *mvm, + char *buf, size_t count, + loff_t *ppos) +{ + struct iwl_op_mode *opmode = container_of((void *)mvm, + struct iwl_op_mode, + op_mode_specific); + struct iwl_rx_cmd_buffer rxb = { + ._rx_page_order = 0, + .truesize = 0, /* not used */ + ._offset = 0, + }; + struct iwl_rx_packet *pkt; + int bin_len = count / 2; + int ret = -EINVAL; + + if (!iwl_mvm_firmware_running(mvm)) + return -EIO; + + /* supporting only MQ RX */ + if (!mvm->trans->trans_cfg->mq_rx_supported) + return -ENOTSUPP; + + rxb._page = alloc_pages(GFP_ATOMIC, 0); + if (!rxb._page) + return -ENOMEM; + pkt = rxb_addr(&rxb); + + ret = hex2bin(page_address(rxb._page), buf, bin_len); + if (ret) + goto out; + + /* avoid invalid memory access and malformed packet */ + if (bin_len < sizeof(*pkt) || + bin_len != sizeof(*pkt) + iwl_rx_packet_payload_len(pkt)) + goto out; + + local_bh_disable(); + iwl_mvm_rx_mq(opmode, NULL, &rxb); + local_bh_enable(); + ret = 0; + +out: + iwl_free_rxb(&rxb); + + return ret ?: count; +} + +static int _iwl_dbgfs_inject_beacon_ie(struct iwl_mvm *mvm, char *bin, int len) +{ + struct ieee80211_vif *vif; + struct iwl_mvm_vif *mvmvif; + struct sk_buff *beacon; + struct ieee80211_tx_info *info; + struct iwl_mac_beacon_cmd beacon_cmd = {}; + u8 rate; + int i; + + len /= 2; + + /* Element len should be represented by u8 */ + if (len >= U8_MAX) + return -EINVAL; + + if (!iwl_mvm_firmware_running(mvm)) + return -EIO; + + if (!iwl_mvm_has_new_tx_api(mvm) && + !fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_NEW_BEACON_TEMPLATE)) + return -EINVAL; + + mutex_lock(&mvm->mutex); + + for (i = 0; i < NUM_MAC_INDEX_DRIVER; i++) { + vif = iwl_mvm_rcu_dereference_vif_id(mvm, i, false); + if (!vif) + continue; + + if (vif->type == NL80211_IFTYPE_AP) + break; + } + + if (i == NUM_MAC_INDEX_DRIVER || !vif) + goto out_err; + + mvm->hw->extra_beacon_tailroom = len; + + beacon = ieee80211_beacon_get_template(mvm->hw, vif, NULL, 0); + if (!beacon) + goto out_err; + + if (len && hex2bin(skb_put_zero(beacon, len), bin, len)) { + dev_kfree_skb(beacon); + goto out_err; + } + + mvm->beacon_inject_active = true; + + mvmvif = iwl_mvm_vif_from_mac80211(vif); + info = IEEE80211_SKB_CB(beacon); + rate = iwl_mvm_mac_ctxt_get_lowest_rate(info, vif); + + beacon_cmd.flags = + cpu_to_le16(iwl_mvm_mac_ctxt_get_beacon_flags(mvm->fw, rate)); + beacon_cmd.byte_cnt = cpu_to_le16((u16)beacon->len); + beacon_cmd.template_id = cpu_to_le32((u32)mvmvif->id); + + iwl_mvm_mac_ctxt_set_tim(mvm, &beacon_cmd.tim_idx, + &beacon_cmd.tim_size, + beacon->data, beacon->len); + + iwl_mvm_mac_ctxt_send_beacon_cmd(mvm, beacon, &beacon_cmd, + sizeof(beacon_cmd)); + mutex_unlock(&mvm->mutex); + + dev_kfree_skb(beacon); + + return 0; + +out_err: + mutex_unlock(&mvm->mutex); + return -EINVAL; +} + +static ssize_t iwl_dbgfs_inject_beacon_ie_write(struct iwl_mvm *mvm, + char *buf, size_t count, + loff_t *ppos) +{ + int ret = _iwl_dbgfs_inject_beacon_ie(mvm, buf, count); + + mvm->hw->extra_beacon_tailroom = 0; + return ret ?: count; +} + +static ssize_t iwl_dbgfs_inject_beacon_ie_restore_write(struct iwl_mvm *mvm, + char *buf, + size_t count, + loff_t *ppos) +{ + int ret = _iwl_dbgfs_inject_beacon_ie(mvm, NULL, 0); + + mvm->hw->extra_beacon_tailroom = 0; + mvm->beacon_inject_active = false; + return ret ?: count; +} + +static ssize_t iwl_dbgfs_fw_dbg_conf_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + int conf; + char buf[8]; + const size_t bufsz = sizeof(buf); + int pos = 0; + + mutex_lock(&mvm->mutex); + conf = mvm->fwrt.dump.conf; + mutex_unlock(&mvm->mutex); + + pos += scnprintf(buf + pos, bufsz - pos, "%d\n", conf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_fw_dbg_conf_write(struct iwl_mvm *mvm, + char *buf, size_t count, + loff_t *ppos) +{ + unsigned int conf_id; + int ret; + + if (!iwl_mvm_firmware_running(mvm)) + return -EIO; + + ret = kstrtouint(buf, 0, &conf_id); + if (ret) + return ret; + + if (WARN_ON(conf_id >= FW_DBG_CONF_MAX)) + return -EINVAL; + + mutex_lock(&mvm->mutex); + ret = iwl_fw_start_dbg_conf(&mvm->fwrt, conf_id); + mutex_unlock(&mvm->mutex); + + return ret ?: count; +} + +static ssize_t iwl_dbgfs_fw_dbg_collect_write(struct iwl_mvm *mvm, + char *buf, size_t count, + loff_t *ppos) +{ + if (count == 0) + return 0; + + iwl_dbg_tlv_time_point(&mvm->fwrt, IWL_FW_INI_TIME_POINT_USER_TRIGGER, + NULL); + + iwl_fw_dbg_collect(&mvm->fwrt, FW_DBG_TRIGGER_USER, buf, + (count - 1), NULL); + + return count; +} + +static ssize_t iwl_dbgfs_dbg_time_point_write(struct iwl_mvm *mvm, + char *buf, size_t count, + loff_t *ppos) +{ + u32 timepoint; + + if (kstrtou32(buf, 0, &timepoint)) + return -EINVAL; + + if (timepoint == IWL_FW_INI_TIME_POINT_INVALID || + timepoint >= IWL_FW_INI_TIME_POINT_NUM) + return -EINVAL; + + iwl_dbg_tlv_time_point(&mvm->fwrt, timepoint, NULL); + + return count; +} + +#define MVM_DEBUGFS_WRITE_FILE_OPS(name, bufsz) \ + _MVM_DEBUGFS_WRITE_FILE_OPS(name, bufsz, struct iwl_mvm) +#define MVM_DEBUGFS_READ_WRITE_FILE_OPS(name, bufsz) \ + _MVM_DEBUGFS_READ_WRITE_FILE_OPS(name, bufsz, struct iwl_mvm) +#define MVM_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode) do { \ + debugfs_create_file(alias, mode, parent, mvm, \ + &iwl_dbgfs_##name##_ops); \ + } while (0) +#define MVM_DEBUGFS_ADD_FILE(name, parent, mode) \ + MVM_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode) + +#define MVM_DEBUGFS_WRITE_STA_FILE_OPS(name, bufsz) \ + _MVM_DEBUGFS_WRITE_FILE_OPS(name, bufsz, struct ieee80211_sta) +#define MVM_DEBUGFS_READ_WRITE_STA_FILE_OPS(name, bufsz) \ + _MVM_DEBUGFS_READ_WRITE_FILE_OPS(name, bufsz, struct ieee80211_sta) + +#define MVM_DEBUGFS_ADD_STA_FILE_ALIAS(alias, name, parent, mode) do { \ + debugfs_create_file(alias, mode, parent, sta, \ + &iwl_dbgfs_##name##_ops); \ + } while (0) +#define MVM_DEBUGFS_ADD_STA_FILE(name, parent, mode) \ + MVM_DEBUGFS_ADD_STA_FILE_ALIAS(#name, name, parent, mode) + +static ssize_t +iwl_dbgfs_prph_reg_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + int pos = 0; + char buf[32]; + const size_t bufsz = sizeof(buf); + + if (!mvm->dbgfs_prph_reg_addr) + return -EINVAL; + + pos += scnprintf(buf + pos, bufsz - pos, "Reg 0x%x: (0x%x)\n", + mvm->dbgfs_prph_reg_addr, + iwl_read_prph(mvm->trans, mvm->dbgfs_prph_reg_addr)); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t +iwl_dbgfs_prph_reg_write(struct iwl_mvm *mvm, char *buf, + size_t count, loff_t *ppos) +{ + u8 args; + u32 value; + + args = sscanf(buf, "%i %i", &mvm->dbgfs_prph_reg_addr, &value); + /* if we only want to set the reg address - nothing more to do */ + if (args == 1) + goto out; + + /* otherwise, make sure we have both address and value */ + if (args != 2) + return -EINVAL; + + iwl_write_prph(mvm->trans, mvm->dbgfs_prph_reg_addr, value); + +out: + return count; +} + +static ssize_t +iwl_dbgfs_send_echo_cmd_write(struct iwl_mvm *mvm, char *buf, + size_t count, loff_t *ppos) +{ + int ret; + + if (!iwl_mvm_firmware_running(mvm)) + return -EIO; + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_send_cmd_pdu(mvm, ECHO_CMD, 0, 0, NULL); + mutex_unlock(&mvm->mutex); + + return ret ?: count; +} + +struct iwl_mvm_sniffer_apply { + struct iwl_mvm *mvm; + u8 *bssid; + u16 aid; +}; + +static bool iwl_mvm_sniffer_apply(struct iwl_notif_wait_data *notif_data, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_mvm_sniffer_apply *apply = data; + + apply->mvm->cur_aid = cpu_to_le16(apply->aid); + memcpy(apply->mvm->cur_bssid, apply->bssid, + sizeof(apply->mvm->cur_bssid)); + + return true; +} + +static ssize_t +iwl_dbgfs_he_sniffer_params_write(struct iwl_mvm *mvm, char *buf, + size_t count, loff_t *ppos) +{ + struct iwl_notification_wait wait; + struct iwl_he_monitor_cmd he_mon_cmd = {}; + struct iwl_mvm_sniffer_apply apply = { + .mvm = mvm, + }; + u16 wait_cmds[] = { + WIDE_ID(DATA_PATH_GROUP, HE_AIR_SNIFFER_CONFIG_CMD), + }; + u32 aid; + int ret; + + if (!iwl_mvm_firmware_running(mvm)) + return -EIO; + + ret = sscanf(buf, "%x %2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx", &aid, + &he_mon_cmd.bssid[0], &he_mon_cmd.bssid[1], + &he_mon_cmd.bssid[2], &he_mon_cmd.bssid[3], + &he_mon_cmd.bssid[4], &he_mon_cmd.bssid[5]); + if (ret != 7) + return -EINVAL; + + he_mon_cmd.aid = cpu_to_le16(aid); + + apply.aid = aid; + apply.bssid = (void *)he_mon_cmd.bssid; + + mutex_lock(&mvm->mutex); + + /* + * Use the notification waiter to get our function triggered + * in sequence with other RX. This ensures that frames we get + * on the RX queue _before_ the new configuration is applied + * still have mvm->cur_aid pointing to the old AID, and that + * frames on the RX queue _after_ the firmware processed the + * new configuration (and sent the response, synchronously) + * get mvm->cur_aid correctly set to the new AID. + */ + iwl_init_notification_wait(&mvm->notif_wait, &wait, + wait_cmds, ARRAY_SIZE(wait_cmds), + iwl_mvm_sniffer_apply, &apply); + + ret = iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(DATA_PATH_GROUP, HE_AIR_SNIFFER_CONFIG_CMD), + 0, + sizeof(he_mon_cmd), &he_mon_cmd); + + /* no need to really wait, we already did anyway */ + iwl_remove_notification(&mvm->notif_wait, &wait); + + mutex_unlock(&mvm->mutex); + + return ret ?: count; +} + +static ssize_t +iwl_dbgfs_he_sniffer_params_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + u8 buf[32]; + int len; + + len = scnprintf(buf, sizeof(buf), + "%d %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", + le16_to_cpu(mvm->cur_aid), mvm->cur_bssid[0], + mvm->cur_bssid[1], mvm->cur_bssid[2], mvm->cur_bssid[3], + mvm->cur_bssid[4], mvm->cur_bssid[5]); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t +iwl_dbgfs_uapsd_noagg_bssids_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + u8 buf[IWL_MVM_UAPSD_NOAGG_BSSIDS_NUM * ETH_ALEN * 3 + 1]; + unsigned int pos = 0; + size_t bufsz = sizeof(buf); + int i; + + mutex_lock(&mvm->mutex); + + for (i = 0; i < IWL_MVM_UAPSD_NOAGG_LIST_LEN; i++) + pos += scnprintf(buf + pos, bufsz - pos, "%pM\n", + mvm->uapsd_noagg_bssids[i].addr); + + mutex_unlock(&mvm->mutex); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t +iwl_dbgfs_ltr_config_write(struct iwl_mvm *mvm, + char *buf, size_t count, loff_t *ppos) +{ + int ret; + struct iwl_ltr_config_cmd ltr_config = {0}; + + if (!iwl_mvm_firmware_running(mvm)) + return -EIO; + + if (sscanf(buf, "%x,%x,%x,%x,%x,%x,%x", + <r_config.flags, + <r_config.static_long, + <r_config.static_short, + <r_config.ltr_cfg_values[0], + <r_config.ltr_cfg_values[1], + <r_config.ltr_cfg_values[2], + <r_config.ltr_cfg_values[3]) != 7) { + return -EINVAL; + } + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_send_cmd_pdu(mvm, LTR_CONFIG, 0, sizeof(ltr_config), + <r_config); + mutex_unlock(&mvm->mutex); + + if (ret) + IWL_ERR(mvm, "failed to send ltr configuration cmd\n"); + + return ret ?: count; +} + +static ssize_t iwl_dbgfs_rfi_freq_table_write(struct iwl_mvm *mvm, char *buf, + size_t count, loff_t *ppos) +{ + int ret = 0; + u16 op_id; + + if (kstrtou16(buf, 10, &op_id)) + return -EINVAL; + + /* value zero triggers re-sending the default table to the device */ + if (!op_id) { + mutex_lock(&mvm->mutex); + ret = iwl_rfi_send_config_cmd(mvm, NULL); + mutex_unlock(&mvm->mutex); + } else { + ret = -EOPNOTSUPP; /* in the future a new table will be added */ + } + + return ret ?: count; +} + +/* The size computation is as follows: + * each number needs at most 3 characters, number of rows is the size of + * the table; So, need 5 chars for the "freq: " part and each tuple afterwards + * needs 6 characters for numbers and 5 for the punctuation around. + */ +#define IWL_RFI_BUF_SIZE (IWL_RFI_LUT_INSTALLED_SIZE *\ + (5 + IWL_RFI_LUT_ENTRY_CHANNELS_NUM * (6 + 5))) + +static ssize_t iwl_dbgfs_rfi_freq_table_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + struct iwl_rfi_freq_table_resp_cmd *resp; + u32 status; + char buf[IWL_RFI_BUF_SIZE]; + int i, j, pos = 0; + + resp = iwl_rfi_get_freq_table(mvm); + if (IS_ERR(resp)) + return PTR_ERR(resp); + + status = le32_to_cpu(resp->status); + if (status != RFI_FREQ_TABLE_OK) { + scnprintf(buf, IWL_RFI_BUF_SIZE, "status = %d\n", status); + goto out; + } + + for (i = 0; i < ARRAY_SIZE(resp->table); i++) { + pos += scnprintf(buf + pos, IWL_RFI_BUF_SIZE - pos, "%d: ", + resp->table[i].freq); + + for (j = 0; j < ARRAY_SIZE(resp->table[i].channels); j++) + pos += scnprintf(buf + pos, IWL_RFI_BUF_SIZE - pos, + "(%d, %d) ", + resp->table[i].channels[j], + resp->table[i].bands[j]); + pos += scnprintf(buf + pos, IWL_RFI_BUF_SIZE - pos, "\n"); + } + +out: + kfree(resp); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +MVM_DEBUGFS_READ_WRITE_FILE_OPS(prph_reg, 64); + +/* Device wide debugfs entries */ +MVM_DEBUGFS_READ_FILE_OPS(ctdp_budget); +MVM_DEBUGFS_WRITE_FILE_OPS(stop_ctdp, 8); +MVM_DEBUGFS_WRITE_FILE_OPS(force_ctkill, 8); +MVM_DEBUGFS_WRITE_FILE_OPS(tx_flush, 16); +MVM_DEBUGFS_WRITE_FILE_OPS(sta_drain, 8); +MVM_DEBUGFS_WRITE_FILE_OPS(send_echo_cmd, 8); +MVM_DEBUGFS_READ_WRITE_FILE_OPS(sram, 64); +MVM_DEBUGFS_READ_WRITE_FILE_OPS(set_nic_temperature, 64); +MVM_DEBUGFS_READ_FILE_OPS(nic_temp); +MVM_DEBUGFS_READ_FILE_OPS(stations); +MVM_DEBUGFS_READ_FILE_OPS(rs_data); +MVM_DEBUGFS_READ_FILE_OPS(bt_notif); +MVM_DEBUGFS_READ_FILE_OPS(bt_cmd); +MVM_DEBUGFS_READ_WRITE_FILE_OPS(disable_power_off, 64); +MVM_DEBUGFS_READ_FILE_OPS(fw_rx_stats); +MVM_DEBUGFS_READ_FILE_OPS(drv_rx_stats); +MVM_DEBUGFS_READ_FILE_OPS(fw_ver); +MVM_DEBUGFS_READ_FILE_OPS(phy_integration_ver); +MVM_DEBUGFS_WRITE_FILE_OPS(fw_restart, 10); +MVM_DEBUGFS_WRITE_FILE_OPS(fw_nmi, 10); +MVM_DEBUGFS_WRITE_FILE_OPS(bt_tx_prio, 10); +MVM_DEBUGFS_WRITE_FILE_OPS(bt_force_ant, 10); +MVM_DEBUGFS_READ_WRITE_FILE_OPS(scan_ant_rxchain, 8); +MVM_DEBUGFS_READ_WRITE_FILE_OPS(fw_dbg_conf, 8); +MVM_DEBUGFS_WRITE_FILE_OPS(fw_dbg_collect, 64); +MVM_DEBUGFS_WRITE_FILE_OPS(dbg_time_point, 64); +MVM_DEBUGFS_WRITE_FILE_OPS(indirection_tbl, + (IWL_RSS_INDIRECTION_TABLE_SIZE * 2)); +MVM_DEBUGFS_WRITE_FILE_OPS(inject_packet, 512); +MVM_DEBUGFS_WRITE_FILE_OPS(inject_beacon_ie, 512); +MVM_DEBUGFS_WRITE_FILE_OPS(inject_beacon_ie_restore, 512); + +MVM_DEBUGFS_READ_FILE_OPS(uapsd_noagg_bssids); + +#ifdef CONFIG_ACPI +MVM_DEBUGFS_READ_FILE_OPS(sar_geo_profile); +#endif + +MVM_DEBUGFS_READ_WRITE_STA_FILE_OPS(amsdu_len, 16); + +MVM_DEBUGFS_READ_WRITE_FILE_OPS(he_sniffer_params, 32); + +MVM_DEBUGFS_WRITE_FILE_OPS(ltr_config, 512); +MVM_DEBUGFS_READ_WRITE_FILE_OPS(rfi_freq_table, 16); + +static ssize_t iwl_dbgfs_mem_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + struct iwl_dbg_mem_access_cmd cmd = {}; + struct iwl_dbg_mem_access_rsp *rsp; + struct iwl_host_cmd hcmd = { + .flags = CMD_WANT_SKB | CMD_SEND_IN_RFKILL, + .data = { &cmd, }, + .len = { sizeof(cmd) }, + }; + size_t delta; + ssize_t ret, len; + + if (!iwl_mvm_firmware_running(mvm)) + return -EIO; + + hcmd.id = WIDE_ID(DEBUG_GROUP, *ppos >> 24 ? UMAC_RD_WR : LMAC_RD_WR); + cmd.op = cpu_to_le32(DEBUG_MEM_OP_READ); + + /* Take care of alignment of both the position and the length */ + delta = *ppos & 0x3; + cmd.addr = cpu_to_le32(*ppos - delta); + cmd.len = cpu_to_le32(min(ALIGN(count + delta, 4) / 4, + (size_t)DEBUG_MEM_MAX_SIZE_DWORDS)); + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_send_cmd(mvm, &hcmd); + mutex_unlock(&mvm->mutex); + + if (ret < 0) + return ret; + + if (iwl_rx_packet_payload_len(hcmd.resp_pkt) < sizeof(*rsp)) { + ret = -EIO; + goto out; + } + + rsp = (void *)hcmd.resp_pkt->data; + if (le32_to_cpu(rsp->status) != DEBUG_MEM_STATUS_SUCCESS) { + ret = -ENXIO; + goto out; + } + + len = min((size_t)le32_to_cpu(rsp->len) << 2, + iwl_rx_packet_payload_len(hcmd.resp_pkt) - sizeof(*rsp)); + len = min(len - delta, count); + if (len < 0) { + ret = -EFAULT; + goto out; + } + + ret = len - copy_to_user(user_buf, (u8 *)rsp->data + delta, len); + *ppos += ret; + +out: + iwl_free_resp(&hcmd); + return ret; +} + +static ssize_t iwl_dbgfs_mem_write(struct file *file, + const char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + struct iwl_dbg_mem_access_cmd *cmd; + struct iwl_dbg_mem_access_rsp *rsp; + struct iwl_host_cmd hcmd = {}; + size_t cmd_size; + size_t data_size; + u32 op, len; + ssize_t ret; + + if (!iwl_mvm_firmware_running(mvm)) + return -EIO; + + hcmd.id = WIDE_ID(DEBUG_GROUP, *ppos >> 24 ? UMAC_RD_WR : LMAC_RD_WR); + + if (*ppos & 0x3 || count < 4) { + op = DEBUG_MEM_OP_WRITE_BYTES; + len = min(count, (size_t)(4 - (*ppos & 0x3))); + data_size = len; + } else { + op = DEBUG_MEM_OP_WRITE; + len = min(count >> 2, (size_t)DEBUG_MEM_MAX_SIZE_DWORDS); + data_size = len << 2; + } + + cmd_size = sizeof(*cmd) + ALIGN(data_size, 4); + cmd = kzalloc(cmd_size, GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + cmd->op = cpu_to_le32(op); + cmd->len = cpu_to_le32(len); + cmd->addr = cpu_to_le32(*ppos); + if (copy_from_user((void *)cmd->data, user_buf, data_size)) { + kfree(cmd); + return -EFAULT; + } + + hcmd.flags = CMD_WANT_SKB | CMD_SEND_IN_RFKILL, + hcmd.data[0] = (void *)cmd; + hcmd.len[0] = cmd_size; + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_send_cmd(mvm, &hcmd); + mutex_unlock(&mvm->mutex); + + kfree(cmd); + + if (ret < 0) + return ret; + + if (iwl_rx_packet_payload_len(hcmd.resp_pkt) < sizeof(*rsp)) { + ret = -EIO; + goto out; + } + + rsp = (void *)hcmd.resp_pkt->data; + if (rsp->status != DEBUG_MEM_STATUS_SUCCESS) { + ret = -ENXIO; + goto out; + } + + ret = data_size; + *ppos += ret; + +out: + iwl_free_resp(&hcmd); + return ret; +} + +static const struct file_operations iwl_dbgfs_mem_ops = { + .read = iwl_dbgfs_mem_read, + .write = iwl_dbgfs_mem_write, + .open = simple_open, + .llseek = default_llseek, +}; + +void iwl_mvm_sta_add_debugfs(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct dentry *dir) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + if (iwl_mvm_has_tlc_offload(mvm)) { + MVM_DEBUGFS_ADD_STA_FILE(rs_data, dir, 0400); + } + MVM_DEBUGFS_ADD_STA_FILE(amsdu_len, dir, 0600); +} + +void iwl_mvm_dbgfs_register(struct iwl_mvm *mvm) +{ + struct dentry *bcast_dir __maybe_unused; + + spin_lock_init(&mvm->drv_stats_lock); + + MVM_DEBUGFS_ADD_FILE(tx_flush, mvm->debugfs_dir, 0200); + MVM_DEBUGFS_ADD_FILE(sta_drain, mvm->debugfs_dir, 0200); + MVM_DEBUGFS_ADD_FILE(sram, mvm->debugfs_dir, 0600); + MVM_DEBUGFS_ADD_FILE(set_nic_temperature, mvm->debugfs_dir, 0600); + MVM_DEBUGFS_ADD_FILE(nic_temp, mvm->debugfs_dir, 0400); + MVM_DEBUGFS_ADD_FILE(ctdp_budget, mvm->debugfs_dir, 0400); + MVM_DEBUGFS_ADD_FILE(stop_ctdp, mvm->debugfs_dir, 0200); + MVM_DEBUGFS_ADD_FILE(force_ctkill, mvm->debugfs_dir, 0200); + MVM_DEBUGFS_ADD_FILE(stations, mvm->debugfs_dir, 0400); + MVM_DEBUGFS_ADD_FILE(bt_notif, mvm->debugfs_dir, 0400); + MVM_DEBUGFS_ADD_FILE(bt_cmd, mvm->debugfs_dir, 0400); + MVM_DEBUGFS_ADD_FILE(disable_power_off, mvm->debugfs_dir, 0600); + MVM_DEBUGFS_ADD_FILE(fw_ver, mvm->debugfs_dir, 0400); + MVM_DEBUGFS_ADD_FILE(fw_rx_stats, mvm->debugfs_dir, 0400); + MVM_DEBUGFS_ADD_FILE(drv_rx_stats, mvm->debugfs_dir, 0400); + MVM_DEBUGFS_ADD_FILE(fw_restart, mvm->debugfs_dir, 0200); + MVM_DEBUGFS_ADD_FILE(fw_nmi, mvm->debugfs_dir, 0200); + MVM_DEBUGFS_ADD_FILE(bt_tx_prio, mvm->debugfs_dir, 0200); + MVM_DEBUGFS_ADD_FILE(bt_force_ant, mvm->debugfs_dir, 0200); + MVM_DEBUGFS_ADD_FILE(scan_ant_rxchain, mvm->debugfs_dir, 0600); + MVM_DEBUGFS_ADD_FILE(prph_reg, mvm->debugfs_dir, 0600); + MVM_DEBUGFS_ADD_FILE(fw_dbg_conf, mvm->debugfs_dir, 0600); + MVM_DEBUGFS_ADD_FILE(fw_dbg_collect, mvm->debugfs_dir, 0200); + MVM_DEBUGFS_ADD_FILE(dbg_time_point, mvm->debugfs_dir, 0200); + MVM_DEBUGFS_ADD_FILE(send_echo_cmd, mvm->debugfs_dir, 0200); + MVM_DEBUGFS_ADD_FILE(indirection_tbl, mvm->debugfs_dir, 0200); + MVM_DEBUGFS_ADD_FILE(inject_packet, mvm->debugfs_dir, 0200); + MVM_DEBUGFS_ADD_FILE(inject_beacon_ie, mvm->debugfs_dir, 0200); + MVM_DEBUGFS_ADD_FILE(inject_beacon_ie_restore, mvm->debugfs_dir, 0200); + MVM_DEBUGFS_ADD_FILE(rfi_freq_table, mvm->debugfs_dir, 0600); + + if (mvm->fw->phy_integration_ver) + MVM_DEBUGFS_ADD_FILE(phy_integration_ver, mvm->debugfs_dir, 0400); +#ifdef CONFIG_ACPI + MVM_DEBUGFS_ADD_FILE(sar_geo_profile, mvm->debugfs_dir, 0400); +#endif + MVM_DEBUGFS_ADD_FILE(he_sniffer_params, mvm->debugfs_dir, 0600); + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_SET_LTR_GEN2)) + MVM_DEBUGFS_ADD_FILE(ltr_config, mvm->debugfs_dir, 0200); + + debugfs_create_bool("enable_scan_iteration_notif", 0600, + mvm->debugfs_dir, &mvm->scan_iter_notif_enabled); + debugfs_create_bool("drop_bcn_ap_mode", 0600, mvm->debugfs_dir, + &mvm->drop_bcn_ap_mode); + + MVM_DEBUGFS_ADD_FILE(uapsd_noagg_bssids, mvm->debugfs_dir, S_IRUSR); + +#ifdef CONFIG_PM_SLEEP + MVM_DEBUGFS_ADD_FILE(d3_test, mvm->debugfs_dir, 0400); + debugfs_create_bool("d3_wake_sysassert", 0600, mvm->debugfs_dir, + &mvm->d3_wake_sysassert); + debugfs_create_u32("last_netdetect_scans", 0400, mvm->debugfs_dir, + &mvm->last_netdetect_scans); +#endif + + debugfs_create_u8("ps_disabled", 0400, mvm->debugfs_dir, + &mvm->ps_disabled); + debugfs_create_blob("nvm_hw", 0400, mvm->debugfs_dir, + &mvm->nvm_hw_blob); + debugfs_create_blob("nvm_sw", 0400, mvm->debugfs_dir, + &mvm->nvm_sw_blob); + debugfs_create_blob("nvm_calib", 0400, mvm->debugfs_dir, + &mvm->nvm_calib_blob); + debugfs_create_blob("nvm_prod", 0400, mvm->debugfs_dir, + &mvm->nvm_prod_blob); + debugfs_create_blob("nvm_phy_sku", 0400, mvm->debugfs_dir, + &mvm->nvm_phy_sku_blob); + debugfs_create_blob("nvm_reg", S_IRUSR, + mvm->debugfs_dir, &mvm->nvm_reg_blob); + + debugfs_create_file("mem", 0600, mvm->debugfs_dir, mvm, + &iwl_dbgfs_mem_ops); + + /* + * Create a symlink with mac80211. It will be removed when mac80211 + * exists (before the opmode exists which removes the target.) + */ + if (!IS_ERR(mvm->debugfs_dir)) { + char buf[100]; + + snprintf(buf, 100, "../../%pd2", mvm->debugfs_dir->d_parent); + debugfs_create_symlink("iwlwifi", mvm->hw->wiphy->debugfsdir, + buf); + } +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/debugfs.h b/drivers/net/wireless/intel/iwlwifi/mvm/debugfs.h new file mode 100644 index 000000000..0711ab689 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/debugfs.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + */ +#define MVM_DEBUGFS_READ_FILE_OPS(name) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .read = iwl_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +} + +#define MVM_DEBUGFS_WRITE_WRAPPER(name, buflen, argtype) \ +static ssize_t _iwl_dbgfs_##name##_write(struct file *file, \ + const char __user *user_buf, \ + size_t count, loff_t *ppos) \ +{ \ + argtype *arg = file->private_data; \ + char buf[buflen] = {}; \ + size_t buf_size = min(count, sizeof(buf) - 1); \ + \ + if (copy_from_user(buf, user_buf, buf_size)) \ + return -EFAULT; \ + \ + return iwl_dbgfs_##name##_write(arg, buf, buf_size, ppos); \ +} \ + +#define _MVM_DEBUGFS_READ_WRITE_FILE_OPS(name, buflen, argtype) \ +MVM_DEBUGFS_WRITE_WRAPPER(name, buflen, argtype) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .write = _iwl_dbgfs_##name##_write, \ + .read = iwl_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define _MVM_DEBUGFS_WRITE_FILE_OPS(name, buflen, argtype) \ +MVM_DEBUGFS_WRITE_WRAPPER(name, buflen, argtype) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .write = _iwl_dbgfs_##name##_write, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c b/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c new file mode 100644 index 000000000..8c5b97fb1 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c @@ -0,0 +1,1367 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2015-2017 Intel Deutschland GmbH + * Copyright (C) 2018-2022 Intel Corporation + */ +#include <linux/etherdevice.h> +#include <linux/math64.h> +#include <net/cfg80211.h> +#include "mvm.h" +#include "iwl-io.h" +#include "iwl-prph.h" +#include "constants.h" + +struct iwl_mvm_loc_entry { + struct list_head list; + u8 addr[ETH_ALEN]; + u8 lci_len, civic_len; + u8 buf[]; +}; + +struct iwl_mvm_smooth_entry { + struct list_head list; + u8 addr[ETH_ALEN]; + s64 rtt_avg; + u64 host_time; +}; + +struct iwl_mvm_ftm_pasn_entry { + struct list_head list; + u8 addr[ETH_ALEN]; + u8 hltk[HLTK_11AZ_LEN]; + u8 tk[TK_11AZ_LEN]; + u8 cipher; + u8 tx_pn[IEEE80211_CCMP_PN_LEN]; + u8 rx_pn[IEEE80211_CCMP_PN_LEN]; +}; + +int iwl_mvm_ftm_add_pasn_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + u8 *addr, u32 cipher, u8 *tk, u32 tk_len, + u8 *hltk, u32 hltk_len) +{ + struct iwl_mvm_ftm_pasn_entry *pasn = kzalloc(sizeof(*pasn), + GFP_KERNEL); + u32 expected_tk_len; + + lockdep_assert_held(&mvm->mutex); + + if (!pasn) + return -ENOBUFS; + + pasn->cipher = iwl_mvm_cipher_to_location_cipher(cipher); + + switch (pasn->cipher) { + case IWL_LOCATION_CIPHER_CCMP_128: + case IWL_LOCATION_CIPHER_GCMP_128: + expected_tk_len = WLAN_KEY_LEN_CCMP; + break; + case IWL_LOCATION_CIPHER_GCMP_256: + expected_tk_len = WLAN_KEY_LEN_GCMP_256; + break; + default: + goto out; + } + + /* + * If associated to this AP and already have security context, + * the TK is already configured for this station, so it + * shouldn't be set again here. + */ + if (vif->cfg.assoc && + !memcmp(addr, vif->bss_conf.bssid, ETH_ALEN)) { + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_sta *sta; + + rcu_read_lock(); + sta = rcu_dereference(mvm->fw_id_to_mac_id[mvmvif->ap_sta_id]); + if (!IS_ERR_OR_NULL(sta) && sta->mfp) + expected_tk_len = 0; + rcu_read_unlock(); + } + + if (tk_len != expected_tk_len || hltk_len != sizeof(pasn->hltk)) { + IWL_ERR(mvm, "Invalid key length: tk_len=%u hltk_len=%u\n", + tk_len, hltk_len); + goto out; + } + + memcpy(pasn->addr, addr, sizeof(pasn->addr)); + memcpy(pasn->hltk, hltk, sizeof(pasn->hltk)); + + if (tk && tk_len) + memcpy(pasn->tk, tk, sizeof(pasn->tk)); + + list_add_tail(&pasn->list, &mvm->ftm_initiator.pasn_list); + return 0; +out: + kfree(pasn); + return -EINVAL; +} + +void iwl_mvm_ftm_remove_pasn_sta(struct iwl_mvm *mvm, u8 *addr) +{ + struct iwl_mvm_ftm_pasn_entry *entry, *prev; + + lockdep_assert_held(&mvm->mutex); + + list_for_each_entry_safe(entry, prev, &mvm->ftm_initiator.pasn_list, + list) { + if (memcmp(entry->addr, addr, sizeof(entry->addr))) + continue; + + list_del(&entry->list); + kfree(entry); + return; + } +} + +static void iwl_mvm_ftm_reset(struct iwl_mvm *mvm) +{ + struct iwl_mvm_loc_entry *e, *t; + + mvm->ftm_initiator.req = NULL; + mvm->ftm_initiator.req_wdev = NULL; + memset(mvm->ftm_initiator.responses, 0, + sizeof(mvm->ftm_initiator.responses)); + + list_for_each_entry_safe(e, t, &mvm->ftm_initiator.loc_list, list) { + list_del(&e->list); + kfree(e); + } +} + +void iwl_mvm_ftm_restart(struct iwl_mvm *mvm) +{ + struct cfg80211_pmsr_result result = { + .status = NL80211_PMSR_STATUS_FAILURE, + .final = 1, + .host_time = ktime_get_boottime_ns(), + .type = NL80211_PMSR_TYPE_FTM, + }; + int i; + + lockdep_assert_held(&mvm->mutex); + + if (!mvm->ftm_initiator.req) + return; + + for (i = 0; i < mvm->ftm_initiator.req->n_peers; i++) { + memcpy(result.addr, mvm->ftm_initiator.req->peers[i].addr, + ETH_ALEN); + result.ftm.burst_index = mvm->ftm_initiator.responses[i]; + + cfg80211_pmsr_report(mvm->ftm_initiator.req_wdev, + mvm->ftm_initiator.req, + &result, GFP_KERNEL); + } + + cfg80211_pmsr_complete(mvm->ftm_initiator.req_wdev, + mvm->ftm_initiator.req, GFP_KERNEL); + iwl_mvm_ftm_reset(mvm); +} + +void iwl_mvm_ftm_initiator_smooth_config(struct iwl_mvm *mvm) +{ + INIT_LIST_HEAD(&mvm->ftm_initiator.smooth.resp); + + IWL_DEBUG_INFO(mvm, + "enable=%u, alpha=%u, age_jiffies=%u, thresh=(%u:%u)\n", + IWL_MVM_FTM_INITIATOR_ENABLE_SMOOTH, + IWL_MVM_FTM_INITIATOR_SMOOTH_ALPHA, + IWL_MVM_FTM_INITIATOR_SMOOTH_AGE_SEC * HZ, + IWL_MVM_FTM_INITIATOR_SMOOTH_OVERSHOOT, + IWL_MVM_FTM_INITIATOR_SMOOTH_UNDERSHOOT); +} + +void iwl_mvm_ftm_initiator_smooth_stop(struct iwl_mvm *mvm) +{ + struct iwl_mvm_smooth_entry *se, *st; + + list_for_each_entry_safe(se, st, &mvm->ftm_initiator.smooth.resp, + list) { + list_del(&se->list); + kfree(se); + } +} + +static int +iwl_ftm_range_request_status_to_err(enum iwl_tof_range_request_status s) +{ + switch (s) { + case IWL_TOF_RANGE_REQUEST_STATUS_SUCCESS: + return 0; + case IWL_TOF_RANGE_REQUEST_STATUS_BUSY: + return -EBUSY; + default: + WARN_ON_ONCE(1); + return -EIO; + } +} + +static void iwl_mvm_ftm_cmd_v5(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_tof_range_req_cmd_v5 *cmd, + struct cfg80211_pmsr_request *req) +{ + int i; + + cmd->request_id = req->cookie; + cmd->num_of_ap = req->n_peers; + + /* use maximum for "no timeout" or bigger than what we can do */ + if (!req->timeout || req->timeout > 255 * 100) + cmd->req_timeout = 255; + else + cmd->req_timeout = DIV_ROUND_UP(req->timeout, 100); + + /* + * We treat it always as random, since if not we'll + * have filled our local address there instead. + */ + cmd->macaddr_random = 1; + memcpy(cmd->macaddr_template, req->mac_addr, ETH_ALEN); + for (i = 0; i < ETH_ALEN; i++) + cmd->macaddr_mask[i] = ~req->mac_addr_mask[i]; + + if (vif->cfg.assoc) + memcpy(cmd->range_req_bssid, vif->bss_conf.bssid, ETH_ALEN); + else + eth_broadcast_addr(cmd->range_req_bssid); +} + +static void iwl_mvm_ftm_cmd_common(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_tof_range_req_cmd_v9 *cmd, + struct cfg80211_pmsr_request *req) +{ + int i; + + cmd->initiator_flags = + cpu_to_le32(IWL_TOF_INITIATOR_FLAGS_MACADDR_RANDOM | + IWL_TOF_INITIATOR_FLAGS_NON_ASAP_SUPPORT); + cmd->request_id = req->cookie; + cmd->num_of_ap = req->n_peers; + + /* + * Use a large value for "no timeout". Don't use the maximum value + * because of fw limitations. + */ + if (req->timeout) + cmd->req_timeout_ms = cpu_to_le32(req->timeout); + else + cmd->req_timeout_ms = cpu_to_le32(0xfffff); + + memcpy(cmd->macaddr_template, req->mac_addr, ETH_ALEN); + for (i = 0; i < ETH_ALEN; i++) + cmd->macaddr_mask[i] = ~req->mac_addr_mask[i]; + + if (vif->cfg.assoc) { + memcpy(cmd->range_req_bssid, vif->bss_conf.bssid, ETH_ALEN); + + /* AP's TSF is only relevant if associated */ + for (i = 0; i < req->n_peers; i++) { + if (req->peers[i].report_ap_tsf) { + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(vif); + + cmd->tsf_mac_id = cpu_to_le32(mvmvif->id); + return; + } + } + } else { + eth_broadcast_addr(cmd->range_req_bssid); + } + + /* Don't report AP's TSF */ + cmd->tsf_mac_id = cpu_to_le32(0xff); +} + +static void iwl_mvm_ftm_cmd_v8(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_tof_range_req_cmd_v8 *cmd, + struct cfg80211_pmsr_request *req) +{ + iwl_mvm_ftm_cmd_common(mvm, vif, (void *)cmd, req); +} + +static int +iwl_mvm_ftm_target_chandef_v1(struct iwl_mvm *mvm, + struct cfg80211_pmsr_request_peer *peer, + u8 *channel, u8 *bandwidth, + u8 *ctrl_ch_position) +{ + u32 freq = peer->chandef.chan->center_freq; + + *channel = ieee80211_frequency_to_channel(freq); + + switch (peer->chandef.width) { + case NL80211_CHAN_WIDTH_20_NOHT: + *bandwidth = IWL_TOF_BW_20_LEGACY; + break; + case NL80211_CHAN_WIDTH_20: + *bandwidth = IWL_TOF_BW_20_HT; + break; + case NL80211_CHAN_WIDTH_40: + *bandwidth = IWL_TOF_BW_40; + break; + case NL80211_CHAN_WIDTH_80: + *bandwidth = IWL_TOF_BW_80; + break; + default: + IWL_ERR(mvm, "Unsupported BW in FTM request (%d)\n", + peer->chandef.width); + return -EINVAL; + } + + *ctrl_ch_position = (peer->chandef.width > NL80211_CHAN_WIDTH_20) ? + iwl_mvm_get_ctrl_pos(&peer->chandef) : 0; + + return 0; +} + +static int +iwl_mvm_ftm_target_chandef_v2(struct iwl_mvm *mvm, + struct cfg80211_pmsr_request_peer *peer, + u8 *channel, u8 *format_bw, + u8 *ctrl_ch_position) +{ + u32 freq = peer->chandef.chan->center_freq; + u8 cmd_ver; + + *channel = ieee80211_frequency_to_channel(freq); + + switch (peer->chandef.width) { + case NL80211_CHAN_WIDTH_20_NOHT: + *format_bw = IWL_LOCATION_FRAME_FORMAT_LEGACY; + *format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + break; + case NL80211_CHAN_WIDTH_20: + *format_bw = IWL_LOCATION_FRAME_FORMAT_HT; + *format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + break; + case NL80211_CHAN_WIDTH_40: + *format_bw = IWL_LOCATION_FRAME_FORMAT_HT; + *format_bw |= IWL_LOCATION_BW_40MHZ << LOCATION_BW_POS; + break; + case NL80211_CHAN_WIDTH_80: + *format_bw = IWL_LOCATION_FRAME_FORMAT_VHT; + *format_bw |= IWL_LOCATION_BW_80MHZ << LOCATION_BW_POS; + break; + case NL80211_CHAN_WIDTH_160: + cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, + WIDE_ID(LOCATION_GROUP, TOF_RANGE_REQ_CMD), + IWL_FW_CMD_VER_UNKNOWN); + + if (cmd_ver >= 13) { + *format_bw = IWL_LOCATION_FRAME_FORMAT_HE; + *format_bw |= IWL_LOCATION_BW_160MHZ << LOCATION_BW_POS; + break; + } + fallthrough; + default: + IWL_ERR(mvm, "Unsupported BW in FTM request (%d)\n", + peer->chandef.width); + return -EINVAL; + } + + /* non EDCA based measurement must use HE preamble */ + if (peer->ftm.trigger_based || peer->ftm.non_trigger_based) + *format_bw |= IWL_LOCATION_FRAME_FORMAT_HE; + + *ctrl_ch_position = (peer->chandef.width > NL80211_CHAN_WIDTH_20) ? + iwl_mvm_get_ctrl_pos(&peer->chandef) : 0; + + return 0; +} + +static int +iwl_mvm_ftm_put_target_v2(struct iwl_mvm *mvm, + struct cfg80211_pmsr_request_peer *peer, + struct iwl_tof_range_req_ap_entry_v2 *target) +{ + int ret; + + ret = iwl_mvm_ftm_target_chandef_v1(mvm, peer, &target->channel_num, + &target->bandwidth, + &target->ctrl_ch_position); + if (ret) + return ret; + + memcpy(target->bssid, peer->addr, ETH_ALEN); + target->burst_period = + cpu_to_le16(peer->ftm.burst_period); + target->samples_per_burst = peer->ftm.ftms_per_burst; + target->num_of_bursts = peer->ftm.num_bursts_exp; + target->measure_type = 0; /* regular two-sided FTM */ + target->retries_per_sample = peer->ftm.ftmr_retries; + target->asap_mode = peer->ftm.asap; + target->enable_dyn_ack = IWL_MVM_FTM_INITIATOR_DYNACK; + + if (peer->ftm.request_lci) + target->location_req |= IWL_TOF_LOC_LCI; + if (peer->ftm.request_civicloc) + target->location_req |= IWL_TOF_LOC_CIVIC; + + target->algo_type = IWL_MVM_FTM_INITIATOR_ALGO; + + return 0; +} + +#define FTM_PUT_FLAG(flag) (target->initiator_ap_flags |= \ + cpu_to_le32(IWL_INITIATOR_AP_FLAGS_##flag)) + +static void +iwl_mvm_ftm_put_target_common(struct iwl_mvm *mvm, + struct cfg80211_pmsr_request_peer *peer, + struct iwl_tof_range_req_ap_entry_v6 *target) +{ + memcpy(target->bssid, peer->addr, ETH_ALEN); + target->burst_period = + cpu_to_le16(peer->ftm.burst_period); + target->samples_per_burst = peer->ftm.ftms_per_burst; + target->num_of_bursts = peer->ftm.num_bursts_exp; + target->ftmr_max_retries = peer->ftm.ftmr_retries; + target->initiator_ap_flags = cpu_to_le32(0); + + if (peer->ftm.asap) + FTM_PUT_FLAG(ASAP); + + if (peer->ftm.request_lci) + FTM_PUT_FLAG(LCI_REQUEST); + + if (peer->ftm.request_civicloc) + FTM_PUT_FLAG(CIVIC_REQUEST); + + if (IWL_MVM_FTM_INITIATOR_DYNACK) + FTM_PUT_FLAG(DYN_ACK); + + if (IWL_MVM_FTM_INITIATOR_ALGO == IWL_TOF_ALGO_TYPE_LINEAR_REG) + FTM_PUT_FLAG(ALGO_LR); + else if (IWL_MVM_FTM_INITIATOR_ALGO == IWL_TOF_ALGO_TYPE_FFT) + FTM_PUT_FLAG(ALGO_FFT); + + if (peer->ftm.trigger_based) + FTM_PUT_FLAG(TB); + else if (peer->ftm.non_trigger_based) + FTM_PUT_FLAG(NON_TB); + + if ((peer->ftm.trigger_based || peer->ftm.non_trigger_based) && + peer->ftm.lmr_feedback) + FTM_PUT_FLAG(LMR_FEEDBACK); +} + +static int +iwl_mvm_ftm_put_target_v3(struct iwl_mvm *mvm, + struct cfg80211_pmsr_request_peer *peer, + struct iwl_tof_range_req_ap_entry_v3 *target) +{ + int ret; + + ret = iwl_mvm_ftm_target_chandef_v1(mvm, peer, &target->channel_num, + &target->bandwidth, + &target->ctrl_ch_position); + if (ret) + return ret; + + /* + * Versions 3 and 4 has some common fields, so + * iwl_mvm_ftm_put_target_common() can be used for version 7 too. + */ + iwl_mvm_ftm_put_target_common(mvm, peer, (void *)target); + + return 0; +} + +static int +iwl_mvm_ftm_put_target_v4(struct iwl_mvm *mvm, + struct cfg80211_pmsr_request_peer *peer, + struct iwl_tof_range_req_ap_entry_v4 *target) +{ + int ret; + + ret = iwl_mvm_ftm_target_chandef_v2(mvm, peer, &target->channel_num, + &target->format_bw, + &target->ctrl_ch_position); + if (ret) + return ret; + + iwl_mvm_ftm_put_target_common(mvm, peer, (void *)target); + + return 0; +} + +static int +iwl_mvm_ftm_put_target(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request_peer *peer, + struct iwl_tof_range_req_ap_entry_v6 *target) +{ + int ret; + + ret = iwl_mvm_ftm_target_chandef_v2(mvm, peer, &target->channel_num, + &target->format_bw, + &target->ctrl_ch_position); + if (ret) + return ret; + + iwl_mvm_ftm_put_target_common(mvm, peer, target); + + if (vif->cfg.assoc && + !memcmp(peer->addr, vif->bss_conf.bssid, ETH_ALEN)) { + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_sta *sta; + + rcu_read_lock(); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[mvmvif->ap_sta_id]); + if (sta->mfp && (peer->ftm.trigger_based || peer->ftm.non_trigger_based)) + FTM_PUT_FLAG(PMF); + + rcu_read_unlock(); + + target->sta_id = mvmvif->ap_sta_id; + } else { + target->sta_id = IWL_MVM_INVALID_STA; + } + + /* + * TODO: Beacon interval is currently unknown, so use the common value + * of 100 TUs. + */ + target->beacon_interval = cpu_to_le16(100); + return 0; +} + +static int iwl_mvm_ftm_send_cmd(struct iwl_mvm *mvm, struct iwl_host_cmd *hcmd) +{ + u32 status; + int err = iwl_mvm_send_cmd_status(mvm, hcmd, &status); + + if (!err && status) { + IWL_ERR(mvm, "FTM range request command failure, status: %u\n", + status); + err = iwl_ftm_range_request_status_to_err(status); + } + + return err; +} + +static int iwl_mvm_ftm_start_v5(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_req_cmd_v5 cmd_v5; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(LOCATION_GROUP, TOF_RANGE_REQ_CMD), + .dataflags[0] = IWL_HCMD_DFL_DUP, + .data[0] = &cmd_v5, + .len[0] = sizeof(cmd_v5), + }; + u8 i; + int err; + + iwl_mvm_ftm_cmd_v5(mvm, vif, &cmd_v5, req); + + for (i = 0; i < cmd_v5.num_of_ap; i++) { + struct cfg80211_pmsr_request_peer *peer = &req->peers[i]; + + err = iwl_mvm_ftm_put_target_v2(mvm, peer, &cmd_v5.ap[i]); + if (err) + return err; + } + + return iwl_mvm_ftm_send_cmd(mvm, &hcmd); +} + +static int iwl_mvm_ftm_start_v7(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_req_cmd_v7 cmd_v7; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(LOCATION_GROUP, TOF_RANGE_REQ_CMD), + .dataflags[0] = IWL_HCMD_DFL_DUP, + .data[0] = &cmd_v7, + .len[0] = sizeof(cmd_v7), + }; + u8 i; + int err; + + /* + * Versions 7 and 8 has the same structure except from the responders + * list, so iwl_mvm_ftm_cmd() can be used for version 7 too. + */ + iwl_mvm_ftm_cmd_v8(mvm, vif, (void *)&cmd_v7, req); + + for (i = 0; i < cmd_v7.num_of_ap; i++) { + struct cfg80211_pmsr_request_peer *peer = &req->peers[i]; + + err = iwl_mvm_ftm_put_target_v3(mvm, peer, &cmd_v7.ap[i]); + if (err) + return err; + } + + return iwl_mvm_ftm_send_cmd(mvm, &hcmd); +} + +static int iwl_mvm_ftm_start_v8(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_req_cmd_v8 cmd; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(LOCATION_GROUP, TOF_RANGE_REQ_CMD), + .dataflags[0] = IWL_HCMD_DFL_DUP, + .data[0] = &cmd, + .len[0] = sizeof(cmd), + }; + u8 i; + int err; + + iwl_mvm_ftm_cmd_v8(mvm, vif, (void *)&cmd, req); + + for (i = 0; i < cmd.num_of_ap; i++) { + struct cfg80211_pmsr_request_peer *peer = &req->peers[i]; + + err = iwl_mvm_ftm_put_target_v4(mvm, peer, &cmd.ap[i]); + if (err) + return err; + } + + return iwl_mvm_ftm_send_cmd(mvm, &hcmd); +} + +static int iwl_mvm_ftm_start_v9(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_req_cmd_v9 cmd; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(LOCATION_GROUP, TOF_RANGE_REQ_CMD), + .dataflags[0] = IWL_HCMD_DFL_DUP, + .data[0] = &cmd, + .len[0] = sizeof(cmd), + }; + u8 i; + int err; + + iwl_mvm_ftm_cmd_common(mvm, vif, &cmd, req); + + for (i = 0; i < cmd.num_of_ap; i++) { + struct cfg80211_pmsr_request_peer *peer = &req->peers[i]; + struct iwl_tof_range_req_ap_entry_v6 *target = &cmd.ap[i]; + + err = iwl_mvm_ftm_put_target(mvm, vif, peer, target); + if (err) + return err; + } + + return iwl_mvm_ftm_send_cmd(mvm, &hcmd); +} + +static void iter(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, + void *data) +{ + struct iwl_tof_range_req_ap_entry_v6 *target = data; + + if (!sta || memcmp(sta->addr, target->bssid, ETH_ALEN)) + return; + + WARN_ON(!sta->mfp); + + if (WARN_ON(key->keylen > sizeof(target->tk))) + return; + + memcpy(target->tk, key->key, key->keylen); + target->cipher = iwl_mvm_cipher_to_location_cipher(key->cipher); + WARN_ON(target->cipher == IWL_LOCATION_CIPHER_INVALID); +} + +static void +iwl_mvm_ftm_set_secured_ranging(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_tof_range_req_ap_entry_v7 *target) +{ + struct iwl_mvm_ftm_pasn_entry *entry; + u32 flags = le32_to_cpu(target->initiator_ap_flags); + + if (!(flags & (IWL_INITIATOR_AP_FLAGS_NON_TB | + IWL_INITIATOR_AP_FLAGS_TB))) + return; + + lockdep_assert_held(&mvm->mutex); + + list_for_each_entry(entry, &mvm->ftm_initiator.pasn_list, list) { + if (memcmp(entry->addr, target->bssid, sizeof(entry->addr))) + continue; + + target->cipher = entry->cipher; + memcpy(target->hltk, entry->hltk, sizeof(target->hltk)); + + if (vif->cfg.assoc && + !memcmp(vif->bss_conf.bssid, target->bssid, + sizeof(target->bssid))) + ieee80211_iter_keys(mvm->hw, vif, iter, target); + else + memcpy(target->tk, entry->tk, sizeof(target->tk)); + + memcpy(target->rx_pn, entry->rx_pn, sizeof(target->rx_pn)); + memcpy(target->tx_pn, entry->tx_pn, sizeof(target->tx_pn)); + + target->initiator_ap_flags |= + cpu_to_le32(IWL_INITIATOR_AP_FLAGS_SECURED); + return; + } +} + +static int +iwl_mvm_ftm_put_target_v7(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request_peer *peer, + struct iwl_tof_range_req_ap_entry_v7 *target) +{ + int err = iwl_mvm_ftm_put_target(mvm, vif, peer, (void *)target); + if (err) + return err; + + iwl_mvm_ftm_set_secured_ranging(mvm, vif, target); + return err; +} + +static int iwl_mvm_ftm_start_v11(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_req_cmd_v11 cmd; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(LOCATION_GROUP, TOF_RANGE_REQ_CMD), + .dataflags[0] = IWL_HCMD_DFL_DUP, + .data[0] = &cmd, + .len[0] = sizeof(cmd), + }; + u8 i; + int err; + + iwl_mvm_ftm_cmd_common(mvm, vif, (void *)&cmd, req); + + for (i = 0; i < cmd.num_of_ap; i++) { + struct cfg80211_pmsr_request_peer *peer = &req->peers[i]; + struct iwl_tof_range_req_ap_entry_v7 *target = &cmd.ap[i]; + + err = iwl_mvm_ftm_put_target_v7(mvm, vif, peer, target); + if (err) + return err; + } + + return iwl_mvm_ftm_send_cmd(mvm, &hcmd); +} + +static void +iwl_mvm_ftm_set_ndp_params(struct iwl_mvm *mvm, + struct iwl_tof_range_req_ap_entry_v8 *target) +{ + /* Only 2 STS are supported on Tx */ + u32 i2r_max_sts = IWL_MVM_FTM_I2R_MAX_STS > 1 ? 1 : + IWL_MVM_FTM_I2R_MAX_STS; + + target->r2i_ndp_params = IWL_MVM_FTM_R2I_MAX_REP | + (IWL_MVM_FTM_R2I_MAX_STS << IWL_LOCATION_MAX_STS_POS); + target->i2r_ndp_params = IWL_MVM_FTM_I2R_MAX_REP | + (i2r_max_sts << IWL_LOCATION_MAX_STS_POS); + target->r2i_max_total_ltf = IWL_MVM_FTM_R2I_MAX_TOTAL_LTF; + target->i2r_max_total_ltf = IWL_MVM_FTM_I2R_MAX_TOTAL_LTF; +} + +static int +iwl_mvm_ftm_put_target_v8(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request_peer *peer, + struct iwl_tof_range_req_ap_entry_v8 *target) +{ + u32 flags; + int ret = iwl_mvm_ftm_put_target_v7(mvm, vif, peer, (void *)target); + + if (ret) + return ret; + + iwl_mvm_ftm_set_ndp_params(mvm, target); + + /* + * If secure LTF is turned off, replace the flag with PMF only + */ + flags = le32_to_cpu(target->initiator_ap_flags); + if ((flags & IWL_INITIATOR_AP_FLAGS_SECURED) && + !IWL_MVM_FTM_INITIATOR_SECURE_LTF) { + flags &= ~IWL_INITIATOR_AP_FLAGS_SECURED; + flags |= IWL_INITIATOR_AP_FLAGS_PMF; + target->initiator_ap_flags = cpu_to_le32(flags); + } + + return 0; +} + +static int iwl_mvm_ftm_start_v12(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_req_cmd_v12 cmd; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(LOCATION_GROUP, TOF_RANGE_REQ_CMD), + .dataflags[0] = IWL_HCMD_DFL_DUP, + .data[0] = &cmd, + .len[0] = sizeof(cmd), + }; + u8 i; + int err; + + iwl_mvm_ftm_cmd_common(mvm, vif, (void *)&cmd, req); + + for (i = 0; i < cmd.num_of_ap; i++) { + struct cfg80211_pmsr_request_peer *peer = &req->peers[i]; + struct iwl_tof_range_req_ap_entry_v8 *target = &cmd.ap[i]; + + err = iwl_mvm_ftm_put_target_v8(mvm, vif, peer, target); + if (err) + return err; + } + + return iwl_mvm_ftm_send_cmd(mvm, &hcmd); +} + +static int iwl_mvm_ftm_start_v13(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_req_cmd_v13 cmd; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(LOCATION_GROUP, TOF_RANGE_REQ_CMD), + .dataflags[0] = IWL_HCMD_DFL_DUP, + .data[0] = &cmd, + .len[0] = sizeof(cmd), + }; + u8 i; + int err; + + iwl_mvm_ftm_cmd_common(mvm, vif, (void *)&cmd, req); + + for (i = 0; i < cmd.num_of_ap; i++) { + struct cfg80211_pmsr_request_peer *peer = &req->peers[i]; + struct iwl_tof_range_req_ap_entry_v9 *target = &cmd.ap[i]; + + err = iwl_mvm_ftm_put_target_v8(mvm, vif, peer, (void *)target); + if (err) + return err; + + if (peer->ftm.trigger_based || peer->ftm.non_trigger_based) + target->bss_color = peer->ftm.bss_color; + + if (peer->ftm.non_trigger_based) { + target->min_time_between_msr = + cpu_to_le16(IWL_MVM_FTM_NON_TB_MIN_TIME_BETWEEN_MSR); + target->burst_period = + cpu_to_le16(IWL_MVM_FTM_NON_TB_MAX_TIME_BETWEEN_MSR); + } else { + target->min_time_between_msr = cpu_to_le16(0); + } + + target->band = + iwl_mvm_phy_band_from_nl80211(peer->chandef.chan->band); + } + + return iwl_mvm_ftm_send_cmd(mvm, &hcmd); +} + +int iwl_mvm_ftm_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *req) +{ + bool new_api = fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_FTM_NEW_RANGE_REQ); + int err; + + lockdep_assert_held(&mvm->mutex); + + if (mvm->ftm_initiator.req) + return -EBUSY; + + if (new_api) { + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, + WIDE_ID(LOCATION_GROUP, TOF_RANGE_REQ_CMD), + IWL_FW_CMD_VER_UNKNOWN); + + switch (cmd_ver) { + case 13: + err = iwl_mvm_ftm_start_v13(mvm, vif, req); + break; + case 12: + err = iwl_mvm_ftm_start_v12(mvm, vif, req); + break; + case 11: + err = iwl_mvm_ftm_start_v11(mvm, vif, req); + break; + case 9: + case 10: + err = iwl_mvm_ftm_start_v9(mvm, vif, req); + break; + case 8: + err = iwl_mvm_ftm_start_v8(mvm, vif, req); + break; + default: + err = iwl_mvm_ftm_start_v7(mvm, vif, req); + break; + } + } else { + err = iwl_mvm_ftm_start_v5(mvm, vif, req); + } + + if (!err) { + mvm->ftm_initiator.req = req; + mvm->ftm_initiator.req_wdev = ieee80211_vif_to_wdev(vif); + } + + return err; +} + +void iwl_mvm_ftm_abort(struct iwl_mvm *mvm, struct cfg80211_pmsr_request *req) +{ + struct iwl_tof_range_abort_cmd cmd = { + .request_id = req->cookie, + }; + + lockdep_assert_held(&mvm->mutex); + + if (req != mvm->ftm_initiator.req) + return; + + iwl_mvm_ftm_reset(mvm); + + if (iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(LOCATION_GROUP, TOF_RANGE_ABORT_CMD), + 0, sizeof(cmd), &cmd)) + IWL_ERR(mvm, "failed to abort FTM process\n"); +} + +static int iwl_mvm_ftm_find_peer(struct cfg80211_pmsr_request *req, + const u8 *addr) +{ + int i; + + for (i = 0; i < req->n_peers; i++) { + struct cfg80211_pmsr_request_peer *peer = &req->peers[i]; + + if (ether_addr_equal_unaligned(peer->addr, addr)) + return i; + } + + return -ENOENT; +} + +static u64 iwl_mvm_ftm_get_host_time(struct iwl_mvm *mvm, __le32 fw_gp2_ts) +{ + u32 gp2_ts = le32_to_cpu(fw_gp2_ts); + u32 curr_gp2, diff; + u64 now_from_boot_ns; + + iwl_mvm_get_sync_time(mvm, CLOCK_BOOTTIME, &curr_gp2, + &now_from_boot_ns, NULL); + + if (curr_gp2 >= gp2_ts) + diff = curr_gp2 - gp2_ts; + else + diff = curr_gp2 + (U32_MAX - gp2_ts + 1); + + return now_from_boot_ns - (u64)diff * 1000; +} + +static void iwl_mvm_ftm_get_lci_civic(struct iwl_mvm *mvm, + struct cfg80211_pmsr_result *res) +{ + struct iwl_mvm_loc_entry *entry; + + list_for_each_entry(entry, &mvm->ftm_initiator.loc_list, list) { + if (!ether_addr_equal_unaligned(res->addr, entry->addr)) + continue; + + if (entry->lci_len) { + res->ftm.lci_len = entry->lci_len; + res->ftm.lci = entry->buf; + } + + if (entry->civic_len) { + res->ftm.civicloc_len = entry->civic_len; + res->ftm.civicloc = entry->buf + entry->lci_len; + } + + /* we found the entry we needed */ + break; + } +} + +static int iwl_mvm_ftm_range_resp_valid(struct iwl_mvm *mvm, u8 request_id, + u8 num_of_aps) +{ + lockdep_assert_held(&mvm->mutex); + + if (request_id != (u8)mvm->ftm_initiator.req->cookie) { + IWL_ERR(mvm, "Request ID mismatch, got %u, active %u\n", + request_id, (u8)mvm->ftm_initiator.req->cookie); + return -EINVAL; + } + + if (num_of_aps > mvm->ftm_initiator.req->n_peers) { + IWL_ERR(mvm, "FTM range response invalid\n"); + return -EINVAL; + } + + return 0; +} + +static void iwl_mvm_ftm_rtt_smoothing(struct iwl_mvm *mvm, + struct cfg80211_pmsr_result *res) +{ + struct iwl_mvm_smooth_entry *resp; + s64 rtt_avg, rtt = res->ftm.rtt_avg; + u32 undershoot, overshoot; + u8 alpha; + bool found; + + if (!IWL_MVM_FTM_INITIATOR_ENABLE_SMOOTH) + return; + + WARN_ON(rtt < 0); + + if (res->status != NL80211_PMSR_STATUS_SUCCESS) { + IWL_DEBUG_INFO(mvm, + ": %pM: ignore failed measurement. Status=%u\n", + res->addr, res->status); + return; + } + + found = false; + list_for_each_entry(resp, &mvm->ftm_initiator.smooth.resp, list) { + if (!memcmp(res->addr, resp->addr, ETH_ALEN)) { + found = true; + break; + } + } + + if (!found) { + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) + return; + + memcpy(resp->addr, res->addr, ETH_ALEN); + list_add_tail(&resp->list, &mvm->ftm_initiator.smooth.resp); + + resp->rtt_avg = rtt; + + IWL_DEBUG_INFO(mvm, "new: %pM: rtt_avg=%lld\n", + resp->addr, resp->rtt_avg); + goto update_time; + } + + if (res->host_time - resp->host_time > + IWL_MVM_FTM_INITIATOR_SMOOTH_AGE_SEC * 1000000000) { + resp->rtt_avg = rtt; + + IWL_DEBUG_INFO(mvm, "expired: %pM: rtt_avg=%lld\n", + resp->addr, resp->rtt_avg); + goto update_time; + } + + /* Smooth the results based on the tracked RTT average */ + undershoot = IWL_MVM_FTM_INITIATOR_SMOOTH_UNDERSHOOT; + overshoot = IWL_MVM_FTM_INITIATOR_SMOOTH_OVERSHOOT; + alpha = IWL_MVM_FTM_INITIATOR_SMOOTH_ALPHA; + + rtt_avg = div_s64(alpha * rtt + (100 - alpha) * resp->rtt_avg, 100); + + IWL_DEBUG_INFO(mvm, + "%pM: prev rtt_avg=%lld, new rtt_avg=%lld, rtt=%lld\n", + resp->addr, resp->rtt_avg, rtt_avg, rtt); + + /* + * update the responder's average RTT results regardless of + * the under/over shoot logic below + */ + resp->rtt_avg = rtt_avg; + + /* smooth the results */ + if (rtt_avg > rtt && (rtt_avg - rtt) > undershoot) { + res->ftm.rtt_avg = rtt_avg; + + IWL_DEBUG_INFO(mvm, + "undershoot: val=%lld\n", + (rtt_avg - rtt)); + } else if (rtt_avg < rtt && (rtt - rtt_avg) > + overshoot) { + res->ftm.rtt_avg = rtt_avg; + IWL_DEBUG_INFO(mvm, + "overshoot: val=%lld\n", + (rtt - rtt_avg)); + } + +update_time: + resp->host_time = res->host_time; +} + +static void iwl_mvm_debug_range_resp(struct iwl_mvm *mvm, u8 index, + struct cfg80211_pmsr_result *res) +{ + s64 rtt_avg = div_s64(res->ftm.rtt_avg * 100, 6666); + + IWL_DEBUG_INFO(mvm, "entry %d\n", index); + IWL_DEBUG_INFO(mvm, "\tstatus: %d\n", res->status); + IWL_DEBUG_INFO(mvm, "\tBSSID: %pM\n", res->addr); + IWL_DEBUG_INFO(mvm, "\thost time: %llu\n", res->host_time); + IWL_DEBUG_INFO(mvm, "\tburst index: %d\n", res->ftm.burst_index); + IWL_DEBUG_INFO(mvm, "\tsuccess num: %u\n", res->ftm.num_ftmr_successes); + IWL_DEBUG_INFO(mvm, "\trssi: %d\n", res->ftm.rssi_avg); + IWL_DEBUG_INFO(mvm, "\trssi spread: %d\n", res->ftm.rssi_spread); + IWL_DEBUG_INFO(mvm, "\trtt: %lld\n", res->ftm.rtt_avg); + IWL_DEBUG_INFO(mvm, "\trtt var: %llu\n", res->ftm.rtt_variance); + IWL_DEBUG_INFO(mvm, "\trtt spread: %llu\n", res->ftm.rtt_spread); + IWL_DEBUG_INFO(mvm, "\tdistance: %lld\n", rtt_avg); +} + +static void +iwl_mvm_ftm_pasn_update_pn(struct iwl_mvm *mvm, + struct iwl_tof_range_rsp_ap_entry_ntfy_v6 *fw_ap) +{ + struct iwl_mvm_ftm_pasn_entry *entry; + + lockdep_assert_held(&mvm->mutex); + + list_for_each_entry(entry, &mvm->ftm_initiator.pasn_list, list) { + if (memcmp(fw_ap->bssid, entry->addr, sizeof(entry->addr))) + continue; + + memcpy(entry->rx_pn, fw_ap->rx_pn, sizeof(entry->rx_pn)); + memcpy(entry->tx_pn, fw_ap->tx_pn, sizeof(entry->tx_pn)); + return; + } +} + +static u8 iwl_mvm_ftm_get_range_resp_ver(struct iwl_mvm *mvm) +{ + if (!fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_FTM_NEW_RANGE_REQ)) + return 5; + + /* Starting from version 8, the FW advertises the version */ + if (mvm->cmd_ver.range_resp >= 8) + return mvm->cmd_ver.range_resp; + else if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_FTM_RTT_ACCURACY)) + return 7; + + /* The first version of the new range request API */ + return 6; +} + +static bool iwl_mvm_ftm_resp_size_validation(u8 ver, unsigned int pkt_len) +{ + switch (ver) { + case 9: + case 8: + return pkt_len == sizeof(struct iwl_tof_range_rsp_ntfy_v8); + case 7: + return pkt_len == sizeof(struct iwl_tof_range_rsp_ntfy_v7); + case 6: + return pkt_len == sizeof(struct iwl_tof_range_rsp_ntfy_v6); + case 5: + return pkt_len == sizeof(struct iwl_tof_range_rsp_ntfy_v5); + default: + WARN_ONCE(1, "FTM: unsupported range response version %u", ver); + return false; + } +} + +void iwl_mvm_ftm_range_resp(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); + struct iwl_tof_range_rsp_ntfy_v5 *fw_resp_v5 = (void *)pkt->data; + struct iwl_tof_range_rsp_ntfy_v6 *fw_resp_v6 = (void *)pkt->data; + struct iwl_tof_range_rsp_ntfy_v7 *fw_resp_v7 = (void *)pkt->data; + struct iwl_tof_range_rsp_ntfy_v8 *fw_resp_v8 = (void *)pkt->data; + int i; + bool new_api = fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_FTM_NEW_RANGE_REQ); + u8 num_of_aps, last_in_batch; + u8 notif_ver = iwl_mvm_ftm_get_range_resp_ver(mvm); + + lockdep_assert_held(&mvm->mutex); + + if (!mvm->ftm_initiator.req) { + return; + } + + if (unlikely(!iwl_mvm_ftm_resp_size_validation(notif_ver, pkt_len))) + return; + + if (new_api) { + if (iwl_mvm_ftm_range_resp_valid(mvm, fw_resp_v8->request_id, + fw_resp_v8->num_of_aps)) + return; + + num_of_aps = fw_resp_v8->num_of_aps; + last_in_batch = fw_resp_v8->last_report; + } else { + if (iwl_mvm_ftm_range_resp_valid(mvm, fw_resp_v5->request_id, + fw_resp_v5->num_of_aps)) + return; + + num_of_aps = fw_resp_v5->num_of_aps; + last_in_batch = fw_resp_v5->last_in_batch; + } + + IWL_DEBUG_INFO(mvm, "Range response received\n"); + IWL_DEBUG_INFO(mvm, "request id: %lld, num of entries: %hhu\n", + mvm->ftm_initiator.req->cookie, num_of_aps); + + for (i = 0; i < num_of_aps && i < IWL_MVM_TOF_MAX_APS; i++) { + struct cfg80211_pmsr_result result = {}; + struct iwl_tof_range_rsp_ap_entry_ntfy_v6 *fw_ap; + int peer_idx; + + if (new_api) { + if (notif_ver >= 8) { + fw_ap = &fw_resp_v8->ap[i]; + iwl_mvm_ftm_pasn_update_pn(mvm, fw_ap); + } else if (notif_ver == 7) { + fw_ap = (void *)&fw_resp_v7->ap[i]; + } else { + fw_ap = (void *)&fw_resp_v6->ap[i]; + } + + result.final = fw_ap->last_burst; + result.ap_tsf = le32_to_cpu(fw_ap->start_tsf); + result.ap_tsf_valid = 1; + } else { + /* the first part is the same for old and new APIs */ + fw_ap = (void *)&fw_resp_v5->ap[i]; + /* + * FIXME: the firmware needs to report this, we don't + * even know the number of bursts the responder picked + * (if we asked it to) + */ + result.final = 0; + } + + peer_idx = iwl_mvm_ftm_find_peer(mvm->ftm_initiator.req, + fw_ap->bssid); + if (peer_idx < 0) { + IWL_WARN(mvm, + "Unknown address (%pM, target #%d) in FTM response\n", + fw_ap->bssid, i); + continue; + } + + switch (fw_ap->measure_status) { + case IWL_TOF_ENTRY_SUCCESS: + result.status = NL80211_PMSR_STATUS_SUCCESS; + break; + case IWL_TOF_ENTRY_TIMING_MEASURE_TIMEOUT: + result.status = NL80211_PMSR_STATUS_TIMEOUT; + break; + case IWL_TOF_ENTRY_NO_RESPONSE: + result.status = NL80211_PMSR_STATUS_FAILURE; + result.ftm.failure_reason = + NL80211_PMSR_FTM_FAILURE_NO_RESPONSE; + break; + case IWL_TOF_ENTRY_REQUEST_REJECTED: + result.status = NL80211_PMSR_STATUS_FAILURE; + result.ftm.failure_reason = + NL80211_PMSR_FTM_FAILURE_PEER_BUSY; + result.ftm.busy_retry_time = fw_ap->refusal_period; + break; + default: + result.status = NL80211_PMSR_STATUS_FAILURE; + result.ftm.failure_reason = + NL80211_PMSR_FTM_FAILURE_UNSPECIFIED; + break; + } + memcpy(result.addr, fw_ap->bssid, ETH_ALEN); + result.host_time = iwl_mvm_ftm_get_host_time(mvm, + fw_ap->timestamp); + result.type = NL80211_PMSR_TYPE_FTM; + result.ftm.burst_index = mvm->ftm_initiator.responses[peer_idx]; + mvm->ftm_initiator.responses[peer_idx]++; + result.ftm.rssi_avg = fw_ap->rssi; + result.ftm.rssi_avg_valid = 1; + result.ftm.rssi_spread = fw_ap->rssi_spread; + result.ftm.rssi_spread_valid = 1; + result.ftm.rtt_avg = (s32)le32_to_cpu(fw_ap->rtt); + result.ftm.rtt_avg_valid = 1; + result.ftm.rtt_variance = le32_to_cpu(fw_ap->rtt_variance); + result.ftm.rtt_variance_valid = 1; + result.ftm.rtt_spread = le32_to_cpu(fw_ap->rtt_spread); + result.ftm.rtt_spread_valid = 1; + + iwl_mvm_ftm_get_lci_civic(mvm, &result); + + iwl_mvm_ftm_rtt_smoothing(mvm, &result); + + cfg80211_pmsr_report(mvm->ftm_initiator.req_wdev, + mvm->ftm_initiator.req, + &result, GFP_KERNEL); + + if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_FTM_RTT_ACCURACY)) + IWL_DEBUG_INFO(mvm, "RTT confidence: %hhu\n", + fw_ap->rttConfidence); + + iwl_mvm_debug_range_resp(mvm, i, &result); + } + + if (last_in_batch) { + cfg80211_pmsr_complete(mvm->ftm_initiator.req_wdev, + mvm->ftm_initiator.req, + GFP_KERNEL); + iwl_mvm_ftm_reset(mvm); + } +} + +void iwl_mvm_ftm_lc_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + const struct ieee80211_mgmt *mgmt = (void *)pkt->data; + size_t len = iwl_rx_packet_payload_len(pkt); + struct iwl_mvm_loc_entry *entry; + const u8 *ies, *lci, *civic, *msr_ie; + size_t ies_len, lci_len = 0, civic_len = 0; + size_t baselen = IEEE80211_MIN_ACTION_SIZE + + sizeof(mgmt->u.action.u.ftm); + static const u8 rprt_type_lci = IEEE80211_SPCT_MSR_RPRT_TYPE_LCI; + static const u8 rprt_type_civic = IEEE80211_SPCT_MSR_RPRT_TYPE_CIVIC; + + if (len <= baselen) + return; + + lockdep_assert_held(&mvm->mutex); + + ies = mgmt->u.action.u.ftm.variable; + ies_len = len - baselen; + + msr_ie = cfg80211_find_ie_match(WLAN_EID_MEASURE_REPORT, ies, ies_len, + &rprt_type_lci, 1, 4); + if (msr_ie) { + lci = msr_ie + 2; + lci_len = msr_ie[1]; + } + + msr_ie = cfg80211_find_ie_match(WLAN_EID_MEASURE_REPORT, ies, ies_len, + &rprt_type_civic, 1, 4); + if (msr_ie) { + civic = msr_ie + 2; + civic_len = msr_ie[1]; + } + + entry = kmalloc(sizeof(*entry) + lci_len + civic_len, GFP_KERNEL); + if (!entry) + return; + + memcpy(entry->addr, mgmt->bssid, ETH_ALEN); + + entry->lci_len = lci_len; + if (lci_len) + memcpy(entry->buf, lci, lci_len); + + entry->civic_len = civic_len; + if (civic_len) + memcpy(entry->buf + lci_len, civic, civic_len); + + list_add_tail(&entry->list, &mvm->ftm_initiator.loc_list); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ftm-responder.c b/drivers/net/wireless/intel/iwlwifi/mvm/ftm-responder.c new file mode 100644 index 000000000..e862d1b43 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ftm-responder.c @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2015-2017 Intel Deutschland GmbH + * Copyright (C) 2018-2022 Intel Corporation + */ +#include <net/cfg80211.h> +#include <linux/etherdevice.h> +#include "mvm.h" +#include "constants.h" + +struct iwl_mvm_pasn_sta { + struct list_head list; + struct iwl_mvm_int_sta int_sta; + u8 addr[ETH_ALEN]; +}; + +struct iwl_mvm_pasn_hltk_data { + u8 *addr; + u8 cipher; + u8 *hltk; +}; + +static int iwl_mvm_ftm_responder_set_bw_v1(struct cfg80211_chan_def *chandef, + u8 *bw, u8 *ctrl_ch_position) +{ + switch (chandef->width) { + case NL80211_CHAN_WIDTH_20_NOHT: + *bw = IWL_TOF_BW_20_LEGACY; + break; + case NL80211_CHAN_WIDTH_20: + *bw = IWL_TOF_BW_20_HT; + break; + case NL80211_CHAN_WIDTH_40: + *bw = IWL_TOF_BW_40; + *ctrl_ch_position = iwl_mvm_get_ctrl_pos(chandef); + break; + case NL80211_CHAN_WIDTH_80: + *bw = IWL_TOF_BW_80; + *ctrl_ch_position = iwl_mvm_get_ctrl_pos(chandef); + break; + default: + return -ENOTSUPP; + } + + return 0; +} + +static int iwl_mvm_ftm_responder_set_bw_v2(struct cfg80211_chan_def *chandef, + u8 *format_bw, u8 *ctrl_ch_position, + u8 cmd_ver) +{ + switch (chandef->width) { + case NL80211_CHAN_WIDTH_20_NOHT: + *format_bw = IWL_LOCATION_FRAME_FORMAT_LEGACY; + *format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + break; + case NL80211_CHAN_WIDTH_20: + *format_bw = IWL_LOCATION_FRAME_FORMAT_HT; + *format_bw |= IWL_LOCATION_BW_20MHZ << LOCATION_BW_POS; + break; + case NL80211_CHAN_WIDTH_40: + *format_bw = IWL_LOCATION_FRAME_FORMAT_HT; + *format_bw |= IWL_LOCATION_BW_40MHZ << LOCATION_BW_POS; + *ctrl_ch_position = iwl_mvm_get_ctrl_pos(chandef); + break; + case NL80211_CHAN_WIDTH_80: + *format_bw = IWL_LOCATION_FRAME_FORMAT_VHT; + *format_bw |= IWL_LOCATION_BW_80MHZ << LOCATION_BW_POS; + *ctrl_ch_position = iwl_mvm_get_ctrl_pos(chandef); + break; + case NL80211_CHAN_WIDTH_160: + if (cmd_ver >= 9) { + *format_bw = IWL_LOCATION_FRAME_FORMAT_HE; + *format_bw |= IWL_LOCATION_BW_160MHZ << LOCATION_BW_POS; + *ctrl_ch_position = iwl_mvm_get_ctrl_pos(chandef); + break; + } + fallthrough; + default: + return -ENOTSUPP; + } + + return 0; +} + +static void +iwl_mvm_ftm_responder_set_ndp(struct iwl_mvm *mvm, + struct iwl_tof_responder_config_cmd_v9 *cmd) +{ + /* Up to 2 R2I STS are allowed on the responder */ + u32 r2i_max_sts = IWL_MVM_FTM_R2I_MAX_STS < 2 ? + IWL_MVM_FTM_R2I_MAX_STS : 1; + + cmd->r2i_ndp_params = IWL_MVM_FTM_R2I_MAX_REP | + (r2i_max_sts << IWL_RESPONDER_STS_POS) | + (IWL_MVM_FTM_R2I_MAX_TOTAL_LTF << IWL_RESPONDER_TOTAL_LTF_POS); + cmd->i2r_ndp_params = IWL_MVM_FTM_I2R_MAX_REP | + (IWL_MVM_FTM_I2R_MAX_STS << IWL_RESPONDER_STS_POS) | + (IWL_MVM_FTM_I2R_MAX_TOTAL_LTF << IWL_RESPONDER_TOTAL_LTF_POS); + cmd->cmd_valid_fields |= + cpu_to_le32(IWL_TOF_RESPONDER_CMD_VALID_NDP_PARAMS); +} + +static int +iwl_mvm_ftm_responder_cmd(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct cfg80211_chan_def *chandef) +{ + u32 cmd_id = WIDE_ID(LOCATION_GROUP, TOF_RESPONDER_CONFIG_CMD); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + /* + * The command structure is the same for versions 6, 7 and 8 (only the + * field interpretation is different), so the same struct can be use + * for all cases. + */ + struct iwl_tof_responder_config_cmd_v9 cmd = { + .channel_num = chandef->chan->hw_value, + .cmd_valid_fields = + cpu_to_le32(IWL_TOF_RESPONDER_CMD_VALID_CHAN_INFO | + IWL_TOF_RESPONDER_CMD_VALID_BSSID | + IWL_TOF_RESPONDER_CMD_VALID_STA_ID), + .sta_id = mvmvif->bcast_sta.sta_id, + }; + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, 6); + int err; + int cmd_size; + + lockdep_assert_held(&mvm->mutex); + + /* Use a default of bss_color=1 for now */ + if (cmd_ver == 9) { + cmd.cmd_valid_fields |= + cpu_to_le32(IWL_TOF_RESPONDER_CMD_VALID_BSS_COLOR | + IWL_TOF_RESPONDER_CMD_VALID_MIN_MAX_TIME_BETWEEN_MSR); + cmd.bss_color = 1; + cmd.min_time_between_msr = + cpu_to_le16(IWL_MVM_FTM_NON_TB_MIN_TIME_BETWEEN_MSR); + cmd.max_time_between_msr = + cpu_to_le16(IWL_MVM_FTM_NON_TB_MAX_TIME_BETWEEN_MSR); + cmd_size = sizeof(struct iwl_tof_responder_config_cmd_v9); + } else { + /* All versions up to version 8 have the same size */ + cmd_size = sizeof(struct iwl_tof_responder_config_cmd_v8); + } + + if (cmd_ver >= 8) + iwl_mvm_ftm_responder_set_ndp(mvm, &cmd); + + if (cmd_ver >= 7) + err = iwl_mvm_ftm_responder_set_bw_v2(chandef, &cmd.format_bw, + &cmd.ctrl_ch_position, + cmd_ver); + else + err = iwl_mvm_ftm_responder_set_bw_v1(chandef, &cmd.format_bw, + &cmd.ctrl_ch_position); + + if (err) { + IWL_ERR(mvm, "Failed to set responder bandwidth\n"); + return err; + } + + memcpy(cmd.bssid, vif->addr, ETH_ALEN); + + return iwl_mvm_send_cmd_pdu(mvm, cmd_id, 0, cmd_size, &cmd); +} + +static int +iwl_mvm_ftm_responder_dyn_cfg_v2(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_ftm_responder_params *params) +{ + struct iwl_tof_responder_dyn_config_cmd_v2 cmd = { + .lci_len = cpu_to_le32(params->lci_len + 2), + .civic_len = cpu_to_le32(params->civicloc_len + 2), + }; + u8 data[IWL_LCI_CIVIC_IE_MAX_SIZE] = {0}; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(LOCATION_GROUP, TOF_RESPONDER_DYN_CONFIG_CMD), + .data[0] = &cmd, + .len[0] = sizeof(cmd), + .data[1] = &data, + /* .len[1] set later */ + /* may not be able to DMA from stack */ + .dataflags[1] = IWL_HCMD_DFL_DUP, + }; + u32 aligned_lci_len = ALIGN(params->lci_len + 2, 4); + u32 aligned_civicloc_len = ALIGN(params->civicloc_len + 2, 4); + u8 *pos = data; + + lockdep_assert_held(&mvm->mutex); + + if (aligned_lci_len + aligned_civicloc_len > sizeof(data)) { + IWL_ERR(mvm, "LCI/civicloc data too big (%zd + %zd)\n", + params->lci_len, params->civicloc_len); + return -ENOBUFS; + } + + pos[0] = WLAN_EID_MEASURE_REPORT; + pos[1] = params->lci_len; + memcpy(pos + 2, params->lci, params->lci_len); + + pos += aligned_lci_len; + pos[0] = WLAN_EID_MEASURE_REPORT; + pos[1] = params->civicloc_len; + memcpy(pos + 2, params->civicloc, params->civicloc_len); + + hcmd.len[1] = aligned_lci_len + aligned_civicloc_len; + + return iwl_mvm_send_cmd(mvm, &hcmd); +} + +static int +iwl_mvm_ftm_responder_dyn_cfg_v3(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_ftm_responder_params *params, + struct iwl_mvm_pasn_hltk_data *hltk_data) +{ + struct iwl_tof_responder_dyn_config_cmd cmd; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(LOCATION_GROUP, TOF_RESPONDER_DYN_CONFIG_CMD), + .data[0] = &cmd, + .len[0] = sizeof(cmd), + /* may not be able to DMA from stack */ + .dataflags[0] = IWL_HCMD_DFL_DUP, + }; + + lockdep_assert_held(&mvm->mutex); + + cmd.valid_flags = 0; + + if (params) { + if (params->lci_len + 2 > sizeof(cmd.lci_buf) || + params->civicloc_len + 2 > sizeof(cmd.civic_buf)) { + IWL_ERR(mvm, + "LCI/civic data too big (lci=%zd, civic=%zd)\n", + params->lci_len, params->civicloc_len); + return -ENOBUFS; + } + + cmd.lci_buf[0] = WLAN_EID_MEASURE_REPORT; + cmd.lci_buf[1] = params->lci_len; + memcpy(cmd.lci_buf + 2, params->lci, params->lci_len); + cmd.lci_len = params->lci_len + 2; + + cmd.civic_buf[0] = WLAN_EID_MEASURE_REPORT; + cmd.civic_buf[1] = params->civicloc_len; + memcpy(cmd.civic_buf + 2, params->civicloc, + params->civicloc_len); + cmd.civic_len = params->civicloc_len + 2; + + cmd.valid_flags |= IWL_RESPONDER_DYN_CFG_VALID_LCI | + IWL_RESPONDER_DYN_CFG_VALID_CIVIC; + } + + if (hltk_data) { + if (hltk_data->cipher > IWL_LOCATION_CIPHER_GCMP_256) { + IWL_ERR(mvm, "invalid cipher: %u\n", + hltk_data->cipher); + return -EINVAL; + } + + cmd.cipher = hltk_data->cipher; + memcpy(cmd.addr, hltk_data->addr, sizeof(cmd.addr)); + memcpy(cmd.hltk_buf, hltk_data->hltk, sizeof(cmd.hltk_buf)); + cmd.valid_flags |= IWL_RESPONDER_DYN_CFG_VALID_PASN_STA; + } + + return iwl_mvm_send_cmd(mvm, &hcmd); +} + +static int +iwl_mvm_ftm_responder_dyn_cfg_cmd(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_ftm_responder_params *params) +{ + int ret; + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, + WIDE_ID(LOCATION_GROUP, TOF_RESPONDER_DYN_CONFIG_CMD), + 2); + + switch (cmd_ver) { + case 2: + ret = iwl_mvm_ftm_responder_dyn_cfg_v2(mvm, vif, + params); + break; + case 3: + ret = iwl_mvm_ftm_responder_dyn_cfg_v3(mvm, vif, + params, NULL); + break; + default: + IWL_ERR(mvm, "Unsupported DYN_CONFIG_CMD version %u\n", + cmd_ver); + ret = -ENOTSUPP; + } + + return ret; +} + +static void iwl_mvm_resp_del_pasn_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mvm_pasn_sta *sta) +{ + list_del(&sta->list); + iwl_mvm_rm_sta_id(mvm, vif, sta->int_sta.sta_id); + iwl_mvm_dealloc_int_sta(mvm, &sta->int_sta); + kfree(sta); +} + +int iwl_mvm_ftm_respoder_add_pasn_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u8 *addr, u32 cipher, u8 *tk, u32 tk_len, + u8 *hltk, u32 hltk_len) +{ + int ret; + struct iwl_mvm_pasn_sta *sta = NULL; + struct iwl_mvm_pasn_hltk_data hltk_data = { + .addr = addr, + .hltk = hltk, + }; + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, + WIDE_ID(LOCATION_GROUP, TOF_RESPONDER_DYN_CONFIG_CMD), + 2); + + lockdep_assert_held(&mvm->mutex); + + if (cmd_ver < 3) { + IWL_ERR(mvm, "Adding PASN station not supported by FW\n"); + return -ENOTSUPP; + } + + hltk_data.cipher = iwl_mvm_cipher_to_location_cipher(cipher); + if (hltk_data.cipher == IWL_LOCATION_CIPHER_INVALID) { + IWL_ERR(mvm, "invalid cipher: %u\n", cipher); + return -EINVAL; + } + + if (tk && tk_len) { + sta = kzalloc(sizeof(*sta), GFP_KERNEL); + if (!sta) + return -ENOBUFS; + + ret = iwl_mvm_add_pasn_sta(mvm, vif, &sta->int_sta, addr, + cipher, tk, tk_len); + if (ret) { + kfree(sta); + return ret; + } + + memcpy(sta->addr, addr, ETH_ALEN); + list_add_tail(&sta->list, &mvm->resp_pasn_list); + } + + ret = iwl_mvm_ftm_responder_dyn_cfg_v3(mvm, vif, NULL, &hltk_data); + if (ret && sta) + iwl_mvm_resp_del_pasn_sta(mvm, vif, sta); + + return ret; +} + +int iwl_mvm_ftm_resp_remove_pasn_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, u8 *addr) +{ + struct iwl_mvm_pasn_sta *sta, *prev; + + lockdep_assert_held(&mvm->mutex); + + list_for_each_entry_safe(sta, prev, &mvm->resp_pasn_list, list) { + if (!memcmp(sta->addr, addr, ETH_ALEN)) { + iwl_mvm_resp_del_pasn_sta(mvm, vif, sta); + return 0; + } + } + + IWL_ERR(mvm, "FTM: PASN station %pM not found\n", addr); + return -EINVAL; +} + +int iwl_mvm_ftm_start_responder(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_ftm_responder_params *params; + struct ieee80211_chanctx_conf ctx, *pctx; + u16 *phy_ctxt_id; + struct iwl_mvm_phy_ctxt *phy_ctxt; + int ret; + + params = vif->bss_conf.ftmr_params; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON_ONCE(!vif->bss_conf.ftm_responder)) + return -EINVAL; + + if (vif->p2p || vif->type != NL80211_IFTYPE_AP || + !mvmvif->ap_ibss_active) { + IWL_ERR(mvm, "Cannot start responder, not in AP mode\n"); + return -EIO; + } + + rcu_read_lock(); + pctx = rcu_dereference(vif->bss_conf.chanctx_conf); + /* Copy the ctx to unlock the rcu and send the phy ctxt. We don't care + * about changes in the ctx after releasing the lock because the driver + * is still protected by the mutex. */ + ctx = *pctx; + phy_ctxt_id = (u16 *)pctx->drv_priv; + rcu_read_unlock(); + + phy_ctxt = &mvm->phy_ctxts[*phy_ctxt_id]; + ret = iwl_mvm_phy_ctxt_changed(mvm, phy_ctxt, &ctx.def, + ctx.rx_chains_static, + ctx.rx_chains_dynamic); + if (ret) + return ret; + + ret = iwl_mvm_ftm_responder_cmd(mvm, vif, &ctx.def); + if (ret) + return ret; + + if (params) + ret = iwl_mvm_ftm_responder_dyn_cfg_cmd(mvm, vif, params); + + return ret; +} + +void iwl_mvm_ftm_responder_clear(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_pasn_sta *sta, *prev; + + lockdep_assert_held(&mvm->mutex); + + list_for_each_entry_safe(sta, prev, &mvm->resp_pasn_list, list) + iwl_mvm_resp_del_pasn_sta(mvm, vif, sta); +} + +void iwl_mvm_ftm_restart_responder(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + if (!vif->bss_conf.ftm_responder) + return; + + iwl_mvm_ftm_responder_clear(mvm, vif); + iwl_mvm_ftm_start_responder(mvm, vif); +} + +void iwl_mvm_ftm_responder_stats(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_ftm_responder_stats *resp = (void *)pkt->data; + struct cfg80211_ftm_responder_stats *stats = &mvm->ftm_resp_stats; + u32 flags = le32_to_cpu(resp->flags); + + if (resp->success_ftm == resp->ftm_per_burst) + stats->success_num++; + else if (resp->success_ftm >= 2) + stats->partial_num++; + else + stats->failed_num++; + + if ((flags & FTM_RESP_STAT_ASAP_REQ) && + (flags & FTM_RESP_STAT_ASAP_RESP)) + stats->asap_num++; + + if (flags & FTM_RESP_STAT_NON_ASAP_RESP) + stats->non_asap_num++; + + stats->total_duration_ms += le32_to_cpu(resp->duration) / USEC_PER_MSEC; + + if (flags & FTM_RESP_STAT_TRIGGER_UNKNOWN) + stats->unknown_triggers_num++; + + if (flags & FTM_RESP_STAT_DUP) + stats->reschedule_requests_num++; + + if (flags & FTM_RESP_STAT_NON_ASAP_OUT_WIN) + stats->out_of_window_triggers_num++; +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/fw-api.h b/drivers/net/wireless/intel/iwlwifi/mvm/fw-api.h new file mode 100644 index 000000000..083f86fa5 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/fw-api.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018, 2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __fw_api_h__ +#define __fw_api_h__ + +#include "fw/api/tdls.h" +#include "fw/api/mac-cfg.h" +#include "fw/api/offload.h" +#include "fw/api/context.h" +#include "fw/api/time-event.h" +#include "fw/api/datapath.h" +#include "fw/api/phy.h" +#include "fw/api/config.h" +#include "fw/api/system.h" +#include "fw/api/alive.h" +#include "fw/api/binding.h" +#include "fw/api/cmdhdr.h" +#include "fw/api/coex.h" +#include "fw/api/commands.h" +#include "fw/api/d3.h" +#include "fw/api/filter.h" +#include "fw/api/led.h" +#include "fw/api/mac.h" +#include "fw/api/nvm-reg.h" +#include "fw/api/phy-ctxt.h" +#include "fw/api/power.h" +#include "fw/api/rs.h" +#include "fw/api/rx.h" +#include "fw/api/scan.h" +#include "fw/api/sf.h" +#include "fw/api/sta.h" +#include "fw/api/stats.h" +#include "fw/api/location.h" +#include "fw/api/tx.h" +#include "fw/api/rfi.h" + +#endif /* __fw_api_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/fw.c b/drivers/net/wireless/intel/iwlwifi/mvm/fw.c new file mode 100644 index 000000000..2e3c98eaa --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/fw.c @@ -0,0 +1,1748 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include <net/mac80211.h> +#include <linux/netdevice.h> +#include <linux/dmi.h> + +#include "iwl-trans.h" +#include "iwl-op-mode.h" +#include "fw/img.h" +#include "iwl-debug.h" +#include "iwl-prph.h" +#include "fw/acpi.h" +#include "fw/pnvm.h" + +#include "mvm.h" +#include "fw/dbg.h" +#include "iwl-phy-db.h" +#include "iwl-modparams.h" +#include "iwl-nvm-parse.h" + +#define MVM_UCODE_ALIVE_TIMEOUT (HZ) +#define MVM_UCODE_CALIB_TIMEOUT (2 * HZ) + +#define IWL_TAS_US_MCC 0x5553 +#define IWL_TAS_CANADA_MCC 0x4341 + +struct iwl_mvm_alive_data { + bool valid; + u32 scd_base_addr; +}; + +static int iwl_send_tx_ant_cfg(struct iwl_mvm *mvm, u8 valid_tx_ant) +{ + struct iwl_tx_ant_cfg_cmd tx_ant_cmd = { + .valid = cpu_to_le32(valid_tx_ant), + }; + + IWL_DEBUG_FW(mvm, "select valid tx ant: %u\n", valid_tx_ant); + return iwl_mvm_send_cmd_pdu(mvm, TX_ANT_CONFIGURATION_CMD, 0, + sizeof(tx_ant_cmd), &tx_ant_cmd); +} + +static int iwl_send_rss_cfg_cmd(struct iwl_mvm *mvm) +{ + int i; + struct iwl_rss_config_cmd cmd = { + .flags = cpu_to_le32(IWL_RSS_ENABLE), + .hash_mask = BIT(IWL_RSS_HASH_TYPE_IPV4_TCP) | + BIT(IWL_RSS_HASH_TYPE_IPV4_UDP) | + BIT(IWL_RSS_HASH_TYPE_IPV4_PAYLOAD) | + BIT(IWL_RSS_HASH_TYPE_IPV6_TCP) | + BIT(IWL_RSS_HASH_TYPE_IPV6_UDP) | + BIT(IWL_RSS_HASH_TYPE_IPV6_PAYLOAD), + }; + + if (mvm->trans->num_rx_queues == 1) + return 0; + + /* Do not direct RSS traffic to Q 0 which is our fallback queue */ + for (i = 0; i < ARRAY_SIZE(cmd.indirection_table); i++) + cmd.indirection_table[i] = + 1 + (i % (mvm->trans->num_rx_queues - 1)); + netdev_rss_key_fill(cmd.secret_key, sizeof(cmd.secret_key)); + + return iwl_mvm_send_cmd_pdu(mvm, RSS_CONFIG_CMD, 0, sizeof(cmd), &cmd); +} + +static int iwl_mvm_send_dqa_cmd(struct iwl_mvm *mvm) +{ + struct iwl_dqa_enable_cmd dqa_cmd = { + .cmd_queue = cpu_to_le32(IWL_MVM_DQA_CMD_QUEUE), + }; + u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, DQA_ENABLE_CMD); + int ret; + + ret = iwl_mvm_send_cmd_pdu(mvm, cmd_id, 0, sizeof(dqa_cmd), &dqa_cmd); + if (ret) + IWL_ERR(mvm, "Failed to send DQA enabling command: %d\n", ret); + else + IWL_DEBUG_FW(mvm, "Working in DQA mode\n"); + + return ret; +} + +void iwl_mvm_mfu_assert_dump_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mfu_assert_dump_notif *mfu_dump_notif = (void *)pkt->data; + __le32 *dump_data = mfu_dump_notif->data; + int n_words = le32_to_cpu(mfu_dump_notif->data_size) / sizeof(__le32); + int i; + + if (mfu_dump_notif->index_num == 0) + IWL_INFO(mvm, "MFUART assert id 0x%x occurred\n", + le32_to_cpu(mfu_dump_notif->assert_id)); + + for (i = 0; i < n_words; i++) + IWL_DEBUG_INFO(mvm, + "MFUART assert dump, dword %u: 0x%08x\n", + le16_to_cpu(mfu_dump_notif->index_num) * + n_words + i, + le32_to_cpu(dump_data[i])); +} + +static bool iwl_alive_fn(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); + struct iwl_mvm *mvm = + container_of(notif_wait, struct iwl_mvm, notif_wait); + struct iwl_mvm_alive_data *alive_data = data; + struct iwl_umac_alive *umac; + struct iwl_lmac_alive *lmac1; + struct iwl_lmac_alive *lmac2 = NULL; + u16 status; + u32 lmac_error_event_table, umac_error_table; + u32 version = iwl_fw_lookup_notif_ver(mvm->fw, LEGACY_GROUP, + UCODE_ALIVE_NTFY, 0); + u32 i; + + if (version == 6) { + struct iwl_alive_ntf_v6 *palive; + + if (pkt_len < sizeof(*palive)) + return false; + + palive = (void *)pkt->data; + mvm->trans->dbg.imr_data.imr_enable = + le32_to_cpu(palive->imr.enabled); + mvm->trans->dbg.imr_data.imr_size = + le32_to_cpu(palive->imr.size); + mvm->trans->dbg.imr_data.imr2sram_remainbyte = + mvm->trans->dbg.imr_data.imr_size; + mvm->trans->dbg.imr_data.imr_base_addr = + palive->imr.base_addr; + mvm->trans->dbg.imr_data.imr_curr_addr = + le64_to_cpu(mvm->trans->dbg.imr_data.imr_base_addr); + IWL_DEBUG_FW(mvm, "IMR Enabled: 0x0%x size 0x0%x Address 0x%016llx\n", + mvm->trans->dbg.imr_data.imr_enable, + mvm->trans->dbg.imr_data.imr_size, + le64_to_cpu(mvm->trans->dbg.imr_data.imr_base_addr)); + + if (!mvm->trans->dbg.imr_data.imr_enable) { + for (i = 0; i < ARRAY_SIZE(mvm->trans->dbg.active_regions); i++) { + struct iwl_ucode_tlv *reg_tlv; + struct iwl_fw_ini_region_tlv *reg; + + reg_tlv = mvm->trans->dbg.active_regions[i]; + if (!reg_tlv) + continue; + + reg = (void *)reg_tlv->data; + /* + * We have only one DRAM IMR region, so we + * can break as soon as we find the first + * one. + */ + if (reg->type == IWL_FW_INI_REGION_DRAM_IMR) { + mvm->trans->dbg.unsupported_region_msk |= BIT(i); + break; + } + } + } + } + + if (version >= 5) { + struct iwl_alive_ntf_v5 *palive; + + if (pkt_len < sizeof(*palive)) + return false; + + palive = (void *)pkt->data; + umac = &palive->umac_data; + lmac1 = &palive->lmac_data[0]; + lmac2 = &palive->lmac_data[1]; + status = le16_to_cpu(palive->status); + + mvm->trans->sku_id[0] = le32_to_cpu(palive->sku_id.data[0]); + mvm->trans->sku_id[1] = le32_to_cpu(palive->sku_id.data[1]); + mvm->trans->sku_id[2] = le32_to_cpu(palive->sku_id.data[2]); + + IWL_DEBUG_FW(mvm, "Got sku_id: 0x0%x 0x0%x 0x0%x\n", + mvm->trans->sku_id[0], + mvm->trans->sku_id[1], + mvm->trans->sku_id[2]); + } else if (iwl_rx_packet_payload_len(pkt) == sizeof(struct iwl_alive_ntf_v4)) { + struct iwl_alive_ntf_v4 *palive; + + if (pkt_len < sizeof(*palive)) + return false; + + palive = (void *)pkt->data; + umac = &palive->umac_data; + lmac1 = &palive->lmac_data[0]; + lmac2 = &palive->lmac_data[1]; + status = le16_to_cpu(palive->status); + } else if (iwl_rx_packet_payload_len(pkt) == + sizeof(struct iwl_alive_ntf_v3)) { + struct iwl_alive_ntf_v3 *palive3; + + if (pkt_len < sizeof(*palive3)) + return false; + + palive3 = (void *)pkt->data; + umac = &palive3->umac_data; + lmac1 = &palive3->lmac_data; + status = le16_to_cpu(palive3->status); + } else { + WARN(1, "unsupported alive notification (size %d)\n", + iwl_rx_packet_payload_len(pkt)); + /* get timeout later */ + return false; + } + + lmac_error_event_table = + le32_to_cpu(lmac1->dbg_ptrs.error_event_table_ptr); + iwl_fw_lmac1_set_alive_err_table(mvm->trans, lmac_error_event_table); + + if (lmac2) + mvm->trans->dbg.lmac_error_event_table[1] = + le32_to_cpu(lmac2->dbg_ptrs.error_event_table_ptr); + + umac_error_table = le32_to_cpu(umac->dbg_ptrs.error_info_addr) & + ~FW_ADDR_CACHE_CONTROL; + + if (umac_error_table) { + if (umac_error_table >= + mvm->trans->cfg->min_umac_error_event_table) { + iwl_fw_umac_set_alive_err_table(mvm->trans, + umac_error_table); + } else { + IWL_ERR(mvm, + "Not valid error log pointer 0x%08X for %s uCode\n", + umac_error_table, + (mvm->fwrt.cur_fw_img == IWL_UCODE_INIT) ? + "Init" : "RT"); + } + } + + alive_data->scd_base_addr = le32_to_cpu(lmac1->dbg_ptrs.scd_base_ptr); + alive_data->valid = status == IWL_ALIVE_STATUS_OK; + + IWL_DEBUG_FW(mvm, + "Alive ucode status 0x%04x revision 0x%01X 0x%01X\n", + status, lmac1->ver_type, lmac1->ver_subtype); + + if (lmac2) + IWL_DEBUG_FW(mvm, "Alive ucode CDB\n"); + + IWL_DEBUG_FW(mvm, + "UMAC version: Major - 0x%x, Minor - 0x%x\n", + le32_to_cpu(umac->umac_major), + le32_to_cpu(umac->umac_minor)); + + iwl_fwrt_update_fw_versions(&mvm->fwrt, lmac1, umac); + + return true; +} + +static bool iwl_wait_init_complete(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + WARN_ON(pkt->hdr.cmd != INIT_COMPLETE_NOTIF); + + return true; +} + +static bool iwl_wait_phy_db_entry(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_phy_db *phy_db = data; + + if (pkt->hdr.cmd != CALIB_RES_NOTIF_PHY_DB) { + WARN_ON(pkt->hdr.cmd != INIT_COMPLETE_NOTIF); + return true; + } + + WARN_ON(iwl_phy_db_set_section(phy_db, pkt)); + + return false; +} + +static void iwl_mvm_print_pd_notification(struct iwl_mvm *mvm) +{ +#define IWL_FW_PRINT_REG_INFO(reg_name) \ + IWL_ERR(mvm, #reg_name ": 0x%x\n", iwl_read_umac_prph(trans, reg_name)) + + struct iwl_trans *trans = mvm->trans; + enum iwl_device_family device_family = trans->trans_cfg->device_family; + + if (device_family < IWL_DEVICE_FAMILY_8000) + return; + + if (device_family <= IWL_DEVICE_FAMILY_9000) + IWL_FW_PRINT_REG_INFO(WFPM_ARC1_PD_NOTIFICATION); + else + IWL_FW_PRINT_REG_INFO(WFPM_LMAC1_PD_NOTIFICATION); + + IWL_FW_PRINT_REG_INFO(HPM_SECONDARY_DEVICE_STATE); + + /* print OPT info */ + IWL_FW_PRINT_REG_INFO(WFPM_MAC_OTP_CFG7_ADDR); + IWL_FW_PRINT_REG_INFO(WFPM_MAC_OTP_CFG7_DATA); +} + +static int iwl_mvm_load_ucode_wait_alive(struct iwl_mvm *mvm, + enum iwl_ucode_type ucode_type) +{ + struct iwl_notification_wait alive_wait; + struct iwl_mvm_alive_data alive_data = {}; + const struct fw_img *fw; + int ret; + enum iwl_ucode_type old_type = mvm->fwrt.cur_fw_img; + static const u16 alive_cmd[] = { UCODE_ALIVE_NTFY }; + bool run_in_rfkill = + ucode_type == IWL_UCODE_INIT || iwl_mvm_has_unified_ucode(mvm); + + if (ucode_type == IWL_UCODE_REGULAR && + iwl_fw_dbg_conf_usniffer(mvm->fw, FW_DBG_START_FROM_ALIVE) && + !(fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_USNIFFER_UNIFIED))) + fw = iwl_get_ucode_image(mvm->fw, IWL_UCODE_REGULAR_USNIFFER); + else + fw = iwl_get_ucode_image(mvm->fw, ucode_type); + if (WARN_ON(!fw)) + return -EINVAL; + iwl_fw_set_current_image(&mvm->fwrt, ucode_type); + clear_bit(IWL_MVM_STATUS_FIRMWARE_RUNNING, &mvm->status); + + iwl_init_notification_wait(&mvm->notif_wait, &alive_wait, + alive_cmd, ARRAY_SIZE(alive_cmd), + iwl_alive_fn, &alive_data); + + /* + * We want to load the INIT firmware even in RFKILL + * For the unified firmware case, the ucode_type is not + * INIT, but we still need to run it. + */ + ret = iwl_trans_start_fw(mvm->trans, fw, run_in_rfkill); + if (ret) { + iwl_fw_set_current_image(&mvm->fwrt, old_type); + iwl_remove_notification(&mvm->notif_wait, &alive_wait); + return ret; + } + + /* + * Some things may run in the background now, but we + * just wait for the ALIVE notification here. + */ + ret = iwl_wait_notification(&mvm->notif_wait, &alive_wait, + MVM_UCODE_ALIVE_TIMEOUT); + if (ret) { + struct iwl_trans *trans = mvm->trans; + + /* SecBoot info */ + if (trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_22000) { + IWL_ERR(mvm, + "SecBoot CPU1 Status: 0x%x, CPU2 Status: 0x%x\n", + iwl_read_umac_prph(trans, UMAG_SB_CPU_1_STATUS), + iwl_read_umac_prph(trans, + UMAG_SB_CPU_2_STATUS)); + } else if (trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_8000) { + IWL_ERR(mvm, + "SecBoot CPU1 Status: 0x%x, CPU2 Status: 0x%x\n", + iwl_read_prph(trans, SB_CPU_1_STATUS), + iwl_read_prph(trans, SB_CPU_2_STATUS)); + } + + iwl_mvm_print_pd_notification(mvm); + + /* LMAC/UMAC PC info */ + if (trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_9000) { + IWL_ERR(mvm, "UMAC PC: 0x%x\n", + iwl_read_umac_prph(trans, + UREG_UMAC_CURRENT_PC)); + IWL_ERR(mvm, "LMAC PC: 0x%x\n", + iwl_read_umac_prph(trans, + UREG_LMAC1_CURRENT_PC)); + if (iwl_mvm_is_cdb_supported(mvm)) + IWL_ERR(mvm, "LMAC2 PC: 0x%x\n", + iwl_read_umac_prph(trans, + UREG_LMAC2_CURRENT_PC)); + } + + if (ret == -ETIMEDOUT) + iwl_fw_dbg_error_collect(&mvm->fwrt, + FW_DBG_TRIGGER_ALIVE_TIMEOUT); + + iwl_fw_set_current_image(&mvm->fwrt, old_type); + return ret; + } + + if (!alive_data.valid) { + IWL_ERR(mvm, "Loaded ucode is not valid!\n"); + iwl_fw_set_current_image(&mvm->fwrt, old_type); + return -EIO; + } + + ret = iwl_pnvm_load(mvm->trans, &mvm->notif_wait); + if (ret) { + IWL_ERR(mvm, "Timeout waiting for PNVM load!\n"); + iwl_fw_set_current_image(&mvm->fwrt, old_type); + return ret; + } + + iwl_trans_fw_alive(mvm->trans, alive_data.scd_base_addr); + + /* + * Note: all the queues are enabled as part of the interface + * initialization, but in firmware restart scenarios they + * could be stopped, so wake them up. In firmware restart, + * mac80211 will have the queues stopped as well until the + * reconfiguration completes. During normal startup, they + * will be empty. + */ + + memset(&mvm->queue_info, 0, sizeof(mvm->queue_info)); + /* + * Set a 'fake' TID for the command queue, since we use the + * hweight() of the tid_bitmap as a refcount now. Not that + * we ever even consider the command queue as one we might + * want to reuse, but be safe nevertheless. + */ + mvm->queue_info[IWL_MVM_DQA_CMD_QUEUE].tid_bitmap = + BIT(IWL_MAX_TID_COUNT + 2); + + set_bit(IWL_MVM_STATUS_FIRMWARE_RUNNING, &mvm->status); +#ifdef CONFIG_IWLWIFI_DEBUGFS + iwl_fw_set_dbg_rec_on(&mvm->fwrt); +#endif + + /* + * All the BSSes in the BSS table include the GP2 in the system + * at the beacon Rx time, this is of course no longer relevant + * since we are resetting the firmware. + * Purge all the BSS table. + */ + cfg80211_bss_flush(mvm->hw->wiphy); + + return 0; +} + +static int iwl_run_unified_mvm_ucode(struct iwl_mvm *mvm) +{ + struct iwl_notification_wait init_wait; + struct iwl_nvm_access_complete_cmd nvm_complete = {}; + struct iwl_init_extended_cfg_cmd init_cfg = { + .init_flags = cpu_to_le32(BIT(IWL_INIT_NVM)), + }; + static const u16 init_complete[] = { + INIT_COMPLETE_NOTIF, + }; + int ret; + + if (mvm->trans->cfg->tx_with_siso_diversity) + init_cfg.init_flags |= cpu_to_le32(BIT(IWL_INIT_PHY)); + + lockdep_assert_held(&mvm->mutex); + + mvm->rfkill_safe_init_done = false; + + iwl_init_notification_wait(&mvm->notif_wait, + &init_wait, + init_complete, + ARRAY_SIZE(init_complete), + iwl_wait_init_complete, + NULL); + + iwl_dbg_tlv_time_point(&mvm->fwrt, IWL_FW_INI_TIME_POINT_EARLY, NULL); + + /* Will also start the device */ + ret = iwl_mvm_load_ucode_wait_alive(mvm, IWL_UCODE_REGULAR); + if (ret) { + IWL_ERR(mvm, "Failed to start RT ucode: %d\n", ret); + goto error; + } + iwl_dbg_tlv_time_point(&mvm->fwrt, IWL_FW_INI_TIME_POINT_AFTER_ALIVE, + NULL); + + /* Send init config command to mark that we are sending NVM access + * commands + */ + ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(SYSTEM_GROUP, + INIT_EXTENDED_CFG_CMD), + CMD_SEND_IN_RFKILL, + sizeof(init_cfg), &init_cfg); + if (ret) { + IWL_ERR(mvm, "Failed to run init config command: %d\n", + ret); + goto error; + } + + /* Load NVM to NIC if needed */ + if (mvm->nvm_file_name) { + ret = iwl_read_external_nvm(mvm->trans, mvm->nvm_file_name, + mvm->nvm_sections); + if (ret) + goto error; + ret = iwl_mvm_load_nvm_to_nic(mvm); + if (ret) + goto error; + } + + if (IWL_MVM_PARSE_NVM && !mvm->nvm_data) { + ret = iwl_nvm_init(mvm); + if (ret) { + IWL_ERR(mvm, "Failed to read NVM: %d\n", ret); + goto error; + } + } + + ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(REGULATORY_AND_NVM_GROUP, + NVM_ACCESS_COMPLETE), + CMD_SEND_IN_RFKILL, + sizeof(nvm_complete), &nvm_complete); + if (ret) { + IWL_ERR(mvm, "Failed to run complete NVM access: %d\n", + ret); + goto error; + } + + /* We wait for the INIT complete notification */ + ret = iwl_wait_notification(&mvm->notif_wait, &init_wait, + MVM_UCODE_ALIVE_TIMEOUT); + if (ret) + return ret; + + /* Read the NVM only at driver load time, no need to do this twice */ + if (!IWL_MVM_PARSE_NVM && !mvm->nvm_data) { + mvm->nvm_data = iwl_get_nvm(mvm->trans, mvm->fw); + if (IS_ERR(mvm->nvm_data)) { + ret = PTR_ERR(mvm->nvm_data); + mvm->nvm_data = NULL; + IWL_ERR(mvm, "Failed to read NVM: %d\n", ret); + return ret; + } + } + + mvm->rfkill_safe_init_done = true; + + return 0; + +error: + iwl_remove_notification(&mvm->notif_wait, &init_wait); + return ret; +} + +#ifdef CONFIG_ACPI +static void iwl_mvm_phy_filter_init(struct iwl_mvm *mvm, + struct iwl_phy_specific_cfg *phy_filters) +{ + /* + * TODO: read specific phy config from BIOS + * ACPI table for this feature has not been defined yet, + * so for now we use hardcoded values. + */ + + if (IWL_MVM_PHY_FILTER_CHAIN_A) { + phy_filters->filter_cfg_chain_a = + cpu_to_le32(IWL_MVM_PHY_FILTER_CHAIN_A); + } + if (IWL_MVM_PHY_FILTER_CHAIN_B) { + phy_filters->filter_cfg_chain_b = + cpu_to_le32(IWL_MVM_PHY_FILTER_CHAIN_B); + } + if (IWL_MVM_PHY_FILTER_CHAIN_C) { + phy_filters->filter_cfg_chain_c = + cpu_to_le32(IWL_MVM_PHY_FILTER_CHAIN_C); + } + if (IWL_MVM_PHY_FILTER_CHAIN_D) { + phy_filters->filter_cfg_chain_d = + cpu_to_le32(IWL_MVM_PHY_FILTER_CHAIN_D); + } +} +#else /* CONFIG_ACPI */ + +static void iwl_mvm_phy_filter_init(struct iwl_mvm *mvm, + struct iwl_phy_specific_cfg *phy_filters) +{ +} +#endif /* CONFIG_ACPI */ + +#if defined(CONFIG_ACPI) && defined(CONFIG_EFI) +static int iwl_mvm_sgom_init(struct iwl_mvm *mvm) +{ + u8 cmd_ver; + int ret; + struct iwl_host_cmd cmd = { + .id = WIDE_ID(REGULATORY_AND_NVM_GROUP, + SAR_OFFSET_MAPPING_TABLE_CMD), + .flags = 0, + .data[0] = &mvm->fwrt.sgom_table, + .len[0] = sizeof(mvm->fwrt.sgom_table), + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + }; + + if (!mvm->fwrt.sgom_enabled) { + IWL_DEBUG_RADIO(mvm, "SGOM table is disabled\n"); + return 0; + } + + cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd.id, + IWL_FW_CMD_VER_UNKNOWN); + + if (cmd_ver != 2) { + IWL_DEBUG_RADIO(mvm, "command version is unsupported. version = %d\n", + cmd_ver); + return 0; + } + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (ret < 0) + IWL_ERR(mvm, "failed to send SAR_OFFSET_MAPPING_CMD (%d)\n", ret); + + return ret; +} +#else + +static int iwl_mvm_sgom_init(struct iwl_mvm *mvm) +{ + return 0; +} +#endif + +static int iwl_send_phy_cfg_cmd(struct iwl_mvm *mvm) +{ + u32 cmd_id = PHY_CONFIGURATION_CMD; + struct iwl_phy_cfg_cmd_v3 phy_cfg_cmd; + enum iwl_ucode_type ucode_type = mvm->fwrt.cur_fw_img; + struct iwl_phy_specific_cfg phy_filters = {}; + u8 cmd_ver; + size_t cmd_size; + + if (iwl_mvm_has_unified_ucode(mvm) && + !mvm->trans->cfg->tx_with_siso_diversity) + return 0; + + if (mvm->trans->cfg->tx_with_siso_diversity) { + /* + * TODO: currently we don't set the antenna but letting the NIC + * to decide which antenna to use. This should come from BIOS. + */ + phy_cfg_cmd.phy_cfg = + cpu_to_le32(FW_PHY_CFG_CHAIN_SAD_ENABLED); + } + + /* Set parameters */ + phy_cfg_cmd.phy_cfg = cpu_to_le32(iwl_mvm_get_phy_config(mvm)); + + /* set flags extra PHY configuration flags from the device's cfg */ + phy_cfg_cmd.phy_cfg |= + cpu_to_le32(mvm->trans->trans_cfg->extra_phy_cfg_flags); + + phy_cfg_cmd.calib_control.event_trigger = + mvm->fw->default_calib[ucode_type].event_trigger; + phy_cfg_cmd.calib_control.flow_trigger = + mvm->fw->default_calib[ucode_type].flow_trigger; + + cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, + IWL_FW_CMD_VER_UNKNOWN); + if (cmd_ver == 3) { + iwl_mvm_phy_filter_init(mvm, &phy_filters); + memcpy(&phy_cfg_cmd.phy_specific_cfg, &phy_filters, + sizeof(struct iwl_phy_specific_cfg)); + } + + IWL_DEBUG_INFO(mvm, "Sending Phy CFG command: 0x%x\n", + phy_cfg_cmd.phy_cfg); + cmd_size = (cmd_ver == 3) ? sizeof(struct iwl_phy_cfg_cmd_v3) : + sizeof(struct iwl_phy_cfg_cmd_v1); + return iwl_mvm_send_cmd_pdu(mvm, cmd_id, 0, cmd_size, &phy_cfg_cmd); +} + +int iwl_run_init_mvm_ucode(struct iwl_mvm *mvm) +{ + struct iwl_notification_wait calib_wait; + static const u16 init_complete[] = { + INIT_COMPLETE_NOTIF, + CALIB_RES_NOTIF_PHY_DB + }; + int ret; + + if (iwl_mvm_has_unified_ucode(mvm)) + return iwl_run_unified_mvm_ucode(mvm); + + lockdep_assert_held(&mvm->mutex); + + mvm->rfkill_safe_init_done = false; + + iwl_init_notification_wait(&mvm->notif_wait, + &calib_wait, + init_complete, + ARRAY_SIZE(init_complete), + iwl_wait_phy_db_entry, + mvm->phy_db); + + iwl_dbg_tlv_time_point(&mvm->fwrt, IWL_FW_INI_TIME_POINT_EARLY, NULL); + + /* Will also start the device */ + ret = iwl_mvm_load_ucode_wait_alive(mvm, IWL_UCODE_INIT); + if (ret) { + IWL_ERR(mvm, "Failed to start INIT ucode: %d\n", ret); + goto remove_notif; + } + + if (mvm->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_8000) { + ret = iwl_mvm_send_bt_init_conf(mvm); + if (ret) + goto remove_notif; + } + + /* Read the NVM only at driver load time, no need to do this twice */ + if (!mvm->nvm_data) { + ret = iwl_nvm_init(mvm); + if (ret) { + IWL_ERR(mvm, "Failed to read NVM: %d\n", ret); + goto remove_notif; + } + } + + /* In case we read the NVM from external file, load it to the NIC */ + if (mvm->nvm_file_name) { + ret = iwl_mvm_load_nvm_to_nic(mvm); + if (ret) + goto remove_notif; + } + + WARN_ONCE(mvm->nvm_data->nvm_version < mvm->trans->cfg->nvm_ver, + "Too old NVM version (0x%0x, required = 0x%0x)", + mvm->nvm_data->nvm_version, mvm->trans->cfg->nvm_ver); + + /* + * abort after reading the nvm in case RF Kill is on, we will complete + * the init seq later when RF kill will switch to off + */ + if (iwl_mvm_is_radio_hw_killed(mvm)) { + IWL_DEBUG_RF_KILL(mvm, + "jump over all phy activities due to RF kill\n"); + goto remove_notif; + } + + mvm->rfkill_safe_init_done = true; + + /* Send TX valid antennas before triggering calibrations */ + ret = iwl_send_tx_ant_cfg(mvm, iwl_mvm_get_valid_tx_ant(mvm)); + if (ret) + goto remove_notif; + + ret = iwl_send_phy_cfg_cmd(mvm); + if (ret) { + IWL_ERR(mvm, "Failed to run INIT calibrations: %d\n", + ret); + goto remove_notif; + } + + /* + * Some things may run in the background now, but we + * just wait for the calibration complete notification. + */ + ret = iwl_wait_notification(&mvm->notif_wait, &calib_wait, + MVM_UCODE_CALIB_TIMEOUT); + if (!ret) + goto out; + + if (iwl_mvm_is_radio_hw_killed(mvm)) { + IWL_DEBUG_RF_KILL(mvm, "RFKILL while calibrating.\n"); + ret = 0; + } else { + IWL_ERR(mvm, "Failed to run INIT calibrations: %d\n", + ret); + } + + goto out; + +remove_notif: + iwl_remove_notification(&mvm->notif_wait, &calib_wait); +out: + mvm->rfkill_safe_init_done = false; + if (iwlmvm_mod_params.init_dbg && !mvm->nvm_data) { + /* we want to debug INIT and we have no NVM - fake */ + mvm->nvm_data = kzalloc(sizeof(struct iwl_nvm_data) + + sizeof(struct ieee80211_channel) + + sizeof(struct ieee80211_rate), + GFP_KERNEL); + if (!mvm->nvm_data) + return -ENOMEM; + mvm->nvm_data->bands[0].channels = mvm->nvm_data->channels; + mvm->nvm_data->bands[0].n_channels = 1; + mvm->nvm_data->bands[0].n_bitrates = 1; + mvm->nvm_data->bands[0].bitrates = + (void *)(mvm->nvm_data->channels + 1); + mvm->nvm_data->bands[0].bitrates->hw_value = 10; + } + + return ret; +} + +static int iwl_mvm_config_ltr(struct iwl_mvm *mvm) +{ + struct iwl_ltr_config_cmd cmd = { + .flags = cpu_to_le32(LTR_CFG_FLAG_FEATURE_ENABLE), + }; + + if (!mvm->trans->ltr_enabled) + return 0; + + return iwl_mvm_send_cmd_pdu(mvm, LTR_CONFIG, 0, + sizeof(cmd), &cmd); +} + +#ifdef CONFIG_ACPI +int iwl_mvm_sar_select_profile(struct iwl_mvm *mvm, int prof_a, int prof_b) +{ + u32 cmd_id = REDUCE_TX_POWER_CMD; + struct iwl_dev_tx_power_cmd cmd = { + .common.set_mode = cpu_to_le32(IWL_TX_POWER_MODE_SET_CHAINS), + }; + __le16 *per_chain; + int ret; + u16 len = 0; + u32 n_subbands; + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, + IWL_FW_CMD_VER_UNKNOWN); + if (cmd_ver == 7) { + len = sizeof(cmd.v7); + n_subbands = IWL_NUM_SUB_BANDS_V2; + per_chain = cmd.v7.per_chain[0][0]; + cmd.v7.flags = cpu_to_le32(mvm->fwrt.reduced_power_flags); + } else if (cmd_ver == 6) { + len = sizeof(cmd.v6); + n_subbands = IWL_NUM_SUB_BANDS_V2; + per_chain = cmd.v6.per_chain[0][0]; + } else if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_REDUCE_TX_POWER)) { + len = sizeof(cmd.v5); + n_subbands = IWL_NUM_SUB_BANDS_V1; + per_chain = cmd.v5.per_chain[0][0]; + } else if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TX_POWER_ACK)) { + len = sizeof(cmd.v4); + n_subbands = IWL_NUM_SUB_BANDS_V1; + per_chain = cmd.v4.per_chain[0][0]; + } else { + len = sizeof(cmd.v3); + n_subbands = IWL_NUM_SUB_BANDS_V1; + per_chain = cmd.v3.per_chain[0][0]; + } + + /* all structs have the same common part, add it */ + len += sizeof(cmd.common); + + ret = iwl_sar_select_profile(&mvm->fwrt, per_chain, + IWL_NUM_CHAIN_TABLES, + n_subbands, prof_a, prof_b); + + /* return on error or if the profile is disabled (positive number) */ + if (ret) + return ret; + + iwl_mei_set_power_limit(per_chain); + + IWL_DEBUG_RADIO(mvm, "Sending REDUCE_TX_POWER_CMD per chain\n"); + return iwl_mvm_send_cmd_pdu(mvm, cmd_id, 0, len, &cmd); +} + +int iwl_mvm_get_sar_geo_profile(struct iwl_mvm *mvm) +{ + union iwl_geo_tx_power_profiles_cmd geo_tx_cmd; + struct iwl_geo_tx_power_profiles_resp *resp; + u16 len; + int ret; + struct iwl_host_cmd cmd = { + .id = WIDE_ID(PHY_OPS_GROUP, PER_CHAIN_LIMIT_OFFSET_CMD), + .flags = CMD_WANT_SKB, + .data = { &geo_tx_cmd }, + }; + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd.id, + IWL_FW_CMD_VER_UNKNOWN); + + /* the ops field is at the same spot for all versions, so set in v1 */ + geo_tx_cmd.v1.ops = + cpu_to_le32(IWL_PER_CHAIN_OFFSET_GET_CURRENT_TABLE); + + if (cmd_ver == 5) + len = sizeof(geo_tx_cmd.v5); + else if (cmd_ver == 4) + len = sizeof(geo_tx_cmd.v4); + else if (cmd_ver == 3) + len = sizeof(geo_tx_cmd.v3); + else if (fw_has_api(&mvm->fwrt.fw->ucode_capa, + IWL_UCODE_TLV_API_SAR_TABLE_VER)) + len = sizeof(geo_tx_cmd.v2); + else + len = sizeof(geo_tx_cmd.v1); + + if (!iwl_sar_geo_support(&mvm->fwrt)) + return -EOPNOTSUPP; + + cmd.len[0] = len; + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (ret) { + IWL_ERR(mvm, "Failed to get geographic profile info %d\n", ret); + return ret; + } + + resp = (void *)cmd.resp_pkt->data; + ret = le32_to_cpu(resp->profile_idx); + + if (WARN_ON(ret > ACPI_NUM_GEO_PROFILES_REV3)) + ret = -EIO; + + iwl_free_resp(&cmd); + return ret; +} + +static int iwl_mvm_sar_geo_init(struct iwl_mvm *mvm) +{ + u32 cmd_id = WIDE_ID(PHY_OPS_GROUP, PER_CHAIN_LIMIT_OFFSET_CMD); + union iwl_geo_tx_power_profiles_cmd cmd; + u16 len; + u32 n_bands; + u32 n_profiles; + u32 sk = 0; + int ret; + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, + IWL_FW_CMD_VER_UNKNOWN); + + BUILD_BUG_ON(offsetof(struct iwl_geo_tx_power_profiles_cmd_v1, ops) != + offsetof(struct iwl_geo_tx_power_profiles_cmd_v2, ops) || + offsetof(struct iwl_geo_tx_power_profiles_cmd_v2, ops) != + offsetof(struct iwl_geo_tx_power_profiles_cmd_v3, ops) || + offsetof(struct iwl_geo_tx_power_profiles_cmd_v3, ops) != + offsetof(struct iwl_geo_tx_power_profiles_cmd_v4, ops) || + offsetof(struct iwl_geo_tx_power_profiles_cmd_v4, ops) != + offsetof(struct iwl_geo_tx_power_profiles_cmd_v5, ops)); + + /* the ops field is at the same spot for all versions, so set in v1 */ + cmd.v1.ops = cpu_to_le32(IWL_PER_CHAIN_OFFSET_SET_TABLES); + + if (cmd_ver == 5) { + len = sizeof(cmd.v5); + n_bands = ARRAY_SIZE(cmd.v5.table[0]); + n_profiles = ACPI_NUM_GEO_PROFILES_REV3; + } else if (cmd_ver == 4) { + len = sizeof(cmd.v4); + n_bands = ARRAY_SIZE(cmd.v4.table[0]); + n_profiles = ACPI_NUM_GEO_PROFILES_REV3; + } else if (cmd_ver == 3) { + len = sizeof(cmd.v3); + n_bands = ARRAY_SIZE(cmd.v3.table[0]); + n_profiles = ACPI_NUM_GEO_PROFILES; + } else if (fw_has_api(&mvm->fwrt.fw->ucode_capa, + IWL_UCODE_TLV_API_SAR_TABLE_VER)) { + len = sizeof(cmd.v2); + n_bands = ARRAY_SIZE(cmd.v2.table[0]); + n_profiles = ACPI_NUM_GEO_PROFILES; + } else { + len = sizeof(cmd.v1); + n_bands = ARRAY_SIZE(cmd.v1.table[0]); + n_profiles = ACPI_NUM_GEO_PROFILES; + } + + BUILD_BUG_ON(offsetof(struct iwl_geo_tx_power_profiles_cmd_v1, table) != + offsetof(struct iwl_geo_tx_power_profiles_cmd_v2, table) || + offsetof(struct iwl_geo_tx_power_profiles_cmd_v2, table) != + offsetof(struct iwl_geo_tx_power_profiles_cmd_v3, table) || + offsetof(struct iwl_geo_tx_power_profiles_cmd_v3, table) != + offsetof(struct iwl_geo_tx_power_profiles_cmd_v4, table) || + offsetof(struct iwl_geo_tx_power_profiles_cmd_v4, table) != + offsetof(struct iwl_geo_tx_power_profiles_cmd_v5, table)); + /* the table is at the same position for all versions, so set use v1 */ + ret = iwl_sar_geo_init(&mvm->fwrt, &cmd.v1.table[0][0], + n_bands, n_profiles); + + /* + * It is a valid scenario to not support SAR, or miss wgds table, + * but in that case there is no need to send the command. + */ + if (ret) + return 0; + + /* Only set to South Korea if the table revision is 1 */ + if (mvm->fwrt.geo_rev == 1) + sk = 1; + + /* + * Set the table_revision to South Korea (1) or not (0). The + * element name is misleading, as it doesn't contain the table + * revision number, but whether the South Korea variation + * should be used. + * This must be done after calling iwl_sar_geo_init(). + */ + if (cmd_ver == 5) + cmd.v5.table_revision = cpu_to_le32(sk); + else if (cmd_ver == 4) + cmd.v4.table_revision = cpu_to_le32(sk); + else if (cmd_ver == 3) + cmd.v3.table_revision = cpu_to_le32(sk); + else if (fw_has_api(&mvm->fwrt.fw->ucode_capa, + IWL_UCODE_TLV_API_SAR_TABLE_VER)) + cmd.v2.table_revision = cpu_to_le32(sk); + + return iwl_mvm_send_cmd_pdu(mvm, cmd_id, 0, len, &cmd); +} + +int iwl_mvm_ppag_send_cmd(struct iwl_mvm *mvm) +{ + union iwl_ppag_table_cmd cmd; + int ret, cmd_size; + + ret = iwl_read_ppag_table(&mvm->fwrt, &cmd, &cmd_size); + /* Not supporting PPAG table is a valid scenario */ + if(ret < 0) + return 0; + + IWL_DEBUG_RADIO(mvm, "Sending PER_PLATFORM_ANT_GAIN_CMD\n"); + ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(PHY_OPS_GROUP, + PER_PLATFORM_ANT_GAIN_CMD), + 0, cmd_size, &cmd); + if (ret < 0) + IWL_ERR(mvm, "failed to send PER_PLATFORM_ANT_GAIN_CMD (%d)\n", + ret); + + return ret; +} + +static int iwl_mvm_ppag_init(struct iwl_mvm *mvm) +{ + /* no need to read the table, done in INIT stage */ + if (!(iwl_acpi_is_ppag_approved(&mvm->fwrt))) + return 0; + + return iwl_mvm_ppag_send_cmd(mvm); +} + +static const struct dmi_system_id dmi_tas_approved_list[] = { + { .ident = "HP", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + }, + }, + { .ident = "SAMSUNG", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD"), + }, + }, + { .ident = "LENOVO", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + }, + }, + { .ident = "DELL", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + }, + }, + + /* keep last */ + {} +}; + +static bool iwl_mvm_add_to_tas_block_list(__le32 *list, __le32 *le_size, unsigned int mcc) +{ + int i; + u32 size = le32_to_cpu(*le_size); + + /* Verify that there is room for another country */ + if (size >= IWL_TAS_BLOCK_LIST_MAX) + return false; + + for (i = 0; i < size; i++) { + if (list[i] == cpu_to_le32(mcc)) + return true; + } + + list[size++] = cpu_to_le32(mcc); + *le_size = cpu_to_le32(size); + return true; +} + +static void iwl_mvm_tas_init(struct iwl_mvm *mvm) +{ + u32 cmd_id = WIDE_ID(REGULATORY_AND_NVM_GROUP, TAS_CONFIG); + int ret; + union iwl_tas_config_cmd cmd = {}; + int cmd_size, fw_ver; + + BUILD_BUG_ON(ARRAY_SIZE(cmd.v3.block_list_array) < + APCI_WTAS_BLACK_LIST_MAX); + + if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_TAS_CFG)) { + IWL_DEBUG_RADIO(mvm, "TAS not enabled in FW\n"); + return; + } + + fw_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, + IWL_FW_CMD_VER_UNKNOWN); + + ret = iwl_acpi_get_tas(&mvm->fwrt, &cmd, fw_ver); + if (ret < 0) { + IWL_DEBUG_RADIO(mvm, + "TAS table invalid or unavailable. (%d)\n", + ret); + return; + } + + if (ret == 0) + return; + + if (!dmi_check_system(dmi_tas_approved_list)) { + IWL_DEBUG_RADIO(mvm, + "System vendor '%s' is not in the approved list, disabling TAS in US and Canada.\n", + dmi_get_system_info(DMI_SYS_VENDOR)); + if ((!iwl_mvm_add_to_tas_block_list(cmd.v4.block_list_array, + &cmd.v4.block_list_size, + IWL_TAS_US_MCC)) || + (!iwl_mvm_add_to_tas_block_list(cmd.v4.block_list_array, + &cmd.v4.block_list_size, + IWL_TAS_CANADA_MCC))) { + IWL_DEBUG_RADIO(mvm, + "Unable to add US/Canada to TAS block list, disabling TAS\n"); + return; + } + } + + /* v4 is the same size as v3, so no need to differentiate here */ + cmd_size = fw_ver < 3 ? + sizeof(struct iwl_tas_config_cmd_v2) : + sizeof(struct iwl_tas_config_cmd_v3); + + ret = iwl_mvm_send_cmd_pdu(mvm, cmd_id, 0, cmd_size, &cmd); + if (ret < 0) + IWL_DEBUG_RADIO(mvm, "failed to send TAS_CONFIG (%d)\n", ret); +} + +static u8 iwl_mvm_eval_dsm_rfi(struct iwl_mvm *mvm) +{ + u8 value; + int ret = iwl_acpi_get_dsm_u8(mvm->fwrt.dev, 0, DSM_RFI_FUNC_ENABLE, + &iwl_rfi_guid, &value); + + if (ret < 0) { + IWL_DEBUG_RADIO(mvm, "Failed to get DSM RFI, ret=%d\n", ret); + + } else if (value >= DSM_VALUE_RFI_MAX) { + IWL_DEBUG_RADIO(mvm, "DSM RFI got invalid value, ret=%d\n", + value); + + } else if (value == DSM_VALUE_RFI_ENABLE) { + IWL_DEBUG_RADIO(mvm, "DSM RFI is evaluated to enable\n"); + return DSM_VALUE_RFI_ENABLE; + } + + IWL_DEBUG_RADIO(mvm, "DSM RFI is disabled\n"); + + /* default behaviour is disabled */ + return DSM_VALUE_RFI_DISABLE; +} + +static void iwl_mvm_lari_cfg(struct iwl_mvm *mvm) +{ + int ret; + u32 value; + struct iwl_lari_config_change_cmd_v6 cmd = {}; + + cmd.config_bitmap = iwl_acpi_get_lari_config_bitmap(&mvm->fwrt); + + ret = iwl_acpi_get_dsm_u32(mvm->fwrt.dev, 0, DSM_FUNC_11AX_ENABLEMENT, + &iwl_guid, &value); + if (!ret) + cmd.oem_11ax_allow_bitmap = cpu_to_le32(value); + + ret = iwl_acpi_get_dsm_u32(mvm->fwrt.dev, 0, + DSM_FUNC_ENABLE_UNII4_CHAN, + &iwl_guid, &value); + if (!ret) + cmd.oem_unii4_allow_bitmap = cpu_to_le32(value); + + ret = iwl_acpi_get_dsm_u32(mvm->fwrt.dev, 0, + DSM_FUNC_ACTIVATE_CHANNEL, + &iwl_guid, &value); + if (!ret) + cmd.chan_state_active_bitmap = cpu_to_le32(value); + + ret = iwl_acpi_get_dsm_u32(mvm->fwrt.dev, 0, + DSM_FUNC_ENABLE_6E, + &iwl_guid, &value); + if (!ret) + cmd.oem_uhb_allow_bitmap = cpu_to_le32(value); + + ret = iwl_acpi_get_dsm_u32(mvm->fwrt.dev, 0, + DSM_FUNC_FORCE_DISABLE_CHANNELS, + &iwl_guid, &value); + if (!ret) + cmd.force_disable_channels_bitmap = cpu_to_le32(value); + + if (cmd.config_bitmap || + cmd.oem_uhb_allow_bitmap || + cmd.oem_11ax_allow_bitmap || + cmd.oem_unii4_allow_bitmap || + cmd.chan_state_active_bitmap || + cmd.force_disable_channels_bitmap) { + size_t cmd_size; + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, + WIDE_ID(REGULATORY_AND_NVM_GROUP, + LARI_CONFIG_CHANGE), + 1); + switch (cmd_ver) { + case 6: + cmd_size = sizeof(struct iwl_lari_config_change_cmd_v6); + break; + case 5: + cmd_size = sizeof(struct iwl_lari_config_change_cmd_v5); + break; + case 4: + cmd_size = sizeof(struct iwl_lari_config_change_cmd_v4); + break; + case 3: + cmd_size = sizeof(struct iwl_lari_config_change_cmd_v3); + break; + case 2: + cmd_size = sizeof(struct iwl_lari_config_change_cmd_v2); + break; + default: + cmd_size = sizeof(struct iwl_lari_config_change_cmd_v1); + break; + } + + IWL_DEBUG_RADIO(mvm, + "sending LARI_CONFIG_CHANGE, config_bitmap=0x%x, oem_11ax_allow_bitmap=0x%x\n", + le32_to_cpu(cmd.config_bitmap), + le32_to_cpu(cmd.oem_11ax_allow_bitmap)); + IWL_DEBUG_RADIO(mvm, + "sending LARI_CONFIG_CHANGE, oem_unii4_allow_bitmap=0x%x, chan_state_active_bitmap=0x%x, cmd_ver=%d\n", + le32_to_cpu(cmd.oem_unii4_allow_bitmap), + le32_to_cpu(cmd.chan_state_active_bitmap), + cmd_ver); + IWL_DEBUG_RADIO(mvm, + "sending LARI_CONFIG_CHANGE, oem_uhb_allow_bitmap=0x%x, force_disable_channels_bitmap=0x%x\n", + le32_to_cpu(cmd.oem_uhb_allow_bitmap), + le32_to_cpu(cmd.force_disable_channels_bitmap)); + ret = iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(REGULATORY_AND_NVM_GROUP, + LARI_CONFIG_CHANGE), + 0, cmd_size, &cmd); + if (ret < 0) + IWL_DEBUG_RADIO(mvm, + "Failed to send LARI_CONFIG_CHANGE (%d)\n", + ret); + } +} + +void iwl_mvm_get_acpi_tables(struct iwl_mvm *mvm) +{ + int ret; + + /* read PPAG table */ + ret = iwl_acpi_get_ppag_table(&mvm->fwrt); + if (ret < 0) { + IWL_DEBUG_RADIO(mvm, + "PPAG BIOS table invalid or unavailable. (%d)\n", + ret); + } + + /* read SAR tables */ + ret = iwl_sar_get_wrds_table(&mvm->fwrt); + if (ret < 0) { + IWL_DEBUG_RADIO(mvm, + "WRDS SAR BIOS table invalid or unavailable. (%d)\n", + ret); + /* + * If not available, don't fail and don't bother with EWRD and + * WGDS */ + + if (!iwl_sar_get_wgds_table(&mvm->fwrt)) { + /* + * If basic SAR is not available, we check for WGDS, + * which should *not* be available either. If it is + * available, issue an error, because we can't use SAR + * Geo without basic SAR. + */ + IWL_ERR(mvm, "BIOS contains WGDS but no WRDS\n"); + } + + } else { + ret = iwl_sar_get_ewrd_table(&mvm->fwrt); + /* if EWRD is not available, we can still use + * WRDS, so don't fail */ + if (ret < 0) + IWL_DEBUG_RADIO(mvm, + "EWRD SAR BIOS table invalid or unavailable. (%d)\n", + ret); + + /* read geo SAR table */ + if (iwl_sar_geo_support(&mvm->fwrt)) { + ret = iwl_sar_get_wgds_table(&mvm->fwrt); + if (ret < 0) + IWL_DEBUG_RADIO(mvm, + "Geo SAR BIOS table invalid or unavailable. (%d)\n", + ret); + /* we don't fail if the table is not available */ + } + } +} +#else /* CONFIG_ACPI */ + +inline int iwl_mvm_sar_select_profile(struct iwl_mvm *mvm, + int prof_a, int prof_b) +{ + return 1; +} + +inline int iwl_mvm_get_sar_geo_profile(struct iwl_mvm *mvm) +{ + return -ENOENT; +} + +static int iwl_mvm_sar_geo_init(struct iwl_mvm *mvm) +{ + return 0; +} + +int iwl_mvm_ppag_send_cmd(struct iwl_mvm *mvm) +{ + return -ENOENT; +} + +static int iwl_mvm_ppag_init(struct iwl_mvm *mvm) +{ + return 0; +} + +static void iwl_mvm_tas_init(struct iwl_mvm *mvm) +{ +} + +static void iwl_mvm_lari_cfg(struct iwl_mvm *mvm) +{ +} + +static u8 iwl_mvm_eval_dsm_rfi(struct iwl_mvm *mvm) +{ + return DSM_VALUE_RFI_DISABLE; +} + +void iwl_mvm_get_acpi_tables(struct iwl_mvm *mvm) +{ +} + +#endif /* CONFIG_ACPI */ + +void iwl_mvm_send_recovery_cmd(struct iwl_mvm *mvm, u32 flags) +{ + u32 error_log_size = mvm->fw->ucode_capa.error_log_size; + int ret; + u32 resp; + + struct iwl_fw_error_recovery_cmd recovery_cmd = { + .flags = cpu_to_le32(flags), + .buf_size = 0, + }; + struct iwl_host_cmd host_cmd = { + .id = WIDE_ID(SYSTEM_GROUP, FW_ERROR_RECOVERY_CMD), + .flags = CMD_WANT_SKB, + .data = {&recovery_cmd, }, + .len = {sizeof(recovery_cmd), }, + }; + + /* no error log was defined in TLV */ + if (!error_log_size) + return; + + if (flags & ERROR_RECOVERY_UPDATE_DB) { + /* no buf was allocated while HW reset */ + if (!mvm->error_recovery_buf) + return; + + host_cmd.data[1] = mvm->error_recovery_buf; + host_cmd.len[1] = error_log_size; + host_cmd.dataflags[1] = IWL_HCMD_DFL_NOCOPY; + recovery_cmd.buf_size = cpu_to_le32(error_log_size); + } + + ret = iwl_mvm_send_cmd(mvm, &host_cmd); + kfree(mvm->error_recovery_buf); + mvm->error_recovery_buf = NULL; + + if (ret) { + IWL_ERR(mvm, "Failed to send recovery cmd %d\n", ret); + return; + } + + /* skb respond is only relevant in ERROR_RECOVERY_UPDATE_DB */ + if (flags & ERROR_RECOVERY_UPDATE_DB) { + resp = le32_to_cpu(*(__le32 *)host_cmd.resp_pkt->data); + if (resp) + IWL_ERR(mvm, + "Failed to send recovery cmd blob was invalid %d\n", + resp); + } +} + +static int iwl_mvm_sar_init(struct iwl_mvm *mvm) +{ + return iwl_mvm_sar_select_profile(mvm, 1, 1); +} + +static int iwl_mvm_load_rt_fw(struct iwl_mvm *mvm) +{ + int ret; + + if (iwl_mvm_has_unified_ucode(mvm)) + return iwl_run_unified_mvm_ucode(mvm); + + ret = iwl_run_init_mvm_ucode(mvm); + + if (ret) { + IWL_ERR(mvm, "Failed to run INIT ucode: %d\n", ret); + + if (iwlmvm_mod_params.init_dbg) + return 0; + return ret; + } + + iwl_fw_dbg_stop_sync(&mvm->fwrt); + iwl_trans_stop_device(mvm->trans); + ret = iwl_trans_start_hw(mvm->trans); + if (ret) + return ret; + + mvm->rfkill_safe_init_done = false; + ret = iwl_mvm_load_ucode_wait_alive(mvm, IWL_UCODE_REGULAR); + if (ret) + return ret; + + mvm->rfkill_safe_init_done = true; + + iwl_dbg_tlv_time_point(&mvm->fwrt, IWL_FW_INI_TIME_POINT_AFTER_ALIVE, + NULL); + + return iwl_init_paging(&mvm->fwrt, mvm->fwrt.cur_fw_img); +} + +int iwl_mvm_up(struct iwl_mvm *mvm) +{ + int ret, i; + struct ieee80211_channel *chan; + struct cfg80211_chan_def chandef; + struct ieee80211_supported_band *sband = NULL; + + lockdep_assert_held(&mvm->mutex); + + ret = iwl_trans_start_hw(mvm->trans); + if (ret) + return ret; + + ret = iwl_mvm_load_rt_fw(mvm); + if (ret) { + IWL_ERR(mvm, "Failed to start RT ucode: %d\n", ret); + if (ret != -ERFKILL) + iwl_fw_dbg_error_collect(&mvm->fwrt, + FW_DBG_TRIGGER_DRIVER); + goto error; + } + + iwl_get_shared_mem_conf(&mvm->fwrt); + + ret = iwl_mvm_sf_update(mvm, NULL, false); + if (ret) + IWL_ERR(mvm, "Failed to initialize Smart Fifo\n"); + + if (!iwl_trans_dbg_ini_valid(mvm->trans)) { + mvm->fwrt.dump.conf = FW_DBG_INVALID; + /* if we have a destination, assume EARLY START */ + if (mvm->fw->dbg.dest_tlv) + mvm->fwrt.dump.conf = FW_DBG_START_FROM_ALIVE; + iwl_fw_start_dbg_conf(&mvm->fwrt, FW_DBG_START_FROM_ALIVE); + } + + ret = iwl_send_tx_ant_cfg(mvm, iwl_mvm_get_valid_tx_ant(mvm)); + if (ret) + goto error; + + if (!iwl_mvm_has_unified_ucode(mvm)) { + /* Send phy db control command and then phy db calibration */ + ret = iwl_send_phy_db_data(mvm->phy_db); + if (ret) + goto error; + } + + ret = iwl_send_phy_cfg_cmd(mvm); + if (ret) + goto error; + + ret = iwl_mvm_send_bt_init_conf(mvm); + if (ret) + goto error; + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SOC_LATENCY_SUPPORT)) { + ret = iwl_set_soc_latency(&mvm->fwrt); + if (ret) + goto error; + } + + /* Init RSS configuration */ + ret = iwl_configure_rxq(&mvm->fwrt); + if (ret) + goto error; + + if (iwl_mvm_has_new_rx_api(mvm)) { + ret = iwl_send_rss_cfg_cmd(mvm); + if (ret) { + IWL_ERR(mvm, "Failed to configure RSS queues: %d\n", + ret); + goto error; + } + } + + /* init the fw <-> mac80211 STA mapping */ + for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) + RCU_INIT_POINTER(mvm->fw_id_to_mac_id[i], NULL); + + mvm->tdls_cs.peer.sta_id = IWL_MVM_INVALID_STA; + + /* reset quota debouncing buffer - 0xff will yield invalid data */ + memset(&mvm->last_quota_cmd, 0xff, sizeof(mvm->last_quota_cmd)); + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_DQA_SUPPORT)) { + ret = iwl_mvm_send_dqa_cmd(mvm); + if (ret) + goto error; + } + + /* + * Add auxiliary station for scanning. + * Newer versions of this command implies that the fw uses + * internal aux station for all aux activities that don't + * requires a dedicated data queue. + */ + if (iwl_fw_lookup_cmd_ver(mvm->fw, ADD_STA, 0) < 12) { + /* + * In old version the aux station uses mac id like other + * station and not lmac id + */ + ret = iwl_mvm_add_aux_sta(mvm, MAC_INDEX_AUX); + if (ret) + goto error; + } + + /* Add all the PHY contexts */ + i = 0; + while (!sband && i < NUM_NL80211_BANDS) + sband = mvm->hw->wiphy->bands[i++]; + + if (WARN_ON_ONCE(!sband)) { + ret = -ENODEV; + goto error; + } + + chan = &sband->channels[0]; + + cfg80211_chandef_create(&chandef, chan, NL80211_CHAN_NO_HT); + for (i = 0; i < NUM_PHY_CTX; i++) { + /* + * The channel used here isn't relevant as it's + * going to be overwritten in the other flows. + * For now use the first channel we have. + */ + ret = iwl_mvm_phy_ctxt_add(mvm, &mvm->phy_ctxts[i], + &chandef, 1, 1); + if (ret) + goto error; + } + + if (iwl_mvm_is_tt_in_fw(mvm)) { + /* in order to give the responsibility of ct-kill and + * TX backoff to FW we need to send empty temperature reporting + * cmd during init time + */ + iwl_mvm_send_temp_report_ths_cmd(mvm); + } else { + /* Initialize tx backoffs to the minimal possible */ + iwl_mvm_tt_tx_backoff(mvm, 0); + } + +#ifdef CONFIG_THERMAL + /* TODO: read the budget from BIOS / Platform NVM */ + + /* + * In case there is no budget from BIOS / Platform NVM the default + * budget should be 2000mW (cooling state 0). + */ + if (iwl_mvm_is_ctdp_supported(mvm)) { + ret = iwl_mvm_ctdp_command(mvm, CTDP_CMD_OPERATION_START, + mvm->cooling_dev.cur_state); + if (ret) + goto error; + } +#endif + + if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_SET_LTR_GEN2)) + WARN_ON(iwl_mvm_config_ltr(mvm)); + + ret = iwl_mvm_power_update_device(mvm); + if (ret) + goto error; + + iwl_mvm_lari_cfg(mvm); + /* + * RTNL is not taken during Ct-kill, but we don't need to scan/Tx + * anyway, so don't init MCC. + */ + if (!test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status)) { + ret = iwl_mvm_init_mcc(mvm); + if (ret) + goto error; + } + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) { + mvm->scan_type = IWL_SCAN_TYPE_NOT_SET; + mvm->hb_scan_type = IWL_SCAN_TYPE_NOT_SET; + ret = iwl_mvm_config_scan(mvm); + if (ret) + goto error; + } + + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) + iwl_mvm_send_recovery_cmd(mvm, ERROR_RECOVERY_UPDATE_DB); + + if (iwl_acpi_get_eckv(mvm->dev, &mvm->ext_clock_valid)) + IWL_DEBUG_INFO(mvm, "ECKV table doesn't exist in BIOS\n"); + + ret = iwl_mvm_ppag_init(mvm); + if (ret) + goto error; + + ret = iwl_mvm_sar_init(mvm); + if (ret == 0) + ret = iwl_mvm_sar_geo_init(mvm); + if (ret < 0) + goto error; + + ret = iwl_mvm_sgom_init(mvm); + if (ret) + goto error; + + iwl_mvm_tas_init(mvm); + iwl_mvm_leds_sync(mvm); + + iwl_mvm_ftm_initiator_smooth_config(mvm); + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_RFIM_SUPPORT)) { + if (iwl_mvm_eval_dsm_rfi(mvm) == DSM_VALUE_RFI_ENABLE) + iwl_rfi_send_config_cmd(mvm, NULL); + } + + iwl_mvm_mei_device_state(mvm, true); + + IWL_DEBUG_INFO(mvm, "RT uCode started.\n"); + return 0; + error: + if (!iwlmvm_mod_params.init_dbg || !ret) + iwl_mvm_stop_device(mvm); + return ret; +} + +int iwl_mvm_load_d3_fw(struct iwl_mvm *mvm) +{ + int ret, i; + + lockdep_assert_held(&mvm->mutex); + + ret = iwl_trans_start_hw(mvm->trans); + if (ret) + return ret; + + ret = iwl_mvm_load_ucode_wait_alive(mvm, IWL_UCODE_WOWLAN); + if (ret) { + IWL_ERR(mvm, "Failed to start WoWLAN firmware: %d\n", ret); + goto error; + } + + ret = iwl_send_tx_ant_cfg(mvm, iwl_mvm_get_valid_tx_ant(mvm)); + if (ret) + goto error; + + /* Send phy db control command and then phy db calibration*/ + ret = iwl_send_phy_db_data(mvm->phy_db); + if (ret) + goto error; + + ret = iwl_send_phy_cfg_cmd(mvm); + if (ret) + goto error; + + /* init the fw <-> mac80211 STA mapping */ + for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) + RCU_INIT_POINTER(mvm->fw_id_to_mac_id[i], NULL); + + if (iwl_fw_lookup_cmd_ver(mvm->fw, ADD_STA, 0) < 12) { + /* + * Add auxiliary station for scanning. + * Newer versions of this command implies that the fw uses + * internal aux station for all aux activities that don't + * requires a dedicated data queue. + * In old version the aux station uses mac id like other + * station and not lmac id + */ + ret = iwl_mvm_add_aux_sta(mvm, MAC_INDEX_AUX); + if (ret) + goto error; + } + + return 0; + error: + iwl_mvm_stop_device(mvm); + return ret; +} + +void iwl_mvm_rx_mfuart_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mfuart_load_notif *mfuart_notif = (void *)pkt->data; + + IWL_DEBUG_INFO(mvm, + "MFUART: installed ver: 0x%08x, external ver: 0x%08x, status: 0x%08x, duration: 0x%08x\n", + le32_to_cpu(mfuart_notif->installed_ver), + le32_to_cpu(mfuart_notif->external_ver), + le32_to_cpu(mfuart_notif->status), + le32_to_cpu(mfuart_notif->duration)); + + if (iwl_rx_packet_payload_len(pkt) == sizeof(*mfuart_notif)) + IWL_DEBUG_INFO(mvm, + "MFUART: image size: 0x%08x\n", + le32_to_cpu(mfuart_notif->image_size)); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/led.c b/drivers/net/wireless/intel/iwlwifi/mvm/led.c new file mode 100644 index 000000000..1ea7c4425 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/led.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2019 Intel Corporation + * Copyright (C) 2017 Intel Deutschland GmbH + */ +#include <linux/leds.h> +#include "iwl-io.h" +#include "iwl-csr.h" +#include "mvm.h" + +static void iwl_mvm_send_led_fw_cmd(struct iwl_mvm *mvm, bool on) +{ + struct iwl_led_cmd led_cmd = { + .status = cpu_to_le32(on), + }; + struct iwl_host_cmd cmd = { + .id = WIDE_ID(LONG_GROUP, LEDS_CMD), + .len = { sizeof(led_cmd), }, + .data = { &led_cmd, }, + .flags = CMD_ASYNC, + }; + int err; + + if (!iwl_mvm_firmware_running(mvm)) + return; + + err = iwl_mvm_send_cmd(mvm, &cmd); + + if (err) + IWL_WARN(mvm, "LED command failed: %d\n", err); +} + +static void iwl_mvm_led_set(struct iwl_mvm *mvm, bool on) +{ + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_LED_CMD_SUPPORT)) { + iwl_mvm_send_led_fw_cmd(mvm, on); + return; + } + + iwl_write32(mvm->trans, CSR_LED_REG, + on ? CSR_LED_REG_TURN_ON : CSR_LED_REG_TURN_OFF); +} + +static void iwl_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct iwl_mvm *mvm = container_of(led_cdev, struct iwl_mvm, led); + + iwl_mvm_led_set(mvm, brightness > 0); +} + +int iwl_mvm_leds_init(struct iwl_mvm *mvm) +{ + int mode = iwlwifi_mod_params.led_mode; + int ret; + + switch (mode) { + case IWL_LED_BLINK: + IWL_ERR(mvm, "Blink led mode not supported, used default\n"); + fallthrough; + case IWL_LED_DEFAULT: + case IWL_LED_RF_STATE: + mode = IWL_LED_RF_STATE; + break; + case IWL_LED_DISABLE: + IWL_INFO(mvm, "Led disabled\n"); + return 0; + default: + return -EINVAL; + } + + mvm->led.name = kasprintf(GFP_KERNEL, "%s-led", + wiphy_name(mvm->hw->wiphy)); + if (!mvm->led.name) + return -ENOMEM; + + mvm->led.brightness_set = iwl_led_brightness_set; + mvm->led.max_brightness = 1; + + if (mode == IWL_LED_RF_STATE) + mvm->led.default_trigger = + ieee80211_get_radio_led_name(mvm->hw); + + ret = led_classdev_register(mvm->trans->dev, &mvm->led); + if (ret) { + kfree(mvm->led.name); + IWL_INFO(mvm, "Failed to enable led\n"); + return ret; + } + + mvm->init_status |= IWL_MVM_INIT_STATUS_LEDS_INIT_COMPLETE; + return 0; +} + +void iwl_mvm_leds_sync(struct iwl_mvm *mvm) +{ + if (!(mvm->init_status & IWL_MVM_INIT_STATUS_LEDS_INIT_COMPLETE)) + return; + + /* + * if we control through the register, we're doing it + * even when the firmware isn't up, so no need to sync + */ + if (mvm->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_8000) + return; + + iwl_mvm_led_set(mvm, mvm->led.brightness > 0); +} + +void iwl_mvm_leds_exit(struct iwl_mvm *mvm) +{ + if (!(mvm->init_status & IWL_MVM_INIT_STATUS_LEDS_INIT_COMPLETE)) + return; + + led_classdev_unregister(&mvm->led); + kfree(mvm->led.name); + mvm->init_status &= ~IWL_MVM_INIT_STATUS_LEDS_INIT_COMPLETE; +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac-ctxt.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac-ctxt.c new file mode 100644 index 000000000..de0c545d5 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac-ctxt.c @@ -0,0 +1,1655 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#include <linux/etherdevice.h> +#include <linux/crc32.h> +#include <net/mac80211.h> +#include "iwl-io.h" +#include "iwl-prph.h" +#include "fw-api.h" +#include "mvm.h" +#include "time-event.h" + +const u8 iwl_mvm_ac_to_tx_fifo[] = { + IWL_MVM_TX_FIFO_VO, + IWL_MVM_TX_FIFO_VI, + IWL_MVM_TX_FIFO_BE, + IWL_MVM_TX_FIFO_BK, +}; + +const u8 iwl_mvm_ac_to_gen2_tx_fifo[] = { + IWL_GEN2_EDCA_TX_FIFO_VO, + IWL_GEN2_EDCA_TX_FIFO_VI, + IWL_GEN2_EDCA_TX_FIFO_BE, + IWL_GEN2_EDCA_TX_FIFO_BK, + IWL_GEN2_TRIG_TX_FIFO_VO, + IWL_GEN2_TRIG_TX_FIFO_VI, + IWL_GEN2_TRIG_TX_FIFO_BE, + IWL_GEN2_TRIG_TX_FIFO_BK, +}; + +struct iwl_mvm_mac_iface_iterator_data { + struct iwl_mvm *mvm; + struct ieee80211_vif *vif; + unsigned long available_mac_ids[BITS_TO_LONGS(NUM_MAC_INDEX_DRIVER)]; + unsigned long available_tsf_ids[BITS_TO_LONGS(NUM_TSF_IDS)]; + enum iwl_tsf_id preferred_tsf; + bool found_vif; +}; + +static void iwl_mvm_mac_tsf_id_iter(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_mac_iface_iterator_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u16 min_bi; + + /* Skip the interface for which we are trying to assign a tsf_id */ + if (vif == data->vif) + return; + + /* + * The TSF is a hardware/firmware resource, there are 4 and + * the driver should assign and free them as needed. However, + * there are cases where 2 MACs should share the same TSF ID + * for the purpose of clock sync, an optimization to avoid + * clock drift causing overlapping TBTTs/DTIMs for a GO and + * client in the system. + * + * The firmware will decide according to the MAC type which + * will be the leader and follower. Clients that need to sync + * with a remote station will be the leader, and an AP or GO + * will be the follower. + * + * Depending on the new interface type it can be following + * or become the leader of an existing interface. + */ + switch (data->vif->type) { + case NL80211_IFTYPE_STATION: + /* + * The new interface is a client, so if the one we're iterating + * is an AP, and the beacon interval of the AP is a multiple or + * divisor of the beacon interval of the client, the same TSF + * should be used to avoid drift between the new client and + * existing AP. The existing AP will get drift updates from the + * new client context in this case. + */ + if (vif->type != NL80211_IFTYPE_AP || + data->preferred_tsf != NUM_TSF_IDS || + !test_bit(mvmvif->tsf_id, data->available_tsf_ids)) + break; + + min_bi = min(data->vif->bss_conf.beacon_int, + vif->bss_conf.beacon_int); + + if (!min_bi) + break; + + if ((data->vif->bss_conf.beacon_int - + vif->bss_conf.beacon_int) % min_bi == 0) { + data->preferred_tsf = mvmvif->tsf_id; + return; + } + break; + + case NL80211_IFTYPE_AP: + /* + * The new interface is AP/GO, so if its beacon interval is a + * multiple or a divisor of the beacon interval of an existing + * interface, it should get drift updates from an existing + * client or use the same TSF as an existing GO. There's no + * drift between TSFs internally but if they used different + * TSFs then a new client MAC could update one of them and + * cause drift that way. + */ + if ((vif->type != NL80211_IFTYPE_AP && + vif->type != NL80211_IFTYPE_STATION) || + data->preferred_tsf != NUM_TSF_IDS || + !test_bit(mvmvif->tsf_id, data->available_tsf_ids)) + break; + + min_bi = min(data->vif->bss_conf.beacon_int, + vif->bss_conf.beacon_int); + + if (!min_bi) + break; + + if ((data->vif->bss_conf.beacon_int - + vif->bss_conf.beacon_int) % min_bi == 0) { + data->preferred_tsf = mvmvif->tsf_id; + return; + } + break; + default: + /* + * For all other interface types there's no need to + * take drift into account. Either they're exclusive + * like IBSS and monitor, or we don't care much about + * their TSF (like P2P Device), but we won't be able + * to share the TSF resource. + */ + break; + } + + /* + * Unless we exited above, we can't share the TSF resource + * that the virtual interface we're iterating over is using + * with the new one, so clear the available bit and if this + * was the preferred one, reset that as well. + */ + __clear_bit(mvmvif->tsf_id, data->available_tsf_ids); + + if (data->preferred_tsf == mvmvif->tsf_id) + data->preferred_tsf = NUM_TSF_IDS; +} + +static void iwl_mvm_mac_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_mac_iface_iterator_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + /* Iterator may already find the interface being added -- skip it */ + if (vif == data->vif) { + data->found_vif = true; + return; + } + + /* Mark MAC IDs as used by clearing the available bit, and + * (below) mark TSFs as used if their existing use is not + * compatible with the new interface type. + * No locking or atomic bit operations are needed since the + * data is on the stack of the caller function. + */ + __clear_bit(mvmvif->id, data->available_mac_ids); + + /* find a suitable tsf_id */ + iwl_mvm_mac_tsf_id_iter(_data, mac, vif); +} + +void iwl_mvm_mac_ctxt_recalc_tsf_id(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_mac_iface_iterator_data data = { + .mvm = mvm, + .vif = vif, + .available_tsf_ids = { (1 << NUM_TSF_IDS) - 1 }, + /* no preference yet */ + .preferred_tsf = NUM_TSF_IDS, + }; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + iwl_mvm_mac_tsf_id_iter, &data); + + if (data.preferred_tsf != NUM_TSF_IDS) + mvmvif->tsf_id = data.preferred_tsf; + else if (!test_bit(mvmvif->tsf_id, data.available_tsf_ids)) + mvmvif->tsf_id = find_first_bit(data.available_tsf_ids, + NUM_TSF_IDS); +} + +int iwl_mvm_mac_ctxt_init(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_mac_iface_iterator_data data = { + .mvm = mvm, + .vif = vif, + .available_mac_ids = { (1 << NUM_MAC_INDEX_DRIVER) - 1 }, + .available_tsf_ids = { (1 << NUM_TSF_IDS) - 1 }, + /* no preference yet */ + .preferred_tsf = NUM_TSF_IDS, + .found_vif = false, + }; + int ret, i; + + lockdep_assert_held(&mvm->mutex); + + /* + * Allocate a MAC ID and a TSF for this MAC, along with the queues + * and other resources. + */ + + /* + * Before the iterator, we start with all MAC IDs and TSFs available. + * + * During iteration, all MAC IDs are cleared that are in use by other + * virtual interfaces, and all TSF IDs are cleared that can't be used + * by this new virtual interface because they're used by an interface + * that can't share it with the new one. + * At the same time, we check if there's a preferred TSF in the case + * that we should share it with another interface. + */ + + /* Currently, MAC ID 0 should be used only for the managed/IBSS vif */ + switch (vif->type) { + case NL80211_IFTYPE_ADHOC: + break; + case NL80211_IFTYPE_STATION: + if (!vif->p2p) + break; + fallthrough; + default: + __clear_bit(0, data.available_mac_ids); + } + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + iwl_mvm_mac_iface_iterator, &data); + + /* + * In the case we're getting here during resume, it's similar to + * firmware restart, and with RESUME_ALL the iterator will find + * the vif being added already. + * We don't want to reassign any IDs in either case since doing + * so would probably assign different IDs (as interfaces aren't + * necessarily added in the same order), but the old IDs were + * preserved anyway, so skip ID assignment for both resume and + * recovery. + */ + if (data.found_vif) + return 0; + + /* Therefore, in recovery, we can't get here */ + if (WARN_ON_ONCE(test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status))) + return -EBUSY; + + mvmvif->id = find_first_bit(data.available_mac_ids, + NUM_MAC_INDEX_DRIVER); + if (mvmvif->id == NUM_MAC_INDEX_DRIVER) { + IWL_ERR(mvm, "Failed to init MAC context - no free ID!\n"); + ret = -EIO; + goto exit_fail; + } + + if (data.preferred_tsf != NUM_TSF_IDS) + mvmvif->tsf_id = data.preferred_tsf; + else + mvmvif->tsf_id = find_first_bit(data.available_tsf_ids, + NUM_TSF_IDS); + if (mvmvif->tsf_id == NUM_TSF_IDS) { + IWL_ERR(mvm, "Failed to init MAC context - no free TSF!\n"); + ret = -EIO; + goto exit_fail; + } + + mvmvif->color = 0; + + INIT_LIST_HEAD(&mvmvif->time_event_data.list); + mvmvif->time_event_data.id = TE_MAX; + + /* No need to allocate data queues to P2P Device MAC and NAN.*/ + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) + return 0; + + /* Allocate the CAB queue for softAP and GO interfaces */ + if (vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_ADHOC) { + /* + * For TVQM this will be overwritten later with the FW assigned + * queue value (when queue is enabled). + */ + mvmvif->cab_queue = IWL_MVM_DQA_GCAST_QUEUE; + } + + mvmvif->bcast_sta.sta_id = IWL_MVM_INVALID_STA; + mvmvif->mcast_sta.sta_id = IWL_MVM_INVALID_STA; + mvmvif->ap_sta_id = IWL_MVM_INVALID_STA; + + for (i = 0; i < NUM_IWL_MVM_SMPS_REQ; i++) + mvmvif->smps_requests[i] = IEEE80211_SMPS_AUTOMATIC; + + return 0; + +exit_fail: + memset(mvmvif, 0, sizeof(struct iwl_mvm_vif)); + return ret; +} + +static void iwl_mvm_ack_rates(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + enum nl80211_band band, + u8 *cck_rates, u8 *ofdm_rates) +{ + struct ieee80211_supported_band *sband; + unsigned long basic = vif->bss_conf.basic_rates; + int lowest_present_ofdm = 100; + int lowest_present_cck = 100; + u8 cck = 0; + u8 ofdm = 0; + int i; + + sband = mvm->hw->wiphy->bands[band]; + + for_each_set_bit(i, &basic, BITS_PER_LONG) { + int hw = sband->bitrates[i].hw_value; + if (hw >= IWL_FIRST_OFDM_RATE) { + ofdm |= BIT(hw - IWL_FIRST_OFDM_RATE); + if (lowest_present_ofdm > hw) + lowest_present_ofdm = hw; + } else { + BUILD_BUG_ON(IWL_FIRST_CCK_RATE != 0); + + cck |= BIT(hw); + if (lowest_present_cck > hw) + lowest_present_cck = hw; + } + } + + /* + * Now we've got the basic rates as bitmaps in the ofdm and cck + * variables. This isn't sufficient though, as there might not + * be all the right rates in the bitmap. E.g. if the only basic + * rates are 5.5 Mbps and 11 Mbps, we still need to add 1 Mbps + * and 6 Mbps because the 802.11-2007 standard says in 9.6: + * + * [...] a STA responding to a received frame shall transmit + * its Control Response frame [...] at the highest rate in the + * BSSBasicRateSet parameter that is less than or equal to the + * rate of the immediately previous frame in the frame exchange + * sequence ([...]) and that is of the same modulation class + * ([...]) as the received frame. If no rate contained in the + * BSSBasicRateSet parameter meets these conditions, then the + * control frame sent in response to a received frame shall be + * transmitted at the highest mandatory rate of the PHY that is + * less than or equal to the rate of the received frame, and + * that is of the same modulation class as the received frame. + * + * As a consequence, we need to add all mandatory rates that are + * lower than all of the basic rates to these bitmaps. + */ + + if (IWL_RATE_24M_INDEX < lowest_present_ofdm) + ofdm |= IWL_RATE_BIT_MSK(24) >> IWL_FIRST_OFDM_RATE; + if (IWL_RATE_12M_INDEX < lowest_present_ofdm) + ofdm |= IWL_RATE_BIT_MSK(12) >> IWL_FIRST_OFDM_RATE; + /* 6M already there or needed so always add */ + ofdm |= IWL_RATE_BIT_MSK(6) >> IWL_FIRST_OFDM_RATE; + + /* + * CCK is a bit more complex with DSSS vs. HR/DSSS vs. ERP. + * Note, however: + * - if no CCK rates are basic, it must be ERP since there must + * be some basic rates at all, so they're OFDM => ERP PHY + * (or we're in 5 GHz, and the cck bitmap will never be used) + * - if 11M is a basic rate, it must be ERP as well, so add 5.5M + * - if 5.5M is basic, 1M and 2M are mandatory + * - if 2M is basic, 1M is mandatory + * - if 1M is basic, that's the only valid ACK rate. + * As a consequence, it's not as complicated as it sounds, just add + * any lower rates to the ACK rate bitmap. + */ + if (IWL_RATE_11M_INDEX < lowest_present_cck) + cck |= IWL_RATE_BIT_MSK(11) >> IWL_FIRST_CCK_RATE; + if (IWL_RATE_5M_INDEX < lowest_present_cck) + cck |= IWL_RATE_BIT_MSK(5) >> IWL_FIRST_CCK_RATE; + if (IWL_RATE_2M_INDEX < lowest_present_cck) + cck |= IWL_RATE_BIT_MSK(2) >> IWL_FIRST_CCK_RATE; + /* 1M already there or needed so always add */ + cck |= IWL_RATE_BIT_MSK(1) >> IWL_FIRST_CCK_RATE; + + *cck_rates = cck; + *ofdm_rates = ofdm; +} + +static void iwl_mvm_mac_ctxt_set_ht_flags(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mac_ctx_cmd *cmd) +{ + /* for both sta and ap, ht_operation_mode hold the protection_mode */ + u8 protection_mode = vif->bss_conf.ht_operation_mode & + IEEE80211_HT_OP_MODE_PROTECTION; + /* The fw does not distinguish between ht and fat */ + u32 ht_flag = MAC_PROT_FLG_HT_PROT | MAC_PROT_FLG_FAT_PROT; + + IWL_DEBUG_RATE(mvm, "protection mode set to %d\n", protection_mode); + /* + * See section 9.23.3.1 of IEEE 80211-2012. + * Nongreenfield HT STAs Present is not supported. + */ + switch (protection_mode) { + case IEEE80211_HT_OP_MODE_PROTECTION_NONE: + break; + case IEEE80211_HT_OP_MODE_PROTECTION_NONMEMBER: + case IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED: + cmd->protection_flags |= cpu_to_le32(ht_flag); + break; + case IEEE80211_HT_OP_MODE_PROTECTION_20MHZ: + /* Protect when channel wider than 20MHz */ + if (vif->bss_conf.chandef.width > NL80211_CHAN_WIDTH_20) + cmd->protection_flags |= cpu_to_le32(ht_flag); + break; + default: + IWL_ERR(mvm, "Illegal protection mode %d\n", + protection_mode); + break; + } +} + +static void iwl_mvm_mac_ctxt_cmd_common(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mac_ctx_cmd *cmd, + const u8 *bssid_override, + u32 action) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_chanctx_conf *chanctx; + bool ht_enabled = !!(vif->bss_conf.ht_operation_mode & + IEEE80211_HT_OP_MODE_PROTECTION); + u8 cck_ack_rates, ofdm_ack_rates; + const u8 *bssid = bssid_override ?: vif->bss_conf.bssid; + int i; + + cmd->id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)); + cmd->action = cpu_to_le32(action); + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + if (vif->p2p) + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_P2P_STA); + else + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_BSS_STA); + break; + case NL80211_IFTYPE_AP: + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_GO); + break; + case NL80211_IFTYPE_MONITOR: + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_LISTENER); + break; + case NL80211_IFTYPE_P2P_DEVICE: + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_P2P_DEVICE); + break; + case NL80211_IFTYPE_ADHOC: + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_IBSS); + break; + default: + WARN_ON_ONCE(1); + } + + cmd->tsf_id = cpu_to_le32(mvmvif->tsf_id); + + memcpy(cmd->node_addr, vif->addr, ETH_ALEN); + + if (bssid) + memcpy(cmd->bssid_addr, bssid, ETH_ALEN); + else + eth_broadcast_addr(cmd->bssid_addr); + + rcu_read_lock(); + chanctx = rcu_dereference(vif->bss_conf.chanctx_conf); + iwl_mvm_ack_rates(mvm, vif, chanctx ? chanctx->def.chan->band + : NL80211_BAND_2GHZ, + &cck_ack_rates, &ofdm_ack_rates); + rcu_read_unlock(); + + cmd->cck_rates = cpu_to_le32((u32)cck_ack_rates); + cmd->ofdm_rates = cpu_to_le32((u32)ofdm_ack_rates); + + cmd->cck_short_preamble = + cpu_to_le32(vif->bss_conf.use_short_preamble ? + MAC_FLG_SHORT_PREAMBLE : 0); + cmd->short_slot = + cpu_to_le32(vif->bss_conf.use_short_slot ? + MAC_FLG_SHORT_SLOT : 0); + + cmd->filter_flags = 0; + + for (i = 0; i < IEEE80211_NUM_ACS; i++) { + u8 txf = iwl_mvm_mac_ac_to_tx_fifo(mvm, i); + u8 ucode_ac = iwl_mvm_mac80211_ac_to_ucode_ac(i); + + cmd->ac[ucode_ac].cw_min = + cpu_to_le16(mvmvif->queue_params[i].cw_min); + cmd->ac[ucode_ac].cw_max = + cpu_to_le16(mvmvif->queue_params[i].cw_max); + cmd->ac[ucode_ac].edca_txop = + cpu_to_le16(mvmvif->queue_params[i].txop * 32); + cmd->ac[ucode_ac].aifsn = mvmvif->queue_params[i].aifs; + cmd->ac[ucode_ac].fifos_mask = BIT(txf); + } + + if (vif->bss_conf.qos) + cmd->qos_flags |= cpu_to_le32(MAC_QOS_FLG_UPDATE_EDCA); + + if (vif->bss_conf.use_cts_prot) + cmd->protection_flags |= cpu_to_le32(MAC_PROT_FLG_TGG_PROTECT); + + IWL_DEBUG_RATE(mvm, "use_cts_prot %d, ht_operation_mode %d\n", + vif->bss_conf.use_cts_prot, + vif->bss_conf.ht_operation_mode); + if (vif->bss_conf.chandef.width != NL80211_CHAN_WIDTH_20_NOHT) + cmd->qos_flags |= cpu_to_le32(MAC_QOS_FLG_TGN); + if (ht_enabled) + iwl_mvm_mac_ctxt_set_ht_flags(mvm, vif, cmd); +} + +static int iwl_mvm_mac_ctxt_send_cmd(struct iwl_mvm *mvm, + struct iwl_mac_ctx_cmd *cmd) +{ + int ret = iwl_mvm_send_cmd_pdu(mvm, MAC_CONTEXT_CMD, 0, + sizeof(*cmd), cmd); + if (ret) + IWL_ERR(mvm, "Failed to send MAC context (action:%d): %d\n", + le32_to_cpu(cmd->action), ret); + return ret; +} + +static int iwl_mvm_mac_ctxt_cmd_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action, bool force_assoc_off, + const u8 *bssid_override) +{ + struct iwl_mac_ctx_cmd cmd = {}; + struct iwl_mac_data_sta *ctxt_sta; + + WARN_ON(vif->type != NL80211_IFTYPE_STATION); + + /* Fill the common data for all mac context types */ + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, bssid_override, action); + + /* + * We always want to hear MCAST frames, if we're not authorized yet, + * we'll drop them. + */ + cmd.filter_flags |= cpu_to_le32(MAC_FILTER_ACCEPT_GRP); + + if (vif->p2p) { + struct ieee80211_p2p_noa_attr *noa = + &vif->bss_conf.p2p_noa_attr; + + cmd.p2p_sta.ctwin = cpu_to_le32(noa->oppps_ctwindow & + IEEE80211_P2P_OPPPS_CTWINDOW_MASK); + ctxt_sta = &cmd.p2p_sta.sta; + } else { + ctxt_sta = &cmd.sta; + } + + /* We need the dtim_period to set the MAC as associated */ + if (vif->cfg.assoc && vif->bss_conf.dtim_period && + !force_assoc_off) { + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u32 dtim_offs; + + /* + * The DTIM count counts down, so when it is N that means N + * more beacon intervals happen until the DTIM TBTT. Therefore + * add this to the current time. If that ends up being in the + * future, the firmware will handle it. + * + * Also note that the system_timestamp (which we get here as + * "sync_device_ts") and TSF timestamp aren't at exactly the + * same offset in the frame -- the TSF is at the first symbol + * of the TSF, the system timestamp is at signal acquisition + * time. This means there's an offset between them of at most + * a few hundred microseconds (24 * 8 bits + PLCP time gives + * 384us in the longest case), this is currently not relevant + * as the firmware wakes up around 2ms before the TBTT. + */ + dtim_offs = vif->bss_conf.sync_dtim_count * + vif->bss_conf.beacon_int; + /* convert TU to usecs */ + dtim_offs *= 1024; + + ctxt_sta->dtim_tsf = + cpu_to_le64(vif->bss_conf.sync_tsf + dtim_offs); + ctxt_sta->dtim_time = + cpu_to_le32(vif->bss_conf.sync_device_ts + dtim_offs); + ctxt_sta->assoc_beacon_arrive_time = + cpu_to_le32(vif->bss_conf.sync_device_ts); + + IWL_DEBUG_INFO(mvm, "DTIM TBTT is 0x%llx/0x%x, offset %d\n", + le64_to_cpu(ctxt_sta->dtim_tsf), + le32_to_cpu(ctxt_sta->dtim_time), + dtim_offs); + + ctxt_sta->is_assoc = cpu_to_le32(1); + + if (!mvmvif->authorized && + fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_COEX_HIGH_PRIO)) + ctxt_sta->data_policy |= + cpu_to_le32(COEX_HIGH_PRIORITY_ENABLE); + } else { + ctxt_sta->is_assoc = cpu_to_le32(0); + + /* Allow beacons to pass through as long as we are not + * associated, or we do not have dtim period information. + */ + cmd.filter_flags |= cpu_to_le32(MAC_FILTER_IN_BEACON); + } + + ctxt_sta->bi = cpu_to_le32(vif->bss_conf.beacon_int); + ctxt_sta->dtim_interval = cpu_to_le32(vif->bss_conf.beacon_int * + vif->bss_conf.dtim_period); + + ctxt_sta->listen_interval = cpu_to_le32(mvm->hw->conf.listen_interval); + ctxt_sta->assoc_id = cpu_to_le32(vif->cfg.aid); + + if (vif->probe_req_reg && vif->cfg.assoc && vif->p2p) + cmd.filter_flags |= cpu_to_le32(MAC_FILTER_IN_PROBE_REQUEST); + + if (vif->bss_conf.he_support && !iwlwifi_mod_params.disable_11ax) { + cmd.filter_flags |= cpu_to_le32(MAC_FILTER_IN_11AX); + if (vif->bss_conf.twt_requester && IWL_MVM_USE_TWT) + ctxt_sta->data_policy |= cpu_to_le32(TWT_SUPPORTED); + if (vif->bss_conf.twt_protected) + ctxt_sta->data_policy |= + cpu_to_le32(PROTECTED_TWT_SUPPORTED); + if (vif->bss_conf.twt_broadcast) + ctxt_sta->data_policy |= + cpu_to_le32(BROADCAST_TWT_SUPPORTED); + } + + + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctxt_cmd_listener(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action) +{ + struct iwl_mac_ctx_cmd cmd = {}; + u32 tfd_queue_msk = BIT(mvm->snif_queue); + int ret; + + WARN_ON(vif->type != NL80211_IFTYPE_MONITOR); + + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, NULL, action); + + cmd.filter_flags = cpu_to_le32(MAC_FILTER_IN_PROMISC | + MAC_FILTER_IN_CONTROL_AND_MGMT | + MAC_FILTER_IN_BEACON | + MAC_FILTER_IN_PROBE_REQUEST | + MAC_FILTER_IN_CRC32 | + MAC_FILTER_ACCEPT_GRP); + ieee80211_hw_set(mvm->hw, RX_INCLUDES_FCS); + + /* Allocate sniffer station */ + ret = iwl_mvm_allocate_int_sta(mvm, &mvm->snif_sta, tfd_queue_msk, + vif->type, IWL_STA_GENERAL_PURPOSE); + if (ret) + return ret; + + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctxt_cmd_ibss(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mac_ctx_cmd cmd = {}; + + WARN_ON(vif->type != NL80211_IFTYPE_ADHOC); + + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, NULL, action); + + cmd.filter_flags = cpu_to_le32(MAC_FILTER_IN_BEACON | + MAC_FILTER_IN_PROBE_REQUEST | + MAC_FILTER_ACCEPT_GRP); + + /* cmd.ibss.beacon_time/cmd.ibss.beacon_tsf are curently ignored */ + cmd.ibss.bi = cpu_to_le32(vif->bss_conf.beacon_int); + + /* TODO: Assumes that the beacon id == mac context id */ + cmd.ibss.beacon_template = cpu_to_le32(mvmvif->id); + + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +struct iwl_mvm_go_iterator_data { + bool go_active; +}; + +static void iwl_mvm_go_iterator(void *_data, u8 *mac, struct ieee80211_vif *vif) +{ + struct iwl_mvm_go_iterator_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif->type == NL80211_IFTYPE_AP && vif->p2p && + mvmvif->ap_ibss_active) + data->go_active = true; +} + +static int iwl_mvm_mac_ctxt_cmd_p2p_device(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action) +{ + struct iwl_mac_ctx_cmd cmd = {}; + struct iwl_mvm_go_iterator_data data = {}; + + WARN_ON(vif->type != NL80211_IFTYPE_P2P_DEVICE); + + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, NULL, action); + + /* Override the filter flags to accept only probe requests */ + cmd.filter_flags = cpu_to_le32(MAC_FILTER_IN_PROBE_REQUEST); + + /* + * This flag should be set to true when the P2P Device is + * discoverable and there is at least another active P2P GO. Settings + * this flag will allow the P2P Device to be discoverable on other + * channels in addition to its listen channel. + * Note that this flag should not be set in other cases as it opens the + * Rx filters on all MAC and increases the number of interrupts. + */ + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + iwl_mvm_go_iterator, &data); + + cmd.p2p_dev.is_disc_extended = cpu_to_le32(data.go_active ? 1 : 0); + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +void iwl_mvm_mac_ctxt_set_tim(struct iwl_mvm *mvm, + __le32 *tim_index, __le32 *tim_size, + u8 *beacon, u32 frame_size) +{ + u32 tim_idx; + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)beacon; + + /* The index is relative to frame start but we start looking at the + * variable-length part of the beacon. */ + tim_idx = mgmt->u.beacon.variable - beacon; + + /* Parse variable-length elements of beacon to find WLAN_EID_TIM */ + while ((tim_idx < (frame_size - 2)) && + (beacon[tim_idx] != WLAN_EID_TIM)) + tim_idx += beacon[tim_idx+1] + 2; + + /* If TIM field was found, set variables */ + if ((tim_idx < (frame_size - 1)) && (beacon[tim_idx] == WLAN_EID_TIM)) { + *tim_index = cpu_to_le32(tim_idx); + *tim_size = cpu_to_le32((u32)beacon[tim_idx + 1]); + } else { + IWL_WARN(mvm, "Unable to find TIM Element in beacon\n"); + } +} + +static u32 iwl_mvm_find_ie_offset(u8 *beacon, u8 eid, u32 frame_size) +{ + struct ieee80211_mgmt *mgmt = (void *)beacon; + const u8 *ie; + + if (WARN_ON_ONCE(frame_size <= (mgmt->u.beacon.variable - beacon))) + return 0; + + frame_size -= mgmt->u.beacon.variable - beacon; + + ie = cfg80211_find_ie(eid, mgmt->u.beacon.variable, frame_size); + if (!ie) + return 0; + + return ie - beacon; +} + +u8 iwl_mvm_mac_ctxt_get_lowest_rate(struct ieee80211_tx_info *info, + struct ieee80211_vif *vif) +{ + u8 rate; + if (info->band == NL80211_BAND_2GHZ && !vif->p2p) + rate = IWL_FIRST_CCK_RATE; + else + rate = IWL_FIRST_OFDM_RATE; + + return rate; +} + +u16 iwl_mvm_mac_ctxt_get_beacon_flags(const struct iwl_fw *fw, u8 rate_idx) +{ + u16 flags = iwl_mvm_mac80211_idx_to_hwrate(fw, rate_idx); + bool is_new_rate = iwl_fw_lookup_cmd_ver(fw, BEACON_TEMPLATE_CMD, 0) > 10; + + if (rate_idx <= IWL_FIRST_CCK_RATE) + flags |= is_new_rate ? IWL_MAC_BEACON_CCK + : IWL_MAC_BEACON_CCK_V1; + + return flags; +} + +static void iwl_mvm_mac_ctxt_set_tx(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct sk_buff *beacon, + struct iwl_tx_cmd *tx) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_tx_info *info; + u8 rate; + u32 tx_flags; + + info = IEEE80211_SKB_CB(beacon); + + /* Set up TX command fields */ + tx->len = cpu_to_le16((u16)beacon->len); + tx->sta_id = mvmvif->bcast_sta.sta_id; + tx->life_time = cpu_to_le32(TX_CMD_LIFE_TIME_INFINITE); + tx_flags = TX_CMD_FLG_SEQ_CTL | TX_CMD_FLG_TSF; + tx_flags |= + iwl_mvm_bt_coex_tx_prio(mvm, (void *)beacon->data, info, 0) << + TX_CMD_FLG_BT_PRIO_POS; + tx->tx_flags = cpu_to_le32(tx_flags); + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_BEACON_ANT_SELECTION)) + iwl_mvm_toggle_tx_ant(mvm, &mvm->mgmt_last_antenna_idx); + + tx->rate_n_flags = + cpu_to_le32(BIT(mvm->mgmt_last_antenna_idx) << + RATE_MCS_ANT_POS); + + rate = iwl_mvm_mac_ctxt_get_lowest_rate(info, vif); + + tx->rate_n_flags |= + cpu_to_le32(iwl_mvm_mac80211_idx_to_hwrate(mvm->fw, rate)); + if (rate == IWL_FIRST_CCK_RATE) + tx->rate_n_flags |= cpu_to_le32(RATE_MCS_CCK_MSK_V1); + +} + +int iwl_mvm_mac_ctxt_send_beacon_cmd(struct iwl_mvm *mvm, + struct sk_buff *beacon, + void *data, int len) +{ + struct iwl_host_cmd cmd = { + .id = BEACON_TEMPLATE_CMD, + .flags = CMD_ASYNC, + }; + + cmd.len[0] = len; + cmd.data[0] = data; + cmd.dataflags[0] = 0; + cmd.len[1] = beacon->len; + cmd.data[1] = beacon->data; + cmd.dataflags[1] = IWL_HCMD_DFL_DUP; + + return iwl_mvm_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctxt_send_beacon_v6(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct sk_buff *beacon) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mac_beacon_cmd_v6 beacon_cmd = {}; + + iwl_mvm_mac_ctxt_set_tx(mvm, vif, beacon, &beacon_cmd.tx); + + beacon_cmd.template_id = cpu_to_le32((u32)mvmvif->id); + + if (vif->type == NL80211_IFTYPE_AP) + iwl_mvm_mac_ctxt_set_tim(mvm, &beacon_cmd.tim_idx, + &beacon_cmd.tim_size, + beacon->data, beacon->len); + + return iwl_mvm_mac_ctxt_send_beacon_cmd(mvm, beacon, &beacon_cmd, + sizeof(beacon_cmd)); +} + +static int iwl_mvm_mac_ctxt_send_beacon_v7(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct sk_buff *beacon) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mac_beacon_cmd_v7 beacon_cmd = {}; + + iwl_mvm_mac_ctxt_set_tx(mvm, vif, beacon, &beacon_cmd.tx); + + beacon_cmd.template_id = cpu_to_le32((u32)mvmvif->id); + + if (vif->type == NL80211_IFTYPE_AP) + iwl_mvm_mac_ctxt_set_tim(mvm, &beacon_cmd.tim_idx, + &beacon_cmd.tim_size, + beacon->data, beacon->len); + + beacon_cmd.csa_offset = + cpu_to_le32(iwl_mvm_find_ie_offset(beacon->data, + WLAN_EID_CHANNEL_SWITCH, + beacon->len)); + beacon_cmd.ecsa_offset = + cpu_to_le32(iwl_mvm_find_ie_offset(beacon->data, + WLAN_EID_EXT_CHANSWITCH_ANN, + beacon->len)); + + return iwl_mvm_mac_ctxt_send_beacon_cmd(mvm, beacon, &beacon_cmd, + sizeof(beacon_cmd)); +} + +static int iwl_mvm_mac_ctxt_send_beacon_v9(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct sk_buff *beacon) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(beacon); + struct iwl_mac_beacon_cmd beacon_cmd = {}; + u8 rate = iwl_mvm_mac_ctxt_get_lowest_rate(info, vif); + u16 flags; + struct ieee80211_chanctx_conf *ctx; + int channel; + flags = iwl_mvm_mac_ctxt_get_beacon_flags(mvm->fw, rate); + + /* Enable FILS on PSC channels only */ + rcu_read_lock(); + ctx = rcu_dereference(vif->bss_conf.chanctx_conf); + channel = ieee80211_frequency_to_channel(ctx->def.chan->center_freq); + WARN_ON(channel == 0); + if (cfg80211_channel_is_psc(ctx->def.chan) && + !IWL_MVM_DISABLE_AP_FILS) { + flags |= iwl_fw_lookup_cmd_ver(mvm->fw, BEACON_TEMPLATE_CMD, + 0) > 10 ? + IWL_MAC_BEACON_FILS : + IWL_MAC_BEACON_FILS_V1; + beacon_cmd.short_ssid = + cpu_to_le32(~crc32_le(~0, vif->cfg.ssid, + vif->cfg.ssid_len)); + } + rcu_read_unlock(); + + beacon_cmd.flags = cpu_to_le16(flags); + beacon_cmd.byte_cnt = cpu_to_le16((u16)beacon->len); + beacon_cmd.template_id = cpu_to_le32((u32)mvmvif->id); + + if (vif->type == NL80211_IFTYPE_AP) + iwl_mvm_mac_ctxt_set_tim(mvm, &beacon_cmd.tim_idx, + &beacon_cmd.tim_size, + beacon->data, beacon->len); + + beacon_cmd.csa_offset = + cpu_to_le32(iwl_mvm_find_ie_offset(beacon->data, + WLAN_EID_CHANNEL_SWITCH, + beacon->len)); + beacon_cmd.ecsa_offset = + cpu_to_le32(iwl_mvm_find_ie_offset(beacon->data, + WLAN_EID_EXT_CHANSWITCH_ANN, + beacon->len)); + + return iwl_mvm_mac_ctxt_send_beacon_cmd(mvm, beacon, &beacon_cmd, + sizeof(beacon_cmd)); +} + +int iwl_mvm_mac_ctxt_send_beacon(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct sk_buff *beacon) +{ + if (WARN_ON(!beacon)) + return -EINVAL; + + if (IWL_MVM_NON_TRANSMITTING_AP) + return 0; + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CSA_AND_TBTT_OFFLOAD)) + return iwl_mvm_mac_ctxt_send_beacon_v6(mvm, vif, beacon); + + if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_NEW_BEACON_TEMPLATE)) + return iwl_mvm_mac_ctxt_send_beacon_v9(mvm, vif, beacon); + + return iwl_mvm_mac_ctxt_send_beacon_v7(mvm, vif, beacon); +} + +/* The beacon template for the AP/GO/IBSS has changed and needs update */ +int iwl_mvm_mac_ctxt_beacon_changed(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct sk_buff *beacon; + int ret; + + WARN_ON(vif->type != NL80211_IFTYPE_AP && + vif->type != NL80211_IFTYPE_ADHOC); + + beacon = ieee80211_beacon_get_template(mvm->hw, vif, NULL, 0); + if (!beacon) + return -ENOMEM; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (mvm->beacon_inject_active) { + dev_kfree_skb(beacon); + return -EBUSY; + } +#endif + + ret = iwl_mvm_mac_ctxt_send_beacon(mvm, vif, beacon); + dev_kfree_skb(beacon); + return ret; +} + +struct iwl_mvm_mac_ap_iterator_data { + struct iwl_mvm *mvm; + struct ieee80211_vif *vif; + u32 beacon_device_ts; + u16 beacon_int; +}; + +/* Find the beacon_device_ts and beacon_int for a managed interface */ +static void iwl_mvm_mac_ap_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_mac_ap_iterator_data *data = _data; + + if (vif->type != NL80211_IFTYPE_STATION || !vif->cfg.assoc) + return; + + /* Station client has higher priority over P2P client*/ + if (vif->p2p && data->beacon_device_ts) + return; + + data->beacon_device_ts = vif->bss_conf.sync_device_ts; + data->beacon_int = vif->bss_conf.beacon_int; +} + +/* + * Fill the specific data for mac context of type AP of P2P GO + */ +static void iwl_mvm_mac_ctxt_cmd_fill_ap(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mac_ctx_cmd *cmd, + struct iwl_mac_data_ap *ctxt_ap, + bool add) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_mac_ap_iterator_data data = { + .mvm = mvm, + .vif = vif, + .beacon_device_ts = 0 + }; + + /* in AP mode, the MCAST FIFO takes the EDCA params from VO */ + cmd->ac[IWL_MVM_TX_FIFO_VO].fifos_mask |= BIT(IWL_MVM_TX_FIFO_MCAST); + + /* + * in AP mode, pass probe requests and beacons from other APs + * (needed for ht protection); when there're no any associated + * station don't ask FW to pass beacons to prevent unnecessary + * wake-ups. + */ + cmd->filter_flags |= cpu_to_le32(MAC_FILTER_IN_PROBE_REQUEST); + if (mvmvif->ap_assoc_sta_count || !mvm->drop_bcn_ap_mode) { + cmd->filter_flags |= cpu_to_le32(MAC_FILTER_IN_BEACON); + IWL_DEBUG_HC(mvm, "Asking FW to pass beacons\n"); + } else { + IWL_DEBUG_HC(mvm, "No need to receive beacons\n"); + } + + ctxt_ap->bi = cpu_to_le32(vif->bss_conf.beacon_int); + ctxt_ap->dtim_interval = cpu_to_le32(vif->bss_conf.beacon_int * + vif->bss_conf.dtim_period); + + if (!fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_STA_TYPE)) + ctxt_ap->mcast_qid = cpu_to_le32(mvmvif->cab_queue); + + /* + * Only set the beacon time when the MAC is being added, when we + * just modify the MAC then we should keep the time -- the firmware + * can otherwise have a "jumping" TBTT. + */ + if (add) { + /* + * If there is a station/P2P client interface which is + * associated, set the AP's TBTT far enough from the station's + * TBTT. Otherwise, set it to the current system time + */ + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + iwl_mvm_mac_ap_iterator, &data); + + if (data.beacon_device_ts) { + u32 rand = prandom_u32_max(64 - 36) + 36; + mvmvif->ap_beacon_time = data.beacon_device_ts + + ieee80211_tu_to_usec(data.beacon_int * rand / + 100); + } else { + mvmvif->ap_beacon_time = iwl_mvm_get_systime(mvm); + } + } + + ctxt_ap->beacon_time = cpu_to_le32(mvmvif->ap_beacon_time); + ctxt_ap->beacon_tsf = 0; /* unused */ + + /* TODO: Assume that the beacon id == mac context id */ + ctxt_ap->beacon_template = cpu_to_le32(mvmvif->id); +} + +static int iwl_mvm_mac_ctxt_cmd_ap(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action) +{ + struct iwl_mac_ctx_cmd cmd = {}; + + WARN_ON(vif->type != NL80211_IFTYPE_AP || vif->p2p); + + /* Fill the common data for all mac context types */ + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, NULL, action); + + /* Fill the data specific for ap mode */ + iwl_mvm_mac_ctxt_cmd_fill_ap(mvm, vif, &cmd, &cmd.ap, + action == FW_CTXT_ACTION_ADD); + + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctxt_cmd_go(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action) +{ + struct iwl_mac_ctx_cmd cmd = {}; + struct ieee80211_p2p_noa_attr *noa = &vif->bss_conf.p2p_noa_attr; + + WARN_ON(vif->type != NL80211_IFTYPE_AP || !vif->p2p); + + /* Fill the common data for all mac context types */ + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, NULL, action); + + /* Fill the data specific for GO mode */ + iwl_mvm_mac_ctxt_cmd_fill_ap(mvm, vif, &cmd, &cmd.go.ap, + action == FW_CTXT_ACTION_ADD); + + cmd.go.ctwin = cpu_to_le32(noa->oppps_ctwindow & + IEEE80211_P2P_OPPPS_CTWINDOW_MASK); + cmd.go.opp_ps_enabled = + cpu_to_le32(!!(noa->oppps_ctwindow & + IEEE80211_P2P_OPPPS_ENABLE_BIT)); + + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctx_send(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + u32 action, bool force_assoc_off, + const u8 *bssid_override) +{ + switch (vif->type) { + case NL80211_IFTYPE_STATION: + return iwl_mvm_mac_ctxt_cmd_sta(mvm, vif, action, + force_assoc_off, + bssid_override); + case NL80211_IFTYPE_AP: + if (!vif->p2p) + return iwl_mvm_mac_ctxt_cmd_ap(mvm, vif, action); + else + return iwl_mvm_mac_ctxt_cmd_go(mvm, vif, action); + case NL80211_IFTYPE_MONITOR: + return iwl_mvm_mac_ctxt_cmd_listener(mvm, vif, action); + case NL80211_IFTYPE_P2P_DEVICE: + return iwl_mvm_mac_ctxt_cmd_p2p_device(mvm, vif, action); + case NL80211_IFTYPE_ADHOC: + return iwl_mvm_mac_ctxt_cmd_ibss(mvm, vif, action); + default: + break; + } + + return -EOPNOTSUPP; +} + +int iwl_mvm_mac_ctxt_add(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + if (WARN_ONCE(mvmvif->uploaded, "Adding active MAC %pM/%d\n", + vif->addr, ieee80211_vif_type_p2p(vif))) + return -EIO; + + ret = iwl_mvm_mac_ctx_send(mvm, vif, FW_CTXT_ACTION_ADD, + true, NULL); + if (ret) + return ret; + + /* will only do anything at resume from D3 time */ + iwl_mvm_set_last_nonqos_seq(mvm, vif); + + mvmvif->uploaded = true; + return 0; +} + +int iwl_mvm_mac_ctxt_changed(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + bool force_assoc_off, const u8 *bssid_override) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (WARN_ONCE(!mvmvif->uploaded, "Changing inactive MAC %pM/%d\n", + vif->addr, ieee80211_vif_type_p2p(vif))) + return -EIO; + + return iwl_mvm_mac_ctx_send(mvm, vif, FW_CTXT_ACTION_MODIFY, + force_assoc_off, bssid_override); +} + +int iwl_mvm_mac_ctxt_remove(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mac_ctx_cmd cmd; + int ret; + + if (WARN_ONCE(!mvmvif->uploaded, "Removing inactive MAC %pM/%d\n", + vif->addr, ieee80211_vif_type_p2p(vif))) + return -EIO; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)); + cmd.action = cpu_to_le32(FW_CTXT_ACTION_REMOVE); + + ret = iwl_mvm_send_cmd_pdu(mvm, MAC_CONTEXT_CMD, 0, + sizeof(cmd), &cmd); + if (ret) { + IWL_ERR(mvm, "Failed to remove MAC context: %d\n", ret); + return ret; + } + + mvmvif->uploaded = false; + + if (vif->type == NL80211_IFTYPE_MONITOR) { + __clear_bit(IEEE80211_HW_RX_INCLUDES_FCS, mvm->hw->flags); + iwl_mvm_dealloc_snif_sta(mvm); + } + + return 0; +} + +static void iwl_mvm_csa_count_down(struct iwl_mvm *mvm, + struct ieee80211_vif *csa_vif, u32 gp2, + bool tx_success) +{ + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(csa_vif); + + /* Don't start to countdown from a failed beacon */ + if (!tx_success && !mvmvif->csa_countdown) + return; + + mvmvif->csa_countdown = true; + + if (!ieee80211_beacon_cntdwn_is_complete(csa_vif)) { + int c = ieee80211_beacon_update_cntdwn(csa_vif); + + iwl_mvm_mac_ctxt_beacon_changed(mvm, csa_vif); + if (csa_vif->p2p && + !iwl_mvm_te_scheduled(&mvmvif->time_event_data) && gp2 && + tx_success) { + u32 rel_time = (c + 1) * + csa_vif->bss_conf.beacon_int - + IWL_MVM_CHANNEL_SWITCH_TIME_GO; + u32 apply_time = gp2 + rel_time * 1024; + + iwl_mvm_schedule_csa_period(mvm, csa_vif, + IWL_MVM_CHANNEL_SWITCH_TIME_GO - + IWL_MVM_CHANNEL_SWITCH_MARGIN, + apply_time); + } + } else if (!iwl_mvm_te_scheduled(&mvmvif->time_event_data)) { + /* we don't have CSA NoA scheduled yet, switch now */ + ieee80211_csa_finish(csa_vif); + RCU_INIT_POINTER(mvm->csa_vif, NULL); + } +} + +void iwl_mvm_rx_beacon_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); + struct iwl_extended_beacon_notif *beacon = (void *)pkt->data; + struct iwl_extended_beacon_notif_v5 *beacon_v5 = (void *)pkt->data; + struct ieee80211_vif *csa_vif; + struct ieee80211_vif *tx_blocked_vif; + struct agg_tx_status *agg_status; + u16 status; + + lockdep_assert_held(&mvm->mutex); + + mvm->ap_last_beacon_gp2 = le32_to_cpu(beacon->gp2); + + if (!iwl_mvm_is_short_beacon_notif_supported(mvm)) { + struct iwl_mvm_tx_resp *beacon_notify_hdr = + &beacon_v5->beacon_notify_hdr; + + if (unlikely(pkt_len < sizeof(*beacon_v5))) + return; + + mvm->ibss_manager = beacon_v5->ibss_mgr_status != 0; + agg_status = iwl_mvm_get_agg_status(mvm, beacon_notify_hdr); + status = le16_to_cpu(agg_status->status) & TX_STATUS_MSK; + IWL_DEBUG_RX(mvm, + "beacon status %#x retries:%d tsf:0x%016llX gp2:0x%X rate:%d\n", + status, beacon_notify_hdr->failure_frame, + le64_to_cpu(beacon->tsf), + mvm->ap_last_beacon_gp2, + le32_to_cpu(beacon_notify_hdr->initial_rate)); + } else { + if (unlikely(pkt_len < sizeof(*beacon))) + return; + + mvm->ibss_manager = beacon->ibss_mgr_status != 0; + status = le32_to_cpu(beacon->status) & TX_STATUS_MSK; + IWL_DEBUG_RX(mvm, + "beacon status %#x tsf:0x%016llX gp2:0x%X\n", + status, le64_to_cpu(beacon->tsf), + mvm->ap_last_beacon_gp2); + } + + csa_vif = rcu_dereference_protected(mvm->csa_vif, + lockdep_is_held(&mvm->mutex)); + if (unlikely(csa_vif && csa_vif->bss_conf.csa_active)) + iwl_mvm_csa_count_down(mvm, csa_vif, mvm->ap_last_beacon_gp2, + (status == TX_STATUS_SUCCESS)); + + tx_blocked_vif = rcu_dereference_protected(mvm->csa_tx_blocked_vif, + lockdep_is_held(&mvm->mutex)); + if (unlikely(tx_blocked_vif)) { + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(tx_blocked_vif); + + /* + * The channel switch is started and we have blocked the + * stations. If this is the first beacon (the timeout wasn't + * set), set the unblock timeout, otherwise countdown + */ + if (!mvm->csa_tx_block_bcn_timeout) + mvm->csa_tx_block_bcn_timeout = + IWL_MVM_CS_UNBLOCK_TX_TIMEOUT; + else + mvm->csa_tx_block_bcn_timeout--; + + /* Check if the timeout is expired, and unblock tx */ + if (mvm->csa_tx_block_bcn_timeout == 0) { + iwl_mvm_modify_all_sta_disable_tx(mvm, mvmvif, false); + RCU_INIT_POINTER(mvm->csa_tx_blocked_vif, NULL); + } + } +} + +void iwl_mvm_rx_missed_beacons_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_missed_beacons_notif *mb = (void *)pkt->data; + struct iwl_fw_dbg_trigger_missed_bcon *bcon_trig; + struct iwl_fw_dbg_trigger_tlv *trigger; + u32 stop_trig_missed_bcon, stop_trig_missed_bcon_since_rx; + u32 rx_missed_bcon, rx_missed_bcon_since_rx; + struct ieee80211_vif *vif; + u32 id = le32_to_cpu(mb->mac_id); + union iwl_dbg_tlv_tp_data tp_data = { .fw_pkt = pkt }; + + IWL_DEBUG_INFO(mvm, + "missed bcn mac_id=%u, consecutive=%u (%u, %u, %u)\n", + le32_to_cpu(mb->mac_id), + le32_to_cpu(mb->consec_missed_beacons), + le32_to_cpu(mb->consec_missed_beacons_since_last_rx), + le32_to_cpu(mb->num_recvd_beacons), + le32_to_cpu(mb->num_expected_beacons)); + + rcu_read_lock(); + + vif = iwl_mvm_rcu_dereference_vif_id(mvm, id, true); + if (!vif) + goto out; + + rx_missed_bcon = le32_to_cpu(mb->consec_missed_beacons); + rx_missed_bcon_since_rx = + le32_to_cpu(mb->consec_missed_beacons_since_last_rx); + /* + * TODO: the threshold should be adjusted based on latency conditions, + * and/or in case of a CS flow on one of the other AP vifs. + */ + if (rx_missed_bcon > IWL_MVM_MISSED_BEACONS_THRESHOLD_LONG) + iwl_mvm_connection_loss(mvm, vif, "missed beacons"); + else if (rx_missed_bcon_since_rx > IWL_MVM_MISSED_BEACONS_THRESHOLD) + ieee80211_beacon_loss(vif); + + iwl_dbg_tlv_time_point(&mvm->fwrt, + IWL_FW_INI_TIME_POINT_MISSED_BEACONS, &tp_data); + + trigger = iwl_fw_dbg_trigger_on(&mvm->fwrt, ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_MISSED_BEACONS); + if (!trigger) + goto out; + + bcon_trig = (void *)trigger->data; + stop_trig_missed_bcon = le32_to_cpu(bcon_trig->stop_consec_missed_bcon); + stop_trig_missed_bcon_since_rx = + le32_to_cpu(bcon_trig->stop_consec_missed_bcon_since_rx); + + /* TODO: implement start trigger */ + + if (rx_missed_bcon_since_rx >= stop_trig_missed_bcon_since_rx || + rx_missed_bcon >= stop_trig_missed_bcon) + iwl_fw_dbg_collect_trig(&mvm->fwrt, trigger, NULL); + +out: + rcu_read_unlock(); +} + +void iwl_mvm_rx_stored_beacon_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); + struct iwl_stored_beacon_notif_common *sb = (void *)pkt->data; + struct ieee80211_rx_status rx_status; + struct sk_buff *skb; + u8 *data; + u32 size = le32_to_cpu(sb->byte_count); + int ver = iwl_fw_lookup_cmd_ver(mvm->fw, + WIDE_ID(PROT_OFFLOAD_GROUP, STORED_BEACON_NTF), + 0); + + if (size == 0) + return; + + /* handle per-version differences */ + if (ver <= 2) { + struct iwl_stored_beacon_notif_v2 *sb_v2 = (void *)pkt->data; + + if (pkt_len < struct_size(sb_v2, data, size)) + return; + + data = sb_v2->data; + } else { + struct iwl_stored_beacon_notif_v3 *sb_v3 = (void *)pkt->data; + + if (pkt_len < struct_size(sb_v3, data, size)) + return; + + data = sb_v3->data; + } + + skb = alloc_skb(size, GFP_ATOMIC); + if (!skb) { + IWL_ERR(mvm, "alloc_skb failed\n"); + return; + } + + /* update rx_status according to the notification's metadata */ + memset(&rx_status, 0, sizeof(rx_status)); + rx_status.mactime = le64_to_cpu(sb->tsf); + /* TSF as indicated by the firmware is at INA time */ + rx_status.flag |= RX_FLAG_MACTIME_PLCP_START; + rx_status.device_timestamp = le32_to_cpu(sb->system_time); + rx_status.band = + (sb->band & cpu_to_le16(RX_RES_PHY_FLAGS_BAND_24)) ? + NL80211_BAND_2GHZ : NL80211_BAND_5GHZ; + rx_status.freq = + ieee80211_channel_to_frequency(le16_to_cpu(sb->channel), + rx_status.band); + + /* copy the data */ + skb_put_data(skb, data, size); + memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); + + /* pass it as regular rx to mac80211 */ + ieee80211_rx_napi(mvm->hw, NULL, skb, NULL); +} + +void iwl_mvm_probe_resp_data_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_probe_resp_data_notif *notif = (void *)pkt->data; + struct iwl_probe_resp_data *old_data, *new_data; + u32 id = le32_to_cpu(notif->mac_id); + struct ieee80211_vif *vif; + struct iwl_mvm_vif *mvmvif; + + IWL_DEBUG_INFO(mvm, "Probe response data notif: noa %d, csa %d\n", + notif->noa_active, notif->csa_counter); + + vif = iwl_mvm_rcu_dereference_vif_id(mvm, id, false); + if (!vif) + return; + + mvmvif = iwl_mvm_vif_from_mac80211(vif); + + new_data = kzalloc(sizeof(*new_data), GFP_KERNEL); + if (!new_data) + return; + + memcpy(&new_data->notif, notif, sizeof(new_data->notif)); + + /* noa_attr contains 1 reserved byte, need to substruct it */ + new_data->noa_len = sizeof(struct ieee80211_vendor_ie) + + sizeof(new_data->notif.noa_attr) - 1; + + /* + * If it's a one time NoA, only one descriptor is needed, + * adjust the length according to len_low. + */ + if (new_data->notif.noa_attr.len_low == + sizeof(struct ieee80211_p2p_noa_desc) + 2) + new_data->noa_len -= sizeof(struct ieee80211_p2p_noa_desc); + + old_data = rcu_dereference_protected(mvmvif->probe_resp_data, + lockdep_is_held(&mvmvif->mvm->mutex)); + rcu_assign_pointer(mvmvif->probe_resp_data, new_data); + + if (old_data) + kfree_rcu(old_data, rcu_head); + + if (notif->csa_counter != IWL_PROBE_RESP_DATA_NO_CSA && + notif->csa_counter >= 1) + ieee80211_beacon_set_cntdwn(vif, notif->csa_counter); +} + +void iwl_mvm_channel_switch_start_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_channel_switch_start_notif *notif = (void *)pkt->data; + struct ieee80211_vif *csa_vif, *vif; + struct iwl_mvm_vif *mvmvif; + u32 id_n_color, csa_id, mac_id; + + id_n_color = le32_to_cpu(notif->id_and_color); + mac_id = id_n_color & FW_CTXT_ID_MSK; + + if (WARN_ON_ONCE(mac_id >= NUM_MAC_INDEX_DRIVER)) + return; + + rcu_read_lock(); + vif = rcu_dereference(mvm->vif_id_to_mac[mac_id]); + mvmvif = iwl_mvm_vif_from_mac80211(vif); + + switch (vif->type) { + case NL80211_IFTYPE_AP: + csa_vif = rcu_dereference(mvm->csa_vif); + if (WARN_ON(!csa_vif || !csa_vif->bss_conf.csa_active || + csa_vif != vif)) + goto out_unlock; + + csa_id = FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color); + if (WARN(csa_id != id_n_color, + "channel switch noa notification on unexpected vif (csa_vif=%d, notif=%d)", + csa_id, id_n_color)) + goto out_unlock; + + IWL_DEBUG_INFO(mvm, "Channel Switch Started Notification\n"); + + schedule_delayed_work(&mvm->cs_tx_unblock_dwork, + msecs_to_jiffies(IWL_MVM_CS_UNBLOCK_TX_TIMEOUT * + csa_vif->bss_conf.beacon_int)); + + ieee80211_csa_finish(csa_vif); + + rcu_read_unlock(); + + RCU_INIT_POINTER(mvm->csa_vif, NULL); + return; + case NL80211_IFTYPE_STATION: + /* + * if we don't know about an ongoing channel switch, + * make sure FW cancels it + */ + if (iwl_fw_lookup_notif_ver(mvm->fw, MAC_CONF_GROUP, + CHANNEL_SWITCH_ERROR_NOTIF, + 0) && !vif->bss_conf.csa_active) { + IWL_DEBUG_INFO(mvm, "Channel Switch was canceled\n"); + iwl_mvm_cancel_channel_switch(mvm, vif, mac_id); + break; + } + + iwl_mvm_csa_client_absent(mvm, vif); + cancel_delayed_work(&mvmvif->csa_work); + ieee80211_chswitch_done(vif, true); + break; + default: + /* should never happen */ + WARN_ON_ONCE(1); + break; + } +out_unlock: + rcu_read_unlock(); +} + +void iwl_mvm_channel_switch_error_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_channel_switch_error_notif *notif = (void *)pkt->data; + struct ieee80211_vif *vif; + u32 id = le32_to_cpu(notif->mac_id); + u32 csa_err_mask = le32_to_cpu(notif->csa_err_mask); + + rcu_read_lock(); + vif = iwl_mvm_rcu_dereference_vif_id(mvm, id, true); + if (!vif) { + rcu_read_unlock(); + return; + } + + IWL_DEBUG_INFO(mvm, "FW reports CSA error: mac_id=%u, csa_err_mask=%u\n", + id, csa_err_mask); + if (csa_err_mask & (CS_ERR_COUNT_ERROR | + CS_ERR_LONG_DELAY_AFTER_CS | + CS_ERR_TX_BLOCK_TIMER_EXPIRED)) + ieee80211_channel_switch_disconnect(vif, true); + rcu_read_unlock(); +} + +void iwl_mvm_rx_missed_vap_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_missed_vap_notif *mb = (void *)pkt->data; + struct ieee80211_vif *vif; + u32 id = le32_to_cpu(mb->mac_id); + + IWL_DEBUG_INFO(mvm, + "missed_vap notify mac_id=%u, num_beacon_intervals_elapsed=%u, profile_periodicity=%u\n", + le32_to_cpu(mb->mac_id), + mb->num_beacon_intervals_elapsed, + mb->profile_periodicity); + + rcu_read_lock(); + + vif = iwl_mvm_rcu_dereference_vif_id(mvm, id, true); + if (vif) + iwl_mvm_connection_loss(mvm, vif, "missed vap beacon"); + + rcu_read_unlock(); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c new file mode 100644 index 000000000..801098c51 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c @@ -0,0 +1,5475 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/ip.h> +#include <linux/if_arp.h> +#include <linux/time.h> +#include <net/mac80211.h> +#include <net/ieee80211_radiotap.h> +#include <net/tcp.h> + +#include "iwl-drv.h" +#include "iwl-op-mode.h" +#include "iwl-io.h" +#include "mvm.h" +#include "sta.h" +#include "time-event.h" +#include "iwl-eeprom-parse.h" +#include "iwl-phy-db.h" +#include "testmode.h" +#include "fw/error-dump.h" +#include "iwl-prph.h" +#include "iwl-nvm-parse.h" + +static const struct ieee80211_iface_limit iwl_mvm_limits[] = { + { + .max = 1, + .types = BIT(NL80211_IFTYPE_STATION), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_P2P_DEVICE), + }, +}; + +static const struct ieee80211_iface_combination iwl_mvm_iface_combinations[] = { + { + .num_different_channels = 2, + .max_interfaces = 3, + .limits = iwl_mvm_limits, + .n_limits = ARRAY_SIZE(iwl_mvm_limits), + }, +}; + +static const struct cfg80211_pmsr_capabilities iwl_mvm_pmsr_capa = { + .max_peers = IWL_MVM_TOF_MAX_APS, + .report_ap_tsf = 1, + .randomize_mac_addr = 1, + + .ftm = { + .supported = 1, + .asap = 1, + .non_asap = 1, + .request_lci = 1, + .request_civicloc = 1, + .trigger_based = 1, + .non_trigger_based = 1, + .max_bursts_exponent = -1, /* all supported */ + .max_ftms_per_burst = 0, /* no limits */ + .bandwidths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | + BIT(NL80211_CHAN_WIDTH_20) | + BIT(NL80211_CHAN_WIDTH_40) | + BIT(NL80211_CHAN_WIDTH_80) | + BIT(NL80211_CHAN_WIDTH_160), + .preambles = BIT(NL80211_PREAMBLE_LEGACY) | + BIT(NL80211_PREAMBLE_HT) | + BIT(NL80211_PREAMBLE_VHT) | + BIT(NL80211_PREAMBLE_HE), + }, +}; + +static int __iwl_mvm_mac_set_key(struct ieee80211_hw *hw, + enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key); + +static void iwl_mvm_reset_phy_ctxts(struct iwl_mvm *mvm) +{ + int i; + + memset(mvm->phy_ctxts, 0, sizeof(mvm->phy_ctxts)); + for (i = 0; i < NUM_PHY_CTX; i++) { + mvm->phy_ctxts[i].id = i; + mvm->phy_ctxts[i].ref = 0; + } +} + +struct ieee80211_regdomain *iwl_mvm_get_regdomain(struct wiphy *wiphy, + const char *alpha2, + enum iwl_mcc_source src_id, + bool *changed) +{ + struct ieee80211_regdomain *regd = NULL; + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mcc_update_resp *resp; + u8 resp_ver; + + IWL_DEBUG_LAR(mvm, "Getting regdomain data for %s from FW\n", alpha2); + + lockdep_assert_held(&mvm->mutex); + + resp = iwl_mvm_update_mcc(mvm, alpha2, src_id); + if (IS_ERR_OR_NULL(resp)) { + IWL_DEBUG_LAR(mvm, "Could not get update from FW %d\n", + PTR_ERR_OR_ZERO(resp)); + resp = NULL; + goto out; + } + + if (changed) { + u32 status = le32_to_cpu(resp->status); + + *changed = (status == MCC_RESP_NEW_CHAN_PROFILE || + status == MCC_RESP_ILLEGAL); + } + resp_ver = iwl_fw_lookup_notif_ver(mvm->fw, IWL_ALWAYS_LONG_GROUP, + MCC_UPDATE_CMD, 0); + IWL_DEBUG_LAR(mvm, "MCC update response version: %d\n", resp_ver); + + regd = iwl_parse_nvm_mcc_info(mvm->trans->dev, mvm->cfg, + __le32_to_cpu(resp->n_channels), + resp->channels, + __le16_to_cpu(resp->mcc), + __le16_to_cpu(resp->geo_info), + __le16_to_cpu(resp->cap), resp_ver); + /* Store the return source id */ + src_id = resp->source_id; + if (IS_ERR_OR_NULL(regd)) { + IWL_DEBUG_LAR(mvm, "Could not get parse update from FW %d\n", + PTR_ERR_OR_ZERO(regd)); + goto out; + } + + IWL_DEBUG_LAR(mvm, "setting alpha2 from FW to %s (0x%x, 0x%x) src=%d\n", + regd->alpha2, regd->alpha2[0], regd->alpha2[1], src_id); + mvm->lar_regdom_set = true; + mvm->mcc_src = src_id; + + iwl_mei_set_country_code(__le16_to_cpu(resp->mcc)); + +out: + kfree(resp); + return regd; +} + +void iwl_mvm_update_changed_regdom(struct iwl_mvm *mvm) +{ + bool changed; + struct ieee80211_regdomain *regd; + + if (!iwl_mvm_is_lar_supported(mvm)) + return; + + regd = iwl_mvm_get_current_regdomain(mvm, &changed); + if (!IS_ERR_OR_NULL(regd)) { + /* only update the regulatory core if changed */ + if (changed) + regulatory_set_wiphy_regd(mvm->hw->wiphy, regd); + + kfree(regd); + } +} + +struct ieee80211_regdomain *iwl_mvm_get_current_regdomain(struct iwl_mvm *mvm, + bool *changed) +{ + return iwl_mvm_get_regdomain(mvm->hw->wiphy, "ZZ", + iwl_mvm_is_wifi_mcc_supported(mvm) ? + MCC_SOURCE_GET_CURRENT : + MCC_SOURCE_OLD_FW, changed); +} + +int iwl_mvm_init_fw_regd(struct iwl_mvm *mvm) +{ + enum iwl_mcc_source used_src; + struct ieee80211_regdomain *regd; + int ret; + bool changed; + const struct ieee80211_regdomain *r = + wiphy_dereference(mvm->hw->wiphy, mvm->hw->wiphy->regd); + + if (!r) + return -ENOENT; + + /* save the last source in case we overwrite it below */ + used_src = mvm->mcc_src; + if (iwl_mvm_is_wifi_mcc_supported(mvm)) { + /* Notify the firmware we support wifi location updates */ + regd = iwl_mvm_get_current_regdomain(mvm, NULL); + if (!IS_ERR_OR_NULL(regd)) + kfree(regd); + } + + /* Now set our last stored MCC and source */ + regd = iwl_mvm_get_regdomain(mvm->hw->wiphy, r->alpha2, used_src, + &changed); + if (IS_ERR_OR_NULL(regd)) + return -EIO; + + /* update cfg80211 if the regdomain was changed */ + if (changed) + ret = regulatory_set_wiphy_regd_sync(mvm->hw->wiphy, regd); + else + ret = 0; + + kfree(regd); + return ret; +} + +static const u8 he_if_types_ext_capa_sta[] = { + [0] = WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING, + [2] = WLAN_EXT_CAPA3_MULTI_BSSID_SUPPORT, + [7] = WLAN_EXT_CAPA8_OPMODE_NOTIF, +}; + +static const struct wiphy_iftype_ext_capab he_iftypes_ext_capa[] = { + { + .iftype = NL80211_IFTYPE_STATION, + .extended_capabilities = he_if_types_ext_capa_sta, + .extended_capabilities_mask = he_if_types_ext_capa_sta, + .extended_capabilities_len = sizeof(he_if_types_ext_capa_sta), + }, +}; + +static int +iwl_mvm_op_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + *tx_ant = iwl_mvm_get_valid_tx_ant(mvm); + *rx_ant = iwl_mvm_get_valid_rx_ant(mvm); + return 0; +} + +int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm) +{ + struct ieee80211_hw *hw = mvm->hw; + int num_mac, ret, i; + static const u32 mvm_ciphers[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, + }; +#ifdef CONFIG_PM_SLEEP + bool unified = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG); +#endif + + /* Tell mac80211 our characteristics */ + ieee80211_hw_set(hw, SIGNAL_DBM); + ieee80211_hw_set(hw, SPECTRUM_MGMT); + ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS); + ieee80211_hw_set(hw, WANT_MONITOR_VIF); + ieee80211_hw_set(hw, SUPPORTS_PS); + ieee80211_hw_set(hw, SUPPORTS_DYNAMIC_PS); + ieee80211_hw_set(hw, AMPDU_AGGREGATION); + ieee80211_hw_set(hw, TIMING_BEACON_ONLY); + ieee80211_hw_set(hw, CONNECTION_MONITOR); + ieee80211_hw_set(hw, CHANCTX_STA_CSA); + ieee80211_hw_set(hw, SUPPORT_FAST_XMIT); + ieee80211_hw_set(hw, SUPPORTS_CLONED_SKBS); + ieee80211_hw_set(hw, SUPPORTS_AMSDU_IN_AMPDU); + ieee80211_hw_set(hw, NEEDS_UNIQUE_STA_ADDR); + ieee80211_hw_set(hw, DEAUTH_NEED_MGD_TX_PREP); + ieee80211_hw_set(hw, SUPPORTS_VHT_EXT_NSS_BW); + ieee80211_hw_set(hw, BUFF_MMPDU_TXQ); + ieee80211_hw_set(hw, STA_MMPDU_TXQ); + /* + * On older devices, enabling TX A-MSDU occasionally leads to + * something getting messed up, the command read from the FIFO + * gets out of sync and isn't a TX command, so that we have an + * assert EDC. + * + * It's not clear where the bug is, but since we didn't used to + * support A-MSDU until moving the mac80211 iTXQs, just leave it + * for older devices. We also don't see this issue on any newer + * devices. + */ + if (mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_9000) + ieee80211_hw_set(hw, TX_AMSDU); + ieee80211_hw_set(hw, TX_FRAG_LIST); + + if (iwl_mvm_has_tlc_offload(mvm)) { + ieee80211_hw_set(hw, TX_AMPDU_SETUP_IN_HW); + ieee80211_hw_set(hw, HAS_RATE_CONTROL); + } + + if (iwl_mvm_has_new_rx_api(mvm)) + ieee80211_hw_set(hw, SUPPORTS_REORDERING_BUFFER); + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_STA_PM_NOTIF)) { + ieee80211_hw_set(hw, AP_LINK_PS); + } else if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) { + /* + * we absolutely need this for the new TX API since that comes + * with many more queues than the current code can deal with + * for station powersave + */ + return -EINVAL; + } + + if (mvm->trans->num_rx_queues > 1) + ieee80211_hw_set(hw, USES_RSS); + + if (mvm->trans->max_skb_frags) + hw->netdev_features = NETIF_F_HIGHDMA | NETIF_F_SG; + + hw->queues = IEEE80211_NUM_ACS; + hw->offchannel_tx_hw_queue = IWL_MVM_OFFCHANNEL_QUEUE; + hw->radiotap_mcs_details |= IEEE80211_RADIOTAP_MCS_HAVE_FEC | + IEEE80211_RADIOTAP_MCS_HAVE_STBC; + hw->radiotap_vht_details |= IEEE80211_RADIOTAP_VHT_KNOWN_STBC | + IEEE80211_RADIOTAP_VHT_KNOWN_BEAMFORMED; + + hw->radiotap_timestamp.units_pos = + IEEE80211_RADIOTAP_TIMESTAMP_UNIT_US | + IEEE80211_RADIOTAP_TIMESTAMP_SPOS_PLCP_SIG_ACQ; + /* this is the case for CCK frames, it's better (only 8) for OFDM */ + hw->radiotap_timestamp.accuracy = 22; + + if (!iwl_mvm_has_tlc_offload(mvm)) + hw->rate_control_algorithm = RS_NAME; + + hw->uapsd_queues = IWL_MVM_UAPSD_QUEUES; + hw->uapsd_max_sp_len = IWL_UAPSD_MAX_SP; + hw->max_tx_fragments = mvm->trans->max_skb_frags; + + BUILD_BUG_ON(ARRAY_SIZE(mvm->ciphers) < ARRAY_SIZE(mvm_ciphers) + 6); + memcpy(mvm->ciphers, mvm_ciphers, sizeof(mvm_ciphers)); + hw->wiphy->n_cipher_suites = ARRAY_SIZE(mvm_ciphers); + hw->wiphy->cipher_suites = mvm->ciphers; + + if (iwl_mvm_has_new_rx_api(mvm)) { + mvm->ciphers[hw->wiphy->n_cipher_suites] = + WLAN_CIPHER_SUITE_GCMP; + hw->wiphy->n_cipher_suites++; + mvm->ciphers[hw->wiphy->n_cipher_suites] = + WLAN_CIPHER_SUITE_GCMP_256; + hw->wiphy->n_cipher_suites++; + } + + if (iwlwifi_mod_params.swcrypto) + IWL_ERR(mvm, + "iwlmvm doesn't allow to disable HW crypto, check swcrypto module parameter\n"); + if (!iwlwifi_mod_params.bt_coex_active) + IWL_ERR(mvm, + "iwlmvm doesn't allow to disable BT Coex, check bt_coex_active module parameter\n"); + + ieee80211_hw_set(hw, MFP_CAPABLE); + mvm->ciphers[hw->wiphy->n_cipher_suites] = WLAN_CIPHER_SUITE_AES_CMAC; + hw->wiphy->n_cipher_suites++; + if (iwl_mvm_has_new_rx_api(mvm)) { + mvm->ciphers[hw->wiphy->n_cipher_suites] = + WLAN_CIPHER_SUITE_BIP_GMAC_128; + hw->wiphy->n_cipher_suites++; + mvm->ciphers[hw->wiphy->n_cipher_suites] = + WLAN_CIPHER_SUITE_BIP_GMAC_256; + hw->wiphy->n_cipher_suites++; + } + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_FTM_CALIBRATED)) { + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_ENABLE_FTM_RESPONDER); + hw->wiphy->pmsr_capa = &iwl_mvm_pmsr_capa; + } + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_BIGTK_SUPPORT)) + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_BEACON_PROTECTION_CLIENT); + + ieee80211_hw_set(hw, SINGLE_SCAN_ON_ALL_BANDS); + hw->wiphy->features |= + NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR | + NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR | + NL80211_FEATURE_ND_RANDOM_MAC_ADDR; + + hw->sta_data_size = sizeof(struct iwl_mvm_sta); + hw->vif_data_size = sizeof(struct iwl_mvm_vif); + hw->chanctx_data_size = sizeof(u16); + hw->txq_data_size = sizeof(struct iwl_mvm_txq); + + hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_P2P_DEVICE) | + BIT(NL80211_IFTYPE_ADHOC); + + hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN; + wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_VHT_IBSS); + + /* The new Tx API does not allow to pass the key or keyid of a MPDU to + * the hw, preventing us to control which key(id) to use per MPDU. + * Till that's fixed we can't use Extended Key ID for the newer cards. + */ + if (!iwl_mvm_has_new_tx_api(mvm)) + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_EXT_KEY_ID); + hw->wiphy->features |= NL80211_FEATURE_HT_IBSS; + + hw->wiphy->regulatory_flags |= REGULATORY_ENABLE_RELAX_NO_IR; + if (iwl_mvm_is_lar_supported(mvm)) + hw->wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED; + else + hw->wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG | + REGULATORY_DISABLE_BEACON_HINTS; + + hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD; + hw->wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH; + hw->wiphy->flags |= WIPHY_FLAG_SPLIT_SCAN_6GHZ; + + hw->wiphy->iface_combinations = iwl_mvm_iface_combinations; + hw->wiphy->n_iface_combinations = + ARRAY_SIZE(iwl_mvm_iface_combinations); + + hw->wiphy->max_remain_on_channel_duration = 10000; + hw->max_listen_interval = IWL_CONN_MAX_LISTEN_INTERVAL; + + /* Extract MAC address */ + memcpy(mvm->addresses[0].addr, mvm->nvm_data->hw_addr, ETH_ALEN); + hw->wiphy->addresses = mvm->addresses; + hw->wiphy->n_addresses = 1; + + /* Extract additional MAC addresses if available */ + num_mac = (mvm->nvm_data->n_hw_addrs > 1) ? + min(IWL_MVM_MAX_ADDRESSES, mvm->nvm_data->n_hw_addrs) : 1; + + for (i = 1; i < num_mac; i++) { + memcpy(mvm->addresses[i].addr, mvm->addresses[i-1].addr, + ETH_ALEN); + mvm->addresses[i].addr[5]++; + hw->wiphy->n_addresses++; + } + + iwl_mvm_reset_phy_ctxts(mvm); + + hw->wiphy->max_scan_ie_len = iwl_mvm_max_scan_ie_len(mvm); + + hw->wiphy->max_scan_ssids = PROBE_OPTION_MAX; + + BUILD_BUG_ON(IWL_MVM_SCAN_STOPPING_MASK & IWL_MVM_SCAN_MASK); + BUILD_BUG_ON(IWL_MVM_MAX_UMAC_SCANS > HWEIGHT32(IWL_MVM_SCAN_MASK) || + IWL_MVM_MAX_LMAC_SCANS > HWEIGHT32(IWL_MVM_SCAN_MASK)); + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) + mvm->max_scans = IWL_MVM_MAX_UMAC_SCANS; + else + mvm->max_scans = IWL_MVM_MAX_LMAC_SCANS; + + if (mvm->nvm_data->bands[NL80211_BAND_2GHZ].n_channels) + hw->wiphy->bands[NL80211_BAND_2GHZ] = + &mvm->nvm_data->bands[NL80211_BAND_2GHZ]; + if (mvm->nvm_data->bands[NL80211_BAND_5GHZ].n_channels) { + hw->wiphy->bands[NL80211_BAND_5GHZ] = + &mvm->nvm_data->bands[NL80211_BAND_5GHZ]; + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_BEAMFORMER) && + fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_LQ_SS_PARAMS)) + hw->wiphy->bands[NL80211_BAND_5GHZ]->vht_cap.cap |= + IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE; + } + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_PSC_CHAN_SUPPORT) && + mvm->nvm_data->bands[NL80211_BAND_6GHZ].n_channels) + hw->wiphy->bands[NL80211_BAND_6GHZ] = + &mvm->nvm_data->bands[NL80211_BAND_6GHZ]; + + hw->wiphy->hw_version = mvm->trans->hw_id; + + if (iwlmvm_mod_params.power_scheme != IWL_POWER_SCHEME_CAM) + hw->wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT; + else + hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + + hw->wiphy->max_sched_scan_reqs = 1; + hw->wiphy->max_sched_scan_ssids = PROBE_OPTION_MAX; + hw->wiphy->max_match_sets = iwl_umac_scan_get_max_profiles(mvm->fw); + /* we create the 802.11 header and zero length SSID IE. */ + hw->wiphy->max_sched_scan_ie_len = + SCAN_OFFLOAD_PROBE_REQ_SIZE - 24 - 2; + hw->wiphy->max_sched_scan_plans = IWL_MAX_SCHED_SCAN_PLANS; + hw->wiphy->max_sched_scan_plan_interval = U16_MAX; + + /* + * the firmware uses u8 for num of iterations, but 0xff is saved for + * infinite loop, so the maximum number of iterations is actually 254. + */ + hw->wiphy->max_sched_scan_plan_iterations = 254; + + hw->wiphy->features |= NL80211_FEATURE_P2P_GO_CTWIN | + NL80211_FEATURE_LOW_PRIORITY_SCAN | + NL80211_FEATURE_P2P_GO_OPPPS | + NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE | + NL80211_FEATURE_DYNAMIC_SMPS | + NL80211_FEATURE_STATIC_SMPS | + NL80211_FEATURE_SUPPORTS_WMM_ADMISSION; + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TXPOWER_INSERTION_SUPPORT)) + hw->wiphy->features |= NL80211_FEATURE_TX_POWER_INSERTION; + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_QUIET_PERIOD_SUPPORT)) + hw->wiphy->features |= NL80211_FEATURE_QUIET; + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT)) + hw->wiphy->features |= + NL80211_FEATURE_DS_PARAM_SET_IE_IN_PROBES; + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_WFA_TPC_REP_IE_SUPPORT)) + hw->wiphy->features |= NL80211_FEATURE_WFA_TPC_IE_IN_PROBES; + + if (iwl_fw_lookup_cmd_ver(mvm->fw, WOWLAN_KEK_KCK_MATERIAL, + IWL_FW_CMD_VER_UNKNOWN) == 3) + hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_EXT_KEK_KCK; + + if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_SCAN_TSF_REPORT)) { + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_SCAN_START_TIME); + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_BSS_PARENT_TSF); + } + + if (iwl_mvm_is_oce_supported(mvm)) { + u8 scan_ver = iwl_fw_lookup_cmd_ver(mvm->fw, SCAN_REQ_UMAC, 0); + + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_ACCEPT_BCAST_PROBE_RESP); + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_FILS_MAX_CHANNEL_TIME); + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_OCE_PROBE_REQ_HIGH_TX_RATE); + + /* Old firmware also supports probe deferral and suppression */ + if (scan_ver < 15) + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_OCE_PROBE_REQ_DEFERRAL_SUPPRESSION); + } + + if (mvm->nvm_data->sku_cap_11ax_enable && + !iwlwifi_mod_params.disable_11ax) { + hw->wiphy->iftype_ext_capab = he_iftypes_ext_capa; + hw->wiphy->num_iftype_ext_capab = + ARRAY_SIZE(he_iftypes_ext_capa); + + ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID); + ieee80211_hw_set(hw, SUPPORTS_ONLY_HE_MULTI_BSSID); + } + + mvm->rts_threshold = IEEE80211_MAX_RTS_THRESHOLD; + +#ifdef CONFIG_PM_SLEEP + if ((unified || mvm->fw->img[IWL_UCODE_WOWLAN].num_sec) && + mvm->trans->ops->d3_suspend && + mvm->trans->ops->d3_resume && + device_can_wakeup(mvm->trans->dev)) { + mvm->wowlan.flags |= WIPHY_WOWLAN_MAGIC_PKT | + WIPHY_WOWLAN_DISCONNECT | + WIPHY_WOWLAN_EAP_IDENTITY_REQ | + WIPHY_WOWLAN_RFKILL_RELEASE | + WIPHY_WOWLAN_NET_DETECT; + mvm->wowlan.flags |= WIPHY_WOWLAN_SUPPORTS_GTK_REKEY | + WIPHY_WOWLAN_GTK_REKEY_FAILURE | + WIPHY_WOWLAN_4WAY_HANDSHAKE; + + mvm->wowlan.n_patterns = IWL_WOWLAN_MAX_PATTERNS; + mvm->wowlan.pattern_min_len = IWL_WOWLAN_MIN_PATTERN_LEN; + mvm->wowlan.pattern_max_len = IWL_WOWLAN_MAX_PATTERN_LEN; + mvm->wowlan.max_nd_match_sets = + iwl_umac_scan_get_max_profiles(mvm->fw); + hw->wiphy->wowlan = &mvm->wowlan; + } +#endif + + ret = iwl_mvm_leds_init(mvm); + if (ret) + return ret; + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TDLS_SUPPORT)) { + IWL_DEBUG_TDLS(mvm, "TDLS supported\n"); + hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS; + ieee80211_hw_set(hw, TDLS_WIDER_BW); + } + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TDLS_CHANNEL_SWITCH)) { + IWL_DEBUG_TDLS(mvm, "TDLS channel switch supported\n"); + hw->wiphy->features |= NL80211_FEATURE_TDLS_CHANNEL_SWITCH; + } + + hw->netdev_features |= mvm->cfg->features; + if (!iwl_mvm_is_csum_supported(mvm)) + hw->netdev_features &= ~IWL_CSUM_NETIF_FLAGS_MASK; + + if (mvm->cfg->vht_mu_mimo_supported) + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_MU_MIMO_AIR_SNIFFER); + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_PROTECTED_TWT)) + wiphy_ext_feature_set(hw->wiphy, + NL80211_EXT_FEATURE_PROTECTED_TWT); + + iwl_mvm_vendor_cmds_register(mvm); + + hw->wiphy->available_antennas_tx = iwl_mvm_get_valid_tx_ant(mvm); + hw->wiphy->available_antennas_rx = iwl_mvm_get_valid_rx_ant(mvm); + + ret = ieee80211_register_hw(mvm->hw); + if (ret) { + iwl_mvm_leds_exit(mvm); + } + + return ret; +} + +static void iwl_mvm_tx_skb(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_sta *sta) +{ + if (likely(sta)) { + if (likely(iwl_mvm_tx_skb_sta(mvm, skb, sta) == 0)) + return; + } else { + if (likely(iwl_mvm_tx_skb_non_sta(mvm, skb) == 0)) + return; + } + + ieee80211_free_txskb(mvm->hw, skb); +} + +static void iwl_mvm_mac_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct ieee80211_sta *sta = control->sta; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *hdr = (void *)skb->data; + bool offchannel = IEEE80211_SKB_CB(skb)->flags & + IEEE80211_TX_CTL_TX_OFFCHAN; + + if (iwl_mvm_is_radio_killed(mvm)) { + IWL_DEBUG_DROP(mvm, "Dropping - RF/CT KILL\n"); + goto drop; + } + + if (offchannel && + !test_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status) && + !test_bit(IWL_MVM_STATUS_ROC_AUX_RUNNING, &mvm->status)) + goto drop; + + /* + * bufferable MMPDUs or MMPDUs on STA interfaces come via TXQs + * so we treat the others as broadcast + */ + if (ieee80211_is_mgmt(hdr->frame_control)) + sta = NULL; + + /* If there is no sta, and it's not offchannel - send through AP */ + if (!sta && info->control.vif->type == NL80211_IFTYPE_STATION && + !offchannel) { + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(info->control.vif); + u8 ap_sta_id = READ_ONCE(mvmvif->ap_sta_id); + + if (ap_sta_id < mvm->fw->ucode_capa.num_stations) { + /* mac80211 holds rcu read lock */ + sta = rcu_dereference(mvm->fw_id_to_mac_id[ap_sta_id]); + if (IS_ERR_OR_NULL(sta)) + goto drop; + } + } + + iwl_mvm_tx_skb(mvm, skb, sta); + return; + drop: + ieee80211_free_txskb(hw, skb); +} + +void iwl_mvm_mac_itxq_xmit(struct ieee80211_hw *hw, struct ieee80211_txq *txq) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_txq *mvmtxq = iwl_mvm_txq_from_mac80211(txq); + struct sk_buff *skb = NULL; + + /* + * No need for threads to be pending here, they can leave the first + * taker all the work. + * + * mvmtxq->tx_request logic: + * + * If 0, no one is currently TXing, set to 1 to indicate current thread + * will now start TX and other threads should quit. + * + * If 1, another thread is currently TXing, set to 2 to indicate to + * that thread that there was another request. Since that request may + * have raced with the check whether the queue is empty, the TXing + * thread should check the queue's status one more time before leaving. + * This check is done in order to not leave any TX hanging in the queue + * until the next TX invocation (which may not even happen). + * + * If 2, another thread is currently TXing, and it will already double + * check the queue, so do nothing. + */ + if (atomic_fetch_add_unless(&mvmtxq->tx_request, 1, 2)) + return; + + rcu_read_lock(); + do { + while (likely(!test_bit(IWL_MVM_TXQ_STATE_STOP_FULL, + &mvmtxq->state) && + !test_bit(IWL_MVM_TXQ_STATE_STOP_REDIRECT, + &mvmtxq->state) && + !test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status))) { + skb = ieee80211_tx_dequeue(hw, txq); + + if (!skb) { + if (txq->sta) + IWL_DEBUG_TX(mvm, + "TXQ of sta %pM tid %d is now empty\n", + txq->sta->addr, + txq->tid); + break; + } + + iwl_mvm_tx_skb(mvm, skb, txq->sta); + } + } while (atomic_dec_return(&mvmtxq->tx_request)); + rcu_read_unlock(); +} + +static void iwl_mvm_mac_wake_tx_queue(struct ieee80211_hw *hw, + struct ieee80211_txq *txq) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_txq *mvmtxq = iwl_mvm_txq_from_mac80211(txq); + + if (likely(test_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state)) || + !txq->sta) { + iwl_mvm_mac_itxq_xmit(hw, txq); + return; + } + + /* iwl_mvm_mac_itxq_xmit() will later be called by the worker + * to handle any packets we leave on the txq now + */ + + spin_lock_bh(&mvm->add_stream_lock); + /* The list is being deleted only after the queue is fully allocated. */ + if (list_empty(&mvmtxq->list) && + /* recheck under lock */ + !test_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state)) { + list_add_tail(&mvmtxq->list, &mvm->add_stream_txqs); + schedule_work(&mvm->add_stream_wk); + } + spin_unlock_bh(&mvm->add_stream_lock); +} + +#define CHECK_BA_TRIGGER(_mvm, _trig, _tid_bm, _tid, _fmt...) \ + do { \ + if (!(le16_to_cpu(_tid_bm) & BIT(_tid))) \ + break; \ + iwl_fw_dbg_collect_trig(&(_mvm)->fwrt, _trig, _fmt); \ + } while (0) + +static void +iwl_mvm_ampdu_check_trigger(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 rx_ba_ssn, + enum ieee80211_ampdu_mlme_action action) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_ba *ba_trig; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_BA); + if (!trig) + return; + + ba_trig = (void *)trig->data; + + switch (action) { + case IEEE80211_AMPDU_TX_OPERATIONAL: { + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; + + CHECK_BA_TRIGGER(mvm, trig, ba_trig->tx_ba_start, tid, + "TX AGG START: MAC %pM tid %d ssn %d\n", + sta->addr, tid, tid_data->ssn); + break; + } + case IEEE80211_AMPDU_TX_STOP_CONT: + CHECK_BA_TRIGGER(mvm, trig, ba_trig->tx_ba_stop, tid, + "TX AGG STOP: MAC %pM tid %d\n", + sta->addr, tid); + break; + case IEEE80211_AMPDU_RX_START: + CHECK_BA_TRIGGER(mvm, trig, ba_trig->rx_ba_start, tid, + "RX AGG START: MAC %pM tid %d ssn %d\n", + sta->addr, tid, rx_ba_ssn); + break; + case IEEE80211_AMPDU_RX_STOP: + CHECK_BA_TRIGGER(mvm, trig, ba_trig->rx_ba_stop, tid, + "RX AGG STOP: MAC %pM tid %d\n", + sta->addr, tid); + break; + default: + break; + } +} + +static int iwl_mvm_mac_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_ampdu_params *params) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + struct ieee80211_sta *sta = params->sta; + enum ieee80211_ampdu_mlme_action action = params->action; + u16 tid = params->tid; + u16 *ssn = ¶ms->ssn; + u16 buf_size = params->buf_size; + bool amsdu = params->amsdu; + u16 timeout = params->timeout; + + IWL_DEBUG_HT(mvm, "A-MPDU action on addr %pM tid %d: action %d\n", + sta->addr, tid, action); + + if (!(mvm->nvm_data->sku_cap_11n_enable)) + return -EACCES; + + mutex_lock(&mvm->mutex); + + switch (action) { + case IEEE80211_AMPDU_RX_START: + if (iwl_mvm_vif_from_mac80211(vif)->ap_sta_id == + iwl_mvm_sta_from_mac80211(sta)->sta_id) { + struct iwl_mvm_vif *mvmvif; + u16 macid = iwl_mvm_vif_from_mac80211(vif)->id; + struct iwl_mvm_tcm_mac *mdata = &mvm->tcm.data[macid]; + + mdata->opened_rx_ba_sessions = true; + mvmvif = iwl_mvm_vif_from_mac80211(vif); + cancel_delayed_work(&mvmvif->uapsd_nonagg_detected_wk); + } + if (!iwl_enable_rx_ampdu()) { + ret = -EINVAL; + break; + } + ret = iwl_mvm_sta_rx_agg(mvm, sta, tid, *ssn, true, buf_size, + timeout); + break; + case IEEE80211_AMPDU_RX_STOP: + ret = iwl_mvm_sta_rx_agg(mvm, sta, tid, 0, false, buf_size, + timeout); + break; + case IEEE80211_AMPDU_TX_START: + if (!iwl_enable_tx_ampdu()) { + ret = -EINVAL; + break; + } + ret = iwl_mvm_sta_tx_agg_start(mvm, vif, sta, tid, ssn); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + ret = iwl_mvm_sta_tx_agg_stop(mvm, vif, sta, tid); + break; + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + ret = iwl_mvm_sta_tx_agg_flush(mvm, vif, sta, tid); + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + ret = iwl_mvm_sta_tx_agg_oper(mvm, vif, sta, tid, + buf_size, amsdu); + break; + default: + WARN_ON_ONCE(1); + ret = -EINVAL; + break; + } + + if (!ret) { + u16 rx_ba_ssn = 0; + + if (action == IEEE80211_AMPDU_RX_START) + rx_ba_ssn = *ssn; + + iwl_mvm_ampdu_check_trigger(mvm, vif, sta, tid, + rx_ba_ssn, action); + } + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_cleanup_iterator(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + mvmvif->uploaded = false; + mvmvif->ap_sta_id = IWL_MVM_INVALID_STA; + + spin_lock_bh(&mvm->time_event_lock); + iwl_mvm_te_clear_data(mvm, &mvmvif->time_event_data); + spin_unlock_bh(&mvm->time_event_lock); + + mvmvif->phy_ctxt = NULL; + memset(&mvmvif->bf_data, 0, sizeof(mvmvif->bf_data)); + memset(&mvmvif->probe_resp_data, 0, sizeof(mvmvif->probe_resp_data)); +} + +static void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm) +{ + iwl_mvm_stop_device(mvm); + + mvm->cur_aid = 0; + + mvm->scan_status = 0; + mvm->ps_disabled = false; + mvm->rfkill_safe_init_done = false; + + /* just in case one was running */ + iwl_mvm_cleanup_roc_te(mvm); + ieee80211_remain_on_channel_expired(mvm->hw); + + iwl_mvm_ftm_restart(mvm); + + /* + * cleanup all interfaces, even inactive ones, as some might have + * gone down during the HW restart + */ + ieee80211_iterate_interfaces(mvm->hw, 0, iwl_mvm_cleanup_iterator, mvm); + + mvm->p2p_device_vif = NULL; + + iwl_mvm_reset_phy_ctxts(mvm); + memset(mvm->fw_key_table, 0, sizeof(mvm->fw_key_table)); + memset(&mvm->last_bt_notif, 0, sizeof(mvm->last_bt_notif)); + memset(&mvm->last_bt_ci_cmd, 0, sizeof(mvm->last_bt_ci_cmd)); + + ieee80211_wake_queues(mvm->hw); + + mvm->rx_ba_sessions = 0; + mvm->fwrt.dump.conf = FW_DBG_INVALID; + mvm->monitor_on = false; + + /* keep statistics ticking */ + iwl_mvm_accu_radio_stats(mvm); +} + +int __iwl_mvm_mac_start(struct iwl_mvm *mvm) +{ + int ret; + + lockdep_assert_held(&mvm->mutex); + + ret = iwl_mvm_mei_get_ownership(mvm); + if (ret) + return ret; + + if (mvm->mei_nvm_data) { + /* We got the NIC, we can now free the MEI NVM data */ + kfree(mvm->mei_nvm_data); + mvm->mei_nvm_data = NULL; + + /* + * We can't free the nvm_data we allocated based on the SAP + * data because we registered to cfg80211 with the channels + * allocated on mvm->nvm_data. Keep a pointer in temp_nvm_data + * just in order to be able free it later. + * NULLify nvm_data so that we will read the NVM from the + * firmware this time. + */ + mvm->temp_nvm_data = mvm->nvm_data; + mvm->nvm_data = NULL; + } + + if (test_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, &mvm->status)) { + /* + * Now convert the HW_RESTART_REQUESTED flag to IN_HW_RESTART + * so later code will - from now on - see that we're doing it. + */ + set_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status); + clear_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, &mvm->status); + /* Clean up some internal and mac80211 state on restart */ + iwl_mvm_restart_cleanup(mvm); + } + ret = iwl_mvm_up(mvm); + + iwl_dbg_tlv_time_point(&mvm->fwrt, IWL_FW_INI_TIME_POINT_POST_INIT, + NULL); + iwl_dbg_tlv_time_point(&mvm->fwrt, IWL_FW_INI_TIME_POINT_PERIODIC, + NULL); + + mvm->last_reset_or_resume_time_jiffies = jiffies; + + 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); + } + + return ret; +} + +static int iwl_mvm_mac_start(struct ieee80211_hw *hw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + int retry, max_retry = 0; + + mutex_lock(&mvm->mutex); + + /* we are starting the mac not in error flow, and restart is enabled */ + if (!test_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, &mvm->status) && + iwlwifi_mod_params.fw_restart) { + max_retry = IWL_MAX_INIT_RETRY; + /* + * This will prevent mac80211 recovery flows to trigger during + * init failures + */ + set_bit(IWL_MVM_STATUS_STARTING, &mvm->status); + } + + for (retry = 0; retry <= max_retry; retry++) { + ret = __iwl_mvm_mac_start(mvm); + if (!ret) + break; + + IWL_ERR(mvm, "mac start retry %d\n", retry); + } + clear_bit(IWL_MVM_STATUS_STARTING, &mvm->status); + + mutex_unlock(&mvm->mutex); + + iwl_mvm_mei_set_sw_rfkill_state(mvm); + + return ret; +} + +static void iwl_mvm_restart_complete(struct iwl_mvm *mvm) +{ + int ret; + + mutex_lock(&mvm->mutex); + + clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status); + + ret = iwl_mvm_update_quotas(mvm, true, NULL); + if (ret) + IWL_ERR(mvm, "Failed to update quotas after restart (%d)\n", + ret); + + iwl_mvm_send_recovery_cmd(mvm, ERROR_RECOVERY_END_OF_RECOVERY); + + /* + * If we have TDLS peers, remove them. We don't know the last seqno/PN + * of packets the FW sent out, so we must reconnect. + */ + iwl_mvm_teardown_tdls_peers(mvm); + + mutex_unlock(&mvm->mutex); +} + +static void +iwl_mvm_mac_reconfig_complete(struct ieee80211_hw *hw, + enum ieee80211_reconfig_type reconfig_type) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + switch (reconfig_type) { + case IEEE80211_RECONFIG_TYPE_RESTART: + iwl_mvm_restart_complete(mvm); + break; + case IEEE80211_RECONFIG_TYPE_SUSPEND: + break; + } +} + +void __iwl_mvm_mac_stop(struct iwl_mvm *mvm) +{ + lockdep_assert_held(&mvm->mutex); + + iwl_mvm_ftm_initiator_smooth_stop(mvm); + + /* firmware counters are obviously reset now, but we shouldn't + * partially track so also clear the fw_reset_accu counters. + */ + memset(&mvm->accu_radio_stats, 0, sizeof(mvm->accu_radio_stats)); + + /* async_handlers_wk is now blocked */ + + if (iwl_fw_lookup_cmd_ver(mvm->fw, ADD_STA, 0) < 12) + iwl_mvm_rm_aux_sta(mvm); + + iwl_mvm_stop_device(mvm); + + iwl_mvm_async_handlers_purge(mvm); + /* async_handlers_list is empty and will stay empty: HW is stopped */ + + /* + * Clear IN_HW_RESTART and HW_RESTART_REQUESTED flag when stopping the + * hw (as restart_complete() won't be called in this case) and mac80211 + * won't execute the restart. + * But make sure to cleanup interfaces that have gone down before/during + * HW restart was requested. + */ + if (test_and_clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) || + test_and_clear_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, + &mvm->status)) + ieee80211_iterate_interfaces(mvm->hw, 0, + iwl_mvm_cleanup_iterator, mvm); + + /* We shouldn't have any UIDs still set. Loop over all the UIDs to + * make sure there's nothing left there and warn if any is found. + */ + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) { + int i; + + for (i = 0; i < mvm->max_scans; i++) { + if (WARN_ONCE(mvm->scan_uid_status[i], + "UMAC scan UID %d status was not cleaned\n", + i)) + mvm->scan_uid_status[i] = 0; + } + } +} + +static void iwl_mvm_mac_stop(struct ieee80211_hw *hw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + flush_work(&mvm->async_handlers_wk); + flush_work(&mvm->add_stream_wk); + + /* + * Lock and clear the firmware running bit here already, so that + * new commands coming in elsewhere, e.g. from debugfs, will not + * be able to proceed. This is important here because one of those + * debugfs files causes the firmware dump to be triggered, and if we + * don't stop debugfs accesses before canceling that it could be + * retriggered after we flush it but before we've cleared the bit. + */ + clear_bit(IWL_MVM_STATUS_FIRMWARE_RUNNING, &mvm->status); + + cancel_delayed_work_sync(&mvm->cs_tx_unblock_dwork); + cancel_delayed_work_sync(&mvm->scan_timeout_dwork); + + /* + * The work item could be running or queued if the + * ROC time event stops just as we get here. + */ + flush_work(&mvm->roc_done_wk); + + iwl_mvm_mei_set_sw_rfkill_state(mvm); + + mutex_lock(&mvm->mutex); + __iwl_mvm_mac_stop(mvm); + mutex_unlock(&mvm->mutex); + + /* + * The worker might have been waiting for the mutex, let it run and + * discover that its list is now empty. + */ + cancel_work_sync(&mvm->async_handlers_wk); +} + +static struct iwl_mvm_phy_ctxt *iwl_mvm_get_free_phy_ctxt(struct iwl_mvm *mvm) +{ + u16 i; + + lockdep_assert_held(&mvm->mutex); + + for (i = 0; i < NUM_PHY_CTX; i++) + if (!mvm->phy_ctxts[i].ref) + return &mvm->phy_ctxts[i]; + + IWL_ERR(mvm, "No available PHY context\n"); + return NULL; +} + +static int iwl_mvm_set_tx_power(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + s16 tx_power) +{ + u32 cmd_id = REDUCE_TX_POWER_CMD; + int len; + struct iwl_dev_tx_power_cmd cmd = { + .common.set_mode = cpu_to_le32(IWL_TX_POWER_MODE_SET_MAC), + .common.mac_context_id = + cpu_to_le32(iwl_mvm_vif_from_mac80211(vif)->id), + .common.pwr_restriction = cpu_to_le16(8 * tx_power), + }; + u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, + IWL_FW_CMD_VER_UNKNOWN); + + if (tx_power == IWL_DEFAULT_MAX_TX_POWER) + cmd.common.pwr_restriction = cpu_to_le16(IWL_DEV_MAX_TX_POWER); + + if (cmd_ver == 7) + len = sizeof(cmd.v7); + else if (cmd_ver == 6) + len = sizeof(cmd.v6); + else if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_REDUCE_TX_POWER)) + len = sizeof(cmd.v5); + else if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TX_POWER_ACK)) + len = sizeof(cmd.v4); + else + len = sizeof(cmd.v3); + + /* all structs have the same common part, add it */ + len += sizeof(cmd.common); + + return iwl_mvm_send_cmd_pdu(mvm, cmd_id, 0, len, &cmd); +} + +static int iwl_mvm_post_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + mutex_lock(&mvm->mutex); + + if (vif->type == NL80211_IFTYPE_STATION) { + struct iwl_mvm_sta *mvmsta; + + mvmvif->csa_bcn_pending = false; + mvmsta = iwl_mvm_sta_from_staid_protected(mvm, + mvmvif->ap_sta_id); + + if (WARN_ON(!mvmsta)) { + ret = -EIO; + goto out_unlock; + } + + iwl_mvm_sta_modify_disable_tx(mvm, mvmsta, false); + + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CHANNEL_SWITCH_CMD)) { + ret = iwl_mvm_enable_beacon_filter(mvm, vif, 0); + if (ret) + goto out_unlock; + + iwl_mvm_stop_session_protection(mvm, vif); + } + } + + mvmvif->ps_disabled = false; + + ret = iwl_mvm_power_update_ps(mvm); + +out_unlock: + if (mvmvif->csa_failed) + ret = -EIO; + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_abort_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_chan_switch_te_cmd cmd = { + .mac_id = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)), + .action = cpu_to_le32(FW_CTXT_ACTION_REMOVE), + }; + + /* + * In the new flow since FW is in charge of the timing, + * if driver has canceled the channel switch he will receive the + * CHANNEL_SWITCH_START_NOTIF notification from FW and then cancel it + */ + if (iwl_fw_lookup_notif_ver(mvm->fw, MAC_CONF_GROUP, + CHANNEL_SWITCH_ERROR_NOTIF, 0)) + return; + + IWL_DEBUG_MAC80211(mvm, "Abort CSA on mac %d\n", mvmvif->id); + + mutex_lock(&mvm->mutex); + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CHANNEL_SWITCH_CMD)) + iwl_mvm_remove_csa_period(mvm, vif); + else + WARN_ON(iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(MAC_CONF_GROUP, + CHANNEL_SWITCH_TIME_EVENT_CMD), + 0, sizeof(cmd), &cmd)); + mvmvif->csa_failed = true; + mutex_unlock(&mvm->mutex); + + iwl_mvm_post_channel_switch(hw, vif); +} + +static void iwl_mvm_channel_switch_disconnect_wk(struct work_struct *wk) +{ + struct iwl_mvm_vif *mvmvif; + struct ieee80211_vif *vif; + + mvmvif = container_of(wk, struct iwl_mvm_vif, csa_work.work); + vif = container_of((void *)mvmvif, struct ieee80211_vif, drv_priv); + + /* Trigger disconnect (should clear the CSA state) */ + ieee80211_chswitch_done(vif, false); +} + +static int iwl_mvm_mac_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + mvmvif->mvm = mvm; + RCU_INIT_POINTER(mvmvif->probe_resp_data, NULL); + + /* + * 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. + */ + + mutex_lock(&mvm->mutex); + + /* make sure that beacon statistics don't go backwards with FW reset */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) + mvmvif->beacon_stats.accu_num_beacons += + mvmvif->beacon_stats.num_beacons; + + /* Allocate resources for the MAC context, and add it to the fw */ + ret = iwl_mvm_mac_ctxt_init(mvm, vif); + if (ret) + goto out_unlock; + + rcu_assign_pointer(mvm->vif_id_to_mac[mvmvif->id], vif); + + /* + * The AP binding flow can be done only after the beacon + * template is configured (which happens only in the mac80211 + * start_ap() flow), and adding the broadcast station can happen + * only after the binding. + * In addition, since modifying the MAC before adding a bcast + * station is not allowed by the FW, delay the adding of MAC context to + * the point where we can also add the bcast station. + * In short: there's not much we can do at this point, other than + * allocating resources :) + */ + if (vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_ADHOC) { + ret = iwl_mvm_alloc_bcast_sta(mvm, vif); + if (ret) { + IWL_ERR(mvm, "Failed to allocate bcast sta\n"); + goto out_unlock; + } + + /* + * Only queue for this station is the mcast queue, + * which shouldn't be in TFD mask anyway + */ + ret = iwl_mvm_allocate_int_sta(mvm, &mvmvif->mcast_sta, + 0, vif->type, + IWL_STA_MULTICAST); + if (ret) + goto out_unlock; + + iwl_mvm_vif_dbgfs_register(mvm, vif); + goto out_unlock; + } + + mvmvif->features |= hw->netdev_features; + + ret = iwl_mvm_mac_ctxt_add(mvm, vif); + if (ret) + goto out_unlock; + + ret = iwl_mvm_power_update_mac(mvm); + if (ret) + goto out_remove_mac; + + /* beacon filtering */ + ret = iwl_mvm_disable_beacon_filter(mvm, vif, 0); + if (ret) + goto out_remove_mac; + + if (!mvm->bf_allowed_vif && + vif->type == NL80211_IFTYPE_STATION && !vif->p2p) { + mvm->bf_allowed_vif = mvmvif; + vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER | + IEEE80211_VIF_SUPPORTS_CQM_RSSI; + } + + /* + * P2P_DEVICE interface does not have a channel context assigned to it, + * so a dedicated PHY context is allocated to it and the corresponding + * MAC context is bound to it at this stage. + */ + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { + + mvmvif->phy_ctxt = iwl_mvm_get_free_phy_ctxt(mvm); + if (!mvmvif->phy_ctxt) { + ret = -ENOSPC; + goto out_free_bf; + } + + iwl_mvm_phy_ctxt_ref(mvm, mvmvif->phy_ctxt); + ret = iwl_mvm_binding_add_vif(mvm, vif); + if (ret) + goto out_unref_phy; + + ret = iwl_mvm_add_p2p_bcast_sta(mvm, vif); + if (ret) + goto out_unbind; + + /* Save a pointer to p2p device vif, so it can later be used to + * update the p2p device MAC when a GO is started/stopped */ + mvm->p2p_device_vif = vif; + } + + iwl_mvm_tcm_add_vif(mvm, vif); + INIT_DELAYED_WORK(&mvmvif->csa_work, + iwl_mvm_channel_switch_disconnect_wk); + + if (vif->type == NL80211_IFTYPE_MONITOR) + mvm->monitor_on = true; + + iwl_mvm_vif_dbgfs_register(mvm, vif); + + if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) && + vif->type == NL80211_IFTYPE_STATION && !vif->p2p && + !mvm->csme_vif && mvm->mei_registered) { + iwl_mei_set_nic_info(vif->addr, mvm->nvm_data->hw_addr); + iwl_mei_set_netdev(ieee80211_vif_to_wdev(vif)->netdev); + mvm->csme_vif = vif; + } + + goto out_unlock; + + out_unbind: + iwl_mvm_binding_remove_vif(mvm, vif); + out_unref_phy: + iwl_mvm_phy_ctxt_unref(mvm, mvmvif->phy_ctxt); + out_free_bf: + if (mvm->bf_allowed_vif == mvmvif) { + mvm->bf_allowed_vif = NULL; + vif->driver_flags &= ~(IEEE80211_VIF_BEACON_FILTER | + IEEE80211_VIF_SUPPORTS_CQM_RSSI); + } + out_remove_mac: + mvmvif->phy_ctxt = NULL; + iwl_mvm_mac_ctxt_remove(mvm, vif); + out_unlock: + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_prepare_mac_removal(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { + /* + * Flush the ROC worker which will flush the OFFCHANNEL queue. + * We assume here that all the packets sent to the OFFCHANNEL + * queue are sent in ROC session. + */ + flush_work(&mvm->roc_done_wk); + } +} + +static void iwl_mvm_mac_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_probe_resp_data *probe_data; + + iwl_mvm_prepare_mac_removal(mvm, vif); + + if (!(vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_ADHOC)) + iwl_mvm_tcm_rm_vif(mvm, vif); + + mutex_lock(&mvm->mutex); + + if (vif == mvm->csme_vif) { + iwl_mei_set_netdev(NULL); + mvm->csme_vif = NULL; + } + + probe_data = rcu_dereference_protected(mvmvif->probe_resp_data, + lockdep_is_held(&mvm->mutex)); + RCU_INIT_POINTER(mvmvif->probe_resp_data, NULL); + if (probe_data) + kfree_rcu(probe_data, rcu_head); + + if (mvm->bf_allowed_vif == mvmvif) { + mvm->bf_allowed_vif = NULL; + vif->driver_flags &= ~(IEEE80211_VIF_BEACON_FILTER | + IEEE80211_VIF_SUPPORTS_CQM_RSSI); + } + + if (vif->bss_conf.ftm_responder) + memset(&mvm->ftm_resp_stats, 0, sizeof(mvm->ftm_resp_stats)); + + iwl_mvm_vif_dbgfs_clean(mvm, vif); + + /* + * For AP/GO interface, the tear down of the resources allocated to the + * interface is be handled as part of the stop_ap flow. + */ + if (vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_ADHOC) { +#ifdef CONFIG_NL80211_TESTMODE + if (vif == mvm->noa_vif) { + mvm->noa_vif = NULL; + mvm->noa_duration = 0; + } +#endif + iwl_mvm_dealloc_int_sta(mvm, &mvmvif->mcast_sta); + iwl_mvm_dealloc_bcast_sta(mvm, vif); + goto out_release; + } + + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { + mvm->p2p_device_vif = NULL; + iwl_mvm_rm_p2p_bcast_sta(mvm, vif); + iwl_mvm_binding_remove_vif(mvm, vif); + iwl_mvm_phy_ctxt_unref(mvm, mvmvif->phy_ctxt); + mvmvif->phy_ctxt = NULL; + } + + iwl_mvm_power_update_mac(mvm); + iwl_mvm_mac_ctxt_remove(mvm, vif); + + RCU_INIT_POINTER(mvm->vif_id_to_mac[mvmvif->id], NULL); + + if (vif->type == NL80211_IFTYPE_MONITOR) + mvm->monitor_on = false; + +out_release: + mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_mac_config(struct ieee80211_hw *hw, u32 changed) +{ + return 0; +} + +struct iwl_mvm_mc_iter_data { + struct iwl_mvm *mvm; + int port_id; +}; + +static void iwl_mvm_mc_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_mc_iter_data *data = _data; + struct iwl_mvm *mvm = data->mvm; + struct iwl_mcast_filter_cmd *cmd = mvm->mcast_filter_cmd; + struct iwl_host_cmd hcmd = { + .id = MCAST_FILTER_CMD, + .flags = CMD_ASYNC, + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + }; + int ret, len; + + /* if we don't have free ports, mcast frames will be dropped */ + if (WARN_ON_ONCE(data->port_id >= MAX_PORT_ID_NUM)) + return; + + if (vif->type != NL80211_IFTYPE_STATION || + !vif->cfg.assoc) + return; + + cmd->port_id = data->port_id++; + memcpy(cmd->bssid, vif->bss_conf.bssid, ETH_ALEN); + len = roundup(sizeof(*cmd) + cmd->count * ETH_ALEN, 4); + + hcmd.len[0] = len; + hcmd.data[0] = cmd; + + ret = iwl_mvm_send_cmd(mvm, &hcmd); + if (ret) + IWL_ERR(mvm, "mcast filter cmd error. ret=%d\n", ret); +} + +static void iwl_mvm_recalc_multicast(struct iwl_mvm *mvm) +{ + struct iwl_mvm_mc_iter_data iter_data = { + .mvm = mvm, + }; + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON_ONCE(!mvm->mcast_filter_cmd)) + return; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_mc_iface_iterator, &iter_data); + + /* + * Send a (synchronous) ech command so that we wait for the + * multiple asynchronous MCAST_FILTER_CMD commands sent by + * the interface iterator. Otherwise, we might get here over + * and over again (by userspace just sending a lot of these) + * and the CPU can send them faster than the firmware can + * process them. + * Note that the CPU is still faster - but with this we'll + * actually send fewer commands overall because the CPU will + * not schedule the work in mac80211 as frequently if it's + * still running when rescheduled (possibly multiple times). + */ + ret = iwl_mvm_send_cmd_pdu(mvm, ECHO_CMD, 0, 0, NULL); + if (ret) + IWL_ERR(mvm, "Failed to synchronize multicast groups update\n"); +} + +static u64 iwl_mvm_prepare_multicast(struct ieee80211_hw *hw, + struct netdev_hw_addr_list *mc_list) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mcast_filter_cmd *cmd; + struct netdev_hw_addr *addr; + int addr_count; + bool pass_all; + int len; + + addr_count = netdev_hw_addr_list_count(mc_list); + pass_all = addr_count > MAX_MCAST_FILTERING_ADDRESSES || + IWL_MVM_FW_MCAST_FILTER_PASS_ALL; + if (pass_all) + addr_count = 0; + + len = roundup(sizeof(*cmd) + addr_count * ETH_ALEN, 4); + cmd = kzalloc(len, GFP_ATOMIC); + if (!cmd) + return 0; + + if (pass_all) { + cmd->pass_all = 1; + return (u64)(unsigned long)cmd; + } + + netdev_hw_addr_list_for_each(addr, mc_list) { + IWL_DEBUG_MAC80211(mvm, "mcast addr (%d): %pM\n", + cmd->count, addr->addr); + memcpy(&cmd->addr_list[cmd->count * ETH_ALEN], + addr->addr, ETH_ALEN); + cmd->count++; + } + + return (u64)(unsigned long)cmd; +} + +static void iwl_mvm_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mcast_filter_cmd *cmd = (void *)(unsigned long)multicast; + + mutex_lock(&mvm->mutex); + + /* replace previous configuration */ + kfree(mvm->mcast_filter_cmd); + mvm->mcast_filter_cmd = cmd; + + if (!cmd) + goto out; + + if (changed_flags & FIF_ALLMULTI) + cmd->pass_all = !!(*total_flags & FIF_ALLMULTI); + + if (cmd->pass_all) + cmd->count = 0; + + iwl_mvm_recalc_multicast(mvm); +out: + mutex_unlock(&mvm->mutex); + *total_flags = 0; +} + +static void iwl_mvm_config_iface_filter(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + unsigned int filter_flags, + unsigned int changed_flags) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + /* We support only filter for probe requests */ + if (!(changed_flags & FIF_PROBE_REQ)) + return; + + /* Supported only for p2p client interfaces */ + if (vif->type != NL80211_IFTYPE_STATION || !vif->cfg.assoc || + !vif->p2p) + return; + + mutex_lock(&mvm->mutex); + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_update_mu_groups(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mu_group_mgmt_cmd cmd = {}; + + memcpy(cmd.membership_status, vif->bss_conf.mu_group.membership, + WLAN_MEMBERSHIP_LEN); + memcpy(cmd.user_position, vif->bss_conf.mu_group.position, + WLAN_USER_POSITION_LEN); + + return iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(DATA_PATH_GROUP, + UPDATE_MU_GROUPS_CMD), + 0, sizeof(cmd), &cmd); +} + +static void iwl_mvm_mu_mimo_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + if (vif->bss_conf.mu_mimo_owner) { + struct iwl_mu_group_mgmt_notif *notif = _data; + + /* + * MU-MIMO Group Id action frame is little endian. We treat + * the data received from firmware as if it came from the + * action frame, so no conversion is needed. + */ + ieee80211_update_mu_groups(vif, 0, + (u8 *)¬if->membership_status, + (u8 *)¬if->user_position); + } +} + +void iwl_mvm_mu_mimo_grp_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mu_group_mgmt_notif *notif = (void *)pkt->data; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_mu_mimo_iface_iterator, notif); +} + +static u8 iwl_mvm_he_get_ppe_val(u8 *ppe, u8 ppe_pos_bit) +{ + u8 byte_num = ppe_pos_bit / 8; + u8 bit_num = ppe_pos_bit % 8; + u8 residue_bits; + u8 res; + + if (bit_num <= 5) + return (ppe[byte_num] >> bit_num) & + (BIT(IEEE80211_PPE_THRES_INFO_PPET_SIZE) - 1); + + /* + * If bit_num > 5, we have to combine bits with next byte. + * Calculate how many bits we need to take from current byte (called + * here "residue_bits"), and add them to bits from next byte. + */ + + residue_bits = 8 - bit_num; + + res = (ppe[byte_num + 1] & + (BIT(IEEE80211_PPE_THRES_INFO_PPET_SIZE - residue_bits) - 1)) << + residue_bits; + res += (ppe[byte_num] >> bit_num) & (BIT(residue_bits) - 1); + + return res; +} + +static void iwl_mvm_parse_ppe(struct iwl_mvm *mvm, + struct iwl_he_pkt_ext_v2 *pkt_ext, u8 nss, + u8 ru_index_bitmap, u8 *ppe, u8 ppe_pos_bit) +{ + int i; + + /* + * FW currently supports only nss == MAX_HE_SUPP_NSS + * + * If nss > MAX: we can ignore values we don't support + * If nss < MAX: we can set zeros in other streams + */ + if (nss > MAX_HE_SUPP_NSS) { + IWL_DEBUG_INFO(mvm, "Got NSS = %d - trimming to %d\n", nss, + MAX_HE_SUPP_NSS); + nss = MAX_HE_SUPP_NSS; + } + + for (i = 0; i < nss; i++) { + u8 ru_index_tmp = ru_index_bitmap << 1; + u8 low_th = IWL_HE_PKT_EXT_NONE, high_th = IWL_HE_PKT_EXT_NONE; + u8 bw; + + for (bw = 0; + bw < ARRAY_SIZE(pkt_ext->pkt_ext_qam_th[i]); + bw++) { + ru_index_tmp >>= 1; + + if (!(ru_index_tmp & 1)) + continue; + + high_th = iwl_mvm_he_get_ppe_val(ppe, ppe_pos_bit); + ppe_pos_bit += IEEE80211_PPE_THRES_INFO_PPET_SIZE; + low_th = iwl_mvm_he_get_ppe_val(ppe, ppe_pos_bit); + ppe_pos_bit += IEEE80211_PPE_THRES_INFO_PPET_SIZE; + + pkt_ext->pkt_ext_qam_th[i][bw][0] = low_th; + pkt_ext->pkt_ext_qam_th[i][bw][1] = high_th; + } + } +} + +static void iwl_mvm_set_pkt_ext_from_he_ppe(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct iwl_he_pkt_ext_v2 *pkt_ext) +{ + u8 nss = (sta->deflink.he_cap.ppe_thres[0] & IEEE80211_PPE_THRES_NSS_MASK) + 1; + u8 *ppe = &sta->deflink.he_cap.ppe_thres[0]; + u8 ru_index_bitmap = + u8_get_bits(*ppe, + IEEE80211_PPE_THRES_RU_INDEX_BITMASK_MASK); + /* Starting after PPE header */ + u8 ppe_pos_bit = IEEE80211_HE_PPE_THRES_INFO_HEADER_SIZE; + + iwl_mvm_parse_ppe(mvm, pkt_ext, nss, ru_index_bitmap, ppe, ppe_pos_bit); +} + +static void iwl_mvm_set_pkt_ext_from_nominal_padding(struct iwl_he_pkt_ext_v2 *pkt_ext, + u8 nominal_padding, + u32 *flags) +{ + int low_th = -1; + int high_th = -1; + int i; + + switch (nominal_padding) { + case IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_0US: + low_th = IWL_HE_PKT_EXT_NONE; + high_th = IWL_HE_PKT_EXT_NONE; + break; + case IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_8US: + low_th = IWL_HE_PKT_EXT_BPSK; + high_th = IWL_HE_PKT_EXT_NONE; + break; + case IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_16US: + low_th = IWL_HE_PKT_EXT_NONE; + high_th = IWL_HE_PKT_EXT_BPSK; + break; + } + + /* Set the PPE thresholds accordingly */ + if (low_th >= 0 && high_th >= 0) { + for (i = 0; i < MAX_HE_SUPP_NSS; i++) { + u8 bw; + + for (bw = 0; + bw < ARRAY_SIZE(pkt_ext->pkt_ext_qam_th[i]); + bw++) { + pkt_ext->pkt_ext_qam_th[i][bw][0] = low_th; + pkt_ext->pkt_ext_qam_th[i][bw][1] = high_th; + } + } + + *flags |= STA_CTXT_HE_PACKET_EXT; + } +} + +static void iwl_mvm_cfg_he_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, u8 sta_id) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_he_sta_context_cmd_v3 sta_ctxt_cmd = { + .sta_id = sta_id, + .tid_limit = IWL_MAX_TID_COUNT, + .bss_color = vif->bss_conf.he_bss_color.color, + .htc_trig_based_pkt_ext = vif->bss_conf.htc_trig_based_pkt_ext, + .frame_time_rts_th = + cpu_to_le16(vif->bss_conf.frame_time_rts_th), + }; + struct iwl_he_sta_context_cmd_v2 sta_ctxt_cmd_v2 = {}; + u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, STA_HE_CTXT_CMD); + u8 ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, 2); + int size; + struct ieee80211_sta *sta; + u32 flags; + int i; + const struct ieee80211_sta_he_cap *own_he_cap = NULL; + struct ieee80211_chanctx_conf *chanctx_conf; + const struct ieee80211_supported_band *sband; + void *cmd; + + if (!fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_MBSSID_HE)) + ver = 1; + + switch (ver) { + case 1: + /* same layout as v2 except some data at the end */ + cmd = &sta_ctxt_cmd_v2; + size = sizeof(struct iwl_he_sta_context_cmd_v1); + break; + case 2: + cmd = &sta_ctxt_cmd_v2; + size = sizeof(struct iwl_he_sta_context_cmd_v2); + break; + case 3: + cmd = &sta_ctxt_cmd; + size = sizeof(struct iwl_he_sta_context_cmd_v3); + break; + default: + IWL_ERR(mvm, "bad STA_HE_CTXT_CMD version %d\n", ver); + return; + } + + rcu_read_lock(); + + chanctx_conf = rcu_dereference(vif->bss_conf.chanctx_conf); + if (WARN_ON(!chanctx_conf)) { + rcu_read_unlock(); + return; + } + + sband = mvm->hw->wiphy->bands[chanctx_conf->def.chan->band]; + own_he_cap = ieee80211_get_he_iftype_cap(sband, + ieee80211_vif_type_p2p(vif)); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_ctxt_cmd.sta_id]); + if (IS_ERR_OR_NULL(sta)) { + rcu_read_unlock(); + WARN(1, "Can't find STA to configure HE\n"); + return; + } + + if (!sta->deflink.he_cap.has_he) { + rcu_read_unlock(); + return; + } + + flags = 0; + + /* Block 26-tone RU OFDMA transmissions */ + if (mvmvif->he_ru_2mhz_block) + flags |= STA_CTXT_HE_RU_2MHZ_BLOCK; + + /* HTC flags */ + if (sta->deflink.he_cap.he_cap_elem.mac_cap_info[0] & + IEEE80211_HE_MAC_CAP0_HTC_HE) + sta_ctxt_cmd.htc_flags |= cpu_to_le32(IWL_HE_HTC_SUPPORT); + if ((sta->deflink.he_cap.he_cap_elem.mac_cap_info[1] & + IEEE80211_HE_MAC_CAP1_LINK_ADAPTATION) || + (sta->deflink.he_cap.he_cap_elem.mac_cap_info[2] & + IEEE80211_HE_MAC_CAP2_LINK_ADAPTATION)) { + u8 link_adap = + ((sta->deflink.he_cap.he_cap_elem.mac_cap_info[2] & + IEEE80211_HE_MAC_CAP2_LINK_ADAPTATION) << 1) + + (sta->deflink.he_cap.he_cap_elem.mac_cap_info[1] & + IEEE80211_HE_MAC_CAP1_LINK_ADAPTATION); + + if (link_adap == 2) + sta_ctxt_cmd.htc_flags |= + cpu_to_le32(IWL_HE_HTC_LINK_ADAP_UNSOLICITED); + else if (link_adap == 3) + sta_ctxt_cmd.htc_flags |= + cpu_to_le32(IWL_HE_HTC_LINK_ADAP_BOTH); + } + if (sta->deflink.he_cap.he_cap_elem.mac_cap_info[2] & IEEE80211_HE_MAC_CAP2_BSR) + sta_ctxt_cmd.htc_flags |= cpu_to_le32(IWL_HE_HTC_BSR_SUPP); + if (sta->deflink.he_cap.he_cap_elem.mac_cap_info[3] & + IEEE80211_HE_MAC_CAP3_OMI_CONTROL) + sta_ctxt_cmd.htc_flags |= cpu_to_le32(IWL_HE_HTC_OMI_SUPP); + if (sta->deflink.he_cap.he_cap_elem.mac_cap_info[4] & IEEE80211_HE_MAC_CAP4_BQR) + sta_ctxt_cmd.htc_flags |= cpu_to_le32(IWL_HE_HTC_BQR_SUPP); + + /* + * Initialize the PPE thresholds to "None" (7), as described in Table + * 9-262ac of 80211.ax/D3.0. + */ + memset(&sta_ctxt_cmd.pkt_ext, IWL_HE_PKT_EXT_NONE, + sizeof(sta_ctxt_cmd.pkt_ext)); + + /* If PPE Thresholds exist, parse them into a FW-familiar format. */ + if (sta->deflink.he_cap.he_cap_elem.phy_cap_info[6] & + IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT) { + iwl_mvm_set_pkt_ext_from_he_ppe(mvm, sta, + &sta_ctxt_cmd.pkt_ext); + flags |= STA_CTXT_HE_PACKET_EXT; + /* PPE Thresholds doesn't exist - set the API PPE values + * according to Common Nominal Packet Padding fiels. */ + } else { + u8 nominal_padding = + u8_get_bits(sta->deflink.he_cap.he_cap_elem.phy_cap_info[9], + IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_MASK); + if (nominal_padding != IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_RESERVED) + iwl_mvm_set_pkt_ext_from_nominal_padding(&sta_ctxt_cmd.pkt_ext, + nominal_padding, + &flags); + } + + if (sta->deflink.he_cap.he_cap_elem.mac_cap_info[2] & + IEEE80211_HE_MAC_CAP2_32BIT_BA_BITMAP) + flags |= STA_CTXT_HE_32BIT_BA_BITMAP; + + if (sta->deflink.he_cap.he_cap_elem.mac_cap_info[2] & + IEEE80211_HE_MAC_CAP2_ACK_EN) + flags |= STA_CTXT_HE_ACK_ENABLED; + + rcu_read_unlock(); + + /* Mark MU EDCA as enabled, unless none detected on some AC */ + flags |= STA_CTXT_HE_MU_EDCA_CW; + for (i = 0; i < IEEE80211_NUM_ACS; i++) { + struct ieee80211_he_mu_edca_param_ac_rec *mu_edca = + &mvmvif->queue_params[i].mu_edca_param_rec; + u8 ac = iwl_mvm_mac80211_ac_to_ucode_ac(i); + + if (!mvmvif->queue_params[i].mu_edca) { + flags &= ~STA_CTXT_HE_MU_EDCA_CW; + break; + } + + sta_ctxt_cmd.trig_based_txf[ac].cwmin = + cpu_to_le16(mu_edca->ecw_min_max & 0xf); + sta_ctxt_cmd.trig_based_txf[ac].cwmax = + cpu_to_le16((mu_edca->ecw_min_max & 0xf0) >> 4); + sta_ctxt_cmd.trig_based_txf[ac].aifsn = + cpu_to_le16(mu_edca->aifsn); + sta_ctxt_cmd.trig_based_txf[ac].mu_time = + cpu_to_le16(mu_edca->mu_edca_timer); + } + + + if (vif->bss_conf.uora_exists) { + flags |= STA_CTXT_HE_TRIG_RND_ALLOC; + + sta_ctxt_cmd.rand_alloc_ecwmin = + vif->bss_conf.uora_ocw_range & 0x7; + sta_ctxt_cmd.rand_alloc_ecwmax = + (vif->bss_conf.uora_ocw_range >> 3) & 0x7; + } + + if (own_he_cap && !(own_he_cap->he_cap_elem.mac_cap_info[2] & + IEEE80211_HE_MAC_CAP2_ACK_EN)) + flags |= STA_CTXT_HE_NIC_NOT_ACK_ENABLED; + + if (vif->bss_conf.nontransmitted) { + flags |= STA_CTXT_HE_REF_BSSID_VALID; + ether_addr_copy(sta_ctxt_cmd.ref_bssid_addr, + vif->bss_conf.transmitter_bssid); + sta_ctxt_cmd.max_bssid_indicator = + vif->bss_conf.bssid_indicator; + sta_ctxt_cmd.bssid_index = vif->bss_conf.bssid_index; + sta_ctxt_cmd.ema_ap = vif->bss_conf.ema_ap; + sta_ctxt_cmd.profile_periodicity = + vif->bss_conf.profile_periodicity; + } + + sta_ctxt_cmd.flags = cpu_to_le32(flags); + + if (ver < 3) { + /* fields before pkt_ext */ + BUILD_BUG_ON(offsetof(typeof(sta_ctxt_cmd), pkt_ext) != + offsetof(typeof(sta_ctxt_cmd_v2), pkt_ext)); + memcpy(&sta_ctxt_cmd_v2, &sta_ctxt_cmd, + offsetof(typeof(sta_ctxt_cmd), pkt_ext)); + + /* pkt_ext */ + for (i = 0; + i < ARRAY_SIZE(sta_ctxt_cmd_v2.pkt_ext.pkt_ext_qam_th); + i++) { + u8 bw; + + for (bw = 0; + bw < ARRAY_SIZE(sta_ctxt_cmd_v2.pkt_ext.pkt_ext_qam_th[i]); + bw++) { + BUILD_BUG_ON(sizeof(sta_ctxt_cmd.pkt_ext.pkt_ext_qam_th[i][bw]) != + sizeof(sta_ctxt_cmd_v2.pkt_ext.pkt_ext_qam_th[i][bw])); + + memcpy(&sta_ctxt_cmd_v2.pkt_ext.pkt_ext_qam_th[i][bw], + &sta_ctxt_cmd.pkt_ext.pkt_ext_qam_th[i][bw], + sizeof(sta_ctxt_cmd.pkt_ext.pkt_ext_qam_th[i][bw])); + } + } + + /* fields after pkt_ext */ + BUILD_BUG_ON(sizeof(sta_ctxt_cmd) - + offsetofend(typeof(sta_ctxt_cmd), pkt_ext) != + sizeof(sta_ctxt_cmd_v2) - + offsetofend(typeof(sta_ctxt_cmd_v2), pkt_ext)); + memcpy((u8 *)&sta_ctxt_cmd_v2 + + offsetofend(typeof(sta_ctxt_cmd_v2), pkt_ext), + (u8 *)&sta_ctxt_cmd + + offsetofend(typeof(sta_ctxt_cmd), pkt_ext), + sizeof(sta_ctxt_cmd) - + offsetofend(typeof(sta_ctxt_cmd), pkt_ext)); + sta_ctxt_cmd_v2.reserved3 = 0; + } + + if (iwl_mvm_send_cmd_pdu(mvm, cmd_id, 0, size, cmd)) + IWL_ERR(mvm, "Failed to config FW to work HE!\n"); +} + +static void iwl_mvm_protect_assoc(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 duration_override) +{ + u32 duration = IWL_MVM_TE_SESSION_PROTECTION_MAX_TIME_MS; + u32 min_duration = IWL_MVM_TE_SESSION_PROTECTION_MIN_TIME_MS; + + if (duration_override > duration) + duration = duration_override; + + /* Try really hard to protect the session and hear a beacon + * The new session protection command allows us to protect the + * session for a much longer time since the firmware will internally + * create two events: a 300TU one with a very high priority that + * won't be fragmented which should be enough for 99% of the cases, + * and another one (which we configure here to be 900TU long) which + * will have a slightly lower priority, but more importantly, can be + * fragmented so that it'll allow other activities to run. + */ + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) + iwl_mvm_schedule_session_protection(mvm, vif, 900, + min_duration, false); + else + iwl_mvm_protect_session(mvm, vif, duration, + min_duration, 500, false); +} + +static void iwl_mvm_bss_info_changed_station(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u64 changes) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + /* + * Re-calculate the tsf id, as the leader-follower relations depend + * on the beacon interval, which was not known when the station + * interface was added. + */ + if (changes & BSS_CHANGED_ASSOC && vif->cfg.assoc) { + if (vif->bss_conf.he_support && + !iwlwifi_mod_params.disable_11ax) + iwl_mvm_cfg_he_sta(mvm, vif, mvmvif->ap_sta_id); + + iwl_mvm_mac_ctxt_recalc_tsf_id(mvm, vif); + } + + /* Update MU EDCA params */ + if (changes & BSS_CHANGED_QOS && mvmvif->associated && + vif->cfg.assoc && vif->bss_conf.he_support && + !iwlwifi_mod_params.disable_11ax) + iwl_mvm_cfg_he_sta(mvm, vif, mvmvif->ap_sta_id); + + /* + * If we're not associated yet, take the (new) BSSID before associating + * so the firmware knows. If we're already associated, then use the old + * BSSID here, and we'll send a cleared one later in the CHANGED_ASSOC + * branch for disassociation below. + */ + if (changes & BSS_CHANGED_BSSID && !mvmvif->associated) + memcpy(mvmvif->bssid, bss_conf->bssid, ETH_ALEN); + + ret = iwl_mvm_mac_ctxt_changed(mvm, vif, false, mvmvif->bssid); + if (ret) + IWL_ERR(mvm, "failed to update MAC %pM\n", vif->addr); + + /* after sending it once, adopt mac80211 data */ + memcpy(mvmvif->bssid, bss_conf->bssid, ETH_ALEN); + mvmvif->associated = vif->cfg.assoc; + + if (changes & BSS_CHANGED_ASSOC) { + if (vif->cfg.assoc) { + /* clear statistics to get clean beacon counter */ + iwl_mvm_request_statistics(mvm, true); + memset(&mvmvif->beacon_stats, 0, + sizeof(mvmvif->beacon_stats)); + + /* add quota for this interface */ + ret = iwl_mvm_update_quotas(mvm, true, NULL); + if (ret) { + IWL_ERR(mvm, "failed to update quotas\n"); + return; + } + + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, + &mvm->status) && + !fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) { + /* + * If we're restarting then the firmware will + * obviously have lost synchronisation with + * the AP. It will attempt to synchronise by + * itself, but we can make it more reliable by + * scheduling a session protection time event. + * + * The firmware needs to receive a beacon to + * catch up with synchronisation, use 110% of + * the beacon interval. + * + * Set a large maximum delay to allow for more + * than a single interface. + * + * For new firmware versions, rely on the + * firmware. This is relevant for DCM scenarios + * only anyway. + */ + u32 dur = (11 * vif->bss_conf.beacon_int) / 10; + iwl_mvm_protect_session(mvm, vif, dur, dur, + 5 * dur, false); + } else if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, + &mvm->status) && + !vif->bss_conf.dtim_period) { + /* + * If we're not restarting and still haven't + * heard a beacon (dtim period unknown) then + * make sure we still have enough minimum time + * remaining in the time event, since the auth + * might actually have taken quite a while + * (especially for SAE) and so the remaining + * time could be small without us having heard + * a beacon yet. + */ + iwl_mvm_protect_assoc(mvm, vif, 0); + } + + iwl_mvm_sf_update(mvm, vif, false); + iwl_mvm_power_vif_assoc(mvm, vif); + if (vif->p2p) { + iwl_mvm_update_smps(mvm, vif, + IWL_MVM_SMPS_REQ_PROT, + IEEE80211_SMPS_DYNAMIC); + } + } else if (mvmvif->ap_sta_id != IWL_MVM_INVALID_STA) { + iwl_mvm_mei_host_disassociated(mvm); + /* + * If update fails - SF might be running in associated + * mode while disassociated - which is forbidden. + */ + ret = iwl_mvm_sf_update(mvm, vif, false); + WARN_ONCE(ret && + !test_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, + &mvm->status), + "Failed to update SF upon disassociation\n"); + + /* + * If we get an assert during the connection (after the + * station has been added, but before the vif is set + * to associated), mac80211 will re-add the station and + * then configure the vif. Since the vif is not + * associated, we would remove the station here and + * this would fail the recovery. + */ + if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, + &mvm->status)) { + /* + * Remove AP station now that + * the MAC is unassoc + */ + ret = iwl_mvm_rm_sta_id(mvm, vif, + mvmvif->ap_sta_id); + if (ret) + IWL_ERR(mvm, + "failed to remove AP station\n"); + + mvmvif->ap_sta_id = IWL_MVM_INVALID_STA; + } + + /* remove quota for this interface */ + ret = iwl_mvm_update_quotas(mvm, false, NULL); + if (ret) + IWL_ERR(mvm, "failed to update quotas\n"); + + /* this will take the cleared BSSID from bss_conf */ + ret = iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + if (ret) + IWL_ERR(mvm, + "failed to update MAC %pM (clear after unassoc)\n", + vif->addr); + } + + /* + * The firmware tracks the MU-MIMO group on its own. + * However, on HW restart we should restore this data. + */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) && + (changes & BSS_CHANGED_MU_GROUPS) && vif->bss_conf.mu_mimo_owner) { + ret = iwl_mvm_update_mu_groups(mvm, vif); + if (ret) + IWL_ERR(mvm, + "failed to update VHT MU_MIMO groups\n"); + } + + iwl_mvm_recalc_multicast(mvm); + + /* reset rssi values */ + mvmvif->bf_data.ave_beacon_signal = 0; + + iwl_mvm_bt_coex_vif_change(mvm); + iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_TT, + IEEE80211_SMPS_AUTOMATIC); + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_UMAC_SCAN)) + iwl_mvm_config_scan(mvm); + } + + if (changes & BSS_CHANGED_BEACON_INFO) { + /* + * We received a beacon from the associated AP so + * remove the session protection. + */ + iwl_mvm_stop_session_protection(mvm, vif); + + iwl_mvm_sf_update(mvm, vif, false); + WARN_ON(iwl_mvm_enable_beacon_filter(mvm, vif, 0)); + } + + if (changes & (BSS_CHANGED_PS | BSS_CHANGED_P2P_PS | BSS_CHANGED_QOS | + /* + * Send power command on every beacon change, + * because we may have not enabled beacon abort yet. + */ + BSS_CHANGED_BEACON_INFO)) { + ret = iwl_mvm_power_update_mac(mvm); + if (ret) + IWL_ERR(mvm, "failed to update power mode\n"); + } + + if (changes & BSS_CHANGED_CQM) { + IWL_DEBUG_MAC80211(mvm, "cqm info_changed\n"); + /* reset cqm events tracking */ + mvmvif->bf_data.last_cqm_event = 0; + if (mvmvif->bf_data.bf_enabled) { + ret = iwl_mvm_enable_beacon_filter(mvm, vif, 0); + if (ret) + IWL_ERR(mvm, + "failed to update CQM thresholds\n"); + } + } + + if (changes & BSS_CHANGED_BANDWIDTH) + iwl_mvm_apply_fw_smps_request(vif); +} + +static int iwl_mvm_start_ap_ibss(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret, i; + + mutex_lock(&mvm->mutex); + + /* Send the beacon template */ + ret = iwl_mvm_mac_ctxt_beacon_changed(mvm, vif); + if (ret) + goto out_unlock; + + /* + * Re-calculate the tsf id, as the leader-follower relations depend on + * the beacon interval, which was not known when the AP interface + * was added. + */ + if (vif->type == NL80211_IFTYPE_AP) + iwl_mvm_mac_ctxt_recalc_tsf_id(mvm, vif); + + mvmvif->ap_assoc_sta_count = 0; + + /* Add the mac context */ + ret = iwl_mvm_mac_ctxt_add(mvm, vif); + if (ret) + goto out_unlock; + + /* Perform the binding */ + ret = iwl_mvm_binding_add_vif(mvm, vif); + if (ret) + goto out_remove; + + /* + * This is not very nice, but the simplest: + * For older FWs adding the mcast sta before the bcast station may + * cause assert 0x2b00. + * This is fixed in later FW so make the order of removal depend on + * the TLV + */ + if (fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_STA_TYPE)) { + ret = iwl_mvm_add_mcast_sta(mvm, vif); + if (ret) + goto out_unbind; + /* + * Send the bcast station. At this stage the TBTT and DTIM time + * events are added and applied to the scheduler + */ + ret = iwl_mvm_send_add_bcast_sta(mvm, vif); + if (ret) { + iwl_mvm_rm_mcast_sta(mvm, vif); + goto out_unbind; + } + } else { + /* + * Send the bcast station. At this stage the TBTT and DTIM time + * events are added and applied to the scheduler + */ + ret = iwl_mvm_send_add_bcast_sta(mvm, vif); + if (ret) + goto out_unbind; + ret = iwl_mvm_add_mcast_sta(mvm, vif); + if (ret) { + iwl_mvm_send_rm_bcast_sta(mvm, vif); + goto out_unbind; + } + } + + /* must be set before quota calculations */ + mvmvif->ap_ibss_active = true; + + /* send all the early keys to the device now */ + for (i = 0; i < ARRAY_SIZE(mvmvif->ap_early_keys); i++) { + struct ieee80211_key_conf *key = mvmvif->ap_early_keys[i]; + + if (!key) + continue; + + mvmvif->ap_early_keys[i] = NULL; + + ret = __iwl_mvm_mac_set_key(hw, SET_KEY, vif, NULL, key); + if (ret) + goto out_quota_failed; + } + + if (vif->type == NL80211_IFTYPE_AP && !vif->p2p) { + iwl_mvm_vif_set_low_latency(mvmvif, true, + LOW_LATENCY_VIF_TYPE); + iwl_mvm_send_low_latency_cmd(mvm, true, mvmvif->id); + } + + /* power updated needs to be done before quotas */ + iwl_mvm_power_update_mac(mvm); + + ret = iwl_mvm_update_quotas(mvm, false, NULL); + if (ret) + goto out_quota_failed; + + /* Need to update the P2P Device MAC (only GO, IBSS is single vif) */ + if (vif->p2p && mvm->p2p_device_vif) + iwl_mvm_mac_ctxt_changed(mvm, mvm->p2p_device_vif, false, NULL); + + 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); + + iwl_mvm_ftm_restart_responder(mvm, vif); + + goto out_unlock; + +out_quota_failed: + iwl_mvm_power_update_mac(mvm); + mvmvif->ap_ibss_active = false; + iwl_mvm_send_rm_bcast_sta(mvm, vif); + iwl_mvm_rm_mcast_sta(mvm, vif); +out_unbind: + iwl_mvm_binding_remove_vif(mvm, vif); +out_remove: + iwl_mvm_mac_ctxt_remove(mvm, vif); +out_unlock: + mutex_unlock(&mvm->mutex); + return ret; +} + +static int iwl_mvm_start_ap(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf) +{ + return iwl_mvm_start_ap_ibss(hw, vif, link_conf); +} + +static int iwl_mvm_start_ibss(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + return iwl_mvm_start_ap_ibss(hw, vif, &vif->bss_conf); +} + +static void iwl_mvm_stop_ap_ibss(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + iwl_mvm_prepare_mac_removal(mvm, vif); + + 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); + mvmvif->csa_countdown = false; + } + + 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; + + if (vif->type == NL80211_IFTYPE_AP && !vif->p2p) { + iwl_mvm_vif_set_low_latency(mvmvif, false, + LOW_LATENCY_VIF_TYPE); + iwl_mvm_send_low_latency_cmd(mvm, false, mvmvif->id); + } + + iwl_mvm_bt_coex_vif_change(mvm); + + /* Need to update the P2P Device MAC (only GO, IBSS is single vif) */ + if (vif->p2p && mvm->p2p_device_vif) + iwl_mvm_mac_ctxt_changed(mvm, mvm->p2p_device_vif, false, NULL); + + iwl_mvm_update_quotas(mvm, false, NULL); + + iwl_mvm_ftm_responder_clear(mvm, vif); + + /* + * This is not very nice, but the simplest: + * For older FWs removing the mcast sta before the bcast station may + * cause assert 0x2b00. + * This is fixed in later FW (which will stop beaconing when removing + * bcast station). + * So make the order of removal depend on the TLV + */ + if (!fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_STA_TYPE)) + iwl_mvm_rm_mcast_sta(mvm, vif); + iwl_mvm_send_rm_bcast_sta(mvm, vif); + if (fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_STA_TYPE)) + iwl_mvm_rm_mcast_sta(mvm, vif); + iwl_mvm_binding_remove_vif(mvm, vif); + + iwl_mvm_power_update_mac(mvm); + + iwl_mvm_mac_ctxt_remove(mvm, vif); + + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_stop_ap(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf) +{ + iwl_mvm_stop_ap_ibss(hw, vif, link_conf); +} + +static void iwl_mvm_stop_ibss(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + iwl_mvm_stop_ap_ibss(hw, vif, &vif->bss_conf); +} + +static void +iwl_mvm_bss_info_changed_ap_ibss(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u64 changes) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + /* Changes will be applied when the AP/IBSS is started */ + if (!mvmvif->ap_ibss_active) + return; + + if (changes & (BSS_CHANGED_ERP_CTS_PROT | BSS_CHANGED_HT | + BSS_CHANGED_BANDWIDTH | BSS_CHANGED_QOS) && + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL)) + IWL_ERR(mvm, "failed to update MAC %pM\n", vif->addr); + + /* Need to send a new beacon template to the FW */ + if (changes & BSS_CHANGED_BEACON && + iwl_mvm_mac_ctxt_beacon_changed(mvm, vif)) + IWL_WARN(mvm, "Failed updating beacon data\n"); + + if (changes & BSS_CHANGED_FTM_RESPONDER) { + int ret = iwl_mvm_ftm_start_responder(mvm, vif); + + if (ret) + IWL_WARN(mvm, "Failed to enable FTM responder (%d)\n", + ret); + } + +} + +static void iwl_mvm_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u64 changes) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mutex_lock(&mvm->mutex); + + if (changes & BSS_CHANGED_IDLE && !vif->cfg.idle) + iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_SCHED, true); + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + iwl_mvm_bss_info_changed_station(mvm, vif, bss_conf, changes); + break; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_ADHOC: + iwl_mvm_bss_info_changed_ap_ibss(mvm, vif, bss_conf, changes); + break; + case NL80211_IFTYPE_MONITOR: + if (changes & BSS_CHANGED_MU_GROUPS) + iwl_mvm_update_mu_groups(mvm, vif); + break; + default: + /* shouldn't happen */ + WARN_ON_ONCE(1); + } + + if (changes & BSS_CHANGED_TXPOWER) { + IWL_DEBUG_CALIB(mvm, "Changing TX Power to %d dBm\n", + bss_conf->txpower); + iwl_mvm_set_tx_power(mvm, vif, bss_conf->txpower); + } + + mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_mac_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_scan_request *hw_req) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + if (hw_req->req.n_channels == 0 || + hw_req->req.n_channels > mvm->fw->ucode_capa.n_scan_channels) + return -EINVAL; + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_reg_scan_start(mvm, vif, &hw_req->req, &hw_req->ies); + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_mac_cancel_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mutex_lock(&mvm->mutex); + + /* Due to a race condition, it's possible that mac80211 asks + * us to stop a hw_scan when it's already stopped. This can + * happen, for instance, if we stopped the scan ourselves, + * called ieee80211_scan_completed() and the userspace called + * cancel scan scan before ieee80211_scan_work() could run. + * To handle that, simply return if the scan is not running. + */ + if (mvm->scan_status & IWL_MVM_SCAN_REGULAR) + iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_REGULAR, true); + + mutex_unlock(&mvm->mutex); +} + +static void +iwl_mvm_mac_allow_buffered_frames(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u16 tids, + int num_frames, + enum ieee80211_frame_release_type reason, + bool more_data) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + /* Called when we need to transmit (a) frame(s) from mac80211 */ + + iwl_mvm_sta_modify_sleep_tx_count(mvm, sta, reason, num_frames, + tids, more_data, false); +} + +static void +iwl_mvm_mac_release_buffered_frames(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u16 tids, + int num_frames, + enum ieee80211_frame_release_type reason, + bool more_data) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + /* Called when we need to transmit (a) frame(s) from agg or dqa queue */ + + iwl_mvm_sta_modify_sleep_tx_count(mvm, sta, reason, num_frames, + tids, more_data, true); +} + +static void __iwl_mvm_mac_sta_notify(struct ieee80211_hw *hw, + enum sta_notify_cmd cmd, + struct ieee80211_sta *sta) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + unsigned long txqs = 0, tids = 0; + int tid; + + /* + * If we have TVQM then we get too high queue numbers - luckily + * we really shouldn't get here with that because such hardware + * should have firmware supporting buffer station offload. + */ + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return; + + spin_lock_bh(&mvmsta->lock); + for (tid = 0; tid < ARRAY_SIZE(mvmsta->tid_data); tid++) { + struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; + + if (tid_data->txq_id == IWL_MVM_INVALID_QUEUE) + continue; + + __set_bit(tid_data->txq_id, &txqs); + + if (iwl_mvm_tid_queued(mvm, tid_data) == 0) + continue; + + __set_bit(tid, &tids); + } + + switch (cmd) { + case STA_NOTIFY_SLEEP: + for_each_set_bit(tid, &tids, IWL_MAX_TID_COUNT) + ieee80211_sta_set_buffered(sta, tid, true); + + if (txqs) + iwl_trans_freeze_txq_timer(mvm->trans, txqs, true); + /* + * The fw updates the STA to be asleep. Tx packets on the Tx + * queues to this station will not be transmitted. The fw will + * send a Tx response with TX_STATUS_FAIL_DEST_PS. + */ + break; + case STA_NOTIFY_AWAKE: + if (WARN_ON(mvmsta->sta_id == IWL_MVM_INVALID_STA)) + break; + + if (txqs) + iwl_trans_freeze_txq_timer(mvm->trans, txqs, false); + iwl_mvm_sta_modify_ps_wake(mvm, sta); + break; + default: + break; + } + spin_unlock_bh(&mvmsta->lock); +} + +static void iwl_mvm_mac_sta_notify(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum sta_notify_cmd cmd, + struct ieee80211_sta *sta) +{ + __iwl_mvm_mac_sta_notify(hw, cmd, sta); +} + +void iwl_mvm_sta_pm_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mvm_pm_state_notification *notif = (void *)pkt->data; + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + bool sleeping = (notif->type != IWL_MVM_PM_EVENT_AWAKE); + + if (WARN_ON(notif->sta_id >= mvm->fw->ucode_capa.num_stations)) + return; + + rcu_read_lock(); + sta = rcu_dereference(mvm->fw_id_to_mac_id[notif->sta_id]); + if (WARN_ON(IS_ERR_OR_NULL(sta))) { + rcu_read_unlock(); + return; + } + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + if (!mvmsta->vif || + mvmsta->vif->type != NL80211_IFTYPE_AP) { + rcu_read_unlock(); + return; + } + + if (mvmsta->sleeping != sleeping) { + mvmsta->sleeping = sleeping; + __iwl_mvm_mac_sta_notify(mvm->hw, + sleeping ? STA_NOTIFY_SLEEP : STA_NOTIFY_AWAKE, + sta); + ieee80211_sta_ps_transition(sta, sleeping); + } + + if (sleeping) { + switch (notif->type) { + case IWL_MVM_PM_EVENT_AWAKE: + case IWL_MVM_PM_EVENT_ASLEEP: + break; + case IWL_MVM_PM_EVENT_UAPSD: + ieee80211_sta_uapsd_trigger(sta, IEEE80211_NUM_TIDS); + break; + case IWL_MVM_PM_EVENT_PS_POLL: + ieee80211_sta_pspoll(sta); + break; + default: + break; + } + } + + rcu_read_unlock(); +} + +static void iwl_mvm_sta_pre_rcu_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + + /* + * This is called before mac80211 does RCU synchronisation, + * so here we already invalidate our internal RCU-protected + * station pointer. The rest of the code will thus no longer + * be able to find the station this way, and we don't rely + * on further RCU synchronisation after the sta_state() + * callback deleted the station. + */ + mutex_lock(&mvm->mutex); + if (sta == rcu_access_pointer(mvm->fw_id_to_mac_id[mvm_sta->sta_id])) + rcu_assign_pointer(mvm->fw_id_to_mac_id[mvm_sta->sta_id], + ERR_PTR(-ENOENT)); + + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_check_uapsd(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + const u8 *bssid) +{ + int i; + + if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { + struct iwl_mvm_tcm_mac *mdata; + + mdata = &mvm->tcm.data[iwl_mvm_vif_from_mac80211(vif)->id]; + ewma_rate_init(&mdata->uapsd_nonagg_detect.rate); + mdata->opened_rx_ba_sessions = false; + } + + if (!(mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_UAPSD_SUPPORT)) + return; + + if (vif->p2p && !iwl_mvm_is_p2p_scm_uapsd_supported(mvm)) { + vif->driver_flags &= ~IEEE80211_VIF_SUPPORTS_UAPSD; + return; + } + + if (!vif->p2p && + (iwlwifi_mod_params.uapsd_disable & IWL_DISABLE_UAPSD_BSS)) { + vif->driver_flags &= ~IEEE80211_VIF_SUPPORTS_UAPSD; + return; + } + + for (i = 0; i < IWL_MVM_UAPSD_NOAGG_LIST_LEN; i++) { + if (ether_addr_equal(mvm->uapsd_noagg_bssids[i].addr, bssid)) { + vif->driver_flags &= ~IEEE80211_VIF_SUPPORTS_UAPSD; + return; + } + } + + vif->driver_flags |= IEEE80211_VIF_SUPPORTS_UAPSD; +} + +static void +iwl_mvm_tdls_check_trigger(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, u8 *peer_addr, + enum nl80211_tdls_operation action) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_tdls *tdls_trig; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_TDLS); + if (!trig) + return; + + tdls_trig = (void *)trig->data; + + if (!(tdls_trig->action_bitmap & BIT(action))) + return; + + if (tdls_trig->peer_mode && + memcmp(tdls_trig->peer, peer_addr, ETH_ALEN) != 0) + return; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + "TDLS event occurred, peer %pM, action %d", + peer_addr, action); +} + +struct iwl_mvm_he_obss_narrow_bw_ru_data { + bool tolerated; +}; + +static void iwl_mvm_check_he_obss_narrow_bw_ru_iter(struct wiphy *wiphy, + struct cfg80211_bss *bss, + void *_data) +{ + struct iwl_mvm_he_obss_narrow_bw_ru_data *data = _data; + const struct cfg80211_bss_ies *ies; + const struct element *elem; + + rcu_read_lock(); + ies = rcu_dereference(bss->ies); + elem = cfg80211_find_elem(WLAN_EID_EXT_CAPABILITY, ies->data, + ies->len); + + if (!elem || elem->datalen < 10 || + !(elem->data[10] & + WLAN_EXT_CAPA10_OBSS_NARROW_BW_RU_TOLERANCE_SUPPORT)) { + data->tolerated = false; + } + rcu_read_unlock(); +} + +static void iwl_mvm_check_he_obss_narrow_bw_ru(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_he_obss_narrow_bw_ru_data iter_data = { + .tolerated = true, + }; + + if (!(vif->bss_conf.chandef.chan->flags & IEEE80211_CHAN_RADAR)) { + mvmvif->he_ru_2mhz_block = false; + return; + } + + cfg80211_bss_iter(hw->wiphy, &vif->bss_conf.chandef, + iwl_mvm_check_he_obss_narrow_bw_ru_iter, + &iter_data); + + /* + * If there is at least one AP on radar channel that cannot + * tolerate 26-tone RU UL OFDMA transmissions using HE TB PPDU. + */ + mvmvif->he_ru_2mhz_block = !iter_data.tolerated; +} + +static void iwl_mvm_reset_cca_40mhz_workaround(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct ieee80211_supported_band *sband; + const struct ieee80211_sta_he_cap *he_cap; + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + if (!mvm->cca_40mhz_workaround) + return; + + /* decrement and check that we reached zero */ + mvm->cca_40mhz_workaround--; + if (mvm->cca_40mhz_workaround) + return; + + sband = mvm->hw->wiphy->bands[NL80211_BAND_2GHZ]; + + sband->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + + he_cap = ieee80211_get_he_iftype_cap(sband, + ieee80211_vif_type_p2p(vif)); + + if (he_cap) { + /* we know that ours is writable */ + struct ieee80211_sta_he_cap *he = (void *)(uintptr_t)he_cap; + + he->he_cap_elem.phy_cap_info[0] |= + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G; + } +} + +static void iwl_mvm_mei_host_associated(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mvm_sta *mvm_sta) +{ +#if IS_ENABLED(CONFIG_IWLMEI) + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mei_conn_info conn_info = { + .ssid_len = vif->cfg.ssid_len, + .channel = vif->bss_conf.chandef.chan->hw_value, + }; + + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) + return; + + if (!mvm->mei_registered) + return; + + switch (mvm_sta->pairwise_cipher) { + case WLAN_CIPHER_SUITE_CCMP: + conn_info.pairwise_cipher = IWL_MEI_CIPHER_CCMP; + break; + case WLAN_CIPHER_SUITE_GCMP: + conn_info.pairwise_cipher = IWL_MEI_CIPHER_GCMP; + break; + case WLAN_CIPHER_SUITE_GCMP_256: + conn_info.pairwise_cipher = IWL_MEI_CIPHER_GCMP_256; + break; + case 0: + /* open profile */ + break; + default: + /* cipher not supported, don't send anything to iwlmei */ + return; + } + + switch (mvmvif->rekey_data.akm) { + case WLAN_AKM_SUITE_SAE & 0xff: + conn_info.auth_mode = IWL_MEI_AKM_AUTH_SAE; + break; + case WLAN_AKM_SUITE_PSK & 0xff: + conn_info.auth_mode = IWL_MEI_AKM_AUTH_RSNA_PSK; + break; + case WLAN_AKM_SUITE_8021X & 0xff: + conn_info.auth_mode = IWL_MEI_AKM_AUTH_RSNA; + break; + case 0: + /* open profile */ + conn_info.auth_mode = IWL_MEI_AKM_AUTH_OPEN; + break; + default: + /* auth method / AKM not supported */ + /* TODO: All the FT vesions of these? */ + return; + } + + memcpy(conn_info.ssid, vif->cfg.ssid, vif->cfg.ssid_len); + memcpy(conn_info.bssid, vif->bss_conf.bssid, ETH_ALEN); + + /* TODO: add support for collocated AP data */ + iwl_mei_host_associated(&conn_info, NULL); +#endif +} + +static int iwl_mvm_mac_sta_state(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + enum ieee80211_sta_state old_state, + enum ieee80211_sta_state new_state) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + int ret; + + IWL_DEBUG_MAC80211(mvm, "station %pM state change %d->%d\n", + sta->addr, old_state, new_state); + + /* this would be a mac80211 bug ... but don't crash */ + if (WARN_ON_ONCE(!mvmvif->phy_ctxt)) + return test_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, &mvm->status) ? 0 : -EINVAL; + + /* + * If we are in a STA removal flow and in DQA mode: + * + * This is after the sync_rcu part, so the queues have already been + * flushed. No more TXs on their way in mac80211's path, and no more in + * the queues. + * Also, we won't be getting any new TX frames for this station. + * What we might have are deferred TX frames that need to be taken care + * of. + * + * Drop any still-queued deferred-frame before removing the STA, and + * make sure the worker is no longer handling frames for this STA. + */ + if (old_state == IEEE80211_STA_NONE && + new_state == IEEE80211_STA_NOTEXIST) { + flush_work(&mvm->add_stream_wk); + + /* + * No need to make sure deferred TX indication is off since the + * worker will already remove it if it was on + */ + + /* + * Additionally, reset the 40 MHz capability if we disconnected + * from the AP now. + */ + iwl_mvm_reset_cca_40mhz_workaround(mvm, vif); + } + + mutex_lock(&mvm->mutex); + /* track whether or not the station is associated */ + mvm_sta->sta_state = new_state; + + if (old_state == IEEE80211_STA_NOTEXIST && + new_state == IEEE80211_STA_NONE) { + /* + * Firmware bug - it'll crash if the beacon interval is less + * than 16. We can't avoid connecting at all, so refuse the + * station state change, this will cause mac80211 to abandon + * attempts to connect to this AP, and eventually wpa_s will + * blocklist the AP... + */ + if (vif->type == NL80211_IFTYPE_STATION && + vif->bss_conf.beacon_int < 16) { + IWL_ERR(mvm, + "AP %pM beacon interval is %d, refusing due to firmware bug!\n", + sta->addr, vif->bss_conf.beacon_int); + ret = -EINVAL; + goto out_unlock; + } + + if (vif->type == NL80211_IFTYPE_STATION) + vif->bss_conf.he_support = sta->deflink.he_cap.has_he; + + 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); + iwl_mvm_tdls_check_trigger(mvm, vif, sta->addr, + NL80211_TDLS_SETUP); + } + + sta->deflink.agg.max_rc_amsdu_len = 1; + } else if (old_state == IEEE80211_STA_NONE && + new_state == IEEE80211_STA_AUTH) { + /* + * EBS may be disabled due to previous failures reported by FW. + * Reset EBS status here assuming environment has been changed. + */ + mvm->last_ebs_successful = true; + iwl_mvm_check_uapsd(mvm, vif, sta->addr); + ret = 0; + } else if (old_state == IEEE80211_STA_AUTH && + new_state == IEEE80211_STA_ASSOC) { + if (vif->type == NL80211_IFTYPE_AP) { + vif->bss_conf.he_support = sta->deflink.he_cap.has_he; + mvmvif->ap_assoc_sta_count++; + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + if (vif->bss_conf.he_support && + !iwlwifi_mod_params.disable_11ax) + iwl_mvm_cfg_he_sta(mvm, vif, mvm_sta->sta_id); + } else if (vif->type == NL80211_IFTYPE_STATION) { + vif->bss_conf.he_support = sta->deflink.he_cap.has_he; + + mvmvif->he_ru_2mhz_block = false; + if (sta->deflink.he_cap.has_he) + iwl_mvm_check_he_obss_narrow_bw_ru(hw, vif); + + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + } + + iwl_mvm_rs_rate_init(mvm, sta, mvmvif->phy_ctxt->channel->band, + false); + ret = iwl_mvm_update_sta(mvm, vif, sta); + } else if (old_state == IEEE80211_STA_ASSOC && + new_state == IEEE80211_STA_AUTHORIZED) { + ret = 0; + + /* we don't support TDLS during DCM */ + if (iwl_mvm_phy_ctx_count(mvm) > 1) + iwl_mvm_teardown_tdls_peers(mvm); + + if (sta->tdls) { + iwl_mvm_tdls_check_trigger(mvm, vif, sta->addr, + NL80211_TDLS_ENABLE_LINK); + } else { + /* enable beacon filtering */ + WARN_ON(iwl_mvm_enable_beacon_filter(mvm, vif, 0)); + + mvmvif->authorized = 1; + + /* + * Now that the station is authorized, i.e., keys were already + * installed, need to indicate to the FW that + * multicast data frames can be forwarded to the driver + */ + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + iwl_mvm_mei_host_associated(mvm, vif, mvm_sta); + } + + iwl_mvm_rs_rate_init(mvm, sta, mvmvif->phy_ctxt->channel->band, + true); + } else if (old_state == IEEE80211_STA_AUTHORIZED && + new_state == IEEE80211_STA_ASSOC) { + /* once we move into assoc state, need to update rate scale to + * disable using wide bandwidth + */ + iwl_mvm_rs_rate_init(mvm, sta, mvmvif->phy_ctxt->channel->band, + false); + if (!sta->tdls) { + /* Multicast data frames are no longer allowed */ + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + + /* + * Set this after the above iwl_mvm_mac_ctxt_changed() + * to avoid sending high prio again for a little time. + */ + mvmvif->authorized = 0; + + /* disable beacon filtering */ + ret = iwl_mvm_disable_beacon_filter(mvm, vif, 0); + WARN_ON(ret && + !test_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, + &mvm->status)); + } + ret = 0; + } else if (old_state == IEEE80211_STA_ASSOC && + new_state == IEEE80211_STA_AUTH) { + if (vif->type == NL80211_IFTYPE_AP) { + mvmvif->ap_assoc_sta_count--; + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + } else if (vif->type == NL80211_IFTYPE_STATION && !sta->tdls) + iwl_mvm_stop_session_protection(mvm, vif); + ret = 0; + } else if (old_state == IEEE80211_STA_AUTH && + new_state == IEEE80211_STA_NONE) { + ret = 0; + } else if (old_state == IEEE80211_STA_NONE && + new_state == IEEE80211_STA_NOTEXIST) { + if (vif->type == NL80211_IFTYPE_STATION && !sta->tdls) + iwl_mvm_stop_session_protection(mvm, vif); + ret = iwl_mvm_rm_sta(mvm, vif, sta); + if (sta->tdls) { + iwl_mvm_recalc_tdls_state(mvm, vif, false); + iwl_mvm_tdls_check_trigger(mvm, vif, sta->addr, + NL80211_TDLS_DISABLE_LINK); + } + + if (unlikely(ret && + test_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, + &mvm->status))) + ret = 0; + } else { + ret = -EIO; + } + out_unlock: + mutex_unlock(&mvm->mutex); + + if (sta->tdls && ret == 0) { + if (old_state == IEEE80211_STA_NOTEXIST && + new_state == IEEE80211_STA_NONE) + ieee80211_reserve_tid(sta, IWL_MVM_TDLS_FW_TID); + else if (old_state == IEEE80211_STA_NONE && + new_state == IEEE80211_STA_NOTEXIST) + ieee80211_unreserve_tid(sta, IWL_MVM_TDLS_FW_TID); + } + + return ret; +} + +static int iwl_mvm_mac_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mvm->rts_threshold = value; + + return 0; +} + +static void iwl_mvm_sta_rc_update(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u32 changed) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (changed & (IEEE80211_RC_BW_CHANGED | + IEEE80211_RC_SUPP_RATES_CHANGED | + IEEE80211_RC_NSS_CHANGED)) + iwl_mvm_rs_rate_init(mvm, sta, mvmvif->phy_ctxt->channel->band, + true); + + if (vif->type == NL80211_IFTYPE_STATION && + changed & IEEE80211_RC_NSS_CHANGED) + iwl_mvm_sf_update(mvm, vif, false); +} + +static int iwl_mvm_mac_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + unsigned int link_id, u16 ac, + const struct ieee80211_tx_queue_params *params) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + mvmvif->queue_params[ac] = *params; + + /* + * No need to update right away, we'll get BSS_CHANGED_QOS + * The exception is P2P_DEVICE interface which needs immediate update. + */ + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { + int ret; + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + mutex_unlock(&mvm->mutex); + return ret; + } + return 0; +} + +static void iwl_mvm_mac_mgd_prepare_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_prep_tx_info *info) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mutex_lock(&mvm->mutex); + iwl_mvm_protect_assoc(mvm, vif, info->duration); + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_mac_mgd_complete_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_prep_tx_info *info) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + /* for successful cases (auth/assoc), don't cancel session protection */ + if (info->success) + return; + + mutex_lock(&mvm->mutex); + iwl_mvm_stop_session_protection(mvm, vif); + mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_mac_sched_scan_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_scan_ies *ies) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + int ret; + + mutex_lock(&mvm->mutex); + + if (!vif->cfg.idle) { + ret = -EBUSY; + goto out; + } + + ret = iwl_mvm_sched_scan_start(mvm, vif, req, ies, IWL_MVM_SCAN_SCHED); + +out: + mutex_unlock(&mvm->mutex); + return ret; +} + +static int iwl_mvm_mac_sched_scan_stop(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + mutex_lock(&mvm->mutex); + + /* Due to a race condition, it's possible that mac80211 asks + * us to stop a sched_scan when it's already stopped. This + * can happen, for instance, if we stopped the scan ourselves, + * called ieee80211_sched_scan_stopped() and the userspace called + * stop sched scan scan before ieee80211_sched_scan_stopped_work() + * could run. To handle this, simply return if the scan is + * not running. + */ + if (!(mvm->scan_status & IWL_MVM_SCAN_SCHED)) { + mutex_unlock(&mvm->mutex); + return 0; + } + + ret = iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_SCHED, false); + mutex_unlock(&mvm->mutex); + iwl_mvm_wait_for_async_handlers(mvm); + + return ret; +} + +static int __iwl_mvm_mac_set_key(struct ieee80211_hw *hw, + enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_sta *mvmsta = NULL; + struct iwl_mvm_key_pn *ptk_pn = NULL; + int keyidx = key->keyidx; + int ret, i; + u8 key_offset; + + if (sta) + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_TKIP: + if (!mvm->trans->trans_cfg->gen2) { + key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC; + key->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE; + } else if (vif->type == NL80211_IFTYPE_STATION) { + key->flags |= IEEE80211_KEY_FLAG_PUT_MIC_SPACE; + } else { + IWL_DEBUG_MAC80211(mvm, "Use SW encryption for TKIP\n"); + return -EOPNOTSUPP; + } + break; + case WLAN_CIPHER_SUITE_CCMP: + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_GCMP_256: + if (!iwl_mvm_has_new_tx_api(mvm)) + key->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE; + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + case WLAN_CIPHER_SUITE_BIP_GMAC_128: + case WLAN_CIPHER_SUITE_BIP_GMAC_256: + WARN_ON_ONCE(!ieee80211_hw_check(hw, MFP_CAPABLE)); + break; + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + if (vif->type == NL80211_IFTYPE_STATION) + break; + if (iwl_mvm_has_new_tx_api(mvm)) + return -EOPNOTSUPP; + /* support HW crypto on TX */ + return 0; + default: + return -EOPNOTSUPP; + } + + switch (cmd) { + case SET_KEY: + if (keyidx == 6 || keyidx == 7) + rcu_assign_pointer(mvmvif->bcn_prot.keys[keyidx - 6], + key); + + if ((vif->type == NL80211_IFTYPE_ADHOC || + vif->type == NL80211_IFTYPE_AP) && !sta) { + /* + * GTK on AP interface is a TX-only key, return 0; + * on IBSS they're per-station and because we're lazy + * we don't support them for RX, so do the same. + * CMAC/GMAC in AP/IBSS modes must be done in software. + */ + if (key->cipher == WLAN_CIPHER_SUITE_AES_CMAC || + key->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_128 || + key->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_256) { + ret = -EOPNOTSUPP; + break; + } + + if (key->cipher != WLAN_CIPHER_SUITE_GCMP && + key->cipher != WLAN_CIPHER_SUITE_GCMP_256 && + !iwl_mvm_has_new_tx_api(mvm)) { + key->hw_key_idx = STA_KEY_IDX_INVALID; + ret = 0; + break; + } + + if (!mvmvif->ap_ibss_active) { + for (i = 0; + i < ARRAY_SIZE(mvmvif->ap_early_keys); + i++) { + if (!mvmvif->ap_early_keys[i]) { + mvmvif->ap_early_keys[i] = key; + break; + } + } + + if (i >= ARRAY_SIZE(mvmvif->ap_early_keys)) + ret = -ENOSPC; + else + ret = 0; + + break; + } + } + + /* During FW restart, in order to restore the state as it was, + * don't try to reprogram keys we previously failed for. + */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) && + key->hw_key_idx == STA_KEY_IDX_INVALID) { + IWL_DEBUG_MAC80211(mvm, + "skip invalid idx key programming during restart\n"); + ret = 0; + break; + } + + if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) && + mvmsta && iwl_mvm_has_new_rx_api(mvm) && + key->flags & IEEE80211_KEY_FLAG_PAIRWISE && + (key->cipher == WLAN_CIPHER_SUITE_CCMP || + key->cipher == WLAN_CIPHER_SUITE_GCMP || + key->cipher == WLAN_CIPHER_SUITE_GCMP_256)) { + struct ieee80211_key_seq seq; + int tid, q; + + WARN_ON(rcu_access_pointer(mvmsta->ptk_pn[keyidx])); + ptk_pn = kzalloc(struct_size(ptk_pn, q, + mvm->trans->num_rx_queues), + GFP_KERNEL); + if (!ptk_pn) { + ret = -ENOMEM; + break; + } + + for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++) { + ieee80211_get_key_rx_seq(key, tid, &seq); + for (q = 0; q < mvm->trans->num_rx_queues; q++) + memcpy(ptk_pn->q[q].pn[tid], + seq.ccmp.pn, + IEEE80211_CCMP_PN_LEN); + } + + rcu_assign_pointer(mvmsta->ptk_pn[keyidx], ptk_pn); + } + + /* in HW restart reuse the index, otherwise request a new one */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) + key_offset = key->hw_key_idx; + else + key_offset = STA_KEY_IDX_INVALID; + + if (mvmsta && key->flags & IEEE80211_KEY_FLAG_PAIRWISE) + mvmsta->pairwise_cipher = key->cipher; + + IWL_DEBUG_MAC80211(mvm, "set hwcrypto key\n"); + ret = iwl_mvm_set_sta_key(mvm, vif, sta, key, key_offset); + if (ret) { + IWL_WARN(mvm, "set key failed\n"); + key->hw_key_idx = STA_KEY_IDX_INVALID; + if (ptk_pn) { + RCU_INIT_POINTER(mvmsta->ptk_pn[keyidx], NULL); + kfree(ptk_pn); + } + /* + * can't add key for RX, but we don't need it + * in the device for TX so still return 0, + * unless we have new TX API where we cannot + * put key material into the TX_CMD + */ + if (iwl_mvm_has_new_tx_api(mvm)) + ret = -EOPNOTSUPP; + else + ret = 0; + } + + break; + case DISABLE_KEY: + if (keyidx == 6 || keyidx == 7) + RCU_INIT_POINTER(mvmvif->bcn_prot.keys[keyidx - 6], + NULL); + + ret = -ENOENT; + for (i = 0; i < ARRAY_SIZE(mvmvif->ap_early_keys); i++) { + if (mvmvif->ap_early_keys[i] == key) { + mvmvif->ap_early_keys[i] = NULL; + ret = 0; + } + } + + /* found in pending list - don't do anything else */ + if (ret == 0) + break; + + if (key->hw_key_idx == STA_KEY_IDX_INVALID) { + ret = 0; + break; + } + + if (mvmsta && iwl_mvm_has_new_rx_api(mvm) && + key->flags & IEEE80211_KEY_FLAG_PAIRWISE && + (key->cipher == WLAN_CIPHER_SUITE_CCMP || + key->cipher == WLAN_CIPHER_SUITE_GCMP || + key->cipher == WLAN_CIPHER_SUITE_GCMP_256)) { + ptk_pn = rcu_dereference_protected( + mvmsta->ptk_pn[keyidx], + lockdep_is_held(&mvm->mutex)); + RCU_INIT_POINTER(mvmsta->ptk_pn[keyidx], NULL); + if (ptk_pn) + kfree_rcu(ptk_pn, rcu_head); + } + + IWL_DEBUG_MAC80211(mvm, "disable hwcrypto key\n"); + ret = iwl_mvm_remove_sta_key(mvm, vif, sta, key); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int iwl_mvm_mac_set_key(struct ieee80211_hw *hw, + enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + mutex_lock(&mvm->mutex); + ret = __iwl_mvm_mac_set_key(hw, cmd, vif, sta, key); + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_mac_update_tkip_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, + u32 iv32, u16 *phase1key) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + if (keyconf->hw_key_idx == STA_KEY_IDX_INVALID) + return; + + iwl_mvm_update_tkip_key(mvm, vif, keyconf, sta, iv32, phase1key); +} + + +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: Received 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_MIN_DURATION MSEC_TO_TU(100) +#define AUX_ROC_MIN_DELAY MSEC_TO_TU(200) +#define AUX_ROC_MAX_DELAY MSEC_TO_TU(600) +#define AUX_ROC_SAFETY_BUFFER MSEC_TO_TU(20) +#define AUX_ROC_MIN_SAFETY_BUFFER MSEC_TO_TU(10) +static int iwl_mvm_send_aux_roc_cmd(struct iwl_mvm *mvm, + struct ieee80211_channel *channel, + struct ieee80211_vif *vif, + int duration) +{ + int res; + 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 u16 time_event_response[] = { HOT_SPOT_CMD }; + struct iwl_notification_wait wait_time_event; + u32 dtim_interval = vif->bss_conf.dtim_period * + vif->bss_conf.beacon_int; + u32 req_dur, delay; + 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), + }; + struct iwl_hs20_roc_req_tail *tail = iwl_mvm_chan_info_cmd_tail(mvm, + &aux_roc_req.channel_info); + u16 len = sizeof(aux_roc_req) - iwl_mvm_chan_info_padding(mvm); + + /* Set the channel info data */ + iwl_mvm_set_chan_info(mvm, &aux_roc_req.channel_info, channel->hw_value, + iwl_mvm_phy_band_from_nl80211(channel->band), + PHY_VHT_CHANNEL_MODE20, + 0); + + /* Set the time and duration */ + tail->apply_time = cpu_to_le32(iwl_mvm_get_systime(mvm)); + + delay = AUX_ROC_MIN_DELAY; + req_dur = MSEC_TO_TU(duration); + + /* + * If we are associated we want the delay time to be at least one + * dtim interval so that the FW can wait until after the DTIM and + * then start the time event, this will potentially allow us to + * remain off-channel for the max duration. + * Since we want to use almost a whole dtim interval we would also + * like the delay to be for 2-3 dtim intervals, in case there are + * other time events with higher priority. + */ + if (vif->cfg.assoc) { + delay = min_t(u32, dtim_interval * 3, AUX_ROC_MAX_DELAY); + /* We cannot remain off-channel longer than the DTIM interval */ + if (dtim_interval <= req_dur) { + req_dur = dtim_interval - AUX_ROC_SAFETY_BUFFER; + if (req_dur <= AUX_ROC_MIN_DURATION) + req_dur = dtim_interval - + AUX_ROC_MIN_SAFETY_BUFFER; + } + } + + tail->duration = cpu_to_le32(req_dur); + tail->apply_time_max_delay = cpu_to_le32(delay); + + IWL_DEBUG_TE(mvm, + "ROC: Requesting to remain on channel %u for %ums\n", + channel->hw_value, req_dur); + IWL_DEBUG_TE(mvm, + "\t(requested = %ums, max_delay = %ums, dtim_interval = %ums)\n", + duration, delay, dtim_interval); + + /* Set the node address */ + memcpy(tail->node_addr, vif->addr, ETH_ALEN); + + lockdep_assert_held(&mvm->mutex); + + spin_lock_bh(&mvm->time_event_lock); + + if (WARN_ON(te_data->id == HOT_SPOT_CMD)) { + spin_unlock_bh(&mvm->time_event_lock); + return -EIO; + } + + te_data->vif = vif; + te_data->duration = duration; + te_data->id = HOT_SPOT_CMD; + + 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, len, + &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, + int duration, + enum ieee80211_roc_type type) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct cfg80211_chan_def chandef; + struct iwl_mvm_phy_ctxt *phy_ctxt; + bool band_change_removal; + int ret, i; + + IWL_DEBUG_MAC80211(mvm, "enter (%d, %d, %d)\n", channel->hw_value, + duration, type); + + /* + * Flush the done work, just in case it's still pending, so that + * the work it does can complete and we can accept new frames. + */ + flush_work(&mvm->roc_done_wk); + + mutex_lock(&mvm->mutex); + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_HOTSPOT_SUPPORT)) { + /* Use aux roc framework (HS20) */ + if (iwl_fw_lookup_cmd_ver(mvm->fw, ADD_STA, 0) >= 12) { + u32 lmac_id; + + lmac_id = iwl_mvm_get_lmac_id(mvm->fw, + channel->band); + ret = iwl_mvm_add_aux_sta(mvm, lmac_id); + if (WARN(ret, + "Failed to allocate aux station")) + goto out_unlock; + } + ret = iwl_mvm_send_aux_roc_cmd(mvm, channel, + vif, duration); + goto out_unlock; + } + IWL_ERR(mvm, "hotspot not supported\n"); + ret = -EINVAL; + goto out_unlock; + case NL80211_IFTYPE_P2P_DEVICE: + /* handle below */ + break; + default: + IWL_ERR(mvm, "vif isn't P2P_DEVICE: %d\n", vif->type); + ret = -EINVAL; + goto out_unlock; + } + + for (i = 0; i < NUM_PHY_CTX; i++) { + phy_ctxt = &mvm->phy_ctxts[i]; + if (phy_ctxt->ref == 0 || mvmvif->phy_ctxt == phy_ctxt) + continue; + + if (phy_ctxt->ref && channel == phy_ctxt->channel) { + /* + * Unbind the P2P_DEVICE from the current PHY context, + * and if the PHY context is not used remove it. + */ + ret = iwl_mvm_binding_remove_vif(mvm, vif); + if (WARN(ret, "Failed unbinding P2P_DEVICE\n")) + goto out_unlock; + + iwl_mvm_phy_ctxt_unref(mvm, mvmvif->phy_ctxt); + + /* Bind the P2P_DEVICE to the current PHY Context */ + mvmvif->phy_ctxt = phy_ctxt; + + ret = iwl_mvm_binding_add_vif(mvm, vif); + if (WARN(ret, "Failed binding P2P_DEVICE\n")) + goto out_unlock; + + iwl_mvm_phy_ctxt_ref(mvm, mvmvif->phy_ctxt); + goto schedule_time_event; + } + } + + /* Need to update the PHY context only if the ROC channel changed */ + if (channel == mvmvif->phy_ctxt->channel) + goto schedule_time_event; + + cfg80211_chandef_create(&chandef, channel, NL80211_CHAN_NO_HT); + + /* + * Check if the remain-on-channel is on a different band and that + * requires context removal, see iwl_mvm_phy_ctxt_changed(). If + * so, we'll need to release and then re-configure here, since we + * must not remove a PHY context that's part of a binding. + */ + band_change_removal = + fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_BINDING_CDB_SUPPORT) && + mvmvif->phy_ctxt->channel->band != chandef.chan->band; + + if (mvmvif->phy_ctxt->ref == 1 && !band_change_removal) { + /* + * Change the PHY context configuration as it is currently + * referenced only by the P2P Device MAC (and we can modify it) + */ + ret = iwl_mvm_phy_ctxt_changed(mvm, mvmvif->phy_ctxt, + &chandef, 1, 1); + if (ret) + goto out_unlock; + } else { + /* + * The PHY context is shared with other MACs (or we're trying to + * switch bands), so remove the P2P Device from the binding, + * allocate an new PHY context and create a new binding. + */ + phy_ctxt = iwl_mvm_get_free_phy_ctxt(mvm); + if (!phy_ctxt) { + ret = -ENOSPC; + goto out_unlock; + } + + ret = iwl_mvm_phy_ctxt_changed(mvm, phy_ctxt, &chandef, + 1, 1); + if (ret) { + IWL_ERR(mvm, "Failed to change PHY context\n"); + goto out_unlock; + } + + /* Unbind the P2P_DEVICE from the current PHY context */ + ret = iwl_mvm_binding_remove_vif(mvm, vif); + if (WARN(ret, "Failed unbinding P2P_DEVICE\n")) + goto out_unlock; + + iwl_mvm_phy_ctxt_unref(mvm, mvmvif->phy_ctxt); + + /* Bind the P2P_DEVICE to the new allocated PHY context */ + mvmvif->phy_ctxt = phy_ctxt; + + ret = iwl_mvm_binding_add_vif(mvm, vif); + if (WARN(ret, "Failed binding P2P_DEVICE\n")) + goto out_unlock; + + iwl_mvm_phy_ctxt_ref(mvm, mvmvif->phy_ctxt); + } + +schedule_time_event: + /* Schedule the time events */ + ret = iwl_mvm_start_p2p_roc(mvm, vif, duration, type); + +out_unlock: + mutex_unlock(&mvm->mutex); + IWL_DEBUG_MAC80211(mvm, "leave\n"); + return ret; +} + +static int iwl_mvm_cancel_roc(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + IWL_DEBUG_MAC80211(mvm, "enter\n"); + + mutex_lock(&mvm->mutex); + iwl_mvm_stop_roc(mvm, vif); + mutex_unlock(&mvm->mutex); + + IWL_DEBUG_MAC80211(mvm, "leave\n"); + return 0; +} + +struct iwl_mvm_ftm_responder_iter_data { + bool responder; + struct ieee80211_chanctx_conf *ctx; +}; + +static void iwl_mvm_ftm_responder_chanctx_iter(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_ftm_responder_iter_data *data = _data; + + if (rcu_access_pointer(vif->bss_conf.chanctx_conf) == data->ctx && + vif->type == NL80211_IFTYPE_AP && vif->bss_conf.ftmr_params) + data->responder = true; +} + +static bool iwl_mvm_is_ftm_responder_chanctx(struct iwl_mvm *mvm, + struct ieee80211_chanctx_conf *ctx) +{ + struct iwl_mvm_ftm_responder_iter_data data = { + .responder = false, + .ctx = ctx, + }; + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_ftm_responder_chanctx_iter, + &data); + return data.responder; +} + +static int __iwl_mvm_add_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; + bool responder = iwl_mvm_is_ftm_responder_chanctx(mvm, ctx); + struct cfg80211_chan_def *def = responder ? &ctx->def : &ctx->min_def; + int ret; + + lockdep_assert_held(&mvm->mutex); + + IWL_DEBUG_MAC80211(mvm, "Add channel context\n"); + + phy_ctxt = iwl_mvm_get_free_phy_ctxt(mvm); + if (!phy_ctxt) { + ret = -ENOSPC; + goto out; + } + + ret = iwl_mvm_phy_ctxt_changed(mvm, phy_ctxt, def, + ctx->rx_chains_static, + ctx->rx_chains_dynamic); + if (ret) { + IWL_ERR(mvm, "Failed to add PHY context\n"); + goto out; + } + + 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); + + mutex_lock(&mvm->mutex); + __iwl_mvm_remove_chanctx(mvm, ctx); + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_change_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx, + u32 changed) +{ + 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]; + bool responder = iwl_mvm_is_ftm_responder_chanctx(mvm, ctx); + struct cfg80211_chan_def *def = responder ? &ctx->def : &ctx->min_def; + + if (WARN_ONCE((phy_ctxt->ref > 1) && + (changed & ~(IEEE80211_CHANCTX_CHANGE_WIDTH | + IEEE80211_CHANCTX_CHANGE_RX_CHAINS | + IEEE80211_CHANCTX_CHANGE_RADAR | + IEEE80211_CHANCTX_CHANGE_MIN_WIDTH)), + "Cannot change PHY. Ref=%d, changed=0x%X\n", + phy_ctxt->ref, changed)) + return; + + mutex_lock(&mvm->mutex); + + /* we are only changing the min_width, may be a noop */ + if (changed == IEEE80211_CHANCTX_CHANGE_MIN_WIDTH) { + if (phy_ctxt->width == def->width) + goto out_unlock; + + /* we are just toggling between 20_NOHT and 20 */ + if (phy_ctxt->width <= NL80211_CHAN_WIDTH_20 && + def->width <= NL80211_CHAN_WIDTH_20) + goto out_unlock; + } + + iwl_mvm_bt_coex_vif_change(mvm); + iwl_mvm_phy_ctxt_changed(mvm, phy_ctxt, def, + ctx->rx_chains_static, + ctx->rx_chains_dynamic); + +out_unlock: + mutex_unlock(&mvm->mutex); +} + +static int __iwl_mvm_assign_vif_chanctx(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *ctx, + bool switching_chanctx) +{ + 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; + + lockdep_assert_held(&mvm->mutex); + + mvmvif->phy_ctxt = phy_ctxt; + + switch (vif->type) { + case NL80211_IFTYPE_AP: + /* only needed if we're switching chanctx (i.e. during CSA) */ + if (switching_chanctx) { + mvmvif->ap_ibss_active = true; + break; + } + fallthrough; + case NL80211_IFTYPE_ADHOC: + /* + * The AP binding flow is handled as part of the start_ap flow + * (in bss_info_changed), similarly for IBSS. + */ + ret = 0; + goto out; + case NL80211_IFTYPE_STATION: + mvmvif->csa_bcn_pending = false; + break; + case NL80211_IFTYPE_MONITOR: + /* always disable PS when a monitor interface is active */ + mvmvif->ps_disabled = true; + break; + default: + ret = -EINVAL; + goto out; + } + + ret = iwl_mvm_binding_add_vif(mvm, vif); + if (ret) + goto out; + + /* + * Power state must be updated before quotas, + * otherwise fw will complain. + */ + iwl_mvm_power_update_mac(mvm); + + /* Setting the quota at this stage is only required for monitor + * interfaces. For the other types, the bss_info changed flow + * will handle quota settings. + */ + if (vif->type == NL80211_IFTYPE_MONITOR) { + mvmvif->monitor_active = true; + ret = iwl_mvm_update_quotas(mvm, false, NULL); + if (ret) + goto out_remove_binding; + + ret = iwl_mvm_add_snif_sta(mvm, vif); + if (ret) + goto out_remove_binding; + + } + + /* Handle binding during CSA */ + if (vif->type == NL80211_IFTYPE_AP) { + iwl_mvm_update_quotas(mvm, false, NULL); + iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL); + } + + if (switching_chanctx && vif->type == NL80211_IFTYPE_STATION) { + mvmvif->csa_bcn_pending = true; + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CHANNEL_SWITCH_CMD)) { + u32 duration = 3 * vif->bss_conf.beacon_int; + + /* Protect the session to make sure we hear the first + * beacon on the new channel. + */ + iwl_mvm_protect_session(mvm, vif, duration, duration, + vif->bss_conf.beacon_int / 2, + true); + } + + iwl_mvm_update_quotas(mvm, false, NULL); + } + + goto out; + +out_remove_binding: + iwl_mvm_binding_remove_vif(mvm, vif); + iwl_mvm_power_update_mac(mvm); +out: + if (ret) + mvmvif->phy_ctxt = NULL; + return ret; +} +static int iwl_mvm_assign_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf, + struct ieee80211_chanctx_conf *ctx) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + 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; + case NL80211_IFTYPE_MONITOR: + mvmvif->monitor_active = false; + mvmvif->ps_disabled = false; + iwl_mvm_rm_snif_sta(mvm, vif); + break; + case NL80211_IFTYPE_AP: + /* This part is triggered only during CSA */ + if (!switching_chanctx || !mvmvif->ap_ibss_active) + goto out; + + mvmvif->csa_countdown = false; + + /* 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; + break; + case NL80211_IFTYPE_STATION: + if (!switching_chanctx) + break; + + disabled_vif = vif; + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CHANNEL_SWITCH_CMD)) + iwl_mvm_mac_ctxt_changed(mvm, vif, true, NULL); + break; + default: + break; + } + + iwl_mvm_update_quotas(mvm, false, disabled_vif); + iwl_mvm_binding_remove_vif(mvm, vif); + +out: + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_CHANNEL_SWITCH_CMD) && + switching_chanctx) + return; + 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_bss_conf *link_conf, + 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_swap(struct iwl_mvm *mvm, + struct ieee80211_vif_chanctx_switch *vifs) +{ + int ret; + + 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: + if (__iwl_mvm_add_chanctx(mvm, vifs[0].old_ctx)) { + IWL_ERR(mvm, "failed to add old_ctx back after failure.\n"); + goto out_restart; + } + + if (__iwl_mvm_assign_vif_chanctx(mvm, vifs[0].vif, vifs[0].old_ctx, + true)) { + 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_switch_vif_chanctx_reassign(struct iwl_mvm *mvm, + struct ieee80211_vif_chanctx_switch *vifs) +{ + int ret; + + mutex_lock(&mvm->mutex); + __iwl_mvm_unassign_vif_chanctx(mvm, vifs[0].vif, vifs[0].old_ctx, true); + + 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_reassign; + } + + goto out; + +out_reassign: + if (__iwl_mvm_assign_vif_chanctx(mvm, vifs[0].vif, vifs[0].old_ctx, + true)) { + 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_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 a single-vif right now */ + if (n_vifs > 1) + return -EOPNOTSUPP; + + switch (mode) { + case CHANCTX_SWMODE_SWAP_CONTEXTS: + ret = iwl_mvm_switch_vif_chanctx_swap(mvm, vifs); + break; + case CHANCTX_SWMODE_REASSIGN_VIF: + ret = iwl_mvm_switch_vif_chanctx_reassign(mvm, vifs); + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} + +static int iwl_mvm_tx_last_beacon(struct ieee80211_hw *hw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + return mvm->ibss_manager; +} + +static int iwl_mvm_set_tim(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + bool set) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + + if (!mvm_sta || !mvm_sta->vif) { + IWL_ERR(mvm, "Station is not associated to a vif\n"); + return -EINVAL; + } + + return iwl_mvm_mac_ctxt_beacon_changed(mvm, mvm_sta->vif); +} + +#ifdef CONFIG_NL80211_TESTMODE +static const struct nla_policy iwl_mvm_tm_policy[IWL_MVM_TM_ATTR_MAX + 1] = { + [IWL_MVM_TM_ATTR_CMD] = { .type = NLA_U32 }, + [IWL_MVM_TM_ATTR_NOA_DURATION] = { .type = NLA_U32 }, + [IWL_MVM_TM_ATTR_BEACON_FILTER_STATE] = { .type = NLA_U32 }, +}; + +static int __iwl_mvm_mac_testmode_cmd(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + void *data, int len) +{ + struct nlattr *tb[IWL_MVM_TM_ATTR_MAX + 1]; + int err; + u32 noa_duration; + + err = nla_parse_deprecated(tb, IWL_MVM_TM_ATTR_MAX, data, len, + iwl_mvm_tm_policy, NULL); + if (err) + return err; + + if (!tb[IWL_MVM_TM_ATTR_CMD]) + return -EINVAL; + + switch (nla_get_u32(tb[IWL_MVM_TM_ATTR_CMD])) { + case IWL_MVM_TM_CMD_SET_NOA: + if (!vif || vif->type != NL80211_IFTYPE_AP || !vif->p2p || + !vif->bss_conf.enable_beacon || + !tb[IWL_MVM_TM_ATTR_NOA_DURATION]) + return -EINVAL; + + noa_duration = nla_get_u32(tb[IWL_MVM_TM_ATTR_NOA_DURATION]); + if (noa_duration >= vif->bss_conf.beacon_int) + return -EINVAL; + + mvm->noa_duration = noa_duration; + mvm->noa_vif = vif; + + return iwl_mvm_update_quotas(mvm, true, NULL); + case IWL_MVM_TM_CMD_SET_BEACON_FILTER: + /* must be associated client vif - ignore authorized */ + if (!vif || vif->type != NL80211_IFTYPE_STATION || + !vif->cfg.assoc || !vif->bss_conf.dtim_period || + !tb[IWL_MVM_TM_ATTR_BEACON_FILTER_STATE]) + return -EINVAL; + + if (nla_get_u32(tb[IWL_MVM_TM_ATTR_BEACON_FILTER_STATE])) + return iwl_mvm_enable_beacon_filter(mvm, vif, 0); + return iwl_mvm_disable_beacon_filter(mvm, vif, 0); + } + + return -EOPNOTSUPP; +} + +static int iwl_mvm_mac_testmode_cmd(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + void *data, int len) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int err; + + mutex_lock(&mvm->mutex); + err = __iwl_mvm_mac_testmode_cmd(mvm, vif, data, len); + mutex_unlock(&mvm->mutex); + + return err; +} +#endif + +static void iwl_mvm_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel_switch *chsw) +{ + /* By implementing this operation, we prevent mac80211 from + * starting its own channel switch timer, so that we can call + * ieee80211_chswitch_done() ourselves at the right time + * (which is when the absence time event starts). + */ + + IWL_DEBUG_MAC80211(IWL_MAC80211_GET_MVM(hw), + "dummy channel switch op\n"); +} + +static int iwl_mvm_schedule_client_csa(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_channel_switch *chsw) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_chan_switch_te_cmd cmd = { + .mac_id = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)), + .action = cpu_to_le32(FW_CTXT_ACTION_ADD), + .tsf = cpu_to_le32(chsw->timestamp), + .cs_count = chsw->count, + .cs_mode = chsw->block_tx, + }; + + lockdep_assert_held(&mvm->mutex); + + if (chsw->delay) + cmd.cs_delayed_bcn_count = + DIV_ROUND_UP(chsw->delay, vif->bss_conf.beacon_int); + + return iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(MAC_CONF_GROUP, + CHANNEL_SWITCH_TIME_EVENT_CMD), + 0, sizeof(cmd), &cmd); +} + +static int iwl_mvm_old_pre_chan_sw_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_channel_switch *chsw) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u32 apply_time; + + /* Schedule the time event to a bit before beacon 1, + * to make sure we're in the new channel when the + * GO/AP arrives. In case count <= 1 immediately schedule the + * TE (this might result with some packet loss or connection + * loss). + */ + if (chsw->count <= 1) + apply_time = 0; + else + apply_time = chsw->device_timestamp + + ((vif->bss_conf.beacon_int * (chsw->count - 1) - + IWL_MVM_CHANNEL_SWITCH_TIME_CLIENT) * 1024); + + if (chsw->block_tx) + iwl_mvm_csa_client_absent(mvm, vif); + + if (mvmvif->bf_data.bf_enabled) { + int ret = iwl_mvm_disable_beacon_filter(mvm, vif, 0); + + if (ret) + return ret; + } + + iwl_mvm_schedule_csa_period(mvm, vif, vif->bss_conf.beacon_int, + apply_time); + + return 0; +} + +#define IWL_MAX_CSA_BLOCK_TX 1500 +static int iwl_mvm_pre_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel_switch *chsw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct ieee80211_vif *csa_vif; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + mutex_lock(&mvm->mutex); + + mvmvif->csa_failed = false; + + IWL_DEBUG_MAC80211(mvm, "pre CSA to freq %d\n", + chsw->chandef.center_freq1); + + iwl_fw_dbg_trigger_simple_stop(&mvm->fwrt, + ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_CHANNEL_SWITCH); + + switch (vif->type) { + case NL80211_IFTYPE_AP: + csa_vif = + rcu_dereference_protected(mvm->csa_vif, + lockdep_is_held(&mvm->mutex)); + if (WARN_ONCE(csa_vif && csa_vif->bss_conf.csa_active, + "Another CSA is already in progress")) { + ret = -EBUSY; + goto out_unlock; + } + + /* we still didn't unblock tx. prevent new CS meanwhile */ + if (rcu_dereference_protected(mvm->csa_tx_blocked_vif, + lockdep_is_held(&mvm->mutex))) { + ret = -EBUSY; + goto out_unlock; + } + + rcu_assign_pointer(mvm->csa_vif, vif); + + if (WARN_ONCE(mvmvif->csa_countdown, + "Previous CSA countdown didn't complete")) { + ret = -EBUSY; + goto out_unlock; + } + + mvmvif->csa_target_freq = chsw->chandef.chan->center_freq; + + break; + case NL80211_IFTYPE_STATION: + /* + * In the new flow FW is in charge of timing the switch so there + * is no need for all of this + */ + if (iwl_fw_lookup_notif_ver(mvm->fw, MAC_CONF_GROUP, + CHANNEL_SWITCH_ERROR_NOTIF, + 0)) + break; + + /* + * We haven't configured the firmware to be associated yet since + * we don't know the dtim period. In this case, the firmware can't + * track the beacons. + */ + if (!vif->cfg.assoc || !vif->bss_conf.dtim_period) { + ret = -EBUSY; + goto out_unlock; + } + + if (chsw->delay > IWL_MAX_CSA_BLOCK_TX) + schedule_delayed_work(&mvmvif->csa_work, 0); + + if (chsw->block_tx) { + /* + * In case of undetermined / long time with immediate + * quiet monitor status to gracefully disconnect + */ + if (!chsw->count || + chsw->count * vif->bss_conf.beacon_int > + IWL_MAX_CSA_BLOCK_TX) + schedule_delayed_work(&mvmvif->csa_work, + msecs_to_jiffies(IWL_MAX_CSA_BLOCK_TX)); + } + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CHANNEL_SWITCH_CMD)) { + ret = iwl_mvm_old_pre_chan_sw_sta(mvm, vif, chsw); + if (ret) + goto out_unlock; + } else { + iwl_mvm_schedule_client_csa(mvm, vif, chsw); + } + + mvmvif->csa_count = chsw->count; + mvmvif->csa_misbehave = false; + break; + default: + break; + } + + mvmvif->ps_disabled = true; + + ret = iwl_mvm_power_update_ps(mvm); + if (ret) + goto out_unlock; + + /* we won't be on this channel any longer */ + iwl_mvm_teardown_tdls_peers(mvm); + +out_unlock: + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_channel_switch_rx_beacon(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel_switch *chsw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_chan_switch_te_cmd cmd = { + .mac_id = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)), + .action = cpu_to_le32(FW_CTXT_ACTION_MODIFY), + .tsf = cpu_to_le32(chsw->timestamp), + .cs_count = chsw->count, + .cs_mode = chsw->block_tx, + }; + + /* + * In the new flow FW is in charge of timing the switch so there is no + * need for all of this + */ + if (iwl_fw_lookup_notif_ver(mvm->fw, MAC_CONF_GROUP, + CHANNEL_SWITCH_ERROR_NOTIF, 0)) + return; + + if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_CS_MODIFY)) + return; + + IWL_DEBUG_MAC80211(mvm, "Modify CSA on mac %d count = %d (old %d) mode = %d\n", + mvmvif->id, chsw->count, mvmvif->csa_count, chsw->block_tx); + + if (chsw->count >= mvmvif->csa_count && chsw->block_tx) { + if (mvmvif->csa_misbehave) { + /* Second time, give up on this AP*/ + iwl_mvm_abort_channel_switch(hw, vif); + ieee80211_chswitch_done(vif, false); + mvmvif->csa_misbehave = false; + return; + } + mvmvif->csa_misbehave = true; + } + mvmvif->csa_count = chsw->count; + + mutex_lock(&mvm->mutex); + if (mvmvif->csa_failed) + goto out_unlock; + + WARN_ON(iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(MAC_CONF_GROUP, + CHANNEL_SWITCH_TIME_EVENT_CMD), + 0, sizeof(cmd), &cmd)); +out_unlock: + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_flush_no_vif(struct iwl_mvm *mvm, u32 queues, bool drop) +{ + int i; + + if (!iwl_mvm_has_new_tx_api(mvm)) { + if (drop) { + mutex_lock(&mvm->mutex); + iwl_mvm_flush_tx_path(mvm, + iwl_mvm_flushable_queues(mvm) & queues); + mutex_unlock(&mvm->mutex); + } else { + iwl_trans_wait_tx_queues_empty(mvm->trans, queues); + } + return; + } + + mutex_lock(&mvm->mutex); + for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) { + struct ieee80211_sta *sta; + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i], + lockdep_is_held(&mvm->mutex)); + if (IS_ERR_OR_NULL(sta)) + continue; + + if (drop) + iwl_mvm_flush_sta_tids(mvm, i, 0xFFFF); + else + iwl_mvm_wait_sta_queues_empty(mvm, + iwl_mvm_sta_from_mac80211(sta)); + } + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_mac_flush(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u32 queues, bool drop) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif; + struct iwl_mvm_sta *mvmsta; + struct ieee80211_sta *sta; + int i; + u32 msk = 0; + + if (!vif) { + iwl_mvm_flush_no_vif(mvm, queues, drop); + return; + } + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + /* Make sure we're done with the deferred traffic before flushing */ + flush_work(&mvm->add_stream_wk); + + mutex_lock(&mvm->mutex); + mvmvif = iwl_mvm_vif_from_mac80211(vif); + + /* flush the AP-station and all TDLS peers */ + for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) { + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i], + lockdep_is_held(&mvm->mutex)); + if (IS_ERR_OR_NULL(sta)) + continue; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + if (mvmsta->vif != vif) + continue; + + /* make sure only TDLS peers or the AP are flushed */ + WARN_ON(i != mvmvif->ap_sta_id && !sta->tdls); + + if (drop) { + if (iwl_mvm_flush_sta(mvm, mvmsta, false)) + IWL_ERR(mvm, "flush request fail\n"); + } else { + msk |= mvmsta->tfd_queue_msk; + if (iwl_mvm_has_new_tx_api(mvm)) + iwl_mvm_wait_sta_queues_empty(mvm, mvmsta); + } + } + + mutex_unlock(&mvm->mutex); + + /* this can take a while, and we may need/want other operations + * to succeed while doing this, so do it without the mutex held + */ + if (!drop && !iwl_mvm_has_new_tx_api(mvm)) + iwl_trans_wait_tx_queues_empty(mvm->trans, msk); +} + +static int iwl_mvm_mac_get_survey(struct ieee80211_hw *hw, int idx, + struct survey_info *survey) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + memset(survey, 0, sizeof(*survey)); + + /* only support global statistics right now */ + if (idx != 0) + return -ENOENT; + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_RADIO_BEACON_STATS)) + return -ENOENT; + + mutex_lock(&mvm->mutex); + + if (iwl_mvm_firmware_running(mvm)) { + ret = iwl_mvm_request_statistics(mvm, false); + if (ret) + goto out; + } + + survey->filled = SURVEY_INFO_TIME | + SURVEY_INFO_TIME_RX | + SURVEY_INFO_TIME_TX | + SURVEY_INFO_TIME_SCAN; + survey->time = mvm->accu_radio_stats.on_time_rf + + mvm->radio_stats.on_time_rf; + do_div(survey->time, USEC_PER_MSEC); + + survey->time_rx = mvm->accu_radio_stats.rx_time + + mvm->radio_stats.rx_time; + do_div(survey->time_rx, USEC_PER_MSEC); + + survey->time_tx = mvm->accu_radio_stats.tx_time + + mvm->radio_stats.tx_time; + do_div(survey->time_tx, USEC_PER_MSEC); + + survey->time_scan = mvm->accu_radio_stats.on_time_scan + + mvm->radio_stats.on_time_scan; + do_div(survey->time_scan, USEC_PER_MSEC); + + ret = 0; + out: + mutex_unlock(&mvm->mutex); + return ret; +} + +static void iwl_mvm_set_sta_rate(u32 rate_n_flags, struct rate_info *rinfo) +{ + u32 format = rate_n_flags & RATE_MCS_MOD_TYPE_MSK; + u32 gi_ltf; + + switch (rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK) { + case RATE_MCS_CHAN_WIDTH_20: + rinfo->bw = RATE_INFO_BW_20; + break; + case RATE_MCS_CHAN_WIDTH_40: + rinfo->bw = RATE_INFO_BW_40; + break; + case RATE_MCS_CHAN_WIDTH_80: + rinfo->bw = RATE_INFO_BW_80; + break; + case RATE_MCS_CHAN_WIDTH_160: + rinfo->bw = RATE_INFO_BW_160; + break; + } + + if (format == RATE_MCS_CCK_MSK || + format == RATE_MCS_LEGACY_OFDM_MSK) { + int rate = u32_get_bits(rate_n_flags, RATE_LEGACY_RATE_MSK); + + /* add the offset needed to get to the legacy ofdm indices */ + if (format == RATE_MCS_LEGACY_OFDM_MSK) + rate += IWL_FIRST_OFDM_RATE; + + switch (rate) { + case IWL_RATE_1M_INDEX: + rinfo->legacy = 10; + break; + case IWL_RATE_2M_INDEX: + rinfo->legacy = 20; + break; + case IWL_RATE_5M_INDEX: + rinfo->legacy = 55; + break; + case IWL_RATE_11M_INDEX: + rinfo->legacy = 110; + break; + case IWL_RATE_6M_INDEX: + rinfo->legacy = 60; + break; + case IWL_RATE_9M_INDEX: + rinfo->legacy = 90; + break; + case IWL_RATE_12M_INDEX: + rinfo->legacy = 120; + break; + case IWL_RATE_18M_INDEX: + rinfo->legacy = 180; + break; + case IWL_RATE_24M_INDEX: + rinfo->legacy = 240; + break; + case IWL_RATE_36M_INDEX: + rinfo->legacy = 360; + break; + case IWL_RATE_48M_INDEX: + rinfo->legacy = 480; + break; + case IWL_RATE_54M_INDEX: + rinfo->legacy = 540; + } + return; + } + + rinfo->nss = u32_get_bits(rate_n_flags, + RATE_MCS_NSS_MSK) + 1; + rinfo->mcs = format == RATE_MCS_HT_MSK ? + RATE_HT_MCS_INDEX(rate_n_flags) : + u32_get_bits(rate_n_flags, RATE_MCS_CODE_MSK); + + if (rate_n_flags & RATE_MCS_SGI_MSK) + rinfo->flags |= RATE_INFO_FLAGS_SHORT_GI; + + switch (format) { + case RATE_MCS_HE_MSK: + gi_ltf = u32_get_bits(rate_n_flags, RATE_MCS_HE_GI_LTF_MSK); + + rinfo->flags |= RATE_INFO_FLAGS_HE_MCS; + + if (rate_n_flags & RATE_MCS_HE_106T_MSK) { + rinfo->bw = RATE_INFO_BW_HE_RU; + rinfo->he_ru_alloc = NL80211_RATE_INFO_HE_RU_ALLOC_106; + } + + switch (rate_n_flags & RATE_MCS_HE_TYPE_MSK) { + case RATE_MCS_HE_TYPE_SU: + case RATE_MCS_HE_TYPE_EXT_SU: + if (gi_ltf == 0 || gi_ltf == 1) + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_0_8; + else if (gi_ltf == 2) + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_1_6; + else if (gi_ltf == 3) + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_3_2; + else + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_0_8; + break; + case RATE_MCS_HE_TYPE_MU: + if (gi_ltf == 0 || gi_ltf == 1) + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_0_8; + else if (gi_ltf == 2) + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_1_6; + else + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_3_2; + break; + case RATE_MCS_HE_TYPE_TRIG: + if (gi_ltf == 0 || gi_ltf == 1) + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_1_6; + else + rinfo->he_gi = NL80211_RATE_INFO_HE_GI_3_2; + break; + } + + if (rate_n_flags & RATE_HE_DUAL_CARRIER_MODE_MSK) + rinfo->he_dcm = 1; + break; + case RATE_MCS_HT_MSK: + rinfo->flags |= RATE_INFO_FLAGS_MCS; + break; + case RATE_MCS_VHT_MSK: + rinfo->flags |= RATE_INFO_FLAGS_VHT_MCS; + break; + } +} + +static void iwl_mvm_mac_sta_statistics(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct station_info *sinfo) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + + if (mvmsta->avg_energy) { + sinfo->signal_avg = -(s8)mvmsta->avg_energy; + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_SIGNAL_AVG); + } + + if (iwl_mvm_has_tlc_offload(mvm)) { + struct iwl_lq_sta_rs_fw *lq_sta = &mvmsta->lq_sta.rs_fw; + + iwl_mvm_set_sta_rate(lq_sta->last_rate_n_flags, &sinfo->txrate); + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE); + } + + /* if beacon filtering isn't on mac80211 does it anyway */ + if (!(vif->driver_flags & IEEE80211_VIF_BEACON_FILTER)) + return; + + if (!vif->cfg.assoc) + return; + + mutex_lock(&mvm->mutex); + + if (mvmvif->ap_sta_id != mvmsta->sta_id) + goto unlock; + + if (iwl_mvm_request_statistics(mvm, false)) + goto unlock; + + sinfo->rx_beacon = mvmvif->beacon_stats.num_beacons + + mvmvif->beacon_stats.accu_num_beacons; + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_BEACON_RX); + if (mvmvif->beacon_stats.avg_signal) { + /* firmware only reports a value after RXing a few beacons */ + sinfo->rx_beacon_signal_avg = mvmvif->beacon_stats.avg_signal; + sinfo->filled |= BIT_ULL(NL80211_STA_INFO_BEACON_SIGNAL_AVG); + } + unlock: + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_event_mlme_callback_ini(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + const struct ieee80211_mlme_event *mlme) +{ + if ((mlme->data == ASSOC_EVENT || mlme->data == AUTH_EVENT) && + (mlme->status == MLME_DENIED || mlme->status == MLME_TIMEOUT)) { + iwl_dbg_tlv_time_point(&mvm->fwrt, + IWL_FW_INI_TIME_POINT_ASSOC_FAILED, + NULL); + return; + } + + if (mlme->data == DEAUTH_RX_EVENT || mlme->data == DEAUTH_TX_EVENT) { + iwl_dbg_tlv_time_point(&mvm->fwrt, + IWL_FW_INI_TIME_POINT_DEASSOC, + NULL); + return; + } +} + +static void iwl_mvm_event_mlme_callback(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + const struct ieee80211_event *event) +{ +#define CHECK_MLME_TRIGGER(_cnt, _fmt...) \ + do { \ + if ((trig_mlme->_cnt) && --(trig_mlme->_cnt)) \ + break; \ + iwl_fw_dbg_collect_trig(&(mvm)->fwrt, trig, _fmt); \ + } while (0) + + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_mlme *trig_mlme; + + if (iwl_trans_dbg_ini_valid(mvm->trans)) { + iwl_mvm_event_mlme_callback_ini(mvm, vif, &event->u.mlme); + return; + } + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_MLME); + if (!trig) + return; + + trig_mlme = (void *)trig->data; + + if (event->u.mlme.data == ASSOC_EVENT) { + if (event->u.mlme.status == MLME_DENIED) + CHECK_MLME_TRIGGER(stop_assoc_denied, + "DENIED ASSOC: reason %d", + event->u.mlme.reason); + else if (event->u.mlme.status == MLME_TIMEOUT) + CHECK_MLME_TRIGGER(stop_assoc_timeout, + "ASSOC TIMEOUT"); + } else if (event->u.mlme.data == AUTH_EVENT) { + if (event->u.mlme.status == MLME_DENIED) + CHECK_MLME_TRIGGER(stop_auth_denied, + "DENIED AUTH: reason %d", + event->u.mlme.reason); + else if (event->u.mlme.status == MLME_TIMEOUT) + CHECK_MLME_TRIGGER(stop_auth_timeout, + "AUTH TIMEOUT"); + } else if (event->u.mlme.data == DEAUTH_RX_EVENT) { + CHECK_MLME_TRIGGER(stop_rx_deauth, + "DEAUTH RX %d", event->u.mlme.reason); + } else if (event->u.mlme.data == DEAUTH_TX_EVENT) { + CHECK_MLME_TRIGGER(stop_tx_deauth, + "DEAUTH TX %d", event->u.mlme.reason); + } +#undef CHECK_MLME_TRIGGER +} + +static void iwl_mvm_event_bar_rx_callback(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + const struct ieee80211_event *event) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_ba *ba_trig; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_BA); + if (!trig) + return; + + ba_trig = (void *)trig->data; + + if (!(le16_to_cpu(ba_trig->rx_bar) & BIT(event->u.ba.tid))) + return; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + "BAR received from %pM, tid %d, ssn %d", + event->u.ba.sta->addr, event->u.ba.tid, + event->u.ba.ssn); +} + +static void iwl_mvm_mac_event_callback(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const struct ieee80211_event *event) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + switch (event->type) { + case MLME_EVENT: + iwl_mvm_event_mlme_callback(mvm, vif, event); + break; + case BAR_RX_EVENT: + iwl_mvm_event_bar_rx_callback(mvm, vif, event); + break; + case BA_FRAME_TIMEOUT: + iwl_mvm_event_frame_timeout_callback(mvm, vif, event->u.ba.sta, + event->u.ba.tid); + break; + default: + break; + } +} + +void iwl_mvm_sync_rx_queues_internal(struct iwl_mvm *mvm, + enum iwl_mvm_rxq_notif_type type, + bool sync, + const void *data, u32 size) +{ + struct { + struct iwl_rxq_sync_cmd cmd; + struct iwl_mvm_internal_rxq_notif notif; + } __packed cmd = { + .cmd.rxq_mask = cpu_to_le32(BIT(mvm->trans->num_rx_queues) - 1), + .cmd.count = + cpu_to_le32(sizeof(struct iwl_mvm_internal_rxq_notif) + + size), + .notif.type = type, + .notif.sync = sync, + }; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(DATA_PATH_GROUP, TRIGGER_RX_QUEUES_NOTIF_CMD), + .data[0] = &cmd, + .len[0] = sizeof(cmd), + .data[1] = data, + .len[1] = size, + .flags = sync ? 0 : CMD_ASYNC, + }; + int ret; + + /* size must be a multiple of DWORD */ + if (WARN_ON(cmd.cmd.count & cpu_to_le32(3))) + return; + + if (!iwl_mvm_has_new_rx_api(mvm)) + return; + + if (sync) { + cmd.notif.cookie = mvm->queue_sync_cookie; + mvm->queue_sync_state = (1 << mvm->trans->num_rx_queues) - 1; + } + + ret = iwl_mvm_send_cmd(mvm, &hcmd); + if (ret) { + IWL_ERR(mvm, "Failed to trigger RX queues sync (%d)\n", ret); + goto out; + } + + if (sync) { + lockdep_assert_held(&mvm->mutex); + ret = wait_event_timeout(mvm->rx_sync_waitq, + READ_ONCE(mvm->queue_sync_state) == 0 || + iwl_mvm_is_radio_killed(mvm), + HZ); + WARN_ONCE(!ret && !iwl_mvm_is_radio_killed(mvm), + "queue sync: failed to sync, state is 0x%lx\n", + mvm->queue_sync_state); + } + +out: + if (sync) { + mvm->queue_sync_state = 0; + mvm->queue_sync_cookie++; + } +} + +static void iwl_mvm_sync_rx_queues(struct ieee80211_hw *hw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mutex_lock(&mvm->mutex); + iwl_mvm_sync_rx_queues_internal(mvm, IWL_MVM_RXQ_EMPTY, true, NULL, 0); + mutex_unlock(&mvm->mutex); +} + +static int +iwl_mvm_mac_get_ftm_responder_stats(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_ftm_responder_stats *stats) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif->p2p || vif->type != NL80211_IFTYPE_AP || + !mvmvif->ap_ibss_active || !vif->bss_conf.ftm_responder) + return -EINVAL; + + mutex_lock(&mvm->mutex); + *stats = mvm->ftm_resp_stats; + mutex_unlock(&mvm->mutex); + + stats->filled = BIT(NL80211_FTM_STATS_SUCCESS_NUM) | + BIT(NL80211_FTM_STATS_PARTIAL_NUM) | + BIT(NL80211_FTM_STATS_FAILED_NUM) | + BIT(NL80211_FTM_STATS_ASAP_NUM) | + BIT(NL80211_FTM_STATS_NON_ASAP_NUM) | + BIT(NL80211_FTM_STATS_TOTAL_DURATION_MSEC) | + BIT(NL80211_FTM_STATS_UNKNOWN_TRIGGERS_NUM) | + BIT(NL80211_FTM_STATS_RESCHEDULE_REQUESTS_NUM) | + BIT(NL80211_FTM_STATS_OUT_OF_WINDOW_TRIGGERS_NUM); + + return 0; +} + +static int iwl_mvm_start_pmsr(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *request) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_ftm_start(mvm, vif, request); + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_abort_pmsr(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *request) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mutex_lock(&mvm->mutex); + iwl_mvm_ftm_abort(mvm, request); + mutex_unlock(&mvm->mutex); +} + +static bool iwl_mvm_can_hw_csum(struct sk_buff *skb) +{ + u8 protocol = ip_hdr(skb)->protocol; + + if (!IS_ENABLED(CONFIG_INET)) + return false; + + return protocol == IPPROTO_TCP || protocol == IPPROTO_UDP; +} + +static bool iwl_mvm_mac_can_aggregate(struct ieee80211_hw *hw, + struct sk_buff *head, + struct sk_buff *skb) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + if (mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + return iwl_mvm_tx_csum_bz(mvm, head, true) == + iwl_mvm_tx_csum_bz(mvm, skb, true); + + /* For now don't aggregate IPv6 in AMSDU */ + if (skb->protocol != htons(ETH_P_IP)) + return false; + + if (!iwl_mvm_is_csum_supported(mvm)) + return true; + + return iwl_mvm_can_hw_csum(skb) == iwl_mvm_can_hw_csum(head); +} + +const struct ieee80211_ops iwl_mvm_hw_ops = { + .tx = iwl_mvm_mac_tx, + .wake_tx_queue = iwl_mvm_mac_wake_tx_queue, + .ampdu_action = iwl_mvm_mac_ampdu_action, + .get_antenna = iwl_mvm_op_get_antenna, + .start = iwl_mvm_mac_start, + .reconfig_complete = iwl_mvm_mac_reconfig_complete, + .stop = iwl_mvm_mac_stop, + .add_interface = iwl_mvm_mac_add_interface, + .remove_interface = iwl_mvm_mac_remove_interface, + .config = iwl_mvm_mac_config, + .prepare_multicast = iwl_mvm_prepare_multicast, + .configure_filter = iwl_mvm_configure_filter, + .config_iface_filter = iwl_mvm_config_iface_filter, + .bss_info_changed = iwl_mvm_bss_info_changed, + .hw_scan = iwl_mvm_mac_hw_scan, + .cancel_hw_scan = iwl_mvm_mac_cancel_hw_scan, + .sta_pre_rcu_remove = iwl_mvm_sta_pre_rcu_remove, + .sta_state = iwl_mvm_mac_sta_state, + .sta_notify = iwl_mvm_mac_sta_notify, + .allow_buffered_frames = iwl_mvm_mac_allow_buffered_frames, + .release_buffered_frames = iwl_mvm_mac_release_buffered_frames, + .set_rts_threshold = iwl_mvm_mac_set_rts_threshold, + .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_complete_tx = iwl_mvm_mac_mgd_complete_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, + .set_key = iwl_mvm_mac_set_key, + .update_tkip_key = iwl_mvm_mac_update_tkip_key, + .remain_on_channel = iwl_mvm_roc, + .cancel_remain_on_channel = iwl_mvm_cancel_roc, + .add_chanctx = iwl_mvm_add_chanctx, + .remove_chanctx = iwl_mvm_remove_chanctx, + .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, + .stop_ap = iwl_mvm_stop_ap, + .join_ibss = iwl_mvm_start_ibss, + .leave_ibss = iwl_mvm_stop_ibss, + + .tx_last_beacon = iwl_mvm_tx_last_beacon, + + .set_tim = iwl_mvm_set_tim, + + .channel_switch = iwl_mvm_channel_switch, + .pre_channel_switch = iwl_mvm_pre_channel_switch, + .post_channel_switch = iwl_mvm_post_channel_switch, + .abort_channel_switch = iwl_mvm_abort_channel_switch, + .channel_switch_rx_beacon = iwl_mvm_channel_switch_rx_beacon, + + .tdls_channel_switch = iwl_mvm_tdls_channel_switch, + .tdls_cancel_channel_switch = iwl_mvm_tdls_cancel_channel_switch, + .tdls_recv_channel_switch = iwl_mvm_tdls_recv_channel_switch, + + .event_callback = iwl_mvm_mac_event_callback, + + .sync_rx_queues = iwl_mvm_sync_rx_queues, + + CFG80211_TESTMODE_CMD(iwl_mvm_mac_testmode_cmd) + +#ifdef CONFIG_PM_SLEEP + /* look at d3.c */ + .suspend = iwl_mvm_suspend, + .resume = iwl_mvm_resume, + .set_wakeup = iwl_mvm_set_wakeup, + .set_rekey_data = iwl_mvm_set_rekey_data, +#if IS_ENABLED(CONFIG_IPV6) + .ipv6_addr_change = iwl_mvm_ipv6_addr_change, +#endif + .set_default_unicast_key = iwl_mvm_set_default_unicast_key, +#endif + .get_survey = iwl_mvm_mac_get_survey, + .sta_statistics = iwl_mvm_mac_sta_statistics, + .get_ftm_responder_stats = iwl_mvm_mac_get_ftm_responder_stats, + .start_pmsr = iwl_mvm_start_pmsr, + .abort_pmsr = iwl_mvm_abort_pmsr, + + .can_aggregate_in_amsdu = iwl_mvm_mac_can_aggregate, +#ifdef CONFIG_IWLWIFI_DEBUGFS + .sta_add_debugfs = iwl_mvm_sta_add_debugfs, +#endif +}; diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h new file mode 100644 index 000000000..f5c921c41 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h @@ -0,0 +1,2228 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __IWL_MVM_H__ +#define __IWL_MVM_H__ + +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/leds.h> +#include <linux/in6.h> + +#ifdef CONFIG_THERMAL +#include <linux/thermal.h> +#endif + +#include <linux/ktime.h> + +#include "iwl-op-mode.h" +#include "iwl-trans.h" +#include "fw/notif-wait.h" +#include "iwl-eeprom-parse.h" +#include "fw/file.h" +#include "iwl-config.h" +#include "sta.h" +#include "fw-api.h" +#include "constants.h" +#include "fw/runtime.h" +#include "fw/dbg.h" +#include "fw/acpi.h" +#include "mei/iwl-mei.h" +#include "iwl-nvm-parse.h" + +#include <linux/average.h> + +#define IWL_MVM_MAX_ADDRESSES 5 +/* RSSI offset for WkP */ +#define IWL_RSSI_OFFSET 50 +#define IWL_MVM_MISSED_BEACONS_THRESHOLD 8 +#define IWL_MVM_MISSED_BEACONS_THRESHOLD_LONG 16 + +/* A TimeUnit is 1024 microsecond */ +#define MSEC_TO_TU(_msec) (_msec*1000/1024) + +/* For GO, this value represents the number of TUs before CSA "beacon + * 0" TBTT when the CSA time-event needs to be scheduled to start. It + * must be big enough to ensure that we switch in time. + */ +#define IWL_MVM_CHANNEL_SWITCH_TIME_GO 40 + +/* For client, this value represents the number of TUs before CSA + * "beacon 1" TBTT, instead. This is because we don't know when the + * GO/AP will be in the new channel, so we switch early enough. + */ +#define IWL_MVM_CHANNEL_SWITCH_TIME_CLIENT 10 + +/* + * This value (in TUs) is used to fine tune the CSA NoA end time which should + * be just before "beacon 0" TBTT. + */ +#define IWL_MVM_CHANNEL_SWITCH_MARGIN 4 + +/* + * Number of beacons to transmit on a new channel until we unblock tx to + * the stations, even if we didn't identify them on a new channel + */ +#define IWL_MVM_CS_UNBLOCK_TX_TIMEOUT 3 + +/* offchannel queue towards mac80211 */ +#define IWL_MVM_OFFCHANNEL_QUEUE 0 + +extern const struct ieee80211_ops iwl_mvm_hw_ops; + +/** + * struct iwl_mvm_mod_params - module parameters for iwlmvm + * @init_dbg: if true, then the NIC won't be stopped if the INIT fw asserted. + * We will register to mac80211 to have testmode working. The NIC must not + * be up'ed after the INIT fw asserted. This is useful to be able to use + * proprietary tools over testmode to debug the INIT fw. + * @power_scheme: one of enum iwl_power_scheme + */ +struct iwl_mvm_mod_params { + bool init_dbg; + int power_scheme; +}; +extern struct iwl_mvm_mod_params iwlmvm_mod_params; + +struct iwl_mvm_phy_ctxt { + u16 id; + u16 color; + u32 ref; + + enum nl80211_chan_width width; + + struct ieee80211_channel *channel; + + /* track for RLC config command */ + u32 center_freq1; +}; + +struct iwl_mvm_time_event_data { + struct ieee80211_vif *vif; + struct list_head list; + unsigned long end_jiffies; + u32 duration; + bool running; + u32 uid; + + /* + * The access to the 'id' field must be done when the + * mvm->time_event_lock is held, as it value is used to indicate + * if the te is in the time event list or not (when id == TE_MAX) + */ + u32 id; +}; + + /* Power management */ + +/** + * enum iwl_power_scheme + * @IWL_POWER_LEVEL_CAM - Continuously Active Mode + * @IWL_POWER_LEVEL_BPS - Balanced Power Save (default) + * @IWL_POWER_LEVEL_LP - Low Power + */ +enum iwl_power_scheme { + IWL_POWER_SCHEME_CAM = 1, + IWL_POWER_SCHEME_BPS, + IWL_POWER_SCHEME_LP +}; + +#define IWL_CONN_MAX_LISTEN_INTERVAL 10 +#define IWL_UAPSD_MAX_SP IEEE80211_WMM_IE_STA_QOSINFO_SP_ALL + +#ifdef CONFIG_IWLWIFI_DEBUGFS +enum iwl_dbgfs_pm_mask { + MVM_DEBUGFS_PM_KEEP_ALIVE = BIT(0), + MVM_DEBUGFS_PM_SKIP_OVER_DTIM = BIT(1), + MVM_DEBUGFS_PM_SKIP_DTIM_PERIODS = BIT(2), + MVM_DEBUGFS_PM_RX_DATA_TIMEOUT = BIT(3), + MVM_DEBUGFS_PM_TX_DATA_TIMEOUT = BIT(4), + MVM_DEBUGFS_PM_LPRX_ENA = BIT(6), + MVM_DEBUGFS_PM_LPRX_RSSI_THRESHOLD = BIT(7), + MVM_DEBUGFS_PM_SNOOZE_ENABLE = BIT(8), + MVM_DEBUGFS_PM_UAPSD_MISBEHAVING = BIT(9), + MVM_DEBUGFS_PM_USE_PS_POLL = BIT(10), +}; + +struct iwl_dbgfs_pm { + u16 keep_alive_seconds; + u32 rx_data_timeout; + u32 tx_data_timeout; + bool skip_over_dtim; + u8 skip_dtim_periods; + bool lprx_ena; + u32 lprx_rssi_threshold; + bool snooze_ena; + bool uapsd_misbehaving; + bool use_ps_poll; + int mask; +}; + +/* beacon filtering */ + +enum iwl_dbgfs_bf_mask { + MVM_DEBUGFS_BF_ENERGY_DELTA = BIT(0), + MVM_DEBUGFS_BF_ROAMING_ENERGY_DELTA = BIT(1), + MVM_DEBUGFS_BF_ROAMING_STATE = BIT(2), + MVM_DEBUGFS_BF_TEMP_THRESHOLD = BIT(3), + MVM_DEBUGFS_BF_TEMP_FAST_FILTER = BIT(4), + MVM_DEBUGFS_BF_TEMP_SLOW_FILTER = BIT(5), + MVM_DEBUGFS_BF_ENABLE_BEACON_FILTER = BIT(6), + MVM_DEBUGFS_BF_DEBUG_FLAG = BIT(7), + MVM_DEBUGFS_BF_ESCAPE_TIMER = BIT(8), + MVM_DEBUGFS_BA_ESCAPE_TIMER = BIT(9), + MVM_DEBUGFS_BA_ENABLE_BEACON_ABORT = BIT(10), +}; + +struct iwl_dbgfs_bf { + u32 bf_energy_delta; + u32 bf_roaming_energy_delta; + u32 bf_roaming_state; + u32 bf_temp_threshold; + u32 bf_temp_fast_filter; + u32 bf_temp_slow_filter; + u32 bf_enable_beacon_filter; + u32 bf_debug_flag; + u32 bf_escape_timer; + u32 ba_escape_timer; + u32 ba_enable_beacon_abort; + int mask; +}; +#endif + +enum iwl_mvm_smps_type_request { + IWL_MVM_SMPS_REQ_BT_COEX, + IWL_MVM_SMPS_REQ_TT, + IWL_MVM_SMPS_REQ_PROT, + IWL_MVM_SMPS_REQ_FW, + NUM_IWL_MVM_SMPS_REQ, +}; + +enum iwl_bt_force_ant_mode { + BT_FORCE_ANT_DIS = 0, + BT_FORCE_ANT_AUTO, + BT_FORCE_ANT_BT, + BT_FORCE_ANT_WIFI, + + BT_FORCE_ANT_MAX, +}; + +/** + * struct iwl_mvm_low_latency_force - low latency force mode set by debugfs + * @LOW_LATENCY_FORCE_UNSET: unset force mode + * @LOW_LATENCY_FORCE_ON: for low latency on + * @LOW_LATENCY_FORCE_OFF: for low latency off + * @NUM_LOW_LATENCY_FORCE: max num of modes + */ +enum iwl_mvm_low_latency_force { + LOW_LATENCY_FORCE_UNSET, + LOW_LATENCY_FORCE_ON, + LOW_LATENCY_FORCE_OFF, + NUM_LOW_LATENCY_FORCE +}; + +/** +* struct iwl_mvm_low_latency_cause - low latency set causes +* @LOW_LATENCY_TRAFFIC: indicates low latency traffic was detected +* @LOW_LATENCY_DEBUGFS: low latency mode set from debugfs +* @LOW_LATENCY_VCMD: low latency mode set from vendor command +* @LOW_LATENCY_VIF_TYPE: low latency mode set because of vif type (ap) +* @LOW_LATENCY_DEBUGFS_FORCE_ENABLE: indicate that force mode is enabled +* the actual set/unset is done with LOW_LATENCY_DEBUGFS_FORCE +* @LOW_LATENCY_DEBUGFS_FORCE: low latency force mode from debugfs +* set this with LOW_LATENCY_DEBUGFS_FORCE_ENABLE flag +* in low_latency. +*/ +enum iwl_mvm_low_latency_cause { + LOW_LATENCY_TRAFFIC = BIT(0), + LOW_LATENCY_DEBUGFS = BIT(1), + LOW_LATENCY_VCMD = BIT(2), + LOW_LATENCY_VIF_TYPE = BIT(3), + LOW_LATENCY_DEBUGFS_FORCE_ENABLE = BIT(4), + LOW_LATENCY_DEBUGFS_FORCE = BIT(5), +}; + +/** +* struct iwl_mvm_vif_bf_data - beacon filtering related data +* @bf_enabled: indicates if beacon filtering is enabled +* @ba_enabled: indicated if beacon abort is enabled +* @ave_beacon_signal: average beacon signal +* @last_cqm_event: rssi of the last cqm event +* @bt_coex_min_thold: minimum threshold for BT coex +* @bt_coex_max_thold: maximum threshold for BT coex +* @last_bt_coex_event: rssi of the last BT coex event +*/ +struct iwl_mvm_vif_bf_data { + bool bf_enabled; + bool ba_enabled; + int ave_beacon_signal; + int last_cqm_event; + int bt_coex_min_thold; + int bt_coex_max_thold; + int last_bt_coex_event; +}; + +/** + * struct iwl_probe_resp_data - data for NoA/CSA updates + * @rcu_head: used for freeing the data on update + * @notif: notification data + * @noa_len: length of NoA attribute, calculated from the notification + */ +struct iwl_probe_resp_data { + struct rcu_head rcu_head; + struct iwl_probe_resp_data_notif notif; + int noa_len; +}; + +/** + * struct iwl_mvm_vif - data per Virtual Interface, it is a MAC context + * @id: between 0 and 3 + * @color: to solve races upon MAC addition and removal + * @ap_sta_id: the sta_id of the AP - valid only if VIF type is STA + * @bssid: BSSID for this (client) interface + * @associated: indicates that we're currently associated, used only for + * managing the firmware state in iwl_mvm_bss_info_changed_station() + * @ap_assoc_sta_count: count of stations associated to us - valid only + * if VIF type is AP + * @uploaded: indicates the MAC context has been added to the device + * @ap_ibss_active: indicates that AP/IBSS is configured and that the interface + * should get quota etc. + * @pm_enabled - Indicate if MAC power management is allowed + * @monitor_active: indicates that monitor context is configured, and that the + * interface should get quota etc. + * @low_latency: bit flags for low latency + * see enum &iwl_mvm_low_latency_cause for causes. + * @low_latency_actual: boolean, indicates low latency is set, + * as a result from low_latency bit flags and takes force into account. + * @authorized: indicates the AP station was set to authorized + * @ps_disabled: indicates that this interface requires PS to be disabled + * @queue_params: QoS params for this MAC + * @bcast_sta: station used for broadcast packets. Used by the following + * vifs: P2P_DEVICE, GO and AP. + * @beacon_skb: the skb used to hold the AP/GO beacon template + * @smps_requests: the SMPS requests of different parts of the driver, + * combined on update to yield the overall request to mac80211. + * @beacon_stats: beacon statistics, containing the # of received beacons, + * # of received beacons accumulated over FW restart, and the current + * average signal of beacons retrieved from the firmware + * @csa_failed: CSA failed to schedule time event, report an error later + * @features: hw features active for this vif + * @probe_resp_data: data from FW notification to store NOA and CSA related + * data to be inserted into probe response. + */ +struct iwl_mvm_vif { + struct iwl_mvm *mvm; + u16 id; + u16 color; + u8 ap_sta_id; + + u8 bssid[ETH_ALEN]; + bool associated; + u8 ap_assoc_sta_count; + + u16 cab_queue; + + bool uploaded; + bool ap_ibss_active; + bool pm_enabled; + bool monitor_active; + u8 low_latency: 6; + u8 low_latency_actual: 1; + u8 authorized:1; + bool ps_disabled; + struct iwl_mvm_vif_bf_data bf_data; + + struct { + u32 num_beacons, accu_num_beacons; + u8 avg_signal; + } beacon_stats; + + u32 ap_beacon_time; + + enum iwl_tsf_id tsf_id; + + /* + * QoS data from mac80211, need to store this here + * as mac80211 has a separate callback but we need + * to have the data for the MAC context + */ + struct ieee80211_tx_queue_params queue_params[IEEE80211_NUM_ACS]; + struct iwl_mvm_time_event_data time_event_data; + struct iwl_mvm_time_event_data hs_time_event_data; + + struct iwl_mvm_int_sta bcast_sta; + struct iwl_mvm_int_sta mcast_sta; + + /* + * Assigned while mac80211 has the interface in a channel context, + * or, for P2P Device, while it exists. + */ + struct iwl_mvm_phy_ctxt *phy_ctxt; + +#ifdef CONFIG_PM + /* WoWLAN GTK rekey data */ + struct { + u8 kck[NL80211_KCK_EXT_LEN]; + u8 kek[NL80211_KEK_EXT_LEN]; + size_t kek_len; + size_t kck_len; + u32 akm; + __le64 replay_ctr; + bool valid; + } rekey_data; + + int tx_key_idx; + + bool seqno_valid; + u16 seqno; +#endif + +#if IS_ENABLED(CONFIG_IPV6) + /* IPv6 addresses for WoWLAN */ + struct in6_addr target_ipv6_addrs[IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_MAX]; + unsigned long tentative_addrs[BITS_TO_LONGS(IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_MAX)]; + int num_target_ipv6_addrs; +#endif + +#ifdef CONFIG_IWLWIFI_DEBUGFS + struct dentry *dbgfs_dir; + struct dentry *dbgfs_slink; + struct iwl_dbgfs_pm dbgfs_pm; + struct iwl_dbgfs_bf dbgfs_bf; + struct iwl_mac_power_cmd mac_pwr_cmd; + int dbgfs_quota_min; +#endif + + enum ieee80211_smps_mode smps_requests[NUM_IWL_MVM_SMPS_REQ]; + + /* FW identified misbehaving AP */ + u8 uapsd_misbehaving_bssid[ETH_ALEN]; + + struct delayed_work uapsd_nonagg_detected_wk; + + /* Indicates that CSA countdown may be started */ + bool csa_countdown; + bool csa_failed; + u16 csa_target_freq; + u16 csa_count; + u16 csa_misbehave; + struct delayed_work csa_work; + + /* Indicates that we are waiting for a beacon on a new channel */ + bool csa_bcn_pending; + + /* TCP Checksum Offload */ + netdev_features_t features; + + struct iwl_probe_resp_data __rcu *probe_resp_data; + + /* we can only have 2 GTK + 2 IGTK active at a time */ + struct ieee80211_key_conf *ap_early_keys[4]; + + /* 26-tone RU OFDMA transmissions should be blocked */ + bool he_ru_2mhz_block; + + struct { + struct ieee80211_key_conf __rcu *keys[2]; + } bcn_prot; +}; + +static inline struct iwl_mvm_vif * +iwl_mvm_vif_from_mac80211(struct ieee80211_vif *vif) +{ + return (void *)vif->drv_priv; +} + +extern const u8 tid_to_mac80211_ac[]; + +#define IWL_MVM_SCAN_STOPPING_SHIFT 8 + +enum iwl_scan_status { + IWL_MVM_SCAN_REGULAR = BIT(0), + IWL_MVM_SCAN_SCHED = BIT(1), + IWL_MVM_SCAN_NETDETECT = BIT(2), + + IWL_MVM_SCAN_STOPPING_REGULAR = BIT(8), + IWL_MVM_SCAN_STOPPING_SCHED = BIT(9), + IWL_MVM_SCAN_STOPPING_NETDETECT = BIT(10), + + IWL_MVM_SCAN_REGULAR_MASK = IWL_MVM_SCAN_REGULAR | + IWL_MVM_SCAN_STOPPING_REGULAR, + IWL_MVM_SCAN_SCHED_MASK = IWL_MVM_SCAN_SCHED | + IWL_MVM_SCAN_STOPPING_SCHED, + IWL_MVM_SCAN_NETDETECT_MASK = IWL_MVM_SCAN_NETDETECT | + IWL_MVM_SCAN_STOPPING_NETDETECT, + + IWL_MVM_SCAN_STOPPING_MASK = 0xff << IWL_MVM_SCAN_STOPPING_SHIFT, + IWL_MVM_SCAN_MASK = 0xff, +}; + +enum iwl_mvm_scan_type { + IWL_SCAN_TYPE_NOT_SET, + IWL_SCAN_TYPE_UNASSOC, + IWL_SCAN_TYPE_WILD, + IWL_SCAN_TYPE_MILD, + IWL_SCAN_TYPE_FRAGMENTED, + IWL_SCAN_TYPE_FAST_BALANCE, +}; + +enum iwl_mvm_sched_scan_pass_all_states { + SCHED_SCAN_PASS_ALL_DISABLED, + SCHED_SCAN_PASS_ALL_ENABLED, + SCHED_SCAN_PASS_ALL_FOUND, +}; + +/** + * struct iwl_mvm_tt_mgnt - Thermal Throttling Management structure + * @ct_kill_exit: worker to exit thermal kill + * @dynamic_smps: Is thermal throttling enabled dynamic_smps? + * @tx_backoff: The current thremal throttling tx backoff in uSec. + * @min_backoff: The minimal tx backoff due to power restrictions + * @params: Parameters to configure the thermal throttling algorithm. + * @throttle: Is thermal throttling is active? + */ +struct iwl_mvm_tt_mgmt { + struct delayed_work ct_kill_exit; + bool dynamic_smps; + u32 tx_backoff; + u32 min_backoff; + struct iwl_tt_params params; + bool throttle; +}; + +#ifdef CONFIG_THERMAL +/** + *struct iwl_mvm_thermal_device - thermal zone related data + * @temp_trips: temperature thresholds for report + * @fw_trips_index: keep indexes to original array - temp_trips + * @tzone: thermal zone device data +*/ +struct iwl_mvm_thermal_device { + s16 temp_trips[IWL_MAX_DTS_TRIPS]; + u8 fw_trips_index[IWL_MAX_DTS_TRIPS]; + struct thermal_zone_device *tzone; +}; + +/* + * struct iwl_mvm_cooling_device + * @cur_state: current state + * @cdev: struct thermal cooling device + */ +struct iwl_mvm_cooling_device { + u32 cur_state; + struct thermal_cooling_device *cdev; +}; +#endif + +#define IWL_MVM_NUM_LAST_FRAMES_UCODE_RATES 8 + +struct iwl_mvm_frame_stats { + u32 legacy_frames; + u32 ht_frames; + u32 vht_frames; + u32 bw_20_frames; + u32 bw_40_frames; + u32 bw_80_frames; + u32 bw_160_frames; + u32 sgi_frames; + u32 ngi_frames; + u32 siso_frames; + u32 mimo2_frames; + u32 agg_frames; + u32 ampdu_count; + u32 success_frames; + u32 fail_frames; + u32 last_rates[IWL_MVM_NUM_LAST_FRAMES_UCODE_RATES]; + int last_frame_idx; +}; + +#define IWL_MVM_DEBUG_SET_TEMPERATURE_DISABLE 0xff +#define IWL_MVM_DEBUG_SET_TEMPERATURE_MIN -100 +#define IWL_MVM_DEBUG_SET_TEMPERATURE_MAX 200 + +enum iwl_mvm_tdls_cs_state { + IWL_MVM_TDLS_SW_IDLE = 0, + IWL_MVM_TDLS_SW_REQ_SENT, + IWL_MVM_TDLS_SW_RESP_RCVD, + IWL_MVM_TDLS_SW_REQ_RCVD, + IWL_MVM_TDLS_SW_ACTIVE, +}; + +enum iwl_mvm_traffic_load { + IWL_MVM_TRAFFIC_LOW, + IWL_MVM_TRAFFIC_MEDIUM, + IWL_MVM_TRAFFIC_HIGH, +}; + +DECLARE_EWMA(rate, 16, 16) + +struct iwl_mvm_tcm_mac { + struct { + u32 pkts[IEEE80211_NUM_ACS]; + u32 airtime; + } tx; + struct { + u32 pkts[IEEE80211_NUM_ACS]; + u32 airtime; + u32 last_ampdu_ref; + } rx; + struct { + /* track AP's transfer in client mode */ + u64 rx_bytes; + struct ewma_rate rate; + bool detected; + } uapsd_nonagg_detect; + bool opened_rx_ba_sessions; +}; + +struct iwl_mvm_tcm { + struct delayed_work work; + spinlock_t lock; /* used when time elapsed */ + unsigned long ts; /* timestamp when period ends */ + unsigned long ll_ts; + unsigned long uapsd_nonagg_ts; + bool paused; + struct iwl_mvm_tcm_mac data[NUM_MAC_INDEX_DRIVER]; + struct { + u32 elapsed; /* milliseconds for this TCM period */ + u32 airtime[NUM_MAC_INDEX_DRIVER]; + enum iwl_mvm_traffic_load load[NUM_MAC_INDEX_DRIVER]; + enum iwl_mvm_traffic_load band_load[NUM_NL80211_BANDS]; + enum iwl_mvm_traffic_load global_load; + bool low_latency[NUM_MAC_INDEX_DRIVER]; + bool change[NUM_MAC_INDEX_DRIVER]; + } result; +}; + +/** + * struct iwl_mvm_reorder_buffer - per ra/tid/queue reorder buffer + * @head_sn: reorder window head sn + * @num_stored: number of mpdus stored in the buffer + * @buf_size: the reorder buffer size as set by the last addba request + * @queue: queue of this reorder buffer + * @last_amsdu: track last ASMDU SN for duplication detection + * @last_sub_index: track ASMDU sub frame index for duplication detection + * @reorder_timer: timer for frames are in the reorder buffer. For AMSDU + * it is the time of last received sub-frame + * @removed: prevent timer re-arming + * @valid: reordering is valid for this queue + * @lock: protect reorder buffer internal state + * @mvm: mvm pointer, needed for frame timer context + * @consec_oldsn_drops: consecutive drops due to old SN + * @consec_oldsn_ampdu_gp2: A-MPDU GP2 timestamp to track + * when to apply old SN consecutive drop workaround + * @consec_oldsn_prev_drop: track whether or not an MPDU + * that was single/part of the previous A-MPDU was + * dropped due to old SN + */ +struct iwl_mvm_reorder_buffer { + u16 head_sn; + u16 num_stored; + u16 buf_size; + int queue; + u16 last_amsdu; + u8 last_sub_index; + struct timer_list reorder_timer; + bool removed; + bool valid; + spinlock_t lock; + struct iwl_mvm *mvm; + unsigned int consec_oldsn_drops; + u32 consec_oldsn_ampdu_gp2; + unsigned int consec_oldsn_prev_drop:1; +} ____cacheline_aligned_in_smp; + +/** + * struct _iwl_mvm_reorder_buf_entry - reorder buffer entry per-queue/per-seqno + * @frames: list of skbs stored + * @reorder_time: time the packet was stored in the reorder buffer + */ +struct _iwl_mvm_reorder_buf_entry { + struct sk_buff_head frames; + unsigned long reorder_time; +}; + +/* make this indirection to get the aligned thing */ +struct iwl_mvm_reorder_buf_entry { + struct _iwl_mvm_reorder_buf_entry e; +} +#ifndef __CHECKER__ +/* sparse doesn't like this construct: "bad integer constant expression" */ +__aligned(roundup_pow_of_two(sizeof(struct _iwl_mvm_reorder_buf_entry))) +#endif +; + +/** + * struct iwl_mvm_baid_data - BA session data + * @sta_id: station id + * @tid: tid of the session + * @baid baid of the session + * @timeout: the timeout set in the addba request + * @entries_per_queue: # of buffers per queue, this actually gets + * aligned up to avoid cache line sharing between queues + * @last_rx: last rx jiffies, updated only if timeout passed from last update + * @session_timer: timer to check if BA session expired, runs at 2 * timeout + * @mvm: mvm pointer, needed for timer context + * @reorder_buf: reorder buffer, allocated per queue + * @reorder_buf_data: data + */ +struct iwl_mvm_baid_data { + struct rcu_head rcu_head; + u8 sta_id; + u8 tid; + u8 baid; + u16 timeout; + u16 entries_per_queue; + unsigned long last_rx; + struct timer_list session_timer; + struct iwl_mvm_baid_data __rcu **rcu_ptr; + struct iwl_mvm *mvm; + struct iwl_mvm_reorder_buffer reorder_buf[IWL_MAX_RX_HW_QUEUES]; + struct iwl_mvm_reorder_buf_entry entries[]; +}; + +static inline struct iwl_mvm_baid_data * +iwl_mvm_baid_data_from_reorder_buf(struct iwl_mvm_reorder_buffer *buf) +{ + return (void *)((u8 *)buf - + offsetof(struct iwl_mvm_baid_data, reorder_buf) - + sizeof(*buf) * buf->queue); +} + +/* + * enum iwl_mvm_queue_status - queue status + * @IWL_MVM_QUEUE_FREE: the queue is not allocated nor reserved + * Basically, this means that this queue can be used for any purpose + * @IWL_MVM_QUEUE_RESERVED: queue is reserved but not yet in use + * This is the state of a queue that has been dedicated for some RATID + * (agg'd or not), but that hasn't yet gone through the actual enablement + * of iwl_mvm_enable_txq(), and therefore no traffic can go through it yet. + * Note that in this state there is no requirement to already know what TID + * should be used with this queue, it is just marked as a queue that will + * be used, and shouldn't be allocated to anyone else. + * @IWL_MVM_QUEUE_READY: queue is ready to be used + * This is the state of a queue that has been fully configured (including + * SCD pointers, etc), has a specific RA/TID assigned to it, and can be + * used to send traffic. + * @IWL_MVM_QUEUE_SHARED: queue is shared, or in a process of becoming shared + * This is a state in which a single queue serves more than one TID, all of + * which are not aggregated. Note that the queue is only associated to one + * RA. + */ +enum iwl_mvm_queue_status { + IWL_MVM_QUEUE_FREE, + IWL_MVM_QUEUE_RESERVED, + IWL_MVM_QUEUE_READY, + IWL_MVM_QUEUE_SHARED, +}; + +#define IWL_MVM_DQA_QUEUE_TIMEOUT (5 * HZ) +#define IWL_MVM_INVALID_QUEUE 0xFFFF + +#define IWL_MVM_NUM_CIPHERS 10 + + +struct iwl_mvm_txq { + struct list_head list; + u16 txq_id; + atomic_t tx_request; +#define IWL_MVM_TXQ_STATE_STOP_FULL 0 +#define IWL_MVM_TXQ_STATE_STOP_REDIRECT 1 +#define IWL_MVM_TXQ_STATE_READY 2 + unsigned long state; +}; + +static inline struct iwl_mvm_txq * +iwl_mvm_txq_from_mac80211(struct ieee80211_txq *txq) +{ + return (void *)txq->drv_priv; +} + +static inline struct iwl_mvm_txq * +iwl_mvm_txq_from_tid(struct ieee80211_sta *sta, u8 tid) +{ + if (tid == IWL_MAX_TID_COUNT) + tid = IEEE80211_NUM_TIDS; + + return (void *)sta->txq[tid]->drv_priv; +} + +/** + * struct iwl_mvm_tvqm_txq_info - maps TVQM hw queue to tid + * + * @sta_id: sta id + * @txq_tid: txq tid + */ +struct iwl_mvm_tvqm_txq_info { + u8 sta_id; + u8 txq_tid; +}; + +struct iwl_mvm_dqa_txq_info { + u8 ra_sta_id; /* The RA this queue is mapped to, if exists */ + bool reserved; /* Is this the TXQ reserved for a STA */ + u8 mac80211_ac; /* The mac80211 AC this queue is mapped to */ + u8 txq_tid; /* The TID "owner" of this queue*/ + u16 tid_bitmap; /* Bitmap of the TIDs mapped to this queue */ + /* Timestamp for inactivation per TID of this queue */ + unsigned long last_frame_time[IWL_MAX_TID_COUNT + 1]; + enum iwl_mvm_queue_status status; +}; + +struct iwl_mvm { + /* for logger access */ + struct device *dev; + + struct iwl_trans *trans; + const struct iwl_fw *fw; + const struct iwl_cfg *cfg; + struct iwl_phy_db *phy_db; + struct ieee80211_hw *hw; + + /* for protecting access to iwl_mvm */ + struct mutex mutex; + struct list_head async_handlers_list; + spinlock_t async_handlers_lock; + struct work_struct async_handlers_wk; + + struct work_struct roc_done_wk; + + unsigned long init_status; + + unsigned long status; + + u32 queue_sync_cookie; + unsigned long queue_sync_state; + /* + * for beacon filtering - + * currently only one interface can be supported + */ + struct iwl_mvm_vif *bf_allowed_vif; + + bool hw_registered; + bool rfkill_safe_init_done; + + u8 cca_40mhz_workaround; + + u32 ampdu_ref; + bool ampdu_toggle; + + struct iwl_notif_wait_data notif_wait; + + union { + struct mvm_statistics_rx_v3 rx_stats_v3; + struct mvm_statistics_rx rx_stats; + }; + + struct { + u64 rx_time; + u64 tx_time; + u64 on_time_rf; + u64 on_time_scan; + } radio_stats, accu_radio_stats; + + struct list_head add_stream_txqs; + union { + struct iwl_mvm_dqa_txq_info queue_info[IWL_MAX_HW_QUEUES]; + struct iwl_mvm_tvqm_txq_info tvqm_info[IWL_MAX_TVQM_QUEUES]; + }; + struct work_struct add_stream_wk; /* To add streams to queues */ + spinlock_t add_stream_lock; + + const char *nvm_file_name; + struct iwl_nvm_data *nvm_data; + struct iwl_mei_nvm *mei_nvm_data; + struct iwl_mvm_csme_conn_info __rcu *csme_conn_info; + bool mei_rfkill_blocked; + bool mei_registered; + struct work_struct sap_connected_wk; + + /* + * NVM built based on the SAP data but that we can't free even after + * we get ownership because it contains the cfg80211's channel. + */ + struct iwl_nvm_data *temp_nvm_data; + + /* NVM sections */ + struct iwl_nvm_section nvm_sections[NVM_MAX_NUM_SECTIONS]; + + struct iwl_fw_runtime fwrt; + + /* EEPROM MAC addresses */ + struct mac_address addresses[IWL_MVM_MAX_ADDRESSES]; + + /* data related to data path */ + struct iwl_rx_phy_info last_phy_info; + struct ieee80211_sta __rcu *fw_id_to_mac_id[IWL_MVM_STATION_COUNT_MAX]; + u8 rx_ba_sessions; + + /* configured by mac80211 */ + u32 rts_threshold; + + /* Scan status, cmd (pre-allocated) and auxiliary station */ + unsigned int scan_status; + size_t scan_cmd_size; + void *scan_cmd; + struct iwl_mcast_filter_cmd *mcast_filter_cmd; + /* For CDB this is low band scan type, for non-CDB - type. */ + enum iwl_mvm_scan_type scan_type; + enum iwl_mvm_scan_type hb_scan_type; + + enum iwl_mvm_sched_scan_pass_all_states sched_scan_pass_all; + struct delayed_work scan_timeout_dwork; + + /* max number of simultaneous scans the FW supports */ + unsigned int max_scans; + + /* UMAC scan tracking */ + u32 scan_uid_status[IWL_MVM_MAX_UMAC_SCANS]; + + /* start time of last scan in TSF of the mac that requested the scan */ + u64 scan_start; + + /* the vif that requested the current scan */ + struct iwl_mvm_vif *scan_vif; + + /* rx chain antennas set through debugfs for the scan command */ + u8 scan_rx_ant; + + /* Internal station */ + struct iwl_mvm_int_sta aux_sta; + struct iwl_mvm_int_sta snif_sta; + + bool last_ebs_successful; + + u8 scan_last_antenna_idx; /* to toggle TX between antennas */ + u8 mgmt_last_antenna_idx; + + /* last smart fifo state that was successfully sent to firmware */ + enum iwl_sf_state sf_state; + + /* + * Leave this pointer outside the ifdef below so that it can be + * assigned without ifdef in the source code. + */ + struct dentry *debugfs_dir; +#ifdef CONFIG_IWLWIFI_DEBUGFS + u32 dbgfs_sram_offset, dbgfs_sram_len; + u32 dbgfs_prph_reg_addr; + bool disable_power_off; + bool disable_power_off_d3; + bool beacon_inject_active; + + bool scan_iter_notif_enabled; + + struct debugfs_blob_wrapper nvm_hw_blob; + struct debugfs_blob_wrapper nvm_sw_blob; + struct debugfs_blob_wrapper nvm_calib_blob; + struct debugfs_blob_wrapper nvm_prod_blob; + struct debugfs_blob_wrapper nvm_phy_sku_blob; + struct debugfs_blob_wrapper nvm_reg_blob; + + struct iwl_mvm_frame_stats drv_rx_stats; + spinlock_t drv_stats_lock; + u16 dbgfs_rx_phyinfo; +#endif + + struct iwl_mvm_phy_ctxt phy_ctxts[NUM_PHY_CTX]; + + struct list_head time_event_list; + spinlock_t time_event_lock; + + /* + * A bitmap indicating the index of the key in use. The firmware + * can hold 16 keys at most. Reflect this fact. + */ + unsigned long fw_key_table[BITS_TO_LONGS(STA_KEY_MAX_NUM)]; + u8 fw_key_deleted[STA_KEY_MAX_NUM]; + + struct ieee80211_vif __rcu *vif_id_to_mac[NUM_MAC_INDEX_DRIVER]; + + /* -1 for always, 0 for never, >0 for that many times */ + s8 fw_restart; + u8 *error_recovery_buf; + +#ifdef CONFIG_IWLWIFI_LEDS + struct led_classdev led; +#endif + + struct ieee80211_vif *p2p_device_vif; + +#ifdef CONFIG_PM + struct wiphy_wowlan_support wowlan; + int gtk_ivlen, gtk_icvlen, ptk_ivlen, ptk_icvlen; + + /* sched scan settings for net detect */ + struct ieee80211_scan_ies nd_ies; + struct cfg80211_match_set *nd_match_sets; + int n_nd_match_sets; + struct ieee80211_channel **nd_channels; + int n_nd_channels; + bool net_detect; + u8 offload_tid; +#ifdef CONFIG_IWLWIFI_DEBUGFS + bool d3_wake_sysassert; + bool d3_test_active; + u32 d3_test_pme_ptr; + struct ieee80211_vif *keep_vif; + u32 last_netdetect_scans; /* no. of scans in the last net-detect wake */ +#endif +#endif + + wait_queue_head_t rx_sync_waitq; + + /* BT-Coex */ + struct iwl_bt_coex_profile_notif last_bt_notif; + struct iwl_bt_coex_ci_cmd last_bt_ci_cmd; + + u8 bt_tx_prio; + enum iwl_bt_force_ant_mode bt_force_ant_mode; + + /* Aux ROC */ + struct list_head aux_roc_te_list; + + /* Thermal Throttling and CTkill */ + struct iwl_mvm_tt_mgmt thermal_throttle; +#ifdef CONFIG_THERMAL + struct iwl_mvm_thermal_device tz_device; + struct iwl_mvm_cooling_device cooling_dev; +#endif + + s32 temperature; /* Celsius */ + /* + * Debug option to set the NIC temperature. This option makes the + * driver think this is the actual NIC temperature, and ignore the + * real temperature that is received from the fw + */ + bool temperature_test; /* Debug test temperature is enabled */ + + bool fw_static_smps_request; + + unsigned long bt_coex_last_tcm_ts; + struct iwl_mvm_tcm tcm; + + u8 uapsd_noagg_bssid_write_idx; + struct mac_address uapsd_noagg_bssids[IWL_MVM_UAPSD_NOAGG_BSSIDS_NUM] + __aligned(2); + + struct iwl_time_quota_cmd last_quota_cmd; + +#ifdef CONFIG_NL80211_TESTMODE + u32 noa_duration; + struct ieee80211_vif *noa_vif; +#endif + + /* Tx queues */ + u16 aux_queue; + u16 snif_queue; + u16 probe_queue; + u16 p2p_dev_queue; + + /* Indicate if device power save is allowed */ + u8 ps_disabled; /* u8 instead of bool to ease debugfs_create_* usage */ + /* Indicate if 32Khz external clock is valid */ + u32 ext_clock_valid; + + /* This vif used by CSME to send / receive traffic */ + struct ieee80211_vif *csme_vif; + struct ieee80211_vif __rcu *csa_vif; + struct ieee80211_vif __rcu *csa_tx_blocked_vif; + u8 csa_tx_block_bcn_timeout; + + /* system time of last beacon (for AP/GO interface) */ + u32 ap_last_beacon_gp2; + + /* indicates that we transmitted the last beacon */ + bool ibss_manager; + + bool lar_regdom_set; + enum iwl_mcc_source mcc_src; + + /* TDLS channel switch data */ + struct { + struct delayed_work dwork; + enum iwl_mvm_tdls_cs_state state; + + /* + * Current cs sta - might be different from periodic cs peer + * station. Value is meaningless when the cs-state is idle. + */ + u8 cur_sta_id; + + /* TDLS periodic channel-switch peer */ + struct { + u8 sta_id; + u8 op_class; + bool initiator; /* are we the link initiator */ + struct cfg80211_chan_def chandef; + struct sk_buff *skb; /* ch sw template */ + u32 ch_sw_tm_ie; + + /* timestamp of last ch-sw request sent (GP2 time) */ + u32 sent_timestamp; + } peer; + } tdls_cs; + + + u32 ciphers[IWL_MVM_NUM_CIPHERS]; + + struct cfg80211_ftm_responder_stats ftm_resp_stats; + struct { + struct cfg80211_pmsr_request *req; + struct wireless_dev *req_wdev; + struct list_head loc_list; + int responses[IWL_MVM_TOF_MAX_APS]; + struct { + struct list_head resp; + } smooth; + struct list_head pasn_list; + } ftm_initiator; + + struct list_head resp_pasn_list; + + struct { + u8 range_resp; + } cmd_ver; + + struct ieee80211_vif *nan_vif; + struct iwl_mvm_baid_data __rcu *baid_map[IWL_MAX_BAID]; + + /* + * Drop beacons from other APs in AP mode when there are no connected + * clients. + */ + bool drop_bcn_ap_mode; + + struct delayed_work cs_tx_unblock_dwork; + + /* does a monitor vif exist (only one can exist hence bool) */ + bool monitor_on; + + /* sniffer data to include in radiotap */ + __le16 cur_aid; + u8 cur_bssid[ETH_ALEN]; + + unsigned long last_6ghz_passive_scan_jiffies; + unsigned long last_reset_or_resume_time_jiffies; + + bool sta_remove_requires_queue_remove; +}; + +/* Extract MVM priv from op_mode and _hw */ +#define IWL_OP_MODE_GET_MVM(_iwl_op_mode) \ + ((struct iwl_mvm *)(_iwl_op_mode)->op_mode_specific) + +#define IWL_MAC80211_GET_MVM(_hw) \ + IWL_OP_MODE_GET_MVM((struct iwl_op_mode *)((_hw)->priv)) + +/** + * enum iwl_mvm_status - MVM status bits + * @IWL_MVM_STATUS_HW_RFKILL: HW RF-kill is asserted + * @IWL_MVM_STATUS_HW_CTKILL: CT-kill is active + * @IWL_MVM_STATUS_ROC_RUNNING: remain-on-channel is running + * @IWL_MVM_STATUS_HW_RESTART_REQUESTED: HW restart was requested + * @IWL_MVM_STATUS_IN_HW_RESTART: HW restart is active + * @IWL_MVM_STATUS_ROC_AUX_RUNNING: AUX remain-on-channel is running + * @IWL_MVM_STATUS_FIRMWARE_RUNNING: firmware is running + * @IWL_MVM_STATUS_NEED_FLUSH_P2P: need to flush P2P bcast STA + * @IWL_MVM_STATUS_IN_D3: in D3 (or at least about to go into it) + * @IWL_MVM_STATUS_SUPPRESS_ERROR_LOG_ONCE: suppress one error log + * if this is set, when intentionally triggered + * @IWL_MVM_STATUS_STARTING: starting mac, + * used to disable restart flow while in STARTING state + */ +enum iwl_mvm_status { + IWL_MVM_STATUS_HW_RFKILL, + IWL_MVM_STATUS_HW_CTKILL, + IWL_MVM_STATUS_ROC_RUNNING, + IWL_MVM_STATUS_HW_RESTART_REQUESTED, + IWL_MVM_STATUS_IN_HW_RESTART, + IWL_MVM_STATUS_ROC_AUX_RUNNING, + IWL_MVM_STATUS_FIRMWARE_RUNNING, + IWL_MVM_STATUS_NEED_FLUSH_P2P, + IWL_MVM_STATUS_IN_D3, + IWL_MVM_STATUS_SUPPRESS_ERROR_LOG_ONCE, + IWL_MVM_STATUS_STARTING, +}; + +struct iwl_mvm_csme_conn_info { + struct rcu_head rcu_head; + struct iwl_mei_conn_info conn_info; +}; + +/* Keep track of completed init configuration */ +enum iwl_mvm_init_status { + IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE = BIT(0), + IWL_MVM_INIT_STATUS_LEDS_INIT_COMPLETE = BIT(1), +}; + +static inline bool iwl_mvm_is_radio_killed(struct iwl_mvm *mvm) +{ + return test_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status) || + test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status); +} + +static inline bool iwl_mvm_is_radio_hw_killed(struct iwl_mvm *mvm) +{ + return test_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status); +} + +static inline bool iwl_mvm_firmware_running(struct iwl_mvm *mvm) +{ + return test_bit(IWL_MVM_STATUS_FIRMWARE_RUNNING, &mvm->status); +} + +/* Must be called with rcu_read_lock() held and it can only be + * released when mvmsta is not needed anymore. + */ +static inline struct iwl_mvm_sta * +iwl_mvm_sta_from_staid_rcu(struct iwl_mvm *mvm, u8 sta_id) +{ + struct ieee80211_sta *sta; + + if (sta_id >= mvm->fw->ucode_capa.num_stations) + return NULL; + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + + /* This can happen if the station has been removed right now */ + if (IS_ERR_OR_NULL(sta)) + return NULL; + + return iwl_mvm_sta_from_mac80211(sta); +} + +static inline struct iwl_mvm_sta * +iwl_mvm_sta_from_staid_protected(struct iwl_mvm *mvm, u8 sta_id) +{ + struct ieee80211_sta *sta; + + if (sta_id >= mvm->fw->ucode_capa.num_stations) + return NULL; + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + + /* This can happen if the station has been removed right now */ + if (IS_ERR_OR_NULL(sta)) + return NULL; + + return iwl_mvm_sta_from_mac80211(sta); +} + +static inline struct ieee80211_vif * +iwl_mvm_rcu_dereference_vif_id(struct iwl_mvm *mvm, u8 vif_id, bool rcu) +{ + if (WARN_ON(vif_id >= ARRAY_SIZE(mvm->vif_id_to_mac))) + return NULL; + + if (rcu) + return rcu_dereference(mvm->vif_id_to_mac[vif_id]); + + return rcu_dereference_protected(mvm->vif_id_to_mac[vif_id], + lockdep_is_held(&mvm->mutex)); +} + +static inline bool iwl_mvm_is_adaptive_dwell_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_ADAPTIVE_DWELL); +} + +static inline bool iwl_mvm_is_adaptive_dwell_v2_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_ADAPTIVE_DWELL_V2); +} + +static inline bool iwl_mvm_is_adwell_hb_ap_num_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_ADWELL_HB_DEF_N_AP); +} + +static inline bool iwl_mvm_is_oce_supported(struct iwl_mvm *mvm) +{ + /* OCE should never be enabled for LMAC scan FWs */ + return fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_OCE); +} + +static inline bool iwl_mvm_is_frag_ebs_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_FRAG_EBS); +} + +static inline bool iwl_mvm_is_short_beacon_notif_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_SHORT_BEACON_NOTIF); +} + +static inline bool iwl_mvm_is_dqa_data_queue(struct iwl_mvm *mvm, u8 queue) +{ + return (queue >= IWL_MVM_DQA_MIN_DATA_QUEUE) && + (queue <= IWL_MVM_DQA_MAX_DATA_QUEUE); +} + +static inline bool iwl_mvm_is_dqa_mgmt_queue(struct iwl_mvm *mvm, u8 queue) +{ + return (queue >= IWL_MVM_DQA_MIN_MGMT_QUEUE) && + (queue <= IWL_MVM_DQA_MAX_MGMT_QUEUE); +} + +static inline bool iwl_mvm_is_lar_supported(struct iwl_mvm *mvm) +{ + bool nvm_lar = mvm->nvm_data->lar_enabled; + bool tlv_lar = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_LAR_SUPPORT); + + /* + * Enable LAR only if it is supported by the FW (TLV) && + * enabled in the NVM + */ + if (mvm->cfg->nvm_type == IWL_NVM_EXT) + return nvm_lar && tlv_lar; + else + return tlv_lar; +} + +static inline bool iwl_mvm_is_wifi_mcc_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_WIFI_MCC_UPDATE) || + fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_LAR_MULTI_MCC); +} + +static inline bool iwl_mvm_bt_is_rrc_supported(struct iwl_mvm *mvm) +{ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_BT_COEX_RRC) && + IWL_MVM_BT_COEX_RRC; +} + +static inline bool iwl_mvm_is_csum_supported(struct iwl_mvm *mvm) +{ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CSUM_SUPPORT) && + !IWL_MVM_HW_CSUM_DISABLE; +} + +static inline bool iwl_mvm_is_mplut_supported(struct iwl_mvm *mvm) +{ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_BT_MPLUT_SUPPORT) && + IWL_MVM_BT_COEX_MPLUT; +} + +static inline +bool iwl_mvm_is_p2p_scm_uapsd_supported(struct iwl_mvm *mvm) +{ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_P2P_SCM_UAPSD) && + !(iwlwifi_mod_params.uapsd_disable & + IWL_DISABLE_UAPSD_P2P_CLIENT); +} + +static inline bool iwl_mvm_has_new_rx_api(struct iwl_mvm *mvm) +{ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_MULTI_QUEUE_RX_SUPPORT); +} + +static inline bool iwl_mvm_has_new_tx_api(struct iwl_mvm *mvm) +{ + /* TODO - replace with TLV once defined */ + return mvm->trans->trans_cfg->use_tfh; +} + +static inline bool iwl_mvm_has_unified_ucode(struct iwl_mvm *mvm) +{ + /* TODO - better define this */ + return mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_22000; +} + +static inline bool iwl_mvm_is_cdb_supported(struct iwl_mvm *mvm) +{ + /* + * TODO: + * The issue of how to determine CDB APIs and usage is still not fully + * defined. + * There is a compilation for CDB and non-CDB FW, but there may + * be also runtime check. + * For now there is a TLV for checking compilation mode, but a + * runtime check will also have to be here - once defined. + */ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CDB_SUPPORT); +} + +static inline bool iwl_mvm_cdb_scan_api(struct iwl_mvm *mvm) +{ + /* + * TODO: should this be the same as iwl_mvm_is_cdb_supported()? + * but then there's a little bit of code in scan that won't make + * any sense... + */ + return mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_22000; +} + +static inline bool iwl_mvm_is_scan_ext_chan_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_SCAN_EXT_CHAN_VER); +} + + +static inline bool iwl_mvm_is_reduced_config_scan_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_REDUCED_SCAN_CONFIG); +} + +static inline bool iwl_mvm_is_band_in_rx_supported(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_BAND_IN_RX_DATA); +} + +static inline bool iwl_mvm_has_new_rx_stats_api(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_NEW_RX_STATS); +} + +static inline bool iwl_mvm_has_quota_low_latency(struct iwl_mvm *mvm) +{ + return fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_QUOTA_LOW_LATENCY); +} + +static inline bool iwl_mvm_has_tlc_offload(const struct iwl_mvm *mvm) +{ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TLC_OFFLOAD); +} + +static inline struct agg_tx_status * +iwl_mvm_get_agg_status(struct iwl_mvm *mvm, void *tx_resp) +{ + if (iwl_mvm_has_new_tx_api(mvm)) + return &((struct iwl_mvm_tx_resp *)tx_resp)->status; + else + return ((struct iwl_mvm_tx_resp_v3 *)tx_resp)->status; +} + +static inline bool iwl_mvm_is_tt_in_fw(struct iwl_mvm *mvm) +{ + /* these two TLV are redundant since the responsibility to CT-kill by + * FW happens only after we send at least one command of + * temperature THs report. + */ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CT_KILL_BY_FW) && + fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TEMP_THS_REPORT_SUPPORT); +} + +static inline bool iwl_mvm_is_ctdp_supported(struct iwl_mvm *mvm) +{ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CTDP_SUPPORT); +} + +extern const u8 iwl_mvm_ac_to_tx_fifo[]; +extern const u8 iwl_mvm_ac_to_gen2_tx_fifo[]; + +static inline u8 iwl_mvm_mac_ac_to_tx_fifo(struct iwl_mvm *mvm, + enum ieee80211_ac_numbers ac) +{ + return iwl_mvm_has_new_tx_api(mvm) ? + iwl_mvm_ac_to_gen2_tx_fifo[ac] : iwl_mvm_ac_to_tx_fifo[ac]; +} + +struct iwl_rate_info { + u8 plcp; /* uCode API: IWL_RATE_6M_PLCP, etc. */ + u8 plcp_siso; /* uCode API: IWL_RATE_SISO_6M_PLCP, etc. */ + u8 plcp_mimo2; /* uCode API: IWL_RATE_MIMO2_6M_PLCP, etc. */ + u8 plcp_mimo3; /* uCode API: IWL_RATE_MIMO3_6M_PLCP, etc. */ + u8 ieee; /* MAC header: IWL_RATE_6M_IEEE, etc. */ +}; + +void __iwl_mvm_mac_stop(struct iwl_mvm *mvm); +int __iwl_mvm_mac_start(struct iwl_mvm *mvm); + +/****************** + * MVM Methods + ******************/ +/* uCode */ +int iwl_run_init_mvm_ucode(struct iwl_mvm *mvm); + +/* Utils */ +int iwl_mvm_legacy_hw_idx_to_mac80211_idx(u32 rate_n_flags, + enum nl80211_band band); +int iwl_mvm_legacy_rate_to_mac80211_idx(u32 rate_n_flags, + enum nl80211_band band); +void iwl_mvm_hwrate_to_tx_rate(u32 rate_n_flags, + enum nl80211_band band, + struct ieee80211_tx_rate *r); +void iwl_mvm_hwrate_to_tx_rate_v1(u32 rate_n_flags, + enum nl80211_band band, + struct ieee80211_tx_rate *r); +u8 iwl_mvm_mac80211_idx_to_hwrate(const struct iwl_fw *fw, int rate_idx); +u8 iwl_mvm_mac80211_ac_to_ucode_ac(enum ieee80211_ac_numbers ac); + +static inline void iwl_mvm_dump_nic_error_log(struct iwl_mvm *mvm) +{ + iwl_fwrt_dump_error_logs(&mvm->fwrt); +} + +u8 first_antenna(u8 mask); +u8 iwl_mvm_next_antenna(struct iwl_mvm *mvm, u8 valid, u8 last_idx); +void iwl_mvm_get_sync_time(struct iwl_mvm *mvm, int clock_type, u32 *gp2, + u64 *boottime, ktime_t *realtime); +u32 iwl_mvm_get_systime(struct iwl_mvm *mvm); + +/* Tx / Host Commands */ +int __must_check iwl_mvm_send_cmd(struct iwl_mvm *mvm, + struct iwl_host_cmd *cmd); +int __must_check iwl_mvm_send_cmd_pdu(struct iwl_mvm *mvm, u32 id, + u32 flags, u16 len, const void *data); +int __must_check iwl_mvm_send_cmd_status(struct iwl_mvm *mvm, + struct iwl_host_cmd *cmd, + u32 *status); +int __must_check iwl_mvm_send_cmd_pdu_status(struct iwl_mvm *mvm, u32 id, + u16 len, const void *data, + u32 *status); +int iwl_mvm_tx_skb_sta(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_sta *sta); +int iwl_mvm_tx_skb_non_sta(struct iwl_mvm *mvm, struct sk_buff *skb); +void iwl_mvm_set_tx_cmd(struct iwl_mvm *mvm, struct sk_buff *skb, + struct iwl_tx_cmd *tx_cmd, + struct ieee80211_tx_info *info, u8 sta_id); +void iwl_mvm_set_tx_cmd_rate(struct iwl_mvm *mvm, struct iwl_tx_cmd *tx_cmd, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, __le16 fc); +void iwl_mvm_mac_itxq_xmit(struct ieee80211_hw *hw, struct ieee80211_txq *txq); +unsigned int iwl_mvm_max_amsdu_size(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + unsigned int tid); +u32 iwl_mvm_tx_csum_bz(struct iwl_mvm *mvm, struct sk_buff *skb, bool amsdu); + +#ifdef CONFIG_IWLWIFI_DEBUG +const char *iwl_mvm_get_tx_fail_reason(u32 status); +#else +static inline const char *iwl_mvm_get_tx_fail_reason(u32 status) { return ""; } +#endif +int iwl_mvm_flush_tx_path(struct iwl_mvm *mvm, u32 tfd_msk); +int iwl_mvm_flush_sta(struct iwl_mvm *mvm, void *sta, bool internal); +int iwl_mvm_flush_sta_tids(struct iwl_mvm *mvm, u32 sta_id, u16 tids); + +void iwl_mvm_async_handlers_purge(struct iwl_mvm *mvm); + +static inline void iwl_mvm_set_tx_cmd_ccmp(struct ieee80211_tx_info *info, + struct iwl_tx_cmd *tx_cmd) +{ + struct ieee80211_key_conf *keyconf = info->control.hw_key; + + tx_cmd->sec_ctl = TX_CMD_SEC_CCM; + memcpy(tx_cmd->key, keyconf->key, keyconf->keylen); +} + +static inline void iwl_mvm_wait_for_async_handlers(struct iwl_mvm *mvm) +{ + flush_work(&mvm->async_handlers_wk); +} + +/* Statistics */ +void iwl_mvm_handle_rx_statistics(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt); +void iwl_mvm_rx_statistics(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +int iwl_mvm_request_statistics(struct iwl_mvm *mvm, bool clear); +void iwl_mvm_accu_radio_stats(struct iwl_mvm *mvm); + +/* NVM */ +int iwl_nvm_init(struct iwl_mvm *mvm); +int iwl_mvm_load_nvm_to_nic(struct iwl_mvm *mvm); + +static inline u8 iwl_mvm_get_valid_tx_ant(struct iwl_mvm *mvm) +{ + return mvm->nvm_data && mvm->nvm_data->valid_tx_ant ? + mvm->fw->valid_tx_ant & mvm->nvm_data->valid_tx_ant : + mvm->fw->valid_tx_ant; +} + +static inline u8 iwl_mvm_get_valid_rx_ant(struct iwl_mvm *mvm) +{ + return mvm->nvm_data && mvm->nvm_data->valid_rx_ant ? + mvm->fw->valid_rx_ant & mvm->nvm_data->valid_rx_ant : + mvm->fw->valid_rx_ant; +} + +static inline void iwl_mvm_toggle_tx_ant(struct iwl_mvm *mvm, u8 *ant) +{ + *ant = iwl_mvm_next_antenna(mvm, iwl_mvm_get_valid_tx_ant(mvm), *ant); +} + +static inline u32 iwl_mvm_get_phy_config(struct iwl_mvm *mvm) +{ + u32 phy_config = ~(FW_PHY_CFG_TX_CHAIN | + FW_PHY_CFG_RX_CHAIN); + u32 valid_rx_ant = iwl_mvm_get_valid_rx_ant(mvm); + u32 valid_tx_ant = iwl_mvm_get_valid_tx_ant(mvm); + + phy_config |= valid_tx_ant << FW_PHY_CFG_TX_CHAIN_POS | + valid_rx_ant << FW_PHY_CFG_RX_CHAIN_POS; + + return mvm->fw->phy_config & phy_config; +} + +int iwl_mvm_up(struct iwl_mvm *mvm); +int iwl_mvm_load_d3_fw(struct iwl_mvm *mvm); + +int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm); + +/* + * FW notifications / CMD responses handlers + * Convention: iwl_mvm_rx_<NAME OF THE CMD> + */ +void iwl_mvm_rx_mq(struct iwl_op_mode *op_mode, + struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_rx_phy_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_mpdu_mq(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue); +void iwl_mvm_rx_monitor_no_data(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue); +void iwl_mvm_rx_frame_release(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue); +void iwl_mvm_rx_bar_frame_release(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue); +void iwl_mvm_rx_queue_notif(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue); +void iwl_mvm_rx_tx_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_mfu_assert_dump_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_send_recovery_cmd(struct iwl_mvm *mvm, u32 flags); +void iwl_mvm_rx_ba_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_ant_coupling_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_fw_error(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_mfuart_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_shared_mem_cfg_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); + +/* MVM PHY */ +int iwl_mvm_phy_ctxt_add(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic); +int iwl_mvm_phy_ctxt_changed(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic); +void iwl_mvm_phy_ctxt_ref(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt); +void iwl_mvm_phy_ctxt_unref(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt); +int iwl_mvm_phy_ctx_count(struct iwl_mvm *mvm); +u8 iwl_mvm_get_channel_width(struct cfg80211_chan_def *chandef); +u8 iwl_mvm_get_ctrl_pos(struct cfg80211_chan_def *chandef); + +/* MAC (virtual interface) programming */ +int iwl_mvm_mac_ctxt_init(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_mac_ctxt_add(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_mac_ctxt_changed(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + bool force_assoc_off, const u8 *bssid_override); +int iwl_mvm_mac_ctxt_remove(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_mac_ctxt_beacon_changed(struct iwl_mvm *mvm, + struct ieee80211_vif *vif); +int iwl_mvm_mac_ctxt_send_beacon(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct sk_buff *beacon); +int iwl_mvm_mac_ctxt_send_beacon_cmd(struct iwl_mvm *mvm, + struct sk_buff *beacon, + void *data, int len); +u8 iwl_mvm_mac_ctxt_get_lowest_rate(struct ieee80211_tx_info *info, + struct ieee80211_vif *vif); +u16 iwl_mvm_mac_ctxt_get_beacon_flags(const struct iwl_fw *fw, + u8 rate_idx); +void iwl_mvm_mac_ctxt_set_tim(struct iwl_mvm *mvm, + __le32 *tim_index, __le32 *tim_size, + u8 *beacon, u32 frame_size); +void iwl_mvm_rx_beacon_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_missed_beacons_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_stored_beacon_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_mu_mimo_grp_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_sta_pm_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_window_status_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_mac_ctxt_recalc_tsf_id(struct iwl_mvm *mvm, + struct ieee80211_vif *vif); +void iwl_mvm_probe_resp_data_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_missed_vap_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_channel_switch_start_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_channel_switch_error_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +/* Bindings */ +int iwl_mvm_binding_add_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_binding_remove_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif); + +/* Quota management */ +static inline size_t iwl_mvm_quota_cmd_size(struct iwl_mvm *mvm) +{ + return iwl_mvm_has_quota_low_latency(mvm) ? + sizeof(struct iwl_time_quota_cmd) : + sizeof(struct iwl_time_quota_cmd_v1); +} + +static inline struct iwl_time_quota_data +*iwl_mvm_quota_cmd_get_quota(struct iwl_mvm *mvm, + struct iwl_time_quota_cmd *cmd, + int i) +{ + struct iwl_time_quota_data_v1 *quotas; + + if (iwl_mvm_has_quota_low_latency(mvm)) + return &cmd->quotas[i]; + + quotas = (struct iwl_time_quota_data_v1 *)cmd->quotas; + return (struct iwl_time_quota_data *)"as[i]; +} + +int iwl_mvm_update_quotas(struct iwl_mvm *mvm, bool force_upload, + struct ieee80211_vif *disabled_vif); + +/* Scanning */ +int iwl_mvm_reg_scan_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_scan_request *req, + struct ieee80211_scan_ies *ies); +size_t iwl_mvm_scan_size(struct iwl_mvm *mvm); +int iwl_mvm_scan_stop(struct iwl_mvm *mvm, int type, bool notify); +int iwl_mvm_max_scan_ie_len(struct iwl_mvm *mvm); +void iwl_mvm_report_scan_aborted(struct iwl_mvm *mvm); +void iwl_mvm_scan_timeout_wk(struct work_struct *work); + +/* Scheduled scan */ +void iwl_mvm_rx_lmac_scan_complete_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_lmac_scan_iter_complete_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +int iwl_mvm_sched_scan_start(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_scan_ies *ies, + int type); +void iwl_mvm_rx_scan_match_found(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); + +/* UMAC scan */ +int iwl_mvm_config_scan(struct iwl_mvm *mvm); +void iwl_mvm_rx_umac_scan_complete_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_rx_umac_scan_iter_complete_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); + +/* MVM debugfs */ +#ifdef CONFIG_IWLWIFI_DEBUGFS +void iwl_mvm_dbgfs_register(struct iwl_mvm *mvm); +void iwl_mvm_vif_dbgfs_register(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_vif_dbgfs_clean(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +#else +static inline void iwl_mvm_dbgfs_register(struct iwl_mvm *mvm) +{ +} +static inline void +iwl_mvm_vif_dbgfs_register(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +} +static inline void +iwl_mvm_vif_dbgfs_clean(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +} +#endif /* CONFIG_IWLWIFI_DEBUGFS */ + +/* rate scaling */ +int iwl_mvm_send_lq_cmd(struct iwl_mvm *mvm, struct iwl_lq_cmd *lq); +void iwl_mvm_update_frame_stats(struct iwl_mvm *mvm, u32 rate, bool agg); +int rs_pretty_print_rate_v1(char *buf, int bufsz, const u32 rate); +void rs_update_last_rssi(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvmsta, + struct ieee80211_rx_status *rx_status); + +/* power management */ +int iwl_mvm_power_update_device(struct iwl_mvm *mvm); +int iwl_mvm_power_update_mac(struct iwl_mvm *mvm); +int iwl_mvm_power_update_ps(struct iwl_mvm *mvm); +int iwl_mvm_power_mac_dbgfs_read(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + char *buf, int bufsz); + +void iwl_mvm_power_vif_assoc(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_power_uapsd_misbehaving_ap_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); + +#ifdef CONFIG_IWLWIFI_LEDS +int iwl_mvm_leds_init(struct iwl_mvm *mvm); +void iwl_mvm_leds_exit(struct iwl_mvm *mvm); +void iwl_mvm_leds_sync(struct iwl_mvm *mvm); +#else +static inline int iwl_mvm_leds_init(struct iwl_mvm *mvm) +{ + return 0; +} +static inline void iwl_mvm_leds_exit(struct iwl_mvm *mvm) +{ +} +static inline void iwl_mvm_leds_sync(struct iwl_mvm *mvm) +{ +} +#endif + +/* D3 (WoWLAN, NetDetect) */ +int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan); +int iwl_mvm_resume(struct ieee80211_hw *hw); +void iwl_mvm_set_wakeup(struct ieee80211_hw *hw, bool enabled); +void iwl_mvm_set_rekey_data(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_gtk_rekey_data *data); +void iwl_mvm_ipv6_addr_change(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct inet6_dev *idev); +void iwl_mvm_set_default_unicast_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, int idx); +extern const struct file_operations iwl_dbgfs_d3_test_ops; +#ifdef CONFIG_PM +void iwl_mvm_set_last_nonqos_seq(struct iwl_mvm *mvm, + struct ieee80211_vif *vif); +#else +static inline void +iwl_mvm_set_last_nonqos_seq(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +} +#endif +void iwl_mvm_set_wowlan_qos_seq(struct iwl_mvm_sta *mvm_ap_sta, + struct iwl_wowlan_config_cmd *cmd); +int iwl_mvm_send_proto_offload(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + bool disable_offloading, + bool offload_ns, + u32 cmd_flags); + +/* BT Coex */ +int iwl_mvm_send_bt_init_conf(struct iwl_mvm *mvm); +void iwl_mvm_rx_bt_coex_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_bt_rssi_event(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + enum ieee80211_rssi_event_data); +void iwl_mvm_bt_coex_vif_change(struct iwl_mvm *mvm); +u16 iwl_mvm_coex_agg_time_limit(struct iwl_mvm *mvm, + struct ieee80211_sta *sta); +bool iwl_mvm_bt_coex_is_mimo_allowed(struct iwl_mvm *mvm, + struct ieee80211_sta *sta); +bool iwl_mvm_bt_coex_is_ant_avail(struct iwl_mvm *mvm, u8 ant); +bool iwl_mvm_bt_coex_is_shared_ant_avail(struct iwl_mvm *mvm); +bool iwl_mvm_bt_coex_is_tpc_allowed(struct iwl_mvm *mvm, + enum nl80211_band band); +u8 iwl_mvm_bt_coex_get_single_ant_msk(struct iwl_mvm *mvm, u8 enabled_ants); +u8 iwl_mvm_bt_coex_tx_prio(struct iwl_mvm *mvm, struct ieee80211_hdr *hdr, + struct ieee80211_tx_info *info, u8 ac); + +/* beacon filtering */ +#ifdef CONFIG_IWLWIFI_DEBUGFS +void +iwl_mvm_beacon_filter_debugfs_parameters(struct ieee80211_vif *vif, + struct iwl_beacon_filter_cmd *cmd); +#else +static inline void +iwl_mvm_beacon_filter_debugfs_parameters(struct ieee80211_vif *vif, + struct iwl_beacon_filter_cmd *cmd) +{} +#endif +int iwl_mvm_enable_beacon_filter(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 flags); +int iwl_mvm_disable_beacon_filter(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 flags); +/* SMPS */ +void iwl_mvm_update_smps(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + enum iwl_mvm_smps_type_request req_type, + enum ieee80211_smps_mode smps_request); +bool iwl_mvm_rx_diversity_allowed(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt); +void iwl_mvm_apply_fw_smps_request(struct ieee80211_vif *vif); + +/* Low latency */ +int iwl_mvm_update_low_latency(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + bool low_latency, + enum iwl_mvm_low_latency_cause cause); +/* get SystemLowLatencyMode - only needed for beacon threshold? */ +bool iwl_mvm_low_latency(struct iwl_mvm *mvm); +bool iwl_mvm_low_latency_band(struct iwl_mvm *mvm, enum nl80211_band band); +void iwl_mvm_send_low_latency_cmd(struct iwl_mvm *mvm, bool low_latency, + u16 mac_id); + +/* get VMACLowLatencyMode */ +static inline bool iwl_mvm_vif_low_latency(struct iwl_mvm_vif *mvmvif) +{ + /* + * should this consider associated/active/... state? + * + * Normally low-latency should only be active on interfaces + * that are active, but at least with debugfs it can also be + * enabled on interfaces that aren't active. However, when + * interface aren't active then they aren't added into the + * binding, so this has no real impact. For now, just return + * the current desired low-latency state. + */ + return mvmvif->low_latency_actual; +} + +static inline +void iwl_mvm_vif_set_low_latency(struct iwl_mvm_vif *mvmvif, bool set, + enum iwl_mvm_low_latency_cause cause) +{ + u8 new_state; + + if (set) + mvmvif->low_latency |= cause; + else + mvmvif->low_latency &= ~cause; + + /* + * if LOW_LATENCY_DEBUGFS_FORCE_ENABLE is enabled no changes are + * allowed to actual mode. + */ + if (mvmvif->low_latency & LOW_LATENCY_DEBUGFS_FORCE_ENABLE && + cause != LOW_LATENCY_DEBUGFS_FORCE_ENABLE) + return; + + if (cause == LOW_LATENCY_DEBUGFS_FORCE_ENABLE && set) + /* + * We enter force state + */ + new_state = !!(mvmvif->low_latency & + LOW_LATENCY_DEBUGFS_FORCE); + else + /* + * Check if any other one set low latency + */ + new_state = !!(mvmvif->low_latency & + ~(LOW_LATENCY_DEBUGFS_FORCE_ENABLE | + LOW_LATENCY_DEBUGFS_FORCE)); + + mvmvif->low_latency_actual = new_state; +} + +/* Return a bitmask with all the hw supported queues, except for the + * command queue, which can't be flushed. + */ +static inline u32 iwl_mvm_flushable_queues(struct iwl_mvm *mvm) +{ + return ((BIT(mvm->trans->trans_cfg->base_params->num_of_queues) - 1) & + ~BIT(IWL_MVM_DQA_CMD_QUEUE)); +} + +void iwl_mvm_stop_device(struct iwl_mvm *mvm); + +/* Thermal management and CT-kill */ +void iwl_mvm_tt_tx_backoff(struct iwl_mvm *mvm, u32 backoff); +void iwl_mvm_temp_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_tt_handler(struct iwl_mvm *mvm); +void iwl_mvm_thermal_initialize(struct iwl_mvm *mvm, u32 min_backoff); +void iwl_mvm_thermal_exit(struct iwl_mvm *mvm); +void iwl_mvm_set_hw_ctkill_state(struct iwl_mvm *mvm, bool state); +int iwl_mvm_get_temp(struct iwl_mvm *mvm, s32 *temp); +void iwl_mvm_ct_kill_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_enter_ctkill(struct iwl_mvm *mvm); +int iwl_mvm_send_temp_report_ths_cmd(struct iwl_mvm *mvm); +int iwl_mvm_ctdp_command(struct iwl_mvm *mvm, u32 op, u32 budget); + +#if IS_ENABLED(CONFIG_IWLMEI) + +/* vendor commands */ +void iwl_mvm_vendor_cmds_register(struct iwl_mvm *mvm); + +#else + +static inline void iwl_mvm_vendor_cmds_register(struct iwl_mvm *mvm) {} + +#endif + +/* Location Aware Regulatory */ +struct iwl_mcc_update_resp * +iwl_mvm_update_mcc(struct iwl_mvm *mvm, const char *alpha2, + enum iwl_mcc_source src_id); +int iwl_mvm_init_mcc(struct iwl_mvm *mvm); +void iwl_mvm_rx_chub_update_mcc(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +struct ieee80211_regdomain *iwl_mvm_get_regdomain(struct wiphy *wiphy, + const char *alpha2, + enum iwl_mcc_source src_id, + bool *changed); +struct ieee80211_regdomain *iwl_mvm_get_current_regdomain(struct iwl_mvm *mvm, + bool *changed); +int iwl_mvm_init_fw_regd(struct iwl_mvm *mvm); +void iwl_mvm_update_changed_regdom(struct iwl_mvm *mvm); + +/* smart fifo */ +int iwl_mvm_sf_update(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + bool added_vif); + +/* FTM responder */ +int iwl_mvm_ftm_start_responder(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_ftm_restart_responder(struct iwl_mvm *mvm, + struct ieee80211_vif *vif); +void iwl_mvm_ftm_responder_stats(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +int iwl_mvm_ftm_resp_remove_pasn_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, u8 *addr); +int iwl_mvm_ftm_respoder_add_pasn_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u8 *addr, u32 cipher, u8 *tk, u32 tk_len, + u8 *hltk, u32 hltk_len); +void iwl_mvm_ftm_responder_clear(struct iwl_mvm *mvm, + struct ieee80211_vif *vif); + +/* FTM initiator */ +void iwl_mvm_ftm_restart(struct iwl_mvm *mvm); +void iwl_mvm_ftm_range_resp(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_ftm_lc_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); +int iwl_mvm_ftm_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_pmsr_request *request); +void iwl_mvm_ftm_abort(struct iwl_mvm *mvm, struct cfg80211_pmsr_request *req); +void iwl_mvm_ftm_initiator_smooth_config(struct iwl_mvm *mvm); +void iwl_mvm_ftm_initiator_smooth_stop(struct iwl_mvm *mvm); +int iwl_mvm_ftm_add_pasn_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + u8 *addr, u32 cipher, u8 *tk, u32 tk_len, + u8 *hltk, u32 hltk_len); +void iwl_mvm_ftm_remove_pasn_sta(struct iwl_mvm *mvm, u8 *addr); + +/* TDLS */ + +/* + * We use TID 4 (VI) as a FW-used-only TID when TDLS connections are present. + * This TID is marked as used vs the AP and all connected TDLS peers. + */ +#define IWL_MVM_TDLS_FW_TID 4 + +int iwl_mvm_tdls_sta_count(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_teardown_tdls_peers(struct iwl_mvm *mvm); +void iwl_mvm_recalc_tdls_state(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + bool sta_added); +void iwl_mvm_mac_mgd_protect_tdls_discover(struct ieee80211_hw *hw, + struct ieee80211_vif *vif); +int iwl_mvm_tdls_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u8 oper_class, + struct cfg80211_chan_def *chandef, + struct sk_buff *tmpl_skb, u32 ch_sw_tm_ie); +void iwl_mvm_tdls_recv_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_tdls_ch_sw_params *params); +void iwl_mvm_tdls_cancel_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +void iwl_mvm_rx_tdls_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_tdls_ch_switch_work(struct work_struct *work); + +void iwl_mvm_sync_rx_queues_internal(struct iwl_mvm *mvm, + enum iwl_mvm_rxq_notif_type type, + bool sync, + const void *data, u32 size); +void iwl_mvm_reorder_timer_expired(struct timer_list *t); +struct ieee80211_vif *iwl_mvm_get_bss_vif(struct iwl_mvm *mvm); +struct ieee80211_vif *iwl_mvm_get_vif_by_macid(struct iwl_mvm *mvm, u32 macid); +bool iwl_mvm_is_vif_assoc(struct iwl_mvm *mvm); + +#define MVM_TCM_PERIOD_MSEC 500 +#define MVM_TCM_PERIOD (HZ * MVM_TCM_PERIOD_MSEC / 1000) +#define MVM_LL_PERIOD (10 * HZ) +void iwl_mvm_tcm_work(struct work_struct *work); +void iwl_mvm_recalc_tcm(struct iwl_mvm *mvm); +void iwl_mvm_pause_tcm(struct iwl_mvm *mvm, bool with_cancel); +void iwl_mvm_resume_tcm(struct iwl_mvm *mvm); +void iwl_mvm_tcm_add_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_tcm_rm_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +u8 iwl_mvm_tcm_load_percentage(u32 airtime, u32 elapsed); + +void iwl_mvm_nic_restart(struct iwl_mvm *mvm, bool fw_error); +unsigned int iwl_mvm_get_wd_timeout(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + bool tdls, bool cmd_q); +void iwl_mvm_connection_loss(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + const char *errmsg); +void iwl_mvm_event_frame_timeout_callback(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + const struct ieee80211_sta *sta, + u16 tid); + +int iwl_mvm_sar_select_profile(struct iwl_mvm *mvm, int prof_a, int prof_b); +int iwl_mvm_get_sar_geo_profile(struct iwl_mvm *mvm); +int iwl_mvm_ppag_send_cmd(struct iwl_mvm *mvm); +void iwl_mvm_get_acpi_tables(struct iwl_mvm *mvm); +#ifdef CONFIG_IWLWIFI_DEBUGFS +void iwl_mvm_sta_add_debugfs(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct dentry *dir); +#endif + +int iwl_rfi_send_config_cmd(struct iwl_mvm *mvm, + struct iwl_rfi_lut_entry *rfi_table); +struct iwl_rfi_freq_table_resp_cmd *iwl_rfi_get_freq_table(struct iwl_mvm *mvm); +void iwl_rfi_deactivate_notif_handler(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); + +static inline u8 iwl_mvm_phy_band_from_nl80211(enum nl80211_band band) +{ + switch (band) { + case NL80211_BAND_2GHZ: + return PHY_BAND_24; + case NL80211_BAND_5GHZ: + return PHY_BAND_5; + case NL80211_BAND_6GHZ: + return PHY_BAND_6; + default: + WARN_ONCE(1, "Unsupported band (%u)\n", band); + return PHY_BAND_5; + } +} + +/* Channel info utils */ +static inline bool iwl_mvm_has_ultra_hb_channel(struct iwl_mvm *mvm) +{ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_ULTRA_HB_CHANNELS); +} + +static inline void *iwl_mvm_chan_info_cmd_tail(struct iwl_mvm *mvm, + struct iwl_fw_channel_info *ci) +{ + return (u8 *)ci + (iwl_mvm_has_ultra_hb_channel(mvm) ? + sizeof(struct iwl_fw_channel_info) : + sizeof(struct iwl_fw_channel_info_v1)); +} + +static inline size_t iwl_mvm_chan_info_padding(struct iwl_mvm *mvm) +{ + return iwl_mvm_has_ultra_hb_channel(mvm) ? 0 : + sizeof(struct iwl_fw_channel_info) - + sizeof(struct iwl_fw_channel_info_v1); +} + +static inline void iwl_mvm_set_chan_info(struct iwl_mvm *mvm, + struct iwl_fw_channel_info *ci, + u32 chan, u8 band, u8 width, + u8 ctrl_pos) +{ + if (iwl_mvm_has_ultra_hb_channel(mvm)) { + ci->channel = cpu_to_le32(chan); + ci->band = band; + ci->width = width; + ci->ctrl_pos = ctrl_pos; + } else { + struct iwl_fw_channel_info_v1 *ci_v1 = + (struct iwl_fw_channel_info_v1 *)ci; + + ci_v1->channel = chan; + ci_v1->band = band; + ci_v1->width = width; + ci_v1->ctrl_pos = ctrl_pos; + } +} + +static inline void +iwl_mvm_set_chan_info_chandef(struct iwl_mvm *mvm, + struct iwl_fw_channel_info *ci, + struct cfg80211_chan_def *chandef) +{ + enum nl80211_band band = chandef->chan->band; + + iwl_mvm_set_chan_info(mvm, ci, chandef->chan->hw_value, + iwl_mvm_phy_band_from_nl80211(band), + iwl_mvm_get_channel_width(chandef), + iwl_mvm_get_ctrl_pos(chandef)); +} + +static inline int iwl_umac_scan_get_max_profiles(const struct iwl_fw *fw) +{ + u8 ver = iwl_fw_lookup_cmd_ver(fw, SCAN_OFFLOAD_UPDATE_PROFILES_CMD, + IWL_FW_CMD_VER_UNKNOWN); + return (ver == IWL_FW_CMD_VER_UNKNOWN || ver < 3) ? + IWL_SCAN_MAX_PROFILES : IWL_SCAN_MAX_PROFILES_V2; +} + +static inline +enum iwl_location_cipher iwl_mvm_cipher_to_location_cipher(u32 cipher) +{ + switch (cipher) { + case WLAN_CIPHER_SUITE_CCMP: + return IWL_LOCATION_CIPHER_CCMP_128; + case WLAN_CIPHER_SUITE_GCMP: + return IWL_LOCATION_CIPHER_GCMP_128; + case WLAN_CIPHER_SUITE_GCMP_256: + return IWL_LOCATION_CIPHER_GCMP_256; + default: + return IWL_LOCATION_CIPHER_INVALID; + } +} + +struct iwl_mvm_csme_conn_info *iwl_mvm_get_csme_conn_info(struct iwl_mvm *mvm); +static inline int iwl_mvm_mei_get_ownership(struct iwl_mvm *mvm) +{ + if (mvm->mei_registered) + return iwl_mei_get_ownership(); + return 0; +} + +static inline void iwl_mvm_mei_tx_copy_to_csme(struct iwl_mvm *mvm, + struct sk_buff *skb, + unsigned int ivlen) +{ + if (mvm->mei_registered) + iwl_mei_tx_copy_to_csme(skb, ivlen); +} + +static inline void iwl_mvm_mei_host_disassociated(struct iwl_mvm *mvm) +{ + if (mvm->mei_registered) + iwl_mei_host_disassociated(); +} + +static inline void iwl_mvm_mei_device_state(struct iwl_mvm *mvm, bool up) +{ + if (mvm->mei_registered) + iwl_mei_device_state(up); +} + +static inline void iwl_mvm_mei_set_sw_rfkill_state(struct iwl_mvm *mvm) +{ + bool sw_rfkill = + mvm->hw_registered ? rfkill_soft_blocked(mvm->hw->wiphy->rfkill) : false; + + if (mvm->mei_registered) + iwl_mei_set_rfkill_state(iwl_mvm_is_radio_killed(mvm), + sw_rfkill); +} + +void iwl_mvm_send_roaming_forbidden_event(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + bool forbidden); + +#endif /* __IWL_MVM_H__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/nvm.c b/drivers/net/wireless/intel/iwlwifi/mvm/nvm.c new file mode 100644 index 000000000..fdf60afb0 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/nvm.c @@ -0,0 +1,606 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2019, 2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include <linux/firmware.h> +#include <linux/rtnetlink.h> +#include "iwl-trans.h" +#include "iwl-csr.h" +#include "mvm.h" +#include "iwl-eeprom-parse.h" +#include "iwl-eeprom-read.h" +#include "iwl-nvm-parse.h" +#include "iwl-prph.h" +#include "fw/acpi.h" + +/* Default NVM size to read */ +#define IWL_NVM_DEFAULT_CHUNK_SIZE (2 * 1024) + +#define NVM_WRITE_OPCODE 1 +#define NVM_READ_OPCODE 0 + +/* load nvm chunk response */ +enum { + READ_NVM_CHUNK_SUCCEED = 0, + READ_NVM_CHUNK_NOT_VALID_ADDRESS = 1 +}; + +/* + * prepare the NVM host command w/ the pointers to the nvm buffer + * and send it to fw + */ +static int iwl_nvm_write_chunk(struct iwl_mvm *mvm, u16 section, + u16 offset, u16 length, const u8 *data) +{ + struct iwl_nvm_access_cmd nvm_access_cmd = { + .offset = cpu_to_le16(offset), + .length = cpu_to_le16(length), + .type = cpu_to_le16(section), + .op_code = NVM_WRITE_OPCODE, + }; + struct iwl_host_cmd cmd = { + .id = NVM_ACCESS_CMD, + .len = { sizeof(struct iwl_nvm_access_cmd), length }, + .flags = CMD_WANT_SKB | CMD_SEND_IN_RFKILL, + .data = { &nvm_access_cmd, data }, + /* data may come from vmalloc, so use _DUP */ + .dataflags = { 0, IWL_HCMD_DFL_DUP }, + }; + struct iwl_rx_packet *pkt; + struct iwl_nvm_access_resp *nvm_resp; + int ret; + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (ret) + return ret; + + pkt = cmd.resp_pkt; + /* Extract & check NVM write response */ + nvm_resp = (void *)pkt->data; + if (le16_to_cpu(nvm_resp->status) != READ_NVM_CHUNK_SUCCEED) { + IWL_ERR(mvm, + "NVM access write command failed for section %u (status = 0x%x)\n", + section, le16_to_cpu(nvm_resp->status)); + ret = -EIO; + } + + iwl_free_resp(&cmd); + return ret; +} + +static int iwl_nvm_read_chunk(struct iwl_mvm *mvm, u16 section, + u16 offset, u16 length, u8 *data) +{ + struct iwl_nvm_access_cmd nvm_access_cmd = { + .offset = cpu_to_le16(offset), + .length = cpu_to_le16(length), + .type = cpu_to_le16(section), + .op_code = NVM_READ_OPCODE, + }; + struct iwl_nvm_access_resp *nvm_resp; + struct iwl_rx_packet *pkt; + struct iwl_host_cmd cmd = { + .id = NVM_ACCESS_CMD, + .flags = CMD_WANT_SKB | CMD_SEND_IN_RFKILL, + .data = { &nvm_access_cmd, }, + }; + int ret, bytes_read, offset_read; + u8 *resp_data; + + cmd.len[0] = sizeof(struct iwl_nvm_access_cmd); + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (ret) + return ret; + + pkt = cmd.resp_pkt; + + /* Extract NVM response */ + nvm_resp = (void *)pkt->data; + ret = le16_to_cpu(nvm_resp->status); + bytes_read = le16_to_cpu(nvm_resp->length); + offset_read = le16_to_cpu(nvm_resp->offset); + resp_data = nvm_resp->data; + if (ret) { + if ((offset != 0) && + (ret == READ_NVM_CHUNK_NOT_VALID_ADDRESS)) { + /* + * meaning of NOT_VALID_ADDRESS: + * driver try to read chunk from address that is + * multiple of 2K and got an error since addr is empty. + * meaning of (offset != 0): driver already + * read valid data from another chunk so this case + * is not an error. + */ + IWL_DEBUG_EEPROM(mvm->trans->dev, + "NVM access command failed on offset 0x%x since that section size is multiple 2K\n", + offset); + ret = 0; + } else { + IWL_DEBUG_EEPROM(mvm->trans->dev, + "NVM access command failed with status %d (device: %s)\n", + ret, mvm->trans->name); + ret = -ENODATA; + } + goto exit; + } + + if (offset_read != offset) { + IWL_ERR(mvm, "NVM ACCESS response with invalid offset %d\n", + offset_read); + ret = -EINVAL; + goto exit; + } + + /* Write data to NVM */ + memcpy(data + offset, resp_data, bytes_read); + ret = bytes_read; + +exit: + iwl_free_resp(&cmd); + return ret; +} + +static int iwl_nvm_write_section(struct iwl_mvm *mvm, u16 section, + const u8 *data, u16 length) +{ + int offset = 0; + + /* copy data in chunks of 2k (and remainder if any) */ + + while (offset < length) { + int chunk_size, ret; + + chunk_size = min(IWL_NVM_DEFAULT_CHUNK_SIZE, + length - offset); + + ret = iwl_nvm_write_chunk(mvm, section, offset, + chunk_size, data + offset); + if (ret < 0) + return ret; + + offset += chunk_size; + } + + return 0; +} + +/* + * Reads an NVM section completely. + * NICs prior to 7000 family doesn't have a real NVM, but just read + * section 0 which is the EEPROM. Because the EEPROM reading is unlimited + * by uCode, we need to manually check in this case that we don't + * overflow and try to read more than the EEPROM size. + * For 7000 family NICs, we supply the maximal size we can read, and + * the uCode fills the response with as much data as we can, + * without overflowing, so no check is needed. + */ +static int iwl_nvm_read_section(struct iwl_mvm *mvm, u16 section, + u8 *data, u32 size_read) +{ + u16 length, offset = 0; + int ret; + + /* Set nvm section read length */ + length = IWL_NVM_DEFAULT_CHUNK_SIZE; + + ret = length; + + /* Read the NVM until exhausted (reading less than requested) */ + while (ret == length) { + /* Check no memory assumptions fail and cause an overflow */ + if ((size_read + offset + length) > + mvm->trans->trans_cfg->base_params->eeprom_size) { + IWL_ERR(mvm, "EEPROM size is too small for NVM\n"); + return -ENOBUFS; + } + + ret = iwl_nvm_read_chunk(mvm, section, offset, length, data); + if (ret < 0) { + IWL_DEBUG_EEPROM(mvm->trans->dev, + "Cannot read NVM from section %d offset %d, length %d\n", + section, offset, length); + return ret; + } + offset += ret; + } + + iwl_nvm_fixups(mvm->trans->hw_id, section, data, offset); + + IWL_DEBUG_EEPROM(mvm->trans->dev, + "NVM section %d read completed\n", section); + return offset; +} + +static struct iwl_nvm_data * +iwl_parse_nvm_sections(struct iwl_mvm *mvm) +{ + struct iwl_nvm_section *sections = mvm->nvm_sections; + const __be16 *hw; + const __le16 *sw, *calib, *regulatory, *mac_override, *phy_sku; + int regulatory_type; + + /* Checking for required sections */ + if (mvm->trans->cfg->nvm_type == IWL_NVM) { + if (!mvm->nvm_sections[NVM_SECTION_TYPE_SW].data || + !mvm->nvm_sections[mvm->cfg->nvm_hw_section_num].data) { + IWL_ERR(mvm, "Can't parse empty OTP/NVM sections\n"); + return NULL; + } + } else { + if (mvm->trans->cfg->nvm_type == IWL_NVM_SDP) + regulatory_type = NVM_SECTION_TYPE_REGULATORY_SDP; + else + regulatory_type = NVM_SECTION_TYPE_REGULATORY; + + /* SW and REGULATORY sections are mandatory */ + if (!mvm->nvm_sections[NVM_SECTION_TYPE_SW].data || + !mvm->nvm_sections[regulatory_type].data) { + IWL_ERR(mvm, + "Can't parse empty family 8000 OTP/NVM sections\n"); + return NULL; + } + /* MAC_OVERRIDE or at least HW section must exist */ + if (!mvm->nvm_sections[mvm->cfg->nvm_hw_section_num].data && + !mvm->nvm_sections[NVM_SECTION_TYPE_MAC_OVERRIDE].data) { + IWL_ERR(mvm, + "Can't parse mac_address, empty sections\n"); + return NULL; + } + + /* PHY_SKU section is mandatory in B0 */ + if (mvm->trans->cfg->nvm_type == IWL_NVM_EXT && + !mvm->nvm_sections[NVM_SECTION_TYPE_PHY_SKU].data) { + IWL_ERR(mvm, + "Can't parse phy_sku in B0, empty sections\n"); + return NULL; + } + } + + hw = (const __be16 *)sections[mvm->cfg->nvm_hw_section_num].data; + sw = (const __le16 *)sections[NVM_SECTION_TYPE_SW].data; + calib = (const __le16 *)sections[NVM_SECTION_TYPE_CALIBRATION].data; + mac_override = + (const __le16 *)sections[NVM_SECTION_TYPE_MAC_OVERRIDE].data; + phy_sku = (const __le16 *)sections[NVM_SECTION_TYPE_PHY_SKU].data; + + regulatory = mvm->trans->cfg->nvm_type == IWL_NVM_SDP ? + (const __le16 *)sections[NVM_SECTION_TYPE_REGULATORY_SDP].data : + (const __le16 *)sections[NVM_SECTION_TYPE_REGULATORY].data; + + return iwl_parse_nvm_data(mvm->trans, mvm->cfg, mvm->fw, hw, sw, calib, + regulatory, mac_override, phy_sku, + mvm->fw->valid_tx_ant, mvm->fw->valid_rx_ant); +} + +/* Loads the NVM data stored in mvm->nvm_sections into the NIC */ +int iwl_mvm_load_nvm_to_nic(struct iwl_mvm *mvm) +{ + int i, ret = 0; + struct iwl_nvm_section *sections = mvm->nvm_sections; + + IWL_DEBUG_EEPROM(mvm->trans->dev, "'Write to NVM\n"); + + for (i = 0; i < ARRAY_SIZE(mvm->nvm_sections); i++) { + if (!mvm->nvm_sections[i].data || !mvm->nvm_sections[i].length) + continue; + ret = iwl_nvm_write_section(mvm, i, sections[i].data, + sections[i].length); + if (ret < 0) { + IWL_ERR(mvm, "iwl_mvm_send_cmd failed: %d\n", ret); + break; + } + } + return ret; +} + +int iwl_nvm_init(struct iwl_mvm *mvm) +{ + int ret, section; + u32 size_read = 0; + u8 *nvm_buffer, *temp; + const char *nvm_file_C = mvm->cfg->default_nvm_file_C_step; + + if (WARN_ON_ONCE(mvm->cfg->nvm_hw_section_num >= NVM_MAX_NUM_SECTIONS)) + return -EINVAL; + + /* load NVM values from nic */ + /* Read From FW NVM */ + IWL_DEBUG_EEPROM(mvm->trans->dev, "Read from NVM\n"); + + nvm_buffer = kmalloc(mvm->trans->trans_cfg->base_params->eeprom_size, + GFP_KERNEL); + if (!nvm_buffer) + return -ENOMEM; + for (section = 0; section < NVM_MAX_NUM_SECTIONS; section++) { + /* we override the constness for initial read */ + ret = iwl_nvm_read_section(mvm, section, nvm_buffer, + size_read); + if (ret == -ENODATA) { + ret = 0; + continue; + } + if (ret < 0) + break; + size_read += ret; + temp = kmemdup(nvm_buffer, ret, GFP_KERNEL); + if (!temp) { + ret = -ENOMEM; + break; + } + + iwl_nvm_fixups(mvm->trans->hw_id, section, temp, ret); + + mvm->nvm_sections[section].data = temp; + mvm->nvm_sections[section].length = ret; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + switch (section) { + case NVM_SECTION_TYPE_SW: + mvm->nvm_sw_blob.data = temp; + mvm->nvm_sw_blob.size = ret; + break; + case NVM_SECTION_TYPE_CALIBRATION: + mvm->nvm_calib_blob.data = temp; + mvm->nvm_calib_blob.size = ret; + break; + case NVM_SECTION_TYPE_PRODUCTION: + mvm->nvm_prod_blob.data = temp; + mvm->nvm_prod_blob.size = ret; + break; + case NVM_SECTION_TYPE_PHY_SKU: + mvm->nvm_phy_sku_blob.data = temp; + mvm->nvm_phy_sku_blob.size = ret; + break; + case NVM_SECTION_TYPE_REGULATORY_SDP: + case NVM_SECTION_TYPE_REGULATORY: + mvm->nvm_reg_blob.data = temp; + mvm->nvm_reg_blob.size = ret; + break; + default: + if (section == mvm->cfg->nvm_hw_section_num) { + mvm->nvm_hw_blob.data = temp; + mvm->nvm_hw_blob.size = ret; + break; + } + } +#endif + } + if (!size_read) + IWL_ERR(mvm, "OTP is blank\n"); + kfree(nvm_buffer); + + /* Only if PNVM selected in the mod param - load external NVM */ + if (mvm->nvm_file_name) { + /* read External NVM file from the mod param */ + ret = iwl_read_external_nvm(mvm->trans, mvm->nvm_file_name, + mvm->nvm_sections); + if (ret) { + mvm->nvm_file_name = nvm_file_C; + + if ((ret == -EFAULT || ret == -ENOENT) && + mvm->nvm_file_name) { + /* in case nvm file was failed try again */ + ret = iwl_read_external_nvm(mvm->trans, + mvm->nvm_file_name, + mvm->nvm_sections); + if (ret) + return ret; + } else { + return ret; + } + } + } + + /* parse the relevant nvm sections */ + mvm->nvm_data = iwl_parse_nvm_sections(mvm); + if (!mvm->nvm_data) + return -ENODATA; + IWL_DEBUG_EEPROM(mvm->trans->dev, "nvm version = %x\n", + mvm->nvm_data->nvm_version); + + return ret < 0 ? ret : 0; +} + +struct iwl_mcc_update_resp * +iwl_mvm_update_mcc(struct iwl_mvm *mvm, const char *alpha2, + enum iwl_mcc_source src_id) +{ + struct iwl_mcc_update_cmd mcc_update_cmd = { + .mcc = cpu_to_le16(alpha2[0] << 8 | alpha2[1]), + .source_id = (u8)src_id, + }; + struct iwl_mcc_update_resp *resp_cp; + struct iwl_rx_packet *pkt; + struct iwl_host_cmd cmd = { + .id = MCC_UPDATE_CMD, + .flags = CMD_WANT_SKB | CMD_SEND_IN_RFKILL, + .data = { &mcc_update_cmd }, + }; + + int ret; + u32 status; + int resp_len, n_channels; + u16 mcc; + + if (WARN_ON_ONCE(!iwl_mvm_is_lar_supported(mvm))) + return ERR_PTR(-EOPNOTSUPP); + + cmd.len[0] = sizeof(struct iwl_mcc_update_cmd); + + IWL_DEBUG_LAR(mvm, "send MCC update to FW with '%c%c' src = %d\n", + alpha2[0], alpha2[1], src_id); + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (ret) + return ERR_PTR(ret); + + pkt = cmd.resp_pkt; + + /* Extract MCC response */ + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_MCC_UPDATE_11AX_SUPPORT)) { + struct iwl_mcc_update_resp *mcc_resp = (void *)pkt->data; + + n_channels = __le32_to_cpu(mcc_resp->n_channels); + if (iwl_rx_packet_payload_len(pkt) != + struct_size(mcc_resp, channels, n_channels)) { + resp_cp = ERR_PTR(-EINVAL); + goto exit; + } + resp_len = sizeof(struct iwl_mcc_update_resp) + + n_channels * sizeof(__le32); + resp_cp = kmemdup(mcc_resp, resp_len, GFP_KERNEL); + if (!resp_cp) { + resp_cp = ERR_PTR(-ENOMEM); + goto exit; + } + } else { + struct iwl_mcc_update_resp_v3 *mcc_resp_v3 = (void *)pkt->data; + + n_channels = __le32_to_cpu(mcc_resp_v3->n_channels); + if (iwl_rx_packet_payload_len(pkt) != + struct_size(mcc_resp_v3, channels, n_channels)) { + resp_cp = ERR_PTR(-EINVAL); + goto exit; + } + resp_len = sizeof(struct iwl_mcc_update_resp) + + n_channels * sizeof(__le32); + resp_cp = kzalloc(resp_len, GFP_KERNEL); + if (!resp_cp) { + resp_cp = ERR_PTR(-ENOMEM); + goto exit; + } + + resp_cp->status = mcc_resp_v3->status; + resp_cp->mcc = mcc_resp_v3->mcc; + resp_cp->cap = cpu_to_le16(mcc_resp_v3->cap); + resp_cp->source_id = mcc_resp_v3->source_id; + resp_cp->time = mcc_resp_v3->time; + resp_cp->geo_info = mcc_resp_v3->geo_info; + resp_cp->n_channels = mcc_resp_v3->n_channels; + memcpy(resp_cp->channels, mcc_resp_v3->channels, + n_channels * sizeof(__le32)); + } + + status = le32_to_cpu(resp_cp->status); + + mcc = le16_to_cpu(resp_cp->mcc); + + /* W/A for a FW/NVM issue - returns 0x00 for the world domain */ + if (mcc == 0) { + mcc = 0x3030; /* "00" - world */ + resp_cp->mcc = cpu_to_le16(mcc); + } + + IWL_DEBUG_LAR(mvm, + "MCC response status: 0x%x. new MCC: 0x%x ('%c%c') n_chans: %d\n", + status, mcc, mcc >> 8, mcc & 0xff, n_channels); + +exit: + iwl_free_resp(&cmd); + return resp_cp; +} + +int iwl_mvm_init_mcc(struct iwl_mvm *mvm) +{ + bool tlv_lar; + bool nvm_lar; + int retval; + struct ieee80211_regdomain *regd; + char mcc[3]; + + if (mvm->cfg->nvm_type == IWL_NVM_EXT) { + tlv_lar = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_LAR_SUPPORT); + nvm_lar = mvm->nvm_data->lar_enabled; + if (tlv_lar != nvm_lar) + IWL_INFO(mvm, + "Conflict between TLV & NVM regarding enabling LAR (TLV = %s NVM =%s)\n", + tlv_lar ? "enabled" : "disabled", + nvm_lar ? "enabled" : "disabled"); + } + + if (!iwl_mvm_is_lar_supported(mvm)) + return 0; + + /* + * try to replay the last set MCC to FW. If it doesn't exist, + * queue an update to cfg80211 to retrieve the default alpha2 from FW. + */ + retval = iwl_mvm_init_fw_regd(mvm); + if (retval != -ENOENT) + return retval; + + /* + * Driver regulatory hint for initial update, this also informs the + * firmware we support wifi location updates. + * Disallow scans that might crash the FW while the LAR regdomain + * is not set. + */ + mvm->lar_regdom_set = false; + + regd = iwl_mvm_get_current_regdomain(mvm, NULL); + if (IS_ERR_OR_NULL(regd)) + return -EIO; + + if (iwl_mvm_is_wifi_mcc_supported(mvm) && + !iwl_acpi_get_mcc(mvm->dev, mcc)) { + kfree(regd); + regd = iwl_mvm_get_regdomain(mvm->hw->wiphy, mcc, + MCC_SOURCE_BIOS, NULL); + if (IS_ERR_OR_NULL(regd)) + return -EIO; + } + + retval = regulatory_set_wiphy_regd_sync(mvm->hw->wiphy, regd); + kfree(regd); + return retval; +} + +void iwl_mvm_rx_chub_update_mcc(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mcc_chub_notif *notif = (void *)pkt->data; + enum iwl_mcc_source src; + char mcc[3]; + struct ieee80211_regdomain *regd; + int wgds_tbl_idx; + + lockdep_assert_held(&mvm->mutex); + + if (iwl_mvm_is_vif_assoc(mvm) && notif->source_id == MCC_SOURCE_WIFI) { + IWL_DEBUG_LAR(mvm, "Ignore mcc update while associated\n"); + return; + } + + if (WARN_ON_ONCE(!iwl_mvm_is_lar_supported(mvm))) + return; + + mcc[0] = le16_to_cpu(notif->mcc) >> 8; + mcc[1] = le16_to_cpu(notif->mcc) & 0xff; + mcc[2] = '\0'; + src = notif->source_id; + + IWL_DEBUG_LAR(mvm, + "RX: received chub update mcc cmd (mcc '%s' src %d)\n", + mcc, src); + regd = iwl_mvm_get_regdomain(mvm->hw->wiphy, mcc, src, NULL); + if (IS_ERR_OR_NULL(regd)) + return; + + wgds_tbl_idx = iwl_mvm_get_sar_geo_profile(mvm); + if (wgds_tbl_idx < 1) + IWL_DEBUG_INFO(mvm, + "SAR WGDS is disabled or error received (%d)\n", + wgds_tbl_idx); + else + IWL_DEBUG_INFO(mvm, "SAR WGDS: geo profile %d is configured\n", + wgds_tbl_idx); + + regulatory_set_wiphy_regd(mvm->hw->wiphy, regd); + kfree(regd); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/offloading.c b/drivers/net/wireless/intel/iwlwifi/mvm/offloading.c new file mode 100644 index 000000000..a8bd0f5f7 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/offloading.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2021-2022 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015 Intel Deutschland GmbH + */ +#include <net/ipv6.h> +#include <net/addrconf.h> +#include <linux/bitops.h> +#include "mvm.h" + +void iwl_mvm_set_wowlan_qos_seq(struct iwl_mvm_sta *mvm_ap_sta, + struct iwl_wowlan_config_cmd *cmd) +{ + int i; + + /* + * For QoS counters, we store the one to use next, so subtract 0x10 + * since the uCode will add 0x10 *before* using the value while we + * increment after using the value (i.e. store the next value to use). + */ + for (i = 0; i < IWL_MAX_TID_COUNT; i++) { + u16 seq = mvm_ap_sta->tid_data[i].seq_number; + seq -= 0x10; + cmd->qos_seq[i] = cpu_to_le16(seq); + } +} + +int iwl_mvm_send_proto_offload(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + bool disable_offloading, + bool offload_ns, + u32 cmd_flags) +{ + union { + struct iwl_proto_offload_cmd_v1 v1; + struct iwl_proto_offload_cmd_v2 v2; + struct iwl_proto_offload_cmd_v3_small v3s; + struct iwl_proto_offload_cmd_v4 v4; + } cmd = {}; + struct iwl_host_cmd hcmd = { + .id = PROT_OFFLOAD_CONFIG_CMD, + .flags = cmd_flags, + .data[0] = &cmd, + .dataflags[0] = IWL_HCMD_DFL_DUP, + }; + struct iwl_proto_offload_cmd_common *common; + u32 enabled = 0, size; + u32 capa_flags = mvm->fw->ucode_capa.flags; + int ver = iwl_fw_lookup_cmd_ver(mvm->fw, hcmd.id, 0); + +#if IS_ENABLED(CONFIG_IPV6) + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int i; + /* + * Skip tentative address when ns offload is enabled to avoid + * violating RFC4862. + * Keep tentative address when ns offload is disabled so the NS packets + * will not be filtered out and will wake up the host. + */ + bool skip_tentative = offload_ns; + + if (capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL || + capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_LARGE) { + struct iwl_ns_config *nsc; + struct iwl_targ_addr *addrs; + int n_nsc, n_addrs; + int c; + int num_skipped = 0; + + if (capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL) { + nsc = cmd.v3s.ns_config; + n_nsc = IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3S; + addrs = cmd.v3s.targ_addrs; + n_addrs = IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3S; + } else { + nsc = cmd.v4.ns_config; + n_nsc = IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3L; + addrs = cmd.v4.targ_addrs; + n_addrs = IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3L; + } + + /* + * For each address we have (and that will fit) fill a target + * address struct and combine for NS offload structs with the + * solicited node addresses. + */ + for (i = 0, c = 0; + i < mvmvif->num_target_ipv6_addrs && + i < n_addrs && c < n_nsc; i++) { + struct in6_addr solicited_addr; + int j; + + if (skip_tentative && + test_bit(i, mvmvif->tentative_addrs)) { + num_skipped++; + continue; + } + + addrconf_addr_solict_mult(&mvmvif->target_ipv6_addrs[i], + &solicited_addr); + for (j = 0; j < c; j++) + if (ipv6_addr_cmp(&nsc[j].dest_ipv6_addr, + &solicited_addr) == 0) + break; + if (j == c) + c++; + addrs[i].addr = mvmvif->target_ipv6_addrs[i]; + addrs[i].config_num = cpu_to_le32(j); + nsc[j].dest_ipv6_addr = solicited_addr; + memcpy(nsc[j].target_mac_addr, vif->addr, ETH_ALEN); + } + + if (mvmvif->num_target_ipv6_addrs - num_skipped) + enabled |= IWL_D3_PROTO_IPV6_VALID; + + if (capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL) + cmd.v3s.num_valid_ipv6_addrs = + cpu_to_le32(i - num_skipped); + else + cmd.v4.num_valid_ipv6_addrs = + cpu_to_le32(i - num_skipped); + } else if (capa_flags & IWL_UCODE_TLV_FLAGS_D3_6_IPV6_ADDRS) { + bool found = false; + + BUILD_BUG_ON(sizeof(cmd.v2.target_ipv6_addr[0]) != + sizeof(mvmvif->target_ipv6_addrs[0])); + + for (i = 0; i < min(mvmvif->num_target_ipv6_addrs, + IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V2); i++) { + if (skip_tentative && + test_bit(i, mvmvif->tentative_addrs)) + continue; + + memcpy(cmd.v2.target_ipv6_addr[i], + &mvmvif->target_ipv6_addrs[i], + sizeof(cmd.v2.target_ipv6_addr[i])); + + found = true; + } + if (found) { + enabled |= IWL_D3_PROTO_IPV6_VALID; + memcpy(cmd.v2.ndp_mac_addr, vif->addr, ETH_ALEN); + } + } else { + bool found = false; + BUILD_BUG_ON(sizeof(cmd.v1.target_ipv6_addr[0]) != + sizeof(mvmvif->target_ipv6_addrs[0])); + + for (i = 0; i < min(mvmvif->num_target_ipv6_addrs, + IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V1); i++) { + if (skip_tentative && + test_bit(i, mvmvif->tentative_addrs)) + continue; + + memcpy(cmd.v1.target_ipv6_addr[i], + &mvmvif->target_ipv6_addrs[i], + sizeof(cmd.v1.target_ipv6_addr[i])); + + found = true; + } + + if (found) { + enabled |= IWL_D3_PROTO_IPV6_VALID; + memcpy(cmd.v1.ndp_mac_addr, vif->addr, ETH_ALEN); + } + } + + if (offload_ns && (enabled & IWL_D3_PROTO_IPV6_VALID)) + enabled |= IWL_D3_PROTO_OFFLOAD_NS; +#endif + if (capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_SMALL) { + common = &cmd.v3s.common; + size = sizeof(cmd.v3s); + } else if (capa_flags & IWL_UCODE_TLV_FLAGS_NEW_NSOFFL_LARGE) { + common = &cmd.v4.common; + size = sizeof(cmd.v4); + if (ver < 4) { + /* + * This basically uses iwl_proto_offload_cmd_v3_large + * which doesn't have the sta_id parameter before the + * common part. + */ + size -= sizeof(cmd.v4.sta_id); + hcmd.data[0] = common; + } + } else if (capa_flags & IWL_UCODE_TLV_FLAGS_D3_6_IPV6_ADDRS) { + common = &cmd.v2.common; + size = sizeof(cmd.v2); + } else { + common = &cmd.v1.common; + size = sizeof(cmd.v1); + } + + if (vif->cfg.arp_addr_cnt) { + enabled |= IWL_D3_PROTO_OFFLOAD_ARP | IWL_D3_PROTO_IPV4_VALID; + common->host_ipv4_addr = vif->cfg.arp_addr_list[0]; + memcpy(common->arp_mac_addr, vif->addr, ETH_ALEN); + } + + if (!disable_offloading) + common->enabled = cpu_to_le32(enabled); + + hcmd.len[0] = size; + return iwl_mvm_send_cmd(mvm, &hcmd); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c new file mode 100644 index 000000000..864f5fb26 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c @@ -0,0 +1,1979 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2020 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include <linux/module.h> +#include <linux/rtnetlink.h> +#include <linux/vmalloc.h> +#include <net/mac80211.h> + +#include "fw/notif-wait.h" +#include "iwl-trans.h" +#include "iwl-op-mode.h" +#include "fw/img.h" +#include "iwl-debug.h" +#include "iwl-drv.h" +#include "iwl-modparams.h" +#include "mvm.h" +#include "iwl-phy-db.h" +#include "iwl-eeprom-parse.h" +#include "iwl-csr.h" +#include "iwl-io.h" +#include "iwl-prph.h" +#include "rs.h" +#include "fw/api/scan.h" +#include "fw/api/rfi.h" +#include "time-event.h" +#include "fw-api.h" +#include "fw/acpi.h" +#include "fw/uefi.h" + +#define DRV_DESCRIPTION "The new Intel(R) wireless AGN driver for Linux" +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(IWLWIFI); + +static const struct iwl_op_mode_ops iwl_mvm_ops; +static const struct iwl_op_mode_ops iwl_mvm_ops_mq; + +struct iwl_mvm_mod_params iwlmvm_mod_params = { + .power_scheme = IWL_POWER_SCHEME_BPS, + /* rest of fields are 0 by default */ +}; + +module_param_named(init_dbg, iwlmvm_mod_params.init_dbg, bool, 0444); +MODULE_PARM_DESC(init_dbg, + "set to true to debug an ASSERT in INIT fw (default: false"); +module_param_named(power_scheme, iwlmvm_mod_params.power_scheme, int, 0444); +MODULE_PARM_DESC(power_scheme, + "power management scheme: 1-active, 2-balanced, 3-low power, default: 2"); + +/* + * module init and exit functions + */ +static int __init iwl_mvm_init(void) +{ + int ret; + + ret = iwl_mvm_rate_control_register(); + if (ret) { + pr_err("Unable to register rate control algorithm: %d\n", ret); + return ret; + } + + ret = iwl_opmode_register("iwlmvm", &iwl_mvm_ops); + if (ret) + pr_err("Unable to register MVM op_mode: %d\n", ret); + + return ret; +} +module_init(iwl_mvm_init); + +static void __exit iwl_mvm_exit(void) +{ + iwl_opmode_deregister("iwlmvm"); + iwl_mvm_rate_control_unregister(); +} +module_exit(iwl_mvm_exit); + +static void iwl_mvm_nic_config(struct iwl_op_mode *op_mode) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + u8 radio_cfg_type, radio_cfg_step, radio_cfg_dash; + u32 reg_val; + u32 phy_config = iwl_mvm_get_phy_config(mvm); + + radio_cfg_type = (phy_config & FW_PHY_CFG_RADIO_TYPE) >> + FW_PHY_CFG_RADIO_TYPE_POS; + radio_cfg_step = (phy_config & FW_PHY_CFG_RADIO_STEP) >> + FW_PHY_CFG_RADIO_STEP_POS; + radio_cfg_dash = (phy_config & FW_PHY_CFG_RADIO_DASH) >> + FW_PHY_CFG_RADIO_DASH_POS; + + /* SKU control */ + reg_val = CSR_HW_REV_STEP_DASH(mvm->trans->hw_rev); + + /* radio configuration */ + reg_val |= radio_cfg_type << CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE; + reg_val |= radio_cfg_step << CSR_HW_IF_CONFIG_REG_POS_PHY_STEP; + reg_val |= radio_cfg_dash << CSR_HW_IF_CONFIG_REG_POS_PHY_DASH; + + WARN_ON((radio_cfg_type << CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE) & + ~CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE); + + /* + * TODO: Bits 7-8 of CSR in 8000 HW family and higher set the ADC + * sampling, and shouldn't be set to any non-zero value. + * The same is supposed to be true of the other HW, but unsetting + * them (such as the 7260) causes automatic tests to fail on seemingly + * unrelated errors. Need to further investigate this, but for now + * we'll separate cases. + */ + if (mvm->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_8000) + reg_val |= CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI; + + if (iwl_fw_dbg_is_d3_debug_enabled(&mvm->fwrt)) + reg_val |= CSR_HW_IF_CONFIG_REG_D3_DEBUG; + + iwl_trans_set_bits_mask(mvm->trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_MSK_MAC_STEP_DASH | + CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE | + CSR_HW_IF_CONFIG_REG_MSK_PHY_STEP | + CSR_HW_IF_CONFIG_REG_MSK_PHY_DASH | + CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI | + CSR_HW_IF_CONFIG_REG_BIT_MAC_SI | + CSR_HW_IF_CONFIG_REG_D3_DEBUG, + reg_val); + + IWL_DEBUG_INFO(mvm, "Radio type=0x%x-0x%x-0x%x\n", radio_cfg_type, + radio_cfg_step, radio_cfg_dash); + + /* + * W/A : NIC is stuck in a reset state after Early PCIe power off + * (PCIe power is lost before PERST# is asserted), causing ME FW + * to lose ownership and not being able to obtain it back. + */ + if (!mvm->trans->cfg->apmg_not_supported) + iwl_set_bits_mask_prph(mvm->trans, APMG_PS_CTRL_REG, + APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS, + ~APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS); +} + +static void iwl_mvm_rx_monitor_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_datapath_monitor_notif *notif = (void *)pkt->data; + struct ieee80211_supported_band *sband; + const struct ieee80211_sta_he_cap *he_cap; + struct ieee80211_vif *vif; + + if (notif->type != cpu_to_le32(IWL_DP_MON_NOTIF_TYPE_EXT_CCA)) + return; + + vif = iwl_mvm_get_vif_by_macid(mvm, notif->mac_id); + if (!vif || vif->type != NL80211_IFTYPE_STATION) + return; + + if (!vif->bss_conf.chandef.chan || + vif->bss_conf.chandef.chan->band != NL80211_BAND_2GHZ || + vif->bss_conf.chandef.width < NL80211_CHAN_WIDTH_40) + return; + + if (!vif->cfg.assoc) + return; + + /* this shouldn't happen *again*, ignore it */ + if (mvm->cca_40mhz_workaround) + return; + + /* + * We'll decrement this on disconnect - so set to 2 since we'll + * still have to disconnect from the current AP first. + */ + mvm->cca_40mhz_workaround = 2; + + /* + * This capability manipulation isn't really ideal, but it's the + * easiest choice - otherwise we'd have to do some major changes + * in mac80211 to support this, which isn't worth it. This does + * mean that userspace may have outdated information, but that's + * actually not an issue at all. + */ + sband = mvm->hw->wiphy->bands[NL80211_BAND_2GHZ]; + + WARN_ON(!sband->ht_cap.ht_supported); + WARN_ON(!(sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40)); + sband->ht_cap.cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40; + + he_cap = ieee80211_get_he_iftype_cap(sband, + ieee80211_vif_type_p2p(vif)); + + if (he_cap) { + /* we know that ours is writable */ + struct ieee80211_sta_he_cap *he = (void *)(uintptr_t)he_cap; + + WARN_ON(!he->has_he); + WARN_ON(!(he->he_cap_elem.phy_cap_info[0] & + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G)); + he->he_cap_elem.phy_cap_info[0] &= + ~IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G; + } + + ieee80211_disconnect(vif, true); +} + +void iwl_mvm_apply_fw_smps_request(struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = mvmvif->mvm; + enum ieee80211_smps_mode mode = IEEE80211_SMPS_AUTOMATIC; + + if (mvm->fw_static_smps_request && + vif->bss_conf.chandef.width == NL80211_CHAN_WIDTH_160 && + vif->bss_conf.he_support) + mode = IEEE80211_SMPS_STATIC; + + iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_FW, mode); +} + +static void iwl_mvm_intf_dual_chain_req(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + iwl_mvm_apply_fw_smps_request(vif); +} + +static void iwl_mvm_rx_thermal_dual_chain_req(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_thermal_dual_chain_request *req = (void *)pkt->data; + + /* + * We could pass it to the iterator data, but also need to remember + * it for new interfaces that are added while in this state. + */ + mvm->fw_static_smps_request = + req->event == cpu_to_le32(THERMAL_DUAL_CHAIN_REQ_DISABLE); + ieee80211_iterate_interfaces(mvm->hw, + IEEE80211_IFACE_SKIP_SDATA_NOT_IN_DRIVER, + iwl_mvm_intf_dual_chain_req, NULL); +} + +/** + * enum iwl_rx_handler_context context for Rx handler + * @RX_HANDLER_SYNC : this means that it will be called in the Rx path + * which can't acquire mvm->mutex. + * @RX_HANDLER_ASYNC_LOCKED : If the handler needs to hold mvm->mutex + * (and only in this case!), it should be set as ASYNC. In that case, + * it will be called from a worker with mvm->mutex held. + * @RX_HANDLER_ASYNC_UNLOCKED : in case the handler needs to lock the + * mutex itself, it will be called from a worker without mvm->mutex held. + */ +enum iwl_rx_handler_context { + RX_HANDLER_SYNC, + RX_HANDLER_ASYNC_LOCKED, + RX_HANDLER_ASYNC_UNLOCKED, +}; + +/** + * struct iwl_rx_handlers handler for FW notification + * @cmd_id: command id + * @min_size: minimum size to expect for the notification + * @context: see &iwl_rx_handler_context + * @fn: the function is called when notification is received + */ +struct iwl_rx_handlers { + u16 cmd_id, min_size; + enum iwl_rx_handler_context context; + void (*fn)(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +}; + +#define RX_HANDLER_NO_SIZE(_cmd_id, _fn, _context) \ + { .cmd_id = _cmd_id, .fn = _fn, .context = _context, } +#define RX_HANDLER_GRP_NO_SIZE(_grp, _cmd, _fn, _context) \ + { .cmd_id = WIDE_ID(_grp, _cmd), .fn = _fn, .context = _context, } +#define RX_HANDLER(_cmd_id, _fn, _context, _struct) \ + { .cmd_id = _cmd_id, .fn = _fn, \ + .context = _context, .min_size = sizeof(_struct), } +#define RX_HANDLER_GRP(_grp, _cmd, _fn, _context, _struct) \ + { .cmd_id = WIDE_ID(_grp, _cmd), .fn = _fn, \ + .context = _context, .min_size = sizeof(_struct), } + +/* + * Handlers for fw notifications + * Convention: RX_HANDLER(CMD_NAME, iwl_mvm_rx_CMD_NAME + * This list should be in order of frequency for performance purposes. + * + * The handler can be one from three contexts, see &iwl_rx_handler_context + */ +static const struct iwl_rx_handlers iwl_mvm_rx_handlers[] = { + RX_HANDLER(TX_CMD, iwl_mvm_rx_tx_cmd, RX_HANDLER_SYNC, + struct iwl_mvm_tx_resp), + RX_HANDLER(BA_NOTIF, iwl_mvm_rx_ba_notif, RX_HANDLER_SYNC, + struct iwl_mvm_ba_notif), + + RX_HANDLER_GRP(DATA_PATH_GROUP, TLC_MNG_UPDATE_NOTIF, + iwl_mvm_tlc_update_notif, RX_HANDLER_SYNC, + struct iwl_tlc_update_notif), + + RX_HANDLER(BT_PROFILE_NOTIFICATION, iwl_mvm_rx_bt_coex_notif, + RX_HANDLER_ASYNC_LOCKED, struct iwl_bt_coex_profile_notif), + RX_HANDLER_NO_SIZE(BEACON_NOTIFICATION, iwl_mvm_rx_beacon_notif, + RX_HANDLER_ASYNC_LOCKED), + RX_HANDLER_NO_SIZE(STATISTICS_NOTIFICATION, iwl_mvm_rx_statistics, + RX_HANDLER_ASYNC_LOCKED), + + RX_HANDLER(BA_WINDOW_STATUS_NOTIFICATION_ID, + iwl_mvm_window_status_notif, RX_HANDLER_SYNC, + struct iwl_ba_window_status_notif), + + RX_HANDLER(TIME_EVENT_NOTIFICATION, iwl_mvm_rx_time_event_notif, + RX_HANDLER_SYNC, struct iwl_time_event_notif), + RX_HANDLER_GRP(MAC_CONF_GROUP, SESSION_PROTECTION_NOTIF, + iwl_mvm_rx_session_protect_notif, RX_HANDLER_SYNC, + struct iwl_mvm_session_prot_notif), + RX_HANDLER(MCC_CHUB_UPDATE_CMD, iwl_mvm_rx_chub_update_mcc, + RX_HANDLER_ASYNC_LOCKED, struct iwl_mcc_chub_notif), + + RX_HANDLER(EOSP_NOTIFICATION, iwl_mvm_rx_eosp_notif, RX_HANDLER_SYNC, + struct iwl_mvm_eosp_notification), + + RX_HANDLER(SCAN_ITERATION_COMPLETE, + iwl_mvm_rx_lmac_scan_iter_complete_notif, RX_HANDLER_SYNC, + struct iwl_lmac_scan_complete_notif), + RX_HANDLER(SCAN_OFFLOAD_COMPLETE, + iwl_mvm_rx_lmac_scan_complete_notif, + RX_HANDLER_ASYNC_LOCKED, struct iwl_periodic_scan_complete), + RX_HANDLER_NO_SIZE(MATCH_FOUND_NOTIFICATION, + iwl_mvm_rx_scan_match_found, + RX_HANDLER_SYNC), + RX_HANDLER(SCAN_COMPLETE_UMAC, iwl_mvm_rx_umac_scan_complete_notif, + RX_HANDLER_ASYNC_LOCKED, struct iwl_umac_scan_complete), + RX_HANDLER(SCAN_ITERATION_COMPLETE_UMAC, + iwl_mvm_rx_umac_scan_iter_complete_notif, RX_HANDLER_SYNC, + struct iwl_umac_scan_iter_complete_notif), + + RX_HANDLER(MISSED_BEACONS_NOTIFICATION, iwl_mvm_rx_missed_beacons_notif, + RX_HANDLER_SYNC, struct iwl_missed_beacons_notif), + + RX_HANDLER(REPLY_ERROR, iwl_mvm_rx_fw_error, RX_HANDLER_SYNC, + struct iwl_error_resp), + RX_HANDLER(PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION, + iwl_mvm_power_uapsd_misbehaving_ap_notif, RX_HANDLER_SYNC, + struct iwl_uapsd_misbehaving_ap_notif), + RX_HANDLER_NO_SIZE(DTS_MEASUREMENT_NOTIFICATION, iwl_mvm_temp_notif, + RX_HANDLER_ASYNC_LOCKED), + RX_HANDLER_GRP_NO_SIZE(PHY_OPS_GROUP, DTS_MEASUREMENT_NOTIF_WIDE, + iwl_mvm_temp_notif, RX_HANDLER_ASYNC_UNLOCKED), + RX_HANDLER_GRP(PHY_OPS_GROUP, CT_KILL_NOTIFICATION, + iwl_mvm_ct_kill_notif, RX_HANDLER_SYNC, + struct ct_kill_notif), + + RX_HANDLER(TDLS_CHANNEL_SWITCH_NOTIFICATION, iwl_mvm_rx_tdls_notif, + RX_HANDLER_ASYNC_LOCKED, + struct iwl_tdls_channel_switch_notif), + RX_HANDLER(MFUART_LOAD_NOTIFICATION, iwl_mvm_rx_mfuart_notif, + RX_HANDLER_SYNC, struct iwl_mfuart_load_notif_v1), + RX_HANDLER_GRP(LOCATION_GROUP, TOF_RESPONDER_STATS, + iwl_mvm_ftm_responder_stats, RX_HANDLER_ASYNC_LOCKED, + struct iwl_ftm_responder_stats), + + RX_HANDLER_GRP_NO_SIZE(LOCATION_GROUP, TOF_RANGE_RESPONSE_NOTIF, + iwl_mvm_ftm_range_resp, RX_HANDLER_ASYNC_LOCKED), + RX_HANDLER_GRP_NO_SIZE(LOCATION_GROUP, TOF_LC_NOTIF, + iwl_mvm_ftm_lc_notif, RX_HANDLER_ASYNC_LOCKED), + + RX_HANDLER_GRP(DEBUG_GROUP, MFU_ASSERT_DUMP_NTF, + iwl_mvm_mfu_assert_dump_notif, RX_HANDLER_SYNC, + struct iwl_mfu_assert_dump_notif), + RX_HANDLER_GRP(PROT_OFFLOAD_GROUP, STORED_BEACON_NTF, + iwl_mvm_rx_stored_beacon_notif, RX_HANDLER_SYNC, + struct iwl_stored_beacon_notif_v2), + RX_HANDLER_GRP(DATA_PATH_GROUP, MU_GROUP_MGMT_NOTIF, + iwl_mvm_mu_mimo_grp_notif, RX_HANDLER_SYNC, + struct iwl_mu_group_mgmt_notif), + RX_HANDLER_GRP(DATA_PATH_GROUP, STA_PM_NOTIF, + iwl_mvm_sta_pm_notif, RX_HANDLER_SYNC, + struct iwl_mvm_pm_state_notification), + RX_HANDLER_GRP(MAC_CONF_GROUP, PROBE_RESPONSE_DATA_NOTIF, + iwl_mvm_probe_resp_data_notif, + RX_HANDLER_ASYNC_LOCKED, + struct iwl_probe_resp_data_notif), + RX_HANDLER_GRP(MAC_CONF_GROUP, CHANNEL_SWITCH_START_NOTIF, + iwl_mvm_channel_switch_start_notif, + RX_HANDLER_SYNC, struct iwl_channel_switch_start_notif), + RX_HANDLER_GRP(MAC_CONF_GROUP, CHANNEL_SWITCH_ERROR_NOTIF, + iwl_mvm_channel_switch_error_notif, + RX_HANDLER_ASYNC_UNLOCKED, + struct iwl_channel_switch_error_notif), + RX_HANDLER_GRP(DATA_PATH_GROUP, MONITOR_NOTIF, + iwl_mvm_rx_monitor_notif, RX_HANDLER_ASYNC_LOCKED, + struct iwl_datapath_monitor_notif), + + RX_HANDLER_GRP(DATA_PATH_GROUP, THERMAL_DUAL_CHAIN_REQUEST, + iwl_mvm_rx_thermal_dual_chain_req, + RX_HANDLER_ASYNC_LOCKED, + struct iwl_thermal_dual_chain_request), + + RX_HANDLER_GRP(SYSTEM_GROUP, RFI_DEACTIVATE_NOTIF, + iwl_rfi_deactivate_notif_handler, RX_HANDLER_ASYNC_UNLOCKED, + struct iwl_rfi_deactivate_notif), +}; +#undef RX_HANDLER +#undef RX_HANDLER_GRP + +/* Please keep this array *SORTED* by hex value. + * Access is done through binary search + */ +static const struct iwl_hcmd_names iwl_mvm_legacy_names[] = { + HCMD_NAME(UCODE_ALIVE_NTFY), + HCMD_NAME(REPLY_ERROR), + HCMD_NAME(ECHO_CMD), + HCMD_NAME(INIT_COMPLETE_NOTIF), + HCMD_NAME(PHY_CONTEXT_CMD), + HCMD_NAME(DBG_CFG), + HCMD_NAME(SCAN_CFG_CMD), + HCMD_NAME(SCAN_REQ_UMAC), + HCMD_NAME(SCAN_ABORT_UMAC), + HCMD_NAME(SCAN_COMPLETE_UMAC), + HCMD_NAME(BA_WINDOW_STATUS_NOTIFICATION_ID), + HCMD_NAME(ADD_STA_KEY), + HCMD_NAME(ADD_STA), + HCMD_NAME(REMOVE_STA), + HCMD_NAME(FW_GET_ITEM_CMD), + HCMD_NAME(TX_CMD), + HCMD_NAME(SCD_QUEUE_CFG), + HCMD_NAME(TXPATH_FLUSH), + HCMD_NAME(MGMT_MCAST_KEY), + HCMD_NAME(WEP_KEY), + HCMD_NAME(SHARED_MEM_CFG), + HCMD_NAME(TDLS_CHANNEL_SWITCH_CMD), + HCMD_NAME(MAC_CONTEXT_CMD), + HCMD_NAME(TIME_EVENT_CMD), + HCMD_NAME(TIME_EVENT_NOTIFICATION), + HCMD_NAME(BINDING_CONTEXT_CMD), + HCMD_NAME(TIME_QUOTA_CMD), + HCMD_NAME(NON_QOS_TX_COUNTER_CMD), + HCMD_NAME(LEDS_CMD), + HCMD_NAME(LQ_CMD), + HCMD_NAME(FW_PAGING_BLOCK_CMD), + HCMD_NAME(SCAN_OFFLOAD_REQUEST_CMD), + HCMD_NAME(SCAN_OFFLOAD_ABORT_CMD), + HCMD_NAME(HOT_SPOT_CMD), + HCMD_NAME(SCAN_OFFLOAD_PROFILES_QUERY_CMD), + HCMD_NAME(BT_COEX_UPDATE_REDUCED_TXP), + HCMD_NAME(BT_COEX_CI), + HCMD_NAME(PHY_CONFIGURATION_CMD), + HCMD_NAME(CALIB_RES_NOTIF_PHY_DB), + HCMD_NAME(PHY_DB_CMD), + HCMD_NAME(SCAN_OFFLOAD_COMPLETE), + HCMD_NAME(SCAN_OFFLOAD_UPDATE_PROFILES_CMD), + HCMD_NAME(POWER_TABLE_CMD), + HCMD_NAME(PSM_UAPSD_AP_MISBEHAVING_NOTIFICATION), + HCMD_NAME(REPLY_THERMAL_MNG_BACKOFF), + HCMD_NAME(NVM_ACCESS_CMD), + HCMD_NAME(BEACON_NOTIFICATION), + HCMD_NAME(BEACON_TEMPLATE_CMD), + HCMD_NAME(TX_ANT_CONFIGURATION_CMD), + HCMD_NAME(BT_CONFIG), + HCMD_NAME(STATISTICS_CMD), + HCMD_NAME(STATISTICS_NOTIFICATION), + HCMD_NAME(EOSP_NOTIFICATION), + HCMD_NAME(REDUCE_TX_POWER_CMD), + HCMD_NAME(MISSED_BEACONS_NOTIFICATION), + HCMD_NAME(TDLS_CONFIG_CMD), + HCMD_NAME(MAC_PM_POWER_TABLE), + HCMD_NAME(TDLS_CHANNEL_SWITCH_NOTIFICATION), + HCMD_NAME(MFUART_LOAD_NOTIFICATION), + HCMD_NAME(RSS_CONFIG_CMD), + HCMD_NAME(SCAN_ITERATION_COMPLETE_UMAC), + HCMD_NAME(REPLY_RX_PHY_CMD), + HCMD_NAME(REPLY_RX_MPDU_CMD), + HCMD_NAME(BAR_FRAME_RELEASE), + HCMD_NAME(FRAME_RELEASE), + HCMD_NAME(BA_NOTIF), + HCMD_NAME(MCC_UPDATE_CMD), + HCMD_NAME(MCC_CHUB_UPDATE_CMD), + HCMD_NAME(MARKER_CMD), + HCMD_NAME(BT_PROFILE_NOTIFICATION), + HCMD_NAME(MCAST_FILTER_CMD), + HCMD_NAME(REPLY_SF_CFG_CMD), + HCMD_NAME(REPLY_BEACON_FILTERING_CMD), + HCMD_NAME(D3_CONFIG_CMD), + HCMD_NAME(PROT_OFFLOAD_CONFIG_CMD), + HCMD_NAME(OFFLOADS_QUERY_CMD), + HCMD_NAME(MATCH_FOUND_NOTIFICATION), + HCMD_NAME(DTS_MEASUREMENT_NOTIFICATION), + HCMD_NAME(WOWLAN_PATTERNS), + HCMD_NAME(WOWLAN_CONFIGURATION), + HCMD_NAME(WOWLAN_TSC_RSC_PARAM), + HCMD_NAME(WOWLAN_TKIP_PARAM), + HCMD_NAME(WOWLAN_KEK_KCK_MATERIAL), + HCMD_NAME(WOWLAN_GET_STATUSES), + HCMD_NAME(SCAN_ITERATION_COMPLETE), + HCMD_NAME(D0I3_END_CMD), + HCMD_NAME(LTR_CONFIG), + 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_mvm_system_names[] = { + HCMD_NAME(SHARED_MEM_CFG_CMD), + HCMD_NAME(INIT_EXTENDED_CFG_CMD), + HCMD_NAME(FW_ERROR_RECOVERY_CMD), + HCMD_NAME(RFI_CONFIG_CMD), + HCMD_NAME(RFI_GET_FREQ_TABLE_CMD), + HCMD_NAME(SYSTEM_FEATURES_CONTROL_CMD), + HCMD_NAME(RFI_DEACTIVATE_NOTIF), +}; + +/* Please keep this array *SORTED* by hex value. + * Access is done through binary search + */ +static const struct iwl_hcmd_names iwl_mvm_mac_conf_names[] = { + HCMD_NAME(CHANNEL_SWITCH_TIME_EVENT_CMD), + HCMD_NAME(SESSION_PROTECTION_CMD), + HCMD_NAME(SESSION_PROTECTION_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_mvm_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_mvm_data_path_names[] = { + HCMD_NAME(DQA_ENABLE_CMD), + HCMD_NAME(UPDATE_MU_GROUPS_CMD), + HCMD_NAME(TRIGGER_RX_QUEUES_NOTIF_CMD), + HCMD_NAME(STA_HE_CTXT_CMD), + HCMD_NAME(RLC_CONFIG_CMD), + HCMD_NAME(RFH_QUEUE_CONFIG_CMD), + HCMD_NAME(TLC_MNG_CONFIG_CMD), + HCMD_NAME(CHEST_COLLECTOR_FILTER_CONFIG_CMD), + HCMD_NAME(SCD_QUEUE_CONFIG_CMD), + HCMD_NAME(MONITOR_NOTIF), + HCMD_NAME(THERMAL_DUAL_CHAIN_REQUEST), + HCMD_NAME(STA_PM_NOTIF), + HCMD_NAME(MU_GROUP_MGMT_NOTIF), + HCMD_NAME(RX_QUEUES_NOTIFICATION), +}; + +/* Please keep this array *SORTED* by hex value. + * Access is done through binary search + */ +static const struct iwl_hcmd_names iwl_mvm_scan_names[] = { + HCMD_NAME(OFFLOAD_MATCH_INFO_NOTIF), +}; + +/* Please keep this array *SORTED* by hex value. + * Access is done through binary search + */ +static const struct iwl_hcmd_names iwl_mvm_location_names[] = { + HCMD_NAME(TOF_RANGE_REQ_CMD), + HCMD_NAME(TOF_CONFIG_CMD), + HCMD_NAME(TOF_RANGE_ABORT_CMD), + HCMD_NAME(TOF_RANGE_REQ_EXT_CMD), + HCMD_NAME(TOF_RESPONDER_CONFIG_CMD), + HCMD_NAME(TOF_RESPONDER_DYN_CONFIG_CMD), + HCMD_NAME(TOF_LC_NOTIF), + HCMD_NAME(TOF_RESPONDER_STATS), + HCMD_NAME(TOF_MCSI_DEBUG_NOTIF), + 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_mvm_prot_offload_names[] = { + HCMD_NAME(WOWLAN_WAKE_PKT_NOTIFICATION), + HCMD_NAME(WOWLAN_INFO_NOTIFICATION), + HCMD_NAME(D3_END_NOTIFICATION), + 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_mvm_regulatory_and_nvm_names[] = { + HCMD_NAME(NVM_ACCESS_COMPLETE), + HCMD_NAME(NVM_GET_INFO), + HCMD_NAME(TAS_CONFIG), +}; + +static const struct iwl_hcmd_arr iwl_mvm_groups[] = { + [LEGACY_GROUP] = HCMD_ARR(iwl_mvm_legacy_names), + [LONG_GROUP] = HCMD_ARR(iwl_mvm_legacy_names), + [SYSTEM_GROUP] = HCMD_ARR(iwl_mvm_system_names), + [MAC_CONF_GROUP] = HCMD_ARR(iwl_mvm_mac_conf_names), + [PHY_OPS_GROUP] = HCMD_ARR(iwl_mvm_phy_names), + [DATA_PATH_GROUP] = HCMD_ARR(iwl_mvm_data_path_names), + [SCAN_GROUP] = HCMD_ARR(iwl_mvm_scan_names), + [LOCATION_GROUP] = HCMD_ARR(iwl_mvm_location_names), + [PROT_OFFLOAD_GROUP] = HCMD_ARR(iwl_mvm_prot_offload_names), + [REGULATORY_AND_NVM_GROUP] = + HCMD_ARR(iwl_mvm_regulatory_and_nvm_names), +}; + +/* this forward declaration can avoid to export the function */ +static void iwl_mvm_async_handlers_wk(struct work_struct *wk); + +static u32 iwl_mvm_min_backoff(struct iwl_mvm *mvm) +{ + const struct iwl_pwr_tx_backoff *backoff = mvm->cfg->pwr_tx_backoffs; + u64 dflt_pwr_limit; + + if (!backoff) + return 0; + + dflt_pwr_limit = iwl_acpi_get_pwr_limit(mvm->dev); + + while (backoff->pwr) { + if (dflt_pwr_limit >= backoff->pwr) + return backoff->backoff; + + backoff++; + } + + return 0; +} + +static void iwl_mvm_tx_unblock_dwork(struct work_struct *work) +{ + struct iwl_mvm *mvm = + container_of(work, struct iwl_mvm, cs_tx_unblock_dwork.work); + struct ieee80211_vif *tx_blocked_vif; + struct iwl_mvm_vif *mvmvif; + + mutex_lock(&mvm->mutex); + + tx_blocked_vif = + rcu_dereference_protected(mvm->csa_tx_blocked_vif, + lockdep_is_held(&mvm->mutex)); + + if (!tx_blocked_vif) + goto unlock; + + mvmvif = iwl_mvm_vif_from_mac80211(tx_blocked_vif); + iwl_mvm_modify_all_sta_disable_tx(mvm, mvmvif, false); + RCU_INIT_POINTER(mvm->csa_tx_blocked_vif, NULL); +unlock: + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_fwrt_dump_start(void *ctx) +{ + struct iwl_mvm *mvm = ctx; + + mutex_lock(&mvm->mutex); +} + +static void iwl_mvm_fwrt_dump_end(void *ctx) +{ + struct iwl_mvm *mvm = ctx; + + mutex_unlock(&mvm->mutex); +} + +static bool iwl_mvm_fwrt_fw_running(void *ctx) +{ + return iwl_mvm_firmware_running(ctx); +} + +static int iwl_mvm_fwrt_send_hcmd(void *ctx, struct iwl_host_cmd *host_cmd) +{ + struct iwl_mvm *mvm = (struct iwl_mvm *)ctx; + int ret; + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_send_cmd(mvm, host_cmd); + mutex_unlock(&mvm->mutex); + + return ret; +} + +static bool iwl_mvm_d3_debug_enable(void *ctx) +{ + return IWL_MVM_D3_DEBUG; +} + +static const struct iwl_fw_runtime_ops iwl_mvm_fwrt_ops = { + .dump_start = iwl_mvm_fwrt_dump_start, + .dump_end = iwl_mvm_fwrt_dump_end, + .fw_running = iwl_mvm_fwrt_fw_running, + .send_hcmd = iwl_mvm_fwrt_send_hcmd, + .d3_debug_enable = iwl_mvm_d3_debug_enable, +}; + +static int iwl_mvm_start_get_nvm(struct iwl_mvm *mvm) +{ + struct iwl_trans *trans = mvm->trans; + int ret; + + if (trans->csme_own) { + if (WARN(!mvm->mei_registered, + "csme is owner, but we aren't registered to iwlmei\n")) + goto get_nvm_from_fw; + + mvm->mei_nvm_data = iwl_mei_get_nvm(); + if (mvm->mei_nvm_data) { + /* + * mvm->mei_nvm_data is set and because of that, + * we'll load the NVM from the FW when we'll get + * ownership. + */ + mvm->nvm_data = + iwl_parse_mei_nvm_data(trans, trans->cfg, + mvm->mei_nvm_data, mvm->fw); + return 0; + } + + IWL_ERR(mvm, + "Got a NULL NVM from CSME, trying to get it from the device\n"); + } + +get_nvm_from_fw: + rtnl_lock(); + wiphy_lock(mvm->hw->wiphy); + mutex_lock(&mvm->mutex); + + ret = iwl_trans_start_hw(mvm->trans); + if (ret) { + mutex_unlock(&mvm->mutex); + wiphy_unlock(mvm->hw->wiphy); + rtnl_unlock(); + return ret; + } + + ret = iwl_run_init_mvm_ucode(mvm); + if (ret && ret != -ERFKILL) + iwl_fw_dbg_error_collect(&mvm->fwrt, FW_DBG_TRIGGER_DRIVER); + if (!ret && iwl_mvm_is_lar_supported(mvm)) { + mvm->hw->wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED; + ret = iwl_mvm_init_mcc(mvm); + } + + if (!iwlmvm_mod_params.init_dbg || !ret) + iwl_mvm_stop_device(mvm); + + mutex_unlock(&mvm->mutex); + wiphy_unlock(mvm->hw->wiphy); + rtnl_unlock(); + + if (ret) + IWL_ERR(mvm, "Failed to run INIT ucode: %d\n", ret); + + return ret; +} + +static int iwl_mvm_start_post_nvm(struct iwl_mvm *mvm) +{ + struct iwl_mvm_csme_conn_info *csme_conn_info __maybe_unused; + int ret; + + iwl_mvm_toggle_tx_ant(mvm, &mvm->mgmt_last_antenna_idx); + + ret = iwl_mvm_mac_setup_register(mvm); + if (ret) + return ret; + + mvm->hw_registered = true; + + iwl_mvm_dbgfs_register(mvm); + + wiphy_rfkill_set_hw_state_reason(mvm->hw->wiphy, + mvm->mei_rfkill_blocked, + RFKILL_HARD_BLOCK_NOT_OWNER); + + iwl_mvm_mei_set_sw_rfkill_state(mvm); + + return 0; +} + +struct iwl_mvm_frob_txf_data { + u8 *buf; + size_t buflen; +}; + +static void iwl_mvm_frob_txf_key_iter(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, + void *data) +{ + struct iwl_mvm_frob_txf_data *txf = data; + u8 keylen, match, matchend; + u8 *keydata; + size_t i; + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + keydata = key->key; + keylen = key->keylen; + break; + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + case WLAN_CIPHER_SUITE_TKIP: + /* + * WEP has short keys which might show up in the payload, + * and then you can deduce the key, so in this case just + * remove all FIFO data. + * For TKIP, we don't know the phase 2 keys here, so same. + */ + memset(txf->buf, 0xBB, txf->buflen); + return; + default: + return; + } + + /* scan for key material and clear it out */ + match = 0; + for (i = 0; i < txf->buflen; i++) { + if (txf->buf[i] != keydata[match]) { + match = 0; + continue; + } + match++; + if (match == keylen) { + memset(txf->buf + i - keylen, 0xAA, keylen); + match = 0; + } + } + + /* we're dealing with a FIFO, so check wrapped around data */ + matchend = match; + for (i = 0; match && i < keylen - match; i++) { + if (txf->buf[i] != keydata[match]) + break; + match++; + if (match == keylen) { + memset(txf->buf, 0xAA, i + 1); + memset(txf->buf + txf->buflen - matchend, 0xAA, + matchend); + break; + } + } +} + +static void iwl_mvm_frob_txf(void *ctx, void *buf, size_t buflen) +{ + struct iwl_mvm_frob_txf_data txf = { + .buf = buf, + .buflen = buflen, + }; + struct iwl_mvm *mvm = ctx; + + /* embedded key material exists only on old API */ + if (iwl_mvm_has_new_tx_api(mvm)) + return; + + rcu_read_lock(); + ieee80211_iter_keys_rcu(mvm->hw, NULL, iwl_mvm_frob_txf_key_iter, &txf); + rcu_read_unlock(); +} + +static void iwl_mvm_frob_hcmd(void *ctx, void *hcmd, size_t len) +{ + /* we only use wide headers for commands */ + struct iwl_cmd_header_wide *hdr = hcmd; + unsigned int frob_start = sizeof(*hdr), frob_end = 0; + + if (len < sizeof(hdr)) + return; + + /* all the commands we care about are in LONG_GROUP */ + if (hdr->group_id != LONG_GROUP) + return; + + switch (hdr->cmd) { + case WEP_KEY: + case WOWLAN_TKIP_PARAM: + case WOWLAN_KEK_KCK_MATERIAL: + case ADD_STA_KEY: + /* + * blank out everything here, easier than dealing + * with the various versions of the command + */ + frob_end = INT_MAX; + break; + case MGMT_MCAST_KEY: + frob_start = offsetof(struct iwl_mvm_mgmt_mcast_key_cmd, igtk); + BUILD_BUG_ON(offsetof(struct iwl_mvm_mgmt_mcast_key_cmd, igtk) != + offsetof(struct iwl_mvm_mgmt_mcast_key_cmd_v1, igtk)); + + frob_end = offsetofend(struct iwl_mvm_mgmt_mcast_key_cmd, igtk); + BUILD_BUG_ON(offsetof(struct iwl_mvm_mgmt_mcast_key_cmd, igtk) < + offsetof(struct iwl_mvm_mgmt_mcast_key_cmd_v1, igtk)); + break; + } + + if (frob_start >= frob_end) + return; + + if (frob_end > len) + frob_end = len; + + memset((u8 *)hcmd + frob_start, 0xAA, frob_end - frob_start); +} + +static void iwl_mvm_frob_mem(void *ctx, u32 mem_addr, void *mem, size_t buflen) +{ + const struct iwl_dump_exclude *excl; + struct iwl_mvm *mvm = ctx; + int i; + + switch (mvm->fwrt.cur_fw_img) { + case IWL_UCODE_INIT: + default: + /* not relevant */ + return; + case IWL_UCODE_REGULAR: + case IWL_UCODE_REGULAR_USNIFFER: + excl = mvm->fw->dump_excl; + break; + case IWL_UCODE_WOWLAN: + excl = mvm->fw->dump_excl_wowlan; + break; + } + + BUILD_BUG_ON(sizeof(mvm->fw->dump_excl) != + sizeof(mvm->fw->dump_excl_wowlan)); + + for (i = 0; i < ARRAY_SIZE(mvm->fw->dump_excl); i++) { + u32 start, end; + + if (!excl[i].addr || !excl[i].size) + continue; + + start = excl[i].addr; + end = start + excl[i].size; + + if (end <= mem_addr || start >= mem_addr + buflen) + continue; + + if (start < mem_addr) + start = mem_addr; + + if (end > mem_addr + buflen) + end = mem_addr + buflen; + + memset((u8 *)mem + start - mem_addr, 0xAA, end - start); + } +} + +static const struct iwl_dump_sanitize_ops iwl_mvm_sanitize_ops = { + .frob_txf = iwl_mvm_frob_txf, + .frob_hcmd = iwl_mvm_frob_hcmd, + .frob_mem = iwl_mvm_frob_mem, +}; + +static void iwl_mvm_me_conn_status(void *priv, const struct iwl_mei_conn_info *conn_info) +{ + struct iwl_mvm *mvm = priv; + struct iwl_mvm_csme_conn_info *prev_conn_info, *curr_conn_info; + + /* + * This is protected by the guarantee that this function will not be + * called twice on two different threads + */ + prev_conn_info = rcu_dereference_protected(mvm->csme_conn_info, true); + + curr_conn_info = kzalloc(sizeof(*curr_conn_info), GFP_KERNEL); + if (!curr_conn_info) + return; + + curr_conn_info->conn_info = *conn_info; + + rcu_assign_pointer(mvm->csme_conn_info, curr_conn_info); + + if (prev_conn_info) + kfree_rcu(prev_conn_info, rcu_head); +} + +static void iwl_mvm_mei_rfkill(void *priv, bool blocked) +{ + struct iwl_mvm *mvm = priv; + + mvm->mei_rfkill_blocked = blocked; + if (!mvm->hw_registered) + return; + + wiphy_rfkill_set_hw_state_reason(mvm->hw->wiphy, + mvm->mei_rfkill_blocked, + RFKILL_HARD_BLOCK_NOT_OWNER); +} + +static void iwl_mvm_mei_roaming_forbidden(void *priv, bool forbidden) +{ + struct iwl_mvm *mvm = priv; + + if (!mvm->hw_registered || !mvm->csme_vif) + return; + + iwl_mvm_send_roaming_forbidden_event(mvm, mvm->csme_vif, forbidden); +} + +static void iwl_mvm_sap_connected_wk(struct work_struct *wk) +{ + struct iwl_mvm *mvm = + container_of(wk, struct iwl_mvm, sap_connected_wk); + int ret; + + ret = iwl_mvm_start_get_nvm(mvm); + if (ret) + goto out_free; + + ret = iwl_mvm_start_post_nvm(mvm); + if (ret) + goto out_free; + + return; + +out_free: + IWL_ERR(mvm, "Couldn't get started...\n"); + iwl_mei_start_unregister(); + iwl_mei_unregister_complete(); + iwl_fw_flush_dumps(&mvm->fwrt); + iwl_mvm_thermal_exit(mvm); + iwl_fw_runtime_free(&mvm->fwrt); + iwl_phy_db_free(mvm->phy_db); + kfree(mvm->scan_cmd); + iwl_trans_op_mode_leave(mvm->trans); + kfree(mvm->nvm_data); + kfree(mvm->mei_nvm_data); + + ieee80211_free_hw(mvm->hw); +} + +static void iwl_mvm_mei_sap_connected(void *priv) +{ + struct iwl_mvm *mvm = priv; + + if (!mvm->hw_registered) + schedule_work(&mvm->sap_connected_wk); +} + +static void iwl_mvm_mei_nic_stolen(void *priv) +{ + struct iwl_mvm *mvm = priv; + + rtnl_lock(); + cfg80211_shutdown_all_interfaces(mvm->hw->wiphy); + rtnl_unlock(); +} + +static const struct iwl_mei_ops mei_ops = { + .me_conn_status = iwl_mvm_me_conn_status, + .rfkill = iwl_mvm_mei_rfkill, + .roaming_forbidden = iwl_mvm_mei_roaming_forbidden, + .sap_connected = iwl_mvm_mei_sap_connected, + .nic_stolen = iwl_mvm_mei_nic_stolen, +}; + +static struct iwl_op_mode * +iwl_op_mode_mvm_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_mvm *mvm; + struct iwl_trans_config trans_cfg = {}; + static const u8 no_reclaim_cmds[] = { + TX_CMD, + }; + size_t scan_size; + u32 min_backoff; + struct iwl_mvm_csme_conn_info *csme_conn_info __maybe_unused; + + /* + * We use IWL_MVM_STATION_COUNT_MAX to check the validity of the station + * index all over the driver - check that its value corresponds to the + * array size. + */ + BUILD_BUG_ON(ARRAY_SIZE(mvm->fw_id_to_mac_id) != + IWL_MVM_STATION_COUNT_MAX); + + /******************************** + * 1. Allocating and configuring HW data + ********************************/ + hw = ieee80211_alloc_hw(sizeof(struct iwl_op_mode) + + sizeof(struct iwl_mvm), + &iwl_mvm_hw_ops); + if (!hw) + return NULL; + + hw->max_rx_aggregation_subframes = IEEE80211_MAX_AMPDU_BUF_HE; + + if (cfg->max_tx_agg_size) + hw->max_tx_aggregation_subframes = cfg->max_tx_agg_size; + else + hw->max_tx_aggregation_subframes = IEEE80211_MAX_AMPDU_BUF_HE; + + op_mode = hw->priv; + + mvm = IWL_OP_MODE_GET_MVM(op_mode); + mvm->dev = trans->dev; + mvm->trans = trans; + mvm->cfg = cfg; + mvm->fw = fw; + mvm->hw = hw; + + iwl_fw_runtime_init(&mvm->fwrt, trans, fw, &iwl_mvm_fwrt_ops, mvm, + &iwl_mvm_sanitize_ops, mvm, dbgfs_dir); + + iwl_mvm_get_acpi_tables(mvm); + iwl_uefi_get_sgom_table(trans, &mvm->fwrt); + + mvm->init_status = 0; + + if (iwl_mvm_has_new_rx_api(mvm)) { + op_mode->ops = &iwl_mvm_ops_mq; + trans->rx_mpdu_cmd_hdr_size = + (trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_AX210) ? + sizeof(struct iwl_rx_mpdu_desc) : + IWL_RX_DESC_SIZE_V1; + } else { + op_mode->ops = &iwl_mvm_ops; + trans->rx_mpdu_cmd_hdr_size = + sizeof(struct iwl_rx_mpdu_res_start); + + if (WARN_ON(trans->num_rx_queues > 1)) + goto out_free; + } + + mvm->fw_restart = iwlwifi_mod_params.fw_restart ? -1 : 0; + + if (iwl_mvm_has_new_tx_api(mvm)) { + /* + * If we have the new TX/queue allocation API initialize them + * all to invalid numbers. We'll rewrite the ones that we need + * later, but that doesn't happen for all of them all of the + * time (e.g. P2P Device is optional), and if a dynamic queue + * ends up getting number 2 (IWL_MVM_DQA_P2P_DEVICE_QUEUE) then + * iwl_mvm_is_static_queue() erroneously returns true, and we + * might have things getting stuck. + */ + mvm->aux_queue = IWL_MVM_INVALID_QUEUE; + mvm->snif_queue = IWL_MVM_INVALID_QUEUE; + mvm->probe_queue = IWL_MVM_INVALID_QUEUE; + mvm->p2p_dev_queue = IWL_MVM_INVALID_QUEUE; + } else { + mvm->aux_queue = IWL_MVM_DQA_AUX_QUEUE; + mvm->snif_queue = IWL_MVM_DQA_INJECT_MONITOR_QUEUE; + mvm->probe_queue = IWL_MVM_DQA_AP_PROBE_RESP_QUEUE; + mvm->p2p_dev_queue = IWL_MVM_DQA_P2P_DEVICE_QUEUE; + } + + mvm->sf_state = SF_UNINIT; + if (iwl_mvm_has_unified_ucode(mvm)) + iwl_fw_set_current_image(&mvm->fwrt, IWL_UCODE_REGULAR); + else + iwl_fw_set_current_image(&mvm->fwrt, IWL_UCODE_INIT); + mvm->drop_bcn_ap_mode = true; + + mutex_init(&mvm->mutex); + spin_lock_init(&mvm->async_handlers_lock); + INIT_LIST_HEAD(&mvm->time_event_list); + INIT_LIST_HEAD(&mvm->aux_roc_te_list); + INIT_LIST_HEAD(&mvm->async_handlers_list); + spin_lock_init(&mvm->time_event_lock); + INIT_LIST_HEAD(&mvm->ftm_initiator.loc_list); + INIT_LIST_HEAD(&mvm->ftm_initiator.pasn_list); + INIT_LIST_HEAD(&mvm->resp_pasn_list); + + INIT_WORK(&mvm->async_handlers_wk, iwl_mvm_async_handlers_wk); + INIT_WORK(&mvm->roc_done_wk, iwl_mvm_roc_done_wk); + INIT_WORK(&mvm->sap_connected_wk, iwl_mvm_sap_connected_wk); + INIT_DELAYED_WORK(&mvm->tdls_cs.dwork, iwl_mvm_tdls_ch_switch_work); + INIT_DELAYED_WORK(&mvm->scan_timeout_dwork, iwl_mvm_scan_timeout_wk); + INIT_WORK(&mvm->add_stream_wk, iwl_mvm_add_new_dqa_stream_wk); + INIT_LIST_HEAD(&mvm->add_stream_txqs); + spin_lock_init(&mvm->add_stream_lock); + + init_waitqueue_head(&mvm->rx_sync_waitq); + + mvm->queue_sync_state = 0; + + SET_IEEE80211_DEV(mvm->hw, mvm->trans->dev); + + spin_lock_init(&mvm->tcm.lock); + INIT_DELAYED_WORK(&mvm->tcm.work, iwl_mvm_tcm_work); + mvm->tcm.ts = jiffies; + mvm->tcm.ll_ts = jiffies; + mvm->tcm.uapsd_nonagg_ts = jiffies; + + INIT_DELAYED_WORK(&mvm->cs_tx_unblock_dwork, iwl_mvm_tx_unblock_dwork); + + mvm->cmd_ver.range_resp = + iwl_fw_lookup_notif_ver(mvm->fw, LOCATION_GROUP, + TOF_RANGE_RESPONSE_NOTIF, 5); + /* we only support up to version 9 */ + if (WARN_ON_ONCE(mvm->cmd_ver.range_resp > 9)) + goto out_free; + + /* + * Populate the state variables that the transport layer needs + * to know about. + */ + trans_cfg.op_mode = op_mode; + trans_cfg.no_reclaim_cmds = no_reclaim_cmds; + trans_cfg.n_no_reclaim_cmds = ARRAY_SIZE(no_reclaim_cmds); + + switch (iwlwifi_mod_params.amsdu_size) { + case IWL_AMSDU_DEF: + trans_cfg.rx_buf_size = IWL_AMSDU_4K; + break; + case IWL_AMSDU_4K: + trans_cfg.rx_buf_size = IWL_AMSDU_4K; + break; + case IWL_AMSDU_8K: + trans_cfg.rx_buf_size = IWL_AMSDU_8K; + break; + case IWL_AMSDU_12K: + trans_cfg.rx_buf_size = IWL_AMSDU_12K; + break; + default: + pr_err("%s: Unsupported amsdu_size: %d\n", KBUILD_MODNAME, + iwlwifi_mod_params.amsdu_size); + trans_cfg.rx_buf_size = IWL_AMSDU_4K; + } + + trans->wide_cmd_header = true; + trans_cfg.bc_table_dword = + mvm->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210; + + trans_cfg.command_groups = iwl_mvm_groups; + trans_cfg.command_groups_size = ARRAY_SIZE(iwl_mvm_groups); + + trans_cfg.cmd_queue = IWL_MVM_DQA_CMD_QUEUE; + trans_cfg.cmd_fifo = IWL_MVM_TX_FIFO_CMD; + trans_cfg.scd_set_active = true; + + trans_cfg.cb_data_offs = offsetof(struct ieee80211_tx_info, + driver_data[2]); + + /* Set a short watchdog for the command queue */ + trans_cfg.cmd_q_wdg_timeout = + iwl_mvm_get_wd_timeout(mvm, NULL, false, true); + + snprintf(mvm->hw->wiphy->fw_version, + sizeof(mvm->hw->wiphy->fw_version), + "%s", fw->fw_version); + + trans_cfg.fw_reset_handshake = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_FW_RESET_HANDSHAKE); + + trans_cfg.queue_alloc_cmd_ver = + iwl_fw_lookup_cmd_ver(mvm->fw, + WIDE_ID(DATA_PATH_GROUP, + SCD_QUEUE_CONFIG_CMD), + 0); + mvm->sta_remove_requires_queue_remove = + trans_cfg.queue_alloc_cmd_ver > 0; + + /* Configure transport layer */ + iwl_trans_configure(mvm->trans, &trans_cfg); + + trans->rx_mpdu_cmd = REPLY_RX_MPDU_CMD; + trans->dbg.dest_tlv = mvm->fw->dbg.dest_tlv; + trans->dbg.n_dest_reg = mvm->fw->dbg.n_dest_reg; + memcpy(trans->dbg.conf_tlv, mvm->fw->dbg.conf_tlv, + sizeof(trans->dbg.conf_tlv)); + trans->dbg.trigger_tlv = mvm->fw->dbg.trigger_tlv; + + trans->iml = mvm->fw->iml; + trans->iml_len = mvm->fw->iml_len; + + /* set up notification wait support */ + iwl_notification_wait_init(&mvm->notif_wait); + + /* Init phy db */ + mvm->phy_db = iwl_phy_db_init(trans); + if (!mvm->phy_db) { + IWL_ERR(mvm, "Cannot init phy_db\n"); + goto out_free; + } + + IWL_INFO(mvm, "Detected %s, REV=0x%X\n", + mvm->trans->name, mvm->trans->hw_rev); + + if (iwlwifi_mod_params.nvm_file) + mvm->nvm_file_name = iwlwifi_mod_params.nvm_file; + else + IWL_DEBUG_EEPROM(mvm->trans->dev, + "working without external nvm file\n"); + + scan_size = iwl_mvm_scan_size(mvm); + + mvm->scan_cmd = kmalloc(scan_size, GFP_KERNEL); + if (!mvm->scan_cmd) + goto out_free; + mvm->scan_cmd_size = scan_size; + + /* invalidate ids to prevent accidental removal of sta_id 0 */ + mvm->aux_sta.sta_id = IWL_MVM_INVALID_STA; + mvm->snif_sta.sta_id = IWL_MVM_INVALID_STA; + + /* Set EBS as successful as long as not stated otherwise by the FW. */ + mvm->last_ebs_successful = true; + + min_backoff = iwl_mvm_min_backoff(mvm); + iwl_mvm_thermal_initialize(mvm, min_backoff); + + if (!iwl_mvm_has_new_rx_stats_api(mvm)) + memset(&mvm->rx_stats_v3, 0, + sizeof(struct mvm_statistics_rx_v3)); + else + memset(&mvm->rx_stats, 0, sizeof(struct mvm_statistics_rx)); + + mvm->debugfs_dir = dbgfs_dir; + + mvm->mei_registered = !iwl_mei_register(mvm, &mei_ops); + + if (iwl_mvm_start_get_nvm(mvm)) { + /* + * Getting NVM failed while CSME is the owner, but we are + * registered to MEI, we'll get the NVM later when it'll be + * possible to get it from CSME. + */ + if (trans->csme_own && mvm->mei_registered) + return op_mode; + + goto out_thermal_exit; + } + + + if (iwl_mvm_start_post_nvm(mvm)) + goto out_thermal_exit; + + return op_mode; + + out_thermal_exit: + iwl_mvm_thermal_exit(mvm); + if (mvm->mei_registered) { + iwl_mei_start_unregister(); + iwl_mei_unregister_complete(); + } + out_free: + iwl_fw_flush_dumps(&mvm->fwrt); + iwl_fw_runtime_free(&mvm->fwrt); + + if (iwlmvm_mod_params.init_dbg) + return op_mode; + iwl_phy_db_free(mvm->phy_db); + kfree(mvm->scan_cmd); + iwl_trans_op_mode_leave(trans); + + ieee80211_free_hw(mvm->hw); + return NULL; +} + +void iwl_mvm_stop_device(struct iwl_mvm *mvm) +{ + lockdep_assert_held(&mvm->mutex); + + iwl_fw_cancel_timestamp(&mvm->fwrt); + + clear_bit(IWL_MVM_STATUS_FIRMWARE_RUNNING, &mvm->status); + + iwl_fw_dbg_stop_sync(&mvm->fwrt); + iwl_trans_stop_device(mvm->trans); + iwl_free_fw_paging(&mvm->fwrt); + iwl_fw_dump_conf_clear(&mvm->fwrt); + iwl_mvm_mei_device_state(mvm, false); +} + +static void iwl_op_mode_mvm_stop(struct iwl_op_mode *op_mode) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + int i; + + if (mvm->mei_registered) { + rtnl_lock(); + iwl_mei_set_netdev(NULL); + rtnl_unlock(); + iwl_mei_start_unregister(); + } + + /* + * After we unregister from mei, the worker can't be scheduled + * anymore. + */ + cancel_work_sync(&mvm->sap_connected_wk); + + iwl_mvm_leds_exit(mvm); + + iwl_mvm_thermal_exit(mvm); + + /* + * If we couldn't get ownership on the device and we couldn't + * get the NVM from CSME, we haven't registered to mac80211. + * In that case, we didn't fail op_mode_start, because we are + * waiting for CSME to allow us to get the NVM to register to + * mac80211. If that didn't happen, we haven't registered to + * mac80211, hence the if below. + */ + if (mvm->hw_registered) + ieee80211_unregister_hw(mvm->hw); + + kfree(mvm->scan_cmd); + kfree(mvm->mcast_filter_cmd); + mvm->mcast_filter_cmd = NULL; + + kfree(mvm->error_recovery_buf); + mvm->error_recovery_buf = NULL; + + iwl_trans_op_mode_leave(mvm->trans); + + iwl_phy_db_free(mvm->phy_db); + mvm->phy_db = NULL; + + kfree(mvm->nvm_data); + kfree(mvm->mei_nvm_data); + kfree(rcu_access_pointer(mvm->csme_conn_info)); + kfree(mvm->temp_nvm_data); + for (i = 0; i < NVM_MAX_NUM_SECTIONS; i++) + kfree(mvm->nvm_sections[i].data); + + cancel_delayed_work_sync(&mvm->tcm.work); + + iwl_fw_runtime_free(&mvm->fwrt); + mutex_destroy(&mvm->mutex); + + if (mvm->mei_registered) + iwl_mei_unregister_complete(); + + ieee80211_free_hw(mvm->hw); +} + +struct iwl_async_handler_entry { + struct list_head list; + struct iwl_rx_cmd_buffer rxb; + enum iwl_rx_handler_context context; + void (*fn)(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb); +}; + +void iwl_mvm_async_handlers_purge(struct iwl_mvm *mvm) +{ + struct iwl_async_handler_entry *entry, *tmp; + + spin_lock_bh(&mvm->async_handlers_lock); + list_for_each_entry_safe(entry, tmp, &mvm->async_handlers_list, list) { + iwl_free_rxb(&entry->rxb); + list_del(&entry->list); + kfree(entry); + } + spin_unlock_bh(&mvm->async_handlers_lock); +} + +static void iwl_mvm_async_handlers_wk(struct work_struct *wk) +{ + struct iwl_mvm *mvm = + container_of(wk, struct iwl_mvm, async_handlers_wk); + struct iwl_async_handler_entry *entry, *tmp; + LIST_HEAD(local_list); + + /* Ensure that we are not in stop flow (check iwl_mvm_mac_stop) */ + + /* + * Sync with Rx path with a lock. Remove all the entries from this list, + * add them to a local one (lock free), and then handle them. + */ + spin_lock_bh(&mvm->async_handlers_lock); + list_splice_init(&mvm->async_handlers_list, &local_list); + spin_unlock_bh(&mvm->async_handlers_lock); + + list_for_each_entry_safe(entry, tmp, &local_list, list) { + if (entry->context == RX_HANDLER_ASYNC_LOCKED) + mutex_lock(&mvm->mutex); + entry->fn(mvm, &entry->rxb); + iwl_free_rxb(&entry->rxb); + list_del(&entry->list); + if (entry->context == RX_HANDLER_ASYNC_LOCKED) + mutex_unlock(&mvm->mutex); + kfree(entry); + } +} + +static inline void iwl_mvm_rx_check_trigger(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_cmd *cmds_trig; + int i; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, NULL, + FW_DBG_TRIGGER_FW_NOTIF); + if (!trig) + return; + + cmds_trig = (void *)trig->data; + + for (i = 0; i < ARRAY_SIZE(cmds_trig->cmds); i++) { + /* don't collect on CMD 0 */ + if (!cmds_trig->cmds[i].cmd_id) + break; + + if (cmds_trig->cmds[i].cmd_id != pkt->hdr.cmd || + cmds_trig->cmds[i].group_id != pkt->hdr.group_id) + continue; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + "CMD 0x%02x.%02x received", + pkt->hdr.group_id, pkt->hdr.cmd); + break; + } +} + +static void iwl_mvm_rx_common(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_rx_packet *pkt) +{ + unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); + int i; + union iwl_dbg_tlv_tp_data tp_data = { .fw_pkt = pkt }; + + iwl_dbg_tlv_time_point(&mvm->fwrt, + IWL_FW_INI_TIME_POINT_FW_RSP_OR_NOTIF, &tp_data); + iwl_mvm_rx_check_trigger(mvm, pkt); + + /* + * Do the notification wait before RX handlers so + * even if the RX handler consumes the RXB we have + * access to it in the notification wait entry. + */ + iwl_notification_wait_notify(&mvm->notif_wait, pkt); + + for (i = 0; i < ARRAY_SIZE(iwl_mvm_rx_handlers); i++) { + const struct iwl_rx_handlers *rx_h = &iwl_mvm_rx_handlers[i]; + struct iwl_async_handler_entry *entry; + + if (rx_h->cmd_id != WIDE_ID(pkt->hdr.group_id, pkt->hdr.cmd)) + continue; + + if (unlikely(pkt_len < rx_h->min_size)) + return; + + if (rx_h->context == RX_HANDLER_SYNC) { + rx_h->fn(mvm, rxb); + return; + } + + entry = kzalloc(sizeof(*entry), GFP_ATOMIC); + /* we can't do much... */ + if (!entry) + return; + + entry->rxb._page = rxb_steal_page(rxb); + entry->rxb._offset = rxb->_offset; + entry->rxb._rx_page_order = rxb->_rx_page_order; + entry->fn = rx_h->fn; + entry->context = rx_h->context; + spin_lock(&mvm->async_handlers_lock); + list_add_tail(&entry->list, &mvm->async_handlers_list); + spin_unlock(&mvm->async_handlers_lock); + schedule_work(&mvm->async_handlers_wk); + break; + } +} + +static void iwl_mvm_rx(struct iwl_op_mode *op_mode, + struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + u16 cmd = WIDE_ID(pkt->hdr.group_id, pkt->hdr.cmd); + + if (likely(cmd == WIDE_ID(LEGACY_GROUP, REPLY_RX_MPDU_CMD))) + iwl_mvm_rx_rx_mpdu(mvm, napi, rxb); + else if (cmd == WIDE_ID(LEGACY_GROUP, REPLY_RX_PHY_CMD)) + iwl_mvm_rx_rx_phy_cmd(mvm, rxb); + else + iwl_mvm_rx_common(mvm, rxb, pkt); +} + +void iwl_mvm_rx_mq(struct iwl_op_mode *op_mode, + struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + u16 cmd = WIDE_ID(pkt->hdr.group_id, pkt->hdr.cmd); + + if (likely(cmd == WIDE_ID(LEGACY_GROUP, REPLY_RX_MPDU_CMD))) + iwl_mvm_rx_mpdu_mq(mvm, napi, rxb, 0); + else if (unlikely(cmd == WIDE_ID(DATA_PATH_GROUP, + RX_QUEUES_NOTIFICATION))) + iwl_mvm_rx_queue_notif(mvm, napi, rxb, 0); + else if (cmd == WIDE_ID(LEGACY_GROUP, FRAME_RELEASE)) + iwl_mvm_rx_frame_release(mvm, napi, rxb, 0); + else if (cmd == WIDE_ID(LEGACY_GROUP, BAR_FRAME_RELEASE)) + iwl_mvm_rx_bar_frame_release(mvm, napi, rxb, 0); + else if (cmd == WIDE_ID(DATA_PATH_GROUP, RX_NO_DATA_NOTIF)) + iwl_mvm_rx_monitor_no_data(mvm, napi, rxb, 0); + else + iwl_mvm_rx_common(mvm, rxb, pkt); +} + +static void iwl_mvm_async_cb(struct iwl_op_mode *op_mode, + const struct iwl_device_cmd *cmd) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + + /* + * For now, we only set the CMD_WANT_ASYNC_CALLBACK for ADD_STA + * commands that need to block the Tx queues. + */ + iwl_trans_block_txq_ptrs(mvm->trans, false); +} + +static int iwl_mvm_is_static_queue(struct iwl_mvm *mvm, int queue) +{ + return queue == mvm->aux_queue || queue == mvm->probe_queue || + queue == mvm->p2p_dev_queue || queue == mvm->snif_queue; +} + +static void iwl_mvm_queue_state_change(struct iwl_op_mode *op_mode, + int hw_queue, bool start) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + struct ieee80211_sta *sta; + struct ieee80211_txq *txq; + struct iwl_mvm_txq *mvmtxq; + int i; + unsigned long tid_bitmap; + struct iwl_mvm_sta *mvmsta; + u8 sta_id; + + sta_id = iwl_mvm_has_new_tx_api(mvm) ? + mvm->tvqm_info[hw_queue].sta_id : + mvm->queue_info[hw_queue].ra_sta_id; + + if (WARN_ON_ONCE(sta_id >= mvm->fw->ucode_capa.num_stations)) + return; + + rcu_read_lock(); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + if (IS_ERR_OR_NULL(sta)) + goto out; + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + if (iwl_mvm_is_static_queue(mvm, hw_queue)) { + if (!start) + ieee80211_stop_queues(mvm->hw); + else if (mvmsta->sta_state != IEEE80211_STA_NOTEXIST) + ieee80211_wake_queues(mvm->hw); + + goto out; + } + + if (iwl_mvm_has_new_tx_api(mvm)) { + int tid = mvm->tvqm_info[hw_queue].txq_tid; + + tid_bitmap = BIT(tid); + } else { + tid_bitmap = mvm->queue_info[hw_queue].tid_bitmap; + } + + for_each_set_bit(i, &tid_bitmap, IWL_MAX_TID_COUNT + 1) { + int tid = i; + + if (tid == IWL_MAX_TID_COUNT) + tid = IEEE80211_NUM_TIDS; + + txq = sta->txq[tid]; + mvmtxq = iwl_mvm_txq_from_mac80211(txq); + if (start) + clear_bit(IWL_MVM_TXQ_STATE_STOP_FULL, &mvmtxq->state); + else + set_bit(IWL_MVM_TXQ_STATE_STOP_FULL, &mvmtxq->state); + + if (start && mvmsta->sta_state != IEEE80211_STA_NOTEXIST) { + local_bh_disable(); + iwl_mvm_mac_itxq_xmit(mvm->hw, txq); + local_bh_enable(); + } + } + +out: + rcu_read_unlock(); +} + +static void iwl_mvm_stop_sw_queue(struct iwl_op_mode *op_mode, int hw_queue) +{ + iwl_mvm_queue_state_change(op_mode, hw_queue, false); +} + +static void iwl_mvm_wake_sw_queue(struct iwl_op_mode *op_mode, int hw_queue) +{ + iwl_mvm_queue_state_change(op_mode, hw_queue, true); +} + +static void iwl_mvm_set_rfkill_state(struct iwl_mvm *mvm) +{ + bool state = iwl_mvm_is_radio_killed(mvm); + + if (state) + wake_up(&mvm->rx_sync_waitq); + + wiphy_rfkill_set_hw_state(mvm->hw->wiphy, state); +} + +void iwl_mvm_set_hw_ctkill_state(struct iwl_mvm *mvm, bool state) +{ + if (state) + set_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status); + else + clear_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status); + + iwl_mvm_set_rfkill_state(mvm); +} + +struct iwl_mvm_csme_conn_info *iwl_mvm_get_csme_conn_info(struct iwl_mvm *mvm) +{ + return rcu_dereference_protected(mvm->csme_conn_info, + lockdep_is_held(&mvm->mutex)); +} + +static bool iwl_mvm_set_hw_rfkill_state(struct iwl_op_mode *op_mode, bool state) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + bool rfkill_safe_init_done = READ_ONCE(mvm->rfkill_safe_init_done); + bool unified = iwl_mvm_has_unified_ucode(mvm); + + if (state) + set_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status); + else + clear_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status); + + iwl_mvm_set_rfkill_state(mvm); + + /* iwl_run_init_mvm_ucode is waiting for results, abort it. */ + if (rfkill_safe_init_done) + iwl_abort_notification_waits(&mvm->notif_wait); + + /* + * Don't ask the transport to stop the firmware. We'll do it + * after cfg80211 takes us down. + */ + if (unified) + return false; + + /* + * Stop the device if we run OPERATIONAL firmware or if we are in the + * middle of the calibrations. + */ + return state && rfkill_safe_init_done; +} + +static void iwl_mvm_free_skb(struct iwl_op_mode *op_mode, struct sk_buff *skb) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + struct ieee80211_tx_info *info; + + info = IEEE80211_SKB_CB(skb); + iwl_trans_free_tx_cmd(mvm->trans, info->driver_data[1]); + ieee80211_free_txskb(mvm->hw, skb); +} + +struct iwl_mvm_reprobe { + struct device *dev; + struct work_struct work; +}; + +static void iwl_mvm_reprobe_wk(struct work_struct *wk) +{ + struct iwl_mvm_reprobe *reprobe; + + reprobe = container_of(wk, struct iwl_mvm_reprobe, work); + if (device_reprobe(reprobe->dev)) + dev_err(reprobe->dev, "reprobe failed!\n"); + put_device(reprobe->dev); + kfree(reprobe); + module_put(THIS_MODULE); +} + +void iwl_mvm_nic_restart(struct iwl_mvm *mvm, bool fw_error) +{ + iwl_abort_notification_waits(&mvm->notif_wait); + iwl_dbg_tlv_del_timers(mvm->trans); + + /* + * This is a bit racy, but worst case we tell mac80211 about + * a stopped/aborted scan when that was already done which + * is not a problem. 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 start() call from mac80211. If restart isn't called + * (no fw restart) scan status will stay busy. + */ + iwl_mvm_report_scan_aborted(mvm); + + /* + * If we're restarting already, don't cycle restarts. + * If INIT fw asserted, it will likely fail again. + * If WoWLAN fw asserted, don't restart either, mac80211 + * can't recover this since we're already half suspended. + */ + if (!mvm->fw_restart && fw_error) { + iwl_fw_error_collect(&mvm->fwrt, false); + } else if (test_bit(IWL_MVM_STATUS_STARTING, + &mvm->status)) { + IWL_ERR(mvm, "Starting mac, retry will be triggered anyway\n"); + } else if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { + struct iwl_mvm_reprobe *reprobe; + + IWL_ERR(mvm, + "Firmware error during reconfiguration - reprobe!\n"); + + /* + * get a module reference to avoid doing this while unloading + * anyway and to avoid scheduling a work with code that's + * being removed. + */ + if (!try_module_get(THIS_MODULE)) { + IWL_ERR(mvm, "Module is being unloaded - abort\n"); + return; + } + + reprobe = kzalloc(sizeof(*reprobe), GFP_ATOMIC); + if (!reprobe) { + module_put(THIS_MODULE); + return; + } + reprobe->dev = get_device(mvm->trans->dev); + INIT_WORK(&reprobe->work, iwl_mvm_reprobe_wk); + schedule_work(&reprobe->work); + } else if (test_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, + &mvm->status)) { + IWL_ERR(mvm, "HW restart already requested, but not started\n"); + } else if (mvm->fwrt.cur_fw_img == IWL_UCODE_REGULAR && + mvm->hw_registered && + !test_bit(STATUS_TRANS_DEAD, &mvm->trans->status)) { + /* This should be first thing before trying to collect any + * data to avoid endless loops if any HW error happens while + * collecting debug data. + */ + set_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, &mvm->status); + + if (mvm->fw->ucode_capa.error_log_size) { + u32 src_size = mvm->fw->ucode_capa.error_log_size; + u32 src_addr = mvm->fw->ucode_capa.error_log_addr; + u8 *recover_buf = kzalloc(src_size, GFP_ATOMIC); + + if (recover_buf) { + mvm->error_recovery_buf = recover_buf; + iwl_trans_read_mem_bytes(mvm->trans, + src_addr, + recover_buf, + src_size); + } + } + + iwl_fw_error_collect(&mvm->fwrt, false); + + if (fw_error && mvm->fw_restart > 0) { + mvm->fw_restart--; + ieee80211_restart_hw(mvm->hw); + } else if (mvm->fwrt.trans->dbg.restart_required) { + IWL_DEBUG_INFO(mvm, "FW restart requested after debug collection\n"); + mvm->fwrt.trans->dbg.restart_required = FALSE; + ieee80211_restart_hw(mvm->hw); + } else if (mvm->trans->trans_cfg->device_family <= IWL_DEVICE_FAMILY_8000) { + ieee80211_restart_hw(mvm->hw); + } + } +} + +static void iwl_mvm_nic_error(struct iwl_op_mode *op_mode, bool sync) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + + if (!test_bit(STATUS_TRANS_DEAD, &mvm->trans->status) && + !test_and_clear_bit(IWL_MVM_STATUS_SUPPRESS_ERROR_LOG_ONCE, + &mvm->status)) + iwl_mvm_dump_nic_error_log(mvm); + + if (sync) { + iwl_fw_error_collect(&mvm->fwrt, true); + /* + * Currently, the only case for sync=true is during + * shutdown, so just stop in this case. If/when that + * changes, we need to be a bit smarter here. + */ + return; + } + + /* + * If the firmware crashes while we're already considering it + * to be dead then don't ask for a restart, that cannot do + * anything useful anyway. + */ + if (!test_bit(IWL_MVM_STATUS_FIRMWARE_RUNNING, &mvm->status)) + return; + + iwl_mvm_nic_restart(mvm, false); +} + +static void iwl_mvm_cmd_queue_full(struct iwl_op_mode *op_mode) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + + WARN_ON(1); + iwl_mvm_nic_restart(mvm, true); +} + +static void iwl_op_mode_mvm_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_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + + iwl_dbg_tlv_time_point(&mvm->fwrt, tp_id, tp_data); +} + +#define IWL_MVM_COMMON_OPS \ + /* these could be differentiated */ \ + .async_cb = iwl_mvm_async_cb, \ + .queue_full = iwl_mvm_stop_sw_queue, \ + .queue_not_full = iwl_mvm_wake_sw_queue, \ + .hw_rf_kill = iwl_mvm_set_hw_rfkill_state, \ + .free_skb = iwl_mvm_free_skb, \ + .nic_error = iwl_mvm_nic_error, \ + .cmd_queue_full = iwl_mvm_cmd_queue_full, \ + .nic_config = iwl_mvm_nic_config, \ + /* as we only register one, these MUST be common! */ \ + .start = iwl_op_mode_mvm_start, \ + .stop = iwl_op_mode_mvm_stop, \ + .time_point = iwl_op_mode_mvm_time_point + +static const struct iwl_op_mode_ops iwl_mvm_ops = { + IWL_MVM_COMMON_OPS, + .rx = iwl_mvm_rx, +}; + +static void iwl_mvm_rx_mq_rss(struct iwl_op_mode *op_mode, + struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, + unsigned int queue) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + struct iwl_rx_packet *pkt = rxb_addr(rxb); + u16 cmd = WIDE_ID(pkt->hdr.group_id, pkt->hdr.cmd); + + if (unlikely(queue >= mvm->trans->num_rx_queues)) + return; + + if (unlikely(cmd == WIDE_ID(LEGACY_GROUP, FRAME_RELEASE))) + iwl_mvm_rx_frame_release(mvm, napi, rxb, queue); + else if (unlikely(cmd == WIDE_ID(DATA_PATH_GROUP, + RX_QUEUES_NOTIFICATION))) + iwl_mvm_rx_queue_notif(mvm, napi, rxb, queue); + else if (likely(cmd == WIDE_ID(LEGACY_GROUP, REPLY_RX_MPDU_CMD))) + iwl_mvm_rx_mpdu_mq(mvm, napi, rxb, queue); +} + +static const struct iwl_op_mode_ops iwl_mvm_ops_mq = { + IWL_MVM_COMMON_OPS, + .rx = iwl_mvm_rx_mq, + .rx_rss = iwl_mvm_rx_mq_rss, +}; diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/phy-ctxt.c b/drivers/net/wireless/intel/iwlwifi/mvm/phy-ctxt.c new file mode 100644 index 000000000..2c14188f3 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/phy-ctxt.c @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2017 Intel Deutschland GmbH + */ +#include <net/mac80211.h> +#include "fw-api.h" +#include "mvm.h" + +/* Maps the driver specific channel width definition to the fw values */ +u8 iwl_mvm_get_channel_width(struct cfg80211_chan_def *chandef) +{ + switch (chandef->width) { + case NL80211_CHAN_WIDTH_20_NOHT: + case NL80211_CHAN_WIDTH_20: + return PHY_VHT_CHANNEL_MODE20; + case NL80211_CHAN_WIDTH_40: + return PHY_VHT_CHANNEL_MODE40; + case NL80211_CHAN_WIDTH_80: + return PHY_VHT_CHANNEL_MODE80; + case NL80211_CHAN_WIDTH_160: + return PHY_VHT_CHANNEL_MODE160; + default: + WARN(1, "Invalid channel width=%u", chandef->width); + return PHY_VHT_CHANNEL_MODE20; + } +} + +/* + * Maps the driver specific control channel position (relative to the center + * freq) definitions to the the fw values + */ +u8 iwl_mvm_get_ctrl_pos(struct cfg80211_chan_def *chandef) +{ + switch (chandef->chan->center_freq - chandef->center_freq1) { + case -70: + return PHY_VHT_CTRL_POS_4_BELOW; + case -50: + return PHY_VHT_CTRL_POS_3_BELOW; + case -30: + return PHY_VHT_CTRL_POS_2_BELOW; + case -10: + return PHY_VHT_CTRL_POS_1_BELOW; + case 10: + return PHY_VHT_CTRL_POS_1_ABOVE; + case 30: + return PHY_VHT_CTRL_POS_2_ABOVE; + case 50: + return PHY_VHT_CTRL_POS_3_ABOVE; + case 70: + return PHY_VHT_CTRL_POS_4_ABOVE; + default: + WARN(1, "Invalid channel definition"); + fallthrough; + case 0: + /* + * The FW is expected to check the control channel position only + * when in HT/VHT and the channel width is not 20MHz. Return + * this value as the default one. + */ + return PHY_VHT_CTRL_POS_1_BELOW; + } +} + +/* + * Construct the generic fields of the PHY context command + */ +static void iwl_mvm_phy_ctxt_cmd_hdr(struct iwl_mvm_phy_ctxt *ctxt, + struct iwl_phy_context_cmd *cmd, + u32 action) +{ + cmd->id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(ctxt->id, + ctxt->color)); + cmd->action = cpu_to_le32(action); +} + +static void iwl_mvm_phy_ctxt_set_rxchain(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt, + __le32 *rxchain_info, + u8 chains_static, + u8 chains_dynamic) +{ + u8 active_cnt, idle_cnt; + + /* Set rx the chains */ + idle_cnt = chains_static; + active_cnt = chains_dynamic; + + /* In scenarios where we only ever use a single-stream rates, + * i.e. legacy 11b/g/a associations, single-stream APs or even + * static SMPS, enable both chains to get diversity, improving + * the case where we're far enough from the AP that attenuation + * between the two antennas is sufficiently different to impact + * performance. + */ + if (active_cnt == 1 && iwl_mvm_rx_diversity_allowed(mvm, ctxt)) { + idle_cnt = 2; + active_cnt = 2; + } + + *rxchain_info = cpu_to_le32(iwl_mvm_get_valid_rx_ant(mvm) << + PHY_RX_CHAIN_VALID_POS); + *rxchain_info |= cpu_to_le32(idle_cnt << PHY_RX_CHAIN_CNT_POS); + *rxchain_info |= cpu_to_le32(active_cnt << + PHY_RX_CHAIN_MIMO_CNT_POS); +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (unlikely(mvm->dbgfs_rx_phyinfo)) + *rxchain_info = cpu_to_le32(mvm->dbgfs_rx_phyinfo); +#endif +} + +/* + * Add the phy configuration to the PHY context command + */ +static void iwl_mvm_phy_ctxt_cmd_data_v1(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt, + struct iwl_phy_context_cmd_v1 *cmd, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic) +{ + struct iwl_phy_context_cmd_tail *tail = + iwl_mvm_chan_info_cmd_tail(mvm, &cmd->ci); + + /* Set the channel info data */ + iwl_mvm_set_chan_info_chandef(mvm, &cmd->ci, chandef); + + iwl_mvm_phy_ctxt_set_rxchain(mvm, ctxt, &tail->rxchain_info, + chains_static, chains_dynamic); + + tail->txchain_info = cpu_to_le32(iwl_mvm_get_valid_tx_ant(mvm)); +} + +/* + * Add the phy configuration to the PHY context command + */ +static void iwl_mvm_phy_ctxt_cmd_data(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt, + struct iwl_phy_context_cmd *cmd, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic) +{ + cmd->lmac_id = cpu_to_le32(iwl_mvm_get_lmac_id(mvm->fw, + chandef->chan->band)); + + /* Set the channel info data */ + iwl_mvm_set_chan_info_chandef(mvm, &cmd->ci, chandef); + + /* we only support RLC command version 2 */ + if (iwl_fw_lookup_cmd_ver(mvm->fw, WIDE_ID(DATA_PATH_GROUP, RLC_CONFIG_CMD), 0) < 2) + iwl_mvm_phy_ctxt_set_rxchain(mvm, ctxt, &cmd->rxchain_info, + chains_static, chains_dynamic); +} + +static int iwl_mvm_phy_send_rlc(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt, + u8 chains_static, u8 chains_dynamic) +{ + struct iwl_rlc_config_cmd cmd = { + .phy_id = cpu_to_le32(ctxt->id), + }; + + if (iwl_fw_lookup_cmd_ver(mvm->fw, WIDE_ID(DATA_PATH_GROUP, RLC_CONFIG_CMD), 0) < 2) + return 0; + + BUILD_BUG_ON(IWL_RLC_CHAIN_INFO_DRIVER_FORCE != + PHY_RX_CHAIN_DRIVER_FORCE_MSK); + BUILD_BUG_ON(IWL_RLC_CHAIN_INFO_VALID != + PHY_RX_CHAIN_VALID_MSK); + BUILD_BUG_ON(IWL_RLC_CHAIN_INFO_FORCE != + PHY_RX_CHAIN_FORCE_SEL_MSK); + BUILD_BUG_ON(IWL_RLC_CHAIN_INFO_FORCE_MIMO != + PHY_RX_CHAIN_FORCE_MIMO_SEL_MSK); + BUILD_BUG_ON(IWL_RLC_CHAIN_INFO_COUNT != PHY_RX_CHAIN_CNT_MSK); + BUILD_BUG_ON(IWL_RLC_CHAIN_INFO_MIMO_COUNT != + PHY_RX_CHAIN_MIMO_CNT_MSK); + + iwl_mvm_phy_ctxt_set_rxchain(mvm, ctxt, &cmd.rlc.rx_chain_info, + chains_static, chains_dynamic); + + return iwl_mvm_send_cmd_pdu(mvm, iwl_cmd_id(RLC_CONFIG_CMD, + DATA_PATH_GROUP, 2), + 0, sizeof(cmd), &cmd); +} + +/* + * Send a command to apply the current phy configuration. The command is send + * only if something in the configuration changed: in case that this is the + * first time that the phy configuration is applied or in case that the phy + * configuration changed from the previous apply. + */ +static int iwl_mvm_phy_ctxt_apply(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic, + u32 action) +{ + int ret; + int ver = iwl_fw_lookup_cmd_ver(mvm->fw, PHY_CONTEXT_CMD, 1); + + if (ver == 3 || ver == 4) { + struct iwl_phy_context_cmd cmd = {}; + + /* Set the command header fields */ + iwl_mvm_phy_ctxt_cmd_hdr(ctxt, &cmd, action); + + /* Set the command data */ + iwl_mvm_phy_ctxt_cmd_data(mvm, ctxt, &cmd, chandef, + chains_static, + chains_dynamic); + + ret = iwl_mvm_send_cmd_pdu(mvm, PHY_CONTEXT_CMD, + 0, sizeof(cmd), &cmd); + } else if (ver < 3) { + struct iwl_phy_context_cmd_v1 cmd = {}; + u16 len = sizeof(cmd) - iwl_mvm_chan_info_padding(mvm); + + /* Set the command header fields */ + iwl_mvm_phy_ctxt_cmd_hdr(ctxt, + (struct iwl_phy_context_cmd *)&cmd, + action); + + /* Set the command data */ + iwl_mvm_phy_ctxt_cmd_data_v1(mvm, ctxt, &cmd, chandef, + chains_static, + chains_dynamic); + ret = iwl_mvm_send_cmd_pdu(mvm, PHY_CONTEXT_CMD, + 0, len, &cmd); + } else { + IWL_ERR(mvm, "PHY ctxt cmd error ver %d not supported\n", ver); + return -EOPNOTSUPP; + } + + + if (ret) { + IWL_ERR(mvm, "PHY ctxt cmd error. ret=%d\n", ret); + return ret; + } + + if (action != FW_CTXT_ACTION_REMOVE) + return iwl_mvm_phy_send_rlc(mvm, ctxt, chains_static, + chains_dynamic); + + return 0; +} + +/* + * Send a command to add a PHY context based on the current HW configuration. + */ +int iwl_mvm_phy_ctxt_add(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic) +{ + WARN_ON(!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) && + ctxt->ref); + lockdep_assert_held(&mvm->mutex); + + ctxt->channel = chandef->chan; + ctxt->width = chandef->width; + ctxt->center_freq1 = chandef->center_freq1; + + return iwl_mvm_phy_ctxt_apply(mvm, ctxt, chandef, + chains_static, chains_dynamic, + FW_CTXT_ACTION_ADD); +} + +/* + * Update the number of references to the given PHY context. This is valid only + * in case the PHY context was already created, i.e., its reference count > 0. + */ +void iwl_mvm_phy_ctxt_ref(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt) +{ + lockdep_assert_held(&mvm->mutex); + ctxt->ref++; +} + +/* + * Send a command to modify the PHY context based on the current HW + * configuration. Note that the function does not check that the configuration + * changed. + */ +int iwl_mvm_phy_ctxt_changed(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic) +{ + enum iwl_ctxt_action action = FW_CTXT_ACTION_MODIFY; + + lockdep_assert_held(&mvm->mutex); + + if (iwl_fw_lookup_cmd_ver(mvm->fw, WIDE_ID(DATA_PATH_GROUP, RLC_CONFIG_CMD), 0) >= 2 && + ctxt->channel == chandef->chan && + ctxt->width == chandef->width && + ctxt->center_freq1 == chandef->center_freq1) + return iwl_mvm_phy_send_rlc(mvm, ctxt, chains_static, + chains_dynamic); + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_BINDING_CDB_SUPPORT) && + ctxt->channel->band != chandef->chan->band) { + int ret; + + /* ... remove it here ...*/ + ret = iwl_mvm_phy_ctxt_apply(mvm, ctxt, chandef, + chains_static, chains_dynamic, + FW_CTXT_ACTION_REMOVE); + if (ret) + return ret; + + /* ... and proceed to add it again */ + action = FW_CTXT_ACTION_ADD; + } + + ctxt->channel = chandef->chan; + ctxt->width = chandef->width; + ctxt->center_freq1 = chandef->center_freq1; + + return iwl_mvm_phy_ctxt_apply(mvm, ctxt, chandef, + chains_static, chains_dynamic, + action); +} + +void iwl_mvm_phy_ctxt_unref(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt) +{ + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON_ONCE(!ctxt)) + return; + + ctxt->ref--; + + /* + * Move unused phy's to a default channel. When the phy is moved the, + * fw will cleanup immediate quiet bit if it was previously set, + * otherwise we might not be able to reuse this phy. + */ + if (ctxt->ref == 0) { + struct ieee80211_channel *chan = NULL; + struct cfg80211_chan_def chandef; + struct ieee80211_supported_band *sband; + enum nl80211_band band; + int channel; + + for (band = NL80211_BAND_2GHZ; band < NUM_NL80211_BANDS; band++) { + sband = mvm->hw->wiphy->bands[band]; + + if (!sband) + continue; + + for (channel = 0; channel < sband->n_channels; channel++) + if (!(sband->channels[channel].flags & + IEEE80211_CHAN_DISABLED)) { + chan = &sband->channels[channel]; + break; + } + + if (chan) + break; + } + + if (WARN_ON(!chan)) + return; + + cfg80211_chandef_create(&chandef, chan, NL80211_CHAN_NO_HT); + iwl_mvm_phy_ctxt_changed(mvm, ctxt, &chandef, 1, 1); + } +} + +static void iwl_mvm_binding_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + unsigned long *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (!mvmvif->phy_ctxt) + return; + + if (vif->type == NL80211_IFTYPE_STATION || + vif->type == NL80211_IFTYPE_AP) + __set_bit(mvmvif->phy_ctxt->id, data); +} + +int iwl_mvm_phy_ctx_count(struct iwl_mvm *mvm) +{ + unsigned long phy_ctxt_counter = 0; + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_binding_iterator, + &phy_ctxt_counter); + + return hweight8(phy_ctxt_counter); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/power.c b/drivers/net/wireless/intel/iwlwifi/mvm/power.c new file mode 100644 index 000000000..f5744162d --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/power.c @@ -0,0 +1,959 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2019, 2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/etherdevice.h> + +#include <net/mac80211.h> + +#include "iwl-debug.h" +#include "mvm.h" +#include "iwl-modparams.h" +#include "fw/api/power.h" + +#define POWER_KEEP_ALIVE_PERIOD_SEC 25 + +static +int iwl_mvm_beacon_filter_send_cmd(struct iwl_mvm *mvm, + struct iwl_beacon_filter_cmd *cmd, + u32 flags) +{ + u16 len; + + IWL_DEBUG_POWER(mvm, "ba_enable_beacon_abort is: %d\n", + le32_to_cpu(cmd->ba_enable_beacon_abort)); + IWL_DEBUG_POWER(mvm, "ba_escape_timer is: %d\n", + le32_to_cpu(cmd->ba_escape_timer)); + IWL_DEBUG_POWER(mvm, "bf_debug_flag is: %d\n", + le32_to_cpu(cmd->bf_debug_flag)); + IWL_DEBUG_POWER(mvm, "bf_enable_beacon_filter is: %d\n", + le32_to_cpu(cmd->bf_enable_beacon_filter)); + IWL_DEBUG_POWER(mvm, "bf_energy_delta is: %d\n", + le32_to_cpu(cmd->bf_energy_delta)); + IWL_DEBUG_POWER(mvm, "bf_escape_timer is: %d\n", + le32_to_cpu(cmd->bf_escape_timer)); + IWL_DEBUG_POWER(mvm, "bf_roaming_energy_delta is: %d\n", + le32_to_cpu(cmd->bf_roaming_energy_delta)); + IWL_DEBUG_POWER(mvm, "bf_roaming_state is: %d\n", + le32_to_cpu(cmd->bf_roaming_state)); + IWL_DEBUG_POWER(mvm, "bf_temp_threshold is: %d\n", + le32_to_cpu(cmd->bf_temp_threshold)); + IWL_DEBUG_POWER(mvm, "bf_temp_fast_filter is: %d\n", + le32_to_cpu(cmd->bf_temp_fast_filter)); + IWL_DEBUG_POWER(mvm, "bf_temp_slow_filter is: %d\n", + le32_to_cpu(cmd->bf_temp_slow_filter)); + IWL_DEBUG_POWER(mvm, "bf_threshold_absolute_low is: %d, %d\n", + le32_to_cpu(cmd->bf_threshold_absolute_low[0]), + le32_to_cpu(cmd->bf_threshold_absolute_low[1])); + + IWL_DEBUG_POWER(mvm, "bf_threshold_absolute_high is: %d, %d\n", + le32_to_cpu(cmd->bf_threshold_absolute_high[0]), + le32_to_cpu(cmd->bf_threshold_absolute_high[1])); + + if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_BEACON_FILTER_V4)) + len = sizeof(struct iwl_beacon_filter_cmd); + else + len = offsetof(struct iwl_beacon_filter_cmd, + bf_threshold_absolute_low); + + return iwl_mvm_send_cmd_pdu(mvm, REPLY_BEACON_FILTERING_CMD, flags, + len, cmd); +} + +static +void iwl_mvm_beacon_filter_set_cqm_params(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_beacon_filter_cmd *cmd) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif->bss_conf.cqm_rssi_thold) { + cmd->bf_energy_delta = + cpu_to_le32(vif->bss_conf.cqm_rssi_hyst); + /* fw uses an absolute value for this */ + cmd->bf_roaming_state = + cpu_to_le32(-vif->bss_conf.cqm_rssi_thold); + } + cmd->ba_enable_beacon_abort = cpu_to_le32(mvmvif->bf_data.ba_enabled); +} + +static void iwl_mvm_power_log(struct iwl_mvm *mvm, + struct iwl_mac_power_cmd *cmd) +{ + IWL_DEBUG_POWER(mvm, + "Sending power table command on mac id 0x%X for power level %d, flags = 0x%X\n", + cmd->id_and_color, iwlmvm_mod_params.power_scheme, + le16_to_cpu(cmd->flags)); + IWL_DEBUG_POWER(mvm, "Keep alive = %u sec\n", + le16_to_cpu(cmd->keep_alive_seconds)); + + if (!(cmd->flags & cpu_to_le16(POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK))) { + IWL_DEBUG_POWER(mvm, "Disable power management\n"); + return; + } + + IWL_DEBUG_POWER(mvm, "Rx timeout = %u usec\n", + le32_to_cpu(cmd->rx_data_timeout)); + IWL_DEBUG_POWER(mvm, "Tx timeout = %u usec\n", + le32_to_cpu(cmd->tx_data_timeout)); + if (cmd->flags & cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK)) + IWL_DEBUG_POWER(mvm, "DTIM periods to skip = %u\n", + cmd->skip_dtim_periods); + if (cmd->flags & cpu_to_le16(POWER_FLAGS_LPRX_ENA_MSK)) + IWL_DEBUG_POWER(mvm, "LP RX RSSI threshold = %u\n", + cmd->lprx_rssi_threshold); + if (cmd->flags & cpu_to_le16(POWER_FLAGS_ADVANCE_PM_ENA_MSK)) { + IWL_DEBUG_POWER(mvm, "uAPSD enabled\n"); + IWL_DEBUG_POWER(mvm, "Rx timeout (uAPSD) = %u usec\n", + le32_to_cpu(cmd->rx_data_timeout_uapsd)); + IWL_DEBUG_POWER(mvm, "Tx timeout (uAPSD) = %u usec\n", + le32_to_cpu(cmd->tx_data_timeout_uapsd)); + IWL_DEBUG_POWER(mvm, "QNDP TID = %d\n", cmd->qndp_tid); + IWL_DEBUG_POWER(mvm, "ACs flags = 0x%x\n", cmd->uapsd_ac_flags); + IWL_DEBUG_POWER(mvm, "Max SP = %d\n", cmd->uapsd_max_sp); + } +} + +static void iwl_mvm_power_configure_uapsd(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mac_power_cmd *cmd) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + enum ieee80211_ac_numbers ac; + bool tid_found = false; + + if (test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status) || + cmd->flags & cpu_to_le16(POWER_FLAGS_SNOOZE_ENA_MSK)) { + cmd->rx_data_timeout_uapsd = + cpu_to_le32(IWL_MVM_WOWLAN_PS_RX_DATA_TIMEOUT); + cmd->tx_data_timeout_uapsd = + cpu_to_le32(IWL_MVM_WOWLAN_PS_TX_DATA_TIMEOUT); + } else { + cmd->rx_data_timeout_uapsd = + cpu_to_le32(IWL_MVM_UAPSD_RX_DATA_TIMEOUT); + cmd->tx_data_timeout_uapsd = + cpu_to_le32(IWL_MVM_UAPSD_TX_DATA_TIMEOUT); + } + +#ifdef CONFIG_IWLWIFI_DEBUGFS + /* set advanced pm flag with no uapsd ACs to enable ps-poll */ + if (mvmvif->dbgfs_pm.use_ps_poll) { + cmd->flags |= cpu_to_le16(POWER_FLAGS_ADVANCE_PM_ENA_MSK); + return; + } +#endif + + for (ac = IEEE80211_AC_VO; ac <= IEEE80211_AC_BK; ac++) { + if (!mvmvif->queue_params[ac].uapsd) + continue; + + if (!test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status)) + cmd->flags |= + cpu_to_le16(POWER_FLAGS_ADVANCE_PM_ENA_MSK); + + cmd->uapsd_ac_flags |= BIT(ac); + + /* QNDP TID - the highest TID with no admission control */ + if (!tid_found && !mvmvif->queue_params[ac].acm) { + tid_found = true; + switch (ac) { + case IEEE80211_AC_VO: + cmd->qndp_tid = 6; + break; + case IEEE80211_AC_VI: + cmd->qndp_tid = 5; + break; + case IEEE80211_AC_BE: + cmd->qndp_tid = 0; + break; + case IEEE80211_AC_BK: + cmd->qndp_tid = 1; + break; + } + } + } + + cmd->flags |= cpu_to_le16(POWER_FLAGS_UAPSD_MISBEHAVING_ENA_MSK); + + if (cmd->uapsd_ac_flags == (BIT(IEEE80211_AC_VO) | + BIT(IEEE80211_AC_VI) | + BIT(IEEE80211_AC_BE) | + BIT(IEEE80211_AC_BK))) { + cmd->flags |= cpu_to_le16(POWER_FLAGS_SNOOZE_ENA_MSK); + cmd->snooze_interval = cpu_to_le16(IWL_MVM_PS_SNOOZE_INTERVAL); + cmd->snooze_window = + test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status) ? + cpu_to_le16(IWL_MVM_WOWLAN_PS_SNOOZE_WINDOW) : + cpu_to_le16(IWL_MVM_PS_SNOOZE_WINDOW); + } + + cmd->uapsd_max_sp = mvm->hw->uapsd_max_sp_len; + + if (cmd->flags & cpu_to_le16(POWER_FLAGS_SNOOZE_ENA_MSK)) { + cmd->heavy_tx_thld_packets = + IWL_MVM_PS_SNOOZE_HEAVY_TX_THLD_PACKETS; + cmd->heavy_rx_thld_packets = + IWL_MVM_PS_SNOOZE_HEAVY_RX_THLD_PACKETS; + } else { + cmd->heavy_tx_thld_packets = + IWL_MVM_PS_HEAVY_TX_THLD_PACKETS; + cmd->heavy_rx_thld_packets = + IWL_MVM_PS_HEAVY_RX_THLD_PACKETS; + } + cmd->heavy_tx_thld_percentage = + IWL_MVM_PS_HEAVY_TX_THLD_PERCENT; + cmd->heavy_rx_thld_percentage = + IWL_MVM_PS_HEAVY_RX_THLD_PERCENT; +} + +static void iwl_mvm_p2p_standalone_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + bool *is_p2p_standalone = _data; + + switch (ieee80211_vif_type_p2p(vif)) { + case NL80211_IFTYPE_P2P_GO: + case NL80211_IFTYPE_AP: + *is_p2p_standalone = false; + break; + case NL80211_IFTYPE_STATION: + if (vif->cfg.assoc) + *is_p2p_standalone = false; + break; + + default: + break; + } +} + +static bool iwl_mvm_power_allow_uapsd(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (!memcmp(mvmvif->uapsd_misbehaving_bssid, vif->bss_conf.bssid, + ETH_ALEN)) + return false; + + /* + * Avoid using uAPSD if P2P client is associated to GO that uses + * opportunistic power save. This is due to current FW limitation. + */ + if (vif->p2p && + (vif->bss_conf.p2p_noa_attr.oppps_ctwindow & + IEEE80211_P2P_OPPPS_ENABLE_BIT)) + return false; + + /* + * Avoid using uAPSD if client is in DCM - + * low latency issue in Miracast + */ + if (iwl_mvm_phy_ctx_count(mvm) >= 2) + return false; + + if (vif->p2p) { + /* Allow U-APSD only if p2p is stand alone */ + bool is_p2p_standalone = true; + + if (!iwl_mvm_is_p2p_scm_uapsd_supported(mvm)) + return false; + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_p2p_standalone_iterator, + &is_p2p_standalone); + + if (!is_p2p_standalone) + return false; + } + + return true; +} + +static bool iwl_mvm_power_is_radar(struct ieee80211_vif *vif) +{ + struct ieee80211_chanctx_conf *chanctx_conf; + struct ieee80211_channel *chan; + bool radar_detect = false; + + rcu_read_lock(); + chanctx_conf = rcu_dereference(vif->bss_conf.chanctx_conf); + WARN_ON(!chanctx_conf); + if (chanctx_conf) { + chan = chanctx_conf->def.chan; + radar_detect = chan->flags & IEEE80211_CHAN_RADAR; + } + rcu_read_unlock(); + + return radar_detect; +} + +static void iwl_mvm_power_config_skip_dtim(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mac_power_cmd *cmd) +{ + int dtimper = vif->bss_conf.dtim_period ?: 1; + int skip; + + /* disable, in case we're supposed to override */ + cmd->skip_dtim_periods = 0; + cmd->flags &= ~cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK); + + if (iwl_mvm_power_is_radar(vif)) + return; + + if (dtimper >= 10) + return; + + if (!test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status)) { + if (iwlmvm_mod_params.power_scheme != IWL_POWER_SCHEME_LP) + return; + skip = 2; + } else { + int dtimper_tu = dtimper * vif->bss_conf.beacon_int; + + if (WARN_ON(!dtimper_tu)) + return; + /* configure skip over dtim up to 306TU - 314 msec */ + skip = max_t(u8, 1, 306 / dtimper_tu); + } + + /* the firmware really expects "look at every X DTIMs", so add 1 */ + cmd->skip_dtim_periods = 1 + skip; + cmd->flags |= cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK); +} + +static void iwl_mvm_power_build_cmd(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mac_power_cmd *cmd) +{ + int dtimper, bi; + int keep_alive; + struct iwl_mvm_vif *mvmvif __maybe_unused = + iwl_mvm_vif_from_mac80211(vif); + + cmd->id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)); + dtimper = vif->bss_conf.dtim_period; + bi = vif->bss_conf.beacon_int; + + /* + * Regardless of power management state the driver must set + * keep alive period. FW will use it for sending keep alive NDPs + * immediately after association. Check that keep alive period + * is at least 3 * DTIM + */ + keep_alive = DIV_ROUND_UP(ieee80211_tu_to_usec(3 * dtimper * bi), + USEC_PER_SEC); + keep_alive = max(keep_alive, POWER_KEEP_ALIVE_PERIOD_SEC); + cmd->keep_alive_seconds = cpu_to_le16(keep_alive); + + if (mvm->ps_disabled) + return; + + cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_SAVE_ENA_MSK); + + if (!vif->cfg.ps || !mvmvif->pm_enabled) + return; + + if (iwl_mvm_vif_low_latency(mvmvif) && vif->p2p && + (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SHORT_PM_TIMEOUTS) || + !IWL_MVM_P2P_LOWLATENCY_PS_ENABLE)) + return; + + cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK); + + if (vif->bss_conf.beacon_rate && + (vif->bss_conf.beacon_rate->bitrate == 10 || + vif->bss_conf.beacon_rate->bitrate == 60)) { + cmd->flags |= cpu_to_le16(POWER_FLAGS_LPRX_ENA_MSK); + cmd->lprx_rssi_threshold = POWER_LPRX_RSSI_THRESHOLD; + } + + iwl_mvm_power_config_skip_dtim(mvm, vif, cmd); + + if (test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status)) { + cmd->rx_data_timeout = + cpu_to_le32(IWL_MVM_WOWLAN_PS_RX_DATA_TIMEOUT); + cmd->tx_data_timeout = + cpu_to_le32(IWL_MVM_WOWLAN_PS_TX_DATA_TIMEOUT); + } else if (iwl_mvm_vif_low_latency(mvmvif) && vif->p2p && + fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SHORT_PM_TIMEOUTS)) { + cmd->tx_data_timeout = + cpu_to_le32(IWL_MVM_SHORT_PS_TX_DATA_TIMEOUT); + cmd->rx_data_timeout = + cpu_to_le32(IWL_MVM_SHORT_PS_RX_DATA_TIMEOUT); + } else { + cmd->rx_data_timeout = + cpu_to_le32(IWL_MVM_DEFAULT_PS_RX_DATA_TIMEOUT); + cmd->tx_data_timeout = + cpu_to_le32(IWL_MVM_DEFAULT_PS_TX_DATA_TIMEOUT); + } + + if (iwl_mvm_power_allow_uapsd(mvm, vif)) + iwl_mvm_power_configure_uapsd(mvm, vif, cmd); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_KEEP_ALIVE) + cmd->keep_alive_seconds = + cpu_to_le16(mvmvif->dbgfs_pm.keep_alive_seconds); + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_SKIP_OVER_DTIM) { + if (mvmvif->dbgfs_pm.skip_over_dtim) + cmd->flags |= + cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK); + else + cmd->flags &= + cpu_to_le16(~POWER_FLAGS_SKIP_OVER_DTIM_MSK); + } + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_RX_DATA_TIMEOUT) + cmd->rx_data_timeout = + cpu_to_le32(mvmvif->dbgfs_pm.rx_data_timeout); + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_TX_DATA_TIMEOUT) + cmd->tx_data_timeout = + cpu_to_le32(mvmvif->dbgfs_pm.tx_data_timeout); + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_SKIP_DTIM_PERIODS) + cmd->skip_dtim_periods = mvmvif->dbgfs_pm.skip_dtim_periods; + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_LPRX_ENA) { + if (mvmvif->dbgfs_pm.lprx_ena) + cmd->flags |= cpu_to_le16(POWER_FLAGS_LPRX_ENA_MSK); + else + cmd->flags &= cpu_to_le16(~POWER_FLAGS_LPRX_ENA_MSK); + } + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_LPRX_RSSI_THRESHOLD) + cmd->lprx_rssi_threshold = mvmvif->dbgfs_pm.lprx_rssi_threshold; + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_SNOOZE_ENABLE) { + if (mvmvif->dbgfs_pm.snooze_ena) + cmd->flags |= + cpu_to_le16(POWER_FLAGS_SNOOZE_ENA_MSK); + else + cmd->flags &= + cpu_to_le16(~POWER_FLAGS_SNOOZE_ENA_MSK); + } + if (mvmvif->dbgfs_pm.mask & MVM_DEBUGFS_PM_UAPSD_MISBEHAVING) { + u16 flag = POWER_FLAGS_UAPSD_MISBEHAVING_ENA_MSK; + if (mvmvif->dbgfs_pm.uapsd_misbehaving) + cmd->flags |= cpu_to_le16(flag); + else + cmd->flags &= cpu_to_le16(flag); + } +#endif /* CONFIG_IWLWIFI_DEBUGFS */ +} + +static int iwl_mvm_power_send_cmd(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mac_power_cmd cmd = {}; + + iwl_mvm_power_build_cmd(mvm, vif, &cmd); + iwl_mvm_power_log(mvm, &cmd); +#ifdef CONFIG_IWLWIFI_DEBUGFS + memcpy(&iwl_mvm_vif_from_mac80211(vif)->mac_pwr_cmd, &cmd, sizeof(cmd)); +#endif + + return iwl_mvm_send_cmd_pdu(mvm, MAC_PM_POWER_TABLE, 0, + sizeof(cmd), &cmd); +} + +int iwl_mvm_power_update_device(struct iwl_mvm *mvm) +{ + struct iwl_device_power_cmd cmd = { + .flags = 0, + }; + + if (iwlmvm_mod_params.power_scheme == IWL_POWER_SCHEME_CAM) + mvm->ps_disabled = true; + + if (!mvm->ps_disabled) + cmd.flags |= cpu_to_le16(DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status) ? + mvm->disable_power_off_d3 : mvm->disable_power_off) + cmd.flags &= + cpu_to_le16(~DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK); +#endif + if (mvm->ext_clock_valid) + cmd.flags |= cpu_to_le16(DEVICE_POWER_FLAGS_32K_CLK_VALID_MSK); + + IWL_DEBUG_POWER(mvm, + "Sending device power command with flags = 0x%X\n", + cmd.flags); + + return iwl_mvm_send_cmd_pdu(mvm, POWER_TABLE_CMD, 0, sizeof(cmd), + &cmd); +} + +void iwl_mvm_power_vif_assoc(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (memcmp(vif->bss_conf.bssid, mvmvif->uapsd_misbehaving_bssid, + ETH_ALEN)) + eth_zero_addr(mvmvif->uapsd_misbehaving_bssid); +} + +static void iwl_mvm_power_uapsd_misbehav_ap_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + u8 *ap_sta_id = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + /* The ap_sta_id is not expected to change during current association + * so no explicit protection is needed + */ + if (mvmvif->ap_sta_id == *ap_sta_id) + memcpy(mvmvif->uapsd_misbehaving_bssid, vif->bss_conf.bssid, + ETH_ALEN); +} + +void iwl_mvm_power_uapsd_misbehaving_ap_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_uapsd_misbehaving_ap_notif *notif = (void *)pkt->data; + u8 ap_sta_id = le32_to_cpu(notif->sta_id); + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_power_uapsd_misbehav_ap_iterator, &ap_sta_id); +} + +struct iwl_power_vifs { + struct iwl_mvm *mvm; + struct ieee80211_vif *bss_vif; + struct ieee80211_vif *p2p_vif; + struct ieee80211_vif *ap_vif; + struct ieee80211_vif *monitor_vif; + bool p2p_active; + bool bss_active; + bool ap_active; + bool monitor_active; +}; + +static void iwl_mvm_power_disable_pm_iterator(void *_data, u8* mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + mvmvif->pm_enabled = false; +} + +static void iwl_mvm_power_ps_disabled_iterator(void *_data, u8* mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + bool *disable_ps = _data; + + if (mvmvif->phy_ctxt && mvmvif->phy_ctxt->id < NUM_PHY_CTX) + *disable_ps |= mvmvif->ps_disabled; +} + +static void iwl_mvm_power_get_vifs_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_power_vifs *power_iterator = _data; + bool active = mvmvif->phy_ctxt && mvmvif->phy_ctxt->id < NUM_PHY_CTX; + + if (!mvmvif->uploaded) + return; + + switch (ieee80211_vif_type_p2p(vif)) { + case NL80211_IFTYPE_P2P_DEVICE: + break; + + case NL80211_IFTYPE_P2P_GO: + case NL80211_IFTYPE_AP: + /* only a single MAC of the same type */ + WARN_ON(power_iterator->ap_vif); + power_iterator->ap_vif = vif; + if (active) + power_iterator->ap_active = true; + break; + + case NL80211_IFTYPE_MONITOR: + /* only a single MAC of the same type */ + WARN_ON(power_iterator->monitor_vif); + power_iterator->monitor_vif = vif; + if (active) + power_iterator->monitor_active = true; + break; + + case NL80211_IFTYPE_P2P_CLIENT: + /* only a single MAC of the same type */ + WARN_ON(power_iterator->p2p_vif); + power_iterator->p2p_vif = vif; + if (active) + power_iterator->p2p_active = true; + break; + + case NL80211_IFTYPE_STATION: + power_iterator->bss_vif = vif; + if (active) + power_iterator->bss_active = true; + break; + + default: + break; + } +} + +static void iwl_mvm_power_set_pm(struct iwl_mvm *mvm, + struct iwl_power_vifs *vifs) +{ + struct iwl_mvm_vif *bss_mvmvif = NULL; + struct iwl_mvm_vif *p2p_mvmvif = NULL; + struct iwl_mvm_vif *ap_mvmvif = NULL; + bool client_same_channel = false; + bool ap_same_channel = false; + + lockdep_assert_held(&mvm->mutex); + + /* set pm_enable to false */ + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_power_disable_pm_iterator, + NULL); + + if (vifs->bss_vif) + bss_mvmvif = iwl_mvm_vif_from_mac80211(vifs->bss_vif); + + if (vifs->p2p_vif) + p2p_mvmvif = iwl_mvm_vif_from_mac80211(vifs->p2p_vif); + + if (vifs->ap_vif) + ap_mvmvif = iwl_mvm_vif_from_mac80211(vifs->ap_vif); + + /* don't allow PM if any TDLS stations exist */ + if (iwl_mvm_tdls_sta_count(mvm, NULL)) + return; + + /* enable PM on bss if bss stand alone */ + if (vifs->bss_active && !vifs->p2p_active && !vifs->ap_active) { + bss_mvmvif->pm_enabled = true; + return; + } + + /* enable PM on p2p if p2p stand alone */ + if (vifs->p2p_active && !vifs->bss_active && !vifs->ap_active) { + p2p_mvmvif->pm_enabled = true; + return; + } + + if (vifs->bss_active && vifs->p2p_active) + client_same_channel = (bss_mvmvif->phy_ctxt->id == + p2p_mvmvif->phy_ctxt->id); + if (vifs->bss_active && vifs->ap_active) + ap_same_channel = (bss_mvmvif->phy_ctxt->id == + ap_mvmvif->phy_ctxt->id); + + /* clients are not stand alone: enable PM if DCM */ + if (!(client_same_channel || ap_same_channel)) { + if (vifs->bss_active) + bss_mvmvif->pm_enabled = true; + if (vifs->p2p_active) + p2p_mvmvif->pm_enabled = true; + return; + } + + /* + * There is only one channel in the system and there are only + * bss and p2p clients that share it + */ + if (client_same_channel && !vifs->ap_active) { + /* share same channel*/ + bss_mvmvif->pm_enabled = true; + p2p_mvmvif->pm_enabled = true; + } +} + +#ifdef CONFIG_IWLWIFI_DEBUGFS +int iwl_mvm_power_mac_dbgfs_read(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, char *buf, + int bufsz) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mac_power_cmd cmd = {}; + int pos = 0; + + mutex_lock(&mvm->mutex); + memcpy(&cmd, &mvmvif->mac_pwr_cmd, sizeof(cmd)); + mutex_unlock(&mvm->mutex); + + pos += scnprintf(buf+pos, bufsz-pos, "power_scheme = %d\n", + iwlmvm_mod_params.power_scheme); + pos += scnprintf(buf+pos, bufsz-pos, "flags = 0x%x\n", + le16_to_cpu(cmd.flags)); + pos += scnprintf(buf+pos, bufsz-pos, "keep_alive = %d\n", + le16_to_cpu(cmd.keep_alive_seconds)); + + if (!(cmd.flags & cpu_to_le16(POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK))) + return pos; + + pos += scnprintf(buf+pos, bufsz-pos, "skip_over_dtim = %d\n", + (cmd.flags & + cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK)) ? 1 : 0); + pos += scnprintf(buf+pos, bufsz-pos, "skip_dtim_periods = %d\n", + cmd.skip_dtim_periods); + if (!(cmd.flags & cpu_to_le16(POWER_FLAGS_ADVANCE_PM_ENA_MSK))) { + pos += scnprintf(buf+pos, bufsz-pos, "rx_data_timeout = %d\n", + le32_to_cpu(cmd.rx_data_timeout)); + pos += scnprintf(buf+pos, bufsz-pos, "tx_data_timeout = %d\n", + le32_to_cpu(cmd.tx_data_timeout)); + } + if (cmd.flags & cpu_to_le16(POWER_FLAGS_LPRX_ENA_MSK)) + pos += scnprintf(buf+pos, bufsz-pos, + "lprx_rssi_threshold = %d\n", + cmd.lprx_rssi_threshold); + + if (!(cmd.flags & cpu_to_le16(POWER_FLAGS_ADVANCE_PM_ENA_MSK))) + return pos; + + pos += scnprintf(buf+pos, bufsz-pos, "rx_data_timeout_uapsd = %d\n", + le32_to_cpu(cmd.rx_data_timeout_uapsd)); + pos += scnprintf(buf+pos, bufsz-pos, "tx_data_timeout_uapsd = %d\n", + le32_to_cpu(cmd.tx_data_timeout_uapsd)); + pos += scnprintf(buf+pos, bufsz-pos, "qndp_tid = %d\n", cmd.qndp_tid); + pos += scnprintf(buf+pos, bufsz-pos, "uapsd_ac_flags = 0x%x\n", + cmd.uapsd_ac_flags); + pos += scnprintf(buf+pos, bufsz-pos, "uapsd_max_sp = %d\n", + cmd.uapsd_max_sp); + pos += scnprintf(buf+pos, bufsz-pos, "heavy_tx_thld_packets = %d\n", + cmd.heavy_tx_thld_packets); + pos += scnprintf(buf+pos, bufsz-pos, "heavy_rx_thld_packets = %d\n", + cmd.heavy_rx_thld_packets); + pos += scnprintf(buf+pos, bufsz-pos, "heavy_tx_thld_percentage = %d\n", + cmd.heavy_tx_thld_percentage); + pos += scnprintf(buf+pos, bufsz-pos, "heavy_rx_thld_percentage = %d\n", + cmd.heavy_rx_thld_percentage); + pos += scnprintf(buf+pos, bufsz-pos, "uapsd_misbehaving_enable = %d\n", + (cmd.flags & + cpu_to_le16(POWER_FLAGS_UAPSD_MISBEHAVING_ENA_MSK)) ? + 1 : 0); + + if (!(cmd.flags & cpu_to_le16(POWER_FLAGS_SNOOZE_ENA_MSK))) + return pos; + + pos += scnprintf(buf+pos, bufsz-pos, "snooze_interval = %d\n", + cmd.snooze_interval); + pos += scnprintf(buf+pos, bufsz-pos, "snooze_window = %d\n", + cmd.snooze_window); + + return pos; +} + +void +iwl_mvm_beacon_filter_debugfs_parameters(struct ieee80211_vif *vif, + struct iwl_beacon_filter_cmd *cmd) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_dbgfs_bf *dbgfs_bf = &mvmvif->dbgfs_bf; + + if (dbgfs_bf->mask & MVM_DEBUGFS_BF_ENERGY_DELTA) + cmd->bf_energy_delta = cpu_to_le32(dbgfs_bf->bf_energy_delta); + if (dbgfs_bf->mask & MVM_DEBUGFS_BF_ROAMING_ENERGY_DELTA) + cmd->bf_roaming_energy_delta = + cpu_to_le32(dbgfs_bf->bf_roaming_energy_delta); + if (dbgfs_bf->mask & MVM_DEBUGFS_BF_ROAMING_STATE) + cmd->bf_roaming_state = cpu_to_le32(dbgfs_bf->bf_roaming_state); + if (dbgfs_bf->mask & MVM_DEBUGFS_BF_TEMP_THRESHOLD) + cmd->bf_temp_threshold = + cpu_to_le32(dbgfs_bf->bf_temp_threshold); + if (dbgfs_bf->mask & MVM_DEBUGFS_BF_TEMP_FAST_FILTER) + cmd->bf_temp_fast_filter = + cpu_to_le32(dbgfs_bf->bf_temp_fast_filter); + if (dbgfs_bf->mask & MVM_DEBUGFS_BF_TEMP_SLOW_FILTER) + cmd->bf_temp_slow_filter = + cpu_to_le32(dbgfs_bf->bf_temp_slow_filter); + if (dbgfs_bf->mask & MVM_DEBUGFS_BF_DEBUG_FLAG) + cmd->bf_debug_flag = cpu_to_le32(dbgfs_bf->bf_debug_flag); + if (dbgfs_bf->mask & MVM_DEBUGFS_BF_ESCAPE_TIMER) + cmd->bf_escape_timer = cpu_to_le32(dbgfs_bf->bf_escape_timer); + if (dbgfs_bf->mask & MVM_DEBUGFS_BA_ESCAPE_TIMER) + cmd->ba_escape_timer = cpu_to_le32(dbgfs_bf->ba_escape_timer); + if (dbgfs_bf->mask & MVM_DEBUGFS_BA_ENABLE_BEACON_ABORT) + cmd->ba_enable_beacon_abort = + cpu_to_le32(dbgfs_bf->ba_enable_beacon_abort); +} +#endif + +static int _iwl_mvm_enable_beacon_filter(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_beacon_filter_cmd *cmd, + u32 cmd_flags) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + if (mvmvif != mvm->bf_allowed_vif || !vif->bss_conf.dtim_period || + vif->type != NL80211_IFTYPE_STATION || vif->p2p) + return 0; + + iwl_mvm_beacon_filter_set_cqm_params(mvm, vif, cmd); + iwl_mvm_beacon_filter_debugfs_parameters(vif, cmd); + ret = iwl_mvm_beacon_filter_send_cmd(mvm, cmd, cmd_flags); + + if (!ret) + mvmvif->bf_data.bf_enabled = true; + + return ret; +} + +int iwl_mvm_enable_beacon_filter(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 flags) +{ + struct iwl_beacon_filter_cmd cmd = { + IWL_BF_CMD_CONFIG_DEFAULTS, + .bf_enable_beacon_filter = cpu_to_le32(1), + }; + + return _iwl_mvm_enable_beacon_filter(mvm, vif, &cmd, flags); +} + +static int _iwl_mvm_disable_beacon_filter(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 flags) +{ + struct iwl_beacon_filter_cmd cmd = {}; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + if (vif->type != NL80211_IFTYPE_STATION || vif->p2p) + return 0; + + ret = iwl_mvm_beacon_filter_send_cmd(mvm, &cmd, flags); + + if (!ret) + mvmvif->bf_data.bf_enabled = false; + + return ret; +} + +int iwl_mvm_disable_beacon_filter(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 flags) +{ + return _iwl_mvm_disable_beacon_filter(mvm, vif, flags); +} + +static int iwl_mvm_power_set_ps(struct iwl_mvm *mvm) +{ + bool disable_ps; + int ret; + + /* disable PS if CAM */ + disable_ps = (iwlmvm_mod_params.power_scheme == IWL_POWER_SCHEME_CAM); + /* ...or if any of the vifs require PS to be off */ + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_power_ps_disabled_iterator, + &disable_ps); + + /* update device power state if it has changed */ + if (mvm->ps_disabled != disable_ps) { + bool old_ps_disabled = mvm->ps_disabled; + + mvm->ps_disabled = disable_ps; + ret = iwl_mvm_power_update_device(mvm); + if (ret) { + mvm->ps_disabled = old_ps_disabled; + return ret; + } + } + + return 0; +} + +static int iwl_mvm_power_set_ba(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_beacon_filter_cmd cmd = { + IWL_BF_CMD_CONFIG_DEFAULTS, + .bf_enable_beacon_filter = cpu_to_le32(1), + }; + + if (!mvmvif->bf_data.bf_enabled) + return 0; + + if (test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status)) + cmd.ba_escape_timer = cpu_to_le32(IWL_BA_ESCAPE_TIMER_D3); + + mvmvif->bf_data.ba_enabled = !(!mvmvif->pm_enabled || + mvm->ps_disabled || + !vif->cfg.ps || + iwl_mvm_vif_low_latency(mvmvif)); + + return _iwl_mvm_enable_beacon_filter(mvm, vif, &cmd, 0); +} + +int iwl_mvm_power_update_ps(struct iwl_mvm *mvm) +{ + struct iwl_power_vifs vifs = { + .mvm = mvm, + }; + int ret; + + lockdep_assert_held(&mvm->mutex); + + /* get vifs info */ + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_power_get_vifs_iterator, &vifs); + + ret = iwl_mvm_power_set_ps(mvm); + if (ret) + return ret; + + if (vifs.bss_vif) + return iwl_mvm_power_set_ba(mvm, vifs.bss_vif); + + return 0; +} + +int iwl_mvm_power_update_mac(struct iwl_mvm *mvm) +{ + struct iwl_power_vifs vifs = { + .mvm = mvm, + }; + int ret; + + lockdep_assert_held(&mvm->mutex); + + /* get vifs info */ + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_power_get_vifs_iterator, &vifs); + + iwl_mvm_power_set_pm(mvm, &vifs); + + ret = iwl_mvm_power_set_ps(mvm); + if (ret) + return ret; + + if (vifs.bss_vif) { + ret = iwl_mvm_power_send_cmd(mvm, vifs.bss_vif); + if (ret) + return ret; + } + + if (vifs.p2p_vif) { + ret = iwl_mvm_power_send_cmd(mvm, vifs.p2p_vif); + if (ret) + return ret; + } + + if (vifs.bss_vif) + return iwl_mvm_power_set_ba(mvm, vifs.bss_vif); + + return 0; +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/quota.c b/drivers/net/wireless/intel/iwlwifi/mvm/quota.c new file mode 100644 index 000000000..cea1a34f9 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/quota.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018, 2021-2022 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include <net/mac80211.h> +#include "fw-api.h" +#include "mvm.h" + +#define QUOTA_100 IWL_MVM_MAX_QUOTA +#define QUOTA_LOWLAT_MIN ((QUOTA_100 * IWL_MVM_LOWLAT_QUOTA_MIN_PERCENT) / 100) + +struct iwl_mvm_quota_iterator_data { + int n_interfaces[MAX_BINDINGS]; + int colors[MAX_BINDINGS]; + int low_latency[MAX_BINDINGS]; +#ifdef CONFIG_IWLWIFI_DEBUGFS + int dbgfs_min[MAX_BINDINGS]; +#endif + int n_low_latency_bindings; + struct ieee80211_vif *disabled_vif; +}; + +static void iwl_mvm_quota_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_quota_iterator_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u16 id; + + /* skip disabled interfaces here immediately */ + if (vif == data->disabled_vif) + return; + + if (!mvmvif->phy_ctxt) + return; + + /* currently, PHY ID == binding ID */ + id = mvmvif->phy_ctxt->id; + + /* need at least one binding per PHY */ + BUILD_BUG_ON(NUM_PHY_CTX > MAX_BINDINGS); + + if (WARN_ON_ONCE(id >= MAX_BINDINGS)) + return; + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + if (vif->cfg.assoc) + break; + return; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_ADHOC: + if (mvmvif->ap_ibss_active) + break; + return; + case NL80211_IFTYPE_MONITOR: + if (mvmvif->monitor_active) + break; + return; + case NL80211_IFTYPE_P2P_DEVICE: + return; + default: + WARN_ON_ONCE(1); + return; + } + + if (data->colors[id] < 0) + data->colors[id] = mvmvif->phy_ctxt->color; + else + WARN_ON_ONCE(data->colors[id] != mvmvif->phy_ctxt->color); + + data->n_interfaces[id]++; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (mvmvif->dbgfs_quota_min) + data->dbgfs_min[id] = max(data->dbgfs_min[id], + mvmvif->dbgfs_quota_min); +#endif + + if (iwl_mvm_vif_low_latency(mvmvif) && !data->low_latency[id]) { + data->n_low_latency_bindings++; + data->low_latency[id] = true; + } +} + +static void iwl_mvm_adjust_quota_for_noa(struct iwl_mvm *mvm, + struct iwl_time_quota_cmd *cmd) +{ +#ifdef CONFIG_NL80211_TESTMODE + struct iwl_mvm_vif *mvmvif; + int i, phy_id = -1, beacon_int = 0; + + if (!mvm->noa_duration || !mvm->noa_vif) + return; + + mvmvif = iwl_mvm_vif_from_mac80211(mvm->noa_vif); + if (!mvmvif->ap_ibss_active) + return; + + phy_id = mvmvif->phy_ctxt->id; + beacon_int = mvm->noa_vif->bss_conf.beacon_int; + + for (i = 0; i < MAX_BINDINGS; i++) { + struct iwl_time_quota_data *data = + iwl_mvm_quota_cmd_get_quota(mvm, cmd, + i); + u32 id_n_c = le32_to_cpu(data->id_and_color); + u32 id = (id_n_c & FW_CTXT_ID_MSK) >> FW_CTXT_ID_POS; + u32 quota = le32_to_cpu(data->quota); + + if (id != phy_id) + continue; + + quota *= (beacon_int - mvm->noa_duration); + quota /= beacon_int; + + IWL_DEBUG_QUOTA(mvm, "quota: adjust for NoA from %d to %d\n", + le32_to_cpu(data->quota), quota); + + data->quota = cpu_to_le32(quota); + } +#endif +} + +int iwl_mvm_update_quotas(struct iwl_mvm *mvm, + bool force_update, + struct ieee80211_vif *disabled_vif) +{ + struct iwl_time_quota_cmd cmd = {}; + int i, idx, err, num_active_macs, quota, quota_rem, n_non_lowlat; + struct iwl_mvm_quota_iterator_data data = { + .n_interfaces = {}, + .colors = { -1, -1, -1, -1 }, + .disabled_vif = disabled_vif, + }; + struct iwl_time_quota_cmd *last = &mvm->last_quota_cmd; + struct iwl_time_quota_data *qdata, *last_data; + bool send = false; + + lockdep_assert_held(&mvm->mutex); + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DYNAMIC_QUOTA)) + return 0; + + /* update all upon completion */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) + return 0; + + /* iterator data above must match */ + BUILD_BUG_ON(MAX_BINDINGS != 4); + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_quota_iterator, &data); + + /* + * The FW's scheduling session consists of + * IWL_MVM_MAX_QUOTA fragments. Divide these fragments + * equally between all the bindings that require quota + */ + num_active_macs = 0; + for (i = 0; i < MAX_BINDINGS; i++) { + qdata = iwl_mvm_quota_cmd_get_quota(mvm, &cmd, i); + qdata->id_and_color = cpu_to_le32(FW_CTXT_INVALID); + num_active_macs += data.n_interfaces[i]; + } + + n_non_lowlat = num_active_macs; + + if (data.n_low_latency_bindings == 1) { + for (i = 0; i < MAX_BINDINGS; i++) { + if (data.low_latency[i]) { + n_non_lowlat -= data.n_interfaces[i]; + break; + } + } + } + + if (data.n_low_latency_bindings == 1 && n_non_lowlat) { + /* + * Reserve quota for the low latency binding in case that + * there are several data bindings but only a single + * low latency one. Split the rest of the quota equally + * between the other data interfaces. + */ + quota = (QUOTA_100 - QUOTA_LOWLAT_MIN) / n_non_lowlat; + quota_rem = QUOTA_100 - n_non_lowlat * quota - + QUOTA_LOWLAT_MIN; + IWL_DEBUG_QUOTA(mvm, + "quota: low-latency binding active, remaining quota per other binding: %d\n", + quota); + } else if (num_active_macs) { + /* + * There are 0 or more than 1 low latency bindings, or all the + * data interfaces belong to the single low latency binding. + * Split the quota equally between the data interfaces. + */ + quota = QUOTA_100 / num_active_macs; + quota_rem = QUOTA_100 % num_active_macs; + IWL_DEBUG_QUOTA(mvm, + "quota: splitting evenly per binding: %d\n", + quota); + } else { + /* values don't really matter - won't be used */ + quota = 0; + quota_rem = 0; + } + + for (idx = 0, i = 0; i < MAX_BINDINGS; i++) { + if (data.colors[i] < 0) + continue; + + qdata = iwl_mvm_quota_cmd_get_quota(mvm, &cmd, idx); + + qdata->id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(i, data.colors[i])); + + if (data.n_interfaces[i] <= 0) + qdata->quota = cpu_to_le32(0); +#ifdef CONFIG_IWLWIFI_DEBUGFS + else if (data.dbgfs_min[i]) + qdata->quota = + cpu_to_le32(data.dbgfs_min[i] * QUOTA_100 / 100); +#endif + else if (data.n_low_latency_bindings == 1 && n_non_lowlat && + data.low_latency[i]) + /* + * There is more than one binding, but only one of the + * bindings is in low latency. For this case, allocate + * the minimal required quota for the low latency + * binding. + */ + qdata->quota = cpu_to_le32(QUOTA_LOWLAT_MIN); + else + qdata->quota = + cpu_to_le32(quota * data.n_interfaces[i]); + + WARN_ONCE(le32_to_cpu(qdata->quota) > QUOTA_100, + "Binding=%d, quota=%u > max=%u\n", + idx, le32_to_cpu(qdata->quota), QUOTA_100); + + qdata->max_duration = cpu_to_le32(0); + + idx++; + } + + /* Give the remainder of the session to the first data binding */ + for (i = 0; i < MAX_BINDINGS; i++) { + qdata = iwl_mvm_quota_cmd_get_quota(mvm, &cmd, i); + if (le32_to_cpu(qdata->quota) != 0) { + le32_add_cpu(&qdata->quota, quota_rem); + IWL_DEBUG_QUOTA(mvm, + "quota: giving remainder of %d to binding %d\n", + quota_rem, i); + break; + } + } + + iwl_mvm_adjust_quota_for_noa(mvm, &cmd); + + /* check that we have non-zero quota for all valid bindings */ + for (i = 0; i < MAX_BINDINGS; i++) { + qdata = iwl_mvm_quota_cmd_get_quota(mvm, &cmd, i); + last_data = iwl_mvm_quota_cmd_get_quota(mvm, last, i); + if (qdata->id_and_color != last_data->id_and_color) + send = true; + if (qdata->max_duration != last_data->max_duration) + send = true; + if (abs((int)le32_to_cpu(qdata->quota) - + (int)le32_to_cpu(last_data->quota)) + > IWL_MVM_QUOTA_THRESHOLD) + send = true; + if (qdata->id_and_color == cpu_to_le32(FW_CTXT_INVALID)) + continue; + WARN_ONCE(qdata->quota == 0, + "zero quota on binding %d\n", i); + } + + if (!send && !force_update) { + /* don't send a practically unchanged command, the firmware has + * to re-initialize a lot of state and that can have an adverse + * impact on it + */ + return 0; + } + + err = iwl_mvm_send_cmd_pdu(mvm, TIME_QUOTA_CMD, 0, + iwl_mvm_quota_cmd_size(mvm), &cmd); + + if (err) + IWL_ERR(mvm, "Failed to send quota: %d\n", err); + else + mvm->last_quota_cmd = cmd; + return err; +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rfi.c b/drivers/net/wireless/intel/iwlwifi/mvm/rfi.c new file mode 100644 index 000000000..bb77bc9aa --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/rfi.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2020 - 2021 Intel Corporation + */ + +#include "mvm.h" +#include "fw/api/commands.h" +#include "fw/api/phy-ctxt.h" + +/* + * DDR needs frequency in units of 16.666MHz, so provide FW with the + * frequency values in the adjusted format. + */ +static const struct iwl_rfi_lut_entry iwl_rfi_table[IWL_RFI_LUT_SIZE] = { + /* frequency 2667MHz */ + {cpu_to_le16(160), {50, 58, 60, 62, 64, 52, 54, 56}, + {PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, + PHY_BAND_5, PHY_BAND_5, PHY_BAND_5,}}, + + /* frequency 2933MHz */ + {cpu_to_le16(176), {149, 151, 153, 157, 159, 161, 165, 163, 167, 169, + 171, 173, 175}, + {PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, + PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, + PHY_BAND_5, PHY_BAND_5, PHY_BAND_5,}}, + + /* frequency 3200MHz */ + {cpu_to_le16(192), {79, 81, 83, 85, 87, 89, 91, 93}, + {PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, + PHY_BAND_6, PHY_BAND_6, PHY_BAND_6,}}, + + /* frequency 3733MHz */ + {cpu_to_le16(223), {114, 116, 118, 120, 122, 106, 110, 124, 126}, + {PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, + PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5,}}, + + /* frequency 4000MHz */ + {cpu_to_le16(240), {114, 151, 155, 157, 159, 161, 165}, + {PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, + PHY_BAND_5, PHY_BAND_5,}}, + + /* frequency 4267MHz */ + {cpu_to_le16(256), {79, 83, 85, 87, 89, 91, 93,}, + {PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, + PHY_BAND_6, PHY_BAND_6,}}, + + /* frequency 4400MHz */ + {cpu_to_le16(264), {111, 119, 123, 125, 129, 131, 133, 135, 143,}, + {PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, + PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6,}}, + + /* frequency 5200MHz */ + {cpu_to_le16(312), {36, 38, 40, 42, 44, 46, 50,}, + {PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, + PHY_BAND_5, PHY_BAND_5,}}, + + /* frequency 5600MHz */ + {cpu_to_le16(336), {106, 110, 112, 114, 116, 118, 120, 122}, + {PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, PHY_BAND_5, + PHY_BAND_5, PHY_BAND_5, PHY_BAND_5,}}, + + /* frequency 6000MHz */ + {cpu_to_le16(360), {3, 5, 7, 9, 11, 13, 15,}, + {PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, + PHY_BAND_6, PHY_BAND_6,}}, + + /* frequency 6400MHz */ + {cpu_to_le16(384), {79, 83, 85, 87, 89, 91, 93,}, + {PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, PHY_BAND_6, + PHY_BAND_6, PHY_BAND_6,}}, +}; + +int iwl_rfi_send_config_cmd(struct iwl_mvm *mvm, struct iwl_rfi_lut_entry *rfi_table) +{ + int ret; + struct iwl_rfi_config_cmd cmd; + struct iwl_host_cmd hcmd = { + .id = WIDE_ID(SYSTEM_GROUP, RFI_CONFIG_CMD), + .dataflags[0] = IWL_HCMD_DFL_DUP, + .data[0] = &cmd, + .len[0] = sizeof(cmd), + }; + + if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_RFIM_SUPPORT)) + return -EOPNOTSUPP; + + lockdep_assert_held(&mvm->mutex); + + /* in case no table is passed, use the default one */ + if (!rfi_table) { + memcpy(cmd.table, iwl_rfi_table, sizeof(cmd.table)); + } else { + memcpy(cmd.table, rfi_table, sizeof(cmd.table)); + /* notify FW the table is not the default one */ + cmd.oem = 1; + } + + ret = iwl_mvm_send_cmd(mvm, &hcmd); + + if (ret) + IWL_ERR(mvm, "Failed to send RFI config cmd %d\n", ret); + + return ret; +} + +struct iwl_rfi_freq_table_resp_cmd *iwl_rfi_get_freq_table(struct iwl_mvm *mvm) +{ + struct iwl_rfi_freq_table_resp_cmd *resp; + int resp_size = sizeof(*resp); + int ret; + struct iwl_host_cmd cmd = { + .id = WIDE_ID(SYSTEM_GROUP, RFI_GET_FREQ_TABLE_CMD), + .flags = CMD_WANT_SKB, + }; + + if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_RFIM_SUPPORT)) + return ERR_PTR(-EOPNOTSUPP); + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_send_cmd(mvm, &cmd); + mutex_unlock(&mvm->mutex); + if (ret) + return ERR_PTR(ret); + + if (WARN_ON_ONCE(iwl_rx_packet_payload_len(cmd.resp_pkt) != resp_size)) + return ERR_PTR(-EIO); + + resp = kmemdup(cmd.resp_pkt->data, resp_size, GFP_KERNEL); + if (!resp) + return ERR_PTR(-ENOMEM); + + iwl_free_resp(&cmd); + return resp; +} + +void iwl_rfi_deactivate_notif_handler(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_rfi_deactivate_notif *notif = (void *)pkt->data; + + IWL_INFO(mvm, "RFIm is deactivated, reason = %d\n", notif->reason); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rs-fw.c b/drivers/net/wireless/intel/iwlwifi/mvm/rs-fw.c new file mode 100644 index 000000000..2e9081cb6 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/rs-fw.c @@ -0,0 +1,533 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2018-2022 Intel Corporation + */ +#include "rs.h" +#include "fw-api.h" +#include "sta.h" +#include "iwl-op-mode.h" +#include "mvm.h" + +static u8 rs_fw_bw_from_sta_bw(struct ieee80211_sta *sta) +{ + switch (sta->deflink.bandwidth) { + case IEEE80211_STA_RX_BW_160: + return IWL_TLC_MNG_CH_WIDTH_160MHZ; + case IEEE80211_STA_RX_BW_80: + return IWL_TLC_MNG_CH_WIDTH_80MHZ; + case IEEE80211_STA_RX_BW_40: + return IWL_TLC_MNG_CH_WIDTH_40MHZ; + case IEEE80211_STA_RX_BW_20: + default: + return IWL_TLC_MNG_CH_WIDTH_20MHZ; + } +} + +static u8 rs_fw_set_active_chains(u8 chains) +{ + u8 fw_chains = 0; + + if (chains & ANT_A) + fw_chains |= IWL_TLC_MNG_CHAIN_A_MSK; + if (chains & ANT_B) + fw_chains |= IWL_TLC_MNG_CHAIN_B_MSK; + + return fw_chains; +} + +static u8 rs_fw_sgi_cw_support(struct ieee80211_sta *sta) +{ + struct ieee80211_sta_ht_cap *ht_cap = &sta->deflink.ht_cap; + struct ieee80211_sta_vht_cap *vht_cap = &sta->deflink.vht_cap; + struct ieee80211_sta_he_cap *he_cap = &sta->deflink.he_cap; + u8 supp = 0; + + if (he_cap->has_he) + return 0; + + if (ht_cap->cap & IEEE80211_HT_CAP_SGI_20) + supp |= BIT(IWL_TLC_MNG_CH_WIDTH_20MHZ); + if (ht_cap->cap & IEEE80211_HT_CAP_SGI_40) + supp |= BIT(IWL_TLC_MNG_CH_WIDTH_40MHZ); + if (vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_80) + supp |= BIT(IWL_TLC_MNG_CH_WIDTH_80MHZ); + if (vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_160) + supp |= BIT(IWL_TLC_MNG_CH_WIDTH_160MHZ); + + return supp; +} + +static u16 rs_fw_get_config_flags(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct ieee80211_supported_band *sband) +{ + struct ieee80211_sta_ht_cap *ht_cap = &sta->deflink.ht_cap; + struct ieee80211_sta_vht_cap *vht_cap = &sta->deflink.vht_cap; + struct ieee80211_sta_he_cap *he_cap = &sta->deflink.he_cap; + bool vht_ena = vht_cap->vht_supported; + u16 flags = 0; + + /* get STBC flags */ + if (mvm->cfg->ht_params->stbc && + (num_of_ant(iwl_mvm_get_valid_tx_ant(mvm)) > 1)) { + if (he_cap->has_he && he_cap->he_cap_elem.phy_cap_info[2] & + IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ) + flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK; + else if (vht_cap->cap & IEEE80211_VHT_CAP_RXSTBC_MASK) + flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK; + else if (ht_cap->cap & IEEE80211_HT_CAP_RX_STBC) + flags |= IWL_TLC_MNG_CFG_FLAGS_STBC_MSK; + } + + if (mvm->cfg->ht_params->ldpc && + ((ht_cap->cap & IEEE80211_HT_CAP_LDPC_CODING) || + (vht_ena && (vht_cap->cap & IEEE80211_VHT_CAP_RXLDPC)))) + flags |= IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK; + + /* consider LDPC support in case of HE */ + if (he_cap->has_he && (he_cap->he_cap_elem.phy_cap_info[1] & + IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD)) + flags |= IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK; + + if (sband->iftype_data && sband->iftype_data->he_cap.has_he && + !(sband->iftype_data->he_cap.he_cap_elem.phy_cap_info[1] & + IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD)) + flags &= ~IWL_TLC_MNG_CFG_FLAGS_LDPC_MSK; + + if (he_cap->has_he && + (he_cap->he_cap_elem.phy_cap_info[3] & + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_MASK && + sband->iftype_data && + sband->iftype_data->he_cap.he_cap_elem.phy_cap_info[3] & + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_MASK)) + flags |= IWL_TLC_MNG_CFG_FLAGS_HE_DCM_NSS_1_MSK; + + return flags; +} + +static +int rs_fw_vht_highest_rx_mcs_index(const struct ieee80211_sta_vht_cap *vht_cap, + int nss) +{ + u16 rx_mcs = le16_to_cpu(vht_cap->vht_mcs.rx_mcs_map) & + (0x3 << (2 * (nss - 1))); + rx_mcs >>= (2 * (nss - 1)); + + switch (rx_mcs) { + case IEEE80211_VHT_MCS_SUPPORT_0_7: + return IWL_TLC_MNG_HT_RATE_MCS7; + case IEEE80211_VHT_MCS_SUPPORT_0_8: + return IWL_TLC_MNG_HT_RATE_MCS8; + case IEEE80211_VHT_MCS_SUPPORT_0_9: + return IWL_TLC_MNG_HT_RATE_MCS9; + default: + WARN_ON_ONCE(1); + break; + } + + return 0; +} + +static void +rs_fw_vht_set_enabled_rates(const struct ieee80211_sta *sta, + const struct ieee80211_sta_vht_cap *vht_cap, + struct iwl_tlc_config_cmd_v4 *cmd) +{ + u16 supp; + int i, highest_mcs; + u8 max_nss = sta->deflink.rx_nss; + struct ieee80211_vht_cap ieee_vht_cap = { + .vht_cap_info = cpu_to_le32(vht_cap->cap), + .supp_mcs = vht_cap->vht_mcs, + }; + + /* the station support only a single receive chain */ + if (sta->deflink.smps_mode == IEEE80211_SMPS_STATIC) + max_nss = 1; + + for (i = 0; i < max_nss && i < IWL_TLC_NSS_MAX; i++) { + int nss = i + 1; + + highest_mcs = rs_fw_vht_highest_rx_mcs_index(vht_cap, nss); + if (!highest_mcs) + continue; + + supp = BIT(highest_mcs + 1) - 1; + if (sta->deflink.bandwidth == IEEE80211_STA_RX_BW_20) + supp &= ~BIT(IWL_TLC_MNG_HT_RATE_MCS9); + + cmd->ht_rates[i][IWL_TLC_MCS_PER_BW_80] = cpu_to_le16(supp); + /* + * Check if VHT extended NSS indicates that the bandwidth/NSS + * configuration is supported - only for MCS 0 since we already + * decoded the MCS bits anyway ourselves. + */ + if (sta->deflink.bandwidth == IEEE80211_STA_RX_BW_160 && + ieee80211_get_vht_max_nss(&ieee_vht_cap, + IEEE80211_VHT_CHANWIDTH_160MHZ, + 0, true, nss) >= nss) + cmd->ht_rates[i][IWL_TLC_MCS_PER_BW_160] = + cmd->ht_rates[i][IWL_TLC_MCS_PER_BW_80]; + } +} + +static u16 rs_fw_he_ieee80211_mcs_to_rs_mcs(u16 mcs) +{ + switch (mcs) { + case IEEE80211_HE_MCS_SUPPORT_0_7: + return BIT(IWL_TLC_MNG_HT_RATE_MCS7 + 1) - 1; + case IEEE80211_HE_MCS_SUPPORT_0_9: + return BIT(IWL_TLC_MNG_HT_RATE_MCS9 + 1) - 1; + case IEEE80211_HE_MCS_SUPPORT_0_11: + return BIT(IWL_TLC_MNG_HT_RATE_MCS11 + 1) - 1; + case IEEE80211_HE_MCS_NOT_SUPPORTED: + return 0; + } + + WARN(1, "invalid HE MCS %d\n", mcs); + return 0; +} + +static void +rs_fw_he_set_enabled_rates(const struct ieee80211_sta *sta, + struct ieee80211_supported_band *sband, + struct iwl_tlc_config_cmd_v4 *cmd) +{ + const struct ieee80211_sta_he_cap *he_cap = &sta->deflink.he_cap; + u16 mcs_160 = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_160); + u16 mcs_80 = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_80); + u16 tx_mcs_80 = + le16_to_cpu(sband->iftype_data->he_cap.he_mcs_nss_supp.tx_mcs_80); + u16 tx_mcs_160 = + le16_to_cpu(sband->iftype_data->he_cap.he_mcs_nss_supp.tx_mcs_160); + int i; + u8 nss = sta->deflink.rx_nss; + + /* the station support only a single receive chain */ + if (sta->deflink.smps_mode == IEEE80211_SMPS_STATIC) + nss = 1; + + for (i = 0; i < nss && i < IWL_TLC_NSS_MAX; i++) { + u16 _mcs_160 = (mcs_160 >> (2 * i)) & 0x3; + u16 _mcs_80 = (mcs_80 >> (2 * i)) & 0x3; + u16 _tx_mcs_160 = (tx_mcs_160 >> (2 * i)) & 0x3; + u16 _tx_mcs_80 = (tx_mcs_80 >> (2 * i)) & 0x3; + + /* If one side doesn't support - mark both as not supporting */ + if (_mcs_80 == IEEE80211_HE_MCS_NOT_SUPPORTED || + _tx_mcs_80 == IEEE80211_HE_MCS_NOT_SUPPORTED) { + _mcs_80 = IEEE80211_HE_MCS_NOT_SUPPORTED; + _tx_mcs_80 = IEEE80211_HE_MCS_NOT_SUPPORTED; + } + if (_mcs_80 > _tx_mcs_80) + _mcs_80 = _tx_mcs_80; + cmd->ht_rates[i][IWL_TLC_MCS_PER_BW_80] = + cpu_to_le16(rs_fw_he_ieee80211_mcs_to_rs_mcs(_mcs_80)); + + /* If one side doesn't support - mark both as not supporting */ + if (_mcs_160 == IEEE80211_HE_MCS_NOT_SUPPORTED || + _tx_mcs_160 == IEEE80211_HE_MCS_NOT_SUPPORTED) { + _mcs_160 = IEEE80211_HE_MCS_NOT_SUPPORTED; + _tx_mcs_160 = IEEE80211_HE_MCS_NOT_SUPPORTED; + } + if (_mcs_160 > _tx_mcs_160) + _mcs_160 = _tx_mcs_160; + cmd->ht_rates[i][IWL_TLC_MCS_PER_BW_160] = + cpu_to_le16(rs_fw_he_ieee80211_mcs_to_rs_mcs(_mcs_160)); + } +} + +static void rs_fw_set_supp_rates(struct ieee80211_sta *sta, + struct ieee80211_supported_band *sband, + struct iwl_tlc_config_cmd_v4 *cmd) +{ + int i; + u16 supp = 0; + unsigned long tmp; /* must be unsigned long for for_each_set_bit */ + const struct ieee80211_sta_ht_cap *ht_cap = &sta->deflink.ht_cap; + const struct ieee80211_sta_vht_cap *vht_cap = &sta->deflink.vht_cap; + const struct ieee80211_sta_he_cap *he_cap = &sta->deflink.he_cap; + + /* non HT rates */ + tmp = sta->deflink.supp_rates[sband->band]; + for_each_set_bit(i, &tmp, BITS_PER_LONG) + supp |= BIT(sband->bitrates[i].hw_value); + + cmd->non_ht_rates = cpu_to_le16(supp); + cmd->mode = IWL_TLC_MNG_MODE_NON_HT; + + /* HT/VHT rates */ + if (he_cap->has_he) { + cmd->mode = IWL_TLC_MNG_MODE_HE; + rs_fw_he_set_enabled_rates(sta, sband, cmd); + } else if (vht_cap->vht_supported) { + cmd->mode = IWL_TLC_MNG_MODE_VHT; + rs_fw_vht_set_enabled_rates(sta, vht_cap, cmd); + } else if (ht_cap->ht_supported) { + cmd->mode = IWL_TLC_MNG_MODE_HT; + cmd->ht_rates[IWL_TLC_NSS_1][IWL_TLC_MCS_PER_BW_80] = + cpu_to_le16(ht_cap->mcs.rx_mask[0]); + + /* the station support only a single receive chain */ + if (sta->deflink.smps_mode == IEEE80211_SMPS_STATIC) + cmd->ht_rates[IWL_TLC_NSS_2][IWL_TLC_MCS_PER_BW_80] = + 0; + else + cmd->ht_rates[IWL_TLC_NSS_2][IWL_TLC_MCS_PER_BW_80] = + cpu_to_le16(ht_cap->mcs.rx_mask[1]); + } +} + +void iwl_mvm_tlc_update_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_tlc_update_notif *notif; + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + struct iwl_lq_sta_rs_fw *lq_sta; + u32 flags; + + rcu_read_lock(); + + notif = (void *)pkt->data; + sta = rcu_dereference(mvm->fw_id_to_mac_id[notif->sta_id]); + if (IS_ERR_OR_NULL(sta)) { + /* can happen in remove station flow where mvm removed internally + * the station before removing from FW + */ + IWL_DEBUG_RATE(mvm, + "Invalid mvm RCU pointer for sta id (%d) in TLC notification\n", + notif->sta_id); + goto out; + } + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + if (!mvmsta) { + IWL_ERR(mvm, "Invalid sta id (%d) in FW TLC notification\n", + notif->sta_id); + goto out; + } + + flags = le32_to_cpu(notif->flags); + + lq_sta = &mvmsta->lq_sta.rs_fw; + + if (flags & IWL_TLC_NOTIF_FLAG_RATE) { + char pretty_rate[100]; + + if (iwl_fw_lookup_notif_ver(mvm->fw, DATA_PATH_GROUP, + TLC_MNG_UPDATE_NOTIF, 0) < 3) { + rs_pretty_print_rate_v1(pretty_rate, + sizeof(pretty_rate), + le32_to_cpu(notif->rate)); + IWL_DEBUG_RATE(mvm, + "Got rate in old format. Rate: %s. Converting.\n", + pretty_rate); + lq_sta->last_rate_n_flags = + iwl_new_rate_from_v1(le32_to_cpu(notif->rate)); + } else { + lq_sta->last_rate_n_flags = le32_to_cpu(notif->rate); + } + rs_pretty_print_rate(pretty_rate, sizeof(pretty_rate), + lq_sta->last_rate_n_flags); + IWL_DEBUG_RATE(mvm, "new rate: %s\n", pretty_rate); + } + + if (flags & IWL_TLC_NOTIF_FLAG_AMSDU && !mvmsta->orig_amsdu_len) { + u16 size = le32_to_cpu(notif->amsdu_size); + int i; + + if (sta->deflink.agg.max_amsdu_len < size) { + /* + * In debug sta->deflink.agg.max_amsdu_len < size + * so also check with orig_amsdu_len which holds the + * original data before debugfs changed the value + */ + WARN_ON(mvmsta->orig_amsdu_len < size); + goto out; + } + + mvmsta->amsdu_enabled = le32_to_cpu(notif->amsdu_enabled); + mvmsta->max_amsdu_len = size; + sta->deflink.agg.max_rc_amsdu_len = mvmsta->max_amsdu_len; + + for (i = 0; i < IWL_MAX_TID_COUNT; i++) { + if (mvmsta->amsdu_enabled & BIT(i)) + sta->deflink.agg.max_tid_amsdu_len[i] = + iwl_mvm_max_amsdu_size(mvm, sta, i); + else + /* + * Not so elegant, but this will effectively + * prevent AMSDU on this TID + */ + sta->deflink.agg.max_tid_amsdu_len[i] = 1; + } + + IWL_DEBUG_RATE(mvm, + "AMSDU update. AMSDU size: %d, AMSDU selected size: %d, AMSDU TID bitmap 0x%X\n", + le32_to_cpu(notif->amsdu_size), size, + mvmsta->amsdu_enabled); + } +out: + rcu_read_unlock(); +} + +u16 rs_fw_get_max_amsdu_len(struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + const struct ieee80211_sta_vht_cap *vht_cap = &sta->deflink.vht_cap; + const struct ieee80211_sta_ht_cap *ht_cap = &sta->deflink.ht_cap; + + if (mvmsta->vif->bss_conf.chandef.chan->band == NL80211_BAND_6GHZ) { + switch (le16_get_bits(sta->deflink.he_6ghz_capa.capa, + IEEE80211_HE_6GHZ_CAP_MAX_MPDU_LEN)) { + case IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454: + return IEEE80211_MAX_MPDU_LEN_VHT_11454; + case IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_7991: + return IEEE80211_MAX_MPDU_LEN_VHT_7991; + default: + return IEEE80211_MAX_MPDU_LEN_VHT_3895; + } + } else + if (vht_cap->vht_supported) { + switch (vht_cap->cap & IEEE80211_VHT_CAP_MAX_MPDU_MASK) { + case IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454: + return IEEE80211_MAX_MPDU_LEN_VHT_11454; + case IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_7991: + return IEEE80211_MAX_MPDU_LEN_VHT_7991; + default: + return IEEE80211_MAX_MPDU_LEN_VHT_3895; + } + } else if (ht_cap->ht_supported) { + if (ht_cap->cap & IEEE80211_HT_CAP_MAX_AMSDU) + /* + * agg is offloaded so we need to assume that agg + * are enabled and max mpdu in ampdu is 4095 + * (spec 802.11-2016 9.3.2.1) + */ + return IEEE80211_MAX_MPDU_LEN_HT_BA; + else + return IEEE80211_MAX_MPDU_LEN_HT_3839; + } + + /* in legacy mode no amsdu is enabled so return zero */ + return 0; +} + +void rs_fw_rate_init(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + enum nl80211_band band, bool update) +{ + struct ieee80211_hw *hw = mvm->hw; + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_lq_sta_rs_fw *lq_sta = &mvmsta->lq_sta.rs_fw; + u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, TLC_MNG_CONFIG_CMD); + struct ieee80211_supported_band *sband = hw->wiphy->bands[band]; + u16 max_amsdu_len = rs_fw_get_max_amsdu_len(sta); + struct iwl_tlc_config_cmd_v4 cfg_cmd = { + .sta_id = mvmsta->sta_id, + .max_ch_width = update ? + rs_fw_bw_from_sta_bw(sta) : RATE_MCS_CHAN_WIDTH_20, + .flags = cpu_to_le16(rs_fw_get_config_flags(mvm, sta, sband)), + .chains = rs_fw_set_active_chains(iwl_mvm_get_valid_tx_ant(mvm)), + .sgi_ch_width_supp = rs_fw_sgi_cw_support(sta), + .max_mpdu_len = iwl_mvm_is_csum_supported(mvm) ? + cpu_to_le16(max_amsdu_len) : 0, + }; + int ret; + int cmd_ver; + + memset(lq_sta, 0, offsetof(typeof(*lq_sta), pers)); + +#ifdef CONFIG_IWLWIFI_DEBUGFS + iwl_mvm_reset_frame_stats(mvm); +#endif + rs_fw_set_supp_rates(sta, sband, &cfg_cmd); + + /* + * since TLC offload works with one mode we can assume + * that only vht/ht is used and also set it as station max amsdu + */ + sta->deflink.agg.max_amsdu_len = max_amsdu_len; + + cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, + WIDE_ID(DATA_PATH_GROUP, + TLC_MNG_CONFIG_CMD), + 0); + IWL_DEBUG_RATE(mvm, "TLC CONFIG CMD, sta_id=%d, max_ch_width=%d, mode=%d\n", + cfg_cmd.sta_id, cfg_cmd.max_ch_width, cfg_cmd.mode); + IWL_DEBUG_RATE(mvm, "TLC CONFIG CMD, chains=0x%X, ch_wid_supp=%d, flags=0x%X\n", + cfg_cmd.chains, cfg_cmd.sgi_ch_width_supp, cfg_cmd.flags); + IWL_DEBUG_RATE(mvm, "TLC CONFIG CMD, mpdu_len=%d, no_ht_rate=0x%X, tx_op=%d\n", + cfg_cmd.max_mpdu_len, cfg_cmd.non_ht_rates, cfg_cmd.max_tx_op); + IWL_DEBUG_RATE(mvm, "TLC CONFIG CMD, ht_rate[0][0]=0x%X, ht_rate[1][0]=0x%X\n", + cfg_cmd.ht_rates[0][0], cfg_cmd.ht_rates[1][0]); + IWL_DEBUG_RATE(mvm, "TLC CONFIG CMD, ht_rate[0][1]=0x%X, ht_rate[1][1]=0x%X\n", + cfg_cmd.ht_rates[0][1], cfg_cmd.ht_rates[1][1]); + IWL_DEBUG_RATE(mvm, "TLC CONFIG CMD, ht_rate[0][2]=0x%X, ht_rate[1][2]=0x%X\n", + cfg_cmd.ht_rates[0][2], cfg_cmd.ht_rates[1][2]); + if (cmd_ver == 4) { + ret = iwl_mvm_send_cmd_pdu(mvm, cmd_id, CMD_ASYNC, + sizeof(cfg_cmd), &cfg_cmd); + } else if (cmd_ver < 4) { + struct iwl_tlc_config_cmd_v3 cfg_cmd_v3 = { + .sta_id = cfg_cmd.sta_id, + .max_ch_width = cfg_cmd.max_ch_width, + .mode = cfg_cmd.mode, + .chains = cfg_cmd.chains, + .amsdu = !!cfg_cmd.max_mpdu_len, + .flags = cfg_cmd.flags, + .non_ht_rates = cfg_cmd.non_ht_rates, + .ht_rates[0][0] = cfg_cmd.ht_rates[0][0], + .ht_rates[0][1] = cfg_cmd.ht_rates[0][1], + .ht_rates[1][0] = cfg_cmd.ht_rates[1][0], + .ht_rates[1][1] = cfg_cmd.ht_rates[1][1], + .sgi_ch_width_supp = cfg_cmd.sgi_ch_width_supp, + .max_mpdu_len = cfg_cmd.max_mpdu_len, + }; + + u16 cmd_size = sizeof(cfg_cmd_v3); + + /* In old versions of the API the struct is 4 bytes smaller */ + if (iwl_fw_lookup_cmd_ver(mvm->fw, + WIDE_ID(DATA_PATH_GROUP, + TLC_MNG_CONFIG_CMD), 0) < 3) + cmd_size -= 4; + + ret = iwl_mvm_send_cmd_pdu(mvm, cmd_id, CMD_ASYNC, cmd_size, + &cfg_cmd_v3); + } else { + ret = -EINVAL; + } + + if (ret) + IWL_ERR(mvm, "Failed to send rate scale config (%d)\n", ret); +} + +int rs_fw_tx_protection(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta, + bool enable) +{ + /* TODO: need to introduce a new FW cmd since LQ cmd is not relevant */ + IWL_DEBUG_RATE(mvm, "tx protection - not implemented yet.\n"); + return 0; +} + +void iwl_mvm_rs_add_sta(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta) +{ + struct iwl_lq_sta_rs_fw *lq_sta = &mvmsta->lq_sta.rs_fw; + + IWL_DEBUG_RATE(mvm, "create station rate scale window\n"); + + lq_sta->pers.drv = mvm; + lq_sta->pers.sta_id = mvmsta->sta_id; + lq_sta->pers.chains = 0; + memset(lq_sta->pers.chain_signal, 0, sizeof(lq_sta->pers.chain_signal)); + lq_sta->pers.last_rssi = S8_MIN; + lq_sta->last_rate_n_flags = 0; + +#ifdef CONFIG_MAC80211_DEBUGFS + lq_sta->pers.dbg_fixed_rate = 0; +#endif +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rs.c b/drivers/net/wireless/intel/iwlwifi/mvm/rs.c new file mode 100644 index 000000000..2be6801d4 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/rs.c @@ -0,0 +1,4163 @@ +// SPDX-License-Identifier: GPL-2.0-only +/****************************************************************************** + * + * Copyright(c) 2005 - 2014, 2018 - 2021 Intel Corporation. All rights reserved. + * Copyright(c) 2013 - 2015 Intel Mobile Communications GmbH + * Copyright(c) 2016 - 2017 Intel Deutschland GmbH + *****************************************************************************/ +#include <linux/kernel.h> +#include <linux/skbuff.h> +#include <linux/slab.h> +#include <net/mac80211.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/delay.h> + +#include <linux/workqueue.h> +#include "rs.h" +#include "fw-api.h" +#include "sta.h" +#include "iwl-op-mode.h" +#include "mvm.h" +#include "debugfs.h" + +#define IWL_RATE_MAX_WINDOW 62 /* # tx in history window */ + +/* Calculations of success ratio are done in fixed point where 12800 is 100%. + * Use this macro when dealing with thresholds consts set as a percentage + */ +#define RS_PERCENT(x) (128 * x) + +static u8 rs_ht_to_legacy[] = { + [IWL_RATE_MCS_0_INDEX] = IWL_RATE_6M_INDEX, + [IWL_RATE_MCS_1_INDEX] = IWL_RATE_9M_INDEX, + [IWL_RATE_MCS_2_INDEX] = IWL_RATE_12M_INDEX, + [IWL_RATE_MCS_3_INDEX] = IWL_RATE_18M_INDEX, + [IWL_RATE_MCS_4_INDEX] = IWL_RATE_24M_INDEX, + [IWL_RATE_MCS_5_INDEX] = IWL_RATE_36M_INDEX, + [IWL_RATE_MCS_6_INDEX] = IWL_RATE_48M_INDEX, + [IWL_RATE_MCS_7_INDEX] = IWL_RATE_54M_INDEX, + [IWL_RATE_MCS_8_INDEX] = IWL_RATE_54M_INDEX, + [IWL_RATE_MCS_9_INDEX] = IWL_RATE_54M_INDEX, +}; + +static const u8 ant_toggle_lookup[] = { + [ANT_NONE] = ANT_NONE, + [ANT_A] = ANT_B, + [ANT_B] = ANT_A, + [ANT_AB] = ANT_AB, +}; + +#define IWL_DECLARE_RATE_INFO(r, s, rp, rn) \ + [IWL_RATE_##r##M_INDEX] = { IWL_RATE_##r##M_PLCP, \ + IWL_RATE_HT_SISO_MCS_##s##_PLCP, \ + IWL_RATE_HT_MIMO2_MCS_##s##_PLCP, \ + IWL_RATE_VHT_SISO_MCS_##s##_PLCP, \ + IWL_RATE_VHT_MIMO2_MCS_##s##_PLCP,\ + IWL_RATE_##rp##M_INDEX, \ + IWL_RATE_##rn##M_INDEX } + +#define IWL_DECLARE_MCS_RATE(s) \ + [IWL_RATE_MCS_##s##_INDEX] = { IWL_RATE_INVM_PLCP, \ + IWL_RATE_HT_SISO_MCS_##s##_PLCP, \ + IWL_RATE_HT_MIMO2_MCS_##s##_PLCP, \ + IWL_RATE_VHT_SISO_MCS_##s##_PLCP, \ + IWL_RATE_VHT_MIMO2_MCS_##s##_PLCP, \ + IWL_RATE_INVM_INDEX, \ + IWL_RATE_INVM_INDEX } + +/* + * Parameter order: + * rate, ht rate, prev rate, next rate + * + * If there isn't a valid next or previous rate then INV is used which + * maps to IWL_RATE_INVALID + * + */ +static const struct iwl_rs_rate_info iwl_rates[IWL_RATE_COUNT] = { + IWL_DECLARE_RATE_INFO(1, INV, INV, 2), /* 1mbps */ + IWL_DECLARE_RATE_INFO(2, INV, 1, 5), /* 2mbps */ + IWL_DECLARE_RATE_INFO(5, INV, 2, 11), /*5.5mbps */ + IWL_DECLARE_RATE_INFO(11, INV, 9, 12), /* 11mbps */ + IWL_DECLARE_RATE_INFO(6, 0, 5, 11), /* 6mbps ; MCS 0 */ + IWL_DECLARE_RATE_INFO(9, INV, 6, 11), /* 9mbps */ + IWL_DECLARE_RATE_INFO(12, 1, 11, 18), /* 12mbps ; MCS 1 */ + IWL_DECLARE_RATE_INFO(18, 2, 12, 24), /* 18mbps ; MCS 2 */ + IWL_DECLARE_RATE_INFO(24, 3, 18, 36), /* 24mbps ; MCS 3 */ + IWL_DECLARE_RATE_INFO(36, 4, 24, 48), /* 36mbps ; MCS 4 */ + IWL_DECLARE_RATE_INFO(48, 5, 36, 54), /* 48mbps ; MCS 5 */ + IWL_DECLARE_RATE_INFO(54, 6, 48, INV), /* 54mbps ; MCS 6 */ + IWL_DECLARE_MCS_RATE(7), /* MCS 7 */ + IWL_DECLARE_MCS_RATE(8), /* MCS 8 */ + IWL_DECLARE_MCS_RATE(9), /* MCS 9 */ +}; + +enum rs_action { + RS_ACTION_STAY = 0, + RS_ACTION_DOWNSCALE = -1, + RS_ACTION_UPSCALE = 1, +}; + +enum rs_column_mode { + RS_INVALID = 0, + RS_LEGACY, + RS_SISO, + RS_MIMO2, +}; + +#define MAX_NEXT_COLUMNS 7 +#define MAX_COLUMN_CHECKS 3 + +struct rs_tx_column; + +typedef bool (*allow_column_func_t) (struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct rs_rate *rate, + const struct rs_tx_column *next_col); + +struct rs_tx_column { + enum rs_column_mode mode; + u8 ant; + bool sgi; + enum rs_column next_columns[MAX_NEXT_COLUMNS]; + allow_column_func_t checks[MAX_COLUMN_CHECKS]; +}; + +static bool rs_ant_allow(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + struct rs_rate *rate, + const struct rs_tx_column *next_col) +{ + return iwl_mvm_bt_coex_is_ant_avail(mvm, next_col->ant); +} + +static bool rs_mimo_allow(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + struct rs_rate *rate, + const struct rs_tx_column *next_col) +{ + if (!sta->deflink.ht_cap.ht_supported) + return false; + + if (sta->deflink.smps_mode == IEEE80211_SMPS_STATIC) + return false; + + if (num_of_ant(iwl_mvm_get_valid_tx_ant(mvm)) < 2) + return false; + + if (!iwl_mvm_bt_coex_is_mimo_allowed(mvm, sta)) + return false; + + if (mvm->nvm_data->sku_cap_mimo_disabled) + return false; + + return true; +} + +static bool rs_siso_allow(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + struct rs_rate *rate, + const struct rs_tx_column *next_col) +{ + if (!sta->deflink.ht_cap.ht_supported) + return false; + + return true; +} + +static bool rs_sgi_allow(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + struct rs_rate *rate, + const struct rs_tx_column *next_col) +{ + struct ieee80211_sta_ht_cap *ht_cap = &sta->deflink.ht_cap; + struct ieee80211_sta_vht_cap *vht_cap = &sta->deflink.vht_cap; + + if (is_ht20(rate) && (ht_cap->cap & + IEEE80211_HT_CAP_SGI_20)) + return true; + if (is_ht40(rate) && (ht_cap->cap & + IEEE80211_HT_CAP_SGI_40)) + return true; + if (is_ht80(rate) && (vht_cap->cap & + IEEE80211_VHT_CAP_SHORT_GI_80)) + return true; + if (is_ht160(rate) && (vht_cap->cap & + IEEE80211_VHT_CAP_SHORT_GI_160)) + return true; + + return false; +} + +static const struct rs_tx_column rs_tx_columns[] = { + [RS_COLUMN_LEGACY_ANT_A] = { + .mode = RS_LEGACY, + .ant = ANT_A, + .next_columns = { + RS_COLUMN_LEGACY_ANT_B, + RS_COLUMN_SISO_ANT_A, + RS_COLUMN_MIMO2, + RS_COLUMN_INVALID, + RS_COLUMN_INVALID, + RS_COLUMN_INVALID, + RS_COLUMN_INVALID, + }, + .checks = { + rs_ant_allow, + }, + }, + [RS_COLUMN_LEGACY_ANT_B] = { + .mode = RS_LEGACY, + .ant = ANT_B, + .next_columns = { + RS_COLUMN_LEGACY_ANT_A, + RS_COLUMN_SISO_ANT_B, + RS_COLUMN_MIMO2, + RS_COLUMN_INVALID, + RS_COLUMN_INVALID, + RS_COLUMN_INVALID, + RS_COLUMN_INVALID, + }, + .checks = { + rs_ant_allow, + }, + }, + [RS_COLUMN_SISO_ANT_A] = { + .mode = RS_SISO, + .ant = ANT_A, + .next_columns = { + RS_COLUMN_SISO_ANT_B, + RS_COLUMN_MIMO2, + RS_COLUMN_SISO_ANT_A_SGI, + RS_COLUMN_LEGACY_ANT_A, + RS_COLUMN_LEGACY_ANT_B, + RS_COLUMN_INVALID, + RS_COLUMN_INVALID, + }, + .checks = { + rs_siso_allow, + rs_ant_allow, + }, + }, + [RS_COLUMN_SISO_ANT_B] = { + .mode = RS_SISO, + .ant = ANT_B, + .next_columns = { + RS_COLUMN_SISO_ANT_A, + RS_COLUMN_MIMO2, + RS_COLUMN_SISO_ANT_B_SGI, + RS_COLUMN_LEGACY_ANT_A, + RS_COLUMN_LEGACY_ANT_B, + RS_COLUMN_INVALID, + RS_COLUMN_INVALID, + }, + .checks = { + rs_siso_allow, + rs_ant_allow, + }, + }, + [RS_COLUMN_SISO_ANT_A_SGI] = { + .mode = RS_SISO, + .ant = ANT_A, + .sgi = true, + .next_columns = { + RS_COLUMN_SISO_ANT_B_SGI, + RS_COLUMN_MIMO2_SGI, + RS_COLUMN_SISO_ANT_A, + RS_COLUMN_LEGACY_ANT_A, + RS_COLUMN_LEGACY_ANT_B, + RS_COLUMN_INVALID, + RS_COLUMN_INVALID, + }, + .checks = { + rs_siso_allow, + rs_ant_allow, + rs_sgi_allow, + }, + }, + [RS_COLUMN_SISO_ANT_B_SGI] = { + .mode = RS_SISO, + .ant = ANT_B, + .sgi = true, + .next_columns = { + RS_COLUMN_SISO_ANT_A_SGI, + RS_COLUMN_MIMO2_SGI, + RS_COLUMN_SISO_ANT_B, + RS_COLUMN_LEGACY_ANT_A, + RS_COLUMN_LEGACY_ANT_B, + RS_COLUMN_INVALID, + RS_COLUMN_INVALID, + }, + .checks = { + rs_siso_allow, + rs_ant_allow, + rs_sgi_allow, + }, + }, + [RS_COLUMN_MIMO2] = { + .mode = RS_MIMO2, + .ant = ANT_AB, + .next_columns = { + RS_COLUMN_SISO_ANT_A, + RS_COLUMN_MIMO2_SGI, + RS_COLUMN_LEGACY_ANT_A, + RS_COLUMN_LEGACY_ANT_B, + RS_COLUMN_INVALID, + RS_COLUMN_INVALID, + RS_COLUMN_INVALID, + }, + .checks = { + rs_mimo_allow, + }, + }, + [RS_COLUMN_MIMO2_SGI] = { + .mode = RS_MIMO2, + .ant = ANT_AB, + .sgi = true, + .next_columns = { + RS_COLUMN_SISO_ANT_A_SGI, + RS_COLUMN_MIMO2, + RS_COLUMN_LEGACY_ANT_A, + RS_COLUMN_LEGACY_ANT_B, + RS_COLUMN_INVALID, + RS_COLUMN_INVALID, + RS_COLUMN_INVALID, + }, + .checks = { + rs_mimo_allow, + rs_sgi_allow, + }, + }, +}; + +static inline u8 rs_extract_rate(u32 rate_n_flags) +{ + /* also works for HT because bits 7:6 are zero there */ + return (u8)(rate_n_flags & RATE_LEGACY_RATE_MSK_V1); +} + +static int iwl_hwrate_to_plcp_idx(u32 rate_n_flags) +{ + int idx = 0; + + if (rate_n_flags & RATE_MCS_HT_MSK_V1) { + idx = rate_n_flags & RATE_HT_MCS_RATE_CODE_MSK_V1; + idx += IWL_RATE_MCS_0_INDEX; + + /* skip 9M not supported in HT*/ + if (idx >= IWL_RATE_9M_INDEX) + idx += 1; + if ((idx >= IWL_FIRST_HT_RATE) && (idx <= IWL_LAST_HT_RATE)) + return idx; + } else if (rate_n_flags & RATE_MCS_VHT_MSK_V1 || + rate_n_flags & RATE_MCS_HE_MSK_V1) { + idx = rate_n_flags & RATE_VHT_MCS_RATE_CODE_MSK; + idx += IWL_RATE_MCS_0_INDEX; + + /* skip 9M not supported in VHT*/ + if (idx >= IWL_RATE_9M_INDEX) + idx++; + if ((idx >= IWL_FIRST_VHT_RATE) && (idx <= IWL_LAST_VHT_RATE)) + return idx; + if ((rate_n_flags & RATE_MCS_HE_MSK_V1) && + idx <= IWL_LAST_HE_RATE) + return idx; + } else { + /* legacy rate format, search for match in table */ + + u8 legacy_rate = rs_extract_rate(rate_n_flags); + for (idx = 0; idx < ARRAY_SIZE(iwl_rates); idx++) + if (iwl_rates[idx].plcp == legacy_rate) + return idx; + } + + return IWL_RATE_INVALID; +} + +static void rs_rate_scale_perform(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta, + int tid, bool ndp); +static void rs_fill_lq_cmd(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta, + const struct rs_rate *initial_rate); +static void rs_stay_in_table(struct iwl_lq_sta *lq_sta, bool force_search); + +/* + * The following tables contain the expected throughput metrics for all rates + * + * 1, 2, 5.5, 11, 6, 9, 12, 18, 24, 36, 48, 54, 60 MBits + * + * where invalid entries are zeros. + * + * CCK rates are only valid in legacy table and will only be used in G + * (2.4 GHz) band. + */ +static const u16 expected_tpt_legacy[IWL_RATE_COUNT] = { + 7, 13, 35, 58, 40, 57, 72, 98, 121, 154, 177, 186, 0, 0, 0 +}; + +/* Expected TpT tables. 4 indexes: + * 0 - NGI, 1 - SGI, 2 - AGG+NGI, 3 - AGG+SGI + */ +static const u16 expected_tpt_siso_20MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 42, 0, 76, 102, 124, 159, 183, 193, 202, 216, 0}, + {0, 0, 0, 0, 46, 0, 82, 110, 132, 168, 192, 202, 210, 225, 0}, + {0, 0, 0, 0, 49, 0, 97, 145, 192, 285, 375, 420, 464, 551, 0}, + {0, 0, 0, 0, 54, 0, 108, 160, 213, 315, 415, 465, 513, 608, 0}, +}; + +static const u16 expected_tpt_siso_40MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 77, 0, 127, 160, 184, 220, 242, 250, 257, 269, 275}, + {0, 0, 0, 0, 83, 0, 135, 169, 193, 229, 250, 257, 264, 275, 280}, + {0, 0, 0, 0, 101, 0, 199, 295, 389, 570, 744, 828, 911, 1070, 1173}, + {0, 0, 0, 0, 112, 0, 220, 326, 429, 629, 819, 912, 1000, 1173, 1284}, +}; + +static const u16 expected_tpt_siso_80MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 130, 0, 191, 223, 244, 273, 288, 294, 298, 305, 308}, + {0, 0, 0, 0, 138, 0, 200, 231, 251, 279, 293, 298, 302, 308, 312}, + {0, 0, 0, 0, 217, 0, 429, 634, 834, 1220, 1585, 1760, 1931, 2258, 2466}, + {0, 0, 0, 0, 241, 0, 475, 701, 921, 1343, 1741, 1931, 2117, 2468, 2691}, +}; + +static const u16 expected_tpt_siso_160MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 191, 0, 244, 288, 298, 308, 313, 318, 323, 328, 330}, + {0, 0, 0, 0, 200, 0, 251, 293, 302, 312, 317, 322, 327, 332, 334}, + {0, 0, 0, 0, 439, 0, 875, 1307, 1736, 2584, 3419, 3831, 4240, 5049, 5581}, + {0, 0, 0, 0, 488, 0, 972, 1451, 1925, 2864, 3785, 4240, 4691, 5581, 6165}, +}; + +static const u16 expected_tpt_mimo2_20MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 74, 0, 123, 155, 179, 213, 235, 243, 250, 261, 0}, + {0, 0, 0, 0, 81, 0, 131, 164, 187, 221, 242, 250, 256, 267, 0}, + {0, 0, 0, 0, 98, 0, 193, 286, 375, 550, 718, 799, 878, 1032, 0}, + {0, 0, 0, 0, 109, 0, 214, 316, 414, 607, 790, 879, 965, 1132, 0}, +}; + +static const u16 expected_tpt_mimo2_40MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 123, 0, 182, 214, 235, 264, 279, 285, 289, 296, 300}, + {0, 0, 0, 0, 131, 0, 191, 222, 242, 270, 284, 289, 293, 300, 303}, + {0, 0, 0, 0, 200, 0, 390, 571, 741, 1067, 1365, 1505, 1640, 1894, 2053}, + {0, 0, 0, 0, 221, 0, 430, 630, 816, 1169, 1490, 1641, 1784, 2053, 2221}, +}; + +static const u16 expected_tpt_mimo2_80MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 182, 0, 240, 264, 278, 299, 308, 311, 313, 317, 319}, + {0, 0, 0, 0, 190, 0, 247, 269, 282, 302, 310, 313, 315, 319, 320}, + {0, 0, 0, 0, 428, 0, 833, 1215, 1577, 2254, 2863, 3147, 3418, 3913, 4219}, + {0, 0, 0, 0, 474, 0, 920, 1338, 1732, 2464, 3116, 3418, 3705, 4225, 4545}, +}; + +static const u16 expected_tpt_mimo2_160MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 240, 0, 278, 308, 313, 319, 322, 324, 328, 330, 334}, + {0, 0, 0, 0, 247, 0, 282, 310, 315, 320, 323, 325, 329, 332, 338}, + {0, 0, 0, 0, 875, 0, 1735, 2582, 3414, 5043, 6619, 7389, 8147, 9629, 10592}, + {0, 0, 0, 0, 971, 0, 1925, 2861, 3779, 5574, 7304, 8147, 8976, 10592, 11640}, +}; + +static const char *rs_pretty_lq_type(enum iwl_table_type type) +{ + static const char * const lq_types[] = { + [LQ_NONE] = "NONE", + [LQ_LEGACY_A] = "LEGACY_A", + [LQ_LEGACY_G] = "LEGACY_G", + [LQ_HT_SISO] = "HT SISO", + [LQ_HT_MIMO2] = "HT MIMO", + [LQ_VHT_SISO] = "VHT SISO", + [LQ_VHT_MIMO2] = "VHT MIMO", + [LQ_HE_SISO] = "HE SISO", + [LQ_HE_MIMO2] = "HE MIMO", + }; + + if (type < LQ_NONE || type >= LQ_MAX) + return "UNKNOWN"; + + return lq_types[type]; +} + +static char *rs_pretty_rate(const struct rs_rate *rate) +{ + static char buf[40]; + static const char * const legacy_rates[] = { + [IWL_RATE_1M_INDEX] = "1M", + [IWL_RATE_2M_INDEX] = "2M", + [IWL_RATE_5M_INDEX] = "5.5M", + [IWL_RATE_11M_INDEX] = "11M", + [IWL_RATE_6M_INDEX] = "6M", + [IWL_RATE_9M_INDEX] = "9M", + [IWL_RATE_12M_INDEX] = "12M", + [IWL_RATE_18M_INDEX] = "18M", + [IWL_RATE_24M_INDEX] = "24M", + [IWL_RATE_36M_INDEX] = "36M", + [IWL_RATE_48M_INDEX] = "48M", + [IWL_RATE_54M_INDEX] = "54M", + }; + static const char *const ht_vht_rates[] = { + [IWL_RATE_MCS_0_INDEX] = "MCS0", + [IWL_RATE_MCS_1_INDEX] = "MCS1", + [IWL_RATE_MCS_2_INDEX] = "MCS2", + [IWL_RATE_MCS_3_INDEX] = "MCS3", + [IWL_RATE_MCS_4_INDEX] = "MCS4", + [IWL_RATE_MCS_5_INDEX] = "MCS5", + [IWL_RATE_MCS_6_INDEX] = "MCS6", + [IWL_RATE_MCS_7_INDEX] = "MCS7", + [IWL_RATE_MCS_8_INDEX] = "MCS8", + [IWL_RATE_MCS_9_INDEX] = "MCS9", + }; + const char *rate_str; + + if (is_type_legacy(rate->type) && (rate->index <= IWL_RATE_54M_INDEX)) + rate_str = legacy_rates[rate->index]; + else if ((is_type_ht(rate->type) || is_type_vht(rate->type)) && + (rate->index >= IWL_RATE_MCS_0_INDEX) && + (rate->index <= IWL_RATE_MCS_9_INDEX)) + rate_str = ht_vht_rates[rate->index]; + else + rate_str = "BAD_RATE"; + + sprintf(buf, "(%s|%s|%s)", rs_pretty_lq_type(rate->type), + iwl_rs_pretty_ant(rate->ant), rate_str); + return buf; +} + +static inline void rs_dump_rate(struct iwl_mvm *mvm, const struct rs_rate *rate, + const char *prefix) +{ + IWL_DEBUG_RATE(mvm, + "%s: %s BW: %d SGI: %d LDPC: %d STBC: %d\n", + prefix, rs_pretty_rate(rate), rate->bw, + rate->sgi, rate->ldpc, rate->stbc); +} + +static void rs_rate_scale_clear_window(struct iwl_rate_scale_data *window) +{ + window->data = 0; + window->success_counter = 0; + window->success_ratio = IWL_INVALID_VALUE; + window->counter = 0; + window->average_tpt = IWL_INVALID_VALUE; +} + +static void rs_rate_scale_clear_tbl_windows(struct iwl_mvm *mvm, + struct iwl_scale_tbl_info *tbl) +{ + int i; + + IWL_DEBUG_RATE(mvm, "Clearing up window stats\n"); + for (i = 0; i < IWL_RATE_COUNT; i++) + rs_rate_scale_clear_window(&tbl->win[i]); + + for (i = 0; i < ARRAY_SIZE(tbl->tpc_win); i++) + rs_rate_scale_clear_window(&tbl->tpc_win[i]); +} + +static inline u8 rs_is_valid_ant(u8 valid_antenna, u8 ant_type) +{ + return (ant_type & valid_antenna) == ant_type; +} + +static int rs_tl_turn_on_agg_for_tid(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_data, u8 tid, + struct ieee80211_sta *sta) +{ + int ret; + + IWL_DEBUG_HT(mvm, "Starting Tx agg: STA: %pM tid: %d\n", + sta->addr, tid); + + /* start BA session until the peer sends del BA */ + ret = ieee80211_start_tx_ba_session(sta, tid, 0); + if (ret == -EAGAIN) { + /* + * driver and mac80211 is out of sync + * this might be cause by reloading firmware + * stop the tx ba session here + */ + IWL_ERR(mvm, "Fail start Tx agg on tid: %d\n", + tid); + ieee80211_stop_tx_ba_session(sta, tid); + } + return ret; +} + +static void rs_tl_turn_on_agg(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta, + u8 tid, struct iwl_lq_sta *lq_sta, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_tid_data *tid_data; + + /* + * In AP mode, tid can be equal to IWL_MAX_TID_COUNT + * when the frame is not QoS + */ + if (WARN_ON_ONCE(tid > IWL_MAX_TID_COUNT)) { + IWL_ERR(mvm, "tid exceeds max TID count: %d/%d\n", + tid, IWL_MAX_TID_COUNT); + return; + } else if (tid == IWL_MAX_TID_COUNT) { + return; + } + + tid_data = &mvmsta->tid_data[tid]; + if (mvmsta->sta_state >= IEEE80211_STA_AUTHORIZED && + tid_data->state == IWL_AGG_OFF && + (lq_sta->tx_agg_tid_en & BIT(tid)) && + tid_data->tx_count_last >= IWL_MVM_RS_AGG_START_THRESHOLD) { + IWL_DEBUG_RATE(mvm, "try to aggregate tid %d\n", tid); + if (rs_tl_turn_on_agg_for_tid(mvm, lq_sta, tid, sta) == 0) + tid_data->state = IWL_AGG_QUEUED; + } +} + +static inline int get_num_of_ant_from_rate(u32 rate_n_flags) +{ + return !!(rate_n_flags & RATE_MCS_ANT_A_MSK) + + !!(rate_n_flags & RATE_MCS_ANT_B_MSK); +} + +/* + * Static function to get the expected throughput from an iwl_scale_tbl_info + * that wraps a NULL pointer check + */ +static s32 get_expected_tpt(struct iwl_scale_tbl_info *tbl, int rs_index) +{ + if (tbl->expected_tpt) + return tbl->expected_tpt[rs_index]; + return 0; +} + +/* + * rs_collect_tx_data - Update the success/failure sliding window + * + * We keep a sliding window of the last 62 packets transmitted + * at this rate. window->data contains the bitmask of successful + * packets. + */ +static int _rs_collect_tx_data(struct iwl_mvm *mvm, + struct iwl_scale_tbl_info *tbl, + int scale_index, int attempts, int successes, + struct iwl_rate_scale_data *window) +{ + static const u64 mask = (((u64)1) << (IWL_RATE_MAX_WINDOW - 1)); + s32 fail_count, tpt; + + /* Get expected throughput */ + tpt = get_expected_tpt(tbl, scale_index); + + /* + * Keep track of only the latest 62 tx frame attempts in this rate's + * history window; anything older isn't really relevant any more. + * If we have filled up the sliding window, drop the oldest attempt; + * if the oldest attempt (highest bit in bitmap) shows "success", + * subtract "1" from the success counter (this is the main reason + * we keep these bitmaps!). + */ + while (attempts > 0) { + if (window->counter >= IWL_RATE_MAX_WINDOW) { + /* remove earliest */ + window->counter = IWL_RATE_MAX_WINDOW - 1; + + if (window->data & mask) { + window->data &= ~mask; + window->success_counter--; + } + } + + /* Increment frames-attempted counter */ + window->counter++; + + /* Shift bitmap by one frame to throw away oldest history */ + window->data <<= 1; + + /* Mark the most recent #successes attempts as successful */ + if (successes > 0) { + window->success_counter++; + window->data |= 0x1; + successes--; + } + + attempts--; + } + + /* Calculate current success ratio, avoid divide-by-0! */ + if (window->counter > 0) + window->success_ratio = 128 * (100 * window->success_counter) + / window->counter; + else + window->success_ratio = IWL_INVALID_VALUE; + + fail_count = window->counter - window->success_counter; + + /* Calculate average throughput, if we have enough history. */ + if ((fail_count >= IWL_MVM_RS_RATE_MIN_FAILURE_TH) || + (window->success_counter >= IWL_MVM_RS_RATE_MIN_SUCCESS_TH)) + window->average_tpt = (window->success_ratio * tpt + 64) / 128; + else + window->average_tpt = IWL_INVALID_VALUE; + + return 0; +} + +static int rs_collect_tpc_data(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl, + int scale_index, int attempts, int successes, + u8 reduced_txp) +{ + struct iwl_rate_scale_data *window = NULL; + + if (WARN_ON_ONCE(reduced_txp > TPC_MAX_REDUCTION)) + return -EINVAL; + + window = &tbl->tpc_win[reduced_txp]; + return _rs_collect_tx_data(mvm, tbl, scale_index, attempts, successes, + window); +} + +static void rs_update_tid_tpt_stats(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvmsta, + u8 tid, int successes) +{ + struct iwl_mvm_tid_data *tid_data; + + if (tid >= IWL_MAX_TID_COUNT) + return; + + tid_data = &mvmsta->tid_data[tid]; + + /* + * Measure if there're enough successful transmits per second. + * These statistics are used only to decide if we can start a + * BA session, so it should be updated only when A-MPDU is + * off. + */ + if (tid_data->state != IWL_AGG_OFF) + return; + + if (time_is_before_jiffies(tid_data->tpt_meas_start + HZ) || + (tid_data->tx_count >= IWL_MVM_RS_AGG_START_THRESHOLD)) { + tid_data->tx_count_last = tid_data->tx_count; + tid_data->tx_count = 0; + tid_data->tpt_meas_start = jiffies; + } else { + tid_data->tx_count += successes; + } +} + +static int rs_collect_tlc_data(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvmsta, u8 tid, + struct iwl_scale_tbl_info *tbl, + int scale_index, int attempts, int successes) +{ + struct iwl_rate_scale_data *window = NULL; + + if (scale_index < 0 || scale_index >= IWL_RATE_COUNT) + return -EINVAL; + + if (tbl->column != RS_COLUMN_INVALID) { + struct lq_sta_pers *pers = &mvmsta->lq_sta.rs_drv.pers; + + pers->tx_stats[tbl->column][scale_index].total += attempts; + pers->tx_stats[tbl->column][scale_index].success += successes; + } + + rs_update_tid_tpt_stats(mvm, mvmsta, tid, successes); + + /* Select window for current tx bit rate */ + window = &(tbl->win[scale_index]); + return _rs_collect_tx_data(mvm, tbl, scale_index, attempts, successes, + window); +} + +/* Convert rs_rate object into ucode rate bitmask */ +static u32 ucode_rate_from_rs_rate(struct iwl_mvm *mvm, + struct rs_rate *rate) +{ + u32 ucode_rate = 0; + int index = rate->index; + + ucode_rate |= ((rate->ant << RATE_MCS_ANT_POS) & + RATE_MCS_ANT_AB_MSK); + + if (is_legacy(rate)) { + ucode_rate |= iwl_rates[index].plcp; + if (index >= IWL_FIRST_CCK_RATE && index <= IWL_LAST_CCK_RATE) + ucode_rate |= RATE_MCS_CCK_MSK_V1; + return ucode_rate; + } + + /* set RTS protection for all non legacy rates + * This helps with congested environments reducing the conflict cost to + * RTS retries only, instead of the entire BA packet. + */ + ucode_rate |= RATE_MCS_RTS_REQUIRED_MSK; + + if (is_ht(rate)) { + if (index < IWL_FIRST_HT_RATE || index > IWL_LAST_HT_RATE) { + IWL_ERR(mvm, "Invalid HT rate index %d\n", index); + index = IWL_LAST_HT_RATE; + } + ucode_rate |= RATE_MCS_HT_MSK_V1; + + if (is_ht_siso(rate)) + ucode_rate |= iwl_rates[index].plcp_ht_siso; + else if (is_ht_mimo2(rate)) + ucode_rate |= iwl_rates[index].plcp_ht_mimo2; + else + WARN_ON_ONCE(1); + } else if (is_vht(rate)) { + if (index < IWL_FIRST_VHT_RATE || index > IWL_LAST_VHT_RATE) { + IWL_ERR(mvm, "Invalid VHT rate index %d\n", index); + index = IWL_LAST_VHT_RATE; + } + ucode_rate |= RATE_MCS_VHT_MSK_V1; + if (is_vht_siso(rate)) + ucode_rate |= iwl_rates[index].plcp_vht_siso; + else if (is_vht_mimo2(rate)) + ucode_rate |= iwl_rates[index].plcp_vht_mimo2; + else + WARN_ON_ONCE(1); + + } else { + IWL_ERR(mvm, "Invalid rate->type %d\n", rate->type); + } + + if (is_siso(rate) && rate->stbc) { + /* To enable STBC we need to set both a flag and ANT_AB */ + ucode_rate |= RATE_MCS_ANT_AB_MSK; + ucode_rate |= RATE_MCS_STBC_MSK; + } + + ucode_rate |= rate->bw; + if (rate->sgi) + ucode_rate |= RATE_MCS_SGI_MSK_V1; + if (rate->ldpc) + ucode_rate |= RATE_MCS_LDPC_MSK_V1; + + return ucode_rate; +} + +/* Convert a ucode rate into an rs_rate object */ +static int rs_rate_from_ucode_rate(const u32 ucode_rate, + enum nl80211_band band, + struct rs_rate *rate) +{ + u32 ant_msk = ucode_rate & RATE_MCS_ANT_AB_MSK; + u8 num_of_ant = get_num_of_ant_from_rate(ucode_rate); + u8 nss; + + memset(rate, 0, sizeof(*rate)); + rate->index = iwl_hwrate_to_plcp_idx(ucode_rate); + + if (rate->index == IWL_RATE_INVALID) + return -EINVAL; + + rate->ant = (ant_msk >> RATE_MCS_ANT_POS); + + /* Legacy */ + if (!(ucode_rate & RATE_MCS_HT_MSK_V1) && + !(ucode_rate & RATE_MCS_VHT_MSK_V1) && + !(ucode_rate & RATE_MCS_HE_MSK_V1)) { + if (num_of_ant == 1) { + if (band == NL80211_BAND_5GHZ) + rate->type = LQ_LEGACY_A; + else + rate->type = LQ_LEGACY_G; + } + + return 0; + } + + /* HT, VHT or HE */ + if (ucode_rate & RATE_MCS_SGI_MSK_V1) + rate->sgi = true; + if (ucode_rate & RATE_MCS_LDPC_MSK_V1) + rate->ldpc = true; + if (ucode_rate & RATE_MCS_STBC_MSK) + rate->stbc = true; + if (ucode_rate & RATE_MCS_BF_MSK) + rate->bfer = true; + + rate->bw = ucode_rate & RATE_MCS_CHAN_WIDTH_MSK_V1; + + if (ucode_rate & RATE_MCS_HT_MSK_V1) { + nss = ((ucode_rate & RATE_HT_MCS_NSS_MSK_V1) >> + RATE_HT_MCS_NSS_POS_V1) + 1; + + if (nss == 1) { + rate->type = LQ_HT_SISO; + WARN_ONCE(!rate->stbc && !rate->bfer && num_of_ant != 1, + "stbc %d bfer %d", + rate->stbc, rate->bfer); + } else if (nss == 2) { + rate->type = LQ_HT_MIMO2; + WARN_ON_ONCE(num_of_ant != 2); + } else { + WARN_ON_ONCE(1); + } + } else if (ucode_rate & RATE_MCS_VHT_MSK_V1) { + nss = ((ucode_rate & RATE_VHT_MCS_NSS_MSK) >> + RATE_VHT_MCS_NSS_POS) + 1; + + if (nss == 1) { + rate->type = LQ_VHT_SISO; + WARN_ONCE(!rate->stbc && !rate->bfer && num_of_ant != 1, + "stbc %d bfer %d", + rate->stbc, rate->bfer); + } else if (nss == 2) { + rate->type = LQ_VHT_MIMO2; + WARN_ON_ONCE(num_of_ant != 2); + } else { + WARN_ON_ONCE(1); + } + } else if (ucode_rate & RATE_MCS_HE_MSK_V1) { + nss = ((ucode_rate & RATE_VHT_MCS_NSS_MSK) >> + RATE_VHT_MCS_NSS_POS) + 1; + + if (nss == 1) { + rate->type = LQ_HE_SISO; + WARN_ONCE(!rate->stbc && !rate->bfer && num_of_ant != 1, + "stbc %d bfer %d", rate->stbc, rate->bfer); + } else if (nss == 2) { + rate->type = LQ_HE_MIMO2; + WARN_ON_ONCE(num_of_ant != 2); + } else { + WARN_ON_ONCE(1); + } + } + + WARN_ON_ONCE(rate->bw == RATE_MCS_CHAN_WIDTH_80 && + !is_he(rate) && !is_vht(rate)); + + return 0; +} + +/* switch to another antenna/antennas and return 1 */ +/* if no other valid antenna found, return 0 */ +static int rs_toggle_antenna(u32 valid_ant, struct rs_rate *rate) +{ + u8 new_ant_type; + + if (!rs_is_valid_ant(valid_ant, rate->ant)) + return 0; + + new_ant_type = ant_toggle_lookup[rate->ant]; + + while ((new_ant_type != rate->ant) && + !rs_is_valid_ant(valid_ant, new_ant_type)) + new_ant_type = ant_toggle_lookup[new_ant_type]; + + if (new_ant_type == rate->ant) + return 0; + + rate->ant = new_ant_type; + + return 1; +} + +static u16 rs_get_supported_rates(struct iwl_lq_sta *lq_sta, + struct rs_rate *rate) +{ + if (is_legacy(rate)) + return lq_sta->active_legacy_rate; + else if (is_siso(rate)) + return lq_sta->active_siso_rate; + else if (is_mimo2(rate)) + return lq_sta->active_mimo2_rate; + + WARN_ON_ONCE(1); + return 0; +} + +static u16 rs_get_adjacent_rate(struct iwl_mvm *mvm, u8 index, u16 rate_mask, + int rate_type) +{ + u8 high = IWL_RATE_INVALID; + u8 low = IWL_RATE_INVALID; + + /* 802.11A or ht walks to the next literal adjacent rate in + * the rate table */ + if (is_type_a_band(rate_type) || !is_type_legacy(rate_type)) { + int i; + u32 mask; + + /* Find the previous rate that is in the rate mask */ + i = index - 1; + if (i >= 0) + mask = BIT(i); + for (; i >= 0; i--, mask >>= 1) { + if (rate_mask & mask) { + low = i; + break; + } + } + + /* Find the next rate that is in the rate mask */ + i = index + 1; + for (mask = (1 << i); i < IWL_RATE_COUNT; i++, mask <<= 1) { + if (rate_mask & mask) { + high = i; + break; + } + } + + return (high << 8) | low; + } + + low = index; + while (low != IWL_RATE_INVALID) { + low = iwl_rates[low].prev_rs; + if (low == IWL_RATE_INVALID) + break; + if (rate_mask & (1 << low)) + break; + } + + high = index; + while (high != IWL_RATE_INVALID) { + high = iwl_rates[high].next_rs; + if (high == IWL_RATE_INVALID) + break; + if (rate_mask & (1 << high)) + break; + } + + return (high << 8) | low; +} + +static inline bool rs_rate_supported(struct iwl_lq_sta *lq_sta, + struct rs_rate *rate) +{ + return BIT(rate->index) & rs_get_supported_rates(lq_sta, rate); +} + +/* Get the next supported lower rate in the current column. + * Return true if bottom rate in the current column was reached + */ +static bool rs_get_lower_rate_in_column(struct iwl_lq_sta *lq_sta, + struct rs_rate *rate) +{ + u8 low; + u16 high_low; + u16 rate_mask; + struct iwl_mvm *mvm = lq_sta->pers.drv; + + rate_mask = rs_get_supported_rates(lq_sta, rate); + high_low = rs_get_adjacent_rate(mvm, rate->index, rate_mask, + rate->type); + low = high_low & 0xff; + + /* Bottom rate of column reached */ + if (low == IWL_RATE_INVALID) + return true; + + rate->index = low; + return false; +} + +/* Get the next rate to use following a column downgrade */ +static void rs_get_lower_rate_down_column(struct iwl_lq_sta *lq_sta, + struct rs_rate *rate) +{ + struct iwl_mvm *mvm = lq_sta->pers.drv; + + if (is_legacy(rate)) { + /* No column to downgrade from Legacy */ + return; + } else if (is_siso(rate)) { + /* Downgrade to Legacy if we were in SISO */ + if (lq_sta->band == NL80211_BAND_5GHZ) + rate->type = LQ_LEGACY_A; + else + rate->type = LQ_LEGACY_G; + + rate->bw = RATE_MCS_CHAN_WIDTH_20; + + WARN_ON_ONCE(rate->index < IWL_RATE_MCS_0_INDEX || + rate->index > IWL_RATE_MCS_9_INDEX); + + rate->index = rs_ht_to_legacy[rate->index]; + rate->ldpc = false; + } else { + /* Downgrade to SISO with same MCS if in MIMO */ + rate->type = is_vht_mimo2(rate) ? + LQ_VHT_SISO : LQ_HT_SISO; + } + + if (num_of_ant(rate->ant) > 1) + rate->ant = first_antenna(iwl_mvm_get_valid_tx_ant(mvm)); + + /* Relevant in both switching to SISO or Legacy */ + rate->sgi = false; + + if (!rs_rate_supported(lq_sta, rate)) + rs_get_lower_rate_in_column(lq_sta, rate); +} + +/* Check if both rates share the same column */ +static inline bool rs_rate_column_match(struct rs_rate *a, + struct rs_rate *b) +{ + bool ant_match; + + if (a->stbc || a->bfer) + ant_match = (b->ant == ANT_A || b->ant == ANT_B); + else + ant_match = (a->ant == b->ant); + + return (a->type == b->type) && (a->bw == b->bw) && (a->sgi == b->sgi) + && ant_match; +} + +static inline enum rs_column rs_get_column_from_rate(struct rs_rate *rate) +{ + if (is_legacy(rate)) { + if (rate->ant == ANT_A) + return RS_COLUMN_LEGACY_ANT_A; + + if (rate->ant == ANT_B) + return RS_COLUMN_LEGACY_ANT_B; + + goto err; + } + + if (is_siso(rate)) { + if (rate->ant == ANT_A || rate->stbc || rate->bfer) + return rate->sgi ? RS_COLUMN_SISO_ANT_A_SGI : + RS_COLUMN_SISO_ANT_A; + + if (rate->ant == ANT_B) + return rate->sgi ? RS_COLUMN_SISO_ANT_B_SGI : + RS_COLUMN_SISO_ANT_B; + + goto err; + } + + if (is_mimo(rate)) + return rate->sgi ? RS_COLUMN_MIMO2_SGI : RS_COLUMN_MIMO2; + +err: + return RS_COLUMN_INVALID; +} + +static u8 rs_get_tid(struct ieee80211_hdr *hdr) +{ + u8 tid = IWL_MAX_TID_COUNT; + + if (ieee80211_is_data_qos(hdr->frame_control)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + tid = qc[0] & 0xf; + } + + if (unlikely(tid > IWL_MAX_TID_COUNT)) + tid = IWL_MAX_TID_COUNT; + + return tid; +} + +/* + * mac80211 sends us Tx status + */ +static void rs_drv_mac80211_tx_status(void *mvm_r, + struct ieee80211_supported_band *sband, + struct ieee80211_sta *sta, void *priv_sta, + struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct iwl_op_mode *op_mode = mvm_r; + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + + if (!mvmsta->vif) + return; + + if (!ieee80211_is_data(hdr->frame_control) || + info->flags & IEEE80211_TX_CTL_NO_ACK) + return; + + iwl_mvm_rs_tx_status(mvm, sta, rs_get_tid(hdr), info, + ieee80211_is_qos_nullfunc(hdr->frame_control)); +} + +/* + * Begin a period of staying with a selected modulation mode. + * Set "stay_in_tbl" flag to prevent any mode switches. + * Set frame tx success limits according to legacy vs. high-throughput, + * and reset overall (spanning all rates) tx success history statistics. + * These control how long we stay using same modulation mode before + * searching for a new mode. + */ +static void rs_set_stay_in_table(struct iwl_mvm *mvm, u8 is_legacy, + struct iwl_lq_sta *lq_sta) +{ + IWL_DEBUG_RATE(mvm, "Moving to RS_STATE_STAY_IN_COLUMN\n"); + lq_sta->rs_state = RS_STATE_STAY_IN_COLUMN; + if (is_legacy) { + lq_sta->table_count_limit = IWL_MVM_RS_LEGACY_TABLE_COUNT; + lq_sta->max_failure_limit = IWL_MVM_RS_LEGACY_FAILURE_LIMIT; + lq_sta->max_success_limit = IWL_MVM_RS_LEGACY_SUCCESS_LIMIT; + } else { + lq_sta->table_count_limit = IWL_MVM_RS_NON_LEGACY_TABLE_COUNT; + lq_sta->max_failure_limit = IWL_MVM_RS_NON_LEGACY_FAILURE_LIMIT; + lq_sta->max_success_limit = IWL_MVM_RS_NON_LEGACY_SUCCESS_LIMIT; + } + lq_sta->table_count = 0; + lq_sta->total_failed = 0; + lq_sta->total_success = 0; + lq_sta->flush_timer = jiffies; + lq_sta->visited_columns = 0; +} + +static inline int rs_get_max_rate_from_mask(unsigned long rate_mask) +{ + if (rate_mask) + return find_last_bit(&rate_mask, BITS_PER_LONG); + return IWL_RATE_INVALID; +} + +static int rs_get_max_allowed_rate(struct iwl_lq_sta *lq_sta, + const struct rs_tx_column *column) +{ + switch (column->mode) { + case RS_LEGACY: + return lq_sta->max_legacy_rate_idx; + case RS_SISO: + return lq_sta->max_siso_rate_idx; + case RS_MIMO2: + return lq_sta->max_mimo2_rate_idx; + default: + WARN_ON_ONCE(1); + } + + return lq_sta->max_legacy_rate_idx; +} + +static const u16 *rs_get_expected_tpt_table(struct iwl_lq_sta *lq_sta, + const struct rs_tx_column *column, + u32 bw) +{ + /* Used to choose among HT tables */ + const u16 (*ht_tbl_pointer)[IWL_RATE_COUNT]; + + if (WARN_ON_ONCE(column->mode != RS_LEGACY && + column->mode != RS_SISO && + column->mode != RS_MIMO2)) + return expected_tpt_legacy; + + /* Legacy rates have only one table */ + if (column->mode == RS_LEGACY) + return expected_tpt_legacy; + + ht_tbl_pointer = expected_tpt_mimo2_20MHz; + /* Choose among many HT tables depending on number of streams + * (SISO/MIMO2), channel width (20/40/80), SGI, and aggregation + * status */ + if (column->mode == RS_SISO) { + switch (bw) { + case RATE_MCS_CHAN_WIDTH_20: + ht_tbl_pointer = expected_tpt_siso_20MHz; + break; + case RATE_MCS_CHAN_WIDTH_40: + ht_tbl_pointer = expected_tpt_siso_40MHz; + break; + case RATE_MCS_CHAN_WIDTH_80: + ht_tbl_pointer = expected_tpt_siso_80MHz; + break; + case RATE_MCS_CHAN_WIDTH_160: + ht_tbl_pointer = expected_tpt_siso_160MHz; + break; + default: + WARN_ON_ONCE(1); + } + } else if (column->mode == RS_MIMO2) { + switch (bw) { + case RATE_MCS_CHAN_WIDTH_20: + ht_tbl_pointer = expected_tpt_mimo2_20MHz; + break; + case RATE_MCS_CHAN_WIDTH_40: + ht_tbl_pointer = expected_tpt_mimo2_40MHz; + break; + case RATE_MCS_CHAN_WIDTH_80: + ht_tbl_pointer = expected_tpt_mimo2_80MHz; + break; + case RATE_MCS_CHAN_WIDTH_160: + ht_tbl_pointer = expected_tpt_mimo2_160MHz; + break; + default: + WARN_ON_ONCE(1); + } + } else { + WARN_ON_ONCE(1); + } + + if (!column->sgi && !lq_sta->is_agg) /* Normal */ + return ht_tbl_pointer[0]; + else if (column->sgi && !lq_sta->is_agg) /* SGI */ + return ht_tbl_pointer[1]; + else if (!column->sgi && lq_sta->is_agg) /* AGG */ + return ht_tbl_pointer[2]; + else /* AGG+SGI */ + return ht_tbl_pointer[3]; +} + +static void rs_set_expected_tpt_table(struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl) +{ + struct rs_rate *rate = &tbl->rate; + const struct rs_tx_column *column = &rs_tx_columns[tbl->column]; + + tbl->expected_tpt = rs_get_expected_tpt_table(lq_sta, column, rate->bw); +} + +/* rs uses two tables, one is active and the second is for searching better + * configuration. This function, according to the index of the currently + * active table returns the search table, which is located at the + * index complementary to 1 according to the active table (active = 1, + * search = 0 or active = 0, search = 1). + * Since lq_info is an arary of size 2, make sure index cannot be out of bounds. + */ +static inline u8 rs_search_tbl(u8 active_tbl) +{ + return (active_tbl ^ 1) & 1; +} + +static s32 rs_get_best_rate(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl, /* "search" */ + unsigned long rate_mask, s8 index) +{ + struct iwl_scale_tbl_info *active_tbl = + &(lq_sta->lq_info[lq_sta->active_tbl]); + s32 success_ratio = active_tbl->win[index].success_ratio; + u16 expected_current_tpt = active_tbl->expected_tpt[index]; + const u16 *tpt_tbl = tbl->expected_tpt; + u16 high_low; + u32 target_tpt; + int rate_idx; + + if (success_ratio >= RS_PERCENT(IWL_MVM_RS_SR_NO_DECREASE)) { + target_tpt = 100 * expected_current_tpt; + IWL_DEBUG_RATE(mvm, + "SR %d high. Find rate exceeding EXPECTED_CURRENT %d\n", + success_ratio, target_tpt); + } else { + target_tpt = lq_sta->last_tpt; + IWL_DEBUG_RATE(mvm, + "SR %d not that good. Find rate exceeding ACTUAL_TPT %d\n", + success_ratio, target_tpt); + } + + rate_idx = find_first_bit(&rate_mask, BITS_PER_LONG); + + while (rate_idx != IWL_RATE_INVALID) { + if (target_tpt < (100 * tpt_tbl[rate_idx])) + break; + + high_low = rs_get_adjacent_rate(mvm, rate_idx, rate_mask, + tbl->rate.type); + + rate_idx = (high_low >> 8) & 0xff; + } + + IWL_DEBUG_RATE(mvm, "Best rate found %d target_tp %d expected_new %d\n", + rate_idx, target_tpt, + rate_idx != IWL_RATE_INVALID ? + 100 * tpt_tbl[rate_idx] : IWL_INVALID_VALUE); + + return rate_idx; +} + +static u32 rs_bw_from_sta_bw(struct ieee80211_sta *sta) +{ + struct ieee80211_sta_vht_cap *sta_vht_cap = &sta->deflink.vht_cap; + struct ieee80211_vht_cap vht_cap = { + .vht_cap_info = cpu_to_le32(sta_vht_cap->cap), + .supp_mcs = sta_vht_cap->vht_mcs, + }; + + switch (sta->deflink.bandwidth) { + case IEEE80211_STA_RX_BW_160: + /* + * Don't use 160 MHz if VHT extended NSS support + * says we cannot use 2 streams, we don't want to + * deal with this. + * We only check MCS 0 - they will support that if + * we got here at all and we don't care which MCS, + * we want to determine a more global state. + */ + if (ieee80211_get_vht_max_nss(&vht_cap, + IEEE80211_VHT_CHANWIDTH_160MHZ, + 0, true, + sta->deflink.rx_nss) < sta->deflink.rx_nss) + return RATE_MCS_CHAN_WIDTH_80; + return RATE_MCS_CHAN_WIDTH_160; + case IEEE80211_STA_RX_BW_80: + return RATE_MCS_CHAN_WIDTH_80; + case IEEE80211_STA_RX_BW_40: + return RATE_MCS_CHAN_WIDTH_40; + case IEEE80211_STA_RX_BW_20: + default: + return RATE_MCS_CHAN_WIDTH_20; + } +} + +/* + * Check whether we should continue using same modulation mode, or + * begin search for a new mode, based on: + * 1) # tx successes or failures while using this mode + * 2) # times calling this function + * 3) elapsed time in this mode (not used, for now) + */ +static void rs_stay_in_table(struct iwl_lq_sta *lq_sta, bool force_search) +{ + struct iwl_scale_tbl_info *tbl; + int active_tbl; + int flush_interval_passed = 0; + struct iwl_mvm *mvm; + + mvm = lq_sta->pers.drv; + active_tbl = lq_sta->active_tbl; + + tbl = &(lq_sta->lq_info[active_tbl]); + + /* If we've been disallowing search, see if we should now allow it */ + if (lq_sta->rs_state == RS_STATE_STAY_IN_COLUMN) { + /* Elapsed time using current modulation mode */ + if (lq_sta->flush_timer) + flush_interval_passed = + time_after(jiffies, + (unsigned long)(lq_sta->flush_timer + + (IWL_MVM_RS_STAY_IN_COLUMN_TIMEOUT * HZ))); + + /* + * Check if we should allow search for new modulation mode. + * If many frames have failed or succeeded, or we've used + * this same modulation for a long time, allow search, and + * reset history stats that keep track of whether we should + * allow a new search. Also (below) reset all bitmaps and + * stats in active history. + */ + if (force_search || + (lq_sta->total_failed > lq_sta->max_failure_limit) || + (lq_sta->total_success > lq_sta->max_success_limit) || + ((!lq_sta->search_better_tbl) && + (lq_sta->flush_timer) && (flush_interval_passed))) { + IWL_DEBUG_RATE(mvm, + "LQ: stay is expired %d %d %d\n", + lq_sta->total_failed, + lq_sta->total_success, + flush_interval_passed); + + /* Allow search for new mode */ + lq_sta->rs_state = RS_STATE_SEARCH_CYCLE_STARTED; + IWL_DEBUG_RATE(mvm, + "Moving to RS_STATE_SEARCH_CYCLE_STARTED\n"); + lq_sta->total_failed = 0; + lq_sta->total_success = 0; + lq_sta->flush_timer = 0; + /* mark the current column as visited */ + lq_sta->visited_columns = BIT(tbl->column); + /* + * Else if we've used this modulation mode enough repetitions + * (regardless of elapsed time or success/failure), reset + * history bitmaps and rate-specific stats for all rates in + * active table. + */ + } else { + lq_sta->table_count++; + if (lq_sta->table_count >= + lq_sta->table_count_limit) { + lq_sta->table_count = 0; + + IWL_DEBUG_RATE(mvm, + "LQ: stay in table clear win\n"); + rs_rate_scale_clear_tbl_windows(mvm, tbl); + } + } + + /* If transitioning to allow "search", reset all history + * bitmaps and stats in active table (this will become the new + * "search" table). */ + if (lq_sta->rs_state == RS_STATE_SEARCH_CYCLE_STARTED) { + rs_rate_scale_clear_tbl_windows(mvm, tbl); + } + } +} + +static void rs_set_amsdu_len(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + struct iwl_scale_tbl_info *tbl, + enum rs_action scale_action) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + int i; + + sta->deflink.agg.max_amsdu_len = rs_fw_get_max_amsdu_len(sta); + + /* + * In case TLC offload is not active amsdu_enabled is either 0xFFFF + * or 0, since there is no per-TID alg. + */ + if ((!is_vht(&tbl->rate) && !is_ht(&tbl->rate)) || + tbl->rate.index < IWL_RATE_MCS_5_INDEX || + scale_action == RS_ACTION_DOWNSCALE) + mvmsta->amsdu_enabled = 0; + else + mvmsta->amsdu_enabled = 0xFFFF; + + if (mvmsta->vif->bss_conf.he_support && + !iwlwifi_mod_params.disable_11ax) + mvmsta->max_amsdu_len = sta->deflink.agg.max_amsdu_len; + else + mvmsta->max_amsdu_len = + min_t(int, sta->deflink.agg.max_amsdu_len, 8500); + + sta->deflink.agg.max_rc_amsdu_len = mvmsta->max_amsdu_len; + + for (i = 0; i < IWL_MAX_TID_COUNT; i++) { + if (mvmsta->amsdu_enabled) + sta->deflink.agg.max_tid_amsdu_len[i] = + iwl_mvm_max_amsdu_size(mvm, sta, i); + else + /* + * Not so elegant, but this will effectively + * prevent AMSDU on this TID + */ + sta->deflink.agg.max_tid_amsdu_len[i] = 1; + } +} + +/* + * setup rate table in uCode + */ +static void rs_update_rate_tbl(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl) +{ + rs_fill_lq_cmd(mvm, sta, lq_sta, &tbl->rate); + iwl_mvm_send_lq_cmd(mvm, &lq_sta->lq); +} + +static bool rs_tweak_rate_tbl(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl, + enum rs_action scale_action) +{ + if (rs_bw_from_sta_bw(sta) != RATE_MCS_CHAN_WIDTH_80) + return false; + + if (!is_vht_siso(&tbl->rate)) + return false; + + if ((tbl->rate.bw == RATE_MCS_CHAN_WIDTH_80) && + (tbl->rate.index == IWL_RATE_MCS_0_INDEX) && + (scale_action == RS_ACTION_DOWNSCALE)) { + tbl->rate.bw = RATE_MCS_CHAN_WIDTH_20; + tbl->rate.index = IWL_RATE_MCS_4_INDEX; + IWL_DEBUG_RATE(mvm, "Switch 80Mhz SISO MCS0 -> 20Mhz MCS4\n"); + goto tweaked; + } + + /* Go back to 80Mhz MCS1 only if we've established that 20Mhz MCS5 is + * sustainable, i.e. we're past the test window. We can't go back + * if MCS5 is just tested as this will happen always after switching + * to 20Mhz MCS4 because the rate stats are cleared. + */ + if ((tbl->rate.bw == RATE_MCS_CHAN_WIDTH_20) && + (((tbl->rate.index == IWL_RATE_MCS_5_INDEX) && + (scale_action == RS_ACTION_STAY)) || + ((tbl->rate.index > IWL_RATE_MCS_5_INDEX) && + (scale_action == RS_ACTION_UPSCALE)))) { + tbl->rate.bw = RATE_MCS_CHAN_WIDTH_80; + tbl->rate.index = IWL_RATE_MCS_1_INDEX; + IWL_DEBUG_RATE(mvm, "Switch 20Mhz SISO MCS5 -> 80Mhz MCS1\n"); + goto tweaked; + } + + return false; + +tweaked: + rs_set_expected_tpt_table(lq_sta, tbl); + rs_rate_scale_clear_tbl_windows(mvm, tbl); + return true; +} + +static enum rs_column rs_get_next_column(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta, + struct ieee80211_sta *sta, + struct iwl_scale_tbl_info *tbl) +{ + int i, j, max_rate; + enum rs_column next_col_id; + const struct rs_tx_column *curr_col = &rs_tx_columns[tbl->column]; + const struct rs_tx_column *next_col; + allow_column_func_t allow_func; + u8 valid_ants = iwl_mvm_get_valid_tx_ant(mvm); + const u16 *expected_tpt_tbl; + u16 tpt, max_expected_tpt; + + for (i = 0; i < MAX_NEXT_COLUMNS; i++) { + next_col_id = curr_col->next_columns[i]; + + if (next_col_id == RS_COLUMN_INVALID) + continue; + + if (lq_sta->visited_columns & BIT(next_col_id)) { + IWL_DEBUG_RATE(mvm, "Skip already visited column %d\n", + next_col_id); + continue; + } + + next_col = &rs_tx_columns[next_col_id]; + + if (!rs_is_valid_ant(valid_ants, next_col->ant)) { + IWL_DEBUG_RATE(mvm, + "Skip column %d as ANT config isn't supported by chip. valid_ants 0x%x column ant 0x%x\n", + next_col_id, valid_ants, next_col->ant); + continue; + } + + for (j = 0; j < MAX_COLUMN_CHECKS; j++) { + allow_func = next_col->checks[j]; + if (allow_func && !allow_func(mvm, sta, &tbl->rate, + next_col)) + break; + } + + if (j != MAX_COLUMN_CHECKS) { + IWL_DEBUG_RATE(mvm, + "Skip column %d: not allowed (check %d failed)\n", + next_col_id, j); + + continue; + } + + tpt = lq_sta->last_tpt / 100; + expected_tpt_tbl = rs_get_expected_tpt_table(lq_sta, next_col, + rs_bw_from_sta_bw(sta)); + if (WARN_ON_ONCE(!expected_tpt_tbl)) + continue; + + max_rate = rs_get_max_allowed_rate(lq_sta, next_col); + if (max_rate == IWL_RATE_INVALID) { + IWL_DEBUG_RATE(mvm, + "Skip column %d: no rate is allowed in this column\n", + next_col_id); + continue; + } + + max_expected_tpt = expected_tpt_tbl[max_rate]; + if (tpt >= max_expected_tpt) { + IWL_DEBUG_RATE(mvm, + "Skip column %d: can't beat current TPT. Max expected %d current %d\n", + next_col_id, max_expected_tpt, tpt); + continue; + } + + IWL_DEBUG_RATE(mvm, + "Found potential column %d. Max expected %d current %d\n", + next_col_id, max_expected_tpt, tpt); + break; + } + + if (i == MAX_NEXT_COLUMNS) + return RS_COLUMN_INVALID; + + return next_col_id; +} + +static int rs_switch_to_column(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta, + struct ieee80211_sta *sta, + enum rs_column col_id) +{ + struct iwl_scale_tbl_info *tbl = &lq_sta->lq_info[lq_sta->active_tbl]; + struct iwl_scale_tbl_info *search_tbl = + &lq_sta->lq_info[rs_search_tbl(lq_sta->active_tbl)]; + struct rs_rate *rate = &search_tbl->rate; + const struct rs_tx_column *column = &rs_tx_columns[col_id]; + const struct rs_tx_column *curr_column = &rs_tx_columns[tbl->column]; + unsigned long rate_mask = 0; + u32 rate_idx = 0; + + memcpy(search_tbl, tbl, offsetof(struct iwl_scale_tbl_info, win)); + + rate->sgi = column->sgi; + rate->ant = column->ant; + + if (column->mode == RS_LEGACY) { + if (lq_sta->band == NL80211_BAND_5GHZ) + rate->type = LQ_LEGACY_A; + else + rate->type = LQ_LEGACY_G; + + rate->bw = RATE_MCS_CHAN_WIDTH_20; + rate->ldpc = false; + rate_mask = lq_sta->active_legacy_rate; + } else if (column->mode == RS_SISO) { + rate->type = lq_sta->is_vht ? LQ_VHT_SISO : LQ_HT_SISO; + rate_mask = lq_sta->active_siso_rate; + } else if (column->mode == RS_MIMO2) { + rate->type = lq_sta->is_vht ? LQ_VHT_MIMO2 : LQ_HT_MIMO2; + rate_mask = lq_sta->active_mimo2_rate; + } else { + WARN_ONCE(1, "Bad column mode"); + } + + if (column->mode != RS_LEGACY) { + rate->bw = rs_bw_from_sta_bw(sta); + rate->ldpc = lq_sta->ldpc; + } + + search_tbl->column = col_id; + rs_set_expected_tpt_table(lq_sta, search_tbl); + + lq_sta->visited_columns |= BIT(col_id); + + /* Get the best matching rate if we're changing modes. e.g. + * SISO->MIMO, LEGACY->SISO, MIMO->SISO + */ + if (curr_column->mode != column->mode) { + rate_idx = rs_get_best_rate(mvm, lq_sta, search_tbl, + rate_mask, rate->index); + + if ((rate_idx == IWL_RATE_INVALID) || + !(BIT(rate_idx) & rate_mask)) { + IWL_DEBUG_RATE(mvm, + "can not switch with index %d" + " rate mask %lx\n", + rate_idx, rate_mask); + + goto err; + } + + rate->index = rate_idx; + } + + IWL_DEBUG_RATE(mvm, "Switched to column %d: Index %d\n", + col_id, rate->index); + + return 0; + +err: + rate->type = LQ_NONE; + return -1; +} + +static enum rs_action rs_get_rate_action(struct iwl_mvm *mvm, + struct iwl_scale_tbl_info *tbl, + s32 sr, int low, int high, + int current_tpt, + int low_tpt, int high_tpt) +{ + enum rs_action action = RS_ACTION_STAY; + + if ((sr <= RS_PERCENT(IWL_MVM_RS_SR_FORCE_DECREASE)) || + (current_tpt == 0)) { + IWL_DEBUG_RATE(mvm, + "Decrease rate because of low SR\n"); + return RS_ACTION_DOWNSCALE; + } + + if ((low_tpt == IWL_INVALID_VALUE) && + (high_tpt == IWL_INVALID_VALUE) && + (high != IWL_RATE_INVALID)) { + IWL_DEBUG_RATE(mvm, + "No data about high/low rates. Increase rate\n"); + return RS_ACTION_UPSCALE; + } + + if ((high_tpt == IWL_INVALID_VALUE) && + (high != IWL_RATE_INVALID) && + (low_tpt != IWL_INVALID_VALUE) && + (low_tpt < current_tpt)) { + IWL_DEBUG_RATE(mvm, + "No data about high rate and low rate is worse. Increase rate\n"); + return RS_ACTION_UPSCALE; + } + + if ((high_tpt != IWL_INVALID_VALUE) && + (high_tpt > current_tpt)) { + IWL_DEBUG_RATE(mvm, + "Higher rate is better. Increate rate\n"); + return RS_ACTION_UPSCALE; + } + + if ((low_tpt != IWL_INVALID_VALUE) && + (high_tpt != IWL_INVALID_VALUE) && + (low_tpt < current_tpt) && + (high_tpt < current_tpt)) { + IWL_DEBUG_RATE(mvm, + "Both high and low are worse. Maintain rate\n"); + return RS_ACTION_STAY; + } + + if ((low_tpt != IWL_INVALID_VALUE) && + (low_tpt > current_tpt)) { + IWL_DEBUG_RATE(mvm, + "Lower rate is better\n"); + action = RS_ACTION_DOWNSCALE; + goto out; + } + + if ((low_tpt == IWL_INVALID_VALUE) && + (low != IWL_RATE_INVALID)) { + IWL_DEBUG_RATE(mvm, + "No data about lower rate\n"); + action = RS_ACTION_DOWNSCALE; + goto out; + } + + IWL_DEBUG_RATE(mvm, "Maintain rate\n"); + +out: + if ((action == RS_ACTION_DOWNSCALE) && (low != IWL_RATE_INVALID)) { + if (sr >= RS_PERCENT(IWL_MVM_RS_SR_NO_DECREASE)) { + IWL_DEBUG_RATE(mvm, + "SR is above NO DECREASE. Avoid downscale\n"); + action = RS_ACTION_STAY; + } else if (current_tpt > (100 * tbl->expected_tpt[low])) { + IWL_DEBUG_RATE(mvm, + "Current TPT is higher than max expected in low rate. Avoid downscale\n"); + action = RS_ACTION_STAY; + } else { + IWL_DEBUG_RATE(mvm, "Decrease rate\n"); + } + } + + return action; +} + +static bool rs_stbc_allow(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta) +{ + /* Our chip supports Tx STBC and the peer is an HT/VHT STA which + * supports STBC of at least 1*SS + */ + if (!lq_sta->stbc_capable) + return false; + + if (!iwl_mvm_bt_coex_is_mimo_allowed(mvm, sta)) + return false; + + return true; +} + +static void rs_get_adjacent_txp(struct iwl_mvm *mvm, int index, + int *weaker, int *stronger) +{ + *weaker = index + IWL_MVM_RS_TPC_TX_POWER_STEP; + if (*weaker > TPC_MAX_REDUCTION) + *weaker = TPC_INVALID; + + *stronger = index - IWL_MVM_RS_TPC_TX_POWER_STEP; + if (*stronger < 0) + *stronger = TPC_INVALID; +} + +static bool rs_tpc_allowed(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct rs_rate *rate, enum nl80211_band band) +{ + int index = rate->index; + bool cam = (iwlmvm_mod_params.power_scheme == IWL_POWER_SCHEME_CAM); + bool sta_ps_disabled = (vif->type == NL80211_IFTYPE_STATION && + !vif->cfg.ps); + + IWL_DEBUG_RATE(mvm, "cam: %d sta_ps_disabled %d\n", + cam, sta_ps_disabled); + /* + * allow tpc only if power management is enabled, or bt coex + * activity grade allows it and we are on 2.4Ghz. + */ + if ((cam || sta_ps_disabled) && + !iwl_mvm_bt_coex_is_tpc_allowed(mvm, band)) + return false; + + IWL_DEBUG_RATE(mvm, "check rate, table type: %d\n", rate->type); + if (is_legacy(rate)) + return index == IWL_RATE_54M_INDEX; + if (is_ht(rate)) + return index == IWL_RATE_MCS_7_INDEX; + if (is_vht(rate)) + return index == IWL_RATE_MCS_9_INDEX; + + WARN_ON_ONCE(1); + return false; +} + +enum tpc_action { + TPC_ACTION_STAY, + TPC_ACTION_DECREASE, + TPC_ACTION_INCREASE, + TPC_ACTION_NO_RESTIRCTION, +}; + +static enum tpc_action rs_get_tpc_action(struct iwl_mvm *mvm, + s32 sr, int weak, int strong, + int current_tpt, + int weak_tpt, int strong_tpt) +{ + /* stay until we have valid tpt */ + if (current_tpt == IWL_INVALID_VALUE) { + IWL_DEBUG_RATE(mvm, "no current tpt. stay.\n"); + return TPC_ACTION_STAY; + } + + /* Too many failures, increase txp */ + if (sr <= RS_PERCENT(IWL_MVM_RS_TPC_SR_FORCE_INCREASE) || + current_tpt == 0) { + IWL_DEBUG_RATE(mvm, "increase txp because of weak SR\n"); + return TPC_ACTION_NO_RESTIRCTION; + } + + /* try decreasing first if applicable */ + if (sr >= RS_PERCENT(IWL_MVM_RS_TPC_SR_NO_INCREASE) && + weak != TPC_INVALID) { + if (weak_tpt == IWL_INVALID_VALUE && + (strong_tpt == IWL_INVALID_VALUE || + current_tpt >= strong_tpt)) { + IWL_DEBUG_RATE(mvm, + "no weak txp measurement. decrease txp\n"); + return TPC_ACTION_DECREASE; + } + + if (weak_tpt > current_tpt) { + IWL_DEBUG_RATE(mvm, + "lower txp has better tpt. decrease txp\n"); + return TPC_ACTION_DECREASE; + } + } + + /* next, increase if needed */ + if (sr < RS_PERCENT(IWL_MVM_RS_TPC_SR_NO_INCREASE) && + strong != TPC_INVALID) { + if (weak_tpt == IWL_INVALID_VALUE && + strong_tpt != IWL_INVALID_VALUE && + current_tpt < strong_tpt) { + IWL_DEBUG_RATE(mvm, + "higher txp has better tpt. increase txp\n"); + return TPC_ACTION_INCREASE; + } + + if (weak_tpt < current_tpt && + (strong_tpt == IWL_INVALID_VALUE || + strong_tpt > current_tpt)) { + IWL_DEBUG_RATE(mvm, + "lower txp has worse tpt. increase txp\n"); + return TPC_ACTION_INCREASE; + } + } + + IWL_DEBUG_RATE(mvm, "no need to increase or decrease txp - stay\n"); + return TPC_ACTION_STAY; +} + +static bool rs_tpc_perform(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl) +{ + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + struct ieee80211_vif *vif = mvm_sta->vif; + struct ieee80211_chanctx_conf *chanctx_conf; + enum nl80211_band band; + struct iwl_rate_scale_data *window; + struct rs_rate *rate = &tbl->rate; + enum tpc_action action; + s32 sr; + u8 cur = lq_sta->lq.reduced_tpc; + int current_tpt; + int weak, strong; + int weak_tpt = IWL_INVALID_VALUE, strong_tpt = IWL_INVALID_VALUE; + +#ifdef CONFIG_MAC80211_DEBUGFS + if (lq_sta->pers.dbg_fixed_txp_reduction <= TPC_MAX_REDUCTION) { + IWL_DEBUG_RATE(mvm, "fixed tpc: %d\n", + lq_sta->pers.dbg_fixed_txp_reduction); + lq_sta->lq.reduced_tpc = lq_sta->pers.dbg_fixed_txp_reduction; + return cur != lq_sta->pers.dbg_fixed_txp_reduction; + } +#endif + + rcu_read_lock(); + chanctx_conf = rcu_dereference(vif->bss_conf.chanctx_conf); + if (WARN_ON(!chanctx_conf)) + band = NUM_NL80211_BANDS; + else + band = chanctx_conf->def.chan->band; + rcu_read_unlock(); + + if (!rs_tpc_allowed(mvm, vif, rate, band)) { + IWL_DEBUG_RATE(mvm, + "tpc is not allowed. remove txp restrictions\n"); + lq_sta->lq.reduced_tpc = TPC_NO_REDUCTION; + return cur != TPC_NO_REDUCTION; + } + + rs_get_adjacent_txp(mvm, cur, &weak, &strong); + + /* Collect measured throughputs for current and adjacent rates */ + window = tbl->tpc_win; + sr = window[cur].success_ratio; + current_tpt = window[cur].average_tpt; + if (weak != TPC_INVALID) + weak_tpt = window[weak].average_tpt; + if (strong != TPC_INVALID) + strong_tpt = window[strong].average_tpt; + + IWL_DEBUG_RATE(mvm, + "(TPC: %d): cur_tpt %d SR %d weak %d strong %d weak_tpt %d strong_tpt %d\n", + cur, current_tpt, sr, weak, strong, + weak_tpt, strong_tpt); + + action = rs_get_tpc_action(mvm, sr, weak, strong, + current_tpt, weak_tpt, strong_tpt); + + /* override actions if we are on the edge */ + if (weak == TPC_INVALID && action == TPC_ACTION_DECREASE) { + IWL_DEBUG_RATE(mvm, "already in lowest txp, stay\n"); + action = TPC_ACTION_STAY; + } else if (strong == TPC_INVALID && + (action == TPC_ACTION_INCREASE || + action == TPC_ACTION_NO_RESTIRCTION)) { + IWL_DEBUG_RATE(mvm, "already in highest txp, stay\n"); + action = TPC_ACTION_STAY; + } + + switch (action) { + case TPC_ACTION_DECREASE: + lq_sta->lq.reduced_tpc = weak; + return true; + case TPC_ACTION_INCREASE: + lq_sta->lq.reduced_tpc = strong; + return true; + case TPC_ACTION_NO_RESTIRCTION: + lq_sta->lq.reduced_tpc = TPC_NO_REDUCTION; + return true; + case TPC_ACTION_STAY: + /* do nothing */ + break; + } + return false; +} + +/* + * Do rate scaling and search for new modulation mode. + */ +static void rs_rate_scale_perform(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta, + int tid, bool ndp) +{ + int low = IWL_RATE_INVALID; + int high = IWL_RATE_INVALID; + int index; + struct iwl_rate_scale_data *window = NULL; + int current_tpt = IWL_INVALID_VALUE; + int low_tpt = IWL_INVALID_VALUE; + int high_tpt = IWL_INVALID_VALUE; + u32 fail_count; + enum rs_action scale_action = RS_ACTION_STAY; + u16 rate_mask; + u8 update_lq = 0; + struct iwl_scale_tbl_info *tbl, *tbl1; + u8 active_tbl = 0; + u8 done_search = 0; + u16 high_low; + s32 sr; + u8 prev_agg = lq_sta->is_agg; + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct rs_rate *rate; + + lq_sta->is_agg = !!mvmsta->agg_tids; + + /* + * Select rate-scale / modulation-mode table to work with in + * the rest of this function: "search" if searching for better + * modulation mode, or "active" if doing rate scaling within a mode. + */ + if (!lq_sta->search_better_tbl) + active_tbl = lq_sta->active_tbl; + else + active_tbl = rs_search_tbl(lq_sta->active_tbl); + + tbl = &(lq_sta->lq_info[active_tbl]); + rate = &tbl->rate; + + if (prev_agg != lq_sta->is_agg) { + IWL_DEBUG_RATE(mvm, + "Aggregation changed: prev %d current %d. Update expected TPT table\n", + prev_agg, lq_sta->is_agg); + rs_set_expected_tpt_table(lq_sta, tbl); + rs_rate_scale_clear_tbl_windows(mvm, tbl); + } + + /* current tx rate */ + index = rate->index; + + /* rates available for this association, and for modulation mode */ + rate_mask = rs_get_supported_rates(lq_sta, rate); + + if (!(BIT(index) & rate_mask)) { + IWL_ERR(mvm, "Current Rate is not valid\n"); + if (lq_sta->search_better_tbl) { + /* revert to active table if search table is not valid*/ + rate->type = LQ_NONE; + lq_sta->search_better_tbl = 0; + tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + rs_update_rate_tbl(mvm, sta, lq_sta, tbl); + } + return; + } + + /* Get expected throughput table and history window for current rate */ + if (!tbl->expected_tpt) { + IWL_ERR(mvm, "tbl->expected_tpt is NULL\n"); + return; + } + + /* TODO: handle rate_idx_mask and rate_idx_mcs_mask */ + window = &(tbl->win[index]); + + /* + * If there is not enough history to calculate actual average + * throughput, keep analyzing results of more tx frames, without + * changing rate or mode (bypass most of the rest of this function). + * Set up new rate table in uCode only if old rate is not supported + * in current association (use new rate found above). + */ + fail_count = window->counter - window->success_counter; + if ((fail_count < IWL_MVM_RS_RATE_MIN_FAILURE_TH) && + (window->success_counter < IWL_MVM_RS_RATE_MIN_SUCCESS_TH)) { + IWL_DEBUG_RATE(mvm, + "%s: Test Window: succ %d total %d\n", + rs_pretty_rate(rate), + window->success_counter, window->counter); + + /* Can't calculate this yet; not enough history */ + window->average_tpt = IWL_INVALID_VALUE; + + /* Should we stay with this modulation mode, + * or search for a new one? */ + rs_stay_in_table(lq_sta, false); + + return; + } + + /* If we are searching for better modulation mode, check success. */ + if (lq_sta->search_better_tbl) { + /* If good success, continue using the "search" mode; + * no need to send new link quality command, since we're + * continuing to use the setup that we've been trying. */ + if (window->average_tpt > lq_sta->last_tpt) { + IWL_DEBUG_RATE(mvm, + "SWITCHING TO NEW TABLE SR: %d " + "cur-tpt %d old-tpt %d\n", + window->success_ratio, + window->average_tpt, + lq_sta->last_tpt); + + /* Swap tables; "search" becomes "active" */ + lq_sta->active_tbl = active_tbl; + current_tpt = window->average_tpt; + /* Else poor success; go back to mode in "active" table */ + } else { + IWL_DEBUG_RATE(mvm, + "GOING BACK TO THE OLD TABLE: SR %d " + "cur-tpt %d old-tpt %d\n", + window->success_ratio, + window->average_tpt, + lq_sta->last_tpt); + + /* Nullify "search" table */ + rate->type = LQ_NONE; + + /* Revert to "active" table */ + active_tbl = lq_sta->active_tbl; + tbl = &(lq_sta->lq_info[active_tbl]); + + /* Revert to "active" rate and throughput info */ + index = tbl->rate.index; + current_tpt = lq_sta->last_tpt; + + /* Need to set up a new rate table in uCode */ + update_lq = 1; + } + + /* Either way, we've made a decision; modulation mode + * search is done, allow rate adjustment next time. */ + lq_sta->search_better_tbl = 0; + done_search = 1; /* Don't switch modes below! */ + goto lq_update; + } + + /* (Else) not in search of better modulation mode, try for better + * starting rate, while staying in this mode. */ + high_low = rs_get_adjacent_rate(mvm, index, rate_mask, rate->type); + low = high_low & 0xff; + high = (high_low >> 8) & 0xff; + + /* TODO: handle rate_idx_mask and rate_idx_mcs_mask */ + + sr = window->success_ratio; + + /* Collect measured throughputs for current and adjacent rates */ + current_tpt = window->average_tpt; + if (low != IWL_RATE_INVALID) + low_tpt = tbl->win[low].average_tpt; + if (high != IWL_RATE_INVALID) + high_tpt = tbl->win[high].average_tpt; + + IWL_DEBUG_RATE(mvm, + "%s: cur_tpt %d SR %d low %d high %d low_tpt %d high_tpt %d\n", + rs_pretty_rate(rate), current_tpt, sr, + low, high, low_tpt, high_tpt); + + scale_action = rs_get_rate_action(mvm, tbl, sr, low, high, + current_tpt, low_tpt, high_tpt); + + /* Force a search in case BT doesn't like us being in MIMO */ + if (is_mimo(rate) && + !iwl_mvm_bt_coex_is_mimo_allowed(mvm, sta)) { + IWL_DEBUG_RATE(mvm, + "BT Coex forbids MIMO. Search for new config\n"); + rs_stay_in_table(lq_sta, true); + goto lq_update; + } + + switch (scale_action) { + case RS_ACTION_DOWNSCALE: + /* Decrease starting rate, update uCode's rate table */ + if (low != IWL_RATE_INVALID) { + update_lq = 1; + index = low; + } else { + IWL_DEBUG_RATE(mvm, + "At the bottom rate. Can't decrease\n"); + } + + break; + case RS_ACTION_UPSCALE: + /* Increase starting rate, update uCode's rate table */ + if (high != IWL_RATE_INVALID) { + update_lq = 1; + index = high; + } else { + IWL_DEBUG_RATE(mvm, + "At the top rate. Can't increase\n"); + } + + break; + case RS_ACTION_STAY: + /* No change */ + if (lq_sta->rs_state == RS_STATE_STAY_IN_COLUMN) + update_lq = rs_tpc_perform(mvm, sta, lq_sta, tbl); + break; + default: + break; + } + +lq_update: + /* Replace uCode's rate table for the destination station. */ + if (update_lq) { + tbl->rate.index = index; + if (IWL_MVM_RS_80_20_FAR_RANGE_TWEAK) + rs_tweak_rate_tbl(mvm, sta, lq_sta, tbl, scale_action); + rs_set_amsdu_len(mvm, sta, tbl, scale_action); + rs_update_rate_tbl(mvm, sta, lq_sta, tbl); + } + + rs_stay_in_table(lq_sta, false); + + /* + * Search for new modulation mode if we're: + * 1) Not changing rates right now + * 2) Not just finishing up a search + * 3) Allowing a new search + */ + if (!update_lq && !done_search && + lq_sta->rs_state == RS_STATE_SEARCH_CYCLE_STARTED + && window->counter) { + enum rs_column next_column; + + /* Save current throughput to compare with "search" throughput*/ + lq_sta->last_tpt = current_tpt; + + IWL_DEBUG_RATE(mvm, + "Start Search: update_lq %d done_search %d rs_state %d win->counter %d\n", + update_lq, done_search, lq_sta->rs_state, + window->counter); + + next_column = rs_get_next_column(mvm, lq_sta, sta, tbl); + if (next_column != RS_COLUMN_INVALID) { + int ret = rs_switch_to_column(mvm, lq_sta, sta, + next_column); + if (!ret) + lq_sta->search_better_tbl = 1; + } else { + IWL_DEBUG_RATE(mvm, + "No more columns to explore in search cycle. Go to RS_STATE_SEARCH_CYCLE_ENDED\n"); + lq_sta->rs_state = RS_STATE_SEARCH_CYCLE_ENDED; + } + + /* If new "search" mode was selected, set up in uCode table */ + if (lq_sta->search_better_tbl) { + /* Access the "search" table, clear its history. */ + tbl = &lq_sta->lq_info[rs_search_tbl(lq_sta->active_tbl)]; + rs_rate_scale_clear_tbl_windows(mvm, tbl); + + /* Use new "search" start rate */ + index = tbl->rate.index; + + rs_dump_rate(mvm, &tbl->rate, + "Switch to SEARCH TABLE:"); + rs_update_rate_tbl(mvm, sta, lq_sta, tbl); + } else { + done_search = 1; + } + } + + if (!ndp) + rs_tl_turn_on_agg(mvm, mvmsta, tid, lq_sta, sta); + + if (done_search && lq_sta->rs_state == RS_STATE_SEARCH_CYCLE_ENDED) { + tbl1 = &(lq_sta->lq_info[lq_sta->active_tbl]); + rs_set_stay_in_table(mvm, is_legacy(&tbl1->rate), lq_sta); + } +} + +struct rs_init_rate_info { + s8 rssi; + u8 rate_idx; +}; + +static const struct rs_init_rate_info rs_optimal_rates_24ghz_legacy[] = { + { -60, IWL_RATE_54M_INDEX }, + { -64, IWL_RATE_48M_INDEX }, + { -68, IWL_RATE_36M_INDEX }, + { -80, IWL_RATE_24M_INDEX }, + { -84, IWL_RATE_18M_INDEX }, + { -85, IWL_RATE_12M_INDEX }, + { -86, IWL_RATE_11M_INDEX }, + { -88, IWL_RATE_5M_INDEX }, + { -90, IWL_RATE_2M_INDEX }, + { S8_MIN, IWL_RATE_1M_INDEX }, +}; + +static const struct rs_init_rate_info rs_optimal_rates_5ghz_legacy[] = { + { -60, IWL_RATE_54M_INDEX }, + { -64, IWL_RATE_48M_INDEX }, + { -72, IWL_RATE_36M_INDEX }, + { -80, IWL_RATE_24M_INDEX }, + { -84, IWL_RATE_18M_INDEX }, + { -85, IWL_RATE_12M_INDEX }, + { -87, IWL_RATE_9M_INDEX }, + { S8_MIN, IWL_RATE_6M_INDEX }, +}; + +static const struct rs_init_rate_info rs_optimal_rates_ht[] = { + { -60, IWL_RATE_MCS_7_INDEX }, + { -64, IWL_RATE_MCS_6_INDEX }, + { -68, IWL_RATE_MCS_5_INDEX }, + { -72, IWL_RATE_MCS_4_INDEX }, + { -80, IWL_RATE_MCS_3_INDEX }, + { -84, IWL_RATE_MCS_2_INDEX }, + { -85, IWL_RATE_MCS_1_INDEX }, + { S8_MIN, IWL_RATE_MCS_0_INDEX}, +}; + +/* MCS index 9 is not valid for 20MHz VHT channel width, + * but is ok for 40, 80 and 160MHz channels. + */ +static const struct rs_init_rate_info rs_optimal_rates_vht_20mhz[] = { + { -60, IWL_RATE_MCS_8_INDEX }, + { -64, IWL_RATE_MCS_7_INDEX }, + { -68, IWL_RATE_MCS_6_INDEX }, + { -72, IWL_RATE_MCS_5_INDEX }, + { -80, IWL_RATE_MCS_4_INDEX }, + { -84, IWL_RATE_MCS_3_INDEX }, + { -85, IWL_RATE_MCS_2_INDEX }, + { -87, IWL_RATE_MCS_1_INDEX }, + { S8_MIN, IWL_RATE_MCS_0_INDEX}, +}; + +static const struct rs_init_rate_info rs_optimal_rates_vht[] = { + { -60, IWL_RATE_MCS_9_INDEX }, + { -64, IWL_RATE_MCS_8_INDEX }, + { -68, IWL_RATE_MCS_7_INDEX }, + { -72, IWL_RATE_MCS_6_INDEX }, + { -80, IWL_RATE_MCS_5_INDEX }, + { -84, IWL_RATE_MCS_4_INDEX }, + { -85, IWL_RATE_MCS_3_INDEX }, + { -87, IWL_RATE_MCS_2_INDEX }, + { -88, IWL_RATE_MCS_1_INDEX }, + { S8_MIN, IWL_RATE_MCS_0_INDEX }, +}; + +#define IWL_RS_LOW_RSSI_THRESHOLD (-76) /* dBm */ + +/* Init the optimal rate based on STA caps + * This combined with rssi is used to report the last tx rate + * to userspace when we haven't transmitted enough frames. + */ +static void rs_init_optimal_rate(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta) +{ + struct rs_rate *rate = &lq_sta->optimal_rate; + + if (lq_sta->max_mimo2_rate_idx != IWL_RATE_INVALID) + rate->type = lq_sta->is_vht ? LQ_VHT_MIMO2 : LQ_HT_MIMO2; + else if (lq_sta->max_siso_rate_idx != IWL_RATE_INVALID) + rate->type = lq_sta->is_vht ? LQ_VHT_SISO : LQ_HT_SISO; + else if (lq_sta->band == NL80211_BAND_5GHZ) + rate->type = LQ_LEGACY_A; + else + rate->type = LQ_LEGACY_G; + + rate->bw = rs_bw_from_sta_bw(sta); + rate->sgi = rs_sgi_allow(mvm, sta, rate, NULL); + + /* ANT/LDPC/STBC aren't relevant for the rate reported to userspace */ + + if (is_mimo(rate)) { + lq_sta->optimal_rate_mask = lq_sta->active_mimo2_rate; + } else if (is_siso(rate)) { + lq_sta->optimal_rate_mask = lq_sta->active_siso_rate; + } else { + lq_sta->optimal_rate_mask = lq_sta->active_legacy_rate; + + if (lq_sta->band == NL80211_BAND_5GHZ) { + lq_sta->optimal_rates = rs_optimal_rates_5ghz_legacy; + lq_sta->optimal_nentries = + ARRAY_SIZE(rs_optimal_rates_5ghz_legacy); + } else { + lq_sta->optimal_rates = rs_optimal_rates_24ghz_legacy; + lq_sta->optimal_nentries = + ARRAY_SIZE(rs_optimal_rates_24ghz_legacy); + } + } + + if (is_vht(rate)) { + if (rate->bw == RATE_MCS_CHAN_WIDTH_20) { + lq_sta->optimal_rates = rs_optimal_rates_vht_20mhz; + lq_sta->optimal_nentries = + ARRAY_SIZE(rs_optimal_rates_vht_20mhz); + } else { + lq_sta->optimal_rates = rs_optimal_rates_vht; + lq_sta->optimal_nentries = + ARRAY_SIZE(rs_optimal_rates_vht); + } + } else if (is_ht(rate)) { + lq_sta->optimal_rates = rs_optimal_rates_ht; + lq_sta->optimal_nentries = ARRAY_SIZE(rs_optimal_rates_ht); + } +} + +/* Compute the optimal rate index based on RSSI */ +static struct rs_rate *rs_get_optimal_rate(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta) +{ + struct rs_rate *rate = &lq_sta->optimal_rate; + int i; + + rate->index = find_first_bit(&lq_sta->optimal_rate_mask, + BITS_PER_LONG); + + for (i = 0; i < lq_sta->optimal_nentries; i++) { + int rate_idx = lq_sta->optimal_rates[i].rate_idx; + + if ((lq_sta->pers.last_rssi >= lq_sta->optimal_rates[i].rssi) && + (BIT(rate_idx) & lq_sta->optimal_rate_mask)) { + rate->index = rate_idx; + break; + } + } + + return rate; +} + +/* Choose an initial legacy rate and antenna to use based on the RSSI + * of last Rx + */ +static void rs_get_initial_rate(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta, + enum nl80211_band band, + struct rs_rate *rate) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + int i, nentries; + unsigned long active_rate; + s8 best_rssi = S8_MIN; + u8 best_ant = ANT_NONE; + u8 valid_tx_ant = iwl_mvm_get_valid_tx_ant(mvm); + const struct rs_init_rate_info *initial_rates; + + for (i = 0; i < ARRAY_SIZE(lq_sta->pers.chain_signal); i++) { + if (!(lq_sta->pers.chains & BIT(i))) + continue; + + if (lq_sta->pers.chain_signal[i] > best_rssi) { + best_rssi = lq_sta->pers.chain_signal[i]; + best_ant = BIT(i); + } + } + + IWL_DEBUG_RATE(mvm, "Best ANT: %s Best RSSI: %d\n", + iwl_rs_pretty_ant(best_ant), best_rssi); + + if (best_ant != ANT_A && best_ant != ANT_B) + rate->ant = first_antenna(valid_tx_ant); + else + rate->ant = best_ant; + + rate->sgi = false; + rate->ldpc = false; + rate->bw = RATE_MCS_CHAN_WIDTH_20; + + rate->index = find_first_bit(&lq_sta->active_legacy_rate, + BITS_PER_LONG); + + if (band == NL80211_BAND_5GHZ) { + rate->type = LQ_LEGACY_A; + initial_rates = rs_optimal_rates_5ghz_legacy; + nentries = ARRAY_SIZE(rs_optimal_rates_5ghz_legacy); + } else { + rate->type = LQ_LEGACY_G; + initial_rates = rs_optimal_rates_24ghz_legacy; + nentries = ARRAY_SIZE(rs_optimal_rates_24ghz_legacy); + } + + if (!IWL_MVM_RS_RSSI_BASED_INIT_RATE) + goto out; + + /* Start from a higher rate if the corresponding debug capability + * is enabled. The rate is chosen according to AP capabilities. + * In case of VHT/HT when the rssi is low fallback to the case of + * legacy rates. + */ + if (sta->deflink.vht_cap.vht_supported && + best_rssi > IWL_RS_LOW_RSSI_THRESHOLD) { + /* + * In AP mode, when a new station associates, rs is initialized + * immediately upon association completion, before the phy + * context is updated with the association parameters, so the + * sta bandwidth might be wider than the phy context allows. + * To avoid this issue, always initialize rs with 20mhz + * bandwidth rate, and after authorization, when the phy context + * is already up-to-date, re-init rs with the correct bw. + */ + u32 bw = mvmsta->sta_state < IEEE80211_STA_AUTHORIZED ? + RATE_MCS_CHAN_WIDTH_20 : rs_bw_from_sta_bw(sta); + + switch (bw) { + case RATE_MCS_CHAN_WIDTH_40: + case RATE_MCS_CHAN_WIDTH_80: + case RATE_MCS_CHAN_WIDTH_160: + initial_rates = rs_optimal_rates_vht; + nentries = ARRAY_SIZE(rs_optimal_rates_vht); + break; + case RATE_MCS_CHAN_WIDTH_20: + initial_rates = rs_optimal_rates_vht_20mhz; + nentries = ARRAY_SIZE(rs_optimal_rates_vht_20mhz); + break; + default: + IWL_ERR(mvm, "Invalid BW %d\n", + sta->deflink.bandwidth); + goto out; + } + + active_rate = lq_sta->active_siso_rate; + rate->type = LQ_VHT_SISO; + rate->bw = bw; + } else if (sta->deflink.ht_cap.ht_supported && + best_rssi > IWL_RS_LOW_RSSI_THRESHOLD) { + initial_rates = rs_optimal_rates_ht; + nentries = ARRAY_SIZE(rs_optimal_rates_ht); + active_rate = lq_sta->active_siso_rate; + rate->type = LQ_HT_SISO; + } else { + active_rate = lq_sta->active_legacy_rate; + } + + for (i = 0; i < nentries; i++) { + int rate_idx = initial_rates[i].rate_idx; + + if ((best_rssi >= initial_rates[i].rssi) && + (BIT(rate_idx) & active_rate)) { + rate->index = rate_idx; + break; + } + } + +out: + rs_dump_rate(mvm, rate, "INITIAL"); +} + +/* Save info about RSSI of last Rx */ +void rs_update_last_rssi(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvmsta, + struct ieee80211_rx_status *rx_status) +{ + struct iwl_lq_sta *lq_sta = &mvmsta->lq_sta.rs_drv; + int i; + + lq_sta->pers.chains = rx_status->chains; + lq_sta->pers.chain_signal[0] = rx_status->chain_signal[0]; + lq_sta->pers.chain_signal[1] = rx_status->chain_signal[1]; + lq_sta->pers.last_rssi = S8_MIN; + + for (i = 0; i < ARRAY_SIZE(lq_sta->pers.chain_signal); i++) { + if (!(lq_sta->pers.chains & BIT(i))) + continue; + + if (lq_sta->pers.chain_signal[i] > lq_sta->pers.last_rssi) + lq_sta->pers.last_rssi = lq_sta->pers.chain_signal[i]; + } +} + +/* + * rs_initialize_lq - Initialize a station's hardware rate table + * + * The uCode's station table contains a table of fallback rates + * for automatic fallback during transmission. + * + * NOTE: This sets up a default set of values. These will be replaced later + * if the driver's iwl-agn-rs rate scaling algorithm is used, instead of + * rc80211_simple. + * + * NOTE: Run REPLY_ADD_STA command to set up station table entry, before + * calling this function (which runs REPLY_TX_LINK_QUALITY_CMD, + * which requires station table entry to exist). + */ +static void rs_initialize_lq(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta, + enum nl80211_band band) +{ + struct iwl_scale_tbl_info *tbl; + struct rs_rate *rate; + u8 active_tbl = 0; + + if (!sta || !lq_sta) + return; + + if (!lq_sta->search_better_tbl) + active_tbl = lq_sta->active_tbl; + else + active_tbl = rs_search_tbl(lq_sta->active_tbl); + + tbl = &(lq_sta->lq_info[active_tbl]); + rate = &tbl->rate; + + rs_get_initial_rate(mvm, sta, lq_sta, band, rate); + rs_init_optimal_rate(mvm, sta, lq_sta); + + WARN_ONCE(rate->ant != ANT_A && rate->ant != ANT_B, + "ant: 0x%x, chains 0x%x, fw tx ant: 0x%x, nvm tx ant: 0x%x\n", + rate->ant, lq_sta->pers.chains, mvm->fw->valid_tx_ant, + mvm->nvm_data ? mvm->nvm_data->valid_tx_ant : ANT_INVALID); + + tbl->column = rs_get_column_from_rate(rate); + + rs_set_expected_tpt_table(lq_sta, tbl); + rs_fill_lq_cmd(mvm, sta, lq_sta, rate); + /* TODO restore station should remember the lq cmd */ + iwl_mvm_send_lq_cmd(mvm, &lq_sta->lq); +} + +static void rs_drv_get_rate(void *mvm_r, struct ieee80211_sta *sta, + void *mvm_sta, + struct ieee80211_tx_rate_control *txrc) +{ + struct iwl_op_mode *op_mode = mvm_r; + struct iwl_mvm *mvm __maybe_unused = IWL_OP_MODE_GET_MVM(op_mode); + struct sk_buff *skb = txrc->skb; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct iwl_lq_sta *lq_sta; + struct rs_rate *optimal_rate; + u32 last_ucode_rate; + + if (sta && !iwl_mvm_sta_from_mac80211(sta)->vif) { + /* if vif isn't initialized mvm doesn't know about + * this station, so don't do anything with the it + */ + sta = NULL; + mvm_sta = NULL; + } + + if (!mvm_sta) + return; + + lq_sta = mvm_sta; + + spin_lock(&lq_sta->pers.lock); + iwl_mvm_hwrate_to_tx_rate_v1(lq_sta->last_rate_n_flags, + info->band, &info->control.rates[0]); + info->control.rates[0].count = 1; + + /* Report the optimal rate based on rssi and STA caps if we haven't + * converged yet (too little traffic) or exploring other modulations + */ + if (lq_sta->rs_state != RS_STATE_STAY_IN_COLUMN) { + optimal_rate = rs_get_optimal_rate(mvm, lq_sta); + last_ucode_rate = ucode_rate_from_rs_rate(mvm, + optimal_rate); + iwl_mvm_hwrate_to_tx_rate_v1(last_ucode_rate, info->band, + &txrc->reported_rate); + } + spin_unlock(&lq_sta->pers.lock); +} + +static void *rs_drv_alloc_sta(void *mvm_rate, struct ieee80211_sta *sta, + gfp_t gfp) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_op_mode *op_mode = (struct iwl_op_mode *)mvm_rate; + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + struct iwl_lq_sta *lq_sta = &mvmsta->lq_sta.rs_drv; + + IWL_DEBUG_RATE(mvm, "create station rate scale window\n"); + + lq_sta->pers.drv = mvm; +#ifdef CONFIG_MAC80211_DEBUGFS + lq_sta->pers.dbg_fixed_rate = 0; + lq_sta->pers.dbg_fixed_txp_reduction = TPC_INVALID; + lq_sta->pers.ss_force = RS_SS_FORCE_NONE; +#endif + lq_sta->pers.chains = 0; + memset(lq_sta->pers.chain_signal, 0, sizeof(lq_sta->pers.chain_signal)); + lq_sta->pers.last_rssi = S8_MIN; + + return lq_sta; +} + +static int rs_vht_highest_rx_mcs_index(struct ieee80211_sta_vht_cap *vht_cap, + int nss) +{ + u16 rx_mcs = le16_to_cpu(vht_cap->vht_mcs.rx_mcs_map) & + (0x3 << (2 * (nss - 1))); + rx_mcs >>= (2 * (nss - 1)); + + if (rx_mcs == IEEE80211_VHT_MCS_SUPPORT_0_7) + return IWL_RATE_MCS_7_INDEX; + else if (rx_mcs == IEEE80211_VHT_MCS_SUPPORT_0_8) + return IWL_RATE_MCS_8_INDEX; + else if (rx_mcs == IEEE80211_VHT_MCS_SUPPORT_0_9) + return IWL_RATE_MCS_9_INDEX; + + WARN_ON_ONCE(rx_mcs != IEEE80211_VHT_MCS_NOT_SUPPORTED); + return -1; +} + +static void rs_vht_set_enabled_rates(struct ieee80211_sta *sta, + struct ieee80211_sta_vht_cap *vht_cap, + struct iwl_lq_sta *lq_sta) +{ + int i; + int highest_mcs = rs_vht_highest_rx_mcs_index(vht_cap, 1); + + if (highest_mcs >= IWL_RATE_MCS_0_INDEX) { + for (i = IWL_RATE_MCS_0_INDEX; i <= highest_mcs; i++) { + if (i == IWL_RATE_9M_INDEX) + continue; + + /* VHT MCS9 isn't valid for 20Mhz for NSS=1,2 */ + if (i == IWL_RATE_MCS_9_INDEX && + sta->deflink.bandwidth == IEEE80211_STA_RX_BW_20) + continue; + + lq_sta->active_siso_rate |= BIT(i); + } + } + + if (sta->deflink.rx_nss < 2) + return; + + highest_mcs = rs_vht_highest_rx_mcs_index(vht_cap, 2); + if (highest_mcs >= IWL_RATE_MCS_0_INDEX) { + for (i = IWL_RATE_MCS_0_INDEX; i <= highest_mcs; i++) { + if (i == IWL_RATE_9M_INDEX) + continue; + + /* VHT MCS9 isn't valid for 20Mhz for NSS=1,2 */ + if (i == IWL_RATE_MCS_9_INDEX && + sta->deflink.bandwidth == IEEE80211_STA_RX_BW_20) + continue; + + lq_sta->active_mimo2_rate |= BIT(i); + } + } +} + +static void rs_ht_init(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta, + struct ieee80211_sta_ht_cap *ht_cap) +{ + /* active_siso_rate mask includes 9 MBits (bit 5), + * and CCK (bits 0-3), supp_rates[] does not; + * shift to convert format, force 9 MBits off. + */ + lq_sta->active_siso_rate = ht_cap->mcs.rx_mask[0] << 1; + lq_sta->active_siso_rate |= ht_cap->mcs.rx_mask[0] & 0x1; + lq_sta->active_siso_rate &= ~((u16)0x2); + lq_sta->active_siso_rate <<= IWL_FIRST_OFDM_RATE; + + lq_sta->active_mimo2_rate = ht_cap->mcs.rx_mask[1] << 1; + lq_sta->active_mimo2_rate |= ht_cap->mcs.rx_mask[1] & 0x1; + lq_sta->active_mimo2_rate &= ~((u16)0x2); + lq_sta->active_mimo2_rate <<= IWL_FIRST_OFDM_RATE; + + if (mvm->cfg->ht_params->ldpc && + (ht_cap->cap & IEEE80211_HT_CAP_LDPC_CODING)) + lq_sta->ldpc = true; + + if (mvm->cfg->ht_params->stbc && + (num_of_ant(iwl_mvm_get_valid_tx_ant(mvm)) > 1) && + (ht_cap->cap & IEEE80211_HT_CAP_RX_STBC)) + lq_sta->stbc_capable = true; + + lq_sta->is_vht = false; +} + +static void rs_vht_init(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta, + struct ieee80211_sta_vht_cap *vht_cap) +{ + rs_vht_set_enabled_rates(sta, vht_cap, lq_sta); + + if (mvm->cfg->ht_params->ldpc && + (vht_cap->cap & IEEE80211_VHT_CAP_RXLDPC)) + lq_sta->ldpc = true; + + if (mvm->cfg->ht_params->stbc && + (num_of_ant(iwl_mvm_get_valid_tx_ant(mvm)) > 1) && + (vht_cap->cap & IEEE80211_VHT_CAP_RXSTBC_MASK)) + lq_sta->stbc_capable = true; + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_BEAMFORMER) && + (num_of_ant(iwl_mvm_get_valid_tx_ant(mvm)) > 1) && + (vht_cap->cap & IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE)) + lq_sta->bfer_capable = true; + + lq_sta->is_vht = true; +} + +#ifdef CONFIG_IWLWIFI_DEBUGFS +void iwl_mvm_reset_frame_stats(struct iwl_mvm *mvm) +{ + spin_lock_bh(&mvm->drv_stats_lock); + memset(&mvm->drv_rx_stats, 0, sizeof(mvm->drv_rx_stats)); + spin_unlock_bh(&mvm->drv_stats_lock); +} + +void iwl_mvm_update_frame_stats(struct iwl_mvm *mvm, u32 rate, bool agg) +{ + u8 nss = 0; + + spin_lock(&mvm->drv_stats_lock); + + if (agg) + mvm->drv_rx_stats.agg_frames++; + + mvm->drv_rx_stats.success_frames++; + + switch (rate & RATE_MCS_CHAN_WIDTH_MSK_V1) { + case RATE_MCS_CHAN_WIDTH_20: + mvm->drv_rx_stats.bw_20_frames++; + break; + case RATE_MCS_CHAN_WIDTH_40: + mvm->drv_rx_stats.bw_40_frames++; + break; + case RATE_MCS_CHAN_WIDTH_80: + mvm->drv_rx_stats.bw_80_frames++; + break; + case RATE_MCS_CHAN_WIDTH_160: + mvm->drv_rx_stats.bw_160_frames++; + break; + default: + WARN_ONCE(1, "bad BW. rate 0x%x", rate); + } + + if (rate & RATE_MCS_HT_MSK_V1) { + mvm->drv_rx_stats.ht_frames++; + nss = ((rate & RATE_HT_MCS_NSS_MSK_V1) >> RATE_HT_MCS_NSS_POS_V1) + 1; + } else if (rate & RATE_MCS_VHT_MSK_V1) { + mvm->drv_rx_stats.vht_frames++; + nss = ((rate & RATE_VHT_MCS_NSS_MSK) >> + RATE_VHT_MCS_NSS_POS) + 1; + } else { + mvm->drv_rx_stats.legacy_frames++; + } + + if (nss == 1) + mvm->drv_rx_stats.siso_frames++; + else if (nss == 2) + mvm->drv_rx_stats.mimo2_frames++; + + if (rate & RATE_MCS_SGI_MSK_V1) + mvm->drv_rx_stats.sgi_frames++; + else + mvm->drv_rx_stats.ngi_frames++; + + mvm->drv_rx_stats.last_rates[mvm->drv_rx_stats.last_frame_idx] = rate; + mvm->drv_rx_stats.last_frame_idx = + (mvm->drv_rx_stats.last_frame_idx + 1) % + ARRAY_SIZE(mvm->drv_rx_stats.last_rates); + + spin_unlock(&mvm->drv_stats_lock); +} +#endif + +/* + * Called after adding a new station to initialize rate scaling + */ +static void rs_drv_rate_init(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + enum nl80211_band band) +{ + int i, j; + struct ieee80211_hw *hw = mvm->hw; + struct ieee80211_sta_ht_cap *ht_cap = &sta->deflink.ht_cap; + struct ieee80211_sta_vht_cap *vht_cap = &sta->deflink.vht_cap; + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_lq_sta *lq_sta = &mvmsta->lq_sta.rs_drv; + struct ieee80211_supported_band *sband; + unsigned long supp; /* must be unsigned long for for_each_set_bit */ + + lockdep_assert_held(&mvmsta->lq_sta.rs_drv.pers.lock); + + /* clear all non-persistent lq data */ + memset(lq_sta, 0, offsetof(typeof(*lq_sta), pers)); + + sband = hw->wiphy->bands[band]; + + lq_sta->lq.sta_id = mvmsta->sta_id; + mvmsta->amsdu_enabled = 0; + mvmsta->max_amsdu_len = sta->cur->max_amsdu_len; + + for (j = 0; j < LQ_SIZE; j++) + rs_rate_scale_clear_tbl_windows(mvm, &lq_sta->lq_info[j]); + + lq_sta->flush_timer = 0; + lq_sta->last_tx = jiffies; + + IWL_DEBUG_RATE(mvm, + "LQ: *** rate scale station global init for station %d ***\n", + mvmsta->sta_id); + /* TODO: what is a good starting rate for STA? About middle? Maybe not + * the lowest or the highest rate.. Could consider using RSSI from + * previous packets? Need to have IEEE 802.1X auth succeed immediately + * after assoc.. */ + + lq_sta->missed_rate_counter = IWL_MVM_RS_MISSED_RATE_MAX; + lq_sta->band = sband->band; + /* + * active legacy rates as per supported rates bitmap + */ + supp = sta->deflink.supp_rates[sband->band]; + lq_sta->active_legacy_rate = 0; + for_each_set_bit(i, &supp, BITS_PER_LONG) + lq_sta->active_legacy_rate |= BIT(sband->bitrates[i].hw_value); + + /* TODO: should probably account for rx_highest for both HT/VHT */ + if (!vht_cap || !vht_cap->vht_supported) + rs_ht_init(mvm, sta, lq_sta, ht_cap); + else + rs_vht_init(mvm, sta, lq_sta, vht_cap); + + lq_sta->max_legacy_rate_idx = + rs_get_max_rate_from_mask(lq_sta->active_legacy_rate); + lq_sta->max_siso_rate_idx = + rs_get_max_rate_from_mask(lq_sta->active_siso_rate); + lq_sta->max_mimo2_rate_idx = + rs_get_max_rate_from_mask(lq_sta->active_mimo2_rate); + + IWL_DEBUG_RATE(mvm, + "LEGACY=%lX SISO=%lX MIMO2=%lX VHT=%d LDPC=%d STBC=%d BFER=%d\n", + lq_sta->active_legacy_rate, + lq_sta->active_siso_rate, + lq_sta->active_mimo2_rate, + lq_sta->is_vht, lq_sta->ldpc, lq_sta->stbc_capable, + lq_sta->bfer_capable); + IWL_DEBUG_RATE(mvm, "MAX RATE: LEGACY=%d SISO=%d MIMO2=%d\n", + lq_sta->max_legacy_rate_idx, + lq_sta->max_siso_rate_idx, + lq_sta->max_mimo2_rate_idx); + + /* These values will be overridden later */ + lq_sta->lq.single_stream_ant_msk = + iwl_mvm_bt_coex_get_single_ant_msk(mvm, iwl_mvm_get_valid_tx_ant(mvm)); + lq_sta->lq.dual_stream_ant_msk = ANT_AB; + + /* as default allow aggregation for all tids */ + lq_sta->tx_agg_tid_en = IWL_AGG_ALL_TID; + lq_sta->is_agg = 0; +#ifdef CONFIG_IWLWIFI_DEBUGFS + iwl_mvm_reset_frame_stats(mvm); +#endif + rs_initialize_lq(mvm, sta, lq_sta, band); +} + +static void rs_drv_rate_update(void *mvm_r, + struct ieee80211_supported_band *sband, + struct cfg80211_chan_def *chandef, + struct ieee80211_sta *sta, + void *priv_sta, u32 changed) +{ + struct iwl_op_mode *op_mode = mvm_r; + struct iwl_mvm *mvm __maybe_unused = IWL_OP_MODE_GET_MVM(op_mode); + u8 tid; + + if (!iwl_mvm_sta_from_mac80211(sta)->vif) + return; + + /* Stop any ongoing aggregations as rs starts off assuming no agg */ + for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++) + ieee80211_stop_tx_ba_session(sta, tid); + + iwl_mvm_rs_rate_init(mvm, sta, sband->band, true); +} + +static void __iwl_mvm_rs_tx_status(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + int tid, struct ieee80211_tx_info *info, + bool ndp) +{ + int legacy_success; + int retries; + int i; + struct iwl_lq_cmd *table; + u32 lq_hwrate; + struct rs_rate lq_rate, tx_resp_rate; + struct iwl_scale_tbl_info *curr_tbl, *other_tbl, *tmp_tbl; + u32 tlc_info = (uintptr_t)info->status.status_driver_data[0]; + u8 reduced_txp = tlc_info & RS_DRV_DATA_TXP_MSK; + u8 lq_color = RS_DRV_DATA_LQ_COLOR_GET(tlc_info); + u32 tx_resp_hwrate = (uintptr_t)info->status.status_driver_data[1]; + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_lq_sta *lq_sta = &mvmsta->lq_sta.rs_drv; + + if (!lq_sta->pers.drv) { + IWL_DEBUG_RATE(mvm, "Rate scaling not initialized yet.\n"); + return; + } + + /* This packet was aggregated but doesn't carry status info */ + if ((info->flags & IEEE80211_TX_CTL_AMPDU) && + !(info->flags & IEEE80211_TX_STAT_AMPDU)) + return; + + if (rs_rate_from_ucode_rate(tx_resp_hwrate, info->band, + &tx_resp_rate)) { + WARN_ON_ONCE(1); + return; + } + +#ifdef CONFIG_MAC80211_DEBUGFS + /* Disable last tx check if we are debugging with fixed rate but + * update tx stats + */ + if (lq_sta->pers.dbg_fixed_rate) { + int index = tx_resp_rate.index; + enum rs_column column; + int attempts, success; + + column = rs_get_column_from_rate(&tx_resp_rate); + if (WARN_ONCE(column == RS_COLUMN_INVALID, + "Can't map rate 0x%x to column", + tx_resp_hwrate)) + return; + + if (info->flags & IEEE80211_TX_STAT_AMPDU) { + attempts = info->status.ampdu_len; + success = info->status.ampdu_ack_len; + } else { + attempts = info->status.rates[0].count; + success = !!(info->flags & IEEE80211_TX_STAT_ACK); + } + + lq_sta->pers.tx_stats[column][index].total += attempts; + lq_sta->pers.tx_stats[column][index].success += success; + + IWL_DEBUG_RATE(mvm, "Fixed rate 0x%x success %d attempts %d\n", + tx_resp_hwrate, success, attempts); + return; + } +#endif + + if (time_after(jiffies, + (unsigned long)(lq_sta->last_tx + + (IWL_MVM_RS_IDLE_TIMEOUT * HZ)))) { + IWL_DEBUG_RATE(mvm, "Tx idle for too long. reinit rs\n"); + /* reach here only in case of driver RS, call directly + * the unlocked version + */ + rs_drv_rate_init(mvm, sta, info->band); + return; + } + lq_sta->last_tx = jiffies; + + /* Ignore this Tx frame response if its initial rate doesn't match + * that of latest Link Quality command. There may be stragglers + * from a previous Link Quality command, but we're no longer interested + * in those; they're either from the "active" mode while we're trying + * to check "search" mode, or a prior "search" mode after we've moved + * to a new "search" mode (which might become the new "active" mode). + */ + table = &lq_sta->lq; + lq_hwrate = le32_to_cpu(table->rs_table[0]); + if (rs_rate_from_ucode_rate(lq_hwrate, info->band, &lq_rate)) { + WARN_ON_ONCE(1); + return; + } + + /* Here we actually compare this rate to the latest LQ command */ + if (lq_color != LQ_FLAG_COLOR_GET(table->flags)) { + IWL_DEBUG_RATE(mvm, + "tx resp color 0x%x does not match 0x%x\n", + lq_color, LQ_FLAG_COLOR_GET(table->flags)); + + /* Since rates mis-match, the last LQ command may have failed. + * After IWL_MISSED_RATE_MAX mis-matches, resync the uCode with + * ... driver. + */ + lq_sta->missed_rate_counter++; + if (lq_sta->missed_rate_counter > IWL_MVM_RS_MISSED_RATE_MAX) { + lq_sta->missed_rate_counter = 0; + IWL_DEBUG_RATE(mvm, + "Too many rates mismatch. Send sync LQ. rs_state %d\n", + lq_sta->rs_state); + iwl_mvm_send_lq_cmd(mvm, &lq_sta->lq); + } + /* Regardless, ignore this status info for outdated rate */ + return; + } + + /* Rate did match, so reset the missed_rate_counter */ + lq_sta->missed_rate_counter = 0; + + if (!lq_sta->search_better_tbl) { + curr_tbl = &lq_sta->lq_info[lq_sta->active_tbl]; + other_tbl = &lq_sta->lq_info[rs_search_tbl(lq_sta->active_tbl)]; + } else { + curr_tbl = &lq_sta->lq_info[rs_search_tbl(lq_sta->active_tbl)]; + other_tbl = &lq_sta->lq_info[lq_sta->active_tbl]; + } + + if (WARN_ON_ONCE(!rs_rate_column_match(&lq_rate, &curr_tbl->rate))) { + IWL_DEBUG_RATE(mvm, + "Neither active nor search matches tx rate\n"); + tmp_tbl = &lq_sta->lq_info[lq_sta->active_tbl]; + rs_dump_rate(mvm, &tmp_tbl->rate, "ACTIVE"); + tmp_tbl = &lq_sta->lq_info[rs_search_tbl(lq_sta->active_tbl)]; + rs_dump_rate(mvm, &tmp_tbl->rate, "SEARCH"); + rs_dump_rate(mvm, &lq_rate, "ACTUAL"); + + /* no matching table found, let's by-pass the data collection + * and continue to perform rate scale to find the rate table + */ + rs_stay_in_table(lq_sta, true); + goto done; + } + + /* Updating the frame history depends on whether packets were + * aggregated. + * + * For aggregation, all packets were transmitted at the same rate, the + * first index into rate scale table. + */ + if (info->flags & IEEE80211_TX_STAT_AMPDU) { + rs_collect_tpc_data(mvm, lq_sta, curr_tbl, tx_resp_rate.index, + info->status.ampdu_len, + info->status.ampdu_ack_len, + reduced_txp); + + /* ampdu_ack_len = 0 marks no BA was received. For TLC, treat + * it as a single frame loss as we don't want the success ratio + * to dip too quickly because a BA wasn't received. + * For TPC, there's no need for this optimisation since we want + * to recover very quickly from a bad power reduction and, + * therefore we'd like the success ratio to get an immediate hit + * when failing to get a BA, so we'd switch back to a lower or + * zero power reduction. When FW transmits agg with a rate + * different from the initial rate, it will not use reduced txp + * and will send BA notification twice (one empty with reduced + * txp equal to the value from LQ and one with reduced txp 0). + * We need to update counters for each txp level accordingly. + */ + if (info->status.ampdu_ack_len == 0) + info->status.ampdu_len = 1; + + rs_collect_tlc_data(mvm, mvmsta, tid, curr_tbl, + tx_resp_rate.index, + info->status.ampdu_len, + info->status.ampdu_ack_len); + + /* Update success/fail counts if not searching for new mode */ + if (lq_sta->rs_state == RS_STATE_STAY_IN_COLUMN) { + lq_sta->total_success += info->status.ampdu_ack_len; + lq_sta->total_failed += (info->status.ampdu_len - + info->status.ampdu_ack_len); + } + } else { + /* For legacy, update frame history with for each Tx retry. */ + retries = info->status.rates[0].count - 1; + /* HW doesn't send more than 15 retries */ + retries = min(retries, 15); + + /* The last transmission may have been successful */ + legacy_success = !!(info->flags & IEEE80211_TX_STAT_ACK); + /* Collect data for each rate used during failed TX attempts */ + for (i = 0; i <= retries; ++i) { + lq_hwrate = le32_to_cpu(table->rs_table[i]); + if (rs_rate_from_ucode_rate(lq_hwrate, info->band, + &lq_rate)) { + WARN_ON_ONCE(1); + return; + } + + /* Only collect stats if retried rate is in the same RS + * table as active/search. + */ + if (rs_rate_column_match(&lq_rate, &curr_tbl->rate)) + tmp_tbl = curr_tbl; + else if (rs_rate_column_match(&lq_rate, + &other_tbl->rate)) + tmp_tbl = other_tbl; + else + continue; + + rs_collect_tpc_data(mvm, lq_sta, tmp_tbl, + tx_resp_rate.index, 1, + i < retries ? 0 : legacy_success, + reduced_txp); + rs_collect_tlc_data(mvm, mvmsta, tid, tmp_tbl, + tx_resp_rate.index, 1, + i < retries ? 0 : legacy_success); + } + + /* Update success/fail counts if not searching for new mode */ + if (lq_sta->rs_state == RS_STATE_STAY_IN_COLUMN) { + lq_sta->total_success += legacy_success; + lq_sta->total_failed += retries + (1 - legacy_success); + } + } + /* The last TX rate is cached in lq_sta; it's set in if/else above */ + lq_sta->last_rate_n_flags = lq_hwrate; + IWL_DEBUG_RATE(mvm, "reduced txpower: %d\n", reduced_txp); +done: + /* See if there's a better rate or modulation mode to try. */ + if (sta->deflink.supp_rates[info->band]) + rs_rate_scale_perform(mvm, sta, lq_sta, tid, ndp); +} + +void iwl_mvm_rs_tx_status(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + int tid, struct ieee80211_tx_info *info, bool ndp) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + + /* If it's locked we are in middle of init flow + * just wait for next tx status to update the lq_sta data + */ + if (!spin_trylock(&mvmsta->lq_sta.rs_drv.pers.lock)) + return; + + __iwl_mvm_rs_tx_status(mvm, sta, tid, info, ndp); + spin_unlock(&mvmsta->lq_sta.rs_drv.pers.lock); +} + +#ifdef CONFIG_MAC80211_DEBUGFS +static void rs_build_rates_table_from_fixed(struct iwl_mvm *mvm, + struct iwl_lq_cmd *lq_cmd, + enum nl80211_band band, + u32 ucode_rate) +{ + struct rs_rate rate; + int i; + int num_rates = ARRAY_SIZE(lq_cmd->rs_table); + __le32 ucode_rate_le32 = cpu_to_le32(ucode_rate); + u8 ant = (ucode_rate & RATE_MCS_ANT_AB_MSK) >> RATE_MCS_ANT_POS; + + for (i = 0; i < num_rates; i++) + lq_cmd->rs_table[i] = ucode_rate_le32; + + if (rs_rate_from_ucode_rate(ucode_rate, band, &rate)) { + WARN_ON_ONCE(1); + return; + } + + if (is_mimo(&rate)) + lq_cmd->mimo_delim = num_rates - 1; + else + lq_cmd->mimo_delim = 0; + + lq_cmd->reduced_tpc = 0; + + if (num_of_ant(ant) == 1) + lq_cmd->single_stream_ant_msk = ant; + + if (!mvm->trans->trans_cfg->gen2) + lq_cmd->agg_frame_cnt_limit = LINK_QUAL_AGG_FRAME_LIMIT_DEF; + else + lq_cmd->agg_frame_cnt_limit = + LINK_QUAL_AGG_FRAME_LIMIT_GEN2_DEF; +} +#endif /* CONFIG_MAC80211_DEBUGFS */ + +static void rs_fill_rates_for_column(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta, + struct rs_rate *rate, + __le32 *rs_table, int *rs_table_index, + int num_rates, int num_retries, + u8 valid_tx_ant, bool toggle_ant) +{ + int i, j; + __le32 ucode_rate; + bool bottom_reached = false; + int prev_rate_idx = rate->index; + int end = LINK_QUAL_MAX_RETRY_NUM; + int index = *rs_table_index; + + for (i = 0; i < num_rates && index < end; i++) { + for (j = 0; j < num_retries && index < end; j++, index++) { + ucode_rate = cpu_to_le32(ucode_rate_from_rs_rate(mvm, + rate)); + rs_table[index] = ucode_rate; + if (toggle_ant) + rs_toggle_antenna(valid_tx_ant, rate); + } + + prev_rate_idx = rate->index; + bottom_reached = rs_get_lower_rate_in_column(lq_sta, rate); + if (bottom_reached && !is_legacy(rate)) + break; + } + + if (!bottom_reached && !is_legacy(rate)) + rate->index = prev_rate_idx; + + *rs_table_index = index; +} + +/* Building the rate table is non trivial. When we're in MIMO2/VHT/80Mhz/SGI + * column the rate table should look like this: + * + * rate[0] 0x400F019 VHT | ANT: AB BW: 80Mhz MCS: 9 NSS: 2 SGI + * rate[1] 0x400F019 VHT | ANT: AB BW: 80Mhz MCS: 9 NSS: 2 SGI + * rate[2] 0x400F018 VHT | ANT: AB BW: 80Mhz MCS: 8 NSS: 2 SGI + * rate[3] 0x400F018 VHT | ANT: AB BW: 80Mhz MCS: 8 NSS: 2 SGI + * rate[4] 0x400F017 VHT | ANT: AB BW: 80Mhz MCS: 7 NSS: 2 SGI + * rate[5] 0x400F017 VHT | ANT: AB BW: 80Mhz MCS: 7 NSS: 2 SGI + * rate[6] 0x4005007 VHT | ANT: A BW: 80Mhz MCS: 7 NSS: 1 NGI + * rate[7] 0x4009006 VHT | ANT: B BW: 80Mhz MCS: 6 NSS: 1 NGI + * rate[8] 0x4005005 VHT | ANT: A BW: 80Mhz MCS: 5 NSS: 1 NGI + * rate[9] 0x800B Legacy | ANT: B Rate: 36 Mbps + * rate[10] 0x4009 Legacy | ANT: A Rate: 24 Mbps + * rate[11] 0x8007 Legacy | ANT: B Rate: 18 Mbps + * rate[12] 0x4005 Legacy | ANT: A Rate: 12 Mbps + * rate[13] 0x800F Legacy | ANT: B Rate: 9 Mbps + * rate[14] 0x400D Legacy | ANT: A Rate: 6 Mbps + * rate[15] 0x800D Legacy | ANT: B Rate: 6 Mbps + */ +static void rs_build_rates_table(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta, + const struct rs_rate *initial_rate) +{ + struct rs_rate rate; + int num_rates, num_retries, index = 0; + u8 valid_tx_ant = 0; + struct iwl_lq_cmd *lq_cmd = &lq_sta->lq; + bool toggle_ant = false; + u32 color; + + memcpy(&rate, initial_rate, sizeof(rate)); + + valid_tx_ant = iwl_mvm_get_valid_tx_ant(mvm); + + /* TODO: remove old API when min FW API hits 14 */ + if (!fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_LQ_SS_PARAMS) && + rs_stbc_allow(mvm, sta, lq_sta)) + rate.stbc = true; + + if (is_siso(&rate)) { + num_rates = IWL_MVM_RS_INITIAL_SISO_NUM_RATES; + num_retries = IWL_MVM_RS_HT_VHT_RETRIES_PER_RATE; + } else if (is_mimo(&rate)) { + num_rates = IWL_MVM_RS_INITIAL_MIMO_NUM_RATES; + num_retries = IWL_MVM_RS_HT_VHT_RETRIES_PER_RATE; + } else { + num_rates = IWL_MVM_RS_INITIAL_LEGACY_NUM_RATES; + num_retries = IWL_MVM_RS_INITIAL_LEGACY_RETRIES; + toggle_ant = true; + } + + rs_fill_rates_for_column(mvm, lq_sta, &rate, lq_cmd->rs_table, &index, + num_rates, num_retries, valid_tx_ant, + toggle_ant); + + rs_get_lower_rate_down_column(lq_sta, &rate); + + if (is_siso(&rate)) { + num_rates = IWL_MVM_RS_SECONDARY_SISO_NUM_RATES; + num_retries = IWL_MVM_RS_SECONDARY_SISO_RETRIES; + lq_cmd->mimo_delim = index; + } else if (is_legacy(&rate)) { + num_rates = IWL_MVM_RS_SECONDARY_LEGACY_NUM_RATES; + num_retries = IWL_MVM_RS_SECONDARY_LEGACY_RETRIES; + } else { + WARN_ON_ONCE(1); + } + + toggle_ant = true; + + rs_fill_rates_for_column(mvm, lq_sta, &rate, lq_cmd->rs_table, &index, + num_rates, num_retries, valid_tx_ant, + toggle_ant); + + rs_get_lower_rate_down_column(lq_sta, &rate); + + num_rates = IWL_MVM_RS_SECONDARY_LEGACY_NUM_RATES; + num_retries = IWL_MVM_RS_SECONDARY_LEGACY_RETRIES; + + rs_fill_rates_for_column(mvm, lq_sta, &rate, lq_cmd->rs_table, &index, + num_rates, num_retries, valid_tx_ant, + toggle_ant); + + /* update the color of the LQ command (as a counter at bits 1-3) */ + color = LQ_FLAGS_COLOR_INC(LQ_FLAG_COLOR_GET(lq_cmd->flags)); + lq_cmd->flags = LQ_FLAG_COLOR_SET(lq_cmd->flags, color); +} + +struct rs_bfer_active_iter_data { + struct ieee80211_sta *exclude_sta; + struct iwl_mvm_sta *bfer_mvmsta; +}; + +static void rs_bfer_active_iter(void *_data, + struct ieee80211_sta *sta) +{ + struct rs_bfer_active_iter_data *data = _data; + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_lq_cmd *lq_cmd = &mvmsta->lq_sta.rs_drv.lq; + u32 ss_params = le32_to_cpu(lq_cmd->ss_params); + + if (sta == data->exclude_sta) + return; + + /* The current sta has BFER allowed */ + if (ss_params & LQ_SS_BFER_ALLOWED) { + WARN_ON_ONCE(data->bfer_mvmsta != NULL); + + data->bfer_mvmsta = mvmsta; + } +} + +static int rs_bfer_priority(struct iwl_mvm_sta *sta) +{ + int prio = -1; + enum nl80211_iftype viftype = ieee80211_vif_type_p2p(sta->vif); + + switch (viftype) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + prio = 3; + break; + case NL80211_IFTYPE_P2P_CLIENT: + prio = 2; + break; + case NL80211_IFTYPE_STATION: + prio = 1; + break; + default: + WARN_ONCE(true, "viftype %d sta_id %d", viftype, sta->sta_id); + prio = -1; + } + + return prio; +} + +/* Returns >0 if sta1 has a higher BFER priority compared to sta2 */ +static int rs_bfer_priority_cmp(struct iwl_mvm_sta *sta1, + struct iwl_mvm_sta *sta2) +{ + int prio1 = rs_bfer_priority(sta1); + int prio2 = rs_bfer_priority(sta2); + + if (prio1 > prio2) + return 1; + if (prio1 < prio2) + return -1; + return 0; +} + +static void rs_set_lq_ss_params(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta, + const struct rs_rate *initial_rate) +{ + struct iwl_lq_cmd *lq_cmd = &lq_sta->lq; + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct rs_bfer_active_iter_data data = { + .exclude_sta = sta, + .bfer_mvmsta = NULL, + }; + struct iwl_mvm_sta *bfer_mvmsta = NULL; + u32 ss_params = LQ_SS_PARAMS_VALID; + + if (!iwl_mvm_bt_coex_is_mimo_allowed(mvm, sta)) + goto out; + +#ifdef CONFIG_MAC80211_DEBUGFS + /* Check if forcing the decision is configured. + * Note that SISO is forced by not allowing STBC or BFER + */ + if (lq_sta->pers.ss_force == RS_SS_FORCE_STBC) + ss_params |= (LQ_SS_STBC_1SS_ALLOWED | LQ_SS_FORCE); + else if (lq_sta->pers.ss_force == RS_SS_FORCE_BFER) + ss_params |= (LQ_SS_BFER_ALLOWED | LQ_SS_FORCE); + + if (lq_sta->pers.ss_force != RS_SS_FORCE_NONE) { + IWL_DEBUG_RATE(mvm, "Forcing single stream Tx decision %d\n", + lq_sta->pers.ss_force); + goto out; + } +#endif + + if (lq_sta->stbc_capable) + ss_params |= LQ_SS_STBC_1SS_ALLOWED; + + if (!lq_sta->bfer_capable) + goto out; + + ieee80211_iterate_stations_atomic(mvm->hw, + rs_bfer_active_iter, + &data); + bfer_mvmsta = data.bfer_mvmsta; + + /* This code is safe as it doesn't run concurrently for different + * stations. This is guaranteed by the fact that calls to + * ieee80211_tx_status wouldn't run concurrently for a single HW. + */ + if (!bfer_mvmsta) { + IWL_DEBUG_RATE(mvm, "No sta with BFER allowed found. Allow\n"); + + ss_params |= LQ_SS_BFER_ALLOWED; + goto out; + } + + IWL_DEBUG_RATE(mvm, "Found existing sta %d with BFER activated\n", + bfer_mvmsta->sta_id); + + /* Disallow BFER on another STA if active and we're a higher priority */ + if (rs_bfer_priority_cmp(mvmsta, bfer_mvmsta) > 0) { + struct iwl_lq_cmd *bfersta_lq_cmd = + &bfer_mvmsta->lq_sta.rs_drv.lq; + u32 bfersta_ss_params = le32_to_cpu(bfersta_lq_cmd->ss_params); + + bfersta_ss_params &= ~LQ_SS_BFER_ALLOWED; + bfersta_lq_cmd->ss_params = cpu_to_le32(bfersta_ss_params); + iwl_mvm_send_lq_cmd(mvm, bfersta_lq_cmd); + + ss_params |= LQ_SS_BFER_ALLOWED; + IWL_DEBUG_RATE(mvm, + "Lower priority BFER sta found (%d). Switch BFER\n", + bfer_mvmsta->sta_id); + } +out: + lq_cmd->ss_params = cpu_to_le32(ss_params); +} + +static void rs_fill_lq_cmd(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta, + const struct rs_rate *initial_rate) +{ + struct iwl_lq_cmd *lq_cmd = &lq_sta->lq; + struct iwl_mvm_sta *mvmsta; + struct iwl_mvm_vif *mvmvif; + + lq_cmd->agg_disable_start_th = IWL_MVM_RS_AGG_DISABLE_START; + lq_cmd->agg_time_limit = + cpu_to_le16(IWL_MVM_RS_AGG_TIME_LIMIT); + +#ifdef CONFIG_MAC80211_DEBUGFS + if (lq_sta->pers.dbg_fixed_rate) { + rs_build_rates_table_from_fixed(mvm, lq_cmd, + lq_sta->band, + lq_sta->pers.dbg_fixed_rate); + return; + } +#endif + if (WARN_ON_ONCE(!sta || !initial_rate)) + return; + + rs_build_rates_table(mvm, sta, lq_sta, initial_rate); + + if (fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_LQ_SS_PARAMS)) + rs_set_lq_ss_params(mvm, sta, lq_sta, initial_rate); + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + mvmvif = iwl_mvm_vif_from_mac80211(mvmsta->vif); + + if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_COEX_SCHEMA_2) && + num_of_ant(initial_rate->ant) == 1) + lq_cmd->single_stream_ant_msk = initial_rate->ant; + + lq_cmd->agg_frame_cnt_limit = mvmsta->max_agg_bufsize; + + /* + * In case of low latency, tell the firmware to leave a frame in the + * Tx Fifo so that it can start a transaction in the same TxOP. This + * basically allows the firmware to send bursts. + */ + if (iwl_mvm_vif_low_latency(mvmvif)) + lq_cmd->agg_frame_cnt_limit--; + + if (mvmsta->vif->p2p) + lq_cmd->flags |= LQ_FLAG_USE_RTS_MSK; + + lq_cmd->agg_time_limit = + cpu_to_le16(iwl_mvm_coex_agg_time_limit(mvm, sta)); +} + +static void *rs_alloc(struct ieee80211_hw *hw) +{ + return hw->priv; +} + +/* rate scale requires free function to be implemented */ +static void rs_free(void *mvm_rate) +{ + return; +} + +static void rs_free_sta(void *mvm_r, struct ieee80211_sta *sta, void *mvm_sta) +{ + struct iwl_op_mode *op_mode __maybe_unused = mvm_r; + struct iwl_mvm *mvm __maybe_unused = IWL_OP_MODE_GET_MVM(op_mode); + + IWL_DEBUG_RATE(mvm, "enter\n"); + IWL_DEBUG_RATE(mvm, "leave\n"); +} + +int rs_pretty_print_rate_v1(char *buf, int bufsz, const u32 rate) +{ + + char *type; + u8 mcs = 0, nss = 0; + u8 ant = (rate & RATE_MCS_ANT_AB_MSK) >> RATE_MCS_ANT_POS; + u32 bw = (rate & RATE_MCS_CHAN_WIDTH_MSK_V1) >> + RATE_MCS_CHAN_WIDTH_POS; + + if (!(rate & RATE_MCS_HT_MSK_V1) && + !(rate & RATE_MCS_VHT_MSK_V1) && + !(rate & RATE_MCS_HE_MSK_V1)) { + int index = iwl_hwrate_to_plcp_idx(rate); + + return scnprintf(buf, bufsz, "Legacy | ANT: %s Rate: %s Mbps", + iwl_rs_pretty_ant(ant), + index == IWL_RATE_INVALID ? "BAD" : + iwl_rate_mcs(index)->mbps); + } + + if (rate & RATE_MCS_VHT_MSK_V1) { + type = "VHT"; + mcs = rate & RATE_VHT_MCS_RATE_CODE_MSK; + nss = ((rate & RATE_VHT_MCS_NSS_MSK) + >> RATE_VHT_MCS_NSS_POS) + 1; + } else if (rate & RATE_MCS_HT_MSK_V1) { + type = "HT"; + mcs = rate & RATE_HT_MCS_INDEX_MSK_V1; + nss = ((rate & RATE_HT_MCS_NSS_MSK_V1) + >> RATE_HT_MCS_NSS_POS_V1) + 1; + } else if (rate & RATE_MCS_HE_MSK_V1) { + type = "HE"; + mcs = rate & RATE_VHT_MCS_RATE_CODE_MSK; + nss = ((rate & RATE_VHT_MCS_NSS_MSK) + >> RATE_VHT_MCS_NSS_POS) + 1; + } else { + type = "Unknown"; /* shouldn't happen */ + } + + return scnprintf(buf, bufsz, + "0x%x: %s | ANT: %s BW: %s MCS: %d NSS: %d %s%s%s%s%s", + rate, type, iwl_rs_pretty_ant(ant), iwl_rs_pretty_bw(bw), mcs, nss, + (rate & RATE_MCS_SGI_MSK_V1) ? "SGI " : "NGI ", + (rate & RATE_MCS_STBC_MSK) ? "STBC " : "", + (rate & RATE_MCS_LDPC_MSK_V1) ? "LDPC " : "", + (rate & RATE_HE_DUAL_CARRIER_MODE_MSK) ? "DCM " : "", + (rate & RATE_MCS_BF_MSK) ? "BF " : ""); +} + +#ifdef CONFIG_MAC80211_DEBUGFS +/* + * Program the device to use fixed rate for frame transmit + * This is for debugging/testing only + * once the device start use fixed rate, we need to reload the module + * to being back the normal operation. + */ +static void rs_program_fix_rate(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta) +{ + lq_sta->active_legacy_rate = 0x0FFF; /* 1 - 54 MBits, includes CCK */ + lq_sta->active_siso_rate = 0x1FD0; /* 6 - 60 MBits, no 9, no CCK */ + lq_sta->active_mimo2_rate = 0x1FD0; /* 6 - 60 MBits, no 9, no CCK */ + + IWL_DEBUG_RATE(mvm, "sta_id %d rate 0x%X\n", + lq_sta->lq.sta_id, lq_sta->pers.dbg_fixed_rate); + + if (lq_sta->pers.dbg_fixed_rate) { + rs_fill_lq_cmd(mvm, NULL, lq_sta, NULL); + iwl_mvm_send_lq_cmd(lq_sta->pers.drv, &lq_sta->lq); + } +} + +static ssize_t rs_sta_dbgfs_scale_table_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct iwl_lq_sta *lq_sta = file->private_data; + struct iwl_mvm *mvm; + char buf[64]; + size_t buf_size; + u32 parsed_rate; + + mvm = lq_sta->pers.drv; + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (sscanf(buf, "%x", &parsed_rate) == 1) + lq_sta->pers.dbg_fixed_rate = parsed_rate; + else + lq_sta->pers.dbg_fixed_rate = 0; + + rs_program_fix_rate(mvm, lq_sta); + + return count; +} + +static ssize_t rs_sta_dbgfs_scale_table_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + char *buff; + int desc = 0; + int i = 0; + ssize_t ret; + static const size_t bufsz = 2048; + + struct iwl_lq_sta *lq_sta = file->private_data; + struct iwl_mvm_sta *mvmsta = + container_of(lq_sta, struct iwl_mvm_sta, lq_sta.rs_drv); + struct iwl_mvm *mvm; + struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + struct rs_rate *rate = &tbl->rate; + u32 ss_params; + + mvm = lq_sta->pers.drv; + buff = kmalloc(bufsz, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + desc += scnprintf(buff + desc, bufsz - desc, + "sta_id %d\n", lq_sta->lq.sta_id); + desc += scnprintf(buff + desc, bufsz - desc, + "failed=%d success=%d rate=0%lX\n", + lq_sta->total_failed, lq_sta->total_success, + lq_sta->active_legacy_rate); + desc += scnprintf(buff + desc, bufsz - desc, "fixed rate 0x%X\n", + lq_sta->pers.dbg_fixed_rate); + desc += scnprintf(buff + desc, bufsz - desc, "valid_tx_ant %s%s\n", + (iwl_mvm_get_valid_tx_ant(mvm) & ANT_A) ? "ANT_A," : "", + (iwl_mvm_get_valid_tx_ant(mvm) & ANT_B) ? "ANT_B," : ""); + desc += scnprintf(buff + desc, bufsz - desc, "lq type %s\n", + (is_legacy(rate)) ? "legacy" : + is_vht(rate) ? "VHT" : "HT"); + if (!is_legacy(rate)) { + desc += scnprintf(buff + desc, bufsz - desc, " %s", + (is_siso(rate)) ? "SISO" : "MIMO2"); + desc += scnprintf(buff + desc, bufsz - desc, " %s", + (is_ht20(rate)) ? "20MHz" : + (is_ht40(rate)) ? "40MHz" : + (is_ht80(rate)) ? "80MHz" : + (is_ht160(rate)) ? "160MHz" : "BAD BW"); + desc += scnprintf(buff + desc, bufsz - desc, " %s %s %s %s\n", + (rate->sgi) ? "SGI" : "NGI", + (rate->ldpc) ? "LDPC" : "BCC", + (lq_sta->is_agg) ? "AGG on" : "", + (mvmsta->amsdu_enabled) ? "AMSDU on" : ""); + } + desc += scnprintf(buff + desc, bufsz - desc, "last tx rate=0x%X\n", + lq_sta->last_rate_n_flags); + desc += scnprintf(buff + desc, bufsz - desc, + "general: flags=0x%X mimo-d=%d s-ant=0x%x d-ant=0x%x\n", + lq_sta->lq.flags, + lq_sta->lq.mimo_delim, + lq_sta->lq.single_stream_ant_msk, + lq_sta->lq.dual_stream_ant_msk); + + desc += scnprintf(buff + desc, bufsz - desc, + "agg: time_limit=%d dist_start_th=%d frame_cnt_limit=%d\n", + le16_to_cpu(lq_sta->lq.agg_time_limit), + lq_sta->lq.agg_disable_start_th, + lq_sta->lq.agg_frame_cnt_limit); + + desc += scnprintf(buff + desc, bufsz - desc, "reduced tpc=%d\n", + lq_sta->lq.reduced_tpc); + ss_params = le32_to_cpu(lq_sta->lq.ss_params); + desc += scnprintf(buff + desc, bufsz - desc, + "single stream params: %s%s%s%s\n", + (ss_params & LQ_SS_PARAMS_VALID) ? + "VALID" : "INVALID", + (ss_params & LQ_SS_BFER_ALLOWED) ? + ", BFER" : "", + (ss_params & LQ_SS_STBC_1SS_ALLOWED) ? + ", STBC" : "", + (ss_params & LQ_SS_FORCE) ? + ", FORCE" : ""); + desc += scnprintf(buff + desc, bufsz - desc, + "Start idx [0]=0x%x [1]=0x%x [2]=0x%x [3]=0x%x\n", + lq_sta->lq.initial_rate_index[0], + lq_sta->lq.initial_rate_index[1], + lq_sta->lq.initial_rate_index[2], + lq_sta->lq.initial_rate_index[3]); + + for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++) { + u32 r = le32_to_cpu(lq_sta->lq.rs_table[i]); + + desc += scnprintf(buff + desc, bufsz - desc, + " rate[%d] 0x%X ", i, r); + desc += rs_pretty_print_rate_v1(buff + desc, bufsz - desc, r); + if (desc < bufsz - 1) + buff[desc++] = '\n'; + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buff, desc); + kfree(buff); + return ret; +} + +static const struct file_operations rs_sta_dbgfs_scale_table_ops = { + .write = rs_sta_dbgfs_scale_table_write, + .read = rs_sta_dbgfs_scale_table_read, + .open = simple_open, + .llseek = default_llseek, +}; +static ssize_t rs_sta_dbgfs_stats_table_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + char *buff; + int desc = 0; + int i, j; + ssize_t ret; + struct iwl_scale_tbl_info *tbl; + struct rs_rate *rate; + struct iwl_lq_sta *lq_sta = file->private_data; + + buff = kmalloc(1024, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + for (i = 0; i < LQ_SIZE; i++) { + tbl = &(lq_sta->lq_info[i]); + rate = &tbl->rate; + desc += sprintf(buff+desc, + "%s type=%d SGI=%d BW=%s DUP=0\n" + "index=%d\n", + lq_sta->active_tbl == i ? "*" : "x", + rate->type, + rate->sgi, + is_ht20(rate) ? "20MHz" : + is_ht40(rate) ? "40MHz" : + is_ht80(rate) ? "80MHz" : + is_ht160(rate) ? "160MHz" : "ERR", + rate->index); + for (j = 0; j < IWL_RATE_COUNT; j++) { + desc += sprintf(buff+desc, + "counter=%d success=%d %%=%d\n", + tbl->win[j].counter, + tbl->win[j].success_counter, + tbl->win[j].success_ratio); + } + } + ret = simple_read_from_buffer(user_buf, count, ppos, buff, desc); + kfree(buff); + return ret; +} + +static const struct file_operations rs_sta_dbgfs_stats_table_ops = { + .read = rs_sta_dbgfs_stats_table_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t rs_sta_dbgfs_drv_tx_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + static const char * const column_name[] = { + [RS_COLUMN_LEGACY_ANT_A] = "LEGACY_ANT_A", + [RS_COLUMN_LEGACY_ANT_B] = "LEGACY_ANT_B", + [RS_COLUMN_SISO_ANT_A] = "SISO_ANT_A", + [RS_COLUMN_SISO_ANT_B] = "SISO_ANT_B", + [RS_COLUMN_SISO_ANT_A_SGI] = "SISO_ANT_A_SGI", + [RS_COLUMN_SISO_ANT_B_SGI] = "SISO_ANT_B_SGI", + [RS_COLUMN_MIMO2] = "MIMO2", + [RS_COLUMN_MIMO2_SGI] = "MIMO2_SGI", + }; + + static const char * const rate_name[] = { + [IWL_RATE_1M_INDEX] = "1M", + [IWL_RATE_2M_INDEX] = "2M", + [IWL_RATE_5M_INDEX] = "5.5M", + [IWL_RATE_11M_INDEX] = "11M", + [IWL_RATE_6M_INDEX] = "6M|MCS0", + [IWL_RATE_9M_INDEX] = "9M", + [IWL_RATE_12M_INDEX] = "12M|MCS1", + [IWL_RATE_18M_INDEX] = "18M|MCS2", + [IWL_RATE_24M_INDEX] = "24M|MCS3", + [IWL_RATE_36M_INDEX] = "36M|MCS4", + [IWL_RATE_48M_INDEX] = "48M|MCS5", + [IWL_RATE_54M_INDEX] = "54M|MCS6", + [IWL_RATE_MCS_7_INDEX] = "MCS7", + [IWL_RATE_MCS_8_INDEX] = "MCS8", + [IWL_RATE_MCS_9_INDEX] = "MCS9", + [IWL_RATE_MCS_10_INDEX] = "MCS10", + [IWL_RATE_MCS_11_INDEX] = "MCS11", + }; + + char *buff, *pos, *endpos; + int col, rate; + ssize_t ret; + struct iwl_lq_sta *lq_sta = file->private_data; + struct rs_rate_stats *stats; + static const size_t bufsz = 1024; + + buff = kmalloc(bufsz, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + pos = buff; + endpos = pos + bufsz; + + pos += scnprintf(pos, endpos - pos, "COLUMN,"); + for (rate = 0; rate < IWL_RATE_COUNT; rate++) + pos += scnprintf(pos, endpos - pos, "%s,", rate_name[rate]); + pos += scnprintf(pos, endpos - pos, "\n"); + + for (col = 0; col < RS_COLUMN_COUNT; col++) { + pos += scnprintf(pos, endpos - pos, + "%s,", column_name[col]); + + for (rate = 0; rate < IWL_RATE_COUNT; rate++) { + stats = &(lq_sta->pers.tx_stats[col][rate]); + pos += scnprintf(pos, endpos - pos, + "%llu/%llu,", + stats->success, + stats->total); + } + pos += scnprintf(pos, endpos - pos, "\n"); + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buff, pos - buff); + kfree(buff); + return ret; +} + +static ssize_t rs_sta_dbgfs_drv_tx_stats_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_lq_sta *lq_sta = file->private_data; + memset(lq_sta->pers.tx_stats, 0, sizeof(lq_sta->pers.tx_stats)); + + return count; +} + +static const struct file_operations rs_sta_dbgfs_drv_tx_stats_ops = { + .read = rs_sta_dbgfs_drv_tx_stats_read, + .write = rs_sta_dbgfs_drv_tx_stats_write, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t iwl_dbgfs_ss_force_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_lq_sta *lq_sta = file->private_data; + char buf[12]; + int bufsz = sizeof(buf); + int pos = 0; + static const char * const ss_force_name[] = { + [RS_SS_FORCE_NONE] = "none", + [RS_SS_FORCE_STBC] = "stbc", + [RS_SS_FORCE_BFER] = "bfer", + [RS_SS_FORCE_SISO] = "siso", + }; + + pos += scnprintf(buf+pos, bufsz-pos, "%s\n", + ss_force_name[lq_sta->pers.ss_force]); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_ss_force_write(struct iwl_lq_sta *lq_sta, char *buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = lq_sta->pers.drv; + int ret = 0; + + if (!strncmp("none", buf, 4)) { + lq_sta->pers.ss_force = RS_SS_FORCE_NONE; + } else if (!strncmp("siso", buf, 4)) { + lq_sta->pers.ss_force = RS_SS_FORCE_SISO; + } else if (!strncmp("stbc", buf, 4)) { + if (lq_sta->stbc_capable) { + lq_sta->pers.ss_force = RS_SS_FORCE_STBC; + } else { + IWL_ERR(mvm, + "can't force STBC. peer doesn't support\n"); + ret = -EINVAL; + } + } else if (!strncmp("bfer", buf, 4)) { + if (lq_sta->bfer_capable) { + lq_sta->pers.ss_force = RS_SS_FORCE_BFER; + } else { + IWL_ERR(mvm, + "can't force BFER. peer doesn't support\n"); + ret = -EINVAL; + } + } else { + IWL_ERR(mvm, "valid values none|siso|stbc|bfer\n"); + ret = -EINVAL; + } + return ret ?: count; +} + +#define MVM_DEBUGFS_READ_WRITE_FILE_OPS(name, bufsz) \ + _MVM_DEBUGFS_READ_WRITE_FILE_OPS(name, bufsz, struct iwl_lq_sta) +#define MVM_DEBUGFS_ADD_FILE_RS(name, parent, mode) do { \ + debugfs_create_file(#name, mode, parent, lq_sta, \ + &iwl_dbgfs_##name##_ops); \ + } while (0) + +MVM_DEBUGFS_READ_WRITE_FILE_OPS(ss_force, 32); + +static void rs_drv_add_sta_debugfs(void *mvm, void *priv_sta, + struct dentry *dir) +{ + struct iwl_lq_sta *lq_sta = priv_sta; + struct iwl_mvm_sta *mvmsta; + + mvmsta = container_of(lq_sta, struct iwl_mvm_sta, lq_sta.rs_drv); + + if (!mvmsta->vif) + return; + + debugfs_create_file("rate_scale_table", 0600, dir, + lq_sta, &rs_sta_dbgfs_scale_table_ops); + debugfs_create_file("rate_stats_table", 0400, dir, + lq_sta, &rs_sta_dbgfs_stats_table_ops); + debugfs_create_file("drv_tx_stats", 0600, dir, + lq_sta, &rs_sta_dbgfs_drv_tx_stats_ops); + debugfs_create_u8("tx_agg_tid_enable", 0600, dir, + &lq_sta->tx_agg_tid_en); + debugfs_create_u8("reduced_tpc", 0600, dir, + &lq_sta->pers.dbg_fixed_txp_reduction); + + MVM_DEBUGFS_ADD_FILE_RS(ss_force, dir, 0600); +} +#endif + +/* + * Initialization of rate scaling information is done by driver after + * the station is added. Since mac80211 calls this function before a + * station is added we ignore it. + */ +static void rs_rate_init_ops(void *mvm_r, + struct ieee80211_supported_band *sband, + struct cfg80211_chan_def *chandef, + struct ieee80211_sta *sta, void *mvm_sta) +{ +} + +/* ops for rate scaling implemented in the driver */ +static const struct rate_control_ops rs_mvm_ops_drv = { + .name = RS_NAME, + .tx_status = rs_drv_mac80211_tx_status, + .get_rate = rs_drv_get_rate, + .rate_init = rs_rate_init_ops, + .alloc = rs_alloc, + .free = rs_free, + .alloc_sta = rs_drv_alloc_sta, + .free_sta = rs_free_sta, + .rate_update = rs_drv_rate_update, +#ifdef CONFIG_MAC80211_DEBUGFS + .add_sta_debugfs = rs_drv_add_sta_debugfs, +#endif + .capa = RATE_CTRL_CAPA_VHT_EXT_NSS_BW, +}; + +void iwl_mvm_rs_rate_init(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + enum nl80211_band band, bool update) +{ + if (iwl_mvm_has_tlc_offload(mvm)) { + rs_fw_rate_init(mvm, sta, band, update); + } else { + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + + spin_lock(&mvmsta->lq_sta.rs_drv.pers.lock); + rs_drv_rate_init(mvm, sta, band); + spin_unlock(&mvmsta->lq_sta.rs_drv.pers.lock); + } +} + +int iwl_mvm_rate_control_register(void) +{ + return ieee80211_rate_control_register(&rs_mvm_ops_drv); +} + +void iwl_mvm_rate_control_unregister(void) +{ + ieee80211_rate_control_unregister(&rs_mvm_ops_drv); +} + +static int rs_drv_tx_protection(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta, + bool enable) +{ + struct iwl_lq_cmd *lq = &mvmsta->lq_sta.rs_drv.lq; + + lockdep_assert_held(&mvm->mutex); + + if (enable) { + if (mvmsta->tx_protection == 0) + lq->flags |= LQ_FLAG_USE_RTS_MSK; + mvmsta->tx_protection++; + } else { + mvmsta->tx_protection--; + if (mvmsta->tx_protection == 0) + lq->flags &= ~LQ_FLAG_USE_RTS_MSK; + } + + return iwl_mvm_send_lq_cmd(mvm, lq); +} + +/** + * iwl_mvm_tx_protection - ask FW to enable RTS/CTS protection + * @mvm: The mvm component + * @mvmsta: The station + * @enable: Enable Tx protection? + */ +int iwl_mvm_tx_protection(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta, + bool enable) +{ + if (iwl_mvm_has_tlc_offload(mvm)) + return rs_fw_tx_protection(mvm, mvmsta, enable); + else + return rs_drv_tx_protection(mvm, mvmsta, enable); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rs.h b/drivers/net/wireless/intel/iwlwifi/mvm/rs.h new file mode 100644 index 000000000..b7bc8c1b2 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/rs.h @@ -0,0 +1,440 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/****************************************************************************** + * + * Copyright(c) 2003 - 2014 Intel Corporation. All rights reserved. + * Copyright(c) 2015 Intel Mobile Communications GmbH + * Copyright(c) 2017 Intel Deutschland GmbH + * Copyright(c) 2018 - 2019 Intel Corporation + *****************************************************************************/ + +#ifndef __rs_h__ +#define __rs_h__ + +#include <net/mac80211.h> + +#include "iwl-config.h" + +#include "fw-api.h" +#include "iwl-trans.h" + +#define RS_NAME "iwl-mvm-rs" + +struct iwl_rs_rate_info { + u8 plcp; /* uCode API: IWL_RATE_6M_PLCP, etc. */ + u8 plcp_ht_siso; /* uCode API: IWL_RATE_SISO_6M_PLCP, etc. */ + u8 plcp_ht_mimo2; /* uCode API: IWL_RATE_MIMO2_6M_PLCP, etc. */ + u8 plcp_vht_siso; + u8 plcp_vht_mimo2; + u8 prev_rs; /* previous rate used in rs algo */ + u8 next_rs; /* next rate used in rs algo */ +}; + +#define IWL_RATE_60M_PLCP 3 + +#define LINK_QUAL_MAX_RETRY_NUM 16 + +enum { + IWL_RATE_6M_INDEX_TABLE = 0, + IWL_RATE_9M_INDEX_TABLE, + IWL_RATE_12M_INDEX_TABLE, + IWL_RATE_18M_INDEX_TABLE, + IWL_RATE_24M_INDEX_TABLE, + IWL_RATE_36M_INDEX_TABLE, + IWL_RATE_48M_INDEX_TABLE, + IWL_RATE_54M_INDEX_TABLE, + IWL_RATE_1M_INDEX_TABLE, + IWL_RATE_2M_INDEX_TABLE, + IWL_RATE_5M_INDEX_TABLE, + IWL_RATE_11M_INDEX_TABLE, + IWL_RATE_INVM_INDEX_TABLE = IWL_RATE_INVM_INDEX - 1, +}; + +/* #define vs. enum to keep from defaulting to 'large integer' */ +#define IWL_RATE_6M_MASK (1 << IWL_RATE_6M_INDEX) +#define IWL_RATE_9M_MASK (1 << IWL_RATE_9M_INDEX) +#define IWL_RATE_12M_MASK (1 << IWL_RATE_12M_INDEX) +#define IWL_RATE_18M_MASK (1 << IWL_RATE_18M_INDEX) +#define IWL_RATE_24M_MASK (1 << IWL_RATE_24M_INDEX) +#define IWL_RATE_36M_MASK (1 << IWL_RATE_36M_INDEX) +#define IWL_RATE_48M_MASK (1 << IWL_RATE_48M_INDEX) +#define IWL_RATE_54M_MASK (1 << IWL_RATE_54M_INDEX) +#define IWL_RATE_60M_MASK (1 << IWL_RATE_60M_INDEX) +#define IWL_RATE_1M_MASK (1 << IWL_RATE_1M_INDEX) +#define IWL_RATE_2M_MASK (1 << IWL_RATE_2M_INDEX) +#define IWL_RATE_5M_MASK (1 << IWL_RATE_5M_INDEX) +#define IWL_RATE_11M_MASK (1 << IWL_RATE_11M_INDEX) + + +/* uCode API values for HT/VHT bit rates */ +enum { + IWL_RATE_HT_SISO_MCS_0_PLCP = 0, + IWL_RATE_HT_SISO_MCS_1_PLCP = 1, + IWL_RATE_HT_SISO_MCS_2_PLCP = 2, + IWL_RATE_HT_SISO_MCS_3_PLCP = 3, + IWL_RATE_HT_SISO_MCS_4_PLCP = 4, + IWL_RATE_HT_SISO_MCS_5_PLCP = 5, + IWL_RATE_HT_SISO_MCS_6_PLCP = 6, + IWL_RATE_HT_SISO_MCS_7_PLCP = 7, + IWL_RATE_HT_MIMO2_MCS_0_PLCP = 0x8, + IWL_RATE_HT_MIMO2_MCS_1_PLCP = 0x9, + IWL_RATE_HT_MIMO2_MCS_2_PLCP = 0xA, + IWL_RATE_HT_MIMO2_MCS_3_PLCP = 0xB, + IWL_RATE_HT_MIMO2_MCS_4_PLCP = 0xC, + IWL_RATE_HT_MIMO2_MCS_5_PLCP = 0xD, + IWL_RATE_HT_MIMO2_MCS_6_PLCP = 0xE, + IWL_RATE_HT_MIMO2_MCS_7_PLCP = 0xF, + IWL_RATE_VHT_SISO_MCS_0_PLCP = 0, + IWL_RATE_VHT_SISO_MCS_1_PLCP = 1, + IWL_RATE_VHT_SISO_MCS_2_PLCP = 2, + IWL_RATE_VHT_SISO_MCS_3_PLCP = 3, + IWL_RATE_VHT_SISO_MCS_4_PLCP = 4, + IWL_RATE_VHT_SISO_MCS_5_PLCP = 5, + IWL_RATE_VHT_SISO_MCS_6_PLCP = 6, + IWL_RATE_VHT_SISO_MCS_7_PLCP = 7, + IWL_RATE_VHT_SISO_MCS_8_PLCP = 8, + IWL_RATE_VHT_SISO_MCS_9_PLCP = 9, + IWL_RATE_VHT_MIMO2_MCS_0_PLCP = 0x10, + IWL_RATE_VHT_MIMO2_MCS_1_PLCP = 0x11, + IWL_RATE_VHT_MIMO2_MCS_2_PLCP = 0x12, + IWL_RATE_VHT_MIMO2_MCS_3_PLCP = 0x13, + IWL_RATE_VHT_MIMO2_MCS_4_PLCP = 0x14, + IWL_RATE_VHT_MIMO2_MCS_5_PLCP = 0x15, + IWL_RATE_VHT_MIMO2_MCS_6_PLCP = 0x16, + IWL_RATE_VHT_MIMO2_MCS_7_PLCP = 0x17, + IWL_RATE_VHT_MIMO2_MCS_8_PLCP = 0x18, + IWL_RATE_VHT_MIMO2_MCS_9_PLCP = 0x19, + IWL_RATE_HT_SISO_MCS_INV_PLCP, + IWL_RATE_HT_MIMO2_MCS_INV_PLCP = IWL_RATE_HT_SISO_MCS_INV_PLCP, + IWL_RATE_VHT_SISO_MCS_INV_PLCP = IWL_RATE_HT_SISO_MCS_INV_PLCP, + IWL_RATE_VHT_MIMO2_MCS_INV_PLCP = IWL_RATE_HT_SISO_MCS_INV_PLCP, + IWL_RATE_HT_SISO_MCS_8_PLCP = IWL_RATE_HT_SISO_MCS_INV_PLCP, + IWL_RATE_HT_SISO_MCS_9_PLCP = IWL_RATE_HT_SISO_MCS_INV_PLCP, + IWL_RATE_HT_MIMO2_MCS_8_PLCP = IWL_RATE_HT_SISO_MCS_INV_PLCP, + IWL_RATE_HT_MIMO2_MCS_9_PLCP = IWL_RATE_HT_SISO_MCS_INV_PLCP, +}; + +#define IWL_RATES_MASK ((1 << IWL_RATE_COUNT) - 1) + +#define IWL_INVALID_VALUE -1 + +#define TPC_MAX_REDUCTION 15 +#define TPC_NO_REDUCTION 0 +#define TPC_INVALID 0xff + +#define LINK_QUAL_AGG_FRAME_LIMIT_DEF (63) +#define LINK_QUAL_AGG_FRAME_LIMIT_MAX (63) +/* + * FIXME - various places in firmware API still use u8, + * e.g. LQ command and SCD config command. + * This should be 256 instead. + */ +#define LINK_QUAL_AGG_FRAME_LIMIT_GEN2_DEF (255) +#define LINK_QUAL_AGG_FRAME_LIMIT_GEN2_MAX (255) +#define LINK_QUAL_AGG_FRAME_LIMIT_MIN (0) + +#define LQ_SIZE 2 /* 2 mode tables: "Active" and "Search" */ + +/* load per tid defines for A-MPDU activation */ +#define IWL_AGG_TPT_THREHOLD 0 +#define IWL_AGG_ALL_TID 0xff + +enum iwl_table_type { + LQ_NONE, + LQ_LEGACY_G, /* legacy types */ + LQ_LEGACY_A, + LQ_HT_SISO, /* HT types */ + LQ_HT_MIMO2, + LQ_VHT_SISO, /* VHT types */ + LQ_VHT_MIMO2, + LQ_HE_SISO, /* HE types */ + LQ_HE_MIMO2, + LQ_MAX, +}; + +struct rs_rate { + int index; + enum iwl_table_type type; + u8 ant; + u32 bw; + bool sgi; + bool ldpc; + bool stbc; + bool bfer; +}; + + +#define is_type_legacy(type) (((type) == LQ_LEGACY_G) || \ + ((type) == LQ_LEGACY_A)) +#define is_type_ht_siso(type) ((type) == LQ_HT_SISO) +#define is_type_ht_mimo2(type) ((type) == LQ_HT_MIMO2) +#define is_type_vht_siso(type) ((type) == LQ_VHT_SISO) +#define is_type_vht_mimo2(type) ((type) == LQ_VHT_MIMO2) +#define is_type_he_siso(type) ((type) == LQ_HE_SISO) +#define is_type_he_mimo2(type) ((type) == LQ_HE_MIMO2) +#define is_type_siso(type) (is_type_ht_siso(type) || is_type_vht_siso(type) || \ + is_type_he_siso(type)) +#define is_type_mimo2(type) (is_type_ht_mimo2(type) || \ + is_type_vht_mimo2(type) || is_type_he_mimo2(type)) +#define is_type_mimo(type) (is_type_mimo2(type)) +#define is_type_ht(type) (is_type_ht_siso(type) || is_type_ht_mimo2(type)) +#define is_type_vht(type) (is_type_vht_siso(type) || is_type_vht_mimo2(type)) +#define is_type_he(type) (is_type_he_siso(type) || is_type_he_mimo2(type)) +#define is_type_a_band(type) ((type) == LQ_LEGACY_A) +#define is_type_g_band(type) ((type) == LQ_LEGACY_G) + +#define is_legacy(rate) is_type_legacy((rate)->type) +#define is_ht_siso(rate) is_type_ht_siso((rate)->type) +#define is_ht_mimo2(rate) is_type_ht_mimo2((rate)->type) +#define is_vht_siso(rate) is_type_vht_siso((rate)->type) +#define is_vht_mimo2(rate) is_type_vht_mimo2((rate)->type) +#define is_siso(rate) is_type_siso((rate)->type) +#define is_mimo2(rate) is_type_mimo2((rate)->type) +#define is_mimo(rate) is_type_mimo((rate)->type) +#define is_ht(rate) is_type_ht((rate)->type) +#define is_vht(rate) is_type_vht((rate)->type) +#define is_he(rate) is_type_he((rate)->type) +#define is_a_band(rate) is_type_a_band((rate)->type) +#define is_g_band(rate) is_type_g_band((rate)->type) + +#define is_ht20(rate) ((rate)->bw == RATE_MCS_CHAN_WIDTH_20) +#define is_ht40(rate) ((rate)->bw == RATE_MCS_CHAN_WIDTH_40) +#define is_ht80(rate) ((rate)->bw == RATE_MCS_CHAN_WIDTH_80) +#define is_ht160(rate) ((rate)->bw == RATE_MCS_CHAN_WIDTH_160) + +/** + * struct iwl_lq_sta_rs_fw - rate and related statistics for RS in FW + * @last_rate_n_flags: last rate reported by FW + * @sta_id: the id of the station +#ifdef CONFIG_MAC80211_DEBUGFS + * @dbg_fixed_rate: for debug, use fixed rate if not 0 + * @dbg_agg_frame_count_lim: for debug, max number of frames in A-MPDU +#endif + * @chains: bitmask of chains reported in %chain_signal + * @chain_signal: per chain signal strength + * @last_rssi: last rssi reported + * @drv: pointer back to the driver data + */ + +struct iwl_lq_sta_rs_fw { + /* last tx rate_n_flags */ + u32 last_rate_n_flags; + + /* persistent fields - initialized only once - keep last! */ + struct lq_sta_pers_rs_fw { + u32 sta_id; +#ifdef CONFIG_MAC80211_DEBUGFS + u32 dbg_fixed_rate; + u16 dbg_agg_frame_count_lim; +#endif + u8 chains; + s8 chain_signal[IEEE80211_MAX_CHAINS]; + s8 last_rssi; + struct iwl_mvm *drv; + } pers; +}; + +/** + * struct iwl_rate_scale_data -- tx success history for one rate + */ +struct iwl_rate_scale_data { + u64 data; /* bitmap of successful frames */ + s32 success_counter; /* number of frames successful */ + s32 success_ratio; /* per-cent * 128 */ + s32 counter; /* number of frames attempted */ + s32 average_tpt; /* success ratio * expected throughput */ +}; + +/* Possible Tx columns + * Tx Column = a combo of legacy/siso/mimo x antenna x SGI + */ +enum rs_column { + RS_COLUMN_LEGACY_ANT_A = 0, + RS_COLUMN_LEGACY_ANT_B, + RS_COLUMN_SISO_ANT_A, + RS_COLUMN_SISO_ANT_B, + RS_COLUMN_SISO_ANT_A_SGI, + RS_COLUMN_SISO_ANT_B_SGI, + RS_COLUMN_MIMO2, + RS_COLUMN_MIMO2_SGI, + + RS_COLUMN_LAST = RS_COLUMN_MIMO2_SGI, + RS_COLUMN_COUNT = RS_COLUMN_LAST + 1, + RS_COLUMN_INVALID, +}; + +enum rs_ss_force_opt { + RS_SS_FORCE_NONE = 0, + RS_SS_FORCE_STBC, + RS_SS_FORCE_BFER, + RS_SS_FORCE_SISO, +}; + +/* Packet stats per rate */ +struct rs_rate_stats { + u64 success; + u64 total; +}; + +/** + * struct iwl_scale_tbl_info -- tx params and success history for all rates + * + * There are two of these in struct iwl_lq_sta, + * one for "active", and one for "search". + */ +struct iwl_scale_tbl_info { + struct rs_rate rate; + enum rs_column column; + const u16 *expected_tpt; /* throughput metrics; expected_tpt_G, etc. */ + struct iwl_rate_scale_data win[IWL_RATE_COUNT]; /* rate histories */ + /* per txpower-reduction history */ + struct iwl_rate_scale_data tpc_win[TPC_MAX_REDUCTION + 1]; +}; + +enum { + RS_STATE_SEARCH_CYCLE_STARTED, + RS_STATE_SEARCH_CYCLE_ENDED, + RS_STATE_STAY_IN_COLUMN, +}; + +/** + * struct iwl_lq_sta -- driver's rate scaling private structure + * + * Pointer to this gets passed back and forth between driver and mac80211. + */ +struct iwl_lq_sta { + u8 active_tbl; /* index of active table, range 0-1 */ + u8 rs_state; /* RS_STATE_* */ + u8 search_better_tbl; /* 1: currently trying alternate mode */ + s32 last_tpt; + + /* The following determine when to search for a new mode */ + u32 table_count_limit; + u32 max_failure_limit; /* # failed frames before new search */ + u32 max_success_limit; /* # successful frames before new search */ + u32 table_count; + u32 total_failed; /* total failed frames, any/all rates */ + u32 total_success; /* total successful frames, any/all rates */ + u64 flush_timer; /* time staying in mode before new search */ + + u32 visited_columns; /* Bitmask marking which Tx columns were + * explored during a search cycle + */ + u64 last_tx; + bool is_vht; + bool ldpc; /* LDPC Rx is supported by the STA */ + bool stbc_capable; /* Tx STBC is supported by chip and Rx by STA */ + bool bfer_capable; /* Remote supports beamformee and we BFer */ + + enum nl80211_band band; + + /* The following are bitmaps of rates; IWL_RATE_6M_MASK, etc. */ + unsigned long active_legacy_rate; + unsigned long active_siso_rate; + unsigned long active_mimo2_rate; + + /* Highest rate per Tx mode */ + u8 max_legacy_rate_idx; + u8 max_siso_rate_idx; + u8 max_mimo2_rate_idx; + + /* Optimal rate based on RSSI and STA caps. + * Used only to reflect link speed to userspace. + */ + struct rs_rate optimal_rate; + unsigned long optimal_rate_mask; + const struct rs_init_rate_info *optimal_rates; + int optimal_nentries; + + u8 missed_rate_counter; + + struct iwl_lq_cmd lq; + struct iwl_scale_tbl_info lq_info[LQ_SIZE]; /* "active", "search" */ + u8 tx_agg_tid_en; + + /* last tx rate_n_flags */ + u32 last_rate_n_flags; + /* packets destined for this STA are aggregated */ + u8 is_agg; + + /* tx power reduce for this sta */ + int tpc_reduce; + + /* persistent fields - initialized only once - keep last! */ + struct lq_sta_pers { +#ifdef CONFIG_MAC80211_DEBUGFS + u32 dbg_fixed_rate; + u8 dbg_fixed_txp_reduction; + + /* force STBC/BFER/SISO for testing */ + enum rs_ss_force_opt ss_force; +#endif + u8 chains; + s8 chain_signal[IEEE80211_MAX_CHAINS]; + s8 last_rssi; + struct rs_rate_stats tx_stats[RS_COLUMN_COUNT][IWL_RATE_COUNT]; + struct iwl_mvm *drv; + spinlock_t lock; /* for races in reinit/update table */ + } pers; +}; + +/* ieee80211_tx_info's status_driver_data[0] is packed with lq color and txp + * Note, it's iwlmvm <-> mac80211 interface. + * bits 0-7: reduced tx power + * bits 8-10: LQ command's color + */ +#define RS_DRV_DATA_TXP_MSK 0xff +#define RS_DRV_DATA_LQ_COLOR_POS 8 +#define RS_DRV_DATA_LQ_COLOR_MSK (7 << RS_DRV_DATA_LQ_COLOR_POS) +#define RS_DRV_DATA_LQ_COLOR_GET(_f) (((_f) & RS_DRV_DATA_LQ_COLOR_MSK) >>\ + RS_DRV_DATA_LQ_COLOR_POS) +#define RS_DRV_DATA_PACK(_c, _p) ((void *)(uintptr_t)\ + (((uintptr_t)_p) |\ + ((_c) << RS_DRV_DATA_LQ_COLOR_POS))) + +/* Initialize station's rate scaling information after adding station */ +void iwl_mvm_rs_rate_init(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + enum nl80211_band band, bool init); + +/* Notify RS about Tx status */ +void iwl_mvm_rs_tx_status(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + int tid, struct ieee80211_tx_info *info, bool ndp); + +/** + * iwl_rate_control_register - Register the rate control algorithm callbacks + * + * Since the rate control algorithm is hardware specific, there is no need + * or reason to place it as a stand alone module. The driver can call + * iwl_rate_control_register in order to register the rate control callbacks + * with the mac80211 subsystem. This should be performed prior to calling + * ieee80211_register_hw + * + */ +int iwl_mvm_rate_control_register(void); + +/** + * iwl_rate_control_unregister - Unregister the rate control callbacks + * + * This should be called after calling ieee80211_unregister_hw, but before + * the driver is unloaded. + */ +void iwl_mvm_rate_control_unregister(void); + +struct iwl_mvm_sta; + +int iwl_mvm_tx_protection(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta, + bool enable); + +#ifdef CONFIG_IWLWIFI_DEBUGFS +void iwl_mvm_reset_frame_stats(struct iwl_mvm *mvm); +#endif + +void iwl_mvm_rs_add_sta(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta); +void rs_fw_rate_init(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + enum nl80211_band band, bool update); +int rs_fw_tx_protection(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta, + bool enable); +void iwl_mvm_tlc_update_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); + +u16 rs_fw_get_max_amsdu_len(struct ieee80211_sta *sta); +#endif /* __rs__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c new file mode 100644 index 000000000..eee98cebb --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c @@ -0,0 +1,1071 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include <asm/unaligned.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include "iwl-trans.h" +#include "mvm.h" +#include "fw-api.h" + +/* + * iwl_mvm_rx_rx_phy_cmd - REPLY_RX_PHY_CMD handler + * + * Copies the phy information in mvm->last_phy_info, it will be used when the + * actual data will come from the fw in the next packet. + */ +void iwl_mvm_rx_rx_phy_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); + + if (unlikely(pkt_len < sizeof(mvm->last_phy_info))) + return; + + memcpy(&mvm->last_phy_info, pkt->data, sizeof(mvm->last_phy_info)); + mvm->ampdu_ref++; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (mvm->last_phy_info.phy_flags & cpu_to_le16(RX_RES_PHY_FLAGS_AGG)) { + spin_lock(&mvm->drv_stats_lock); + mvm->drv_rx_stats.ampdu_count++; + spin_unlock(&mvm->drv_stats_lock); + } +#endif +} + +/* + * iwl_mvm_pass_packet_to_mac80211 - builds the packet for mac80211 + * + * Adds the rxb to a new skb and give it to mac80211 + */ +static void iwl_mvm_pass_packet_to_mac80211(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct napi_struct *napi, + struct sk_buff *skb, + struct ieee80211_hdr *hdr, u16 len, + u8 crypt_len, + struct iwl_rx_cmd_buffer *rxb) +{ + unsigned int hdrlen = ieee80211_hdrlen(hdr->frame_control); + unsigned int fraglen; + + /* + * The 'hdrlen' (plus the 8 bytes for the SNAP and the crypt_len, + * but those are all multiples of 4 long) all goes away, but we + * want the *end* of it, which is going to be the start of the IP + * header, to be aligned when it gets pulled in. + * The beginning of the skb->data is aligned on at least a 4-byte + * boundary after allocation. Everything here is aligned at least + * on a 2-byte boundary so we can just take hdrlen & 3 and pad by + * the result. + */ + skb_reserve(skb, hdrlen & 3); + + /* If frame is small enough to fit in skb->head, pull it completely. + * If not, only pull ieee80211_hdr (including crypto if present, and + * an additional 8 bytes for SNAP/ethertype, see below) so that + * splice() or TCP coalesce are more efficient. + * + * Since, in addition, ieee80211_data_to_8023() always pull in at + * least 8 bytes (possibly more for mesh) we can do the same here + * to save the cost of doing it later. That still doesn't pull in + * the actual IP header since the typical case has a SNAP header. + * If the latter changes (there are efforts in the standards group + * to do so) we should revisit this and ieee80211_data_to_8023(). + */ + hdrlen = (len <= skb_tailroom(skb)) ? len : hdrlen + crypt_len + 8; + + skb_put_data(skb, hdr, hdrlen); + fraglen = len - hdrlen; + + if (fraglen) { + int offset = (u8 *)hdr + hdrlen - + (u8 *)rxb_addr(rxb) + rxb_offset(rxb); + + skb_add_rx_frag(skb, 0, rxb_steal_page(rxb), offset, + fraglen, rxb->truesize); + } + + ieee80211_rx_napi(mvm->hw, sta, skb, napi); +} + +/* + * iwl_mvm_get_signal_strength - use new rx PHY INFO API + * values are reported by the fw as positive values - need to negate + * to obtain their dBM. Account for missing antennas by replacing 0 + * values by -256dBm: practically 0 power and a non-feasible 8 bit value. + */ +static void iwl_mvm_get_signal_strength(struct iwl_mvm *mvm, + struct iwl_rx_phy_info *phy_info, + struct ieee80211_rx_status *rx_status) +{ + int energy_a, energy_b, max_energy; + u32 val; + + val = + le32_to_cpu(phy_info->non_cfg_phy[IWL_RX_INFO_ENERGY_ANT_ABC_IDX]); + energy_a = (val & IWL_RX_INFO_ENERGY_ANT_A_MSK) >> + IWL_RX_INFO_ENERGY_ANT_A_POS; + energy_a = energy_a ? -energy_a : S8_MIN; + energy_b = (val & IWL_RX_INFO_ENERGY_ANT_B_MSK) >> + IWL_RX_INFO_ENERGY_ANT_B_POS; + energy_b = energy_b ? -energy_b : S8_MIN; + max_energy = max(energy_a, energy_b); + + IWL_DEBUG_STATS(mvm, "energy In A %d B %d , and max %d\n", + energy_a, energy_b, max_energy); + + rx_status->signal = max_energy; + rx_status->chains = (le16_to_cpu(phy_info->phy_flags) & + RX_RES_PHY_FLAGS_ANTENNA) + >> RX_RES_PHY_FLAGS_ANTENNA_POS; + rx_status->chain_signal[0] = energy_a; + rx_status->chain_signal[1] = energy_b; +} + +/* + * iwl_mvm_set_mac80211_rx_flag - translate fw status to mac80211 format + * @mvm: the mvm object + * @hdr: 80211 header + * @stats: status in mac80211's format + * @rx_pkt_status: status coming from fw + * + * returns non 0 value if the packet should be dropped + */ +static u32 iwl_mvm_set_mac80211_rx_flag(struct iwl_mvm *mvm, + struct ieee80211_hdr *hdr, + struct ieee80211_rx_status *stats, + u32 rx_pkt_status, + u8 *crypt_len) +{ + if (!ieee80211_has_protected(hdr->frame_control) || + (rx_pkt_status & RX_MPDU_RES_STATUS_SEC_ENC_MSK) == + RX_MPDU_RES_STATUS_SEC_NO_ENC) + return 0; + + /* packet was encrypted with unknown alg */ + if ((rx_pkt_status & RX_MPDU_RES_STATUS_SEC_ENC_MSK) == + RX_MPDU_RES_STATUS_SEC_ENC_ERR) + return 0; + + switch (rx_pkt_status & RX_MPDU_RES_STATUS_SEC_ENC_MSK) { + case RX_MPDU_RES_STATUS_SEC_CCM_ENC: + /* alg is CCM: check MIC only */ + if (!(rx_pkt_status & RX_MPDU_RES_STATUS_MIC_OK)) + return -1; + + stats->flag |= RX_FLAG_DECRYPTED; + *crypt_len = IEEE80211_CCMP_HDR_LEN; + return 0; + + case RX_MPDU_RES_STATUS_SEC_TKIP_ENC: + /* Don't drop the frame and decrypt it in SW */ + if (!fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_DEPRECATE_TTAK) && + !(rx_pkt_status & RX_MPDU_RES_STATUS_TTAK_OK)) + return 0; + *crypt_len = IEEE80211_TKIP_IV_LEN; + fallthrough; + + case RX_MPDU_RES_STATUS_SEC_WEP_ENC: + if (!(rx_pkt_status & RX_MPDU_RES_STATUS_ICV_OK)) + return -1; + + stats->flag |= RX_FLAG_DECRYPTED; + if ((rx_pkt_status & RX_MPDU_RES_STATUS_SEC_ENC_MSK) == + RX_MPDU_RES_STATUS_SEC_WEP_ENC) + *crypt_len = IEEE80211_WEP_IV_LEN; + return 0; + + case RX_MPDU_RES_STATUS_SEC_EXT_ENC: + if (!(rx_pkt_status & RX_MPDU_RES_STATUS_MIC_OK)) + return -1; + stats->flag |= RX_FLAG_DECRYPTED; + return 0; + + default: + /* Expected in monitor (not having the keys) */ + if (!mvm->monitor_on) + IWL_ERR(mvm, "Unhandled alg: 0x%x\n", rx_pkt_status); + } + + return 0; +} + +static void iwl_mvm_rx_handle_tcm(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct ieee80211_hdr *hdr, u32 len, + struct iwl_rx_phy_info *phy_info, + u32 rate_n_flags) +{ + struct iwl_mvm_sta *mvmsta; + struct iwl_mvm_tcm_mac *mdata; + int mac; + int ac = IEEE80211_AC_BE; /* treat non-QoS as BE */ + struct iwl_mvm_vif *mvmvif; + /* expected throughput in 100Kbps, single stream, 20 MHz */ + static const u8 thresh_tpt[] = { + 9, 18, 30, 42, 60, 78, 90, 96, 120, 135, + }; + u16 thr; + + if (ieee80211_is_data_qos(hdr->frame_control)) + ac = tid_to_mac80211_ac[ieee80211_get_tid(hdr)]; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + mac = mvmsta->mac_id_n_color & FW_CTXT_ID_MSK; + + if (time_after(jiffies, mvm->tcm.ts + MVM_TCM_PERIOD)) + schedule_delayed_work(&mvm->tcm.work, 0); + mdata = &mvm->tcm.data[mac]; + mdata->rx.pkts[ac]++; + + /* count the airtime only once for each ampdu */ + if (mdata->rx.last_ampdu_ref != mvm->ampdu_ref) { + mdata->rx.last_ampdu_ref = mvm->ampdu_ref; + mdata->rx.airtime += le16_to_cpu(phy_info->frame_time); + } + + if (!(rate_n_flags & (RATE_MCS_HT_MSK_V1 | RATE_MCS_VHT_MSK_V1))) + return; + + mvmvif = iwl_mvm_vif_from_mac80211(mvmsta->vif); + + if (mdata->opened_rx_ba_sessions || + mdata->uapsd_nonagg_detect.detected || + (!mvmvif->queue_params[IEEE80211_AC_VO].uapsd && + !mvmvif->queue_params[IEEE80211_AC_VI].uapsd && + !mvmvif->queue_params[IEEE80211_AC_BE].uapsd && + !mvmvif->queue_params[IEEE80211_AC_BK].uapsd) || + mvmsta->sta_id != mvmvif->ap_sta_id) + return; + + if (rate_n_flags & RATE_MCS_HT_MSK_V1) { + thr = thresh_tpt[rate_n_flags & RATE_HT_MCS_RATE_CODE_MSK_V1]; + thr *= 1 + ((rate_n_flags & RATE_HT_MCS_NSS_MSK_V1) >> + RATE_HT_MCS_NSS_POS_V1); + } else { + if (WARN_ON((rate_n_flags & RATE_VHT_MCS_RATE_CODE_MSK) >= + ARRAY_SIZE(thresh_tpt))) + return; + thr = thresh_tpt[rate_n_flags & RATE_VHT_MCS_RATE_CODE_MSK]; + thr *= 1 + ((rate_n_flags & RATE_VHT_MCS_NSS_MSK) >> + RATE_VHT_MCS_NSS_POS); + } + + thr <<= ((rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK_V1) >> + RATE_MCS_CHAN_WIDTH_POS); + + mdata->uapsd_nonagg_detect.rx_bytes += len; + ewma_rate_add(&mdata->uapsd_nonagg_detect.rate, thr); +} + +static void iwl_mvm_rx_csum(struct ieee80211_sta *sta, + struct sk_buff *skb, + u32 status) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(mvmsta->vif); + + if (mvmvif->features & NETIF_F_RXCSUM && + status & RX_MPDU_RES_STATUS_CSUM_DONE && + status & RX_MPDU_RES_STATUS_CSUM_OK) + skb->ip_summed = CHECKSUM_UNNECESSARY; +} + +/* + * iwl_mvm_rx_rx_mpdu - REPLY_RX_MPDU_CMD handler + * + * Handles the actual data of the Rx packet from the fw + */ +void iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb) +{ + struct ieee80211_hdr *hdr; + struct ieee80211_rx_status *rx_status; + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_rx_phy_info *phy_info; + struct iwl_rx_mpdu_res_start *rx_res; + struct ieee80211_sta *sta = NULL; + struct sk_buff *skb; + u32 len, pkt_len = iwl_rx_packet_payload_len(pkt); + u32 rate_n_flags; + u32 rx_pkt_status; + u8 crypt_len = 0; + + if (unlikely(pkt_len < sizeof(*rx_res))) { + IWL_DEBUG_DROP(mvm, "Bad REPLY_RX_MPDU_CMD size\n"); + return; + } + + phy_info = &mvm->last_phy_info; + rx_res = (struct iwl_rx_mpdu_res_start *)pkt->data; + hdr = (struct ieee80211_hdr *)(pkt->data + sizeof(*rx_res)); + len = le16_to_cpu(rx_res->byte_count); + + if (unlikely(len + sizeof(*rx_res) + sizeof(__le32) > pkt_len)) { + IWL_DEBUG_DROP(mvm, "FW lied about packet len\n"); + return; + } + + rx_pkt_status = get_unaligned_le32((__le32 *) + (pkt->data + sizeof(*rx_res) + len)); + + /* Dont use dev_alloc_skb(), we'll have enough headroom once + * ieee80211_hdr pulled. + */ + skb = alloc_skb(128, GFP_ATOMIC); + if (!skb) { + IWL_ERR(mvm, "alloc_skb failed\n"); + return; + } + + rx_status = IEEE80211_SKB_RXCB(skb); + + /* + * Keep packets with CRC errors (and with overrun) for monitor mode + * (otherwise the firmware discards them) but mark them as bad. + */ + if (!(rx_pkt_status & RX_MPDU_RES_STATUS_CRC_OK) || + !(rx_pkt_status & RX_MPDU_RES_STATUS_OVERRUN_OK)) { + IWL_DEBUG_RX(mvm, "Bad CRC or FIFO: 0x%08X.\n", rx_pkt_status); + rx_status->flag |= RX_FLAG_FAILED_FCS_CRC; + } + + /* This will be used in several places later */ + rate_n_flags = le32_to_cpu(phy_info->rate_n_flags); + + /* rx_status carries information about the packet to mac80211 */ + rx_status->mactime = le64_to_cpu(phy_info->timestamp); + rx_status->device_timestamp = le32_to_cpu(phy_info->system_timestamp); + rx_status->band = + (phy_info->phy_flags & cpu_to_le16(RX_RES_PHY_FLAGS_BAND_24)) ? + NL80211_BAND_2GHZ : NL80211_BAND_5GHZ; + rx_status->freq = + ieee80211_channel_to_frequency(le16_to_cpu(phy_info->channel), + rx_status->band); + + /* TSF as indicated by the firmware is at INA time */ + rx_status->flag |= RX_FLAG_MACTIME_PLCP_START; + + iwl_mvm_get_signal_strength(mvm, phy_info, rx_status); + + IWL_DEBUG_STATS_LIMIT(mvm, "Rssi %d, TSF %llu\n", rx_status->signal, + (unsigned long long)rx_status->mactime); + + rcu_read_lock(); + if (rx_pkt_status & RX_MPDU_RES_STATUS_SRC_STA_FOUND) { + u32 id = rx_pkt_status & RX_MPDU_RES_STATUS_STA_ID_MSK; + + id >>= RX_MDPU_RES_STATUS_STA_ID_SHIFT; + + if (!WARN_ON_ONCE(id >= mvm->fw->ucode_capa.num_stations)) { + sta = rcu_dereference(mvm->fw_id_to_mac_id[id]); + if (IS_ERR(sta)) + sta = NULL; + } + } else if (!is_multicast_ether_addr(hdr->addr2)) { + /* This is fine since we prevent two stations with the same + * address from being added. + */ + sta = ieee80211_find_sta_by_ifaddr(mvm->hw, hdr->addr2, NULL); + } + + if (sta) { + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct ieee80211_vif *vif = mvmsta->vif; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + /* + * Don't even try to decrypt a MCAST frame that was received + * before the managed vif is authorized, we'd fail anyway. + */ + if (is_multicast_ether_addr(hdr->addr1) && + vif->type == NL80211_IFTYPE_STATION && + !mvmvif->authorized && + ieee80211_has_protected(hdr->frame_control)) { + IWL_DEBUG_DROP(mvm, "MCAST before the vif is authorized\n"); + kfree_skb(skb); + rcu_read_unlock(); + return; + } + } + + /* + * drop the packet if it has failed being decrypted by HW + */ + if (iwl_mvm_set_mac80211_rx_flag(mvm, hdr, rx_status, rx_pkt_status, + &crypt_len)) { + IWL_DEBUG_DROP(mvm, "Bad decryption results 0x%08x\n", + rx_pkt_status); + kfree_skb(skb); + rcu_read_unlock(); + return; + } + + if (sta) { + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct ieee80211_vif *tx_blocked_vif = + rcu_dereference(mvm->csa_tx_blocked_vif); + struct iwl_fw_dbg_trigger_tlv *trig; + struct ieee80211_vif *vif = mvmsta->vif; + + /* We have tx blocked stations (with CS bit). If we heard + * frames from a blocked station on a new channel we can + * TX to it again. + */ + if (unlikely(tx_blocked_vif) && vif == tx_blocked_vif) { + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(tx_blocked_vif); + + if (mvmvif->csa_target_freq == rx_status->freq) + iwl_mvm_sta_modify_disable_tx_ap(mvm, sta, + false); + } + + rs_update_last_rssi(mvm, mvmsta, rx_status); + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, + ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_RSSI); + + if (trig && ieee80211_is_beacon(hdr->frame_control)) { + struct iwl_fw_dbg_trigger_low_rssi *rssi_trig; + s32 rssi; + + rssi_trig = (void *)trig->data; + rssi = le32_to_cpu(rssi_trig->rssi); + + if (rx_status->signal < rssi) + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + NULL); + } + + if (!mvm->tcm.paused && len >= sizeof(*hdr) && + !is_multicast_ether_addr(hdr->addr1) && + ieee80211_is_data(hdr->frame_control)) + iwl_mvm_rx_handle_tcm(mvm, sta, hdr, len, phy_info, + rate_n_flags); + + if (ieee80211_is_data(hdr->frame_control)) + iwl_mvm_rx_csum(sta, skb, rx_pkt_status); + } + rcu_read_unlock(); + + /* set the preamble flag if appropriate */ + if (phy_info->phy_flags & cpu_to_le16(RX_RES_PHY_FLAGS_SHORT_PREAMBLE)) + rx_status->enc_flags |= RX_ENC_FLAG_SHORTPRE; + + if (phy_info->phy_flags & cpu_to_le16(RX_RES_PHY_FLAGS_AGG)) { + /* + * We know which subframes of an A-MPDU belong + * together since we get a single PHY response + * from the firmware for all of them + */ + rx_status->flag |= RX_FLAG_AMPDU_DETAILS; + rx_status->ampdu_reference = mvm->ampdu_ref; + } + + /* Set up the HT phy flags */ + switch (rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK_V1) { + case RATE_MCS_CHAN_WIDTH_20: + break; + case RATE_MCS_CHAN_WIDTH_40: + rx_status->bw = RATE_INFO_BW_40; + break; + case RATE_MCS_CHAN_WIDTH_80: + rx_status->bw = RATE_INFO_BW_80; + break; + case RATE_MCS_CHAN_WIDTH_160: + rx_status->bw = RATE_INFO_BW_160; + break; + } + if (!(rate_n_flags & RATE_MCS_CCK_MSK_V1) && + rate_n_flags & RATE_MCS_SGI_MSK_V1) + rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI; + if (rate_n_flags & RATE_HT_MCS_GF_MSK) + rx_status->enc_flags |= RX_ENC_FLAG_HT_GF; + if (rate_n_flags & RATE_MCS_LDPC_MSK_V1) + rx_status->enc_flags |= RX_ENC_FLAG_LDPC; + if (rate_n_flags & RATE_MCS_HT_MSK_V1) { + u8 stbc = (rate_n_flags & RATE_MCS_STBC_MSK) >> + RATE_MCS_STBC_POS; + rx_status->encoding = RX_ENC_HT; + rx_status->rate_idx = rate_n_flags & RATE_HT_MCS_INDEX_MSK_V1; + rx_status->enc_flags |= stbc << RX_ENC_FLAG_STBC_SHIFT; + } else if (rate_n_flags & RATE_MCS_VHT_MSK_V1) { + u8 stbc = (rate_n_flags & RATE_MCS_STBC_MSK) >> + RATE_MCS_STBC_POS; + rx_status->nss = + ((rate_n_flags & RATE_VHT_MCS_NSS_MSK) >> + RATE_VHT_MCS_NSS_POS) + 1; + rx_status->rate_idx = rate_n_flags & RATE_VHT_MCS_RATE_CODE_MSK; + rx_status->encoding = RX_ENC_VHT; + rx_status->enc_flags |= stbc << RX_ENC_FLAG_STBC_SHIFT; + if (rate_n_flags & RATE_MCS_BF_MSK) + rx_status->enc_flags |= RX_ENC_FLAG_BF; + } else { + int rate = iwl_mvm_legacy_rate_to_mac80211_idx(rate_n_flags, + rx_status->band); + + if (WARN(rate < 0 || rate > 0xFF, + "Invalid rate flags 0x%x, band %d,\n", + rate_n_flags, rx_status->band)) { + kfree_skb(skb); + return; + } + rx_status->rate_idx = rate; + } + +#ifdef CONFIG_IWLWIFI_DEBUGFS + iwl_mvm_update_frame_stats(mvm, rate_n_flags, + rx_status->flag & RX_FLAG_AMPDU_DETAILS); +#endif + + if (unlikely((ieee80211_is_beacon(hdr->frame_control) || + ieee80211_is_probe_resp(hdr->frame_control)) && + mvm->sched_scan_pass_all == SCHED_SCAN_PASS_ALL_ENABLED)) + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_FOUND; + + if (unlikely(ieee80211_is_beacon(hdr->frame_control) || + ieee80211_is_probe_resp(hdr->frame_control))) + rx_status->boottime_ns = ktime_get_boottime_ns(); + + iwl_mvm_pass_packet_to_mac80211(mvm, sta, napi, skb, hdr, len, + crypt_len, rxb); +} + +struct iwl_mvm_stat_data { + struct iwl_mvm *mvm; + __le32 flags; + __le32 mac_id; + u8 beacon_filter_average_energy; + __le32 *beacon_counter; + u8 *beacon_average_energy; +}; + +struct iwl_mvm_stat_data_all_macs { + struct iwl_mvm *mvm; + __le32 flags; + struct iwl_statistics_ntfy_per_mac *per_mac_stats; +}; + +static void iwl_mvm_update_vif_sig(struct ieee80211_vif *vif, int sig) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = mvmvif->mvm; + int thold = vif->bss_conf.cqm_rssi_thold; + int hyst = vif->bss_conf.cqm_rssi_hyst; + int last_event; + + if (sig == 0) { + IWL_DEBUG_RX(mvm, "RSSI is 0 - skip signal based decision\n"); + return; + } + + mvmvif->bf_data.ave_beacon_signal = sig; + + /* BT Coex */ + if (mvmvif->bf_data.bt_coex_min_thold != + mvmvif->bf_data.bt_coex_max_thold) { + last_event = mvmvif->bf_data.last_bt_coex_event; + if (sig > mvmvif->bf_data.bt_coex_max_thold && + (last_event <= mvmvif->bf_data.bt_coex_min_thold || + last_event == 0)) { + mvmvif->bf_data.last_bt_coex_event = sig; + IWL_DEBUG_RX(mvm, "cqm_iterator bt coex high %d\n", + sig); + iwl_mvm_bt_rssi_event(mvm, vif, RSSI_EVENT_HIGH); + } else if (sig < mvmvif->bf_data.bt_coex_min_thold && + (last_event >= mvmvif->bf_data.bt_coex_max_thold || + last_event == 0)) { + mvmvif->bf_data.last_bt_coex_event = sig; + IWL_DEBUG_RX(mvm, "cqm_iterator bt coex low %d\n", + sig); + iwl_mvm_bt_rssi_event(mvm, vif, RSSI_EVENT_LOW); + } + } + + if (!(vif->driver_flags & IEEE80211_VIF_SUPPORTS_CQM_RSSI)) + return; + + /* CQM Notification */ + last_event = mvmvif->bf_data.last_cqm_event; + if (thold && sig < thold && (last_event == 0 || + sig < last_event - hyst)) { + mvmvif->bf_data.last_cqm_event = sig; + IWL_DEBUG_RX(mvm, "cqm_iterator cqm low %d\n", + sig); + ieee80211_cqm_rssi_notify( + vif, + NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW, + sig, + GFP_KERNEL); + } else if (sig > thold && + (last_event == 0 || sig > last_event + hyst)) { + mvmvif->bf_data.last_cqm_event = sig; + IWL_DEBUG_RX(mvm, "cqm_iterator cqm high %d\n", + sig); + ieee80211_cqm_rssi_notify( + vif, + NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH, + sig, + GFP_KERNEL); + } +} + +static void iwl_mvm_stat_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_stat_data *data = _data; + int sig = -data->beacon_filter_average_energy; + u16 id = le32_to_cpu(data->mac_id); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u16 vif_id = mvmvif->id; + + /* This doesn't need the MAC ID check since it's not taking the + * data copied into the "data" struct, but rather the data from + * the notification directly. + */ + mvmvif->beacon_stats.num_beacons = + le32_to_cpu(data->beacon_counter[vif_id]); + mvmvif->beacon_stats.avg_signal = + -data->beacon_average_energy[vif_id]; + + if (mvmvif->id != id) + return; + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + /* make sure that beacon statistics don't go backwards with TCM + * request to clear statistics + */ + if (le32_to_cpu(data->flags) & IWL_STATISTICS_REPLY_FLG_CLEAR) + mvmvif->beacon_stats.accu_num_beacons += + mvmvif->beacon_stats.num_beacons; + + iwl_mvm_update_vif_sig(vif, sig); +} + +static void iwl_mvm_stat_iterator_all_macs(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_stat_data_all_macs *data = _data; + struct iwl_statistics_ntfy_per_mac *mac_stats; + int sig; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u16 vif_id = mvmvif->id; + + if (WARN_ONCE(vif_id >= MAC_INDEX_AUX, "invalid vif id: %d", vif_id)) + return; + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + mac_stats = &data->per_mac_stats[vif_id]; + + mvmvif->beacon_stats.num_beacons = + le32_to_cpu(mac_stats->beacon_counter); + mvmvif->beacon_stats.avg_signal = + -le32_to_cpu(mac_stats->beacon_average_energy); + + /* make sure that beacon statistics don't go backwards with TCM + * request to clear statistics + */ + if (le32_to_cpu(data->flags) & IWL_STATISTICS_REPLY_FLG_CLEAR) + mvmvif->beacon_stats.accu_num_beacons += + mvmvif->beacon_stats.num_beacons; + + sig = -le32_to_cpu(mac_stats->beacon_filter_average_energy); + iwl_mvm_update_vif_sig(vif, sig); +} + +static inline void +iwl_mvm_rx_stats_check_trigger(struct iwl_mvm *mvm, struct iwl_rx_packet *pkt) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_stats *trig_stats; + u32 trig_offset, trig_thold; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, NULL, FW_DBG_TRIGGER_STATS); + if (!trig) + return; + + trig_stats = (void *)trig->data; + + trig_offset = le32_to_cpu(trig_stats->stop_offset); + trig_thold = le32_to_cpu(trig_stats->stop_threshold); + + if (WARN_ON_ONCE(trig_offset >= iwl_rx_packet_payload_len(pkt))) + return; + + if (le32_to_cpup((__le32 *) (pkt->data + trig_offset)) < trig_thold) + return; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, NULL); +} + +static void iwl_mvm_stats_energy_iter(void *_data, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + u8 *energy = _data; + u32 sta_id = mvmsta->sta_id; + + if (WARN_ONCE(sta_id >= IWL_MVM_STATION_COUNT_MAX, "sta_id %d >= %d", + sta_id, IWL_MVM_STATION_COUNT_MAX)) + return; + + if (energy[sta_id]) + mvmsta->avg_energy = energy[sta_id]; + +} + +static void +iwl_mvm_update_tcm_from_stats(struct iwl_mvm *mvm, __le32 *air_time_le, + __le32 *rx_bytes_le) +{ + int i; + + spin_lock(&mvm->tcm.lock); + for (i = 0; i < NUM_MAC_INDEX_DRIVER; i++) { + struct iwl_mvm_tcm_mac *mdata = &mvm->tcm.data[i]; + u32 rx_bytes = le32_to_cpu(rx_bytes_le[i]); + u32 airtime = le32_to_cpu(air_time_le[i]); + + mdata->rx.airtime += airtime; + mdata->uapsd_nonagg_detect.rx_bytes += rx_bytes; + if (airtime) { + /* re-init every time to store rate from FW */ + ewma_rate_init(&mdata->uapsd_nonagg_detect.rate); + ewma_rate_add(&mdata->uapsd_nonagg_detect.rate, + rx_bytes * 8 / airtime); + } + } + spin_unlock(&mvm->tcm.lock); +} + +static void +iwl_mvm_stats_ver_15(struct iwl_mvm *mvm, + struct iwl_statistics_operational_ntfy *stats) +{ + struct iwl_mvm_stat_data_all_macs data = { + .mvm = mvm, + .flags = stats->flags, + .per_mac_stats = stats->per_mac_stats, + }; + + ieee80211_iterate_active_interfaces(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_stat_iterator_all_macs, + &data); +} + +static void +iwl_mvm_stats_ver_14(struct iwl_mvm *mvm, + struct iwl_statistics_operational_ntfy_ver_14 *stats) +{ + struct iwl_mvm_stat_data data = { + .mvm = mvm, + }; + + u8 beacon_average_energy[MAC_INDEX_AUX]; + __le32 flags; + int i; + + flags = stats->flags; + + data.mac_id = stats->mac_id; + data.beacon_filter_average_energy = + le32_to_cpu(stats->beacon_filter_average_energy); + data.flags = flags; + data.beacon_counter = stats->beacon_counter; + + for (i = 0; i < ARRAY_SIZE(beacon_average_energy); i++) + beacon_average_energy[i] = + le32_to_cpu(stats->beacon_average_energy[i]); + + data.beacon_average_energy = beacon_average_energy; + + ieee80211_iterate_active_interfaces(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_stat_iterator, + &data); +} + +static bool iwl_mvm_verify_stats_len(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt, + u32 expected_size) +{ + struct iwl_statistics_ntfy_hdr *hdr; + + if (WARN_ONCE(iwl_rx_packet_payload_len(pkt) < expected_size, + "received invalid statistics size (%d)!, expected_size: %d\n", + iwl_rx_packet_payload_len(pkt), expected_size)) + return false; + + hdr = (void *)&pkt->data; + + if (WARN_ONCE((hdr->type & IWL_STATISTICS_TYPE_MSK) != FW_STATISTICS_OPERATIONAL || + hdr->version != + iwl_fw_lookup_notif_ver(mvm->fw, LEGACY_GROUP, STATISTICS_NOTIFICATION, 0), + "received unsupported hdr type %d, version %d\n", + hdr->type, hdr->version)) + return false; + + if (WARN_ONCE(le16_to_cpu(hdr->size) != expected_size, + "received invalid statistics size in header (%d)!, expected_size: %d\n", + le16_to_cpu(hdr->size), expected_size)) + return false; + + return true; +} + +static void +iwl_mvm_handle_rx_statistics_tlv(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{ + u8 average_energy[IWL_MVM_STATION_COUNT_MAX]; + __le32 air_time[MAC_INDEX_AUX]; + __le32 rx_bytes[MAC_INDEX_AUX]; + __le32 flags = 0; + int i; + u32 notif_ver = iwl_fw_lookup_notif_ver(mvm->fw, LEGACY_GROUP, + STATISTICS_NOTIFICATION, 0); + + if (WARN_ONCE(notif_ver > 15, + "invalid statistics version id: %d\n", notif_ver)) + return; + + if (notif_ver == 14) { + struct iwl_statistics_operational_ntfy_ver_14 *stats = + (void *)pkt->data; + + if (!iwl_mvm_verify_stats_len(mvm, pkt, sizeof(*stats))) + return; + + iwl_mvm_stats_ver_14(mvm, stats); + + flags = stats->flags; + mvm->radio_stats.rx_time = le64_to_cpu(stats->rx_time); + mvm->radio_stats.tx_time = le64_to_cpu(stats->tx_time); + mvm->radio_stats.on_time_rf = le64_to_cpu(stats->on_time_rf); + mvm->radio_stats.on_time_scan = + le64_to_cpu(stats->on_time_scan); + + for (i = 0; i < ARRAY_SIZE(average_energy); i++) + average_energy[i] = le32_to_cpu(stats->average_energy[i]); + + for (i = 0; i < ARRAY_SIZE(air_time); i++) { + air_time[i] = stats->air_time[i]; + rx_bytes[i] = stats->rx_bytes[i]; + } + } + + if (notif_ver == 15) { + struct iwl_statistics_operational_ntfy *stats = + (void *)pkt->data; + + if (!iwl_mvm_verify_stats_len(mvm, pkt, sizeof(*stats))) + return; + + iwl_mvm_stats_ver_15(mvm, stats); + + flags = stats->flags; + mvm->radio_stats.rx_time = le64_to_cpu(stats->rx_time); + mvm->radio_stats.tx_time = le64_to_cpu(stats->tx_time); + mvm->radio_stats.on_time_rf = le64_to_cpu(stats->on_time_rf); + mvm->radio_stats.on_time_scan = + le64_to_cpu(stats->on_time_scan); + + for (i = 0; i < ARRAY_SIZE(average_energy); i++) + average_energy[i] = + le32_to_cpu(stats->per_sta_stats[i].average_energy); + + for (i = 0; i < ARRAY_SIZE(air_time); i++) { + air_time[i] = stats->per_mac_stats[i].air_time; + rx_bytes[i] = stats->per_mac_stats[i].rx_bytes; + } + } + + iwl_mvm_rx_stats_check_trigger(mvm, pkt); + + ieee80211_iterate_stations_atomic(mvm->hw, iwl_mvm_stats_energy_iter, + average_energy); + /* + * Don't update in case the statistics are not cleared, since + * we will end up counting twice the same airtime, once in TCM + * request and once in statistics notification. + */ + if (le32_to_cpu(flags) & IWL_STATISTICS_REPLY_FLG_CLEAR) + iwl_mvm_update_tcm_from_stats(mvm, air_time, rx_bytes); +} + +void iwl_mvm_handle_rx_statistics(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{ + struct iwl_mvm_stat_data data = { + .mvm = mvm, + }; + __le32 *bytes, *air_time, flags; + int expected_size; + u8 *energy; + + /* From ver 14 and up we use TLV statistics format */ + if (iwl_fw_lookup_notif_ver(mvm->fw, LEGACY_GROUP, + STATISTICS_NOTIFICATION, 0) >= 14) + return iwl_mvm_handle_rx_statistics_tlv(mvm, pkt); + + if (!iwl_mvm_has_new_rx_stats_api(mvm)) { + if (iwl_mvm_has_new_rx_api(mvm)) + expected_size = sizeof(struct iwl_notif_statistics_v11); + else + expected_size = sizeof(struct iwl_notif_statistics_v10); + } else { + expected_size = sizeof(struct iwl_notif_statistics); + } + + if (WARN_ONCE(iwl_rx_packet_payload_len(pkt) != expected_size, + "received invalid statistics size (%d)!\n", + iwl_rx_packet_payload_len(pkt))) + return; + + if (!iwl_mvm_has_new_rx_stats_api(mvm)) { + struct iwl_notif_statistics_v11 *stats = (void *)&pkt->data; + + data.mac_id = stats->rx.general.mac_id; + data.beacon_filter_average_energy = + stats->general.common.beacon_filter_average_energy; + + mvm->rx_stats_v3 = stats->rx; + + mvm->radio_stats.rx_time = + le64_to_cpu(stats->general.common.rx_time); + mvm->radio_stats.tx_time = + le64_to_cpu(stats->general.common.tx_time); + mvm->radio_stats.on_time_rf = + le64_to_cpu(stats->general.common.on_time_rf); + mvm->radio_stats.on_time_scan = + le64_to_cpu(stats->general.common.on_time_scan); + + data.beacon_counter = stats->general.beacon_counter; + data.beacon_average_energy = + stats->general.beacon_average_energy; + flags = stats->flag; + } else { + struct iwl_notif_statistics *stats = (void *)&pkt->data; + + data.mac_id = stats->rx.general.mac_id; + data.beacon_filter_average_energy = + stats->general.common.beacon_filter_average_energy; + + mvm->rx_stats = stats->rx; + + mvm->radio_stats.rx_time = + le64_to_cpu(stats->general.common.rx_time); + mvm->radio_stats.tx_time = + le64_to_cpu(stats->general.common.tx_time); + mvm->radio_stats.on_time_rf = + le64_to_cpu(stats->general.common.on_time_rf); + mvm->radio_stats.on_time_scan = + le64_to_cpu(stats->general.common.on_time_scan); + + data.beacon_counter = stats->general.beacon_counter; + data.beacon_average_energy = + stats->general.beacon_average_energy; + flags = stats->flag; + } + data.flags = flags; + + iwl_mvm_rx_stats_check_trigger(mvm, pkt); + + ieee80211_iterate_active_interfaces(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_stat_iterator, + &data); + + if (!iwl_mvm_has_new_rx_api(mvm)) + return; + + if (!iwl_mvm_has_new_rx_stats_api(mvm)) { + struct iwl_notif_statistics_v11 *v11 = (void *)&pkt->data; + + energy = (void *)&v11->load_stats.avg_energy; + bytes = (void *)&v11->load_stats.byte_count; + air_time = (void *)&v11->load_stats.air_time; + } else { + struct iwl_notif_statistics *stats = (void *)&pkt->data; + + energy = (void *)&stats->load_stats.avg_energy; + bytes = (void *)&stats->load_stats.byte_count; + air_time = (void *)&stats->load_stats.air_time; + } + ieee80211_iterate_stations_atomic(mvm->hw, iwl_mvm_stats_energy_iter, + energy); + + /* + * Don't update in case the statistics are not cleared, since + * we will end up counting twice the same airtime, once in TCM + * request and once in statistics notification. + */ + if (le32_to_cpu(flags) & IWL_STATISTICS_REPLY_FLG_CLEAR) + iwl_mvm_update_tcm_from_stats(mvm, air_time, bytes); + +} + +void iwl_mvm_rx_statistics(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + iwl_mvm_handle_rx_statistics(mvm, rxb_addr(rxb)); +} + +void iwl_mvm_window_status_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_ba_window_status_notif *notif = (void *)pkt->data; + int i; + + BUILD_BUG_ON(ARRAY_SIZE(notif->ra_tid) != BA_WINDOW_STREAMS_MAX); + BUILD_BUG_ON(ARRAY_SIZE(notif->mpdu_rx_count) != BA_WINDOW_STREAMS_MAX); + BUILD_BUG_ON(ARRAY_SIZE(notif->bitmap) != BA_WINDOW_STREAMS_MAX); + BUILD_BUG_ON(ARRAY_SIZE(notif->start_seq_num) != BA_WINDOW_STREAMS_MAX); + + rcu_read_lock(); + for (i = 0; i < BA_WINDOW_STREAMS_MAX; i++) { + struct ieee80211_sta *sta; + u8 sta_id, tid; + u64 bitmap; + u32 ssn; + u16 ratid; + u16 received_mpdu; + + ratid = le16_to_cpu(notif->ra_tid[i]); + /* check that this TID is valid */ + if (!(ratid & BA_WINDOW_STATUS_VALID_MSK)) + continue; + + received_mpdu = le16_to_cpu(notif->mpdu_rx_count[i]); + if (received_mpdu == 0) + continue; + + tid = ratid & BA_WINDOW_STATUS_TID_MSK; + /* get the station */ + sta_id = (ratid & BA_WINDOW_STATUS_STA_ID_MSK) + >> BA_WINDOW_STATUS_STA_ID_POS; + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + if (IS_ERR_OR_NULL(sta)) + continue; + bitmap = le64_to_cpu(notif->bitmap[i]); + ssn = le32_to_cpu(notif->start_seq_num[i]); + + /* update mac80211 with the bitmap for the reordering buffer */ + ieee80211_mark_rx_ba_filtered_frames(sta, tid, ssn, bitmap, + received_mpdu); + } + rcu_read_unlock(); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c b/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c new file mode 100644 index 000000000..f268a31ce --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c @@ -0,0 +1,2214 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include "iwl-trans.h" +#include "mvm.h" +#include "fw-api.h" + +static void *iwl_mvm_skb_get_hdr(struct sk_buff *skb) +{ + struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); + u8 *data = skb->data; + + /* Alignment concerns */ + BUILD_BUG_ON(sizeof(struct ieee80211_radiotap_he) % 4); + BUILD_BUG_ON(sizeof(struct ieee80211_radiotap_he_mu) % 4); + BUILD_BUG_ON(sizeof(struct ieee80211_radiotap_lsig) % 4); + BUILD_BUG_ON(sizeof(struct ieee80211_vendor_radiotap) % 4); + + if (rx_status->flag & RX_FLAG_RADIOTAP_HE) + data += sizeof(struct ieee80211_radiotap_he); + if (rx_status->flag & RX_FLAG_RADIOTAP_HE_MU) + data += sizeof(struct ieee80211_radiotap_he_mu); + if (rx_status->flag & RX_FLAG_RADIOTAP_LSIG) + data += sizeof(struct ieee80211_radiotap_lsig); + if (rx_status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA) { + struct ieee80211_vendor_radiotap *radiotap = (void *)data; + + data += sizeof(*radiotap) + radiotap->len + radiotap->pad; + } + + return data; +} + +static inline int iwl_mvm_check_pn(struct iwl_mvm *mvm, struct sk_buff *skb, + int queue, struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvmsta; + struct ieee80211_hdr *hdr = iwl_mvm_skb_get_hdr(skb); + struct ieee80211_rx_status *stats = IEEE80211_SKB_RXCB(skb); + struct iwl_mvm_key_pn *ptk_pn; + int res; + u8 tid, keyidx; + u8 pn[IEEE80211_CCMP_PN_LEN]; + u8 *extiv; + + /* do PN checking */ + + /* multicast and non-data only arrives on default queue */ + if (!ieee80211_is_data(hdr->frame_control) || + is_multicast_ether_addr(hdr->addr1)) + return 0; + + /* do not check PN for open AP */ + if (!(stats->flag & RX_FLAG_DECRYPTED)) + return 0; + + /* + * avoid checking for default queue - we don't want to replicate + * all the logic that's necessary for checking the PN on fragmented + * frames, leave that to mac80211 + */ + if (queue == 0) + return 0; + + /* if we are here - this for sure is either CCMP or GCMP */ + if (IS_ERR_OR_NULL(sta)) { + IWL_DEBUG_DROP(mvm, + "expected hw-decrypted unicast frame for station\n"); + return -1; + } + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + extiv = (u8 *)hdr + ieee80211_hdrlen(hdr->frame_control); + keyidx = extiv[3] >> 6; + + ptk_pn = rcu_dereference(mvmsta->ptk_pn[keyidx]); + if (!ptk_pn) + return -1; + + if (ieee80211_is_data_qos(hdr->frame_control)) + tid = ieee80211_get_tid(hdr); + else + tid = 0; + + /* we don't use HCCA/802.11 QoS TSPECs, so drop such frames */ + if (tid >= IWL_MAX_TID_COUNT) + return -1; + + /* load pn */ + pn[0] = extiv[7]; + pn[1] = extiv[6]; + pn[2] = extiv[5]; + pn[3] = extiv[4]; + pn[4] = extiv[1]; + pn[5] = extiv[0]; + + res = memcmp(pn, ptk_pn->q[queue].pn[tid], IEEE80211_CCMP_PN_LEN); + if (res < 0) + return -1; + if (!res && !(stats->flag & RX_FLAG_ALLOW_SAME_PN)) + return -1; + + memcpy(ptk_pn->q[queue].pn[tid], pn, IEEE80211_CCMP_PN_LEN); + stats->flag |= RX_FLAG_PN_VALIDATED; + + return 0; +} + +/* iwl_mvm_create_skb Adds the rxb to a new skb */ +static int iwl_mvm_create_skb(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_hdr *hdr, u16 len, u8 crypt_len, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_rx_mpdu_desc *desc = (void *)pkt->data; + unsigned int headlen, fraglen, pad_len = 0; + unsigned int hdrlen = ieee80211_hdrlen(hdr->frame_control); + u8 mic_crc_len = u8_get_bits(desc->mac_flags1, + IWL_RX_MPDU_MFLG1_MIC_CRC_LEN_MASK) << 1; + + if (desc->mac_flags2 & IWL_RX_MPDU_MFLG2_PAD) { + len -= 2; + pad_len = 2; + } + + /* + * For non monitor interface strip the bytes the RADA might not have + * removed. As monitor interface cannot exist with other interfaces + * this removal is safe. + */ + if (mic_crc_len && !ieee80211_hw_check(mvm->hw, RX_INCLUDES_FCS)) { + u32 pkt_flags = le32_to_cpu(pkt->len_n_flags); + + /* + * If RADA was not enabled then decryption was not performed so + * the MIC cannot be removed. + */ + if (!(pkt_flags & FH_RSCSR_RADA_EN)) { + if (WARN_ON(crypt_len > mic_crc_len)) + return -EINVAL; + + mic_crc_len -= crypt_len; + } + + if (WARN_ON(mic_crc_len > len)) + return -EINVAL; + + len -= mic_crc_len; + } + + /* If frame is small enough to fit in skb->head, pull it completely. + * If not, only pull ieee80211_hdr (including crypto if present, and + * an additional 8 bytes for SNAP/ethertype, see below) so that + * splice() or TCP coalesce are more efficient. + * + * Since, in addition, ieee80211_data_to_8023() always pull in at + * least 8 bytes (possibly more for mesh) we can do the same here + * to save the cost of doing it later. That still doesn't pull in + * the actual IP header since the typical case has a SNAP header. + * If the latter changes (there are efforts in the standards group + * to do so) we should revisit this and ieee80211_data_to_8023(). + */ + headlen = (len <= skb_tailroom(skb)) ? len : + hdrlen + crypt_len + 8; + + /* The firmware may align the packet to DWORD. + * The padding is inserted after the IV. + * After copying the header + IV skip the padding if + * present before copying packet data. + */ + hdrlen += crypt_len; + + if (unlikely(headlen < hdrlen)) + return -EINVAL; + + skb_put_data(skb, hdr, hdrlen); + skb_put_data(skb, (u8 *)hdr + hdrlen + pad_len, headlen - hdrlen); + + /* + * If we did CHECKSUM_COMPLETE, the hardware only does it right for + * certain cases and starts the checksum after the SNAP. Check if + * this is the case - it's easier to just bail out to CHECKSUM_NONE + * in the cases the hardware didn't handle, since it's rare to see + * such packets, even though the hardware did calculate the checksum + * in this case, just starting after the MAC header instead. + * + * Starting from Bz hardware, it calculates starting directly after + * the MAC header, so that matches mac80211's expectation. + */ + if (skb->ip_summed == CHECKSUM_COMPLETE) { + struct { + u8 hdr[6]; + __be16 type; + } __packed *shdr = (void *)((u8 *)hdr + hdrlen + pad_len); + + if (unlikely(headlen - hdrlen < sizeof(*shdr) || + !ether_addr_equal(shdr->hdr, rfc1042_header) || + (shdr->type != htons(ETH_P_IP) && + shdr->type != htons(ETH_P_ARP) && + shdr->type != htons(ETH_P_IPV6) && + shdr->type != htons(ETH_P_8021Q) && + shdr->type != htons(ETH_P_PAE) && + shdr->type != htons(ETH_P_TDLS)))) + skb->ip_summed = CHECKSUM_NONE; + else if (mvm->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_BZ) + /* mac80211 assumes full CSUM including SNAP header */ + skb_postpush_rcsum(skb, shdr, sizeof(*shdr)); + } + + fraglen = len - headlen; + + if (fraglen) { + int offset = (u8 *)hdr + headlen + pad_len - + (u8 *)rxb_addr(rxb) + rxb_offset(rxb); + + skb_add_rx_frag(skb, 0, rxb_steal_page(rxb), offset, + fraglen, rxb->truesize); + } + + return 0; +} + +static void iwl_mvm_add_rtap_sniffer_config(struct iwl_mvm *mvm, + struct sk_buff *skb) +{ + struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); + struct ieee80211_vendor_radiotap *radiotap; + const int size = sizeof(*radiotap) + sizeof(__le16); + + if (!mvm->cur_aid) + return; + + /* ensure alignment */ + BUILD_BUG_ON((size + 2) % 4); + + radiotap = skb_put(skb, size + 2); + radiotap->align = 1; + /* Intel OUI */ + radiotap->oui[0] = 0xf6; + radiotap->oui[1] = 0x54; + radiotap->oui[2] = 0x25; + /* radiotap sniffer config sub-namespace */ + radiotap->subns = 1; + radiotap->present = 0x1; + radiotap->len = size - sizeof(*radiotap); + radiotap->pad = 2; + + /* fill the data now */ + memcpy(radiotap->data, &mvm->cur_aid, sizeof(mvm->cur_aid)); + /* and clear the padding */ + memset(radiotap->data + sizeof(__le16), 0, radiotap->pad); + + rx_status->flag |= RX_FLAG_RADIOTAP_VENDOR_DATA; +} + +/* iwl_mvm_pass_packet_to_mac80211 - passes the packet for mac80211 */ +static void iwl_mvm_pass_packet_to_mac80211(struct iwl_mvm *mvm, + struct napi_struct *napi, + struct sk_buff *skb, int queue, + struct ieee80211_sta *sta) +{ + if (iwl_mvm_check_pn(mvm, skb, queue, sta)) + kfree_skb(skb); + else + ieee80211_rx_napi(mvm->hw, sta, skb, napi); +} + +static void iwl_mvm_get_signal_strength(struct iwl_mvm *mvm, + struct ieee80211_rx_status *rx_status, + u32 rate_n_flags, int energy_a, + int energy_b) +{ + int max_energy; + u32 rate_flags = rate_n_flags; + + energy_a = energy_a ? -energy_a : S8_MIN; + energy_b = energy_b ? -energy_b : S8_MIN; + max_energy = max(energy_a, energy_b); + + IWL_DEBUG_STATS(mvm, "energy In A %d B %d, and max %d\n", + energy_a, energy_b, max_energy); + + rx_status->signal = max_energy; + rx_status->chains = + (rate_flags & RATE_MCS_ANT_AB_MSK) >> RATE_MCS_ANT_POS; + rx_status->chain_signal[0] = energy_a; + rx_status->chain_signal[1] = energy_b; +} + +static int iwl_mvm_rx_mgmt_prot(struct ieee80211_sta *sta, + struct ieee80211_hdr *hdr, + struct iwl_rx_mpdu_desc *desc, + u32 status, + struct ieee80211_rx_status *stats) +{ + struct iwl_mvm_sta *mvmsta; + struct iwl_mvm_vif *mvmvif; + u8 keyid; + struct ieee80211_key_conf *key; + u32 len = le16_to_cpu(desc->mpdu_len); + const u8 *frame = (void *)hdr; + + if ((status & IWL_RX_MPDU_STATUS_SEC_MASK) == IWL_RX_MPDU_STATUS_SEC_NONE) + return 0; + + /* + * For non-beacon, we don't really care. But beacons may + * be filtered out, and we thus need the firmware's replay + * detection, otherwise beacons the firmware previously + * filtered could be replayed, or something like that, and + * it can filter a lot - though usually only if nothing has + * changed. + */ + if (!ieee80211_is_beacon(hdr->frame_control)) + return 0; + + /* key mismatch - will also report !MIC_OK but we shouldn't count it */ + if (!(status & IWL_RX_MPDU_STATUS_KEY_VALID)) + return -1; + + /* good cases */ + if (likely(status & IWL_RX_MPDU_STATUS_MIC_OK && + !(status & IWL_RX_MPDU_STATUS_REPLAY_ERROR))) { + stats->flag |= RX_FLAG_DECRYPTED; + return 0; + } + + if (!sta) + return -1; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + mvmvif = iwl_mvm_vif_from_mac80211(mvmsta->vif); + + /* + * both keys will have the same cipher and MIC length, use + * whichever one is available + */ + key = rcu_dereference(mvmvif->bcn_prot.keys[0]); + if (!key) { + key = rcu_dereference(mvmvif->bcn_prot.keys[1]); + if (!key) + return -1; + } + + if (len < key->icv_len + IEEE80211_GMAC_PN_LEN + 2) + return -1; + + /* get the real key ID */ + keyid = frame[len - key->icv_len - IEEE80211_GMAC_PN_LEN - 2]; + /* and if that's the other key, look it up */ + if (keyid != key->keyidx) { + /* + * shouldn't happen since firmware checked, but be safe + * in case the MIC length is wrong too, for example + */ + if (keyid != 6 && keyid != 7) + return -1; + key = rcu_dereference(mvmvif->bcn_prot.keys[keyid - 6]); + if (!key) + return -1; + } + + /* Report status to mac80211 */ + if (!(status & IWL_RX_MPDU_STATUS_MIC_OK)) + ieee80211_key_mic_failure(key); + else if (status & IWL_RX_MPDU_STATUS_REPLAY_ERROR) + ieee80211_key_replay(key); + + return -1; +} + +static int iwl_mvm_rx_crypto(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + struct ieee80211_hdr *hdr, + struct ieee80211_rx_status *stats, u16 phy_info, + struct iwl_rx_mpdu_desc *desc, + u32 pkt_flags, int queue, u8 *crypt_len) +{ + u32 status = le32_to_cpu(desc->status); + + /* + * Drop UNKNOWN frames in aggregation, unless in monitor mode + * (where we don't have the keys). + * We limit this to aggregation because in TKIP this is a valid + * scenario, since we may not have the (correct) TTAK (phase 1 + * key) in the firmware. + */ + if (phy_info & IWL_RX_MPDU_PHY_AMPDU && + (status & IWL_RX_MPDU_STATUS_SEC_MASK) == + IWL_RX_MPDU_STATUS_SEC_UNKNOWN && !mvm->monitor_on) + return -1; + + if (unlikely(ieee80211_is_mgmt(hdr->frame_control) && + !ieee80211_has_protected(hdr->frame_control))) + return iwl_mvm_rx_mgmt_prot(sta, hdr, desc, status, stats); + + if (!ieee80211_has_protected(hdr->frame_control) || + (status & IWL_RX_MPDU_STATUS_SEC_MASK) == + IWL_RX_MPDU_STATUS_SEC_NONE) + return 0; + + /* TODO: handle packets encrypted with unknown alg */ + + switch (status & IWL_RX_MPDU_STATUS_SEC_MASK) { + case IWL_RX_MPDU_STATUS_SEC_CCM: + case IWL_RX_MPDU_STATUS_SEC_GCM: + BUILD_BUG_ON(IEEE80211_CCMP_PN_LEN != IEEE80211_GCMP_PN_LEN); + /* alg is CCM: check MIC only */ + if (!(status & IWL_RX_MPDU_STATUS_MIC_OK)) + return -1; + + stats->flag |= RX_FLAG_DECRYPTED; + if (pkt_flags & FH_RSCSR_RADA_EN) + stats->flag |= RX_FLAG_MIC_STRIPPED; + *crypt_len = IEEE80211_CCMP_HDR_LEN; + return 0; + case IWL_RX_MPDU_STATUS_SEC_TKIP: + /* Don't drop the frame and decrypt it in SW */ + if (!fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_DEPRECATE_TTAK) && + !(status & IWL_RX_MPDU_RES_STATUS_TTAK_OK)) + return 0; + + if (mvm->trans->trans_cfg->gen2 && + !(status & RX_MPDU_RES_STATUS_MIC_OK)) + stats->flag |= RX_FLAG_MMIC_ERROR; + + *crypt_len = IEEE80211_TKIP_IV_LEN; + fallthrough; + case IWL_RX_MPDU_STATUS_SEC_WEP: + if (!(status & IWL_RX_MPDU_STATUS_ICV_OK)) + return -1; + + stats->flag |= RX_FLAG_DECRYPTED; + if ((status & IWL_RX_MPDU_STATUS_SEC_MASK) == + IWL_RX_MPDU_STATUS_SEC_WEP) + *crypt_len = IEEE80211_WEP_IV_LEN; + + if (pkt_flags & FH_RSCSR_RADA_EN) { + stats->flag |= RX_FLAG_ICV_STRIPPED; + if (mvm->trans->trans_cfg->gen2) + stats->flag |= RX_FLAG_MMIC_STRIPPED; + } + + return 0; + case IWL_RX_MPDU_STATUS_SEC_EXT_ENC: + if (!(status & IWL_RX_MPDU_STATUS_MIC_OK)) + return -1; + stats->flag |= RX_FLAG_DECRYPTED; + return 0; + case RX_MPDU_RES_STATUS_SEC_CMAC_GMAC_ENC: + break; + default: + /* + * Sometimes we can get frames that were not decrypted + * because the firmware didn't have the keys yet. This can + * happen after connection where we can get multicast frames + * before the GTK is installed. + * Silently drop those frames. + * Also drop un-decrypted frames in monitor mode. + */ + if (!is_multicast_ether_addr(hdr->addr1) && + !mvm->monitor_on && net_ratelimit()) + IWL_ERR(mvm, "Unhandled alg: 0x%x\n", status); + } + + return 0; +} + +static void iwl_mvm_rx_csum(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct sk_buff *skb, + struct iwl_rx_packet *pkt) +{ + struct iwl_rx_mpdu_desc *desc = (void *)pkt->data; + + if (mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + if (pkt->len_n_flags & cpu_to_le32(FH_RSCSR_RPA_EN)) { + u16 hwsum = be16_to_cpu(desc->v3.raw_xsum); + + skb->ip_summed = CHECKSUM_COMPLETE; + skb->csum = csum_unfold(~(__force __sum16)hwsum); + } + } else { + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_vif *mvmvif; + u16 flags = le16_to_cpu(desc->l3l4_flags); + u8 l3_prot = (u8)((flags & IWL_RX_L3L4_L3_PROTO_MASK) >> + IWL_RX_L3_PROTO_POS); + + mvmvif = iwl_mvm_vif_from_mac80211(mvmsta->vif); + + if (mvmvif->features & NETIF_F_RXCSUM && + flags & IWL_RX_L3L4_TCP_UDP_CSUM_OK && + (flags & IWL_RX_L3L4_IP_HDR_CSUM_OK || + l3_prot == IWL_RX_L3_TYPE_IPV6 || + l3_prot == IWL_RX_L3_TYPE_IPV6_FRAG)) + skb->ip_summed = CHECKSUM_UNNECESSARY; + } +} + +/* + * returns true if a packet is a duplicate and should be dropped. + * Updates AMSDU PN tracking info + */ +static bool iwl_mvm_is_dup(struct ieee80211_sta *sta, int queue, + struct ieee80211_rx_status *rx_status, + struct ieee80211_hdr *hdr, + struct iwl_rx_mpdu_desc *desc) +{ + struct iwl_mvm_sta *mvm_sta; + struct iwl_mvm_rxq_dup_data *dup_data; + u8 tid, sub_frame_idx; + + if (WARN_ON(IS_ERR_OR_NULL(sta))) + return false; + + mvm_sta = iwl_mvm_sta_from_mac80211(sta); + dup_data = &mvm_sta->dup_data[queue]; + + /* + * Drop duplicate 802.11 retransmissions + * (IEEE 802.11-2012: 9.3.2.10 "Duplicate detection and recovery") + */ + if (ieee80211_is_ctl(hdr->frame_control) || + ieee80211_is_qos_nullfunc(hdr->frame_control) || + is_multicast_ether_addr(hdr->addr1)) { + rx_status->flag |= RX_FLAG_DUP_VALIDATED; + return false; + } + + if (ieee80211_is_data_qos(hdr->frame_control)) + /* frame has qos control */ + tid = ieee80211_get_tid(hdr); + else + tid = IWL_MAX_TID_COUNT; + + /* If this wasn't a part of an A-MSDU the sub-frame index will be 0 */ + sub_frame_idx = desc->amsdu_info & + IWL_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK; + + if (unlikely(ieee80211_has_retry(hdr->frame_control) && + dup_data->last_seq[tid] == hdr->seq_ctrl && + dup_data->last_sub_frame[tid] >= sub_frame_idx)) + return true; + + /* Allow same PN as the first subframe for following sub frames */ + if (dup_data->last_seq[tid] == hdr->seq_ctrl && + sub_frame_idx > dup_data->last_sub_frame[tid] && + desc->mac_flags2 & IWL_RX_MPDU_MFLG2_AMSDU) + rx_status->flag |= RX_FLAG_ALLOW_SAME_PN; + + dup_data->last_seq[tid] = hdr->seq_ctrl; + dup_data->last_sub_frame[tid] = sub_frame_idx; + + rx_status->flag |= RX_FLAG_DUP_VALIDATED; + + return false; +} + +/* + * Returns true if sn2 - buffer_size < sn1 < sn2. + * To be used only in order to compare reorder buffer head with NSSN. + * We fully trust NSSN unless it is behind us due to reorder timeout. + * Reorder timeout can only bring us up to buffer_size SNs ahead of NSSN. + */ +static bool iwl_mvm_is_sn_less(u16 sn1, u16 sn2, u16 buffer_size) +{ + return ieee80211_sn_less(sn1, sn2) && + !ieee80211_sn_less(sn1, sn2 - buffer_size); +} + +static void iwl_mvm_sync_nssn(struct iwl_mvm *mvm, u8 baid, u16 nssn) +{ + if (IWL_MVM_USE_NSSN_SYNC) { + struct iwl_mvm_nssn_sync_data notif = { + .baid = baid, + .nssn = nssn, + }; + + iwl_mvm_sync_rx_queues_internal(mvm, IWL_MVM_RXQ_NSSN_SYNC, false, + ¬if, sizeof(notif)); + } +} + +#define RX_REORDER_BUF_TIMEOUT_MQ (HZ / 10) + +enum iwl_mvm_release_flags { + IWL_MVM_RELEASE_SEND_RSS_SYNC = BIT(0), + IWL_MVM_RELEASE_FROM_RSS_SYNC = BIT(1), +}; + +static void iwl_mvm_release_frames(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct napi_struct *napi, + struct iwl_mvm_baid_data *baid_data, + struct iwl_mvm_reorder_buffer *reorder_buf, + u16 nssn, u32 flags) +{ + struct iwl_mvm_reorder_buf_entry *entries = + &baid_data->entries[reorder_buf->queue * + baid_data->entries_per_queue]; + u16 ssn = reorder_buf->head_sn; + + lockdep_assert_held(&reorder_buf->lock); + + /* + * We keep the NSSN not too far behind, if we are sync'ing it and it + * is more than 2048 ahead of us, it must be behind us. Discard it. + * This can happen if the queue that hit the 0 / 2048 seqno was lagging + * behind and this queue already processed packets. The next if + * would have caught cases where this queue would have processed less + * than 64 packets, but it may have processed more than 64 packets. + */ + if ((flags & IWL_MVM_RELEASE_FROM_RSS_SYNC) && + ieee80211_sn_less(nssn, ssn)) + goto set_timer; + + /* ignore nssn smaller than head sn - this can happen due to timeout */ + if (iwl_mvm_is_sn_less(nssn, ssn, reorder_buf->buf_size)) + goto set_timer; + + while (iwl_mvm_is_sn_less(ssn, nssn, reorder_buf->buf_size)) { + int index = ssn % reorder_buf->buf_size; + struct sk_buff_head *skb_list = &entries[index].e.frames; + struct sk_buff *skb; + + ssn = ieee80211_sn_inc(ssn); + if ((flags & IWL_MVM_RELEASE_SEND_RSS_SYNC) && + (ssn == 2048 || ssn == 0)) + iwl_mvm_sync_nssn(mvm, baid_data->baid, ssn); + + /* + * Empty the list. Will have more than one frame for A-MSDU. + * Empty list is valid as well since nssn indicates frames were + * received. + */ + while ((skb = __skb_dequeue(skb_list))) { + iwl_mvm_pass_packet_to_mac80211(mvm, napi, skb, + reorder_buf->queue, + sta); + reorder_buf->num_stored--; + } + } + reorder_buf->head_sn = nssn; + +set_timer: + if (reorder_buf->num_stored && !reorder_buf->removed) { + u16 index = reorder_buf->head_sn % reorder_buf->buf_size; + + while (skb_queue_empty(&entries[index].e.frames)) + index = (index + 1) % reorder_buf->buf_size; + /* modify timer to match next frame's expiration time */ + mod_timer(&reorder_buf->reorder_timer, + entries[index].e.reorder_time + 1 + + RX_REORDER_BUF_TIMEOUT_MQ); + } else { + del_timer(&reorder_buf->reorder_timer); + } +} + +void iwl_mvm_reorder_timer_expired(struct timer_list *t) +{ + struct iwl_mvm_reorder_buffer *buf = from_timer(buf, t, reorder_timer); + struct iwl_mvm_baid_data *baid_data = + iwl_mvm_baid_data_from_reorder_buf(buf); + struct iwl_mvm_reorder_buf_entry *entries = + &baid_data->entries[buf->queue * baid_data->entries_per_queue]; + int i; + u16 sn = 0, index = 0; + bool expired = false; + bool cont = false; + + spin_lock(&buf->lock); + + if (!buf->num_stored || buf->removed) { + spin_unlock(&buf->lock); + return; + } + + for (i = 0; i < buf->buf_size ; i++) { + index = (buf->head_sn + i) % buf->buf_size; + + if (skb_queue_empty(&entries[index].e.frames)) { + /* + * If there is a hole and the next frame didn't expire + * we want to break and not advance SN + */ + cont = false; + continue; + } + if (!cont && + !time_after(jiffies, entries[index].e.reorder_time + + RX_REORDER_BUF_TIMEOUT_MQ)) + break; + + expired = true; + /* continue until next hole after this expired frames */ + cont = true; + sn = ieee80211_sn_add(buf->head_sn, i + 1); + } + + if (expired) { + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + u8 sta_id = baid_data->sta_id; + + rcu_read_lock(); + sta = rcu_dereference(buf->mvm->fw_id_to_mac_id[sta_id]); + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + /* SN is set to the last expired frame + 1 */ + IWL_DEBUG_HT(buf->mvm, + "Releasing expired frames for sta %u, sn %d\n", + sta_id, sn); + iwl_mvm_event_frame_timeout_callback(buf->mvm, mvmsta->vif, + sta, baid_data->tid); + iwl_mvm_release_frames(buf->mvm, sta, NULL, baid_data, + buf, sn, IWL_MVM_RELEASE_SEND_RSS_SYNC); + rcu_read_unlock(); + } else { + /* + * If no frame expired and there are stored frames, index is now + * pointing to the first unexpired frame - modify timer + * accordingly to this frame. + */ + mod_timer(&buf->reorder_timer, + entries[index].e.reorder_time + + 1 + RX_REORDER_BUF_TIMEOUT_MQ); + } + spin_unlock(&buf->lock); +} + +static void iwl_mvm_del_ba(struct iwl_mvm *mvm, int queue, + struct iwl_mvm_delba_data *data) +{ + struct iwl_mvm_baid_data *ba_data; + struct ieee80211_sta *sta; + struct iwl_mvm_reorder_buffer *reorder_buf; + u8 baid = data->baid; + + if (WARN_ONCE(baid >= IWL_MAX_BAID, "invalid BAID: %x\n", baid)) + return; + + rcu_read_lock(); + + ba_data = rcu_dereference(mvm->baid_map[baid]); + if (WARN_ON_ONCE(!ba_data)) + goto out; + + sta = rcu_dereference(mvm->fw_id_to_mac_id[ba_data->sta_id]); + if (WARN_ON_ONCE(IS_ERR_OR_NULL(sta))) + goto out; + + reorder_buf = &ba_data->reorder_buf[queue]; + + /* release all frames that are in the reorder buffer to the stack */ + spin_lock_bh(&reorder_buf->lock); + iwl_mvm_release_frames(mvm, sta, NULL, ba_data, reorder_buf, + ieee80211_sn_add(reorder_buf->head_sn, + reorder_buf->buf_size), + 0); + spin_unlock_bh(&reorder_buf->lock); + del_timer_sync(&reorder_buf->reorder_timer); + +out: + rcu_read_unlock(); +} + +static void iwl_mvm_release_frames_from_notif(struct iwl_mvm *mvm, + struct napi_struct *napi, + u8 baid, u16 nssn, int queue, + u32 flags) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_reorder_buffer *reorder_buf; + struct iwl_mvm_baid_data *ba_data; + + IWL_DEBUG_HT(mvm, "Frame release notification for BAID %u, NSSN %d\n", + baid, nssn); + + if (WARN_ON_ONCE(baid == IWL_RX_REORDER_DATA_INVALID_BAID || + baid >= ARRAY_SIZE(mvm->baid_map))) + return; + + rcu_read_lock(); + + ba_data = rcu_dereference(mvm->baid_map[baid]); + if (!ba_data) { + WARN(!(flags & IWL_MVM_RELEASE_FROM_RSS_SYNC), + "BAID %d not found in map\n", baid); + goto out; + } + + sta = rcu_dereference(mvm->fw_id_to_mac_id[ba_data->sta_id]); + if (WARN_ON_ONCE(IS_ERR_OR_NULL(sta))) + goto out; + + reorder_buf = &ba_data->reorder_buf[queue]; + + spin_lock_bh(&reorder_buf->lock); + iwl_mvm_release_frames(mvm, sta, napi, ba_data, + reorder_buf, nssn, flags); + spin_unlock_bh(&reorder_buf->lock); + +out: + rcu_read_unlock(); +} + +static void iwl_mvm_nssn_sync(struct iwl_mvm *mvm, + struct napi_struct *napi, int queue, + const struct iwl_mvm_nssn_sync_data *data) +{ + iwl_mvm_release_frames_from_notif(mvm, napi, data->baid, + data->nssn, queue, + IWL_MVM_RELEASE_FROM_RSS_SYNC); +} + +void iwl_mvm_rx_queue_notif(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_rxq_sync_notification *notif; + struct iwl_mvm_internal_rxq_notif *internal_notif; + u32 len = iwl_rx_packet_payload_len(pkt); + + notif = (void *)pkt->data; + internal_notif = (void *)notif->payload; + + if (WARN_ONCE(len < sizeof(*notif) + sizeof(*internal_notif), + "invalid notification size %d (%d)", + len, (int)(sizeof(*notif) + sizeof(*internal_notif)))) + return; + len -= sizeof(*notif) + sizeof(*internal_notif); + + if (internal_notif->sync && + mvm->queue_sync_cookie != internal_notif->cookie) { + WARN_ONCE(1, "Received expired RX queue sync message\n"); + return; + } + + switch (internal_notif->type) { + case IWL_MVM_RXQ_EMPTY: + WARN_ONCE(len, "invalid empty notification size %d", len); + break; + case IWL_MVM_RXQ_NOTIF_DEL_BA: + if (WARN_ONCE(len != sizeof(struct iwl_mvm_delba_data), + "invalid delba notification size %d (%d)", + len, (int)sizeof(struct iwl_mvm_delba_data))) + break; + iwl_mvm_del_ba(mvm, queue, (void *)internal_notif->data); + break; + case IWL_MVM_RXQ_NSSN_SYNC: + if (WARN_ONCE(len != sizeof(struct iwl_mvm_nssn_sync_data), + "invalid nssn sync notification size %d (%d)", + len, (int)sizeof(struct iwl_mvm_nssn_sync_data))) + break; + iwl_mvm_nssn_sync(mvm, napi, queue, + (void *)internal_notif->data); + break; + default: + WARN_ONCE(1, "Invalid identifier %d", internal_notif->type); + } + + if (internal_notif->sync) { + WARN_ONCE(!test_and_clear_bit(queue, &mvm->queue_sync_state), + "queue sync: queue %d responded a second time!\n", + queue); + if (READ_ONCE(mvm->queue_sync_state) == 0) + wake_up(&mvm->rx_sync_waitq); + } +} + +static void iwl_mvm_oldsn_workaround(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, int tid, + struct iwl_mvm_reorder_buffer *buffer, + u32 reorder, u32 gp2, int queue) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + + if (gp2 != buffer->consec_oldsn_ampdu_gp2) { + /* we have a new (A-)MPDU ... */ + + /* + * reset counter to 0 if we didn't have any oldsn in + * the last A-MPDU (as detected by GP2 being identical) + */ + if (!buffer->consec_oldsn_prev_drop) + buffer->consec_oldsn_drops = 0; + + /* either way, update our tracking state */ + buffer->consec_oldsn_ampdu_gp2 = gp2; + } else if (buffer->consec_oldsn_prev_drop) { + /* + * tracking state didn't change, and we had an old SN + * indication before - do nothing in this case, we + * already noted this one down and are waiting for the + * next A-MPDU (by GP2) + */ + return; + } + + /* return unless this MPDU has old SN */ + if (!(reorder & IWL_RX_MPDU_REORDER_BA_OLD_SN)) + return; + + /* update state */ + buffer->consec_oldsn_prev_drop = 1; + buffer->consec_oldsn_drops++; + + /* if limit is reached, send del BA and reset state */ + if (buffer->consec_oldsn_drops == IWL_MVM_AMPDU_CONSEC_DROPS_DELBA) { + IWL_WARN(mvm, + "reached %d old SN frames from %pM on queue %d, stopping BA session on TID %d\n", + IWL_MVM_AMPDU_CONSEC_DROPS_DELBA, + sta->addr, queue, tid); + ieee80211_stop_rx_ba_session(mvmsta->vif, BIT(tid), sta->addr); + buffer->consec_oldsn_prev_drop = 0; + buffer->consec_oldsn_drops = 0; + } +} + +/* + * Returns true if the MPDU was buffered\dropped, false if it should be passed + * to upper layer. + */ +static bool iwl_mvm_reorder(struct iwl_mvm *mvm, + struct napi_struct *napi, + int queue, + struct ieee80211_sta *sta, + struct sk_buff *skb, + struct iwl_rx_mpdu_desc *desc) +{ + struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); + struct ieee80211_hdr *hdr = iwl_mvm_skb_get_hdr(skb); + struct iwl_mvm_sta *mvm_sta; + struct iwl_mvm_baid_data *baid_data; + struct iwl_mvm_reorder_buffer *buffer; + struct sk_buff *tail; + u32 reorder = le32_to_cpu(desc->reorder_data); + bool amsdu = desc->mac_flags2 & IWL_RX_MPDU_MFLG2_AMSDU; + bool last_subframe = + desc->amsdu_info & IWL_RX_MPDU_AMSDU_LAST_SUBFRAME; + u8 tid = ieee80211_get_tid(hdr); + u8 sub_frame_idx = desc->amsdu_info & + IWL_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK; + struct iwl_mvm_reorder_buf_entry *entries; + int index; + u16 nssn, sn; + u8 baid; + + baid = (reorder & IWL_RX_MPDU_REORDER_BAID_MASK) >> + IWL_RX_MPDU_REORDER_BAID_SHIFT; + + /* + * This also covers the case of receiving a Block Ack Request + * outside a BA session; we'll pass it to mac80211 and that + * then sends a delBA action frame. + * This also covers pure monitor mode, in which case we won't + * have any BA sessions. + */ + if (baid == IWL_RX_REORDER_DATA_INVALID_BAID) + return false; + + /* no sta yet */ + if (WARN_ONCE(IS_ERR_OR_NULL(sta), + "Got valid BAID without a valid station assigned\n")) + return false; + + mvm_sta = iwl_mvm_sta_from_mac80211(sta); + + /* not a data packet or a bar */ + if (!ieee80211_is_back_req(hdr->frame_control) && + (!ieee80211_is_data_qos(hdr->frame_control) || + is_multicast_ether_addr(hdr->addr1))) + return false; + + if (unlikely(!ieee80211_is_data_present(hdr->frame_control))) + return false; + + baid_data = rcu_dereference(mvm->baid_map[baid]); + if (!baid_data) { + IWL_DEBUG_RX(mvm, + "Got valid BAID but no baid allocated, bypass the re-ordering buffer. Baid %d reorder 0x%x\n", + baid, reorder); + return false; + } + + if (WARN(tid != baid_data->tid || mvm_sta->sta_id != baid_data->sta_id, + "baid 0x%x is mapped to sta:%d tid:%d, but was received for sta:%d tid:%d\n", + baid, baid_data->sta_id, baid_data->tid, mvm_sta->sta_id, + tid)) + return false; + + nssn = reorder & IWL_RX_MPDU_REORDER_NSSN_MASK; + sn = (reorder & IWL_RX_MPDU_REORDER_SN_MASK) >> + IWL_RX_MPDU_REORDER_SN_SHIFT; + + buffer = &baid_data->reorder_buf[queue]; + entries = &baid_data->entries[queue * baid_data->entries_per_queue]; + + spin_lock_bh(&buffer->lock); + + if (!buffer->valid) { + if (reorder & IWL_RX_MPDU_REORDER_BA_OLD_SN) { + spin_unlock_bh(&buffer->lock); + return false; + } + buffer->valid = true; + } + + if (ieee80211_is_back_req(hdr->frame_control)) { + iwl_mvm_release_frames(mvm, sta, napi, baid_data, + buffer, nssn, 0); + goto drop; + } + + /* + * If there was a significant jump in the nssn - adjust. + * If the SN is smaller than the NSSN it might need to first go into + * the reorder buffer, in which case we just release up to it and the + * rest of the function will take care of storing it and releasing up to + * the nssn. + * This should not happen. This queue has been lagging and it should + * have been updated by a IWL_MVM_RXQ_NSSN_SYNC notification. Be nice + * and update the other queues. + */ + if (!iwl_mvm_is_sn_less(nssn, buffer->head_sn + buffer->buf_size, + buffer->buf_size) || + !ieee80211_sn_less(sn, buffer->head_sn + buffer->buf_size)) { + u16 min_sn = ieee80211_sn_less(sn, nssn) ? sn : nssn; + + iwl_mvm_release_frames(mvm, sta, napi, baid_data, buffer, + min_sn, IWL_MVM_RELEASE_SEND_RSS_SYNC); + } + + iwl_mvm_oldsn_workaround(mvm, sta, tid, buffer, reorder, + rx_status->device_timestamp, queue); + + /* drop any oudated packets */ + if (ieee80211_sn_less(sn, buffer->head_sn)) + goto drop; + + /* release immediately if allowed by nssn and no stored frames */ + if (!buffer->num_stored && ieee80211_sn_less(sn, nssn)) { + if (iwl_mvm_is_sn_less(buffer->head_sn, nssn, + buffer->buf_size) && + (!amsdu || last_subframe)) { + /* + * If we crossed the 2048 or 0 SN, notify all the + * queues. This is done in order to avoid having a + * head_sn that lags behind for too long. When that + * happens, we can get to a situation where the head_sn + * is within the interval [nssn - buf_size : nssn] + * which will make us think that the nssn is a packet + * that we already freed because of the reordering + * buffer and we will ignore it. So maintain the + * head_sn somewhat updated across all the queues: + * when it crosses 0 and 2048. + */ + if (sn == 2048 || sn == 0) + iwl_mvm_sync_nssn(mvm, baid, sn); + buffer->head_sn = nssn; + } + /* No need to update AMSDU last SN - we are moving the head */ + spin_unlock_bh(&buffer->lock); + return false; + } + + /* + * release immediately if there are no stored frames, and the sn is + * equal to the head. + * This can happen due to reorder timer, where NSSN is behind head_sn. + * When we released everything, and we got the next frame in the + * sequence, according to the NSSN we can't release immediately, + * while technically there is no hole and we can move forward. + */ + if (!buffer->num_stored && sn == buffer->head_sn) { + if (!amsdu || last_subframe) { + if (sn == 2048 || sn == 0) + iwl_mvm_sync_nssn(mvm, baid, sn); + buffer->head_sn = ieee80211_sn_inc(buffer->head_sn); + } + /* No need to update AMSDU last SN - we are moving the head */ + spin_unlock_bh(&buffer->lock); + return false; + } + + index = sn % buffer->buf_size; + + /* + * Check if we already stored this frame + * As AMSDU is either received or not as whole, logic is simple: + * If we have frames in that position in the buffer and the last frame + * originated from AMSDU had a different SN then it is a retransmission. + * If it is the same SN then if the subframe index is incrementing it + * is the same AMSDU - otherwise it is a retransmission. + */ + tail = skb_peek_tail(&entries[index].e.frames); + if (tail && !amsdu) + goto drop; + else if (tail && (sn != buffer->last_amsdu || + buffer->last_sub_index >= sub_frame_idx)) + goto drop; + + /* put in reorder buffer */ + __skb_queue_tail(&entries[index].e.frames, skb); + buffer->num_stored++; + entries[index].e.reorder_time = jiffies; + + if (amsdu) { + buffer->last_amsdu = sn; + buffer->last_sub_index = sub_frame_idx; + } + + /* + * We cannot trust NSSN for AMSDU sub-frames that are not the last. + * The reason is that NSSN advances on the first sub-frame, and may + * cause the reorder buffer to advance before all the sub-frames arrive. + * Example: reorder buffer contains SN 0 & 2, and we receive AMSDU with + * SN 1. NSSN for first sub frame will be 3 with the result of driver + * releasing SN 0,1, 2. When sub-frame 1 arrives - reorder buffer is + * already ahead and it will be dropped. + * If the last sub-frame is not on this queue - we will get frame + * release notification with up to date NSSN. + */ + if (!amsdu || last_subframe) + iwl_mvm_release_frames(mvm, sta, napi, baid_data, + buffer, nssn, + IWL_MVM_RELEASE_SEND_RSS_SYNC); + + spin_unlock_bh(&buffer->lock); + return true; + +drop: + kfree_skb(skb); + spin_unlock_bh(&buffer->lock); + return true; +} + +static void iwl_mvm_agg_rx_received(struct iwl_mvm *mvm, + u32 reorder_data, u8 baid) +{ + unsigned long now = jiffies; + unsigned long timeout; + struct iwl_mvm_baid_data *data; + + rcu_read_lock(); + + data = rcu_dereference(mvm->baid_map[baid]); + if (!data) { + IWL_DEBUG_RX(mvm, + "Got valid BAID but no baid allocated, bypass the re-ordering buffer. Baid %d reorder 0x%x\n", + baid, reorder_data); + goto out; + } + + if (!data->timeout) + goto out; + + timeout = data->timeout; + /* + * Do not update last rx all the time to avoid cache bouncing + * between the rx queues. + * Update it every timeout. Worst case is the session will + * expire after ~ 2 * timeout, which doesn't matter that much. + */ + if (time_before(data->last_rx + TU_TO_JIFFIES(timeout), now)) + /* Update is atomic */ + data->last_rx = now; + +out: + rcu_read_unlock(); +} + +static void iwl_mvm_flip_address(u8 *addr) +{ + int i; + u8 mac_addr[ETH_ALEN]; + + for (i = 0; i < ETH_ALEN; i++) + mac_addr[i] = addr[ETH_ALEN - i - 1]; + ether_addr_copy(addr, mac_addr); +} + +struct iwl_mvm_rx_phy_data { + enum iwl_rx_phy_info_type info_type; + __le32 d0, d1, d2, d3; + __le16 d4; + + u32 rate_n_flags; + u32 gp2_on_air_rise; + u16 phy_info; + u8 energy_a, energy_b; + u8 channel; +}; + +static void iwl_mvm_decode_he_mu_ext(struct iwl_mvm *mvm, + struct iwl_mvm_rx_phy_data *phy_data, + struct ieee80211_radiotap_he_mu *he_mu) +{ + u32 phy_data2 = le32_to_cpu(phy_data->d2); + u32 phy_data3 = le32_to_cpu(phy_data->d3); + u16 phy_data4 = le16_to_cpu(phy_data->d4); + u32 rate_n_flags = phy_data->rate_n_flags; + + if (FIELD_GET(IWL_RX_PHY_DATA4_HE_MU_EXT_CH1_CRC_OK, phy_data4)) { + he_mu->flags1 |= + cpu_to_le16(IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH1_RU_KNOWN | + IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH1_CTR_26T_RU_KNOWN); + + he_mu->flags1 |= + le16_encode_bits(FIELD_GET(IWL_RX_PHY_DATA4_HE_MU_EXT_CH1_CTR_RU, + phy_data4), + IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH1_CTR_26T_RU); + + he_mu->ru_ch1[0] = FIELD_GET(IWL_RX_PHY_DATA2_HE_MU_EXT_CH1_RU0, + phy_data2); + he_mu->ru_ch1[1] = FIELD_GET(IWL_RX_PHY_DATA3_HE_MU_EXT_CH1_RU1, + phy_data3); + he_mu->ru_ch1[2] = FIELD_GET(IWL_RX_PHY_DATA2_HE_MU_EXT_CH1_RU2, + phy_data2); + he_mu->ru_ch1[3] = FIELD_GET(IWL_RX_PHY_DATA3_HE_MU_EXT_CH1_RU3, + phy_data3); + } + + if (FIELD_GET(IWL_RX_PHY_DATA4_HE_MU_EXT_CH2_CRC_OK, phy_data4) && + (rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK_V1) != RATE_MCS_CHAN_WIDTH_20) { + he_mu->flags1 |= + cpu_to_le16(IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH2_RU_KNOWN | + IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH2_CTR_26T_RU_KNOWN); + + he_mu->flags2 |= + le16_encode_bits(FIELD_GET(IWL_RX_PHY_DATA4_HE_MU_EXT_CH2_CTR_RU, + phy_data4), + IEEE80211_RADIOTAP_HE_MU_FLAGS2_CH2_CTR_26T_RU); + + he_mu->ru_ch2[0] = FIELD_GET(IWL_RX_PHY_DATA2_HE_MU_EXT_CH2_RU0, + phy_data2); + he_mu->ru_ch2[1] = FIELD_GET(IWL_RX_PHY_DATA3_HE_MU_EXT_CH2_RU1, + phy_data3); + he_mu->ru_ch2[2] = FIELD_GET(IWL_RX_PHY_DATA2_HE_MU_EXT_CH2_RU2, + phy_data2); + he_mu->ru_ch2[3] = FIELD_GET(IWL_RX_PHY_DATA3_HE_MU_EXT_CH2_RU3, + phy_data3); + } +} + +static void +iwl_mvm_decode_he_phy_ru_alloc(struct iwl_mvm_rx_phy_data *phy_data, + struct ieee80211_radiotap_he *he, + struct ieee80211_radiotap_he_mu *he_mu, + struct ieee80211_rx_status *rx_status) +{ + /* + * Unfortunately, we have to leave the mac80211 data + * incorrect for the case that we receive an HE-MU + * transmission and *don't* have the HE phy data (due + * to the bits being used for TSF). This shouldn't + * happen though as management frames where we need + * the TSF/timers are not be transmitted in HE-MU. + */ + u8 ru = le32_get_bits(phy_data->d1, IWL_RX_PHY_DATA1_HE_RU_ALLOC_MASK); + u32 rate_n_flags = phy_data->rate_n_flags; + u32 he_type = rate_n_flags & RATE_MCS_HE_TYPE_MSK_V1; + u8 offs = 0; + + rx_status->bw = RATE_INFO_BW_HE_RU; + + he->data1 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_BW_RU_ALLOC_KNOWN); + + switch (ru) { + case 0 ... 36: + rx_status->he_ru = NL80211_RATE_INFO_HE_RU_ALLOC_26; + offs = ru; + break; + case 37 ... 52: + rx_status->he_ru = NL80211_RATE_INFO_HE_RU_ALLOC_52; + offs = ru - 37; + break; + case 53 ... 60: + rx_status->he_ru = NL80211_RATE_INFO_HE_RU_ALLOC_106; + offs = ru - 53; + break; + case 61 ... 64: + rx_status->he_ru = NL80211_RATE_INFO_HE_RU_ALLOC_242; + offs = ru - 61; + break; + case 65 ... 66: + rx_status->he_ru = NL80211_RATE_INFO_HE_RU_ALLOC_484; + offs = ru - 65; + break; + case 67: + rx_status->he_ru = NL80211_RATE_INFO_HE_RU_ALLOC_996; + break; + case 68: + rx_status->he_ru = NL80211_RATE_INFO_HE_RU_ALLOC_2x996; + break; + } + he->data2 |= le16_encode_bits(offs, + IEEE80211_RADIOTAP_HE_DATA2_RU_OFFSET); + he->data2 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA2_PRISEC_80_KNOWN | + IEEE80211_RADIOTAP_HE_DATA2_RU_OFFSET_KNOWN); + if (phy_data->d1 & cpu_to_le32(IWL_RX_PHY_DATA1_HE_RU_ALLOC_SEC80)) + he->data2 |= + cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA2_PRISEC_80_SEC); + +#define CHECK_BW(bw) \ + BUILD_BUG_ON(IEEE80211_RADIOTAP_HE_MU_FLAGS2_BW_FROM_SIG_A_BW_ ## bw ## MHZ != \ + RATE_MCS_CHAN_WIDTH_##bw >> RATE_MCS_CHAN_WIDTH_POS); \ + BUILD_BUG_ON(IEEE80211_RADIOTAP_HE_DATA6_TB_PPDU_BW_ ## bw ## MHZ != \ + RATE_MCS_CHAN_WIDTH_##bw >> RATE_MCS_CHAN_WIDTH_POS) + CHECK_BW(20); + CHECK_BW(40); + CHECK_BW(80); + CHECK_BW(160); + + if (he_mu) + he_mu->flags2 |= + le16_encode_bits(FIELD_GET(RATE_MCS_CHAN_WIDTH_MSK_V1, + rate_n_flags), + IEEE80211_RADIOTAP_HE_MU_FLAGS2_BW_FROM_SIG_A_BW); + else if (he_type == RATE_MCS_HE_TYPE_TRIG_V1) + he->data6 |= + cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA6_TB_PPDU_BW_KNOWN) | + le16_encode_bits(FIELD_GET(RATE_MCS_CHAN_WIDTH_MSK_V1, + rate_n_flags), + IEEE80211_RADIOTAP_HE_DATA6_TB_PPDU_BW); +} + +static void iwl_mvm_decode_he_phy_data(struct iwl_mvm *mvm, + struct iwl_mvm_rx_phy_data *phy_data, + struct ieee80211_radiotap_he *he, + struct ieee80211_radiotap_he_mu *he_mu, + struct ieee80211_rx_status *rx_status, + int queue) +{ + switch (phy_data->info_type) { + case IWL_RX_PHY_INFO_TYPE_NONE: + case IWL_RX_PHY_INFO_TYPE_CCK: + case IWL_RX_PHY_INFO_TYPE_OFDM_LGCY: + case IWL_RX_PHY_INFO_TYPE_HT: + case IWL_RX_PHY_INFO_TYPE_VHT_SU: + case IWL_RX_PHY_INFO_TYPE_VHT_MU: + return; + case IWL_RX_PHY_INFO_TYPE_HE_TB_EXT: + he->data1 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE_KNOWN | + IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE2_KNOWN | + IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE3_KNOWN | + IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE4_KNOWN); + he->data4 |= le16_encode_bits(le32_get_bits(phy_data->d2, + IWL_RX_PHY_DATA2_HE_TB_EXT_SPTL_REUSE1), + IEEE80211_RADIOTAP_HE_DATA4_TB_SPTL_REUSE1); + he->data4 |= le16_encode_bits(le32_get_bits(phy_data->d2, + IWL_RX_PHY_DATA2_HE_TB_EXT_SPTL_REUSE2), + IEEE80211_RADIOTAP_HE_DATA4_TB_SPTL_REUSE2); + he->data4 |= le16_encode_bits(le32_get_bits(phy_data->d2, + IWL_RX_PHY_DATA2_HE_TB_EXT_SPTL_REUSE3), + IEEE80211_RADIOTAP_HE_DATA4_TB_SPTL_REUSE3); + he->data4 |= le16_encode_bits(le32_get_bits(phy_data->d2, + IWL_RX_PHY_DATA2_HE_TB_EXT_SPTL_REUSE4), + IEEE80211_RADIOTAP_HE_DATA4_TB_SPTL_REUSE4); + fallthrough; + case IWL_RX_PHY_INFO_TYPE_HE_SU: + case IWL_RX_PHY_INFO_TYPE_HE_MU: + case IWL_RX_PHY_INFO_TYPE_HE_MU_EXT: + case IWL_RX_PHY_INFO_TYPE_HE_TB: + /* HE common */ + he->data1 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_LDPC_XSYMSEG_KNOWN | + IEEE80211_RADIOTAP_HE_DATA1_DOPPLER_KNOWN | + IEEE80211_RADIOTAP_HE_DATA1_BSS_COLOR_KNOWN); + he->data2 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA2_PRE_FEC_PAD_KNOWN | + IEEE80211_RADIOTAP_HE_DATA2_PE_DISAMBIG_KNOWN | + IEEE80211_RADIOTAP_HE_DATA2_TXOP_KNOWN | + IEEE80211_RADIOTAP_HE_DATA2_NUM_LTF_SYMS_KNOWN); + he->data3 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_BSS_COLOR_MASK), + IEEE80211_RADIOTAP_HE_DATA3_BSS_COLOR); + if (phy_data->info_type != IWL_RX_PHY_INFO_TYPE_HE_TB && + phy_data->info_type != IWL_RX_PHY_INFO_TYPE_HE_TB_EXT) { + he->data1 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_UL_DL_KNOWN); + he->data3 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_UPLINK), + IEEE80211_RADIOTAP_HE_DATA3_UL_DL); + } + he->data3 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_LDPC_EXT_SYM), + IEEE80211_RADIOTAP_HE_DATA3_LDPC_XSYMSEG); + he->data5 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_PRE_FEC_PAD_MASK), + IEEE80211_RADIOTAP_HE_DATA5_PRE_FEC_PAD); + he->data5 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_PE_DISAMBIG), + IEEE80211_RADIOTAP_HE_DATA5_PE_DISAMBIG); + he->data5 |= le16_encode_bits(le32_get_bits(phy_data->d1, + IWL_RX_PHY_DATA1_HE_LTF_NUM_MASK), + IEEE80211_RADIOTAP_HE_DATA5_NUM_LTF_SYMS); + he->data6 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_TXOP_DUR_MASK), + IEEE80211_RADIOTAP_HE_DATA6_TXOP); + he->data6 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_DOPPLER), + IEEE80211_RADIOTAP_HE_DATA6_DOPPLER); + break; + } + + switch (phy_data->info_type) { + case IWL_RX_PHY_INFO_TYPE_HE_MU_EXT: + case IWL_RX_PHY_INFO_TYPE_HE_MU: + case IWL_RX_PHY_INFO_TYPE_HE_SU: + he->data1 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE_KNOWN); + he->data4 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_SPATIAL_REUSE_MASK), + IEEE80211_RADIOTAP_HE_DATA4_SU_MU_SPTL_REUSE); + break; + default: + /* nothing here */ + break; + } + + switch (phy_data->info_type) { + case IWL_RX_PHY_INFO_TYPE_HE_MU_EXT: + he_mu->flags1 |= + le16_encode_bits(le16_get_bits(phy_data->d4, + IWL_RX_PHY_DATA4_HE_MU_EXT_SIGB_DCM), + IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_DCM); + he_mu->flags1 |= + le16_encode_bits(le16_get_bits(phy_data->d4, + IWL_RX_PHY_DATA4_HE_MU_EXT_SIGB_MCS_MASK), + IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_MCS); + he_mu->flags2 |= + le16_encode_bits(le16_get_bits(phy_data->d4, + IWL_RX_PHY_DATA4_HE_MU_EXT_PREAMBLE_PUNC_TYPE_MASK), + IEEE80211_RADIOTAP_HE_MU_FLAGS2_PUNC_FROM_SIG_A_BW); + iwl_mvm_decode_he_mu_ext(mvm, phy_data, he_mu); + fallthrough; + case IWL_RX_PHY_INFO_TYPE_HE_MU: + he_mu->flags2 |= + le16_encode_bits(le32_get_bits(phy_data->d1, + IWL_RX_PHY_DATA1_HE_MU_SIBG_SYM_OR_USER_NUM_MASK), + IEEE80211_RADIOTAP_HE_MU_FLAGS2_SIG_B_SYMS_USERS); + he_mu->flags2 |= + le16_encode_bits(le32_get_bits(phy_data->d1, + IWL_RX_PHY_DATA1_HE_MU_SIGB_COMPRESSION), + IEEE80211_RADIOTAP_HE_MU_FLAGS2_SIG_B_COMP); + fallthrough; + case IWL_RX_PHY_INFO_TYPE_HE_TB: + case IWL_RX_PHY_INFO_TYPE_HE_TB_EXT: + iwl_mvm_decode_he_phy_ru_alloc(phy_data, he, he_mu, rx_status); + break; + case IWL_RX_PHY_INFO_TYPE_HE_SU: + he->data1 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_BEAM_CHANGE_KNOWN); + he->data3 |= le16_encode_bits(le32_get_bits(phy_data->d0, + IWL_RX_PHY_DATA0_HE_BEAM_CHNG), + IEEE80211_RADIOTAP_HE_DATA3_BEAM_CHANGE); + break; + default: + /* nothing */ + break; + } +} + +static void iwl_mvm_rx_he(struct iwl_mvm *mvm, struct sk_buff *skb, + struct iwl_mvm_rx_phy_data *phy_data, + int queue) +{ + struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); + struct ieee80211_radiotap_he *he = NULL; + struct ieee80211_radiotap_he_mu *he_mu = NULL; + u32 rate_n_flags = phy_data->rate_n_flags; + u32 he_type = rate_n_flags & RATE_MCS_HE_TYPE_MSK; + u8 ltf; + static const struct ieee80211_radiotap_he known = { + .data1 = cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_DATA_MCS_KNOWN | + IEEE80211_RADIOTAP_HE_DATA1_DATA_DCM_KNOWN | + IEEE80211_RADIOTAP_HE_DATA1_STBC_KNOWN | + IEEE80211_RADIOTAP_HE_DATA1_CODING_KNOWN), + .data2 = cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA2_GI_KNOWN | + IEEE80211_RADIOTAP_HE_DATA2_TXBF_KNOWN), + }; + static const struct ieee80211_radiotap_he_mu mu_known = { + .flags1 = cpu_to_le16(IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_MCS_KNOWN | + IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_DCM_KNOWN | + IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_SYMS_USERS_KNOWN | + IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_COMP_KNOWN), + .flags2 = cpu_to_le16(IEEE80211_RADIOTAP_HE_MU_FLAGS2_PUNC_FROM_SIG_A_BW_KNOWN | + IEEE80211_RADIOTAP_HE_MU_FLAGS2_BW_FROM_SIG_A_BW_KNOWN), + }; + u16 phy_info = phy_data->phy_info; + + he = skb_put_data(skb, &known, sizeof(known)); + rx_status->flag |= RX_FLAG_RADIOTAP_HE; + + if (phy_data->info_type == IWL_RX_PHY_INFO_TYPE_HE_MU || + phy_data->info_type == IWL_RX_PHY_INFO_TYPE_HE_MU_EXT) { + he_mu = skb_put_data(skb, &mu_known, sizeof(mu_known)); + rx_status->flag |= RX_FLAG_RADIOTAP_HE_MU; + } + + /* report the AMPDU-EOF bit on single frames */ + if (!queue && !(phy_info & IWL_RX_MPDU_PHY_AMPDU)) { + rx_status->flag |= RX_FLAG_AMPDU_DETAILS; + rx_status->flag |= RX_FLAG_AMPDU_EOF_BIT_KNOWN; + if (phy_data->d0 & cpu_to_le32(IWL_RX_PHY_DATA0_HE_DELIM_EOF)) + rx_status->flag |= RX_FLAG_AMPDU_EOF_BIT; + } + + if (phy_info & IWL_RX_MPDU_PHY_TSF_OVERLOAD) + iwl_mvm_decode_he_phy_data(mvm, phy_data, he, he_mu, rx_status, + queue); + + /* update aggregation data for monitor sake on default queue */ + if (!queue && (phy_info & IWL_RX_MPDU_PHY_TSF_OVERLOAD) && + (phy_info & IWL_RX_MPDU_PHY_AMPDU)) { + bool toggle_bit = phy_info & IWL_RX_MPDU_PHY_AMPDU_TOGGLE; + + /* toggle is switched whenever new aggregation starts */ + if (toggle_bit != mvm->ampdu_toggle) { + rx_status->flag |= RX_FLAG_AMPDU_EOF_BIT_KNOWN; + if (phy_data->d0 & cpu_to_le32(IWL_RX_PHY_DATA0_HE_DELIM_EOF)) + rx_status->flag |= RX_FLAG_AMPDU_EOF_BIT; + } + } + + if (he_type == RATE_MCS_HE_TYPE_EXT_SU && + rate_n_flags & RATE_MCS_HE_106T_MSK) { + rx_status->bw = RATE_INFO_BW_HE_RU; + rx_status->he_ru = NL80211_RATE_INFO_HE_RU_ALLOC_106; + } + + /* actually data is filled in mac80211 */ + if (he_type == RATE_MCS_HE_TYPE_SU || + he_type == RATE_MCS_HE_TYPE_EXT_SU) + he->data1 |= + cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_BW_RU_ALLOC_KNOWN); + +#define CHECK_TYPE(F) \ + BUILD_BUG_ON(IEEE80211_RADIOTAP_HE_DATA1_FORMAT_ ## F != \ + (RATE_MCS_HE_TYPE_ ## F >> RATE_MCS_HE_TYPE_POS)) + + CHECK_TYPE(SU); + CHECK_TYPE(EXT_SU); + CHECK_TYPE(MU); + CHECK_TYPE(TRIG); + + he->data1 |= cpu_to_le16(he_type >> RATE_MCS_HE_TYPE_POS); + + if (rate_n_flags & RATE_MCS_BF_MSK) + he->data5 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA5_TXBF); + + switch ((rate_n_flags & RATE_MCS_HE_GI_LTF_MSK) >> + RATE_MCS_HE_GI_LTF_POS) { + case 0: + if (he_type == RATE_MCS_HE_TYPE_TRIG) + rx_status->he_gi = NL80211_RATE_INFO_HE_GI_1_6; + else + rx_status->he_gi = NL80211_RATE_INFO_HE_GI_0_8; + if (he_type == RATE_MCS_HE_TYPE_MU) + ltf = IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_4X; + else + ltf = IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_1X; + break; + case 1: + if (he_type == RATE_MCS_HE_TYPE_TRIG) + rx_status->he_gi = NL80211_RATE_INFO_HE_GI_1_6; + else + rx_status->he_gi = NL80211_RATE_INFO_HE_GI_0_8; + ltf = IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_2X; + break; + case 2: + if (he_type == RATE_MCS_HE_TYPE_TRIG) { + rx_status->he_gi = NL80211_RATE_INFO_HE_GI_3_2; + ltf = IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_4X; + } else { + rx_status->he_gi = NL80211_RATE_INFO_HE_GI_1_6; + ltf = IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_2X; + } + break; + case 3: + rx_status->he_gi = NL80211_RATE_INFO_HE_GI_3_2; + ltf = IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_4X; + break; + case 4: + rx_status->he_gi = NL80211_RATE_INFO_HE_GI_0_8; + ltf = IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_4X; + break; + default: + ltf = IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_UNKNOWN; + } + + he->data5 |= le16_encode_bits(ltf, + IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE); +} + +static void iwl_mvm_decode_lsig(struct sk_buff *skb, + struct iwl_mvm_rx_phy_data *phy_data) +{ + struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); + struct ieee80211_radiotap_lsig *lsig; + + switch (phy_data->info_type) { + case IWL_RX_PHY_INFO_TYPE_HT: + case IWL_RX_PHY_INFO_TYPE_VHT_SU: + case IWL_RX_PHY_INFO_TYPE_VHT_MU: + case IWL_RX_PHY_INFO_TYPE_HE_TB_EXT: + case IWL_RX_PHY_INFO_TYPE_HE_SU: + case IWL_RX_PHY_INFO_TYPE_HE_MU: + case IWL_RX_PHY_INFO_TYPE_HE_MU_EXT: + case IWL_RX_PHY_INFO_TYPE_HE_TB: + lsig = skb_put(skb, sizeof(*lsig)); + lsig->data1 = cpu_to_le16(IEEE80211_RADIOTAP_LSIG_DATA1_LENGTH_KNOWN); + lsig->data2 = le16_encode_bits(le32_get_bits(phy_data->d1, + IWL_RX_PHY_DATA1_LSIG_LEN_MASK), + IEEE80211_RADIOTAP_LSIG_DATA2_LENGTH); + rx_status->flag |= RX_FLAG_RADIOTAP_LSIG; + break; + default: + break; + } +} + +static inline u8 iwl_mvm_nl80211_band_from_rx_msdu(u8 phy_band) +{ + switch (phy_band) { + case PHY_BAND_24: + return NL80211_BAND_2GHZ; + case PHY_BAND_5: + return NL80211_BAND_5GHZ; + case PHY_BAND_6: + return NL80211_BAND_6GHZ; + default: + WARN_ONCE(1, "Unsupported phy band (%u)\n", phy_band); + return NL80211_BAND_5GHZ; + } +} + +struct iwl_rx_sta_csa { + bool all_sta_unblocked; + struct ieee80211_vif *vif; +}; + +static void iwl_mvm_rx_get_sta_block_tx(void *data, struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_rx_sta_csa *rx_sta_csa = data; + + if (mvmsta->vif != rx_sta_csa->vif) + return; + + if (mvmsta->disable_tx) + rx_sta_csa->all_sta_unblocked = false; +} + +/* + * Note: requires also rx_status->band to be prefilled, as well + * as phy_data (apart from phy_data->info_type) + */ +static void iwl_mvm_rx_fill_status(struct iwl_mvm *mvm, + struct sk_buff *skb, + struct iwl_mvm_rx_phy_data *phy_data, + int queue) +{ + struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); + u32 rate_n_flags = phy_data->rate_n_flags; + u8 stbc = u32_get_bits(rate_n_flags, RATE_MCS_STBC_MSK); + u32 format = rate_n_flags & RATE_MCS_MOD_TYPE_MSK; + bool is_sgi; + + phy_data->info_type = IWL_RX_PHY_INFO_TYPE_NONE; + + if (phy_data->phy_info & IWL_RX_MPDU_PHY_TSF_OVERLOAD) + phy_data->info_type = + le32_get_bits(phy_data->d1, + IWL_RX_PHY_DATA1_INFO_TYPE_MASK); + + /* This may be overridden by iwl_mvm_rx_he() to HE_RU */ + switch (rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK) { + case RATE_MCS_CHAN_WIDTH_20: + break; + case RATE_MCS_CHAN_WIDTH_40: + rx_status->bw = RATE_INFO_BW_40; + break; + case RATE_MCS_CHAN_WIDTH_80: + rx_status->bw = RATE_INFO_BW_80; + break; + case RATE_MCS_CHAN_WIDTH_160: + rx_status->bw = RATE_INFO_BW_160; + break; + } + + /* must be before L-SIG data */ + if (format == RATE_MCS_HE_MSK) + iwl_mvm_rx_he(mvm, skb, phy_data, queue); + + iwl_mvm_decode_lsig(skb, phy_data); + + rx_status->device_timestamp = phy_data->gp2_on_air_rise; + rx_status->freq = ieee80211_channel_to_frequency(phy_data->channel, + rx_status->band); + iwl_mvm_get_signal_strength(mvm, rx_status, rate_n_flags, + phy_data->energy_a, phy_data->energy_b); + + if (unlikely(mvm->monitor_on)) + iwl_mvm_add_rtap_sniffer_config(mvm, skb); + + is_sgi = format == RATE_MCS_HE_MSK ? + iwl_he_is_sgi(rate_n_flags) : + rate_n_flags & RATE_MCS_SGI_MSK; + + if (!(format == RATE_MCS_CCK_MSK) && is_sgi) + rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI; + + if (rate_n_flags & RATE_MCS_LDPC_MSK) + rx_status->enc_flags |= RX_ENC_FLAG_LDPC; + + switch (format) { + case RATE_MCS_VHT_MSK: + rx_status->encoding = RX_ENC_VHT; + break; + case RATE_MCS_HE_MSK: + rx_status->encoding = RX_ENC_HE; + rx_status->he_dcm = + !!(rate_n_flags & RATE_HE_DUAL_CARRIER_MODE_MSK); + break; + } + + switch (format) { + case RATE_MCS_HT_MSK: + rx_status->encoding = RX_ENC_HT; + rx_status->rate_idx = RATE_HT_MCS_INDEX(rate_n_flags); + rx_status->enc_flags |= stbc << RX_ENC_FLAG_STBC_SHIFT; + break; + case RATE_MCS_VHT_MSK: + case RATE_MCS_HE_MSK: + rx_status->nss = + u32_get_bits(rate_n_flags, RATE_MCS_NSS_MSK) + 1; + rx_status->rate_idx = rate_n_flags & RATE_MCS_CODE_MSK; + rx_status->enc_flags |= stbc << RX_ENC_FLAG_STBC_SHIFT; + break; + default: { + int rate = iwl_mvm_legacy_hw_idx_to_mac80211_idx(rate_n_flags, + rx_status->band); + + rx_status->rate_idx = rate; + + if (WARN_ONCE(rate < 0 || rate > 0xFF, + "Invalid rate flags 0x%x, band %d,\n", + rate_n_flags, rx_status->band)) + rx_status->rate_idx = 0; + break; + } + } +} + +void iwl_mvm_rx_mpdu_mq(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue) +{ + struct ieee80211_rx_status *rx_status; + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_rx_mpdu_desc *desc = (void *)pkt->data; + struct ieee80211_hdr *hdr; + u32 len; + u32 pkt_len = iwl_rx_packet_payload_len(pkt); + struct ieee80211_sta *sta = NULL; + struct sk_buff *skb; + u8 crypt_len = 0; + size_t desc_size; + struct iwl_mvm_rx_phy_data phy_data = {}; + u32 format; + + if (unlikely(test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status))) + return; + + if (mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + desc_size = sizeof(*desc); + else + desc_size = IWL_RX_DESC_SIZE_V1; + + if (unlikely(pkt_len < desc_size)) { + IWL_DEBUG_DROP(mvm, "Bad REPLY_RX_MPDU_CMD size\n"); + return; + } + + if (mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + phy_data.rate_n_flags = le32_to_cpu(desc->v3.rate_n_flags); + phy_data.channel = desc->v3.channel; + phy_data.gp2_on_air_rise = le32_to_cpu(desc->v3.gp2_on_air_rise); + phy_data.energy_a = desc->v3.energy_a; + phy_data.energy_b = desc->v3.energy_b; + + phy_data.d0 = desc->v3.phy_data0; + phy_data.d1 = desc->v3.phy_data1; + phy_data.d2 = desc->v3.phy_data2; + phy_data.d3 = desc->v3.phy_data3; + } else { + phy_data.rate_n_flags = le32_to_cpu(desc->v1.rate_n_flags); + phy_data.channel = desc->v1.channel; + phy_data.gp2_on_air_rise = le32_to_cpu(desc->v1.gp2_on_air_rise); + phy_data.energy_a = desc->v1.energy_a; + phy_data.energy_b = desc->v1.energy_b; + + phy_data.d0 = desc->v1.phy_data0; + phy_data.d1 = desc->v1.phy_data1; + phy_data.d2 = desc->v1.phy_data2; + phy_data.d3 = desc->v1.phy_data3; + } + + if (iwl_fw_lookup_notif_ver(mvm->fw, LEGACY_GROUP, + REPLY_RX_MPDU_CMD, 0) < 4) { + phy_data.rate_n_flags = iwl_new_rate_from_v1(phy_data.rate_n_flags); + IWL_DEBUG_DROP(mvm, "Got old format rate, converting. New rate: 0x%x\n", + phy_data.rate_n_flags); + } + + format = phy_data.rate_n_flags & RATE_MCS_MOD_TYPE_MSK; + + len = le16_to_cpu(desc->mpdu_len); + + if (unlikely(len + desc_size > pkt_len)) { + IWL_DEBUG_DROP(mvm, "FW lied about packet len\n"); + return; + } + + phy_data.phy_info = le16_to_cpu(desc->phy_info); + phy_data.d4 = desc->phy_data4; + + hdr = (void *)(pkt->data + desc_size); + /* Dont use dev_alloc_skb(), we'll have enough headroom once + * ieee80211_hdr pulled. + */ + skb = alloc_skb(128, GFP_ATOMIC); + if (!skb) { + IWL_ERR(mvm, "alloc_skb failed\n"); + return; + } + + if (desc->mac_flags2 & IWL_RX_MPDU_MFLG2_PAD) { + /* + * If the device inserted padding it means that (it thought) + * the 802.11 header wasn't a multiple of 4 bytes long. In + * this case, reserve two bytes at the start of the SKB to + * align the payload properly in case we end up copying it. + */ + skb_reserve(skb, 2); + } + + rx_status = IEEE80211_SKB_RXCB(skb); + + /* + * Keep packets with CRC errors (and with overrun) for monitor mode + * (otherwise the firmware discards them) but mark them as bad. + */ + if (!(desc->status & cpu_to_le32(IWL_RX_MPDU_STATUS_CRC_OK)) || + !(desc->status & cpu_to_le32(IWL_RX_MPDU_STATUS_OVERRUN_OK))) { + IWL_DEBUG_RX(mvm, "Bad CRC or FIFO: 0x%08X.\n", + le32_to_cpu(desc->status)); + rx_status->flag |= RX_FLAG_FAILED_FCS_CRC; + } + + /* set the preamble flag if appropriate */ + if (format == RATE_MCS_CCK_MSK && + phy_data.phy_info & IWL_RX_MPDU_PHY_SHORT_PREAMBLE) + rx_status->enc_flags |= RX_ENC_FLAG_SHORTPRE; + + if (likely(!(phy_data.phy_info & IWL_RX_MPDU_PHY_TSF_OVERLOAD))) { + u64 tsf_on_air_rise; + + if (mvm->trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_AX210) + tsf_on_air_rise = le64_to_cpu(desc->v3.tsf_on_air_rise); + else + tsf_on_air_rise = le64_to_cpu(desc->v1.tsf_on_air_rise); + + rx_status->mactime = tsf_on_air_rise; + /* TSF as indicated by the firmware is at INA time */ + rx_status->flag |= RX_FLAG_MACTIME_PLCP_START; + } + + if (iwl_mvm_is_band_in_rx_supported(mvm)) { + u8 band = BAND_IN_RX_STATUS(desc->mac_phy_idx); + + rx_status->band = iwl_mvm_nl80211_band_from_rx_msdu(band); + } else { + rx_status->band = phy_data.channel > 14 ? NL80211_BAND_5GHZ : + NL80211_BAND_2GHZ; + } + + /* update aggregation data for monitor sake on default queue */ + if (!queue && (phy_data.phy_info & IWL_RX_MPDU_PHY_AMPDU)) { + bool toggle_bit; + + toggle_bit = phy_data.phy_info & IWL_RX_MPDU_PHY_AMPDU_TOGGLE; + rx_status->flag |= RX_FLAG_AMPDU_DETAILS; + /* + * Toggle is switched whenever new aggregation starts. Make + * sure ampdu_reference is never 0 so we can later use it to + * see if the frame was really part of an A-MPDU or not. + */ + if (toggle_bit != mvm->ampdu_toggle) { + mvm->ampdu_ref++; + if (mvm->ampdu_ref == 0) + mvm->ampdu_ref++; + mvm->ampdu_toggle = toggle_bit; + } + rx_status->ampdu_reference = mvm->ampdu_ref; + } + + rcu_read_lock(); + + if (desc->status & cpu_to_le32(IWL_RX_MPDU_STATUS_SRC_STA_FOUND)) { + u8 id = le32_get_bits(desc->status, IWL_RX_MPDU_STATUS_STA_ID); + + if (!WARN_ON_ONCE(id >= mvm->fw->ucode_capa.num_stations)) { + sta = rcu_dereference(mvm->fw_id_to_mac_id[id]); + if (IS_ERR(sta)) + sta = NULL; + } + } else if (!is_multicast_ether_addr(hdr->addr2)) { + /* + * This is fine since we prevent two stations with the same + * address from being added. + */ + sta = ieee80211_find_sta_by_ifaddr(mvm->hw, hdr->addr2, NULL); + } + + if (iwl_mvm_rx_crypto(mvm, sta, hdr, rx_status, phy_data.phy_info, desc, + le32_to_cpu(pkt->len_n_flags), queue, + &crypt_len)) { + kfree_skb(skb); + goto out; + } + + iwl_mvm_rx_fill_status(mvm, skb, &phy_data, queue); + + if (sta) { + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct ieee80211_vif *tx_blocked_vif = + rcu_dereference(mvm->csa_tx_blocked_vif); + u8 baid = (u8)((le32_to_cpu(desc->reorder_data) & + IWL_RX_MPDU_REORDER_BAID_MASK) >> + IWL_RX_MPDU_REORDER_BAID_SHIFT); + struct iwl_fw_dbg_trigger_tlv *trig; + struct ieee80211_vif *vif = mvmsta->vif; + + if (!mvm->tcm.paused && len >= sizeof(*hdr) && + !is_multicast_ether_addr(hdr->addr1) && + ieee80211_is_data(hdr->frame_control) && + time_after(jiffies, mvm->tcm.ts + MVM_TCM_PERIOD)) + schedule_delayed_work(&mvm->tcm.work, 0); + + /* + * We have tx blocked stations (with CS bit). If we heard + * frames from a blocked station on a new channel we can + * TX to it again. + */ + if (unlikely(tx_blocked_vif) && tx_blocked_vif == vif) { + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(tx_blocked_vif); + struct iwl_rx_sta_csa rx_sta_csa = { + .all_sta_unblocked = true, + .vif = tx_blocked_vif, + }; + + if (mvmvif->csa_target_freq == rx_status->freq) + iwl_mvm_sta_modify_disable_tx_ap(mvm, sta, + false); + ieee80211_iterate_stations_atomic(mvm->hw, + iwl_mvm_rx_get_sta_block_tx, + &rx_sta_csa); + + if (rx_sta_csa.all_sta_unblocked) { + RCU_INIT_POINTER(mvm->csa_tx_blocked_vif, NULL); + /* Unblock BCAST / MCAST station */ + iwl_mvm_modify_all_sta_disable_tx(mvm, mvmvif, false); + cancel_delayed_work(&mvm->cs_tx_unblock_dwork); + } + } + + rs_update_last_rssi(mvm, mvmsta, rx_status); + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, + ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_RSSI); + + if (trig && ieee80211_is_beacon(hdr->frame_control)) { + struct iwl_fw_dbg_trigger_low_rssi *rssi_trig; + s32 rssi; + + rssi_trig = (void *)trig->data; + rssi = le32_to_cpu(rssi_trig->rssi); + + if (rx_status->signal < rssi) + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + NULL); + } + + if (ieee80211_is_data(hdr->frame_control)) + iwl_mvm_rx_csum(mvm, sta, skb, pkt); + + if (iwl_mvm_is_dup(sta, queue, rx_status, hdr, desc)) { + kfree_skb(skb); + goto out; + } + + /* + * Our hardware de-aggregates AMSDUs but copies the mac header + * as it to the de-aggregated MPDUs. We need to turn off the + * AMSDU bit in the QoS control ourselves. + * In addition, HW reverses addr3 and addr4 - reverse it back. + */ + if ((desc->mac_flags2 & IWL_RX_MPDU_MFLG2_AMSDU) && + !WARN_ON(!ieee80211_is_data_qos(hdr->frame_control))) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + + *qc &= ~IEEE80211_QOS_CTL_A_MSDU_PRESENT; + + if (mvm->trans->trans_cfg->device_family == + IWL_DEVICE_FAMILY_9000) { + iwl_mvm_flip_address(hdr->addr3); + + if (ieee80211_has_a4(hdr->frame_control)) + iwl_mvm_flip_address(hdr->addr4); + } + } + if (baid != IWL_RX_REORDER_DATA_INVALID_BAID) { + u32 reorder_data = le32_to_cpu(desc->reorder_data); + + iwl_mvm_agg_rx_received(mvm, reorder_data, baid); + } + } + + /* management stuff on default queue */ + if (!queue) { + if (unlikely((ieee80211_is_beacon(hdr->frame_control) || + ieee80211_is_probe_resp(hdr->frame_control)) && + mvm->sched_scan_pass_all == + SCHED_SCAN_PASS_ALL_ENABLED)) + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_FOUND; + + if (unlikely(ieee80211_is_beacon(hdr->frame_control) || + ieee80211_is_probe_resp(hdr->frame_control))) + rx_status->boottime_ns = ktime_get_boottime_ns(); + } + + if (iwl_mvm_create_skb(mvm, skb, hdr, len, crypt_len, rxb)) { + kfree_skb(skb); + goto out; + } + + if (!iwl_mvm_reorder(mvm, napi, queue, sta, skb, desc)) + iwl_mvm_pass_packet_to_mac80211(mvm, napi, skb, queue, + sta); +out: + rcu_read_unlock(); +} + +void iwl_mvm_rx_monitor_no_data(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue) +{ + struct ieee80211_rx_status *rx_status; + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_rx_no_data *desc = (void *)pkt->data; + u32 rssi = le32_to_cpu(desc->rssi); + u32 info_type = le32_to_cpu(desc->info) & RX_NO_DATA_INFO_TYPE_MSK; + struct ieee80211_sta *sta = NULL; + struct sk_buff *skb; + struct iwl_mvm_rx_phy_data phy_data = { + .d0 = desc->phy_info[0], + .d1 = desc->phy_info[1], + .phy_info = IWL_RX_MPDU_PHY_TSF_OVERLOAD, + .gp2_on_air_rise = le32_to_cpu(desc->on_air_rise_time), + .rate_n_flags = le32_to_cpu(desc->rate), + .energy_a = u32_get_bits(rssi, RX_NO_DATA_CHAIN_A_MSK), + .energy_b = u32_get_bits(rssi, RX_NO_DATA_CHAIN_B_MSK), + .channel = u32_get_bits(rssi, RX_NO_DATA_CHANNEL_MSK), + }; + u32 format; + + if (iwl_fw_lookup_notif_ver(mvm->fw, DATA_PATH_GROUP, + RX_NO_DATA_NOTIF, 0) < 2) { + IWL_DEBUG_DROP(mvm, "Got an old rate format. Old rate: 0x%x\n", + phy_data.rate_n_flags); + phy_data.rate_n_flags = iwl_new_rate_from_v1(phy_data.rate_n_flags); + IWL_DEBUG_DROP(mvm, " Rate after conversion to the new format: 0x%x\n", + phy_data.rate_n_flags); + } + + format = phy_data.rate_n_flags & RATE_MCS_MOD_TYPE_MSK; + + if (unlikely(iwl_rx_packet_payload_len(pkt) < sizeof(*desc))) + return; + + if (unlikely(test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status))) + return; + + /* Dont use dev_alloc_skb(), we'll have enough headroom once + * ieee80211_hdr pulled. + */ + skb = alloc_skb(128, GFP_ATOMIC); + if (!skb) { + IWL_ERR(mvm, "alloc_skb failed\n"); + return; + } + + rx_status = IEEE80211_SKB_RXCB(skb); + + /* 0-length PSDU */ + rx_status->flag |= RX_FLAG_NO_PSDU; + + switch (info_type) { + case RX_NO_DATA_INFO_TYPE_NDP: + rx_status->zero_length_psdu_type = + IEEE80211_RADIOTAP_ZERO_LEN_PSDU_SOUNDING; + break; + case RX_NO_DATA_INFO_TYPE_MU_UNMATCHED: + case RX_NO_DATA_INFO_TYPE_HE_TB_UNMATCHED: + rx_status->zero_length_psdu_type = + IEEE80211_RADIOTAP_ZERO_LEN_PSDU_NOT_CAPTURED; + break; + default: + rx_status->zero_length_psdu_type = + IEEE80211_RADIOTAP_ZERO_LEN_PSDU_VENDOR; + break; + } + + rx_status->band = phy_data.channel > 14 ? NL80211_BAND_5GHZ : + NL80211_BAND_2GHZ; + + iwl_mvm_rx_fill_status(mvm, skb, &phy_data, queue); + + /* + * Override the nss from the rx_vec since the rate_n_flags has + * only 2 bits for the nss which gives a max of 4 ss but there + * may be up to 8 spatial streams. + */ + switch (format) { + case RATE_MCS_VHT_MSK: + rx_status->nss = + le32_get_bits(desc->rx_vec[0], + RX_NO_DATA_RX_VEC0_VHT_NSTS_MSK) + 1; + break; + case RATE_MCS_HE_MSK: + rx_status->nss = + le32_get_bits(desc->rx_vec[0], + RX_NO_DATA_RX_VEC0_HE_NSTS_MSK) + 1; + break; + } + + rcu_read_lock(); + ieee80211_rx_napi(mvm->hw, sta, skb, napi); + rcu_read_unlock(); +} + +void iwl_mvm_rx_frame_release(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_frame_release *release = (void *)pkt->data; + + if (unlikely(iwl_rx_packet_payload_len(pkt) < sizeof(*release))) + return; + + iwl_mvm_release_frames_from_notif(mvm, napi, release->baid, + le16_to_cpu(release->nssn), + queue, 0); +} + +void iwl_mvm_rx_bar_frame_release(struct iwl_mvm *mvm, struct napi_struct *napi, + struct iwl_rx_cmd_buffer *rxb, int queue) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_bar_frame_release *release = (void *)pkt->data; + unsigned int baid = le32_get_bits(release->ba_info, + IWL_BAR_FRAME_RELEASE_BAID_MASK); + unsigned int nssn = le32_get_bits(release->ba_info, + IWL_BAR_FRAME_RELEASE_NSSN_MASK); + unsigned int sta_id = le32_get_bits(release->sta_tid, + IWL_BAR_FRAME_RELEASE_STA_MASK); + unsigned int tid = le32_get_bits(release->sta_tid, + IWL_BAR_FRAME_RELEASE_TID_MASK); + struct iwl_mvm_baid_data *baid_data; + + if (unlikely(iwl_rx_packet_payload_len(pkt) < sizeof(*release))) + return; + + if (WARN_ON_ONCE(baid == IWL_RX_REORDER_DATA_INVALID_BAID || + baid >= ARRAY_SIZE(mvm->baid_map))) + return; + + rcu_read_lock(); + baid_data = rcu_dereference(mvm->baid_map[baid]); + if (!baid_data) { + IWL_DEBUG_RX(mvm, + "Got valid BAID %d but not allocated, invalid BAR release!\n", + baid); + goto out; + } + + if (WARN(tid != baid_data->tid || sta_id != baid_data->sta_id, + "baid 0x%x is mapped to sta:%d tid:%d, but BAR release received for sta:%d tid:%d\n", + baid, baid_data->sta_id, baid_data->tid, sta_id, + tid)) + goto out; + + iwl_mvm_release_frames_from_notif(mvm, napi, baid, nssn, queue, 0); +out: + rcu_read_unlock(); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/scan.c b/drivers/net/wireless/intel/iwlwifi/mvm/scan.c new file mode 100644 index 000000000..acd8803db --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/scan.c @@ -0,0 +1,3255 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include <linux/etherdevice.h> +#include <net/mac80211.h> +#include <linux/crc32.h> + +#include "mvm.h" +#include "fw/api/scan.h" +#include "iwl-io.h" + +#define IWL_DENSE_EBS_SCAN_RATIO 5 +#define IWL_SPARSE_EBS_SCAN_RATIO 1 + +#define IWL_SCAN_DWELL_ACTIVE 10 +#define IWL_SCAN_DWELL_PASSIVE 110 +#define IWL_SCAN_DWELL_FRAGMENTED 44 +#define IWL_SCAN_DWELL_EXTENDED 90 +#define IWL_SCAN_NUM_OF_FRAGS 3 + +/* adaptive dwell max budget time [TU] for full scan */ +#define IWL_SCAN_ADWELL_MAX_BUDGET_FULL_SCAN 300 +/* adaptive dwell max budget time [TU] for directed scan */ +#define IWL_SCAN_ADWELL_MAX_BUDGET_DIRECTED_SCAN 100 +/* adaptive dwell default high band APs number */ +#define IWL_SCAN_ADWELL_DEFAULT_HB_N_APS 8 +/* adaptive dwell default low band APs number */ +#define IWL_SCAN_ADWELL_DEFAULT_LB_N_APS 2 +/* adaptive dwell default APs number in social channels (1, 6, 11) */ +#define IWL_SCAN_ADWELL_DEFAULT_N_APS_SOCIAL 10 +/* number of scan channels */ +#define IWL_SCAN_NUM_CHANNELS 112 +/* adaptive dwell number of APs override mask for p2p friendly GO */ +#define IWL_SCAN_ADWELL_N_APS_GO_FRIENDLY_BIT BIT(20) +/* adaptive dwell number of APs override mask for social channels */ +#define IWL_SCAN_ADWELL_N_APS_SOCIAL_CHS_BIT BIT(21) +/* adaptive dwell number of APs override for p2p friendly GO channels */ +#define IWL_SCAN_ADWELL_N_APS_GO_FRIENDLY 10 +/* adaptive dwell number of APs override for social channels */ +#define IWL_SCAN_ADWELL_N_APS_SOCIAL_CHS 2 + +/* minimal number of 2GHz and 5GHz channels in the regular scan request */ +#define IWL_MVM_6GHZ_PASSIVE_SCAN_MIN_CHANS 4 + +struct iwl_mvm_scan_timing_params { + u32 suspend_time; + u32 max_out_time; +}; + +static struct iwl_mvm_scan_timing_params scan_timing[] = { + [IWL_SCAN_TYPE_UNASSOC] = { + .suspend_time = 0, + .max_out_time = 0, + }, + [IWL_SCAN_TYPE_WILD] = { + .suspend_time = 30, + .max_out_time = 120, + }, + [IWL_SCAN_TYPE_MILD] = { + .suspend_time = 120, + .max_out_time = 120, + }, + [IWL_SCAN_TYPE_FRAGMENTED] = { + .suspend_time = 95, + .max_out_time = 44, + }, + [IWL_SCAN_TYPE_FAST_BALANCE] = { + .suspend_time = 30, + .max_out_time = 37, + }, +}; + +struct iwl_mvm_scan_params { + /* For CDB this is low band scan type, for non-CDB - type. */ + enum iwl_mvm_scan_type type; + enum iwl_mvm_scan_type hb_type; + u32 n_channels; + u16 delay; + int n_ssids; + struct cfg80211_ssid *ssids; + struct ieee80211_channel **channels; + u32 flags; + u8 *mac_addr; + u8 *mac_addr_mask; + bool no_cck; + bool pass_all; + int n_match_sets; + struct iwl_scan_probe_req preq; + struct cfg80211_match_set *match_sets; + int n_scan_plans; + struct cfg80211_sched_scan_plan *scan_plans; + bool iter_notif; + struct cfg80211_scan_6ghz_params *scan_6ghz_params; + u32 n_6ghz_params; + bool scan_6ghz; + bool enable_6ghz_passive; + bool respect_p2p_go, respect_p2p_go_hb; +}; + +static inline void *iwl_mvm_get_scan_req_umac_data(struct iwl_mvm *mvm) +{ + struct iwl_scan_req_umac *cmd = mvm->scan_cmd; + + if (iwl_mvm_is_adaptive_dwell_v2_supported(mvm)) + return (void *)&cmd->v8.data; + + if (iwl_mvm_is_adaptive_dwell_supported(mvm)) + return (void *)&cmd->v7.data; + + if (iwl_mvm_cdb_scan_api(mvm)) + return (void *)&cmd->v6.data; + + return (void *)&cmd->v1.data; +} + +static inline struct iwl_scan_umac_chan_param * +iwl_mvm_get_scan_req_umac_channel(struct iwl_mvm *mvm) +{ + struct iwl_scan_req_umac *cmd = mvm->scan_cmd; + + if (iwl_mvm_is_adaptive_dwell_v2_supported(mvm)) + return &cmd->v8.channel; + + if (iwl_mvm_is_adaptive_dwell_supported(mvm)) + return &cmd->v7.channel; + + if (iwl_mvm_cdb_scan_api(mvm)) + return &cmd->v6.channel; + + return &cmd->v1.channel; +} + +static u8 iwl_mvm_scan_rx_ant(struct iwl_mvm *mvm) +{ + if (mvm->scan_rx_ant != ANT_NONE) + return mvm->scan_rx_ant; + return iwl_mvm_get_valid_rx_ant(mvm); +} + +static inline __le16 iwl_mvm_scan_rx_chain(struct iwl_mvm *mvm) +{ + u16 rx_chain; + u8 rx_ant; + + rx_ant = iwl_mvm_scan_rx_ant(mvm); + rx_chain = rx_ant << PHY_RX_CHAIN_VALID_POS; + rx_chain |= rx_ant << PHY_RX_CHAIN_FORCE_MIMO_SEL_POS; + rx_chain |= rx_ant << PHY_RX_CHAIN_FORCE_SEL_POS; + rx_chain |= 0x1 << PHY_RX_CHAIN_DRIVER_FORCE_POS; + return cpu_to_le16(rx_chain); +} + +static inline __le32 +iwl_mvm_scan_rate_n_flags(struct iwl_mvm *mvm, enum nl80211_band band, + bool no_cck) +{ + u32 tx_ant; + + iwl_mvm_toggle_tx_ant(mvm, &mvm->scan_last_antenna_idx); + tx_ant = BIT(mvm->scan_last_antenna_idx) << RATE_MCS_ANT_POS; + + if (band == NL80211_BAND_2GHZ && !no_cck) + return cpu_to_le32(IWL_RATE_1M_PLCP | RATE_MCS_CCK_MSK_V1 | + tx_ant); + else + return cpu_to_le32(IWL_RATE_6M_PLCP | tx_ant); +} + +static enum iwl_mvm_traffic_load iwl_mvm_get_traffic_load(struct iwl_mvm *mvm) +{ + return mvm->tcm.result.global_load; +} + +static enum iwl_mvm_traffic_load +iwl_mvm_get_traffic_load_band(struct iwl_mvm *mvm, enum nl80211_band band) +{ + return mvm->tcm.result.band_load[band]; +} + +struct iwl_mvm_scan_iter_data { + u32 global_cnt; + struct ieee80211_vif *current_vif; + bool is_dcm_with_p2p_go; +}; + +static void iwl_mvm_scan_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_scan_iter_data *data = _data; + struct iwl_mvm_vif *curr_mvmvif; + + if (vif->type != NL80211_IFTYPE_P2P_DEVICE && mvmvif->phy_ctxt && + mvmvif->phy_ctxt->id < NUM_PHY_CTX) + data->global_cnt += 1; + + if (!data->current_vif || vif == data->current_vif) + return; + + curr_mvmvif = iwl_mvm_vif_from_mac80211(data->current_vif); + + if (vif->type == NL80211_IFTYPE_AP && vif->p2p && + mvmvif->phy_ctxt && curr_mvmvif->phy_ctxt && + mvmvif->phy_ctxt->id != curr_mvmvif->phy_ctxt->id) + data->is_dcm_with_p2p_go = true; +} + +static enum +iwl_mvm_scan_type _iwl_mvm_get_scan_type(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + enum iwl_mvm_traffic_load load, + bool low_latency) +{ + struct iwl_mvm_scan_iter_data data = { + .current_vif = vif, + .is_dcm_with_p2p_go = false, + .global_cnt = 0, + }; + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_scan_iterator, + &data); + + if (!data.global_cnt) + return IWL_SCAN_TYPE_UNASSOC; + + if (fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_FRAGMENTED_SCAN)) { + if ((load == IWL_MVM_TRAFFIC_HIGH || low_latency) && + (!vif || vif->type != NL80211_IFTYPE_P2P_DEVICE)) + return IWL_SCAN_TYPE_FRAGMENTED; + + /* + * in case of DCM with GO where BSS DTIM interval < 220msec + * set all scan requests as fast-balance scan + */ + if (vif && vif->type == NL80211_IFTYPE_STATION && + vif->bss_conf.dtim_period < 220 && + data.is_dcm_with_p2p_go) + return IWL_SCAN_TYPE_FAST_BALANCE; + } + + if (load >= IWL_MVM_TRAFFIC_MEDIUM || low_latency) + return IWL_SCAN_TYPE_MILD; + + return IWL_SCAN_TYPE_WILD; +} + +static enum +iwl_mvm_scan_type iwl_mvm_get_scan_type(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + enum iwl_mvm_traffic_load load; + bool low_latency; + + load = iwl_mvm_get_traffic_load(mvm); + low_latency = iwl_mvm_low_latency(mvm); + + return _iwl_mvm_get_scan_type(mvm, vif, load, low_latency); +} + +static enum +iwl_mvm_scan_type iwl_mvm_get_scan_type_band(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + enum nl80211_band band) +{ + enum iwl_mvm_traffic_load load; + bool low_latency; + + load = iwl_mvm_get_traffic_load_band(mvm, band); + low_latency = iwl_mvm_low_latency_band(mvm, band); + + return _iwl_mvm_get_scan_type(mvm, vif, load, low_latency); +} + +static inline bool iwl_mvm_rrm_scan_needed(struct iwl_mvm *mvm) +{ + /* require rrm scan whenever the fw supports it */ + return fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT); +} + +static int iwl_mvm_max_scan_ie_fw_cmd_room(struct iwl_mvm *mvm) +{ + int max_probe_len; + + max_probe_len = SCAN_OFFLOAD_PROBE_REQ_SIZE; + + /* we create the 802.11 header and SSID element */ + max_probe_len -= 24 + 2; + + /* DS parameter set element is added on 2.4GHZ band if required */ + if (iwl_mvm_rrm_scan_needed(mvm)) + max_probe_len -= 3; + + return max_probe_len; +} + +int iwl_mvm_max_scan_ie_len(struct iwl_mvm *mvm) +{ + int max_ie_len = iwl_mvm_max_scan_ie_fw_cmd_room(mvm); + + /* TODO: [BUG] This function should return the maximum allowed size of + * scan IEs, however the LMAC scan api contains both 2GHZ and 5GHZ IEs + * in the same command. So the correct implementation of this function + * is just iwl_mvm_max_scan_ie_fw_cmd_room() / 2. Currently the scan + * command has only 512 bytes and it would leave us with about 240 + * bytes for scan IEs, which is clearly not enough. So meanwhile + * we will report an incorrect value. This may result in a failure to + * issue a scan in unified_scan_lmac and unified_sched_scan_lmac + * functions with -ENOBUFS, if a large enough probe will be provided. + */ + return max_ie_len; +} + +void iwl_mvm_rx_lmac_scan_iter_complete_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_lmac_scan_complete_notif *notif = (void *)pkt->data; + + IWL_DEBUG_SCAN(mvm, + "Scan offload iteration complete: status=0x%x scanned channels=%d\n", + notif->status, notif->scanned_channels); + + if (mvm->sched_scan_pass_all == SCHED_SCAN_PASS_ALL_FOUND) { + IWL_DEBUG_SCAN(mvm, "Pass all scheduled scan results found\n"); + ieee80211_sched_scan_results(mvm->hw); + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_ENABLED; + } +} + +void iwl_mvm_rx_scan_match_found(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + IWL_DEBUG_SCAN(mvm, "Scheduled scan results\n"); + ieee80211_sched_scan_results(mvm->hw); +} + +static const char *iwl_mvm_ebs_status_str(enum iwl_scan_ebs_status status) +{ + switch (status) { + case IWL_SCAN_EBS_SUCCESS: + return "successful"; + case IWL_SCAN_EBS_INACTIVE: + return "inactive"; + case IWL_SCAN_EBS_FAILED: + case IWL_SCAN_EBS_CHAN_NOT_FOUND: + default: + return "failed"; + } +} + +void iwl_mvm_rx_lmac_scan_complete_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_periodic_scan_complete *scan_notif = (void *)pkt->data; + bool aborted = (scan_notif->status == IWL_SCAN_OFFLOAD_ABORTED); + + /* If this happens, the firmware has mistakenly sent an LMAC + * notification during UMAC scans -- warn and ignore it. + */ + if (WARN_ON_ONCE(fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_UMAC_SCAN))) + return; + + /* scan status must be locked for proper checking */ + lockdep_assert_held(&mvm->mutex); + + /* We first check if we were stopping a scan, in which case we + * just clear the stopping flag. Then we check if it was a + * firmware initiated stop, in which case we need to inform + * mac80211. + * Note that we can have a stopping and a running scan + * simultaneously, but we can't have two different types of + * scans stopping or running at the same time (since LMAC + * doesn't support it). + */ + + if (mvm->scan_status & IWL_MVM_SCAN_STOPPING_SCHED) { + WARN_ON_ONCE(mvm->scan_status & IWL_MVM_SCAN_STOPPING_REGULAR); + + IWL_DEBUG_SCAN(mvm, "Scheduled scan %s, EBS status %s\n", + aborted ? "aborted" : "completed", + iwl_mvm_ebs_status_str(scan_notif->ebs_status)); + IWL_DEBUG_SCAN(mvm, + "Last line %d, Last iteration %d, Time after last iteration %d\n", + scan_notif->last_schedule_line, + scan_notif->last_schedule_iteration, + __le32_to_cpu(scan_notif->time_after_last_iter)); + + mvm->scan_status &= ~IWL_MVM_SCAN_STOPPING_SCHED; + } else if (mvm->scan_status & IWL_MVM_SCAN_STOPPING_REGULAR) { + IWL_DEBUG_SCAN(mvm, "Regular scan %s, EBS status %s\n", + aborted ? "aborted" : "completed", + iwl_mvm_ebs_status_str(scan_notif->ebs_status)); + + mvm->scan_status &= ~IWL_MVM_SCAN_STOPPING_REGULAR; + } else if (mvm->scan_status & IWL_MVM_SCAN_SCHED) { + WARN_ON_ONCE(mvm->scan_status & IWL_MVM_SCAN_REGULAR); + + IWL_DEBUG_SCAN(mvm, "Scheduled scan %s, EBS status %s\n", + aborted ? "aborted" : "completed", + iwl_mvm_ebs_status_str(scan_notif->ebs_status)); + IWL_DEBUG_SCAN(mvm, + "Last line %d, Last iteration %d, Time after last iteration %d (FW)\n", + scan_notif->last_schedule_line, + scan_notif->last_schedule_iteration, + __le32_to_cpu(scan_notif->time_after_last_iter)); + + mvm->scan_status &= ~IWL_MVM_SCAN_SCHED; + ieee80211_sched_scan_stopped(mvm->hw); + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_DISABLED; + } else if (mvm->scan_status & IWL_MVM_SCAN_REGULAR) { + struct cfg80211_scan_info info = { + .aborted = aborted, + }; + + IWL_DEBUG_SCAN(mvm, "Regular scan %s, EBS status %s (FW)\n", + aborted ? "aborted" : "completed", + iwl_mvm_ebs_status_str(scan_notif->ebs_status)); + + mvm->scan_status &= ~IWL_MVM_SCAN_REGULAR; + ieee80211_scan_completed(mvm->hw, &info); + cancel_delayed_work(&mvm->scan_timeout_dwork); + iwl_mvm_resume_tcm(mvm); + } else { + IWL_ERR(mvm, + "got scan complete notification but no scan is running\n"); + } + + mvm->last_ebs_successful = + scan_notif->ebs_status == IWL_SCAN_EBS_SUCCESS || + scan_notif->ebs_status == IWL_SCAN_EBS_INACTIVE; +} + +static int iwl_ssid_exist(u8 *ssid, u8 ssid_len, struct iwl_ssid_ie *ssid_list) +{ + int i; + + for (i = 0; i < PROBE_OPTION_MAX; i++) { + if (!ssid_list[i].len) + break; + if (ssid_list[i].len == ssid_len && + !memcmp(ssid_list->ssid, ssid, ssid_len)) + return i; + } + return -1; +} + +/* We insert the SSIDs in an inverted order, because the FW will + * invert it back. + */ +static void iwl_scan_build_ssids(struct iwl_mvm_scan_params *params, + struct iwl_ssid_ie *ssids, + u32 *ssid_bitmap) +{ + int i, j; + int index; + u32 tmp_bitmap = 0; + + /* + * copy SSIDs from match list. + * iwl_config_sched_scan_profiles() uses the order of these ssids to + * config match list. + */ + for (i = 0, j = params->n_match_sets - 1; + j >= 0 && i < PROBE_OPTION_MAX; + i++, j--) { + /* skip empty SSID matchsets */ + if (!params->match_sets[j].ssid.ssid_len) + continue; + ssids[i].id = WLAN_EID_SSID; + ssids[i].len = params->match_sets[j].ssid.ssid_len; + memcpy(ssids[i].ssid, params->match_sets[j].ssid.ssid, + ssids[i].len); + } + + /* add SSIDs from scan SSID list */ + for (j = params->n_ssids - 1; + j >= 0 && i < PROBE_OPTION_MAX; + i++, j--) { + index = iwl_ssid_exist(params->ssids[j].ssid, + params->ssids[j].ssid_len, + ssids); + if (index < 0) { + ssids[i].id = WLAN_EID_SSID; + ssids[i].len = params->ssids[j].ssid_len; + memcpy(ssids[i].ssid, params->ssids[j].ssid, + ssids[i].len); + tmp_bitmap |= BIT(i); + } else { + tmp_bitmap |= BIT(index); + } + } + if (ssid_bitmap) + *ssid_bitmap = tmp_bitmap; +} + +static int +iwl_mvm_config_sched_scan_profiles(struct iwl_mvm *mvm, + struct cfg80211_sched_scan_request *req) +{ + struct iwl_scan_offload_profile *profile; + struct iwl_scan_offload_profile_cfg_v1 *profile_cfg_v1; + struct iwl_scan_offload_blocklist *blocklist; + struct iwl_scan_offload_profile_cfg_data *data; + int max_profiles = iwl_umac_scan_get_max_profiles(mvm->fw); + int profile_cfg_size = sizeof(*data) + + sizeof(*profile) * max_profiles; + struct iwl_host_cmd cmd = { + .id = SCAN_OFFLOAD_UPDATE_PROFILES_CMD, + .len[1] = profile_cfg_size, + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + .dataflags[1] = IWL_HCMD_DFL_NOCOPY, + }; + int blocklist_len; + int i; + int ret; + + if (WARN_ON(req->n_match_sets > max_profiles)) + return -EIO; + + if (mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_SHORT_BL) + blocklist_len = IWL_SCAN_SHORT_BLACKLIST_LEN; + else + blocklist_len = IWL_SCAN_MAX_BLACKLIST_LEN; + + blocklist = kcalloc(blocklist_len, sizeof(*blocklist), GFP_KERNEL); + if (!blocklist) + return -ENOMEM; + + profile_cfg_v1 = kzalloc(profile_cfg_size, GFP_KERNEL); + if (!profile_cfg_v1) { + ret = -ENOMEM; + goto free_blocklist; + } + + cmd.data[0] = blocklist; + cmd.len[0] = sizeof(*blocklist) * blocklist_len; + cmd.data[1] = profile_cfg_v1; + + /* if max_profile is MAX_PROFILES_V2, we have the new API */ + if (max_profiles == IWL_SCAN_MAX_PROFILES_V2) { + struct iwl_scan_offload_profile_cfg *profile_cfg = + (struct iwl_scan_offload_profile_cfg *)profile_cfg_v1; + + data = &profile_cfg->data; + } else { + data = &profile_cfg_v1->data; + } + + /* No blocklist configuration */ + data->num_profiles = req->n_match_sets; + data->active_clients = SCAN_CLIENT_SCHED_SCAN; + data->pass_match = SCAN_CLIENT_SCHED_SCAN; + data->match_notify = SCAN_CLIENT_SCHED_SCAN; + + if (!req->n_match_sets || !req->match_sets[0].ssid.ssid_len) + data->any_beacon_notify = SCAN_CLIENT_SCHED_SCAN; + + for (i = 0; i < req->n_match_sets; i++) { + profile = &profile_cfg_v1->profiles[i]; + profile->ssid_index = i; + /* Support any cipher and auth algorithm */ + profile->unicast_cipher = 0xff; + profile->auth_alg = IWL_AUTH_ALGO_UNSUPPORTED | + IWL_AUTH_ALGO_NONE | IWL_AUTH_ALGO_PSK | IWL_AUTH_ALGO_8021X | + IWL_AUTH_ALGO_SAE | IWL_AUTH_ALGO_8021X_SHA384 | IWL_AUTH_ALGO_OWE; + profile->network_type = IWL_NETWORK_TYPE_ANY; + profile->band_selection = IWL_SCAN_OFFLOAD_SELECT_ANY; + profile->client_bitmap = SCAN_CLIENT_SCHED_SCAN; + } + + IWL_DEBUG_SCAN(mvm, "Sending scheduled scan profile config\n"); + + ret = iwl_mvm_send_cmd(mvm, &cmd); + kfree(profile_cfg_v1); +free_blocklist: + kfree(blocklist); + + return ret; +} + +static bool iwl_mvm_scan_pass_all(struct iwl_mvm *mvm, + struct cfg80211_sched_scan_request *req) +{ + if (req->n_match_sets && req->match_sets[0].ssid.ssid_len) { + IWL_DEBUG_SCAN(mvm, + "Sending scheduled scan with filtering, n_match_sets %d\n", + req->n_match_sets); + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_DISABLED; + return false; + } + + IWL_DEBUG_SCAN(mvm, "Sending Scheduled scan without filtering\n"); + + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_ENABLED; + return true; +} + +static int iwl_mvm_lmac_scan_abort(struct iwl_mvm *mvm) +{ + int ret; + struct iwl_host_cmd cmd = { + .id = SCAN_OFFLOAD_ABORT_CMD, + }; + u32 status = CAN_ABORT_STATUS; + + ret = iwl_mvm_send_cmd_status(mvm, &cmd, &status); + if (ret) + return ret; + + if (status != CAN_ABORT_STATUS) { + /* + * The scan abort will return 1 for success or + * 2 for "failure". A failure condition can be + * due to simply not being in an active scan which + * can occur if we send the scan abort before the + * microcode has notified us that a scan is completed. + */ + IWL_DEBUG_SCAN(mvm, "SCAN OFFLOAD ABORT ret %d.\n", status); + ret = -ENOENT; + } + + return ret; +} + +static void iwl_mvm_scan_fill_tx_cmd(struct iwl_mvm *mvm, + struct iwl_scan_req_tx_cmd *tx_cmd, + bool no_cck) +{ + tx_cmd[0].tx_flags = cpu_to_le32(TX_CMD_FLG_SEQ_CTL | + TX_CMD_FLG_BT_DIS); + tx_cmd[0].rate_n_flags = iwl_mvm_scan_rate_n_flags(mvm, + NL80211_BAND_2GHZ, + no_cck); + + if (iwl_fw_lookup_cmd_ver(mvm->fw, ADD_STA, 0) < 12) { + tx_cmd[0].sta_id = mvm->aux_sta.sta_id; + tx_cmd[1].sta_id = mvm->aux_sta.sta_id; + + /* + * Fw doesn't use this sta anymore, pending deprecation via HOST API + * change + */ + } else { + tx_cmd[0].sta_id = 0xff; + tx_cmd[1].sta_id = 0xff; + } + + tx_cmd[1].tx_flags = cpu_to_le32(TX_CMD_FLG_SEQ_CTL | + TX_CMD_FLG_BT_DIS); + + tx_cmd[1].rate_n_flags = iwl_mvm_scan_rate_n_flags(mvm, + NL80211_BAND_5GHZ, + no_cck); +} + +static void +iwl_mvm_lmac_scan_cfg_channels(struct iwl_mvm *mvm, + struct ieee80211_channel **channels, + int n_channels, u32 ssid_bitmap, + struct iwl_scan_req_lmac *cmd) +{ + struct iwl_scan_channel_cfg_lmac *channel_cfg = (void *)&cmd->data; + int i; + + for (i = 0; i < n_channels; i++) { + channel_cfg[i].channel_num = + cpu_to_le16(channels[i]->hw_value); + channel_cfg[i].iter_count = cpu_to_le16(1); + channel_cfg[i].iter_interval = 0; + channel_cfg[i].flags = + cpu_to_le32(IWL_UNIFIED_SCAN_CHANNEL_PARTIAL | + ssid_bitmap); + } +} + +static u8 *iwl_mvm_copy_and_insert_ds_elem(struct iwl_mvm *mvm, const u8 *ies, + size_t len, u8 *const pos) +{ + static const u8 before_ds_params[] = { + WLAN_EID_SSID, + WLAN_EID_SUPP_RATES, + WLAN_EID_REQUEST, + WLAN_EID_EXT_SUPP_RATES, + }; + size_t offs; + u8 *newpos = pos; + + if (!iwl_mvm_rrm_scan_needed(mvm)) { + memcpy(newpos, ies, len); + return newpos + len; + } + + offs = ieee80211_ie_split(ies, len, + before_ds_params, + ARRAY_SIZE(before_ds_params), + 0); + + memcpy(newpos, ies, offs); + newpos += offs; + + /* Add a placeholder for DS Parameter Set element */ + *newpos++ = WLAN_EID_DS_PARAMS; + *newpos++ = 1; + *newpos++ = 0; + + memcpy(newpos, ies + offs, len - offs); + newpos += len - offs; + + return newpos; +} + +#define WFA_TPC_IE_LEN 9 + +static void iwl_mvm_add_tpc_report_ie(u8 *pos) +{ + pos[0] = WLAN_EID_VENDOR_SPECIFIC; + pos[1] = WFA_TPC_IE_LEN - 2; + pos[2] = (WLAN_OUI_MICROSOFT >> 16) & 0xff; + pos[3] = (WLAN_OUI_MICROSOFT >> 8) & 0xff; + pos[4] = WLAN_OUI_MICROSOFT & 0xff; + pos[5] = WLAN_OUI_TYPE_MICROSOFT_TPC; + pos[6] = 0; + /* pos[7] - tx power will be inserted by the FW */ + pos[7] = 0; + pos[8] = 0; +} + +static void +iwl_mvm_build_scan_probe(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_scan_ies *ies, + struct iwl_mvm_scan_params *params) +{ + struct ieee80211_mgmt *frame = (void *)params->preq.buf; + u8 *pos, *newpos; + const u8 *mac_addr = params->flags & NL80211_SCAN_FLAG_RANDOM_ADDR ? + params->mac_addr : NULL; + + /* + * Unfortunately, right now the offload scan doesn't support randomising + * within the firmware, so until the firmware API is ready we implement + * it in the driver. This means that the scan iterations won't really be + * random, only when it's restarted, but at least that helps a bit. + */ + if (mac_addr) + get_random_mask_addr(frame->sa, mac_addr, + params->mac_addr_mask); + else + memcpy(frame->sa, vif->addr, ETH_ALEN); + + frame->frame_control = cpu_to_le16(IEEE80211_STYPE_PROBE_REQ); + eth_broadcast_addr(frame->da); + eth_broadcast_addr(frame->bssid); + frame->seq_ctrl = 0; + + pos = frame->u.probe_req.variable; + *pos++ = WLAN_EID_SSID; + *pos++ = 0; + + params->preq.mac_header.offset = 0; + params->preq.mac_header.len = cpu_to_le16(24 + 2); + + /* Insert ds parameter set element on 2.4 GHz band */ + newpos = iwl_mvm_copy_and_insert_ds_elem(mvm, + ies->ies[NL80211_BAND_2GHZ], + ies->len[NL80211_BAND_2GHZ], + pos); + params->preq.band_data[0].offset = cpu_to_le16(pos - params->preq.buf); + params->preq.band_data[0].len = cpu_to_le16(newpos - pos); + pos = newpos; + + memcpy(pos, ies->ies[NL80211_BAND_5GHZ], + ies->len[NL80211_BAND_5GHZ]); + params->preq.band_data[1].offset = cpu_to_le16(pos - params->preq.buf); + params->preq.band_data[1].len = + cpu_to_le16(ies->len[NL80211_BAND_5GHZ]); + pos += ies->len[NL80211_BAND_5GHZ]; + + memcpy(pos, ies->ies[NL80211_BAND_6GHZ], + ies->len[NL80211_BAND_6GHZ]); + params->preq.band_data[2].offset = cpu_to_le16(pos - params->preq.buf); + params->preq.band_data[2].len = + cpu_to_le16(ies->len[NL80211_BAND_6GHZ]); + pos += ies->len[NL80211_BAND_6GHZ]; + memcpy(pos, ies->common_ies, ies->common_ie_len); + params->preq.common_data.offset = cpu_to_le16(pos - params->preq.buf); + + if (iwl_mvm_rrm_scan_needed(mvm) && + !fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_WFA_TPC_REP_IE_SUPPORT)) { + iwl_mvm_add_tpc_report_ie(pos + ies->common_ie_len); + params->preq.common_data.len = cpu_to_le16(ies->common_ie_len + + WFA_TPC_IE_LEN); + } else { + params->preq.common_data.len = cpu_to_le16(ies->common_ie_len); + } +} + +static void iwl_mvm_scan_lmac_dwell(struct iwl_mvm *mvm, + struct iwl_scan_req_lmac *cmd, + struct iwl_mvm_scan_params *params) +{ + cmd->active_dwell = IWL_SCAN_DWELL_ACTIVE; + cmd->passive_dwell = IWL_SCAN_DWELL_PASSIVE; + cmd->fragmented_dwell = IWL_SCAN_DWELL_FRAGMENTED; + cmd->extended_dwell = IWL_SCAN_DWELL_EXTENDED; + cmd->max_out_time = cpu_to_le32(scan_timing[params->type].max_out_time); + cmd->suspend_time = cpu_to_le32(scan_timing[params->type].suspend_time); + cmd->scan_prio = cpu_to_le32(IWL_SCAN_PRIORITY_EXT_6); +} + +static inline bool iwl_mvm_scan_fits(struct iwl_mvm *mvm, int n_ssids, + struct ieee80211_scan_ies *ies, + int n_channels) +{ + return ((n_ssids <= PROBE_OPTION_MAX) && + (n_channels <= mvm->fw->ucode_capa.n_scan_channels) & + (ies->common_ie_len + + ies->len[NL80211_BAND_2GHZ] + + ies->len[NL80211_BAND_5GHZ] <= + iwl_mvm_max_scan_ie_fw_cmd_room(mvm))); +} + +static inline bool iwl_mvm_scan_use_ebs(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + const struct iwl_ucode_capabilities *capa = &mvm->fw->ucode_capa; + bool low_latency; + + if (iwl_mvm_is_cdb_supported(mvm)) + low_latency = iwl_mvm_low_latency_band(mvm, NL80211_BAND_5GHZ); + else + low_latency = iwl_mvm_low_latency(mvm); + + /* We can only use EBS if: + * 1. the feature is supported; + * 2. the last EBS was successful; + * 3. if only single scan, the single scan EBS API is supported; + * 4. it's not a p2p find operation. + * 5. we are not in low latency mode, + * or if fragmented ebs is supported by the FW + */ + return ((capa->flags & IWL_UCODE_TLV_FLAGS_EBS_SUPPORT) && + mvm->last_ebs_successful && IWL_MVM_ENABLE_EBS && + vif->type != NL80211_IFTYPE_P2P_DEVICE && + (!low_latency || iwl_mvm_is_frag_ebs_supported(mvm))); +} + +static inline bool iwl_mvm_is_regular_scan(struct iwl_mvm_scan_params *params) +{ + return params->n_scan_plans == 1 && + params->scan_plans[0].iterations == 1; +} + +static bool iwl_mvm_is_scan_fragmented(enum iwl_mvm_scan_type type) +{ + return (type == IWL_SCAN_TYPE_FRAGMENTED || + type == IWL_SCAN_TYPE_FAST_BALANCE); +} + +static int iwl_mvm_scan_lmac_flags(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif) +{ + int flags = 0; + + if (params->n_ssids == 0) + flags |= IWL_MVM_LMAC_SCAN_FLAG_PASSIVE; + + if (params->n_ssids == 1 && params->ssids[0].ssid_len != 0) + flags |= IWL_MVM_LMAC_SCAN_FLAG_PRE_CONNECTION; + + if (iwl_mvm_is_scan_fragmented(params->type)) + flags |= IWL_MVM_LMAC_SCAN_FLAG_FRAGMENTED; + + if (iwl_mvm_rrm_scan_needed(mvm) && + fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_WFA_TPC_REP_IE_SUPPORT)) + flags |= IWL_MVM_LMAC_SCAN_FLAGS_RRM_ENABLED; + + if (params->pass_all) + flags |= IWL_MVM_LMAC_SCAN_FLAG_PASS_ALL; + else + flags |= IWL_MVM_LMAC_SCAN_FLAG_MATCH; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (mvm->scan_iter_notif_enabled) + flags |= IWL_MVM_LMAC_SCAN_FLAG_ITER_COMPLETE; +#endif + + if (mvm->sched_scan_pass_all == SCHED_SCAN_PASS_ALL_ENABLED) + flags |= IWL_MVM_LMAC_SCAN_FLAG_ITER_COMPLETE; + + if (iwl_mvm_is_regular_scan(params) && + vif->type != NL80211_IFTYPE_P2P_DEVICE && + !iwl_mvm_is_scan_fragmented(params->type)) + flags |= IWL_MVM_LMAC_SCAN_FLAG_EXTENDED_DWELL; + + return flags; +} + +static void +iwl_mvm_scan_set_legacy_probe_req(struct iwl_scan_probe_req_v1 *p_req, + struct iwl_scan_probe_req *src_p_req) +{ + int i; + + p_req->mac_header = src_p_req->mac_header; + for (i = 0; i < SCAN_NUM_BAND_PROBE_DATA_V_1; i++) + p_req->band_data[i] = src_p_req->band_data[i]; + p_req->common_data = src_p_req->common_data; + memcpy(p_req->buf, src_p_req->buf, sizeof(p_req->buf)); +} + +static int iwl_mvm_scan_lmac(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_scan_params *params) +{ + struct iwl_scan_req_lmac *cmd = mvm->scan_cmd; + struct iwl_scan_probe_req_v1 *preq = + (void *)(cmd->data + sizeof(struct iwl_scan_channel_cfg_lmac) * + mvm->fw->ucode_capa.n_scan_channels); + u32 ssid_bitmap = 0; + int i; + u8 band; + + if (WARN_ON(params->n_scan_plans > IWL_MAX_SCHED_SCAN_PLANS)) + return -EINVAL; + + iwl_mvm_scan_lmac_dwell(mvm, cmd, params); + + cmd->rx_chain_select = iwl_mvm_scan_rx_chain(mvm); + cmd->iter_num = cpu_to_le32(1); + cmd->n_channels = (u8)params->n_channels; + + cmd->delay = cpu_to_le32(params->delay); + + cmd->scan_flags = cpu_to_le32(iwl_mvm_scan_lmac_flags(mvm, params, + vif)); + + band = iwl_mvm_phy_band_from_nl80211(params->channels[0]->band); + cmd->flags = cpu_to_le32(band); + cmd->filter_flags = cpu_to_le32(MAC_FILTER_ACCEPT_GRP | + MAC_FILTER_IN_BEACON); + iwl_mvm_scan_fill_tx_cmd(mvm, cmd->tx_cmd, params->no_cck); + iwl_scan_build_ssids(params, cmd->direct_scan, &ssid_bitmap); + + /* this API uses bits 1-20 instead of 0-19 */ + ssid_bitmap <<= 1; + + for (i = 0; i < params->n_scan_plans; i++) { + struct cfg80211_sched_scan_plan *scan_plan = + ¶ms->scan_plans[i]; + + cmd->schedule[i].delay = + cpu_to_le16(scan_plan->interval); + cmd->schedule[i].iterations = scan_plan->iterations; + cmd->schedule[i].full_scan_mul = 1; + } + + /* + * If the number of iterations of the last scan plan is set to + * zero, it should run infinitely. However, this is not always the case. + * For example, when regular scan is requested the driver sets one scan + * plan with one iteration. + */ + if (!cmd->schedule[i - 1].iterations) + cmd->schedule[i - 1].iterations = 0xff; + + if (iwl_mvm_scan_use_ebs(mvm, vif)) { + cmd->channel_opt[0].flags = + cpu_to_le16(IWL_SCAN_CHANNEL_FLAG_EBS | + IWL_SCAN_CHANNEL_FLAG_EBS_ACCURATE | + IWL_SCAN_CHANNEL_FLAG_CACHE_ADD); + cmd->channel_opt[0].non_ebs_ratio = + cpu_to_le16(IWL_DENSE_EBS_SCAN_RATIO); + cmd->channel_opt[1].flags = + cpu_to_le16(IWL_SCAN_CHANNEL_FLAG_EBS | + IWL_SCAN_CHANNEL_FLAG_EBS_ACCURATE | + IWL_SCAN_CHANNEL_FLAG_CACHE_ADD); + cmd->channel_opt[1].non_ebs_ratio = + cpu_to_le16(IWL_SPARSE_EBS_SCAN_RATIO); + } + + iwl_mvm_lmac_scan_cfg_channels(mvm, params->channels, + params->n_channels, ssid_bitmap, cmd); + + iwl_mvm_scan_set_legacy_probe_req(preq, ¶ms->preq); + + return 0; +} + +static int rate_to_scan_rate_flag(unsigned int rate) +{ + static const int rate_to_scan_rate[IWL_RATE_COUNT] = { + [IWL_RATE_1M_INDEX] = SCAN_CONFIG_RATE_1M, + [IWL_RATE_2M_INDEX] = SCAN_CONFIG_RATE_2M, + [IWL_RATE_5M_INDEX] = SCAN_CONFIG_RATE_5M, + [IWL_RATE_11M_INDEX] = SCAN_CONFIG_RATE_11M, + [IWL_RATE_6M_INDEX] = SCAN_CONFIG_RATE_6M, + [IWL_RATE_9M_INDEX] = SCAN_CONFIG_RATE_9M, + [IWL_RATE_12M_INDEX] = SCAN_CONFIG_RATE_12M, + [IWL_RATE_18M_INDEX] = SCAN_CONFIG_RATE_18M, + [IWL_RATE_24M_INDEX] = SCAN_CONFIG_RATE_24M, + [IWL_RATE_36M_INDEX] = SCAN_CONFIG_RATE_36M, + [IWL_RATE_48M_INDEX] = SCAN_CONFIG_RATE_48M, + [IWL_RATE_54M_INDEX] = SCAN_CONFIG_RATE_54M, + }; + + return rate_to_scan_rate[rate]; +} + +static __le32 iwl_mvm_scan_config_rates(struct iwl_mvm *mvm) +{ + struct ieee80211_supported_band *band; + unsigned int rates = 0; + int i; + + band = &mvm->nvm_data->bands[NL80211_BAND_2GHZ]; + for (i = 0; i < band->n_bitrates; i++) + rates |= rate_to_scan_rate_flag(band->bitrates[i].hw_value); + band = &mvm->nvm_data->bands[NL80211_BAND_5GHZ]; + for (i = 0; i < band->n_bitrates; i++) + rates |= rate_to_scan_rate_flag(band->bitrates[i].hw_value); + + /* Set both basic rates and supported rates */ + rates |= SCAN_CONFIG_SUPPORTED_RATE(rates); + + return cpu_to_le32(rates); +} + +static void iwl_mvm_fill_scan_dwell(struct iwl_mvm *mvm, + struct iwl_scan_dwell *dwell) +{ + dwell->active = IWL_SCAN_DWELL_ACTIVE; + dwell->passive = IWL_SCAN_DWELL_PASSIVE; + dwell->fragmented = IWL_SCAN_DWELL_FRAGMENTED; + dwell->extended = IWL_SCAN_DWELL_EXTENDED; +} + +static void iwl_mvm_fill_channels(struct iwl_mvm *mvm, u8 *channels, + u32 max_channels) +{ + struct ieee80211_supported_band *band; + int i, j = 0; + + band = &mvm->nvm_data->bands[NL80211_BAND_2GHZ]; + for (i = 0; i < band->n_channels && j < max_channels; i++, j++) + channels[j] = band->channels[i].hw_value; + band = &mvm->nvm_data->bands[NL80211_BAND_5GHZ]; + for (i = 0; i < band->n_channels && j < max_channels; i++, j++) + channels[j] = band->channels[i].hw_value; +} + +static void iwl_mvm_fill_scan_config_v1(struct iwl_mvm *mvm, void *config, + u32 flags, u8 channel_flags, + u32 max_channels) +{ + enum iwl_mvm_scan_type type = iwl_mvm_get_scan_type(mvm, NULL); + struct iwl_scan_config_v1 *cfg = config; + + cfg->flags = cpu_to_le32(flags); + cfg->tx_chains = cpu_to_le32(iwl_mvm_get_valid_tx_ant(mvm)); + cfg->rx_chains = cpu_to_le32(iwl_mvm_scan_rx_ant(mvm)); + cfg->legacy_rates = iwl_mvm_scan_config_rates(mvm); + cfg->out_of_channel_time = cpu_to_le32(scan_timing[type].max_out_time); + cfg->suspend_time = cpu_to_le32(scan_timing[type].suspend_time); + + iwl_mvm_fill_scan_dwell(mvm, &cfg->dwell); + + memcpy(&cfg->mac_addr, &mvm->addresses[0].addr, ETH_ALEN); + + /* This function should not be called when using ADD_STA ver >=12 */ + WARN_ON_ONCE(iwl_fw_lookup_cmd_ver(mvm->fw, ADD_STA, 0) >= 12); + + cfg->bcast_sta_id = mvm->aux_sta.sta_id; + cfg->channel_flags = channel_flags; + + iwl_mvm_fill_channels(mvm, cfg->channel_array, max_channels); +} + +static void iwl_mvm_fill_scan_config_v2(struct iwl_mvm *mvm, void *config, + u32 flags, u8 channel_flags, + u32 max_channels) +{ + struct iwl_scan_config_v2 *cfg = config; + + cfg->flags = cpu_to_le32(flags); + cfg->tx_chains = cpu_to_le32(iwl_mvm_get_valid_tx_ant(mvm)); + cfg->rx_chains = cpu_to_le32(iwl_mvm_scan_rx_ant(mvm)); + cfg->legacy_rates = iwl_mvm_scan_config_rates(mvm); + + if (iwl_mvm_is_cdb_supported(mvm)) { + enum iwl_mvm_scan_type lb_type, hb_type; + + lb_type = iwl_mvm_get_scan_type_band(mvm, NULL, + NL80211_BAND_2GHZ); + hb_type = iwl_mvm_get_scan_type_band(mvm, NULL, + NL80211_BAND_5GHZ); + + cfg->out_of_channel_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(scan_timing[lb_type].max_out_time); + cfg->suspend_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(scan_timing[lb_type].suspend_time); + + cfg->out_of_channel_time[SCAN_HB_LMAC_IDX] = + cpu_to_le32(scan_timing[hb_type].max_out_time); + cfg->suspend_time[SCAN_HB_LMAC_IDX] = + cpu_to_le32(scan_timing[hb_type].suspend_time); + } else { + enum iwl_mvm_scan_type type = + iwl_mvm_get_scan_type(mvm, NULL); + + cfg->out_of_channel_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(scan_timing[type].max_out_time); + cfg->suspend_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(scan_timing[type].suspend_time); + } + + iwl_mvm_fill_scan_dwell(mvm, &cfg->dwell); + + memcpy(&cfg->mac_addr, &mvm->addresses[0].addr, ETH_ALEN); + + /* This function should not be called when using ADD_STA ver >=12 */ + WARN_ON_ONCE(iwl_fw_lookup_cmd_ver(mvm->fw, ADD_STA, 0) >= 12); + + cfg->bcast_sta_id = mvm->aux_sta.sta_id; + cfg->channel_flags = channel_flags; + + iwl_mvm_fill_channels(mvm, cfg->channel_array, max_channels); +} + +static int iwl_mvm_legacy_config_scan(struct iwl_mvm *mvm) +{ + void *cfg; + int ret, cmd_size; + struct iwl_host_cmd cmd = { + .id = WIDE_ID(IWL_ALWAYS_LONG_GROUP, SCAN_CFG_CMD), + }; + enum iwl_mvm_scan_type type; + enum iwl_mvm_scan_type hb_type = IWL_SCAN_TYPE_NOT_SET; + int num_channels = + mvm->nvm_data->bands[NL80211_BAND_2GHZ].n_channels + + mvm->nvm_data->bands[NL80211_BAND_5GHZ].n_channels; + u32 flags; + u8 channel_flags; + + if (WARN_ON(num_channels > mvm->fw->ucode_capa.n_scan_channels)) + num_channels = mvm->fw->ucode_capa.n_scan_channels; + + if (iwl_mvm_is_cdb_supported(mvm)) { + type = iwl_mvm_get_scan_type_band(mvm, NULL, + NL80211_BAND_2GHZ); + hb_type = iwl_mvm_get_scan_type_band(mvm, NULL, + NL80211_BAND_5GHZ); + if (type == mvm->scan_type && hb_type == mvm->hb_scan_type) + return 0; + } else { + type = iwl_mvm_get_scan_type(mvm, NULL); + if (type == mvm->scan_type) + return 0; + } + + if (iwl_mvm_cdb_scan_api(mvm)) + cmd_size = sizeof(struct iwl_scan_config_v2); + else + cmd_size = sizeof(struct iwl_scan_config_v1); + cmd_size += mvm->fw->ucode_capa.n_scan_channels; + + cfg = kzalloc(cmd_size, GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + flags = SCAN_CONFIG_FLAG_ACTIVATE | + SCAN_CONFIG_FLAG_ALLOW_CHUB_REQS | + SCAN_CONFIG_FLAG_SET_TX_CHAINS | + SCAN_CONFIG_FLAG_SET_RX_CHAINS | + SCAN_CONFIG_FLAG_SET_AUX_STA_ID | + SCAN_CONFIG_FLAG_SET_ALL_TIMES | + SCAN_CONFIG_FLAG_SET_LEGACY_RATES | + SCAN_CONFIG_FLAG_SET_MAC_ADDR | + SCAN_CONFIG_FLAG_SET_CHANNEL_FLAGS | + SCAN_CONFIG_N_CHANNELS(num_channels) | + (iwl_mvm_is_scan_fragmented(type) ? + SCAN_CONFIG_FLAG_SET_FRAGMENTED : + SCAN_CONFIG_FLAG_CLEAR_FRAGMENTED); + + channel_flags = IWL_CHANNEL_FLAG_EBS | + IWL_CHANNEL_FLAG_ACCURATE_EBS | + IWL_CHANNEL_FLAG_EBS_ADD | + IWL_CHANNEL_FLAG_PRE_SCAN_PASSIVE2ACTIVE; + + /* + * Check for fragmented scan on LMAC2 - high band. + * LMAC1 - low band is checked above. + */ + if (iwl_mvm_cdb_scan_api(mvm)) { + if (iwl_mvm_is_cdb_supported(mvm)) + flags |= (iwl_mvm_is_scan_fragmented(hb_type)) ? + SCAN_CONFIG_FLAG_SET_LMAC2_FRAGMENTED : + SCAN_CONFIG_FLAG_CLEAR_LMAC2_FRAGMENTED; + iwl_mvm_fill_scan_config_v2(mvm, cfg, flags, channel_flags, + num_channels); + } else { + iwl_mvm_fill_scan_config_v1(mvm, cfg, flags, channel_flags, + num_channels); + } + + cmd.data[0] = cfg; + cmd.len[0] = cmd_size; + cmd.dataflags[0] = IWL_HCMD_DFL_NOCOPY; + + IWL_DEBUG_SCAN(mvm, "Sending UMAC scan config\n"); + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (!ret) { + mvm->scan_type = type; + mvm->hb_scan_type = hb_type; + } + + kfree(cfg); + return ret; +} + +int iwl_mvm_config_scan(struct iwl_mvm *mvm) +{ + struct iwl_scan_config cfg; + struct iwl_host_cmd cmd = { + .id = WIDE_ID(IWL_ALWAYS_LONG_GROUP, SCAN_CFG_CMD), + .len[0] = sizeof(cfg), + .data[0] = &cfg, + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + }; + + if (!iwl_mvm_is_reduced_config_scan_supported(mvm)) + return iwl_mvm_legacy_config_scan(mvm); + + memset(&cfg, 0, sizeof(cfg)); + + if (iwl_fw_lookup_cmd_ver(mvm->fw, ADD_STA, 0) < 12) { + cfg.bcast_sta_id = mvm->aux_sta.sta_id; + } else if (iwl_fw_lookup_cmd_ver(mvm->fw, SCAN_CFG_CMD, 0) < 5) { + /* + * Fw doesn't use this sta anymore. Deprecated on SCAN_CFG_CMD + * version 5. + */ + cfg.bcast_sta_id = 0xff; + } + + cfg.tx_chains = cpu_to_le32(iwl_mvm_get_valid_tx_ant(mvm)); + cfg.rx_chains = cpu_to_le32(iwl_mvm_scan_rx_ant(mvm)); + + IWL_DEBUG_SCAN(mvm, "Sending UMAC scan config\n"); + + return iwl_mvm_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_scan_uid_by_status(struct iwl_mvm *mvm, int status) +{ + int i; + + for (i = 0; i < mvm->max_scans; i++) + if (mvm->scan_uid_status[i] == status) + return i; + + return -ENOENT; +} + +static void iwl_mvm_scan_umac_dwell(struct iwl_mvm *mvm, + struct iwl_scan_req_umac *cmd, + struct iwl_mvm_scan_params *params) +{ + struct iwl_mvm_scan_timing_params *timing, *hb_timing; + u8 active_dwell, passive_dwell; + + timing = &scan_timing[params->type]; + active_dwell = IWL_SCAN_DWELL_ACTIVE; + passive_dwell = IWL_SCAN_DWELL_PASSIVE; + + if (iwl_mvm_is_adaptive_dwell_supported(mvm)) { + cmd->v7.adwell_default_n_aps_social = + IWL_SCAN_ADWELL_DEFAULT_N_APS_SOCIAL; + cmd->v7.adwell_default_n_aps = + IWL_SCAN_ADWELL_DEFAULT_LB_N_APS; + + if (iwl_mvm_is_adwell_hb_ap_num_supported(mvm)) + cmd->v9.adwell_default_hb_n_aps = + IWL_SCAN_ADWELL_DEFAULT_HB_N_APS; + + /* if custom max budget was configured with debugfs */ + if (IWL_MVM_ADWELL_MAX_BUDGET) + cmd->v7.adwell_max_budget = + cpu_to_le16(IWL_MVM_ADWELL_MAX_BUDGET); + else if (params->ssids && params->ssids[0].ssid_len) + cmd->v7.adwell_max_budget = + cpu_to_le16(IWL_SCAN_ADWELL_MAX_BUDGET_DIRECTED_SCAN); + else + cmd->v7.adwell_max_budget = + cpu_to_le16(IWL_SCAN_ADWELL_MAX_BUDGET_FULL_SCAN); + + cmd->v7.scan_priority = cpu_to_le32(IWL_SCAN_PRIORITY_EXT_6); + cmd->v7.max_out_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(timing->max_out_time); + cmd->v7.suspend_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(timing->suspend_time); + + if (iwl_mvm_is_cdb_supported(mvm)) { + hb_timing = &scan_timing[params->hb_type]; + + cmd->v7.max_out_time[SCAN_HB_LMAC_IDX] = + cpu_to_le32(hb_timing->max_out_time); + cmd->v7.suspend_time[SCAN_HB_LMAC_IDX] = + cpu_to_le32(hb_timing->suspend_time); + } + + if (!iwl_mvm_is_adaptive_dwell_v2_supported(mvm)) { + cmd->v7.active_dwell = active_dwell; + cmd->v7.passive_dwell = passive_dwell; + cmd->v7.fragmented_dwell = IWL_SCAN_DWELL_FRAGMENTED; + } else { + cmd->v8.active_dwell[SCAN_LB_LMAC_IDX] = active_dwell; + cmd->v8.passive_dwell[SCAN_LB_LMAC_IDX] = passive_dwell; + if (iwl_mvm_is_cdb_supported(mvm)) { + cmd->v8.active_dwell[SCAN_HB_LMAC_IDX] = + active_dwell; + cmd->v8.passive_dwell[SCAN_HB_LMAC_IDX] = + passive_dwell; + } + } + } else { + cmd->v1.extended_dwell = IWL_SCAN_DWELL_EXTENDED; + cmd->v1.active_dwell = active_dwell; + cmd->v1.passive_dwell = passive_dwell; + cmd->v1.fragmented_dwell = IWL_SCAN_DWELL_FRAGMENTED; + + if (iwl_mvm_is_cdb_supported(mvm)) { + hb_timing = &scan_timing[params->hb_type]; + + cmd->v6.max_out_time[SCAN_HB_LMAC_IDX] = + cpu_to_le32(hb_timing->max_out_time); + cmd->v6.suspend_time[SCAN_HB_LMAC_IDX] = + cpu_to_le32(hb_timing->suspend_time); + } + + if (iwl_mvm_cdb_scan_api(mvm)) { + cmd->v6.scan_priority = + cpu_to_le32(IWL_SCAN_PRIORITY_EXT_6); + cmd->v6.max_out_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(timing->max_out_time); + cmd->v6.suspend_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(timing->suspend_time); + } else { + cmd->v1.scan_priority = + cpu_to_le32(IWL_SCAN_PRIORITY_EXT_6); + cmd->v1.max_out_time = + cpu_to_le32(timing->max_out_time); + cmd->v1.suspend_time = + cpu_to_le32(timing->suspend_time); + } + } + + if (iwl_mvm_is_regular_scan(params)) + cmd->ooc_priority = cpu_to_le32(IWL_SCAN_PRIORITY_EXT_6); + else + cmd->ooc_priority = cpu_to_le32(IWL_SCAN_PRIORITY_EXT_2); +} + +static u32 iwl_mvm_scan_umac_ooc_priority(struct iwl_mvm_scan_params *params) +{ + return iwl_mvm_is_regular_scan(params) ? + IWL_SCAN_PRIORITY_EXT_6 : + IWL_SCAN_PRIORITY_EXT_2; +} + +static void +iwl_mvm_scan_umac_dwell_v11(struct iwl_mvm *mvm, + struct iwl_scan_general_params_v11 *general_params, + struct iwl_mvm_scan_params *params) +{ + struct iwl_mvm_scan_timing_params *timing, *hb_timing; + u8 active_dwell, passive_dwell; + + timing = &scan_timing[params->type]; + active_dwell = IWL_SCAN_DWELL_ACTIVE; + passive_dwell = IWL_SCAN_DWELL_PASSIVE; + + general_params->adwell_default_social_chn = + IWL_SCAN_ADWELL_DEFAULT_N_APS_SOCIAL; + general_params->adwell_default_2g = IWL_SCAN_ADWELL_DEFAULT_LB_N_APS; + general_params->adwell_default_5g = IWL_SCAN_ADWELL_DEFAULT_HB_N_APS; + + /* if custom max budget was configured with debugfs */ + if (IWL_MVM_ADWELL_MAX_BUDGET) + general_params->adwell_max_budget = + cpu_to_le16(IWL_MVM_ADWELL_MAX_BUDGET); + else if (params->ssids && params->ssids[0].ssid_len) + general_params->adwell_max_budget = + cpu_to_le16(IWL_SCAN_ADWELL_MAX_BUDGET_DIRECTED_SCAN); + else + general_params->adwell_max_budget = + cpu_to_le16(IWL_SCAN_ADWELL_MAX_BUDGET_FULL_SCAN); + + general_params->scan_priority = cpu_to_le32(IWL_SCAN_PRIORITY_EXT_6); + general_params->max_out_of_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(timing->max_out_time); + general_params->suspend_time[SCAN_LB_LMAC_IDX] = + cpu_to_le32(timing->suspend_time); + + hb_timing = &scan_timing[params->hb_type]; + + general_params->max_out_of_time[SCAN_HB_LMAC_IDX] = + cpu_to_le32(hb_timing->max_out_time); + general_params->suspend_time[SCAN_HB_LMAC_IDX] = + cpu_to_le32(hb_timing->suspend_time); + + general_params->active_dwell[SCAN_LB_LMAC_IDX] = active_dwell; + general_params->passive_dwell[SCAN_LB_LMAC_IDX] = passive_dwell; + general_params->active_dwell[SCAN_HB_LMAC_IDX] = active_dwell; + general_params->passive_dwell[SCAN_HB_LMAC_IDX] = passive_dwell; +} + +struct iwl_mvm_scan_channel_segment { + u8 start_idx; + u8 end_idx; + u8 first_channel_id; + u8 last_channel_id; + u8 channel_spacing_shift; + u8 band; +}; + +static const struct iwl_mvm_scan_channel_segment scan_channel_segments[] = { + { + .start_idx = 0, + .end_idx = 13, + .first_channel_id = 1, + .last_channel_id = 14, + .channel_spacing_shift = 0, + .band = PHY_BAND_24 + }, + { + .start_idx = 14, + .end_idx = 41, + .first_channel_id = 36, + .last_channel_id = 144, + .channel_spacing_shift = 2, + .band = PHY_BAND_5 + }, + { + .start_idx = 42, + .end_idx = 50, + .first_channel_id = 149, + .last_channel_id = 181, + .channel_spacing_shift = 2, + .band = PHY_BAND_5 + }, + { + .start_idx = 51, + .end_idx = 111, + .first_channel_id = 1, + .last_channel_id = 241, + .channel_spacing_shift = 2, + .band = PHY_BAND_6 + }, +}; + +static int iwl_mvm_scan_ch_and_band_to_idx(u8 channel_id, u8 band) +{ + int i, index; + + if (!channel_id) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(scan_channel_segments); i++) { + const struct iwl_mvm_scan_channel_segment *ch_segment = + &scan_channel_segments[i]; + u32 ch_offset; + + if (ch_segment->band != band || + ch_segment->first_channel_id > channel_id || + ch_segment->last_channel_id < channel_id) + continue; + + ch_offset = (channel_id - ch_segment->first_channel_id) >> + ch_segment->channel_spacing_shift; + + index = scan_channel_segments[i].start_idx + ch_offset; + if (index < IWL_SCAN_NUM_CHANNELS) + return index; + + break; + } + + return -EINVAL; +} + +static const u8 p2p_go_friendly_chs[] = { + 36, 40, 44, 48, 149, 153, 157, 161, 165, +}; + +static const u8 social_chs[] = { + 1, 6, 11 +}; + +static void iwl_mvm_scan_ch_add_n_aps_override(enum nl80211_iftype vif_type, + u8 ch_id, u8 band, u8 *ch_bitmap, + size_t bitmap_n_entries) +{ + int i; + + if (vif_type != NL80211_IFTYPE_P2P_DEVICE) + return; + + for (i = 0; i < ARRAY_SIZE(p2p_go_friendly_chs); i++) { + if (p2p_go_friendly_chs[i] == ch_id) { + int ch_idx, bitmap_idx; + + ch_idx = iwl_mvm_scan_ch_and_band_to_idx(ch_id, band); + if (ch_idx < 0) + return; + + bitmap_idx = ch_idx / 8; + if (bitmap_idx >= bitmap_n_entries) + return; + + ch_idx = ch_idx % 8; + ch_bitmap[bitmap_idx] |= BIT(ch_idx); + + return; + } + } +} + +static u32 iwl_mvm_scan_ch_n_aps_flag(enum nl80211_iftype vif_type, u8 ch_id) +{ + int i; + u32 flags = 0; + + if (vif_type != NL80211_IFTYPE_P2P_DEVICE) + goto out; + + for (i = 0; i < ARRAY_SIZE(p2p_go_friendly_chs); i++) { + if (p2p_go_friendly_chs[i] == ch_id) { + flags |= IWL_SCAN_ADWELL_N_APS_GO_FRIENDLY_BIT; + break; + } + } + + if (flags) + goto out; + + for (i = 0; i < ARRAY_SIZE(social_chs); i++) { + if (social_chs[i] == ch_id) { + flags |= IWL_SCAN_ADWELL_N_APS_SOCIAL_CHS_BIT; + break; + } + } + +out: + return flags; +} + +static void +iwl_mvm_umac_scan_cfg_channels(struct iwl_mvm *mvm, + struct ieee80211_channel **channels, + int n_channels, u32 flags, + struct iwl_scan_channel_cfg_umac *channel_cfg) +{ + int i; + + for (i = 0; i < n_channels; i++) { + channel_cfg[i].flags = cpu_to_le32(flags); + channel_cfg[i].v1.channel_num = channels[i]->hw_value; + if (iwl_mvm_is_scan_ext_chan_supported(mvm)) { + enum nl80211_band band = channels[i]->band; + + channel_cfg[i].v2.band = + iwl_mvm_phy_band_from_nl80211(band); + channel_cfg[i].v2.iter_count = 1; + channel_cfg[i].v2.iter_interval = 0; + } else { + channel_cfg[i].v1.iter_count = 1; + channel_cfg[i].v1.iter_interval = 0; + } + } +} + +static void +iwl_mvm_umac_scan_cfg_channels_v4(struct iwl_mvm *mvm, + struct ieee80211_channel **channels, + struct iwl_scan_channel_params_v4 *cp, + int n_channels, u32 flags, + enum nl80211_iftype vif_type) +{ + u8 *bitmap = cp->adwell_ch_override_bitmap; + size_t bitmap_n_entries = ARRAY_SIZE(cp->adwell_ch_override_bitmap); + int i; + + for (i = 0; i < n_channels; i++) { + enum nl80211_band band = channels[i]->band; + struct iwl_scan_channel_cfg_umac *cfg = + &cp->channel_config[i]; + + cfg->flags = cpu_to_le32(flags); + cfg->v2.channel_num = channels[i]->hw_value; + cfg->v2.band = iwl_mvm_phy_band_from_nl80211(band); + cfg->v2.iter_count = 1; + cfg->v2.iter_interval = 0; + + iwl_mvm_scan_ch_add_n_aps_override(vif_type, + cfg->v2.channel_num, + cfg->v2.band, bitmap, + bitmap_n_entries); + } +} + +static void +iwl_mvm_umac_scan_cfg_channels_v6(struct iwl_mvm *mvm, + struct ieee80211_channel **channels, + struct iwl_scan_channel_params_v6 *cp, + int n_channels, u32 flags, + enum nl80211_iftype vif_type) +{ + int i; + + for (i = 0; i < n_channels; i++) { + enum nl80211_band band = channels[i]->band; + struct iwl_scan_channel_cfg_umac *cfg = &cp->channel_config[i]; + u32 n_aps_flag = + iwl_mvm_scan_ch_n_aps_flag(vif_type, + channels[i]->hw_value); + + cfg->flags = cpu_to_le32(flags | n_aps_flag); + cfg->v2.channel_num = channels[i]->hw_value; + cfg->v2.band = iwl_mvm_phy_band_from_nl80211(band); + if (cfg80211_channel_is_psc(channels[i])) + cfg->flags = 0; + cfg->v2.iter_count = 1; + cfg->v2.iter_interval = 0; + } +} + +static void +iwl_mvm_umac_scan_fill_6g_chan_list(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct iwl_scan_probe_params_v4 *pp) +{ + int j, idex_s = 0, idex_b = 0; + struct cfg80211_scan_6ghz_params *scan_6ghz_params = + params->scan_6ghz_params; + bool hidden_supported = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_HIDDEN_6GHZ_SCAN); + + for (j = 0; j < params->n_ssids && idex_s < SCAN_SHORT_SSID_MAX_SIZE; + j++) { + if (!params->ssids[j].ssid_len) + continue; + + pp->short_ssid[idex_s] = + cpu_to_le32(~crc32_le(~0, params->ssids[j].ssid, + params->ssids[j].ssid_len)); + + if (hidden_supported) { + pp->direct_scan[idex_s].id = WLAN_EID_SSID; + pp->direct_scan[idex_s].len = params->ssids[j].ssid_len; + memcpy(pp->direct_scan[idex_s].ssid, params->ssids[j].ssid, + params->ssids[j].ssid_len); + } + idex_s++; + } + + /* + * Populate the arrays of the short SSIDs and the BSSIDs using the 6GHz + * collocated parameters. This might not be optimal, as this processing + * does not (yet) correspond to the actual channels, so it is possible + * that some entries would be left out. + * + * TODO: improve this logic. + */ + for (j = 0; j < params->n_6ghz_params; j++) { + int k; + + /* First, try to place the short SSID */ + if (scan_6ghz_params[j].short_ssid_valid) { + for (k = 0; k < idex_s; k++) { + if (pp->short_ssid[k] == + cpu_to_le32(scan_6ghz_params[j].short_ssid)) + break; + } + + if (k == idex_s && idex_s < SCAN_SHORT_SSID_MAX_SIZE) { + pp->short_ssid[idex_s++] = + cpu_to_le32(scan_6ghz_params[j].short_ssid); + } + } + + /* try to place BSSID for the same entry */ + for (k = 0; k < idex_b; k++) { + if (!memcmp(&pp->bssid_array[k], + scan_6ghz_params[j].bssid, ETH_ALEN)) + break; + } + + if (k == idex_b && idex_b < SCAN_BSSID_MAX_SIZE) { + memcpy(&pp->bssid_array[idex_b++], + scan_6ghz_params[j].bssid, ETH_ALEN); + } + } + + pp->short_ssid_num = idex_s; + pp->bssid_num = idex_b; +} + +/* TODO: this function can be merged with iwl_mvm_scan_umac_fill_ch_p_v6 */ +static u32 +iwl_mvm_umac_scan_cfg_channels_v6_6g(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + u32 n_channels, + struct iwl_scan_probe_params_v4 *pp, + struct iwl_scan_channel_params_v6 *cp, + enum nl80211_iftype vif_type) +{ + int i; + struct cfg80211_scan_6ghz_params *scan_6ghz_params = + params->scan_6ghz_params; + u32 ch_cnt; + + for (i = 0, ch_cnt = 0; i < params->n_channels; i++) { + struct iwl_scan_channel_cfg_umac *cfg = + &cp->channel_config[ch_cnt]; + + u32 s_ssid_bitmap = 0, bssid_bitmap = 0, flags = 0; + u8 j, k, s_max = 0, b_max = 0, n_used_bssid_entries; + bool force_passive, found = false, allow_passive = true, + unsolicited_probe_on_chan = false, psc_no_listen = false; + + /* + * Avoid performing passive scan on non PSC channels unless the + * scan is specifically a passive scan, i.e., no SSIDs + * configured in the scan command. + */ + if (!cfg80211_channel_is_psc(params->channels[i]) && + !params->n_6ghz_params && params->n_ssids) + continue; + + cfg->v1.channel_num = params->channels[i]->hw_value; + cfg->v2.band = 2; + cfg->v2.iter_count = 1; + cfg->v2.iter_interval = 0; + + /* + * The optimize the scan time, i.e., reduce the scan dwell time + * on each channel, the below logic tries to set 3 direct BSSID + * probe requests for each broadcast probe request with a short + * SSID. + * TODO: improve this logic + */ + n_used_bssid_entries = 3; + for (j = 0; j < params->n_6ghz_params; j++) { + if (!(scan_6ghz_params[j].channel_idx == i)) + continue; + + found = false; + unsolicited_probe_on_chan |= + scan_6ghz_params[j].unsolicited_probe; + psc_no_listen |= scan_6ghz_params[j].psc_no_listen; + + for (k = 0; k < pp->short_ssid_num; k++) { + if (!scan_6ghz_params[j].unsolicited_probe && + le32_to_cpu(pp->short_ssid[k]) == + scan_6ghz_params[j].short_ssid) { + /* Relevant short SSID bit set */ + if (s_ssid_bitmap & BIT(k)) { + found = true; + break; + } + + /* + * Use short SSID only to create a new + * iteration during channel dwell or in + * case that the short SSID has a + * matching SSID, i.e., scan for hidden + * APs. + */ + if (n_used_bssid_entries >= 3) { + s_ssid_bitmap |= BIT(k); + s_max++; + n_used_bssid_entries -= 3; + found = true; + break; + } else if (pp->direct_scan[k].len) { + s_ssid_bitmap |= BIT(k); + s_max++; + found = true; + allow_passive = false; + break; + } + } + } + + if (found) + continue; + + for (k = 0; k < pp->bssid_num; k++) { + if (!memcmp(&pp->bssid_array[k], + scan_6ghz_params[j].bssid, + ETH_ALEN)) { + if (!(bssid_bitmap & BIT(k))) { + bssid_bitmap |= BIT(k); + b_max++; + n_used_bssid_entries++; + } + break; + } + } + } + + if (cfg80211_channel_is_psc(params->channels[i]) && + psc_no_listen) + flags |= IWL_UHB_CHAN_CFG_FLAG_PSC_CHAN_NO_LISTEN; + + if (unsolicited_probe_on_chan) + flags |= IWL_UHB_CHAN_CFG_FLAG_UNSOLICITED_PROBE_RES; + + /* + * In the following cases apply passive scan: + * 1. Non fragmented scan: + * - PSC channel with NO_LISTEN_FLAG on should be treated + * like non PSC channel + * - Non PSC channel with more than 3 short SSIDs or more + * than 9 BSSIDs. + * - Non PSC Channel with unsolicited probe response and + * more than 2 short SSIDs or more than 6 BSSIDs. + * - PSC channel with more than 2 short SSIDs or more than + * 6 BSSIDs. + * 3. Fragmented scan: + * - PSC channel with more than 1 SSID or 3 BSSIDs. + * - Non PSC channel with more than 2 SSIDs or 6 BSSIDs. + * - Non PSC channel with unsolicited probe response and + * more than 1 SSID or more than 3 BSSIDs. + */ + if (!iwl_mvm_is_scan_fragmented(params->type)) { + if (!cfg80211_channel_is_psc(params->channels[i]) || + flags & IWL_UHB_CHAN_CFG_FLAG_PSC_CHAN_NO_LISTEN) { + force_passive = (s_max > 3 || b_max > 9); + force_passive |= (unsolicited_probe_on_chan && + (s_max > 2 || b_max > 6)); + } else { + force_passive = (s_max > 2 || b_max > 6); + } + } else if (cfg80211_channel_is_psc(params->channels[i])) { + force_passive = (s_max > 1 || b_max > 3); + } else { + force_passive = (s_max > 2 || b_max > 6); + force_passive |= (unsolicited_probe_on_chan && + (s_max > 1 || b_max > 3)); + } + if ((allow_passive && force_passive) || + (!(bssid_bitmap | s_ssid_bitmap) && + !cfg80211_channel_is_psc(params->channels[i]))) + flags |= IWL_UHB_CHAN_CFG_FLAG_FORCE_PASSIVE; + else + flags |= bssid_bitmap | (s_ssid_bitmap << 16); + + cfg->flags |= cpu_to_le32(flags); + ch_cnt++; + } + + if (params->n_channels > ch_cnt) + IWL_DEBUG_SCAN(mvm, + "6GHz: reducing number channels: (%u->%u)\n", + params->n_channels, ch_cnt); + + return ch_cnt; +} + +static u8 iwl_mvm_scan_umac_chan_flags_v2(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif) +{ + u8 flags = 0; + + flags |= IWL_SCAN_CHANNEL_FLAG_ENABLE_CHAN_ORDER; + + if (iwl_mvm_scan_use_ebs(mvm, vif)) + flags |= IWL_SCAN_CHANNEL_FLAG_EBS | + IWL_SCAN_CHANNEL_FLAG_EBS_ACCURATE | + IWL_SCAN_CHANNEL_FLAG_CACHE_ADD; + + /* set fragmented ebs for fragmented scan on HB channels */ + if ((!iwl_mvm_is_cdb_supported(mvm) && + iwl_mvm_is_scan_fragmented(params->type)) || + (iwl_mvm_is_cdb_supported(mvm) && + iwl_mvm_is_scan_fragmented(params->hb_type))) + flags |= IWL_SCAN_CHANNEL_FLAG_EBS_FRAG; + + /* + * force EBS in case the scan is a fragmented and there is a need to take P2P + * GO operation into consideration during scan operation. + */ + if ((!iwl_mvm_is_cdb_supported(mvm) && + iwl_mvm_is_scan_fragmented(params->type) && params->respect_p2p_go) || + (iwl_mvm_is_cdb_supported(mvm) && + iwl_mvm_is_scan_fragmented(params->hb_type) && + params->respect_p2p_go_hb)) { + IWL_DEBUG_SCAN(mvm, "Respect P2P GO. Force EBS\n"); + flags |= IWL_SCAN_CHANNEL_FLAG_FORCE_EBS; + } + + return flags; +} + +static void iwl_mvm_scan_6ghz_passive_scan(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif) +{ + struct ieee80211_supported_band *sband = + &mvm->nvm_data->bands[NL80211_BAND_6GHZ]; + u32 n_disabled, i; + + params->enable_6ghz_passive = false; + + if (params->scan_6ghz) + return; + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_PASSIVE_6GHZ_SCAN)) { + IWL_DEBUG_SCAN(mvm, + "6GHz passive scan: Not supported by FW\n"); + return; + } + + /* 6GHz passive scan allowed only on station interface */ + if (vif->type != NL80211_IFTYPE_STATION) { + IWL_DEBUG_SCAN(mvm, + "6GHz passive scan: not station interface\n"); + return; + } + + /* + * 6GHz passive scan is allowed in a defined time interval following HW + * reset or resume flow, or while not associated and a large interval + * has passed since the last 6GHz passive scan. + */ + if ((vif->cfg.assoc || + time_after(mvm->last_6ghz_passive_scan_jiffies + + (IWL_MVM_6GHZ_PASSIVE_SCAN_TIMEOUT * HZ), jiffies)) && + (time_before(mvm->last_reset_or_resume_time_jiffies + + (IWL_MVM_6GHZ_PASSIVE_SCAN_ASSOC_TIMEOUT * HZ), + jiffies))) { + IWL_DEBUG_SCAN(mvm, "6GHz passive scan: %s\n", + vif->cfg.assoc ? "associated" : + "timeout did not expire"); + return; + } + + /* not enough channels in the regular scan request */ + if (params->n_channels < IWL_MVM_6GHZ_PASSIVE_SCAN_MIN_CHANS) { + IWL_DEBUG_SCAN(mvm, + "6GHz passive scan: not enough channels\n"); + return; + } + + for (i = 0; i < params->n_ssids; i++) { + if (!params->ssids[i].ssid_len) + break; + } + + /* not a wildcard scan, so cannot enable passive 6GHz scan */ + if (i == params->n_ssids) { + IWL_DEBUG_SCAN(mvm, + "6GHz passive scan: no wildcard SSID\n"); + return; + } + + if (!sband || !sband->n_channels) { + IWL_DEBUG_SCAN(mvm, + "6GHz passive scan: no 6GHz channels\n"); + return; + } + + for (i = 0, n_disabled = 0; i < sband->n_channels; i++) { + if (sband->channels[i].flags & (IEEE80211_CHAN_DISABLED)) + n_disabled++; + } + + /* + * Not all the 6GHz channels are disabled, so no need for 6GHz passive + * scan + */ + if (n_disabled != sband->n_channels) { + IWL_DEBUG_SCAN(mvm, + "6GHz passive scan: 6GHz channels enabled\n"); + return; + } + + /* all conditions to enable 6ghz passive scan are satisfied */ + IWL_DEBUG_SCAN(mvm, "6GHz passive scan: can be enabled\n"); + params->enable_6ghz_passive = true; +} + +static u16 iwl_mvm_scan_umac_flags_v2(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif, + int type) +{ + u16 flags = 0; + + /* + * If no direct SSIDs are provided perform a passive scan. Otherwise, + * if there is a single SSID which is not the broadcast SSID, assume + * that the scan is intended for roaming purposes and thus enable Rx on + * all chains to improve chances of hearing the beacons/probe responses. + */ + if (params->n_ssids == 0) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_FORCE_PASSIVE; + else if (params->n_ssids == 1 && params->ssids[0].ssid_len) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_USE_ALL_RX_CHAINS; + + if (iwl_mvm_is_scan_fragmented(params->type)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC1; + + if (iwl_mvm_is_scan_fragmented(params->hb_type)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC2; + + if (params->pass_all) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_PASS_ALL; + else + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_MATCH; + + if (!iwl_mvm_is_regular_scan(params)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_PERIODIC; + + if (params->iter_notif || + mvm->sched_scan_pass_all == SCHED_SCAN_PASS_ALL_ENABLED) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_NTFY_ITER_COMPLETE; + + if (IWL_MVM_ADWELL_ENABLE) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_ADAPTIVE_DWELL; + + if (type == IWL_MVM_SCAN_SCHED || type == IWL_MVM_SCAN_NETDETECT) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_PREEMPTIVE; + + if ((type == IWL_MVM_SCAN_SCHED || type == IWL_MVM_SCAN_NETDETECT) && + params->flags & NL80211_SCAN_FLAG_COLOCATED_6GHZ) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_TRIGGER_UHB_SCAN; + + if (params->enable_6ghz_passive) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_6GHZ_PASSIVE_SCAN; + + if (iwl_mvm_is_oce_supported(mvm) && + (params->flags & (NL80211_SCAN_FLAG_ACCEPT_BCAST_PROBE_RESP | + NL80211_SCAN_FLAG_OCE_PROBE_REQ_HIGH_TX_RATE | + NL80211_SCAN_FLAG_FILS_MAX_CHANNEL_TIME))) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_V2_OCE; + + return flags; +} + +static u8 iwl_mvm_scan_umac_flags2(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif, int type) +{ + u8 flags = 0; + + if (iwl_mvm_is_cdb_supported(mvm)) { + if (params->respect_p2p_go) + flags |= IWL_UMAC_SCAN_GEN_PARAMS_FLAGS2_RESPECT_P2P_GO_LB; + if (params->respect_p2p_go_hb) + flags |= IWL_UMAC_SCAN_GEN_PARAMS_FLAGS2_RESPECT_P2P_GO_HB; + } else { + if (params->respect_p2p_go) + flags = IWL_UMAC_SCAN_GEN_PARAMS_FLAGS2_RESPECT_P2P_GO_LB | + IWL_UMAC_SCAN_GEN_PARAMS_FLAGS2_RESPECT_P2P_GO_HB; + } + + return flags; +} + +static u16 iwl_mvm_scan_umac_flags(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif) +{ + u16 flags = 0; + + if (params->n_ssids == 0) + flags = IWL_UMAC_SCAN_GEN_FLAGS_PASSIVE; + + if (params->n_ssids == 1 && params->ssids[0].ssid_len != 0) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_PRE_CONNECT; + + if (iwl_mvm_is_scan_fragmented(params->type)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_FRAGMENTED; + + if (iwl_mvm_is_cdb_supported(mvm) && + iwl_mvm_is_scan_fragmented(params->hb_type)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_LMAC2_FRAGMENTED; + + if (iwl_mvm_rrm_scan_needed(mvm) && + fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_WFA_TPC_REP_IE_SUPPORT)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_RRM_ENABLED; + + if (params->pass_all) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_PASS_ALL; + else + flags |= IWL_UMAC_SCAN_GEN_FLAGS_MATCH; + + if (!iwl_mvm_is_regular_scan(params)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_PERIODIC; + + if (params->iter_notif) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_ITER_COMPLETE; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + if (mvm->scan_iter_notif_enabled) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_ITER_COMPLETE; +#endif + + if (mvm->sched_scan_pass_all == SCHED_SCAN_PASS_ALL_ENABLED) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_ITER_COMPLETE; + + if (iwl_mvm_is_adaptive_dwell_supported(mvm) && IWL_MVM_ADWELL_ENABLE) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_ADAPTIVE_DWELL; + + /* + * Extended dwell is relevant only for low band to start with, as it is + * being used for social channles only (1, 6, 11), so we can check + * only scan type on low band also for CDB. + */ + if (iwl_mvm_is_regular_scan(params) && + vif->type != NL80211_IFTYPE_P2P_DEVICE && + !iwl_mvm_is_scan_fragmented(params->type) && + !iwl_mvm_is_adaptive_dwell_supported(mvm) && + !iwl_mvm_is_oce_supported(mvm)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_EXTENDED_DWELL; + + if (iwl_mvm_is_oce_supported(mvm)) { + if ((params->flags & + NL80211_SCAN_FLAG_OCE_PROBE_REQ_HIGH_TX_RATE)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_PROB_REQ_HIGH_TX_RATE; + /* Since IWL_UMAC_SCAN_GEN_FLAGS_EXTENDED_DWELL and + * NL80211_SCAN_FLAG_OCE_PROBE_REQ_DEFERRAL_SUPPRESSION shares + * the same bit, we need to make sure that we use this bit here + * only when IWL_UMAC_SCAN_GEN_FLAGS_EXTENDED_DWELL cannot be + * used. */ + if ((params->flags & + NL80211_SCAN_FLAG_OCE_PROBE_REQ_DEFERRAL_SUPPRESSION) && + !WARN_ON_ONCE(!iwl_mvm_is_adaptive_dwell_supported(mvm))) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_PROB_REQ_DEFER_SUPP; + if ((params->flags & NL80211_SCAN_FLAG_FILS_MAX_CHANNEL_TIME)) + flags |= IWL_UMAC_SCAN_GEN_FLAGS_MAX_CHNL_TIME; + } + + return flags; +} + +static int +iwl_mvm_fill_scan_sched_params(struct iwl_mvm_scan_params *params, + struct iwl_scan_umac_schedule *schedule, + __le16 *delay) +{ + int i; + if (WARN_ON(!params->n_scan_plans || + params->n_scan_plans > IWL_MAX_SCHED_SCAN_PLANS)) + return -EINVAL; + + for (i = 0; i < params->n_scan_plans; i++) { + struct cfg80211_sched_scan_plan *scan_plan = + ¶ms->scan_plans[i]; + + schedule[i].iter_count = scan_plan->iterations; + schedule[i].interval = + cpu_to_le16(scan_plan->interval); + } + + /* + * If the number of iterations of the last scan plan is set to + * zero, it should run infinitely. However, this is not always the case. + * For example, when regular scan is requested the driver sets one scan + * plan with one iteration. + */ + if (!schedule[params->n_scan_plans - 1].iter_count) + schedule[params->n_scan_plans - 1].iter_count = 0xff; + + *delay = cpu_to_le16(params->delay); + + return 0; +} + +static int iwl_mvm_scan_umac(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_scan_params *params, + int type, int uid) +{ + struct iwl_scan_req_umac *cmd = mvm->scan_cmd; + struct iwl_scan_umac_chan_param *chan_param; + void *cmd_data = iwl_mvm_get_scan_req_umac_data(mvm); + void *sec_part = (u8 *)cmd_data + sizeof(struct iwl_scan_channel_cfg_umac) * + mvm->fw->ucode_capa.n_scan_channels; + struct iwl_scan_req_umac_tail_v2 *tail_v2 = + (struct iwl_scan_req_umac_tail_v2 *)sec_part; + struct iwl_scan_req_umac_tail_v1 *tail_v1; + struct iwl_ssid_ie *direct_scan; + int ret = 0; + u32 ssid_bitmap = 0; + u8 channel_flags = 0; + u16 gen_flags; + struct iwl_mvm_vif *scan_vif = iwl_mvm_vif_from_mac80211(vif); + + chan_param = iwl_mvm_get_scan_req_umac_channel(mvm); + + iwl_mvm_scan_umac_dwell(mvm, cmd, params); + + mvm->scan_uid_status[uid] = type; + + cmd->uid = cpu_to_le32(uid); + gen_flags = iwl_mvm_scan_umac_flags(mvm, params, vif); + cmd->general_flags = cpu_to_le16(gen_flags); + if (iwl_mvm_is_adaptive_dwell_v2_supported(mvm)) { + if (gen_flags & IWL_UMAC_SCAN_GEN_FLAGS_FRAGMENTED) + cmd->v8.num_of_fragments[SCAN_LB_LMAC_IDX] = + IWL_SCAN_NUM_OF_FRAGS; + if (gen_flags & IWL_UMAC_SCAN_GEN_FLAGS_LMAC2_FRAGMENTED) + cmd->v8.num_of_fragments[SCAN_HB_LMAC_IDX] = + IWL_SCAN_NUM_OF_FRAGS; + + cmd->v8.general_flags2 = + IWL_UMAC_SCAN_GEN_FLAGS2_ALLOW_CHNL_REORDER; + } + + cmd->scan_start_mac_id = scan_vif->id; + + if (type == IWL_MVM_SCAN_SCHED || type == IWL_MVM_SCAN_NETDETECT) + cmd->flags = cpu_to_le32(IWL_UMAC_SCAN_FLAG_PREEMPTIVE); + + if (iwl_mvm_scan_use_ebs(mvm, vif)) { + channel_flags = IWL_SCAN_CHANNEL_FLAG_EBS | + IWL_SCAN_CHANNEL_FLAG_EBS_ACCURATE | + IWL_SCAN_CHANNEL_FLAG_CACHE_ADD; + + /* set fragmented ebs for fragmented scan on HB channels */ + if (iwl_mvm_is_frag_ebs_supported(mvm)) { + if (gen_flags & + IWL_UMAC_SCAN_GEN_FLAGS_LMAC2_FRAGMENTED || + (!iwl_mvm_is_cdb_supported(mvm) && + gen_flags & IWL_UMAC_SCAN_GEN_FLAGS_FRAGMENTED)) + channel_flags |= IWL_SCAN_CHANNEL_FLAG_EBS_FRAG; + } + } + + chan_param->flags = channel_flags; + chan_param->count = params->n_channels; + + ret = iwl_mvm_fill_scan_sched_params(params, tail_v2->schedule, + &tail_v2->delay); + if (ret) { + mvm->scan_uid_status[uid] = 0; + return ret; + } + + if (iwl_mvm_is_scan_ext_chan_supported(mvm)) { + tail_v2->preq = params->preq; + direct_scan = tail_v2->direct_scan; + } else { + tail_v1 = (struct iwl_scan_req_umac_tail_v1 *)sec_part; + iwl_mvm_scan_set_legacy_probe_req(&tail_v1->preq, + ¶ms->preq); + direct_scan = tail_v1->direct_scan; + } + iwl_scan_build_ssids(params, direct_scan, &ssid_bitmap); + iwl_mvm_umac_scan_cfg_channels(mvm, params->channels, + params->n_channels, ssid_bitmap, + cmd_data); + return 0; +} + +static void +iwl_mvm_scan_umac_fill_general_p_v11(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif, + struct iwl_scan_general_params_v11 *gp, + u16 gen_flags, u8 gen_flags2) +{ + struct iwl_mvm_vif *scan_vif = iwl_mvm_vif_from_mac80211(vif); + + iwl_mvm_scan_umac_dwell_v11(mvm, gp, params); + + IWL_DEBUG_SCAN(mvm, "Gerenal: flags=0x%x, flags2=0x%x\n", + gen_flags, gen_flags2); + + gp->flags = cpu_to_le16(gen_flags); + gp->flags2 = gen_flags2; + + if (gen_flags & IWL_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC1) + gp->num_of_fragments[SCAN_LB_LMAC_IDX] = IWL_SCAN_NUM_OF_FRAGS; + if (gen_flags & IWL_UMAC_SCAN_GEN_FLAGS_V2_FRAGMENTED_LMAC2) + gp->num_of_fragments[SCAN_HB_LMAC_IDX] = IWL_SCAN_NUM_OF_FRAGS; + + gp->scan_start_mac_id = scan_vif->id; +} + +static void +iwl_mvm_scan_umac_fill_probe_p_v3(struct iwl_mvm_scan_params *params, + struct iwl_scan_probe_params_v3 *pp) +{ + pp->preq = params->preq; + pp->ssid_num = params->n_ssids; + iwl_scan_build_ssids(params, pp->direct_scan, NULL); +} + +static void +iwl_mvm_scan_umac_fill_probe_p_v4(struct iwl_mvm_scan_params *params, + struct iwl_scan_probe_params_v4 *pp, + u32 *bitmap_ssid) +{ + pp->preq = params->preq; + iwl_scan_build_ssids(params, pp->direct_scan, bitmap_ssid); +} + +static void +iwl_mvm_scan_umac_fill_ch_p_v4(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif, + struct iwl_scan_channel_params_v4 *cp, + u32 channel_cfg_flags) +{ + cp->flags = iwl_mvm_scan_umac_chan_flags_v2(mvm, params, vif); + cp->count = params->n_channels; + cp->num_of_aps_override = IWL_SCAN_ADWELL_N_APS_GO_FRIENDLY; + + iwl_mvm_umac_scan_cfg_channels_v4(mvm, params->channels, cp, + params->n_channels, + channel_cfg_flags, + vif->type); +} + +static void +iwl_mvm_scan_umac_fill_ch_p_v6(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif, + struct iwl_scan_channel_params_v6 *cp, + u32 channel_cfg_flags) +{ + cp->flags = iwl_mvm_scan_umac_chan_flags_v2(mvm, params, vif); + cp->count = params->n_channels; + cp->n_aps_override[0] = IWL_SCAN_ADWELL_N_APS_GO_FRIENDLY; + cp->n_aps_override[1] = IWL_SCAN_ADWELL_N_APS_SOCIAL_CHS; + + iwl_mvm_umac_scan_cfg_channels_v6(mvm, params->channels, cp, + params->n_channels, + channel_cfg_flags, + vif->type); + + if (params->enable_6ghz_passive) { + struct ieee80211_supported_band *sband = + &mvm->nvm_data->bands[NL80211_BAND_6GHZ]; + u32 i; + + for (i = 0; i < sband->n_channels; i++) { + struct ieee80211_channel *channel = + &sband->channels[i]; + + struct iwl_scan_channel_cfg_umac *cfg = + &cp->channel_config[cp->count]; + + if (!cfg80211_channel_is_psc(channel)) + continue; + + cfg->flags = 0; + cfg->v2.channel_num = channel->hw_value; + cfg->v2.band = PHY_BAND_6; + cfg->v2.iter_count = 1; + cfg->v2.iter_interval = 0; + cp->count++; + } + } +} + +static int iwl_mvm_scan_umac_v12(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_scan_params *params, int type, + int uid) +{ + struct iwl_scan_req_umac_v12 *cmd = mvm->scan_cmd; + struct iwl_scan_req_params_v12 *scan_p = &cmd->scan_params; + int ret; + u16 gen_flags; + + mvm->scan_uid_status[uid] = type; + + cmd->ooc_priority = cpu_to_le32(iwl_mvm_scan_umac_ooc_priority(params)); + cmd->uid = cpu_to_le32(uid); + + gen_flags = iwl_mvm_scan_umac_flags_v2(mvm, params, vif, type); + iwl_mvm_scan_umac_fill_general_p_v11(mvm, params, vif, + &scan_p->general_params, + gen_flags, 0); + + ret = iwl_mvm_fill_scan_sched_params(params, + scan_p->periodic_params.schedule, + &scan_p->periodic_params.delay); + if (ret) + return ret; + + iwl_mvm_scan_umac_fill_probe_p_v3(params, &scan_p->probe_params); + iwl_mvm_scan_umac_fill_ch_p_v4(mvm, params, vif, + &scan_p->channel_params, 0); + + return 0; +} + +static int iwl_mvm_scan_umac_v14_and_above(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mvm_scan_params *params, + int type, int uid, u32 version) +{ + struct iwl_scan_req_umac_v15 *cmd = mvm->scan_cmd; + struct iwl_scan_req_params_v15 *scan_p = &cmd->scan_params; + struct iwl_scan_channel_params_v6 *cp = &scan_p->channel_params; + struct iwl_scan_probe_params_v4 *pb = &scan_p->probe_params; + int ret; + u16 gen_flags; + u8 gen_flags2; + u32 bitmap_ssid = 0; + + mvm->scan_uid_status[uid] = type; + + cmd->ooc_priority = cpu_to_le32(iwl_mvm_scan_umac_ooc_priority(params)); + cmd->uid = cpu_to_le32(uid); + + gen_flags = iwl_mvm_scan_umac_flags_v2(mvm, params, vif, type); + + if (version >= 15) + gen_flags2 = iwl_mvm_scan_umac_flags2(mvm, params, vif, type); + else + gen_flags2 = 0; + + iwl_mvm_scan_umac_fill_general_p_v11(mvm, params, vif, + &scan_p->general_params, + gen_flags, gen_flags2); + + ret = iwl_mvm_fill_scan_sched_params(params, + scan_p->periodic_params.schedule, + &scan_p->periodic_params.delay); + if (ret) + return ret; + + if (!params->scan_6ghz) { + iwl_mvm_scan_umac_fill_probe_p_v4(params, &scan_p->probe_params, + &bitmap_ssid); + iwl_mvm_scan_umac_fill_ch_p_v6(mvm, params, vif, + &scan_p->channel_params, bitmap_ssid); + + return 0; + } else { + pb->preq = params->preq; + } + + cp->flags = iwl_mvm_scan_umac_chan_flags_v2(mvm, params, vif); + cp->n_aps_override[0] = IWL_SCAN_ADWELL_N_APS_GO_FRIENDLY; + cp->n_aps_override[1] = IWL_SCAN_ADWELL_N_APS_SOCIAL_CHS; + + iwl_mvm_umac_scan_fill_6g_chan_list(mvm, params, pb); + + cp->count = iwl_mvm_umac_scan_cfg_channels_v6_6g(mvm, params, + params->n_channels, + pb, cp, vif->type); + if (!cp->count) { + mvm->scan_uid_status[uid] = 0; + return -EINVAL; + } + + if (!params->n_ssids || + (params->n_ssids == 1 && !params->ssids[0].ssid_len)) + cp->flags |= IWL_SCAN_CHANNEL_FLAG_6G_PSC_NO_FILTER; + + return 0; +} + +static int iwl_mvm_scan_umac_v14(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_scan_params *params, int type, + int uid) +{ + return iwl_mvm_scan_umac_v14_and_above(mvm, vif, params, type, uid, 14); +} + +static int iwl_mvm_scan_umac_v15(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_scan_params *params, int type, + int uid) +{ + return iwl_mvm_scan_umac_v14_and_above(mvm, vif, params, type, uid, 15); +} + +static int iwl_mvm_num_scans(struct iwl_mvm *mvm) +{ + return hweight32(mvm->scan_status & IWL_MVM_SCAN_MASK); +} + +static int iwl_mvm_check_running_scans(struct iwl_mvm *mvm, int type) +{ + bool unified_image = fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG); + + /* This looks a bit arbitrary, but the idea is that if we run + * out of possible simultaneous scans and the userspace is + * trying to run a scan type that is already running, we + * return -EBUSY. But if the userspace wants to start a + * different type of scan, we stop the opposite type to make + * space for the new request. The reason is backwards + * compatibility with old wpa_supplicant that wouldn't stop a + * scheduled scan before starting a normal scan. + */ + + /* FW supports only a single periodic scan */ + if ((type == IWL_MVM_SCAN_SCHED || type == IWL_MVM_SCAN_NETDETECT) && + mvm->scan_status & (IWL_MVM_SCAN_SCHED | IWL_MVM_SCAN_NETDETECT)) + return -EBUSY; + + if (iwl_mvm_num_scans(mvm) < mvm->max_scans) + return 0; + + /* Use a switch, even though this is a bitmask, so that more + * than one bits set will fall in default and we will warn. + */ + switch (type) { + case IWL_MVM_SCAN_REGULAR: + if (mvm->scan_status & IWL_MVM_SCAN_REGULAR_MASK) + return -EBUSY; + return iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_SCHED, true); + case IWL_MVM_SCAN_SCHED: + if (mvm->scan_status & IWL_MVM_SCAN_SCHED_MASK) + return -EBUSY; + return iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_REGULAR, true); + case IWL_MVM_SCAN_NETDETECT: + /* For non-unified images, there's no need to stop + * anything for net-detect since the firmware is + * restarted anyway. This way, any sched scans that + * were running will be restarted when we resume. + */ + if (!unified_image) + return 0; + + /* If this is a unified image and we ran out of scans, + * we need to stop something. Prefer stopping regular + * scans, because the results are useless at this + * point, and we should be able to keep running + * another scheduled scan while suspended. + */ + if (mvm->scan_status & IWL_MVM_SCAN_REGULAR_MASK) + return iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_REGULAR, + true); + if (mvm->scan_status & IWL_MVM_SCAN_SCHED_MASK) + return iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_SCHED, + true); + /* Something is wrong if no scan was running but we + * ran out of scans. + */ + fallthrough; + default: + WARN_ON(1); + break; + } + + return -EIO; +} + +#define SCAN_TIMEOUT 30000 + +void iwl_mvm_scan_timeout_wk(struct work_struct *work) +{ + struct delayed_work *delayed_work = to_delayed_work(work); + struct iwl_mvm *mvm = container_of(delayed_work, struct iwl_mvm, + scan_timeout_dwork); + + IWL_ERR(mvm, "regular scan timed out\n"); + + iwl_force_nmi(mvm->trans); +} + +static void iwl_mvm_fill_scan_type(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif) +{ + if (iwl_mvm_is_cdb_supported(mvm)) { + params->type = + iwl_mvm_get_scan_type_band(mvm, vif, + NL80211_BAND_2GHZ); + params->hb_type = + iwl_mvm_get_scan_type_band(mvm, vif, + NL80211_BAND_5GHZ); + } else { + params->type = iwl_mvm_get_scan_type(mvm, vif); + } +} + +struct iwl_scan_umac_handler { + u8 version; + int (*handler)(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_scan_params *params, int type, int uid); +}; + +#define IWL_SCAN_UMAC_HANDLER(_ver) { \ + .version = _ver, \ + .handler = iwl_mvm_scan_umac_v##_ver, \ +} + +static const struct iwl_scan_umac_handler iwl_scan_umac_handlers[] = { + /* set the newest version first to shorten the list traverse time */ + IWL_SCAN_UMAC_HANDLER(15), + IWL_SCAN_UMAC_HANDLER(14), + IWL_SCAN_UMAC_HANDLER(12), +}; + +static int iwl_mvm_build_scan_cmd(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_host_cmd *hcmd, + struct iwl_mvm_scan_params *params, + int type) +{ + int uid, i, err; + u8 scan_ver; + + lockdep_assert_held(&mvm->mutex); + memset(mvm->scan_cmd, 0, mvm->scan_cmd_size); + + if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) { + hcmd->id = SCAN_OFFLOAD_REQUEST_CMD; + + return iwl_mvm_scan_lmac(mvm, vif, params); + } + + uid = iwl_mvm_scan_uid_by_status(mvm, 0); + if (uid < 0) + return uid; + + hcmd->id = WIDE_ID(IWL_ALWAYS_LONG_GROUP, SCAN_REQ_UMAC); + + scan_ver = iwl_fw_lookup_cmd_ver(mvm->fw, SCAN_REQ_UMAC, + IWL_FW_CMD_VER_UNKNOWN); + + for (i = 0; i < ARRAY_SIZE(iwl_scan_umac_handlers); i++) { + const struct iwl_scan_umac_handler *ver_handler = + &iwl_scan_umac_handlers[i]; + + if (ver_handler->version != scan_ver) + continue; + + return ver_handler->handler(mvm, vif, params, type, uid); + } + + err = iwl_mvm_scan_umac(mvm, vif, params, type, uid); + if (err) + return err; + + return uid; +} + +struct iwl_mvm_scan_respect_p2p_go_iter_data { + struct ieee80211_vif *current_vif; + bool p2p_go; + enum nl80211_band band; +}; + +static void iwl_mvm_scan_respect_p2p_go_iter(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_scan_respect_p2p_go_iter_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + /* exclude the given vif */ + if (vif == data->current_vif) + return; + + if (vif->type == NL80211_IFTYPE_AP && vif->p2p && + mvmvif->phy_ctxt->id < NUM_PHY_CTX && + (data->band == NUM_NL80211_BANDS || + mvmvif->phy_ctxt->channel->band == data->band)) + data->p2p_go = true; +} + +static bool _iwl_mvm_get_respect_p2p_go(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + bool low_latency, + enum nl80211_band band) +{ + struct iwl_mvm_scan_respect_p2p_go_iter_data data = { + .current_vif = vif, + .p2p_go = false, + .band = band, + }; + + if (!low_latency) + return false; + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_scan_respect_p2p_go_iter, + &data); + + return data.p2p_go; +} + +static bool iwl_mvm_get_respect_p2p_go_band(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + enum nl80211_band band) +{ + bool low_latency = iwl_mvm_low_latency_band(mvm, band); + + return _iwl_mvm_get_respect_p2p_go(mvm, vif, low_latency, band); +} + +static bool iwl_mvm_get_respect_p2p_go(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + bool low_latency = iwl_mvm_low_latency(mvm); + + return _iwl_mvm_get_respect_p2p_go(mvm, vif, low_latency, + NUM_NL80211_BANDS); +} + +static void iwl_mvm_fill_respect_p2p_go(struct iwl_mvm *mvm, + struct iwl_mvm_scan_params *params, + struct ieee80211_vif *vif) +{ + if (iwl_mvm_is_cdb_supported(mvm)) { + params->respect_p2p_go = + iwl_mvm_get_respect_p2p_go_band(mvm, vif, + NL80211_BAND_2GHZ); + params->respect_p2p_go_hb = + iwl_mvm_get_respect_p2p_go_band(mvm, vif, + NL80211_BAND_5GHZ); + } else { + params->respect_p2p_go = iwl_mvm_get_respect_p2p_go(mvm, vif); + } +} + +int iwl_mvm_reg_scan_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct cfg80211_scan_request *req, + struct ieee80211_scan_ies *ies) +{ + struct iwl_host_cmd hcmd = { + .len = { iwl_mvm_scan_size(mvm), }, + .data = { mvm->scan_cmd, }, + .dataflags = { IWL_HCMD_DFL_NOCOPY, }, + }; + struct iwl_mvm_scan_params params = {}; + int ret, uid; + struct cfg80211_sched_scan_plan scan_plan = { .iterations = 1 }; + + lockdep_assert_held(&mvm->mutex); + + if (iwl_mvm_is_lar_supported(mvm) && !mvm->lar_regdom_set) { + IWL_ERR(mvm, "scan while LAR regdomain is not set\n"); + return -EBUSY; + } + + ret = iwl_mvm_check_running_scans(mvm, IWL_MVM_SCAN_REGULAR); + if (ret) + return ret; + + /* we should have failed registration if scan_cmd was NULL */ + if (WARN_ON(!mvm->scan_cmd)) + return -ENOMEM; + + if (!iwl_mvm_scan_fits(mvm, req->n_ssids, ies, req->n_channels)) + return -ENOBUFS; + + params.n_ssids = req->n_ssids; + params.flags = req->flags; + params.n_channels = req->n_channels; + params.delay = 0; + params.ssids = req->ssids; + params.channels = req->channels; + params.mac_addr = req->mac_addr; + params.mac_addr_mask = req->mac_addr_mask; + params.no_cck = req->no_cck; + params.pass_all = true; + params.n_match_sets = 0; + params.match_sets = NULL; + + params.scan_plans = &scan_plan; + params.n_scan_plans = 1; + + params.n_6ghz_params = req->n_6ghz_params; + params.scan_6ghz_params = req->scan_6ghz_params; + params.scan_6ghz = req->scan_6ghz; + iwl_mvm_fill_scan_type(mvm, ¶ms, vif); + iwl_mvm_fill_respect_p2p_go(mvm, ¶ms, vif); + + if (req->duration) + params.iter_notif = true; + + iwl_mvm_build_scan_probe(mvm, vif, ies, ¶ms); + + iwl_mvm_scan_6ghz_passive_scan(mvm, ¶ms, vif); + + uid = iwl_mvm_build_scan_cmd(mvm, vif, &hcmd, ¶ms, + IWL_MVM_SCAN_REGULAR); + + if (uid < 0) + return uid; + + iwl_mvm_pause_tcm(mvm, false); + + ret = iwl_mvm_send_cmd(mvm, &hcmd); + if (ret) { + /* If the scan failed, it usually means that the FW was unable + * to allocate the time events. Warn on it, but maybe we + * should try to send the command again with different params. + */ + IWL_ERR(mvm, "Scan failed! ret %d\n", ret); + iwl_mvm_resume_tcm(mvm); + mvm->scan_uid_status[uid] = 0; + return ret; + } + + IWL_DEBUG_SCAN(mvm, "Scan request was sent successfully\n"); + mvm->scan_status |= IWL_MVM_SCAN_REGULAR; + mvm->scan_vif = iwl_mvm_vif_from_mac80211(vif); + + if (params.enable_6ghz_passive) + mvm->last_6ghz_passive_scan_jiffies = jiffies; + + schedule_delayed_work(&mvm->scan_timeout_dwork, + msecs_to_jiffies(SCAN_TIMEOUT)); + + return 0; +} + +int iwl_mvm_sched_scan_start(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_scan_ies *ies, + int type) +{ + struct iwl_host_cmd hcmd = { + .len = { iwl_mvm_scan_size(mvm), }, + .data = { mvm->scan_cmd, }, + .dataflags = { IWL_HCMD_DFL_NOCOPY, }, + }; + struct iwl_mvm_scan_params params = {}; + int ret, uid; + int i, j; + bool non_psc_included = false; + + lockdep_assert_held(&mvm->mutex); + + if (iwl_mvm_is_lar_supported(mvm) && !mvm->lar_regdom_set) { + IWL_ERR(mvm, "sched-scan while LAR regdomain is not set\n"); + return -EBUSY; + } + + ret = iwl_mvm_check_running_scans(mvm, type); + if (ret) + return ret; + + /* we should have failed registration if scan_cmd was NULL */ + if (WARN_ON(!mvm->scan_cmd)) + return -ENOMEM; + + + params.n_ssids = req->n_ssids; + params.flags = req->flags; + params.n_channels = req->n_channels; + params.ssids = req->ssids; + params.channels = req->channels; + params.mac_addr = req->mac_addr; + params.mac_addr_mask = req->mac_addr_mask; + params.no_cck = false; + params.pass_all = iwl_mvm_scan_pass_all(mvm, req); + params.n_match_sets = req->n_match_sets; + params.match_sets = req->match_sets; + if (!req->n_scan_plans) + return -EINVAL; + + params.n_scan_plans = req->n_scan_plans; + params.scan_plans = req->scan_plans; + + iwl_mvm_fill_scan_type(mvm, ¶ms, vif); + iwl_mvm_fill_respect_p2p_go(mvm, ¶ms, vif); + + /* In theory, LMAC scans can handle a 32-bit delay, but since + * waiting for over 18 hours to start the scan is a bit silly + * and to keep it aligned with UMAC scans (which only support + * 16-bit delays), trim it down to 16-bits. + */ + if (req->delay > U16_MAX) { + IWL_DEBUG_SCAN(mvm, + "delay value is > 16-bits, set to max possible\n"); + params.delay = U16_MAX; + } else { + params.delay = req->delay; + } + + ret = iwl_mvm_config_sched_scan_profiles(mvm, req); + if (ret) + return ret; + + iwl_mvm_build_scan_probe(mvm, vif, ies, ¶ms); + + /* for 6 GHZ band only PSC channels need to be added */ + for (i = 0; i < params.n_channels; i++) { + struct ieee80211_channel *channel = params.channels[i]; + + if (channel->band == NL80211_BAND_6GHZ && + !cfg80211_channel_is_psc(channel)) { + non_psc_included = true; + break; + } + } + + if (non_psc_included) { + params.channels = kmemdup(params.channels, + sizeof(params.channels[0]) * + params.n_channels, + GFP_KERNEL); + if (!params.channels) + return -ENOMEM; + + for (i = j = 0; i < params.n_channels; i++) { + if (params.channels[i]->band == NL80211_BAND_6GHZ && + !cfg80211_channel_is_psc(params.channels[i])) + continue; + params.channels[j++] = params.channels[i]; + } + params.n_channels = j; + } + + if (non_psc_included && + !iwl_mvm_scan_fits(mvm, req->n_ssids, ies, params.n_channels)) { + kfree(params.channels); + return -ENOBUFS; + } + + uid = iwl_mvm_build_scan_cmd(mvm, vif, &hcmd, ¶ms, type); + + if (non_psc_included) + kfree(params.channels); + if (uid < 0) + return uid; + + ret = iwl_mvm_send_cmd(mvm, &hcmd); + if (!ret) { + IWL_DEBUG_SCAN(mvm, + "Sched scan request was sent successfully\n"); + mvm->scan_status |= type; + } else { + /* If the scan failed, it usually means that the FW was unable + * to allocate the time events. Warn on it, but maybe we + * should try to send the command again with different params. + */ + IWL_ERR(mvm, "Sched scan failed! ret %d\n", ret); + mvm->scan_uid_status[uid] = 0; + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_DISABLED; + } + + return ret; +} + +void iwl_mvm_rx_umac_scan_complete_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_umac_scan_complete *notif = (void *)pkt->data; + u32 uid = __le32_to_cpu(notif->uid); + bool aborted = (notif->status == IWL_SCAN_OFFLOAD_ABORTED); + + if (WARN_ON(!(mvm->scan_uid_status[uid] & mvm->scan_status))) + return; + + /* if the scan is already stopping, we don't need to notify mac80211 */ + if (mvm->scan_uid_status[uid] == IWL_MVM_SCAN_REGULAR) { + struct cfg80211_scan_info info = { + .aborted = aborted, + .scan_start_tsf = mvm->scan_start, + }; + + memcpy(info.tsf_bssid, mvm->scan_vif->bssid, ETH_ALEN); + ieee80211_scan_completed(mvm->hw, &info); + mvm->scan_vif = NULL; + cancel_delayed_work(&mvm->scan_timeout_dwork); + iwl_mvm_resume_tcm(mvm); + } else if (mvm->scan_uid_status[uid] == IWL_MVM_SCAN_SCHED) { + ieee80211_sched_scan_stopped(mvm->hw); + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_DISABLED; + } + + mvm->scan_status &= ~mvm->scan_uid_status[uid]; + IWL_DEBUG_SCAN(mvm, + "Scan completed, uid %u type %u, status %s, EBS status %s\n", + uid, mvm->scan_uid_status[uid], + notif->status == IWL_SCAN_OFFLOAD_COMPLETED ? + "completed" : "aborted", + iwl_mvm_ebs_status_str(notif->ebs_status)); + IWL_DEBUG_SCAN(mvm, + "Last line %d, Last iteration %d, Time from last iteration %d\n", + notif->last_schedule, notif->last_iter, + __le32_to_cpu(notif->time_from_last_iter)); + + if (notif->ebs_status != IWL_SCAN_EBS_SUCCESS && + notif->ebs_status != IWL_SCAN_EBS_INACTIVE) + mvm->last_ebs_successful = false; + + mvm->scan_uid_status[uid] = 0; +} + +void iwl_mvm_rx_umac_scan_iter_complete_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_umac_scan_iter_complete_notif *notif = (void *)pkt->data; + + mvm->scan_start = le64_to_cpu(notif->start_tsf); + + IWL_DEBUG_SCAN(mvm, + "UMAC Scan iteration complete: status=0x%x scanned_channels=%d\n", + notif->status, notif->scanned_channels); + + if (mvm->sched_scan_pass_all == SCHED_SCAN_PASS_ALL_FOUND) { + IWL_DEBUG_SCAN(mvm, "Pass all scheduled scan results found\n"); + ieee80211_sched_scan_results(mvm->hw); + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_ENABLED; + } + + IWL_DEBUG_SCAN(mvm, + "UMAC Scan iteration complete: scan started at %llu (TSF)\n", + mvm->scan_start); +} + +static int iwl_mvm_umac_scan_abort(struct iwl_mvm *mvm, int type) +{ + struct iwl_umac_scan_abort cmd = {}; + int uid, ret; + + lockdep_assert_held(&mvm->mutex); + + /* We should always get a valid index here, because we already + * checked that this type of scan was running in the generic + * code. + */ + uid = iwl_mvm_scan_uid_by_status(mvm, type); + if (WARN_ON_ONCE(uid < 0)) + return uid; + + cmd.uid = cpu_to_le32(uid); + + IWL_DEBUG_SCAN(mvm, "Sending scan abort, uid %u\n", uid); + + ret = iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(IWL_ALWAYS_LONG_GROUP, SCAN_ABORT_UMAC), + 0, sizeof(cmd), &cmd); + if (!ret) + mvm->scan_uid_status[uid] = type << IWL_MVM_SCAN_STOPPING_SHIFT; + + return ret; +} + +static int iwl_mvm_scan_stop_wait(struct iwl_mvm *mvm, int type) +{ + struct iwl_notification_wait wait_scan_done; + static const u16 scan_done_notif[] = { SCAN_COMPLETE_UMAC, + SCAN_OFFLOAD_COMPLETE, }; + int ret; + + lockdep_assert_held(&mvm->mutex); + + iwl_init_notification_wait(&mvm->notif_wait, &wait_scan_done, + scan_done_notif, + ARRAY_SIZE(scan_done_notif), + NULL, NULL); + + IWL_DEBUG_SCAN(mvm, "Preparing to stop scan, type %x\n", type); + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) + ret = iwl_mvm_umac_scan_abort(mvm, type); + else + ret = iwl_mvm_lmac_scan_abort(mvm); + + if (ret) { + IWL_DEBUG_SCAN(mvm, "couldn't stop scan type %d\n", type); + iwl_remove_notification(&mvm->notif_wait, &wait_scan_done); + return ret; + } + + return iwl_wait_notification(&mvm->notif_wait, &wait_scan_done, + 1 * HZ); +} + +static size_t iwl_scan_req_umac_get_size(u8 scan_ver) +{ + switch (scan_ver) { + case 12: + return sizeof(struct iwl_scan_req_umac_v12); + case 14: + case 15: + return sizeof(struct iwl_scan_req_umac_v15); + } + + return 0; +} + +size_t iwl_mvm_scan_size(struct iwl_mvm *mvm) +{ + int base_size, tail_size; + u8 scan_ver = iwl_fw_lookup_cmd_ver(mvm->fw, SCAN_REQ_UMAC, + IWL_FW_CMD_VER_UNKNOWN); + + base_size = iwl_scan_req_umac_get_size(scan_ver); + if (base_size) + return base_size; + + + if (iwl_mvm_is_adaptive_dwell_v2_supported(mvm)) + base_size = IWL_SCAN_REQ_UMAC_SIZE_V8; + else if (iwl_mvm_is_adaptive_dwell_supported(mvm)) + base_size = IWL_SCAN_REQ_UMAC_SIZE_V7; + else if (iwl_mvm_cdb_scan_api(mvm)) + base_size = IWL_SCAN_REQ_UMAC_SIZE_V6; + else + base_size = IWL_SCAN_REQ_UMAC_SIZE_V1; + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) { + if (iwl_mvm_is_scan_ext_chan_supported(mvm)) + tail_size = sizeof(struct iwl_scan_req_umac_tail_v2); + else + tail_size = sizeof(struct iwl_scan_req_umac_tail_v1); + + return base_size + + sizeof(struct iwl_scan_channel_cfg_umac) * + mvm->fw->ucode_capa.n_scan_channels + + tail_size; + } + return sizeof(struct iwl_scan_req_lmac) + + sizeof(struct iwl_scan_channel_cfg_lmac) * + mvm->fw->ucode_capa.n_scan_channels + + sizeof(struct iwl_scan_probe_req_v1); +} + +/* + * This function is used in nic restart flow, to inform mac80211 about scans + * that was aborted by restart flow or by an assert. + */ +void iwl_mvm_report_scan_aborted(struct iwl_mvm *mvm) +{ + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) { + int uid, i; + + uid = iwl_mvm_scan_uid_by_status(mvm, IWL_MVM_SCAN_REGULAR); + if (uid >= 0) { + struct cfg80211_scan_info info = { + .aborted = true, + }; + + cancel_delayed_work(&mvm->scan_timeout_dwork); + + ieee80211_scan_completed(mvm->hw, &info); + mvm->scan_uid_status[uid] = 0; + } + uid = iwl_mvm_scan_uid_by_status(mvm, IWL_MVM_SCAN_SCHED); + if (uid >= 0) { + /* Sched scan will be restarted by mac80211 in + * restart_hw, so do not report if FW is about to be + * restarted. + */ + if (!mvm->fw_restart) + ieee80211_sched_scan_stopped(mvm->hw); + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_DISABLED; + mvm->scan_uid_status[uid] = 0; + } + uid = iwl_mvm_scan_uid_by_status(mvm, + IWL_MVM_SCAN_STOPPING_REGULAR); + if (uid >= 0) + mvm->scan_uid_status[uid] = 0; + + uid = iwl_mvm_scan_uid_by_status(mvm, + IWL_MVM_SCAN_STOPPING_SCHED); + if (uid >= 0) + mvm->scan_uid_status[uid] = 0; + + /* We shouldn't have any UIDs still set. Loop over all the + * UIDs to make sure there's nothing left there and warn if + * any is found. + */ + for (i = 0; i < mvm->max_scans; i++) { + if (WARN_ONCE(mvm->scan_uid_status[i], + "UMAC scan UID %d status was not cleaned\n", + i)) + mvm->scan_uid_status[i] = 0; + } + } else { + if (mvm->scan_status & IWL_MVM_SCAN_REGULAR) { + struct cfg80211_scan_info info = { + .aborted = true, + }; + + cancel_delayed_work(&mvm->scan_timeout_dwork); + ieee80211_scan_completed(mvm->hw, &info); + } + + /* Sched scan will be restarted by mac80211 in + * restart_hw, so do not report if FW is about to be + * restarted. + */ + if ((mvm->scan_status & IWL_MVM_SCAN_SCHED) && + !mvm->fw_restart) { + ieee80211_sched_scan_stopped(mvm->hw); + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_DISABLED; + } + } +} + +int iwl_mvm_scan_stop(struct iwl_mvm *mvm, int type, bool notify) +{ + int ret; + + if (!(mvm->scan_status & type)) + return 0; + + if (iwl_mvm_is_radio_killed(mvm)) { + ret = 0; + goto out; + } + + ret = iwl_mvm_scan_stop_wait(mvm, type); + if (!ret) + mvm->scan_status |= type << IWL_MVM_SCAN_STOPPING_SHIFT; +out: + /* Clear the scan status so the next scan requests will + * succeed and mark the scan as stopping, so that the Rx + * handler doesn't do anything, as the scan was stopped from + * above. + */ + mvm->scan_status &= ~type; + + if (type == IWL_MVM_SCAN_REGULAR) { + cancel_delayed_work(&mvm->scan_timeout_dwork); + if (notify) { + struct cfg80211_scan_info info = { + .aborted = true, + }; + + ieee80211_scan_completed(mvm->hw, &info); + } + } else if (notify) { + ieee80211_sched_scan_stopped(mvm->hw); + mvm->sched_scan_pass_all = SCHED_SCAN_PASS_ALL_DISABLED; + } + + return ret; +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/sf.c b/drivers/net/wireless/intel/iwlwifi/mvm/sf.c new file mode 100644 index 000000000..1f4ac1e93 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/sf.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2013-2014, 2018-2019, 2022 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + */ +#include "mvm.h" + +/* For counting bound interfaces */ +struct iwl_mvm_active_iface_iterator_data { + struct ieee80211_vif *ignore_vif; + u8 sta_vif_ap_sta_id; + enum iwl_sf_state sta_vif_state; + u32 num_active_macs; +}; + +/* + * Count bound interfaces which are not p2p, besides data->ignore_vif. + * data->station_vif will point to one bound vif of type station, if exists. + */ +static void iwl_mvm_bound_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_active_iface_iterator_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif == data->ignore_vif || !mvmvif->phy_ctxt || + vif->type == NL80211_IFTYPE_P2P_DEVICE) + return; + + data->num_active_macs++; + + if (vif->type == NL80211_IFTYPE_STATION) { + data->sta_vif_ap_sta_id = mvmvif->ap_sta_id; + if (vif->cfg.assoc) + data->sta_vif_state = SF_FULL_ON; + else + data->sta_vif_state = SF_INIT_OFF; + } +} + +/* + * Aging and idle timeouts for the different possible scenarios + * in default configuration + */ +static const +__le32 sf_full_timeout_def[SF_NUM_SCENARIO][SF_NUM_TIMEOUT_TYPES] = { + { + cpu_to_le32(SF_SINGLE_UNICAST_AGING_TIMER_DEF), + cpu_to_le32(SF_SINGLE_UNICAST_IDLE_TIMER_DEF) + }, + { + cpu_to_le32(SF_AGG_UNICAST_AGING_TIMER_DEF), + cpu_to_le32(SF_AGG_UNICAST_IDLE_TIMER_DEF) + }, + { + cpu_to_le32(SF_MCAST_AGING_TIMER_DEF), + cpu_to_le32(SF_MCAST_IDLE_TIMER_DEF) + }, + { + cpu_to_le32(SF_BA_AGING_TIMER_DEF), + cpu_to_le32(SF_BA_IDLE_TIMER_DEF) + }, + { + cpu_to_le32(SF_TX_RE_AGING_TIMER_DEF), + cpu_to_le32(SF_TX_RE_IDLE_TIMER_DEF) + }, +}; + +/* + * Aging and idle timeouts for the different possible scenarios + * in single BSS MAC configuration. + */ +static const __le32 sf_full_timeout[SF_NUM_SCENARIO][SF_NUM_TIMEOUT_TYPES] = { + { + cpu_to_le32(SF_SINGLE_UNICAST_AGING_TIMER), + cpu_to_le32(SF_SINGLE_UNICAST_IDLE_TIMER) + }, + { + cpu_to_le32(SF_AGG_UNICAST_AGING_TIMER), + cpu_to_le32(SF_AGG_UNICAST_IDLE_TIMER) + }, + { + cpu_to_le32(SF_MCAST_AGING_TIMER), + cpu_to_le32(SF_MCAST_IDLE_TIMER) + }, + { + cpu_to_le32(SF_BA_AGING_TIMER), + cpu_to_le32(SF_BA_IDLE_TIMER) + }, + { + cpu_to_le32(SF_TX_RE_AGING_TIMER), + cpu_to_le32(SF_TX_RE_IDLE_TIMER) + }, +}; + +static void iwl_mvm_fill_sf_command(struct iwl_mvm *mvm, + struct iwl_sf_cfg_cmd *sf_cmd, + struct ieee80211_sta *sta) +{ + int i, j, watermark; + + sf_cmd->watermark[SF_LONG_DELAY_ON] = cpu_to_le32(SF_W_MARK_SCAN); + + /* + * If we are in association flow - check antenna configuration + * capabilities of the AP station, and choose the watermark accordingly. + */ + if (sta) { + if (sta->deflink.ht_cap.ht_supported || + sta->deflink.vht_cap.vht_supported || + sta->deflink.he_cap.has_he) { + switch (sta->deflink.rx_nss) { + case 1: + watermark = SF_W_MARK_SISO; + break; + case 2: + watermark = SF_W_MARK_MIMO2; + break; + default: + watermark = SF_W_MARK_MIMO3; + break; + } + } else { + watermark = SF_W_MARK_LEGACY; + } + /* default watermark value for unassociated mode. */ + } else { + watermark = SF_W_MARK_MIMO2; + } + sf_cmd->watermark[SF_FULL_ON] = cpu_to_le32(watermark); + + for (i = 0; i < SF_NUM_SCENARIO; i++) { + for (j = 0; j < SF_NUM_TIMEOUT_TYPES; j++) { + sf_cmd->long_delay_timeouts[i][j] = + cpu_to_le32(SF_LONG_DELAY_AGING_TIMER); + } + } + + if (sta) { + BUILD_BUG_ON(sizeof(sf_full_timeout) != + sizeof(__le32) * SF_NUM_SCENARIO * + SF_NUM_TIMEOUT_TYPES); + + memcpy(sf_cmd->full_on_timeouts, sf_full_timeout, + sizeof(sf_full_timeout)); + } else { + BUILD_BUG_ON(sizeof(sf_full_timeout_def) != + sizeof(__le32) * SF_NUM_SCENARIO * + SF_NUM_TIMEOUT_TYPES); + + memcpy(sf_cmd->full_on_timeouts, sf_full_timeout_def, + sizeof(sf_full_timeout_def)); + } + +} + +static int iwl_mvm_sf_config(struct iwl_mvm *mvm, u8 sta_id, + enum iwl_sf_state new_state) +{ + struct iwl_sf_cfg_cmd sf_cmd = { + .state = cpu_to_le32(new_state), + }; + struct ieee80211_sta *sta; + int ret = 0; + + if (mvm->cfg->disable_dummy_notification) + sf_cmd.state |= cpu_to_le32(SF_CFG_DUMMY_NOTIF_OFF); + + /* + * If an associated AP sta changed its antenna configuration, the state + * will remain FULL_ON but SF parameters need to be reconsidered. + */ + if (new_state != SF_FULL_ON && mvm->sf_state == new_state) + return 0; + + switch (new_state) { + case SF_UNINIT: + iwl_mvm_fill_sf_command(mvm, &sf_cmd, NULL); + break; + case SF_FULL_ON: + if (sta_id == IWL_MVM_INVALID_STA) { + IWL_ERR(mvm, + "No station: Cannot switch SF to FULL_ON\n"); + return -EINVAL; + } + rcu_read_lock(); + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + if (IS_ERR_OR_NULL(sta)) { + IWL_ERR(mvm, "Invalid station id\n"); + rcu_read_unlock(); + return -EINVAL; + } + iwl_mvm_fill_sf_command(mvm, &sf_cmd, sta); + rcu_read_unlock(); + break; + case SF_INIT_OFF: + iwl_mvm_fill_sf_command(mvm, &sf_cmd, NULL); + break; + default: + WARN_ONCE(1, "Invalid state: %d. not sending Smart Fifo cmd\n", + new_state); + return -EINVAL; + } + + ret = iwl_mvm_send_cmd_pdu(mvm, REPLY_SF_CFG_CMD, CMD_ASYNC, + sizeof(sf_cmd), &sf_cmd); + if (!ret) + mvm->sf_state = new_state; + + return ret; +} + +/* + * Update Smart fifo: + * Count bound interfaces that are not to be removed, ignoring p2p devices, + * and set new state accordingly. + */ +int iwl_mvm_sf_update(struct iwl_mvm *mvm, struct ieee80211_vif *changed_vif, + bool remove_vif) +{ + enum iwl_sf_state new_state; + u8 sta_id = IWL_MVM_INVALID_STA; + struct iwl_mvm_vif *mvmvif = NULL; + struct iwl_mvm_active_iface_iterator_data data = { + .ignore_vif = changed_vif, + .sta_vif_state = SF_UNINIT, + .sta_vif_ap_sta_id = IWL_MVM_INVALID_STA, + }; + + /* + * Ignore the call if we are in HW Restart flow, or if the handled + * vif is a p2p device. + */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) || + (changed_vif && changed_vif->type == NL80211_IFTYPE_P2P_DEVICE)) + return 0; + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_bound_iface_iterator, + &data); + + /* If changed_vif exists and is not to be removed, add to the count */ + if (changed_vif && !remove_vif) + data.num_active_macs++; + + switch (data.num_active_macs) { + case 0: + /* If there are no active macs - change state to SF_INIT_OFF */ + new_state = SF_INIT_OFF; + break; + case 1: + if (remove_vif) { + /* The one active mac left is of type station + * and we filled the relevant data during iteration + */ + new_state = data.sta_vif_state; + sta_id = data.sta_vif_ap_sta_id; + } else { + if (WARN_ON(!changed_vif)) + return -EINVAL; + if (changed_vif->type != NL80211_IFTYPE_STATION) { + new_state = SF_UNINIT; + } else if (changed_vif->cfg.assoc && + changed_vif->bss_conf.dtim_period) { + mvmvif = iwl_mvm_vif_from_mac80211(changed_vif); + sta_id = mvmvif->ap_sta_id; + new_state = SF_FULL_ON; + } else { + new_state = SF_INIT_OFF; + } + } + break; + default: + /* If there are multiple active macs - change to SF_UNINIT */ + new_state = SF_UNINIT; + } + return iwl_mvm_sf_config(mvm, sta_id, new_state); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/sta.c b/drivers/net/wireless/intel/iwlwifi/mvm/sta.c new file mode 100644 index 000000000..6b52afcf0 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/sta.c @@ -0,0 +1,4182 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2015, 2018-2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include <net/mac80211.h> + +#include "mvm.h" +#include "sta.h" +#include "rs.h" + +/* + * New version of ADD_STA_sta command added new fields at the end of the + * structure, so sending the size of the relevant API's structure is enough to + * support both API versions. + */ +static inline int iwl_mvm_add_sta_cmd_size(struct iwl_mvm *mvm) +{ + if (iwl_mvm_has_new_rx_api(mvm) || + fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_STA_TYPE)) + return sizeof(struct iwl_mvm_add_sta_cmd); + else + return sizeof(struct iwl_mvm_add_sta_cmd_v7); +} + +static int iwl_mvm_find_free_sta_id(struct iwl_mvm *mvm, + enum nl80211_iftype iftype) +{ + int sta_id; + u32 reserved_ids = 0; + + BUILD_BUG_ON(IWL_MVM_STATION_COUNT_MAX > 32); + WARN_ON_ONCE(test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)); + + lockdep_assert_held(&mvm->mutex); + + /* d0i3/d3 assumes the AP's sta_id (of sta vif) is 0. reserve it. */ + if (iftype != NL80211_IFTYPE_STATION) + reserved_ids = BIT(0); + + /* Don't take rcu_read_lock() since we are protected by mvm->mutex */ + for (sta_id = 0; sta_id < mvm->fw->ucode_capa.num_stations; sta_id++) { + if (BIT(sta_id) & reserved_ids) + continue; + + if (!rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex))) + return sta_id; + } + return IWL_MVM_INVALID_STA; +} + +/* send station add/update command to firmware */ +int iwl_mvm_sta_send_to_fw(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + bool update, unsigned int flags) +{ + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_add_sta_cmd add_sta_cmd = { + .sta_id = mvm_sta->sta_id, + .mac_id_n_color = cpu_to_le32(mvm_sta->mac_id_n_color), + .add_modify = update ? 1 : 0, + .station_flags_msk = cpu_to_le32(STA_FLG_FAT_EN_MSK | + STA_FLG_MIMO_EN_MSK | + STA_FLG_RTS_MIMO_PROT), + .tid_disable_tx = cpu_to_le16(mvm_sta->tid_disable_agg), + }; + int ret; + u32 status; + u32 agg_size = 0, mpdu_dens = 0; + + if (fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_STA_TYPE)) + add_sta_cmd.station_type = mvm_sta->sta_type; + + if (!update || (flags & STA_MODIFY_QUEUES)) { + memcpy(&add_sta_cmd.addr, sta->addr, ETH_ALEN); + + if (!iwl_mvm_has_new_tx_api(mvm)) { + add_sta_cmd.tfd_queue_msk = + cpu_to_le32(mvm_sta->tfd_queue_msk); + + if (flags & STA_MODIFY_QUEUES) + add_sta_cmd.modify_mask |= STA_MODIFY_QUEUES; + } else { + WARN_ON(flags & STA_MODIFY_QUEUES); + } + } + + switch (sta->deflink.bandwidth) { + case IEEE80211_STA_RX_BW_320: + case IEEE80211_STA_RX_BW_160: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_160MHZ); + fallthrough; + case IEEE80211_STA_RX_BW_80: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_80MHZ); + fallthrough; + case IEEE80211_STA_RX_BW_40: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_40MHZ); + fallthrough; + case IEEE80211_STA_RX_BW_20: + if (sta->deflink.ht_cap.ht_supported) + add_sta_cmd.station_flags |= + cpu_to_le32(STA_FLG_FAT_EN_20MHZ); + break; + } + + switch (sta->deflink.rx_nss) { + case 1: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_SISO); + break; + case 2: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_MIMO2); + break; + case 3 ... 8: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_MIMO3); + break; + } + + switch (sta->deflink.smps_mode) { + case IEEE80211_SMPS_AUTOMATIC: + case IEEE80211_SMPS_NUM_MODES: + WARN_ON(1); + break; + case IEEE80211_SMPS_STATIC: + /* override NSS */ + add_sta_cmd.station_flags &= ~cpu_to_le32(STA_FLG_MIMO_EN_MSK); + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_SISO); + break; + case IEEE80211_SMPS_DYNAMIC: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_RTS_MIMO_PROT); + break; + case IEEE80211_SMPS_OFF: + /* nothing */ + break; + } + + if (sta->deflink.ht_cap.ht_supported) { + add_sta_cmd.station_flags_msk |= + cpu_to_le32(STA_FLG_MAX_AGG_SIZE_MSK | + STA_FLG_AGG_MPDU_DENS_MSK); + + mpdu_dens = sta->deflink.ht_cap.ampdu_density; + } + + if (mvm_sta->vif->bss_conf.chandef.chan->band == NL80211_BAND_6GHZ) { + add_sta_cmd.station_flags_msk |= + cpu_to_le32(STA_FLG_MAX_AGG_SIZE_MSK | + STA_FLG_AGG_MPDU_DENS_MSK); + + mpdu_dens = le16_get_bits(sta->deflink.he_6ghz_capa.capa, + IEEE80211_HE_6GHZ_CAP_MIN_MPDU_START); + agg_size = le16_get_bits(sta->deflink.he_6ghz_capa.capa, + IEEE80211_HE_6GHZ_CAP_MAX_AMPDU_LEN_EXP); + } else if (sta->deflink.vht_cap.vht_supported) { + agg_size = sta->deflink.vht_cap.cap & + IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK; + agg_size >>= + IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT; + } else if (sta->deflink.ht_cap.ht_supported) { + agg_size = sta->deflink.ht_cap.ampdu_factor; + } + + /* D6.0 10.12.2 A-MPDU length limit rules + * A STA indicates the maximum length of the A-MPDU preEOF padding + * that it can receive in an HE PPDU in the Maximum A-MPDU Length + * Exponent field in its HT Capabilities, VHT Capabilities, + * and HE 6 GHz Band Capabilities elements (if present) and the + * Maximum AMPDU Length Exponent Extension field in its HE + * Capabilities element + */ + if (sta->deflink.he_cap.has_he) + agg_size += u8_get_bits(sta->deflink.he_cap.he_cap_elem.mac_cap_info[3], + IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_MASK); + + /* Limit to max A-MPDU supported by FW */ + if (agg_size > (STA_FLG_MAX_AGG_SIZE_4M >> STA_FLG_MAX_AGG_SIZE_SHIFT)) + agg_size = (STA_FLG_MAX_AGG_SIZE_4M >> + STA_FLG_MAX_AGG_SIZE_SHIFT); + + add_sta_cmd.station_flags |= + cpu_to_le32(agg_size << STA_FLG_MAX_AGG_SIZE_SHIFT); + add_sta_cmd.station_flags |= + cpu_to_le32(mpdu_dens << STA_FLG_AGG_MPDU_DENS_SHIFT); + if (mvm_sta->sta_state >= IEEE80211_STA_ASSOC) + add_sta_cmd.assoc_id = cpu_to_le16(sta->aid); + + if (sta->wme) { + add_sta_cmd.modify_mask |= STA_MODIFY_UAPSD_ACS; + + if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BK) + add_sta_cmd.uapsd_acs |= BIT(AC_BK); + if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BE) + add_sta_cmd.uapsd_acs |= BIT(AC_BE); + if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VI) + add_sta_cmd.uapsd_acs |= BIT(AC_VI); + if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VO) + add_sta_cmd.uapsd_acs |= BIT(AC_VO); + add_sta_cmd.uapsd_acs |= add_sta_cmd.uapsd_acs << 4; + add_sta_cmd.sp_length = sta->max_sp ? sta->max_sp * 2 : 128; + } + + status = ADD_STA_SUCCESS; + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, + iwl_mvm_add_sta_cmd_size(mvm), + &add_sta_cmd, &status); + if (ret) + return ret; + + switch (status & IWL_ADD_STA_STATUS_MASK) { + case ADD_STA_SUCCESS: + IWL_DEBUG_ASSOC(mvm, "ADD_STA PASSED\n"); + break; + default: + ret = -EIO; + IWL_ERR(mvm, "ADD_STA failed\n"); + break; + } + + return ret; +} + +static void iwl_mvm_rx_agg_session_expired(struct timer_list *t) +{ + struct iwl_mvm_baid_data *data = + from_timer(data, t, session_timer); + struct iwl_mvm_baid_data __rcu **rcu_ptr = data->rcu_ptr; + struct iwl_mvm_baid_data *ba_data; + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvm_sta; + unsigned long timeout; + + rcu_read_lock(); + + ba_data = rcu_dereference(*rcu_ptr); + + if (WARN_ON(!ba_data)) + goto unlock; + + if (!ba_data->timeout) + goto unlock; + + timeout = ba_data->last_rx + TU_TO_JIFFIES(ba_data->timeout * 2); + if (time_is_after_jiffies(timeout)) { + mod_timer(&ba_data->session_timer, timeout); + goto unlock; + } + + /* Timer expired */ + sta = rcu_dereference(ba_data->mvm->fw_id_to_mac_id[ba_data->sta_id]); + + /* + * sta should be valid unless the following happens: + * The firmware asserts which triggers a reconfig flow, but + * the reconfig fails before we set the pointer to sta into + * the fw_id_to_mac_id pointer table. Mac80211 can't stop + * A-MDPU and hence the timer continues to run. Then, the + * timer expires and sta is NULL. + */ + if (!sta) + goto unlock; + + mvm_sta = iwl_mvm_sta_from_mac80211(sta); + ieee80211_rx_ba_timer_expired(mvm_sta->vif, + sta->addr, ba_data->tid); +unlock: + rcu_read_unlock(); +} + +/* Disable aggregations for a bitmap of TIDs for a given station */ +static int iwl_mvm_invalidate_sta_queue(struct iwl_mvm *mvm, int queue, + unsigned long disable_agg_tids, + bool remove_queue) +{ + struct iwl_mvm_add_sta_cmd cmd = {}; + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + u32 status; + u8 sta_id; + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return -EINVAL; + + sta_id = mvm->queue_info[queue].ra_sta_id; + + rcu_read_lock(); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + + if (WARN_ON_ONCE(IS_ERR_OR_NULL(sta))) { + rcu_read_unlock(); + return -EINVAL; + } + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + mvmsta->tid_disable_agg |= disable_agg_tids; + + cmd.mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color); + cmd.sta_id = mvmsta->sta_id; + cmd.add_modify = STA_MODE_MODIFY; + cmd.modify_mask = STA_MODIFY_QUEUES; + if (disable_agg_tids) + cmd.modify_mask |= STA_MODIFY_TID_DISABLE_TX; + if (remove_queue) + cmd.modify_mask |= STA_MODIFY_QUEUE_REMOVAL; + cmd.tfd_queue_msk = cpu_to_le32(mvmsta->tfd_queue_msk); + cmd.tid_disable_tx = cpu_to_le16(mvmsta->tid_disable_agg); + + rcu_read_unlock(); + + /* Notify FW of queue removal from the STA queues */ + status = ADD_STA_SUCCESS; + return iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, + iwl_mvm_add_sta_cmd_size(mvm), + &cmd, &status); +} + +static int iwl_mvm_disable_txq(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + u16 *queueptr, u8 tid) +{ + int queue = *queueptr; + struct iwl_scd_txq_cfg_cmd cmd = { + .scd_queue = queue, + .action = SCD_CFG_DISABLE_QUEUE, + }; + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (iwl_mvm_has_new_tx_api(mvm)) { + if (mvm->sta_remove_requires_queue_remove) { + u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, + SCD_QUEUE_CONFIG_CMD); + struct iwl_scd_queue_cfg_cmd remove_cmd = { + .operation = cpu_to_le32(IWL_SCD_QUEUE_REMOVE), + .u.remove.queue = cpu_to_le32(queue), + }; + + ret = iwl_mvm_send_cmd_pdu(mvm, cmd_id, 0, + sizeof(remove_cmd), + &remove_cmd); + } else { + ret = 0; + } + + iwl_trans_txq_free(mvm->trans, queue); + *queueptr = IWL_MVM_INVALID_QUEUE; + + return ret; + } + + if (WARN_ON(mvm->queue_info[queue].tid_bitmap == 0)) + return 0; + + mvm->queue_info[queue].tid_bitmap &= ~BIT(tid); + + cmd.action = mvm->queue_info[queue].tid_bitmap ? + SCD_CFG_ENABLE_QUEUE : SCD_CFG_DISABLE_QUEUE; + if (cmd.action == SCD_CFG_DISABLE_QUEUE) + mvm->queue_info[queue].status = IWL_MVM_QUEUE_FREE; + + IWL_DEBUG_TX_QUEUES(mvm, + "Disabling TXQ #%d tids=0x%x\n", + queue, + mvm->queue_info[queue].tid_bitmap); + + /* If the queue is still enabled - nothing left to do in this func */ + if (cmd.action == SCD_CFG_ENABLE_QUEUE) + return 0; + + cmd.sta_id = mvm->queue_info[queue].ra_sta_id; + cmd.tid = mvm->queue_info[queue].txq_tid; + + /* Make sure queue info is correct even though we overwrite it */ + WARN(mvm->queue_info[queue].tid_bitmap, + "TXQ #%d info out-of-sync - tids=0x%x\n", + queue, mvm->queue_info[queue].tid_bitmap); + + /* If we are here - the queue is freed and we can zero out these vals */ + mvm->queue_info[queue].tid_bitmap = 0; + + if (sta) { + struct iwl_mvm_txq *mvmtxq = + iwl_mvm_txq_from_tid(sta, tid); + + spin_lock_bh(&mvm->add_stream_lock); + list_del_init(&mvmtxq->list); + clear_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state); + mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE; + spin_unlock_bh(&mvm->add_stream_lock); + } + + /* Regardless if this is a reserved TXQ for a STA - mark it as false */ + mvm->queue_info[queue].reserved = false; + + iwl_trans_txq_disable(mvm->trans, queue, false); + ret = iwl_mvm_send_cmd_pdu(mvm, SCD_QUEUE_CFG, 0, + sizeof(struct iwl_scd_txq_cfg_cmd), &cmd); + + if (ret) + IWL_ERR(mvm, "Failed to disable queue %d (ret=%d)\n", + queue, ret); + return ret; +} + +static int iwl_mvm_get_queue_agg_tids(struct iwl_mvm *mvm, int queue) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + unsigned long tid_bitmap; + unsigned long agg_tids = 0; + u8 sta_id; + int tid; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return -EINVAL; + + sta_id = mvm->queue_info[queue].ra_sta_id; + tid_bitmap = mvm->queue_info[queue].tid_bitmap; + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + + if (WARN_ON_ONCE(IS_ERR_OR_NULL(sta))) + return -EINVAL; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + spin_lock_bh(&mvmsta->lock); + for_each_set_bit(tid, &tid_bitmap, IWL_MAX_TID_COUNT + 1) { + if (mvmsta->tid_data[tid].state == IWL_AGG_ON) + agg_tids |= BIT(tid); + } + spin_unlock_bh(&mvmsta->lock); + + return agg_tids; +} + +/* + * Remove a queue from a station's resources. + * Note that this only marks as free. It DOESN'T delete a BA agreement, and + * doesn't disable the queue + */ +static int iwl_mvm_remove_sta_queue_marking(struct iwl_mvm *mvm, int queue) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + unsigned long tid_bitmap; + unsigned long disable_agg_tids = 0; + u8 sta_id; + int tid; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return -EINVAL; + + sta_id = mvm->queue_info[queue].ra_sta_id; + tid_bitmap = mvm->queue_info[queue].tid_bitmap; + + rcu_read_lock(); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + + if (WARN_ON_ONCE(IS_ERR_OR_NULL(sta))) { + rcu_read_unlock(); + return 0; + } + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + spin_lock_bh(&mvmsta->lock); + /* Unmap MAC queues and TIDs from this queue */ + for_each_set_bit(tid, &tid_bitmap, IWL_MAX_TID_COUNT + 1) { + struct iwl_mvm_txq *mvmtxq = + iwl_mvm_txq_from_tid(sta, tid); + + if (mvmsta->tid_data[tid].state == IWL_AGG_ON) + disable_agg_tids |= BIT(tid); + mvmsta->tid_data[tid].txq_id = IWL_MVM_INVALID_QUEUE; + + spin_lock_bh(&mvm->add_stream_lock); + list_del_init(&mvmtxq->list); + clear_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state); + mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE; + spin_unlock_bh(&mvm->add_stream_lock); + } + + mvmsta->tfd_queue_msk &= ~BIT(queue); /* Don't use this queue anymore */ + spin_unlock_bh(&mvmsta->lock); + + rcu_read_unlock(); + + /* + * The TX path may have been using this TXQ_ID from the tid_data, + * so make sure it's no longer running so that we can safely reuse + * this TXQ later. We've set all the TIDs to IWL_MVM_INVALID_QUEUE + * above, but nothing guarantees we've stopped using them. Thus, + * without this, we could get to iwl_mvm_disable_txq() and remove + * the queue while still sending frames to it. + */ + synchronize_net(); + + return disable_agg_tids; +} + +static int iwl_mvm_free_inactive_queue(struct iwl_mvm *mvm, int queue, + struct ieee80211_sta *old_sta, + u8 new_sta_id) +{ + struct iwl_mvm_sta *mvmsta; + u8 sta_id, tid; + unsigned long disable_agg_tids = 0; + bool same_sta; + u16 queue_tmp = queue; + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return -EINVAL; + + sta_id = mvm->queue_info[queue].ra_sta_id; + tid = mvm->queue_info[queue].txq_tid; + + same_sta = sta_id == new_sta_id; + + mvmsta = iwl_mvm_sta_from_staid_protected(mvm, sta_id); + if (WARN_ON(!mvmsta)) + return -EINVAL; + + disable_agg_tids = iwl_mvm_remove_sta_queue_marking(mvm, queue); + /* Disable the queue */ + if (disable_agg_tids) + iwl_mvm_invalidate_sta_queue(mvm, queue, + disable_agg_tids, false); + + ret = iwl_mvm_disable_txq(mvm, old_sta, &queue_tmp, tid); + if (ret) { + IWL_ERR(mvm, + "Failed to free inactive queue %d (ret=%d)\n", + queue, ret); + + return ret; + } + + /* If TXQ is allocated to another STA, update removal in FW */ + if (!same_sta) + iwl_mvm_invalidate_sta_queue(mvm, queue, 0, true); + + return 0; +} + +static int iwl_mvm_get_shared_queue(struct iwl_mvm *mvm, + unsigned long tfd_queue_mask, u8 ac) +{ + int queue = 0; + u8 ac_to_queue[IEEE80211_NUM_ACS]; + int i; + + /* + * This protects us against grabbing a queue that's being reconfigured + * by the inactivity checker. + */ + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return -EINVAL; + + memset(&ac_to_queue, IEEE80211_INVAL_HW_QUEUE, sizeof(ac_to_queue)); + + /* See what ACs the existing queues for this STA have */ + for_each_set_bit(i, &tfd_queue_mask, IWL_MVM_DQA_MAX_DATA_QUEUE) { + /* Only DATA queues can be shared */ + if (i < IWL_MVM_DQA_MIN_DATA_QUEUE && + i != IWL_MVM_DQA_BSS_CLIENT_QUEUE) + continue; + + ac_to_queue[mvm->queue_info[i].mac80211_ac] = i; + } + + /* + * The queue to share is chosen only from DATA queues as follows (in + * descending priority): + * 1. An AC_BE queue + * 2. Same AC queue + * 3. Highest AC queue that is lower than new AC + * 4. Any existing AC (there always is at least 1 DATA queue) + */ + + /* Priority 1: An AC_BE queue */ + if (ac_to_queue[IEEE80211_AC_BE] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[IEEE80211_AC_BE]; + /* Priority 2: Same AC queue */ + else if (ac_to_queue[ac] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[ac]; + /* Priority 3a: If new AC is VO and VI exists - use VI */ + else if (ac == IEEE80211_AC_VO && + ac_to_queue[IEEE80211_AC_VI] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[IEEE80211_AC_VI]; + /* Priority 3b: No BE so only AC less than the new one is BK */ + else if (ac_to_queue[IEEE80211_AC_BK] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[IEEE80211_AC_BK]; + /* Priority 4a: No BE nor BK - use VI if exists */ + else if (ac_to_queue[IEEE80211_AC_VI] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[IEEE80211_AC_VI]; + /* Priority 4b: No BE, BK nor VI - use VO if exists */ + else if (ac_to_queue[IEEE80211_AC_VO] != IEEE80211_INVAL_HW_QUEUE) + queue = ac_to_queue[IEEE80211_AC_VO]; + + /* Make sure queue found (or not) is legal */ + if (!iwl_mvm_is_dqa_data_queue(mvm, queue) && + !iwl_mvm_is_dqa_mgmt_queue(mvm, queue) && + (queue != IWL_MVM_DQA_BSS_CLIENT_QUEUE)) { + IWL_ERR(mvm, "No DATA queues available to share\n"); + return -ENOSPC; + } + + return queue; +} + +/* Re-configure the SCD for a queue that has already been configured */ +static int iwl_mvm_reconfig_scd(struct iwl_mvm *mvm, int queue, int fifo, + int sta_id, int tid, int frame_limit, u16 ssn) +{ + struct iwl_scd_txq_cfg_cmd cmd = { + .scd_queue = queue, + .action = SCD_CFG_ENABLE_QUEUE, + .window = frame_limit, + .sta_id = sta_id, + .ssn = cpu_to_le16(ssn), + .tx_fifo = fifo, + .aggregate = (queue >= IWL_MVM_DQA_MIN_DATA_QUEUE || + queue == IWL_MVM_DQA_BSS_CLIENT_QUEUE), + .tid = tid, + }; + int ret; + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return -EINVAL; + + if (WARN(mvm->queue_info[queue].tid_bitmap == 0, + "Trying to reconfig unallocated queue %d\n", queue)) + return -ENXIO; + + IWL_DEBUG_TX_QUEUES(mvm, "Reconfig SCD for TXQ #%d\n", queue); + + ret = iwl_mvm_send_cmd_pdu(mvm, SCD_QUEUE_CFG, 0, sizeof(cmd), &cmd); + WARN_ONCE(ret, "Failed to re-configure queue %d on FIFO %d, ret=%d\n", + queue, fifo, ret); + + return ret; +} + +/* + * If a given queue has a higher AC than the TID stream that is being compared + * to, the queue needs to be redirected to the lower AC. This function does that + * in such a case, otherwise - if no redirection required - it does nothing, + * unless the %force param is true. + */ +static int iwl_mvm_redirect_queue(struct iwl_mvm *mvm, int queue, int tid, + int ac, int ssn, unsigned int wdg_timeout, + bool force, struct iwl_mvm_txq *txq) +{ + struct iwl_scd_txq_cfg_cmd cmd = { + .scd_queue = queue, + .action = SCD_CFG_DISABLE_QUEUE, + }; + bool shared_queue; + int ret; + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return -EINVAL; + + /* + * If the AC is lower than current one - FIFO needs to be redirected to + * the lowest one of the streams in the queue. Check if this is needed + * here. + * Notice that the enum ieee80211_ac_numbers is "flipped", so BK is with + * value 3 and VO with value 0, so to check if ac X is lower than ac Y + * we need to check if the numerical value of X is LARGER than of Y. + */ + if (ac <= mvm->queue_info[queue].mac80211_ac && !force) { + IWL_DEBUG_TX_QUEUES(mvm, + "No redirection needed on TXQ #%d\n", + queue); + return 0; + } + + cmd.sta_id = mvm->queue_info[queue].ra_sta_id; + cmd.tx_fifo = iwl_mvm_ac_to_tx_fifo[mvm->queue_info[queue].mac80211_ac]; + cmd.tid = mvm->queue_info[queue].txq_tid; + shared_queue = hweight16(mvm->queue_info[queue].tid_bitmap) > 1; + + IWL_DEBUG_TX_QUEUES(mvm, "Redirecting TXQ #%d to FIFO #%d\n", + queue, iwl_mvm_ac_to_tx_fifo[ac]); + + /* Stop the queue and wait for it to empty */ + set_bit(IWL_MVM_TXQ_STATE_STOP_REDIRECT, &txq->state); + + ret = iwl_trans_wait_tx_queues_empty(mvm->trans, BIT(queue)); + if (ret) { + IWL_ERR(mvm, "Error draining queue %d before reconfig\n", + queue); + ret = -EIO; + goto out; + } + + /* Before redirecting the queue we need to de-activate it */ + iwl_trans_txq_disable(mvm->trans, queue, false); + ret = iwl_mvm_send_cmd_pdu(mvm, SCD_QUEUE_CFG, 0, sizeof(cmd), &cmd); + if (ret) + IWL_ERR(mvm, "Failed SCD disable TXQ %d (ret=%d)\n", queue, + ret); + + /* Make sure the SCD wrptr is correctly set before reconfiguring */ + iwl_trans_txq_enable_cfg(mvm->trans, queue, ssn, NULL, wdg_timeout); + + /* Update the TID "owner" of the queue */ + mvm->queue_info[queue].txq_tid = tid; + + /* TODO: Work-around SCD bug when moving back by multiples of 0x40 */ + + /* Redirect to lower AC */ + iwl_mvm_reconfig_scd(mvm, queue, iwl_mvm_ac_to_tx_fifo[ac], + cmd.sta_id, tid, IWL_FRAME_LIMIT, ssn); + + /* Update AC marking of the queue */ + mvm->queue_info[queue].mac80211_ac = ac; + + /* + * Mark queue as shared in transport if shared + * Note this has to be done after queue enablement because enablement + * can also set this value, and there is no indication there to shared + * queues + */ + if (shared_queue) + iwl_trans_txq_set_shared_mode(mvm->trans, queue, true); + +out: + /* Continue using the queue */ + clear_bit(IWL_MVM_TXQ_STATE_STOP_REDIRECT, &txq->state); + + return ret; +} + +static int iwl_mvm_find_free_queue(struct iwl_mvm *mvm, u8 sta_id, + u8 minq, u8 maxq) +{ + int i; + + lockdep_assert_held(&mvm->mutex); + + if (WARN(maxq >= mvm->trans->trans_cfg->base_params->num_of_queues, + "max queue %d >= num_of_queues (%d)", maxq, + mvm->trans->trans_cfg->base_params->num_of_queues)) + maxq = mvm->trans->trans_cfg->base_params->num_of_queues - 1; + + /* This should not be hit with new TX path */ + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return -ENOSPC; + + /* Start by looking for a free queue */ + for (i = minq; i <= maxq; i++) + if (mvm->queue_info[i].tid_bitmap == 0 && + mvm->queue_info[i].status == IWL_MVM_QUEUE_FREE) + return i; + + return -ENOSPC; +} + +static int iwl_mvm_tvqm_enable_txq(struct iwl_mvm *mvm, + u8 sta_id, u8 tid, unsigned int timeout) +{ + int queue, size; + + if (tid == IWL_MAX_TID_COUNT) { + tid = IWL_MGMT_TID; + size = max_t(u32, IWL_MGMT_QUEUE_SIZE, + mvm->trans->cfg->min_txq_size); + } else { + struct ieee80211_sta *sta; + + rcu_read_lock(); + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + + /* this queue isn't used for traffic (cab_queue) */ + if (IS_ERR_OR_NULL(sta)) { + size = IWL_MGMT_QUEUE_SIZE; + } else if (sta->deflink.he_cap.has_he) { + /* support for 256 ba size */ + size = IWL_DEFAULT_QUEUE_SIZE_HE; + } else { + size = IWL_DEFAULT_QUEUE_SIZE; + } + + rcu_read_unlock(); + } + + /* take the min with bc tbl entries allowed */ + size = min_t(u32, size, mvm->trans->txqs.bc_tbl_size / sizeof(u16)); + + /* size needs to be power of 2 values for calculating read/write pointers */ + size = rounddown_pow_of_two(size); + + do { + queue = iwl_trans_txq_alloc(mvm->trans, 0, BIT(sta_id), + tid, size, timeout); + + if (queue < 0) + IWL_DEBUG_TX_QUEUES(mvm, + "Failed allocating TXQ of size %d for sta %d tid %d, ret: %d\n", + size, sta_id, tid, queue); + size /= 2; + } while (queue < 0 && size >= 16); + + if (queue < 0) + return queue; + + IWL_DEBUG_TX_QUEUES(mvm, "Enabling TXQ #%d for sta %d tid %d\n", + queue, sta_id, tid); + + return queue; +} + +static int iwl_mvm_sta_alloc_queue_tvqm(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, u8 ac, + int tid) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_txq *mvmtxq = + iwl_mvm_txq_from_tid(sta, tid); + unsigned int wdg_timeout = + iwl_mvm_get_wd_timeout(mvm, mvmsta->vif, false, false); + int queue = -1; + + lockdep_assert_held(&mvm->mutex); + + IWL_DEBUG_TX_QUEUES(mvm, + "Allocating queue for sta %d on tid %d\n", + mvmsta->sta_id, tid); + queue = iwl_mvm_tvqm_enable_txq(mvm, mvmsta->sta_id, tid, wdg_timeout); + if (queue < 0) + return queue; + + mvmtxq->txq_id = queue; + mvm->tvqm_info[queue].txq_tid = tid; + mvm->tvqm_info[queue].sta_id = mvmsta->sta_id; + + IWL_DEBUG_TX_QUEUES(mvm, "Allocated queue is %d\n", queue); + + spin_lock_bh(&mvmsta->lock); + mvmsta->tid_data[tid].txq_id = queue; + spin_unlock_bh(&mvmsta->lock); + + return 0; +} + +static bool iwl_mvm_update_txq_mapping(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + int queue, u8 sta_id, u8 tid) +{ + bool enable_queue = true; + + /* Make sure this TID isn't already enabled */ + if (mvm->queue_info[queue].tid_bitmap & BIT(tid)) { + IWL_ERR(mvm, "Trying to enable TXQ %d with existing TID %d\n", + queue, tid); + return false; + } + + /* Update mappings and refcounts */ + if (mvm->queue_info[queue].tid_bitmap) + enable_queue = false; + + mvm->queue_info[queue].tid_bitmap |= BIT(tid); + mvm->queue_info[queue].ra_sta_id = sta_id; + + if (enable_queue) { + if (tid != IWL_MAX_TID_COUNT) + mvm->queue_info[queue].mac80211_ac = + tid_to_mac80211_ac[tid]; + else + mvm->queue_info[queue].mac80211_ac = IEEE80211_AC_VO; + + mvm->queue_info[queue].txq_tid = tid; + } + + if (sta) { + struct iwl_mvm_txq *mvmtxq = + iwl_mvm_txq_from_tid(sta, tid); + + mvmtxq->txq_id = queue; + } + + IWL_DEBUG_TX_QUEUES(mvm, + "Enabling TXQ #%d tids=0x%x\n", + queue, mvm->queue_info[queue].tid_bitmap); + + return enable_queue; +} + +static bool iwl_mvm_enable_txq(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + int queue, u16 ssn, + const struct iwl_trans_txq_scd_cfg *cfg, + unsigned int wdg_timeout) +{ + struct iwl_scd_txq_cfg_cmd cmd = { + .scd_queue = queue, + .action = SCD_CFG_ENABLE_QUEUE, + .window = cfg->frame_limit, + .sta_id = cfg->sta_id, + .ssn = cpu_to_le16(ssn), + .tx_fifo = cfg->fifo, + .aggregate = cfg->aggregate, + .tid = cfg->tid, + }; + bool inc_ssn; + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return false; + + /* Send the enabling command if we need to */ + if (!iwl_mvm_update_txq_mapping(mvm, sta, queue, cfg->sta_id, cfg->tid)) + return false; + + inc_ssn = iwl_trans_txq_enable_cfg(mvm->trans, queue, ssn, + NULL, wdg_timeout); + if (inc_ssn) + le16_add_cpu(&cmd.ssn, 1); + + WARN(iwl_mvm_send_cmd_pdu(mvm, SCD_QUEUE_CFG, 0, sizeof(cmd), &cmd), + "Failed to configure queue %d on FIFO %d\n", queue, cfg->fifo); + + return inc_ssn; +} + +static void iwl_mvm_change_queue_tid(struct iwl_mvm *mvm, int queue) +{ + struct iwl_scd_txq_cfg_cmd cmd = { + .scd_queue = queue, + .action = SCD_CFG_UPDATE_QUEUE_TID, + }; + int tid; + unsigned long tid_bitmap; + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return; + + tid_bitmap = mvm->queue_info[queue].tid_bitmap; + + if (WARN(!tid_bitmap, "TXQ %d has no tids assigned to it\n", queue)) + return; + + /* Find any TID for queue */ + tid = find_first_bit(&tid_bitmap, IWL_MAX_TID_COUNT + 1); + cmd.tid = tid; + cmd.tx_fifo = iwl_mvm_ac_to_tx_fifo[tid_to_mac80211_ac[tid]]; + + ret = iwl_mvm_send_cmd_pdu(mvm, SCD_QUEUE_CFG, 0, sizeof(cmd), &cmd); + if (ret) { + IWL_ERR(mvm, "Failed to update owner of TXQ %d (ret=%d)\n", + queue, ret); + return; + } + + mvm->queue_info[queue].txq_tid = tid; + IWL_DEBUG_TX_QUEUES(mvm, "Changed TXQ %d ownership to tid %d\n", + queue, tid); +} + +static void iwl_mvm_unshare_queue(struct iwl_mvm *mvm, int queue) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + u8 sta_id; + int tid = -1; + unsigned long tid_bitmap; + unsigned int wdg_timeout; + int ssn; + int ret = true; + + /* queue sharing is disabled on new TX path */ + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return; + + lockdep_assert_held(&mvm->mutex); + + sta_id = mvm->queue_info[queue].ra_sta_id; + tid_bitmap = mvm->queue_info[queue].tid_bitmap; + + /* Find TID for queue, and make sure it is the only one on the queue */ + tid = find_first_bit(&tid_bitmap, IWL_MAX_TID_COUNT + 1); + if (tid_bitmap != BIT(tid)) { + IWL_ERR(mvm, "Failed to unshare q %d, active tids=0x%lx\n", + queue, tid_bitmap); + return; + } + + IWL_DEBUG_TX_QUEUES(mvm, "Unsharing TXQ %d, keeping tid %d\n", queue, + tid); + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + + if (WARN_ON_ONCE(IS_ERR_OR_NULL(sta))) + return; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + wdg_timeout = iwl_mvm_get_wd_timeout(mvm, mvmsta->vif, false, false); + + ssn = IEEE80211_SEQ_TO_SN(mvmsta->tid_data[tid].seq_number); + + ret = iwl_mvm_redirect_queue(mvm, queue, tid, + tid_to_mac80211_ac[tid], ssn, + wdg_timeout, true, + iwl_mvm_txq_from_tid(sta, tid)); + if (ret) { + IWL_ERR(mvm, "Failed to redirect TXQ %d\n", queue); + return; + } + + /* If aggs should be turned back on - do it */ + if (mvmsta->tid_data[tid].state == IWL_AGG_ON) { + struct iwl_mvm_add_sta_cmd cmd = {0}; + + mvmsta->tid_disable_agg &= ~BIT(tid); + + cmd.mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color); + cmd.sta_id = mvmsta->sta_id; + cmd.add_modify = STA_MODE_MODIFY; + cmd.modify_mask = STA_MODIFY_TID_DISABLE_TX; + cmd.tfd_queue_msk = cpu_to_le32(mvmsta->tfd_queue_msk); + cmd.tid_disable_tx = cpu_to_le16(mvmsta->tid_disable_agg); + + ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA, CMD_ASYNC, + iwl_mvm_add_sta_cmd_size(mvm), &cmd); + if (!ret) { + IWL_DEBUG_TX_QUEUES(mvm, + "TXQ #%d is now aggregated again\n", + queue); + + /* Mark queue intenally as aggregating again */ + iwl_trans_txq_set_shared_mode(mvm->trans, queue, false); + } + } + + mvm->queue_info[queue].status = IWL_MVM_QUEUE_READY; +} + +/* + * Remove inactive TIDs of a given queue. + * If all queue TIDs are inactive - mark the queue as inactive + * If only some the queue TIDs are inactive - unmap them from the queue + * + * Returns %true if all TIDs were removed and the queue could be reused. + */ +static bool iwl_mvm_remove_inactive_tids(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvmsta, int queue, + unsigned long tid_bitmap, + unsigned long *unshare_queues, + unsigned long *changetid_queues) +{ + unsigned int tid; + + lockdep_assert_held(&mvmsta->lock); + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return false; + + /* Go over all non-active TIDs, incl. IWL_MAX_TID_COUNT (for mgmt) */ + for_each_set_bit(tid, &tid_bitmap, IWL_MAX_TID_COUNT + 1) { + /* If some TFDs are still queued - don't mark TID as inactive */ + if (iwl_mvm_tid_queued(mvm, &mvmsta->tid_data[tid])) + tid_bitmap &= ~BIT(tid); + + /* Don't mark as inactive any TID that has an active BA */ + if (mvmsta->tid_data[tid].state != IWL_AGG_OFF) + tid_bitmap &= ~BIT(tid); + } + + /* If all TIDs in the queue are inactive - return it can be reused */ + if (tid_bitmap == mvm->queue_info[queue].tid_bitmap) { + IWL_DEBUG_TX_QUEUES(mvm, "Queue %d is inactive\n", queue); + return true; + } + + /* + * If we are here, this is a shared queue and not all TIDs timed-out. + * Remove the ones that did. + */ + for_each_set_bit(tid, &tid_bitmap, IWL_MAX_TID_COUNT + 1) { + u16 q_tid_bitmap; + + mvmsta->tid_data[tid].txq_id = IWL_MVM_INVALID_QUEUE; + mvm->queue_info[queue].tid_bitmap &= ~BIT(tid); + + q_tid_bitmap = mvm->queue_info[queue].tid_bitmap; + + /* + * We need to take into account a situation in which a TXQ was + * allocated to TID x, and then turned shared by adding TIDs y + * and z. If TID x becomes inactive and is removed from the TXQ, + * ownership must be given to one of the remaining TIDs. + * This is mainly because if TID x continues - a new queue can't + * be allocated for it as long as it is an owner of another TXQ. + * + * Mark this queue in the right bitmap, we'll send the command + * to the firmware later. + */ + if (!(q_tid_bitmap & BIT(mvm->queue_info[queue].txq_tid))) + set_bit(queue, changetid_queues); + + IWL_DEBUG_TX_QUEUES(mvm, + "Removing inactive TID %d from shared Q:%d\n", + tid, queue); + } + + IWL_DEBUG_TX_QUEUES(mvm, + "TXQ #%d left with tid bitmap 0x%x\n", queue, + mvm->queue_info[queue].tid_bitmap); + + /* + * There may be different TIDs with the same mac queues, so make + * sure all TIDs have existing corresponding mac queues enabled + */ + tid_bitmap = mvm->queue_info[queue].tid_bitmap; + + /* If the queue is marked as shared - "unshare" it */ + if (hweight16(mvm->queue_info[queue].tid_bitmap) == 1 && + mvm->queue_info[queue].status == IWL_MVM_QUEUE_SHARED) { + IWL_DEBUG_TX_QUEUES(mvm, "Marking Q:%d for reconfig\n", + queue); + set_bit(queue, unshare_queues); + } + + return false; +} + +/* + * Check for inactivity - this includes checking if any queue + * can be unshared and finding one (and only one) that can be + * reused. + * This function is also invoked as a sort of clean-up task, + * in which case @alloc_for_sta is IWL_MVM_INVALID_STA. + * + * Returns the queue number, or -ENOSPC. + */ +static int iwl_mvm_inactivity_check(struct iwl_mvm *mvm, u8 alloc_for_sta) +{ + unsigned long now = jiffies; + unsigned long unshare_queues = 0; + unsigned long changetid_queues = 0; + int i, ret, free_queue = -ENOSPC; + struct ieee80211_sta *queue_owner = NULL; + + lockdep_assert_held(&mvm->mutex); + + if (iwl_mvm_has_new_tx_api(mvm)) + return -ENOSPC; + + rcu_read_lock(); + + /* we skip the CMD queue below by starting at 1 */ + BUILD_BUG_ON(IWL_MVM_DQA_CMD_QUEUE != 0); + + for (i = 1; i < IWL_MAX_HW_QUEUES; i++) { + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + u8 sta_id; + int tid; + unsigned long inactive_tid_bitmap = 0; + unsigned long queue_tid_bitmap; + + queue_tid_bitmap = mvm->queue_info[i].tid_bitmap; + if (!queue_tid_bitmap) + continue; + + /* If TXQ isn't in active use anyway - nothing to do here... */ + if (mvm->queue_info[i].status != IWL_MVM_QUEUE_READY && + mvm->queue_info[i].status != IWL_MVM_QUEUE_SHARED) + continue; + + /* Check to see if there are inactive TIDs on this queue */ + for_each_set_bit(tid, &queue_tid_bitmap, + IWL_MAX_TID_COUNT + 1) { + if (time_after(mvm->queue_info[i].last_frame_time[tid] + + IWL_MVM_DQA_QUEUE_TIMEOUT, now)) + continue; + + inactive_tid_bitmap |= BIT(tid); + } + + /* If all TIDs are active - finish check on this queue */ + if (!inactive_tid_bitmap) + continue; + + /* + * If we are here - the queue hadn't been served recently and is + * in use + */ + + sta_id = mvm->queue_info[i].ra_sta_id; + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + + /* + * If the STA doesn't exist anymore, it isn't an error. It could + * be that it was removed since getting the queues, and in this + * case it should've inactivated its queues anyway. + */ + if (IS_ERR_OR_NULL(sta)) + continue; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + + spin_lock_bh(&mvmsta->lock); + ret = iwl_mvm_remove_inactive_tids(mvm, mvmsta, i, + inactive_tid_bitmap, + &unshare_queues, + &changetid_queues); + if (ret && free_queue < 0) { + queue_owner = sta; + free_queue = i; + } + /* only unlock sta lock - we still need the queue info lock */ + spin_unlock_bh(&mvmsta->lock); + } + + + /* Reconfigure queues requiring reconfiguation */ + for_each_set_bit(i, &unshare_queues, IWL_MAX_HW_QUEUES) + iwl_mvm_unshare_queue(mvm, i); + for_each_set_bit(i, &changetid_queues, IWL_MAX_HW_QUEUES) + iwl_mvm_change_queue_tid(mvm, i); + + rcu_read_unlock(); + + if (free_queue >= 0 && alloc_for_sta != IWL_MVM_INVALID_STA) { + ret = iwl_mvm_free_inactive_queue(mvm, free_queue, queue_owner, + alloc_for_sta); + if (ret) + return ret; + } + + return free_queue; +} + +static int iwl_mvm_sta_alloc_queue(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, u8 ac, int tid) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_trans_txq_scd_cfg cfg = { + .fifo = iwl_mvm_mac_ac_to_tx_fifo(mvm, ac), + .sta_id = mvmsta->sta_id, + .tid = tid, + .frame_limit = IWL_FRAME_LIMIT, + }; + unsigned int wdg_timeout = + iwl_mvm_get_wd_timeout(mvm, mvmsta->vif, false, false); + int queue = -1; + u16 queue_tmp; + unsigned long disable_agg_tids = 0; + enum iwl_mvm_agg_state queue_state; + bool shared_queue = false, inc_ssn; + int ssn; + unsigned long tfd_queue_mask; + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (iwl_mvm_has_new_tx_api(mvm)) + return iwl_mvm_sta_alloc_queue_tvqm(mvm, sta, ac, tid); + + spin_lock_bh(&mvmsta->lock); + tfd_queue_mask = mvmsta->tfd_queue_msk; + ssn = IEEE80211_SEQ_TO_SN(mvmsta->tid_data[tid].seq_number); + spin_unlock_bh(&mvmsta->lock); + + if (tid == IWL_MAX_TID_COUNT) { + queue = iwl_mvm_find_free_queue(mvm, mvmsta->sta_id, + IWL_MVM_DQA_MIN_MGMT_QUEUE, + IWL_MVM_DQA_MAX_MGMT_QUEUE); + if (queue >= IWL_MVM_DQA_MIN_MGMT_QUEUE) + IWL_DEBUG_TX_QUEUES(mvm, "Found free MGMT queue #%d\n", + queue); + + /* If no such queue is found, we'll use a DATA queue instead */ + } + + if ((queue < 0 && mvmsta->reserved_queue != IEEE80211_INVAL_HW_QUEUE) && + (mvm->queue_info[mvmsta->reserved_queue].status == + IWL_MVM_QUEUE_RESERVED)) { + queue = mvmsta->reserved_queue; + mvm->queue_info[queue].reserved = true; + IWL_DEBUG_TX_QUEUES(mvm, "Using reserved queue #%d\n", queue); + } + + if (queue < 0) + queue = iwl_mvm_find_free_queue(mvm, mvmsta->sta_id, + IWL_MVM_DQA_MIN_DATA_QUEUE, + IWL_MVM_DQA_MAX_DATA_QUEUE); + if (queue < 0) { + /* try harder - perhaps kill an inactive queue */ + queue = iwl_mvm_inactivity_check(mvm, mvmsta->sta_id); + } + + /* No free queue - we'll have to share */ + if (queue <= 0) { + queue = iwl_mvm_get_shared_queue(mvm, tfd_queue_mask, ac); + if (queue > 0) { + shared_queue = true; + mvm->queue_info[queue].status = IWL_MVM_QUEUE_SHARED; + } + } + + /* + * Mark TXQ as ready, even though it hasn't been fully configured yet, + * to make sure no one else takes it. + * This will allow avoiding re-acquiring the lock at the end of the + * configuration. On error we'll mark it back as free. + */ + if (queue > 0 && !shared_queue) + mvm->queue_info[queue].status = IWL_MVM_QUEUE_READY; + + /* This shouldn't happen - out of queues */ + if (WARN_ON(queue <= 0)) { + IWL_ERR(mvm, "No available queues for tid %d on sta_id %d\n", + tid, cfg.sta_id); + return queue; + } + + /* + * Actual en/disablement of aggregations is through the ADD_STA HCMD, + * but for configuring the SCD to send A-MPDUs we need to mark the queue + * as aggregatable. + * Mark all DATA queues as allowing to be aggregated at some point + */ + cfg.aggregate = (queue >= IWL_MVM_DQA_MIN_DATA_QUEUE || + queue == IWL_MVM_DQA_BSS_CLIENT_QUEUE); + + IWL_DEBUG_TX_QUEUES(mvm, + "Allocating %squeue #%d to sta %d on tid %d\n", + shared_queue ? "shared " : "", queue, + mvmsta->sta_id, tid); + + if (shared_queue) { + /* Disable any open aggs on this queue */ + disable_agg_tids = iwl_mvm_get_queue_agg_tids(mvm, queue); + + if (disable_agg_tids) { + IWL_DEBUG_TX_QUEUES(mvm, "Disabling aggs on queue %d\n", + queue); + iwl_mvm_invalidate_sta_queue(mvm, queue, + disable_agg_tids, false); + } + } + + inc_ssn = iwl_mvm_enable_txq(mvm, sta, queue, ssn, &cfg, wdg_timeout); + + /* + * Mark queue as shared in transport if shared + * Note this has to be done after queue enablement because enablement + * can also set this value, and there is no indication there to shared + * queues + */ + if (shared_queue) + iwl_trans_txq_set_shared_mode(mvm->trans, queue, true); + + spin_lock_bh(&mvmsta->lock); + /* + * This looks racy, but it is not. We have only one packet for + * this ra/tid in our Tx path since we stop the Qdisc when we + * need to allocate a new TFD queue. + */ + if (inc_ssn) { + mvmsta->tid_data[tid].seq_number += 0x10; + ssn = (ssn + 1) & IEEE80211_SCTL_SEQ; + } + mvmsta->tid_data[tid].txq_id = queue; + mvmsta->tfd_queue_msk |= BIT(queue); + queue_state = mvmsta->tid_data[tid].state; + + if (mvmsta->reserved_queue == queue) + mvmsta->reserved_queue = IEEE80211_INVAL_HW_QUEUE; + spin_unlock_bh(&mvmsta->lock); + + if (!shared_queue) { + ret = iwl_mvm_sta_send_to_fw(mvm, sta, true, STA_MODIFY_QUEUES); + if (ret) + goto out_err; + + /* If we need to re-enable aggregations... */ + if (queue_state == IWL_AGG_ON) { + ret = iwl_mvm_sta_tx_agg(mvm, sta, tid, queue, true); + if (ret) + goto out_err; + } + } else { + /* Redirect queue, if needed */ + ret = iwl_mvm_redirect_queue(mvm, queue, tid, ac, ssn, + wdg_timeout, false, + iwl_mvm_txq_from_tid(sta, tid)); + if (ret) + goto out_err; + } + + return 0; + +out_err: + queue_tmp = queue; + iwl_mvm_disable_txq(mvm, sta, &queue_tmp, tid); + + return ret; +} + +void iwl_mvm_add_new_dqa_stream_wk(struct work_struct *wk) +{ + struct iwl_mvm *mvm = container_of(wk, struct iwl_mvm, + add_stream_wk); + + mutex_lock(&mvm->mutex); + + iwl_mvm_inactivity_check(mvm, IWL_MVM_INVALID_STA); + + while (!list_empty(&mvm->add_stream_txqs)) { + struct iwl_mvm_txq *mvmtxq; + struct ieee80211_txq *txq; + u8 tid; + + mvmtxq = list_first_entry(&mvm->add_stream_txqs, + struct iwl_mvm_txq, list); + + txq = container_of((void *)mvmtxq, struct ieee80211_txq, + drv_priv); + tid = txq->tid; + if (tid == IEEE80211_NUM_TIDS) + tid = IWL_MAX_TID_COUNT; + + /* + * We can't really do much here, but if this fails we can't + * transmit anyway - so just don't transmit the frame etc. + * and let them back up ... we've tried our best to allocate + * a queue in the function itself. + */ + if (iwl_mvm_sta_alloc_queue(mvm, txq->sta, txq->ac, tid)) { + spin_lock_bh(&mvm->add_stream_lock); + list_del_init(&mvmtxq->list); + spin_unlock_bh(&mvm->add_stream_lock); + continue; + } + + /* now we're ready, any remaining races/concurrency will be + * handled in iwl_mvm_mac_itxq_xmit() + */ + set_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state); + + local_bh_disable(); + spin_lock(&mvm->add_stream_lock); + list_del_init(&mvmtxq->list); + spin_unlock(&mvm->add_stream_lock); + + iwl_mvm_mac_itxq_xmit(mvm->hw, txq); + local_bh_enable(); + } + + mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_reserve_sta_stream(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + enum nl80211_iftype vif_type) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + int queue; + + /* queue reserving is disabled on new TX path */ + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return 0; + + /* run the general cleanup/unsharing of queues */ + iwl_mvm_inactivity_check(mvm, IWL_MVM_INVALID_STA); + + /* Make sure we have free resources for this STA */ + if (vif_type == NL80211_IFTYPE_STATION && !sta->tdls && + !mvm->queue_info[IWL_MVM_DQA_BSS_CLIENT_QUEUE].tid_bitmap && + (mvm->queue_info[IWL_MVM_DQA_BSS_CLIENT_QUEUE].status == + IWL_MVM_QUEUE_FREE)) + queue = IWL_MVM_DQA_BSS_CLIENT_QUEUE; + else + queue = iwl_mvm_find_free_queue(mvm, mvmsta->sta_id, + IWL_MVM_DQA_MIN_DATA_QUEUE, + IWL_MVM_DQA_MAX_DATA_QUEUE); + if (queue < 0) { + /* try again - this time kick out a queue if needed */ + queue = iwl_mvm_inactivity_check(mvm, mvmsta->sta_id); + if (queue < 0) { + IWL_ERR(mvm, "No available queues for new station\n"); + return -ENOSPC; + } + } + mvm->queue_info[queue].status = IWL_MVM_QUEUE_RESERVED; + + mvmsta->reserved_queue = queue; + + IWL_DEBUG_TX_QUEUES(mvm, "Reserving data queue #%d for sta_id %d\n", + queue, mvmsta->sta_id); + + return 0; +} + +/* + * In DQA mode, after a HW restart the queues should be allocated as before, in + * order to avoid race conditions when there are shared queues. This function + * does the re-mapping and queue allocation. + * + * Note that re-enabling aggregations isn't done in this function. + */ +static void iwl_mvm_realloc_queues_after_restart(struct iwl_mvm *mvm, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + unsigned int wdg = + iwl_mvm_get_wd_timeout(mvm, mvm_sta->vif, false, false); + int i; + struct iwl_trans_txq_scd_cfg cfg = { + .sta_id = mvm_sta->sta_id, + .frame_limit = IWL_FRAME_LIMIT, + }; + + /* Make sure reserved queue is still marked as such (if allocated) */ + if (mvm_sta->reserved_queue != IEEE80211_INVAL_HW_QUEUE) + mvm->queue_info[mvm_sta->reserved_queue].status = + IWL_MVM_QUEUE_RESERVED; + + for (i = 0; i <= IWL_MAX_TID_COUNT; i++) { + struct iwl_mvm_tid_data *tid_data = &mvm_sta->tid_data[i]; + int txq_id = tid_data->txq_id; + int ac; + + if (txq_id == IWL_MVM_INVALID_QUEUE) + continue; + + ac = tid_to_mac80211_ac[i]; + + if (iwl_mvm_has_new_tx_api(mvm)) { + IWL_DEBUG_TX_QUEUES(mvm, + "Re-mapping sta %d tid %d\n", + mvm_sta->sta_id, i); + txq_id = iwl_mvm_tvqm_enable_txq(mvm, mvm_sta->sta_id, + i, wdg); + /* + * on failures, just set it to IWL_MVM_INVALID_QUEUE + * to try again later, we have no other good way of + * failing here + */ + if (txq_id < 0) + txq_id = IWL_MVM_INVALID_QUEUE; + tid_data->txq_id = txq_id; + + /* + * Since we don't set the seq number after reset, and HW + * sets it now, FW reset will cause the seq num to start + * at 0 again, so driver will need to update it + * internally as well, so it keeps in sync with real val + */ + tid_data->seq_number = 0; + } else { + u16 seq = IEEE80211_SEQ_TO_SN(tid_data->seq_number); + + cfg.tid = i; + cfg.fifo = iwl_mvm_mac_ac_to_tx_fifo(mvm, ac); + cfg.aggregate = (txq_id >= IWL_MVM_DQA_MIN_DATA_QUEUE || + txq_id == + IWL_MVM_DQA_BSS_CLIENT_QUEUE); + + IWL_DEBUG_TX_QUEUES(mvm, + "Re-mapping sta %d tid %d to queue %d\n", + mvm_sta->sta_id, i, txq_id); + + iwl_mvm_enable_txq(mvm, sta, txq_id, seq, &cfg, wdg); + mvm->queue_info[txq_id].status = IWL_MVM_QUEUE_READY; + } + } +} + +static int iwl_mvm_add_int_sta_common(struct iwl_mvm *mvm, + struct iwl_mvm_int_sta *sta, + const u8 *addr, + u16 mac_id, u16 color) +{ + struct iwl_mvm_add_sta_cmd cmd; + int ret; + u32 status = ADD_STA_SUCCESS; + + lockdep_assert_held(&mvm->mutex); + + memset(&cmd, 0, sizeof(cmd)); + cmd.sta_id = sta->sta_id; + + if (iwl_fw_lookup_cmd_ver(mvm->fw, ADD_STA, 0) >= 12 && + sta->type == IWL_STA_AUX_ACTIVITY) + cmd.mac_id_n_color = cpu_to_le32(mac_id); + else + cmd.mac_id_n_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(mac_id, + color)); + + if (fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_STA_TYPE)) + cmd.station_type = sta->type; + + if (!iwl_mvm_has_new_tx_api(mvm)) + cmd.tfd_queue_msk = cpu_to_le32(sta->tfd_queue_msk); + cmd.tid_disable_tx = cpu_to_le16(0xffff); + + if (addr) + memcpy(cmd.addr, addr, ETH_ALEN); + + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, + iwl_mvm_add_sta_cmd_size(mvm), + &cmd, &status); + if (ret) + return ret; + + switch (status & IWL_ADD_STA_STATUS_MASK) { + case ADD_STA_SUCCESS: + IWL_DEBUG_INFO(mvm, "Internal station added.\n"); + return 0; + default: + ret = -EIO; + IWL_ERR(mvm, "Add internal station failed, status=0x%x\n", + status); + break; + } + return ret; +} + +int iwl_mvm_add_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_rxq_dup_data *dup_data; + int i, ret, sta_id; + bool sta_update = false; + unsigned int sta_flags = 0; + + lockdep_assert_held(&mvm->mutex); + + if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) + sta_id = iwl_mvm_find_free_sta_id(mvm, + ieee80211_vif_type_p2p(vif)); + else + sta_id = mvm_sta->sta_id; + + if (sta_id == IWL_MVM_INVALID_STA) + return -ENOSPC; + + spin_lock_init(&mvm_sta->lock); + + /* if this is a HW restart re-alloc existing queues */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { + struct iwl_mvm_int_sta tmp_sta = { + .sta_id = sta_id, + .type = mvm_sta->sta_type, + }; + + /* + * First add an empty station since allocating + * a queue requires a valid station + */ + ret = iwl_mvm_add_int_sta_common(mvm, &tmp_sta, sta->addr, + mvmvif->id, mvmvif->color); + if (ret) + goto err; + + iwl_mvm_realloc_queues_after_restart(mvm, sta); + sta_update = true; + sta_flags = iwl_mvm_has_new_tx_api(mvm) ? 0 : STA_MODIFY_QUEUES; + goto update_fw; + } + + mvm_sta->sta_id = sta_id; + mvm_sta->mac_id_n_color = FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color); + mvm_sta->vif = vif; + if (!mvm->trans->trans_cfg->gen2) + mvm_sta->max_agg_bufsize = LINK_QUAL_AGG_FRAME_LIMIT_DEF; + else + mvm_sta->max_agg_bufsize = LINK_QUAL_AGG_FRAME_LIMIT_GEN2_DEF; + mvm_sta->tx_protection = 0; + mvm_sta->tt_tx_protection = false; + mvm_sta->sta_type = sta->tdls ? IWL_STA_TDLS_LINK : IWL_STA_LINK; + + /* HW restart, don't assume the memory has been zeroed */ + mvm_sta->tid_disable_agg = 0xffff; /* No aggs at first */ + mvm_sta->tfd_queue_msk = 0; + + /* for HW restart - reset everything but the sequence number */ + for (i = 0; i <= IWL_MAX_TID_COUNT; i++) { + u16 seq = mvm_sta->tid_data[i].seq_number; + memset(&mvm_sta->tid_data[i], 0, sizeof(mvm_sta->tid_data[i])); + mvm_sta->tid_data[i].seq_number = seq; + + /* + * Mark all queues for this STA as unallocated and defer TX + * frames until the queue is allocated + */ + mvm_sta->tid_data[i].txq_id = IWL_MVM_INVALID_QUEUE; + } + + for (i = 0; i < ARRAY_SIZE(sta->txq); i++) { + struct iwl_mvm_txq *mvmtxq = + iwl_mvm_txq_from_mac80211(sta->txq[i]); + + mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE; + INIT_LIST_HEAD(&mvmtxq->list); + atomic_set(&mvmtxq->tx_request, 0); + } + + mvm_sta->agg_tids = 0; + + if (iwl_mvm_has_new_rx_api(mvm) && + !test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { + int q; + + dup_data = kcalloc(mvm->trans->num_rx_queues, + sizeof(*dup_data), GFP_KERNEL); + if (!dup_data) + return -ENOMEM; + /* + * Initialize all the last_seq values to 0xffff which can never + * compare equal to the frame's seq_ctrl in the check in + * iwl_mvm_is_dup() since the lower 4 bits are the fragment + * number and fragmented packets don't reach that function. + * + * This thus allows receiving a packet with seqno 0 and the + * retry bit set as the very first packet on a new TID. + */ + for (q = 0; q < mvm->trans->num_rx_queues; q++) + memset(dup_data[q].last_seq, 0xff, + sizeof(dup_data[q].last_seq)); + mvm_sta->dup_data = dup_data; + } + + if (!iwl_mvm_has_new_tx_api(mvm)) { + ret = iwl_mvm_reserve_sta_stream(mvm, sta, + ieee80211_vif_type_p2p(vif)); + if (ret) + goto err; + } + + /* + * if rs is registered with mac80211, then "add station" will be handled + * via the corresponding ops, otherwise need to notify rate scaling here + */ + if (iwl_mvm_has_tlc_offload(mvm)) + iwl_mvm_rs_add_sta(mvm, mvm_sta); + else + spin_lock_init(&mvm_sta->lq_sta.rs_drv.pers.lock); + + iwl_mvm_toggle_tx_ant(mvm, &mvm_sta->tx_ant); + +update_fw: + ret = iwl_mvm_sta_send_to_fw(mvm, sta, sta_update, sta_flags); + if (ret) + goto err; + + if (vif->type == NL80211_IFTYPE_STATION) { + if (!sta->tdls) { + WARN_ON(mvmvif->ap_sta_id != IWL_MVM_INVALID_STA); + mvmvif->ap_sta_id = sta_id; + } else { + WARN_ON(mvmvif->ap_sta_id == IWL_MVM_INVALID_STA); + } + } + + rcu_assign_pointer(mvm->fw_id_to_mac_id[sta_id], sta); + + return 0; + +err: + return ret; +} + +int iwl_mvm_drain_sta(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta, + bool drain) +{ + struct iwl_mvm_add_sta_cmd cmd = {}; + int ret; + u32 status; + + lockdep_assert_held(&mvm->mutex); + + cmd.mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color); + cmd.sta_id = mvmsta->sta_id; + cmd.add_modify = STA_MODE_MODIFY; + cmd.station_flags = drain ? cpu_to_le32(STA_FLG_DRAIN_FLOW) : 0; + cmd.station_flags_msk = cpu_to_le32(STA_FLG_DRAIN_FLOW); + + status = ADD_STA_SUCCESS; + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, + iwl_mvm_add_sta_cmd_size(mvm), + &cmd, &status); + if (ret) + return ret; + + switch (status & IWL_ADD_STA_STATUS_MASK) { + case ADD_STA_SUCCESS: + IWL_DEBUG_INFO(mvm, "Frames for staid %d will drained in fw\n", + mvmsta->sta_id); + break; + default: + ret = -EIO; + IWL_ERR(mvm, "Couldn't drain frames for staid %d\n", + mvmsta->sta_id); + break; + } + + return ret; +} + +/* + * Remove a station from the FW table. Before sending the command to remove + * the station validate that the station is indeed known to the driver (sanity + * only). + */ +static int iwl_mvm_rm_sta_common(struct iwl_mvm *mvm, u8 sta_id) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_rm_sta_cmd rm_sta_cmd = { + .sta_id = sta_id, + }; + int ret; + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + + /* Note: internal stations are marked as error values */ + if (!sta) { + IWL_ERR(mvm, "Invalid station id\n"); + return -EINVAL; + } + + ret = iwl_mvm_send_cmd_pdu(mvm, REMOVE_STA, 0, + sizeof(rm_sta_cmd), &rm_sta_cmd); + if (ret) { + IWL_ERR(mvm, "Failed to remove station. Id=%d\n", sta_id); + return ret; + } + + return 0; +} + +static void iwl_mvm_disable_sta_queues(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + int i; + + lockdep_assert_held(&mvm->mutex); + + for (i = 0; i < ARRAY_SIZE(mvm_sta->tid_data); i++) { + if (mvm_sta->tid_data[i].txq_id == IWL_MVM_INVALID_QUEUE) + continue; + + iwl_mvm_disable_txq(mvm, sta, &mvm_sta->tid_data[i].txq_id, i); + mvm_sta->tid_data[i].txq_id = IWL_MVM_INVALID_QUEUE; + } + + for (i = 0; i < ARRAY_SIZE(sta->txq); i++) { + struct iwl_mvm_txq *mvmtxq = + iwl_mvm_txq_from_mac80211(sta->txq[i]); + + spin_lock_bh(&mvm->add_stream_lock); + mvmtxq->txq_id = IWL_MVM_INVALID_QUEUE; + list_del_init(&mvmtxq->list); + clear_bit(IWL_MVM_TXQ_STATE_READY, &mvmtxq->state); + spin_unlock_bh(&mvm->add_stream_lock); + } +} + +int iwl_mvm_wait_sta_queues_empty(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvm_sta) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mvm_sta->tid_data); i++) { + u16 txq_id; + int ret; + + spin_lock_bh(&mvm_sta->lock); + txq_id = mvm_sta->tid_data[i].txq_id; + spin_unlock_bh(&mvm_sta->lock); + + if (txq_id == IWL_MVM_INVALID_QUEUE) + continue; + + ret = iwl_trans_wait_txq_empty(mvm->trans, txq_id); + if (ret) + return ret; + } + + return 0; +} + +int iwl_mvm_rm_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + u8 sta_id = mvm_sta->sta_id; + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (iwl_mvm_has_new_rx_api(mvm)) + kfree(mvm_sta->dup_data); + + ret = iwl_mvm_drain_sta(mvm, mvm_sta, true); + if (ret) + return ret; + + /* flush its queues here since we are freeing mvm_sta */ + ret = iwl_mvm_flush_sta(mvm, mvm_sta, false); + if (ret) + return ret; + if (iwl_mvm_has_new_tx_api(mvm)) { + ret = iwl_mvm_wait_sta_queues_empty(mvm, mvm_sta); + } else { + u32 q_mask = mvm_sta->tfd_queue_msk; + + ret = iwl_trans_wait_tx_queues_empty(mvm->trans, + q_mask); + } + if (ret) + return ret; + + ret = iwl_mvm_drain_sta(mvm, mvm_sta, false); + + iwl_mvm_disable_sta_queues(mvm, vif, sta); + + /* If there is a TXQ still marked as reserved - free it */ + if (mvm_sta->reserved_queue != IEEE80211_INVAL_HW_QUEUE) { + u8 reserved_txq = mvm_sta->reserved_queue; + enum iwl_mvm_queue_status *status; + + /* + * If no traffic has gone through the reserved TXQ - it + * is still marked as IWL_MVM_QUEUE_RESERVED, and + * should be manually marked as free again + */ + status = &mvm->queue_info[reserved_txq].status; + if (WARN((*status != IWL_MVM_QUEUE_RESERVED) && + (*status != IWL_MVM_QUEUE_FREE), + "sta_id %d reserved txq %d status %d", + sta_id, reserved_txq, *status)) + return -EINVAL; + + *status = IWL_MVM_QUEUE_FREE; + } + + if (vif->type == NL80211_IFTYPE_STATION && + mvmvif->ap_sta_id == sta_id) { + /* if associated - we can't remove the AP STA now */ + if (vif->cfg.assoc) + return ret; + + /* unassoc - go ahead - remove the AP STA now */ + mvmvif->ap_sta_id = IWL_MVM_INVALID_STA; + } + + /* + * This shouldn't happen - the TDLS channel switch should be canceled + * before the STA is removed. + */ + if (WARN_ON_ONCE(mvm->tdls_cs.peer.sta_id == sta_id)) { + mvm->tdls_cs.peer.sta_id = IWL_MVM_INVALID_STA; + cancel_delayed_work(&mvm->tdls_cs.dwork); + } + + /* + * Make sure that the tx response code sees the station as -EBUSY and + * calls the drain worker. + */ + spin_lock_bh(&mvm_sta->lock); + spin_unlock_bh(&mvm_sta->lock); + + ret = iwl_mvm_rm_sta_common(mvm, mvm_sta->sta_id); + RCU_INIT_POINTER(mvm->fw_id_to_mac_id[mvm_sta->sta_id], NULL); + + return ret; +} + +int iwl_mvm_rm_sta_id(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u8 sta_id) +{ + int ret = iwl_mvm_rm_sta_common(mvm, sta_id); + + lockdep_assert_held(&mvm->mutex); + + RCU_INIT_POINTER(mvm->fw_id_to_mac_id[sta_id], NULL); + return ret; +} + +int iwl_mvm_allocate_int_sta(struct iwl_mvm *mvm, + struct iwl_mvm_int_sta *sta, + u32 qmask, enum nl80211_iftype iftype, + enum iwl_sta_type type) +{ + if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) || + sta->sta_id == IWL_MVM_INVALID_STA) { + sta->sta_id = iwl_mvm_find_free_sta_id(mvm, iftype); + if (WARN_ON_ONCE(sta->sta_id == IWL_MVM_INVALID_STA)) + return -ENOSPC; + } + + sta->tfd_queue_msk = qmask; + sta->type = type; + + /* put a non-NULL value so iterating over the stations won't stop */ + rcu_assign_pointer(mvm->fw_id_to_mac_id[sta->sta_id], ERR_PTR(-EINVAL)); + return 0; +} + +void iwl_mvm_dealloc_int_sta(struct iwl_mvm *mvm, struct iwl_mvm_int_sta *sta) +{ + RCU_INIT_POINTER(mvm->fw_id_to_mac_id[sta->sta_id], NULL); + memset(sta, 0, sizeof(struct iwl_mvm_int_sta)); + sta->sta_id = IWL_MVM_INVALID_STA; +} + +static void iwl_mvm_enable_aux_snif_queue(struct iwl_mvm *mvm, u16 queue, + u8 sta_id, u8 fifo) +{ + unsigned int wdg_timeout = + mvm->trans->trans_cfg->base_params->wd_timeout; + struct iwl_trans_txq_scd_cfg cfg = { + .fifo = fifo, + .sta_id = sta_id, + .tid = IWL_MAX_TID_COUNT, + .aggregate = false, + .frame_limit = IWL_FRAME_LIMIT, + }; + + WARN_ON(iwl_mvm_has_new_tx_api(mvm)); + + iwl_mvm_enable_txq(mvm, NULL, queue, 0, &cfg, wdg_timeout); +} + +static int iwl_mvm_enable_aux_snif_queue_tvqm(struct iwl_mvm *mvm, u8 sta_id) +{ + unsigned int wdg_timeout = + mvm->trans->trans_cfg->base_params->wd_timeout; + + WARN_ON(!iwl_mvm_has_new_tx_api(mvm)); + + return iwl_mvm_tvqm_enable_txq(mvm, sta_id, IWL_MAX_TID_COUNT, + wdg_timeout); +} + +static int iwl_mvm_add_int_sta_with_queue(struct iwl_mvm *mvm, int macidx, + int maccolor, u8 *addr, + struct iwl_mvm_int_sta *sta, + u16 *queue, int fifo) +{ + int ret; + + /* Map queue to fifo - needs to happen before adding station */ + if (!iwl_mvm_has_new_tx_api(mvm)) + iwl_mvm_enable_aux_snif_queue(mvm, *queue, sta->sta_id, fifo); + + ret = iwl_mvm_add_int_sta_common(mvm, sta, addr, macidx, maccolor); + if (ret) { + if (!iwl_mvm_has_new_tx_api(mvm)) + iwl_mvm_disable_txq(mvm, NULL, queue, + IWL_MAX_TID_COUNT); + return ret; + } + + /* + * For 22000 firmware and on we cannot add queue to a station unknown + * to firmware so enable queue here - after the station was added + */ + if (iwl_mvm_has_new_tx_api(mvm)) { + int txq; + + txq = iwl_mvm_enable_aux_snif_queue_tvqm(mvm, sta->sta_id); + if (txq < 0) { + iwl_mvm_rm_sta_common(mvm, sta->sta_id); + return txq; + } + + *queue = txq; + } + + return 0; +} + +int iwl_mvm_add_aux_sta(struct iwl_mvm *mvm, u32 lmac_id) +{ + int ret; + + lockdep_assert_held(&mvm->mutex); + + /* Allocate aux station and assign to it the aux queue */ + ret = iwl_mvm_allocate_int_sta(mvm, &mvm->aux_sta, BIT(mvm->aux_queue), + NL80211_IFTYPE_UNSPECIFIED, + IWL_STA_AUX_ACTIVITY); + if (ret) + return ret; + + /* + * In CDB NICs we need to specify which lmac to use for aux activity + * using the mac_id argument place to send lmac_id to the function + */ + ret = iwl_mvm_add_int_sta_with_queue(mvm, lmac_id, 0, NULL, + &mvm->aux_sta, &mvm->aux_queue, + IWL_MVM_TX_FIFO_MCAST); + if (ret) { + iwl_mvm_dealloc_int_sta(mvm, &mvm->aux_sta); + return ret; + } + + return 0; +} + +int iwl_mvm_add_snif_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + lockdep_assert_held(&mvm->mutex); + + return iwl_mvm_add_int_sta_with_queue(mvm, mvmvif->id, mvmvif->color, + NULL, &mvm->snif_sta, + &mvm->snif_queue, + IWL_MVM_TX_FIFO_BE); +} + +int iwl_mvm_rm_snif_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON_ONCE(mvm->snif_sta.sta_id == IWL_MVM_INVALID_STA)) + return -EINVAL; + + iwl_mvm_disable_txq(mvm, NULL, &mvm->snif_queue, IWL_MAX_TID_COUNT); + ret = iwl_mvm_rm_sta_common(mvm, mvm->snif_sta.sta_id); + if (ret) + IWL_WARN(mvm, "Failed sending remove station\n"); + + return ret; +} + +int iwl_mvm_rm_aux_sta(struct iwl_mvm *mvm) +{ + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON_ONCE(mvm->aux_sta.sta_id == IWL_MVM_INVALID_STA)) + return -EINVAL; + + iwl_mvm_disable_txq(mvm, NULL, &mvm->aux_queue, IWL_MAX_TID_COUNT); + ret = iwl_mvm_rm_sta_common(mvm, mvm->aux_sta.sta_id); + if (ret) + IWL_WARN(mvm, "Failed sending remove station\n"); + iwl_mvm_dealloc_int_sta(mvm, &mvm->aux_sta); + + return ret; +} + +void iwl_mvm_dealloc_snif_sta(struct iwl_mvm *mvm) +{ + iwl_mvm_dealloc_int_sta(mvm, &mvm->snif_sta); +} + +/* + * Send the add station command for the vif's broadcast station. + * Assumes that the station was already allocated. + * + * @mvm: the mvm component + * @vif: the interface to which the broadcast station is added + * @bsta: the broadcast station to add. + */ +int iwl_mvm_send_add_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_int_sta *bsta = &mvmvif->bcast_sta; + static const u8 _baddr[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + const u8 *baddr = _baddr; + int queue; + int ret; + unsigned int wdg_timeout = + iwl_mvm_get_wd_timeout(mvm, vif, false, false); + struct iwl_trans_txq_scd_cfg cfg = { + .fifo = IWL_MVM_TX_FIFO_VO, + .sta_id = mvmvif->bcast_sta.sta_id, + .tid = IWL_MAX_TID_COUNT, + .aggregate = false, + .frame_limit = IWL_FRAME_LIMIT, + }; + + lockdep_assert_held(&mvm->mutex); + + if (!iwl_mvm_has_new_tx_api(mvm)) { + if (vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_ADHOC) { + queue = mvm->probe_queue; + } else if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { + queue = mvm->p2p_dev_queue; + } else { + WARN(1, "Missing required TXQ for adding bcast STA\n"); + return -EINVAL; + } + + bsta->tfd_queue_msk |= BIT(queue); + + iwl_mvm_enable_txq(mvm, NULL, queue, 0, &cfg, wdg_timeout); + } + + if (vif->type == NL80211_IFTYPE_ADHOC) + baddr = vif->bss_conf.bssid; + + if (WARN_ON_ONCE(bsta->sta_id == IWL_MVM_INVALID_STA)) + return -ENOSPC; + + ret = iwl_mvm_add_int_sta_common(mvm, bsta, baddr, + mvmvif->id, mvmvif->color); + if (ret) + return ret; + + /* + * For 22000 firmware and on we cannot add queue to a station unknown + * to firmware so enable queue here - after the station was added + */ + if (iwl_mvm_has_new_tx_api(mvm)) { + queue = iwl_mvm_tvqm_enable_txq(mvm, bsta->sta_id, + IWL_MAX_TID_COUNT, + wdg_timeout); + if (queue < 0) { + iwl_mvm_rm_sta_common(mvm, bsta->sta_id); + return queue; + } + + if (vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_ADHOC) + mvm->probe_queue = queue; + else if (vif->type == NL80211_IFTYPE_P2P_DEVICE) + mvm->p2p_dev_queue = queue; + } + + return 0; +} + +static void iwl_mvm_free_bcast_sta_queues(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u16 *queueptr, queue; + + lockdep_assert_held(&mvm->mutex); + + iwl_mvm_flush_sta(mvm, &mvmvif->bcast_sta, true); + + switch (vif->type) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_ADHOC: + queueptr = &mvm->probe_queue; + break; + case NL80211_IFTYPE_P2P_DEVICE: + queueptr = &mvm->p2p_dev_queue; + break; + default: + WARN(1, "Can't free bcast queue on vif type %d\n", + vif->type); + return; + } + + queue = *queueptr; + iwl_mvm_disable_txq(mvm, NULL, queueptr, IWL_MAX_TID_COUNT); + if (iwl_mvm_has_new_tx_api(mvm)) + return; + + WARN_ON(!(mvmvif->bcast_sta.tfd_queue_msk & BIT(queue))); + mvmvif->bcast_sta.tfd_queue_msk &= ~BIT(queue); +} + +/* Send the FW a request to remove the station from it's internal data + * structures, but DO NOT remove the entry from the local data structures. */ +int iwl_mvm_send_rm_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + lockdep_assert_held(&mvm->mutex); + + iwl_mvm_free_bcast_sta_queues(mvm, vif); + + ret = iwl_mvm_rm_sta_common(mvm, mvmvif->bcast_sta.sta_id); + if (ret) + IWL_WARN(mvm, "Failed sending remove station\n"); + return ret; +} + +int iwl_mvm_alloc_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + lockdep_assert_held(&mvm->mutex); + + return iwl_mvm_allocate_int_sta(mvm, &mvmvif->bcast_sta, 0, + ieee80211_vif_type_p2p(vif), + IWL_STA_GENERAL_PURPOSE); +} + +/* Allocate a new station entry for the broadcast station to the given vif, + * and send it to the FW. + * Note that each P2P mac should have its own broadcast station. + * + * @mvm: the mvm component + * @vif: the interface to which the broadcast station is added + * @bsta: the broadcast station to add. */ +int iwl_mvm_add_p2p_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_int_sta *bsta = &mvmvif->bcast_sta; + int ret; + + lockdep_assert_held(&mvm->mutex); + + ret = iwl_mvm_alloc_bcast_sta(mvm, vif); + if (ret) + return ret; + + ret = iwl_mvm_send_add_bcast_sta(mvm, vif); + + if (ret) + iwl_mvm_dealloc_int_sta(mvm, bsta); + + return ret; +} + +void iwl_mvm_dealloc_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + iwl_mvm_dealloc_int_sta(mvm, &mvmvif->bcast_sta); +} + +/* + * Send the FW a request to remove the station from it's internal data + * structures, and in addition remove it from the local data structure. + */ +int iwl_mvm_rm_p2p_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + int ret; + + lockdep_assert_held(&mvm->mutex); + + ret = iwl_mvm_send_rm_bcast_sta(mvm, vif); + + iwl_mvm_dealloc_bcast_sta(mvm, vif); + + return ret; +} + +/* + * Allocate a new station entry for the multicast station to the given vif, + * and send it to the FW. + * Note that each AP/GO mac should have its own multicast station. + * + * @mvm: the mvm component + * @vif: the interface to which the multicast station is added + */ +int iwl_mvm_add_mcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_int_sta *msta = &mvmvif->mcast_sta; + static const u8 _maddr[] = {0x03, 0x00, 0x00, 0x00, 0x00, 0x00}; + const u8 *maddr = _maddr; + struct iwl_trans_txq_scd_cfg cfg = { + .fifo = vif->type == NL80211_IFTYPE_AP ? + IWL_MVM_TX_FIFO_MCAST : IWL_MVM_TX_FIFO_BE, + .sta_id = msta->sta_id, + .tid = 0, + .aggregate = false, + .frame_limit = IWL_FRAME_LIMIT, + }; + unsigned int timeout = iwl_mvm_get_wd_timeout(mvm, vif, false, false); + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON(vif->type != NL80211_IFTYPE_AP && + vif->type != NL80211_IFTYPE_ADHOC)) + return -ENOTSUPP; + + /* + * In IBSS, ieee80211_check_queues() sets the cab_queue to be + * invalid, so make sure we use the queue we want. + * Note that this is done here as we want to avoid making DQA + * changes in mac80211 layer. + */ + if (vif->type == NL80211_IFTYPE_ADHOC) + mvmvif->cab_queue = IWL_MVM_DQA_GCAST_QUEUE; + + /* + * While in previous FWs we had to exclude cab queue from TFD queue + * mask, now it is needed as any other queue. + */ + if (!iwl_mvm_has_new_tx_api(mvm) && + fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_STA_TYPE)) { + iwl_mvm_enable_txq(mvm, NULL, mvmvif->cab_queue, 0, &cfg, + timeout); + msta->tfd_queue_msk |= BIT(mvmvif->cab_queue); + } + ret = iwl_mvm_add_int_sta_common(mvm, msta, maddr, + mvmvif->id, mvmvif->color); + if (ret) + goto err; + + /* + * Enable cab queue after the ADD_STA command is sent. + * This is needed for 22000 firmware which won't accept SCD_QUEUE_CFG + * command with unknown station id, and for FW that doesn't support + * station API since the cab queue is not included in the + * tfd_queue_mask. + */ + if (iwl_mvm_has_new_tx_api(mvm)) { + int queue = iwl_mvm_tvqm_enable_txq(mvm, msta->sta_id, + 0, + timeout); + if (queue < 0) { + ret = queue; + goto err; + } + mvmvif->cab_queue = queue; + } else if (!fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_STA_TYPE)) + iwl_mvm_enable_txq(mvm, NULL, mvmvif->cab_queue, 0, &cfg, + timeout); + + return 0; +err: + iwl_mvm_dealloc_int_sta(mvm, msta); + return ret; +} + +static int __iwl_mvm_remove_sta_key(struct iwl_mvm *mvm, u8 sta_id, + struct ieee80211_key_conf *keyconf, + bool mcast) +{ + union { + struct iwl_mvm_add_sta_key_cmd_v1 cmd_v1; + struct iwl_mvm_add_sta_key_cmd cmd; + } u = {}; + bool new_api = fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_TKIP_MIC_KEYS); + __le16 key_flags; + int ret, size; + u32 status; + + /* This is a valid situation for GTK removal */ + if (sta_id == IWL_MVM_INVALID_STA) + return 0; + + key_flags = cpu_to_le16((keyconf->keyidx << STA_KEY_FLG_KEYID_POS) & + STA_KEY_FLG_KEYID_MSK); + key_flags |= cpu_to_le16(STA_KEY_FLG_NO_ENC | STA_KEY_FLG_WEP_KEY_MAP); + key_flags |= cpu_to_le16(STA_KEY_NOT_VALID); + + if (mcast) + key_flags |= cpu_to_le16(STA_KEY_MULTICAST); + + /* + * The fields assigned here are in the same location at the start + * of the command, so we can do this union trick. + */ + u.cmd.common.key_flags = key_flags; + u.cmd.common.key_offset = keyconf->hw_key_idx; + u.cmd.common.sta_id = sta_id; + + size = new_api ? sizeof(u.cmd) : sizeof(u.cmd_v1); + + status = ADD_STA_SUCCESS; + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA_KEY, size, &u.cmd, + &status); + + switch (status) { + case ADD_STA_SUCCESS: + IWL_DEBUG_WEP(mvm, "MODIFY_STA: remove sta key passed\n"); + break; + default: + ret = -EIO; + IWL_ERR(mvm, "MODIFY_STA: remove sta key failed\n"); + break; + } + + return ret; +} + +/* + * Send the FW a request to remove the station from it's internal data + * structures, and in addition remove it from the local data structure. + */ +int iwl_mvm_rm_mcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + lockdep_assert_held(&mvm->mutex); + + iwl_mvm_flush_sta(mvm, &mvmvif->mcast_sta, true); + + iwl_mvm_disable_txq(mvm, NULL, &mvmvif->cab_queue, 0); + + ret = iwl_mvm_rm_sta_common(mvm, mvmvif->mcast_sta.sta_id); + if (ret) + IWL_WARN(mvm, "Failed sending remove station\n"); + + return ret; +} + +static void iwl_mvm_sync_rxq_del_ba(struct iwl_mvm *mvm, u8 baid) +{ + struct iwl_mvm_delba_data notif = { + .baid = baid, + }; + + iwl_mvm_sync_rx_queues_internal(mvm, IWL_MVM_RXQ_NOTIF_DEL_BA, true, + ¬if, sizeof(notif)); +}; + +static void iwl_mvm_free_reorder(struct iwl_mvm *mvm, + struct iwl_mvm_baid_data *data) +{ + int i; + + iwl_mvm_sync_rxq_del_ba(mvm, data->baid); + + for (i = 0; i < mvm->trans->num_rx_queues; i++) { + int j; + struct iwl_mvm_reorder_buffer *reorder_buf = + &data->reorder_buf[i]; + struct iwl_mvm_reorder_buf_entry *entries = + &data->entries[i * data->entries_per_queue]; + + spin_lock_bh(&reorder_buf->lock); + if (likely(!reorder_buf->num_stored)) { + spin_unlock_bh(&reorder_buf->lock); + continue; + } + + /* + * This shouldn't happen in regular DELBA since the internal + * delBA notification should trigger a release of all frames in + * the reorder buffer. + */ + WARN_ON(1); + + for (j = 0; j < reorder_buf->buf_size; j++) + __skb_queue_purge(&entries[j].e.frames); + /* + * Prevent timer re-arm. This prevents a very far fetched case + * where we timed out on the notification. There may be prior + * RX frames pending in the RX queue before the notification + * that might get processed between now and the actual deletion + * and we would re-arm the timer although we are deleting the + * reorder buffer. + */ + reorder_buf->removed = true; + spin_unlock_bh(&reorder_buf->lock); + del_timer_sync(&reorder_buf->reorder_timer); + } +} + +static void iwl_mvm_init_reorder_buffer(struct iwl_mvm *mvm, + struct iwl_mvm_baid_data *data, + u16 ssn, u16 buf_size) +{ + int i; + + for (i = 0; i < mvm->trans->num_rx_queues; i++) { + struct iwl_mvm_reorder_buffer *reorder_buf = + &data->reorder_buf[i]; + struct iwl_mvm_reorder_buf_entry *entries = + &data->entries[i * data->entries_per_queue]; + int j; + + reorder_buf->num_stored = 0; + reorder_buf->head_sn = ssn; + reorder_buf->buf_size = buf_size; + /* rx reorder timer */ + timer_setup(&reorder_buf->reorder_timer, + iwl_mvm_reorder_timer_expired, 0); + spin_lock_init(&reorder_buf->lock); + reorder_buf->mvm = mvm; + reorder_buf->queue = i; + reorder_buf->valid = false; + for (j = 0; j < reorder_buf->buf_size; j++) + __skb_queue_head_init(&entries[j].e.frames); + } +} + +static int iwl_mvm_fw_baid_op_sta(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvm_sta, + bool start, int tid, u16 ssn, + u16 buf_size) +{ + struct iwl_mvm_add_sta_cmd cmd = { + .mac_id_n_color = cpu_to_le32(mvm_sta->mac_id_n_color), + .sta_id = mvm_sta->sta_id, + .add_modify = STA_MODE_MODIFY, + }; + u32 status; + int ret; + + if (start) { + cmd.add_immediate_ba_tid = tid; + cmd.add_immediate_ba_ssn = cpu_to_le16(ssn); + cmd.rx_ba_window = cpu_to_le16(buf_size); + cmd.modify_mask = STA_MODIFY_ADD_BA_TID; + } else { + cmd.remove_immediate_ba_tid = tid; + cmd.modify_mask = STA_MODIFY_REMOVE_BA_TID; + } + + status = ADD_STA_SUCCESS; + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, + iwl_mvm_add_sta_cmd_size(mvm), + &cmd, &status); + if (ret) + return ret; + + switch (status & IWL_ADD_STA_STATUS_MASK) { + case ADD_STA_SUCCESS: + IWL_DEBUG_HT(mvm, "RX BA Session %sed in fw\n", + start ? "start" : "stopp"); + if (WARN_ON(start && iwl_mvm_has_new_rx_api(mvm) && + !(status & IWL_ADD_STA_BAID_VALID_MASK))) + return -EINVAL; + return u32_get_bits(status, IWL_ADD_STA_BAID_MASK); + case ADD_STA_IMMEDIATE_BA_FAILURE: + IWL_WARN(mvm, "RX BA Session refused by fw\n"); + return -ENOSPC; + default: + IWL_ERR(mvm, "RX BA Session failed %sing, status 0x%x\n", + start ? "start" : "stopp", status); + return -EIO; + } +} + +static int iwl_mvm_fw_baid_op_cmd(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvm_sta, + bool start, int tid, u16 ssn, + u16 buf_size, int baid) +{ + struct iwl_rx_baid_cfg_cmd cmd = { + .action = start ? cpu_to_le32(IWL_RX_BAID_ACTION_ADD) : + cpu_to_le32(IWL_RX_BAID_ACTION_REMOVE), + }; + u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, RX_BAID_ALLOCATION_CONFIG_CMD); + int ret; + + BUILD_BUG_ON(sizeof(struct iwl_rx_baid_cfg_resp) != sizeof(baid)); + + if (start) { + cmd.alloc.sta_id_mask = cpu_to_le32(BIT(mvm_sta->sta_id)); + cmd.alloc.tid = tid; + cmd.alloc.ssn = cpu_to_le16(ssn); + cmd.alloc.win_size = cpu_to_le16(buf_size); + baid = -EIO; + } else if (iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, 1) == 1) { + cmd.remove_v1.baid = cpu_to_le32(baid); + BUILD_BUG_ON(sizeof(cmd.remove_v1) > sizeof(cmd.remove)); + } else { + cmd.remove.sta_id_mask = cpu_to_le32(BIT(mvm_sta->sta_id)); + cmd.remove.tid = cpu_to_le32(tid); + } + + ret = iwl_mvm_send_cmd_pdu_status(mvm, cmd_id, sizeof(cmd), + &cmd, &baid); + if (ret) + return ret; + + if (!start) { + /* ignore firmware baid on remove */ + baid = 0; + } + + IWL_DEBUG_HT(mvm, "RX BA Session %sed in fw\n", + start ? "start" : "stopp"); + + if (baid < 0 || baid >= ARRAY_SIZE(mvm->baid_map)) + return -EINVAL; + + return baid; +} + +static int iwl_mvm_fw_baid_op(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvm_sta, + bool start, int tid, u16 ssn, u16 buf_size, + int baid) +{ + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_BAID_ML_SUPPORT)) + return iwl_mvm_fw_baid_op_cmd(mvm, mvm_sta, start, + tid, ssn, buf_size, baid); + + return iwl_mvm_fw_baid_op_sta(mvm, mvm_sta, start, + tid, ssn, buf_size); +} + +int iwl_mvm_sta_rx_agg(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + int tid, u16 ssn, bool start, u16 buf_size, u16 timeout) +{ + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_baid_data *baid_data = NULL; + int ret, baid; + u32 max_ba_id_sessions = iwl_mvm_has_new_tx_api(mvm) ? IWL_MAX_BAID : + IWL_MAX_BAID_OLD; + + lockdep_assert_held(&mvm->mutex); + + if (start && mvm->rx_ba_sessions >= max_ba_id_sessions) { + IWL_WARN(mvm, "Not enough RX BA SESSIONS\n"); + return -ENOSPC; + } + + if (iwl_mvm_has_new_rx_api(mvm) && start) { + u32 reorder_buf_size = buf_size * sizeof(baid_data->entries[0]); + + /* sparse doesn't like the __align() so don't check */ +#ifndef __CHECKER__ + /* + * The division below will be OK if either the cache line size + * can be divided by the entry size (ALIGN will round up) or if + * if the entry size can be divided by the cache line size, in + * which case the ALIGN() will do nothing. + */ + BUILD_BUG_ON(SMP_CACHE_BYTES % sizeof(baid_data->entries[0]) && + sizeof(baid_data->entries[0]) % SMP_CACHE_BYTES); +#endif + + /* + * Upward align the reorder buffer size to fill an entire cache + * line for each queue, to avoid sharing cache lines between + * different queues. + */ + reorder_buf_size = ALIGN(reorder_buf_size, SMP_CACHE_BYTES); + + /* + * Allocate here so if allocation fails we can bail out early + * before starting the BA session in the firmware + */ + baid_data = kzalloc(sizeof(*baid_data) + + mvm->trans->num_rx_queues * + reorder_buf_size, + GFP_KERNEL); + if (!baid_data) + return -ENOMEM; + + /* + * This division is why we need the above BUILD_BUG_ON(), + * if that doesn't hold then this will not be right. + */ + baid_data->entries_per_queue = + reorder_buf_size / sizeof(baid_data->entries[0]); + } + + if (iwl_mvm_has_new_rx_api(mvm) && !start) { + baid = mvm_sta->tid_to_baid[tid]; + } else { + /* we don't really need it in this case */ + baid = -1; + } + + /* Don't send command to remove (start=0) BAID during restart */ + if (start || !test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) + baid = iwl_mvm_fw_baid_op(mvm, mvm_sta, start, tid, ssn, buf_size, + baid); + + if (baid < 0) { + ret = baid; + goto out_free; + } + + if (start) { + mvm->rx_ba_sessions++; + + if (!iwl_mvm_has_new_rx_api(mvm)) + return 0; + + baid_data->baid = baid; + baid_data->timeout = timeout; + baid_data->last_rx = jiffies; + baid_data->rcu_ptr = &mvm->baid_map[baid]; + timer_setup(&baid_data->session_timer, + iwl_mvm_rx_agg_session_expired, 0); + baid_data->mvm = mvm; + baid_data->tid = tid; + baid_data->sta_id = mvm_sta->sta_id; + + mvm_sta->tid_to_baid[tid] = baid; + if (timeout) + mod_timer(&baid_data->session_timer, + TU_TO_EXP_TIME(timeout * 2)); + + iwl_mvm_init_reorder_buffer(mvm, baid_data, ssn, buf_size); + /* + * protect the BA data with RCU to cover a case where our + * internal RX sync mechanism will timeout (not that it's + * supposed to happen) and we will free the session data while + * RX is being processed in parallel + */ + IWL_DEBUG_HT(mvm, "Sta %d(%d) is assigned to BAID %d\n", + mvm_sta->sta_id, tid, baid); + WARN_ON(rcu_access_pointer(mvm->baid_map[baid])); + rcu_assign_pointer(mvm->baid_map[baid], baid_data); + } else { + baid = mvm_sta->tid_to_baid[tid]; + + if (mvm->rx_ba_sessions > 0) + /* check that restart flow didn't zero the counter */ + mvm->rx_ba_sessions--; + if (!iwl_mvm_has_new_rx_api(mvm)) + return 0; + + if (WARN_ON(baid == IWL_RX_REORDER_DATA_INVALID_BAID)) + return -EINVAL; + + baid_data = rcu_access_pointer(mvm->baid_map[baid]); + if (WARN_ON(!baid_data)) + return -EINVAL; + + /* synchronize all rx queues so we can safely delete */ + iwl_mvm_free_reorder(mvm, baid_data); + del_timer_sync(&baid_data->session_timer); + RCU_INIT_POINTER(mvm->baid_map[baid], NULL); + kfree_rcu(baid_data, rcu_head); + IWL_DEBUG_HT(mvm, "BAID %d is free\n", baid); + + /* + * After we've deleted it, do another queue sync + * so if an IWL_MVM_RXQ_NSSN_SYNC was concurrently + * running it won't find a new session in the old + * BAID. It can find the NULL pointer for the BAID, + * but we must not have it find a different session. + */ + iwl_mvm_sync_rx_queues_internal(mvm, IWL_MVM_RXQ_EMPTY, + true, NULL, 0); + } + return 0; + +out_free: + kfree(baid_data); + return ret; +} + +int iwl_mvm_sta_tx_agg(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + int tid, u8 queue, bool start) +{ + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_add_sta_cmd cmd = {}; + int ret; + u32 status; + + lockdep_assert_held(&mvm->mutex); + + if (start) { + mvm_sta->tfd_queue_msk |= BIT(queue); + mvm_sta->tid_disable_agg &= ~BIT(tid); + } else { + /* In DQA-mode the queue isn't removed on agg termination */ + mvm_sta->tid_disable_agg |= BIT(tid); + } + + cmd.mac_id_n_color = cpu_to_le32(mvm_sta->mac_id_n_color); + cmd.sta_id = mvm_sta->sta_id; + cmd.add_modify = STA_MODE_MODIFY; + if (!iwl_mvm_has_new_tx_api(mvm)) + cmd.modify_mask = STA_MODIFY_QUEUES; + cmd.modify_mask |= STA_MODIFY_TID_DISABLE_TX; + cmd.tfd_queue_msk = cpu_to_le32(mvm_sta->tfd_queue_msk); + cmd.tid_disable_tx = cpu_to_le16(mvm_sta->tid_disable_agg); + + status = ADD_STA_SUCCESS; + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, + iwl_mvm_add_sta_cmd_size(mvm), + &cmd, &status); + if (ret) + return ret; + + switch (status & IWL_ADD_STA_STATUS_MASK) { + case ADD_STA_SUCCESS: + break; + default: + ret = -EIO; + IWL_ERR(mvm, "TX BA Session failed %sing, status 0x%x\n", + start ? "start" : "stopp", status); + break; + } + + return ret; +} + +const u8 tid_to_mac80211_ac[] = { + IEEE80211_AC_BE, + IEEE80211_AC_BK, + IEEE80211_AC_BK, + IEEE80211_AC_BE, + IEEE80211_AC_VI, + IEEE80211_AC_VI, + IEEE80211_AC_VO, + IEEE80211_AC_VO, + IEEE80211_AC_VO, /* We treat MGMT as TID 8, which is set as AC_VO */ +}; + +static const u8 tid_to_ucode_ac[] = { + AC_BE, + AC_BK, + AC_BK, + AC_BE, + AC_VI, + AC_VI, + AC_VO, + AC_VO, +}; + +int iwl_mvm_sta_tx_agg_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 *ssn) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_tid_data *tid_data; + u16 normalized_ssn; + u16 txq_id; + int ret; + + if (WARN_ON_ONCE(tid >= IWL_MAX_TID_COUNT)) + return -EINVAL; + + if (mvmsta->tid_data[tid].state != IWL_AGG_QUEUED && + mvmsta->tid_data[tid].state != IWL_AGG_OFF) { + IWL_ERR(mvm, + "Start AGG when state is not IWL_AGG_QUEUED or IWL_AGG_OFF %d!\n", + mvmsta->tid_data[tid].state); + return -ENXIO; + } + + lockdep_assert_held(&mvm->mutex); + + if (mvmsta->tid_data[tid].txq_id == IWL_MVM_INVALID_QUEUE && + iwl_mvm_has_new_tx_api(mvm)) { + u8 ac = tid_to_mac80211_ac[tid]; + + ret = iwl_mvm_sta_alloc_queue_tvqm(mvm, sta, ac, tid); + if (ret) + return ret; + } + + spin_lock_bh(&mvmsta->lock); + + /* + * Note the possible cases: + * 1. An enabled TXQ - TXQ needs to become agg'ed + * 2. The TXQ hasn't yet been enabled, so find a free one and mark + * it as reserved + */ + txq_id = mvmsta->tid_data[tid].txq_id; + if (txq_id == IWL_MVM_INVALID_QUEUE) { + ret = iwl_mvm_find_free_queue(mvm, mvmsta->sta_id, + IWL_MVM_DQA_MIN_DATA_QUEUE, + IWL_MVM_DQA_MAX_DATA_QUEUE); + if (ret < 0) { + IWL_ERR(mvm, "Failed to allocate agg queue\n"); + goto out; + } + + txq_id = ret; + + /* TXQ hasn't yet been enabled, so mark it only as reserved */ + mvm->queue_info[txq_id].status = IWL_MVM_QUEUE_RESERVED; + } else if (WARN_ON(txq_id >= IWL_MAX_HW_QUEUES)) { + ret = -ENXIO; + IWL_ERR(mvm, "tid_id %d out of range (0, %d)!\n", + tid, IWL_MAX_HW_QUEUES - 1); + goto out; + + } else if (unlikely(mvm->queue_info[txq_id].status == + IWL_MVM_QUEUE_SHARED)) { + ret = -ENXIO; + IWL_DEBUG_TX_QUEUES(mvm, + "Can't start tid %d agg on shared queue!\n", + tid); + goto out; + } + + IWL_DEBUG_TX_QUEUES(mvm, + "AGG for tid %d will be on queue #%d\n", + tid, txq_id); + + tid_data = &mvmsta->tid_data[tid]; + tid_data->ssn = IEEE80211_SEQ_TO_SN(tid_data->seq_number); + tid_data->txq_id = txq_id; + *ssn = tid_data->ssn; + + IWL_DEBUG_TX_QUEUES(mvm, + "Start AGG: sta %d tid %d queue %d - ssn = %d, next_recl = %d\n", + mvmsta->sta_id, tid, txq_id, tid_data->ssn, + tid_data->next_reclaimed); + + /* + * In 22000 HW, the next_reclaimed index is only 8 bit, so we'll need + * to align the wrap around of ssn so we compare relevant values. + */ + normalized_ssn = tid_data->ssn; + if (mvm->trans->trans_cfg->gen2) + normalized_ssn &= 0xff; + + if (normalized_ssn == tid_data->next_reclaimed) { + tid_data->state = IWL_AGG_STARTING; + ret = IEEE80211_AMPDU_TX_START_IMMEDIATE; + } else { + tid_data->state = IWL_EMPTYING_HW_QUEUE_ADDBA; + ret = IEEE80211_AMPDU_TX_START_DELAY_ADDBA; + } + +out: + spin_unlock_bh(&mvmsta->lock); + + return ret; +} + +int iwl_mvm_sta_tx_agg_oper(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 buf_size, + bool amsdu) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; + unsigned int wdg_timeout = + iwl_mvm_get_wd_timeout(mvm, vif, sta->tdls, false); + int queue, ret; + bool alloc_queue = true; + enum iwl_mvm_queue_status queue_status; + u16 ssn; + + struct iwl_trans_txq_scd_cfg cfg = { + .sta_id = mvmsta->sta_id, + .tid = tid, + .frame_limit = buf_size, + .aggregate = true, + }; + + /* + * When FW supports TLC_OFFLOAD, it also implements Tx aggregation + * manager, so this function should never be called in this case. + */ + if (WARN_ON_ONCE(iwl_mvm_has_tlc_offload(mvm))) + return -EINVAL; + + BUILD_BUG_ON((sizeof(mvmsta->agg_tids) * BITS_PER_BYTE) + != IWL_MAX_TID_COUNT); + + spin_lock_bh(&mvmsta->lock); + ssn = tid_data->ssn; + queue = tid_data->txq_id; + tid_data->state = IWL_AGG_ON; + mvmsta->agg_tids |= BIT(tid); + tid_data->ssn = 0xffff; + tid_data->amsdu_in_ampdu_allowed = amsdu; + spin_unlock_bh(&mvmsta->lock); + + if (iwl_mvm_has_new_tx_api(mvm)) { + /* + * If there is no queue for this tid, iwl_mvm_sta_tx_agg_start() + * would have failed, so if we are here there is no need to + * allocate a queue. + * However, if aggregation size is different than the default + * size, the scheduler should be reconfigured. + * We cannot do this with the new TX API, so return unsupported + * for now, until it will be offloaded to firmware.. + * Note that if SCD default value changes - this condition + * should be updated as well. + */ + if (buf_size < IWL_FRAME_LIMIT) + return -ENOTSUPP; + + ret = iwl_mvm_sta_tx_agg(mvm, sta, tid, queue, true); + if (ret) + return -EIO; + goto out; + } + + cfg.fifo = iwl_mvm_ac_to_tx_fifo[tid_to_mac80211_ac[tid]]; + + queue_status = mvm->queue_info[queue].status; + + /* Maybe there is no need to even alloc a queue... */ + if (mvm->queue_info[queue].status == IWL_MVM_QUEUE_READY) + alloc_queue = false; + + /* + * Only reconfig the SCD for the queue if the window size has + * changed from current (become smaller) + */ + if (!alloc_queue && buf_size < IWL_FRAME_LIMIT) { + /* + * If reconfiguring an existing queue, it first must be + * drained + */ + ret = iwl_trans_wait_tx_queues_empty(mvm->trans, + BIT(queue)); + if (ret) { + IWL_ERR(mvm, + "Error draining queue before reconfig\n"); + return ret; + } + + ret = iwl_mvm_reconfig_scd(mvm, queue, cfg.fifo, + mvmsta->sta_id, tid, + buf_size, ssn); + if (ret) { + IWL_ERR(mvm, + "Error reconfiguring TXQ #%d\n", queue); + return ret; + } + } + + if (alloc_queue) + iwl_mvm_enable_txq(mvm, sta, queue, ssn, + &cfg, wdg_timeout); + + /* Send ADD_STA command to enable aggs only if the queue isn't shared */ + if (queue_status != IWL_MVM_QUEUE_SHARED) { + ret = iwl_mvm_sta_tx_agg(mvm, sta, tid, queue, true); + if (ret) + return -EIO; + } + + /* No need to mark as reserved */ + mvm->queue_info[queue].status = IWL_MVM_QUEUE_READY; + +out: + /* + * Even though in theory the peer could have different + * aggregation reorder buffer sizes for different sessions, + * our ucode doesn't allow for that and has a global limit + * for each station. Therefore, use the minimum of all the + * aggregation sessions and our default value. + */ + mvmsta->max_agg_bufsize = + min(mvmsta->max_agg_bufsize, buf_size); + mvmsta->lq_sta.rs_drv.lq.agg_frame_cnt_limit = mvmsta->max_agg_bufsize; + + IWL_DEBUG_HT(mvm, "Tx aggregation enabled on ra = %pM tid = %d\n", + sta->addr, tid); + + return iwl_mvm_send_lq_cmd(mvm, &mvmsta->lq_sta.rs_drv.lq); +} + +static void iwl_mvm_unreserve_agg_queue(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvmsta, + struct iwl_mvm_tid_data *tid_data) +{ + u16 txq_id = tid_data->txq_id; + + lockdep_assert_held(&mvm->mutex); + + if (iwl_mvm_has_new_tx_api(mvm)) + return; + + /* + * The TXQ is marked as reserved only if no traffic came through yet + * This means no traffic has been sent on this TID (agg'd or not), so + * we no longer have use for the queue. Since it hasn't even been + * allocated through iwl_mvm_enable_txq, so we can just mark it back as + * free. + */ + if (mvm->queue_info[txq_id].status == IWL_MVM_QUEUE_RESERVED) { + mvm->queue_info[txq_id].status = IWL_MVM_QUEUE_FREE; + tid_data->txq_id = IWL_MVM_INVALID_QUEUE; + } +} + +int iwl_mvm_sta_tx_agg_stop(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; + u16 txq_id; + int err; + + /* + * If mac80211 is cleaning its state, then say that we finished since + * our state has been cleared anyway. + */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + return 0; + } + + spin_lock_bh(&mvmsta->lock); + + txq_id = tid_data->txq_id; + + IWL_DEBUG_TX_QUEUES(mvm, "Stop AGG: sta %d tid %d q %d state %d\n", + mvmsta->sta_id, tid, txq_id, tid_data->state); + + mvmsta->agg_tids &= ~BIT(tid); + + iwl_mvm_unreserve_agg_queue(mvm, mvmsta, tid_data); + + switch (tid_data->state) { + case IWL_AGG_ON: + tid_data->ssn = IEEE80211_SEQ_TO_SN(tid_data->seq_number); + + IWL_DEBUG_TX_QUEUES(mvm, + "ssn = %d, next_recl = %d\n", + tid_data->ssn, tid_data->next_reclaimed); + + tid_data->ssn = 0xffff; + tid_data->state = IWL_AGG_OFF; + spin_unlock_bh(&mvmsta->lock); + + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + + iwl_mvm_sta_tx_agg(mvm, sta, tid, txq_id, false); + return 0; + case IWL_AGG_STARTING: + case IWL_EMPTYING_HW_QUEUE_ADDBA: + /* + * The agg session has been stopped before it was set up. This + * can happen when the AddBA timer times out for example. + */ + + /* No barriers since we are under mutex */ + lockdep_assert_held(&mvm->mutex); + + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + tid_data->state = IWL_AGG_OFF; + err = 0; + break; + default: + IWL_ERR(mvm, + "Stopping AGG while state not ON or starting for %d on %d (%d)\n", + mvmsta->sta_id, tid, tid_data->state); + IWL_ERR(mvm, + "\ttid_data->txq_id = %d\n", tid_data->txq_id); + err = -EINVAL; + } + + spin_unlock_bh(&mvmsta->lock); + + return err; +} + +int iwl_mvm_sta_tx_agg_flush(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; + u16 txq_id; + enum iwl_mvm_agg_state old_state; + + /* + * First set the agg state to OFF to avoid calling + * ieee80211_stop_tx_ba_cb in iwl_mvm_check_ratid_empty. + */ + spin_lock_bh(&mvmsta->lock); + txq_id = tid_data->txq_id; + IWL_DEBUG_TX_QUEUES(mvm, "Flush AGG: sta %d tid %d q %d state %d\n", + mvmsta->sta_id, tid, txq_id, tid_data->state); + old_state = tid_data->state; + tid_data->state = IWL_AGG_OFF; + mvmsta->agg_tids &= ~BIT(tid); + spin_unlock_bh(&mvmsta->lock); + + iwl_mvm_unreserve_agg_queue(mvm, mvmsta, tid_data); + + if (old_state >= IWL_AGG_ON) { + iwl_mvm_drain_sta(mvm, mvmsta, true); + + if (iwl_mvm_has_new_tx_api(mvm)) { + if (iwl_mvm_flush_sta_tids(mvm, mvmsta->sta_id, + BIT(tid))) + IWL_ERR(mvm, "Couldn't flush the AGG queue\n"); + iwl_trans_wait_txq_empty(mvm->trans, txq_id); + } else { + if (iwl_mvm_flush_tx_path(mvm, BIT(txq_id))) + IWL_ERR(mvm, "Couldn't flush the AGG queue\n"); + iwl_trans_wait_tx_queues_empty(mvm->trans, BIT(txq_id)); + } + + iwl_mvm_drain_sta(mvm, mvmsta, false); + + iwl_mvm_sta_tx_agg(mvm, sta, tid, txq_id, false); + } + + return 0; +} + +static int iwl_mvm_set_fw_key_idx(struct iwl_mvm *mvm) +{ + int i, max = -1, max_offs = -1; + + lockdep_assert_held(&mvm->mutex); + + /* Pick the unused key offset with the highest 'deleted' + * counter. Every time a key is deleted, all the counters + * are incremented and the one that was just deleted is + * reset to zero. Thus, the highest counter is the one + * that was deleted longest ago. Pick that one. + */ + for (i = 0; i < STA_KEY_MAX_NUM; i++) { + if (test_bit(i, mvm->fw_key_table)) + continue; + if (mvm->fw_key_deleted[i] > max) { + max = mvm->fw_key_deleted[i]; + max_offs = i; + } + } + + if (max_offs < 0) + return STA_KEY_IDX_INVALID; + + return max_offs; +} + +static struct iwl_mvm_sta *iwl_mvm_get_key_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (sta) + return iwl_mvm_sta_from_mac80211(sta); + + /* + * The device expects GTKs for station interfaces to be + * installed as GTKs for the AP station. If we have no + * station ID, then use AP's station ID. + */ + if (vif->type == NL80211_IFTYPE_STATION && + mvmvif->ap_sta_id != IWL_MVM_INVALID_STA) { + u8 sta_id = mvmvif->ap_sta_id; + + sta = rcu_dereference_check(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + + /* + * It is possible that the 'sta' parameter is NULL, + * for example when a GTK is removed - the sta_id will then + * be the AP ID, and no station was passed by mac80211. + */ + if (IS_ERR_OR_NULL(sta)) + return NULL; + + return iwl_mvm_sta_from_mac80211(sta); + } + + return NULL; +} + +static int iwl_mvm_pn_cmp(const u8 *pn1, const u8 *pn2, int len) +{ + int i; + + for (i = len - 1; i >= 0; i--) { + if (pn1[i] > pn2[i]) + return 1; + if (pn1[i] < pn2[i]) + return -1; + } + + return 0; +} + +static int iwl_mvm_send_sta_key(struct iwl_mvm *mvm, + u32 sta_id, + struct ieee80211_key_conf *key, bool mcast, + u32 tkip_iv32, u16 *tkip_p1k, u32 cmd_flags, + u8 key_offset, bool mfp) +{ + union { + struct iwl_mvm_add_sta_key_cmd_v1 cmd_v1; + struct iwl_mvm_add_sta_key_cmd cmd; + } u = {}; + __le16 key_flags; + int ret; + u32 status; + u16 keyidx; + u64 pn = 0; + int i, size; + bool new_api = fw_has_api(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_API_TKIP_MIC_KEYS); + int api_ver = iwl_fw_lookup_cmd_ver(mvm->fw, ADD_STA_KEY, + new_api ? 2 : 1); + + if (sta_id == IWL_MVM_INVALID_STA) + return -EINVAL; + + keyidx = (key->keyidx << STA_KEY_FLG_KEYID_POS) & + STA_KEY_FLG_KEYID_MSK; + key_flags = cpu_to_le16(keyidx); + key_flags |= cpu_to_le16(STA_KEY_FLG_WEP_KEY_MAP); + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_TKIP: + key_flags |= cpu_to_le16(STA_KEY_FLG_TKIP); + if (api_ver >= 2) { + memcpy((void *)&u.cmd.tx_mic_key, + &key->key[NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY], + IWL_MIC_KEY_SIZE); + + memcpy((void *)&u.cmd.rx_mic_key, + &key->key[NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY], + IWL_MIC_KEY_SIZE); + pn = atomic64_read(&key->tx_pn); + + } else { + u.cmd_v1.tkip_rx_tsc_byte2 = tkip_iv32; + for (i = 0; i < 5; i++) + u.cmd_v1.tkip_rx_ttak[i] = + cpu_to_le16(tkip_p1k[i]); + } + memcpy(u.cmd.common.key, key->key, key->keylen); + break; + case WLAN_CIPHER_SUITE_CCMP: + key_flags |= cpu_to_le16(STA_KEY_FLG_CCM); + memcpy(u.cmd.common.key, key->key, key->keylen); + if (api_ver >= 2) + pn = atomic64_read(&key->tx_pn); + break; + case WLAN_CIPHER_SUITE_WEP104: + key_flags |= cpu_to_le16(STA_KEY_FLG_WEP_13BYTES); + fallthrough; + case WLAN_CIPHER_SUITE_WEP40: + key_flags |= cpu_to_le16(STA_KEY_FLG_WEP); + memcpy(u.cmd.common.key + 3, key->key, key->keylen); + break; + case WLAN_CIPHER_SUITE_GCMP_256: + key_flags |= cpu_to_le16(STA_KEY_FLG_KEY_32BYTES); + fallthrough; + case WLAN_CIPHER_SUITE_GCMP: + key_flags |= cpu_to_le16(STA_KEY_FLG_GCMP); + memcpy(u.cmd.common.key, key->key, key->keylen); + if (api_ver >= 2) + pn = atomic64_read(&key->tx_pn); + break; + default: + key_flags |= cpu_to_le16(STA_KEY_FLG_EXT); + memcpy(u.cmd.common.key, key->key, key->keylen); + } + + if (mcast) + key_flags |= cpu_to_le16(STA_KEY_MULTICAST); + if (mfp) + key_flags |= cpu_to_le16(STA_KEY_MFP); + + u.cmd.common.key_offset = key_offset; + u.cmd.common.key_flags = key_flags; + u.cmd.common.sta_id = sta_id; + + if (key->cipher == WLAN_CIPHER_SUITE_TKIP) + i = 0; + else + i = -1; + + for (; i < IEEE80211_NUM_TIDS; i++) { + struct ieee80211_key_seq seq = {}; + u8 _rx_pn[IEEE80211_MAX_PN_LEN] = {}, *rx_pn = _rx_pn; + int rx_pn_len = 8; + /* there's a hole at 2/3 in FW format depending on version */ + int hole = api_ver >= 3 ? 0 : 2; + + ieee80211_get_key_rx_seq(key, i, &seq); + + if (key->cipher == WLAN_CIPHER_SUITE_TKIP) { + rx_pn[0] = seq.tkip.iv16; + rx_pn[1] = seq.tkip.iv16 >> 8; + rx_pn[2 + hole] = seq.tkip.iv32; + rx_pn[3 + hole] = seq.tkip.iv32 >> 8; + rx_pn[4 + hole] = seq.tkip.iv32 >> 16; + rx_pn[5 + hole] = seq.tkip.iv32 >> 24; + } else if (key_flags & cpu_to_le16(STA_KEY_FLG_EXT)) { + rx_pn = seq.hw.seq; + rx_pn_len = seq.hw.seq_len; + } else { + rx_pn[0] = seq.ccmp.pn[0]; + rx_pn[1] = seq.ccmp.pn[1]; + rx_pn[2 + hole] = seq.ccmp.pn[2]; + rx_pn[3 + hole] = seq.ccmp.pn[3]; + rx_pn[4 + hole] = seq.ccmp.pn[4]; + rx_pn[5 + hole] = seq.ccmp.pn[5]; + } + + if (iwl_mvm_pn_cmp(rx_pn, (u8 *)&u.cmd.common.rx_secur_seq_cnt, + rx_pn_len) > 0) + memcpy(&u.cmd.common.rx_secur_seq_cnt, rx_pn, + rx_pn_len); + } + + if (api_ver >= 2) { + u.cmd.transmit_seq_cnt = cpu_to_le64(pn); + size = sizeof(u.cmd); + } else { + size = sizeof(u.cmd_v1); + } + + status = ADD_STA_SUCCESS; + if (cmd_flags & CMD_ASYNC) + ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA_KEY, CMD_ASYNC, size, + &u.cmd); + else + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA_KEY, size, + &u.cmd, &status); + + switch (status) { + case ADD_STA_SUCCESS: + IWL_DEBUG_WEP(mvm, "MODIFY_STA: set dynamic key passed\n"); + break; + default: + ret = -EIO; + IWL_ERR(mvm, "MODIFY_STA: set dynamic key failed\n"); + break; + } + + return ret; +} + +static int iwl_mvm_send_sta_igtk(struct iwl_mvm *mvm, + struct ieee80211_key_conf *keyconf, + u8 sta_id, bool remove_key) +{ + struct iwl_mvm_mgmt_mcast_key_cmd igtk_cmd = {}; + + /* verify the key details match the required command's expectations */ + if (WARN_ON((keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE) || + (keyconf->keyidx != 4 && keyconf->keyidx != 5 && + keyconf->keyidx != 6 && keyconf->keyidx != 7) || + (keyconf->cipher != WLAN_CIPHER_SUITE_AES_CMAC && + keyconf->cipher != WLAN_CIPHER_SUITE_BIP_GMAC_128 && + keyconf->cipher != WLAN_CIPHER_SUITE_BIP_GMAC_256))) + return -EINVAL; + + if (WARN_ON(!iwl_mvm_has_new_rx_api(mvm) && + keyconf->cipher != WLAN_CIPHER_SUITE_AES_CMAC)) + return -EINVAL; + + igtk_cmd.key_id = cpu_to_le32(keyconf->keyidx); + igtk_cmd.sta_id = cpu_to_le32(sta_id); + + if (remove_key) { + /* This is a valid situation for IGTK */ + if (sta_id == IWL_MVM_INVALID_STA) + return 0; + + igtk_cmd.ctrl_flags |= cpu_to_le32(STA_KEY_NOT_VALID); + } else { + struct ieee80211_key_seq seq; + const u8 *pn; + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_AES_CMAC: + igtk_cmd.ctrl_flags |= cpu_to_le32(STA_KEY_FLG_CCM); + break; + case WLAN_CIPHER_SUITE_BIP_GMAC_128: + case WLAN_CIPHER_SUITE_BIP_GMAC_256: + igtk_cmd.ctrl_flags |= cpu_to_le32(STA_KEY_FLG_GCMP); + break; + default: + return -EINVAL; + } + + memcpy(igtk_cmd.igtk, keyconf->key, keyconf->keylen); + if (keyconf->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_256) + igtk_cmd.ctrl_flags |= + cpu_to_le32(STA_KEY_FLG_KEY_32BYTES); + ieee80211_get_key_rx_seq(keyconf, 0, &seq); + pn = seq.aes_cmac.pn; + igtk_cmd.receive_seq_cnt = cpu_to_le64(((u64) pn[5] << 0) | + ((u64) pn[4] << 8) | + ((u64) pn[3] << 16) | + ((u64) pn[2] << 24) | + ((u64) pn[1] << 32) | + ((u64) pn[0] << 40)); + } + + IWL_DEBUG_INFO(mvm, "%s %sIGTK (%d) for sta %u\n", + remove_key ? "removing" : "installing", + keyconf->keyidx >= 6 ? "B" : "", + keyconf->keyidx, igtk_cmd.sta_id); + + if (!iwl_mvm_has_new_rx_api(mvm)) { + struct iwl_mvm_mgmt_mcast_key_cmd_v1 igtk_cmd_v1 = { + .ctrl_flags = igtk_cmd.ctrl_flags, + .key_id = igtk_cmd.key_id, + .sta_id = igtk_cmd.sta_id, + .receive_seq_cnt = igtk_cmd.receive_seq_cnt + }; + + memcpy(igtk_cmd_v1.igtk, igtk_cmd.igtk, + ARRAY_SIZE(igtk_cmd_v1.igtk)); + return iwl_mvm_send_cmd_pdu(mvm, MGMT_MCAST_KEY, 0, + sizeof(igtk_cmd_v1), &igtk_cmd_v1); + } + return iwl_mvm_send_cmd_pdu(mvm, MGMT_MCAST_KEY, 0, + sizeof(igtk_cmd), &igtk_cmd); +} + + +static inline u8 *iwl_mvm_get_mac_addr(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (sta) + return sta->addr; + + if (vif->type == NL80211_IFTYPE_STATION && + mvmvif->ap_sta_id != IWL_MVM_INVALID_STA) { + u8 sta_id = mvmvif->ap_sta_id; + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + return sta->addr; + } + + + return NULL; +} + +static int __iwl_mvm_set_sta_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *keyconf, + u8 key_offset, + bool mcast) +{ + const u8 *addr; + struct ieee80211_key_seq seq; + u16 p1k[5]; + u32 sta_id; + bool mfp = false; + + if (sta) { + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + + sta_id = mvm_sta->sta_id; + mfp = sta->mfp; + } else if (vif->type == NL80211_IFTYPE_AP && + !(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE)) { + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + sta_id = mvmvif->mcast_sta.sta_id; + } else { + IWL_ERR(mvm, "Failed to find station id\n"); + return -EINVAL; + } + + if (keyconf->cipher == WLAN_CIPHER_SUITE_TKIP) { + addr = iwl_mvm_get_mac_addr(mvm, vif, sta); + /* get phase 1 key from mac80211 */ + ieee80211_get_key_rx_seq(keyconf, 0, &seq); + ieee80211_get_tkip_rx_p1k(keyconf, addr, seq.tkip.iv32, p1k); + + return iwl_mvm_send_sta_key(mvm, sta_id, keyconf, mcast, + seq.tkip.iv32, p1k, 0, key_offset, + mfp); + } + + return iwl_mvm_send_sta_key(mvm, sta_id, keyconf, mcast, + 0, NULL, 0, key_offset, mfp); +} + +int iwl_mvm_set_sta_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *keyconf, + u8 key_offset) +{ + bool mcast = !(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE); + struct iwl_mvm_sta *mvm_sta; + u8 sta_id = IWL_MVM_INVALID_STA; + int ret; + static const u8 __maybe_unused zero_addr[ETH_ALEN] = {0}; + + lockdep_assert_held(&mvm->mutex); + + if (vif->type != NL80211_IFTYPE_AP || + keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE) { + /* Get the station id from the mvm local station table */ + mvm_sta = iwl_mvm_get_key_sta(mvm, vif, sta); + if (!mvm_sta) { + IWL_ERR(mvm, "Failed to find station\n"); + return -EINVAL; + } + sta_id = mvm_sta->sta_id; + + /* + * It is possible that the 'sta' parameter is NULL, and thus + * there is a need to retrieve the sta from the local station + * table. + */ + if (!sta) { + sta = rcu_dereference_protected( + mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + if (IS_ERR_OR_NULL(sta)) { + IWL_ERR(mvm, "Invalid station id\n"); + return -EINVAL; + } + } + + if (WARN_ON_ONCE(iwl_mvm_sta_from_mac80211(sta)->vif != vif)) + return -EINVAL; + } else { + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + sta_id = mvmvif->mcast_sta.sta_id; + } + + if (keyconf->cipher == WLAN_CIPHER_SUITE_AES_CMAC || + keyconf->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_128 || + keyconf->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_256) { + ret = iwl_mvm_send_sta_igtk(mvm, keyconf, sta_id, false); + goto end; + } + + /* If the key_offset is not pre-assigned, we need to find a + * new offset to use. In normal cases, the offset is not + * pre-assigned, but during HW_RESTART we want to reuse the + * same indices, so we pass them when this function is called. + * + * In D3 entry, we need to hardcoded the indices (because the + * firmware hardcodes the PTK offset to 0). In this case, we + * need to make sure we don't overwrite the hw_key_idx in the + * keyconf structure, because otherwise we cannot configure + * the original ones back when resuming. + */ + if (key_offset == STA_KEY_IDX_INVALID) { + key_offset = iwl_mvm_set_fw_key_idx(mvm); + if (key_offset == STA_KEY_IDX_INVALID) + return -ENOSPC; + keyconf->hw_key_idx = key_offset; + } + + ret = __iwl_mvm_set_sta_key(mvm, vif, sta, keyconf, key_offset, mcast); + if (ret) + goto end; + + /* + * For WEP, the same key is used for multicast and unicast. Upload it + * again, using the same key offset, and now pointing the other one + * to the same key slot (offset). + * If this fails, remove the original as well. + */ + if ((keyconf->cipher == WLAN_CIPHER_SUITE_WEP40 || + keyconf->cipher == WLAN_CIPHER_SUITE_WEP104) && + sta) { + ret = __iwl_mvm_set_sta_key(mvm, vif, sta, keyconf, + key_offset, !mcast); + if (ret) { + __iwl_mvm_remove_sta_key(mvm, sta_id, keyconf, mcast); + goto end; + } + } + + __set_bit(key_offset, mvm->fw_key_table); + +end: + IWL_DEBUG_WEP(mvm, "key: cipher=%x len=%d idx=%d sta=%pM ret=%d\n", + keyconf->cipher, keyconf->keylen, keyconf->keyidx, + sta ? sta->addr : zero_addr, ret); + return ret; +} + +int iwl_mvm_remove_sta_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *keyconf) +{ + bool mcast = !(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE); + struct iwl_mvm_sta *mvm_sta; + u8 sta_id = IWL_MVM_INVALID_STA; + int ret, i; + + lockdep_assert_held(&mvm->mutex); + + /* Get the station from the mvm local station table */ + mvm_sta = iwl_mvm_get_key_sta(mvm, vif, sta); + if (mvm_sta) + sta_id = mvm_sta->sta_id; + else if (!sta && vif->type == NL80211_IFTYPE_AP && mcast) + sta_id = iwl_mvm_vif_from_mac80211(vif)->mcast_sta.sta_id; + + + IWL_DEBUG_WEP(mvm, "mvm remove dynamic key: idx=%d sta=%d\n", + keyconf->keyidx, sta_id); + + if (keyconf->cipher == WLAN_CIPHER_SUITE_AES_CMAC || + keyconf->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_128 || + keyconf->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_256) + return iwl_mvm_send_sta_igtk(mvm, keyconf, sta_id, true); + + if (!__test_and_clear_bit(keyconf->hw_key_idx, mvm->fw_key_table)) { + IWL_ERR(mvm, "offset %d not used in fw key table.\n", + keyconf->hw_key_idx); + return -ENOENT; + } + + /* track which key was deleted last */ + for (i = 0; i < STA_KEY_MAX_NUM; i++) { + if (mvm->fw_key_deleted[i] < U8_MAX) + mvm->fw_key_deleted[i]++; + } + mvm->fw_key_deleted[keyconf->hw_key_idx] = 0; + + if (sta && !mvm_sta) { + IWL_DEBUG_WEP(mvm, "station non-existent, early return.\n"); + return 0; + } + + ret = __iwl_mvm_remove_sta_key(mvm, sta_id, keyconf, mcast); + if (ret) + return ret; + + /* delete WEP key twice to get rid of (now useless) offset */ + if (keyconf->cipher == WLAN_CIPHER_SUITE_WEP40 || + keyconf->cipher == WLAN_CIPHER_SUITE_WEP104) + ret = __iwl_mvm_remove_sta_key(mvm, sta_id, keyconf, !mcast); + + return ret; +} + +void iwl_mvm_update_tkip_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, u32 iv32, + u16 *phase1key) +{ + struct iwl_mvm_sta *mvm_sta; + bool mcast = !(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE); + bool mfp = sta ? sta->mfp : false; + + rcu_read_lock(); + + mvm_sta = iwl_mvm_get_key_sta(mvm, vif, sta); + if (WARN_ON_ONCE(!mvm_sta)) + goto unlock; + iwl_mvm_send_sta_key(mvm, mvm_sta->sta_id, keyconf, mcast, + iv32, phase1key, CMD_ASYNC, keyconf->hw_key_idx, + mfp); + + unlock: + rcu_read_unlock(); +} + +void iwl_mvm_sta_modify_ps_wake(struct iwl_mvm *mvm, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_add_sta_cmd cmd = { + .add_modify = STA_MODE_MODIFY, + .sta_id = mvmsta->sta_id, + .station_flags_msk = cpu_to_le32(STA_FLG_PS), + .mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color), + }; + int ret; + + ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA, CMD_ASYNC, + iwl_mvm_add_sta_cmd_size(mvm), &cmd); + if (ret) + IWL_ERR(mvm, "Failed to send ADD_STA command (%d)\n", ret); +} + +void iwl_mvm_sta_modify_sleep_tx_count(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + enum ieee80211_frame_release_type reason, + u16 cnt, u16 tids, bool more_data, + bool single_sta_queue) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_add_sta_cmd cmd = { + .add_modify = STA_MODE_MODIFY, + .sta_id = mvmsta->sta_id, + .modify_mask = STA_MODIFY_SLEEPING_STA_TX_COUNT, + .sleep_tx_count = cpu_to_le16(cnt), + .mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color), + }; + int tid, ret; + unsigned long _tids = tids; + + /* convert TIDs to ACs - we don't support TSPEC so that's OK + * Note that this field is reserved and unused by firmware not + * supporting GO uAPSD, so it's safe to always do this. + */ + for_each_set_bit(tid, &_tids, IWL_MAX_TID_COUNT) + cmd.awake_acs |= BIT(tid_to_ucode_ac[tid]); + + /* If we're releasing frames from aggregation or dqa queues then check + * if all the queues that we're releasing frames from, combined, have: + * - more frames than the service period, in which case more_data + * needs to be set + * - fewer than 'cnt' frames, in which case we need to adjust the + * firmware command (but do that unconditionally) + */ + if (single_sta_queue) { + int remaining = cnt; + int sleep_tx_count; + + spin_lock_bh(&mvmsta->lock); + for_each_set_bit(tid, &_tids, IWL_MAX_TID_COUNT) { + struct iwl_mvm_tid_data *tid_data; + u16 n_queued; + + tid_data = &mvmsta->tid_data[tid]; + + n_queued = iwl_mvm_tid_queued(mvm, tid_data); + if (n_queued > remaining) { + more_data = true; + remaining = 0; + break; + } + remaining -= n_queued; + } + sleep_tx_count = cnt - remaining; + if (reason == IEEE80211_FRAME_RELEASE_UAPSD) + mvmsta->sleep_tx_count = sleep_tx_count; + spin_unlock_bh(&mvmsta->lock); + + cmd.sleep_tx_count = cpu_to_le16(sleep_tx_count); + if (WARN_ON(cnt - remaining == 0)) { + ieee80211_sta_eosp(sta); + return; + } + } + + /* Note: this is ignored by firmware not supporting GO uAPSD */ + if (more_data) + cmd.sleep_state_flags |= STA_SLEEP_STATE_MOREDATA; + + if (reason == IEEE80211_FRAME_RELEASE_PSPOLL) { + mvmsta->next_status_eosp = true; + cmd.sleep_state_flags |= STA_SLEEP_STATE_PS_POLL; + } else { + cmd.sleep_state_flags |= STA_SLEEP_STATE_UAPSD; + } + + /* block the Tx queues until the FW updated the sleep Tx count */ + iwl_trans_block_txq_ptrs(mvm->trans, true); + + ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA, + CMD_ASYNC | CMD_WANT_ASYNC_CALLBACK, + iwl_mvm_add_sta_cmd_size(mvm), &cmd); + if (ret) + IWL_ERR(mvm, "Failed to send ADD_STA command (%d)\n", ret); +} + +void iwl_mvm_rx_eosp_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mvm_eosp_notification *notif = (void *)pkt->data; + struct ieee80211_sta *sta; + u32 sta_id = le32_to_cpu(notif->sta_id); + + if (WARN_ON_ONCE(sta_id >= mvm->fw->ucode_capa.num_stations)) + return; + + rcu_read_lock(); + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + if (!IS_ERR_OR_NULL(sta)) + ieee80211_sta_eosp(sta); + rcu_read_unlock(); +} + +void iwl_mvm_sta_modify_disable_tx(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvmsta, bool disable) +{ + struct iwl_mvm_add_sta_cmd cmd = { + .add_modify = STA_MODE_MODIFY, + .sta_id = mvmsta->sta_id, + .station_flags = disable ? cpu_to_le32(STA_FLG_DISABLE_TX) : 0, + .station_flags_msk = cpu_to_le32(STA_FLG_DISABLE_TX), + .mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color), + }; + int ret; + + ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA, CMD_ASYNC, + iwl_mvm_add_sta_cmd_size(mvm), &cmd); + if (ret) + IWL_ERR(mvm, "Failed to send ADD_STA command (%d)\n", ret); +} + +void iwl_mvm_sta_modify_disable_tx_ap(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + bool disable) +{ + struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); + + spin_lock_bh(&mvm_sta->lock); + + if (mvm_sta->disable_tx == disable) { + spin_unlock_bh(&mvm_sta->lock); + return; + } + + mvm_sta->disable_tx = disable; + + /* + * If sta PS state is handled by mac80211, tell it to start/stop + * queuing tx for this station. + */ + if (!ieee80211_hw_check(mvm->hw, AP_LINK_PS)) + ieee80211_sta_block_awake(mvm->hw, sta, disable); + + iwl_mvm_sta_modify_disable_tx(mvm, mvm_sta, disable); + + spin_unlock_bh(&mvm_sta->lock); +} + +static void iwl_mvm_int_sta_modify_disable_tx(struct iwl_mvm *mvm, + struct iwl_mvm_vif *mvmvif, + struct iwl_mvm_int_sta *sta, + bool disable) +{ + u32 id = FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color); + struct iwl_mvm_add_sta_cmd cmd = { + .add_modify = STA_MODE_MODIFY, + .sta_id = sta->sta_id, + .station_flags = disable ? cpu_to_le32(STA_FLG_DISABLE_TX) : 0, + .station_flags_msk = cpu_to_le32(STA_FLG_DISABLE_TX), + .mac_id_n_color = cpu_to_le32(id), + }; + int ret; + + ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA, CMD_ASYNC, + iwl_mvm_add_sta_cmd_size(mvm), &cmd); + if (ret) + IWL_ERR(mvm, "Failed to send ADD_STA command (%d)\n", ret); +} + +void iwl_mvm_modify_all_sta_disable_tx(struct iwl_mvm *mvm, + struct iwl_mvm_vif *mvmvif, + bool disable) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvm_sta; + int i; + + rcu_read_lock(); + + /* Block/unblock all the stations of the given mvmvif */ + for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) { + sta = rcu_dereference(mvm->fw_id_to_mac_id[i]); + if (IS_ERR_OR_NULL(sta)) + continue; + + mvm_sta = iwl_mvm_sta_from_mac80211(sta); + if (mvm_sta->mac_id_n_color != + FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)) + continue; + + iwl_mvm_sta_modify_disable_tx_ap(mvm, sta, disable); + } + + rcu_read_unlock(); + + if (!fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_STA_TYPE)) + return; + + /* Need to block/unblock also multicast station */ + if (mvmvif->mcast_sta.sta_id != IWL_MVM_INVALID_STA) + iwl_mvm_int_sta_modify_disable_tx(mvm, mvmvif, + &mvmvif->mcast_sta, disable); + + /* + * Only unblock the broadcast station (FW blocks it for immediate + * quiet, not the driver) + */ + if (!disable && mvmvif->bcast_sta.sta_id != IWL_MVM_INVALID_STA) + iwl_mvm_int_sta_modify_disable_tx(mvm, mvmvif, + &mvmvif->bcast_sta, disable); +} + +void iwl_mvm_csa_client_absent(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_sta *mvmsta; + + rcu_read_lock(); + + mvmsta = iwl_mvm_sta_from_staid_rcu(mvm, mvmvif->ap_sta_id); + + if (mvmsta) + iwl_mvm_sta_modify_disable_tx(mvm, mvmsta, true); + + rcu_read_unlock(); +} + +u16 iwl_mvm_tid_queued(struct iwl_mvm *mvm, struct iwl_mvm_tid_data *tid_data) +{ + u16 sn = IEEE80211_SEQ_TO_SN(tid_data->seq_number); + + /* + * In 22000 HW, the next_reclaimed index is only 8 bit, so we'll need + * to align the wrap around of ssn so we compare relevant values. + */ + if (mvm->trans->trans_cfg->gen2) + sn &= 0xff; + + return ieee80211_sn_sub(sn, tid_data->next_reclaimed); +} + +int iwl_mvm_add_pasn_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_int_sta *sta, u8 *addr, u32 cipher, + u8 *key, u32 key_len) +{ + int ret; + u16 queue; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_key_conf *keyconf; + + ret = iwl_mvm_allocate_int_sta(mvm, sta, 0, + NL80211_IFTYPE_UNSPECIFIED, + IWL_STA_LINK); + if (ret) + return ret; + + ret = iwl_mvm_add_int_sta_with_queue(mvm, mvmvif->id, mvmvif->color, + addr, sta, &queue, + IWL_MVM_TX_FIFO_BE); + if (ret) + goto out; + + keyconf = kzalloc(sizeof(*keyconf) + key_len, GFP_KERNEL); + if (!keyconf) { + ret = -ENOBUFS; + goto out; + } + + keyconf->cipher = cipher; + memcpy(keyconf->key, key, key_len); + keyconf->keylen = key_len; + + ret = iwl_mvm_send_sta_key(mvm, sta->sta_id, keyconf, false, + 0, NULL, 0, 0, true); + kfree(keyconf); + return 0; +out: + iwl_mvm_dealloc_int_sta(mvm, sta); + return ret; +} + +void iwl_mvm_cancel_channel_switch(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 mac_id) +{ + struct iwl_cancel_channel_switch_cmd cancel_channel_switch_cmd = { + .mac_id = cpu_to_le32(mac_id), + }; + int ret; + + ret = iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(MAC_CONF_GROUP, CANCEL_CHANNEL_SWITCH_CMD), + CMD_ASYNC, + sizeof(cancel_channel_switch_cmd), + &cancel_channel_switch_cmd); + if (ret) + IWL_ERR(mvm, "Failed to cancel the channel switch\n"); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/sta.h b/drivers/net/wireless/intel/iwlwifi/mvm/sta.h new file mode 100644 index 000000000..f1a4fc3e4 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/sta.h @@ -0,0 +1,554 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015-2016 Intel Deutschland GmbH + */ +#ifndef __sta_h__ +#define __sta_h__ + +#include <linux/spinlock.h> +#include <net/mac80211.h> +#include <linux/wait.h> + +#include "iwl-trans.h" /* for IWL_MAX_TID_COUNT */ +#include "fw-api.h" /* IWL_MVM_STATION_COUNT_MAX */ +#include "rs.h" + +struct iwl_mvm; +struct iwl_mvm_vif; + +/** + * DOC: DQA - Dynamic Queue Allocation -introduction + * + * Dynamic Queue Allocation (AKA "DQA") is a feature implemented in iwlwifi + * driver to allow dynamic allocation of queues on-demand, rather than allocate + * them statically ahead of time. Ideally, we would like to allocate one queue + * per RA/TID, thus allowing an AP - for example - to send BE traffic to STA2 + * even if it also needs to send traffic to a sleeping STA1, without being + * blocked by the sleeping station. + * + * Although the queues in DQA mode are dynamically allocated, there are still + * some queues that are statically allocated: + * TXQ #0 - command queue + * TXQ #1 - aux frames + * TXQ #2 - P2P device frames + * TXQ #3 - P2P GO/SoftAP GCAST/BCAST frames + * TXQ #4 - BSS DATA frames queue + * TXQ #5-8 - Non-QoS and MGMT frames queue pool + * TXQ #9 - P2P GO/SoftAP probe responses + * TXQ #10-31 - DATA frames queue pool + * The queues are dynamically taken from either the MGMT frames queue pool or + * the DATA frames one. See the %iwl_mvm_dqa_txq for more information on every + * queue. + * + * When a frame for a previously unseen RA/TID comes in, it needs to be deferred + * until a queue is allocated for it, and only then can be TXed. Therefore, it + * is placed into %iwl_mvm_tid_data.deferred_tx_frames, and a worker called + * %mvm->add_stream_wk later allocates the queues and TXes the deferred frames. + * + * For convenience, MGMT is considered as if it has TID=8, and go to the MGMT + * queues in the pool. If there is no longer a free MGMT queue to allocate, a + * queue will be allocated from the DATA pool instead. Since QoS NDPs can create + * a problem for aggregations, they too will use a MGMT queue. + * + * When adding a STA, a DATA queue is reserved for it so that it can TX from + * it. If no such free queue exists for reserving, the STA addition will fail. + * + * If the DATA queue pool gets exhausted, no new STA will be accepted, and if a + * new RA/TID comes in for an existing STA, one of the STA's queues will become + * shared and will serve more than the single TID (but always for the same RA!). + * + * When a RA/TID needs to become aggregated, no new queue is required to be + * allocated, only mark the queue as aggregated via the ADD_STA command. Note, + * however, that a shared queue cannot be aggregated, and only after the other + * TIDs become inactive and are removed - only then can the queue be + * reconfigured and become aggregated. + * + * When removing a station, its queues are returned to the pool for reuse. Here + * we also need to make sure that we are synced with the worker thread that TXes + * the deferred frames so we don't get into a situation where the queues are + * removed and then the worker puts deferred frames onto the released queues or + * tries to allocate new queues for a STA we don't need anymore. + */ + +/** + * DOC: station table - introduction + * + * The station table is a list of data structure that reprensent the stations. + * In STA/P2P client mode, the driver will hold one station for the AP/ GO. + * In GO/AP mode, the driver will have as many stations as associated clients. + * All these stations are reflected in the fw's station table. The driver + * keeps the fw's station table up to date with the ADD_STA command. Stations + * can be removed by the REMOVE_STA command. + * + * All the data related to a station is held in the structure %iwl_mvm_sta + * which is embed in the mac80211's %ieee80211_sta (in the drv_priv) area. + * This data includes the index of the station in the fw, per tid information + * (sequence numbers, Block-ack state machine, etc...). The stations are + * created and deleted by the %sta_state callback from %ieee80211_ops. + * + * The driver holds a map: %fw_id_to_mac_id that allows to fetch a + * %ieee80211_sta (and the %iwl_mvm_sta embedded into it) based on a fw + * station index. That way, the driver is able to get the tid related data in + * O(1) in time sensitive paths (Tx / Tx response / BA notification). These + * paths are triggered by the fw, and the driver needs to get a pointer to the + * %ieee80211 structure. This map helps to get that pointer quickly. + */ + +/** + * DOC: station table - locking + * + * As stated before, the station is created / deleted by mac80211's %sta_state + * callback from %ieee80211_ops which can sleep. The next paragraph explains + * the locking of a single stations, the next ones relates to the station + * table. + * + * The station holds the sequence number per tid. So this data needs to be + * accessed in the Tx path (which is softIRQ). It also holds the Block-Ack + * information (the state machine / and the logic that checks if the queues + * were drained), so it also needs to be accessible from the Tx response flow. + * In short, the station needs to be access from sleepable context as well as + * from tasklets, so the station itself needs a spinlock. + * + * The writers of %fw_id_to_mac_id map are serialized by the global mutex of + * the mvm op_mode. This is possible since %sta_state can sleep. + * The pointers in this map are RCU protected, hence we won't replace the + * station while we have Tx / Tx response / BA notification running. + * + * If a station is deleted while it still has packets in its A-MPDU queues, + * then the reclaim flow will notice that there is no station in the map for + * sta_id and it will dump the responses. + */ + +/** + * DOC: station table - internal stations + * + * The FW needs a few internal stations that are not reflected in + * mac80211, such as broadcast station in AP / GO mode, or AUX sta for + * scanning and P2P device (during the GO negotiation). + * For these kind of stations we have %iwl_mvm_int_sta struct which holds the + * data relevant for them from both %iwl_mvm_sta and %ieee80211_sta. + * Usually the data for these stations is static, so no locking is required, + * and no TID data as this is also not needed. + * One thing to note, is that these stations have an ID in the fw, but not + * in mac80211. In order to "reserve" them a sta_id in %fw_id_to_mac_id + * we fill ERR_PTR(EINVAL) in this mapping and all other dereferencing of + * pointers from this mapping need to check that the value is not error + * or NULL. + * + * Currently there is only one auxiliary station for scanning, initialized + * on init. + */ + +/** + * DOC: station table - AP Station in STA mode + * + * %iwl_mvm_vif includes the index of the AP station in the fw's STA table: + * %ap_sta_id. To get the point to the corresponding %ieee80211_sta, + * &fw_id_to_mac_id can be used. Due to the way the fw works, we must not remove + * the AP station from the fw before setting the MAC context as unassociated. + * Hence, %fw_id_to_mac_id[%ap_sta_id] will be NULLed when the AP station is + * removed by mac80211, but the station won't be removed in the fw until the + * VIF is set as unassociated. Then, %ap_sta_id will be invalidated. + */ + +/** + * DOC: station table - Drain vs. Flush + * + * Flush means that all the frames in the SCD queue are dumped regardless the + * station to which they were sent. We do that when we disassociate and before + * we remove the STA of the AP. The flush can be done synchronously against the + * fw. + * Drain means that the fw will drop all the frames sent to a specific station. + * This is useful when a client (if we are IBSS / GO or AP) disassociates. + */ + +/** + * DOC: station table - fw restart + * + * When the fw asserts, or we have any other issue that requires to reset the + * driver, we require mac80211 to reconfigure the driver. Since the private + * data of the stations is embed in mac80211's %ieee80211_sta, that data will + * not be zeroed and needs to be reinitialized manually. + * %IWL_MVM_STATUS_IN_HW_RESTART is set during restart and that will hint us + * that we must not allocate a new sta_id but reuse the previous one. This + * means that the stations being re-added after the reset will have the same + * place in the fw as before the reset. We do need to zero the %fw_id_to_mac_id + * map, since the stations aren't in the fw any more. Internal stations that + * are not added by mac80211 will be re-added in the init flow that is called + * after the restart: mac80211 call's %iwl_mvm_mac_start which calls to + * %iwl_mvm_up. + */ + +/** + * DOC: AP mode - PS + * + * When a station is asleep, the fw will set it as "asleep". All frames on + * shared queues (i.e. non-aggregation queues) to that station will be dropped + * by the fw (%TX_STATUS_FAIL_DEST_PS failure code). + * + * AMPDUs are in a separate queue that is stopped by the fw. We just need to + * let mac80211 know when there are frames in these queues so that it can + * properly handle trigger frames. + * + * When a trigger frame is received, mac80211 tells the driver to send frames + * from the AMPDU queues or sends frames to non-aggregation queues itself, + * depending on which ACs are delivery-enabled and what TID has frames to + * transmit. Note that mac80211 has all the knowledge since all the non-agg + * frames are buffered / filtered, and the driver tells mac80211 about agg + * frames). The driver needs to tell the fw to let frames out even if the + * station is asleep. This is done by %iwl_mvm_sta_modify_sleep_tx_count. + * + * When we receive a frame from that station with PM bit unset, the driver + * needs to let the fw know that this station isn't asleep any more. This is + * done by %iwl_mvm_sta_modify_ps_wake in response to mac80211 signaling the + * station's wakeup. + * + * For a GO, the Service Period might be cut short due to an absence period + * of the GO. In this (and all other cases) the firmware notifies us with the + * EOSP_NOTIFICATION, and we notify mac80211 of that. Further frames that we + * already sent to the device will be rejected again. + * + * See also "AP support for powersaving clients" in mac80211.h. + */ + +/** + * enum iwl_mvm_agg_state + * + * The state machine of the BA agreement establishment / tear down. + * These states relate to a specific RA / TID. + * + * @IWL_AGG_OFF: aggregation is not used + * @IWL_AGG_QUEUED: aggregation start work has been queued + * @IWL_AGG_STARTING: aggregation are starting (between start and oper) + * @IWL_AGG_ON: aggregation session is up + * @IWL_EMPTYING_HW_QUEUE_ADDBA: establishing a BA session - waiting for the + * HW queue to be empty from packets for this RA /TID. + * @IWL_EMPTYING_HW_QUEUE_DELBA: tearing down a BA session - waiting for the + * HW queue to be empty from packets for this RA /TID. + */ +enum iwl_mvm_agg_state { + IWL_AGG_OFF = 0, + IWL_AGG_QUEUED, + IWL_AGG_STARTING, + IWL_AGG_ON, + IWL_EMPTYING_HW_QUEUE_ADDBA, + IWL_EMPTYING_HW_QUEUE_DELBA, +}; + +/** + * struct iwl_mvm_tid_data - holds the states for each RA / TID + * @seq_number: the next WiFi sequence number to use + * @next_reclaimed: the WiFi sequence number of the next packet to be acked. + * This is basically (last acked packet++). + * @rate_n_flags: Rate at which Tx was attempted. Holds the data between the + * Tx response (TX_CMD), and the block ack notification (COMPRESSED_BA). + * @lq_color: the color of the LQ command as it appears in tx response. + * @amsdu_in_ampdu_allowed: true if A-MSDU in A-MPDU is allowed. + * @state: state of the BA agreement establishment / tear down. + * @txq_id: Tx queue used by the BA session / DQA + * @ssn: the first packet to be sent in AGG HW queue in Tx AGG start flow, or + * the first packet to be sent in legacy HW queue in Tx AGG stop flow. + * Basically when next_reclaimed reaches ssn, we can tell mac80211 that + * we are ready to finish the Tx AGG stop / start flow. + * @tx_time: medium time consumed by this A-MPDU + * @tpt_meas_start: time of the throughput measurements start, is reset every HZ + * @tx_count_last: number of frames transmitted during the last second + * @tx_count: counts the number of frames transmitted since the last reset of + * tpt_meas_start + */ +struct iwl_mvm_tid_data { + u16 seq_number; + u16 next_reclaimed; + /* The rest is Tx AGG related */ + u32 rate_n_flags; + u8 lq_color; + bool amsdu_in_ampdu_allowed; + enum iwl_mvm_agg_state state; + u16 txq_id; + u16 ssn; + u16 tx_time; + unsigned long tpt_meas_start; + u32 tx_count_last; + u32 tx_count; +}; + +struct iwl_mvm_key_pn { + struct rcu_head rcu_head; + struct { + u8 pn[IWL_MAX_TID_COUNT][IEEE80211_CCMP_PN_LEN]; + } ____cacheline_aligned_in_smp q[]; +}; + +/** + * enum iwl_mvm_rxq_notif_type - Internal message identifier + * + * @IWL_MVM_RXQ_EMPTY: empty sync notification + * @IWL_MVM_RXQ_NOTIF_DEL_BA: notify RSS queues of delBA + * @IWL_MVM_RXQ_NSSN_SYNC: notify all the RSS queues with the new NSSN + */ +enum iwl_mvm_rxq_notif_type { + IWL_MVM_RXQ_EMPTY, + IWL_MVM_RXQ_NOTIF_DEL_BA, + IWL_MVM_RXQ_NSSN_SYNC, +}; + +/** + * struct iwl_mvm_internal_rxq_notif - Internal representation of the data sent + * in &iwl_rxq_sync_cmd. Should be DWORD aligned. + * FW is agnostic to the payload, so there are no endianity requirements. + * + * @type: value from &iwl_mvm_rxq_notif_type + * @sync: ctrl path is waiting for all notifications to be received + * @cookie: internal cookie to identify old notifications + * @data: payload + */ +struct iwl_mvm_internal_rxq_notif { + u16 type; + u16 sync; + u32 cookie; + u8 data[]; +} __packed; + +struct iwl_mvm_delba_data { + u32 baid; +} __packed; + +struct iwl_mvm_nssn_sync_data { + u32 baid; + u32 nssn; +} __packed; + +/** + * struct iwl_mvm_rxq_dup_data - per station per rx queue data + * @last_seq: last sequence per tid for duplicate packet detection + * @last_sub_frame: last subframe packet + */ +struct iwl_mvm_rxq_dup_data { + __le16 last_seq[IWL_MAX_TID_COUNT + 1]; + u8 last_sub_frame[IWL_MAX_TID_COUNT + 1]; +} ____cacheline_aligned_in_smp; + +/** + * struct iwl_mvm_sta - representation of a station in the driver + * @sta_id: the index of the station in the fw (will be replaced by id_n_color) + * @tfd_queue_msk: the tfd queues used by the station + * @mac_id_n_color: the MAC context this station is linked to + * @tid_disable_agg: bitmap: if bit(tid) is set, the fw won't send ampdus for + * tid. + * @max_agg_bufsize: the maximal size of the AGG buffer for this station + * @sta_type: station type + * @sta_state: station state according to enum %ieee80211_sta_state + * @bt_reduced_txpower: is reduced tx power enabled for this station + * @next_status_eosp: the next reclaimed packet is a PS-Poll response and + * we need to signal the EOSP + * @lock: lock to protect the whole struct. Since %tid_data is access from Tx + * and from Tx response flow, it needs a spinlock. + * @tid_data: per tid data + mgmt. Look at %iwl_mvm_tid_data. + * @tid_to_baid: a simple map of TID to baid + * @lq_sta: holds rate scaling data, either for the case when RS is done in + * the driver - %rs_drv or in the FW - %rs_fw. + * @reserved_queue: the queue reserved for this STA for DQA purposes + * Every STA has is given one reserved queue to allow it to operate. If no + * such queue can be guaranteed, the STA addition will fail. + * @tx_protection: reference counter for controlling the Tx protection. + * @tt_tx_protection: is thermal throttling enable Tx protection? + * @disable_tx: is tx to this STA disabled? + * @amsdu_enabled: bitmap of TX AMSDU allowed TIDs. + * In case TLC offload is not active it is either 0xFFFF or 0. + * @max_amsdu_len: max AMSDU length + * @orig_amsdu_len: used to save the original amsdu_len when it is changed via + * debugfs. If it's set to 0, it means that it is it's not set via + * debugfs. + * @agg_tids: bitmap of tids whose status is operational aggregated (IWL_AGG_ON) + * @sleep_tx_count: the number of frames that we told the firmware to let out + * even when that station is asleep. This is useful in case the queue + * gets empty before all the frames were sent, which can happen when + * we are sending frames from an AMPDU queue and there was a hole in + * the BA window. To be used for UAPSD only. + * @ptk_pn: per-queue PTK PN data structures + * @dup_data: per queue duplicate packet detection data + * @deferred_traffic_tid_map: indication bitmap of deferred traffic per-TID + * @tx_ant: the index of the antenna to use for data tx to this station. Only + * used during connection establishment (e.g. for the 4 way handshake + * exchange). + * @pairwise_cipher: used to feed iwlmei upon authorization + * + * When mac80211 creates a station it reserves some space (hw->sta_data_size) + * in the structure for use by driver. This structure is placed in that + * space. + * + */ +struct iwl_mvm_sta { + u32 sta_id; + u32 tfd_queue_msk; + u32 mac_id_n_color; + u16 tid_disable_agg; + u16 max_agg_bufsize; + enum iwl_sta_type sta_type; + enum ieee80211_sta_state sta_state; + bool bt_reduced_txpower; + bool next_status_eosp; + spinlock_t lock; + struct iwl_mvm_tid_data tid_data[IWL_MAX_TID_COUNT + 1]; + u8 tid_to_baid[IWL_MAX_TID_COUNT]; + union { + struct iwl_lq_sta_rs_fw rs_fw; + struct iwl_lq_sta rs_drv; + } lq_sta; + struct ieee80211_vif *vif; + struct iwl_mvm_key_pn __rcu *ptk_pn[4]; + struct iwl_mvm_rxq_dup_data *dup_data; + + u8 reserved_queue; + + /* Temporary, until the new TLC will control the Tx protection */ + s8 tx_protection; + bool tt_tx_protection; + + bool disable_tx; + u16 amsdu_enabled; + u16 max_amsdu_len; + u16 orig_amsdu_len; + bool sleeping; + u8 agg_tids; + u8 sleep_tx_count; + u8 avg_energy; + u8 tx_ant; + u32 pairwise_cipher; +}; + +u16 iwl_mvm_tid_queued(struct iwl_mvm *mvm, struct iwl_mvm_tid_data *tid_data); + +static inline struct iwl_mvm_sta * +iwl_mvm_sta_from_mac80211(struct ieee80211_sta *sta) +{ + return (void *)sta->drv_priv; +} + +/** + * struct iwl_mvm_int_sta - representation of an internal station (auxiliary or + * broadcast) + * @sta_id: the index of the station in the fw (will be replaced by id_n_color) + * @type: station type + * @tfd_queue_msk: the tfd queues used by the station + */ +struct iwl_mvm_int_sta { + u32 sta_id; + enum iwl_sta_type type; + u32 tfd_queue_msk; +}; + +/** + * Send the STA info to the FW. + * + * @mvm: the iwl_mvm* to use + * @sta: the STA + * @update: this is true if the FW is being updated about a STA it already knows + * about. Otherwise (if this is a new STA), this should be false. + * @flags: if update==true, this marks what is being changed via ORs of values + * from enum iwl_sta_modify_flag. Otherwise, this is ignored. + */ +int iwl_mvm_sta_send_to_fw(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + bool update, unsigned int flags); +int iwl_mvm_add_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); + +static inline int iwl_mvm_update_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + return iwl_mvm_sta_send_to_fw(mvm, sta, true, 0); +} + +int iwl_mvm_wait_sta_queues_empty(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvm_sta); +int iwl_mvm_rm_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +int iwl_mvm_rm_sta_id(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u8 sta_id); +int iwl_mvm_set_sta_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *keyconf, + u8 key_offset); +int iwl_mvm_remove_sta_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *keyconf); + +void iwl_mvm_update_tkip_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, u32 iv32, + u16 *phase1key); + +void iwl_mvm_rx_eosp_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); + +/* AMPDU */ +int iwl_mvm_sta_rx_agg(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + int tid, u16 ssn, bool start, u16 buf_size, u16 timeout); +int iwl_mvm_sta_tx_agg_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 *ssn); +int iwl_mvm_sta_tx_agg_oper(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 buf_size, + bool amsdu); +int iwl_mvm_sta_tx_agg_stop(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid); +int iwl_mvm_sta_tx_agg_flush(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid); + +int iwl_mvm_sta_tx_agg(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + int tid, u8 queue, bool start); + +int iwl_mvm_add_aux_sta(struct iwl_mvm *mvm, u32 lmac_id); +int iwl_mvm_rm_aux_sta(struct iwl_mvm *mvm); + +int iwl_mvm_alloc_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_send_add_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_add_p2p_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_send_rm_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_rm_p2p_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_add_mcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_rm_mcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_allocate_int_sta(struct iwl_mvm *mvm, + struct iwl_mvm_int_sta *sta, + u32 qmask, enum nl80211_iftype iftype, + enum iwl_sta_type type); +void iwl_mvm_dealloc_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_dealloc_int_sta(struct iwl_mvm *mvm, struct iwl_mvm_int_sta *sta); +int iwl_mvm_add_snif_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_rm_snif_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_dealloc_snif_sta(struct iwl_mvm *mvm); + +void iwl_mvm_sta_modify_ps_wake(struct iwl_mvm *mvm, + struct ieee80211_sta *sta); +void iwl_mvm_sta_modify_sleep_tx_count(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + enum ieee80211_frame_release_type reason, + u16 cnt, u16 tids, bool more_data, + bool single_sta_queue); +int iwl_mvm_drain_sta(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta, + bool drain); +void iwl_mvm_sta_modify_disable_tx(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvmsta, bool disable); +void iwl_mvm_sta_modify_disable_tx_ap(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + bool disable); +void iwl_mvm_modify_all_sta_disable_tx(struct iwl_mvm *mvm, + struct iwl_mvm_vif *mvmvif, + bool disable); +void iwl_mvm_csa_client_absent(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_add_new_dqa_stream_wk(struct work_struct *wk); +int iwl_mvm_add_pasn_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_int_sta *sta, u8 *addr, u32 cipher, + u8 *key, u32 key_len); +void iwl_mvm_cancel_channel_switch(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 mac_id); +#endif /* __sta_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/tdls.c b/drivers/net/wireless/intel/iwlwifi/mvm/tdls.c new file mode 100644 index 000000000..674dd137f --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/tdls.c @@ -0,0 +1,673 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2014 Intel Mobile Communications GmbH + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2018-2020, 2022 Intel Corporation + */ +#include <linux/etherdevice.h> +#include "mvm.h" +#include "time-event.h" +#include "iwl-io.h" +#include "iwl-prph.h" + +#define TU_TO_US(x) (x * 1024) +#define TU_TO_MS(x) (TU_TO_US(x) / 1000) + +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 < mvm->fw->ucode_capa.num_stations; 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); + } +} + +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 < mvm->fw->ucode_capa.num_stations; 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_tdls_config(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_rx_packet *pkt; + struct iwl_tdls_config_res *resp; + struct iwl_tdls_config_cmd tdls_cfg_cmd = {}; + struct iwl_host_cmd cmd = { + .id = TDLS_CONFIG_CMD, + .flags = CMD_WANT_SKB, + .data = { &tdls_cfg_cmd, }, + .len = { sizeof(struct iwl_tdls_config_cmd), }, + }; + struct ieee80211_sta *sta; + int ret, i, cnt; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + lockdep_assert_held(&mvm->mutex); + + tdls_cfg_cmd.id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); + tdls_cfg_cmd.tx_to_ap_tid = IWL_MVM_TDLS_FW_TID; + tdls_cfg_cmd.tx_to_ap_ssn = cpu_to_le16(0); /* not used for now */ + + /* for now the Tx cmd is empty and unused */ + + /* populate TDLS peer data */ + cnt = 0; + for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) { + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i], + lockdep_is_held(&mvm->mutex)); + if (IS_ERR_OR_NULL(sta) || !sta->tdls) + continue; + + tdls_cfg_cmd.sta_info[cnt].sta_id = i; + tdls_cfg_cmd.sta_info[cnt].tx_to_peer_tid = + IWL_MVM_TDLS_FW_TID; + tdls_cfg_cmd.sta_info[cnt].tx_to_peer_ssn = cpu_to_le16(0); + tdls_cfg_cmd.sta_info[cnt].is_initiator = + cpu_to_le32(sta->tdls_initiator ? 1 : 0); + + cnt++; + } + + tdls_cfg_cmd.tdls_peer_count = cnt; + IWL_DEBUG_TDLS(mvm, "send TDLS config to FW for %d peers\n", cnt); + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (WARN_ON_ONCE(ret)) + return; + + pkt = cmd.resp_pkt; + + WARN_ON_ONCE(iwl_rx_packet_payload_len(pkt) != sizeof(*resp)); + + /* we don't really care about the response at this point */ + + iwl_free_resp(&cmd); +} + +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); + + /* when the first peer joins, send a power update first */ + if (tdls_sta_cnt == 1 && sta_added) + iwl_mvm_power_update_mac(mvm); + + /* Configure the FW with TDLS peer info only if TDLS channel switch + * capability is set. + * TDLS config data is used currently only in TDLS channel switch code. + * Supposed to serve also TDLS buffer station which is not implemneted + * yet in FW*/ + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TDLS_CHANNEL_SWITCH)) + iwl_mvm_tdls_config(mvm, vif); + + /* when the last peer leaves, send a power update last */ + if (tdls_sta_cnt == 0 && !sta_added) + iwl_mvm_power_update_mac(mvm); +} + +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; + + /* Protect the session to hear the TDLS setup response on the channel */ + mutex_lock(&mvm->mutex); + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) + iwl_mvm_schedule_session_protection(mvm, vif, duration, + duration, true); + else + iwl_mvm_protect_session(mvm, vif, duration, + duration, 100, true); + mutex_unlock(&mvm->mutex); +} + +static const char * +iwl_mvm_tdls_cs_state_str(enum iwl_mvm_tdls_cs_state state) +{ + switch (state) { + case IWL_MVM_TDLS_SW_IDLE: + return "IDLE"; + case IWL_MVM_TDLS_SW_REQ_SENT: + return "REQ SENT"; + case IWL_MVM_TDLS_SW_RESP_RCVD: + return "RESP RECEIVED"; + case IWL_MVM_TDLS_SW_REQ_RCVD: + return "REQ RECEIVED"; + case IWL_MVM_TDLS_SW_ACTIVE: + return "ACTIVE"; + } + + return NULL; +} + +static void iwl_mvm_tdls_update_cs_state(struct iwl_mvm *mvm, + enum iwl_mvm_tdls_cs_state state) +{ + if (mvm->tdls_cs.state == state) + return; + + IWL_DEBUG_TDLS(mvm, "TDLS channel switch state: %s -> %s\n", + iwl_mvm_tdls_cs_state_str(mvm->tdls_cs.state), + iwl_mvm_tdls_cs_state_str(state)); + mvm->tdls_cs.state = state; + + /* we only send requests to our switching peer - update sent time */ + if (state == IWL_MVM_TDLS_SW_REQ_SENT) + mvm->tdls_cs.peer.sent_timestamp = iwl_mvm_get_systime(mvm); + + if (state == IWL_MVM_TDLS_SW_IDLE) + mvm->tdls_cs.cur_sta_id = IWL_MVM_INVALID_STA; +} + +void iwl_mvm_rx_tdls_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_tdls_channel_switch_notif *notif = (void *)pkt->data; + struct ieee80211_sta *sta; + unsigned int delay; + struct iwl_mvm_sta *mvmsta; + struct ieee80211_vif *vif; + u32 sta_id = le32_to_cpu(notif->sta_id); + + lockdep_assert_held(&mvm->mutex); + + /* can fail sometimes */ + if (!le32_to_cpu(notif->status)) { + iwl_mvm_tdls_update_cs_state(mvm, IWL_MVM_TDLS_SW_IDLE); + return; + } + + if (WARN_ON(sta_id >= mvm->fw->ucode_capa.num_stations)) + return; + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + /* the station may not be here, but if it is, it must be a TDLS peer */ + if (IS_ERR_OR_NULL(sta) || WARN_ON(!sta->tdls)) + return; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + vif = mvmsta->vif; + + /* + * Update state and possibly switch again after this is over (DTIM). + * Also convert TU to msec. + */ + delay = TU_TO_MS(vif->bss_conf.dtim_period * vif->bss_conf.beacon_int); + mod_delayed_work(system_wq, &mvm->tdls_cs.dwork, + msecs_to_jiffies(delay)); + + iwl_mvm_tdls_update_cs_state(mvm, IWL_MVM_TDLS_SW_ACTIVE); +} + +static int +iwl_mvm_tdls_check_action(struct iwl_mvm *mvm, + enum iwl_tdls_channel_switch_type type, + const u8 *peer, bool peer_initiator, u32 timestamp) +{ + bool same_peer = false; + int ret = 0; + + /* get the existing peer if it's there */ + if (mvm->tdls_cs.state != IWL_MVM_TDLS_SW_IDLE && + mvm->tdls_cs.cur_sta_id != IWL_MVM_INVALID_STA) { + struct ieee80211_sta *sta = rcu_dereference_protected( + mvm->fw_id_to_mac_id[mvm->tdls_cs.cur_sta_id], + lockdep_is_held(&mvm->mutex)); + if (!IS_ERR_OR_NULL(sta)) + same_peer = ether_addr_equal(peer, sta->addr); + } + + switch (mvm->tdls_cs.state) { + case IWL_MVM_TDLS_SW_IDLE: + /* + * might be spurious packet from the peer after the switch is + * already done + */ + if (type == TDLS_MOVE_CH) + ret = -EINVAL; + break; + case IWL_MVM_TDLS_SW_REQ_SENT: + /* only allow requests from the same peer */ + if (!same_peer) + ret = -EBUSY; + else if (type == TDLS_SEND_CHAN_SW_RESP_AND_MOVE_CH && + !peer_initiator) + /* + * We received a ch-switch request while an outgoing + * one is pending. Allow it if the peer is the link + * initiator. + */ + ret = -EBUSY; + else if (type == TDLS_SEND_CHAN_SW_REQ) + /* wait for idle before sending another request */ + ret = -EBUSY; + else if (timestamp <= mvm->tdls_cs.peer.sent_timestamp) + /* we got a stale response - ignore it */ + ret = -EINVAL; + break; + case IWL_MVM_TDLS_SW_RESP_RCVD: + /* + * we are waiting for the FW to give an "active" notification, + * so ignore requests in the meantime + */ + ret = -EBUSY; + break; + case IWL_MVM_TDLS_SW_REQ_RCVD: + /* as above, allow the link initiator to proceed */ + if (type == TDLS_SEND_CHAN_SW_REQ) { + if (!same_peer) + ret = -EBUSY; + else if (peer_initiator) /* they are the initiator */ + ret = -EBUSY; + } else if (type == TDLS_MOVE_CH) { + ret = -EINVAL; + } + break; + case IWL_MVM_TDLS_SW_ACTIVE: + /* + * the only valid request when active is a request to return + * to the base channel by the current off-channel peer + */ + if (type != TDLS_MOVE_CH || !same_peer) + ret = -EBUSY; + break; + } + + if (ret) + IWL_DEBUG_TDLS(mvm, + "Invalid TDLS action %d state %d peer %pM same_peer %d initiator %d\n", + type, mvm->tdls_cs.state, peer, same_peer, + peer_initiator); + + return ret; +} + +static int +iwl_mvm_tdls_config_channel_switch(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + enum iwl_tdls_channel_switch_type type, + const u8 *peer, bool peer_initiator, + u8 oper_class, + struct cfg80211_chan_def *chandef, + u32 timestamp, u16 switch_time, + u16 switch_timeout, struct sk_buff *skb, + u32 ch_sw_tm_ie) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + struct ieee80211_tx_info *info; + struct ieee80211_hdr *hdr; + struct iwl_tdls_channel_switch_cmd cmd = {0}; + struct iwl_tdls_channel_switch_cmd_tail *tail = + iwl_mvm_chan_info_cmd_tail(mvm, &cmd.ci); + u16 len = sizeof(cmd) - iwl_mvm_chan_info_padding(mvm); + int ret; + + lockdep_assert_held(&mvm->mutex); + + ret = iwl_mvm_tdls_check_action(mvm, type, peer, peer_initiator, + timestamp); + if (ret) + return ret; + + if (!skb || WARN_ON(skb->len > IWL_TDLS_CH_SW_FRAME_MAX_SIZE)) { + ret = -EINVAL; + goto out; + } + + cmd.switch_type = type; + tail->timing.frame_timestamp = cpu_to_le32(timestamp); + tail->timing.switch_time = cpu_to_le32(switch_time); + tail->timing.switch_timeout = cpu_to_le32(switch_timeout); + + rcu_read_lock(); + sta = ieee80211_find_sta(vif, peer); + if (!sta) { + rcu_read_unlock(); + ret = -ENOENT; + goto out; + } + mvmsta = iwl_mvm_sta_from_mac80211(sta); + cmd.peer_sta_id = cpu_to_le32(mvmsta->sta_id); + + if (!chandef) { + if (mvm->tdls_cs.state == IWL_MVM_TDLS_SW_REQ_SENT && + mvm->tdls_cs.peer.chandef.chan) { + /* actually moving to the channel */ + chandef = &mvm->tdls_cs.peer.chandef; + } else if (mvm->tdls_cs.state == IWL_MVM_TDLS_SW_ACTIVE && + type == TDLS_MOVE_CH) { + /* we need to return to base channel */ + struct ieee80211_chanctx_conf *chanctx = + rcu_dereference(vif->bss_conf.chanctx_conf); + + if (WARN_ON_ONCE(!chanctx)) { + rcu_read_unlock(); + goto out; + } + + chandef = &chanctx->def; + } + } + + if (chandef) + iwl_mvm_set_chan_info_chandef(mvm, &cmd.ci, chandef); + + /* keep quota calculation simple for now - 50% of DTIM for TDLS */ + tail->timing.max_offchan_duration = + cpu_to_le32(TU_TO_US(vif->bss_conf.dtim_period * + vif->bss_conf.beacon_int) / 2); + + /* Switch time is the first element in the switch-timing IE. */ + tail->frame.switch_time_offset = cpu_to_le32(ch_sw_tm_ie + 2); + + info = IEEE80211_SKB_CB(skb); + hdr = (void *)skb->data; + if (info->control.hw_key) { + if (info->control.hw_key->cipher != WLAN_CIPHER_SUITE_CCMP) { + rcu_read_unlock(); + ret = -EINVAL; + goto out; + } + iwl_mvm_set_tx_cmd_ccmp(info, &tail->frame.tx_cmd); + } + + iwl_mvm_set_tx_cmd(mvm, skb, &tail->frame.tx_cmd, info, + mvmsta->sta_id); + + iwl_mvm_set_tx_cmd_rate(mvm, &tail->frame.tx_cmd, info, sta, + hdr->frame_control); + rcu_read_unlock(); + + memcpy(tail->frame.data, skb->data, skb->len); + + ret = iwl_mvm_send_cmd_pdu(mvm, TDLS_CHANNEL_SWITCH_CMD, 0, len, &cmd); + if (ret) { + IWL_ERR(mvm, "Failed to send TDLS_CHANNEL_SWITCH cmd: %d\n", + ret); + goto out; + } + + /* channel switch has started, update state */ + if (type != TDLS_MOVE_CH) { + mvm->tdls_cs.cur_sta_id = mvmsta->sta_id; + iwl_mvm_tdls_update_cs_state(mvm, + type == TDLS_SEND_CHAN_SW_REQ ? + IWL_MVM_TDLS_SW_REQ_SENT : + IWL_MVM_TDLS_SW_REQ_RCVD); + } else { + iwl_mvm_tdls_update_cs_state(mvm, IWL_MVM_TDLS_SW_RESP_RCVD); + } + +out: + + /* channel switch failed - we are idle */ + if (ret) + iwl_mvm_tdls_update_cs_state(mvm, IWL_MVM_TDLS_SW_IDLE); + + return ret; +} + +void iwl_mvm_tdls_ch_switch_work(struct work_struct *work) +{ + struct iwl_mvm *mvm; + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + struct ieee80211_vif *vif; + unsigned int delay; + int ret; + + mvm = container_of(work, struct iwl_mvm, tdls_cs.dwork.work); + mutex_lock(&mvm->mutex); + + /* called after an active channel switch has finished or timed-out */ + iwl_mvm_tdls_update_cs_state(mvm, IWL_MVM_TDLS_SW_IDLE); + + /* station might be gone, in that case do nothing */ + if (mvm->tdls_cs.peer.sta_id == IWL_MVM_INVALID_STA) + goto out; + + sta = rcu_dereference_protected( + mvm->fw_id_to_mac_id[mvm->tdls_cs.peer.sta_id], + lockdep_is_held(&mvm->mutex)); + /* the station may not be here, but if it is, it must be a TDLS peer */ + if (!sta || IS_ERR(sta) || WARN_ON(!sta->tdls)) + goto out; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + vif = mvmsta->vif; + ret = iwl_mvm_tdls_config_channel_switch(mvm, vif, + TDLS_SEND_CHAN_SW_REQ, + sta->addr, + mvm->tdls_cs.peer.initiator, + mvm->tdls_cs.peer.op_class, + &mvm->tdls_cs.peer.chandef, + 0, 0, 0, + mvm->tdls_cs.peer.skb, + mvm->tdls_cs.peer.ch_sw_tm_ie); + if (ret) + IWL_ERR(mvm, "Not sending TDLS channel switch: %d\n", ret); + + /* retry after a DTIM if we failed sending now */ + delay = TU_TO_MS(vif->bss_conf.dtim_period * vif->bss_conf.beacon_int); + schedule_delayed_work(&mvm->tdls_cs.dwork, msecs_to_jiffies(delay)); +out: + mutex_unlock(&mvm->mutex); +} + +int +iwl_mvm_tdls_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u8 oper_class, + struct cfg80211_chan_def *chandef, + struct sk_buff *tmpl_skb, u32 ch_sw_tm_ie) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_sta *mvmsta; + unsigned int delay; + int ret; + + mutex_lock(&mvm->mutex); + + IWL_DEBUG_TDLS(mvm, "TDLS channel switch with %pM ch %d width %d\n", + sta->addr, chandef->chan->center_freq, chandef->width); + + /* we only support a single peer for channel switching */ + if (mvm->tdls_cs.peer.sta_id != IWL_MVM_INVALID_STA) { + IWL_DEBUG_TDLS(mvm, + "Existing peer. Can't start switch with %pM\n", + sta->addr); + ret = -EBUSY; + goto out; + } + + ret = iwl_mvm_tdls_config_channel_switch(mvm, vif, + TDLS_SEND_CHAN_SW_REQ, + sta->addr, sta->tdls_initiator, + oper_class, chandef, 0, 0, 0, + tmpl_skb, ch_sw_tm_ie); + if (ret) + goto out; + + /* + * Mark the peer as "in tdls switch" for this vif. We only allow a + * single such peer per vif. + */ + mvm->tdls_cs.peer.skb = skb_copy(tmpl_skb, GFP_KERNEL); + if (!mvm->tdls_cs.peer.skb) { + ret = -ENOMEM; + goto out; + } + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + mvm->tdls_cs.peer.sta_id = mvmsta->sta_id; + mvm->tdls_cs.peer.chandef = *chandef; + mvm->tdls_cs.peer.initiator = sta->tdls_initiator; + mvm->tdls_cs.peer.op_class = oper_class; + mvm->tdls_cs.peer.ch_sw_tm_ie = ch_sw_tm_ie; + + /* + * Wait for 2 DTIM periods before attempting the next switch. The next + * switch will be made sooner if the current one completes before that. + */ + delay = 2 * TU_TO_MS(vif->bss_conf.dtim_period * + vif->bss_conf.beacon_int); + mod_delayed_work(system_wq, &mvm->tdls_cs.dwork, + msecs_to_jiffies(delay)); + +out: + mutex_unlock(&mvm->mutex); + return ret; +} + +void iwl_mvm_tdls_cancel_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct ieee80211_sta *cur_sta; + bool wait_for_phy = false; + + mutex_lock(&mvm->mutex); + + IWL_DEBUG_TDLS(mvm, "TDLS cancel channel switch with %pM\n", sta->addr); + + /* we only support a single peer for channel switching */ + if (mvm->tdls_cs.peer.sta_id == IWL_MVM_INVALID_STA) { + IWL_DEBUG_TDLS(mvm, "No ch switch peer - %pM\n", sta->addr); + goto out; + } + + cur_sta = rcu_dereference_protected( + mvm->fw_id_to_mac_id[mvm->tdls_cs.peer.sta_id], + lockdep_is_held(&mvm->mutex)); + /* make sure it's the same peer */ + if (cur_sta != sta) + goto out; + + /* + * If we're currently in a switch because of the now canceled peer, + * wait a DTIM here to make sure the phy is back on the base channel. + * We can't otherwise force it. + */ + if (mvm->tdls_cs.cur_sta_id == mvm->tdls_cs.peer.sta_id && + mvm->tdls_cs.state != IWL_MVM_TDLS_SW_IDLE) + wait_for_phy = true; + + mvm->tdls_cs.peer.sta_id = IWL_MVM_INVALID_STA; + dev_kfree_skb(mvm->tdls_cs.peer.skb); + mvm->tdls_cs.peer.skb = NULL; + +out: + mutex_unlock(&mvm->mutex); + + /* make sure the phy is on the base channel */ + if (wait_for_phy) + msleep(TU_TO_MS(vif->bss_conf.dtim_period * + vif->bss_conf.beacon_int)); + + /* flush the channel switch state */ + flush_delayed_work(&mvm->tdls_cs.dwork); + + IWL_DEBUG_TDLS(mvm, "TDLS ending channel switch with %pM\n", sta->addr); +} + +void +iwl_mvm_tdls_recv_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_tdls_ch_sw_params *params) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + enum iwl_tdls_channel_switch_type type; + unsigned int delay; + const char *action_str = + params->action_code == WLAN_TDLS_CHANNEL_SWITCH_REQUEST ? + "REQ" : "RESP"; + + mutex_lock(&mvm->mutex); + + IWL_DEBUG_TDLS(mvm, + "Received TDLS ch switch action %s from %pM status %d\n", + action_str, params->sta->addr, params->status); + + /* + * we got a non-zero status from a peer we were switching to - move to + * the idle state and retry again later + */ + if (params->action_code == WLAN_TDLS_CHANNEL_SWITCH_RESPONSE && + params->status != 0 && + mvm->tdls_cs.state == IWL_MVM_TDLS_SW_REQ_SENT && + mvm->tdls_cs.cur_sta_id != IWL_MVM_INVALID_STA) { + struct ieee80211_sta *cur_sta; + + /* make sure it's the same peer */ + cur_sta = rcu_dereference_protected( + mvm->fw_id_to_mac_id[mvm->tdls_cs.cur_sta_id], + lockdep_is_held(&mvm->mutex)); + if (cur_sta == params->sta) { + iwl_mvm_tdls_update_cs_state(mvm, + IWL_MVM_TDLS_SW_IDLE); + goto retry; + } + } + + type = (params->action_code == WLAN_TDLS_CHANNEL_SWITCH_REQUEST) ? + TDLS_SEND_CHAN_SW_RESP_AND_MOVE_CH : TDLS_MOVE_CH; + + iwl_mvm_tdls_config_channel_switch(mvm, vif, type, params->sta->addr, + params->sta->tdls_initiator, 0, + params->chandef, params->timestamp, + params->switch_time, + params->switch_timeout, + params->tmpl_skb, + params->ch_sw_tm_ie); + +retry: + /* register a timeout in case we don't succeed in switching */ + delay = vif->bss_conf.dtim_period * vif->bss_conf.beacon_int * + 1024 / 1000; + mod_delayed_work(system_wq, &mvm->tdls_cs.dwork, + msecs_to_jiffies(delay)); + mutex_unlock(&mvm->mutex); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/testmode.h b/drivers/net/wireless/intel/iwlwifi/mvm/testmode.h new file mode 100644 index 000000000..ff82af11d --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/testmode.h @@ -0,0 +1,92 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2013 - 2014 Intel Corporation. All rights reserved. + * Copyright(c) 2013 - 2014 Intel Mobile Communications GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless <linuxwifi@intel.com> + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2013 - 2014 Intel Corporation. All rights reserved. + * Copyright(c) 2013 - 2014 Intel Mobile Communications GmbH + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#ifndef __IWL_MVM_TESTMODE_H__ +#define __IWL_MVM_TESTMODE_H__ + +/** + * enum iwl_mvm_testmode_attrs - testmode attributes inside NL80211_ATTR_TESTDATA + * @IWL_MVM_TM_ATTR_UNSPEC: (invalid attribute) + * @IWL_MVM_TM_ATTR_CMD: sub command, see &enum iwl_mvm_testmode_commands (u32) + * @IWL_MVM_TM_ATTR_NOA_DURATION: requested NoA duration (u32) + * @IWL_MVM_TM_ATTR_BEACON_FILTER_STATE: beacon filter state (0 or 1, u32) + */ +enum iwl_mvm_testmode_attrs { + IWL_MVM_TM_ATTR_UNSPEC, + IWL_MVM_TM_ATTR_CMD, + IWL_MVM_TM_ATTR_NOA_DURATION, + IWL_MVM_TM_ATTR_BEACON_FILTER_STATE, + + /* keep last */ + NUM_IWL_MVM_TM_ATTRS, + IWL_MVM_TM_ATTR_MAX = NUM_IWL_MVM_TM_ATTRS - 1, +}; + +/** + * enum iwl_mvm_testmode_commands - MVM testmode commands + * @IWL_MVM_TM_CMD_SET_NOA: set NoA on GO vif for testing + * @IWL_MVM_TM_CMD_SET_BEACON_FILTER: turn beacon filtering off/on + */ +enum iwl_mvm_testmode_commands { + IWL_MVM_TM_CMD_SET_NOA, + IWL_MVM_TM_CMD_SET_BEACON_FILTER, +}; + +#endif /* __IWL_MVM_TESTMODE_H__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/time-event.c b/drivers/net/wireless/intel/iwlwifi/mvm/time-event.c new file mode 100644 index 000000000..ed8ba81a6 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/time-event.c @@ -0,0 +1,1229 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2017 Intel Deutschland GmbH + */ +#include <linux/jiffies.h> +#include <net/mac80211.h> + +#include "fw/notif-wait.h" +#include "iwl-trans.h" +#include "fw-api.h" +#include "time-event.h" +#include "mvm.h" +#include "iwl-io.h" +#include "iwl-prph.h" + +/* + * For the high priority TE use a time event type that has similar priority to + * the FW's action scan priority. + */ +#define IWL_MVM_ROC_TE_TYPE_NORMAL TE_P2P_DEVICE_DISCOVERABLE +#define IWL_MVM_ROC_TE_TYPE_MGMT_TX TE_P2P_CLIENT_ASSOC + +void iwl_mvm_te_clear_data(struct iwl_mvm *mvm, + struct iwl_mvm_time_event_data *te_data) +{ + lockdep_assert_held(&mvm->time_event_lock); + + if (!te_data || !te_data->vif) + return; + + list_del(&te_data->list); + + /* + * the list is only used for AUX ROC events so make sure it is always + * initialized + */ + INIT_LIST_HEAD(&te_data->list); + + te_data->running = false; + te_data->uid = 0; + te_data->id = TE_MAX; + te_data->vif = NULL; +} + +void iwl_mvm_roc_done_wk(struct work_struct *wk) +{ + struct iwl_mvm *mvm = container_of(wk, struct iwl_mvm, roc_done_wk); + + /* + * Clear the ROC_RUNNING status bit. + * This will cause the TX path to drop offchannel transmissions. + * That would also be done by mac80211, but it is racy, in particular + * in the case that the time event actually completed in the firmware + * (which is handled in iwl_mvm_te_handle_notif). + */ + clear_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status); + + synchronize_net(); + + /* + * Flush the offchannel queue -- this is called when the time + * event finishes or is canceled, so that frames queued for it + * won't get stuck on the queue and be transmitted in the next + * time event. + */ + + mutex_lock(&mvm->mutex); + if (test_and_clear_bit(IWL_MVM_STATUS_NEED_FLUSH_P2P, &mvm->status)) { + struct iwl_mvm_vif *mvmvif; + + /* + * NB: access to this pointer would be racy, but the flush bit + * can only be set when we had a P2P-Device VIF, and we have a + * flush of this work in iwl_mvm_prepare_mac_removal() so it's + * not really racy. + */ + + if (!WARN_ON(!mvm->p2p_device_vif)) { + mvmvif = iwl_mvm_vif_from_mac80211(mvm->p2p_device_vif); + iwl_mvm_flush_sta(mvm, &mvmvif->bcast_sta, true); + } + } + + /* + * Clear the ROC_AUX_RUNNING status bit. + * This will cause the TX path to drop offchannel transmissions. + * That would also be done by mac80211, but it is racy, in particular + * in the case that the time event actually completed in the firmware + * (which is handled in iwl_mvm_te_handle_notif). + */ + if (test_and_clear_bit(IWL_MVM_STATUS_ROC_AUX_RUNNING, &mvm->status)) { + /* do the same in case of hot spot 2.0 */ + iwl_mvm_flush_sta(mvm, &mvm->aux_sta, true); + + /* In newer version of this command an aux station is added only + * in cases of dedicated tx queue and need to be removed in end + * of use */ + if (iwl_fw_lookup_cmd_ver(mvm->fw, ADD_STA, 0) >= 12) + iwl_mvm_rm_aux_sta(mvm); + } + + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_roc_finished(struct iwl_mvm *mvm) +{ + /* + * Of course, our status bit is just as racy as mac80211, so in + * addition, fire off the work struct which will drop all frames + * from the hardware queues that made it through the race. First + * it will of course synchronize the TX path to make sure that + * any *new* TX will be rejected. + */ + schedule_work(&mvm->roc_done_wk); +} + +static void iwl_mvm_csa_noa_start(struct iwl_mvm *mvm) +{ + struct ieee80211_vif *csa_vif; + + rcu_read_lock(); + + csa_vif = rcu_dereference(mvm->csa_vif); + if (!csa_vif || !csa_vif->bss_conf.csa_active) + goto out_unlock; + + IWL_DEBUG_TE(mvm, "CSA NOA started\n"); + + /* + * CSA NoA is started but we still have beacons to + * transmit on the current channel. + * So we just do nothing here and the switch + * will be performed on the last TBTT. + */ + if (!ieee80211_beacon_cntdwn_is_complete(csa_vif)) { + IWL_WARN(mvm, "CSA NOA started too early\n"); + goto out_unlock; + } + + ieee80211_csa_finish(csa_vif); + + rcu_read_unlock(); + + RCU_INIT_POINTER(mvm->csa_vif, NULL); + + return; + +out_unlock: + rcu_read_unlock(); +} + +static bool iwl_mvm_te_check_disconnect(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + const char *errmsg) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif->type != NL80211_IFTYPE_STATION) + return false; + + if (!mvmvif->csa_bcn_pending && vif->cfg.assoc && + vif->bss_conf.dtim_period) + return false; + if (errmsg) + IWL_ERR(mvm, "%s\n", errmsg); + + if (mvmvif->csa_bcn_pending) { + struct iwl_mvm_sta *mvmsta; + + rcu_read_lock(); + mvmsta = iwl_mvm_sta_from_staid_rcu(mvm, mvmvif->ap_sta_id); + if (!WARN_ON(!mvmsta)) + iwl_mvm_sta_modify_disable_tx(mvm, mvmsta, false); + rcu_read_unlock(); + } + + if (vif->cfg.assoc) { + /* + * When not associated, this will be called from + * iwl_mvm_event_mlme_callback_ini() + */ + iwl_dbg_tlv_time_point(&mvm->fwrt, + IWL_FW_INI_TIME_POINT_ASSOC_FAILED, + NULL); + } + + iwl_mvm_connection_loss(mvm, vif, errmsg); + return true; +} + +static void +iwl_mvm_te_handle_notify_csa(struct iwl_mvm *mvm, + struct iwl_mvm_time_event_data *te_data, + struct iwl_time_event_notif *notif) +{ + struct ieee80211_vif *vif = te_data->vif; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (!notif->status) + IWL_DEBUG_TE(mvm, "CSA time event failed to start\n"); + + switch (te_data->vif->type) { + case NL80211_IFTYPE_AP: + if (!notif->status) + mvmvif->csa_failed = true; + iwl_mvm_csa_noa_start(mvm); + break; + case NL80211_IFTYPE_STATION: + if (!notif->status) { + iwl_mvm_connection_loss(mvm, vif, + "CSA TE failed to start"); + break; + } + iwl_mvm_csa_client_absent(mvm, te_data->vif); + cancel_delayed_work(&mvmvif->csa_work); + ieee80211_chswitch_done(te_data->vif, true); + break; + default: + /* should never happen */ + WARN_ON_ONCE(1); + break; + } + + /* we don't need it anymore */ + iwl_mvm_te_clear_data(mvm, te_data); +} + +static void iwl_mvm_te_check_trigger(struct iwl_mvm *mvm, + struct iwl_time_event_notif *notif, + struct iwl_mvm_time_event_data *te_data) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_time_event *te_trig; + int i; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, + ieee80211_vif_to_wdev(te_data->vif), + FW_DBG_TRIGGER_TIME_EVENT); + if (!trig) + return; + + te_trig = (void *)trig->data; + + for (i = 0; i < ARRAY_SIZE(te_trig->time_events); i++) { + u32 trig_te_id = le32_to_cpu(te_trig->time_events[i].id); + u32 trig_action_bitmap = + le32_to_cpu(te_trig->time_events[i].action_bitmap); + u32 trig_status_bitmap = + le32_to_cpu(te_trig->time_events[i].status_bitmap); + + if (trig_te_id != te_data->id || + !(trig_action_bitmap & le32_to_cpu(notif->action)) || + !(trig_status_bitmap & BIT(le32_to_cpu(notif->status)))) + continue; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + "Time event %d Action 0x%x received status: %d", + te_data->id, + le32_to_cpu(notif->action), + le32_to_cpu(notif->status)); + break; + } +} + +static void iwl_mvm_p2p_roc_finished(struct iwl_mvm *mvm) +{ + /* + * If the IWL_MVM_STATUS_NEED_FLUSH_P2P is already set, then the + * roc_done_wk is already scheduled or running, so don't schedule it + * again to avoid a race where the roc_done_wk clears this bit after + * it is set here, affecting the next run of the roc_done_wk. + */ + if (!test_and_set_bit(IWL_MVM_STATUS_NEED_FLUSH_P2P, &mvm->status)) + iwl_mvm_roc_finished(mvm); +} + +/* + * Handles a FW notification for an event that is known to the driver. + * + * @mvm: the mvm component + * @te_data: the time event data + * @notif: the notification data corresponding the time event data. + */ +static void iwl_mvm_te_handle_notif(struct iwl_mvm *mvm, + struct iwl_mvm_time_event_data *te_data, + struct iwl_time_event_notif *notif) +{ + lockdep_assert_held(&mvm->time_event_lock); + + IWL_DEBUG_TE(mvm, "Handle time event notif - UID = 0x%x action %d\n", + le32_to_cpu(notif->unique_id), + le32_to_cpu(notif->action)); + + iwl_mvm_te_check_trigger(mvm, notif, te_data); + + /* + * The FW sends the start/end time event notifications even for events + * that it fails to schedule. This is indicated in the status field of + * the notification. This happens in cases that the scheduler cannot + * find a schedule that can handle the event (for example requesting a + * P2P Device discoveribility, while there are other higher priority + * events in the system). + */ + if (!le32_to_cpu(notif->status)) { + const char *msg; + + if (notif->action & cpu_to_le32(TE_V2_NOTIF_HOST_EVENT_START)) + msg = "Time Event start notification failure"; + else + msg = "Time Event end notification failure"; + + IWL_DEBUG_TE(mvm, "%s\n", msg); + + if (iwl_mvm_te_check_disconnect(mvm, te_data->vif, msg)) { + iwl_mvm_te_clear_data(mvm, te_data); + return; + } + } + + if (le32_to_cpu(notif->action) & TE_V2_NOTIF_HOST_EVENT_END) { + IWL_DEBUG_TE(mvm, + "TE ended - current time %lu, estimated end %lu\n", + jiffies, te_data->end_jiffies); + + switch (te_data->vif->type) { + case NL80211_IFTYPE_P2P_DEVICE: + ieee80211_remain_on_channel_expired(mvm->hw); + iwl_mvm_p2p_roc_finished(mvm); + break; + case NL80211_IFTYPE_STATION: + /* + * If we are switching channel, don't disconnect + * if the time event is already done. Beacons can + * be delayed a bit after the switch. + */ + if (te_data->id == TE_CHANNEL_SWITCH_PERIOD) { + IWL_DEBUG_TE(mvm, + "No beacon heard and the CS time event is over, don't disconnect\n"); + break; + } + + /* + * By now, we should have finished association + * and know the dtim period. + */ + iwl_mvm_te_check_disconnect(mvm, te_data->vif, + !te_data->vif->cfg.assoc ? + "Not associated and the time event is over already..." : + "No beacon heard and the time event is over already..."); + break; + default: + break; + } + + iwl_mvm_te_clear_data(mvm, te_data); + } else if (le32_to_cpu(notif->action) & TE_V2_NOTIF_HOST_EVENT_START) { + te_data->running = true; + te_data->end_jiffies = TU_TO_EXP_TIME(te_data->duration); + + if (te_data->vif->type == NL80211_IFTYPE_P2P_DEVICE) { + set_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status); + ieee80211_ready_on_channel(mvm->hw); + } else if (te_data->id == TE_CHANNEL_SWITCH_PERIOD) { + iwl_mvm_te_handle_notify_csa(mvm, te_data, notif); + } + } else { + IWL_WARN(mvm, "Got TE with unknown action\n"); + } +} + +/* + * Handle A Aux ROC time event + */ +static int iwl_mvm_aux_roc_te_handle_notif(struct iwl_mvm *mvm, + struct iwl_time_event_notif *notif) +{ + struct iwl_mvm_time_event_data *te_data, *tmp; + bool aux_roc_te = false; + + list_for_each_entry_safe(te_data, tmp, &mvm->aux_roc_te_list, list) { + if (le32_to_cpu(notif->unique_id) == te_data->uid) { + aux_roc_te = true; + break; + } + } + if (!aux_roc_te) /* Not a Aux ROC time event */ + return -EINVAL; + + iwl_mvm_te_check_trigger(mvm, notif, te_data); + + IWL_DEBUG_TE(mvm, + "Aux ROC time event notification - UID = 0x%x action %d (error = %d)\n", + le32_to_cpu(notif->unique_id), + le32_to_cpu(notif->action), le32_to_cpu(notif->status)); + + if (!le32_to_cpu(notif->status) || + le32_to_cpu(notif->action) == TE_V2_NOTIF_HOST_EVENT_END) { + /* End TE, notify mac80211 */ + ieee80211_remain_on_channel_expired(mvm->hw); + iwl_mvm_roc_finished(mvm); /* flush aux queue */ + list_del(&te_data->list); /* remove from list */ + te_data->running = false; + te_data->vif = NULL; + te_data->uid = 0; + te_data->id = TE_MAX; + } else if (le32_to_cpu(notif->action) == TE_V2_NOTIF_HOST_EVENT_START) { + set_bit(IWL_MVM_STATUS_ROC_AUX_RUNNING, &mvm->status); + te_data->running = true; + ieee80211_ready_on_channel(mvm->hw); /* Start TE */ + } else { + IWL_DEBUG_TE(mvm, + "ERROR: Unknown Aux ROC Time Event (action = %d)\n", + le32_to_cpu(notif->action)); + return -EINVAL; + } + + return 0; +} + +/* + * The Rx handler for time event notifications + */ +void iwl_mvm_rx_time_event_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_time_event_notif *notif = (void *)pkt->data; + struct iwl_mvm_time_event_data *te_data, *tmp; + + IWL_DEBUG_TE(mvm, "Time event notification - UID = 0x%x action %d\n", + le32_to_cpu(notif->unique_id), + le32_to_cpu(notif->action)); + + spin_lock_bh(&mvm->time_event_lock); + /* This time event is triggered for Aux ROC request */ + if (!iwl_mvm_aux_roc_te_handle_notif(mvm, notif)) + goto unlock; + + list_for_each_entry_safe(te_data, tmp, &mvm->time_event_list, list) { + if (le32_to_cpu(notif->unique_id) == te_data->uid) + iwl_mvm_te_handle_notif(mvm, te_data, notif); + } +unlock: + spin_unlock_bh(&mvm->time_event_lock); +} + +static bool iwl_mvm_te_notif(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_mvm_time_event_data *te_data = data; + struct iwl_time_event_notif *resp; + int resp_len = iwl_rx_packet_payload_len(pkt); + + if (WARN_ON(pkt->hdr.cmd != TIME_EVENT_NOTIFICATION)) + return true; + + if (WARN_ON_ONCE(resp_len != sizeof(*resp))) { + IWL_ERR(mvm, "Invalid TIME_EVENT_NOTIFICATION response\n"); + return true; + } + + resp = (void *)pkt->data; + + /* te_data->uid is already set in the TIME_EVENT_CMD response */ + if (le32_to_cpu(resp->unique_id) != te_data->uid) + return false; + + IWL_DEBUG_TE(mvm, "TIME_EVENT_NOTIFICATION response - UID = 0x%x\n", + te_data->uid); + if (!resp->status) + IWL_ERR(mvm, + "TIME_EVENT_NOTIFICATION received but not executed\n"); + + return true; +} + +static bool iwl_mvm_time_event_response(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_mvm_time_event_data *te_data = data; + struct iwl_time_event_resp *resp; + int resp_len = iwl_rx_packet_payload_len(pkt); + + if (WARN_ON(pkt->hdr.cmd != TIME_EVENT_CMD)) + return true; + + if (WARN_ON_ONCE(resp_len != sizeof(*resp))) { + IWL_ERR(mvm, "Invalid TIME_EVENT_CMD response\n"); + return true; + } + + resp = (void *)pkt->data; + + /* we should never get a response to another TIME_EVENT_CMD here */ + if (WARN_ON_ONCE(le32_to_cpu(resp->id) != te_data->id)) + return false; + + te_data->uid = le32_to_cpu(resp->unique_id); + IWL_DEBUG_TE(mvm, "TIME_EVENT_CMD response - UID = 0x%x\n", + te_data->uid); + return true; +} + +static int iwl_mvm_time_event_send_add(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mvm_time_event_data *te_data, + struct iwl_time_event_cmd *te_cmd) +{ + static const u16 time_event_response[] = { TIME_EVENT_CMD }; + struct iwl_notification_wait wait_time_event; + int ret; + + lockdep_assert_held(&mvm->mutex); + + IWL_DEBUG_TE(mvm, "Add new TE, duration %d TU\n", + le32_to_cpu(te_cmd->duration)); + + spin_lock_bh(&mvm->time_event_lock); + if (WARN_ON(te_data->id != TE_MAX)) { + spin_unlock_bh(&mvm->time_event_lock); + return -EIO; + } + te_data->vif = vif; + te_data->duration = le32_to_cpu(te_cmd->duration); + te_data->id = le32_to_cpu(te_cmd->id); + 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_time_event_response, te_data); + + ret = iwl_mvm_send_cmd_pdu(mvm, TIME_EVENT_CMD, 0, + sizeof(*te_cmd), te_cmd); + if (ret) { + IWL_ERR(mvm, "Couldn't send TIME_EVENT_CMD: %d\n", ret); + 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) */ + ret = iwl_wait_notification(&mvm->notif_wait, &wait_time_event, 1); + /* should never fail */ + WARN_ON_ONCE(ret); + + if (ret) { + 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 ret; +} + +void iwl_mvm_protect_session(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 duration, u32 min_duration, + u32 max_delay, bool wait_for_notif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; + const u16 te_notif_response[] = { TIME_EVENT_NOTIFICATION }; + struct iwl_notification_wait wait_te_notif; + struct iwl_time_event_cmd time_cmd = {}; + + lockdep_assert_held(&mvm->mutex); + + if (te_data->running && + time_after(te_data->end_jiffies, TU_TO_EXP_TIME(min_duration))) { + IWL_DEBUG_TE(mvm, "We have enough time in the current TE: %u\n", + jiffies_to_msecs(te_data->end_jiffies - jiffies)); + return; + } + + if (te_data->running) { + IWL_DEBUG_TE(mvm, "extend 0x%x: only %u ms left\n", + te_data->uid, + jiffies_to_msecs(te_data->end_jiffies - jiffies)); + /* + * we don't have enough time + * cancel the current TE and issue a new one + * Of course it would be better to remove the old one only + * when the new one is added, but we don't care if we are off + * channel for a bit. All we need to do, is not to return + * before we actually begin to be on the channel. + */ + iwl_mvm_stop_session_protection(mvm, vif); + } + + time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_ADD); + time_cmd.id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); + time_cmd.id = cpu_to_le32(TE_BSS_STA_AGGRESSIVE_ASSOC); + + time_cmd.apply_time = cpu_to_le32(0); + + time_cmd.max_frags = TE_V2_FRAG_NONE; + time_cmd.max_delay = cpu_to_le32(max_delay); + /* TODO: why do we need to interval = bi if it is not periodic? */ + time_cmd.interval = cpu_to_le32(1); + time_cmd.duration = cpu_to_le32(duration); + time_cmd.repeat = 1; + time_cmd.policy = cpu_to_le16(TE_V2_NOTIF_HOST_EVENT_START | + TE_V2_NOTIF_HOST_EVENT_END | + TE_V2_START_IMMEDIATELY); + + if (!wait_for_notif) { + iwl_mvm_time_event_send_add(mvm, vif, te_data, &time_cmd); + return; + } + + /* + * Create notification_wait for the TIME_EVENT_NOTIFICATION to use + * right after we send the time event + */ + iwl_init_notification_wait(&mvm->notif_wait, &wait_te_notif, + te_notif_response, + ARRAY_SIZE(te_notif_response), + iwl_mvm_te_notif, te_data); + + /* If TE was sent OK - wait for the notification that started */ + if (iwl_mvm_time_event_send_add(mvm, vif, te_data, &time_cmd)) { + IWL_ERR(mvm, "Failed to add TE to protect session\n"); + iwl_remove_notification(&mvm->notif_wait, &wait_te_notif); + } else if (iwl_wait_notification(&mvm->notif_wait, &wait_te_notif, + TU_TO_JIFFIES(max_delay))) { + IWL_ERR(mvm, "Failed to protect session until TE\n"); + } +} + +static void iwl_mvm_cancel_session_protection(struct iwl_mvm *mvm, + struct iwl_mvm_vif *mvmvif, + u32 id) +{ + struct iwl_mvm_session_prot_cmd cmd = { + .id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)), + .action = cpu_to_le32(FW_CTXT_ACTION_REMOVE), + .conf_id = cpu_to_le32(id), + }; + int ret; + + ret = iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(MAC_CONF_GROUP, SESSION_PROTECTION_CMD), + 0, sizeof(cmd), &cmd); + if (ret) + IWL_ERR(mvm, + "Couldn't send the SESSION_PROTECTION_CMD: %d\n", ret); +} + +static bool __iwl_mvm_remove_time_event(struct iwl_mvm *mvm, + struct iwl_mvm_time_event_data *te_data, + u32 *uid) +{ + u32 id; + struct iwl_mvm_vif *mvmvif; + enum nl80211_iftype iftype; + + if (!te_data->vif) + return false; + + mvmvif = iwl_mvm_vif_from_mac80211(te_data->vif); + iftype = te_data->vif->type; + + /* + * It is possible that by the time we got to this point the time + * event was already removed. + */ + spin_lock_bh(&mvm->time_event_lock); + + /* Save time event uid before clearing its data */ + *uid = te_data->uid; + id = te_data->id; + + /* + * The clear_data function handles time events that were already removed + */ + iwl_mvm_te_clear_data(mvm, te_data); + spin_unlock_bh(&mvm->time_event_lock); + + /* When session protection is used, the te_data->id field + * is reused to save session protection's configuration. + * For AUX ROC, HOT_SPOT_CMD is used and the te_data->id field is set + * to HOT_SPOT_CMD. + */ + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD) && + id != HOT_SPOT_CMD) { + if (mvmvif && id < SESSION_PROTECT_CONF_MAX_ID) { + /* Session protection is still ongoing. Cancel it */ + iwl_mvm_cancel_session_protection(mvm, mvmvif, id); + if (iftype == NL80211_IFTYPE_P2P_DEVICE) { + iwl_mvm_p2p_roc_finished(mvm); + } + } + return false; + } else { + /* It is possible that by the time we try to remove it, the + * time event has already ended and removed. In such a case + * there is no need to send a removal command. + */ + if (id == TE_MAX) { + IWL_DEBUG_TE(mvm, "TE 0x%x has already ended\n", *uid); + return false; + } + } + + return true; +} + +/* + * Explicit request to remove a aux roc time event. The removal of a time + * event needs to be synchronized with the flow of a time event's end + * notification, which also removes the time event from the op mode + * data structures. + */ +static void iwl_mvm_remove_aux_roc_te(struct iwl_mvm *mvm, + struct iwl_mvm_vif *mvmvif, + struct iwl_mvm_time_event_data *te_data) +{ + struct iwl_hs20_roc_req aux_cmd = {}; + u16 len = sizeof(aux_cmd) - iwl_mvm_chan_info_padding(mvm); + + u32 uid; + int ret; + + if (!__iwl_mvm_remove_time_event(mvm, te_data, &uid)) + return; + + aux_cmd.event_unique_id = cpu_to_le32(uid); + aux_cmd.action = cpu_to_le32(FW_CTXT_ACTION_REMOVE); + aux_cmd.id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); + IWL_DEBUG_TE(mvm, "Removing BSS AUX ROC TE 0x%x\n", + le32_to_cpu(aux_cmd.event_unique_id)); + ret = iwl_mvm_send_cmd_pdu(mvm, HOT_SPOT_CMD, 0, + len, &aux_cmd); + + if (WARN_ON(ret)) + return; +} + +/* + * Explicit request to remove a time event. The removal of a time event needs to + * be synchronized with the flow of a time event's end notification, which also + * removes the time event from the op mode data structures. + */ +void iwl_mvm_remove_time_event(struct iwl_mvm *mvm, + struct iwl_mvm_vif *mvmvif, + struct iwl_mvm_time_event_data *te_data) +{ + struct iwl_time_event_cmd time_cmd = {}; + u32 uid; + int ret; + + if (!__iwl_mvm_remove_time_event(mvm, te_data, &uid)) + return; + + /* When we remove a TE, the UID is to be set in the id field */ + time_cmd.id = cpu_to_le32(uid); + time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_REMOVE); + time_cmd.id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); + + IWL_DEBUG_TE(mvm, "Removing TE 0x%x\n", le32_to_cpu(time_cmd.id)); + ret = iwl_mvm_send_cmd_pdu(mvm, TIME_EVENT_CMD, 0, + sizeof(time_cmd), &time_cmd); + if (ret) + IWL_ERR(mvm, "Couldn't remove the time event\n"); +} + +void iwl_mvm_stop_session_protection(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; + u32 id; + + lockdep_assert_held(&mvm->mutex); + + spin_lock_bh(&mvm->time_event_lock); + id = te_data->id; + spin_unlock_bh(&mvm->time_event_lock); + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) { + if (id != SESSION_PROTECT_CONF_ASSOC) { + IWL_DEBUG_TE(mvm, + "don't remove session protection id=%u\n", + id); + return; + } + } else if (id != TE_BSS_STA_AGGRESSIVE_ASSOC) { + IWL_DEBUG_TE(mvm, + "don't remove TE with id=%u (not session protection)\n", + id); + return; + } + + iwl_mvm_remove_time_event(mvm, mvmvif, te_data); +} + +void iwl_mvm_rx_session_protect_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mvm_session_prot_notif *notif = (void *)pkt->data; + struct ieee80211_vif *vif; + struct iwl_mvm_vif *mvmvif; + + rcu_read_lock(); + vif = iwl_mvm_rcu_dereference_vif_id(mvm, le32_to_cpu(notif->mac_id), + true); + + if (!vif) + goto out_unlock; + + mvmvif = iwl_mvm_vif_from_mac80211(vif); + + /* The vif is not a P2P_DEVICE, maintain its time_event_data */ + if (vif->type != NL80211_IFTYPE_P2P_DEVICE) { + struct iwl_mvm_time_event_data *te_data = + &mvmvif->time_event_data; + + if (!le32_to_cpu(notif->status)) { + iwl_mvm_te_check_disconnect(mvm, vif, + "Session protection failure"); + spin_lock_bh(&mvm->time_event_lock); + iwl_mvm_te_clear_data(mvm, te_data); + spin_unlock_bh(&mvm->time_event_lock); + } + + if (le32_to_cpu(notif->start)) { + spin_lock_bh(&mvm->time_event_lock); + te_data->running = le32_to_cpu(notif->start); + te_data->end_jiffies = + TU_TO_EXP_TIME(te_data->duration); + spin_unlock_bh(&mvm->time_event_lock); + } else { + /* + * By now, we should have finished association + * and know the dtim period. + */ + iwl_mvm_te_check_disconnect(mvm, vif, + !vif->cfg.assoc ? + "Not associated and the session protection is over already..." : + "No beacon heard and the session protection is over already..."); + spin_lock_bh(&mvm->time_event_lock); + iwl_mvm_te_clear_data(mvm, te_data); + spin_unlock_bh(&mvm->time_event_lock); + } + + goto out_unlock; + } + + if (!le32_to_cpu(notif->status) || !le32_to_cpu(notif->start)) { + /* End TE, notify mac80211 */ + mvmvif->time_event_data.id = SESSION_PROTECT_CONF_MAX_ID; + ieee80211_remain_on_channel_expired(mvm->hw); + iwl_mvm_p2p_roc_finished(mvm); + } else if (le32_to_cpu(notif->start)) { + if (WARN_ON(mvmvif->time_event_data.id != + le32_to_cpu(notif->conf_id))) + goto out_unlock; + set_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status); + ieee80211_ready_on_channel(mvm->hw); /* Start TE */ + } + + out_unlock: + rcu_read_unlock(); +} + +static int +iwl_mvm_start_p2p_roc_session_protection(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + int duration, + enum ieee80211_roc_type type) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_session_prot_cmd cmd = { + .id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)), + .action = cpu_to_le32(FW_CTXT_ACTION_ADD), + .duration_tu = cpu_to_le32(MSEC_TO_TU(duration)), + }; + + lockdep_assert_held(&mvm->mutex); + + /* The time_event_data.id field is reused to save session + * protection's configuration. + */ + switch (type) { + case IEEE80211_ROC_TYPE_NORMAL: + mvmvif->time_event_data.id = + SESSION_PROTECT_CONF_P2P_DEVICE_DISCOV; + break; + case IEEE80211_ROC_TYPE_MGMT_TX: + mvmvif->time_event_data.id = + SESSION_PROTECT_CONF_P2P_GO_NEGOTIATION; + break; + default: + WARN_ONCE(1, "Got an invalid ROC type\n"); + return -EINVAL; + } + + cmd.conf_id = cpu_to_le32(mvmvif->time_event_data.id); + return iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(MAC_CONF_GROUP, SESSION_PROTECTION_CMD), + 0, sizeof(cmd), &cmd); +} + +int iwl_mvm_start_p2p_roc(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + int duration, enum ieee80211_roc_type type) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; + struct iwl_time_event_cmd time_cmd = {}; + + lockdep_assert_held(&mvm->mutex); + if (te_data->running) { + IWL_WARN(mvm, "P2P_DEVICE remain on channel already running\n"); + return -EBUSY; + } + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) + return iwl_mvm_start_p2p_roc_session_protection(mvm, vif, + duration, + type); + + time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_ADD); + time_cmd.id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); + + switch (type) { + case IEEE80211_ROC_TYPE_NORMAL: + time_cmd.id = cpu_to_le32(IWL_MVM_ROC_TE_TYPE_NORMAL); + break; + case IEEE80211_ROC_TYPE_MGMT_TX: + time_cmd.id = cpu_to_le32(IWL_MVM_ROC_TE_TYPE_MGMT_TX); + break; + default: + WARN_ONCE(1, "Got an invalid ROC type\n"); + return -EINVAL; + } + + time_cmd.apply_time = cpu_to_le32(0); + time_cmd.interval = cpu_to_le32(1); + + /* + * The P2P Device TEs can have lower priority than other events + * that are being scheduled by the driver/fw, and thus it might not be + * scheduled. To improve the chances of it being scheduled, allow them + * to be fragmented, and in addition allow them to be delayed. + */ + time_cmd.max_frags = min(MSEC_TO_TU(duration)/50, TE_V2_FRAG_ENDLESS); + time_cmd.max_delay = cpu_to_le32(MSEC_TO_TU(duration/2)); + time_cmd.duration = cpu_to_le32(MSEC_TO_TU(duration)); + time_cmd.repeat = 1; + time_cmd.policy = cpu_to_le16(TE_V2_NOTIF_HOST_EVENT_START | + TE_V2_NOTIF_HOST_EVENT_END | + TE_V2_START_IMMEDIATELY); + + return iwl_mvm_time_event_send_add(mvm, vif, te_data, &time_cmd); +} + +static struct iwl_mvm_time_event_data *iwl_mvm_get_roc_te(struct iwl_mvm *mvm) +{ + struct iwl_mvm_time_event_data *te_data; + + lockdep_assert_held(&mvm->mutex); + + spin_lock_bh(&mvm->time_event_lock); + + /* + * Iterate over the list of time events and find the time event that is + * associated with a P2P_DEVICE interface. + * This assumes that a P2P_DEVICE interface can have only a single time + * event at any given time and this time event coresponds to a ROC + * request + */ + list_for_each_entry(te_data, &mvm->time_event_list, list) { + if (te_data->vif->type == NL80211_IFTYPE_P2P_DEVICE) + goto out; + } + + /* There can only be at most one AUX ROC time event, we just use the + * list to simplify/unify code. Remove it if it exists. + */ + te_data = list_first_entry_or_null(&mvm->aux_roc_te_list, + struct iwl_mvm_time_event_data, + list); +out: + spin_unlock_bh(&mvm->time_event_lock); + return te_data; +} + +void iwl_mvm_cleanup_roc_te(struct iwl_mvm *mvm) +{ + struct iwl_mvm_time_event_data *te_data; + u32 uid; + + te_data = iwl_mvm_get_roc_te(mvm); + if (te_data) + __iwl_mvm_remove_time_event(mvm, te_data, &uid); +} + +void iwl_mvm_stop_roc(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif; + struct iwl_mvm_time_event_data *te_data; + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) { + mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { + iwl_mvm_cancel_session_protection(mvm, mvmvif, + mvmvif->time_event_data.id); + iwl_mvm_p2p_roc_finished(mvm); + } else { + iwl_mvm_remove_aux_roc_te(mvm, mvmvif, + &mvmvif->hs_time_event_data); + iwl_mvm_roc_finished(mvm); + } + + return; + } + + te_data = iwl_mvm_get_roc_te(mvm); + if (!te_data) { + IWL_WARN(mvm, "No remain on channel event\n"); + return; + } + + mvmvif = iwl_mvm_vif_from_mac80211(te_data->vif); + + if (te_data->vif->type == NL80211_IFTYPE_P2P_DEVICE) { + iwl_mvm_remove_time_event(mvm, mvmvif, te_data); + iwl_mvm_p2p_roc_finished(mvm); + } else { + iwl_mvm_remove_aux_roc_te(mvm, mvmvif, te_data); + iwl_mvm_roc_finished(mvm); + } +} + +void iwl_mvm_remove_csa_period(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; + u32 id; + + lockdep_assert_held(&mvm->mutex); + + spin_lock_bh(&mvm->time_event_lock); + id = te_data->id; + spin_unlock_bh(&mvm->time_event_lock); + + if (id != TE_CHANNEL_SWITCH_PERIOD) + return; + + iwl_mvm_remove_time_event(mvm, mvmvif, te_data); +} + +int iwl_mvm_schedule_csa_period(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 duration, u32 apply_time) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; + struct iwl_time_event_cmd time_cmd = {}; + + lockdep_assert_held(&mvm->mutex); + + if (te_data->running) { + u32 id; + + spin_lock_bh(&mvm->time_event_lock); + id = te_data->id; + spin_unlock_bh(&mvm->time_event_lock); + + if (id == TE_CHANNEL_SWITCH_PERIOD) { + IWL_DEBUG_TE(mvm, "CS period is already scheduled\n"); + return -EBUSY; + } + + /* + * Remove the session protection time event to allow the + * channel switch. If we got here, we just heard a beacon so + * the session protection is not needed anymore anyway. + */ + iwl_mvm_remove_time_event(mvm, mvmvif, te_data); + } + + time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_ADD); + time_cmd.id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); + time_cmd.id = cpu_to_le32(TE_CHANNEL_SWITCH_PERIOD); + time_cmd.apply_time = cpu_to_le32(apply_time); + time_cmd.max_frags = TE_V2_FRAG_NONE; + time_cmd.duration = cpu_to_le32(duration); + time_cmd.repeat = 1; + time_cmd.interval = cpu_to_le32(1); + time_cmd.policy = cpu_to_le16(TE_V2_NOTIF_HOST_EVENT_START | + TE_V2_ABSENCE); + if (!apply_time) + time_cmd.policy |= cpu_to_le16(TE_V2_START_IMMEDIATELY); + + return iwl_mvm_time_event_send_add(mvm, vif, te_data, &time_cmd); +} + +static bool iwl_mvm_session_prot_notif(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_mvm_session_prot_notif *resp; + int resp_len = iwl_rx_packet_payload_len(pkt); + + if (WARN_ON(pkt->hdr.cmd != SESSION_PROTECTION_NOTIF || + pkt->hdr.group_id != MAC_CONF_GROUP)) + return true; + + if (WARN_ON_ONCE(resp_len != sizeof(*resp))) { + IWL_ERR(mvm, "Invalid SESSION_PROTECTION_NOTIF response\n"); + return true; + } + + resp = (void *)pkt->data; + + if (!resp->status) + IWL_ERR(mvm, + "TIME_EVENT_NOTIFICATION received but not executed\n"); + + return true; +} + +void iwl_mvm_schedule_session_protection(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 duration, u32 min_duration, + bool wait_for_notif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; + const u16 notif[] = { WIDE_ID(MAC_CONF_GROUP, SESSION_PROTECTION_NOTIF) }; + struct iwl_notification_wait wait_notif; + struct iwl_mvm_session_prot_cmd cmd = { + .id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)), + .action = cpu_to_le32(FW_CTXT_ACTION_ADD), + .conf_id = cpu_to_le32(SESSION_PROTECT_CONF_ASSOC), + .duration_tu = cpu_to_le32(MSEC_TO_TU(duration)), + }; + + lockdep_assert_held(&mvm->mutex); + + spin_lock_bh(&mvm->time_event_lock); + if (te_data->running && + time_after(te_data->end_jiffies, TU_TO_EXP_TIME(min_duration))) { + IWL_DEBUG_TE(mvm, "We have enough time in the current TE: %u\n", + jiffies_to_msecs(te_data->end_jiffies - jiffies)); + spin_unlock_bh(&mvm->time_event_lock); + + return; + } + + iwl_mvm_te_clear_data(mvm, te_data); + /* + * The time_event_data.id field is reused to save session + * protection's configuration. + */ + te_data->id = le32_to_cpu(cmd.conf_id); + te_data->duration = le32_to_cpu(cmd.duration_tu); + te_data->vif = vif; + spin_unlock_bh(&mvm->time_event_lock); + + IWL_DEBUG_TE(mvm, "Add new session protection, duration %d TU\n", + le32_to_cpu(cmd.duration_tu)); + + if (!wait_for_notif) { + if (iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(MAC_CONF_GROUP, SESSION_PROTECTION_CMD), + 0, sizeof(cmd), &cmd)) { + IWL_ERR(mvm, + "Couldn't send the SESSION_PROTECTION_CMD\n"); + spin_lock_bh(&mvm->time_event_lock); + iwl_mvm_te_clear_data(mvm, te_data); + spin_unlock_bh(&mvm->time_event_lock); + } + + return; + } + + iwl_init_notification_wait(&mvm->notif_wait, &wait_notif, + notif, ARRAY_SIZE(notif), + iwl_mvm_session_prot_notif, NULL); + + if (iwl_mvm_send_cmd_pdu(mvm, + WIDE_ID(MAC_CONF_GROUP, SESSION_PROTECTION_CMD), + 0, sizeof(cmd), &cmd)) { + IWL_ERR(mvm, + "Couldn't send the SESSION_PROTECTION_CMD\n"); + iwl_remove_notification(&mvm->notif_wait, &wait_notif); + } else if (iwl_wait_notification(&mvm->notif_wait, &wait_notif, + TU_TO_JIFFIES(100))) { + IWL_ERR(mvm, + "Failed to protect session until session protection\n"); + } +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/time-event.h b/drivers/net/wireless/intel/iwlwifi/mvm/time-event.h new file mode 100644 index 000000000..989a5319f --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/time-event.h @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2012-2014, 2019-2020 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + */ +#ifndef __time_event_h__ +#define __time_event_h__ + +#include "fw-api.h" + +#include "mvm.h" + +/** + * DOC: Time Events - what is it? + * + * Time Events are a fw feature that allows the driver to control the presence + * of the device on the channel. Since the fw supports multiple channels + * concurrently, the fw may choose to jump to another channel at any time. + * In order to make sure that the fw is on a specific channel at a certain time + * and for a certain duration, the driver needs to issue a time event. + * + * The simplest example is for BSS association. The driver issues a time event, + * waits for it to start, and only then tells mac80211 that we can start the + * association. This way, we make sure that the association will be done + * smoothly and won't be interrupted by channel switch decided within the fw. + */ + + /** + * DOC: The flow against the fw + * + * When the driver needs to make sure we are in a certain channel, at a certain + * time and for a certain duration, it sends a Time Event. The flow against the + * fw goes like this: + * 1) Driver sends a TIME_EVENT_CMD to the fw + * 2) Driver gets the response for that command. This response contains the + * Unique ID (UID) of the event. + * 3) The fw sends notification when the event starts. + * + * Of course the API provides various options that allow to cover parameters + * of the flow. + * What is the duration of the event? + * What is the start time of the event? + * Is there an end-time for the event? + * How much can the event be delayed? + * Can the event be split? + * If yes what is the maximal number of chunks? + * etc... + */ + +/** + * DOC: Abstraction to the driver + * + * In order to simplify the use of time events to the rest of the driver, + * we abstract the use of time events. This component provides the functions + * needed by the driver. + */ + +#define IWL_MVM_TE_SESSION_PROTECTION_MAX_TIME_MS 600 +#define IWL_MVM_TE_SESSION_PROTECTION_MIN_TIME_MS 400 + +/** + * iwl_mvm_protect_session - start / extend the session protection. + * @mvm: the mvm component + * @vif: the virtual interface for which the session is issued + * @duration: the duration of the session in TU. + * @min_duration: will start a new session if the current session will end + * in less than min_duration. + * @max_delay: maximum delay before starting the time event (in TU) + * @wait_for_notif: true if it is required that a time event notification be + * waited for (that the time event has been scheduled before returning) + * + * This function can be used to start a session protection which means that the + * fw will stay on the channel for %duration_ms milliseconds. This function + * can block (sleep) until the session starts. This function can also be used + * to extend a currently running session. + * This function is meant to be used for BSS association for example, where we + * want to make sure that the fw stays on the channel during the association. + */ +void iwl_mvm_protect_session(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 duration, u32 min_duration, + u32 max_delay, bool wait_for_notif); + +/** + * iwl_mvm_stop_session_protection - cancel the session protection. + * @mvm: the mvm component + * @vif: the virtual interface for which the session is issued + * + * This functions cancels the session protection which is an act of good + * citizenship. If it is not needed any more it should be canceled because + * the other bindings wait for the medium during that time. + * This funtions doesn't sleep. + */ +void iwl_mvm_stop_session_protection(struct iwl_mvm *mvm, + struct ieee80211_vif *vif); + +/* + * iwl_mvm_rx_time_event_notif - handles %TIME_EVENT_NOTIFICATION. + */ +void iwl_mvm_rx_time_event_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); + +/** + * iwl_mvm_start_p2p_roc - start remain on channel for p2p device functionality + * @mvm: the mvm component + * @vif: the virtual interface for which the roc is requested. It is assumed + * that the vif type is NL80211_IFTYPE_P2P_DEVICE + * @duration: the requested duration in millisecond for the fw to be on the + * channel that is bound to the vif. + * @type: the remain on channel request type + * + * This function can be used to issue a remain on channel session, + * which means that the fw will stay in the channel for the request %duration + * milliseconds. The function is async, meaning that it only issues the ROC + * request but does not wait for it to start. Once the FW is ready to serve the + * ROC request, it will issue a notification to the driver that it is on the + * requested channel. Once the FW completes the ROC request it will issue + * another notification to the driver. + */ +int iwl_mvm_start_p2p_roc(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + int duration, enum ieee80211_roc_type type); + +/** + * iwl_mvm_stop_roc - stop remain on channel functionality + * @mvm: the mvm component + * @vif: the virtual interface for which the roc is stopped + * + * This function can be used to cancel an ongoing ROC session. + * The function is async, it will instruct the FW to stop serving the ROC + * session, but will not wait for the actual stopping of the session. + */ +void iwl_mvm_stop_roc(struct iwl_mvm *mvm, struct ieee80211_vif *vif); + +/** + * iwl_mvm_remove_time_event - general function to clean up of time event + * @mvm: the mvm component + * @vif: the vif to which the time event belongs + * @te_data: the time event data that corresponds to that time event + * + * This function can be used to cancel a time event regardless its type. + * It is useful for cleaning up time events running before removing an + * interface. + */ +void iwl_mvm_remove_time_event(struct iwl_mvm *mvm, + struct iwl_mvm_vif *mvmvif, + struct iwl_mvm_time_event_data *te_data); + +/** + * iwl_mvm_te_clear_data - remove time event from list + * @mvm: the mvm component + * @te_data: the time event data to remove + * + * This function is mostly internal, it is made available here only + * for firmware restart purposes. + */ +void iwl_mvm_te_clear_data(struct iwl_mvm *mvm, + struct iwl_mvm_time_event_data *te_data); + +void iwl_mvm_cleanup_roc_te(struct iwl_mvm *mvm); +void iwl_mvm_roc_done_wk(struct work_struct *wk); + +void iwl_mvm_remove_csa_period(struct iwl_mvm *mvm, + struct ieee80211_vif *vif); + +/** + * iwl_mvm_schedule_csa_period - request channel switch absence period + * @mvm: the mvm component + * @vif: the virtual interface for which the channel switch is issued + * @duration: the duration of the NoA in TU. + * @apply_time: NoA start time in GP2. + * + * This function is used to schedule NoA time event and is used to perform + * the channel switch flow. + */ +int iwl_mvm_schedule_csa_period(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 duration, u32 apply_time); + +/** + * iwl_mvm_te_scheduled - check if the fw received the TE cmd + * @te_data: the time event data that corresponds to that time event + * + * This function returns true iff this TE is added to the fw. + */ +static inline bool +iwl_mvm_te_scheduled(struct iwl_mvm_time_event_data *te_data) +{ + if (!te_data) + return false; + + return !!te_data->uid; +} + +/** + * iwl_mvm_schedule_session_protection - schedule a session protection + * @mvm: the mvm component + * @vif: the virtual interface for which the protection issued + * @duration: the duration of the protection + * @wait_for_notif: if true, will block until the start of the protection + */ +void iwl_mvm_schedule_session_protection(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 duration, u32 min_duration, + bool wait_for_notif); + +/** + * iwl_mvm_rx_session_protect_notif - handles %SESSION_PROTECTION_NOTIF + */ +void iwl_mvm_rx_session_protect_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb); + +#endif /* __time_event_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/tt.c b/drivers/net/wireless/intel/iwlwifi/mvm/tt.c new file mode 100644 index 000000000..69cf3a372 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/tt.c @@ -0,0 +1,908 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2019-2021 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015-2016 Intel Deutschland GmbH + */ +#include <linux/sort.h> + +#include "mvm.h" + +#define IWL_MVM_TEMP_NOTIF_WAIT_TIMEOUT HZ + +void iwl_mvm_enter_ctkill(struct iwl_mvm *mvm) +{ + struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle; + u32 duration = tt->params.ct_kill_duration; + + if (test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status)) + return; + + IWL_ERR(mvm, "Enter CT Kill\n"); + iwl_mvm_set_hw_ctkill_state(mvm, true); + + if (!iwl_mvm_is_tt_in_fw(mvm)) { + tt->throttle = false; + tt->dynamic_smps = false; + } + + /* Don't schedule an exit work if we're in test mode, since + * the temperature will not change unless we manually set it + * again (or disable testing). + */ + if (!mvm->temperature_test) + schedule_delayed_work(&tt->ct_kill_exit, + round_jiffies_relative(duration * HZ)); +} + +static void iwl_mvm_exit_ctkill(struct iwl_mvm *mvm) +{ + if (!test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status)) + return; + + IWL_ERR(mvm, "Exit CT Kill\n"); + iwl_mvm_set_hw_ctkill_state(mvm, false); +} + +static void iwl_mvm_tt_temp_changed(struct iwl_mvm *mvm, u32 temp) +{ + /* ignore the notification if we are in test mode */ + if (mvm->temperature_test) + return; + + if (mvm->temperature == temp) + return; + + mvm->temperature = temp; + iwl_mvm_tt_handler(mvm); +} + +static int iwl_mvm_temp_notif_parse(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{ + struct iwl_dts_measurement_notif_v1 *notif_v1; + int len = iwl_rx_packet_payload_len(pkt); + int temp; + + /* we can use notif_v1 only, because v2 only adds an additional + * parameter, which is not used in this function. + */ + if (WARN_ON_ONCE(len < sizeof(*notif_v1))) { + IWL_ERR(mvm, "Invalid DTS_MEASUREMENT_NOTIFICATION\n"); + return -EINVAL; + } + + notif_v1 = (void *)pkt->data; + + temp = le32_to_cpu(notif_v1->temp); + + /* shouldn't be negative, but since it's s32, make sure it isn't */ + if (WARN_ON_ONCE(temp < 0)) + temp = 0; + + IWL_DEBUG_TEMP(mvm, "DTS_MEASUREMENT_NOTIFICATION - %d\n", temp); + + return temp; +} + +static bool iwl_mvm_temp_notif_wait(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); + int *temp = data; + int ret; + + ret = iwl_mvm_temp_notif_parse(mvm, pkt); + if (ret < 0) + return true; + + *temp = ret; + + return true; +} + +void iwl_mvm_temp_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_dts_measurement_notif_v2 *notif_v2; + int len = iwl_rx_packet_payload_len(pkt); + int temp; + u32 ths_crossed; + + /* the notification is handled synchronously in ctkill, so skip here */ + if (test_bit(IWL_MVM_STATUS_HW_CTKILL, &mvm->status)) + return; + + temp = iwl_mvm_temp_notif_parse(mvm, pkt); + + if (!iwl_mvm_is_tt_in_fw(mvm)) { + if (temp >= 0) + iwl_mvm_tt_temp_changed(mvm, temp); + return; + } + + if (WARN_ON_ONCE(len < sizeof(*notif_v2))) { + IWL_ERR(mvm, "Invalid DTS_MEASUREMENT_NOTIFICATION\n"); + return; + } + + notif_v2 = (void *)pkt->data; + ths_crossed = le32_to_cpu(notif_v2->threshold_idx); + + /* 0xFF in ths_crossed means the notification is not related + * to a trip, so we can ignore it here. + */ + if (ths_crossed == 0xFF) + return; + + IWL_DEBUG_TEMP(mvm, "Temp = %d Threshold crossed = %d\n", + temp, ths_crossed); + +#ifdef CONFIG_THERMAL + if (WARN_ON(ths_crossed >= IWL_MAX_DTS_TRIPS)) + return; + + if (mvm->tz_device.tzone) { + struct iwl_mvm_thermal_device *tz_dev = &mvm->tz_device; + + thermal_zone_device_update(tz_dev->tzone, + THERMAL_TRIP_VIOLATED); + } +#endif /* CONFIG_THERMAL */ +} + +void iwl_mvm_ct_kill_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct ct_kill_notif *notif; + + notif = (struct ct_kill_notif *)pkt->data; + IWL_DEBUG_TEMP(mvm, "CT Kill notification temperature = %d\n", + notif->temperature); + if (iwl_fw_lookup_notif_ver(mvm->fw, PHY_OPS_GROUP, + CT_KILL_NOTIFICATION, 0) > 1) + IWL_DEBUG_TEMP(mvm, + "CT kill notification DTS bitmap = 0x%x, Scheme = %d\n", + notif->dts, notif->scheme); + + iwl_mvm_enter_ctkill(mvm); +} + +/* + * send the DTS_MEASUREMENT_TRIGGER command with or without waiting for a + * response. If we get a response then the measurement is stored in 'temp' + */ +static int iwl_mvm_send_temp_cmd(struct iwl_mvm *mvm, bool response, s32 *temp) +{ + struct iwl_host_cmd cmd = {}; + struct iwl_dts_measurement_cmd dts_cmd = { + .flags = cpu_to_le32(DTS_TRIGGER_CMD_FLAGS_TEMP), + }; + struct iwl_ext_dts_measurement_cmd ext_cmd = { + .control_mode = cpu_to_le32(DTS_DIRECT_WITHOUT_MEASURE), + }; + struct iwl_dts_measurement_resp *resp; + void *cmd_ptr; + int ret; + u32 cmd_flags = 0; + u16 len; + + /* Check which command format is used (regular/extended) */ + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_EXTENDED_DTS_MEASURE)) { + len = sizeof(ext_cmd); + cmd_ptr = &ext_cmd; + } else { + len = sizeof(dts_cmd); + cmd_ptr = &dts_cmd; + } + /* The command version where we get a response is zero length */ + if (response) { + cmd_flags = CMD_WANT_SKB; + len = 0; + } + + cmd.id = WIDE_ID(PHY_OPS_GROUP, CMD_DTS_MEASUREMENT_TRIGGER_WIDE); + cmd.len[0] = len; + cmd.flags = cmd_flags; + cmd.data[0] = cmd_ptr; + + IWL_DEBUG_TEMP(mvm, + "Sending temperature measurement command - %s response\n", + response ? "with" : "without"); + ret = iwl_mvm_send_cmd(mvm, &cmd); + + if (ret) { + IWL_ERR(mvm, + "Failed to send the temperature measurement command (err=%d)\n", + ret); + return ret; + } + + if (response) { + resp = (void *)cmd.resp_pkt->data; + *temp = le32_to_cpu(resp->temp); + IWL_DEBUG_TEMP(mvm, + "Got temperature measurement response: temp=%d\n", + *temp); + iwl_free_resp(&cmd); + } + + return ret; +} + +int iwl_mvm_get_temp(struct iwl_mvm *mvm, s32 *temp) +{ + struct iwl_notification_wait wait_temp_notif; + static u16 temp_notif[] = { WIDE_ID(PHY_OPS_GROUP, + DTS_MEASUREMENT_NOTIF_WIDE) }; + int ret; + u8 cmd_ver; + + /* + * If command version is 1 we send the command and immediately get + * a response. For older versions we send the command and wait for a + * notification (no command TLV for previous versions). + */ + cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, + WIDE_ID(PHY_OPS_GROUP, CMD_DTS_MEASUREMENT_TRIGGER_WIDE), + IWL_FW_CMD_VER_UNKNOWN); + if (cmd_ver == 1) + return iwl_mvm_send_temp_cmd(mvm, true, temp); + + lockdep_assert_held(&mvm->mutex); + + iwl_init_notification_wait(&mvm->notif_wait, &wait_temp_notif, + temp_notif, ARRAY_SIZE(temp_notif), + iwl_mvm_temp_notif_wait, temp); + + ret = iwl_mvm_send_temp_cmd(mvm, false, temp); + if (ret) { + iwl_remove_notification(&mvm->notif_wait, &wait_temp_notif); + return ret; + } + + ret = iwl_wait_notification(&mvm->notif_wait, &wait_temp_notif, + IWL_MVM_TEMP_NOTIF_WAIT_TIMEOUT); + if (ret) + IWL_WARN(mvm, "Getting the temperature timed out\n"); + + return ret; +} + +static void check_exit_ctkill(struct work_struct *work) +{ + struct iwl_mvm_tt_mgmt *tt; + struct iwl_mvm *mvm; + u32 duration; + s32 temp; + int ret; + + tt = container_of(work, struct iwl_mvm_tt_mgmt, ct_kill_exit.work); + mvm = container_of(tt, struct iwl_mvm, thermal_throttle); + + if (iwl_mvm_is_tt_in_fw(mvm)) { + iwl_mvm_exit_ctkill(mvm); + + return; + } + + duration = tt->params.ct_kill_duration; + + flush_work(&mvm->roc_done_wk); + + mutex_lock(&mvm->mutex); + + if (__iwl_mvm_mac_start(mvm)) + goto reschedule; + + ret = iwl_mvm_get_temp(mvm, &temp); + + __iwl_mvm_mac_stop(mvm); + + if (ret) + goto reschedule; + + IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", temp); + + if (temp <= tt->params.ct_kill_exit) { + mutex_unlock(&mvm->mutex); + iwl_mvm_exit_ctkill(mvm); + return; + } + +reschedule: + mutex_unlock(&mvm->mutex); + schedule_delayed_work(&mvm->thermal_throttle.ct_kill_exit, + round_jiffies(duration * HZ)); +} + +static void iwl_mvm_tt_smps_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = _data; + enum ieee80211_smps_mode smps_mode; + + lockdep_assert_held(&mvm->mutex); + + if (mvm->thermal_throttle.dynamic_smps) + smps_mode = IEEE80211_SMPS_DYNAMIC; + else + smps_mode = IEEE80211_SMPS_AUTOMATIC; + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_TT, smps_mode); +} + +static void iwl_mvm_tt_tx_protection(struct iwl_mvm *mvm, bool enable) +{ + struct iwl_mvm_sta *mvmsta; + int i, err; + + for (i = 0; i < mvm->fw->ucode_capa.num_stations; i++) { + mvmsta = iwl_mvm_sta_from_staid_protected(mvm, i); + if (!mvmsta) + continue; + + if (enable == mvmsta->tt_tx_protection) + continue; + err = iwl_mvm_tx_protection(mvm, mvmsta, enable); + if (err) { + IWL_ERR(mvm, "Failed to %s Tx protection\n", + enable ? "enable" : "disable"); + } else { + IWL_DEBUG_TEMP(mvm, "%s Tx protection\n", + enable ? "Enable" : "Disable"); + mvmsta->tt_tx_protection = enable; + } + } +} + +void iwl_mvm_tt_tx_backoff(struct iwl_mvm *mvm, u32 backoff) +{ + struct iwl_host_cmd cmd = { + .id = REPLY_THERMAL_MNG_BACKOFF, + .len = { sizeof(u32), }, + .data = { &backoff, }, + }; + + backoff = max(backoff, mvm->thermal_throttle.min_backoff); + + if (iwl_mvm_send_cmd(mvm, &cmd) == 0) { + IWL_DEBUG_TEMP(mvm, "Set Thermal Tx backoff to: %u\n", + backoff); + mvm->thermal_throttle.tx_backoff = backoff; + } else { + IWL_ERR(mvm, "Failed to change Thermal Tx backoff\n"); + } +} + +void iwl_mvm_tt_handler(struct iwl_mvm *mvm) +{ + struct iwl_tt_params *params = &mvm->thermal_throttle.params; + struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle; + s32 temperature = mvm->temperature; + bool throttle_enable = false; + int i; + u32 tx_backoff; + + IWL_DEBUG_TEMP(mvm, "NIC temperature: %d\n", mvm->temperature); + + if (params->support_ct_kill && temperature >= params->ct_kill_entry) { + iwl_mvm_enter_ctkill(mvm); + return; + } + + if (params->support_ct_kill && + temperature <= params->ct_kill_exit) { + iwl_mvm_exit_ctkill(mvm); + return; + } + + if (params->support_dynamic_smps) { + if (!tt->dynamic_smps && + temperature >= params->dynamic_smps_entry) { + IWL_DEBUG_TEMP(mvm, "Enable dynamic SMPS\n"); + tt->dynamic_smps = true; + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_tt_smps_iterator, mvm); + throttle_enable = true; + } else if (tt->dynamic_smps && + temperature <= params->dynamic_smps_exit) { + IWL_DEBUG_TEMP(mvm, "Disable dynamic SMPS\n"); + tt->dynamic_smps = false; + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_tt_smps_iterator, mvm); + } + } + + if (params->support_tx_protection) { + if (temperature >= params->tx_protection_entry) { + iwl_mvm_tt_tx_protection(mvm, true); + throttle_enable = true; + } else if (temperature <= params->tx_protection_exit) { + iwl_mvm_tt_tx_protection(mvm, false); + } + } + + if (params->support_tx_backoff) { + tx_backoff = tt->min_backoff; + for (i = 0; i < TT_TX_BACKOFF_SIZE; i++) { + if (temperature < params->tx_backoff[i].temperature) + break; + tx_backoff = max(tt->min_backoff, + params->tx_backoff[i].backoff); + } + if (tx_backoff != tt->min_backoff) + throttle_enable = true; + if (tt->tx_backoff != tx_backoff) + iwl_mvm_tt_tx_backoff(mvm, tx_backoff); + } + + if (!tt->throttle && throttle_enable) { + IWL_WARN(mvm, + "Due to high temperature thermal throttling initiated\n"); + tt->throttle = true; + } else if (tt->throttle && !tt->dynamic_smps && + tt->tx_backoff == tt->min_backoff && + temperature <= params->tx_protection_exit) { + IWL_WARN(mvm, + "Temperature is back to normal thermal throttling stopped\n"); + tt->throttle = false; + } +} + +static const struct iwl_tt_params iwl_mvm_default_tt_params = { + .ct_kill_entry = 118, + .ct_kill_exit = 96, + .ct_kill_duration = 5, + .dynamic_smps_entry = 114, + .dynamic_smps_exit = 110, + .tx_protection_entry = 114, + .tx_protection_exit = 108, + .tx_backoff = { + {.temperature = 112, .backoff = 200}, + {.temperature = 113, .backoff = 600}, + {.temperature = 114, .backoff = 1200}, + {.temperature = 115, .backoff = 2000}, + {.temperature = 116, .backoff = 4000}, + {.temperature = 117, .backoff = 10000}, + }, + .support_ct_kill = true, + .support_dynamic_smps = true, + .support_tx_protection = true, + .support_tx_backoff = true, +}; + +/* budget in mWatt */ +static const u32 iwl_mvm_cdev_budgets[] = { + 2400, /* cooling state 0 */ + 2000, /* cooling state 1 */ + 1800, /* cooling state 2 */ + 1600, /* cooling state 3 */ + 1400, /* cooling state 4 */ + 1200, /* cooling state 5 */ + 1000, /* cooling state 6 */ + 900, /* cooling state 7 */ + 800, /* cooling state 8 */ + 700, /* cooling state 9 */ + 650, /* cooling state 10 */ + 600, /* cooling state 11 */ + 550, /* cooling state 12 */ + 500, /* cooling state 13 */ + 450, /* cooling state 14 */ + 400, /* cooling state 15 */ + 350, /* cooling state 16 */ + 300, /* cooling state 17 */ + 250, /* cooling state 18 */ + 200, /* cooling state 19 */ + 150, /* cooling state 20 */ +}; + +int iwl_mvm_ctdp_command(struct iwl_mvm *mvm, u32 op, u32 state) +{ + struct iwl_mvm_ctdp_cmd cmd = { + .operation = cpu_to_le32(op), + .budget = cpu_to_le32(iwl_mvm_cdev_budgets[state]), + .window_size = 0, + }; + int ret; + u32 status; + + lockdep_assert_held(&mvm->mutex); + + status = 0; + ret = iwl_mvm_send_cmd_pdu_status(mvm, WIDE_ID(PHY_OPS_GROUP, + CTDP_CONFIG_CMD), + sizeof(cmd), &cmd, &status); + + if (ret) { + IWL_ERR(mvm, "cTDP command failed (err=%d)\n", ret); + return ret; + } + + switch (op) { + case CTDP_CMD_OPERATION_START: +#ifdef CONFIG_THERMAL + mvm->cooling_dev.cur_state = state; +#endif /* CONFIG_THERMAL */ + break; + case CTDP_CMD_OPERATION_REPORT: + IWL_DEBUG_TEMP(mvm, "cTDP avg energy in mWatt = %d\n", status); + /* when the function is called with CTDP_CMD_OPERATION_REPORT + * option the function should return the average budget value + * that is received from the FW. + * The budget can't be less or equal to 0, so it's possible + * to distinguish between error values and budgets. + */ + return status; + case CTDP_CMD_OPERATION_STOP: + IWL_DEBUG_TEMP(mvm, "cTDP stopped successfully\n"); + break; + } + + return 0; +} + +#ifdef CONFIG_THERMAL +static int compare_temps(const void *a, const void *b) +{ + return ((s16)le16_to_cpu(*(__le16 *)a) - + (s16)le16_to_cpu(*(__le16 *)b)); +} +#endif + +int iwl_mvm_send_temp_report_ths_cmd(struct iwl_mvm *mvm) +{ + struct temp_report_ths_cmd cmd = {0}; + int ret; +#ifdef CONFIG_THERMAL + int i, j, idx = 0; + + lockdep_assert_held(&mvm->mutex); + + if (!mvm->tz_device.tzone) + goto send; + + /* The driver holds array of temperature trips that are unsorted + * and uncompressed, the FW should get it compressed and sorted + */ + + /* compress temp_trips to cmd array, remove uninitialized values*/ + for (i = 0; i < IWL_MAX_DTS_TRIPS; i++) { + if (mvm->tz_device.temp_trips[i] != S16_MIN) { + cmd.thresholds[idx++] = + cpu_to_le16(mvm->tz_device.temp_trips[i]); + } + } + cmd.num_temps = cpu_to_le32(idx); + + if (!idx) + goto send; + + /*sort cmd array*/ + sort(cmd.thresholds, idx, sizeof(s16), compare_temps, NULL); + + /* we should save the indexes of trips because we sort + * and compress the orginal array + */ + for (i = 0; i < idx; i++) { + for (j = 0; j < IWL_MAX_DTS_TRIPS; j++) { + if (le16_to_cpu(cmd.thresholds[i]) == + mvm->tz_device.temp_trips[j]) + mvm->tz_device.fw_trips_index[i] = j; + } + } + +send: +#endif + ret = iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(PHY_OPS_GROUP, + TEMP_REPORTING_THRESHOLDS_CMD), + 0, sizeof(cmd), &cmd); + if (ret) + IWL_ERR(mvm, "TEMP_REPORT_THS_CMD command failed (err=%d)\n", + ret); + + return ret; +} + +#ifdef CONFIG_THERMAL +static int iwl_mvm_tzone_get_temp(struct thermal_zone_device *device, + int *temperature) +{ + struct iwl_mvm *mvm = (struct iwl_mvm *)device->devdata; + int ret; + int temp; + + mutex_lock(&mvm->mutex); + + if (!iwl_mvm_firmware_running(mvm) || + mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) { + ret = -ENODATA; + goto out; + } + + ret = iwl_mvm_get_temp(mvm, &temp); + if (ret) + goto out; + + *temperature = temp * 1000; + +out: + mutex_unlock(&mvm->mutex); + return ret; +} + +static int iwl_mvm_tzone_get_trip_temp(struct thermal_zone_device *device, + int trip, int *temp) +{ + struct iwl_mvm *mvm = (struct iwl_mvm *)device->devdata; + + if (trip < 0 || trip >= IWL_MAX_DTS_TRIPS) + return -EINVAL; + + *temp = mvm->tz_device.temp_trips[trip] * 1000; + + return 0; +} + +static int iwl_mvm_tzone_get_trip_type(struct thermal_zone_device *device, + int trip, enum thermal_trip_type *type) +{ + if (trip < 0 || trip >= IWL_MAX_DTS_TRIPS) + return -EINVAL; + + *type = THERMAL_TRIP_PASSIVE; + + return 0; +} + +static int iwl_mvm_tzone_set_trip_temp(struct thermal_zone_device *device, + int trip, int temp) +{ + struct iwl_mvm *mvm = (struct iwl_mvm *)device->devdata; + struct iwl_mvm_thermal_device *tzone; + int i, ret; + s16 temperature; + + mutex_lock(&mvm->mutex); + + if (!iwl_mvm_firmware_running(mvm) || + mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) { + ret = -EIO; + goto out; + } + + if (trip < 0 || trip >= IWL_MAX_DTS_TRIPS) { + ret = -EINVAL; + goto out; + } + + if ((temp / 1000) > S16_MAX) { + ret = -EINVAL; + goto out; + } + + temperature = (s16)(temp / 1000); + tzone = &mvm->tz_device; + + if (!tzone) { + ret = -EIO; + goto out; + } + + /* no updates*/ + if (tzone->temp_trips[trip] == temperature) { + ret = 0; + goto out; + } + + /* already existing temperature */ + for (i = 0; i < IWL_MAX_DTS_TRIPS; i++) { + if (tzone->temp_trips[i] == temperature) { + ret = -EINVAL; + goto out; + } + } + + tzone->temp_trips[trip] = temperature; + + ret = iwl_mvm_send_temp_report_ths_cmd(mvm); +out: + mutex_unlock(&mvm->mutex); + return ret; +} + +static struct thermal_zone_device_ops tzone_ops = { + .get_temp = iwl_mvm_tzone_get_temp, + .get_trip_temp = iwl_mvm_tzone_get_trip_temp, + .get_trip_type = iwl_mvm_tzone_get_trip_type, + .set_trip_temp = iwl_mvm_tzone_set_trip_temp, +}; + +/* make all trips writable */ +#define IWL_WRITABLE_TRIPS_MSK (BIT(IWL_MAX_DTS_TRIPS) - 1) + +static void iwl_mvm_thermal_zone_register(struct iwl_mvm *mvm) +{ + int i, ret; + char name[16]; + static atomic_t counter = ATOMIC_INIT(0); + + if (!iwl_mvm_is_tt_in_fw(mvm)) { + mvm->tz_device.tzone = NULL; + + return; + } + + BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH); + + sprintf(name, "iwlwifi_%u", atomic_inc_return(&counter) & 0xFF); + mvm->tz_device.tzone = thermal_zone_device_register(name, + IWL_MAX_DTS_TRIPS, + IWL_WRITABLE_TRIPS_MSK, + mvm, &tzone_ops, + NULL, 0, 0); + if (IS_ERR(mvm->tz_device.tzone)) { + IWL_DEBUG_TEMP(mvm, + "Failed to register to thermal zone (err = %ld)\n", + PTR_ERR(mvm->tz_device.tzone)); + mvm->tz_device.tzone = NULL; + return; + } + + ret = thermal_zone_device_enable(mvm->tz_device.tzone); + if (ret) { + IWL_DEBUG_TEMP(mvm, "Failed to enable thermal zone\n"); + thermal_zone_device_unregister(mvm->tz_device.tzone); + return; + } + + /* 0 is a valid temperature, + * so initialize the array with S16_MIN which invalid temperature + */ + for (i = 0 ; i < IWL_MAX_DTS_TRIPS; i++) + mvm->tz_device.temp_trips[i] = S16_MIN; +} + +static int iwl_mvm_tcool_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = ARRAY_SIZE(iwl_mvm_cdev_budgets) - 1; + + return 0; +} + +static int iwl_mvm_tcool_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct iwl_mvm *mvm = (struct iwl_mvm *)(cdev->devdata); + + *state = mvm->cooling_dev.cur_state; + + return 0; +} + +static int iwl_mvm_tcool_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long new_state) +{ + struct iwl_mvm *mvm = (struct iwl_mvm *)(cdev->devdata); + int ret; + + mutex_lock(&mvm->mutex); + + if (!iwl_mvm_firmware_running(mvm) || + mvm->fwrt.cur_fw_img != IWL_UCODE_REGULAR) { + ret = -EIO; + goto unlock; + } + + if (new_state >= ARRAY_SIZE(iwl_mvm_cdev_budgets)) { + ret = -EINVAL; + goto unlock; + } + + ret = iwl_mvm_ctdp_command(mvm, CTDP_CMD_OPERATION_START, + new_state); + +unlock: + mutex_unlock(&mvm->mutex); + return ret; +} + +static const struct thermal_cooling_device_ops tcooling_ops = { + .get_max_state = iwl_mvm_tcool_get_max_state, + .get_cur_state = iwl_mvm_tcool_get_cur_state, + .set_cur_state = iwl_mvm_tcool_set_cur_state, +}; + +static void iwl_mvm_cooling_device_register(struct iwl_mvm *mvm) +{ + char name[] = "iwlwifi"; + + if (!iwl_mvm_is_ctdp_supported(mvm)) + return; + + BUILD_BUG_ON(ARRAY_SIZE(name) >= THERMAL_NAME_LENGTH); + + mvm->cooling_dev.cdev = + thermal_cooling_device_register(name, + mvm, + &tcooling_ops); + + if (IS_ERR(mvm->cooling_dev.cdev)) { + IWL_DEBUG_TEMP(mvm, + "Failed to register to cooling device (err = %ld)\n", + PTR_ERR(mvm->cooling_dev.cdev)); + mvm->cooling_dev.cdev = NULL; + return; + } +} + +static void iwl_mvm_thermal_zone_unregister(struct iwl_mvm *mvm) +{ + if (!iwl_mvm_is_tt_in_fw(mvm) || !mvm->tz_device.tzone) + return; + + IWL_DEBUG_TEMP(mvm, "Thermal zone device unregister\n"); + if (mvm->tz_device.tzone) { + thermal_zone_device_unregister(mvm->tz_device.tzone); + mvm->tz_device.tzone = NULL; + } +} + +static void iwl_mvm_cooling_device_unregister(struct iwl_mvm *mvm) +{ + if (!iwl_mvm_is_ctdp_supported(mvm) || !mvm->cooling_dev.cdev) + return; + + IWL_DEBUG_TEMP(mvm, "Cooling device unregister\n"); + if (mvm->cooling_dev.cdev) { + thermal_cooling_device_unregister(mvm->cooling_dev.cdev); + mvm->cooling_dev.cdev = NULL; + } +} +#endif /* CONFIG_THERMAL */ + +void iwl_mvm_thermal_initialize(struct iwl_mvm *mvm, u32 min_backoff) +{ + struct iwl_mvm_tt_mgmt *tt = &mvm->thermal_throttle; + + IWL_DEBUG_TEMP(mvm, "Initialize Thermal Throttling\n"); + + if (mvm->cfg->thermal_params) + tt->params = *mvm->cfg->thermal_params; + else + tt->params = iwl_mvm_default_tt_params; + + tt->throttle = false; + tt->dynamic_smps = false; + tt->min_backoff = min_backoff; + INIT_DELAYED_WORK(&tt->ct_kill_exit, check_exit_ctkill); + +#ifdef CONFIG_THERMAL + iwl_mvm_cooling_device_register(mvm); + iwl_mvm_thermal_zone_register(mvm); +#endif + mvm->init_status |= IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE; +} + +void iwl_mvm_thermal_exit(struct iwl_mvm *mvm) +{ + if (!(mvm->init_status & IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE)) + return; + + cancel_delayed_work_sync(&mvm->thermal_throttle.ct_kill_exit); + IWL_DEBUG_TEMP(mvm, "Exit Thermal Throttling\n"); + +#ifdef CONFIG_THERMAL + iwl_mvm_cooling_device_unregister(mvm); + iwl_mvm_thermal_zone_unregister(mvm); +#endif + mvm->init_status &= ~IWL_MVM_INIT_STATUS_THERMAL_INIT_COMPLETE; +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/tx.c b/drivers/net/wireless/intel/iwlwifi/mvm/tx.c new file mode 100644 index 000000000..76219486b --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/tx.c @@ -0,0 +1,2279 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include <linux/ieee80211.h> +#include <linux/etherdevice.h> +#include <linux/tcp.h> +#include <net/ip.h> +#include <net/ipv6.h> + +#include "iwl-trans.h" +#include "iwl-eeprom-parse.h" +#include "mvm.h" +#include "sta.h" + +static void +iwl_mvm_bar_check_trigger(struct iwl_mvm *mvm, const u8 *addr, + u16 tid, u16 ssn) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_ba *ba_trig; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, NULL, FW_DBG_TRIGGER_BA); + if (!trig) + return; + + ba_trig = (void *)trig->data; + + if (!(le16_to_cpu(ba_trig->tx_bar) & BIT(tid))) + return; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + "BAR sent to %pM, tid %d, ssn %d", + addr, tid, ssn); +} + +#define OPT_HDR(type, skb, off) \ + (type *)(skb_network_header(skb) + (off)) + +static u16 iwl_mvm_tx_csum_pre_bz(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_tx_info *info, bool amsdu) +{ + struct ieee80211_hdr *hdr = (void *)skb->data; + u16 offload_assist = 0; +#if IS_ENABLED(CONFIG_INET) + u16 mh_len = ieee80211_hdrlen(hdr->frame_control); + u8 protocol = 0; + + /* Do not compute checksum if already computed */ + if (skb->ip_summed != CHECKSUM_PARTIAL) + goto out; + + /* We do not expect to be requested to csum stuff we do not support */ + if (WARN_ONCE(!(mvm->hw->netdev_features & IWL_TX_CSUM_NETIF_FLAGS) || + (skb->protocol != htons(ETH_P_IP) && + skb->protocol != htons(ETH_P_IPV6)), + "No support for requested checksum\n")) { + skb_checksum_help(skb); + goto out; + } + + if (skb->protocol == htons(ETH_P_IP)) { + protocol = ip_hdr(skb)->protocol; + } else { +#if IS_ENABLED(CONFIG_IPV6) + struct ipv6hdr *ipv6h = + (struct ipv6hdr *)skb_network_header(skb); + unsigned int off = sizeof(*ipv6h); + + protocol = ipv6h->nexthdr; + while (protocol != NEXTHDR_NONE && ipv6_ext_hdr(protocol)) { + struct ipv6_opt_hdr *hp; + + /* only supported extension headers */ + if (protocol != NEXTHDR_ROUTING && + protocol != NEXTHDR_HOP && + protocol != NEXTHDR_DEST) { + skb_checksum_help(skb); + goto out; + } + + hp = OPT_HDR(struct ipv6_opt_hdr, skb, off); + protocol = hp->nexthdr; + off += ipv6_optlen(hp); + } + /* if we get here - protocol now should be TCP/UDP */ +#endif + } + + if (protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) { + WARN_ON_ONCE(1); + skb_checksum_help(skb); + goto out; + } + + /* enable L4 csum */ + offload_assist |= BIT(TX_CMD_OFFLD_L4_EN); + + /* + * Set offset to IP header (snap). + * We don't support tunneling so no need to take care of inner header. + * Size is in words. + */ + offload_assist |= (4 << TX_CMD_OFFLD_IP_HDR); + + /* Do IPv4 csum for AMSDU only (no IP csum for Ipv6) */ + if (skb->protocol == htons(ETH_P_IP) && amsdu) { + ip_hdr(skb)->check = 0; + offload_assist |= BIT(TX_CMD_OFFLD_L3_EN); + } + + /* reset UDP/TCP header csum */ + if (protocol == IPPROTO_TCP) + tcp_hdr(skb)->check = 0; + else + udp_hdr(skb)->check = 0; + + /* + * mac header len should include IV, size is in words unless + * the IV is added by the firmware like in WEP. + * In new Tx API, the IV is always added by the firmware. + */ + if (!iwl_mvm_has_new_tx_api(mvm) && info->control.hw_key && + info->control.hw_key->cipher != WLAN_CIPHER_SUITE_WEP40 && + info->control.hw_key->cipher != WLAN_CIPHER_SUITE_WEP104) + mh_len += info->control.hw_key->iv_len; + mh_len /= 2; + offload_assist |= mh_len << TX_CMD_OFFLD_MH_SIZE; + +out: +#endif + if (amsdu) + offload_assist |= BIT(TX_CMD_OFFLD_AMSDU); + else if (ieee80211_hdrlen(hdr->frame_control) % 4) + /* padding is inserted later in transport */ + offload_assist |= BIT(TX_CMD_OFFLD_PAD); + + return offload_assist; +} + +u32 iwl_mvm_tx_csum_bz(struct iwl_mvm *mvm, struct sk_buff *skb, bool amsdu) +{ + struct ieee80211_hdr *hdr = (void *)skb->data; + u32 offload_assist = IWL_TX_CMD_OFFLD_BZ_PARTIAL_CSUM; + unsigned int hdrlen = ieee80211_hdrlen(hdr->frame_control); + unsigned int csum_start = skb_checksum_start_offset(skb); + + offload_assist |= u32_encode_bits(hdrlen / 2, + IWL_TX_CMD_OFFLD_BZ_MH_LEN); + if (amsdu) + offload_assist |= IWL_TX_CMD_OFFLD_BZ_AMSDU; + else if (hdrlen % 4) + /* padding is inserted later in transport */ + offload_assist |= IWL_TX_CMD_OFFLD_BZ_MH_PAD; + + if (skb->ip_summed != CHECKSUM_PARTIAL) + return offload_assist; + + offload_assist |= IWL_TX_CMD_OFFLD_BZ_ENABLE_CSUM | + IWL_TX_CMD_OFFLD_BZ_ZERO2ONES; + + /* + * mac80211 will always calculate checksum in software for + * non-fast-xmit, and so we can only do offloaded checksum + * for fast-xmit frames. In this case, we always have the + * RFC 1042 header present. skb_checksum_start_offset() + * returns the offset from the beginning, but the hardware + * needs it from after the header & SNAP header. + */ + csum_start -= hdrlen + 8; + + offload_assist |= u32_encode_bits(csum_start, + IWL_TX_CMD_OFFLD_BZ_START_OFFS); + offload_assist |= u32_encode_bits(csum_start + skb->csum_offset, + IWL_TX_CMD_OFFLD_BZ_RESULT_OFFS); + + return offload_assist; +} + +static u32 iwl_mvm_tx_csum(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_tx_info *info, + bool amsdu) +{ + if (mvm->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_BZ) + return iwl_mvm_tx_csum_pre_bz(mvm, skb, info, amsdu); + return iwl_mvm_tx_csum_bz(mvm, skb, amsdu); +} + +/* + * Sets most of the Tx cmd's fields + */ +void iwl_mvm_set_tx_cmd(struct iwl_mvm *mvm, struct sk_buff *skb, + struct iwl_tx_cmd *tx_cmd, + struct ieee80211_tx_info *info, u8 sta_id) +{ + struct ieee80211_hdr *hdr = (void *)skb->data; + __le16 fc = hdr->frame_control; + u32 tx_flags = le32_to_cpu(tx_cmd->tx_flags); + u32 len = skb->len + FCS_LEN; + bool amsdu = false; + u8 ac; + + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK) || + (ieee80211_is_probe_resp(fc) && + !is_multicast_ether_addr(hdr->addr1))) + tx_flags |= TX_CMD_FLG_ACK; + else + tx_flags &= ~TX_CMD_FLG_ACK; + + if (ieee80211_is_probe_resp(fc)) + tx_flags |= TX_CMD_FLG_TSF; + + if (ieee80211_has_morefrags(fc)) + tx_flags |= TX_CMD_FLG_MORE_FRAG; + + if (ieee80211_is_data_qos(fc)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + tx_cmd->tid_tspec = qc[0] & 0xf; + tx_flags &= ~TX_CMD_FLG_SEQ_CTL; + amsdu = *qc & IEEE80211_QOS_CTL_A_MSDU_PRESENT; + } else if (ieee80211_is_back_req(fc)) { + struct ieee80211_bar *bar = (void *)skb->data; + u16 control = le16_to_cpu(bar->control); + u16 ssn = le16_to_cpu(bar->start_seq_num); + + tx_flags |= TX_CMD_FLG_ACK | TX_CMD_FLG_BAR; + tx_cmd->tid_tspec = (control & + IEEE80211_BAR_CTRL_TID_INFO_MASK) >> + IEEE80211_BAR_CTRL_TID_INFO_SHIFT; + WARN_ON_ONCE(tx_cmd->tid_tspec >= IWL_MAX_TID_COUNT); + iwl_mvm_bar_check_trigger(mvm, bar->ra, tx_cmd->tid_tspec, + ssn); + } else { + if (ieee80211_is_data(fc)) + tx_cmd->tid_tspec = IWL_TID_NON_QOS; + else + tx_cmd->tid_tspec = IWL_MAX_TID_COUNT; + + if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) + tx_flags |= TX_CMD_FLG_SEQ_CTL; + else + tx_flags &= ~TX_CMD_FLG_SEQ_CTL; + } + + /* Default to 0 (BE) when tid_spec is set to IWL_MAX_TID_COUNT */ + if (tx_cmd->tid_tspec < IWL_MAX_TID_COUNT) + ac = tid_to_mac80211_ac[tx_cmd->tid_tspec]; + else + ac = tid_to_mac80211_ac[0]; + + tx_flags |= iwl_mvm_bt_coex_tx_prio(mvm, hdr, info, ac) << + TX_CMD_FLG_BT_PRIO_POS; + + if (ieee80211_is_mgmt(fc)) { + if (ieee80211_is_assoc_req(fc) || ieee80211_is_reassoc_req(fc)) + tx_cmd->pm_frame_timeout = cpu_to_le16(PM_FRAME_ASSOC); + else if (ieee80211_is_action(fc)) + tx_cmd->pm_frame_timeout = cpu_to_le16(PM_FRAME_NONE); + else + tx_cmd->pm_frame_timeout = cpu_to_le16(PM_FRAME_MGMT); + + /* The spec allows Action frames in A-MPDU, we don't support + * it + */ + WARN_ON_ONCE(info->flags & IEEE80211_TX_CTL_AMPDU); + } else if (info->control.flags & IEEE80211_TX_CTRL_PORT_CTRL_PROTO) { + tx_cmd->pm_frame_timeout = cpu_to_le16(PM_FRAME_MGMT); + } else { + tx_cmd->pm_frame_timeout = cpu_to_le16(PM_FRAME_NONE); + } + + if (ieee80211_is_data(fc) && len > mvm->rts_threshold && + !is_multicast_ether_addr(hdr->addr1)) + tx_flags |= TX_CMD_FLG_PROT_REQUIRE; + + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_TXPOWER_INSERTION_SUPPORT) && + ieee80211_action_contains_tpc(skb)) + tx_flags |= TX_CMD_FLG_WRITE_TX_POWER; + + tx_cmd->tx_flags = cpu_to_le32(tx_flags); + /* Total # bytes to be transmitted - PCIe code will adjust for A-MSDU */ + tx_cmd->len = cpu_to_le16((u16)skb->len); + tx_cmd->life_time = cpu_to_le32(TX_CMD_LIFE_TIME_INFINITE); + tx_cmd->sta_id = sta_id; + + tx_cmd->offload_assist = + cpu_to_le16(iwl_mvm_tx_csum_pre_bz(mvm, skb, info, amsdu)); +} + +static u32 iwl_mvm_get_tx_ant(struct iwl_mvm *mvm, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, __le16 fc) +{ + if (info->band == NL80211_BAND_2GHZ && + !iwl_mvm_bt_coex_is_shared_ant_avail(mvm)) + return mvm->cfg->non_shared_ant << RATE_MCS_ANT_POS; + + if (sta && ieee80211_is_data(fc)) { + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + + return BIT(mvmsta->tx_ant) << RATE_MCS_ANT_POS; + } + + return BIT(mvm->mgmt_last_antenna_idx) << RATE_MCS_ANT_POS; +} + +static u32 iwl_mvm_get_tx_rate(struct iwl_mvm *mvm, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, __le16 fc) +{ + int rate_idx = -1; + u8 rate_plcp; + u32 rate_flags = 0; + bool is_cck; + + /* info->control is only relevant for non HW rate control */ + if (!ieee80211_hw_check(mvm->hw, HAS_RATE_CONTROL)) { + /* HT rate doesn't make sense for a non data frame */ + WARN_ONCE(info->control.rates[0].flags & IEEE80211_TX_RC_MCS && + !ieee80211_is_data(fc), + "Got a HT rate (flags:0x%x/mcs:%d/fc:0x%x/state:%d) for a non data frame\n", + info->control.rates[0].flags, + info->control.rates[0].idx, + le16_to_cpu(fc), + sta ? iwl_mvm_sta_from_mac80211(sta)->sta_state : -1); + + rate_idx = info->control.rates[0].idx; + } + + /* if the rate isn't a well known legacy rate, take the lowest one */ + if (rate_idx < 0 || rate_idx >= IWL_RATE_COUNT_LEGACY) + rate_idx = rate_lowest_index( + &mvm->nvm_data->bands[info->band], sta); + + /* + * For non 2 GHZ band, remap mac80211 rate + * indices into driver indices + */ + if (info->band != NL80211_BAND_2GHZ) + rate_idx += IWL_FIRST_OFDM_RATE; + + /* For 2.4 GHZ band, check that there is no need to remap */ + BUILD_BUG_ON(IWL_FIRST_CCK_RATE != 0); + + /* Get PLCP rate for tx_cmd->rate_n_flags */ + rate_plcp = iwl_mvm_mac80211_idx_to_hwrate(mvm->fw, rate_idx); + is_cck = (rate_idx >= IWL_FIRST_CCK_RATE) && (rate_idx <= IWL_LAST_CCK_RATE); + + /* Set CCK or OFDM flag */ + if (iwl_fw_lookup_cmd_ver(mvm->fw, TX_CMD, 0) > 8) { + if (!is_cck) + rate_flags |= RATE_MCS_LEGACY_OFDM_MSK; + else + rate_flags |= RATE_MCS_CCK_MSK; + } else if (is_cck) { + rate_flags |= RATE_MCS_CCK_MSK_V1; + } + + return (u32)rate_plcp | rate_flags; +} + +static u32 iwl_mvm_get_tx_rate_n_flags(struct iwl_mvm *mvm, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, __le16 fc) +{ + return iwl_mvm_get_tx_rate(mvm, info, sta, fc) | + iwl_mvm_get_tx_ant(mvm, info, sta, fc); +} + +/* + * Sets the fields in the Tx cmd that are rate related + */ +void iwl_mvm_set_tx_cmd_rate(struct iwl_mvm *mvm, struct iwl_tx_cmd *tx_cmd, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, __le16 fc) +{ + /* Set retry limit on RTS packets */ + tx_cmd->rts_retry_limit = IWL_RTS_DFAULT_RETRY_LIMIT; + + /* Set retry limit on DATA packets and Probe Responses*/ + if (ieee80211_is_probe_resp(fc)) { + tx_cmd->data_retry_limit = IWL_MGMT_DFAULT_RETRY_LIMIT; + tx_cmd->rts_retry_limit = + min(tx_cmd->data_retry_limit, tx_cmd->rts_retry_limit); + } else if (ieee80211_is_back_req(fc)) { + tx_cmd->data_retry_limit = IWL_BAR_DFAULT_RETRY_LIMIT; + } else { + tx_cmd->data_retry_limit = IWL_DEFAULT_TX_RETRY; + } + + /* + * for data packets, rate info comes from the table inside the fw. This + * table is controlled by LINK_QUALITY commands + */ + + if (ieee80211_is_data(fc) && sta) { + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + + if (mvmsta->sta_state >= IEEE80211_STA_AUTHORIZED) { + tx_cmd->initial_rate_index = 0; + tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_STA_RATE); + return; + } + } else if (ieee80211_is_back_req(fc)) { + tx_cmd->tx_flags |= + cpu_to_le32(TX_CMD_FLG_ACK | TX_CMD_FLG_BAR); + } + + /* Set the rate in the TX cmd */ + tx_cmd->rate_n_flags = + cpu_to_le32(iwl_mvm_get_tx_rate_n_flags(mvm, info, sta, fc)); +} + +static inline void iwl_mvm_set_tx_cmd_pn(struct ieee80211_tx_info *info, + u8 *crypto_hdr) +{ + struct ieee80211_key_conf *keyconf = info->control.hw_key; + u64 pn; + + pn = atomic64_inc_return(&keyconf->tx_pn); + crypto_hdr[0] = pn; + crypto_hdr[2] = 0; + crypto_hdr[3] = 0x20 | (keyconf->keyidx << 6); + crypto_hdr[1] = pn >> 8; + crypto_hdr[4] = pn >> 16; + crypto_hdr[5] = pn >> 24; + crypto_hdr[6] = pn >> 32; + crypto_hdr[7] = pn >> 40; +} + +/* + * Sets the fields in the Tx cmd that are crypto related + */ +static void iwl_mvm_set_tx_cmd_crypto(struct iwl_mvm *mvm, + struct ieee80211_tx_info *info, + struct iwl_tx_cmd *tx_cmd, + struct sk_buff *skb_frag, + int hdrlen) +{ + struct ieee80211_key_conf *keyconf = info->control.hw_key; + u8 *crypto_hdr = skb_frag->data + hdrlen; + enum iwl_tx_cmd_sec_ctrl type = TX_CMD_SEC_CCM; + u64 pn; + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + iwl_mvm_set_tx_cmd_ccmp(info, tx_cmd); + iwl_mvm_set_tx_cmd_pn(info, crypto_hdr); + break; + + case WLAN_CIPHER_SUITE_TKIP: + tx_cmd->sec_ctl = TX_CMD_SEC_TKIP; + pn = atomic64_inc_return(&keyconf->tx_pn); + ieee80211_tkip_add_iv(crypto_hdr, keyconf, pn); + ieee80211_get_tkip_p2k(keyconf, skb_frag, tx_cmd->key); + break; + + case WLAN_CIPHER_SUITE_WEP104: + tx_cmd->sec_ctl |= TX_CMD_SEC_KEY128; + fallthrough; + case WLAN_CIPHER_SUITE_WEP40: + tx_cmd->sec_ctl |= TX_CMD_SEC_WEP | + ((keyconf->keyidx << TX_CMD_SEC_WEP_KEY_IDX_POS) & + TX_CMD_SEC_WEP_KEY_IDX_MSK); + + memcpy(&tx_cmd->key[3], keyconf->key, keyconf->keylen); + break; + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_GCMP_256: + type = TX_CMD_SEC_GCMP; + fallthrough; + case WLAN_CIPHER_SUITE_CCMP_256: + /* TODO: Taking the key from the table might introduce a race + * when PTK rekeying is done, having an old packets with a PN + * based on the old key but the message encrypted with a new + * one. + * Need to handle this. + */ + tx_cmd->sec_ctl |= type | TX_CMD_SEC_KEY_FROM_TABLE; + tx_cmd->key[0] = keyconf->hw_key_idx; + iwl_mvm_set_tx_cmd_pn(info, crypto_hdr); + break; + default: + tx_cmd->sec_ctl |= TX_CMD_SEC_EXT; + } +} + +/* + * Allocates and sets the Tx cmd the driver data pointers in the skb + */ +static struct iwl_device_tx_cmd * +iwl_mvm_set_tx_params(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_tx_info *info, int hdrlen, + struct ieee80211_sta *sta, u8 sta_id) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct iwl_device_tx_cmd *dev_cmd; + struct iwl_tx_cmd *tx_cmd; + + dev_cmd = iwl_trans_alloc_tx_cmd(mvm->trans); + + if (unlikely(!dev_cmd)) + return NULL; + + dev_cmd->hdr.cmd = TX_CMD; + + if (iwl_mvm_has_new_tx_api(mvm)) { + u32 rate_n_flags = 0; + u16 flags = 0; + struct iwl_mvm_sta *mvmsta = sta ? + iwl_mvm_sta_from_mac80211(sta) : NULL; + bool amsdu = false; + + if (ieee80211_is_data_qos(hdr->frame_control)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + + amsdu = *qc & IEEE80211_QOS_CTL_A_MSDU_PRESENT; + } + + if (!info->control.hw_key) + flags |= IWL_TX_FLAGS_ENCRYPT_DIS; + + /* + * For data and mgmt packets rate info comes from the fw. Only + * set rate/antenna for injected frames with fixed rate, or + * when no sta is given. + */ + if (unlikely(!sta || + info->control.flags & IEEE80211_TX_CTRL_RATE_INJECT)) { + flags |= IWL_TX_FLAGS_CMD_RATE; + rate_n_flags = + iwl_mvm_get_tx_rate_n_flags(mvm, info, sta, + hdr->frame_control); + } else if (!ieee80211_is_data(hdr->frame_control) || + mvmsta->sta_state < IEEE80211_STA_AUTHORIZED) { + /* These are important frames */ + flags |= IWL_TX_FLAGS_HIGH_PRI; + } + + if (mvm->trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_AX210) { + struct iwl_tx_cmd_gen3 *cmd = (void *)dev_cmd->payload; + u32 offload_assist = iwl_mvm_tx_csum(mvm, skb, + info, amsdu); + + cmd->offload_assist = cpu_to_le32(offload_assist); + + /* Total # bytes to be transmitted */ + cmd->len = cpu_to_le16((u16)skb->len); + + /* Copy MAC header from skb into command buffer */ + memcpy(cmd->hdr, hdr, hdrlen); + + cmd->flags = cpu_to_le16(flags); + cmd->rate_n_flags = cpu_to_le32(rate_n_flags); + } else { + struct iwl_tx_cmd_gen2 *cmd = (void *)dev_cmd->payload; + u16 offload_assist = iwl_mvm_tx_csum_pre_bz(mvm, skb, + info, + amsdu); + + cmd->offload_assist = cpu_to_le16(offload_assist); + + /* Total # bytes to be transmitted */ + cmd->len = cpu_to_le16((u16)skb->len); + + /* Copy MAC header from skb into command buffer */ + memcpy(cmd->hdr, hdr, hdrlen); + + cmd->flags = cpu_to_le32(flags); + cmd->rate_n_flags = cpu_to_le32(rate_n_flags); + } + goto out; + } + + tx_cmd = (struct iwl_tx_cmd *)dev_cmd->payload; + + if (info->control.hw_key) + iwl_mvm_set_tx_cmd_crypto(mvm, info, tx_cmd, skb, hdrlen); + + iwl_mvm_set_tx_cmd(mvm, skb, tx_cmd, info, sta_id); + + iwl_mvm_set_tx_cmd_rate(mvm, tx_cmd, info, sta, hdr->frame_control); + + /* Copy MAC header from skb into command buffer */ + memcpy(tx_cmd->hdr, hdr, hdrlen); + +out: + return dev_cmd; +} + +static void iwl_mvm_skb_prepare_status(struct sk_buff *skb, + struct iwl_device_tx_cmd *cmd) +{ + struct ieee80211_tx_info *skb_info = IEEE80211_SKB_CB(skb); + + memset(&skb_info->status, 0, sizeof(skb_info->status)); + memset(skb_info->driver_data, 0, sizeof(skb_info->driver_data)); + + skb_info->driver_data[1] = cmd; +} + +static int iwl_mvm_get_ctrl_vif_queue(struct iwl_mvm *mvm, + struct ieee80211_tx_info *info, + struct ieee80211_hdr *hdr) +{ + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(info->control.vif); + __le16 fc = hdr->frame_control; + + switch (info->control.vif->type) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_ADHOC: + /* + * Non-bufferable frames use the broadcast station, thus they + * use the probe queue. + * Also take care of the case where we send a deauth to a + * station that we don't have, or similarly an association + * response (with non-success status) for a station we can't + * accept. + * Also, disassociate frames might happen, particular with + * reason 7 ("Class 3 frame received from nonassociated STA"). + */ + if (ieee80211_is_mgmt(fc) && + (!ieee80211_is_bufferable_mmpdu(fc) || + ieee80211_is_deauth(fc) || ieee80211_is_disassoc(fc))) + return mvm->probe_queue; + + if (!ieee80211_has_order(fc) && !ieee80211_is_probe_req(fc) && + is_multicast_ether_addr(hdr->addr1)) + return mvmvif->cab_queue; + + WARN_ONCE(info->control.vif->type != NL80211_IFTYPE_ADHOC, + "fc=0x%02x", le16_to_cpu(fc)); + return mvm->probe_queue; + case NL80211_IFTYPE_P2P_DEVICE: + if (ieee80211_is_mgmt(fc)) + return mvm->p2p_dev_queue; + + WARN_ON_ONCE(1); + return mvm->p2p_dev_queue; + default: + WARN_ONCE(1, "Not a ctrl vif, no available queue\n"); + return -1; + } +} + +static void iwl_mvm_probe_resp_set_noa(struct iwl_mvm *mvm, + struct sk_buff *skb) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(info->control.vif); + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data; + int base_len = (u8 *)mgmt->u.probe_resp.variable - (u8 *)mgmt; + struct iwl_probe_resp_data *resp_data; + const u8 *ie; + u8 *pos; + u8 match[] = { + (WLAN_OUI_WFA >> 16) & 0xff, + (WLAN_OUI_WFA >> 8) & 0xff, + WLAN_OUI_WFA & 0xff, + WLAN_OUI_TYPE_WFA_P2P, + }; + + rcu_read_lock(); + + resp_data = rcu_dereference(mvmvif->probe_resp_data); + if (!resp_data) + goto out; + + if (!resp_data->notif.noa_active) + goto out; + + ie = cfg80211_find_ie_match(WLAN_EID_VENDOR_SPECIFIC, + mgmt->u.probe_resp.variable, + skb->len - base_len, + match, 4, 2); + if (!ie) { + IWL_DEBUG_TX(mvm, "probe resp doesn't have P2P IE\n"); + goto out; + } + + if (skb_tailroom(skb) < resp_data->noa_len) { + if (pskb_expand_head(skb, 0, resp_data->noa_len, GFP_ATOMIC)) { + IWL_ERR(mvm, + "Failed to reallocate probe resp\n"); + goto out; + } + } + + pos = skb_put(skb, resp_data->noa_len); + + *pos++ = WLAN_EID_VENDOR_SPECIFIC; + /* Set length of IE body (not including ID and length itself) */ + *pos++ = resp_data->noa_len - 2; + *pos++ = (WLAN_OUI_WFA >> 16) & 0xff; + *pos++ = (WLAN_OUI_WFA >> 8) & 0xff; + *pos++ = WLAN_OUI_WFA & 0xff; + *pos++ = WLAN_OUI_TYPE_WFA_P2P; + + memcpy(pos, &resp_data->notif.noa_attr, + resp_data->noa_len - sizeof(struct ieee80211_vendor_ie)); + +out: + rcu_read_unlock(); +} + +int iwl_mvm_tx_skb_non_sta(struct iwl_mvm *mvm, struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_tx_info info; + struct iwl_device_tx_cmd *dev_cmd; + u8 sta_id; + int hdrlen = ieee80211_hdrlen(hdr->frame_control); + __le16 fc = hdr->frame_control; + bool offchannel = IEEE80211_SKB_CB(skb)->flags & + IEEE80211_TX_CTL_TX_OFFCHAN; + int queue = -1; + + if (IWL_MVM_NON_TRANSMITTING_AP && ieee80211_is_probe_resp(fc)) + return -1; + + memcpy(&info, skb->cb, sizeof(info)); + + if (WARN_ON_ONCE(skb->len > IEEE80211_MAX_DATA_LEN + hdrlen)) + return -1; + + if (WARN_ON_ONCE(info.flags & IEEE80211_TX_CTL_AMPDU)) + return -1; + + if (info.control.vif) { + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(info.control.vif); + + if (info.control.vif->type == NL80211_IFTYPE_P2P_DEVICE || + info.control.vif->type == NL80211_IFTYPE_AP || + info.control.vif->type == NL80211_IFTYPE_ADHOC) { + if (!ieee80211_is_data(hdr->frame_control)) + sta_id = mvmvif->bcast_sta.sta_id; + else + sta_id = mvmvif->mcast_sta.sta_id; + + queue = iwl_mvm_get_ctrl_vif_queue(mvm, &info, hdr); + } else if (info.control.vif->type == NL80211_IFTYPE_MONITOR) { + queue = mvm->snif_queue; + sta_id = mvm->snif_sta.sta_id; + } else if (info.control.vif->type == NL80211_IFTYPE_STATION && + offchannel) { + /* + * IWL_MVM_OFFCHANNEL_QUEUE is used for ROC packets + * that can be used in 2 different types of vifs, P2P & + * STATION. + * P2P uses the offchannel queue. + * STATION (HS2.0) uses the auxiliary context of the FW, + * and hence needs to be sent on the aux queue. + */ + sta_id = mvm->aux_sta.sta_id; + queue = mvm->aux_queue; + } + } + + if (queue < 0) { + IWL_ERR(mvm, "No queue was found. Dropping TX\n"); + return -1; + } + + if (unlikely(ieee80211_is_probe_resp(fc))) + iwl_mvm_probe_resp_set_noa(mvm, skb); + + IWL_DEBUG_TX(mvm, "station Id %d, queue=%d\n", sta_id, queue); + + dev_cmd = iwl_mvm_set_tx_params(mvm, skb, &info, hdrlen, NULL, sta_id); + if (!dev_cmd) + return -1; + + /* From now on, we cannot access info->control */ + iwl_mvm_skb_prepare_status(skb, dev_cmd); + + if (iwl_trans_tx(mvm->trans, skb, dev_cmd, queue)) { + iwl_trans_free_tx_cmd(mvm->trans, dev_cmd); + return -1; + } + + return 0; +} + +unsigned int iwl_mvm_max_amsdu_size(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, unsigned int tid) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + u8 ac = tid_to_mac80211_ac[tid]; + enum nl80211_band band; + unsigned int txf; + unsigned int val; + int lmac; + + /* For HE redirect to trigger based fifos */ + if (sta->deflink.he_cap.has_he && !WARN_ON(!iwl_mvm_has_new_tx_api(mvm))) + ac += 4; + + txf = iwl_mvm_mac_ac_to_tx_fifo(mvm, ac); + + /* + * Don't send an AMSDU that will be longer than the TXF. + * Add a security margin of 256 for the TX command + headers. + * We also want to have the start of the next packet inside the + * fifo to be able to send bursts. + */ + val = mvmsta->max_amsdu_len; + + if (hweight16(sta->valid_links) <= 1) { + if (sta->valid_links) { + struct ieee80211_bss_conf *link_conf; + unsigned int link = ffs(sta->valid_links) - 1; + + rcu_read_lock(); + link_conf = rcu_dereference(mvmsta->vif->link_conf[link]); + if (WARN_ON(!link_conf)) + band = NL80211_BAND_2GHZ; + else + band = link_conf->chandef.chan->band; + rcu_read_unlock(); + } else { + band = mvmsta->vif->bss_conf.chandef.chan->band; + } + + lmac = iwl_mvm_get_lmac_id(mvm->fw, band); + } else if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_CDB_SUPPORT)) { + /* for real MLO restrict to both LMACs if they exist */ + lmac = IWL_LMAC_5G_INDEX; + val = min_t(unsigned int, val, + mvm->fwrt.smem_cfg.lmac[lmac].txfifo_size[txf] - 256); + lmac = IWL_LMAC_24G_INDEX; + } else { + lmac = IWL_LMAC_24G_INDEX; + } + + return min_t(unsigned int, val, + mvm->fwrt.smem_cfg.lmac[lmac].txfifo_size[txf] - 256); +} + +#ifdef CONFIG_INET + +static int +iwl_mvm_tx_tso_segment(struct sk_buff *skb, unsigned int num_subframes, + netdev_features_t netdev_flags, + struct sk_buff_head *mpdus_skb) +{ + struct sk_buff *tmp, *next; + struct ieee80211_hdr *hdr = (void *)skb->data; + char cb[sizeof(skb->cb)]; + u16 i = 0; + unsigned int tcp_payload_len; + unsigned int mss = skb_shinfo(skb)->gso_size; + bool ipv4 = (skb->protocol == htons(ETH_P_IP)); + bool qos = ieee80211_is_data_qos(hdr->frame_control); + u16 ip_base_id = ipv4 ? ntohs(ip_hdr(skb)->id) : 0; + + skb_shinfo(skb)->gso_size = num_subframes * mss; + memcpy(cb, skb->cb, sizeof(cb)); + + next = skb_gso_segment(skb, netdev_flags); + skb_shinfo(skb)->gso_size = mss; + skb_shinfo(skb)->gso_type = ipv4 ? SKB_GSO_TCPV4 : SKB_GSO_TCPV6; + if (WARN_ON_ONCE(IS_ERR(next))) + return -EINVAL; + else if (next) + consume_skb(skb); + + skb_list_walk_safe(next, tmp, next) { + memcpy(tmp->cb, cb, sizeof(tmp->cb)); + /* + * Compute the length of all the data added for the A-MSDU. + * This will be used to compute the length to write in the TX + * command. We have: SNAP + IP + TCP for n -1 subframes and + * ETH header for n subframes. + */ + tcp_payload_len = skb_tail_pointer(tmp) - + skb_transport_header(tmp) - + tcp_hdrlen(tmp) + tmp->data_len; + + if (ipv4) + ip_hdr(tmp)->id = htons(ip_base_id + i * num_subframes); + + if (tcp_payload_len > mss) { + skb_shinfo(tmp)->gso_size = mss; + skb_shinfo(tmp)->gso_type = ipv4 ? SKB_GSO_TCPV4 : + SKB_GSO_TCPV6; + } else { + if (qos) { + u8 *qc; + + if (ipv4) + ip_send_check(ip_hdr(tmp)); + + qc = ieee80211_get_qos_ctl((void *)tmp->data); + *qc &= ~IEEE80211_QOS_CTL_A_MSDU_PRESENT; + } + skb_shinfo(tmp)->gso_size = 0; + } + + skb_mark_not_on_list(tmp); + __skb_queue_tail(mpdus_skb, tmp); + i++; + } + + return 0; +} + +static int iwl_mvm_tx_tso(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff_head *mpdus_skb) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct ieee80211_hdr *hdr = (void *)skb->data; + unsigned int mss = skb_shinfo(skb)->gso_size; + unsigned int num_subframes, tcp_payload_len, subf_len, max_amsdu_len; + u16 snap_ip_tcp, pad; + netdev_features_t netdev_flags = NETIF_F_CSUM_MASK | NETIF_F_SG; + u8 tid; + + snap_ip_tcp = 8 + skb_transport_header(skb) - skb_network_header(skb) + + tcp_hdrlen(skb); + + if (!mvmsta->max_amsdu_len || + !ieee80211_is_data_qos(hdr->frame_control) || + !mvmsta->amsdu_enabled) + return iwl_mvm_tx_tso_segment(skb, 1, netdev_flags, mpdus_skb); + + /* + * Do not build AMSDU for IPv6 with extension headers. + * ask stack to segment and checkum the generated MPDUs for us. + */ + if (skb->protocol == htons(ETH_P_IPV6) && + ((struct ipv6hdr *)skb_network_header(skb))->nexthdr != + IPPROTO_TCP) { + netdev_flags &= ~NETIF_F_CSUM_MASK; + return iwl_mvm_tx_tso_segment(skb, 1, netdev_flags, mpdus_skb); + } + + tid = ieee80211_get_tid(hdr); + if (WARN_ON_ONCE(tid >= IWL_MAX_TID_COUNT)) + return -EINVAL; + + /* + * No need to lock amsdu_in_ampdu_allowed since it can't be modified + * during an BA session. + */ + if ((info->flags & IEEE80211_TX_CTL_AMPDU && + !mvmsta->tid_data[tid].amsdu_in_ampdu_allowed) || + !(mvmsta->amsdu_enabled & BIT(tid))) + return iwl_mvm_tx_tso_segment(skb, 1, netdev_flags, mpdus_skb); + + /* + * Take the min of ieee80211 station and mvm station + */ + max_amsdu_len = + min_t(unsigned int, sta->cur->max_amsdu_len, + iwl_mvm_max_amsdu_size(mvm, sta, tid)); + + /* + * Limit A-MSDU in A-MPDU to 4095 bytes when VHT is not + * supported. This is a spec requirement (IEEE 802.11-2015 + * section 8.7.3 NOTE 3). + */ + if (info->flags & IEEE80211_TX_CTL_AMPDU && + !sta->deflink.vht_cap.vht_supported) + max_amsdu_len = min_t(unsigned int, max_amsdu_len, 4095); + + /* Sub frame header + SNAP + IP header + TCP header + MSS */ + subf_len = sizeof(struct ethhdr) + snap_ip_tcp + mss; + pad = (4 - subf_len) & 0x3; + + /* + * If we have N subframes in the A-MSDU, then the A-MSDU's size is + * N * subf_len + (N - 1) * pad. + */ + num_subframes = (max_amsdu_len + pad) / (subf_len + pad); + + if (sta->max_amsdu_subframes && + num_subframes > sta->max_amsdu_subframes) + num_subframes = sta->max_amsdu_subframes; + + tcp_payload_len = skb_tail_pointer(skb) - skb_transport_header(skb) - + tcp_hdrlen(skb) + skb->data_len; + + /* + * Make sure we have enough TBs for the A-MSDU: + * 2 for each subframe + * 1 more for each fragment + * 1 more for the potential data in the header + */ + if ((num_subframes * 2 + skb_shinfo(skb)->nr_frags + 1) > + mvm->trans->max_skb_frags) + num_subframes = 1; + + if (num_subframes > 1) + *ieee80211_get_qos_ctl(hdr) |= IEEE80211_QOS_CTL_A_MSDU_PRESENT; + + /* This skb fits in one single A-MSDU */ + if (num_subframes * mss >= tcp_payload_len) { + __skb_queue_tail(mpdus_skb, skb); + return 0; + } + + /* + * Trick the segmentation function to make it + * create SKBs that can fit into one A-MSDU. + */ + return iwl_mvm_tx_tso_segment(skb, num_subframes, netdev_flags, + mpdus_skb); +} +#else /* CONFIG_INET */ +static int iwl_mvm_tx_tso(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff_head *mpdus_skb) +{ + /* Impossible to get TSO with CONFIG_INET */ + WARN_ON(1); + + return -1; +} +#endif + +/* Check if there are any timed-out TIDs on a given shared TXQ */ +static bool iwl_mvm_txq_should_update(struct iwl_mvm *mvm, int txq_id) +{ + unsigned long queue_tid_bitmap = mvm->queue_info[txq_id].tid_bitmap; + unsigned long now = jiffies; + int tid; + + if (WARN_ON(iwl_mvm_has_new_tx_api(mvm))) + return false; + + for_each_set_bit(tid, &queue_tid_bitmap, IWL_MAX_TID_COUNT + 1) { + if (time_before(mvm->queue_info[txq_id].last_frame_time[tid] + + IWL_MVM_DQA_QUEUE_TIMEOUT, now)) + return true; + } + + return false; +} + +static void iwl_mvm_tx_airtime(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvmsta, + int airtime) +{ + int mac = mvmsta->mac_id_n_color & FW_CTXT_ID_MSK; + struct iwl_mvm_tcm_mac *mdata; + + if (mac >= NUM_MAC_INDEX_DRIVER) + return; + + mdata = &mvm->tcm.data[mac]; + + if (mvm->tcm.paused) + return; + + if (time_after(jiffies, mvm->tcm.ts + MVM_TCM_PERIOD)) + schedule_delayed_work(&mvm->tcm.work, 0); + + mdata->tx.airtime += airtime; +} + +static int iwl_mvm_tx_pkt_queued(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvmsta, int tid) +{ + u32 ac = tid_to_mac80211_ac[tid]; + int mac = mvmsta->mac_id_n_color & FW_CTXT_ID_MSK; + struct iwl_mvm_tcm_mac *mdata; + + if (mac >= NUM_MAC_INDEX_DRIVER) + return -EINVAL; + + mdata = &mvm->tcm.data[mac]; + + mdata->tx.pkts[ac]++; + + return 0; +} + +/* + * Sets the fields in the Tx cmd that are crypto related. + * + * This function must be called with BHs disabled. + */ +static int iwl_mvm_tx_mpdu(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct iwl_mvm_sta *mvmsta; + struct iwl_device_tx_cmd *dev_cmd; + __le16 fc; + u16 seq_number = 0; + u8 tid = IWL_MAX_TID_COUNT; + u16 txq_id; + bool is_ampdu = false; + int hdrlen; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + fc = hdr->frame_control; + hdrlen = ieee80211_hdrlen(fc); + + if (IWL_MVM_NON_TRANSMITTING_AP && ieee80211_is_probe_resp(fc)) + return -1; + + if (WARN_ON_ONCE(!mvmsta)) + return -1; + + if (WARN_ON_ONCE(mvmsta->sta_id == IWL_MVM_INVALID_STA)) + return -1; + + if (unlikely(ieee80211_is_any_nullfunc(fc)) && sta->deflink.he_cap.has_he) + return -1; + + if (unlikely(ieee80211_is_probe_resp(fc))) + iwl_mvm_probe_resp_set_noa(mvm, skb); + + dev_cmd = iwl_mvm_set_tx_params(mvm, skb, info, hdrlen, + sta, mvmsta->sta_id); + if (!dev_cmd) + goto drop; + + /* + * we handle that entirely ourselves -- for uAPSD the firmware + * will always send a notification, and for PS-Poll responses + * we'll notify mac80211 when getting frame status + */ + info->flags &= ~IEEE80211_TX_STATUS_EOSP; + + spin_lock(&mvmsta->lock); + + /* nullfunc frames should go to the MGMT queue regardless of QOS, + * the condition of !ieee80211_is_qos_nullfunc(fc) keeps the default + * assignment of MGMT TID + */ + if (ieee80211_is_data_qos(fc) && !ieee80211_is_qos_nullfunc(fc)) { + tid = ieee80211_get_tid(hdr); + if (WARN_ONCE(tid >= IWL_MAX_TID_COUNT, "Invalid TID %d", tid)) + goto drop_unlock_sta; + + is_ampdu = info->flags & IEEE80211_TX_CTL_AMPDU; + if (WARN_ONCE(is_ampdu && + mvmsta->tid_data[tid].state != IWL_AGG_ON, + "Invalid internal agg state %d for TID %d", + mvmsta->tid_data[tid].state, tid)) + goto drop_unlock_sta; + + seq_number = mvmsta->tid_data[tid].seq_number; + seq_number &= IEEE80211_SCTL_SEQ; + + if (!iwl_mvm_has_new_tx_api(mvm)) { + struct iwl_tx_cmd *tx_cmd = (void *)dev_cmd->payload; + + hdr->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG); + hdr->seq_ctrl |= cpu_to_le16(seq_number); + /* update the tx_cmd hdr as it was already copied */ + tx_cmd->hdr->seq_ctrl = hdr->seq_ctrl; + } + } else if (ieee80211_is_data(fc) && !ieee80211_is_data_qos(fc)) { + tid = IWL_TID_NON_QOS; + } + + txq_id = mvmsta->tid_data[tid].txq_id; + + WARN_ON_ONCE(info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM); + + if (WARN_ONCE(txq_id == IWL_MVM_INVALID_QUEUE, "Invalid TXQ id")) { + iwl_trans_free_tx_cmd(mvm->trans, dev_cmd); + spin_unlock(&mvmsta->lock); + return -1; + } + + if (!iwl_mvm_has_new_tx_api(mvm)) { + /* Keep track of the time of the last frame for this RA/TID */ + mvm->queue_info[txq_id].last_frame_time[tid] = jiffies; + + /* + * If we have timed-out TIDs - schedule the worker that will + * reconfig the queues and update them + * + * Note that the no lock is taken here in order to not serialize + * the TX flow. This isn't dangerous because scheduling + * mvm->add_stream_wk can't ruin the state, and if we DON'T + * schedule it due to some race condition then next TX we get + * here we will. + */ + if (unlikely(mvm->queue_info[txq_id].status == + IWL_MVM_QUEUE_SHARED && + iwl_mvm_txq_should_update(mvm, txq_id))) + schedule_work(&mvm->add_stream_wk); + } + + IWL_DEBUG_TX(mvm, "TX to [%d|%d] Q:%d - seq: 0x%x len %d\n", + mvmsta->sta_id, tid, txq_id, + IEEE80211_SEQ_TO_SN(seq_number), skb->len); + + /* From now on, we cannot access info->control */ + iwl_mvm_skb_prepare_status(skb, dev_cmd); + + /* + * The IV is introduced by the HW for new tx api, and it is not present + * in the skb, hence, don't tell iwl_mvm_mei_tx_copy_to_csme about the + * IV for those devices. + */ + if (ieee80211_is_data(fc)) + iwl_mvm_mei_tx_copy_to_csme(mvm, skb, + info->control.hw_key && + !iwl_mvm_has_new_tx_api(mvm) ? + info->control.hw_key->iv_len : 0); + + if (iwl_trans_tx(mvm->trans, skb, dev_cmd, txq_id)) + goto drop_unlock_sta; + + if (tid < IWL_MAX_TID_COUNT && !ieee80211_has_morefrags(fc)) + mvmsta->tid_data[tid].seq_number = seq_number + 0x10; + + spin_unlock(&mvmsta->lock); + + if (iwl_mvm_tx_pkt_queued(mvm, mvmsta, + tid == IWL_MAX_TID_COUNT ? 0 : tid)) + goto drop; + + return 0; + +drop_unlock_sta: + iwl_trans_free_tx_cmd(mvm->trans, dev_cmd); + spin_unlock(&mvmsta->lock); +drop: + IWL_DEBUG_TX(mvm, "TX to [%d|%d] dropped\n", mvmsta->sta_id, tid); + return -1; +} + +int iwl_mvm_tx_skb_sta(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct ieee80211_tx_info info; + struct sk_buff_head mpdus_skbs; + unsigned int payload_len; + int ret; + struct sk_buff *orig_skb = skb; + + if (WARN_ON_ONCE(!mvmsta)) + return -1; + + if (WARN_ON_ONCE(mvmsta->sta_id == IWL_MVM_INVALID_STA)) + return -1; + + memcpy(&info, skb->cb, sizeof(info)); + + if (!skb_is_gso(skb)) + return iwl_mvm_tx_mpdu(mvm, skb, &info, sta); + + payload_len = skb_tail_pointer(skb) - skb_transport_header(skb) - + tcp_hdrlen(skb) + skb->data_len; + + if (payload_len <= skb_shinfo(skb)->gso_size) + return iwl_mvm_tx_mpdu(mvm, skb, &info, sta); + + __skb_queue_head_init(&mpdus_skbs); + + ret = iwl_mvm_tx_tso(mvm, skb, &info, sta, &mpdus_skbs); + if (ret) + return ret; + + if (WARN_ON(skb_queue_empty(&mpdus_skbs))) + return ret; + + while (!skb_queue_empty(&mpdus_skbs)) { + skb = __skb_dequeue(&mpdus_skbs); + + ret = iwl_mvm_tx_mpdu(mvm, skb, &info, sta); + if (ret) { + /* Free skbs created as part of TSO logic that have not yet been dequeued */ + __skb_queue_purge(&mpdus_skbs); + /* skb here is not necessarily same as skb that entered this method, + * so free it explicitly. + */ + if (skb == orig_skb) + ieee80211_free_txskb(mvm->hw, skb); + else + kfree_skb(skb); + /* there was error, but we consumed skb one way or another, so return 0 */ + return 0; + } + } + + return 0; +} + +static void iwl_mvm_check_ratid_empty(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, u8 tid) +{ + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; + struct ieee80211_vif *vif = mvmsta->vif; + u16 normalized_ssn; + + lockdep_assert_held(&mvmsta->lock); + + if ((tid_data->state == IWL_AGG_ON || + tid_data->state == IWL_EMPTYING_HW_QUEUE_DELBA) && + iwl_mvm_tid_queued(mvm, tid_data) == 0) { + /* + * Now that this aggregation or DQA queue is empty tell + * mac80211 so it knows we no longer have frames buffered for + * the station on this TID (for the TIM bitmap calculation.) + */ + ieee80211_sta_set_buffered(sta, tid, false); + } + + /* + * In 22000 HW, the next_reclaimed index is only 8 bit, so we'll need + * to align the wrap around of ssn so we compare relevant values. + */ + normalized_ssn = tid_data->ssn; + if (mvm->trans->trans_cfg->gen2) + normalized_ssn &= 0xff; + + if (normalized_ssn != tid_data->next_reclaimed) + return; + + switch (tid_data->state) { + case IWL_EMPTYING_HW_QUEUE_ADDBA: + IWL_DEBUG_TX_QUEUES(mvm, + "Can continue addBA flow ssn = next_recl = %d\n", + tid_data->next_reclaimed); + tid_data->state = IWL_AGG_STARTING; + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + + case IWL_EMPTYING_HW_QUEUE_DELBA: + IWL_DEBUG_TX_QUEUES(mvm, + "Can continue DELBA flow ssn = next_recl = %d\n", + tid_data->next_reclaimed); + tid_data->state = IWL_AGG_OFF; + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + + default: + break; + } +} + +#ifdef CONFIG_IWLWIFI_DEBUG +const char *iwl_mvm_get_tx_fail_reason(u32 status) +{ +#define TX_STATUS_FAIL(x) case TX_STATUS_FAIL_ ## x: return #x +#define TX_STATUS_POSTPONE(x) case TX_STATUS_POSTPONE_ ## x: return #x + + switch (status & TX_STATUS_MSK) { + case TX_STATUS_SUCCESS: + return "SUCCESS"; + TX_STATUS_POSTPONE(DELAY); + TX_STATUS_POSTPONE(FEW_BYTES); + TX_STATUS_POSTPONE(BT_PRIO); + TX_STATUS_POSTPONE(QUIET_PERIOD); + TX_STATUS_POSTPONE(CALC_TTAK); + TX_STATUS_FAIL(INTERNAL_CROSSED_RETRY); + TX_STATUS_FAIL(SHORT_LIMIT); + TX_STATUS_FAIL(LONG_LIMIT); + TX_STATUS_FAIL(UNDERRUN); + TX_STATUS_FAIL(DRAIN_FLOW); + TX_STATUS_FAIL(RFKILL_FLUSH); + TX_STATUS_FAIL(LIFE_EXPIRE); + TX_STATUS_FAIL(DEST_PS); + TX_STATUS_FAIL(HOST_ABORTED); + TX_STATUS_FAIL(BT_RETRY); + TX_STATUS_FAIL(STA_INVALID); + TX_STATUS_FAIL(FRAG_DROPPED); + TX_STATUS_FAIL(TID_DISABLE); + TX_STATUS_FAIL(FIFO_FLUSHED); + TX_STATUS_FAIL(SMALL_CF_POLL); + TX_STATUS_FAIL(FW_DROP); + TX_STATUS_FAIL(STA_COLOR_MISMATCH); + } + + return "UNKNOWN"; + +#undef TX_STATUS_FAIL +#undef TX_STATUS_POSTPONE +} +#endif /* CONFIG_IWLWIFI_DEBUG */ + +static int iwl_mvm_get_hwrate_chan_width(u32 chan_width) +{ + switch (chan_width) { + case RATE_MCS_CHAN_WIDTH_20: + return 0; + case RATE_MCS_CHAN_WIDTH_40: + return IEEE80211_TX_RC_40_MHZ_WIDTH; + case RATE_MCS_CHAN_WIDTH_80: + return IEEE80211_TX_RC_80_MHZ_WIDTH; + case RATE_MCS_CHAN_WIDTH_160: + return IEEE80211_TX_RC_160_MHZ_WIDTH; + default: + return 0; + } +} + +void iwl_mvm_hwrate_to_tx_rate(u32 rate_n_flags, + enum nl80211_band band, + struct ieee80211_tx_rate *r) +{ + u32 format = rate_n_flags & RATE_MCS_MOD_TYPE_MSK; + u32 rate = format == RATE_MCS_HT_MSK ? + RATE_HT_MCS_INDEX(rate_n_flags) : + rate_n_flags & RATE_MCS_CODE_MSK; + + r->flags |= + iwl_mvm_get_hwrate_chan_width(rate_n_flags & + RATE_MCS_CHAN_WIDTH_MSK); + + if (rate_n_flags & RATE_MCS_SGI_MSK) + r->flags |= IEEE80211_TX_RC_SHORT_GI; + if (format == RATE_MCS_HT_MSK) { + r->flags |= IEEE80211_TX_RC_MCS; + r->idx = rate; + } else if (format == RATE_MCS_VHT_MSK) { + ieee80211_rate_set_vht(r, rate, + ((rate_n_flags & RATE_MCS_NSS_MSK) >> + RATE_MCS_NSS_POS) + 1); + r->flags |= IEEE80211_TX_RC_VHT_MCS; + } else if (format == RATE_MCS_HE_MSK) { + /* mac80211 cannot do this without ieee80211_tx_status_ext() + * but it only matters for radiotap */ + r->idx = 0; + } else { + r->idx = iwl_mvm_legacy_hw_idx_to_mac80211_idx(rate_n_flags, + band); + } +} + +void iwl_mvm_hwrate_to_tx_rate_v1(u32 rate_n_flags, + enum nl80211_band band, + struct ieee80211_tx_rate *r) +{ + if (rate_n_flags & RATE_HT_MCS_GF_MSK) + r->flags |= IEEE80211_TX_RC_GREEN_FIELD; + + r->flags |= + iwl_mvm_get_hwrate_chan_width(rate_n_flags & + RATE_MCS_CHAN_WIDTH_MSK_V1); + + if (rate_n_flags & RATE_MCS_SGI_MSK_V1) + r->flags |= IEEE80211_TX_RC_SHORT_GI; + if (rate_n_flags & RATE_MCS_HT_MSK_V1) { + r->flags |= IEEE80211_TX_RC_MCS; + r->idx = rate_n_flags & RATE_HT_MCS_INDEX_MSK_V1; + } else if (rate_n_flags & RATE_MCS_VHT_MSK_V1) { + ieee80211_rate_set_vht( + r, rate_n_flags & RATE_VHT_MCS_RATE_CODE_MSK, + ((rate_n_flags & RATE_VHT_MCS_NSS_MSK) >> + RATE_VHT_MCS_NSS_POS) + 1); + r->flags |= IEEE80211_TX_RC_VHT_MCS; + } else { + r->idx = iwl_mvm_legacy_rate_to_mac80211_idx(rate_n_flags, + band); + } +} + +/* + * translate ucode response to mac80211 tx status control values + */ +static void iwl_mvm_hwrate_to_tx_status(const struct iwl_fw *fw, + u32 rate_n_flags, + struct ieee80211_tx_info *info) +{ + struct ieee80211_tx_rate *r = &info->status.rates[0]; + + if (iwl_fw_lookup_notif_ver(fw, LONG_GROUP, + TX_CMD, 0) <= 6) + rate_n_flags = iwl_new_rate_from_v1(rate_n_flags); + + info->status.antenna = + ((rate_n_flags & RATE_MCS_ANT_AB_MSK) >> RATE_MCS_ANT_POS); + iwl_mvm_hwrate_to_tx_rate(rate_n_flags, + info->band, r); +} + +static void iwl_mvm_tx_status_check_trigger(struct iwl_mvm *mvm, + u32 status, __le16 frame_control) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_tx_status *status_trig; + int i; + + if ((status & TX_STATUS_MSK) != TX_STATUS_SUCCESS) { + enum iwl_fw_ini_time_point tp = + IWL_FW_INI_TIME_POINT_TX_FAILED; + + if (ieee80211_is_action(frame_control)) + tp = IWL_FW_INI_TIME_POINT_TX_WFD_ACTION_FRAME_FAILED; + + iwl_dbg_tlv_time_point(&mvm->fwrt, + tp, NULL); + return; + } + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, NULL, + FW_DBG_TRIGGER_TX_STATUS); + if (!trig) + return; + + status_trig = (void *)trig->data; + + for (i = 0; i < ARRAY_SIZE(status_trig->statuses); i++) { + /* don't collect on status 0 */ + if (!status_trig->statuses[i].status) + break; + + if (status_trig->statuses[i].status != (status & TX_STATUS_MSK)) + continue; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + "Tx status %d was received", + status & TX_STATUS_MSK); + break; + } +} + +/* + * iwl_mvm_get_scd_ssn - returns the SSN of the SCD + * @tx_resp: the Tx response from the fw (agg or non-agg) + * + * When the fw sends an AMPDU, it fetches the MPDUs one after the other. Since + * it can't know that everything will go well until the end of the AMPDU, it + * can't know in advance the number of MPDUs that will be sent in the current + * batch. This is why it writes the agg Tx response while it fetches the MPDUs. + * Hence, it can't know in advance what the SSN of the SCD will be at the end + * of the batch. This is why the SSN of the SCD is written at the end of the + * whole struct at a variable offset. This function knows how to cope with the + * variable offset and returns the SSN of the SCD. + */ +static inline u32 iwl_mvm_get_scd_ssn(struct iwl_mvm *mvm, + struct iwl_mvm_tx_resp *tx_resp) +{ + return le32_to_cpup((__le32 *)iwl_mvm_get_agg_status(mvm, tx_resp) + + tx_resp->frame_count) & 0xfff; +} + +static void iwl_mvm_rx_tx_cmd_single(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{ + struct ieee80211_sta *sta; + u16 sequence = le16_to_cpu(pkt->hdr.sequence); + int txq_id = SEQ_TO_QUEUE(sequence); + /* struct iwl_mvm_tx_resp_v3 is almost the same */ + struct iwl_mvm_tx_resp *tx_resp = (void *)pkt->data; + int sta_id = IWL_MVM_TX_RES_GET_RA(tx_resp->ra_tid); + int tid = IWL_MVM_TX_RES_GET_TID(tx_resp->ra_tid); + struct agg_tx_status *agg_status = + iwl_mvm_get_agg_status(mvm, tx_resp); + u32 status = le16_to_cpu(agg_status->status); + u16 ssn = iwl_mvm_get_scd_ssn(mvm, tx_resp); + struct sk_buff_head skbs; + u8 skb_freed = 0; + u8 lq_color; + u16 next_reclaimed, seq_ctl; + bool is_ndp = false; + + __skb_queue_head_init(&skbs); + + if (iwl_mvm_has_new_tx_api(mvm)) + txq_id = le16_to_cpu(tx_resp->tx_queue); + + seq_ctl = le16_to_cpu(tx_resp->seq_ctl); + + /* we can free until ssn % q.n_bd not inclusive */ + iwl_trans_reclaim(mvm->trans, txq_id, ssn, &skbs, false); + + while (!skb_queue_empty(&skbs)) { + struct sk_buff *skb = __skb_dequeue(&skbs); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *hdr = (void *)skb->data; + bool flushed = false; + + skb_freed++; + + iwl_trans_free_tx_cmd(mvm->trans, info->driver_data[1]); + + memset(&info->status, 0, sizeof(info->status)); + info->flags &= ~(IEEE80211_TX_STAT_ACK | IEEE80211_TX_STAT_TX_FILTERED); + + /* inform mac80211 about what happened with the frame */ + switch (status & TX_STATUS_MSK) { + case TX_STATUS_SUCCESS: + case TX_STATUS_DIRECT_DONE: + info->flags |= IEEE80211_TX_STAT_ACK; + break; + case TX_STATUS_FAIL_FIFO_FLUSHED: + case TX_STATUS_FAIL_DRAIN_FLOW: + flushed = true; + break; + case TX_STATUS_FAIL_DEST_PS: + /* the FW should have stopped the queue and not + * return this status + */ + IWL_ERR_LIMIT(mvm, + "FW reported TX filtered, status=0x%x, FC=0x%x\n", + status, le16_to_cpu(hdr->frame_control)); + info->flags |= IEEE80211_TX_STAT_TX_FILTERED; + break; + default: + break; + } + + if ((status & TX_STATUS_MSK) != TX_STATUS_SUCCESS && + ieee80211_is_mgmt(hdr->frame_control)) + iwl_mvm_toggle_tx_ant(mvm, &mvm->mgmt_last_antenna_idx); + + /* + * If we are freeing multiple frames, mark all the frames + * but the first one as acked, since they were acknowledged + * before + * */ + if (skb_freed > 1) + info->flags |= IEEE80211_TX_STAT_ACK; + + iwl_mvm_tx_status_check_trigger(mvm, status, hdr->frame_control); + + info->status.rates[0].count = tx_resp->failure_frame + 1; + + iwl_mvm_hwrate_to_tx_status(mvm->fw, + le32_to_cpu(tx_resp->initial_rate), + info); + + /* Don't assign the converted initial_rate, because driver + * TLC uses this and doesn't support the new FW rate + */ + info->status.status_driver_data[1] = + (void *)(uintptr_t)le32_to_cpu(tx_resp->initial_rate); + + /* Single frame failure in an AMPDU queue => send BAR */ + if (info->flags & IEEE80211_TX_CTL_AMPDU && + !(info->flags & IEEE80211_TX_STAT_ACK) && + !(info->flags & IEEE80211_TX_STAT_TX_FILTERED) && !flushed) + info->flags |= IEEE80211_TX_STAT_AMPDU_NO_BACK; + info->flags &= ~IEEE80211_TX_CTL_AMPDU; + + /* W/A FW bug: seq_ctl is wrong upon failure / BAR frame */ + if (ieee80211_is_back_req(hdr->frame_control)) + seq_ctl = 0; + else if (status != TX_STATUS_SUCCESS) + seq_ctl = le16_to_cpu(hdr->seq_ctrl); + + if (unlikely(!seq_ctl)) { + /* + * If it is an NDP, we can't update next_reclaim since + * its sequence control is 0. Note that for that same + * reason, NDPs are never sent to A-MPDU'able queues + * so that we can never have more than one freed frame + * for a single Tx resonse (see WARN_ON below). + */ + if (ieee80211_is_qos_nullfunc(hdr->frame_control)) + is_ndp = true; + } + + /* + * TODO: this is not accurate if we are freeing more than one + * packet. + */ + info->status.tx_time = + le16_to_cpu(tx_resp->wireless_media_time); + BUILD_BUG_ON(ARRAY_SIZE(info->status.status_driver_data) < 1); + lq_color = TX_RES_RATE_TABLE_COL_GET(tx_resp->tlc_info); + info->status.status_driver_data[0] = + RS_DRV_DATA_PACK(lq_color, tx_resp->reduced_tpc); + + ieee80211_tx_status(mvm->hw, skb); + } + + /* This is an aggregation queue or might become one, so we use + * the ssn since: ssn = wifi seq_num % 256. + * The seq_ctl is the sequence control of the packet to which + * this Tx response relates. But if there is a hole in the + * bitmap of the BA we received, this Tx response may allow to + * reclaim the hole and all the subsequent packets that were + * already acked. In that case, seq_ctl != ssn, and the next + * packet to be reclaimed will be ssn and not seq_ctl. In that + * case, several packets will be reclaimed even if + * frame_count = 1. + * + * The ssn is the index (% 256) of the latest packet that has + * treated (acked / dropped) + 1. + */ + next_reclaimed = ssn; + + IWL_DEBUG_TX_REPLY(mvm, + "TXQ %d status %s (0x%08x)\n", + txq_id, iwl_mvm_get_tx_fail_reason(status), status); + + IWL_DEBUG_TX_REPLY(mvm, + "\t\t\t\tinitial_rate 0x%x retries %d, idx=%d ssn=%d next_reclaimed=0x%x seq_ctl=0x%x\n", + le32_to_cpu(tx_resp->initial_rate), + tx_resp->failure_frame, SEQ_TO_INDEX(sequence), + ssn, next_reclaimed, seq_ctl); + + rcu_read_lock(); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + /* + * sta can't be NULL otherwise it'd mean that the sta has been freed in + * the firmware while we still have packets for it in the Tx queues. + */ + if (WARN_ON_ONCE(!sta)) + goto out; + + if (!IS_ERR(sta)) { + struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta); + + iwl_mvm_tx_airtime(mvm, mvmsta, + le16_to_cpu(tx_resp->wireless_media_time)); + + if ((status & TX_STATUS_MSK) != TX_STATUS_SUCCESS && + mvmsta->sta_state < IEEE80211_STA_AUTHORIZED) + iwl_mvm_toggle_tx_ant(mvm, &mvmsta->tx_ant); + + if (sta->wme && tid != IWL_MGMT_TID) { + struct iwl_mvm_tid_data *tid_data = + &mvmsta->tid_data[tid]; + bool send_eosp_ndp = false; + + spin_lock_bh(&mvmsta->lock); + + if (!is_ndp) { + tid_data->next_reclaimed = next_reclaimed; + IWL_DEBUG_TX_REPLY(mvm, + "Next reclaimed packet:%d\n", + next_reclaimed); + } else { + IWL_DEBUG_TX_REPLY(mvm, + "NDP - don't update next_reclaimed\n"); + } + + iwl_mvm_check_ratid_empty(mvm, sta, tid); + + if (mvmsta->sleep_tx_count) { + mvmsta->sleep_tx_count--; + if (mvmsta->sleep_tx_count && + !iwl_mvm_tid_queued(mvm, tid_data)) { + /* + * The number of frames in the queue + * dropped to 0 even if we sent less + * frames than we thought we had on the + * Tx queue. + * This means we had holes in the BA + * window that we just filled, ask + * mac80211 to send EOSP since the + * firmware won't know how to do that. + * Send NDP and the firmware will send + * EOSP notification that will trigger + * a call to ieee80211_sta_eosp(). + */ + send_eosp_ndp = true; + } + } + + spin_unlock_bh(&mvmsta->lock); + if (send_eosp_ndp) { + iwl_mvm_sta_modify_sleep_tx_count(mvm, sta, + IEEE80211_FRAME_RELEASE_UAPSD, + 1, tid, false, false); + mvmsta->sleep_tx_count = 0; + ieee80211_send_eosp_nullfunc(sta, tid); + } + } + + if (mvmsta->next_status_eosp) { + mvmsta->next_status_eosp = false; + ieee80211_sta_eosp(sta); + } + } +out: + rcu_read_unlock(); +} + +#ifdef CONFIG_IWLWIFI_DEBUG +#define AGG_TX_STATE_(x) case AGG_TX_STATE_ ## x: return #x +static const char *iwl_get_agg_tx_status(u16 status) +{ + switch (status & AGG_TX_STATE_STATUS_MSK) { + AGG_TX_STATE_(TRANSMITTED); + AGG_TX_STATE_(UNDERRUN); + AGG_TX_STATE_(BT_PRIO); + AGG_TX_STATE_(FEW_BYTES); + AGG_TX_STATE_(ABORT); + AGG_TX_STATE_(TX_ON_AIR_DROP); + AGG_TX_STATE_(LAST_SENT_TRY_CNT); + AGG_TX_STATE_(LAST_SENT_BT_KILL); + AGG_TX_STATE_(SCD_QUERY); + AGG_TX_STATE_(TEST_BAD_CRC32); + AGG_TX_STATE_(RESPONSE); + AGG_TX_STATE_(DUMP_TX); + AGG_TX_STATE_(DELAY_TX); + } + + return "UNKNOWN"; +} + +static void iwl_mvm_rx_tx_cmd_agg_dbg(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{ + struct iwl_mvm_tx_resp *tx_resp = (void *)pkt->data; + struct agg_tx_status *frame_status = + iwl_mvm_get_agg_status(mvm, tx_resp); + int i; + bool tirgger_timepoint = false; + + for (i = 0; i < tx_resp->frame_count; i++) { + u16 fstatus = le16_to_cpu(frame_status[i].status); + /* In case one frame wasn't transmitted trigger time point */ + tirgger_timepoint |= ((fstatus & AGG_TX_STATE_STATUS_MSK) != + AGG_TX_STATE_TRANSMITTED); + IWL_DEBUG_TX_REPLY(mvm, + "status %s (0x%04x), try-count (%d) seq (0x%x)\n", + iwl_get_agg_tx_status(fstatus), + fstatus & AGG_TX_STATE_STATUS_MSK, + (fstatus & AGG_TX_STATE_TRY_CNT_MSK) >> + AGG_TX_STATE_TRY_CNT_POS, + le16_to_cpu(frame_status[i].sequence)); + } + + if (tirgger_timepoint) + iwl_dbg_tlv_time_point(&mvm->fwrt, + IWL_FW_INI_TIME_POINT_TX_FAILED, NULL); + +} +#else +static void iwl_mvm_rx_tx_cmd_agg_dbg(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{} +#endif /* CONFIG_IWLWIFI_DEBUG */ + +static void iwl_mvm_rx_tx_cmd_agg(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{ + struct iwl_mvm_tx_resp *tx_resp = (void *)pkt->data; + int sta_id = IWL_MVM_TX_RES_GET_RA(tx_resp->ra_tid); + int tid = IWL_MVM_TX_RES_GET_TID(tx_resp->ra_tid); + u16 sequence = le16_to_cpu(pkt->hdr.sequence); + struct iwl_mvm_sta *mvmsta; + int queue = SEQ_TO_QUEUE(sequence); + struct ieee80211_sta *sta; + + if (WARN_ON_ONCE(queue < IWL_MVM_DQA_MIN_DATA_QUEUE && + (queue != IWL_MVM_DQA_BSS_CLIENT_QUEUE))) + return; + + iwl_mvm_rx_tx_cmd_agg_dbg(mvm, pkt); + + rcu_read_lock(); + + mvmsta = iwl_mvm_sta_from_staid_rcu(mvm, sta_id); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + if (WARN_ON_ONCE(!sta || !sta->wme)) { + rcu_read_unlock(); + return; + } + + if (!WARN_ON_ONCE(!mvmsta)) { + mvmsta->tid_data[tid].rate_n_flags = + le32_to_cpu(tx_resp->initial_rate); + mvmsta->tid_data[tid].tx_time = + le16_to_cpu(tx_resp->wireless_media_time); + mvmsta->tid_data[tid].lq_color = + TX_RES_RATE_TABLE_COL_GET(tx_resp->tlc_info); + iwl_mvm_tx_airtime(mvm, mvmsta, + le16_to_cpu(tx_resp->wireless_media_time)); + } + + rcu_read_unlock(); +} + +void iwl_mvm_rx_tx_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mvm_tx_resp *tx_resp = (void *)pkt->data; + + if (tx_resp->frame_count == 1) + iwl_mvm_rx_tx_cmd_single(mvm, pkt); + else + iwl_mvm_rx_tx_cmd_agg(mvm, pkt); +} + +static void iwl_mvm_tx_reclaim(struct iwl_mvm *mvm, int sta_id, int tid, + int txq, int index, + struct ieee80211_tx_info *tx_info, u32 rate, + bool is_flush) +{ + struct sk_buff_head reclaimed_skbs; + struct iwl_mvm_tid_data *tid_data = NULL; + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta = NULL; + struct sk_buff *skb; + int freed; + + if (WARN_ONCE(sta_id >= mvm->fw->ucode_capa.num_stations || + tid > IWL_MAX_TID_COUNT, + "sta_id %d tid %d", sta_id, tid)) + return; + + rcu_read_lock(); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + + /* Reclaiming frames for a station that has been deleted ? */ + if (WARN_ON_ONCE(!sta)) { + rcu_read_unlock(); + return; + } + + __skb_queue_head_init(&reclaimed_skbs); + + /* + * Release all TFDs before the SSN, i.e. all TFDs in front of + * block-ack window (we assume that they've been successfully + * transmitted ... if not, it's too late anyway). + */ + iwl_trans_reclaim(mvm->trans, txq, index, &reclaimed_skbs, is_flush); + + skb_queue_walk(&reclaimed_skbs, skb) { + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + iwl_trans_free_tx_cmd(mvm->trans, info->driver_data[1]); + + memset(&info->status, 0, sizeof(info->status)); + /* Packet was transmitted successfully, failures come as single + * frames because before failing a frame the firmware transmits + * it without aggregation at least once. + */ + if (!is_flush) + info->flags |= IEEE80211_TX_STAT_ACK; + else + info->flags &= ~IEEE80211_TX_STAT_ACK; + } + + /* + * It's possible to get a BA response after invalidating the rcu (rcu is + * invalidated in order to prevent new Tx from being sent, but there may + * be some frames already in-flight). + * In this case we just want to reclaim, and could skip all the + * sta-dependent stuff since it's in the middle of being removed + * anyways. + */ + if (IS_ERR(sta)) + goto out; + + mvmsta = iwl_mvm_sta_from_mac80211(sta); + tid_data = &mvmsta->tid_data[tid]; + + if (tid_data->txq_id != txq) { + IWL_ERR(mvm, + "invalid reclaim request: Q %d, tid %d\n", + tid_data->txq_id, tid); + rcu_read_unlock(); + return; + } + + spin_lock_bh(&mvmsta->lock); + + tid_data->next_reclaimed = index; + + iwl_mvm_check_ratid_empty(mvm, sta, tid); + + freed = 0; + + /* pack lq color from tid_data along the reduced txp */ + tx_info->status.status_driver_data[0] = + RS_DRV_DATA_PACK(tid_data->lq_color, + tx_info->status.status_driver_data[0]); + tx_info->status.status_driver_data[1] = (void *)(uintptr_t)rate; + + skb_queue_walk(&reclaimed_skbs, skb) { + struct ieee80211_hdr *hdr = (void *)skb->data; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + if (!is_flush) { + if (ieee80211_is_data_qos(hdr->frame_control)) + freed++; + else + WARN_ON_ONCE(tid != IWL_MAX_TID_COUNT); + } + + /* this is the first skb we deliver in this batch */ + /* put the rate scaling data there */ + if (freed == 1) { + info->flags |= IEEE80211_TX_STAT_AMPDU; + memcpy(&info->status, &tx_info->status, + sizeof(tx_info->status)); + iwl_mvm_hwrate_to_tx_status(mvm->fw, rate, info); + } + } + + spin_unlock_bh(&mvmsta->lock); + + /* We got a BA notif with 0 acked or scd_ssn didn't progress which is + * possible (i.e. first MPDU in the aggregation wasn't acked) + * Still it's important to update RS about sent vs. acked. + */ + if (!is_flush && skb_queue_empty(&reclaimed_skbs)) { + struct ieee80211_chanctx_conf *chanctx_conf = NULL; + + if (mvmsta->vif) + chanctx_conf = + rcu_dereference(mvmsta->vif->bss_conf.chanctx_conf); + + if (WARN_ON_ONCE(!chanctx_conf)) + goto out; + + tx_info->band = chanctx_conf->def.chan->band; + iwl_mvm_hwrate_to_tx_status(mvm->fw, rate, tx_info); + + if (!iwl_mvm_has_tlc_offload(mvm)) { + IWL_DEBUG_TX_REPLY(mvm, + "No reclaim. Update rs directly\n"); + iwl_mvm_rs_tx_status(mvm, sta, tid, tx_info, false); + } + } + +out: + rcu_read_unlock(); + + while (!skb_queue_empty(&reclaimed_skbs)) { + skb = __skb_dequeue(&reclaimed_skbs); + ieee80211_tx_status(mvm->hw, skb); + } +} + +void iwl_mvm_rx_ba_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); + int sta_id, tid, txq, index; + struct ieee80211_tx_info ba_info = {}; + struct iwl_mvm_ba_notif *ba_notif; + struct iwl_mvm_tid_data *tid_data; + struct iwl_mvm_sta *mvmsta; + + ba_info.flags = IEEE80211_TX_STAT_AMPDU; + + if (iwl_mvm_has_new_tx_api(mvm)) { + struct iwl_mvm_compressed_ba_notif *ba_res = + (void *)pkt->data; + u8 lq_color = TX_RES_RATE_TABLE_COL_GET(ba_res->tlc_rate_info); + u16 tfd_cnt; + int i; + + if (unlikely(sizeof(*ba_res) > pkt_len)) + return; + + sta_id = ba_res->sta_id; + ba_info.status.ampdu_ack_len = (u8)le16_to_cpu(ba_res->done); + ba_info.status.ampdu_len = (u8)le16_to_cpu(ba_res->txed); + ba_info.status.tx_time = + (u16)le32_to_cpu(ba_res->wireless_time); + ba_info.status.status_driver_data[0] = + (void *)(uintptr_t)ba_res->reduced_txp; + + tfd_cnt = le16_to_cpu(ba_res->tfd_cnt); + if (!tfd_cnt || struct_size(ba_res, tfd, tfd_cnt) > pkt_len) + return; + + rcu_read_lock(); + + mvmsta = iwl_mvm_sta_from_staid_rcu(mvm, sta_id); + /* + * It's possible to get a BA response after invalidating the rcu + * (rcu is invalidated in order to prevent new Tx from being + * sent, but there may be some frames already in-flight). + * In this case we just want to reclaim, and could skip all the + * sta-dependent stuff since it's in the middle of being removed + * anyways. + */ + + /* Free per TID */ + for (i = 0; i < tfd_cnt; i++) { + struct iwl_mvm_compressed_ba_tfd *ba_tfd = + &ba_res->tfd[i]; + + tid = ba_tfd->tid; + if (tid == IWL_MGMT_TID) + tid = IWL_MAX_TID_COUNT; + + if (mvmsta) + mvmsta->tid_data[i].lq_color = lq_color; + + iwl_mvm_tx_reclaim(mvm, sta_id, tid, + (int)(le16_to_cpu(ba_tfd->q_num)), + le16_to_cpu(ba_tfd->tfd_index), + &ba_info, + le32_to_cpu(ba_res->tx_rate), false); + } + + if (mvmsta) + iwl_mvm_tx_airtime(mvm, mvmsta, + le32_to_cpu(ba_res->wireless_time)); + rcu_read_unlock(); + + IWL_DEBUG_TX_REPLY(mvm, + "BA_NOTIFICATION Received from sta_id = %d, flags %x, sent:%d, acked:%d\n", + sta_id, le32_to_cpu(ba_res->flags), + le16_to_cpu(ba_res->txed), + le16_to_cpu(ba_res->done)); + return; + } + + ba_notif = (void *)pkt->data; + sta_id = ba_notif->sta_id; + tid = ba_notif->tid; + /* "flow" corresponds to Tx queue */ + txq = le16_to_cpu(ba_notif->scd_flow); + /* "ssn" is start of block-ack Tx window, corresponds to index + * (in Tx queue's circular buffer) of first TFD/frame in window */ + index = le16_to_cpu(ba_notif->scd_ssn); + + rcu_read_lock(); + mvmsta = iwl_mvm_sta_from_staid_rcu(mvm, sta_id); + if (WARN_ON_ONCE(!mvmsta)) { + rcu_read_unlock(); + return; + } + + tid_data = &mvmsta->tid_data[tid]; + + ba_info.status.ampdu_ack_len = ba_notif->txed_2_done; + ba_info.status.ampdu_len = ba_notif->txed; + ba_info.status.tx_time = tid_data->tx_time; + ba_info.status.status_driver_data[0] = + (void *)(uintptr_t)ba_notif->reduced_txp; + + rcu_read_unlock(); + + iwl_mvm_tx_reclaim(mvm, sta_id, tid, txq, index, &ba_info, + tid_data->rate_n_flags, false); + + IWL_DEBUG_TX_REPLY(mvm, + "BA_NOTIFICATION Received from %pM, sta_id = %d\n", + ba_notif->sta_addr, ba_notif->sta_id); + + IWL_DEBUG_TX_REPLY(mvm, + "TID = %d, SeqCtl = %d, bitmap = 0x%llx, scd_flow = %d, scd_ssn = %d sent:%d, acked:%d\n", + ba_notif->tid, le16_to_cpu(ba_notif->seq_ctl), + le64_to_cpu(ba_notif->bitmap), txq, index, + ba_notif->txed, ba_notif->txed_2_done); + + IWL_DEBUG_TX_REPLY(mvm, "reduced txp from ba notif %d\n", + ba_notif->reduced_txp); +} + +/* + * Note that there are transports that buffer frames before they reach + * the firmware. This means that after flush_tx_path is called, the + * queue might not be empty. The race-free way to handle this is to: + * 1) set the station as draining + * 2) flush the Tx path + * 3) wait for the transport queues to be empty + */ +int iwl_mvm_flush_tx_path(struct iwl_mvm *mvm, u32 tfd_msk) +{ + int ret; + struct iwl_tx_path_flush_cmd_v1 flush_cmd = { + .queues_ctl = cpu_to_le32(tfd_msk), + .flush_ctl = cpu_to_le16(DUMP_TX_FIFO_FLUSH), + }; + + WARN_ON(iwl_mvm_has_new_tx_api(mvm)); + ret = iwl_mvm_send_cmd_pdu(mvm, TXPATH_FLUSH, 0, + sizeof(flush_cmd), &flush_cmd); + if (ret) + IWL_ERR(mvm, "Failed to send flush command (%d)\n", ret); + return ret; +} + +int iwl_mvm_flush_sta_tids(struct iwl_mvm *mvm, u32 sta_id, u16 tids) +{ + int ret; + struct iwl_tx_path_flush_cmd_rsp *rsp; + struct iwl_tx_path_flush_cmd flush_cmd = { + .sta_id = cpu_to_le32(sta_id), + .tid_mask = cpu_to_le16(tids), + }; + + struct iwl_host_cmd cmd = { + .id = TXPATH_FLUSH, + .len = { sizeof(flush_cmd), }, + .data = { &flush_cmd, }, + }; + + WARN_ON(!iwl_mvm_has_new_tx_api(mvm)); + + if (iwl_fw_lookup_notif_ver(mvm->fw, LONG_GROUP, TXPATH_FLUSH, 0) > 0) + cmd.flags |= CMD_WANT_SKB | CMD_SEND_IN_RFKILL; + + IWL_DEBUG_TX_QUEUES(mvm, "flush for sta id %d tid mask 0x%x\n", + sta_id, tids); + + ret = iwl_mvm_send_cmd(mvm, &cmd); + + if (ret) { + IWL_ERR(mvm, "Failed to send flush command (%d)\n", ret); + return ret; + } + + if (cmd.flags & CMD_WANT_SKB) { + int i; + int num_flushed_queues; + + if (WARN_ON_ONCE(iwl_rx_packet_payload_len(cmd.resp_pkt) != sizeof(*rsp))) { + ret = -EIO; + goto free_rsp; + } + + rsp = (void *)cmd.resp_pkt->data; + + if (WARN_ONCE(le16_to_cpu(rsp->sta_id) != sta_id, + "sta_id %d != rsp_sta_id %d", + sta_id, le16_to_cpu(rsp->sta_id))) { + ret = -EIO; + goto free_rsp; + } + + num_flushed_queues = le16_to_cpu(rsp->num_flushed_queues); + if (WARN_ONCE(num_flushed_queues > IWL_TX_FLUSH_QUEUE_RSP, + "num_flushed_queues %d", num_flushed_queues)) { + ret = -EIO; + goto free_rsp; + } + + for (i = 0; i < num_flushed_queues; i++) { + struct ieee80211_tx_info tx_info = {}; + struct iwl_flush_queue_info *queue_info = &rsp->queues[i]; + int tid = le16_to_cpu(queue_info->tid); + int read_before = le16_to_cpu(queue_info->read_before_flush); + int read_after = le16_to_cpu(queue_info->read_after_flush); + int queue_num = le16_to_cpu(queue_info->queue_num); + + if (tid == IWL_MGMT_TID) + tid = IWL_MAX_TID_COUNT; + + IWL_DEBUG_TX_QUEUES(mvm, + "tid %d queue_id %d read-before %d read-after %d\n", + tid, queue_num, read_before, read_after); + + iwl_mvm_tx_reclaim(mvm, sta_id, tid, queue_num, read_after, + &tx_info, 0, true); + } +free_rsp: + iwl_free_resp(&cmd); + } + return ret; +} + +int iwl_mvm_flush_sta(struct iwl_mvm *mvm, void *sta, bool internal) +{ + struct iwl_mvm_int_sta *int_sta = sta; + struct iwl_mvm_sta *mvm_sta = sta; + + BUILD_BUG_ON(offsetof(struct iwl_mvm_int_sta, sta_id) != + offsetof(struct iwl_mvm_sta, sta_id)); + + if (iwl_mvm_has_new_tx_api(mvm)) + return iwl_mvm_flush_sta_tids(mvm, mvm_sta->sta_id, 0xffff); + + if (internal) + return iwl_mvm_flush_tx_path(mvm, int_sta->tfd_queue_msk); + + return iwl_mvm_flush_tx_path(mvm, mvm_sta->tfd_queue_msk); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/utils.c b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c new file mode 100644 index 000000000..14b2de65b --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c @@ -0,0 +1,1139 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2012-2014, 2018-2022 Intel Corporation + * Copyright (C) 2013-2014 Intel Mobile Communications GmbH + * Copyright (C) 2015-2017 Intel Deutschland GmbH + */ +#include <net/mac80211.h> + +#include "iwl-debug.h" +#include "iwl-io.h" +#include "iwl-prph.h" +#include "iwl-csr.h" +#include "mvm.h" +#include "fw/api/rs.h" +#include "fw/img.h" + +/* + * Will return 0 even if the cmd failed when RFKILL is asserted unless + * CMD_WANT_SKB is set in cmd->flags. + */ +int iwl_mvm_send_cmd(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd) +{ + int ret; + +#if defined(CONFIG_IWLWIFI_DEBUGFS) && defined(CONFIG_PM_SLEEP) + if (WARN_ON(mvm->d3_test_active)) + return -EIO; +#endif + + /* + * Synchronous commands from this op-mode must hold + * the mutex, this ensures we don't try to send two + * (or more) synchronous commands at a time. + */ + if (!(cmd->flags & CMD_ASYNC)) + lockdep_assert_held(&mvm->mutex); + + ret = iwl_trans_send_cmd(mvm->trans, cmd); + + /* + * If the caller wants the SKB, then don't hide any problems, the + * caller might access the response buffer which will be NULL if + * the command failed. + */ + if (cmd->flags & CMD_WANT_SKB) + return ret; + + /* + * Silently ignore failures if RFKILL is asserted or + * we are in suspend\resume process + */ + if (!ret || ret == -ERFKILL || ret == -EHOSTDOWN) + return 0; + return ret; +} + +int iwl_mvm_send_cmd_pdu(struct iwl_mvm *mvm, u32 id, + u32 flags, u16 len, const void *data) +{ + struct iwl_host_cmd cmd = { + .id = id, + .len = { len, }, + .data = { data, }, + .flags = flags, + }; + + return iwl_mvm_send_cmd(mvm, &cmd); +} + +/* + * We assume that the caller set the status to the success value + */ +int iwl_mvm_send_cmd_status(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd, + u32 *status) +{ + struct iwl_rx_packet *pkt; + struct iwl_cmd_response *resp; + int ret, resp_len; + + lockdep_assert_held(&mvm->mutex); + +#if defined(CONFIG_IWLWIFI_DEBUGFS) && defined(CONFIG_PM_SLEEP) + if (WARN_ON(mvm->d3_test_active)) + return -EIO; +#endif + + /* + * Only synchronous commands can wait for status, + * we use WANT_SKB so the caller can't. + */ + if (WARN_ONCE(cmd->flags & (CMD_ASYNC | CMD_WANT_SKB), + "cmd flags %x", cmd->flags)) + return -EINVAL; + + cmd->flags |= CMD_WANT_SKB; + + ret = iwl_trans_send_cmd(mvm->trans, cmd); + if (ret == -ERFKILL) { + /* + * The command failed because of RFKILL, don't update + * the status, leave it as success and return 0. + */ + return 0; + } else if (ret) { + return ret; + } + + pkt = cmd->resp_pkt; + + resp_len = iwl_rx_packet_payload_len(pkt); + if (WARN_ON_ONCE(resp_len != sizeof(*resp))) { + ret = -EIO; + goto out_free_resp; + } + + resp = (void *)pkt->data; + *status = le32_to_cpu(resp->status); + out_free_resp: + iwl_free_resp(cmd); + return ret; +} + +/* + * We assume that the caller set the status to the sucess value + */ +int iwl_mvm_send_cmd_pdu_status(struct iwl_mvm *mvm, u32 id, u16 len, + const void *data, u32 *status) +{ + struct iwl_host_cmd cmd = { + .id = id, + .len = { len, }, + .data = { data, }, + }; + + return iwl_mvm_send_cmd_status(mvm, &cmd, status); +} + +int iwl_mvm_legacy_hw_idx_to_mac80211_idx(u32 rate_n_flags, + enum nl80211_band band) +{ + int format = rate_n_flags & RATE_MCS_MOD_TYPE_MSK; + int rate = rate_n_flags & RATE_LEGACY_RATE_MSK; + bool is_LB = band == NL80211_BAND_2GHZ; + + if (format == RATE_MCS_LEGACY_OFDM_MSK) + return is_LB ? rate + IWL_FIRST_OFDM_RATE : + rate; + + /* CCK is not allowed in HB */ + return is_LB ? rate : -1; +} + +int iwl_mvm_legacy_rate_to_mac80211_idx(u32 rate_n_flags, + enum nl80211_band band) +{ + int rate = rate_n_flags & RATE_LEGACY_RATE_MSK_V1; + int idx; + int band_offset = 0; + + /* Legacy rate format, search for match in table */ + if (band != NL80211_BAND_2GHZ) + band_offset = IWL_FIRST_OFDM_RATE; + for (idx = band_offset; idx < IWL_RATE_COUNT_LEGACY; idx++) + if (iwl_fw_rate_idx_to_plcp(idx) == rate) + return idx - band_offset; + + return -1; +} + +u8 iwl_mvm_mac80211_idx_to_hwrate(const struct iwl_fw *fw, int rate_idx) +{ + if (iwl_fw_lookup_cmd_ver(fw, TX_CMD, 0) > 8) + /* In the new rate legacy rates are indexed: + * 0 - 3 for CCK and 0 - 7 for OFDM. + */ + return (rate_idx >= IWL_FIRST_OFDM_RATE ? + rate_idx - IWL_FIRST_OFDM_RATE : + rate_idx); + + return iwl_fw_rate_idx_to_plcp(rate_idx); +} + +u8 iwl_mvm_mac80211_ac_to_ucode_ac(enum ieee80211_ac_numbers ac) +{ + static const u8 mac80211_ac_to_ucode_ac[] = { + AC_VO, + AC_VI, + AC_BE, + AC_BK + }; + + return mac80211_ac_to_ucode_ac[ac]; +} + +void iwl_mvm_rx_fw_error(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_error_resp *err_resp = (void *)pkt->data; + + IWL_ERR(mvm, "FW Error notification: type 0x%08X cmd_id 0x%02X\n", + le32_to_cpu(err_resp->error_type), err_resp->cmd_id); + IWL_ERR(mvm, "FW Error notification: seq 0x%04X service 0x%08X\n", + le16_to_cpu(err_resp->bad_cmd_seq_num), + le32_to_cpu(err_resp->error_service)); + IWL_ERR(mvm, "FW Error notification: timestamp 0x%016llX\n", + le64_to_cpu(err_resp->timestamp)); +} + +/* + * Returns the first antenna as ANT_[ABC], as defined in iwl-config.h. + * The parameter should also be a combination of ANT_[ABC]. + */ +u8 first_antenna(u8 mask) +{ + BUILD_BUG_ON(ANT_A != BIT(0)); /* using ffs is wrong if not */ + if (WARN_ON_ONCE(!mask)) /* ffs will return 0 if mask is zeroed */ + return BIT(0); + return BIT(ffs(mask) - 1); +} + +#define MAX_ANT_NUM 2 +/* + * Toggles between TX antennas to send the probe request on. + * Receives the bitmask of valid TX antennas and the *index* used + * for the last TX, and returns the next valid *index* to use. + * In order to set it in the tx_cmd, must do BIT(idx). + */ +u8 iwl_mvm_next_antenna(struct iwl_mvm *mvm, u8 valid, u8 last_idx) +{ + u8 ind = last_idx; + int i; + + for (i = 0; i < MAX_ANT_NUM; i++) { + ind = (ind + 1) % MAX_ANT_NUM; + if (valid & BIT(ind)) + return ind; + } + + WARN_ONCE(1, "Failed to toggle between antennas 0x%x", valid); + return last_idx; +} + +/** + * iwl_mvm_send_lq_cmd() - Send link quality command + * @mvm: Driver data. + * @lq: Link quality command to send. + * + * The link quality command is sent as the last step of station creation. + * This is the special case in which init is set and we call a callback in + * this case to clear the state indicating that station creation is in + * progress. + */ +int iwl_mvm_send_lq_cmd(struct iwl_mvm *mvm, struct iwl_lq_cmd *lq) +{ + struct iwl_host_cmd cmd = { + .id = LQ_CMD, + .len = { sizeof(struct iwl_lq_cmd), }, + .flags = CMD_ASYNC, + .data = { lq, }, + }; + + if (WARN_ON(lq->sta_id == IWL_MVM_INVALID_STA || + iwl_mvm_has_tlc_offload(mvm))) + return -EINVAL; + + return iwl_mvm_send_cmd(mvm, &cmd); +} + +/** + * iwl_mvm_update_smps - Get a request to change the SMPS mode + * @mvm: Driver data. + * @vif: Pointer to the ieee80211_vif structure + * @req_type: The part of the driver who call for a change. + * @smps_request: The request to change the SMPS mode. + * + * Get a requst to change the SMPS mode, + * and change it according to all other requests in the driver. + */ +void iwl_mvm_update_smps(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + enum iwl_mvm_smps_type_request req_type, + enum ieee80211_smps_mode smps_request) +{ + struct iwl_mvm_vif *mvmvif; + enum ieee80211_smps_mode smps_mode = IEEE80211_SMPS_AUTOMATIC; + int i; + + lockdep_assert_held(&mvm->mutex); + + /* SMPS is irrelevant for NICs that don't have at least 2 RX antenna */ + if (num_of_ant(iwl_mvm_get_valid_rx_ant(mvm)) == 1) + return; + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + mvmvif = iwl_mvm_vif_from_mac80211(vif); + mvmvif->smps_requests[req_type] = smps_request; + for (i = 0; i < NUM_IWL_MVM_SMPS_REQ; i++) { + if (mvmvif->smps_requests[i] == IEEE80211_SMPS_STATIC) { + smps_mode = IEEE80211_SMPS_STATIC; + break; + } + if (mvmvif->smps_requests[i] == IEEE80211_SMPS_DYNAMIC) + smps_mode = IEEE80211_SMPS_DYNAMIC; + } + + ieee80211_request_smps(vif, 0, smps_mode); +} + +static bool iwl_wait_stats_complete(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + WARN_ON(pkt->hdr.cmd != STATISTICS_NOTIFICATION); + + return true; +} + +int iwl_mvm_request_statistics(struct iwl_mvm *mvm, bool clear) +{ + struct iwl_statistics_cmd scmd = { + .flags = clear ? cpu_to_le32(IWL_STATISTICS_FLG_CLEAR) : 0, + }; + + struct iwl_host_cmd cmd = { + .id = STATISTICS_CMD, + .len[0] = sizeof(scmd), + .data[0] = &scmd, + }; + int ret; + + /* From version 15 - STATISTICS_NOTIFICATION, the reply for + * STATISTICS_CMD is empty, and the response is with + * STATISTICS_NOTIFICATION notification + */ + if (iwl_fw_lookup_notif_ver(mvm->fw, LEGACY_GROUP, + STATISTICS_NOTIFICATION, 0) < 15) { + cmd.flags = CMD_WANT_SKB; + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (ret) + return ret; + + iwl_mvm_handle_rx_statistics(mvm, cmd.resp_pkt); + iwl_free_resp(&cmd); + } else { + struct iwl_notification_wait stats_wait; + static const u16 stats_complete[] = { + STATISTICS_NOTIFICATION, + }; + + iwl_init_notification_wait(&mvm->notif_wait, &stats_wait, + stats_complete, ARRAY_SIZE(stats_complete), + iwl_wait_stats_complete, NULL); + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (ret) { + iwl_remove_notification(&mvm->notif_wait, &stats_wait); + return ret; + } + + /* 200ms should be enough for FW to collect data from all + * LMACs and send STATISTICS_NOTIFICATION to host + */ + ret = iwl_wait_notification(&mvm->notif_wait, &stats_wait, HZ / 5); + if (ret) + return ret; + } + + if (clear) + iwl_mvm_accu_radio_stats(mvm); + + return 0; +} + +void iwl_mvm_accu_radio_stats(struct iwl_mvm *mvm) +{ + mvm->accu_radio_stats.rx_time += mvm->radio_stats.rx_time; + mvm->accu_radio_stats.tx_time += mvm->radio_stats.tx_time; + mvm->accu_radio_stats.on_time_rf += mvm->radio_stats.on_time_rf; + mvm->accu_radio_stats.on_time_scan += mvm->radio_stats.on_time_scan; +} + +struct iwl_mvm_diversity_iter_data { + struct iwl_mvm_phy_ctxt *ctxt; + bool result; +}; + +static void iwl_mvm_diversity_iter(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_diversity_iter_data *data = _data; + int i; + + if (mvmvif->phy_ctxt != data->ctxt) + return; + + for (i = 0; i < NUM_IWL_MVM_SMPS_REQ; i++) { + if (mvmvif->smps_requests[i] == IEEE80211_SMPS_STATIC || + mvmvif->smps_requests[i] == IEEE80211_SMPS_DYNAMIC) { + data->result = false; + break; + } + } +} + +bool iwl_mvm_rx_diversity_allowed(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt) +{ + struct iwl_mvm_diversity_iter_data data = { + .ctxt = ctxt, + .result = true, + }; + + lockdep_assert_held(&mvm->mutex); + + if (iwlmvm_mod_params.power_scheme != IWL_POWER_SCHEME_CAM) + return false; + + if (num_of_ant(iwl_mvm_get_valid_rx_ant(mvm)) == 1) + return false; + + if (mvm->cfg->rx_with_siso_diversity) + return false; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_diversity_iter, &data); + + return data.result; +} + +void iwl_mvm_send_low_latency_cmd(struct iwl_mvm *mvm, + bool low_latency, u16 mac_id) +{ + struct iwl_mac_low_latency_cmd cmd = { + .mac_id = cpu_to_le32(mac_id) + }; + + if (!fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_DYNAMIC_QUOTA)) + return; + + if (low_latency) { + /* currently we don't care about the direction */ + cmd.low_latency_rx = 1; + cmd.low_latency_tx = 1; + } + + if (iwl_mvm_send_cmd_pdu(mvm, WIDE_ID(MAC_CONF_GROUP, LOW_LATENCY_CMD), + 0, sizeof(cmd), &cmd)) + IWL_ERR(mvm, "Failed to send low latency command\n"); +} + +int iwl_mvm_update_low_latency(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + bool low_latency, + enum iwl_mvm_low_latency_cause cause) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int res; + bool prev; + + lockdep_assert_held(&mvm->mutex); + + prev = iwl_mvm_vif_low_latency(mvmvif); + iwl_mvm_vif_set_low_latency(mvmvif, low_latency, cause); + + low_latency = iwl_mvm_vif_low_latency(mvmvif); + + if (low_latency == prev) + return 0; + + iwl_mvm_send_low_latency_cmd(mvm, low_latency, mvmvif->id); + + res = iwl_mvm_update_quotas(mvm, false, NULL); + if (res) + return res; + + iwl_mvm_bt_coex_vif_change(mvm); + + return iwl_mvm_power_update_mac(mvm); +} + +struct iwl_mvm_low_latency_iter { + bool result; + bool result_per_band[NUM_NL80211_BANDS]; +}; + +static void iwl_mvm_ll_iter(void *_data, u8 *mac, struct ieee80211_vif *vif) +{ + struct iwl_mvm_low_latency_iter *result = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + enum nl80211_band band; + + if (iwl_mvm_vif_low_latency(mvmvif)) { + result->result = true; + + if (!mvmvif->phy_ctxt) + return; + + band = mvmvif->phy_ctxt->channel->band; + result->result_per_band[band] = true; + } +} + +bool iwl_mvm_low_latency(struct iwl_mvm *mvm) +{ + struct iwl_mvm_low_latency_iter data = {}; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_ll_iter, &data); + + return data.result; +} + +bool iwl_mvm_low_latency_band(struct iwl_mvm *mvm, enum nl80211_band band) +{ + struct iwl_mvm_low_latency_iter data = {}; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_ll_iter, &data); + + return data.result_per_band[band]; +} + +struct iwl_bss_iter_data { + struct ieee80211_vif *vif; + bool error; +}; + +static void iwl_mvm_bss_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_bss_iter_data *data = _data; + + if (vif->type != NL80211_IFTYPE_STATION || vif->p2p) + return; + + if (data->vif) { + data->error = true; + return; + } + + data->vif = vif; +} + +struct ieee80211_vif *iwl_mvm_get_bss_vif(struct iwl_mvm *mvm) +{ + struct iwl_bss_iter_data bss_iter_data = {}; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_bss_iface_iterator, &bss_iter_data); + + if (bss_iter_data.error) { + IWL_ERR(mvm, "More than one managed interface active!\n"); + return ERR_PTR(-EINVAL); + } + + return bss_iter_data.vif; +} + +struct iwl_bss_find_iter_data { + struct ieee80211_vif *vif; + u32 macid; +}; + +static void iwl_mvm_bss_find_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_bss_find_iter_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (mvmvif->id == data->macid) + data->vif = vif; +} + +struct ieee80211_vif *iwl_mvm_get_vif_by_macid(struct iwl_mvm *mvm, u32 macid) +{ + struct iwl_bss_find_iter_data data = { + .macid = macid, + }; + + lockdep_assert_held(&mvm->mutex); + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_bss_find_iface_iterator, &data); + + return data.vif; +} + +struct iwl_sta_iter_data { + bool assoc; +}; + +static void iwl_mvm_sta_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_sta_iter_data *data = _data; + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + if (vif->cfg.assoc) + data->assoc = true; +} + +bool iwl_mvm_is_vif_assoc(struct iwl_mvm *mvm) +{ + struct iwl_sta_iter_data data = { + .assoc = false, + }; + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_sta_iface_iterator, + &data); + return data.assoc; +} + +unsigned int iwl_mvm_get_wd_timeout(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + bool tdls, bool cmd_q) +{ + struct iwl_fw_dbg_trigger_tlv *trigger; + struct iwl_fw_dbg_trigger_txq_timer *txq_timer; + unsigned int default_timeout = cmd_q ? + IWL_DEF_WD_TIMEOUT : + mvm->trans->trans_cfg->base_params->wd_timeout; + + if (!iwl_fw_dbg_trigger_enabled(mvm->fw, FW_DBG_TRIGGER_TXQ_TIMERS)) { + /* + * We can't know when the station is asleep or awake, so we + * must disable the queue hang detection. + */ + if (fw_has_capa(&mvm->fw->ucode_capa, + IWL_UCODE_TLV_CAPA_STA_PM_NOTIF) && + vif && vif->type == NL80211_IFTYPE_AP) + return IWL_WATCHDOG_DISABLED; + return default_timeout; + } + + trigger = iwl_fw_dbg_get_trigger(mvm->fw, FW_DBG_TRIGGER_TXQ_TIMERS); + txq_timer = (void *)trigger->data; + + if (tdls) + return le32_to_cpu(txq_timer->tdls); + + if (cmd_q) + return le32_to_cpu(txq_timer->command_queue); + + if (WARN_ON(!vif)) + return default_timeout; + + switch (ieee80211_vif_type_p2p(vif)) { + case NL80211_IFTYPE_ADHOC: + return le32_to_cpu(txq_timer->ibss); + case NL80211_IFTYPE_STATION: + return le32_to_cpu(txq_timer->bss); + case NL80211_IFTYPE_AP: + return le32_to_cpu(txq_timer->softap); + case NL80211_IFTYPE_P2P_CLIENT: + return le32_to_cpu(txq_timer->p2p_client); + case NL80211_IFTYPE_P2P_GO: + return le32_to_cpu(txq_timer->p2p_go); + case NL80211_IFTYPE_P2P_DEVICE: + return le32_to_cpu(txq_timer->p2p_device); + case NL80211_IFTYPE_MONITOR: + return default_timeout; + default: + WARN_ON(1); + return mvm->trans->trans_cfg->base_params->wd_timeout; + } +} + +void iwl_mvm_connection_loss(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + const char *errmsg) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_mlme *trig_mlme; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_MLME); + if (!trig) + goto out; + + trig_mlme = (void *)trig->data; + + if (trig_mlme->stop_connection_loss && + --trig_mlme->stop_connection_loss) + goto out; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, "%s", errmsg); + +out: + ieee80211_connection_loss(vif); +} + +void iwl_mvm_event_frame_timeout_callback(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + const struct ieee80211_sta *sta, + u16 tid) +{ + struct iwl_fw_dbg_trigger_tlv *trig; + struct iwl_fw_dbg_trigger_ba *ba_trig; + + trig = iwl_fw_dbg_trigger_on(&mvm->fwrt, ieee80211_vif_to_wdev(vif), + FW_DBG_TRIGGER_BA); + if (!trig) + return; + + ba_trig = (void *)trig->data; + + if (!(le16_to_cpu(ba_trig->frame_timeout) & BIT(tid))) + return; + + iwl_fw_dbg_collect_trig(&mvm->fwrt, trig, + "Frame from %pM timed out, tid %d", + sta->addr, tid); +} + +u8 iwl_mvm_tcm_load_percentage(u32 airtime, u32 elapsed) +{ + if (!elapsed) + return 0; + + return (100 * airtime / elapsed) / USEC_PER_MSEC; +} + +static enum iwl_mvm_traffic_load +iwl_mvm_tcm_load(struct iwl_mvm *mvm, u32 airtime, unsigned long elapsed) +{ + u8 load = iwl_mvm_tcm_load_percentage(airtime, elapsed); + + if (load > IWL_MVM_TCM_LOAD_HIGH_THRESH) + return IWL_MVM_TRAFFIC_HIGH; + if (load > IWL_MVM_TCM_LOAD_MEDIUM_THRESH) + return IWL_MVM_TRAFFIC_MEDIUM; + + return IWL_MVM_TRAFFIC_LOW; +} + +static void iwl_mvm_tcm_iter(void *_data, u8 *mac, struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + bool low_latency, prev = mvmvif->low_latency & LOW_LATENCY_TRAFFIC; + + if (mvmvif->id >= NUM_MAC_INDEX_DRIVER) + return; + + low_latency = mvm->tcm.result.low_latency[mvmvif->id]; + + if (!mvm->tcm.result.change[mvmvif->id] && + prev == low_latency) { + iwl_mvm_update_quotas(mvm, false, NULL); + return; + } + + if (prev != low_latency) { + /* this sends traffic load and updates quota as well */ + iwl_mvm_update_low_latency(mvm, vif, low_latency, + LOW_LATENCY_TRAFFIC); + } else { + iwl_mvm_update_quotas(mvm, false, NULL); + } +} + +static void iwl_mvm_tcm_results(struct iwl_mvm *mvm) +{ + mutex_lock(&mvm->mutex); + + ieee80211_iterate_active_interfaces( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_tcm_iter, mvm); + + if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) + iwl_mvm_config_scan(mvm); + + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_tcm_uapsd_nonagg_detected_wk(struct work_struct *wk) +{ + struct iwl_mvm *mvm; + struct iwl_mvm_vif *mvmvif; + struct ieee80211_vif *vif; + + mvmvif = container_of(wk, struct iwl_mvm_vif, + uapsd_nonagg_detected_wk.work); + vif = container_of((void *)mvmvif, struct ieee80211_vif, drv_priv); + mvm = mvmvif->mvm; + + if (mvm->tcm.data[mvmvif->id].opened_rx_ba_sessions) + return; + + /* remember that this AP is broken */ + memcpy(mvm->uapsd_noagg_bssids[mvm->uapsd_noagg_bssid_write_idx].addr, + vif->bss_conf.bssid, ETH_ALEN); + mvm->uapsd_noagg_bssid_write_idx++; + if (mvm->uapsd_noagg_bssid_write_idx >= IWL_MVM_UAPSD_NOAGG_LIST_LEN) + mvm->uapsd_noagg_bssid_write_idx = 0; + + iwl_mvm_connection_loss(mvm, vif, + "AP isn't using AMPDU with uAPSD enabled"); +} + +static void iwl_mvm_uapsd_agg_disconnect(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + if (!vif->cfg.assoc) + return; + + if (!mvmvif->queue_params[IEEE80211_AC_VO].uapsd && + !mvmvif->queue_params[IEEE80211_AC_VI].uapsd && + !mvmvif->queue_params[IEEE80211_AC_BE].uapsd && + !mvmvif->queue_params[IEEE80211_AC_BK].uapsd) + return; + + if (mvm->tcm.data[mvmvif->id].uapsd_nonagg_detect.detected) + return; + + mvm->tcm.data[mvmvif->id].uapsd_nonagg_detect.detected = true; + IWL_INFO(mvm, + "detected AP should do aggregation but isn't, likely due to U-APSD\n"); + schedule_delayed_work(&mvmvif->uapsd_nonagg_detected_wk, 15 * HZ); +} + +static void iwl_mvm_check_uapsd_agg_expected_tpt(struct iwl_mvm *mvm, + unsigned int elapsed, + int mac) +{ + u64 bytes = mvm->tcm.data[mac].uapsd_nonagg_detect.rx_bytes; + u64 tpt; + unsigned long rate; + struct ieee80211_vif *vif; + + rate = ewma_rate_read(&mvm->tcm.data[mac].uapsd_nonagg_detect.rate); + + if (!rate || mvm->tcm.data[mac].opened_rx_ba_sessions || + mvm->tcm.data[mac].uapsd_nonagg_detect.detected) + return; + + if (iwl_mvm_has_new_rx_api(mvm)) { + tpt = 8 * bytes; /* kbps */ + do_div(tpt, elapsed); + rate *= 1000; /* kbps */ + if (tpt < 22 * rate / 100) + return; + } else { + /* + * the rate here is actually the threshold, in 100Kbps units, + * so do the needed conversion from bytes to 100Kbps: + * 100kb = bits / (100 * 1000), + * 100kbps = 100kb / (msecs / 1000) == + * (bits / (100 * 1000)) / (msecs / 1000) == + * bits / (100 * msecs) + */ + tpt = (8 * bytes); + do_div(tpt, elapsed * 100); + if (tpt < rate) + return; + } + + rcu_read_lock(); + vif = rcu_dereference(mvm->vif_id_to_mac[mac]); + if (vif) + iwl_mvm_uapsd_agg_disconnect(mvm, vif); + rcu_read_unlock(); +} + +static void iwl_mvm_tcm_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u32 *band = _data; + + if (!mvmvif->phy_ctxt) + return; + + band[mvmvif->id] = mvmvif->phy_ctxt->channel->band; +} + +static unsigned long iwl_mvm_calc_tcm_stats(struct iwl_mvm *mvm, + unsigned long ts, + bool handle_uapsd) +{ + unsigned int elapsed = jiffies_to_msecs(ts - mvm->tcm.ts); + unsigned int uapsd_elapsed = + jiffies_to_msecs(ts - mvm->tcm.uapsd_nonagg_ts); + u32 total_airtime = 0; + u32 band_airtime[NUM_NL80211_BANDS] = {0}; + u32 band[NUM_MAC_INDEX_DRIVER] = {0}; + int ac, mac, i; + bool low_latency = false; + enum iwl_mvm_traffic_load load, band_load; + bool handle_ll = time_after(ts, mvm->tcm.ll_ts + MVM_LL_PERIOD); + + if (handle_ll) + mvm->tcm.ll_ts = ts; + if (handle_uapsd) + mvm->tcm.uapsd_nonagg_ts = ts; + + mvm->tcm.result.elapsed = elapsed; + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_tcm_iterator, + &band); + + for (mac = 0; mac < NUM_MAC_INDEX_DRIVER; mac++) { + struct iwl_mvm_tcm_mac *mdata = &mvm->tcm.data[mac]; + u32 vo_vi_pkts = 0; + u32 airtime = mdata->rx.airtime + mdata->tx.airtime; + + total_airtime += airtime; + band_airtime[band[mac]] += airtime; + + load = iwl_mvm_tcm_load(mvm, airtime, elapsed); + mvm->tcm.result.change[mac] = load != mvm->tcm.result.load[mac]; + mvm->tcm.result.load[mac] = load; + mvm->tcm.result.airtime[mac] = airtime; + + for (ac = IEEE80211_AC_VO; ac <= IEEE80211_AC_VI; ac++) + vo_vi_pkts += mdata->rx.pkts[ac] + + mdata->tx.pkts[ac]; + + /* enable immediately with enough packets but defer disabling */ + if (vo_vi_pkts > IWL_MVM_TCM_LOWLAT_ENABLE_THRESH) + mvm->tcm.result.low_latency[mac] = true; + else if (handle_ll) + mvm->tcm.result.low_latency[mac] = false; + + if (handle_ll) { + /* clear old data */ + memset(&mdata->rx.pkts, 0, sizeof(mdata->rx.pkts)); + memset(&mdata->tx.pkts, 0, sizeof(mdata->tx.pkts)); + } + low_latency |= mvm->tcm.result.low_latency[mac]; + + if (!mvm->tcm.result.low_latency[mac] && handle_uapsd) + iwl_mvm_check_uapsd_agg_expected_tpt(mvm, uapsd_elapsed, + mac); + /* clear old data */ + if (handle_uapsd) + mdata->uapsd_nonagg_detect.rx_bytes = 0; + memset(&mdata->rx.airtime, 0, sizeof(mdata->rx.airtime)); + memset(&mdata->tx.airtime, 0, sizeof(mdata->tx.airtime)); + } + + load = iwl_mvm_tcm_load(mvm, total_airtime, elapsed); + mvm->tcm.result.global_load = load; + + for (i = 0; i < NUM_NL80211_BANDS; i++) { + band_load = iwl_mvm_tcm_load(mvm, band_airtime[i], elapsed); + mvm->tcm.result.band_load[i] = band_load; + } + + /* + * If the current load isn't low we need to force re-evaluation + * in the TCM period, so that we can return to low load if there + * was no traffic at all (and thus iwl_mvm_recalc_tcm didn't get + * triggered by traffic). + */ + if (load != IWL_MVM_TRAFFIC_LOW) + return MVM_TCM_PERIOD; + /* + * If low-latency is active we need to force re-evaluation after + * (the longer) MVM_LL_PERIOD, so that we can disable low-latency + * when there's no traffic at all. + */ + if (low_latency) + return MVM_LL_PERIOD; + /* + * Otherwise, we don't need to run the work struct because we're + * in the default "idle" state - traffic indication is low (which + * also covers the "no traffic" case) and low-latency is disabled + * so there's no state that may need to be disabled when there's + * no traffic at all. + * + * Note that this has no impact on the regular scheduling of the + * updates triggered by traffic - those happen whenever one of the + * two timeouts expire (if there's traffic at all.) + */ + return 0; +} + +void iwl_mvm_recalc_tcm(struct iwl_mvm *mvm) +{ + unsigned long ts = jiffies; + bool handle_uapsd = + time_after(ts, mvm->tcm.uapsd_nonagg_ts + + msecs_to_jiffies(IWL_MVM_UAPSD_NONAGG_PERIOD)); + + spin_lock(&mvm->tcm.lock); + if (mvm->tcm.paused || !time_after(ts, mvm->tcm.ts + MVM_TCM_PERIOD)) { + spin_unlock(&mvm->tcm.lock); + return; + } + spin_unlock(&mvm->tcm.lock); + + if (handle_uapsd && iwl_mvm_has_new_rx_api(mvm)) { + mutex_lock(&mvm->mutex); + if (iwl_mvm_request_statistics(mvm, true)) + handle_uapsd = false; + mutex_unlock(&mvm->mutex); + } + + spin_lock(&mvm->tcm.lock); + /* re-check if somebody else won the recheck race */ + if (!mvm->tcm.paused && time_after(ts, mvm->tcm.ts + MVM_TCM_PERIOD)) { + /* calculate statistics */ + unsigned long work_delay = iwl_mvm_calc_tcm_stats(mvm, ts, + handle_uapsd); + + /* the memset needs to be visible before the timestamp */ + smp_mb(); + mvm->tcm.ts = ts; + if (work_delay) + schedule_delayed_work(&mvm->tcm.work, work_delay); + } + spin_unlock(&mvm->tcm.lock); + + iwl_mvm_tcm_results(mvm); +} + +void iwl_mvm_tcm_work(struct work_struct *work) +{ + struct delayed_work *delayed_work = to_delayed_work(work); + struct iwl_mvm *mvm = container_of(delayed_work, struct iwl_mvm, + tcm.work); + + iwl_mvm_recalc_tcm(mvm); +} + +void iwl_mvm_pause_tcm(struct iwl_mvm *mvm, bool with_cancel) +{ + spin_lock_bh(&mvm->tcm.lock); + mvm->tcm.paused = true; + spin_unlock_bh(&mvm->tcm.lock); + if (with_cancel) + cancel_delayed_work_sync(&mvm->tcm.work); +} + +void iwl_mvm_resume_tcm(struct iwl_mvm *mvm) +{ + int mac; + bool low_latency = false; + + spin_lock_bh(&mvm->tcm.lock); + mvm->tcm.ts = jiffies; + mvm->tcm.ll_ts = jiffies; + for (mac = 0; mac < NUM_MAC_INDEX_DRIVER; mac++) { + struct iwl_mvm_tcm_mac *mdata = &mvm->tcm.data[mac]; + + memset(&mdata->rx.pkts, 0, sizeof(mdata->rx.pkts)); + memset(&mdata->tx.pkts, 0, sizeof(mdata->tx.pkts)); + memset(&mdata->rx.airtime, 0, sizeof(mdata->rx.airtime)); + memset(&mdata->tx.airtime, 0, sizeof(mdata->tx.airtime)); + + if (mvm->tcm.result.low_latency[mac]) + low_latency = true; + } + /* The TCM data needs to be reset before "paused" flag changes */ + smp_mb(); + mvm->tcm.paused = false; + + /* + * if the current load is not low or low latency is active, force + * re-evaluation to cover the case of no traffic. + */ + if (mvm->tcm.result.global_load > IWL_MVM_TRAFFIC_LOW) + schedule_delayed_work(&mvm->tcm.work, MVM_TCM_PERIOD); + else if (low_latency) + schedule_delayed_work(&mvm->tcm.work, MVM_LL_PERIOD); + + spin_unlock_bh(&mvm->tcm.lock); +} + +void iwl_mvm_tcm_add_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + INIT_DELAYED_WORK(&mvmvif->uapsd_nonagg_detected_wk, + iwl_mvm_tcm_uapsd_nonagg_detected_wk); +} + +void iwl_mvm_tcm_rm_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + cancel_delayed_work_sync(&mvmvif->uapsd_nonagg_detected_wk); +} + +u32 iwl_mvm_get_systime(struct iwl_mvm *mvm) +{ + u32 reg_addr = DEVICE_SYSTEM_TIME_REG; + + if (mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_22000 && + mvm->trans->cfg->gp2_reg_addr) + reg_addr = mvm->trans->cfg->gp2_reg_addr; + + return iwl_read_prph(mvm->trans, reg_addr); +} + +void iwl_mvm_get_sync_time(struct iwl_mvm *mvm, int clock_type, + u32 *gp2, u64 *boottime, ktime_t *realtime) +{ + bool ps_disabled; + + lockdep_assert_held(&mvm->mutex); + + /* Disable power save when reading GP2 */ + ps_disabled = mvm->ps_disabled; + if (!ps_disabled) { + mvm->ps_disabled = true; + iwl_mvm_power_update_device(mvm); + } + + *gp2 = iwl_mvm_get_systime(mvm); + + if (clock_type == CLOCK_BOOTTIME && boottime) + *boottime = ktime_get_boottime_ns(); + else if (clock_type == CLOCK_REALTIME && realtime) + *realtime = ktime_get_real(); + + if (!ps_disabled) { + mvm->ps_disabled = ps_disabled; + iwl_mvm_power_update_device(mvm); + } +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/vendor-cmd.c b/drivers/net/wireless/intel/iwlwifi/mvm/vendor-cmd.c new file mode 100644 index 000000000..080a1587c --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/vendor-cmd.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021 Intel Corporation + */ +#include "mvm.h" +#include <linux/nl80211-vnd-intel.h> +#include <net/netlink.h> + +static const struct nla_policy +iwl_mvm_vendor_attr_policy[NUM_IWL_MVM_VENDOR_ATTR] = { + [IWL_MVM_VENDOR_ATTR_ROAMING_FORBIDDEN] = { .type = NLA_U8 }, + [IWL_MVM_VENDOR_ATTR_AUTH_MODE] = { .type = NLA_U32 }, + [IWL_MVM_VENDOR_ATTR_CHANNEL_NUM] = { .type = NLA_U8 }, + [IWL_MVM_VENDOR_ATTR_SSID] = { .type = NLA_BINARY, + .len = IEEE80211_MAX_SSID_LEN }, + [IWL_MVM_VENDOR_ATTR_BAND] = { .type = NLA_U8 }, + [IWL_MVM_VENDOR_ATTR_COLLOC_CHANNEL] = { .type = NLA_U8 }, + [IWL_MVM_VENDOR_ATTR_COLLOC_ADDR] = { .type = NLA_BINARY, .len = ETH_ALEN }, +}; + +static int iwl_mvm_vendor_get_csme_conn_info(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_csme_conn_info *csme_conn_info; + struct sk_buff *skb; + int err = 0; + + mutex_lock(&mvm->mutex); + csme_conn_info = iwl_mvm_get_csme_conn_info(mvm); + + if (!csme_conn_info) { + err = -EINVAL; + goto out_unlock; + } + + skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 200); + if (!skb) { + err = -ENOMEM; + goto out_unlock; + } + + if (nla_put_u32(skb, IWL_MVM_VENDOR_ATTR_AUTH_MODE, + csme_conn_info->conn_info.auth_mode) || + nla_put(skb, IWL_MVM_VENDOR_ATTR_SSID, + csme_conn_info->conn_info.ssid_len, + csme_conn_info->conn_info.ssid) || + nla_put_u32(skb, IWL_MVM_VENDOR_ATTR_STA_CIPHER, + csme_conn_info->conn_info.pairwise_cipher) || + nla_put_u8(skb, IWL_MVM_VENDOR_ATTR_CHANNEL_NUM, + csme_conn_info->conn_info.channel) || + nla_put(skb, IWL_MVM_VENDOR_ATTR_ADDR, ETH_ALEN, + csme_conn_info->conn_info.bssid)) { + kfree_skb(skb); + err = -ENOBUFS; + } + +out_unlock: + mutex_unlock(&mvm->mutex); + if (err) + return err; + + return cfg80211_vendor_cmd_reply(skb); +} + +static int iwl_mvm_vendor_host_get_ownership(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_mei_get_ownership(mvm); + mutex_unlock(&mvm->mutex); + + return ret; +} + +static const struct wiphy_vendor_command iwl_mvm_vendor_commands[] = { + { + .info = { + .vendor_id = INTEL_OUI, + .subcmd = IWL_MVM_VENDOR_CMD_GET_CSME_CONN_INFO, + }, + .doit = iwl_mvm_vendor_get_csme_conn_info, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV, + .policy = iwl_mvm_vendor_attr_policy, + .maxattr = MAX_IWL_MVM_VENDOR_ATTR, + }, + { + .info = { + .vendor_id = INTEL_OUI, + .subcmd = IWL_MVM_VENDOR_CMD_HOST_GET_OWNERSHIP, + }, + .doit = iwl_mvm_vendor_host_get_ownership, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV, + .policy = iwl_mvm_vendor_attr_policy, + .maxattr = MAX_IWL_MVM_VENDOR_ATTR, + }, +}; + +enum iwl_mvm_vendor_events_idx { + /* 0x0 - 0x3 are deprecated */ + IWL_MVM_VENDOR_EVENT_IDX_ROAMING_FORBIDDEN = 4, + NUM_IWL_MVM_VENDOR_EVENT_IDX +}; + +static const struct nl80211_vendor_cmd_info +iwl_mvm_vendor_events[NUM_IWL_MVM_VENDOR_EVENT_IDX] = { + [IWL_MVM_VENDOR_EVENT_IDX_ROAMING_FORBIDDEN] = { + .vendor_id = INTEL_OUI, + .subcmd = IWL_MVM_VENDOR_CMD_ROAMING_FORBIDDEN_EVENT, + }, +}; + +void iwl_mvm_vendor_cmds_register(struct iwl_mvm *mvm) +{ + mvm->hw->wiphy->vendor_commands = iwl_mvm_vendor_commands; + mvm->hw->wiphy->n_vendor_commands = ARRAY_SIZE(iwl_mvm_vendor_commands); + mvm->hw->wiphy->vendor_events = iwl_mvm_vendor_events; + mvm->hw->wiphy->n_vendor_events = ARRAY_SIZE(iwl_mvm_vendor_events); +} + +void iwl_mvm_send_roaming_forbidden_event(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + bool forbidden) +{ + struct sk_buff *msg = + cfg80211_vendor_event_alloc(mvm->hw->wiphy, + ieee80211_vif_to_wdev(vif), + 200, IWL_MVM_VENDOR_EVENT_IDX_ROAMING_FORBIDDEN, + GFP_ATOMIC); + if (!msg) + return; + + if (WARN_ON(!vif)) + return; + + if (nla_put(msg, IWL_MVM_VENDOR_ATTR_VIF_ADDR, + ETH_ALEN, vif->addr) || + nla_put_u8(msg, IWL_MVM_VENDOR_ATTR_ROAMING_FORBIDDEN, forbidden)) + goto nla_put_failure; + + cfg80211_vendor_event(msg, GFP_ATOMIC); + return; + + nla_put_failure: + kfree_skb(msg); +} diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info-gen3.c b/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info-gen3.c new file mode 100644 index 000000000..75fd386b0 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info-gen3.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2018-2022 Intel Corporation + */ +#include "iwl-trans.h" +#include "iwl-fh.h" +#include "iwl-context-info-gen3.h" +#include "internal.h" +#include "iwl-prph.h" + +static void +iwl_pcie_ctxt_info_dbg_enable(struct iwl_trans *trans, + struct iwl_prph_scratch_hwm_cfg *dbg_cfg, + u32 *control_flags) +{ + enum iwl_fw_ini_allocation_id alloc_id = IWL_FW_INI_ALLOCATION_ID_DBGC1; + struct iwl_fw_ini_allocation_tlv *fw_mon_cfg; + u32 dbg_flags = 0; + + if (!iwl_trans_dbg_ini_valid(trans)) { + struct iwl_dram_data *fw_mon = &trans->dbg.fw_mon; + + iwl_pcie_alloc_fw_monitor(trans, 0); + + if (fw_mon->size) { + dbg_flags |= IWL_PRPH_SCRATCH_EDBG_DEST_DRAM; + + IWL_DEBUG_FW(trans, + "WRT: Applying DRAM buffer destination\n"); + + dbg_cfg->hwm_base_addr = cpu_to_le64(fw_mon->physical); + dbg_cfg->hwm_size = cpu_to_le32(fw_mon->size); + } + + goto out; + } + + fw_mon_cfg = &trans->dbg.fw_mon_cfg[alloc_id]; + + switch (le32_to_cpu(fw_mon_cfg->buf_location)) { + case IWL_FW_INI_LOCATION_SRAM_PATH: + dbg_flags |= IWL_PRPH_SCRATCH_EDBG_DEST_INTERNAL; + IWL_DEBUG_FW(trans, + "WRT: Applying SMEM buffer destination\n"); + break; + + case IWL_FW_INI_LOCATION_NPK_PATH: + dbg_flags |= IWL_PRPH_SCRATCH_EDBG_DEST_TB22DTF; + IWL_DEBUG_FW(trans, + "WRT: Applying NPK buffer destination\n"); + break; + + case IWL_FW_INI_LOCATION_DRAM_PATH: + if (trans->dbg.fw_mon_ini[alloc_id].num_frags) { + struct iwl_dram_data *frag = + &trans->dbg.fw_mon_ini[alloc_id].frags[0]; + dbg_flags |= IWL_PRPH_SCRATCH_EDBG_DEST_DRAM; + dbg_cfg->hwm_base_addr = cpu_to_le64(frag->physical); + dbg_cfg->hwm_size = cpu_to_le32(frag->size); + dbg_cfg->debug_token_config = cpu_to_le32(trans->dbg.ucode_preset); + IWL_DEBUG_FW(trans, + "WRT: Applying DRAM destination (debug_token_config=%u)\n", + dbg_cfg->debug_token_config); + IWL_DEBUG_FW(trans, + "WRT: Applying DRAM destination (alloc_id=%u, num_frags=%u)\n", + alloc_id, + trans->dbg.fw_mon_ini[alloc_id].num_frags); + } + break; + default: + IWL_ERR(trans, "WRT: Invalid buffer destination\n"); + } +out: + if (dbg_flags) + *control_flags |= IWL_PRPH_SCRATCH_EARLY_DEBUG_EN | dbg_flags; +} + +int iwl_pcie_ctxt_info_gen3_init(struct iwl_trans *trans, + const struct fw_img *fw) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_context_info_gen3 *ctxt_info_gen3; + struct iwl_prph_scratch *prph_scratch; + struct iwl_prph_scratch_ctrl_cfg *prph_sc_ctrl; + struct iwl_prph_info *prph_info; + u32 control_flags = 0; + int ret; + int cmdq_size = max_t(u32, IWL_CMD_QUEUE_SIZE, + trans->cfg->min_txq_size); + + switch (trans_pcie->rx_buf_size) { + case IWL_AMSDU_DEF: + return -EINVAL; + case IWL_AMSDU_2K: + break; + case IWL_AMSDU_4K: + control_flags |= IWL_PRPH_SCRATCH_RB_SIZE_4K; + break; + case IWL_AMSDU_8K: + control_flags |= IWL_PRPH_SCRATCH_RB_SIZE_4K; + /* if firmware supports the ext size, tell it */ + control_flags |= IWL_PRPH_SCRATCH_RB_SIZE_EXT_8K; + break; + case IWL_AMSDU_12K: + control_flags |= IWL_PRPH_SCRATCH_RB_SIZE_4K; + /* if firmware supports the ext size, tell it */ + control_flags |= IWL_PRPH_SCRATCH_RB_SIZE_EXT_16K; + break; + } + + /* Allocate prph scratch */ + prph_scratch = dma_alloc_coherent(trans->dev, sizeof(*prph_scratch), + &trans_pcie->prph_scratch_dma_addr, + GFP_KERNEL); + if (!prph_scratch) + return -ENOMEM; + + prph_sc_ctrl = &prph_scratch->ctrl_cfg; + + prph_sc_ctrl->version.version = 0; + prph_sc_ctrl->version.mac_id = + cpu_to_le16((u16)iwl_read32(trans, CSR_HW_REV)); + prph_sc_ctrl->version.size = cpu_to_le16(sizeof(*prph_scratch) / 4); + + control_flags |= IWL_PRPH_SCRATCH_MTR_MODE; + control_flags |= IWL_PRPH_MTR_FORMAT_256B & IWL_PRPH_SCRATCH_MTR_FORMAT; + + if (trans->trans_cfg->imr_enabled) + control_flags |= IWL_PRPH_SCRATCH_IMR_DEBUG_EN; + + /* initialize RX default queue */ + prph_sc_ctrl->rbd_cfg.free_rbd_addr = + cpu_to_le64(trans_pcie->rxq->bd_dma); + + iwl_pcie_ctxt_info_dbg_enable(trans, &prph_sc_ctrl->hwm_cfg, + &control_flags); + prph_sc_ctrl->control.control_flags = cpu_to_le32(control_flags); + + /* allocate ucode sections in dram and set addresses */ + ret = iwl_pcie_init_fw_sec(trans, fw, &prph_scratch->dram); + if (ret) + goto err_free_prph_scratch; + + + /* Allocate prph information + * currently we don't assign to the prph info anything, but it would get + * assigned later + * + * We also use the second half of this page to give the device some + * dummy TR/CR tail pointers - which shouldn't be necessary as we don't + * use this, but the hardware still reads/writes there and we can't let + * it go do that with a NULL pointer. + */ + BUILD_BUG_ON(sizeof(*prph_info) > PAGE_SIZE / 2); + prph_info = dma_alloc_coherent(trans->dev, PAGE_SIZE, + &trans_pcie->prph_info_dma_addr, + GFP_KERNEL); + if (!prph_info) { + ret = -ENOMEM; + goto err_free_prph_scratch; + } + + /* Allocate context info */ + ctxt_info_gen3 = dma_alloc_coherent(trans->dev, + sizeof(*ctxt_info_gen3), + &trans_pcie->ctxt_info_dma_addr, + GFP_KERNEL); + if (!ctxt_info_gen3) { + ret = -ENOMEM; + goto err_free_prph_info; + } + + ctxt_info_gen3->prph_info_base_addr = + cpu_to_le64(trans_pcie->prph_info_dma_addr); + ctxt_info_gen3->prph_scratch_base_addr = + cpu_to_le64(trans_pcie->prph_scratch_dma_addr); + ctxt_info_gen3->prph_scratch_size = + cpu_to_le32(sizeof(*prph_scratch)); + ctxt_info_gen3->cr_head_idx_arr_base_addr = + cpu_to_le64(trans_pcie->rxq->rb_stts_dma); + ctxt_info_gen3->tr_tail_idx_arr_base_addr = + cpu_to_le64(trans_pcie->prph_info_dma_addr + PAGE_SIZE / 2); + ctxt_info_gen3->cr_tail_idx_arr_base_addr = + cpu_to_le64(trans_pcie->prph_info_dma_addr + 3 * PAGE_SIZE / 4); + ctxt_info_gen3->mtr_base_addr = + cpu_to_le64(trans->txqs.txq[trans->txqs.cmd.q_id]->dma_addr); + ctxt_info_gen3->mcr_base_addr = + cpu_to_le64(trans_pcie->rxq->used_bd_dma); + ctxt_info_gen3->mtr_size = + cpu_to_le16(TFD_QUEUE_CB_SIZE(cmdq_size)); + ctxt_info_gen3->mcr_size = + cpu_to_le16(RX_QUEUE_CB_SIZE(trans->cfg->num_rbds)); + + trans_pcie->ctxt_info_gen3 = ctxt_info_gen3; + trans_pcie->prph_info = prph_info; + trans_pcie->prph_scratch = prph_scratch; + + /* Allocate IML */ + trans_pcie->iml = dma_alloc_coherent(trans->dev, trans->iml_len, + &trans_pcie->iml_dma_addr, + GFP_KERNEL); + if (!trans_pcie->iml) { + ret = -ENOMEM; + goto err_free_ctxt_info; + } + + memcpy(trans_pcie->iml, trans->iml, trans->iml_len); + + iwl_enable_fw_load_int_ctx_info(trans); + + /* kick FW self load */ + iwl_write64(trans, CSR_CTXT_INFO_ADDR, + trans_pcie->ctxt_info_dma_addr); + iwl_write64(trans, CSR_IML_DATA_ADDR, + trans_pcie->iml_dma_addr); + iwl_write32(trans, CSR_IML_SIZE_ADDR, trans->iml_len); + + iwl_set_bit(trans, CSR_CTXT_INFO_BOOT_CTRL, + CSR_AUTO_FUNC_BOOT_ENA); + + return 0; + +err_free_ctxt_info: + dma_free_coherent(trans->dev, sizeof(*trans_pcie->ctxt_info_gen3), + trans_pcie->ctxt_info_gen3, + trans_pcie->ctxt_info_dma_addr); + trans_pcie->ctxt_info_gen3 = NULL; +err_free_prph_info: + dma_free_coherent(trans->dev, PAGE_SIZE, prph_info, + trans_pcie->prph_info_dma_addr); + +err_free_prph_scratch: + dma_free_coherent(trans->dev, + sizeof(*prph_scratch), + prph_scratch, + trans_pcie->prph_scratch_dma_addr); + return ret; + +} + +void iwl_pcie_ctxt_info_gen3_free(struct iwl_trans *trans, bool alive) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (trans_pcie->iml) { + dma_free_coherent(trans->dev, trans->iml_len, trans_pcie->iml, + trans_pcie->iml_dma_addr); + trans_pcie->iml_dma_addr = 0; + trans_pcie->iml = NULL; + } + + iwl_pcie_ctxt_info_free_fw_img(trans); + + if (alive) + return; + + if (!trans_pcie->ctxt_info_gen3) + return; + + /* ctxt_info_gen3 and prph_scratch are still needed for PNVM load */ + dma_free_coherent(trans->dev, sizeof(*trans_pcie->ctxt_info_gen3), + trans_pcie->ctxt_info_gen3, + trans_pcie->ctxt_info_dma_addr); + trans_pcie->ctxt_info_dma_addr = 0; + trans_pcie->ctxt_info_gen3 = NULL; + + dma_free_coherent(trans->dev, sizeof(*trans_pcie->prph_scratch), + trans_pcie->prph_scratch, + trans_pcie->prph_scratch_dma_addr); + trans_pcie->prph_scratch_dma_addr = 0; + trans_pcie->prph_scratch = NULL; + + /* this is needed for the entire lifetime */ + dma_free_coherent(trans->dev, PAGE_SIZE, trans_pcie->prph_info, + trans_pcie->prph_info_dma_addr); + trans_pcie->prph_info_dma_addr = 0; + trans_pcie->prph_info = NULL; +} + +int iwl_trans_pcie_ctx_info_gen3_set_pnvm(struct iwl_trans *trans, + const void *data, u32 len) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_prph_scratch_ctrl_cfg *prph_sc_ctrl = + &trans_pcie->prph_scratch->ctrl_cfg; + int ret; + + if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) + return 0; + + /* only allocate the DRAM if not allocated yet */ + if (!trans->pnvm_loaded) { + if (WARN_ON(prph_sc_ctrl->pnvm_cfg.pnvm_size)) + return -EBUSY; + + ret = iwl_pcie_ctxt_info_alloc_dma(trans, data, len, + &trans_pcie->pnvm_dram); + if (ret < 0) { + IWL_DEBUG_FW(trans, "Failed to allocate PNVM DMA %d.\n", + ret); + return ret; + } + } + + prph_sc_ctrl->pnvm_cfg.pnvm_base_addr = + cpu_to_le64(trans_pcie->pnvm_dram.physical); + prph_sc_ctrl->pnvm_cfg.pnvm_size = + cpu_to_le32(trans_pcie->pnvm_dram.size); + + return 0; +} + +int iwl_trans_pcie_ctx_info_gen3_set_reduce_power(struct iwl_trans *trans, + const void *data, u32 len) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_prph_scratch_ctrl_cfg *prph_sc_ctrl = + &trans_pcie->prph_scratch->ctrl_cfg; + int ret; + + if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) + return 0; + + /* only allocate the DRAM if not allocated yet */ + if (!trans->reduce_power_loaded) { + if (WARN_ON(prph_sc_ctrl->reduce_power_cfg.size)) + return -EBUSY; + + ret = iwl_pcie_ctxt_info_alloc_dma(trans, data, len, + &trans_pcie->reduce_power_dram); + if (ret < 0) { + IWL_DEBUG_FW(trans, + "Failed to allocate reduce power DMA %d.\n", + ret); + return ret; + } + } + + prph_sc_ctrl->reduce_power_cfg.base_addr = + cpu_to_le64(trans_pcie->reduce_power_dram.physical); + prph_sc_ctrl->reduce_power_cfg.size = + cpu_to_le32(trans_pcie->reduce_power_dram.size); + + return 0; +} diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info.c b/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info.c new file mode 100644 index 000000000..74ce31fdf --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2018-2021 Intel Corporation + */ +#include "iwl-trans.h" +#include "iwl-fh.h" +#include "iwl-context-info.h" +#include "internal.h" +#include "iwl-prph.h" + +static void *_iwl_pcie_ctxt_info_dma_alloc_coherent(struct iwl_trans *trans, + size_t size, + dma_addr_t *phys, + int depth) +{ + void *result; + + if (WARN(depth > 2, + "failed to allocate DMA memory not crossing 2^32 boundary")) + return NULL; + + result = dma_alloc_coherent(trans->dev, size, phys, GFP_KERNEL); + + if (!result) + return NULL; + + if (unlikely(iwl_txq_crosses_4g_boundary(*phys, size))) { + void *old = result; + dma_addr_t oldphys = *phys; + + result = _iwl_pcie_ctxt_info_dma_alloc_coherent(trans, size, + phys, + depth + 1); + dma_free_coherent(trans->dev, size, old, oldphys); + } + + return result; +} + +static void *iwl_pcie_ctxt_info_dma_alloc_coherent(struct iwl_trans *trans, + size_t size, + dma_addr_t *phys) +{ + return _iwl_pcie_ctxt_info_dma_alloc_coherent(trans, size, phys, 0); +} + +int iwl_pcie_ctxt_info_alloc_dma(struct iwl_trans *trans, + const void *data, u32 len, + struct iwl_dram_data *dram) +{ + dram->block = iwl_pcie_ctxt_info_dma_alloc_coherent(trans, len, + &dram->physical); + if (!dram->block) + return -ENOMEM; + + dram->size = len; + memcpy(dram->block, data, len); + + return 0; +} + +void iwl_pcie_ctxt_info_free_paging(struct iwl_trans *trans) +{ + struct iwl_self_init_dram *dram = &trans->init_dram; + int i; + + if (!dram->paging) { + WARN_ON(dram->paging_cnt); + return; + } + + /* free paging*/ + for (i = 0; i < dram->paging_cnt; i++) + dma_free_coherent(trans->dev, dram->paging[i].size, + dram->paging[i].block, + dram->paging[i].physical); + + kfree(dram->paging); + dram->paging_cnt = 0; + dram->paging = NULL; +} + +int iwl_pcie_init_fw_sec(struct iwl_trans *trans, + const struct fw_img *fw, + struct iwl_context_info_dram *ctxt_dram) +{ + struct iwl_self_init_dram *dram = &trans->init_dram; + int i, ret, lmac_cnt, umac_cnt, paging_cnt; + + if (WARN(dram->paging, + "paging shouldn't already be initialized (%d pages)\n", + dram->paging_cnt)) + iwl_pcie_ctxt_info_free_paging(trans); + + lmac_cnt = iwl_pcie_get_num_sections(fw, 0); + /* add 1 due to separator */ + umac_cnt = iwl_pcie_get_num_sections(fw, lmac_cnt + 1); + /* add 2 due to separators */ + paging_cnt = iwl_pcie_get_num_sections(fw, lmac_cnt + umac_cnt + 2); + + dram->fw = kcalloc(umac_cnt + lmac_cnt, sizeof(*dram->fw), GFP_KERNEL); + if (!dram->fw) + return -ENOMEM; + dram->paging = kcalloc(paging_cnt, sizeof(*dram->paging), GFP_KERNEL); + if (!dram->paging) + return -ENOMEM; + + /* initialize lmac sections */ + for (i = 0; i < lmac_cnt; i++) { + ret = iwl_pcie_ctxt_info_alloc_dma(trans, fw->sec[i].data, + fw->sec[i].len, + &dram->fw[dram->fw_cnt]); + if (ret) + return ret; + ctxt_dram->lmac_img[i] = + cpu_to_le64(dram->fw[dram->fw_cnt].physical); + dram->fw_cnt++; + } + + /* initialize umac sections */ + for (i = 0; i < umac_cnt; i++) { + /* access FW with +1 to make up for lmac separator */ + ret = iwl_pcie_ctxt_info_alloc_dma(trans, + fw->sec[dram->fw_cnt + 1].data, + fw->sec[dram->fw_cnt + 1].len, + &dram->fw[dram->fw_cnt]); + if (ret) + return ret; + ctxt_dram->umac_img[i] = + cpu_to_le64(dram->fw[dram->fw_cnt].physical); + dram->fw_cnt++; + } + + /* + * Initialize paging. + * Paging memory isn't stored in dram->fw as the umac and lmac - it is + * stored separately. + * This is since the timing of its release is different - + * while fw memory can be released on alive, the paging memory can be + * freed only when the device goes down. + * Given that, the logic here in accessing the fw image is a bit + * different - fw_cnt isn't changing so loop counter is added to it. + */ + for (i = 0; i < paging_cnt; i++) { + /* access FW with +2 to make up for lmac & umac separators */ + int fw_idx = dram->fw_cnt + i + 2; + + ret = iwl_pcie_ctxt_info_alloc_dma(trans, fw->sec[fw_idx].data, + fw->sec[fw_idx].len, + &dram->paging[i]); + if (ret) + return ret; + + ctxt_dram->virtual_img[i] = + cpu_to_le64(dram->paging[i].physical); + dram->paging_cnt++; + } + + return 0; +} + +int iwl_pcie_ctxt_info_init(struct iwl_trans *trans, + const struct fw_img *fw) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_context_info *ctxt_info; + struct iwl_context_info_rbd_cfg *rx_cfg; + u32 control_flags = 0, rb_size; + dma_addr_t phys; + int ret; + + ctxt_info = iwl_pcie_ctxt_info_dma_alloc_coherent(trans, + sizeof(*ctxt_info), + &phys); + if (!ctxt_info) + return -ENOMEM; + + trans_pcie->ctxt_info_dma_addr = phys; + + ctxt_info->version.version = 0; + ctxt_info->version.mac_id = + cpu_to_le16((u16)iwl_read32(trans, CSR_HW_REV)); + /* size is in DWs */ + ctxt_info->version.size = cpu_to_le16(sizeof(*ctxt_info) / 4); + + switch (trans_pcie->rx_buf_size) { + case IWL_AMSDU_2K: + rb_size = IWL_CTXT_INFO_RB_SIZE_2K; + break; + case IWL_AMSDU_4K: + rb_size = IWL_CTXT_INFO_RB_SIZE_4K; + break; + case IWL_AMSDU_8K: + rb_size = IWL_CTXT_INFO_RB_SIZE_8K; + break; + case IWL_AMSDU_12K: + rb_size = IWL_CTXT_INFO_RB_SIZE_16K; + break; + default: + WARN_ON(1); + rb_size = IWL_CTXT_INFO_RB_SIZE_4K; + } + + WARN_ON(RX_QUEUE_CB_SIZE(trans->cfg->num_rbds) > 12); + control_flags = IWL_CTXT_INFO_TFD_FORMAT_LONG; + control_flags |= + u32_encode_bits(RX_QUEUE_CB_SIZE(trans->cfg->num_rbds), + IWL_CTXT_INFO_RB_CB_SIZE); + control_flags |= u32_encode_bits(rb_size, IWL_CTXT_INFO_RB_SIZE); + ctxt_info->control.control_flags = cpu_to_le32(control_flags); + + /* initialize RX default queue */ + rx_cfg = &ctxt_info->rbd_cfg; + rx_cfg->free_rbd_addr = cpu_to_le64(trans_pcie->rxq->bd_dma); + rx_cfg->used_rbd_addr = cpu_to_le64(trans_pcie->rxq->used_bd_dma); + rx_cfg->status_wr_ptr = cpu_to_le64(trans_pcie->rxq->rb_stts_dma); + + /* initialize TX command queue */ + ctxt_info->hcmd_cfg.cmd_queue_addr = + cpu_to_le64(trans->txqs.txq[trans->txqs.cmd.q_id]->dma_addr); + ctxt_info->hcmd_cfg.cmd_queue_size = + TFD_QUEUE_CB_SIZE(IWL_CMD_QUEUE_SIZE); + + /* allocate ucode sections in dram and set addresses */ + ret = iwl_pcie_init_fw_sec(trans, fw, &ctxt_info->dram); + if (ret) { + dma_free_coherent(trans->dev, sizeof(*trans_pcie->ctxt_info), + ctxt_info, trans_pcie->ctxt_info_dma_addr); + return ret; + } + + trans_pcie->ctxt_info = ctxt_info; + + iwl_enable_fw_load_int_ctx_info(trans); + + /* Configure debug, if exists */ + if (iwl_pcie_dbg_on(trans)) + iwl_pcie_apply_destination(trans); + + /* kick FW self load */ + iwl_write64(trans, CSR_CTXT_INFO_BA, trans_pcie->ctxt_info_dma_addr); + + /* Context info will be released upon alive or failure to get one */ + + return 0; +} + +void iwl_pcie_ctxt_info_free(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (!trans_pcie->ctxt_info) + return; + + dma_free_coherent(trans->dev, sizeof(*trans_pcie->ctxt_info), + trans_pcie->ctxt_info, + trans_pcie->ctxt_info_dma_addr); + trans_pcie->ctxt_info_dma_addr = 0; + trans_pcie->ctxt_info = NULL; + + iwl_pcie_ctxt_info_free_fw_img(trans); +} diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c new file mode 100644 index 000000000..4d4db5f68 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c @@ -0,0 +1,1789 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2005-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/acpi.h> + +#include "fw/acpi.h" + +#include "iwl-trans.h" +#include "iwl-drv.h" +#include "iwl-prph.h" +#include "internal.h" + +#define TRANS_CFG_MARKER BIT(0) +#define _IS_A(cfg, _struct) __builtin_types_compatible_p(typeof(cfg), \ + struct _struct) +extern int _invalid_type; +#define _TRANS_CFG_MARKER(cfg) \ + (__builtin_choose_expr(_IS_A(cfg, iwl_cfg_trans_params), \ + TRANS_CFG_MARKER, \ + __builtin_choose_expr(_IS_A(cfg, iwl_cfg), 0, _invalid_type))) +#define _ASSIGN_CFG(cfg) (_TRANS_CFG_MARKER(cfg) + (kernel_ulong_t)&(cfg)) + +#define IWL_PCI_DEVICE(dev, subdev, cfg) \ + .vendor = PCI_VENDOR_ID_INTEL, .device = (dev), \ + .subvendor = PCI_ANY_ID, .subdevice = (subdev), \ + .driver_data = _ASSIGN_CFG(cfg) + +/* Hardware specific file defines the PCI IDs table for that hardware module */ +static const struct pci_device_id iwl_hw_card_ids[] = { +#if IS_ENABLED(CONFIG_IWLDVM) + {IWL_PCI_DEVICE(0x4232, 0x1201, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1301, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1204, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1304, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1205, iwl5100_bgn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1305, iwl5100_bgn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1206, iwl5100_abg_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1306, iwl5100_abg_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1221, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1321, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1224, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1324, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1225, iwl5100_bgn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1325, iwl5100_bgn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1226, iwl5100_abg_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1326, iwl5100_abg_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1211, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1311, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1214, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1314, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1215, iwl5100_bgn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1315, iwl5100_bgn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1216, iwl5100_abg_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1316, iwl5100_abg_cfg)}, /* Half Mini Card */ + +/* 5300 Series WiFi */ + {IWL_PCI_DEVICE(0x4235, 0x1021, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1121, iwl5300_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1024, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1124, iwl5300_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1001, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1101, iwl5300_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1004, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1104, iwl5300_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4236, 0x1011, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4236, 0x1111, iwl5300_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4236, 0x1014, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4236, 0x1114, iwl5300_agn_cfg)}, /* Half Mini Card */ + +/* 5350 Series WiFi/WiMax */ + {IWL_PCI_DEVICE(0x423A, 0x1001, iwl5350_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423A, 0x1021, iwl5350_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423B, 0x1011, iwl5350_agn_cfg)}, /* Mini Card */ + +/* 5150 Series Wifi/WiMax */ + {IWL_PCI_DEVICE(0x423C, 0x1201, iwl5150_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1301, iwl5150_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1206, iwl5150_abg_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1306, iwl5150_abg_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1221, iwl5150_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1321, iwl5150_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1326, iwl5150_abg_cfg)}, /* Half Mini Card */ + + {IWL_PCI_DEVICE(0x423D, 0x1211, iwl5150_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423D, 0x1311, iwl5150_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x423D, 0x1216, iwl5150_abg_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423D, 0x1316, iwl5150_abg_cfg)}, /* Half Mini Card */ + +/* 6x00 Series */ + {IWL_PCI_DEVICE(0x422B, 0x1101, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x422B, 0x1108, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x422B, 0x1121, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x422B, 0x1128, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x422C, 0x1301, iwl6000i_2agn_cfg)}, + {IWL_PCI_DEVICE(0x422C, 0x1306, iwl6000i_2abg_cfg)}, + {IWL_PCI_DEVICE(0x422C, 0x1307, iwl6000i_2bg_cfg)}, + {IWL_PCI_DEVICE(0x422C, 0x1321, iwl6000i_2agn_cfg)}, + {IWL_PCI_DEVICE(0x422C, 0x1326, iwl6000i_2abg_cfg)}, + {IWL_PCI_DEVICE(0x4238, 0x1111, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x4238, 0x1118, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x4239, 0x1311, iwl6000i_2agn_cfg)}, + {IWL_PCI_DEVICE(0x4239, 0x1316, iwl6000i_2abg_cfg)}, + +/* 6x05 Series */ + {IWL_PCI_DEVICE(0x0082, 0x1301, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1306, iwl6005_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1307, iwl6005_2bg_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1308, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1321, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1326, iwl6005_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1328, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0085, 0x1311, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0085, 0x1318, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0085, 0x1316, iwl6005_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0xC020, iwl6005_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x0085, 0xC220, iwl6005_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x0085, 0xC228, iwl6005_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x4820, iwl6005_2agn_d_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1304, iwl6005_2agn_mow1_cfg)},/* low 5GHz active */ + {IWL_PCI_DEVICE(0x0082, 0x1305, iwl6005_2agn_mow2_cfg)},/* high 5GHz active */ + +/* 6x30 Series */ + {IWL_PCI_DEVICE(0x008A, 0x5305, iwl1030_bgn_cfg)}, + {IWL_PCI_DEVICE(0x008A, 0x5307, iwl1030_bg_cfg)}, + {IWL_PCI_DEVICE(0x008A, 0x5325, iwl1030_bgn_cfg)}, + {IWL_PCI_DEVICE(0x008A, 0x5327, iwl1030_bg_cfg)}, + {IWL_PCI_DEVICE(0x008B, 0x5315, iwl1030_bgn_cfg)}, + {IWL_PCI_DEVICE(0x008B, 0x5317, iwl1030_bg_cfg)}, + {IWL_PCI_DEVICE(0x0090, 0x5211, iwl6030_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0090, 0x5215, iwl6030_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0090, 0x5216, iwl6030_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5201, iwl6030_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5205, iwl6030_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5206, iwl6030_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5207, iwl6030_2bg_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5221, iwl6030_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5225, iwl6030_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5226, iwl6030_2abg_cfg)}, + +/* 6x50 WiFi/WiMax Series */ + {IWL_PCI_DEVICE(0x0087, 0x1301, iwl6050_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0087, 0x1306, iwl6050_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0087, 0x1321, iwl6050_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0087, 0x1326, iwl6050_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0089, 0x1311, iwl6050_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0089, 0x1316, iwl6050_2abg_cfg)}, + +/* 6150 WiFi/WiMax Series */ + {IWL_PCI_DEVICE(0x0885, 0x1305, iwl6150_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0885, 0x1307, iwl6150_bg_cfg)}, + {IWL_PCI_DEVICE(0x0885, 0x1325, iwl6150_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0885, 0x1327, iwl6150_bg_cfg)}, + {IWL_PCI_DEVICE(0x0886, 0x1315, iwl6150_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0886, 0x1317, iwl6150_bg_cfg)}, + +/* 1000 Series WiFi */ + {IWL_PCI_DEVICE(0x0083, 0x1205, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1305, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1225, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1325, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0084, 0x1215, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0084, 0x1315, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1206, iwl1000_bg_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1306, iwl1000_bg_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1226, iwl1000_bg_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1326, iwl1000_bg_cfg)}, + {IWL_PCI_DEVICE(0x0084, 0x1216, iwl1000_bg_cfg)}, + {IWL_PCI_DEVICE(0x0084, 0x1316, iwl1000_bg_cfg)}, + +/* 100 Series WiFi */ + {IWL_PCI_DEVICE(0x08AE, 0x1005, iwl100_bgn_cfg)}, + {IWL_PCI_DEVICE(0x08AE, 0x1007, iwl100_bg_cfg)}, + {IWL_PCI_DEVICE(0x08AF, 0x1015, iwl100_bgn_cfg)}, + {IWL_PCI_DEVICE(0x08AF, 0x1017, iwl100_bg_cfg)}, + {IWL_PCI_DEVICE(0x08AE, 0x1025, iwl100_bgn_cfg)}, + {IWL_PCI_DEVICE(0x08AE, 0x1027, iwl100_bg_cfg)}, + +/* 130 Series WiFi */ + {IWL_PCI_DEVICE(0x0896, 0x5005, iwl130_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0896, 0x5007, iwl130_bg_cfg)}, + {IWL_PCI_DEVICE(0x0897, 0x5015, iwl130_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0897, 0x5017, iwl130_bg_cfg)}, + {IWL_PCI_DEVICE(0x0896, 0x5025, iwl130_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0896, 0x5027, iwl130_bg_cfg)}, + +/* 2x00 Series */ + {IWL_PCI_DEVICE(0x0890, 0x4022, iwl2000_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0891, 0x4222, iwl2000_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0890, 0x4422, iwl2000_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0890, 0x4822, iwl2000_2bgn_d_cfg)}, + +/* 2x30 Series */ + {IWL_PCI_DEVICE(0x0887, 0x4062, iwl2030_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0888, 0x4262, iwl2030_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0887, 0x4462, iwl2030_2bgn_cfg)}, + +/* 6x35 Series */ + {IWL_PCI_DEVICE(0x088E, 0x4060, iwl6035_2agn_cfg)}, + {IWL_PCI_DEVICE(0x088E, 0x406A, iwl6035_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x088F, 0x4260, iwl6035_2agn_cfg)}, + {IWL_PCI_DEVICE(0x088F, 0x426A, iwl6035_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x088E, 0x4460, iwl6035_2agn_cfg)}, + {IWL_PCI_DEVICE(0x088E, 0x446A, iwl6035_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x088E, 0x4860, iwl6035_2agn_cfg)}, + {IWL_PCI_DEVICE(0x088F, 0x5260, iwl6035_2agn_cfg)}, + +/* 105 Series */ + {IWL_PCI_DEVICE(0x0894, 0x0022, iwl105_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0895, 0x0222, iwl105_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0894, 0x0422, iwl105_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0894, 0x0822, iwl105_bgn_d_cfg)}, + +/* 135 Series */ + {IWL_PCI_DEVICE(0x0892, 0x0062, iwl135_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0893, 0x0262, iwl135_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0892, 0x0462, iwl135_bgn_cfg)}, +#endif /* CONFIG_IWLDVM */ + +#if IS_ENABLED(CONFIG_IWLMVM) +/* 7260 Series */ + {IWL_PCI_DEVICE(0x08B1, 0x4070, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4072, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4170, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4C60, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4C70, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4060, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x406A, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4160, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4062, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4162, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0x4270, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0x4272, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0x4260, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0x426A, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0x4262, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4470, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4472, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4460, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x446A, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4462, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4870, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x486E, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4A70, iwl7260_2ac_cfg_high_temp)}, + {IWL_PCI_DEVICE(0x08B1, 0x4A6E, iwl7260_2ac_cfg_high_temp)}, + {IWL_PCI_DEVICE(0x08B1, 0x4A6C, iwl7260_2ac_cfg_high_temp)}, + {IWL_PCI_DEVICE(0x08B1, 0x4570, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4560, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0x4370, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0x4360, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x5070, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x5072, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x5170, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x5770, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4020, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x402A, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0x4220, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4420, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC070, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC072, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC170, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC060, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC06A, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC160, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC062, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC162, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC770, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC760, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0xC270, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xCC70, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xCC60, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0xC272, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0xC260, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0xC26A, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0xC262, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC470, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC472, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC460, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC462, iwl7260_n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC570, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC560, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0xC370, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC360, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC020, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC02A, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B2, 0xC220, iwl7260_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC420, iwl7260_2n_cfg)}, + +/* 3160 Series */ + {IWL_PCI_DEVICE(0x08B3, 0x0070, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x0072, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x0170, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x0172, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x0060, iwl3160_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x0062, iwl3160_n_cfg)}, + {IWL_PCI_DEVICE(0x08B4, 0x0270, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B4, 0x0272, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x0470, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x0472, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B4, 0x0370, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x8070, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x8072, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x8170, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x8172, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x8060, iwl3160_2n_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x8062, iwl3160_n_cfg)}, + {IWL_PCI_DEVICE(0x08B4, 0x8270, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B4, 0x8370, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B4, 0x8272, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x8470, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x8570, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x1070, iwl3160_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x1170, iwl3160_2ac_cfg)}, + +/* 3165 Series */ + {IWL_PCI_DEVICE(0x3165, 0x4010, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3165, 0x4012, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3166, 0x4212, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3165, 0x4410, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3165, 0x4510, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3165, 0x4110, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3166, 0x4310, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3166, 0x4210, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3165, 0x8010, iwl3165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x3165, 0x8110, iwl3165_2ac_cfg)}, + +/* 3168 Series */ + {IWL_PCI_DEVICE(0x24FB, 0x2010, iwl3168_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FB, 0x2110, iwl3168_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FB, 0x2050, iwl3168_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FB, 0x2150, iwl3168_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FB, 0x0000, iwl3168_2ac_cfg)}, + +/* 7265 Series */ + {IWL_PCI_DEVICE(0x095A, 0x5010, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5110, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5100, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x5310, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x5302, iwl7265_n_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x5210, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5C10, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5012, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5412, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5410, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5510, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5400, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x1010, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5000, iwl7265_2n_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x500A, iwl7265_2n_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x5200, iwl7265_2n_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5002, iwl7265_n_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5102, iwl7265_n_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x5202, iwl7265_n_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9010, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9012, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x900A, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9110, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9112, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x9210, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x9200, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9510, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x9310, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9410, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5020, iwl7265_2n_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x502A, iwl7265_2n_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5420, iwl7265_2n_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5090, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5190, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5590, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x5290, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5490, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x5F10, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x5212, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095B, 0x520A, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9000, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9400, iwl7265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x095A, 0x9E10, iwl7265_2ac_cfg)}, + +/* 8000 Series */ + {IWL_PCI_DEVICE(0x24F3, 0x0010, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x1010, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x10B0, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0130, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x1130, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0132, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x1132, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0110, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x01F0, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0012, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x1012, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x1110, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0050, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0250, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x1050, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0150, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x1150, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F4, 0x0030, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F4, 0x1030, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0xC010, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0xC110, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0xD010, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0xC050, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0xD050, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0xD0B0, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0xB0B0, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x8010, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x8110, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x9010, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x9110, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F4, 0x8030, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F4, 0x9030, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F4, 0xC030, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F4, 0xD030, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x8130, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x9130, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x8132, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x9132, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x8050, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x8150, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x9050, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x9150, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0004, iwl8260_2n_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0044, iwl8260_2n_cfg)}, + {IWL_PCI_DEVICE(0x24F5, 0x0010, iwl4165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F6, 0x0030, iwl4165_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0810, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0910, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0850, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0950, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0930, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x0000, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24F3, 0x4010, iwl8260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0010, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0110, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x1110, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x1130, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0130, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x1010, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x10D0, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0050, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0150, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x9010, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x8110, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x8050, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x8010, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0810, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x9110, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x8130, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0910, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0930, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0950, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0850, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x1014, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x3E02, iwl8275_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x3E01, iwl8275_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x1012, iwl8275_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0012, iwl8275_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x0014, iwl8265_2ac_cfg)}, + {IWL_PCI_DEVICE(0x24FD, 0x9074, iwl8265_2ac_cfg)}, + +/* 9000 Series */ + {IWL_PCI_DEVICE(0x2526, PCI_ANY_ID, iwl9000_trans_cfg)}, + {IWL_PCI_DEVICE(0x271B, PCI_ANY_ID, iwl9000_trans_cfg)}, + {IWL_PCI_DEVICE(0x271C, PCI_ANY_ID, iwl9000_trans_cfg)}, + {IWL_PCI_DEVICE(0x30DC, PCI_ANY_ID, iwl9560_long_latency_trans_cfg)}, + {IWL_PCI_DEVICE(0x31DC, PCI_ANY_ID, iwl9560_shared_clk_trans_cfg)}, + {IWL_PCI_DEVICE(0x9DF0, PCI_ANY_ID, iwl9560_trans_cfg)}, + {IWL_PCI_DEVICE(0xA370, PCI_ANY_ID, iwl9560_trans_cfg)}, + +/* Qu devices */ + {IWL_PCI_DEVICE(0x02F0, PCI_ANY_ID, iwl_qu_trans_cfg)}, + {IWL_PCI_DEVICE(0x06F0, PCI_ANY_ID, iwl_qu_trans_cfg)}, + + {IWL_PCI_DEVICE(0x34F0, PCI_ANY_ID, iwl_qu_medium_latency_trans_cfg)}, + {IWL_PCI_DEVICE(0x3DF0, PCI_ANY_ID, iwl_qu_medium_latency_trans_cfg)}, + {IWL_PCI_DEVICE(0x4DF0, PCI_ANY_ID, iwl_qu_medium_latency_trans_cfg)}, + + {IWL_PCI_DEVICE(0x43F0, PCI_ANY_ID, iwl_qu_long_latency_trans_cfg)}, + {IWL_PCI_DEVICE(0xA0F0, PCI_ANY_ID, iwl_qu_long_latency_trans_cfg)}, + + {IWL_PCI_DEVICE(0x2720, PCI_ANY_ID, iwl_qnj_trans_cfg)}, + + {IWL_PCI_DEVICE(0x2723, PCI_ANY_ID, iwl_ax200_trans_cfg)}, + +/* So devices */ + {IWL_PCI_DEVICE(0x2725, PCI_ANY_ID, iwl_so_trans_cfg)}, + {IWL_PCI_DEVICE(0x2726, PCI_ANY_ID, iwl_snj_trans_cfg)}, + {IWL_PCI_DEVICE(0x7A70, PCI_ANY_ID, iwl_so_long_latency_imr_trans_cfg)}, + {IWL_PCI_DEVICE(0x7AF0, PCI_ANY_ID, iwl_so_trans_cfg)}, + {IWL_PCI_DEVICE(0x51F0, PCI_ANY_ID, iwl_so_long_latency_trans_cfg)}, + {IWL_PCI_DEVICE(0x51F1, PCI_ANY_ID, iwl_so_long_latency_imr_trans_cfg)}, + {IWL_PCI_DEVICE(0x51F1, PCI_ANY_ID, iwl_so_long_latency_trans_cfg)}, + {IWL_PCI_DEVICE(0x54F0, PCI_ANY_ID, iwl_so_long_latency_trans_cfg)}, + {IWL_PCI_DEVICE(0x7F70, PCI_ANY_ID, iwl_so_trans_cfg)}, + +/* Ma devices */ + {IWL_PCI_DEVICE(0x2729, PCI_ANY_ID, iwl_ma_trans_cfg)}, + {IWL_PCI_DEVICE(0x7E40, PCI_ANY_ID, iwl_ma_trans_cfg)}, + +/* Bz devices */ + {IWL_PCI_DEVICE(0x2727, PCI_ANY_ID, iwl_bz_trans_cfg)}, + {IWL_PCI_DEVICE(0x272b, PCI_ANY_ID, iwl_bz_trans_cfg)}, + {IWL_PCI_DEVICE(0xA840, PCI_ANY_ID, iwl_bz_trans_cfg)}, + {IWL_PCI_DEVICE(0x7740, PCI_ANY_ID, iwl_bz_trans_cfg)}, +#endif /* CONFIG_IWLMVM */ + + {0} +}; +MODULE_DEVICE_TABLE(pci, iwl_hw_card_ids); + +#define _IWL_DEV_INFO(_device, _subdevice, _mac_type, _mac_step, _rf_type, \ + _rf_id, _no_160, _cores, _cdb, _jacket, _cfg, _name) \ + { .device = (_device), .subdevice = (_subdevice), .cfg = &(_cfg), \ + .name = _name, .mac_type = _mac_type, .rf_type = _rf_type, \ + .no_160 = _no_160, .cores = _cores, .rf_id = _rf_id, \ + .mac_step = _mac_step, .cdb = _cdb, .jacket = _jacket } + +#define IWL_DEV_INFO(_device, _subdevice, _cfg, _name) \ + _IWL_DEV_INFO(_device, _subdevice, IWL_CFG_ANY, IWL_CFG_ANY, \ + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_ANY, \ + IWL_CFG_ANY, IWL_CFG_ANY, _cfg, _name) + +static const struct iwl_dev_info iwl_dev_info_table[] = { +#if IS_ENABLED(CONFIG_IWLMVM) +/* 9000 */ + IWL_DEV_INFO(0x2526, 0x1550, iwl9260_2ac_cfg, iwl9260_killer_1550_name), + IWL_DEV_INFO(0x2526, 0x1551, iwl9560_2ac_cfg_soc, iwl9560_killer_1550s_name), + IWL_DEV_INFO(0x2526, 0x1552, iwl9560_2ac_cfg_soc, iwl9560_killer_1550i_name), + IWL_DEV_INFO(0x30DC, 0x1551, iwl9560_2ac_cfg_soc, iwl9560_killer_1550s_name), + IWL_DEV_INFO(0x30DC, 0x1552, iwl9560_2ac_cfg_soc, iwl9560_killer_1550i_name), + IWL_DEV_INFO(0x31DC, 0x1551, iwl9560_2ac_cfg_soc, iwl9560_killer_1550s_name), + IWL_DEV_INFO(0x31DC, 0x1552, iwl9560_2ac_cfg_soc, iwl9560_killer_1550i_name), + IWL_DEV_INFO(0xA370, 0x1551, iwl9560_2ac_cfg_soc, iwl9560_killer_1550s_name), + IWL_DEV_INFO(0xA370, 0x1552, iwl9560_2ac_cfg_soc, iwl9560_killer_1550i_name), + IWL_DEV_INFO(0x54F0, 0x1551, iwl9560_2ac_cfg_soc, iwl9560_killer_1550s_160_name), + IWL_DEV_INFO(0x54F0, 0x1552, iwl9560_2ac_cfg_soc, iwl9560_killer_1550i_name), + IWL_DEV_INFO(0x51F0, 0x1552, iwl9560_2ac_cfg_soc, iwl9560_killer_1550s_160_name), + IWL_DEV_INFO(0x51F0, 0x1551, iwl9560_2ac_cfg_soc, iwl9560_killer_1550i_160_name), + IWL_DEV_INFO(0x51F0, 0x1691, iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_killer_1690s_name), + IWL_DEV_INFO(0x51F0, 0x1692, iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_killer_1690i_name), + IWL_DEV_INFO(0x51F1, 0x1692, iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_killer_1690i_name), + IWL_DEV_INFO(0x54F0, 0x1691, iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_killer_1690s_name), + IWL_DEV_INFO(0x54F0, 0x1692, iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_killer_1690i_name), + IWL_DEV_INFO(0x7A70, 0x1691, iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_killer_1690s_name), + IWL_DEV_INFO(0x7A70, 0x1692, iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_killer_1690i_name), + IWL_DEV_INFO(0x7AF0, 0x1691, iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_killer_1690s_name), + IWL_DEV_INFO(0x7AF0, 0x1692, iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_killer_1690i_name), + + IWL_DEV_INFO(0x271C, 0x0214, iwl9260_2ac_cfg, iwl9260_1_name), + IWL_DEV_INFO(0x7E40, 0x1691, iwl_cfg_ma_a0_gf4_a0, iwl_ax411_killer_1690s_name), + IWL_DEV_INFO(0x7E40, 0x1692, iwl_cfg_ma_a0_gf4_a0, iwl_ax411_killer_1690i_name), + +/* AX200 */ + IWL_DEV_INFO(0x2723, IWL_CFG_ANY, iwl_ax200_cfg_cc, iwl_ax200_name), + IWL_DEV_INFO(0x2723, 0x1653, iwl_ax200_cfg_cc, iwl_ax200_killer_1650w_name), + IWL_DEV_INFO(0x2723, 0x1654, iwl_ax200_cfg_cc, iwl_ax200_killer_1650x_name), + + /* Qu with Hr */ + IWL_DEV_INFO(0x43F0, 0x0070, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x43F0, 0x0074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x43F0, 0x0078, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x43F0, 0x007C, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x43F0, 0x1651, killer1650s_2ax_cfg_qu_b0_hr_b0, iwl_ax201_killer_1650s_name), + IWL_DEV_INFO(0x43F0, 0x1652, killer1650i_2ax_cfg_qu_b0_hr_b0, iwl_ax201_killer_1650i_name), + IWL_DEV_INFO(0x43F0, 0x2074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x43F0, 0x4070, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0xA0F0, 0x0070, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0xA0F0, 0x0074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0xA0F0, 0x0078, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0xA0F0, 0x007C, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0xA0F0, 0x0A10, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0xA0F0, 0x1651, killer1650s_2ax_cfg_qu_b0_hr_b0, NULL), + IWL_DEV_INFO(0xA0F0, 0x1652, killer1650i_2ax_cfg_qu_b0_hr_b0, NULL), + IWL_DEV_INFO(0xA0F0, 0x2074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0xA0F0, 0x4070, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0xA0F0, 0x6074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x0070, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x0074, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x6074, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x0078, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x007C, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x0310, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x1651, iwl_ax1650s_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x1652, iwl_ax1650i_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x2074, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x02F0, 0x4070, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x0070, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x0074, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x0078, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x007C, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x0310, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x1651, iwl_ax1650s_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x1652, iwl_ax1650i_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x2074, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x06F0, 0x4070, iwl_ax201_cfg_quz_hr, NULL), + IWL_DEV_INFO(0x34F0, 0x0070, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x34F0, 0x0074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x34F0, 0x0078, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x34F0, 0x007C, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x34F0, 0x0310, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x34F0, 0x1651, killer1650s_2ax_cfg_qu_b0_hr_b0, NULL), + IWL_DEV_INFO(0x34F0, 0x1652, killer1650i_2ax_cfg_qu_b0_hr_b0, NULL), + IWL_DEV_INFO(0x34F0, 0x2074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x34F0, 0x4070, iwl_ax201_cfg_qu_hr, NULL), + + IWL_DEV_INFO(0x3DF0, 0x0070, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x3DF0, 0x0074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x3DF0, 0x0078, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x3DF0, 0x007C, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x3DF0, 0x0310, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x3DF0, 0x1651, killer1650s_2ax_cfg_qu_b0_hr_b0, NULL), + IWL_DEV_INFO(0x3DF0, 0x1652, killer1650i_2ax_cfg_qu_b0_hr_b0, NULL), + IWL_DEV_INFO(0x3DF0, 0x2074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x3DF0, 0x4070, iwl_ax201_cfg_qu_hr, NULL), + + IWL_DEV_INFO(0x4DF0, 0x0070, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x4DF0, 0x0074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x4DF0, 0x0078, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x4DF0, 0x007C, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x4DF0, 0x0310, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x4DF0, 0x1651, killer1650s_2ax_cfg_qu_b0_hr_b0, NULL), + IWL_DEV_INFO(0x4DF0, 0x1652, killer1650i_2ax_cfg_qu_b0_hr_b0, NULL), + IWL_DEV_INFO(0x4DF0, 0x2074, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x4DF0, 0x4070, iwl_ax201_cfg_qu_hr, NULL), + IWL_DEV_INFO(0x4DF0, 0x6074, iwl_ax201_cfg_qu_hr, NULL), + + /* So with HR */ + IWL_DEV_INFO(0x2725, 0x0090, iwlax211_2ax_cfg_so_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x0020, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x2020, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x0024, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x0310, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x0510, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x0A10, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0xE020, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0xE024, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x4020, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x6020, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x6024, iwlax210_2ax_cfg_ty_gf_a0, NULL), + IWL_DEV_INFO(0x2725, 0x1673, iwlax210_2ax_cfg_ty_gf_a0, iwl_ax210_killer_1675w_name), + IWL_DEV_INFO(0x2725, 0x1674, iwlax210_2ax_cfg_ty_gf_a0, iwl_ax210_killer_1675x_name), + IWL_DEV_INFO(0x7A70, 0x0090, iwlax211_2ax_cfg_so_gf_a0_long, NULL), + IWL_DEV_INFO(0x7A70, 0x0098, iwlax211_2ax_cfg_so_gf_a0_long, NULL), + IWL_DEV_INFO(0x7A70, 0x00B0, iwlax411_2ax_cfg_so_gf4_a0_long, NULL), + IWL_DEV_INFO(0x7A70, 0x0310, iwlax211_2ax_cfg_so_gf_a0_long, NULL), + IWL_DEV_INFO(0x7A70, 0x0510, iwlax211_2ax_cfg_so_gf_a0_long, NULL), + IWL_DEV_INFO(0x7A70, 0x0A10, iwlax211_2ax_cfg_so_gf_a0_long, NULL), + IWL_DEV_INFO(0x7AF0, 0x0090, iwlax211_2ax_cfg_so_gf_a0, NULL), + IWL_DEV_INFO(0x7AF0, 0x0098, iwlax211_2ax_cfg_so_gf_a0, NULL), + IWL_DEV_INFO(0x7AF0, 0x00B0, iwlax411_2ax_cfg_so_gf4_a0, NULL), + IWL_DEV_INFO(0x7AF0, 0x0310, iwlax211_2ax_cfg_so_gf_a0, NULL), + IWL_DEV_INFO(0x7AF0, 0x0510, iwlax211_2ax_cfg_so_gf_a0, NULL), + IWL_DEV_INFO(0x7AF0, 0x0A10, iwlax211_2ax_cfg_so_gf_a0, NULL), + + /* So with JF */ + IWL_DEV_INFO(0x7A70, 0x1551, iwl9560_2ac_cfg_soc, iwl9560_killer_1550s_160_name), + IWL_DEV_INFO(0x7A70, 0x1552, iwl9560_2ac_cfg_soc, iwl9560_killer_1550i_160_name), + IWL_DEV_INFO(0x7AF0, 0x1551, iwl9560_2ac_cfg_soc, iwl9560_killer_1550s_160_name), + IWL_DEV_INFO(0x7AF0, 0x1552, iwl9560_2ac_cfg_soc, iwl9560_killer_1550i_160_name), + + /* SnJ with HR */ + IWL_DEV_INFO(0x2725, 0x00B0, iwlax411_2ax_cfg_sosnj_gf4_a0, NULL), + IWL_DEV_INFO(0x2726, 0x0090, iwlax211_cfg_snj_gf_a0, NULL), + IWL_DEV_INFO(0x2726, 0x0098, iwlax211_cfg_snj_gf_a0, NULL), + IWL_DEV_INFO(0x2726, 0x00B0, iwlax411_2ax_cfg_sosnj_gf4_a0, NULL), + IWL_DEV_INFO(0x2726, 0x00B4, iwlax411_2ax_cfg_sosnj_gf4_a0, NULL), + IWL_DEV_INFO(0x2726, 0x0510, iwlax211_cfg_snj_gf_a0, NULL), + IWL_DEV_INFO(0x2726, 0x1651, iwl_cfg_snj_hr_b0, iwl_ax201_killer_1650s_name), + IWL_DEV_INFO(0x2726, 0x1652, iwl_cfg_snj_hr_b0, iwl_ax201_killer_1650i_name), + IWL_DEV_INFO(0x2726, 0x1691, iwlax411_2ax_cfg_sosnj_gf4_a0, iwl_ax411_killer_1690s_name), + IWL_DEV_INFO(0x2726, 0x1692, iwlax411_2ax_cfg_sosnj_gf4_a0, iwl_ax411_killer_1690i_name), + IWL_DEV_INFO(0x7F70, 0x1691, iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_killer_1690s_name), + IWL_DEV_INFO(0x7F70, 0x1692, iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_killer_1690i_name), + + /* SO with GF2 */ + IWL_DEV_INFO(0x2726, 0x1671, iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_killer_1675s_name), + IWL_DEV_INFO(0x2726, 0x1672, iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_killer_1675i_name), + IWL_DEV_INFO(0x51F0, 0x1671, iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_killer_1675s_name), + IWL_DEV_INFO(0x51F0, 0x1672, iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_killer_1675i_name), + IWL_DEV_INFO(0x51F1, 0x1671, iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_killer_1675s_name), + IWL_DEV_INFO(0x51F1, 0x1672, iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_killer_1675i_name), + IWL_DEV_INFO(0x54F0, 0x1671, iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_killer_1675s_name), + IWL_DEV_INFO(0x54F0, 0x1672, iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_killer_1675i_name), + IWL_DEV_INFO(0x7A70, 0x1671, iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_killer_1675s_name), + IWL_DEV_INFO(0x7A70, 0x1672, iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_killer_1675i_name), + IWL_DEV_INFO(0x7AF0, 0x1671, iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_killer_1675s_name), + IWL_DEV_INFO(0x7AF0, 0x1672, iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_killer_1675i_name), + IWL_DEV_INFO(0x7F70, 0x1671, iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_killer_1675s_name), + IWL_DEV_INFO(0x7F70, 0x1672, iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_killer_1675i_name), + + /* MA with GF2 */ + IWL_DEV_INFO(0x7E40, 0x1671, iwl_cfg_ma_a0_gf_a0, iwl_ax211_killer_1675s_name), + IWL_DEV_INFO(0x7E40, 0x1672, iwl_cfg_ma_a0_gf_a0, iwl_ax211_killer_1675i_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PU, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_2ac_cfg_soc, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PU, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_2ac_cfg_soc, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PU, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_2ac_cfg_soc, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PU, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_2ac_cfg_soc, iwl9462_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PU, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_2ac_cfg_soc, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PU, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_2ac_cfg_soc, iwl9560_name), + + _IWL_DEV_INFO(0x2526, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9260_2ac_cfg, iwl9461_160_name), + _IWL_DEV_INFO(0x2526, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9260_2ac_cfg, iwl9461_name), + _IWL_DEV_INFO(0x2526, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9260_2ac_cfg, iwl9462_160_name), + _IWL_DEV_INFO(0x2526, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_PNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9260_2ac_cfg, iwl9462_name), + + _IWL_DEV_INFO(0x2526, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_CORES_BT_GNSS, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9260_2ac_cfg, iwl9270_160_name), + _IWL_DEV_INFO(0x2526, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_NO_160, IWL_CFG_CORES_BT_GNSS, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9260_2ac_cfg, iwl9270_name), + + _IWL_DEV_INFO(0x271B, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_TH1, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9260_2ac_cfg, iwl9162_160_name), + _IWL_DEV_INFO(0x271B, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_TH1, IWL_CFG_ANY, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9260_2ac_cfg, iwl9162_name), + + _IWL_DEV_INFO(0x2526, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9260_2ac_cfg, iwl9260_160_name), + _IWL_DEV_INFO(0x2526, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_TH, IWL_CFG_ANY, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9260_2ac_cfg, iwl9260_name), + +/* Qu with Jf */ + /* Qu B step */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qu_b0_jf_b0_cfg, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qu_b0_jf_b0_cfg, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qu_b0_jf_b0_cfg, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qu_b0_jf_b0_cfg, iwl9462_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qu_b0_jf_b0_cfg, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qu_b0_jf_b0_cfg, iwl9560_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, 0x1551, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qu_b0_jf_b0_cfg, iwl9560_killer_1550s_name), + _IWL_DEV_INFO(IWL_CFG_ANY, 0x1552, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qu_b0_jf_b0_cfg, iwl9560_killer_1550i_name), + + /* Qu C step */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qu_c0_jf_b0_cfg, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qu_c0_jf_b0_cfg, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qu_c0_jf_b0_cfg, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qu_c0_jf_b0_cfg, iwl9462_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qu_c0_jf_b0_cfg, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qu_c0_jf_b0_cfg, iwl9560_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, 0x1551, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qu_c0_jf_b0_cfg, iwl9560_killer_1550s_name), + _IWL_DEV_INFO(IWL_CFG_ANY, 0x1552, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qu_c0_jf_b0_cfg, iwl9560_killer_1550i_name), + + /* QuZ */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_quz_a0_jf_b0_cfg, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_quz_a0_jf_b0_cfg, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_quz_a0_jf_b0_cfg, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_quz_a0_jf_b0_cfg, iwl9462_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_quz_a0_jf_b0_cfg, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_quz_a0_jf_b0_cfg, iwl9560_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, 0x1551, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_quz_a0_jf_b0_cfg, iwl9560_killer_1550s_name), + _IWL_DEV_INFO(IWL_CFG_ANY, 0x1552, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_quz_a0_jf_b0_cfg, iwl9560_killer_1550i_name), + + /* QnJ */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qnj_b0_jf_b0_cfg, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qnj_b0_jf_b0_cfg, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qnj_b0_jf_b0_cfg, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qnj_b0_jf_b0_cfg, iwl9462_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qnj_b0_jf_b0_cfg, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qnj_b0_jf_b0_cfg, iwl9560_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, 0x1551, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qnj_b0_jf_b0_cfg, iwl9560_killer_1550s_name), + _IWL_DEV_INFO(IWL_CFG_ANY, 0x1552, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl9560_qnj_b0_jf_b0_cfg, iwl9560_killer_1550i_name), + +/* Qu with Hr */ + /* Qu B step */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_HR1, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_qu_b0_hr1_b0, iwl_ax101_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_B_STEP, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_NO_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_qu_b0_hr_b0, iwl_ax203_name), + + /* Qu C step */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_HR1, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_qu_c0_hr1_b0, iwl_ax101_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_NO_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_qu_c0_hr_b0, iwl_ax203_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QU, SILICON_C_STEP, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_qu_c0_hr_b0, iwl_ax201_name), + + /* QuZ */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QUZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR1, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_quz_a0_hr1_b0, iwl_ax101_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QUZ, SILICON_B_STEP, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_NO_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_quz_a0_hr_b0, iwl_ax203_name), + +/* QnJ with Hr */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_QNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_qnj_b0_hr_b0_cfg, iwl_ax201_name), + +/* SnJ with Jf */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_snj_a0_jf_b0, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_snj_a0_jf_b0, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_snj_a0_jf_b0, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_snj_a0_jf_b0, iwl9462_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_snj_a0_jf_b0, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_snj_a0_jf_b0, iwl9560_name), + +/* SnJ with Hr */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR1, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_snj_hr_b0, iwl_ax101_name), + + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_snj_hr_b0, iwl_ax201_name), + +/* Ma */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_MA, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_ma_a0_hr_b0, iwl_ax201_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_MA, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_GF, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_ma_a0_gf_a0, iwl_ax211_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_MA, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_GF, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_CDB, IWL_CFG_ANY, + iwl_cfg_ma_a0_gf4_a0, iwl_ax211_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_MA, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_MR, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_ma_a0_mr_a0, iwl_ax221_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_MA, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_FM, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_ma_a0_fm_a0, iwl_ax231_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_MR, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_snj_a0_mr_a0, iwl_ax221_name), + +/* So with Hr */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_NO_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_so_a0_hr_a0, iwl_ax203_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR1, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_so_a0_hr_a0, iwl_ax101_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_so_a0_hr_a0, iwl_ax201_name), + +/* So-F with Hr */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_NO_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_so_a0_hr_a0, iwl_ax203_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR1, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_so_a0_hr_a0, iwl_ax101_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_so_a0_hr_a0, iwl_ax201_name), + +/* So-F with Gf */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_GF, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_GF, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_CDB, IWL_CFG_ANY, + iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_name), + +/* Bz */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_BZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR2, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_bz_a0_hr_b0, iwl_bz_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_BZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_GF, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_bz_a0_gf_a0, iwl_bz_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_BZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_GF, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_CDB, IWL_CFG_ANY, + iwl_cfg_bz_a0_gf4_a0, iwl_bz_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_BZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_MR, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_bz_a0_mr_a0, iwl_bz_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_BZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_FM, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_bz_a0_fm_a0, iwl_bz_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_BZ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_FM, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_CDB, IWL_CFG_NO_JACKET, + iwl_cfg_bz_a0_fm4_a0, iwl_bz_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_GL, SILICON_A_STEP, + IWL_CFG_RF_TYPE_FM, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_NO_JACKET, + iwl_cfg_gl_a0_fm_a0, iwl_bz_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_GL, SILICON_B_STEP, + IWL_CFG_RF_TYPE_FM, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_NO_JACKET, + iwl_cfg_gl_b0_fm_b0, iwl_bz_name), + +/* BZ Z step */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_BZ, SILICON_Z_STEP, + IWL_CFG_RF_TYPE_GF, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_bz_z0_gf_a0, iwl_bz_name), + +/* BNJ */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_GL, SILICON_A_STEP, + IWL_CFG_RF_TYPE_FM, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_IS_JACKET, + iwl_cfg_bnj_a0_fm_a0, iwl_bz_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_GL, SILICON_B_STEP, + IWL_CFG_RF_TYPE_FM, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_IS_JACKET, + iwl_cfg_bnj_b0_fm_b0, iwl_bz_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_GL, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_FM, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_CDB, IWL_CFG_IS_JACKET, + iwl_cfg_bnj_a0_fm4_a0, iwl_bz_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_GL, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_GF, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_IS_JACKET, + iwl_cfg_bnj_a0_gf_a0, iwl_bz_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_GL, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_GF, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_CDB, IWL_CFG_IS_JACKET, + iwl_cfg_bnj_a0_gf4_a0, iwl_bz_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_GL, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_HR1, IWL_CFG_ANY, + IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_IS_JACKET, + iwl_cfg_bnj_a0_hr_b0, iwl_bz_name), + +/* SoF with JF2 */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9560_name), + +/* SoF with JF */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9462_name), + +/* SoF with JF2 */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9560_name), + +/* SoF with JF */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9462_name), + +/* So with GF */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_GF, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax211_2ax_cfg_so_gf_a0, iwl_ax211_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_GF, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_CDB, IWL_CFG_ANY, + iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_name), + +/* So with JF2 */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9560_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF2, IWL_CFG_RF_ID_JF, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9560_name), + +/* So with JF */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9461_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9462_160_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9461_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_JF1, IWL_CFG_RF_ID_JF1_DIV, + IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwlax210_2ax_cfg_so_jf_b0, iwl9462_name), + +/* MsP */ +/* For now we use the same FW as MR, but this will change in the future. */ + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SO, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_MS, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_so_a0_ms_a0, iwl_ax204_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SOF, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_MS, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_so_a0_ms_a0, iwl_ax204_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_MA, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_MS, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_ma_a0_ms_a0, iwl_ax204_name), + _IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, + IWL_CFG_MAC_TYPE_SNJ, IWL_CFG_ANY, + IWL_CFG_RF_TYPE_MS, IWL_CFG_ANY, + IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, IWL_CFG_ANY, + iwl_cfg_snj_a0_ms_a0, iwl_ax204_name) + +#endif /* CONFIG_IWLMVM */ +}; + +/* + * In case that there is no OTP on the NIC, get the rf id and cdb info + * from the prph registers. + */ +static int get_crf_id(struct iwl_trans *iwl_trans) +{ + int ret = 0; + u32 sd_reg_ver_addr; + u32 cdb = 0; + u32 val; + + if (iwl_trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + sd_reg_ver_addr = SD_REG_VER_GEN2; + else + sd_reg_ver_addr = SD_REG_VER; + + if (!iwl_trans_grab_nic_access(iwl_trans)) { + IWL_ERR(iwl_trans, "Failed to grab nic access before reading crf id\n"); + ret = -EIO; + goto out; + } + + /* Enable access to peripheral registers */ + val = iwl_read_umac_prph_no_grab(iwl_trans, WFPM_CTRL_REG); + val |= ENABLE_WFPM; + iwl_write_umac_prph_no_grab(iwl_trans, WFPM_CTRL_REG, val); + + /* Read crf info */ + val = iwl_read_prph_no_grab(iwl_trans, sd_reg_ver_addr); + + /* Read cdb info (also contains the jacket info if needed in the future */ + cdb = iwl_read_umac_prph_no_grab(iwl_trans, WFPM_OTP_CFG1_ADDR); + + /* Map between crf id to rf id */ + switch (REG_CRF_ID_TYPE(val)) { + case REG_CRF_ID_TYPE_JF_1: + iwl_trans->hw_rf_id = (IWL_CFG_RF_TYPE_JF1 << 12); + break; + case REG_CRF_ID_TYPE_JF_2: + iwl_trans->hw_rf_id = (IWL_CFG_RF_TYPE_JF2 << 12); + break; + case REG_CRF_ID_TYPE_HR_NONE_CDB: + iwl_trans->hw_rf_id = (IWL_CFG_RF_TYPE_HR1 << 12); + break; + case REG_CRF_ID_TYPE_HR_CDB: + iwl_trans->hw_rf_id = (IWL_CFG_RF_TYPE_HR2 << 12); + break; + case REG_CRF_ID_TYPE_GF: + iwl_trans->hw_rf_id = (IWL_CFG_RF_TYPE_GF << 12); + break; + case REG_CRF_ID_TYPE_MR: + iwl_trans->hw_rf_id = (IWL_CFG_RF_TYPE_MR << 12); + break; + case REG_CRF_ID_TYPE_FM: + iwl_trans->hw_rf_id = (IWL_CFG_RF_TYPE_FM << 12); + break; + default: + ret = -EIO; + IWL_ERR(iwl_trans, + "Can find a correct rfid for crf id 0x%x\n", + REG_CRF_ID_TYPE(val)); + goto out_release; + + } + + /* Set CDB capabilities */ + if (cdb & BIT(4)) { + iwl_trans->hw_rf_id += BIT(28); + IWL_INFO(iwl_trans, "Adding cdb to rf id\n"); + } + + IWL_INFO(iwl_trans, "Detected RF 0x%x from crf id 0x%x\n", + iwl_trans->hw_rf_id, REG_CRF_ID_TYPE(val)); + +out_release: + iwl_trans_release_nic_access(iwl_trans); + +out: + return ret; +} + +/* PCI registers */ +#define PCI_CFG_RETRY_TIMEOUT 0x041 + +static const struct iwl_dev_info * +iwl_pci_find_dev_info(u16 device, u16 subsystem_device, + u16 mac_type, u8 mac_step, + u16 rf_type, u8 cdb, u8 jacket, u8 rf_id, u8 no_160, u8 cores) +{ + int num_devices = ARRAY_SIZE(iwl_dev_info_table); + int i; + + if (!num_devices) + return NULL; + + for (i = num_devices - 1; i >= 0; i--) { + const struct iwl_dev_info *dev_info = &iwl_dev_info_table[i]; + + if (dev_info->device != (u16)IWL_CFG_ANY && + dev_info->device != device) + continue; + + if (dev_info->subdevice != (u16)IWL_CFG_ANY && + dev_info->subdevice != subsystem_device) + continue; + + if (dev_info->mac_type != (u16)IWL_CFG_ANY && + dev_info->mac_type != mac_type) + continue; + + if (dev_info->mac_step != (u8)IWL_CFG_ANY && + dev_info->mac_step != mac_step) + continue; + + if (dev_info->rf_type != (u16)IWL_CFG_ANY && + dev_info->rf_type != rf_type) + continue; + + if (dev_info->cdb != (u8)IWL_CFG_ANY && + dev_info->cdb != cdb) + continue; + + if (dev_info->jacket != (u8)IWL_CFG_ANY && + dev_info->jacket != jacket) + continue; + + if (dev_info->rf_id != (u8)IWL_CFG_ANY && + dev_info->rf_id != rf_id) + continue; + + if (dev_info->no_160 != (u8)IWL_CFG_ANY && + dev_info->no_160 != no_160) + continue; + + if (dev_info->cores != (u8)IWL_CFG_ANY && + dev_info->cores != cores) + continue; + + return dev_info; + } + + return NULL; +} + +static int iwl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + const struct iwl_cfg_trans_params *trans; + const struct iwl_cfg *cfg_7265d __maybe_unused = NULL; + const struct iwl_dev_info *dev_info; + struct iwl_trans *iwl_trans; + struct iwl_trans_pcie *trans_pcie; + int ret; + const struct iwl_cfg *cfg; + + trans = (void *)(ent->driver_data & ~TRANS_CFG_MARKER); + + /* + * This is needed for backwards compatibility with the old + * tables, so we don't need to change all the config structs + * at the same time. The cfg is used to compare with the old + * full cfg structs. + */ + cfg = (void *)(ent->driver_data & ~TRANS_CFG_MARKER); + + /* make sure trans is the first element in iwl_cfg */ + BUILD_BUG_ON(offsetof(struct iwl_cfg, trans)); + + iwl_trans = iwl_trans_pcie_alloc(pdev, ent, trans); + if (IS_ERR(iwl_trans)) + return PTR_ERR(iwl_trans); + + trans_pcie = IWL_TRANS_GET_PCIE_TRANS(iwl_trans); + + /* + * Let's try to grab NIC access early here. Sometimes, NICs may + * fail to initialize, and if that happens it's better if we see + * issues early on (and can reprobe, per the logic inside), than + * first trying to load the firmware etc. and potentially only + * detecting any problems when the first interface is brought up. + */ + ret = iwl_pcie_prepare_card_hw(iwl_trans); + if (!ret) { + ret = iwl_finish_nic_init(iwl_trans); + if (ret) + goto out_free_trans; + if (iwl_trans_grab_nic_access(iwl_trans)) { + /* all good */ + iwl_trans_release_nic_access(iwl_trans); + } else { + ret = -EIO; + goto out_free_trans; + } + } + + iwl_trans->hw_rf_id = iwl_read32(iwl_trans, CSR_HW_RF_ID); + + /* + * The RF_ID is set to zero in blank OTP so read version to + * extract the RF_ID. + * This is relevant only for family 9000 and up. + */ + if (iwl_trans->trans_cfg->rf_id && + iwl_trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_9000 && + !CSR_HW_RFID_TYPE(iwl_trans->hw_rf_id) && get_crf_id(iwl_trans)) { + ret = -EINVAL; + goto out_free_trans; + } + + dev_info = iwl_pci_find_dev_info(pdev->device, pdev->subsystem_device, + CSR_HW_REV_TYPE(iwl_trans->hw_rev), + iwl_trans->hw_rev_step, + CSR_HW_RFID_TYPE(iwl_trans->hw_rf_id), + CSR_HW_RFID_IS_CDB(iwl_trans->hw_rf_id), + CSR_HW_RFID_IS_JACKET(iwl_trans->hw_rf_id), + IWL_SUBDEVICE_RF_ID(pdev->subsystem_device), + IWL_SUBDEVICE_NO_160(pdev->subsystem_device), + IWL_SUBDEVICE_CORES(pdev->subsystem_device)); + + if (dev_info) { + iwl_trans->cfg = dev_info->cfg; + iwl_trans->name = dev_info->name; + } + +#if IS_ENABLED(CONFIG_IWLMVM) + + /* + * Workaround for problematic SnJ device: sometimes when + * certain RF modules are connected to SnJ, the device ID + * changes to QnJ's ID. So we are using QnJ's trans_cfg until + * here. But if we detect that the MAC type is actually SnJ, + * we should switch to it here to avoid problems later. + */ + if (CSR_HW_REV_TYPE(iwl_trans->hw_rev) == IWL_CFG_MAC_TYPE_SNJ) + iwl_trans->trans_cfg = &iwl_so_trans_cfg; + + /* + * special-case 7265D, it has the same PCI IDs. + * + * Note that because we already pass the cfg to the transport above, + * all the parameters that the transport uses must, until that is + * changed, be identical to the ones in the 7265D configuration. + */ + if (cfg == &iwl7265_2ac_cfg) + cfg_7265d = &iwl7265d_2ac_cfg; + else if (cfg == &iwl7265_2n_cfg) + cfg_7265d = &iwl7265d_2n_cfg; + else if (cfg == &iwl7265_n_cfg) + cfg_7265d = &iwl7265d_n_cfg; + if (cfg_7265d && + (iwl_trans->hw_rev & CSR_HW_REV_TYPE_MSK) == CSR_HW_REV_TYPE_7265D) + iwl_trans->cfg = cfg_7265d; + + /* + * This is a hack to switch from Qu B0 to Qu C0. We need to + * do this for all cfgs that use Qu B0, except for those using + * Jf, which have already been moved to the new table. The + * rest must be removed once we convert Qu with Hr as well. + */ + if (iwl_trans->hw_rev == CSR_HW_REV_TYPE_QU_C0) { + if (iwl_trans->cfg == &iwl_ax201_cfg_qu_hr) + iwl_trans->cfg = &iwl_ax201_cfg_qu_c0_hr_b0; + else if (iwl_trans->cfg == &killer1650s_2ax_cfg_qu_b0_hr_b0) + iwl_trans->cfg = &killer1650s_2ax_cfg_qu_c0_hr_b0; + else if (iwl_trans->cfg == &killer1650i_2ax_cfg_qu_b0_hr_b0) + iwl_trans->cfg = &killer1650i_2ax_cfg_qu_c0_hr_b0; + } + + /* same thing for QuZ... */ + if (iwl_trans->hw_rev == CSR_HW_REV_TYPE_QUZ) { + if (iwl_trans->cfg == &iwl_ax201_cfg_qu_hr) + iwl_trans->cfg = &iwl_ax201_cfg_quz_hr; + else if (iwl_trans->cfg == &killer1650s_2ax_cfg_qu_b0_hr_b0) + iwl_trans->cfg = &iwl_ax1650s_cfg_quz_hr; + else if (iwl_trans->cfg == &killer1650i_2ax_cfg_qu_b0_hr_b0) + iwl_trans->cfg = &iwl_ax1650i_cfg_quz_hr; + } + +#endif + /* + * If we didn't set the cfg yet, the PCI ID table entry should have + * been a full config - if yes, use it, otherwise fail. + */ + if (!iwl_trans->cfg) { + if (ent->driver_data & TRANS_CFG_MARKER) { + pr_err("No config found for PCI dev %04x/%04x, rev=0x%x, rfid=0x%x\n", + pdev->device, pdev->subsystem_device, + iwl_trans->hw_rev, iwl_trans->hw_rf_id); + ret = -EINVAL; + goto out_free_trans; + } + iwl_trans->cfg = cfg; + } + + /* if we don't have a name yet, copy name from the old cfg */ + if (!iwl_trans->name) + iwl_trans->name = iwl_trans->cfg->name; + + if (iwl_trans->trans_cfg->mq_rx_supported) { + if (WARN_ON(!iwl_trans->cfg->num_rbds)) { + ret = -EINVAL; + goto out_free_trans; + } + trans_pcie->num_rx_bufs = iwl_trans->cfg->num_rbds; + } else { + trans_pcie->num_rx_bufs = RX_QUEUE_SIZE; + } + + ret = iwl_trans_init(iwl_trans); + if (ret) + goto out_free_trans; + + pci_set_drvdata(pdev, iwl_trans); + + /* try to get ownership so that we'll know if we don't own it */ + iwl_pcie_prepare_card_hw(iwl_trans); + + iwl_trans->drv = iwl_drv_start(iwl_trans); + + if (IS_ERR(iwl_trans->drv)) { + ret = PTR_ERR(iwl_trans->drv); + goto out_free_trans; + } + + /* register transport layer debugfs here */ + iwl_trans_pcie_dbgfs_register(iwl_trans); + + return 0; + +out_free_trans: + iwl_trans_pcie_free(iwl_trans); + return ret; +} + +static void iwl_pci_remove(struct pci_dev *pdev) +{ + struct iwl_trans *trans = pci_get_drvdata(pdev); + + if (!trans) + return; + + iwl_drv_stop(trans->drv); + + iwl_trans_pcie_free(trans); +} + +#ifdef CONFIG_PM_SLEEP + +static int iwl_pci_suspend(struct device *device) +{ + /* Before you put code here, think about WoWLAN. You cannot check here + * whether WoWLAN is enabled or not, and your code will run even if + * WoWLAN is enabled - don't kill the NIC, someone may need it in Sx. + */ + + return 0; +} + +static int iwl_pci_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct iwl_trans *trans = pci_get_drvdata(pdev); + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + /* Before you put code here, think about WoWLAN. You cannot check here + * whether WoWLAN is enabled or not, and your code will run even if + * WoWLAN is enabled - the NIC may be alive. + */ + + /* + * We disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state. + */ + pci_write_config_byte(pdev, PCI_CFG_RETRY_TIMEOUT, 0x00); + + if (!trans->op_mode) + return 0; + + /* In WOWLAN, let iwl_trans_pcie_d3_resume do the rest of the work */ + if (test_bit(STATUS_DEVICE_ENABLED, &trans->status)) + return 0; + + /* reconfigure the MSI-X mapping to get the correct IRQ for rfkill */ + iwl_pcie_conf_msix_hw(trans_pcie); + + /* + * Enable rfkill interrupt (in order to keep track of the rfkill + * status). Must be locked to avoid processing a possible rfkill + * interrupt while in iwl_pcie_check_hw_rf_kill(). + */ + mutex_lock(&trans_pcie->mutex); + iwl_enable_rfkill_int(trans); + iwl_pcie_check_hw_rf_kill(trans); + mutex_unlock(&trans_pcie->mutex); + + return 0; +} + +static const struct dev_pm_ops iwl_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(iwl_pci_suspend, + iwl_pci_resume) +}; + +#define IWL_PM_OPS (&iwl_dev_pm_ops) + +#else /* CONFIG_PM_SLEEP */ + +#define IWL_PM_OPS NULL + +#endif /* CONFIG_PM_SLEEP */ + +static struct pci_driver iwl_pci_driver = { + .name = DRV_NAME, + .id_table = iwl_hw_card_ids, + .probe = iwl_pci_probe, + .remove = iwl_pci_remove, + .driver.pm = IWL_PM_OPS, +}; + +int __must_check iwl_pci_register_driver(void) +{ + int ret; + ret = pci_register_driver(&iwl_pci_driver); + if (ret) + pr_err("Unable to initialize PCI module\n"); + + return ret; +} + +void iwl_pci_unregister_driver(void) +{ + pci_unregister_driver(&iwl_pci_driver); +} diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/internal.h b/drivers/net/wireless/intel/iwlwifi/pcie/internal.h new file mode 100644 index 000000000..2ec4ee8ab --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/pcie/internal.h @@ -0,0 +1,843 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2003-2015, 2018-2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#ifndef __iwl_trans_int_pcie_h__ +#define __iwl_trans_int_pcie_h__ + +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/skbuff.h> +#include <linux/wait.h> +#include <linux/pci.h> +#include <linux/timer.h> +#include <linux/cpu.h> + +#include "iwl-fh.h" +#include "iwl-csr.h" +#include "iwl-trans.h" +#include "iwl-debug.h" +#include "iwl-io.h" +#include "iwl-op-mode.h" +#include "iwl-drv.h" +#include "queue/tx.h" + +/* + * RX related structures and functions + */ +#define RX_NUM_QUEUES 1 +#define RX_POST_REQ_ALLOC 2 +#define RX_CLAIM_REQ_ALLOC 8 +#define RX_PENDING_WATERMARK 16 +#define FIRST_RX_QUEUE 512 + +struct iwl_host_cmd; + +/*This file includes the declaration that are internal to the + * trans_pcie layer */ + +/** + * struct iwl_rx_mem_buffer + * @page_dma: bus address of rxb page + * @page: driver's pointer to the rxb page + * @list: list entry for the membuffer + * @invalid: rxb is in driver ownership - not owned by HW + * @vid: index of this rxb in the global table + * @offset: indicates which offset of the page (in bytes) + * this buffer uses (if multiple RBs fit into one page) + */ +struct iwl_rx_mem_buffer { + dma_addr_t page_dma; + struct page *page; + struct list_head list; + u32 offset; + u16 vid; + bool invalid; +}; + +/** + * struct isr_statistics - interrupt statistics + * + */ +struct isr_statistics { + u32 hw; + u32 sw; + u32 err_code; + u32 sch; + u32 alive; + u32 rfkill; + u32 ctkill; + u32 wakeup; + u32 rx; + u32 tx; + u32 unhandled; +}; + +/** + * struct iwl_rx_transfer_desc - transfer descriptor + * @addr: ptr to free buffer start address + * @rbid: unique tag of the buffer + * @reserved: reserved + */ +struct iwl_rx_transfer_desc { + __le16 rbid; + __le16 reserved[3]; + __le64 addr; +} __packed; + +#define IWL_RX_CD_FLAGS_FRAGMENTED BIT(0) + +/** + * struct iwl_rx_completion_desc - completion descriptor + * @reserved1: reserved + * @rbid: unique tag of the received buffer + * @flags: flags (0: fragmented, all others: reserved) + * @reserved2: reserved + */ +struct iwl_rx_completion_desc { + __le32 reserved1; + __le16 rbid; + u8 flags; + u8 reserved2[25]; +} __packed; + +/** + * struct iwl_rx_completion_desc_bz - Bz completion descriptor + * @rbid: unique tag of the received buffer + * @flags: flags (0: fragmented, all others: reserved) + * @reserved: reserved + */ +struct iwl_rx_completion_desc_bz { + __le16 rbid; + u8 flags; + u8 reserved[1]; +} __packed; + +/** + * struct iwl_rxq - Rx queue + * @id: queue index + * @bd: driver's pointer to buffer of receive buffer descriptors (rbd). + * Address size is 32 bit in pre-9000 devices and 64 bit in 9000 devices. + * In AX210 devices it is a pointer to a list of iwl_rx_transfer_desc's + * @bd_dma: bus address of buffer of receive buffer descriptors (rbd) + * @used_bd: driver's pointer to buffer of used receive buffer descriptors (rbd) + * @used_bd_dma: physical address of buffer of used receive buffer descriptors (rbd) + * @read: Shared index to newest available Rx buffer + * @write: Shared index to oldest written Rx packet + * @free_count: Number of pre-allocated buffers in rx_free + * @used_count: Number of RBDs handled to allocator to use for allocation + * @write_actual: + * @rx_free: list of RBDs with allocated RB ready for use + * @rx_used: list of RBDs with no RB attached + * @need_update: flag to indicate we need to update read/write index + * @rb_stts: driver's pointer to receive buffer status + * @rb_stts_dma: bus address of receive buffer status + * @lock: + * @queue: actual rx queue. Not used for multi-rx queue. + * @next_rb_is_fragment: indicates that the previous RB that we handled set + * the fragmented flag, so the next one is still another fragment + * + * NOTE: rx_free and rx_used are used as a FIFO for iwl_rx_mem_buffers + */ +struct iwl_rxq { + int id; + void *bd; + dma_addr_t bd_dma; + void *used_bd; + dma_addr_t used_bd_dma; + u32 read; + u32 write; + u32 free_count; + u32 used_count; + u32 write_actual; + u32 queue_size; + struct list_head rx_free; + struct list_head rx_used; + bool need_update, next_rb_is_fragment; + void *rb_stts; + dma_addr_t rb_stts_dma; + spinlock_t lock; + struct napi_struct napi; + struct iwl_rx_mem_buffer *queue[RX_QUEUE_SIZE]; +}; + +/** + * struct iwl_rb_allocator - Rx allocator + * @req_pending: number of requests the allcator had not processed yet + * @req_ready: number of requests honored and ready for claiming + * @rbd_allocated: RBDs with pages allocated and ready to be handled to + * the queue. This is a list of &struct iwl_rx_mem_buffer + * @rbd_empty: RBDs with no page attached for allocator use. This is a list + * of &struct iwl_rx_mem_buffer + * @lock: protects the rbd_allocated and rbd_empty lists + * @alloc_wq: work queue for background calls + * @rx_alloc: work struct for background calls + */ +struct iwl_rb_allocator { + atomic_t req_pending; + atomic_t req_ready; + struct list_head rbd_allocated; + struct list_head rbd_empty; + spinlock_t lock; + struct workqueue_struct *alloc_wq; + struct work_struct rx_alloc; +}; + +/** + * iwl_get_closed_rb_stts - get closed rb stts from different structs + * @rxq - the rxq to get the rb stts from + */ +static inline __le16 iwl_get_closed_rb_stts(struct iwl_trans *trans, + struct iwl_rxq *rxq) +{ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + __le16 *rb_stts = rxq->rb_stts; + + return READ_ONCE(*rb_stts); + } else { + struct iwl_rb_status *rb_stts = rxq->rb_stts; + + return READ_ONCE(rb_stts->closed_rb_num); + } +} + +#ifdef CONFIG_IWLWIFI_DEBUGFS +/** + * enum iwl_fw_mon_dbgfs_state - the different states of the monitor_data + * debugfs file + * + * @IWL_FW_MON_DBGFS_STATE_CLOSED: the file is closed. + * @IWL_FW_MON_DBGFS_STATE_OPEN: the file is open. + * @IWL_FW_MON_DBGFS_STATE_DISABLED: the file is disabled, once this state is + * set the file can no longer be used. + */ +enum iwl_fw_mon_dbgfs_state { + IWL_FW_MON_DBGFS_STATE_CLOSED, + IWL_FW_MON_DBGFS_STATE_OPEN, + IWL_FW_MON_DBGFS_STATE_DISABLED, +}; +#endif + +/** + * enum iwl_shared_irq_flags - level of sharing for irq + * @IWL_SHARED_IRQ_NON_RX: interrupt vector serves non rx causes. + * @IWL_SHARED_IRQ_FIRST_RSS: interrupt vector serves first RSS queue. + */ +enum iwl_shared_irq_flags { + IWL_SHARED_IRQ_NON_RX = BIT(0), + IWL_SHARED_IRQ_FIRST_RSS = BIT(1), +}; + +/** + * enum iwl_image_response_code - image response values + * @IWL_IMAGE_RESP_DEF: the default value of the register + * @IWL_IMAGE_RESP_SUCCESS: iml was read successfully + * @IWL_IMAGE_RESP_FAIL: iml reading failed + */ +enum iwl_image_response_code { + IWL_IMAGE_RESP_DEF = 0, + IWL_IMAGE_RESP_SUCCESS = 1, + IWL_IMAGE_RESP_FAIL = 2, +}; + +/** + * struct cont_rec: continuous recording data structure + * @prev_wr_ptr: the last address that was read in monitor_data + * debugfs file + * @prev_wrap_cnt: the wrap count that was used during the last read in + * monitor_data debugfs file + * @state: the state of monitor_data debugfs file as described + * in &iwl_fw_mon_dbgfs_state enum + * @mutex: locked while reading from monitor_data debugfs file + */ +#ifdef CONFIG_IWLWIFI_DEBUGFS +struct cont_rec { + u32 prev_wr_ptr; + u32 prev_wrap_cnt; + u8 state; + /* Used to sync monitor_data debugfs file with driver unload flow */ + struct mutex mutex; +}; +#endif + +enum iwl_pcie_fw_reset_state { + FW_RESET_IDLE, + FW_RESET_REQUESTED, + FW_RESET_OK, + FW_RESET_ERROR, +}; + +/** + * enum wl_pcie_imr_status - imr dma transfer state + * @IMR_D2S_IDLE: default value of the dma transfer + * @IMR_D2S_REQUESTED: dma transfer requested + * @IMR_D2S_COMPLETED: dma transfer completed + * @IMR_D2S_ERROR: dma transfer error + */ +enum iwl_pcie_imr_status { + IMR_D2S_IDLE, + IMR_D2S_REQUESTED, + IMR_D2S_COMPLETED, + IMR_D2S_ERROR, +}; + +/** + * struct iwl_trans_pcie - PCIe transport specific data + * @rxq: all the RX queue data + * @rx_pool: initial pool of iwl_rx_mem_buffer for all the queues + * @global_table: table mapping received VID from hw to rxb + * @rba: allocator for RX replenishing + * @ctxt_info: context information for FW self init + * @ctxt_info_gen3: context information for gen3 devices + * @prph_info: prph info for self init + * @prph_scratch: prph scratch for self init + * @ctxt_info_dma_addr: dma addr of context information + * @prph_info_dma_addr: dma addr of prph info + * @prph_scratch_dma_addr: dma addr of prph scratch + * @ctxt_info_dma_addr: dma addr of context information + * @init_dram: DRAM data of firmware image (including paging). + * Context information addresses will be taken from here. + * This is driver's local copy for keeping track of size and + * count for allocating and freeing the memory. + * @iml: image loader image virtual address + * @iml_dma_addr: image loader image DMA address + * @trans: pointer to the generic transport area + * @scd_base_addr: scheduler sram base address in SRAM + * @kw: keep warm address + * @pnvm_dram: DRAM area that contains the PNVM data + * @pci_dev: basic pci-network driver stuff + * @hw_base: pci hardware address support + * @ucode_write_complete: indicates that the ucode has been copied. + * @ucode_write_waitq: wait queue for uCode load + * @cmd_queue - command queue number + * @def_rx_queue - default rx queue number + * @rx_buf_size: Rx buffer size + * @scd_set_active: should the transport configure the SCD for HCMD queue + * @rx_page_order: page order for receive buffer size + * @rx_buf_bytes: RX buffer (RB) size in bytes + * @reg_lock: protect hw register access + * @mutex: to protect stop_device / start_fw / start_hw + * @cmd_in_flight: true when we have a host command in flight +#ifdef CONFIG_IWLWIFI_DEBUGFS + * @fw_mon_data: fw continuous recording data +#endif + * @msix_entries: array of MSI-X entries + * @msix_enabled: true if managed to enable MSI-X + * @shared_vec_mask: the type of causes the shared vector handles + * (see iwl_shared_irq_flags). + * @alloc_vecs: the number of interrupt vectors allocated by the OS + * @def_irq: default irq for non rx causes + * @fh_init_mask: initial unmasked fh causes + * @hw_init_mask: initial unmasked hw causes + * @fh_mask: current unmasked fh causes + * @hw_mask: current unmasked hw causes + * @in_rescan: true if we have triggered a device rescan + * @base_rb_stts: base virtual address of receive buffer status for all queues + * @base_rb_stts_dma: base physical address of receive buffer status + * @supported_dma_mask: DMA mask to validate the actual address against, + * will be DMA_BIT_MASK(11) or DMA_BIT_MASK(12) depending on the device + * @alloc_page_lock: spinlock for the page allocator + * @alloc_page: allocated page to still use parts of + * @alloc_page_used: how much of the allocated page was already used (bytes) + * @imr_status: imr dma state machine + * @wait_queue_head_t: imr wait queue for dma completion + * @rf_name: name/version of the CRF, if any + */ +struct iwl_trans_pcie { + struct iwl_rxq *rxq; + struct iwl_rx_mem_buffer *rx_pool; + struct iwl_rx_mem_buffer **global_table; + struct iwl_rb_allocator rba; + union { + struct iwl_context_info *ctxt_info; + struct iwl_context_info_gen3 *ctxt_info_gen3; + }; + struct iwl_prph_info *prph_info; + struct iwl_prph_scratch *prph_scratch; + void *iml; + dma_addr_t ctxt_info_dma_addr; + dma_addr_t prph_info_dma_addr; + dma_addr_t prph_scratch_dma_addr; + dma_addr_t iml_dma_addr; + struct iwl_trans *trans; + + struct net_device napi_dev; + + /* INT ICT Table */ + __le32 *ict_tbl; + dma_addr_t ict_tbl_dma; + int ict_index; + bool use_ict; + bool is_down, opmode_down; + s8 debug_rfkill; + struct isr_statistics isr_stats; + + spinlock_t irq_lock; + struct mutex mutex; + u32 inta_mask; + u32 scd_base_addr; + struct iwl_dma_ptr kw; + + struct iwl_dram_data pnvm_dram; + struct iwl_dram_data reduce_power_dram; + + struct iwl_txq *txq_memory; + + /* PCI bus related data */ + struct pci_dev *pci_dev; + u8 __iomem *hw_base; + + bool ucode_write_complete; + bool sx_complete; + wait_queue_head_t ucode_write_waitq; + wait_queue_head_t sx_waitq; + + u8 def_rx_queue; + u8 n_no_reclaim_cmds; + u8 no_reclaim_cmds[MAX_NO_RECLAIM_CMDS]; + u16 num_rx_bufs; + + enum iwl_amsdu_size rx_buf_size; + bool scd_set_active; + bool pcie_dbg_dumped_once; + u32 rx_page_order; + u32 rx_buf_bytes; + u32 supported_dma_mask; + + /* allocator lock for the two values below */ + spinlock_t alloc_page_lock; + struct page *alloc_page; + u32 alloc_page_used; + + /*protect hw register */ + spinlock_t reg_lock; + bool cmd_hold_nic_awake; + +#ifdef CONFIG_IWLWIFI_DEBUGFS + struct cont_rec fw_mon_data; +#endif + + struct msix_entry msix_entries[IWL_MAX_RX_HW_QUEUES]; + bool msix_enabled; + u8 shared_vec_mask; + u32 alloc_vecs; + u32 def_irq; + u32 fh_init_mask; + u32 hw_init_mask; + u32 fh_mask; + u32 hw_mask; + cpumask_t affinity_mask[IWL_MAX_RX_HW_QUEUES]; + u16 tx_cmd_queue_size; + bool in_rescan; + + void *base_rb_stts; + dma_addr_t base_rb_stts_dma; + + bool fw_reset_handshake; + enum iwl_pcie_fw_reset_state fw_reset_state; + wait_queue_head_t fw_reset_waitq; + enum iwl_pcie_imr_status imr_status; + wait_queue_head_t imr_waitq; + char rf_name[32]; +}; + +static inline struct iwl_trans_pcie * +IWL_TRANS_GET_PCIE_TRANS(struct iwl_trans *trans) +{ + return (void *)trans->trans_specific; +} + +static inline void iwl_pcie_clear_irq(struct iwl_trans *trans, int queue) +{ + /* + * Before sending the interrupt the HW disables it to prevent + * a nested interrupt. This is done by writing 1 to the corresponding + * bit in the mask register. After handling the interrupt, it should be + * re-enabled by clearing this bit. This register is defined as + * write 1 clear (W1C) register, meaning that it's being clear + * by writing 1 to the bit. + */ + iwl_write32(trans, CSR_MSIX_AUTOMASK_ST_AD, BIT(queue)); +} + +static inline struct iwl_trans * +iwl_trans_pcie_get_trans(struct iwl_trans_pcie *trans_pcie) +{ + return container_of((void *)trans_pcie, struct iwl_trans, + trans_specific); +} + +/* + * Convention: trans API functions: iwl_trans_pcie_XXX + * Other functions: iwl_pcie_XXX + */ +struct iwl_trans +*iwl_trans_pcie_alloc(struct pci_dev *pdev, + const struct pci_device_id *ent, + const struct iwl_cfg_trans_params *cfg_trans); +void iwl_trans_pcie_free(struct iwl_trans *trans); + +bool __iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans); +#define _iwl_trans_pcie_grab_nic_access(trans) \ + __cond_lock(nic_access_nobh, \ + likely(__iwl_trans_pcie_grab_nic_access(trans))) + +/***************************************************** +* RX +******************************************************/ +int iwl_pcie_rx_init(struct iwl_trans *trans); +int iwl_pcie_gen2_rx_init(struct iwl_trans *trans); +irqreturn_t iwl_pcie_msix_isr(int irq, void *data); +irqreturn_t iwl_pcie_irq_handler(int irq, void *dev_id); +irqreturn_t iwl_pcie_irq_msix_handler(int irq, void *dev_id); +irqreturn_t iwl_pcie_irq_rx_msix_handler(int irq, void *dev_id); +int iwl_pcie_rx_stop(struct iwl_trans *trans); +void iwl_pcie_rx_free(struct iwl_trans *trans); +void iwl_pcie_free_rbs_pool(struct iwl_trans *trans); +void iwl_pcie_rx_init_rxb_lists(struct iwl_rxq *rxq); +void iwl_pcie_rx_napi_sync(struct iwl_trans *trans); +void iwl_pcie_rxq_alloc_rbs(struct iwl_trans *trans, gfp_t priority, + struct iwl_rxq *rxq); + +/***************************************************** +* ICT - interrupt handling +******************************************************/ +irqreturn_t iwl_pcie_isr(int irq, void *data); +int iwl_pcie_alloc_ict(struct iwl_trans *trans); +void iwl_pcie_free_ict(struct iwl_trans *trans); +void iwl_pcie_reset_ict(struct iwl_trans *trans); +void iwl_pcie_disable_ict(struct iwl_trans *trans); + +/***************************************************** +* TX / HCMD +******************************************************/ +int iwl_pcie_tx_init(struct iwl_trans *trans); +void iwl_pcie_tx_start(struct iwl_trans *trans, u32 scd_base_addr); +int iwl_pcie_tx_stop(struct iwl_trans *trans); +void iwl_pcie_tx_free(struct iwl_trans *trans); +bool iwl_trans_pcie_txq_enable(struct iwl_trans *trans, int queue, u16 ssn, + const struct iwl_trans_txq_scd_cfg *cfg, + unsigned int wdg_timeout); +void iwl_trans_pcie_txq_disable(struct iwl_trans *trans, int queue, + bool configure_scd); +void iwl_trans_pcie_txq_set_shared_mode(struct iwl_trans *trans, u32 txq_id, + bool shared_mode); +int iwl_trans_pcie_tx(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_device_tx_cmd *dev_cmd, int txq_id); +void iwl_pcie_txq_check_wrptrs(struct iwl_trans *trans); +int iwl_trans_pcie_send_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd); +void iwl_pcie_hcmd_complete(struct iwl_trans *trans, + struct iwl_rx_cmd_buffer *rxb); +void iwl_trans_pcie_tx_reset(struct iwl_trans *trans); + +/***************************************************** +* Error handling +******************************************************/ +void iwl_pcie_dump_csr(struct iwl_trans *trans); + +/***************************************************** +* Helpers +******************************************************/ +static inline void _iwl_disable_interrupts(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + clear_bit(STATUS_INT_ENABLED, &trans->status); + if (!trans_pcie->msix_enabled) { + /* disable interrupts from uCode/NIC to host */ + iwl_write32(trans, CSR_INT_MASK, 0x00000000); + + /* acknowledge/clear/reset any interrupts still pending + * from uCode or flow handler (Rx/Tx DMA) */ + iwl_write32(trans, CSR_INT, 0xffffffff); + iwl_write32(trans, CSR_FH_INT_STATUS, 0xffffffff); + } else { + /* disable all the interrupt we might use */ + iwl_write32(trans, CSR_MSIX_FH_INT_MASK_AD, + trans_pcie->fh_init_mask); + iwl_write32(trans, CSR_MSIX_HW_INT_MASK_AD, + trans_pcie->hw_init_mask); + } + IWL_DEBUG_ISR(trans, "Disabled interrupts\n"); +} + +static inline int iwl_pcie_get_num_sections(const struct fw_img *fw, + int start) +{ + int i = 0; + + while (start < fw->num_sec && + fw->sec[start].offset != CPU1_CPU2_SEPARATOR_SECTION && + fw->sec[start].offset != PAGING_SEPARATOR_SECTION) { + start++; + i++; + } + + return i; +} + +static inline void iwl_pcie_ctxt_info_free_fw_img(struct iwl_trans *trans) +{ + struct iwl_self_init_dram *dram = &trans->init_dram; + int i; + + if (!dram->fw) { + WARN_ON(dram->fw_cnt); + return; + } + + for (i = 0; i < dram->fw_cnt; i++) + dma_free_coherent(trans->dev, dram->fw[i].size, + dram->fw[i].block, dram->fw[i].physical); + + kfree(dram->fw); + dram->fw_cnt = 0; + dram->fw = NULL; +} + +static inline void iwl_disable_interrupts(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + spin_lock_bh(&trans_pcie->irq_lock); + _iwl_disable_interrupts(trans); + spin_unlock_bh(&trans_pcie->irq_lock); +} + +static inline void _iwl_enable_interrupts(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + IWL_DEBUG_ISR(trans, "Enabling interrupts\n"); + set_bit(STATUS_INT_ENABLED, &trans->status); + if (!trans_pcie->msix_enabled) { + trans_pcie->inta_mask = CSR_INI_SET_MASK; + iwl_write32(trans, CSR_INT_MASK, trans_pcie->inta_mask); + } else { + /* + * fh/hw_mask keeps all the unmasked causes. + * Unlike msi, in msix cause is enabled when it is unset. + */ + trans_pcie->hw_mask = trans_pcie->hw_init_mask; + trans_pcie->fh_mask = trans_pcie->fh_init_mask; + iwl_write32(trans, CSR_MSIX_FH_INT_MASK_AD, + ~trans_pcie->fh_mask); + iwl_write32(trans, CSR_MSIX_HW_INT_MASK_AD, + ~trans_pcie->hw_mask); + } +} + +static inline void iwl_enable_interrupts(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + spin_lock_bh(&trans_pcie->irq_lock); + _iwl_enable_interrupts(trans); + spin_unlock_bh(&trans_pcie->irq_lock); +} +static inline void iwl_enable_hw_int_msk_msix(struct iwl_trans *trans, u32 msk) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + iwl_write32(trans, CSR_MSIX_HW_INT_MASK_AD, ~msk); + trans_pcie->hw_mask = msk; +} + +static inline void iwl_enable_fh_int_msk_msix(struct iwl_trans *trans, u32 msk) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + iwl_write32(trans, CSR_MSIX_FH_INT_MASK_AD, ~msk); + trans_pcie->fh_mask = msk; +} + +static inline void iwl_enable_fw_load_int(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + IWL_DEBUG_ISR(trans, "Enabling FW load interrupt\n"); + if (!trans_pcie->msix_enabled) { + trans_pcie->inta_mask = CSR_INT_BIT_FH_TX; + iwl_write32(trans, CSR_INT_MASK, trans_pcie->inta_mask); + } else { + iwl_write32(trans, CSR_MSIX_HW_INT_MASK_AD, + trans_pcie->hw_init_mask); + iwl_enable_fh_int_msk_msix(trans, + MSIX_FH_INT_CAUSES_D2S_CH0_NUM); + } +} + +static inline void iwl_enable_fw_load_int_ctx_info(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + IWL_DEBUG_ISR(trans, "Enabling ALIVE interrupt only\n"); + + if (!trans_pcie->msix_enabled) { + /* + * When we'll receive the ALIVE interrupt, the ISR will call + * iwl_enable_fw_load_int_ctx_info again to set the ALIVE + * interrupt (which is not really needed anymore) but also the + * RX interrupt which will allow us to receive the ALIVE + * notification (which is Rx) and continue the flow. + */ + trans_pcie->inta_mask = CSR_INT_BIT_ALIVE | CSR_INT_BIT_FH_RX; + iwl_write32(trans, CSR_INT_MASK, trans_pcie->inta_mask); + } else { + iwl_enable_hw_int_msk_msix(trans, + MSIX_HW_INT_CAUSES_REG_ALIVE); + /* + * Leave all the FH causes enabled to get the ALIVE + * notification. + */ + iwl_enable_fh_int_msk_msix(trans, trans_pcie->fh_init_mask); + } +} + +static inline const char *queue_name(struct device *dev, + struct iwl_trans_pcie *trans_p, int i) +{ + if (trans_p->shared_vec_mask) { + int vec = trans_p->shared_vec_mask & + IWL_SHARED_IRQ_FIRST_RSS ? 1 : 0; + + if (i == 0) + return DRV_NAME ":shared_IRQ"; + + return devm_kasprintf(dev, GFP_KERNEL, + DRV_NAME ":queue_%d", i + vec); + } + if (i == 0) + return DRV_NAME ":default_queue"; + + if (i == trans_p->alloc_vecs - 1) + return DRV_NAME ":exception"; + + return devm_kasprintf(dev, GFP_KERNEL, + DRV_NAME ":queue_%d", i); +} + +static inline void iwl_enable_rfkill_int(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + IWL_DEBUG_ISR(trans, "Enabling rfkill interrupt\n"); + if (!trans_pcie->msix_enabled) { + trans_pcie->inta_mask = CSR_INT_BIT_RF_KILL; + iwl_write32(trans, CSR_INT_MASK, trans_pcie->inta_mask); + } else { + iwl_write32(trans, CSR_MSIX_FH_INT_MASK_AD, + trans_pcie->fh_init_mask); + iwl_enable_hw_int_msk_msix(trans, + MSIX_HW_INT_CAUSES_REG_RF_KILL); + } + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_9000) { + /* + * On 9000-series devices this bit isn't enabled by default, so + * when we power down the device we need set the bit to allow it + * to wake up the PCI-E bus for RF-kill interrupts. + */ + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_RFKILL_WAKE_L1A_EN); + } +} + +void iwl_pcie_handle_rfkill_irq(struct iwl_trans *trans, bool from_irq); + +static inline bool iwl_is_rfkill_set(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + lockdep_assert_held(&trans_pcie->mutex); + + if (trans_pcie->debug_rfkill == 1) + return true; + + return !(iwl_read32(trans, CSR_GP_CNTRL) & + CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW); +} + +static inline void __iwl_trans_pcie_set_bits_mask(struct iwl_trans *trans, + u32 reg, u32 mask, u32 value) +{ + u32 v; + +#ifdef CONFIG_IWLWIFI_DEBUG + WARN_ON_ONCE(value & ~mask); +#endif + + v = iwl_read32(trans, reg); + v &= ~mask; + v |= value; + iwl_write32(trans, reg, v); +} + +static inline void __iwl_trans_pcie_clear_bit(struct iwl_trans *trans, + u32 reg, u32 mask) +{ + __iwl_trans_pcie_set_bits_mask(trans, reg, mask, 0); +} + +static inline void __iwl_trans_pcie_set_bit(struct iwl_trans *trans, + u32 reg, u32 mask) +{ + __iwl_trans_pcie_set_bits_mask(trans, reg, mask, mask); +} + +static inline bool iwl_pcie_dbg_on(struct iwl_trans *trans) +{ + return (trans->dbg.dest_tlv || iwl_trans_dbg_ini_valid(trans)); +} + +void iwl_trans_pcie_rf_kill(struct iwl_trans *trans, bool state, bool from_irq); +void iwl_trans_pcie_dump_regs(struct iwl_trans *trans); + +#ifdef CONFIG_IWLWIFI_DEBUGFS +void iwl_trans_pcie_dbgfs_register(struct iwl_trans *trans); +#else +static inline void iwl_trans_pcie_dbgfs_register(struct iwl_trans *trans) { } +#endif + +void iwl_pcie_rx_allocator_work(struct work_struct *data); + +/* common functions that are used by gen2 transport */ +int iwl_pcie_gen2_apm_init(struct iwl_trans *trans); +void iwl_pcie_apm_config(struct iwl_trans *trans); +int iwl_pcie_prepare_card_hw(struct iwl_trans *trans); +void iwl_pcie_synchronize_irqs(struct iwl_trans *trans); +bool iwl_pcie_check_hw_rf_kill(struct iwl_trans *trans); +void iwl_trans_pcie_handle_stop_rfkill(struct iwl_trans *trans, + bool was_in_rfkill); +void iwl_pcie_apm_stop_master(struct iwl_trans *trans); +void iwl_pcie_conf_msix_hw(struct iwl_trans_pcie *trans_pcie); +int iwl_pcie_alloc_dma_ptr(struct iwl_trans *trans, + struct iwl_dma_ptr *ptr, size_t size); +void iwl_pcie_free_dma_ptr(struct iwl_trans *trans, struct iwl_dma_ptr *ptr); +void iwl_pcie_apply_destination(struct iwl_trans *trans); + +/* common functions that are used by gen3 transport */ +void iwl_pcie_alloc_fw_monitor(struct iwl_trans *trans, u8 max_power); + +/* transport gen 2 exported functions */ +int iwl_trans_pcie_gen2_start_fw(struct iwl_trans *trans, + const struct fw_img *fw, bool run_in_rfkill); +void iwl_trans_pcie_gen2_fw_alive(struct iwl_trans *trans, u32 scd_addr); +int iwl_trans_pcie_gen2_send_hcmd(struct iwl_trans *trans, + struct iwl_host_cmd *cmd); +void iwl_trans_pcie_gen2_stop_device(struct iwl_trans *trans); +void _iwl_trans_pcie_gen2_stop_device(struct iwl_trans *trans); +void iwl_pcie_d3_complete_suspend(struct iwl_trans *trans, + bool test, bool reset); +int iwl_pcie_gen2_enqueue_hcmd(struct iwl_trans *trans, + struct iwl_host_cmd *cmd); +int iwl_pcie_enqueue_hcmd(struct iwl_trans *trans, + struct iwl_host_cmd *cmd); +void iwl_trans_pcie_copy_imr_fh(struct iwl_trans *trans, + u32 dst_addr, u64 src_addr, u32 byte_cnt); +int iwl_trans_pcie_copy_imr(struct iwl_trans *trans, + u32 dst_addr, u64 src_addr, u32 byte_cnt); + +#endif /* __iwl_trans_int_pcie_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/rx.c b/drivers/net/wireless/intel/iwlwifi/pcie/rx.c new file mode 100644 index 000000000..91b73e7a4 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/pcie/rx.c @@ -0,0 +1,2388 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2003-2014, 2018-2023 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/gfp.h> + +#include "iwl-prph.h" +#include "iwl-io.h" +#include "internal.h" +#include "iwl-op-mode.h" +#include "iwl-context-info-gen3.h" + +/****************************************************************************** + * + * RX path functions + * + ******************************************************************************/ + +/* + * Rx theory of operation + * + * Driver allocates a circular buffer of Receive Buffer Descriptors (RBDs), + * each of which point to Receive Buffers to be filled by the NIC. These get + * used not only for Rx frames, but for any command response or notification + * from the NIC. The driver and NIC manage the Rx buffers by means + * of indexes into the circular buffer. + * + * Rx Queue Indexes + * The host/firmware share two index registers for managing the Rx buffers. + * + * The READ index maps to the first position that the firmware may be writing + * to -- the driver can read up to (but not including) this position and get + * good data. + * The READ index is managed by the firmware once the card is enabled. + * + * The WRITE index maps to the last position the driver has read from -- the + * position preceding WRITE is the last slot the firmware can place a packet. + * + * The queue is empty (no good data) if WRITE = READ - 1, and is full if + * WRITE = READ. + * + * During initialization, the host sets up the READ queue position to the first + * INDEX position, and WRITE to the last (READ - 1 wrapped) + * + * When the firmware places a packet in a buffer, it will advance the READ index + * and fire the RX interrupt. The driver can then query the READ index and + * process as many packets as possible, moving the WRITE index forward as it + * resets the Rx queue buffers with new memory. + * + * The management in the driver is as follows: + * + A list of pre-allocated RBDs is stored in iwl->rxq->rx_free. + * When the interrupt handler is called, the request is processed. + * The page is either stolen - transferred to the upper layer + * or reused - added immediately to the iwl->rxq->rx_free list. + * + When the page is stolen - the driver updates the matching queue's used + * count, detaches the RBD and transfers it to the queue used list. + * When there are two used RBDs - they are transferred to the allocator empty + * list. Work is then scheduled for the allocator to start allocating + * eight buffers. + * When there are another 6 used RBDs - they are transferred to the allocator + * empty list and the driver tries to claim the pre-allocated buffers and + * add them to iwl->rxq->rx_free. If it fails - it continues to claim them + * until ready. + * When there are 8+ buffers in the free list - either from allocation or from + * 8 reused unstolen pages - restock is called to update the FW and indexes. + * + In order to make sure the allocator always has RBDs to use for allocation + * the allocator has initial pool in the size of num_queues*(8-2) - the + * maximum missing RBDs per allocation request (request posted with 2 + * empty RBDs, there is no guarantee when the other 6 RBDs are supplied). + * The queues supplies the recycle of the rest of the RBDs. + * + A received packet is processed and handed to the kernel network stack, + * detached from the iwl->rxq. The driver 'processed' index is updated. + * + If there are no allocated buffers in iwl->rxq->rx_free, + * the READ INDEX is not incremented and iwl->status(RX_STALLED) is set. + * If there were enough free buffers and RX_STALLED is set it is cleared. + * + * + * Driver sequence: + * + * iwl_rxq_alloc() Allocates rx_free + * iwl_pcie_rx_replenish() Replenishes rx_free list from rx_used, and calls + * iwl_pcie_rxq_restock. + * Used only during initialization. + * iwl_pcie_rxq_restock() Moves available buffers from rx_free into Rx + * queue, updates firmware pointers, and updates + * the WRITE index. + * iwl_pcie_rx_allocator() Background work for allocating pages. + * + * -- enable interrupts -- + * ISR - iwl_rx() Detach iwl_rx_mem_buffers from pool up to the + * READ INDEX, detaching the SKB from the pool. + * Moves the packet buffer from queue to rx_used. + * Posts and claims requests to the allocator. + * Calls iwl_pcie_rxq_restock to refill any empty + * slots. + * + * RBD life-cycle: + * + * Init: + * rxq.pool -> rxq.rx_used -> rxq.rx_free -> rxq.queue + * + * Regular Receive interrupt: + * Page Stolen: + * rxq.queue -> rxq.rx_used -> allocator.rbd_empty -> + * allocator.rbd_allocated -> rxq.rx_free -> rxq.queue + * Page not Stolen: + * rxq.queue -> rxq.rx_free -> rxq.queue + * ... + * + */ + +/* + * iwl_rxq_space - Return number of free slots available in queue. + */ +static int iwl_rxq_space(const struct iwl_rxq *rxq) +{ + /* Make sure rx queue size is a power of 2 */ + WARN_ON(rxq->queue_size & (rxq->queue_size - 1)); + + /* + * There can be up to (RX_QUEUE_SIZE - 1) free slots, to avoid ambiguity + * between empty and completely full queues. + * The following is equivalent to modulo by RX_QUEUE_SIZE and is well + * defined for negative dividends. + */ + return (rxq->read - rxq->write - 1) & (rxq->queue_size - 1); +} + +/* + * iwl_dma_addr2rbd_ptr - convert a DMA address to a uCode read buffer ptr + */ +static inline __le32 iwl_pcie_dma_addr2rbd_ptr(dma_addr_t dma_addr) +{ + return cpu_to_le32((u32)(dma_addr >> 8)); +} + +/* + * iwl_pcie_rx_stop - stops the Rx DMA + */ +int iwl_pcie_rx_stop(struct iwl_trans *trans) +{ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + /* TODO: remove this once fw does it */ + iwl_write_umac_prph(trans, RFH_RXF_DMA_CFG_GEN3, 0); + return iwl_poll_umac_prph_bit(trans, RFH_GEN_STATUS_GEN3, + RXF_DMA_IDLE, RXF_DMA_IDLE, 1000); + } else if (trans->trans_cfg->mq_rx_supported) { + iwl_write_prph(trans, RFH_RXF_DMA_CFG, 0); + return iwl_poll_prph_bit(trans, RFH_GEN_STATUS, + RXF_DMA_IDLE, RXF_DMA_IDLE, 1000); + } else { + iwl_write_direct32(trans, FH_MEM_RCSR_CHNL0_CONFIG_REG, 0); + return iwl_poll_direct_bit(trans, FH_MEM_RSSR_RX_STATUS_REG, + FH_RSSR_CHNL0_RX_STATUS_CHNL_IDLE, + 1000); + } +} + +/* + * iwl_pcie_rxq_inc_wr_ptr - Update the write pointer for the RX queue + */ +static void iwl_pcie_rxq_inc_wr_ptr(struct iwl_trans *trans, + struct iwl_rxq *rxq) +{ + u32 reg; + + lockdep_assert_held(&rxq->lock); + + /* + * explicitly wake up the NIC if: + * 1. shadow registers aren't enabled + * 2. there is a chance that the NIC is asleep + */ + if (!trans->trans_cfg->base_params->shadow_reg_enable && + test_bit(STATUS_TPOWER_PMI, &trans->status)) { + reg = iwl_read32(trans, CSR_UCODE_DRV_GP1); + + if (reg & CSR_UCODE_DRV_GP1_BIT_MAC_SLEEP) { + IWL_DEBUG_INFO(trans, "Rx queue requesting wakeup, GP1 = 0x%x\n", + reg); + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + rxq->need_update = true; + return; + } + } + + rxq->write_actual = round_down(rxq->write, 8); + if (!trans->trans_cfg->mq_rx_supported) + iwl_write32(trans, FH_RSCSR_CHNL0_WPTR, rxq->write_actual); + else if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + iwl_write32(trans, HBUS_TARG_WRPTR, rxq->write_actual | + HBUS_TARG_WRPTR_RX_Q(rxq->id)); + else + iwl_write32(trans, RFH_Q_FRBDCB_WIDX_TRG(rxq->id), + rxq->write_actual); +} + +static void iwl_pcie_rxq_check_wrptr(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int i; + + for (i = 0; i < trans->num_rx_queues; i++) { + struct iwl_rxq *rxq = &trans_pcie->rxq[i]; + + if (!rxq->need_update) + continue; + spin_lock_bh(&rxq->lock); + iwl_pcie_rxq_inc_wr_ptr(trans, rxq); + rxq->need_update = false; + spin_unlock_bh(&rxq->lock); + } +} + +static void iwl_pcie_restock_bd(struct iwl_trans *trans, + struct iwl_rxq *rxq, + struct iwl_rx_mem_buffer *rxb) +{ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + struct iwl_rx_transfer_desc *bd = rxq->bd; + + BUILD_BUG_ON(sizeof(*bd) != 2 * sizeof(u64)); + + bd[rxq->write].addr = cpu_to_le64(rxb->page_dma); + bd[rxq->write].rbid = cpu_to_le16(rxb->vid); + } else { + __le64 *bd = rxq->bd; + + bd[rxq->write] = cpu_to_le64(rxb->page_dma | rxb->vid); + } + + IWL_DEBUG_RX(trans, "Assigned virtual RB ID %u to queue %d index %d\n", + (u32)rxb->vid, rxq->id, rxq->write); +} + +/* + * iwl_pcie_rxmq_restock - restock implementation for multi-queue rx + */ +static void iwl_pcie_rxmq_restock(struct iwl_trans *trans, + struct iwl_rxq *rxq) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rx_mem_buffer *rxb; + + /* + * If the device isn't enabled - no need to try to add buffers... + * This can happen when we stop the device and still have an interrupt + * pending. We stop the APM before we sync the interrupts because we + * have to (see comment there). On the other hand, since the APM is + * stopped, we cannot access the HW (in particular not prph). + * So don't try to restock if the APM has been already stopped. + */ + if (!test_bit(STATUS_DEVICE_ENABLED, &trans->status)) + return; + + spin_lock_bh(&rxq->lock); + while (rxq->free_count) { + /* Get next free Rx buffer, remove from free list */ + rxb = list_first_entry(&rxq->rx_free, struct iwl_rx_mem_buffer, + list); + list_del(&rxb->list); + rxb->invalid = false; + /* some low bits are expected to be unset (depending on hw) */ + WARN_ON(rxb->page_dma & trans_pcie->supported_dma_mask); + /* Point to Rx buffer via next RBD in circular buffer */ + iwl_pcie_restock_bd(trans, rxq, rxb); + rxq->write = (rxq->write + 1) & (rxq->queue_size - 1); + rxq->free_count--; + } + spin_unlock_bh(&rxq->lock); + + /* + * If we've added more space for the firmware to place data, tell it. + * Increment device's write pointer in multiples of 8. + */ + if (rxq->write_actual != (rxq->write & ~0x7)) { + spin_lock_bh(&rxq->lock); + iwl_pcie_rxq_inc_wr_ptr(trans, rxq); + spin_unlock_bh(&rxq->lock); + } +} + +/* + * iwl_pcie_rxsq_restock - restock implementation for single queue rx + */ +static void iwl_pcie_rxsq_restock(struct iwl_trans *trans, + struct iwl_rxq *rxq) +{ + struct iwl_rx_mem_buffer *rxb; + + /* + * If the device isn't enabled - not need to try to add buffers... + * This can happen when we stop the device and still have an interrupt + * pending. We stop the APM before we sync the interrupts because we + * have to (see comment there). On the other hand, since the APM is + * stopped, we cannot access the HW (in particular not prph). + * So don't try to restock if the APM has been already stopped. + */ + if (!test_bit(STATUS_DEVICE_ENABLED, &trans->status)) + return; + + spin_lock_bh(&rxq->lock); + while ((iwl_rxq_space(rxq) > 0) && (rxq->free_count)) { + __le32 *bd = (__le32 *)rxq->bd; + /* The overwritten rxb must be a used one */ + rxb = rxq->queue[rxq->write]; + BUG_ON(rxb && rxb->page); + + /* Get next free Rx buffer, remove from free list */ + rxb = list_first_entry(&rxq->rx_free, struct iwl_rx_mem_buffer, + list); + list_del(&rxb->list); + rxb->invalid = false; + + /* Point to Rx buffer via next RBD in circular buffer */ + bd[rxq->write] = iwl_pcie_dma_addr2rbd_ptr(rxb->page_dma); + rxq->queue[rxq->write] = rxb; + rxq->write = (rxq->write + 1) & RX_QUEUE_MASK; + rxq->free_count--; + } + spin_unlock_bh(&rxq->lock); + + /* If we've added more space for the firmware to place data, tell it. + * Increment device's write pointer in multiples of 8. */ + if (rxq->write_actual != (rxq->write & ~0x7)) { + spin_lock_bh(&rxq->lock); + iwl_pcie_rxq_inc_wr_ptr(trans, rxq); + spin_unlock_bh(&rxq->lock); + } +} + +/* + * iwl_pcie_rxq_restock - refill RX queue from pre-allocated pool + * + * If there are slots in the RX queue that need to be restocked, + * and we have free pre-allocated buffers, fill the ranks as much + * as we can, pulling from rx_free. + * + * This moves the 'write' index forward to catch up with 'processed', and + * also updates the memory address in the firmware to reference the new + * target buffer. + */ +static +void iwl_pcie_rxq_restock(struct iwl_trans *trans, struct iwl_rxq *rxq) +{ + if (trans->trans_cfg->mq_rx_supported) + iwl_pcie_rxmq_restock(trans, rxq); + else + iwl_pcie_rxsq_restock(trans, rxq); +} + +/* + * iwl_pcie_rx_alloc_page - allocates and returns a page. + * + */ +static struct page *iwl_pcie_rx_alloc_page(struct iwl_trans *trans, + u32 *offset, gfp_t priority) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + unsigned int rbsize = iwl_trans_get_rb_size(trans_pcie->rx_buf_size); + unsigned int allocsize = PAGE_SIZE << trans_pcie->rx_page_order; + struct page *page; + gfp_t gfp_mask = priority; + + if (trans_pcie->rx_page_order > 0) + gfp_mask |= __GFP_COMP; + + if (trans_pcie->alloc_page) { + spin_lock_bh(&trans_pcie->alloc_page_lock); + /* recheck */ + if (trans_pcie->alloc_page) { + *offset = trans_pcie->alloc_page_used; + page = trans_pcie->alloc_page; + trans_pcie->alloc_page_used += rbsize; + if (trans_pcie->alloc_page_used >= allocsize) + trans_pcie->alloc_page = NULL; + else + get_page(page); + spin_unlock_bh(&trans_pcie->alloc_page_lock); + return page; + } + spin_unlock_bh(&trans_pcie->alloc_page_lock); + } + + /* Alloc a new receive buffer */ + page = alloc_pages(gfp_mask, trans_pcie->rx_page_order); + if (!page) { + if (net_ratelimit()) + IWL_DEBUG_INFO(trans, "alloc_pages failed, order: %d\n", + trans_pcie->rx_page_order); + /* + * Issue an error if we don't have enough pre-allocated + * buffers. + */ + if (!(gfp_mask & __GFP_NOWARN) && net_ratelimit()) + IWL_CRIT(trans, + "Failed to alloc_pages\n"); + return NULL; + } + + if (2 * rbsize <= allocsize) { + spin_lock_bh(&trans_pcie->alloc_page_lock); + if (!trans_pcie->alloc_page) { + get_page(page); + trans_pcie->alloc_page = page; + trans_pcie->alloc_page_used = rbsize; + } + spin_unlock_bh(&trans_pcie->alloc_page_lock); + } + + *offset = 0; + return page; +} + +/* + * iwl_pcie_rxq_alloc_rbs - allocate a page for each used RBD + * + * A used RBD is an Rx buffer that has been given to the stack. To use it again + * a page must be allocated and the RBD must point to the page. This function + * doesn't change the HW pointer but handles the list of pages that is used by + * iwl_pcie_rxq_restock. The latter function will update the HW to use the newly + * allocated buffers. + */ +void iwl_pcie_rxq_alloc_rbs(struct iwl_trans *trans, gfp_t priority, + struct iwl_rxq *rxq) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rx_mem_buffer *rxb; + struct page *page; + + while (1) { + unsigned int offset; + + spin_lock_bh(&rxq->lock); + if (list_empty(&rxq->rx_used)) { + spin_unlock_bh(&rxq->lock); + return; + } + spin_unlock_bh(&rxq->lock); + + page = iwl_pcie_rx_alloc_page(trans, &offset, priority); + if (!page) + return; + + spin_lock_bh(&rxq->lock); + + if (list_empty(&rxq->rx_used)) { + spin_unlock_bh(&rxq->lock); + __free_pages(page, trans_pcie->rx_page_order); + return; + } + rxb = list_first_entry(&rxq->rx_used, struct iwl_rx_mem_buffer, + list); + list_del(&rxb->list); + spin_unlock_bh(&rxq->lock); + + BUG_ON(rxb->page); + rxb->page = page; + rxb->offset = offset; + /* Get physical address of the RB */ + rxb->page_dma = + dma_map_page(trans->dev, page, rxb->offset, + trans_pcie->rx_buf_bytes, + DMA_FROM_DEVICE); + if (dma_mapping_error(trans->dev, rxb->page_dma)) { + rxb->page = NULL; + spin_lock_bh(&rxq->lock); + list_add(&rxb->list, &rxq->rx_used); + spin_unlock_bh(&rxq->lock); + __free_pages(page, trans_pcie->rx_page_order); + return; + } + + spin_lock_bh(&rxq->lock); + + list_add_tail(&rxb->list, &rxq->rx_free); + rxq->free_count++; + + spin_unlock_bh(&rxq->lock); + } +} + +void iwl_pcie_free_rbs_pool(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int i; + + if (!trans_pcie->rx_pool) + return; + + for (i = 0; i < RX_POOL_SIZE(trans_pcie->num_rx_bufs); i++) { + if (!trans_pcie->rx_pool[i].page) + continue; + dma_unmap_page(trans->dev, trans_pcie->rx_pool[i].page_dma, + trans_pcie->rx_buf_bytes, DMA_FROM_DEVICE); + __free_pages(trans_pcie->rx_pool[i].page, + trans_pcie->rx_page_order); + trans_pcie->rx_pool[i].page = NULL; + } +} + +/* + * iwl_pcie_rx_allocator - Allocates pages in the background for RX queues + * + * Allocates for each received request 8 pages + * Called as a scheduled work item. + */ +static void iwl_pcie_rx_allocator(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rb_allocator *rba = &trans_pcie->rba; + struct list_head local_empty; + int pending = atomic_read(&rba->req_pending); + + IWL_DEBUG_TPT(trans, "Pending allocation requests = %d\n", pending); + + /* If we were scheduled - there is at least one request */ + spin_lock_bh(&rba->lock); + /* swap out the rba->rbd_empty to a local list */ + list_replace_init(&rba->rbd_empty, &local_empty); + spin_unlock_bh(&rba->lock); + + while (pending) { + int i; + LIST_HEAD(local_allocated); + gfp_t gfp_mask = GFP_KERNEL; + + /* Do not post a warning if there are only a few requests */ + if (pending < RX_PENDING_WATERMARK) + gfp_mask |= __GFP_NOWARN; + + for (i = 0; i < RX_CLAIM_REQ_ALLOC;) { + struct iwl_rx_mem_buffer *rxb; + struct page *page; + + /* List should never be empty - each reused RBD is + * returned to the list, and initial pool covers any + * possible gap between the time the page is allocated + * to the time the RBD is added. + */ + BUG_ON(list_empty(&local_empty)); + /* Get the first rxb from the rbd list */ + rxb = list_first_entry(&local_empty, + struct iwl_rx_mem_buffer, list); + BUG_ON(rxb->page); + + /* Alloc a new receive buffer */ + page = iwl_pcie_rx_alloc_page(trans, &rxb->offset, + gfp_mask); + if (!page) + continue; + rxb->page = page; + + /* Get physical address of the RB */ + rxb->page_dma = dma_map_page(trans->dev, page, + rxb->offset, + trans_pcie->rx_buf_bytes, + DMA_FROM_DEVICE); + if (dma_mapping_error(trans->dev, rxb->page_dma)) { + rxb->page = NULL; + __free_pages(page, trans_pcie->rx_page_order); + continue; + } + + /* move the allocated entry to the out list */ + list_move(&rxb->list, &local_allocated); + i++; + } + + atomic_dec(&rba->req_pending); + pending--; + + if (!pending) { + pending = atomic_read(&rba->req_pending); + if (pending) + IWL_DEBUG_TPT(trans, + "Got more pending allocation requests = %d\n", + pending); + } + + spin_lock_bh(&rba->lock); + /* add the allocated rbds to the allocator allocated list */ + list_splice_tail(&local_allocated, &rba->rbd_allocated); + /* get more empty RBDs for current pending requests */ + list_splice_tail_init(&rba->rbd_empty, &local_empty); + spin_unlock_bh(&rba->lock); + + atomic_inc(&rba->req_ready); + + } + + spin_lock_bh(&rba->lock); + /* return unused rbds to the allocator empty list */ + list_splice_tail(&local_empty, &rba->rbd_empty); + spin_unlock_bh(&rba->lock); + + IWL_DEBUG_TPT(trans, "%s, exit.\n", __func__); +} + +/* + * iwl_pcie_rx_allocator_get - returns the pre-allocated pages +.* +.* Called by queue when the queue posted allocation request and + * has freed 8 RBDs in order to restock itself. + * This function directly moves the allocated RBs to the queue's ownership + * and updates the relevant counters. + */ +static void iwl_pcie_rx_allocator_get(struct iwl_trans *trans, + struct iwl_rxq *rxq) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rb_allocator *rba = &trans_pcie->rba; + int i; + + lockdep_assert_held(&rxq->lock); + + /* + * atomic_dec_if_positive returns req_ready - 1 for any scenario. + * If req_ready is 0 atomic_dec_if_positive will return -1 and this + * function will return early, as there are no ready requests. + * atomic_dec_if_positive will perofrm the *actual* decrement only if + * req_ready > 0, i.e. - there are ready requests and the function + * hands one request to the caller. + */ + if (atomic_dec_if_positive(&rba->req_ready) < 0) + return; + + spin_lock(&rba->lock); + for (i = 0; i < RX_CLAIM_REQ_ALLOC; i++) { + /* Get next free Rx buffer, remove it from free list */ + struct iwl_rx_mem_buffer *rxb = + list_first_entry(&rba->rbd_allocated, + struct iwl_rx_mem_buffer, list); + + list_move(&rxb->list, &rxq->rx_free); + } + spin_unlock(&rba->lock); + + rxq->used_count -= RX_CLAIM_REQ_ALLOC; + rxq->free_count += RX_CLAIM_REQ_ALLOC; +} + +void iwl_pcie_rx_allocator_work(struct work_struct *data) +{ + struct iwl_rb_allocator *rba_p = + container_of(data, struct iwl_rb_allocator, rx_alloc); + struct iwl_trans_pcie *trans_pcie = + container_of(rba_p, struct iwl_trans_pcie, rba); + + iwl_pcie_rx_allocator(trans_pcie->trans); +} + +static int iwl_pcie_free_bd_size(struct iwl_trans *trans) +{ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + return sizeof(struct iwl_rx_transfer_desc); + + return trans->trans_cfg->mq_rx_supported ? + sizeof(__le64) : sizeof(__le32); +} + +static int iwl_pcie_used_bd_size(struct iwl_trans *trans) +{ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + return sizeof(struct iwl_rx_completion_desc_bz); + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + return sizeof(struct iwl_rx_completion_desc); + + return sizeof(__le32); +} + +static void iwl_pcie_free_rxq_dma(struct iwl_trans *trans, + struct iwl_rxq *rxq) +{ + int free_size = iwl_pcie_free_bd_size(trans); + + if (rxq->bd) + dma_free_coherent(trans->dev, + free_size * rxq->queue_size, + rxq->bd, rxq->bd_dma); + rxq->bd_dma = 0; + rxq->bd = NULL; + + rxq->rb_stts_dma = 0; + rxq->rb_stts = NULL; + + if (rxq->used_bd) + dma_free_coherent(trans->dev, + iwl_pcie_used_bd_size(trans) * + rxq->queue_size, + rxq->used_bd, rxq->used_bd_dma); + rxq->used_bd_dma = 0; + rxq->used_bd = NULL; +} + +static int iwl_pcie_alloc_rxq_dma(struct iwl_trans *trans, + struct iwl_rxq *rxq) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct device *dev = trans->dev; + int i; + int free_size; + bool use_rx_td = (trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_AX210); + size_t rb_stts_size = use_rx_td ? sizeof(__le16) : + sizeof(struct iwl_rb_status); + + spin_lock_init(&rxq->lock); + if (trans->trans_cfg->mq_rx_supported) + rxq->queue_size = trans->cfg->num_rbds; + else + rxq->queue_size = RX_QUEUE_SIZE; + + free_size = iwl_pcie_free_bd_size(trans); + + /* + * Allocate the circular buffer of Read Buffer Descriptors + * (RBDs) + */ + rxq->bd = dma_alloc_coherent(dev, free_size * rxq->queue_size, + &rxq->bd_dma, GFP_KERNEL); + if (!rxq->bd) + goto err; + + if (trans->trans_cfg->mq_rx_supported) { + rxq->used_bd = dma_alloc_coherent(dev, + iwl_pcie_used_bd_size(trans) * + rxq->queue_size, + &rxq->used_bd_dma, + GFP_KERNEL); + if (!rxq->used_bd) + goto err; + } + + rxq->rb_stts = (u8 *)trans_pcie->base_rb_stts + rxq->id * rb_stts_size; + rxq->rb_stts_dma = + trans_pcie->base_rb_stts_dma + rxq->id * rb_stts_size; + + return 0; + +err: + for (i = 0; i < trans->num_rx_queues; i++) { + struct iwl_rxq *rxq = &trans_pcie->rxq[i]; + + iwl_pcie_free_rxq_dma(trans, rxq); + } + + return -ENOMEM; +} + +static int iwl_pcie_rx_alloc(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rb_allocator *rba = &trans_pcie->rba; + int i, ret; + size_t rb_stts_size = trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_AX210 ? + sizeof(__le16) : sizeof(struct iwl_rb_status); + + if (WARN_ON(trans_pcie->rxq)) + return -EINVAL; + + trans_pcie->rxq = kcalloc(trans->num_rx_queues, sizeof(struct iwl_rxq), + GFP_KERNEL); + trans_pcie->rx_pool = kcalloc(RX_POOL_SIZE(trans_pcie->num_rx_bufs), + sizeof(trans_pcie->rx_pool[0]), + GFP_KERNEL); + trans_pcie->global_table = + kcalloc(RX_POOL_SIZE(trans_pcie->num_rx_bufs), + sizeof(trans_pcie->global_table[0]), + GFP_KERNEL); + if (!trans_pcie->rxq || !trans_pcie->rx_pool || + !trans_pcie->global_table) { + ret = -ENOMEM; + goto err; + } + + spin_lock_init(&rba->lock); + + /* + * Allocate the driver's pointer to receive buffer status. + * Allocate for all queues continuously (HW requirement). + */ + trans_pcie->base_rb_stts = + dma_alloc_coherent(trans->dev, + rb_stts_size * trans->num_rx_queues, + &trans_pcie->base_rb_stts_dma, + GFP_KERNEL); + if (!trans_pcie->base_rb_stts) { + ret = -ENOMEM; + goto err; + } + + for (i = 0; i < trans->num_rx_queues; i++) { + struct iwl_rxq *rxq = &trans_pcie->rxq[i]; + + rxq->id = i; + ret = iwl_pcie_alloc_rxq_dma(trans, rxq); + if (ret) + goto err; + } + return 0; + +err: + if (trans_pcie->base_rb_stts) { + dma_free_coherent(trans->dev, + rb_stts_size * trans->num_rx_queues, + trans_pcie->base_rb_stts, + trans_pcie->base_rb_stts_dma); + trans_pcie->base_rb_stts = NULL; + trans_pcie->base_rb_stts_dma = 0; + } + kfree(trans_pcie->rx_pool); + trans_pcie->rx_pool = NULL; + kfree(trans_pcie->global_table); + trans_pcie->global_table = NULL; + kfree(trans_pcie->rxq); + trans_pcie->rxq = NULL; + + return ret; +} + +static void iwl_pcie_rx_hw_init(struct iwl_trans *trans, struct iwl_rxq *rxq) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 rb_size; + const u32 rfdnlog = RX_QUEUE_SIZE_LOG; /* 256 RBDs */ + + switch (trans_pcie->rx_buf_size) { + case IWL_AMSDU_4K: + rb_size = FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K; + break; + case IWL_AMSDU_8K: + rb_size = FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_8K; + break; + case IWL_AMSDU_12K: + rb_size = FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_12K; + break; + default: + WARN_ON(1); + rb_size = FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K; + } + + if (!iwl_trans_grab_nic_access(trans)) + return; + + /* Stop Rx DMA */ + iwl_write32(trans, FH_MEM_RCSR_CHNL0_CONFIG_REG, 0); + /* reset and flush pointers */ + iwl_write32(trans, FH_MEM_RCSR_CHNL0_RBDCB_WPTR, 0); + iwl_write32(trans, FH_MEM_RCSR_CHNL0_FLUSH_RB_REQ, 0); + iwl_write32(trans, FH_RSCSR_CHNL0_RDPTR, 0); + + /* Reset driver's Rx queue write index */ + iwl_write32(trans, FH_RSCSR_CHNL0_RBDCB_WPTR_REG, 0); + + /* Tell device where to find RBD circular buffer in DRAM */ + iwl_write32(trans, FH_RSCSR_CHNL0_RBDCB_BASE_REG, + (u32)(rxq->bd_dma >> 8)); + + /* Tell device where in DRAM to update its Rx status */ + iwl_write32(trans, FH_RSCSR_CHNL0_STTS_WPTR_REG, + rxq->rb_stts_dma >> 4); + + /* Enable Rx DMA + * FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY is set because of HW bug in + * the credit mechanism in 5000 HW RX FIFO + * Direct rx interrupts to hosts + * Rx buffer size 4 or 8k or 12k + * RB timeout 0x10 + * 256 RBDs + */ + iwl_write32(trans, FH_MEM_RCSR_CHNL0_CONFIG_REG, + FH_RCSR_RX_CONFIG_CHNL_EN_ENABLE_VAL | + FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY | + FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_INT_HOST_VAL | + rb_size | + (RX_RB_TIMEOUT << FH_RCSR_RX_CONFIG_REG_IRQ_RBTH_POS) | + (rfdnlog << FH_RCSR_RX_CONFIG_RBDCB_SIZE_POS)); + + iwl_trans_release_nic_access(trans); + + /* Set interrupt coalescing timer to default (2048 usecs) */ + iwl_write8(trans, CSR_INT_COALESCING, IWL_HOST_INT_TIMEOUT_DEF); + + /* W/A for interrupt coalescing bug in 7260 and 3160 */ + if (trans->cfg->host_interrupt_operation_mode) + iwl_set_bit(trans, CSR_INT_COALESCING, IWL_HOST_INT_OPER_MODE); +} + +static void iwl_pcie_rx_mq_hw_init(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 rb_size, enabled = 0; + int i; + + switch (trans_pcie->rx_buf_size) { + case IWL_AMSDU_2K: + rb_size = RFH_RXF_DMA_RB_SIZE_2K; + break; + case IWL_AMSDU_4K: + rb_size = RFH_RXF_DMA_RB_SIZE_4K; + break; + case IWL_AMSDU_8K: + rb_size = RFH_RXF_DMA_RB_SIZE_8K; + break; + case IWL_AMSDU_12K: + rb_size = RFH_RXF_DMA_RB_SIZE_12K; + break; + default: + WARN_ON(1); + rb_size = RFH_RXF_DMA_RB_SIZE_4K; + } + + if (!iwl_trans_grab_nic_access(trans)) + return; + + /* Stop Rx DMA */ + iwl_write_prph_no_grab(trans, RFH_RXF_DMA_CFG, 0); + /* disable free amd used rx queue operation */ + iwl_write_prph_no_grab(trans, RFH_RXF_RXQ_ACTIVE, 0); + + for (i = 0; i < trans->num_rx_queues; i++) { + /* Tell device where to find RBD free table in DRAM */ + iwl_write_prph64_no_grab(trans, + RFH_Q_FRBDCB_BA_LSB(i), + trans_pcie->rxq[i].bd_dma); + /* Tell device where to find RBD used table in DRAM */ + iwl_write_prph64_no_grab(trans, + RFH_Q_URBDCB_BA_LSB(i), + trans_pcie->rxq[i].used_bd_dma); + /* Tell device where in DRAM to update its Rx status */ + iwl_write_prph64_no_grab(trans, + RFH_Q_URBD_STTS_WPTR_LSB(i), + trans_pcie->rxq[i].rb_stts_dma); + /* Reset device indice tables */ + iwl_write_prph_no_grab(trans, RFH_Q_FRBDCB_WIDX(i), 0); + iwl_write_prph_no_grab(trans, RFH_Q_FRBDCB_RIDX(i), 0); + iwl_write_prph_no_grab(trans, RFH_Q_URBDCB_WIDX(i), 0); + + enabled |= BIT(i) | BIT(i + 16); + } + + /* + * Enable Rx DMA + * Rx buffer size 4 or 8k or 12k + * Min RB size 4 or 8 + * Drop frames that exceed RB size + * 512 RBDs + */ + iwl_write_prph_no_grab(trans, RFH_RXF_DMA_CFG, + RFH_DMA_EN_ENABLE_VAL | rb_size | + RFH_RXF_DMA_MIN_RB_4_8 | + RFH_RXF_DMA_DROP_TOO_LARGE_MASK | + RFH_RXF_DMA_RBDCB_SIZE_512); + + /* + * Activate DMA snooping. + * Set RX DMA chunk size to 64B for IOSF and 128B for PCIe + * Default queue is 0 + */ + iwl_write_prph_no_grab(trans, RFH_GEN_CFG, + RFH_GEN_CFG_RFH_DMA_SNOOP | + RFH_GEN_CFG_VAL(DEFAULT_RXQ_NUM, 0) | + RFH_GEN_CFG_SERVICE_DMA_SNOOP | + RFH_GEN_CFG_VAL(RB_CHUNK_SIZE, + trans->trans_cfg->integrated ? + RFH_GEN_CFG_RB_CHUNK_SIZE_64 : + RFH_GEN_CFG_RB_CHUNK_SIZE_128)); + /* Enable the relevant rx queues */ + iwl_write_prph_no_grab(trans, RFH_RXF_RXQ_ACTIVE, enabled); + + iwl_trans_release_nic_access(trans); + + /* Set interrupt coalescing timer to default (2048 usecs) */ + iwl_write8(trans, CSR_INT_COALESCING, IWL_HOST_INT_TIMEOUT_DEF); +} + +void iwl_pcie_rx_init_rxb_lists(struct iwl_rxq *rxq) +{ + lockdep_assert_held(&rxq->lock); + + INIT_LIST_HEAD(&rxq->rx_free); + INIT_LIST_HEAD(&rxq->rx_used); + rxq->free_count = 0; + rxq->used_count = 0; +} + +static int iwl_pcie_rx_handle(struct iwl_trans *trans, int queue, int budget); + +static int iwl_pcie_napi_poll(struct napi_struct *napi, int budget) +{ + struct iwl_rxq *rxq = container_of(napi, struct iwl_rxq, napi); + struct iwl_trans_pcie *trans_pcie; + struct iwl_trans *trans; + int ret; + + trans_pcie = container_of(napi->dev, struct iwl_trans_pcie, napi_dev); + trans = trans_pcie->trans; + + ret = iwl_pcie_rx_handle(trans, rxq->id, budget); + + IWL_DEBUG_ISR(trans, "[%d] handled %d, budget %d\n", + rxq->id, ret, budget); + + if (ret < budget) { + spin_lock(&trans_pcie->irq_lock); + if (test_bit(STATUS_INT_ENABLED, &trans->status)) + _iwl_enable_interrupts(trans); + spin_unlock(&trans_pcie->irq_lock); + + napi_complete_done(&rxq->napi, ret); + } + + return ret; +} + +static int iwl_pcie_napi_poll_msix(struct napi_struct *napi, int budget) +{ + struct iwl_rxq *rxq = container_of(napi, struct iwl_rxq, napi); + struct iwl_trans_pcie *trans_pcie; + struct iwl_trans *trans; + int ret; + + trans_pcie = container_of(napi->dev, struct iwl_trans_pcie, napi_dev); + trans = trans_pcie->trans; + + ret = iwl_pcie_rx_handle(trans, rxq->id, budget); + IWL_DEBUG_ISR(trans, "[%d] handled %d, budget %d\n", rxq->id, ret, + budget); + + if (ret < budget) { + int irq_line = rxq->id; + + /* FIRST_RSS is shared with line 0 */ + if (trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_FIRST_RSS && + rxq->id == 1) + irq_line = 0; + + spin_lock(&trans_pcie->irq_lock); + iwl_pcie_clear_irq(trans, irq_line); + spin_unlock(&trans_pcie->irq_lock); + + napi_complete_done(&rxq->napi, ret); + } + + return ret; +} + +void iwl_pcie_rx_napi_sync(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int i; + + if (unlikely(!trans_pcie->rxq)) + return; + + for (i = 0; i < trans->num_rx_queues; i++) { + struct iwl_rxq *rxq = &trans_pcie->rxq[i]; + + if (rxq && rxq->napi.poll) + napi_synchronize(&rxq->napi); + } +} + +static int _iwl_pcie_rx_init(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rxq *def_rxq; + struct iwl_rb_allocator *rba = &trans_pcie->rba; + int i, err, queue_size, allocator_pool_size, num_alloc; + + if (!trans_pcie->rxq) { + err = iwl_pcie_rx_alloc(trans); + if (err) + return err; + } + def_rxq = trans_pcie->rxq; + + cancel_work_sync(&rba->rx_alloc); + + spin_lock_bh(&rba->lock); + atomic_set(&rba->req_pending, 0); + atomic_set(&rba->req_ready, 0); + INIT_LIST_HEAD(&rba->rbd_allocated); + INIT_LIST_HEAD(&rba->rbd_empty); + spin_unlock_bh(&rba->lock); + + /* free all first - we overwrite everything here */ + iwl_pcie_free_rbs_pool(trans); + + for (i = 0; i < RX_QUEUE_SIZE; i++) + def_rxq->queue[i] = NULL; + + for (i = 0; i < trans->num_rx_queues; i++) { + struct iwl_rxq *rxq = &trans_pcie->rxq[i]; + + spin_lock_bh(&rxq->lock); + /* + * Set read write pointer to reflect that we have processed + * and used all buffers, but have not restocked the Rx queue + * with fresh buffers + */ + rxq->read = 0; + rxq->write = 0; + rxq->write_actual = 0; + memset(rxq->rb_stts, 0, + (trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_AX210) ? + sizeof(__le16) : sizeof(struct iwl_rb_status)); + + iwl_pcie_rx_init_rxb_lists(rxq); + + spin_unlock_bh(&rxq->lock); + + if (!rxq->napi.poll) { + int (*poll)(struct napi_struct *, int) = iwl_pcie_napi_poll; + + if (trans_pcie->msix_enabled) + poll = iwl_pcie_napi_poll_msix; + + netif_napi_add(&trans_pcie->napi_dev, &rxq->napi, + poll); + napi_enable(&rxq->napi); + } + + } + + /* move the pool to the default queue and allocator ownerships */ + queue_size = trans->trans_cfg->mq_rx_supported ? + trans_pcie->num_rx_bufs - 1 : RX_QUEUE_SIZE; + allocator_pool_size = trans->num_rx_queues * + (RX_CLAIM_REQ_ALLOC - RX_POST_REQ_ALLOC); + num_alloc = queue_size + allocator_pool_size; + + for (i = 0; i < num_alloc; i++) { + struct iwl_rx_mem_buffer *rxb = &trans_pcie->rx_pool[i]; + + if (i < allocator_pool_size) + list_add(&rxb->list, &rba->rbd_empty); + else + list_add(&rxb->list, &def_rxq->rx_used); + trans_pcie->global_table[i] = rxb; + rxb->vid = (u16)(i + 1); + rxb->invalid = true; + } + + iwl_pcie_rxq_alloc_rbs(trans, GFP_KERNEL, def_rxq); + + return 0; +} + +int iwl_pcie_rx_init(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret = _iwl_pcie_rx_init(trans); + + if (ret) + return ret; + + if (trans->trans_cfg->mq_rx_supported) + iwl_pcie_rx_mq_hw_init(trans); + else + iwl_pcie_rx_hw_init(trans, trans_pcie->rxq); + + iwl_pcie_rxq_restock(trans, trans_pcie->rxq); + + spin_lock_bh(&trans_pcie->rxq->lock); + iwl_pcie_rxq_inc_wr_ptr(trans, trans_pcie->rxq); + spin_unlock_bh(&trans_pcie->rxq->lock); + + return 0; +} + +int iwl_pcie_gen2_rx_init(struct iwl_trans *trans) +{ + /* Set interrupt coalescing timer to default (2048 usecs) */ + iwl_write8(trans, CSR_INT_COALESCING, IWL_HOST_INT_TIMEOUT_DEF); + + /* + * We don't configure the RFH. + * Restock will be done at alive, after firmware configured the RFH. + */ + return _iwl_pcie_rx_init(trans); +} + +void iwl_pcie_rx_free(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rb_allocator *rba = &trans_pcie->rba; + int i; + size_t rb_stts_size = trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_AX210 ? + sizeof(__le16) : sizeof(struct iwl_rb_status); + + /* + * if rxq is NULL, it means that nothing has been allocated, + * exit now + */ + if (!trans_pcie->rxq) { + IWL_DEBUG_INFO(trans, "Free NULL rx context\n"); + return; + } + + cancel_work_sync(&rba->rx_alloc); + + iwl_pcie_free_rbs_pool(trans); + + if (trans_pcie->base_rb_stts) { + dma_free_coherent(trans->dev, + rb_stts_size * trans->num_rx_queues, + trans_pcie->base_rb_stts, + trans_pcie->base_rb_stts_dma); + trans_pcie->base_rb_stts = NULL; + trans_pcie->base_rb_stts_dma = 0; + } + + for (i = 0; i < trans->num_rx_queues; i++) { + struct iwl_rxq *rxq = &trans_pcie->rxq[i]; + + iwl_pcie_free_rxq_dma(trans, rxq); + + if (rxq->napi.poll) { + napi_disable(&rxq->napi); + netif_napi_del(&rxq->napi); + } + } + kfree(trans_pcie->rx_pool); + kfree(trans_pcie->global_table); + kfree(trans_pcie->rxq); + + if (trans_pcie->alloc_page) + __free_pages(trans_pcie->alloc_page, trans_pcie->rx_page_order); +} + +static void iwl_pcie_rx_move_to_allocator(struct iwl_rxq *rxq, + struct iwl_rb_allocator *rba) +{ + spin_lock(&rba->lock); + list_splice_tail_init(&rxq->rx_used, &rba->rbd_empty); + spin_unlock(&rba->lock); +} + +/* + * iwl_pcie_rx_reuse_rbd - Recycle used RBDs + * + * Called when a RBD can be reused. The RBD is transferred to the allocator. + * When there are 2 empty RBDs - a request for allocation is posted + */ +static void iwl_pcie_rx_reuse_rbd(struct iwl_trans *trans, + struct iwl_rx_mem_buffer *rxb, + struct iwl_rxq *rxq, bool emergency) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rb_allocator *rba = &trans_pcie->rba; + + /* Move the RBD to the used list, will be moved to allocator in batches + * before claiming or posting a request*/ + list_add_tail(&rxb->list, &rxq->rx_used); + + if (unlikely(emergency)) + return; + + /* Count the allocator owned RBDs */ + rxq->used_count++; + + /* If we have RX_POST_REQ_ALLOC new released rx buffers - + * issue a request for allocator. Modulo RX_CLAIM_REQ_ALLOC is + * used for the case we failed to claim RX_CLAIM_REQ_ALLOC, + * after but we still need to post another request. + */ + if ((rxq->used_count % RX_CLAIM_REQ_ALLOC) == RX_POST_REQ_ALLOC) { + /* Move the 2 RBDs to the allocator ownership. + Allocator has another 6 from pool for the request completion*/ + iwl_pcie_rx_move_to_allocator(rxq, rba); + + atomic_inc(&rba->req_pending); + queue_work(rba->alloc_wq, &rba->rx_alloc); + } +} + +static void iwl_pcie_rx_handle_rb(struct iwl_trans *trans, + struct iwl_rxq *rxq, + struct iwl_rx_mem_buffer *rxb, + bool emergency, + int i) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_txq *txq = trans->txqs.txq[trans->txqs.cmd.q_id]; + bool page_stolen = false; + int max_len = trans_pcie->rx_buf_bytes; + u32 offset = 0; + + if (WARN_ON(!rxb)) + return; + + dma_unmap_page(trans->dev, rxb->page_dma, max_len, DMA_FROM_DEVICE); + + while (offset + sizeof(u32) + sizeof(struct iwl_cmd_header) < max_len) { + struct iwl_rx_packet *pkt; + bool reclaim; + int len; + struct iwl_rx_cmd_buffer rxcb = { + ._offset = rxb->offset + offset, + ._rx_page_order = trans_pcie->rx_page_order, + ._page = rxb->page, + ._page_stolen = false, + .truesize = max_len, + }; + + pkt = rxb_addr(&rxcb); + + if (pkt->len_n_flags == cpu_to_le32(FH_RSCSR_FRAME_INVALID)) { + IWL_DEBUG_RX(trans, + "Q %d: RB end marker at offset %d\n", + rxq->id, offset); + break; + } + + WARN((le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_RXQ_MASK) >> + FH_RSCSR_RXQ_POS != rxq->id, + "frame on invalid queue - is on %d and indicates %d\n", + rxq->id, + (le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_RXQ_MASK) >> + FH_RSCSR_RXQ_POS); + + IWL_DEBUG_RX(trans, + "Q %d: cmd at offset %d: %s (%.2x.%2x, seq 0x%x)\n", + rxq->id, offset, + iwl_get_cmd_string(trans, + WIDE_ID(pkt->hdr.group_id, pkt->hdr.cmd)), + pkt->hdr.group_id, pkt->hdr.cmd, + le16_to_cpu(pkt->hdr.sequence)); + + len = iwl_rx_packet_len(pkt); + len += sizeof(u32); /* account for status word */ + + offset += ALIGN(len, FH_RSCSR_FRAME_ALIGN); + + /* check that what the device tells us made sense */ + if (len < sizeof(*pkt) || offset > max_len) + break; + + trace_iwlwifi_dev_rx(trans->dev, trans, pkt, len); + trace_iwlwifi_dev_rx_data(trans->dev, trans, pkt, len); + + /* Reclaim a command buffer only if this packet is a response + * to a (driver-originated) command. + * If the packet (e.g. Rx frame) originated from uCode, + * there is no command buffer to reclaim. + * Ucode should set SEQ_RX_FRAME bit if ucode-originated, + * but apparently a few don't get set; catch them here. */ + reclaim = !(pkt->hdr.sequence & SEQ_RX_FRAME); + if (reclaim && !pkt->hdr.group_id) { + int i; + + for (i = 0; i < trans_pcie->n_no_reclaim_cmds; i++) { + if (trans_pcie->no_reclaim_cmds[i] == + pkt->hdr.cmd) { + reclaim = false; + break; + } + } + } + + if (rxq->id == trans_pcie->def_rx_queue) + iwl_op_mode_rx(trans->op_mode, &rxq->napi, + &rxcb); + else + iwl_op_mode_rx_rss(trans->op_mode, &rxq->napi, + &rxcb, rxq->id); + + /* + * After here, we should always check rxcb._page_stolen, + * if it is true then one of the handlers took the page. + */ + + if (reclaim && txq) { + u16 sequence = le16_to_cpu(pkt->hdr.sequence); + int index = SEQ_TO_INDEX(sequence); + int cmd_index = iwl_txq_get_cmd_index(txq, index); + + kfree_sensitive(txq->entries[cmd_index].free_buf); + txq->entries[cmd_index].free_buf = NULL; + + /* Invoke any callbacks, transfer the buffer to caller, + * and fire off the (possibly) blocking + * iwl_trans_send_cmd() + * as we reclaim the driver command queue */ + if (!rxcb._page_stolen) + iwl_pcie_hcmd_complete(trans, &rxcb); + else + IWL_WARN(trans, "Claim null rxb?\n"); + } + + page_stolen |= rxcb._page_stolen; + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + break; + } + + /* page was stolen from us -- free our reference */ + if (page_stolen) { + __free_pages(rxb->page, trans_pcie->rx_page_order); + rxb->page = NULL; + } + + /* Reuse the page if possible. For notification packets and + * SKBs that fail to Rx correctly, add them back into the + * rx_free list for reuse later. */ + if (rxb->page != NULL) { + rxb->page_dma = + dma_map_page(trans->dev, rxb->page, rxb->offset, + trans_pcie->rx_buf_bytes, + DMA_FROM_DEVICE); + if (dma_mapping_error(trans->dev, rxb->page_dma)) { + /* + * free the page(s) as well to not break + * the invariant that the items on the used + * list have no page(s) + */ + __free_pages(rxb->page, trans_pcie->rx_page_order); + rxb->page = NULL; + iwl_pcie_rx_reuse_rbd(trans, rxb, rxq, emergency); + } else { + list_add_tail(&rxb->list, &rxq->rx_free); + rxq->free_count++; + } + } else + iwl_pcie_rx_reuse_rbd(trans, rxb, rxq, emergency); +} + +static struct iwl_rx_mem_buffer *iwl_pcie_get_rxb(struct iwl_trans *trans, + struct iwl_rxq *rxq, int i, + bool *join) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rx_mem_buffer *rxb; + u16 vid; + + BUILD_BUG_ON(sizeof(struct iwl_rx_completion_desc) != 32); + BUILD_BUG_ON(sizeof(struct iwl_rx_completion_desc_bz) != 4); + + if (!trans->trans_cfg->mq_rx_supported) { + rxb = rxq->queue[i]; + rxq->queue[i] = NULL; + return rxb; + } + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) { + struct iwl_rx_completion_desc_bz *cd = rxq->used_bd; + + vid = le16_to_cpu(cd[i].rbid); + *join = cd[i].flags & IWL_RX_CD_FLAGS_FRAGMENTED; + } else if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + struct iwl_rx_completion_desc *cd = rxq->used_bd; + + vid = le16_to_cpu(cd[i].rbid); + *join = cd[i].flags & IWL_RX_CD_FLAGS_FRAGMENTED; + } else { + __le32 *cd = rxq->used_bd; + + vid = le32_to_cpu(cd[i]) & 0x0FFF; /* 12-bit VID */ + } + + if (!vid || vid > RX_POOL_SIZE(trans_pcie->num_rx_bufs)) + goto out_err; + + rxb = trans_pcie->global_table[vid - 1]; + if (rxb->invalid) + goto out_err; + + IWL_DEBUG_RX(trans, "Got virtual RB ID %u\n", (u32)rxb->vid); + + rxb->invalid = true; + + return rxb; + +out_err: + WARN(1, "Invalid rxb from HW %u\n", (u32)vid); + iwl_force_nmi(trans); + return NULL; +} + +/* + * iwl_pcie_rx_handle - Main entry function for receiving responses from fw + */ +static int iwl_pcie_rx_handle(struct iwl_trans *trans, int queue, int budget) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rxq *rxq; + u32 r, i, count = 0, handled = 0; + bool emergency = false; + + if (WARN_ON_ONCE(!trans_pcie->rxq || !trans_pcie->rxq[queue].bd)) + return budget; + + rxq = &trans_pcie->rxq[queue]; + +restart: + spin_lock(&rxq->lock); + /* uCode's read index (stored in shared DRAM) indicates the last Rx + * buffer that the driver may process (last buffer filled by ucode). */ + r = le16_to_cpu(iwl_get_closed_rb_stts(trans, rxq)) & 0x0FFF; + i = rxq->read; + + /* W/A 9000 device step A0 wrap-around bug */ + r &= (rxq->queue_size - 1); + + /* Rx interrupt, but nothing sent from uCode */ + if (i == r) + IWL_DEBUG_RX(trans, "Q %d: HW = SW = %d\n", rxq->id, r); + + while (i != r && ++handled < budget) { + struct iwl_rb_allocator *rba = &trans_pcie->rba; + struct iwl_rx_mem_buffer *rxb; + /* number of RBDs still waiting for page allocation */ + u32 rb_pending_alloc = + atomic_read(&trans_pcie->rba.req_pending) * + RX_CLAIM_REQ_ALLOC; + bool join = false; + + if (unlikely(rb_pending_alloc >= rxq->queue_size / 2 && + !emergency)) { + iwl_pcie_rx_move_to_allocator(rxq, rba); + emergency = true; + IWL_DEBUG_TPT(trans, + "RX path is in emergency. Pending allocations %d\n", + rb_pending_alloc); + } + + IWL_DEBUG_RX(trans, "Q %d: HW = %d, SW = %d\n", rxq->id, r, i); + + rxb = iwl_pcie_get_rxb(trans, rxq, i, &join); + if (!rxb) + goto out; + + if (unlikely(join || rxq->next_rb_is_fragment)) { + rxq->next_rb_is_fragment = join; + /* + * We can only get a multi-RB in the following cases: + * - firmware issue, sending a too big notification + * - sniffer mode with a large A-MSDU + * - large MTU frames (>2k) + * since the multi-RB functionality is limited to newer + * hardware that cannot put multiple entries into a + * single RB. + * + * Right now, the higher layers aren't set up to deal + * with that, so discard all of these. + */ + list_add_tail(&rxb->list, &rxq->rx_free); + rxq->free_count++; + } else { + iwl_pcie_rx_handle_rb(trans, rxq, rxb, emergency, i); + } + + i = (i + 1) & (rxq->queue_size - 1); + + /* + * If we have RX_CLAIM_REQ_ALLOC released rx buffers - + * try to claim the pre-allocated buffers from the allocator. + * If not ready - will try to reclaim next time. + * There is no need to reschedule work - allocator exits only + * on success + */ + if (rxq->used_count >= RX_CLAIM_REQ_ALLOC) + iwl_pcie_rx_allocator_get(trans, rxq); + + if (rxq->used_count % RX_CLAIM_REQ_ALLOC == 0 && !emergency) { + /* Add the remaining empty RBDs for allocator use */ + iwl_pcie_rx_move_to_allocator(rxq, rba); + } else if (emergency) { + count++; + if (count == 8) { + count = 0; + if (rb_pending_alloc < rxq->queue_size / 3) { + IWL_DEBUG_TPT(trans, + "RX path exited emergency. Pending allocations %d\n", + rb_pending_alloc); + emergency = false; + } + + rxq->read = i; + spin_unlock(&rxq->lock); + iwl_pcie_rxq_alloc_rbs(trans, GFP_ATOMIC, rxq); + iwl_pcie_rxq_restock(trans, rxq); + goto restart; + } + } + } +out: + /* Backtrack one entry */ + rxq->read = i; + spin_unlock(&rxq->lock); + + /* + * handle a case where in emergency there are some unallocated RBDs. + * those RBDs are in the used list, but are not tracked by the queue's + * used_count which counts allocator owned RBDs. + * unallocated emergency RBDs must be allocated on exit, otherwise + * when called again the function may not be in emergency mode and + * they will be handed to the allocator with no tracking in the RBD + * allocator counters, which will lead to them never being claimed back + * by the queue. + * by allocating them here, they are now in the queue free list, and + * will be restocked by the next call of iwl_pcie_rxq_restock. + */ + if (unlikely(emergency && count)) + iwl_pcie_rxq_alloc_rbs(trans, GFP_ATOMIC, rxq); + + iwl_pcie_rxq_restock(trans, rxq); + + return handled; +} + +static struct iwl_trans_pcie *iwl_pcie_get_trans_pcie(struct msix_entry *entry) +{ + u8 queue = entry->entry; + struct msix_entry *entries = entry - queue; + + return container_of(entries, struct iwl_trans_pcie, msix_entries[0]); +} + +/* + * iwl_pcie_rx_msix_handle - Main entry function for receiving responses from fw + * This interrupt handler should be used with RSS queue only. + */ +irqreturn_t iwl_pcie_irq_rx_msix_handler(int irq, void *dev_id) +{ + struct msix_entry *entry = dev_id; + struct iwl_trans_pcie *trans_pcie = iwl_pcie_get_trans_pcie(entry); + struct iwl_trans *trans = trans_pcie->trans; + struct iwl_rxq *rxq; + + trace_iwlwifi_dev_irq_msix(trans->dev, entry, false, 0, 0); + + if (WARN_ON(entry->entry >= trans->num_rx_queues)) + return IRQ_NONE; + + if (!trans_pcie->rxq) { + if (net_ratelimit()) + IWL_ERR(trans, + "[%d] Got MSI-X interrupt before we have Rx queues\n", + entry->entry); + return IRQ_NONE; + } + + rxq = &trans_pcie->rxq[entry->entry]; + lock_map_acquire(&trans->sync_cmd_lockdep_map); + IWL_DEBUG_ISR(trans, "[%d] Got interrupt\n", entry->entry); + + local_bh_disable(); + if (napi_schedule_prep(&rxq->napi)) + __napi_schedule(&rxq->napi); + else + iwl_pcie_clear_irq(trans, entry->entry); + local_bh_enable(); + + lock_map_release(&trans->sync_cmd_lockdep_map); + + return IRQ_HANDLED; +} + +/* + * iwl_pcie_irq_handle_error - called for HW or SW error interrupt from card + */ +static void iwl_pcie_irq_handle_error(struct iwl_trans *trans) +{ + int i; + + /* W/A for WiFi/WiMAX coex and WiMAX own the RF */ + if (trans->cfg->internal_wimax_coex && + !trans->cfg->apmg_not_supported && + (!(iwl_read_prph(trans, APMG_CLK_CTRL_REG) & + APMS_CLK_VAL_MRB_FUNC_MODE) || + (iwl_read_prph(trans, APMG_PS_CTRL_REG) & + APMG_PS_CTRL_VAL_RESET_REQ))) { + clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status); + iwl_op_mode_wimax_active(trans->op_mode); + wake_up(&trans->wait_command_queue); + return; + } + + for (i = 0; i < trans->trans_cfg->base_params->num_of_queues; i++) { + if (!trans->txqs.txq[i]) + continue; + del_timer(&trans->txqs.txq[i]->stuck_timer); + } + + /* The STATUS_FW_ERROR bit is set in this function. This must happen + * before we wake up the command caller, to ensure a proper cleanup. */ + iwl_trans_fw_error(trans, false); + + clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status); + wake_up(&trans->wait_command_queue); +} + +static u32 iwl_pcie_int_cause_non_ict(struct iwl_trans *trans) +{ + u32 inta; + + lockdep_assert_held(&IWL_TRANS_GET_PCIE_TRANS(trans)->irq_lock); + + trace_iwlwifi_dev_irq(trans->dev); + + /* Discover which interrupts are active/pending */ + inta = iwl_read32(trans, CSR_INT); + + /* the thread will service interrupts and re-enable them */ + return inta; +} + +/* a device (PCI-E) page is 4096 bytes long */ +#define ICT_SHIFT 12 +#define ICT_SIZE (1 << ICT_SHIFT) +#define ICT_COUNT (ICT_SIZE / sizeof(u32)) + +/* interrupt handler using ict table, with this interrupt driver will + * stop using INTA register to get device's interrupt, reading this register + * is expensive, device will write interrupts in ICT dram table, increment + * index then will fire interrupt to driver, driver will OR all ICT table + * entries from current index up to table entry with 0 value. the result is + * the interrupt we need to service, driver will set the entries back to 0 and + * set index. + */ +static u32 iwl_pcie_int_cause_ict(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 inta; + u32 val = 0; + u32 read; + + trace_iwlwifi_dev_irq(trans->dev); + + /* Ignore interrupt if there's nothing in NIC to service. + * This may be due to IRQ shared with another device, + * or due to sporadic interrupts thrown from our NIC. */ + read = le32_to_cpu(trans_pcie->ict_tbl[trans_pcie->ict_index]); + trace_iwlwifi_dev_ict_read(trans->dev, trans_pcie->ict_index, read); + if (!read) + return 0; + + /* + * Collect all entries up to the first 0, starting from ict_index; + * note we already read at ict_index. + */ + do { + val |= read; + IWL_DEBUG_ISR(trans, "ICT index %d value 0x%08X\n", + trans_pcie->ict_index, read); + trans_pcie->ict_tbl[trans_pcie->ict_index] = 0; + trans_pcie->ict_index = + ((trans_pcie->ict_index + 1) & (ICT_COUNT - 1)); + + read = le32_to_cpu(trans_pcie->ict_tbl[trans_pcie->ict_index]); + trace_iwlwifi_dev_ict_read(trans->dev, trans_pcie->ict_index, + read); + } while (read); + + /* We should not get this value, just ignore it. */ + if (val == 0xffffffff) + val = 0; + + /* + * this is a w/a for a h/w bug. the h/w bug may cause the Rx bit + * (bit 15 before shifting it to 31) to clear when using interrupt + * coalescing. fortunately, bits 18 and 19 stay set when this happens + * so we use them to decide on the real state of the Rx bit. + * In order words, bit 15 is set if bit 18 or bit 19 are set. + */ + if (val & 0xC0000) + val |= 0x8000; + + inta = (0xff & val) | ((0xff00 & val) << 16); + return inta; +} + +void iwl_pcie_handle_rfkill_irq(struct iwl_trans *trans, bool from_irq) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct isr_statistics *isr_stats = &trans_pcie->isr_stats; + bool hw_rfkill, prev, report; + + mutex_lock(&trans_pcie->mutex); + prev = test_bit(STATUS_RFKILL_OPMODE, &trans->status); + hw_rfkill = iwl_is_rfkill_set(trans); + if (hw_rfkill) { + set_bit(STATUS_RFKILL_OPMODE, &trans->status); + set_bit(STATUS_RFKILL_HW, &trans->status); + } + if (trans_pcie->opmode_down) + report = hw_rfkill; + else + report = test_bit(STATUS_RFKILL_OPMODE, &trans->status); + + IWL_WARN(trans, "RF_KILL bit toggled to %s.\n", + hw_rfkill ? "disable radio" : "enable radio"); + + isr_stats->rfkill++; + + if (prev != report) + iwl_trans_pcie_rf_kill(trans, report, from_irq); + mutex_unlock(&trans_pcie->mutex); + + if (hw_rfkill) { + if (test_and_clear_bit(STATUS_SYNC_HCMD_ACTIVE, + &trans->status)) + IWL_DEBUG_RF_KILL(trans, + "Rfkill while SYNC HCMD in flight\n"); + wake_up(&trans->wait_command_queue); + } else { + clear_bit(STATUS_RFKILL_HW, &trans->status); + if (trans_pcie->opmode_down) + clear_bit(STATUS_RFKILL_OPMODE, &trans->status); + } +} + +irqreturn_t iwl_pcie_irq_handler(int irq, void *dev_id) +{ + struct iwl_trans *trans = dev_id; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct isr_statistics *isr_stats = &trans_pcie->isr_stats; + u32 inta = 0; + u32 handled = 0; + bool polling = false; + + lock_map_acquire(&trans->sync_cmd_lockdep_map); + + spin_lock_bh(&trans_pcie->irq_lock); + + /* dram interrupt table not set yet, + * use legacy interrupt. + */ + if (likely(trans_pcie->use_ict)) + inta = iwl_pcie_int_cause_ict(trans); + else + inta = iwl_pcie_int_cause_non_ict(trans); + + if (iwl_have_debug_level(IWL_DL_ISR)) { + IWL_DEBUG_ISR(trans, + "ISR inta 0x%08x, enabled 0x%08x(sw), enabled(hw) 0x%08x, fh 0x%08x\n", + inta, trans_pcie->inta_mask, + iwl_read32(trans, CSR_INT_MASK), + iwl_read32(trans, CSR_FH_INT_STATUS)); + if (inta & (~trans_pcie->inta_mask)) + IWL_DEBUG_ISR(trans, + "We got a masked interrupt (0x%08x)\n", + inta & (~trans_pcie->inta_mask)); + } + + inta &= trans_pcie->inta_mask; + + /* + * Ignore interrupt if there's nothing in NIC to service. + * This may be due to IRQ shared with another device, + * or due to sporadic interrupts thrown from our NIC. + */ + if (unlikely(!inta)) { + IWL_DEBUG_ISR(trans, "Ignore interrupt, inta == 0\n"); + /* + * Re-enable interrupts here since we don't + * have anything to service + */ + if (test_bit(STATUS_INT_ENABLED, &trans->status)) + _iwl_enable_interrupts(trans); + spin_unlock_bh(&trans_pcie->irq_lock); + lock_map_release(&trans->sync_cmd_lockdep_map); + return IRQ_NONE; + } + + if (unlikely(inta == 0xFFFFFFFF || (inta & 0xFFFFFFF0) == 0xa5a5a5a0)) { + /* + * Hardware disappeared. It might have + * already raised an interrupt. + */ + IWL_WARN(trans, "HARDWARE GONE?? INTA == 0x%08x\n", inta); + spin_unlock_bh(&trans_pcie->irq_lock); + goto out; + } + + /* Ack/clear/reset pending uCode interrupts. + * Note: Some bits in CSR_INT are "OR" of bits in CSR_FH_INT_STATUS, + */ + /* There is a hardware bug in the interrupt mask function that some + * interrupts (i.e. CSR_INT_BIT_SCD) can still be generated even if + * they are disabled in the CSR_INT_MASK register. Furthermore the + * ICT interrupt handling mechanism has another bug that might cause + * these unmasked interrupts fail to be detected. We workaround the + * hardware bugs here by ACKing all the possible interrupts so that + * interrupt coalescing can still be achieved. + */ + iwl_write32(trans, CSR_INT, inta | ~trans_pcie->inta_mask); + + if (iwl_have_debug_level(IWL_DL_ISR)) + IWL_DEBUG_ISR(trans, "inta 0x%08x, enabled 0x%08x\n", + inta, iwl_read32(trans, CSR_INT_MASK)); + + spin_unlock_bh(&trans_pcie->irq_lock); + + /* Now service all interrupt bits discovered above. */ + if (inta & CSR_INT_BIT_HW_ERR) { + IWL_ERR(trans, "Hardware error detected. Restarting.\n"); + + /* Tell the device to stop sending interrupts */ + iwl_disable_interrupts(trans); + + isr_stats->hw++; + iwl_pcie_irq_handle_error(trans); + + handled |= CSR_INT_BIT_HW_ERR; + + goto out; + } + + /* NIC fires this, but we don't use it, redundant with WAKEUP */ + if (inta & CSR_INT_BIT_SCD) { + IWL_DEBUG_ISR(trans, + "Scheduler finished to transmit the frame/frames.\n"); + isr_stats->sch++; + } + + /* Alive notification via Rx interrupt will do the real work */ + if (inta & CSR_INT_BIT_ALIVE) { + IWL_DEBUG_ISR(trans, "Alive interrupt\n"); + isr_stats->alive++; + if (trans->trans_cfg->gen2) { + /* + * We can restock, since firmware configured + * the RFH + */ + iwl_pcie_rxmq_restock(trans, trans_pcie->rxq); + } + + handled |= CSR_INT_BIT_ALIVE; + } + + /* Safely ignore these bits for debug checks below */ + inta &= ~(CSR_INT_BIT_SCD | CSR_INT_BIT_ALIVE); + + /* HW RF KILL switch toggled */ + if (inta & CSR_INT_BIT_RF_KILL) { + iwl_pcie_handle_rfkill_irq(trans, true); + handled |= CSR_INT_BIT_RF_KILL; + } + + /* Chip got too hot and stopped itself */ + if (inta & CSR_INT_BIT_CT_KILL) { + IWL_ERR(trans, "Microcode CT kill error detected.\n"); + isr_stats->ctkill++; + handled |= CSR_INT_BIT_CT_KILL; + } + + /* Error detected by uCode */ + if (inta & CSR_INT_BIT_SW_ERR) { + IWL_ERR(trans, "Microcode SW error detected. " + " Restarting 0x%X.\n", inta); + isr_stats->sw++; + iwl_pcie_irq_handle_error(trans); + handled |= CSR_INT_BIT_SW_ERR; + } + + /* uCode wakes up after power-down sleep */ + if (inta & CSR_INT_BIT_WAKEUP) { + IWL_DEBUG_ISR(trans, "Wakeup interrupt\n"); + iwl_pcie_rxq_check_wrptr(trans); + iwl_pcie_txq_check_wrptrs(trans); + + isr_stats->wakeup++; + + handled |= CSR_INT_BIT_WAKEUP; + } + + /* All uCode command responses, including Tx command responses, + * Rx "responses" (frame-received notification), and other + * notifications from uCode come through here*/ + if (inta & (CSR_INT_BIT_FH_RX | CSR_INT_BIT_SW_RX | + CSR_INT_BIT_RX_PERIODIC)) { + IWL_DEBUG_ISR(trans, "Rx interrupt\n"); + if (inta & (CSR_INT_BIT_FH_RX | CSR_INT_BIT_SW_RX)) { + handled |= (CSR_INT_BIT_FH_RX | CSR_INT_BIT_SW_RX); + iwl_write32(trans, CSR_FH_INT_STATUS, + CSR_FH_INT_RX_MASK); + } + if (inta & CSR_INT_BIT_RX_PERIODIC) { + handled |= CSR_INT_BIT_RX_PERIODIC; + iwl_write32(trans, + CSR_INT, CSR_INT_BIT_RX_PERIODIC); + } + /* Sending RX interrupt require many steps to be done in the + * device: + * 1- write interrupt to current index in ICT table. + * 2- dma RX frame. + * 3- update RX shared data to indicate last write index. + * 4- send interrupt. + * This could lead to RX race, driver could receive RX interrupt + * but the shared data changes does not reflect this; + * periodic interrupt will detect any dangling Rx activity. + */ + + /* Disable periodic interrupt; we use it as just a one-shot. */ + iwl_write8(trans, CSR_INT_PERIODIC_REG, + CSR_INT_PERIODIC_DIS); + + /* + * Enable periodic interrupt in 8 msec only if we received + * real RX interrupt (instead of just periodic int), to catch + * any dangling Rx interrupt. If it was just the periodic + * interrupt, there was no dangling Rx activity, and no need + * to extend the periodic interrupt; one-shot is enough. + */ + if (inta & (CSR_INT_BIT_FH_RX | CSR_INT_BIT_SW_RX)) + iwl_write8(trans, CSR_INT_PERIODIC_REG, + CSR_INT_PERIODIC_ENA); + + isr_stats->rx++; + + local_bh_disable(); + if (napi_schedule_prep(&trans_pcie->rxq[0].napi)) { + polling = true; + __napi_schedule(&trans_pcie->rxq[0].napi); + } + local_bh_enable(); + } + + /* This "Tx" DMA channel is used only for loading uCode */ + if (inta & CSR_INT_BIT_FH_TX) { + iwl_write32(trans, CSR_FH_INT_STATUS, CSR_FH_INT_TX_MASK); + IWL_DEBUG_ISR(trans, "uCode load interrupt\n"); + isr_stats->tx++; + handled |= CSR_INT_BIT_FH_TX; + /* Wake up uCode load routine, now that load is complete */ + trans_pcie->ucode_write_complete = true; + wake_up(&trans_pcie->ucode_write_waitq); + /* Wake up IMR write routine, now that write to SRAM is complete */ + if (trans_pcie->imr_status == IMR_D2S_REQUESTED) { + trans_pcie->imr_status = IMR_D2S_COMPLETED; + wake_up(&trans_pcie->ucode_write_waitq); + } + } + + if (inta & ~handled) { + IWL_ERR(trans, "Unhandled INTA bits 0x%08x\n", inta & ~handled); + isr_stats->unhandled++; + } + + if (inta & ~(trans_pcie->inta_mask)) { + IWL_WARN(trans, "Disabled INTA bits 0x%08x were pending\n", + inta & ~trans_pcie->inta_mask); + } + + if (!polling) { + spin_lock_bh(&trans_pcie->irq_lock); + /* only Re-enable all interrupt if disabled by irq */ + if (test_bit(STATUS_INT_ENABLED, &trans->status)) + _iwl_enable_interrupts(trans); + /* we are loading the firmware, enable FH_TX interrupt only */ + else if (handled & CSR_INT_BIT_FH_TX) + iwl_enable_fw_load_int(trans); + /* Re-enable RF_KILL if it occurred */ + else if (handled & CSR_INT_BIT_RF_KILL) + iwl_enable_rfkill_int(trans); + /* Re-enable the ALIVE / Rx interrupt if it occurred */ + else if (handled & (CSR_INT_BIT_ALIVE | CSR_INT_BIT_FH_RX)) + iwl_enable_fw_load_int_ctx_info(trans); + spin_unlock_bh(&trans_pcie->irq_lock); + } + +out: + lock_map_release(&trans->sync_cmd_lockdep_map); + return IRQ_HANDLED; +} + +/****************************************************************************** + * + * ICT functions + * + ******************************************************************************/ + +/* Free dram table */ +void iwl_pcie_free_ict(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (trans_pcie->ict_tbl) { + dma_free_coherent(trans->dev, ICT_SIZE, + trans_pcie->ict_tbl, + trans_pcie->ict_tbl_dma); + trans_pcie->ict_tbl = NULL; + trans_pcie->ict_tbl_dma = 0; + } +} + +/* + * allocate dram shared table, it is an aligned memory + * block of ICT_SIZE. + * also reset all data related to ICT table interrupt. + */ +int iwl_pcie_alloc_ict(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + trans_pcie->ict_tbl = + dma_alloc_coherent(trans->dev, ICT_SIZE, + &trans_pcie->ict_tbl_dma, GFP_KERNEL); + if (!trans_pcie->ict_tbl) + return -ENOMEM; + + /* just an API sanity check ... it is guaranteed to be aligned */ + if (WARN_ON(trans_pcie->ict_tbl_dma & (ICT_SIZE - 1))) { + iwl_pcie_free_ict(trans); + return -EINVAL; + } + + return 0; +} + +/* Device is going up inform it about using ICT interrupt table, + * also we need to tell the driver to start using ICT interrupt. + */ +void iwl_pcie_reset_ict(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 val; + + if (!trans_pcie->ict_tbl) + return; + + spin_lock_bh(&trans_pcie->irq_lock); + _iwl_disable_interrupts(trans); + + memset(trans_pcie->ict_tbl, 0, ICT_SIZE); + + val = trans_pcie->ict_tbl_dma >> ICT_SHIFT; + + val |= CSR_DRAM_INT_TBL_ENABLE | + CSR_DRAM_INIT_TBL_WRAP_CHECK | + CSR_DRAM_INIT_TBL_WRITE_POINTER; + + IWL_DEBUG_ISR(trans, "CSR_DRAM_INT_TBL_REG =0x%x\n", val); + + iwl_write32(trans, CSR_DRAM_INT_TBL_REG, val); + trans_pcie->use_ict = true; + trans_pcie->ict_index = 0; + iwl_write32(trans, CSR_INT, trans_pcie->inta_mask); + _iwl_enable_interrupts(trans); + spin_unlock_bh(&trans_pcie->irq_lock); +} + +/* Device is going down disable ict interrupt usage */ +void iwl_pcie_disable_ict(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + spin_lock_bh(&trans_pcie->irq_lock); + trans_pcie->use_ict = false; + spin_unlock_bh(&trans_pcie->irq_lock); +} + +irqreturn_t iwl_pcie_isr(int irq, void *data) +{ + struct iwl_trans *trans = data; + + if (!trans) + return IRQ_NONE; + + /* Disable (but don't clear!) interrupts here to avoid + * back-to-back ISRs and sporadic interrupts from our NIC. + * If we have something to service, the tasklet will re-enable ints. + * If we *don't* have something, we'll re-enable before leaving here. + */ + iwl_write32(trans, CSR_INT_MASK, 0x00000000); + + return IRQ_WAKE_THREAD; +} + +irqreturn_t iwl_pcie_msix_isr(int irq, void *data) +{ + return IRQ_WAKE_THREAD; +} + +irqreturn_t iwl_pcie_irq_msix_handler(int irq, void *dev_id) +{ + struct msix_entry *entry = dev_id; + struct iwl_trans_pcie *trans_pcie = iwl_pcie_get_trans_pcie(entry); + struct iwl_trans *trans = trans_pcie->trans; + struct isr_statistics *isr_stats = &trans_pcie->isr_stats; + u32 inta_fh_msk = ~MSIX_FH_INT_CAUSES_DATA_QUEUE; + u32 inta_fh, inta_hw; + bool polling = false; + bool sw_err; + + if (trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_NON_RX) + inta_fh_msk |= MSIX_FH_INT_CAUSES_Q0; + + if (trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_FIRST_RSS) + inta_fh_msk |= MSIX_FH_INT_CAUSES_Q1; + + lock_map_acquire(&trans->sync_cmd_lockdep_map); + + spin_lock_bh(&trans_pcie->irq_lock); + inta_fh = iwl_read32(trans, CSR_MSIX_FH_INT_CAUSES_AD); + inta_hw = iwl_read32(trans, CSR_MSIX_HW_INT_CAUSES_AD); + /* + * Clear causes registers to avoid being handling the same cause. + */ + iwl_write32(trans, CSR_MSIX_FH_INT_CAUSES_AD, inta_fh & inta_fh_msk); + iwl_write32(trans, CSR_MSIX_HW_INT_CAUSES_AD, inta_hw); + spin_unlock_bh(&trans_pcie->irq_lock); + + trace_iwlwifi_dev_irq_msix(trans->dev, entry, true, inta_fh, inta_hw); + + if (unlikely(!(inta_fh | inta_hw))) { + IWL_DEBUG_ISR(trans, "Ignore interrupt, inta == 0\n"); + lock_map_release(&trans->sync_cmd_lockdep_map); + return IRQ_NONE; + } + + if (iwl_have_debug_level(IWL_DL_ISR)) { + IWL_DEBUG_ISR(trans, + "ISR[%d] inta_fh 0x%08x, enabled (sw) 0x%08x (hw) 0x%08x\n", + entry->entry, inta_fh, trans_pcie->fh_mask, + iwl_read32(trans, CSR_MSIX_FH_INT_MASK_AD)); + if (inta_fh & ~trans_pcie->fh_mask) + IWL_DEBUG_ISR(trans, + "We got a masked interrupt (0x%08x)\n", + inta_fh & ~trans_pcie->fh_mask); + } + + inta_fh &= trans_pcie->fh_mask; + + if ((trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_NON_RX) && + inta_fh & MSIX_FH_INT_CAUSES_Q0) { + local_bh_disable(); + if (napi_schedule_prep(&trans_pcie->rxq[0].napi)) { + polling = true; + __napi_schedule(&trans_pcie->rxq[0].napi); + } + local_bh_enable(); + } + + if ((trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_FIRST_RSS) && + inta_fh & MSIX_FH_INT_CAUSES_Q1) { + local_bh_disable(); + if (napi_schedule_prep(&trans_pcie->rxq[1].napi)) { + polling = true; + __napi_schedule(&trans_pcie->rxq[1].napi); + } + local_bh_enable(); + } + + /* This "Tx" DMA channel is used only for loading uCode */ + if (inta_fh & MSIX_FH_INT_CAUSES_D2S_CH0_NUM && + trans_pcie->imr_status == IMR_D2S_REQUESTED) { + IWL_DEBUG_ISR(trans, "IMR Complete interrupt\n"); + isr_stats->tx++; + + /* Wake up IMR routine once write to SRAM is complete */ + if (trans_pcie->imr_status == IMR_D2S_REQUESTED) { + trans_pcie->imr_status = IMR_D2S_COMPLETED; + wake_up(&trans_pcie->ucode_write_waitq); + } + } else if (inta_fh & MSIX_FH_INT_CAUSES_D2S_CH0_NUM) { + IWL_DEBUG_ISR(trans, "uCode load interrupt\n"); + isr_stats->tx++; + /* + * Wake up uCode load routine, + * now that load is complete + */ + trans_pcie->ucode_write_complete = true; + wake_up(&trans_pcie->ucode_write_waitq); + + /* Wake up IMR routine once write to SRAM is complete */ + if (trans_pcie->imr_status == IMR_D2S_REQUESTED) { + trans_pcie->imr_status = IMR_D2S_COMPLETED; + wake_up(&trans_pcie->ucode_write_waitq); + } + } + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + sw_err = inta_hw & MSIX_HW_INT_CAUSES_REG_SW_ERR_BZ; + else + sw_err = inta_hw & MSIX_HW_INT_CAUSES_REG_SW_ERR; + + /* Error detected by uCode */ + if ((inta_fh & MSIX_FH_INT_CAUSES_FH_ERR) || sw_err) { + IWL_ERR(trans, + "Microcode SW error detected. Restarting 0x%X.\n", + inta_fh); + isr_stats->sw++; + /* during FW reset flow report errors from there */ + if (trans_pcie->imr_status == IMR_D2S_REQUESTED) { + trans_pcie->imr_status = IMR_D2S_ERROR; + wake_up(&trans_pcie->imr_waitq); + } else if (trans_pcie->fw_reset_state == FW_RESET_REQUESTED) { + trans_pcie->fw_reset_state = FW_RESET_ERROR; + wake_up(&trans_pcie->fw_reset_waitq); + } else { + iwl_pcie_irq_handle_error(trans); + } + } + + /* After checking FH register check HW register */ + if (iwl_have_debug_level(IWL_DL_ISR)) { + IWL_DEBUG_ISR(trans, + "ISR[%d] inta_hw 0x%08x, enabled (sw) 0x%08x (hw) 0x%08x\n", + entry->entry, inta_hw, trans_pcie->hw_mask, + iwl_read32(trans, CSR_MSIX_HW_INT_MASK_AD)); + if (inta_hw & ~trans_pcie->hw_mask) + IWL_DEBUG_ISR(trans, + "We got a masked interrupt 0x%08x\n", + inta_hw & ~trans_pcie->hw_mask); + } + + inta_hw &= trans_pcie->hw_mask; + + /* Alive notification via Rx interrupt will do the real work */ + if (inta_hw & MSIX_HW_INT_CAUSES_REG_ALIVE) { + IWL_DEBUG_ISR(trans, "Alive interrupt\n"); + isr_stats->alive++; + if (trans->trans_cfg->gen2) { + /* We can restock, since firmware configured the RFH */ + iwl_pcie_rxmq_restock(trans, trans_pcie->rxq); + } + } + + /* + * In some rare cases when the HW is in a bad state, we may + * get this interrupt too early, when prph_info is still NULL. + * So make sure that it's not NULL to prevent crashing. + */ + if (inta_hw & MSIX_HW_INT_CAUSES_REG_WAKEUP && trans_pcie->prph_info) { + u32 sleep_notif = + le32_to_cpu(trans_pcie->prph_info->sleep_notif); + if (sleep_notif == IWL_D3_SLEEP_STATUS_SUSPEND || + sleep_notif == IWL_D3_SLEEP_STATUS_RESUME) { + IWL_DEBUG_ISR(trans, + "Sx interrupt: sleep notification = 0x%x\n", + sleep_notif); + trans_pcie->sx_complete = true; + wake_up(&trans_pcie->sx_waitq); + } else { + /* uCode wakes up after power-down sleep */ + IWL_DEBUG_ISR(trans, "Wakeup interrupt\n"); + iwl_pcie_rxq_check_wrptr(trans); + iwl_pcie_txq_check_wrptrs(trans); + + isr_stats->wakeup++; + } + } + + /* Chip got too hot and stopped itself */ + if (inta_hw & MSIX_HW_INT_CAUSES_REG_CT_KILL) { + IWL_ERR(trans, "Microcode CT kill error detected.\n"); + isr_stats->ctkill++; + } + + /* HW RF KILL switch toggled */ + if (inta_hw & MSIX_HW_INT_CAUSES_REG_RF_KILL) + iwl_pcie_handle_rfkill_irq(trans, true); + + if (inta_hw & MSIX_HW_INT_CAUSES_REG_HW_ERR) { + IWL_ERR(trans, + "Hardware error detected. Restarting.\n"); + + isr_stats->hw++; + trans->dbg.hw_error = true; + iwl_pcie_irq_handle_error(trans); + } + + if (inta_hw & MSIX_HW_INT_CAUSES_REG_RESET_DONE) { + IWL_DEBUG_ISR(trans, "Reset flow completed\n"); + trans_pcie->fw_reset_state = FW_RESET_OK; + wake_up(&trans_pcie->fw_reset_waitq); + } + + if (!polling) + iwl_pcie_clear_irq(trans, entry->entry); + + lock_map_release(&trans->sync_cmd_lockdep_map); + + return IRQ_HANDLED; +} diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/trans-gen2.c b/drivers/net/wireless/intel/iwlwifi/pcie/trans-gen2.c new file mode 100644 index 000000000..8b9e4b9c5 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/pcie/trans-gen2.c @@ -0,0 +1,465 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2018-2021 Intel Corporation + */ +#include "iwl-trans.h" +#include "iwl-prph.h" +#include "iwl-context-info.h" +#include "iwl-context-info-gen3.h" +#include "internal.h" +#include "fw/dbg.h" + +#define FW_RESET_TIMEOUT (HZ / 5) + +/* + * Start up NIC's basic functionality after it has been reset + * (e.g. after platform boot, or shutdown via iwl_pcie_apm_stop()) + * NOTE: This does not load uCode nor start the embedded processor + */ +int iwl_pcie_gen2_apm_init(struct iwl_trans *trans) +{ + int ret = 0; + + IWL_DEBUG_INFO(trans, "Init card's basic functions\n"); + + /* + * Use "set_bit" below rather than "write", to preserve any hardware + * bits already set by default after reset. + */ + + /* + * Disable L0s without affecting L1; + * don't wait for ICH L0s (ICH bug W/A) + */ + iwl_set_bit(trans, CSR_GIO_CHICKEN_BITS, + CSR_GIO_CHICKEN_BITS_REG_BIT_L1A_NO_L0S_RX); + + /* Set FH wait threshold to maximum (HW error during stress W/A) */ + iwl_set_bit(trans, CSR_DBG_HPET_MEM_REG, CSR_DBG_HPET_MEM_REG_VAL); + + /* + * Enable HAP INTA (interrupt from management bus) to + * wake device's PCI Express link L1a -> L0s + */ + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_HAP_WAKE_L1A); + + iwl_pcie_apm_config(trans); + + ret = iwl_finish_nic_init(trans); + if (ret) + return ret; + + set_bit(STATUS_DEVICE_ENABLED, &trans->status); + + return 0; +} + +static void iwl_pcie_gen2_apm_stop(struct iwl_trans *trans, bool op_mode_leave) +{ + IWL_DEBUG_INFO(trans, "Stop card, put in low power state\n"); + + if (op_mode_leave) { + if (!test_bit(STATUS_DEVICE_ENABLED, &trans->status)) + iwl_pcie_gen2_apm_init(trans); + + /* inform ME that we are leaving */ + iwl_set_bit(trans, CSR_DBG_LINK_PWR_MGMT_REG, + CSR_RESET_LINK_PWR_MGMT_DISABLED); + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_PREPARE | + CSR_HW_IF_CONFIG_REG_ENABLE_PME); + mdelay(1); + iwl_clear_bit(trans, CSR_DBG_LINK_PWR_MGMT_REG, + CSR_RESET_LINK_PWR_MGMT_DISABLED); + mdelay(5); + } + + clear_bit(STATUS_DEVICE_ENABLED, &trans->status); + + /* Stop device's DMA activity */ + iwl_pcie_apm_stop_master(trans); + + iwl_trans_sw_reset(trans, false); + + /* + * Clear "initialization complete" bit to move adapter from + * D0A* (powered-up Active) --> D0U* (Uninitialized) state. + */ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + iwl_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_INIT); + else + iwl_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_INIT_DONE); +} + +static void iwl_trans_pcie_fw_reset_handshake(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret; + + trans_pcie->fw_reset_state = FW_RESET_REQUESTED; + + if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) + iwl_write_umac_prph(trans, UREG_NIC_SET_NMI_DRIVER, + UREG_NIC_SET_NMI_DRIVER_RESET_HANDSHAKE); + else if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_AX210) + iwl_write_umac_prph(trans, UREG_DOORBELL_TO_ISR6, + UREG_DOORBELL_TO_ISR6_RESET_HANDSHAKE); + else + iwl_write32(trans, CSR_DOORBELL_VECTOR, + UREG_DOORBELL_TO_ISR6_RESET_HANDSHAKE); + + /* wait 200ms */ + ret = wait_event_timeout(trans_pcie->fw_reset_waitq, + trans_pcie->fw_reset_state != FW_RESET_REQUESTED, + FW_RESET_TIMEOUT); + if (!ret || trans_pcie->fw_reset_state == FW_RESET_ERROR) { + IWL_INFO(trans, + "firmware didn't ACK the reset - continue anyway\n"); + iwl_trans_fw_error(trans, true); + } + + trans_pcie->fw_reset_state = FW_RESET_IDLE; +} + +void _iwl_trans_pcie_gen2_stop_device(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + lockdep_assert_held(&trans_pcie->mutex); + + if (trans_pcie->is_down) + return; + + if (trans->state >= IWL_TRANS_FW_STARTED) + if (trans_pcie->fw_reset_handshake) + iwl_trans_pcie_fw_reset_handshake(trans); + + trans_pcie->is_down = true; + + /* tell the device to stop sending interrupts */ + iwl_disable_interrupts(trans); + + /* device going down, Stop using ICT table */ + iwl_pcie_disable_ict(trans); + + /* + * If a HW restart happens during firmware loading, + * then the firmware loading might call this function + * and later it might be called again due to the + * restart. So don't process again if the device is + * already dead. + */ + if (test_and_clear_bit(STATUS_DEVICE_ENABLED, &trans->status)) { + IWL_DEBUG_INFO(trans, + "DEVICE_ENABLED bit was set and is now cleared\n"); + iwl_pcie_synchronize_irqs(trans); + iwl_pcie_rx_napi_sync(trans); + iwl_txq_gen2_tx_free(trans); + iwl_pcie_rx_stop(trans); + } + + iwl_pcie_ctxt_info_free_paging(trans); + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + iwl_pcie_ctxt_info_gen3_free(trans, false); + else + iwl_pcie_ctxt_info_free(trans); + + /* Stop the device, and put it in low power state */ + iwl_pcie_gen2_apm_stop(trans, false); + + /* re-take ownership to prevent other users from stealing the device */ + iwl_trans_sw_reset(trans, true); + + /* + * Upon stop, the IVAR table gets erased, so msi-x won't + * work. This causes a bug in RF-KILL flows, since the interrupt + * that enables radio won't fire on the correct irq, and the + * driver won't be able to handle the interrupt. + * Configure the IVAR table again after reset. + */ + iwl_pcie_conf_msix_hw(trans_pcie); + + /* + * Upon stop, the APM issues an interrupt if HW RF kill is set. + * This is a bug in certain verions of the hardware. + * Certain devices also keep sending HW RF kill interrupt all + * the time, unless the interrupt is ACKed even if the interrupt + * should be masked. Re-ACK all the interrupts here. + */ + iwl_disable_interrupts(trans); + + /* clear all status bits */ + clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status); + clear_bit(STATUS_INT_ENABLED, &trans->status); + clear_bit(STATUS_TPOWER_PMI, &trans->status); + + /* + * Even if we stop the HW, we still want the RF kill + * interrupt + */ + iwl_enable_rfkill_int(trans); +} + +void iwl_trans_pcie_gen2_stop_device(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + bool was_in_rfkill; + + iwl_op_mode_time_point(trans->op_mode, + IWL_FW_INI_TIME_POINT_HOST_DEVICE_DISABLE, + NULL); + + mutex_lock(&trans_pcie->mutex); + trans_pcie->opmode_down = true; + was_in_rfkill = test_bit(STATUS_RFKILL_OPMODE, &trans->status); + _iwl_trans_pcie_gen2_stop_device(trans); + iwl_trans_pcie_handle_stop_rfkill(trans, was_in_rfkill); + mutex_unlock(&trans_pcie->mutex); +} + +static int iwl_pcie_gen2_nic_init(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int queue_size = max_t(u32, IWL_CMD_QUEUE_SIZE, + trans->cfg->min_txq_size); + + /* TODO: most of the logic can be removed in A0 - but not in Z0 */ + spin_lock_bh(&trans_pcie->irq_lock); + iwl_pcie_gen2_apm_init(trans); + spin_unlock_bh(&trans_pcie->irq_lock); + + iwl_op_mode_nic_config(trans->op_mode); + + /* Allocate the RX queue, or reset if it is already allocated */ + if (iwl_pcie_gen2_rx_init(trans)) + return -ENOMEM; + + /* Allocate or reset and init all Tx and Command queues */ + if (iwl_txq_gen2_init(trans, trans->txqs.cmd.q_id, queue_size)) + return -ENOMEM; + + /* enable shadow regs in HW */ + iwl_set_bit(trans, CSR_MAC_SHADOW_REG_CTRL, 0x800FFFFF); + IWL_DEBUG_INFO(trans, "Enabling shadow registers in device\n"); + + return 0; +} + +static void iwl_pcie_get_rf_name(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + char *buf = trans_pcie->rf_name; + size_t buflen = sizeof(trans_pcie->rf_name); + size_t pos; + u32 version; + + if (buf[0]) + return; + + switch (CSR_HW_RFID_TYPE(trans->hw_rf_id)) { + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_JF): + pos = scnprintf(buf, buflen, "JF"); + break; + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_GF): + pos = scnprintf(buf, buflen, "GF"); + break; + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_GF4): + pos = scnprintf(buf, buflen, "GF4"); + break; + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_HR): + pos = scnprintf(buf, buflen, "HR"); + break; + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_HR1): + pos = scnprintf(buf, buflen, "HR1"); + break; + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_HRCDB): + pos = scnprintf(buf, buflen, "HRCDB"); + break; + default: + return; + } + + switch (CSR_HW_RFID_TYPE(trans->hw_rf_id)) { + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_HR): + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_HR1): + case CSR_HW_RFID_TYPE(CSR_HW_RF_ID_TYPE_HRCDB): + version = iwl_read_prph(trans, CNVI_MBOX_C); + switch (version) { + case 0x20000: + pos += scnprintf(buf + pos, buflen - pos, " B3"); + break; + case 0x120000: + pos += scnprintf(buf + pos, buflen - pos, " B5"); + break; + default: + pos += scnprintf(buf + pos, buflen - pos, + " (0x%x)", version); + break; + } + break; + default: + break; + } + + pos += scnprintf(buf + pos, buflen - pos, ", rfid=0x%x", + trans->hw_rf_id); + + IWL_INFO(trans, "Detected RF %s\n", buf); + + /* + * also add a \n for debugfs - need to do it after printing + * since our IWL_INFO machinery wants to see a static \n at + * the end of the string + */ + pos += scnprintf(buf + pos, buflen - pos, "\n"); +} + +void iwl_trans_pcie_gen2_fw_alive(struct iwl_trans *trans, u32 scd_addr) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + iwl_pcie_reset_ict(trans); + + /* make sure all queue are not stopped/used */ + memset(trans->txqs.queue_stopped, 0, + sizeof(trans->txqs.queue_stopped)); + memset(trans->txqs.queue_used, 0, sizeof(trans->txqs.queue_used)); + + /* now that we got alive we can free the fw image & the context info. + * paging memory cannot be freed included since FW will still use it + */ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + iwl_pcie_ctxt_info_gen3_free(trans, true); + else + iwl_pcie_ctxt_info_free(trans); + + /* + * Re-enable all the interrupts, including the RF-Kill one, now that + * the firmware is alive. + */ + iwl_enable_interrupts(trans); + mutex_lock(&trans_pcie->mutex); + iwl_pcie_check_hw_rf_kill(trans); + + iwl_pcie_get_rf_name(trans); + mutex_unlock(&trans_pcie->mutex); +} + +static void iwl_pcie_set_ltr(struct iwl_trans *trans) +{ + u32 ltr_val = CSR_LTR_LONG_VAL_AD_NO_SNOOP_REQ | + u32_encode_bits(CSR_LTR_LONG_VAL_AD_SCALE_USEC, + CSR_LTR_LONG_VAL_AD_NO_SNOOP_SCALE) | + u32_encode_bits(250, + CSR_LTR_LONG_VAL_AD_NO_SNOOP_VAL) | + CSR_LTR_LONG_VAL_AD_SNOOP_REQ | + u32_encode_bits(CSR_LTR_LONG_VAL_AD_SCALE_USEC, + CSR_LTR_LONG_VAL_AD_SNOOP_SCALE) | + u32_encode_bits(250, CSR_LTR_LONG_VAL_AD_SNOOP_VAL); + + /* + * To workaround hardware latency issues during the boot process, + * initialize the LTR to ~250 usec (see ltr_val above). + * The firmware initializes this again later (to a smaller value). + */ + if ((trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_AX210 || + trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_22000) && + !trans->trans_cfg->integrated) { + iwl_write32(trans, CSR_LTR_LONG_VAL_AD, ltr_val); + } else if (trans->trans_cfg->integrated && + trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_22000) { + iwl_write_prph(trans, HPM_MAC_LTR_CSR, HPM_MAC_LRT_ENABLE_ALL); + iwl_write_prph(trans, HPM_UMAC_LTR, ltr_val); + } +} + +int iwl_trans_pcie_gen2_start_fw(struct iwl_trans *trans, + const struct fw_img *fw, bool run_in_rfkill) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + bool hw_rfkill; + int ret; + + /* This may fail if AMT took ownership of the device */ + if (iwl_pcie_prepare_card_hw(trans)) { + IWL_WARN(trans, "Exit HW not ready\n"); + return -EIO; + } + + iwl_enable_rfkill_int(trans); + + iwl_write32(trans, CSR_INT, 0xFFFFFFFF); + + /* + * We enabled the RF-Kill interrupt and the handler may very + * well be running. Disable the interrupts to make sure no other + * interrupt can be fired. + */ + iwl_disable_interrupts(trans); + + /* Make sure it finished running */ + iwl_pcie_synchronize_irqs(trans); + + mutex_lock(&trans_pcie->mutex); + + /* If platform's RF_KILL switch is NOT set to KILL */ + hw_rfkill = iwl_pcie_check_hw_rf_kill(trans); + if (hw_rfkill && !run_in_rfkill) { + ret = -ERFKILL; + goto out; + } + + /* Someone called stop_device, don't try to start_fw */ + if (trans_pcie->is_down) { + IWL_WARN(trans, + "Can't start_fw since the HW hasn't been started\n"); + ret = -EIO; + goto out; + } + + /* make sure rfkill handshake bits are cleared */ + iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL); + iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, + CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED); + + /* clear (again), then enable host interrupts */ + iwl_write32(trans, CSR_INT, 0xFFFFFFFF); + + ret = iwl_pcie_gen2_nic_init(trans); + if (ret) { + IWL_ERR(trans, "Unable to init nic\n"); + goto out; + } + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + ret = iwl_pcie_ctxt_info_gen3_init(trans, fw); + else + ret = iwl_pcie_ctxt_info_init(trans, fw); + if (ret) + goto out; + + iwl_pcie_set_ltr(trans); + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) { + iwl_write32(trans, CSR_FUNC_SCRATCH, CSR_FUNC_SCRATCH_INIT_VALUE); + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_ROM_START); + } else if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + iwl_write_umac_prph(trans, UREG_CPU_INIT_RUN, 1); + } else { + iwl_write_prph(trans, UREG_CPU_INIT_RUN, 1); + } + + /* re-check RF-Kill state since we may have missed the interrupt */ + hw_rfkill = iwl_pcie_check_hw_rf_kill(trans); + if (hw_rfkill && !run_in_rfkill) + ret = -ERFKILL; + +out: + mutex_unlock(&trans_pcie->mutex); + return ret; +} diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c new file mode 100644 index 000000000..c7ed35b3d --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c @@ -0,0 +1,3735 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2007-2015, 2018-2022 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/debugfs.h> +#include <linux/sched.h> +#include <linux/bitops.h> +#include <linux/gfp.h> +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <linux/wait.h> +#include <linux/seq_file.h> + +#include "iwl-drv.h" +#include "iwl-trans.h" +#include "iwl-csr.h" +#include "iwl-prph.h" +#include "iwl-scd.h" +#include "iwl-agn-hw.h" +#include "fw/error-dump.h" +#include "fw/dbg.h" +#include "fw/api/tx.h" +#include "mei/iwl-mei.h" +#include "internal.h" +#include "iwl-fh.h" +#include "iwl-context-info-gen3.h" + +/* extended range in FW SRAM */ +#define IWL_FW_MEM_EXTENDED_START 0x40000 +#define IWL_FW_MEM_EXTENDED_END 0x57FFF + +void iwl_trans_pcie_dump_regs(struct iwl_trans *trans) +{ +#define PCI_DUMP_SIZE 352 +#define PCI_MEM_DUMP_SIZE 64 +#define PCI_PARENT_DUMP_SIZE 524 +#define PREFIX_LEN 32 + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct pci_dev *pdev = trans_pcie->pci_dev; + u32 i, pos, alloc_size, *ptr, *buf; + char *prefix; + + if (trans_pcie->pcie_dbg_dumped_once) + return; + + /* Should be a multiple of 4 */ + BUILD_BUG_ON(PCI_DUMP_SIZE > 4096 || PCI_DUMP_SIZE & 0x3); + BUILD_BUG_ON(PCI_MEM_DUMP_SIZE > 4096 || PCI_MEM_DUMP_SIZE & 0x3); + BUILD_BUG_ON(PCI_PARENT_DUMP_SIZE > 4096 || PCI_PARENT_DUMP_SIZE & 0x3); + + /* Alloc a max size buffer */ + alloc_size = PCI_ERR_ROOT_ERR_SRC + 4 + PREFIX_LEN; + alloc_size = max_t(u32, alloc_size, PCI_DUMP_SIZE + PREFIX_LEN); + alloc_size = max_t(u32, alloc_size, PCI_MEM_DUMP_SIZE + PREFIX_LEN); + alloc_size = max_t(u32, alloc_size, PCI_PARENT_DUMP_SIZE + PREFIX_LEN); + + buf = kmalloc(alloc_size, GFP_ATOMIC); + if (!buf) + return; + prefix = (char *)buf + alloc_size - PREFIX_LEN; + + IWL_ERR(trans, "iwlwifi transaction failed, dumping registers\n"); + + /* Print wifi device registers */ + sprintf(prefix, "iwlwifi %s: ", pci_name(pdev)); + IWL_ERR(trans, "iwlwifi device config registers:\n"); + for (i = 0, ptr = buf; i < PCI_DUMP_SIZE; i += 4, ptr++) + if (pci_read_config_dword(pdev, i, ptr)) + goto err_read; + print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET, 32, 4, buf, i, 0); + + IWL_ERR(trans, "iwlwifi device memory mapped registers:\n"); + for (i = 0, ptr = buf; i < PCI_MEM_DUMP_SIZE; i += 4, ptr++) + *ptr = iwl_read32(trans, i); + print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET, 32, 4, buf, i, 0); + + pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ERR); + if (pos) { + IWL_ERR(trans, "iwlwifi device AER capability structure:\n"); + for (i = 0, ptr = buf; i < PCI_ERR_ROOT_COMMAND; i += 4, ptr++) + if (pci_read_config_dword(pdev, pos + i, ptr)) + goto err_read; + print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET, + 32, 4, buf, i, 0); + } + + /* Print parent device registers next */ + if (!pdev->bus->self) + goto out; + + pdev = pdev->bus->self; + sprintf(prefix, "iwlwifi %s: ", pci_name(pdev)); + + IWL_ERR(trans, "iwlwifi parent port (%s) config registers:\n", + pci_name(pdev)); + for (i = 0, ptr = buf; i < PCI_PARENT_DUMP_SIZE; i += 4, ptr++) + if (pci_read_config_dword(pdev, i, ptr)) + goto err_read; + print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET, 32, 4, buf, i, 0); + + /* Print root port AER registers */ + pos = 0; + pdev = pcie_find_root_port(pdev); + if (pdev) + pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ERR); + if (pos) { + IWL_ERR(trans, "iwlwifi root port (%s) AER cap structure:\n", + pci_name(pdev)); + sprintf(prefix, "iwlwifi %s: ", pci_name(pdev)); + for (i = 0, ptr = buf; i <= PCI_ERR_ROOT_ERR_SRC; i += 4, ptr++) + if (pci_read_config_dword(pdev, pos + i, ptr)) + goto err_read; + print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET, 32, + 4, buf, i, 0); + } + goto out; + +err_read: + print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET, 32, 4, buf, i, 0); + IWL_ERR(trans, "Read failed at 0x%X\n", i); +out: + trans_pcie->pcie_dbg_dumped_once = 1; + kfree(buf); +} + +static int iwl_trans_pcie_sw_reset(struct iwl_trans *trans, + bool retake_ownership) +{ + /* Reset entire device - do controller reset (results in SHRD_HW_RST) */ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_SW_RESET); + else + iwl_set_bit(trans, CSR_RESET, + CSR_RESET_REG_FLAG_SW_RESET); + usleep_range(5000, 6000); + + if (retake_ownership) + return iwl_pcie_prepare_card_hw(trans); + + return 0; +} + +static void iwl_pcie_free_fw_monitor(struct iwl_trans *trans) +{ + struct iwl_dram_data *fw_mon = &trans->dbg.fw_mon; + + if (!fw_mon->size) + return; + + dma_free_coherent(trans->dev, fw_mon->size, fw_mon->block, + fw_mon->physical); + + fw_mon->block = NULL; + fw_mon->physical = 0; + fw_mon->size = 0; +} + +static void iwl_pcie_alloc_fw_monitor_block(struct iwl_trans *trans, + u8 max_power, u8 min_power) +{ + struct iwl_dram_data *fw_mon = &trans->dbg.fw_mon; + void *block = NULL; + dma_addr_t physical = 0; + u32 size = 0; + u8 power; + + if (fw_mon->size) + return; + + for (power = max_power; power >= min_power; power--) { + size = BIT(power); + block = dma_alloc_coherent(trans->dev, size, &physical, + GFP_KERNEL | __GFP_NOWARN); + if (!block) + continue; + + IWL_INFO(trans, + "Allocated 0x%08x bytes for firmware monitor.\n", + size); + break; + } + + if (WARN_ON_ONCE(!block)) + return; + + if (power != max_power) + IWL_ERR(trans, + "Sorry - debug buffer is only %luK while you requested %luK\n", + (unsigned long)BIT(power - 10), + (unsigned long)BIT(max_power - 10)); + + fw_mon->block = block; + fw_mon->physical = physical; + fw_mon->size = size; +} + +void iwl_pcie_alloc_fw_monitor(struct iwl_trans *trans, u8 max_power) +{ + if (!max_power) { + /* default max_power is maximum */ + max_power = 26; + } else { + max_power += 11; + } + + if (WARN(max_power > 26, + "External buffer size for monitor is too big %d, check the FW TLV\n", + max_power)) + return; + + if (trans->dbg.fw_mon.size) + return; + + iwl_pcie_alloc_fw_monitor_block(trans, max_power, 11); +} + +static u32 iwl_trans_pcie_read_shr(struct iwl_trans *trans, u32 reg) +{ + iwl_write32(trans, HEEP_CTRL_WRD_PCIEX_CTRL_REG, + ((reg & 0x0000ffff) | (2 << 28))); + return iwl_read32(trans, HEEP_CTRL_WRD_PCIEX_DATA_REG); +} + +static void iwl_trans_pcie_write_shr(struct iwl_trans *trans, u32 reg, u32 val) +{ + iwl_write32(trans, HEEP_CTRL_WRD_PCIEX_DATA_REG, val); + iwl_write32(trans, HEEP_CTRL_WRD_PCIEX_CTRL_REG, + ((reg & 0x0000ffff) | (3 << 28))); +} + +static void iwl_pcie_set_pwr(struct iwl_trans *trans, bool vaux) +{ + if (trans->cfg->apmg_not_supported) + return; + + if (vaux && pci_pme_capable(to_pci_dev(trans->dev), PCI_D3cold)) + iwl_set_bits_mask_prph(trans, APMG_PS_CTRL_REG, + APMG_PS_CTRL_VAL_PWR_SRC_VAUX, + ~APMG_PS_CTRL_MSK_PWR_SRC); + else + iwl_set_bits_mask_prph(trans, APMG_PS_CTRL_REG, + APMG_PS_CTRL_VAL_PWR_SRC_VMAIN, + ~APMG_PS_CTRL_MSK_PWR_SRC); +} + +/* PCI registers */ +#define PCI_CFG_RETRY_TIMEOUT 0x041 + +void iwl_pcie_apm_config(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u16 lctl; + u16 cap; + + /* + * L0S states have been found to be unstable with our devices + * and in newer hardware they are not officially supported at + * all, so we must always set the L0S_DISABLED bit. + */ + iwl_set_bit(trans, CSR_GIO_REG, CSR_GIO_REG_VAL_L0S_DISABLED); + + pcie_capability_read_word(trans_pcie->pci_dev, PCI_EXP_LNKCTL, &lctl); + trans->pm_support = !(lctl & PCI_EXP_LNKCTL_ASPM_L0S); + + pcie_capability_read_word(trans_pcie->pci_dev, PCI_EXP_DEVCTL2, &cap); + trans->ltr_enabled = cap & PCI_EXP_DEVCTL2_LTR_EN; + IWL_DEBUG_POWER(trans, "L1 %sabled - LTR %sabled\n", + (lctl & PCI_EXP_LNKCTL_ASPM_L1) ? "En" : "Dis", + trans->ltr_enabled ? "En" : "Dis"); +} + +/* + * Start up NIC's basic functionality after it has been reset + * (e.g. after platform boot, or shutdown via iwl_pcie_apm_stop()) + * NOTE: This does not load uCode nor start the embedded processor + */ +static int iwl_pcie_apm_init(struct iwl_trans *trans) +{ + int ret; + + IWL_DEBUG_INFO(trans, "Init card's basic functions\n"); + + /* + * Use "set_bit" below rather than "write", to preserve any hardware + * bits already set by default after reset. + */ + + /* Disable L0S exit timer (platform NMI Work/Around) */ + if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_8000) + iwl_set_bit(trans, CSR_GIO_CHICKEN_BITS, + CSR_GIO_CHICKEN_BITS_REG_BIT_DIS_L0S_EXIT_TIMER); + + /* + * Disable L0s without affecting L1; + * don't wait for ICH L0s (ICH bug W/A) + */ + iwl_set_bit(trans, CSR_GIO_CHICKEN_BITS, + CSR_GIO_CHICKEN_BITS_REG_BIT_L1A_NO_L0S_RX); + + /* Set FH wait threshold to maximum (HW error during stress W/A) */ + iwl_set_bit(trans, CSR_DBG_HPET_MEM_REG, CSR_DBG_HPET_MEM_REG_VAL); + + /* + * Enable HAP INTA (interrupt from management bus) to + * wake device's PCI Express link L1a -> L0s + */ + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_HAP_WAKE_L1A); + + iwl_pcie_apm_config(trans); + + /* Configure analog phase-lock-loop before activating to D0A */ + if (trans->trans_cfg->base_params->pll_cfg) + iwl_set_bit(trans, CSR_ANA_PLL_CFG, CSR50_ANA_PLL_CFG_VAL); + + ret = iwl_finish_nic_init(trans); + if (ret) + return ret; + + if (trans->cfg->host_interrupt_operation_mode) { + /* + * This is a bit of an abuse - This is needed for 7260 / 3160 + * only check host_interrupt_operation_mode even if this is + * not related to host_interrupt_operation_mode. + * + * Enable the oscillator to count wake up time for L1 exit. This + * consumes slightly more power (100uA) - but allows to be sure + * that we wake up from L1 on time. + * + * This looks weird: read twice the same register, discard the + * value, set a bit, and yet again, read that same register + * just to discard the value. But that's the way the hardware + * seems to like it. + */ + iwl_read_prph(trans, OSC_CLK); + iwl_read_prph(trans, OSC_CLK); + iwl_set_bits_prph(trans, OSC_CLK, OSC_CLK_FORCE_CONTROL); + iwl_read_prph(trans, OSC_CLK); + iwl_read_prph(trans, OSC_CLK); + } + + /* + * Enable DMA clock and wait for it to stabilize. + * + * Write to "CLK_EN_REG"; "1" bits enable clocks, while "0" + * bits do not disable clocks. This preserves any hardware + * bits already set by default in "CLK_CTRL_REG" after reset. + */ + if (!trans->cfg->apmg_not_supported) { + iwl_write_prph(trans, APMG_CLK_EN_REG, + APMG_CLK_VAL_DMA_CLK_RQT); + udelay(20); + + /* Disable L1-Active */ + iwl_set_bits_prph(trans, APMG_PCIDEV_STT_REG, + APMG_PCIDEV_STT_VAL_L1_ACT_DIS); + + /* Clear the interrupt in APMG if the NIC is in RFKILL */ + iwl_write_prph(trans, APMG_RTC_INT_STT_REG, + APMG_RTC_INT_STT_RFKILL); + } + + set_bit(STATUS_DEVICE_ENABLED, &trans->status); + + return 0; +} + +/* + * Enable LP XTAL to avoid HW bug where device may consume much power if + * FW is not loaded after device reset. LP XTAL is disabled by default + * after device HW reset. Do it only if XTAL is fed by internal source. + * Configure device's "persistence" mode to avoid resetting XTAL again when + * SHRD_HW_RST occurs in S3. + */ +static void iwl_pcie_apm_lp_xtal_enable(struct iwl_trans *trans) +{ + int ret; + u32 apmg_gp1_reg; + u32 apmg_xtal_cfg_reg; + u32 dl_cfg_reg; + + /* Force XTAL ON */ + __iwl_trans_pcie_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_XTAL_ON); + + ret = iwl_trans_pcie_sw_reset(trans, true); + + if (!ret) + ret = iwl_finish_nic_init(trans); + + if (WARN_ON(ret)) { + /* Release XTAL ON request */ + __iwl_trans_pcie_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_XTAL_ON); + return; + } + + /* + * Clear "disable persistence" to avoid LP XTAL resetting when + * SHRD_HW_RST is applied in S3. + */ + iwl_clear_bits_prph(trans, APMG_PCIDEV_STT_REG, + APMG_PCIDEV_STT_VAL_PERSIST_DIS); + + /* + * Force APMG XTAL to be active to prevent its disabling by HW + * caused by APMG idle state. + */ + apmg_xtal_cfg_reg = iwl_trans_pcie_read_shr(trans, + SHR_APMG_XTAL_CFG_REG); + iwl_trans_pcie_write_shr(trans, SHR_APMG_XTAL_CFG_REG, + apmg_xtal_cfg_reg | + SHR_APMG_XTAL_CFG_XTAL_ON_REQ); + + ret = iwl_trans_pcie_sw_reset(trans, true); + if (ret) + IWL_ERR(trans, + "iwl_pcie_apm_lp_xtal_enable: failed to retake NIC ownership\n"); + + /* Enable LP XTAL by indirect access through CSR */ + apmg_gp1_reg = iwl_trans_pcie_read_shr(trans, SHR_APMG_GP1_REG); + iwl_trans_pcie_write_shr(trans, SHR_APMG_GP1_REG, apmg_gp1_reg | + SHR_APMG_GP1_WF_XTAL_LP_EN | + SHR_APMG_GP1_CHICKEN_BIT_SELECT); + + /* Clear delay line clock power up */ + dl_cfg_reg = iwl_trans_pcie_read_shr(trans, SHR_APMG_DL_CFG_REG); + iwl_trans_pcie_write_shr(trans, SHR_APMG_DL_CFG_REG, dl_cfg_reg & + ~SHR_APMG_DL_CFG_DL_CLOCK_POWER_UP); + + /* + * Enable persistence mode to avoid LP XTAL resetting when + * SHRD_HW_RST is applied in S3. + */ + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_PERSIST_MODE); + + /* + * Clear "initialization complete" bit to move adapter from + * D0A* (powered-up Active) --> D0U* (Uninitialized) state. + */ + iwl_clear_bit(trans, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE); + + /* Activates XTAL resources monitor */ + __iwl_trans_pcie_set_bit(trans, CSR_MONITOR_CFG_REG, + CSR_MONITOR_XTAL_RESOURCES); + + /* Release XTAL ON request */ + __iwl_trans_pcie_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_XTAL_ON); + udelay(10); + + /* Release APMG XTAL */ + iwl_trans_pcie_write_shr(trans, SHR_APMG_XTAL_CFG_REG, + apmg_xtal_cfg_reg & + ~SHR_APMG_XTAL_CFG_XTAL_ON_REQ); +} + +void iwl_pcie_apm_stop_master(struct iwl_trans *trans) +{ + int ret; + + /* stop device's busmaster DMA activity */ + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) { + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_BUS_MASTER_DISABLE_REQ); + + ret = iwl_poll_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_BUS_MASTER_DISABLE_STATUS, + CSR_GP_CNTRL_REG_FLAG_BUS_MASTER_DISABLE_STATUS, + 100); + msleep(100); + } else { + iwl_set_bit(trans, CSR_RESET, CSR_RESET_REG_FLAG_STOP_MASTER); + + ret = iwl_poll_bit(trans, CSR_RESET, + CSR_RESET_REG_FLAG_MASTER_DISABLED, + CSR_RESET_REG_FLAG_MASTER_DISABLED, 100); + } + + if (ret < 0) + IWL_WARN(trans, "Master Disable Timed Out, 100 usec\n"); + + IWL_DEBUG_INFO(trans, "stop master\n"); +} + +static void iwl_pcie_apm_stop(struct iwl_trans *trans, bool op_mode_leave) +{ + IWL_DEBUG_INFO(trans, "Stop card, put in low power state\n"); + + if (op_mode_leave) { + if (!test_bit(STATUS_DEVICE_ENABLED, &trans->status)) + iwl_pcie_apm_init(trans); + + /* inform ME that we are leaving */ + if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_7000) + iwl_set_bits_prph(trans, APMG_PCIDEV_STT_REG, + APMG_PCIDEV_STT_VAL_WAKE_ME); + else if (trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_8000) { + iwl_set_bit(trans, CSR_DBG_LINK_PWR_MGMT_REG, + CSR_RESET_LINK_PWR_MGMT_DISABLED); + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_PREPARE | + CSR_HW_IF_CONFIG_REG_ENABLE_PME); + mdelay(1); + iwl_clear_bit(trans, CSR_DBG_LINK_PWR_MGMT_REG, + CSR_RESET_LINK_PWR_MGMT_DISABLED); + } + mdelay(5); + } + + clear_bit(STATUS_DEVICE_ENABLED, &trans->status); + + /* Stop device's DMA activity */ + iwl_pcie_apm_stop_master(trans); + + if (trans->cfg->lp_xtal_workaround) { + iwl_pcie_apm_lp_xtal_enable(trans); + return; + } + + iwl_trans_pcie_sw_reset(trans, false); + + /* + * Clear "initialization complete" bit to move adapter from + * D0A* (powered-up Active) --> D0U* (Uninitialized) state. + */ + iwl_clear_bit(trans, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE); +} + +static int iwl_pcie_nic_init(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret; + + /* nic_init */ + spin_lock_bh(&trans_pcie->irq_lock); + ret = iwl_pcie_apm_init(trans); + spin_unlock_bh(&trans_pcie->irq_lock); + + if (ret) + return ret; + + iwl_pcie_set_pwr(trans, false); + + iwl_op_mode_nic_config(trans->op_mode); + + /* Allocate the RX queue, or reset if it is already allocated */ + ret = iwl_pcie_rx_init(trans); + if (ret) + return ret; + + /* Allocate or reset and init all Tx and Command queues */ + if (iwl_pcie_tx_init(trans)) { + iwl_pcie_rx_free(trans); + return -ENOMEM; + } + + if (trans->trans_cfg->base_params->shadow_reg_enable) { + /* enable shadow regs in HW */ + iwl_set_bit(trans, CSR_MAC_SHADOW_REG_CTRL, 0x800FFFFF); + IWL_DEBUG_INFO(trans, "Enabling shadow registers in device\n"); + } + + return 0; +} + +#define HW_READY_TIMEOUT (50) + +/* Note: returns poll_bit return value, which is >= 0 if success */ +static int iwl_pcie_set_hw_ready(struct iwl_trans *trans) +{ + int ret; + + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_NIC_READY); + + /* See if we got it */ + ret = iwl_poll_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_NIC_READY, + CSR_HW_IF_CONFIG_REG_BIT_NIC_READY, + HW_READY_TIMEOUT); + + if (ret >= 0) + iwl_set_bit(trans, CSR_MBOX_SET_REG, CSR_MBOX_SET_REG_OS_ALIVE); + + IWL_DEBUG_INFO(trans, "hardware%s ready\n", ret < 0 ? " not" : ""); + return ret; +} + +/* Note: returns standard 0/-ERROR code */ +int iwl_pcie_prepare_card_hw(struct iwl_trans *trans) +{ + int ret; + int iter; + + IWL_DEBUG_INFO(trans, "iwl_trans_prepare_card_hw enter\n"); + + ret = iwl_pcie_set_hw_ready(trans); + /* If the card is ready, exit 0 */ + if (ret >= 0) { + trans->csme_own = false; + return 0; + } + + iwl_set_bit(trans, CSR_DBG_LINK_PWR_MGMT_REG, + CSR_RESET_LINK_PWR_MGMT_DISABLED); + usleep_range(1000, 2000); + + for (iter = 0; iter < 10; iter++) { + int t = 0; + + /* If HW is not ready, prepare the conditions to check again */ + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_PREPARE); + + do { + ret = iwl_pcie_set_hw_ready(trans); + if (ret >= 0) { + trans->csme_own = false; + return 0; + } + + if (iwl_mei_is_connected()) { + IWL_DEBUG_INFO(trans, + "Couldn't prepare the card but SAP is connected\n"); + trans->csme_own = true; + if (trans->trans_cfg->device_family != + IWL_DEVICE_FAMILY_9000) + IWL_ERR(trans, + "SAP not supported for this NIC family\n"); + + return -EBUSY; + } + + usleep_range(200, 1000); + t += 200; + } while (t < 150000); + msleep(25); + } + + IWL_ERR(trans, "Couldn't prepare the card\n"); + + return ret; +} + +/* + * ucode + */ +static void iwl_pcie_load_firmware_chunk_fh(struct iwl_trans *trans, + u32 dst_addr, dma_addr_t phy_addr, + u32 byte_cnt) +{ + iwl_write32(trans, FH_TCSR_CHNL_TX_CONFIG_REG(FH_SRVC_CHNL), + FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE); + + iwl_write32(trans, FH_SRVC_CHNL_SRAM_ADDR_REG(FH_SRVC_CHNL), + dst_addr); + + iwl_write32(trans, FH_TFDIB_CTRL0_REG(FH_SRVC_CHNL), + phy_addr & FH_MEM_TFDIB_DRAM_ADDR_LSB_MSK); + + iwl_write32(trans, FH_TFDIB_CTRL1_REG(FH_SRVC_CHNL), + (iwl_get_dma_hi_addr(phy_addr) + << FH_MEM_TFDIB_REG1_ADDR_BITSHIFT) | byte_cnt); + + iwl_write32(trans, FH_TCSR_CHNL_TX_BUF_STS_REG(FH_SRVC_CHNL), + BIT(FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_NUM) | + BIT(FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_IDX) | + FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_VALID); + + iwl_write32(trans, FH_TCSR_CHNL_TX_CONFIG_REG(FH_SRVC_CHNL), + FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE | + FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_DISABLE | + FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_ENDTFD); +} + +static int iwl_pcie_load_firmware_chunk(struct iwl_trans *trans, + u32 dst_addr, dma_addr_t phy_addr, + u32 byte_cnt) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret; + + trans_pcie->ucode_write_complete = false; + + if (!iwl_trans_grab_nic_access(trans)) + return -EIO; + + iwl_pcie_load_firmware_chunk_fh(trans, dst_addr, phy_addr, + byte_cnt); + iwl_trans_release_nic_access(trans); + + ret = wait_event_timeout(trans_pcie->ucode_write_waitq, + trans_pcie->ucode_write_complete, 5 * HZ); + if (!ret) { + IWL_ERR(trans, "Failed to load firmware chunk!\n"); + iwl_trans_pcie_dump_regs(trans); + return -ETIMEDOUT; + } + + return 0; +} + +static int iwl_pcie_load_section(struct iwl_trans *trans, u8 section_num, + const struct fw_desc *section) +{ + u8 *v_addr; + dma_addr_t p_addr; + u32 offset, chunk_sz = min_t(u32, FH_MEM_TB_MAX_LENGTH, section->len); + int ret = 0; + + IWL_DEBUG_FW(trans, "[%d] uCode section being loaded...\n", + section_num); + + v_addr = dma_alloc_coherent(trans->dev, chunk_sz, &p_addr, + GFP_KERNEL | __GFP_NOWARN); + if (!v_addr) { + IWL_DEBUG_INFO(trans, "Falling back to small chunks of DMA\n"); + chunk_sz = PAGE_SIZE; + v_addr = dma_alloc_coherent(trans->dev, chunk_sz, + &p_addr, GFP_KERNEL); + if (!v_addr) + return -ENOMEM; + } + + for (offset = 0; offset < section->len; offset += chunk_sz) { + u32 copy_size, dst_addr; + bool extended_addr = false; + + copy_size = min_t(u32, chunk_sz, section->len - offset); + dst_addr = section->offset + offset; + + if (dst_addr >= IWL_FW_MEM_EXTENDED_START && + dst_addr <= IWL_FW_MEM_EXTENDED_END) + extended_addr = true; + + if (extended_addr) + iwl_set_bits_prph(trans, LMPM_CHICK, + LMPM_CHICK_EXTENDED_ADDR_SPACE); + + memcpy(v_addr, (const u8 *)section->data + offset, copy_size); + ret = iwl_pcie_load_firmware_chunk(trans, dst_addr, p_addr, + copy_size); + + if (extended_addr) + iwl_clear_bits_prph(trans, LMPM_CHICK, + LMPM_CHICK_EXTENDED_ADDR_SPACE); + + if (ret) { + IWL_ERR(trans, + "Could not load the [%d] uCode section\n", + section_num); + break; + } + } + + dma_free_coherent(trans->dev, chunk_sz, v_addr, p_addr); + return ret; +} + +static int iwl_pcie_load_cpu_sections_8000(struct iwl_trans *trans, + const struct fw_img *image, + int cpu, + int *first_ucode_section) +{ + int shift_param; + int i, ret = 0, sec_num = 0x1; + u32 val, last_read_idx = 0; + + if (cpu == 1) { + shift_param = 0; + *first_ucode_section = 0; + } else { + shift_param = 16; + (*first_ucode_section)++; + } + + for (i = *first_ucode_section; i < image->num_sec; i++) { + last_read_idx = i; + + /* + * CPU1_CPU2_SEPARATOR_SECTION delimiter - separate between + * CPU1 to CPU2. + * PAGING_SEPARATOR_SECTION delimiter - separate between + * CPU2 non paged to CPU2 paging sec. + */ + if (!image->sec[i].data || + image->sec[i].offset == CPU1_CPU2_SEPARATOR_SECTION || + image->sec[i].offset == PAGING_SEPARATOR_SECTION) { + IWL_DEBUG_FW(trans, + "Break since Data not valid or Empty section, sec = %d\n", + i); + break; + } + + ret = iwl_pcie_load_section(trans, i, &image->sec[i]); + if (ret) + return ret; + + /* Notify ucode of loaded section number and status */ + val = iwl_read_direct32(trans, FH_UCODE_LOAD_STATUS); + val = val | (sec_num << shift_param); + iwl_write_direct32(trans, FH_UCODE_LOAD_STATUS, val); + + sec_num = (sec_num << 1) | 0x1; + } + + *first_ucode_section = last_read_idx; + + iwl_enable_interrupts(trans); + + if (trans->trans_cfg->use_tfh) { + if (cpu == 1) + iwl_write_prph(trans, UREG_UCODE_LOAD_STATUS, + 0xFFFF); + else + iwl_write_prph(trans, UREG_UCODE_LOAD_STATUS, + 0xFFFFFFFF); + } else { + if (cpu == 1) + iwl_write_direct32(trans, FH_UCODE_LOAD_STATUS, + 0xFFFF); + else + iwl_write_direct32(trans, FH_UCODE_LOAD_STATUS, + 0xFFFFFFFF); + } + + return 0; +} + +static int iwl_pcie_load_cpu_sections(struct iwl_trans *trans, + const struct fw_img *image, + int cpu, + int *first_ucode_section) +{ + int i, ret = 0; + u32 last_read_idx = 0; + + if (cpu == 1) + *first_ucode_section = 0; + else + (*first_ucode_section)++; + + for (i = *first_ucode_section; i < image->num_sec; i++) { + last_read_idx = i; + + /* + * CPU1_CPU2_SEPARATOR_SECTION delimiter - separate between + * CPU1 to CPU2. + * PAGING_SEPARATOR_SECTION delimiter - separate between + * CPU2 non paged to CPU2 paging sec. + */ + if (!image->sec[i].data || + image->sec[i].offset == CPU1_CPU2_SEPARATOR_SECTION || + image->sec[i].offset == PAGING_SEPARATOR_SECTION) { + IWL_DEBUG_FW(trans, + "Break since Data not valid or Empty section, sec = %d\n", + i); + break; + } + + ret = iwl_pcie_load_section(trans, i, &image->sec[i]); + if (ret) + return ret; + } + + *first_ucode_section = last_read_idx; + + return 0; +} + +static void iwl_pcie_apply_destination_ini(struct iwl_trans *trans) +{ + enum iwl_fw_ini_allocation_id alloc_id = IWL_FW_INI_ALLOCATION_ID_DBGC1; + struct iwl_fw_ini_allocation_tlv *fw_mon_cfg = + &trans->dbg.fw_mon_cfg[alloc_id]; + struct iwl_dram_data *frag; + + if (!iwl_trans_dbg_ini_valid(trans)) + return; + + if (le32_to_cpu(fw_mon_cfg->buf_location) == + IWL_FW_INI_LOCATION_SRAM_PATH) { + IWL_DEBUG_FW(trans, "WRT: Applying SMEM buffer destination\n"); + /* set sram monitor by enabling bit 7 */ + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_MONITOR_SRAM); + + return; + } + + if (le32_to_cpu(fw_mon_cfg->buf_location) != + IWL_FW_INI_LOCATION_DRAM_PATH || + !trans->dbg.fw_mon_ini[alloc_id].num_frags) + return; + + frag = &trans->dbg.fw_mon_ini[alloc_id].frags[0]; + + IWL_DEBUG_FW(trans, "WRT: Applying DRAM destination (alloc_id=%u)\n", + alloc_id); + + iwl_write_umac_prph(trans, MON_BUFF_BASE_ADDR_VER2, + frag->physical >> MON_BUFF_SHIFT_VER2); + iwl_write_umac_prph(trans, MON_BUFF_END_ADDR_VER2, + (frag->physical + frag->size - 256) >> + MON_BUFF_SHIFT_VER2); +} + +void iwl_pcie_apply_destination(struct iwl_trans *trans) +{ + const struct iwl_fw_dbg_dest_tlv_v1 *dest = trans->dbg.dest_tlv; + const struct iwl_dram_data *fw_mon = &trans->dbg.fw_mon; + int i; + + if (iwl_trans_dbg_ini_valid(trans)) { + iwl_pcie_apply_destination_ini(trans); + return; + } + + IWL_INFO(trans, "Applying debug destination %s\n", + get_fw_dbg_mode_string(dest->monitor_mode)); + + if (dest->monitor_mode == EXTERNAL_MODE) + iwl_pcie_alloc_fw_monitor(trans, dest->size_power); + else + IWL_WARN(trans, "PCI should have external buffer debug\n"); + + for (i = 0; i < trans->dbg.n_dest_reg; i++) { + u32 addr = le32_to_cpu(dest->reg_ops[i].addr); + u32 val = le32_to_cpu(dest->reg_ops[i].val); + + switch (dest->reg_ops[i].op) { + case CSR_ASSIGN: + iwl_write32(trans, addr, val); + break; + case CSR_SETBIT: + iwl_set_bit(trans, addr, BIT(val)); + break; + case CSR_CLEARBIT: + iwl_clear_bit(trans, addr, BIT(val)); + break; + case PRPH_ASSIGN: + iwl_write_prph(trans, addr, val); + break; + case PRPH_SETBIT: + iwl_set_bits_prph(trans, addr, BIT(val)); + break; + case PRPH_CLEARBIT: + iwl_clear_bits_prph(trans, addr, BIT(val)); + break; + case PRPH_BLOCKBIT: + if (iwl_read_prph(trans, addr) & BIT(val)) { + IWL_ERR(trans, + "BIT(%u) in address 0x%x is 1, stopping FW configuration\n", + val, addr); + goto monitor; + } + break; + default: + IWL_ERR(trans, "FW debug - unknown OP %d\n", + dest->reg_ops[i].op); + break; + } + } + +monitor: + if (dest->monitor_mode == EXTERNAL_MODE && fw_mon->size) { + iwl_write_prph(trans, le32_to_cpu(dest->base_reg), + fw_mon->physical >> dest->base_shift); + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_8000) + iwl_write_prph(trans, le32_to_cpu(dest->end_reg), + (fw_mon->physical + fw_mon->size - + 256) >> dest->end_shift); + else + iwl_write_prph(trans, le32_to_cpu(dest->end_reg), + (fw_mon->physical + fw_mon->size) >> + dest->end_shift); + } +} + +static int iwl_pcie_load_given_ucode(struct iwl_trans *trans, + const struct fw_img *image) +{ + int ret = 0; + int first_ucode_section; + + IWL_DEBUG_FW(trans, "working with %s CPU\n", + image->is_dual_cpus ? "Dual" : "Single"); + + /* load to FW the binary non secured sections of CPU1 */ + ret = iwl_pcie_load_cpu_sections(trans, image, 1, &first_ucode_section); + if (ret) + return ret; + + if (image->is_dual_cpus) { + /* set CPU2 header address */ + iwl_write_prph(trans, + LMPM_SECURE_UCODE_LOAD_CPU2_HDR_ADDR, + LMPM_SECURE_CPU2_HDR_MEM_SPACE); + + /* load to FW the binary sections of CPU2 */ + ret = iwl_pcie_load_cpu_sections(trans, image, 2, + &first_ucode_section); + if (ret) + return ret; + } + + if (iwl_pcie_dbg_on(trans)) + iwl_pcie_apply_destination(trans); + + iwl_enable_interrupts(trans); + + /* release CPU reset */ + iwl_write32(trans, CSR_RESET, 0); + + return 0; +} + +static int iwl_pcie_load_given_ucode_8000(struct iwl_trans *trans, + const struct fw_img *image) +{ + int ret = 0; + int first_ucode_section; + + IWL_DEBUG_FW(trans, "working with %s CPU\n", + image->is_dual_cpus ? "Dual" : "Single"); + + if (iwl_pcie_dbg_on(trans)) + iwl_pcie_apply_destination(trans); + + IWL_DEBUG_POWER(trans, "Original WFPM value = 0x%08X\n", + iwl_read_prph(trans, WFPM_GP2)); + + /* + * Set default value. On resume reading the values that were + * zeored can provide debug data on the resume flow. + * This is for debugging only and has no functional impact. + */ + iwl_write_prph(trans, WFPM_GP2, 0x01010101); + + /* configure the ucode to be ready to get the secured image */ + /* release CPU reset */ + iwl_write_prph(trans, RELEASE_CPU_RESET, RELEASE_CPU_RESET_BIT); + + /* load to FW the binary Secured sections of CPU1 */ + ret = iwl_pcie_load_cpu_sections_8000(trans, image, 1, + &first_ucode_section); + if (ret) + return ret; + + /* load to FW the binary sections of CPU2 */ + return iwl_pcie_load_cpu_sections_8000(trans, image, 2, + &first_ucode_section); +} + +bool iwl_pcie_check_hw_rf_kill(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + bool hw_rfkill = iwl_is_rfkill_set(trans); + bool prev = test_bit(STATUS_RFKILL_OPMODE, &trans->status); + bool report; + + if (hw_rfkill) { + set_bit(STATUS_RFKILL_HW, &trans->status); + set_bit(STATUS_RFKILL_OPMODE, &trans->status); + } else { + clear_bit(STATUS_RFKILL_HW, &trans->status); + if (trans_pcie->opmode_down) + clear_bit(STATUS_RFKILL_OPMODE, &trans->status); + } + + report = test_bit(STATUS_RFKILL_OPMODE, &trans->status); + + if (prev != report) + iwl_trans_pcie_rf_kill(trans, report, false); + + return hw_rfkill; +} + +struct iwl_causes_list { + u16 mask_reg; + u8 bit; + u8 addr; +}; + +#define IWL_CAUSE(reg, mask) \ + { \ + .mask_reg = reg, \ + .bit = ilog2(mask), \ + .addr = ilog2(mask) + \ + ((reg) == CSR_MSIX_FH_INT_MASK_AD ? -16 : \ + (reg) == CSR_MSIX_HW_INT_MASK_AD ? 16 : \ + 0xffff), /* causes overflow warning */ \ + } + +static const struct iwl_causes_list causes_list_common[] = { + IWL_CAUSE(CSR_MSIX_FH_INT_MASK_AD, MSIX_FH_INT_CAUSES_D2S_CH0_NUM), + IWL_CAUSE(CSR_MSIX_FH_INT_MASK_AD, MSIX_FH_INT_CAUSES_D2S_CH1_NUM), + IWL_CAUSE(CSR_MSIX_FH_INT_MASK_AD, MSIX_FH_INT_CAUSES_S2D), + IWL_CAUSE(CSR_MSIX_FH_INT_MASK_AD, MSIX_FH_INT_CAUSES_FH_ERR), + IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_ALIVE), + IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_WAKEUP), + IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_RESET_DONE), + IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_CT_KILL), + IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_RF_KILL), + IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_PERIODIC), + IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_SCD), + IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_FH_TX), + IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_HW_ERR), + IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_HAP), +}; + +static const struct iwl_causes_list causes_list_pre_bz[] = { + IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_SW_ERR), +}; + +static const struct iwl_causes_list causes_list_bz[] = { + IWL_CAUSE(CSR_MSIX_HW_INT_MASK_AD, MSIX_HW_INT_CAUSES_REG_SW_ERR_BZ), +}; + +static void iwl_pcie_map_list(struct iwl_trans *trans, + const struct iwl_causes_list *causes, + int arr_size, int val) +{ + int i; + + for (i = 0; i < arr_size; i++) { + iwl_write8(trans, CSR_MSIX_IVAR(causes[i].addr), val); + iwl_clear_bit(trans, causes[i].mask_reg, + BIT(causes[i].bit)); + } +} + +static void iwl_pcie_map_non_rx_causes(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int val = trans_pcie->def_irq | MSIX_NON_AUTO_CLEAR_CAUSE; + /* + * Access all non RX causes and map them to the default irq. + * In case we are missing at least one interrupt vector, + * the first interrupt vector will serve non-RX and FBQ causes. + */ + iwl_pcie_map_list(trans, causes_list_common, + ARRAY_SIZE(causes_list_common), val); + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + iwl_pcie_map_list(trans, causes_list_bz, + ARRAY_SIZE(causes_list_bz), val); + else + iwl_pcie_map_list(trans, causes_list_pre_bz, + ARRAY_SIZE(causes_list_pre_bz), val); +} + +static void iwl_pcie_map_rx_causes(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 offset = + trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_FIRST_RSS ? 1 : 0; + u32 val, idx; + + /* + * The first RX queue - fallback queue, which is designated for + * management frame, command responses etc, is always mapped to the + * first interrupt vector. The other RX queues are mapped to + * the other (N - 2) interrupt vectors. + */ + val = BIT(MSIX_FH_INT_CAUSES_Q(0)); + for (idx = 1; idx < trans->num_rx_queues; idx++) { + iwl_write8(trans, CSR_MSIX_RX_IVAR(idx), + MSIX_FH_INT_CAUSES_Q(idx - offset)); + val |= BIT(MSIX_FH_INT_CAUSES_Q(idx)); + } + iwl_write32(trans, CSR_MSIX_FH_INT_MASK_AD, ~val); + + val = MSIX_FH_INT_CAUSES_Q(0); + if (trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_NON_RX) + val |= MSIX_NON_AUTO_CLEAR_CAUSE; + iwl_write8(trans, CSR_MSIX_RX_IVAR(0), val); + + if (trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_FIRST_RSS) + iwl_write8(trans, CSR_MSIX_RX_IVAR(1), val); +} + +void iwl_pcie_conf_msix_hw(struct iwl_trans_pcie *trans_pcie) +{ + struct iwl_trans *trans = trans_pcie->trans; + + if (!trans_pcie->msix_enabled) { + if (trans->trans_cfg->mq_rx_supported && + test_bit(STATUS_DEVICE_ENABLED, &trans->status)) + iwl_write_umac_prph(trans, UREG_CHICK, + UREG_CHICK_MSI_ENABLE); + return; + } + /* + * The IVAR table needs to be configured again after reset, + * but if the device is disabled, we can't write to + * prph. + */ + if (test_bit(STATUS_DEVICE_ENABLED, &trans->status)) + iwl_write_umac_prph(trans, UREG_CHICK, UREG_CHICK_MSIX_ENABLE); + + /* + * Each cause from the causes list above and the RX causes is + * represented as a byte in the IVAR table. The first nibble + * represents the bound interrupt vector of the cause, the second + * represents no auto clear for this cause. This will be set if its + * interrupt vector is bound to serve other causes. + */ + iwl_pcie_map_rx_causes(trans); + + iwl_pcie_map_non_rx_causes(trans); +} + +static void iwl_pcie_init_msix(struct iwl_trans_pcie *trans_pcie) +{ + struct iwl_trans *trans = trans_pcie->trans; + + iwl_pcie_conf_msix_hw(trans_pcie); + + if (!trans_pcie->msix_enabled) + return; + + trans_pcie->fh_init_mask = ~iwl_read32(trans, CSR_MSIX_FH_INT_MASK_AD); + trans_pcie->fh_mask = trans_pcie->fh_init_mask; + trans_pcie->hw_init_mask = ~iwl_read32(trans, CSR_MSIX_HW_INT_MASK_AD); + trans_pcie->hw_mask = trans_pcie->hw_init_mask; +} + +static void _iwl_trans_pcie_stop_device(struct iwl_trans *trans, bool from_irq) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + lockdep_assert_held(&trans_pcie->mutex); + + if (trans_pcie->is_down) + return; + + trans_pcie->is_down = true; + + /* tell the device to stop sending interrupts */ + iwl_disable_interrupts(trans); + + /* device going down, Stop using ICT table */ + iwl_pcie_disable_ict(trans); + + /* + * If a HW restart happens during firmware loading, + * then the firmware loading might call this function + * and later it might be called again due to the + * restart. So don't process again if the device is + * already dead. + */ + if (test_and_clear_bit(STATUS_DEVICE_ENABLED, &trans->status)) { + IWL_DEBUG_INFO(trans, + "DEVICE_ENABLED bit was set and is now cleared\n"); + if (!from_irq) + iwl_pcie_synchronize_irqs(trans); + iwl_pcie_rx_napi_sync(trans); + iwl_pcie_tx_stop(trans); + iwl_pcie_rx_stop(trans); + + /* Power-down device's busmaster DMA clocks */ + if (!trans->cfg->apmg_not_supported) { + iwl_write_prph(trans, APMG_CLK_DIS_REG, + APMG_CLK_VAL_DMA_CLK_RQT); + udelay(5); + } + } + + /* Make sure (redundant) we've released our request to stay awake */ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + iwl_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ); + else + iwl_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + + /* Stop the device, and put it in low power state */ + iwl_pcie_apm_stop(trans, false); + + /* re-take ownership to prevent other users from stealing the device */ + iwl_trans_pcie_sw_reset(trans, true); + + /* + * Upon stop, the IVAR table gets erased, so msi-x won't + * work. This causes a bug in RF-KILL flows, since the interrupt + * that enables radio won't fire on the correct irq, and the + * driver won't be able to handle the interrupt. + * Configure the IVAR table again after reset. + */ + iwl_pcie_conf_msix_hw(trans_pcie); + + /* + * Upon stop, the APM issues an interrupt if HW RF kill is set. + * This is a bug in certain verions of the hardware. + * Certain devices also keep sending HW RF kill interrupt all + * the time, unless the interrupt is ACKed even if the interrupt + * should be masked. Re-ACK all the interrupts here. + */ + iwl_disable_interrupts(trans); + + /* clear all status bits */ + clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status); + clear_bit(STATUS_INT_ENABLED, &trans->status); + clear_bit(STATUS_TPOWER_PMI, &trans->status); + + /* + * Even if we stop the HW, we still want the RF kill + * interrupt + */ + iwl_enable_rfkill_int(trans); +} + +void iwl_pcie_synchronize_irqs(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (trans_pcie->msix_enabled) { + int i; + + for (i = 0; i < trans_pcie->alloc_vecs; i++) + synchronize_irq(trans_pcie->msix_entries[i].vector); + } else { + synchronize_irq(trans_pcie->pci_dev->irq); + } +} + +static int iwl_trans_pcie_start_fw(struct iwl_trans *trans, + const struct fw_img *fw, bool run_in_rfkill) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + bool hw_rfkill; + int ret; + + /* This may fail if AMT took ownership of the device */ + if (iwl_pcie_prepare_card_hw(trans)) { + IWL_WARN(trans, "Exit HW not ready\n"); + return -EIO; + } + + iwl_enable_rfkill_int(trans); + + iwl_write32(trans, CSR_INT, 0xFFFFFFFF); + + /* + * We enabled the RF-Kill interrupt and the handler may very + * well be running. Disable the interrupts to make sure no other + * interrupt can be fired. + */ + iwl_disable_interrupts(trans); + + /* Make sure it finished running */ + iwl_pcie_synchronize_irqs(trans); + + mutex_lock(&trans_pcie->mutex); + + /* If platform's RF_KILL switch is NOT set to KILL */ + hw_rfkill = iwl_pcie_check_hw_rf_kill(trans); + if (hw_rfkill && !run_in_rfkill) { + ret = -ERFKILL; + goto out; + } + + /* Someone called stop_device, don't try to start_fw */ + if (trans_pcie->is_down) { + IWL_WARN(trans, + "Can't start_fw since the HW hasn't been started\n"); + ret = -EIO; + goto out; + } + + /* make sure rfkill handshake bits are cleared */ + iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL); + iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, + CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED); + + /* clear (again), then enable host interrupts */ + iwl_write32(trans, CSR_INT, 0xFFFFFFFF); + + ret = iwl_pcie_nic_init(trans); + if (ret) { + IWL_ERR(trans, "Unable to init nic\n"); + goto out; + } + + /* + * Now, we load the firmware and don't want to be interrupted, even + * by the RF-Kill interrupt (hence mask all the interrupt besides the + * FH_TX interrupt which is needed to load the firmware). If the + * RF-Kill switch is toggled, we will find out after having loaded + * the firmware and return the proper value to the caller. + */ + iwl_enable_fw_load_int(trans); + + /* really make sure rfkill handshake bits are cleared */ + iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL); + iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL); + + /* Load the given image to the HW */ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_8000) + ret = iwl_pcie_load_given_ucode_8000(trans, fw); + else + ret = iwl_pcie_load_given_ucode(trans, fw); + + /* re-check RF-Kill state since we may have missed the interrupt */ + hw_rfkill = iwl_pcie_check_hw_rf_kill(trans); + if (hw_rfkill && !run_in_rfkill) + ret = -ERFKILL; + +out: + mutex_unlock(&trans_pcie->mutex); + return ret; +} + +static void iwl_trans_pcie_fw_alive(struct iwl_trans *trans, u32 scd_addr) +{ + iwl_pcie_reset_ict(trans); + iwl_pcie_tx_start(trans, scd_addr); +} + +void iwl_trans_pcie_handle_stop_rfkill(struct iwl_trans *trans, + bool was_in_rfkill) +{ + bool hw_rfkill; + + /* + * Check again since the RF kill state may have changed while + * all the interrupts were disabled, in this case we couldn't + * receive the RF kill interrupt and update the state in the + * op_mode. + * Don't call the op_mode if the rkfill state hasn't changed. + * This allows the op_mode to call stop_device from the rfkill + * notification without endless recursion. Under very rare + * circumstances, we might have a small recursion if the rfkill + * state changed exactly now while we were called from stop_device. + * This is very unlikely but can happen and is supported. + */ + hw_rfkill = iwl_is_rfkill_set(trans); + if (hw_rfkill) { + set_bit(STATUS_RFKILL_HW, &trans->status); + set_bit(STATUS_RFKILL_OPMODE, &trans->status); + } else { + clear_bit(STATUS_RFKILL_HW, &trans->status); + clear_bit(STATUS_RFKILL_OPMODE, &trans->status); + } + if (hw_rfkill != was_in_rfkill) + iwl_trans_pcie_rf_kill(trans, hw_rfkill, false); +} + +static void iwl_trans_pcie_stop_device(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + bool was_in_rfkill; + + iwl_op_mode_time_point(trans->op_mode, + IWL_FW_INI_TIME_POINT_HOST_DEVICE_DISABLE, + NULL); + + mutex_lock(&trans_pcie->mutex); + trans_pcie->opmode_down = true; + was_in_rfkill = test_bit(STATUS_RFKILL_OPMODE, &trans->status); + _iwl_trans_pcie_stop_device(trans, false); + iwl_trans_pcie_handle_stop_rfkill(trans, was_in_rfkill); + mutex_unlock(&trans_pcie->mutex); +} + +void iwl_trans_pcie_rf_kill(struct iwl_trans *trans, bool state, bool from_irq) +{ + struct iwl_trans_pcie __maybe_unused *trans_pcie = + IWL_TRANS_GET_PCIE_TRANS(trans); + + lockdep_assert_held(&trans_pcie->mutex); + + IWL_WARN(trans, "reporting RF_KILL (radio %s)\n", + state ? "disabled" : "enabled"); + if (iwl_op_mode_hw_rf_kill(trans->op_mode, state)) { + if (trans->trans_cfg->gen2) + _iwl_trans_pcie_gen2_stop_device(trans); + else + _iwl_trans_pcie_stop_device(trans, from_irq); + } +} + +void iwl_pcie_d3_complete_suspend(struct iwl_trans *trans, + bool test, bool reset) +{ + iwl_disable_interrupts(trans); + + /* + * in testing mode, the host stays awake and the + * hardware won't be reset (not even partially) + */ + if (test) + return; + + iwl_pcie_disable_ict(trans); + + iwl_pcie_synchronize_irqs(trans); + + iwl_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + iwl_clear_bit(trans, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE); + + if (reset) { + /* + * reset TX queues -- some of their registers reset during S3 + * so if we don't reset everything here the D3 image would try + * to execute some invalid memory upon resume + */ + iwl_trans_pcie_tx_reset(trans); + } + + iwl_pcie_set_pwr(trans, true); +} + +static int iwl_pcie_d3_handshake(struct iwl_trans *trans, bool suspend) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret; + + if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_AX210) + iwl_write_umac_prph(trans, UREG_DOORBELL_TO_ISR6, + suspend ? UREG_DOORBELL_TO_ISR6_SUSPEND : + UREG_DOORBELL_TO_ISR6_RESUME); + else if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + iwl_write32(trans, CSR_IPC_SLEEP_CONTROL, + suspend ? CSR_IPC_SLEEP_CONTROL_SUSPEND : + CSR_IPC_SLEEP_CONTROL_RESUME); + else + return 0; + + ret = wait_event_timeout(trans_pcie->sx_waitq, + trans_pcie->sx_complete, 2 * HZ); + + /* Invalidate it toward next suspend or resume */ + trans_pcie->sx_complete = false; + + if (!ret) { + IWL_ERR(trans, "Timeout %s D3\n", + suspend ? "entering" : "exiting"); + return -ETIMEDOUT; + } + + return 0; +} + +static int iwl_trans_pcie_d3_suspend(struct iwl_trans *trans, bool test, + bool reset) +{ + int ret; + + if (!reset) + /* Enable persistence mode to avoid reset */ + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_PERSIST_MODE); + + ret = iwl_pcie_d3_handshake(trans, true); + if (ret) + return ret; + + iwl_pcie_d3_complete_suspend(trans, test, reset); + + return 0; +} + +static int iwl_trans_pcie_d3_resume(struct iwl_trans *trans, + enum iwl_d3_status *status, + bool test, bool reset) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 val; + int ret; + + if (test) { + iwl_enable_interrupts(trans); + *status = IWL_D3_STATUS_ALIVE; + ret = 0; + goto out; + } + + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + + ret = iwl_finish_nic_init(trans); + if (ret) + return ret; + + /* + * Reconfigure IVAR table in case of MSIX or reset ict table in + * MSI mode since HW reset erased it. + * Also enables interrupts - none will happen as + * the device doesn't know we're waking it up, only when + * the opmode actually tells it after this call. + */ + iwl_pcie_conf_msix_hw(trans_pcie); + if (!trans_pcie->msix_enabled) + iwl_pcie_reset_ict(trans); + iwl_enable_interrupts(trans); + + iwl_pcie_set_pwr(trans, false); + + if (!reset) { + iwl_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + } else { + iwl_trans_pcie_tx_reset(trans); + + ret = iwl_pcie_rx_init(trans); + if (ret) { + IWL_ERR(trans, + "Failed to resume the device (RX reset)\n"); + return ret; + } + } + + IWL_DEBUG_POWER(trans, "WFPM value upon resume = 0x%08X\n", + iwl_read_umac_prph(trans, WFPM_GP2)); + + val = iwl_read32(trans, CSR_RESET); + if (val & CSR_RESET_REG_FLAG_NEVO_RESET) + *status = IWL_D3_STATUS_RESET; + else + *status = IWL_D3_STATUS_ALIVE; + +out: + if (*status == IWL_D3_STATUS_ALIVE) + ret = iwl_pcie_d3_handshake(trans, false); + + return ret; +} + +static void +iwl_pcie_set_interrupt_capa(struct pci_dev *pdev, + struct iwl_trans *trans, + const struct iwl_cfg_trans_params *cfg_trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int max_irqs, num_irqs, i, ret; + u16 pci_cmd; + u32 max_rx_queues = IWL_MAX_RX_HW_QUEUES; + + if (!cfg_trans->mq_rx_supported) + goto enable_msi; + + if (cfg_trans->device_family <= IWL_DEVICE_FAMILY_9000) + max_rx_queues = IWL_9000_MAX_RX_HW_QUEUES; + + max_irqs = min_t(u32, num_online_cpus() + 2, max_rx_queues); + for (i = 0; i < max_irqs; i++) + trans_pcie->msix_entries[i].entry = i; + + num_irqs = pci_enable_msix_range(pdev, trans_pcie->msix_entries, + MSIX_MIN_INTERRUPT_VECTORS, + max_irqs); + if (num_irqs < 0) { + IWL_DEBUG_INFO(trans, + "Failed to enable msi-x mode (ret %d). Moving to msi mode.\n", + num_irqs); + goto enable_msi; + } + trans_pcie->def_irq = (num_irqs == max_irqs) ? num_irqs - 1 : 0; + + IWL_DEBUG_INFO(trans, + "MSI-X enabled. %d interrupt vectors were allocated\n", + num_irqs); + + /* + * In case the OS provides fewer interrupts than requested, different + * causes will share the same interrupt vector as follows: + * One interrupt less: non rx causes shared with FBQ. + * Two interrupts less: non rx causes shared with FBQ and RSS. + * More than two interrupts: we will use fewer RSS queues. + */ + if (num_irqs <= max_irqs - 2) { + trans_pcie->trans->num_rx_queues = num_irqs + 1; + trans_pcie->shared_vec_mask = IWL_SHARED_IRQ_NON_RX | + IWL_SHARED_IRQ_FIRST_RSS; + } else if (num_irqs == max_irqs - 1) { + trans_pcie->trans->num_rx_queues = num_irqs; + trans_pcie->shared_vec_mask = IWL_SHARED_IRQ_NON_RX; + } else { + trans_pcie->trans->num_rx_queues = num_irqs - 1; + } + + IWL_DEBUG_INFO(trans, + "MSI-X enabled with rx queues %d, vec mask 0x%x\n", + trans_pcie->trans->num_rx_queues, trans_pcie->shared_vec_mask); + + WARN_ON(trans_pcie->trans->num_rx_queues > IWL_MAX_RX_HW_QUEUES); + + trans_pcie->alloc_vecs = num_irqs; + trans_pcie->msix_enabled = true; + return; + +enable_msi: + ret = pci_enable_msi(pdev); + if (ret) { + dev_err(&pdev->dev, "pci_enable_msi failed - %d\n", ret); + /* enable rfkill interrupt: hw bug w/a */ + pci_read_config_word(pdev, PCI_COMMAND, &pci_cmd); + if (pci_cmd & PCI_COMMAND_INTX_DISABLE) { + pci_cmd &= ~PCI_COMMAND_INTX_DISABLE; + pci_write_config_word(pdev, PCI_COMMAND, pci_cmd); + } + } +} + +static void iwl_pcie_irq_set_affinity(struct iwl_trans *trans) +{ + int iter_rx_q, i, ret, cpu, offset; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + i = trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_FIRST_RSS ? 0 : 1; + iter_rx_q = trans_pcie->trans->num_rx_queues - 1 + i; + offset = 1 + i; + for (; i < iter_rx_q ; i++) { + /* + * Get the cpu prior to the place to search + * (i.e. return will be > i - 1). + */ + cpu = cpumask_next(i - offset, cpu_online_mask); + cpumask_set_cpu(cpu, &trans_pcie->affinity_mask[i]); + ret = irq_set_affinity_hint(trans_pcie->msix_entries[i].vector, + &trans_pcie->affinity_mask[i]); + if (ret) + IWL_ERR(trans_pcie->trans, + "Failed to set affinity mask for IRQ %d\n", + trans_pcie->msix_entries[i].vector); + } +} + +static int iwl_pcie_init_msix_handler(struct pci_dev *pdev, + struct iwl_trans_pcie *trans_pcie) +{ + int i; + + for (i = 0; i < trans_pcie->alloc_vecs; i++) { + int ret; + struct msix_entry *msix_entry; + const char *qname = queue_name(&pdev->dev, trans_pcie, i); + + if (!qname) + return -ENOMEM; + + msix_entry = &trans_pcie->msix_entries[i]; + ret = devm_request_threaded_irq(&pdev->dev, + msix_entry->vector, + iwl_pcie_msix_isr, + (i == trans_pcie->def_irq) ? + iwl_pcie_irq_msix_handler : + iwl_pcie_irq_rx_msix_handler, + IRQF_SHARED, + qname, + msix_entry); + if (ret) { + IWL_ERR(trans_pcie->trans, + "Error allocating IRQ %d\n", i); + + return ret; + } + } + iwl_pcie_irq_set_affinity(trans_pcie->trans); + + return 0; +} + +static int iwl_trans_pcie_clear_persistence_bit(struct iwl_trans *trans) +{ + u32 hpm, wprot; + + switch (trans->trans_cfg->device_family) { + case IWL_DEVICE_FAMILY_9000: + wprot = PREG_PRPH_WPROT_9000; + break; + case IWL_DEVICE_FAMILY_22000: + wprot = PREG_PRPH_WPROT_22000; + break; + default: + return 0; + } + + hpm = iwl_read_umac_prph_no_grab(trans, HPM_DEBUG); + if (hpm != 0xa5a5a5a0 && (hpm & PERSISTENCE_BIT)) { + u32 wprot_val = iwl_read_umac_prph_no_grab(trans, wprot); + + if (wprot_val & PREG_WFPM_ACCESS) { + IWL_ERR(trans, + "Error, can not clear persistence bit\n"); + return -EPERM; + } + iwl_write_umac_prph_no_grab(trans, HPM_DEBUG, + hpm & ~PERSISTENCE_BIT); + } + + return 0; +} + +static int iwl_pcie_gen2_force_power_gating(struct iwl_trans *trans) +{ + int ret; + + ret = iwl_finish_nic_init(trans); + if (ret < 0) + return ret; + + iwl_set_bits_prph(trans, HPM_HIPM_GEN_CFG, + HPM_HIPM_GEN_CFG_CR_FORCE_ACTIVE); + udelay(20); + iwl_set_bits_prph(trans, HPM_HIPM_GEN_CFG, + HPM_HIPM_GEN_CFG_CR_PG_EN | + HPM_HIPM_GEN_CFG_CR_SLP_EN); + udelay(20); + iwl_clear_bits_prph(trans, HPM_HIPM_GEN_CFG, + HPM_HIPM_GEN_CFG_CR_FORCE_ACTIVE); + + return iwl_trans_pcie_sw_reset(trans, true); +} + +static int _iwl_trans_pcie_start_hw(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int err; + + lockdep_assert_held(&trans_pcie->mutex); + + err = iwl_pcie_prepare_card_hw(trans); + if (err) { + IWL_ERR(trans, "Error while preparing HW: %d\n", err); + return err; + } + + err = iwl_trans_pcie_clear_persistence_bit(trans); + if (err) + return err; + + err = iwl_trans_pcie_sw_reset(trans, true); + if (err) + return err; + + if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_22000 && + trans->trans_cfg->integrated) { + err = iwl_pcie_gen2_force_power_gating(trans); + if (err) + return err; + } + + err = iwl_pcie_apm_init(trans); + if (err) + return err; + + iwl_pcie_init_msix(trans_pcie); + + /* From now on, the op_mode will be kept updated about RF kill state */ + iwl_enable_rfkill_int(trans); + + trans_pcie->opmode_down = false; + + /* Set is_down to false here so that...*/ + trans_pcie->is_down = false; + + /* ...rfkill can call stop_device and set it false if needed */ + iwl_pcie_check_hw_rf_kill(trans); + + return 0; +} + +static int iwl_trans_pcie_start_hw(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret; + + mutex_lock(&trans_pcie->mutex); + ret = _iwl_trans_pcie_start_hw(trans); + mutex_unlock(&trans_pcie->mutex); + + return ret; +} + +static void iwl_trans_pcie_op_mode_leave(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + mutex_lock(&trans_pcie->mutex); + + /* disable interrupts - don't enable HW RF kill interrupt */ + iwl_disable_interrupts(trans); + + iwl_pcie_apm_stop(trans, true); + + iwl_disable_interrupts(trans); + + iwl_pcie_disable_ict(trans); + + mutex_unlock(&trans_pcie->mutex); + + iwl_pcie_synchronize_irqs(trans); +} + +static void iwl_trans_pcie_write8(struct iwl_trans *trans, u32 ofs, u8 val) +{ + writeb(val, IWL_TRANS_GET_PCIE_TRANS(trans)->hw_base + ofs); +} + +static void iwl_trans_pcie_write32(struct iwl_trans *trans, u32 ofs, u32 val) +{ + writel(val, IWL_TRANS_GET_PCIE_TRANS(trans)->hw_base + ofs); +} + +static u32 iwl_trans_pcie_read32(struct iwl_trans *trans, u32 ofs) +{ + return readl(IWL_TRANS_GET_PCIE_TRANS(trans)->hw_base + ofs); +} + +static u32 iwl_trans_pcie_prph_msk(struct iwl_trans *trans) +{ + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + return 0x00FFFFFF; + else + return 0x000FFFFF; +} + +static u32 iwl_trans_pcie_read_prph(struct iwl_trans *trans, u32 reg) +{ + u32 mask = iwl_trans_pcie_prph_msk(trans); + + iwl_trans_pcie_write32(trans, HBUS_TARG_PRPH_RADDR, + ((reg & mask) | (3 << 24))); + return iwl_trans_pcie_read32(trans, HBUS_TARG_PRPH_RDAT); +} + +static void iwl_trans_pcie_write_prph(struct iwl_trans *trans, u32 addr, + u32 val) +{ + u32 mask = iwl_trans_pcie_prph_msk(trans); + + iwl_trans_pcie_write32(trans, HBUS_TARG_PRPH_WADDR, + ((addr & mask) | (3 << 24))); + iwl_trans_pcie_write32(trans, HBUS_TARG_PRPH_WDAT, val); +} + +static void iwl_trans_pcie_configure(struct iwl_trans *trans, + const struct iwl_trans_config *trans_cfg) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + /* free all first - we might be reconfigured for a different size */ + iwl_pcie_free_rbs_pool(trans); + + trans->txqs.cmd.q_id = trans_cfg->cmd_queue; + trans->txqs.cmd.fifo = trans_cfg->cmd_fifo; + trans->txqs.cmd.wdg_timeout = trans_cfg->cmd_q_wdg_timeout; + trans->txqs.page_offs = trans_cfg->cb_data_offs; + trans->txqs.dev_cmd_offs = trans_cfg->cb_data_offs + sizeof(void *); + trans->txqs.queue_alloc_cmd_ver = trans_cfg->queue_alloc_cmd_ver; + + if (WARN_ON(trans_cfg->n_no_reclaim_cmds > MAX_NO_RECLAIM_CMDS)) + trans_pcie->n_no_reclaim_cmds = 0; + else + trans_pcie->n_no_reclaim_cmds = trans_cfg->n_no_reclaim_cmds; + if (trans_pcie->n_no_reclaim_cmds) + memcpy(trans_pcie->no_reclaim_cmds, trans_cfg->no_reclaim_cmds, + trans_pcie->n_no_reclaim_cmds * sizeof(u8)); + + trans_pcie->rx_buf_size = trans_cfg->rx_buf_size; + trans_pcie->rx_page_order = + iwl_trans_get_rb_size_order(trans_pcie->rx_buf_size); + trans_pcie->rx_buf_bytes = + iwl_trans_get_rb_size(trans_pcie->rx_buf_size); + trans_pcie->supported_dma_mask = DMA_BIT_MASK(12); + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) + trans_pcie->supported_dma_mask = DMA_BIT_MASK(11); + + trans->txqs.bc_table_dword = trans_cfg->bc_table_dword; + trans_pcie->scd_set_active = trans_cfg->scd_set_active; + + trans->command_groups = trans_cfg->command_groups; + trans->command_groups_size = trans_cfg->command_groups_size; + + /* Initialize NAPI here - it should be before registering to mac80211 + * in the opmode but after the HW struct is allocated. + * As this function may be called again in some corner cases don't + * do anything if NAPI was already initialized. + */ + if (trans_pcie->napi_dev.reg_state != NETREG_DUMMY) + init_dummy_netdev(&trans_pcie->napi_dev); + + trans_pcie->fw_reset_handshake = trans_cfg->fw_reset_handshake; +} + +void iwl_trans_pcie_free(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int i; + + iwl_pcie_synchronize_irqs(trans); + + if (trans->trans_cfg->gen2) + iwl_txq_gen2_tx_free(trans); + else + iwl_pcie_tx_free(trans); + iwl_pcie_rx_free(trans); + + if (trans_pcie->rba.alloc_wq) { + destroy_workqueue(trans_pcie->rba.alloc_wq); + trans_pcie->rba.alloc_wq = NULL; + } + + if (trans_pcie->msix_enabled) { + for (i = 0; i < trans_pcie->alloc_vecs; i++) { + irq_set_affinity_hint( + trans_pcie->msix_entries[i].vector, + NULL); + } + + trans_pcie->msix_enabled = false; + } else { + iwl_pcie_free_ict(trans); + } + + iwl_pcie_free_fw_monitor(trans); + + if (trans_pcie->pnvm_dram.size) + dma_free_coherent(trans->dev, trans_pcie->pnvm_dram.size, + trans_pcie->pnvm_dram.block, + trans_pcie->pnvm_dram.physical); + + if (trans_pcie->reduce_power_dram.size) + dma_free_coherent(trans->dev, + trans_pcie->reduce_power_dram.size, + trans_pcie->reduce_power_dram.block, + trans_pcie->reduce_power_dram.physical); + + mutex_destroy(&trans_pcie->mutex); + iwl_trans_free(trans); +} + +static void iwl_trans_pcie_set_pmi(struct iwl_trans *trans, bool state) +{ + if (state) + set_bit(STATUS_TPOWER_PMI, &trans->status); + else + clear_bit(STATUS_TPOWER_PMI, &trans->status); +} + +struct iwl_trans_pcie_removal { + struct pci_dev *pdev; + struct work_struct work; +}; + +static void iwl_trans_pcie_removal_wk(struct work_struct *wk) +{ + struct iwl_trans_pcie_removal *removal = + container_of(wk, struct iwl_trans_pcie_removal, work); + struct pci_dev *pdev = removal->pdev; + static char *prop[] = {"EVENT=INACCESSIBLE", NULL}; + + dev_err(&pdev->dev, "Device gone - attempting removal\n"); + kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, prop); + pci_lock_rescan_remove(); + pci_dev_put(pdev); + pci_stop_and_remove_bus_device(pdev); + pci_unlock_rescan_remove(); + + kfree(removal); + module_put(THIS_MODULE); +} + +/* + * This version doesn't disable BHs but rather assumes they're + * already disabled. + */ +bool __iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans) +{ + int ret; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 write = CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ; + u32 mask = CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY | + CSR_GP_CNTRL_REG_FLAG_GOING_TO_SLEEP; + u32 poll = CSR_GP_CNTRL_REG_VAL_MAC_ACCESS_EN; + + spin_lock(&trans_pcie->reg_lock); + + if (trans_pcie->cmd_hold_nic_awake) + goto out; + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) { + write = CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ; + mask = CSR_GP_CNTRL_REG_FLAG_MAC_STATUS; + poll = CSR_GP_CNTRL_REG_FLAG_MAC_STATUS; + } + + /* this bit wakes up the NIC */ + __iwl_trans_pcie_set_bit(trans, CSR_GP_CNTRL, write); + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_8000) + udelay(2); + + /* + * These bits say the device is running, and should keep running for + * at least a short while (at least as long as MAC_ACCESS_REQ stays 1), + * but they do not indicate that embedded SRAM is restored yet; + * HW with volatile SRAM must save/restore contents to/from + * host DRAM when sleeping/waking for power-saving. + * Each direction takes approximately 1/4 millisecond; with this + * overhead, it's a good idea to grab and hold MAC_ACCESS_REQUEST if a + * series of register accesses are expected (e.g. reading Event Log), + * to keep device from sleeping. + * + * CSR_UCODE_DRV_GP1 register bit MAC_SLEEP == 0 indicates that + * SRAM is okay/restored. We don't check that here because this call + * is just for hardware register access; but GP1 MAC_SLEEP + * check is a good idea before accessing the SRAM of HW with + * volatile SRAM (e.g. reading Event Log). + * + * 5000 series and later (including 1000 series) have non-volatile SRAM, + * and do not save/restore SRAM when power cycling. + */ + ret = iwl_poll_bit(trans, CSR_GP_CNTRL, poll, mask, 15000); + if (unlikely(ret < 0)) { + u32 cntrl = iwl_read32(trans, CSR_GP_CNTRL); + + WARN_ONCE(1, + "Timeout waiting for hardware access (CSR_GP_CNTRL 0x%08x)\n", + cntrl); + + iwl_trans_pcie_dump_regs(trans); + + if (iwlwifi_mod_params.remove_when_gone && cntrl == ~0U) { + struct iwl_trans_pcie_removal *removal; + + if (test_bit(STATUS_TRANS_DEAD, &trans->status)) + goto err; + + IWL_ERR(trans, "Device gone - scheduling removal!\n"); + + /* + * get a module reference to avoid doing this + * while unloading anyway and to avoid + * scheduling a work with code that's being + * removed. + */ + if (!try_module_get(THIS_MODULE)) { + IWL_ERR(trans, + "Module is being unloaded - abort\n"); + goto err; + } + + removal = kzalloc(sizeof(*removal), GFP_ATOMIC); + if (!removal) { + module_put(THIS_MODULE); + goto err; + } + /* + * we don't need to clear this flag, because + * the trans will be freed and reallocated. + */ + set_bit(STATUS_TRANS_DEAD, &trans->status); + + removal->pdev = to_pci_dev(trans->dev); + INIT_WORK(&removal->work, iwl_trans_pcie_removal_wk); + pci_dev_get(removal->pdev); + schedule_work(&removal->work); + } else { + iwl_write32(trans, CSR_RESET, + CSR_RESET_REG_FLAG_FORCE_NMI); + } + +err: + spin_unlock(&trans_pcie->reg_lock); + return false; + } + +out: + /* + * Fool sparse by faking we release the lock - sparse will + * track nic_access anyway. + */ + __release(&trans_pcie->reg_lock); + return true; +} + +static bool iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans) +{ + bool ret; + + local_bh_disable(); + ret = __iwl_trans_pcie_grab_nic_access(trans); + if (ret) { + /* keep BHs disabled until iwl_trans_pcie_release_nic_access */ + return ret; + } + local_bh_enable(); + return false; +} + +static void iwl_trans_pcie_release_nic_access(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + lockdep_assert_held(&trans_pcie->reg_lock); + + /* + * Fool sparse by faking we acquiring the lock - sparse will + * track nic_access anyway. + */ + __acquire(&trans_pcie->reg_lock); + + if (trans_pcie->cmd_hold_nic_awake) + goto out; + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + __iwl_trans_pcie_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ); + else + __iwl_trans_pcie_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + /* + * Above we read the CSR_GP_CNTRL register, which will flush + * any previous writes, but we need the write that clears the + * MAC_ACCESS_REQ bit to be performed before any other writes + * scheduled on different CPUs (after we drop reg_lock). + */ +out: + spin_unlock_bh(&trans_pcie->reg_lock); +} + +static int iwl_trans_pcie_read_mem(struct iwl_trans *trans, u32 addr, + void *buf, int dwords) +{ + int offs = 0; + u32 *vals = buf; + + while (offs < dwords) { + /* limit the time we spin here under lock to 1/2s */ + unsigned long end = jiffies + HZ / 2; + bool resched = false; + + if (iwl_trans_grab_nic_access(trans)) { + iwl_write32(trans, HBUS_TARG_MEM_RADDR, + addr + 4 * offs); + + while (offs < dwords) { + vals[offs] = iwl_read32(trans, + HBUS_TARG_MEM_RDAT); + offs++; + + if (time_after(jiffies, end)) { + resched = true; + break; + } + } + iwl_trans_release_nic_access(trans); + + if (resched) + cond_resched(); + } else { + return -EBUSY; + } + } + + return 0; +} + +static int iwl_trans_pcie_write_mem(struct iwl_trans *trans, u32 addr, + const void *buf, int dwords) +{ + int offs, ret = 0; + const u32 *vals = buf; + + if (iwl_trans_grab_nic_access(trans)) { + iwl_write32(trans, HBUS_TARG_MEM_WADDR, addr); + for (offs = 0; offs < dwords; offs++) + iwl_write32(trans, HBUS_TARG_MEM_WDAT, + vals ? vals[offs] : 0); + iwl_trans_release_nic_access(trans); + } else { + ret = -EBUSY; + } + return ret; +} + +static int iwl_trans_pcie_read_config32(struct iwl_trans *trans, u32 ofs, + u32 *val) +{ + return pci_read_config_dword(IWL_TRANS_GET_PCIE_TRANS(trans)->pci_dev, + ofs, val); +} + +static void iwl_trans_pcie_block_txq_ptrs(struct iwl_trans *trans, bool block) +{ + int i; + + for (i = 0; i < trans->trans_cfg->base_params->num_of_queues; i++) { + struct iwl_txq *txq = trans->txqs.txq[i]; + + if (i == trans->txqs.cmd.q_id) + continue; + + spin_lock_bh(&txq->lock); + + if (!block && !(WARN_ON_ONCE(!txq->block))) { + txq->block--; + if (!txq->block) { + iwl_write32(trans, HBUS_TARG_WRPTR, + txq->write_ptr | (i << 8)); + } + } else if (block) { + txq->block++; + } + + spin_unlock_bh(&txq->lock); + } +} + +#define IWL_FLUSH_WAIT_MS 2000 + +static int iwl_trans_pcie_rxq_dma_data(struct iwl_trans *trans, int queue, + struct iwl_trans_rxq_dma_data *data) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (queue >= trans->num_rx_queues || !trans_pcie->rxq) + return -EINVAL; + + data->fr_bd_cb = trans_pcie->rxq[queue].bd_dma; + data->urbd_stts_wrptr = trans_pcie->rxq[queue].rb_stts_dma; + data->ur_bd_cb = trans_pcie->rxq[queue].used_bd_dma; + data->fr_bd_wid = 0; + + return 0; +} + +static int iwl_trans_pcie_wait_txq_empty(struct iwl_trans *trans, int txq_idx) +{ + struct iwl_txq *txq; + unsigned long now = jiffies; + bool overflow_tx; + u8 wr_ptr; + + /* Make sure the NIC is still alive in the bus */ + if (test_bit(STATUS_TRANS_DEAD, &trans->status)) + return -ENODEV; + + if (!test_bit(txq_idx, trans->txqs.queue_used)) + return -EINVAL; + + IWL_DEBUG_TX_QUEUES(trans, "Emptying queue %d...\n", txq_idx); + txq = trans->txqs.txq[txq_idx]; + + spin_lock_bh(&txq->lock); + overflow_tx = txq->overflow_tx || + !skb_queue_empty(&txq->overflow_q); + spin_unlock_bh(&txq->lock); + + wr_ptr = READ_ONCE(txq->write_ptr); + + while ((txq->read_ptr != READ_ONCE(txq->write_ptr) || + overflow_tx) && + !time_after(jiffies, + now + msecs_to_jiffies(IWL_FLUSH_WAIT_MS))) { + u8 write_ptr = READ_ONCE(txq->write_ptr); + + /* + * If write pointer moved during the wait, warn only + * if the TX came from op mode. In case TX came from + * trans layer (overflow TX) don't warn. + */ + if (WARN_ONCE(wr_ptr != write_ptr && !overflow_tx, + "WR pointer moved while flushing %d -> %d\n", + wr_ptr, write_ptr)) + return -ETIMEDOUT; + wr_ptr = write_ptr; + + usleep_range(1000, 2000); + + spin_lock_bh(&txq->lock); + overflow_tx = txq->overflow_tx || + !skb_queue_empty(&txq->overflow_q); + spin_unlock_bh(&txq->lock); + } + + if (txq->read_ptr != txq->write_ptr) { + IWL_ERR(trans, + "fail to flush all tx fifo queues Q %d\n", txq_idx); + iwl_txq_log_scd_error(trans, txq); + return -ETIMEDOUT; + } + + IWL_DEBUG_TX_QUEUES(trans, "Queue %d is now empty.\n", txq_idx); + + return 0; +} + +static int iwl_trans_pcie_wait_txqs_empty(struct iwl_trans *trans, u32 txq_bm) +{ + int cnt; + int ret = 0; + + /* waiting for all the tx frames complete might take a while */ + for (cnt = 0; + cnt < trans->trans_cfg->base_params->num_of_queues; + cnt++) { + + if (cnt == trans->txqs.cmd.q_id) + continue; + if (!test_bit(cnt, trans->txqs.queue_used)) + continue; + if (!(BIT(cnt) & txq_bm)) + continue; + + ret = iwl_trans_pcie_wait_txq_empty(trans, cnt); + if (ret) + break; + } + + return ret; +} + +static void iwl_trans_pcie_set_bits_mask(struct iwl_trans *trans, u32 reg, + u32 mask, u32 value) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + spin_lock_bh(&trans_pcie->reg_lock); + __iwl_trans_pcie_set_bits_mask(trans, reg, mask, value); + spin_unlock_bh(&trans_pcie->reg_lock); +} + +static const char *get_csr_string(int cmd) +{ +#define IWL_CMD(x) case x: return #x + switch (cmd) { + IWL_CMD(CSR_HW_IF_CONFIG_REG); + IWL_CMD(CSR_INT_COALESCING); + IWL_CMD(CSR_INT); + IWL_CMD(CSR_INT_MASK); + IWL_CMD(CSR_FH_INT_STATUS); + IWL_CMD(CSR_GPIO_IN); + IWL_CMD(CSR_RESET); + IWL_CMD(CSR_GP_CNTRL); + IWL_CMD(CSR_HW_REV); + IWL_CMD(CSR_EEPROM_REG); + IWL_CMD(CSR_EEPROM_GP); + IWL_CMD(CSR_OTP_GP_REG); + IWL_CMD(CSR_GIO_REG); + IWL_CMD(CSR_GP_UCODE_REG); + IWL_CMD(CSR_GP_DRIVER_REG); + IWL_CMD(CSR_UCODE_DRV_GP1); + IWL_CMD(CSR_UCODE_DRV_GP2); + IWL_CMD(CSR_LED_REG); + IWL_CMD(CSR_DRAM_INT_TBL_REG); + IWL_CMD(CSR_GIO_CHICKEN_BITS); + IWL_CMD(CSR_ANA_PLL_CFG); + IWL_CMD(CSR_HW_REV_WA_REG); + IWL_CMD(CSR_MONITOR_STATUS_REG); + IWL_CMD(CSR_DBG_HPET_MEM_REG); + default: + return "UNKNOWN"; + } +#undef IWL_CMD +} + +void iwl_pcie_dump_csr(struct iwl_trans *trans) +{ + int i; + static const u32 csr_tbl[] = { + CSR_HW_IF_CONFIG_REG, + CSR_INT_COALESCING, + CSR_INT, + CSR_INT_MASK, + CSR_FH_INT_STATUS, + CSR_GPIO_IN, + CSR_RESET, + CSR_GP_CNTRL, + CSR_HW_REV, + CSR_EEPROM_REG, + CSR_EEPROM_GP, + CSR_OTP_GP_REG, + CSR_GIO_REG, + CSR_GP_UCODE_REG, + CSR_GP_DRIVER_REG, + CSR_UCODE_DRV_GP1, + CSR_UCODE_DRV_GP2, + CSR_LED_REG, + CSR_DRAM_INT_TBL_REG, + CSR_GIO_CHICKEN_BITS, + CSR_ANA_PLL_CFG, + CSR_MONITOR_STATUS_REG, + CSR_HW_REV_WA_REG, + CSR_DBG_HPET_MEM_REG + }; + IWL_ERR(trans, "CSR values:\n"); + IWL_ERR(trans, "(2nd byte of CSR_INT_COALESCING is " + "CSR_INT_PERIODIC_REG)\n"); + for (i = 0; i < ARRAY_SIZE(csr_tbl); i++) { + IWL_ERR(trans, " %25s: 0X%08x\n", + get_csr_string(csr_tbl[i]), + iwl_read32(trans, csr_tbl[i])); + } +} + +#ifdef CONFIG_IWLWIFI_DEBUGFS +/* create and remove of files */ +#define DEBUGFS_ADD_FILE(name, parent, mode) do { \ + debugfs_create_file(#name, mode, parent, trans, \ + &iwl_dbgfs_##name##_ops); \ +} while (0) + +/* file operation */ +#define DEBUGFS_READ_FILE_OPS(name) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .read = iwl_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_WRITE_FILE_OPS(name) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .write = iwl_dbgfs_##name##_write, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_READ_WRITE_FILE_OPS(name) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .write = iwl_dbgfs_##name##_write, \ + .read = iwl_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +struct iwl_dbgfs_tx_queue_priv { + struct iwl_trans *trans; +}; + +struct iwl_dbgfs_tx_queue_state { + loff_t pos; +}; + +static void *iwl_dbgfs_tx_queue_seq_start(struct seq_file *seq, loff_t *pos) +{ + struct iwl_dbgfs_tx_queue_priv *priv = seq->private; + struct iwl_dbgfs_tx_queue_state *state; + + if (*pos >= priv->trans->trans_cfg->base_params->num_of_queues) + return NULL; + + state = kmalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + state->pos = *pos; + return state; +} + +static void *iwl_dbgfs_tx_queue_seq_next(struct seq_file *seq, + void *v, loff_t *pos) +{ + struct iwl_dbgfs_tx_queue_priv *priv = seq->private; + struct iwl_dbgfs_tx_queue_state *state = v; + + *pos = ++state->pos; + + if (*pos >= priv->trans->trans_cfg->base_params->num_of_queues) + return NULL; + + return state; +} + +static void iwl_dbgfs_tx_queue_seq_stop(struct seq_file *seq, void *v) +{ + kfree(v); +} + +static int iwl_dbgfs_tx_queue_seq_show(struct seq_file *seq, void *v) +{ + struct iwl_dbgfs_tx_queue_priv *priv = seq->private; + struct iwl_dbgfs_tx_queue_state *state = v; + struct iwl_trans *trans = priv->trans; + struct iwl_txq *txq = trans->txqs.txq[state->pos]; + + seq_printf(seq, "hwq %.3u: used=%d stopped=%d ", + (unsigned int)state->pos, + !!test_bit(state->pos, trans->txqs.queue_used), + !!test_bit(state->pos, trans->txqs.queue_stopped)); + if (txq) + seq_printf(seq, + "read=%u write=%u need_update=%d frozen=%d n_window=%d ampdu=%d", + txq->read_ptr, txq->write_ptr, + txq->need_update, txq->frozen, + txq->n_window, txq->ampdu); + else + seq_puts(seq, "(unallocated)"); + + if (state->pos == trans->txqs.cmd.q_id) + seq_puts(seq, " (HCMD)"); + seq_puts(seq, "\n"); + + return 0; +} + +static const struct seq_operations iwl_dbgfs_tx_queue_seq_ops = { + .start = iwl_dbgfs_tx_queue_seq_start, + .next = iwl_dbgfs_tx_queue_seq_next, + .stop = iwl_dbgfs_tx_queue_seq_stop, + .show = iwl_dbgfs_tx_queue_seq_show, +}; + +static int iwl_dbgfs_tx_queue_open(struct inode *inode, struct file *filp) +{ + struct iwl_dbgfs_tx_queue_priv *priv; + + priv = __seq_open_private(filp, &iwl_dbgfs_tx_queue_seq_ops, + sizeof(*priv)); + + if (!priv) + return -ENOMEM; + + priv->trans = inode->i_private; + return 0; +} + +static ssize_t iwl_dbgfs_rx_queue_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + char *buf; + int pos = 0, i, ret; + size_t bufsz; + + bufsz = sizeof(char) * 121 * trans->num_rx_queues; + + if (!trans_pcie->rxq) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < trans->num_rx_queues && pos < bufsz; i++) { + struct iwl_rxq *rxq = &trans_pcie->rxq[i]; + + pos += scnprintf(buf + pos, bufsz - pos, "queue#: %2d\n", + i); + pos += scnprintf(buf + pos, bufsz - pos, "\tread: %u\n", + rxq->read); + pos += scnprintf(buf + pos, bufsz - pos, "\twrite: %u\n", + rxq->write); + pos += scnprintf(buf + pos, bufsz - pos, "\twrite_actual: %u\n", + rxq->write_actual); + pos += scnprintf(buf + pos, bufsz - pos, "\tneed_update: %2d\n", + rxq->need_update); + pos += scnprintf(buf + pos, bufsz - pos, "\tfree_count: %u\n", + rxq->free_count); + if (rxq->rb_stts) { + u32 r = __le16_to_cpu(iwl_get_closed_rb_stts(trans, + rxq)); + pos += scnprintf(buf + pos, bufsz - pos, + "\tclosed_rb_num: %u\n", + r & 0x0FFF); + } else { + pos += scnprintf(buf + pos, bufsz - pos, + "\tclosed_rb_num: Not Allocated\n"); + } + } + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + + return ret; +} + +static ssize_t iwl_dbgfs_interrupt_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct isr_statistics *isr_stats = &trans_pcie->isr_stats; + + int pos = 0; + char *buf; + int bufsz = 24 * 64; /* 24 items * 64 char per item */ + ssize_t ret; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pos += scnprintf(buf + pos, bufsz - pos, + "Interrupt Statistics Report:\n"); + + pos += scnprintf(buf + pos, bufsz - pos, "HW Error:\t\t\t %u\n", + isr_stats->hw); + pos += scnprintf(buf + pos, bufsz - pos, "SW Error:\t\t\t %u\n", + isr_stats->sw); + if (isr_stats->sw || isr_stats->hw) { + pos += scnprintf(buf + pos, bufsz - pos, + "\tLast Restarting Code: 0x%X\n", + isr_stats->err_code); + } +#ifdef CONFIG_IWLWIFI_DEBUG + pos += scnprintf(buf + pos, bufsz - pos, "Frame transmitted:\t\t %u\n", + isr_stats->sch); + pos += scnprintf(buf + pos, bufsz - pos, "Alive interrupt:\t\t %u\n", + isr_stats->alive); +#endif + pos += scnprintf(buf + pos, bufsz - pos, + "HW RF KILL switch toggled:\t %u\n", isr_stats->rfkill); + + pos += scnprintf(buf + pos, bufsz - pos, "CT KILL:\t\t\t %u\n", + isr_stats->ctkill); + + pos += scnprintf(buf + pos, bufsz - pos, "Wakeup Interrupt:\t\t %u\n", + isr_stats->wakeup); + + pos += scnprintf(buf + pos, bufsz - pos, + "Rx command responses:\t\t %u\n", isr_stats->rx); + + pos += scnprintf(buf + pos, bufsz - pos, "Tx/FH interrupt:\t\t %u\n", + isr_stats->tx); + + pos += scnprintf(buf + pos, bufsz - pos, "Unexpected INTA:\t\t %u\n", + isr_stats->unhandled); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_interrupt_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct isr_statistics *isr_stats = &trans_pcie->isr_stats; + u32 reset_flag; + int ret; + + ret = kstrtou32_from_user(user_buf, count, 16, &reset_flag); + if (ret) + return ret; + if (reset_flag == 0) + memset(isr_stats, 0, sizeof(*isr_stats)); + + return count; +} + +static ssize_t iwl_dbgfs_csr_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + + iwl_pcie_dump_csr(trans); + + return count; +} + +static ssize_t iwl_dbgfs_fh_reg_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + char *buf = NULL; + ssize_t ret; + + ret = iwl_dump_fh(trans, &buf); + if (ret < 0) + return ret; + if (!buf) + return -EINVAL; + ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_rfkill_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + char buf[100]; + int pos; + + pos = scnprintf(buf, sizeof(buf), "debug: %d\nhw: %d\n", + trans_pcie->debug_rfkill, + !(iwl_read32(trans, CSR_GP_CNTRL) & + CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW)); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_rfkill_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + bool new_value; + int ret; + + ret = kstrtobool_from_user(user_buf, count, &new_value); + if (ret) + return ret; + if (new_value == trans_pcie->debug_rfkill) + return count; + IWL_WARN(trans, "changing debug rfkill %d->%d\n", + trans_pcie->debug_rfkill, new_value); + trans_pcie->debug_rfkill = new_value; + iwl_pcie_handle_rfkill_irq(trans, false); + + return count; +} + +static int iwl_dbgfs_monitor_data_open(struct inode *inode, + struct file *file) +{ + struct iwl_trans *trans = inode->i_private; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (!trans->dbg.dest_tlv || + trans->dbg.dest_tlv->monitor_mode != EXTERNAL_MODE) { + IWL_ERR(trans, "Debug destination is not set to DRAM\n"); + return -ENOENT; + } + + if (trans_pcie->fw_mon_data.state != IWL_FW_MON_DBGFS_STATE_CLOSED) + return -EBUSY; + + trans_pcie->fw_mon_data.state = IWL_FW_MON_DBGFS_STATE_OPEN; + return simple_open(inode, file); +} + +static int iwl_dbgfs_monitor_data_release(struct inode *inode, + struct file *file) +{ + struct iwl_trans_pcie *trans_pcie = + IWL_TRANS_GET_PCIE_TRANS(inode->i_private); + + if (trans_pcie->fw_mon_data.state == IWL_FW_MON_DBGFS_STATE_OPEN) + trans_pcie->fw_mon_data.state = IWL_FW_MON_DBGFS_STATE_CLOSED; + return 0; +} + +static bool iwl_write_to_user_buf(char __user *user_buf, ssize_t count, + void *buf, ssize_t *size, + ssize_t *bytes_copied) +{ + ssize_t buf_size_left = count - *bytes_copied; + + buf_size_left = buf_size_left - (buf_size_left % sizeof(u32)); + if (*size > buf_size_left) + *size = buf_size_left; + + *size -= copy_to_user(user_buf, buf, *size); + *bytes_copied += *size; + + if (buf_size_left == *size) + return true; + return false; +} + +static ssize_t iwl_dbgfs_monitor_data_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u8 *cpu_addr = (void *)trans->dbg.fw_mon.block, *curr_buf; + struct cont_rec *data = &trans_pcie->fw_mon_data; + u32 write_ptr_addr, wrap_cnt_addr, write_ptr, wrap_cnt; + ssize_t size, bytes_copied = 0; + bool b_full; + + if (trans->dbg.dest_tlv) { + write_ptr_addr = + le32_to_cpu(trans->dbg.dest_tlv->write_ptr_reg); + wrap_cnt_addr = le32_to_cpu(trans->dbg.dest_tlv->wrap_count); + } else { + write_ptr_addr = MON_BUFF_WRPTR; + wrap_cnt_addr = MON_BUFF_CYCLE_CNT; + } + + if (unlikely(!trans->dbg.rec_on)) + return 0; + + mutex_lock(&data->mutex); + if (data->state == + IWL_FW_MON_DBGFS_STATE_DISABLED) { + mutex_unlock(&data->mutex); + return 0; + } + + /* write_ptr position in bytes rather then DW */ + write_ptr = iwl_read_prph(trans, write_ptr_addr) * sizeof(u32); + wrap_cnt = iwl_read_prph(trans, wrap_cnt_addr); + + if (data->prev_wrap_cnt == wrap_cnt) { + size = write_ptr - data->prev_wr_ptr; + curr_buf = cpu_addr + data->prev_wr_ptr; + b_full = iwl_write_to_user_buf(user_buf, count, + curr_buf, &size, + &bytes_copied); + data->prev_wr_ptr += size; + + } else if (data->prev_wrap_cnt == wrap_cnt - 1 && + write_ptr < data->prev_wr_ptr) { + size = trans->dbg.fw_mon.size - data->prev_wr_ptr; + curr_buf = cpu_addr + data->prev_wr_ptr; + b_full = iwl_write_to_user_buf(user_buf, count, + curr_buf, &size, + &bytes_copied); + data->prev_wr_ptr += size; + + if (!b_full) { + size = write_ptr; + b_full = iwl_write_to_user_buf(user_buf, count, + cpu_addr, &size, + &bytes_copied); + data->prev_wr_ptr = size; + data->prev_wrap_cnt++; + } + } else { + if (data->prev_wrap_cnt == wrap_cnt - 1 && + write_ptr > data->prev_wr_ptr) + IWL_WARN(trans, + "write pointer passed previous write pointer, start copying from the beginning\n"); + else if (!unlikely(data->prev_wrap_cnt == 0 && + data->prev_wr_ptr == 0)) + IWL_WARN(trans, + "monitor data is out of sync, start copying from the beginning\n"); + + size = write_ptr; + b_full = iwl_write_to_user_buf(user_buf, count, + cpu_addr, &size, + &bytes_copied); + data->prev_wr_ptr = size; + data->prev_wrap_cnt = wrap_cnt; + } + + mutex_unlock(&data->mutex); + + return bytes_copied; +} + +static ssize_t iwl_dbgfs_rf_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (!trans_pcie->rf_name[0]) + return -ENODEV; + + return simple_read_from_buffer(user_buf, count, ppos, + trans_pcie->rf_name, + strlen(trans_pcie->rf_name)); +} + +DEBUGFS_READ_WRITE_FILE_OPS(interrupt); +DEBUGFS_READ_FILE_OPS(fh_reg); +DEBUGFS_READ_FILE_OPS(rx_queue); +DEBUGFS_WRITE_FILE_OPS(csr); +DEBUGFS_READ_WRITE_FILE_OPS(rfkill); +DEBUGFS_READ_FILE_OPS(rf); + +static const struct file_operations iwl_dbgfs_tx_queue_ops = { + .owner = THIS_MODULE, + .open = iwl_dbgfs_tx_queue_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release_private, +}; + +static const struct file_operations iwl_dbgfs_monitor_data_ops = { + .read = iwl_dbgfs_monitor_data_read, + .open = iwl_dbgfs_monitor_data_open, + .release = iwl_dbgfs_monitor_data_release, +}; + +/* Create the debugfs files and directories */ +void iwl_trans_pcie_dbgfs_register(struct iwl_trans *trans) +{ + struct dentry *dir = trans->dbgfs_dir; + + DEBUGFS_ADD_FILE(rx_queue, dir, 0400); + DEBUGFS_ADD_FILE(tx_queue, dir, 0400); + DEBUGFS_ADD_FILE(interrupt, dir, 0600); + DEBUGFS_ADD_FILE(csr, dir, 0200); + DEBUGFS_ADD_FILE(fh_reg, dir, 0400); + DEBUGFS_ADD_FILE(rfkill, dir, 0600); + DEBUGFS_ADD_FILE(monitor_data, dir, 0400); + DEBUGFS_ADD_FILE(rf, dir, 0400); +} + +static void iwl_trans_pcie_debugfs_cleanup(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct cont_rec *data = &trans_pcie->fw_mon_data; + + mutex_lock(&data->mutex); + data->state = IWL_FW_MON_DBGFS_STATE_DISABLED; + mutex_unlock(&data->mutex); +} +#endif /*CONFIG_IWLWIFI_DEBUGFS */ + +static u32 iwl_trans_pcie_get_cmdlen(struct iwl_trans *trans, void *tfd) +{ + u32 cmdlen = 0; + int i; + + for (i = 0; i < trans->txqs.tfd.max_tbs; i++) + cmdlen += iwl_txq_gen1_tfd_tb_get_len(trans, tfd, i); + + return cmdlen; +} + +static u32 iwl_trans_pcie_dump_rbs(struct iwl_trans *trans, + struct iwl_fw_error_dump_data **data, + int allocated_rb_nums) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int max_len = trans_pcie->rx_buf_bytes; + /* Dump RBs is supported only for pre-9000 devices (1 queue) */ + struct iwl_rxq *rxq = &trans_pcie->rxq[0]; + u32 i, r, j, rb_len = 0; + + spin_lock_bh(&rxq->lock); + + r = le16_to_cpu(iwl_get_closed_rb_stts(trans, rxq)) & 0x0FFF; + + for (i = rxq->read, j = 0; + i != r && j < allocated_rb_nums; + i = (i + 1) & RX_QUEUE_MASK, j++) { + struct iwl_rx_mem_buffer *rxb = rxq->queue[i]; + struct iwl_fw_error_dump_rb *rb; + + dma_sync_single_for_cpu(trans->dev, rxb->page_dma, + max_len, DMA_FROM_DEVICE); + + rb_len += sizeof(**data) + sizeof(*rb) + max_len; + + (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RB); + (*data)->len = cpu_to_le32(sizeof(*rb) + max_len); + rb = (void *)(*data)->data; + rb->index = cpu_to_le32(i); + memcpy(rb->data, page_address(rxb->page), max_len); + + *data = iwl_fw_error_next_data(*data); + } + + spin_unlock_bh(&rxq->lock); + + return rb_len; +} +#define IWL_CSR_TO_DUMP (0x250) + +static u32 iwl_trans_pcie_dump_csr(struct iwl_trans *trans, + struct iwl_fw_error_dump_data **data) +{ + u32 csr_len = sizeof(**data) + IWL_CSR_TO_DUMP; + __le32 *val; + int i; + + (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_CSR); + (*data)->len = cpu_to_le32(IWL_CSR_TO_DUMP); + val = (void *)(*data)->data; + + for (i = 0; i < IWL_CSR_TO_DUMP; i += 4) + *val++ = cpu_to_le32(iwl_trans_pcie_read32(trans, i)); + + *data = iwl_fw_error_next_data(*data); + + return csr_len; +} + +static u32 iwl_trans_pcie_fh_regs_dump(struct iwl_trans *trans, + struct iwl_fw_error_dump_data **data) +{ + u32 fh_regs_len = FH_MEM_UPPER_BOUND - FH_MEM_LOWER_BOUND; + __le32 *val; + int i; + + if (!iwl_trans_grab_nic_access(trans)) + return 0; + + (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_FH_REGS); + (*data)->len = cpu_to_le32(fh_regs_len); + val = (void *)(*data)->data; + + if (!trans->trans_cfg->gen2) + for (i = FH_MEM_LOWER_BOUND; i < FH_MEM_UPPER_BOUND; + i += sizeof(u32)) + *val++ = cpu_to_le32(iwl_trans_pcie_read32(trans, i)); + else + for (i = iwl_umac_prph(trans, FH_MEM_LOWER_BOUND_GEN2); + i < iwl_umac_prph(trans, FH_MEM_UPPER_BOUND_GEN2); + i += sizeof(u32)) + *val++ = cpu_to_le32(iwl_trans_pcie_read_prph(trans, + i)); + + iwl_trans_release_nic_access(trans); + + *data = iwl_fw_error_next_data(*data); + + return sizeof(**data) + fh_regs_len; +} + +static u32 +iwl_trans_pci_dump_marbh_monitor(struct iwl_trans *trans, + struct iwl_fw_error_dump_fw_mon *fw_mon_data, + u32 monitor_len) +{ + u32 buf_size_in_dwords = (monitor_len >> 2); + u32 *buffer = (u32 *)fw_mon_data->data; + u32 i; + + if (!iwl_trans_grab_nic_access(trans)) + return 0; + + iwl_write_umac_prph_no_grab(trans, MON_DMARB_RD_CTL_ADDR, 0x1); + for (i = 0; i < buf_size_in_dwords; i++) + buffer[i] = iwl_read_umac_prph_no_grab(trans, + MON_DMARB_RD_DATA_ADDR); + iwl_write_umac_prph_no_grab(trans, MON_DMARB_RD_CTL_ADDR, 0x0); + + iwl_trans_release_nic_access(trans); + + return monitor_len; +} + +static void +iwl_trans_pcie_dump_pointers(struct iwl_trans *trans, + struct iwl_fw_error_dump_fw_mon *fw_mon_data) +{ + u32 base, base_high, write_ptr, write_ptr_val, wrap_cnt; + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + base = DBGC_CUR_DBGBUF_BASE_ADDR_LSB; + base_high = DBGC_CUR_DBGBUF_BASE_ADDR_MSB; + write_ptr = DBGC_CUR_DBGBUF_STATUS; + wrap_cnt = DBGC_DBGBUF_WRAP_AROUND; + } else if (trans->dbg.dest_tlv) { + write_ptr = le32_to_cpu(trans->dbg.dest_tlv->write_ptr_reg); + wrap_cnt = le32_to_cpu(trans->dbg.dest_tlv->wrap_count); + base = le32_to_cpu(trans->dbg.dest_tlv->base_reg); + } else { + base = MON_BUFF_BASE_ADDR; + write_ptr = MON_BUFF_WRPTR; + wrap_cnt = MON_BUFF_CYCLE_CNT; + } + + write_ptr_val = iwl_read_prph(trans, write_ptr); + fw_mon_data->fw_mon_cycle_cnt = + cpu_to_le32(iwl_read_prph(trans, wrap_cnt)); + fw_mon_data->fw_mon_base_ptr = + cpu_to_le32(iwl_read_prph(trans, base)); + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + fw_mon_data->fw_mon_base_high_ptr = + cpu_to_le32(iwl_read_prph(trans, base_high)); + write_ptr_val &= DBGC_CUR_DBGBUF_STATUS_OFFSET_MSK; + /* convert wrtPtr to DWs, to align with all HWs */ + write_ptr_val >>= 2; + } + fw_mon_data->fw_mon_wr_ptr = cpu_to_le32(write_ptr_val); +} + +static u32 +iwl_trans_pcie_dump_monitor(struct iwl_trans *trans, + struct iwl_fw_error_dump_data **data, + u32 monitor_len) +{ + struct iwl_dram_data *fw_mon = &trans->dbg.fw_mon; + u32 len = 0; + + if (trans->dbg.dest_tlv || + (fw_mon->size && + (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_7000 || + trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210))) { + struct iwl_fw_error_dump_fw_mon *fw_mon_data; + + (*data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_FW_MONITOR); + fw_mon_data = (void *)(*data)->data; + + iwl_trans_pcie_dump_pointers(trans, fw_mon_data); + + len += sizeof(**data) + sizeof(*fw_mon_data); + if (fw_mon->size) { + memcpy(fw_mon_data->data, fw_mon->block, fw_mon->size); + monitor_len = fw_mon->size; + } else if (trans->dbg.dest_tlv->monitor_mode == SMEM_MODE) { + u32 base = le32_to_cpu(fw_mon_data->fw_mon_base_ptr); + /* + * Update pointers to reflect actual values after + * shifting + */ + if (trans->dbg.dest_tlv->version) { + base = (iwl_read_prph(trans, base) & + IWL_LDBG_M2S_BUF_BA_MSK) << + trans->dbg.dest_tlv->base_shift; + base *= IWL_M2S_UNIT_SIZE; + base += trans->cfg->smem_offset; + } else { + base = iwl_read_prph(trans, base) << + trans->dbg.dest_tlv->base_shift; + } + + iwl_trans_read_mem(trans, base, fw_mon_data->data, + monitor_len / sizeof(u32)); + } else if (trans->dbg.dest_tlv->monitor_mode == MARBH_MODE) { + monitor_len = + iwl_trans_pci_dump_marbh_monitor(trans, + fw_mon_data, + monitor_len); + } else { + /* Didn't match anything - output no monitor data */ + monitor_len = 0; + } + + len += monitor_len; + (*data)->len = cpu_to_le32(monitor_len + sizeof(*fw_mon_data)); + } + + return len; +} + +static int iwl_trans_get_fw_monitor_len(struct iwl_trans *trans, u32 *len) +{ + if (trans->dbg.fw_mon.size) { + *len += sizeof(struct iwl_fw_error_dump_data) + + sizeof(struct iwl_fw_error_dump_fw_mon) + + trans->dbg.fw_mon.size; + return trans->dbg.fw_mon.size; + } else if (trans->dbg.dest_tlv) { + u32 base, end, cfg_reg, monitor_len; + + if (trans->dbg.dest_tlv->version == 1) { + cfg_reg = le32_to_cpu(trans->dbg.dest_tlv->base_reg); + cfg_reg = iwl_read_prph(trans, cfg_reg); + base = (cfg_reg & IWL_LDBG_M2S_BUF_BA_MSK) << + trans->dbg.dest_tlv->base_shift; + base *= IWL_M2S_UNIT_SIZE; + base += trans->cfg->smem_offset; + + monitor_len = + (cfg_reg & IWL_LDBG_M2S_BUF_SIZE_MSK) >> + trans->dbg.dest_tlv->end_shift; + monitor_len *= IWL_M2S_UNIT_SIZE; + } else { + base = le32_to_cpu(trans->dbg.dest_tlv->base_reg); + end = le32_to_cpu(trans->dbg.dest_tlv->end_reg); + + base = iwl_read_prph(trans, base) << + trans->dbg.dest_tlv->base_shift; + end = iwl_read_prph(trans, end) << + trans->dbg.dest_tlv->end_shift; + + /* Make "end" point to the actual end */ + if (trans->trans_cfg->device_family >= + IWL_DEVICE_FAMILY_8000 || + trans->dbg.dest_tlv->monitor_mode == MARBH_MODE) + end += (1 << trans->dbg.dest_tlv->end_shift); + monitor_len = end - base; + } + *len += sizeof(struct iwl_fw_error_dump_data) + + sizeof(struct iwl_fw_error_dump_fw_mon) + + monitor_len; + return monitor_len; + } + return 0; +} + +static struct iwl_trans_dump_data * +iwl_trans_pcie_dump_data(struct iwl_trans *trans, + u32 dump_mask, + const struct iwl_dump_sanitize_ops *sanitize_ops, + void *sanitize_ctx) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_fw_error_dump_data *data; + struct iwl_txq *cmdq = trans->txqs.txq[trans->txqs.cmd.q_id]; + struct iwl_fw_error_dump_txcmd *txcmd; + struct iwl_trans_dump_data *dump_data; + u32 len, num_rbs = 0, monitor_len = 0; + int i, ptr; + bool dump_rbs = test_bit(STATUS_FW_ERROR, &trans->status) && + !trans->trans_cfg->mq_rx_supported && + dump_mask & BIT(IWL_FW_ERROR_DUMP_RB); + + if (!dump_mask) + return NULL; + + /* transport dump header */ + len = sizeof(*dump_data); + + /* host commands */ + if (dump_mask & BIT(IWL_FW_ERROR_DUMP_TXCMD) && cmdq) + len += sizeof(*data) + + cmdq->n_window * (sizeof(*txcmd) + + TFD_MAX_PAYLOAD_SIZE); + + /* FW monitor */ + if (dump_mask & BIT(IWL_FW_ERROR_DUMP_FW_MONITOR)) + monitor_len = iwl_trans_get_fw_monitor_len(trans, &len); + + /* CSR registers */ + if (dump_mask & BIT(IWL_FW_ERROR_DUMP_CSR)) + len += sizeof(*data) + IWL_CSR_TO_DUMP; + + /* FH registers */ + if (dump_mask & BIT(IWL_FW_ERROR_DUMP_FH_REGS)) { + if (trans->trans_cfg->gen2) + len += sizeof(*data) + + (iwl_umac_prph(trans, FH_MEM_UPPER_BOUND_GEN2) - + iwl_umac_prph(trans, FH_MEM_LOWER_BOUND_GEN2)); + else + len += sizeof(*data) + + (FH_MEM_UPPER_BOUND - + FH_MEM_LOWER_BOUND); + } + + if (dump_rbs) { + /* Dump RBs is supported only for pre-9000 devices (1 queue) */ + struct iwl_rxq *rxq = &trans_pcie->rxq[0]; + /* RBs */ + num_rbs = + le16_to_cpu(iwl_get_closed_rb_stts(trans, rxq)) + & 0x0FFF; + num_rbs = (num_rbs - rxq->read) & RX_QUEUE_MASK; + len += num_rbs * (sizeof(*data) + + sizeof(struct iwl_fw_error_dump_rb) + + (PAGE_SIZE << trans_pcie->rx_page_order)); + } + + /* Paged memory for gen2 HW */ + if (trans->trans_cfg->gen2 && dump_mask & BIT(IWL_FW_ERROR_DUMP_PAGING)) + for (i = 0; i < trans->init_dram.paging_cnt; i++) + len += sizeof(*data) + + sizeof(struct iwl_fw_error_dump_paging) + + trans->init_dram.paging[i].size; + + dump_data = vzalloc(len); + if (!dump_data) + return NULL; + + len = 0; + data = (void *)dump_data->data; + + if (dump_mask & BIT(IWL_FW_ERROR_DUMP_TXCMD) && cmdq) { + u16 tfd_size = trans->txqs.tfd.size; + + data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_TXCMD); + txcmd = (void *)data->data; + spin_lock_bh(&cmdq->lock); + ptr = cmdq->write_ptr; + for (i = 0; i < cmdq->n_window; i++) { + u8 idx = iwl_txq_get_cmd_index(cmdq, ptr); + u8 tfdidx; + u32 caplen, cmdlen; + + if (trans->trans_cfg->use_tfh) + tfdidx = idx; + else + tfdidx = ptr; + + cmdlen = iwl_trans_pcie_get_cmdlen(trans, + (u8 *)cmdq->tfds + + tfd_size * tfdidx); + caplen = min_t(u32, TFD_MAX_PAYLOAD_SIZE, cmdlen); + + if (cmdlen) { + len += sizeof(*txcmd) + caplen; + txcmd->cmdlen = cpu_to_le32(cmdlen); + txcmd->caplen = cpu_to_le32(caplen); + memcpy(txcmd->data, cmdq->entries[idx].cmd, + caplen); + if (sanitize_ops && sanitize_ops->frob_hcmd) + sanitize_ops->frob_hcmd(sanitize_ctx, + txcmd->data, + caplen); + txcmd = (void *)((u8 *)txcmd->data + caplen); + } + + ptr = iwl_txq_dec_wrap(trans, ptr); + } + spin_unlock_bh(&cmdq->lock); + + data->len = cpu_to_le32(len); + len += sizeof(*data); + data = iwl_fw_error_next_data(data); + } + + if (dump_mask & BIT(IWL_FW_ERROR_DUMP_CSR)) + len += iwl_trans_pcie_dump_csr(trans, &data); + if (dump_mask & BIT(IWL_FW_ERROR_DUMP_FH_REGS)) + len += iwl_trans_pcie_fh_regs_dump(trans, &data); + if (dump_rbs) + len += iwl_trans_pcie_dump_rbs(trans, &data, num_rbs); + + /* Paged memory for gen2 HW */ + if (trans->trans_cfg->gen2 && + dump_mask & BIT(IWL_FW_ERROR_DUMP_PAGING)) { + for (i = 0; i < trans->init_dram.paging_cnt; i++) { + struct iwl_fw_error_dump_paging *paging; + u32 page_len = trans->init_dram.paging[i].size; + + data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_PAGING); + data->len = cpu_to_le32(sizeof(*paging) + page_len); + paging = (void *)data->data; + paging->index = cpu_to_le32(i); + memcpy(paging->data, + trans->init_dram.paging[i].block, page_len); + data = iwl_fw_error_next_data(data); + + len += sizeof(*data) + sizeof(*paging) + page_len; + } + } + if (dump_mask & BIT(IWL_FW_ERROR_DUMP_FW_MONITOR)) + len += iwl_trans_pcie_dump_monitor(trans, &data, monitor_len); + + dump_data->len = len; + + return dump_data; +} + +static void iwl_trans_pci_interrupts(struct iwl_trans *trans, bool enable) +{ + if (enable) + iwl_enable_interrupts(trans); + else + iwl_disable_interrupts(trans); +} + +static void iwl_trans_pcie_sync_nmi(struct iwl_trans *trans) +{ + u32 inta_addr, sw_err_bit; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (trans_pcie->msix_enabled) { + inta_addr = CSR_MSIX_HW_INT_CAUSES_AD; + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) + sw_err_bit = MSIX_HW_INT_CAUSES_REG_SW_ERR_BZ; + else + sw_err_bit = MSIX_HW_INT_CAUSES_REG_SW_ERR; + } else { + inta_addr = CSR_INT; + sw_err_bit = CSR_INT_BIT_SW_ERR; + } + + iwl_trans_sync_nmi_with_addr(trans, inta_addr, sw_err_bit); +} + +#define IWL_TRANS_COMMON_OPS \ + .op_mode_leave = iwl_trans_pcie_op_mode_leave, \ + .write8 = iwl_trans_pcie_write8, \ + .write32 = iwl_trans_pcie_write32, \ + .read32 = iwl_trans_pcie_read32, \ + .read_prph = iwl_trans_pcie_read_prph, \ + .write_prph = iwl_trans_pcie_write_prph, \ + .read_mem = iwl_trans_pcie_read_mem, \ + .write_mem = iwl_trans_pcie_write_mem, \ + .read_config32 = iwl_trans_pcie_read_config32, \ + .configure = iwl_trans_pcie_configure, \ + .set_pmi = iwl_trans_pcie_set_pmi, \ + .sw_reset = iwl_trans_pcie_sw_reset, \ + .grab_nic_access = iwl_trans_pcie_grab_nic_access, \ + .release_nic_access = iwl_trans_pcie_release_nic_access, \ + .set_bits_mask = iwl_trans_pcie_set_bits_mask, \ + .dump_data = iwl_trans_pcie_dump_data, \ + .d3_suspend = iwl_trans_pcie_d3_suspend, \ + .d3_resume = iwl_trans_pcie_d3_resume, \ + .interrupts = iwl_trans_pci_interrupts, \ + .sync_nmi = iwl_trans_pcie_sync_nmi, \ + .imr_dma_data = iwl_trans_pcie_copy_imr \ + +static const struct iwl_trans_ops trans_ops_pcie = { + IWL_TRANS_COMMON_OPS, + .start_hw = iwl_trans_pcie_start_hw, + .fw_alive = iwl_trans_pcie_fw_alive, + .start_fw = iwl_trans_pcie_start_fw, + .stop_device = iwl_trans_pcie_stop_device, + + .send_cmd = iwl_pcie_enqueue_hcmd, + + .tx = iwl_trans_pcie_tx, + .reclaim = iwl_txq_reclaim, + + .txq_disable = iwl_trans_pcie_txq_disable, + .txq_enable = iwl_trans_pcie_txq_enable, + + .txq_set_shared_mode = iwl_trans_pcie_txq_set_shared_mode, + + .wait_tx_queues_empty = iwl_trans_pcie_wait_txqs_empty, + + .freeze_txq_timer = iwl_trans_txq_freeze_timer, + .block_txq_ptrs = iwl_trans_pcie_block_txq_ptrs, +#ifdef CONFIG_IWLWIFI_DEBUGFS + .debugfs_cleanup = iwl_trans_pcie_debugfs_cleanup, +#endif +}; + +static const struct iwl_trans_ops trans_ops_pcie_gen2 = { + IWL_TRANS_COMMON_OPS, + .start_hw = iwl_trans_pcie_start_hw, + .fw_alive = iwl_trans_pcie_gen2_fw_alive, + .start_fw = iwl_trans_pcie_gen2_start_fw, + .stop_device = iwl_trans_pcie_gen2_stop_device, + + .send_cmd = iwl_pcie_gen2_enqueue_hcmd, + + .tx = iwl_txq_gen2_tx, + .reclaim = iwl_txq_reclaim, + + .set_q_ptrs = iwl_txq_set_q_ptrs, + + .txq_alloc = iwl_txq_dyn_alloc, + .txq_free = iwl_txq_dyn_free, + .wait_txq_empty = iwl_trans_pcie_wait_txq_empty, + .rxq_dma_data = iwl_trans_pcie_rxq_dma_data, + .set_pnvm = iwl_trans_pcie_ctx_info_gen3_set_pnvm, + .set_reduce_power = iwl_trans_pcie_ctx_info_gen3_set_reduce_power, +#ifdef CONFIG_IWLWIFI_DEBUGFS + .debugfs_cleanup = iwl_trans_pcie_debugfs_cleanup, +#endif +}; + +struct iwl_trans *iwl_trans_pcie_alloc(struct pci_dev *pdev, + const struct pci_device_id *ent, + const struct iwl_cfg_trans_params *cfg_trans) +{ + struct iwl_trans_pcie *trans_pcie; + struct iwl_trans *trans; + int ret, addr_size; + const struct iwl_trans_ops *ops = &trans_ops_pcie_gen2; + void __iomem * const *table; + + if (!cfg_trans->gen2) + ops = &trans_ops_pcie; + + ret = pcim_enable_device(pdev); + if (ret) + return ERR_PTR(ret); + + trans = iwl_trans_alloc(sizeof(struct iwl_trans_pcie), &pdev->dev, ops, + cfg_trans); + if (!trans) + return ERR_PTR(-ENOMEM); + + trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + trans_pcie->trans = trans; + trans_pcie->opmode_down = true; + spin_lock_init(&trans_pcie->irq_lock); + spin_lock_init(&trans_pcie->reg_lock); + spin_lock_init(&trans_pcie->alloc_page_lock); + mutex_init(&trans_pcie->mutex); + init_waitqueue_head(&trans_pcie->ucode_write_waitq); + init_waitqueue_head(&trans_pcie->fw_reset_waitq); + init_waitqueue_head(&trans_pcie->imr_waitq); + + trans_pcie->rba.alloc_wq = alloc_workqueue("rb_allocator", + WQ_HIGHPRI | WQ_UNBOUND, 1); + if (!trans_pcie->rba.alloc_wq) { + ret = -ENOMEM; + goto out_free_trans; + } + INIT_WORK(&trans_pcie->rba.rx_alloc, iwl_pcie_rx_allocator_work); + + trans_pcie->debug_rfkill = -1; + + if (!cfg_trans->base_params->pcie_l1_allowed) { + /* + * W/A - seems to solve weird behavior. We need to remove this + * if we don't want to stay in L1 all the time. This wastes a + * lot of power. + */ + pci_disable_link_state(pdev, PCIE_LINK_STATE_L0S | + PCIE_LINK_STATE_L1 | + PCIE_LINK_STATE_CLKPM); + } + + trans_pcie->def_rx_queue = 0; + + pci_set_master(pdev); + + addr_size = trans->txqs.tfd.addr_size; + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(addr_size)); + if (ret) { + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + /* both attempts failed: */ + if (ret) { + dev_err(&pdev->dev, "No suitable DMA available\n"); + goto out_no_pci; + } + } + + ret = pcim_iomap_regions_request_all(pdev, BIT(0), DRV_NAME); + if (ret) { + dev_err(&pdev->dev, "pcim_iomap_regions_request_all failed\n"); + goto out_no_pci; + } + + table = pcim_iomap_table(pdev); + if (!table) { + dev_err(&pdev->dev, "pcim_iomap_table failed\n"); + ret = -ENOMEM; + goto out_no_pci; + } + + trans_pcie->hw_base = table[0]; + if (!trans_pcie->hw_base) { + dev_err(&pdev->dev, "couldn't find IO mem in first BAR\n"); + ret = -ENODEV; + goto out_no_pci; + } + + /* We disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state */ + pci_write_config_byte(pdev, PCI_CFG_RETRY_TIMEOUT, 0x00); + + trans_pcie->pci_dev = pdev; + iwl_disable_interrupts(trans); + + trans->hw_rev = iwl_read32(trans, CSR_HW_REV); + if (trans->hw_rev == 0xffffffff) { + dev_err(&pdev->dev, "HW_REV=0xFFFFFFFF, PCI issues?\n"); + ret = -EIO; + goto out_no_pci; + } + + /* + * In the 8000 HW family the format of the 4 bytes of CSR_HW_REV have + * changed, and now the revision step also includes bit 0-1 (no more + * "dash" value). To keep hw_rev backwards compatible - we'll store it + * in the old format. + */ + if (cfg_trans->device_family >= IWL_DEVICE_FAMILY_8000) + trans->hw_rev_step = trans->hw_rev & 0xF; + else + trans->hw_rev_step = (trans->hw_rev & 0xC) >> 2; + + IWL_DEBUG_INFO(trans, "HW REV: 0x%0x\n", trans->hw_rev); + + iwl_pcie_set_interrupt_capa(pdev, trans, cfg_trans); + trans->hw_id = (pdev->device << 16) + pdev->subsystem_device; + snprintf(trans->hw_id_str, sizeof(trans->hw_id_str), + "PCI ID: 0x%04X:0x%04X", pdev->device, pdev->subsystem_device); + + init_waitqueue_head(&trans_pcie->sx_waitq); + + + if (trans_pcie->msix_enabled) { + ret = iwl_pcie_init_msix_handler(pdev, trans_pcie); + if (ret) + goto out_no_pci; + } else { + ret = iwl_pcie_alloc_ict(trans); + if (ret) + goto out_no_pci; + + ret = devm_request_threaded_irq(&pdev->dev, pdev->irq, + iwl_pcie_isr, + iwl_pcie_irq_handler, + IRQF_SHARED, DRV_NAME, trans); + if (ret) { + IWL_ERR(trans, "Error allocating IRQ %d\n", pdev->irq); + goto out_free_ict; + } + } + +#ifdef CONFIG_IWLWIFI_DEBUGFS + trans_pcie->fw_mon_data.state = IWL_FW_MON_DBGFS_STATE_CLOSED; + mutex_init(&trans_pcie->fw_mon_data.mutex); +#endif + + iwl_dbg_tlv_init(trans); + + return trans; + +out_free_ict: + iwl_pcie_free_ict(trans); +out_no_pci: + destroy_workqueue(trans_pcie->rba.alloc_wq); +out_free_trans: + iwl_trans_free(trans); + return ERR_PTR(ret); +} + +void iwl_trans_pcie_copy_imr_fh(struct iwl_trans *trans, + u32 dst_addr, u64 src_addr, u32 byte_cnt) +{ + iwl_write_prph(trans, IMR_UREG_CHICK, + iwl_read_prph(trans, IMR_UREG_CHICK) | + IMR_UREG_CHICK_HALT_UMAC_PERMANENTLY_MSK); + iwl_write_prph(trans, IMR_TFH_SRV_DMA_CHNL0_SRAM_ADDR, dst_addr); + iwl_write_prph(trans, IMR_TFH_SRV_DMA_CHNL0_DRAM_ADDR_LSB, + (u32)(src_addr & 0xFFFFFFFF)); + iwl_write_prph(trans, IMR_TFH_SRV_DMA_CHNL0_DRAM_ADDR_MSB, + iwl_get_dma_hi_addr(src_addr)); + iwl_write_prph(trans, IMR_TFH_SRV_DMA_CHNL0_BC, byte_cnt); + iwl_write_prph(trans, IMR_TFH_SRV_DMA_CHNL0_CTRL, + IMR_TFH_SRV_DMA_CHNL0_CTRL_D2S_IRQ_TARGET_POS | + IMR_TFH_SRV_DMA_CHNL0_CTRL_D2S_DMA_EN_POS | + IMR_TFH_SRV_DMA_CHNL0_CTRL_D2S_RS_MSK); +} + +int iwl_trans_pcie_copy_imr(struct iwl_trans *trans, + u32 dst_addr, u64 src_addr, u32 byte_cnt) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret = -1; + + trans_pcie->imr_status = IMR_D2S_REQUESTED; + iwl_trans_pcie_copy_imr_fh(trans, dst_addr, src_addr, byte_cnt); + ret = wait_event_timeout(trans_pcie->imr_waitq, + trans_pcie->imr_status != + IMR_D2S_REQUESTED, 5 * HZ); + if (!ret || trans_pcie->imr_status == IMR_D2S_ERROR) { + IWL_ERR(trans, "Failed to copy IMR Memory chunk!\n"); + iwl_trans_pcie_dump_regs(trans); + return -ETIMEDOUT; + } + trans_pcie->imr_status = IMR_D2S_IDLE; + return 0; +} diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/tx-gen2.c b/drivers/net/wireless/intel/iwlwifi/pcie/tx-gen2.c new file mode 100644 index 000000000..c72a84d8b --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/pcie/tx-gen2.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2017 Intel Deutschland GmbH + * Copyright (C) 2018-2020 Intel Corporation + */ +#include <net/tso.h> +#include <linux/tcp.h> + +#include "iwl-debug.h" +#include "iwl-csr.h" +#include "iwl-io.h" +#include "internal.h" +#include "fw/api/tx.h" +#include "queue/tx.h" + +/*************** HOST COMMAND QUEUE FUNCTIONS *****/ + +/* + * iwl_pcie_gen2_enqueue_hcmd - enqueue a uCode command + * @priv: device private data point + * @cmd: a pointer to the ucode command structure + * + * The function returns < 0 values to indicate the operation + * failed. On success, it returns the index (>= 0) of command in the + * command queue. + */ +int iwl_pcie_gen2_enqueue_hcmd(struct iwl_trans *trans, + struct iwl_host_cmd *cmd) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_txq *txq = trans->txqs.txq[trans->txqs.cmd.q_id]; + struct iwl_device_cmd *out_cmd; + struct iwl_cmd_meta *out_meta; + void *dup_buf = NULL; + dma_addr_t phys_addr; + int i, cmd_pos, idx; + u16 copy_size, cmd_size, tb0_size; + bool had_nocopy = false; + u8 group_id = iwl_cmd_groupid(cmd->id); + const u8 *cmddata[IWL_MAX_CMD_TBS_PER_TFD]; + u16 cmdlen[IWL_MAX_CMD_TBS_PER_TFD]; + struct iwl_tfh_tfd *tfd; + unsigned long flags; + + copy_size = sizeof(struct iwl_cmd_header_wide); + cmd_size = sizeof(struct iwl_cmd_header_wide); + + for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) { + cmddata[i] = cmd->data[i]; + cmdlen[i] = cmd->len[i]; + + if (!cmd->len[i]) + continue; + + /* need at least IWL_FIRST_TB_SIZE copied */ + if (copy_size < IWL_FIRST_TB_SIZE) { + int copy = IWL_FIRST_TB_SIZE - copy_size; + + if (copy > cmdlen[i]) + copy = cmdlen[i]; + cmdlen[i] -= copy; + cmddata[i] += copy; + copy_size += copy; + } + + if (cmd->dataflags[i] & IWL_HCMD_DFL_NOCOPY) { + had_nocopy = true; + if (WARN_ON(cmd->dataflags[i] & IWL_HCMD_DFL_DUP)) { + idx = -EINVAL; + goto free_dup_buf; + } + } else if (cmd->dataflags[i] & IWL_HCMD_DFL_DUP) { + /* + * This is also a chunk that isn't copied + * to the static buffer so set had_nocopy. + */ + had_nocopy = true; + + /* only allowed once */ + if (WARN_ON(dup_buf)) { + idx = -EINVAL; + goto free_dup_buf; + } + + dup_buf = kmemdup(cmddata[i], cmdlen[i], + GFP_ATOMIC); + if (!dup_buf) + return -ENOMEM; + } else { + /* NOCOPY must not be followed by normal! */ + if (WARN_ON(had_nocopy)) { + idx = -EINVAL; + goto free_dup_buf; + } + copy_size += cmdlen[i]; + } + cmd_size += cmd->len[i]; + } + + /* + * If any of the command structures end up being larger than the + * TFD_MAX_PAYLOAD_SIZE and they aren't dynamically allocated into + * separate TFDs, then we will need to increase the size of the buffers + */ + if (WARN(copy_size > TFD_MAX_PAYLOAD_SIZE, + "Command %s (%#x) is too large (%d bytes)\n", + iwl_get_cmd_string(trans, cmd->id), cmd->id, copy_size)) { + idx = -EINVAL; + goto free_dup_buf; + } + + spin_lock_irqsave(&txq->lock, flags); + + idx = iwl_txq_get_cmd_index(txq, txq->write_ptr); + tfd = iwl_txq_get_tfd(trans, txq, txq->write_ptr); + memset(tfd, 0, sizeof(*tfd)); + + if (iwl_txq_space(trans, txq) < ((cmd->flags & CMD_ASYNC) ? 2 : 1)) { + spin_unlock_irqrestore(&txq->lock, flags); + + IWL_ERR(trans, "No space in command queue\n"); + iwl_op_mode_cmd_queue_full(trans->op_mode); + idx = -ENOSPC; + goto free_dup_buf; + } + + out_cmd = txq->entries[idx].cmd; + out_meta = &txq->entries[idx].meta; + + /* re-initialize to NULL */ + memset(out_meta, 0, sizeof(*out_meta)); + if (cmd->flags & CMD_WANT_SKB) + out_meta->source = cmd; + + /* set up the header */ + out_cmd->hdr_wide.cmd = iwl_cmd_opcode(cmd->id); + out_cmd->hdr_wide.group_id = group_id; + out_cmd->hdr_wide.version = iwl_cmd_version(cmd->id); + out_cmd->hdr_wide.length = + cpu_to_le16(cmd_size - sizeof(struct iwl_cmd_header_wide)); + out_cmd->hdr_wide.reserved = 0; + out_cmd->hdr_wide.sequence = + cpu_to_le16(QUEUE_TO_SEQ(trans->txqs.cmd.q_id) | + INDEX_TO_SEQ(txq->write_ptr)); + + cmd_pos = sizeof(struct iwl_cmd_header_wide); + copy_size = sizeof(struct iwl_cmd_header_wide); + + /* and copy the data that needs to be copied */ + for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) { + int copy; + + if (!cmd->len[i]) + continue; + + /* copy everything if not nocopy/dup */ + if (!(cmd->dataflags[i] & (IWL_HCMD_DFL_NOCOPY | + IWL_HCMD_DFL_DUP))) { + copy = cmd->len[i]; + + memcpy((u8 *)out_cmd + cmd_pos, cmd->data[i], copy); + cmd_pos += copy; + copy_size += copy; + continue; + } + + /* + * Otherwise we need at least IWL_FIRST_TB_SIZE copied + * in total (for bi-directional DMA), but copy up to what + * we can fit into the payload for debug dump purposes. + */ + copy = min_t(int, TFD_MAX_PAYLOAD_SIZE - cmd_pos, cmd->len[i]); + + memcpy((u8 *)out_cmd + cmd_pos, cmd->data[i], copy); + cmd_pos += copy; + + /* However, treat copy_size the proper way, we need it below */ + if (copy_size < IWL_FIRST_TB_SIZE) { + copy = IWL_FIRST_TB_SIZE - copy_size; + + if (copy > cmd->len[i]) + copy = cmd->len[i]; + copy_size += copy; + } + } + + IWL_DEBUG_HC(trans, + "Sending command %s (%.2x.%.2x), seq: 0x%04X, %d bytes at %d[%d]:%d\n", + iwl_get_cmd_string(trans, cmd->id), group_id, + out_cmd->hdr.cmd, le16_to_cpu(out_cmd->hdr.sequence), + cmd_size, txq->write_ptr, idx, trans->txqs.cmd.q_id); + + /* start the TFD with the minimum copy bytes */ + tb0_size = min_t(int, copy_size, IWL_FIRST_TB_SIZE); + memcpy(&txq->first_tb_bufs[idx], out_cmd, tb0_size); + iwl_txq_gen2_set_tb(trans, tfd, iwl_txq_get_first_tb_dma(txq, idx), + tb0_size); + + /* map first command fragment, if any remains */ + if (copy_size > tb0_size) { + phys_addr = dma_map_single(trans->dev, + (u8 *)out_cmd + tb0_size, + copy_size - tb0_size, + DMA_TO_DEVICE); + if (dma_mapping_error(trans->dev, phys_addr)) { + idx = -ENOMEM; + iwl_txq_gen2_tfd_unmap(trans, out_meta, tfd); + goto out; + } + iwl_txq_gen2_set_tb(trans, tfd, phys_addr, + copy_size - tb0_size); + } + + /* map the remaining (adjusted) nocopy/dup fragments */ + for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) { + void *data = (void *)(uintptr_t)cmddata[i]; + + if (!cmdlen[i]) + continue; + if (!(cmd->dataflags[i] & (IWL_HCMD_DFL_NOCOPY | + IWL_HCMD_DFL_DUP))) + continue; + if (cmd->dataflags[i] & IWL_HCMD_DFL_DUP) + data = dup_buf; + phys_addr = dma_map_single(trans->dev, data, + cmdlen[i], DMA_TO_DEVICE); + if (dma_mapping_error(trans->dev, phys_addr)) { + idx = -ENOMEM; + iwl_txq_gen2_tfd_unmap(trans, out_meta, tfd); + goto out; + } + iwl_txq_gen2_set_tb(trans, tfd, phys_addr, cmdlen[i]); + } + + BUILD_BUG_ON(IWL_TFH_NUM_TBS > sizeof(out_meta->tbs) * BITS_PER_BYTE); + out_meta->flags = cmd->flags; + if (WARN_ON_ONCE(txq->entries[idx].free_buf)) + kfree_sensitive(txq->entries[idx].free_buf); + txq->entries[idx].free_buf = dup_buf; + + trace_iwlwifi_dev_hcmd(trans->dev, cmd, cmd_size, &out_cmd->hdr_wide); + + /* start timer if queue currently empty */ + if (txq->read_ptr == txq->write_ptr && txq->wd_timeout) + mod_timer(&txq->stuck_timer, jiffies + txq->wd_timeout); + + spin_lock(&trans_pcie->reg_lock); + /* Increment and update queue's write index */ + txq->write_ptr = iwl_txq_inc_wrap(trans, txq->write_ptr); + iwl_txq_inc_wr_ptr(trans, txq); + spin_unlock(&trans_pcie->reg_lock); + +out: + spin_unlock_irqrestore(&txq->lock, flags); +free_dup_buf: + if (idx < 0) + kfree(dup_buf); + return idx; +} diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/tx.c b/drivers/net/wireless/intel/iwlwifi/pcie/tx.c new file mode 100644 index 000000000..3546c5269 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/pcie/tx.c @@ -0,0 +1,1630 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2003-2014, 2018-2021 Intel Corporation + * Copyright (C) 2013-2015 Intel Mobile Communications GmbH + * Copyright (C) 2016-2017 Intel Deutschland GmbH + */ +#include <linux/etherdevice.h> +#include <linux/ieee80211.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <net/ip6_checksum.h> +#include <net/tso.h> + +#include "iwl-debug.h" +#include "iwl-csr.h" +#include "iwl-prph.h" +#include "iwl-io.h" +#include "iwl-scd.h" +#include "iwl-op-mode.h" +#include "internal.h" +#include "fw/api/tx.h" + +/*************** DMA-QUEUE-GENERAL-FUNCTIONS ***** + * DMA services + * + * Theory of operation + * + * A Tx or Rx queue resides in host DRAM, and is comprised of a circular buffer + * of buffer descriptors, each of which points to one or more data buffers for + * the device to read from or fill. Driver and device exchange status of each + * queue via "read" and "write" pointers. Driver keeps minimum of 2 empty + * entries in each circular buffer, to protect against confusing empty and full + * queue states. + * + * The device reads or writes the data in the queues via the device's several + * DMA/FIFO channels. Each queue is mapped to a single DMA channel. + * + * For Tx queue, there are low mark and high mark limits. If, after queuing + * the packet for Tx, free space become < low mark, Tx queue stopped. When + * reclaiming packets (on 'tx done IRQ), if free space become > high mark, + * Tx queue resumed. + * + ***************************************************/ + + +int iwl_pcie_alloc_dma_ptr(struct iwl_trans *trans, + struct iwl_dma_ptr *ptr, size_t size) +{ + if (WARN_ON(ptr->addr)) + return -EINVAL; + + ptr->addr = dma_alloc_coherent(trans->dev, size, + &ptr->dma, GFP_KERNEL); + if (!ptr->addr) + return -ENOMEM; + ptr->size = size; + return 0; +} + +void iwl_pcie_free_dma_ptr(struct iwl_trans *trans, struct iwl_dma_ptr *ptr) +{ + if (unlikely(!ptr->addr)) + return; + + dma_free_coherent(trans->dev, ptr->size, ptr->addr, ptr->dma); + memset(ptr, 0, sizeof(*ptr)); +} + +/* + * iwl_pcie_txq_inc_wr_ptr - Send new write index to hardware + */ +static void iwl_pcie_txq_inc_wr_ptr(struct iwl_trans *trans, + struct iwl_txq *txq) +{ + u32 reg = 0; + int txq_id = txq->id; + + lockdep_assert_held(&txq->lock); + + /* + * explicitly wake up the NIC if: + * 1. shadow registers aren't enabled + * 2. NIC is woken up for CMD regardless of shadow outside this function + * 3. there is a chance that the NIC is asleep + */ + if (!trans->trans_cfg->base_params->shadow_reg_enable && + txq_id != trans->txqs.cmd.q_id && + test_bit(STATUS_TPOWER_PMI, &trans->status)) { + /* + * wake up nic if it's powered down ... + * uCode will wake up, and interrupt us again, so next + * time we'll skip this part. + */ + reg = iwl_read32(trans, CSR_UCODE_DRV_GP1); + + if (reg & CSR_UCODE_DRV_GP1_BIT_MAC_SLEEP) { + IWL_DEBUG_INFO(trans, "Tx queue %d requesting wakeup, GP1 = 0x%x\n", + txq_id, reg); + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + txq->need_update = true; + return; + } + } + + /* + * if not in power-save mode, uCode will never sleep when we're + * trying to tx (during RFKILL, we're not trying to tx). + */ + IWL_DEBUG_TX(trans, "Q:%d WR: 0x%x\n", txq_id, txq->write_ptr); + if (!txq->block) + iwl_write32(trans, HBUS_TARG_WRPTR, + txq->write_ptr | (txq_id << 8)); +} + +void iwl_pcie_txq_check_wrptrs(struct iwl_trans *trans) +{ + int i; + + for (i = 0; i < trans->trans_cfg->base_params->num_of_queues; i++) { + struct iwl_txq *txq = trans->txqs.txq[i]; + + if (!test_bit(i, trans->txqs.queue_used)) + continue; + + spin_lock_bh(&txq->lock); + if (txq->need_update) { + iwl_pcie_txq_inc_wr_ptr(trans, txq); + txq->need_update = false; + } + spin_unlock_bh(&txq->lock); + } +} + +static inline void iwl_pcie_tfd_set_tb(struct iwl_trans *trans, void *tfd, + u8 idx, dma_addr_t addr, u16 len) +{ + struct iwl_tfd *tfd_fh = (void *)tfd; + struct iwl_tfd_tb *tb = &tfd_fh->tbs[idx]; + + u16 hi_n_len = len << 4; + + put_unaligned_le32(addr, &tb->lo); + hi_n_len |= iwl_get_dma_hi_addr(addr); + + tb->hi_n_len = cpu_to_le16(hi_n_len); + + tfd_fh->num_tbs = idx + 1; +} + +static int iwl_pcie_txq_build_tfd(struct iwl_trans *trans, struct iwl_txq *txq, + dma_addr_t addr, u16 len, bool reset) +{ + void *tfd; + u32 num_tbs; + + tfd = (u8 *)txq->tfds + trans->txqs.tfd.size * txq->write_ptr; + + if (reset) + memset(tfd, 0, trans->txqs.tfd.size); + + num_tbs = iwl_txq_gen1_tfd_get_num_tbs(trans, tfd); + + /* Each TFD can point to a maximum max_tbs Tx buffers */ + if (num_tbs >= trans->txqs.tfd.max_tbs) { + IWL_ERR(trans, "Error can not send more than %d chunks\n", + trans->txqs.tfd.max_tbs); + return -EINVAL; + } + + if (WARN(addr & ~IWL_TX_DMA_MASK, + "Unaligned address = %llx\n", (unsigned long long)addr)) + return -EINVAL; + + iwl_pcie_tfd_set_tb(trans, tfd, num_tbs, addr, len); + + return num_tbs; +} + +static void iwl_pcie_clear_cmd_in_flight(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (!trans->trans_cfg->base_params->apmg_wake_up_wa) + return; + + spin_lock(&trans_pcie->reg_lock); + + if (WARN_ON(!trans_pcie->cmd_hold_nic_awake)) { + spin_unlock(&trans_pcie->reg_lock); + return; + } + + trans_pcie->cmd_hold_nic_awake = false; + __iwl_trans_pcie_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + spin_unlock(&trans_pcie->reg_lock); +} + +/* + * iwl_pcie_txq_unmap - Unmap any remaining DMA mappings and free skb's + */ +static void iwl_pcie_txq_unmap(struct iwl_trans *trans, int txq_id) +{ + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + + if (!txq) { + IWL_ERR(trans, "Trying to free a queue that wasn't allocated?\n"); + return; + } + + spin_lock_bh(&txq->lock); + while (txq->write_ptr != txq->read_ptr) { + IWL_DEBUG_TX_REPLY(trans, "Q %d Free %d\n", + txq_id, txq->read_ptr); + + if (txq_id != trans->txqs.cmd.q_id) { + struct sk_buff *skb = txq->entries[txq->read_ptr].skb; + + if (WARN_ON_ONCE(!skb)) + continue; + + iwl_txq_free_tso_page(trans, skb); + } + iwl_txq_free_tfd(trans, txq); + txq->read_ptr = iwl_txq_inc_wrap(trans, txq->read_ptr); + + if (txq->read_ptr == txq->write_ptr && + txq_id == trans->txqs.cmd.q_id) + iwl_pcie_clear_cmd_in_flight(trans); + } + + while (!skb_queue_empty(&txq->overflow_q)) { + struct sk_buff *skb = __skb_dequeue(&txq->overflow_q); + + iwl_op_mode_free_skb(trans->op_mode, skb); + } + + spin_unlock_bh(&txq->lock); + + /* just in case - this queue may have been stopped */ + iwl_wake_queue(trans, txq); +} + +/* + * iwl_pcie_txq_free - Deallocate DMA queue. + * @txq: Transmit queue to deallocate. + * + * Empty queue by removing and destroying all BD's. + * Free all buffers. + * 0-fill, but do not free "txq" descriptor structure. + */ +static void iwl_pcie_txq_free(struct iwl_trans *trans, int txq_id) +{ + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + struct device *dev = trans->dev; + int i; + + if (WARN_ON(!txq)) + return; + + iwl_pcie_txq_unmap(trans, txq_id); + + /* De-alloc array of command/tx buffers */ + if (txq_id == trans->txqs.cmd.q_id) + for (i = 0; i < txq->n_window; i++) { + kfree_sensitive(txq->entries[i].cmd); + kfree_sensitive(txq->entries[i].free_buf); + } + + /* De-alloc circular buffer of TFDs */ + if (txq->tfds) { + dma_free_coherent(dev, + trans->txqs.tfd.size * + trans->trans_cfg->base_params->max_tfd_queue_size, + txq->tfds, txq->dma_addr); + txq->dma_addr = 0; + txq->tfds = NULL; + + dma_free_coherent(dev, + sizeof(*txq->first_tb_bufs) * txq->n_window, + txq->first_tb_bufs, txq->first_tb_dma); + } + + kfree(txq->entries); + txq->entries = NULL; + + del_timer_sync(&txq->stuck_timer); + + /* 0-fill queue descriptor structure */ + memset(txq, 0, sizeof(*txq)); +} + +void iwl_pcie_tx_start(struct iwl_trans *trans, u32 scd_base_addr) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int nq = trans->trans_cfg->base_params->num_of_queues; + int chan; + u32 reg_val; + int clear_dwords = (SCD_TRANS_TBL_OFFSET_QUEUE(nq) - + SCD_CONTEXT_MEM_LOWER_BOUND) / sizeof(u32); + + /* make sure all queue are not stopped/used */ + memset(trans->txqs.queue_stopped, 0, + sizeof(trans->txqs.queue_stopped)); + memset(trans->txqs.queue_used, 0, sizeof(trans->txqs.queue_used)); + + trans_pcie->scd_base_addr = + iwl_read_prph(trans, SCD_SRAM_BASE_ADDR); + + WARN_ON(scd_base_addr != 0 && + scd_base_addr != trans_pcie->scd_base_addr); + + /* reset context data, TX status and translation data */ + iwl_trans_write_mem(trans, trans_pcie->scd_base_addr + + SCD_CONTEXT_MEM_LOWER_BOUND, + NULL, clear_dwords); + + iwl_write_prph(trans, SCD_DRAM_BASE_ADDR, + trans->txqs.scd_bc_tbls.dma >> 10); + + /* The chain extension of the SCD doesn't work well. This feature is + * enabled by default by the HW, so we need to disable it manually. + */ + if (trans->trans_cfg->base_params->scd_chain_ext_wa) + iwl_write_prph(trans, SCD_CHAINEXT_EN, 0); + + iwl_trans_ac_txq_enable(trans, trans->txqs.cmd.q_id, + trans->txqs.cmd.fifo, + trans->txqs.cmd.wdg_timeout); + + /* Activate all Tx DMA/FIFO channels */ + iwl_scd_activate_fifos(trans); + + /* Enable DMA channel */ + for (chan = 0; chan < FH_TCSR_CHNL_NUM; chan++) + iwl_write_direct32(trans, FH_TCSR_CHNL_TX_CONFIG_REG(chan), + FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE | + FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE); + + /* Update FH chicken bits */ + reg_val = iwl_read_direct32(trans, FH_TX_CHICKEN_BITS_REG); + iwl_write_direct32(trans, FH_TX_CHICKEN_BITS_REG, + reg_val | FH_TX_CHICKEN_BITS_SCD_AUTO_RETRY_EN); + + /* Enable L1-Active */ + if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_8000) + iwl_clear_bits_prph(trans, APMG_PCIDEV_STT_REG, + APMG_PCIDEV_STT_VAL_L1_ACT_DIS); +} + +void iwl_trans_pcie_tx_reset(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int txq_id; + + /* + * we should never get here in gen2 trans mode return early to avoid + * having invalid accesses + */ + if (WARN_ON_ONCE(trans->trans_cfg->gen2)) + return; + + for (txq_id = 0; txq_id < trans->trans_cfg->base_params->num_of_queues; + txq_id++) { + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + if (trans->trans_cfg->use_tfh) + iwl_write_direct64(trans, + FH_MEM_CBBC_QUEUE(trans, txq_id), + txq->dma_addr); + else + iwl_write_direct32(trans, + FH_MEM_CBBC_QUEUE(trans, txq_id), + txq->dma_addr >> 8); + iwl_pcie_txq_unmap(trans, txq_id); + txq->read_ptr = 0; + txq->write_ptr = 0; + } + + /* Tell NIC where to find the "keep warm" buffer */ + iwl_write_direct32(trans, FH_KW_MEM_ADDR_REG, + trans_pcie->kw.dma >> 4); + + /* + * Send 0 as the scd_base_addr since the device may have be reset + * while we were in WoWLAN in which case SCD_SRAM_BASE_ADDR will + * contain garbage. + */ + iwl_pcie_tx_start(trans, 0); +} + +static void iwl_pcie_tx_stop_fh(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ch, ret; + u32 mask = 0; + + spin_lock_bh(&trans_pcie->irq_lock); + + if (!iwl_trans_grab_nic_access(trans)) + goto out; + + /* Stop each Tx DMA channel */ + for (ch = 0; ch < FH_TCSR_CHNL_NUM; ch++) { + iwl_write32(trans, FH_TCSR_CHNL_TX_CONFIG_REG(ch), 0x0); + mask |= FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(ch); + } + + /* Wait for DMA channels to be idle */ + ret = iwl_poll_bit(trans, FH_TSSR_TX_STATUS_REG, mask, mask, 5000); + if (ret < 0) + IWL_ERR(trans, + "Failing on timeout while stopping DMA channel %d [0x%08x]\n", + ch, iwl_read32(trans, FH_TSSR_TX_STATUS_REG)); + + iwl_trans_release_nic_access(trans); + +out: + spin_unlock_bh(&trans_pcie->irq_lock); +} + +/* + * iwl_pcie_tx_stop - Stop all Tx DMA channels + */ +int iwl_pcie_tx_stop(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int txq_id; + + /* Turn off all Tx DMA fifos */ + iwl_scd_deactivate_fifos(trans); + + /* Turn off all Tx DMA channels */ + iwl_pcie_tx_stop_fh(trans); + + /* + * This function can be called before the op_mode disabled the + * queues. This happens when we have an rfkill interrupt. + * Since we stop Tx altogether - mark the queues as stopped. + */ + memset(trans->txqs.queue_stopped, 0, + sizeof(trans->txqs.queue_stopped)); + memset(trans->txqs.queue_used, 0, sizeof(trans->txqs.queue_used)); + + /* This can happen: start_hw, stop_device */ + if (!trans_pcie->txq_memory) + return 0; + + /* Unmap DMA from host system and free skb's */ + for (txq_id = 0; txq_id < trans->trans_cfg->base_params->num_of_queues; + txq_id++) + iwl_pcie_txq_unmap(trans, txq_id); + + return 0; +} + +/* + * iwl_trans_tx_free - Free TXQ Context + * + * Destroy all TX DMA queues and structures + */ +void iwl_pcie_tx_free(struct iwl_trans *trans) +{ + int txq_id; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + memset(trans->txqs.queue_used, 0, sizeof(trans->txqs.queue_used)); + + /* Tx queues */ + if (trans_pcie->txq_memory) { + for (txq_id = 0; + txq_id < trans->trans_cfg->base_params->num_of_queues; + txq_id++) { + iwl_pcie_txq_free(trans, txq_id); + trans->txqs.txq[txq_id] = NULL; + } + } + + kfree(trans_pcie->txq_memory); + trans_pcie->txq_memory = NULL; + + iwl_pcie_free_dma_ptr(trans, &trans_pcie->kw); + + iwl_pcie_free_dma_ptr(trans, &trans->txqs.scd_bc_tbls); +} + +/* + * iwl_pcie_tx_alloc - allocate TX context + * Allocate all Tx DMA structures and initialize them + */ +static int iwl_pcie_tx_alloc(struct iwl_trans *trans) +{ + int ret; + int txq_id, slots_num; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u16 bc_tbls_size = trans->trans_cfg->base_params->num_of_queues; + + if (WARN_ON(trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210)) + return -EINVAL; + + bc_tbls_size *= sizeof(struct iwlagn_scd_bc_tbl); + + /*It is not allowed to alloc twice, so warn when this happens. + * We cannot rely on the previous allocation, so free and fail */ + if (WARN_ON(trans_pcie->txq_memory)) { + ret = -EINVAL; + goto error; + } + + ret = iwl_pcie_alloc_dma_ptr(trans, &trans->txqs.scd_bc_tbls, + bc_tbls_size); + if (ret) { + IWL_ERR(trans, "Scheduler BC Table allocation failed\n"); + goto error; + } + + /* Alloc keep-warm buffer */ + ret = iwl_pcie_alloc_dma_ptr(trans, &trans_pcie->kw, IWL_KW_SIZE); + if (ret) { + IWL_ERR(trans, "Keep Warm allocation failed\n"); + goto error; + } + + trans_pcie->txq_memory = + kcalloc(trans->trans_cfg->base_params->num_of_queues, + sizeof(struct iwl_txq), GFP_KERNEL); + if (!trans_pcie->txq_memory) { + IWL_ERR(trans, "Not enough memory for txq\n"); + ret = -ENOMEM; + goto error; + } + + /* Alloc and init all Tx queues, including the command queue (#4/#9) */ + for (txq_id = 0; txq_id < trans->trans_cfg->base_params->num_of_queues; + txq_id++) { + bool cmd_queue = (txq_id == trans->txqs.cmd.q_id); + + if (cmd_queue) + slots_num = max_t(u32, IWL_CMD_QUEUE_SIZE, + trans->cfg->min_txq_size); + else + slots_num = max_t(u32, IWL_DEFAULT_QUEUE_SIZE, + trans->cfg->min_ba_txq_size); + trans->txqs.txq[txq_id] = &trans_pcie->txq_memory[txq_id]; + ret = iwl_txq_alloc(trans, trans->txqs.txq[txq_id], slots_num, + cmd_queue); + if (ret) { + IWL_ERR(trans, "Tx %d queue alloc failed\n", txq_id); + goto error; + } + trans->txqs.txq[txq_id]->id = txq_id; + } + + return 0; + +error: + iwl_pcie_tx_free(trans); + + return ret; +} + +int iwl_pcie_tx_init(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret; + int txq_id, slots_num; + bool alloc = false; + + if (!trans_pcie->txq_memory) { + ret = iwl_pcie_tx_alloc(trans); + if (ret) + goto error; + alloc = true; + } + + spin_lock_bh(&trans_pcie->irq_lock); + + /* Turn off all Tx DMA fifos */ + iwl_scd_deactivate_fifos(trans); + + /* Tell NIC where to find the "keep warm" buffer */ + iwl_write_direct32(trans, FH_KW_MEM_ADDR_REG, + trans_pcie->kw.dma >> 4); + + spin_unlock_bh(&trans_pcie->irq_lock); + + /* Alloc and init all Tx queues, including the command queue (#4/#9) */ + for (txq_id = 0; txq_id < trans->trans_cfg->base_params->num_of_queues; + txq_id++) { + bool cmd_queue = (txq_id == trans->txqs.cmd.q_id); + + if (cmd_queue) + slots_num = max_t(u32, IWL_CMD_QUEUE_SIZE, + trans->cfg->min_txq_size); + else + slots_num = max_t(u32, IWL_DEFAULT_QUEUE_SIZE, + trans->cfg->min_ba_txq_size); + ret = iwl_txq_init(trans, trans->txqs.txq[txq_id], slots_num, + cmd_queue); + if (ret) { + IWL_ERR(trans, "Tx %d queue init failed\n", txq_id); + goto error; + } + + /* + * Tell nic where to find circular buffer of TFDs for a + * given Tx queue, and enable the DMA channel used for that + * queue. + * Circular buffer (TFD queue in DRAM) physical base address + */ + iwl_write_direct32(trans, FH_MEM_CBBC_QUEUE(trans, txq_id), + trans->txqs.txq[txq_id]->dma_addr >> 8); + } + + iwl_set_bits_prph(trans, SCD_GP_CTRL, SCD_GP_CTRL_AUTO_ACTIVE_MODE); + if (trans->trans_cfg->base_params->num_of_queues > 20) + iwl_set_bits_prph(trans, SCD_GP_CTRL, + SCD_GP_CTRL_ENABLE_31_QUEUES); + + return 0; +error: + /*Upon error, free only if we allocated something */ + if (alloc) + iwl_pcie_tx_free(trans); + return ret; +} + +static int iwl_pcie_set_cmd_in_flight(struct iwl_trans *trans, + const struct iwl_host_cmd *cmd) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + /* Make sure the NIC is still alive in the bus */ + if (test_bit(STATUS_TRANS_DEAD, &trans->status)) + return -ENODEV; + + if (!trans->trans_cfg->base_params->apmg_wake_up_wa) + return 0; + + /* + * wake up the NIC to make sure that the firmware will see the host + * command - we will let the NIC sleep once all the host commands + * returned. This needs to be done only on NICs that have + * apmg_wake_up_wa set (see above.) + */ + if (!_iwl_trans_pcie_grab_nic_access(trans)) + return -EIO; + + /* + * In iwl_trans_grab_nic_access(), we've acquired the reg_lock. + * There, we also returned immediately if cmd_hold_nic_awake is + * already true, so it's OK to unconditionally set it to true. + */ + trans_pcie->cmd_hold_nic_awake = true; + spin_unlock(&trans_pcie->reg_lock); + + return 0; +} + +/* + * iwl_pcie_cmdq_reclaim - Reclaim TX command queue entries already Tx'd + * + * When FW advances 'R' index, all entries between old and new 'R' index + * need to be reclaimed. As result, some free space forms. If there is + * enough free space (> low mark), wake the stack that feeds us. + */ +static void iwl_pcie_cmdq_reclaim(struct iwl_trans *trans, int txq_id, int idx) +{ + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + int nfreed = 0; + u16 r; + + lockdep_assert_held(&txq->lock); + + idx = iwl_txq_get_cmd_index(txq, idx); + r = iwl_txq_get_cmd_index(txq, txq->read_ptr); + + if (idx >= trans->trans_cfg->base_params->max_tfd_queue_size || + (!iwl_txq_used(txq, idx))) { + WARN_ONCE(test_bit(txq_id, trans->txqs.queue_used), + "%s: Read index for DMA queue txq id (%d), index %d is out of range [0-%d] %d %d.\n", + __func__, txq_id, idx, + trans->trans_cfg->base_params->max_tfd_queue_size, + txq->write_ptr, txq->read_ptr); + return; + } + + for (idx = iwl_txq_inc_wrap(trans, idx); r != idx; + r = iwl_txq_inc_wrap(trans, r)) { + txq->read_ptr = iwl_txq_inc_wrap(trans, txq->read_ptr); + + if (nfreed++ > 0) { + IWL_ERR(trans, "HCMD skipped: index (%d) %d %d\n", + idx, txq->write_ptr, r); + iwl_force_nmi(trans); + } + } + + if (txq->read_ptr == txq->write_ptr) + iwl_pcie_clear_cmd_in_flight(trans); + + iwl_txq_progress(txq); +} + +static int iwl_pcie_txq_set_ratid_map(struct iwl_trans *trans, u16 ra_tid, + u16 txq_id) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 tbl_dw_addr; + u32 tbl_dw; + u16 scd_q2ratid; + + scd_q2ratid = ra_tid & SCD_QUEUE_RA_TID_MAP_RATID_MSK; + + tbl_dw_addr = trans_pcie->scd_base_addr + + SCD_TRANS_TBL_OFFSET_QUEUE(txq_id); + + tbl_dw = iwl_trans_read_mem32(trans, tbl_dw_addr); + + if (txq_id & 0x1) + tbl_dw = (scd_q2ratid << 16) | (tbl_dw & 0x0000FFFF); + else + tbl_dw = scd_q2ratid | (tbl_dw & 0xFFFF0000); + + iwl_trans_write_mem32(trans, tbl_dw_addr, tbl_dw); + + return 0; +} + +/* Receiver address (actually, Rx station's index into station table), + * combined with Traffic ID (QOS priority), in format used by Tx Scheduler */ +#define BUILD_RAxTID(sta_id, tid) (((sta_id) << 4) + (tid)) + +bool iwl_trans_pcie_txq_enable(struct iwl_trans *trans, int txq_id, u16 ssn, + const struct iwl_trans_txq_scd_cfg *cfg, + unsigned int wdg_timeout) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + int fifo = -1; + bool scd_bug = false; + + if (test_and_set_bit(txq_id, trans->txqs.queue_used)) + WARN_ONCE(1, "queue %d already used - expect issues", txq_id); + + txq->wd_timeout = msecs_to_jiffies(wdg_timeout); + + if (cfg) { + fifo = cfg->fifo; + + /* Disable the scheduler prior configuring the cmd queue */ + if (txq_id == trans->txqs.cmd.q_id && + trans_pcie->scd_set_active) + iwl_scd_enable_set_active(trans, 0); + + /* Stop this Tx queue before configuring it */ + iwl_scd_txq_set_inactive(trans, txq_id); + + /* Set this queue as a chain-building queue unless it is CMD */ + if (txq_id != trans->txqs.cmd.q_id) + iwl_scd_txq_set_chain(trans, txq_id); + + if (cfg->aggregate) { + u16 ra_tid = BUILD_RAxTID(cfg->sta_id, cfg->tid); + + /* Map receiver-address / traffic-ID to this queue */ + iwl_pcie_txq_set_ratid_map(trans, ra_tid, txq_id); + + /* enable aggregations for the queue */ + iwl_scd_txq_enable_agg(trans, txq_id); + txq->ampdu = true; + } else { + /* + * disable aggregations for the queue, this will also + * make the ra_tid mapping configuration irrelevant + * since it is now a non-AGG queue. + */ + iwl_scd_txq_disable_agg(trans, txq_id); + + ssn = txq->read_ptr; + } + } else { + /* + * If we need to move the SCD write pointer by steps of + * 0x40, 0x80 or 0xc0, it gets stuck. Avoids this and let + * the op_mode know by returning true later. + * Do this only in case cfg is NULL since this trick can + * be done only if we have DQA enabled which is true for mvm + * only. And mvm never sets a cfg pointer. + * This is really ugly, but this is the easiest way out for + * this sad hardware issue. + * This bug has been fixed on devices 9000 and up. + */ + scd_bug = !trans->trans_cfg->mq_rx_supported && + !((ssn - txq->write_ptr) & 0x3f) && + (ssn != txq->write_ptr); + if (scd_bug) + ssn++; + } + + /* Place first TFD at index corresponding to start sequence number. + * Assumes that ssn_idx is valid (!= 0xFFF) */ + txq->read_ptr = (ssn & 0xff); + txq->write_ptr = (ssn & 0xff); + iwl_write_direct32(trans, HBUS_TARG_WRPTR, + (ssn & 0xff) | (txq_id << 8)); + + if (cfg) { + u8 frame_limit = cfg->frame_limit; + + iwl_write_prph(trans, SCD_QUEUE_RDPTR(txq_id), ssn); + + /* Set up Tx window size and frame limit for this queue */ + iwl_trans_write_mem32(trans, trans_pcie->scd_base_addr + + SCD_CONTEXT_QUEUE_OFFSET(txq_id), 0); + iwl_trans_write_mem32(trans, + trans_pcie->scd_base_addr + + SCD_CONTEXT_QUEUE_OFFSET(txq_id) + sizeof(u32), + SCD_QUEUE_CTX_REG2_VAL(WIN_SIZE, frame_limit) | + SCD_QUEUE_CTX_REG2_VAL(FRAME_LIMIT, frame_limit)); + + /* Set up status area in SRAM, map to Tx DMA/FIFO, activate */ + iwl_write_prph(trans, SCD_QUEUE_STATUS_BITS(txq_id), + (1 << SCD_QUEUE_STTS_REG_POS_ACTIVE) | + (cfg->fifo << SCD_QUEUE_STTS_REG_POS_TXF) | + (1 << SCD_QUEUE_STTS_REG_POS_WSL) | + SCD_QUEUE_STTS_REG_MSK); + + /* enable the scheduler for this queue (only) */ + if (txq_id == trans->txqs.cmd.q_id && + trans_pcie->scd_set_active) + iwl_scd_enable_set_active(trans, BIT(txq_id)); + + IWL_DEBUG_TX_QUEUES(trans, + "Activate queue %d on FIFO %d WrPtr: %d\n", + txq_id, fifo, ssn & 0xff); + } else { + IWL_DEBUG_TX_QUEUES(trans, + "Activate queue %d WrPtr: %d\n", + txq_id, ssn & 0xff); + } + + return scd_bug; +} + +void iwl_trans_pcie_txq_set_shared_mode(struct iwl_trans *trans, u32 txq_id, + bool shared_mode) +{ + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + + txq->ampdu = !shared_mode; +} + +void iwl_trans_pcie_txq_disable(struct iwl_trans *trans, int txq_id, + bool configure_scd) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 stts_addr = trans_pcie->scd_base_addr + + SCD_TX_STTS_QUEUE_OFFSET(txq_id); + static const u32 zero_val[4] = {}; + + trans->txqs.txq[txq_id]->frozen_expiry_remainder = 0; + trans->txqs.txq[txq_id]->frozen = false; + + /* + * Upon HW Rfkill - we stop the device, and then stop the queues + * in the op_mode. Just for the sake of the simplicity of the op_mode, + * allow the op_mode to call txq_disable after it already called + * stop_device. + */ + if (!test_and_clear_bit(txq_id, trans->txqs.queue_used)) { + WARN_ONCE(test_bit(STATUS_DEVICE_ENABLED, &trans->status), + "queue %d not used", txq_id); + return; + } + + if (configure_scd) { + iwl_scd_txq_set_inactive(trans, txq_id); + + iwl_trans_write_mem(trans, stts_addr, (const void *)zero_val, + ARRAY_SIZE(zero_val)); + } + + iwl_pcie_txq_unmap(trans, txq_id); + trans->txqs.txq[txq_id]->ampdu = false; + + IWL_DEBUG_TX_QUEUES(trans, "Deactivate queue %d\n", txq_id); +} + +/*************** HOST COMMAND QUEUE FUNCTIONS *****/ + +/* + * iwl_pcie_enqueue_hcmd - enqueue a uCode command + * @priv: device private data point + * @cmd: a pointer to the ucode command structure + * + * The function returns < 0 values to indicate the operation + * failed. On success, it returns the index (>= 0) of command in the + * command queue. + */ +int iwl_pcie_enqueue_hcmd(struct iwl_trans *trans, + struct iwl_host_cmd *cmd) +{ + struct iwl_txq *txq = trans->txqs.txq[trans->txqs.cmd.q_id]; + struct iwl_device_cmd *out_cmd; + struct iwl_cmd_meta *out_meta; + void *dup_buf = NULL; + dma_addr_t phys_addr; + int idx; + u16 copy_size, cmd_size, tb0_size; + bool had_nocopy = false; + u8 group_id = iwl_cmd_groupid(cmd->id); + int i, ret; + u32 cmd_pos; + const u8 *cmddata[IWL_MAX_CMD_TBS_PER_TFD]; + u16 cmdlen[IWL_MAX_CMD_TBS_PER_TFD]; + unsigned long flags; + + if (WARN(!trans->wide_cmd_header && + group_id > IWL_ALWAYS_LONG_GROUP, + "unsupported wide command %#x\n", cmd->id)) + return -EINVAL; + + if (group_id != 0) { + copy_size = sizeof(struct iwl_cmd_header_wide); + cmd_size = sizeof(struct iwl_cmd_header_wide); + } else { + copy_size = sizeof(struct iwl_cmd_header); + cmd_size = sizeof(struct iwl_cmd_header); + } + + /* need one for the header if the first is NOCOPY */ + BUILD_BUG_ON(IWL_MAX_CMD_TBS_PER_TFD > IWL_NUM_OF_TBS - 1); + + for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) { + cmddata[i] = cmd->data[i]; + cmdlen[i] = cmd->len[i]; + + if (!cmd->len[i]) + continue; + + /* need at least IWL_FIRST_TB_SIZE copied */ + if (copy_size < IWL_FIRST_TB_SIZE) { + int copy = IWL_FIRST_TB_SIZE - copy_size; + + if (copy > cmdlen[i]) + copy = cmdlen[i]; + cmdlen[i] -= copy; + cmddata[i] += copy; + copy_size += copy; + } + + if (cmd->dataflags[i] & IWL_HCMD_DFL_NOCOPY) { + had_nocopy = true; + if (WARN_ON(cmd->dataflags[i] & IWL_HCMD_DFL_DUP)) { + idx = -EINVAL; + goto free_dup_buf; + } + } else if (cmd->dataflags[i] & IWL_HCMD_DFL_DUP) { + /* + * This is also a chunk that isn't copied + * to the static buffer so set had_nocopy. + */ + had_nocopy = true; + + /* only allowed once */ + if (WARN_ON(dup_buf)) { + idx = -EINVAL; + goto free_dup_buf; + } + + dup_buf = kmemdup(cmddata[i], cmdlen[i], + GFP_ATOMIC); + if (!dup_buf) + return -ENOMEM; + } else { + /* NOCOPY must not be followed by normal! */ + if (WARN_ON(had_nocopy)) { + idx = -EINVAL; + goto free_dup_buf; + } + copy_size += cmdlen[i]; + } + cmd_size += cmd->len[i]; + } + + /* + * If any of the command structures end up being larger than + * the TFD_MAX_PAYLOAD_SIZE and they aren't dynamically + * allocated into separate TFDs, then we will need to + * increase the size of the buffers. + */ + if (WARN(copy_size > TFD_MAX_PAYLOAD_SIZE, + "Command %s (%#x) is too large (%d bytes)\n", + iwl_get_cmd_string(trans, cmd->id), + cmd->id, copy_size)) { + idx = -EINVAL; + goto free_dup_buf; + } + + spin_lock_irqsave(&txq->lock, flags); + + if (iwl_txq_space(trans, txq) < ((cmd->flags & CMD_ASYNC) ? 2 : 1)) { + spin_unlock_irqrestore(&txq->lock, flags); + + IWL_ERR(trans, "No space in command queue\n"); + iwl_op_mode_cmd_queue_full(trans->op_mode); + idx = -ENOSPC; + goto free_dup_buf; + } + + idx = iwl_txq_get_cmd_index(txq, txq->write_ptr); + out_cmd = txq->entries[idx].cmd; + out_meta = &txq->entries[idx].meta; + + memset(out_meta, 0, sizeof(*out_meta)); /* re-initialize to NULL */ + if (cmd->flags & CMD_WANT_SKB) + out_meta->source = cmd; + + /* set up the header */ + if (group_id != 0) { + out_cmd->hdr_wide.cmd = iwl_cmd_opcode(cmd->id); + out_cmd->hdr_wide.group_id = group_id; + out_cmd->hdr_wide.version = iwl_cmd_version(cmd->id); + out_cmd->hdr_wide.length = + cpu_to_le16(cmd_size - + sizeof(struct iwl_cmd_header_wide)); + out_cmd->hdr_wide.reserved = 0; + out_cmd->hdr_wide.sequence = + cpu_to_le16(QUEUE_TO_SEQ(trans->txqs.cmd.q_id) | + INDEX_TO_SEQ(txq->write_ptr)); + + cmd_pos = sizeof(struct iwl_cmd_header_wide); + copy_size = sizeof(struct iwl_cmd_header_wide); + } else { + out_cmd->hdr.cmd = iwl_cmd_opcode(cmd->id); + out_cmd->hdr.sequence = + cpu_to_le16(QUEUE_TO_SEQ(trans->txqs.cmd.q_id) | + INDEX_TO_SEQ(txq->write_ptr)); + out_cmd->hdr.group_id = 0; + + cmd_pos = sizeof(struct iwl_cmd_header); + copy_size = sizeof(struct iwl_cmd_header); + } + + /* and copy the data that needs to be copied */ + for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) { + int copy; + + if (!cmd->len[i]) + continue; + + /* copy everything if not nocopy/dup */ + if (!(cmd->dataflags[i] & (IWL_HCMD_DFL_NOCOPY | + IWL_HCMD_DFL_DUP))) { + copy = cmd->len[i]; + + memcpy((u8 *)out_cmd + cmd_pos, cmd->data[i], copy); + cmd_pos += copy; + copy_size += copy; + continue; + } + + /* + * Otherwise we need at least IWL_FIRST_TB_SIZE copied + * in total (for bi-directional DMA), but copy up to what + * we can fit into the payload for debug dump purposes. + */ + copy = min_t(int, TFD_MAX_PAYLOAD_SIZE - cmd_pos, cmd->len[i]); + + memcpy((u8 *)out_cmd + cmd_pos, cmd->data[i], copy); + cmd_pos += copy; + + /* However, treat copy_size the proper way, we need it below */ + if (copy_size < IWL_FIRST_TB_SIZE) { + copy = IWL_FIRST_TB_SIZE - copy_size; + + if (copy > cmd->len[i]) + copy = cmd->len[i]; + copy_size += copy; + } + } + + IWL_DEBUG_HC(trans, + "Sending command %s (%.2x.%.2x), seq: 0x%04X, %d bytes at %d[%d]:%d\n", + iwl_get_cmd_string(trans, cmd->id), + group_id, out_cmd->hdr.cmd, + le16_to_cpu(out_cmd->hdr.sequence), + cmd_size, txq->write_ptr, idx, trans->txqs.cmd.q_id); + + /* start the TFD with the minimum copy bytes */ + tb0_size = min_t(int, copy_size, IWL_FIRST_TB_SIZE); + memcpy(&txq->first_tb_bufs[idx], &out_cmd->hdr, tb0_size); + iwl_pcie_txq_build_tfd(trans, txq, + iwl_txq_get_first_tb_dma(txq, idx), + tb0_size, true); + + /* map first command fragment, if any remains */ + if (copy_size > tb0_size) { + phys_addr = dma_map_single(trans->dev, + ((u8 *)&out_cmd->hdr) + tb0_size, + copy_size - tb0_size, + DMA_TO_DEVICE); + if (dma_mapping_error(trans->dev, phys_addr)) { + iwl_txq_gen1_tfd_unmap(trans, out_meta, txq, + txq->write_ptr); + idx = -ENOMEM; + goto out; + } + + iwl_pcie_txq_build_tfd(trans, txq, phys_addr, + copy_size - tb0_size, false); + } + + /* map the remaining (adjusted) nocopy/dup fragments */ + for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) { + void *data = (void *)(uintptr_t)cmddata[i]; + + if (!cmdlen[i]) + continue; + if (!(cmd->dataflags[i] & (IWL_HCMD_DFL_NOCOPY | + IWL_HCMD_DFL_DUP))) + continue; + if (cmd->dataflags[i] & IWL_HCMD_DFL_DUP) + data = dup_buf; + phys_addr = dma_map_single(trans->dev, data, + cmdlen[i], DMA_TO_DEVICE); + if (dma_mapping_error(trans->dev, phys_addr)) { + iwl_txq_gen1_tfd_unmap(trans, out_meta, txq, + txq->write_ptr); + idx = -ENOMEM; + goto out; + } + + iwl_pcie_txq_build_tfd(trans, txq, phys_addr, cmdlen[i], false); + } + + BUILD_BUG_ON(IWL_TFH_NUM_TBS > sizeof(out_meta->tbs) * BITS_PER_BYTE); + out_meta->flags = cmd->flags; + if (WARN_ON_ONCE(txq->entries[idx].free_buf)) + kfree_sensitive(txq->entries[idx].free_buf); + txq->entries[idx].free_buf = dup_buf; + + trace_iwlwifi_dev_hcmd(trans->dev, cmd, cmd_size, &out_cmd->hdr_wide); + + /* start timer if queue currently empty */ + if (txq->read_ptr == txq->write_ptr && txq->wd_timeout) + mod_timer(&txq->stuck_timer, jiffies + txq->wd_timeout); + + ret = iwl_pcie_set_cmd_in_flight(trans, cmd); + if (ret < 0) { + idx = ret; + goto out; + } + + /* Increment and update queue's write index */ + txq->write_ptr = iwl_txq_inc_wrap(trans, txq->write_ptr); + iwl_pcie_txq_inc_wr_ptr(trans, txq); + + out: + spin_unlock_irqrestore(&txq->lock, flags); + free_dup_buf: + if (idx < 0) + kfree(dup_buf); + return idx; +} + +/* + * iwl_pcie_hcmd_complete - Pull unused buffers off the queue and reclaim them + * @rxb: Rx buffer to reclaim + */ +void iwl_pcie_hcmd_complete(struct iwl_trans *trans, + struct iwl_rx_cmd_buffer *rxb) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + u16 sequence = le16_to_cpu(pkt->hdr.sequence); + u8 group_id; + u32 cmd_id; + int txq_id = SEQ_TO_QUEUE(sequence); + int index = SEQ_TO_INDEX(sequence); + int cmd_index; + struct iwl_device_cmd *cmd; + struct iwl_cmd_meta *meta; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_txq *txq = trans->txqs.txq[trans->txqs.cmd.q_id]; + + /* If a Tx command is being handled and it isn't in the actual + * command queue then there a command routing bug has been introduced + * in the queue management code. */ + if (WARN(txq_id != trans->txqs.cmd.q_id, + "wrong command queue %d (should be %d), sequence 0x%X readp=%d writep=%d\n", + txq_id, trans->txqs.cmd.q_id, sequence, txq->read_ptr, + txq->write_ptr)) { + iwl_print_hex_error(trans, pkt, 32); + return; + } + + spin_lock_bh(&txq->lock); + + cmd_index = iwl_txq_get_cmd_index(txq, index); + cmd = txq->entries[cmd_index].cmd; + meta = &txq->entries[cmd_index].meta; + group_id = cmd->hdr.group_id; + cmd_id = WIDE_ID(group_id, cmd->hdr.cmd); + + iwl_txq_gen1_tfd_unmap(trans, meta, txq, index); + + /* Input error checking is done when commands are added to queue. */ + if (meta->flags & CMD_WANT_SKB) { + struct page *p = rxb_steal_page(rxb); + + meta->source->resp_pkt = pkt; + meta->source->_rx_page_addr = (unsigned long)page_address(p); + meta->source->_rx_page_order = trans_pcie->rx_page_order; + } + + if (meta->flags & CMD_WANT_ASYNC_CALLBACK) + iwl_op_mode_async_cb(trans->op_mode, cmd); + + iwl_pcie_cmdq_reclaim(trans, txq_id, index); + + if (!(meta->flags & CMD_ASYNC)) { + if (!test_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status)) { + IWL_WARN(trans, + "HCMD_ACTIVE already clear for command %s\n", + iwl_get_cmd_string(trans, cmd_id)); + } + clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status); + IWL_DEBUG_INFO(trans, "Clearing HCMD_ACTIVE for command %s\n", + iwl_get_cmd_string(trans, cmd_id)); + wake_up(&trans->wait_command_queue); + } + + meta->flags = 0; + + spin_unlock_bh(&txq->lock); +} + +static int iwl_fill_data_tbs(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_txq *txq, u8 hdr_len, + struct iwl_cmd_meta *out_meta) +{ + u16 head_tb_len; + int i; + + /* + * Set up TFD's third entry to point directly to remainder + * of skb's head, if any + */ + head_tb_len = skb_headlen(skb) - hdr_len; + + if (head_tb_len > 0) { + dma_addr_t tb_phys = dma_map_single(trans->dev, + skb->data + hdr_len, + head_tb_len, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, tb_phys))) + return -EINVAL; + trace_iwlwifi_dev_tx_tb(trans->dev, skb, skb->data + hdr_len, + tb_phys, head_tb_len); + iwl_pcie_txq_build_tfd(trans, txq, tb_phys, head_tb_len, false); + } + + /* set up the remaining entries to point to the data */ + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + const skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; + dma_addr_t tb_phys; + int tb_idx; + + if (!skb_frag_size(frag)) + continue; + + tb_phys = skb_frag_dma_map(trans->dev, frag, 0, + skb_frag_size(frag), DMA_TO_DEVICE); + + if (unlikely(dma_mapping_error(trans->dev, tb_phys))) + return -EINVAL; + trace_iwlwifi_dev_tx_tb(trans->dev, skb, skb_frag_address(frag), + tb_phys, skb_frag_size(frag)); + tb_idx = iwl_pcie_txq_build_tfd(trans, txq, tb_phys, + skb_frag_size(frag), false); + if (tb_idx < 0) + return tb_idx; + + out_meta->tbs |= BIT(tb_idx); + } + + return 0; +} + +#ifdef CONFIG_INET +static int iwl_fill_data_tbs_amsdu(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_txq *txq, u8 hdr_len, + struct iwl_cmd_meta *out_meta, + struct iwl_device_tx_cmd *dev_cmd, + u16 tb1_len) +{ + struct iwl_tx_cmd *tx_cmd = (void *)dev_cmd->payload; + struct ieee80211_hdr *hdr = (void *)skb->data; + unsigned int snap_ip_tcp_hdrlen, ip_hdrlen, total_len, hdr_room; + unsigned int mss = skb_shinfo(skb)->gso_size; + u16 length, iv_len, amsdu_pad; + u8 *start_hdr; + struct iwl_tso_hdr_page *hdr_page; + struct tso_t tso; + + /* if the packet is protected, then it must be CCMP or GCMP */ + BUILD_BUG_ON(IEEE80211_CCMP_HDR_LEN != IEEE80211_GCMP_HDR_LEN); + iv_len = ieee80211_has_protected(hdr->frame_control) ? + IEEE80211_CCMP_HDR_LEN : 0; + + trace_iwlwifi_dev_tx(trans->dev, skb, + iwl_txq_get_tfd(trans, txq, txq->write_ptr), + trans->txqs.tfd.size, + &dev_cmd->hdr, IWL_FIRST_TB_SIZE + tb1_len, 0); + + ip_hdrlen = skb_transport_header(skb) - skb_network_header(skb); + snap_ip_tcp_hdrlen = 8 + ip_hdrlen + tcp_hdrlen(skb); + total_len = skb->len - snap_ip_tcp_hdrlen - hdr_len - iv_len; + amsdu_pad = 0; + + /* total amount of header we may need for this A-MSDU */ + hdr_room = DIV_ROUND_UP(total_len, mss) * + (3 + snap_ip_tcp_hdrlen + sizeof(struct ethhdr)) + iv_len; + + /* Our device supports 9 segments at most, it will fit in 1 page */ + hdr_page = get_page_hdr(trans, hdr_room, skb); + if (!hdr_page) + return -ENOMEM; + + start_hdr = hdr_page->pos; + memcpy(hdr_page->pos, skb->data + hdr_len, iv_len); + hdr_page->pos += iv_len; + + /* + * Pull the ieee80211 header + IV to be able to use TSO core, + * we will restore it for the tx_status flow. + */ + skb_pull(skb, hdr_len + iv_len); + + /* + * Remove the length of all the headers that we don't actually + * have in the MPDU by themselves, but that we duplicate into + * all the different MSDUs inside the A-MSDU. + */ + le16_add_cpu(&tx_cmd->len, -snap_ip_tcp_hdrlen); + + tso_start(skb, &tso); + + while (total_len) { + /* this is the data left for this subframe */ + unsigned int data_left = + min_t(unsigned int, mss, total_len); + unsigned int hdr_tb_len; + dma_addr_t hdr_tb_phys; + u8 *subf_hdrs_start = hdr_page->pos; + + total_len -= data_left; + + memset(hdr_page->pos, 0, amsdu_pad); + hdr_page->pos += amsdu_pad; + amsdu_pad = (4 - (sizeof(struct ethhdr) + snap_ip_tcp_hdrlen + + data_left)) & 0x3; + ether_addr_copy(hdr_page->pos, ieee80211_get_DA(hdr)); + hdr_page->pos += ETH_ALEN; + ether_addr_copy(hdr_page->pos, ieee80211_get_SA(hdr)); + hdr_page->pos += ETH_ALEN; + + length = snap_ip_tcp_hdrlen + data_left; + *((__be16 *)hdr_page->pos) = cpu_to_be16(length); + hdr_page->pos += sizeof(length); + + /* + * This will copy the SNAP as well which will be considered + * as MAC header. + */ + tso_build_hdr(skb, hdr_page->pos, &tso, data_left, !total_len); + + hdr_page->pos += snap_ip_tcp_hdrlen; + + hdr_tb_len = hdr_page->pos - start_hdr; + hdr_tb_phys = dma_map_single(trans->dev, start_hdr, + hdr_tb_len, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, hdr_tb_phys))) + return -EINVAL; + iwl_pcie_txq_build_tfd(trans, txq, hdr_tb_phys, + hdr_tb_len, false); + trace_iwlwifi_dev_tx_tb(trans->dev, skb, start_hdr, + hdr_tb_phys, hdr_tb_len); + /* add this subframe's headers' length to the tx_cmd */ + le16_add_cpu(&tx_cmd->len, hdr_page->pos - subf_hdrs_start); + + /* prepare the start_hdr for the next subframe */ + start_hdr = hdr_page->pos; + + /* put the payload */ + while (data_left) { + unsigned int size = min_t(unsigned int, tso.size, + data_left); + dma_addr_t tb_phys; + + tb_phys = dma_map_single(trans->dev, tso.data, + size, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, tb_phys))) + return -EINVAL; + + iwl_pcie_txq_build_tfd(trans, txq, tb_phys, + size, false); + trace_iwlwifi_dev_tx_tb(trans->dev, skb, tso.data, + tb_phys, size); + + data_left -= size; + tso_build_data(skb, &tso, size); + } + } + + /* re -add the WiFi header and IV */ + skb_push(skb, hdr_len + iv_len); + + return 0; +} +#else /* CONFIG_INET */ +static int iwl_fill_data_tbs_amsdu(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_txq *txq, u8 hdr_len, + struct iwl_cmd_meta *out_meta, + struct iwl_device_tx_cmd *dev_cmd, + u16 tb1_len) +{ + /* No A-MSDU without CONFIG_INET */ + WARN_ON(1); + + return -1; +} +#endif /* CONFIG_INET */ + +int iwl_trans_pcie_tx(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_device_tx_cmd *dev_cmd, int txq_id) +{ + struct ieee80211_hdr *hdr; + struct iwl_tx_cmd *tx_cmd = (struct iwl_tx_cmd *)dev_cmd->payload; + struct iwl_cmd_meta *out_meta; + struct iwl_txq *txq; + dma_addr_t tb0_phys, tb1_phys, scratch_phys; + void *tb1_addr; + void *tfd; + u16 len, tb1_len; + bool wait_write_ptr; + __le16 fc; + u8 hdr_len; + u16 wifi_seq; + bool amsdu; + + txq = trans->txqs.txq[txq_id]; + + if (WARN_ONCE(!test_bit(txq_id, trans->txqs.queue_used), + "TX on unused queue %d\n", txq_id)) + return -EINVAL; + + if (skb_is_nonlinear(skb) && + skb_shinfo(skb)->nr_frags > IWL_TRANS_MAX_FRAGS(trans) && + __skb_linearize(skb)) + return -ENOMEM; + + /* mac80211 always puts the full header into the SKB's head, + * so there's no need to check if it's readable there + */ + hdr = (struct ieee80211_hdr *)skb->data; + fc = hdr->frame_control; + hdr_len = ieee80211_hdrlen(fc); + + spin_lock(&txq->lock); + + if (iwl_txq_space(trans, txq) < txq->high_mark) { + iwl_txq_stop(trans, txq); + + /* don't put the packet on the ring, if there is no room */ + if (unlikely(iwl_txq_space(trans, txq) < 3)) { + struct iwl_device_tx_cmd **dev_cmd_ptr; + + dev_cmd_ptr = (void *)((u8 *)skb->cb + + trans->txqs.dev_cmd_offs); + + *dev_cmd_ptr = dev_cmd; + __skb_queue_tail(&txq->overflow_q, skb); + + spin_unlock(&txq->lock); + return 0; + } + } + + /* In AGG mode, the index in the ring must correspond to the WiFi + * sequence number. This is a HW requirements to help the SCD to parse + * the BA. + * Check here that the packets are in the right place on the ring. + */ + wifi_seq = IEEE80211_SEQ_TO_SN(le16_to_cpu(hdr->seq_ctrl)); + WARN_ONCE(txq->ampdu && + (wifi_seq & 0xff) != txq->write_ptr, + "Q: %d WiFi Seq %d tfdNum %d", + txq_id, wifi_seq, txq->write_ptr); + + /* Set up driver data for this TFD */ + txq->entries[txq->write_ptr].skb = skb; + txq->entries[txq->write_ptr].cmd = dev_cmd; + + dev_cmd->hdr.sequence = + cpu_to_le16((u16)(QUEUE_TO_SEQ(txq_id) | + INDEX_TO_SEQ(txq->write_ptr))); + + tb0_phys = iwl_txq_get_first_tb_dma(txq, txq->write_ptr); + scratch_phys = tb0_phys + sizeof(struct iwl_cmd_header) + + offsetof(struct iwl_tx_cmd, scratch); + + tx_cmd->dram_lsb_ptr = cpu_to_le32(scratch_phys); + tx_cmd->dram_msb_ptr = iwl_get_dma_hi_addr(scratch_phys); + + /* Set up first empty entry in queue's array of Tx/cmd buffers */ + out_meta = &txq->entries[txq->write_ptr].meta; + out_meta->flags = 0; + + /* + * The second TB (tb1) points to the remainder of the TX command + * and the 802.11 header - dword aligned size + * (This calculation modifies the TX command, so do it before the + * setup of the first TB) + */ + len = sizeof(struct iwl_tx_cmd) + sizeof(struct iwl_cmd_header) + + hdr_len - IWL_FIRST_TB_SIZE; + /* do not align A-MSDU to dword as the subframe header aligns it */ + amsdu = ieee80211_is_data_qos(fc) && + (*ieee80211_get_qos_ctl(hdr) & + IEEE80211_QOS_CTL_A_MSDU_PRESENT); + if (!amsdu) { + tb1_len = ALIGN(len, 4); + /* Tell NIC about any 2-byte padding after MAC header */ + if (tb1_len != len) + tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_MH_PAD); + } else { + tb1_len = len; + } + + /* + * The first TB points to bi-directional DMA data, we'll + * memcpy the data into it later. + */ + iwl_pcie_txq_build_tfd(trans, txq, tb0_phys, + IWL_FIRST_TB_SIZE, true); + + /* there must be data left over for TB1 or this code must be changed */ + BUILD_BUG_ON(sizeof(struct iwl_tx_cmd) < IWL_FIRST_TB_SIZE); + + /* map the data for TB1 */ + tb1_addr = ((u8 *)&dev_cmd->hdr) + IWL_FIRST_TB_SIZE; + tb1_phys = dma_map_single(trans->dev, tb1_addr, tb1_len, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, tb1_phys))) + goto out_err; + iwl_pcie_txq_build_tfd(trans, txq, tb1_phys, tb1_len, false); + + trace_iwlwifi_dev_tx(trans->dev, skb, + iwl_txq_get_tfd(trans, txq, txq->write_ptr), + trans->txqs.tfd.size, + &dev_cmd->hdr, IWL_FIRST_TB_SIZE + tb1_len, + hdr_len); + + /* + * If gso_size wasn't set, don't give the frame "amsdu treatment" + * (adding subframes, etc.). + * This can happen in some testing flows when the amsdu was already + * pre-built, and we just need to send the resulting skb. + */ + if (amsdu && skb_shinfo(skb)->gso_size) { + if (unlikely(iwl_fill_data_tbs_amsdu(trans, skb, txq, hdr_len, + out_meta, dev_cmd, + tb1_len))) + goto out_err; + } else { + struct sk_buff *frag; + + if (unlikely(iwl_fill_data_tbs(trans, skb, txq, hdr_len, + out_meta))) + goto out_err; + + skb_walk_frags(skb, frag) { + if (unlikely(iwl_fill_data_tbs(trans, frag, txq, 0, + out_meta))) + goto out_err; + } + } + + /* building the A-MSDU might have changed this data, so memcpy it now */ + memcpy(&txq->first_tb_bufs[txq->write_ptr], dev_cmd, IWL_FIRST_TB_SIZE); + + tfd = iwl_txq_get_tfd(trans, txq, txq->write_ptr); + /* Set up entry for this TFD in Tx byte-count array */ + iwl_txq_gen1_update_byte_cnt_tbl(trans, txq, le16_to_cpu(tx_cmd->len), + iwl_txq_gen1_tfd_get_num_tbs(trans, + tfd)); + + wait_write_ptr = ieee80211_has_morefrags(fc); + + /* start timer if queue currently empty */ + if (txq->read_ptr == txq->write_ptr && txq->wd_timeout) { + /* + * If the TXQ is active, then set the timer, if not, + * set the timer in remainder so that the timer will + * be armed with the right value when the station will + * wake up. + */ + if (!txq->frozen) + mod_timer(&txq->stuck_timer, + jiffies + txq->wd_timeout); + else + txq->frozen_expiry_remainder = txq->wd_timeout; + } + + /* Tell device the write index *just past* this latest filled TFD */ + txq->write_ptr = iwl_txq_inc_wrap(trans, txq->write_ptr); + if (!wait_write_ptr) + iwl_pcie_txq_inc_wr_ptr(trans, txq); + + /* + * At this point the frame is "transmitted" successfully + * and we will get a TX status notification eventually. + */ + spin_unlock(&txq->lock); + return 0; +out_err: + iwl_txq_gen1_tfd_unmap(trans, out_meta, txq, txq->write_ptr); + spin_unlock(&txq->lock); + return -1; +} diff --git a/drivers/net/wireless/intel/iwlwifi/queue/tx.c b/drivers/net/wireless/intel/iwlwifi/queue/tx.c new file mode 100644 index 000000000..8cf206837 --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/queue/tx.c @@ -0,0 +1,1870 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2020-2022 Intel Corporation + */ +#include <net/tso.h> +#include <linux/tcp.h> + +#include "iwl-debug.h" +#include "iwl-io.h" +#include "fw/api/commands.h" +#include "fw/api/tx.h" +#include "fw/api/datapath.h" +#include "queue/tx.h" +#include "iwl-fh.h" +#include "iwl-scd.h" +#include <linux/dmapool.h> + +/* + * iwl_txq_update_byte_tbl - Set up entry in Tx byte-count array + */ +static void iwl_pcie_gen2_update_byte_tbl(struct iwl_trans *trans, + struct iwl_txq *txq, u16 byte_cnt, + int num_tbs) +{ + int idx = iwl_txq_get_cmd_index(txq, txq->write_ptr); + u8 filled_tfd_size, num_fetch_chunks; + u16 len = byte_cnt; + __le16 bc_ent; + + if (WARN(idx >= txq->n_window, "%d >= %d\n", idx, txq->n_window)) + return; + + filled_tfd_size = offsetof(struct iwl_tfh_tfd, tbs) + + num_tbs * sizeof(struct iwl_tfh_tb); + /* + * filled_tfd_size contains the number of filled bytes in the TFD. + * Dividing it by 64 will give the number of chunks to fetch + * to SRAM- 0 for one chunk, 1 for 2 and so on. + * If, for example, TFD contains only 3 TBs then 32 bytes + * of the TFD are used, and only one chunk of 64 bytes should + * be fetched + */ + num_fetch_chunks = DIV_ROUND_UP(filled_tfd_size, 64) - 1; + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + struct iwl_gen3_bc_tbl_entry *scd_bc_tbl_gen3 = txq->bc_tbl.addr; + + /* Starting from AX210, the HW expects bytes */ + WARN_ON(trans->txqs.bc_table_dword); + WARN_ON(len > 0x3FFF); + bc_ent = cpu_to_le16(len | (num_fetch_chunks << 14)); + scd_bc_tbl_gen3[idx].tfd_offset = bc_ent; + } else { + struct iwlagn_scd_bc_tbl *scd_bc_tbl = txq->bc_tbl.addr; + + /* Before AX210, the HW expects DW */ + WARN_ON(!trans->txqs.bc_table_dword); + len = DIV_ROUND_UP(len, 4); + WARN_ON(len > 0xFFF); + bc_ent = cpu_to_le16(len | (num_fetch_chunks << 12)); + scd_bc_tbl->tfd_offset[idx] = bc_ent; + } +} + +/* + * iwl_txq_inc_wr_ptr - Send new write index to hardware + */ +void iwl_txq_inc_wr_ptr(struct iwl_trans *trans, struct iwl_txq *txq) +{ + lockdep_assert_held(&txq->lock); + + IWL_DEBUG_TX(trans, "Q:%d WR: 0x%x\n", txq->id, txq->write_ptr); + + /* + * if not in power-save mode, uCode will never sleep when we're + * trying to tx (during RFKILL, we're not trying to tx). + */ + iwl_write32(trans, HBUS_TARG_WRPTR, txq->write_ptr | (txq->id << 16)); +} + +static u8 iwl_txq_gen2_get_num_tbs(struct iwl_trans *trans, + struct iwl_tfh_tfd *tfd) +{ + return le16_to_cpu(tfd->num_tbs) & 0x1f; +} + +void iwl_txq_gen2_tfd_unmap(struct iwl_trans *trans, struct iwl_cmd_meta *meta, + struct iwl_tfh_tfd *tfd) +{ + int i, num_tbs; + + /* Sanity check on number of chunks */ + num_tbs = iwl_txq_gen2_get_num_tbs(trans, tfd); + + if (num_tbs > trans->txqs.tfd.max_tbs) { + IWL_ERR(trans, "Too many chunks: %i\n", num_tbs); + return; + } + + /* first TB is never freed - it's the bidirectional DMA data */ + for (i = 1; i < num_tbs; i++) { + if (meta->tbs & BIT(i)) + dma_unmap_page(trans->dev, + le64_to_cpu(tfd->tbs[i].addr), + le16_to_cpu(tfd->tbs[i].tb_len), + DMA_TO_DEVICE); + else + dma_unmap_single(trans->dev, + le64_to_cpu(tfd->tbs[i].addr), + le16_to_cpu(tfd->tbs[i].tb_len), + DMA_TO_DEVICE); + } + + tfd->num_tbs = 0; +} + +void iwl_txq_gen2_free_tfd(struct iwl_trans *trans, struct iwl_txq *txq) +{ + /* rd_ptr is bounded by TFD_QUEUE_SIZE_MAX and + * idx is bounded by n_window + */ + int idx = iwl_txq_get_cmd_index(txq, txq->read_ptr); + struct sk_buff *skb; + + lockdep_assert_held(&txq->lock); + + if (!txq->entries) + return; + + iwl_txq_gen2_tfd_unmap(trans, &txq->entries[idx].meta, + iwl_txq_get_tfd(trans, txq, idx)); + + skb = txq->entries[idx].skb; + + /* Can be called from irqs-disabled context + * If skb is not NULL, it means that the whole queue is being + * freed and that the queue is not empty - free the skb + */ + if (skb) { + iwl_op_mode_free_skb(trans->op_mode, skb); + txq->entries[idx].skb = NULL; + } +} + +int iwl_txq_gen2_set_tb(struct iwl_trans *trans, struct iwl_tfh_tfd *tfd, + dma_addr_t addr, u16 len) +{ + int idx = iwl_txq_gen2_get_num_tbs(trans, tfd); + struct iwl_tfh_tb *tb; + + /* + * Only WARN here so we know about the issue, but we mess up our + * unmap path because not every place currently checks for errors + * returned from this function - it can only return an error if + * there's no more space, and so when we know there is enough we + * don't always check ... + */ + WARN(iwl_txq_crosses_4g_boundary(addr, len), + "possible DMA problem with iova:0x%llx, len:%d\n", + (unsigned long long)addr, len); + + if (WARN_ON(idx >= IWL_TFH_NUM_TBS)) + return -EINVAL; + tb = &tfd->tbs[idx]; + + /* Each TFD can point to a maximum max_tbs Tx buffers */ + if (le16_to_cpu(tfd->num_tbs) >= trans->txqs.tfd.max_tbs) { + IWL_ERR(trans, "Error can not send more than %d chunks\n", + trans->txqs.tfd.max_tbs); + return -EINVAL; + } + + put_unaligned_le64(addr, &tb->addr); + tb->tb_len = cpu_to_le16(len); + + tfd->num_tbs = cpu_to_le16(idx + 1); + + return idx; +} + +static struct page *get_workaround_page(struct iwl_trans *trans, + struct sk_buff *skb) +{ + struct page **page_ptr; + struct page *ret; + + page_ptr = (void *)((u8 *)skb->cb + trans->txqs.page_offs); + + ret = alloc_page(GFP_ATOMIC); + if (!ret) + return NULL; + + /* set the chaining pointer to the previous page if there */ + *(void **)((u8 *)page_address(ret) + PAGE_SIZE - sizeof(void *)) = *page_ptr; + *page_ptr = ret; + + return ret; +} + +/* + * Add a TB and if needed apply the FH HW bug workaround; + * meta != NULL indicates that it's a page mapping and we + * need to dma_unmap_page() and set the meta->tbs bit in + * this case. + */ +static int iwl_txq_gen2_set_tb_with_wa(struct iwl_trans *trans, + struct sk_buff *skb, + struct iwl_tfh_tfd *tfd, + dma_addr_t phys, void *virt, + u16 len, struct iwl_cmd_meta *meta) +{ + dma_addr_t oldphys = phys; + struct page *page; + int ret; + + if (unlikely(dma_mapping_error(trans->dev, phys))) + return -ENOMEM; + + if (likely(!iwl_txq_crosses_4g_boundary(phys, len))) { + ret = iwl_txq_gen2_set_tb(trans, tfd, phys, len); + + if (ret < 0) + goto unmap; + + if (meta) + meta->tbs |= BIT(ret); + + ret = 0; + goto trace; + } + + /* + * Work around a hardware bug. If (as expressed in the + * condition above) the TB ends on a 32-bit boundary, + * then the next TB may be accessed with the wrong + * address. + * To work around it, copy the data elsewhere and make + * a new mapping for it so the device will not fail. + */ + + if (WARN_ON(len > PAGE_SIZE - sizeof(void *))) { + ret = -ENOBUFS; + goto unmap; + } + + page = get_workaround_page(trans, skb); + if (!page) { + ret = -ENOMEM; + goto unmap; + } + + memcpy(page_address(page), virt, len); + + phys = dma_map_single(trans->dev, page_address(page), len, + DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, phys))) + return -ENOMEM; + ret = iwl_txq_gen2_set_tb(trans, tfd, phys, len); + if (ret < 0) { + /* unmap the new allocation as single */ + oldphys = phys; + meta = NULL; + goto unmap; + } + IWL_WARN(trans, + "TB bug workaround: copied %d bytes from 0x%llx to 0x%llx\n", + len, (unsigned long long)oldphys, (unsigned long long)phys); + + ret = 0; +unmap: + if (meta) + dma_unmap_page(trans->dev, oldphys, len, DMA_TO_DEVICE); + else + dma_unmap_single(trans->dev, oldphys, len, DMA_TO_DEVICE); +trace: + trace_iwlwifi_dev_tx_tb(trans->dev, skb, virt, phys, len); + + return ret; +} + +#ifdef CONFIG_INET +struct iwl_tso_hdr_page *get_page_hdr(struct iwl_trans *trans, size_t len, + struct sk_buff *skb) +{ + struct iwl_tso_hdr_page *p = this_cpu_ptr(trans->txqs.tso_hdr_page); + struct page **page_ptr; + + page_ptr = (void *)((u8 *)skb->cb + trans->txqs.page_offs); + + if (WARN_ON(*page_ptr)) + return NULL; + + if (!p->page) + goto alloc; + + /* + * Check if there's enough room on this page + * + * Note that we put a page chaining pointer *last* in the + * page - we need it somewhere, and if it's there then we + * avoid DMA mapping the last bits of the page which may + * trigger the 32-bit boundary hardware bug. + * + * (see also get_workaround_page() in tx-gen2.c) + */ + if (p->pos + len < (u8 *)page_address(p->page) + PAGE_SIZE - + sizeof(void *)) + goto out; + + /* We don't have enough room on this page, get a new one. */ + __free_page(p->page); + +alloc: + p->page = alloc_page(GFP_ATOMIC); + if (!p->page) + return NULL; + p->pos = page_address(p->page); + /* set the chaining pointer to NULL */ + *(void **)((u8 *)page_address(p->page) + PAGE_SIZE - sizeof(void *)) = NULL; +out: + *page_ptr = p->page; + get_page(p->page); + return p; +} +#endif + +static int iwl_txq_gen2_build_amsdu(struct iwl_trans *trans, + struct sk_buff *skb, + struct iwl_tfh_tfd *tfd, int start_len, + u8 hdr_len, + struct iwl_device_tx_cmd *dev_cmd) +{ +#ifdef CONFIG_INET + struct iwl_tx_cmd_gen2 *tx_cmd = (void *)dev_cmd->payload; + struct ieee80211_hdr *hdr = (void *)skb->data; + unsigned int snap_ip_tcp_hdrlen, ip_hdrlen, total_len, hdr_room; + unsigned int mss = skb_shinfo(skb)->gso_size; + u16 length, amsdu_pad; + u8 *start_hdr; + struct iwl_tso_hdr_page *hdr_page; + struct tso_t tso; + + trace_iwlwifi_dev_tx(trans->dev, skb, tfd, sizeof(*tfd), + &dev_cmd->hdr, start_len, 0); + + ip_hdrlen = skb_transport_header(skb) - skb_network_header(skb); + snap_ip_tcp_hdrlen = 8 + ip_hdrlen + tcp_hdrlen(skb); + total_len = skb->len - snap_ip_tcp_hdrlen - hdr_len; + amsdu_pad = 0; + + /* total amount of header we may need for this A-MSDU */ + hdr_room = DIV_ROUND_UP(total_len, mss) * + (3 + snap_ip_tcp_hdrlen + sizeof(struct ethhdr)); + + /* Our device supports 9 segments at most, it will fit in 1 page */ + hdr_page = get_page_hdr(trans, hdr_room, skb); + if (!hdr_page) + return -ENOMEM; + + start_hdr = hdr_page->pos; + + /* + * Pull the ieee80211 header to be able to use TSO core, + * we will restore it for the tx_status flow. + */ + skb_pull(skb, hdr_len); + + /* + * Remove the length of all the headers that we don't actually + * have in the MPDU by themselves, but that we duplicate into + * all the different MSDUs inside the A-MSDU. + */ + le16_add_cpu(&tx_cmd->len, -snap_ip_tcp_hdrlen); + + tso_start(skb, &tso); + + while (total_len) { + /* this is the data left for this subframe */ + unsigned int data_left = min_t(unsigned int, mss, total_len); + unsigned int tb_len; + dma_addr_t tb_phys; + u8 *subf_hdrs_start = hdr_page->pos; + + total_len -= data_left; + + memset(hdr_page->pos, 0, amsdu_pad); + hdr_page->pos += amsdu_pad; + amsdu_pad = (4 - (sizeof(struct ethhdr) + snap_ip_tcp_hdrlen + + data_left)) & 0x3; + ether_addr_copy(hdr_page->pos, ieee80211_get_DA(hdr)); + hdr_page->pos += ETH_ALEN; + ether_addr_copy(hdr_page->pos, ieee80211_get_SA(hdr)); + hdr_page->pos += ETH_ALEN; + + length = snap_ip_tcp_hdrlen + data_left; + *((__be16 *)hdr_page->pos) = cpu_to_be16(length); + hdr_page->pos += sizeof(length); + + /* + * This will copy the SNAP as well which will be considered + * as MAC header. + */ + tso_build_hdr(skb, hdr_page->pos, &tso, data_left, !total_len); + + hdr_page->pos += snap_ip_tcp_hdrlen; + + tb_len = hdr_page->pos - start_hdr; + tb_phys = dma_map_single(trans->dev, start_hdr, + tb_len, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, tb_phys))) + goto out_err; + /* + * No need for _with_wa, this is from the TSO page and + * we leave some space at the end of it so can't hit + * the buggy scenario. + */ + iwl_txq_gen2_set_tb(trans, tfd, tb_phys, tb_len); + trace_iwlwifi_dev_tx_tb(trans->dev, skb, start_hdr, + tb_phys, tb_len); + /* add this subframe's headers' length to the tx_cmd */ + le16_add_cpu(&tx_cmd->len, hdr_page->pos - subf_hdrs_start); + + /* prepare the start_hdr for the next subframe */ + start_hdr = hdr_page->pos; + + /* put the payload */ + while (data_left) { + int ret; + + tb_len = min_t(unsigned int, tso.size, data_left); + tb_phys = dma_map_single(trans->dev, tso.data, + tb_len, DMA_TO_DEVICE); + ret = iwl_txq_gen2_set_tb_with_wa(trans, skb, tfd, + tb_phys, tso.data, + tb_len, NULL); + if (ret) + goto out_err; + + data_left -= tb_len; + tso_build_data(skb, &tso, tb_len); + } + } + + /* re -add the WiFi header */ + skb_push(skb, hdr_len); + + return 0; + +out_err: +#endif + return -EINVAL; +} + +static struct +iwl_tfh_tfd *iwl_txq_gen2_build_tx_amsdu(struct iwl_trans *trans, + struct iwl_txq *txq, + struct iwl_device_tx_cmd *dev_cmd, + struct sk_buff *skb, + struct iwl_cmd_meta *out_meta, + int hdr_len, + int tx_cmd_len) +{ + int idx = iwl_txq_get_cmd_index(txq, txq->write_ptr); + struct iwl_tfh_tfd *tfd = iwl_txq_get_tfd(trans, txq, idx); + dma_addr_t tb_phys; + int len; + void *tb1_addr; + + tb_phys = iwl_txq_get_first_tb_dma(txq, idx); + + /* + * No need for _with_wa, the first TB allocation is aligned up + * to a 64-byte boundary and thus can't be at the end or cross + * a page boundary (much less a 2^32 boundary). + */ + iwl_txq_gen2_set_tb(trans, tfd, tb_phys, IWL_FIRST_TB_SIZE); + + /* + * The second TB (tb1) points to the remainder of the TX command + * and the 802.11 header - dword aligned size + * (This calculation modifies the TX command, so do it before the + * setup of the first TB) + */ + len = tx_cmd_len + sizeof(struct iwl_cmd_header) + hdr_len - + IWL_FIRST_TB_SIZE; + + /* do not align A-MSDU to dword as the subframe header aligns it */ + + /* map the data for TB1 */ + tb1_addr = ((u8 *)&dev_cmd->hdr) + IWL_FIRST_TB_SIZE; + tb_phys = dma_map_single(trans->dev, tb1_addr, len, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, tb_phys))) + goto out_err; + /* + * No need for _with_wa(), we ensure (via alignment) that the data + * here can never cross or end at a page boundary. + */ + iwl_txq_gen2_set_tb(trans, tfd, tb_phys, len); + + if (iwl_txq_gen2_build_amsdu(trans, skb, tfd, len + IWL_FIRST_TB_SIZE, + hdr_len, dev_cmd)) + goto out_err; + + /* building the A-MSDU might have changed this data, memcpy it now */ + memcpy(&txq->first_tb_bufs[idx], dev_cmd, IWL_FIRST_TB_SIZE); + return tfd; + +out_err: + iwl_txq_gen2_tfd_unmap(trans, out_meta, tfd); + return NULL; +} + +static int iwl_txq_gen2_tx_add_frags(struct iwl_trans *trans, + struct sk_buff *skb, + struct iwl_tfh_tfd *tfd, + struct iwl_cmd_meta *out_meta) +{ + int i; + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + const skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; + dma_addr_t tb_phys; + unsigned int fragsz = skb_frag_size(frag); + int ret; + + if (!fragsz) + continue; + + tb_phys = skb_frag_dma_map(trans->dev, frag, 0, + fragsz, DMA_TO_DEVICE); + ret = iwl_txq_gen2_set_tb_with_wa(trans, skb, tfd, tb_phys, + skb_frag_address(frag), + fragsz, out_meta); + if (ret) + return ret; + } + + return 0; +} + +static struct +iwl_tfh_tfd *iwl_txq_gen2_build_tx(struct iwl_trans *trans, + struct iwl_txq *txq, + struct iwl_device_tx_cmd *dev_cmd, + struct sk_buff *skb, + struct iwl_cmd_meta *out_meta, + int hdr_len, + int tx_cmd_len, + bool pad) +{ + int idx = iwl_txq_get_cmd_index(txq, txq->write_ptr); + struct iwl_tfh_tfd *tfd = iwl_txq_get_tfd(trans, txq, idx); + dma_addr_t tb_phys; + int len, tb1_len, tb2_len; + void *tb1_addr; + struct sk_buff *frag; + + tb_phys = iwl_txq_get_first_tb_dma(txq, idx); + + /* The first TB points to bi-directional DMA data */ + memcpy(&txq->first_tb_bufs[idx], dev_cmd, IWL_FIRST_TB_SIZE); + + /* + * No need for _with_wa, the first TB allocation is aligned up + * to a 64-byte boundary and thus can't be at the end or cross + * a page boundary (much less a 2^32 boundary). + */ + iwl_txq_gen2_set_tb(trans, tfd, tb_phys, IWL_FIRST_TB_SIZE); + + /* + * The second TB (tb1) points to the remainder of the TX command + * and the 802.11 header - dword aligned size + * (This calculation modifies the TX command, so do it before the + * setup of the first TB) + */ + len = tx_cmd_len + sizeof(struct iwl_cmd_header) + hdr_len - + IWL_FIRST_TB_SIZE; + + if (pad) + tb1_len = ALIGN(len, 4); + else + tb1_len = len; + + /* map the data for TB1 */ + tb1_addr = ((u8 *)&dev_cmd->hdr) + IWL_FIRST_TB_SIZE; + tb_phys = dma_map_single(trans->dev, tb1_addr, tb1_len, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, tb_phys))) + goto out_err; + /* + * No need for _with_wa(), we ensure (via alignment) that the data + * here can never cross or end at a page boundary. + */ + iwl_txq_gen2_set_tb(trans, tfd, tb_phys, tb1_len); + trace_iwlwifi_dev_tx(trans->dev, skb, tfd, sizeof(*tfd), &dev_cmd->hdr, + IWL_FIRST_TB_SIZE + tb1_len, hdr_len); + + /* set up TFD's third entry to point to remainder of skb's head */ + tb2_len = skb_headlen(skb) - hdr_len; + + if (tb2_len > 0) { + int ret; + + tb_phys = dma_map_single(trans->dev, skb->data + hdr_len, + tb2_len, DMA_TO_DEVICE); + ret = iwl_txq_gen2_set_tb_with_wa(trans, skb, tfd, tb_phys, + skb->data + hdr_len, tb2_len, + NULL); + if (ret) + goto out_err; + } + + if (iwl_txq_gen2_tx_add_frags(trans, skb, tfd, out_meta)) + goto out_err; + + skb_walk_frags(skb, frag) { + int ret; + + tb_phys = dma_map_single(trans->dev, frag->data, + skb_headlen(frag), DMA_TO_DEVICE); + ret = iwl_txq_gen2_set_tb_with_wa(trans, skb, tfd, tb_phys, + frag->data, + skb_headlen(frag), NULL); + if (ret) + goto out_err; + if (iwl_txq_gen2_tx_add_frags(trans, frag, tfd, out_meta)) + goto out_err; + } + + return tfd; + +out_err: + iwl_txq_gen2_tfd_unmap(trans, out_meta, tfd); + return NULL; +} + +static +struct iwl_tfh_tfd *iwl_txq_gen2_build_tfd(struct iwl_trans *trans, + struct iwl_txq *txq, + struct iwl_device_tx_cmd *dev_cmd, + struct sk_buff *skb, + struct iwl_cmd_meta *out_meta) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + int idx = iwl_txq_get_cmd_index(txq, txq->write_ptr); + struct iwl_tfh_tfd *tfd = iwl_txq_get_tfd(trans, txq, idx); + int len, hdr_len; + bool amsdu; + + /* There must be data left over for TB1 or this code must be changed */ + BUILD_BUG_ON(sizeof(struct iwl_tx_cmd_gen2) < IWL_FIRST_TB_SIZE); + + memset(tfd, 0, sizeof(*tfd)); + + if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210) + len = sizeof(struct iwl_tx_cmd_gen2); + else + len = sizeof(struct iwl_tx_cmd_gen3); + + amsdu = ieee80211_is_data_qos(hdr->frame_control) && + (*ieee80211_get_qos_ctl(hdr) & + IEEE80211_QOS_CTL_A_MSDU_PRESENT); + + hdr_len = ieee80211_hdrlen(hdr->frame_control); + + /* + * Only build A-MSDUs here if doing so by GSO, otherwise it may be + * an A-MSDU for other reasons, e.g. NAN or an A-MSDU having been + * built in the higher layers already. + */ + if (amsdu && skb_shinfo(skb)->gso_size) + return iwl_txq_gen2_build_tx_amsdu(trans, txq, dev_cmd, skb, + out_meta, hdr_len, len); + return iwl_txq_gen2_build_tx(trans, txq, dev_cmd, skb, out_meta, + hdr_len, len, !amsdu); +} + +int iwl_txq_space(struct iwl_trans *trans, const struct iwl_txq *q) +{ + unsigned int max; + unsigned int used; + + /* + * To avoid ambiguity between empty and completely full queues, there + * should always be less than max_tfd_queue_size elements in the queue. + * If q->n_window is smaller than max_tfd_queue_size, there is no need + * to reserve any queue entries for this purpose. + */ + if (q->n_window < trans->trans_cfg->base_params->max_tfd_queue_size) + max = q->n_window; + else + max = trans->trans_cfg->base_params->max_tfd_queue_size - 1; + + /* + * max_tfd_queue_size is a power of 2, so the following is equivalent to + * modulo by max_tfd_queue_size and is well defined. + */ + used = (q->write_ptr - q->read_ptr) & + (trans->trans_cfg->base_params->max_tfd_queue_size - 1); + + if (WARN_ON(used > max)) + return 0; + + return max - used; +} + +int iwl_txq_gen2_tx(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_device_tx_cmd *dev_cmd, int txq_id) +{ + struct iwl_cmd_meta *out_meta; + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + u16 cmd_len; + int idx; + void *tfd; + + if (WARN_ONCE(txq_id >= IWL_MAX_TVQM_QUEUES, + "queue %d out of range", txq_id)) + return -EINVAL; + + if (WARN_ONCE(!test_bit(txq_id, trans->txqs.queue_used), + "TX on unused queue %d\n", txq_id)) + return -EINVAL; + + if (skb_is_nonlinear(skb) && + skb_shinfo(skb)->nr_frags > IWL_TRANS_MAX_FRAGS(trans) && + __skb_linearize(skb)) + return -ENOMEM; + + spin_lock(&txq->lock); + + if (iwl_txq_space(trans, txq) < txq->high_mark) { + iwl_txq_stop(trans, txq); + + /* don't put the packet on the ring, if there is no room */ + if (unlikely(iwl_txq_space(trans, txq) < 3)) { + struct iwl_device_tx_cmd **dev_cmd_ptr; + + dev_cmd_ptr = (void *)((u8 *)skb->cb + + trans->txqs.dev_cmd_offs); + + *dev_cmd_ptr = dev_cmd; + __skb_queue_tail(&txq->overflow_q, skb); + spin_unlock(&txq->lock); + return 0; + } + } + + idx = iwl_txq_get_cmd_index(txq, txq->write_ptr); + + /* Set up driver data for this TFD */ + txq->entries[idx].skb = skb; + txq->entries[idx].cmd = dev_cmd; + + dev_cmd->hdr.sequence = + cpu_to_le16((u16)(QUEUE_TO_SEQ(txq_id) | + INDEX_TO_SEQ(idx))); + + /* Set up first empty entry in queue's array of Tx/cmd buffers */ + out_meta = &txq->entries[idx].meta; + out_meta->flags = 0; + + tfd = iwl_txq_gen2_build_tfd(trans, txq, dev_cmd, skb, out_meta); + if (!tfd) { + spin_unlock(&txq->lock); + return -1; + } + + if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) { + struct iwl_tx_cmd_gen3 *tx_cmd_gen3 = + (void *)dev_cmd->payload; + + cmd_len = le16_to_cpu(tx_cmd_gen3->len); + } else { + struct iwl_tx_cmd_gen2 *tx_cmd_gen2 = + (void *)dev_cmd->payload; + + cmd_len = le16_to_cpu(tx_cmd_gen2->len); + } + + /* Set up entry for this TFD in Tx byte-count array */ + iwl_pcie_gen2_update_byte_tbl(trans, txq, cmd_len, + iwl_txq_gen2_get_num_tbs(trans, tfd)); + + /* start timer if queue currently empty */ + if (txq->read_ptr == txq->write_ptr && txq->wd_timeout) + mod_timer(&txq->stuck_timer, jiffies + txq->wd_timeout); + + /* Tell device the write index *just past* this latest filled TFD */ + txq->write_ptr = iwl_txq_inc_wrap(trans, txq->write_ptr); + iwl_txq_inc_wr_ptr(trans, txq); + /* + * At this point the frame is "transmitted" successfully + * and we will get a TX status notification eventually. + */ + spin_unlock(&txq->lock); + return 0; +} + +/*************** HOST COMMAND QUEUE FUNCTIONS *****/ + +/* + * iwl_txq_gen2_unmap - Unmap any remaining DMA mappings and free skb's + */ +void iwl_txq_gen2_unmap(struct iwl_trans *trans, int txq_id) +{ + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + + spin_lock_bh(&txq->lock); + while (txq->write_ptr != txq->read_ptr) { + IWL_DEBUG_TX_REPLY(trans, "Q %d Free %d\n", + txq_id, txq->read_ptr); + + if (txq_id != trans->txqs.cmd.q_id) { + int idx = iwl_txq_get_cmd_index(txq, txq->read_ptr); + struct sk_buff *skb = txq->entries[idx].skb; + + if (!WARN_ON_ONCE(!skb)) + iwl_txq_free_tso_page(trans, skb); + } + iwl_txq_gen2_free_tfd(trans, txq); + txq->read_ptr = iwl_txq_inc_wrap(trans, txq->read_ptr); + } + + while (!skb_queue_empty(&txq->overflow_q)) { + struct sk_buff *skb = __skb_dequeue(&txq->overflow_q); + + iwl_op_mode_free_skb(trans->op_mode, skb); + } + + spin_unlock_bh(&txq->lock); + + /* just in case - this queue may have been stopped */ + iwl_wake_queue(trans, txq); +} + +static void iwl_txq_gen2_free_memory(struct iwl_trans *trans, + struct iwl_txq *txq) +{ + struct device *dev = trans->dev; + + /* De-alloc circular buffer of TFDs */ + if (txq->tfds) { + dma_free_coherent(dev, + trans->txqs.tfd.size * txq->n_window, + txq->tfds, txq->dma_addr); + dma_free_coherent(dev, + sizeof(*txq->first_tb_bufs) * txq->n_window, + txq->first_tb_bufs, txq->first_tb_dma); + } + + kfree(txq->entries); + if (txq->bc_tbl.addr) + dma_pool_free(trans->txqs.bc_pool, + txq->bc_tbl.addr, txq->bc_tbl.dma); + kfree(txq); +} + +/* + * iwl_pcie_txq_free - Deallocate DMA queue. + * @txq: Transmit queue to deallocate. + * + * Empty queue by removing and destroying all BD's. + * Free all buffers. + * 0-fill, but do not free "txq" descriptor structure. + */ +static void iwl_txq_gen2_free(struct iwl_trans *trans, int txq_id) +{ + struct iwl_txq *txq; + int i; + + if (WARN_ONCE(txq_id >= IWL_MAX_TVQM_QUEUES, + "queue %d out of range", txq_id)) + return; + + txq = trans->txqs.txq[txq_id]; + + if (WARN_ON(!txq)) + return; + + iwl_txq_gen2_unmap(trans, txq_id); + + /* De-alloc array of command/tx buffers */ + if (txq_id == trans->txqs.cmd.q_id) + for (i = 0; i < txq->n_window; i++) { + kfree_sensitive(txq->entries[i].cmd); + kfree_sensitive(txq->entries[i].free_buf); + } + del_timer_sync(&txq->stuck_timer); + + iwl_txq_gen2_free_memory(trans, txq); + + trans->txqs.txq[txq_id] = NULL; + + clear_bit(txq_id, trans->txqs.queue_used); +} + +/* + * iwl_queue_init - Initialize queue's high/low-water and read/write indexes + */ +static int iwl_queue_init(struct iwl_txq *q, int slots_num) +{ + q->n_window = slots_num; + + /* slots_num must be power-of-two size, otherwise + * iwl_txq_get_cmd_index is broken. */ + if (WARN_ON(!is_power_of_2(slots_num))) + return -EINVAL; + + q->low_mark = q->n_window / 4; + if (q->low_mark < 4) + q->low_mark = 4; + + q->high_mark = q->n_window / 8; + if (q->high_mark < 2) + q->high_mark = 2; + + q->write_ptr = 0; + q->read_ptr = 0; + + return 0; +} + +int iwl_txq_init(struct iwl_trans *trans, struct iwl_txq *txq, int slots_num, + bool cmd_queue) +{ + int ret; + u32 tfd_queue_max_size = + trans->trans_cfg->base_params->max_tfd_queue_size; + + txq->need_update = false; + + /* max_tfd_queue_size must be power-of-two size, otherwise + * iwl_txq_inc_wrap and iwl_txq_dec_wrap are broken. */ + if (WARN_ONCE(tfd_queue_max_size & (tfd_queue_max_size - 1), + "Max tfd queue size must be a power of two, but is %d", + tfd_queue_max_size)) + return -EINVAL; + + /* Initialize queue's high/low-water marks, and head/tail indexes */ + ret = iwl_queue_init(txq, slots_num); + if (ret) + return ret; + + spin_lock_init(&txq->lock); + + if (cmd_queue) { + static struct lock_class_key iwl_txq_cmd_queue_lock_class; + + lockdep_set_class(&txq->lock, &iwl_txq_cmd_queue_lock_class); + } + + __skb_queue_head_init(&txq->overflow_q); + + return 0; +} + +void iwl_txq_free_tso_page(struct iwl_trans *trans, struct sk_buff *skb) +{ + struct page **page_ptr; + struct page *next; + + page_ptr = (void *)((u8 *)skb->cb + trans->txqs.page_offs); + next = *page_ptr; + *page_ptr = NULL; + + while (next) { + struct page *tmp = next; + + next = *(void **)((u8 *)page_address(next) + PAGE_SIZE - + sizeof(void *)); + __free_page(tmp); + } +} + +void iwl_txq_log_scd_error(struct iwl_trans *trans, struct iwl_txq *txq) +{ + u32 txq_id = txq->id; + u32 status; + bool active; + u8 fifo; + + if (trans->trans_cfg->use_tfh) { + IWL_ERR(trans, "Queue %d is stuck %d %d\n", txq_id, + txq->read_ptr, txq->write_ptr); + /* TODO: access new SCD registers and dump them */ + return; + } + + status = iwl_read_prph(trans, SCD_QUEUE_STATUS_BITS(txq_id)); + fifo = (status >> SCD_QUEUE_STTS_REG_POS_TXF) & 0x7; + active = !!(status & BIT(SCD_QUEUE_STTS_REG_POS_ACTIVE)); + + IWL_ERR(trans, + "Queue %d is %sactive on fifo %d and stuck for %u ms. SW [%d, %d] HW [%d, %d] FH TRB=0x0%x\n", + txq_id, active ? "" : "in", fifo, + jiffies_to_msecs(txq->wd_timeout), + txq->read_ptr, txq->write_ptr, + iwl_read_prph(trans, SCD_QUEUE_RDPTR(txq_id)) & + (trans->trans_cfg->base_params->max_tfd_queue_size - 1), + iwl_read_prph(trans, SCD_QUEUE_WRPTR(txq_id)) & + (trans->trans_cfg->base_params->max_tfd_queue_size - 1), + iwl_read_direct32(trans, FH_TX_TRB_REG(fifo))); +} + +static void iwl_txq_stuck_timer(struct timer_list *t) +{ + struct iwl_txq *txq = from_timer(txq, t, stuck_timer); + struct iwl_trans *trans = txq->trans; + + spin_lock(&txq->lock); + /* check if triggered erroneously */ + if (txq->read_ptr == txq->write_ptr) { + spin_unlock(&txq->lock); + return; + } + spin_unlock(&txq->lock); + + iwl_txq_log_scd_error(trans, txq); + + iwl_force_nmi(trans); +} + +int iwl_txq_alloc(struct iwl_trans *trans, struct iwl_txq *txq, int slots_num, + bool cmd_queue) +{ + size_t tfd_sz = trans->txqs.tfd.size * + trans->trans_cfg->base_params->max_tfd_queue_size; + size_t tb0_buf_sz; + int i; + + if (WARN_ON(txq->entries || txq->tfds)) + return -EINVAL; + + if (trans->trans_cfg->use_tfh) + tfd_sz = trans->txqs.tfd.size * slots_num; + + timer_setup(&txq->stuck_timer, iwl_txq_stuck_timer, 0); + txq->trans = trans; + + txq->n_window = slots_num; + + txq->entries = kcalloc(slots_num, + sizeof(struct iwl_pcie_txq_entry), + GFP_KERNEL); + + if (!txq->entries) + goto error; + + if (cmd_queue) + for (i = 0; i < slots_num; i++) { + txq->entries[i].cmd = + kmalloc(sizeof(struct iwl_device_cmd), + GFP_KERNEL); + if (!txq->entries[i].cmd) + goto error; + } + + /* Circular buffer of transmit frame descriptors (TFDs), + * shared with device */ + txq->tfds = dma_alloc_coherent(trans->dev, tfd_sz, + &txq->dma_addr, GFP_KERNEL); + if (!txq->tfds) + goto error; + + BUILD_BUG_ON(sizeof(*txq->first_tb_bufs) != IWL_FIRST_TB_SIZE_ALIGN); + + tb0_buf_sz = sizeof(*txq->first_tb_bufs) * slots_num; + + txq->first_tb_bufs = dma_alloc_coherent(trans->dev, tb0_buf_sz, + &txq->first_tb_dma, + GFP_KERNEL); + if (!txq->first_tb_bufs) + goto err_free_tfds; + + return 0; +err_free_tfds: + dma_free_coherent(trans->dev, tfd_sz, txq->tfds, txq->dma_addr); + txq->tfds = NULL; +error: + if (txq->entries && cmd_queue) + for (i = 0; i < slots_num; i++) + kfree(txq->entries[i].cmd); + kfree(txq->entries); + txq->entries = NULL; + + return -ENOMEM; +} + +static struct iwl_txq * +iwl_txq_dyn_alloc_dma(struct iwl_trans *trans, int size, unsigned int timeout) +{ + size_t bc_tbl_size, bc_tbl_entries; + struct iwl_txq *txq; + int ret; + + WARN_ON(!trans->txqs.bc_tbl_size); + + bc_tbl_size = trans->txqs.bc_tbl_size; + bc_tbl_entries = bc_tbl_size / sizeof(u16); + + if (WARN_ON(size > bc_tbl_entries)) + return ERR_PTR(-EINVAL); + + txq = kzalloc(sizeof(*txq), GFP_KERNEL); + if (!txq) + return ERR_PTR(-ENOMEM); + + txq->bc_tbl.addr = dma_pool_alloc(trans->txqs.bc_pool, GFP_KERNEL, + &txq->bc_tbl.dma); + if (!txq->bc_tbl.addr) { + IWL_ERR(trans, "Scheduler BC Table allocation failed\n"); + kfree(txq); + return ERR_PTR(-ENOMEM); + } + + ret = iwl_txq_alloc(trans, txq, size, false); + if (ret) { + IWL_ERR(trans, "Tx queue alloc failed\n"); + goto error; + } + ret = iwl_txq_init(trans, txq, size, false); + if (ret) { + IWL_ERR(trans, "Tx queue init failed\n"); + goto error; + } + + txq->wd_timeout = msecs_to_jiffies(timeout); + + return txq; + +error: + iwl_txq_gen2_free_memory(trans, txq); + return ERR_PTR(ret); +} + +static int iwl_txq_alloc_response(struct iwl_trans *trans, struct iwl_txq *txq, + struct iwl_host_cmd *hcmd) +{ + struct iwl_tx_queue_cfg_rsp *rsp; + int ret, qid; + u32 wr_ptr; + + if (WARN_ON(iwl_rx_packet_payload_len(hcmd->resp_pkt) != + sizeof(*rsp))) { + ret = -EINVAL; + goto error_free_resp; + } + + rsp = (void *)hcmd->resp_pkt->data; + qid = le16_to_cpu(rsp->queue_number); + wr_ptr = le16_to_cpu(rsp->write_pointer); + + if (qid >= ARRAY_SIZE(trans->txqs.txq)) { + WARN_ONCE(1, "queue index %d unsupported", qid); + ret = -EIO; + goto error_free_resp; + } + + if (test_and_set_bit(qid, trans->txqs.queue_used)) { + WARN_ONCE(1, "queue %d already used", qid); + ret = -EIO; + goto error_free_resp; + } + + if (WARN_ONCE(trans->txqs.txq[qid], + "queue %d already allocated\n", qid)) { + ret = -EIO; + goto error_free_resp; + } + + txq->id = qid; + trans->txqs.txq[qid] = txq; + wr_ptr &= (trans->trans_cfg->base_params->max_tfd_queue_size - 1); + + /* Place first TFD at index corresponding to start sequence number */ + txq->read_ptr = wr_ptr; + txq->write_ptr = wr_ptr; + + IWL_DEBUG_TX_QUEUES(trans, "Activate queue %d\n", qid); + + iwl_free_resp(hcmd); + return qid; + +error_free_resp: + iwl_free_resp(hcmd); + iwl_txq_gen2_free_memory(trans, txq); + return ret; +} + +int iwl_txq_dyn_alloc(struct iwl_trans *trans, u32 flags, u32 sta_mask, + u8 tid, int size, unsigned int timeout) +{ + struct iwl_txq *txq; + union { + struct iwl_tx_queue_cfg_cmd old; + struct iwl_scd_queue_cfg_cmd new; + } cmd; + struct iwl_host_cmd hcmd = { + .flags = CMD_WANT_SKB, + }; + int ret; + + if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_BZ && + trans->hw_rev_step == SILICON_A_STEP) + size = 4096; + + txq = iwl_txq_dyn_alloc_dma(trans, size, timeout); + if (IS_ERR(txq)) + return PTR_ERR(txq); + + if (trans->txqs.queue_alloc_cmd_ver == 0) { + memset(&cmd.old, 0, sizeof(cmd.old)); + cmd.old.tfdq_addr = cpu_to_le64(txq->dma_addr); + cmd.old.byte_cnt_addr = cpu_to_le64(txq->bc_tbl.dma); + cmd.old.cb_size = cpu_to_le32(TFD_QUEUE_CB_SIZE(size)); + cmd.old.flags = cpu_to_le16(flags | TX_QUEUE_CFG_ENABLE_QUEUE); + cmd.old.tid = tid; + + if (hweight32(sta_mask) != 1) { + ret = -EINVAL; + goto error; + } + cmd.old.sta_id = ffs(sta_mask) - 1; + + hcmd.id = SCD_QUEUE_CFG; + hcmd.len[0] = sizeof(cmd.old); + hcmd.data[0] = &cmd.old; + } else if (trans->txqs.queue_alloc_cmd_ver == 3) { + memset(&cmd.new, 0, sizeof(cmd.new)); + cmd.new.operation = cpu_to_le32(IWL_SCD_QUEUE_ADD); + cmd.new.u.add.tfdq_dram_addr = cpu_to_le64(txq->dma_addr); + cmd.new.u.add.bc_dram_addr = cpu_to_le64(txq->bc_tbl.dma); + cmd.new.u.add.cb_size = cpu_to_le32(TFD_QUEUE_CB_SIZE(size)); + cmd.new.u.add.flags = cpu_to_le32(flags); + cmd.new.u.add.sta_mask = cpu_to_le32(sta_mask); + cmd.new.u.add.tid = tid; + + hcmd.id = WIDE_ID(DATA_PATH_GROUP, SCD_QUEUE_CONFIG_CMD); + hcmd.len[0] = sizeof(cmd.new); + hcmd.data[0] = &cmd.new; + } else { + ret = -EOPNOTSUPP; + goto error; + } + + ret = iwl_trans_send_cmd(trans, &hcmd); + if (ret) + goto error; + + return iwl_txq_alloc_response(trans, txq, &hcmd); + +error: + iwl_txq_gen2_free_memory(trans, txq); + return ret; +} + +void iwl_txq_dyn_free(struct iwl_trans *trans, int queue) +{ + if (WARN(queue >= IWL_MAX_TVQM_QUEUES, + "queue %d out of range", queue)) + return; + + /* + * Upon HW Rfkill - we stop the device, and then stop the queues + * in the op_mode. Just for the sake of the simplicity of the op_mode, + * allow the op_mode to call txq_disable after it already called + * stop_device. + */ + if (!test_and_clear_bit(queue, trans->txqs.queue_used)) { + WARN_ONCE(test_bit(STATUS_DEVICE_ENABLED, &trans->status), + "queue %d not used", queue); + return; + } + + iwl_txq_gen2_free(trans, queue); + + IWL_DEBUG_TX_QUEUES(trans, "Deactivate queue %d\n", queue); +} + +void iwl_txq_gen2_tx_free(struct iwl_trans *trans) +{ + int i; + + memset(trans->txqs.queue_used, 0, sizeof(trans->txqs.queue_used)); + + /* Free all TX queues */ + for (i = 0; i < ARRAY_SIZE(trans->txqs.txq); i++) { + if (!trans->txqs.txq[i]) + continue; + + iwl_txq_gen2_free(trans, i); + } +} + +int iwl_txq_gen2_init(struct iwl_trans *trans, int txq_id, int queue_size) +{ + struct iwl_txq *queue; + int ret; + + /* alloc and init the tx queue */ + if (!trans->txqs.txq[txq_id]) { + queue = kzalloc(sizeof(*queue), GFP_KERNEL); + if (!queue) { + IWL_ERR(trans, "Not enough memory for tx queue\n"); + return -ENOMEM; + } + trans->txqs.txq[txq_id] = queue; + ret = iwl_txq_alloc(trans, queue, queue_size, true); + if (ret) { + IWL_ERR(trans, "Tx %d queue init failed\n", txq_id); + goto error; + } + } else { + queue = trans->txqs.txq[txq_id]; + } + + ret = iwl_txq_init(trans, queue, queue_size, + (txq_id == trans->txqs.cmd.q_id)); + if (ret) { + IWL_ERR(trans, "Tx %d queue alloc failed\n", txq_id); + goto error; + } + trans->txqs.txq[txq_id]->id = txq_id; + set_bit(txq_id, trans->txqs.queue_used); + + return 0; + +error: + iwl_txq_gen2_tx_free(trans); + return ret; +} + +static inline dma_addr_t iwl_txq_gen1_tfd_tb_get_addr(struct iwl_trans *trans, + void *_tfd, u8 idx) +{ + struct iwl_tfd *tfd; + struct iwl_tfd_tb *tb; + dma_addr_t addr; + dma_addr_t hi_len; + + if (trans->trans_cfg->use_tfh) { + struct iwl_tfh_tfd *tfh_tfd = _tfd; + struct iwl_tfh_tb *tfh_tb = &tfh_tfd->tbs[idx]; + + return (dma_addr_t)(le64_to_cpu(tfh_tb->addr)); + } + + tfd = _tfd; + tb = &tfd->tbs[idx]; + addr = get_unaligned_le32(&tb->lo); + + if (sizeof(dma_addr_t) <= sizeof(u32)) + return addr; + + hi_len = le16_to_cpu(tb->hi_n_len) & 0xF; + + /* + * shift by 16 twice to avoid warnings on 32-bit + * (where this code never runs anyway due to the + * if statement above) + */ + return addr | ((hi_len << 16) << 16); +} + +void iwl_txq_gen1_tfd_unmap(struct iwl_trans *trans, + struct iwl_cmd_meta *meta, + struct iwl_txq *txq, int index) +{ + int i, num_tbs; + void *tfd = iwl_txq_get_tfd(trans, txq, index); + + /* Sanity check on number of chunks */ + num_tbs = iwl_txq_gen1_tfd_get_num_tbs(trans, tfd); + + if (num_tbs > trans->txqs.tfd.max_tbs) { + IWL_ERR(trans, "Too many chunks: %i\n", num_tbs); + /* @todo issue fatal error, it is quite serious situation */ + return; + } + + /* first TB is never freed - it's the bidirectional DMA data */ + + for (i = 1; i < num_tbs; i++) { + if (meta->tbs & BIT(i)) + dma_unmap_page(trans->dev, + iwl_txq_gen1_tfd_tb_get_addr(trans, + tfd, i), + iwl_txq_gen1_tfd_tb_get_len(trans, + tfd, i), + DMA_TO_DEVICE); + else + dma_unmap_single(trans->dev, + iwl_txq_gen1_tfd_tb_get_addr(trans, + tfd, i), + iwl_txq_gen1_tfd_tb_get_len(trans, + tfd, i), + DMA_TO_DEVICE); + } + + meta->tbs = 0; + + if (trans->trans_cfg->use_tfh) { + struct iwl_tfh_tfd *tfd_fh = (void *)tfd; + + tfd_fh->num_tbs = 0; + } else { + struct iwl_tfd *tfd_fh = (void *)tfd; + + tfd_fh->num_tbs = 0; + } +} + +#define IWL_TX_CRC_SIZE 4 +#define IWL_TX_DELIMITER_SIZE 4 + +/* + * iwl_txq_gen1_update_byte_cnt_tbl - Set up entry in Tx byte-count array + */ +void iwl_txq_gen1_update_byte_cnt_tbl(struct iwl_trans *trans, + struct iwl_txq *txq, u16 byte_cnt, + int num_tbs) +{ + struct iwlagn_scd_bc_tbl *scd_bc_tbl; + int write_ptr = txq->write_ptr; + int txq_id = txq->id; + u8 sec_ctl = 0; + u16 len = byte_cnt + IWL_TX_CRC_SIZE + IWL_TX_DELIMITER_SIZE; + __le16 bc_ent; + struct iwl_device_tx_cmd *dev_cmd = txq->entries[txq->write_ptr].cmd; + struct iwl_tx_cmd *tx_cmd = (void *)dev_cmd->payload; + u8 sta_id = tx_cmd->sta_id; + + scd_bc_tbl = trans->txqs.scd_bc_tbls.addr; + + sec_ctl = tx_cmd->sec_ctl; + + switch (sec_ctl & TX_CMD_SEC_MSK) { + case TX_CMD_SEC_CCM: + len += IEEE80211_CCMP_MIC_LEN; + break; + case TX_CMD_SEC_TKIP: + len += IEEE80211_TKIP_ICV_LEN; + break; + case TX_CMD_SEC_WEP: + len += IEEE80211_WEP_IV_LEN + IEEE80211_WEP_ICV_LEN; + break; + } + if (trans->txqs.bc_table_dword) + len = DIV_ROUND_UP(len, 4); + + if (WARN_ON(len > 0xFFF || write_ptr >= TFD_QUEUE_SIZE_MAX)) + return; + + bc_ent = cpu_to_le16(len | (sta_id << 12)); + + scd_bc_tbl[txq_id].tfd_offset[write_ptr] = bc_ent; + + if (write_ptr < TFD_QUEUE_SIZE_BC_DUP) + scd_bc_tbl[txq_id].tfd_offset[TFD_QUEUE_SIZE_MAX + write_ptr] = + bc_ent; +} + +void iwl_txq_gen1_inval_byte_cnt_tbl(struct iwl_trans *trans, + struct iwl_txq *txq) +{ + struct iwlagn_scd_bc_tbl *scd_bc_tbl = trans->txqs.scd_bc_tbls.addr; + int txq_id = txq->id; + int read_ptr = txq->read_ptr; + u8 sta_id = 0; + __le16 bc_ent; + struct iwl_device_tx_cmd *dev_cmd = txq->entries[read_ptr].cmd; + struct iwl_tx_cmd *tx_cmd = (void *)dev_cmd->payload; + + WARN_ON(read_ptr >= TFD_QUEUE_SIZE_MAX); + + if (txq_id != trans->txqs.cmd.q_id) + sta_id = tx_cmd->sta_id; + + bc_ent = cpu_to_le16(1 | (sta_id << 12)); + + scd_bc_tbl[txq_id].tfd_offset[read_ptr] = bc_ent; + + if (read_ptr < TFD_QUEUE_SIZE_BC_DUP) + scd_bc_tbl[txq_id].tfd_offset[TFD_QUEUE_SIZE_MAX + read_ptr] = + bc_ent; +} + +/* + * iwl_txq_free_tfd - Free all chunks referenced by TFD [txq->q.read_ptr] + * @trans - transport private data + * @txq - tx queue + * @dma_dir - the direction of the DMA mapping + * + * Does NOT advance any TFD circular buffer read/write indexes + * Does NOT free the TFD itself (which is within circular buffer) + */ +void iwl_txq_free_tfd(struct iwl_trans *trans, struct iwl_txq *txq) +{ + /* rd_ptr is bounded by TFD_QUEUE_SIZE_MAX and + * idx is bounded by n_window + */ + int rd_ptr = txq->read_ptr; + int idx = iwl_txq_get_cmd_index(txq, rd_ptr); + struct sk_buff *skb; + + lockdep_assert_held(&txq->lock); + + if (!txq->entries) + return; + + /* We have only q->n_window txq->entries, but we use + * TFD_QUEUE_SIZE_MAX tfds + */ + iwl_txq_gen1_tfd_unmap(trans, &txq->entries[idx].meta, txq, rd_ptr); + + /* free SKB */ + skb = txq->entries[idx].skb; + + /* Can be called from irqs-disabled context + * If skb is not NULL, it means that the whole queue is being + * freed and that the queue is not empty - free the skb + */ + if (skb) { + iwl_op_mode_free_skb(trans->op_mode, skb); + txq->entries[idx].skb = NULL; + } +} + +void iwl_txq_progress(struct iwl_txq *txq) +{ + lockdep_assert_held(&txq->lock); + + if (!txq->wd_timeout) + return; + + /* + * station is asleep and we send data - that must + * be uAPSD or PS-Poll. Don't rearm the timer. + */ + if (txq->frozen) + return; + + /* + * if empty delete timer, otherwise move timer forward + * since we're making progress on this queue + */ + if (txq->read_ptr == txq->write_ptr) + del_timer(&txq->stuck_timer); + else + mod_timer(&txq->stuck_timer, jiffies + txq->wd_timeout); +} + +/* Frees buffers until index _not_ inclusive */ +void iwl_txq_reclaim(struct iwl_trans *trans, int txq_id, int ssn, + struct sk_buff_head *skbs, bool is_flush) +{ + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + int tfd_num = iwl_txq_get_cmd_index(txq, ssn); + int read_ptr = iwl_txq_get_cmd_index(txq, txq->read_ptr); + int last_to_free; + + /* This function is not meant to release cmd queue*/ + if (WARN_ON(txq_id == trans->txqs.cmd.q_id)) + return; + + spin_lock_bh(&txq->lock); + + if (!test_bit(txq_id, trans->txqs.queue_used)) { + IWL_DEBUG_TX_QUEUES(trans, "Q %d inactive - ignoring idx %d\n", + txq_id, ssn); + goto out; + } + + if (read_ptr == tfd_num) + goto out; + + IWL_DEBUG_TX_REPLY(trans, "[Q %d] %d -> %d (%d)\n", + txq_id, txq->read_ptr, tfd_num, ssn); + + /*Since we free until index _not_ inclusive, the one before index is + * the last we will free. This one must be used */ + last_to_free = iwl_txq_dec_wrap(trans, tfd_num); + + if (!iwl_txq_used(txq, last_to_free)) { + IWL_ERR(trans, + "%s: Read index for txq id (%d), last_to_free %d is out of range [0-%d] %d %d.\n", + __func__, txq_id, last_to_free, + trans->trans_cfg->base_params->max_tfd_queue_size, + txq->write_ptr, txq->read_ptr); + + iwl_op_mode_time_point(trans->op_mode, + IWL_FW_INI_TIME_POINT_FAKE_TX, + NULL); + goto out; + } + + if (WARN_ON(!skb_queue_empty(skbs))) + goto out; + + for (; + read_ptr != tfd_num; + txq->read_ptr = iwl_txq_inc_wrap(trans, txq->read_ptr), + read_ptr = iwl_txq_get_cmd_index(txq, txq->read_ptr)) { + struct sk_buff *skb = txq->entries[read_ptr].skb; + + if (WARN_ON_ONCE(!skb)) + continue; + + iwl_txq_free_tso_page(trans, skb); + + __skb_queue_tail(skbs, skb); + + txq->entries[read_ptr].skb = NULL; + + if (!trans->trans_cfg->use_tfh) + iwl_txq_gen1_inval_byte_cnt_tbl(trans, txq); + + iwl_txq_free_tfd(trans, txq); + } + + iwl_txq_progress(txq); + + if (iwl_txq_space(trans, txq) > txq->low_mark && + test_bit(txq_id, trans->txqs.queue_stopped)) { + struct sk_buff_head overflow_skbs; + struct sk_buff *skb; + + __skb_queue_head_init(&overflow_skbs); + skb_queue_splice_init(&txq->overflow_q, + is_flush ? skbs : &overflow_skbs); + + /* + * We are going to transmit from the overflow queue. + * Remember this state so that wait_for_txq_empty will know we + * are adding more packets to the TFD queue. It cannot rely on + * the state of &txq->overflow_q, as we just emptied it, but + * haven't TXed the content yet. + */ + txq->overflow_tx = true; + + /* + * This is tricky: we are in reclaim path which is non + * re-entrant, so noone will try to take the access the + * txq data from that path. We stopped tx, so we can't + * have tx as well. Bottom line, we can unlock and re-lock + * later. + */ + spin_unlock_bh(&txq->lock); + + while ((skb = __skb_dequeue(&overflow_skbs))) { + struct iwl_device_tx_cmd *dev_cmd_ptr; + + dev_cmd_ptr = *(void **)((u8 *)skb->cb + + trans->txqs.dev_cmd_offs); + + /* + * Note that we can very well be overflowing again. + * In that case, iwl_txq_space will be small again + * and we won't wake mac80211's queue. + */ + iwl_trans_tx(trans, skb, dev_cmd_ptr, txq_id); + } + + if (iwl_txq_space(trans, txq) > txq->low_mark) + iwl_wake_queue(trans, txq); + + spin_lock_bh(&txq->lock); + txq->overflow_tx = false; + } + +out: + spin_unlock_bh(&txq->lock); +} + +/* Set wr_ptr of specific device and txq */ +void iwl_txq_set_q_ptrs(struct iwl_trans *trans, int txq_id, int ptr) +{ + struct iwl_txq *txq = trans->txqs.txq[txq_id]; + + spin_lock_bh(&txq->lock); + + txq->write_ptr = ptr; + txq->read_ptr = txq->write_ptr; + + spin_unlock_bh(&txq->lock); +} + +void iwl_trans_txq_freeze_timer(struct iwl_trans *trans, unsigned long txqs, + bool freeze) +{ + int queue; + + for_each_set_bit(queue, &txqs, BITS_PER_LONG) { + struct iwl_txq *txq = trans->txqs.txq[queue]; + unsigned long now; + + spin_lock_bh(&txq->lock); + + now = jiffies; + + if (txq->frozen == freeze) + goto next_queue; + + IWL_DEBUG_TX_QUEUES(trans, "%s TXQ %d\n", + freeze ? "Freezing" : "Waking", queue); + + txq->frozen = freeze; + + if (txq->read_ptr == txq->write_ptr) + goto next_queue; + + if (freeze) { + if (unlikely(time_after(now, + txq->stuck_timer.expires))) { + /* + * The timer should have fired, maybe it is + * spinning right now on the lock. + */ + goto next_queue; + } + /* remember how long until the timer fires */ + txq->frozen_expiry_remainder = + txq->stuck_timer.expires - now; + del_timer(&txq->stuck_timer); + goto next_queue; + } + + /* + * Wake a non-empty queue -> arm timer with the + * remainder before it froze + */ + mod_timer(&txq->stuck_timer, + now + txq->frozen_expiry_remainder); + +next_queue: + spin_unlock_bh(&txq->lock); + } +} + +#define HOST_COMPLETE_TIMEOUT (2 * HZ) + +static int iwl_trans_txq_send_hcmd_sync(struct iwl_trans *trans, + struct iwl_host_cmd *cmd) +{ + const char *cmd_str = iwl_get_cmd_string(trans, cmd->id); + struct iwl_txq *txq = trans->txqs.txq[trans->txqs.cmd.q_id]; + int cmd_idx; + int ret; + + IWL_DEBUG_INFO(trans, "Attempting to send sync command %s\n", cmd_str); + + if (WARN(test_and_set_bit(STATUS_SYNC_HCMD_ACTIVE, + &trans->status), + "Command %s: a command is already active!\n", cmd_str)) + return -EIO; + + IWL_DEBUG_INFO(trans, "Setting HCMD_ACTIVE for command %s\n", cmd_str); + + cmd_idx = trans->ops->send_cmd(trans, cmd); + if (cmd_idx < 0) { + ret = cmd_idx; + clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status); + IWL_ERR(trans, "Error sending %s: enqueue_hcmd failed: %d\n", + cmd_str, ret); + return ret; + } + + ret = wait_event_timeout(trans->wait_command_queue, + !test_bit(STATUS_SYNC_HCMD_ACTIVE, + &trans->status), + HOST_COMPLETE_TIMEOUT); + if (!ret) { + IWL_ERR(trans, "Error sending %s: time out after %dms.\n", + cmd_str, jiffies_to_msecs(HOST_COMPLETE_TIMEOUT)); + + IWL_ERR(trans, "Current CMD queue read_ptr %d write_ptr %d\n", + txq->read_ptr, txq->write_ptr); + + clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status); + IWL_DEBUG_INFO(trans, "Clearing HCMD_ACTIVE for command %s\n", + cmd_str); + ret = -ETIMEDOUT; + + iwl_trans_sync_nmi(trans); + goto cancel; + } + + if (test_bit(STATUS_FW_ERROR, &trans->status)) { + if (!test_and_clear_bit(STATUS_SUPPRESS_CMD_ERROR_ONCE, + &trans->status)) { + IWL_ERR(trans, "FW error in SYNC CMD %s\n", cmd_str); + dump_stack(); + } + ret = -EIO; + goto cancel; + } + + if (!(cmd->flags & CMD_SEND_IN_RFKILL) && + test_bit(STATUS_RFKILL_OPMODE, &trans->status)) { + IWL_DEBUG_RF_KILL(trans, "RFKILL in SYNC CMD... no rsp\n"); + ret = -ERFKILL; + goto cancel; + } + + if ((cmd->flags & CMD_WANT_SKB) && !cmd->resp_pkt) { + IWL_ERR(trans, "Error: Response NULL in '%s'\n", cmd_str); + ret = -EIO; + goto cancel; + } + + return 0; + +cancel: + if (cmd->flags & CMD_WANT_SKB) { + /* + * Cancel the CMD_WANT_SKB flag for the cmd in the + * TX cmd queue. Otherwise in case the cmd comes + * in later, it will possibly set an invalid + * address (cmd->meta.source). + */ + txq->entries[cmd_idx].meta.flags &= ~CMD_WANT_SKB; + } + + if (cmd->resp_pkt) { + iwl_free_resp(cmd); + cmd->resp_pkt = NULL; + } + + return ret; +} + +int iwl_trans_txq_send_hcmd(struct iwl_trans *trans, + struct iwl_host_cmd *cmd) +{ + /* Make sure the NIC is still alive in the bus */ + if (test_bit(STATUS_TRANS_DEAD, &trans->status)) + return -ENODEV; + + if (!(cmd->flags & CMD_SEND_IN_RFKILL) && + test_bit(STATUS_RFKILL_OPMODE, &trans->status)) { + IWL_DEBUG_RF_KILL(trans, "Dropping CMD 0x%x: RF KILL\n", + cmd->id); + return -ERFKILL; + } + + if (unlikely(trans->system_pm_mode == IWL_PLAT_PM_MODE_D3 && + !(cmd->flags & CMD_SEND_IN_D3))) { + IWL_DEBUG_WOWLAN(trans, "Dropping CMD 0x%x: D3\n", cmd->id); + return -EHOSTDOWN; + } + + if (cmd->flags & CMD_ASYNC) { + int ret; + + /* An asynchronous command can not expect an SKB to be set. */ + if (WARN_ON(cmd->flags & CMD_WANT_SKB)) + return -EINVAL; + + ret = trans->ops->send_cmd(trans, cmd); + if (ret < 0) { + IWL_ERR(trans, + "Error sending %s: enqueue_hcmd failed: %d\n", + iwl_get_cmd_string(trans, cmd->id), ret); + return ret; + } + return 0; + } + + return iwl_trans_txq_send_hcmd_sync(trans, cmd); +} + diff --git a/drivers/net/wireless/intel/iwlwifi/queue/tx.h b/drivers/net/wireless/intel/iwlwifi/queue/tx.h new file mode 100644 index 000000000..ceb6812fe --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/queue/tx.h @@ -0,0 +1,183 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2020-2022 Intel Corporation + */ +#ifndef __iwl_trans_queue_tx_h__ +#define __iwl_trans_queue_tx_h__ +#include "iwl-fh.h" +#include "fw/api/tx.h" + +struct iwl_tso_hdr_page { + struct page *page; + u8 *pos; +}; + +static inline dma_addr_t +iwl_txq_get_first_tb_dma(struct iwl_txq *txq, int idx) +{ + return txq->first_tb_dma + + sizeof(struct iwl_pcie_first_tb_buf) * idx; +} + +static inline u16 iwl_txq_get_cmd_index(const struct iwl_txq *q, u32 index) +{ + return index & (q->n_window - 1); +} + +void iwl_txq_gen2_unmap(struct iwl_trans *trans, int txq_id); + +static inline void iwl_wake_queue(struct iwl_trans *trans, + struct iwl_txq *txq) +{ + if (test_and_clear_bit(txq->id, trans->txqs.queue_stopped)) { + IWL_DEBUG_TX_QUEUES(trans, "Wake hwq %d\n", txq->id); + iwl_op_mode_queue_not_full(trans->op_mode, txq->id); + } +} + +static inline void *iwl_txq_get_tfd(struct iwl_trans *trans, + struct iwl_txq *txq, int idx) +{ + if (trans->trans_cfg->use_tfh) + idx = iwl_txq_get_cmd_index(txq, idx); + + return (u8 *)txq->tfds + trans->txqs.tfd.size * idx; +} + +int iwl_txq_alloc(struct iwl_trans *trans, struct iwl_txq *txq, int slots_num, + bool cmd_queue); +/* + * We need this inline in case dma_addr_t is only 32-bits - since the + * hardware is always 64-bit, the issue can still occur in that case, + * so use u64 for 'phys' here to force the addition in 64-bit. + */ +static inline bool iwl_txq_crosses_4g_boundary(u64 phys, u16 len) +{ + return upper_32_bits(phys) != upper_32_bits(phys + len); +} + +int iwl_txq_space(struct iwl_trans *trans, const struct iwl_txq *q); + +static inline void iwl_txq_stop(struct iwl_trans *trans, struct iwl_txq *txq) +{ + if (!test_and_set_bit(txq->id, trans->txqs.queue_stopped)) { + iwl_op_mode_queue_full(trans->op_mode, txq->id); + IWL_DEBUG_TX_QUEUES(trans, "Stop hwq %d\n", txq->id); + } else { + IWL_DEBUG_TX_QUEUES(trans, "hwq %d already stopped\n", + txq->id); + } +} + +/** + * iwl_txq_inc_wrap - increment queue index, wrap back to beginning + * @index -- current index + */ +static inline int iwl_txq_inc_wrap(struct iwl_trans *trans, int index) +{ + return ++index & + (trans->trans_cfg->base_params->max_tfd_queue_size - 1); +} + +/** + * iwl_txq_dec_wrap - decrement queue index, wrap back to end + * @index -- current index + */ +static inline int iwl_txq_dec_wrap(struct iwl_trans *trans, int index) +{ + return --index & + (trans->trans_cfg->base_params->max_tfd_queue_size - 1); +} + +static inline bool iwl_txq_used(const struct iwl_txq *q, int i) +{ + int index = iwl_txq_get_cmd_index(q, i); + int r = iwl_txq_get_cmd_index(q, q->read_ptr); + int w = iwl_txq_get_cmd_index(q, q->write_ptr); + + return w >= r ? + (index >= r && index < w) : + !(index < r && index >= w); +} + +void iwl_txq_free_tso_page(struct iwl_trans *trans, struct sk_buff *skb); + +void iwl_txq_log_scd_error(struct iwl_trans *trans, struct iwl_txq *txq); + +int iwl_txq_gen2_set_tb(struct iwl_trans *trans, + struct iwl_tfh_tfd *tfd, dma_addr_t addr, + u16 len); + +void iwl_txq_gen2_tfd_unmap(struct iwl_trans *trans, + struct iwl_cmd_meta *meta, + struct iwl_tfh_tfd *tfd); + +int iwl_txq_dyn_alloc(struct iwl_trans *trans, u32 flags, + u32 sta_mask, u8 tid, + int size, unsigned int timeout); + +int iwl_txq_gen2_tx(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_device_tx_cmd *dev_cmd, int txq_id); + +void iwl_txq_dyn_free(struct iwl_trans *trans, int queue); +void iwl_txq_gen2_free_tfd(struct iwl_trans *trans, struct iwl_txq *txq); +void iwl_txq_inc_wr_ptr(struct iwl_trans *trans, struct iwl_txq *txq); +void iwl_txq_gen2_tx_free(struct iwl_trans *trans); +int iwl_txq_init(struct iwl_trans *trans, struct iwl_txq *txq, int slots_num, + bool cmd_queue); +int iwl_txq_gen2_init(struct iwl_trans *trans, int txq_id, int queue_size); +#ifdef CONFIG_INET +struct iwl_tso_hdr_page *get_page_hdr(struct iwl_trans *trans, size_t len, + struct sk_buff *skb); +#endif +static inline u8 iwl_txq_gen1_tfd_get_num_tbs(struct iwl_trans *trans, + void *_tfd) +{ + struct iwl_tfd *tfd; + + if (trans->trans_cfg->use_tfh) { + struct iwl_tfh_tfd *tfh_tfd = _tfd; + + return le16_to_cpu(tfh_tfd->num_tbs) & 0x1f; + } + + tfd = (struct iwl_tfd *)_tfd; + return tfd->num_tbs & 0x1f; +} + +static inline u16 iwl_txq_gen1_tfd_tb_get_len(struct iwl_trans *trans, + void *_tfd, u8 idx) +{ + struct iwl_tfd *tfd; + struct iwl_tfd_tb *tb; + + if (trans->trans_cfg->use_tfh) { + struct iwl_tfh_tfd *tfh_tfd = _tfd; + struct iwl_tfh_tb *tfh_tb = &tfh_tfd->tbs[idx]; + + return le16_to_cpu(tfh_tb->tb_len); + } + + tfd = (struct iwl_tfd *)_tfd; + tb = &tfd->tbs[idx]; + + return le16_to_cpu(tb->hi_n_len) >> 4; +} + +void iwl_txq_gen1_tfd_unmap(struct iwl_trans *trans, + struct iwl_cmd_meta *meta, + struct iwl_txq *txq, int index); +void iwl_txq_gen1_inval_byte_cnt_tbl(struct iwl_trans *trans, + struct iwl_txq *txq); +void iwl_txq_gen1_update_byte_cnt_tbl(struct iwl_trans *trans, + struct iwl_txq *txq, u16 byte_cnt, + int num_tbs); +void iwl_txq_reclaim(struct iwl_trans *trans, int txq_id, int ssn, + struct sk_buff_head *skbs, bool is_flush); +void iwl_txq_set_q_ptrs(struct iwl_trans *trans, int txq_id, int ptr); +void iwl_trans_txq_freeze_timer(struct iwl_trans *trans, unsigned long txqs, + bool freeze); +void iwl_txq_progress(struct iwl_txq *txq); +void iwl_txq_free_tfd(struct iwl_trans *trans, struct iwl_txq *txq); +int iwl_trans_txq_send_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd); +#endif /* __iwl_trans_queue_tx_h__ */ |