wifi: mac80211: support parsing S1G TIM PVB

An S1G TIM PVB has 3 mandatory encoding modes, that being
block bitmap, single AID and OBL alongside the ability for
each encoding mode to be inverted. Introduce the ability to
parse the 3 encoding formats. The implementation specification
for the encoding formats can be found in IEEE80211-2024 9.4.2.5.

Signed-off-by: Arien Judge <arien.judge@morsemicro.com>
Signed-off-by: Lachlan Hodges <lachlan.hodges@morsemicro.com>
Link: https://patch.msgid.link/20250725132221.258217-3-lachlan.hodges@morsemicro.com
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
Lachlan Hodges
2025-07-25 23:22:20 +10:00
committed by Johannes Berg
parent ee63609454
commit e0c47c6229
7 changed files with 263 additions and 15 deletions

View File

@@ -555,7 +555,7 @@ static void carl9170_ps_beacon(struct ar9170 *ar, void *data, unsigned int len)
/* Check whenever the PHY can be turned off again. */
/* 1. What about buffered unicast traffic for our AID? */
cam = ieee80211_check_tim(tim_ie, tim_len, ar->common.curaid);
cam = ieee80211_check_tim(tim_ie, tim_len, ar->common.curaid, false);
/* 2. Maybe the AP wants to send multicast/broadcast data? */
cam |= !!(tim_ie->bitmap_ctrl & 0x01);

View File

