// SPDX-License-Identifier: GPL-2.0-only
/*
* This is the linux wireless configuration interface.
*
* Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright 2015-2017 Intel Deutschland GmbH
* Copyright (C) 2018-2021 Intel Corporation
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/if.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/nl80211.h>
#include <linux/debugfs.h>
#include <linux/notifier.h>
#include <linux/device.h>
#include <linux/etherdevice.h>
#include <linux/rtnetlink.h>
#include <linux/sched.h>
#include <net/genetlink.h>
#include <net/cfg80211.h>
#include "nl80211.h"
#include "core.h"
#include "sysfs.h"
#include "debugfs.h"
#include "wext-compat.h"
#include "rdev-ops.h"
/* name for sysfs, %d is appended */
#define PHY_NAME "phy"
MODULE_AUTHOR("Johannes Berg");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("wireless configuration support");
MODULE_ALIAS_GENL_FAMILY(NL80211_GENL_NAME);
/* RCU-protected (and RTNL for writers) */
LIST_HEAD(cfg80211_rdev_list);
int cfg80211_rdev_list_generation;
/* for debugfs */
static struct dentry *ieee80211_debugfs_dir;
/* for the cleanup, scan and event works */
struct workqueue_struct *cfg80211_wq;
static bool cfg80211_disable_40mhz_24ghz;
module_param(cfg80211_disable_40mhz_24ghz, bool, 0644);
MODULE_PARM_DESC(cfg80211_disable_40mhz_24ghz,
"Disable 40MHz support in the 2.4GHz band");
struct cfg80211_registered_device *cfg80211_rdev_by_wiphy_idx(int wiphy_idx)
{
struct cfg80211_registered_device *result = NULL, *rdev;
ASSERT_RTNL();
list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
if (rdev->wiphy_idx == wiphy_idx) {
result = rdev;
break;
}
}
return result;
}
int get_wiphy_idx(struct wiphy *wiphy)
{
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
return rdev->wiphy_idx;
}
struct wiphy *wiphy_idx_to_wiphy(int wiphy_idx)
{
struct cfg80211_registered_device *rdev;
ASSERT_RTNL();
rdev = cfg80211_rdev_by_wiphy_idx(wiphy_idx);
if (!rdev)
return NULL;
return &rdev->wiphy;
}
static int cfg80211_dev_check_name(struct cfg80211_registered_device *rdev,
const char *newname)
{
struct cfg80211_registered_device *rdev2;
int wiphy_idx, taken = -1, digits;
ASSERT_RTNL();
if (strlen(newname) > NL80211_WIPHY_NAME_MAXLEN)
return -EINVAL;
/* prohibit calling the thing phy%d when %d is not its number */
sscanf(newname, PHY_NAME "%d%n", &wiphy_idx, &taken);
if (taken == strlen(newname) && wiphy_idx != rdev->wiphy_idx) {
/* count number of places needed to print wiphy_idx */
digits = 1;
while (wiphy_idx /= 10)
digits++;
/*
* deny the name if it is phy<idx> where <idx> is printed
* without leading zeroes. taken == strlen(newname) here
*/
if (taken == strlen(PHY_NAME) + digits)
return -EINVAL;
}
/* Ensure another device does not already have this name. */
list_for_each_entry(rdev2, &cfg80211_rdev_list, list)
if (strcmp(newname, wiphy_name(&rdev2->wiphy)) == 0)
return -EINVAL;
return 0;
}
int cfg80211_dev_rename(struct cfg80211_registered_device *rdev,
char *newname)
{
int result;
ASSERT_RTNL();
/* Ignore nop renames */
if (strcmp(newname, wiphy_name(&rdev->wiphy)) == 0)
return 0;
result = cfg80211_dev_check_name(rdev, newname);
if (result < 0)
return result;
result = device_rename(&rdev->wiphy.dev, newname);
if (result)
return result;
if (!IS_ERR_OR_NULL(rdev->wiphy.debugfsdir))
debugfs_rename(rdev->wiphy.debugfsdir->d_parent,
rdev->wiphy.debugfsdir,
rdev->wiphy.debugfsdir->d_parent, newname);
nl80211_notify_wiphy(rdev, NL80211_CMD_NEW_WIPHY);
return 0;
}
int cfg80211_switch_netns(struct cfg80211_registered_device *rdev,
struct net *net)
{
struct wireless_dev *wdev;
int err = 0;
if (!(rdev->wiphy.flags & WIPHY_FLAG_NETNS_OK))
return -EOPNOTSUPP;
list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
if (!wdev->netdev)
continue;
wdev->netdev->features &= ~NETIF_F_NETNS_LOCAL;
err = dev_change_net_namespace(wdev->netdev, net, "wlan%d");
if (err)
break;
wdev->netdev->features |= NETIF_F_NETNS_LOCAL;
}
if (err) {
/* failed -- clean up to old netns */
net = wiphy_net(&rdev->wiphy);
list_for_each_entry_continue_reverse(wdev,
&rdev->wiphy.wdev_list,
list) {
if (!wdev->netdev)
continue;
wdev->netdev->features &= ~NETIF_F_NETNS_LOCAL;
err = dev_change_net_namespace(wdev->netdev, net,
"wlan%d");
WARN_ON(err);
wdev->netdev->features |= NETIF_F_NETNS_LOCAL;
}
return err;
}
list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
if (!wdev->netdev)
continue;
nl80211_notify_iface(rdev, wdev, NL80211_CMD_DEL_INTERFACE);
}
nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY);
wiphy_net_set(&rdev->wiphy, net);
err = device_rename(&rdev->wiphy.dev, dev_name(&rdev->wiphy.dev));
WARN_ON(err);
nl80211_notify_wiphy(rdev, NL80211_CMD_NEW_WIPHY);
list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
if (!wdev->netdev)
continue;
nl80211_notify_iface(rdev, wdev, NL80211_CMD_NEW_INTERFACE);
}
return 0;
}
static void cfg80211_rfkill_poll(struct rfkill *rfkill, void *data)
{
struct cfg80211_registered_device *rdev = data;
rdev_rfkill_poll(rdev);
}
void cfg80211_stop_p2p_device(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev)
{
ASSERT_RTNL();
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_P2P_DEVICE))
return;
if (!wdev_running(wdev))
return;
rdev_stop_p2p_device(rdev, wdev);
wdev->is_running = false;
rdev->opencount--;
if (rdev->scan_req && rdev->scan_req->wdev == wdev) {
if (WARN_ON(!rdev->scan_req->notified &&
(!rdev->int_scan_req ||
!rdev->int_scan_req->notified)))
rdev->scan_req->info.aborted = true;
___cfg80211_scan_done(rdev, false);
}
}
void cfg80211_stop_nan(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev)
{
ASSERT_RTNL();
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_NAN))
return;
if (!wdev_running(wdev))
return;
rdev_stop_nan(rdev, wdev);
wdev->is_running = false;
rdev->opencount--;
}
void cfg80211_shutdown_all_interfaces(struct wiphy *wiphy)
{
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
struct wireless_dev *wdev;
ASSERT_RTNL();
list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
if (wdev->netdev) {
dev_close(wdev->netdev);
continue;
}
/* otherwise, check iftype */
switch (wdev->iftype) {
case NL80211_IFTYPE_P2P_DEVICE:
cfg80211_stop_p2p_device(rdev, wdev);
break;
case NL80211_IFTYPE_NAN:
cfg80211_stop_nan(rdev, wdev);
break;
default:
break;
}
}
}
EXPORT_SYMBOL_GPL(cfg80211_shutdown_all_interfaces);
static int cfg80211_rfkill_set_block(void *data, bool blocked)
{
struct cfg80211_registered_device *rdev = data;
if (!blocked)
return 0;
rtnl_lock();
cfg80211_shutdown_all_interfaces(&rdev->wiphy);
rtnl_unlock();
return 0;
}
static void cfg80211_rfkill_block_work(struct work_struct *work)
{
struct cfg80211_registered_device *rdev;
rdev = container_of(work, struct cfg80211_registered_device,
rfkill_block);
cfg80211_rfkill_set_block(rdev, true);
}
static void cfg80211_event_work(struct work_struct
|