1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * KUnit tests for S1G TIM PVB decoding. This test suite covers 4 * IEEE80211-2024 Annex L figures 8, 9, 10, 12, 13, 14. ADE mode 5 * is not covered as it is an optional encoding format and is not 6 * currently supported by mac80211. 7 * 8 * Copyright (C) 2025 Morse Micro 9 */ 10 #include <linux/ieee80211.h> 11 #include <kunit/test.h> 12 #include <kunit/test-bug.h> 13 14 #define MAX_AID 128 15 16 #define BC(enc_mode, inverse, blk_off) \ 17 ((((blk_off) & 0x1f) << 3) | ((inverse) ? BIT(2) : 0) | \ 18 ((enc_mode) & 0x3)) 19 20 static void byte_to_bitstr(u8 v, char *out) 21 { 22 for (int b = 7; b >= 0; b--) 23 *out++ = (v & BIT(b)) ? '1' : '0'; 24 *out = '\0'; 25 } 26 27 static void dump_tim_bits(struct kunit *test, 28 const struct ieee80211_tim_ie *tim, u8 tim_len) 29 { 30 const u8 *ptr = tim->virtual_map; 31 const u8 *end = (const u8 *)tim + tim_len; 32 unsigned int oct = 1; 33 unsigned int blk = 0; 34 char bits[9]; 35 36 while (ptr < end) { 37 u8 ctrl = *ptr++; 38 u8 mode = ctrl & 0x03; 39 bool inverse = ctrl & BIT(2); 40 u8 blk_off = ctrl >> 3; 41 42 kunit_info( 43 test, "Block %u (ENC=%s, blk_off=%u, inverse=%u)", blk, 44 (mode == IEEE80211_S1G_TIM_ENC_MODE_BLOCK) ? "BLOCK" : 45 (mode == IEEE80211_S1G_TIM_ENC_MODE_SINGLE) ? "SINGLE" : 46 "OLB", 47 blk_off, inverse); 48 49 byte_to_bitstr(ctrl, bits); 50 kunit_info(test, " octet %2u (ctrl) : %s (0x%02x)", oct, 51 bits, ctrl); 52 ++oct; 53 54 switch (mode) { 55 case IEEE80211_S1G_TIM_ENC_MODE_BLOCK: { 56 u8 blkmap = *ptr++; 57 58 byte_to_bitstr(blkmap, bits); 59 kunit_info(test, " octet %2u (blk-map) : %s (0x%02x)", 60 oct, bits, blkmap); 61 ++oct; 62 63 for (u8 sb = 0; sb < 8; sb++) { 64 if (!(blkmap & BIT(sb))) 65 continue; 66 u8 sub = *ptr++; 67 68 byte_to_bitstr(sub, bits); 69 kunit_info( 70 test, 71 " octet %2u (SB %2u) : %s (0x%02x)", 72 oct, sb, bits, sub); 73 ++oct; 74 } 75 break; 76 } 77 case IEEE80211_S1G_TIM_ENC_MODE_SINGLE: { 78 u8 single = *ptr++; 79 80 byte_to_bitstr(single, bits); 81 kunit_info(test, " octet %2u (single) : %s (0x%02x)", 82 oct, bits, single); 83 ++oct; 84 break; 85 } 86 case IEEE80211_S1G_TIM_ENC_MODE_OLB: { 87 u8 len = *ptr++; 88 89 byte_to_bitstr(len, bits); 90 kunit_info(test, " octet %2u (len=%2u) : %s (0x%02x)", 91 oct, len, bits, len); 92 ++oct; 93 94 for (u8 i = 0; i < len && ptr < end; i++) { 95 u8 sub = *ptr++; 96 97 byte_to_bitstr(sub, bits); 98 kunit_info( 99 test, 100 " octet %2u (SB %2u) : %s (0x%02x)", 101 oct, i, bits, sub); 102 ++oct; 103 } 104 break; 105 } 106 default: 107 kunit_info(test, " ** unknown encoding 0x%x **", mode); 108 return; 109 } 110 blk++; 111 } 112 } 113 114 static void tim_push(u8 **p, u8 v) 115 { 116 *(*p)++ = v; 117 } 118 119 static void tim_begin(struct ieee80211_tim_ie *tim, u8 **p) 120 { 121 tim->dtim_count = 0; 122 tim->dtim_period = 1; 123 tim->bitmap_ctrl = 0; 124 *p = tim->virtual_map; 125 } 126 127 static u8 tim_end(struct ieee80211_tim_ie *tim, u8 *tail) 128 { 129 return tail - (u8 *)tim; 130 } 131 132 static void pvb_add_block_bitmap(u8 **p, u8 blk_off, bool inverse, u8 blk_bmap, 133 const u8 *subblocks) 134 { 135 u8 enc = IEEE80211_S1G_TIM_ENC_MODE_BLOCK; 136 u8 n = hweight8(blk_bmap); 137 138 tim_push(p, BC(enc, inverse, blk_off)); 139 tim_push(p, blk_bmap); 140 141 for (u8 i = 0; i < n; i++) 142 tim_push(p, subblocks[i]); 143 } 144 145 static void pvb_add_single_aid(u8 **p, u8 blk_off, bool inverse, u8 single6) 146 { 147 u8 enc = IEEE80211_S1G_TIM_ENC_MODE_SINGLE; 148 149 tim_push(p, BC(enc, inverse, blk_off)); 150 tim_push(p, single6 & GENMASK(5, 0)); 151 } 152 153 static void pvb_add_olb(u8 **p, u8 blk_off, bool inverse, const u8 *subblocks, 154 u8 len) 155 { 156 u8 enc = IEEE80211_S1G_TIM_ENC_MODE_OLB; 157 158 tim_push(p, BC(enc, inverse, blk_off)); 159 tim_push(p, len); 160 for (u8 i = 0; i < len; i++) 161 tim_push(p, subblocks[i]); 162 } 163 164 static void check_all_aids(struct kunit *test, 165 const struct ieee80211_tim_ie *tim, u8 tim_len, 166 const unsigned long *expected) 167 { 168 for (u16 aid = 1; aid <= MAX_AID; aid++) { 169 bool want = test_bit(aid, expected); 170 bool got = ieee80211_s1g_check_tim(tim, tim_len, aid); 171 172 KUNIT_ASSERT_EQ_MSG(test, got, want, 173 "AID %u mismatch (got=%d want=%d)", aid, 174 got, want); 175 } 176 } 177 178 static void fill_bitmap(unsigned long *bm, const u16 *list, size_t n) 179 { 180 size_t i; 181 182 bitmap_zero(bm, MAX_AID + 1); 183 for (i = 0; i < n; i++) 184 __set_bit(list[i], bm); 185 } 186 187 static void fill_bitmap_inverse(unsigned long *bm, u16 max_aid, 188 const u16 *except, size_t n_except) 189 { 190 bitmap_zero(bm, MAX_AID + 1); 191 for (u16 aid = 1; aid <= max_aid; aid++) 192 __set_bit(aid, bm); 193 194 for (size_t i = 0; i < n_except; i++) 195 if (except[i] <= max_aid) 196 __clear_bit(except[i], bm); 197 } 198 199 static void s1g_tim_block_test(struct kunit *test) 200 { 201 u8 buf[256] = {}; 202 struct ieee80211_tim_ie *tim = (void *)buf; 203 u8 *p, tim_len; 204 static const u8 subblocks[] = { 205 0x42, /* SB m=0: AIDs 1,6 */ 206 0xA0, /* SB m=2: AIDs 21,23 */ 207 }; 208 u8 blk_bmap = 0x05; /* bits 0 and 2 set */ 209 bool inverse = false; 210 static const u16 set_list[] = { 1, 6, 21, 23 }; 211 DECLARE_BITMAP(exp, MAX_AID + 1); 212 213 tim_begin(tim, &p); 214 pvb_add_block_bitmap(&p, 0, inverse, blk_bmap, subblocks); 215 tim_len = tim_end(tim, p); 216 217 fill_bitmap(exp, set_list, ARRAY_SIZE(set_list)); 218 219 dump_tim_bits(test, tim, tim_len); 220 check_all_aids(test, tim, tim_len, exp); 221 } 222 223 static void s1g_tim_single_test(struct kunit *test) 224 { 225 u8 buf[256] = {}; 226 struct ieee80211_tim_ie *tim = (void *)buf; 227 u8 *p, tim_len; 228 bool inverse = false; 229 u8 blk_off = 0; 230 u8 single6 = 0x1f; /* 31 */ 231 static const u16 set_list[] = { 31 }; 232 DECLARE_BITMAP(exp, MAX_AID + 1); 233 234 tim_begin(tim, &p); 235 pvb_add_single_aid(&p, blk_off, inverse, single6); 236 tim_len = tim_end(tim, p); 237 238 fill_bitmap(exp, set_list, ARRAY_SIZE(set_list)); 239 240 dump_tim_bits(test, tim, tim_len); 241 check_all_aids(test, tim, tim_len, exp); 242 } 243 244 static void s1g_tim_olb_test(struct kunit *test) 245 { 246 u8 buf[256] = {}; 247 struct ieee80211_tim_ie *tim = (void *)buf; 248 u8 *p, tim_len; 249 bool inverse = false; 250 u8 blk_off = 0; 251 static const u16 set_list[] = { 1, 6, 13, 15, 17, 22, 29, 31, 33, 252 38, 45, 47, 49, 54, 61, 63, 65, 70 }; 253 static const u8 subblocks[] = { 0x42, 0xA0, 0x42, 0xA0, 0x42, 254 0xA0, 0x42, 0xA0, 0x42 }; 255 u8 len = ARRAY_SIZE(subblocks); 256 DECLARE_BITMAP(exp, MAX_AID + 1); 257 258 tim_begin(tim, &p); 259 pvb_add_olb(&p, blk_off, inverse, subblocks, len); 260 tim_len = tim_end(tim, p); 261 262 fill_bitmap(exp, set_list, ARRAY_SIZE(set_list)); 263 264 dump_tim_bits(test, tim, tim_len); 265 check_all_aids(test, tim, tim_len, exp); 266 } 267 268 static void s1g_tim_inverse_block_test(struct kunit *test) 269 { 270 u8 buf[256] = {}; 271 struct ieee80211_tim_ie *tim = (void *)buf; 272 u8 *p, tim_len; 273 /* Same sub-block content as Figure L-8, but inverse = true */ 274 static const u8 subblocks[] = { 275 0x42, /* SB m=0: AIDs 1,6 */ 276 0xA0, /* SB m=2: AIDs 21,23 */ 277 }; 278 u8 blk_bmap = 0x05; 279 bool inverse = true; 280 /* All AIDs except 1,6,21,23 are set */ 281 static const u16 except[] = { 1, 6, 21, 23 }; 282 DECLARE_BITMAP(exp, MAX_AID + 1); 283 284 tim_begin(tim, &p); 285 pvb_add_block_bitmap(&p, 0, inverse, blk_bmap, subblocks); 286 tim_len = tim_end(tim, p); 287 288 fill_bitmap_inverse(exp, 63, except, ARRAY_SIZE(except)); 289 290 dump_tim_bits(test, tim, tim_len); 291 check_all_aids(test, tim, tim_len, exp); 292 } 293 294 static void s1g_tim_inverse_single_test(struct kunit *test) 295 { 296 u8 buf[256] = {}; 297 struct ieee80211_tim_ie *tim = (void *)buf; 298 u8 *p, tim_len; 299 bool inverse = true; 300 u8 blk_off = 0; 301 u8 single6 = 0x1f; /* 31 */ 302 /* All AIDs except 31 are set */ 303 static const u16 except[] = { 31 }; 304 DECLARE_BITMAP(exp, MAX_AID + 1); 305 306 tim_begin(tim, &p); 307 pvb_add_single_aid(&p, blk_off, inverse, single6); 308 tim_len = tim_end(tim, p); 309 310 fill_bitmap_inverse(exp, 63, except, ARRAY_SIZE(except)); 311 312 dump_tim_bits(test, tim, tim_len); 313 check_all_aids(test, tim, tim_len, exp); 314 } 315 316 static void s1g_tim_inverse_olb_test(struct kunit *test) 317 { 318 u8 buf[256] = {}; 319 struct ieee80211_tim_ie *tim = (void *)buf; 320 u8 *p, tim_len; 321 bool inverse = true; 322 u8 blk_off = 0, len; 323 /* All AIDs except the list below are set */ 324 static const u16 except[] = { 1, 6, 13, 15, 17, 22, 29, 31, 33, 325 38, 45, 47, 49, 54, 61, 63, 65, 70 }; 326 static const u8 subblocks[] = { 0x42, 0xA0, 0x42, 0xA0, 0x42, 327 0xA0, 0x42, 0xA0, 0x42 }; 328 len = ARRAY_SIZE(subblocks); 329 DECLARE_BITMAP(exp, MAX_AID + 1); 330 331 tim_begin(tim, &p); 332 pvb_add_olb(&p, blk_off, inverse, subblocks, len); 333 tim_len = tim_end(tim, p); 334 335 fill_bitmap_inverse(exp, 127, except, ARRAY_SIZE(except)); 336 337 dump_tim_bits(test, tim, tim_len); 338 check_all_aids(test, tim, tim_len, exp); 339 } 340 341 static struct kunit_case s1g_tim_test_cases[] = { 342 KUNIT_CASE(s1g_tim_block_test), 343 KUNIT_CASE(s1g_tim_single_test), 344 KUNIT_CASE(s1g_tim_olb_test), 345 KUNIT_CASE(s1g_tim_inverse_block_test), 346 KUNIT_CASE(s1g_tim_inverse_single_test), 347 KUNIT_CASE(s1g_tim_inverse_olb_test), 348 {} 349 }; 350 351 static struct kunit_suite s1g_tim = { 352 .name = "mac80211-s1g-tim", 353 .test_cases = s1g_tim_test_cases, 354 }; 355 356 kunit_test_suite(s1g_tim); 357