]>
Commit | Line | Data |
---|---|---|
a0413b17 MB |
1 | /* |
2 | * Wi-Fi Multimedia Admission Control (WMM-AC) | |
3 | * Copyright(c) 2014, Intel Mobile Communication GmbH. | |
4 | * Copyright(c) 2014, Intel Corporation. All rights reserved. | |
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 "utils/common.h" | |
674f6c07 | 13 | #include "utils/list.h" |
d1f88001 | 14 | #include "utils/eloop.h" |
a0413b17 MB |
15 | #include "common/ieee802_11_common.h" |
16 | #include "wpa_supplicant_i.h" | |
674f6c07 | 17 | #include "bss.h" |
a0413b17 MB |
18 | #include "driver_i.h" |
19 | #include "wmm_ac.h" | |
20 | ||
d1f88001 | 21 | static void wmm_ac_addts_req_timeout(void *eloop_ctx, void *timeout_ctx); |
a0413b17 | 22 | |
674f6c07 EP |
23 | static const enum wmm_ac up_to_ac[8] = { |
24 | WMM_AC_BK, | |
25 | WMM_AC_BE, | |
26 | WMM_AC_BE, | |
27 | WMM_AC_BK, | |
28 | WMM_AC_VI, | |
29 | WMM_AC_VI, | |
30 | WMM_AC_VO, | |
31 | WMM_AC_VO | |
32 | }; | |
33 | ||
34 | ||
35 | static inline u8 wmm_ac_get_tsid(const struct wmm_tspec_element *tspec) | |
36 | { | |
37 | return (tspec->ts_info[0] >> 1) & 0x0f; | |
38 | } | |
39 | ||
40 | ||
d1f88001 MB |
41 | static u8 wmm_ac_get_direction(const struct wmm_tspec_element *tspec) |
42 | { | |
43 | return (tspec->ts_info[0] >> 5) & 0x03; | |
44 | } | |
45 | ||
46 | ||
47 | static u8 wmm_ac_get_user_priority(const struct wmm_tspec_element *tspec) | |
48 | { | |
49 | return (tspec->ts_info[1] >> 3) & 0x07; | |
50 | } | |
51 | ||
52 | ||
53 | static u8 wmm_ac_direction_to_idx(u8 direction) | |
54 | { | |
55 | switch (direction) { | |
56 | case WMM_AC_DIR_UPLINK: | |
57 | return TS_DIR_IDX_UPLINK; | |
58 | case WMM_AC_DIR_DOWNLINK: | |
59 | return TS_DIR_IDX_DOWNLINK; | |
60 | case WMM_AC_DIR_BIDIRECTIONAL: | |
61 | return TS_DIR_IDX_BIDI; | |
62 | default: | |
63 | wpa_printf(MSG_ERROR, "Invalid direction: %d", direction); | |
64 | return WMM_AC_DIR_UPLINK; | |
65 | } | |
66 | } | |
67 | ||
68 | ||
69 | static int wmm_ac_add_ts(struct wpa_supplicant *wpa_s, const u8 *addr, | |
70 | const struct wmm_tspec_element *tspec) | |
71 | { | |
72 | struct wmm_tspec_element *_tspec; | |
73 | int ret; | |
74 | u16 admitted_time = le_to_host16(tspec->medium_time); | |
75 | u8 up = wmm_ac_get_user_priority(tspec); | |
76 | u8 ac = up_to_ac[up]; | |
77 | u8 dir = wmm_ac_get_direction(tspec); | |
78 | u8 tsid = wmm_ac_get_tsid(tspec); | |
79 | enum ts_dir_idx idx = wmm_ac_direction_to_idx(dir); | |
80 | ||
81 | /* should have been verified before, but double-check here */ | |
82 | if (wpa_s->tspecs[ac][idx]) { | |
83 | wpa_printf(MSG_ERROR, | |
84 | "WMM AC: tspec (ac=%d, dir=%d) already exists!", | |
85 | ac, dir); | |
86 | return -1; | |
87 | } | |
88 | ||
89 | /* copy tspec */ | |
a1f11e34 | 90 | _tspec = os_memdup(tspec, sizeof(*_tspec)); |
d1f88001 MB |
91 | if (!_tspec) |
92 | return -1; | |
93 | ||
d1f88001 MB |
94 | if (dir != WMM_AC_DIR_DOWNLINK) { |
95 | ret = wpa_drv_add_ts(wpa_s, tsid, addr, up, admitted_time); | |
96 | wpa_printf(MSG_DEBUG, | |
97 | "WMM AC: Add TS: addr=" MACSTR | |
98 | " TSID=%u admitted time=%u, ret=%d", | |
99 | MAC2STR(addr), tsid, admitted_time, ret); | |
100 | if (ret < 0) { | |
101 | os_free(_tspec); | |
102 | return -1; | |
103 | } | |
104 | } | |
105 | ||
106 | wpa_s->tspecs[ac][idx] = _tspec; | |
107 | ||
108 | wpa_printf(MSG_DEBUG, "Traffic stream was created successfully"); | |
109 | ||
110 | wpa_msg(wpa_s, MSG_INFO, WMM_AC_EVENT_TSPEC_ADDED | |
111 | "tsid=%d addr=" MACSTR " admitted_time=%d", | |
112 | tsid, MAC2STR(addr), admitted_time); | |
113 | ||
114 | return 0; | |
115 | } | |
116 | ||
117 | ||
118 | static void wmm_ac_del_ts_idx(struct wpa_supplicant *wpa_s, u8 ac, | |
119 | enum ts_dir_idx dir) | |
120 | { | |
121 | struct wmm_tspec_element *tspec = wpa_s->tspecs[ac][dir]; | |
122 | u8 tsid; | |
123 | ||
124 | if (!tspec) | |
125 | return; | |
126 | ||
127 | tsid = wmm_ac_get_tsid(tspec); | |
128 | wpa_printf(MSG_DEBUG, "WMM AC: Del TS ac=%d tsid=%d", ac, tsid); | |
129 | ||
130 | /* update the driver in case of uplink/bidi */ | |
131 | if (wmm_ac_get_direction(tspec) != WMM_AC_DIR_DOWNLINK) | |
132 | wpa_drv_del_ts(wpa_s, tsid, wpa_s->bssid); | |
133 | ||
134 | wpa_msg(wpa_s, MSG_INFO, WMM_AC_EVENT_TSPEC_REMOVED | |
135 | "tsid=%d addr=" MACSTR, tsid, MAC2STR(wpa_s->bssid)); | |
136 | ||
137 | os_free(wpa_s->tspecs[ac][dir]); | |
138 | wpa_s->tspecs[ac][dir] = NULL; | |
139 | } | |
140 | ||
141 | ||
142 | static void wmm_ac_del_req(struct wpa_supplicant *wpa_s, int failed) | |
143 | { | |
144 | struct wmm_ac_addts_request *req = wpa_s->addts_request; | |
145 | ||
146 | if (!req) | |
147 | return; | |
148 | ||
149 | if (failed) | |
150 | wpa_msg(wpa_s, MSG_INFO, WMM_AC_EVENT_TSPEC_REQ_FAILED | |
151 | "tsid=%u", wmm_ac_get_tsid(&req->tspec)); | |
152 | ||
153 | eloop_cancel_timeout(wmm_ac_addts_req_timeout, wpa_s, req); | |
154 | wpa_s->addts_request = NULL; | |
155 | os_free(req); | |
156 | } | |
157 | ||
158 | ||
159 | static void wmm_ac_addts_req_timeout(void *eloop_ctx, void *timeout_ctx) | |
160 | { | |
161 | struct wpa_supplicant *wpa_s = eloop_ctx; | |
162 | struct wmm_ac_addts_request *addts_req = timeout_ctx; | |
163 | ||
164 | wpa_printf(MSG_DEBUG, | |
165 | "Timeout getting ADDTS response (tsid=%d up=%d)", | |
166 | wmm_ac_get_tsid(&addts_req->tspec), | |
167 | wmm_ac_get_user_priority(&addts_req->tspec)); | |
168 | ||
169 | wmm_ac_del_req(wpa_s, 1); | |
170 | } | |
171 | ||
172 | ||
674f6c07 EP |
173 | static int wmm_ac_send_addts_request(struct wpa_supplicant *wpa_s, |
174 | const struct wmm_ac_addts_request *req) | |
175 | { | |
176 | struct wpabuf *buf; | |
177 | int ret; | |
178 | ||
179 | wpa_printf(MSG_DEBUG, "Sending ADDTS Request to " MACSTR, | |
180 | MAC2STR(req->address)); | |
181 | ||
182 | /* category + action code + dialog token + status + sizeof(tspec) */ | |
183 | buf = wpabuf_alloc(4 + sizeof(req->tspec)); | |
184 | if (!buf) { | |
185 | wpa_printf(MSG_ERROR, "WMM AC: Allocation error"); | |
186 | return -1; | |
187 | } | |
188 | ||
189 | wpabuf_put_u8(buf, WLAN_ACTION_WMM); | |
190 | wpabuf_put_u8(buf, WMM_ACTION_CODE_ADDTS_REQ); | |
191 | wpabuf_put_u8(buf, req->dialog_token); | |
192 | wpabuf_put_u8(buf, 0); /* status code */ | |
193 | wpabuf_put_data(buf, &req->tspec, sizeof(req->tspec)); | |
194 | ||
195 | ret = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, req->address, | |
196 | wpa_s->own_addr, wpa_s->bssid, | |
197 | wpabuf_head(buf), wpabuf_len(buf), 0); | |
198 | if (ret) { | |
199 | wpa_printf(MSG_WARNING, | |
200 | "WMM AC: Failed to send ADDTS Request"); | |
201 | } | |
202 | ||
203 | wpabuf_free(buf); | |
204 | return ret; | |
205 | } | |
206 | ||
207 | ||
208 | static int wmm_ac_send_delts(struct wpa_supplicant *wpa_s, | |
209 | const struct wmm_tspec_element *tspec, | |
210 | const u8 *address) | |
211 | { | |
212 | struct wpabuf *buf; | |
213 | int ret; | |
214 | ||
215 | /* category + action code + dialog token + status + sizeof(tspec) */ | |
216 | buf = wpabuf_alloc(4 + sizeof(*tspec)); | |
217 | if (!buf) | |
218 | return -1; | |
219 | ||
220 | wpa_printf(MSG_DEBUG, "Sending DELTS to " MACSTR, MAC2STR(address)); | |
221 | ||
222 | /* category + action code + dialog token + status + sizeof(tspec) */ | |
223 | wpabuf_put_u8(buf, WLAN_ACTION_WMM); | |
224 | wpabuf_put_u8(buf, WMM_ACTION_CODE_DELTS); | |
225 | wpabuf_put_u8(buf, 0); /* Dialog Token (not used) */ | |
226 | wpabuf_put_u8(buf, 0); /* Status Code (not used) */ | |
227 | wpabuf_put_data(buf, tspec, sizeof(*tspec)); | |
228 | ||
229 | ret = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, address, | |
230 | wpa_s->own_addr, wpa_s->bssid, | |
231 | wpabuf_head(buf), wpabuf_len(buf), 0); | |
232 | if (ret) | |
233 | wpa_printf(MSG_WARNING, "Failed to send DELTS frame"); | |
234 | ||
235 | wpabuf_free(buf); | |
236 | return ret; | |
237 | } | |
238 | ||
239 | ||
240 | /* return the AC using the given TSPEC tid */ | |
241 | static int wmm_ac_find_tsid(struct wpa_supplicant *wpa_s, u8 tsid, | |
242 | enum ts_dir_idx *dir) | |
243 | { | |
244 | int ac; | |
245 | enum ts_dir_idx idx; | |
246 | ||
247 | for (ac = 0; ac < WMM_AC_NUM; ac++) { | |
248 | for (idx = 0; idx < TS_DIR_IDX_COUNT; idx++) { | |
249 | if (wpa_s->tspecs[ac][idx] && | |
250 | wmm_ac_get_tsid(wpa_s->tspecs[ac][idx]) == tsid) { | |
251 | if (dir) | |
252 | *dir = idx; | |
253 | return ac; | |
254 | } | |
255 | } | |
256 | } | |
257 | ||
258 | return -1; | |
259 | } | |
260 | ||
261 | ||
262 | static struct wmm_ac_addts_request * | |
263 | wmm_ac_build_addts_req(struct wpa_supplicant *wpa_s, | |
264 | const struct wmm_ac_ts_setup_params *params, | |
265 | const u8 *address) | |
266 | { | |
267 | struct wmm_ac_addts_request *addts_req; | |
268 | struct wmm_tspec_element *tspec; | |
269 | u8 ac = up_to_ac[params->user_priority]; | |
270 | u8 uapsd = wpa_s->wmm_ac_assoc_info->ac_params[ac].uapsd; | |
271 | ||
272 | addts_req = os_zalloc(sizeof(*addts_req)); | |
273 | if (!addts_req) | |
274 | return NULL; | |
275 | ||
276 | tspec = &addts_req->tspec; | |
277 | os_memcpy(addts_req->address, address, ETH_ALEN); | |
278 | ||
279 | /* The dialog token cannot be zero */ | |
280 | if (++wpa_s->wmm_ac_last_dialog_token == 0) | |
281 | wpa_s->wmm_ac_last_dialog_token++; | |
282 | ||
283 | addts_req->dialog_token = wpa_s->wmm_ac_last_dialog_token; | |
284 | tspec->eid = WLAN_EID_VENDOR_SPECIFIC; | |
285 | tspec->length = sizeof(*tspec) - 2; /* reduce eid and length */ | |
286 | tspec->oui[0] = 0x00; | |
287 | tspec->oui[1] = 0x50; | |
288 | tspec->oui[2] = 0xf2; | |
289 | tspec->oui_type = WMM_OUI_TYPE; | |
290 | tspec->oui_subtype = WMM_OUI_SUBTYPE_TSPEC_ELEMENT; | |
291 | tspec->version = WMM_VERSION; | |
292 | ||
293 | tspec->ts_info[0] = params->tsid << 1; | |
294 | tspec->ts_info[0] |= params->direction << 5; | |
295 | tspec->ts_info[0] |= WMM_AC_ACCESS_POLICY_EDCA << 7; | |
296 | tspec->ts_info[1] = uapsd << 2; | |
297 | tspec->ts_info[1] |= params->user_priority << 3; | |
298 | tspec->ts_info[2] = 0; | |
299 | ||
300 | tspec->nominal_msdu_size = host_to_le16(params->nominal_msdu_size); | |
301 | if (params->fixed_nominal_msdu) | |
302 | tspec->nominal_msdu_size |= | |
303 | host_to_le16(WMM_AC_FIXED_MSDU_SIZE); | |
304 | ||
305 | tspec->mean_data_rate = host_to_le32(params->mean_data_rate); | |
306 | tspec->minimum_phy_rate = host_to_le32(params->minimum_phy_rate); | |
307 | tspec->surplus_bandwidth_allowance = | |
308 | host_to_le16(params->surplus_bandwidth_allowance); | |
309 | ||
310 | return addts_req; | |
311 | } | |
312 | ||
313 | ||
314 | static int param_in_range(const char *name, long value, | |
315 | long min_val, long max_val) | |
316 | { | |
317 | if (value < min_val || (max_val >= 0 && value > max_val)) { | |
318 | wpa_printf(MSG_DEBUG, | |
319 | "WMM AC: param %s (%ld) is out of range (%ld-%ld)", | |
320 | name, value, min_val, max_val); | |
321 | return 0; | |
322 | } | |
323 | ||
324 | return 1; | |
325 | } | |
326 | ||
327 | ||
328 | static int wmm_ac_should_replace_ts(struct wpa_supplicant *wpa_s, | |
329 | u8 tsid, u8 ac, u8 dir) | |
330 | { | |
331 | enum ts_dir_idx idx; | |
332 | int cur_ac, existing_ts = 0, replace_ts = 0; | |
333 | ||
334 | cur_ac = wmm_ac_find_tsid(wpa_s, tsid, &idx); | |
335 | if (cur_ac >= 0) { | |
336 | if (cur_ac != ac) { | |
337 | wpa_printf(MSG_DEBUG, | |
338 | "WMM AC: TSID %i already exists on different ac (%d)", | |
339 | tsid, cur_ac); | |
340 | return -1; | |
341 | } | |
342 | ||
343 | /* same tsid - this tspec will replace the current one */ | |
344 | replace_ts |= BIT(idx); | |
345 | } | |
346 | ||
347 | for (idx = 0; idx < TS_DIR_IDX_COUNT; idx++) { | |
348 | if (wpa_s->tspecs[ac][idx]) | |
349 | existing_ts |= BIT(idx); | |
350 | } | |
351 | ||
352 | switch (dir) { | |
353 | case WMM_AC_DIR_UPLINK: | |
354 | /* replace existing uplink/bidi tspecs */ | |
355 | replace_ts |= existing_ts & (BIT(TS_DIR_IDX_UPLINK) | | |
356 | BIT(TS_DIR_IDX_BIDI)); | |
357 | break; | |
358 | case WMM_AC_DIR_DOWNLINK: | |
359 | /* replace existing downlink/bidi tspecs */ | |
360 | replace_ts |= existing_ts & (BIT(TS_DIR_IDX_DOWNLINK) | | |
361 | BIT(TS_DIR_IDX_BIDI)); | |
362 | break; | |
363 | case WMM_AC_DIR_BIDIRECTIONAL: | |
364 | /* replace all existing tspecs */ | |
365 | replace_ts |= existing_ts; | |
366 | break; | |
367 | default: | |
368 | return -1; | |
369 | } | |
370 | ||
371 | return replace_ts; | |
372 | } | |
373 | ||
374 | ||
375 | static int wmm_ac_ts_req_is_valid(struct wpa_supplicant *wpa_s, | |
376 | const struct wmm_ac_ts_setup_params *params) | |
377 | { | |
378 | enum wmm_ac req_ac; | |
379 | ||
380 | #define PARAM_IN_RANGE(field, min_value, max_value) \ | |
381 | param_in_range(#field, params->field, min_value, max_value) | |
382 | ||
383 | if (!PARAM_IN_RANGE(tsid, 0, WMM_AC_MAX_TID) || | |
384 | !PARAM_IN_RANGE(user_priority, 0, WMM_AC_MAX_USER_PRIORITY) || | |
385 | !PARAM_IN_RANGE(nominal_msdu_size, 1, WMM_AC_MAX_NOMINAL_MSDU) || | |
386 | !PARAM_IN_RANGE(mean_data_rate, 1, -1) || | |
387 | !PARAM_IN_RANGE(minimum_phy_rate, 1, -1) || | |
388 | !PARAM_IN_RANGE(surplus_bandwidth_allowance, WMM_AC_MIN_SBA_UNITY, | |
389 | -1)) | |
390 | return 0; | |
391 | #undef PARAM_IN_RANGE | |
392 | ||
393 | if (!(params->direction == WMM_TSPEC_DIRECTION_UPLINK || | |
394 | params->direction == WMM_TSPEC_DIRECTION_DOWNLINK || | |
395 | params->direction == WMM_TSPEC_DIRECTION_BI_DIRECTIONAL)) { | |
396 | wpa_printf(MSG_DEBUG, "WMM AC: invalid TS direction: %d", | |
397 | params->direction); | |
398 | return 0; | |
399 | } | |
400 | ||
401 | req_ac = up_to_ac[params->user_priority]; | |
402 | ||
403 | /* Requested accesss category must have acm */ | |
404 | if (!wpa_s->wmm_ac_assoc_info->ac_params[req_ac].acm) { | |
405 | wpa_printf(MSG_DEBUG, "WMM AC: AC %d is not ACM", req_ac); | |
406 | return 0; | |
407 | } | |
408 | ||
409 | if (wmm_ac_should_replace_ts(wpa_s, params->tsid, req_ac, | |
410 | params->direction) < 0) | |
411 | return 0; | |
412 | ||
413 | return 1; | |
414 | } | |
415 | ||
416 | ||
a0413b17 MB |
417 | static struct wmm_ac_assoc_data * |
418 | wmm_ac_process_param_elem(struct wpa_supplicant *wpa_s, const u8 *ies, | |
419 | size_t ies_len) | |
420 | { | |
421 | struct ieee802_11_elems elems; | |
422 | struct wmm_parameter_element *wmm_params; | |
423 | struct wmm_ac_assoc_data *assoc_data; | |
424 | int i; | |
425 | ||
426 | /* Parsing WMM Parameter Element */ | |
677e7a95 | 427 | if (ieee802_11_parse_elems(ies, ies_len, &elems, 1) == ParseFailed) { |
a0413b17 MB |
428 | wpa_printf(MSG_DEBUG, "WMM AC: could not parse assoc ies"); |
429 | return NULL; | |
430 | } | |
431 | ||
432 | if (!elems.wmm) { | |
433 | wpa_printf(MSG_DEBUG, "WMM AC: No WMM IE"); | |
434 | return NULL; | |
435 | } | |
436 | ||
437 | if (elems.wmm_len != sizeof(*wmm_params)) { | |
f8093609 | 438 | wpa_printf(MSG_DEBUG, "WMM AC: Invalid WMM ie length"); |
a0413b17 MB |
439 | return NULL; |
440 | } | |
441 | ||
442 | wmm_params = (struct wmm_parameter_element *)(elems.wmm); | |
443 | ||
444 | assoc_data = os_zalloc(sizeof(*assoc_data)); | |
445 | if (!assoc_data) | |
446 | return NULL; | |
447 | ||
448 | for (i = 0; i < WMM_AC_NUM; i++) | |
449 | assoc_data->ac_params[i].acm = | |
450 | !!(wmm_params->ac[i].aci_aifsn & WMM_AC_ACM); | |
451 | ||
452 | wpa_printf(MSG_DEBUG, | |
453 | "WMM AC: AC mandatory: AC_BE=%u AC_BK=%u AC_VI=%u AC_VO=%u", | |
454 | assoc_data->ac_params[WMM_AC_BE].acm, | |
455 | assoc_data->ac_params[WMM_AC_BK].acm, | |
456 | assoc_data->ac_params[WMM_AC_VI].acm, | |
457 | assoc_data->ac_params[WMM_AC_VO].acm); | |
458 | ||
459 | return assoc_data; | |
460 | } | |
461 | ||
462 | ||
463 | static int wmm_ac_init(struct wpa_supplicant *wpa_s, const u8 *ies, | |
464 | size_t ies_len, const struct wmm_params *wmm_params) | |
465 | { | |
466 | struct wmm_ac_assoc_data *assoc_data; | |
467 | u8 ac; | |
468 | ||
469 | if (wpa_s->wmm_ac_assoc_info) { | |
470 | wpa_printf(MSG_ERROR, "WMM AC: Already initialized"); | |
471 | return -1; | |
472 | } | |
473 | ||
22d8bb04 JM |
474 | if (!ies || !(wmm_params->info_bitmap & WMM_PARAMS_UAPSD_QUEUES_INFO)) { |
475 | /* WMM AC not in use for this connection */ | |
a0413b17 MB |
476 | return -1; |
477 | } | |
478 | ||
674f6c07 EP |
479 | os_memset(wpa_s->tspecs, 0, sizeof(wpa_s->tspecs)); |
480 | wpa_s->wmm_ac_last_dialog_token = 0; | |
d1f88001 | 481 | wpa_s->addts_request = NULL; |
674f6c07 | 482 | |
a0413b17 MB |
483 | assoc_data = wmm_ac_process_param_elem(wpa_s, ies, ies_len); |
484 | if (!assoc_data) | |
485 | return -1; | |
486 | ||
487 | wpa_printf(MSG_DEBUG, "WMM AC: U-APSD queues=0x%x", | |
488 | wmm_params->uapsd_queues); | |
489 | ||
490 | for (ac = 0; ac < WMM_AC_NUM; ac++) { | |
491 | assoc_data->ac_params[ac].uapsd = | |
492 | !!(wmm_params->uapsd_queues & BIT(ac)); | |
493 | } | |
494 | ||
495 | wpa_s->wmm_ac_assoc_info = assoc_data; | |
496 | return 0; | |
497 | } | |
498 | ||
499 | ||
674f6c07 EP |
500 | static void wmm_ac_del_ts(struct wpa_supplicant *wpa_s, u8 ac, int dir_bitmap) |
501 | { | |
502 | enum ts_dir_idx idx; | |
503 | ||
504 | for (idx = 0; idx < TS_DIR_IDX_COUNT; idx++) { | |
505 | if (!(dir_bitmap & BIT(idx))) | |
506 | continue; | |
507 | ||
d1f88001 | 508 | wmm_ac_del_ts_idx(wpa_s, ac, idx); |
674f6c07 EP |
509 | } |
510 | } | |
511 | ||
512 | ||
a0413b17 MB |
513 | static void wmm_ac_deinit(struct wpa_supplicant *wpa_s) |
514 | { | |
674f6c07 EP |
515 | int i; |
516 | ||
517 | for (i = 0; i < WMM_AC_NUM; i++) | |
518 | wmm_ac_del_ts(wpa_s, i, TS_DIR_IDX_ALL); | |
519 | ||
fb2dc898 | 520 | /* delete pending add_ts request */ |
d1f88001 MB |
521 | wmm_ac_del_req(wpa_s, 1); |
522 | ||
a0413b17 MB |
523 | os_free(wpa_s->wmm_ac_assoc_info); |
524 | wpa_s->wmm_ac_assoc_info = NULL; | |
525 | } | |
526 | ||
527 | ||
528 | void wmm_ac_notify_assoc(struct wpa_supplicant *wpa_s, const u8 *ies, | |
529 | size_t ies_len, const struct wmm_params *wmm_params) | |
530 | { | |
531 | if (wmm_ac_init(wpa_s, ies, ies_len, wmm_params)) | |
532 | return; | |
533 | ||
534 | wpa_printf(MSG_DEBUG, | |
535 | "WMM AC: Valid WMM association, WMM AC is enabled"); | |
536 | } | |
537 | ||
538 | ||
539 | void wmm_ac_notify_disassoc(struct wpa_supplicant *wpa_s) | |
540 | { | |
541 | if (!wpa_s->wmm_ac_assoc_info) | |
542 | return; | |
543 | ||
544 | wmm_ac_deinit(wpa_s); | |
545 | wpa_printf(MSG_DEBUG, "WMM AC: WMM AC is disabled"); | |
546 | } | |
674f6c07 EP |
547 | |
548 | ||
549 | int wpas_wmm_ac_delts(struct wpa_supplicant *wpa_s, u8 tsid) | |
550 | { | |
83b9d426 | 551 | struct wmm_tspec_element tspec; |
674f6c07 EP |
552 | int ac; |
553 | enum ts_dir_idx dir; | |
554 | ||
555 | if (!wpa_s->wmm_ac_assoc_info) { | |
556 | wpa_printf(MSG_DEBUG, | |
557 | "WMM AC: Failed to delete TS, WMM AC is disabled"); | |
558 | return -1; | |
559 | } | |
560 | ||
561 | ac = wmm_ac_find_tsid(wpa_s, tsid, &dir); | |
562 | if (ac < 0) { | |
563 | wpa_printf(MSG_DEBUG, "WMM AC: TS does not exist"); | |
564 | return -1; | |
565 | } | |
566 | ||
83b9d426 | 567 | tspec = *wpa_s->tspecs[ac][dir]; |
674f6c07 | 568 | |
d1f88001 | 569 | wmm_ac_del_ts_idx(wpa_s, ac, dir); |
674f6c07 | 570 | |
83b9d426 JB |
571 | wmm_ac_send_delts(wpa_s, &tspec, wpa_s->bssid); |
572 | ||
674f6c07 EP |
573 | return 0; |
574 | } | |
575 | ||
576 | ||
577 | int wpas_wmm_ac_addts(struct wpa_supplicant *wpa_s, | |
578 | struct wmm_ac_ts_setup_params *params) | |
579 | { | |
580 | struct wmm_ac_addts_request *addts_req; | |
581 | ||
582 | if (!wpa_s->wmm_ac_assoc_info) { | |
583 | wpa_printf(MSG_DEBUG, | |
584 | "WMM AC: Cannot add TS - missing assoc data"); | |
585 | return -1; | |
586 | } | |
587 | ||
d1f88001 MB |
588 | if (wpa_s->addts_request) { |
589 | wpa_printf(MSG_DEBUG, | |
590 | "WMM AC: can't add TS - ADDTS request is already pending"); | |
591 | return -1; | |
592 | } | |
593 | ||
674f6c07 EP |
594 | /* |
595 | * we can setup downlink TS even without driver support. | |
596 | * however, we need driver support for the other directions. | |
597 | */ | |
598 | if (params->direction != WMM_AC_DIR_DOWNLINK && | |
599 | !wpa_s->wmm_ac_supported) { | |
600 | wpa_printf(MSG_DEBUG, | |
601 | "Cannot set uplink/bidi TS without driver support"); | |
602 | return -1; | |
603 | } | |
604 | ||
605 | if (!wmm_ac_ts_req_is_valid(wpa_s, params)) | |
606 | return -1; | |
607 | ||
608 | wpa_printf(MSG_DEBUG, "WMM AC: TS setup request (addr=" MACSTR | |
609 | " tsid=%u user priority=%u direction=%d)", | |
610 | MAC2STR(wpa_s->bssid), params->tsid, | |
611 | params->user_priority, params->direction); | |
612 | ||
613 | addts_req = wmm_ac_build_addts_req(wpa_s, params, wpa_s->bssid); | |
614 | if (!addts_req) | |
615 | return -1; | |
616 | ||
617 | if (wmm_ac_send_addts_request(wpa_s, addts_req)) | |
618 | goto err; | |
619 | ||
d1f88001 MB |
620 | /* save as pending and set ADDTS resp timeout to 1 second */ |
621 | wpa_s->addts_request = addts_req; | |
622 | eloop_register_timeout(1, 0, wmm_ac_addts_req_timeout, | |
623 | wpa_s, addts_req); | |
674f6c07 EP |
624 | return 0; |
625 | err: | |
626 | os_free(addts_req); | |
627 | return -1; | |
628 | } | |
d1f88001 MB |
629 | |
630 | ||
631 | static void wmm_ac_handle_delts(struct wpa_supplicant *wpa_s, const u8 *sa, | |
632 | const struct wmm_tspec_element *tspec) | |
633 | { | |
634 | int ac; | |
635 | u8 tsid; | |
636 | enum ts_dir_idx idx; | |
637 | ||
638 | tsid = wmm_ac_get_tsid(tspec); | |
639 | ||
640 | wpa_printf(MSG_DEBUG, | |
641 | "WMM AC: DELTS frame has been received TSID=%u addr=" | |
642 | MACSTR, tsid, MAC2STR(sa)); | |
643 | ||
644 | ac = wmm_ac_find_tsid(wpa_s, tsid, &idx); | |
645 | if (ac < 0) { | |
646 | wpa_printf(MSG_DEBUG, | |
647 | "WMM AC: Ignoring DELTS frame - TSID does not exist"); | |
648 | return; | |
649 | } | |
650 | ||
651 | wmm_ac_del_ts_idx(wpa_s, ac, idx); | |
652 | ||
f8093609 | 653 | wpa_printf(MSG_DEBUG, |
d1f88001 MB |
654 | "TS was deleted successfully (tsid=%u address=" MACSTR ")", |
655 | tsid, MAC2STR(sa)); | |
656 | } | |
657 | ||
658 | ||
659 | static void wmm_ac_handle_addts_resp(struct wpa_supplicant *wpa_s, const u8 *sa, | |
660 | const u8 resp_dialog_token, const u8 status_code, | |
661 | const struct wmm_tspec_element *tspec) | |
662 | { | |
663 | struct wmm_ac_addts_request *req = wpa_s->addts_request; | |
664 | u8 ac, tsid, up, dir; | |
665 | int replace_tspecs; | |
666 | ||
667 | tsid = wmm_ac_get_tsid(tspec); | |
668 | dir = wmm_ac_get_direction(tspec); | |
669 | up = wmm_ac_get_user_priority(tspec); | |
670 | ac = up_to_ac[up]; | |
671 | ||
672 | /* make sure we have a matching addts request */ | |
673 | if (!req || req->dialog_token != resp_dialog_token) { | |
f8093609 | 674 | wpa_printf(MSG_DEBUG, |
d1f88001 MB |
675 | "WMM AC: no req with dialog=%u, ignoring frame", |
676 | resp_dialog_token); | |
677 | return; | |
678 | } | |
679 | ||
680 | /* make sure the params are the same */ | |
681 | if (os_memcmp(req->address, sa, ETH_ALEN) != 0 || | |
682 | tsid != wmm_ac_get_tsid(&req->tspec) || | |
683 | up != wmm_ac_get_user_priority(&req->tspec) || | |
684 | dir != wmm_ac_get_direction(&req->tspec)) { | |
f8093609 | 685 | wpa_printf(MSG_DEBUG, |
d1f88001 MB |
686 | "WMM AC: ADDTS params do not match, ignoring frame"); |
687 | return; | |
688 | } | |
689 | ||
690 | /* delete pending request */ | |
691 | wmm_ac_del_req(wpa_s, 0); | |
692 | ||
f8093609 | 693 | wpa_printf(MSG_DEBUG, |
d1f88001 MB |
694 | "ADDTS response status=%d tsid=%u up=%u direction=%u", |
695 | status_code, tsid, up, dir); | |
696 | ||
697 | if (status_code != WMM_ADDTS_STATUS_ADMISSION_ACCEPTED) { | |
698 | wpa_printf(MSG_INFO, "WMM AC: ADDTS request was rejected"); | |
699 | goto err_msg; | |
700 | } | |
701 | ||
702 | replace_tspecs = wmm_ac_should_replace_ts(wpa_s, tsid, ac, dir); | |
703 | if (replace_tspecs < 0) | |
704 | goto err_delts; | |
705 | ||
706 | wpa_printf(MSG_DEBUG, "ts idx replace bitmap: 0x%x", replace_tspecs); | |
707 | ||
708 | /* when replacing tspecs - delete first */ | |
709 | wmm_ac_del_ts(wpa_s, ac, replace_tspecs); | |
710 | ||
711 | /* Creating a new traffic stream */ | |
712 | wpa_printf(MSG_DEBUG, | |
713 | "WMM AC: adding a new TS with TSID=%u address="MACSTR | |
714 | " medium time=%u access category=%d dir=%d ", | |
715 | tsid, MAC2STR(sa), | |
716 | le_to_host16(tspec->medium_time), ac, dir); | |
717 | ||
718 | if (wmm_ac_add_ts(wpa_s, sa, tspec)) | |
719 | goto err_delts; | |
720 | ||
721 | return; | |
722 | ||
723 | err_delts: | |
724 | /* ask the ap to delete the tspec */ | |
725 | wmm_ac_send_delts(wpa_s, tspec, sa); | |
726 | err_msg: | |
727 | wpa_msg(wpa_s, MSG_INFO, WMM_AC_EVENT_TSPEC_REQ_FAILED "tsid=%u", | |
728 | tsid); | |
729 | } | |
730 | ||
731 | ||
732 | void wmm_ac_rx_action(struct wpa_supplicant *wpa_s, const u8 *da, | |
733 | const u8 *sa, const u8 *data, size_t len) | |
734 | { | |
735 | u8 action; | |
736 | u8 dialog_token; | |
737 | u8 status_code; | |
738 | struct ieee802_11_elems elems; | |
739 | struct wmm_tspec_element *tspec; | |
740 | ||
741 | if (wpa_s->wmm_ac_assoc_info == NULL) { | |
f8093609 | 742 | wpa_printf(MSG_DEBUG, |
d1f88001 MB |
743 | "WMM AC: WMM AC is disabled, ignoring action frame"); |
744 | return; | |
745 | } | |
746 | ||
747 | action = data[0]; | |
748 | ||
749 | if (action != WMM_ACTION_CODE_ADDTS_RESP && | |
750 | action != WMM_ACTION_CODE_DELTS) { | |
f8093609 | 751 | wpa_printf(MSG_DEBUG, |
d1f88001 MB |
752 | "WMM AC: Unknown action (%d), ignoring action frame", |
753 | action); | |
754 | return; | |
755 | } | |
756 | ||
757 | /* WMM AC action frame */ | |
758 | if (os_memcmp(da, wpa_s->own_addr, ETH_ALEN) != 0) { | |
759 | wpa_printf(MSG_DEBUG, "WMM AC: frame destination addr="MACSTR | |
760 | " is other than ours, ignoring frame", MAC2STR(da)); | |
761 | return; | |
762 | } | |
763 | ||
764 | if (os_memcmp(sa, wpa_s->bssid, ETH_ALEN) != 0) { | |
765 | wpa_printf(MSG_DEBUG, "WMM AC: ignore frame with sa " MACSTR | |
766 | " different other than our bssid", MAC2STR(da)); | |
767 | return; | |
768 | } | |
769 | ||
770 | if (len < 2 + sizeof(struct wmm_tspec_element)) { | |
771 | wpa_printf(MSG_DEBUG, | |
772 | "WMM AC: Short ADDTS response ignored (len=%lu)", | |
773 | (unsigned long) len); | |
774 | return; | |
775 | } | |
776 | ||
777 | data++; | |
778 | len--; | |
779 | dialog_token = data[0]; | |
780 | status_code = data[1]; | |
781 | ||
782 | if (ieee802_11_parse_elems(data + 2, len - 2, &elems, 1) != ParseOK) { | |
783 | wpa_printf(MSG_DEBUG, | |
784 | "WMM AC: Could not parse WMM AC action from " MACSTR, | |
785 | MAC2STR(sa)); | |
786 | return; | |
787 | } | |
788 | ||
789 | /* the struct also contains the type and value, so decrease it */ | |
790 | if (elems.wmm_tspec_len != sizeof(struct wmm_tspec_element) - 2) { | |
791 | wpa_printf(MSG_DEBUG, "WMM AC: missing or wrong length TSPEC"); | |
792 | return; | |
793 | } | |
794 | ||
795 | tspec = (struct wmm_tspec_element *)(elems.wmm_tspec - 2); | |
796 | ||
797 | wpa_printf(MSG_DEBUG, "WMM AC: RX WMM AC Action from " MACSTR, | |
798 | MAC2STR(sa)); | |
799 | wpa_hexdump(MSG_MSGDUMP, "WMM AC: WMM AC Action content", data, len); | |
800 | ||
801 | switch (action) { | |
802 | case WMM_ACTION_CODE_ADDTS_RESP: | |
803 | wmm_ac_handle_addts_resp(wpa_s, sa, dialog_token, status_code, | |
804 | tspec); | |
805 | break; | |
806 | case WMM_ACTION_CODE_DELTS: | |
807 | wmm_ac_handle_delts(wpa_s, sa, tspec); | |
808 | break; | |
809 | default: | |
810 | break; | |
811 | } | |
812 | } | |
8506ea6f MB |
813 | |
814 | ||
815 | static const char * get_ac_str(u8 ac) | |
816 | { | |
817 | switch (ac) { | |
818 | case WMM_AC_BE: | |
819 | return "BE"; | |
820 | case WMM_AC_BK: | |
821 | return "BK"; | |
822 | case WMM_AC_VI: | |
823 | return "VI"; | |
824 | case WMM_AC_VO: | |
825 | return "VO"; | |
826 | default: | |
827 | return "N/A"; | |
828 | } | |
829 | } | |
830 | ||
831 | ||
832 | static const char * get_direction_str(u8 direction) | |
833 | { | |
834 | switch (direction) { | |
835 | case WMM_AC_DIR_DOWNLINK: | |
836 | return "Downlink"; | |
837 | case WMM_AC_DIR_UPLINK: | |
838 | return "Uplink"; | |
839 | case WMM_AC_DIR_BIDIRECTIONAL: | |
840 | return "Bi-directional"; | |
841 | default: | |
842 | return "N/A"; | |
843 | } | |
844 | } | |
845 | ||
846 | ||
847 | int wpas_wmm_ac_status(struct wpa_supplicant *wpa_s, char *buf, size_t buflen) | |
848 | { | |
849 | struct wmm_ac_assoc_data *assoc_info = wpa_s->wmm_ac_assoc_info; | |
850 | enum ts_dir_idx idx; | |
851 | int pos = 0; | |
20fe7456 | 852 | u8 ac, up; |
8506ea6f MB |
853 | |
854 | if (!assoc_info) { | |
855 | return wpa_scnprintf(buf, buflen - pos, | |
856 | "Not associated to a WMM AP, WMM AC is Disabled\n"); | |
857 | } | |
858 | ||
859 | pos += wpa_scnprintf(buf + pos, buflen - pos, "WMM AC is Enabled\n"); | |
860 | ||
861 | for (ac = 0; ac < WMM_AC_NUM; ac++) { | |
862 | int ts_count = 0; | |
863 | ||
864 | pos += wpa_scnprintf(buf + pos, buflen - pos, | |
865 | "%s: acm=%d uapsd=%d\n", | |
866 | get_ac_str(ac), | |
867 | assoc_info->ac_params[ac].acm, | |
868 | assoc_info->ac_params[ac].uapsd); | |
869 | ||
870 | for (idx = 0; idx < TS_DIR_IDX_COUNT; idx++) { | |
871 | struct wmm_tspec_element *tspec; | |
872 | u8 dir, tsid; | |
873 | const char *dir_str; | |
874 | ||
875 | tspec = wpa_s->tspecs[ac][idx]; | |
876 | if (!tspec) | |
877 | continue; | |
878 | ||
879 | ts_count++; | |
880 | ||
881 | dir = wmm_ac_get_direction(tspec); | |
882 | dir_str = get_direction_str(dir); | |
883 | tsid = wmm_ac_get_tsid(tspec); | |
20fe7456 | 884 | up = wmm_ac_get_user_priority(tspec); |
8506ea6f MB |
885 | |
886 | pos += wpa_scnprintf(buf + pos, buflen - pos, | |
20fe7456 | 887 | "\tTSID=%u UP=%u\n" |
8506ea6f MB |
888 | "\tAddress = "MACSTR"\n" |
889 | "\tWMM AC dir = %s\n" | |
890 | "\tTotal admitted time = %u\n\n", | |
20fe7456 | 891 | tsid, up, |
8506ea6f MB |
892 | MAC2STR(wpa_s->bssid), |
893 | dir_str, | |
894 | le_to_host16(tspec->medium_time)); | |
895 | } | |
896 | ||
897 | if (!ts_count) { | |
898 | pos += wpa_scnprintf(buf + pos, buflen - pos, | |
899 | "\t(No Traffic Stream)\n\n"); | |
900 | } | |
901 | } | |
902 | ||
903 | return pos; | |
904 | } | |
8c42b369 EP |
905 | |
906 | ||
907 | static u8 wmm_ac_get_tspecs_count(struct wpa_supplicant *wpa_s) | |
908 | { | |
909 | int ac, dir, tspecs_count = 0; | |
910 | ||
911 | for (ac = 0; ac < WMM_AC_NUM; ac++) { | |
912 | for (dir = 0; dir < TS_DIR_IDX_COUNT; dir++) { | |
913 | if (wpa_s->tspecs[ac][dir]) | |
914 | tspecs_count++; | |
915 | } | |
916 | } | |
917 | ||
918 | return tspecs_count; | |
919 | } | |
920 | ||
921 | ||
922 | void wmm_ac_save_tspecs(struct wpa_supplicant *wpa_s) | |
923 | { | |
924 | int ac, dir, tspecs_count; | |
925 | ||
926 | wpa_printf(MSG_DEBUG, "WMM AC: Save last configured tspecs"); | |
927 | ||
928 | if (!wpa_s->wmm_ac_assoc_info) | |
929 | return; | |
930 | ||
931 | tspecs_count = wmm_ac_get_tspecs_count(wpa_s); | |
932 | if (!tspecs_count) { | |
933 | wpa_printf(MSG_DEBUG, "WMM AC: No configured TSPECs"); | |
934 | return; | |
935 | } | |
936 | ||
937 | wpa_printf(MSG_DEBUG, "WMM AC: Saving tspecs"); | |
938 | ||
939 | wmm_ac_clear_saved_tspecs(wpa_s); | |
940 | wpa_s->last_tspecs = os_calloc(tspecs_count, | |
941 | sizeof(*wpa_s->last_tspecs)); | |
942 | if (!wpa_s->last_tspecs) { | |
943 | wpa_printf(MSG_ERROR, "WMM AC: Failed to save tspecs!"); | |
944 | return; | |
945 | } | |
946 | ||
947 | for (ac = 0; ac < WMM_AC_NUM; ac++) { | |
948 | for (dir = 0; dir < TS_DIR_IDX_COUNT; dir++) { | |
949 | if (!wpa_s->tspecs[ac][dir]) | |
950 | continue; | |
951 | ||
952 | wpa_s->last_tspecs[wpa_s->last_tspecs_count++] = | |
953 | *wpa_s->tspecs[ac][dir]; | |
954 | } | |
955 | } | |
956 | ||
957 | wpa_printf(MSG_DEBUG, "WMM AC: Successfully saved %d TSPECs", | |
958 | wpa_s->last_tspecs_count); | |
959 | } | |
960 | ||
961 | ||
962 | void wmm_ac_clear_saved_tspecs(struct wpa_supplicant *wpa_s) | |
963 | { | |
964 | if (wpa_s->last_tspecs) { | |
965 | wpa_printf(MSG_DEBUG, "WMM AC: Clear saved tspecs"); | |
966 | os_free(wpa_s->last_tspecs); | |
967 | wpa_s->last_tspecs = NULL; | |
968 | wpa_s->last_tspecs_count = 0; | |
969 | } | |
970 | } | |
971 | ||
972 | ||
973 | int wmm_ac_restore_tspecs(struct wpa_supplicant *wpa_s) | |
974 | { | |
975 | unsigned int i; | |
976 | ||
977 | if (!wpa_s->wmm_ac_assoc_info || !wpa_s->last_tspecs_count) | |
978 | return 0; | |
979 | ||
980 | wpa_printf(MSG_DEBUG, "WMM AC: Restore %u saved tspecs", | |
981 | wpa_s->last_tspecs_count); | |
982 | ||
983 | for (i = 0; i < wpa_s->last_tspecs_count; i++) | |
984 | wmm_ac_add_ts(wpa_s, wpa_s->bssid, &wpa_s->last_tspecs[i]); | |
985 | ||
986 | return 0; | |
987 | } |