#ifndef LINUX_IEEE80211_S1G_H
#define LINUX_IEEE80211_S1G_H
#include <linux/types.h>
#include <linux/if_ether.h>
#define IEEE80211_S1G_BCN_NEXT_TBTT 0x100
#define IEEE80211_S1G_BCN_CSSID 0x200
#define IEEE80211_S1G_BCN_ANO 0x400
#define IEEE80211_S1G_1MHZ_NDP_BITS 25
#define IEEE80211_S1G_1MHZ_NDP_BYTES 4
#define IEEE80211_S1G_2MHZ_NDP_BITS 37
#define IEEE80211_S1G_2MHZ_NDP_BYTES 5
static inline bool ieee80211_is_s1g_beacon(__le16 fc)
{
return (fc & cpu_to_le16(IEEE80211_FCTL_FTYPE |
IEEE80211_FCTL_STYPE)) ==
cpu_to_le16(IEEE80211_FTYPE_EXT | IEEE80211_STYPE_S1G_BEACON);
}
static inline bool ieee80211_s1g_has_next_tbtt(__le16 fc)
{
return ieee80211_is_s1g_beacon(fc) &&
(fc & cpu_to_le16(IEEE80211_S1G_BCN_NEXT_TBTT));
}
static inline bool ieee80211_s1g_has_ano(__le16 fc)
{
return ieee80211_is_s1g_beacon(fc) &&
(fc & cpu_to_le16(IEEE80211_S1G_BCN_ANO));
}
static inline bool ieee80211_s1g_has_cssid(__le16 fc)
{
return ieee80211_is_s1g_beacon(fc) &&
(fc & cpu_to_le16(IEEE80211_S1G_BCN_CSSID));
}
enum ieee80211_s1g_chanwidth {
IEEE80211_S1G_CHANWIDTH_1MHZ = 0,
IEEE80211_S1G_CHANWIDTH_2MHZ = 1,
IEEE80211_S1G_CHANWIDTH_4MHZ = 3,
IEEE80211_S1G_CHANWIDTH_8MHZ = 7,
IEEE80211_S1G_CHANWIDTH_16MHZ = 15,
};
enum ieee80211_s1g_pri_chanwidth {
IEEE80211_S1G_PRI_CHANWIDTH_2MHZ = 0,
IEEE80211_S1G_PRI_CHANWIDTH_1MHZ = 1,
};
struct ieee80211_s1g_bcn_compat_ie {
__le16 compat_info;
__le16 beacon_int;
__le32 tsf_completion;
} __packed;
struct ieee80211_s1g_oper_ie {
u8 ch_width;
u8 oper_class;
u8 primary_ch;
u8 oper_ch;
__le16 basic_mcs_nss;
} __packed;
struct ieee80211_aid_response_ie {
__le16 aid;
u8 switch_count;
__le16 response_int;
} __packed;
struct ieee80211_s1g_cap {
u8 capab_info[10];
u8 supp_mcs_nss[5];
} __packed;
static inline size_t
ieee80211_s1g_optional_len(__le16 fc)
{
size_t len = 0;
if (ieee80211_s1g_has_next_tbtt(fc))
len += 3;
if (ieee80211_s1g_has_cssid(fc))
len += 4;
if (ieee80211_s1g_has_ano(fc))
len += 1;
return len;
}
#define IEEE80211_S1G_CAPABILITY_LEN 15
#define S1G_CAP0_S1G_LONG BIT(0)
#define S1G_CAP0_SGI_1MHZ BIT(1)
#define S1G_CAP0_SGI_2MHZ BIT(2)
#define S1G_CAP0_SGI_4MHZ BIT(3)
#define S1G_CAP0_SGI_8MHZ BIT(4)
#define S1G_CAP0_SGI_16MHZ BIT(5)
#define S1G_CAP0_SUPP_CH_WIDTH GENMASK(7, 6)
#define S1G_SUPP_CH_WIDTH_2 0
#define S1G_SUPP_CH_WIDTH_4 1
#define S1G_SUPP_CH_WIDTH_8 2
#define S1G_SUPP_CH_WIDTH_16 3
#define S1G_SUPP_CH_WIDTH_MAX(cap) ((1 << FIELD_GET(S1G_CAP0_SUPP_CH_WIDTH, \
cap[0])) << 1)
#define S1G_CAP1_RX_LDPC BIT(0)
#define S1G_CAP1_TX_STBC BIT(1)
#define S1G_CAP1_RX_STBC BIT(2)
#define S1G_CAP1_SU_BFER BIT(3)
#define S1G_CAP1_SU_BFEE BIT(4)
#define S1G_CAP1_BFEE_STS GENMASK(7, 5)
#define S1G_CAP2_SOUNDING_DIMENSIONS GENMASK(2, 0)
#define S1G_CAP2_MU_BFER BIT(3)
#define S1G_CAP2_MU_BFEE BIT(4)
#define S1G_CAP2_PLUS_HTC_VHT BIT(5)
#define S1G_CAP2_TRAVELING_PILOT GENMASK(7, 6)
#define S1G_CAP3_RD_RESPONDER BIT(0)
#define S1G_CAP3_HT_DELAYED_BA BIT(1)
#define S1G_CAP3_MAX_MPDU_LEN BIT(2)
#define S1G_CAP3_MAX_AMPDU_LEN_EXP GENMASK(4, 3)
#define S1G_CAP3_MIN_MPDU_START GENMASK(7, 5)
#define S1G_CAP4_UPLINK_SYNC BIT(0)
#define S1G_CAP4_DYNAMIC_AID BIT(1)
#define S1G_CAP4_BAT BIT(2)
#define S1G_CAP4_TIME_ADE BIT(3)
#define S1G_CAP4_NON_TIM BIT(4)
#define S1G_CAP4_GROUP_AID BIT(5)
#define S1G_CAP4_STA_TYPE GENMASK(7, 6)
#define S1G_CAP5_CENT_AUTH_CONTROL BIT(0)
#define S1G_CAP5_DIST_AUTH_CONTROL BIT(1)
#define S1G_CAP5_AMSDU BIT(2)
#define S1G_CAP5_AMPDU BIT(3)
#define S1G_CAP5_ASYMMETRIC_BA BIT(4)
#define S1G_CAP5_FLOW_CONTROL BIT(5)
#define S1G_CAP5_SECTORIZED_BEAM GENMASK(7, 6)
#define S1G_CAP6_OBSS_MITIGATION BIT(0)
#define S1G_CAP6_FRAGMENT_BA BIT(1)
#define S1G_CAP6_NDP_PS_POLL BIT(2)
#define S1G_CAP6_RAW_OPERATION BIT(3)
#define S1G_CAP6_PAGE_SLICING BIT(4)
#define S1G_CAP6_TXOP_SHARING_IMP_ACK BIT(5)
#define S1G_CAP6_VHT_LINK_ADAPT GENMASK(7, 6)
#define S1G_CAP7_TACK_AS_PS_POLL BIT(0)
#define S1G_CAP7_DUP_1MHZ BIT(1)
#define S1G_CAP7_MCS_NEGOTIATION BIT(2)
#define S1G_CAP7_1MHZ_CTL_RESPONSE_PREAMBLE BIT(3)
#define S1G_CAP7_NDP_BFING_REPORT_POLL BIT(4)
#define S1G_CAP7_UNSOLICITED_DYN_AID BIT(5)
#define S1G_CAP7_SECTOR_TRAINING_OPERATION BIT(6)
#define S1G_CAP7_TEMP_PS_MODE_SWITCH BIT(7)
#define S1G_CAP8_TWT_GROUPING BIT(0)
#define S1G_CAP8_BDT BIT(1)
#define S1G_CAP8_COLOR GENMASK(4, 2)
#define S1G_CAP8_TWT_REQUEST BIT(5)
#define S1G_CAP8_TWT_RESPOND BIT(6)
#define S1G_CAP8_PV1_FRAME BIT(7)
#define S1G_CAP9_LINK_ADAPT_PER_CONTROL_RESPONSE BIT(0)
#define S1G_OPER_CH_WIDTH_PRIMARY BIT(0)
#define S1G_OPER_CH_WIDTH_OPER GENMASK(4, 1)
#define S1G_OPER_CH_PRIMARY_LOCATION BIT(5)
#define S1G_2M_PRIMARY_LOCATION_LOWER 0
#define S1G_2M_PRIMARY_LOCATION_UPPER 1
#define LISTEN_INT_USF GENMASK(15, 14)
#define LISTEN_INT_UI GENMASK(13, 0)
#define IEEE80211_MAX_USF FIELD_MAX(LISTEN_INT_USF)
#define IEEE80211_MAX_UI FIELD_MAX(LISTEN_INT_UI)
#define IEEE80211_S1G_TIM_ENC_MODE_BLOCK 0
#define IEEE80211_S1G_TIM_ENC_MODE_SINGLE 1
#define IEEE80211_S1G_TIM_ENC_MODE_OLB 2
enum ieee80211_s1g_actioncode {
WLAN_S1G_AID_SWITCH_REQUEST,
WLAN_S1G_AID_SWITCH_RESPONSE,
WLAN_S1G_SYNC_CONTROL,
WLAN_S1G_STA_INFO_ANNOUNCE,
WLAN_S1G_EDCA_PARAM_SET,
WLAN_S1G_EL_OPERATION,
WLAN_S1G_TWT_SETUP,
WLAN_S1G_TWT_TEARDOWN,
WLAN_S1G_SECT_GROUP_ID_LIST,
WLAN_S1G_SECT_ID_FEEDBACK,
WLAN_S1G_TWT_INFORMATION = 11,
};
static inline bool ieee80211_is_s1g_short_beacon(__le16 fc, const u8 *variable,
size_t variable_len)
{
if (!ieee80211_is_s1g_beacon(fc))
return false;
if (variable_len < 2)
return true;
return variable[0] != WLAN_EID_S1G_BCN_COMPAT;
}
struct s1g_tim_aid {
u16 aid;
u8 target_blk;
u8 target_subblk;
u8 target_subblk_bit;
};
struct s1g_tim_enc_block {
u8 enc_mode;
bool inverse;
const u8 *ptr;
u8 len;
u8 olb_blk_offset;
};
static inline int ieee80211_s1g_len_bitmap(const u8 *ptr, const u8 *end)
{
u8 blkmap;
u8 n_subblks;
if (ptr >= end)
return -EINVAL;
blkmap = *ptr;
n_subblks = hweight8(blkmap);
if (ptr + 1 + n_subblks > end)
return -EINVAL;
return 1 + n_subblks;
}
static inline int ieee80211_s1g_len_single(const u8 *ptr, const u8 *end)
{
return (ptr + 1 > end) ? -EINVAL : 1;
}
static inline int ieee80211_s1g_len_olb(const u8 *ptr, const u8 *end)
{
if (ptr >= end)
return -EINVAL;
return (ptr + 1 + *ptr > end) ? -EINVAL : 1 + *ptr;
}
static inline int ieee80211_s1g_find_target_block(struct s1g_tim_enc_block *enc,
const struct s1g_tim_aid *aid,
const u8 *ptr, const u8 *end)
{
while (ptr + 1 <= end) {
u8 ctrl = *ptr++;
u8 mode = ctrl & 0x03;
bool contains, inverse = ctrl & BIT(2);
u8 span, blk_off = ctrl >> 3;
int len;
switch (mode) {
case IEEE80211_S1G_TIM_ENC_MODE_BLOCK:
len = ieee80211_s1g_len_bitmap(ptr, end);
contains = blk_off == aid->target_blk;
break;
case IEEE80211_S1G_TIM_ENC_MODE_SINGLE:
len = ieee80211_s1g_len_single(ptr, end);
contains = blk_off == aid->target_blk;
break;
case IEEE80211_S1G_TIM_ENC_MODE_OLB:
len = ieee80211_s1g_len_olb(ptr, end);
if (len > 0) {
span = DIV_ROUND_UP(len - 1, 8);
contains = (aid->target_blk >= blk_off) &&
(aid->target_blk < blk_off + span);
}
break;
default:
return -EOPNOTSUPP;
}
if (len < 0)
return len;
if (contains) {
enc->enc_mode = mode;
enc->inverse = inverse;
enc->ptr = ptr;
enc->len = (u8)len;
enc->olb_blk_offset = blk_off;
return 0;
}
ptr += len;
}
return -ENOENT;
}
static inline bool ieee80211_s1g_parse_bitmap(struct s1g_tim_enc_block *enc,
struct s1g_tim_aid *aid)
{
const u8 *ptr = enc->ptr;
u8 blkmap = *ptr++;
if (!(blkmap & BIT(aid->target_subblk)))
return enc->inverse;
if (aid->target_subblk)
ptr += hweight8(blkmap & GENMASK(aid->target_subblk - 1, 0));
return !!(*ptr & BIT(aid->target_subblk_bit)) ^ enc->inverse;
}
static inline bool ieee80211_s1g_parse_single(struct s1g_tim_enc_block *enc,
struct s1g_tim_aid *aid)
{
return ((*enc->ptr & 0x3f) == (aid->aid & 0x3f)) ^ enc->inverse;
}
static inline bool ieee80211_s1g_parse_olb(struct s1g_tim_enc_block *enc,
struct s1g_tim_aid *aid)
{
const u8 *ptr = enc->ptr;
u8 blk_len = *ptr++;
u16 span_offset = aid->target_blk - enc->olb_blk_offset;
u16 subblk_idx = span_offset * 8 + aid->target_subblk;
if (subblk_idx >= blk_len)
return enc->inverse;
return !!(ptr[subblk_idx] & BIT(aid->target_subblk_bit)) ^ enc->inverse;
}
static inline bool ieee80211_s1g_check_tim(const struct ieee80211_tim_ie *tim,
u8 tim_len, u16 aid)
{
int err;
struct s1g_tim_aid target_aid;
struct s1g_tim_enc_block enc_blk;
if (tim_len < 3)
return false;
target_aid.aid = aid;
target_aid.target_blk = (aid >> 6) & 0x1f;
target_aid.target_subblk = (aid >> 3) & 0x7;
target_aid.target_subblk_bit = aid & 0x7;
err = ieee80211_s1g_find_target_block(&enc_blk, &target_aid,
tim->virtual_map,
(const u8 *)tim + tim_len + 2);
if (err)
return false;
switch (enc_blk.enc_mode) {
case IEEE80211_S1G_TIM_ENC_MODE_BLOCK:
return ieee80211_s1g_parse_bitmap(&enc_blk, &target_aid);
case IEEE80211_S1G_TIM_ENC_MODE_SINGLE:
return ieee80211_s1g_parse_single(&enc_blk, &target_aid);
case IEEE80211_S1G_TIM_ENC_MODE_OLB:
return ieee80211_s1g_parse_olb(&enc_blk, &target_aid);
default:
return false;
}
}
#endif