diff options
Diffstat (limited to 'sys/contrib/dev/iwlwifi/mld/power.c')
| -rw-r--r-- | sys/contrib/dev/iwlwifi/mld/power.c | 391 | 
1 files changed, 391 insertions, 0 deletions
| diff --git a/sys/contrib/dev/iwlwifi/mld/power.c b/sys/contrib/dev/iwlwifi/mld/power.c new file mode 100644 index 000000000000..f664b277adf7 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mld/power.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2024-2025 Intel Corporation + */ +#include <net/mac80211.h> + +#include "mld.h" +#include "hcmd.h" +#include "power.h" +#include "iface.h" +#include "link.h" +#include "constants.h" + +static void iwl_mld_vif_ps_iterator(void *data, u8 *mac, +				    struct ieee80211_vif *vif) +{ +	bool *ps_enable = (bool *)data; +	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); + +	if (vif->type != NL80211_IFTYPE_STATION) +		return; + +	*ps_enable &= !mld_vif->ps_disabled; +} + +int iwl_mld_update_device_power(struct iwl_mld *mld, bool d3) +{ +	struct iwl_device_power_cmd cmd = {}; +	bool enable_ps = false; + +	if (iwlmld_mod_params.power_scheme != IWL_POWER_SCHEME_CAM) { +		enable_ps = true; + +		/* Disable power save if any STA interface has +		 * power save turned off +		 */ +		ieee80211_iterate_active_interfaces_mtx(mld->hw, +							IEEE80211_IFACE_ITER_NORMAL, +							iwl_mld_vif_ps_iterator, +							&enable_ps); +	} + +	if (enable_ps) +		cmd.flags |= +			cpu_to_le16(DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK); + +	if (d3) +		cmd.flags |= +			cpu_to_le16(DEVICE_POWER_FLAGS_NO_SLEEP_TILL_D3_MSK); + +	IWL_DEBUG_POWER(mld, +			"Sending device power command with flags = 0x%X\n", +			cmd.flags); + +	return iwl_mld_send_cmd_pdu(mld, POWER_TABLE_CMD, &cmd); +} + +int iwl_mld_enable_beacon_filter(struct iwl_mld *mld, +				 const struct ieee80211_bss_conf *link_conf, +				 bool d3) +{ +	struct iwl_beacon_filter_cmd cmd = { +		IWL_BF_CMD_CONFIG_DEFAULTS, +		.bf_enable_beacon_filter = cpu_to_le32(1), +		.ba_enable_beacon_abort = cpu_to_le32(1), +	}; + +	if (ieee80211_vif_type_p2p(link_conf->vif) != NL80211_IFTYPE_STATION) +		return 0; + +#ifdef CONFIG_IWLWIFI_DEBUGFS +	if (iwl_mld_vif_from_mac80211(link_conf->vif)->disable_bf) +		return 0; +#endif + +	if (link_conf->cqm_rssi_thold) { +		cmd.bf_energy_delta = +			cpu_to_le32(link_conf->cqm_rssi_hyst); +		/* fw uses an absolute value for this */ +		cmd.bf_roaming_state = +			cpu_to_le32(-link_conf->cqm_rssi_thold); +	} + +	if (d3) +		cmd.ba_escape_timer = cpu_to_le32(IWL_BA_ESCAPE_TIMER_D3); + +	return iwl_mld_send_cmd_pdu(mld, REPLY_BEACON_FILTERING_CMD, +				    &cmd); +} + +int iwl_mld_disable_beacon_filter(struct iwl_mld *mld, +				  struct ieee80211_vif *vif) +{ +	struct iwl_beacon_filter_cmd cmd = {}; + +	if (ieee80211_vif_type_p2p(vif) != NL80211_IFTYPE_STATION) +		return 0; + +	return iwl_mld_send_cmd_pdu(mld, REPLY_BEACON_FILTERING_CMD, +				    &cmd); +} + +static bool iwl_mld_power_is_radar(struct iwl_mld *mld, +				   const struct ieee80211_bss_conf *link_conf) +{ +	const struct ieee80211_chanctx_conf *chanctx_conf; + +	chanctx_conf = wiphy_dereference(mld->wiphy, link_conf->chanctx_conf); + +	if (WARN_ON(!chanctx_conf)) +		return false; + +	return chanctx_conf->def.chan->flags & IEEE80211_CHAN_RADAR; +} + +static void iwl_mld_power_configure_uapsd(struct iwl_mld *mld, +					  struct iwl_mld_link *link, +					  struct iwl_mac_power_cmd *cmd, +					  bool ps_poll) +{ +	bool tid_found = false; + +	cmd->rx_data_timeout_uapsd = +		cpu_to_le32(IWL_MLD_UAPSD_RX_DATA_TIMEOUT); +	cmd->tx_data_timeout_uapsd = +		cpu_to_le32(IWL_MLD_UAPSD_TX_DATA_TIMEOUT); + +	 /* set advanced pm flag with no uapsd ACs to enable ps-poll */ +	if (ps_poll) { +		cmd->flags |= cpu_to_le16(POWER_FLAGS_ADVANCE_PM_ENA_MSK); +		return; +	} + +	for (enum ieee80211_ac_numbers ac = IEEE80211_AC_VO; +	     ac <= IEEE80211_AC_BK; +	     ac++) { +		if (!link->queue_params[ac].uapsd) +			continue; + +		cmd->flags |= +			cpu_to_le16(POWER_FLAGS_ADVANCE_PM_ENA_MSK | +				    POWER_FLAGS_UAPSD_MISBEHAVING_ENA_MSK); + +		cmd->uapsd_ac_flags |= BIT(ac); + +		/* QNDP TID - the highest TID with no admission control */ +		if (!tid_found && !link->queue_params[ac].acm) { +			tid_found = true; +			switch (ac) { +			case IEEE80211_AC_VO: +				cmd->qndp_tid = 6; +				break; +			case IEEE80211_AC_VI: +				cmd->qndp_tid = 5; +				break; +			case IEEE80211_AC_BE: +				cmd->qndp_tid = 0; +				break; +			case IEEE80211_AC_BK: +				cmd->qndp_tid = 1; +				break; +			} +		} +	} + +	if (cmd->uapsd_ac_flags == (BIT(IEEE80211_AC_VO) | +				    BIT(IEEE80211_AC_VI) | +				    BIT(IEEE80211_AC_BE) | +				    BIT(IEEE80211_AC_BK))) { +		cmd->flags |= cpu_to_le16(POWER_FLAGS_SNOOZE_ENA_MSK); +		cmd->snooze_interval = cpu_to_le16(IWL_MLD_PS_SNOOZE_INTERVAL); +		cmd->snooze_window = cpu_to_le16(IWL_MLD_PS_SNOOZE_WINDOW); +	} + +	cmd->uapsd_max_sp = mld->hw->uapsd_max_sp_len; +} + +static void +iwl_mld_power_config_skip_dtim(struct iwl_mld *mld, +			       const struct ieee80211_bss_conf *link_conf, +			       struct iwl_mac_power_cmd *cmd) +{ +	unsigned int dtimper_tu; +	unsigned int dtimper; +	unsigned int skip; + +	dtimper = link_conf->dtim_period ?: 1; +	dtimper_tu = dtimper * link_conf->beacon_int; + +	if (dtimper >= 10 || iwl_mld_power_is_radar(mld, link_conf)) +		return; + +	if (WARN_ON(!dtimper_tu)) +		return; + +	/* configure skip over dtim up to 900 TU DTIM interval */ +	skip = max_t(int, 1, 900 / dtimper_tu); + +	cmd->skip_dtim_periods = skip; +	cmd->flags |= cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK); +} + +#define POWER_KEEP_ALIVE_PERIOD_SEC    25 +static void iwl_mld_power_build_cmd(struct iwl_mld *mld, +				    struct ieee80211_vif *vif, +				    struct iwl_mac_power_cmd *cmd, +				    bool d3) +{ +	int dtimper, bi; +	int keep_alive; +	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); +	struct ieee80211_bss_conf *link_conf = &vif->bss_conf; +	struct iwl_mld_link *link = &mld_vif->deflink; +	bool ps_poll = false; + +	cmd->id_and_color = cpu_to_le32(mld_vif->fw_id); + +	if (ieee80211_vif_is_mld(vif)) { +		int link_id; + +		if (WARN_ON(!vif->active_links)) +			return; + +		/* The firmware consumes one single configuration for the vif +		 * and can't differentiate between links, just pick the lowest +		 * link_id's configuration and use that. +		 */ +		link_id = __ffs(vif->active_links); +		link_conf = link_conf_dereference_check(vif, link_id); +		link = iwl_mld_link_dereference_check(mld_vif, link_id); + +		if (WARN_ON(!link_conf || !link)) +			return; +	} +	dtimper = link_conf->dtim_period; +	bi = link_conf->beacon_int; + +	/* Regardless of power management state the driver must set +	 * keep alive period. FW will use it for sending keep alive NDPs +	 * immediately after association. Check that keep alive period +	 * is at least 3 * DTIM +	 */ +	keep_alive = DIV_ROUND_UP(ieee80211_tu_to_usec(3 * dtimper * bi), +				  USEC_PER_SEC); +	keep_alive = max(keep_alive, POWER_KEEP_ALIVE_PERIOD_SEC); +	cmd->keep_alive_seconds = cpu_to_le16(keep_alive); + +	if (iwlmld_mod_params.power_scheme != IWL_POWER_SCHEME_CAM) +		cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_SAVE_ENA_MSK); + +	if (!vif->cfg.ps || iwl_mld_tdls_sta_count(mld) > 0) +		return; + +	cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK); + +	if (iwl_fw_lookup_cmd_ver(mld->fw, MAC_PM_POWER_TABLE, 0) >= 2) +		cmd->flags |= cpu_to_le16(POWER_FLAGS_ENABLE_SMPS_MSK); + +	/* firmware supports LPRX for beacons at rate 1 Mbps or 6 Mbps only */ +	if (link_conf->beacon_rate && +	    (link_conf->beacon_rate->bitrate == 10 || +	     link_conf->beacon_rate->bitrate == 60)) { +		cmd->flags |= cpu_to_le16(POWER_FLAGS_LPRX_ENA_MSK); +		cmd->lprx_rssi_threshold = POWER_LPRX_RSSI_THRESHOLD; +	} + +	if (d3) { +		iwl_mld_power_config_skip_dtim(mld, link_conf, cmd); +		cmd->rx_data_timeout = +			cpu_to_le32(IWL_MLD_WOWLAN_PS_RX_DATA_TIMEOUT); +		cmd->tx_data_timeout = +			cpu_to_le32(IWL_MLD_WOWLAN_PS_TX_DATA_TIMEOUT); +	} else if (iwl_mld_vif_low_latency(mld_vif) && vif->p2p) { +		cmd->tx_data_timeout = +			cpu_to_le32(IWL_MLD_SHORT_PS_TX_DATA_TIMEOUT); +		cmd->rx_data_timeout = +			cpu_to_le32(IWL_MLD_SHORT_PS_RX_DATA_TIMEOUT); +	} else { +		cmd->rx_data_timeout = +			cpu_to_le32(IWL_MLD_DEFAULT_PS_RX_DATA_TIMEOUT); +		cmd->tx_data_timeout = +			cpu_to_le32(IWL_MLD_DEFAULT_PS_TX_DATA_TIMEOUT); +	} + +	/* uAPSD is only enabled for specific certifications. For those cases, +	 * mac80211 will allow uAPSD. Always call iwl_mld_power_configure_uapsd +	 * which will look at what mac80211 is saying. +	 */ +#ifdef CONFIG_IWLWIFI_DEBUGFS +	ps_poll = mld_vif->use_ps_poll; +#endif +	iwl_mld_power_configure_uapsd(mld, link, cmd, ps_poll); +} + +int iwl_mld_update_mac_power(struct iwl_mld *mld, struct ieee80211_vif *vif, +			     bool d3) +{ +	struct iwl_mac_power_cmd cmd = {}; + +	iwl_mld_power_build_cmd(mld, vif, &cmd, d3); + +	return iwl_mld_send_cmd_pdu(mld, MAC_PM_POWER_TABLE, &cmd); +} + +static void +iwl_mld_tpe_sta_cmd_data(struct iwl_txpower_constraints_cmd *cmd, +			 const struct ieee80211_bss_conf *link) +{ +	u8 i; + +	/* NOTE: the 0 here is IEEE80211_TPE_CAT_6GHZ_DEFAULT, +	 * we fully ignore IEEE80211_TPE_CAT_6GHZ_SUBORDINATE +	 */ + +	BUILD_BUG_ON(ARRAY_SIZE(cmd->psd_pwr) != +		     ARRAY_SIZE(link->tpe.psd_local[0].power)); + +	/* if not valid, mac80211 puts default (max value) */ +	for (i = 0; i < ARRAY_SIZE(cmd->psd_pwr); i++) +		cmd->psd_pwr[i] = min(link->tpe.psd_local[0].power[i], +				      link->tpe.psd_reg_client[0].power[i]); + +	BUILD_BUG_ON(ARRAY_SIZE(cmd->eirp_pwr) != +		     ARRAY_SIZE(link->tpe.max_local[0].power)); + +	for (i = 0; i < ARRAY_SIZE(cmd->eirp_pwr); i++) +		cmd->eirp_pwr[i] = min(link->tpe.max_local[0].power[i], +				       link->tpe.max_reg_client[0].power[i]); +} + +void +iwl_mld_send_ap_tx_power_constraint_cmd(struct iwl_mld *mld, +					struct ieee80211_vif *vif, +					struct ieee80211_bss_conf *link) +{ +	struct iwl_txpower_constraints_cmd cmd = {}; +	struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link); +	int ret; + +	lockdep_assert_wiphy(mld->wiphy); + +	if (!mld_link->active) +		return; + +	if (link->chanreq.oper.chan->band != NL80211_BAND_6GHZ) +		return; + +	cmd.link_id = cpu_to_le16(mld_link->fw_id); +	memset(cmd.psd_pwr, DEFAULT_TPE_TX_POWER, sizeof(cmd.psd_pwr)); +	memset(cmd.eirp_pwr, DEFAULT_TPE_TX_POWER, sizeof(cmd.eirp_pwr)); + +	if (vif->type == NL80211_IFTYPE_AP) { +		cmd.ap_type = cpu_to_le16(IWL_6GHZ_AP_TYPE_VLP); +	} else if (link->power_type == IEEE80211_REG_UNSET_AP) { +		return; +	} else { +		cmd.ap_type = cpu_to_le16(link->power_type - 1); +		iwl_mld_tpe_sta_cmd_data(&cmd, link); +	} + +	ret = iwl_mld_send_cmd_pdu(mld, +				   WIDE_ID(PHY_OPS_GROUP, +					   AP_TX_POWER_CONSTRAINTS_CMD), +				   &cmd); +	if (ret) +		IWL_ERR(mld, +			"failed to send AP_TX_POWER_CONSTRAINTS_CMD (%d)\n", +			ret); +} + +int iwl_mld_set_tx_power(struct iwl_mld *mld, +			 struct ieee80211_bss_conf *link_conf, +			 s16 tx_power) +{ +	u32 cmd_id = REDUCE_TX_POWER_CMD; +	struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link_conf); +	u16 u_tx_power = tx_power == IWL_DEFAULT_MAX_TX_POWER ? +		IWL_DEV_MAX_TX_POWER : 8 * tx_power; +	struct iwl_dev_tx_power_cmd cmd = { +		.common.set_mode = cpu_to_le32(IWL_TX_POWER_MODE_SET_LINK), +		.common.pwr_restriction = cpu_to_le16(u_tx_power), +	}; +	int len = sizeof(cmd.common) + sizeof(cmd.v10); + +	if (WARN_ON(!mld_link)) +		return -ENODEV; + +	cmd.common.link_id = cpu_to_le32(mld_link->fw_id); + +	return iwl_mld_send_cmd_pdu(mld, cmd_id, &cmd, len); +} | 
