// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2002-2005, Instant802 Networks, Inc.
* Copyright 2005-2006, Devicescape Software, Inc.
* Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2017 Intel Deutschland GmbH
* Copyright (C) 2018-2022 Intel Corporation
*/
#include <net/mac80211.h>
#include <linux/module.h>
#include <linux/fips.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/skbuff.h>
#include <linux/etherdevice.h>
#include <linux/if_arp.h>
#include <linux/rtnetlink.h>
#include <linux/bitmap.h>
#include <linux/inetdevice.h>
#include <net/net_namespace.h>
#include <net/cfg80211.h>
#include <net/addrconf.h>
#include "ieee80211_i.h"
#include "driver-ops.h"
#include "rate.h"
#include "mesh.h"
#include "wep.h"
#include "led.h"
#include "debugfs.h"
void ieee80211_configure_filter(struct ieee80211_local *local)
{
u64 mc;
unsigned int changed_flags;
unsigned int new_flags = 0;
if (atomic_read(&local->iff_allmultis))
new_flags |= FIF_ALLMULTI;
if (local->monitors || test_bit(SCAN_SW_SCANNING, &local->scanning) ||
test_bit(SCAN_ONCHANNEL_SCANNING, &local->scanning))
new_flags |= FIF_BCN_PRBRESP_PROMISC;
if (local->fif_probe_req || local->probe_req_reg)
new_flags |= FIF_PROBE_REQ;
if (local->fif_fcsfail)
new_flags |= FIF_FCSFAIL;
if (local->fif_plcpfail)
new_flags |= FIF_PLCPFAIL;
if (local->fif_control)
new_flags |= FIF_CONTROL;
if (local->fif_other_bss)
new_flags |= FIF_OTHER_BSS;
if (local->fif_pspoll)
new_flags |= FIF_PSPOLL;
if (local->rx_mcast_action_reg)
new_flags |= FIF_MCAST_ACTION;
spin_lock_bh(&local->filter_lock);
changed_flags = local->filter_flags ^ new_flags;
mc = drv_prepare_multicast(local, &local->mc_list);
spin_unlock_bh(&local->filter_lock);
/* be a bit nasty */
new_flags |= (1<<31);
drv_configure_filter(local, changed_flags, &new_flags, mc);
WARN_ON(new_flags & (1<<31));
local->filter_flags = new_flags & ~(1<<31);
}
static void ieee80211_reconfig_filter(struct work_struct *work)
{
struct ieee80211_local *local =
container_of(work, struct ieee80211_local, reconfig_filter);
ieee80211_configure_filter(local);
}
static u32 ieee80211_hw_conf_chan(struct ieee80211_local *local)
{
struct ieee80211_sub_if_data *sdata;
struct cfg80211_chan_def chandef = {};
u32 changed = 0;
int power;
u32 offchannel_flag;
offchannel_flag = local->hw.conf.flags & IEEE80211_CONF_OFFCHANNEL;
if (local->scan_chandef.chan) {
chandef = local->scan_chandef;
} else if (local->tmp_channel) {
chandef.chan = local->tmp_channel;
chandef.width = NL80211_CHAN_WIDTH_20_NOHT;
chandef.center_freq1 = chandef.chan->center_freq;
chandef.freq1_offset = chandef.chan->freq_offset;
} else
chandef = local->_oper_chandef;
WARN(!cfg80211_chandef_valid(&chandef),
"control:%d.%03d MHz width:%d center: %d.%03d/%d MHz",
chandef.chan->center_freq, chandef.chan->freq_offset,
chandef.width, chandef.center_freq1, chandef.freq1_offset,
chandef.center_freq2);
if (!cfg80211_chandef_identical(&chandef, &local->_oper_chandef))
local->hw.conf.flags |= IEEE80211_CONF_OFFCHANNEL;
else
local->hw.conf.flags &= ~IEEE80211_CONF_OFFCHANNEL;
offchannel_flag ^= local->hw.conf.flags & IEEE80211_CONF_OFFCHANNEL;
if (offchannel_flag ||
!cfg80211_chandef_identical(&local->hw.conf.chandef,
&local->_oper_chandef)) {
local->hw.conf.chandef = chandef;
changed |= IEEE80211_CONF_CHANGE_CHANNEL;
}
if (!conf_is_ht(&local->hw.conf)) {
/*
* mac80211.h documents that this is only valid
* when the channel is set to an HT type, and
* that otherwise STATIC is used.
*/
local->hw.conf.smps_mode = IEEE80211_SMPS_STATIC;
} else if (local->hw.conf.smps_mode != local->smps_mode) {
local->hw.conf.smps_mode = local->smps_mode;
changed |= IEEE80211_CONF_CHANGE_SMPS;
}
power = ieee80211_chandef_max_power(&chandef);
rcu_read_lock();
list_for_each_entry_rcu(sdata, &local->interfaces, list) {
if (!rcu_access_pointer(sdata->vif.bss_conf.chanctx_conf))
continue;
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
continue;
if (sdata->vif.bss_conf.txpower == INT_MIN)
continue;
power = min(power, sdata->vif.bss_conf.txpower);
}
rcu_read_unlock();
if (local->hw.conf.power_level != power) {
changed |= IEEE80211_CONF_CHANGE_POWER;
local->hw.conf.power_level = power;
}
return changed;
}
int ieee80211_hw_config(struct ieee80211_local *local, u32 changed)
{
int ret = 0;
might_sleep();
if (!local->use_chanctx)
changed |= ieee80211_hw_conf_chan(local);
else
changed &= ~(IEEE80211_CONF_CHANGE_CHANNEL |
IEEE80211_CONF_CHANGE_POWER |
IEEE80211_CONF_CHANGE_SMPS);
if (changed && local->open_count) {
ret = drv_config(local, changed);
/*
* Goal:
* HW reconfiguration should never fail, the driver has told
* us what it can support so it should live up to that promise.
*
* Current status:
* rfkill is not integrated with mac80211 and a
* configuration command can thus fail if hardware rfkill
* is enabled
*
* FIXME: integrate rfkill with mac80211 and then add this
* WARN_ON() back
*
*/
/* WARN_ON(ret); */
}
return ret;
}
#define BSS_CHANGED_VIF_CFG_FLAGS (BSS_CHANGED_ASSOC |\
BSS_CHANGED_IDLE |\
BSS_CHANGED_PS |\
BSS_CHANGED_IBSS |\
BSS_CHANGED_ARP_FILTER |\
BSS_CHANGED_SSID)
void ieee80211_bss_info_change_notify(struct ieee80211_sub_if_data *sdata,
u64 changed)
{
struct ieee80211_local *local = sdata->local;
might_sleep();
if (!changed || sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
return;
if (WARN_ON_ONCE(changed & (BSS_CHANGED_BEACON |
BSS_CHANGED_BEACON_ENABLED) &&
sdata->vif.type != NL80211_IFTYPE_AP &&
sdata->vif.type != NL80211_IFTYPE_ADHOC &&
sdata->vif.type != NL80211_IFTYPE_MESH_POINT &&
sdata->vif.type != NL80211_IFTYPE_OCB))
return;
if (WARN_ON_ONCE(sdata->vif.type =
|