/* SPDX-License-Identifier: LGPL-2.1-only */
/*
 * wifi.c - default implementation.
 *
 * Copyright (C) 2020-2024 Iopsys Software Solutions AB. All rights reserved.
 */
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <net/if.h>
#include <linux/if.h>
#include <time.h>
#include <syslog.h>
#include <unistd.h>
#include <stdlib.h>
#include <dirent.h>

#include <easy/easy.h>
#include "debug.h"
#include "wifi.h"
#include "wifiutils.h"
#include "nlwifi.h"
#include "hostapd_ctrl.h"
#include "supplicant_ctrl.h"

static unsigned long radio_maxrate(struct wifi_radio *radio)
{
	int max_mcs = 0;
	enum wifi_guard sgi = WIFI_SGI;

	if (radio->oper_band & BAND_2)
		max_mcs = 7;

	if (radio->oper_band & BAND_5)
		max_mcs = 9;

	if (radio->oper_std & WIFI_AX) {
		sgi = WIFI_4xLTF_GI800;
		max_mcs = 11;
	}

	if (radio->oper_std & WIFI_BE) {
		sgi = WIFI_4xLTF_GI800;
		max_mcs = 13;
	}

	libwifi_dbg("%s called max_mcs=%d, bw=%d, nss=%d sgi=%d \n", __func__, max_mcs, wifi_bw_enum2MHz(radio->curr_bw), radio->rx_streams, sgi);
	return wifi_mcs2rate(max_mcs, wifi_bw_enum2MHz(radio->curr_bw), radio->rx_streams, sgi);
}

static int radio_list(struct radio_entry *radio, int *num)
{
	struct dirent *p;
	struct radio_entry *entry;
	int count = 0;
	DIR *d;

	if (WARN_ON(!(d = opendir("/sys/class/ieee80211"))))
		return -1;

	while ((p = readdir(d))) {
		if (!strcmp(p->d_name, "."))
			continue;
		if (!strcmp(p->d_name, ".."))
			continue;
		if (WARN_ON(count >= *num))
			break;

		libwifi_dbg("radio: %s\n", p->d_name);
		entry = &radio[count];
		memset(entry->name, 0, 16);
		memcpy(entry->name, p->d_name, 15);
		count++;
	}

	closedir(d);
	*num = count;

	return 0;
}

static int is_valid_ap_iface(const char *ifname)
{
	int valid_iface = 0;
	int ret;
	enum wifi_mode mode;
	DIR *d;
	struct dirent *dir;

	d = opendir("/sys/class/net");
	if (WARN_ON(!d))
		return 0;

	while ((dir = readdir(d))) {
		if (strcmp(dir->d_name, ".") == 0 ||
		    strcmp(dir->d_name, "..") == 0)
			continue;
		if (strcmp(ifname, dir->d_name) == 0) {
			valid_iface = 1;
		}
	}
	closedir(d);

	if (!valid_iface)
		return 0;

	ret = nlwifi_get_mode(ifname, &mode);
	if (WARN_ON(ret))
		return 0;

	if (mode == WIFI_MODE_AP)  {
		return 1;
	}

	return 0;
}

static int radio_get_iface_extch_band(const char *netdev, enum wifi_band band, struct wifi_radio *radio)
{
	radio->extch = EXTCH_NONE;

	if (radio->curr_bw == BW20) {
		radio->extch = EXTCH_NONE;
		return 0;
	}

	if (radio->curr_bw != BW40) {
		radio->extch = EXTCH_AUTO;
		return 0;
	}

	if (radio->channel < radio->ccfs0) {
		radio->extch = EXTCH_ABOVE;
	} else if (radio->channel > radio->ccfs0) {
		radio->extch = EXTCH_BELOW;
	}

	return 0;
}

static char *radio_get_netdev(struct wifi_radio *radio, enum wifi_mode mode, enum wifi_band band)
{
	int i;

	for (i = 0; i < radio->num_iface; i++) {
		if (mode && mode != radio->iface[i].mode)
			continue;
		if (band && band != BAND_ANY && band != radio->iface[i].band)
			continue;

		return radio->iface[i].name;
	}

	return NULL;
}

/* Radio callbacks */
static int radio_info_band(const char *name, enum wifi_band band, struct wifi_radio *radio)
{
	char *netdev = NULL;
	int ret;

	libwifi_dbg("[%s, %s] %s called\n", name, wifi_band_to_str(band), __func__);
	ret = nlwifi_radio_info_band(name, band, radio);
	if (ret)
		return ret;

	ret = nlwifi_get_country(name, radio->regdomain);
	if (ret)
		return ret;

	netdev = radio_get_netdev(radio, NLWIFI_MODE_AP, band);
	if (netdev) {
		char *ctrl_iface = NULL;
		ret = wifi_get_ctrl_interface_band(netdev, band, &ctrl_iface);
		if (ret)
			return ret;

		ret = radio_get_iface_extch_band(netdev, band, radio);
		ret |= hostapd_cli_radio_info(ctrl_iface, band, radio);
		radio->acs_capable = true;

		free(ctrl_iface);

		radio->curr_opclass = wifi_radio_opclass(radio);
		radio->maxrate = radio_maxrate(radio);
		return ret;
	}

	netdev = radio_get_netdev(radio, NLWIFI_MODE_STA, band);
	if (netdev) {
		char *ctrl_iface = NULL;
		ret = wifi_get_ctrl_interface_band(netdev, band, &ctrl_iface);
		if (ret)
			return ret;

		ret = radio_get_iface_extch_band(netdev, band, radio);
		ret |= supplicant_cli_get_oper_std(ctrl_iface, &radio->oper_std);
		correct_oper_std_by_band(band, &radio->oper_std);

		free(ctrl_iface);

		radio->curr_opclass = wifi_radio_opclass(radio);
		radio->maxrate = radio_maxrate(radio);
		return ret;
	}

	radio->curr_opclass = wifi_radio_opclass(radio);
	radio->maxrate = radio_maxrate(radio);
	radio->oper_std = radio->supp_std;
	radio->max_iface_sta = 1;
	radio->max_iface_ap = 8;
	return 0;
}

static int radio_info(const char *name, struct wifi_radio *radio)
{
	enum wifi_band band = BAND_ANY;
	nlwifi_get_oper_band(name, &band);

	return radio_info_band(name, band, radio);
}

static int radio_get_supp_band(const char *name, uint32_t *bands)
{
	libwifi_dbg("[%s] %s called\n", name, __func__);
	return nlwifi_get_supp_band(name, bands);
}

static int radio_get_oper_band(const char *name, enum wifi_band *band)
{
	libwifi_dbg("[%s] %s called\n", name, __func__);
	return nlwifi_get_oper_band(name, band);
}

static int radio_get_caps(const char *name, struct wifi_caps *caps)
{
	libwifi_dbg("[%s] %s called\n", name, __func__);
	return nlwifi_radio_get_caps(name, caps);
}

static int radio_get_supp_stds(const char *name, uint8_t *std)
{
	libwifi_dbg("[%s] %s called\n", name, __func__);
	return nlwifi_get_supp_stds(name, std);
}

static int radio_get_band_oper_stds(const char *name, enum wifi_band band, uint8_t *std)
{
	char netdev[16];
	int ret;

	libwifi_dbg("[%s, %s] %s called\n", name, wifi_band_to_str(band), __func__);

	ret = nlwifi_phy_to_netdev_with_type_and_band(name, netdev, sizeof(netdev), NLWIFI_MODE_AP, band);
	if (ret == 0) {
		ret = hostapd_cli_get_oper_stds(netdev, std);
		if (ret) {
			ret = supplicant_cli_get_oper_std(netdev, std);
			if (ret)
				ret = hostapd_cli_get_wds_oper_stds(netdev, std);
		}

		correct_oper_std_by_band(band, std);
	} else {
		ret = nlwifi_get_supp_stds(name, std);
		correct_oper_std_by_band(band, std);
	}

	return ret;
}

static int radio_get_oper_stds(const char *name, uint8_t *std)
{
	return radio_get_band_oper_stds(name, BAND_ANY, std);
}

static int radio_get_country(const char *name, char *alpha2)
{
	libwifi_dbg("[%s] %s called\n", name, __func__);
	return nlwifi_get_country(name, alpha2);
}

static int radio_get_channel(const char *name, uint32_t *channel, enum wifi_bw *bw)
{
	libwifi_dbg("[%s] %s called\n", name, __func__);
	return nlwifi_get_channel(name, channel, bw);
}

static int radio_set_channel(const char *name, uint32_t channel, enum wifi_bw bw)
{
	libwifi_dbg("[%s] %s called\n", name, __func__);
	return -1;
}

static int radio_get_supp_channels(const char *name, uint32_t *chlist, int *num,
				   const char *alpha2, enum wifi_band f, enum wifi_bw b)
{
	libwifi_dbg("[%s] %s called\n", name, __func__);
	return nlwifi_get_supp_channels(name, chlist, num, alpha2, f, b);
}

