[rdkb][common][bsp][Refactor and sync wifi from openwrt]

[Description]
3a2eef0b [MAC80211][Release][Update release note for Filogic 880/860 MLO Beta release]
cfbd2411 [MAC80211][Release][Filogic 880/860 MLO Beta release]
6c180e3f [MAC80211][WiFi7][misc][Add Eagle BE14000 efem default bin]
a55f34db [MAC80211][Release][Prepare for Filogic 880/860 release]
5b45ebca [MAC80211][WiFi7][hostapd][Add puncture bitmap to ucode]
95bbea73 [MAC80211][WiFi6][mt76][Add PID to only report data-frame TX rate]
b15ced26 [MAC80211][WiFi6][hostapd][Fix DFS channel selection issue]
d59133cb [MAC80211][WiFi6][mt76][Fix pse info not correct information]
3921b4b2 [MAC80211][WiFi6][mt76][Fix incomplete QoS-map setting to FW]
4e7690c7 [MAC80211][WiFi6/7][app][Change ATECHANNEL mapping cmd]
eb37af90 [MAC80211][WiFi7][app][Add support for per-packet bw & primary selection]
0ea82adf [MAC80211][WiFi6][core][Fix DFS CAC issue after CSA]

[Release-log]

Change-Id: I9bec97ec1b2e1c49ed43a812a07a5b21fcbb70a6
diff --git a/recipes-wifi/hostapd/files/patches-2.10.3/0121-mtk-hostapd-Add-Triggered-Uplink-Access-Optimization.patch b/recipes-wifi/hostapd/files/patches-2.10.3/0121-mtk-hostapd-Add-Triggered-Uplink-Access-Optimization.patch
new file mode 100644
index 0000000..567d0d3
--- /dev/null
+++ b/recipes-wifi/hostapd/files/patches-2.10.3/0121-mtk-hostapd-Add-Triggered-Uplink-Access-Optimization.patch
@@ -0,0 +1,651 @@
+From d612e6c3981212812e87374f90b7be07edf9bc2a Mon Sep 17 00:00:00 2001
+From: Howard Hsu <howard-yh.hsu@mediatek.com>
+Date: Tue, 2 Jul 2024 09:46:26 +0800
+Subject: [PATCH 121/126] mtk: hostapd: Add Triggered Uplink Access
+ Optimization support
+
+Add TUAO feature support.
+
+Signed-off-by: Howard Hsu <howard-yh.hsu@mediatek.com>
+---
+ hostapd/Makefile                  |   1 +
+ src/ap/Makefile                   |   3 +-
+ src/ap/ap_drv_ops.c               |  13 ++
+ src/ap/ap_drv_ops.h               |   5 +
+ src/ap/ieee802_11.c               |   6 +
+ src/ap/scs.c                      | 247 ++++++++++++++++++++++++++++++
+ src/ap/scs.h                      |  30 ++++
+ src/ap/sta_info.h                 |   5 +
+ src/common/ieee802_11_defs.h      |   5 +
+ src/common/mtk_vendor.h           |  17 ++
+ src/drivers/driver.h              |  26 ++++
+ src/drivers/driver_nl80211.c      |  62 ++++++++
+ src/drivers/driver_nl80211.h      |   1 +
+ src/drivers/driver_nl80211_capa.c |   3 +
+ 14 files changed, 423 insertions(+), 1 deletion(-)
+ create mode 100644 src/ap/scs.c
+ create mode 100644 src/ap/scs.h
+
+diff --git a/hostapd/Makefile b/hostapd/Makefile
+index 8dc6e6216..233176ae5 100644
+--- a/hostapd/Makefile
++++ b/hostapd/Makefile
+@@ -382,6 +382,7 @@ ifdef CONFIG_IEEE80211BE
+ CONFIG_IEEE80211AX=y
+ CFLAGS += -DCONFIG_IEEE80211BE
+ OBJS += ../src/ap/ieee802_11_eht.o
++OBJS += ../src/ap/scs.o
+ endif
+ 
+ ifdef CONFIG_IEEE80211AX
+diff --git a/src/ap/Makefile b/src/ap/Makefile
+index a1e9b7c44..49c6d4a13 100644
+--- a/src/ap/Makefile
++++ b/src/ap/Makefile
+@@ -55,6 +55,7 @@ LIB_OBJS= \
+ 	wpa_auth_glue.o \
+ 	wpa_auth_ie.o \
+ 	wps_hostapd.o \
+-	x_snoop.o
++	x_snoop.o \
++	scs.o
+ 
+ include ../lib.rules
+diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
+index 2f53c518f..64fd0c04c 100644
+--- a/src/ap/ap_drv_ops.c
++++ b/src/ap/ap_drv_ops.c
+@@ -23,6 +23,10 @@
+ #include "wpa_auth.h"
+ #include "ap_drv_ops.h"
+ 
++#ifdef CONFIG_IEEE80211BE
++#include "scs.h"
++#endif
++
+ 
+ u32 hostapd_sta_flags_to_drv(u32 flags)
+ {
+@@ -1531,3 +1535,12 @@ int hostapd_drv_csi_dump(struct hostapd_data *hapd, void *dump_buf)
+ 		return 0;
+ 	return hapd->driver->csi_dump(hapd->drv_priv, hapd->iconf->band_idx, dump_buf);
+ }
++
++#ifdef CONFIG_IEEE80211BE
++int hostapd_drv_set_scs(struct hostapd_data *hapd, struct hostapd_scs_desc_info *info)
++{
++	if (!hapd->driver || !hapd->driver->set_scs)
++		return 0;
++	return hapd->driver->set_scs(hapd->drv_priv, info, hapd->mld_link_id);
++}
++#endif
+diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h
+index a8c60b6f6..3e5743754 100644
+--- a/src/ap/ap_drv_ops.h
++++ b/src/ap/ap_drv_ops.h
+@@ -190,6 +190,11 @@ int hostapd_drv_wnm_oper(struct hostapd_data *hapd,
+ int hostapd_drv_set_qos_map(struct hostapd_data *hapd, const u8 *qos_map_set,
+ 			    u8 qos_map_set_len);
+ 
++#ifdef CONFIG_IEEE80211BE
++int hostapd_drv_set_scs(struct hostapd_data *hapd,
++			struct hostapd_scs_desc_info *info);
++#endif
++
+ void hostapd_get_ext_capa(struct hostapd_iface *iface);
+ void hostapd_get_mld_capa(struct hostapd_iface *iface);
+ 
+diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
+index 886a21a66..09aa1141b 100644
+--- a/src/ap/ieee802_11.c
++++ b/src/ap/ieee802_11.c
+@@ -62,6 +62,9 @@
+ #ifdef CONFIG_APUP
+ #	include "apup.h"
+ #endif // def CONFIG_APUP
++#ifdef CONFIG_IEEE80211BE
++#include "scs.h"
++#endif
+ 
+ #ifdef CONFIG_FILS
+ static struct wpabuf *
+@@ -6247,6 +6250,9 @@ static int handle_action(struct hostapd_data *hapd,
+ 		hostapd_handle_radio_measurement(hapd, (const u8 *) mgmt, len);
+ 		return 1;
+ #endif /* CONFIG_NO_RRM */
++	case WLAN_ACTION_ROBUST_AV_STREAMING:
++		hostapd_handle_scs(hapd, (const u8 *) mgmt, len);
++		return 1;
+ 	}
+ 
+ 	hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
+diff --git a/src/ap/scs.c b/src/ap/scs.c
+new file mode 100644
+index 000000000..6f3c0444e
+--- /dev/null
++++ b/src/ap/scs.c
+@@ -0,0 +1,247 @@
++#include "utils/includes.h"
++
++#include "utils/common.h"
++#include "common/ieee802_11_defs.h"
++#include "common/ieee802_11_common.h"
++#include "hostapd.h"
++#include "ieee802_11.h"
++#include "sta_info.h"
++#include "ap_config.h"
++#include "ap_drv_ops.h"
++#include "scs.h"
++
++static bool hostapd_find_scs_session(struct sta_info *sta, u8 scsid,
++				     u8 *session_idx)
++{
++	u8 idx;
++
++	for (idx = 0; idx < SCS_MAX_CFG_CNT; idx++) {
++		if (sta->scs_session[idx].scs_id == scsid) {
++			*session_idx = idx;
++			return sta->scs_session[idx].alive;
++		}
++	}
++
++	return false;
++}
++
++static int hostapd_find_available_scs_session(struct sta_info *sta)
++{
++	u8 idx;
++
++	for (idx = 0; idx < SCS_MAX_CFG_CNT; idx++) {
++		if (!sta->scs_session[idx].alive)
++			return idx;
++	}
++
++	return -1;
++}
++
++static bool hostapd_parse_qos_char_element(const struct element *elem,
++					   struct hostapd_scs_desc_info *info)
++{
++#define SCS_DIRECTION_UPLINK 0
++	u8 id_extension = elem->data[0];
++	u32 control_info;
++
++	info->qos_ie_len = elem->datalen + 2;
++
++	if (id_extension != WLAN_EID_EXT_QOS_CHARACTERISTICS ||
++	    info->qos_ie_len > sizeof(info->qos_ie))
++		return false;
++
++	control_info = WPA_GET_LE32(&elem->data[1]);
++	info->dir = control_info & 0x3;
++
++	/* Only support Uplink direction SCS request now. */
++	if (info->dir != SCS_DIRECTION_UPLINK)
++		return false;
++
++	os_memcpy(info->qos_ie, elem, info->qos_ie_len);
++
++	return true;
++}
++
++static u16 hostapd_process_scs_descriptor(struct hostapd_data *hapd,
++					  struct sta_info *sta, const u8 *payload,
++					  u8 scs_desc_len,
++					  struct hostapd_scs_desc_info *info)
++{
++	bool scs_avail, qos_char_elem_avail = false;
++	const struct element *elem;
++	u8 session_idx;
++	int ret;
++
++	scs_avail = hostapd_find_scs_session(sta, info->id, &session_idx);
++
++	switch (info->req_type) {
++	case SCS_REQ_TYPE_ADD:
++	case SCS_REQ_TYPE_CHANGE:
++		if ((info->req_type == SCS_REQ_TYPE_ADD && scs_avail) ||
++		    (info->req_type == SCS_REQ_TYPE_CHANGE && !scs_avail))
++			goto decline;
++
++		if (info->req_type == SCS_REQ_TYPE_ADD) {
++			session_idx = hostapd_find_available_scs_session(sta);
++			if (session_idx == -1) {
++				wpa_printf(MSG_ERROR, "%s: Out of SCS resource.\n",
++					   __func__);
++				goto decline;
++			}
++		}
++
++		for_each_element(elem, payload + 2, scs_desc_len - 2) {
++			switch (elem->id) {
++			case WLAN_EID_EXTENSION:
++				qos_char_elem_avail =
++					hostapd_parse_qos_char_element(elem, info);
++				break;
++			default:
++				/* The rest elements would be ignored now. */
++				break;
++			}
++		}
++
++		if (!qos_char_elem_avail) {
++			wpa_printf(MSG_ERROR, "%s: The content of QoS Charactristics"
++				   " element is empty or not supported yet!\n",
++				   __func__);
++			goto decline;
++		}
++
++		break;
++	case SCS_REQ_TYPE_REMOVE:
++		if (!scs_avail)
++			goto decline;
++
++		break;
++	default:
++		goto decline;
++	}
++
++	ret = hostapd_drv_set_scs(hapd, info);
++	if (ret)
++		goto decline;
++
++	sta->scs_session[session_idx].scs_id = info->id;
++	sta->scs_session[session_idx].alive =
++		info->req_type == SCS_REQ_TYPE_REMOVE ? false : true;
++
++	return (info->req_type == SCS_REQ_TYPE_REMOVE) ?
++		SCS_REQ_TCLAS_PROCESSING_TERMINATED : SCS_REQ_SUCCESS;
++
++decline:
++	wpa_printf(MSG_ERROR, "%s: Decline Request Type %d\n",
++		   __func__, info->req_type);
++
++	return SCS_REQ_DECLINED;
++}
++
++static void send_scs_response(struct hostapd_data *hapd,
++			      struct scs_status_duple *scs_status, const u8 *da,
++			      u8 dialog_token, u8 count)
++{
++	struct wpabuf *buf;
++	size_t len;
++	u8 i;
++
++	if (count == 0)
++		return;
++
++	/* Reference to 802_11be_D5.0 Figure 9-1183  */
++	len = 4 + count * sizeof(struct scs_status_duple);
++	buf = wpabuf_alloc(len);
++	if (buf == NULL)
++		return;
++
++	wpabuf_put_u8(buf, WLAN_ACTION_ROBUST_AV_STREAMING);
++	wpabuf_put_u8(buf, ROBUST_AV_SCS_RESP);
++	wpabuf_put_u8(buf, dialog_token);
++	wpabuf_put_u8(buf, count);
++
++	for (i = 0; i < count && i < SCS_MAX_CFG_CNT; i++) {
++		wpabuf_put_u8(buf, scs_status[i].scs_id);
++		wpabuf_put_le16(buf, scs_status[i].status);
++	}
++
++	len = wpabuf_len(buf);
++	hostapd_drv_send_action(hapd, hapd->iface->freq, 0, da,
++				wpabuf_head(buf), len);
++	wpabuf_free(buf);
++}
++
++static void hostapd_handle_scs_req(struct hostapd_data *hapd,
++				   const u8 *buf, size_t len)
++{
++	const struct ieee80211_mgmt *mgmt = (const struct ieee80211_mgmt *) buf;
++	struct hostapd_scs_desc_info info;
++	struct sta_info *sta;
++	struct scs_status_duple scs_status_list[SCS_MAX_CFG_CNT];
++	const u8 *pos, *end;
++	u8 token, index = 0;
++	const struct element *elem;
++
++	sta = ap_get_sta(hapd, mgmt->sa);
++
++	if (!sta) {
++		wpa_printf(MSG_ERROR, "Station " MACSTR " not found "
++			   "for SCS Request frame\n", MAC2STR(mgmt->sa));
++		return;
++	}
++
++	token = mgmt->u.action.u.scs.dialog_token;
++	pos = mgmt->u.action.u.scs.variable;
++
++	end = buf + len;
++	len = end - pos;
++
++	for_each_element(elem, pos, len) {
++		if (elem->id != WLAN_EID_SCS_DESCRIPTOR) {
++			wpa_printf(MSG_ERROR, "%s: no scs elem %d in scs req frame!\n",
++				   __func__, WLAN_EID_SCS_DESCRIPTOR);
++			break;
++		}
++
++		info.id = elem->data[0];
++		if (!info.id) {
++			wpa_printf(MSG_ERROR, "%s: SCSID = 0 is invalid\n", __func__);
++			break;
++		}
++
++		info.req_type = elem->data[1];
++		os_memcpy(info.peer_addr, mgmt->sa, ETH_ALEN);
++		scs_status_list[index].scs_id = info.id;
++		scs_status_list[index].status =
++			hostapd_process_scs_descriptor(hapd, sta, elem->data,
++						       elem->datalen, &info);
++		index++;
++	}
++
++	send_scs_response(hapd, scs_status_list, mgmt->sa, token, index);
++}
++
++void hostapd_handle_scs(struct hostapd_data *hapd, const u8 *buf, size_t len)
++{
++	const struct ieee80211_mgmt *mgmt = (const struct ieee80211_mgmt *) buf;
++
++	/*
++	 * Check for enough bytes: header + (1B)Category + (1B)Action +
++	 * (1B)Dialog Token.
++	 */
++	if (len < IEEE80211_HDRLEN + 3) {
++		wpa_printf(MSG_ERROR, "%s SCS frame len %lu is not enough!",
++			   __func__, len);
++		return;
++	}
++
++	switch (mgmt->u.action.u.scs.action) {
++	case ROBUST_AV_SCS_REQ:
++		hostapd_handle_scs_req(hapd, buf, len);
++		break;
++	case ROBUST_AV_SCS_RESP:
++		/* Not supported yet. */
++		break;
++	default:
++		break;
++	}
++}
+diff --git a/src/ap/scs.h b/src/ap/scs.h
+new file mode 100644
+index 000000000..b69190b65
+--- /dev/null
++++ b/src/ap/scs.h
+@@ -0,0 +1,30 @@
++#ifndef SCS_H
++#define SCS_H
++
++struct hostapd_data;
++
++/* Only support TUAO certification */
++#define SCS_MAX_CFG_CNT 2
++
++struct scs_status_duple {
++	u8 scs_id;
++	u16 status;
++};
++
++struct scs_session_status {
++	u8 scs_id;
++	bool alive;
++};
++
++enum scs_req_type {
++	SCS_REQ_TYPE_ADD,
++	SCS_REQ_TYPE_REMOVE,
++	SCS_REQ_TYPE_CHANGE,
++};
++
++#define SCS_REQ_SUCCESS		0
++#define SCS_REQ_DECLINED	37
++#define SCS_REQ_TCLAS_PROCESSING_TERMINATED	97
++
++void hostapd_handle_scs(struct hostapd_data *hapd, const u8 *buf, size_t len);
++#endif
+diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h
+index 60b33f049..339adf987 100644
+--- a/src/ap/sta_info.h
++++ b/src/ap/sta_info.h
+@@ -18,6 +18,10 @@
+ #include "crypto/sha384.h"
+ #include "pasn/pasn_common.h"
+ 
++#ifdef CONFIG_IEEE80211BE
++#include "scs.h"
++#endif
++
+ /* STA flags */
+ #define WLAN_STA_AUTH BIT(0)
+ #define WLAN_STA_ASSOC BIT(1)
+@@ -335,6 +339,7 @@ struct sta_info {
+ 	struct mld_info mld_info;
+ 	u8 mld_assoc_link_id;
+ 	struct sta_info *mld_assoc_sta;
++	struct scs_session_status scs_session[SCS_MAX_CFG_CNT];
+ #endif /* CONFIG_IEEE80211BE */
+ 
+ 	u16 max_idle_period; /* if nonzero, the granted BSS max idle period in
+diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h
+index afcc2f861..4754d5a69 100644
+--- a/src/common/ieee802_11_defs.h
++++ b/src/common/ieee802_11_defs.h
+@@ -1196,6 +1196,11 @@ struct ieee80211_mgmt {
+ 					u8 action;
+ 					u8 variable[];
+ 				} STRUCT_PACKED eht_prot;
++				struct {
++					u8 action;
++					u8 dialog_token;
++					u8 variable[];
++				} STRUCT_PACKED scs;
+ 			} u;
+ 		} STRUCT_PACKED action;
+ 	} u;
+diff --git a/src/common/mtk_vendor.h b/src/common/mtk_vendor.h
+index 6d75d39c2..1fe459126 100644
+--- a/src/common/mtk_vendor.h
++++ b/src/common/mtk_vendor.h
+@@ -20,6 +20,7 @@ enum mtk_nl80211_vendor_subcmds {
+ 	MTK_NL80211_VENDOR_SUBCMD_PP_CTRL = 0xcc,
+ 	MTK_NL80211_VENDOR_SUBCMD_BEACON_CTRL = 0xcd,
+ 	MTK_NL80211_VENDOR_SUBCMD_TXPOWER_CTRL = 0xce,
++	MTK_NL80211_VENDOR_SUBCMD_SCS_CTRL = 0xd0,
+ 	MTK_NL80211_VENDOR_SUBCMD_EML_CTRL = 0xd3,
+ };
+ 
+@@ -325,6 +326,22 @@ enum mtk_vendor_attr_txpower_ctrl {
+ 		NUM_MTK_VENDOR_ATTRS_TXPOWER_CTRL - 1
+ };
+ 
++enum mtk_vendor_attr_scs_ctrl {
++	MTK_VENDOR_ATTR_SCS_CTRL_UNSPEC,
++
++	MTK_VENDOR_ATTR_SCS_ID,
++	MTK_VENDOR_ATTR_SCS_REQ_TYPE,
++	MTK_VENDOR_ATTR_SCS_DIR,
++	MTK_VENDOR_ATTR_SCS_QOS_IE,
++	MTK_VENDOR_ATTR_SCS_MAC_ADDR,
++	MTK_VENDOR_ATTR_SCS_LINK_ID,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_SCS_CTRL,
++	MTK_VENDOR_ATTR_SCS_CTRL_MAX =
++		NUM_MTK_VENDOR_ATTRS_SCS_CTRL - 1
++};
++
+ #define CSI_BW20_DATA_COUNT	64
+ #define CSI_BW40_DATA_COUNT	128
+ #define CSI_BW80_DATA_COUNT	256
+diff --git a/src/drivers/driver.h b/src/drivers/driver.h
+index 55f62f537..f3de2b3bc 100644
+--- a/src/drivers/driver.h
++++ b/src/drivers/driver.h
+@@ -70,6 +70,25 @@ enum hostapd_chan_width_attr {
+ #define HOSTAPD_DFS_REGION_ETSI	2
+ #define HOSTAPD_DFS_REGION_JP	3
+ 
++/**
++ * struct hostapd_scs_desc_info - SCS Req information
++ * @id: SCSID of each SCS stream
++ * @req_type: request type in SCS Descriptor element
++ * @dir: Direction in the control info of QoS Characteristics element
++ * @peer_addr: the mac addr of SCS requester station
++ * @qos_ie: QoS Characteristics IE in SCS Descriptor element
++ * @qos_ie_len: the length of QoS Characteristics element
++ */
++#define EID_EXT_QOS_CHAR_MAX_SIZE 44
++struct hostapd_scs_desc_info {
++	u8 id;
++	u8 req_type;
++	u8 dir;
++	u8 peer_addr[ETH_ALEN];
++	u8 qos_ie[EID_EXT_QOS_CHAR_MAX_SIZE];
++	u8 qos_ie_len;
++};
++
+ /**
+  * enum reg_change_initiator - Regulatory change initiator
+  */
+@@ -5424,6 +5443,13 @@ struct wpa_driver_ops {
+ 	int (*pp_mode_set)(void *priv, const u8 pp_mode, s8 link_id, u16 punct_bitmap);
+ #ifdef CONFIG_IEEE80211BE
+ 	int (*get_mld_addr)(void *priv, u8 *addr);
++	/**
++	 * set_scs - Configure Stream Classification Service
++	 * @priv: Private driver interface data
++	 * @info: Stream classidication service configuration
++	 * @link_id: MLD link id
++	 */
++	int (*set_scs)(void *priv, struct hostapd_scs_desc_info *info, u8 link_id);
+ #endif
+ 	/**
+ 	 * csi_set - Set csi related mode and parameter
+diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
+index 395bb5926..d2064fbd6 100644
+--- a/src/drivers/driver_nl80211.c
++++ b/src/drivers/driver_nl80211.c
+@@ -42,6 +42,10 @@
+ #include "common/mtk_vendor.h"
+ #include "ap/ap_config.h"
+ 
++#ifdef CONFIG_IEEE80211BE
++#include "ap/scs.h"
++#endif
++
+ 
+ #ifndef NETLINK_CAP_ACK
+ #define NETLINK_CAP_ACK 10
+@@ -2932,6 +2936,9 @@ static int nl80211_action_subscribe_ap(struct i802_bss *bss)
+ 	/* Protected EHT */
+ 	if (nl80211_register_action_frame(bss, (u8 *) "\x25", 1) < 0)
+ 		ret = -1;
++	/* Robust AV SCS Request */
++	if (nl80211_register_action_frame(bss, (u8 *) "\x13\x00", 2) < 0)
++		ret = -1;
+ 	/* Vendor-specific */
+ 	if (nl80211_register_action_frame(bss, (u8 *) "\x7f", 1) < 0)
+ 		ret = -1;
+@@ -15387,6 +15394,60 @@ static int nl80211_set_eml_omn(void *priv, u8 link_id, u8 *addr,
+ 
+ 	return ret;
+ 
++fail:
++	nlmsg_free(msg);
++	return ret;
++}
++
++static int
++nl80211_set_scs(void *priv, struct hostapd_scs_desc_info *info, u8 link_id)
++{
++	struct i802_bss *bss = priv;
++	struct wpa_driver_nl80211_data *drv = bss->drv;
++	struct nl_msg *msg;
++	struct nlattr *data;
++	int ret;
++
++	if (!drv->mtk_scs_vendor_cmd_avail) {
++		wpa_printf(MSG_ERROR,
++			   "nl80211: Driver does not support scs");
++		return 0;
++	}
++
++	msg = nl80211_drv_msg(drv, 0, NL80211_CMD_VENDOR);
++	if (!msg)
++		goto fail;
++
++	if (nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_MTK) ||
++	    nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD,
++			MTK_NL80211_VENDOR_SUBCMD_SCS_CTRL))
++		goto fail;
++
++	data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);
++	if (!data)
++		goto fail;
++
++	if (nla_put_u8(msg, MTK_VENDOR_ATTR_SCS_ID, info->id) ||
++	    nla_put_u8(msg, MTK_VENDOR_ATTR_SCS_REQ_TYPE, info->req_type) ||
++	    nla_put(msg, MTK_VENDOR_ATTR_SCS_MAC_ADDR, ETH_ALEN, info->peer_addr) ||
++	    nla_put_u8(msg, MTK_VENDOR_ATTR_SCS_LINK_ID, link_id))
++		goto fail;
++
++	if (info->req_type == SCS_REQ_TYPE_ADD ||
++	    info->req_type == SCS_REQ_TYPE_CHANGE)
++		if (nla_put_u8(msg, MTK_VENDOR_ATTR_SCS_DIR, info->dir) ||
++		    nla_put(msg, MTK_VENDOR_ATTR_SCS_QOS_IE, info->qos_ie_len,
++			    info->qos_ie))
++			goto fail;
++
++	nla_nest_end(msg, data);
++	ret = send_and_recv_cmd(drv, msg);
++	if (ret)
++		wpa_printf(MSG_ERROR, "Failed to set scs. ret = %d (%s)",
++			   ret, strerror(-ret));
++
++	return ret;
++
+ fail:
+ 	nlmsg_free(msg);
+ 	return ret;
+@@ -15830,6 +15891,7 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = {
+ 	.pp_mode_set = nl80211_pp_mode_set,
+ #ifdef CONFIG_IEEE80211BE
+ 	.get_mld_addr = nl80211_get_mld_addr,
++	.set_scs = nl80211_set_scs,
+ #endif
+ 	.csi_set = nl80211_csi_set,
+ 	.csi_dump = nl80211_csi_dump,
+diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h
+index d32c0ff4d..87ade150b 100644
+--- a/src/drivers/driver_nl80211.h
++++ b/src/drivers/driver_nl80211.h
+@@ -215,6 +215,7 @@ struct wpa_driver_nl80211_data {
+ 	unsigned int mtk_csi_vendor_cmd_avail:1;
+ 	unsigned int mtk_eml_vendor_cmd_avail:1;
+ 	unsigned int mtk_txpower_vendor_cmd_avail:1;
++	unsigned int mtk_scs_vendor_cmd_avail:1;
+ 
+ 	u32 ignore_next_local_disconnect;
+ 	u32 ignore_next_local_deauth;
+diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c
+index fc7f9062f..5db6bf6e6 100644
+--- a/src/drivers/driver_nl80211_capa.c
++++ b/src/drivers/driver_nl80211_capa.c
+@@ -1182,6 +1182,9 @@ static int wiphy_info_handler(struct nl_msg *msg, void *arg)
+ 				case MTK_NL80211_VENDOR_SUBCMD_TXPOWER_CTRL:
+ 					drv->mtk_txpower_vendor_cmd_avail = 1;
+ 					break;
++				case MTK_NL80211_VENDOR_SUBCMD_SCS_CTRL:
++					drv->mtk_scs_vendor_cmd_avail = 1;
++					break;
+ 				}
+ 			}
+ 
+-- 
+2.18.0
+