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
36static 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
56static 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
70static 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
96static 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
111static 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
120static 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
133static 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
230static 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
242int 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
255int 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
270void airtime_policy_update_deinit(struct hostapd_iface *iface)
271{
272	eloop_cancel_timeout(update_airtime_weights, iface, NULL);
273}
274