diff options
Diffstat (limited to 'drivers/net/wireless/intel')
62 files changed, 4020 insertions, 846 deletions
| diff --git a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c index 072b0a5827d1..bc98b87cf2a1 100644 --- a/drivers/net/wireless/intel/iwlwifi/cfg/bz.c +++ b/drivers/net/wireless/intel/iwlwifi/cfg/bz.c @@ -149,6 +149,8 @@ const struct iwl_cfg_trans_params iwl_bz_trans_cfg = {  };  const char iwl_bz_name[] = "Intel(R) TBD Bz device"; +const char iwl_fm_name[] = "Intel(R) Wi-Fi 7 BE201 320MHz"; +const char iwl_gl_name[] = "Intel(R) Wi-Fi 7 BE200 320MHz";  const char iwl_mtp_name[] = "Intel(R) Wi-Fi 7 BE202 160MHz";  const struct iwl_cfg iwl_cfg_bz = { diff --git a/drivers/net/wireless/intel/iwlwifi/dvm/Makefile b/drivers/net/wireless/intel/iwlwifi/dvm/Makefile index 0486b17d7c41..6109d64006db 100644 --- a/drivers/net/wireless/intel/iwlwifi/dvm/Makefile +++ b/drivers/net/wireless/intel/iwlwifi/dvm/Makefile @@ -11,4 +11,4 @@ iwldvm-objs		+= rxon.o devices.o  iwldvm-$(CONFIG_IWLWIFI_LEDS) += led.o  iwldvm-$(CONFIG_IWLWIFI_DEBUGFS) += debugfs.o -ccflags-y += -I $(srctree)/$(src)/../ +ccflags-y += -I $(src)/../ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/acpi.c b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c index 4caf2e25a297..fa339791223b 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/acpi.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/acpi.c @@ -1,7 +1,7 @@  // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause  /*   * Copyright (C) 2017 Intel Deutschland GmbH - * Copyright (C) 2019-2023 Intel Corporation + * Copyright (C) 2019-2024 Intel Corporation   */  #include <linux/uuid.h>  #include "iwl-drv.h" @@ -960,3 +960,37 @@ out_free:  	kfree(data);  }  IWL_EXPORT_SYMBOL(iwl_acpi_get_guid_lock_status); + +int iwl_acpi_get_wbem(struct iwl_fw_runtime *fwrt, u32 *value) +{ +	union acpi_object *wifi_pkg, *data; +	int ret = -ENOENT; +	int tbl_rev; + +	data = iwl_acpi_get_object(fwrt->dev, ACPI_WBEM_METHOD); +	if (IS_ERR(data)) +		return ret; + +	wifi_pkg = iwl_acpi_get_wifi_pkg(fwrt->dev, data, +					 ACPI_WBEM_WIFI_DATA_SIZE, +					 &tbl_rev); +	if (IS_ERR(wifi_pkg)) +		goto out_free; + +	if (tbl_rev != IWL_ACPI_WBEM_REVISION) { +		IWL_DEBUG_RADIO(fwrt, "Unsupported ACPI WBEM revision:%d\n", +				tbl_rev); +		goto out_free; +	} + +	if (wifi_pkg->package.elements[1].type != ACPI_TYPE_INTEGER) +		goto out_free; + +	*value = wifi_pkg->package.elements[1].integer.value & +		 IWL_ACPI_WBEM_REV0_MASK; +	IWL_DEBUG_RADIO(fwrt, "Loaded WBEM config from ACPI\n"); +	ret = 0; +out_free: +	kfree(data); +	return ret; +} diff --git a/drivers/net/wireless/intel/iwlwifi/fw/acpi.h b/drivers/net/wireless/intel/iwlwifi/fw/acpi.h index 1d32b82f73db..bb88398a6987 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/acpi.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/acpi.h @@ -27,6 +27,7 @@  #define ACPI_WTAS_METHOD	"WTAS"  #define ACPI_WPFC_METHOD	"WPFC"  #define ACPI_GLAI_METHOD	"GLAI" +#define ACPI_WBEM_METHOD	"WBEM"  #define ACPI_WIFI_DOMAIN	(0x07) @@ -67,6 +68,12 @@  #define ACPI_WRDD_WIFI_DATA_SIZE	2  #define ACPI_SPLC_WIFI_DATA_SIZE	2  #define ACPI_ECKV_WIFI_DATA_SIZE	2 + +/* + * One element for domain type, + * and one for enablement of Wi-Fi 320MHz per MCC + */ +#define ACPI_WBEM_WIFI_DATA_SIZE	2  /*   * One element for domain type,   * and one for the status @@ -94,6 +101,9 @@  #define ACPI_DSM_REV 0 +#define IWL_ACPI_WBEM_REV0_MASK (BIT(0) | BIT(1)) +#define IWL_ACPI_WBEM_REVISION 0 +  #ifdef CONFIG_ACPI  struct iwl_fw_runtime; @@ -142,6 +152,7 @@ void iwl_acpi_get_guid_lock_status(struct iwl_fw_runtime *fwrt);  int iwl_acpi_get_dsm(struct iwl_fw_runtime *fwrt,  		     enum iwl_dsm_funcs func, u32 *value); +int iwl_acpi_get_wbem(struct iwl_fw_runtime *fwrt, u32 *value);  #else /* CONFIG_ACPI */  static inline void *iwl_acpi_get_dsm_object(struct device *dev, int rev, @@ -205,6 +216,11 @@ static inline int iwl_acpi_get_dsm(struct iwl_fw_runtime *fwrt,  {  	return -ENOENT;  } + +static inline int iwl_acpi_get_wbem(struct iwl_fw_runtime *fwrt, u32 *value) +{ +	return -ENOENT; +}  #endif /* CONFIG_ACPI */  #endif /* __iwl_fw_acpi__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/d3.h b/drivers/net/wireless/intel/iwlwifi/fw/api/d3.h index d2a74beed3a1..bbaaf3c73115 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/d3.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/d3.h @@ -1,6 +1,6 @@  /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */  /* - * Copyright (C) 2012-2014, 2018-2023 Intel Corporation + * Copyright (C) 2012-2014, 2018-2024 Intel Corporation   * Copyright (C) 2013-2014 Intel Mobile Communications GmbH   * Copyright (C) 2015-2017 Intel Deutschland GmbH   */ @@ -843,6 +843,52 @@ struct iwl_wowlan_info_notif_v2 {  	u8 reserved2[2];  } __packed; /* WOWLAN_INFO_NTFY_API_S_VER_2 */ +/* MAX MLO keys of non-active links that can arrive in the notification */ +#define WOWLAN_MAX_MLO_KEYS 18 + +/** + * enum iwl_wowlan_mlo_gtk_type - GTK types + * @WOWLAN_MLO_GTK_KEY_TYPE_GTK: GTK + * @WOWLAN_MLO_GTK_KEY_TYPE_IGTK: IGTK + * @WOWLAN_MLO_GTK_KEY_TYPE_BIGTK: BIGTK + * @WOWLAN_MLO_GTK_KEY_NUM_TYPES: number of key types + */ +enum iwl_wowlan_mlo_gtk_type { +	WOWLAN_MLO_GTK_KEY_TYPE_GTK, +	WOWLAN_MLO_GTK_KEY_TYPE_IGTK, +	WOWLAN_MLO_GTK_KEY_TYPE_BIGTK, +	WOWLAN_MLO_GTK_KEY_NUM_TYPES +}; /* WOWLAN_MLO_GTK_KEY_TYPE_API_E_VER_1 */ + +/** + * enum iwl_wowlan_mlo_gtk_flag - MLO GTK flags + * @WOWLAN_MLO_GTK_FLAG_KEY_LEN_MSK: 0 for len 16, 1 for len 32 + * @WOWLAN_MLO_GTK_FLAG_KEY_ID_MSK: key id (ranges from 0 to 7) + * @WOWLAN_MLO_GTK_FLAG_LINK_ID_MSK: spec link id of the key + * @WOWLAN_MLO_GTK_FLAG_KEY_TYPE_MSK: &enum iwl_wowlan_mlo_gtk_type + * @WOWLAN_MLO_GTK_FLAG_LAST_KEY_MSK: is this the last given key per + *	key-type / link-id - the currently used key + */ +enum iwl_wowlan_mlo_gtk_flag { +	WOWLAN_MLO_GTK_FLAG_KEY_LEN_MSK = 0x0001, +	WOWLAN_MLO_GTK_FLAG_KEY_ID_MSK = 0x000E, +	WOWLAN_MLO_GTK_FLAG_LINK_ID_MSK = 0x00F0, +	WOWLAN_MLO_GTK_FLAG_KEY_TYPE_MSK = 0x0300, +	WOWLAN_MLO_GTK_FLAG_LAST_KEY_MSK = 0x0400 +}; /* WOWLAN_MLO_GTK_FLAG_API_E_VER_1 */ + +/** + * struct iwl_wowlan_mlo_gtk - MLO GTK info + * @key: key material + * @flags: &enum iwl_wowlan_mlo_gtk_flag + * @pn: packet number + */ +struct iwl_wowlan_mlo_gtk { +	u8 key[WOWLAN_KEY_MAX_SIZE]; +	__le16 flags; +	u8 pn[6]; +} __packed; /* WOWLAN_MLO_GTK_KEY_API_S_VER_1 */ +  /**   * struct iwl_wowlan_info_notif - WoWLAN information notification   * @gtk: GTK data @@ -859,7 +905,10 @@ struct iwl_wowlan_info_notif_v2 {   * @tid_tear_down: bit mask of tids whose BA sessions were closed   *	in suspend state   * @station_id: station id + * @num_mlo_link_keys: number of &struct iwl_wowlan_mlo_gtk structs + *	following this notif, or reserved in version < 4   * @reserved2: reserved + * @mlo_gtks: array of GTKs of size num_mlo_link_keys for version >= 4   */  struct iwl_wowlan_info_notif {  	struct iwl_wowlan_gtk_status_v3 gtk[WOWLAN_GTK_KEYS_NUM]; @@ -875,8 +924,10 @@ struct iwl_wowlan_info_notif {  	__le32 received_beacons;  	u8 tid_tear_down;  	u8 station_id; -	u8 reserved2[2]; -} __packed; /* WOWLAN_INFO_NTFY_API_S_VER_3 */ +	u8 num_mlo_link_keys; +	u8 reserved2; +	struct iwl_wowlan_mlo_gtk mlo_gtks[]; +} __packed; /* WOWLAN_INFO_NTFY_API_S_VER_3, _VER_4 */  /**   * struct iwl_wowlan_wake_pkt_notif - WoWLAN wake packet notification diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h b/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h index 0f7903c5a4df..f272b6a4e72e 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/datapath.h @@ -1,5 +1,6 @@  /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */  /* + * Copyright (C) 2024 Intel Corporation   * Copyright (C) 2012-2014, 2018-2022 Intel Corporation   * Copyright (C) 2013-2015 Intel Mobile Communications GmbH   * Copyright (C) 2016-2017 Intel Deutschland GmbH @@ -90,6 +91,12 @@ enum iwl_data_path_subcmd_ids {  	SEC_KEY_CMD = 0x18,  	/** +	 * @ESR_MODE_NOTIF: notification to recommend/force a wanted esr mode, +	 *	uses &struct iwl_mvm_esr_mode_notif +	 */ +	ESR_MODE_NOTIF = 0xF3, + +	/**  	 * @MONITOR_NOTIF: Datapath monitoring notification, using  	 *	&struct iwl_datapath_monitor_notif  	 */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h index c6d1f5644638..754c5d655ad0 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/mac-cfg.h @@ -1,6 +1,6 @@  /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */  /* - * Copyright (C) 2012-2014, 2018-2019, 2021-2023 Intel Corporation + * Copyright (C) 2012-2014, 2018-2019, 2021-2024 Intel Corporation   * Copyright (C) 2013-2015 Intel Mobile Communications GmbH   * Copyright (C) 2016-2017 Intel Deutschland GmbH   */ @@ -642,4 +642,25 @@ struct iwl_mvm_sta_disable_tx_cmd {  	__le32 disable;  } __packed; /* STA_DISABLE_TX_API_S_VER_1 */ +/** + * enum iwl_mvm_fw_esr_recommendation - FW recommendation code + * @ESR_RECOMMEND_LEAVE: recommendation to leave esr + * @ESR_FORCE_LEAVE: force exiting esr + * @ESR_RECOMMEND_ENTER: recommendation to enter esr + */ +enum iwl_mvm_fw_esr_recommendation { +	ESR_RECOMMEND_LEAVE, +	ESR_FORCE_LEAVE, +	ESR_RECOMMEND_ENTER, +}; /* ESR_MODE_RECOMMENDATION_CODE_API_E_VER_1 */ + +/** + * struct iwl_mvm_esr_mode_notif - FWs recommendation/force for esr mode + * + * @action: the action to apply on esr state. See &iwl_mvm_fw_esr_recommendation + */ +struct iwl_mvm_esr_mode_notif { +	__le32 action; +} __packed; /* ESR_MODE_RECOMMENDATION_NTFY_API_S_VER_1 */ +  #endif /* __iwl_fw_api_mac_cfg_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 index 58034dfa7e70..a08497a04733 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/nvm-reg.h @@ -7,7 +7,6 @@  #ifndef __iwl_fw_api_nvm_reg_h__  #define __iwl_fw_api_nvm_reg_h__ -#include "fw/regulatory.h"  /**   * enum iwl_regulatory_and_nvm_subcmd_ids - regulatory/NVM commands   */ @@ -23,8 +22,9 @@ enum iwl_regulatory_and_nvm_subcmd_ids {  	 *	&struct iwl_lari_config_change_cmd_v3,  	 *	&struct iwl_lari_config_change_cmd_v4,  	 *	&struct iwl_lari_config_change_cmd_v5, -	 *	&struct iwl_lari_config_change_cmd_v6 or -	 *	&struct iwl_lari_config_change_cmd_v7 +	 *	&struct iwl_lari_config_change_cmd_v6, +	 *	&struct iwl_lari_config_change_cmd_v7 or +	 *	&struct iwl_lari_config_change_cmd  	 */  	LARI_CONFIG_CHANGE = 0x1, @@ -46,9 +46,9 @@ enum iwl_regulatory_and_nvm_subcmd_ids {  	SAR_OFFSET_MAPPING_TABLE_CMD = 0x4,  	/** -	 * @UATS_TABLE_CMD: &struct iwl_uats_table_cmd +	 * @MCC_ALLOWED_AP_TYPE_CMD: &struct iwl_mcc_allowed_ap_type_cmd  	 */ -	UATS_TABLE_CMD = 0x5, +	MCC_ALLOWED_AP_TYPE_CMD = 0x5,  	/**  	 * @PNVM_INIT_COMPLETE_NTFY: &struct iwl_pnvm_init_complete_ntfy @@ -439,6 +439,7 @@ enum iwl_mcc_source {  	MCC_SOURCE_GETTING_MCC_TEST_MODE = 0x11,  }; +#define IWL_WTAS_BLACK_LIST_MAX		16  /**   * struct iwl_tas_config_cmd_common - configures the TAS.   * This is also the v2 structure. @@ -609,7 +610,7 @@ struct iwl_lari_config_change_cmd_v6 {  /**   * struct iwl_lari_config_change_cmd_v7 - change LARI configuration - * This structure is used also for lari cmd version 8. + * This structure is used also for lari cmd version 8 and 9.   * @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. @@ -619,6 +620,8 @@ struct iwl_lari_config_change_cmd_v6 {   * @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. + *     For LARI cmd version 4 to 8 - bits 0:3 are supported. + *     For LARI cmd version 9 - bits 0:5 are supported.   * @chan_state_active_bitmap: Bitmap to enable different bands per country   *     or region.   *     Each bit represents a country or region, and a band to activate @@ -642,6 +645,46 @@ struct iwl_lari_config_change_cmd_v7 {  } __packed;  /* LARI_CHANGE_CONF_CMD_S_VER_7 */  /* LARI_CHANGE_CONF_CMD_S_VER_8 */ +/* LARI_CHANGE_CONF_CMD_S_VER_9 */ + +/** + * struct iwl_lari_config_change_cmd - 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. + *	For LARI cmd version 10 - bits 0:5 are supported. + * @chan_state_active_bitmap: Bitmap to enable different bands per country + *	or region. + *	Each bit represents a country or region, and a band to activate + *	according to the BIOS definitions. + *	For LARI cmd version 10 - bits 0:4 are supported. + * @force_disable_channels_bitmap: Bitmap of disabled bands/channels. + *	Each bit represents a set of channels in a specific band that should be + *	disabled + * @edt_bitmap: Bitmap of energy detection threshold table. + *	Disable/enable the EDT optimization method for different band. + * @oem_320mhz_allow_bitmap: 320Mhz bandwidth enablement bitmap per MCC. + *	bit0: enable 320Mhz in Japan. + *	bit1: enable 320Mhz in South Korea. + *	bit 2 - 31: reserved. + */ +struct iwl_lari_config_change_cmd { +	__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; +	__le32 edt_bitmap; +	__le32 oem_320mhz_allow_bitmap; +} __packed; +/* LARI_CHANGE_CONF_CMD_S_VER_10 */  /* Activate UNII-1 (5.2GHz) for World Wide */  #define ACTIVATE_5G2_IN_WW_MASK	BIT(4) @@ -658,13 +701,13 @@ struct iwl_pnvm_init_complete_ntfy {  #define UATS_TABLE_COL_SIZE	13  /** - * struct iwl_uats_table_cmd - struct for UATS_TABLE_CMD + * struct iwl_mcc_allowed_ap_type_cmd - struct for MCC_ALLOWED_AP_TYPE_CMD   * @offset_map: mapping a mcc to UHB AP type support (UATS) allowed   * @reserved: reserved   */ -struct iwl_uats_table_cmd { +struct iwl_mcc_allowed_ap_type_cmd {  	u8 offset_map[UATS_TABLE_ROW_SIZE][UATS_TABLE_COL_SIZE];  	__le16 reserved; -} __packed; /* UATS_TABLE_CMD_S_VER_1 */ +} __packed; /* MCC_ALLOWED_AP_TYPE_CMD_API_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 index 2d2b9c8c36ea..2ed7acc09e5a 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/offload.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/offload.h @@ -3,7 +3,7 @@   * Copyright (C) 2012-2014 Intel Corporation   * Copyright (C) 2013-2015 Intel Mobile Communications GmbH   * Copyright (C) 2016-2017 Intel Deutschland GmbH - * Copyright (C) 2021-2023 Intel Corporation + * Copyright (C) 2021-2024 Intel Corporation   */  #ifndef __iwl_fw_api_offload_h__  #define __iwl_fw_api_offload_h__ @@ -20,7 +20,7 @@ enum iwl_prot_offload_subcmd_ids {  	/**  	 * @WOWLAN_INFO_NOTIFICATION: Notification in  	 * &struct iwl_wowlan_info_notif_v1, &struct iwl_wowlan_info_notif_v2, -	 * or iwl_wowlan_info_notif +	 * or &struct iwl_wowlan_info_notif  	 */  	WOWLAN_INFO_NOTIFICATION = 0xFD, diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/phy.h b/drivers/net/wireless/intel/iwlwifi/fw/api/phy.h index 5a3f30e5e06d..92e4b62c119f 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/phy.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/phy.h @@ -1,6 +1,6 @@  /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */  /* - * Copyright (C) 2012-2014, 2019-2022 Intel Corporation + * Copyright (C) 2012-2014, 2019-2022, 2024 Intel Corporation   * Copyright (C) 2013-2015 Intel Mobile Communications GmbH   * Copyright (C) 2016-2017 Intel Deutschland GmbH   */ @@ -43,6 +43,11 @@ enum iwl_phy_ops_subcmd_ids {  	PER_PLATFORM_ANT_GAIN_CMD = 0x07,  	/** +	 * @AP_TX_POWER_CONSTRAINTS_CMD: &struct iwl_txpower_constraints_cmd +	 */ +	AP_TX_POWER_CONSTRAINTS_CMD = 0x0C, + +	/**  	 * @CT_KILL_NOTIFICATION: &struct ct_kill_notif  	 */  	CT_KILL_NOTIFICATION = 0xFE, diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/power.h b/drivers/net/wireless/intel/iwlwifi/fw/api/power.h index 0bf38243f88a..532d5cfa9162 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/power.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/power.h @@ -1,6 +1,6 @@  /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */  /* - * Copyright (C) 2012-2014, 2018-2023 Intel Corporation + * Copyright (C) 2012-2014, 2018-2024 Intel Corporation   * Copyright (C) 2013-2014 Intel Mobile Communications GmbH   * Copyright (C) 2015-2017 Intel Deutschland GmbH   */ @@ -385,6 +385,33 @@ struct iwl_dev_tx_power_cmd_v7 {  	__le32 timer_period;  	__le32 flags;  } __packed; /* TX_REDUCED_POWER_API_S_VER_7 */ + +/** + * struct iwl_dev_tx_power_cmd_v8 - TX power reduction command version 8 + * @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. + * @tpc_vlp_backoff_level: user backoff of UNII5,7 VLP channels in USA. + *	Not in use. + */ +struct iwl_dev_tx_power_cmd_v8 { +	__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; +	__le32 tpc_vlp_backoff_level; +} __packed; /* TX_REDUCED_POWER_API_S_VER_8 */ +  /**   * struct iwl_dev_tx_power_cmd - TX power reduction command (multiversion)   * @common: common part of the command @@ -392,6 +419,8 @@ struct iwl_dev_tx_power_cmd_v7 {   * @v4: version 4 part of the command   * @v5: version 5 part of the command   * @v6: version 6 part of the command + * @v7: version 7 part of the command + * @v8: version 8 part of the command   */  struct iwl_dev_tx_power_cmd {  	struct iwl_dev_tx_power_common common; @@ -401,6 +430,7 @@ struct iwl_dev_tx_power_cmd {  		struct iwl_dev_tx_power_cmd_v5 v5;  		struct iwl_dev_tx_power_cmd_v6 v6;  		struct iwl_dev_tx_power_cmd_v7 v7; +		struct iwl_dev_tx_power_cmd_v8 v8;  	};  }; @@ -537,7 +567,7 @@ enum iwl_ppag_flags {   * union iwl_ppag_table_cmd - union for all versions of PPAG command   * @v1: version 1   * @v2: version 2 - * version 3, 4 and 5 are the same structure as v2, + * version 3, 4, 5 and 6 are the same structure as v2,   *	but has a different format of the flags bitmap   * @flags: values from &enum iwl_ppag_flags   * @gain: table of antenna gain values per chain and sub-band @@ -702,4 +732,44 @@ struct iwl_beacon_filter_cmd {  #define IWL_BF_CMD_CONFIG_DEFAULTS IWL_BF_CMD_CONFIG(_DEFAULT)  #define IWL_BF_CMD_CONFIG_D0I3 IWL_BF_CMD_CONFIG(_D0I3) + +#define DEFAULT_TPE_TX_POWER 0x7F + +/* + *  Bandwidth: 20/40/80/(160/80+80)/320 + */ +#define IWL_MAX_TX_EIRP_PWR_MAX_SIZE 5 +#define IWL_MAX_TX_EIRP_PSD_PWR_MAX_SIZE 16 + +enum iwl_6ghz_ap_type { +	IWL_6GHZ_AP_TYPE_LPI, +	IWL_6GHZ_AP_TYPE_SP, +	IWL_6GHZ_AP_TYPE_VLP, +}; /* PHY_AP_TYPE_API_E_VER_1 */ + +/** + * struct iwl_txpower_constraints_cmd + * AP_TX_POWER_CONSTRAINTS_CMD + * Used for VLP/LPI/AFC Access Point power constraints for 6GHz channels + * @link_id: linkId + * @ap_type: see &enum iwl_ap_type + * @eirp_pwr: 8-bit 2s complement signed integer in the range + *	-64 dBm to 63 dBm with a 0.5 dB step + *	default &DEFAULT_TPE_TX_POWER (no maximum limit) + * @psd_pwr: 8-bit 2s complement signed integer in the range + *	-63.5 to +63 dBm/MHz with a 0.5 step + *	value - 128 indicates that the corresponding 20 + *	MHz channel cannot be used for transmission. + *	value +127 indicates that no maximum PSD limit + *	is specified for the corresponding 20 MHz channel + *	default &DEFAULT_TPE_TX_POWER (no maximum limit) + * @reserved: reserved (padding) + */ +struct iwl_txpower_constraints_cmd { +	__le16 link_id; +	__le16 ap_type; +	__s8 eirp_pwr[IWL_MAX_TX_EIRP_PWR_MAX_SIZE]; +	__s8 psd_pwr[IWL_MAX_TX_EIRP_PSD_PWR_MAX_SIZE]; +	u8 reserved[3]; +} __packed; /* PHY_AP_TX_POWER_CONSTRAINTS_CMD_API_S_VER_1 */  #endif /* __iwl_fw_api_power_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/scan.h b/drivers/net/wireless/intel/iwlwifi/fw/api/scan.h index 93078f8cc08c..6684506f4fc4 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/scan.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/scan.h @@ -1,6 +1,6 @@  /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */  /* - * Copyright (C) 2012-2014, 2018-2023 Intel Corporation + * Copyright (C) 2012-2014, 2018-2024 Intel Corporation   * Copyright (C) 2013-2015 Intel Mobile Communications GmbH   * Copyright (C) 2016-2017 Intel Deutschland GmbH   */ @@ -14,6 +14,10 @@   */  enum iwl_scan_subcmd_ids {  	/** +	 * @CHANNEL_SURVEY_NOTIF: &struct iwl_umac_scan_channel_survey_notif +	 */ +	CHANNEL_SURVEY_NOTIF = 0xFB, +	/**  	 * @OFFLOAD_MATCH_INFO_NOTIF: &struct iwl_scan_offload_match_info  	 */  	OFFLOAD_MATCH_INFO_NOTIF = 0xFC, @@ -62,6 +66,8 @@ struct iwl_ssid_ie {  #define IWL_FAST_SCHED_SCAN_ITERATIONS 3  #define IWL_MAX_SCHED_SCAN_PLANS 2 +#define IWL_MAX_NUM_NOISE_RESULTS 22 +  enum scan_framework_client {  	SCAN_CLIENT_SCHED_SCAN		= BIT(0),  	SCAN_CLIENT_NETDETECT		= BIT(1), @@ -642,10 +648,13 @@ enum iwl_umac_scan_general_flags {   *	notification per channel or not.   * @IWL_UMAC_SCAN_GEN_FLAGS2_ALLOW_CHNL_REORDER: Whether to allow channel   *	reorder optimization or not. + * @IWL_UMAC_SCAN_GEN_FLAGS2_COLLECT_CHANNEL_STATS: Enable channel statistics + *	collection when #IWL_UMAC_SCAN_GEN_FLAGS_V2_FORCE_PASSIVE is set.   */  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), +	IWL_UMAC_SCAN_GEN_FLAGS2_COLLECT_CHANNEL_STATS	= BIT(3),  };  /** @@ -1258,4 +1267,26 @@ struct iwl_umac_scan_iter_complete_notif {  	struct iwl_scan_results_notif results[];  } __packed; /* SCAN_ITER_COMPLETE_NTF_UMAC_API_S_VER_2 */ +/** + * struct iwl_umac_scan_channel_survey_notif - data for survey + * @channel: the channel scanned + * @band: band of channel + * @noise: noise floor measurements in negative dBm, invalid 0xff + * @reserved: for future use and alignment + * @active_time: time in ms the radio was turned on (on the channel) + * @busy_time: time in ms the channel was sensed busy, 0 for a clean channel + * @tx_time: time the radio spent transmitting data + * @rx_time: time the radio spent receiving data + */ +struct iwl_umac_scan_channel_survey_notif { +	__le32 channel; +	__le32 band; +	u8 noise[IWL_MAX_NUM_NOISE_RESULTS]; +	u8 reserved[2]; +	__le32 active_time; +	__le32 busy_time; +	__le32 tx_time; +	__le32 rx_time; +} __packed; /* SCAN_CHANNEL_SURVEY_NTF_API_S_VER_1 */ +  #endif /* __iwl_fw_api_scan_h__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/fw/api/tx.h b/drivers/net/wireless/intel/iwlwifi/fw/api/tx.h index d9e4c75403b8..bbd176d88820 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/api/tx.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/api/tx.h @@ -1,6 +1,6 @@  /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */  /* - * Copyright (C) 2012-2014, 2018-2023 Intel Corporation + * Copyright (C) 2012-2014, 2018-2024 Intel Corporation   * Copyright (C) 2016-2017 Intel Deutschland GmbH   */  #ifndef __iwl_fw_api_tx_h__ @@ -793,7 +793,8 @@ enum iwl_mac_beacon_flags {   * @reserved: reserved   * @link_id: the firmware id of the link that will use this beacon   * @tim_idx: the offset of the tim IE in the beacon - * @tim_size: the length of the tim IE + * @tim_size: the length of the tim IE (version < 14) + * @btwt_offset: offset to the broadcast TWT IE if present (version >= 14)   * @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 @@ -805,14 +806,18 @@ struct iwl_mac_beacon_cmd {  	__le32 reserved;  	__le32 link_id;  	__le32 tim_idx; -	__le32 tim_size; +	union { +		__le32 tim_size; +		__le32 btwt_offset; +	};  	__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, -	     * BEACON_TEMPLATE_CMD_API_S_VER_13 +	     * BEACON_TEMPLATE_CMD_API_S_VER_13, +	     * BEACON_TEMPLATE_CMD_API_S_VER_14  	     */  struct iwl_beacon_notif { diff --git a/drivers/net/wireless/intel/iwlwifi/fw/dbg.c b/drivers/net/wireless/intel/iwlwifi/fw/dbg.c index c3bdf433d8f7..945ffc083d25 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/dbg.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/dbg.c @@ -1026,17 +1026,12 @@ static int iwl_dump_ini_prph_mac_iter_common(struct iwl_fw_runtime *fwrt,  {  	struct iwl_fw_ini_error_dump_range *range = range_ptr;  	__le32 *val = range->data; -	u32 prph_val;  	int i;  	range->internal_base_addr = cpu_to_le32(addr);  	range->range_data_size = size; -	for (i = 0; i < le32_to_cpu(size); i += 4) { -		prph_val = iwl_read_prph(fwrt->trans, addr + i); -		if (iwl_trans_is_hw_error_value(prph_val)) -			return -EBUSY; -		*val++ = cpu_to_le32(prph_val); -	} +	for (i = 0; i < le32_to_cpu(size); i += 4) +		*val++ = cpu_to_le32(iwl_read_prph(fwrt->trans, addr + i));  	return sizeof(*range) + le32_to_cpu(range->range_data_size);  } @@ -3084,6 +3079,7 @@ static void iwl_fw_dbg_collect_sync(struct iwl_fw_runtime *fwrt, u8 wk_idx)  	if (!test_bit(wk_idx, &fwrt->dump.active_wks))  		return; +	/* also checks 'desc' for pre-ini mode, since that shadows in union */  	if (!dump_data->trig) {  		IWL_ERR(fwrt, "dump trigger data is not set\n");  		goto out; diff --git a/drivers/net/wireless/intel/iwlwifi/fw/file.h b/drivers/net/wireless/intel/iwlwifi/fw/file.h index f69d29e531c8..ae05227b6153 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/file.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/file.h @@ -395,6 +395,8 @@ typedef unsigned int __bitwise iwl_ucode_tlv_capa_t;   * @IWL_UCODE_TLV_CAPA_SPP_AMSDU_SUPPORT: Support SPP (signaling and payload   *	protected) A-MSDU.   * @IWL_UCODE_TLV_CAPA_SECURE_LTF_SUPPORT: Support secure LTF measurement. + * @IWL_UCODE_TLV_CAPA_MONITOR_PASSIVE_CHANS: Support monitor mode on otherwise + *	passive channels   *   * @NUM_IWL_UCODE_TLV_CAPA: number of bits used   */ @@ -494,6 +496,7 @@ enum iwl_ucode_tlv_capa {  	IWL_UCODE_TLV_CAPA_SNIFF_VALIDATE_SUPPORT	= (__force iwl_ucode_tlv_capa_t)116,  	IWL_UCODE_TLV_CAPA_CHINA_22_REG_SUPPORT		= (__force iwl_ucode_tlv_capa_t)117,  	IWL_UCODE_TLV_CAPA_SECURE_LTF_SUPPORT		= (__force iwl_ucode_tlv_capa_t)121, +	IWL_UCODE_TLV_CAPA_MONITOR_PASSIVE_CHANS	= (__force iwl_ucode_tlv_capa_t)122,  	NUM_IWL_UCODE_TLV_CAPA  /*   * This construction make both sparse (which cannot increment the previous diff --git a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c index 36d506463e0e..b9bb3636e88f 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.c @@ -38,6 +38,7 @@ IWL_BIOS_TABLE_LOADER_DATA(tas_table, struct iwl_tas_data);  IWL_BIOS_TABLE_LOADER_DATA(pwr_limit, u64);  IWL_BIOS_TABLE_LOADER_DATA(mcc, char);  IWL_BIOS_TABLE_LOADER_DATA(eckv, u32); +IWL_BIOS_TABLE_LOADER_DATA(wbem, u32);  static const struct dmi_system_id dmi_ppag_approved_list[] = { @@ -347,7 +348,7 @@ int iwl_fill_ppag_table(struct iwl_fw_runtime *fwrt,  					"PPAG table rev is %d, send truncated table\n",  					fwrt->ppag_ver);  		} -	} else if (cmd_ver >= 2 && cmd_ver <= 5) { +	} else if (cmd_ver >= 2 && cmd_ver <= 6) {  		num_sub_bands = IWL_NUM_SUB_BANDS_V2;  		gain = cmd->v2.gain[0];  		*cmd_size = sizeof(cmd->v2); @@ -443,7 +444,7 @@ int iwl_parse_tas_selection(struct iwl_fw_runtime *fwrt,  	return enabled;  } -__le32 iwl_get_lari_config_bitmap(struct iwl_fw_runtime *fwrt) +static __le32 iwl_get_lari_config_bitmap(struct iwl_fw_runtime *fwrt)  {  	int ret;  	u32 val; @@ -490,7 +491,127 @@ __le32 iwl_get_lari_config_bitmap(struct iwl_fw_runtime *fwrt)  	return config_bitmap;  } -IWL_EXPORT_SYMBOL(iwl_get_lari_config_bitmap); + +static size_t iwl_get_lari_config_cmd_size(u8 cmd_ver) +{ +	size_t cmd_size; + +	switch (cmd_ver) { +	case 10: +		cmd_size = sizeof(struct iwl_lari_config_change_cmd); +		break; +	case 9: +	case 8: +	case 7: +		cmd_size = sizeof(struct iwl_lari_config_change_cmd_v7); +		break; +	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; +	} +	return cmd_size; +} + +int iwl_fill_lari_config(struct iwl_fw_runtime *fwrt, +			 struct iwl_lari_config_change_cmd *cmd, +			 size_t *cmd_size) +{ +	int ret; +	u32 value; +	u8 cmd_ver = iwl_fw_lookup_cmd_ver(fwrt->fw, +					   WIDE_ID(REGULATORY_AND_NVM_GROUP, +						   LARI_CONFIG_CHANGE), 1); + +	memset(cmd, 0, sizeof(*cmd)); +	*cmd_size = iwl_get_lari_config_cmd_size(cmd_ver); + +	cmd->config_bitmap = iwl_get_lari_config_bitmap(fwrt); + +	ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_11AX_ENABLEMENT, &value); +	if (!ret) +		cmd->oem_11ax_allow_bitmap = cpu_to_le32(value); + +	ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_ENABLE_UNII4_CHAN, &value); +	if (!ret) { +		if (cmd_ver < 9) +			value &= DSM_UNII4_ALLOW_BITMAP_CMD_V8; +		else +			value &= DSM_UNII4_ALLOW_BITMAP; + +		cmd->oem_unii4_allow_bitmap = cpu_to_le32(value); +	} + +	ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_ACTIVATE_CHANNEL, &value); +	if (!ret) { +		if (cmd_ver < 8) +			value &= ~ACTIVATE_5G2_IN_WW_MASK; +		cmd->chan_state_active_bitmap = cpu_to_le32(value); +	} + +	ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_ENABLE_6E, &value); +	if (!ret) +		cmd->oem_uhb_allow_bitmap = cpu_to_le32(value); + +	ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_FORCE_DISABLE_CHANNELS, &value); +	if (!ret) +		cmd->force_disable_channels_bitmap = cpu_to_le32(value); + +	ret = iwl_bios_get_dsm(fwrt, DSM_FUNC_ENERGY_DETECTION_THRESHOLD, +			       &value); +	if (!ret) +		cmd->edt_bitmap = cpu_to_le32(value); + +	ret = iwl_bios_get_wbem(fwrt, &value); +	if (!ret) +		cmd->oem_320mhz_allow_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 || +	    cmd->edt_bitmap || +	    cmd->oem_320mhz_allow_bitmap) { +		IWL_DEBUG_RADIO(fwrt, +				"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(fwrt, +				"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(fwrt, +				"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)); +		IWL_DEBUG_RADIO(fwrt, +				"sending LARI_CONFIG_CHANGE, edt_bitmap=0x%x, oem_320mhz_allow_bitmap=0x%x\n", +				le32_to_cpu(cmd->edt_bitmap), +				le32_to_cpu(cmd->oem_320mhz_allow_bitmap)); +	} else { +		return 1; +	} + +	return 0; +} +IWL_EXPORT_SYMBOL(iwl_fill_lari_config);  int iwl_bios_get_dsm(struct iwl_fw_runtime *fwrt, enum iwl_dsm_funcs func,  		     u32 *value) diff --git a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h index 28e774766847..633c9ad9af84 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/regulatory.h @@ -1,6 +1,6 @@  /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */  /* - * Copyright (C) 2023 Intel Corporation + * Copyright (C) 2023-2024 Intel Corporation   */  #ifndef __fw_regulatory_h__ @@ -11,6 +11,7 @@  #include "fw/api/power.h"  #include "fw/api/phy.h"  #include "fw/api/config.h" +#include "fw/api/nvm-reg.h"  #include "fw/img.h"  #include "iwl-trans.h" @@ -39,7 +40,6 @@  #define IWL_PPAG_ETSI_CHINA_MASK	3  #define IWL_PPAG_REV3_MASK		0x7FF -#define IWL_WTAS_BLACK_LIST_MAX	16  #define IWL_WTAS_ENABLED_MSK		0x1  #define IWL_WTAS_OVERRIDE_IEC_MSK	0x2  #define IWL_WTAS_ENABLE_IEC_MSK	0x4 @@ -132,6 +132,23 @@ enum iwl_dsm_values_indonesia {  	DSM_VALUE_INDONESIA_MAX  }; +enum iwl_dsm_unii4_bitmap { +	DSM_VALUE_UNII4_US_OVERRIDE_MSK		= BIT(0), +	DSM_VALUE_UNII4_US_EN_MSK		= BIT(1), +	DSM_VALUE_UNII4_ETSI_OVERRIDE_MSK	= BIT(2), +	DSM_VALUE_UNII4_ETSI_EN_MSK		= BIT(3), +	DSM_VALUE_UNII4_CANADA_OVERRIDE_MSK	= BIT(4), +	DSM_VALUE_UNII4_CANADA_EN_MSK		= BIT(5), +}; + +#define DSM_UNII4_ALLOW_BITMAP_CMD_V8 (DSM_VALUE_UNII4_US_OVERRIDE_MSK | \ +				       DSM_VALUE_UNII4_US_EN_MSK | \ +				       DSM_VALUE_UNII4_ETSI_OVERRIDE_MSK | \ +				       DSM_VALUE_UNII4_ETSI_EN_MSK) +#define DSM_UNII4_ALLOW_BITMAP (DSM_UNII4_ALLOW_BITMAP_CMD_V8 | \ +				DSM_VALUE_UNII4_CANADA_OVERRIDE_MSK | \ +				DSM_VALUE_UNII4_CANADA_EN_MSK) +  enum iwl_dsm_values_rfi {  	DSM_VALUE_RFI_DLVR_DISABLE	= BIT(0),  	DSM_VALUE_RFI_DDR_DISABLE	= BIT(1), @@ -184,8 +201,11 @@ int iwl_bios_get_pwr_limit(struct iwl_fw_runtime *fwrt,  int iwl_bios_get_mcc(struct iwl_fw_runtime *fwrt, char *mcc);  int iwl_bios_get_eckv(struct iwl_fw_runtime *fwrt, u32 *ext_clk); +int iwl_bios_get_wbem(struct iwl_fw_runtime *fwrt, u32 *value); -__le32 iwl_get_lari_config_bitmap(struct iwl_fw_runtime *fwrt); +int iwl_fill_lari_config(struct iwl_fw_runtime *fwrt, +			 struct iwl_lari_config_change_cmd *cmd, +			 size_t *cmd_size);  int iwl_bios_get_dsm(struct iwl_fw_runtime *fwrt, enum iwl_dsm_funcs func,  		     u32 *value); diff --git a/drivers/net/wireless/intel/iwlwifi/fw/runtime.h b/drivers/net/wireless/intel/iwlwifi/fw/runtime.h index b2bc4fd37abf..9122f9a1260a 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/runtime.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/runtime.h @@ -46,6 +46,10 @@ struct iwl_fwrt_shared_mem_cfg {   * struct iwl_fwrt_dump_data - dump data   * @trig: trigger the worker was scheduled upon   * @fw_pkt: packet received from FW + * + * Note that the decision which part of the union is used + * is based on iwl_trans_dbg_ini_valid(): the 'trig' part + * is used if it is %true, the 'desc' part otherwise.   */  struct iwl_fwrt_dump_data {  	union { @@ -54,6 +58,7 @@ struct iwl_fwrt_dump_data {  			struct iwl_rx_packet *fw_pkt;  		};  		struct { +			/* must be first to be same as 'trig' */  			const struct iwl_fw_dump_desc *desc;  			bool monitor_only;  		}; @@ -177,7 +182,7 @@ struct iwl_fw_runtime {  	u8 ppag_ver;  	struct iwl_sar_offset_mapping_cmd sgom_table;  	bool sgom_enabled; -	struct iwl_uats_table_cmd uats_table; +	struct iwl_mcc_allowed_ap_type_cmd uats_table;  	u8 uefi_tables_lock_status;  	bool uats_enabled;  }; diff --git a/drivers/net/wireless/intel/iwlwifi/fw/uefi.c b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c index e81fc0129b9d..fb982d4fe851 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/uefi.c +++ b/drivers/net/wireless/intel/iwlwifi/fw/uefi.c @@ -674,6 +674,29 @@ out:  	return ret;  } +int iwl_uefi_get_wbem(struct iwl_fw_runtime *fwrt, u32 *value) +{ +	struct uefi_cnv_wlan_wbem_data *data; +	int ret = 0; + +	data = iwl_uefi_get_verified_variable(fwrt->trans, IWL_UEFI_WBEM_NAME, +					      "WBEM", sizeof(*data), NULL); +	if (IS_ERR(data)) +		return -EINVAL; + +	if (data->revision != IWL_UEFI_WBEM_REVISION) { +		ret = -EINVAL; +		IWL_DEBUG_RADIO(fwrt, "Unsupported UEFI WBEM revision:%d\n", +				data->revision); +		goto out; +	} +	*value = data->wbem_320mhz_per_mcc & IWL_UEFI_WBEM_REV0_MASK; +	IWL_DEBUG_RADIO(fwrt, "Loaded WBEM config from UEFI\n"); +out: +	kfree(data); +	return ret; +} +  int iwl_uefi_get_dsm(struct iwl_fw_runtime *fwrt, enum iwl_dsm_funcs func,  		     u32 *value)  { diff --git a/drivers/net/wireless/intel/iwlwifi/fw/uefi.h b/drivers/net/wireless/intel/iwlwifi/fw/uefi.h index 303cc299d1bc..1f8884ca8997 100644 --- a/drivers/net/wireless/intel/iwlwifi/fw/uefi.h +++ b/drivers/net/wireless/intel/iwlwifi/fw/uefi.h @@ -1,6 +1,6 @@  /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */  /* - * Copyright(c) 2021-2023 Intel Corporation + * Copyright(c) 2021-2024 Intel Corporation   */  #ifndef __iwl_fw_uefi__  #define __iwl_fw_uefi__ @@ -21,6 +21,7 @@  #define IWL_UEFI_WRDD_NAME		L"UefiCnvWlanWRDD"  #define IWL_UEFI_ECKV_NAME		L"UefiCnvWlanECKV"  #define IWL_UEFI_DSM_NAME		L"UefiCnvWlanGeneralCfg" +#define IWL_UEFI_WBEM_NAME		L"UefiCnvWlanWBEM"  #define IWL_SGOM_MAP_SIZE		339 @@ -35,6 +36,7 @@  #define IWL_UEFI_SPLC_REVISION		0  #define IWL_UEFI_WRDD_REVISION		0  #define IWL_UEFI_ECKV_REVISION		0 +#define IWL_UEFI_WBEM_REVISION		0  #define IWL_UEFI_DSM_REVISION		4  struct pnvm_sku_package { @@ -178,6 +180,20 @@ struct uefi_cnv_var_general_cfg {  	u32 functions[UEFI_MAX_DSM_FUNCS];  } __packed; +#define IWL_UEFI_WBEM_REV0_MASK (BIT(0) | BIT(1)) +/* struct uefi_cnv_wlan_wbem_data - Bandwidth enablement per MCC as defined + *	in UEFI + * @revision: the revision of the table + * @wbem_320mhz_per_mcc: enablement of 320MHz bandwidth per MCC + *	bit 0 - if set, 320MHz is enabled for Japan + *	bit 1 - if set, 320MHz is enabled for South Korea + *	bit 2- 31, Reserved + */ +struct uefi_cnv_wlan_wbem_data { +	u8 revision; +	u32 wbem_320mhz_per_mcc; +} __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 @@ -202,6 +218,7 @@ int iwl_uefi_get_pwr_limit(struct iwl_fw_runtime *fwrt,  			   u64 *dflt_pwr_limit);  int iwl_uefi_get_mcc(struct iwl_fw_runtime *fwrt, char *mcc);  int iwl_uefi_get_eckv(struct iwl_fw_runtime *fwrt, u32 *extl_clk); +int iwl_uefi_get_wbem(struct iwl_fw_runtime *fwrt, u32 *value);  int iwl_uefi_get_dsm(struct iwl_fw_runtime *fwrt, enum iwl_dsm_funcs func,  		     u32 *value);  void iwl_uefi_get_sgom_table(struct iwl_trans *trans, struct iwl_fw_runtime *fwrt); @@ -281,6 +298,11 @@ static inline int iwl_uefi_get_eckv(struct iwl_fw_runtime *fwrt, u32 *extl_clk)  	return -ENOENT;  } +static inline int iwl_uefi_get_wbem(struct iwl_fw_runtime *fwrt, u32 *value) +{ +	return -ENOENT; +} +  static inline int iwl_uefi_get_dsm(struct iwl_fw_runtime *fwrt,  				   enum iwl_dsm_funcs func, u32 *value)  { diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-config.h b/drivers/net/wireless/intel/iwlwifi/iwl-config.h index 6aa4f7f9c708..732889f96ca2 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-config.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-config.h @@ -11,6 +11,7 @@  #include <linux/netdevice.h>  #include <linux/ieee80211.h>  #include <linux/nl80211.h> +#include <linux/mod_devicetable.h>  #include "iwl-csr.h"  #include "iwl-drv.h" @@ -421,6 +422,7 @@ struct iwl_cfg {  #define IWL_CFG_MAC_TYPE_SC		0x48  #define IWL_CFG_MAC_TYPE_SC2		0x49  #define IWL_CFG_MAC_TYPE_SC2F		0x4A +#define IWL_CFG_MAC_TYPE_BZ_W		0x4B  #define IWL_CFG_RF_TYPE_TH		0x105  #define IWL_CFG_RF_TYPE_TH1		0x108 @@ -429,8 +431,6 @@ struct iwl_cfg {  #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_TYPE_WH		0x113 @@ -484,6 +484,7 @@ 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, u8 rf_step); +extern const struct pci_device_id iwl_hw_card_ids[];  #endif  /* @@ -541,6 +542,8 @@ extern const char iwl_ax221_name[];  extern const char iwl_ax231_name[];  extern const char iwl_ax411_name[];  extern const char iwl_bz_name[]; +extern const char iwl_fm_name[]; +extern const char iwl_gl_name[];  extern const char iwl_mtp_name[];  extern const char iwl_sc_name[];  extern const char iwl_sc2_name[]; diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-context-info-gen3.h b/drivers/net/wireless/intel/iwlwifi/iwl-context-info-gen3.h index 1379dc2d231b..5b62933134cf 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-context-info-gen3.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-context-info-gen3.h @@ -1,6 +1,6 @@  /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */  /* - * Copyright (C) 2018, 2020-2023 Intel Corporation + * Copyright (C) 2018, 2020-2024 Intel Corporation   */  #ifndef __iwl_context_info_file_gen3_h__  #define __iwl_context_info_file_gen3_h__ @@ -56,6 +56,8 @@ enum iwl_prph_scratch_mtr_format {   * @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 + * @IWL_PRPH_SCRATCH_SCU_FORCE_ACTIVE: Indicate fw to set SCU_FORCE_ACTIVE + *	upon reset.   */  enum iwl_prph_scratch_flags {  	IWL_PRPH_SCRATCH_IMR_DEBUG_EN		= BIT(1), @@ -71,6 +73,7 @@ enum iwl_prph_scratch_flags {  	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, +	IWL_PRPH_SCRATCH_SCU_FORCE_ACTIVE	= BIT(29),  };  /* diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c index 4696d73c8971..33654f228ee8 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-drv.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-drv.c @@ -192,12 +192,6 @@ const char *iwl_drv_get_fwname_pre(struct iwl_trans *trans, char *buf)  	case IWL_CFG_RF_TYPE_GF:  		rf = "gf";  		break; -	case IWL_CFG_RF_TYPE_MR: -		rf = "mr"; -		break; -	case IWL_CFG_RF_TYPE_MS: -		rf = "ms"; -		break;  	case IWL_CFG_RF_TYPE_FM:  		rf = "fm";  		break; @@ -1484,7 +1478,6 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)  	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; @@ -1732,19 +1725,12 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)  			goto out_unbind;  		}  	} else { -		load_module = true; +		request_module_nowait("%s", op->name);  	}  	mutex_unlock(&iwlwifi_opmode_table_mtx);  	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; diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c index baa39a18087a..149903f52567 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c @@ -392,11 +392,14 @@ static enum nl80211_band iwl_nl80211_band_from_channel_idx(int ch_idx)  	return NL80211_BAND_2GHZ;  } -static int iwl_init_channel_map(struct device *dev, const struct iwl_cfg *cfg, +static int iwl_init_channel_map(struct iwl_trans *trans, +				const struct iwl_fw *fw,  				struct iwl_nvm_data *data,  				const void * const nvm_ch_flags,  				u32 sbands_flags, bool v4)  { +	const struct iwl_cfg *cfg = trans->cfg; +	struct device *dev = trans->dev;  	int ch_idx;  	int n_channels = 0;  	struct ieee80211_channel *channel; @@ -478,11 +481,10 @@ static int iwl_init_channel_map(struct device *dev, const struct iwl_cfg *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; +		if (fw_has_capa(&fw->ucode_capa, +				IWL_UCODE_TLV_CAPA_MONITOR_PASSIVE_CHANS)) +			channel->flags |= IEEE80211_CHAN_CAN_MONITOR; +  		iwl_nvm_print_channel_flags(dev, IWL_DL_EEPROM,  					    channel->hw_value, ch_flags);  		IWL_DEBUG_EEPROM(dev, "Ch. %d: %ddBm\n", @@ -597,7 +599,8 @@ static const u8 iwl_vendor_caps[] = {  static const struct ieee80211_sband_iftype_data iwl_he_eht_capa[] = {  	{ -		.types_mask = BIT(NL80211_IFTYPE_STATION), +		.types_mask = BIT(NL80211_IFTYPE_STATION) | +			      BIT(NL80211_IFTYPE_P2P_CLIENT),  		.he_cap = {  			.has_he = true,  			.he_cap_elem = { @@ -753,7 +756,8 @@ static const struct ieee80211_sband_iftype_data iwl_he_eht_capa[] = {  		},  	},  	{ -		.types_mask = BIT(NL80211_IFTYPE_AP), +		.types_mask = BIT(NL80211_IFTYPE_AP) | +			      BIT(NL80211_IFTYPE_P2P_GO),  		.he_cap = {  			.has_he = true,  			.he_cap_elem = { @@ -906,7 +910,8 @@ iwl_nvm_fixup_sband_iftd(struct iwl_trans *trans,  			 u8 tx_chains, u8 rx_chains,  			 const struct iwl_fw *fw)  { -	bool is_ap = iftype_data->types_mask & BIT(NL80211_IFTYPE_AP); +	bool is_ap = iftype_data->types_mask & (BIT(NL80211_IFTYPE_AP) | +						BIT(NL80211_IFTYPE_P2P_GO));  	bool no_320;  	no_320 = (!trans->trans_cfg->integrated && @@ -1023,8 +1028,6 @@ iwl_nvm_fixup_sband_iftd(struct iwl_trans *trans,  	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:  	case IWL_CFG_RF_TYPE_FM:  	case IWL_CFG_RF_TYPE_WH:  		iftype_data->he_cap.he_cap_elem.phy_cap_info[9] |= @@ -1176,12 +1179,11 @@ static void iwl_init_sbands(struct iwl_trans *trans,  			    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, +	n_channels = iwl_init_channel_map(trans, fw, data, nvm_ch_flags,  					  sbands_flags, v4);  	sband = &data->bands[NL80211_BAND_2GHZ];  	sband->band = NL80211_BAND_2GHZ; diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-prph.h b/drivers/net/wireless/intel/iwlwifi/iwl-prph.h index a7d44df06eab..898e22e0d1ab 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-prph.h +++ b/drivers/net/wireless/intel/iwlwifi/iwl-prph.h @@ -1,6 +1,6 @@  /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */  /* - * Copyright (C) 2005-2014, 2018-2023 Intel Corporation + * Copyright (C) 2005-2014, 2018-2024 Intel Corporation   * Copyright (C) 2013-2015 Intel Mobile Communications GmbH   * Copyright (C) 2016 Intel Deutschland GmbH   */ @@ -371,7 +371,10 @@ enum {  #define CNVI_AUX_MISC_CHIP			0xA200B0  #define CNVI_AUX_MISC_CHIP_MAC_STEP(_val)	(((_val) & 0xf000000) >> 24)  #define CNVI_AUX_MISC_CHIP_PROD_TYPE(_val)	((_val) & 0xfff) +#define CNVI_AUX_MISC_CHIP_PROD_TYPE_GL		0x910  #define CNVI_AUX_MISC_CHIP_PROD_TYPE_BZ_U	0x930 +#define CNVI_AUX_MISC_CHIP_PROD_TYPE_BZ_I	0x900 +#define CNVI_AUX_MISC_CHIP_PROD_TYPE_BZ_W	0x901  #define CNVR_AUX_MISC_CHIP				0xA2B800  #define CNVR_SCU_SD_REGS_SD_REG_DIG_DCDC_VTRIM		0xA29890 @@ -453,11 +456,7 @@ enum {  #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 REG_CRF_ID_TYPE_FMI			0x930 -#define REG_CRF_ID_TYPE_FMR			0x900  #define REG_CRF_ID_TYPE_WHP			0xA10  #define HPM_DEBUG			0xA03440 diff --git a/drivers/net/wireless/intel/iwlwifi/mei/Makefile b/drivers/net/wireless/intel/iwlwifi/mei/Makefile index 8e3ef0347db7..e05f9421be4a 100644 --- a/drivers/net/wireless/intel/iwlwifi/mei/Makefile +++ b/drivers/net/wireless/intel/iwlwifi/mei/Makefile @@ -5,4 +5,4 @@ iwlmei-y += net.o  iwlmei-$(CONFIG_IWLWIFI_DEVICE_TRACING) += trace.o  CFLAGS_trace.o := -I$(src) -ccflags-y += -I $(srctree)/$(src)/../ +ccflags-y += -I $(src)/../ diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/Makefile b/drivers/net/wireless/intel/iwlwifi/mvm/Makefile index 593fe28d89cf..8873904f51ec 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/Makefile +++ b/drivers/net/wireless/intel/iwlwifi/mvm/Makefile @@ -1,5 +1,6 @@  # SPDX-License-Identifier: GPL-2.0  obj-$(CONFIG_IWLMVM)   += iwlmvm.o +obj-$(CONFIG_IWLWIFI_KUNIT_TESTS) += tests/  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 @@ -15,4 +16,4 @@ iwlmvm-$(CONFIG_IWLWIFI_LEDS) += led.o  iwlmvm-$(CONFIG_PM) += d3.o  iwlmvm-$(CONFIG_IWLMEI) += vendor-cmd.o -ccflags-y += -I $(srctree)/$(src)/../ +subdir-ccflags-y += -I $(src)/../ diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/coex.c b/drivers/net/wireless/intel/iwlwifi/mvm/coex.c index 535edb51d1c0..ad3e14a0d043 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/coex.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/coex.c @@ -1,6 +1,6 @@  // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause  /* - * Copyright (C) 2013-2014, 2018-2020, 2022-2023 Intel Corporation + * Copyright (C) 2013-2014, 2018-2020, 2022-2024 Intel Corporation   * Copyright (C) 2013-2015 Intel Mobile Communications GmbH   */  #include <linux/ieee80211.h> @@ -219,15 +219,13 @@ struct iwl_bt_iterator_data {  static inline  void iwl_mvm_bt_coex_enable_rssi_event(struct iwl_mvm *mvm, -				       struct ieee80211_vif *vif, +				       struct iwl_mvm_vif_link_info *link_info,  				       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 = +	link_info->bf_data.last_bt_coex_event = rssi; +	link_info->bf_data.bt_coex_max_thold =  		enable ? -IWL_MVM_BT_COEX_EN_RED_TXP_THRESH : 0; -	mvmvif->bf_data.bt_coex_min_thold = +	link_info->bf_data.bt_coex_min_thold =  		enable ? -IWL_MVM_BT_COEX_DIS_RED_TXP_THRESH : 0;  } @@ -255,44 +253,6 @@ static void iwl_mvm_bt_coex_tcm_based_ci(struct iwl_mvm *mvm,  	swap(data->primary, data->secondary);  } -static void iwl_mvm_bt_coex_enable_esr(struct iwl_mvm *mvm, -				       struct ieee80211_vif *vif, bool enable) -{ -	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); -	int link_id; - -	lockdep_assert_held(&mvm->mutex); - -	if (!vif->cfg.assoc || !ieee80211_vif_is_mld(vif)) -		return; - -	/* Done already */ -	if (mvmvif->bt_coex_esr_disabled == !enable) -		return; - -	mvmvif->bt_coex_esr_disabled = !enable; - -	/* Nothing to do */ -	if (mvmvif->esr_active == enable) -		return; - -	if (enable) { -		/* Try to re-enable eSR*/ -		iwl_mvm_mld_select_links(mvm, vif, false); -		return; -	} - -	/* -	 * Find the primary link, as we want to switch to it and drop the -	 * secondary one. -	 */ -	link_id = iwl_mvm_mld_get_primary_link(mvm, vif, vif->active_links); -	WARN_ON(link_id < 0); - -	ieee80211_set_active_links_async(vif, -					 vif->active_links & BIT(link_id)); -} -  /*   * This function receives the LB link id and checks if eSR should be   * enabled or disabled (due to BT coex) @@ -300,35 +260,25 @@ static void iwl_mvm_bt_coex_enable_esr(struct iwl_mvm *mvm,  bool  iwl_mvm_bt_coex_calculate_esr_mode(struct iwl_mvm *mvm,  				   struct ieee80211_vif *vif, -				   int link_id, int primary_link) +				   s32 link_rssi, +				   bool primary)  {  	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); -	struct iwl_mvm_vif_link_info *link_info = mvmvif->link[link_id];  	bool have_wifi_loss_rate =  		iwl_fw_lookup_notif_ver(mvm->fw, LEGACY_GROUP,  					BT_PROFILE_NOTIFICATION, 0) > 4; -	s8 link_rssi = 0;  	u8 wifi_loss_rate; -	lockdep_assert_held(&mvm->mutex); -  	if (mvm->last_bt_notif.wifi_loss_low_rssi == BT_OFF)  		return true; -	 /* If LB link is the primary one we should always disable eSR */ -	if (link_id == primary_link) +	if (primary)  		return false;  	/* The feature is not supported */  	if (!have_wifi_loss_rate)  		return true; -	/* -	 * We might not have a link_info when checking whether we can -	 * (re)enable eSR - the LB link might not exist yet -	 */ -	if (link_info) -		link_rssi = (s8)link_info->beacon_stats.avg_signal;  	/*  	 * In case we don't know the RSSI - take the lower wifi loss, @@ -338,7 +288,7 @@ iwl_mvm_bt_coex_calculate_esr_mode(struct iwl_mvm *mvm,  	if (!link_rssi)  		wifi_loss_rate = mvm->last_bt_notif.wifi_loss_mid_high_rssi; -	else if (!mvmvif->bt_coex_esr_disabled) +	else if (mvmvif->esr_active)  		 /* RSSI needs to get really low to disable eSR... */  		wifi_loss_rate =  			link_rssi <= -IWL_MVM_BT_COEX_DISABLE_ESR_THRESH ? @@ -354,23 +304,24 @@ iwl_mvm_bt_coex_calculate_esr_mode(struct iwl_mvm *mvm,  	return wifi_loss_rate <= IWL_MVM_BT_COEX_WIFI_LOSS_THRESH;  } -void iwl_mvm_bt_coex_update_vif_esr(struct iwl_mvm *mvm, -				    struct ieee80211_vif *vif, -				    int link_id) +void iwl_mvm_bt_coex_update_link_esr(struct iwl_mvm *mvm, +				     struct ieee80211_vif *vif, +				     int link_id)  { -	unsigned long usable_links = ieee80211_vif_usable_links(vif); -	int primary_link = iwl_mvm_mld_get_primary_link(mvm, vif, -							usable_links); -	bool enable; +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	struct iwl_mvm_vif_link_info *link = mvmvif->link[link_id]; -	/* Not assoc, not MLD vif or only one usable link */ -	if (primary_link < 0) +	if (!ieee80211_vif_is_mld(vif) || +	    !iwl_mvm_vif_from_mac80211(vif)->authorized || +	    WARN_ON(!link))  		return; -	enable = iwl_mvm_bt_coex_calculate_esr_mode(mvm, vif, link_id, -						    primary_link); - -	iwl_mvm_bt_coex_enable_esr(mvm, vif, enable); +	if (!iwl_mvm_bt_coex_calculate_esr_mode(mvm, vif, +						(s8)link->beacon_stats.avg_signal, +						link_id == iwl_mvm_get_primary_link(vif))) +		/* In case we decided to exit eSR - stay with the primary */ +		iwl_mvm_exit_esr(mvm, vif, IWL_MVM_ESR_EXIT_COEX, +				 iwl_mvm_get_primary_link(vif));  }  static void iwl_mvm_bt_notif_per_link(struct iwl_mvm *mvm, @@ -412,13 +363,13 @@ static void iwl_mvm_bt_notif_per_link(struct iwl_mvm *mvm,  					    smps_mode, link_id);  			iwl_mvm_bt_coex_reduced_txp(mvm, link_info->ap_sta_id,  						    false); -			/* FIXME: should this be per link? */ -			iwl_mvm_bt_coex_enable_rssi_event(mvm, vif, false, 0); +			iwl_mvm_bt_coex_enable_rssi_event(mvm, link_info, false, +							  0);  		}  		return;  	} -	iwl_mvm_bt_coex_update_vif_esr(mvm, vif, link_id); +	iwl_mvm_bt_coex_update_link_esr(mvm, vif, link_id);  	if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_COEX_SCHEMA_2))  		min_ag_for_static_smps = BT_VERY_HIGH_TRAFFIC; @@ -508,13 +459,12 @@ static void iwl_mvm_bt_notif_per_link(struct iwl_mvm *mvm,  	    le32_to_cpu(mvm->last_bt_notif.bt_activity_grading) == BT_OFF ||  	    !vif->cfg.assoc) {  		iwl_mvm_bt_coex_reduced_txp(mvm, link_info->ap_sta_id, false); -		/* FIXME: should this be per link? */ -		iwl_mvm_bt_coex_enable_rssi_event(mvm, vif, false, 0); +		iwl_mvm_bt_coex_enable_rssi_event(mvm, link_info, false, 0);  		return;  	}  	/* try to get the avg rssi from fw */ -	ave_rssi = mvmvif->bf_data.ave_beacon_signal; +	ave_rssi = link_info->bf_data.ave_beacon_signal;  	/* if the RSSI isn't valid, fake it is very low */  	if (!ave_rssi) @@ -530,7 +480,7 @@ static void iwl_mvm_bt_notif_per_link(struct iwl_mvm *mvm,  	}  	/* Begin to monitor the RSSI: it may influence the reduced Tx power */ -	iwl_mvm_bt_coex_enable_rssi_event(mvm, vif, true, ave_rssi); +	iwl_mvm_bt_coex_enable_rssi_event(mvm, link_info, true, ave_rssi);  }  /* must be called under rcu_read_lock */ @@ -555,10 +505,6 @@ static void iwl_mvm_bt_notif_iterator(void *_data, u8 *mac,  		return;  	} -	/* When BT is off this will be 0 */ -	if (data->notif->wifi_loss_low_rssi == BT_OFF) -		iwl_mvm_bt_coex_enable_esr(mvm, vif, true); -  	for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++)  		iwl_mvm_bt_notif_per_link(mvm, vif, data, link_id);  } diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/constants.h b/drivers/net/wireless/intel/iwlwifi/mvm/constants.h index f5122c4678a1..3cbeaddf4358 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/constants.h +++ b/drivers/net/wireless/intel/iwlwifi/mvm/constants.h @@ -1,7 +1,7 @@  /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */  /*   * Copyright (C) 2013-2015 Intel Mobile Communications GmbH - * Copyright (C) 2013-2014, 2018-2023 Intel Corporation + * Copyright (C) 2013-2014, 2018-2024 Intel Corporation   * Copyright (C) 2015 Intel Deutschland GmbH   */  #ifndef __MVM_CONSTANTS_H @@ -14,6 +14,8 @@  #define IWL_MVM_BT_COEX_DISABLE_ESR_THRESH	69  #define IWL_MVM_BT_COEX_ENABLE_ESR_THRESH	63  #define IWL_MVM_BT_COEX_WIFI_LOSS_THRESH	0 +#define IWL_MVM_TRIGGER_LINK_SEL_TIME_SEC	30 +#define IWL_MVM_TPT_COUNT_WINDOW_SEC		5  #define IWL_MVM_DEFAULT_PS_TX_DATA_TIMEOUT	(100 * USEC_PER_MSEC)  #define IWL_MVM_DEFAULT_PS_RX_DATA_TIMEOUT	(100 * USEC_PER_MSEC) @@ -123,5 +125,16 @@  #define IWL_MVM_6GHZ_PASSIVE_SCAN_TIMEOUT       3000 /* in seconds */  #define IWL_MVM_6GHZ_PASSIVE_SCAN_ASSOC_TIMEOUT 60   /* in seconds */  #define IWL_MVM_AUTO_EML_ENABLE                 true +#define IWL_MVM_MISSED_BEACONS_EXIT_ESR_THRESH	7 +#define IWL_MVM_HIGH_RSSI_THRESH_20MHZ		-67 +#define IWL_MVM_LOW_RSSI_THRESH_20MHZ		-71 +#define IWL_MVM_HIGH_RSSI_THRESH_40MHZ		-64 +#define IWL_MVM_LOW_RSSI_THRESH_40MHZ		-67 +#define IWL_MVM_HIGH_RSSI_THRESH_80MHZ		-61 +#define IWL_MVM_LOW_RSSI_THRESH_80MHZ		-74 +#define IWL_MVM_HIGH_RSSI_THRESH_160MHZ		-58 +#define IWL_MVM_LOW_RSSI_THRESH_160MHZ		-61 + +#define IWL_MVM_ENTER_ESR_TPT_THRESH		400  #endif /* __MVM_CONSTANTS_H */ diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c index 52518a47554e..71e6b06481a9 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c @@ -1152,7 +1152,8 @@ iwl_mvm_wowlan_config(struct iwl_mvm *mvm,  	if (ret)  		return ret; -	return iwl_mvm_send_proto_offload(mvm, vif, false, true, 0); +	return iwl_mvm_send_proto_offload(mvm, vif, false, true, 0, +					  mvm_link->ap_sta_id);  }  static int @@ -1242,7 +1243,7 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  		.data[0] = &d3_cfg_cmd_data,  		.len[0] = sizeof(d3_cfg_cmd_data),  	}; -	int ret, primary_link; +	int ret;  	int len __maybe_unused;  	bool unified_image = fw_has_capa(&mvm->fw->ucode_capa,  					 IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG); @@ -1260,28 +1261,9 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  	if (IS_ERR_OR_NULL(vif))  		return 1; -	if (hweight16(vif->active_links) > 1) { -		/* -		 * Select the 'best' link. -		 * May need to revisit, it seems better to not optimize -		 * for throughput but rather range, reliability and -		 * power here - and select 2.4 GHz ... -		 */ -		primary_link = iwl_mvm_mld_get_primary_link(mvm, vif, -							    vif->active_links); - -		if (WARN_ONCE(primary_link < 0, "no primary link in 0x%x\n", -			      vif->active_links)) -			primary_link = __ffs(vif->active_links); - -		ret = ieee80211_set_active_links(vif, BIT(primary_link)); -		if (ret) -			return ret; -	} else if (vif->active_links) { -		primary_link = __ffs(vif->active_links); -	} else { -		primary_link = 0; -	} +	ret = iwl_mvm_block_esr_sync(mvm, vif, IWL_MVM_ESR_BLOCKED_WOWLAN); +	if (ret) +		return ret;  	mutex_lock(&mvm->mutex); @@ -1291,7 +1273,7 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  	mvmvif = iwl_mvm_vif_from_mac80211(vif); -	mvm_link = mvmvif->link[primary_link]; +	mvm_link = mvmvif->link[iwl_mvm_get_primary_link(vif)];  	if (WARN_ON_ONCE(!mvm_link)) {  		ret = -EINVAL;  		goto out_noreset; @@ -1472,6 +1454,9 @@ struct iwl_wowlan_status_data {  	struct iwl_multicast_key_data igtk;  	struct iwl_multicast_key_data bigtk[WOWLAN_BIGTK_KEYS_NUM]; +	int num_mlo_keys; +	struct iwl_wowlan_mlo_gtk mlo_keys[WOWLAN_MAX_MLO_KEYS]; +  	u8 *wake_packet;  }; @@ -1830,6 +1815,10 @@ static void iwl_mvm_d3_find_last_keys(struct ieee80211_hw *hw,  				      void *_data)  {  	struct iwl_mvm_d3_gtk_iter_data *data = _data; +	int link_id = vif->active_links ? __ffs(vif->active_links) : -1; + +	if (link_id >= 0 && key->link_id >= 0 && link_id != key->link_id) +		return;  	if (data->unhandled_cipher)  		return; @@ -1918,6 +1907,10 @@ static void iwl_mvm_d3_update_keys(struct ieee80211_hw *hw,  	struct iwl_mvm_d3_gtk_iter_data *data = _data;  	struct iwl_wowlan_status_data *status = data->status;  	s8 keyidx; +	int link_id = vif->active_links ? __ffs(vif->active_links) : -1; + +	if (link_id >= 0 && key->link_id >= 0 && link_id != key->link_id) +		return;  	if (data->unhandled_cipher)  		return; @@ -1973,6 +1966,169 @@ static void iwl_mvm_d3_update_keys(struct ieee80211_hw *hw,  	}  } +struct iwl_mvm_d3_mlo_old_keys { +	u32 cipher[IEEE80211_MLD_MAX_NUM_LINKS][WOWLAN_MLO_GTK_KEY_NUM_TYPES]; +	struct ieee80211_key_conf *key[IEEE80211_MLD_MAX_NUM_LINKS][8]; +}; + +static void iwl_mvm_mlo_key_ciphers(struct ieee80211_hw *hw, +				    struct ieee80211_vif *vif, +				    struct ieee80211_sta *sta, +				    struct ieee80211_key_conf *key, +				    void *data) +{ +	struct iwl_mvm_d3_mlo_old_keys *old_keys = data; +	enum iwl_wowlan_mlo_gtk_type key_type; + +	if (key->link_id < 0) +		return; + +	if (WARN_ON(key->link_id >= IEEE80211_MLD_MAX_NUM_LINKS || +		    key->keyidx >= 8)) +		return; + +	if (WARN_ON(old_keys->key[key->link_id][key->keyidx])) +		return; + +	switch (key->cipher) { +	case WLAN_CIPHER_SUITE_CCMP: +	case WLAN_CIPHER_SUITE_GCMP: +	case WLAN_CIPHER_SUITE_GCMP_256: +		key_type = WOWLAN_MLO_GTK_KEY_TYPE_GTK; +		break; +	case WLAN_CIPHER_SUITE_BIP_GMAC_128: +	case WLAN_CIPHER_SUITE_BIP_GMAC_256: +	case WLAN_CIPHER_SUITE_BIP_CMAC_256: +	case WLAN_CIPHER_SUITE_AES_CMAC: +		if (key->keyidx == 4 || key->keyidx == 5) { +			key_type = WOWLAN_MLO_GTK_KEY_TYPE_IGTK; +			break; +		} else if (key->keyidx == 6 || key->keyidx == 7) { +			key_type = WOWLAN_MLO_GTK_KEY_TYPE_BIGTK; +			break; +		} +		return; +	default: +		/* ignore WEP/TKIP or unknown ciphers */ +		return; +	} + +	old_keys->cipher[key->link_id][key_type] = key->cipher; +	old_keys->key[key->link_id][key->keyidx] = key; +} + +static bool iwl_mvm_mlo_gtk_rekey(struct iwl_wowlan_status_data *status, +				  struct ieee80211_vif *vif, +				  struct iwl_mvm *mvm) +{ +	int i; +	struct iwl_mvm_d3_mlo_old_keys *old_keys; +	bool ret = true; + +	IWL_DEBUG_WOWLAN(mvm, "Num of MLO Keys: %d\n", status->num_mlo_keys); +	if (!status->num_mlo_keys) +		return true; + +	old_keys = kzalloc(sizeof(*old_keys), GFP_KERNEL); +	if (!old_keys) +		return false; + +	/* find the cipher for each mlo key */ +	ieee80211_iter_keys(mvm->hw, vif, iwl_mvm_mlo_key_ciphers, old_keys); + +	for (i = 0; i < status->num_mlo_keys; i++) { +		struct iwl_wowlan_mlo_gtk *mlo_key = &status->mlo_keys[i]; +		struct ieee80211_key_conf *key, *old_key; +		struct ieee80211_key_seq seq; +		struct { +			struct ieee80211_key_conf conf; +			u8 key[32]; +		} conf = {}; +		u16 flags = le16_to_cpu(mlo_key->flags); +		int j, link_id, key_id, key_type; + +		link_id = u16_get_bits(flags, WOWLAN_MLO_GTK_FLAG_LINK_ID_MSK); +		key_id = u16_get_bits(flags, WOWLAN_MLO_GTK_FLAG_KEY_ID_MSK); +		key_type = u16_get_bits(flags, +					WOWLAN_MLO_GTK_FLAG_KEY_TYPE_MSK); + +		if (!(vif->valid_links & BIT(link_id))) +			continue; + +		if (WARN_ON(link_id >= IEEE80211_MLD_MAX_NUM_LINKS || +			    key_id >= 8 || +			    key_type >= WOWLAN_MLO_GTK_KEY_NUM_TYPES)) +			continue; + +		conf.conf.cipher = old_keys->cipher[link_id][key_type]; +		/* WARN_ON? */ +		if (!conf.conf.cipher) +			continue; + +		conf.conf.keylen = 0; +		switch (conf.conf.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_BIP_GMAC_128: +			conf.conf.keylen = WLAN_KEY_LEN_BIP_GMAC_128; +			break; +		case WLAN_CIPHER_SUITE_BIP_GMAC_256: +			conf.conf.keylen = WLAN_KEY_LEN_BIP_GMAC_256; +			break; +		case WLAN_CIPHER_SUITE_AES_CMAC: +			conf.conf.keylen = WLAN_KEY_LEN_AES_CMAC; +			break; +		case WLAN_CIPHER_SUITE_BIP_CMAC_256: +			conf.conf.keylen = WLAN_KEY_LEN_BIP_CMAC_256; +			break; +		} + +		if (WARN_ON(!conf.conf.keylen || +			    conf.conf.keylen > sizeof(conf.key))) +			continue; + +		memcpy(conf.conf.key, mlo_key->key, conf.conf.keylen); +		conf.conf.keyidx = key_id; + +		old_key = old_keys->key[link_id][key_id]; +		if (old_key) { +			IWL_DEBUG_WOWLAN(mvm, +					 "Remove MLO key id %d, link id %d\n", +					 key_id, link_id); +			ieee80211_remove_key(old_key); +		} + +		IWL_DEBUG_WOWLAN(mvm, "Add MLO key id %d, link id %d\n", +				 key_id, link_id); +		key = ieee80211_gtk_rekey_add(vif, &conf.conf, link_id); +		if (WARN_ON(IS_ERR(key))) { +			ret = false; +			goto out; +		} + +		/* +		 * mac80211 expects the pn in big-endian +		 * also note that seq is a union of all cipher types +		 * (ccmp, gcmp, cmac, gmac), and they all have the same +		 * pn field (of length 6) so just copy it to ccmp.pn. +		 */ +		for (j = 5; j >= 0; j--) +			seq.ccmp.pn[5 - j] = mlo_key->pn[j]; + +		/* group keys are non-QoS and use TID 0 */ +		ieee80211_set_key_rx_seq(key, 0, &seq); +	} + +out: +	kfree(old_keys); +	return ret; +} +  static bool iwl_mvm_gtk_rekey(struct iwl_wowlan_status_data *status,  			      struct ieee80211_vif *vif,  			      struct iwl_mvm *mvm, u32 gtk_cipher) @@ -2176,6 +2332,9 @@ static bool iwl_mvm_setup_connection_keep(struct iwl_mvm *mvm,  				return false;  		} +		if (!iwl_mvm_mlo_gtk_rekey(status, vif, mvm)) +			return false; +  		ieee80211_gtk_rekey_notify(vif, vif->bss_conf.bssid,  					   (void *)&replay_ctr, GFP_KERNEL);  	} @@ -2303,9 +2462,10 @@ static void iwl_mvm_convert_bigtk(struct iwl_wowlan_status_data *status,  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 len, bool has_mlo_keys)  {  	u32 i; +	u32 expected_len = sizeof(*data);  	if (!data) {  		IWL_ERR(mvm, "iwl_wowlan_info_notif data is NULL\n"); @@ -2313,7 +2473,11 @@ static void iwl_mvm_parse_wowlan_info_notif(struct iwl_mvm *mvm,  		return;  	} -	if (len < sizeof(*data)) { +	if (has_mlo_keys) +		expected_len += (data->num_mlo_link_keys * +				 sizeof(status->mlo_keys[0])); + +	if (len < expected_len) {  		IWL_ERR(mvm, "Invalid WoWLAN info notification!\n");  		status = NULL;  		return; @@ -2333,6 +2497,17 @@ static void iwl_mvm_parse_wowlan_info_notif(struct iwl_mvm *mvm,  		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; + +	if (has_mlo_keys && data->num_mlo_link_keys) { +		status->num_mlo_keys = data->num_mlo_link_keys; +		if (IWL_FW_CHECK(mvm, +				 status->num_mlo_keys > WOWLAN_MAX_MLO_KEYS, +				 "Too many mlo keys: %d, max %d\n", +				 status->num_mlo_keys, WOWLAN_MAX_MLO_KEYS)) +			status->num_mlo_keys = WOWLAN_MAX_MLO_KEYS; +		memcpy(status->mlo_keys, data->mlo_gtks, +		       status->num_mlo_keys * sizeof(status->mlo_keys[0])); +	}  }  static void @@ -2553,6 +2728,12 @@ static bool iwl_mvm_query_wakeup_reasons(struct iwl_mvm *mvm,  	int i;  	bool keep = false;  	struct iwl_mvm_sta *mvm_ap_sta; +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	int link_id = vif->active_links ? __ffs(vif->active_links) : 0; +	struct iwl_mvm_vif_link_info *mvm_link = mvmvif->link[link_id]; + +	if (WARN_ON(!mvm_link)) +		goto out_unlock;  	if (!status)  		goto out_unlock; @@ -2560,8 +2741,7 @@ static bool iwl_mvm_query_wakeup_reasons(struct iwl_mvm *mvm,  	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); +	mvm_ap_sta = iwl_mvm_sta_from_staid_protected(mvm, mvm_link->ap_sta_id);  	if (!mvm_ap_sta)  		goto out_unlock; @@ -3074,7 +3254,8 @@ static bool iwl_mvm_wait_d3_notif(struct iwl_notif_wait_data *notif_wait,  				(void *)pkt->data;  			iwl_mvm_parse_wowlan_info_notif(mvm, notif, -							d3_data->status, len); +							d3_data->status, len, +							wowlan_info_ver > 3);  		}  		d3_data->notif_received |= IWL_D3_NOTIF_WOWLAN_INFO; @@ -3272,6 +3453,8 @@ static int __iwl_mvm_resume(struct iwl_mvm *mvm, bool test)  			goto err;  	} +	iwl_mvm_unblock_esr(mvm, vif, IWL_MVM_ESR_BLOCKED_WOWLAN); +  	/* after the successful handshake, we're out of D3 */  	mvm->trans->system_pm_mode = IWL_PLAT_PM_MODE_DISABLED; diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/debugfs-vif.c b/drivers/net/wireless/intel/iwlwifi/mvm/debugfs-vif.c index 7fe57ecd0682..17c97dfbc62a 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/debugfs-vif.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/debugfs-vif.c @@ -407,7 +407,7 @@ static ssize_t iwl_dbgfs_bf_params_read(struct file *file,  	};  	iwl_mvm_beacon_filter_debugfs_parameters(vif, &cmd); -	if (mvmvif->bf_data.bf_enabled) +	if (mvmvif->bf_enabled)  		cmd.bf_enable_beacon_filter = cpu_to_le32(1);  	else  		cmd.bf_enable_beacon_filter = 0; @@ -692,6 +692,96 @@ static ssize_t iwl_dbgfs_quota_min_read(struct file *file,  	return simple_read_from_buffer(user_buf, count, ppos, buf, len);  } +static ssize_t iwl_dbgfs_int_mlo_scan_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; +	u32 action; +	int ret; + +	if (!vif->cfg.assoc || !ieee80211_vif_is_mld(vif)) +		return -EINVAL; + +	if (kstrtou32(buf, 0, &action)) +		return -EINVAL; + +	mutex_lock(&mvm->mutex); + +	if (!action) { +		ret = iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_INT_MLO, false); +	} else if (action == 1) { +		ret = iwl_mvm_int_mlo_scan(mvm, vif); +	} else { +		ret = -EINVAL; +	} + +	mutex_unlock(&mvm->mutex); + +	return ret ?: count; +} + +static ssize_t iwl_dbgfs_esr_disable_reason_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; +	unsigned long esr_mask; +	char *buf; +	int bufsz, pos, i; +	ssize_t rv; + +	mutex_lock(&mvm->mutex); +	esr_mask = mvmvif->esr_disable_reason; +	mutex_unlock(&mvm->mutex); + +	bufsz = hweight32(esr_mask) * 32 + 40; +	buf = kmalloc(bufsz, GFP_KERNEL); +	if (!buf) +		return -ENOMEM; + +	pos = scnprintf(buf, bufsz, "EMLSR state: '0x%lx'\nreasons:\n", +			esr_mask); +	for_each_set_bit(i, &esr_mask, BITS_PER_LONG) +		pos += scnprintf(buf + pos, bufsz - pos, " - %s\n", +				 iwl_get_esr_state_string(BIT(i))); + +	rv = simple_read_from_buffer(user_buf, count, ppos, buf, pos); +	kfree(buf); +	return rv; +} + +static ssize_t iwl_dbgfs_esr_disable_reason_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; +	u32 reason; +	u8 block; +	int ret; + +	ret = sscanf(buf, "%u %hhu", &reason, &block); +	if (ret < 0) +		return ret; + +	if (hweight16(reason) != 1 || !(reason & IWL_MVM_BLOCK_ESR_REASONS)) +		return -EINVAL; + +	mutex_lock(&mvm->mutex); +	if (block) +		iwl_mvm_block_esr(mvm, vif, reason, +				  iwl_mvm_get_primary_link(vif)); +	else +		iwl_mvm_unblock_esr(mvm, vif, reason); +	mutex_unlock(&mvm->mutex); + +	return count; +} +  #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) \ @@ -711,6 +801,8 @@ 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); +MVM_DEBUGFS_WRITE_FILE_OPS(int_mlo_scan, 32); +MVM_DEBUGFS_READ_WRITE_FILE_OPS(esr_disable_reason, 32);  void iwl_mvm_vif_add_debugfs(struct ieee80211_hw *hw, struct ieee80211_vif *vif)  { @@ -738,6 +830,10 @@ void iwl_mvm_vif_add_debugfs(struct ieee80211_hw *hw, struct ieee80211_vif *vif)  	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); +	debugfs_create_bool("ftm_unprotected", 0200, mvmvif->dbgfs_dir, +			    &mvmvif->ftm_unprotected); +	MVM_DEBUGFS_ADD_FILE_VIF(int_mlo_scan, mvmvif->dbgfs_dir, 0200); +	MVM_DEBUGFS_ADD_FILE_VIF(esr_disable_reason, mvmvif->dbgfs_dir, 0600);  	if (vif->type == NL80211_IFTYPE_STATION && !vif->p2p &&  	    mvmvif == mvm->bf_allowed_vif) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c b/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c index 4863a3c74640..72a3d71f46f0 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ftm-initiator.c @@ -1,7 +1,7 @@  // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause  /*   * Copyright (C) 2015-2017 Intel Deutschland GmbH - * Copyright (C) 2018-2023 Intel Corporation + * Copyright (C) 2018-2024 Intel Corporation   */  #include <linux/etherdevice.h>  #include <linux/math64.h> @@ -53,6 +53,8 @@ int iwl_mvm_ftm_add_pasn_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  	if (!pasn)  		return -ENOBUFS; +	iwl_mvm_ftm_remove_pasn_sta(mvm, addr); +  	pasn->cipher = iwl_mvm_cipher_to_location_cipher(cipher);  	switch (pasn->cipher) { @@ -551,6 +553,15 @@ iwl_mvm_ftm_put_target(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  			break;  		}  		rcu_read_unlock(); + +#ifdef CONFIG_IWLWIFI_DEBUGFS +		if (mvmvif->ftm_unprotected) { +			target->sta_id = IWL_MVM_INVALID_STA; +			target->initiator_ap_flags &= +				~cpu_to_le32(IWL_INITIATOR_AP_FLAGS_PMF); +		} + +#endif  	} else {  		target->sta_id = IWL_MVM_INVALID_STA;  	} @@ -713,6 +724,12 @@ iwl_mvm_ftm_set_secured_ranging(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  {  	struct iwl_mvm_ftm_pasn_entry *entry;  	u32 flags = le32_to_cpu(target->initiator_ap_flags); +#ifdef CONFIG_IWLWIFI_DEBUGFS +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + +	if (mvmvif->ftm_unprotected) +		return; +#endif  	if (!(flags & (IWL_INITIATOR_AP_FLAGS_NON_TB |  		       IWL_INITIATOR_AP_FLAGS_TB))) diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/fw.c b/drivers/net/wireless/intel/iwlwifi/mvm/fw.c index e1c2b7fc92ab..e7f5978ef2d7 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/fw.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/fw.c @@ -494,7 +494,7 @@ static void iwl_mvm_uats_init(struct iwl_mvm *mvm)  	int ret;  	struct iwl_host_cmd cmd = {  		.id = WIDE_ID(REGULATORY_AND_NVM_GROUP, -			      UATS_TABLE_CMD), +			      MCC_ALLOWED_AP_TYPE_CMD),  		.flags = 0,  		.data[0] = &mvm->fwrt.uats_table,  		.len[0] =  sizeof(mvm->fwrt.uats_table), @@ -516,7 +516,7 @@ static void iwl_mvm_uats_init(struct iwl_mvm *mvm)  					IWL_FW_CMD_VER_UNKNOWN);  	if (cmd_ver != 1) {  		IWL_DEBUG_RADIO(mvm, -				"UATS_TABLE_CMD ver %d not supported\n", +				"MCC_ALLOWED_AP_TYPE_CMD ver %d not supported\n",  				cmd_ver);  		return;  	} @@ -529,9 +529,10 @@ static void iwl_mvm_uats_init(struct iwl_mvm *mvm)  	ret = iwl_mvm_send_cmd(mvm, &cmd);  	if (ret < 0) -		IWL_ERR(mvm, "failed to send UATS_TABLE_CMD (%d)\n", ret); +		IWL_ERR(mvm, "failed to send MCC_ALLOWED_AP_TYPE_CMD (%d)\n", +			ret);  	else -		IWL_DEBUG_RADIO(mvm, "UATS_TABLE_CMD sent to FW\n"); +		IWL_DEBUG_RADIO(mvm, "MCC_ALLOWED_AP_TYPE_CMD sent to FW\n");  }  static int iwl_mvm_sgom_init(struct iwl_mvm *mvm) @@ -666,7 +667,7 @@ static int iwl_run_unified_mvm_ucode(struct iwl_mvm *mvm)  	iwl_dbg_tlv_time_point(&mvm->fwrt, IWL_FW_INI_TIME_POINT_AFTER_ALIVE,  			       NULL); -	if (mvm->trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_BZ) +	if (mvm->trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ)  		mvm->trans->step_urm = !!(iwl_read_umac_prph(mvm->trans,  							     CNVI_PMU_STEP_FLOW) &  						CNVI_PMU_STEP_FLOW_FORCE_URM); @@ -896,11 +897,13 @@ int iwl_mvm_sar_select_profile(struct iwl_mvm *mvm, int prof_a, int prof_b)  	u32 n_subbands;  	u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id,  					   IWL_FW_CMD_VER_UNKNOWN); -	if (cmd_ver == 7) { +	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); +		if (cmd_ver == 8) +			len = sizeof(cmd.v8);  	} else if (cmd_ver == 6) {  		len = sizeof(cmd.v6);  		n_subbands = IWL_NUM_SUB_BANDS_V2; @@ -1223,94 +1226,12 @@ static bool iwl_mvm_eval_dsm_rfi(struct iwl_mvm *mvm)  static void iwl_mvm_lari_cfg(struct iwl_mvm *mvm)  { +	struct iwl_lari_config_change_cmd cmd; +	size_t cmd_size;  	int ret; -	u32 value; -	struct iwl_lari_config_change_cmd_v7 cmd = {}; -	u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, -					   WIDE_ID(REGULATORY_AND_NVM_GROUP, -						   LARI_CONFIG_CHANGE), 1); - -	cmd.config_bitmap = iwl_get_lari_config_bitmap(&mvm->fwrt); - -	ret = iwl_bios_get_dsm(&mvm->fwrt, DSM_FUNC_11AX_ENABLEMENT, &value); -	if (!ret) -		cmd.oem_11ax_allow_bitmap = cpu_to_le32(value); -	ret = iwl_bios_get_dsm(&mvm->fwrt, DSM_FUNC_ENABLE_UNII4_CHAN, &value); -	if (!ret) -		cmd.oem_unii4_allow_bitmap = cpu_to_le32(value); - -	ret = iwl_bios_get_dsm(&mvm->fwrt, DSM_FUNC_ACTIVATE_CHANNEL, &value); +	ret = iwl_fill_lari_config(&mvm->fwrt, &cmd, &cmd_size);  	if (!ret) { -		if (cmd_ver < 8) -			value &= ~ACTIVATE_5G2_IN_WW_MASK; -		cmd.chan_state_active_bitmap = cpu_to_le32(value); -	} - -	ret = iwl_bios_get_dsm(&mvm->fwrt, DSM_FUNC_ENABLE_6E, &value); -	if (!ret) -		cmd.oem_uhb_allow_bitmap = cpu_to_le32(value); - -	ret = iwl_bios_get_dsm(&mvm->fwrt, DSM_FUNC_FORCE_DISABLE_CHANNELS, -			       &value); -	if (!ret) -		cmd.force_disable_channels_bitmap = cpu_to_le32(value); - -	ret = iwl_bios_get_dsm(&mvm->fwrt, DSM_FUNC_ENERGY_DETECTION_THRESHOLD, -			       &value); -	if (!ret) -		cmd.edt_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 || -	    cmd.edt_bitmap) { -		size_t cmd_size; - -		switch (cmd_ver) { -		case 8: -		case 7: -			cmd_size = sizeof(struct iwl_lari_config_change_cmd_v7); -			break; -		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)); -		IWL_DEBUG_RADIO(mvm, -				"sending LARI_CONFIG_CHANGE, edt_bitmap=0x%x\n", -				le32_to_cpu(cmd.edt_bitmap));  		ret = iwl_mvm_send_cmd_pdu(mvm,  					   WIDE_ID(REGULATORY_AND_NVM_GROUP,  						   LARI_CONFIG_CHANGE), diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/link.c b/drivers/net/wireless/intel/iwlwifi/mvm/link.c index 9f69e04594e4..6ec9a8e21a34 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/link.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/link.c @@ -5,6 +5,48 @@  #include "mvm.h"  #include "time-event.h" +#define HANDLE_ESR_REASONS(HOW)		\ +	HOW(BLOCKED_PREVENTION)		\ +	HOW(BLOCKED_WOWLAN)		\ +	HOW(BLOCKED_TPT)		\ +	HOW(BLOCKED_FW)			\ +	HOW(BLOCKED_NON_BSS)		\ +	HOW(EXIT_MISSED_BEACON)		\ +	HOW(EXIT_LOW_RSSI)		\ +	HOW(EXIT_COEX)			\ +	HOW(EXIT_BANDWIDTH)		\ +	HOW(EXIT_CSA)			\ +	HOW(EXIT_LINK_USAGE) + +static const char *const iwl_mvm_esr_states_names[] = { +#define NAME_ENTRY(x) [ilog2(IWL_MVM_ESR_##x)] = #x, +	HANDLE_ESR_REASONS(NAME_ENTRY) +}; + +const char *iwl_get_esr_state_string(enum iwl_mvm_esr_state state) +{ +	int offs = ilog2(state); + +	if (offs >= ARRAY_SIZE(iwl_mvm_esr_states_names) || +	    !iwl_mvm_esr_states_names[offs]) +		return "UNKNOWN"; + +	return iwl_mvm_esr_states_names[offs]; +} + +static void iwl_mvm_print_esr_state(struct iwl_mvm *mvm, u32 mask) +{ +#define NAME_FMT(x) "%s" +#define NAME_PR(x) (mask & IWL_MVM_ESR_##x) ? "[" #x "]" : "", +	IWL_DEBUG_INFO(mvm, +		       "EMLSR state = " HANDLE_ESR_REASONS(NAME_FMT) +		       " (0x%x)\n", +		       HANDLE_ESR_REASONS(NAME_PR) +		       mask); +#undef NAME_FMT +#undef NAME_PR +} +  static u32 iwl_mvm_get_free_fw_link_id(struct iwl_mvm *mvm,  				       struct iwl_mvm_vif *mvm_vif)  { @@ -108,6 +150,65 @@ int iwl_mvm_add_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  	return iwl_mvm_link_cmd_send(mvm, &cmd, FW_CTXT_ACTION_ADD);  } +struct iwl_mvm_esr_iter_data { +	struct ieee80211_vif *vif; +	unsigned int link_id; +	bool lift_block; +}; + +static void iwl_mvm_esr_vif_iterator(void *_data, u8 *mac, +				     struct ieee80211_vif *vif) +{ +	struct iwl_mvm_esr_iter_data *data = _data; +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	int link_id; + +	if (ieee80211_vif_type_p2p(vif) == NL80211_IFTYPE_STATION) +		return; + +	for_each_mvm_vif_valid_link(mvmvif, link_id) { +		struct iwl_mvm_vif_link_info *link_info = +			mvmvif->link[link_id]; +		if (vif == data->vif && link_id == data->link_id) +			continue; +		if (link_info->active) +			data->lift_block = false; +	} +} + +int iwl_mvm_esr_non_bss_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +			     unsigned int link_id, bool active) +{ +	/* An active link of a non-station vif blocks EMLSR. Upon activation +	 * block EMLSR on the bss vif. Upon deactivation, check if this link +	 * was the last non-station link active, and if so unblock the bss vif +	 */ +	struct ieee80211_vif *bss_vif = iwl_mvm_get_bss_vif(mvm); +	struct iwl_mvm_esr_iter_data data = { +		.vif = vif, +		.link_id = link_id, +		.lift_block = true, +	}; + +	if (IS_ERR_OR_NULL(bss_vif)) +		return 0; + +	if (active) +		return iwl_mvm_block_esr_sync(mvm, bss_vif, +					      IWL_MVM_ESR_BLOCKED_NON_BSS); + +	ieee80211_iterate_active_interfaces(mvm->hw, +					    IEEE80211_IFACE_ITER_NORMAL, +					    iwl_mvm_esr_vif_iterator, &data); +	if (data.lift_block) { +		mutex_lock(&mvm->mutex); +		iwl_mvm_unblock_esr(mvm, bss_vif, IWL_MVM_ESR_BLOCKED_NON_BSS); +		mutex_unlock(&mvm->mutex); +	} + +	return 0; +} +  int iwl_mvm_link_changed(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  			 struct ieee80211_bss_conf *link_conf,  			 u32 changes, bool active) @@ -279,6 +380,7 @@ int iwl_mvm_unset_link_mapping(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  	RCU_INIT_POINTER(mvm->link_id_to_link_conf[link_info->fw_link_id],  			 NULL); +	iwl_mvm_release_fw_link_id(mvm, link_info->fw_link_id);  	return 0;  } @@ -296,7 +398,6 @@ int iwl_mvm_remove_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  		return 0;  	cmd.link_id = cpu_to_le32(link_info->fw_link_id); -	iwl_mvm_release_fw_link_id(mvm, link_info->fw_link_id);  	link_info->fw_link_id = IWL_MVM_FW_LINK_ID_INVALID;  	cmd.spec_link_id = link_conf->link_id;  	cmd.phy_id = cpu_to_le32(FW_CTXT_INVALID); @@ -329,3 +430,702 @@ int iwl_mvm_disable_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  	return ret;  } + +struct iwl_mvm_rssi_to_grade { +	s8 rssi[2]; +	u16 grade; +}; + +#define RSSI_TO_GRADE_LINE(_lb, _hb_uhb, _grade) \ +	{ \ +		.rssi = {_lb, _hb_uhb}, \ +		.grade = _grade \ +	} + +/* + * This array must be sorted by increasing RSSI for proper functionality. + * The grades are actually estimated throughput, represented as fixed-point + * with a scale factor of 1/10. + */ +static const struct iwl_mvm_rssi_to_grade rssi_to_grade_map[] = { +	RSSI_TO_GRADE_LINE(-85, -89, 177), +	RSSI_TO_GRADE_LINE(-83, -86, 344), +	RSSI_TO_GRADE_LINE(-82, -85, 516), +	RSSI_TO_GRADE_LINE(-80, -83, 688), +	RSSI_TO_GRADE_LINE(-77, -79, 1032), +	RSSI_TO_GRADE_LINE(-73, -76, 1376), +	RSSI_TO_GRADE_LINE(-70, -74, 1548), +	RSSI_TO_GRADE_LINE(-69, -72, 1750), +	RSSI_TO_GRADE_LINE(-65, -68, 2064), +	RSSI_TO_GRADE_LINE(-61, -66, 2294), +	RSSI_TO_GRADE_LINE(-58, -61, 2580), +	RSSI_TO_GRADE_LINE(-55, -58, 2868), +	RSSI_TO_GRADE_LINE(-46, -55, 3098), +	RSSI_TO_GRADE_LINE(-43, -54, 3442) +}; + +#define MAX_GRADE (rssi_to_grade_map[ARRAY_SIZE(rssi_to_grade_map) - 1].grade) + +#define DEFAULT_CHAN_LOAD_LB	30 +#define DEFAULT_CHAN_LOAD_HB	15 +#define DEFAULT_CHAN_LOAD_UHB	0 + +/* Factors calculation is done with fixed-point with a scaling factor of 1/256 */ +#define SCALE_FACTOR 256 + +/* Convert a percentage from [0,100] to [0,255] */ +#define NORMALIZE_PERCENT_TO_255(percentage) ((percentage) * SCALE_FACTOR / 100) + +static unsigned int +iwl_mvm_get_puncturing_factor(const struct ieee80211_bss_conf *link_conf) +{ +	enum nl80211_chan_width chan_width = +		link_conf->chanreq.oper.width; +	int mhz = nl80211_chan_width_to_mhz(chan_width); +	unsigned int n_subchannels, n_punctured, puncturing_penalty; + +	if (WARN_ONCE(mhz < 20 || mhz > 320, +		      "Invalid channel width : (%d)\n", mhz)) +		return SCALE_FACTOR; + +	/* No puncturing, no penalty */ +	if (mhz < 80) +		return SCALE_FACTOR; + +	/* total number of subchannels */ +	n_subchannels = mhz / 20; +	/* how many of these are punctured */ +	n_punctured = hweight16(link_conf->chanreq.oper.punctured); + +	puncturing_penalty = n_punctured * SCALE_FACTOR / n_subchannels; +	return SCALE_FACTOR - puncturing_penalty; +} + +static unsigned int +iwl_mvm_get_chan_load(struct ieee80211_bss_conf *link_conf) +{ +	struct iwl_mvm_vif_link_info *mvm_link = +		iwl_mvm_vif_from_mac80211(link_conf->vif)->link[link_conf->link_id]; +	const struct element *bss_load_elem; +	const struct ieee80211_bss_load_elem *bss_load; +	enum nl80211_band band = link_conf->chanreq.oper.chan->band; +	unsigned int chan_load; +	u32 chan_load_by_us; + +	rcu_read_lock(); +	bss_load_elem = ieee80211_bss_get_elem(link_conf->bss, +					       WLAN_EID_QBSS_LOAD); + +	/* If there isn't BSS Load element, take the defaults */ +	if (!bss_load_elem || +	    bss_load_elem->datalen != sizeof(*bss_load)) { +		rcu_read_unlock(); +		switch (band) { +		case NL80211_BAND_2GHZ: +			chan_load = DEFAULT_CHAN_LOAD_LB; +			break; +		case NL80211_BAND_5GHZ: +			chan_load = DEFAULT_CHAN_LOAD_HB; +			break; +		case NL80211_BAND_6GHZ: +			chan_load = DEFAULT_CHAN_LOAD_UHB; +			break; +		default: +			chan_load = 0; +			break; +		} +		/* The defaults are given in percentage */ +		return NORMALIZE_PERCENT_TO_255(chan_load); +	} + +	bss_load = (const void *)bss_load_elem->data; +	/* Channel util is in range 0-255 */ +	chan_load = bss_load->channel_util; +	rcu_read_unlock(); + +	if (!mvm_link || !mvm_link->active) +		return chan_load; + +	if (WARN_ONCE(!mvm_link->phy_ctxt, +		      "Active link (%u) without phy ctxt assigned!\n", +		      link_conf->link_id)) +		return chan_load; + +	/* channel load by us is given in percentage */ +	chan_load_by_us = +		NORMALIZE_PERCENT_TO_255(mvm_link->phy_ctxt->channel_load_by_us); + +	/* Use only values that firmware sends that can possibly be valid */ +	if (chan_load_by_us <= chan_load) +		chan_load -= chan_load_by_us; + +	return chan_load; +} + +static unsigned int +iwl_mvm_get_chan_load_factor(struct ieee80211_bss_conf *link_conf) +{ +	return SCALE_FACTOR - iwl_mvm_get_chan_load(link_conf); +} + +/* This function calculates the grade of a link. Returns 0 in error case */ +VISIBLE_IF_IWLWIFI_KUNIT +unsigned int iwl_mvm_get_link_grade(struct ieee80211_bss_conf *link_conf) +{ +	enum nl80211_band band; +	int i, rssi_idx; +	s32 link_rssi; +	unsigned int grade = MAX_GRADE; + +	if (WARN_ON_ONCE(!link_conf)) +		return 0; + +	band = link_conf->chanreq.oper.chan->band; +	if (WARN_ONCE(band != NL80211_BAND_2GHZ && +		      band != NL80211_BAND_5GHZ && +		      band != NL80211_BAND_6GHZ, +		      "Invalid band (%u)\n", band)) +		return 0; + +	link_rssi = MBM_TO_DBM(link_conf->bss->signal); +	/* +	 * For 6 GHz the RSSI of the beacons is lower than +	 * the RSSI of the data. +	 */ +	if (band == NL80211_BAND_6GHZ) +		link_rssi += 4; + +	rssi_idx = band == NL80211_BAND_2GHZ ? 0 : 1; + +	/* No valid RSSI - take the lowest grade */ +	if (!link_rssi) +		link_rssi = rssi_to_grade_map[0].rssi[rssi_idx]; + +	/* Get grade based on RSSI */ +	for (i = 0; i < ARRAY_SIZE(rssi_to_grade_map); i++) { +		const struct iwl_mvm_rssi_to_grade *line = +			&rssi_to_grade_map[i]; + +		if (link_rssi > line->rssi[rssi_idx]) +			continue; +		grade = line->grade; +		break; +	} + +	/* apply the channel load and puncturing factors */ +	grade = grade * iwl_mvm_get_chan_load_factor(link_conf) / SCALE_FACTOR; +	grade = grade * iwl_mvm_get_puncturing_factor(link_conf) / SCALE_FACTOR; +	return grade; +} +EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mvm_get_link_grade); + +static +u8 iwl_mvm_set_link_selection_data(struct ieee80211_vif *vif, +				   struct iwl_mvm_link_sel_data *data, +				   unsigned long usable_links, +				   u8 *best_link_idx) +{ +	u8 n_data = 0; +	u16 max_grade = 0; +	unsigned long link_id; + +	/* TODO: don't select links that weren't discovered in the last scan */ +	for_each_set_bit(link_id, &usable_links, IEEE80211_MLD_MAX_NUM_LINKS) { +		struct ieee80211_bss_conf *link_conf = +			link_conf_dereference_protected(vif, link_id); + +		if (WARN_ON_ONCE(!link_conf)) +			continue; + +		data[n_data].link_id = link_id; +		data[n_data].chandef = &link_conf->chanreq.oper; +		data[n_data].signal = link_conf->bss->signal / 100; +		data[n_data].grade = iwl_mvm_get_link_grade(link_conf); + +		if (data[n_data].grade > max_grade) { +			max_grade = data[n_data].grade; +			*best_link_idx = n_data; +		} +		n_data++; +	} + +	return n_data; +} + +struct iwl_mvm_bw_to_rssi_threshs { +	s8 low; +	s8 high; +}; + +#define BW_TO_RSSI_THRESHOLDS(_bw)				\ +	[IWL_PHY_CHANNEL_MODE ## _bw] = {			\ +		.low = IWL_MVM_LOW_RSSI_THRESH_##_bw##MHZ,	\ +		.high = IWL_MVM_HIGH_RSSI_THRESH_##_bw##MHZ	\ +	} + +s8 iwl_mvm_get_esr_rssi_thresh(struct iwl_mvm *mvm, +			       const struct cfg80211_chan_def *chandef, +			       bool low) +{ +	const struct iwl_mvm_bw_to_rssi_threshs bw_to_rssi_threshs_map[] = { +		BW_TO_RSSI_THRESHOLDS(20), +		BW_TO_RSSI_THRESHOLDS(40), +		BW_TO_RSSI_THRESHOLDS(80), +		BW_TO_RSSI_THRESHOLDS(160) +		/* 320 MHz has the same thresholds as 20 MHz */ +	}; +	const struct iwl_mvm_bw_to_rssi_threshs *threshs; +	u8 chan_width = iwl_mvm_get_channel_width(chandef); + +	if (WARN_ON(chandef->chan->band != NL80211_BAND_2GHZ && +		    chandef->chan->band != NL80211_BAND_5GHZ && +		    chandef->chan->band != NL80211_BAND_6GHZ)) +		return S8_MAX; + +	/* 6 GHz will always use 20 MHz thresholds, regardless of the BW */ +	if (chan_width == IWL_PHY_CHANNEL_MODE320) +		chan_width = IWL_PHY_CHANNEL_MODE20; + +	threshs = &bw_to_rssi_threshs_map[chan_width]; + +	return low ? threshs->low : threshs->high; +} + +static u32 +iwl_mvm_esr_disallowed_with_link(struct iwl_mvm *mvm, +				 struct ieee80211_vif *vif, +				 const struct iwl_mvm_link_sel_data *link, +				 bool primary) +{ +	struct wiphy *wiphy = mvm->hw->wiphy; +	struct ieee80211_bss_conf *conf; +	enum iwl_mvm_esr_state ret = 0; +	s8 thresh; + +	conf = wiphy_dereference(wiphy, vif->link_conf[link->link_id]); +	if (WARN_ON_ONCE(!conf)) +		return false; + +	/* BT Coex effects eSR mode only if one of the links is on LB */ +	if (link->chandef->chan->band == NL80211_BAND_2GHZ && +	    (!iwl_mvm_bt_coex_calculate_esr_mode(mvm, vif, link->signal, +						 primary))) +		ret |= IWL_MVM_ESR_EXIT_COEX; + +	thresh = iwl_mvm_get_esr_rssi_thresh(mvm, link->chandef, +					     false); + +	if (link->signal < thresh) +		ret |= IWL_MVM_ESR_EXIT_LOW_RSSI; + +	if (conf->csa_active) +		ret |= IWL_MVM_ESR_EXIT_CSA; + +	if (ret) { +		IWL_DEBUG_INFO(mvm, +			       "Link %d is not allowed for esr\n", +			       link->link_id); +		iwl_mvm_print_esr_state(mvm, ret); +	} +	return ret; +} + +VISIBLE_IF_IWLWIFI_KUNIT +bool iwl_mvm_mld_valid_link_pair(struct ieee80211_vif *vif, +				 const struct iwl_mvm_link_sel_data *a, +				 const struct iwl_mvm_link_sel_data *b) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	struct iwl_mvm *mvm = mvmvif->mvm; +	enum iwl_mvm_esr_state ret = 0; + +	/* Per-link considerations */ +	if (iwl_mvm_esr_disallowed_with_link(mvm, vif, a, true) || +	    iwl_mvm_esr_disallowed_with_link(mvm, vif, b, false)) +		return false; + +	if (a->chandef->width != b->chandef->width || +	    !(a->chandef->chan->band == NL80211_BAND_6GHZ && +	      b->chandef->chan->band == NL80211_BAND_5GHZ)) +		ret |= IWL_MVM_ESR_EXIT_BANDWIDTH; + +	if (ret) { +		IWL_DEBUG_INFO(mvm, +			       "Links %d and %d are not a valid pair for EMLSR\n", +			       a->link_id, b->link_id); +		iwl_mvm_print_esr_state(mvm, ret); +		return false; +	} + +	return true; + +} +EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mvm_mld_valid_link_pair); + +/* + * Returns the combined eSR grade of two given links. + * Returns 0 if eSR is not allowed with these 2 links. + */ +static +unsigned int iwl_mvm_get_esr_grade(struct ieee80211_vif *vif, +				   const struct iwl_mvm_link_sel_data *a, +				   const struct iwl_mvm_link_sel_data *b, +				   u8 *primary_id) +{ +	struct ieee80211_bss_conf *primary_conf; +	struct wiphy *wiphy = ieee80211_vif_to_wdev(vif)->wiphy; +	unsigned int primary_load; + +	lockdep_assert_wiphy(wiphy); + +	/* a is always primary, b is always secondary */ +	if (b->grade > a->grade) +		swap(a, b); + +	*primary_id = a->link_id; + +	if (!iwl_mvm_mld_valid_link_pair(vif, a, b)) +		return 0; + +	primary_conf = wiphy_dereference(wiphy, vif->link_conf[*primary_id]); + +	if (WARN_ON_ONCE(!primary_conf)) +		return 0; + +	primary_load = iwl_mvm_get_chan_load(primary_conf); + +	return a->grade + +		((b->grade * primary_load) / SCALE_FACTOR); +} + +void iwl_mvm_select_links(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +	struct iwl_mvm_link_sel_data data[IEEE80211_MLD_MAX_NUM_LINKS]; +	struct iwl_mvm_link_sel_data *best_link; +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	u32 max_active_links = iwl_mvm_max_active_links(mvm, vif); +	u16 usable_links = ieee80211_vif_usable_links(vif); +	u8 best, primary_link, best_in_pair, n_data; +	u16 max_esr_grade = 0, new_active_links; + +	lockdep_assert_wiphy(mvm->hw->wiphy); + +	if (!mvmvif->authorized || !ieee80211_vif_is_mld(vif)) +		return; + +	if (!IWL_MVM_AUTO_EML_ENABLE) +		return; + +	/* The logic below is a simple version that doesn't suit more than 2 +	 * links +	 */ +	WARN_ON_ONCE(max_active_links > 2); + +	n_data = iwl_mvm_set_link_selection_data(vif, data, usable_links, +						 &best); + +	if (WARN(!n_data, "Couldn't find a valid grade for any link!\n")) +		return; + +	best_link = &data[best]; +	primary_link = best_link->link_id; +	new_active_links = BIT(best_link->link_id); + +	/* eSR is not supported/blocked, or only one usable link */ +	if (max_active_links == 1 || !iwl_mvm_vif_has_esr_cap(mvm, vif) || +	    mvmvif->esr_disable_reason || n_data == 1) +		goto set_active; + +	for (u8 a = 0; a < n_data; a++) +		for (u8 b = a + 1; b < n_data; b++) { +			u16 esr_grade = iwl_mvm_get_esr_grade(vif, &data[a], +							      &data[b], +							      &best_in_pair); + +			if (esr_grade <= max_esr_grade) +				continue; + +			max_esr_grade = esr_grade; +			primary_link = best_in_pair; +			new_active_links = BIT(data[a].link_id) | +					   BIT(data[b].link_id); +		} + +	/* No valid pair was found, go with the best link */ +	if (hweight16(new_active_links) <= 1) +		goto set_active; + +	/* For equal grade - prefer EMLSR */ +	if (best_link->grade > max_esr_grade) { +		primary_link = best_link->link_id; +		new_active_links = BIT(best_link->link_id); +	} +set_active: +	IWL_DEBUG_INFO(mvm, "Link selection result: 0x%x. Primary = %d\n", +		       new_active_links, primary_link); +	ieee80211_set_active_links_async(vif, new_active_links); +	mvmvif->link_selection_res = new_active_links; +	mvmvif->link_selection_primary = primary_link; +} + +u8 iwl_mvm_get_primary_link(struct ieee80211_vif *vif) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + +	/* relevant data is written with both locks held, so read with either */ +	lockdep_assert(lockdep_is_held(&mvmvif->mvm->mutex) || +		       lockdep_is_held(&mvmvif->mvm->hw->wiphy->mtx)); + +	if (!ieee80211_vif_is_mld(vif)) +		return 0; + +	/* In AP mode, there is no primary link */ +	if (vif->type == NL80211_IFTYPE_AP) +		return __ffs(vif->active_links); + +	if (mvmvif->esr_active && +	    !WARN_ON(!(BIT(mvmvif->primary_link) & vif->active_links))) +		return mvmvif->primary_link; + +	return __ffs(vif->active_links); +} + +/* + * For non-MLO/single link, this will return the deflink/single active link, + * respectively + */ +u8 iwl_mvm_get_other_link(struct ieee80211_vif *vif, u8 link_id) +{ +	switch (hweight16(vif->active_links)) { +	case 0: +		return 0; +	default: +		WARN_ON(1); +		fallthrough; +	case 1: +		return __ffs(vif->active_links); +	case 2: +		return __ffs(vif->active_links & ~BIT(link_id)); +	} +} + +/* Reasons that can cause esr prevention */ +#define IWL_MVM_ESR_PREVENT_REASONS	IWL_MVM_ESR_EXIT_MISSED_BEACON +#define IWL_MVM_PREVENT_ESR_TIMEOUT	(HZ * 400) +#define IWL_MVM_ESR_PREVENT_SHORT	(HZ * 300) +#define IWL_MVM_ESR_PREVENT_LONG	(HZ * 600) + +static bool iwl_mvm_check_esr_prevention(struct iwl_mvm *mvm, +					 struct iwl_mvm_vif *mvmvif, +					 enum iwl_mvm_esr_state reason) +{ +	bool timeout_expired = time_after(jiffies, +					  mvmvif->last_esr_exit.ts + +					  IWL_MVM_PREVENT_ESR_TIMEOUT); +	unsigned long delay; + +	lockdep_assert_held(&mvm->mutex); + +	/* Only handle reasons that can cause prevention */ +	if (!(reason & IWL_MVM_ESR_PREVENT_REASONS)) +		return false; + +	/* +	 * Reset the counter if more than 400 seconds have passed between one +	 * exit and the other, or if we exited due to a different reason. +	 * Will also reset the counter after the long prevention is done. +	 */ +	if (timeout_expired || mvmvif->last_esr_exit.reason != reason) { +		mvmvif->exit_same_reason_count = 1; +		return false; +	} + +	mvmvif->exit_same_reason_count++; +	if (WARN_ON(mvmvif->exit_same_reason_count < 2 || +		    mvmvif->exit_same_reason_count > 3)) +		return false; + +	mvmvif->esr_disable_reason |= IWL_MVM_ESR_BLOCKED_PREVENTION; + +	/* +	 * For the second exit, use a short prevention, and for the third one, +	 * use a long prevention. +	 */ +	delay = mvmvif->exit_same_reason_count == 2 ? +		IWL_MVM_ESR_PREVENT_SHORT : +		IWL_MVM_ESR_PREVENT_LONG; + +	IWL_DEBUG_INFO(mvm, +		       "Preventing EMLSR for %ld seconds due to %u exits with the reason = %s (0x%x)\n", +		       delay / HZ, mvmvif->exit_same_reason_count, +		       iwl_get_esr_state_string(reason), reason); + +	wiphy_delayed_work_queue(mvm->hw->wiphy, +				 &mvmvif->prevent_esr_done_wk, delay); +	return true; +} + +#define IWL_MVM_TRIGGER_LINK_SEL_TIME (IWL_MVM_TRIGGER_LINK_SEL_TIME_SEC * HZ) + +/* API to exit eSR mode */ +void iwl_mvm_exit_esr(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +		      enum iwl_mvm_esr_state reason, +		      u8 link_to_keep) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	u16 new_active_links; +	bool prevented; + +	lockdep_assert_held(&mvm->mutex); + +	/* Nothing to do */ +	if (!mvmvif->esr_active) +		return; + +	if (WARN_ON(!ieee80211_vif_is_mld(vif) || !mvmvif->authorized)) +		return; + +	if (WARN_ON(!(vif->active_links & BIT(link_to_keep)))) +		link_to_keep = __ffs(vif->active_links); + +	new_active_links = BIT(link_to_keep); +	IWL_DEBUG_INFO(mvm, +		       "Exiting EMLSR. reason = %s (0x%x). Current active links=0x%x, new active links = 0x%x\n", +		       iwl_get_esr_state_string(reason), reason, +		       vif->active_links, new_active_links); + +	ieee80211_set_active_links_async(vif, new_active_links); + +	/* Prevent EMLSR if needed */ +	prevented = iwl_mvm_check_esr_prevention(mvm, mvmvif, reason); + +	/* Remember why and when we exited EMLSR */ +	mvmvif->last_esr_exit.ts = jiffies; +	mvmvif->last_esr_exit.reason = reason; + +	/* +	 * If EMLSR is prevented now - don't try to get back to EMLSR. +	 * If we exited due to a blocking event, we will try to get back to +	 * EMLSR when the corresponding unblocking event will happen. +	 */ +	if (prevented || reason & IWL_MVM_BLOCK_ESR_REASONS) +		return; + +	/* If EMLSR is not blocked - try enabling it again in 30 seconds */ +	wiphy_delayed_work_queue(mvm->hw->wiphy, +				 &mvmvif->mlo_int_scan_wk, +				 round_jiffies_relative(IWL_MVM_TRIGGER_LINK_SEL_TIME)); +} + +void iwl_mvm_block_esr(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +		       enum iwl_mvm_esr_state reason, +		       u8 link_to_keep) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + +	lockdep_assert_held(&mvm->mutex); + +	/* This should be called only with disable reasons */ +	if (WARN_ON(!(reason & IWL_MVM_BLOCK_ESR_REASONS))) +		return; + +	if (!(mvmvif->esr_disable_reason & reason)) { +		IWL_DEBUG_INFO(mvm, +			       "Blocking EMLSR mode. reason = %s (0x%x)\n", +			       iwl_get_esr_state_string(reason), reason); +		iwl_mvm_print_esr_state(mvm, mvmvif->esr_disable_reason); +	} + +	mvmvif->esr_disable_reason |= reason; + +	iwl_mvm_exit_esr(mvm, vif, reason, link_to_keep); +} + +int iwl_mvm_block_esr_sync(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +			   enum iwl_mvm_esr_state reason) +{ +	int primary_link = iwl_mvm_get_primary_link(vif); +	int ret; + +	if (!IWL_MVM_AUTO_EML_ENABLE || !ieee80211_vif_is_mld(vif)) +		return 0; + +	/* This should be called only with blocking reasons */ +	if (WARN_ON(!(reason & IWL_MVM_BLOCK_ESR_REASONS))) +		return 0; + +	/* leave ESR immediately, not only async with iwl_mvm_block_esr() */ +	ret = ieee80211_set_active_links(vif, BIT(primary_link)); +	if (ret) +		return ret; + +	mutex_lock(&mvm->mutex); +	/* only additionally block for consistency and to avoid concurrency */ +	iwl_mvm_block_esr(mvm, vif, reason, primary_link); +	mutex_unlock(&mvm->mutex); + +	return 0; +} + +static void iwl_mvm_esr_unblocked(struct iwl_mvm *mvm, +				  struct ieee80211_vif *vif) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	bool need_new_sel = time_after(jiffies, mvmvif->last_esr_exit.ts + +						IWL_MVM_TRIGGER_LINK_SEL_TIME); + +	lockdep_assert_held(&mvm->mutex); + +	if (!ieee80211_vif_is_mld(vif) || !mvmvif->authorized || +	    mvmvif->esr_active) +		return; + +	IWL_DEBUG_INFO(mvm, "EMLSR is unblocked\n"); + +	/* +	 * If EMLSR was blocked for more than 30 seconds, or the last link +	 * selection decided to not enter EMLSR, trigger a new scan. +	 */ +	if (need_new_sel || hweight16(mvmvif->link_selection_res) < 2) { +		IWL_DEBUG_INFO(mvm, "Trigger MLO scan\n"); +		wiphy_delayed_work_queue(mvm->hw->wiphy, +					 &mvmvif->mlo_int_scan_wk, 0); +	/* +	 * If EMLSR was blocked for less than 30 seconds, and the last link +	 * selection decided to use EMLSR, activate EMLSR using the previous +	 * link selection result. +	 */ +	} else { +		IWL_DEBUG_INFO(mvm, +			       "Use the latest link selection result: 0x%x\n", +			       mvmvif->link_selection_res); +		ieee80211_set_active_links_async(vif, +						 mvmvif->link_selection_res); +	} +} + +void iwl_mvm_unblock_esr(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +			 enum iwl_mvm_esr_state reason) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + +	lockdep_assert_held(&mvm->mutex); + +	/* This should be called only with disable reasons */ +	if (WARN_ON(!(reason & IWL_MVM_BLOCK_ESR_REASONS))) +		return; + +	/* No Change */ +	if (!(mvmvif->esr_disable_reason & reason)) +		return; + +	mvmvif->esr_disable_reason &= ~reason; + +	IWL_DEBUG_INFO(mvm, +		       "Unblocking EMLSR mode. reason = %s (0x%x)\n", +		       iwl_get_esr_state_string(reason), reason); +	iwl_mvm_print_esr_state(mvm, mvmvif->esr_disable_reason); + +	if (!mvmvif->esr_disable_reason) +		iwl_mvm_esr_unblocked(mvm, vif); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac-ctxt.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac-ctxt.c index 228ede7b8957..5a06f887769a 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mac-ctxt.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac-ctxt.c @@ -1163,6 +1163,13 @@ static int iwl_mvm_mac_ctxt_send_beacon_v9(struct iwl_mvm *mvm,  						   WLAN_EID_EXT_CHANSWITCH_ANN,  						   beacon->len)); +	if (vif->type == NL80211_IFTYPE_AP && +	    iwl_fw_lookup_cmd_ver(mvm->fw, BEACON_TEMPLATE_CMD, 0) >= 14) +		beacon_cmd.btwt_offset = +			cpu_to_le32(iwl_mvm_find_ie_offset(beacon->data, +							   WLAN_EID_S1G_TWT, +							   beacon->len)); +  	return iwl_mvm_mac_ctxt_send_beacon_cmd(mvm, beacon, &beacon_cmd,  						sizeof(beacon_cmd));  } @@ -1591,23 +1598,23 @@ void iwl_mvm_rx_missed_beacons_notif(struct iwl_mvm *mvm,  	u32 id = le32_to_cpu(mb->link_id);  	union iwl_dbg_tlv_tp_data tp_data = { .fw_pkt = pkt };  	u32 mac_type; +	int link_id = -1;  	u8 notif_ver = iwl_fw_lookup_notif_ver(mvm->fw, LEGACY_GROUP,  					       MISSED_BEACONS_NOTIFICATION,  					       0); -	rcu_read_lock(); -  	/* before version four the ID in the notification refers to mac ID */  	if (notif_ver < 4) { -		vif = iwl_mvm_rcu_dereference_vif_id(mvm, id, true); +		vif = iwl_mvm_rcu_dereference_vif_id(mvm, id, false);  	} else {  		struct ieee80211_bss_conf *bss_conf = -			iwl_mvm_rcu_fw_link_id_to_link_conf(mvm, id, true); +			iwl_mvm_rcu_fw_link_id_to_link_conf(mvm, id, false);  		if (!bss_conf) -			goto out; +			return;  		vif = bss_conf->vif; +		link_id = bss_conf->link_id;  	}  	IWL_DEBUG_INFO(mvm, @@ -1620,7 +1627,7 @@ void iwl_mvm_rx_missed_beacons_notif(struct iwl_mvm *mvm,  		       le32_to_cpu(mb->num_expected_beacons));  	if (!vif) -		goto out; +		return;  	mac_type = iwl_mvm_get_mac_type(vif); @@ -1647,6 +1654,10 @@ void iwl_mvm_rx_missed_beacons_notif(struct iwl_mvm *mvm,  				 "missed_beacons:%d, missed_beacons_since_rx:%d\n",  				 rx_missed_bcon, rx_missed_bcon_since_rx);  		} +	} else if (rx_missed_bcon >= IWL_MVM_MISSED_BEACONS_EXIT_ESR_THRESH && +		   link_id >= 0 && hweight16(vif->active_links) > 1) { +		iwl_mvm_exit_esr(mvm, vif, IWL_MVM_ESR_EXIT_MISSED_BEACON, +				 iwl_mvm_get_other_link(vif, link_id));  	} else if (rx_missed_bcon_since_rx > IWL_MVM_MISSED_BEACONS_THRESHOLD) {  		if (!iwl_mvm_has_new_tx_api(mvm))  			ieee80211_beacon_loss(vif); @@ -1660,7 +1671,7 @@ void iwl_mvm_rx_missed_beacons_notif(struct iwl_mvm *mvm,  	trigger = iwl_fw_dbg_trigger_on(&mvm->fwrt, ieee80211_vif_to_wdev(vif),  					FW_DBG_TRIGGER_MISSED_BEACONS);  	if (!trigger) -		goto out; +		return;  	bcon_trig = (void *)trigger->data;  	stop_trig_missed_bcon = le32_to_cpu(bcon_trig->stop_consec_missed_bcon); @@ -1672,9 +1683,6 @@ void iwl_mvm_rx_missed_beacons_notif(struct iwl_mvm *mvm,  	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, diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c index 8f4b063d6243..486a6b8f3c97 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c @@ -359,8 +359,11 @@ int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm)  	/* Set this early since we need to have it for the check below */  	if (mvm->mld_api_is_used && mvm->nvm_data->sku_cap_11be_enable &&  	    !iwlwifi_mod_params.disable_11ax && -	    !iwlwifi_mod_params.disable_11be) +	    !iwlwifi_mod_params.disable_11be) {  		hw->wiphy->flags |= WIPHY_FLAG_DISABLE_WEXT; +		/* we handle this already earlier, but need it for MLO */ +		ieee80211_hw_set(hw, HANDLES_QUIET_CSA); +	}  	/* With MLD FW API, it tracks timing by itself,  	 * no need for any timing from the host @@ -720,6 +723,8 @@ int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm)  	mvm->rts_threshold = IEEE80211_MAX_RTS_THRESHOLD; +	ieee80211_hw_set(hw, DISALLOW_PUNCTURING_5GHZ); +  #ifdef CONFIG_PM_SLEEP  	if ((unified || mvm->fw->img[IWL_UCODE_WOWLAN].num_sec) &&  	    mvm->trans->ops->d3_suspend && @@ -903,6 +908,8 @@ void iwl_mvm_mac_itxq_xmit(struct ieee80211_hw *hw, struct ieee80211_txq *txq)  					&mvmtxq->state) &&  			      !test_bit(IWL_MVM_TXQ_STATE_STOP_REDIRECT,  					&mvmtxq->state) && +			      !test_bit(IWL_MVM_TXQ_STATE_STOP_AP_CSA, +					&mvmtxq->state) &&  			      !test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status))) {  			skb = ieee80211_tx_dequeue(hw, txq); @@ -1097,15 +1104,21 @@ static void iwl_mvm_cleanup_iterator(void *data, u8 *mac,  	iwl_mvm_te_clear_data(mvm, &mvmvif->time_event_data);  	spin_unlock_bh(&mvm->time_event_lock); -	memset(&mvmvif->bf_data, 0, sizeof(mvmvif->bf_data)); +	mvmvif->bf_enabled = false; +	mvmvif->ba_enabled = false;  	mvmvif->ap_sta = NULL; +	mvmvif->esr_active = false; +	vif->driver_flags &= ~IEEE80211_VIF_EML_ACTIVE; +  	for_each_mvm_vif_valid_link(mvmvif, link_id) {  		mvmvif->link[link_id]->ap_sta_id = IWL_MVM_INVALID_STA;  		mvmvif->link[link_id]->fw_link_id = IWL_MVM_FW_LINK_ID_INVALID;  		mvmvif->link[link_id]->phy_ctxt = NULL;  		mvmvif->link[link_id]->active = 0;  		mvmvif->link[link_id]->igtk = NULL; +		memset(&mvmvif->link[link_id]->bf_data, 0, +		       sizeof(mvmvif->link[link_id]->bf_data));  	}  	probe_data = rcu_dereference_protected(mvmvif->deflink.probe_resp_data, @@ -1332,6 +1345,13 @@ void iwl_mvm_mac_stop(struct ieee80211_hw *hw)  {  	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); +	/* Stop internal MLO scan, if running */ +	mutex_lock(&mvm->mutex); +	iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_INT_MLO, false); +	mutex_unlock(&mvm->mutex); + +	wiphy_work_cancel(mvm->hw->wiphy, &mvm->trig_link_selection_wk); +	wiphy_work_flush(mvm->hw->wiphy, &mvm->async_handlers_wiphy_wk);  	flush_work(&mvm->async_handlers_wk);  	flush_work(&mvm->add_stream_wk); @@ -1399,7 +1419,9 @@ int iwl_mvm_set_tx_power(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  	if (tx_power == IWL_DEFAULT_MAX_TX_POWER)  		cmd.common.pwr_restriction = cpu_to_le16(IWL_DEV_MAX_TX_POWER); -	if (cmd_ver == 7) +	if (cmd_ver == 8) +		len = sizeof(cmd.v8); +	else if (cmd_ver == 7)  		len = sizeof(cmd.v7);  	else if (cmd_ver == 6)  		len = sizeof(cmd.v6); @@ -1418,6 +1440,20 @@ int iwl_mvm_set_tx_power(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  	return iwl_mvm_send_cmd_pdu(mvm, cmd_id, 0, len, &cmd);  } +static void iwl_mvm_post_csa_tx(void *data, struct ieee80211_sta *sta) +{ +	struct ieee80211_hw *hw = data; +	int i; + +	for (i = 0; i < ARRAY_SIZE(sta->txq); i++) { +		struct iwl_mvm_txq *mvmtxq = +			iwl_mvm_txq_from_mac80211(sta->txq[i]); + +		clear_bit(IWL_MVM_TXQ_STATE_STOP_AP_CSA, &mvmtxq->state); +		iwl_mvm_mac_itxq_xmit(hw, sta->txq[i]); +	} +} +  int iwl_mvm_post_channel_switch(struct ieee80211_hw *hw,  				struct ieee80211_vif *vif,  				struct ieee80211_bss_conf *link_conf) @@ -1434,6 +1470,7 @@ int iwl_mvm_post_channel_switch(struct ieee80211_hw *hw,  		u8 ap_sta_id = mvmvif->link[link_id]->ap_sta_id;  		mvmvif->csa_bcn_pending = false; +		mvmvif->csa_blocks_tx = false;  		mvmsta = iwl_mvm_sta_from_staid_protected(mvm, ap_sta_id);  		if (WARN_ON(!mvmsta)) { @@ -1455,6 +1492,18 @@ int iwl_mvm_post_channel_switch(struct ieee80211_hw *hw,  			iwl_mvm_stop_session_protection(mvm, vif);  		} +	} else if (vif->type == NL80211_IFTYPE_AP && mvmvif->csa_blocks_tx) { +		struct iwl_mvm_txq *mvmtxq = +			iwl_mvm_txq_from_mac80211(vif->txq); + +		clear_bit(IWL_MVM_TXQ_STATE_STOP_AP_CSA, &mvmtxq->state); + +		local_bh_disable(); +		iwl_mvm_mac_itxq_xmit(hw, vif->txq); +		ieee80211_iterate_stations_atomic(hw, iwl_mvm_post_csa_tx, hw); +		local_bh_enable(); + +		mvmvif->csa_blocks_tx = false;  	}  	mvmvif->ps_disabled = false; @@ -1564,6 +1613,65 @@ static int iwl_mvm_alloc_bcast_mcast_sta(struct iwl_mvm *mvm,  					IWL_STA_MULTICAST);  } +static void iwl_mvm_prevent_esr_done_wk(struct wiphy *wiphy, +					struct wiphy_work *wk) +{ +	struct iwl_mvm_vif *mvmvif = +		container_of(wk, struct iwl_mvm_vif, prevent_esr_done_wk.work); +	struct iwl_mvm *mvm = mvmvif->mvm; +	struct ieee80211_vif *vif = iwl_mvm_get_bss_vif(mvm); + +	mutex_lock(&mvm->mutex); +	iwl_mvm_unblock_esr(mvm, vif, IWL_MVM_ESR_BLOCKED_PREVENTION); +	mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_mlo_int_scan_wk(struct wiphy *wiphy, struct wiphy_work *wk) +{ +	struct iwl_mvm_vif *mvmvif = container_of(wk, struct iwl_mvm_vif, +						  mlo_int_scan_wk.work); +	struct ieee80211_vif *vif = +		container_of((void *)mvmvif, struct ieee80211_vif, drv_priv); + +	mutex_lock(&mvmvif->mvm->mutex); + +	iwl_mvm_int_mlo_scan(mvmvif->mvm, vif); + +	mutex_unlock(&mvmvif->mvm->mutex); +} + +static void iwl_mvm_unblock_esr_tpt(struct wiphy *wiphy, struct wiphy_work *wk) +{ +	struct iwl_mvm_vif *mvmvif = +		container_of(wk, struct iwl_mvm_vif, unblock_esr_tpt_wk); +	struct iwl_mvm *mvm = mvmvif->mvm; +	struct ieee80211_vif *vif = iwl_mvm_get_bss_vif(mvm); + +	mutex_lock(&mvm->mutex); +	iwl_mvm_unblock_esr(mvm, vif, IWL_MVM_ESR_BLOCKED_TPT); +	mutex_unlock(&mvm->mutex); +} + +void iwl_mvm_mac_init_mvmvif(struct iwl_mvm *mvm, struct iwl_mvm_vif *mvmvif) +{ +	lockdep_assert_held(&mvm->mutex); + +	if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) +		return; + +	INIT_DELAYED_WORK(&mvmvif->csa_work, +			  iwl_mvm_channel_switch_disconnect_wk); + +	wiphy_delayed_work_init(&mvmvif->prevent_esr_done_wk, +				iwl_mvm_prevent_esr_done_wk); + +	wiphy_delayed_work_init(&mvmvif->mlo_int_scan_wk, +				iwl_mvm_mlo_int_scan_wk); + +	wiphy_work_init(&mvmvif->unblock_esr_tpt_wk, +			iwl_mvm_unblock_esr_tpt); +} +  static int iwl_mvm_mac_add_interface(struct ieee80211_hw *hw,  				     struct ieee80211_vif *vif)  { @@ -1574,6 +1682,8 @@ static int iwl_mvm_mac_add_interface(struct ieee80211_hw *hw,  	mutex_lock(&mvm->mutex); +	iwl_mvm_mac_init_mvmvif(mvm, mvmvif); +  	mvmvif->mvm = mvm;  	/* the first link always points to the default one */ @@ -1651,15 +1761,10 @@ static int iwl_mvm_mac_add_interface(struct ieee80211_hw *hw,  				     IEEE80211_VIF_SUPPORTS_CQM_RSSI;  	} -	if (vif->p2p || iwl_fw_lookup_cmd_ver(mvm->fw, PHY_CONTEXT_CMD, 1) < 5) -		vif->driver_flags |= IEEE80211_VIF_IGNORE_OFDMA_WIDER_BW; -  	if (vif->type == NL80211_IFTYPE_P2P_DEVICE)  		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; @@ -1697,6 +1802,8 @@ out:  void iwl_mvm_prepare_mac_removal(struct iwl_mvm *mvm,  				 struct ieee80211_vif *vif)  { +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +  	if (vif->type == NL80211_IFTYPE_P2P_DEVICE) {  		/*  		 * Flush the ROC worker which will flush the OFFCHANNEL queue. @@ -1705,6 +1812,16 @@ void iwl_mvm_prepare_mac_removal(struct iwl_mvm *mvm,  		 */  		flush_work(&mvm->roc_done_wk);  	} + +	wiphy_delayed_work_cancel(mvm->hw->wiphy, +				  &mvmvif->prevent_esr_done_wk); + +	wiphy_delayed_work_cancel(mvm->hw->wiphy, +				  &mvmvif->mlo_int_scan_wk); + +	wiphy_work_cancel(mvm->hw->wiphy, &mvmvif->unblock_esr_tpt_wk); + +	cancel_delayed_work_sync(&mvmvif->csa_work);  }  /* This function is doing the common part of removing the interface for @@ -2545,6 +2662,7 @@ void iwl_mvm_bss_info_changed_station_assoc(struct iwl_mvm *mvm,  {  	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);  	int ret; +	int link_id;  	/* The firmware tracks the MU-MIMO group on its own.  	 * However, on HW restart we should restore this data. @@ -2560,7 +2678,8 @@ void iwl_mvm_bss_info_changed_station_assoc(struct iwl_mvm *mvm,  	iwl_mvm_recalc_multicast(mvm);  	/* reset rssi values */ -	mvmvif->bf_data.ave_beacon_signal = 0; +	for_each_mvm_vif_valid_link(mvmvif, link_id) +		mvmvif->link[link_id]->bf_data.ave_beacon_signal = 0;  	iwl_mvm_bt_coex_vif_change(mvm);  	iwl_mvm_update_smps_on_active_links(mvm, vif, IWL_MVM_SMPS_REQ_TT, @@ -2601,10 +2720,14 @@ iwl_mvm_bss_info_changed_station_common(struct iwl_mvm *mvm,  	}  	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) { +		struct iwl_mvm_vif_link_info *link_info = +			mvmvif->link[link_conf->link_id]; + +		IWL_DEBUG_MAC80211(mvm, "CQM info_changed\n"); +		if (link_info) +			link_info->bf_data.last_cqm_event = 0; + +		if (mvmvif->bf_enabled) {  			/* FIXME: need to update per link when FW API will  			 * support it  			 */ @@ -3816,15 +3939,29 @@ iwl_mvm_sta_state_assoc_to_authorized(struct iwl_mvm *mvm,  		mvmvif->authorized = 1; +		if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { +			mvmvif->link_selection_res = vif->active_links; +			mvmvif->link_selection_primary = +				vif->active_links ? __ffs(vif->active_links) : 0; +		} +  		callbacks->mac_ctxt_changed(mvm, vif, false);  		iwl_mvm_mei_host_associated(mvm, vif, mvm_sta); +		memset(&mvmvif->last_esr_exit, 0, +		       sizeof(mvmvif->last_esr_exit)); + +		iwl_mvm_block_esr(mvm, vif, IWL_MVM_ESR_BLOCKED_TPT, 0); + +		/* Block until FW notif will arrive */ +		iwl_mvm_block_esr(mvm, vif, IWL_MVM_ESR_BLOCKED_FW, 0); +  		/* when client is authorized (AP station marked as such), -		 * try to enable more links +		 * try to enable the best link(s).  		 */  		if (vif->type == NL80211_IFTYPE_STATION &&  		    !test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) -			iwl_mvm_mld_select_links(mvm, vif, false); +			iwl_mvm_select_links(mvm, vif);  	}  	mvm_sta->authorized = true; @@ -3868,9 +4005,22 @@ iwl_mvm_sta_state_authorized_to_assoc(struct iwl_mvm *mvm,  		 * time.  		 */  		mvmvif->authorized = 0; +		mvmvif->link_selection_res = 0;  		/* disable beacon filtering */  		iwl_mvm_disable_beacon_filter(mvm, vif); + +		wiphy_delayed_work_cancel(mvm->hw->wiphy, +					  &mvmvif->prevent_esr_done_wk); + +		wiphy_delayed_work_cancel(mvm->hw->wiphy, +					  &mvmvif->mlo_int_scan_wk); + +		wiphy_work_cancel(mvm->hw->wiphy, &mvmvif->unblock_esr_tpt_wk); + +		/* No need for the periodic statistics anymore */ +		if (ieee80211_vif_is_mld(vif) && mvmvif->esr_active) +			iwl_mvm_request_periodic_system_statistics(mvm, false);  	}  	return 0; @@ -4684,6 +4834,10 @@ int iwl_mvm_roc_common(struct ieee80211_hw *hw, struct ieee80211_vif *vif,  	 */  	flush_work(&mvm->roc_done_wk); +	ret = iwl_mvm_esr_non_bss_link(mvm, vif, 0, true); +	if (ret) +		return ret; +  	mutex_lock(&mvm->mutex);  	switch (vif->type) { @@ -4727,9 +4881,7 @@ int iwl_mvm_cancel_roc(struct ieee80211_hw *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; @@ -5398,7 +5550,7 @@ static int iwl_mvm_old_pre_chan_sw_sta(struct iwl_mvm *mvm,  	if (chsw->block_tx)  		iwl_mvm_csa_client_absent(mvm, vif); -	if (mvmvif->bf_data.bf_enabled) { +	if (mvmvif->bf_enabled) {  		int ret = iwl_mvm_disable_beacon_filter(mvm, vif);  		if (ret) @@ -5411,19 +5563,32 @@ static int iwl_mvm_old_pre_chan_sw_sta(struct iwl_mvm *mvm,  	return 0;  } +static void iwl_mvm_csa_block_txqs(void *data, struct ieee80211_sta *sta) +{ +	int i; + +	for (i = 0; i < ARRAY_SIZE(sta->txq); i++) { +		struct iwl_mvm_txq *mvmtxq = +			iwl_mvm_txq_from_mac80211(sta->txq[i]); + +		set_bit(IWL_MVM_TXQ_STATE_STOP_AP_CSA, &mvmtxq->state); +	} +} +  #define IWL_MAX_CSA_BLOCK_TX 1500 -int iwl_mvm_pre_channel_switch(struct ieee80211_hw *hw, +int iwl_mvm_pre_channel_switch(struct iwl_mvm *mvm,  			       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); +	struct iwl_mvm_txq *mvmtxq;  	int ret; -	mutex_lock(&mvm->mutex); +	lockdep_assert_held(&mvm->mutex);  	mvmvif->csa_failed = false; +	mvmvif->csa_blocks_tx = false;  	IWL_DEBUG_MAC80211(mvm, "pre CSA to freq %d\n",  			   chsw->chandef.center_freq1); @@ -5438,30 +5603,38 @@ int iwl_mvm_pre_channel_switch(struct ieee80211_hw *hw,  			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; -		} +			      "Another CSA is already in progress")) +			return -EBUSY;  		/* 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; -		} +					      lockdep_is_held(&mvm->mutex))) +			return -EBUSY;  		rcu_assign_pointer(mvm->csa_vif, vif);  		if (WARN_ONCE(mvmvif->csa_countdown, -			      "Previous CSA countdown didn't complete")) { -			ret = -EBUSY; -			goto out_unlock; -		} +			      "Previous CSA countdown didn't complete")) +			return -EBUSY;  		mvmvif->csa_target_freq = chsw->chandef.chan->center_freq; +		if (!chsw->block_tx) +			break; +		/* don't need blocking in driver otherwise - mac80211 will do */ +		if (!ieee80211_hw_check(mvm->hw, HANDLES_QUIET_CSA)) +			break; + +		mvmvif->csa_blocks_tx = true; +		mvmtxq = iwl_mvm_txq_from_mac80211(vif->txq); +		set_bit(IWL_MVM_TXQ_STATE_STOP_AP_CSA, &mvmtxq->state); +		ieee80211_iterate_stations_atomic(mvm->hw, +						  iwl_mvm_csa_block_txqs, +						  NULL);  		break;  	case NL80211_IFTYPE_STATION: +		mvmvif->csa_blocks_tx = chsw->block_tx; +  		/*  		 * In the new flow FW is in charge of timing the switch so there  		 * is no need for all of this @@ -5476,10 +5649,8 @@ int iwl_mvm_pre_channel_switch(struct ieee80211_hw *hw,  		 * 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 (!vif->cfg.assoc || !vif->bss_conf.dtim_period) +			return -EBUSY;  		if (chsw->delay > IWL_MAX_CSA_BLOCK_TX &&  		    hweight16(vif->valid_links) <= 1) @@ -5501,7 +5672,7 @@ int iwl_mvm_pre_channel_switch(struct ieee80211_hw *hw,  				 IWL_UCODE_TLV_CAPA_CHANNEL_SWITCH_CMD)) {  			ret = iwl_mvm_old_pre_chan_sw_sta(mvm, vif, chsw);  			if (ret) -				goto out_unlock; +				return ret;  		} else {  			iwl_mvm_schedule_client_csa(mvm, vif, chsw);  		} @@ -5517,12 +5688,23 @@ int iwl_mvm_pre_channel_switch(struct ieee80211_hw *hw,  	ret = iwl_mvm_power_update_ps(mvm);  	if (ret) -		goto out_unlock; +		return ret;  	/* we won't be on this channel any longer */  	iwl_mvm_teardown_tdls_peers(mvm); -out_unlock: +	return ret; +} + +static int iwl_mvm_mac_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); +	int ret; + +	mutex_lock(&mvm->mutex); +	ret = iwl_mvm_pre_channel_switch(mvm, vif, chsw);  	mutex_unlock(&mvm->mutex);  	return ret; @@ -5626,8 +5808,8 @@ static void iwl_mvm_flush_no_vif(struct iwl_mvm *mvm, u32 queues, bool drop)  void iwl_mvm_mac_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif,  		       u32 queues, bool drop)  { +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);  	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); -	struct iwl_mvm_vif *mvmvif;  	struct iwl_mvm_sta *mvmsta;  	struct ieee80211_sta *sta;  	bool ap_sta_done = false; @@ -5639,11 +5821,22 @@ void iwl_mvm_mac_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif,  		return;  	} +	if (!drop && hweight16(vif->active_links) <= 1) { +		int link_id = vif->active_links ? __ffs(vif->active_links) : 0; +		struct ieee80211_bss_conf *link_conf; + +		link_conf = wiphy_dereference(hw->wiphy, +					      vif->link_conf[link_id]); +		if (WARN_ON(!link_conf)) +			return; +		if (link_conf->csa_active && mvmvif->csa_blocks_tx) +			drop = true; +	} +  	/* 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++) { @@ -5706,6 +5899,65 @@ void iwl_mvm_mac_flush_sta(struct ieee80211_hw *hw, struct ieee80211_vif *vif,  	mutex_unlock(&mvm->mutex);  } +static int iwl_mvm_mac_get_acs_survey(struct iwl_mvm *mvm, int idx, +				      struct survey_info *survey) +{ +	int chan_idx; +	enum nl80211_band band; +	int ret; + +	mutex_lock(&mvm->mutex); + +	if (!mvm->acs_survey) { +		ret = -ENOENT; +		goto out; +	} + +	/* Find and return the next entry that has a non-zero active time */ +	for (band = 0; band < NUM_NL80211_BANDS; band++) { +		struct ieee80211_supported_band *sband = +			mvm->hw->wiphy->bands[band]; + +		if (!sband) +			continue; + +		for (chan_idx = 0; chan_idx < sband->n_channels; chan_idx++) { +			struct iwl_mvm_acs_survey_channel *info = +				&mvm->acs_survey->bands[band][chan_idx]; + +			if (!info->time) +				continue; + +			/* Found (the next) channel to report */ +			survey->channel = &sband->channels[chan_idx]; +			survey->filled = SURVEY_INFO_TIME | +					 SURVEY_INFO_TIME_BUSY | +					 SURVEY_INFO_TIME_RX | +					 SURVEY_INFO_TIME_TX; +			survey->time = info->time; +			survey->time_busy = info->time_busy; +			survey->time_rx = info->time_rx; +			survey->time_tx = info->time_tx; +			survey->noise = info->noise; +			if (survey->noise < 0) +				survey->filled |= SURVEY_INFO_NOISE_DBM; + +			/* Clear time so that channel is only reported once */ +			info->time = 0; + +			ret = 0; +			goto out; +		} +	} + +	ret = -ENOENT; + +out: +	mutex_unlock(&mvm->mutex); + +	return ret; +} +  int iwl_mvm_mac_get_survey(struct ieee80211_hw *hw, int idx,  			   struct survey_info *survey)  { @@ -5718,14 +5970,18 @@ int iwl_mvm_mac_get_survey(struct ieee80211_hw *hw, int idx,  	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; +	/* +	 * Return the beacon stats at index zero and pass on following indices +	 * to the function returning the full survey, most likely for ACS +	 * (Automatic Channel Selection). +	 */ +	if (idx > 0) +		return iwl_mvm_mac_get_acs_survey(mvm, idx - 1, survey); +  	mutex_lock(&mvm->mutex);  	if (iwl_mvm_firmware_running(mvm)) { @@ -6297,7 +6553,7 @@ const struct ieee80211_ops iwl_mvm_hw_ops = {  	.set_tim = iwl_mvm_set_tim,  	.channel_switch = iwl_mvm_channel_switch, -	.pre_channel_switch = iwl_mvm_pre_channel_switch, +	.pre_channel_switch = iwl_mvm_mac_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, diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mld-mac80211.c b/drivers/net/wireless/intel/iwlwifi/mvm/mld-mac80211.c index 084314bf6f36..0a3b7284eedd 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mld-mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mld-mac80211.c @@ -14,6 +14,8 @@ static int iwl_mvm_mld_mac_add_interface(struct ieee80211_hw *hw,  	mutex_lock(&mvm->mutex); +	iwl_mvm_mac_init_mvmvif(mvm, mvmvif); +  	mvmvif->mvm = mvm;  	/* Not much to do here. The stack will not allow interface @@ -92,6 +94,9 @@ static int iwl_mvm_mld_mac_add_interface(struct ieee80211_hw *hw,  		mvm->csme_vif = vif;  	} +	if (vif->p2p || iwl_fw_lookup_cmd_ver(mvm->fw, PHY_CONTEXT_CMD, 1) < 5) +		vif->driver_flags |= IEEE80211_VIF_IGNORE_OFDMA_WIDER_BW; +  	goto out_unlock;   out_free_bf: @@ -189,23 +194,43 @@ static void iwl_mvm_mld_mac_remove_interface(struct ieee80211_hw *hw,  	mutex_unlock(&mvm->mutex);  } -static unsigned int iwl_mvm_mld_count_active_links(struct ieee80211_vif *vif) +static unsigned int iwl_mvm_mld_count_active_links(struct iwl_mvm_vif *mvmvif)  {  	unsigned int n_active = 0;  	int i;  	for (i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) { -		struct ieee80211_bss_conf *link_conf; - -		link_conf = link_conf_dereference_protected(vif, i); -		if (link_conf && -		    rcu_access_pointer(link_conf->chanctx_conf)) +		if (mvmvif->link[i] && mvmvif->link[i]->phy_ctxt)  			n_active++;  	}  	return n_active;  } +static void iwl_mvm_restart_mpdu_count(struct iwl_mvm *mvm, +				       struct iwl_mvm_vif *mvmvif) +{ +	struct ieee80211_sta *ap_sta = mvmvif->ap_sta; +	struct iwl_mvm_sta *mvmsta; + +	lockdep_assert_held(&mvm->mutex); + +	if (!ap_sta) +		return; + +	mvmsta = iwl_mvm_sta_from_mac80211(ap_sta); +	if (!mvmsta->mpdu_counters) +		return; + +	for (int q = 0; q < mvm->trans->num_rx_queues; q++) { +		spin_lock_bh(&mvmsta->mpdu_counters[q].lock); +		memset(mvmsta->mpdu_counters[q].per_link, 0, +		       sizeof(mvmsta->mpdu_counters[q].per_link)); +		mvmsta->mpdu_counters[q].window_start = jiffies; +		spin_unlock_bh(&mvmsta->mpdu_counters[q].lock); +	} +} +  static int iwl_mvm_esr_mode_active(struct iwl_mvm *mvm,  				   struct ieee80211_vif *vif)  { @@ -233,6 +258,22 @@ static int iwl_mvm_esr_mode_active(struct iwl_mvm *mvm,  		link->phy_ctxt->rlc_disabled = true;  	} +	if (vif->active_links == mvmvif->link_selection_res && +	    !WARN_ON(!(vif->active_links & BIT(mvmvif->link_selection_primary)))) +		mvmvif->primary_link = mvmvif->link_selection_primary; +	else +		mvmvif->primary_link = __ffs(vif->active_links); + +	/* Needed for tracking RSSI */ +	iwl_mvm_request_periodic_system_statistics(mvm, true); + +	/* +	 * Restart the MPDU counters and the counting window, so when the +	 * statistics arrive (which is where we look at the counters) we +	 * will be at the end of the window. +	 */ +	iwl_mvm_restart_mpdu_count(mvm, mvmvif); +  	return ret;  } @@ -245,18 +286,18 @@ __iwl_mvm_mld_assign_vif_chanctx(struct iwl_mvm *mvm,  {  	u16 *phy_ctxt_id = (u16 *)ctx->drv_priv;  	struct iwl_mvm_phy_ctxt *phy_ctxt = &mvm->phy_ctxts[*phy_ctxt_id]; -	unsigned int n_active = iwl_mvm_mld_count_active_links(vif);  	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	unsigned int n_active = iwl_mvm_mld_count_active_links(mvmvif);  	unsigned int link_id = link_conf->link_id;  	int ret; -	/* if the assigned one was not counted yet, count it now */ -	if (!rcu_access_pointer(link_conf->chanctx_conf)) -		n_active++; -  	if (WARN_ON_ONCE(!mvmvif->link[link_id]))  		return -EINVAL; +	/* if the assigned one was not counted yet, count it now */ +	if (!mvmvif->link[link_id]->phy_ctxt) +		n_active++; +  	/* mac parameters such as HE support can change at this stage  	 * For sta, need first to configure correct state from drv_sta_state  	 * and only after that update mac config. @@ -276,6 +317,7 @@ __iwl_mvm_mld_assign_vif_chanctx(struct iwl_mvm *mvm,  		ret = iwl_mvm_esr_mode_active(mvm, vif);  		if (ret) {  			IWL_ERR(mvm, "failed to activate ESR mode (%d)\n", ret); +			iwl_mvm_request_periodic_system_statistics(mvm, false);  			goto out;  		}  	} @@ -296,13 +338,8 @@ __iwl_mvm_mld_assign_vif_chanctx(struct iwl_mvm *mvm,  	 * this needs the phy context assigned (and in FW?), and we cannot  	 * do it later because it needs to be initialized as soon as we're  	 * able to TX on the link, i.e. when active. -	 * -	 * Firmware restart isn't quite correct yet for MLO, but we don't -	 * need to do it in that case anyway since it will happen from the -	 * normal station state callback.  	 */ -	if (mvmvif->ap_sta && -	    !test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { +	if (mvmvif->ap_sta) {  		struct ieee80211_link_sta *link_sta;  		rcu_read_lock(); @@ -354,6 +391,18 @@ static int iwl_mvm_mld_assign_vif_chanctx(struct ieee80211_hw *hw,  	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);  	int ret; +	/* update EMLSR mode */ +	if (ieee80211_vif_type_p2p(vif) != NL80211_IFTYPE_STATION) { +		ret = iwl_mvm_esr_non_bss_link(mvm, vif, link_conf->link_id, +					       true); +		/* +		 * Don't activate this link if failed to exit EMLSR in +		 * the BSS interface +		 */ +		if (ret) +			return ret; +	} +  	mutex_lock(&mvm->mutex);  	ret = __iwl_mvm_mld_assign_vif_chanctx(mvm, vif, link_conf, ctx, false);  	mutex_unlock(&mvm->mutex); @@ -404,6 +453,11 @@ static int iwl_mvm_esr_mode_inactive(struct iwl_mvm *mvm,  			break;  	} +	iwl_mvm_request_periodic_system_statistics(mvm, false); + +	/* Start a new counting window */ +	iwl_mvm_restart_mpdu_count(mvm, mvmvif); +  	return ret;  } @@ -416,7 +470,7 @@ __iwl_mvm_mld_unassign_vif_chanctx(struct iwl_mvm *mvm,  {  	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); -	unsigned int n_active = iwl_mvm_mld_count_active_links(vif); +	unsigned int n_active = iwl_mvm_mld_count_active_links(mvmvif);  	unsigned int link_id = link_conf->link_id;  	/* shouldn't happen, but verify link_id is valid before accessing */ @@ -472,6 +526,56 @@ static void iwl_mvm_mld_unassign_vif_chanctx(struct ieee80211_hw *hw,  		iwl_mvm_add_link(mvm, vif, link_conf);  	}  	mutex_unlock(&mvm->mutex); + +	/* update EMLSR mode */ +	if (ieee80211_vif_type_p2p(vif) != NL80211_IFTYPE_STATION) +		iwl_mvm_esr_non_bss_link(mvm, vif, link_conf->link_id, false); +} + +static void +iwl_mvm_send_ap_tx_power_constraint_cmd(struct iwl_mvm *mvm, +					struct ieee80211_vif *vif, +					struct ieee80211_bss_conf *bss_conf) +{ +	struct iwl_txpower_constraints_cmd cmd = {}; +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	struct iwl_mvm_vif_link_info *link_info = +			mvmvif->link[bss_conf->link_id]; +	u32 cmd_id = WIDE_ID(PHY_OPS_GROUP, AP_TX_POWER_CONSTRAINTS_CMD); +	u32 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, +					    IWL_FW_CMD_VER_UNKNOWN); +	int ret; + +	lockdep_assert_held(&mvm->mutex); + +	if (cmd_ver == IWL_FW_CMD_VER_UNKNOWN) +		return; + +	if (!link_info->active || +	    link_info->fw_link_id == IWL_MVM_FW_LINK_ID_INVALID) +		return; + +	if (bss_conf->chanreq.oper.chan->band != NL80211_BAND_6GHZ || +	    bss_conf->chanreq.oper.chan->flags & +		    IEEE80211_CHAN_NO_6GHZ_VLP_CLIENT) +		return; + +	cmd.link_id = cpu_to_le16(link_info->fw_link_id); +	/* +	 * Currently supporting VLP Soft AP only. +	 */ +	cmd.ap_type = cpu_to_le16(IWL_6GHZ_AP_TYPE_VLP); +	memset(cmd.psd_pwr, DEFAULT_TPE_TX_POWER, sizeof(cmd.psd_pwr)); +	memset(cmd.eirp_pwr, DEFAULT_TPE_TX_POWER, sizeof(cmd.eirp_pwr)); + +	ret = iwl_mvm_send_cmd_pdu(mvm, +				   WIDE_ID(PHY_OPS_GROUP, +					   AP_TX_POWER_CONSTRAINTS_CMD), +				   0, sizeof(cmd), &cmd); +	if (ret) +		IWL_ERR(mvm, +			"failed to send AP_TX_POWER_CONSTRAINTS_CMD (%d)\n", +			ret);  }  static int iwl_mvm_mld_start_ap_ibss(struct ieee80211_hw *hw, @@ -483,6 +587,10 @@ static int iwl_mvm_mld_start_ap_ibss(struct ieee80211_hw *hw,  	int ret;  	mutex_lock(&mvm->mutex); + +	if (vif->type == NL80211_IFTYPE_AP) +		iwl_mvm_send_ap_tx_power_constraint_cmd(mvm, vif, link_conf); +  	/* Send the beacon template */  	ret = iwl_mvm_mac_ctxt_beacon_changed(mvm, vif, link_conf);  	if (ret) @@ -601,126 +709,23 @@ static int iwl_mvm_mld_mac_sta_state(struct ieee80211_hw *hw,  					    &callbacks);  } -struct iwl_mvm_link_sel_data { -	u8 link_id; -	enum nl80211_band band; -	enum nl80211_chan_width width; -	bool active; -}; - -static bool iwl_mvm_mld_valid_link_pair(struct iwl_mvm_link_sel_data *a, -					struct iwl_mvm_link_sel_data *b) -{ -	return a->band != b->band; -} - -void iwl_mvm_mld_select_links(struct iwl_mvm *mvm, struct ieee80211_vif *vif, -			      bool valid_links_changed) +static bool iwl_mvm_esr_bw_criteria(struct iwl_mvm *mvm, +				    struct ieee80211_vif *vif, +				    struct ieee80211_bss_conf *link_conf)  { -	struct iwl_mvm_link_sel_data data[IEEE80211_MLD_MAX_NUM_LINKS]; -	unsigned long usable_links = ieee80211_vif_usable_links(vif); -	u32 max_active_links = iwl_mvm_max_active_links(mvm, vif); -	u16 new_active_links; -	u8 link_id, n_data = 0, i, j; - -	if (!IWL_MVM_AUTO_EML_ENABLE) -		return; - -	if (!ieee80211_vif_is_mld(vif) || usable_links == 1) -		return; - -	/* The logic below is a simple version that doesn't suit more than 2 -	 * links -	 */ -	WARN_ON_ONCE(max_active_links > 2); - -	/* if only a single active link is supported, assume that the one -	 * selected by higher layer for connection establishment is the best. -	 */ -	if (max_active_links == 1 && !valid_links_changed) -		return; - -	/* If we are already using the maximal number of active links, don't do -	 * any change. This can later be optimized to pick a 'better' link pair. -	 */ -	if (hweight16(vif->active_links) == max_active_links) -		return; - -	rcu_read_lock(); - -	for_each_set_bit(link_id, &usable_links, IEEE80211_MLD_MAX_NUM_LINKS) { -		struct ieee80211_bss_conf *link_conf = -			rcu_dereference(vif->link_conf[link_id]); +	struct ieee80211_bss_conf *other_link; +	int link_id; -		if (WARN_ON_ONCE(!link_conf)) +	/* Exit EMLSR if links don't have equal bandwidths */ +	for_each_vif_active_link(vif, other_link, link_id) { +		if (link_id == link_conf->link_id)  			continue; - -		data[n_data].link_id = link_id; -		data[n_data].band = link_conf->chanreq.oper.chan->band; -		data[n_data].width = link_conf->chanreq.oper.width; -		data[n_data].active = vif->active_links & BIT(link_id); -		n_data++; -	} - -	rcu_read_unlock(); - -	/* this is expected to be the current active link */ -	if (n_data == 1) -		return; - -	new_active_links = 0; - -	/* Assume that after association only a single link is active, thus, -	 * select only the 2nd link -	 */ -	if (!valid_links_changed) { -		for (i = 0; i < n_data; i++) { -			if (data[i].active) -				break; -		} - -		if (WARN_ON_ONCE(i == n_data)) -			return; - -		for (j = 0; j < n_data; j++) { -			if (i == j) -				continue; - -			if (iwl_mvm_mld_valid_link_pair(&data[i], &data[j])) -				break; -		} - -		if (j != n_data) -			new_active_links = BIT(data[i].link_id) | -				BIT(data[j].link_id); -	} else { -		/* Try to find a valid link pair for EMLSR operation. If a pair -		 * is not found continue using the current active link. -		 */ -		for (i = 0; i < n_data; i++) { -			for (j = 0; j < n_data; j++) { -				if (i == j) -					continue; - -				if (iwl_mvm_mld_valid_link_pair(&data[i], -								&data[j])) -					break; -			} - -			/* found a valid pair for EMLSR, use it */ -			if (j != n_data) { -				new_active_links = BIT(data[i].link_id) | -					BIT(data[j].link_id); -				break; -			} -		} +		if (link_conf->chanreq.oper.width == +		    other_link->chanreq.oper.width) +			return true;  	} -	if (!new_active_links) -		return; - -	if (vif->active_links != new_active_links) -		ieee80211_set_active_links_async(vif, new_active_links); +	return false;  }  static void @@ -752,6 +757,14 @@ iwl_mvm_mld_link_info_changed_station(struct iwl_mvm *mvm,  		link_changes |= LINK_CONTEXT_MODIFY_HE_PARAMS;  	} +	if ((changes & BSS_CHANGED_BANDWIDTH) && +	    ieee80211_vif_link_active(vif, link_conf->link_id) && +	    mvmvif->esr_active && +	    !iwl_mvm_esr_bw_criteria(mvm, vif, link_conf)) +		iwl_mvm_exit_esr(mvm, vif, +				 IWL_MVM_ESR_EXIT_BANDWIDTH, +				 iwl_mvm_get_primary_link(vif)); +  	/* if associated, maybe puncturing changed - we'll check later */  	if (vif->cfg.assoc)  		link_changes |= LINK_CONTEXT_MODIFY_EHT_PARAMS; @@ -767,9 +780,6 @@ iwl_mvm_mld_link_info_changed_station(struct iwl_mvm *mvm,  	if (ret)  		IWL_ERR(mvm, "failed to update MAC %pM\n", vif->addr); -	if (changes & BSS_CHANGED_MLD_VALID_LINKS) -		iwl_mvm_mld_select_links(mvm, vif, true); -  	memcpy(mvmvif->link[link_conf->link_id]->bssid, link_conf->bssid,  	       ETH_ALEN); @@ -912,6 +922,11 @@ static void iwl_mvm_mld_vif_cfg_changed_station(struct iwl_mvm *mvm,  		if (ret)  			IWL_ERR(mvm, "failed to update power mode\n");  	} + +	if (changes & (BSS_CHANGED_MLD_VALID_LINKS | BSS_CHANGED_MLD_TTLM) && +	    ieee80211_vif_is_mld(vif) && mvmvif->authorized) +		wiphy_delayed_work_queue(mvm->hw->wiphy, +					 &mvmvif->mlo_int_scan_wk, 0);  }  static void @@ -1190,6 +1205,14 @@ iwl_mvm_mld_change_vif_links(struct ieee80211_hw *hw,  	if (new_links == 0) {  		mvmvif->link[0] = &mvmvif->deflink;  		err = iwl_mvm_add_link(mvm, vif, &vif->bss_conf); +		if (err == 0) +			mvmvif->primary_link = 0; +	} else if (!(new_links & BIT(mvmvif->primary_link))) { +		/* +		 * Ensure we always have a valid primary_link, the real +		 * decision happens later when PHY is activated. +		 */ +		mvmvif->primary_link = __ffs(new_links);  	}  out_err: @@ -1218,68 +1241,14 @@ iwl_mvm_mld_change_sta_links(struct ieee80211_hw *hw,  	return ret;  } -/* - * This function receives a subset of the usable links bitmap and - * returns the primary link id, and -1 if such link doesn't exist - * (e.g. non-MLO connection) or wasn't found. - */ -int iwl_mvm_mld_get_primary_link(struct iwl_mvm *mvm, -				 struct ieee80211_vif *vif, -				 unsigned long usable_links) -{ -	struct iwl_mvm_link_sel_data data[IEEE80211_MLD_MAX_NUM_LINKS]; -	u8 link_id, n_data = 0; - -	if (!ieee80211_vif_is_mld(vif) || !vif->cfg.assoc) -		return -1; - -	for_each_set_bit(link_id, &usable_links, IEEE80211_MLD_MAX_NUM_LINKS) { -		struct ieee80211_bss_conf *link_conf = -			link_conf_dereference_protected(vif, link_id); - -		if (WARN_ON_ONCE(!link_conf)) -			continue; - -		data[n_data].link_id = link_id; -		data[n_data].band = link_conf->chanreq.oper.chan->band; -		data[n_data].width = link_conf->chanreq.oper.width; -		data[n_data].active = true; -		n_data++; -	} - -	if (n_data <= 1) -		return -1; - -	/* The logic should be modified to handle more than 2 links */ -	WARN_ON_ONCE(n_data > 2); - -	/* Primary link is the link with the wider bandwidth or higher band */ -	if (data[0].width > data[1].width) -		return data[0].link_id; -	if (data[0].width < data[1].width) -		return data[1].link_id; -	if (data[0].band >= data[1].band) -		return data[0].link_id; - -	return data[1].link_id; -} - -/* - * This function receives a bitmap of usable links and check if we can enter - * eSR on those links. - */ -static bool iwl_mvm_can_enter_esr(struct iwl_mvm *mvm, -				  struct ieee80211_vif *vif, -				  unsigned long desired_links) +bool iwl_mvm_vif_has_esr_cap(struct iwl_mvm *mvm, struct ieee80211_vif *vif)  { -	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); -	int primary_link = iwl_mvm_mld_get_primary_link(mvm, vif, -							desired_links);  	const struct wiphy_iftype_ext_capab *ext_capa; -	bool ret = true; -	int link_id; -	if (primary_link < 0) +	lockdep_assert_held(&mvm->mutex); + +	if (!ieee80211_vif_is_mld(vif) || !vif->cfg.assoc || +	    hweight16(ieee80211_vif_usable_links(vif)) == 1)  		return false;  	if (!(vif->cfg.eml_cap & IEEE80211_EML_CAP_EMLSR_SUPP)) @@ -1287,30 +1256,8 @@ static bool iwl_mvm_can_enter_esr(struct iwl_mvm *mvm,  	ext_capa = cfg80211_get_iftype_ext_capa(mvm->hw->wiphy,  						ieee80211_vif_type_p2p(vif)); -	if (!ext_capa || -	    !(ext_capa->eml_capabilities & IEEE80211_EML_CAP_EMLSR_SUPP)) -		return false; - -	for_each_set_bit(link_id, &desired_links, IEEE80211_MLD_MAX_NUM_LINKS) { -		struct ieee80211_bss_conf *link_conf = -			link_conf_dereference_protected(vif, link_id); - -		if (WARN_ON_ONCE(!link_conf)) -			continue; - -		/* BT Coex effects eSR mode only if one of the link is on LB */ -		if (link_conf->chanreq.oper.chan->band != NL80211_BAND_2GHZ) -			continue; - -		ret = iwl_mvm_bt_coex_calculate_esr_mode(mvm, vif, link_id, -							 primary_link); -		// Mark eSR as disabled for the next time -		if (!ret) -			mvmvif->bt_coex_esr_disabled = true; -		break; -	} - -	return ret; +	return (ext_capa && +		(ext_capa->eml_capabilities & IEEE80211_EML_CAP_EMLSR_SUPP));  }  static bool iwl_mvm_mld_can_activate_links(struct ieee80211_hw *hw, @@ -1333,8 +1280,9 @@ static bool iwl_mvm_mld_can_activate_links(struct ieee80211_hw *hw,  	}  	/* If it is an eSR device, check that we can enter eSR */ -	if (iwl_mvm_is_esr_supported(mvm->fwrt.trans)) -		ret = iwl_mvm_can_enter_esr(mvm, vif, desired_links); +	ret = iwl_mvm_is_esr_supported(mvm->fwrt.trans) && +	      iwl_mvm_vif_has_esr_cap(mvm, vif); +  unlock:  	mutex_unlock(&mvm->mutex);  	return ret; @@ -1358,6 +1306,45 @@ iwl_mvm_mld_can_neg_ttlm(struct ieee80211_hw *hw, struct ieee80211_vif *vif,  	return NEG_TTLM_RES_ACCEPT;  } +static int +iwl_mvm_mld_mac_pre_channel_switch(struct ieee80211_hw *hw, +				   struct ieee80211_vif *vif, +				   struct ieee80211_channel_switch *chsw) +{ +	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 (mvmvif->esr_active) { +		u8 primary = iwl_mvm_get_primary_link(vif); +		int selected; + +		/* prefer primary unless quiet CSA on it */ +		if (chsw->link_id == primary && chsw->block_tx) +			selected = iwl_mvm_get_other_link(vif, primary); +		else +			selected = primary; + +		iwl_mvm_exit_esr(mvm, vif, IWL_MVM_ESR_EXIT_CSA, selected); +		mutex_unlock(&mvm->mutex); + +		/* +		 * If we've not kept the link active that's doing the CSA +		 * then we don't need to do anything else, just return. +		 */ +		if (selected != chsw->link_id) +			return 0; + +		mutex_lock(&mvm->mutex); +	} + +	ret = iwl_mvm_pre_channel_switch(mvm, vif, chsw); +	mutex_unlock(&mvm->mutex); + +	return ret; +} +  const struct ieee80211_ops iwl_mvm_mld_hw_ops = {  	.tx = iwl_mvm_mac_tx,  	.wake_tx_queue = iwl_mvm_mac_wake_tx_queue, @@ -1411,7 +1398,7 @@ const struct ieee80211_ops iwl_mvm_mld_hw_ops = {  	.tx_last_beacon = iwl_mvm_tx_last_beacon,  	.channel_switch = iwl_mvm_channel_switch, -	.pre_channel_switch = iwl_mvm_pre_channel_switch, +	.pre_channel_switch = iwl_mvm_mld_mac_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, diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mld-sta.c b/drivers/net/wireless/intel/iwlwifi/mvm/mld-sta.c index 23e64a757cfe..b7a461dba41e 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mld-sta.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mld-sta.c @@ -1,6 +1,6 @@  // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause  /* - * Copyright (C) 2022-2023 Intel Corporation + * Copyright (C) 2022-2024 Intel Corporation   */  #include "mvm.h"  #include "time-sync.h" @@ -9,7 +9,9 @@  u32 iwl_mvm_sta_fw_id_mask(struct iwl_mvm *mvm, struct ieee80211_sta *sta,  			   int filter_link_id)  { +	struct ieee80211_link_sta *link_sta;  	struct iwl_mvm_sta *mvmsta; +	struct ieee80211_vif *vif;  	unsigned int link_id;  	u32 result = 0; @@ -17,26 +19,27 @@ u32 iwl_mvm_sta_fw_id_mask(struct iwl_mvm *mvm, struct ieee80211_sta *sta,  		return 0;  	mvmsta = iwl_mvm_sta_from_mac80211(sta); +	vif = mvmsta->vif;  	/* it's easy when the STA is not an MLD */  	if (!sta->valid_links)  		return BIT(mvmsta->deflink.sta_id);  	/* but if it is an MLD, get the mask of all the FW STAs it has ... */ -	for (link_id = 0; link_id < ARRAY_SIZE(mvmsta->link); link_id++) { -		struct iwl_mvm_link_sta *link_sta; +	for_each_sta_active_link(vif, sta, link_sta, link_id) { +		struct iwl_mvm_link_sta *mvm_link_sta;  		/* unless we have a specific link in mind */  		if (filter_link_id >= 0 && link_id != filter_link_id)  			continue; -		link_sta = +		mvm_link_sta =  			rcu_dereference_check(mvmsta->link[link_id],  					      lockdep_is_held(&mvm->mutex)); -		if (!link_sta) +		if (!mvm_link_sta)  			continue; -		result |= BIT(link_sta->sta_id); +		result |= BIT(mvm_link_sta->sta_id);  	}  	return result; @@ -582,14 +585,14 @@ static int iwl_mvm_mld_alloc_sta_links(struct iwl_mvm *mvm,  				       struct ieee80211_sta *sta)  {  	struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta); +	struct ieee80211_link_sta *link_sta;  	unsigned int link_id;  	int ret;  	lockdep_assert_held(&mvm->mutex); -	for (link_id = 0; link_id < ARRAY_SIZE(sta->link); link_id++) { -		if (!rcu_access_pointer(sta->link[link_id]) || -		    mvm_sta->link[link_id]) +	for_each_sta_active_link(vif, sta, link_sta, link_id) { +		if (WARN_ON(mvm_sta->link[link_id]))  			continue;  		ret = iwl_mvm_mld_alloc_sta_link(mvm, vif, sta, link_id); @@ -616,9 +619,6 @@ static void iwl_mvm_mld_set_ap_sta_id(struct ieee80211_sta *sta,  	}  } -/* FIXME: consider waiting for mac80211 to add the STA instead of allocating - * queues here - */  static int iwl_mvm_alloc_sta_after_restart(struct iwl_mvm *mvm,  					   struct ieee80211_vif *vif,  					   struct ieee80211_sta *sta) @@ -723,7 +723,6 @@ int iwl_mvm_mld_add_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  			iwl_mvm_mld_set_ap_sta_id(sta, mvm_vif->link[link_id],  						  mvm_link_sta);  	} -  	return 0;  err: @@ -849,6 +848,8 @@ int iwl_mvm_mld_rm_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  		iwl_mvm_mld_free_sta_link(mvm, mvm_sta, mvm_link_sta,  					  link_id, stay_in_fw);  	} +	kfree(mvm_sta->mpdu_counters); +	mvm_sta->mpdu_counters = NULL;  	return ret;  } @@ -989,6 +990,10 @@ static int iwl_mvm_mld_update_sta_baids(struct iwl_mvm *mvm,  	u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, RX_BAID_ALLOCATION_CONFIG_CMD);  	int baid; +	/* mac80211 will remove sessions later, but we ignore all that */ +	if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) +		return 0; +  	BUILD_BUG_ON(sizeof(struct iwl_rx_baid_cfg_resp) != sizeof(baid));  	for (baid = 0; baid < ARRAY_SIZE(mvm->baid_map); baid++) { @@ -1122,10 +1127,21 @@ int iwl_mvm_mld_update_sta_links(struct iwl_mvm *mvm,  		}  		if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { -			if (WARN_ON(!mvm_sta->link[link_id])) { +			struct iwl_mvm_link_sta *mvm_link_sta = +				rcu_dereference_protected(mvm_sta->link[link_id], +							  lockdep_is_held(&mvm->mutex)); +			u32 sta_id; + +			if (WARN_ON(!mvm_link_sta)) {  				ret = -EINVAL;  				goto err;  			} + +			sta_id = mvm_link_sta->sta_id; + +			rcu_assign_pointer(mvm->fw_id_to_mac_id[sta_id], sta); +			rcu_assign_pointer(mvm->fw_id_to_link_sta[sta_id], +					   link_sta);  		} else {  			if (WARN_ON(mvm_sta->link[link_id])) {  				ret = -EINVAL; diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h index f0b24f00938b..1f58c727fa63 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h @@ -255,18 +255,14 @@ enum iwl_mvm_low_latency_cause {  };  /** -* 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 +* struct iwl_mvm_link_bf_data - beacon filtering related data  * @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; +struct iwl_mvm_link_bf_data {  	int ave_beacon_signal;  	int last_cqm_event;  	int bt_coex_min_thold; @@ -309,6 +305,7 @@ struct iwl_probe_resp_data {   * @listen_lmac: indicates this link is allocated to the listen LMAC   * @mcast_sta: multicast station   * @phy_ctxt: phy context allocated to this link, if any + * @bf_data: beacon filtering data   */  struct iwl_mvm_vif_link_info {  	u8 bssid[ETH_ALEN]; @@ -344,6 +341,63 @@ struct iwl_mvm_vif_link_info {  	struct ieee80211_tx_queue_params queue_params[IEEE80211_NUM_ACS];  	u16 mgmt_queue; + +	struct iwl_mvm_link_bf_data bf_data; +}; + +/** + * enum iwl_mvm_esr_state - defines reasons for which the EMLSR is exited or + * blocked. + * The low 16 bits are used for blocking reasons, and the 16 higher bits + * are used for exit reasons. + * For the blocking reasons - use iwl_mvm_(un)block_esr(), and for the exit + * reasons - use iwl_mvm_exit_esr(). + * + * Note: new reasons shall be added to HANDLE_ESR_REASONS as well (for logs) + * + * @IWL_MVM_ESR_BLOCKED_PREVENTION: Prevent EMLSR to avoid entering and exiting + *	in a loop. + * @IWL_MVM_ESR_BLOCKED_WOWLAN: WOWLAN is preventing the enablement of EMLSR + * @IWL_MVM_ESR_BLOCKED_TPT: block EMLSR when there is not enough traffic + * @IWL_MVM_ESR_BLOCKED_FW: FW didn't recommended/forced exit from EMLSR + * @IWL_MVM_ESR_BLOCKED_NON_BSS: An active non-bssid link's preventing EMLSR + * @IWL_MVM_ESR_EXIT_MISSED_BEACON: exited EMLSR due to missed beacons + * @IWL_MVM_ESR_EXIT_LOW_RSSI: link is deactivated/not allowed for EMLSR + *	due to low RSSI. + * @IWL_MVM_ESR_EXIT_COEX: link is deactivated/not allowed for EMLSR + *	due to BT Coex. + * @IWL_MVM_ESR_EXIT_BANDWIDTH: Bandwidths of primary and secondry links + *	preventing the enablement of EMLSR + * @IWL_MVM_ESR_EXIT_CSA: CSA happened, so exit EMLSR + * @IWL_MVM_ESR_EXIT_LINK_USAGE: Exit EMLSR due to low tpt on secondary link + */ +enum iwl_mvm_esr_state { +	IWL_MVM_ESR_BLOCKED_PREVENTION	= 0x1, +	IWL_MVM_ESR_BLOCKED_WOWLAN	= 0x2, +	IWL_MVM_ESR_BLOCKED_TPT		= 0x4, +	IWL_MVM_ESR_BLOCKED_FW		= 0x8, +	IWL_MVM_ESR_BLOCKED_NON_BSS	= 0x10, +	IWL_MVM_ESR_EXIT_MISSED_BEACON	= 0x10000, +	IWL_MVM_ESR_EXIT_LOW_RSSI	= 0x20000, +	IWL_MVM_ESR_EXIT_COEX		= 0x40000, +	IWL_MVM_ESR_EXIT_BANDWIDTH	= 0x80000, +	IWL_MVM_ESR_EXIT_CSA		= 0x100000, +	IWL_MVM_ESR_EXIT_LINK_USAGE	= 0x200000, +}; + +#define IWL_MVM_BLOCK_ESR_REASONS 0xffff + +const char *iwl_get_esr_state_string(enum iwl_mvm_esr_state state); + +/** + * struct iwl_mvm_esr_exit - details of the last exit from EMLSR mode. + * @reason: The reason for the last exit from EMLSR. + *	&iwl_mvm_prevent_esr_reasons. Will be 0 before exiting EMLSR. + * @ts: the time stamp of the last time we existed EMLSR. + */ +struct iwl_mvm_esr_exit { +	unsigned long ts; +	enum iwl_mvm_esr_state reason;  };  /** @@ -361,7 +415,6 @@ struct iwl_mvm_vif_link_info {   * @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. - * @bt_coex_esr_disabled: indicates if esr is disabled due to bt coex   * @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, @@ -371,14 +424,30 @@ struct iwl_mvm_vif_link_info {   * @csa_countdown: indicates that CSA countdown may be started   * @csa_failed: CSA failed to schedule time event, report an error later   * @csa_bcn_pending: indicates that we are waiting for a beacon on a new channel + * @csa_blocks_tx: CSA is blocking TX   * @features: hw features active for this vif   * @ap_beacon_time: AP beacon time for synchronisation (on older FW) + * @bf_enabled: indicates if beacon filtering is enabled + * @ba_enabled: indicated if beacon abort is enabled   * @bcn_prot: beacon protection data (keys; FIXME: needs to be per link) - * @bf_data: beacon filtering data   * @deflink: default link data for use in non-MLO   * @link: link data for each link in MLO   * @esr_active: indicates eSR mode is active + * @esr_disable_reason: a bitmap of &enum iwl_mvm_esr_state   * @pm_enabled: indicates powersave is enabled + * @link_selection_res: bitmap of active links as it was decided in the last + *	link selection. Valid only for a MLO vif after assoc. 0 if there wasn't + *	any link selection yet. + * @link_selection_primary: primary link selected by link selection + * @primary_link: primary link in eSR. Valid only for an associated MLD vif, + *	and in eSR mode. Valid only for a STA. + * @last_esr_exit: Details of the last exit from EMLSR. + * @exit_same_reason_count: The number of times we exited due to the specified + *	@last_esr_exit::reason, only counting exits due to + *	&IWL_MVM_ESR_PREVENT_REASONS. + * @prevent_esr_done_wk: work that should be done when esr prevention ends. + * @mlo_int_scan_wk: work for the internal MLO scan. + * @unblock_esr_tpt_wk: work for unblocking EMLSR when tpt is high enough.   */  struct iwl_mvm_vif {  	struct iwl_mvm *mvm; @@ -392,7 +461,6 @@ struct iwl_mvm_vif {  	bool pm_enabled;  	bool monitor_active;  	bool esr_active; -	bool bt_coex_esr_disabled;  	u8 low_latency: 6;  	u8 low_latency_actual: 1; @@ -400,8 +468,10 @@ struct iwl_mvm_vif {  	u8 authorized:1;  	bool ps_disabled; +	u32 esr_disable_reason;  	u32 ap_beacon_time; -	struct iwl_mvm_vif_bf_data bf_data; +	bool bf_enabled; +	bool ba_enabled;  #ifdef CONFIG_PM  	/* WoWLAN GTK rekey data */ @@ -435,6 +505,7 @@ struct iwl_mvm_vif {  	struct iwl_dbgfs_bf dbgfs_bf;  	struct iwl_mac_power_cmd mac_pwr_cmd;  	int dbgfs_quota_min; +	bool ftm_unprotected;  #endif  	/* FW identified misbehaving AP */ @@ -444,6 +515,7 @@ struct iwl_mvm_vif {  	bool csa_countdown;  	bool csa_failed;  	bool csa_bcn_pending; +	bool csa_blocks_tx;  	u16 csa_target_freq;  	u16 csa_count;  	u16 csa_misbehave; @@ -466,6 +538,15 @@ struct iwl_mvm_vif {  		struct ieee80211_key_conf __rcu *keys[2];  	} bcn_prot; +	u16 link_selection_res; +	u8 link_selection_primary; +	u8 primary_link; +	struct iwl_mvm_esr_exit last_esr_exit; +	u8 exit_same_reason_count; +	struct wiphy_delayed_work prevent_esr_done_wk; +	struct wiphy_delayed_work mlo_int_scan_wk; +	struct wiphy_work unblock_esr_tpt_wk; +  	struct iwl_mvm_vif_link_info deflink;  	struct iwl_mvm_vif_link_info *link[IEEE80211_MLD_MAX_NUM_LINKS];  }; @@ -490,10 +571,12 @@ 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_INT_MLO		= BIT(3),  	IWL_MVM_SCAN_STOPPING_REGULAR	= BIT(8),  	IWL_MVM_SCAN_STOPPING_SCHED	= BIT(9),  	IWL_MVM_SCAN_STOPPING_NETDETECT	= BIT(10), +	IWL_MVM_SCAN_STOPPING_INT_MLO	= BIT(11),  	IWL_MVM_SCAN_REGULAR_MASK	= IWL_MVM_SCAN_REGULAR |  					  IWL_MVM_SCAN_STOPPING_REGULAR, @@ -501,6 +584,8 @@ enum iwl_scan_status {  					  IWL_MVM_SCAN_STOPPING_SCHED,  	IWL_MVM_SCAN_NETDETECT_MASK	= IWL_MVM_SCAN_NETDETECT |  					  IWL_MVM_SCAN_STOPPING_NETDETECT, +	IWL_MVM_SCAN_INT_MLO_MASK       = IWL_MVM_SCAN_INT_MLO | +					  IWL_MVM_SCAN_STOPPING_INT_MLO,  	IWL_MVM_SCAN_STOPPING_MASK	= 0xff << IWL_MVM_SCAN_STOPPING_SHIFT,  	IWL_MVM_SCAN_MASK		= 0xff, @@ -754,9 +839,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 +#define IWL_MVM_TXQ_STATE_READY		0 +#define IWL_MVM_TXQ_STATE_STOP_FULL	1 +#define IWL_MVM_TXQ_STATE_STOP_REDIRECT	2 +#define IWL_MVM_TXQ_STATE_STOP_AP_CSA	3  	unsigned long state;  }; @@ -834,6 +920,35 @@ struct iwl_mei_scan_filter {  	struct work_struct scan_work;  }; +/** + * struct iwl_mvm_acs_survey_channel - per-channel survey information + * + * Stripped down version of &struct survey_info. + * + * @time: time in ms the radio was on the channel + * @time_busy: time in ms the channel was sensed busy + * @time_tx: time in ms spent transmitting data + * @time_rx: time in ms spent receiving data + * @noise: channel noise in dBm + */ +struct iwl_mvm_acs_survey_channel { +	u32 time; +	u32 time_busy; +	u32 time_tx; +	u32 time_rx; +	s8 noise; +}; + +struct iwl_mvm_acs_survey { +	struct iwl_mvm_acs_survey_channel *bands[NUM_NL80211_BANDS]; + +	/* Overall number of channels */ +	int n_channels; + +	/* Storage space for per-channel information follows */ +	struct iwl_mvm_acs_survey_channel channels[] __counted_by(n_channels); +}; +  struct iwl_mvm {  	/* for logger access */  	struct device *dev; @@ -853,6 +968,8 @@ struct iwl_mvm {  	/* For async rx handlers that require the wiphy lock */  	struct wiphy_work async_handlers_wiphy_wk; +	struct wiphy_work trig_link_selection_wk; +  	struct work_struct roc_done_wk;  	unsigned long init_status; @@ -1201,6 +1318,8 @@ struct iwl_mvm {  	struct iwl_mei_scan_filter mei_scan_filter; +	struct iwl_mvm_acs_survey *acs_survey; +  	bool statistics_clear;  }; @@ -1570,17 +1689,14 @@ static inline int iwl_mvm_max_active_links(struct iwl_mvm *mvm,  					   struct ieee80211_vif *vif)  {  	struct iwl_trans *trans = mvm->fwrt.trans; -	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); - -	lockdep_assert_held(&mvm->mutex);  	if (vif->type == NL80211_IFTYPE_AP)  		return mvm->fw->ucode_capa.num_beacons; -	if ((iwl_mvm_is_esr_supported(trans) && -	     !mvmvif->bt_coex_esr_disabled) || -	    ((CSR_HW_RFID_TYPE(trans->hw_rf_id) == IWL_CFG_RF_TYPE_FM && -	     CSR_HW_RFID_IS_CDB(trans->hw_rf_id)))) +	/* Check if HW supports eSR or STR */ +	if (iwl_mvm_is_esr_supported(trans) || +	    (CSR_HW_RFID_TYPE(trans->hw_rf_id) == IWL_CFG_RF_TYPE_FM && +	     CSR_HW_RFID_IS_CDB(trans->hw_rf_id)))  		return IWL_MVM_FW_MAX_ACTIVE_LINKS_NUM;  	return 1; @@ -1720,6 +1836,8 @@ void iwl_mvm_handle_rx_statistics(struct iwl_mvm *mvm,  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); +int iwl_mvm_request_periodic_system_statistics(struct iwl_mvm *mvm, +					       bool enable);  void iwl_mvm_accu_radio_stats(struct iwl_mvm *mvm);  /* NVM */ @@ -1776,6 +1894,8 @@ int iwl_mvm_load_d3_fw(struct iwl_mvm *mvm);  int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm); +void iwl_mvm_mac_init_mvmvif(struct iwl_mvm *mvm, struct iwl_mvm_vif *mvmvif); +  /*   * FW notifications / CMD responses handlers   * Convention: iwl_mvm_rx_<NAME OF THE CMD> @@ -1930,6 +2050,26 @@ int iwl_mvm_remove_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  int iwl_mvm_disable_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  			 struct ieee80211_bss_conf *link_conf); +void iwl_mvm_select_links(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +u8 iwl_mvm_get_primary_link(struct ieee80211_vif *vif); +u8 iwl_mvm_get_other_link(struct ieee80211_vif *vif, u8 link_id); + +struct iwl_mvm_link_sel_data { +	u8 link_id; +	const struct cfg80211_chan_def *chandef; +	s32 signal; +	u16 grade; +}; + +#if IS_ENABLED(CONFIG_IWLWIFI_KUNIT_TESTS) +unsigned int iwl_mvm_get_link_grade(struct ieee80211_bss_conf *link_conf); +bool iwl_mvm_mld_valid_link_pair(struct ieee80211_vif *vif, +				 const struct iwl_mvm_link_sel_data *a, +				 const struct iwl_mvm_link_sel_data *b); + +s8 iwl_mvm_average_dbm_values(const struct iwl_umac_scan_channel_survey_notif *notif); +#endif +  /* AP and IBSS */  bool iwl_mvm_start_ap_ibss_common(struct ieee80211_hw *hw,  				  struct ieee80211_vif *vif, int *ret); @@ -2005,9 +2145,13 @@ int iwl_mvm_reg_scan_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  			   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); +int iwl_mvm_int_mlo_scan(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_rx_channel_survey_notif(struct iwl_mvm *mvm, +				     struct iwl_rx_cmd_buffer *rxb);  /* Scheduled scan */  void iwl_mvm_rx_lmac_scan_complete_notif(struct iwl_mvm *mvm, @@ -2113,7 +2257,8 @@ int iwl_mvm_send_proto_offload(struct iwl_mvm *mvm,  			       struct ieee80211_vif *vif,  			       bool disable_offloading,  			       bool offload_ns, -			       u32 cmd_flags); +			       u32 cmd_flags, +			       u8 sta_id);  /* BT Coex */  int iwl_mvm_send_bt_init_conf(struct iwl_mvm *mvm); @@ -2133,12 +2278,6 @@ bool iwl_mvm_bt_coex_is_tpc_allowed(struct iwl_mvm *mvm,  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); -bool iwl_mvm_bt_coex_calculate_esr_mode(struct iwl_mvm *mvm, -					struct ieee80211_vif *vif, -					int link_id, int primary_link); -void iwl_mvm_bt_coex_update_vif_esr(struct iwl_mvm *mvm, -				    struct ieee80211_vif *vif, -				    int link_id);  /* beacon filtering */  #ifdef CONFIG_IWLWIFI_DEBUGFS @@ -2450,6 +2589,21 @@ static inline u8 iwl_mvm_phy_band_from_nl80211(enum nl80211_band band)  	}  } +static inline u8 iwl_mvm_nl80211_band_from_phy(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; +	} +} +  /* Channel Switch */  void iwl_mvm_channel_switch_disconnect_wk(struct work_struct *wk);  int iwl_mvm_post_channel_switch(struct ieee80211_hw *hw, @@ -2711,7 +2865,7 @@ void iwl_mvm_change_chanctx(struct ieee80211_hw *hw,  int iwl_mvm_tx_last_beacon(struct ieee80211_hw *hw);  void iwl_mvm_channel_switch(struct ieee80211_hw *hw, struct ieee80211_vif *vif,  			    struct ieee80211_channel_switch *chsw); -int iwl_mvm_pre_channel_switch(struct ieee80211_hw *hw, +int iwl_mvm_pre_channel_switch(struct iwl_mvm *mvm,  			       struct ieee80211_vif *vif,  			       struct ieee80211_channel_switch *chsw);  void iwl_mvm_abort_channel_switch(struct ieee80211_hw *hw, @@ -2753,12 +2907,6 @@ int iwl_mvm_set_hw_timestamp(struct ieee80211_hw *hw,  int iwl_mvm_update_mu_groups(struct iwl_mvm *mvm, struct ieee80211_vif *vif);  bool iwl_mvm_enable_fils(struct iwl_mvm *mvm,  			 struct ieee80211_chanctx_conf *ctx); -void iwl_mvm_mld_select_links(struct iwl_mvm *mvm, struct ieee80211_vif *vif, -			      bool valid_links_changed); -int iwl_mvm_mld_get_primary_link(struct iwl_mvm *mvm, -				 struct ieee80211_vif *vif, -				 unsigned long usable_links); -  bool iwl_mvm_is_ftm_responder_chanctx(struct iwl_mvm *mvm,  				      struct ieee80211_chanctx_conf *ctx); @@ -2779,4 +2927,30 @@ int iwl_mvm_roc_add_cmd(struct iwl_mvm *mvm,  			struct ieee80211_channel *channel,  			struct ieee80211_vif *vif,  			int duration, u32 activity); + +/* EMLSR */ +bool iwl_mvm_vif_has_esr_cap(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_block_esr(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +		       enum iwl_mvm_esr_state reason, +		       u8 link_to_keep); +int iwl_mvm_block_esr_sync(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +			   enum iwl_mvm_esr_state reason); +void iwl_mvm_unblock_esr(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +			 enum iwl_mvm_esr_state reason); +void iwl_mvm_exit_esr(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +		      enum iwl_mvm_esr_state reason, +		      u8 link_to_keep); +s8 iwl_mvm_get_esr_rssi_thresh(struct iwl_mvm *mvm, +			       const struct cfg80211_chan_def *chandef, +			       bool low); +void iwl_mvm_bt_coex_update_link_esr(struct iwl_mvm *mvm, +				     struct ieee80211_vif *vif, +				     int link_id); +bool +iwl_mvm_bt_coex_calculate_esr_mode(struct iwl_mvm *mvm, +				   struct ieee80211_vif *vif, +				   s32 link_rssi, +				   bool primary); +int iwl_mvm_esr_non_bss_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +			     unsigned int link_id, bool active);  #endif /* __IWL_MVM_H__ */ diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/offloading.c b/drivers/net/wireless/intel/iwlwifi/mvm/offloading.c index dfb16ca5b438..1eb21fe861e5 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/offloading.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/offloading.c @@ -1,6 +1,6 @@  // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause  /* - * Copyright (C) 2012-2014, 2021-2022 Intel Corporation + * Copyright (C) 2012-2014, 2021-2022, 2024 Intel Corporation   * Copyright (C) 2013-2014 Intel Mobile Communications GmbH   * Copyright (C) 2015 Intel Deutschland GmbH   */ @@ -30,7 +30,8 @@ int iwl_mvm_send_proto_offload(struct iwl_mvm *mvm,  			       struct ieee80211_vif *vif,  			       bool disable_offloading,  			       bool offload_ns, -			       u32 cmd_flags) +			       u32 cmd_flags, +			       u8 sta_id)  {  	union {  		struct iwl_proto_offload_cmd_v1 v1; @@ -205,6 +206,9 @@ int iwl_mvm_send_proto_offload(struct iwl_mvm *mvm,  	if (!disable_offloading)  		common->enabled = cpu_to_le32(enabled); +	if (ver >= 4) +		cmd.v4.sta_id = cpu_to_le32(sta_id); +  	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 index a93981cb9714..53283d052e18 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c @@ -145,6 +145,24 @@ static void iwl_mvm_nic_config(struct iwl_op_mode *op_mode)  				       ~APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS);  } +static void iwl_mvm_rx_esr_mode_notif(struct iwl_mvm *mvm, +				      struct iwl_rx_cmd_buffer *rxb) +{ +	struct iwl_rx_packet *pkt = rxb_addr(rxb); +	struct iwl_mvm_esr_mode_notif *notif = (void *)pkt->data; +	struct ieee80211_vif *vif = iwl_mvm_get_bss_vif(mvm); + +	/* FW recommendations is only for entering EMLSR */ +	if (!vif || iwl_mvm_vif_from_mac80211(vif)->esr_active) +		return; + +	if (le32_to_cpu(notif->action) == ESR_RECOMMEND_ENTER) +		iwl_mvm_unblock_esr(mvm, vif, IWL_MVM_ESR_BLOCKED_FW); +	else +		iwl_mvm_block_esr(mvm, vif, IWL_MVM_ESR_BLOCKED_FW, +				  iwl_mvm_get_primary_link(vif)); +} +  static void iwl_mvm_rx_monitor_notif(struct iwl_mvm *mvm,  				     struct iwl_rx_cmd_buffer *rxb)  { @@ -365,13 +383,15 @@ static const struct iwl_rx_handlers iwl_mvm_rx_handlers[] = {  			   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_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_ASYNC_LOCKED_WIPHY, +		   struct iwl_missed_beacons_notif),  	RX_HANDLER(REPLY_ERROR, iwl_mvm_rx_fw_error, RX_HANDLER_SYNC,  		   struct iwl_error_resp), @@ -423,6 +443,12 @@ static const struct iwl_rx_handlers iwl_mvm_rx_handlers[] = {  		       iwl_mvm_channel_switch_error_notif,  		       RX_HANDLER_ASYNC_UNLOCKED,  		       struct iwl_channel_switch_error_notif), + +	RX_HANDLER_GRP(DATA_PATH_GROUP, ESR_MODE_NOTIF, +		       iwl_mvm_rx_esr_mode_notif, +		       RX_HANDLER_ASYNC_LOCKED_WIPHY, +		       struct iwl_mvm_esr_mode_notif), +  	RX_HANDLER_GRP(DATA_PATH_GROUP, MONITOR_NOTIF,  		       iwl_mvm_rx_monitor_notif, RX_HANDLER_ASYNC_LOCKED,  		       struct iwl_datapath_monitor_notif), @@ -447,6 +473,9 @@ static const struct iwl_rx_handlers iwl_mvm_rx_handlers[] = {  	RX_HANDLER_GRP(MAC_CONF_GROUP, ROC_NOTIF,  		       iwl_mvm_rx_roc_notif, RX_HANDLER_SYNC,  		       struct iwl_roc_notif), +	RX_HANDLER_GRP(SCAN_GROUP, CHANNEL_SURVEY_NOTIF, +		       iwl_mvm_rx_channel_survey_notif, RX_HANDLER_ASYNC_LOCKED, +		       struct iwl_umac_scan_channel_survey_notif),  };  #undef RX_HANDLER  #undef RX_HANDLER_GRP @@ -586,6 +615,7 @@ static const struct iwl_hcmd_names iwl_mvm_phy_names[] = {  	HCMD_NAME(CTDP_CONFIG_CMD),  	HCMD_NAME(TEMP_REPORTING_THRESHOLDS_CMD),  	HCMD_NAME(PER_CHAIN_LIMIT_OFFSET_CMD), +	HCMD_NAME(AP_TX_POWER_CONSTRAINTS_CMD),  	HCMD_NAME(CT_KILL_NOTIFICATION),  	HCMD_NAME(DTS_MEASUREMENT_NOTIF_WIDE),  }; @@ -604,6 +634,7 @@ static const struct iwl_hcmd_names iwl_mvm_data_path_names[] = {  	HCMD_NAME(CHEST_COLLECTOR_FILTER_CONFIG_CMD),  	HCMD_NAME(SCD_QUEUE_CONFIG_CMD),  	HCMD_NAME(SEC_KEY_CMD), +	HCMD_NAME(ESR_MODE_NOTIF),  	HCMD_NAME(MONITOR_NOTIF),  	HCMD_NAME(THERMAL_DUAL_CHAIN_REQUEST),  	HCMD_NAME(STA_PM_NOTIF), @@ -623,6 +654,7 @@ static const struct iwl_hcmd_names iwl_mvm_statistics_names[] = {   * Access is done through binary search   */  static const struct iwl_hcmd_names iwl_mvm_scan_names[] = { +	HCMD_NAME(CHANNEL_SURVEY_NOTIF),  	HCMD_NAME(OFFLOAD_MATCH_INFO_NOTIF),  }; @@ -1143,6 +1175,27 @@ static const struct iwl_mei_ops mei_ops = {  	.nic_stolen = iwl_mvm_mei_nic_stolen,  }; +static void iwl_mvm_find_link_selection_vif(void *_data, u8 *mac, +					    struct ieee80211_vif *vif) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + +	if (ieee80211_vif_is_mld(vif) && mvmvif->authorized) +		iwl_mvm_select_links(mvmvif->mvm, vif); +} + +static void iwl_mvm_trig_link_selection(struct wiphy *wiphy, +					struct wiphy_work *wk) +{ +	struct iwl_mvm *mvm = +		container_of(wk, struct iwl_mvm, trig_link_selection_wk); + +	ieee80211_iterate_active_interfaces(mvm->hw, +					    IEEE80211_IFACE_ITER_NORMAL, +					    iwl_mvm_find_link_selection_vif, +					    NULL); +} +  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) @@ -1274,6 +1327,10 @@ iwl_op_mode_mvm_start(struct iwl_trans *trans, const struct iwl_cfg *cfg,  	wiphy_work_init(&mvm->async_handlers_wiphy_wk,  			iwl_mvm_async_handlers_wiphy_wk); + +	wiphy_work_init(&mvm->trig_link_selection_wk, +			iwl_mvm_trig_link_selection); +  	init_waitqueue_head(&mvm->rx_sync_waitq);  	mvm->queue_sync_state = 0; @@ -1528,6 +1585,7 @@ static void iwl_op_mode_mvm_stop(struct iwl_op_mode *op_mode)  	kfree(mvm->temp_nvm_data);  	for (i = 0; i < NVM_MAX_NUM_SECTIONS; i++)  		kfree(mvm->nvm_sections[i].data); +	kfree(mvm->acs_survey);  	cancel_delayed_work_sync(&mvm->tcm.work); diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/power.c b/drivers/net/wireless/intel/iwlwifi/mvm/power.c index 41e68aa6bec8..568f53c56199 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/power.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/power.c @@ -79,7 +79,7 @@ void iwl_mvm_beacon_filter_set_cqm_params(struct iwl_mvm *mvm,  		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); +	cmd->ba_enable_beacon_abort = cpu_to_le32(mvmvif->ba_enabled);  }  static void iwl_mvm_power_log(struct iwl_mvm *mvm, @@ -826,7 +826,7 @@ static int _iwl_mvm_enable_beacon_filter(struct iwl_mvm *mvm,  	ret = iwl_mvm_beacon_filter_send_cmd(mvm, cmd);  	if (!ret) -		mvmvif->bf_data.bf_enabled = true; +		mvmvif->bf_enabled = true;  	return ret;  } @@ -855,7 +855,7 @@ static int _iwl_mvm_disable_beacon_filter(struct iwl_mvm *mvm,  	ret = iwl_mvm_beacon_filter_send_cmd(mvm, &cmd);  	if (!ret) -		mvmvif->bf_data.bf_enabled = false; +		mvmvif->bf_enabled = false;  	return ret;  } @@ -903,16 +903,16 @@ static int iwl_mvm_power_set_ba(struct iwl_mvm *mvm,  		.bf_enable_beacon_filter = cpu_to_le32(1),  	}; -	if (!mvmvif->bf_data.bf_enabled) +	if (!mvmvif->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)); +	mvmvif->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);  } diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rs-fw.c b/drivers/net/wireless/intel/iwlwifi/mvm/rs-fw.c index 00860feefa7a..3ba62fb2c85e 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/rs-fw.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/rs-fw.c @@ -654,10 +654,7 @@ void iwl_mvm_rs_fw_rate_init(struct iwl_mvm *mvm,  	 */  	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); +	cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, 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", @@ -693,9 +690,7 @@ void iwl_mvm_rs_fw_rate_init(struct iwl_mvm *mvm,  		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) +		if (iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, 0) < 3)  			cmd_size -= 4;  		ret = iwl_mvm_send_cmd_pdu(mvm, cmd_id, CMD_ASYNC, cmd_size, diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c index b1add7942c5b..4fa8066a89b6 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c @@ -556,36 +556,40 @@ struct iwl_mvm_stat_data_all_macs {  	struct iwl_stats_ntfy_per_mac *per_mac;  }; -static void iwl_mvm_update_vif_sig(struct ieee80211_vif *vif, int sig) +static void iwl_mvm_update_link_sig(struct ieee80211_vif *vif, int sig, +				    struct iwl_mvm_vif_link_info *link_info)  { -	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; +	struct iwl_mvm *mvm = iwl_mvm_vif_from_mac80211(vif)->mvm; +	struct ieee80211_bss_conf *bss_conf = +		iwl_mvm_rcu_fw_link_id_to_link_conf(mvm, link_info->fw_link_id, +						    false); +	int thold = bss_conf->cqm_rssi_thold; +	int hyst = bss_conf->cqm_rssi_hyst;  	int last_event; +	s8 exit_esr_thresh;  	if (sig == 0) {  		IWL_DEBUG_RX(mvm, "RSSI is 0 - skip signal based decision\n");  		return;  	} -	mvmvif->bf_data.ave_beacon_signal = sig; +	link_info->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 || +	if (link_info->bf_data.bt_coex_min_thold != +	    link_info->bf_data.bt_coex_max_thold) { +		last_event = link_info->bf_data.last_bt_coex_event; +		if (sig > link_info->bf_data.bt_coex_max_thold && +		    (last_event <= link_info->bf_data.bt_coex_min_thold ||  		     last_event == 0)) { -			mvmvif->bf_data.last_bt_coex_event = sig; +			link_info->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 || +		} else if (sig < link_info->bf_data.bt_coex_min_thold && +			   (last_event >= link_info->bf_data.bt_coex_max_thold ||  			    last_event == 0)) { -			mvmvif->bf_data.last_bt_coex_event = sig; +			link_info->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); @@ -596,10 +600,10 @@ static void iwl_mvm_update_vif_sig(struct ieee80211_vif *vif, int sig)  		return;  	/* CQM Notification */ -	last_event = mvmvif->bf_data.last_cqm_event; +	last_event = link_info->bf_data.last_cqm_event;  	if (thold && sig < thold && (last_event == 0 ||  				     sig < last_event - hyst)) { -		mvmvif->bf_data.last_cqm_event = sig; +		link_info->bf_data.last_cqm_event = sig;  		IWL_DEBUG_RX(mvm, "cqm_iterator cqm low %d\n",  			     sig);  		ieee80211_cqm_rssi_notify( @@ -609,7 +613,7 @@ static void iwl_mvm_update_vif_sig(struct ieee80211_vif *vif, int sig)  			GFP_KERNEL);  	} else if (sig > thold &&  		   (last_event == 0 || sig > last_event + hyst)) { -		mvmvif->bf_data.last_cqm_event = sig; +		link_info->bf_data.last_cqm_event = sig;  		IWL_DEBUG_RX(mvm, "cqm_iterator cqm high %d\n",  			     sig);  		ieee80211_cqm_rssi_notify( @@ -618,6 +622,20 @@ static void iwl_mvm_update_vif_sig(struct ieee80211_vif *vif, int sig)  			sig,  			GFP_KERNEL);  	} + +	/* ESR recalculation */ +	if (!vif->cfg.assoc || !ieee80211_vif_is_mld(vif)) +		return; + +	exit_esr_thresh = +		iwl_mvm_get_esr_rssi_thresh(mvm, +					    &bss_conf->chanreq.oper, +					    true); + +	if (sig < exit_esr_thresh) +		iwl_mvm_exit_esr(mvm, vif, IWL_MVM_ESR_EXIT_LOW_RSSI, +				 iwl_mvm_get_other_link(vif, +							bss_conf->link_id));  }  static void iwl_mvm_stat_iterator(void *_data, u8 *mac, @@ -651,7 +669,8 @@ static void iwl_mvm_stat_iterator(void *_data, u8 *mac,  		mvmvif->deflink.beacon_stats.accu_num_beacons +=  			mvmvif->deflink.beacon_stats.num_beacons; -	iwl_mvm_update_vif_sig(vif, sig); +	/* This is used in pre-MLO API so use deflink */ +	iwl_mvm_update_link_sig(vif, sig, &mvmvif->deflink);  }  static void iwl_mvm_stat_iterator_all_macs(void *_data, u8 *mac, @@ -684,7 +703,9 @@ static void iwl_mvm_stat_iterator_all_macs(void *_data, u8 *mac,  			mvmvif->deflink.beacon_stats.num_beacons;  	sig = -le32_to_cpu(mac_stats->beacon_filter_average_energy); -	iwl_mvm_update_vif_sig(vif, sig); + +	/* This is used in pre-MLO API so use deflink */ +	iwl_mvm_update_link_sig(vif, sig, &mvmvif->deflink);  }  static inline void @@ -889,8 +910,8 @@ iwl_mvm_stat_iterator_all_links(struct iwl_mvm *mvm,  		if (link_info->phy_ctxt &&  		    link_info->phy_ctxt->channel->band == NL80211_BAND_2GHZ) -			iwl_mvm_bt_coex_update_vif_esr(mvm, bss_conf->vif, -						       link_id); +			iwl_mvm_bt_coex_update_link_esr(mvm, bss_conf->vif, +							link_id);  		/* make sure that beacon statistics don't go backwards with TCM  		 * request to clear statistics @@ -900,7 +921,7 @@ iwl_mvm_stat_iterator_all_links(struct iwl_mvm *mvm,  				mvmvif->link[link_id]->beacon_stats.num_beacons;  		sig = -le32_to_cpu(link_stats->beacon_filter_average_energy); -		iwl_mvm_update_vif_sig(bss_conf->vif, sig); +		iwl_mvm_update_link_sig(bss_conf->vif, sig, link_info);  		if (WARN_ONCE(mvmvif->id >= MAC_INDEX_AUX,  			      "invalid mvmvif id: %d", mvmvif->id)) @@ -930,6 +951,89 @@ iwl_mvm_stat_iterator_all_links(struct iwl_mvm *mvm,  	}  } +#define SEC_LINK_MIN_PERC 10 +#define SEC_LINK_MIN_TX 3000 +#define SEC_LINK_MIN_RX 400 + +static void iwl_mvm_update_esr_mode_tpt(struct iwl_mvm *mvm) +{ +	struct ieee80211_vif *bss_vif = iwl_mvm_get_bss_vif(mvm); +	struct iwl_mvm_vif *mvmvif; +	struct iwl_mvm_sta *mvmsta; +	unsigned long total_tx = 0, total_rx = 0; +	unsigned long sec_link_tx = 0, sec_link_rx = 0; +	u8 sec_link_tx_perc, sec_link_rx_perc; +	u8 sec_link; + +	lockdep_assert_held(&mvm->mutex); + +	if (!bss_vif) +		return; + +	mvmvif = iwl_mvm_vif_from_mac80211(bss_vif); + +	if (!mvmvif->esr_active || !mvmvif->ap_sta) +		return; + +	mvmsta = iwl_mvm_sta_from_mac80211(mvmvif->ap_sta); +	/* We only count for the AP sta in a MLO connection */ +	if (!mvmsta->mpdu_counters) +		return; + +	/* Get the FW ID of the secondary link */ +	sec_link = iwl_mvm_get_other_link(bss_vif, +					  iwl_mvm_get_primary_link(bss_vif)); +	if (WARN_ON(!mvmvif->link[sec_link])) +		return; +	sec_link = mvmvif->link[sec_link]->fw_link_id; + +	/* Sum up RX and TX MPDUs from the different queues/links */ +	for (int q = 0; q < mvm->trans->num_rx_queues; q++) { +		spin_lock_bh(&mvmsta->mpdu_counters[q].lock); + +		/* The link IDs that doesn't exist will contain 0 */ +		for (int link = 0; link < IWL_MVM_FW_MAX_LINK_ID; link++) { +			total_tx += mvmsta->mpdu_counters[q].per_link[link].tx; +			total_rx += mvmsta->mpdu_counters[q].per_link[link].rx; +		} + +		sec_link_tx += mvmsta->mpdu_counters[q].per_link[sec_link].tx; +		sec_link_rx += mvmsta->mpdu_counters[q].per_link[sec_link].rx; + +		/* +		 * In EMLSR we have statistics every 5 seconds, so we can reset +		 * the counters upon every statistics notification. +		 */ +		memset(mvmsta->mpdu_counters[q].per_link, 0, +		       sizeof(mvmsta->mpdu_counters[q].per_link)); + +		spin_unlock_bh(&mvmsta->mpdu_counters[q].lock); +	} + +	/* If we don't have enough MPDUs - exit EMLSR */ +	if (total_tx < IWL_MVM_ENTER_ESR_TPT_THRESH && +	    total_rx < IWL_MVM_ENTER_ESR_TPT_THRESH) { +		iwl_mvm_block_esr(mvm, bss_vif, IWL_MVM_ESR_BLOCKED_TPT, +				  iwl_mvm_get_primary_link(bss_vif)); +		return; +	} + +	/* Calculate the percentage of the secondary link TX/RX */ +	sec_link_tx_perc = total_tx ? sec_link_tx * 100 / total_tx : 0; +	sec_link_rx_perc = total_rx ? sec_link_rx * 100 / total_rx : 0; + +	/* +	 * The TX/RX percentage is checked only if it exceeds the required +	 * minimum. In addition, RX is checked only if the TX check failed. +	 */ +	if ((total_tx > SEC_LINK_MIN_TX && +	     sec_link_tx_perc < SEC_LINK_MIN_PERC) || +	    (total_rx > SEC_LINK_MIN_RX && +	     sec_link_rx_perc < SEC_LINK_MIN_PERC)) +		iwl_mvm_exit_esr(mvm, bss_vif, IWL_MVM_ESR_EXIT_LINK_USAGE, +				 iwl_mvm_get_primary_link(bss_vif)); +} +  void iwl_mvm_handle_rx_system_oper_stats(struct iwl_mvm *mvm,  					 struct iwl_rx_cmd_buffer *rxb)  { @@ -957,6 +1061,8 @@ void iwl_mvm_handle_rx_system_oper_stats(struct iwl_mvm *mvm,  	ieee80211_iterate_stations_atomic(mvm->hw, iwl_mvm_stats_energy_iter,  					  average_energy);  	iwl_mvm_handle_per_phy_stats(mvm, stats->per_phy); + +	iwl_mvm_update_esr_mode_tpt(mvm);  }  void iwl_mvm_handle_rx_system_oper_part1_stats(struct iwl_mvm *mvm, diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c b/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c index ce8d83c771a7..d78af2928152 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c @@ -1890,21 +1890,6 @@ static void iwl_mvm_decode_lsig(struct sk_buff *skb,  	}  } -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; @@ -2050,6 +2035,7 @@ void iwl_mvm_rx_mpdu_mq(struct iwl_mvm *mvm, struct napi_struct *napi,  	struct ieee80211_link_sta *link_sta = NULL;  	struct sk_buff *skb;  	u8 crypt_len = 0; +	u8 sta_id = le32_get_bits(desc->status, IWL_RX_MPDU_STATUS_STA_ID);  	size_t desc_size;  	struct iwl_mvm_rx_phy_data phy_data = {};  	u32 format; @@ -2168,7 +2154,7 @@ void iwl_mvm_rx_mpdu_mq(struct iwl_mvm *mvm, struct napi_struct *napi,  	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); +		rx_status->band = iwl_mvm_nl80211_band_from_phy(band);  	} else {  		rx_status->band = phy_data.channel > 14 ? NL80211_BAND_5GHZ :  			NL80211_BAND_2GHZ; @@ -2198,13 +2184,11 @@ void iwl_mvm_rx_mpdu_mq(struct iwl_mvm *mvm, struct napi_struct *napi,  	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 (!WARN_ON_ONCE(sta_id >= mvm->fw->ucode_capa.num_stations)) { +			sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]);  			if (IS_ERR(sta))  				sta = NULL; -			link_sta = rcu_dereference(mvm->fw_id_to_link_sta[id]); +			link_sta = rcu_dereference(mvm->fw_id_to_link_sta[sta_id]);  			if (sta && sta->valid_links && link_sta) {  				rx_status->link_valid = 1; @@ -2325,6 +2309,16 @@ void iwl_mvm_rx_mpdu_mq(struct iwl_mvm *mvm, struct napi_struct *napi,  			iwl_mvm_agg_rx_received(mvm, reorder_data, baid);  		} + +		if (ieee80211_is_data(hdr->frame_control)) { +			u8 sub_frame_idx = desc->amsdu_info & +				IWL_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK; + +			/* 0 means not an A-MSDU, and 1 means a new A-MSDU */ +			if (!sub_frame_idx || sub_frame_idx == 1) +				iwl_mvm_count_mpdu(mvmsta, sta_id, 1, false, +						   queue); +		}  	}  	/* management stuff on default queue */ diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/scan.c b/drivers/net/wireless/intel/iwlwifi/mvm/scan.c index f3e3986b4c72..a7ec172eeade 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/scan.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/scan.c @@ -1,6 +1,6 @@  // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause  /* - * Copyright (C) 2012-2014, 2018-2023 Intel Corporation + * Copyright (C) 2012-2014, 2018-2024 Intel Corporation   * Copyright (C) 2013-2015 Intel Mobile Communications GmbH   * Copyright (C) 2016-2017 Intel Deutschland GmbH   */ @@ -226,6 +226,14 @@ iwl_mvm_scan_type _iwl_mvm_get_scan_type(struct iwl_mvm *mvm,  		.global_cnt = 0,  	}; +	/* +	 * A scanning AP interface probably wants to generate a survey to do +	 * ACS (automatic channel selection). +	 * Force a non-fragmented scan in that case. +	 */ +	if (vif && ieee80211_vif_type_p2p(vif) == NL80211_IFTYPE_AP) +		return IWL_SCAN_TYPE_WILD; +  	ieee80211_iterate_active_interfaces_atomic(mvm->hw,  						   IEEE80211_IFACE_ITER_NORMAL,  						   iwl_mvm_scan_iterator, @@ -852,11 +860,13 @@ static inline bool iwl_mvm_scan_use_ebs(struct iwl_mvm *mvm,  	 *	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 +	 *	6. the VIF is not an AP interface (scan wants survey results)  	 */  	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))); +		(!low_latency || iwl_mvm_is_frag_ebs_supported(mvm)) && +		ieee80211_vif_type_p2p(vif) != NL80211_IFTYPE_AP);  }  static inline bool iwl_mvm_is_regular_scan(struct iwl_mvm_scan_params *params) @@ -1377,11 +1387,14 @@ static void iwl_mvm_scan_umac_dwell(struct iwl_mvm *mvm,  		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) +static u32 iwl_mvm_scan_umac_ooc_priority(int type)  { -	return iwl_mvm_is_regular_scan(params) ? -		IWL_SCAN_PRIORITY_EXT_6 : -		IWL_SCAN_PRIORITY_EXT_2; +	if (type == IWL_MVM_SCAN_REGULAR) +		return IWL_SCAN_PRIORITY_EXT_6; +	if (type == IWL_MVM_SCAN_INT_MLO) +		return IWL_SCAN_PRIORITY_EXT_4; + +	return IWL_SCAN_PRIORITY_EXT_2;  }  static void @@ -1747,8 +1760,9 @@ iwl_mvm_umac_scan_cfg_channels_v7_6g(struct iwl_mvm *mvm,  			&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, +		u8 j, k, n_s_ssids = 0, n_bssids = 0; +		u8 max_s_ssids, max_bssids; +		bool force_passive = false, found = false, allow_passive = true,  		     unsolicited_probe_on_chan = false, psc_no_listen = false;  		s8 psd_20 = IEEE80211_RNR_TBTT_PARAMS_PSD_RESERVED; @@ -1771,20 +1785,15 @@ iwl_mvm_umac_scan_cfg_channels_v7_6g(struct iwl_mvm *mvm,  		cfg->v5.iter_count = 1;  		cfg->v5.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++) {  			s8 tmp_psd_20;  			if (!(scan_6ghz_params[j].channel_idx == i))  				continue; +			unsolicited_probe_on_chan |= +				scan_6ghz_params[j].unsolicited_probe; +  			/* Use the highest PSD value allowed as advertised by  			 * APs for this channel  			 */ @@ -1796,12 +1805,69 @@ iwl_mvm_umac_scan_cfg_channels_v7_6g(struct iwl_mvm *mvm,  			     psd_20 < tmp_psd_20))  				psd_20 = tmp_psd_20; -			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++) { +		/* +		 * 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) { +				if (unsolicited_probe_on_chan) { +					max_s_ssids = 2; +					max_bssids = 6; +				} else { +					max_s_ssids = 3; +					max_bssids = 9; +				} +			} else { +				max_s_ssids = 2; +				max_bssids = 6; +			} +		} else if (cfg80211_channel_is_psc(params->channels[i])) { +			max_s_ssids = 1; +			max_bssids = 3; +		} else { +			if (unsolicited_probe_on_chan) { +				max_s_ssids = 1; +				max_bssids = 3; +			} else { +				max_s_ssids = 2; +				max_bssids = 6; +			} +		} + +		/* +		 * 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 +		 */ +		for (j = 0; j < params->n_6ghz_params; j++) { +			if (!(scan_6ghz_params[j].channel_idx == i)) +				continue; + +			found = false; + +			for (k = 0; +			     k < pp->short_ssid_num && n_s_ssids < max_s_ssids; +			     k++) {  				if (!scan_6ghz_params[j].unsolicited_probe &&  				    le32_to_cpu(pp->short_ssid[k]) ==  				    scan_6ghz_params[j].short_ssid) { @@ -1812,25 +1878,25 @@ iwl_mvm_umac_scan_cfg_channels_v7_6g(struct iwl_mvm *mvm,  					}  					/* -					 * 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. +					 * Prefer creating BSSID entries unless +					 * the short SSID probe can be done in +					 * the same channel dwell iteration. +					 * +					 * We also need to create a short SSID +					 * entry for any hidden AP.  					 */ -					if (n_used_bssid_entries >= 3) { -						s_ssid_bitmap |= BIT(k); -						s_max++; -						n_used_bssid_entries -= 3; -						found = true; +					if (3 * n_s_ssids > n_bssids && +					    !pp->direct_scan[k].len)  						break; -					} else if (pp->direct_scan[k].len) { -						s_ssid_bitmap |= BIT(k); -						s_max++; -						found = true; + +					/* Hidden AP, cannot do passive scan */ +					if (pp->direct_scan[k].len)  						allow_passive = false; -						break; -					} + +					s_ssid_bitmap |= BIT(k); +					n_s_ssids++; +					found = true; +					break;  				}  			} @@ -1842,9 +1908,12 @@ iwl_mvm_umac_scan_cfg_channels_v7_6g(struct iwl_mvm *mvm,  					    scan_6ghz_params[j].bssid,  					    ETH_ALEN)) {  					if (!(bssid_bitmap & BIT(k))) { -						bssid_bitmap |= BIT(k); -						b_max++; -						n_used_bssid_entries++; +						if (n_bssids < max_bssids) { +							bssid_bitmap |= BIT(k); +							n_bssids++; +						} else { +							force_passive = TRUE; +						}  					}  					break;  				} @@ -1858,39 +1927,6 @@ iwl_mvm_umac_scan_cfg_channels_v7_6g(struct iwl_mvm *mvm,  		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]))) @@ -2098,7 +2134,8 @@ static u16 iwl_mvm_scan_umac_flags_v2(struct iwl_mvm *mvm,  static u8 iwl_mvm_scan_umac_flags2(struct iwl_mvm *mvm,  				   struct iwl_mvm_scan_params *params, -				   struct ieee80211_vif *vif, int type) +				   struct ieee80211_vif *vif, int type, +				   u16 gen_flags)  {  	u8 flags = 0; @@ -2118,6 +2155,13 @@ static u8 iwl_mvm_scan_umac_flags2(struct iwl_mvm *mvm,  			IWL_UCODE_TLV_CAPA_SCAN_DONT_TOGGLE_ANT))  		flags |= IWL_UMAC_SCAN_GEN_PARAMS_FLAGS2_DONT_TOGGLE_ANT; +	/* Passive and AP interface -> ACS (automatic channel selection) */ +	if (gen_flags & IWL_UMAC_SCAN_GEN_FLAGS_V2_FORCE_PASSIVE && +	    ieee80211_vif_type_p2p(vif) == NL80211_IFTYPE_AP && +	    iwl_fw_lookup_notif_ver(mvm->fw, SCAN_GROUP, CHANNEL_SURVEY_NOTIF, +				    0) >= 1) +		flags |= IWL_UMAC_SCAN_GEN_FLAGS2_COLLECT_CHANNEL_STATS; +  	return flags;  } @@ -2255,8 +2299,6 @@ static int iwl_mvm_scan_umac(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  	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); @@ -2297,10 +2339,8 @@ static int iwl_mvm_scan_umac(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  	ret = iwl_mvm_fill_scan_sched_params(params, tail_v2->schedule,  					     &tail_v2->delay); -	if (ret) { -		mvm->scan_uid_status[uid] = 0; +	if (ret)  		return ret; -	}  	if (iwl_mvm_is_scan_ext_chan_supported(mvm)) {  		tail_v2->preq = params->preq; @@ -2450,9 +2490,7 @@ static int iwl_mvm_scan_umac_v12(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  	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->ooc_priority = cpu_to_le32(iwl_mvm_scan_umac_ooc_priority(type));  	cmd->uid = cpu_to_le32(uid);  	gen_flags = iwl_mvm_scan_umac_flags_v2(mvm, params, vif, type); @@ -2487,15 +2525,14 @@ static int iwl_mvm_scan_umac_v14_and_above(struct iwl_mvm *mvm,  	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->ooc_priority = cpu_to_le32(iwl_mvm_scan_umac_ooc_priority(type));  	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); +		gen_flags2 = iwl_mvm_scan_umac_flags2(mvm, params, vif, type, +						      gen_flags);  	else  		gen_flags2 = 0; @@ -2532,10 +2569,8 @@ static int iwl_mvm_scan_umac_v14_and_above(struct iwl_mvm *mvm,  							 params->n_channels,  							 pb, cp, vif->type,  							 version); -	if (!cp->count) { -		mvm->scan_uid_status[uid] = 0; +	if (!cp->count)  		return -EINVAL; -	}  	if (!params->n_ssids ||  	    (params->n_ssids == 1 && !params->ssids[0].ssid_len)) @@ -2813,7 +2848,8 @@ static int iwl_mvm_build_scan_cmd(struct iwl_mvm *mvm,  		if (ver_handler->version != scan_ver)  			continue; -		return ver_handler->handler(mvm, vif, params, type, uid); +		err = ver_handler->handler(mvm, vif, params, type, uid); +		return err ? : uid;  	}  	err = iwl_mvm_scan_umac(mvm, vif, params, type, uid); @@ -2914,9 +2950,11 @@ static void iwl_mvm_fill_respect_p2p_go(struct iwl_mvm *mvm,  	}  } -int iwl_mvm_reg_scan_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif, -			   struct cfg80211_scan_request *req, -			   struct ieee80211_scan_ies *ies) +static int _iwl_mvm_single_scan_start(struct iwl_mvm *mvm, +				      struct ieee80211_vif *vif, +				      struct cfg80211_scan_request *req, +				      struct ieee80211_scan_ies *ies, +				      int type)  {  	struct iwl_host_cmd hcmd = {  		.len = { iwl_mvm_scan_size(mvm), }, @@ -2934,7 +2972,7 @@ int iwl_mvm_reg_scan_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  		return -EBUSY;  	} -	ret = iwl_mvm_check_running_scans(mvm, IWL_MVM_SCAN_REGULAR); +	ret = iwl_mvm_check_running_scans(mvm, type);  	if (ret)  		return ret; @@ -2983,8 +3021,7 @@ int iwl_mvm_reg_scan_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  	iwl_mvm_scan_6ghz_passive_scan(mvm, ¶ms, vif); -	uid = iwl_mvm_build_scan_cmd(mvm, vif, &hcmd, ¶ms, -				     IWL_MVM_SCAN_REGULAR); +	uid = iwl_mvm_build_scan_cmd(mvm, vif, &hcmd, ¶ms, type);  	if (uid < 0)  		return uid; @@ -2999,23 +3036,35 @@ int iwl_mvm_reg_scan_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  		 */  		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); +	IWL_DEBUG_SCAN(mvm, "Scan request send success: type=%u, uid=%u\n", +		       type, uid); + +	mvm->scan_uid_status[uid] = type; +	mvm->scan_status |= type; + +	if (type == IWL_MVM_SCAN_REGULAR) { +		mvm->scan_vif = iwl_mvm_vif_from_mac80211(vif); +		schedule_delayed_work(&mvm->scan_timeout_dwork, +				      msecs_to_jiffies(SCAN_TIMEOUT)); +	}  	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_reg_scan_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +			   struct cfg80211_scan_request *req, +			   struct ieee80211_scan_ies *ies) +{ +	return _iwl_mvm_single_scan_start(mvm, vif, req, ies, +					  IWL_MVM_SCAN_REGULAR); +} +  int iwl_mvm_sched_scan_start(struct iwl_mvm *mvm,  			     struct ieee80211_vif *vif,  			     struct cfg80211_sched_scan_request *req, @@ -3132,7 +3181,9 @@ int iwl_mvm_sched_scan_start(struct iwl_mvm *mvm,  	ret = iwl_mvm_send_cmd(mvm, &hcmd);  	if (!ret) {  		IWL_DEBUG_SCAN(mvm, -			       "Sched scan request was sent successfully\n"); +			       "Sched scan request send success: type=%u, uid=%u\n", +			       type, uid); +		mvm->scan_uid_status[uid] = type;  		mvm->scan_status |= type;  	} else {  		/* If the scan failed, it usually means that the FW was unable @@ -3140,7 +3191,6 @@ int iwl_mvm_sched_scan_start(struct iwl_mvm *mvm,  		 * 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;  	} @@ -3154,9 +3204,25 @@ void iwl_mvm_rx_umac_scan_complete_notif(struct iwl_mvm *mvm,  	struct iwl_umac_scan_complete *notif = (void *)pkt->data;  	u32 uid = __le32_to_cpu(notif->uid);  	bool aborted = (notif->status == IWL_SCAN_OFFLOAD_ABORTED); +	bool select_links = false;  	mvm->mei_scan_filter.is_mei_limited_scan = false; +	IWL_DEBUG_SCAN(mvm, +		       "Scan completed: uid=%u type=%u, status=%s, EBS=%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, "Scan completed: scan_status=0x%x\n", +		       mvm->scan_status); + +	IWL_DEBUG_SCAN(mvm, +		       "Scan completed: line=%u, iter=%u, elapsed time=%u\n", +		       notif->last_schedule, notif->last_iter, +		       __le32_to_cpu(notif->time_from_last_iter)); +  	if (WARN_ON(!(mvm->scan_uid_status[uid] & mvm->scan_status)))  		return; @@ -3170,8 +3236,13 @@ void iwl_mvm_rx_umac_scan_complete_notif(struct iwl_mvm *mvm,  		struct iwl_mvm_vif_link_info *link_info =  			scan_vif->link[mvm->scan_link_id]; -		if (!WARN_ON(!link_info)) +		/* It is possible that by the time the scan is complete the link +		 * was already removed and is not valid. +		 */ +		if (link_info)  			memcpy(info.tsf_bssid, link_info->bssid, ETH_ALEN); +		else +			IWL_DEBUG_SCAN(mvm, "Scan link is no longer valid\n");  		ieee80211_scan_completed(mvm->hw, &info);  		mvm->scan_vif = NULL; @@ -3180,25 +3251,28 @@ void iwl_mvm_rx_umac_scan_complete_notif(struct iwl_mvm *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; +	} else if (mvm->scan_uid_status[uid] == IWL_MVM_SCAN_INT_MLO) { +		IWL_DEBUG_SCAN(mvm, "Internal MLO scan completed\n"); +		/* +		 * Other scan types won't necessarily scan for the MLD links channels. +		 * Therefore, only select links after successful internal scan. +		 */ +		select_links = notif->status == IWL_SCAN_OFFLOAD_COMPLETED;  	}  	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)); + +	IWL_DEBUG_SCAN(mvm, "Scan completed: after update: scan_status=0x%x\n", +		       mvm->scan_status);  	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; + +	if (select_links) +		wiphy_work_queue(mvm->hw->wiphy, &mvm->trig_link_selection_wk);  }  void iwl_mvm_rx_umac_scan_iter_complete_notif(struct iwl_mvm *mvm, @@ -3366,6 +3440,12 @@ void iwl_mvm_report_scan_aborted(struct iwl_mvm *mvm)  			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_INT_MLO); +		if (uid >= 0) { +			IWL_DEBUG_SCAN(mvm, "Internal MLO scan aborted\n"); +			mvm->scan_uid_status[uid] = 0; +		} +  		uid = iwl_mvm_scan_uid_by_status(mvm,  						 IWL_MVM_SCAN_STOPPING_REGULAR);  		if (uid >= 0) @@ -3376,6 +3456,11 @@ void iwl_mvm_report_scan_aborted(struct iwl_mvm *mvm)  		if (uid >= 0)  			mvm->scan_uid_status[uid] = 0; +		uid = iwl_mvm_scan_uid_by_status(mvm, +						 IWL_MVM_SCAN_STOPPING_INT_MLO); +		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. @@ -3412,6 +3497,10 @@ int iwl_mvm_scan_stop(struct iwl_mvm *mvm, int type, bool notify)  {  	int ret; +	IWL_DEBUG_SCAN(mvm, +		       "Request to stop scan: type=0x%x, status=0x%x\n", +		       type, mvm->scan_status); +  	if (!(mvm->scan_status & type))  		return 0; @@ -3423,6 +3512,9 @@ int iwl_mvm_scan_stop(struct iwl_mvm *mvm, int type, bool notify)  	ret = iwl_mvm_scan_stop_wait(mvm, type);  	if (!ret)  		mvm->scan_status |= type << IWL_MVM_SCAN_STOPPING_SHIFT; +	else +		IWL_DEBUG_SCAN(mvm, "Failed to stop scan\n"); +  out:  	/* Clear the scan status so the next scan requests will  	 * succeed and mark the scan as stopping, so that the Rx @@ -3447,3 +3539,276 @@ out:  	return ret;  } + +static int iwl_mvm_int_mlo_scan_start(struct iwl_mvm *mvm, +				      struct ieee80211_vif *vif, +				      struct ieee80211_channel **channels, +				      size_t n_channels) +{ +	struct cfg80211_scan_request *req = NULL; +	struct ieee80211_scan_ies ies = {}; +	size_t size, i; +	int ret; + +	lockdep_assert_held(&mvm->mutex); + +	IWL_DEBUG_SCAN(mvm, "Starting Internal MLO scan: n_channels=%zu\n", +		       n_channels); + +	if (!vif->cfg.assoc || !ieee80211_vif_is_mld(vif)) +		return -EINVAL; + +	size = struct_size(req, channels, n_channels); +	req = kzalloc(size, GFP_KERNEL); +	if (!req) +		return -ENOMEM; + +	/* set the requested channels */ +	for (i = 0; i < n_channels; i++) +		req->channels[i] = channels[i]; + +	req->n_channels = n_channels; + +	/* set the rates */ +	for (i = 0; i < NUM_NL80211_BANDS; i++) +		if (mvm->hw->wiphy->bands[i]) +			req->rates[i] = +				(1 << mvm->hw->wiphy->bands[i]->n_bitrates) - 1; + +	req->wdev = ieee80211_vif_to_wdev(vif); +	req->wiphy = mvm->hw->wiphy; +	req->scan_start = jiffies; +	req->tsf_report_link_id = -1; + +	ret = _iwl_mvm_single_scan_start(mvm, vif, req, &ies, +					 IWL_MVM_SCAN_INT_MLO); +	kfree(req); + +	IWL_DEBUG_SCAN(mvm, "Internal MLO scan: ret=%d\n", ret); +	return ret; +} + +int iwl_mvm_int_mlo_scan(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +	struct ieee80211_channel *channels[IEEE80211_MLD_MAX_NUM_LINKS]; +	unsigned long usable_links = ieee80211_vif_usable_links(vif); +	size_t n_channels = 0; +	u8 link_id; + +	lockdep_assert_held(&mvm->mutex); + +	if (mvm->scan_status & IWL_MVM_SCAN_INT_MLO) { +		IWL_DEBUG_SCAN(mvm, "Internal MLO scan is already running\n"); +		return -EBUSY; +	} + +	rcu_read_lock(); + +	for_each_set_bit(link_id, &usable_links, IEEE80211_MLD_MAX_NUM_LINKS) { +		struct ieee80211_bss_conf *link_conf = +			rcu_dereference(vif->link_conf[link_id]); + +		if (WARN_ON_ONCE(!link_conf)) +			continue; + +		channels[n_channels++] = link_conf->chanreq.oper.chan; +	} + +	rcu_read_unlock(); + +	if (!n_channels) +		return -EINVAL; + +	return iwl_mvm_int_mlo_scan_start(mvm, vif, channels, n_channels); +} + +static int iwl_mvm_chanidx_from_phy(struct iwl_mvm *mvm, +				    enum nl80211_band band, +				    u16 phy_chan_num) +{ +	struct ieee80211_supported_band *sband = mvm->hw->wiphy->bands[band]; +	int chan_idx; + +	if (WARN_ON_ONCE(!sband)) +		return -EINVAL; + +	for (chan_idx = 0; chan_idx < sband->n_channels; chan_idx++) { +		struct ieee80211_channel *channel = &sband->channels[chan_idx]; + +		if (channel->hw_value == phy_chan_num) +			return chan_idx; +	} + +	return -EINVAL; +} + +static u32 iwl_mvm_div_by_db(u32 value, u8 db) +{ +	/* +	 * 2^32 * 10**(i / 10) for i = [1, 10], skipping 0 and simply stopping +	 * at 10 dB and looping instead of using a much larger table. +	 * +	 * Using 64 bit math is overkill, but means the helper does not require +	 * a limit on the input range. +	 */ +	static const u32 db_to_val[] = { +		0xcb59185e, 0xa1866ba8, 0x804dce7a, 0x65ea59fe, 0x50f44d89, +		0x404de61f, 0x331426af, 0x2892c18b, 0x203a7e5b, 0x1999999a, +	}; + +	while (value && db > 0) { +		u8 change = min_t(u8, db, ARRAY_SIZE(db_to_val)); + +		value = (((u64)value) * db_to_val[change - 1]) >> 32; + +		db -= change; +	} + +	return value; +} + +VISIBLE_IF_IWLWIFI_KUNIT s8 +iwl_mvm_average_dbm_values(const struct iwl_umac_scan_channel_survey_notif *notif) +{ +	s8 average_magnitude; +	u32 average_factor; +	s8 sum_magnitude = -128; +	u32 sum_factor = 0; +	int i, count = 0; + +	/* +	 * To properly average the decibel values (signal values given in dBm) +	 * we need to do the math in linear space.  Doing a linear average of +	 * dB (dBm) values is a bit annoying though due to the large range of +	 * at least -10 to -110 dBm that will not fit into a 32 bit integer. +	 * +	 * A 64 bit integer should be sufficient, but then we still have the +	 * problem that there are no directly usable utility functions +	 * available. +	 * +	 * So, lets not deal with that and instead do much of the calculation +	 * with a 16.16 fixed point integer along with a base in dBm. 16.16 bit +	 * gives us plenty of head-room for adding up a few values and even +	 * doing some math on it. And the tail should be accurate enough too +	 * (1/2^16 is somewhere around -48 dB, so effectively zero). +	 * +	 * i.e. the real value of sum is: +	 *      sum = sum_factor / 2^16 * 10^(sum_magnitude / 10) mW +	 * +	 * However, that does mean we need to be able to bring two values to +	 * a common base, so we need a helper for that. +	 * +	 * Note that this function takes an input with unsigned negative dBm +	 * values but returns a signed dBm (i.e. a negative value). +	 */ + +	for (i = 0; i < ARRAY_SIZE(notif->noise); i++) { +		s8 val_magnitude; +		u32 val_factor; + +		if (notif->noise[i] == 0xff) +			continue; + +		val_factor = 0x10000; +		val_magnitude = -notif->noise[i]; + +		if (val_magnitude <= sum_magnitude) { +			u8 div_db = sum_magnitude - val_magnitude; + +			val_factor = iwl_mvm_div_by_db(val_factor, div_db); +			val_magnitude = sum_magnitude; +		} else { +			u8 div_db = val_magnitude - sum_magnitude; + +			sum_factor = iwl_mvm_div_by_db(sum_factor, div_db); +			sum_magnitude = val_magnitude; +		} + +		sum_factor += val_factor; +		count++; +	} + +	/* No valid noise measurement, return a very high noise level */ +	if (count == 0) +		return 0; + +	average_magnitude = sum_magnitude; +	average_factor = sum_factor / count; + +	/* +	 * average_factor will be a number smaller than 1.0 (0x10000) at this +	 * point. What we need to do now is to adjust average_magnitude so that +	 * average_factor is between -0.5 dB and 0.5 dB. +	 * +	 * Just do -1 dB steps and find the point where +	 *   -0.5 dB * -i dB = 0x10000 * 10^(-0.5/10) / i dB +	 *                   = div_by_db(0xe429, i) +	 * is smaller than average_factor. +	 */ +	for (i = 0; average_factor < iwl_mvm_div_by_db(0xe429, i); i++) { +		/* nothing */ +	} + +	return average_magnitude - i; +} +EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mvm_average_dbm_values); + +void iwl_mvm_rx_channel_survey_notif(struct iwl_mvm *mvm, +				     struct iwl_rx_cmd_buffer *rxb) +{ +	struct iwl_rx_packet *pkt = rxb_addr(rxb); +	const struct iwl_umac_scan_channel_survey_notif *notif = +		(void *)pkt->data; +	struct iwl_mvm_acs_survey_channel *info; +	enum nl80211_band band; +	int chan_idx; + +	lockdep_assert_held(&mvm->mutex); + +	if (!mvm->acs_survey) { +		size_t n_channels = 0; + +		for (band = 0; band < NUM_NL80211_BANDS; band++) { +			if (!mvm->hw->wiphy->bands[band]) +				continue; + +			n_channels += mvm->hw->wiphy->bands[band]->n_channels; +		} + +		mvm->acs_survey = kzalloc(struct_size(mvm->acs_survey, +						      channels, n_channels), +					  GFP_KERNEL); + +		if (!mvm->acs_survey) +			return; + +		mvm->acs_survey->n_channels = n_channels; +		n_channels = 0; +		for (band = 0; band < NUM_NL80211_BANDS; band++) { +			if (!mvm->hw->wiphy->bands[band]) +				continue; + +			mvm->acs_survey->bands[band] = +				&mvm->acs_survey->channels[n_channels]; +			n_channels += mvm->hw->wiphy->bands[band]->n_channels; +		} +	} + +	band = iwl_mvm_nl80211_band_from_phy(le32_to_cpu(notif->band)); +	chan_idx = iwl_mvm_chanidx_from_phy(mvm, band, +					    le32_to_cpu(notif->channel)); +	if (WARN_ON_ONCE(chan_idx < 0)) +		return; + +	IWL_DEBUG_SCAN(mvm, "channel survey received for freq %d\n", +		       mvm->hw->wiphy->bands[band]->channels[chan_idx].center_freq); + +	info = &mvm->acs_survey->bands[band][chan_idx]; + +	/* Times are all in ms */ +	info->time = le32_to_cpu(notif->active_time); +	info->time_busy = le32_to_cpu(notif->busy_time); +	info->time_rx = le32_to_cpu(notif->rx_time); +	info->time_tx = le32_to_cpu(notif->tx_time); +	info->noise = iwl_mvm_average_dbm_values(notif); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/sta.c b/drivers/net/wireless/intel/iwlwifi/mvm/sta.c index 491c449fd431..20d4968d692a 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/sta.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/sta.c @@ -1847,6 +1847,18 @@ int iwl_mvm_sta_init(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  	iwl_mvm_toggle_tx_ant(mvm, &mvm_sta->tx_ant); +	/* MPDUs are counted only when EMLSR is possible */ +	if (vif->type == NL80211_IFTYPE_STATION && !vif->p2p && +	    !sta->tdls && ieee80211_vif_is_mld(vif)) { +		mvm_sta->mpdu_counters = +			kcalloc(mvm->trans->num_rx_queues, +				sizeof(*mvm_sta->mpdu_counters), +				GFP_KERNEL); +		if (mvm_sta->mpdu_counters) +			for (int q = 0; q < mvm->trans->num_rx_queues; q++) +				spin_lock_init(&mvm_sta->mpdu_counters[q].lock); +	} +  	return 0;  } @@ -4392,3 +4404,77 @@ void iwl_mvm_cancel_channel_switch(struct iwl_mvm *mvm,  	if (ret)  		IWL_ERR(mvm, "Failed to cancel the channel switch\n");  } + +static int iwl_mvm_fw_sta_id_to_fw_link_id(struct iwl_mvm_vif *mvmvif, +					   u8 fw_sta_id) +{ +	struct ieee80211_link_sta *link_sta = +		rcu_dereference(mvmvif->mvm->fw_id_to_link_sta[fw_sta_id]); +	struct iwl_mvm_vif_link_info *link; + +	if (WARN_ON_ONCE(!link_sta)) +		return -EINVAL; + +	link = mvmvif->link[link_sta->link_id]; + +	if (WARN_ON_ONCE(!link)) +		return -EINVAL; + +	return link->fw_link_id; +} + +#define IWL_MVM_TPT_COUNT_WINDOW (IWL_MVM_TPT_COUNT_WINDOW_SEC * HZ) + +void iwl_mvm_count_mpdu(struct iwl_mvm_sta *mvm_sta, u8 fw_sta_id, u32 count, +			bool tx, int queue) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(mvm_sta->vif); +	struct iwl_mvm_tpt_counter *queue_counter; +	struct iwl_mvm_mpdu_counter *link_counter; +	u32 total_mpdus = 0; +	int fw_link_id; + +	/* Count only for a BSS sta, and only when EMLSR is possible */ +	if (!mvm_sta->mpdu_counters) +		return; + +	/* Map sta id to link id */ +	fw_link_id = iwl_mvm_fw_sta_id_to_fw_link_id(mvmvif, fw_sta_id); +	if (fw_link_id < 0) +		return; + +	queue_counter = &mvm_sta->mpdu_counters[queue]; +	link_counter = &queue_counter->per_link[fw_link_id]; + +	spin_lock_bh(&queue_counter->lock); + +	if (tx) +		link_counter->tx += count; +	else +		link_counter->rx += count; + +	/* +	 * When not in EMLSR, the window and the decision to enter EMLSR are +	 * handled during counting, when in EMLSR - in the statistics flow +	 */ +	if (mvmvif->esr_active) +		goto out; + +	if (time_is_before_jiffies(queue_counter->window_start + +					IWL_MVM_TPT_COUNT_WINDOW)) { +		memset(queue_counter->per_link, 0, +		       sizeof(queue_counter->per_link)); +		queue_counter->window_start = jiffies; +	} + +	for (int i = 0; i < IWL_MVM_FW_MAX_LINK_ID; i++) +		total_mpdus += tx ? queue_counter->per_link[i].tx : +				    queue_counter->per_link[i].rx; + +	if (total_mpdus > IWL_MVM_ENTER_ESR_TPT_THRESH) +		wiphy_work_queue(mvmvif->mvm->hw->wiphy, +				 &mvmvif->unblock_esr_tpt_wk); + +out: +	spin_unlock_bh(&queue_counter->lock); +} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/sta.h b/drivers/net/wireless/intel/iwlwifi/mvm/sta.h index b3450569864e..264f1f9394b6 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/sta.h +++ b/drivers/net/wireless/intel/iwlwifi/mvm/sta.h @@ -347,6 +347,24 @@ struct iwl_mvm_link_sta {  	u8 avg_energy;  }; +struct iwl_mvm_mpdu_counter { +	u32 tx; +	u32 rx; +}; + +/** + * struct iwl_mvm_tpt_counter - per-queue MPDU counter + * + * @lock: Needed to protect the counters when modified from statistics. + * @per_link: per-link counters. + * @window_start: timestamp of the counting-window start + */ +struct iwl_mvm_tpt_counter { +	spinlock_t lock; +	struct iwl_mvm_mpdu_counter per_link[IWL_MVM_FW_MAX_LINK_ID]; +	unsigned long window_start; +} ____cacheline_aligned_in_smp; +  /**   * struct iwl_mvm_sta - representation of a station in the driver   * @vif: the interface the station belongs to @@ -394,6 +412,7 @@ struct iwl_mvm_link_sta {   * @link: per link sta entries. For non-MLO only link[0] holds data. For MLO,   *	link[0] points to deflink and link[link_id] is allocated when new link   *	sta is added. + * @mpdu_counters: RX/TX MPDUs counters for each queue.   *   * 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 @@ -433,6 +452,8 @@ struct iwl_mvm_sta {  	struct iwl_mvm_link_sta deflink;  	struct iwl_mvm_link_sta __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS]; + +	struct iwl_mvm_tpt_counter *mpdu_counters;  };  u16 iwl_mvm_tid_queued(struct iwl_mvm *mvm, struct iwl_mvm_tid_data *tid_data); @@ -514,6 +535,9 @@ void iwl_mvm_update_tkip_key(struct iwl_mvm *mvm,  void iwl_mvm_rx_eosp_notif(struct iwl_mvm *mvm,  			   struct iwl_rx_cmd_buffer *rxb); +void iwl_mvm_count_mpdu(struct iwl_mvm_sta *mvm_sta, u8 fw_sta_id, u32 count, +			bool tx, int queue); +  /* 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); diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/tests/Makefile b/drivers/net/wireless/intel/iwlwifi/mvm/tests/Makefile new file mode 100644 index 000000000000..6bd56a28cffd --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/tests/Makefile @@ -0,0 +1,3 @@ +iwlmvm-tests-y += module.o links.o scan.o + +obj-$(CONFIG_IWLWIFI_KUNIT_TESTS) += iwlmvm-tests.o diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/tests/links.c b/drivers/net/wireless/intel/iwlwifi/mvm/tests/links.c new file mode 100644 index 000000000000..f49e3c98b1ba --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/tests/links.c @@ -0,0 +1,435 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * KUnit tests for channel helper functions + * + * Copyright (C) 2024 Intel Corporation + */ +#include <net/mac80211.h> +#include "../mvm.h" +#include <kunit/test.h> + +MODULE_IMPORT_NS(EXPORTED_FOR_KUNIT_TESTING); + +static struct wiphy wiphy = { +	.mtx = __MUTEX_INITIALIZER(wiphy.mtx), +}; + +static struct ieee80211_hw hw = { +	.wiphy = &wiphy, +}; + +static struct ieee80211_channel chan_5ghz = { +	.band = NL80211_BAND_5GHZ, +}; + +static struct ieee80211_channel chan_6ghz = { +	.band = NL80211_BAND_6GHZ, +}; + +static struct ieee80211_channel chan_2ghz = { +	.band = NL80211_BAND_2GHZ, +}; + +static struct cfg80211_chan_def chandef_a = {}; + +static struct cfg80211_chan_def chandef_b = {}; + +static struct iwl_mvm_phy_ctxt ctx = {}; + +static struct iwl_mvm_vif_link_info mvm_link = { +	.phy_ctxt = &ctx, +	.active = true +}; + +static struct cfg80211_bss bss = {}; + +static struct ieee80211_bss_conf link_conf = {.bss = &bss}; + +static const struct iwl_fw_cmd_version entry = { +	.group = LEGACY_GROUP, +	.cmd = BT_PROFILE_NOTIFICATION, +	.notif_ver = 4 +}; + +static struct iwl_fw fw = { +	.ucode_capa = { +		.n_cmd_versions = 1, +		.cmd_versions = &entry, +	}, +}; + +static struct iwl_mvm mvm = { +	.hw = &hw, +	.fw = &fw, +}; + +static const struct link_grading_case { +	const char *desc; +	const struct cfg80211_chan_def chandef; +	s32 signal; +	s16 channel_util; +	int chan_load_by_us; +	unsigned int grade; +} link_grading_cases[] = { +	{ +		.desc = "UHB, RSSI below range, no factors", +		.chandef = { +			.chan = &chan_6ghz, +			.width = NL80211_CHAN_WIDTH_20, +		}, +		.signal = -100, +		.grade = 177, +	}, +	{ +		.desc = "LB, RSSI in range, no factors", +		.chandef = { +			.chan = &chan_2ghz, +			.width = NL80211_CHAN_WIDTH_20, +		}, +		.signal = -84, +		.grade = 344, +	}, +	{ +		.desc = "HB, RSSI above range, no factors", +		.chandef = { +			.chan = &chan_5ghz, +			.width = NL80211_CHAN_WIDTH_20, +		}, +		.signal = -50, +		.grade = 3442, +	}, +	{ +		.desc = "HB, BSS Load IE (20 percent), inactive link, no puncturing factor", +		.chandef = { +			.chan = &chan_5ghz, +			.width = NL80211_CHAN_WIDTH_20, +		}, +		.signal = -66, +		.channel_util = 51, +		.grade = 1836, +	}, +	{ +		.desc = "LB, BSS Load IE (20 percent), active link, chan_load_by_us=10 percent. No puncturing factor", +		.chandef = { +			.chan = &chan_2ghz, +			.width = NL80211_CHAN_WIDTH_20, +		}, +		.signal = -61, +		.channel_util = 51, +		.chan_load_by_us = 10, +		.grade = 2061, +	}, +	{ +		.desc = "UHB, BSS Load IE (40 percent), active link, chan_load_by_us=50 (invalid) percent. No puncturing factor", +		.chandef = { +			.chan = &chan_6ghz, +			.width = NL80211_CHAN_WIDTH_20, +		}, +		.signal = -66, +		.channel_util = 102, +		.chan_load_by_us = 50, +		.grade = 1552, +	}, +	{	.desc = "HB, 80 MHz, no channel load factor, punctured percentage 0", +		.chandef = { +			.chan = &chan_5ghz, +			.width = NL80211_CHAN_WIDTH_80, +			.punctured = 0x0000 +		}, +		.signal = -72, +		.grade = 1750, +	}, +	{	.desc = "HB, 160 MHz, no channel load factor, punctured percentage 25", +		.chandef = { +			.chan = &chan_5ghz, +			.width = NL80211_CHAN_WIDTH_160, +			.punctured = 0x3 +		}, +		.signal = -72, +		.grade = 1312, +	}, +	{	.desc = "UHB, 320 MHz, no channel load factor, punctured percentage 12.5 (2/16)", +		.chandef = { +			.chan = &chan_6ghz, +			.width = NL80211_CHAN_WIDTH_320, +			.punctured = 0x3 +		}, +		.signal = -72, +		.grade = 1806, +	}, +	{	.desc = "HB, 160 MHz, channel load 20, channel load by us 10, punctured percentage 25", +		.chandef = { +			.chan = &chan_5ghz, +			.width = NL80211_CHAN_WIDTH_160, +			.punctured = 0x3 +		}, +		.channel_util = 51, +		.chan_load_by_us = 10, +		.signal = -72, +		.grade = 1179, +	}, +}; + +KUNIT_ARRAY_PARAM_DESC(link_grading, link_grading_cases, desc) + +static void setup_link_conf(struct kunit *test) +{ +	const struct link_grading_case *params = test->param_value; +	size_t vif_size = sizeof(struct ieee80211_vif) + +		sizeof(struct iwl_mvm_vif); +	struct ieee80211_vif *vif = kunit_kzalloc(test, vif_size, GFP_KERNEL); +	struct ieee80211_bss_load_elem *bss_load; +	struct element *element; +	size_t ies_size = sizeof(struct cfg80211_bss_ies) + sizeof(*bss_load) + sizeof(element); +	struct cfg80211_bss_ies *ies; +	struct iwl_mvm_vif *mvmvif; + +	KUNIT_ASSERT_NOT_NULL(test, vif); + +	mvmvif = iwl_mvm_vif_from_mac80211(vif); +	if (params->chan_load_by_us > 0) { +		ctx.channel_load_by_us = params->chan_load_by_us; +		mvmvif->link[0] = &mvm_link; +	} + +	link_conf.vif = vif; +	link_conf.chanreq.oper = params->chandef; +	bss.signal = DBM_TO_MBM(params->signal); + +	ies = kunit_kzalloc(test, ies_size, GFP_KERNEL); +	KUNIT_ASSERT_NOT_NULL(test, ies); +	ies->len = sizeof(*bss_load) + sizeof(struct element); + +	element = (void *)ies->data; +	element->datalen = sizeof(*bss_load); +	element->id = 11; + +	bss_load = (void *)element->data; +	bss_load->channel_util = params->channel_util; + +	rcu_assign_pointer(bss.ies, ies); +} + +static void test_link_grading(struct kunit *test) +{ +	const struct link_grading_case *params = test->param_value; +	unsigned int ret; + +	setup_link_conf(test); + +	rcu_read_lock(); +	ret = iwl_mvm_get_link_grade(&link_conf); +	rcu_read_unlock(); + +	KUNIT_EXPECT_EQ(test, ret, params->grade); + +	kunit_kfree(test, link_conf.vif); +	RCU_INIT_POINTER(bss.ies, NULL); +} + +static struct kunit_case link_grading_test_cases[] = { +	KUNIT_CASE_PARAM(test_link_grading, link_grading_gen_params), +	{} +}; + +static struct kunit_suite link_grading = { +	.name = "iwlmvm-link-grading", +	.test_cases = link_grading_test_cases, +}; + +kunit_test_suite(link_grading); + +static const struct valid_link_pair_case { +	const char *desc; +	bool bt; +	struct ieee80211_channel *chan_a; +	struct ieee80211_channel *chan_b; +	enum nl80211_chan_width cw_a; +	enum nl80211_chan_width cw_b; +	s32 sig_a; +	s32 sig_b; +	bool csa_a; +	bool valid; +} valid_link_pair_cases[] = { +	{ +		.desc = "HB + UHB, valid.", +		.chan_a = &chan_6ghz, +		.chan_b = &chan_5ghz, +		.valid = true, +	}, +	{ +		.desc = "LB + HB, no BT.", +		.chan_a = &chan_2ghz, +		.chan_b = &chan_5ghz, +		.valid = false, +	}, +	{ +		.desc = "LB + HB, with BT.", +		.bt = true, +		.chan_a = &chan_2ghz, +		.chan_b = &chan_5ghz, +		.valid = false, +	}, +	{ +		.desc = "Same band", +		.chan_a = &chan_2ghz, +		.chan_b = &chan_2ghz, +		.valid = false, +	}, +	{ +		.desc = "RSSI: LB, 20 MHz, low", +		.chan_a = &chan_2ghz, +		.cw_a = NL80211_CHAN_WIDTH_20, +		.sig_a = -68, +		.chan_b = &chan_5ghz, +		.valid = false, +	}, +	{ +		.desc = "RSSI: UHB, 20 MHz, high", +		.chan_a = &chan_6ghz, +		.cw_a = NL80211_CHAN_WIDTH_20, +		.sig_a = -66, +		.chan_b = &chan_5ghz, +		.cw_b = NL80211_CHAN_WIDTH_20, +		.valid = true, +	}, +	{ +		.desc = "RSSI: UHB, 40 MHz, low", +		.chan_a = &chan_6ghz, +		.cw_a = NL80211_CHAN_WIDTH_40, +		.sig_a = -65, +		.chan_b = &chan_5ghz, +		.cw_b = NL80211_CHAN_WIDTH_40, +		.valid = false, +	}, +	{ +		.desc = "RSSI: UHB, 40 MHz, high", +		.chan_a = &chan_6ghz, +		.cw_a = NL80211_CHAN_WIDTH_40, +		.sig_a = -63, +		.chan_b = &chan_5ghz, +		.cw_b = NL80211_CHAN_WIDTH_40, +		.valid = true, +	}, +	{ +		.desc = "RSSI: UHB, 80 MHz, low", +		.chan_a = &chan_6ghz, +		.cw_a = NL80211_CHAN_WIDTH_80, +		.sig_a = -62, +		.chan_b = &chan_5ghz, +		.cw_b = NL80211_CHAN_WIDTH_80, +		.valid = false, +	}, +	{ +		.desc = "RSSI: UHB, 80 MHz, high", +		.chan_a = &chan_6ghz, +		.cw_a = NL80211_CHAN_WIDTH_80, +		.sig_a = -60, +		.chan_b = &chan_5ghz, +		.cw_b = NL80211_CHAN_WIDTH_80, +		.valid = true, +	}, +	{ +		.desc = "RSSI: UHB, 160 MHz, low", +		.chan_a = &chan_6ghz, +		.cw_a = NL80211_CHAN_WIDTH_160, +		.sig_a = -59, +		.chan_b = &chan_5ghz, +		.cw_b = NL80211_CHAN_WIDTH_160, +		.valid = false, +	}, +	{ +		.desc = "RSSI: HB, 160 MHz, high", +		.chan_a = &chan_6ghz, +		.cw_a = NL80211_CHAN_WIDTH_160, +		.sig_a = -5, +		.chan_b = &chan_5ghz, +		.cw_b = NL80211_CHAN_WIDTH_160, +		.valid = true, +	}, +	{ +		.desc = "CSA active", +		.chan_a = &chan_6ghz, +		.cw_a = NL80211_CHAN_WIDTH_160, +		.sig_a = -5, +		.chan_b = &chan_5ghz, +		.cw_b = NL80211_CHAN_WIDTH_160, +		.valid = false, +		/* same as previous entry with valid=true except for CSA */ +		.csa_a = true, +	}, +}; + +KUNIT_ARRAY_PARAM_DESC(valid_link_pair, valid_link_pair_cases, desc) + +static void test_valid_link_pair(struct kunit *test) +{ +	const struct valid_link_pair_case *params = test->param_value; +	size_t vif_size = sizeof(struct ieee80211_vif) + +		sizeof(struct iwl_mvm_vif); +	struct ieee80211_vif *vif = kunit_kzalloc(test, vif_size, GFP_KERNEL); +	struct iwl_trans *trans = kunit_kzalloc(test, sizeof(struct iwl_trans), +						GFP_KERNEL); +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	struct iwl_mvm_link_sel_data link_a = { +		.chandef = &chandef_a, +		.link_id = 1, +		.signal = params->sig_a, +	}; +	struct iwl_mvm_link_sel_data link_b = { +		.chandef = &chandef_b, +		.link_id = 5, +		.signal = params->sig_b, +	}; +	struct ieee80211_bss_conf *conf; +	bool result; + +	KUNIT_ASSERT_NOT_NULL(test, vif); +	KUNIT_ASSERT_NOT_NULL(test, trans); + +	chandef_a.chan = params->chan_a; +	chandef_b.chan = params->chan_b; + +	chandef_a.width = params->cw_a ?: NL80211_CHAN_WIDTH_20; +	chandef_b.width = params->cw_b ?: NL80211_CHAN_WIDTH_20; + +#ifdef CONFIG_IWLWIFI_SUPPORT_DEBUG_OVERRIDES +	trans->dbg_cfg = default_dbg_config; +#endif +	mvm.trans = trans; + +	mvm.last_bt_notif.wifi_loss_low_rssi = params->bt; +	mvmvif->mvm = &mvm; + +	conf = kunit_kzalloc(test, sizeof(*vif->link_conf[0]), GFP_KERNEL); +	KUNIT_ASSERT_NOT_NULL(test, conf); +	conf->chanreq.oper = chandef_a; +	conf->csa_active = params->csa_a; +	vif->link_conf[link_a.link_id] = (void __rcu *)conf; + +	conf = kunit_kzalloc(test, sizeof(*vif->link_conf[0]), GFP_KERNEL); +	KUNIT_ASSERT_NOT_NULL(test, conf); +	conf->chanreq.oper = chandef_b; +	vif->link_conf[link_b.link_id] = (void __rcu *)conf; + +	wiphy_lock(&wiphy); +	result = iwl_mvm_mld_valid_link_pair(vif, &link_a, &link_b); +	wiphy_unlock(&wiphy); + +	KUNIT_EXPECT_EQ(test, result, params->valid); + +	kunit_kfree(test, vif); +	kunit_kfree(test, trans); +} + +static struct kunit_case valid_link_pair_test_cases[] = { +	KUNIT_CASE_PARAM(test_valid_link_pair, valid_link_pair_gen_params), +	{}, +}; + +static struct kunit_suite valid_link_pair = { +	.name = "iwlmvm-valid-link-pair", +	.test_cases = valid_link_pair_test_cases, +}; + +kunit_test_suite(valid_link_pair); diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/tests/module.c b/drivers/net/wireless/intel/iwlwifi/mvm/tests/module.c new file mode 100644 index 000000000000..f556acbac77f --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/tests/module.c @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This is just module boilerplate for the iwlmvm kunit module. + * + * Copyright (C) 2024 Intel Corporation + */ +#include <linux/module.h> + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("kunit tests for iwlmvm"); diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/tests/scan.c b/drivers/net/wireless/intel/iwlwifi/mvm/tests/scan.c new file mode 100644 index 000000000000..d3b6a57c3ebe --- /dev/null +++ b/drivers/net/wireless/intel/iwlwifi/mvm/tests/scan.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * KUnit tests for channel helper functions + * + * Copyright (C) 2024 Intel Corporation + */ +#include <net/mac80211.h> +#include "../mvm.h" +#include <kunit/test.h> + +MODULE_IMPORT_NS(EXPORTED_FOR_KUNIT_TESTING); + +static const struct acs_average_db_case { +	const char *desc; +	u8 neg_dbm[22]; +	s8 result; +} acs_average_db_cases[] = { +	{ +		.desc = "Smallest possible value, all filled", +		.neg_dbm = { +			128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +			128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +			128, 128 +		}, +		.result = -128, +	}, +	{ +		.desc = "Biggest possible value, all filled", +		.neg_dbm = { +			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +			0, 0, +		}, +		.result = 0, +	}, +	{ +		.desc = "Smallest possible value, partial filled", +		.neg_dbm = { +			128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +			0xff, 0xff, 0xff, 0xff, 0xff, +			0xff, 0xff, 0xff, 0xff, 0xff, +			0xff, 0xff, +		}, +		.result = -128, +	}, +	{ +		.desc = "Biggest possible value, partial filled", +		.neg_dbm = { +			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +			0xff, 0xff, 0xff, 0xff, 0xff, +			0xff, 0xff, 0xff, 0xff, 0xff, +			0xff, 0xff, +		}, +		.result = 0, +	}, +	{ +		.desc = "Adding -80dBm to -75dBm until it is still rounded to -79dBm", +		.neg_dbm = { +			75, 80, 80, 80, 80, 80, 80, 80, 80, 80, +			80, 80, 80, 80, 80, 80, 80, 0xff, 0xff, 0xff, +			0xff, 0xff, +		}, +		.result = -79, +	}, +	{ +		.desc = "Adding -80dBm to -75dBm until it is just rounded to -80dBm", +		.neg_dbm = { +			75, 80, 80, 80, 80, 80, 80, 80, 80, 80, +			80, 80, 80, 80, 80, 80, 80, 80, 0xff, 0xff, +			0xff, 0xff, +		}, +		.result = -80, +	}, +}; + +KUNIT_ARRAY_PARAM_DESC(acs_average_db, acs_average_db_cases, desc) + +static void test_acs_average_db(struct kunit *test) +{ +	const struct acs_average_db_case *params = test->param_value; +	struct iwl_umac_scan_channel_survey_notif notif; +	int i; + +	/* Test the values in the given order */ +	for (i = 0; i < ARRAY_SIZE(params->neg_dbm); i++) +		notif.noise[i] = params->neg_dbm[i]; +	KUNIT_ASSERT_EQ(test, +			iwl_mvm_average_dbm_values(¬if), +			params->result); + +	/* Test in reverse order */ +	for (i = 0; i < ARRAY_SIZE(params->neg_dbm); i++) +		notif.noise[ARRAY_SIZE(params->neg_dbm) - i - 1] = +			params->neg_dbm[i]; +	KUNIT_ASSERT_EQ(test, +			iwl_mvm_average_dbm_values(¬if), +			params->result); +} + +static struct kunit_case acs_average_db_case[] = { +	KUNIT_CASE_PARAM(test_acs_average_db, acs_average_db_gen_params), +	{} +}; + +static struct kunit_suite acs_average_db = { +	.name = "iwlmvm-acs-average-db", +	.test_cases = acs_average_db_case, +}; + +kunit_test_suite(acs_average_db); diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/time-event.c b/drivers/net/wireless/intel/iwlwifi/mvm/time-event.c index ad960faceb0d..8ee4498f4245 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/time-event.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/time-event.c @@ -47,6 +47,10 @@ void iwl_mvm_te_clear_data(struct iwl_mvm *mvm,  static void iwl_mvm_cleanup_roc(struct iwl_mvm *mvm)  { +	struct ieee80211_vif *vif = mvm->p2p_device_vif; + +	lockdep_assert_held(&mvm->mutex); +  	/*  	 * Clear the ROC_RUNNING status bit.  	 * This will cause the TX path to drop offchannel transmissions. @@ -70,9 +74,7 @@ static void iwl_mvm_cleanup_roc(struct iwl_mvm *mvm)  		 * not really racy.  		 */ -		if (!WARN_ON(!mvm->p2p_device_vif)) { -			struct ieee80211_vif *vif = mvm->p2p_device_vif; - +		if (!WARN_ON(!vif)) {  			mvmvif = iwl_mvm_vif_from_mac80211(vif);  			iwl_mvm_flush_sta(mvm, mvmvif->deflink.bcast_sta.sta_id,  					  mvmvif->deflink.bcast_sta.tfd_queue_msk); @@ -106,6 +108,7 @@ static void iwl_mvm_cleanup_roc(struct iwl_mvm *mvm)  		if (mvm->mld_api_is_used) {  			iwl_mvm_mld_rm_aux_sta(mvm); +			mutex_unlock(&mvm->mutex);  			return;  		} @@ -115,6 +118,10 @@ static void iwl_mvm_cleanup_roc(struct iwl_mvm *mvm)  		if (iwl_mvm_has_new_station_api(mvm->fw))  			iwl_mvm_rm_aux_sta(mvm);  	} + +	mutex_unlock(&mvm->mutex); +	if (vif) +		iwl_mvm_esr_non_bss_link(mvm, vif, 0, false);  }  void iwl_mvm_roc_done_wk(struct work_struct *wk) @@ -122,8 +129,8 @@ void iwl_mvm_roc_done_wk(struct work_struct *wk)  	struct iwl_mvm *mvm = container_of(wk, struct iwl_mvm, roc_done_wk);  	mutex_lock(&mvm->mutex); +	/* Mutex is released inside */  	iwl_mvm_cleanup_roc(mvm); -	mutex_unlock(&mvm->mutex);  }  static void iwl_mvm_roc_finished(struct iwl_mvm *mvm) @@ -1220,6 +1227,8 @@ 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; +	mutex_lock(&mvm->mutex); +  	if (fw_has_capa(&mvm->fw->ucode_capa,  			IWL_UCODE_TLV_CAPA_SESSION_PROT_CMD)) {  		mvmvif = iwl_mvm_vif_from_mac80211(vif); @@ -1263,6 +1272,8 @@ cleanup_roc:  	set_bit(vif->type == NL80211_IFTYPE_P2P_DEVICE ?  		IWL_MVM_STATUS_ROC_RUNNING : IWL_MVM_STATUS_ROC_AUX_RUNNING,  		&mvm->status); + +	/* Mutex is released inside this function */  	iwl_mvm_cleanup_roc(mvm);  } diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/tx.c b/drivers/net/wireless/intel/iwlwifi/mvm/tx.c index 782ddc8c296b..1d695ece93e9 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/tx.c @@ -1870,6 +1870,7 @@ static void iwl_mvm_rx_tx_cmd_single(struct iwl_mvm *mvm,  				IWL_DEBUG_TX_REPLY(mvm,  						   "Next reclaimed packet:%d\n",  						   next_reclaimed); +				iwl_mvm_count_mpdu(mvmsta, sta_id, 1, true, 0);  			} else {  				IWL_DEBUG_TX_REPLY(mvm,  						   "NDP - don't update next_reclaimed\n"); @@ -2247,9 +2248,13 @@ void iwl_mvm_rx_ba_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb)  					   le32_to_cpu(ba_res->tx_rate), false);  		} -		if (mvmsta) +		if (mvmsta) {  			iwl_mvm_tx_airtime(mvm, mvmsta,  					   le32_to_cpu(ba_res->wireless_time)); + +			iwl_mvm_count_mpdu(mvmsta, sta_id, +					   le16_to_cpu(ba_res->txed), true, 0); +		}  		rcu_read_unlock();  		return;  	} diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/utils.c b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c index ab56ff87c6f9..47283a358ffd 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/utils.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c @@ -1,6 +1,6 @@  // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause  /* - * Copyright (C) 2012-2014, 2018-2023 Intel Corporation + * Copyright (C) 2012-2014, 2018-2024 Intel Corporation   * Copyright (C) 2013-2014 Intel Mobile Communications GmbH   * Copyright (C) 2015-2017 Intel Deutschland GmbH   */ @@ -344,6 +344,26 @@ static bool iwl_wait_stats_complete(struct iwl_notif_wait_data *notif_wait,  	return true;  } +#define PERIODIC_STAT_RATE 5 + +int iwl_mvm_request_periodic_system_statistics(struct iwl_mvm *mvm, bool enable) +{ +	u32 flags = enable ? 0 : IWL_STATS_CFG_FLG_DISABLE_NTFY_MSK; +	u32 type = enable ? (IWL_STATS_NTFY_TYPE_ID_OPER | +			     IWL_STATS_NTFY_TYPE_ID_OPER_PART1) : 0; +	struct iwl_system_statistics_cmd system_cmd = { +		.cfg_mask = cpu_to_le32(flags), +		.config_time_sec = cpu_to_le32(enable ? +					       PERIODIC_STAT_RATE : 0), +		.type_id_mask = cpu_to_le32(type), +	}; + +	return iwl_mvm_send_cmd_pdu(mvm, +				    WIDE_ID(SYSTEM_GROUP, +					    SYSTEM_STATISTICS_CMD), +				    0, sizeof(system_cmd), &system_cmd); +} +  static int iwl_mvm_request_system_statistics(struct iwl_mvm *mvm, bool clear,  					     u8 cmd_ver)  { @@ -415,6 +435,13 @@ int iwl_mvm_request_statistics(struct iwl_mvm *mvm, bool clear)  					   IWL_FW_CMD_VER_UNKNOWN);  	int ret; +	/* +	 * Don't request statistics during restart, they'll not have any useful +	 * information right after restart, nor is clearing needed +	 */ +	if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) +		return 0; +  	if (cmd_ver != IWL_FW_CMD_VER_UNKNOWN)  		return iwl_mvm_request_system_statistics(mvm, clear, cmd_ver); diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info-gen3.c b/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info-gen3.c index c8fc8b4fd85c..ebf11f276b20 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info-gen3.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info-gen3.c @@ -1,13 +1,34 @@  // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause  /* - * Copyright (C) 2018-2023 Intel Corporation + * Copyright (C) 2018-2024 Intel Corporation   */ +#include <linux/dmi.h>  #include "iwl-trans.h"  #include "iwl-fh.h"  #include "iwl-context-info-gen3.h"  #include "internal.h"  #include "iwl-prph.h" +static const struct dmi_system_id dmi_force_scu_active_approved_list[] = { +	{ .ident = "DELL", +	  .matches = { +			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), +		}, +	}, +	{ .ident = "DELL", +	  .matches = { +			DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), +		}, +	}, +	/* keep last */ +	{} +}; + +static bool iwl_is_force_scu_active_approved(void) +{ +	return !!dmi_check_system(dmi_force_scu_active_approved_list); +} +  static void  iwl_pcie_ctxt_info_dbg_enable(struct iwl_trans *trans,  			      struct iwl_prph_scratch_hwm_cfg *dbg_cfg, @@ -128,6 +149,14 @@ int iwl_pcie_ctxt_info_gen3_init(struct iwl_trans *trans,  	if (trans->trans_cfg->imr_enabled)  		control_flags |= IWL_PRPH_SCRATCH_IMR_DEBUG_EN; +	if (CSR_HW_REV_TYPE(trans->hw_rev) == IWL_CFG_MAC_TYPE_GL && +	    iwl_is_force_scu_active_approved()) { +		control_flags |= IWL_PRPH_SCRATCH_SCU_FORCE_ACTIVE; +		IWL_DEBUG_FW(trans, +			     "Context Info: Set SCU_FORCE_ACTIVE (0x%x) in control_flags\n", +			     IWL_PRPH_SCRATCH_SCU_FORCE_ACTIVE); +	} +  	/* initialize RX default queue */  	prph_sc_ctrl->rbd_cfg.free_rbd_addr =  		cpu_to_le64(trans_pcie->rxq->bd_dma); diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c index 4a657036b9d6..fed2754be680 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/drv.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/drv.c @@ -33,7 +33,7 @@ extern int _invalid_type;  	.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[] = { +VISIBLE_IF_IWLWIFI_KUNIT 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 */ @@ -492,7 +492,6 @@ static const struct pci_device_id iwl_hw_card_ids[] = {  	{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)}, @@ -506,6 +505,7 @@ static const struct pci_device_id iwl_hw_card_ids[] = {  	{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)}, +	{IWL_PCI_DEVICE(0x4D40, PCI_ANY_ID, iwl_bz_trans_cfg)},  /* Sc devices */  	{IWL_PCI_DEVICE(0xE440, PCI_ANY_ID, iwl_sc_trans_cfg)}, @@ -517,6 +517,7 @@ static const struct pci_device_id iwl_hw_card_ids[] = {  	{0}  };  MODULE_DEVICE_TABLE(pci, iwl_hw_card_ids); +EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_hw_card_ids);  #define _IWL_DEV_INFO(_device, _subdevice, _mac_type, _mac_step, _rf_type, \  		      _rf_id, _rf_step, _no_160, _cores, _cdb, _cfg, _name) \ @@ -946,11 +947,6 @@ VISIBLE_IF_IWLWIFI_KUNIT const struct iwl_dev_info iwl_dev_info_table[] = {  		      iwl_cfg_ma, 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_ANY, IWL_CFG_NO_CDB, -		      iwl_cfg_ma, 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_ANY, IWL_CFG_NO_CDB,  		      iwl_cfg_ma, iwl_ax231_name), @@ -1002,18 +998,25 @@ VISIBLE_IF_IWLWIFI_KUNIT const struct iwl_dev_info iwl_dev_info_table[] = {  		      iwlax411_2ax_cfg_so_gf4_a0, iwl_ax411_name),  /* Bz */ +/* FIXME: need to change the naming according to the actual CRF */  	_IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY,  		      IWL_CFG_MAC_TYPE_BZ, IWL_CFG_ANY,  		      IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_ANY,  		      IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_ANY, -		      iwl_cfg_bz, iwl_bz_name), +		      iwl_cfg_bz, iwl_fm_name), + +	_IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY, +		      IWL_CFG_MAC_TYPE_BZ_W, IWL_CFG_ANY, +		      IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_ANY, +		      IWL_CFG_ANY, IWL_CFG_ANY, IWL_CFG_ANY, +		      iwl_cfg_bz, iwl_fm_name),  /* Ga (Gl) */  	_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_320, IWL_CFG_ANY, IWL_CFG_NO_CDB, -		      iwl_cfg_gl, iwl_bz_name), +		      iwl_cfg_gl, iwl_gl_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, @@ -1100,24 +1103,6 @@ VISIBLE_IF_IWLWIFI_KUNIT const struct iwl_dev_info iwl_dev_info_table[] = {  		      IWL_CFG_NO_160, IWL_CFG_CORES_BT, IWL_CFG_NO_CDB,  		      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_ANY, -		      IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, -		      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_ANY, -		      IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, -		      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_ANY, -		      IWL_CFG_160, IWL_CFG_ANY, IWL_CFG_NO_CDB, -		      iwl_cfg_ma, iwl_ax204_name), -  /* Sc */  	_IWL_DEV_INFO(IWL_CFG_ANY, IWL_CFG_ANY,  		      IWL_CFG_MAC_TYPE_SC, IWL_CFG_ANY, @@ -1150,6 +1135,7 @@ static void get_crf_id(struct iwl_trans *iwl_trans)  {  	u32 sd_reg_ver_addr;  	u32 val = 0; +	u8 step;  	if (iwl_trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210)  		sd_reg_ver_addr = SD_REG_VER_GEN2; @@ -1168,16 +1154,23 @@ static void get_crf_id(struct iwl_trans *iwl_trans)  	iwl_trans->hw_cnv_id =  		iwl_read_prph_no_grab(iwl_trans, CNVI_AUX_MISC_CHIP); +	/* For BZ-W, take B step also when A step is indicated */ +	if (CSR_HW_REV_TYPE(iwl_trans->hw_rev) == IWL_CFG_MAC_TYPE_BZ_W) +		step = SILICON_B_STEP; +  	/* In BZ, the MAC step must be read from the CNVI aux register */  	if (CSR_HW_REV_TYPE(iwl_trans->hw_rev) == IWL_CFG_MAC_TYPE_BZ) { -		u8 step = CNVI_AUX_MISC_CHIP_MAC_STEP(iwl_trans->hw_cnv_id); +		step = CNVI_AUX_MISC_CHIP_MAC_STEP(iwl_trans->hw_cnv_id);  		/* For BZ-U, take B step also when A step is indicated */  		if ((CNVI_AUX_MISC_CHIP_PROD_TYPE(iwl_trans->hw_cnv_id) ==  		    CNVI_AUX_MISC_CHIP_PROD_TYPE_BZ_U) &&  		    step == SILICON_A_STEP)  			step = SILICON_B_STEP; +	} +	if (CSR_HW_REV_TYPE(iwl_trans->hw_rev) == IWL_CFG_MAC_TYPE_BZ || +	    CSR_HW_REV_TYPE(iwl_trans->hw_rev) == IWL_CFG_MAC_TYPE_BZ_W) {  		iwl_trans->hw_rev_step = step;  		iwl_trans->hw_rev |= step;  	} @@ -1224,12 +1217,7 @@ static int map_crf_id(struct iwl_trans *iwl_trans)  	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: -	case REG_CRF_ID_TYPE_FMI: -	case REG_CRF_ID_TYPE_FMR:  		iwl_trans->hw_rf_id = (IWL_CFG_RF_TYPE_FM << 12);  		break;  	case REG_CRF_ID_TYPE_WHP: diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/internal.h b/drivers/net/wireless/intel/iwlwifi/pcie/internal.h index 7805a42948af..a7eebe400b5b 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/internal.h +++ b/drivers/net/wireless/intel/iwlwifi/pcie/internal.h @@ -386,7 +386,7 @@ struct iwl_trans_pcie {  	dma_addr_t iml_dma_addr;  	struct iwl_trans *trans; -	struct net_device napi_dev; +	struct net_device *napi_dev;  	/* INT ICT Table */  	__le32 *ict_tbl; diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/rx.c b/drivers/net/wireless/intel/iwlwifi/pcie/rx.c index 9c2461ba13c5..984d7bcd381f 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/rx.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/rx.c @@ -1000,6 +1000,11 @@ void iwl_pcie_rx_init_rxb_lists(struct iwl_rxq *rxq)  static int iwl_pcie_rx_handle(struct iwl_trans *trans, int queue, int budget); +static inline struct iwl_trans_pcie *iwl_netdev_to_trans_pcie(struct net_device *dev) +{ +	return *(struct iwl_trans_pcie **)netdev_priv(dev); +} +  static int iwl_pcie_napi_poll(struct napi_struct *napi, int budget)  {  	struct iwl_rxq *rxq = container_of(napi, struct iwl_rxq, napi); @@ -1007,7 +1012,7 @@ static int iwl_pcie_napi_poll(struct napi_struct *napi, int budget)  	struct iwl_trans *trans;  	int ret; -	trans_pcie = container_of(napi->dev, struct iwl_trans_pcie, napi_dev); +	trans_pcie = iwl_netdev_to_trans_pcie(napi->dev);  	trans = trans_pcie->trans;  	ret = iwl_pcie_rx_handle(trans, rxq->id, budget); @@ -1034,7 +1039,7 @@ static int iwl_pcie_napi_poll_msix(struct napi_struct *napi, int budget)  	struct iwl_trans *trans;  	int ret; -	trans_pcie = container_of(napi->dev, struct iwl_trans_pcie, napi_dev); +	trans_pcie = iwl_netdev_to_trans_pcie(napi->dev);  	trans = trans_pcie->trans;  	ret = iwl_pcie_rx_handle(trans, rxq->id, budget); @@ -1131,7 +1136,7 @@ static int _iwl_pcie_rx_init(struct iwl_trans *trans)  			if (trans_pcie->msix_enabled)  				poll = iwl_pcie_napi_poll_msix; -			netif_napi_add(&trans_pcie->napi_dev, &rxq->napi, +			netif_napi_add(trans_pcie->napi_dev, &rxq->napi,  				       poll);  			napi_enable(&rxq->napi);  		} diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c index 6c76b2dd6878..d5a887b3a4bb 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/trans.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/trans.c @@ -1986,13 +1986,6 @@ static void iwl_trans_pcie_configure(struct iwl_trans *trans,  	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;  } @@ -2074,6 +2067,8 @@ void iwl_trans_pcie_free(struct iwl_trans *trans)  		iwl_pcie_free_ict(trans);  	} +	free_netdev(trans_pcie->napi_dev); +  	iwl_pcie_free_invalid_tx_cmd(trans);  	iwl_pcie_free_fw_monitor(trans); @@ -3594,7 +3589,7 @@ 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_pcie *trans_pcie, **priv;  	struct iwl_trans *trans;  	int ret, addr_size;  	const struct iwl_trans_ops *ops = &trans_ops_pcie_gen2; @@ -3623,6 +3618,18 @@ struct iwl_trans *iwl_trans_pcie_alloc(struct pci_dev *pdev,  	trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); +	/* Initialize NAPI here - it should be before registering to mac80211 +	 * in the opmode but after the HW struct is allocated. +	 */ +	trans_pcie->napi_dev = alloc_netdev_dummy(sizeof(struct iwl_trans_pcie *)); +	if (!trans_pcie->napi_dev) { +		ret = -ENOMEM; +		goto out_free_trans; +	} +	/* The private struct in netdev is a pointer to struct iwl_trans_pcie */ +	priv = netdev_priv(trans_pcie->napi_dev); +	*priv = trans_pcie; +  	trans_pcie->trans = trans;  	trans_pcie->opmode_down = true;  	spin_lock_init(&trans_pcie->irq_lock); @@ -3637,7 +3644,7 @@ struct iwl_trans *iwl_trans_pcie_alloc(struct pci_dev *pdev,  						   WQ_HIGHPRI | WQ_UNBOUND, 0);  	if (!trans_pcie->rba.alloc_wq) {  		ret = -ENOMEM; -		goto out_free_trans; +		goto out_free_ndev;  	}  	INIT_WORK(&trans_pcie->rba.rx_alloc, iwl_pcie_rx_allocator_work); @@ -3757,6 +3764,8 @@ out_free_ict:  	iwl_pcie_free_ict(trans);  out_no_pci:  	destroy_workqueue(trans_pcie->rba.alloc_wq); +out_free_ndev: +	free_netdev(trans_pcie->napi_dev);  out_free_trans:  	iwl_trans_free(trans);  	return ERR_PTR(ret); diff --git a/drivers/net/wireless/intel/iwlwifi/tests/Makefile b/drivers/net/wireless/intel/iwlwifi/tests/Makefile index 5658471bdf0a..84491488f589 100644 --- a/drivers/net/wireless/intel/iwlwifi/tests/Makefile +++ b/drivers/net/wireless/intel/iwlwifi/tests/Makefile @@ -2,6 +2,6 @@  iwlwifi-tests-y += module.o devinfo.o -ccflags-y += -I$(srctree)/$(src)/../ +ccflags-y += -I$(src)/../  obj-$(CONFIG_IWLWIFI_KUNIT_TESTS) += iwlwifi-tests.o diff --git a/drivers/net/wireless/intel/iwlwifi/tests/devinfo.c b/drivers/net/wireless/intel/iwlwifi/tests/devinfo.c index 7aa47fce6e2d..7361b6d0cdb8 100644 --- a/drivers/net/wireless/intel/iwlwifi/tests/devinfo.c +++ b/drivers/net/wireless/intel/iwlwifi/tests/devinfo.c @@ -2,9 +2,10 @@  /*   * KUnit tests for the iwlwifi device info table   * - * Copyright (C) 2023 Intel Corporation + * Copyright (C) 2023-2024 Intel Corporation   */  #include <kunit/test.h> +#include <linux/pci.h>  #include "iwl-drv.h"  #include "iwl-config.h" @@ -41,8 +42,31 @@ static void devinfo_table_order(struct kunit *test)  	}  } +static void devinfo_pci_ids(struct kunit *test) +{ +	struct pci_dev *dev; + +	dev = kunit_kmalloc(test, sizeof(*dev), GFP_KERNEL); +	KUNIT_ASSERT_NOT_NULL(test, dev); + +	for (int i = 0; iwl_hw_card_ids[i].vendor; i++) { +		const struct pci_device_id *s, *t; + +		s = &iwl_hw_card_ids[i]; +		dev->vendor = s->vendor; +		dev->device = s->device; +		dev->subsystem_vendor = s->subvendor; +		dev->subsystem_device = s->subdevice; +		dev->class = s->class; + +		t = pci_match_id(iwl_hw_card_ids, dev); +		KUNIT_EXPECT_PTR_EQ(test, t, s); +	} +} +  static struct kunit_case devinfo_test_cases[] = {  	KUNIT_CASE(devinfo_table_order), +	KUNIT_CASE(devinfo_pci_ids),  	{}  }; | 
