]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: ath12k: handle regulatory hints during mac registration
authorAditya Kumar Singh <aditya.kumar.singh@oss.qualcomm.com>
Tue, 17 Jun 2025 03:35:59 +0000 (09:05 +0530)
committerJeff Johnson <jeff.johnson@oss.qualcomm.com>
Mon, 23 Jun 2025 14:28:32 +0000 (07:28 -0700)
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>
drivers/net/wireless/ath/ath12k/core.c
drivers/net/wireless/ath/ath12k/core.h
drivers/net/wireless/ath/ath12k/mac.c
drivers/net/wireless/ath/ath12k/reg.c
drivers/net/wireless/ath/ath12k/reg.h
drivers/net/wireless/ath/ath12k/wmi.c

index cd58ab9c232239eb344bd693f210084a975c98f3..d47cf17ed430da6826d006218d5821b1d011f027 100644 (file)
@@ -1473,6 +1473,7 @@ static void ath12k_core_pre_reconfigure_recovery(struct ath12k_base *ab)
                        complete(&ar->vdev_setup_done);
                        complete(&ar->vdev_delete_done);
                        complete(&ar->bss_survey_done);
+                       complete(&ar->regd_update_completed);
 
                        wake_up(&ar->dp.tx_empty_waitq);
                        idr_for_each(&ar->txmgmt_idr,
@@ -1512,6 +1513,9 @@ static void ath12k_update_11d(struct work_struct *work)
                ar = pdev->ar;
 
                memcpy(&ar->alpha2, &arg.alpha2, 2);
+
+               reinit_completion(&ar->regd_update_completed);
+
                ret = ath12k_wmi_send_set_current_country_cmd(ar, &arg);
                if (ret)
                        ath12k_warn(ar->ab,
index 46fc14dce4c7c77349efa6c59195f8c6f0bdc550..cf10c62d4751af225229903ffa1c3bb495e59ee2 100644 (file)
@@ -811,6 +811,7 @@ struct ath12k {
        enum ath12k_11d_state state_11d;
        u8 alpha2[REG_ALPHA2_LEN];
        bool regdom_set_by_user;
+       struct completion regd_update_completed;
 
        struct completion fw_stats_complete;
        struct completion fw_stats_done;
index b9c8854786b4f9218b1294914976aa61b3734316..3c9c9066d3201c00708b65f8a6d964dc23913b8b 100644 (file)
@@ -11381,6 +11381,7 @@ ath12k_mac_op_reconfig_complete(struct ieee80211_hw *hw,
                        struct wmi_set_current_country_arg arg = {};
 
                        memcpy(&arg.alpha2, ar->alpha2, 2);
+                       reinit_completion(&ar->regd_update_completed);
                        ath12k_wmi_send_set_current_country_cmd(ar, &arg);
                }
 
@@ -12680,6 +12681,16 @@ static int ath12k_mac_hw_register(struct ath12k_hw *ah)
                goto err_cleanup_if_combs;
        }
 
+       /* Boot-time regulatory updates have already been processed.
+        * Mark them as complete now, because after registration,
+        * cfg80211 will notify us again if there are any pending hints.
+        * We need to wait for those hints to be processed, so it's
+        * important to mark the boot-time updates as complete before
+        * proceeding with registration.
+        */
+       for_each_ar(ah, ar, i)
+               complete(&ar->regd_update_completed);
+
        ret = ieee80211_register_hw(hw);
        if (ret) {
                ath12k_err(ab, "ieee80211 registration failed: %d\n", ret);
@@ -12707,6 +12718,9 @@ static int ath12k_mac_hw_register(struct ath12k_hw *ah)
 
                        memcpy(&current_cc.alpha2, ab->new_alpha2, 2);
                        memcpy(&ar->alpha2, ab->new_alpha2, 2);
+
+                       reinit_completion(&ar->regd_update_completed);
+
                        ret = ath12k_wmi_send_set_current_country_cmd(ar, &current_cc);
                        if (ret)
                                ath12k_warn(ar->ab,
@@ -12779,6 +12793,7 @@ static void ath12k_mac_setup(struct ath12k *ar)
        init_completion(&ar->scan.on_channel);
        init_completion(&ar->mlo_setup_done);
        init_completion(&ar->completed_11d_scan);
+       init_completion(&ar->regd_update_completed);
 
        INIT_DELAYED_WORK(&ar->scan.timeout, ath12k_scan_timeout_work);
        wiphy_work_init(&ar->scan.vdev_clean_wk, ath12k_scan_vdev_clean_work);
index 7c997c9dad4d8bcb5294b3a7a79bde34fc5724fc..96254d6fc6757c2901294aa5a33cc4d8793fcdf7 100644 (file)
@@ -102,6 +102,8 @@ ath12k_reg_notifier(struct wiphy *wiphy, struct regulatory_request *request)
 
        /* Send the reg change request to all the radios */
        for_each_ar(ah, ar, i) {
+               reinit_completion(&ar->regd_update_completed);
+
                if (ar->ab->hw_params->current_cc_support) {
                        memcpy(&current_arg.alpha2, request->alpha2, 2);
                        memcpy(&ar->alpha2, &current_arg.alpha2, 2);
@@ -273,9 +275,19 @@ int ath12k_regd_update(struct ath12k *ar, bool init)
        struct ieee80211_regdomain *regd, *regd_copy = NULL;
        int ret, regd_len, pdev_id;
        struct ath12k_base *ab;
+       long time_left;
 
        ab = ar->ab;
 
+       time_left = wait_for_completion_timeout(&ar->regd_update_completed,
+                                               ATH12K_REG_UPDATE_TIMEOUT_HZ);
+       if (time_left == 0) {
+               ath12k_warn(ab, "Timeout while waiting for regulatory update");
+               /* Even though timeout has occurred, still continue since at least boot
+                * time data would be there to process
+                */
+       }
+
        supported_bands = ar->pdev->cap.supported_bands;
        reg_cap = &ab->hal_reg_cap[ar->pdev_idx];
 
index 0aeba06182c50ce4e19202f6f0e60b9c6938c6f0..da5128b8c97f5893c6b2fe8225ef7edc640e7a0d 100644 (file)
@@ -13,6 +13,8 @@
 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
 
index 1b82fe0e50993c4710045772fd51061adf08435f..b38f22118d732aa182f9fd6dda376b0d41de65e2 100644 (file)
@@ -6684,7 +6684,8 @@ static void ath12k_wmi_htc_tx_complete(struct ath12k_base *ab,
 static int ath12k_reg_chan_list_event(struct ath12k_base *ab, struct sk_buff *skb)
 {
        struct ath12k_reg_info *reg_info;
-       u8 pdev_idx;
+       struct ath12k *ar = NULL;
+       u8 pdev_idx = 255;
        int ret;
 
        reg_info = kzalloc(sizeof(*reg_info), GFP_ATOMIC);
@@ -6739,7 +6740,7 @@ mem_free:
        kfree(reg_info);
 
        if (ret == ATH12K_REG_STATUS_VALID)
-               return ret;
+               goto out;
 
 fallback:
        /* Fallback to older reg (by sending previous country setting
@@ -6753,6 +6754,18 @@ fallback:
        WARN_ON(1);
 
 out:
+       /* In some error cases, even a valid pdev_idx might not be available */
+       if (pdev_idx != 255)
+               ar = ab->pdevs[pdev_idx].ar;
+
+       /* During the boot-time update, 'ar' might not be allocated,
+        * so the completion cannot be marked at that point.
+        * This boot-time update is handled in ath12k_mac_hw_register()
+        * before registering the hardware.
+        */
+       if (ar)
+               complete(&ar->regd_update_completed);
+
        return ret;
 }