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