mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-09-04 18:49:41 +00:00

If a regulatory notification is there in the system while the hardware is being registered, it attempts to set the new regulatory country. However, ath12k currently boots with a default country derived from the BDF. If this default country differs from the one provided in the notification, a race condition can occur while updating the regulatory information back to userspace. This potentially leads to driver having the incorrect regulatory applied. For example, suppose the regulatory domain for France (FR) is already applied, and then the driver is loaded with a BDF that has the United States (US) country programmed. When the driver finishes loading, the regulatory domain shown in phyX still reflects the US regulatory settings. This is incorrect, as the driver had already received a notification for FR during hardware registration, but failed to process it properly due to the race condition. The race condition exists during driver initialization and hardware registration: - On driver load, the firmware sends BDF-based country regulatory rules, which are stored in default_regd via ath12k_reg_handle_chan_list(). - During hardware registration, a regulatory notification is triggered through: ath12k_mac_hw_register() -> ieee80211_register_hw() -> wiphy_register() -> wiphy_regulatory_register() -> reg_call_notifier() This sends a country code to the firmware, which responds with updated regulatory rules. - After registration, ath12k_mac_hw_register() calls ath12k_regd_update(), which copies default_regd and passes it to the upper layers. The race occurs between the firmware's response and the execution of ath12k_regd_update(). If the firmware's new rules are processed before the update call, the correct values are used. Otherwise, outdated boot-time country settings are exposed to userspace. To resolve this issue, introduce a completion mechanism within the hardware group (ah). Trigger this completion whenever a regulatory change is requested from the firmware. Then, in ath12k_regd_update(), wait for the firmware to complete its regulatory processing before proceeding with the update. This ensures that during driver load, the default country is processed first. However, before ath12k_regd_update() is called, the new regulatory notification will have already been received by the driver. As a result, it will wait for the firmware's regulatory processing to complete, and only the final, correct regulatory domain will be updated to userspace. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1 Signed-off-by: Aditya Kumar Singh <aditya.kumar.singh@oss.qualcomm.com> Reviewed-by: Vasanthakumar Thiagarajan <vasanthakumar.thiagarajan@oss.qualcomm.com> Link: https://patch.msgid.link/20250617-handle_user_regd_update_hints_during_insmod-v2-1-10a6a48efe81@oss.qualcomm.com Signed-off-by: Jeff Johnson <jeff.johnson@oss.qualcomm.com>
124 lines
3.6 KiB
C
124 lines
3.6 KiB
C
/* SPDX-License-Identifier: BSD-3-Clause-Clear */
|
|
/*
|
|
* Copyright (c) 2019-2021 The Linux Foundation. All rights reserved.
|
|
* Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
#ifndef ATH12K_REG_H
|
|
#define ATH12K_REG_H
|
|
|
|
#include <linux/kernel.h>
|
|
#include <net/regulatory.h>
|
|
|
|
struct ath12k_base;
|
|
struct ath12k;
|
|
|
|
#define ATH12K_REG_UPDATE_TIMEOUT_HZ (3 * HZ)
|
|
|
|
#define ATH12K_2GHZ_MAX_FREQUENCY 2495
|
|
#define ATH12K_5GHZ_MAX_FREQUENCY 5920
|
|
|
|
/* DFS regdomains supported by Firmware */
|
|
enum ath12k_dfs_region {
|
|
ATH12K_DFS_REG_UNSET,
|
|
ATH12K_DFS_REG_FCC,
|
|
ATH12K_DFS_REG_ETSI,
|
|
ATH12K_DFS_REG_MKK,
|
|
ATH12K_DFS_REG_CN,
|
|
ATH12K_DFS_REG_KR,
|
|
ATH12K_DFS_REG_MKK_N,
|
|
ATH12K_DFS_REG_UNDEF,
|
|
};
|
|
|
|
enum ath12k_reg_cc_code {
|
|
REG_SET_CC_STATUS_PASS = 0,
|
|
REG_CURRENT_ALPHA2_NOT_FOUND = 1,
|
|
REG_INIT_ALPHA2_NOT_FOUND = 2,
|
|
REG_SET_CC_CHANGE_NOT_ALLOWED = 3,
|
|
REG_SET_CC_STATUS_NO_MEMORY = 4,
|
|
REG_SET_CC_STATUS_FAIL = 5,
|
|
};
|
|
|
|
struct ath12k_reg_rule {
|
|
u16 start_freq;
|
|
u16 end_freq;
|
|
u16 max_bw;
|
|
u8 reg_power;
|
|
u8 ant_gain;
|
|
u16 flags;
|
|
bool psd_flag;
|
|
u16 psd_eirp;
|
|
};
|
|
|
|
struct ath12k_reg_info {
|
|
enum ath12k_reg_cc_code status_code;
|
|
u8 num_phy;
|
|
u8 phy_id;
|
|
u16 reg_dmn_pair;
|
|
u16 ctry_code;
|
|
u8 alpha2[REG_ALPHA2_LEN + 1];
|
|
u32 dfs_region;
|
|
u32 phybitmap;
|
|
bool is_ext_reg_event;
|
|
u32 min_bw_2g;
|
|
u32 max_bw_2g;
|
|
u32 min_bw_5g;
|
|
u32 max_bw_5g;
|
|
u32 num_2g_reg_rules;
|
|
u32 num_5g_reg_rules;
|
|
struct ath12k_reg_rule *reg_rules_2g_ptr;
|
|
struct ath12k_reg_rule *reg_rules_5g_ptr;
|
|
enum wmi_reg_6g_client_type client_type;
|
|
bool rnr_tpe_usable;
|
|
bool unspecified_ap_usable;
|
|
/* TODO: All 6G related info can be stored only for required
|
|
* combination instead of all types, to optimize memory usage.
|
|
*/
|
|
u8 domain_code_6g_ap[WMI_REG_CURRENT_MAX_AP_TYPE];
|
|
u8 domain_code_6g_client[WMI_REG_CURRENT_MAX_AP_TYPE][WMI_REG_MAX_CLIENT_TYPE];
|
|
u32 domain_code_6g_super_id;
|
|
u32 min_bw_6g_ap[WMI_REG_CURRENT_MAX_AP_TYPE];
|
|
u32 max_bw_6g_ap[WMI_REG_CURRENT_MAX_AP_TYPE];
|
|
u32 min_bw_6g_client[WMI_REG_CURRENT_MAX_AP_TYPE][WMI_REG_MAX_CLIENT_TYPE];
|
|
u32 max_bw_6g_client[WMI_REG_CURRENT_MAX_AP_TYPE][WMI_REG_MAX_CLIENT_TYPE];
|
|
u32 num_6g_reg_rules_ap[WMI_REG_CURRENT_MAX_AP_TYPE];
|
|
u32 num_6g_reg_rules_cl[WMI_REG_CURRENT_MAX_AP_TYPE][WMI_REG_MAX_CLIENT_TYPE];
|
|
struct ath12k_reg_rule *reg_rules_6g_ap_ptr[WMI_REG_CURRENT_MAX_AP_TYPE];
|
|
struct ath12k_reg_rule *reg_rules_6g_client_ptr
|
|
[WMI_REG_CURRENT_MAX_AP_TYPE][WMI_REG_MAX_CLIENT_TYPE];
|
|
};
|
|
|
|
/* Phy bitmaps */
|
|
enum ath12k_reg_phy_bitmap {
|
|
ATH12K_REG_PHY_BITMAP_NO11AX = BIT(5),
|
|
ATH12K_REG_PHY_BITMAP_NO11BE = BIT(6),
|
|
};
|
|
|
|
enum ath12k_reg_status {
|
|
ATH12K_REG_STATUS_VALID,
|
|
ATH12K_REG_STATUS_DROP,
|
|
ATH12K_REG_STATUS_FALLBACK,
|
|
};
|
|
|
|
void ath12k_reg_init(struct ieee80211_hw *hw);
|
|
void ath12k_reg_free(struct ath12k_base *ab);
|
|
void ath12k_regd_update_work(struct work_struct *work);
|
|
struct ieee80211_regdomain *ath12k_reg_build_regd(struct ath12k_base *ab,
|
|
struct ath12k_reg_info *reg_info,
|
|
enum wmi_vdev_type vdev_type,
|
|
enum ieee80211_ap_reg_power power_type);
|
|
int ath12k_regd_update(struct ath12k *ar, bool init);
|
|
int ath12k_reg_update_chan_list(struct ath12k *ar, bool wait);
|
|
|
|
void ath12k_reg_reset_reg_info(struct ath12k_reg_info *reg_info);
|
|
int ath12k_reg_handle_chan_list(struct ath12k_base *ab,
|
|
struct ath12k_reg_info *reg_info,
|
|
enum wmi_vdev_type vdev_type,
|
|
enum ieee80211_ap_reg_power power_type);
|
|
void ath12k_regd_update_chan_list_work(struct work_struct *work);
|
|
enum wmi_reg_6g_ap_type
|
|
ath12k_reg_ap_pwr_convert(enum ieee80211_ap_reg_power power_type);
|
|
enum ath12k_reg_status ath12k_reg_validate_reg_info(struct ath12k_base *ab,
|
|
struct ath12k_reg_info *reg_info);
|
|
#endif
|