xref: /freebsd/contrib/wpa/src/ap/airtime_policy.c (revision 8ddb146abcdf061be9f2c0db7e391697dafad85c)
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 #ifdef CONFIG_TESTING_OPTIONS
83 		if (hapd->force_backlog_bytes)
84 			data.backlog_bytes = 1;
85 #endif /* CONFIG_TESTING_OPTIONS */
86 
87 		if (data.backlog_bytes > 0)
88 			set_new_backlog_time(hapd, sta, &now);
89 		if (os_reltime_before(&now, &sta->backlogged_until))
90 			num_backlogged++;
91 	}
92 	hapd->num_backlogged_sta = num_backlogged;
93 }
94 
95 
96 static int sta_set_airtime_weight(struct hostapd_data *hapd,
97 				  struct sta_info *sta,
98 				  unsigned int weight)
99 {
100 	int ret = 0;
101 
102 	if (weight != sta->airtime_weight &&
103 	    (ret = hostapd_sta_set_airtime_weight(hapd, sta->addr, weight)))
104 		return ret;
105 
106 	sta->airtime_weight = weight;
107 	return ret;
108 }
109 
110 
111 static void set_sta_weights(struct hostapd_data *hapd, unsigned int weight)
112 {
113 	struct sta_info *sta;
114 
115 	for (sta = hapd->sta_list; sta; sta = sta->next)
116 		sta_set_airtime_weight(hapd, sta, weight);
117 }
118 
119 
120 static unsigned int get_airtime_quantum(unsigned int max_wt)
121 {
122 	unsigned int quantum = AIRTIME_QUANTUM_TARGET / max_wt;
123 
124 	if (quantum < AIRTIME_QUANTUM_MIN)
125 		quantum = AIRTIME_QUANTUM_MIN;
126 	else if (quantum > AIRTIME_QUANTUM_MAX)
127 		quantum = AIRTIME_QUANTUM_MAX;
128 
129 	return quantum;
130 }
131 
132 
133 static void update_airtime_weights(void *eloop_data, void *user_data)
134 {
135 	struct hostapd_iface *iface = eloop_data;
136 	struct hostapd_data *bss;
137 	unsigned int sec, usec;
138 	unsigned int num_sta_min = 0, num_sta_prod = 1, num_sta_sum = 0,
139 		wt_sum = 0;
140 	unsigned int quantum;
141 	bool all_div_min = true;
142 	bool apply_limit = iface->conf->airtime_mode == AIRTIME_MODE_DYNAMIC;
143 	int wt, num_bss = 0, max_wt = 0;
144 	size_t i;
145 
146 	for (i = 0; i < iface->num_bss; i++) {
147 		bss = iface->bss[i];
148 		if (!bss->started || !bss->conf->airtime_weight)
149 			continue;
150 
151 		count_backlogged_sta(bss);
152 		if (!bss->num_backlogged_sta)
153 			continue;
154 
155 		if (!num_sta_min || bss->num_backlogged_sta < num_sta_min)
156 			num_sta_min = bss->num_backlogged_sta;
157 
158 		num_sta_prod *= bss->num_backlogged_sta;
159 		num_sta_sum += bss->num_backlogged_sta;
160 		wt_sum += bss->conf->airtime_weight;
161 		num_bss++;
162 	}
163 
164 	if (num_sta_min) {
165 		for (i = 0; i < iface->num_bss; i++) {
166 			bss = iface->bss[i];
167 			if (!bss->started || !bss->conf->airtime_weight)
168 				continue;
169 
170 			/* Check if we can divide all sta numbers by the
171 			 * smallest number to keep weights as small as possible.
172 			 * This is a lazy way to avoid having to factor
173 			 * integers. */
174 			if (bss->num_backlogged_sta &&
175 			    bss->num_backlogged_sta % num_sta_min > 0)
176 				all_div_min = false;
177 
178 			/* If we're in LIMIT mode, we only apply the weight
179 			 * scaling when the BSS(es) marked as limited would a
180 			 * larger share than the relative BSS weights indicates
181 			 * it should. */
182 			if (!apply_limit && bss->conf->airtime_limit) {
183 				if (bss->num_backlogged_sta * wt_sum >
184 				    bss->conf->airtime_weight * num_sta_sum)
185 					apply_limit = true;
186 			}
187 		}
188 		if (all_div_min)
189 			num_sta_prod /= num_sta_min;
190 	}
191 
192 	for (i = 0; i < iface->num_bss; i++) {
193 		bss = iface->bss[i];
194 		if (!bss->started || !bss->conf->airtime_weight)
195 			continue;
196 
197 		/* We only set the calculated weight if the BSS has active
198 		 * stations and there are other active interfaces as well -
199 		 * otherwise we just set a unit weight. This ensures that
200 		 * the weights are set reasonably when stations transition from
201 		 * inactive to active. */
202 		if (apply_limit && bss->num_backlogged_sta && num_bss > 1)
203 			wt = bss->conf->airtime_weight * num_sta_prod /
204 				bss->num_backlogged_sta;
205 		else
206 			wt = 1;
207 
208 		bss->airtime_weight = wt;
209 		if (wt > max_wt)
210 			max_wt = wt;
211 	}
212 
213 	quantum = get_airtime_quantum(max_wt);
214 
215 	for (i = 0; i < iface->num_bss; i++) {
216 		bss = iface->bss[i];
217 		if (!bss->started || !bss->conf->airtime_weight)
218 			continue;
219 		set_sta_weights(bss, bss->airtime_weight * quantum);
220 	}
221 
222 	if (get_airtime_policy_update_timeout(iface, &sec, &usec) < 0)
223 		return;
224 
225 	eloop_register_timeout(sec, usec, update_airtime_weights, iface,
226 			       NULL);
227 }
228 
229 
230 static int get_weight_for_sta(struct hostapd_data *hapd, const u8 *sta)
231 {
232 	struct airtime_sta_weight *wt;
233 
234 	wt = hapd->conf->airtime_weight_list;
235 	while (wt && os_memcmp(wt->addr, sta, ETH_ALEN) != 0)
236 		wt = wt->next;
237 
238 	return wt ? wt->weight : hapd->conf->airtime_weight;
239 }
240 
241 
242 int airtime_policy_new_sta(struct hostapd_data *hapd, struct sta_info *sta)
243 {
244 	unsigned int weight;
245 
246 	if (hapd->iconf->airtime_mode == AIRTIME_MODE_STATIC) {
247 		weight = get_weight_for_sta(hapd, sta->addr);
248 		if (weight)
249 			return sta_set_airtime_weight(hapd, sta, weight);
250 	}
251 	return 0;
252 }
253 
254 
255 int airtime_policy_update_init(struct hostapd_iface *iface)
256 {
257 	unsigned int sec, usec;
258 
259 	if (iface->conf->airtime_mode < AIRTIME_MODE_DYNAMIC)
260 		return 0;
261 
262 	if (get_airtime_policy_update_timeout(iface, &sec, &usec) < 0)
263 		return -1;
264 
265 	eloop_register_timeout(sec, usec, update_airtime_weights, iface, NULL);
266 	return 0;
267 }
268 
269 
270 void airtime_policy_update_deinit(struct hostapd_iface *iface)
271 {
272 	eloop_cancel_timeout(update_airtime_weights, iface, NULL);
273 }
274