/* SPDX-License-Identifier: LGPL-2.1-only */
/*
 * mtk.c - for cfg80211 based drivers from mediatek.
 *
 * Copyright (C) 2025 Iopsys Software Solutions AB. All rights reserved.
 */
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <net/if.h>
#include <linux/nl80211.h>
#include <mtk_vendor_nl80211.h>
#include <netlink/genl/genl.h>
#include <easy/easy.h>

#include "wifi.h"
#include "drivers.h"
#include "nlwifi.h"
#include "hostapd_ctrl.h"
#include "supplicant_ctrl.h"

#include "mediatek_common.h"

/* Prefix of the AP MLD interface name in /etc/config/wireless */
#define MLD_AP_NAME	"bss"

/* Prefix of the STA MLD interface name in /etc/config/wireless */
#define MLD_STA_NAME	"sta"

/* Group id for STA */
#define STA_MLD_GROUP_ID	66

/* Maximum number of associated clients */
#define STA_MAX		128

/* Maximum AP group identifier */
#define MLD_AP_GROUP_ID_MAX	4

/* Statistics per radio */
struct mtk_radio_stats {
	    unsigned long BytesSent;
	    unsigned long BytesReceived;
	    unsigned long PacketsSent;
	    unsigned long PacketsReceived;
	    unsigned long ErrorsSent;
	    unsigned long ErrorsReceived;
	    unsigned long DiscardPacketsSent;
	    unsigned long DiscardPacketsReceived;
	    unsigned int PLCPErrorCount;
	    unsigned int FCSErrorCount;
	    unsigned int InvalidMACCount;
	    unsigned int PacketsOtherReceived;
	    unsigned long CtsReceived;
	    unsigned long NoCtsReceived;
	    unsigned long FrameHeaderError;
	    unsigned long GoodPLCPReceived;
	    unsigned long DPacketOtherMACReceived;
	    unsigned long MPacketOtherMACReceived;
	    unsigned long CPacketOtherMACReceived;
	    unsigned long CtsOtherMACReceived;
	    unsigned long RtsOtherMACReceived;
};

static inline enum wifi_band name2band(const char *ifname)
{
	if (memcmp(ifname, "rai", 3) == 0)
		return BAND_5;
	else if (memcmp(ifname, "rax", 3) == 0)
		return BAND_6;
	else if (memcmp(ifname, "ra", 2) == 0)
		return BAND_2;
	else 	if (memcmp(ifname, "apclii", 6) == 0)
		return BAND_5;
	else if (memcmp(ifname, "apclix", 6) == 0)
		return BAND_6;
	else if (memcmp(ifname, "apcli", 5) == 0)
		return BAND_2;
	else
		return BAND_UNKNOWN;
}

/* Get list of MLD group indices in use (as bitmap) */
static uint8_t get_mld_indices(void)
{
	int i;
	uint8_t res = 0;

	for (i = 0; i < 3; i++)
	{
		char buf[64] = { 0, }, *s;
		FILE *f;

		sprintf(buf, "/etc/wireless/mediatek/mld_b%d.dat", i);
		f = fopen(buf, "r");
		if (f != NULL)
		{
			if (fgets(buf, sizeof(buf) - 1, f) != NULL)
			{
				for (s = buf; *s != 0; s++)
					if (*s >= '1' && *s <= MLD_AP_GROUP_ID_MAX + '0')
						res |= 1 << (*s - '0');
			}
			fclose(f);
		}
	}
	return res;
}

static struct nl_msg *msg_build(const char *ifname, int flags, uint8_t cmd,
	struct nl_sock **p_nl)
{
	unsigned int ifindex;
	struct nl_sock *nl = NULL;
	struct nl_msg *msg;

	if (ifname == NULL)
		ifname = "ra0";

	ifindex = if_nametoindex(ifname);
	if (ifindex == 0)
		return NULL;

	if ((nl = nlwifi_socket()) == NULL)
		return NULL;

	msg = nlwifi_alloc_msg(nl, NL80211_CMD_VENDOR, flags, 0);

	if (!msg) {
		nl_socket_free(nl);
		return NULL;
	}

	if (nla_put_u32(msg, NL80211_ATTR_IFINDEX, ifindex)) {
		nlmsg_free(msg);
		nl_socket_free(nl);
		return NULL;
	}

	*p_nl = nl;

	return msg;
}

static int bss_mlo_info_iface_cb(struct nl_msg *msg, void *arg)
{
	struct nlattr *tb[NL80211_ATTR_MAX + 1];
	struct nlattr *sub_tb[MTK_NL80211_VENDOR_ATTR_BSS_MLO_INFO_ATTR_MAX + 1];
	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
	struct bss_mlo_info *param = arg;
	int ret;

	if (gnlh->cmd != NL80211_CMD_VENDOR)
		return NL_SKIP;

	ret = nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
					genlmsg_attrlen(gnlh, 0), NULL);
	if (ret)
		return NL_SKIP;

	if (!tb[NL80211_ATTR_VENDOR_DATA])
		return NL_SKIP;

	ret = nla_parse_nested(sub_tb, MTK_NL80211_VENDOR_ATTR_BSS_MLO_INFO_ATTR_MAX,
							tb[NL80211_ATTR_VENDOR_DATA], NULL);
	if (ret)
		return NL_SKIP;

	if (sub_tb[MTK_NL80211_VENDOR_ATTR_BSS_MLO_INFO]) {
		if ((size_t) nla_len(sub_tb[MTK_NL80211_VENDOR_ATTR_BSS_MLO_INFO])
			!= sizeof(struct bss_mlo_info)) {
			return NL_SKIP;
		}

		memcpy(param, nla_data(sub_tb[MTK_NL80211_VENDOR_ATTR_BSS_MLO_INFO]),
				nla_len(sub_tb[MTK_NL80211_VENDOR_ATTR_BSS_MLO_INFO]));
		/* Driver creates fake groups with big indices */
		if ((param->mld_grp_idx == 0 || param->mld_grp_idx > MLD_AP_GROUP_ID_MAX) &&
		    param->mld_grp_idx != STA_MLD_GROUP_ID) {
			param->link_id = WIFI_MLO_INVALID_LINK_ID;
		}
	}

	return NL_SKIP;
}

static int get_bss_mlo_info_iface(const char *ifname, struct bss_mlo_info *data)
{
	struct nl_msg *msg = NULL;
	struct nl_sock *nl = NULL;
	void *attr;

	if (!(msg = msg_build(ifname, 0, NL80211_CMD_VENDOR, &nl)) ||
		nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, MTK_NL80211_VENDOR_ID) ||
		nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD,
					MTK_NL80211_VENDOR_SUBCMD_GET_BSS_MLO_INFO)) {
		nlmsg_free(msg);
		nl_socket_free(nl);
		return -1;
	}

	attr = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);
	if (!attr) {
		nlmsg_free(msg);
		nl_socket_free(nl);
		return -1;
	}

	if (nla_put_flag(msg, MTK_NL80211_VENDOR_ATTR_BSS_MLO_INFO)) {
		nlmsg_free(msg);
		nl_socket_free(nl);
		return -1;
	}

	nla_nest_end(msg, attr);

	nlwifi_send_msg(nl, msg, bss_mlo_info_iface_cb, data);

	nlmsg_free(msg);
	nl_socket_free(nl);

	return 0;
}

/* Maximum number of links supported by driver */
#define MLD_LINK_MAX	3

/* Structure for bss_mlo_info_group and bss_mlo_info_group_for_sta */
struct bss_mlo_info_group {
	uint8_t bssid[MAC_ADDR_LEN]; /* MLO BSSID */
	uint8_t num; /* Number of links */
	int max; /* Array size */
	struct wifi_mlo_link *links; /* Array of links */
	/* Names of net devices corresponding to affiliated interfaces */
	char ifnames[MLD_LINK_MAX][IF_NAMESIZE];

	union {
		struct {
			enum wifi_band band;             /* Band to scan */
			uint8_t ssid[SSID_MAX_LEN + 1];  /* MLO SSID */
			size_t ssidlen;                  /* MLO SSID length */
		} ap;
		struct {
			/* MLD MAC address of the STA group */
			uint8_t mld_mac[MAC_ADDR_LEN];
		} sta;
	} u;
};