@@ -317,7 +317,7 @@ static void p54_pspoll_workaround(struct p54_common *priv, struct sk_buff *skb)
tim_len = tim[1];
tim_ie = (struct ieee80211_tim_ie *) &tim[2];
new_psm = ieee80211_check_tim(tim_ie, tim_len, priv->aid);
new_psm = ieee80211_check_tim(tim_ie, tim_len, priv->aid, false);
if (new_psm != priv->powersave_override) {
priv->powersave_override = new_psm;
p54_set_ps(priv);

View File

@@ -679,7 +679,7 @@ static void rt2x00lib_rxdone_check_ps(struct rt2x00_dev *rt2x00dev,
/* Check whenever the PHY can be turned off again. */
/* 1. What about buffered unicast traffic for our AID? */
cam = ieee80211_check_tim(tim_ie, tim_len, rt2x00dev->aid);
cam = ieee80211_check_tim(tim_ie, tim_len, rt2x00dev->aid, false);
/* 2. Maybe the AP wants to send multicast/broadcast data? */
cam |= (tim_ie->bitmap_ctrl & 0x01);

View File

@@ -519,7 +519,7 @@ void rtl_swlps_beacon(struct ieee80211_hw *hw, void *data, unsigned int len)
/* 1. What about buffered unicast traffic for our AID? */
u_buffed = ieee80211_check_tim(tim_ie, tim_len,
rtlpriv->mac80211.assoc_id);
rtlpriv->mac80211.assoc_id, false);
/* 2. Maybe the AP wants to send multicast/broadcast data? */
m_buffed = tim_ie->bitmap_ctrl & 0x01;

View File

@@ -220,6 +220,12 @@ static inline u16 ieee80211_sn_sub(u16 sn1, u16 sn2)
#define IEEE80211_MAX_AID_S1G 8191
#define IEEE80211_MAX_TIM_LEN 251
#define IEEE80211_MAX_MESH_PEERINGS 63
/* S1G encoding types */
#define IEEE80211_S1G_TIM_ENC_MODE_BLOCK 0
#define IEEE80211_S1G_TIM_ENC_MODE_SINGLE 1
#define IEEE80211_S1G_TIM_ENC_MODE_OLB 2
/* Maximum size for the MA-UNITDATA primitive, 802.11 standard section
6.2.1.1.2.
@@ -4757,15 +4763,8 @@ static inline unsigned long ieee80211_tu_to_usec(unsigned long tu)
return 1024 * tu;
}
/**
* ieee80211_check_tim - check if AID bit is set in TIM
* @tim: the TIM IE
* @tim_len: length of the TIM IE
* @aid: the AID to look for
* Return: whether or not traffic is indicated in the TIM for the given AID
*/
static inline bool ieee80211_check_tim(const struct ieee80211_tim_ie *tim,
u8 tim_len, u16 aid)
static inline bool __ieee80211_check_tim(const struct ieee80211_tim_ie *tim,
u8 tim_len, u16 aid)
{
u8 mask;
u8 index, indexn1, indexn2;
@@ -4788,6 +4787,254 @@ static inline bool ieee80211_check_tim(const struct ieee80211_tim_ie *tim,
return !!(tim->virtual_map[index] & mask);
}
struct s1g_tim_aid {
u16 aid;
u8 target_blk; /* Target block index */
u8 target_subblk; /* Target subblock index */
u8 target_subblk_bit; /* Target subblock bit */
};
struct s1g_tim_enc_block {
u8 enc_mode;
bool inverse;
const u8 *ptr;
u8 len;
/*
* For an OLB encoded block that spans multiple blocks, this
* is the offset into the span described by that encoded block.
*/
u8 olb_blk_offset;
};
/*
* Helper routines to quickly extract the length of an encoded block. Validation
* is also performed to ensure the length extracted lies within the TIM.
*/
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;
}
/*
* Enumerate all encoded blocks until we find the encoded block that describes
* our target AID. OLB is a special case as a single encoded block can describe
* multiple blocks as a single encoded block.
*/
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)
{
/* need at least block-control octet */
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);
/*
* An OLB encoded block can describe more then one
* block, meaning an encoded OLB block can span more
* then a single block.
*/
if (len > 0) {
/* Minus one for the length octet */
span = DIV_ROUND_UP(len - 1, 8);
/*
* Check if our target block lies within the
* block span described by this encoded block.
*/
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 our block bitmap does not contain a set bit that corresponds
* to our AID, it could mean a variety of things depending on if
* the encoding mode is inverted or not.
*
* 1. If inverted, it means the entire subblock is present and hence
* our AID has been set.
* 2. If not inverted, it means our subblock is not present and hence
* it is all zero meaning our AID is not set.
*/
if (!(blkmap & BIT(aid->target_subblk)))
return enc->inverse;
/*
* Increment ptr by the number of set subblocks that appear before our
* target subblock. If our target subblock is 0, do nothing as ptr
* already points to our target subblock.
*/
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)
{
/*
* Single AID mode describes, as the name suggests, a single AID
* within the block described by the encoded block. The octet
* contains the 6 LSBs of the AID described in the block. The other
* 2 bits are reserved. When inversed, every single AID described
* by the current block have buffered traffic except for the AID
* described in the single AID octet.
*/
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++;
/*
* Given an OLB encoded block that describes multiple blocks,
* calculate the offset into the span. Then calculate the
* subblock location normally.
*/
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;
}
/*
* An S1G PVB has 3 non optional encoding types, each that can be inverted.
* An S1G PVB is constructed with zero or more encoded block subfields. Each
* encoded block represents a single "block" of AIDs (64), and each encoded
* block can contain one of the 3 encoding types alongside a single bit for
* whether the bits should be inverted.
*
* As the standard makes no guarantee about the ordering of encoded blocks,
* we must parse every encoded block in the worst case scenario given an
* AID that lies within the last block.
*/
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;
/*
* Find our AIDs target encoded block and fill &enc_blk with the
* encoded blocks information. If no entry is found or an error
* occurs return false.
*/
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;
}
}
/**
* ieee80211_check_tim - check if AID bit is set in TIM
* @tim: the TIM IE
* @tim_len: length of the TIM IE
* @aid: the AID to look for
* @s1g: whether the TIM is from an S1G PPDU
* Return: whether or not traffic is indicated in the TIM for the given AID
*/
static inline bool ieee80211_check_tim(const struct ieee80211_tim_ie *tim,
u8 tim_len, u16 aid, bool s1g)
{
return s1g ? ieee80211_s1g_check_tim(tim, tim_len, aid) :
__ieee80211_check_tim(tim, tim_len, aid);
}
/**
* ieee80211_get_tdls_action - get TDLS action code
* @skb: the skb containing the frame, length will not be checked

View File

@@ -586,7 +586,7 @@ void ieee80211_mps_frame_release(struct sta_info *sta,
if (sta->mesh->plink_state == NL80211_PLINK_ESTAB)
has_buffered = ieee80211_check_tim(elems->tim, elems->tim_len,
sta->mesh->aid);
sta->mesh->aid, false);
if (has_buffered)
mps_dbg(sta->sdata, "%pM indicates buffered frames\n",

View File

@@ -7438,7 +7438,8 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
ncrc = elems->crc;
if (ieee80211_hw_check(&local->hw, PS_NULLFUNC_STACK) &&
ieee80211_check_tim(elems->tim, elems->tim_len, vif_cfg->aid)) {
ieee80211_check_tim(elems->tim, elems->tim_len, vif_cfg->aid,
vif_cfg->s1g)) {
if (local->hw.conf.dynamic_ps_timeout > 0) {
if (local->hw.conf.flags & IEEE80211_CONF_PS) {
local->hw.conf.flags &= ~IEEE80211_CONF_PS;