static int radio_get_oper_channels(const char *name, uint32_t *chlist, int *num,
				   const char *alpha2, enum wifi_band f, enum wifi_bw b)
{
	libwifi_dbg("[%s] %s called\n", name, __func__);
	return -1;
}

static int radio_get_band_curr_opclass(const char *name, enum wifi_band band, struct wifi_opclass *o)
{
	char netdev[16];
	int ret;

	ret = nlwifi_phy_to_netdev_with_type_and_band(name, netdev, sizeof(netdev), NLWIFI_MODE_AP_STA, band);
	libwifi_dbg("[%s, %s, %s] %s called\n", name, netdev, wifi_band_to_str(band), __func__);

	/* If no netdev or STA not connected */
	if (ret)
		return nlwifi_get_default_opclass(band, o);

	return wifi_get_band_opclass(netdev, band, o);;
}

UNUSED_FUNC static int radio_get_band_supp_opclass(const char *name, enum wifi_band band,
				       int *num_opclass, struct wifi_opclass *o)
{
	return nlwifi_get_band_supp_opclass(name, band, num_opclass, o);
}

static int radio_get_curr_opclass(const char *name, struct wifi_opclass *o)
{
	char netdev[16];

	nlwifi_phy_to_netdev_with_type(name, netdev, sizeof(netdev), NLWIFI_MODE_AP_STA);
	libwifi_dbg("[%s, %s] %s called\n", name, netdev, __func__);
	return wifi_get_opclass(netdev, o);
}

static int radio_get_bandwidth(const char *name, enum wifi_bw *bw)
{
	char netdev[16];

	nlwifi_phy_to_netdev_with_type(name, netdev, sizeof(netdev), NLWIFI_MODE_AP_STA);
	libwifi_dbg("[%s, %s] %s called\n", name, netdev, __func__);
	return nlwifi_get_bandwidth(netdev, bw);
}

static int radio_get_band_maxrate(const char *name, enum wifi_band band, unsigned long *rate_Mbps)
{
	struct wifi_radio radio = { 0 };
	char netdev[16];
	int ret = 0;
	char *ctrl_iface = NULL;
	uint32_t channel;

	libwifi_dbg("[%s, %s] %s called\n", name, wifi_band_to_str(band), __func__);

	ret = nlwifi_get_phy_info(name, band, &radio);
	if (WARN_ON(ret))
		return ret;

	if (nlwifi_phy_to_netdev_with_type_and_band(name, netdev, sizeof(netdev), NLWIFI_MODE_AP, band) == 0) {
		wifi_get_ctrl_interface_band(netdev, band, &ctrl_iface);
		ret = hostapd_cli_get_oper_stds(ctrl_iface ? ctrl_iface : netdev, &radio.oper_std);
		free(ctrl_iface);
		correct_oper_std_by_band(band, &radio.oper_std);
	} else if (nlwifi_phy_to_netdev_with_type_and_band(name, netdev, sizeof(netdev), NLWIFI_MODE_STA, band) == 0) {
		wifi_get_ctrl_interface_band(netdev, band, &ctrl_iface);
		ret = supplicant_cli_get_oper_std(ctrl_iface ? ctrl_iface : netdev, &radio.oper_std);
		free(ctrl_iface);
		correct_oper_std_by_band(band, &radio.oper_std);
	}

	if (ret) {
		ret = nlwifi_get_band_supp_stds(name, band, &radio.oper_std);
		if (WARN_ON(ret))
			return ret;
	}

	ret = nlwifi_get_band_channel(netdev, band, &channel, &radio.curr_bw);
	libwifi_dbg("[%s, %s] %s called curr_bw:%s ret %d\n",
		    netdev, wifi_band_to_str(band),__func__, wifi_bw_to_str(radio.curr_bw), ret);

	if (ret) {
		ret = nlwifi_get_max_bandwidth(name, &radio.curr_bw);
		libwifi_dbg("[%s] %s called max_bw: %s\n", name, __func__, wifi_bw_to_str(radio.curr_bw));
	}

	*rate_Mbps = radio_maxrate(&radio);
	return ret;
}

static int radio_get_maxrate(const char *name, unsigned long *rate_Mbps)
{
	return radio_get_band_maxrate(name, BAND_ANY, rate_Mbps);
}

static int radio_get_basic_rates(const char *name, int *num, uint32_t *rates_kbps)
{
	libwifi_dbg("[%s] %s called\n", name, __func__);
	return -1;
}

static int radio_get_oper_rates(const char *name, int *num, uint32_t *rates_kbps)
{
	libwifi_dbg("[%s] %s called\n", name, __func__);
	return -1;
}

static int radio_get_supp_rates(const char *name, int *num, uint32_t *rates)
{
	libwifi_dbg("[%s] %s called\n", name, __func__);
	return -1;
}

static int radio_get_diagnostic(const char *ifname, enum wifi_band band,
				struct wifi_radio_diagnostic *diag)
{
	libwifi_dbg("[%s, %s] %s called\n", ifname,  __func__, wifi_band_to_str(band));

	return nlwifi_get_diagnostic(ifname, band, diag, NULL);
}

static int radio_scan(const char *name, struct scan_param *p)
{
	char netdev[16];

	nlwifi_phy_to_netdev_with_type(name, netdev, sizeof(netdev), NLWIFI_MODE_AP_STA);
	libwifi_dbg("[%s, %s] %s called\n", name, netdev, __func__);
	return nlwifi_scan(netdev, p);
}

int radio_scan_ex(const char *name, struct scan_param_ex *sp)
{
	char netdev[16];

	nlwifi_phy_to_netdev_with_type(name, netdev, sizeof(netdev), NLWIFI_MODE_AP_STA);
	libwifi_dbg("[%s, %s] %s called\n", name, netdev, __func__);

	return nlwifi_scan_ex(netdev, sp);
}

int radio_scan_band_ex(const char *name, enum wifi_band band, struct scan_param_ex *sp)
{
	char netdev[16];

	nlwifi_phy_to_netdev_with_type_and_band(name, netdev, sizeof(netdev), NLWIFI_MODE_AP_STA, band);
	libwifi_dbg("[%s, %s, %s] %s called\n", name, netdev, __func__, wifi_band_to_str(band));

	return nlwifi_scan_ex(netdev, sp);
}

static int radio_get_scan_results(const char *name, struct wifi_bss *bsss, int *num)
{
	char netdev[16];

	nlwifi_phy_to_netdev_with_type(name, netdev, sizeof(netdev), NLWIFI_MODE_AP_STA);
	libwifi_dbg("[%s, %s] %s called\n", name, netdev, __func__);
	return nlwifi_get_scan_results(netdev, bsss, num);
}

static int radio_get_band_scan_results(const char *name, enum wifi_band band,
				       struct wifi_bss *bsss, int *num)
{
	char netdev[16];

	if (nlwifi_phy_to_netdev_with_type_and_band(name, netdev, sizeof(netdev), NLWIFI_MODE_AP_STA, band))
		nlwifi_phy_to_netdev(name, netdev, sizeof(netdev));

	libwifi_dbg("[%s, %s, %s] %s called\n", name, netdev, wifi_band_to_str(band), __func__);
	return nlwifi_get_band_scan_results(netdev, band, bsss, num);
}

static int radio_get_bss_scan_result(const char *name, uint8_t *bssid,
				     struct wifi_bss_detail *b)
{
	libwifi_dbg("[%s] %s called\n", name, __func__);
	return -1;
}

static int radio_get_band_noise(const char *name, enum wifi_band band, int *noise)
{
	char netdev[16];

	nlwifi_phy_to_netdev_with_type_and_band(name, netdev, sizeof(netdev), NLWIFI_MODE_AP_STA, band);
	libwifi_dbg("[%s, %s, %s] %s called\n", name, netdev, wifi_band_to_str(band),  __func__);
	if (nlwifi_get_band_noise(netdev, band, noise))
		/* TODO - for 7615 upgrade backports/mt76 */
		*noise = -90;

	return 0;
}

static int radio_get_noise(const char *name, int *noise)
{
	enum wifi_band band;

	if (nlwifi_get_oper_band(name, &band))
		band = BAND_ANY;

	return radio_get_band_noise(name, band, noise);
}

static int radio_get_band_stats(const char *name, enum wifi_band band, struct wifi_radio_stats *s)
{
	libwifi_dbg("[%s, %s] %s called\n", name, wifi_band_to_str(band),  __func__);

	memset(s, 0, sizeof(*s));
	return radio_get_band_noise(name, band, &s->noise);
}

static int radio_get_stats(const char *name, struct wifi_radio_stats *s)
{
	return radio_get_band_stats(name, BAND_ANY, s);
}

static int radio_acs(const char *name, struct acs_param *p)
{
	libwifi_dbg("[%s] %s called\n", name, __func__);
	return -1;
}

