vendor/wpa_supplicant: upgrade from 2.1 to 2.9
[dragonfly.git] / contrib / wpa_supplicant / src / ap / airtime_policy.c
1 /*
2  * Airtime policy configuration
3  * Copyright (c) 2018-2019, Toke Høiland-Jørgensen <toke@toke.dk>
4  *
5  * This software may be distributed under the terms of the BSD license.
6  * See README for more details.
7  */
8
9 #include "utils/includes.h"
10
11 #include "utils/common.h"
12 #include "utils/eloop.h"
13 #include "hostapd.h"
14 #include "ap_drv_ops.h"
15 #include "sta_info.h"
16 #include "airtime_policy.h"
17
18 /* Idea:
19  * Two modes of airtime enforcement:
20  * 1. Static weights: specify weights per MAC address with a per-BSS default
21  * 2. Per-BSS limits: Dynamically calculate weights of backlogged stations to
22  *    enforce relative total shares between BSSes.
23  *
24  * - Periodic per-station callback to update queue status.
25  *
26  * Copy accounting_sta_update_stats() to get TXQ info and airtime weights and
27  * keep them updated in sta_info.
28  *
29  * - Separate periodic per-bss (or per-iface?) callback to update weights.
30  *
31  * Just need to loop through all interfaces, count sum the active stations (or
32  * should the per-STA callback just adjust that for the BSS?) and calculate new
33  * weights.
34  */
35
36 static int get_airtime_policy_update_timeout(struct hostapd_iface *iface,
37                                              unsigned int *sec,
38                                              unsigned int *usec)
39 {
40         unsigned int update_int = iface->conf->airtime_update_interval;
41
42         if (!update_int) {
43                 wpa_printf(MSG_ERROR,
44                            "Airtime policy: Invalid airtime policy update interval %u",
45                            update_int);
46                 return -1;
47         }
48
49         *sec = update_int / 1000;
50         *usec = (update_int % 1000) * 1000;
51
52         return 0;
53 }
54
55
56 static void set_new_backlog_time(struct hostapd_data *hapd,
57                                  struct sta_info *sta,
58                                  struct os_reltime *now)
59 {
60         sta->backlogged_until = *now;
61         sta->backlogged_until.usec += hapd->iconf->airtime_update_interval *
62                 AIRTIME_BACKLOG_EXPIRY_FACTOR;
63         while (sta->backlogged_until.usec >= 1000000) {
64                 sta->backlogged_until.sec++;
65                 sta->backlogged_until.usec -= 1000000;
66         }
67 }
68
69
70 static void count_backlogged_sta(struct hostapd_data *hapd)
71 {
72         struct sta_info *sta;
73         struct hostap_sta_driver_data data = {};
74         unsigned int num_backlogged = 0;
75         struct os_reltime now;
76
77         os_get_reltime(&now);
78
79         for (sta = hapd->sta_list; sta; sta = sta->next) {
80                 if (hostapd_drv_read_sta_data(hapd, &data, sta->addr))
81                         continue;
82
83                 if (data.backlog_bytes > 0)
84                         set_new_backlog_time(hapd, sta, &now);
85                 if (os_reltime_before(&now, &sta->backlogged_until))
86                         num_backlogged++;
87         }
88         hapd->num_backlogged_sta = num_backlogged;
89 }
90
91
92 static int sta_set_airtime_weight(struct hostapd_data *hapd,
93                                   struct sta_info *sta,
94                                   unsigned int weight)
95 {
96         int ret = 0;
97
98         if (weight != sta->airtime_weight &&
99             (ret = hostapd_sta_set_airtime_weight(hapd, sta->addr, weight)))
100                 return ret;
101
102         sta->airtime_weight = weight;
103         return ret;
104 }
105
106
107 static void set_sta_weights(struct hostapd_data *hapd, unsigned int weight)
108 {
109         struct sta_info *sta;
110
111         for (sta = hapd->sta_list; sta; sta = sta->next)
112                 sta_set_airtime_weight(hapd, sta, weight);
113 }
114
115
116 static unsigned int get_airtime_quantum(unsigned int max_wt)
117 {
118         unsigned int quantum = AIRTIME_QUANTUM_TARGET / max_wt;
119
120         if (quantum < AIRTIME_QUANTUM_MIN)
121                 quantum = AIRTIME_QUANTUM_MIN;
122         else if (quantum > AIRTIME_QUANTUM_MAX)
123                 quantum = AIRTIME_QUANTUM_MAX;
124
125         return quantum;
126 }
127
128
129 static void update_airtime_weights(void *eloop_data, void *user_data)
130 {
131         struct hostapd_iface *iface = eloop_data;
132         struct hostapd_data *bss;
133         unsigned int sec, usec;
134         unsigned int num_sta_min = 0, num_sta_prod = 1, num_sta_sum = 0,
135                 wt_sum = 0;
136         unsigned int quantum;
137         Boolean all_div_min = TRUE;
138         Boolean apply_limit = iface->conf->airtime_mode == AIRTIME_MODE_DYNAMIC;
139         int wt, num_bss = 0, max_wt = 0;
140         size_t i;
141
142         for (i = 0; i < iface->num_bss; i++) {
143                 bss = iface->bss[i];
144                 if (!bss->started || !bss->conf->airtime_weight)
145                         continue;
146
147                 count_backlogged_sta(bss);
148                 if (!bss->num_backlogged_sta)
149                         continue;
150
151                 if (!num_sta_min || bss->num_backlogged_sta < num_sta_min)
152                         num_sta_min = bss->num_backlogged_sta;
153
154                 num_sta_prod *= bss->num_backlogged_sta;
155                 num_sta_sum += bss->num_backlogged_sta;
156                 wt_sum += bss->conf->airtime_weight;
157                 num_bss++;
158         }
159
160         if (num_sta_min) {
161                 for (i = 0; i < iface->num_bss; i++) {
162                         bss = iface->bss[i];
163                         if (!bss->started || !bss->conf->airtime_weight)
164                                 continue;
165
166                         /* Check if we can divide all sta numbers by the
167                          * smallest number to keep weights as small as possible.
168                          * This is a lazy way to avoid having to factor
169                          * integers. */
170                         if (bss->num_backlogged_sta &&
171                             bss->num_backlogged_sta % num_sta_min > 0)
172                                 all_div_min = FALSE;
173
174                         /* If we're in LIMIT mode, we only apply the weight
175                          * scaling when the BSS(es) marked as limited would a
176                          * larger share than the relative BSS weights indicates
177                          * it should. */
178                         if (!apply_limit && bss->conf->airtime_limit) {
179                                 if (bss->num_backlogged_sta * wt_sum >
180                                     bss->conf->airtime_weight * num_sta_sum)
181                                         apply_limit = TRUE;
182                         }
183                 }
184                 if (all_div_min)
185                         num_sta_prod /= num_sta_min;
186         }
187
188         for (i = 0; i < iface->num_bss; i++) {
189                 bss = iface->bss[i];
190                 if (!bss->started || !bss->conf->airtime_weight)
191                         continue;
192
193                 /* We only set the calculated weight if the BSS has active
194                  * stations and there are other active interfaces as well -
195                  * otherwise we just set a unit weight. This ensures that
196                  * the weights are set reasonably when stations transition from
197                  * inactive to active. */
198                 if (apply_limit && bss->num_backlogged_sta && num_bss > 1)
199                         wt = bss->conf->airtime_weight * num_sta_prod /
200                                 bss->num_backlogged_sta;
201                 else
202                         wt = 1;
203
204                 bss->airtime_weight = wt;
205                 if (wt > max_wt)
206                         max_wt = wt;
207         }
208
209         quantum = get_airtime_quantum(max_wt);
210
211         for (i = 0; i < iface->num_bss; i++) {
212                 bss = iface->bss[i];
213                 if (!bss->started || !bss->conf->airtime_weight)
214                         continue;
215                 set_sta_weights(bss, bss->airtime_weight * quantum);
216         }
217
218         if (get_airtime_policy_update_timeout(iface, &sec, &usec) < 0)
219                 return;
220
221         eloop_register_timeout(sec, usec, update_airtime_weights, iface,
222                                NULL);
223 }
224
225
226 static int get_weight_for_sta(struct hostapd_data *hapd, const u8 *sta)
227 {
228         struct airtime_sta_weight *wt;
229
230         wt = hapd->conf->airtime_weight_list;
231         while (wt && os_memcmp(wt->addr, sta, ETH_ALEN) != 0)
232                 wt = wt->next;
233
234         return wt ? wt->weight : hapd->conf->airtime_weight;
235 }
236
237
238 int airtime_policy_new_sta(struct hostapd_data *hapd, struct sta_info *sta)
239 {
240         unsigned int weight;
241
242         if (hapd->iconf->airtime_mode == AIRTIME_MODE_STATIC) {
243                 weight = get_weight_for_sta(hapd, sta->addr);
244                 if (weight)
245                         return sta_set_airtime_weight(hapd, sta, weight);
246         }
247         return 0;
248 }
249
250
251 int airtime_policy_update_init(struct hostapd_iface *iface)
252 {
253         unsigned int sec, usec;
254
255         if (iface->conf->airtime_mode < AIRTIME_MODE_DYNAMIC)
256                 return 0;
257
258         if (get_airtime_policy_update_timeout(iface, &sec, &usec) < 0)
259                 return -1;
260
261         eloop_register_timeout(sec, usec, update_airtime_weights, iface, NULL);
262         return 0;
263 }
264
265
266 void airtime_policy_update_deinit(struct hostapd_iface *iface)
267 {
268         eloop_cancel_timeout(update_airtime_weights, iface, NULL);
269 }