Newer
Older
* cntlr.c - Multi-AP controller
* Copyright (C) 2020-2022 IOPSYS Software Solutions AB. All rights reserved.
* See LICENSE file for source code license information.
#include <1905_tlvs.h>
#include <easymesh.h>
#include <errno.h>
#include <easy/easy.h>
#include <inttypes.h>
#include <libubox/blob.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubus.h>
#include <cmdu_ackq.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "acs.h"
#include "cntlr_cmdu.h"
#include "cntlr_map.h"
#include "cntlr_ubus.h"
#include "config.h"
#if (EASYMESH_VERSION >= 3)
#include "libubox/blobmsg.h"
#include "libubox/list.h"
#include "mactable.h"
#include "sta.h"
#include "stdbool.h"
#include "timer_impl.h"
#include "utils/debug.h"
#include "utils/utils.h"
#include "wifi_dataelements.h"
#include "steer.h"
extern bool waitext;
struct netif_iface *cntlr_find_iface(struct controller *c, uint8_t *macaddr)
struct macaddr_entry *entry = NULL;
entry = mactable_lookup(c->mac_table, macaddr, MAC_ENTRY_FBSS);
return NULL;
return (struct netif_iface *)entry->data;
struct netif_iface *cntlr_find_iface_type(struct controller *c, uint8_t *macaddr,
{
struct macaddr_entry *entry = NULL;
entry = mactable_lookup(c->mac_table, macaddr, type);
if (WARN_ON(!entry))
return NULL;
return (struct netif_iface *)entry->data;
}
struct netif_iface *cntlr_find_fbss(struct controller *c, uint8_t *macaddr)
{
return cntlr_find_iface_type(c, macaddr, MAC_ENTRY_FBSS);
}
struct netif_iface *cntlr_find_bbss(struct controller *c, uint8_t *macaddr)
{
return cntlr_find_iface_type(c, macaddr, MAC_ENTRY_BBSS);
}
struct netif_iface *cntlr_find_bss(struct controller *c, uint8_t *macaddr)
{
return cntlr_find_iface_type(c, macaddr, MAC_ENTRY_FBSS | MAC_ENTRY_BBSS);
}
struct netif_radio *cntlr_find_radio(struct controller *c, uint8_t *macaddr)
struct macaddr_entry *entry = NULL;
entry = mactable_lookup(c->mac_table, macaddr, MAC_ENTRY_RADIO);
if (WARN_ON(!entry))
return NULL;
return (struct netif_radio *)entry->data;
struct node *cntlr_find_node(struct controller *c, uint8_t *macaddr)
struct macaddr_entry *entry = NULL;
entry = mactable_lookup(c->mac_table, macaddr, MAC_ENTRY_ALID);
return NULL;
return (struct node *)entry->data;
/* finds radio struct from interface macaddr */
struct netif_radio *cntlr_find_radio_with_bssid(struct controller *c, uint8_t *bssid)
struct node *n = NULL;
struct netif_radio *r = NULL;
struct netif_iface *p = NULL;
list_for_each_entry(n, &c->nodelist, list) {
list_for_each_entry(r, &n->radiolist, list) {
list_for_each_entry(p, &r->iflist, list) {
if (!memcmp(p->bss->bssid, bssid, 6))
return r;
}
}
}
return NULL;
}
struct netif_radio *cntlr_find_radio_in_node_by_band(struct controller *c, struct node *n,
enum wifi_band band)
{
struct netif_radio *r = NULL;
list_for_each_entry(r, &n->radiolist, list) {
if (r->radio_el->band == band)
return r;
}
return NULL;
}
struct netif_link *cntlr_find_link(struct controller *c, uint8_t *ul_macaddr, uint8_t *dl_macaddr)
list_for_each_entry(l, &c->linklist, list) {
if (!memcmp(l->upstream->bss->bssid, ul_macaddr, 6)
&& !memcmp(l->downstream->bss->bssid, dl_macaddr, 6))
return l;
}
return NULL;
}
int cntlr_get_node_assoclist(struct controller *c, uint8_t *node_alid,
int *num_sta, uint8_t *sta_macaddrs)
{
struct sta *s = NULL;
int limit = *num_sta;
int ret = 0;
int i = 0;
*num_sta = 0;
n = cntlr_find_node(c, node_alid);
if (!n)
return -1;
list_for_each_entry(s, &n->stalist, list) {
if (i < limit)
memcpy(&sta_macaddrs[i*6], s->macaddr, 6);
}
*num_sta = i;
return ret;
}
struct bcnreq *cntlr_find_bcnreq(struct controller *c, uint8_t *sta, uint8_t *almacaddr)
list_for_each_entry(br, &c->bcnreqlist, list) {
if (!memcmp(br->sta_mac, sta, 6) && !memcmp(br->agent_mac, almacaddr, 6))
return br;
}
return NULL;
}
/* find node based on bssid */
struct node *cntlr_find_node_with_bssid(struct controller *c, uint8_t *bssid)
list_for_each_entry(n, &c->nodelist, list) {
struct netif_radio *r = NULL;
list_for_each_entry(r, &n->radiolist, list) {
struct netif_iface *p = NULL;
list_for_each_entry(p, &r->iflist, list) {
if (!memcmp(p->bss->bssid, bssid, 6))
}
}
}
return NULL;
}
/* returns first found node with given STA MAC on its stalist */
struct node *cntlr_find_node_with_sta(struct controller *c, uint8_t *stamacaddr)
{
struct node *n = NULL;
list_for_each_entry(n, &c->nodelist, list) {
struct sta *e = NULL;
list_for_each_entry(e, &n->stalist, list) {
if (!memcmp(e->macaddr, stamacaddr, 6))
return n;
}
}
return NULL;
}
#if (EASYMESH_VERSION >= 6)
Jakob Olsson
committed
bool cntlr_radio_support_ap_wifi7(struct wifi7_radio_capabilities *wifi7_caps)
{
if (wifi7_caps->ap.str_support || wifi7_caps->ap.nstr_support ||
wifi7_caps->ap.emlsr_support || wifi7_caps->ap.emlmr_support)
return true;
return false;
}
Jakob Olsson
committed
bool cntlr_radio_support_bsta_wifi7(struct wifi7_radio_capabilities *wifi7_caps)
{
if (wifi7_caps->bsta.str_support || wifi7_caps->bsta.nstr_support ||
wifi7_caps->bsta.emlsr_support || wifi7_caps->bsta.emlmr_support)
return true;
return false;
}
Jakob Olsson
committed
bool cntlr_node_support_ap_wifi7(struct node *n)
{
struct netif_radio *r = NULL;
Jakob Olsson
committed
list_for_each_entry(r, &n->radiolist, list) {
if (cntlr_radio_support_ap_wifi7(&r->wifi7_caps))
return true;
}
return false;
}
#endif
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
int cntlr_update_txlink_metric_data(struct controller *c, struct tlv_tx_linkmetric *txl, int len)
{
int i = 0;
int size = 0;
struct tx_link_info *txlinfo;
struct netif_link *txlink;
struct link_metrics *metrics;
/* For each tx link in the mesg */
size = len - (sizeof(struct tlv_tx_linkmetric));
size = size / (sizeof(struct tx_link_info));
for (i = 0; i < size; i++) {
txlinfo = (struct tx_link_info *)&txl->link[i];
/* Find or alloc the backhaul link to the controller */
txlink = cntlr_alloc_link(c, txlinfo->local_macaddr, txlinfo->neighbor_macaddr);
if (!txlink) {
trace("No link!\n");
return -1;
}
metrics = txlink->metrics;
metrics->l = txlink;
memcpy(metrics->l->upstream->bss->bssid, txlinfo->local_macaddr, 6);
memcpy(metrics->l->downstream->bss->bssid, txlinfo->neighbor_macaddr, 6);
metrics->type = BUF_GET_BE16(txlinfo->mediatype);
metrics->bridge = txlinfo->has_bridge;
metrics->packet_tx_error = BUF_GET_BE32(txlinfo->errors);
metrics->packet_trans = BUF_GET_BE32(txlinfo->packets);
metrics->thp = BUF_GET_BE16(txlinfo->max_throughput);
metrics->link_av = BUF_GET_BE16(txlinfo->availability);
metrics->phy_rate = BUF_GET_BE16(txlinfo->phyrate);
}
c->num_tx_links += size;
return 0;
}
int cntlr_update_rxlink_metric_data(struct controller *c, struct tlv_rx_linkmetric *rxl, int len)
{
int i = 0;
int size = 0;
struct rx_link_info *rxlinfo;
struct netif_link *rxlink;
struct link_metrics *metrics;
/* For each rx link in the mesg */
size = len - (sizeof(struct tlv_rx_linkmetric));
size = size / (sizeof(struct rx_link_info));
for (i = 0; i < size; i++) {
rxlinfo = (struct rx_link_info *)&rxl->link[i];
/* Find or alloc the backhaul link to the controller */
rxlink = cntlr_alloc_link(c, rxlinfo->local_macaddr, rxlinfo->neighbor_macaddr);
if (!rxlink) {
trace("No link!\n");
return 0;
}
metrics = rxlink->metrics;
metrics->l = rxlink;
memcpy(metrics->l->upstream->bss->bssid, rxlinfo->local_macaddr, 6);
memcpy(metrics->l->downstream->bss->bssid, rxlinfo->neighbor_macaddr, 6);
metrics->type = BUF_GET_BE16(rxlinfo->mediatype);
metrics->packet_rec = BUF_GET_BE32(rxlinfo->packets);
metrics->packet_rx_error = BUF_GET_BE32(rxlinfo->errors);
metrics->rssi = rxlinfo->rssi;
}
c->num_rx_links += size;
return 0;
}
struct node *cntlr_add_node(struct controller *c, uint8_t *almacaddr)
struct cmdu_buff *cmdu;
if (!hwaddr_ntoa(almacaddr, mac_str))
n = cntlr_find_node(c, almacaddr);
n = cntlr_alloc_node(c, almacaddr);
if (!n) {
err("|%s:%d| failed to allocate node "MACFMT"\n",
__func__, __LINE__, MAC2STR(almacaddr));
return NULL;
}
} else {
return n;
}
ret = cntlr_config_add_node(&c->cfg, mac_str);
if (!ret) {
dbg("|%s:%d| resync config\n", __func__, __LINE__);
cntlr_resync_config(c, true);
}
/* update the timestamp of the node */
if (n)
timestamp_update(&n->last_tsp_seen);
if (!hwaddr_equal(c->almacaddr, almacaddr))
cntlr_sync_dyn_controller_config(c, almacaddr);
cntlr_qos_sync_node(c, n->almacaddr);
cmdu = cntlr_gen_bk_caps_query(c, n->almacaddr);
if (!cmdu)
return n;
send_cmdu(c, cmdu);
cmdu_free(cmdu);
cmdu = cntlr_gen_ap_capability_query(c, n->almacaddr);
if (!cmdu)
return n;
send_cmdu(c, cmdu);
cmdu_free(cmdu);
int i = 0;
list_for_each_entry(n, &c->nodelist, list) {
cntlr_dbg(LOG_MISC,
" %d | agent = %p, hwaddr = '"MACFMT"')\n",
i++, n, MAC2STR(n->almacaddr));
void cntlr_send_max_wifi_bh_hops_policy(struct controller *c)
struct node *node = NULL;
if (c->cfg.max_node_bh_hops == 0)
return;
trace("%s: max_node_bh_hops %d\n",
__func__, c->cfg.max_node_bh_hops);
list_for_each_entry(node, &c->nodelist, list) {
enum bh_control {
BLOCK = 0x00,
UNBLOCK = 0x01
} bh_control = UNBLOCK;
struct bh_topology_dev *topo_dev = topology_find_device(&c->topology, node->almacaddr);
struct netif_radio *radio = NULL;
if (!topo_dev) {
err("%s: device not found in bh topo model, logical error.\n", __func__);
continue;
}
if (topo_dev->bh.depth == UNKNOWN_TREE_LEVEL) {
warn("%s: Level in BH treee unknown for: " MACFMT "\n", __func__, MAC2STR(node->almacaddr));
if (c->cfg.max_node_bh_hops <= topo_dev->bh.wifi_hops_from_root)
bh_control = BLOCK;
list_for_each_entry(radio, &node->radiolist, list) {
list_for_each_entry(iface, &radio->iflist, list) {
if (iface->bss->is_bbss) {
const uint8_t NO_VALIDITY_PERIOD = 0;
uint8_t ALL_BSTAS[6] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
uint16_t mid;
trace("%s: sending BH assoc_mode %s, node al_mac: " MACFMT
", wifi_hops_from_root: %d, bssid: " MACFMT"\n",
__func__,
bh_control ? "UNBLOCK" : "BLOCK",
MAC2STR(node->almacaddr),
topo_dev->bh.wifi_hops_from_root,
MAC2STR(iface->bss->bssid));
cntlr_send_client_assoc_ctrl_request(
c, node->almacaddr, iface->bss->bssid,
bh_control, NO_VALIDITY_PERIOD, 1,
ALL_BSTAS, &mid);
}
}
}
}
}
void cntlr_bcn_metrics_timer_cb(atimer_t *t)
{
struct sta *s = container_of(t, struct sta, bcn_metrics_timer);
struct controller *c = s->cntlr;
cntlr_dbg(LOG_STA, "%s: No Beacon Report from STA " MACFMT" yet.\n",
if (!s->bcn_response_tmo) {
s->bcn_response_tmo = true;
timer_set(&s->bcn_metrics_timer, 30 * 1000);
cntlr_dbg(LOG_STA, "%s: Wait for 30s more.\n", __func__);
} else {
cntlr_del_bcnreq(c, s->macaddr, s->agent_almacaddr);
s->bcn_response_tmo = false;
}
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
static void cntlr_freeze_sta(struct controller *c, struct sta *s)
{
timer_del(&s->bcn_metrics_timer);
timer_del(&s->btm_req_timer);
timer_del(&s->ageout_timer);
sta_free_assoc_frame(s);
sta_free_bcn_metrics(s);
cntlr_clean_bcnreqlist_sta(c, s);
}
static void cntlr_remove_sta(struct controller *c, struct sta *s)
{
struct node *n = NULL;
do {
n = cntlr_find_node_with_sta(c, s->macaddr);
if (n)
node_del_sta(n, s);
} while (n);
cntlr_freeze_sta(c, s);
cntlr_del_sta(c->sta_table, s);
}
void cntlr_sta_ageout_timer_cb(atimer_t *t)
{
trace("%s: --->\n", __func__);
struct sta *s = container_of(t, struct sta, ageout_timer);
struct controller *c = s->cntlr;
if (!c)
return;
cntlr_dbg(LOG_STA, "%s: Delete STA " MACFMT " after %ds of disassociation\n",
__func__, MAC2STR(s->macaddr), c->cfg.stale_sta_timeout);
cntlr_remove_sta(c, s);
}
int node_add_sta(struct node *n, struct sta *s)
if (WARN_ON(node_find_sta(n, s->macaddr))) {
cntlr_dbg(LOG_STA,
"%s: Warn! STA " MACFMT " already in node " MACFMT"\n",
__func__, MAC2STR(s->macaddr), MAC2STR(n->almacaddr));
memcpy(s->agent_almacaddr, n->almacaddr, 6);
list_add(&s->list, &n->stalist);
n->sta_count++;
int node_del_sta(struct node *n, struct sta *s)
if (WARN_ON(!node_find_sta(n, s->macaddr))) {
cntlr_dbg(LOG_STA,
"%s: Warn! STA " MACFMT " not in node " MACFMT"\n",
__func__, MAC2STR(s->macaddr), MAC2STR(n->almacaddr));
list_del(&s->list);
n->sta_count--;
struct sta *node_find_sta(struct node *n, uint8_t *macaddr)
{
struct sta *s = NULL;
list_for_each_entry(s, &n->stalist, list) {
if (!memcmp(s->macaddr, macaddr, 6))
return s;
}
return NULL;
}
void node_update_stalist(struct node *n, uint8_t *stalist, int num)
{
struct sta *s = NULL, *tmp;
list_for_each_entry_safe(s, tmp, &n->stalist, list) {
bool found = false;
for (int i = 0; i < num; i++) {
if (!memcmp(s->macaddr, &stalist[i*6], 6)) {
found = true;
break;
}
}
if (!found) {
list_del(&s->list);
n->sta_count--;
struct unassoc_sta_metrics *cntlr_find_usta_metric(struct controller *c,
struct sta *s,
uint8_t *agent_macaddr)
struct unassoc_sta_metrics *u = NULL;
list_for_each_entry(u, &s->de_sta->umetriclist, list) {
if (!memcmp(u->agent_macaddr, agent_macaddr, 6))
return u;
}
struct cmdu_buff *cntlr_query_sta_metric(struct controller *c, struct sta *s)
{
if (!c || !s)
return NULL;
if (hwaddr_is_zero(s->agent_almacaddr)) {
cntlr_warn(LOG_STA, "%s: Agent macaddr is zero\n", __func__);
return cntlr_gen_sta_metric_query(c, s->agent_almacaddr, s->macaddr);
static void cntlr_get_all_sta_metrics(struct controller *c)
list_for_each_entry(n, &c->nodelist, list) {
struct sta *s = NULL;
list_for_each_entry(s, &n->stalist, list) {
struct cmdu_buff *cmdu;
cmdu = cntlr_query_sta_metric(c, s);
if (!cmdu)
continue;
send_cmdu(c, cmdu);
cmdu_free(cmdu);
}
struct wifi_bss_element *cntlr_alloc_wifi_bss(struct controller *c,
uint8_t *macaddr)
{
struct wifi_bss_element *bss = NULL;
bss = calloc(1, sizeof(struct wifi_bss_element));
if (!bss)
return NULL;
memcpy(bss->bssid, macaddr, 6);
bss->sta_assoc_allowed = true;
return bss;
}
struct netif_iface *cntlr_radio_add_iface(struct controller *c,
struct netif_radio *r,
n = cntlr_find_iface(c, macaddr);
n->bss->enabled = true;
n = calloc(1, sizeof(*n));
if (!n)
return NULL;
n->bss = cntlr_alloc_wifi_bss(c, macaddr);
n->band = wifi_opclass_get_band(r->radio_el->cur_opclass.opclass[0].id);
if (n->band == BAND_UNKNOWN) {
int ret;
ret = cntlr_config_add_node_radio(&c->cfg, r->agent->almacaddr, r->radio_el->macaddr, n->band);
if (!ret)
cntlr_resync_config(c, true);
}
n->bss->is_fbss = true;
n->bss->is_bbss = false;
n->bss->enabled = true;
list_add(&n->list, &r->iflist);
n->agent = r->agent;
#if (EASYMESH_VERSION >= 6)
/* send channel selection request to propagate puncture bitmap */
cntlr_send_channel_selection(c, r->agent->almacaddr, r->radio_el->macaddr, NULL);
mactable_add_entry(c->mac_table, macaddr, MAC_ENTRY_FBSS, (void *)n);
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;
radio_el = calloc(1, sizeof(struct wifi_radio_element));
if (!radio_el)
return NULL;
INIT_LIST_HEAD(&radio_el->scanlist);
radio_el->acs = cntlr_radio_acs_alloc(c, n, r);
struct netif_radio *cntlr_node_add_radio(struct controller *c, struct node *n,
r = cntlr_find_radio(c, macaddr);
r = calloc(1, sizeof(*r));
if (!r)
return NULL;
r->radio_el = cntlr_alloc_wifi_radio(c, n, r);
if (!r->radio_el) {
free(r);
return NULL;
}
INIT_LIST_HEAD(&r->iflist);
list_add(&r->list, &n->radiolist);
r->agent = n;
memcpy(r->radio_el->macaddr, macaddr, 6);
mactable_add_entry(c->mac_table, macaddr, MAC_ENTRY_RADIO, (void *)r);
cntlr_send_channel_selection(c, n->almacaddr, macaddr, NULL);
p = cntlr_get_radio_policy(&c->cfg, macaddr);
if (p)
r->radio_el->band = p->band;
int cntlr_radio_get_beacon_channel(struct wifi_radio_element *radio,
uint8_t *opclass, uint8_t *channel)
{
struct wifi_radio_opclass *curr = &radio->cur_opclass;
*channel = 0;
*opclass = 0;
for (int i = 0; i < curr->num_opclass; i++) {
struct wifi_radio_opclass_entry *e = &curr->opclass[i];
if (e->bandwidth == 20) {
*opclass = e->id;
*channel = e->channel[0].channel;
return 0;
}
}
return -1;
}
struct node *cntlr_alloc_node(struct controller *c, uint8_t *almacaddr)
n = calloc(1, sizeof(struct node));
if (!n) {
warn("OOM: node malloc failed!\n");
return NULL;
n->cntlr = c;
n->depth = -1;
n->scan_supported = true;
n->np = NULL; //c->cfg.apolicy;
memcpy(n->almacaddr, almacaddr, 6);
n->map_profile = MULTIAP_PROFILE_1;
INIT_LIST_HEAD(&n->radiolist);
list_add(&n->list, &c->nodelist);
mactable_add_entry(c->mac_table, almacaddr, MAC_ENTRY_ALID, (void *)n);
dbg("%s %d --------- " MACFMT "\n", __func__, __LINE__, MAC2STR(almacaddr));
uint32_t cntlr_estimate_max_throughput_for_node(struct controller *c, uint8_t *node_almacaddr)
{
const struct backhaul_info *b = NULL;
const struct bh_topology_dev *dev;
uint32_t est_thput = 0xffffffff;
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
dev = topology_find_device(&c->topology, node_almacaddr);
if (!dev)
return est_thput;
while (dev->bh.parent) {
b = &dev->bh;
if (b->own_iface) {
bool is_wifi = !!(b->own_iface->media_type & 0x0100);
struct sta *s = NULL;
if (!is_wifi) {
dev = b->parent;
continue;
}
s = cntlr_find_sta(c->sta_table, (uint8_t *)b->own_iface->macaddr);
if (s && s->is_bsta && s->de_sta) {
if (s->de_sta->dl_est_thput < est_thput)
est_thput = s->de_sta->dl_est_thput;
}
}
dev = b->parent;
}
if (est_thput != 0xffffffff)
est_thput /= 2; /* TODO: MLO link */
return est_thput;
}
void cntlr_clean_bcnreqlist(struct controller *c)
{
struct bcnreq *b = NULL, *tmp;
list_for_each_entry_safe(b, tmp, &c->bcnreqlist, list) {
list_del(&b->list);
free(b);
}
}
void cntlr_clean_bcnreqlist_sta(struct controller *c, struct sta *s)
{
dbg("%s: --->\n", __func__);
struct bcnreq *b = NULL, *tmp;
list_for_each_entry_safe(b, tmp, &c->bcnreqlist, list) {
if (!memcmp(b->sta_mac, s->macaddr, 6)) {
dbg("%s Remove Beacon Request for STA " MACFMT "\n",
__func__, MAC2STR(b->sta_mac));
list_del(&b->list);
free(b);
}
}
}
void cntlr_clean_linklist(struct controller *c)
{
struct netif_link *l = NULL, *tmp;
list_for_each_entry_safe(l, tmp, &c->linklist, list) {
free(l->metrics);
list_del(&l->list);
free(l);
}
static void radio_clean_iflist(struct controller *c, struct netif_radio *r)
struct netif_link *l = NULL, *temp;
list_for_each_entry_safe(ni, tmp, &r->iflist, list) {
/* Here we need to clean the linklist */
list_for_each_entry_safe(l, temp, &c->linklist, list) {
if (l->upstream == ni || l->downstream == ni) {
free(l->metrics);
list_del(&l->list);
free(l);
}
}
mactable_del_entry(c->mac_table, ni->bss->bssid, MAC_ENTRY_FBSS);
static void radio_clean_radio_el(struct netif_radio *r)
{
struct wifi_scanres_element *b = NULL, *tmp;
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);
}
static void cntlr_clean_radiolist(struct controller *c, struct node *n)
{
struct netif_radio *r = NULL, *tmp;
list_for_each_entry_safe(r, tmp, &n->radiolist, list) {
mactable_del_entry(c->mac_table, r->radio_el->macaddr, MAC_ENTRY_RADIO);
radio_clean_radio_el(r);
list_del(&r->list);
free(r);
}
}
static void node_clean_stalist(struct controller *c, struct node *n)
{
struct sta *s = NULL, *tmp;
if (!c || !n)
return;
list_for_each_entry_safe(s, tmp, &n->stalist, list) {
if (WARN_ON(n->sta_count != 0)) {
err("%s: Misalligned station counter (cnt=%d)!\n",
__func__, n->sta_count);
static void cntlr_clean_mac_hashtable(struct controller *c)
{
mactable_flush(c->mac_table);
static void cntlr_clean_all_sta(struct controller *c)
{
struct hlist_node *tmp = NULL;
int i;
for (i = 0; i < MAC_HASHTABLE_SIZE; i++) {
struct sta *s = NULL;
if (hlist_empty(&c->sta_table[i]))
continue;
hlist_for_each_entry_safe(s, tmp, &c->sta_table[i], hlist) {
hlist_del(&s->hlist, &c->sta_table[i]);
cntlr_freeze_sta(c, s);
cntlr_free_sta(s);
}
}
c->num_sta = 0;
}
static void cntlr_clean_nodelist(struct controller *c)
{
struct node *n = NULL, *tmp;
list_for_each_entry_safe(n, tmp, &c->nodelist, list) {
cntlr_clean_radiolist(c, n);
struct netif_link *cntlr_alloc_link(struct controller *c,
uint8_t *upstream, uint8_t *downstream)
{