static int radio_start_cac(const char *name, int channel, enum wifi_bw bw,
			   enum wifi_cac_method method)
{
	char netdev[16] = {};
	libwifi_dbg("[%s] %s called\n", name, __func__);

	WARN_ON(nlwifi_phy_to_netdev_with_type_and_band(name, netdev, sizeof(netdev), NLWIFI_MODE_AP, BAND_5));
	return nlwifi_start_cac(netdev, channel, bw, method);
}

static int radio_stop_cac(const char *name, uint32_t channel, enum wifi_bw bw)
{
	char netdev[16] = {};
	libwifi_dbg("[%s] %s called\n", name, __func__);

	WARN_ON(nlwifi_phy_to_netdev_with_type_and_band(name, netdev, sizeof(netdev), NLWIFI_MODE_AP, BAND_5));
	return nlwifi_stop_cac(netdev, channel, bw);
}

static int radio_get_opclass_preferences(const char *name,
					 struct wifi_opclass *opclass,
					 int *num)
{
	libwifi_dbg("[%s] %s called\n", name, __func__);
	return wifi_get_opclass_pref(name, num, opclass);
}

static int radio_get_band_opclass_preferences(const char *name,
				enum wifi_band band,
				struct wifi_opclass *opclass,
				int *num)
{
	libwifi_dbg("[%s, %s] %s called\n", name, wifi_band_to_str(band), __func__);
	return wifi_get_band_opclass_pref(name, band, num, opclass);
}

static int radio_simulate_radar(const char *name, struct wifi_radar_args *radar)
{
	if (WARN_ON(!radar))
		return -1;

	libwifi_dbg("[%s] %s called ch:%d, bandwidth:%d, type:0x%x, subband:0x%x\n",
	            name, __func__, radar->channel, radar->bandwidth, radar->type,
	            radar->subband_mask);

	return -1;
}

static int radio_get_param(const char *name, const char *param, int *len, void *val)
{
	libwifi_dbg("[%s] %s called\n", name, __func__);
	return -1;
}

static int radio_set_param(const char *name, const char *param, int len, void *val)
{
	libwifi_dbg("[%s] %s called\n", name, __func__);
	return -1;
}

static int radio_get_hwaddr(const char *name, uint8_t *hwaddr)
{
	int fd;
	char path[256];
	char buf[512] = {};

	libwifi_dbg("[%s] %s called\n", name, __func__);

	snprintf(path, sizeof(path), "/sys/class/ieee80211/%s/macaddress", name);
	fd = open(path, O_RDONLY);

	if (WARN_ON(fd < 0))
		return -1;

	if (!read(fd, buf, sizeof(buf))) {
		close(fd);
		return -1;
	}

	if (sscanf(buf, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
				&hwaddr[0],
				&hwaddr[1],
				&hwaddr[2],
				&hwaddr[3],
				&hwaddr[4],
				&hwaddr[5]) != 6) {
		close(fd);
		return -1;
	}

	close(fd);
	return 0;
}

static int radio_add_iface(const char *name, enum wifi_mode m, char *argv[])
{
	char buf[1024];
	char *ifname = NULL;
	int i = 0;

	libwifi_dbg("[%s] %s called\n", name, __func__);

	while (argv[i]) {
		if (!strcmp(argv[i], "ifname")) {
			ifname = argv[i+1];
			break;
		}

		i+=2;
	}

	if (WARN_ON(!ifname))
		return -1;

	switch (m) {
	case WIFI_MODE_STA:
		chrCmd(buf, sizeof(buf), "iw %s interface add %s type managed", name, ifname);
		break;
	case WIFI_MODE_AP:
		chrCmd(buf, sizeof(buf), "iw %s interface add %s type ap", name, ifname);
		break;
	case WIFI_MODE_MONITOR:
		chrCmd(buf, sizeof(buf), "iw %s interface add %s type monitor", name, ifname);
		break;
	default:
		return -1;
	}

	return 0;
}

static int radio_del_iface(const char *name, const char *ifname)
{
	char buf[1024];

	libwifi_dbg("[%s] %s called\n", name, __func__);

	chrCmd(buf, sizeof(buf), "iw %s del", ifname);

	return 0;
}

static int radio_list_iface(const char *name, struct iface_entry *iface, int *num)
{
	char path[256];
	struct dirent *p;
	struct iface_entry *entry;
	int count = 0;
	DIR *d;

	snprintf(path, sizeof(path), "/sys/class/ieee80211/%s/device/net", name);
	if (WARN_ON(!(d = opendir(path))))
		return -1;

	while ((p = readdir(d))) {
		if (!strcmp(p->d_name, "."))
			continue;
		if (!strcmp(p->d_name, ".."))
			continue;
		if (WARN_ON(count >= *num))
			break;

		libwifi_dbg("[%s] iface  %s\n", name, p->d_name);
		entry = &iface[count];
		memset(entry->name, 0, 16);
		memcpy(entry->name, p->d_name, 15);
		nlwifi_get_mode(entry->name, &entry->mode);
		count++;
	}

	closedir(d);
	*num = count;

	return 0;
}

static int radio_channels_info_band(const char *name, enum wifi_band band, struct chan_entry *channel, int *num)
{
	char cc[3] = {0};
	int ret;
	int i;

	ret = nlwifi_channels_info_band(name, band, channel, num);
	if (ret)
		return ret;

	WARN_ON(nlwifi_get_country(name, cc));
	if (!strcmp(cc, "US") || !strcmp(cc, "JP"))	/* FIXME-CR: other non-ETSI countries */
		return 0;

	/* Check weather channels - just in case of regulatory issue */
	for (i = 0; i < *num; i++) {
		switch (channel[i].channel) {
		case 120:
		case 124:
		case 128:
			channel[i].cac_time = 600;
			break;
		default:
			break;
		}
	}

	return ret;
}

static int radio_channels_info(const char *name, struct chan_entry *channel, int *num)
{
	return radio_channels_info_band(name, BAND_ANY, channel, num);
}

static int radio_get_band_ifstatus(const char *name, enum wifi_band band, ifstatus_t *f)
{
	char path[256];
	struct dirent *p;
	DIR *d;
	ifstatus_t ifstatus;

	libwifi_dbg("[%s] %s called\n", name, __func__);

	*f = 0;
	if (get_ifstatus(name, f)) {
		snprintf(path, sizeof(path), "/sys/class/ieee80211/%s/device/net", name);

		if (WARN_ON(!(d = opendir(path))))
			return -1;

		while ((p = readdir(d))) {
			enum wifi_band nband = BAND_UNKNOWN;

			if (!strcmp(p->d_name, "."))
				continue;
			if (!strcmp(p->d_name, ".."))
				continue;

			nlwifi_iface_get_band(p->d_name, &nband);
			if (band != BAND_ANY && nband != band)
				continue;

			if (!get_ifstatus(p->d_name, &ifstatus)) {
				if (ifstatus & IFF_UP) {
					*f |= IFF_UP;
					break;
				}
			}
		}

		closedir(d);
	}

	return 0;
}

static int radio_get_ifstatus(const char *name, ifstatus_t *f)
{
	return radio_get_band_ifstatus(name, BAND_ANY, f);
}

/* common iface callbacks */

static int iface_start_wps(const char *ifname, struct wps_param wps)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return hostapd_cli_iface_start_wps(ifname, wps);
}

static int iface_stop_wps(const char *ifname)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return hostapd_cli_iface_stop_wps(ifname);
}

static int iface_get_wps_status(const char *ifname, enum wps_status *s)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return hostapd_cli_iface_get_wps_status(ifname, s);
}

static int iface_get_wps_pin(const char *ifname, unsigned long *pin)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return hostapd_cli_iface_get_wps_ap_pin(ifname, pin);
}

static int iface_set_wps_pin(const char *ifname, unsigned long pin)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return hostapd_cli_iface_set_wps_ap_pin(ifname, pin);
}

static int iface_get_wps_device_info(const char *ifname, struct wps_device *info)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return -1;
}

static int iface_get_caps(const char *ifname, struct wifi_caps *caps)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return nlwifi_ap_get_caps(ifname, caps);
}

static int iface_get_mode(const char *ifname, enum wifi_mode *mode)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return nlwifi_get_mode(ifname, mode);
}

static int iface_get_security(const char *ifname, uint32_t *auth, uint32_t *enc)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);

	*auth = 0;
	*enc = 0;

	return -1;
}

static int iface_add_vendor_ie(const char *ifname, struct vendor_iereq *req)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return hostapd_cli_iface_add_vendor_ie(ifname, req);
}

static int iface_del_vendor_ie(const char *ifname, struct vendor_iereq *req)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return hostapd_cli_iface_del_vendor_ie(ifname, req);
}

static int iface_get_vendor_ies(const char *ifname, struct vendor_ie *ies, int *num_ies)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return -1;
}

