1351377Scy/*
2351377Scy * Airtime policy configuration
3351377Scy * Copyright (c) 2018-2019, Toke H��iland-J��rgensen <toke@toke.dk>
4351377Scy *
5351377Scy * This software may be distributed under the terms of the BSD license.
6351377Scy * See README for more details.
7351377Scy */
8351377Scy
9351377Scy#include "utils/includes.h"
10351377Scy
11351377Scy#include "utils/common.h"
12351377Scy#include "utils/eloop.h"
13351377Scy#include "hostapd.h"
14351377Scy#include "ap_drv_ops.h"
15351377Scy#include "sta_info.h"
16351377Scy#include "airtime_policy.h"
17351377Scy
18351377Scy/* Idea:
19351377Scy * Two modes of airtime enforcement:
20351377Scy * 1. Static weights: specify weights per MAC address with a per-BSS default
21351377Scy * 2. Per-BSS limits: Dynamically calculate weights of backlogged stations to
22351377Scy *    enforce relative total shares between BSSes.
23351377Scy *
24351377Scy * - Periodic per-station callback to update queue status.
25351377Scy *
26351377Scy * Copy accounting_sta_update_stats() to get TXQ info and airtime weights and
27351377Scy * keep them updated in sta_info.
28351377Scy *
29351377Scy * - Separate periodic per-bss (or per-iface?) callback to update weights.
30351377Scy *
31351377Scy * Just need to loop through all interfaces, count sum the active stations (or
32351377Scy * should the per-STA callback just adjust that for the BSS?) and calculate new
33351377Scy * weights.
34351377Scy */
35351377Scy
36351377Scystatic int get_airtime_policy_update_timeout(struct hostapd_iface *iface,
37351377Scy					     unsigned int *sec,
38351377Scy					     unsigned int *usec)
39351377Scy{
40351377Scy	unsigned int update_int = iface->conf->airtime_update_interval;
41351377Scy
42351377Scy	if (!update_int) {
43351377Scy		wpa_printf(MSG_ERROR,
44351377Scy			   "Airtime policy: Invalid airtime policy update interval %u",
45351377Scy			   update_int);
46351377Scy		return -1;
47351377Scy	}
48351377Scy
49351377Scy	*sec = update_int / 1000;
50351377Scy	*usec = (update_int % 1000) * 1000;
51351377Scy
52351377Scy	return 0;
53351377Scy}
54351377Scy
55351377Scy
56351377Scystatic void set_new_backlog_time(struct hostapd_data *hapd,
57351377Scy				 struct sta_info *sta,
58351377Scy				 struct os_reltime *now)
59351377Scy{
60351377Scy	sta->backlogged_until = *now;
61351377Scy	sta->backlogged_until.usec += hapd->iconf->airtime_update_interval *
62351377Scy		AIRTIME_BACKLOG_EXPIRY_FACTOR;
63351377Scy	while (sta->backlogged_until.usec >= 1000000) {
64351377Scy		sta->backlogged_until.sec++;
65351377Scy		sta->backlogged_until.usec -= 1000000;
66351377Scy	}
67351377Scy}
68351377Scy
69351377Scy
70351377Scystatic void count_backlogged_sta(struct hostapd_data *hapd)
71351377Scy{
72351377Scy	struct sta_info *sta;
73351377Scy	struct hostap_sta_driver_data data = {};
74351377Scy	unsigned int num_backlogged = 0;
75351377Scy	struct os_reltime now;
76351377Scy
77351377Scy	os_get_reltime(&now);
78351377Scy
79351377Scy	for (sta = hapd->sta_list; sta; sta = sta->next) {
80351377Scy		if (hostapd_drv_read_sta_data(hapd, &data, sta->addr))
81351377Scy			continue;
82351377Scy
83351377Scy		if (data.backlog_bytes > 0)
84351377Scy			set_new_backlog_time(hapd, sta, &now);
85351377Scy		if (os_reltime_before(&now, &sta->backlogged_until))
86351377Scy			num_backlogged++;
87351377Scy	}
88351377Scy	hapd->num_backlogged_sta = num_backlogged;
89351377Scy}
90351377Scy
91351377Scy
92351377Scystatic int sta_set_airtime_weight(struct hostapd_data *hapd,
93351377Scy				  struct sta_info *sta,
94351377Scy				  unsigned int weight)
95351377Scy{
96351377Scy	int ret = 0;
97351377Scy
98351377Scy	if (weight != sta->airtime_weight &&
99351377Scy	    (ret = hostapd_sta_set_airtime_weight(hapd, sta->addr, weight)))
100351377Scy		return ret;
101351377Scy
102351377Scy	sta->airtime_weight = weight;
103351377Scy	return ret;
104351377Scy}
105351377Scy
106351377Scy
107351377Scystatic void set_sta_weights(struct hostapd_data *hapd, unsigned int weight)
108351377Scy{
109351377Scy	struct sta_info *sta;
110351377Scy
111351377Scy	for (sta = hapd->sta_list; sta; sta = sta->next)
112351377Scy		sta_set_airtime_weight(hapd, sta, weight);
113351377Scy}
114351377Scy
115351377Scy
116351377Scystatic unsigned int get_airtime_quantum(unsigned int max_wt)
117351377Scy{
118351377Scy	unsigned int quantum = AIRTIME_QUANTUM_TARGET / max_wt;
119351377Scy
120351377Scy	if (quantum < AIRTIME_QUANTUM_MIN)
121351377Scy		quantum = AIRTIME_QUANTUM_MIN;
122351377Scy	else if (quantum > AIRTIME_QUANTUM_MAX)
123351377Scy		quantum = AIRTIME_QUANTUM_MAX;
124351377Scy
125351377Scy	return quantum;
126351377Scy}
127351377Scy
128351377Scy
129351377Scystatic void update_airtime_weights(void *eloop_data, void *user_data)
130351377Scy{
131351377Scy	struct hostapd_iface *iface = eloop_data;
132351377Scy	struct hostapd_data *bss;
133351377Scy	unsigned int sec, usec;
134351377Scy	unsigned int num_sta_min = 0, num_sta_prod = 1, num_sta_sum = 0,
135351377Scy		wt_sum = 0;
136351377Scy	unsigned int quantum;
137351377Scy	Boolean all_div_min = TRUE;
138351377Scy	Boolean apply_limit = iface->conf->airtime_mode == AIRTIME_MODE_DYNAMIC;
139351377Scy	int wt, num_bss = 0, max_wt = 0;
140351377Scy	size_t i;
141351377Scy
142351377Scy	for (i = 0; i < iface->num_bss; i++) {
143351377Scy		bss = iface->bss[i];
144351377Scy		if (!bss->started || !bss->conf->airtime_weight)
145351377Scy			continue;
146351377Scy
147351377Scy		count_backlogged_sta(bss);
148351377Scy		if (!bss->num_backlogged_sta)
149351377Scy			continue;
150351377Scy
151351377Scy		if (!num_sta_min || bss->num_backlogged_sta < num_sta_min)
152351377Scy			num_sta_min = bss->num_backlogged_sta;
153351377Scy
154351377Scy		num_sta_prod *= bss->num_backlogged_sta;
155351377Scy		num_sta_sum += bss->num_backlogged_sta;
156351377Scy		wt_sum += bss->conf->airtime_weight;
157351377Scy		num_bss++;
158351377Scy	}
159351377Scy
160351377Scy	if (num_sta_min) {
161351377Scy		for (i = 0; i < iface->num_bss; i++) {
162351377Scy			bss = iface->bss[i];
163351377Scy			if (!bss->started || !bss->conf->airtime_weight)
164351377Scy				continue;
165351377Scy
166351377Scy			/* Check if we can divide all sta numbers by the
167351377Scy			 * smallest number to keep weights as small as possible.
168351377Scy			 * This is a lazy way to avoid having to factor
169351377Scy			 * integers. */
170351377Scy			if (bss->num_backlogged_sta &&
171351377Scy			    bss->num_backlogged_sta % num_sta_min > 0)
172351377Scy				all_div_min = FALSE;
173351377Scy
174351377Scy			/* If we're in LIMIT mode, we only apply the weight
175351377Scy			 * scaling when the BSS(es) marked as limited would a
176351377Scy			 * larger share than the relative BSS weights indicates
177351377Scy			 * it should. */
178351377Scy			if (!apply_limit && bss->conf->airtime_limit) {
179351377Scy				if (bss->num_backlogged_sta * wt_sum >
180351377Scy				    bss->conf->airtime_weight * num_sta_sum)
181351377Scy					apply_limit = TRUE;
182351377Scy			}
183351377Scy		}
184351377Scy		if (all_div_min)
185351377Scy			num_sta_prod /= num_sta_min;
186351377Scy	}
187351377Scy
188351377Scy	for (i = 0; i < iface->num_bss; i++) {
189351377Scy		bss = iface->bss[i];
190351377Scy		if (!bss->started || !bss->conf->airtime_weight)
191351377Scy			continue;
192351377Scy
193351377Scy		/* We only set the calculated weight if the BSS has active
194351377Scy		 * stations and there are other active interfaces as well -
195351377Scy		 * otherwise we just set a unit weight. This ensures that
196351377Scy		 * the weights are set reasonably when stations transition from
197351377Scy		 * inactive to active. */
198351377Scy		if (apply_limit && bss->num_backlogged_sta && num_bss > 1)
199351377Scy			wt = bss->conf->airtime_weight * num_sta_prod /
200351377Scy				bss->num_backlogged_sta;
201351377Scy		else
202351377Scy			wt = 1;
203351377Scy
204351377Scy		bss->airtime_weight = wt;
205351377Scy		if (wt > max_wt)
206351377Scy			max_wt = wt;
207351377Scy	}
208351377Scy
209351377Scy	quantum = get_airtime_quantum(max_wt);
210351377Scy
211351377Scy	for (i = 0; i < iface->num_bss; i++) {
212351377Scy		bss = iface->bss[i];
213351377Scy		if (!bss->started || !bss->conf->airtime_weight)
214351377Scy			continue;
215351377Scy		set_sta_weights(bss, bss->airtime_weight * quantum);
216351377Scy	}
217351377Scy
218351377Scy	if (get_airtime_policy_update_timeout(iface, &sec, &usec) < 0)
219351377Scy		return;
220351377Scy
221351377Scy	eloop_register_timeout(sec, usec, update_airtime_weights, iface,
222351377Scy			       NULL);
223351377Scy}
224351377Scy
225351377Scy
226351377Scystatic int get_weight_for_sta(struct hostapd_data *hapd, const u8 *sta)
227351377Scy{
228351377Scy	struct airtime_sta_weight *wt;
229351377Scy
230351377Scy	wt = hapd->conf->airtime_weight_list;
231351377Scy	while (wt && os_memcmp(wt->addr, sta, ETH_ALEN) != 0)
232351377Scy		wt = wt->next;
233351377Scy
234351377Scy	return wt ? wt->weight : hapd->conf->airtime_weight;
235351377Scy}
236351377Scy
237351377Scy
238351377Scyint airtime_policy_new_sta(struct hostapd_data *hapd, struct sta_info *sta)
239351377Scy{
240351377Scy	unsigned int weight;
241351377Scy
242351377Scy	if (hapd->iconf->airtime_mode == AIRTIME_MODE_STATIC) {
243351377Scy		weight = get_weight_for_sta(hapd, sta->addr);
244351377Scy		if (weight)
245351377Scy			return sta_set_airtime_weight(hapd, sta, weight);
246351377Scy	}
247351377Scy	return 0;
248351377Scy}
249351377Scy
250351377Scy
251351377Scyint airtime_policy_update_init(struct hostapd_iface *iface)
252351377Scy{
253351377Scy	unsigned int sec, usec;
254351377Scy
255351377Scy	if (iface->conf->airtime_mode < AIRTIME_MODE_DYNAMIC)
256351377Scy		return 0;
257351377Scy
258351377Scy	if (get_airtime_policy_update_timeout(iface, &sec, &usec) < 0)
259351377Scy		return -1;
260351377Scy
261351377Scy	eloop_register_timeout(sec, usec, update_airtime_weights, iface, NULL);
262351377Scy	return 0;
263351377Scy}
264351377Scy
265351377Scy
266351377Scyvoid airtime_policy_update_deinit(struct hostapd_iface *iface)
267351377Scy{
268351377Scy	eloop_cancel_timeout(update_airtime_weights, iface, NULL);
269351377Scy}
270