// SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved.
*/ #include <linux/rtnetlink.h>
#include"core.h" #include"debug.h"
/* World regdom to be used in case default regd from fw is unavailable */ #define ATH11K_2GHZ_CH01_11 REG_RULE(2412 - 10, 2462 + 10, 40, 0, 20, 0) #define ATH11K_5GHZ_5150_5350 REG_RULE(5150 - 10, 5350 + 10, 80, 0, 30,\
NL80211_RRF_NO_IR) #define ATH11K_5GHZ_5725_5850 REG_RULE(5725 - 10, 5850 + 10, 80, 0, 30,\
NL80211_RRF_NO_IR)
regd = rcu_dereference_rtnl(ar->hw->wiphy->regd); /* This can happen during wiphy registration where the previous * user request is received before we update the regd received * from firmware.
*/ if (!regd) returntrue;
ath11k_dbg(ar->ab, ATH11K_DBG_REG, "Regulatory Notification received for %s\n", wiphy_name(wiphy));
if (request->initiator == NL80211_REGDOM_SET_BY_DRIVER) {
ath11k_dbg(ar->ab, ATH11K_DBG_REG, "driver initiated regd update\n"); if (ar->state != ATH11K_STATE_ON) return;
ret = ath11k_reg_update_chan_list(ar, true); if (ret)
ath11k_warn(ar->ab, "failed to update channel list: %d\n", ret);
return;
}
/* Currently supporting only General User Hints. Cell base user * hints to be handled later. * Hints from other sources like Core, Beacons are not expected for * self managed wiphy's
*/ if (!(request->initiator == NL80211_REGDOM_SET_BY_USER &&
request->user_reg_hint_type == NL80211_USER_REG_HINT_USER)) {
ath11k_warn(ar->ab, "Unexpected Regulatory event for this wiphy\n"); return;
}
if (!IS_ENABLED(CONFIG_ATH_REG_DYNAMIC_USER_REG_HINTS)) {
ath11k_dbg(ar->ab, ATH11K_DBG_REG, "Country Setting is not allowed\n"); return;
}
if (!ath11k_regdom_changes(ar, request->alpha2)) {
ath11k_dbg(ar->ab, ATH11K_DBG_REG, "Country is already set\n"); return;
}
/* Set the country code to the firmware and will receive * the WMI_REG_CHAN_LIST_CC EVENT for updating the * reg info
*/ if (ar->ab->hw_params.current_cc_support) {
memcpy(&ar->alpha2, request->alpha2, 2);
ret = ath11k_reg_set_cc(ar); if (ret)
ath11k_warn(ar->ab, "failed set current country code: %d\n", ret);
} else {
init_country_param.flags = ALPHA_IS_SET;
memcpy(&init_country_param.cc_info.alpha2, request->alpha2, 2);
init_country_param.cc_info.alpha2[2] = 0;
ret = ath11k_wmi_send_init_country_cmd(ar, init_country_param); if (ret)
ath11k_warn(ar->ab, "INIT Country code set to fw failed : %d\n", ret);
}
/* The caller should have checked error conditions */
memcpy(regd_copy, regd_orig, sizeof(*regd_orig));
for (i = 0; i < regd_orig->n_reg_rules; i++)
memcpy(®d_copy->reg_rules[i], ®d_orig->reg_rules[i], sizeof(struct ieee80211_reg_rule));
}
int ath11k_regd_update(struct ath11k *ar)
{ struct ieee80211_regdomain *regd, *regd_copy = NULL; int ret, regd_len, pdev_id; struct ath11k_base *ab;
ab = ar->ab;
pdev_id = ar->pdev_idx;
spin_lock_bh(&ab->base_lock);
/* Prefer the latest regd update over default if it's available */ if (ab->new_regd[pdev_id]) {
regd = ab->new_regd[pdev_id];
} else { /* Apply the regd received during init through * WMI_REG_CHAN_LIST_CC event. In case of failure to * receive the regd, initialize with a default world * regulatory.
*/ if (ab->default_regd[pdev_id]) {
regd = ab->default_regd[pdev_id];
} else {
ath11k_warn(ab, "failed to receive default regd during init\n");
regd = (struct ieee80211_regdomain *)&ath11k_world_regd;
}
}
if (!regd) {
ret = -EINVAL;
spin_unlock_bh(&ab->base_lock); goto err;
}
/* TODO: Should we restrict intersection feasibility * based on min bandwidth of the intersected region also, * say the intersected rule should have a min bandwidth * of 20MHz?
*/
/* Find the number of intersecting rules to allocate new regd memory */ for (i = 0; i < num_old_regd_rules; i++) {
old_rule = default_regd->reg_rules + i; for (j = 0; j < num_curr_regd_rules; j++) {
curr_rule = curr_regd->reg_rules + j;
if (ath11k_reg_can_intersect(old_rule, curr_rule))
num_new_regd_rules++;
}
}
/* We set the new country and dfs region directly and only trim * the freq, power, antenna gain by intersecting with the * default regdomain. Also MAX of the dfs cac timeout is selected.
*/
new_regd->n_reg_rules = num_new_regd_rules;
memcpy(new_regd->alpha2, curr_regd->alpha2, sizeof(new_regd->alpha2));
new_regd->dfs_region = curr_regd->dfs_region;
new_rule = new_regd->reg_rules;
for (i = 0, k = 0; i < num_old_regd_rules; i++) {
old_rule = default_regd->reg_rules + i; for (j = 0; j < num_curr_regd_rules; j++) {
curr_rule = curr_regd->reg_rules + j;
ath11k_dbg(ab, ATH11K_DBG_REG, "Country %s, CFG Regdomain %s FW Regdomain %d, num_reg_rules %d\n",
alpha2, ath11k_reg_get_regdom_str(tmp_regd->dfs_region),
reg_info->dfs_region, num_rules); /* Update reg_rules[] below. Firmware is expected to * send these rules in order(2 GHz rules first and then 5 GHz)
*/ for (; i < num_rules; i++) { if (reg_info->num_2ghz_reg_rules &&
(i < reg_info->num_2ghz_reg_rules)) {
reg_rule = reg_info->reg_rules_2ghz_ptr + i;
max_bw = min_t(u16, reg_rule->max_bw,
reg_info->max_bw_2ghz);
flags = 0;
} elseif (reg_info->num_5ghz_reg_rules &&
(j < reg_info->num_5ghz_reg_rules)) {
reg_rule = reg_info->reg_rules_5ghz_ptr + j++;
max_bw = min_t(u16, reg_rule->max_bw,
reg_info->max_bw_5ghz);
/* FW doesn't pass NL80211_RRF_AUTO_BW flag for * BW Auto correction, we can enable this by default * for all 5G rules here. The regulatory core performs * BW correction if required and applies flags as * per other BW rule flags we pass from here
*/
flags = NL80211_RRF_AUTO_BW;
} elseif (reg_info->is_ext_reg_event && reg_6ghz_number &&
k < reg_6ghz_number) {
reg_rule = reg_rule_6ghz + k++;
max_bw = min_t(u16, reg_rule->max_bw, max_bw_6ghz);
flags = NL80211_RRF_AUTO_BW; if (reg_rule->psd_flag)
flags |= NL80211_RRF_PSD;
} else { break;
}
ath11k_reg_update_rule(tmp_regd->reg_rules + i,
reg_rule->start_freq,
reg_rule->end_freq, max_bw,
reg_rule->ant_gain, reg_rule->reg_power,
reg_rule->psd_eirp, flags);
/* Update dfs cac timeout if the dfs domain is ETSI and the * new rule covers weather radar band. * Default value of '0' corresponds to 60s timeout, so no * need to update that for other rules.
*/ if (flags & NL80211_RRF_DFS &&
reg_info->dfs_region == ATH11K_DFS_REG_ETSI &&
(reg_rule->end_freq > ETSI_WEATHER_RADAR_BAND_LOW &&
reg_rule->start_freq < ETSI_WEATHER_RADAR_BAND_HIGH)){
ath11k_reg_update_weather_radar_band(ab, tmp_regd,
reg_rule, &i,
flags, max_bw); continue;
}
if (intersect) {
default_regd = ab->default_regd[reg_info->phy_id];
/* Get a new regd by intersecting the received regd with * our default regd.
*/
new_regd = ath11k_regd_intersect(default_regd, tmp_regd);
kfree(tmp_regd); if (!new_regd) {
ath11k_warn(ab, "Unable to create intersected regdomain\n"); goto ret;
}
} else {
new_regd = tmp_regd;
}
/* Currently each struct ath11k maps to one struct ieee80211_hw/wiphy * and one struct ieee80211_regdomain, so it could only store one group * reg rules. It means multi-interface concurrency in the same ath11k is * not support for the regdomain. So get the vdev type of the first entry * now. After concurrency support for the regdomain, this should change.
*/
arvif = list_first_entry_or_null(&ar->arvifs, struct ath11k_vif, list); if (arvif) return arvif->vdev_type;
if (reg_info->status_code != REG_SET_CC_STATUS_PASS) { /* In case of failure to set the requested ctry, * fw retains the current regd. We print a failure info * and return from here.
*/
ath11k_warn(ab, "Failed to set the requested Country regulatory setting\n"); return -EINVAL;
}
pdev_idx = reg_info->phy_id;
/* Avoid default reg rule updates sent during FW recovery if * it is already available
*/
spin_lock_bh(&ab->base_lock); if (test_bit(ATH11K_FLAG_RECOVERY, &ab->dev_flags) &&
ab->default_regd[pdev_idx]) {
spin_unlock_bh(&ab->base_lock); goto retfail;
}
spin_unlock_bh(&ab->base_lock);
if (pdev_idx >= ab->num_radios) { /* Process the event for phy0 only if single_pdev_only * is true. If pdev_idx is valid but not 0, discard the * event. Otherwise, it goes to fallback. In either case * ath11k_reg_reset_info() needs to be called to avoid * memory leak issue.
*/
ath11k_reg_reset_info(reg_info);
/* Avoid multiple overwrites to default regd, during core * stop-start after mac registration.
*/ if (ab->default_regd[pdev_idx] && !ab->new_regd[pdev_idx] &&
!memcmp((char *)ab->default_regd[pdev_idx]->alpha2,
(char *)reg_info->alpha2, 2)) goto retfail;
/* Intersect new rules with default regd if a new country setting was * requested, i.e a default regd was already set during initialization * and the regd coming from this event has a valid country info.
*/ if (ab->default_regd[pdev_idx] &&
!ath11k_reg_is_world_alpha((char *)
ab->default_regd[pdev_idx]->alpha2) &&
!ath11k_reg_is_world_alpha((char *)reg_info->alpha2))
intersect = true;
ar = ab->pdevs[pdev_idx].ar;
vdev_type = ath11k_reg_get_ar_vdev_type(ar);
ath11k_dbg(ab, ATH11K_DBG_WMI, "wmi handle chan list power type %d vdev type %d intersect %d\n",
power_type, vdev_type, intersect);
regd = ath11k_reg_build_regd(ab, reg_info, intersect, vdev_type, power_type); if (!regd) {
ath11k_warn(ab, "failed to build regd from reg_info\n"); goto fallback;
}
if (power_type == IEEE80211_REG_UNSET_AP) {
ath11k_reg_reset_info(&ab->reg_info_store[pdev_idx]);
ab->reg_info_store[pdev_idx] = *reg_info;
}
spin_lock_bh(&ab->base_lock); if (ab->default_regd[pdev_idx]) { /* The initial rules from FW after WMI Init is to build * the default regd. From then on, any rules updated for * the pdev could be due to user reg changes. * Free previously built regd before assigning the newly * generated regd to ar. NULL pointer handling will be * taken care by kfree itself.
*/
ar = ab->pdevs[pdev_idx].ar;
kfree(ab->new_regd[pdev_idx]);
ab->new_regd[pdev_idx] = regd;
queue_work(ab->workqueue, &ar->regd_update_work);
} else { /* This regd would be applied during mac registration and is * held constant throughout for regd intersection purpose
*/
ab->default_regd[pdev_idx] = regd;
}
ab->dfs_region = reg_info->dfs_region;
spin_unlock_bh(&ab->base_lock);
return 0;
fallback: /* Fallback to older reg (by sending previous country setting * again if fw has succeeded and we failed to process here. * The Regdomain should be uniform across driver and fw. Since the * FW has processed the command and sent a success status, we expect * this function to succeed as well. If it doesn't, CTRY needs to be * reverted at the fw and the old SCAN_CHAN_LIST cmd needs to be sent.
*/ /* TODO: This is rare, but still should also be handled */
WARN_ON(1);
ret = ath11k_regd_update(ar); if (ret) { /* Firmware has already moved to the new regd. We need * to maintain channel consistency across FW, Host driver * and userspace. Hence as a fallback mechanism we can set * the prev or default country code to the firmware.
*/ /* TODO: Implement Fallback Mechanism */
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.