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