static int iface_get_param(const char *ifname, const char *param, int *len, void *val)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return -1;
}

static int iface_set_param(const char *ifname, const char *param, int len, void *val)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return -1;
}

static int iface_vendor_cmd(const char *ifname, uint32_t vid, uint32_t subcmd,
			    uint8_t *in, int inlen, uint8_t *out, int *outlen)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return nlwifi_vendor_cmd(ifname, vid, subcmd, in, inlen, out, outlen);
}

static int iface_subscribe_frame(const char *ifname, uint8_t type, uint8_t stype)
{
	enum wifi_mode mode;
	int ret = -1;

	libwifi_dbg("[%s] %s called\n", ifname, __func__);

	ret = iface_get_mode(ifname, &mode);
	if (WARN_ON(ret))
		return ret;

	switch (mode) {
	case WIFI_MODE_AP:
		ret = hostapd_ubus_iface_subscribe_frame(ifname, type, stype);
		break;
	default:
		ret = -1;
		break;
	}

	return ret;
}

static int iface_unsubscribe_frame(const char *ifname, uint8_t type, uint8_t stype)
{
	enum wifi_mode mode;
	int ret = -1;

	libwifi_dbg("[%s] %s called\n", ifname, __func__);

	ret = iface_get_mode(ifname, &mode);
	if (WARN_ON(ret))
		return ret;

	switch (mode) {
	case WIFI_MODE_AP:
		ret = hostapd_ubus_iface_unsubscribe_frame(ifname, type, stype);
		break;
	default:
		ret = -1;
		break;
	}

	return ret;
}

static int iface_send_action_frame(const char *ifname, struct wifi_frame_arg *arg,
				   uint8_t *frame, size_t len, uint64_t *cookie)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);

#ifndef ACTION_FRAME_SIZE
#define ACTION_FRAME_SIZE 2022
#endif

	if (len > ACTION_FRAME_SIZE)
		return -EINVAL;

	return hostapd_cli_iface_send_action_frame(ifname, arg, frame, len, cookie);
}

static int iface_dpp_listen(const char *ifname, uint32_t freq)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return hostapd_cli_iface_dpp_listen(ifname, freq);
}

static int iface_dpp_stop_listen(const char *ifname)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return hostapd_cli_iface_dpp_stop_listen(ifname);
}

static int iface_set_4addr(const char *ifname, bool enable)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return -1;
}

static int iface_get_4addr(const char *ifname, bool *enable)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return nlwifi_get_4addr(ifname, enable);
}

static int iface_get_4addr_parent(const char *ifname, char *parent)
{
	enum wifi_mode mode;
	int ret;
	libwifi_dbg("[%s] %s called\n", ifname, __func__);

	ret = nlwifi_get_mode(ifname, &mode);
	if (ret)
		return ret;

	if (mode != WIFI_MODE_AP_VLAN)
		return -1;

	ret = hostapd_cli_get_4addr_parent((char *)ifname, parent);
	libwifi_dbg("[%s] %s ret %d report parent %s\n", ifname, __func__, ret, parent);
	return ret;
}

static int iface_set_vlan(const char *ifname, struct vlan_param vlan)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return -1;
}

static int iface_link_measure(const char *ifname, uint8_t *sta)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);

	if (!sta || hwaddr_is_zero(sta)) {
		libwifi_dbg("[%s] %s invalid arg\n", ifname, __func__);
		return -1;
	}

	return hostapd_cli_rrm_lm_req(ifname, sta);
}

/* ap interface ops */
static int iface_ap_info_band(const char *ifname, enum wifi_band band, struct wifi_ap *ap)
{
	struct wifi_mlo_link link[WIFI_NUM_LINK_PER_MLD] = {0};
	int num_links = WIFI_NUM_LINK_PER_MLD;
	char *ctrl_iface = NULL;
	ifstatus_t flags = 0;
	struct wifi_bss *bss;
	int ret;

	libwifi_dbg("[%s, %s] %s called\n", ifname, wifi_band_to_str(band), __func__);

	if (!is_valid_ap_iface(ifname)) {
		libwifi_dbg("[%s] %s not valid ap iface\n", ifname, __func__);
		return -1;
	}

	get_ifstatus(ifname, &flags);
	if (!(flags & IFF_UP)) {
		libwifi_dbg("%s: %s not UP\n", __func__, ifname);
		errno = -EIO;
		return -1;
	}

	ret = wifi_get_mlo_links(ifname, band, link, &num_links);
	if (ret)
		num_links = 0;

	if (!ret && num_links > 1) {
		libwifi_dbg("%s is for non-mld or 1-link-mld only; use ap_mld_info()\n",
			    __func__);
		errno = -ENOTSUP;
		return -1;
	}

	if (num_links &&
	    link[0].mode != WIFI_MODE_AP && link[0].mode != WIFI_MODE_AP_VLAN) {
		errno = -ENOTSUP;
		return -1;
	}

	ap->enabled = true;
	bss = &ap->bss;
	memset(bss, 0, sizeof(*bss));

	bss->mode = WIFI_INFRA;
	bss->mlo_eml_mode = WIFI_EML_MODE_NONE;
	if (num_links) {
		bss->mlo_link_id = link[0].id;
		bss->band = link[0].band;
		bss->channel = link[0].channel;
		bss->ccfs0 = link[0].ccfs0;
		bss->ccfs1 = link[0].ccfs1;
		bss->curr_bw = link[0].bandwidth;
		memcpy(bss->bssid, link[0].macaddr, 6);
		bss->ssidlen = link[0].ssidlen;
		memcpy(bss->ssid, link[0].ssid, link[0].ssidlen);
		if (link[0].ssidlen == 0)
			ap->enabled = false;
	} else {
		uint32_t ch = 0;

		ret = nlwifi_get_bssid(ifname, bss->bssid);
		if (ret)
			libwifi_dbg("get_bssid error ret = %d\n", ret);

		ret = nlwifi_get_ssid(ifname, bss->ssid, &bss->ssidlen);
		if (ret)
			libwifi_dbg("get_ssid error ret = %d\n", ret);

		if (bss->ssidlen == 0)
			ap->enabled = false;

		nlwifi_get_channel(ifname, &ch, &bss->curr_bw);
		bss->channel = (uint8_t)ch;
		nlwifi_get_bandwidth(ifname, &bss->curr_bw);
		nlwifi_iface_get_band(ifname, &bss->band);
		nlwifi_get_band_seg0_seg1(ifname, band, &bss->ccfs0, &bss->ccfs1);
	}

	ret = wifi_get_ctrl_interface_band(ifname, band, &ctrl_iface);
	if (WARN_ON(ret))
		return ret;

	/* get security, capabilities, oper_std and bssload from beacon buffer */
	hostapd_cli_iface_ap_info(ctrl_iface, ap);

	if (bss->ssidlen == 0)
		ap->enabled = false;

	correct_oper_std_by_band(bss->band, &bss->oper_std);
	nlwifi_get_band_supp_stds(ifname, bss->band, &ap->bss.supp_std);

	hostapd_cli_get_security_cap(ctrl_iface, &ap->sec.supp_modes);
	correct_supp_sec_by_band(bss->band, &ap->sec.supp_modes);

	/* TODO ap->isolate_enabled */

	free(ctrl_iface);

	return 0;
}

static int iface_ap_info(const char *ifname, struct wifi_ap *ap)
{
	return iface_ap_info_band(ifname, BAND_ANY, ap);
}

static int iface_get_bssid(const char *ifname, uint8_t *bssid)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return nlwifi_get_bssid(ifname, bssid);
}

static int iface_get_ssid(const char *ifname, uint8_t *ssid, size_t *ssidlen)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
        return nlwifi_get_ssid(ifname, ssid, ssidlen);
}

static int iface_get_stats(const char *ifname, struct wifi_ap_stats *s)
{
	ifstatus_t flags = 0;
	libwifi_dbg("[%s] %s called\n", ifname, __func__);

	get_ifstatus(ifname, &flags);
	if (!(flags & IFF_UP)) {
		libwifi_dbg("%s: %s not UP\n", __func__, ifname);
		return -1;
	}

	memset(s, 0, sizeof(*s));
	return 0;
}

static int get_ctrl_interface_band(const char *ifname, enum wifi_band band, char **ctrl_interface)
{
	*ctrl_interface = strdup(ifname);
	return 0;
}

static int get_ctrl_interface(const char *ifname, char **ctrl_interface)
{
	return get_ctrl_interface_band(ifname, BAND_ANY, ctrl_interface);
}

static int iface_get_beacon_ies_band(const char *ifname, enum wifi_band band, uint8_t *ies, int *len)
{
	char *ctrl_iface = NULL;
	int ret;

	libwifi_dbg("[%s, %s] %s called\n", ifname, wifi_band_to_str(band), __func__);

	ret = wifi_get_ctrl_interface_band(ifname, band, &ctrl_iface);
	if (WARN_ON(ret))
		return ret;

	ret = hostapd_cli_get_beacon_ies(ctrl_iface, ies, (size_t *)len);
	free(ctrl_iface);

	return ret;
}

