]>
Commit | Line | Data |
---|---|---|
269dfe23 JD |
1 | /* |
2 | * Common hostapd/wpa_supplicant HW features | |
3 | * Copyright (c) 2002-2013, Jouni Malinen <j@w1.fi> | |
4 | * Copyright (c) 2015, Qualcomm Atheros, Inc. | |
5 | * | |
6 | * This software may be distributed under the terms of the BSD license. | |
7 | * See README for more details. | |
8 | */ | |
9 | ||
10 | #include "includes.h" | |
11 | ||
12 | #include "common.h" | |
13 | #include "defs.h" | |
6d5d098f JD |
14 | #include "ieee802_11_defs.h" |
15 | #include "ieee802_11_common.h" | |
269dfe23 JD |
16 | #include "hw_features_common.h" |
17 | ||
18 | ||
19 | struct hostapd_channel_data * hw_get_channel_chan(struct hostapd_hw_modes *mode, | |
20 | int chan, int *freq) | |
21 | { | |
22 | int i; | |
23 | ||
24 | if (freq) | |
25 | *freq = 0; | |
26 | ||
27 | if (!mode) | |
28 | return NULL; | |
29 | ||
30 | for (i = 0; i < mode->num_channels; i++) { | |
31 | struct hostapd_channel_data *ch = &mode->channels[i]; | |
32 | if (ch->chan == chan) { | |
33 | if (freq) | |
34 | *freq = ch->freq; | |
35 | return ch; | |
36 | } | |
37 | } | |
38 | ||
39 | return NULL; | |
40 | } | |
41 | ||
42 | ||
43 | struct hostapd_channel_data * hw_get_channel_freq(struct hostapd_hw_modes *mode, | |
44 | int freq, int *chan) | |
45 | { | |
46 | int i; | |
47 | ||
48 | if (chan) | |
49 | *chan = 0; | |
50 | ||
51 | if (!mode) | |
52 | return NULL; | |
53 | ||
54 | for (i = 0; i < mode->num_channels; i++) { | |
55 | struct hostapd_channel_data *ch = &mode->channels[i]; | |
56 | if (ch->freq == freq) { | |
57 | if (chan) | |
58 | *chan = ch->chan; | |
59 | return ch; | |
60 | } | |
61 | } | |
62 | ||
63 | return NULL; | |
64 | } | |
65 | ||
66 | ||
67 | int hw_get_freq(struct hostapd_hw_modes *mode, int chan) | |
68 | { | |
69 | int freq; | |
70 | ||
71 | hw_get_channel_chan(mode, chan, &freq); | |
72 | ||
73 | return freq; | |
74 | } | |
75 | ||
76 | ||
77 | int hw_get_chan(struct hostapd_hw_modes *mode, int freq) | |
78 | { | |
79 | int chan; | |
80 | ||
81 | hw_get_channel_freq(mode, freq, &chan); | |
82 | ||
83 | return chan; | |
84 | } | |
51442743 JD |
85 | |
86 | ||
87 | int allowed_ht40_channel_pair(struct hostapd_hw_modes *mode, int pri_chan, | |
88 | int sec_chan) | |
89 | { | |
ce6d9ce1 | 90 | int ok, first; |
6128a909 | 91 | int allowed[] = { 36, 44, 52, 60, 100, 108, 116, 124, 132, 140, |
4ab0f11b | 92 | 149, 157, 165, 184, 192 }; |
51442743 | 93 | size_t k; |
ce6d9ce1 DL |
94 | struct hostapd_channel_data *p_chan, *s_chan; |
95 | const int ht40_plus = pri_chan < sec_chan; | |
51442743 | 96 | |
ce6d9ce1 DL |
97 | p_chan = hw_get_channel_chan(mode, pri_chan, NULL); |
98 | if (!p_chan) | |
99 | return 0; | |
100 | ||
101 | if (pri_chan == sec_chan || !sec_chan) { | |
102 | if (chan_pri_allowed(p_chan)) | |
103 | return 1; /* HT40 not used */ | |
104 | ||
105 | wpa_printf(MSG_ERROR, "Channel %d is not allowed as primary", | |
106 | pri_chan); | |
107 | return 0; | |
108 | } | |
109 | ||
110 | s_chan = hw_get_channel_chan(mode, sec_chan, NULL); | |
111 | if (!s_chan) | |
112 | return 0; | |
51442743 JD |
113 | |
114 | wpa_printf(MSG_DEBUG, | |
115 | "HT40: control channel: %d secondary channel: %d", | |
116 | pri_chan, sec_chan); | |
117 | ||
118 | /* Verify that HT40 secondary channel is an allowed 20 MHz | |
119 | * channel */ | |
ce6d9ce1 DL |
120 | if ((s_chan->flag & HOSTAPD_CHAN_DISABLED) || |
121 | (ht40_plus && !(p_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) || | |
122 | (!ht40_plus && !(p_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40M))) { | |
51442743 JD |
123 | wpa_printf(MSG_ERROR, "HT40 secondary channel %d not allowed", |
124 | sec_chan); | |
125 | return 0; | |
126 | } | |
127 | ||
128 | /* | |
129 | * Verify that HT40 primary,secondary channel pair is allowed per | |
130 | * IEEE 802.11n Annex J. This is only needed for 5 GHz band since | |
131 | * 2.4 GHz rules allow all cases where the secondary channel fits into | |
132 | * the list of allowed channels (already checked above). | |
133 | */ | |
134 | if (mode->mode != HOSTAPD_MODE_IEEE80211A) | |
135 | return 1; | |
136 | ||
137 | first = pri_chan < sec_chan ? pri_chan : sec_chan; | |
138 | ||
139 | ok = 0; | |
140 | for (k = 0; k < ARRAY_SIZE(allowed); k++) { | |
141 | if (first == allowed[k]) { | |
142 | ok = 1; | |
143 | break; | |
144 | } | |
145 | } | |
146 | if (!ok) { | |
147 | wpa_printf(MSG_ERROR, "HT40 channel pair (%d, %d) not allowed", | |
148 | pri_chan, sec_chan); | |
149 | return 0; | |
150 | } | |
151 | ||
152 | return 1; | |
153 | } | |
6d5d098f JD |
154 | |
155 | ||
156 | void get_pri_sec_chan(struct wpa_scan_res *bss, int *pri_chan, int *sec_chan) | |
157 | { | |
158 | struct ieee80211_ht_operation *oper; | |
159 | struct ieee802_11_elems elems; | |
160 | ||
161 | *pri_chan = *sec_chan = 0; | |
162 | ||
163 | ieee802_11_parse_elems((u8 *) (bss + 1), bss->ie_len, &elems, 0); | |
d6fefd64 | 164 | if (elems.ht_operation) { |
6d5d098f JD |
165 | oper = (struct ieee80211_ht_operation *) elems.ht_operation; |
166 | *pri_chan = oper->primary_chan; | |
167 | if (oper->ht_param & HT_INFO_HT_PARAM_STA_CHNL_WIDTH) { | |
168 | int sec = oper->ht_param & | |
169 | HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK; | |
170 | if (sec == HT_INFO_HT_PARAM_SECONDARY_CHNL_ABOVE) | |
171 | *sec_chan = *pri_chan + 4; | |
172 | else if (sec == HT_INFO_HT_PARAM_SECONDARY_CHNL_BELOW) | |
173 | *sec_chan = *pri_chan - 4; | |
174 | } | |
175 | } | |
176 | } | |
0e550fe4 JD |
177 | |
178 | ||
179 | int check_40mhz_5g(struct hostapd_hw_modes *mode, | |
180 | struct wpa_scan_results *scan_res, int pri_chan, | |
181 | int sec_chan) | |
182 | { | |
183 | int pri_freq, sec_freq, pri_bss, sec_bss; | |
184 | int bss_pri_chan, bss_sec_chan; | |
185 | size_t i; | |
186 | int match; | |
187 | ||
5ed65196 JM |
188 | if (!mode || !scan_res || !pri_chan || !sec_chan || |
189 | pri_chan == sec_chan) | |
0e550fe4 JD |
190 | return 0; |
191 | ||
192 | pri_freq = hw_get_freq(mode, pri_chan); | |
193 | sec_freq = hw_get_freq(mode, sec_chan); | |
194 | ||
195 | /* | |
196 | * Switch PRI/SEC channels if Beacons were detected on selected SEC | |
197 | * channel, but not on selected PRI channel. | |
198 | */ | |
199 | pri_bss = sec_bss = 0; | |
200 | for (i = 0; i < scan_res->num; i++) { | |
201 | struct wpa_scan_res *bss = scan_res->res[i]; | |
202 | if (bss->freq == pri_freq) | |
203 | pri_bss++; | |
204 | else if (bss->freq == sec_freq) | |
205 | sec_bss++; | |
206 | } | |
207 | if (sec_bss && !pri_bss) { | |
208 | wpa_printf(MSG_INFO, | |
209 | "Switch own primary and secondary channel to get secondary channel with no Beacons from other BSSes"); | |
210 | return 2; | |
211 | } | |
212 | ||
213 | /* | |
214 | * Match PRI/SEC channel with any existing HT40 BSS on the same | |
215 | * channels that we are about to use (if already mixed order in | |
216 | * existing BSSes, use own preference). | |
217 | */ | |
218 | match = 0; | |
219 | for (i = 0; i < scan_res->num; i++) { | |
220 | struct wpa_scan_res *bss = scan_res->res[i]; | |
221 | get_pri_sec_chan(bss, &bss_pri_chan, &bss_sec_chan); | |
222 | if (pri_chan == bss_pri_chan && | |
223 | sec_chan == bss_sec_chan) { | |
224 | match = 1; | |
225 | break; | |
226 | } | |
227 | } | |
228 | if (!match) { | |
229 | for (i = 0; i < scan_res->num; i++) { | |
230 | struct wpa_scan_res *bss = scan_res->res[i]; | |
231 | get_pri_sec_chan(bss, &bss_pri_chan, &bss_sec_chan); | |
232 | if (pri_chan == bss_sec_chan && | |
233 | sec_chan == bss_pri_chan) { | |
234 | wpa_printf(MSG_INFO, "Switch own primary and " | |
235 | "secondary channel due to BSS " | |
236 | "overlap with " MACSTR, | |
237 | MAC2STR(bss->bssid)); | |
238 | return 2; | |
239 | } | |
240 | } | |
241 | } | |
242 | ||
243 | return 1; | |
244 | } | |
fdd989d1 JD |
245 | |
246 | ||
1887be4f JM |
247 | static int check_20mhz_bss(struct wpa_scan_res *bss, int pri_freq, int start, |
248 | int end) | |
fdd989d1 JD |
249 | { |
250 | struct ieee802_11_elems elems; | |
251 | struct ieee80211_ht_operation *oper; | |
252 | ||
253 | if (bss->freq < start || bss->freq > end || bss->freq == pri_freq) | |
254 | return 0; | |
255 | ||
256 | ieee802_11_parse_elems((u8 *) (bss + 1), bss->ie_len, &elems, 0); | |
257 | if (!elems.ht_capabilities) { | |
258 | wpa_printf(MSG_DEBUG, "Found overlapping legacy BSS: " | |
259 | MACSTR " freq=%d", MAC2STR(bss->bssid), bss->freq); | |
260 | return 1; | |
261 | } | |
262 | ||
d6fefd64 | 263 | if (elems.ht_operation) { |
fdd989d1 JD |
264 | oper = (struct ieee80211_ht_operation *) elems.ht_operation; |
265 | if (oper->ht_param & HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK) | |
266 | return 0; | |
267 | ||
268 | wpa_printf(MSG_DEBUG, "Found overlapping 20 MHz HT BSS: " | |
269 | MACSTR " freq=%d", MAC2STR(bss->bssid), bss->freq); | |
270 | return 1; | |
271 | } | |
272 | return 0; | |
273 | } | |
a828f626 JD |
274 | |
275 | ||
276 | int check_40mhz_2g4(struct hostapd_hw_modes *mode, | |
277 | struct wpa_scan_results *scan_res, int pri_chan, | |
278 | int sec_chan) | |
279 | { | |
280 | int pri_freq, sec_freq; | |
281 | int affected_start, affected_end; | |
282 | size_t i; | |
283 | ||
35963617 JM |
284 | if (!mode || !scan_res || !pri_chan || !sec_chan || |
285 | pri_chan == sec_chan) | |
a828f626 JD |
286 | return 0; |
287 | ||
288 | pri_freq = hw_get_freq(mode, pri_chan); | |
289 | sec_freq = hw_get_freq(mode, sec_chan); | |
290 | ||
291 | affected_start = (pri_freq + sec_freq) / 2 - 25; | |
292 | affected_end = (pri_freq + sec_freq) / 2 + 25; | |
293 | wpa_printf(MSG_DEBUG, "40 MHz affected channel range: [%d,%d] MHz", | |
294 | affected_start, affected_end); | |
295 | for (i = 0; i < scan_res->num; i++) { | |
296 | struct wpa_scan_res *bss = scan_res->res[i]; | |
297 | int pri = bss->freq; | |
298 | int sec = pri; | |
299 | struct ieee802_11_elems elems; | |
300 | ||
301 | /* Check for overlapping 20 MHz BSS */ | |
302 | if (check_20mhz_bss(bss, pri_freq, affected_start, | |
303 | affected_end)) { | |
304 | wpa_printf(MSG_DEBUG, | |
305 | "Overlapping 20 MHz BSS is found"); | |
306 | return 0; | |
307 | } | |
308 | ||
309 | get_pri_sec_chan(bss, &pri_chan, &sec_chan); | |
310 | ||
311 | if (sec_chan) { | |
312 | if (sec_chan < pri_chan) | |
313 | sec = pri - 20; | |
314 | else | |
315 | sec = pri + 20; | |
316 | } | |
317 | ||
318 | if ((pri < affected_start || pri > affected_end) && | |
319 | (sec < affected_start || sec > affected_end)) | |
320 | continue; /* not within affected channel range */ | |
321 | ||
322 | wpa_printf(MSG_DEBUG, "Neighboring BSS: " MACSTR | |
323 | " freq=%d pri=%d sec=%d", | |
324 | MAC2STR(bss->bssid), bss->freq, pri_chan, sec_chan); | |
325 | ||
326 | if (sec_chan) { | |
327 | if (pri_freq != pri || sec_freq != sec) { | |
328 | wpa_printf(MSG_DEBUG, | |
329 | "40 MHz pri/sec mismatch with BSS " | |
330 | MACSTR | |
331 | " <%d,%d> (chan=%d%c) vs. <%d,%d>", | |
332 | MAC2STR(bss->bssid), | |
333 | pri, sec, pri_chan, | |
334 | sec > pri ? '+' : '-', | |
335 | pri_freq, sec_freq); | |
336 | return 0; | |
337 | } | |
338 | } | |
339 | ||
340 | ieee802_11_parse_elems((u8 *) (bss + 1), bss->ie_len, &elems, | |
341 | 0); | |
baae4cb9 | 342 | if (elems.ht_capabilities) { |
a828f626 JD |
343 | struct ieee80211_ht_capabilities *ht_cap = |
344 | (struct ieee80211_ht_capabilities *) | |
345 | elems.ht_capabilities; | |
346 | ||
347 | if (le_to_host16(ht_cap->ht_capabilities_info) & | |
348 | HT_CAP_INFO_40MHZ_INTOLERANT) { | |
349 | wpa_printf(MSG_DEBUG, | |
350 | "40 MHz Intolerant is set on channel %d in BSS " | |
351 | MACSTR, pri, MAC2STR(bss->bssid)); | |
352 | return 0; | |
353 | } | |
354 | } | |
355 | } | |
356 | ||
357 | return 1; | |
358 | } | |
ada157f3 JD |
359 | |
360 | ||
361 | int hostapd_set_freq_params(struct hostapd_freq_params *data, | |
362 | enum hostapd_hw_mode mode, | |
363 | int freq, int channel, int ht_enabled, | |
364 | int vht_enabled, int sec_channel_offset, | |
365 | int vht_oper_chwidth, int center_segment0, | |
366 | int center_segment1, u32 vht_caps) | |
367 | { | |
ada157f3 JD |
368 | os_memset(data, 0, sizeof(*data)); |
369 | data->mode = mode; | |
370 | data->freq = freq; | |
371 | data->channel = channel; | |
372 | data->ht_enabled = ht_enabled; | |
373 | data->vht_enabled = vht_enabled; | |
374 | data->sec_channel_offset = sec_channel_offset; | |
375 | data->center_freq1 = freq + sec_channel_offset * 10; | |
376 | data->center_freq2 = 0; | |
377 | data->bandwidth = sec_channel_offset ? 40 : 20; | |
378 | ||
379 | if (data->vht_enabled) switch (vht_oper_chwidth) { | |
380 | case VHT_CHANWIDTH_USE_HT: | |
5ed65196 JM |
381 | if (center_segment1 || |
382 | (center_segment0 != 0 && | |
383 | 5000 + center_segment0 * 5 != data->center_freq1 && | |
384 | 2407 + center_segment0 * 5 != data->center_freq1)) | |
ada157f3 JD |
385 | return -1; |
386 | break; | |
387 | case VHT_CHANWIDTH_80P80MHZ: | |
388 | if (!(vht_caps & VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ)) { | |
389 | wpa_printf(MSG_ERROR, | |
390 | "80+80 channel width is not supported!"); | |
391 | return -1; | |
392 | } | |
393 | if (center_segment1 == center_segment0 + 4 || | |
394 | center_segment1 == center_segment0 - 4) | |
395 | return -1; | |
396 | data->center_freq2 = 5000 + center_segment1 * 5; | |
397 | /* fall through */ | |
398 | case VHT_CHANWIDTH_80MHZ: | |
399 | data->bandwidth = 80; | |
4d6e79f8 JM |
400 | if ((vht_oper_chwidth == VHT_CHANWIDTH_80MHZ && |
401 | center_segment1) || | |
402 | (vht_oper_chwidth == VHT_CHANWIDTH_80P80MHZ && | |
403 | !center_segment1) || | |
5ed65196 | 404 | !sec_channel_offset) |
ada157f3 | 405 | return -1; |
857d9422 MM |
406 | if (!center_segment0) { |
407 | if (channel <= 48) | |
408 | center_segment0 = 42; | |
409 | else if (channel <= 64) | |
410 | center_segment0 = 58; | |
411 | else if (channel <= 112) | |
412 | center_segment0 = 106; | |
413 | else if (channel <= 128) | |
414 | center_segment0 = 122; | |
415 | else if (channel <= 144) | |
416 | center_segment0 = 138; | |
417 | else if (channel <= 161) | |
418 | center_segment0 = 155; | |
419 | data->center_freq1 = 5000 + center_segment0 * 5; | |
420 | } else { | |
421 | /* | |
422 | * Note: HT/VHT config and params are coupled. Check if | |
423 | * HT40 channel band is in VHT80 Pri channel band | |
424 | * configuration. | |
425 | */ | |
426 | if (center_segment0 == channel + 6 || | |
427 | center_segment0 == channel + 2 || | |
428 | center_segment0 == channel - 2 || | |
429 | center_segment0 == channel - 6) | |
430 | data->center_freq1 = 5000 + center_segment0 * 5; | |
431 | else | |
432 | return -1; | |
433 | } | |
ada157f3 JD |
434 | break; |
435 | case VHT_CHANWIDTH_160MHZ: | |
436 | data->bandwidth = 160; | |
437 | if (!(vht_caps & (VHT_CAP_SUPP_CHAN_WIDTH_160MHZ | | |
438 | VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ))) { | |
439 | wpa_printf(MSG_ERROR, | |
440 | "160MHZ channel width is not supported!"); | |
441 | return -1; | |
442 | } | |
443 | if (center_segment1) | |
444 | return -1; | |
445 | if (!sec_channel_offset) | |
446 | return -1; | |
857d9422 MM |
447 | /* |
448 | * Note: HT/VHT config and params are coupled. Check if | |
449 | * HT40 channel band is in VHT160 channel band configuration. | |
450 | */ | |
451 | if (center_segment0 == channel + 14 || | |
452 | center_segment0 == channel + 10 || | |
453 | center_segment0 == channel + 6 || | |
454 | center_segment0 == channel + 2 || | |
455 | center_segment0 == channel - 2 || | |
456 | center_segment0 == channel - 6 || | |
457 | center_segment0 == channel - 10 || | |
458 | center_segment0 == channel - 14) | |
459 | data->center_freq1 = 5000 + center_segment0 * 5; | |
460 | else | |
ada157f3 | 461 | return -1; |
ada157f3 JD |
462 | break; |
463 | } | |
464 | ||
465 | return 0; | |
466 | } | |
9eb5757a MH |
467 | |
468 | ||
469 | void set_disable_ht40(struct ieee80211_ht_capabilities *htcaps, | |
470 | int disabled) | |
471 | { | |
472 | /* Masking these out disables HT40 */ | |
473 | le16 msk = host_to_le16(HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET | | |
474 | HT_CAP_INFO_SHORT_GI40MHZ); | |
475 | ||
476 | if (disabled) | |
477 | htcaps->ht_capabilities_info &= ~msk; | |
478 | else | |
479 | htcaps->ht_capabilities_info |= msk; | |
480 | } | |
a7a638c2 MH |
481 | |
482 | ||
483 | #ifdef CONFIG_IEEE80211AC | |
484 | ||
485 | static int _ieee80211ac_cap_check(u32 hw, u32 conf, u32 cap, | |
486 | const char *name) | |
487 | { | |
488 | u32 req_cap = conf & cap; | |
489 | ||
490 | /* | |
491 | * Make sure we support all requested capabilities. | |
492 | * NOTE: We assume that 'cap' represents a capability mask, | |
493 | * not a discrete value. | |
494 | */ | |
495 | if ((hw & req_cap) != req_cap) { | |
496 | wpa_printf(MSG_ERROR, | |
497 | "Driver does not support configured VHT capability [%s]", | |
498 | name); | |
499 | return 0; | |
500 | } | |
501 | return 1; | |
502 | } | |
503 | ||
504 | ||
505 | static int ieee80211ac_cap_check_max(u32 hw, u32 conf, u32 mask, | |
506 | unsigned int shift, | |
507 | const char *name) | |
508 | { | |
509 | u32 hw_max = hw & mask; | |
510 | u32 conf_val = conf & mask; | |
511 | ||
512 | if (conf_val > hw_max) { | |
513 | wpa_printf(MSG_ERROR, | |
514 | "Configured VHT capability [%s] exceeds max value supported by the driver (%d > %d)", | |
515 | name, conf_val >> shift, hw_max >> shift); | |
516 | return 0; | |
517 | } | |
518 | return 1; | |
519 | } | |
520 | ||
521 | ||
522 | int ieee80211ac_cap_check(u32 hw, u32 conf) | |
523 | { | |
524 | #define VHT_CAP_CHECK(cap) \ | |
525 | do { \ | |
526 | if (!_ieee80211ac_cap_check(hw, conf, cap, #cap)) \ | |
527 | return 0; \ | |
528 | } while (0) | |
529 | ||
530 | #define VHT_CAP_CHECK_MAX(cap) \ | |
531 | do { \ | |
532 | if (!ieee80211ac_cap_check_max(hw, conf, cap, cap ## _SHIFT, \ | |
533 | #cap)) \ | |
534 | return 0; \ | |
535 | } while (0) | |
536 | ||
537 | VHT_CAP_CHECK_MAX(VHT_CAP_MAX_MPDU_LENGTH_MASK); | |
b0fc2ef3 | 538 | VHT_CAP_CHECK_MAX(VHT_CAP_SUPP_CHAN_WIDTH_MASK); |
a7a638c2 MH |
539 | VHT_CAP_CHECK(VHT_CAP_RXLDPC); |
540 | VHT_CAP_CHECK(VHT_CAP_SHORT_GI_80); | |
541 | VHT_CAP_CHECK(VHT_CAP_SHORT_GI_160); | |
542 | VHT_CAP_CHECK(VHT_CAP_TXSTBC); | |
543 | VHT_CAP_CHECK_MAX(VHT_CAP_RXSTBC_MASK); | |
544 | VHT_CAP_CHECK(VHT_CAP_SU_BEAMFORMER_CAPABLE); | |
545 | VHT_CAP_CHECK(VHT_CAP_SU_BEAMFORMEE_CAPABLE); | |
546 | VHT_CAP_CHECK_MAX(VHT_CAP_BEAMFORMEE_STS_MAX); | |
547 | VHT_CAP_CHECK_MAX(VHT_CAP_SOUNDING_DIMENSION_MAX); | |
548 | VHT_CAP_CHECK(VHT_CAP_MU_BEAMFORMER_CAPABLE); | |
549 | VHT_CAP_CHECK(VHT_CAP_MU_BEAMFORMEE_CAPABLE); | |
550 | VHT_CAP_CHECK(VHT_CAP_VHT_TXOP_PS); | |
551 | VHT_CAP_CHECK(VHT_CAP_HTC_VHT); | |
552 | VHT_CAP_CHECK_MAX(VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MAX); | |
553 | VHT_CAP_CHECK(VHT_CAP_VHT_LINK_ADAPTATION_VHT_UNSOL_MFB); | |
554 | VHT_CAP_CHECK(VHT_CAP_VHT_LINK_ADAPTATION_VHT_MRQ_MFB); | |
555 | VHT_CAP_CHECK(VHT_CAP_RX_ANTENNA_PATTERN); | |
556 | VHT_CAP_CHECK(VHT_CAP_TX_ANTENNA_PATTERN); | |
557 | ||
558 | #undef VHT_CAP_CHECK | |
559 | #undef VHT_CAP_CHECK_MAX | |
560 | ||
561 | return 1; | |
562 | } | |
563 | ||
564 | #endif /* CONFIG_IEEE80211AC */ | |
ce6d9ce1 DL |
565 | |
566 | ||
567 | u32 num_chan_to_bw(int num_chans) | |
568 | { | |
569 | switch (num_chans) { | |
570 | case 2: | |
571 | case 4: | |
572 | case 8: | |
573 | return num_chans * 20; | |
574 | default: | |
575 | return 20; | |
576 | } | |
577 | } | |
578 | ||
579 | ||
580 | /* check if BW is applicable for channel */ | |
581 | int chan_bw_allowed(const struct hostapd_channel_data *chan, u32 bw, | |
582 | int ht40_plus, int pri) | |
583 | { | |
584 | u32 bw_mask; | |
585 | ||
586 | switch (bw) { | |
587 | case 20: | |
588 | bw_mask = HOSTAPD_CHAN_WIDTH_20; | |
589 | break; | |
590 | case 40: | |
591 | /* HT 40 MHz support declared only for primary channel, | |
592 | * just skip 40 MHz secondary checking */ | |
593 | if (pri && ht40_plus) | |
594 | bw_mask = HOSTAPD_CHAN_WIDTH_40P; | |
595 | else if (pri && !ht40_plus) | |
596 | bw_mask = HOSTAPD_CHAN_WIDTH_40M; | |
597 | else | |
598 | bw_mask = 0; | |
599 | break; | |
600 | case 80: | |
601 | bw_mask = HOSTAPD_CHAN_WIDTH_80; | |
602 | break; | |
603 | case 160: | |
604 | bw_mask = HOSTAPD_CHAN_WIDTH_160; | |
605 | break; | |
606 | default: | |
607 | bw_mask = 0; | |
608 | break; | |
609 | } | |
610 | ||
611 | return (chan->allowed_bw & bw_mask) == bw_mask; | |
612 | } | |
613 | ||
614 | ||
615 | /* check if channel is allowed to be used as primary */ | |
616 | int chan_pri_allowed(const struct hostapd_channel_data *chan) | |
617 | { | |
618 | return !(chan->flag & HOSTAPD_CHAN_DISABLED) && | |
619 | (chan->allowed_bw & HOSTAPD_CHAN_WIDTH_20); | |
620 | } |