ath11k: Avoid race during regd updates
authorSriram R <srirrama@codeaurora.org>
Tue, 28 Sep 2021 09:05:40 +0000 (12:05 +0300)
committerKalle Valo <kvalo@codeaurora.org>
Tue, 28 Sep 2021 10:57:32 +0000 (13:57 +0300)
Whenever ath11k is bootup with a user country already set, cfg80211
notifies this country info to ath11k soon after registration, where the
notification is sent to the firmware for fetching the rules of this user
country input.

Multiple race conditions could be seen in this scenario where a new
request is either lost as pointed in [1] or a new regd overwrites the
default regd provided by the firmware during bootup. Note that, the
default regd is used for intersection purpose and hence it should not be
overwritten.

The main reason as pointed by [1] is the usage of ATH11K_FLAG_REGISTERED
flag which is updated after completion of core registration, whereas the
reg notification from cfg80211 and wmi events for the corresponding
request can happen much before that. Since the ATH11K_FLAG_REGISTERED is
currently used to determine if the event containing reg rules belong to
default regd or for user request, there is a possibility of the default
regd getting overwritten.

Since the default reg rules will be received only once per pdev on
firmware load, the above flag based check can be replaced with a check
to see if default_regd is already set, so that we can now always update
the new_regd. Also if the new_regd is set, this will be always used to
update the reg rules for the registered phy.

[1] https://patchwork.kernel.org/project/linux-wireless/patch/1829665.1PRlr7bOQj@ripper/

Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.4.0.1-01460-QCAHKSWPL_SILICONZ-1
Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices")
Signed-off-by: Sriram R <srirrama@codeaurora.org>
Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
Link: https://lore.kernel.org/r/20210721212029.142388-4-jouni@codeaurora.org
drivers/net/wireless/ath/ath11k/mac.c
drivers/net/wireless/ath/ath11k/reg.c
drivers/net/wireless/ath/ath11k/reg.h
drivers/net/wireless/ath/ath11k/wmi.c

index 7cd08b5f6a5a27f9b48831eb4c15dd7186f3f590..833c5bceac6238bb305b14393159f5442be3fd35 100644 (file)
@@ -7660,7 +7660,7 @@ static int __ath11k_mac_register(struct ath11k *ar)
                ar->hw->wiphy->interface_modes &= ~BIT(NL80211_IFTYPE_MONITOR);
 
        /* Apply the regd received during initialization */
-       ret = ath11k_regd_update(ar, true);
+       ret = ath11k_regd_update(ar);
        if (ret) {
                ath11k_err(ar->ab, "ath11k regd update failed: %d\n", ret);
                goto err_unregister_hw;
index c83d265185f1beb12b3e51c2fc20062b84a53f00..a66b5bdd21679ef6a6c46d01fd3b0e39247146f1 100644 (file)
@@ -195,7 +195,7 @@ static void ath11k_copy_regd(struct ieee80211_regdomain *regd_orig,
                       sizeof(struct ieee80211_reg_rule));
 }
 
-int ath11k_regd_update(struct ath11k *ar, bool init)
+int ath11k_regd_update(struct ath11k *ar)
 {
        struct ieee80211_regdomain *regd, *regd_copy = NULL;
        int ret, regd_len, pdev_id;
@@ -206,7 +206,10 @@ int ath11k_regd_update(struct ath11k *ar, bool init)
 
        spin_lock_bh(&ab->base_lock);
 
-       if (init) {
+       /* 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
@@ -219,8 +222,6 @@ int ath11k_regd_update(struct ath11k *ar, bool init)
                                    "failed to receive default regd during init\n");
                        regd = (struct ieee80211_regdomain *)&ath11k_world_regd;
                }
-       } else {
-               regd = ab->new_regd[pdev_id];
        }
 
        if (!regd) {
@@ -680,7 +681,7 @@ void ath11k_regd_update_work(struct work_struct *work)
                                         regd_update_work);
        int ret;
 
-       ret = ath11k_regd_update(ar, false);
+       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
index 65d56d44796f62f07410e567bce6339458961c6e..5fb9dc03a74e82a2a76048ca35ee236b950f895b 100644 (file)
@@ -31,6 +31,6 @@ void ath11k_regd_update_work(struct work_struct *work);
 struct ieee80211_regdomain *
 ath11k_reg_build_regd(struct ath11k_base *ab,
                      struct cur_regulatory_info *reg_info, bool intersect);
-int ath11k_regd_update(struct ath11k *ar, bool init);
+int ath11k_regd_update(struct ath11k *ar);
 int ath11k_reg_update_chan_list(struct ath11k *ar);
 #endif
index c17981c6863fc4dd8d3ae803cefbc4a7b92be7eb..85c2507682d65bef01377ad0295f828cf13f4c35 100644 (file)
@@ -5896,10 +5896,10 @@ static int ath11k_reg_chan_list_event(struct ath11k_base *ab, struct sk_buff *sk
        }
 
        spin_lock(&ab->base_lock);
-       if (test_bit(ATH11K_FLAG_REGISTERED, &ab->dev_flags)) {
-               /* Once mac is registered, ar is valid and all CC events from
-                * fw is considered to be received due to user requests
-                * currently.
+       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.
@@ -5909,13 +5909,9 @@ static int ath11k_reg_chan_list_event(struct ath11k_base *ab, struct sk_buff *sk
                ab->new_regd[pdev_idx] = regd;
                ieee80211_queue_work(ar->hw, &ar->regd_update_work);
        } else {
-               /* Multiple events for the same *ar is not expected. But we
-                * can still clear any previously stored default_regd if we
-                * are receiving this event for the same radio by mistake.
-                * NULL pointer handling will be taken care by kfree itself.
+               /* This regd would be applied during mac registration and is
+                * held constant throughout for regd intersection purpose
                 */
-               kfree(ab->default_regd[pdev_idx]);
-               /* This regd would be applied during mac registration */
                ab->default_regd[pdev_idx] = regd;
        }
        ab->dfs_region = reg_info->dfs_region;