Commit 300fbb24 authored by Jakub Kicinski's avatar Jakub Kicinski

Merge tag 'wireless-2023-11-29' of git://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless

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.netSigned-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents 0d47fa5c 4ded3bfe
...@@ -57,8 +57,7 @@ config ATH9K_AHB ...@@ -57,8 +57,7 @@ config ATH9K_AHB
config ATH9K_DEBUGFS config ATH9K_DEBUGFS
bool "Atheros ath9k debugging" bool "Atheros ath9k debugging"
depends on ATH9K && DEBUG_FS depends on ATH9K && DEBUG_FS && MAC80211_DEBUGFS
select MAC80211_DEBUGFS
select ATH9K_COMMON_DEBUG select ATH9K_COMMON_DEBUG
help help
Say Y, if you need access to ath9k's statistics for Say Y, if you need access to ath9k's statistics for
...@@ -70,7 +69,6 @@ config ATH9K_DEBUGFS ...@@ -70,7 +69,6 @@ config ATH9K_DEBUGFS
config ATH9K_STATION_STATISTICS config ATH9K_STATION_STATISTICS
bool "Detailed station statistics" bool "Detailed station statistics"
depends on ATH9K && ATH9K_DEBUGFS && DEBUG_FS depends on ATH9K && ATH9K_DEBUGFS && DEBUG_FS
select MAC80211_DEBUGFS
default n default n
help help
This option enables detailed statistics for association stations. This option enables detailed statistics for association stations.
......
...@@ -707,8 +707,10 @@ int iwl_mvm_mld_add_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif, ...@@ -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], rcu_dereference_protected(mvm_sta->link[link_id],
lockdep_is_held(&mvm->mutex)); 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; goto err;
}
ret = iwl_mvm_mld_cfg_sta(mvm, sta, vif, link_sta, link_conf, ret = iwl_mvm_mld_cfg_sta(mvm, sta, vif, link_sta, link_conf,
mvm_link_sta); mvm_link_sta);
......
...@@ -375,6 +375,7 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name) ...@@ -375,6 +375,7 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name)
int ret, i, len, offset = 0; int ret, i, len, offset = 0;
u8 *clc_base = NULL, hw_encap = 0; u8 *clc_base = NULL, hw_encap = 0;
dev->phy.clc_chan_conf = 0xff;
if (mt7921_disable_clc || if (mt7921_disable_clc ||
mt76_is_usb(&dev->mt76)) mt76_is_usb(&dev->mt76))
return 0; return 0;
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
static void static void
mt7925_init_he_caps(struct mt792x_phy *phy, enum nl80211_band band, mt7925_init_he_caps(struct mt792x_phy *phy, enum nl80211_band band,
struct ieee80211_sband_iftype_data *data, 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_sta_he_cap *he_cap = &data->he_cap;
struct ieee80211_he_cap_elem *he_cap_elem = &he_cap->he_cap_elem; 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, ...@@ -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_FULL_MU_MIMO |
IEEE80211_HE_PHY_CAP2_UL_MU_PARTIAL_MU_MIMO; IEEE80211_HE_PHY_CAP2_UL_MU_PARTIAL_MU_MIMO;
switch (i) { switch (iftype) {
case NL80211_IFTYPE_AP: case NL80211_IFTYPE_AP:
he_cap_elem->mac_cap_info[2] |= he_cap_elem->mac_cap_info[2] |=
IEEE80211_HE_MAC_CAP2_BSR; IEEE80211_HE_MAC_CAP2_BSR;
......
...@@ -84,6 +84,14 @@ int debugfs_file_get(struct dentry *dentry) ...@@ -84,6 +84,14 @@ int debugfs_file_get(struct dentry *dentry)
struct debugfs_fsdata *fsd; struct debugfs_fsdata *fsd;
void *d_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); d_fsd = READ_ONCE(dentry->d_fsdata);
if (!((unsigned long)d_fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)) { if (!((unsigned long)d_fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)) {
fsd = d_fsd; fsd = d_fsd;
...@@ -100,6 +108,14 @@ int debugfs_file_get(struct dentry *dentry) ...@@ -100,6 +108,14 @@ int debugfs_file_get(struct dentry *dentry)
kfree(fsd); kfree(fsd);
fsd = READ_ONCE(dentry->d_fsdata); 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) ...@@ -116,6 +132,8 @@ int debugfs_file_get(struct dentry *dentry)
if (!refcount_inc_not_zero(&fsd->active_users)) if (!refcount_inc_not_zero(&fsd->active_users))
return -EIO; return -EIO;
lock_map_acquire_read(&fsd->lockdep_map);
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(debugfs_file_get); EXPORT_SYMBOL_GPL(debugfs_file_get);
...@@ -133,11 +151,93 @@ void debugfs_file_put(struct dentry *dentry) ...@@ -133,11 +151,93 @@ void debugfs_file_put(struct dentry *dentry)
{ {
struct debugfs_fsdata *fsd = READ_ONCE(dentry->d_fsdata); struct debugfs_fsdata *fsd = READ_ONCE(dentry->d_fsdata);
lock_map_release(&fsd->lockdep_map);
if (refcount_dec_and_test(&fsd->active_users)) if (refcount_dec_and_test(&fsd->active_users))
complete(&fsd->active_users_drained); complete(&fsd->active_users_drained);
} }
EXPORT_SYMBOL_GPL(debugfs_file_put); 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. * 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 * We also need to exclude any file that has ways to write or alter it as root
......
...@@ -236,17 +236,29 @@ static const struct super_operations debugfs_super_operations = { ...@@ -236,17 +236,29 @@ static const struct super_operations debugfs_super_operations = {
static void debugfs_release_dentry(struct dentry *dentry) 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)) if ((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)
kfree(dentry->d_fsdata); 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) static struct vfsmount *debugfs_automount(struct path *path)
{ {
debugfs_automount_t f; struct debugfs_fsdata *fsd = path->dentry->d_fsdata;
f = (debugfs_automount_t)path->dentry->d_fsdata;
return f(path->dentry, d_inode(path->dentry)->i_private); return fsd->automount(path->dentry, d_inode(path->dentry)->i_private);
} }
static const struct dentry_operations debugfs_dops = { static const struct dentry_operations debugfs_dops = {
...@@ -634,13 +646,23 @@ struct dentry *debugfs_create_automount(const char *name, ...@@ -634,13 +646,23 @@ struct dentry *debugfs_create_automount(const char *name,
void *data) void *data)
{ {
struct dentry *dentry = start_creating(name, parent); struct dentry *dentry = start_creating(name, parent);
struct debugfs_fsdata *fsd;
struct inode *inode; struct inode *inode;
if (IS_ERR(dentry)) if (IS_ERR(dentry))
return 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)) { if (!(debugfs_allow & DEBUGFS_ALLOW_API)) {
failed_creating(dentry); failed_creating(dentry);
kfree(fsd);
return ERR_PTR(-EPERM); return ERR_PTR(-EPERM);
} }
...@@ -648,13 +670,14 @@ struct dentry *debugfs_create_automount(const char *name, ...@@ -648,13 +670,14 @@ struct dentry *debugfs_create_automount(const char *name,
if (unlikely(!inode)) { if (unlikely(!inode)) {
pr_err("out of free dentries, can not create automount '%s'\n", pr_err("out of free dentries, can not create automount '%s'\n",
name); name);
kfree(fsd);
return failed_creating(dentry); return failed_creating(dentry);
} }
make_empty_dir_inode(inode); make_empty_dir_inode(inode);
inode->i_flags |= S_AUTOMOUNT; inode->i_flags |= S_AUTOMOUNT;
inode->i_private = data; inode->i_private = data;
dentry->d_fsdata = (void *)f; dentry->d_fsdata = fsd;
/* directory inodes start off with i_nlink == 2 (for "." entry) */ /* directory inodes start off with i_nlink == 2 (for "." entry) */
inc_nlink(inode); inc_nlink(inode);
d_instantiate(dentry, inode); d_instantiate(dentry, inode);
...@@ -731,8 +754,40 @@ static void __debugfs_file_removed(struct dentry *dentry) ...@@ -731,8 +754,40 @@ static void __debugfs_file_removed(struct dentry *dentry)
fsd = READ_ONCE(dentry->d_fsdata); fsd = READ_ONCE(dentry->d_fsdata);
if ((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT) if ((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)
return; 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); wait_for_completion(&fsd->active_users_drained);
}
} }
static void remove_one(struct dentry *victim) static void remove_one(struct dentry *victim)
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
#ifndef _DEBUGFS_INTERNAL_H_ #ifndef _DEBUGFS_INTERNAL_H_
#define _DEBUGFS_INTERNAL_H_ #define _DEBUGFS_INTERNAL_H_
#include <linux/lockdep.h>
#include <linux/list.h>
struct file_operations; struct file_operations;
...@@ -17,8 +19,23 @@ extern const struct file_operations debugfs_full_proxy_file_operations; ...@@ -17,8 +19,23 @@ extern const struct file_operations debugfs_full_proxy_file_operations;
struct debugfs_fsdata { struct debugfs_fsdata {
const struct file_operations *real_fops; const struct file_operations *real_fops;
refcount_t active_users; union {
struct completion active_users_drained; /* 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;
};
};
}; };
/* /*
......
...@@ -171,6 +171,25 @@ ssize_t debugfs_write_file_bool(struct file *file, const char __user *user_buf, ...@@ -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, ssize_t debugfs_read_file_str(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos); 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 #else
#include <linux/err.h> #include <linux/err.h>
......
...@@ -2830,12 +2830,14 @@ ieee80211_he_oper_size(const u8 *he_oper_ie) ...@@ -2830,12 +2830,14 @@ ieee80211_he_oper_size(const u8 *he_oper_ie)
static inline const struct ieee80211_he_6ghz_oper * static inline const struct ieee80211_he_6ghz_oper *
ieee80211_he_6ghz_oper(const struct ieee80211_he_operation *he_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; u32 he_oper_params;
if (!he_oper) if (!he_oper)
return NULL; return NULL;
ret = (const void *)&he_oper->optional;
he_oper_params = le32_to_cpu(he_oper->he_oper_params); he_oper_params = le32_to_cpu(he_oper->he_oper_params);
if (!(he_oper_params & IEEE80211_HE_OPERATION_6GHZ_OP_INFO)) if (!(he_oper_params & IEEE80211_HE_OPERATION_6GHZ_OP_INFO))
......
...@@ -9299,4 +9299,50 @@ bool cfg80211_valid_disable_subchannel_bitmap(u16 *bitmap, ...@@ -9299,4 +9299,50 @@ bool cfg80211_valid_disable_subchannel_bitmap(u16 *bitmap,
*/ */
void cfg80211_links_removed(struct net_device *dev, u16 link_mask); 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 */ #endif /* __NET_CFG80211_H */
...@@ -88,7 +88,7 @@ config MAC80211_LEDS ...@@ -88,7 +88,7 @@ config MAC80211_LEDS
config MAC80211_DEBUGFS config MAC80211_DEBUGFS
bool "Export mac80211 internals in DebugFS" bool "Export mac80211 internals in DebugFS"
depends on MAC80211 && DEBUG_FS depends on MAC80211 && CFG80211_DEBUGFS
help help
Select this to see extensive information about Select this to see extensive information about
the internal state of mac80211 in debugfs. the internal state of mac80211 in debugfs.
......
...@@ -22,88 +22,148 @@ ...@@ -22,88 +22,148 @@
#include "debugfs_netdev.h" #include "debugfs_netdev.h"
#include "driver-ops.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( static ssize_t ieee80211_if_read_sdata(
struct ieee80211_sub_if_data *sdata, struct file *file,
char __user *userbuf, char __user *userbuf,
size_t count, loff_t *ppos, size_t count, loff_t *ppos,
ssize_t (*format)(const struct ieee80211_sub_if_data *sdata, char *, int)) 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]; char buf[200];
ssize_t ret = -EINVAL;
wiphy_lock(sdata->local->hw.wiphy); return wiphy_locked_debugfs_read(sdata->local->hw.wiphy,
ret = (*format)(sdata, buf, sizeof(buf)); file, buf, sizeof(buf),
wiphy_unlock(sdata->local->hw.wiphy); userbuf, count, ppos,
ieee80211_if_read_sdata_handler,
&data);
}
if (ret >= 0) struct ieee80211_if_write_sdata_data {
ret = simple_read_from_buffer(userbuf, count, ppos, buf, ret); 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( static ssize_t ieee80211_if_write_sdata(
struct ieee80211_sub_if_data *sdata, struct file *file,
const char __user *userbuf, const char __user *userbuf,
size_t count, loff_t *ppos, size_t count, loff_t *ppos,
ssize_t (*write)(struct ieee80211_sub_if_data *sdata, const char *, int)) 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]; char buf[64];
ssize_t ret;
if (count >= sizeof(buf)) return wiphy_locked_debugfs_write(sdata->local->hw.wiphy,
return -E2BIG; file, buf, sizeof(buf),
userbuf, count,
ieee80211_if_write_sdata_handler,
&data);
}
if (copy_from_user(buf, userbuf, count)) struct ieee80211_if_read_link_data {
return -EFAULT; ssize_t (*format)(const struct ieee80211_link_data *, char *, int);
buf[count] = '\0'; struct ieee80211_link_data *link;
};
wiphy_lock(sdata->local->hw.wiphy); static ssize_t ieee80211_if_read_link_handler(struct wiphy *wiphy,
ret = (*write)(sdata, buf, count); struct file *file,
wiphy_unlock(sdata->local->hw.wiphy); 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( static ssize_t ieee80211_if_read_link(
struct ieee80211_link_data *link, struct file *file,
char __user *userbuf, char __user *userbuf,
size_t count, loff_t *ppos, size_t count, loff_t *ppos,
ssize_t (*format)(const struct ieee80211_link_data *link, char *, int)) 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]; char buf[200];
ssize_t ret = -EINVAL;
wiphy_lock(link->sdata->local->hw.wiphy); return wiphy_locked_debugfs_read(link->sdata->local->hw.wiphy,
ret = (*format)(link, buf, sizeof(buf)); file, buf, sizeof(buf),
wiphy_unlock(link->sdata->local->hw.wiphy); 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) static ssize_t ieee80211_if_write_link_handler(struct wiphy *wiphy,
ret = simple_read_from_buffer(userbuf, count, ppos, buf, ret); 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( static ssize_t ieee80211_if_write_link(
struct ieee80211_link_data *link, struct file *file,
const char __user *userbuf, const char __user *userbuf,
size_t count, loff_t *ppos, size_t count, loff_t *ppos,
ssize_t (*write)(struct ieee80211_link_data *link, const char *, int)) 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]; 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) \ #define IEEE80211_IF_FMT(name, type, field, format_string) \
...@@ -173,7 +233,7 @@ static ssize_t ieee80211_if_read_##name(struct file *file, \ ...@@ -173,7 +233,7 @@ static ssize_t ieee80211_if_read_##name(struct file *file, \
char __user *userbuf, \ char __user *userbuf, \
size_t count, loff_t *ppos) \ size_t count, loff_t *ppos) \
{ \ { \
return ieee80211_if_read_sdata(file->private_data, \ return ieee80211_if_read_sdata(file, \
userbuf, count, ppos, \ userbuf, count, ppos, \
ieee80211_if_fmt_##name); \ ieee80211_if_fmt_##name); \
} }
...@@ -183,7 +243,7 @@ static ssize_t ieee80211_if_write_##name(struct file *file, \ ...@@ -183,7 +243,7 @@ static ssize_t ieee80211_if_write_##name(struct file *file, \
const char __user *userbuf, \ const char __user *userbuf, \
size_t count, loff_t *ppos) \ size_t count, loff_t *ppos) \
{ \ { \
return ieee80211_if_write_sdata(file->private_data, userbuf, \ return ieee80211_if_write_sdata(file, userbuf, \
count, ppos, \ count, ppos, \
ieee80211_if_parse_##name); \ ieee80211_if_parse_##name); \
} }
...@@ -211,7 +271,7 @@ static ssize_t ieee80211_if_read_##name(struct file *file, \ ...@@ -211,7 +271,7 @@ static ssize_t ieee80211_if_read_##name(struct file *file, \
char __user *userbuf, \ char __user *userbuf, \
size_t count, loff_t *ppos) \ size_t count, loff_t *ppos) \
{ \ { \
return ieee80211_if_read_link(file->private_data, \ return ieee80211_if_read_link(file, \
userbuf, count, ppos, \ userbuf, count, ppos, \
ieee80211_if_fmt_##name); \ ieee80211_if_fmt_##name); \
} }
...@@ -221,7 +281,7 @@ static ssize_t ieee80211_if_write_##name(struct file *file, \ ...@@ -221,7 +281,7 @@ static ssize_t ieee80211_if_write_##name(struct file *file, \
const char __user *userbuf, \ const char __user *userbuf, \
size_t count, loff_t *ppos) \ size_t count, loff_t *ppos) \
{ \ { \
return ieee80211_if_write_link(file->private_data, userbuf, \ return ieee80211_if_write_link(file, userbuf, \
count, ppos, \ count, ppos, \
ieee80211_if_parse_##name); \ ieee80211_if_parse_##name); \
} }
......
...@@ -312,23 +312,14 @@ static ssize_t sta_aql_write(struct file *file, const char __user *userbuf, ...@@ -312,23 +312,14 @@ static ssize_t sta_aql_write(struct file *file, const char __user *userbuf,
STA_OPS_RW(aql); STA_OPS_RW(aql);
static ssize_t sta_agg_status_read(struct file *file, char __user *userbuf, static ssize_t sta_agg_status_do_read(struct wiphy *wiphy, struct file *file,
size_t count, loff_t *ppos) char *buf, size_t bufsz, void *data)
{ {
char *buf, *p; struct sta_info *sta = data;
ssize_t bufsz = 71 + IEEE80211_NUM_TIDS * 40; char *p = buf;
int i; int i;
struct sta_info *sta = file->private_data;
struct tid_ampdu_rx *tid_rx; struct tid_ampdu_rx *tid_rx;
struct tid_ampdu_tx *tid_tx; 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", p += scnprintf(p, bufsz + buf - p, "next dialog_token: %#02x\n",
sta->ampdu_mlme.dialog_token_allocator + 1); sta->ampdu_mlme.dialog_token_allocator + 1);
...@@ -338,8 +329,8 @@ static ssize_t sta_agg_status_read(struct file *file, char __user *userbuf, ...@@ -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++) { for (i = 0; i < IEEE80211_NUM_TIDS; i++) {
bool tid_rx_valid; bool tid_rx_valid;
tid_rx = rcu_dereference(sta->ampdu_mlme.tid_rx[i]); tid_rx = wiphy_dereference(wiphy, sta->ampdu_mlme.tid_rx[i]);
tid_tx = rcu_dereference(sta->ampdu_mlme.tid_tx[i]); tid_tx = wiphy_dereference(wiphy, sta->ampdu_mlme.tid_tx[i]);
tid_rx_valid = test_bit(i, sta->ampdu_mlme.agg_session_valid); tid_rx_valid = test_bit(i, sta->ampdu_mlme.agg_session_valid);
p += scnprintf(p, bufsz + buf - p, "%02d", i); 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, ...@@ -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); tid_tx ? skb_queue_len(&tid_tx->pending) : 0);
p += scnprintf(p, bufsz + buf - p, "\n"); 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); kfree(buf);
return ret; return ret;
} }
static ssize_t sta_agg_status_write(struct file *file, const char __user *userbuf, static ssize_t sta_agg_status_do_write(struct wiphy *wiphy, struct file *file,
size_t count, loff_t *ppos) char *buf, size_t count, void *data)
{ {
char _buf[25] = {}, *buf = _buf; struct sta_info *sta = data;
struct sta_info *sta = file->private_data;
bool start, tx; bool start, tx;
unsigned long tid; unsigned long tid;
char *pos; char *pos = buf;
int ret, timeout = 5000; 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, " "); buf = strsep(&pos, " ");
if (!buf) if (!buf)
return -EINVAL; return -EINVAL;
...@@ -420,7 +419,6 @@ static ssize_t sta_agg_status_write(struct file *file, const char __user *userbu ...@@ -420,7 +419,6 @@ static ssize_t sta_agg_status_write(struct file *file, const char __user *userbu
if (ret || tid >= IEEE80211_NUM_TIDS) if (ret || tid >= IEEE80211_NUM_TIDS)
return -EINVAL; return -EINVAL;
wiphy_lock(sta->local->hw.wiphy);
if (tx) { if (tx) {
if (start) if (start)
ret = ieee80211_start_tx_ba_session(&sta->sta, tid, 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 ...@@ -432,10 +430,22 @@ static ssize_t sta_agg_status_write(struct file *file, const char __user *userbu
3, true); 3, true);
ret = 0; ret = 0;
} }
wiphy_unlock(sta->local->hw.wiphy);
return ret ?: count; 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); STA_OPS_RW(agg_status);
/* link sta attributes */ /* link sta attributes */
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
static inline struct ieee80211_sub_if_data * static inline struct ieee80211_sub_if_data *
get_bss_sdata(struct ieee80211_sub_if_data *sdata) 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, sdata = container_of(sdata->bss, struct ieee80211_sub_if_data,
u.ap); u.ap);
...@@ -695,11 +695,14 @@ static inline void drv_flush(struct ieee80211_local *local, ...@@ -695,11 +695,14 @@ static inline void drv_flush(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata, struct ieee80211_sub_if_data *sdata,
u32 queues, bool drop) u32 queues, bool drop)
{ {
struct ieee80211_vif *vif = sdata ? &sdata->vif : NULL; struct ieee80211_vif *vif;
might_sleep(); might_sleep();
lockdep_assert_wiphy(local->hw.wiphy); lockdep_assert_wiphy(local->hw.wiphy);
sdata = get_bss_sdata(sdata);
vif = sdata ? &sdata->vif : NULL;
if (sdata && !check_sdata_in_driver(sdata)) if (sdata && !check_sdata_in_driver(sdata))
return; return;
...@@ -716,6 +719,8 @@ static inline void drv_flush_sta(struct ieee80211_local *local, ...@@ -716,6 +719,8 @@ static inline void drv_flush_sta(struct ieee80211_local *local,
might_sleep(); might_sleep();
lockdep_assert_wiphy(local->hw.wiphy); lockdep_assert_wiphy(local->hw.wiphy);
sdata = get_bss_sdata(sdata);
if (sdata && !check_sdata_in_driver(sdata)) if (sdata && !check_sdata_in_driver(sdata))
return; return;
......
...@@ -271,6 +271,7 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata, ...@@ -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_80:
case NL80211_CHAN_WIDTH_80P80: case NL80211_CHAN_WIDTH_80P80:
case NL80211_CHAN_WIDTH_160: case NL80211_CHAN_WIDTH_160:
case NL80211_CHAN_WIDTH_320:
bw = ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ? bw = ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20; IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20;
break; break;
......
...@@ -191,13 +191,13 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, ...@@ -191,13 +191,13 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev,
return err; return err;
} }
wiphy_lock(&rdev->wiphy);
list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
if (!wdev->netdev) if (!wdev->netdev)
continue; continue;
nl80211_notify_iface(rdev, wdev, NL80211_CMD_DEL_INTERFACE); nl80211_notify_iface(rdev, wdev, NL80211_CMD_DEL_INTERFACE);
} }
wiphy_lock(&rdev->wiphy);
nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY); nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY);
wiphy_net_set(&rdev->wiphy, net); wiphy_net_set(&rdev->wiphy, net);
...@@ -206,13 +206,13 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, ...@@ -206,13 +206,13 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev,
WARN_ON(err); WARN_ON(err);
nl80211_notify_wiphy(rdev, NL80211_CMD_NEW_WIPHY); nl80211_notify_wiphy(rdev, NL80211_CMD_NEW_WIPHY);
wiphy_unlock(&rdev->wiphy);
list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
if (!wdev->netdev) if (!wdev->netdev)
continue; continue;
nl80211_notify_iface(rdev, wdev, NL80211_CMD_NEW_INTERFACE); nl80211_notify_iface(rdev, wdev, NL80211_CMD_NEW_INTERFACE);
} }
wiphy_unlock(&rdev->wiphy);
return 0; return 0;
} }
...@@ -221,7 +221,9 @@ static void cfg80211_rfkill_poll(struct rfkill *rfkill, void *data) ...@@ -221,7 +221,9 @@ static void cfg80211_rfkill_poll(struct rfkill *rfkill, void *data)
{ {
struct cfg80211_registered_device *rdev = data; struct cfg80211_registered_device *rdev = data;
wiphy_lock(&rdev->wiphy);
rdev_rfkill_poll(rdev); rdev_rfkill_poll(rdev);
wiphy_unlock(&rdev->wiphy);
} }
void cfg80211_stop_p2p_device(struct cfg80211_registered_device *rdev, void cfg80211_stop_p2p_device(struct cfg80211_registered_device *rdev,
......
...@@ -293,6 +293,7 @@ struct cfg80211_cqm_config { ...@@ -293,6 +293,7 @@ struct cfg80211_cqm_config {
u32 rssi_hyst; u32 rssi_hyst;
s32 last_rssi_event_value; s32 last_rssi_event_value;
enum nl80211_cqm_rssi_threshold_event last_rssi_event_type; enum nl80211_cqm_rssi_threshold_event last_rssi_event_type;
bool use_range_api;
int n_rssi_thresholds; int n_rssi_thresholds;
s32 rssi_thresholds[] __counted_by(n_rssi_thresholds); s32 rssi_thresholds[] __counted_by(n_rssi_thresholds);
}; };
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
* *
* Copyright 2009 Luis R. Rodriguez <lrodriguez@atheros.com> * Copyright 2009 Luis R. Rodriguez <lrodriguez@atheros.com>
* Copyright 2007 Johannes Berg <johannes@sipsolutions.net> * Copyright 2007 Johannes Berg <johannes@sipsolutions.net>
* Copyright (C) 2023 Intel Corporation
*/ */
#include <linux/slab.h> #include <linux/slab.h>
...@@ -109,3 +110,162 @@ void cfg80211_debugfs_rdev_add(struct cfg80211_registered_device *rdev) ...@@ -109,3 +110,162 @@ void cfg80211_debugfs_rdev_add(struct cfg80211_registered_device *rdev)
DEBUGFS_ADD(long_retry_limit); DEBUGFS_ADD(long_retry_limit);
DEBUGFS_ADD(ht40allow_map); 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);
...@@ -3822,6 +3822,8 @@ static int nl80211_send_iface(struct sk_buff *msg, u32 portid, u32 seq, int flag ...@@ -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; struct net_device *dev = wdev->netdev;
void *hdr; void *hdr;
lockdep_assert_wiphy(&rdev->wiphy);
WARN_ON(cmd != NL80211_CMD_NEW_INTERFACE && WARN_ON(cmd != NL80211_CMD_NEW_INTERFACE &&
cmd != NL80211_CMD_DEL_INTERFACE && cmd != NL80211_CMD_DEL_INTERFACE &&
cmd != NL80211_CMD_SET_INTERFACE); cmd != NL80211_CMD_SET_INTERFACE);
...@@ -3989,6 +3991,7 @@ static int nl80211_dump_interface(struct sk_buff *skb, struct netlink_callback * ...@@ -3989,6 +3991,7 @@ static int nl80211_dump_interface(struct sk_buff *skb, struct netlink_callback *
if_idx = 0; if_idx = 0;
wiphy_lock(&rdev->wiphy);
list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
if (if_idx < if_start) { if (if_idx < if_start) {
if_idx++; if_idx++;
...@@ -3998,10 +4001,12 @@ static int nl80211_dump_interface(struct sk_buff *skb, struct netlink_callback * ...@@ -3998,10 +4001,12 @@ static int nl80211_dump_interface(struct sk_buff *skb, struct netlink_callback *
cb->nlh->nlmsg_seq, NLM_F_MULTI, cb->nlh->nlmsg_seq, NLM_F_MULTI,
rdev, wdev, rdev, wdev,
NL80211_CMD_NEW_INTERFACE) < 0) { NL80211_CMD_NEW_INTERFACE) < 0) {
wiphy_unlock(&rdev->wiphy);
goto out; goto out;
} }
if_idx++; if_idx++;
} }
wiphy_unlock(&rdev->wiphy);
wp_idx++; wp_idx++;
} }
...@@ -12787,10 +12792,6 @@ static int cfg80211_cqm_rssi_update(struct cfg80211_registered_device *rdev, ...@@ -12787,10 +12792,6 @@ static int cfg80211_cqm_rssi_update(struct cfg80211_registered_device *rdev,
int i, n, low_index; int i, n, low_index;
int err; 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 * Obtain current RSSI value if possible, if not and no RSSI threshold
* event has been received yet, we should receive an event after a * 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, ...@@ -12865,23 +12866,25 @@ static int nl80211_set_cqm_rssi(struct genl_info *info,
wdev->iftype != NL80211_IFTYPE_P2P_CLIENT) wdev->iftype != NL80211_IFTYPE_P2P_CLIENT)
return -EOPNOTSUPP; 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 */ if (n_thresholds == 1 && thresholds[0] == 0) /* Disabling */
n_thresholds = 0; n_thresholds = 0;
old = wiphy_dereference(wdev->wiphy, wdev->cqm_config); 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) { if (n_thresholds) {
cqm_config = kzalloc(struct_size(cqm_config, rssi_thresholds, cqm_config = kzalloc(struct_size(cqm_config, rssi_thresholds,
n_thresholds), n_thresholds),
...@@ -12894,13 +12897,26 @@ static int nl80211_set_cqm_rssi(struct genl_info *info, ...@@ -12894,13 +12897,26 @@ static int nl80211_set_cqm_rssi(struct genl_info *info,
memcpy(cqm_config->rssi_thresholds, thresholds, memcpy(cqm_config->rssi_thresholds, thresholds,
flex_array_size(cqm_config, rssi_thresholds, flex_array_size(cqm_config, rssi_thresholds,
n_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); 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 { } else {
RCU_INIT_POINTER(wdev->cqm_config, NULL); 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) { if (err) {
rcu_assign_pointer(wdev->cqm_config, old); rcu_assign_pointer(wdev->cqm_config, old);
kfree_rcu(cqm_config, rcu_head); kfree_rcu(cqm_config, rcu_head);
...@@ -19009,10 +19025,11 @@ void cfg80211_cqm_rssi_notify_work(struct wiphy *wiphy, struct wiphy_work *work) ...@@ -19009,10 +19025,11 @@ void cfg80211_cqm_rssi_notify_work(struct wiphy *wiphy, struct wiphy_work *work)
s32 rssi_level; s32 rssi_level;
cqm_config = wiphy_dereference(wdev->wiphy, wdev->cqm_config); cqm_config = wiphy_dereference(wdev->wiphy, wdev->cqm_config);
if (!wdev->cqm_config) if (!cqm_config)
return; 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_level = cqm_config->last_rssi_event_value;
rssi_event = cqm_config->last_rssi_event_type; rssi_event = cqm_config->last_rssi_event_type;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment