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