static int iface_get_beacon_ies(const char *ifname, uint8_t *ies, int *len)
{
	return iface_get_beacon_ies_band(ifname, BAND_ANY, ies, len);
}

static void remove_mac_duplicates(uint8_t *stas, int *num_stas)
{
	uint8_t stamacs[256 * 6] = {0};
	int num = 0;
	int limit = *num_stas;
	int i, j;

	if (WARN_ON(limit > 256))
		return;

	for(i = 0; i < limit; i++) {
		uint8_t *mac;
		int skip;

		mac = &stas[i * 6];
		skip = 0;

		/* Check if already there */
		for (j = 0; j < num; j++) {
			if (!memcmp(mac, &stamacs[j*6], 6)) {
				skip = 1;
				break;
			}
		}

		if (skip) {
			libwifi_dbg("%s skip " MACSTR "\n", __func__, MAC2STR(mac));
			continue;
		}

		memcpy(&stamacs[num*6], mac, 6);
		num++;
	}

	memcpy(stas, stamacs, num *6);
	*num_stas = num;
}

static int iface_get_assoclist_band(const char *ifname, enum wifi_band band, uint8_t *stas, int *num_stas)
{
	struct wifi_mlo_link links[8];
	int num = ARRAY_SIZE(links);
	char *ctrl_iface = NULL;
	uint8_t *stations = stas;
	int max = *num_stas;
	int nums = 0;
	int ret = -1;
	int i;

	libwifi_dbg("[%s, %s] %s called\n", ifname, wifi_band_to_str(band), __func__);

	if (!is_valid_ap_iface(ifname)) {
		*num_stas = 0;
		return -1;
	}

	ret = nlwifi_get_mlo_links(ifname, band, links, &num);
	if (!ret && num > 1) {
		for (i = 0; i < num; i++) {
			uint8_t stamacs[256 * 6] = {};
			int stanum = 256;
			int j;

			ctrl_iface = NULL;
			ret = wifi_get_ctrl_interface_band(ifname, links[i].band, &ctrl_iface);
			if (ret)
				continue;

			ret = hostapd_iface_get_assoclist(ctrl_iface, stamacs, &stanum);
			free(ctrl_iface);

			if (ret)
				continue;

			for (j = 0; j < stanum; j+=6)
				libwifi_dbg("[%s] " MACSTR "\n", ctrl_iface, MAC2STR(&stamacs[j]));

			if (stanum < (max - nums)) {
				memcpy(stations, stamacs, stanum *6);
				nums += stanum;
				stations = &stas[nums * 6];
			}
		}

		*num_stas = nums;

		remove_mac_duplicates(stas, num_stas);
		return 0;
	}

	ret = wifi_get_ctrl_interface_band(ifname, band, &ctrl_iface);
	if (WARN_ON(ret))
		return ret;

	ret = hostapd_iface_get_assoclist(ctrl_iface, stas, num_stas);
	free(ctrl_iface);

	return ret;
}

static int iface_get_channel(const char *ifname, struct wifi_channel *ch)
{
	int ret;

	libwifi_dbg("[%s] %s called\n", ifname, __func__);

	ret = nlwifi_iface_get_channel(ifname, ch);
	hostapd_cli_get_ccfs0_ccfs1(ifname, &ch->ccfs0, &ch->ccfs1);

	return ret;
}

static int iface_get_assoclist(const char *ifname, uint8_t *stas, int *num_stas)
{
#define NUM_STA_MAX	256

	uint8_t stamacs[NUM_STA_MAX * 6] = {0};
	int num = NUM_STA_MAX;
	int limit = *num_stas;
	int ret = -1;

	ret = nlwifi_get_assoclist(ifname, stas, num_stas);
	if (WARN_ON(ret)) {
		*num_stas = 0;
		return ret;
	}

	/* union with hostapad's view of assoclist */
	ret = iface_get_assoclist_band(ifname, BAND_ANY, stamacs, &num);
	if (!ret) {
		int num_f = *num_stas;

		for (int i = 0; i < num; i++) {
			int has = 0;

			for (int j = 0; j < *num_stas; j++) {
				if (!memcmp(&stas[j * 6], &stamacs[i * 6], 6)) {
					has = 1;
					break;
				}
			}

			if (!has) {
				if (num_f < limit) {
					memcpy(&stas[num_f * 6], &stamacs[i * 6], 6);
					num_f++;
				}
			}
		}

		*num_stas = num_f;
	}

#undef NUM_STA_MAX

	return ret;
}

static int iface_get_blocked_stas(const char *ifname, enum wifi_band band, uint8_t *stas, int *num_stas)
{
	char *ctrl_iface = NULL;
	int ret;

	libwifi_dbg("[%s, %s] %s called\n", ifname, wifi_band_to_str(band), __func__);

	ret = wifi_get_ctrl_interface_band(ifname, band, &ctrl_iface);
	if (WARN_ON(ret))
		return ret;

	ret = hostapd_get_blocked_stas(ctrl_iface, stas, num_stas);
	free(ctrl_iface);

	return ret;
}

static int iface_get_snr_exp_tp(const char *ifname, enum wifi_band band,
				uint8_t *macaddr, struct wifi_sta *sta)
{
	uint32_t max_rate;
	enum wifi_bw bw = BW20;
	int busy = 0;
	int noise;
	int rssi;
	int snr;

        if (radio_get_band_noise(ifname, band, &noise))
                noise = -90;
	rssi = sta->rssi[0];
	snr = rssi - noise;

	nlwifi_get_band_busy(ifname, band, &busy);

	bw = wifi_bw_to_enum_bw(sta->rate.m.bw);
	max_rate = wifi_get_estimated_throughput(snr, bw, &sta->caps, sta->rate.m.nss, busy);

	/* Assume 85% airtime usage, and 20% for TCP ACK */
	sta->est_rx_thput = (max_rate * 85 * 80) / 10000;
	sta->est_tx_thput = (max_rate * 85 * 80) / 10000;

	return 0;
}

static int iface_get_sta_info_band(const char *ifname, enum wifi_band band, uint8_t *addr, struct wifi_sta *info)
{
	const char *iface;
	char *ctrl_iface = NULL;
	char ifname_wds[256] = { 0 };
	enum wifi_bw bw;
	uint32_t ch;
	int ret = 0;

	libwifi_dbg("[%s, %s] %s called\n", ifname, wifi_band_to_str(band), __func__);

	ret = wifi_get_ctrl_interface_band(ifname, band, &ctrl_iface);
	if (WARN_ON(ret))
		return ret;

	if (hostapd_cli_is_wds_sta(ctrl_iface, addr, ifname_wds, sizeof(ifname_wds))) {
		libwifi_dbg("[%s] has virtual AP iface[%s]\n", ifname, ifname_wds);
		iface = ifname_wds;
		nlwifi_get_band_channel(iface, band, &ch, &bw);
		info->rate.m.bw = wifi_bw_enum2MHz(bw);
		info->channel = ch;
		info->bandwidth = info->rate.m.bw;
	} else {
		iface = ifname;
	}

	if (band == BAND_ANY)
		WARN_ON(nlwifi_get_oper_band(ifname, &band));

	info->band = band;

	ret = nlwifi_get_sta_info_band(iface, band, addr, info);
	if (!ret) {
		uint16_t ap_width, sta_width;
		enum wifi_bw ap_bw;

		ret = hostapd_cli_iface_get_sta_info(ctrl_iface, addr, info);
		if (!ret && info->rate.m.bw == 0) {
			WARN_ON(nlwifi_get_band_channel(iface, band, &ch, &ap_bw));

			ap_width = wifi_bw_enum2MHz(ap_bw);
			sta_width = wifi_get_max_bandwidth_from_caps(ifname, band, &info->caps, info->cbitmap);

			info->rate.m.bw = min(ap_width, sta_width);
			info->bandwidth = wifi_bw_to_enum_bw(info->rate.m.bw);
			info->channel = ch;
		}

		info->oper_std = wifi_standard_from_caps(ifname, band, &info->caps);
	}

	if (!ret) {
		bw = wifi_bw_to_enum_bw(info->rate.m.bw);
		info->maxrate = wifi_get_estimated_throughput(80, bw, &info->caps, info->rate.m.nss, 0);
	}

	iface_get_snr_exp_tp(ifname, band, addr, info);
	free(ctrl_iface);

	return ret;
}

static int iface_get_sta_info(const char *ifname, uint8_t *addr, struct wifi_sta *info)
{
	return iface_get_sta_info_band(ifname, BAND_ANY, addr, info);
}

static int iface_get_sta_stats(const char *ifname, uint8_t *addr, struct wifi_sta_stats *s)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return -1;
}