static int bss_mlo_info_group_for_ap_cb(struct nl_msg *msg, void *arg)
{
	struct nlattr *tb[NL80211_ATTR_MAX + 1];
	struct nlattr *sub_tb[MTK_NL80211_VENDOR_ATTR_BSS_MLO_INFO_ATTR_MAX + 1];
	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
	struct nlattr *nla;
	struct bss_mlo_info_group *param = arg;
	int ret, ssidlen, rem;

	if (gnlh->cmd != NL80211_CMD_VENDOR)
		return NL_SKIP;

	ret = nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
					genlmsg_attrlen(gnlh, 0), NULL);
	if (ret)
		return NL_SKIP;

	if (!tb[NL80211_ATTR_VENDOR_DATA])
		return NL_SKIP;

	ret = nla_parse_nested(sub_tb, MTK_NL80211_VENDOR_ATTR_BSS_MLO_INFO_ATTR_MAX,
							tb[NL80211_ATTR_VENDOR_DATA], NULL);
	if (ret)
		return NL_SKIP;

	if (!sub_tb[MTK_NL80211_VENDOR_ATTR_AP_MLD_ADDRESS] ||
		nla_len(sub_tb[MTK_NL80211_VENDOR_ATTR_AP_MLD_ADDRESS]) != MAC_ADDR_LEN)
			return NL_SKIP;

	memcpy(param->bssid, nla_data(sub_tb[MTK_NL80211_VENDOR_ATTR_AP_MLD_ADDRESS]),
			MAC_ADDR_LEN);

	if (!sub_tb[MTK_NL80211_VENDOR_ATTR_AP_MLD_SSID])
		return NL_SKIP;

	ssidlen = nla_len(sub_tb[MTK_NL80211_VENDOR_ATTR_AP_MLD_SSID]);
	if (ssidlen >= SSID_MAX_LEN)
		ssidlen = SSID_MAX_LEN - 1;
	memcpy(param->u.ap.ssid, nla_data(sub_tb[MTK_NL80211_VENDOR_ATTR_AP_MLD_SSID]), ssidlen);
	param->u.ap.ssid[ssidlen] = 0;
	param->u.ap.ssidlen = ssidlen;

	if (param->u.ap.band == BAND_UNKNOWN) /* Caller does not want links */
		return NL_SKIP;

	param->num = 0;

	if (!sub_tb[MTK_NL80211_VENDOR_ATTR_AP_MLD_AFFILIATED_APS])
		return NL_SKIP;

	nla_for_each_nested(nla, sub_tb[MTK_NL80211_VENDOR_ATTR_AP_MLD_AFFILIATED_APS], rem) {
		struct nlattr *sub_tb_i[MTK_NL80211_VENDOR_ATTR_AP_MLD_ATTR_MAX + 1];
		int err = nla_parse(sub_tb_i, MTK_NL80211_VENDOR_ATTR_AP_MLD_ATTR_MAX,
							nla_data(nla), nla_len(nla), NULL);
		uint32_t ifindex;

		if (err)
			return NL_SKIP;

		if (!sub_tb_i[MTK_NL80211_VENDOR_ATTR_AP_MLD_AFFILIATED_AP_IFINDEX])
			return NL_SKIP;
		ifindex =
			nla_get_u32(sub_tb_i[MTK_NL80211_VENDOR_ATTR_AP_MLD_AFFILIATED_AP_IFINDEX]);
		if (if_indextoname(ifindex, param->ifnames[param->num]) == NULL)
			return NL_SKIP;

		if (param->num >= param->max)
			break;

		param->links[param->num].band = name2band(param->ifnames[param->num]);
		if (param->u.ap.band != BAND_ANY && param->u.ap.band != param->links[param->num].band)
			continue;

		if (!sub_tb_i[MTK_NL80211_VENDOR_ATTR_AP_MLD_AFFILIATED_AP_BSSID] ||
			nla_len(sub_tb_i[MTK_NL80211_VENDOR_ATTR_AP_MLD_AFFILIATED_AP_BSSID]) !=
			MAC_ADDR_LEN)
			return NL_SKIP;

		memcpy(param->links[param->num].macaddr,
				nla_data(sub_tb_i[MTK_NL80211_VENDOR_ATTR_AP_MLD_AFFILIATED_AP_BSSID]),
				MAC_ADDR_LEN);

		if (!sub_tb_i[MTK_NL80211_VENDOR_ATTR_AP_MLD_AFFILIATED_AP_LINKID])
			return NL_SKIP;

		param->links[param->num].id =
			nla_get_u8(sub_tb_i[MTK_NL80211_VENDOR_ATTR_AP_MLD_AFFILIATED_AP_LINKID]);

		param->links[param->num].ssidlen = ssidlen;
		memcpy(param->links[param->num].ssid, param->u.ap.ssid, ssidlen + 1);

		param->num++;
	}

	return NL_SKIP;
}

static int get_bss_mlo_info_group_for_ap(const char *ifname, uint8_t group,
                                         struct bss_mlo_info_group *data)
{
	struct nl_msg *msg = NULL;
	struct nl_sock *nl = NULL;
	void *attr;
	int ret;

	if (!(msg = msg_build(ifname, 0, NL80211_CMD_VENDOR, &nl)) ||
		nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, MTK_NL80211_VENDOR_ID) ||
		nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD,
					MTK_NL80211_VENDOR_SUBCMD_GET_BSS_MLO_INFO)) {
		nlmsg_free(msg);
		return -1;
	}

	attr = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);
	if (!attr) {
		nlmsg_free(msg);
		nl_socket_free(nl);
		return -1;
	}

	if (nla_put_u8(msg, MTK_NL80211_VENDOR_ATTR_AP_MLD_INDEX, group)) {
		nlmsg_free(msg);
		nl_socket_free(nl);
		return -1;
	}

	nla_nest_end(msg, attr);

	ret = nlwifi_send_msg(nl, msg, bss_mlo_info_group_for_ap_cb, data);

	nlmsg_free(msg);
	nl_socket_free(nl);

	return ret;
}

static int bss_mlo_info_group_for_sta_cb(struct nl_msg *msg, void *arg)
{
	struct nlattr *tb[NL80211_ATTR_MAX + 1];
	struct nlattr *sub_tb[MTK_NL80211_VENDOR_ATTR_BSS_MLO_INFO_ATTR_MAX + 1];
	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
	struct nlattr *nla;
	struct bss_mlo_info_group *param = arg;
	int ret, rem;

	if (gnlh->cmd != NL80211_CMD_VENDOR)
		return NL_SKIP;

	ret = nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
					genlmsg_attrlen(gnlh, 0), NULL);
	if (ret)
		return NL_SKIP;

	if (!tb[NL80211_ATTR_VENDOR_DATA])
		return NL_SKIP;

	ret = nla_parse_nested(sub_tb, MTK_NL80211_VENDOR_ATTR_APCLI_MLD_ATTR_MAX,
							tb[NL80211_ATTR_VENDOR_DATA], NULL);
	if (ret)
		return NL_SKIP;

	if (!sub_tb[MTK_NL80211_VENDOR_ATTR_APCLI_MLD_MAC] ||
		nla_len(sub_tb[MTK_NL80211_VENDOR_ATTR_APCLI_MLD_MAC]) != MAC_ADDR_LEN)
			return NL_SKIP;

	memcpy(param->u.sta.mld_mac, nla_data(sub_tb[MTK_NL80211_VENDOR_ATTR_APCLI_MLD_MAC]),
			MAC_ADDR_LEN);

	if (sub_tb[MTK_NL80211_VENDOR_ATTR_CONNECTED_AP_MLD_MAC] &&
		nla_len(sub_tb[MTK_NL80211_VENDOR_ATTR_CONNECTED_AP_MLD_MAC]) == MAC_ADDR_LEN) {
		memcpy(param->bssid, nla_data(sub_tb[MTK_NL80211_VENDOR_ATTR_CONNECTED_AP_MLD_MAC]),
				MAC_ADDR_LEN);
	}

	param->num = 0;

	if (!sub_tb[MTK_NL80211_VENDOR_ATTR_APCLI_MLD_AFFILIATED_STAS])
		return NL_SKIP;

	nla_for_each_nested(nla, sub_tb[MTK_NL80211_VENDOR_ATTR_APCLI_MLD_AFFILIATED_STAS], rem) {
		struct nlattr *sub_tb_i[MTK_NL80211_VENDOR_ATTR_APCLI_MLD_ATTR_MAX + 1];
		int err = nla_parse(sub_tb_i, MTK_NL80211_VENDOR_ATTR_APCLI_MLD_ATTR_MAX,
							nla_data(nla), nla_len(nla), NULL);
		uint32_t ifindex;

		if (err)
			return NL_SKIP;

		if (!sub_tb_i[MTK_NL80211_VENDOR_ATTR_APCLI_MLD_AFFILIATED_STA_IFINDEX])
			return NL_SKIP;
		ifindex =
			nla_get_u32(sub_tb_i[MTK_NL80211_VENDOR_ATTR_APCLI_MLD_AFFILIATED_STA_IFINDEX]);
		if (if_indextoname(ifindex, param->ifnames[param->num]) == NULL)
			return NL_SKIP;

		if (param->num >= param->max)
			break;

		if (!sub_tb_i[MTK_NL80211_VENDOR_ATTR_APCLI_MLD_AFFILIATED_STA_MAC] ||
			nla_len(sub_tb_i[MTK_NL80211_VENDOR_ATTR_APCLI_MLD_AFFILIATED_STA_MAC]) !=
			MAC_ADDR_LEN)
			return NL_SKIP;

		memcpy(param->links[param->num].macaddr,
				nla_data(sub_tb_i[MTK_NL80211_VENDOR_ATTR_APCLI_MLD_AFFILIATED_STA_MAC]),
				MAC_ADDR_LEN);

		if (!sub_tb_i[MTK_NL80211_VENDOR_ATTR_APCLI_MLD_AFFILIATED_STA_LINKID])
			return NL_SKIP;

		param->links[param->num].id =
			nla_get_u8(sub_tb_i[MTK_NL80211_VENDOR_ATTR_APCLI_MLD_AFFILIATED_STA_LINKID]);


		if (!sub_tb_i[MTK_NL80211_VENDOR_ATTR_APCLI_MLD_AFFILIATED_STA_IS_SETUP_LINK])
			return NL_SKIP;

		param->links[param->num].is_setup_link =
			nla_get_u8(sub_tb_i[MTK_NL80211_VENDOR_ATTR_APCLI_MLD_AFFILIATED_STA_IS_SETUP_LINK]);

		param->num++;
	}

	return NL_SKIP;
}

