From ead2703ea9c80bbe8238f855303f436ec01d05f6 Mon Sep 17 00:00:00 2001 From: Janusz Dziedzic <janusz.dziedzic@iopsys.eu> Date: Mon, 9 Jun 2025 11:26:21 +0200 Subject: [PATCH] acs: introduce improvements UBUS: - add trigger_acs - improve trigger_channel_clearing - report clearing/acs status in ubus status - report status request during ubus call CMDUS: - handle and use CAC completion report - handle and use CAC status - handle and use oper channel report - fix generation of channel selection request ACS: - add CMDUs ACS callbacks - use them for recalc - remove dfs_cleanup timer - base on events when recalc - fix bsta connected check - run clearing when background CAC supported by node - run acs recalc when DFS chan available - introduce backoff when platform don't report CAC ongoing correctly - allow to choose highest bandwidth - store and check radio metrics CONFIG: Available options: config channel_plan option preclear_dfs '1' option acs '1' option acs_timeout '3h' option acs_highest_bandwidth '0' option acs_skip_dfs '0' option acs_prevent_cac '1' option acs_scan_before_recalc '1' --- src/acs.c | 1329 ++++++++++++++++++++++++++++++++++--- src/acs.h | 52 +- src/cntlr.c | 36 +- src/cntlr.h | 1 - src/cntlr_cmdu.c | 39 +- src/cntlr_cmdu.h | 9 +- src/cntlr_commands.c | 54 +- src/cntlr_commands.h | 23 + src/cntlr_commands_impl.c | 135 +++- src/cntlr_commands_impl.h | 1 + src/cntlr_map.c | 164 ++++- src/cntlr_tlv.c | 146 ++-- src/cntlr_tlv.h | 3 - src/cntlr_ubus.c | 29 + src/cntlr_ubus_dbg.c | 2 +- src/config.c | 163 ++++- src/config.h | 7 +- src/timer.c | 2 +- src/utils/utils.c | 16 + src/utils/utils.h | 1 + src/wifi_dataelements.h | 4 + src/wifi_opclass.c | 151 ++++- src/wifi_opclass.h | 9 +- 23 files changed, 2007 insertions(+), 369 deletions(-) diff --git a/src/acs.c b/src/acs.c index 6c014f08..6fc7dd82 100644 --- a/src/acs.c +++ b/src/acs.c @@ -17,11 +17,18 @@ #include "cntlr.h" #include "cntlr_cmdu.h" +#include "cntlr_map.h" #include "utils/debug.h" +#include "utils/utils.h" #include "wifi_dataelements.h" #include "wifi_opclass.h" +#include "timer.h" +#include "sta.h" +/* Per radio ACS recalc timer */ +static void cntlr_radio_acs_timer(atimer_t *t); +/* Opclass helpers - move it to other place */ int cntlr_radio_pref_opclass_reset(struct wifi_radio_element *radio) { /* @@ -30,10 +37,29 @@ int cntlr_radio_pref_opclass_reset(struct wifi_radio_element *radio) */ memcpy(&radio->pref_opclass, &radio->supp_opclass, sizeof(radio->pref_opclass)); wifi_opclass_set_preferences(&radio->pref_opclass, 15 << 4); + timestamp_update(&radio->pref_opclass.entry_time); return radio->pref_opclass.num_opclass; } +enum wifi_radio_opclass_dfs dfs_state_from_preference(uint8_t preference) +{ + uint8_t reas = preference & CHANNEL_PREF_REASON; + + switch(reas) { + case CHANNEL_PREF_REASON_DFS_USABLE: + return WIFI_RADIO_OPCLASS_CHANNEL_DFS_USABLE; + case CHANNEL_PREF_REASON_DFS_AVAILABLE: + return WIFI_RADIO_OPCLASS_CHANNEL_DFS_AVAILABLE; + case CHANNEL_PREF_REASON_DFS_NOP: + return WIFI_RADIO_OPCLASS_CHANNEL_DFS_NOP; + default: + break; + } + + return WIFI_RADIO_OPCLASS_CHANNEL_DFS_NONE; +} + int cntlr_radio_pref_opclass_add(struct wifi_radio_element *radio, uint8_t classid, uint8_t channel, uint8_t preference) { @@ -53,6 +79,7 @@ int cntlr_radio_pref_opclass_add(struct wifi_radio_element *radio, uint8_t class new_chan.channel = channel; new_chan.preference = preference; + new_chan.dfs = dfs_state_from_preference(preference); timestamp_update(&opclass->entry_time); @@ -61,12 +88,31 @@ int cntlr_radio_pref_opclass_add(struct wifi_radio_element *radio, uint8_t class if (chan) { chan->channel = channel; chan->preference = preference; + + chan->dfs = dfs_state_from_preference(preference); + return 0; } return wifi_opclass_add_channel(entry, &new_chan); } +int cntlr_radio_pref_opclass_set_dfs_status(struct wifi_radio_element *radio, uint8_t classid, + uint8_t channel, enum wifi_radio_opclass_dfs state) +{ + struct wifi_radio_opclass_channel *chan; + struct wifi_radio_opclass *opclass; + + opclass = &radio->pref_opclass; + + chan = wifi_opclass_get_channel(opclass, classid, channel); + if (!chan) + return -1; + + chan->dfs = state; + return 0; +} + void cntlr_radio_pref_opclass_dump(struct wifi_radio_element *radio) { wifi_opclass_dump(&radio->pref_opclass, "dev_pref_opclass", radio->macaddr); @@ -105,7 +151,7 @@ uint8_t ctrl_radio_cur_opclass_id(struct wifi_radio_element *radio) uint8_t id = 0; int ret; - ret = wifi_opclass_get_max_bw(&radio->cur_opclass, NULL, NULL, &id); + ret = wifi_opclass_get_max_bw(&radio->cur_opclass, NULL, NULL, &id, NULL); if (ret) return 0; return id; @@ -116,18 +162,29 @@ uint8_t ctrl_radio_cur_opclass_ctrl_chan(struct wifi_radio_element *radio) uint8_t ctrl_chan = 0; int ret; - ret = wifi_opclass_get_max_bw(&radio->cur_opclass, &ctrl_chan, NULL, NULL); + ret = wifi_opclass_get_max_bw(&radio->cur_opclass, &ctrl_chan, NULL, NULL, NULL); if (ret) return 0; return ctrl_chan; } -uint8_t ctrl_radio_cur_opclass_max_bw(struct wifi_radio_element *radio) +uint8_t ctrl_radio_cur_opclass_chan(struct wifi_radio_element *radio) +{ + uint8_t chan = 0; + int ret; + + ret = wifi_opclass_get_max_bw(&radio->cur_opclass, NULL, NULL, NULL, &chan); + if (ret) + return 0; + return chan; +} + +uint16_t ctrl_radio_cur_opclass_max_bw(struct wifi_radio_element *radio) { uint32_t bw = 0; int ret; - ret = wifi_opclass_get_max_bw(&radio->cur_opclass, NULL, &bw, NULL); + ret = wifi_opclass_get_max_bw(&radio->cur_opclass, NULL, &bw, NULL, NULL); if (ret) return 0; return bw; @@ -143,42 +200,552 @@ void cntlr_radio_cur_opclass_dump(struct wifi_radio_element *radio) wifi_opclass_dump(&radio->cur_opclass, "dev_cur_opclass", radio->macaddr); } -static bool cntlr_radio_pref_opclass_expired(struct wifi_radio_element *radio) +static bool cntlr_radio_pref_opclass_expired(struct wifi_radio_element *radio, int age) { - return wifi_opclass_expired(&radio->pref_opclass, 120); + return wifi_opclass_expired(&radio->pref_opclass, age); } -bool cntlr_node_pref_opclass_expired(struct node *node) +bool cntlr_node_pref_opclass_expired(struct node *node, int age) { struct netif_radio *r = NULL; bool expired = false; list_for_each_entry(r, &node->radiolist, list) { - expired |= cntlr_radio_pref_opclass_expired(r->radio_el); + expired |= cntlr_radio_pref_opclass_expired(r->radio_el, age); } return expired; } +enum acs_cleanup_status { + ACS_CLEANUP_STATUS_SKIPPED, + ACS_CLEANUP_STATUS_SELECTED, + ACS_CLEANUP_STATUS_REQUESTED, + ACS_CLEANUP_STATUS_REJECTED, + ACS_CLEANUP_STATUS_RADAR, + ACS_CLEANUP_STATUS_ACCEPTED, + ACS_CLEANUP_STATUS_DONE, + ACS_CLEANUP_STATUS_ALL_CLEAN, + ACS_CLEANUP_STATUS_NOT_SUPPORTED, + ACS_CLEANUP_STATUS_NO_APS, + ACS_CLEANUP_STATUS_ALREADY_ONGOING, + ACS_CLEANUP_STATUS_TIMEOUT, +}; + +enum acs_recalc_status { + ACS_RECALC_STATUS_SKIPPED, + ACS_RECALC_STATUS_SCAN_REQUESTED, + ACS_RECALC_STATUS_SCAN_DONE, + ACS_RECALC_STATUS_BEST_SELECTED, + ACS_RECALC_STATUS_BEST_REQUESTED, + ACS_RECALC_STATUS_BEST_REJECTED, + ACS_RECALC_STATUS_BEST_ACCEPTED, + ACS_RECALC_STATUS_BEST_SET, + ACS_RECALC_STATUS_BEST_ALREADY_SET, + ACS_RECALC_STATUS_INPUT_DATA_INSUFFICIENT, + ACS_RECALC_STATUS_BSTA_CONNECTED, + ACS_RECALC_STATUS_INVALID_DATA, + ACS_RECALC_STATUS_BEST_SET_TIMEOUT, + ACS_RECALC_STATUS_BEST_CAC, + ACS_RECALC_STATUS_BEST_CAC_RADAR, +}; + +struct acs_params { + struct timespec entry_time; + + /* Input params */ + int opclass; /* recalc for this opclass */ + int bw; /* recalc for this bandwidth */ + + bool skip_dfs; /* Skip all DFS channels */ + bool skip_dfs_not_available; /* Prevent CAC */ + bool higest_bandwidth; /* Use highest possible bandwidth */ + + /* Output params - for channel selection request */ + int best_channel; + int best_opclass; + int best_bw; + int best_pref; + uint32_t best_cac_time; + + enum acs_recalc_status status; /* current recalc state */ + uint16_t scan_mid; /* scan request mid */ + uint16_t mid; /* channel selection mid */ + + bool recalc; /* run new recalc */ +}; + +struct acs_cac_data { + struct cac_data data; /* data for CAC CMDU */ + + struct timespec entry_time; + enum acs_cleanup_status status; /* current CAC state */ + uint16_t mid; /* CAC request mid */ + uint32_t cac_time; /* CAC time for current request */ +}; + +struct acs_radio_metrics_entry { + struct timespec time; + + uint8_t anpi; /* Noise level */ + uint8_t obss; /* Other BSS busy factor */ +}; + +struct acs_radio_metrics { + struct timespec time; + + uint8_t anpi; /* EWMA value */ + uint8_t obss; /* EWMA value */ + + /* Ring buffer for more data */ + uint8_t idx; + struct acs_radio_metrics_entry entry[16]; +}; + +/* acs per radio structure */ +struct acs { + struct acs_params last_acs; + struct acs_cac_data last_cac_data; + struct acs_radio_metrics radio_metrics; + + struct controller *cntlr; + struct node *node; + struct netif_radio *radio; + + atimer_t acs_timer; +}; + +/* Called when conroller alloc radio structure */ +void *cntlr_radio_acs_alloc(struct controller *c, struct node *n, struct netif_radio *r) +{ + struct acs *acs; + + acs = calloc(1, sizeof(struct acs)); + if (!acs) + return NULL; + + acs->cntlr = c; + acs->node = n; + acs->radio = r; + + timer_init(&acs->acs_timer, cntlr_radio_acs_timer); + return acs; +} + +/* Called before controller destroy radio structure */ +void cntlr_radio_acs_free(void *acs) +{ + struct acs *ptr = acs; + + if (!ptr) + return; + + timer_del(&ptr->acs_timer); + ptr->cntlr = NULL; + ptr->node = NULL; + ptr->radio = NULL; + + /* Do required cleanup here before free memory */ + free(ptr); +} + +/* acs/cac/metrics - helpers */ +static struct acs_params *cntlr_radio_get_last_acs(struct netif_radio *r) +{ + struct acs *acs; + + acs = r->radio_el->acs; + if (!acs) + return NULL; + + return &acs->last_acs; +} + +static struct acs_cac_data *cntlr_radio_get_last_cac(struct netif_radio *r) +{ + struct acs *acs; + + acs = r->radio_el->acs; + if (!acs) + return NULL; + + return &acs->last_cac_data; +} + +static struct acs_radio_metrics *cntlr_radio_get_metrics(struct netif_radio *r) +{ + struct acs *acs; + + acs = r->radio_el->acs; + if (!acs) + return NULL; + + return &acs->radio_metrics; +} + +static int cntlr_radio_acs_timer_set(struct netif_radio *r, uint32_t tmo_ms) +{ + struct acs *acs; + + acs = r->radio_el->acs; + if (!acs) + return -1; + + return timer_set(&acs->acs_timer, tmo_ms); +} + +/* JSON status print - for UBUS radio status and trigger_acs/trigger_channel_clearing */ +static char *cntlr_acs_radio_status(enum acs_recalc_status status) +{ + switch (status) { + case ACS_RECALC_STATUS_SKIPPED: + return "skipped"; + case ACS_RECALC_STATUS_SCAN_REQUESTED: + return "scan requested"; + case ACS_RECALC_STATUS_SCAN_DONE: + return "scan done"; + case ACS_RECALC_STATUS_BEST_SELECTED: + return "best_selected"; + case ACS_RECALC_STATUS_BEST_REQUESTED: + return "best_requested"; + case ACS_RECALC_STATUS_BEST_ACCEPTED: + return "best_accepted"; + case ACS_RECALC_STATUS_BEST_REJECTED: + return "best_rejected"; + case ACS_RECALC_STATUS_BEST_SET: + return "best_set"; + case ACS_RECALC_STATUS_INPUT_DATA_INSUFFICIENT: + return "insufficient_data"; + case ACS_RECALC_STATUS_BEST_ALREADY_SET: + return "current_best"; + case ACS_RECALC_STATUS_BSTA_CONNECTED: + return "skipped - bsta connected"; + case ACS_RECALC_STATUS_INVALID_DATA: + return "skipped - invalid data"; + case ACS_RECALC_STATUS_BEST_SET_TIMEOUT: + return "best_set timeout"; + case ACS_RECALC_STATUS_BEST_CAC: + return "best_set CAC ongoing"; + case ACS_RECALC_STATUS_BEST_CAC_RADAR: + return "best_set CAC RADAR hit"; + default: + break; + } + + return "recalc_unknown"; +} + +void cntlr_acs_radio_info(struct blob_buf *bb, struct netif_radio *r) +{ + struct acs_params *acs; + void *t, *a; + + acs = cntlr_radio_get_last_acs(r); + if (!acs) + return; + + blobmsg_add_u32(bb, "acs_request_age", (uint32_t) timestamp_elapsed_sec(&acs->entry_time)); + a = blobmsg_open_array(bb, "acs_request"); + t = blobmsg_open_table(bb, ""); + blobmsg_add_string(bb, "status", cntlr_acs_radio_status(acs->status)); + blobmsg_add_u32(bb, "channel", acs->best_channel); + blobmsg_add_u32(bb, "bandwidth", acs->best_bw); + + if (acs->status == ACS_RECALC_STATUS_BEST_REQUESTED) + blobmsg_add_u32(bb, "mid", acs->mid); + blobmsg_close_table(bb, t); + blobmsg_close_array(bb, a); +} + +void cntlr_acs_node_info(struct node *node, enum wifi_band band, struct blob_buf *bb) +{ + struct netif_radio *r = NULL; + void *t; + + list_for_each_entry(r, &node->radiolist, list) { + struct acs_params *acs; + + acs = cntlr_radio_get_last_acs(r); + if (!acs) + continue; + + if (band && band != BAND_ANY && band != r->radio_el->band) + continue; + + if (acs->status == ACS_RECALC_STATUS_SKIPPED) + continue; + + t = blobmsg_open_table(bb, ""); + blobmsg_add_macaddr(bb, "agent", node->almacaddr); + blobmsg_add_macaddr(bb, "radio", r->radio_el->macaddr); + blobmsg_add_string(bb, "status", cntlr_acs_radio_status(acs->status)); + blobmsg_add_u32(bb, "channel", acs->best_channel); + blobmsg_add_u32(bb, "bandwidth", acs->best_bw); + + if (acs->status == ACS_RECALC_STATUS_BEST_REQUESTED) + blobmsg_add_u32(bb, "mid", acs->mid); + + blobmsg_close_table(bb, t); + } +} + +static char *cntlr_acs_radio_cleanup_status(enum acs_cleanup_status status) +{ + switch (status) { + case ACS_CLEANUP_STATUS_SKIPPED: + return "none"; + case ACS_CLEANUP_STATUS_SELECTED: + return "selected"; + case ACS_CLEANUP_STATUS_REQUESTED: + return "requested"; + case ACS_CLEANUP_STATUS_REJECTED: + return "rejected"; + case ACS_CLEANUP_STATUS_RADAR: + return "radar"; + case ACS_CLEANUP_STATUS_ACCEPTED: + return "accepted"; + case ACS_CLEANUP_STATUS_DONE: + return "done"; + case ACS_CLEANUP_STATUS_ALL_CLEAN: + return "all clean"; + case ACS_CLEANUP_STATUS_NOT_SUPPORTED: + return "not supported"; + case ACS_CLEANUP_STATUS_NO_APS: + return "no APs iface"; + case ACS_CLEANUP_STATUS_ALREADY_ONGOING: + return "CAC already ongoing"; + case ACS_CLEANUP_STATUS_TIMEOUT: + return "timeout"; + default: + break; + } + return "unknown"; +} + +void cntlr_dfs_radio_cleanup_info(struct node *n, struct netif_radio *r, struct blob_buf *bb) +{ + struct acs_cac_data *cac; + void *t; + + cac = cntlr_radio_get_last_cac(r); + if (!cac) + return; + + t = blobmsg_open_table(bb, ""); + blobmsg_add_macaddr(bb, "agent", n->almacaddr); + blobmsg_add_macaddr(bb, "radio", r->radio_el->macaddr); + blobmsg_add_string(bb, "status", cntlr_acs_radio_cleanup_status(cac->status)); + blobmsg_add_u32(bb, "channel", cac->data.channel); + blobmsg_add_u32(bb, "opclass", cac->data.opclass); + + if (cac->status == ACS_CLEANUP_STATUS_REQUESTED) + blobmsg_add_u32(bb, "mid", cac->mid); + blobmsg_close_table(bb, t); +} + +void cntlr_acs_radio_cleanup_info(struct blob_buf *bb, struct netif_radio *r) +{ + struct acs_cac_data *cac; + void *t, *a; + + cac = cntlr_radio_get_last_cac(r); + if (!cac) + return; + + blobmsg_add_u32(bb, "cleanup_request_age", (uint32_t) timestamp_elapsed_sec(&cac->entry_time)); + a = blobmsg_open_array(bb, "cleanup_request"); + t = blobmsg_open_table(bb, ""); + blobmsg_add_string(bb, "status", cntlr_acs_radio_cleanup_status(cac->status)); + blobmsg_add_u32(bb, "channel", cac->data.channel); + blobmsg_add_u32(bb, "opclass", cac->data.opclass); + + if (cac->status == ACS_CLEANUP_STATUS_REQUESTED) + blobmsg_add_u32(bb, "mid", cac->mid); + blobmsg_close_table(bb, t); + blobmsg_close_array(bb, a); +} + +/* CMDU ACS builders - for scan request / preference request */ +static uint16_t cntlr_acs_send_scan_request(struct node *n, struct netif_radio *r) +{ + struct wifi_radio_opclass_entry *entry; + struct wifi_radio_opclass *opclass; + struct scan_req_data req = {}; + struct cmdu_buff *cmdu; + uint16_t mid = 0xffff; + int num = 0; + int i; + + cntlr_dbg(LOG_CHANNEL, "acs node " MACFMT " " MACFMT " - scan request\n", + MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr)); + + opclass = &r->radio_el->pref_opclass; + + if (!opclass->num_opclass) + goto out; + + /* Build proper scan request - all 20MHz channels */ + req.is_fresh_scan = true; + req.num_radio = 1; + memcpy(req.radios[0].radio_mac, r->radio_el->macaddr, 6); + + for (i = 0; i < opclass->num_opclass; i++) { + entry = &opclass->opclass[i]; + + if (entry->bandwidth != 20) + continue; + if (entry->num_channel == 0) + continue; + + if (num >= ARRAY_SIZE(req.radios[0].opclasses)) + break; + req.radios[0].opclasses[num].classid = entry->id; + num++; + } + + req.radios[0].num_opclass = num; + + cmdu = cntlr_gen_channel_scan_request(n->cntlr, n->almacaddr, &req); + if (!cmdu) + goto out; + + mid = send_cmdu(n->cntlr, cmdu); + cmdu_free(cmdu); + +out: + cntlr_dbg(LOG_CHANNEL, "acs node " MACFMT " " MACFMT " - scan request mid %u\n", + MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr), mid); + return mid; +} + +static uint16_t cntlr_acs_send_pref_request(struct node *n) +{ + struct cmdu_buff *cmdu; + uint16_t mid = 0xffff; + + cntlr_dbg(LOG_CHANNEL, "acs node send pref request " MACFMT "\n", MAC2STR(n->almacaddr)); + cmdu = cntlr_gen_channel_preference_query(n->cntlr, n->almacaddr); + if (!cmdu) + return mid; + + mid = send_cmdu(n->cntlr, cmdu); + cmdu_free(cmdu); + + return mid; +} + +/* ACS recalc code */ +uint8_t cntrl_acs_radio_ctrl_channel(struct wifi_radio_element *radio, uint8_t opclass, uint8_t channel) +{ + struct wifi_radio_opclass_channel *chan; + + chan = wifi_opclass_get_channel(&radio->pref_opclass, opclass, channel); + if (!chan) + return 0; + + return wifi_get_best_ctrl_channel(&radio->pref_opclass, + chan->ctrl_channels, + ARRAY_SIZE(chan->ctrl_channels)); +} + +static bool cntlr_acs_radio_is_bsta_connected(struct controller *cntlr, struct netif_radio *radio) +{ + struct netif_iface *iface = NULL; + + list_for_each_entry(iface, &radio->iflist, list) { + struct node *n = NULL; + + /* Check if sta iface */ + if (iface->bss->is_bbss || iface->bss->is_fbss) + continue; + + /* Check all nodes */ + list_for_each_entry(n, &cntlr->nodelist, list) { + struct sta *s = NULL; + + if (!n->sta_count) + continue; + + list_for_each_entry(s, &n->stalist, list) { + if (!s->de_sta) + continue; + /* skip other band */ + if (memcmp(iface->bss->bssid, s->de_sta->macaddr, sizeof(iface->bss->bssid))) + continue; + + if (s->is_bsta && s->state == STA_ASSOCIATED) + return true; + } + } + } + + return false; +} + +static void cntrl_acs_radio_common_opclass(struct controller *cntlr, struct netif_radio *rd, + struct wifi_radio_opclass *opclass) +{ + memcpy(opclass, &rd->radio_el->pref_opclass, sizeof(*opclass)); -int cntlr_acs_radio_channel_recalc(struct wifi_radio_element *radio, struct acs_params *params) + /* + * For future use just in case nodes using different supported + * channels. Prevent switch to channel/opclass not supported + * by leaf(s). + * We could skip this step when ethernet backhaul used. + */ +} + +int cntlr_acs_radio_channel_recalc(struct node *node, struct netif_radio *rd, struct acs_params *params) { + struct wifi_radio_element *radio = rd->radio_el; + struct wifi_radio_opclass common_opclass = {}; struct acs_params acs_params[64] = {}; - int acs_params_num = 0; struct wifi_radio_opclass_entry *entry; struct wifi_radio_opclass *opclass; + struct acs_params *last_acs; + int acs_params_num = 0; int chan, pref, reas; int pref_best = 0; int pref_cur = 0; + uint32_t cac_time = 0; int prefered[64] = {0}; int i, j, r; - opclass = &radio->pref_opclass; - - cntlr_dbg(LOG_CHANNEL, "acs radio channel recalc " MACFMT " opclass %d bw %d skip_dfs %d\n", + cntlr_trace(LOG_CHANNEL, "acs radio channel recalc " MACFMT " opclass %d bw %d skip_dfs %d\n", MAC2STR(radio->macaddr), params->opclass, params->bw, params->skip_dfs); + last_acs = cntlr_radio_get_last_acs(rd); + if (!last_acs) + return -1; + + memset(last_acs, 0, sizeof(*last_acs)); + last_acs->scan_mid = 0xffff; + + if (!radio->pref_opclass.num_opclass) { + /* We don't know node preferences yet */ + cntlr_dbg(LOG_CHANNEL, "acs radio channel recalc " MACFMT " - no pref opclass - skip recalc\n", + MAC2STR(radio->macaddr)); + + params->status = ACS_RECALC_STATUS_INPUT_DATA_INSUFFICIENT; + goto out; + } + + if (cntlr_acs_radio_is_bsta_connected(node->cntlr, rd)) { + /* + * Node is leaf with active wifi backhaul. In such leaf should + * simple follow parent node. Skip channel switch request. + */ + cntlr_dbg(LOG_CHANNEL, "acs radio " MACFMT " - skip switch - bsta connected\n", + MAC2STR(radio->macaddr)); + + params->status = ACS_RECALC_STATUS_BSTA_CONNECTED; + goto out; + } + + cntrl_acs_radio_common_opclass(node->cntlr, rd, &common_opclass); + opclass = &common_opclass; + for (i = 0; i < opclass->num_opclass; i++) { entry = &opclass->opclass[i]; @@ -194,8 +761,9 @@ int cntlr_acs_radio_channel_recalc(struct wifi_radio_element *radio, struct acs_ chan = entry->channel[j].channel; pref = (entry->channel[j].preference & CHANNEL_PREF_MASK) >> 4; reas = entry->channel[j].preference & CHANNEL_PREF_REASON; + cac_time = entry->channel[j].cac_time; - trace("\tacs check/cmp chan %d pref %d reas %d\n", chan, pref, reas); + cntlr_trace(LOG_CHANNEL, "\tacs check/cmp chan %d pref %d reas %d\n", chan, pref, reas); /* Always skip disabled channels */ if (reas == CHANNEL_PREF_REASON_DFS_NOP) @@ -203,6 +771,10 @@ int cntlr_acs_radio_channel_recalc(struct wifi_radio_element *radio, struct acs_ if (reas == CHANNEL_PREF_REASON_REG_DISALLOWED) continue; + /* Current channel preference */ + if (chan == params->best_channel) + pref_cur = pref; + /* Skip DFS channels if requested */ if (params->skip_dfs) { if (reas == CHANNEL_PREF_REASON_DFS_AVAILABLE || @@ -211,16 +783,18 @@ int cntlr_acs_radio_channel_recalc(struct wifi_radio_element *radio, struct acs_ } /* Skip non available DFS channels if requested */ - if (params->skip_dfs_not_available && reas != CHANNEL_PREF_REASON_DFS_AVAILABLE) + if (params->skip_dfs_not_available && reas == CHANNEL_PREF_REASON_DFS_USABLE) + continue; + + /* If background CAC supported and enabled - wait background CAC complete */ + if (radio->bgcac_supported && + reas == CHANNEL_PREF_REASON_DFS_USABLE && + node->cntlr->cfg.dfs_cleanup) continue; if (WARN_ON(acs_params_num >= ARRAY_SIZE(acs_params))) break; - /* Current channel preference */ - if (chan == params->best_channel) - pref_cur = pref; - /* Kick best value */ if (pref > pref_best) pref_best = pref; @@ -229,21 +803,30 @@ int cntlr_acs_radio_channel_recalc(struct wifi_radio_element *radio, struct acs_ acs_params[acs_params_num].best_opclass = entry->id; acs_params[acs_params_num].best_bw = entry->bandwidth; acs_params[acs_params_num].best_pref = pref; + acs_params[acs_params_num].best_cac_time = cac_time; acs_params_num++; } } - if (!pref_best) - return -1; + if (!pref_best) { + cntlr_dbg(LOG_CHANNEL, "acs radio channel recalc " MACFMT " - no pref best - skip recalc\n", + MAC2STR(radio->macaddr)); - cntlr_dbg(LOG_CHANNEL, "acs radio " MACFMT " best pref %d vs current pref %d\n", + params->status = ACS_RECALC_STATUS_INPUT_DATA_INSUFFICIENT; + goto out; + } + + cntlr_trace(LOG_CHANNEL, "acs radio " MACFMT " best pref %d vs current pref %d\n", MAC2STR(radio->macaddr), pref_best, pref_cur); /* If current channel equal to best don't switch */ if (pref_cur == pref_best) { - cntlr_dbg(LOG_CHANNEL, "acs skip - current channel %d is the best\n", params->best_channel); - return -1; + cntlr_warn(LOG_CHANNEL, "acs node " MACFMT " " MACFMT " current channel %d is the best\n", + MAC2STR(node->almacaddr), MAC2STR(radio->macaddr), params->best_channel); + + params->status = ACS_RECALC_STATUS_BEST_ALREADY_SET; + goto out; } /* Get random channel from best performance */ @@ -259,45 +842,39 @@ int cntlr_acs_radio_channel_recalc(struct wifi_radio_element *radio, struct acs_ j++; } - if (WARN_ON(!j)) - return -1; + if (WARN_ON(!j)) { + params->status = ACS_RECALC_STATUS_INPUT_DATA_INSUFFICIENT; + goto out; + } srandom(time(NULL)); r = random() % j; - cntlr_dbg(LOG_CHANNEL, "acs radio " MACFMT " table size %d - rand %d, index %d\n", + cntlr_trace(LOG_CHANNEL, "acs radio " MACFMT " table size %d - rand %d, index %d\n", MAC2STR(radio->macaddr), j, r, prefered[r]); - if (prefered[r] >= acs_params_num) - return -1; + if (prefered[r] >= acs_params_num) { + params->status = ACS_RECALC_STATUS_INPUT_DATA_INSUFFICIENT; + goto out; + } params->best_channel = acs_params[prefered[r]].best_channel; params->best_bw = acs_params[prefered[r]].best_bw; params->best_opclass = acs_params[prefered[r]].best_opclass; + params->best_cac_time = acs_params[prefered[r]].best_cac_time; + params->best_pref = acs_params[prefered[r]].best_pref; + params->status = ACS_RECALC_STATUS_BEST_SELECTED; + cntlr_dbg(LOG_CHANNEL, "acs radio " MACFMT " best chan %d/%d opclass %d\n", MAC2STR(radio->macaddr), params->best_channel, params->best_bw, params->best_opclass); - return 0; -} +out: + memcpy(last_acs, params, sizeof(*last_acs)); + timestamp_update(&last_acs->entry_time); -static bool cntlr_acs_radio_is_bsta_connected(struct netif_radio *radio) -{ - struct netif_iface *iface = NULL; - - list_for_each_entry(iface, &radio->iflist, list) { - /* Check if sta iface connected */ - if (iface->bss->is_bbss || iface->bss->is_fbss) - continue; - - if (hwaddr_is_zero(iface->upstream_bssid)) - continue; - - return true; - } - - return false; + return params->status != ACS_RECALC_STATUS_BEST_SELECTED; } static int cntlr_get_current_acs_params(struct wifi_radio_element *radio, struct acs_params *params) @@ -310,65 +887,201 @@ static int cntlr_get_current_acs_params(struct wifi_radio_element *radio, struct params->opclass = ctrl_radio_cur_opclass_id(radio); params->bw = ctrl_radio_cur_opclass_max_bw(radio); - params->best_channel = ctrl_radio_cur_opclass_ctrl_chan(radio); + params->best_channel = ctrl_radio_cur_opclass_chan(radio); params->best_bw = params->bw; params->best_opclass = params->opclass; return 0; } -void cntlr_acs_node_channel_recalc(struct node *node, bool skip_dfs) +void cntlr_acs_node_channel_recalc(struct node *node, enum wifi_band band, uint8_t opclass, + uint32_t bandwidth, bool skip_dfs, bool prevent_cac, + bool highest_bandwidth) { struct acs_params cur_acs_params = {}; struct acs_params acs_params = {}; + uint32_t bw = bandwidth; struct netif_radio *r = NULL; int ret; acs_params.skip_dfs = skip_dfs; + acs_params.skip_dfs_not_available = prevent_cac; - cntlr_dbg(LOG_CHANNEL, "acs node channel recalc " MACFMT " skip_dfs %d\n", - MAC2STR(node->almacaddr), acs_params.skip_dfs); + cntlr_dbg(LOG_CHANNEL, "acs node channel recalc " MACFMT " opclass %u bandwidth %u skip_dfs %d prevent_cac %d higest_bandwidth %d\n", + MAC2STR(node->almacaddr), opclass, bandwidth, acs_params.skip_dfs, acs_params.skip_dfs_not_available, highest_bandwidth); list_for_each_entry(r, &node->radiolist, list) { - WARN_ON(cntlr_get_current_acs_params(r->radio_el, &cur_acs_params)); + struct wifi_radio_opclass req_opclass = {}; + struct acs_params *last_acs; + uint8_t ctrl_channel; + + if (band && band != BAND_ANY && band != r->radio_el->band) + continue; + + last_acs = cntlr_radio_get_last_acs(r); + if (!last_acs) + continue; + + memset(&acs_params, 0, sizeof(acs_params)); - /* Use current opclass - TODO: if no opclass check 80/40/20 */ - acs_params.opclass = cur_acs_params.opclass; - acs_params.best_channel = cur_acs_params.best_channel; + /* Try best bandwidth */ + if (highest_bandwidth && !bandwidth && !opclass) { + bw = wifi_opclass_highest_bandwidth(&r->radio_el->pref_opclass, prevent_cac); + cntlr_dbg(LOG_CHANNEL, "acs node channel recalc " MACFMT " radio " MACFMT " highest bandwidth %d\n", + MAC2STR(node->almacaddr), MAC2STR(r->radio_el->macaddr), bw); + } + + if (opclass) { + cntlr_get_current_acs_params(r->radio_el, &cur_acs_params); + acs_params.opclass = opclass; + + if (cur_acs_params.opclass == opclass) { + acs_params.best_channel = cur_acs_params.best_channel; + acs_params.best_bw = cur_acs_params.best_bw; + } + } else if (bw) { + cntlr_get_current_acs_params(r->radio_el, &cur_acs_params); + acs_params.bw = bw; - ret = cntlr_acs_radio_channel_recalc(r->radio_el, &acs_params); + if (cur_acs_params.bw == bw) { + acs_params.best_channel = cur_acs_params.best_channel; + acs_params.best_bw = cur_acs_params.best_bw; + } + } else { + if (cntlr_get_current_acs_params(r->radio_el, &cur_acs_params)) { + cntlr_dbg(LOG_CHANNEL, "acs node " MACFMT " " MACFMT " - current channel not known\n", + MAC2STR(node->almacaddr), MAC2STR(r->radio_el->macaddr)); + continue; + } + + acs_params.opclass = cur_acs_params.opclass; + acs_params.best_channel = cur_acs_params.best_channel; + acs_params.best_bw = cur_acs_params.best_bw; + } + + ret = cntlr_acs_radio_channel_recalc(node, r, &acs_params); if (WARN_ON(ret)) continue; - cntlr_dbg(LOG_CHANNEL, "acs node " MACFMT " radio " MACFMT " new %d/%d opclass %d vs old %d/%d opclass %d\n", + cntlr_trace(LOG_CHANNEL, "acs node " MACFMT " radio " MACFMT " new %d/%d opclass %d vs old %d/%d opclass %d cac_time %u\n", MAC2STR(node->almacaddr), MAC2STR(r->radio_el->macaddr), acs_params.best_channel, acs_params.best_bw, acs_params.best_opclass, cur_acs_params.best_channel, - cur_acs_params.bw, cur_acs_params.opclass); + cur_acs_params.bw, cur_acs_params.opclass, acs_params.best_cac_time); + + /* Pick up control channel */ + if (acs_params.best_bw > 40) { + ctrl_channel = cntrl_acs_radio_ctrl_channel(r->radio_el, + acs_params.best_opclass, + acs_params.best_channel); + } else { + ctrl_channel = acs_params.best_channel; + } - if (cntlr_acs_radio_is_bsta_connected(r)) + /* Build opclass we would like to send */ + memcpy(&req_opclass, &r->radio_el->supp_opclass, sizeof(req_opclass)); + wifi_opclass_set_preferences(&req_opclass, 0x0); + + if (wifi_radio_opclass_update_channel(&req_opclass, + wifi_band_to_freqband(r->radio_el->band), + ctrl_channel, acs_params.best_bw, 15)) { + + cntlr_dbg(LOG_CHANNEL, "acs node " MACFMT " " MACFMT " - update opclass failed\n", + MAC2STR(node->almacaddr), MAC2STR(r->radio_el->macaddr)); continue; + } + + wifi_opclass_dump_ex(&req_opclass, "acs_selection", r->radio_el->macaddr, false); - warn("acs switch to best channel %d/%d\n", acs_params.best_channel, acs_params.best_bw); ret = cntlr_send_channel_selection(node->cntlr, node->almacaddr, - r->radio_el->macaddr, - acs_params.best_channel, - acs_params.best_opclass, - 14); + r->radio_el->macaddr, &req_opclass); + if (ret == 0xffff) { + cntlr_warn(LOG_CHANNEL, "acs node " MACFMT " " MACFMT " send channel switch request failed\n", + MAC2STR(node->almacaddr), MAC2STR(r->radio_el->macaddr)); + continue; + } - if (ret) - warn("acs switch failed\n"); + cntlr_warn(LOG_CHANNEL, "acs node " MACFMT " " MACFMT " switch to best channel %d/%d ctrl %d mid %u\n", + MAC2STR(node->almacaddr), MAC2STR(r->radio_el->macaddr), acs_params.best_channel, + acs_params.best_bw, ctrl_channel, ret); + + last_acs->mid = ret; + last_acs->status = ACS_RECALC_STATUS_BEST_REQUESTED; + timestamp_update(&last_acs->entry_time); } } -void cntlr_acs_recalc(struct controller *c, bool skip_dfs) +static void cntlr_radio_acs_timer(atimer_t *t) +{ + struct acs *acs = container_of(t, struct acs, acs_timer); + struct netif_radio *r = acs->radio; + struct controller *c = acs->cntlr; + struct node *n = acs->node; + struct acs_params *last_acs; + + last_acs = cntlr_radio_get_last_acs(r); + if (!last_acs) + return; + + if (cntlr_acs_radio_is_bsta_connected(c, r)) { + cntlr_dbg(LOG_CHANNEL, "acs timer recalc (bsta connected) " MACFMT " " MACFMT "\n", + MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr)); + last_acs->status = ACS_RECALC_STATUS_BSTA_CONNECTED; + timestamp_update(&last_acs->entry_time); + return; + } + + if (last_acs->recalc) { + cntlr_dbg(LOG_CHANNEL, "acs timer recalc (recalc already set) " MACFMT " " MACFMT "\n", + MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr)); + return; + } + + if (c->cfg.acs_scan_before_recalc == false) { + cntlr_dbg(LOG_CHANNEL, "acs timer recalc node (don't scan) " MACFMT " " MACFMT "\n", + MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr)); + + last_acs->scan_mid = 0xffff; + last_acs->recalc = true; + + /* Send node preference request */ + cntlr_acs_send_pref_request(n); + } else { + cntlr_dbg(LOG_CHANNEL, "acs timer recalc node (scan) " MACFMT " " MACFMT "\n", + MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr)); + + /* Send scan request */ + last_acs->recalc = true; + last_acs->scan_mid = cntlr_acs_send_scan_request(n, r); + if (last_acs->scan_mid != 0xffff) { + last_acs->status = ACS_RECALC_STATUS_SCAN_REQUESTED; + timestamp_update(&last_acs->entry_time); + } + } +} + +void cntlr_acs_recalc(struct controller *c) { struct node *n = NULL; + uint32_t delay = 10; + /* Handle main timer - prevent scan/switch at same time */ list_for_each_entry(n, &c->nodelist, list) { - cntlr_acs_node_channel_recalc(n, skip_dfs); + struct netif_radio *r = NULL; + + /* Run fresh scan before preference recalc */ + list_for_each_entry(r, &n->radiolist, list) { + cntlr_dbg(LOG_CHANNEL, "acs setup recalc timer %u seconds " MACFMT " " MACFMT "\n", + delay, MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr)); + + /* In the future check more params - possible recalc postpond */ + cntlr_radio_acs_timer_set(r, delay * 1000); + delay += 60; + } } } + +/* DFS background CAC cleanup */ static uint8_t cntlr_dfs_cac_method(uint32_t methods) { uint8_t cac_method = 0; @@ -384,7 +1097,27 @@ static uint8_t cntlr_dfs_cac_method(uint32_t methods) return cac_method; } -static bool cntlr_dfs_get_usable(struct wifi_radio_opclass_entry *entry, struct cac_data *cac_data) +static bool cntlr_dfs_cac_ongoing(struct wifi_radio_element *radio) +{ + struct wifi_radio_opclass_entry *entry; + struct wifi_radio_opclass *opclass; + int i, j; + + opclass = &radio->pref_opclass; + + for (i = 0; i < opclass->num_opclass; i++) { + entry = &opclass->opclass[i]; + + for (j = 0; j < entry->num_channel; j++) { + if (entry->channel[j].dfs == WIFI_RADIO_OPCLASS_CHANNEL_DFS_CAC) + return true; + } + } + + return false; +} + +static bool cntlr_dfs_get_usable(struct wifi_radio_opclass_entry *entry, struct acs_cac_data *cac) { uint8_t reas; int i; @@ -395,12 +1128,14 @@ static bool cntlr_dfs_get_usable(struct wifi_radio_opclass_entry *entry, struct if (reas != CHANNEL_PREF_REASON_DFS_USABLE) continue; - cac_data->cac_method = cntlr_dfs_cac_method(entry->channel[i].cac_methods); - if (!cac_data->cac_method) + /* Check if background CAC supported */ + cac->data.cac_method = cntlr_dfs_cac_method(entry->channel[i].cac_methods); + if (!cac->data.cac_method) continue; - cac_data->channel = entry->channel[i].channel; - cac_data->opclass = entry->id; + cac->data.channel = entry->channel[i].channel; + cac->data.opclass = entry->id; + cac->cac_time = entry->channel[i].cac_time; return true; } @@ -422,35 +1157,40 @@ static bool cntlr_radio_is_ap_iface(struct netif_radio *radio) return false; } -static bool cntlr_dfs_get_cac_data(struct wifi_radio_element *radio, struct cac_data *cac_data) +static bool cntlr_dfs_get_cac_data(struct wifi_radio_element *radio, struct acs_cac_data *cac, uint16_t bw) { struct wifi_radio_opclass_entry *entry; struct wifi_radio_opclass *opclass; + uint32_t cur_bw = 0; + uint32_t cur_chan = 0; int i; - /* TODO check dfs_region early - skip non EU */ + if (!bw) { + cur_bw = ctrl_radio_cur_opclass_max_bw(radio); + cur_chan = ctrl_radio_cur_opclass_chan(radio); + } else { + cur_bw = bw; + cur_chan = 0; + } + + if (!cur_bw) + return false; opclass = &radio->pref_opclass; for (i = 0; i < opclass->num_opclass; i++) { entry = &opclass->opclass[i]; - uint32_t cur_bw = 0; - uint32_t cur_ctrl_chan = 0; - - cur_bw = ctrl_radio_cur_opclass_max_bw(radio); - cur_ctrl_chan = ctrl_radio_cur_opclass_ctrl_chan(radio); if (entry->bandwidth != cur_bw) continue; - if (!cntlr_dfs_get_usable(entry, cac_data)) + if (!cntlr_dfs_get_usable(entry, cac)) continue; - /* TODO check chan/bw - not only control channel */ - if (cac_data->channel == cur_ctrl_chan) + if (cac->data.channel == cur_chan) continue; - memcpy(cac_data->radio, radio->macaddr, sizeof(cac_data->radio)); + memcpy(cac->data.radio, radio->macaddr, sizeof(cac->data.radio)); return true; } @@ -460,30 +1200,82 @@ static bool cntlr_dfs_get_cac_data(struct wifi_radio_element *radio, struct cac_ void cntlr_dfs_radio_cleanup(struct node *node, struct netif_radio *radio) { - struct cac_data cac_data = {}; + struct acs_cac_data cac_data = {}; + struct acs_cac_data *last_cac_data; + + last_cac_data = cntlr_radio_get_last_cac(radio); + if (!last_cac_data) + return; + + if (!radio->radio_el->pref_opclass.num_opclass || + !radio->radio_el->cur_opclass.num_opclass) { + /* We don't know preferences and channels DFS state */ + cntlr_dbg(LOG_CHANNEL, "dfs radio preCAC not yet " MACFMT "\n", MAC2STR(radio->radio_el->macaddr)); + timestamp_update(&last_cac_data->entry_time); + last_cac_data->status = ACS_CLEANUP_STATUS_SKIPPED; + return; + } + + if (!radio->radio_el->bgcac_supported) { + /* Some nodes don't even support it */ + cntlr_dbg(LOG_CHANNEL, "dfs radio preCAC not supported " MACFMT "\n", MAC2STR(radio->radio_el->macaddr)); + timestamp_update(&last_cac_data->entry_time); + last_cac_data->status = ACS_CLEANUP_STATUS_NOT_SUPPORTED; + return; + } cntlr_dbg(LOG_CHANNEL, "dfs radio preCAC cleanup " MACFMT "\n", MAC2STR(radio->radio_el->macaddr)); if (!cntlr_radio_is_ap_iface(radio)) { + /* Wait controller get proper AP list */ cntlr_dbg(LOG_CHANNEL, "dfs radio preCAC no AP ifaces, skip radio\n"); + timestamp_update(&last_cac_data->entry_time); + last_cac_data->status = ACS_CLEANUP_STATUS_NO_APS; return; } - if (!cntlr_dfs_get_cac_data(radio->radio_el, &cac_data)) { - cntlr_dbg(LOG_CHANNEL, "dfs radio preCAC cleanup no channels left\n"); + /* So far keep it simple - don't start new CAC request if one ongoing */ + if (cntlr_dfs_cac_ongoing(radio->radio_el)) { + /* Could be device run own background CAC */ + cntlr_dbg(LOG_CHANNEL, "dfs radio CAC ongoing " MACFMT "\n", MAC2STR(radio->radio_el->macaddr)); + timestamp_update(&last_cac_data->entry_time); + last_cac_data->status = ACS_CLEANUP_STATUS_ALREADY_ONGOING; return; } - cntlr_dbg(LOG_CHANNEL, "dfs radio preCAC run chan %d opclass %d\n", cac_data.channel, cac_data.opclass); - WARN_ON(cntlr_send_cac_req(node->cntlr, node->almacaddr, 1, &cac_data)); + if (!cntlr_dfs_get_cac_data(radio->radio_el, &cac_data, 0)) { + /* First use current bandwidth for clearing. Finally switch to 20MHz for left channels */ + cntlr_dbg(LOG_CHANNEL, "dfs radio preCAC cleanup no channels left check 20MHz\n"); + if (!cntlr_dfs_get_cac_data(radio->radio_el, &cac_data, 20)) { + cntlr_dbg(LOG_CHANNEL, "dfs radio preCAC cleanup no channels left\n"); + timestamp_update(&last_cac_data->entry_time); + last_cac_data->status = ACS_CLEANUP_STATUS_ALL_CLEAN; + return; + } + } + + cntlr_warn(LOG_CHANNEL, "dfs node " MACFMT " radio " MACFMT " preCAC run chan %d opclass %d\n", + MAC2STR(node->almacaddr), MAC2STR(radio->radio_el->macaddr), + cac_data.data.channel, cac_data.data.opclass); + memcpy(last_cac_data, &cac_data, sizeof(*last_cac_data)); + timestamp_update(&last_cac_data->entry_time); + last_cac_data->status = ACS_CLEANUP_STATUS_SELECTED; + + cac_data.mid = cntlr_send_cac_req(node->cntlr, node->almacaddr, 1, &cac_data.data); + + last_cac_data->mid = cac_data.mid; + if (cac_data.mid != 0xffff) + last_cac_data->status = ACS_CLEANUP_STATUS_REQUESTED; } void cntlr_dfs_node_cleanup(struct node *node) { struct netif_radio *radio = NULL; - cntlr_dbg(LOG_CHANNEL, "dfs node preCAC cleanup " MACFMT "\n", MAC2STR(node->almacaddr)); + cntlr_trace(LOG_CHANNEL, "dfs node preCAC cleanup " MACFMT "\n", MAC2STR(node->almacaddr)); list_for_each_entry(radio, &node->radiolist, list) { + if (radio->radio_el->band != BAND_5) + continue; cntlr_dfs_radio_cleanup(node, radio); } } @@ -496,3 +1288,360 @@ void cntlr_dfs_cleanup(struct controller *c) cntlr_dfs_node_cleanup(n); } } + +/* CMDU callbacks we expect to receive from nodes */ +void cntlr_acs_channel_sel_response(struct netif_radio *r, uint16_t mid, uint8_t status) +{ + struct acs_params *acs; + + cntlr_dbg(LOG_CHANNEL, "new channel selection response radio " MACFMT " mid %u status %u\n", + MAC2STR(r->radio_el->macaddr), mid, status); + + acs = cntlr_radio_get_last_acs(r); + if (!acs) + return; + + if (acs->status != ACS_RECALC_STATUS_BEST_REQUESTED) + return; + + if (acs->mid != mid) + return; + + if (status == 0) + acs->status = ACS_RECALC_STATUS_BEST_ACCEPTED; + else + acs->status = ACS_RECALC_STATUS_BEST_REJECTED; + + timestamp_update(&acs->entry_time); +} + +void cntlr_acs_oper_channel_report(struct netif_radio *r) +{ + struct acs_params *acs; + struct wifi_radio_opclass_entry *entry; + struct wifi_radio_opclass *cur; + int i, j; + + cntlr_dbg(LOG_CHANNEL, "new oper channel report radio " MACFMT " %u/%u\n", + MAC2STR(r->radio_el->macaddr), + ctrl_radio_cur_opclass_ctrl_chan(r->radio_el), + ctrl_radio_cur_opclass_max_bw(r->radio_el)); + + acs = cntlr_radio_get_last_acs(r); + if (!acs) + return; + + if (acs->status != ACS_RECALC_STATUS_BEST_REQUESTED && + acs->status != ACS_RECALC_STATUS_BEST_ACCEPTED) + return; + + cur = &r->radio_el->cur_opclass; + for (i = 0; i < cur->num_opclass; i++) { + entry = &cur->opclass[i]; + + if (entry->bandwidth != acs->best_bw) + continue; + + for (j = 0; j < entry->num_channel; j++) { + if (entry->channel[j].channel == acs->best_channel) { + acs->status = ACS_RECALC_STATUS_BEST_SET; + timestamp_update(&acs->entry_time); + return; + } + } + } + + /* timeout check - worst case when continous CAC required */ + if (timestamp_elapsed_sec(&acs->entry_time) > 30 + acs->best_cac_time) { + acs->status = ACS_RECALC_STATUS_BEST_SET_TIMEOUT; + timestamp_update(&acs->entry_time); + } +} + +void cntlr_acs_cac_completion(struct netif_radio *r, uint8_t classid, + uint8_t channel, uint8_t status) +{ + struct acs_params *acs; + struct acs_cac_data *cac; + + cntlr_dbg(LOG_CHANNEL, "new cac completion radio " MACFMT " opclass %u channel %u status %u\n", + MAC2STR(r->radio_el->macaddr), classid, channel, status); + + cac = cntlr_radio_get_last_cac(r); + if (!cac) + return; + + acs = cntlr_radio_get_last_acs(r); + + if (cac->status != ACS_CLEANUP_STATUS_REQUESTED) + return; + + if (cac->data.channel != channel) + return; + if (cac->data.opclass != classid) + return; + + + switch (status) { + case CAC_COMP_REPORT_STATUS_SUCCESSFUL: + cac->status = ACS_CLEANUP_STATUS_DONE; + + /* Kick ACS recalc - we have new available channel */ + if (acs && acs->scan_mid == 0xffff) + cntlr_radio_acs_timer_set(r, 5 * 1000); + break; + case CAC_COMP_REPORT_STATUS_CAC_NOT_SUPPORTED: + case CAC_COMP_REPORT_STATUS_TOO_BUSY: + case CAC_COMP_REPORT_STATUS_NON_CONFORMANT: + case CAC_COMP_REPORT_STATUS_OTHER: + cac->status = ACS_CLEANUP_STATUS_REJECTED; + break; + case CAC_COMP_REPORT_STATUS_RADAR_DETECTED: + cac->status = ACS_CLEANUP_STATUS_RADAR; + break; + default: + break; + } +} + +void cntlr_acs_channel_pref_report(struct node *n, struct netif_radio *r) +{ + struct acs_params *acs; + struct acs_cac_data *cac; + + cntlr_dbg(LOG_CHANNEL, "new pref report node " MACFMT " radio " MACFMT "\n", + MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr)); + + cac = cntlr_radio_get_last_cac(r); + if (!cac) + return; + + acs = cntlr_radio_get_last_acs(r); + if (!acs) + return; + + /* ACS status update */ + if (acs->status == ACS_RECALC_STATUS_BEST_REQUESTED || + acs->status == ACS_RECALC_STATUS_BEST_ACCEPTED) { + struct wifi_radio_opclass_channel *chan; + + chan = wifi_opclass_get_channel(&r->radio_el->pref_opclass, + acs->best_opclass, + acs->best_channel); + if (chan) { + switch (chan->dfs) { + case WIFI_RADIO_OPCLASS_CHANNEL_DFS_CAC: + acs->status = ACS_RECALC_STATUS_BEST_CAC; + timestamp_update(&acs->entry_time); + break; + case WIFI_RADIO_OPCLASS_CHANNEL_DFS_NOP: + acs->status = ACS_RECALC_STATUS_BEST_CAC_RADAR; + timestamp_update(&acs->entry_time); + break; + default: + break; + } + } + } + + /* Background CAC status update */ + if (cac->status == ACS_CLEANUP_STATUS_REQUESTED || + cac->status == ACS_CLEANUP_STATUS_ACCEPTED || + cac->status == ACS_CLEANUP_STATUS_TIMEOUT) { + struct wifi_radio_opclass_channel *chan; + + chan = wifi_opclass_get_channel(&r->radio_el->pref_opclass, + cac->data.opclass, + cac->data.channel); + if (chan) { + switch (chan->dfs) { + case WIFI_RADIO_OPCLASS_CHANNEL_DFS_AVAILABLE: + cac->status = ACS_CLEANUP_STATUS_DONE; + break; + case WIFI_RADIO_OPCLASS_CHANNEL_DFS_CAC: + cac->status = ACS_CLEANUP_STATUS_ACCEPTED; + break; + case WIFI_RADIO_OPCLASS_CHANNEL_DFS_NOP: + cac->status = ACS_CLEANUP_STATUS_RADAR; + break; + case WIFI_RADIO_OPCLASS_CHANNEL_DFS_USABLE: + /* change CAC -> USABLE - abort possible */ + if (cac->status == ACS_CLEANUP_STATUS_ACCEPTED) + cac->status = ACS_CLEANUP_STATUS_REJECTED; + if (timestamp_elapsed_sec(&cac->entry_time) > 30 + cac->cac_time) + cac->status = ACS_CLEANUP_STATUS_TIMEOUT; + break; + default: + break; + } + } + } + + /* Kick CAC background clearing */ + if (n->cntlr->cfg.dfs_cleanup && + r->radio_el->band == BAND_5 && + r->radio_el->bgcac_supported && + cac->status != ACS_CLEANUP_STATUS_REQUESTED && + cac->status != ACS_CLEANUP_STATUS_ACCEPTED) { + cntlr_dbg(LOG_CHANNEL, "kick dfs cleanup " MACFMT " radio " MACFMT "\n", + MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr)); + + cntlr_dfs_radio_cleanup(n, r); + } + + /* Kick ACS recalc code */ + if (acs->recalc && n->cntlr->cfg.acs && acs->scan_mid == 0xffff) { + cntlr_dbg(LOG_CHANNEL, "kick acs reclac " MACFMT " radio " MACFMT "\n", + MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr)); + + acs->recalc = false; + cntlr_acs_node_channel_recalc(n, r->radio_el->band, 0, 0, + n->cntlr->cfg.acs_skip_dfs, + n->cntlr->cfg.acs_prevent_cac, + n->cntlr->cfg.acs_highest_bandwidth); + } +} + +static uint8_t ewma(uint8_t cur, uint8_t prev, uint8_t alpha) +{ + uint8_t new; + + if (alpha > 100) + alpha = 100; + + new = (alpha * cur)/100 + ((100 - alpha) * prev) / 100; + + return new; +} + +static bool cntlr_acs_radio_add_and_check_metrics(struct netif_radio *r, uint8_t anpi, uint8_t obss) +{ + struct acs_radio_metrics *m; + struct acs_radio_metrics_entry *last, *next; + + m = cntlr_radio_get_metrics(r); + if (!m) + return false; + + timestamp_update(&m->time); + m->anpi = ewma(anpi, m->anpi, 60); + m->obss = ewma(obss, m->obss, 60); + + last = &m->entry[m->idx]; + + cntlr_trace(LOG_CHANNEL, "radio ewma metrics " MACFMT " anpi %u obss %u\n", + MAC2STR(r->radio_el->macaddr), m->anpi, m->obss); + + /* Update ring buffer */ + if (timestamp_invalid(&last->time)) { + timestamp_update(&last->time); + last->anpi = m->anpi; + last->obss = m->obss; + } + + /* Save/check data to ring buffer each 300 seconds */ + if (timestamp_elapsed_sec(&last->time) >= 300) { + m->idx++; + m->idx %= ARRAY_SIZE(m->entry); + + next = &m->entry[m->idx]; + + timestamp_update(&next->time); + next->anpi = m->anpi; + next->obss = m->obss; + + cntlr_dbg(LOG_CHANNEL, "metrics[%u] radio " MACFMT " anpi %u (%d) obss %u (%d)\n", + m->idx, MAC2STR(r->radio_el->macaddr), next->anpi, + next->anpi - last->anpi, next->obss, next->obss - last->obss); + + /* If other BSS busy increase - worth to check it (100% == 255) */ + if (next->obss - last->obss > 30) + return true; + + /* If noise level worst - worth to check it (0.5 dBm) */ + if (next->anpi - last->anpi > 20) + return true; + } + + return false; +} + +void cntlr_acs_radio_metrics(struct node *n, struct netif_radio *r, + uint8_t anpi, uint8_t obss) +{ + struct acs_params *acs; + struct acs_cac_data *cac; + + cntlr_trace(LOG_CHANNEL, "new radio metrics node " MACFMT " radio " MACFMT " anpi %u busy %u\n", + MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr), anpi, obss); + + cac = cntlr_radio_get_last_cac(r); + if (!cac) + return; + + acs = cntlr_radio_get_last_acs(r); + if (!acs) + return; + + /* Kick CAC background clearing - check AP apears */ + if (n->cntlr->cfg.dfs_cleanup && + r->radio_el->band == BAND_5 && + r->radio_el->bgcac_supported && + cac->status == ACS_CLEANUP_STATUS_NO_APS && + timestamp_elapsed_sec(&cac->entry_time) >= 30) { + cntlr_dbg(LOG_CHANNEL, "kick dfs cleanup (NO_APS before) " MACFMT " radio " MACFMT "\n", + MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr)); + cntlr_dfs_radio_cleanup(n, r); + } + + if (!n->cntlr->cfg.acs) + return; + + /* Store data for future use/compare */ + if (!cntlr_acs_radio_add_and_check_metrics(r, anpi, obss)) + return; + + /* Smth changed in current channel noise/busy */ + cntlr_dbg(LOG_CHANNEL, "issue ACS recalc due to metrics (request fresh pref) " MACFMT " radio " MACFMT "\n", + MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr)); + + /* Trigger scan/pref_req and get fresh preference report */ + cntlr_radio_acs_timer_set(r, 5 * 1000); +} + +void cntlr_acs_scan_report(struct node *n, struct netif_radio *r, uint16_t mid) +{ + struct acs_params *acs; + + cntlr_dbg(LOG_CHANNEL, "new scan report node " MACFMT " radio " MACFMT " mid %u\n", + MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr), mid); + + acs = cntlr_radio_get_last_acs(r); + if (!acs) + return; + + if (!n->cntlr->cfg.acs) + return; + + /* Scan done for this radio, safe to switch channel */ + if (acs->recalc && acs->scan_mid != 0xffff) { + acs->status = ACS_RECALC_STATUS_SCAN_DONE; + timestamp_update(&acs->entry_time); + } + + acs->scan_mid = 0xffff; + + /* Request fresh preferences if required */ + if (cntlr_node_pref_opclass_expired(n, 10) || acs->recalc) + /* Preference responose with new data will trigger actions */ + cntlr_acs_send_pref_request(n); +} + +void cntlr_acs_dev_supp_opclass(struct node *n, struct netif_radio *r) +{ + cntlr_trace(LOG_CHANNEL, "new dev supp opclass " MACFMT " radio " MACFMT "\n", + MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr)); + + if (r->radio_el->pref_opclass.num_opclass == 0) + cntlr_acs_send_pref_request(n); +} diff --git a/src/acs.h b/src/acs.h index 8dc7a024..9cac6fea 100644 --- a/src/acs.h +++ b/src/acs.h @@ -13,29 +13,48 @@ struct controller; struct node; struct wifi_radio_element; +struct netif_radio; +enum wifi_band; +enum wifi_radio_opclass_dfs; +struct blob_buf; +/* Alloc free private storage */ +void *cntlr_radio_acs_alloc(struct controller *c, struct node *n, struct netif_radio *r); +void cntlr_radio_acs_free(void *acs); -struct acs_params { - int opclass; - int bw; +/* Format UBUS output */ +void cntlr_acs_node_info(struct node *node, enum wifi_band band, struct blob_buf *bb); +void cntlr_acs_radio_info(struct blob_buf *bb, struct netif_radio *r); +void cntlr_dfs_radio_cleanup_info(struct node *n, struct netif_radio *r, struct blob_buf *bb); +void cntlr_acs_radio_cleanup_info(struct blob_buf *bb, struct netif_radio *r); - bool skip_dfs; - bool skip_dfs_not_available; +/* CMDU info/event for ACS */ +void cntlr_acs_channel_sel_response(struct netif_radio *r, uint16_t mid, uint8_t status); +void cntlr_acs_oper_channel_report(struct netif_radio *r); +void cntlr_acs_cac_completion(struct netif_radio *r, uint8_t classid, + uint8_t channel, uint8_t status); +void cntlr_acs_channel_pref_report(struct node *n, struct netif_radio *r); +void cntlr_acs_radio_metrics(struct node *n, struct netif_radio *r, + uint8_t anpi, uint8_t obss); +void cntlr_acs_scan_report(struct node *n, struct netif_radio *r, uint16_t mid); +void cntlr_acs_dev_supp_opclass(struct node *n, struct netif_radio *r); - int best_channel; - int best_opclass; - int best_bw; - int best_pref; -}; +/* Generic requests */ +void cntlr_acs_recalc(struct controller *c); +void cntlr_acs_node_channel_recalc(struct node *node, enum wifi_band band, + uint8_t opclass, uint32_t bandwidth, + bool skip_dfs, bool prevent_cac, + bool highest_bandwidth); -int cntlr_acs_radio_channel_recalc(struct wifi_radio_element *radio, struct acs_params *params); -void cntlr_acs_node_channel_recalc(struct node *node, bool skip_dfs); -void cntlr_dfs_node_cleanup(struct node *node); -void cntlr_acs_recalc(struct controller *c, bool skip_dfs); void cntlr_dfs_cleanup(struct controller *c); +void cntlr_dfs_node_cleanup(struct node *node); +void cntlr_dfs_radio_cleanup(struct node *node, struct netif_radio *radio); +/* Opclass helpers */ int cntlr_radio_pref_opclass_add(struct wifi_radio_element *radio, uint8_t classid, uint8_t channel, uint8_t preference); +int cntlr_radio_pref_opclass_set_dfs_status(struct wifi_radio_element *radio, uint8_t classid, + uint8_t channel, enum wifi_radio_opclass_dfs state); int cntlr_radio_pref_opclass_reset(struct wifi_radio_element *radio); void cntlr_radio_pref_opclass_dump(struct wifi_radio_element *radio); @@ -43,10 +62,9 @@ int cntlr_radio_cur_opclass_add(struct wifi_radio_element *radio, uint8_t opclas uint8_t channel, uint8_t txpower); void cntlr_radio_cur_opclass_reset(struct wifi_radio_element *radio); void cntlr_radio_cur_opclass_dump(struct wifi_radio_element *radio); -bool cntlr_node_pref_opclass_expired(struct node *node); +bool cntlr_node_pref_opclass_expired(struct node *node, int age); void cntlr_radio_pref_opclass_set_pref(struct wifi_radio_element *radio, uint8_t id, uint8_t preference); uint8_t ctrl_radio_cur_opclass_id(struct wifi_radio_element *radio); uint8_t ctrl_radio_cur_opclass_ctrl_chan(struct wifi_radio_element *radio); -uint8_t ctrl_radio_cur_opclass_max_bw(struct wifi_radio_element *radio); - +uint16_t ctrl_radio_cur_opclass_max_bw(struct wifi_radio_element *radio); #endif diff --git a/src/cntlr.c b/src/cntlr.c index c0f6f7cc..f40e635b 100644 --- a/src/cntlr.c +++ b/src/cntlr.c @@ -639,15 +639,15 @@ struct netif_iface *cntlr_radio_add_iface(struct controller *c, #if (EASYMESH_VERSION >= 6) /* send channel selection request to propagate puncture bitmap */ - cntlr_send_channel_selection(c, r->agent->almacaddr, r->radio_el->macaddr, - 0, 0, 0); + cntlr_send_channel_selection(c, r->agent->almacaddr, r->radio_el->macaddr, NULL); #endif mactable_add_entry(c->mac_table, macaddr, MAC_ENTRY_FBSS, (void *)n); return n; } -static struct wifi_radio_element *cntlr_alloc_wifi_radio(struct controller *c) +static struct wifi_radio_element *cntlr_alloc_wifi_radio(struct controller *c, struct node *n, + struct netif_radio *r) { struct wifi_radio_element *radio_el = NULL; @@ -656,6 +656,7 @@ static struct wifi_radio_element *cntlr_alloc_wifi_radio(struct controller *c) return NULL; INIT_LIST_HEAD(&radio_el->scanlist); + radio_el->acs = cntlr_radio_acs_alloc(c, n, r); return radio_el; } @@ -674,7 +675,7 @@ struct netif_radio *cntlr_node_add_radio(struct controller *c, struct node *n, if (!r) return NULL; - r->radio_el = cntlr_alloc_wifi_radio(c); + r->radio_el = cntlr_alloc_wifi_radio(c, n, r); if (!r->radio_el) { free(r); return NULL; @@ -688,7 +689,7 @@ struct netif_radio *cntlr_node_add_radio(struct controller *c, struct node *n, mactable_add_entry(c->mac_table, macaddr, MAC_ENTRY_RADIO, (void *)r); - cntlr_send_channel_selection(c, n->almacaddr, macaddr, 0, 0, 0); + cntlr_send_channel_selection(c, n->almacaddr, macaddr, NULL); p = cntlr_get_radio_policy(&c->cfg, macaddr); if (p) @@ -851,6 +852,9 @@ static void radio_clean_radio_el(struct netif_radio *r) list_for_each_entry_safe(b, tmp, &r->radio_el->scanlist, list) { cntlr_radio_clean_scanlist_el(b); } + + cntlr_radio_acs_free(r->radio_el->acs); + r->radio_el->acs = NULL; free(r->radio_el); } @@ -1449,28 +1453,15 @@ static void cntlr_metric_collection(struct controller *c) static void cntlr_acs_run(atimer_t *t) { struct controller *c = container_of(t, struct controller, acs); - bool skip_dfs = false; /* Run ACS recalc here */ dbg("acs timeout - run recalc\n"); - cntlr_acs_recalc(c, skip_dfs); + cntlr_acs_recalc(c); - if (c->cfg.acs_timeout) + if (c->cfg.acs && c->cfg.acs_timeout) timer_set(&c->acs, c->cfg.acs_timeout * 1000); } -static void cntlr_dfs_cleanup_run(atimer_t *t) -{ - struct controller *c = container_of(t, struct controller, dfs_cleanup); - - /* Run background CAC here */ - dbg("dfs bgcac timeout - run cleanup\n"); - cntlr_dfs_cleanup(c); - - if (c->cfg.dfs_cleanup_timeout) - timer_set(&c->dfs_cleanup, c->cfg.dfs_cleanup_timeout * 1000); -} - static void cntlr_steer_sched_run(atimer_t *t) { struct controller *c = container_of(t, struct controller, steer_sched_timer); @@ -1878,7 +1869,6 @@ void run_controller(void *opts) timer_init(&c->signal_handler, cntlr_signal_periodic_run); timer_init(&c->query_nodes, cntlr_query_nodes); timer_init(&c->acs, cntlr_acs_run); - timer_init(&c->dfs_cleanup, cntlr_dfs_cleanup_run); timer_init(&c->steer_sched_timer, cntlr_steer_sched_run); timer_set(&c->heartbeat, 1 * 1000); @@ -1887,10 +1877,8 @@ void run_controller(void *opts) timer_set(&c->signal_handler, 5 * 1000); timer_set(&c->query_nodes, 60 * 1000); - if (c->cfg.acs_timeout) + if (c->cfg.acs && c->cfg.acs_timeout) timer_set(&c->acs, c->cfg.acs_timeout * 1000); - if (c->cfg.dfs_cleanup_timeout) - timer_set(&c->dfs_cleanup, c->cfg.dfs_cleanup_timeout * 1000); c->evh.cb = cntlr_event_handler; ubus_register_event_handler(ctx, &c->evh, "ubus.object.*"); diff --git a/src/cntlr.h b/src/cntlr.h index bdbf6729..a7ea588a 100644 --- a/src/cntlr.h +++ b/src/cntlr.h @@ -291,7 +291,6 @@ struct controller { struct ubus_event_handler evh; atimer_t heartbeat; atimer_t acs; - atimer_t dfs_cleanup; int num_nodes; int num_tx_links; int num_rx_links; diff --git a/src/cntlr_cmdu.c b/src/cntlr_cmdu.c index 0f8a61ca..34f90be3 100644 --- a/src/cntlr_cmdu.c +++ b/src/cntlr_cmdu.c @@ -901,46 +901,36 @@ int cntlr_send_channel_preference_query(struct controller *c, uint8_t *agent) return 0; } -int cntlr_send_channel_selection(struct controller *c, uint8_t *agent, uint8_t *radio, - uint8_t channel, uint8_t opclass, uint8_t pref) +uint16_t cntlr_send_channel_selection(struct controller *c, uint8_t *agent, uint8_t *radio, + struct wifi_radio_opclass *opclass) { - uint8_t chanlist[1] = {}; + struct cmdu_buff *cmdu; + uint16_t mid; #if (EASYMESH_VERSION >= 6) struct node *n; + int ret; #endif - struct cmdu_buff *cmdu; - uint16_t mid = 0; - int ret = 0; - - chanlist[0] = channel; - cmdu = cmdu_alloc_simple(CMDU_CHANNEL_SELECTION_REQ, &mid); + cmdu = cntlr_gen_channel_sel_request(c, agent, radio, opclass); if (!cmdu) return -1; - memcpy(cmdu->origin, agent, 6); - ret = cntlr_gen_channel_pref(c, cmdu, radio, opclass, channel ? ARRAY_SIZE(chanlist) : 0, chanlist, pref); - if (ret) { - cmdu_free(cmdu); - return -1; - } - #if (EASYMESH_VERSION >= 6) n = cntlr_find_node(c, agent); if (n) { ret = cntlr_gen_eht_operations(c, cmdu, n); if (ret) { cmdu_free(cmdu); - return -1; + return 0xffff; } } #endif cmdu_put_eom(cmdu); - send_cmdu(c, cmdu); + mid = send_cmdu(c, cmdu); cmdu_free(cmdu); - return 0; + return mid; } struct cmdu_buff* cntlr_gen_cac_req(struct controller *c, uint8_t *agent, @@ -1012,19 +1002,20 @@ int cntlr_send_channel_scan_request(struct controller *c, uint8_t *agent_mac, return 0; } -int cntlr_send_cac_req(struct controller *c, uint8_t *agent, - int num_data, struct cac_data *data) +uint16_t cntlr_send_cac_req(struct controller *c, uint8_t *agent, + int num_data, struct cac_data *data) { struct cmdu_buff *cmdu; + uint16_t mid = 0xffff; cmdu = cntlr_gen_cac_req(c, agent, num_data, data); if (!cmdu) - return -1; + return mid; - send_cmdu(c, cmdu); + mid = send_cmdu(c, cmdu); cmdu_free(cmdu); - return 0; + return mid; } int cntlr_send_cac_term(struct controller *c, uint8_t *agent, diff --git a/src/cntlr_cmdu.h b/src/cntlr_cmdu.h index fbbf88dc..fd1919a7 100644 --- a/src/cntlr_cmdu.h +++ b/src/cntlr_cmdu.h @@ -24,6 +24,7 @@ struct sta_error_response; struct tlv; struct unassoc_sta_metric; struct wifi7_radio_capabilities; +struct wifi_radio_opclass; struct cmdu_buff *cntlr_gen_ap_autoconfig_renew(struct controller *c, uint8_t *dst); @@ -104,16 +105,16 @@ struct cmdu_buff *cntlr_gen_client_steer_request(struct controller *c, struct cmdu_buff *cntlr_gen_comb_infra_metrics_query(struct controller *c, uint8_t *origin, uint8_t *bssid_mac); int cntlr_send_channel_preference_query(struct controller *c, uint8_t *agent); -int cntlr_send_channel_selection(struct controller *c, uint8_t *agent, uint8_t *radio, - uint8_t channel, uint8_t opclass, uint8_t pref); +uint16_t cntlr_send_channel_selection(struct controller *c, uint8_t *agent, uint8_t *radio, + struct wifi_radio_opclass *opclass); int cntlr_send_channel_scan_request(struct controller *c, uint8_t *agent_mac, struct scan_req_data *data); int cntlr_send_client_assoc_ctrl_request(struct controller *c, uint8_t *agent_mac, uint8_t *bssid, uint8_t assoc_cntl_mode, uint16_t assoc_timeout, uint8_t sta_nr, uint8_t *stalist, uint16_t *mid); -int cntlr_send_cac_req(struct controller *c, uint8_t *agent, - int num_data, struct cac_data *data); +uint16_t cntlr_send_cac_req(struct controller *c, uint8_t *agent, + int num_data, struct cac_data *data); int cntlr_send_cac_term(struct controller *c, uint8_t *agent, int num_data, struct cac_data *data); int cntlr_send_client_steer_request(struct controller *c, uint8_t *agent, diff --git a/src/cntlr_commands.c b/src/cntlr_commands.c index deaaa015..ea88ce35 100644 --- a/src/cntlr_commands.c +++ b/src/cntlr_commands.c @@ -243,7 +243,7 @@ DEFINE_ATTR(send_channel_sel) = { DEFINE_ATTR(trigger_channel_clearing) = { [CHANNEL_CLEARING_ATTR_AGENT] = { - .optional = 0, + .optional = 1, .pol = { .name = "agent", .type = BLOBMSG_TYPE_STRING, @@ -252,6 +252,57 @@ DEFINE_ATTR(trigger_channel_clearing) = { }, }; +DEFINE_ATTR(trigger_acs) = { + [CHANNEL_ACS_ATTR_AGENT] = { + .optional = 1, + .pol = { + .name = "agent", + .type = BLOBMSG_TYPE_STRING, + }, + .help = "AL-address of Agent for channel recalc request", + }, + [CHANNEL_ACS_ATTR_BAND] = { + .optional = 1, + .pol = { + .name = "band", + .type = BLOBMSG_TYPE_INT32, + }, + .help = "Band for channel recalc request", + }, + [CHANNEL_ACS_ATTR_SKIP_DFS] = { + .optional = 1, + .pol = { + .name = "skip_dfs", + .type = BLOBMSG_TYPE_INT32, + }, + .help = "skip DFS channels when recalc", + }, + [CHANNEL_ACS_ATTR_OPCLASS] = { + .optional = 1, + .pol = { + .name = "opclass", + .type = BLOBMSG_TYPE_INT32, + }, + .help = "opclass for channel recalc request", + }, + [CHANNEL_ACS_ATTR_BANDWIDTH] = { + .optional = 1, + .pol = { + .name = "bandwidth", + .type = BLOBMSG_TYPE_INT32, + }, + .help = "bandwidth for channel recalc request", + }, + [CHANNEL_ACS_ATTR_PREVENT_CAC] = { + .optional = 1, + .pol = { + .name = "prevent_cac", + .type = BLOBMSG_TYPE_INT32, + }, + .help = "prevent DFS CAC when recalc", + }, +}; + DEFINE_ATTR(query_beacon_metrics) = { [BCN_METRICS_ATTR_AGENT] = { .optional = 0, @@ -985,6 +1036,7 @@ static struct controller_command cntlr_commandlist[] = { CNTLR_CMD(query_beacon_metrics, "Send Beacon Metrics Query to Agent"), CNTLR_CMD(send_channel_sel, "Send Channel Selection Request to Agent"), CNTLR_CMD(trigger_channel_clearing, "Trigger CAC Request in Agent for clearing DFS channels"), + CNTLR_CMD(trigger_acs, "Trigger ACS"), CNTLR_CMD(steer, "Send STA steering request to Agent"), CNTLR_CMD(steer_op, "Send STA steering opportunity request to Agent"), CNTLR_CMD(assoc_control, "Send STA/bSTA association control request to Agent"), diff --git a/src/cntlr_commands.h b/src/cntlr_commands.h index 3f87e281..fba99d39 100644 --- a/src/cntlr_commands.h +++ b/src/cntlr_commands.h @@ -214,6 +214,29 @@ enum channel_clearing_attrs { NUM_ATTRS_CHANNEL_CLEARING, }; +/** + * @enum channel_acs_attrs + * @brief ACS Channel attributes + * + * @ingroup cmdattrs + * @CHANNEL_ACS_ATTR_AGENT: Agent's AL-address. + * @CHANNEL_ACS_ATTR_BAND: band in Agent. + * @CHANNEL_ACS_ATTR_SKIP_DFS: skip DFS channels. + * @CHANNEL_ACS_ATTR_OPCLASS: requested opclass. + * @CHANNEL_ACS_ATTR_BANDWIDTH: requested bandwidth. + * @CHANNEL_ACS_ATTR_PREVENT_CAC: prevent DFS CAC. + * @NUM_ATTRS_CHANNEL_ACS: Number of Channel ACS attributes. + */ +enum channel_acs_attrs { + CHANNEL_ACS_ATTR_AGENT, + CHANNEL_ACS_ATTR_BAND, + CHANNEL_ACS_ATTR_SKIP_DFS, + CHANNEL_ACS_ATTR_OPCLASS, + CHANNEL_ACS_ATTR_BANDWIDTH, + CHANNEL_ACS_ATTR_PREVENT_CAC, + NUM_ATTRS_CHANNEL_ACS, +}; + /** * @enum steer_attrs * @brief Steer Request attributes diff --git a/src/cntlr_commands_impl.c b/src/cntlr_commands_impl.c index 963c2281..f407a646 100644 --- a/src/cntlr_commands_impl.c +++ b/src/cntlr_commands_impl.c @@ -1030,22 +1030,6 @@ int COMMAND(send_hld)(void *priv, void *args, void *out) return mid != 0xffff ? 0 : -1; } -static uint8_t wifi_band_to_band(enum wifi_band band) -{ - switch (band) { - case BAND_2: - return 2; - case BAND_5: - return 5; - case BAND_6: - return 6; - default: - break; - } - - return 0; -} - int COMMAND(send_channel_sel)(void *priv, void *args, void *out) { struct controller *c = (struct controller *)priv; @@ -1100,18 +1084,18 @@ int COMMAND(send_channel_sel)(void *priv, void *args, void *out) memcpy(&opclass, &r->radio_el->supp_opclass, sizeof(opclass)); wifi_opclass_set_preferences(&opclass, 0x0); - band = wifi_band_to_band(r->radio_el->band); + band = wifi_band_to_freqband(r->radio_el->band); if (!band) { err("%s unknown band %u\n", __func__, r->radio_el->band); return -EINVAL; } - if (wifi_radio_opclass_update_channel(&opclass, band, channel, bandwidth, 15 << 4)) { + if (wifi_radio_opclass_update_channel(&opclass, band, channel, bandwidth, 15)) { err("%s: opclass_update_channel(%u, %u, %u) error\n", __func__, band, channel, bandwidth); return -EINVAL; } - wifi_opclass_dump(&opclass, "chan_selection", radio_mac); + wifi_opclass_dump_ex(&opclass, "chan_selection", radio_mac, false); cmdu = cntlr_gen_channel_sel_request(c, agent_mac, radio_mac, &opclass); if (!cmdu) { @@ -1119,6 +1103,7 @@ int COMMAND(send_channel_sel)(void *priv, void *args, void *out) return -1; } + cmdu_put_eom(cmdu); mid = send_cmdu(c, cmdu); cmdu_free(cmdu); @@ -1130,6 +1115,7 @@ int COMMAND(send_channel_sel)(void *priv, void *args, void *out) return mid != 0xffff ? 0 : -1; } + int COMMAND(trigger_channel_clearing)(void *priv, void *args, void *out) { struct controller *c = (struct controller *)priv; @@ -1146,20 +1132,101 @@ int COMMAND(trigger_channel_clearing)(void *priv, void *args, void *out) return -EINVAL; } - strncpy(agent, blobmsg_data(tb[CHANNEL_CLEARING_ATTR_AGENT]), sizeof(agent) - 1); - if (!hwaddr_aton(agent, agent_mac)) + if (tb[CHANNEL_CLEARING_ATTR_AGENT]) { + strncpy(agent, blobmsg_data(tb[CHANNEL_CLEARING_ATTR_AGENT]), sizeof(agent) - 1); + if (!hwaddr_aton(agent, agent_mac)) + return -EINVAL; + } + + list_for_each_entry(node, &c->nodelist, list) { + struct netif_radio *radio = NULL; + + if (!hwaddr_is_zero(agent_mac) && memcmp(agent_mac, node->almacaddr, 6)) + continue; + + list_for_each_entry(radio, &node->radiolist, list) { + if (radio->radio_el->band != BAND_5) + continue; + + /* Action here */ + cntlr_dfs_radio_cleanup(node, radio); + cntlr_dfs_radio_cleanup_info(node, radio, bb); + } + } + + return 0; +} + +int COMMAND(trigger_acs)(void *priv, void *args, void *out) +{ + struct controller *c = (struct controller *)priv; + struct blob_buf *bb = (struct blob_buf *)out; + struct blob_attr *tb[NUM_ATTRS_CHANNEL_ACS]; + uint8_t agent_mac[6] = { 0 }; + enum wifi_band band = BAND_ANY; + struct node *node = NULL; + char agent[18] = { 0 }; + bool skip_dfs = c->cfg.acs_skip_dfs; + bool prevent_cac = c->cfg.acs_prevent_cac; + bool highest_bandwidth = c->cfg.acs_highest_bandwidth; + uint32_t bandwidth = 0; + uint8_t opclass = 0; + int ret; + + ret = controller_command_parse("trigger_acs", args, tb); + if (ret) { + err("%s: Error (ret = %d)\n", __func__, ret); return -EINVAL; + } + + if (tb[CHANNEL_ACS_ATTR_SKIP_DFS]) + skip_dfs = blobmsg_get_u32(tb[CHANNEL_ACS_ATTR_SKIP_DFS]); + + if (tb[CHANNEL_ACS_ATTR_PREVENT_CAC]) + prevent_cac = blobmsg_get_u32(tb[CHANNEL_ACS_ATTR_PREVENT_CAC]); + + if (tb[CHANNEL_ACS_ATTR_AGENT]) { + strncpy(agent, blobmsg_data(tb[CHANNEL_ACS_ATTR_AGENT]), sizeof(agent) - 1); + if (!hwaddr_aton(agent, agent_mac)) + return -EINVAL; + } + + if (tb[CHANNEL_ACS_ATTR_BAND]) { + switch (blobmsg_get_u32(tb[CHANNEL_ACS_ATTR_BAND])) { + case 2: + band = BAND_2; + break; + case 5: + band = BAND_5; + break; + case 6: + band = BAND_6; + break; + default: + break; + } + } + + if (tb[CHANNEL_ACS_ATTR_OPCLASS]) + opclass = blobmsg_get_u32(tb[CHANNEL_ACS_ATTR_OPCLASS]); + + if (tb[CHANNEL_ACS_ATTR_BANDWIDTH]) { + bandwidth = blobmsg_get_u32(tb[CHANNEL_ACS_ATTR_BANDWIDTH]); + if (!bandwidth) + highest_bandwidth = true; + } list_for_each_entry(node, &c->nodelist, list) { if (!hwaddr_is_zero(agent_mac) && memcmp(agent_mac, node->almacaddr, 6)) continue; /* Action here */ - cntlr_dfs_node_cleanup(node); + cntlr_acs_node_channel_recalc(node, band, opclass, bandwidth, skip_dfs, prevent_cac, highest_bandwidth); + + /* Show information about recalc */ + cntlr_acs_node_info(node, band, bb); } - /* reply with status ok */ - blobmsg_add_string(bb, "status", "ok"); return 0; } @@ -2849,7 +2916,8 @@ out: return mid != 0xffff ? 0 : -1; } -static char *cntlr_status_channel_pref_reason(uint8_t reason) +static char *cntlr_status_channel_pref_reason(uint8_t reason, + enum wifi_radio_opclass_dfs status) { switch (reason) { case CHANNEL_PREF_REASON_UNSPEC: @@ -2871,7 +2939,11 @@ static char *cntlr_status_channel_pref_reason(uint8_t reason) case CHANNEL_PREF_REASON_SHARED_BHAUL_PREVENT: return "shared-bhaul-prevent"; case CHANNEL_PREF_REASON_DFS_USABLE: - return "dfs-usable"; + /* Two options here, CAC required or CAC ongoing */ + if (status == WIFI_RADIO_OPCLASS_CHANNEL_DFS_CAC) + return "dfs-cac"; + else + return "dfs-usable"; case CHANNEL_PREF_REASON_DFS_AVAILABLE: return "dfs-available"; case CHANNEL_PREF_REASON_REG_DISALLOWED: @@ -2886,6 +2958,7 @@ static char *cntlr_status_channel_pref_reason(uint8_t reason) void cntlr_status_add_opclass(struct blob_buf *bb, struct wifi_radio_opclass *opclass, const char *name, int opclass_id) { + enum wifi_radio_opclass_dfs dfs_status; uint32_t cac_methods, cac_time; void *a, *aa, *t, *tt; uint8_t reas, pref; @@ -2912,9 +2985,10 @@ void cntlr_status_add_opclass(struct blob_buf *bb, struct wifi_radio_opclass *op if (!strstr(name, "cur")) { pref = (opclass->opclass[j].channel[k].preference & CHANNEL_PREF_MASK) >> 4; reas = opclass->opclass[j].channel[k].preference & CHANNEL_PREF_REASON; + dfs_status = opclass->opclass[j].channel[k].dfs; blobmsg_add_u32(bb, "preference", pref); - blobmsg_add_string(bb, "reason", cntlr_status_channel_pref_reason(reas)); + blobmsg_add_string(bb, "reason", cntlr_status_channel_pref_reason(reas, dfs_status)); if (opclass->opclass[j].channel[k].cac_methods) { cac_methods = opclass->opclass[j].channel[k].cac_methods; @@ -2966,6 +3040,10 @@ static int _cntlr_status(struct controller *c, void *args, void *out, bool full) /* Show current/prefered opclasses */ cntlr_status_add_opclass(bb, &p->radio_el->cur_opclass, "cur_opclass", 0); + cntlr_acs_radio_info(bb, p); + + if (p->radio_el->bgcac_supported) + cntlr_acs_radio_cleanup_info(bb, p); /* Limit opclass output if possible */ cur_opclass_id = ctrl_radio_cur_opclass_id(p->radio_el); @@ -3096,8 +3174,7 @@ int COMMAND(timers)(void *priv, void *args, void *out) void *t; t = blobmsg_open_table(bb, "channel_planning"); - blobmsg_add_u32(bb, "channel_plan", timer_remaining_ms(&c->acs)); - blobmsg_add_u32(bb, "allow_bgdfs", timer_remaining_ms(&c->dfs_cleanup)); + blobmsg_add_u32(bb, "acs_timeout", timer_remaining_ms(&c->acs)); blobmsg_close_table(bb, t); return 0; diff --git a/src/cntlr_commands_impl.h b/src/cntlr_commands_impl.h index b1ae36eb..437db813 100644 --- a/src/cntlr_commands_impl.h +++ b/src/cntlr_commands_impl.h @@ -38,6 +38,7 @@ DECLARE_COMMAND(query_channel_pref); DECLARE_COMMAND(query_beacon_metrics); DECLARE_COMMAND(send_channel_sel); DECLARE_COMMAND(trigger_channel_clearing); +DECLARE_COMMAND(trigger_acs); DECLARE_COMMAND(steer); DECLARE_COMMAND(steer_op); DECLARE_COMMAND(assoc_control); diff --git a/src/cntlr_map.c b/src/cntlr_map.c index 4a537ddb..4cd4022c 100644 --- a/src/cntlr_map.c +++ b/src/cntlr_map.c @@ -666,7 +666,7 @@ int handle_topology_response(void *cntlr, struct cmdu_buff *cmdu, struct node *n } /* Check opclass preferency age */ - if (cntlr_node_pref_opclass_expired(n)) { + if (cntlr_node_pref_opclass_expired(n, 300)) { cntlr_trace(LOG_CHANNEL, "node " MACFMT " pref opclass expired\n", MAC2STR(n->almacaddr)); cntlr_send_channel_preference_query(c, n->almacaddr); } @@ -1520,6 +1520,9 @@ static int cntlr_parse_radio_cac_caps(struct controller *c, struct node *n, stru chan->cac_methods |= get_supp_methods(cac->supp_method); chan->cac_time = get_duration_data(cac->duration); + + if (chan->cac_methods && chan->cac_methods != (1 << WIFI_CAC_CONTINUOUS)) + radio_el->bgcac_supported = true; } } } @@ -1634,6 +1637,7 @@ int handle_ap_caps_report(void *cntlr, struct cmdu_buff *cmdu, struct node *n) } wifi_opclass_dump(opclass, "dev_supp_opclass", re->macaddr); + cntlr_acs_dev_supp_opclass(n, r); } @@ -1769,6 +1773,102 @@ int handle_channel_pref_report(void *cntlr, struct cmdu_buff *cmdu, struct node } cntlr_radio_pref_opclass_dump(r->radio_el); + + /* Kick ACS code - for 5GHz we will send from CAC status */ + if (r->radio_el->band != BAND_5) + cntlr_acs_channel_pref_report(n, r); + } + + idx = 0; + while (tv[CHANNEL_PREF_REPORT_CAC_COMPLETION_REPORT_IDX][idx]) { + struct tlv *t = (struct tlv *)tv[CHANNEL_PREF_REPORT_CAC_COMPLETION_REPORT_IDX][idx++]; + uint8_t num_radio; + uint8_t num_pairs; + + offset = 0; + num_radio = t->data[offset++]; + + for (i = 0; i < num_radio; i++) { + uint8_t mac[6] = { 0 }; + uint8_t opclass, channel, status; + + memcpy(mac, &t->data[offset], 6); + offset += 6; + + opclass = t->data[offset++]; + channel = t->data[offset++]; + status = t->data[offset++]; + + num_pairs = t->data[offset++]; + for (j = 0; j < num_pairs; j++) + offset += 2; + + r = cntlr_find_radio(cntlr, mac); + if (!r) + continue; + + /* Kick ACS code */ + cntlr_acs_cac_completion(r, opclass, channel, status); + } + + break; + } + + idx = 0; + while (tv[CHANNEL_PREF_REPORT_CAC_STATUS_REPORT_IDX][idx]) { + struct tlv *t = (struct tlv *)tv[CHANNEL_PREF_REPORT_CAC_STATUS_REPORT_IDX][idx++]; + uint8_t channel, opclassid; + uint16_t time; + uint8_t num; + + r = cntlr_find_radio_in_node_by_band(n->cntlr, n, BAND_5); + if (!r) + break; + + offset = 0; + + /* CAC completed */ + num = t->data[offset++]; + for (i = 0; i < num; i++) { + opclassid = t->data[offset++]; + channel = t->data[offset++]; + time = BUF_GET_BE16(t->data[offset]); + offset += 2; + + cntlr_radio_pref_opclass_set_dfs_status(r->radio_el, opclassid, channel, + time ? WIFI_RADIO_OPCLASS_CHANNEL_DFS_AVAILABLE : + WIFI_RADIO_OPCLASS_CHANNEL_DFS_NONE); + } + + /* NOP */ + num = t->data[offset++]; + for (i = 0; i < num; i++) { + opclassid = t->data[offset++]; + channel = t->data[offset++]; + time = BUF_GET_BE16(t->data[offset]); + offset += 2; + + cntlr_radio_pref_opclass_set_dfs_status(r->radio_el, opclassid, channel, + WIFI_RADIO_OPCLASS_CHANNEL_DFS_NOP); + } + + /* CAC ongoing */ + num = t->data[offset++]; + for (i = 0; i < num; i++) { + opclassid = t->data[offset++]; + channel = t->data[offset++]; + time = BUF_GET_BE24(t->data[offset]); + offset += 3; + + cntlr_radio_pref_opclass_set_dfs_status(r->radio_el, opclassid, channel, + WIFI_RADIO_OPCLASS_CHANNEL_DFS_CAC); + } + + /* Kick ACS code */ + cntlr_acs_channel_pref_report(n, r); + + /* Only one allowed */ + break; } return 0; @@ -1776,8 +1876,40 @@ int handle_channel_pref_report(void *cntlr, struct cmdu_buff *cmdu, struct node int handle_channel_sel_response(void *cntlr, struct cmdu_buff *cmdu, struct node *n) { - trace("%s: --->\n", __func__); - return 0; + int idx = 0; + struct controller *c = (struct controller *) cntlr; + struct tlv *tv[CHANNEL_SELECTION_RESP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0}; + uint16_t mid; + + cntlr_dbg(LOG_CHANNEL, "%s called\n", __func__); + + if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) { + dbg("%s: map_cmdu_validate_parse ( ..,EMP=%d) failed, err = (%d) '%s'\n", __func__, + n->map_profile, map_error, map_strerror(map_error)); + return -1; + } + + mid = cmdu_get_mid(cmdu); + while (idx < TLV_MAXNUM && tv[CHANNEL_SELECTION_RESP_CHANNEL_SELECTION_IDX][idx]) { + struct netif_radio *r; + struct tlv_channel_selection_resp *p; + + p = (struct tlv_channel_selection_resp *) + tv[CHANNEL_SELECTION_RESP_CHANNEL_SELECTION_IDX][idx++]->data; + + cntlr_dbg(LOG_CHANNEL, "\tmid: %d\n", mid); + cntlr_dbg(LOG_CHANNEL, "\tradio_id: " MACFMT "\n", MAC2STR(p->radio)); + cntlr_dbg(LOG_CHANNEL, "\tresponse_code: %d\n", p->response); + + r = cntlr_find_radio(c, p->radio); + if (!r) + continue; + + /* kick acs code */ + cntlr_acs_channel_sel_response(r, mid, p->response); + } + + return 0; } int handle_oper_channel_report(void *cntlr, struct cmdu_buff *cmdu, struct node *n) @@ -1848,6 +1980,9 @@ int handle_oper_channel_report(void *cntlr, struct cmdu_buff *cmdu, struct node } cntlr_radio_cur_opclass_dump(r->radio_el); + + /* kick acs code */ + cntlr_acs_oper_channel_report(r); } return 0; @@ -2059,6 +2194,9 @@ int handle_ap_metrics_response(void *cntlr, struct cmdu_buff *cmdu, struct node radio = cntlr_find_radio(c, p->radio); if (radio) { + /* Kick ACS code */ + cntlr_acs_radio_metrics(n, radio, p->noise, p->receive_other); + radio->radio_el->anpi = p->noise; radio->radio_el->tx_utilization = p->transmit; radio->radio_el->rx_utilization = p->receive_self; @@ -3041,6 +3179,9 @@ int handle_channel_scan_report(void *cntlr, struct cmdu_buff *cmdu, char timestamp[TIMESTAMP_MAX_LEN] = {0}; struct tlv_timestamp *p = NULL; uint8_t *tv_data = NULL; + struct netif_radio *r = NULL; + uint16_t mid; + int i; if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) { dbg("%s: map_cmdu_validate_parse failed, err = (%d) '%s'\n", @@ -3058,6 +3199,23 @@ int handle_channel_scan_report(void *cntlr, struct cmdu_buff *cmdu, dbg("%s: timestamp = %s\n", __func__, timestamp); + /* Inform ACS about fresh scan and preference */ + mid = cmdu_get_mid(cmdu); + list_for_each_entry(r, &n->radiolist, list) { + for (i = 0; i < num_result; i++) { + struct tlv_channel_scan_result *tlv = + (struct tlv_channel_scan_result *)tv_scan[i]->data; + + if (!memcmp(r->radio_el->macaddr, tlv->radio, 6)) + break; + } + + if (i == num_result) + continue; + + cntlr_acs_scan_report(n, r, mid); + } + return cntlr_radio_update_scanlist(cntlr, timestamp, tv_scan, num_result); } diff --git a/src/cntlr_tlv.c b/src/cntlr_tlv.c index eca7759f..c98b70ae 100644 --- a/src/cntlr_tlv.c +++ b/src/cntlr_tlv.c @@ -1357,6 +1357,7 @@ struct cmdu_buff *cntlr_gen_channel_sel_request(struct controller *c, { struct wifi_radio_opclass_entry *entry; struct wifi_radio_opclass_channel *channel; + struct wifi_radio_opclass empty = {}; int ret, offset = 0; struct cmdu_buff *cmdu; struct netif_radio *radio; @@ -1391,6 +1392,12 @@ struct cmdu_buff *cntlr_gen_channel_sel_request(struct controller *c, memcpy(&t->data[offset], radio_id, 6); /* radio id */ offset += 6; + if (!opclass) { + memcpy(&empty, &radio->radio_el->supp_opclass, sizeof(empty)); + wifi_opclass_set_preferences(&empty, 0x0); + opclass = ∅ + } + /* Now update prefered */ opclass_num_offset = offset; t->data[offset++] = 0; /* m */ @@ -1410,125 +1417,60 @@ struct cmdu_buff *cntlr_gen_channel_sel_request(struct controller *c, continue; } - /* Group disabled channels - assume other with pref=15 */ - t->data[offset++] = entry->id; - channel_offset = offset; - t->data[offset++] = 0; /* k */ - - for (j = 0; j < entry->num_channel; j++) { - channel = &entry->channel[j]; - - if (((channel->preference & CHANNEL_PREF_MASK) >> 4) != 0) - continue; - - t->data[offset++] = channel->channel; - channel_num++; - } - - t->data[offset++] = 0; - t->data[channel_offset] = channel_num; - opclass_num++; - } - - t->data[opclass_num_offset] = opclass_num; /* m */ - t->len = offset; - ret = cmdu_put_tlv(cmdu, t); - if (ret) { - dbg("%s: error: cmdu_put_tlv()\n", __func__); - cmdu_free(cmdu); - return NULL; - } + if (wifi_opclass_same_preference(opclass, &preference)) { + /* Group disabled channels - assume other with pref=15 */ + t->data[offset++] = entry->id; + channel_offset = offset; + t->data[offset++] = 0; /* k */ - cmdu_put_eom(cmdu); - return cmdu; -} + for (j = 0; j < entry->num_channel; j++) { + channel = &entry->channel[j]; -int cntlr_gen_channel_pref(struct controller *c, struct cmdu_buff *frm, - uint8_t *radio_id, uint8_t class_id, uint8_t channel_nr, - const uint8_t *chanlist, uint8_t pref) -{ - int ret, offset = 0; - struct wifi_radio_opclass opclass = {}; - struct wifi_radio_opclass_entry *entry; - struct wifi_radio_opclass_channel *channel; - struct netif_radio *radio; - struct tlv *t; - int opclass_num_offset; - int opclass_num; - int i, j; - - /* Find radio and supported opclasses */ - radio = cntlr_find_radio(c, radio_id); - if (!radio) - return -1; - - if (!radio->radio_el) - return -1; - - if (!radio->radio_el->supp_opclass.num_opclass && channel_nr) - return -1; + if (((channel->preference & CHANNEL_PREF_MASK) >> 4) != 0) + continue; - /* Build opclass we would like to send */ - memcpy(&opclass, &radio->radio_el->supp_opclass, sizeof(opclass)); - wifi_opclass_set_preferences(&opclass, 0x0); - - /* Update requested preference */ - for (i = 0; i < channel_nr; i++) - wifi_opclass_id_set_channel_preferences(&opclass, class_id, chanlist[i], pref << 4); - - wifi_opclass_dump(&opclass, "send_chan_pref", radio->radio_el->macaddr); - - t = cmdu_reserve_tlv(frm, 1024); - if (!t) - return -1; - - /* Prefer all supported - don't include any opclass */ - if (!channel_nr) - opclass.num_opclass = 0; - - t->type = MAP_TLV_CHANNEL_PREFERENCE; - - memcpy(&t->data[offset], radio_id, 6); /* radio id */ - offset += 6; + t->data[offset++] = channel->channel; + channel_num++; + } - /* Now update prefered */ - opclass_num_offset = offset; - t->data[offset++] = 0; /* m */ + t->data[offset++] = 0; + t->data[channel_offset] = channel_num; + opclass_num++; + continue; + } - opclass_num = 0; - for (i = 0; i < opclass.num_opclass; i++) { - entry = &opclass.opclass[i]; - uint8_t preference; + /* Setup whole opclass disabled */ + t->data[offset++] = entry->id; + t->data[offset++] = 0; /* k */ + t->data[offset++] = 0; + opclass_num++; - if (wifi_opclass_id_same_preference(&opclass, entry->id, &preference)) { - t->data[offset++] = entry->id; - t->data[offset++] = 0; /* k */ - t->data[offset++] = preference; - opclass_num++; - continue; - } + /* Next unlock required channels */ + for (j = 0; j < entry->num_channel; j++) { + channel = &entry->channel[j]; - for (j = 0; j < entry->num_channel; j++) { - channel = &entry->channel[j]; + if (((channel->preference & CHANNEL_PREF_MASK) >> 4) == 0) + continue; - t->data[offset++] = entry->id; - t->data[offset++] = 1; /* k */ - t->data[offset++] = channel->channel; - t->data[offset++] = channel->preference; + t->data[offset++] = entry->id; + t->data[offset++] = 1; /* k */ + t->data[offset++] = channel->channel; + t->data[offset++] = channel->preference; - opclass_num++; - } + opclass_num++; + } } t->data[opclass_num_offset] = opclass_num; /* m */ t->len = offset; - ret = cmdu_put_tlv(frm, t); + ret = cmdu_put_tlv(cmdu, t); if (ret) { dbg("%s: error: cmdu_put_tlv()\n", __func__); - return -1; + cmdu_free(cmdu); + return NULL; } - return 0; + return cmdu; } int cntlr_gen_txpower_limit(struct controller *c, struct cmdu_buff *frm, diff --git a/src/cntlr_tlv.h b/src/cntlr_tlv.h index 68acf3af..0f8a4473 100644 --- a/src/cntlr_tlv.h +++ b/src/cntlr_tlv.h @@ -114,9 +114,6 @@ int cnltr_gen_searched_service(struct controller *c, struct cmdu_buff *frm, uint8_t service); int agent_gen_tlv_error_code(struct controller *c, struct cmdu_buff *cmdu, uint8_t *macaddr, uint8_t reason_code); -int cntlr_gen_channel_pref(struct controller *c, struct cmdu_buff *frm, - uint8_t *radio_id, uint8_t class_id, uint8_t channel_nr, - const uint8_t *chanlist, uint8_t pref); int cntlr_gen_txpower_limit(struct controller *c, struct cmdu_buff *frm, uint8_t *radio_id, uint8_t txpower_limit); int cntlr_gen_cac_tlv(struct controller *c, struct cmdu_buff *frm, diff --git a/src/cntlr_ubus.c b/src/cntlr_ubus.c index d035e25c..a6e1854a 100644 --- a/src/cntlr_ubus.c +++ b/src/cntlr_ubus.c @@ -409,6 +409,34 @@ out: return ret; } +int cntlr_ubus_trigger_acs(struct ubus_context *ctx, + struct ubus_object *obj, + struct ubus_request_data *req, + const char *method, + struct blob_attr *msg) +{ + //TRACE_ENTER(); + struct controller *c = container_of(obj, struct controller, obj); + struct blob_buf bb; + int ret; + + memset(&bb, 0, sizeof(bb)); + blob_buf_init(&bb, 0); + + ret = COMMAND(trigger_acs)(c, msg, &bb); + if (ret) { + if (ret == -EINVAL) + ret = UBUS_STATUS_INVALID_ARGUMENT; + + goto out; + } + + ubus_send_reply(ctx, req, bb.head); +out: + blob_buf_free(&bb); + return ret; +} + int cntlr_ubus_steer(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, @@ -1128,6 +1156,7 @@ int cntlr_publish_object(struct controller *c, const char *objname) { "query_beacon_metrics", cntlr_ubus_query_beacon_metrics }, { "send_channel_sel", cntlr_ubus_send_channel_sel }, { "trigger_channel_clearing", cntlr_ubus_trigger_channel_clearing }, + { "trigger_acs", cntlr_ubus_trigger_acs }, { "steer", cntlr_ubus_steer }, { "steer_op", cntlr_ubus_steer_op }, { "assoc_control", cntlr_ubus_assoc_control }, diff --git a/src/cntlr_ubus_dbg.c b/src/cntlr_ubus_dbg.c index e66a91b0..236446d0 100644 --- a/src/cntlr_ubus_dbg.c +++ b/src/cntlr_ubus_dbg.c @@ -214,7 +214,7 @@ static int cntlr_dbg_csr(struct ubus_context *ctx, struct netif_radio *r = NULL; list_for_each_entry(r, &node->radiolist, list) { - cntlr_send_channel_selection(c, node->almacaddr, r->radio_el->macaddr, 0, 0, 0); + cntlr_send_channel_selection(c, node->almacaddr, r->radio_el->macaddr, NULL); } } diff --git a/src/config.c b/src/config.c index 34696ba3..4325d949 100644 --- a/src/config.c +++ b/src/config.c @@ -721,8 +721,6 @@ static int cntlr_config_get_base(struct controller_config *c, CNTLR_RESEND_NUM, CNTLR_BCN_METRICS_MAX_NUM, CNTLR_INITIAL_CHANNEL_SCAN, - CNTLR_CHANNEL_PLAN_TIMEOUT, - CNTLR_BGDFS_TIMEOUT, CNTLR_STALE_STA_TIMEOUT, CNTLR_PRIMARY_VID, CNTLR_DEFAULT_PCP, @@ -741,8 +739,6 @@ static int cntlr_config_get_base(struct controller_config *c, [CNTLR_RESEND_NUM] = { .name = "resend_num", .type = UCI_TYPE_STRING }, [CNTLR_BCN_METRICS_MAX_NUM] = { .name = "bcn_metrics_max_num", .type = UCI_TYPE_STRING }, [CNTLR_INITIAL_CHANNEL_SCAN] = { .name = "initial_channel_scan", .type = UCI_TYPE_STRING }, - [CNTLR_CHANNEL_PLAN_TIMEOUT] = { .name = "channel_plan", .type = UCI_TYPE_STRING }, - [CNTLR_BGDFS_TIMEOUT] = { .name = "allow_bgdfs", .type = UCI_TYPE_STRING }, [CNTLR_STALE_STA_TIMEOUT] = { .name = "stale_sta_timeout", .type = UCI_TYPE_STRING }, [CNTLR_PRIMARY_VID] = { .name = "primary_vid", .type = UCI_TYPE_STRING }, [CNTLR_DEFAULT_PCP] = { .name = "default_pcp", .type = UCI_TYPE_STRING }, @@ -855,35 +851,6 @@ static int cntlr_config_get_base(struct controller_config *c, c->initial_channel_scan = (enbl == 1) ? true : false; } - if (tb[CNTLR_CHANNEL_PLAN_TIMEOUT]) { - const char *val = tb[CNTLR_CHANNEL_PLAN_TIMEOUT]->v.string; - - errno = 0; - c->acs_timeout = strtol(val, &endptr, 10); - if (errno || *endptr != '\0') { - cntlr_dbg(LOG_MISC, "|%s:%d| Error parsing channel_plan value: %s\n", - __func__, __LINE__, val); - return -1; - } - /* TODO agree conf param - by default run each 3 hours */ - if (c->acs_timeout > 0 && c->acs_timeout < 180) - c->acs_timeout = 3600 * 3; - } - - if (tb[CNTLR_BGDFS_TIMEOUT]) { - const char *val = tb[CNTLR_BGDFS_TIMEOUT]->v.string; - - errno = 0; - c->dfs_cleanup_timeout = strtol(val, &endptr, 10); - if (errno || *endptr != '\0') { - cntlr_dbg(LOG_MISC, "|%s:%d| Error parsing allow_bgdfs value: %s\n", - __func__, __LINE__, val); - return -1; - } - if (c->dfs_cleanup_timeout > 0 && c->dfs_cleanup_timeout < 120) - c->dfs_cleanup_timeout = 120; - } - #define DEFAULT_STALE_STA_TIMEOUT (30 * 86400) /* Default 30days */ if (tb[CNTLR_STALE_STA_TIMEOUT]) { @@ -1197,6 +1164,134 @@ static int cntlr_config_get_steer_policy(struct controller_config *cc, return 0; } +static int cntlr_config_get_channel_plan(struct controller_config *c, + struct uci_section *s) +{ + enum { + CNTLR_CHANNEL_PLAN_ACS, + CNTLR_CHANNEL_PLAN_ACS_RECALC_TIMEOUT, + CNTLR_CHANNEL_PLAN_ACS_SKIP_DFS, + CNTLR_CHANNEL_PLAN_ACS_PREVENT_CAC, + CNTLR_CHANNEL_PLAN_ACS_HIGHEST_BANDWIDTH, + CNTLR_CHANNEL_PLAN_ACS_SCAN_BEFORE_RECALC, + CNTLR_CHANNEL_PLAN_BGCAC, + NUM_CNTLR_CHANNEL_PLAN_ATTRS + }; + const struct uci_parse_option opts[NUM_CNTLR_CHANNEL_PLAN_ATTRS] = { + [CNTLR_CHANNEL_PLAN_ACS] = { .name = "acs", .type = UCI_TYPE_STRING }, + [CNTLR_CHANNEL_PLAN_ACS_RECALC_TIMEOUT] = { .name = "acs_timeout", .type = UCI_TYPE_STRING }, + [CNTLR_CHANNEL_PLAN_ACS_SKIP_DFS] = { .name = "acs_skip_dfs", .type = UCI_TYPE_STRING }, + [CNTLR_CHANNEL_PLAN_ACS_PREVENT_CAC] = { .name = "acs_prevent_cac", .type = UCI_TYPE_STRING }, + [CNTLR_CHANNEL_PLAN_ACS_HIGHEST_BANDWIDTH] = { .name = "acs_highest_bandwidth", .type = UCI_TYPE_STRING }, + [CNTLR_CHANNEL_PLAN_ACS_SCAN_BEFORE_RECALC] = { .name = "acs_scan_before_recalc", .type = UCI_TYPE_STRING }, + [CNTLR_CHANNEL_PLAN_BGCAC] = { .name = "preclear_dfs", .type = UCI_TYPE_STRING }, + }; + struct uci_option *tb[NUM_CNTLR_CHANNEL_PLAN_ATTRS]; + char *endptr = NULL; + + uci_parse_section(s, opts, NUM_CNTLR_CHANNEL_PLAN_ATTRS, tb); + + if (tb[CNTLR_CHANNEL_PLAN_ACS]) { + const char *val = tb[CNTLR_CHANNEL_PLAN_ACS]->v.string; + + errno = 0; + c->acs = strtol(val, &endptr, 10); + if (errno || *endptr != '\0') { + cntlr_dbg(LOG_MISC, "|%s:%d| Error parsing channel_plan acs value: %s\n", + __func__, __LINE__, val); + return -1; + } + } else { + c->acs = false; + } + + if (tb[CNTLR_CHANNEL_PLAN_ACS_RECALC_TIMEOUT]) { + const char *val = tb[CNTLR_CHANNEL_PLAN_ACS_RECALC_TIMEOUT]->v.string; + + c->acs_timeout = time_str_to_sec(val); + if (c->acs_timeout == -1) { + cntlr_dbg(LOG_MISC, "|%s:%d| Error parsing channel_plan acs_timeout value: %s\n", + __func__, __LINE__, val); + return -1; + } + } else { + c->acs_timeout = 0; + } + + if (tb[CNTLR_CHANNEL_PLAN_ACS_SKIP_DFS]) { + const char *val = tb[CNTLR_CHANNEL_PLAN_ACS_SKIP_DFS]->v.string; + + errno = 0; + c->acs_skip_dfs = strtol(val, &endptr, 10); + if (errno || *endptr != '\0') { + cntlr_dbg(LOG_MISC, "|%s:%d| Error parsing channel_plan skip_dfs value: %s\n", + __func__, __LINE__, val); + return -1; + } + } else { + c->acs_skip_dfs = false; + } + + if (tb[CNTLR_CHANNEL_PLAN_ACS_PREVENT_CAC]) { + const char *val = tb[CNTLR_CHANNEL_PLAN_ACS_PREVENT_CAC]->v.string; + + errno = 0; + c->acs_prevent_cac = strtol(val, &endptr, 10); + if (errno || *endptr != '\0') { + cntlr_dbg(LOG_MISC, "|%s:%d| Error parsing channel_plan prevent_cac value: %s\n", + __func__, __LINE__, val); + return -1; + } + } else { + c->acs_prevent_cac = true; + } + + if (tb[CNTLR_CHANNEL_PLAN_ACS_HIGHEST_BANDWIDTH]) { + const char *val = tb[CNTLR_CHANNEL_PLAN_ACS_HIGHEST_BANDWIDTH]->v.string; + + errno = 0; + c->acs_highest_bandwidth = strtol(val, &endptr, 10); + if (errno || *endptr != '\0') { + cntlr_dbg(LOG_MISC, "|%s:%d| Error parsing channel_plan higest_bandwidth value: %s\n", + __func__, __LINE__, val); + return -1; + } + } else { + /* Use configured bandwidth */ + c->acs_highest_bandwidth = false; + } + + if (tb[CNTLR_CHANNEL_PLAN_ACS_SCAN_BEFORE_RECALC]) { + const char *val = tb[CNTLR_CHANNEL_PLAN_ACS_SCAN_BEFORE_RECALC]->v.string; + + errno = 0; + c->acs_scan_before_recalc = strtol(val, &endptr, 10); + if (errno || *endptr != '\0') { + cntlr_dbg(LOG_MISC, "|%s:%d| Error parsing channel_plan scan_before_recalc value: %s\n", + __func__, __LINE__, val); + return -1; + } + } else { + c->acs_scan_before_recalc = true; + } + + if (tb[CNTLR_CHANNEL_PLAN_BGCAC]) { + const char *val = tb[CNTLR_CHANNEL_PLAN_BGCAC]->v.string; + + errno = 0; + c->dfs_cleanup = !!strtol(val, &endptr, 10); + if (errno || *endptr != '\0') { + cntlr_dbg(LOG_MISC, "|%s:%d| Error parsing preclear_dfs value: %s\n", + __func__, __LINE__, val); + return -1; + } + } else { + c->dfs_cleanup = false; + } + + return 0; +} + static int cntlr_config_get_credentials(struct controller_config *c, struct uci_section *s) { @@ -3194,6 +3289,8 @@ uint32_t cntlr_config_reload(struct controller_config *cfg) cntlr_config_get_steer_policy(cfg, s); cntlr_load_steer_modules(c); cntlr_reorder_steer_modules(c); + } else if (!strcmp(s->type, "channel_plan")) { + cntlr_config_get_channel_plan(cfg, s); } else if (!strcmp(s->type, "ap")) { cntlr_config_get_credentials(cfg, s); } else if (!strcmp(s->type, "node")) { diff --git a/src/config.h b/src/config.h index cb45fb50..759b2903 100644 --- a/src/config.h +++ b/src/config.h @@ -258,8 +258,13 @@ struct controller_config { bool initial_channel_scan; int num_bss; int num_apolicy; + bool acs; int acs_timeout; - int dfs_cleanup_timeout; + bool acs_skip_dfs; + bool acs_prevent_cac; + bool acs_highest_bandwidth; + bool acs_scan_before_recalc; + bool dfs_cleanup; int stale_sta_timeout; unsigned int primary_vid; unsigned int default_pcp; diff --git a/src/timer.c b/src/timer.c index 04baf847..e8476b2f 100644 --- a/src/timer.c +++ b/src/timer.c @@ -76,7 +76,7 @@ int timer_remaining_ms(atimer_t *t) clock_getnow(&now); timersub(&t->expires, &now, &res); - return res.tv_sec + res.tv_usec / 1000; + return res.tv_sec * 1000 + res.tv_usec / 1000; } void timestamp_reset(struct timespec *ts) diff --git a/src/utils/utils.c b/src/utils/utils.c index 3985011c..8d012680 100644 --- a/src/utils/utils.c +++ b/src/utils/utils.c @@ -590,6 +590,22 @@ const char *wifi_freqband_to_str(uint32_t band) return NULL; } +uint8_t wifi_band_to_freqband(enum wifi_band band) +{ + switch (band) { + case BAND_2: + return 2; + case BAND_5: + return 5; + case BAND_6: + return 6; + default: + break; + } + + return 0; +} + int time_str_to_sec(const char *val) { if (!val || *val == '\0') { diff --git a/src/utils/utils.h b/src/utils/utils.h index 2468d368..08798ad2 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -160,6 +160,7 @@ int rcpi_to_rssi(uint8_t rcpi); bool is_vid_valid(uint16_t vid); uint16_t wifi_sec_to_auth_types(enum wifi_security sec); const char *wifi_freqband_to_str(uint32_t band); +uint8_t wifi_band_to_freqband(enum wifi_band band); int time_str_to_sec(const char *val); #define blobmsg_add_macaddr(b, f, v) \ diff --git a/src/wifi_dataelements.h b/src/wifi_dataelements.h index 20c53ef4..0ede77b7 100644 --- a/src/wifi_dataelements.h +++ b/src/wifi_dataelements.h @@ -636,6 +636,7 @@ struct wifi_radio_element { uint8_t tx_utilization; uint8_t rx_utilization; uint8_t other_utilization; + uint8_t tx_streams; uint8_t rx_streams; char country_code[4]; @@ -672,6 +673,9 @@ struct wifi_radio_element { struct list_head fbss_akmlist; /* fBSS AKM list */ struct list_head bbss_akmlist; /* bBSS AKM list */ + + bool bgcac_supported; + void *acs; }; struct wifi_default_8021q { diff --git a/src/wifi_opclass.c b/src/wifi_opclass.c index af46639f..3ac47d72 100644 --- a/src/wifi_opclass.c +++ b/src/wifi_opclass.c @@ -214,7 +214,7 @@ static const struct wifi_radio_opclass e4 = { { .channel = 153, .ctrl_channels = {153}}, { .channel = 157, .ctrl_channels = {157}}, { .channel = 161, .ctrl_channels = {161}}, - { .channel = 164, .ctrl_channels = {164}}, + { .channel = 165, .ctrl_channels = {165}}, { .channel = 169, .ctrl_channels = {169}}, { .channel = 173, .ctrl_channels = {173}}, { .channel = 173, .ctrl_channels = {173}}, @@ -616,7 +616,7 @@ void wifi_opclass_reset(struct wifi_radio_opclass *opclass) memset(opclass->opclass, 0, sizeof(opclass->opclass)); } -void wifi_opclass_dump(struct wifi_radio_opclass *opclass, const char *name, uint8_t *radio) +void wifi_opclass_dump_ex(struct wifi_radio_opclass *opclass, const char *name, uint8_t *radio, bool full) { struct wifi_radio_opclass_entry *entry; char radio_str[18] = {}; @@ -628,15 +628,26 @@ void wifi_opclass_dump(struct wifi_radio_opclass *opclass, const char *name, uin cntlr_dbg(LOG_CHANNEL, ">>> %s %s opclass num: %d\n", radio_str, name ? name : "", opclass->num_opclass); for (i = 0; i < opclass->num_opclass; i++) { entry = &opclass->opclass[i]; - cntlr_dbg(LOG_CHANNEL, "opclass: %u\n", entry->id); + + if (full) + cntlr_trace(LOG_CHANNEL, "opclass: %u\n", entry->id); for (j = 0; j < entry->num_channel; j++) { - cntlr_dbg(LOG_CHANNEL, "\tchan %u pref %u reason %u\n", + if (!full && !((entry->channel[j].preference & CHANNEL_PREF_MASK) >> 4)) + continue; + if (!full) + cntlr_trace(LOG_CHANNEL, "opclass: %u\n", entry->id); + cntlr_trace(LOG_CHANNEL, "\tchan %u pref %u reason %u\n", entry->channel[j].channel, (entry->channel[j].preference & CHANNEL_PREF_MASK) >> 4, entry->channel[j].preference & CHANNEL_PREF_REASON); } } - cntlr_dbg(LOG_CHANNEL, "<<<\n"); + cntlr_trace(LOG_CHANNEL, "<<<\n"); +} + +void wifi_opclass_dump(struct wifi_radio_opclass *opclass, const char *name, uint8_t *radio) +{ + return wifi_opclass_dump_ex(opclass, name, radio, true); } uint8_t wifi_opclass_get_id(struct wifi_radio_opclass *opclass, uint8_t channel, int bandwidth) @@ -680,7 +691,7 @@ _wifi_opclass_get_higest_preference(struct wifi_radio_opclass *opclass, int req_bandwidth, uint8_t *opclass_id, uint8_t *channel, - uint8_t *bandwidth) + uint16_t *bandwidth) { struct wifi_radio_opclass_entry *entry; struct wifi_radio_opclass_channel *chan; @@ -726,7 +737,7 @@ _wifi_opclass_get_higest_preference(struct wifi_radio_opclass *opclass, return best; } -static uint8_t wifi_get_best_ctrl_channel(struct wifi_radio_opclass *opclass, const uint8_t *channels, int channels_num) +uint8_t wifi_get_best_ctrl_channel(struct wifi_radio_opclass *opclass, const uint8_t *channels, int channels_num) { struct wifi_radio_opclass_entry *entry; struct wifi_radio_opclass_channel *chan; @@ -767,13 +778,14 @@ int wifi_opclass_get_higest_preference(struct wifi_radio_opclass *opclass, int b uint8_t *opclass_id, uint8_t *channel) { struct wifi_radio_opclass_channel *best; - uint8_t bw; + uint16_t bw; best = _wifi_opclass_get_higest_preference(opclass, bandwidth, opclass_id, channel, &bw); if (!best) return -1; switch (bw) { + case 320: case 160: case 80: *channel = wifi_get_best_ctrl_channel(opclass, best->ctrl_channels, ARRAY_SIZE(best->ctrl_channels)); @@ -836,6 +848,33 @@ bool wifi_opclass_id_same_preference(struct wifi_radio_opclass *opclass, uint8_t return true; } +bool wifi_opclass_same_preference(struct wifi_radio_opclass *opclass, uint8_t *preferences) +{ + struct wifi_radio_opclass_entry *entry; + struct wifi_radio_opclass_channel *chan; + uint8_t pref = 0; + int i, j; + + for (i = 0; i < opclass->num_opclass; i++) { + entry = &opclass->opclass[i]; + + for (j = 0; j < entry->num_channel; j++) { + chan = &entry->channel[j]; + + if (((chan->preference & CHANNEL_PREF_MASK) >> 4) == 0) + continue; + + if (pref && pref != ((chan->preference & CHANNEL_PREF_MASK) >> 4)) + return false; + + pref = (chan->preference & CHANNEL_PREF_MASK) >> 4; + } + } + + *preferences = pref; + return true; +} + bool wifi_opclass_max_preference(uint8_t preference) { uint8_t pref; @@ -1388,36 +1427,24 @@ bool wifi_opclass_has_channel(uint8_t id, uint8_t channel) } int wifi_radio_opclass_update_channel(struct wifi_radio_opclass *opclass, uint8_t band, - uint32_t channel, uint32_t bw, uint32_t pref) + uint32_t channel, uint32_t bw, uint8_t pref) { struct wifi_radio_opclass_channel *channel_entry; - int bws[] = {20, 40, 80, 160, 320}; + int bws[] = {320, 160, 80, 40, 20}; int i; - if (!bw) { - for (i = 0; i < ARRAY_SIZE(bws); i++) { - channel_entry = wifi_opclass_find_opclass_channel(opclass, band, channel, bws[i]); - if (!channel_entry) - continue; - - channel_entry->preference = pref; - } - - return 0; - } - - channel_entry = wifi_opclass_find_opclass_channel(opclass, band, channel, bw); - if (!channel_entry) - return -1; - - channel_entry->preference = pref; + for (i = 0; i < ARRAY_SIZE(bws); i++) { + if (bw && bw < bws[i]) + continue; - /* Setup also 20MHz channel - so agent will know ctrl channel */ - if (bw >= 80) { - channel_entry = wifi_opclass_find_opclass_channel(opclass, band, channel, 20); + channel_entry = wifi_opclass_find_opclass_channel(opclass, band, channel, bws[i]); if (!channel_entry) - return -1; - channel_entry->preference = pref; + continue; + + if (pref > 1) + channel_entry->preference = (pref--) << 4; + else + channel_entry->preference = pref << 4; } return 0; @@ -1426,11 +1453,13 @@ int wifi_radio_opclass_update_channel(struct wifi_radio_opclass *opclass, uint8_ int wifi_opclass_get_max_bw(struct wifi_radio_opclass *opclass, uint8_t *ctrl_channel, uint32_t *bw, - uint8_t *id) + uint8_t *id, + uint8_t *channel) { struct wifi_radio_opclass_entry *entry, *max = NULL; uint32_t max_bw = 0; uint8_t ctrl_chan = 0; + uint8_t chan = 0; int i; for (i = 0; i < opclass->num_opclass; i++) { @@ -1443,6 +1472,7 @@ int wifi_opclass_get_max_bw(struct wifi_radio_opclass *opclass, if (entry->bandwidth > max_bw) { max_bw = entry->bandwidth; max = entry; + chan = entry->channel[0].channel; } } @@ -1457,5 +1487,60 @@ int wifi_opclass_get_max_bw(struct wifi_radio_opclass *opclass, if (ctrl_channel) *ctrl_channel = ctrl_chan; + if (channel) + *channel = chan; + return 0; } + +uint32_t wifi_opclass_highest_bandwidth(struct wifi_radio_opclass *opclass, bool available_only) +{ + struct wifi_radio_opclass_entry *entry; + struct wifi_radio_opclass_channel *chan; + uint32_t bandwidth = 0; + uint8_t pref = 0; + int i, j; + + for (i = 0; i < opclass->num_opclass; i++) { + entry = &opclass->opclass[i]; + + if (!entry->num_channel) + continue; + + for (j = 0; j < entry->num_channel; j++) { + chan = &entry->channel[j]; + + /* Not supported */ + pref = (chan->preference & CHANNEL_PREF_MASK) >> 4; + if (!pref) + continue; + + if (entry->band != 5) + break; + + if (chan->dfs == WIFI_RADIO_OPCLASS_CHANNEL_DFS_NONE) + break; + + /* Check if we pass CAC */ + if (available_only && + chan->dfs != WIFI_RADIO_OPCLASS_CHANNEL_DFS_AVAILABLE) + continue; + + /* Skip NOP */ + if (chan->dfs == WIFI_RADIO_OPCLASS_CHANNEL_DFS_NOP || + chan->dfs == WIFI_RADIO_OPCLASS_CHANNEL_DFS_CAC) + continue; + + break; + } + + /* Not found */ + if (j == entry->num_channel) + continue; + + if (entry->bandwidth > bandwidth) + bandwidth = entry->bandwidth; + } + + return bandwidth; +} diff --git a/src/wifi_opclass.h b/src/wifi_opclass.h index 29220902..ca2366f4 100644 --- a/src/wifi_opclass.h +++ b/src/wifi_opclass.h @@ -23,11 +23,13 @@ int wifi_opclass_add_entry(struct wifi_radio_opclass *opclass, struct wifi_radio bool wifi_opclass_expired(struct wifi_radio_opclass *opclass, uint32_t seconds); void wifi_opclass_reset(struct wifi_radio_opclass *opclass); void wifi_opclass_dump(struct wifi_radio_opclass *opclass, const char *name, uint8_t *radio); +void wifi_opclass_dump_ex(struct wifi_radio_opclass *opclass, const char *name, uint8_t *radio, bool full); uint8_t wifi_opclass_get_id(struct wifi_radio_opclass *opclass, uint8_t channel, int bandwidth); void wifi_opclass_set_preferences(struct wifi_radio_opclass *opclass, uint8_t preference); int wifi_opclass_get_higest_preference(struct wifi_radio_opclass *opclass, int bandwith, uint8_t *opclass_id, uint8_t *channel); +uint8_t wifi_get_best_ctrl_channel(struct wifi_radio_opclass *opclass, const uint8_t *channels, int channels_num); bool wifi_opclass_id_supported(struct wifi_radio_opclass *opclass, uint8_t id); uint8_t wifi_opclass_num_supported(struct wifi_radio_opclass *opclass); bool wifi_opclass_cac_required(struct wifi_radio_opclass *opclass, @@ -54,6 +56,7 @@ uint8_t wifi_opclass_find_id_from_channel(struct wifi_radio_opclass *opclass, int ctrl_channel, int bandwidth); bool wifi_opclass_id_same_preference(struct wifi_radio_opclass *opclass, uint8_t id, uint8_t *pref); +bool wifi_opclass_same_preference(struct wifi_radio_opclass *opclass, uint8_t *preferences); bool wifi_opclass_max_preference(uint8_t preference); bool wifi_opclass_is_channel_dfs_available(struct wifi_radio_opclass_channel *chan); bool wifi_opclass_is_channel_dfs_nop(struct wifi_radio_opclass_channel *chan); @@ -67,9 +70,11 @@ struct wifi_radio_opclass_channel *wifi_opclass_get_channel(struct wifi_radio_op struct wifi_radio_opclass_channel *wifi_opclass_get_ctrl_channel(struct wifi_radio_opclass *opclass, uint8_t id, uint8_t channel); bool wifi_opclass_has_channel(uint8_t id, uint8_t channel); int wifi_radio_opclass_update_channel(struct wifi_radio_opclass *opclass, uint8_t band, - uint32_t channel, uint32_t bw, uint32_t pref); + uint32_t channel, uint32_t bw, uint8_t pref); int wifi_opclass_get_max_bw(struct wifi_radio_opclass *opclass, uint8_t *ctrl_channel, uint32_t *bw, - uint8_t *id); + uint8_t *id, + uint8_t *channel); +uint32_t wifi_opclass_highest_bandwidth(struct wifi_radio_opclass *opclass, bool available_only); #endif /* _WIFI_RADIO_OPCLASS_H_ */ -- GitLab