static int iface_disconnect_sta(const char *ifname, uint8_t *sta, uint16_t reason)
{
	libwifi_dbg("[%s] %s called reason[%d]\n", ifname, __func__, reason);
	return hostapd_cli_disconnect_sta(ifname, sta, reason);
}

static int iface_block_sta(const char *ifname, enum wifi_band band, uint8_t *sta, int block)
{
	char *ctrl_iface = NULL;
	int ret;

	libwifi_dbg("[%s, %s] %s called " MACSTR " %s\n", ifname, wifi_band_to_str(band),
		    __func__, MAC2STR(sta), block ? "block" : "unblock");

	ret = wifi_get_ctrl_interface_band(ifname, band, &ctrl_iface);
	if (WARN_ON(ret))
		return ret;

	ret = hostapd_iface_block_sta(ctrl_iface, sta, block);
	free(ctrl_iface);

	return ret;
}

static int iface_restrict_sta(const char *ifname, uint8_t *sta, int enable)
{
	libwifi_dbg("[%s] %s called " MACSTR " %s\n", ifname, __func__,
		    MAC2STR(sta), enable ? "enable" : "disable");

	return hostapd_iface_block_sta(ifname, sta, !enable);
}

static int iface_monitor_sta(const char *ifname, uint8_t *sta, struct wifi_monsta_config *cfg)
{
	libwifi_dbg("[%s] %s called " MACSTR " %s\n", ifname, __func__,
		    MAC2STR(sta), cfg->enable ? "enable" : "disable");

	/* Enable event driven probe req monitoring */
	return hostapd_ubus_iface_monitor_sta(ifname, sta, cfg);
}

static int iface_probe_sta(const char *ifname, uint8_t *sta)
{
	libwifi_dbg("[%s] %s called " MACSTR "\n", ifname, __func__, MAC2STR(sta));

	return hostapd_cli_probe_sta(ifname, sta);
}

static int iface_get_monitor_sta(const char *ifname, uint8_t *sta, struct wifi_monsta *mon)
{
	struct wifi_sta info = {};
	int ret;
	int i;

	libwifi_dbg("[%s] %s " MACSTR " called\n", ifname, __func__, MAC2STR(sta));

	memset(mon, 0, sizeof(*mon));

	/* Check if added for monitoring */
	ret = hostapd_ubus_iface_get_monitor_sta(ifname, sta, mon);
	if (ret)
		return ret;

	/* Get RSSI from nlwifi (from data frames) for connected stations */
	ret = nlwifi_get_sta_info(ifname, sta, &info);
	if (ret)
		return 0;

	/* Check if connected */
	if (!memcmp(mon->macaddr, info.macaddr, 6)) {
		WARN_ON(sizeof(mon->rssi) != sizeof(info.rssi));
		for (i = 0; i < sizeof(mon->rssi); i++)
			mon->rssi[i] = info.rssi[i];

		mon->rssi_avg = info.rssi_avg;
		mon->last_seen = info.idle_time;
		memcpy(mon->macaddr, info.macaddr, 6);
	}

	return 0;
}

static int iface_get_monitor_stas(const char *ifname, struct wifi_monsta *stas, int *num)
{
	struct wifi_monsta *mon;
	uint8_t sta[6];
	int ret;
	int i;

	libwifi_dbg("[%s] %s called\n", ifname, __func__);

	ret = hostapd_ubus_iface_get_monitor_stas(ifname, stas, num);
	if (WARN_ON(ret))
		return ret;

	for (i = 0; i < *num; i++) {
		mon = &stas[i];
		memcpy(sta, mon->macaddr, sizeof(sta));
		WARN_ON(iface_get_monitor_sta(ifname, sta, mon));
	}

	return ret;
}

static int iface_add_neighbor(const char *ifname, struct nbr nbr)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return hostapd_cli_iface_add_neighbor(ifname, &nbr, sizeof(nbr));
}

static int iface_del_neighbor(const char *ifname, unsigned char *bssid)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return hostapd_cli_iface_del_neighbor(ifname, bssid);
}

static int iface_get_neighbor_list(const char *ifname, struct nbr *nbr, int *nr)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return hostapd_cli_iface_get_neighbor_list(ifname, nbr, nr);
}

static int iface_req_beacon_report(const char *ifname, uint8_t *sta,
					struct wifi_beacon_req *param, size_t param_sz)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);

	if (param_sz > 0 && param != NULL)
		return hostapd_cli_iface_req_beacon(ifname, sta, param, param_sz);

	/* No params passed - use default (minimal) configuration */
	return hostapd_cli_iface_req_beacon_default(ifname, sta);
}

static int iface_get_beacon_report(const char *ifname, uint8_t *sta,
				   struct sta_nbr *snbr, int *nr)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return -1;
}

#define MAX_BTM_NBR_NUM 64
static int iface_req_bss_transition(const char *ifname, unsigned char *sta,
				    int bsss_nr, unsigned char *bsss, unsigned int tmo)
{
	struct bss_transition_params params = {
		.dialog_token = 1,
	};
	struct nbr nbss[MAX_BTM_NBR_NUM] = {};
	unsigned char *bss;
	int i;

	libwifi_dbg("[%s] %s called " MACSTR " nr %d\n", ifname, __func__,
		    MAC2STR(sta), bsss_nr);

	if (bsss_nr > MAX_BTM_NBR_NUM)
		return -1;

	params.valid_int = (uint8_t)tmo;
	params.pref = bsss_nr ? 1 : 0;
	params.disassoc_imminent = 1;

	for (i = 0; i < bsss_nr; i++) {
		bss = bsss + i * 6;
		memcpy(&nbss[i].bssid, bss, 6);
	}

	return hostapd_cli_iface_req_bss_transition(ifname, sta, bsss_nr, nbss, &params);
}

static int iface_req_btm(const char *ifname, unsigned char *sta,
			 int bsss_nr, struct nbr *bsss, struct wifi_btmreq *b)
{
	struct bss_transition_params params = {
		.dialog_token = 1,
	};

	libwifi_dbg("[%s] %s called " MACSTR " nr %d\n", ifname, __func__,
		    MAC2STR(sta), bsss_nr);

	params.valid_int = b->validity_int;
	params.disassoc_timer = b->disassoc_tmo;

	if (b->dialog_token)
		params.dialog_token = b->dialog_token;

	if (b->mode & WIFI_BTMREQ_PREF_INC || bsss_nr)
		params.pref = 1;
	if (b->mode & WIFI_BTMREQ_ABRIDGED)
		params.abridged = 1;
	if (b->mode & WIFI_BTMREQ_DISASSOC_IMM)
		params.disassoc_imminent = 1;
	if (b->mode & WIFI_BTMREQ_BSSTERM_INC)
		params.bss_term = b->bssterm_dur;

	return hostapd_cli_iface_req_bss_transition(ifname, sta, bsss_nr, bsss, &params);
}

static int iface_get_11rkeys(const char *ifname, unsigned char *sta, uint8_t *r1khid)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return -1;
}

static int iface_set_11rkeys(const char *ifname, struct fbt_keys *fk)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return -1;
}


static int get_sec_chan_offset(uint32_t freq)
{
	int ht40plus[] = { 5180, 5220, 5260, 5300, 5500, 5540, 5580, 5620, 5660, 5700, 5745, 5785, 5805, 5825,
			   5865, 5920,
			   5955, 5995, 6035, 6075, 6115, 6155, 6195, 6235, 6275, 6315, 6355, 6395, 6435,
			   6475, 6515, 6555, 6595, 6635, 6675, 6715, 6755, 6795, 6835, 6875, 6915, 6955,
			   6995, 7035, 7075 };
	int i;

	for (i = 0; i < ARRAY_SIZE(ht40plus); i++) {
		if (freq == ht40plus[i])
			return 1;
	}

	return -1;
}