static int get_bss_mlo_info_group_for_sta(struct bss_mlo_info_group *data)
{
	struct nl_msg *msg = NULL;
	struct nl_sock *nl = NULL;
	void *attr;
	int ret;

	if (!(msg = msg_build(NULL, 0, NL80211_CMD_VENDOR, &nl)) ||
		nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, MTK_NL80211_VENDOR_ID) ||
		nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD,
					MTK_NL80211_VENDOR_SUBCMD_GET_APCLI_MLD)) {
		nlmsg_free(msg);
		return -1;
	}

	attr = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);
	if (!attr) {
		nlmsg_free(msg);
		nl_socket_free(nl);
		return -1;
	}

	if (nla_put_flag(msg, MTK_NL80211_VENDOR_ATTR_DUMP_APCLI_MLD)) {
		nlmsg_free(msg);
		nl_socket_free(nl);
		return -1;
	}

	nla_nest_end(msg, attr);

	ret = nlwifi_send_msg(nl, msg, bss_mlo_info_group_for_sta_cb, data);

	nlmsg_free(msg);
	nl_socket_free(nl);

	return ret;
}

static int get_bss_mlo_info_group(const char *ifname, uint8_t group,
                                  struct bss_mlo_info_group *data)
{
	return group == STA_MLD_GROUP_ID ?
			get_bss_mlo_info_group_for_sta(data) :
			get_bss_mlo_info_group_for_ap(ifname, group, data);
}

static int bss_mlo_eml_mode_cb(struct nl_msg *msg, void *arg)
{
	struct nlattr *tb[NL80211_ATTR_MAX + 1];
	struct nlattr *sub_tb[MTK_NL80211_VENDOR_ATTR_BSS_MLO_INFO_ATTR_MAX + 1];
	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
	struct nlattr *nla;
	enum wifi_eml_mode *mode = arg;
	int ret, rem;

	if (gnlh->cmd != NL80211_CMD_VENDOR)
		return NL_SKIP;

	ret = nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
					genlmsg_attrlen(gnlh, 0), NULL);
	if (ret)
		return NL_SKIP;

	if (!tb[NL80211_ATTR_VENDOR_DATA])
		return NL_SKIP;

	ret = nla_parse_nested(sub_tb, MTK_NL80211_VENDOR_ATTR_BSS_MLO_INFO_ATTR_MAX,
							tb[NL80211_ATTR_VENDOR_DATA], NULL);
	if (ret)
		return NL_SKIP;

	if (!sub_tb[MTK_NL80211_VENDOR_ATTR_AP_MLD_AFFILIATED_APS])
		return NL_SKIP;

	nla_for_each_nested(nla, sub_tb[MTK_NL80211_VENDOR_ATTR_AP_MLD_AFFILIATED_APS], rem) {
		struct nlattr *sub_tb_i[MTK_NL80211_VENDOR_ATTR_AP_MLD_ATTR_MAX + 1];
		int err = nla_parse(sub_tb_i, MTK_NL80211_VENDOR_ATTR_AP_MLD_ATTR_MAX,
							nla_data(nla), nla_len(nla), NULL);
		if (err)
			return NL_SKIP;

		if (sub_tb_i[MTK_NL80211_VENDOR_ATTR_AP_MLD_EMLSR])
			*mode = WIFI_EML_MODE_EMLSR;
		else if (sub_tb_i[MTK_NL80211_VENDOR_ATTR_AP_MLD_EMLMR])
			*mode = WIFI_EML_MODE_EMLMR;
		else
			*mode = WIFI_EML_MODE_NONE;

		return NL_STOP;
	}

	return NL_SKIP;
}

static int get_mlo_eml_mode(enum wifi_eml_mode *mode)
{
	struct nl_msg *msg = NULL;
	struct nl_sock *nl = NULL;
	void *attr;
	int ret;
	uint8_t groups = get_mld_indices(), grp_idx;

	*mode = WIFI_EML_MODE_NONE;
	for (grp_idx = 1;
		grp_idx <= MLD_AP_GROUP_ID_MAX && (groups & (1 << grp_idx)) == 0;
		grp_idx++);

	if (grp_idx > MLD_AP_GROUP_ID_MAX)
		return 0;

	if (!(msg = msg_build(NULL, 0, NL80211_CMD_VENDOR, &nl)) ||
		nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, MTK_NL80211_VENDOR_ID) ||
		nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD,
					MTK_NL80211_VENDOR_SUBCMD_GET_BSS_MLO_INFO)) {
		nlmsg_free(msg);
		return -1;
	}

	attr = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);
	if (!attr) {
		nlmsg_free(msg);
		nl_socket_free(nl);
		return -1;
	}

	/* If number of groups > 0, group with index 1 must exist */
	if (nla_put_u8(msg, MTK_NL80211_VENDOR_ATTR_AP_MLD_INDEX, grp_idx)) {
		nlmsg_free(msg);
		nl_socket_free(nl);
		return -1;
	}

	nla_nest_end(msg, attr);

	ret = nlwifi_send_msg(nl, msg, bss_mlo_eml_mode_cb, mode);

	nlmsg_free(msg);
	nl_socket_free(nl);

	return ret;
}

/* Structure for get_connected_sta_cb */
struct connected_sta {
	bool all;
	bool found;
	int *num_stas;
	uint8_t *stas;
	struct wifi_mlsta *mlsta;
};

static int get_connected_sta_cb(struct nl_msg *msg, void *arg)
{
	struct nlattr *tb[NL80211_ATTR_MAX + 1];
	struct nlattr *sub_tb[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_MAX + 1];
	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
	struct nlattr *nla;
	struct connected_sta *param = arg;
	int ret, rem;
	struct wifi_mlsta *mlsta;
	struct wifi_sta *asta;
	uint8_t *mac;
#ifdef MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_CAPS
	struct wifi_sta *info;
	uint8_t *ie;
	int ie_len;
#endif

	if (gnlh->cmd != NL80211_CMD_VENDOR)
		return NL_SKIP;

	ret = nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
					genlmsg_attrlen(gnlh, 0), NULL);
	if (ret)
		return NL_SKIP;

	if (!tb[NL80211_ATTR_VENDOR_DATA])
		return NL_SKIP;

	ret = nla_parse_nested(sub_tb, MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_MAX,
							tb[NL80211_ATTR_VENDOR_DATA], NULL);
	if (ret)
		return NL_SKIP;

	if (!sub_tb[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_MAC] ||
		nla_len(sub_tb[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_MAC]) !=
		MAC_ADDR_LEN)
		return NL_SKIP;

	mac = nla_data(sub_tb[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_MAC]);

	if (param->all) {
		memcpy(param->stas + *param->num_stas * MAC_ADDR_LEN, mac, MAC_ADDR_LEN);
		mlsta = param->mlsta + *(param->num_stas);
		if (++(*(param->num_stas)) == STA_MAX)
			return NL_STOP;
	} else if (memcmp(mac, param->mlsta[0].macaddr, MAC_ADDR_LEN) == 0) {
		mlsta = param->mlsta;
		param->found = true;
	}
	else
		return NL_SKIP;

	mlsta->num_link = 0;
	asta = mlsta->sta;
	nla_for_each_nested(nla, sub_tb[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA],
				rem) {
		struct nlattr *sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_MAX + 1];
		int err = nla_parse(sub_tb_i, MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_MAX,
							nla_data(nla), nla_len(nla), NULL);

		if (err)
			return NL_SKIP;

		if (!sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_MAC] ||
			nla_len(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_MAC]) !=
			MAC_ADDR_LEN)
			return NL_SKIP;

		memcpy(asta[mlsta->num_link].macaddr,
				nla_data(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_MAC]),
				MAC_ADDR_LEN);

		if (!sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_BSSID] ||
			nla_len(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_BSSID]) !=
			MAC_ADDR_LEN)
			return NL_SKIP;

		memcpy(asta[mlsta->num_link].bssid,
				nla_data(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_BSSID]),
				MAC_ADDR_LEN);

		if (!sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_LINKID])
			return NL_SKIP;

		asta[mlsta->num_link].mlo_link_id =
			nla_get_u8(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_LINKID]);

		if (!sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_IS_SETUP_LINK])
			return NL_SKIP;

		asta[mlsta->num_link].is_setup_link =
			nla_get_u8(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_IS_SETUP_LINK]);

		if (sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_EST_DL_RATE]) {
			asta[mlsta->num_link].maxrate =
				nla_get_u32(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_EST_DL_RATE]);
		}

