/* * cntlr_acs.c - Auto Channel Selection * * Copyright (C) 2021 IOPSYS Software Solutions AB. All rights reserved. * */ #include "acs.h" #include <easymesh.h> #include <stddef.h> #include <stdlib.h> #include <string.h> #include <easy/easy.h> #include <libubox/list.h> #include <wifidefs.h> #include <time.h> #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) { /* * Build initial preferred opclasses from supported opclasses * we receive in basic radio capabilities. */ 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) { struct wifi_radio_opclass_entry *entry; struct wifi_radio_opclass_channel *chan; struct wifi_radio_opclass_channel new_chan = {}; struct wifi_radio_opclass *opclass; opclass = &radio->pref_opclass; entry = wifi_opclass_find_entry(opclass, classid); if (!entry) return -1; entry->id = classid; entry->bandwidth = wifi_opclass_get_bw(classid); new_chan.channel = channel; new_chan.preference = preference; new_chan.dfs = dfs_state_from_preference(preference); timestamp_update(&opclass->entry_time); /* Don't clean cac_methods/cac_time */ chan = wifi_opclass_find_channel(entry, channel); 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); } void cntlr_radio_cur_opclass_reset(struct wifi_radio_element *radio) { wifi_opclass_reset(&radio->cur_opclass); } int cntlr_radio_cur_opclass_add(struct wifi_radio_element *radio, uint8_t classid, uint8_t channel, uint8_t txpower) { struct wifi_radio_opclass_entry *entry; struct wifi_radio_opclass_channel chan = {}; entry = wifi_opclass_find_entry(&radio->cur_opclass, classid); if (!entry) entry = wifi_opclass_new_entry(&radio->cur_opclass); if (!entry) return -1; entry->id = classid; entry->bandwidth = wifi_opclass_get_bw(classid); entry->max_txpower = txpower; chan.channel = channel; chan.preference = 15 << 4; timestamp_update(&radio->cur_opclass.entry_time); return wifi_opclass_add_channel(entry, &chan); } uint8_t ctrl_radio_cur_opclass_id(struct wifi_radio_element *radio) { uint8_t id = 0; int ret; ret = wifi_opclass_get_attr(&radio->cur_opclass, NULL, NULL, &id, NULL); if (ret) return 0; return id; } uint8_t ctrl_radio_cur_opclass_ctrl_chan(struct wifi_radio_element *radio) { uint8_t ctrl_chan = 0; int ret; ret = wifi_opclass_get_attr(&radio->cur_opclass, &ctrl_chan, NULL, NULL, NULL); if (ret) return 0; return ctrl_chan; } uint8_t ctrl_radio_cur_opclass_chan(struct wifi_radio_element *radio) { uint8_t chan = 0; int ret; ret = wifi_opclass_get_attr(&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_attr(&radio->cur_opclass, NULL, &bw, NULL, NULL); if (ret) return 0; return bw; } void cntlr_radio_pref_opclass_set_pref(struct wifi_radio_element *radio, uint8_t id, uint8_t preference) { wifi_opclass_id_set_preferences(&radio->pref_opclass, id, preference); } 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, int age) { return wifi_opclass_expired(&radio->pref_opclass, age); } 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, 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 */ uint8_t tx; /* tx self */ uint8_t rx; /* rx self */ }; struct acs_radio_metrics { struct timespec time; uint8_t anpi; /* EWMA value */ uint8_t obss; /* EWMA value */ uint8_t tx; /* EWMA value */ uint8_t rx; /* 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, bool fast) { struct wifi_radio_opclass_entry *entry; struct wifi_radio_opclass *opclass; struct scan_req_data req = {}; enum wifi_band band; struct cmdu_buff *cmdu; uint16_t mid = 0xffff; int num = 0; int i; cntlr_dbg(LOG_CHANNEL, "acs node " MACFMT " " MACFMT " - scan request (%s)\n", MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr), fast ? "short": "full"); 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); band = r->radio_el->band; 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; if (fast) { if (band == BAND_2 && entry->id == 81) { req.radios[0].opclasses[num].channels[0] = 1; req.radios[0].opclasses[num].channels[1] = 6; req.radios[0].opclasses[num].channels[2] = 11; req.radios[0].opclasses[num].num_channel = 3; } if (band == BAND_5 && entry->id == 115) { req.radios[0].opclasses[num].channels[0] = 36; req.radios[0].opclasses[num].num_channel = 1; } if (band == BAND_5 && entry->id == 118) { req.radios[0].opclasses[num].channels[0] = 52; req.radios[0].opclasses[num].num_channel = 1; } if (band == BAND_5 && entry->id == 121) { req.radios[0].opclasses[num].channels[0] = 100; req.radios[0].opclasses[num].channels[1] = 116; req.radios[0].opclasses[num].num_channel = 2; } if (band == BAND_5 && entry->id == 124) { req.radios[0].opclasses[num].channels[0] = 149; req.radios[0].opclasses[num].num_channel = 1; } if (band == BAND_5 && entry->id == 125) continue; if (band == BAND_6 && entry->id == 131) { /* scan PSC channels */ req.radios[0].opclasses[num].channels[0] = 5; req.radios[0].opclasses[num].channels[1] = 21; req.radios[0].opclasses[num].channels[2] = 37; req.radios[0].opclasses[num].channels[3] = 53; req.radios[0].opclasses[num].channels[4] = 69; req.radios[0].opclasses[num].channels[5] = 85; req.radios[0].opclasses[num].channels[6] = 101; req.radios[0].opclasses[num].channels[7] = 117; req.radios[0].opclasses[num].channels[8] = 133; req.radios[0].opclasses[num].channels[9] = 149; req.radios[0].opclasses[num].channels[10] = 165; req.radios[0].opclasses[num].channels[11] = 181; req.radios[0].opclasses[num].channels[12] = 197; req.radios[0].opclasses[num].num_channel = 13; } } 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_CONNECTED) 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)); /* * 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] = {}; 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; 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]; /* First check if opclass set */ if (params->opclass && params->opclass != entry->id) continue; /* Next check if bandwidth set */ if (params->bw && params->bw != entry->bandwidth) continue; for (j = 0; j < entry->num_channel; j++) { 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; 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) continue; 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 || reas == CHANNEL_PREF_REASON_DFS_USABLE) continue; } /* Skip non available DFS channels if requested */ 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; /* Kick best value */ if (pref > pref_best) pref_best = pref; acs_params[acs_params_num].best_channel = chan; 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) { cntlr_dbg(LOG_CHANNEL, "acs radio channel recalc " MACFMT " - no pref best - skip recalc\n", MAC2STR(radio->macaddr)); 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_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 */ for (i = 0, j = 0; i < acs_params_num; i++) { if (acs_params[i].best_pref != pref_best) continue; if (j >= ARRAY_SIZE(prefered) - 1) break; /* Save index in table */ prefered[j] = i; j++; } if (WARN_ON(!j)) { params->status = ACS_RECALC_STATUS_INPUT_DATA_INSUFFICIENT; goto out; } srandom(time(NULL)); r = random() % j; 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) { 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); out: memcpy(last_acs, params, sizeof(*last_acs)); timestamp_update(&last_acs->entry_time); return params->status != ACS_RECALC_STATUS_BEST_SELECTED; } static int cntlr_get_current_acs_params(struct wifi_radio_element *radio, struct acs_params *params) { memset(params, 0, sizeof(*params)); if (!radio->cur_opclass.num_opclass) return -1; params->opclass = ctrl_radio_cur_opclass_id(radio); params->bw = ctrl_radio_cur_opclass_max_bw(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, 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 " 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) { 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)); acs_params.skip_dfs = skip_dfs; acs_params.skip_dfs_not_available = prevent_cac; /* 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; 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_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, 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; } /* 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); ret = cntlr_send_channel_selection(node->cntlr, node->almacaddr, 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; } 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); } } 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 { struct acs_radio_metrics *m; bool fast = false; cntlr_dbg(LOG_CHANNEL, "acs timer recalc node (scan) " MACFMT " " MACFMT "\n", MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr)); m = cntlr_radio_get_metrics(r); if (m && (m->tx || m->rx)) fast = true; /* Send scan request */ last_acs->recalc = true; last_acs->scan_mid = cntlr_acs_send_scan_request(n, r, fast); 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) { 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; /* allow only bgcac methods */ if (methods & (1 << WIFI_CAC_MIMO_REDUCED)) cac_method = CAC_METHOD_MIMO_DIM_REDUCED; else if (methods & (1 << WIFI_CAC_DEDICATED)) cac_method = CAC_METHOD_DEDICATED_RADIO; else if (methods & (1 << WIFI_CAC_TIME_SLICED)) cac_method = CAC_METHOD_TIME_SLICED; return cac_method; } 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; for (i = 0; i < entry->num_channel; i++) { reas = entry->channel[i].preference & CHANNEL_PREF_REASON; if (reas != CHANNEL_PREF_REASON_DFS_USABLE) continue; /* 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->cac_time = entry->channel[i].cac_time; return true; } return false; } static bool cntlr_radio_is_ap_iface(struct netif_radio *radio) { struct netif_iface *iface = NULL; list_for_each_entry(iface, &radio->iflist, list) { /* Check if AP iface connected */ if (iface->bss->is_fbss) return true; if (iface->bss->is_bbss) return true; } return false; } 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; 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]; if (entry->bandwidth != cur_bw) continue; if (!cntlr_dfs_get_usable(entry, cac)) continue; if (cac->data.channel == cur_chan) continue; memcpy(cac->data.radio, radio->macaddr, sizeof(cac->data.radio)); return true; } return false; } void cntlr_dfs_radio_cleanup(struct node *node, struct netif_radio *radio) { 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; } /* 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; } 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_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); } } void cntlr_dfs_cleanup(struct controller *c) { struct node *n = NULL; list_for_each_entry(n, &c->nodelist, list) { 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, uint8_t tx, uint8_t rx) { 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); m->tx = ewma(tx, m->tx, 60); m->rx = ewma(rx, m->rx, 60); last = &m->entry[m->idx]; cntlr_trace(LOG_CHANNEL, "radio ewma metrics " MACFMT " anpi %u obss %u tx %u rx %u\n", MAC2STR(r->radio_el->macaddr), m->anpi, m->obss, m->tx, m->rx); /* Update ring buffer */ if (timestamp_invalid(&last->time)) { timestamp_update(&last->time); last->anpi = m->anpi; last->obss = m->obss; last->tx = m->tx; last->rx = m->rx; } /* 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; next->tx = m->tx; next->rx = m->rx; cntlr_dbg(LOG_CHANNEL, "metrics[%u] radio " MACFMT " anpi %u (%d) obss %u (%d) tx %u (%d) rx %u (%d)\n", m->idx, MAC2STR(r->radio_el->macaddr), next->anpi, next->anpi - last->anpi, next->obss, next->obss - last->obss, next->tx, next->tx - last->tx, next->rx, next->rx - last->rx); /* 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, uint8_t tx, uint8_t rx) { struct acs_params *acs; struct acs_cac_data *cac; cntlr_trace(LOG_CHANNEL, "new radio metrics node " MACFMT " radio " MACFMT " anpi %u busy %u tx %u rx %u\n", MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr), anpi, obss, tx, rx); 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, tx, rx)) 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); }