blob: 1084ef0cd4597a808dae782be83dd7c29bb0724c [file] [log] [blame]
/*
* Wireless utility functions
*
* Copyright 2007-2009 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2018-2020, 2022-2024 Intel Corporation
*/
#include <linux/export.h>
#include <net/cfg80211.h>
#if CFG80211_VERSION < KERNEL_VERSION(5,6,0)
int ieee80211_get_vht_max_nss(struct ieee80211_vht_cap *cap,
enum ieee80211_vht_chanwidth bw,
int mcs, bool ext_nss_bw_capable,
unsigned int max_vht_nss)
{
u16 map = le16_to_cpu(cap->supp_mcs.rx_mcs_map);
int ext_nss_bw;
int supp_width;
int i, mcs_encoding;
if (map == 0xffff)
return 0;
if (WARN_ON(mcs > 9))
return 0;
if (mcs <= 7)
mcs_encoding = 0;
else if (mcs == 8)
mcs_encoding = 1;
else
mcs_encoding = 2;
if (!max_vht_nss) {
/* find max_vht_nss for the given MCS */
for (i = 7; i >= 0; i--) {
int supp = (map >> (2 * i)) & 3;
if (supp == 3)
continue;
if (supp >= mcs_encoding) {
max_vht_nss = i + 1;
break;
}
}
}
if (!(cap->supp_mcs.tx_mcs_map &
cpu_to_le16(IEEE80211_VHT_EXT_NSS_BW_CAPABLE)))
return max_vht_nss;
ext_nss_bw = le32_get_bits(cap->vht_cap_info,
IEEE80211_VHT_CAP_EXT_NSS_BW_MASK);
supp_width = le32_get_bits(cap->vht_cap_info,
IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK);
/* if not capable, treat ext_nss_bw as 0 */
if (!ext_nss_bw_capable)
ext_nss_bw = 0;
/* This is invalid */
if (supp_width == 3)
return 0;
/* This is an invalid combination so pretend nothing is supported */
if (supp_width == 2 && (ext_nss_bw == 1 || ext_nss_bw == 2))
return 0;
/*
* Cover all the special cases according to IEEE 802.11-2016
* Table 9-250. All other cases are either factor of 1 or not
* valid/supported.
*/
switch (bw) {
case IEEE80211_VHT_CHANWIDTH_USE_HT:
case IEEE80211_VHT_CHANWIDTH_80MHZ:
if ((supp_width == 1 || supp_width == 2) &&
ext_nss_bw == 3)
return 2 * max_vht_nss;
break;
case IEEE80211_VHT_CHANWIDTH_160MHZ:
if (supp_width == 0 &&
(ext_nss_bw == 1 || ext_nss_bw == 2))
return max_vht_nss / 2;
if (supp_width == 0 &&
ext_nss_bw == 3)
return (3 * max_vht_nss) / 4;
if (supp_width == 1 &&
ext_nss_bw == 3)
return 2 * max_vht_nss;
break;
case IEEE80211_VHT_CHANWIDTH_80P80MHZ:
if (supp_width == 0 && ext_nss_bw == 1)
return 0; /* not possible */
if (supp_width == 0 &&
ext_nss_bw == 2)
return max_vht_nss / 2;
if (supp_width == 0 &&
ext_nss_bw == 3)
return (3 * max_vht_nss) / 4;
if (supp_width == 1 &&
ext_nss_bw == 0)
return 0; /* not possible */
if (supp_width == 1 &&
ext_nss_bw == 1)
return max_vht_nss / 2;
if (supp_width == 1 &&
ext_nss_bw == 2)
return (3 * max_vht_nss) / 4;
break;
}
/* not covered or invalid combination received */
return max_vht_nss;
}
EXPORT_SYMBOL(ieee80211_get_vht_max_nss);
#endif
#if CFG80211_VERSION < KERNEL_VERSION(6,9,0)
bool cfg80211_iter_rnr(const u8 *elems, size_t elems_len,
enum cfg80211_rnr_iter_ret
(*iter)(void *data, u8 type,
const struct ieee80211_neighbor_ap_info *info,
const u8 *tbtt_info, u8 tbtt_info_len),
void *iter_data)
{
const struct element *rnr;
const u8 *pos, *end;
for_each_element_id(rnr, WLAN_EID_REDUCED_NEIGHBOR_REPORT,
elems, elems_len) {
const struct ieee80211_neighbor_ap_info *info;
pos = rnr->data;
end = rnr->data + rnr->datalen;
/* RNR IE may contain more than one NEIGHBOR_AP_INFO */
while (sizeof(*info) <= end - pos) {
u8 length, i, count;
u8 type;
info = (void *)pos;
count = u8_get_bits(info->tbtt_info_hdr,
IEEE80211_AP_INFO_TBTT_HDR_COUNT) +
1;
length = info->tbtt_info_len;
pos += sizeof(*info);
if (count * length > end - pos)
return false;
type = u8_get_bits(info->tbtt_info_hdr,
IEEE80211_AP_INFO_TBTT_HDR_TYPE);
for (i = 0; i < count; i++) {
switch (iter(iter_data, type, info,
pos, length)) {
case RNR_ITER_CONTINUE:
break;
case RNR_ITER_BREAK:
return true;
case RNR_ITER_ERROR:
return false;
}
pos += length;
}
}
if (pos != end)
return false;
}
return true;
}
ssize_t cfg80211_defragment_element(const struct element *elem, const u8 *ies,
size_t ieslen, u8 *data, size_t data_len,
u8 frag_id)
{
const struct element *next;
ssize_t copied;
u8 elem_datalen;
if (!elem)
return -EINVAL;
/* elem might be invalid after the memmove */
next = (void *)(elem->data + elem->datalen);
elem_datalen = elem->datalen;
if (elem->id == WLAN_EID_EXTENSION) {
copied = elem->datalen - 1;
if (data) {
if (copied > data_len)
return -ENOSPC;
memmove(data, elem->data + 1, copied);
}
} else {
copied = elem->datalen;
if (data) {
if (copied > data_len)
return -ENOSPC;
memmove(data, elem->data, copied);
}
}
/* Fragmented elements must have 255 bytes */
if (elem_datalen < 255)
return copied;
for (elem = next;
elem->data < ies + ieslen &&
elem->data + elem->datalen <= ies + ieslen;
elem = next) {
/* elem might be invalid after the memmove */
next = (void *)(elem->data + elem->datalen);
if (elem->id != frag_id)
break;
elem_datalen = elem->datalen;
if (data) {
if (copied + elem_datalen > data_len)
return -ENOSPC;
memmove(data + copied, elem->data, elem_datalen);
}
copied += elem_datalen;
/* Only the last fragment may be short */
if (elem_datalen != 255)
break;
}
return copied;
}
EXPORT_SYMBOL(cfg80211_defragment_element);
#endif
#if CFG80211_VERSION < KERNEL_VERSION(6,7,0)
void ieee80211_fragment_element(struct sk_buff *skb, u8 *len_pos, u8 frag_id)
{
unsigned int elem_len;
if (!len_pos)
return;
elem_len = skb->data + skb->len - len_pos - 1;
while (elem_len > 255) {
/* this one is 255 */
*len_pos = 255;
/* remaining data gets smaller */
elem_len -= 255;
/* make space for the fragment ID/len in SKB */
skb_put(skb, 2);
/* shift back the remaining data to place fragment ID/len */
memmove(len_pos + 255 + 3, len_pos + 255 + 1, elem_len);
/* place the fragment ID */
len_pos += 255 + 1;
*len_pos = frag_id;
/* and point to fragment length to update later */
len_pos++;
}
*len_pos = elem_len;
}
EXPORT_SYMBOL(ieee80211_fragment_element);
#endif
#if CFG80211_VERSION <= KERNEL_VERSION(6,8,0)
bool
ieee80211_uhb_power_type_valid(struct ieee80211_mgmt *mgmt, size_t len,
struct ieee80211_channel *channel)
{
const struct element *tmp;
struct ieee80211_he_operation *he_oper;
bool ret = false;
size_t ielen, min_hdr_len;
u8 *variable = mgmt->u.probe_resp.variable;
min_hdr_len = offsetof(struct ieee80211_mgmt,
u.probe_resp.variable);
ielen = len - min_hdr_len;
if (channel->band != NL80211_BAND_6GHZ)
return true;
tmp = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION,
variable, ielen);
if (tmp && tmp->datalen >= sizeof(*he_oper) + 1) {
const struct ieee80211_he_6ghz_oper *he_6ghz_oper;
he_oper = (void *)&tmp->data[1];
he_6ghz_oper = ieee80211_he_6ghz_oper(he_oper);
if (!he_6ghz_oper)
return false;
switch (u8_get_bits(he_6ghz_oper->control,
IEEE80211_HE_6GHZ_OPER_CTRL_REG_INFO)) {
case IEEE80211_6GHZ_CTRL_REG_LPI_AP:
case IEEE80211_6GHZ_CTRL_REG_INDOOR_LPI_AP:
return true;
case IEEE80211_6GHZ_CTRL_REG_SP_AP:
case IEEE80211_6GHZ_CTRL_REG_INDOOR_SP_AP:
return !(channel->flags &
IEEE80211_CHAN_NO_6GHZ_AFC_CLIENT);
case IEEE80211_6GHZ_CTRL_REG_VLP_AP:
return !(channel->flags &
IEEE80211_CHAN_NO_6GHZ_VLP_CLIENT);
default:
return false;
}
}
return false;
}
#endif
#if CFG80211_VERSION < KERNEL_VERSION(6,9,0)
bool ieee80211_operating_class_to_chandef(u8 operating_class,
struct ieee80211_channel *chan,
struct cfg80211_chan_def *chandef)
{
u32 control_freq, offset = 0;
enum nl80211_band band;
if (!ieee80211_operating_class_to_band(operating_class, &band) ||
!chan || band != chan->band)
return false;
control_freq = chan->center_freq;
chandef->chan = chan;
if (control_freq >= 5955)
offset = control_freq - 5955;
else if (control_freq >= 5745)
offset = control_freq - 5745;
else if (control_freq >= 5180)
offset = control_freq - 5180;
offset /= 20;
switch (operating_class) {
case 81: /* 2 GHz band; 20 MHz; channels 1..13 */
case 82: /* 2 GHz band; 20 MHz; channel 14 */
case 115: /* 5 GHz band; 20 MHz; channels 36,40,44,48 */
case 118: /* 5 GHz band; 20 MHz; channels 52,56,60,64 */
case 121: /* 5 GHz band; 20 MHz; channels 100..144 */
case 124: /* 5 GHz band; 20 MHz; channels 149,153,157,161 */
case 125: /* 5 GHz band; 20 MHz; channels 149..177 */
case 131: /* 6 GHz band; 20 MHz; channels 1..233*/
case 136: /* 6 GHz band; 20 MHz; channel 2 */
chandef->center_freq1 = control_freq;
chandef->width = NL80211_CHAN_WIDTH_20;
return true;
case 83: /* 2 GHz band; 40 MHz; channels 1..9 */
case 116: /* 5 GHz band; 40 MHz; channels 36,44 */
case 119: /* 5 GHz band; 40 MHz; channels 52,60 */
case 122: /* 5 GHz band; 40 MHz; channels 100,108,116,124,132,140 */
case 126: /* 5 GHz band; 40 MHz; channels 149,157,165,173 */
chandef->center_freq1 = control_freq + 10;
chandef->width = NL80211_CHAN_WIDTH_40;
return true;
case 84: /* 2 GHz band; 40 MHz; channels 5..13 */
case 117: /* 5 GHz band; 40 MHz; channels 40,48 */
case 120: /* 5 GHz band; 40 MHz; channels 56,64 */
case 123: /* 5 GHz band; 40 MHz; channels 104,112,120,128,136,144 */
case 127: /* 5 GHz band; 40 MHz; channels 153,161,169,177 */
chandef->center_freq1 = control_freq - 10;
chandef->width = NL80211_CHAN_WIDTH_40;
return true;
case 132: /* 6 GHz band; 40 MHz; channels 1,5,..,229*/
chandef->center_freq1 = control_freq + 10 - (offset & 1) * 20;
chandef->width = NL80211_CHAN_WIDTH_40;
return true;
case 128: /* 5 GHz band; 80 MHz; channels 36..64,100..144,149..177 */
case 133: /* 6 GHz band; 80 MHz; channels 1,5,..,229 */
chandef->center_freq1 = control_freq + 30 - (offset & 3) * 20;
chandef->width = NL80211_CHAN_WIDTH_80;
return true;
case 129: /* 5 GHz band; 160 MHz; channels 36..64,100..144,149..177 */
case 134: /* 6 GHz band; 160 MHz; channels 1,5,..,229 */
chandef->center_freq1 = control_freq + 70 - (offset & 7) * 20;
chandef->width = NL80211_CHAN_WIDTH_160;
return true;
case 130: /* 5 GHz band; 80+80 MHz; channels 36..64,100..144,149..177 */
case 135: /* 6 GHz band; 80+80 MHz; channels 1,5,..,229 */
/* The center_freq2 of 80+80 MHz is unknown */
case 137: /* 6 GHz band; 320 MHz; channels 1,5,..,229 */
/* 320-1 or 320-2 channelization is unknown */
default:
return false;
}
}
#endif