#ifdef MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_CAPS
		info = &asta[mlsta->num_link];

		if (sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_MAX_NSS]) {
			info->rate.m.nss =
				nla_get_u8(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_MAX_NSS]);
			info->nss = info->rate.m.nss;
		}

		if (sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_HT_CAP]) {
			ie_len = nla_len(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_HT_CAP]);
			ie = nla_data(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_HT_CAP]);

			wifi_cap_set_from_ht_capabilities_info(info->cbitmap, ie, ie_len);
			memcpy(&info->caps.ht, ie, ie_len < sizeof(info->caps.ht) ? ie_len : sizeof(info->caps.ht));
			info->caps.valid |= WIFI_CAP_HT_VALID;
		}

		if (sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_EXT_CAP]) {
			ie_len = nla_len(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_EXT_CAP]);
			ie = nla_data(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_EXT_CAP]);

			wifi_cap_set_from_extended_capabilities(info->cbitmap, ie, ie_len);
			memcpy(&info->caps.ext, ie, ie_len < sizeof(info->caps.ext) ? ie_len : sizeof(info->caps.ext));
			info->caps.valid |= WIFI_CAP_EXT_VALID;
		}

		if (sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_VHT_CAP]) {
			ie_len = nla_len(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_VHT_CAP]);
			ie = nla_data(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_VHT_CAP]);

			wifi_cap_set_from_vht_capabilities_info(info->cbitmap, ie, ie_len);
			memcpy(&info->caps.vht, ie, ie_len < sizeof(info->caps.vht) ? ie_len : sizeof(info->caps.vht));
			info->caps.valid |= WIFI_CAP_VHT_VALID;
		}

		if (sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_HE_CAP]) {
			ie_len = nla_len(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_HE_CAP]);
			ie = nla_data(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_HE_CAP]);

			wifi_cap_set_from_he(info->cbitmap, ie, ie_len);
			memcpy(&info->caps.he, ie, ie_len < sizeof(info->caps.he) ? ie_len : sizeof(info->caps.he));
			info->caps.valid |= WIFI_CAP_HE_VALID;
		}

		if (sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_EHT_CAP]) {
			ie_len = nla_len(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_EHT_CAP]);
			ie = nla_data(sub_tb_i[MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_EHT_CAP]);

			wifi_cap_set_from_eht(info->cbitmap, ie, ie_len);
			memcpy(&info->caps.eht, ie, ie_len < sizeof(info->caps.eht) ? ie_len : sizeof(info->caps.eht));
			info->caps.valid |= WIFI_CAP_EHT_VALID;
		}
#endif
		mlsta->num_link++;
	}
	mlsta->mlo_capable = param->mlsta->num_link > 0;

	return param->all ? NL_SKIP : NL_STOP;
}

static int get_connected_sta(const char *ifname, uint8_t group,
		struct connected_sta *data)
{
	struct nl_msg *msg = NULL;
	struct nl_sock *nl = NULL;
	void *attr;
	int ret;

	if (!(msg = msg_build(ifname, 0, NL80211_CMD_VENDOR, &nl)) ||
		nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, MTK_NL80211_VENDOR_ID) ||
		nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD,
					MTK_NL80211_VENDOR_SUBCMD_GET_CONNECTED_STA_MLD)) {
		nlmsg_free(msg);
		return -1;
	}

	attr = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);
	if (!attr) {
		nlmsg_free(msg);
		nl_socket_free(nl);
		return -1;
	}

	if (nla_put_u8(msg, MTK_NL80211_VENDOR_ATTR_AP_MLD_INDEX_TO_DUMP, group)) {
		nlmsg_free(msg);
		nl_socket_free(nl);
		return -1;
	}

	nla_nest_end(msg, attr);

	ret = nlwifi_send_msg(nl, msg, get_connected_sta_cb, data);

	nlmsg_free(msg);
	nl_socket_free(nl);

	return ret;
}

/*
 * Find MLD or affiliated STA by MAC.
 *
 * If MAC matches MLD MAC, just mlsta is returned.
 * If MAC matches MAC of affiliated STA, mlsta, sta and interface that it is
 * connected to are returned.
 */
static int find_sta(uint8_t *mac, struct bss_mlo_info_group *gdata,
			struct connected_sta *data, int num,
			struct wifi_mlsta **p_mlsta, struct wifi_sta **p_sta)
{
	int i, j;

	*p_mlsta = NULL;
	*p_sta = NULL;

	for (i = 0; i < num; i++) {
		if (memcmp(data->mlsta[i].macaddr, mac, MAC_ADDR_LEN) == 0) {
			*p_mlsta = data->mlsta + i;
			return 0;
		}
		for (j = 0; j < data->mlsta[i].num_link; j++) {
			if (memcmp(data->mlsta[i].sta[j].macaddr, mac, MAC_ADDR_LEN) == 0) {
				*p_mlsta = data->mlsta + i;
				*p_sta = data->mlsta[i].sta + j;
				return 0;
			}
		}
	}

	return -1;
}

static int mtk_radio_is_multiband(const char *ifname, bool *res)
{
	*res = false;
	return 0;
}

static int mtk_radio_info_band(const char *name, enum wifi_band band, struct wifi_radio *radio)
{
	int ret = default_wifi_driver.radio.info_band(name, band, radio);
	int i;

	if (ret)
		return ret;

	correct_oper_std_by_band(band, &radio->supp_std);
	correct_oper_std_by_band(band, &radio->oper_std);
	if (band != BAND_ANY)
		radio->supp_band = band;

	for (i = 0; i < radio->num_iface; i++) {
		struct bss_mlo_info data;

		if (get_bss_mlo_info_iface(radio->iface[i].name, &data))
			continue;

		radio->iface[i].mlo_link_id = data.link_id;
	}

	radio->max_iface_sta = 1;
	radio->max_iface_ap = 8;

	return 0;
}

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

	return mtk_radio_info_band(name, band, radio);
}

static int radio_stats_cb(struct nl_msg *msg, void *arg)
{
	struct nlattr *tb[NL80211_ATTR_MAX + 1];
	struct nlattr *sub_tb[MTK_NL80211_VENDOR_ATTR_RADIO_STATS_ATTR_MAX + 1];
	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
	struct wifi_radio_stats *param = arg;
	struct mtk_radio_stats stats = {0, };
	int ret;

	if (gnlh->cmd != NL80211_CMD_VENDOR)
		return NL_SKIP;

	ret = nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
			genlmsg_attrlen(gnlh, 0), NULL);
	if (ret)
		return NL_SKIP;

	if (!tb[NL80211_ATTR_VENDOR_DATA])
		return NL_SKIP;

	ret = nla_parse_nested(sub_tb, MTK_NL80211_VENDOR_ATTR_RADIO_STATS_ATTR_MAX,
				tb[NL80211_ATTR_VENDOR_DATA], NULL);
	if (ret)
		return NL_SKIP;

	if (sub_tb[MTK_NL80211_VENDOR_ATTR_RADIO_STATS]) {
		if ((size_t) nla_len(sub_tb[MTK_NL80211_VENDOR_ATTR_RADIO_STATS])
		    < sizeof(struct mtk_radio_stats)) {
			return NL_SKIP;
		}

		memcpy(&stats, nla_data(sub_tb[MTK_NL80211_VENDOR_ATTR_RADIO_STATS]),
		       sizeof(struct mtk_radio_stats));

		param->tx_bytes = stats.BytesSent;
		param->rx_bytes = stats.BytesReceived;
		param->tx_pkts = stats.PacketsSent;
		param->rx_pkts = stats.PacketsReceived;
		param->tx_err_pkts = stats.ErrorsSent;
		param->rx_err_pkts = stats.ErrorsReceived;
		param->tx_dropped_pkts = stats.DiscardPacketsSent;
		param->rx_dropped_pkts = stats.DiscardPacketsReceived;
		param->rx_plcp_err_pkts = stats.PLCPErrorCount;
		param->rx_fcs_err_pkts = stats.FCSErrorCount;
		param->rx_mac_err_pkts = stats.InvalidMACCount;
		param->rx_unknown_pkts = stats.PacketsOtherReceived;
		param->cts_rcvd = stats.CtsReceived;
		param->cts_not_rcvd = stats.NoCtsReceived;
		param->rx_frame_err_pkts = stats.FrameHeaderError;
		param->rx_good_plcp_pkts = stats.GoodPLCPReceived;
		param->omac_data_pkts = stats.DPacketOtherMACReceived;
		param->omac_mgmt_pkts = stats.MPacketOtherMACReceived;
		param->omac_ctrl_pkts = stats.CPacketOtherMACReceived;
		param->omac_cts = stats.CtsOtherMACReceived;
		param->omac_rts = stats.RtsOtherMACReceived;
	}

	return NL_SKIP;
}

