blob: d1e6e3e2223110e19d1746e338c67e016aa54e1c [file] [log] [blame]
/*
* Copyright(c) 2015 - 2017 Intel Deutschland GmbH
* Copyright (C) 2018, 2020, 2022-2024 Intel Corporation
*
* Backport functionality introduced in Linux 4.4.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include "mac80211-exp.h"
#include <linux/export.h>
#include <linux/if_vlan.h>
#include <linux/debugfs.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <net/ip.h>
#include <asm/unaligned.h>
#include <linux/device.h>
#if CFG80211_VERSION < KERNEL_VERSION(5,18,0)
static unsigned int __ieee80211_get_mesh_hdrlen(u8 flags)
{
int ae = flags & MESH_FLAGS_AE;
/* 802.11-2012, 8.2.4.7.3 */
switch (ae) {
default:
case 0:
return 6;
case MESH_FLAGS_AE_A4:
return 12;
case MESH_FLAGS_AE_A5_A6:
return 18;
}
}
int ieee80211_data_to_8023_exthdr(struct sk_buff *skb, struct ethhdr *ehdr,
const u8 *addr, enum nl80211_iftype iftype,
u8 data_offset, bool is_amsdu)
{
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
struct {
u8 hdr[ETH_ALEN] __aligned(2);
__be16 proto;
} payload;
struct ethhdr tmp;
u16 hdrlen;
u8 mesh_flags = 0;
if (unlikely(!ieee80211_is_data_present(hdr->frame_control)))
return -1;
hdrlen = ieee80211_hdrlen(hdr->frame_control) + data_offset;
if (skb->len < hdrlen + 8)
return -1;
/* convert IEEE 802.11 header + possible LLC headers into Ethernet
* header
* IEEE 802.11 address fields:
* ToDS FromDS Addr1 Addr2 Addr3 Addr4
* 0 0 DA SA BSSID n/a
* 0 1 DA BSSID SA n/a
* 1 0 BSSID SA DA n/a
* 1 1 RA TA DA SA
*/
memcpy(tmp.h_dest, ieee80211_get_DA(hdr), ETH_ALEN);
memcpy(tmp.h_source, ieee80211_get_SA(hdr), ETH_ALEN);
if (iftype == NL80211_IFTYPE_MESH_POINT)
skb_copy_bits(skb, hdrlen, &mesh_flags, 1);
mesh_flags &= MESH_FLAGS_AE;
switch (hdr->frame_control &
cpu_to_le16(IEEE80211_FCTL_TODS | IEEE80211_FCTL_FROMDS)) {
case cpu_to_le16(IEEE80211_FCTL_TODS):
if (unlikely(iftype != NL80211_IFTYPE_AP &&
iftype != NL80211_IFTYPE_AP_VLAN &&
iftype != NL80211_IFTYPE_P2P_GO))
return -1;
break;
case cpu_to_le16(IEEE80211_FCTL_TODS | IEEE80211_FCTL_FROMDS):
if (unlikely(iftype != NL80211_IFTYPE_MESH_POINT &&
iftype != NL80211_IFTYPE_AP_VLAN &&
iftype != NL80211_IFTYPE_STATION))
return -1;
if (iftype == NL80211_IFTYPE_MESH_POINT) {
if (mesh_flags == MESH_FLAGS_AE_A4)
return -1;
if (mesh_flags == MESH_FLAGS_AE_A5_A6) {
skb_copy_bits(skb, hdrlen +
offsetof(struct ieee80211s_hdr, eaddr1),
tmp.h_dest, 2 * ETH_ALEN);
}
hdrlen += __ieee80211_get_mesh_hdrlen(mesh_flags);
}
break;
case cpu_to_le16(IEEE80211_FCTL_FROMDS):
if ((iftype != NL80211_IFTYPE_STATION &&
iftype != NL80211_IFTYPE_P2P_CLIENT &&
iftype != NL80211_IFTYPE_MESH_POINT) ||
(is_multicast_ether_addr(tmp.h_dest) &&
ether_addr_equal(tmp.h_source, addr)))
return -1;
if (iftype == NL80211_IFTYPE_MESH_POINT) {
if (mesh_flags == MESH_FLAGS_AE_A5_A6)
return -1;
if (mesh_flags == MESH_FLAGS_AE_A4)
skb_copy_bits(skb, hdrlen +
offsetof(struct ieee80211s_hdr, eaddr1),
tmp.h_source, ETH_ALEN);
hdrlen += __ieee80211_get_mesh_hdrlen(mesh_flags);
}
break;
case cpu_to_le16(0):
if (iftype != NL80211_IFTYPE_ADHOC &&
iftype != NL80211_IFTYPE_STATION &&
iftype != NL80211_IFTYPE_OCB)
return -1;
break;
}
skb_copy_bits(skb, hdrlen, &payload, sizeof(payload));
tmp.h_proto = payload.proto;
if (likely((!is_amsdu && ether_addr_equal(payload.hdr, rfc1042_header) &&
tmp.h_proto != htons(ETH_P_AARP) &&
tmp.h_proto != htons(ETH_P_IPX)) ||
ether_addr_equal(payload.hdr, bridge_tunnel_header))) {
/* remove RFC1042 or Bridge-Tunnel encapsulation and
* replace EtherType */
hdrlen += ETH_ALEN + 2;
skb_postpull_rcsum(skb, &payload, ETH_ALEN + 2);
} else {
tmp.h_proto = htons(skb->len - hdrlen);
}
pskb_pull(skb, hdrlen);
if (!ehdr)
ehdr = skb_push(skb, sizeof(struct ethhdr));
memcpy(ehdr, &tmp, sizeof(tmp));
return 0;
}
EXPORT_SYMBOL(/* don't auto-generate a rename */
ieee80211_data_to_8023_exthdr);
#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(5,18,0) */
#if CFG80211_VERSION < KERNEL_VERSION(5,8,0)
#include "mac80211/ieee80211_i.h"
#include "mac80211/driver-ops.h"
void ieee80211_mgmt_frame_register(struct wiphy *wiphy,
struct wireless_dev *wdev,
u16 frame_type, bool reg)
{
struct ieee80211_local *local = wiphy_priv(wiphy);
struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
switch (frame_type) {
case IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_PROBE_REQ:
if (reg) {
local->probe_req_reg = true;
sdata->vif.probe_req_reg = true;
} else {
if (local->probe_req_reg)
local->probe_req_reg = false;
if (sdata->vif.probe_req_reg)
sdata->vif.probe_req_reg = false;
}
if (!local->open_count)
break;
if (ieee80211_sdata_running(sdata)) {
if (sdata->vif.probe_req_reg == 1)
drv_config_iface_filter(local, sdata,
FIF_PROBE_REQ,
FIF_PROBE_REQ);
else if (sdata->vif.probe_req_reg == 0)
drv_config_iface_filter(local, sdata, 0,
FIF_PROBE_REQ);
}
ieee80211_configure_filter(local);
break;
default:
break;
}
}
#endif /* < 5.8 */
#ifdef CONFIG_THERMAL
#if CFG80211_VERSION < KERNEL_VERSION(6,0,0)
struct thermal_zone_device *
thermal_zone_device_register_with_trips(const char *type,
struct thermal_trip *trips,
int num_trips, int mask, void *devdata,
struct thermal_zone_device_ops *ops,
struct thermal_zone_params *tzp, int passive_delay,
int polling_delay)
{
return thermal_zone_device_register(type, num_trips, mask, devdata, ops, tzp,
passive_delay, polling_delay);
}
EXPORT_SYMBOL_GPL(thermal_zone_device_register_with_trips);
#endif
#if CFG80211_VERSION < KERNEL_VERSION(6,4,0)
void *thermal_zone_device_priv(struct thermal_zone_device *tzd)
{
return tzd->devdata;
}
EXPORT_SYMBOL_GPL(thermal_zone_device_priv);
#endif /* < 6.4 */
#endif
#if CFG80211_VERSION < KERNEL_VERSION(6,1,0)
struct ieee80211_per_bw_puncturing_values {
u8 len;
const u16 *valid_values;
};
static const u16 puncturing_values_80mhz[] = {
0x8, 0x4, 0x2, 0x1
};
static const u16 puncturing_values_160mhz[] = {
0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1, 0xc0, 0x30, 0xc, 0x3
};
static const u16 puncturing_values_320mhz[] = {
0xc000, 0x3000, 0xc00, 0x300, 0xc0, 0x30, 0xc, 0x3, 0xf000, 0xf00,
0xf0, 0xf, 0xfc00, 0xf300, 0xf0c0, 0xf030, 0xf00c, 0xf003, 0xc00f,
0x300f, 0xc0f, 0x30f, 0xcf, 0x3f
};
#define IEEE80211_PER_BW_VALID_PUNCTURING_VALUES(_bw) \
{ \
.len = ARRAY_SIZE(puncturing_values_ ## _bw ## mhz), \
.valid_values = puncturing_values_ ## _bw ## mhz \
}
static const struct ieee80211_per_bw_puncturing_values per_bw_puncturing[] = {
IEEE80211_PER_BW_VALID_PUNCTURING_VALUES(80),
IEEE80211_PER_BW_VALID_PUNCTURING_VALUES(160),
IEEE80211_PER_BW_VALID_PUNCTURING_VALUES(320)
};
static bool
ieee80211_valid_disable_subchannel_bitmap(u16 *bitmap, enum nl80211_chan_width bw)
{
u32 idx, i;
switch((int)bw) {
case NL80211_CHAN_WIDTH_80:
idx = 0;
break;
case NL80211_CHAN_WIDTH_160:
idx = 1;
break;
case NL80211_CHAN_WIDTH_320:
idx = 2;
break;
default:
*bitmap = 0;
break;
}
if (!*bitmap)
return true;
for (i = 0; i < per_bw_puncturing[idx].len; i++)
if (per_bw_puncturing[idx].valid_values[i] == *bitmap)
return true;
return false;
}
bool cfg80211_valid_disable_subchannel_bitmap(u16 *bitmap,
struct cfg80211_chan_def *chandef)
{
return ieee80211_valid_disable_subchannel_bitmap(bitmap, chandef->width);
}
#endif /* < 6.3 */
#if CFG80211_VERSION < KERNEL_VERSION(6,7,0)
#include "mac80211/ieee80211_i.h"
static void cfg80211_wiphy_work(struct work_struct *work)
{
struct ieee80211_local *local;
struct wiphy_work *wk;
local = container_of(work, struct ieee80211_local, wiphy_work);
#if CFG80211_VERSION < KERNEL_VERSION(5,12,0)
rtnl_lock();
#else
wiphy_lock(local->hw.wiphy);
#endif
if (local->suspended)
goto out;
spin_lock_irq(&local->wiphy_work_lock);
wk = list_first_entry_or_null(&local->wiphy_work_list,
struct wiphy_work, entry);
if (wk) {
list_del_init(&wk->entry);
if (!list_empty(&local->wiphy_work_list))
schedule_work(work);
spin_unlock_irq(&local->wiphy_work_lock);
wk->func(local->hw.wiphy, wk);
} else {
spin_unlock_irq(&local->wiphy_work_lock);
}
out:
#if CFG80211_VERSION < KERNEL_VERSION(5,12,0)
rtnl_unlock();
#else
wiphy_unlock(local->hw.wiphy);
#endif
}
void wiphy_work_setup(struct ieee80211_local *local)
{
INIT_WORK(&local->wiphy_work, cfg80211_wiphy_work);
INIT_LIST_HEAD(&local->wiphy_work_list);
spin_lock_init(&local->wiphy_work_lock);
}
void wiphy_work_flush(struct wiphy *wiphy, struct wiphy_work *end)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct ieee80211_local *local = hw_to_local(hw);
struct wiphy_work *wk;
unsigned long flags;
int runaway_limit = 100;
lockdep_assert_wiphy(wiphy);
spin_lock_irqsave(&local->wiphy_work_lock, flags);
while (!list_empty(&local->wiphy_work_list)) {
struct wiphy_work *wk;
wk = list_first_entry(&local->wiphy_work_list,
struct wiphy_work, entry);
list_del_init(&wk->entry);
spin_unlock_irqrestore(&local->wiphy_work_lock, flags);
wk->func(local->hw.wiphy, wk);
spin_lock_irqsave(&local->wiphy_work_lock, flags);
if (wk == end)
break;
if (WARN_ON(--runaway_limit == 0))
INIT_LIST_HEAD(&local->wiphy_work_list);
}
spin_unlock_irqrestore(&local->wiphy_work_lock, flags);
}
EXPORT_SYMBOL(wiphy_work_flush);
void wiphy_delayed_work_flush(struct wiphy *wiphy,
struct wiphy_delayed_work *dwork)
{
del_timer_sync(&dwork->timer);
wiphy_work_flush(wiphy, &dwork->work);
}
EXPORT_SYMBOL(wiphy_delayed_work_flush);
void wiphy_work_teardown(struct ieee80211_local *local)
{
#if CFG80211_VERSION < KERNEL_VERSION(5,12,0)
rtnl_lock();
#else
wiphy_lock(local->hw.wiphy);
#endif
wiphy_work_flush(local->hw.wiphy, NULL);
#if CFG80211_VERSION < KERNEL_VERSION(5,12,0)
rtnl_unlock();
#else
wiphy_unlock(local->hw.wiphy);
#endif
cancel_work_sync(&local->wiphy_work);
}
void wiphy_work_queue(struct wiphy *wiphy, struct wiphy_work *work)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct ieee80211_local *local = hw_to_local(hw);
unsigned long flags;
spin_lock_irqsave(&local->wiphy_work_lock, flags);
if (list_empty(&work->entry))
list_add_tail(&work->entry, &local->wiphy_work_list);
spin_unlock_irqrestore(&local->wiphy_work_lock, flags);
schedule_work(&local->wiphy_work);
}
EXPORT_SYMBOL_GPL(wiphy_work_queue);
void wiphy_work_cancel(struct wiphy *wiphy, struct wiphy_work *work)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct ieee80211_local *local = hw_to_local(hw);
unsigned long flags;
#if CFG80211_VERSION < KERNEL_VERSION(5,12,0)
ASSERT_RTNL();
#else
lockdep_assert_held(&wiphy->mtx);
#endif
spin_lock_irqsave(&local->wiphy_work_lock, flags);
if (!list_empty(&work->entry))
list_del_init(&work->entry);
spin_unlock_irqrestore(&local->wiphy_work_lock, flags);
}
EXPORT_SYMBOL_GPL(wiphy_work_cancel);
void wiphy_delayed_work_timer(struct timer_list *t)
{
struct wiphy_delayed_work *dwork = from_timer(dwork, t, timer);
wiphy_work_queue(dwork->wiphy, &dwork->work);
}
EXPORT_SYMBOL(wiphy_delayed_work_timer);
void wiphy_delayed_work_queue(struct wiphy *wiphy,
struct wiphy_delayed_work *dwork,
unsigned long delay)
{
if (!delay) {
del_timer(&dwork->timer);
wiphy_work_queue(wiphy, &dwork->work);
return;
}
dwork->wiphy = wiphy;
mod_timer(&dwork->timer, jiffies + delay);
}
EXPORT_SYMBOL_GPL(wiphy_delayed_work_queue);
void wiphy_delayed_work_cancel(struct wiphy *wiphy,
struct wiphy_delayed_work *dwork)
{
lockdep_assert_held(&wiphy->mtx);
del_timer_sync(&dwork->timer);
wiphy_work_cancel(wiphy, &dwork->work);
}
EXPORT_SYMBOL_GPL(wiphy_delayed_work_cancel);
#endif /* CFG80211_VERSION < KERNEL_VERSION(6,5,0) */
#if CFG80211_VERSION < KERNEL_VERSION(6,8,0)
int nl80211_chan_width_to_mhz(enum nl80211_chan_width chan_width)
{
int mhz;
switch((int)chan_width) {
case NL80211_CHAN_WIDTH_1:
mhz = 1;
break;
case NL80211_CHAN_WIDTH_2:
mhz = 2;
break;
case NL80211_CHAN_WIDTH_4:
mhz = 4;
break;
case NL80211_CHAN_WIDTH_8:
mhz = 8;
break;
case NL80211_CHAN_WIDTH_16:
mhz = 16;
break;
case NL80211_CHAN_WIDTH_5:
mhz = 5;
break;
case NL80211_CHAN_WIDTH_10:
mhz = 10;
break;
case NL80211_CHAN_WIDTH_20:
case NL80211_CHAN_WIDTH_20_NOHT:
mhz = 20;
break;
case NL80211_CHAN_WIDTH_40:
mhz = 40;
break;
case NL80211_CHAN_WIDTH_80P80:
case NL80211_CHAN_WIDTH_80:
mhz = 80;
break;
case NL80211_CHAN_WIDTH_160:
mhz = 160;
break;
case NL80211_CHAN_WIDTH_320:
mhz = 320;
break;
default:
WARN_ON_ONCE(1);
return -1;
}
return mhz;
}
EXPORT_SYMBOL_GPL(nl80211_chan_width_to_mhz);
static int cfg80211_chandef_get_width(const struct cfg80211_chan_def *c)
{
return nl80211_chan_width_to_mhz(c->width);
}
int cfg80211_chandef_primary(const struct cfg80211_chan_def *c,
enum nl80211_chan_width primary_chan_width,
u16 *punctured)
{
int pri_width = nl80211_chan_width_to_mhz(primary_chan_width);
int width = cfg80211_chandef_get_width(c);
u32 control = c->chan->center_freq;
u32 center = c->center_freq1;
u16 _punct = 0;
if (WARN_ON_ONCE(pri_width < 0 || width < 0))
return -1;
/* not intended to be called this way, can't determine */
if (WARN_ON_ONCE(pri_width > width))
return -1;
if (!punctured)
punctured = &_punct;
*punctured = chandef_punctured(c);
while (width > pri_width) {
unsigned int bits_to_drop = width / 20 / 2;
if (control > center) {
center += width / 4;
*punctured >>= bits_to_drop;
} else {
center -= width / 4;
*punctured &= (1 << bits_to_drop) - 1;
}
width /= 2;
}
return center;
}
#endif /* cfg < 6.8 */