Skip to content
Snippets Groups Projects
cntlr_acs.c 7.01 KiB
Newer Older
  • Learn to ignore specific revisions
  • Janusz Dziedzic's avatar
    Janusz Dziedzic committed
    /*
     * cntlr_acs.c - Auto Channel Selection
     *
     * Copyright (C) 2021 IOPSYS Software Solutions AB. All rights reserved.
     *
     */
    #include <stdio.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include <libubox/blobmsg.h>
    #include <libubox/blobmsg_json.h>
    #include <libubox/uloop.h>
    #include <libubox/ustream.h>
    #include <libubox/utils.h>
    #include <libubus.h>
    #include <uci.h>
    
    #include <easy/easy.h>
    #include <timer_impl.h>
    #include <cmdu.h>
    #include <1905_tlvs.h>
    #include <map2.h>
    #include <map_module.h>
    
    #include "utils.h"
    #include "debug.h"
    #include "config.h"
    #include "cntlr.h"
    #include "allsta.h"
    #include "cntlr_map.h"
    #include "cntlr_ubus.h"
    #include "cntlr_tlv.h"
    
    #include "cntlr_tlv.h"
    #include "cntlr_cmdu.h"
    #include "cntlr_acs.h"
    
    int cntlr_acs_radio_channel_recalc(struct netif_radio *radio, struct acs_params *params)
    {
    	struct opclass_entry *entry;
    	struct opclass *opclass;
    	int chan, pref, reas;
    	int pref_best = 0;
    	int i, j;
    
    	opclass = &radio->opclass;
    
    	dbg("acs radio channel recalc " MACFMT " opclass %d bw %d skip_dfs %d\n", MAC2STR(radio->macaddr),
    	    params->opclass, params->bw, params->skip_dfs);
    
    	for (i = 0; i < opclass->opclass_entry_num; i++) {
    		entry = &opclass->opclass_entry[i];
    
    		/* First check if opclass set */
    		if (params->opclass && params->opclass != entry->opclass)
    			continue;
    
    		/* Next check if bandwidth set */
    		if (params->bw && params->bw != entry->bw)
    			continue;
    
    		for (j = 0; j < entry->channels_num; j++) {
    			chan = entry->channels[j].channel;
    			pref = (entry->channels[j].preference & CHANNEL_PREF_MASK) >> 4;
    			reas = entry->channels[j].preference & CHANNEL_PREF_REASON;
    
    			trace("\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;
    
    			/* 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_AVAILABLE)
    				continue;
    
    			/* Finally we are here, choose best value */
    			if (pref > pref_best) {
    				pref_best = pref;
    
    				params->best_channel = chan;
    				params->best_opclass = entry->opclass;
    				params->best_bw = entry->bw;
    			}
    		}
    	}
    
    	dbg("acs radio " MACFMT " best chan %d/%d opclass %d\n", MAC2STR(radio->macaddr),
    	    params->best_channel, params->best_bw, params->best_opclass);
    
    	if (!pref_best)
    		return -1;
    
    	return 0;
    }
    
    static bool cntlr_acs_radio_is_bsta_connected(struct netif_radio *radio)
    {
    	struct netif_iface *iface;
    
    	list_for_each_entry(iface, &radio->iflist, list) {
    		/* Check if sta iface connected */
    		if (iface->type != NETIF_BSTA)
    			continue;
    		if (hwaddr_is_zero(iface->upstream_bssid))
    			continue;
    
    		return true;
    	}
    
    	return false;
    }
    
    static int cntlr_get_current_acs_params(struct netif_radio *radio, struct acs_params *params)
    {
    	memset(params, 0, sizeof(*params));
    
    	if (!radio->cur_opclass.opclass_entry_num)
    		return -1;
    
    	params->opclass = radio->cur_opclass.opclass_entry[0].opclass;
    	params->bw = radio->cur_opclass.opclass_entry[0].bw;
    
    	params->best_channel = radio->cur_opclass.opclass_entry[0].channels[0].channel;
    	params->best_bw = params->bw;
    	params->best_opclass = params->opclass;
    
    	return 0;
    }
    
    void cntlr_acs_node_channel_recalc(struct node *node, bool skip_dfs)
    {
    	struct acs_params cur_acs_params = {};
    	struct acs_params acs_params = {};
    	struct netif_radio *radio;
    	int ret;
    
    	acs_params.skip_dfs = skip_dfs;
    
    	dbg("acs node channel recalc " MACFMT " skip_dfs %d\n",
    	    MAC2STR(node->alid), acs_params.skip_dfs);
    
    	list_for_each_entry(radio, &node->radiolist, list) {
    		WARN_ON(cntlr_get_current_acs_params(radio, &cur_acs_params));
    
    		/* Use current opclass - TODO: if no opclass check 80/40/20 */
    		acs_params.opclass = cur_acs_params.opclass;
    
    		ret = cntlr_acs_radio_channel_recalc(radio, &acs_params);
    		if (WARN_ON(ret))
    			continue;
    
    		dbg("acs node " MACFMT " radio " MACFMT " new %d/%d opclass %d vs old %d/%d opclass %d\n",
    		    MAC2STR(node->alid), MAC2STR(radio->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);
    
    		if (cntlr_acs_radio_is_bsta_connected(radio))
    			continue;
    
    		warn("acs switch to best channel %d/%d\n", acs_params.best_channel, acs_params.best_bw);
    		ret = cntrl_send_channel_selection(node->cntlr, node->alid,
    						   radio->macaddr,
    						   acs_params.best_channel,
    						   acs_params.best_opclass,
    						   15);
    
    		if (ret)
    			warn("acs switch failed\n");
    	}
    }
    
    
    static bool cntlr_dfs_get_usable(struct opclass_entry *entry, struct cac_data *cac_data)
    {
    	uint8_t reas;
    	int i;
    
    	for (i = 0; i < entry->channels_num; i++) {
    		reas = entry->channels[i].preference & CHANNEL_PREF_REASON;
    
    		/* Usable - we can run CAC */
    		if (reas == CHANNEL_PREF_REASON_DFS_USABLE) {
    			cac_data->channel = entry->channels[i].channel;
    			cac_data->opclass = entry->opclass;
    			return true;
    		}
    	}
    
    	return false;
    }
    
    
    static bool cntrlr_radio_is_ap_iface(struct netif_radio *radio)
    {
    	struct netif_iface *iface;
    
    	list_for_each_entry(iface, &radio->iflist, list) {
    		/* Check if AP iface connected */
    		if (iface->type == NETIF_FHBSS)
    			return true;
    		if (iface->type == NETIF_BKBSS)
    			return true;
    	}
    
    	return false;
    }
    
    
    static bool cntlr_dfs_get_cac_data(struct netif_radio *radio, struct cac_data *cac_data)
    {
    	struct opclass_entry *entry;
    	struct opclass *cur_opclass;
    	struct opclass *opclass;
    	int i;
    
    	/* TODO check dfs_region early - skip non EU */
    
    	opclass = &radio->opclass;
    	cur_opclass = &radio->cur_opclass;
    
    	for (i = 0; i < opclass->opclass_entry_num; i++) {
    		entry = &opclass->opclass_entry[i];
    
    		if (entry->opclass != cur_opclass->opclass_entry[0].opclass)
    			continue;
    
    		if (!cntlr_dfs_get_usable(entry, cac_data))
    			continue;
    
    		/* TODO check chan/bw - not only control channel */
    		if (cac_data->channel == cur_opclass->opclass_entry[0].channels[0].channel)
    			continue;
    
    		/* TODO define this in ieee1905 */
    		cac_data->cac_method = 2;
    		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 cac_data cac_data = {};
    
    	dbg("dfs radio preCAC cleanup " MACFMT "\n", MAC2STR(radio->macaddr));
    
    	if (!cntrlr_radio_is_ap_iface(radio)) {
    		dbg("dfs radio preCAC no AP ifaces, skip radio\n");
    		return;
    	}
    
    
    	if (!cntlr_dfs_get_cac_data(radio, &cac_data)) {
    		dbg("dfs radio preCAC cleanup no channels left\n");
    		return;
    	}
    
    	dbg("dfs radio preCAC run chan %d opclass %d\n", cac_data.channel, cac_data.opclass);
    	WARN_ON(cntlr_send_cac_req(node->cntlr, node->alid, 1, &cac_data));
    }
    
    void cntlr_dfs_node_cleanup(struct node *node)
    {
    	struct netif_radio *radio;
    
    	dbg("dfs node preCAC cleanup " MACFMT "\n", MAC2STR(node->alid));
    
    	list_for_each_entry(radio, &node->radiolist, list) {
    		cntlr_dfs_radio_cleanup(node, radio);
    	}
    }