static int mtk_radio_get_stats(const char *name, struct wifi_radio_stats *s)
{
	struct nl_msg *msg = NULL;
	struct nl_sock *nl = NULL;
	void *attr;
	struct wifi_iface ifaces[WIFI_IFACE_MAX_NUM];
	uint8_t num = WIFI_IFACE_MAX_NUM;

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

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

	default_wifi_driver.get_noise(name, &s->noise);

	if (nlwifi_get_phy_wifi_ifaces(name, BAND_ANY, ifaces, &num) < 0) {
		libwifi_dbg("Failed to get list of interfaces of %s\n", name);
		return -1;
	}

	/* Driver radio stats requires to specify any interface from the desired band */
	if (num == 0)
		return 0;

	if (!(msg = msg_build(ifaces[0].name, 0, NL80211_CMD_VENDOR, &nl)) ||
		nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, MTK_NL80211_VENDOR_ID) ||
		nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD,
			    MTK_NL80211_VENDOR_SUBCMD_GET_RADIO_STATS)) {
		nlmsg_free(msg);
		nl_socket_free(nl);
		return -1;
	}

	attr = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);
	if (!attr) {
		nlmsg_free(msg);
		nl_socket_free(nl);
		return -1;
	}

	if (nla_put_flag(msg, MTK_NL80211_VENDOR_ATTR_RADIO_STATS)) {
		nlmsg_free(msg);
		nl_socket_free(nl);
		return -1;
	}

	nla_nest_end(msg, attr);

	nlwifi_send_msg(nl, msg, radio_stats_cb, s);

	nlmsg_free(msg);
	nl_socket_free(nl);

	return 0;
}

static int get_iface_mld(const char *name, uint8_t *grp_idx, uint8_t *link_id,
			uint8_t *mac)
{
	struct bss_mlo_info data;

	if (get_bss_mlo_info_iface(name, &data))
		return -1;

	*grp_idx = data.mld_grp_idx;
	*link_id = data.link_id;
	memcpy(mac, data.addr, sizeof(data.addr));

	return 0;
}

static int mtk_ap_info_band(const char *name, enum wifi_band band, struct wifi_ap *ap)
{
	int ret = default_wifi_driver.iface.ap_info_band(name, band, ap);

	if (ret != 0)
		return ret;

	get_mlo_eml_mode(&ap->bss.mlo_eml_mode);

	return get_iface_mld(name, &ap->bss.mld_id, &ap->bss.mlo_link_id,
			ap->bss.mld_macaddr);
}

static int mtk_ap_info(const char *name, struct wifi_ap *ap)
{
	return mtk_ap_info_band(name, BAND_ANY, ap);
}

int mtk_iface_get_stats(const char *ifname, struct wifi_ap_stats *s)
{
	ifstatus_t flags = 0;
	char phyname[8];

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

	get_phyname(ifname, phyname);

	memset(s, 0, sizeof(*s));
	s->tx_bytes = read_u64_from_file("/sys/class/ieee80211/%s/device/net/%s/statistics/tx_bytes", phyname, ifname);
	s->rx_bytes = read_u64_from_file("/sys/class/ieee80211/%s/device/net/%s/statistics/rx_bytes", phyname, ifname);
	s->tx_pkts = read_u64_from_file("/sys/class/ieee80211/%s/device/net/%s/statistics/tx_packets", phyname, ifname);
	s->rx_pkts = read_u64_from_file("/sys/class/ieee80211/%s/device/net/%s/statistics/rx_packets", phyname, ifname);
	s->tx_err_pkts = read_u64_from_file("/sys/class/ieee80211/%s/device/net/%s/statistics/tx_errors", phyname, ifname);
	s->rx_err_pkts = read_u64_from_file("/sys/class/ieee80211/%s/device/net/%s/statistics/rx_errors", phyname, ifname);
	s->tx_dropped_pkts = read_u64_from_file("/sys/class/ieee80211/%s/device/net/%s/statistics/tx_dropped", phyname, ifname);
	s->rx_dropped_pkts = read_u64_from_file("/sys/class/ieee80211/%s/device/net/%s/statistics/rx_dropped", phyname, ifname);
	s->rx_unknown_pkts = read_u64_from_file("/sys/class/ieee80211/%s/device/net/%s/statistics/rx_nohandler", phyname, ifname);

	return 0;
}

static int mtk_get_assoclist(const char *ifname, uint8_t *stas, int *num_stas)
{
	ifstatus_t flags = 0;

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

	return nlwifi_get_assoclist_band(ifname, BAND_ANY, stas, num_stas);
}

static int mtk_get_assoclist_band(const char *ifname, enum wifi_band band,
		uint8_t *stas, int *num_stas)
{
	ifstatus_t flags = 0;

	default_wifi_driver.radio.get_ifstatus(ifname, &flags);
	if (!(flags & IFF_UP)) {
		libwifi_dbg("%s: %s not UP\n", __func__, ifname);
		return -1;
	}
	return nlwifi_get_assoclist_band(ifname, band, stas, num_stas);
}

