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
byte_to_bitstr(u8 v,char * out)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
dump_tim_bits(struct kunit * test,const struct ieee80211_tim_ie * tim,u8 tim_len)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
tim_push(u8 ** p,u8 v)114 static void tim_push(u8 **p, u8 v)
115 {
116 *(*p)++ = v;
117 }
118
tim_begin(struct ieee80211_tim_ie * tim,u8 ** p)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
tim_end(struct ieee80211_tim_ie * tim,u8 * tail)127 static u8 tim_end(struct ieee80211_tim_ie *tim, u8 *tail)
128 {
129 return tail - (u8 *)tim;
130 }
131
pvb_add_block_bitmap(u8 ** p,u8 blk_off,bool inverse,u8 blk_bmap,const u8 * subblocks)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
pvb_add_single_aid(u8 ** p,u8 blk_off,bool inverse,u8 single6)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
pvb_add_olb(u8 ** p,u8 blk_off,bool inverse,const u8 * subblocks,u8 len)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
check_all_aids(struct kunit * test,const struct ieee80211_tim_ie * tim,u8 tim_len,const unsigned long * expected)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
fill_bitmap(unsigned long * bm,const u16 * list,size_t n)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
fill_bitmap_inverse(unsigned long * bm,u16 max_aid,const u16 * except,size_t n_except)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
s1g_tim_block_test(struct kunit * test)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
s1g_tim_single_test(struct kunit * test)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
s1g_tim_olb_test(struct kunit * test)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
s1g_tim_inverse_block_test(struct kunit * test)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
s1g_tim_inverse_single_test(struct kunit * test)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
s1g_tim_inverse_olb_test(struct kunit * test)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