// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* * Copyright (C) 2024-2025 Intel Corporation */ #include #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); /* 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 = { /* Those fields sit on the same place for v9 and v10 */ .common.set_mode = cpu_to_le32(IWL_TX_POWER_MODE_SET_LINK), .common.pwr_restriction = cpu_to_le16(u_tx_power), }; u8 cmd_ver = iwl_fw_lookup_cmd_ver(mld->fw, cmd_id, IWL_FW_CMD_VER_UNKNOWN); int len = sizeof(cmd.common); if (WARN_ON(!mld_link)) return -ENODEV; cmd.common.link_id = cpu_to_le32(mld_link->fw_id); if (cmd_ver == 10) len += sizeof(cmd.v10); else if (cmd_ver == 9) len += sizeof(cmd.v9); return iwl_mld_send_cmd_pdu(mld, cmd_id, &cmd, len); }