Merge tag 'wireless-2023-11-29' of git://git.kernel.org/pub/scm/linux/kernel/git...
authorJakub Kicinski <kuba@kernel.org>
Thu, 30 Nov 2023 03:43:34 +0000 (19:43 -0800)
committerJakub Kicinski <kuba@kernel.org>
Thu, 30 Nov 2023 03:43:34 +0000 (19:43 -0800)
Johannes Berg says:

====================
wireless fixes:
 - debugfs had a deadlock (removal vs. use of files),
   fixes going through wireless ACKed by Greg
 - support for HT STAs on 320 MHz channels, even if it's
   not clear that should ever happen (that's 6 GHz), best
   not to WARN()
 - fix for the previous CQM fix that broke most cases
 - various wiphy locking fixes
 - various small driver fixes

* tag 'wireless-2023-11-29' of git://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless:
  wifi: mac80211: use wiphy locked debugfs for sdata/link
  wifi: mac80211: use wiphy locked debugfs helpers for agg_status
  wifi: cfg80211: add locked debugfs wrappers
  debugfs: add API to allow debugfs operations cancellation
  debugfs: annotate debugfs handlers vs. removal with lockdep
  debugfs: fix automount d_fsdata usage
  wifi: mac80211: handle 320 MHz in ieee80211_ht_cap_ie_to_sta_ht_cap
  wifi: avoid offset calculation on NULL pointer
  wifi: cfg80211: hold wiphy mutex for send_interface
  wifi: cfg80211: lock wiphy mutex for rfkill poll
  wifi: cfg80211: fix CQM for non-range use
  wifi: mac80211: do not pass AP_VLAN vif pointer to drivers during flush
  wifi: iwlwifi: mvm: fix an error code in iwl_mvm_mld_add_sta()
  wifi: mt76: mt7925: fix typo in mt7925_init_he_caps
  wifi: mt76: mt7921: fix 6GHz disabled by the missing default CLC config
====================

Link: https://lore.kernel.org/r/20231129150809.31083-3-johannes@sipsolutions.net
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
19 files changed:
drivers/net/wireless/ath/ath9k/Kconfig
drivers/net/wireless/intel/iwlwifi/mvm/mld-sta.c
drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
drivers/net/wireless/mediatek/mt76/mt7925/main.c
fs/debugfs/file.c
fs/debugfs/inode.c
fs/debugfs/internal.h
include/linux/debugfs.h
include/linux/ieee80211.h
include/net/cfg80211.h
net/mac80211/Kconfig
net/mac80211/debugfs_netdev.c
net/mac80211/debugfs_sta.c
net/mac80211/driver-ops.h
net/mac80211/ht.c
net/wireless/core.c
net/wireless/core.h
net/wireless/debugfs.c
net/wireless/nl80211.c

index e150d82eddb6c72859872abc7b3d7e73b7484e94..0c47be06c153be18c410324a62f12c77264b38b9 100644 (file)
@@ -57,8 +57,7 @@ config ATH9K_AHB
 
 config ATH9K_DEBUGFS
        bool "Atheros ath9k debugging"
-       depends on ATH9K && DEBUG_FS
-       select MAC80211_DEBUGFS
+       depends on ATH9K && DEBUG_FS && MAC80211_DEBUGFS
        select ATH9K_COMMON_DEBUG
        help
          Say Y, if you need access to ath9k's statistics for
@@ -70,7 +69,6 @@ config ATH9K_DEBUGFS
 config ATH9K_STATION_STATISTICS
        bool "Detailed station statistics"
        depends on ATH9K && ATH9K_DEBUGFS && DEBUG_FS
-       select MAC80211_DEBUGFS
        default n
        help
          This option enables detailed statistics for association stations.
index ca5e4fbcf8ce53108448b4fe338658e04b820398..6af606e5da657ed5d860f7cf67726424480ce595 100644 (file)
@@ -707,8 +707,10 @@ int iwl_mvm_mld_add_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
                        rcu_dereference_protected(mvm_sta->link[link_id],
                                                  lockdep_is_held(&mvm->mutex));
 
-               if (WARN_ON(!link_conf || !mvm_link_sta))
+               if (WARN_ON(!link_conf || !mvm_link_sta)) {
+                       ret = -EINVAL;
                        goto err;
+               }
 
                ret = iwl_mvm_mld_cfg_sta(mvm, sta, vif, link_sta, link_conf,
                                          mvm_link_sta);
index 63f3d4a5c9aa6e9daafc69fbe049c04b91b766ad..2cc2d2788f831257980e08cc475cdec0f6b3b4c6 100644 (file)
@@ -375,6 +375,7 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name)
        int ret, i, len, offset = 0;
        u8 *clc_base = NULL, hw_encap = 0;
 
+       dev->phy.clc_chan_conf = 0xff;
        if (mt7921_disable_clc ||
            mt76_is_usb(&dev->mt76))
                return 0;