static int mtk_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 (default_wifi_driver.radio.get_band_noise(ifname, band, &noise))
                noise = -90;
	rssi = sta->rssi_avg;
	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 mtk_get_sta_info_band_check_mlo(const char *ifname, enum wifi_band band,
	uint8_t *addr, struct wifi_sta *info, bool check_mlo)
{
	int ret;
	struct wifi_interface_info ii;
	uint16_t sta_bw;
	uint8_t group;
	uint8_t mac[MAC_ADDR_LEN];
	char *ctrl_iface = NULL;

	/* nlwifi_get_sta_info_band returns 0 even if STA is not found, so backup and check MAC */
	memcpy(mac, addr, MAC_ADDR_LEN);
	memset(info->macaddr, 0, MAC_ADDR_LEN);
	ret = nlwifi_get_sta_info_band(ifname, band, mac, info);
	if (ret != 0)
		return ret;
	if (memcmp(info->macaddr, mac, MAC_ADDR_LEN) != 0)
		return -1;

	info->band = name2band(ifname);

	if (nlwifi_get_interface(ifname, &ii) == 0) {
		info->channel = ii.channel;
		info->bandwidth = ii.bandwidth;
		info->rate.m.bw = wifi_bw_enum2MHz(info->bandwidth);

	}

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

	ret = hostapd_cli_iface_get_sta_info(ctrl_iface, addr, info);
	if (!ret) {
	    sta_bw = wifi_get_max_bandwidth_from_caps(ifname, info->band, &info->caps,
						      info->cbitmap);
	    if (wifi_bw_enum2MHz(info->bandwidth) > sta_bw) {
		    info->bandwidth = wifi_bw_to_enum_bw(sta_bw);
		    info->rate.m.bw = sta_bw;
	    }

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

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

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

	if (!check_mlo)
		return 0;

	ret = get_iface_mld(ifname, &group, (uint8_t *)&info->mlo_link_id, info->mld_bssid);
	if (ret)
		return ret;

	if (info->mlo_link_id == WIFI_MLO_INVALID_LINK_ID)
		return 0; /* FIXME: fill mlo_capable */

	/* Affiliated interface, look for STA MLD address if exist */
	{
		struct wifi_mlsta mlsta[STA_MAX], *m;
		struct wifi_sta *s = NULL;
		uint8_t stas[MAC_ADDR_LEN * STA_MAX] = { 0, };
		int num = 0;
		struct connected_sta data = { .all = true, .stas = stas, .num_stas = &num, .mlsta = mlsta};

		struct wifi_mlo_link links[MLD_LINK_MAX];
		struct bss_mlo_info_group gdata = { .u.ap.band = BAND_ANY, .links = links, .max = ARRAY_SIZE(links) };

		if (get_bss_mlo_info_group(NULL, group, &gdata))
			return -1;

		ret = get_connected_sta(NULL, group, &data);
		if (ret)
			return ret;

		info->mlo_capable = find_sta(addr, &gdata, &data, num, &m, &s) == 0;
		if (info->mlo_capable)
			memcpy(info->mld_macaddr, m->macaddr, MAC_ADDR_LEN);
		if (s) {
			info->maxrate = s->maxrate;

#ifdef MTK_NL80211_VENDOR_ATTR_CONNECTED_STA_MLD_AFFILIATED_STA_CAPS
			info->rate.m.nss = s->rate.m.nss;
			info->nss = s->nss;
			memcpy(&info->caps, &s->caps, sizeof(info->caps));
#endif
		}

		mtk_iface_get_snr_exp_tp(ifname, band, addr, info);
	}

	return 0;
}

static int mtk_get_sta_info_band(const char *ifname, enum wifi_band band, uint8_t *addr,
				struct wifi_sta *info)
{
	return mtk_get_sta_info_band_check_mlo(ifname, band, addr, info, true);
}

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

static int mtk_sta_info(const char *ifname, struct wifi_sta *data)
{
	int ret = default_wifi_driver.iface.sta_info(ifname, data);
	struct wifi_mlo_link links[MLD_LINK_MAX];
	struct bss_mlo_info_group gdata = { .u.ap.band = BAND_ANY, .links = links, .max = ARRAY_SIZE(links) };
	int i;

	if (ret != 0)
		return ret;

	data->mlo_link_id = WIFI_MLO_INVALID_LINK_ID;
	data->mlo_capable = false;

	if (get_bss_mlo_info_group(NULL, STA_MLD_GROUP_ID, &gdata) != 0)
		return 0;

	for (i = 0; i < gdata.num; i++) {
		if (strcmp(ifname, gdata.ifnames[i]) == 0) {
			data->mlo_capable = true;
			memcpy(data->mld_macaddr, gdata.u.sta.mld_mac, sizeof(data->mld_macaddr));
			memcpy(data->mld_bssid, gdata.bssid, sizeof(data->mld_bssid));
			data->mlo_link_id = gdata.links[i].id;
			break;
		}
	}

	return 0;
}

static int mtk_acs(const char *phyname, struct acs_param *p)
{
	char ifname[16] = {0};

	UNUSED(p);	//TODO: p

	if (!nlwifi_phy_to_netdev_with_type(phyname, ifname, sizeof(ifname), NLWIFI_MODE_AP))
		runCmd("mwctl %s set AutoChannelSel=3", ifname);

	return 0;
}

const struct wifi_driver mtk_driver = {
	.name = "phy,rai,ra,apclii,apcli",

	.radio.is_multiband = mtk_radio_is_multiband,
	.radio.info = mtk_radio_info,
	.radio.info_band = mtk_radio_info_band,
	.radio.get_stats = mtk_radio_get_stats,
	.acs = mtk_acs,

	.iface.ap_info = mtk_ap_info,
	.iface.ap_info_band = mtk_ap_info_band,

	.iface.get_stats = mtk_iface_get_stats,
	.get_assoclist = mtk_get_assoclist,
	.iface.get_assoclist_band = mtk_get_assoclist_band,
	.get_sta_info = mtk_get_sta_info,
	.iface.get_sta_info_band = mtk_get_sta_info_band,

	.iface.sta_info = mtk_sta_info,

	.fallback = &default_wifi_driver,
};

static int mtk_mld_info(const char *name, struct wifi_metainfo *info)
{
	UNUSED(name);

	/* Vendor and device information should be the same as for links */
	return nlwifi_driver_info("ra0", info);
}

static int mtk_mld_radio_list(struct radio_entry *radio, int *num)
{
	UNUSED(radio);
	/* All radios should be listed by mtk_driver */
	*num = 0;
	return 0;
}

static int mtk_mld_get_mode(const char *ifname, enum wifi_mode *mode)
{
	if (memcmp(ifname, MLD_AP_NAME, strlen(MLD_AP_NAME)) == 0)
		*mode = WIFI_MODE_AP;
	else if (memcmp(ifname, MLD_STA_NAME, strlen(MLD_STA_NAME)) == 0)
		*mode = WIFI_MODE_STA;
	else
		return -1;

	return 0;
}

/* Get group by ifname */
static inline uint8_t ifname2group(const char *ifname)
{
	if (memcmp(ifname, MLD_AP_NAME, strlen(MLD_AP_NAME)) == 0)
	{
		uint8_t groups = get_mld_indices();
		uint8_t group = ifname[strlen(MLD_AP_NAME)] - '0' + 1;

		if ((groups & (1 << group)) == 0)
			return STA_MLD_GROUP_ID;

		return group;
	}
	else if (memcmp(ifname, MLD_STA_NAME, strlen(MLD_STA_NAME)) == 0)
		return STA_MLD_GROUP_ID;
	else
		return 0;
}

static int mtk_mld_get_mlo_links(const char *ifname, enum wifi_band band,
				 struct wifi_mlo_link *link, int *num)
{
	struct bss_mlo_info_group data = { .u.ap.band = band, .links = link, .max = *num };
	uint8_t group = ifname2group(ifname);
	int i;

	if (group == 0 || get_bss_mlo_info_group(NULL, group, &data))
		return -1;

	*num = data.num;

	for (i = 0; i < *num; i++) {
		 struct wifi_interface_info info = {};

		if (nlwifi_get_interface(data.ifnames[i], &info) == 0) {
			link[i].frequency = info.frequency;
			link[i].channel = info.channel;
			link[i].bandwidth = info.bandwidth;
			link[i].ccfs0 = info.ccfs0;
			link[i].ccfs1 = info.ccfs1;
			link[i].is4addr = info.is4addr;
		}
	}

	return 0;
}

static int mtk_mld_get_bssid(const char *ifname, uint8_t *bssid)
{
	struct bss_mlo_info_group data = { .u.ap.band = BAND_UNKNOWN, };
	uint8_t group = ifname2group(ifname);

	if (group == 0 || get_bss_mlo_info_group(NULL, group, &data))
		return -1;

	memcpy(bssid, data.bssid, MAC_ADDR_LEN);

	return 0;
}

static int mtk_mld_get_ssid(const char *ifname, uint8_t *ssid, size_t *ssidlen)
{
	struct bss_mlo_info_group data = { .u.ap.band = BAND_UNKNOWN, };
	uint8_t group = ifname2group(ifname);

	if (group == 0 || group == STA_MLD_GROUP_ID ||
		get_bss_mlo_info_group(NULL, group, &data))
		return -1;

	memcpy(ssid, data.u.ap.ssid, data.u.ap.ssidlen);
	ssid[data.u.ap.ssidlen] = '\0';
	*ssidlen = data.u.ap.ssidlen;

	return 0;
}

int mtk_mld_get_stats(const char *ifname, struct wifi_ap_stats *s)
{
	struct wifi_mlo_link links[MLD_LINK_MAX];
	struct bss_mlo_info_group data = { .u.ap.band = BAND_ANY, .links = links, .max = ARRAY_SIZE(links) };
	uint8_t group = ifname2group(ifname);
	int i;

	if (group == 0 || get_bss_mlo_info_group(NULL, group, &data))
		return -1;

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

	for (i = 0; i < data.num; i++) {
		 struct wifi_ap_stats ais;

		if (wifi_ap_get_stats(data.ifnames[i], &ais) == 0) {
			s->tx_bytes          += ais.tx_bytes;
			s->rx_bytes          += ais.rx_bytes;
			s->tx_pkts           += ais.tx_pkts;
			s->rx_pkts           += ais.rx_pkts;
			s->tx_err_pkts       += ais.tx_err_pkts;
			s->tx_rtx_pkts       += ais.tx_rtx_pkts;
			s->tx_rtx_fail_pkts  += ais.tx_rtx_fail_pkts;
			s->tx_retry_pkts     += ais.tx_retry_pkts;
			s->tx_mretry_pkts    += ais.tx_mretry_pkts;
			s->ack_fail_pkts     += ais.ack_fail_pkts;
			s->aggr_pkts         += ais.aggr_pkts;
			s->rx_err_pkts       += ais.rx_err_pkts;
			s->tx_ucast_pkts     += ais.tx_ucast_pkts;
			s->rx_ucast_pkts     += ais.rx_ucast_pkts;
			s->tx_dropped_pkts   += ais.tx_dropped_pkts;
			s->rx_dropped_pkts   += ais.rx_dropped_pkts;
			s->tx_mcast_pkts     += ais.tx_mcast_pkts;
			s->rx_mcast_pkts     += ais.rx_mcast_pkts;
			s->tx_bcast_pkts     += ais.tx_bcast_pkts;
			s->rx_bcast_pkts     += ais.rx_bcast_pkts;
			s->rx_unknown_pkts   += ais.rx_unknown_pkts;
			s->tx_buf_overflow   += ais.tx_buf_overflow;
			s->tx_sta_not_assoc  += ais.tx_sta_not_assoc;
			s->tx_frags          += ais.tx_frags;
			s->tx_no_ack_pkts    += ais.tx_no_ack_pkts;
			s->rx_dup_pkts       += ais.rx_dup_pkts;
			s->tx_too_long_pkts  += ais.tx_too_long_pkts;
			s->tx_too_short_pkts += ais.tx_too_short_pkts;
			s->ucast_ack         += ais.ucast_ack;
		}
	}

	return 0;
}

static int mtk_mld_get_assoclist(const char *ifname, uint8_t *stas, int *num_stas)
{
	struct wifi_mlsta mlsta[STA_MAX];
	struct connected_sta data = { .all = true, .stas = stas, .num_stas = num_stas, .mlsta = mlsta};
	struct wifi_mlo_link links[MLD_LINK_MAX];
	struct bss_mlo_info_group gdata = { .u.ap.band = BAND_ANY, .links = links, .max = ARRAY_SIZE(links) };
	uint8_t group = ifname2group(ifname);
	int ret, i, k, n;

	if (group == 0)
		return -1;

	if (get_bss_mlo_info_group(NULL, group, &gdata))
		return -1;

	*num_stas = 0;
	ret = get_connected_sta(NULL, group, &data);
	if (ret)
		return ret;

	n = *num_stas;
	for (k = 0; k < gdata.num; k++) {
		uint8_t l_sta[STA_MAX * MAC_ADDR_LEN]; /* Legacy STAs */
		int ln = STA_MAX;

		ret = nlwifi_get_assoclist_band(gdata.ifnames[k], BAND_ANY, l_sta, &ln);
		if (ret)
			return ret;

		for (i = 0; i < ln; i++) {
			uint8_t *mac = l_sta + i * MAC_ADDR_LEN;
			struct wifi_mlsta *m;
			struct wifi_sta *s;

			if (find_sta(mac, &gdata, &data, n, &m, &s)  != 0 && *num_stas < STA_MAX) {
				memcpy(stas + *num_stas * MAC_ADDR_LEN, mac, MAC_ADDR_LEN);
				(*num_stas)++;
			}
		}
	}

	return 0;
}

static int mtk_mld_get_assoclist_band(const char *ifname, enum wifi_band band,
		uint8_t *stas, int *num_stas)
{
	UNUSED(band);
	return mtk_mld_get_assoclist(ifname, stas, num_stas);
}

static int mtk_mld_get_mlsta_info(const char *ifname, uint8_t *macaddr,
		struct wifi_mlsta *mlsta)
{
	struct connected_sta data = { .all = false, .mlsta = mlsta };
	struct wifi_mlo_link links[MLD_LINK_MAX];
	struct bss_mlo_info_group gdata = { .u.ap.band = BAND_ANY, .links = links, .max = ARRAY_SIZE(links) };
	uint8_t group = ifname2group(ifname);
	int ret, i, k;

	if (group == 0)
		return -1;

	memset(mlsta, 0, sizeof(*mlsta));
	memcpy(mlsta->macaddr, macaddr, MAC_ADDR_LEN);

	ret = get_connected_sta(NULL, group, &data);
	if (ret != 0)
		return ret;

	if (get_bss_mlo_info_group(NULL, group, &gdata))
		return -1;

	if (!data.found)
	{
		/* Legacy STA */
		for (k = 0; k < gdata.num; k++) {
			if (mtk_get_sta_info_band_check_mlo(gdata.ifnames[k], BAND_ANY, 
							macaddr, mlsta->sta, false) == 0)
				break;
		}
		if (k == gdata.num)
			return -1;
		mlsta->mlo_capable = false;
		mlsta->sta[0].mlo_link_id = WIFI_MLO_INVALID_LINK_ID;
		mlsta->sta[0].is_setup_link = false;
		mlsta->num_link = 1;
		mlsta->is4addr = mlsta->sta[0].is4addr;
		mlsta->stats = mlsta->sta[0].stats;
		memcpy(mlsta->bssid, mlsta->sta[0].bssid, MAC_ADDR_LEN);
		/* Hack to get correct output on ubus call */
		memcpy(mlsta->sta[0].mld_macaddr, macaddr, MAC_ADDR_LEN);
		return 0;
	}

	memcpy(mlsta->ssid, gdata.u.ap.ssid, gdata.u.ap.ssidlen);
	mlsta->ssid[gdata.u.ap.ssidlen] = '\0';
	mlsta->ssidlen = gdata.u.ap.ssidlen;
	memcpy(mlsta->bssid, gdata.bssid, MAC_ADDR_LEN);

	for (i = 0; i < mlsta->num_link; i++) {
		for (k = 0; k < gdata.num; k++) {
			if (memcmp(mlsta->sta[i].bssid, links[k].macaddr, MAC_ADDR_LEN) == 0) {
				ret = mtk_get_sta_info_band_check_mlo(gdata.ifnames[k], BAND_ANY,
								mlsta->sta[i].macaddr, mlsta->sta + i, false);
				if (ret)
					return ret;
				mlsta->sta[i].mlo_capable = true;
				mlsta->sta[i].mlo_link_id = links[k].id;
				break;
			}
		}
		if (k == gdata.num)
			return -1;
		memcpy(mlsta->sta[i].mld_macaddr, macaddr, MAC_ADDR_LEN);
		memcpy(mlsta->sta[i].mld_bssid, gdata.bssid, MAC_ADDR_LEN);
		mlsta->stats.tx_bytes += mlsta->sta[i].stats.tx_bytes;
		mlsta->stats.rx_bytes += mlsta->sta[i].stats.rx_bytes;
		mlsta->stats.tx_ucast_bytes += mlsta->sta[i].stats.tx_ucast_bytes;
		mlsta->stats.rx_ucast_bytes += mlsta->sta[i].stats.rx_ucast_bytes;
		mlsta->stats.tx_mcast_bytes += mlsta->sta[i].stats.tx_mcast_bytes;
		mlsta->stats.rx_mcast_bytes += mlsta->sta[i].stats.rx_mcast_bytes;
		mlsta->stats.tx_pkts += mlsta->sta[i].stats.tx_pkts;
		mlsta->stats.rx_pkts += mlsta->sta[i].stats.rx_pkts;
		mlsta->stats.tx_ucast_pkts += mlsta->sta[i].stats.tx_ucast_pkts;
		mlsta->stats.rx_ucast_pkts += mlsta->sta[i].stats.rx_ucast_pkts;
		mlsta->stats.tx_mcast_pkts += mlsta->sta[i].stats.tx_mcast_pkts;
		mlsta->stats.rx_mcast_pkts += mlsta->sta[i].stats.rx_mcast_pkts;
		mlsta->stats.tx_err_pkts += mlsta->sta[i].stats.tx_err_pkts;
		mlsta->stats.tx_rtx_pkts += mlsta->sta[i].stats.tx_rtx_pkts;
		mlsta->stats.tx_rtx_fail_pkts += mlsta->sta[i].stats.tx_rtx_fail_pkts;
		mlsta->stats.tx_retry_pkts += mlsta->sta[i].stats.tx_retry_pkts;
		mlsta->stats.tx_mretry_pkts += mlsta->sta[i].stats.tx_mretry_pkts;
		mlsta->stats.tx_fail_pkts += mlsta->sta[i].stats.tx_fail_pkts;
		mlsta->stats.rx_fail_pkts += mlsta->sta[i].stats.rx_fail_pkts;
	}

	return 0;
}

static int mtk_mld_disconnect_sta(const char *ifname, uint8_t *sta, uint16_t reason)
{
	struct wifi_mlo_link links[MLD_LINK_MAX];
	struct wifi_mlsta mlsta;
	struct bss_mlo_info_group gdata = { .u.ap.band = BAND_ANY, .links = links, .max = ARRAY_SIZE(links) };
	struct connected_sta data = { .all = false, .mlsta = &mlsta };
	uint8_t group = ifname2group(ifname);
	int ret, i, k;

	if (group == 0)
		return -1;

	if (get_bss_mlo_info_group(NULL, group, &gdata))
		return -1;

	/* 
	 * Ignore return codes for disconnect methods here because hostapd 
	 * returns 0 if it has not found STA 
	 */

	memcpy(mlsta.macaddr, sta, MAC_ADDR_LEN);
	ret = get_connected_sta(NULL, group, &data);
	if (ret != 0)
		return ret;

	if (!data.found) {
		/* Try to disconnect legacy STA */
		for (k = 0; k < gdata.num; k++)
			default_wifi_driver.disconnect_sta(gdata.ifnames[k], sta, reason);
		return 0;
	}

	for (i = 0; i < mlsta.num_link; i++) {
		for (k = 0; k < gdata.num; k++) {
			if (memcmp(mlsta.sta[i].bssid, links[k].macaddr, MAC_ADDR_LEN) == 0) {
				default_wifi_driver.disconnect_sta(gdata.ifnames[k],
								mlsta.sta[i].macaddr, reason);
			}
		}
	}

	return 0;
}

static int mtk_mld_get_netdev_ifname(const char *name, enum wifi_band band, char **ifname)
{
	struct wifi_mlo_link links[MLD_LINK_MAX];
	struct bss_mlo_info_group gdata = { .u.ap.band = band, .links = links, .max = ARRAY_SIZE(links)};
	uint8_t group;

	if (band == 0)
		band = BAND_ANY;

	if (!strstr(name, MLD_AP_NAME) && !strstr(name, MLD_STA_NAME)) {
		*ifname = strdup(name);
		return 0;
	}

	group = ifname2group(name);
	if (get_bss_mlo_info_group(NULL, group, &gdata))
		return -1;

	if (gdata.num != 1 && band != BAND_ANY)
		return -1;

	*ifname = strdup(gdata.ifnames[0]);

	return 0;
}

static int mtk_mld_get_ctrl_interface_band(const char *name, enum wifi_band band, char **ctrl_interface)
{
	return mtk_mld_get_netdev_ifname(name, band, ctrl_interface);
}

static int mtk_mld_ap_info_band(const char *name, enum wifi_band band, struct wifi_ap *ap)
{
	char *ifname = NULL;
	int ret;

	ret = mtk_mld_get_netdev_ifname(name, band, &ifname);
	if (ret)
		return ret;

	ret = mtk_ap_info_band(ifname, band, ap);
	free(ifname);

	return ret;
}

static int mtk_mld_get_beacon_ies_band(const char *ifname, enum wifi_band band, uint8_t *ies, int *len)
{
	/* Get it via default path - hostapd. TODO check quality */
	return default_wifi_driver.iface.get_beacon_ies_band(ifname, band, ies, len);
}

static int mtk_mld_get_beacon_ies(const char *ifname, uint8_t *ies, int *len)
{
	return default_wifi_driver.get_beacon_ies(ifname, ies, len);
}

static int mtk_mld_block_sta(const char *ifname, enum wifi_band band, uint8_t *sta, int block)
{
	struct wifi_mlo_link links[MLD_LINK_MAX];
	struct bss_mlo_info_group gdata = { .u.ap.band = band, .links = links, .max = ARRAY_SIZE(links) };
	uint8_t group = ifname2group(ifname);
	int ret = 0, k;

	if (group == 0)
		return -1;

	if (band < BAND_2 || band > BAND_6)
		gdata.u.ap.band = BAND_ANY;

	if (get_bss_mlo_info_group(NULL, group, &gdata))
		return -1;

	for (k = 0; k < gdata.num && !ret; k++) {
		ret = default_wifi_driver.iface.block_sta(gdata.ifnames[k],
								name2band(gdata.ifnames[k]), sta, block);
	}

	if (ret) {
		for (k--; k >= 0; k--)
			default_wifi_driver.iface.block_sta(gdata.ifnames[k], name2band(gdata.ifnames[k]),
								sta, !block);
	}

	return ret;
}

static int mtk_mld_get_blocked_stas(const char *ifname, enum wifi_band band, uint8_t *stas, int *num_stas)
{
	struct wifi_mlo_link links[MLD_LINK_MAX];
	struct bss_mlo_info_group gdata = { .u.ap.band = BAND_ANY, .links = links, .max = ARRAY_SIZE(links) };
	uint8_t group = ifname2group(ifname);
	int total = 0, max = *num_stas;
	int k;

	if (band != BAND_ANY)
		return default_wifi_driver.iface.get_blocked_stas(ifname, band, stas, num_stas);

	if (get_bss_mlo_info_group(NULL, group, &gdata))
		return -1;

	for (k = 0; k < gdata.num && total < max; k++) {
		uint8_t band_stas[STA_MAX * MAC_ADDR_LEN];
		int band_stas_num, ret, i;

		ret = default_wifi_driver.iface.get_blocked_stas(gdata.ifnames[k],
								name2band(gdata.ifnames[k]), band_stas, &band_stas_num);
		if (ret)
			return ret;

		for (i = 0; i < band_stas_num && total < max; i++) {
			if (memmem(stas, total * MAC_ADDR_LEN,
					band_stas + i * MAC_ADDR_LEN, MAC_ADDR_LEN) == NULL) {
				memcpy(stas + total * MAC_ADDR_LEN, band_stas + i * MAC_ADDR_LEN,
					MAC_ADDR_LEN);
				total++;
			}
		}
	}

	*num_stas = total;

	return 0;
}

static int mtk_mld_mlsta_interface_info(const char *ifname, struct wifi_mlsta *data)
{
	struct wifi_mlo_link links[MLD_LINK_MAX];
	struct bss_mlo_info_group gdata = { .u.ap.band = BAND_ANY, .links = links, .max = ARRAY_SIZE(links) };
	int ret, i;

	UNUSED(ifname);
	if (get_bss_mlo_info_group(ifname, STA_MLD_GROUP_ID, &gdata) || gdata.num < 1)
		return -1;

	memcpy(data->macaddr, gdata.u.sta.mld_mac, sizeof(data->macaddr));
	memcpy(data->bssid, gdata.bssid, sizeof(data->bssid));

	data->num_link = gdata.num;
	data->ssidlen = 0;

	for (i = 0; i < gdata.num; i++) {
		ret = default_wifi_driver.iface.sta_info(gdata.ifnames[i], data->sta + i);
		if (ret < 0)
			return ret;
		data->sta[i].mlo_capable = true;
		data->sta[i].mlo_link_id = gdata.links[i].id;
		data->sta[i].is_setup_link = gdata.links[i].is_setup_link;
		if (data->ssidlen) {
			memcpy(data->ssid, data->sta[i].ssid, data->sta[i].ssidlen);
			data->ssid[data->sta[i].ssidlen] = '\0';
			data->ssidlen = data->sta[i].ssidlen;
		}
	}
	data->is4addr = data->sta[0].is4addr;

	return 0;
}

static int mtk_mld_set_4addr(const char *ifname, bool enable)
{
	return -1;
}

static int mtk_mld_get_4addr(const char *ifname, bool *enabled)
{
	struct wifi_mlo_link links[MLD_LINK_MAX];
	uint8_t group = ifname2group(ifname);
	struct bss_mlo_info_group gdata = { .u.ap.band = BAND_ANY, .links = links, .max = ARRAY_SIZE(links) };

	if (get_bss_mlo_info_group(NULL, group, &gdata) || gdata.num < 1)
		return -1;

	return nlwifi_get_4addr(gdata.ifnames[0], enabled);
}

static void mtk_mld_merge_status(const char *ifname, ifstatus_t *f)
{
	ifstatus_t aff_f = 0;
	int ret;

	ret = get_ifstatus(ifname, &aff_f);
	if (ret < 0)
		return;

	if ((*f & IFF_UP) && !(aff_f & IFF_UP))
		return;

	*f = aff_f;
}

static int mtk_mld_get_ifstatus(const char *ifname, ifstatus_t *f)
{
	struct wifi_mlo_link links[MLD_LINK_MAX];
	struct bss_mlo_info_group gdata = { .u.ap.band = BAND_ANY, .links = links,
										.max = ARRAY_SIZE(links) };
	uint8_t group = ifname2group(ifname);
	int i;

	*f = 0;

	if (get_bss_mlo_info_group(NULL, group, &gdata) || gdata.num < 1)
		return 0;

	for (i = 0; i < gdata.num && !(*f & IFF_RUNNING); i++)
		mtk_mld_merge_status(gdata.ifnames[i], f);

	return 0;
}

const struct wifi_driver mtk_mld_driver = {
	.name = MLD_AP_NAME "," MLD_STA_NAME,
	.info = mtk_mld_info,
	.radio_list = mtk_mld_radio_list,

	/* Methods for radio should be NULL */

	/* Interface methods */
	.iface.get_mode = mtk_mld_get_mode,
	.get_mlo_links = mtk_mld_get_mlo_links,

	.get_bssid = mtk_mld_get_bssid,
	.get_ssid = mtk_mld_get_ssid,
	.iface.get_stats = mtk_mld_get_stats,
	.get_assoclist = mtk_mld_get_assoclist,
	.iface.get_assoclist_band = mtk_mld_get_assoclist_band,
	.iface.get_mlsta_info = mtk_mld_get_mlsta_info,
	.disconnect_sta = mtk_mld_disconnect_sta,
	.get_beacon_ies = mtk_mld_get_beacon_ies,
	.iface.get_beacon_ies_band = mtk_mld_get_beacon_ies_band,
	.iface.get_ctrl_interface_band = mtk_mld_get_ctrl_interface_band,
	.iface.ap_info_band = mtk_mld_ap_info_band,
	.iface.block_sta = mtk_mld_block_sta,
	.iface.get_blocked_stas = mtk_mld_get_blocked_stas,
	.iface.mlsta_interface_info = mtk_mld_mlsta_interface_info,
	.get_4addr = mtk_mld_get_4addr,
	.set_4addr = mtk_mld_set_4addr,
	.iface.mld_get_ifstatus = mtk_mld_get_ifstatus,
};