static uint32_t get_cf1(uint32_t freq, uint32_t bw)
{
	uint32_t cf1 = 0;
	int cf1_bw40[] = { 5190, 5230, 5270, 5310, 5510, 5550, 5590, 5630, 5670, 5710, 5755, 5795, 5815, 5835,
			   5875, 5930,
			   5965, 6005, 6045, 6085, 6125, 6165, 6205, 6245, 6285, 6325, 6365, 6405, 6445,
			   6485, 6525, 6565, 6605, 6645, 6685, 6725, 6765, 6805, 6845, 6885, 6925, 6965,
			   7005, 7045, 7085 };
	int cf1_bw80[] = { 5210, 5290, 5530, 5610, 5690, 5775, 5855,
			   5985, 6065, 6145, 6225, 6305, 6385, 6465,
			   6545, 6625, 6705, 6785, 6865, 6945, 7025 };
	int cf1_bw160[] = { 5250, 5570, 5815, 6025, 6185, 6345, 6505, 6665, 6825, 6985 };
	int cf1_bw320[] = { 6105, 6425, 6745 };
	int i;

	switch (bw) {
	case 320:
		/*
		 * Best if upper layer pass correct cf1 - we can choose
		 * but use wrong one - different than master.
		 */
		for (i = 0; i < ARRAY_SIZE(cf1_bw320); i++) {
			if (freq >= cf1_bw320[i] - 150 &&
			    freq <= cf1_bw320[i] + 150) {
				cf1 = cf1_bw320[i];
				break;
			}
		}
		break;
	case 160:
		for (i = 0; i < ARRAY_SIZE(cf1_bw160); i++) {
			if (freq >= cf1_bw160[i] - 70 &&
			    freq <= cf1_bw160[i] + 70) {
				cf1 = cf1_bw160[i];
				break;
			}
		}
		break;
	case 80:
		for (i = 0; i < ARRAY_SIZE(cf1_bw80); i++) {
			if (freq >= cf1_bw80[i] - 30 &&
			    freq <= cf1_bw80[i] + 30) {
				cf1 = cf1_bw80[i];
				break;
			}
		}
		break;
	case 40:
		for (i = 0; i < ARRAY_SIZE(cf1_bw40); i++) {
			if (freq >= cf1_bw40[i] - 10 &&
			    freq <= cf1_bw40[i] + 10) {
				cf1 = cf1_bw40[i];
				break;
			}
		}
		break;

	default:
		cf1 = freq;
	}

	return cf1;
}

static int iface_chan_switch(const char *ifname, struct chan_switch_param *param)
{
	char *ctrl_iface = NULL;
	enum wifi_band band;
	int ret;

	libwifi_dbg("[%s] %s called\n", ifname, __func__);

	band = wifi_freq_to_band(param->freq);

	switch (param->bandwidth) {
	case 320:
		WARN_ON(!param->cf1);
	case 160:
	case 80:
	case 40:
		if (!param->sec_chan_offset)
			param->sec_chan_offset = get_sec_chan_offset(param->freq);
		if (!param->cf1 && band != BAND_2)
			param->cf1 = get_cf1(param->freq, param->bandwidth);
		break;
	case 8080:
		if (WARN_ON(!param->cf1))
			return -1;
		if (WARN_ON(!param->cf2))
			return -1;
		break;
	case 20:
		if (!param->cf1)
			param->cf1 = param->freq;
		break;
	default:
		break;
	}

	ret = wifi_get_ctrl_interface_band(ifname, band, &ctrl_iface);
	if (ret)
		return ret;

	ret = hostapd_cli_iface_chan_switch(ctrl_iface, param);
	free(ctrl_iface);

	libwifi_dbg("[%s, %s] %s %d/%d ret %d\n", ifname, wifi_band_to_str(band), __func__,
		    param->freq, param->bandwidth, ret);
	return ret;
}

static int iface_mbo_disallow_assoc(const char *ifname, uint8_t reason)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return hostapd_cli_mbo_disallow_assoc(ifname, reason);
}

static int iface_ap_set_state(const char *ifname, bool up)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);

	return hostapd_cli_ap_set_state(ifname, up);
}

static int iface_ap_set_qos_map(const char *ifname,
	struct dscp_pcp_map *map,
	struct dscp_exception *exception,
	int num_exceptions)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);

	return hostapd_cli_ap_set_qos_map(ifname, map, exception,
		num_exceptions);
}

static int iface_ap_send_qos_map_conf(const char *ifname, uint8_t *sta)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);

	return hostapd_cli_ap_send_qos_map_conf(ifname, sta);
}

static int iface_get_mlsta_4addr_info(const char *ifname, uint8_t *macaddr,
				     struct wifi_mlsta *mlsta)
{
	struct wifi_mlo_link aplinks[WIFI_NUM_LINK_PER_MLD] = {0};
	int num_aplinks = WIFI_NUM_LINK_PER_MLD;
	int ret;
	int i;

	ret = wifi_get_mlo_links(ifname, BAND_ANY, aplinks, &num_aplinks);
	if (WARN_ON(ret))
		return ret;

	mlsta->num_link = 0;
	for (i = 0; i < num_aplinks; i++) {
		struct wifi_sta *sta;

		sta = &mlsta->sta[mlsta->num_link];
		ret = iface_get_sta_info_band(ifname, aplinks[i].band, macaddr, sta);
		if (ret)
			continue;

		if (mlsta->num_link >= WIFI_NUM_LINK_PER_MLD)
			break;

		memcpy(mlsta->macaddr, macaddr, 6);

		if (!hwaddr_is_zero(sta->mld_macaddr) &&
		    memcmp(sta->mld_macaddr, sta->macaddr, 6))
			mlsta->mlo_capable = 1;

		memcpy(&mlsta->stats, &sta->stats, sizeof(mlsta->stats));
		mlsta->num_link++;
	}

	return 0;
}

static int iface_get_mlsta_info(const char *ifname, uint8_t *macaddr,
				struct wifi_mlsta *mlsta)
{
	struct wifi_mlo_link aplinks[WIFI_NUM_LINK_PER_MLD] = {0};
	int num_aplinks = WIFI_NUM_LINK_PER_MLD;
	int ret;

	ret = nlwifi_get_mlsta_info(ifname, macaddr, mlsta);
	if (ret)
		return iface_get_mlsta_4addr_info(ifname, macaddr, mlsta);

	if (mlsta->num_link > 1)
		mlsta->mlo_capable = true;

	ret = wifi_get_mlo_links(ifname, BAND_ANY, aplinks, &num_aplinks);
	if (WARN_ON(ret))
		return ret;

	for (int i = 0; i < mlsta->num_link; i++) {
		for (int j = 0; j < num_aplinks; j++) {
			if (mlsta->sta[i].mlo_link_id == aplinks[j].id) {
				struct wifi_sta *sta;

				sta = &mlsta->sta[i];
				memcpy(sta->bssid, aplinks[j].macaddr, 6);

				ret = iface_get_sta_info_band(ifname, aplinks[j].band, macaddr, sta);
				if (WARN_ON(ret))
					return ret;

				if (!(sta->caps.valid & WIFI_CAP_EHT_VALID))
					mlsta->mlo_capable = false;

				break;
			}
		}
	}

	return 0;
}

/* sta interface ops */
static int iface_sta_info(const char *ifname, struct wifi_sta *sta)
{
	struct wifi_interface_info info = {};
	unsigned long maxrate = 0;
	int noise = 0;
	int ret;

	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	ret = nlwifi_sta_info(ifname, sta);
	if(ret)
		return -1;

	if_gethwaddr(ifname, sta->macaddr);
	ret = nlwifi_get_interface(ifname, &info);
	if (ret)
		return -1;

	sta->band = info.band;
	sta->channel = info.channel;
	sta->bandwidth = info.bandwidth;
	sta->is4addr = info.is4addr;
	memcpy(sta->ssid, info.ssid, info.ssidlen);
	sta->ssidlen = info.ssidlen;
	sta->mlo_capable = info.mlo_capable;

	radio_get_band_maxrate(ifname, sta->band, &maxrate);
	sta->maxrate = maxrate;

	nlwifi_get_band_noise(ifname, sta->band, &noise);
	if (!noise)
		noise = -90;
	sta->noise_avg = noise;

	ret = supplicant_sta_info(ifname, sta);

	sta->rate.m.bw = wifi_bw_enum2MHz(info.bandwidth);
	wifi_nss_from_caps(&sta->caps, &sta->rate.m.nss);
	sta->nss = sta->rate.m.nss;
	sta->rate.rate = maxrate;

	iface_get_snr_exp_tp(ifname, sta->band, sta->macaddr, sta);

	return ret;
}

static int iface_mlsta_interface_info(const char *ifname, struct wifi_mlsta *mlsta)
{
	struct wifi_mlo_link link[WIFI_NUM_LINK_PER_MLD] = {};
	int num = ARRAY_SIZE(link);
	int i, j;
	int ret;

	libwifi_dbg("[%s] %s called\n", ifname, __func__);

	memset(mlsta, 0, sizeof(*mlsta));
	ret = nlwifi_mlsta_interface_info(ifname, mlsta);
	if (ret || mlsta->num_link == 0) {
		ret = iface_sta_info(ifname, &mlsta->sta[0]);
		memcpy(mlsta->macaddr, mlsta->sta[0].macaddr, 6);
		mlsta->mlo_capable = false;
		return ret;
	}

	WARN_ON(nlwifi_get_ssid(ifname, mlsta->ssid, &mlsta->ssidlen));

	/* MLO bsta - get all links */
	ret = nlwifi_get_mlo_links(ifname, BAND_ANY, link, &num);
	if (ret)
		return ret;

	for (i = 0; i < mlsta->num_link; i++) {
		struct wifi_sta *sta;

		sta = &mlsta->sta[i];
		for (j = 0; j < num; j++) {
			unsigned long maxrate = 0;
			struct wifi_mlo_link *l;
			char *ctrl_iface = NULL;

			l = &link[j];

			if (l->id != sta->mlo_link_id)
				continue;

			memcpy(sta->macaddr, l->macaddr, 6);
			sta->channel = l->channel;
			sta->bandwidth = l->bandwidth;
			sta->band = l->band;
			sta->is4addr = l->is4addr;

			ret = wifi_get_ctrl_interface_band(ifname, sta->band, &ctrl_iface);
			if (ret)
				continue;

			ret = supplicant_sta_info(ctrl_iface, sta);

			radio_get_band_maxrate(ifname, sta->band, &maxrate);
			sta->maxrate = maxrate;

			free(ctrl_iface);
		}
	}

	return ret;
}

