xref: /freebsd/contrib/wpa/src/ap/airtime_policy.c (revision a90b9d0159070121c221b966469c3e36d912bf82)
1206b73d0SCy Schubert /*
2206b73d0SCy Schubert  * Airtime policy configuration
3206b73d0SCy Schubert  * Copyright (c) 2018-2019, Toke Høiland-Jørgensen <toke@toke.dk>
4206b73d0SCy Schubert  *
5206b73d0SCy Schubert  * This software may be distributed under the terms of the BSD license.
6206b73d0SCy Schubert  * See README for more details.
7206b73d0SCy Schubert  */
8206b73d0SCy Schubert 
9206b73d0SCy Schubert #include "utils/includes.h"
10206b73d0SCy Schubert 
11206b73d0SCy Schubert #include "utils/common.h"
12206b73d0SCy Schubert #include "utils/eloop.h"
13206b73d0SCy Schubert #include "hostapd.h"
14206b73d0SCy Schubert #include "ap_drv_ops.h"
15206b73d0SCy Schubert #include "sta_info.h"
16206b73d0SCy Schubert #include "airtime_policy.h"
17206b73d0SCy Schubert 
18206b73d0SCy Schubert /* Idea:
19206b73d0SCy Schubert  * Two modes of airtime enforcement:
20206b73d0SCy Schubert  * 1. Static weights: specify weights per MAC address with a per-BSS default
21206b73d0SCy Schubert  * 2. Per-BSS limits: Dynamically calculate weights of backlogged stations to
22206b73d0SCy Schubert  *    enforce relative total shares between BSSes.
23206b73d0SCy Schubert  *
24206b73d0SCy Schubert  * - Periodic per-station callback to update queue status.
25206b73d0SCy Schubert  *
26206b73d0SCy Schubert  * Copy accounting_sta_update_stats() to get TXQ info and airtime weights and
27206b73d0SCy Schubert  * keep them updated in sta_info.
28206b73d0SCy Schubert  *
29206b73d0SCy Schubert  * - Separate periodic per-bss (or per-iface?) callback to update weights.
30206b73d0SCy Schubert  *
31206b73d0SCy Schubert  * Just need to loop through all interfaces, count sum the active stations (or
32206b73d0SCy Schubert  * should the per-STA callback just adjust that for the BSS?) and calculate new
33206b73d0SCy Schubert  * weights.
34206b73d0SCy Schubert  */
35206b73d0SCy Schubert 
get_airtime_policy_update_timeout(struct hostapd_iface * iface,unsigned int * sec,unsigned int * usec)36206b73d0SCy Schubert static int get_airtime_policy_update_timeout(struct hostapd_iface *iface,
37206b73d0SCy Schubert 					     unsigned int *sec,
38206b73d0SCy Schubert 					     unsigned int *usec)
39206b73d0SCy Schubert {
40206b73d0SCy Schubert 	unsigned int update_int = iface->conf->airtime_update_interval;
41206b73d0SCy Schubert 
42206b73d0SCy Schubert 	if (!update_int) {
43206b73d0SCy Schubert 		wpa_printf(MSG_ERROR,
44206b73d0SCy Schubert 			   "Airtime policy: Invalid airtime policy update interval %u",
45206b73d0SCy Schubert 			   update_int);
46206b73d0SCy Schubert 		return -1;
47206b73d0SCy Schubert 	}
48206b73d0SCy Schubert 
49206b73d0SCy Schubert 	*sec = update_int / 1000;
50206b73d0SCy Schubert 	*usec = (update_int % 1000) * 1000;
51206b73d0SCy Schubert 
52206b73d0SCy Schubert 	return 0;
53206b73d0SCy Schubert }
54206b73d0SCy Schubert 
55206b73d0SCy Schubert 
set_new_backlog_time(struct hostapd_data * hapd,struct sta_info * sta,struct os_reltime * now)56206b73d0SCy Schubert static void set_new_backlog_time(struct hostapd_data *hapd,
57206b73d0SCy Schubert 				 struct sta_info *sta,
58206b73d0SCy Schubert 				 struct os_reltime *now)
59206b73d0SCy Schubert {
60206b73d0SCy Schubert 	sta->backlogged_until = *now;
61206b73d0SCy Schubert 	sta->backlogged_until.usec += hapd->iconf->airtime_update_interval *
62206b73d0SCy Schubert 		AIRTIME_BACKLOG_EXPIRY_FACTOR;
63206b73d0SCy Schubert 	while (sta->backlogged_until.usec >= 1000000) {
64206b73d0SCy Schubert 		sta->backlogged_until.sec++;
65206b73d0SCy Schubert 		sta->backlogged_until.usec -= 1000000;
66206b73d0SCy Schubert 	}
67206b73d0SCy Schubert }
68206b73d0SCy Schubert 
69206b73d0SCy Schubert 
count_backlogged_sta(struct hostapd_data * hapd)70206b73d0SCy Schubert static void count_backlogged_sta(struct hostapd_data *hapd)
71206b73d0SCy Schubert {
72206b73d0SCy Schubert 	struct sta_info *sta;
73206b73d0SCy Schubert 	struct hostap_sta_driver_data data = {};
74206b73d0SCy Schubert 	unsigned int num_backlogged = 0;
75206b73d0SCy Schubert 	struct os_reltime now;
76206b73d0SCy Schubert 
77206b73d0SCy Schubert 	os_get_reltime(&now);
78206b73d0SCy Schubert 
79206b73d0SCy Schubert 	for (sta = hapd->sta_list; sta; sta = sta->next) {
80206b73d0SCy Schubert 		if (hostapd_drv_read_sta_data(hapd, &data, sta->addr))
81206b73d0SCy Schubert 			continue;
82c1d255d3SCy Schubert #ifdef CONFIG_TESTING_OPTIONS
83c1d255d3SCy Schubert 		if (hapd->force_backlog_bytes)
84c1d255d3SCy Schubert 			data.backlog_bytes = 1;
85c1d255d3SCy Schubert #endif /* CONFIG_TESTING_OPTIONS */
86206b73d0SCy Schubert 
87206b73d0SCy Schubert 		if (data.backlog_bytes > 0)
88206b73d0SCy Schubert 			set_new_backlog_time(hapd, sta, &now);
89206b73d0SCy Schubert 		if (os_reltime_before(&now, &sta->backlogged_until))
90206b73d0SCy Schubert 			num_backlogged++;
91206b73d0SCy Schubert 	}
92206b73d0SCy Schubert 	hapd->num_backlogged_sta = num_backlogged;
93206b73d0SCy Schubert }
94206b73d0SCy Schubert 
95206b73d0SCy Schubert 
sta_set_airtime_weight(struct hostapd_data * hapd,struct sta_info * sta,unsigned int weight)96206b73d0SCy Schubert static int sta_set_airtime_weight(struct hostapd_data *hapd,
97206b73d0SCy Schubert 				  struct sta_info *sta,
98206b73d0SCy Schubert 				  unsigned int weight)
99206b73d0SCy Schubert {
100206b73d0SCy Schubert 	int ret = 0;
101206b73d0SCy Schubert 
102206b73d0SCy Schubert 	if (weight != sta->airtime_weight &&
103206b73d0SCy Schubert 	    (ret = hostapd_sta_set_airtime_weight(hapd, sta->addr, weight)))
104206b73d0SCy Schubert 		return ret;
105206b73d0SCy Schubert 
106206b73d0SCy Schubert 	sta->airtime_weight = weight;
107206b73d0SCy Schubert 	return ret;
108206b73d0SCy Schubert }
109206b73d0SCy Schubert 
110206b73d0SCy Schubert 
set_sta_weights(struct hostapd_data * hapd,unsigned int weight)111206b73d0SCy Schubert static void set_sta_weights(struct hostapd_data *hapd, unsigned int weight)
112206b73d0SCy Schubert {
113206b73d0SCy Schubert 	struct sta_info *sta;
114206b73d0SCy Schubert 
115206b73d0SCy Schubert 	for (sta = hapd->sta_list; sta; sta = sta->next)
116206b73d0SCy Schubert 		sta_set_airtime_weight(hapd, sta, weight);
117206b73d0SCy Schubert }
118206b73d0SCy Schubert 
119206b73d0SCy Schubert 
get_airtime_quantum(unsigned int max_wt)120206b73d0SCy Schubert static unsigned int get_airtime_quantum(unsigned int max_wt)
121206b73d0SCy Schubert {
122206b73d0SCy Schubert 	unsigned int quantum = AIRTIME_QUANTUM_TARGET / max_wt;
123206b73d0SCy Schubert 
124206b73d0SCy Schubert 	if (quantum < AIRTIME_QUANTUM_MIN)
125206b73d0SCy Schubert 		quantum = AIRTIME_QUANTUM_MIN;
126206b73d0SCy Schubert 	else if (quantum > AIRTIME_QUANTUM_MAX)
127206b73d0SCy Schubert 		quantum = AIRTIME_QUANTUM_MAX;
128206b73d0SCy Schubert 
129206b73d0SCy Schubert 	return quantum;
130206b73d0SCy Schubert }
131206b73d0SCy Schubert 
132206b73d0SCy Schubert 
update_airtime_weights(void * eloop_data,void * user_data)133206b73d0SCy Schubert static void update_airtime_weights(void *eloop_data, void *user_data)
134206b73d0SCy Schubert {
135206b73d0SCy Schubert 	struct hostapd_iface *iface = eloop_data;
136206b73d0SCy Schubert 	struct hostapd_data *bss;
137206b73d0SCy Schubert 	unsigned int sec, usec;
138206b73d0SCy Schubert 	unsigned int num_sta_min = 0, num_sta_prod = 1, num_sta_sum = 0,
139206b73d0SCy Schubert 		wt_sum = 0;
140206b73d0SCy Schubert 	unsigned int quantum;
141c1d255d3SCy Schubert 	bool all_div_min = true;
142c1d255d3SCy Schubert 	bool apply_limit = iface->conf->airtime_mode == AIRTIME_MODE_DYNAMIC;
143206b73d0SCy Schubert 	int wt, num_bss = 0, max_wt = 0;
144206b73d0SCy Schubert 	size_t i;
145206b73d0SCy Schubert 
146206b73d0SCy Schubert 	for (i = 0; i < iface->num_bss; i++) {
147206b73d0SCy Schubert 		bss = iface->bss[i];
148206b73d0SCy Schubert 		if (!bss->started || !bss->conf->airtime_weight)
149206b73d0SCy Schubert 			continue;
150206b73d0SCy Schubert 
151206b73d0SCy Schubert 		count_backlogged_sta(bss);
152206b73d0SCy Schubert 		if (!bss->num_backlogged_sta)
153206b73d0SCy Schubert 			continue;
154206b73d0SCy Schubert 
155206b73d0SCy Schubert 		if (!num_sta_min || bss->num_backlogged_sta < num_sta_min)
156206b73d0SCy Schubert 			num_sta_min = bss->num_backlogged_sta;
157206b73d0SCy Schubert 
158206b73d0SCy Schubert 		num_sta_prod *= bss->num_backlogged_sta;
159206b73d0SCy Schubert 		num_sta_sum += bss->num_backlogged_sta;
160206b73d0SCy Schubert 		wt_sum += bss->conf->airtime_weight;
161206b73d0SCy Schubert 		num_bss++;
162206b73d0SCy Schubert 	}
163206b73d0SCy Schubert 
164206b73d0SCy Schubert 	if (num_sta_min) {
165206b73d0SCy Schubert 		for (i = 0; i < iface->num_bss; i++) {
166206b73d0SCy Schubert 			bss = iface->bss[i];
167206b73d0SCy Schubert 			if (!bss->started || !bss->conf->airtime_weight)
168206b73d0SCy Schubert 				continue;
169206b73d0SCy Schubert 
170206b73d0SCy Schubert 			/* Check if we can divide all sta numbers by the
171206b73d0SCy Schubert 			 * smallest number to keep weights as small as possible.
172206b73d0SCy Schubert 			 * This is a lazy way to avoid having to factor
173206b73d0SCy Schubert 			 * integers. */
174206b73d0SCy Schubert 			if (bss->num_backlogged_sta &&
175206b73d0SCy Schubert 			    bss->num_backlogged_sta % num_sta_min > 0)
176c1d255d3SCy Schubert 				all_div_min = false;
177206b73d0SCy Schubert 
178206b73d0SCy Schubert 			/* If we're in LIMIT mode, we only apply the weight
179206b73d0SCy Schubert 			 * scaling when the BSS(es) marked as limited would a
180206b73d0SCy Schubert 			 * larger share than the relative BSS weights indicates
181206b73d0SCy Schubert 			 * it should. */
182206b73d0SCy Schubert 			if (!apply_limit && bss->conf->airtime_limit) {
183206b73d0SCy Schubert 				if (bss->num_backlogged_sta * wt_sum >
184206b73d0SCy Schubert 				    bss->conf->airtime_weight * num_sta_sum)
185c1d255d3SCy Schubert 					apply_limit = true;
186206b73d0SCy Schubert 			}
187206b73d0SCy Schubert 		}
188206b73d0SCy Schubert 		if (all_div_min)
189206b73d0SCy Schubert 			num_sta_prod /= num_sta_min;
190206b73d0SCy Schubert 	}
191206b73d0SCy Schubert 
192206b73d0SCy Schubert 	for (i = 0; i < iface->num_bss; i++) {
193206b73d0SCy Schubert 		bss = iface->bss[i];
194206b73d0SCy Schubert 		if (!bss->started || !bss->conf->airtime_weight)
195206b73d0SCy Schubert 			continue;
196206b73d0SCy Schubert 
197206b73d0SCy Schubert 		/* We only set the calculated weight if the BSS has active
198206b73d0SCy Schubert 		 * stations and there are other active interfaces as well -
199206b73d0SCy Schubert 		 * otherwise we just set a unit weight. This ensures that
200206b73d0SCy Schubert 		 * the weights are set reasonably when stations transition from
201206b73d0SCy Schubert 		 * inactive to active. */
202206b73d0SCy Schubert 		if (apply_limit && bss->num_backlogged_sta && num_bss > 1)
203206b73d0SCy Schubert 			wt = bss->conf->airtime_weight * num_sta_prod /
204206b73d0SCy Schubert 				bss->num_backlogged_sta;
205206b73d0SCy Schubert 		else
206206b73d0SCy Schubert 			wt = 1;
207206b73d0SCy Schubert 
208206b73d0SCy Schubert 		bss->airtime_weight = wt;
209206b73d0SCy Schubert 		if (wt > max_wt)
210206b73d0SCy Schubert 			max_wt = wt;
211206b73d0SCy Schubert 	}
212206b73d0SCy Schubert 
213206b73d0SCy Schubert 	quantum = get_airtime_quantum(max_wt);
214206b73d0SCy Schubert 
215206b73d0SCy Schubert 	for (i = 0; i < iface->num_bss; i++) {
216206b73d0SCy Schubert 		bss = iface->bss[i];
217206b73d0SCy Schubert 		if (!bss->started || !bss->conf->airtime_weight)
218206b73d0SCy Schubert 			continue;
219206b73d0SCy Schubert 		set_sta_weights(bss, bss->airtime_weight * quantum);
220206b73d0SCy Schubert 	}
221206b73d0SCy Schubert 
222206b73d0SCy Schubert 	if (get_airtime_policy_update_timeout(iface, &sec, &usec) < 0)
223206b73d0SCy Schubert 		return;
224206b73d0SCy Schubert 
225206b73d0SCy Schubert 	eloop_register_timeout(sec, usec, update_airtime_weights, iface,
226206b73d0SCy Schubert 			       NULL);
227206b73d0SCy Schubert }
228206b73d0SCy Schubert 
229206b73d0SCy Schubert 
get_weight_for_sta(struct hostapd_data * hapd,const u8 * sta)230206b73d0SCy Schubert static int get_weight_for_sta(struct hostapd_data *hapd, const u8 *sta)
231206b73d0SCy Schubert {
232206b73d0SCy Schubert 	struct airtime_sta_weight *wt;
233206b73d0SCy Schubert 
234206b73d0SCy Schubert 	wt = hapd->conf->airtime_weight_list;
235*a90b9d01SCy Schubert 	while (wt && !ether_addr_equal(wt->addr, sta))
236206b73d0SCy Schubert 		wt = wt->next;
237206b73d0SCy Schubert 
238206b73d0SCy Schubert 	return wt ? wt->weight : hapd->conf->airtime_weight;
239206b73d0SCy Schubert }
240206b73d0SCy Schubert 
241206b73d0SCy Schubert 
airtime_policy_new_sta(struct hostapd_data * hapd,struct sta_info * sta)242206b73d0SCy Schubert int airtime_policy_new_sta(struct hostapd_data *hapd, struct sta_info *sta)
243206b73d0SCy Schubert {
244206b73d0SCy Schubert 	unsigned int weight;
245206b73d0SCy Schubert 
246206b73d0SCy Schubert 	if (hapd->iconf->airtime_mode == AIRTIME_MODE_STATIC) {
247206b73d0SCy Schubert 		weight = get_weight_for_sta(hapd, sta->addr);
248206b73d0SCy Schubert 		if (weight)
249206b73d0SCy Schubert 			return sta_set_airtime_weight(hapd, sta, weight);
250206b73d0SCy Schubert 	}
251206b73d0SCy Schubert 	return 0;
252206b73d0SCy Schubert }
253206b73d0SCy Schubert 
254206b73d0SCy Schubert 
airtime_policy_update_init(struct hostapd_iface * iface)255206b73d0SCy Schubert int airtime_policy_update_init(struct hostapd_iface *iface)
256206b73d0SCy Schubert {
257206b73d0SCy Schubert 	unsigned int sec, usec;
258206b73d0SCy Schubert 
259206b73d0SCy Schubert 	if (iface->conf->airtime_mode < AIRTIME_MODE_DYNAMIC)
260206b73d0SCy Schubert 		return 0;
261206b73d0SCy Schubert 
262206b73d0SCy Schubert 	if (get_airtime_policy_update_timeout(iface, &sec, &usec) < 0)
263206b73d0SCy Schubert 		return -1;
264206b73d0SCy Schubert 
265206b73d0SCy Schubert 	eloop_register_timeout(sec, usec, update_airtime_weights, iface, NULL);
266206b73d0SCy Schubert 	return 0;
267206b73d0SCy Schubert }
268206b73d0SCy Schubert 
269206b73d0SCy Schubert 
airtime_policy_update_deinit(struct hostapd_iface * iface)270206b73d0SCy Schubert void airtime_policy_update_deinit(struct hostapd_iface *iface)
271206b73d0SCy Schubert {
272206b73d0SCy Schubert 	eloop_cancel_timeout(update_airtime_weights, iface, NULL);
273206b73d0SCy Schubert }
274