mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2024-12-29 17:22:07 +00:00
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 -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEpeA8sTs3M8SN2hR410qiO8sPaAAFAmVnUrkACgkQ10qiO8sP aACE4A/9EdhyexRlNKdF3yLh0Q4acuR8IlaLBNgGiQDFxx17djE04p2PTpwkxOyn 0EPy8dKhhWwoAkbvZ+6ToNFa+Jv9w+C5xVls2osGRuYGSVNlxCNy+8tWSNVT73jp rrapEkYHzuz9wBZiJpKwC+mK9uH9gAQgWyYUaTTeOnO/+m3HVhZdU6y71j8gQlm6 9YFSI3r/VWlq1JpThn8WGULJXOMICWN0Sp4XRhEzHPLjK7MiCLNrrQSyV+uMHHUI PmRQN8QW6Oomjbcih3YNn+Geps7xUJFEG4mZvM6GUBXugnIq9t9xmzEbEJyBRIpl MGTwrIAyxtvBeHMpB7U/R3RSEyHfSW6fXHgN2S8ZEFTu6v70gP3TK8lL1gWc5mR4 GogNaGQ0G6LdiHjM7XeGUv/SfzD6HJWa9aR1JbRwapkvxFfM7BEc14gxip0nFDLG z9sixiztGmzBsxmDc+h7M6YLlvWe4oqueUyjA7/p42NhYq41Zy/BYK5oUgsuL4ah Lsb00O6Mz9m6iPtJlREe81Qsclkjg8xMY2YcCYELXP3v50KwU3THBAOYbh5hvgrL T2gibkR0zFAGz1EDcPyQq/SBeUtASPVHyLGUMae6DOcVnOFdAhDTbX0B/TvcnRSq e1gvmNywL7wFVZLD/RYZtWW9g6NUcwK+CU59PRBh2OqVb0ow8Ls= =X6t2 -----END PGP SIGNATURE----- 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.net Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
300fbb247e
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -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>
|
||||
|
@ -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))
|
||||
|
@ -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 */
|
||||
|
@ -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.
|
||||
|
@ -22,88 +22,148 @@
|
||||
#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;
|
||||
};
|
||||
|
||||
return ret;
|
||||
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 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);
|
||||
}
|
||||
|
||||
if (ret >= 0)
|
||||
ret = simple_read_from_buffer(userbuf, count, ppos, buf, ret);
|
||||
struct ieee80211_if_write_link_data {
|
||||
ssize_t (*write)(struct ieee80211_link_data *, const char *, int);
|
||||
struct ieee80211_link_data *link;
|
||||
};
|
||||
|
||||
return 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 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); \
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user