static int iface_sta_get_stats(const char *ifname, struct wifi_sta_stats *s)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return nlwifi_sta_get_stats(ifname, s);
}

static int iface_sta_get_ap_info(const char *ifname, struct wifi_bss *info)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);
	return -1;
}

static int iface_sta_disconnect_ap(const char *ifname, uint32_t reason)
{
	libwifi_dbg("[%s] %s called reason %d\n", ifname, __func__, reason);
	return hostapd_cli_sta_disconnect_ap(ifname, reason);
}

static int iface_get_mlo_links(const char *ifname, enum wifi_band band,
			       struct wifi_mlo_link *link, int *num)
{
	libwifi_dbg("[%s] %s called\n", ifname, __func__);

	return nlwifi_get_mlo_links(ifname, band, link, num);
}


const struct wifi_driver default_wifi_driver = {
	.name = "wlan,phy",
	.info = nlwifi_driver_info,

	/* Radio/phy callbacks */
	.radio.is_multiband = nlwifi_radio_is_multiband,
	.radio.info = radio_info,
	.radio.info_band = radio_info_band,
	.get_supp_band = radio_get_supp_band,
	.get_oper_band = radio_get_oper_band,
	.radio.get_ifstatus = radio_get_ifstatus,
	.radio.get_band_ifstatus = radio_get_band_ifstatus,
	.radio.get_caps = radio_get_caps,
	.radio.get_band_caps = nlwifi_radio_get_band_caps,
	.radio.get_supp_stds = radio_get_supp_stds,
	.radio.get_band_supp_stds = nlwifi_get_band_supp_stds,
	.get_oper_stds = radio_get_oper_stds,
	.radio.get_band_oper_stds = radio_get_band_oper_stds,

	.get_country = radio_get_country,
	.get_channel = radio_get_channel,
	.radio.get_band_channel = nlwifi_get_band_channel,
	.set_channel = radio_set_channel,
	.get_supp_channels = radio_get_supp_channels,
	.get_oper_channels = radio_get_oper_channels,

	.get_curr_opclass = radio_get_curr_opclass,
	.radio.get_band_curr_opclass = radio_get_band_curr_opclass,

	.get_bandwidth = radio_get_bandwidth,
	.get_supp_bandwidths = nlwifi_get_supp_bandwidths,
	.radio.get_band_supp_bandwidths = nlwifi_get_band_supp_bandwidths,
	.get_maxrate = radio_get_maxrate,
	.radio.get_band_maxrate = radio_get_band_maxrate,
	.radio.get_basic_rates = radio_get_basic_rates,
	.radio.get_oper_rates = radio_get_oper_rates,
	.radio.get_supp_rates = radio_get_supp_rates,
	.radio.get_stats = radio_get_stats,
	.radio.get_band_stats = radio_get_band_stats,
	.radio.get_diagnostic = radio_get_diagnostic,

	.scan = radio_scan,
	.scan_ex = radio_scan_ex,
	.radio.scan_band_ex = radio_scan_band_ex,
	.get_scan_results = radio_get_scan_results,
	.radio.get_band_scan_results = radio_get_band_scan_results,
	.get_bss_scan_result = radio_get_bss_scan_result,

	.get_noise = radio_get_noise,
	.radio.get_band_noise = radio_get_band_noise,

	.acs = radio_acs,
	.start_cac = radio_start_cac,
	.stop_cac = radio_stop_cac,
	.get_opclass_preferences = radio_get_opclass_preferences,
	.radio.get_band_opclass_preferences = radio_get_band_opclass_preferences,
	.simulate_radar = radio_simulate_radar,

	.radio.get_param = radio_get_param,
	.radio.set_param = radio_set_param,

	.radio.get_hwaddr = radio_get_hwaddr,

	.add_iface = radio_add_iface,
	.del_iface = radio_del_iface,
	.list_iface = radio_list_iface,
	.channels_info = radio_channels_info,
	.radio.channels_info_band = radio_channels_info_band,

	/* Interface/vif common callbacks */
	.iface.start_wps = iface_start_wps,
	.iface.stop_wps = iface_stop_wps,
	.iface.get_wps_status = iface_get_wps_status,
	.iface.get_wps_pin = iface_get_wps_pin,
	.iface.set_wps_pin = iface_set_wps_pin,
	.iface.get_wps_device_info = iface_get_wps_device_info,

	.iface.get_caps = iface_get_caps,
	.iface.get_mode = iface_get_mode,
	.iface.get_security = iface_get_security,

	.get_bssid = iface_get_bssid,
	.get_ssid = iface_get_ssid,

	.iface.add_vendor_ie = iface_add_vendor_ie,
	.iface.del_vendor_ie = iface_del_vendor_ie,
	.iface.get_vendor_ies = iface_get_vendor_ies,

	.iface.get_param = iface_get_param,
	.iface.set_param = iface_set_param,
	.vendor_cmd = iface_vendor_cmd,
	.iface.subscribe_frame = iface_subscribe_frame,
	.iface.unsubscribe_frame = iface_unsubscribe_frame,
	.iface.send_action_frame = iface_send_action_frame,
	.iface.dpp_listen = iface_dpp_listen,
	.iface.dpp_stop_listen = iface_dpp_stop_listen,
	.set_4addr = iface_set_4addr,
	.get_4addr = iface_get_4addr,
	.get_4addr_parent = iface_get_4addr_parent,
	.set_vlan = iface_set_vlan,
	.link_measure = iface_link_measure,
	.get_mlo_links = iface_get_mlo_links,

        /* Interface/vif ap callbacks */
	.iface.ap_info = iface_ap_info,
	.iface.ap_info_band = iface_ap_info_band,
	.iface.get_stats = iface_get_stats,
	.get_beacon_ies = iface_get_beacon_ies,
	.iface.get_beacon_ies_band = iface_get_beacon_ies_band,

	.get_assoclist = iface_get_assoclist,
	.iface.get_assoclist_band = iface_get_assoclist_band,
	.iface.if_get_channel = iface_get_channel,
	.get_sta_info = iface_get_sta_info,
	.iface.get_sta_info_band = iface_get_sta_info_band,
	.get_sta_stats = iface_get_sta_stats,
	.disconnect_sta = iface_disconnect_sta,
	.restrict_sta = iface_restrict_sta,
	.iface.probe_sta = iface_probe_sta,
	.monitor_sta = iface_monitor_sta,
	.get_monitor_sta = iface_get_monitor_sta,
	.get_monitor_stas = iface_get_monitor_stas,
	.iface.add_neighbor = iface_add_neighbor,
	.iface.del_neighbor = iface_del_neighbor,
	.iface.get_neighbor_list = iface_get_neighbor_list,
	.iface.req_beacon_report = iface_req_beacon_report,
	.iface.get_beacon_report = iface_get_beacon_report,

	.iface.req_bss_transition = iface_req_bss_transition,
	.iface.req_btm = iface_req_btm,

	.iface.get_11rkeys = iface_get_11rkeys,
	.iface.set_11rkeys = iface_set_11rkeys,

	.chan_switch = iface_chan_switch,
	.mbo_disallow_assoc = iface_mbo_disallow_assoc,

	.ap_set_state = iface_ap_set_state,

	.ap_set_qos_map = iface_ap_set_qos_map,
	.ap_send_qos_map_conf = iface_ap_send_qos_map_conf,
	.iface.get_mlsta_info = iface_get_mlsta_info,

	.iface.get_ctrl_interface = get_ctrl_interface,
	.iface.get_ctrl_interface_band = get_ctrl_interface_band,
	.iface.block_sta = iface_block_sta,
	.iface.get_blocked_stas = iface_get_blocked_stas,

         /* Interface/vif sta callbacks */
	.iface.sta_info = iface_sta_info,
	.iface.sta_get_stats = iface_sta_get_stats,
	.iface.sta_get_ap_info = iface_sta_get_ap_info,
	.iface.sta_disconnect_ap = iface_sta_disconnect_ap,
	.iface.mlsta_interface_info = iface_mlsta_interface_info,
	.iface.mld_get_ifstatus = get_ifstatus,

	.radio_list = radio_list,
	.register_event = nlwifi_register_event,
	.unregister_event = nlwifi_unregister_event,
	.recv_event = nlwifi_recv_event,
};
