]>
Commit | Line | Data |
---|---|---|
ef721751 THJ |
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 | } |