Skip to content
Snippets Groups Projects
bcm.c 67.3 KiB
Newer Older
  • Learn to ignore specific revisions
  •  /*
     * bcm.c - implements for Broadcom wifi
     *
    
     * Copyright (C) 2019 iopsys Software Solutions AB. All rights reserved.
    
     * Author: anjan.chanda@iopsys.eu
    
     *
     * This program is free software; you can redistribute it and/or
     * modify it under the terms of the GNU General Public License
     * version 2 as published by the Free Software Foundation.
     *
     * This program is distributed in the hope that it will be useful, but
     * WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     * General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program; if not, write to the Free Software
     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
     * 02110-1301 USA
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    #include <stdarg.h>
    
    #include <ctype.h>
    
    #include <fcntl.h>
    
    #include <sys/ioctl.h>
    #include <sys/socket.h>
    
    #include <sys/types.h>
    #include <net/if.h>
    #include <stdbool.h>
    
    #include <errno.h>
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    #include "debug.h"
    
    #include "util.h"
    
    #include "wifi.h"
    
    #include "wpactrl_util.h"
    
    #include "broadcom.h"
    
    
    #define WORKAROUND_BCM_DFS_SCAN		1
    
    int
    wl_ether_atoe(const char *a, struct wl_ether_addr *n)
    {
    	char *c = NULL;
    	int i = 0;
    
    	memset(n, 0, ETHER_ADDR_LEN);
    	for (;;) {
    		n->octet[i++] = (uint8)strtoul(a, &c, 16);
    		if (!*c++ || i == ETHER_ADDR_LEN)
    			break;
    		a = c;
    	}
    	return (i == ETHER_ADDR_LEN);
    }
    
    char *
    wl_ether_etoa(const struct wl_ether_addr *n)
    {
    	static char etoa_buf[ETHER_ADDR_LEN * 3];
    	char *c = etoa_buf;
    	int i;
    
    	for (i = 0; i < ETHER_ADDR_LEN; i++) {
    		if (i)
    			*c++ = ':';
    		c += sprintf(c, "%02X", n->octet[i] & 0xff);
    	}
    	return etoa_buf;
    }
    
    
    
    static int wl_ioctl(const char *ifname, int cmd, void *buf, int len)
    
    {
    	struct ifreq ifr;
    
    	wl_ioctl_t wioc = {0};
    
    	int s;
    
    	wioc.cmd = cmd;
    	wioc.buf = buf;
    	wioc.len = len;
    
    	strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
    	ifr.ifr_data = (caddr_t) &wioc;
    
    	s = socket(AF_INET, SOCK_DGRAM, 0);
    	if (s < 0)
    		return -1;
    
    	fcntl(s, F_SETFD, fcntl(s, F_GETFD) | FD_CLOEXEC);
    
    	if (ioctl(s, SIOCDEVPRIVATE, &ifr) < 0) {
    		close(s);
    		return -1;
    	}
    
    	close(s);
    	return 0;
    }
    
    
    static int wl_iovar(const char *ifname, int set, char *iovar,
    
    				void *param, int paramlen,
    				void *bufptr, int buflen)
    {
    	int len;
    	int total_len;
    
    	len = strlen(iovar) + 1;
    	total_len = len + paramlen;
    
    	if (buflen < total_len)
    		return -1;
    
    
    	snprintf(bufptr, buflen, "%s", iovar);
    
    	memcpy((uint8_t *)bufptr + len, param, paramlen);
    
    
    	if (set)
    		return wl_ioctl(ifname, WLC_SET_VAR, bufptr, total_len);
    
    	return wl_ioctl(ifname, WLC_GET_VAR, bufptr, buflen);
    
    int wl_iovar_set(const char *ifname, char *iovar, void *param, int paramlen,
    
    			void *buf, int buflen)
    {
    	return wl_iovar(ifname, 1, iovar, param, paramlen, buf, buflen);
    }
    
    
    int wl_iovar_get_noparam(const char *ifname, char *iovar, void *bufptr, int buflen)
    
    {
    	return wl_iovar(ifname, 0, iovar, NULL, 0, bufptr, buflen);
    }
    
    
    int wl_iovar_get(const char *ifname, char *iovar, void *param, int paramlen,
    
    			void *buf, int buflen)
    {
    	return wl_iovar(ifname, 0, iovar, param, paramlen, buf, buflen);
    }
    
    
    static int wl_swap(const char *ifname)
    
    {
    	int val = 0;
    
    	if (wl_ioctl(ifname, WLC_GET_MAGIC, &val, sizeof(int)) < 0)
    		return 0; /* don't swap on error */
    
    	/* is endian swap needed */
    	if (val == (int)BCMSWAP32(WLC_IOCTL_MAGIC))
    		return 1;
    
    	return 0;
    }
    
    
    #if 0	// TODO: remove
    static int bcm_get_ifstatus(const char *ifname, uint32_t *buf)
    
    {
    	unsigned int isup;
    	int swap = 0;
    
    	swap = wl_swap(ifname);
    
    	if (wl_ioctl(ifname, WLC_GET_UP, &isup, sizeof(isup)) < 0)
    		isup = 0;
    
    	*buf = swap ? BCMSWAP32(isup) : isup;
    
    	return 0;
    }
    
    
    int bcm_get_isap(char *ifname, int *buf)
    {
    	unsigned int isap;
    	int swap = 0;
    
    	swap = wl_swap(ifname);
    
    	if (wl_ioctl(ifname, WLC_GET_AP, &isap, sizeof(isap)) < 0)
    		return -1;
    
    	*buf = swap ? BCMSWAP32(isap) : isap;
    
    	return 0;
    }
    
    
    static int bcm_get_oper_band(const char *ifname, enum wifi_band *band)
    
    	unsigned int b = 0;
    
    	int swap = 0;
    
    	swap = wl_swap(ifname);
    
    
    	if (wl_ioctl(ifname, WLC_GET_BAND, &b, sizeof(b)) < 0) {
    
    		*band = BAND_UNKNOWN;
    
    		return -1;
    	}
    
    	b = swap ? BCMSWAP32(b) : b ;
    
    	switch (b) {
    	case 1:
    
    		*band = BAND_DUAL;
    
    		*band = BAND_UNKNOWN;
    
    static int bcm_get_country(const char *ifname, char *alpha2)
    
    {
    	wl_country_t cc;
    
    	if (!alpha2)
    		return -1;
    
    	memset(&cc, 0, sizeof(cc));
    	if (wl_iovar_get_noparam(ifname, "country", &cc, sizeof(cc)) < 0)
    		return -1;
    
    
    	snprintf(alpha2, 4, "%s", cc.country_abbrev);
    
    int wl_format_ssid(char *ssid_buf, uint8_t *ssid, int ssid_len)
    {
    	int i, c;
    	char *p = ssid_buf;
    
    	if (ssid_len > 32)
    		ssid_len = 32;
    
    	for (i = 0; i < ssid_len; i++) {
    		c = (int)ssid[i];
    		if (c == '\\') {
    			*p++ = '\\';
    			*p++ = '\\';
    		} else if (isprint((uchar)c)) {
    			*p++ = (char)c;
    		} else {
    			p += sprintf(p, "\\x%02X", c);
    		}
    	}
    	*p = '\0';
    
    	return p - ssid_buf;
    }
    
    
    int bcm_get_bitrate(char *ifname, unsigned long *buf)
    
    {
    	int rate = 0;
    	int swap = 0;
    
    	swap = wl_swap(ifname);
    	if (wl_ioctl(ifname, WLC_GET_RATE, &rate, sizeof(rate)) < 0)
    		rate = 0;
    
    	*buf = swap ? BCMSWAP32(rate) : rate;
    
    
    static int bcm_get_maxrate(const char *ifname, unsigned long *out)
    
    {
    	int ioctl_req_version = 0x2000;
    	wl_bss_info_t *bi;
    	chanspec_t chspec;
    	uint16_t vht_rxmap;
    	int swap = 0;
    	uint8_t supp_mcs[2] = {0};
    	int bandwidth = 20;
    
    	float maxrate = 54.0;
    
    	int nss = 0;
    	int octet = 0;
    	int max_mcs = -1;
    	int sgi = 1;
    	char *tmp;
    	int l;
    
    	swap = wl_swap(ifname);
    	tmp = malloc(WLC_IOCTL_MAXLEN * sizeof(char));
    	if (tmp == NULL)
    		return -1;
    
    	memset(tmp, 0, WLC_IOCTL_MAXLEN);
    	memcpy(tmp, &ioctl_req_version, sizeof(ioctl_req_version));
    	if (wl_ioctl(ifname, WLC_GET_BSS_INFO, tmp, WLC_IOCTL_MAXLEN) < 0) {
    		free(tmp);
    		return -1;
    	}
    
    	bi = (wl_bss_info_t *)(tmp + 4);
    	chspec = bi->chanspec;
    	if (CHSPEC_IS160(chspec) || CHSPEC_IS8080(chspec))
    		bandwidth = 160;
    	else if (CHSPEC_IS80(chspec))
    		bandwidth = 80;
    	else if (CHSPEC_IS40(chspec))
    		bandwidth = 40;
    
    	vht_rxmap = swap ? BCMSWAP16(bi->vht_rxmcsmap) : bi->vht_rxmcsmap;
    	if (bi->vht_cap) {
    		*((uint16_t *)supp_mcs) = vht_rxmap;
    
    		for (l = 0; l < 16; l += 2) {
    			uint8_t supp_mcs_mask = 0;
    
    			if (l && !(l % 8))
    				octet++;
    
    			supp_mcs_mask = supp_mcs[octet] & (0x3 << (l % 8));
    			supp_mcs_mask >>= (l % 8);
    			if (supp_mcs_mask == 3)
    				break;
    
    			nss++;
    			if (supp_mcs_mask == 0)
    				max_mcs = 7;
    			else if (supp_mcs_mask == 1)
    				max_mcs = 8;
    			else if (supp_mcs_mask == 2)
    				max_mcs = 9;
    		}
    
    		maxrate = wifi_mcs2rate(max_mcs, bandwidth, nss, sgi ? WIFI_SGI : WIFI_LGI);
    
    	} else if (bi->n_cap) {
    
    		int i;
    		int bit_i = 0;
    		int more = 1;
    
    		for (i = 0; i < MCSSET_LEN && more; i++) {
    			for (bit_i = 0; bit_i < 8; bit_i++) {
    				if (!(bi->basic_mcs[i] & (1 << bit_i))) {
    					more = 0;
    					break;
    				}
    				max_mcs++;
    			}
    		}
    
    		nss = (max_mcs / 8) + 1;
    		max_mcs %= 8;
    
    		maxrate = wifi_mcs2rate(max_mcs, bandwidth, nss, sgi ? WIFI_SGI : WIFI_LGI);
    
    	*out = (unsigned long)maxrate;     /* in Mbps */
    
    static int bcm_get_noise(const char *ifname, int *buf)
    
    {
    	unsigned int noise;
    	int swap = 0;
    
    	swap = wl_swap(ifname);
    
    	if (wl_ioctl(ifname, WLC_GET_PHY_NOISE, &noise, sizeof(noise)) < 0)
    		noise = 0;
    
    	*buf = swap ? BCMSWAP32(noise) : noise;
    
    	return 0;
    }
    
    
    static int bcm_get_security(const char *ifname, uint32_t *auth, uint32_t *enc)
    
    	unsigned int wpa_auth = 0;
    	unsigned int wsec = 0;
    
    	swap = wl_swap(ifname);
    
    	if (wl_ioctl(ifname, WLC_GET_WPA_AUTH, &wpa_auth,
    					sizeof(wpa_auth)) < 0) {
    		*auth = AUTH_UNKNOWN;
    		*enc = CIPHER_UNKNOWN;
    		return -1;
    	}
    
    
    	if (wl_ioctl(ifname, WLC_GET_WSEC, &wsec, sizeof(wsec)) < 0) {
    		*enc = CIPHER_UNKNOWN;
    		return -1;
    	}
    
    
    	wpa_auth = swap ? BCMSWAP32(wpa_auth) : wpa_auth;
    
    	wsec = swap ? BCMSWAP32(wsec) : wsec;
    
    	/* libwifi_dbg("wpa_auth = 0x%x wsec = 0x%x\n", wpa_auth, wsec); */
    
    	if (!!(wsec & 0x1)) {
    		*enc = CIPHER_WEP;
    		*auth = AUTH_OPEN;
    		return 0;
    	}
    
    	if (wpa_auth == WPA_AUTH_DISABLED) {
    		*auth = AUTH_OPEN;
    		*enc = CIPHER_NONE;
    		return 0;
    	}
    
    	/* in wpa_auth mode ... */
    	if (!!(wpa_auth & WPA_AUTH_PSK) && !!(wpa_auth & WPA2_AUTH_PSK)) {
    
    		*auth = AUTH_WPAPSK | AUTH_WPA2PSK;
    
    	} else if (!!(wpa_auth & WPA2_AUTH_PSK)) {
    
    		*auth = AUTH_WPA2PSK;
    
    	} else if (!!(wpa_auth & WPA_AUTH_PSK)) {
    
    		*auth = AUTH_WPAPSK;
    
    	} else if (!!(wpa_auth & WPA_AUTH_UNSPECIFIED) &&
    			!!(wpa_auth & WPA2_AUTH_UNSPECIFIED)) {
    
    		*auth = AUTH_WPA | AUTH_WPA2;
    
    	} else if (!!(wpa_auth & WPA2_AUTH_UNSPECIFIED)) {
    
    		*auth = AUTH_WPA2;
    
    	}
    	else if (!!(wpa_auth & WPA_AUTH_UNSPECIFIED)) {
    
    		*auth = AUTH_WPA;
    
    	/* else if (wpa_auth & WPA2_AUTH_1X_SHA256)
    		strcpy(wpa, "1X-SHA256");
    	else if (wpa_auth & WPA2_AUTH_FT)
    		strcpy(wpa, "FT");	// TODO
    	else if (wpa_auth & WPA2_AUTH_PSK_SHA256)
    		strcpy(wpa, "PSK-SHA256"); */
    
    		*auth = AUTH_UNKNOWN;
    	}
    
    
    		*enc |= CIPHER_TKIP;
    
    		*enc |= CIPHER_AES;
    
    	return 0;
    }
    
    
    int bcm_get_bssinfo(const char *ifname, enum wifi_bw *bandwidth,
    					uint32_t *channel,
    					int *noise)
    
    {
    	wl_bss_info_t *bi;
    	int ioctl_req_version = 0x2000;
    	char *tmp;
    	int swap = 0;
    
    	swap = wl_swap(ifname);
    
    	tmp = malloc(WLC_IOCTL_MAXLEN * sizeof(char));
    	if (tmp == NULL)
    		return -1;
    
    	memset(tmp, 0, WLC_IOCTL_MAXLEN);
    	memcpy(tmp, &ioctl_req_version, sizeof(ioctl_req_version));
    
    	if (wl_ioctl(ifname, WLC_GET_BSS_INFO, tmp, WLC_IOCTL_MAXLEN) < 0) {
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    		libwifi_err("ioctl BSS_INFO error!!!\n");
    
    		free(tmp);
    		return 0;
    	}
    
    	bi = (wl_bss_info_t *)(tmp + 4);
    
    	if (channel != NULL) {
    		if (bi->ctl_ch) {
    			*channel = bi->ctl_ch;
    		} else {
    			chanspec_t chs;
    
    			chs = swap ? BCMSWAP16(bi->chanspec) : bi->chanspec;
    			*channel = CHSPEC_CHANNEL(chs);
    		}
    	}
    
    	if (noise != NULL)
    
    		*noise = bi->phy_noise;
    
    
    	if (bandwidth != NULL) {
    
    			bw = (CHSPEC_IS160(BCMSWAP16(bi->chanspec)) ?
    			160 : (CHSPEC_IS8080(BCMSWAP16(bi->chanspec)) ?
    			8080 : (CHSPEC_IS80(BCMSWAP16(bi->chanspec)) ?
    
    			80 : (CHSPEC_IS40(BCMSWAP16(bi->chanspec)) ?
    			40 : (CHSPEC_IS20(BCMSWAP16(bi->chanspec)) ?
    
    			20 : 10)))));
    
    			bw = (CHSPEC_IS160(bi->chanspec) ?
    			160 : (CHSPEC_IS8080(bi->chanspec) ?
    			8080 : (CHSPEC_IS80(bi->chanspec)) ?
    
    			80 : (CHSPEC_IS40(bi->chanspec) ?
    			40 : (CHSPEC_IS20(bi->chanspec) ?
    			20 : 10))));
    
    			*bandwidth = BW80;
    
    		else if (bw == 40)
    
    			*bandwidth = BW40;
    
    		else if (bw == 20)
    
    			*bandwidth = BW20;
    
    		else if (bw == 160)
    
    			*bandwidth = BW160;
    
    		else if (bw == 8080)
    
    			*bandwidth = BW8080;
    
    			*bandwidth = BW_UNKNOWN;
    
    static int bcm_get_bssload_from_beacon(const char *ifname, struct wifi_ap_load *load)
    
    {
    	unsigned char *ie, *ie_start;
    	int bcnlen, len;
    	int swap = 0;
    	unsigned char *buf;
    	unsigned char bssid[6] = {0};
    	int offset;
    
    	if (wl_ioctl(ifname, WLC_GET_BSSID, bssid, 6))
    		return -1;
    
    	swap = wl_swap(ifname);
    	buf = malloc(WLC_IOCTL_MAXLEN);
    	if (buf == NULL)
    		return -1;
    
    	memset(buf, 0, WLC_IOCTL_MAXLEN);
    	if (wl_iovar_get_noparam(ifname, "beacon_info", &buf[0], WLC_IOCTL_MAXLEN) < 0) {
    		free(buf);
    		return -1;
    	}
    
    	bcnlen = *((int *)buf);
    	bcnlen = swap ? BCMSWAP32(bcnlen) : bcnlen;
    	if (bcnlen <= 0) {
    		free(buf);
    		return -1;
    	}
    
    	/* Don't trust BCM's API -
    	 * Frame starts after the first 4 bytes, which holds sizeof
    	 * returned buffer.
    	 * This however is not always true across all BCM implementations.
    	 * So, look for beacon frame start pattern in the returned buffer.
    	 */
    	offset = 4;
    	while (offset < bcnlen) {
    		if (buf[offset] == 0x80) {
    			if (!memcmp(&buf[offset + 4], "\xff\xff\xff\xff\xff\xff", 6) &&
    				!memcmp(&buf[offset + 10], bssid, 6) &&
    				!memcmp(&buf[offset + 16], bssid, 6))
    				/* found start of bcn frame */
    				break;
    		}
    		offset++;
    	}
    
    	if (offset >= bcnlen) {
    		free(buf);
    		return -1;
    	}
    
    	/* ie starts after buf_len + mac header + tsf + bcnintv + capinfo */
    	ie_start = buf + offset + 24 + 8 + 2 + 2;
    	bcnlen -= (offset + 24 + 8 + 2 + 2);
    	for (ie = ie_start; ie < ie_start + bcnlen; ie += len + 2) {
    		len = ie[1];
    		libwifi_dbg("Elm: 0x%02x  len = 0x%02x\n", ie[0], len);
    
    		if (ie[0] == IE_BSS_LOAD && len == 5) {
    
    			/* QoS Load Elm */
    			load->sta_count = *((uint16_t *)&ie[2]);
    			load->utilization = (int)ie[4];
    			load->utilization *= (float)100 / (float)255;
    
    			if (load->utilization > 100)
    				load->utilization = 100;
    
    			load->available = *((uint16_t *)&ie[5]);
    			libwifi_dbg("load = %d   sta_count = %d\n",
    					load->utilization, load->sta_count);
    			break;
    		}
    	}
    
    	free(buf);
    	return 0;
    }
    
    
    /* This function is almost duplicate of bcm_get_bssload_from_beacon().
     * TODO - merge both the functions and cleanup.
     */
    
    static int bcm_get_beacon_ies(const char *ifname, uint8_t *ies, int *ies_len)
    
    	uint8_t *ie_start;
    
    	int bcnlen;
    	int swap = 0;
    	unsigned char *buf;
    	unsigned char bssid[6] = {0};
    	int offset;
    
    	if (wl_ioctl(ifname, WLC_GET_BSSID, bssid, 6))
    		return -1;
    
    	swap = wl_swap(ifname);
    	buf = malloc(WLC_IOCTL_MAXLEN);
    	if (buf == NULL)
    		return -1;
    
    	memset(buf, 0, WLC_IOCTL_MAXLEN);
    	if (wl_iovar_get_noparam(ifname, "beacon_info", &buf[0], WLC_IOCTL_MAXLEN) < 0) {
    		free(buf);
    		return -1;
    	}
    
    	bcnlen = *((int *)buf);
    	bcnlen = swap ? BCMSWAP32(bcnlen) : bcnlen;
    	if (bcnlen <= 0) {
    		free(buf);
    		return -1;
    	}
    
    	offset = 4;
    
    	/* Look for beacon frame start in the returned buffer */
    
    	while (offset < bcnlen) {
    		if (buf[offset] == 0x80) {
    			if (!memcmp(&buf[offset + 4], "\xff\xff\xff\xff\xff\xff", 6) &&
    				!memcmp(&buf[offset + 10], bssid, 6) &&
    				!memcmp(&buf[offset + 16], bssid, 6))
    				/* found start of bcn frame */
    				break;
    		}
    		offset++;
    	}
    
    	if (offset >= bcnlen) {
    		free(buf);
    		return -1;
    	}
    
    	/* ie starts after buf_len + mac header + tsf + bcnintv + capinfo */
    	ie_start = buf + offset + 24 + 8 + 2 + 2;
    	bcnlen -= (offset + 24 + 8 + 2 + 2);
    
    	if (*ies_len >= bcnlen) {
    		memcpy(ies, ie_start, bcnlen);
    		*ies_len = bcnlen;
    	} else
    		*ies_len = 0;
    
    	free(buf);
    	return 0;
    }
    
    
    int bcm_get_oper_stds(const char *ifname, uint8_t *mode)
    {
    #define INVALID_MODE	0xffffffff
    	uint32_t gmode = 0;
    	uint32_t nmode = 0;
    	uint32_t vhtmode = 0;
    	unsigned char _iovar_buf[256] = {0};
    	int swap;
    	enum wifi_band band = 0;
    	bool band_is_2g = false;
    	bool band_is_5g = false;
    
    	*mode = 0;
    	swap = wl_swap(ifname);
    
    	bcm_get_oper_band(ifname, &band);
    	switch (band) {
    
    		band_is_5g = true;
    		break;
    
    		band_is_2g = true;
    		break;
    
    		band_is_2g = true;
    		band_is_5g = true;
    		break;
    	default:
    		break;
    	}
    
    	if (band_is_2g &&
    		!wl_ioctl(ifname, WLC_GET_GMODE, &gmode, sizeof(gmode))) {
    		gmode = swap ? BCMSWAP32(gmode) : gmode;
    
    #define GMODE_LEGACY_B		0
    #define GMODE_AUTO		1
    #define GMODE_ONLY		2
    
    		if (gmode == GMODE_LEGACY_B)
    
    		else if (gmode == GMODE_AUTO)
    
    			*mode |= (WIFI_B | WIFI_G);
    
    		else if (gmode == GMODE_ONLY)
    
    	} else
    		gmode = INVALID_MODE;
    
    	if (!wl_iovar_get_noparam(ifname, "nmode", &_iovar_buf[0], 256)) {
    		nmode = *((uint32_t *)_iovar_buf);
    		nmode = swap ? BCMSWAP32(nmode) : nmode;
    
    		if (nmode)
    
    	} else
    		nmode = INVALID_MODE;
    
    	memset(_iovar_buf, 0, sizeof(_iovar_buf));
    	if (band_is_5g &&
    		!wl_iovar_get_noparam(ifname, "vhtmode", &_iovar_buf[0], 256)) {
    		vhtmode = *((uint32_t *)_iovar_buf);
    		vhtmode = swap ? BCMSWAP32(vhtmode) : vhtmode;
    
    		if (vhtmode)
    
    			*mode |= WIFI_AC;
    		else if (!(*mode & WIFI_N))
    			*mode |= WIFI_A;
    
    	} else
    		vhtmode = INVALID_MODE;
    
    
    	/* libwifi_dbg("g = 0x%x    n = 0x%x     vht = 0x%x\n",
    
    						gmode, nmode, vhtmode); */
    
    	return 0;
    }
    
    static int bcm_wifi_bss_info(const char *ifname, struct wifi_bss *bss)
    
    {
    	int ioctl_req_version = 0x2000;
    	struct wl_maclist *macs;
    	char bufptr[1536] = {0};
    
    	//char ssid[33] = {0};
    	//char bssid[18] = {0};
    
    	cca_req_t req, *res;
    	wl_bss_info_t *bi;
    	chanspec_t chspec;
    	int macs_len;
    	int swap = 0;
    	char *tmp;
    	cca_t *c;
    
    	uint32_t bw = 0;
    
    	uint8_t *ext_ie, *rrm_ie, *ft_ie;
    	uint8_t *vht_ie, *ht_ie;
    
    	uint8_t *wmm_ie;
    
    	uint8_t *he_ie;
    
    	uint16_t capability = 0;
    	uint8_t *ie_start;
    	uint16_t ie_offset = 0;
    	uint32_t ie_len = 0;
    
    	uint8_t *ies = (uint8_t *)bufptr;
    
    	int ies_length = sizeof(bufptr);
    
    
    	swap = wl_swap(ifname);
    
    	tmp = malloc(WLC_IOCTL_MAXLEN * sizeof(char));
    	if (tmp == NULL)
    
    		return -ENOMEM;
    
    
    	memset(tmp, 0, WLC_IOCTL_MAXLEN);
    	memcpy(tmp, &ioctl_req_version, sizeof(ioctl_req_version));
    
    	if (wl_ioctl(ifname, WLC_GET_BSS_INFO, tmp, WLC_IOCTL_MAXLEN) < 0) {
    		free(tmp);
    		return 0;
    	}
    
    	bi = (wl_bss_info_t *)(tmp + 4);
    	chspec = bi->chanspec;
    
    	if (bi->ctl_ch) {
    		bss->channel = bi->ctl_ch;
    	} else {
    		chanspec_t chs;
    
    		chs = swap ? BCMSWAP16(bi->chanspec) : bi->chanspec;
    		bss->channel = CHSPEC_CHANNEL(chs);
    	}
    
    	bss->rssi = swap ? BCMSWAP16(bi->RSSI) : bi->RSSI;
    
    	bss->noise = bi->phy_noise;
    
    	libwifi_dbg("last_rssi = %d   phy_noise = %d\n", bss->rssi, bss->noise);
    
    		bw = (CHSPEC_IS160(BCMSWAP16(bi->chanspec)) ?
    		160 : (CHSPEC_IS8080(BCMSWAP16(bi->chanspec)) ?
    		8080 : (CHSPEC_IS80(BCMSWAP16(bi->chanspec)) ?
    		80 : (CHSPEC_IS40(BCMSWAP16(bi->chanspec)) ?
    		40 : (CHSPEC_IS20(BCMSWAP16(bi->chanspec)) ?
    		20 : 10)))));
    
    		bw = (CHSPEC_IS160(bi->chanspec) ?
    		160 : (CHSPEC_IS8080(bi->chanspec) ?
    		8080 : (CHSPEC_IS80(bi->chanspec)) ?
    		80 : (CHSPEC_IS40(bi->chanspec) ?
    		40 : (CHSPEC_IS20(bi->chanspec) ?
    
    		bss->curr_bw = BW80;
    
    	else if (bw == 40)
    
    		bss->curr_bw = BW40;
    
    	else if (bw == 20)
    
    		bss->curr_bw = BW20;
    
    	else if (bw == 160)
    
    		bss->curr_bw = BW160;
    
    	else if (bw == 8080)
    
    		bss->curr_bw = BW8080;
    
    		bss->curr_bw = BW_UNKNOWN;
    
    
    	snprintf((char *)bss->ssid, bi->SSID_len + 1, "%s", bi->SSID);
    	memcpy(bss->bssid, bi->BSSID.octet, 6);
    
    	/* get capabilities */
    	capability = swap ? BCMSWAP16(bi->capability) : bi->capability;
    	wifi_cap_set_from_ie(bss->cbitmap, (uint8_t *)&capability, 2);
    
    	ie_len = swap ? BCMSWAP32(bi->ie_length) : bi->ie_length;
    	ie_offset = swap ? BCMSWAP16(bi->ie_offset) : bi->ie_offset;
    	ie_start = (uint8_t *)((uint8_t *)bi + ie_offset);
    	if (ie_len == 0) {
    
    		if (bcm_get_beacon_ies(ifname, ies, &ies_length) == 0) {
    
    			ie_start = ies;
    			ie_len = ies_length;
    		}
    	}
    
    	/* {
    		int _x;
    
    
    		libwifi_dbg("IEs:  [ ");
    
    		for (_x = 0; _x < ie_len; _x++)
    
    			libwifi_dbg("%02X ", ie_start[_x]);
    		libwifi_dbg("]\n");
    
    	ht_ie = wifi_find_ie(ie_start, ie_len, IE_HT_CAP);
    	vht_ie = wifi_find_ie(ie_start, ie_len, IE_VHT_CAP);
    	ext_ie = wifi_find_ie(ie_start, ie_len, IE_EXT_CAP);
    	rrm_ie = wifi_find_ie(ie_start, ie_len, IE_RRM);
    	ft_ie = wifi_find_ie(ie_start, ie_len, IE_MDE);
    
    	he_ie = wifi_find_ie_ext(ie_start, ie_len, IE_EXT_HE_CAP);
    
    	wmm_ie = wifi_find_vsie(ie_start, ie_len, (uint8_t *)"\x00\x50\xf2", 2, 0xff);
    	if (wmm_ie) {
    		wifi_cap_set(bss->cbitmap, WIFI_CAP_WMM);
    		wmm_ie += 2 + 3 + 2 + 1;  /* skip upto 'version' */
    		if (!!(wmm_ie[0] & 0x80))
    			wifi_cap_set(bss->cbitmap, WIFI_CAP_APSD);
    	}
    
    
    	if (ext_ie) {
    		bss->caps.valid |= WIFI_CAP_EXT_VALID;
    
    		memcpy(bss->caps.ext.byte, &ext_ie[2], min(ext_ie[1], 16));
    
    		wifi_cap_set_from_ie(bss->cbitmap, ext_ie, ext_ie[1] + 2);
    	}
    
    	if (rrm_ie) {
    		bss->caps.valid |= WIFI_CAP_RM_VALID;
    
    		memcpy(&bss->caps.rrm, &rrm_ie[2], rrm_ie[1]);
    
    		wifi_cap_set_from_ie(bss->cbitmap, rrm_ie, rrm_ie[1] + 2);
    	}
    
    	if (ft_ie) {
    		wifi_cap_set_from_ie(bss->cbitmap, ft_ie, ft_ie[1] + 2);
    	}
    
    	if (ht_ie) {
    		bss->caps.valid |= WIFI_CAP_HT_VALID;
    
    		memcpy(&bss->caps.ht, &ht_ie[2], ht_ie[1]);
    
    		wifi_cap_set_from_ie(bss->cbitmap, ht_ie, ht_ie[1] + 2);
    	}
    
    	if (vht_ie) {
    		bss->caps.valid |= WIFI_CAP_VHT_VALID;
    
    		memcpy(&bss->caps.vht, &vht_ie[2], vht_ie[1]);
    
    		wifi_cap_set_from_ie(bss->cbitmap, vht_ie, vht_ie[1] + 2);
    	}
    
    
    	if (he_ie) {
    		bss->caps.valid |= WIFI_CAP_HE_VALID;
    		memcpy(&bss->caps.he, &he_ie[3], min(he_ie[1], sizeof(struct wifi_caps_he)));
    		wifi_cap_set_from_ie(bss->cbitmap, he_ie, he_ie[1] + 2);
    	}
    
    
    	bcm_get_security(ifname, &bss->auth, &bss->enc); // TODO: deprecate
    
    	wifi_get_bss_security_from_ies(bss, ie_start, ie_len);
    
    
    	/* get bss ch utilization */
    
    	memset(bufptr, 0, sizeof(bufptr));
    
    	req.chanspec = chspec;
    	req.num_secs = 1;
    	if (wl_iovar_get(ifname, "cca_get_stats", &req, sizeof(req), bufptr, 1536) < 0)
    
    		return bcm_get_bssload_from_beacon(ifname, &bss->load);
    
    
    	res = (cca_req_t *)bufptr;
    	if (res->chanspec == 0 || res->num_secs == 0)
    		return -1;
    
    
    	if (swap) {
    		res->num_secs = BCMSWAP16(res->num_secs);
    		c->duration = BCMSWAP32(c->duration);
    		c->congest_ibss = BCMSWAP32(c->congest_ibss);
    		c->congest_obss = BCMSWAP32(c->congest_obss);
    		c->interference = BCMSWAP32(c->interference);
    		c->timestamp = BCMSWAP32(c->timestamp);
    	}
    
    	if (c->duration) {
    
    		int ibss_pcnt, obss_pcnt, inter_pcnt;
    
    
    		ibss_pcnt = c->congest_ibss * 100 /c->duration;
    		obss_pcnt = c->congest_obss * 100 /c->duration;
    		inter_pcnt = c->interference * 100 /c->duration;
    
    
    		bss->load.utilization = ibss_pcnt + obss_pcnt + inter_pcnt;
    		if (bss->load.utilization > 100)
    			bss->load.utilization = 100;
    
    	}
    
    	/* get connected stas num */
    	macs_len = 4 + WL_MAX_STA_COUNT * 6;
    	macs = (struct wl_maclist *)malloc(macs_len);
    	if (macs == NULL)
    
    		return -ENOMEM;
    
    
    	memset(macs, 0, macs_len);
    	macs->count = WL_MAX_STA_COUNT;
    	if (wl_ioctl(ifname, WLC_GET_ASSOCLIST, macs, macs_len)) {
    		free(macs);
    		return -1;
    	}
    
    	macs->count = swap ? BCMSWAP32(macs->count) : macs->count;
    	bss->load.sta_count = macs->count;
    
    	free(macs);
    
    static int bcm_get_ap_wmm_params(const char *ifname,
    						struct wifi_ap_wmm_ac a[])
    {
    	struct edcf_acparam p[WIFI_NUM_AC];
    	int swap = 0;
    	int ret;
    
    	swap = wl_swap(ifname);
    
    	ret = wl_iovar_get_noparam(ifname, "wme_ac_ap", &p, sizeof(p));
    	if (ret)
    		return -1;
    
    	a[0].ac = BE;
    	a[0].aifsn = p[0].ACI & EDCF_AIFSN_MASK;
    	a[0].cwmin = p[0].ECW & EDCF_ECWMIN_MASK;
    	a[0].cwmax = (p[0].ECW & EDCF_ECWMAX_MASK) >> EDCF_ECWMAX_SHIFT;
    	a[0].txop = swap ? BCMSWAP16(p[0].TXOP) : p[0].TXOP;
    
    	a[1].ac = BK;
    	a[1].aifsn = p[1].ACI & EDCF_AIFSN_MASK;
    	a[1].cwmin = p[1].ECW & EDCF_ECWMIN_MASK;
    	a[1].cwmax = (p[1].ECW & EDCF_ECWMAX_MASK) >> EDCF_ECWMAX_SHIFT;
    	a[1].txop = swap ? BCMSWAP16(p[1].TXOP) : p[1].TXOP;
    
    	a[2].ac = VI;
    	a[2].aifsn = p[2].ACI & EDCF_AIFSN_MASK;
    	a[2].cwmin = p[2].ECW & EDCF_ECWMIN_MASK;
    	a[2].cwmax = (p[2].ECW & EDCF_ECWMAX_MASK) >> EDCF_ECWMAX_SHIFT;
    	a[2].txop = swap ? BCMSWAP16(p[2].TXOP) : p[2].TXOP;
    
    	a[3].ac = VO;
    	a[3].aifsn = p[3].ACI & EDCF_AIFSN_MASK;
    	a[3].cwmin = p[3].ECW & EDCF_ECWMIN_MASK;
    	a[3].cwmax = (p[3].ECW & EDCF_ECWMAX_MASK) >> EDCF_ECWMAX_SHIFT;
    	a[3].txop = swap ? BCMSWAP16(p[3].TXOP) : p[3].TXOP;
    
    	return 0;
    }