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