index 15c2fb0bcb1b98d1ea436db26c3c6da09441534a..aa918b9b0469f8444ce2fb1f68a5d79b71c53ede 100644 (file)
@@ -14,7 +14,7 @@
 static void
 mt7925_init_he_caps(struct mt792x_phy *phy, enum nl80211_band band,
                    struct ieee80211_sband_iftype_data *data,
-                       enum nl80211_iftype iftype)
+                   enum nl80211_iftype iftype)
 {
        struct ieee80211_sta_he_cap *he_cap = &data->he_cap;
        struct ieee80211_he_cap_elem *he_cap_elem = &he_cap->he_cap_elem;
@@ -53,7 +53,7 @@ mt7925_init_he_caps(struct mt792x_phy *phy, enum nl80211_band band,
                IEEE80211_HE_PHY_CAP2_UL_MU_FULL_MU_MIMO |
                IEEE80211_HE_PHY_CAP2_UL_MU_PARTIAL_MU_MIMO;
 
-       switch (i) {
+       switch (iftype) {
        case NL80211_IFTYPE_AP:
                he_cap_elem->mac_cap_info[2] |=
                        IEEE80211_HE_MAC_CAP2_BSR;
index c45e8c2d62e11655b4e340cb6e01edd71d9e9eed..a5ade8c163754bf09036d81eff378f3ff5c545cd 100644 (file)
@@ -84,6 +84,14 @@ int debugfs_file_get(struct dentry *dentry)
        struct debugfs_fsdata *fsd;
        void *d_fsd;
 
+       /*
+        * This could only happen if some debugfs user erroneously calls
+        * debugfs_file_get() on a dentry that isn't even a file, let
+        * them know about it.
+        */
+       if (WARN_ON(!d_is_reg(dentry)))
+               return -EINVAL;
+
        d_fsd = READ_ONCE(dentry->d_fsdata);
        if (!((unsigned long)d_fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)) {
                fsd = d_fsd;
@@ -100,6 +108,14 @@ int debugfs_file_get(struct dentry *dentry)
                        kfree(fsd);
                        fsd = READ_ONCE(dentry->d_fsdata);
                }
+#ifdef CONFIG_LOCKDEP
+               fsd->lock_name = kasprintf(GFP_KERNEL, "debugfs:%pd", dentry);
+               lockdep_register_key(&fsd->key);
+               lockdep_init_map(&fsd->lockdep_map, fsd->lock_name ?: "debugfs",
+                                &fsd->key, 0);
+#endif
+               INIT_LIST_HEAD(&fsd->cancellations);
+               mutex_init(&fsd->cancellations_mtx);
        }
 
        /*
@@ -116,6 +132,8 @@ int debugfs_file_get(struct dentry *dentry)
        if (!refcount_inc_not_zero(&fsd->active_users))
                return -EIO;
 
+       lock_map_acquire_read(&fsd->lockdep_map);
+
        return 0;
 }
 EXPORT_SYMBOL_GPL(debugfs_file_get);
@@ -133,11 +151,93 @@ void debugfs_file_put(struct dentry *dentry)
 {
        struct debugfs_fsdata *fsd = READ_ONCE(dentry->d_fsdata);
 
+       lock_map_release(&fsd->lockdep_map);
+
        if (refcount_dec_and_test(&fsd->active_users))
                complete(&fsd->active_users_drained);
 }
 EXPORT_SYMBOL_GPL(debugfs_file_put);
 
+/**
+ * debugfs_enter_cancellation - enter a debugfs cancellation
+ * @file: the file being accessed
+ * @cancellation: the cancellation object, the cancel callback
+ *     inside of it must be initialized
+ *
+ * When a debugfs file is removed it needs to wait for all active
+ * operations to complete. However, the operation itself may need
+ * to wait for hardware or completion of some asynchronous process
+ * or similar. As such, it may need to be cancelled to avoid long
+ * waits or even deadlocks.
+ *
+ * This function can be used inside a debugfs handler that may
+ * need to be cancelled. As soon as this function is called, the
+ * cancellation's 'cancel' callback may be called, at which point
+ * the caller should proceed to call debugfs_leave_cancellation()
+ * and leave the debugfs handler function as soon as possible.
+ * Note that the 'cancel' callback is only ever called in the
+ * context of some kind of debugfs_remove().
+ *
+ * This function must be paired with debugfs_leave_cancellation().
+ */
+void debugfs_enter_cancellation(struct file *file,
+                               struct debugfs_cancellation *cancellation)
+{
+       struct debugfs_fsdata *fsd;
+       struct dentry *dentry = F_DENTRY(file);
+
+       INIT_LIST_HEAD(&cancellation->list);
+
+       if (WARN_ON(!d_is_reg(dentry)))
+               return;
+
+       if (WARN_ON(!cancellation->cancel))
+               return;
+
+       fsd = READ_ONCE(dentry->d_fsdata);
+       if (WARN_ON(!fsd ||
+                   ((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)))
+               return;
+
+       mutex_lock(&fsd->cancellations_mtx);
+       list_add(&cancellation->list, &fsd->cancellations);
+       mutex_unlock(&fsd->cancellations_mtx);
+
+       /* if we're already removing wake it up to cancel */
+       if (d_unlinked(dentry))
+               complete(&fsd->active_users_drained);
+}
+EXPORT_SYMBOL_GPL(debugfs_enter_cancellation);
+
+/**
+ * debugfs_leave_cancellation - leave cancellation section
+ * @file: the file being accessed
+ * @cancellation: the cancellation previously registered with
+ *     debugfs_enter_cancellation()
+ *
+ * See the documentation of debugfs_enter_cancellation().
+ */
+void debugfs_leave_cancellation(struct file *file,
+                               struct debugfs_cancellation *cancellation)
+{
+       struct debugfs_fsdata *fsd;
+       struct dentry *dentry = F_DENTRY(file);
+
+       if (WARN_ON(!d_is_reg(dentry)))
+               return;
+
+       fsd = READ_ONCE(dentry->d_fsdata);
+       if (WARN_ON(!fsd ||
+                   ((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)))
+               return;
+
+       mutex_lock(&fsd->cancellations_mtx);
+       if (!list_empty(&cancellation->list))
+               list_del(&cancellation->list);
+       mutex_unlock(&fsd->cancellations_mtx);
+}
+EXPORT_SYMBOL_GPL(debugfs_leave_cancellation);
+
 /*
  * Only permit access to world-readable files when the kernel is locked down.
  * We also need to exclude any file that has ways to write or alter it as root
index 5d41765e0c77695c21a4b53fa4db052190a66597..e4e7fe1bd9fbfaa316364404a3dcfe1cd9961bfe 100644 (file)
@@ -236,17 +236,29 @@ static const struct super_operations debugfs_super_operations = {
 
 static void debugfs_release_dentry(struct dentry *dentry)
 {
-       void *fsd = dentry->d_fsdata;
+       struct debugfs_fsdata *fsd = dentry->d_fsdata;
 
-       if (!((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT))
-               kfree(dentry->d_fsdata);
+       if ((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)
+               return;
+
+       /* check it wasn't a dir (no fsdata) or automount (no real_fops) */
+       if (fsd && fsd->real_fops) {
+#ifdef CONFIG_LOCKDEP
+               lockdep_unregister_key(&fsd->key);
+               kfree(fsd->lock_name);
+#endif
+               WARN_ON(!list_empty(&fsd->cancellations));
+               mutex_destroy(&fsd->cancellations_mtx);
+       }
+
+       kfree(fsd);
 }
 
 static struct vfsmount *debugfs_automount(struct path *path)
 {
-       debugfs_automount_t f;
-       f = (debugfs_automount_t)path->dentry->d_fsdata;
-       return f(path->dentry, d_inode(path->dentry)->i_private);
+       struct debugfs_fsdata *fsd = path->dentry->d_fsdata;
+
+       return fsd->automount(path->dentry, d_inode(path->dentry)->i_private);
 }
 
 static const struct dentry_operations debugfs_dops = {
@@ -634,13 +646,23 @@ struct dentry *debugfs_create_automount(const char *name,
                                        void *data)
 {
        struct dentry *dentry = start_creating(name, parent);
+       struct debugfs_fsdata *fsd;
        struct inode *inode;
 
        if (IS_ERR(dentry))
                return dentry;
 
+       fsd = kzalloc(sizeof(*fsd), GFP_KERNEL);
+       if (!fsd) {
+               failed_creating(dentry);
+               return ERR_PTR(-ENOMEM);
+       }
+
+       fsd->automount = f;
+
        if (!(debugfs_allow & DEBUGFS_ALLOW_API)) {
                failed_creating(dentry);
+               kfree(fsd);
                return ERR_PTR(-EPERM);
        }
 
@@ -648,13 +670,14 @@ struct dentry *debugfs_create_automount(const char *name,
        if (unlikely(!inode)) {
                pr_err("out of free dentries, can not create automount '%s'\n",
                       name);
+               kfree(fsd);
                return failed_creating(dentry);
        }
 
        make_empty_dir_inode(inode);
        inode->i_flags |= S_AUTOMOUNT;
        inode->i_private = data;
-       dentry->d_fsdata = (void *)f;
+       dentry->d_fsdata = fsd;
        /* directory inodes start off with i_nlink == 2 (for "." entry) */
        inc_nlink(inode);
        d_instantiate(dentry, inode);
@@ -731,8 +754,40 @@ static void __debugfs_file_removed(struct dentry *dentry)
        fsd = READ_ONCE(dentry->d_fsdata);
        if ((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)
                return;
-       if (!refcount_dec_and_test(&fsd->active_users))
+
+       lock_map_acquire(&fsd->lockdep_map);
+       lock_map_release(&fsd->lockdep_map);
+
+       /* if we hit zero, just wait for all to finish */
+       if (!refcount_dec_and_test(&fsd->active_users)) {
+               wait_for_completion(&fsd->active_users_drained);
+               return;
+       }
+
+       /* if we didn't hit zero, try to cancel any we can */
+       while (refcount_read(&fsd->active_users)) {
+               struct debugfs_cancellation *c;
+
+               /*
+                * Lock the cancellations. Note that the cancellations
+                * structs are meant to be on the stack, so we need to
+                * ensure we either use them here or don't touch them,
+                * and debugfs_leave_cancellation() will wait for this
+                * to be finished processing before exiting one. It may
+                * of course win and remove the cancellation, but then
+                * chances are we never even got into this bit, we only
+                * do if the refcount isn't zero already.
+                */
+               mutex_lock(&fsd->cancellations_mtx);
+               while ((c = list_first_entry_or_null(&fsd->cancellations,
+                                                    typeof(*c), list))) {
+                       list_del_init(&c->list);
+                       c->cancel(dentry, c->cancel_data);
+               }
+               mutex_unlock(&fsd->cancellations_mtx);
+
                wait_for_completion(&fsd->active_users_drained);
+       }
 }
 
 static void remove_one(struct dentry *victim)
index 92af8ae313134654e52bcb9f90b22280a6ae775e..0c4c68cf161f8742cf25c072291a26095e35f74e 100644 (file)
@@ -7,6 +7,8 @@
 
 #ifndef _DEBUGFS_INTERNAL_H_
 #define _DEBUGFS_INTERNAL_H_
+#include <linux/lockdep.h>
+#include <linux/list.h>
 
 struct file_operations;
 
@@ -17,8 +19,23 @@ extern const struct file_operations debugfs_full_proxy_file_operations;
 
 struct debugfs_fsdata {
        const struct file_operations *real_fops;
-       refcount_t active_users;
-       struct completion active_users_drained;
+       union {
+               /* automount_fn is used when real_fops is NULL */
+               debugfs_automount_t automount;
+               struct {
+                       refcount_t active_users;
+                       struct completion active_users_drained;
+#ifdef CONFIG_LOCKDEP
+                       struct lockdep_map lockdep_map;
+                       struct lock_class_key key;
+                       char *lock_name;
+#endif
+
+                       /* protect cancellations */
+                       struct mutex cancellations_mtx;
+                       struct list_head cancellations;
+               };
+       };
 };
 
 /*
index ea2d919fd9c7990061ba4f469f92bbe5880a7b45..c9c65b132c0fd7fcf12c95c0ed50281bb0e66efa 100644 (file)
@@ -171,6 +171,25 @@ ssize_t debugfs_write_file_bool(struct file *file, const char __user *user_buf,
 ssize_t debugfs_read_file_str(struct file *file, char __user *user_buf,
                              size_t count, loff_t *ppos);
 
+/**
+ * struct debugfs_cancellation - cancellation data
+ * @list: internal, for keeping track
+ * @cancel: callback to call
+ * @cancel_data: extra data for the callback to call
+ */
+struct debugfs_cancellation {
+       struct list_head list;
+       void (*cancel)(struct dentry *, void *);
+       void *cancel_data;
+};
+
+void __acquires(cancellation)
+debugfs_enter_cancellation(struct file *file,
+                          struct debugfs_cancellation *cancellation);
+void __releases(cancellation)
+debugfs_leave_cancellation(struct file *file,
+                          struct debugfs_cancellation *cancellation);
+
 #else
 
 #include <linux/err.h>
index 958771bac9c0295f2a7ee57f56cac436933626fd..c2ac9e9e7ee9a8bc80ebaa6465875418bdabe397 100644 (file)
@@ -2830,12 +2830,14 @@ ieee80211_he_oper_size(const u8 *he_oper_ie)
 static inline const struct ieee80211_he_6ghz_oper *
 ieee80211_he_6ghz_oper(const struct ieee80211_he_operation *he_oper)
 {
-       const u8 *ret = (const void *)&he_oper->optional;
+       const u8 *ret;
        u32 he_oper_params;
 
        if (!he_oper)
                return NULL;
 
+       ret = (const void *)&he_oper->optional;
+
        he_oper_params = le32_to_cpu(he_oper->he_oper_params);
 
        if (!(he_oper_params & IEEE80211_HE_OPERATION_6GHZ_OP_INFO))
index b137a33a1b6892190dfa4e9067cf1045b82cf4b4..4ecfb06c413dbfd468467840dc5d1a178c6f5869 100644 (file)
@@ -9299,4 +9299,50 @@ bool cfg80211_valid_disable_subchannel_bitmap(u16 *bitmap,
  */
 void cfg80211_links_removed(struct net_device *dev, u16 link_mask);
 
+#ifdef CONFIG_CFG80211_DEBUGFS
+/**
+ * wiphy_locked_debugfs_read - do a locked read in debugfs
+ * @wiphy: the wiphy to use
+ * @file: the file being read
+ * @buf: the buffer to fill and then read from
+ * @bufsize: size of the buffer
+ * @userbuf: the user buffer to copy to
+ * @count: read count
+ * @ppos: read position
+ * @handler: the read handler to call (under wiphy lock)
+ * @data: additional data to pass to the read handler
+ */
+ssize_t wiphy_locked_debugfs_read(struct wiphy *wiphy, struct file *file,
+                                 char *buf, size_t bufsize,
+                                 char __user *userbuf, size_t count,
+                                 loff_t *ppos,
+                                 ssize_t (*handler)(struct wiphy *wiphy,
+                                                    struct file *file,
+                                                    char *buf,
+                                                    size_t bufsize,
+                                                    void *data),
+                                 void *data);
+
+/**
+ * wiphy_locked_debugfs_write - do a locked write in debugfs
+ * @wiphy: the wiphy to use
+ * @file: the file being written to
+ * @buf: the buffer to copy the user data to
+ * @bufsize: size of the buffer
+ * @userbuf: the user buffer to copy from
+ * @count: read count
+ * @handler: the write handler to call (under wiphy lock)
+ * @data: additional data to pass to the write handler
+ */
+ssize_t wiphy_locked_debugfs_write(struct wiphy *wiphy, struct file *file,
+                                  char *buf, size_t bufsize,
+                                  const char __user *userbuf, size_t count,
+                                  ssize_t (*handler)(struct wiphy *wiphy,
+                                                     struct file *file,
+                                                     char *buf,
+                                                     size_t count,
+                                                     void *data),
+                                  void *data);
+#endif
+
 #endif /* __NET_CFG80211_H */
index 037ab74f5ade5b1961ff7292c4113f7aae4ef2d2..cb0291decf2e56c7d4111e649f41d28577af987e 100644 (file)
@@ -88,7 +88,7 @@ config MAC80211_LEDS
 
 config MAC80211_DEBUGFS
        bool "Export mac80211 internals in DebugFS"
-       depends on MAC80211 && DEBUG_FS
+       depends on MAC80211 && CFG80211_DEBUGFS
        help
          Select this to see extensive information about
          the internal state of mac80211 in debugfs.
index ec91e131b29e5aa8e5e2baf177819fcf6d973fe0..80aeb25f1b68d1e63e2aca0049258a2524848675 100644 (file)
 #include "debugfs_netdev.h"
 #include "driver-ops.h"
 
+struct ieee80211_if_read_sdata_data {
+       ssize_t (*format)(const struct ieee80211_sub_if_data *, char *, int);
+       struct ieee80211_sub_if_data *sdata;
+};
+
+static ssize_t ieee80211_if_read_sdata_handler(struct wiphy *wiphy,
+                                              struct file *file,
+                                              char *buf,
+                                              size_t bufsize,
+                                              void *data)
+{
+       struct ieee80211_if_read_sdata_data *d = data;
+
+       return d->format(d->sdata, buf, bufsize);
+}
+
 static ssize_t ieee80211_if_read_sdata(
-       struct ieee80211_sub_if_data *sdata,
+       struct file *file,
        char __user *userbuf,
        size_t count, loff_t *ppos,
        ssize_t (*format)(const struct ieee80211_sub_if_data *sdata, char *, int))
 {
+       struct ieee80211_sub_if_data *sdata = file->private_data;
+       struct ieee80211_if_read_sdata_data data = {
+               .format = format,
+               .sdata = sdata,
+       };
        char buf[200];
-       ssize_t ret = -EINVAL;
 
-       wiphy_lock(sdata->local->hw.wiphy);
-       ret = (*format)(sdata, buf, sizeof(buf));
-       wiphy_unlock(sdata->local->hw.wiphy);
+       return wiphy_locked_debugfs_read(sdata->local->hw.wiphy,
+                                        file, buf, sizeof(buf),
+                                        userbuf, count, ppos,
+                                        ieee80211_if_read_sdata_handler,
+                                        &data);
+}
 
-       if (ret >= 0)
-               ret = simple_read_from_buffer(userbuf, count, ppos, buf, ret);
+struct ieee80211_if_write_sdata_data {
+       ssize_t (*write)(struct ieee80211_sub_if_data *, const char *, int);
+       struct ieee80211_sub_if_data *sdata;
+};
+
+static ssize_t ieee80211_if_write_sdata_handler(struct wiphy *wiphy,
+                                               struct file *file,
+                                               char *buf,
+                                               size_t count,
+                                               void *data)
+{
+       struct ieee80211_if_write_sdata_data *d = data;
 
-       return ret;
+       return d->write(d->sdata, buf, count);
 }
 
 static ssize_t ieee80211_if_write_sdata(
-       struct ieee80211_sub_if_data *sdata,
+       struct file *file,
        const char __user *userbuf,
        size_t count, loff_t *ppos,
        ssize_t (*write)(struct ieee80211_sub_if_data *sdata, const char *, int))
 {
+       struct ieee80211_sub_if_data *sdata = file->private_data;
+       struct ieee80211_if_write_sdata_data data = {
+               .write = write,
+               .sdata = sdata,
+       };
        char buf[64];
-       ssize_t ret;
 
-       if (count >= sizeof(buf))
-               return -E2BIG;
+       return wiphy_locked_debugfs_write(sdata->local->hw.wiphy,
+                                         file, buf, sizeof(buf),
+                                         userbuf, count,
+                                         ieee80211_if_write_sdata_handler,
+                                         &data);
+}
 
-       if (copy_from_user(buf, userbuf, count))
-               return -EFAULT;
-       buf[count] = '\0';
+struct ieee80211_if_read_link_data {
+       ssize_t (*format)(const struct ieee80211_link_data *, char *, int);
+       struct ieee80211_link_data *link;
+};
 
-       wiphy_lock(sdata->local->hw.wiphy);
-       ret = (*write)(sdata, buf, count);
-       wiphy_unlock(sdata->local->hw.wiphy);
+static ssize_t ieee80211_if_read_link_handler(struct wiphy *wiphy,
+                                             struct file *file,
+                                             char *buf,
+                                             size_t bufsize,
+                                             void *data)
+{
+       struct ieee80211_if_read_link_data *d = data;
 
-       return ret;
+       return d->format(d->link, buf, bufsize);
 }
 
 static ssize_t ieee80211_if_read_link(
-       struct ieee80211_link_data *link,
+       struct file *file,
        char __user *userbuf,
        size_t count, loff_t *ppos,
        ssize_t (*format)(const struct ieee80211_link_data *link, char *, int))
 {
+       struct ieee80211_link_data *link = file->private_data;
+       struct ieee80211_if_read_link_data data = {
+               .format = format,
+               .link = link,
+       };
        char buf[200];
-       ssize_t ret = -EINVAL;
 
-       wiphy_lock(link->sdata->local->hw.wiphy);
-       ret = (*format)(link, buf, sizeof(buf));
-       wiphy_unlock(link->sdata->local->hw.wiphy);
+       return wiphy_locked_debugfs_read(link->sdata->local->hw.wiphy,
+                                        file, buf, sizeof(buf),
+                                        userbuf, count, ppos,
+                                        ieee80211_if_read_link_handler,
+                                        &data);
+}
+
+struct ieee80211_if_write_link_data {
+       ssize_t (*write)(struct ieee80211_link_data *, const char *, int);
+       struct ieee80211_link_data *link;
+};
 
-       if (ret >= 0)
-               ret = simple_read_from_buffer(userbuf, count, ppos, buf, ret);
+static ssize_t ieee80211_if_write_link_handler(struct wiphy *wiphy,
+                                              struct file *file,
+                                              char *buf,
+                                              size_t count,
+                                              void *data)
+{
+       struct ieee80211_if_write_sdata_data *d = data;
 
-       return ret;
+       return d->write(d->sdata, buf, count);
 }
 
 static ssize_t ieee80211_if_write_link(
-       struct ieee80211_link_data *link,
+       struct file *file,
        const char __user *userbuf,
        size_t count, loff_t *ppos,
        ssize_t (*write)(struct ieee80211_link_data *link, const char *, int))
 {
+       struct ieee80211_link_data *link = file->private_data;
+       struct ieee80211_if_write_link_data data = {
+               .write = write,
+               .link = link,
+       };
        char buf[64];
-       ssize_t ret;
-
-       if (count >= sizeof(buf))
-               return -E2BIG;
-
-       if (copy_from_user(buf, userbuf, count))
-               return -EFAULT;
-       buf[count] = '\0';
-
-       wiphy_lock(link->sdata->local->hw.wiphy);
-       ret = (*write)(link, buf, count);
-       wiphy_unlock(link->sdata->local->hw.wiphy);
 
-       return ret;
+       return wiphy_locked_debugfs_write(link->sdata->local->hw.wiphy,
+                                         file, buf, sizeof(buf),
+                                         userbuf, count,
+                                         ieee80211_if_write_link_handler,
+                                         &data);
 }
 
 #define IEEE80211_IF_FMT(name, type, field, format_string)             \
@@ -173,7 +233,7 @@ static ssize_t ieee80211_if_read_##name(struct file *file,          \
                                        char __user *userbuf,           \
                                        size_t count, loff_t *ppos)     \
 {                                                                      \
-       return ieee80211_if_read_sdata(file->private_data,              \
+       return ieee80211_if_read_sdata(file,                            \
                                       userbuf, count, ppos,            \
                                       ieee80211_if_fmt_##name);        \
 }
@@ -183,7 +243,7 @@ static ssize_t ieee80211_if_write_##name(struct file *file,         \
                                         const char __user *userbuf,    \
                                         size_t count, loff_t *ppos)    \
 {                                                                      \
-       return ieee80211_if_write_sdata(file->private_data, userbuf,    \
+       return ieee80211_if_write_sdata(file, userbuf,                  \
                                        count, ppos,                    \
                                        ieee80211_if_parse_##name);     \
 }
@@ -211,7 +271,7 @@ static ssize_t ieee80211_if_read_##name(struct file *file,          \
                                        char __user *userbuf,           \
                                        size_t count, loff_t *ppos)     \
 {                                                                      \
-       return ieee80211_if_read_link(file->private_data,               \
+       return ieee80211_if_read_link(file,                             \
                                      userbuf, count, ppos,             \
                                      ieee80211_if_fmt_##name); \
 }
@@ -221,7 +281,7 @@ static ssize_t ieee80211_if_write_##name(struct file *file,         \
                                         const char __user *userbuf,    \
                                         size_t count, loff_t *ppos)    \
 {                                                                      \
-       return ieee80211_if_write_link(file->private_data, userbuf,     \
+       return ieee80211_if_write_link(file, userbuf,                   \
                                       count, ppos,                     \
                                       ieee80211_if_parse_##name);      \
 }
index 06e3613bf46bd918a69ff3d50cfa1411d466e4a8..5bf507ebb096be315ceb13162a761e20134c3434 100644 (file)
@@ -312,23 +312,14 @@ static ssize_t sta_aql_write(struct file *file, const char __user *userbuf,
 STA_OPS_RW(aql);
 
 
-static ssize_t sta_agg_status_read(struct file *file, char __user *userbuf,
-                                       size_t count, loff_t *ppos)
+static ssize_t sta_agg_status_do_read(struct wiphy *wiphy, struct file *file,
+                                     char *buf, size_t bufsz, void *data)
 {
-       char *buf, *p;
-       ssize_t bufsz = 71 + IEEE80211_NUM_TIDS * 40;
+       struct sta_info *sta = data;
+       char *p = buf;
        int i;
-       struct sta_info *sta = file->private_data;
        struct tid_ampdu_rx *tid_rx;
        struct tid_ampdu_tx *tid_tx;
-       ssize_t ret;
-
-       buf = kzalloc(bufsz, GFP_KERNEL);
-       if (!buf)
-               return -ENOMEM;
-       p = buf;
-
-       rcu_read_lock();
 
        p += scnprintf(p, bufsz + buf - p, "next dialog_token: %#02x\n",
                        sta->ampdu_mlme.dialog_token_allocator + 1);
@@ -338,8 +329,8 @@ static ssize_t sta_agg_status_read(struct file *file, char __user *userbuf,
        for (i = 0; i < IEEE80211_NUM_TIDS; i++) {
                bool tid_rx_valid;
 
-               tid_rx = rcu_dereference(sta->ampdu_mlme.tid_rx[i]);
-               tid_tx = rcu_dereference(sta->ampdu_mlme.tid_tx[i]);
+               tid_rx = wiphy_dereference(wiphy, sta->ampdu_mlme.tid_rx[i]);
+               tid_tx = wiphy_dereference(wiphy, sta->ampdu_mlme.tid_tx[i]);
                tid_rx_valid = test_bit(i, sta->ampdu_mlme.agg_session_valid);
 
                p += scnprintf(p, bufsz + buf - p, "%02d", i);
@@ -358,31 +349,39 @@ static ssize_t sta_agg_status_read(struct file *file, char __user *userbuf,
                                tid_tx ? skb_queue_len(&tid_tx->pending) : 0);
                p += scnprintf(p, bufsz + buf - p, "\n");
        }
-       rcu_read_unlock();
 
-       ret = simple_read_from_buffer(userbuf, count, ppos, buf, p - buf);
+       return p - buf;
+}
+
+static ssize_t sta_agg_status_read(struct file *file, char __user *userbuf,
+                                  size_t count, loff_t *ppos)
+{
+       struct sta_info *sta = file->private_data;
+       struct wiphy *wiphy = sta->local->hw.wiphy;
+       size_t bufsz = 71 + IEEE80211_NUM_TIDS * 40;
+       char *buf = kmalloc(bufsz, GFP_KERNEL);
+       ssize_t ret;
+
+       if (!buf)
+               return -ENOMEM;
+
+       ret = wiphy_locked_debugfs_read(wiphy, file, buf, bufsz,
+                                       userbuf, count, ppos,
+                                       sta_agg_status_do_read, sta);
        kfree(buf);
+
        return ret;
 }
 
-static ssize_t sta_agg_status_write(struct file *file, const char __user *userbuf,
-                                   size_t count, loff_t *ppos)
+static ssize_t sta_agg_status_do_write(struct wiphy *wiphy, struct file *file,
+                                      char *buf, size_t count, void *data)
 {
-       char _buf[25] = {}, *buf = _buf;
-       struct sta_info *sta = file->private_data;
+       struct sta_info *sta = data;
        bool start, tx;
        unsigned long tid;
-       char *pos;
+       char *pos = buf;
        int ret, timeout = 5000;
 
-       if (count > sizeof(_buf))
-               return -EINVAL;
-
-       if (copy_from_user(buf, userbuf, count))
-               return -EFAULT;
-
-       buf[sizeof(_buf) - 1] = '\0';
-       pos = buf;
        buf = strsep(&pos, " ");
        if (!buf)
                return -EINVAL;
@@ -420,7 +419,6 @@ static ssize_t sta_agg_status_write(struct file *file, const char __user *userbu
        if (ret || tid >= IEEE80211_NUM_TIDS)
                return -EINVAL;
 
-       wiphy_lock(sta->local->hw.wiphy);
        if (tx) {
                if (start)
                        ret = ieee80211_start_tx_ba_session(&sta->sta, tid,
@@ -432,10 +430,22 @@ static ssize_t sta_agg_status_write(struct file *file, const char __user *userbu
                                               3, true);
                ret = 0;
        }
-       wiphy_unlock(sta->local->hw.wiphy);
 
        return ret ?: count;
 }
+
+static ssize_t sta_agg_status_write(struct file *file,
+                                   const char __user *userbuf,
+                                   size_t count, loff_t *ppos)
+{
+       struct sta_info *sta = file->private_data;
+       struct wiphy *wiphy = sta->local->hw.wiphy;
+       char _buf[26];
+
+       return wiphy_locked_debugfs_write(wiphy, file, _buf, sizeof(_buf),
+                                         userbuf, count,
+                                         sta_agg_status_do_write, sta);
+}
 STA_OPS_RW(agg_status);
 
 /* link sta attributes */
index 568633b38c47360d0de6e6f856da63b3776ad19f..f690c385a345a60d81d994336fea44aa7e1792ef 100644 (file)
@@ -23,7 +23,7 @@
 static inline struct ieee80211_sub_if_data *
 get_bss_sdata(struct ieee80211_sub_if_data *sdata)
 {
-       if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
+       if (sdata && sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
                sdata = container_of(sdata->bss, struct ieee80211_sub_if_data,
                                     u.ap);
 
@@ -695,11 +695,14 @@ static inline void drv_flush(struct ieee80211_local *local,
                             struct ieee80211_sub_if_data *sdata,
                             u32 queues, bool drop)
 {
-       struct ieee80211_vif *vif = sdata ? &sdata->vif : NULL;
+       struct ieee80211_vif *vif;
 
        might_sleep();
        lockdep_assert_wiphy(local->hw.wiphy);
 
+       sdata = get_bss_sdata(sdata);
+       vif = sdata ? &sdata->vif : NULL;
+
        if (sdata && !check_sdata_in_driver(sdata))
                return;
 
@@ -716,6 +719,8 @@ static inline void drv_flush_sta(struct ieee80211_local *local,
        might_sleep();
        lockdep_assert_wiphy(local->hw.wiphy);
 
+       sdata = get_bss_sdata(sdata);
+
        if (sdata && !check_sdata_in_driver(sdata))
                return;
 
index 68cea2685224b92fbb03896b0ab82d7c143cfd80..749f4ecab99030310e6c5b82633f8cad85fe589b 100644 (file)
@@ -271,6 +271,7 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
        case NL80211_CHAN_WIDTH_80:
        case NL80211_CHAN_WIDTH_80P80:
        case NL80211_CHAN_WIDTH_160:
+       case NL80211_CHAN_WIDTH_320:
                bw = ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
                                IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20;
                break;
index 758c9a2a12c0d3ce5f1baefdcc3a9dd7076b7eb1..409d74c57ca0d8c8d36c2260897fce39557620ee 100644 (file)
@@ -191,13 +191,13 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev,
                return err;
        }
 
+       wiphy_lock(&rdev->wiphy);
        list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
                if (!wdev->netdev)
                        continue;
                nl80211_notify_iface(rdev, wdev, NL80211_CMD_DEL_INTERFACE);
        }
 
-       wiphy_lock(&rdev->wiphy);
        nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY);
 
        wiphy_net_set(&rdev->wiphy, net);
@@ -206,13 +206,13 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev,
        WARN_ON(err);
 
        nl80211_notify_wiphy(rdev, NL80211_CMD_NEW_WIPHY);
-       wiphy_unlock(&rdev->wiphy);
 
        list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
                if (!wdev->netdev)
                        continue;
                nl80211_notify_iface(rdev, wdev, NL80211_CMD_NEW_INTERFACE);
        }
+       wiphy_unlock(&rdev->wiphy);
 
        return 0;
 }
@@ -221,7 +221,9 @@ static void cfg80211_rfkill_poll(struct rfkill *rfkill, void *data)
 {
        struct cfg80211_registered_device *rdev = data;
 
+       wiphy_lock(&rdev->wiphy);
        rdev_rfkill_poll(rdev);
+       wiphy_unlock(&rdev->wiphy);
 }
 
 void cfg80211_stop_p2p_device(struct cfg80211_registered_device *rdev,
index 4c692c7faf30d408ab2cb3101b5344786bb690ab..cb61d33d4f1ebc4c42f21e568d51e15d5bca0797 100644 (file)
@@ -293,6 +293,7 @@ struct cfg80211_cqm_config {
        u32 rssi_hyst;
        s32 last_rssi_event_value;
        enum nl80211_cqm_rssi_threshold_event last_rssi_event_type;
+       bool use_range_api;
        int n_rssi_thresholds;
        s32 rssi_thresholds[] __counted_by(n_rssi_thresholds);
 };
index 0878b162890af7c57073991468415a87dc9edc16..40e49074e2eeb956a8f5e1c23563e61abd8900d1 100644 (file)
@@ -4,6 +4,7 @@
  *
  * Copyright 2009      Luis R. Rodriguez <lrodriguez@atheros.com>
  * Copyright 2007      Johannes Berg <johannes@sipsolutions.net>
+ * Copyright (C) 2023 Intel Corporation
  */
 
 #include <linux/slab.h>
@@ -109,3 +110,162 @@ void cfg80211_debugfs_rdev_add(struct cfg80211_registered_device *rdev)
        DEBUGFS_ADD(long_retry_limit);
        DEBUGFS_ADD(ht40allow_map);
 }
+
+struct debugfs_read_work {
+       struct wiphy_work work;
+       ssize_t (*handler)(struct wiphy *wiphy,
+                          struct file *file,
+                          char *buf,
+                          size_t count,
+                          void *data);
+       struct wiphy *wiphy;
+       struct file *file;
+       char *buf;
+       size_t bufsize;
+       void *data;
+       ssize_t ret;
+       struct completion completion;
+};
+
+static void wiphy_locked_debugfs_read_work(struct wiphy *wiphy,
+                                          struct wiphy_work *work)
+{
+       struct debugfs_read_work *w = container_of(work, typeof(*w), work);
+
+       w->ret = w->handler(w->wiphy, w->file, w->buf, w->bufsize, w->data);
+       complete(&w->completion);
+}
+
+static void wiphy_locked_debugfs_read_cancel(struct dentry *dentry,
+                                            void *data)
+{
+       struct debugfs_read_work *w = data;
+
+       wiphy_work_cancel(w->wiphy, &w->work);
+       complete(&w->completion);
+}
+
+ssize_t wiphy_locked_debugfs_read(struct wiphy *wiphy, struct file *file,
+                                 char *buf, size_t bufsize,
+                                 char __user *userbuf, size_t count,
+                                 loff_t *ppos,
+                                 ssize_t (*handler)(struct wiphy *wiphy,
+                                                    struct file *file,
+                                                    char *buf,
+                                                    size_t bufsize,
+                                                    void *data),
+                                 void *data)
+{
+       struct debugfs_read_work work = {
+               .handler = handler,
+               .wiphy = wiphy,
+               .file = file,
+               .buf = buf,
+               .bufsize = bufsize,
+               .data = data,
+               .ret = -ENODEV,
+               .completion = COMPLETION_INITIALIZER_ONSTACK(work.completion),
+       };
+       struct debugfs_cancellation cancellation = {
+               .cancel = wiphy_locked_debugfs_read_cancel,
+               .cancel_data = &work,
+       };
+
+       /* don't leak stack data or whatever */
+       memset(buf, 0, bufsize);
+
+       wiphy_work_init(&work.work, wiphy_locked_debugfs_read_work);
+       wiphy_work_queue(wiphy, &work.work);
+
+       debugfs_enter_cancellation(file, &cancellation);
+       wait_for_completion(&work.completion);
+       debugfs_leave_cancellation(file, &cancellation);
+
+       if (work.ret < 0)
+               return work.ret;
+
+       if (WARN_ON(work.ret > bufsize))
+               return -EINVAL;
+
+       return simple_read_from_buffer(userbuf, count, ppos, buf, work.ret);
+}
+EXPORT_SYMBOL_GPL(wiphy_locked_debugfs_read);
+
+struct debugfs_write_work {
+       struct wiphy_work work;
+       ssize_t (*handler)(struct wiphy *wiphy,
+                          struct file *file,
+                          char *buf,
+                          size_t count,
+                          void *data);
+       struct wiphy *wiphy;
+       struct file *file;
+       char *buf;
+       size_t count;
+       void *data;
+       ssize_t ret;
+       struct completion completion;
+};
+
+static void wiphy_locked_debugfs_write_work(struct wiphy *wiphy,
+                                           struct wiphy_work *work)
+{
+       struct debugfs_write_work *w = container_of(work, typeof(*w), work);
+
+       w->ret = w->handler(w->wiphy, w->file, w->buf, w->count, w->data);
+       complete(&w->completion);
+}
+
+static void wiphy_locked_debugfs_write_cancel(struct dentry *dentry,
+                                             void *data)
+{
+       struct debugfs_write_work *w = data;
+
+       wiphy_work_cancel(w->wiphy, &w->work);
+       complete(&w->completion);
+}
+
+ssize_t wiphy_locked_debugfs_write(struct wiphy *wiphy,
+                                  struct file *file, char *buf, size_t bufsize,
+                                  const char __user *userbuf, size_t count,
+                                  ssize_t (*handler)(struct wiphy *wiphy,
+                                                     struct file *file,
+                                                     char *buf,
+                                                     size_t count,
+                                                     void *data),
+                                  void *data)
+{
+       struct debugfs_write_work work = {
+               .handler = handler,
+               .wiphy = wiphy,
+               .file = file,
+               .buf = buf,
+               .count = count,
+               .data = data,
+               .ret = -ENODEV,
+               .completion = COMPLETION_INITIALIZER_ONSTACK(work.completion),
+       };
+       struct debugfs_cancellation cancellation = {
+               .cancel = wiphy_locked_debugfs_write_cancel,
+               .cancel_data = &work,
+       };
+
+       /* mostly used for strings so enforce NUL-termination for safety */
+       if (count >= bufsize)
+               return -EINVAL;
+
+       memset(buf, 0, bufsize);
+
+       if (copy_from_user(buf, userbuf, count))
+               return -EFAULT;
+
+       wiphy_work_init(&work.work, wiphy_locked_debugfs_write_work);
+       wiphy_work_queue(wiphy, &work.work);
+
+       debugfs_enter_cancellation(file, &cancellation);
+       wait_for_completion(&work.completion);
+       debugfs_leave_cancellation(file, &cancellation);
+
+       return work.ret;
+}
+EXPORT_SYMBOL_GPL(wiphy_locked_debugfs_write);
index 569234bc2be6ae5e94fd248a9162941fd0bbdf17..1cbbb11ea5033fdeb0bfe1b8c1e796bfb748ec86 100644 (file)
@@ -3822,6 +3822,8 @@ static int nl80211_send_iface(struct sk_buff *msg, u32 portid, u32 seq, int flag
        struct net_device *dev = wdev->netdev;
        void *hdr;
 
+       lockdep_assert_wiphy(&rdev->wiphy);
+
        WARN_ON(cmd != NL80211_CMD_NEW_INTERFACE &&
                cmd != NL80211_CMD_DEL_INTERFACE &&
                cmd != NL80211_CMD_SET_INTERFACE);
@@ -3989,6 +3991,7 @@ static int nl80211_dump_interface(struct sk_buff *skb, struct netlink_callback *
 
                if_idx = 0;
 
+               wiphy_lock(&rdev->wiphy);
                list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
                        if (if_idx < if_start) {
                                if_idx++;
@@ -3998,10 +4001,12 @@ static int nl80211_dump_interface(struct sk_buff *skb, struct netlink_callback *
                                               cb->nlh->nlmsg_seq, NLM_F_MULTI,
                                               rdev, wdev,
                                               NL80211_CMD_NEW_INTERFACE) < 0) {
+                               wiphy_unlock(&rdev->wiphy);
                                goto out;
                        }
                        if_idx++;
                }
+               wiphy_unlock(&rdev->wiphy);
 
                wp_idx++;
        }
@@ -12787,10 +12792,6 @@ static int cfg80211_cqm_rssi_update(struct cfg80211_registered_device *rdev,
        int i, n, low_index;
        int err;
 
-       /* RSSI reporting disabled? */
-       if (!cqm_config)
-               return rdev_set_cqm_rssi_range_config(rdev, dev, 0, 0);
-
        /*
         * Obtain current RSSI value if possible, if not and no RSSI threshold
         * event has been received yet, we should receive an event after a
@@ -12865,23 +12866,25 @@ static int nl80211_set_cqm_rssi(struct genl_info *info,
            wdev->iftype != NL80211_IFTYPE_P2P_CLIENT)
                return -EOPNOTSUPP;
 
-       if (n_thresholds <= 1 && rdev->ops->set_cqm_rssi_config) {
-               if (n_thresholds == 0 || thresholds[0] == 0) /* Disabling */
-                       return rdev_set_cqm_rssi_config(rdev, dev, 0, 0);
-
-               return rdev_set_cqm_rssi_config(rdev, dev,
-                                               thresholds[0], hysteresis);
-       }
-
-       if (!wiphy_ext_feature_isset(&rdev->wiphy,
-                                    NL80211_EXT_FEATURE_CQM_RSSI_LIST))
-               return -EOPNOTSUPP;
-
        if (n_thresholds == 1 && thresholds[0] == 0) /* Disabling */
                n_thresholds = 0;
 
        old = wiphy_dereference(wdev->wiphy, wdev->cqm_config);
 
+       /* if already disabled just succeed */
+       if (!n_thresholds && !old)
+               return 0;
+
+       if (n_thresholds > 1) {
+               if (!wiphy_ext_feature_isset(&rdev->wiphy,
+                                            NL80211_EXT_FEATURE_CQM_RSSI_LIST) ||
+                   !rdev->ops->set_cqm_rssi_range_config)
+                       return -EOPNOTSUPP;
+       } else {
+               if (!rdev->ops->set_cqm_rssi_config)
+                       return -EOPNOTSUPP;
+       }
+
        if (n_thresholds) {
                cqm_config = kzalloc(struct_size(cqm_config, rssi_thresholds,
                                                 n_thresholds),
@@ -12894,13 +12897,26 @@ static int nl80211_set_cqm_rssi(struct genl_info *info,
                memcpy(cqm_config->rssi_thresholds, thresholds,
                       flex_array_size(cqm_config, rssi_thresholds,
                                       n_thresholds));
+               cqm_config->use_range_api = n_thresholds > 1 ||
+                                           !rdev->ops->set_cqm_rssi_config;
 
                rcu_assign_pointer(wdev->cqm_config, cqm_config);
+
+               if (cqm_config->use_range_api)
+                       err = cfg80211_cqm_rssi_update(rdev, dev, cqm_config);
+               else
+                       err = rdev_set_cqm_rssi_config(rdev, dev,
+                                                      thresholds[0],
+                                                      hysteresis);
        } else {
                RCU_INIT_POINTER(wdev->cqm_config, NULL);
+               /* if enabled as range also disable via range */
+               if (old->use_range_api)
+                       err = rdev_set_cqm_rssi_range_config(rdev, dev, 0, 0);
+               else
+                       err = rdev_set_cqm_rssi_config(rdev, dev, 0, 0);
        }
 
-       err = cfg80211_cqm_rssi_update(rdev, dev, cqm_config);
        if (err) {
                rcu_assign_pointer(wdev->cqm_config, old);
                kfree_rcu(cqm_config, rcu_head);
@@ -19009,10 +19025,11 @@ void cfg80211_cqm_rssi_notify_work(struct wiphy *wiphy, struct wiphy_work *work)
        s32 rssi_level;
 
        cqm_config = wiphy_dereference(wdev->wiphy, wdev->cqm_config);
-       if (!wdev->cqm_config)
+       if (!cqm_config)
                return;
 
-       cfg80211_cqm_rssi_update(rdev, wdev->netdev, cqm_config);
+       if (cqm_config->use_range_api)
+               cfg80211_cqm_rssi_update(rdev, wdev->netdev, cqm_config);
 
        rssi_level = cqm_config->last_rssi_event_value;
        rssi_event = cqm_config->last_rssi_event_type;