ASR_BASE

Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/Makefile b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/Makefile
new file mode 100644
index 0000000..9c41962
--- /dev/null
+++ b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/Makefile
@@ -0,0 +1,8 @@
+all:
+	@echo Nothing to be made.
+
+clean:
+	rm -f *~ *.o *.d
+
+install:
+	@echo Nothing to be made.
diff --git a/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst.c b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst.c
new file mode 100644
index 0000000..21ef3d8
--- /dev/null
+++ b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst.c
@@ -0,0 +1,240 @@
+/*
+ * FST module implementation
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "utils/eloop.h"
+#include "fst/fst.h"
+#include "fst/fst_internal.h"
+#include "fst/fst_defs.h"
+#include "fst/fst_ctrl_iface.h"
+
+static int fst_global_initialized = 0;
+struct dl_list fst_global_ctrls_list;
+
+
+static void fst_ctrl_iface_notify_peer_state_change(struct fst_iface *iface,
+						    bool connected,
+						    const u8 *peer_addr)
+{
+	union fst_event_extra extra;
+
+	extra.peer_state.connected = connected;
+	os_strlcpy(extra.peer_state.ifname, fst_iface_get_name(iface),
+		   sizeof(extra.peer_state.ifname));
+	os_memcpy(extra.peer_state.addr, peer_addr, ETH_ALEN);
+
+	foreach_fst_ctrl_call(on_event, EVENT_PEER_STATE_CHANGED,
+			      iface, NULL, &extra);
+}
+
+
+struct fst_iface * fst_attach(const char *ifname, const u8 *own_addr,
+			      const struct fst_wpa_obj *iface_obj,
+			      const struct fst_iface_cfg *cfg)
+{
+	struct fst_group *g;
+	struct fst_group *group = NULL;
+	struct fst_iface *iface = NULL;
+	bool new_group = false;
+
+	WPA_ASSERT(ifname != NULL);
+	WPA_ASSERT(iface_obj != NULL);
+	WPA_ASSERT(cfg != NULL);
+
+	foreach_fst_group(g) {
+		if (os_strcmp(cfg->group_id, fst_group_get_id(g)) == 0) {
+			group = g;
+			break;
+		}
+	}
+
+	if (!group) {
+		group = fst_group_create(cfg->group_id);
+		if (!group) {
+			fst_printf(MSG_ERROR, "%s: FST group cannot be created",
+				   cfg->group_id);
+			return NULL;
+		}
+		new_group = true;
+	}
+
+	iface = fst_iface_create(group, ifname, own_addr, iface_obj, cfg);
+	if (!iface) {
+		fst_printf_group(group, MSG_ERROR, "cannot create iface for %s",
+				 ifname);
+		if (new_group)
+			fst_group_delete(group);
+		return NULL;
+	}
+
+	fst_group_attach_iface(group, iface);
+	fst_group_update_ie(group);
+
+	foreach_fst_ctrl_call(on_iface_added, iface);
+
+	fst_printf_iface(iface, MSG_DEBUG,
+			 "iface attached to group %s (prio=%d, llt=%d)",
+			 cfg->group_id, cfg->priority, cfg->llt);
+
+	return iface;
+}
+
+
+void fst_detach(struct fst_iface *iface)
+{
+	struct fst_group *group = fst_iface_get_group(iface);
+
+	fst_printf_iface(iface, MSG_DEBUG, "iface detached from group %s",
+			 fst_group_get_id(group));
+	fst_session_global_on_iface_detached(iface);
+	foreach_fst_ctrl_call(on_iface_removed, iface);
+	fst_group_detach_iface(group, iface);
+	fst_iface_delete(iface);
+	fst_group_update_ie(group);
+	fst_group_delete_if_empty(group);
+}
+
+
+int fst_global_init(void)
+{
+	dl_list_init(&fst_global_groups_list);
+	dl_list_init(&fst_global_ctrls_list);
+	fst_session_global_init();
+	fst_global_initialized = 1;
+	return 0;
+}
+
+
+void fst_global_deinit(void)
+{
+	struct fst_group *group;
+	struct fst_ctrl_handle *h;
+
+	if (!fst_global_initialized)
+		return;
+
+	fst_session_global_deinit();
+	while ((group = fst_first_group()) != NULL)
+		fst_group_delete(group);
+	while ((h = dl_list_first(&fst_global_ctrls_list,
+				  struct fst_ctrl_handle,
+				  global_ctrls_lentry)))
+		fst_global_del_ctrl(h);
+	fst_global_initialized = 0;
+}
+
+
+struct fst_ctrl_handle * fst_global_add_ctrl(const struct fst_ctrl *ctrl)
+{
+	struct fst_ctrl_handle *h;
+
+	if (!ctrl)
+		return NULL;
+
+	h = os_zalloc(sizeof(*h));
+	if (!h)
+		return NULL;
+
+	if (ctrl->init && ctrl->init()) {
+		os_free(h);
+		return NULL;
+	}
+
+	h->ctrl = *ctrl;
+	dl_list_add_tail(&fst_global_ctrls_list, &h->global_ctrls_lentry);
+
+	return h;
+}
+
+
+void fst_global_del_ctrl(struct fst_ctrl_handle *h)
+{
+	dl_list_del(&h->global_ctrls_lentry);
+	if (h->ctrl.deinit)
+		h->ctrl.deinit();
+	os_free(h);
+}
+
+
+void fst_rx_action(struct fst_iface *iface, const struct ieee80211_mgmt *mgmt,
+		   size_t len)
+{
+	if (fst_iface_is_connected(iface, mgmt->sa, false))
+		fst_session_on_action_rx(iface, mgmt, len);
+	else
+		wpa_printf(MSG_DEBUG,
+			   "FST: Ignore FST Action frame - no FST connection with "
+			   MACSTR, MAC2STR(mgmt->sa));
+}
+
+
+void fst_notify_peer_connected(struct fst_iface *iface, const u8 *addr)
+{
+	if (is_zero_ether_addr(addr))
+		return;
+
+#ifndef HOSTAPD
+	fst_group_update_ie(fst_iface_get_group(iface));
+#endif /* HOSTAPD */
+
+	fst_printf_iface(iface, MSG_DEBUG, MACSTR " became connected",
+			 MAC2STR(addr));
+
+	fst_ctrl_iface_notify_peer_state_change(iface, true, addr);
+}
+
+
+void fst_notify_peer_disconnected(struct fst_iface *iface, const u8 *addr)
+{
+	if (is_zero_ether_addr(addr))
+		return;
+
+#ifndef HOSTAPD
+	fst_group_update_ie(fst_iface_get_group(iface));
+#endif /* HOSTAPD */
+
+	fst_printf_iface(iface, MSG_DEBUG, MACSTR " became disconnected",
+			 MAC2STR(addr));
+
+	fst_ctrl_iface_notify_peer_state_change(iface, false, addr);
+}
+
+
+bool fst_are_ifaces_aggregated(struct fst_iface *iface1,
+			       struct fst_iface *iface2)
+{
+	return fst_iface_get_group(iface1) == fst_iface_get_group(iface2);
+}
+
+
+void fst_update_mac_addr(struct fst_iface *iface, const u8 *addr)
+{
+	fst_printf_iface(iface, MSG_DEBUG, "new MAC address " MACSTR,
+			 MAC2STR(addr));
+	os_memcpy(iface->own_addr, addr, sizeof(iface->own_addr));
+	fst_group_update_ie(fst_iface_get_group(iface));
+}
+
+
+enum mb_band_id fst_hw_mode_to_band(enum hostapd_hw_mode mode)
+{
+	switch (mode) {
+	case HOSTAPD_MODE_IEEE80211B:
+	case HOSTAPD_MODE_IEEE80211G:
+		return MB_BAND_ID_WIFI_2_4GHZ;
+	case HOSTAPD_MODE_IEEE80211A:
+		return MB_BAND_ID_WIFI_5GHZ;
+	case HOSTAPD_MODE_IEEE80211AD:
+		return MB_BAND_ID_WIFI_60GHZ;
+	default:
+		WPA_ASSERT(0);
+		return MB_BAND_ID_WIFI_2_4GHZ;
+	}
+}
diff --git a/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst.h b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst.h
new file mode 100644
index 0000000..2410a6e
--- /dev/null
+++ b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst.h
@@ -0,0 +1,311 @@
+/*
+ * FST module - interface definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef FST_H
+#define FST_H
+
+#ifdef CONFIG_FST
+
+#include "common/defs.h"
+#include "fst/fst_ctrl_iface.h"
+
+/* FST module hostap integration API */
+
+#define US_IN_MS           1000
+#define LLT_UNIT_US        32 /* See 10.32.2.2  Transitioning between states */
+
+/*
+ * These were originally
+ * #define FST_LLT_MS_TO_VAL(m) (((u32) (m)) * US_IN_MS / LLT_UNIT_US)
+ * #define FST_LLT_VAL_TO_MS(v) (((u32) (v)) * LLT_UNIT_US / US_IN_MS)
+ * #define FST_MAX_LLT_MS FST_LLT_VAL_TO_MS(-1)
+ * but those can overflow 32-bit unsigned integer, so use alternative defines
+ * to avoid undefined behavior with such overflow.
+ * LLT_UNIT_US/US_IN_MS = 32/1000 = 4/125
+ */
+#define FST_LLT_MS_TO_VAL(m) (((u32) (m)) * 125 / 4)
+#define FST_LLT_VAL_TO_MS(v) (((u32) (v)) * 4 / 125)
+#define FST_MAX_LLT_MS       (((u32) -1) / 4)
+#define FST_MAX_PRIO_VALUE   ((u8) -1)
+#define FST_MAX_GROUP_ID_LEN IFNAMSIZ
+
+#define FST_DEFAULT_LLT_CFG_VALUE 50
+
+struct hostapd_hw_modes;
+struct ieee80211_mgmt;
+struct fst_iface;
+struct fst_group;
+struct fst_session;
+struct fst_get_peer_ctx;
+struct fst_ctrl_handle;
+
+struct fst_wpa_obj {
+	void *ctx;
+
+	/**
+	 * get_bssid - Get BSSID of the interface
+	 * @ctx: User context %ctx
+	 * Returns: BSSID for success, %NULL for failure.
+	 *
+	 * NOTE: For AP it returns the own BSSID, while for STA - the BSSID of
+	 * the associated AP.
+	 */
+	const u8 * (*get_bssid)(void *ctx);
+
+	/**
+	 * get_channel_info - Get current channel info
+	 * @ctx: User context %ctx
+	 * @hw_mode: OUT, current HW mode
+	 * @channel: OUT, current channel
+	 */
+	void (*get_channel_info)(void *ctx, enum hostapd_hw_mode *hw_mode,
+				 u8 *channel);
+
+	/**
+	 * get_hw_modes - Get hardware modes
+	 * @ctx: User context %ctx
+	 * @modes: OUT, pointer on array of hw modes
+	 *
+	 * Returns: Number of hw modes available.
+	 */
+	int (*get_hw_modes)(void *ctx, struct hostapd_hw_modes **modes);
+
+	/**
+	 * set_ies - Set interface's MB IE
+	 * @ctx: User context %ctx
+	 * @fst_ies: MB IE buffer (owned by FST module)
+	 */
+	void (*set_ies)(void *ctx, const struct wpabuf *fst_ies);
+
+	/**
+	 * send_action - Send FST Action frame via the interface
+	 * @ctx: User context %ctx
+	 * @addr: Address of the destination STA
+	 * @data: Action frame buffer
+	 * Returns: 0 for success, negative error code for failure.
+	 */
+	int (*send_action)(void *ctx, const u8 *addr, struct wpabuf *data);
+
+	/**
+	 * get_mb_ie - Get last MB IE received from STA
+	 * @ctx: User context %ctx
+	 * @addr: Address of the STA
+	 * Returns: MB IE buffer, %NULL if no MB IE received from the STA
+	 */
+	const struct wpabuf * (*get_mb_ie)(void *ctx, const u8 *addr);
+
+	/**
+	 * update_mb_ie - Update last MB IE received from STA
+	 * @ctx: User context %ctx
+	 * @addr: Address of the STA
+	 * @buf: Buffer that contains the MB IEs data
+	 * @size: Size of data in %buf
+	 */
+	void (*update_mb_ie)(void *ctx, const u8 *addr,
+			     const u8 *buf, size_t size);
+
+	/**
+	 * get_peer_first - Get MAC address of the 1st connected STA
+	 * @ctx: User context %ctx
+	 * @get_ctx: Context to be used for %get_peer_next call
+	 * @mb_only: %true if only multi-band capable peer should be reported
+	 * Returns: Address of the 1st connected STA, %NULL if no STAs connected
+	 */
+	const u8 * (*get_peer_first)(void *ctx,
+				     struct fst_get_peer_ctx **get_ctx,
+				     bool mb_only);
+	/**
+	 * get_peer_next - Get MAC address of the next connected STA
+	 * @ctx: User context %ctx
+	 * @get_ctx: Context received from %get_peer_first or previous
+	 *           %get_peer_next call
+	 * @mb_only: %true if only multi-band capable peer should be reported
+	 * Returns: Address of the next connected STA, %NULL if no more STAs
+	 *          connected
+	 */
+	const u8 * (*get_peer_next)(void *ctx,
+				    struct fst_get_peer_ctx **get_ctx,
+				    bool mb_only);
+};
+
+/**
+ * fst_global_init - Global FST module initiator
+ * Returns: 0 for success, negative error code for failure.
+ * Note: The purpose of this function is to allocate and initiate global
+ *       FST module data structures (linked lists, static data etc.)
+ *       This function should be called prior to the 1st %fst_attach call.
+ */
+int fst_global_init(void);
+
+/**
+ * fst_global_deinit - Global FST module de-initiator
+ * Note: The purpose of this function is to deallocate and de-initiate global
+ *       FST module data structures (linked lists, static data etc.)
+ */
+void fst_global_deinit(void);
+
+/**
+ * struct fst_ctrl - Notification interface for FST module
+ */
+struct fst_ctrl {
+	/**
+	 * init - Initialize the notification interface
+	 * Returns: 0 for success, negative error code for failure.
+	 */
+	int (*init)(void);
+
+	/**
+	 * deinit - Deinitialize the notification interface
+	 */
+	void (*deinit)(void);
+
+	/**
+	 * on_group_created - Notify about FST group creation
+	 * Returns: 0 for success, negative error code for failure.
+	 */
+	int (*on_group_created)(struct fst_group *g);
+
+	/**
+	 * on_group_deleted - Notify about FST group deletion
+	 */
+	void (*on_group_deleted)(struct fst_group *g);
+
+	/**
+	 * on_iface_added - Notify about interface addition
+	 * Returns: 0 for success, negative error code for failure.
+	 */
+	int (*on_iface_added)(struct fst_iface *i);
+
+	/**
+	 * on_iface_removed - Notify about interface removal
+	 */
+	void (*on_iface_removed)(struct fst_iface *i);
+
+	/**
+	 * on_session_added - Notify about FST session addition
+	 * Returns: 0 for success, negative error code for failure.
+	 */
+	int (*on_session_added)(struct fst_session *s);
+
+	/**
+	 * on_session_removed - Notify about FST session removal
+	 */
+	void (*on_session_removed)(struct fst_session *s);
+
+	/**
+	 * on_event - Notify about FST event
+	 * @event_type: Event type
+	 * @i: Interface object that relates to the event or NULL
+	 * @g: Group object that relates to the event or NULL
+	 * @extra - Event specific data (see fst_ctrl_iface.h for more info)
+	 */
+	void (*on_event)(enum fst_event_type event_type, struct fst_iface *i,
+			 struct fst_session *s,
+			 const union fst_event_extra *extra);
+};
+
+struct fst_ctrl_handle * fst_global_add_ctrl(const struct fst_ctrl *ctrl);
+void fst_global_del_ctrl(struct fst_ctrl_handle *h);
+
+/**
+ * NOTE: These values have to be read from configuration file
+ */
+struct fst_iface_cfg {
+	char group_id[FST_MAX_GROUP_ID_LEN + 1];
+	u8 priority;
+	u32 llt;
+};
+
+/**
+ * fst_attach - Attach interface to an FST group according to configuration read
+ * @ifname: Interface name
+ * @own_addr: Own interface MAC address
+ * @iface_obj: Callbacks to be used by FST module to communicate with
+ *             hostapd/wpa_supplicant
+ * @cfg: FST-related interface configuration read from the configuration file
+ * Returns: FST interface object for success, %NULL for failure.
+ */
+struct fst_iface * fst_attach(const char *ifname,
+			      const u8 *own_addr,
+			      const struct fst_wpa_obj *iface_obj,
+			      const struct fst_iface_cfg *cfg);
+
+/**
+ * fst_detach - Detach an interface
+ * @iface: FST interface object
+ */
+void fst_detach(struct fst_iface *iface);
+
+/* FST module inputs */
+/**
+ * fst_rx_action - FST Action frames handler
+ * @iface: FST interface object
+ * @mgmt: Action frame arrived
+ * @len: Action frame length
+ */
+void fst_rx_action(struct fst_iface *iface, const struct ieee80211_mgmt *mgmt,
+		   size_t len);
+
+/**
+ * fst_notify_peer_connected - FST STA connect handler
+ * @iface: FST interface object
+ * @addr: Address of the connected STA
+ */
+void fst_notify_peer_connected(struct fst_iface *iface, const u8 *addr);
+
+/**
+ * fst_notify_peer_disconnected - FST STA disconnect handler
+ * @iface: FST interface object
+ * @addr: Address of the disconnected STA
+ */
+void fst_notify_peer_disconnected(struct fst_iface *iface, const u8 *addr);
+
+/* FST module auxiliary routines */
+
+/**
+ * fst_are_ifaces_aggregated - Determines whether 2 interfaces belong to the
+ *                             same FST group
+ * @iface1: 1st FST interface object
+ * @iface1: 2nd FST interface object
+ *
+ * Returns: %true if the interfaces belong to the same FST group,
+ *          %false otherwise
+ */
+bool fst_are_ifaces_aggregated(struct fst_iface *iface1,
+			       struct fst_iface *iface2);
+
+/**
+ * fst_update_mac_addr - Notify FST about MAC address change
+ * @iface: FST interface object
+ * @addr: New MAC address
+ */
+void fst_update_mac_addr(struct fst_iface *iface, const u8 *addr);
+
+#else /* CONFIG_FST */
+
+static inline int fst_global_init(void)
+{
+	return 0;
+}
+
+static inline int fst_global_start(void)
+{
+	return 0;
+}
+
+static inline void fst_global_stop(void)
+{
+}
+
+static inline void fst_global_deinit(void)
+{
+}
+
+#endif /* CONFIG_FST */
+
+#endif /* FST_H */
diff --git a/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_ctrl_aux.c b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_ctrl_aux.c
new file mode 100644
index 0000000..b632827
--- /dev/null
+++ b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_ctrl_aux.c
@@ -0,0 +1,70 @@
+/*
+ * FST module implementation
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+#include "utils/common.h"
+#include "common/defs.h"
+#include "fst_ctrl_defs.h"
+#include "fst_ctrl_aux.h"
+
+
+static const char *session_event_names[] = {
+	[EVENT_FST_ESTABLISHED] = FST_PVAL_EVT_TYPE_ESTABLISHED,
+	[EVENT_FST_SETUP] = FST_PVAL_EVT_TYPE_SETUP,
+	[EVENT_FST_SESSION_STATE_CHANGED] = FST_PVAL_EVT_TYPE_SESSION_STATE,
+};
+
+static const char *reason_names[] = {
+	[REASON_TEARDOWN] = FST_CS_PVAL_REASON_TEARDOWN,
+	[REASON_SETUP] = FST_CS_PVAL_REASON_SETUP,
+	[REASON_SWITCH] = FST_CS_PVAL_REASON_SWITCH,
+	[REASON_STT] = FST_CS_PVAL_REASON_STT,
+	[REASON_REJECT] = FST_CS_PVAL_REASON_REJECT,
+	[REASON_ERROR_PARAMS] = FST_CS_PVAL_REASON_ERROR_PARAMS,
+	[REASON_RESET] = FST_CS_PVAL_REASON_RESET,
+	[REASON_DETACH_IFACE] = FST_CS_PVAL_REASON_DETACH_IFACE,
+};
+
+static const char *session_state_names[] = {
+	[FST_SESSION_STATE_INITIAL] = FST_CS_PVAL_STATE_INITIAL,
+	[FST_SESSION_STATE_SETUP_COMPLETION] =
+	FST_CS_PVAL_STATE_SETUP_COMPLETION,
+	[FST_SESSION_STATE_TRANSITION_DONE] = FST_CS_PVAL_STATE_TRANSITION_DONE,
+	[FST_SESSION_STATE_TRANSITION_CONFIRMED] =
+	FST_CS_PVAL_STATE_TRANSITION_CONFIRMED,
+};
+
+
+/* helpers */
+const char * fst_get_str_name(unsigned index, const char *names[],
+			      size_t names_size)
+{
+	if (index >= names_size || !names[index])
+		return FST_NAME_UNKNOWN;
+	return names[index];
+}
+
+
+const char * fst_session_event_type_name(enum fst_event_type event)
+{
+	return fst_get_str_name(event, session_event_names,
+				ARRAY_SIZE(session_event_names));
+}
+
+
+const char * fst_reason_name(enum fst_reason reason)
+{
+	return fst_get_str_name(reason, reason_names, ARRAY_SIZE(reason_names));
+}
+
+
+const char * fst_session_state_name(enum fst_session_state state)
+{
+	return fst_get_str_name(state, session_state_names,
+				ARRAY_SIZE(session_state_names));
+}
diff --git a/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_ctrl_aux.h b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_ctrl_aux.h
new file mode 100644
index 0000000..ab80b6f
--- /dev/null
+++ b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_ctrl_aux.h
@@ -0,0 +1,93 @@
+/*
+ * FST module - miscellaneous definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef FST_CTRL_AUX_H
+#define FST_CTRL_AUX_H
+
+#include "common/defs.h"
+
+/* FST module control interface API */
+#define FST_INVALID_SESSION_ID ((u32) -1)
+#define FST_MAX_GROUP_ID_SIZE   32
+#define FST_MAX_INTERFACE_SIZE  32
+
+enum fst_session_state {
+	FST_SESSION_STATE_INITIAL,
+	FST_SESSION_STATE_SETUP_COMPLETION,
+	FST_SESSION_STATE_TRANSITION_DONE,
+	FST_SESSION_STATE_TRANSITION_CONFIRMED,
+	FST_SESSION_STATE_LAST
+};
+
+enum fst_event_type {
+	EVENT_FST_IFACE_STATE_CHANGED,  /* An interface has been either attached
+					 * to or detached from an FST group */
+	EVENT_FST_ESTABLISHED,          /* FST Session has been established */
+	EVENT_FST_SETUP,                /* FST Session request received */
+	EVENT_FST_SESSION_STATE_CHANGED,/* FST Session state has been changed */
+	EVENT_PEER_STATE_CHANGED        /* FST related generic event occurred,
+					 * see struct fst_hostap_event_data for
+					 *  more info */
+};
+
+enum fst_reason {
+	REASON_TEARDOWN,
+	REASON_SETUP,
+	REASON_SWITCH,
+	REASON_STT,
+	REASON_REJECT,
+	REASON_ERROR_PARAMS,
+	REASON_RESET,
+	REASON_DETACH_IFACE,
+};
+
+enum fst_initiator {
+	FST_INITIATOR_UNDEFINED,
+	FST_INITIATOR_LOCAL,
+	FST_INITIATOR_REMOTE,
+};
+
+union fst_event_extra {
+	struct fst_event_extra_iface_state {
+		bool attached;
+		char ifname[FST_MAX_INTERFACE_SIZE];
+		char group_id[FST_MAX_GROUP_ID_SIZE];
+	} iface_state; /* for EVENT_FST_IFACE_STATE_CHANGED */
+	struct fst_event_extra_peer_state {
+		bool connected;
+		char ifname[FST_MAX_INTERFACE_SIZE];
+		u8 addr[ETH_ALEN];
+	} peer_state; /* for EVENT_PEER_STATE_CHANGED */
+	struct fst_event_extra_session_state {
+		enum fst_session_state old_state;
+		enum fst_session_state new_state;
+		union fst_session_state_switch_extra {
+			struct {
+				enum fst_reason reason;
+				u8 reject_code; /* REASON_REJECT */
+				/* REASON_SWITCH,
+				 * REASON_TEARDOWN,
+				 * REASON_REJECT
+				 */
+				enum fst_initiator initiator;
+			} to_initial;
+		} extra;
+	} session_state; /* for EVENT_FST_SESSION_STATE_CHANGED */
+};
+
+/* helpers - prints enum in string form */
+#define FST_NAME_UNKNOWN "UNKNOWN"
+
+const char * fst_get_str_name(unsigned index, const char *names[],
+			      size_t names_size);
+
+const char * fst_session_event_type_name(enum fst_event_type);
+const char * fst_reason_name(enum fst_reason reason);
+const char * fst_session_state_name(enum fst_session_state state);
+
+#endif /* FST_CTRL_AUX_H */
diff --git a/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_ctrl_defs.h b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_ctrl_defs.h
new file mode 100644
index 0000000..6735389
--- /dev/null
+++ b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_ctrl_defs.h
@@ -0,0 +1,109 @@
+/*
+ * FST module - shared Control interface definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef FST_CTRL_DEFS_H
+#define FST_CTRL_DEFS_H
+
+/* Undefined value */
+#define FST_CTRL_PVAL_NONE                     "NONE"
+
+/* FST-ATTACH parameters */
+#define FST_ATTACH_CMD_PNAME_LLT      "llt"      /* pval = desired LLT */
+#define FST_ATTACH_CMD_PNAME_PRIORITY "priority" /* pval = desired priority */
+
+/* FST-MANAGER parameters */
+/* FST Session states */
+#define FST_CS_PVAL_STATE_INITIAL              "INITIAL"
+#define FST_CS_PVAL_STATE_SETUP_COMPLETION     "SETUP_COMPLETION"
+#define FST_CS_PVAL_STATE_TRANSITION_DONE      "TRANSITION_DONE"
+#define FST_CS_PVAL_STATE_TRANSITION_CONFIRMED "TRANSITION_CONFIRMED"
+
+/* FST Session reset reasons */
+#define FST_CS_PVAL_REASON_TEARDOWN     "REASON_TEARDOWN"
+#define FST_CS_PVAL_REASON_SETUP        "REASON_SETUP"
+#define FST_CS_PVAL_REASON_SWITCH       "REASON_SWITCH"
+#define FST_CS_PVAL_REASON_STT          "REASON_STT"
+#define FST_CS_PVAL_REASON_REJECT       "REASON_REJECT"
+#define FST_CS_PVAL_REASON_ERROR_PARAMS "REASON_ERROR_PARAMS"
+#define FST_CS_PVAL_REASON_RESET        "REASON_RESET"
+#define FST_CS_PVAL_REASON_DETACH_IFACE "REASON_DETACH_IFACE"
+
+/* FST Session responses */
+#define FST_CS_PVAL_RESPONSE_ACCEPT "ACCEPT"
+#define FST_CS_PVAL_RESPONSE_REJECT "REJECT"
+
+/* FST Session action initiator */
+#define FST_CS_PVAL_INITIATOR_LOCAL  "LOCAL"
+#define FST_CS_PVAL_INITIATOR_REMOTE "REMOTE"
+
+/* FST-CLI subcommands and parameter names */
+#define FST_CMD_LIST_GROUPS      "list_groups"
+#define FST_CMD_LIST_IFACES      "list_ifaces"
+#define FST_CMD_IFACE_PEERS      "iface_peers"
+#define FST_CMD_GET_PEER_MBIES   "get_peer_mbies"
+#define FST_CMD_LIST_SESSIONS    "list_sessions"
+#define FST_CMD_SESSION_ADD      "session_add"
+#define FST_CMD_SESSION_REMOVE   "session_remove"
+#define FST_CMD_SESSION_GET      "session_get"
+#define FST_CSG_PNAME_OLD_PEER_ADDR  "old_peer_addr" /* pval = address string */
+#define FST_CSG_PNAME_NEW_PEER_ADDR  "new_peer_addr" /* pval = address string */
+#define FST_CSG_PNAME_OLD_IFNAME "old_ifname" /* pval = ifname */
+#define FST_CSG_PNAME_NEW_IFNAME "new_ifname" /* pval = ifname */
+#define FST_CSG_PNAME_LLT        "llt"        /* pval = numeric llt value */
+#define FST_CSG_PNAME_STATE      "state"      /* pval = FST_CS_PVAL_STATE_... */
+#define FST_CMD_SESSION_SET      "session_set"
+#define FST_CSS_PNAME_OLD_PEER_ADDR  FST_CSG_PNAME_OLD_PEER_ADDR
+#define FST_CSS_PNAME_NEW_PEER_ADDR  FST_CSG_PNAME_NEW_PEER_ADDR
+#define FST_CSS_PNAME_OLD_IFNAME     FST_CSG_PNAME_OLD_IFNAME
+#define FST_CSS_PNAME_NEW_IFNAME     FST_CSG_PNAME_NEW_IFNAME
+#define FST_CSS_PNAME_LLT            FST_CSG_PNAME_LLT
+#define FST_CMD_SESSION_INITIATE "session_initiate"
+#define FST_CMD_SESSION_RESPOND  "session_respond"
+#define FST_CMD_SESSION_TRANSFER "session_transfer"
+#define FST_CMD_SESSION_TEARDOWN "session_teardown"
+
+#ifdef CONFIG_FST_TEST
+#define FST_CTR_PVAL_BAD_NEW_BAND        "bad_new_band"
+
+#define FST_CMD_TEST_REQUEST    "test_request"
+#define FST_CTR_IS_SUPPORTED        "is_supported"
+#define FST_CTR_SEND_SETUP_REQUEST  "send_setup_request"
+#define FST_CTR_SEND_SETUP_RESPONSE "send_setup_response"
+#define FST_CTR_SEND_ACK_REQUEST    "send_ack_request"
+#define FST_CTR_SEND_ACK_RESPONSE   "send_ack_response"
+#define FST_CTR_SEND_TEAR_DOWN      "send_tear_down"
+#define FST_CTR_GET_FSTS_ID         "get_fsts_id"
+#define FST_CTR_GET_LOCAL_MBIES     "get_local_mbies"
+#endif /* CONFIG_FST_TEST */
+
+/* Events */
+#define FST_CTRL_EVENT_IFACE "FST-EVENT-IFACE"
+#define FST_CEI_PNAME_IFNAME       "ifname"
+#define FST_CEI_PNAME_GROUP        "group"
+#define FST_CEI_PNAME_ATTACHED     "attached"
+#define FST_CEI_PNAME_DETACHED     "detached"
+#define FST_CTRL_EVENT_PEER "FST-EVENT-PEER"
+#define FST_CEP_PNAME_IFNAME       "ifname"
+#define FST_CEP_PNAME_ADDR         "peer_addr"
+#define FST_CEP_PNAME_CONNECTED    "connected"
+#define FST_CEP_PNAME_DISCONNECTED "disconnected"
+#define FST_CTRL_EVENT_SESSION "FST-EVENT-SESSION"
+#define FST_CES_PNAME_SESSION_ID "session_id"
+#define FST_CES_PNAME_EVT_TYPE   "event_type"
+#define FST_PVAL_EVT_TYPE_SESSION_STATE "EVENT_FST_SESSION_STATE"
+/* old_state/new_state: pval = FST_CS_PVAL_STATE_... */
+#define FST_CES_PNAME_OLD_STATE   "old_state"
+#define FST_CES_PNAME_NEW_STATE   "new_state"
+#define FST_CES_PNAME_REASON      "reason" /* pval = FST_CS_PVAL_REASON_... */
+#define FST_CES_PNAME_REJECT_CODE "reject_code" /* pval = u8 code */
+/* pval = FST_CS_PVAL_INITIATOR_... */
+#define FST_CES_PNAME_INITIATOR   "initiator"
+#define FST_PVAL_EVT_TYPE_ESTABLISHED "EVENT_FST_ESTABLISHED"
+#define FST_PVAL_EVT_TYPE_SETUP "EVENT_FST_SETUP"
+
+#endif /* FST_CTRL_DEFS_H */
diff --git a/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_ctrl_iface.c b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_ctrl_iface.c
new file mode 100644
index 0000000..45607b6
--- /dev/null
+++ b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_ctrl_iface.c
@@ -0,0 +1,948 @@
+/*
+ * FST module - Control Interface implementation
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+#include "utils/common.h"
+#include "common/defs.h"
+#include "list.h"
+#include "fst/fst.h"
+#include "fst/fst_internal.h"
+#include "fst_ctrl_defs.h"
+#include "fst_ctrl_iface.h"
+
+
+static struct fst_group * get_fst_group_by_id(const char *id)
+{
+	struct fst_group *g;
+
+	foreach_fst_group(g) {
+		const char *group_id = fst_group_get_id(g);
+
+		if (os_strncmp(group_id, id, os_strlen(group_id)) == 0)
+			return g;
+	}
+
+	return NULL;
+}
+
+
+/* notifications */
+static bool format_session_state_extra(const union fst_event_extra *extra,
+				       char *buffer, size_t size)
+{
+	int len;
+	char reject_str[32] = FST_CTRL_PVAL_NONE;
+	const char *initiator = FST_CTRL_PVAL_NONE;
+	const struct fst_event_extra_session_state *ss;
+
+	ss = &extra->session_state;
+	if (ss->new_state != FST_SESSION_STATE_INITIAL)
+		return true;
+
+	switch (ss->extra.to_initial.reason) {
+	case REASON_REJECT:
+		if (ss->extra.to_initial.reject_code != WLAN_STATUS_SUCCESS)
+			os_snprintf(reject_str, sizeof(reject_str), "%u",
+				    ss->extra.to_initial.reject_code);
+		/* fall through */
+	case REASON_TEARDOWN:
+	case REASON_SWITCH:
+		switch (ss->extra.to_initial.initiator) {
+		case FST_INITIATOR_LOCAL:
+			initiator = FST_CS_PVAL_INITIATOR_LOCAL;
+			break;
+		case FST_INITIATOR_REMOTE:
+			initiator = FST_CS_PVAL_INITIATOR_REMOTE;
+			break;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	len = os_snprintf(buffer, size,
+			  FST_CES_PNAME_REASON "=%s "
+			  FST_CES_PNAME_REJECT_CODE "=%s "
+			  FST_CES_PNAME_INITIATOR "=%s",
+			  fst_reason_name(ss->extra.to_initial.reason),
+			  reject_str, initiator);
+
+	return !os_snprintf_error(size, len);
+}
+
+
+static void fst_ctrl_iface_notify(struct fst_iface *f, u32 session_id,
+				  enum fst_event_type event_type,
+				  const union fst_event_extra *extra)
+{
+	struct fst_group *g;
+	char extra_str[128] = "";
+	const struct fst_event_extra_session_state *ss;
+	const struct fst_event_extra_iface_state *is;
+	const struct fst_event_extra_peer_state *ps;
+
+	/*
+	 * FST can use any of interface objects as it only sends messages
+	 * on global Control Interface, so we just pick the 1st one.
+	 */
+
+	if (!f) {
+		foreach_fst_group(g) {
+			f = fst_group_first_iface(g);
+			if (f)
+				break;
+		}
+		if (!f)
+			return;
+	}
+
+	WPA_ASSERT(f->iface_obj.ctx);
+
+	switch (event_type) {
+	case EVENT_FST_IFACE_STATE_CHANGED:
+		if (!extra)
+			return;
+		is = &extra->iface_state;
+		wpa_msg_global_only(f->iface_obj.ctx, MSG_INFO,
+				    FST_CTRL_EVENT_IFACE " %s "
+				    FST_CEI_PNAME_IFNAME "=%s "
+				    FST_CEI_PNAME_GROUP "=%s",
+				    is->attached ? FST_CEI_PNAME_ATTACHED :
+				    FST_CEI_PNAME_DETACHED,
+				    is->ifname, is->group_id);
+		break;
+	case EVENT_PEER_STATE_CHANGED:
+		if (!extra)
+			return;
+		ps = &extra->peer_state;
+		wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO,
+				    FST_CTRL_EVENT_PEER " %s "
+				    FST_CEP_PNAME_IFNAME "=%s "
+				    FST_CEP_PNAME_ADDR "=" MACSTR,
+				    ps->connected ? FST_CEP_PNAME_CONNECTED :
+				    FST_CEP_PNAME_DISCONNECTED,
+				    ps->ifname, MAC2STR(ps->addr));
+		break;
+	case EVENT_FST_SESSION_STATE_CHANGED:
+		if (!extra)
+			return;
+		if (!format_session_state_extra(extra, extra_str,
+						sizeof(extra_str))) {
+			fst_printf(MSG_ERROR,
+				   "CTRL: Cannot format STATE_CHANGE extra");
+			extra_str[0] = 0;
+		}
+		ss = &extra->session_state;
+		wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO,
+				    FST_CTRL_EVENT_SESSION " "
+				    FST_CES_PNAME_SESSION_ID "=%u "
+				    FST_CES_PNAME_EVT_TYPE "=%s "
+				    FST_CES_PNAME_OLD_STATE "=%s "
+				    FST_CES_PNAME_NEW_STATE "=%s %s",
+				    session_id,
+				    fst_session_event_type_name(event_type),
+				    fst_session_state_name(ss->old_state),
+				    fst_session_state_name(ss->new_state),
+				    extra_str);
+		break;
+	case EVENT_FST_ESTABLISHED:
+	case EVENT_FST_SETUP:
+		wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO,
+				    FST_CTRL_EVENT_SESSION " "
+				    FST_CES_PNAME_SESSION_ID "=%u "
+				    FST_CES_PNAME_EVT_TYPE "=%s",
+				    session_id,
+				    fst_session_event_type_name(event_type));
+		break;
+	}
+}
+
+
+/* command processors */
+
+/* fst session_get */
+static int session_get(const char *session_id, char *buf, size_t buflen)
+{
+	struct fst_session *s;
+	struct fst_iface *new_iface, *old_iface;
+	const u8 *old_peer_addr, *new_peer_addr;
+	u32 id;
+
+	id = strtoul(session_id, NULL, 0);
+
+	s = fst_session_get_by_id(id);
+	if (!s) {
+		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	old_peer_addr = fst_session_get_peer_addr(s, true);
+	new_peer_addr = fst_session_get_peer_addr(s, false);
+	new_iface = fst_session_get_iface(s, false);
+	old_iface = fst_session_get_iface(s, true);
+
+	return os_snprintf(buf, buflen,
+			   FST_CSG_PNAME_OLD_PEER_ADDR "=" MACSTR "\n"
+			   FST_CSG_PNAME_NEW_PEER_ADDR "=" MACSTR "\n"
+			   FST_CSG_PNAME_NEW_IFNAME "=%s\n"
+			   FST_CSG_PNAME_OLD_IFNAME "=%s\n"
+			   FST_CSG_PNAME_LLT "=%u\n"
+			   FST_CSG_PNAME_STATE "=%s\n",
+			   MAC2STR(old_peer_addr),
+			   MAC2STR(new_peer_addr),
+			   new_iface ? fst_iface_get_name(new_iface) :
+			   FST_CTRL_PVAL_NONE,
+			   old_iface ? fst_iface_get_name(old_iface) :
+			   FST_CTRL_PVAL_NONE,
+			   fst_session_get_llt(s),
+			   fst_session_state_name(fst_session_get_state(s)));
+}
+
+
+/* fst session_set */
+static int session_set(const char *session_id, char *buf, size_t buflen)
+{
+	struct fst_session *s;
+	char *p, *q;
+	u32 id;
+	int ret;
+
+	id = strtoul(session_id, &p, 0);
+
+	s = fst_session_get_by_id(id);
+	if (!s) {
+		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	if (*p != ' ' || !(q = os_strchr(p + 1, '=')))
+		return os_snprintf(buf, buflen, "FAIL\n");
+	p++;
+
+	if (os_strncasecmp(p, FST_CSS_PNAME_OLD_IFNAME, q - p) == 0) {
+		ret = fst_session_set_str_ifname(s, q + 1, true);
+	} else if (os_strncasecmp(p, FST_CSS_PNAME_NEW_IFNAME, q - p) == 0) {
+		ret = fst_session_set_str_ifname(s, q + 1, false);
+	} else if (os_strncasecmp(p, FST_CSS_PNAME_OLD_PEER_ADDR, q - p) == 0) {
+		ret = fst_session_set_str_peer_addr(s, q + 1, true);
+	} else if (os_strncasecmp(p, FST_CSS_PNAME_NEW_PEER_ADDR, q - p) == 0) {
+		ret = fst_session_set_str_peer_addr(s, q + 1, false);
+	} else if (os_strncasecmp(p, FST_CSS_PNAME_LLT, q - p) == 0) {
+		ret = fst_session_set_str_llt(s, q + 1);
+	} else {
+		fst_printf(MSG_ERROR, "CTRL: Unknown parameter: %s", p);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	return os_snprintf(buf, buflen, "%s\n", ret ? "FAIL" : "OK");
+}
+
+
+/* fst session_add/remove */
+static int session_add(const char *group_id, char *buf, size_t buflen)
+{
+	struct fst_group *g;
+	struct fst_session *s;
+
+	g = get_fst_group_by_id(group_id);
+	if (!g) {
+		fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
+			   group_id);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	s = fst_session_create(g);
+	if (!s) {
+		fst_printf(MSG_ERROR,
+			   "CTRL: Cannot create session for group '%s'",
+			   group_id);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	return os_snprintf(buf, buflen, "%u\n", fst_session_get_id(s));
+}
+
+
+static int session_remove(const char *session_id, char *buf, size_t buflen)
+{
+	struct fst_session *s;
+	struct fst_group *g;
+	u32 id;
+
+	id = strtoul(session_id, NULL, 0);
+
+	s = fst_session_get_by_id(id);
+	if (!s) {
+		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	g = fst_session_get_group(s);
+	fst_session_reset(s);
+	fst_session_delete(s);
+	fst_group_delete_if_empty(g);
+
+	return os_snprintf(buf, buflen, "OK\n");
+}
+
+
+/* fst session_initiate */
+static int session_initiate(const char *session_id, char *buf, size_t buflen)
+{
+	struct fst_session *s;
+	u32 id;
+
+	id = strtoul(session_id, NULL, 0);
+
+	s = fst_session_get_by_id(id);
+	if (!s) {
+		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	if (fst_session_initiate_setup(s)) {
+		fst_printf(MSG_WARNING, "CTRL: Cannot initiate session %u", id);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	return os_snprintf(buf, buflen, "OK\n");
+}
+
+
+/* fst session_respond */
+static int session_respond(const char *session_id, char *buf, size_t buflen)
+{
+	struct fst_session *s;
+	char *p;
+	u32 id;
+	u8 status_code;
+
+	id = strtoul(session_id, &p, 0);
+
+	s = fst_session_get_by_id(id);
+	if (!s) {
+		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	if (*p != ' ')
+		return os_snprintf(buf, buflen, "FAIL\n");
+	p++;
+
+	if (!os_strcasecmp(p, FST_CS_PVAL_RESPONSE_ACCEPT)) {
+		status_code = WLAN_STATUS_SUCCESS;
+	} else if (!os_strcasecmp(p, FST_CS_PVAL_RESPONSE_REJECT)) {
+		status_code = WLAN_STATUS_PENDING_ADMITTING_FST_SESSION;
+	} else {
+		fst_printf(MSG_WARNING,
+			   "CTRL: session %u: unknown response status: %s",
+			   id, p);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	if (fst_session_respond(s, status_code)) {
+		fst_printf(MSG_WARNING, "CTRL: Cannot respond to session %u",
+			   id);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	fst_printf(MSG_INFO, "CTRL: session %u responded", id);
+
+	return os_snprintf(buf, buflen, "OK\n");
+}
+
+
+/* fst session_transfer */
+static int session_transfer(const char *session_id, char *buf, size_t buflen)
+{
+	struct fst_session *s;
+	u32 id;
+
+	id = strtoul(session_id, NULL, 0);
+
+	s = fst_session_get_by_id(id);
+	if (!s) {
+		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	if (fst_session_initiate_switch(s)) {
+		fst_printf(MSG_WARNING,
+			   "CTRL: Cannot initiate ST for session %u", id);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	return os_snprintf(buf, buflen, "OK\n");
+}
+
+
+/* fst session_teardown */
+static int session_teardown(const char *session_id, char *buf, size_t buflen)
+{
+	struct fst_session *s;
+	u32 id;
+
+	id = strtoul(session_id, NULL, 0);
+
+	s = fst_session_get_by_id(id);
+	if (!s) {
+		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	if (fst_session_tear_down_setup(s)) {
+		fst_printf(MSG_WARNING, "CTRL: Cannot tear down session %u",
+			   id);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	return os_snprintf(buf, buflen, "OK\n");
+}
+
+
+#ifdef CONFIG_FST_TEST
+/* fst test_request */
+static int test_request(const char *request, char *buf, size_t buflen)
+{
+	const char *p = request;
+	int ret;
+
+	if (!os_strncasecmp(p, FST_CTR_SEND_SETUP_REQUEST,
+			    os_strlen(FST_CTR_SEND_SETUP_REQUEST))) {
+		ret = fst_test_req_send_fst_request(
+			p + os_strlen(FST_CTR_SEND_SETUP_REQUEST));
+	} else if (!os_strncasecmp(p, FST_CTR_SEND_SETUP_RESPONSE,
+				   os_strlen(FST_CTR_SEND_SETUP_RESPONSE))) {
+		ret = fst_test_req_send_fst_response(
+			p + os_strlen(FST_CTR_SEND_SETUP_RESPONSE));
+	} else if (!os_strncasecmp(p, FST_CTR_SEND_ACK_REQUEST,
+				   os_strlen(FST_CTR_SEND_ACK_REQUEST))) {
+		ret = fst_test_req_send_ack_request(
+			p + os_strlen(FST_CTR_SEND_ACK_REQUEST));
+	} else if (!os_strncasecmp(p, FST_CTR_SEND_ACK_RESPONSE,
+				   os_strlen(FST_CTR_SEND_ACK_RESPONSE))) {
+		ret = fst_test_req_send_ack_response(
+			p + os_strlen(FST_CTR_SEND_ACK_RESPONSE));
+	} else if (!os_strncasecmp(p, FST_CTR_SEND_TEAR_DOWN,
+				   os_strlen(FST_CTR_SEND_TEAR_DOWN))) {
+		ret = fst_test_req_send_tear_down(
+			p + os_strlen(FST_CTR_SEND_TEAR_DOWN));
+	} else if (!os_strncasecmp(p, FST_CTR_GET_FSTS_ID,
+				   os_strlen(FST_CTR_GET_FSTS_ID))) {
+		u32 fsts_id = fst_test_req_get_fsts_id(
+			p + os_strlen(FST_CTR_GET_FSTS_ID));
+		if (fsts_id != FST_FSTS_ID_NOT_FOUND)
+			return os_snprintf(buf, buflen, "%u\n", fsts_id);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	} else if (!os_strncasecmp(p, FST_CTR_GET_LOCAL_MBIES,
+				   os_strlen(FST_CTR_GET_LOCAL_MBIES))) {
+		return fst_test_req_get_local_mbies(
+			p + os_strlen(FST_CTR_GET_LOCAL_MBIES), buf, buflen);
+	} else if (!os_strncasecmp(p, FST_CTR_IS_SUPPORTED,
+				   os_strlen(FST_CTR_IS_SUPPORTED))) {
+		ret = 0;
+	} else {
+		fst_printf(MSG_ERROR, "CTRL: Unknown parameter: %s", p);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	return os_snprintf(buf, buflen, "%s\n", ret ? "FAIL" : "OK");
+}
+#endif /* CONFIG_FST_TEST */
+
+
+/* fst list_sessions */
+struct list_sessions_cb_ctx {
+	char *buf;
+	size_t buflen;
+	size_t reply_len;
+};
+
+
+static void list_session_enum_cb(struct fst_group *g, struct fst_session *s,
+				 void *ctx)
+{
+	struct list_sessions_cb_ctx *c = ctx;
+	int ret;
+
+	ret = os_snprintf(c->buf, c->buflen, " %u", fst_session_get_id(s));
+
+	c->buf += ret;
+	c->buflen -= ret;
+	c->reply_len += ret;
+}
+
+
+static int list_sessions(const char *group_id, char *buf, size_t buflen)
+{
+	struct list_sessions_cb_ctx ctx;
+	struct fst_group *g;
+
+	g = get_fst_group_by_id(group_id);
+	if (!g) {
+		fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
+			   group_id);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	ctx.buf = buf;
+	ctx.buflen = buflen;
+	ctx.reply_len = 0;
+
+	fst_session_enum(g, list_session_enum_cb, &ctx);
+
+	ctx.reply_len += os_snprintf(buf + ctx.reply_len, ctx.buflen, "\n");
+
+	return ctx.reply_len;
+}
+
+
+/* fst iface_peers */
+static int iface_peers(const char *group_id, char *buf, size_t buflen)
+{
+	const char *ifname;
+	struct fst_group *g;
+	struct fst_iface *f;
+	struct fst_get_peer_ctx *ctx;
+	const u8 *addr;
+	unsigned found = 0;
+	int ret = 0;
+
+	g = get_fst_group_by_id(group_id);
+	if (!g) {
+		fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
+			   group_id);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	ifname = os_strchr(group_id, ' ');
+	if (!ifname)
+		return os_snprintf(buf, buflen, "FAIL\n");
+	ifname++;
+
+	foreach_fst_group_iface(g, f) {
+		const char *in = fst_iface_get_name(f);
+
+		if (os_strncmp(ifname, in, os_strlen(in)) == 0) {
+			found = 1;
+			break;
+		}
+	}
+
+	if (!found)
+		return os_snprintf(buf, buflen, "FAIL\n");
+
+	addr = fst_iface_get_peer_first(f, &ctx, false);
+	for (; addr != NULL; addr = fst_iface_get_peer_next(f, &ctx, false)) {
+		int res;
+
+		res = os_snprintf(buf + ret, buflen - ret, MACSTR "\n",
+				  MAC2STR(addr));
+		if (os_snprintf_error(buflen - ret, res))
+			break;
+		ret += res;
+	}
+
+	return ret;
+}
+
+
+static int get_peer_mbies(const char *params, char *buf, size_t buflen)
+{
+	char *endp;
+	char ifname[FST_MAX_INTERFACE_SIZE];
+	u8 peer_addr[ETH_ALEN];
+	struct fst_group *g;
+	struct fst_iface *iface = NULL;
+	const struct wpabuf *mbies;
+
+	if (fst_read_next_text_param(params, ifname, sizeof(ifname), &endp) ||
+	    !*ifname)
+		goto problem;
+
+	while (isspace(*endp))
+		endp++;
+	if (fst_read_peer_addr(endp, peer_addr))
+		goto problem;
+
+	foreach_fst_group(g) {
+		iface = fst_group_get_iface_by_name(g, ifname);
+		if (iface)
+			break;
+	}
+	if (!iface)
+		goto problem;
+
+	mbies = fst_iface_get_peer_mb_ie(iface, peer_addr);
+	if (!mbies)
+		goto problem;
+
+	return wpa_snprintf_hex(buf, buflen, wpabuf_head(mbies),
+				wpabuf_len(mbies));
+
+problem:
+	return os_snprintf(buf, buflen, "FAIL\n");
+}
+
+
+/* fst list_ifaces */
+static int list_ifaces(const char *group_id, char *buf, size_t buflen)
+{
+	struct fst_group *g;
+	struct fst_iface *f;
+	int ret = 0;
+
+	g = get_fst_group_by_id(group_id);
+	if (!g) {
+		fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
+			   group_id);
+		return os_snprintf(buf, buflen, "FAIL\n");
+	}
+
+	foreach_fst_group_iface(g, f) {
+		int res;
+		const u8 *iface_addr = fst_iface_get_addr(f);
+
+		res = os_snprintf(buf + ret, buflen - ret,
+				  "%s|" MACSTR "|%u|%u\n",
+				  fst_iface_get_name(f),
+				  MAC2STR(iface_addr),
+				  fst_iface_get_priority(f),
+				  fst_iface_get_llt(f));
+		if (os_snprintf_error(buflen - ret, res))
+			break;
+		ret += res;
+	}
+
+	return ret;
+}
+
+
+/* fst list_groups */
+static int list_groups(const char *cmd, char *buf, size_t buflen)
+{
+	struct fst_group *g;
+	int ret = 0;
+
+	foreach_fst_group(g) {
+		int res;
+
+		res = os_snprintf(buf + ret, buflen - ret, "%s\n",
+				  fst_group_get_id(g));
+		if (os_snprintf_error(buflen - ret, res))
+			break;
+		ret += res;
+	}
+
+	return ret;
+}
+
+
+static const char * band_freq(enum mb_band_id band)
+{
+	static const char *band_names[] = {
+		[MB_BAND_ID_WIFI_2_4GHZ] = "2.4GHZ",
+		[MB_BAND_ID_WIFI_5GHZ] = "5GHZ",
+		[MB_BAND_ID_WIFI_60GHZ] = "60GHZ",
+	};
+
+	return fst_get_str_name(band, band_names, ARRAY_SIZE(band_names));
+}
+
+
+static int print_band(unsigned num, struct fst_iface *iface, const u8 *addr,
+		      char *buf, size_t buflen)
+{
+	const struct wpabuf *wpabuf;
+	enum hostapd_hw_mode hw_mode;
+	u8 channel;
+	int ret = 0;
+
+	fst_iface_get_channel_info(iface, &hw_mode, &channel);
+
+	ret += os_snprintf(buf + ret, buflen - ret, "band%u_frequency=%s\n",
+			   num, band_freq(fst_hw_mode_to_band(hw_mode)));
+	ret += os_snprintf(buf + ret, buflen - ret, "band%u_iface=%s\n",
+			   num, fst_iface_get_name(iface));
+	wpabuf = fst_iface_get_peer_mb_ie(iface, addr);
+	if (wpabuf) {
+		ret += os_snprintf(buf + ret, buflen - ret, "band%u_mb_ies=",
+				   num);
+		ret += wpa_snprintf_hex(buf + ret, buflen - ret,
+					wpabuf_head(wpabuf),
+					wpabuf_len(wpabuf));
+		ret += os_snprintf(buf + ret, buflen - ret, "\n");
+	}
+	ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_group_id=%s\n",
+			   num, fst_iface_get_group_id(iface));
+	ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_priority=%u\n",
+			   num, fst_iface_get_priority(iface));
+	ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_llt=%u\n",
+			   num, fst_iface_get_llt(iface));
+
+	return ret;
+}
+
+
+static void fst_ctrl_iface_on_iface_state_changed(struct fst_iface *i,
+						  bool attached)
+{
+	union fst_event_extra extra;
+
+	os_memset(&extra, 0, sizeof(extra));
+	extra.iface_state.attached = attached;
+	os_strlcpy(extra.iface_state.ifname, fst_iface_get_name(i),
+		   sizeof(extra.iface_state.ifname));
+	os_strlcpy(extra.iface_state.group_id, fst_iface_get_group_id(i),
+		   sizeof(extra.iface_state.group_id));
+
+	fst_ctrl_iface_notify(i, FST_INVALID_SESSION_ID,
+			      EVENT_FST_IFACE_STATE_CHANGED, &extra);
+}
+
+
+static int fst_ctrl_iface_on_iface_added(struct fst_iface *i)
+{
+	fst_ctrl_iface_on_iface_state_changed(i, true);
+	return 0;
+}
+
+
+static void fst_ctrl_iface_on_iface_removed(struct fst_iface *i)
+{
+	fst_ctrl_iface_on_iface_state_changed(i, false);
+}
+
+
+static void fst_ctrl_iface_on_event(enum fst_event_type event_type,
+				    struct fst_iface *i, struct fst_session *s,
+				    const union fst_event_extra *extra)
+{
+	u32 session_id = s ? fst_session_get_id(s) : FST_INVALID_SESSION_ID;
+
+	fst_ctrl_iface_notify(i, session_id, event_type, extra);
+}
+
+
+static const struct fst_ctrl ctrl_cli = {
+	.on_iface_added = fst_ctrl_iface_on_iface_added,
+	.on_iface_removed =  fst_ctrl_iface_on_iface_removed,
+	.on_event = fst_ctrl_iface_on_event,
+};
+
+const struct fst_ctrl *fst_ctrl_cli = &ctrl_cli;
+
+
+int fst_ctrl_iface_mb_info(const u8 *addr, char *buf, size_t buflen)
+{
+	struct fst_group *g;
+	struct fst_iface *f;
+	unsigned num = 0;
+	int ret = 0;
+
+	foreach_fst_group(g) {
+		foreach_fst_group_iface(g, f) {
+			if (fst_iface_is_connected(f, addr, true)) {
+				ret += print_band(num++, f, addr,
+						  buf + ret, buflen - ret);
+			}
+		}
+	}
+
+	return ret;
+}
+
+
+/* fst ctrl processor */
+int fst_ctrl_iface_receive(const char *cmd, char *reply, size_t reply_size)
+{
+	static const struct fst_command {
+		const char *name;
+		unsigned has_param;
+		int (*process)(const char *group_id, char *buf, size_t buflen);
+	} commands[] = {
+		{ FST_CMD_LIST_GROUPS, 0, list_groups},
+		{ FST_CMD_LIST_IFACES, 1, list_ifaces},
+		{ FST_CMD_IFACE_PEERS, 1, iface_peers},
+		{ FST_CMD_GET_PEER_MBIES, 1, get_peer_mbies},
+		{ FST_CMD_LIST_SESSIONS, 1, list_sessions},
+		{ FST_CMD_SESSION_ADD, 1, session_add},
+		{ FST_CMD_SESSION_REMOVE, 1, session_remove},
+		{ FST_CMD_SESSION_GET, 1, session_get},
+		{ FST_CMD_SESSION_SET, 1, session_set},
+		{ FST_CMD_SESSION_INITIATE, 1, session_initiate},
+		{ FST_CMD_SESSION_RESPOND, 1, session_respond},
+		{ FST_CMD_SESSION_TRANSFER, 1, session_transfer},
+		{ FST_CMD_SESSION_TEARDOWN, 1, session_teardown},
+#ifdef CONFIG_FST_TEST
+		{ FST_CMD_TEST_REQUEST, 1, test_request },
+#endif /* CONFIG_FST_TEST */
+		{ NULL, 0, NULL }
+	};
+	const struct fst_command *c;
+	const char *p;
+	const char *temp;
+	bool non_spaces_found;
+
+	for (c = commands; c->name; c++) {
+		if (os_strncasecmp(cmd, c->name, os_strlen(c->name)) != 0)
+			continue;
+		p = cmd + os_strlen(c->name);
+		if (c->has_param) {
+			if (!isspace(p[0]))
+				return os_snprintf(reply, reply_size, "FAIL\n");
+			p++;
+			temp = p;
+			non_spaces_found = false;
+			while (*temp) {
+				if (!isspace(*temp)) {
+					non_spaces_found = true;
+					break;
+				}
+				temp++;
+			}
+			if (!non_spaces_found)
+				return os_snprintf(reply, reply_size, "FAIL\n");
+		}
+		return c->process(p, reply, reply_size);
+	}
+
+	return os_snprintf(reply, reply_size, "UNKNOWN FST COMMAND\n");
+}
+
+
+int fst_read_next_int_param(const char *params, bool *valid, char **endp)
+{
+	int ret = -1;
+	const char *curp;
+
+	*valid = false;
+	*endp = (char *) params;
+	curp = params;
+	if (*curp) {
+		ret = (int) strtol(curp, endp, 0);
+		if (!**endp || isspace(**endp))
+			*valid = true;
+	}
+
+	return ret;
+}
+
+
+int fst_read_next_text_param(const char *params, char *buf, size_t buflen,
+			     char **endp)
+{
+	size_t max_chars_to_copy;
+	char *cur_dest;
+
+	*endp = (char *) params;
+	while (isspace(**endp))
+		(*endp)++;
+	if (!**endp || buflen <= 1)
+		return -EINVAL;
+
+	max_chars_to_copy = buflen - 1;
+	/* We need 1 byte for the terminating zero */
+	cur_dest = buf;
+	while (**endp && !isspace(**endp) && max_chars_to_copy > 0) {
+		*cur_dest = **endp;
+		(*endp)++;
+		cur_dest++;
+		max_chars_to_copy--;
+	}
+	*cur_dest = 0;
+
+	return 0;
+}
+
+
+int fst_read_peer_addr(const char *mac, u8 *peer_addr)
+{
+	if (hwaddr_aton(mac, peer_addr)) {
+		fst_printf(MSG_WARNING, "Bad peer_mac %s: invalid addr string",
+			   mac);
+		return -1;
+	}
+
+	if (is_zero_ether_addr(peer_addr) ||
+	    is_multicast_ether_addr(peer_addr)) {
+		fst_printf(MSG_WARNING, "Bad peer_mac %s: not a unicast addr",
+			   mac);
+		return -1;
+	}
+
+	return 0;
+}
+
+
+int fst_parse_attach_command(const char *cmd, char *ifname, size_t ifname_size,
+			     struct fst_iface_cfg *cfg)
+{
+	char *pos;
+	char *endp;
+	bool is_valid;
+	int val;
+
+	if (fst_read_next_text_param(cmd, ifname, ifname_size, &endp) ||
+	    fst_read_next_text_param(endp, cfg->group_id, sizeof(cfg->group_id),
+				     &endp))
+		return -EINVAL;
+
+	cfg->llt = FST_DEFAULT_LLT_CFG_VALUE;
+	cfg->priority = 0;
+	pos = os_strstr(endp, FST_ATTACH_CMD_PNAME_LLT);
+	if (pos) {
+		pos += os_strlen(FST_ATTACH_CMD_PNAME_LLT);
+		if (*pos == '=') {
+			val = fst_read_next_int_param(pos + 1, &is_valid,
+						      &endp);
+			if (is_valid)
+				cfg->llt = val;
+		}
+	}
+	pos = os_strstr(endp, FST_ATTACH_CMD_PNAME_PRIORITY);
+	if (pos) {
+		pos += os_strlen(FST_ATTACH_CMD_PNAME_PRIORITY);
+		if (*pos == '=') {
+			val = fst_read_next_int_param(pos + 1, &is_valid,
+						      &endp);
+			if (is_valid)
+				cfg->priority = (u8) val;
+		}
+	}
+
+	return 0;
+}
+
+
+int fst_parse_detach_command(const char *cmd, char *ifname, size_t ifname_size)
+{
+	char *endp;
+
+	return fst_read_next_text_param(cmd, ifname, ifname_size, &endp);
+}
+
+
+int fst_iface_detach(const char *ifname)
+{
+	struct fst_group *g;
+
+	foreach_fst_group(g) {
+		struct fst_iface *f;
+
+		f = fst_group_get_iface_by_name(g, ifname);
+		if (f) {
+			fst_detach(f);
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
diff --git a/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_ctrl_iface.h b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_ctrl_iface.h
new file mode 100644
index 0000000..354b81f
--- /dev/null
+++ b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_ctrl_iface.h
@@ -0,0 +1,45 @@
+/*
+ * FST module - internal Control interface definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef FST_CTRL_IFACE_H
+#define FST_CTRL_IFACE_H
+
+#include "fst/fst_ctrl_aux.h"
+
+#ifdef CONFIG_FST
+
+/* receiver */
+int fst_ctrl_iface_mb_info(const u8 *addr, char *buf, size_t buflen);
+
+int fst_ctrl_iface_receive(const char *txtaddr, char *buf, size_t buflen);
+
+extern const struct fst_ctrl *fst_ctrl_cli;
+
+#else /* CONFIG_FST */
+
+static inline int
+fst_ctrl_iface_mb_info(const u8 *addr, char *buf, size_t buflen)
+{
+	return 0;
+}
+
+#endif /* CONFIG_FST */
+
+int fst_read_next_int_param(const char *params, bool *valid, char **endp);
+int fst_read_next_text_param(const char *params, char *buf, size_t buflen,
+			     char **endp);
+int fst_read_peer_addr(const char *mac, u8 *peer_addr);
+
+struct fst_iface_cfg;
+
+int fst_parse_attach_command(const char *cmd, char *ifname, size_t ifname_size,
+			     struct fst_iface_cfg *cfg);
+int fst_parse_detach_command(const char *cmd, char *ifname, size_t ifname_size);
+int fst_iface_detach(const char *ifname);
+
+#endif /* CTRL_IFACE_FST_H */
diff --git a/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_defs.h b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_defs.h
new file mode 100644
index 0000000..5859f6f
--- /dev/null
+++ b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_defs.h
@@ -0,0 +1,87 @@
+/*
+ * FST module - FST related definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef IEEE_80211_FST_DEFS_H
+#define IEEE_80211_FST_DEFS_H
+
+/* IEEE Std 802.11ad */
+
+#define MB_STA_CHANNEL_ALL 0
+
+enum session_type {
+	SESSION_TYPE_BSS = 0, /*  Infrastructure BSS */
+	SESSION_TYPE_IBSS = 1,
+	SESSION_TYPE_DLS = 2,
+	SESSION_TYPE_TDLS = 3,
+	SESSION_TYPE_PBSS = 4
+};
+
+#define SESSION_CONTROL(session_type, switch_intent) \
+	(((u8) ((session_type) & 0x7)) | ((switch_intent) ? 0x10 : 0x00))
+
+#define GET_SESSION_CONTROL_TYPE(session_control) \
+	((u8) ((session_control) & 0x7))
+
+#define GET_SESSION_CONTROL_SWITCH_INTENT(session_control) \
+	(((session_control) & 0x10) >> 4)
+
+/* 8.4.2.147  Session Transition element */
+struct session_transition_ie {
+	u8 element_id;
+	u8 length;
+	le32 fsts_id;
+	u8 session_control;
+	u8 new_band_id;
+	u8 new_band_setup;
+	u8 new_band_op;
+	u8 old_band_id;
+	u8 old_band_setup;
+	u8 old_band_op;
+} STRUCT_PACKED;
+
+struct fst_setup_req {
+	u8 action;
+	u8 dialog_token;
+	le32 llt;
+	struct session_transition_ie stie;
+	/* Multi-band (optional) */
+	/* Wakeup Schedule (optional) */
+	/* Awake Window (optional) */
+	/* Switching Stream (optional) */
+} STRUCT_PACKED;
+
+struct fst_setup_res {
+	u8 action;
+	u8 dialog_token;
+	u8 status_code;
+	struct session_transition_ie stie;
+	/* Multi-band (optional) */
+	/* Wakeup Schedule (optional) */
+	/* Awake Window (optional) */
+	/* Switching Stream (optional) */
+	/* Timeout Interval (optional) */
+} STRUCT_PACKED;
+
+struct fst_ack_req {
+	u8 action;
+	u8 dialog_token;
+	le32 fsts_id;
+} STRUCT_PACKED;
+
+struct fst_ack_res {
+	u8 action;
+	u8 dialog_token;
+	le32 fsts_id;
+} STRUCT_PACKED;
+
+struct fst_tear_down {
+	u8 action;
+	le32 fsts_id;
+} STRUCT_PACKED;
+
+#endif /* IEEE_80211_FST_DEFS_H */
diff --git a/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_group.c b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_group.c
new file mode 100644
index 0000000..255d0fd
--- /dev/null
+++ b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_group.c
@@ -0,0 +1,527 @@
+/*
+ * FST module - FST group object implementation
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+#include "utils/common.h"
+#include "common/defs.h"
+#include "common/ieee802_11_defs.h"
+#include "common/ieee802_11_common.h"
+#include "drivers/driver.h"
+#include "fst/fst_internal.h"
+#include "fst/fst_defs.h"
+
+
+struct dl_list fst_global_groups_list;
+
+
+static void fst_dump_mb_ies(const char *group_id, const char *ifname,
+			    struct wpabuf *mbies)
+{
+	const u8 *p = wpabuf_head(mbies);
+	size_t s = wpabuf_len(mbies);
+
+	while (s >= 2) {
+		const struct multi_band_ie *mbie =
+			(const struct multi_band_ie *) p;
+		size_t len;
+
+		WPA_ASSERT(mbie->eid == WLAN_EID_MULTI_BAND);
+		WPA_ASSERT(2U + mbie->len >= sizeof(*mbie));
+		len = 2 + mbie->len;
+		if (len > s)
+			break;
+
+		fst_printf(MSG_WARNING,
+			   "%s: %s: mb_ctrl=%u band_id=%u op_class=%u chan=%u bssid="
+			   MACSTR
+			   " beacon_int=%u tsf_offs=[%u %u %u %u %u %u %u %u] mb_cc=0x%02x tmout=%u",
+			   group_id, ifname,
+			   mbie->mb_ctrl, mbie->band_id, mbie->op_class,
+			   mbie->chan, MAC2STR(mbie->bssid), mbie->beacon_int,
+			   mbie->tsf_offs[0], mbie->tsf_offs[1],
+			   mbie->tsf_offs[2], mbie->tsf_offs[3],
+			   mbie->tsf_offs[4], mbie->tsf_offs[5],
+			   mbie->tsf_offs[6], mbie->tsf_offs[7],
+			   mbie->mb_connection_capability,
+			   mbie->fst_session_tmout);
+
+		p += len;
+		s -= len;
+	}
+}
+
+
+static void fst_fill_mb_ie(struct wpabuf *buf, const u8 *bssid,
+			   const u8 *own_addr, enum mb_band_id band, u8 channel)
+{
+	struct multi_band_ie *mbie;
+	size_t len = sizeof(*mbie);
+
+	if (own_addr)
+		len += ETH_ALEN;
+
+	mbie = wpabuf_put(buf, len);
+
+	os_memset(mbie, 0, len);
+
+	mbie->eid = WLAN_EID_MULTI_BAND;
+	mbie->len = len - 2;
+#ifdef HOSTAPD
+	mbie->mb_ctrl = MB_STA_ROLE_AP;
+	mbie->mb_connection_capability = MB_CONNECTION_CAPABILITY_AP;
+#else /* HOSTAPD */
+	mbie->mb_ctrl = MB_STA_ROLE_NON_PCP_NON_AP;
+	mbie->mb_connection_capability = 0;
+#endif /* HOSTAPD */
+	if (bssid)
+		os_memcpy(mbie->bssid, bssid, ETH_ALEN);
+	mbie->band_id = band;
+	mbie->op_class = 0;  /* means all */
+	mbie->chan = channel;
+	mbie->fst_session_tmout = FST_DEFAULT_SESSION_TIMEOUT_TU;
+
+	if (own_addr) {
+		mbie->mb_ctrl |= MB_CTRL_STA_MAC_PRESENT;
+		os_memcpy(&mbie[1], own_addr, ETH_ALEN);
+	}
+}
+
+
+static unsigned fst_fill_iface_mb_ies(struct fst_iface *f, struct wpabuf *buf)
+{
+	const  u8 *bssid;
+
+	bssid = fst_iface_get_bssid(f);
+	if (bssid) {
+		enum hostapd_hw_mode hw_mode;
+		u8 channel;
+
+		if (buf) {
+			fst_iface_get_channel_info(f, &hw_mode, &channel);
+			fst_fill_mb_ie(buf, bssid, fst_iface_get_addr(f),
+				       fst_hw_mode_to_band(hw_mode), channel);
+		}
+		return 1;
+	} else {
+		unsigned bands[MB_BAND_ID_WIFI_60GHZ + 1] = {};
+		struct hostapd_hw_modes *modes;
+		enum mb_band_id b;
+		int num_modes = fst_iface_get_hw_modes(f, &modes);
+		int ret = 0;
+
+		while (num_modes--) {
+			b = fst_hw_mode_to_band(modes->mode);
+			modes++;
+			if (b >= ARRAY_SIZE(bands) || bands[b]++)
+				continue;
+			ret++;
+			if (buf)
+				fst_fill_mb_ie(buf, NULL, fst_iface_get_addr(f),
+					       b, MB_STA_CHANNEL_ALL);
+		}
+		return ret;
+	}
+}
+
+
+static struct wpabuf * fst_group_create_mb_ie(struct fst_group *g,
+					      struct fst_iface *i)
+{
+	struct wpabuf *buf;
+	struct fst_iface *f;
+	unsigned int nof_mbies = 0;
+	unsigned int nof_ifaces_added = 0;
+
+	foreach_fst_group_iface(g, f) {
+		if (f == i)
+			continue;
+		nof_mbies += fst_fill_iface_mb_ies(f, NULL);
+	}
+
+	buf = wpabuf_alloc(nof_mbies *
+			   (sizeof(struct multi_band_ie) + ETH_ALEN));
+	if (!buf) {
+		fst_printf_iface(i, MSG_ERROR,
+				 "cannot allocate mem for %u MB IEs",
+				 nof_mbies);
+		return NULL;
+	}
+
+	/* The list is sorted in descending order by priorities, so MB IEs will
+	 * be arranged in the same order, as required by spec (see corresponding
+	 * comment in.fst_attach().
+	 */
+	foreach_fst_group_iface(g, f) {
+		if (f == i)
+			continue;
+
+		fst_fill_iface_mb_ies(f, buf);
+		++nof_ifaces_added;
+
+		fst_printf_iface(i, MSG_DEBUG, "added to MB IE");
+	}
+
+	if (!nof_ifaces_added) {
+		wpabuf_free(buf);
+		buf = NULL;
+		fst_printf_iface(i, MSG_INFO,
+				 "cannot add MB IE: no backup ifaces");
+	} else {
+		fst_dump_mb_ies(fst_group_get_id(g), fst_iface_get_name(i),
+				buf);
+	}
+
+	return buf;
+}
+
+
+static const u8 * fst_mbie_get_peer_addr(const struct multi_band_ie *mbie)
+{
+	const u8 *peer_addr = NULL;
+
+	switch (MB_CTRL_ROLE(mbie->mb_ctrl)) {
+	case MB_STA_ROLE_AP:
+		peer_addr = mbie->bssid;
+		break;
+	case MB_STA_ROLE_NON_PCP_NON_AP:
+		if (mbie->mb_ctrl & MB_CTRL_STA_MAC_PRESENT &&
+		    (size_t) 2 + mbie->len >= sizeof(*mbie) + ETH_ALEN)
+			peer_addr = (const u8 *) &mbie[1];
+		break;
+	default:
+		break;
+	}
+
+	return peer_addr;
+}
+
+
+static const u8 * fst_mbie_get_peer_addr_for_band(const struct wpabuf *mbies,
+						  u8 band_id)
+{
+	const u8 *p = wpabuf_head(mbies);
+	size_t s = wpabuf_len(mbies);
+
+	while (s >= 2) {
+		const struct multi_band_ie *mbie =
+			(const struct multi_band_ie *) p;
+
+		if (mbie->eid != WLAN_EID_MULTI_BAND) {
+			fst_printf(MSG_INFO, "unexpected eid %d", mbie->eid);
+			return NULL;
+		}
+
+		if (mbie->len < sizeof(*mbie) - 2 || mbie->len > s - 2) {
+			fst_printf(MSG_INFO, "invalid mbie len %d",
+				   mbie->len);
+			return NULL;
+		}
+
+		if (mbie->band_id == band_id)
+			return fst_mbie_get_peer_addr(mbie);
+
+		p += 2 + mbie->len;
+		s -= 2 + mbie->len;
+	}
+
+	fst_printf(MSG_INFO, "mbie doesn't contain band %d", band_id);
+	return NULL;
+}
+
+
+struct fst_iface * fst_group_get_iface_by_name(struct fst_group *g,
+					       const char *ifname)
+{
+	struct fst_iface *f;
+
+	foreach_fst_group_iface(g, f) {
+		const char *in = fst_iface_get_name(f);
+
+		if (os_strncmp(in, ifname, os_strlen(in)) == 0)
+			return f;
+	}
+
+	return NULL;
+}
+
+
+u8 fst_group_assign_dialog_token(struct fst_group *g)
+{
+	g->dialog_token++;
+	if (g->dialog_token == 0)
+		g->dialog_token++;
+	return g->dialog_token;
+}
+
+
+u32 fst_group_assign_fsts_id(struct fst_group *g)
+{
+	g->fsts_id++;
+	return g->fsts_id;
+}
+
+
+/**
+ * fst_group_get_peer_other_connection_1 - Find peer's "other" connection
+ * (iface, MAC tuple) by using peer's MB IE on iface.
+ *
+ * @iface: iface on which FST Setup Request was received
+ * @peer_addr: Peer address on iface
+ * @band_id: "other" connection band id
+ * @other_peer_addr (out): Peer's MAC address on the "other" connection (on the
+ *   "other" iface)
+ *
+ * This function parses peer's MB IE on iface. It looks for peer's MAC address
+ * on band_id (tmp_peer_addr). Next all interfaces are iterated to find an
+ * interface which correlates with band_id. If such interface is found, peer
+ * database is iterated to see if tmp_peer_addr is connected over it.
+ */
+static struct fst_iface *
+fst_group_get_peer_other_connection_1(struct fst_iface *iface,
+				      const u8 *peer_addr, u8 band_id,
+				      u8 *other_peer_addr)
+{
+	const struct wpabuf *mbies;
+	struct fst_iface *other_iface;
+	const u8 *tmp_peer_addr;
+
+	/* Get peer's MB IEs on iface */
+	mbies = fst_iface_get_peer_mb_ie(iface, peer_addr);
+	if (!mbies)
+		return NULL;
+
+	/* Get peer's MAC address on the "other" interface */
+	tmp_peer_addr = fst_mbie_get_peer_addr_for_band(mbies, band_id);
+	if (!tmp_peer_addr) {
+		fst_printf(MSG_INFO,
+			   "couldn't extract other peer addr from mbies");
+		return NULL;
+	}
+
+	fst_printf(MSG_DEBUG, "found other peer addr from mbies: " MACSTR,
+		   MAC2STR(tmp_peer_addr));
+
+	foreach_fst_group_iface(fst_iface_get_group(iface), other_iface) {
+		if (other_iface == iface ||
+		    band_id != fst_iface_get_band_id(other_iface))
+			continue;
+		if (fst_iface_is_connected(other_iface, tmp_peer_addr, false)) {
+			os_memcpy(other_peer_addr, tmp_peer_addr, ETH_ALEN);
+			return other_iface;
+		}
+	}
+
+	return NULL;
+}
+
+
+/**
+ * fst_group_get_peer_other_connection_2 - Find peer's "other" connection
+ * (iface, MAC tuple) by using MB IEs of other peers.
+ *
+ * @iface: iface on which FST Setup Request was received
+ * @peer_addr: Peer address on iface
+ * @band_id: "other" connection band id
+ * @other_peer_addr (out): Peer's MAC address on the "other" connection (on the
+ *   "other" iface)
+ *
+ * This function iterates all connection (other_iface, cur_peer_addr tuples).
+ * For each connection, MB IE (of cur_peer_addr on other_iface) is parsed and
+ * MAC address on iface's band_id is extracted (this_peer_addr).
+ * this_peer_addr is then compared to peer_addr. A match indicates we have
+ * found the "other" connection.
+ */
+static struct fst_iface *
+fst_group_get_peer_other_connection_2(struct fst_iface *iface,
+				      const u8 *peer_addr, u8 band_id,
+				      u8 *other_peer_addr)
+{
+	u8 this_band_id = fst_iface_get_band_id(iface);
+	const u8 *cur_peer_addr, *this_peer_addr;
+	struct fst_get_peer_ctx *ctx;
+	struct fst_iface *other_iface;
+	const struct wpabuf *cur_mbie;
+
+	foreach_fst_group_iface(fst_iface_get_group(iface), other_iface) {
+		if (other_iface == iface ||
+		    band_id != fst_iface_get_band_id(other_iface))
+			continue;
+		cur_peer_addr = fst_iface_get_peer_first(other_iface, &ctx,
+							 true);
+		for (; cur_peer_addr;
+		     cur_peer_addr = fst_iface_get_peer_next(other_iface, &ctx,
+							     true)) {
+			cur_mbie = fst_iface_get_peer_mb_ie(other_iface,
+							    cur_peer_addr);
+			if (!cur_mbie)
+				continue;
+			this_peer_addr = fst_mbie_get_peer_addr_for_band(
+				cur_mbie, this_band_id);
+			if (!this_peer_addr)
+				continue;
+			if (os_memcmp(this_peer_addr, peer_addr, ETH_ALEN) ==
+			    0) {
+				os_memcpy(other_peer_addr, cur_peer_addr,
+					  ETH_ALEN);
+				return other_iface;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+
+/**
+ * fst_group_get_peer_other_connection - Find peer's "other" connection (iface,
+ * MAC tuple).
+ *
+ * @iface: iface on which FST Setup Request was received
+ * @peer_addr: Peer address on iface
+ * @band_id: "other" connection band id
+ * @other_peer_addr (out): Peer's MAC address on the "other" connection (on the
+ *   "other" iface)
+ *
+ * This function is called upon receiving FST Setup Request from some peer who
+ * has peer_addr on iface. It searches for another connection of the same peer
+ * on different interface which correlates with band_id. MB IEs received from
+ * peer (on the two different interfaces) are used to identify same peer.
+ */
+struct fst_iface *
+fst_group_get_peer_other_connection(struct fst_iface *iface,
+				    const u8 *peer_addr, u8 band_id,
+				    u8 *other_peer_addr)
+{
+	struct fst_iface *other_iface;
+
+	fst_printf(MSG_DEBUG, "%s: %s:" MACSTR ", %d", __func__,
+		   fst_iface_get_name(iface), MAC2STR(peer_addr), band_id);
+
+	/*
+	 * Two search methods are used:
+	 * 1. Use peer's MB IE on iface to extract peer's MAC address on
+	 *    "other" connection. Then check if such "other" connection exists.
+	 * 2. Iterate peer database, examine each MB IE to see if it points to
+	 *    (iface, peer_addr) tuple
+	 */
+
+	other_iface = fst_group_get_peer_other_connection_1(iface, peer_addr,
+							    band_id,
+							    other_peer_addr);
+	if (other_iface) {
+		fst_printf(MSG_DEBUG, "found by method #1. %s:" MACSTR,
+			   fst_iface_get_name(other_iface),
+			   MAC2STR(other_peer_addr));
+		return other_iface;
+	}
+
+	other_iface = fst_group_get_peer_other_connection_2(iface, peer_addr,
+							    band_id,
+							    other_peer_addr);
+	if (other_iface) {
+		fst_printf(MSG_DEBUG, "found by method #2. %s:" MACSTR,
+			   fst_iface_get_name(other_iface),
+			   MAC2STR(other_peer_addr));
+		return other_iface;
+	}
+
+	fst_printf(MSG_INFO, "%s: other connection not found", __func__);
+	return NULL;
+}
+
+
+struct fst_group * fst_group_create(const char *group_id)
+{
+	struct fst_group *g;
+
+	g = os_zalloc(sizeof(*g));
+	if (g == NULL) {
+		fst_printf(MSG_ERROR, "%s: Cannot alloc group", group_id);
+		return NULL;
+	}
+
+	dl_list_init(&g->ifaces);
+	os_strlcpy(g->group_id, group_id, sizeof(g->group_id));
+
+	dl_list_add_tail(&fst_global_groups_list, &g->global_groups_lentry);
+	fst_printf_group(g, MSG_DEBUG, "instance created");
+
+	foreach_fst_ctrl_call(on_group_created, g);
+
+	return g;
+}
+
+
+void fst_group_attach_iface(struct fst_group *g, struct fst_iface *i)
+{
+	struct dl_list *list = &g->ifaces;
+	struct fst_iface *f;
+
+	/*
+	 * Add new interface to the list.
+	 * The list is sorted in descending order by priority to allow
+	 * multiple MB IEs creation according to the spec (see 10.32 Multi-band
+	 * operation, 10.32.1 General), as they should be ordered according to
+	 * priorities.
+	 */
+	foreach_fst_group_iface(g, f) {
+		if (fst_iface_get_priority(f) < fst_iface_get_priority(i))
+			break;
+		list = &f->group_lentry;
+	}
+	dl_list_add(list, &i->group_lentry);
+}
+
+
+void fst_group_detach_iface(struct fst_group *g, struct fst_iface *i)
+{
+	dl_list_del(&i->group_lentry);
+}
+
+
+void fst_group_delete(struct fst_group *group)
+{
+	struct fst_session *s;
+
+	dl_list_del(&group->global_groups_lentry);
+	WPA_ASSERT(dl_list_empty(&group->ifaces));
+	foreach_fst_ctrl_call(on_group_deleted, group);
+	fst_printf_group(group, MSG_DEBUG, "instance deleted");
+	while ((s = fst_session_global_get_first_by_group(group)) != NULL)
+		fst_session_delete(s);
+	os_free(group);
+}
+
+
+bool fst_group_delete_if_empty(struct fst_group *group)
+{
+	bool is_empty = !fst_group_has_ifaces(group) &&
+		!fst_session_global_get_first_by_group(group);
+
+	if (is_empty)
+		fst_group_delete(group);
+
+	return is_empty;
+}
+
+
+void fst_group_update_ie(struct fst_group *g)
+{
+	struct fst_iface *i;
+
+	foreach_fst_group_iface(g, i) {
+		struct wpabuf *mbie = fst_group_create_mb_ie(g, i);
+
+		if (!mbie)
+			fst_printf_iface(i, MSG_WARNING, "cannot create MB IE");
+
+		fst_iface_attach_mbie(i, mbie);
+		fst_iface_set_ies(i, mbie);
+		fst_printf_iface(i, MSG_DEBUG, "multi-band IE set to %p", mbie);
+	}
+}
diff --git a/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_group.h b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_group.h
new file mode 100644
index 0000000..4a9ff3e
--- /dev/null
+++ b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_group.h
@@ -0,0 +1,69 @@
+/*
+ * FST module - FST group object definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef FST_GROUP_H
+#define FST_GROUP_H
+
+struct fst_group {
+	char group_id[IFNAMSIZ + 1];
+	struct dl_list ifaces;
+	u8 dialog_token;
+	u32 fsts_id;
+	struct dl_list global_groups_lentry;
+};
+
+struct session_transition_ie;
+
+#define foreach_fst_group_iface(g, i) \
+	dl_list_for_each((i), &(g)->ifaces, struct fst_iface, group_lentry)
+
+struct fst_group * fst_group_create(const char *group_id);
+void fst_group_attach_iface(struct fst_group *g, struct fst_iface *i);
+void fst_group_detach_iface(struct fst_group *g, struct fst_iface *i);
+void fst_group_delete(struct fst_group *g);
+
+void fst_group_update_ie(struct fst_group *g);
+
+static inline bool fst_group_has_ifaces(struct fst_group *g)
+{
+	return !dl_list_empty(&g->ifaces);
+}
+
+static inline struct fst_iface * fst_group_first_iface(struct fst_group *g)
+{
+	return dl_list_first(&g->ifaces, struct fst_iface, group_lentry);
+}
+
+static inline const char * fst_group_get_id(struct fst_group *g)
+{
+	return g->group_id;
+}
+
+bool fst_group_delete_if_empty(struct fst_group *group);
+struct fst_iface * fst_group_get_iface_by_name(struct fst_group *g,
+					       const char *ifname);
+struct fst_iface *
+fst_group_get_peer_other_connection(struct fst_iface *iface,
+				    const u8 *peer_addr, u8 band_id,
+				    u8 *other_peer_addr);
+u8  fst_group_assign_dialog_token(struct fst_group *g);
+u32 fst_group_assign_fsts_id(struct fst_group *g);
+
+extern struct dl_list fst_global_groups_list;
+
+#define foreach_fst_group(g) \
+	dl_list_for_each((g), &fst_global_groups_list, \
+			 struct fst_group, global_groups_lentry)
+
+static inline struct fst_group * fst_first_group(void)
+{
+	return dl_list_first(&fst_global_groups_list, struct fst_group,
+			     global_groups_lentry);
+}
+
+#endif /* FST_GROUP_H */
diff --git a/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_iface.c b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_iface.c
new file mode 100644
index 0000000..90c5fc0
--- /dev/null
+++ b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_iface.c
@@ -0,0 +1,80 @@
+/*
+ * FST module - FST interface object implementation
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+#include "utils/common.h"
+#include "fst/fst_internal.h"
+#include "fst/fst_defs.h"
+
+
+struct fst_iface * fst_iface_create(struct fst_group *g, const char *ifname,
+				    const u8 *own_addr,
+				    const struct fst_wpa_obj *iface_obj,
+				    const struct fst_iface_cfg *cfg)
+{
+	struct fst_iface *i;
+
+	i = os_zalloc(sizeof(*i));
+	if (!i) {
+		fst_printf_group(g, MSG_ERROR, "cannot allocate iface for %s",
+				ifname);
+		return NULL;
+	}
+
+	i->cfg = *cfg;
+	i->iface_obj = *iface_obj;
+	i->group = g;
+	os_strlcpy(i->ifname, ifname, sizeof(i->ifname));
+	os_memcpy(i->own_addr, own_addr, sizeof(i->own_addr));
+
+	if (!i->cfg.llt) {
+		fst_printf_iface(i, MSG_WARNING, "Zero llt adjusted");
+		i->cfg.llt = FST_DEFAULT_LLT_CFG_VALUE;
+	}
+
+	return i;
+}
+
+
+void fst_iface_delete(struct fst_iface *i)
+{
+	fst_iface_set_ies(i, NULL);
+	wpabuf_free(i->mb_ie);
+	os_free(i);
+}
+
+
+bool fst_iface_is_connected(struct fst_iface *iface, const u8 *addr,
+			    bool mb_only)
+{
+	struct fst_get_peer_ctx *ctx;
+	const u8 *a = fst_iface_get_peer_first(iface, &ctx, mb_only);
+
+	for (; a != NULL; a = fst_iface_get_peer_next(iface, &ctx, mb_only))
+		if (os_memcmp(addr, a, ETH_ALEN) == 0)
+			return true;
+
+	return false;
+}
+
+
+void fst_iface_attach_mbie(struct fst_iface *i, struct wpabuf *mbie)
+{
+	wpabuf_free(i->mb_ie);
+	i->mb_ie = mbie;
+}
+
+
+enum mb_band_id fst_iface_get_band_id(struct fst_iface *i)
+{
+	enum hostapd_hw_mode hw_mode;
+	u8 channel;
+
+	fst_iface_get_channel_info(i, &hw_mode, &channel);
+	return fst_hw_mode_to_band(hw_mode);
+}
diff --git a/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_iface.h b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_iface.h
new file mode 100644
index 0000000..af7156c
--- /dev/null
+++ b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_iface.h
@@ -0,0 +1,136 @@
+/*
+ * FST module - FST interface object definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+
+#ifndef FST_IFACE_H
+#define FST_IFACE_H
+
+#include "utils/includes.h"
+#include "utils/common.h"
+#include "list.h"
+#include "fst.h"
+
+struct fst_iface {
+	struct fst_group *group;
+	struct fst_wpa_obj iface_obj;
+	u8 own_addr[ETH_ALEN];
+	struct wpabuf *mb_ie;
+	char ifname[IFNAMSIZ + 1];
+	struct fst_iface_cfg cfg;
+	struct dl_list group_lentry;
+};
+
+struct fst_iface * fst_iface_create(struct fst_group *g, const char *ifname,
+				    const u8 *own_addr,
+				    const struct fst_wpa_obj *iface_obj,
+				    const struct fst_iface_cfg *cfg);
+void fst_iface_delete(struct fst_iface *i);
+
+static inline struct fst_group * fst_iface_get_group(struct fst_iface *i)
+{
+	return i->group;
+}
+
+static inline const char * fst_iface_get_name(struct fst_iface *i)
+{
+	return i->ifname;
+}
+
+static inline const u8 * fst_iface_get_addr(struct fst_iface *i)
+{
+	return i->own_addr;
+}
+
+static inline const char * fst_iface_get_group_id(struct fst_iface *i)
+{
+	return i->cfg.group_id;
+}
+
+static inline u8 fst_iface_get_priority(struct fst_iface *i)
+{
+	return i->cfg.priority;
+}
+
+static inline u32 fst_iface_get_llt(struct fst_iface *i)
+{
+	return i->cfg.llt;
+}
+
+static inline const struct wpabuf * fst_iface_get_mbie(struct fst_iface *i)
+{
+	return i->mb_ie;
+}
+
+static inline const u8 * fst_iface_get_bssid(struct fst_iface *i)
+{
+	return i->iface_obj.get_bssid(i->iface_obj.ctx);
+}
+
+static inline void fst_iface_get_channel_info(struct fst_iface *i,
+					      enum hostapd_hw_mode *hw_mode,
+					      u8 *channel)
+{
+	i->iface_obj.get_channel_info(i->iface_obj.ctx, hw_mode, channel);
+}
+
+static inline int fst_iface_get_hw_modes(struct fst_iface *i,
+					 struct hostapd_hw_modes **modes)
+{
+	return i->iface_obj.get_hw_modes(i->iface_obj.ctx, modes);
+}
+
+static inline void fst_iface_set_ies(struct fst_iface *i,
+				     const struct wpabuf *fst_ies)
+{
+	i->iface_obj.set_ies(i->iface_obj.ctx, fst_ies);
+}
+
+static inline int fst_iface_send_action(struct fst_iface *i,
+					const u8 *addr, struct wpabuf *data)
+{
+	return i->iface_obj.send_action(i->iface_obj.ctx, addr, data);
+}
+
+static inline const struct wpabuf *
+fst_iface_get_peer_mb_ie(struct fst_iface *i, const u8 *addr)
+{
+	return i->iface_obj.get_mb_ie(i->iface_obj.ctx, addr);
+}
+
+static inline void fst_iface_update_mb_ie(struct fst_iface *i,
+					  const u8 *addr,
+					  const u8 *buf, size_t size)
+{
+	i->iface_obj.update_mb_ie(i->iface_obj.ctx, addr, buf, size);
+}
+
+static inline const u8 * fst_iface_get_peer_first(struct fst_iface *i,
+						  struct fst_get_peer_ctx **ctx,
+						  bool mb_only)
+{
+	return i->iface_obj.get_peer_first(i->iface_obj.ctx, ctx, mb_only);
+}
+
+static inline const u8 * fst_iface_get_peer_next(struct fst_iface *i,
+						 struct fst_get_peer_ctx **ctx,
+						 bool mb_only)
+{
+	return i->iface_obj.get_peer_next(i->iface_obj.ctx, ctx, mb_only);
+}
+
+bool fst_iface_is_connected(struct fst_iface *iface, const u8 *addr,
+			    bool mb_only);
+void fst_iface_attach_mbie(struct fst_iface *i, struct wpabuf *mbie);
+enum mb_band_id fst_iface_get_band_id(struct fst_iface *i);
+
+static inline void * fst_iface_get_wpa_obj_ctx(struct fst_iface *i)
+{
+	return i->iface_obj.ctx;
+}
+
+#endif /* FST_IFACE_H */
diff --git a/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_internal.h b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_internal.h
new file mode 100644
index 0000000..9fe32b8
--- /dev/null
+++ b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_internal.h
@@ -0,0 +1,49 @@
+/*
+ * FST module - auxiliary definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef FST_INTERNAL_H
+#define FST_INTERNAL_H
+
+#include "utils/includes.h"
+#include "utils/common.h"
+#include "common/defs.h"
+#include "common/ieee802_11_defs.h"
+#include "fst/fst_iface.h"
+#include "fst/fst_group.h"
+#include "fst/fst_session.h"
+
+#define fst_printf(level, format, ...) \
+	wpa_printf((level), "FST: " format, ##__VA_ARGS__)
+
+#define fst_printf_group(group, level, format, ...) \
+	wpa_printf((level), "FST: %s: " format, \
+		   fst_group_get_id(group), ##__VA_ARGS__)
+
+#define fst_printf_iface(iface, level, format, ...) \
+	fst_printf_group(fst_iface_get_group(iface), (level), "%s: " format, \
+			 fst_iface_get_name(iface), ##__VA_ARGS__)
+
+enum mb_band_id fst_hw_mode_to_band(enum hostapd_hw_mode mode);
+
+struct fst_ctrl_handle {
+	struct fst_ctrl ctrl;
+	struct dl_list global_ctrls_lentry;
+};
+
+extern struct dl_list fst_global_ctrls_list;
+
+#define foreach_fst_ctrl_call(clb, ...) \
+	do { \
+		struct fst_ctrl_handle *__fst_ctrl_h; \
+		dl_list_for_each(__fst_ctrl_h, &fst_global_ctrls_list, \
+			struct fst_ctrl_handle, global_ctrls_lentry) \
+			if (__fst_ctrl_h->ctrl.clb) \
+				__fst_ctrl_h->ctrl.clb(__VA_ARGS__);\
+	} while (0)
+
+#endif /* FST_INTERNAL_H */
diff --git a/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_session.c b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_session.c
new file mode 100644
index 0000000..e42a85c
--- /dev/null
+++ b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_session.c
@@ -0,0 +1,1609 @@
+/*
+ * FST module - FST Session implementation
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "utils/eloop.h"
+#include "common/defs.h"
+#include "fst/fst_internal.h"
+#include "fst/fst_defs.h"
+#include "fst/fst_ctrl_iface.h"
+#ifdef CONFIG_FST_TEST
+#include "fst/fst_ctrl_defs.h"
+#endif /* CONFIG_FST_TEST */
+
+#define US_80211_TU 1024
+
+#define US_TO_TU(m) ((m) * / US_80211_TU)
+#define TU_TO_US(m) ((m) * US_80211_TU)
+
+#define FST_LLT_SWITCH_IMMEDIATELY 0
+
+#define fst_printf_session(s, level, format, ...) \
+	fst_printf((level), "%u (0x%08x): [" MACSTR "," MACSTR "] :" format, \
+		   (s)->id, (s)->data.fsts_id, \
+		   MAC2STR((s)->data.old_peer_addr), \
+		   MAC2STR((s)->data.new_peer_addr), \
+		   ##__VA_ARGS__)
+
+#define fst_printf_siface(s, iface, level, format, ...) \
+	fst_printf_session((s), (level), "%s: " format, \
+			   fst_iface_get_name(iface), ##__VA_ARGS__)
+
+#define fst_printf_sframe(s, is_old, level, format, ...) \
+	fst_printf_siface((s), \
+		(is_old) ? (s)->data.old_iface : (s)->data.new_iface, \
+		(level), format, ##__VA_ARGS__)
+
+#define FST_LLT_MS_DEFAULT 50
+#define FST_ACTION_MAX_SUPPORTED   FST_ACTION_ON_CHANNEL_TUNNEL
+
+static const char * const fst_action_names[] = {
+	[FST_ACTION_SETUP_REQUEST]     = "Setup Request",
+	[FST_ACTION_SETUP_RESPONSE]    = "Setup Response",
+	[FST_ACTION_TEAR_DOWN]         = "Tear Down",
+	[FST_ACTION_ACK_REQUEST]       = "Ack Request",
+	[FST_ACTION_ACK_RESPONSE]      = "Ack Response",
+	[FST_ACTION_ON_CHANNEL_TUNNEL] = "On Channel Tunnel",
+};
+
+struct fst_session {
+	struct {
+		/* Session configuration that can be zeroed on reset */
+		u8 old_peer_addr[ETH_ALEN];
+		u8 new_peer_addr[ETH_ALEN];
+		struct fst_iface *new_iface;
+		struct fst_iface *old_iface;
+		u32 llt_ms;
+		u8 pending_setup_req_dlgt;
+		u32 fsts_id; /* FSTS ID, see spec, 8.4.2.147
+			      * Session Transition element */
+	} data;
+	/* Session object internal fields which won't be zeroed on reset */
+	struct dl_list global_sessions_lentry;
+	u32 id; /* Session object ID used to identify
+		 * specific session object */
+	struct fst_group *group;
+	enum fst_session_state state;
+	bool stt_armed;
+};
+
+static struct dl_list global_sessions_list;
+static u32 global_session_id = 0;
+
+#define foreach_fst_session(s) \
+	dl_list_for_each((s), &global_sessions_list, \
+			 struct fst_session, global_sessions_lentry)
+
+#define foreach_fst_session_safe(s, temp) \
+	dl_list_for_each_safe((s), (temp), &global_sessions_list, \
+			      struct fst_session, global_sessions_lentry)
+
+
+static void fst_session_global_inc_id(void)
+{
+	global_session_id++;
+	if (global_session_id == FST_INVALID_SESSION_ID)
+		global_session_id++;
+}
+
+
+int fst_session_global_init(void)
+{
+	dl_list_init(&global_sessions_list);
+	return 0;
+}
+
+
+void fst_session_global_deinit(void)
+{
+	WPA_ASSERT(dl_list_empty(&global_sessions_list));
+}
+
+
+static inline void fst_session_notify_ctrl(struct fst_session *s,
+					   enum fst_event_type event_type,
+					   union fst_event_extra *extra)
+{
+	foreach_fst_ctrl_call(on_event, event_type, NULL, s, extra);
+}
+
+
+static void fst_session_set_state(struct fst_session *s,
+				  enum fst_session_state state,
+				  union fst_session_state_switch_extra *extra)
+{
+	if (s->state != state) {
+		union fst_event_extra evext = {
+			.session_state = {
+				.old_state = s->state,
+				.new_state = state,
+			},
+		};
+
+		if (extra)
+			evext.session_state.extra = *extra;
+		fst_session_notify_ctrl(s, EVENT_FST_SESSION_STATE_CHANGED,
+					&evext);
+		fst_printf_session(s, MSG_INFO, "State: %s => %s",
+				   fst_session_state_name(s->state),
+				   fst_session_state_name(state));
+		s->state = state;
+	}
+}
+
+
+static u32 fst_find_free_session_id(void)
+{
+	u32 i, id = FST_INVALID_SESSION_ID;
+	struct fst_session *s;
+
+	for (i = 0; i < (u32) -1; i++) {
+		bool in_use = false;
+
+		foreach_fst_session(s) {
+			if (s->id == global_session_id) {
+				fst_session_global_inc_id();
+				in_use = true;
+				break;
+			}
+		}
+		if (!in_use) {
+			id = global_session_id;
+			fst_session_global_inc_id();
+			break;
+		}
+	}
+
+	return id;
+}
+
+
+static void fst_session_timeout_handler(void *eloop_data, void *user_ctx)
+{
+	struct fst_session *s = user_ctx;
+	union fst_session_state_switch_extra extra = {
+		.to_initial = {
+			.reason = REASON_STT,
+		},
+	};
+
+	fst_printf_session(s, MSG_WARNING, "Session State Timeout");
+	fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &extra);
+}
+
+
+static void fst_session_stt_arm(struct fst_session *s)
+{
+	/* Action frames sometimes get delayed. Use relaxed timeout (2*) */
+	eloop_register_timeout(0, 2 * TU_TO_US(FST_DEFAULT_SESSION_TIMEOUT_TU),
+			       fst_session_timeout_handler, NULL, s);
+	s->stt_armed = true;
+}
+
+
+static void fst_session_stt_disarm(struct fst_session *s)
+{
+	if (s->stt_armed) {
+		eloop_cancel_timeout(fst_session_timeout_handler, NULL, s);
+		s->stt_armed = false;
+	}
+}
+
+
+static bool fst_session_is_in_transition(struct fst_session *s)
+{
+	/* See spec, 10.32.2.2  Transitioning between states */
+	return s->stt_armed;
+}
+
+
+static int fst_session_is_in_progress(struct fst_session *s)
+{
+	return s->state != FST_SESSION_STATE_INITIAL;
+}
+
+
+static int fst_session_is_ready_pending(struct fst_session *s)
+{
+	return s->state == FST_SESSION_STATE_SETUP_COMPLETION &&
+		fst_session_is_in_transition(s);
+}
+
+
+static int fst_session_is_ready(struct fst_session *s)
+{
+	return s->state == FST_SESSION_STATE_SETUP_COMPLETION &&
+		!fst_session_is_in_transition(s);
+}
+
+
+static int fst_session_is_switch_requested(struct fst_session *s)
+{
+	return s->state == FST_SESSION_STATE_TRANSITION_DONE &&
+		fst_session_is_in_transition(s);
+}
+
+
+static struct fst_session *
+fst_find_session_in_progress(const u8 *peer_addr, struct fst_group *g)
+{
+	struct fst_session *s;
+
+	foreach_fst_session(s) {
+		if (s->group == g &&
+		    (os_memcmp(s->data.old_peer_addr, peer_addr,
+			       ETH_ALEN) == 0 ||
+		     os_memcmp(s->data.new_peer_addr, peer_addr,
+			       ETH_ALEN) == 0) &&
+		    fst_session_is_in_progress(s))
+			return s;
+	}
+
+	return NULL;
+}
+
+
+static void fst_session_reset_ex(struct fst_session *s, enum fst_reason reason)
+{
+	union fst_session_state_switch_extra evext = {
+		.to_initial = {
+			.reason = reason,
+		},
+	};
+
+	if (s->state == FST_SESSION_STATE_SETUP_COMPLETION ||
+	    s->state == FST_SESSION_STATE_TRANSITION_DONE)
+		fst_session_tear_down_setup(s);
+	fst_session_stt_disarm(s);
+	os_memset(&s->data, 0, sizeof(s->data));
+	fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+}
+
+
+static int fst_session_send_action(struct fst_session *s, bool old_iface,
+				   const void *payload, size_t size,
+				   const struct wpabuf *extra_buf)
+{
+	size_t len;
+	int res;
+	struct wpabuf *buf;
+	u8 action;
+	struct fst_iface *iface =
+		old_iface ? s->data.old_iface : s->data.new_iface;
+
+	WPA_ASSERT(payload != NULL);
+	WPA_ASSERT(size != 0);
+
+	action = *(const u8 *) payload;
+
+	WPA_ASSERT(action <= FST_ACTION_MAX_SUPPORTED);
+
+	if (!iface) {
+		fst_printf_session(s, MSG_ERROR,
+				   "no %s interface for FST Action '%s' sending",
+				   old_iface ? "old" : "new",
+				   fst_action_names[action]);
+		return -1;
+	}
+
+	len = sizeof(u8) /* category */ + size;
+	if (extra_buf)
+		len += wpabuf_size(extra_buf);
+
+	buf = wpabuf_alloc(len);
+	if (!buf) {
+		fst_printf_session(s, MSG_ERROR,
+				   "cannot allocate buffer of %zu bytes for FST Action '%s' sending",
+				   len, fst_action_names[action]);
+		return -1;
+	}
+
+	wpabuf_put_u8(buf, WLAN_ACTION_FST);
+	wpabuf_put_data(buf, payload, size);
+	if (extra_buf)
+		wpabuf_put_buf(buf, extra_buf);
+
+	res = fst_iface_send_action(iface,
+				    old_iface ? s->data.old_peer_addr :
+				    s->data.new_peer_addr, buf);
+	if (res < 0)
+		fst_printf_siface(s, iface, MSG_ERROR,
+				  "failed to send FST Action '%s'",
+				  fst_action_names[action]);
+	else
+		fst_printf_siface(s, iface, MSG_DEBUG, "FST Action '%s' sent",
+				  fst_action_names[action]);
+	wpabuf_free(buf);
+
+	return res;
+}
+
+
+static int fst_session_send_tear_down(struct fst_session *s)
+{
+	struct fst_tear_down td;
+	int res;
+
+	if (!fst_session_is_in_progress(s)) {
+		fst_printf_session(s, MSG_ERROR, "No FST setup to tear down");
+		return -1;
+	}
+
+	WPA_ASSERT(s->data.old_iface != NULL);
+	WPA_ASSERT(s->data.new_iface != NULL);
+
+	os_memset(&td, 0, sizeof(td));
+
+	td.action = FST_ACTION_TEAR_DOWN;
+	td.fsts_id = host_to_le32(s->data.fsts_id);
+
+	res = fst_session_send_action(s, true, &td, sizeof(td), NULL);
+	if (!res)
+		fst_printf_sframe(s, true, MSG_INFO, "FST TearDown sent");
+	else
+		fst_printf_sframe(s, true, MSG_ERROR,
+				  "failed to send FST TearDown");
+
+	return res;
+}
+
+
+static void fst_session_handle_setup_request(struct fst_iface *iface,
+					     const struct ieee80211_mgmt *mgmt,
+					     size_t frame_len)
+{
+	struct fst_session *s;
+	const struct fst_setup_req *req;
+	struct fst_iface *new_iface = NULL;
+	struct fst_group *g;
+	u8 new_iface_peer_addr[ETH_ALEN];
+	size_t plen;
+
+	if (frame_len < IEEE80211_HDRLEN + 1 + sizeof(*req))  {
+		fst_printf_iface(iface, MSG_WARNING,
+				 "FST Request dropped: too short (%zu < %zu)",
+				 frame_len,
+				 IEEE80211_HDRLEN + 1 + sizeof(*req));
+		return;
+	}
+	plen = frame_len - IEEE80211_HDRLEN - 1;
+	req = (const struct fst_setup_req *)
+		(((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
+	if (req->stie.element_id != WLAN_EID_SESSION_TRANSITION ||
+	    req->stie.length < 11) {
+		fst_printf_iface(iface, MSG_WARNING,
+				 "FST Request dropped: invalid STIE");
+		return;
+	}
+
+	if (req->stie.new_band_id == req->stie.old_band_id) {
+		fst_printf_iface(iface, MSG_WARNING,
+				 "FST Request dropped: new and old band IDs are the same");
+		return;
+	}
+
+	g = fst_iface_get_group(iface);
+
+	if (plen > sizeof(*req)) {
+		fst_iface_update_mb_ie(iface, mgmt->sa, (const u8 *) (req + 1),
+				       plen - sizeof(*req));
+		fst_printf_iface(iface, MSG_INFO,
+				 "FST Request: MB IEs updated for " MACSTR,
+				 MAC2STR(mgmt->sa));
+	}
+
+	new_iface = fst_group_get_peer_other_connection(iface, mgmt->sa,
+							req->stie.new_band_id,
+							new_iface_peer_addr);
+	if (!new_iface) {
+		fst_printf_iface(iface, MSG_WARNING,
+				 "FST Request dropped: new iface not found");
+		return;
+	}
+	fst_printf_iface(iface, MSG_INFO,
+			 "FST Request: new iface (%s:" MACSTR ") found",
+			 fst_iface_get_name(new_iface),
+			 MAC2STR(new_iface_peer_addr));
+
+	s = fst_find_session_in_progress(mgmt->sa, g);
+	if (s) {
+		union fst_session_state_switch_extra evext = {
+			.to_initial = {
+				.reason = REASON_SETUP,
+			},
+		};
+
+		/*
+		 * 10.32.2.2  Transitioning between states:
+		 * Upon receipt of an FST Setup Request frame, the responder
+		 * shall respond with an FST Setup Response frame unless it has
+		 * a pending FST Setup Request frame addressed to the initiator
+		 * and the responder has a numerically larger MAC address than
+		 * the initiator’s MAC address, in which case, the responder
+		 * shall delete the received FST Setup Request.
+		 */
+		if (fst_session_is_ready_pending(s) &&
+		    /* waiting for Setup Response */
+		    os_memcmp(mgmt->da, mgmt->sa, ETH_ALEN) > 0) {
+			fst_printf_session(s, MSG_WARNING,
+					   "FST Request dropped due to MAC comparison (our MAC is "
+					   MACSTR ")",
+					   MAC2STR(mgmt->da));
+			return;
+		}
+
+		/*
+		 * State is SETUP_COMPLETION (either in transition or not) or
+		 * TRANSITION_DONE (in transition).
+		 * Setup Request arriving in this state could mean:
+		 * 1. peer sent it before receiving our Setup Request (race
+		 *    condition)
+		 * 2. peer didn't receive our Setup Response. Peer is retrying
+		 *    after STT timeout
+		 * 3. peer's FST state machines are out of sync due to some
+		 *    other reason
+		 *
+		 * We will reset our session and create a new one instead.
+		 */
+
+		fst_printf_session(s, MSG_WARNING,
+			"resetting due to FST request");
+
+		/*
+		 * If FST Setup Request arrived with the same FSTS ID as one we
+		 * initialized before, there's no need to tear down the session.
+		 * Moreover, as FSTS ID is the same, the other side will
+		 * associate this tear down with the session it initiated that
+		 * will break the sync.
+		 */
+		if (le_to_host32(req->stie.fsts_id) != s->data.fsts_id)
+			fst_session_send_tear_down(s);
+		else
+			fst_printf_session(s, MSG_WARNING,
+					   "Skipping TearDown as the FST request has the same FSTS ID as initiated");
+		fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+		fst_session_stt_disarm(s);
+	}
+
+	s = fst_session_create(g);
+	if (!s) {
+		fst_printf(MSG_WARNING,
+			   "FST Request dropped: cannot create session for %s and %s",
+			   fst_iface_get_name(iface),
+			   fst_iface_get_name(new_iface));
+		return;
+	}
+
+	fst_session_set_iface(s, iface, true);
+	fst_session_set_peer_addr(s, mgmt->sa, true);
+	fst_session_set_iface(s, new_iface, false);
+	fst_session_set_peer_addr(s, new_iface_peer_addr, false);
+	fst_session_set_llt(s, FST_LLT_VAL_TO_MS(le_to_host32(req->llt)));
+	s->data.pending_setup_req_dlgt = req->dialog_token;
+	s->data.fsts_id = le_to_host32(req->stie.fsts_id);
+
+	fst_session_stt_arm(s);
+
+	fst_session_notify_ctrl(s, EVENT_FST_SETUP, NULL);
+
+	fst_session_set_state(s, FST_SESSION_STATE_SETUP_COMPLETION, NULL);
+}
+
+
+static void fst_session_handle_setup_response(struct fst_session *s,
+					      struct fst_iface *iface,
+					      const struct ieee80211_mgmt *mgmt,
+					      size_t frame_len)
+{
+	const struct fst_setup_res *res;
+	size_t plen = frame_len - IEEE80211_HDRLEN - 1;
+	enum hostapd_hw_mode hw_mode;
+	u8 channel;
+	union fst_session_state_switch_extra evext = {
+		.to_initial = {
+			.reject_code = 0,
+		},
+	};
+
+	if (iface != s->data.old_iface) {
+		fst_printf_session(s, MSG_WARNING,
+				   "FST Response dropped: %s is not the old iface",
+				   fst_iface_get_name(iface));
+		return;
+	}
+
+	if (!fst_session_is_ready_pending(s)) {
+		fst_printf_session(s, MSG_WARNING,
+				   "FST Response dropped due to wrong state: %s",
+				   fst_session_state_name(s->state));
+		return;
+	}
+
+	if (plen < sizeof(*res)) {
+		fst_printf_session(s, MSG_WARNING,
+				   "Too short FST Response dropped");
+		return;
+	}
+	res = (const struct fst_setup_res *)
+		(((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
+	if (res->stie.element_id != WLAN_EID_SESSION_TRANSITION ||
+	    res->stie.length < 11) {
+		fst_printf_iface(iface, MSG_WARNING,
+				 "FST Response dropped: invalid STIE");
+		return;
+	}
+
+	if (res->dialog_token != s->data.pending_setup_req_dlgt)  {
+		fst_printf_session(s, MSG_WARNING,
+				   "FST Response dropped due to wrong dialog token (%u != %u)",
+				   s->data.pending_setup_req_dlgt,
+				   res->dialog_token);
+		return;
+	}
+
+	if (res->status_code == WLAN_STATUS_SUCCESS &&
+	    le_to_host32(res->stie.fsts_id) != s->data.fsts_id) {
+		fst_printf_session(s, MSG_WARNING,
+				   "FST Response dropped due to wrong FST Session ID (%u)",
+				   le_to_host32(res->stie.fsts_id));
+		return;
+	}
+
+	fst_session_stt_disarm(s);
+
+	if (res->status_code != WLAN_STATUS_SUCCESS) {
+		/*
+		 * 10.32.2.2  Transitioning between states
+		 * The initiator shall set the STT to the value of the
+		 * FSTSessionTimeOut field at ... and at each ACK frame sent in
+		 * response to a received FST Setup Response with the Status
+		 * Code field equal to PENDING_ADMITTING_FST_SESSION or
+		 * PENDING_GAP_IN_BA_WINDOW.
+		 */
+		evext.to_initial.reason = REASON_REJECT;
+		evext.to_initial.reject_code = res->status_code;
+		evext.to_initial.initiator = FST_INITIATOR_REMOTE;
+		fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+		fst_printf_session(s, MSG_WARNING,
+				   "FST Setup rejected by remote side with status %u",
+				   res->status_code);
+		return;
+	}
+
+	fst_iface_get_channel_info(s->data.new_iface, &hw_mode, &channel);
+
+	if (fst_hw_mode_to_band(hw_mode) != res->stie.new_band_id) {
+		evext.to_initial.reason = REASON_ERROR_PARAMS;
+		fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+		fst_printf_session(s, MSG_WARNING,
+				   "invalid FST Setup parameters");
+		fst_session_tear_down_setup(s);
+		return;
+	}
+
+	fst_printf_session(s, MSG_INFO,
+			   "%s: FST Setup established for %s (llt=%u)",
+			   fst_iface_get_name(s->data.old_iface),
+			   fst_iface_get_name(s->data.new_iface),
+			   s->data.llt_ms);
+
+	fst_session_notify_ctrl(s, EVENT_FST_ESTABLISHED, NULL);
+
+	if (s->data.llt_ms == FST_LLT_SWITCH_IMMEDIATELY)
+		fst_session_initiate_switch(s);
+}
+
+
+static void fst_session_handle_tear_down(struct fst_session *s,
+					 struct fst_iface *iface,
+					 const struct ieee80211_mgmt *mgmt,
+					 size_t frame_len)
+{
+	const struct fst_tear_down *td;
+	size_t plen = frame_len - IEEE80211_HDRLEN - 1;
+	union fst_session_state_switch_extra evext = {
+		.to_initial = {
+			.reason = REASON_TEARDOWN,
+			.initiator = FST_INITIATOR_REMOTE,
+		},
+	};
+
+	if (plen < sizeof(*td)) {
+		fst_printf_session(s, MSG_WARNING,
+				   "Too short FST Tear Down dropped");
+		return;
+	}
+	td = (const struct fst_tear_down *)
+		(((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
+
+	if (le_to_host32(td->fsts_id) != s->data.fsts_id) {
+		fst_printf_siface(s, iface, MSG_WARNING,
+				  "tear down for wrong FST Setup ID (%u)",
+				  le_to_host32(td->fsts_id));
+		return;
+	}
+
+	fst_session_stt_disarm(s);
+
+	fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+}
+
+
+static void fst_session_handle_ack_request(struct fst_session *s,
+					   struct fst_iface *iface,
+					   const struct ieee80211_mgmt *mgmt,
+					   size_t frame_len)
+{
+	const struct fst_ack_req *req;
+	size_t plen = frame_len - IEEE80211_HDRLEN - 1;
+	struct fst_ack_res res;
+	union fst_session_state_switch_extra evext = {
+		.to_initial = {
+			.reason = REASON_SWITCH,
+			.initiator = FST_INITIATOR_REMOTE,
+		},
+	};
+
+	if (!fst_session_is_ready(s) && !fst_session_is_switch_requested(s)) {
+		fst_printf_siface(s, iface, MSG_ERROR,
+				  "cannot initiate switch due to wrong session state (%s)",
+				  fst_session_state_name(s->state));
+		return;
+	}
+
+	WPA_ASSERT(s->data.new_iface != NULL);
+
+	if (iface != s->data.new_iface) {
+		fst_printf_siface(s, iface, MSG_ERROR,
+				  "Ack received on wrong interface");
+		return;
+	}
+
+	if (plen < sizeof(*req)) {
+		fst_printf_session(s, MSG_WARNING,
+				   "Too short FST Ack Request dropped");
+		return;
+	}
+	req = (const struct fst_ack_req *)
+		(((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
+
+	if (le_to_host32(req->fsts_id) != s->data.fsts_id) {
+		fst_printf_siface(s, iface, MSG_WARNING,
+				  "Ack for wrong FST Setup ID (%u)",
+				  le_to_host32(req->fsts_id));
+		return;
+	}
+
+	os_memset(&res, 0, sizeof(res));
+
+	res.action = FST_ACTION_ACK_RESPONSE;
+	res.dialog_token = req->dialog_token;
+	res.fsts_id = req->fsts_id;
+
+	if (!fst_session_send_action(s, false, &res, sizeof(res), NULL)) {
+		fst_printf_sframe(s, false, MSG_INFO, "FST Ack Response sent");
+		fst_session_stt_disarm(s);
+		fst_session_set_state(s, FST_SESSION_STATE_TRANSITION_DONE,
+				      NULL);
+		fst_session_set_state(s, FST_SESSION_STATE_TRANSITION_CONFIRMED,
+				      NULL);
+		fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+	}
+}
+
+
+static void
+fst_session_handle_ack_response(struct fst_session *s,
+				struct fst_iface *iface,
+				const struct ieee80211_mgmt *mgmt,
+				size_t frame_len)
+{
+	const struct fst_ack_res *res;
+	size_t plen = frame_len - IEEE80211_HDRLEN - 1;
+	union fst_session_state_switch_extra evext = {
+		.to_initial = {
+			.reason = REASON_SWITCH,
+			.initiator = FST_INITIATOR_LOCAL,
+		},
+	};
+
+	if (!fst_session_is_switch_requested(s)) {
+		fst_printf_siface(s, iface, MSG_ERROR,
+				  "Ack Response in inappropriate session state (%s)",
+				  fst_session_state_name(s->state));
+		return;
+	}
+
+	WPA_ASSERT(s->data.new_iface != NULL);
+
+	if (iface != s->data.new_iface) {
+		fst_printf_siface(s, iface, MSG_ERROR,
+				  "Ack response received on wrong interface");
+		return;
+	}
+
+	if (plen < sizeof(*res)) {
+		fst_printf_session(s, MSG_WARNING,
+				   "Too short FST Ack Response dropped");
+		return;
+	}
+	res = (const struct fst_ack_res *)
+		(((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
+
+	if (le_to_host32(res->fsts_id) != s->data.fsts_id) {
+		fst_printf_siface(s, iface, MSG_ERROR,
+				  "Ack response for wrong FST Setup ID (%u)",
+				  le_to_host32(res->fsts_id));
+		return;
+	}
+
+	fst_session_set_state(s, FST_SESSION_STATE_TRANSITION_CONFIRMED, NULL);
+	fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+
+	fst_session_stt_disarm(s);
+}
+
+
+struct fst_session * fst_session_create(struct fst_group *g)
+{
+	struct fst_session *s;
+	u32 id;
+
+	id = fst_find_free_session_id();
+	if (id == FST_INVALID_SESSION_ID) {
+		fst_printf(MSG_ERROR, "Cannot assign new session ID");
+		return NULL;
+	}
+
+	s = os_zalloc(sizeof(*s));
+	if (!s) {
+		fst_printf(MSG_ERROR, "Cannot allocate new session object");
+		return NULL;
+	}
+
+	s->id = id;
+	s->group = g;
+	s->state = FST_SESSION_STATE_INITIAL;
+
+	s->data.llt_ms = FST_LLT_MS_DEFAULT;
+
+	fst_printf(MSG_INFO, "Session %u created", s->id);
+
+	dl_list_add_tail(&global_sessions_list, &s->global_sessions_lentry);
+
+	foreach_fst_ctrl_call(on_session_added, s);
+
+	return s;
+}
+
+
+void fst_session_set_iface(struct fst_session *s, struct fst_iface *iface,
+			   bool is_old)
+{
+	if (is_old)
+		s->data.old_iface = iface;
+	else
+		s->data.new_iface = iface;
+
+}
+
+
+void fst_session_set_llt(struct fst_session *s, u32 llt)
+{
+	s->data.llt_ms = llt;
+}
+
+
+void fst_session_set_peer_addr(struct fst_session *s, const u8 *addr,
+			       bool is_old)
+{
+	u8 *a = is_old ? s->data.old_peer_addr : s->data.new_peer_addr;
+
+	os_memcpy(a, addr, ETH_ALEN);
+}
+
+
+int fst_session_initiate_setup(struct fst_session *s)
+{
+	struct fst_setup_req req;
+	int res;
+	u32 fsts_id;
+	u8 dialog_token;
+	struct fst_session *_s;
+
+	if (fst_session_is_in_progress(s)) {
+		fst_printf_session(s, MSG_ERROR, "Session in progress");
+		return -EINVAL;
+	}
+
+	if (is_zero_ether_addr(s->data.old_peer_addr)) {
+		fst_printf_session(s, MSG_ERROR, "No old peer MAC address");
+		return -EINVAL;
+	}
+
+	if (is_zero_ether_addr(s->data.new_peer_addr)) {
+		fst_printf_session(s, MSG_ERROR, "No new peer MAC address");
+		return -EINVAL;
+	}
+
+	if (!s->data.old_iface) {
+		fst_printf_session(s, MSG_ERROR, "No old interface defined");
+		return -EINVAL;
+	}
+
+	if (!s->data.new_iface) {
+		fst_printf_session(s, MSG_ERROR, "No new interface defined");
+		return -EINVAL;
+	}
+
+	if (s->data.new_iface == s->data.old_iface) {
+		fst_printf_session(s, MSG_ERROR,
+				   "Same interface set as old and new");
+		return -EINVAL;
+	}
+
+	if (!fst_iface_is_connected(s->data.old_iface, s->data.old_peer_addr,
+				    false)) {
+		fst_printf_session(s, MSG_ERROR,
+				   "The preset old peer address is not connected");
+		return -EINVAL;
+	}
+
+	if (!fst_iface_is_connected(s->data.new_iface, s->data.new_peer_addr,
+				    false)) {
+		fst_printf_session(s, MSG_ERROR,
+				   "The preset new peer address is not connected");
+		return -EINVAL;
+	}
+
+	_s = fst_find_session_in_progress(s->data.old_peer_addr, s->group);
+	if (_s) {
+		fst_printf_session(s, MSG_ERROR,
+				   "There is another session in progress (old): %u",
+				   _s->id);
+		return -EINVAL;
+	}
+
+	_s = fst_find_session_in_progress(s->data.new_peer_addr, s->group);
+	if (_s) {
+		fst_printf_session(s, MSG_ERROR,
+				   "There is another session in progress (new): %u",
+				   _s->id);
+		return -EINVAL;
+	}
+
+	dialog_token = fst_group_assign_dialog_token(s->group);
+	fsts_id = fst_group_assign_fsts_id(s->group);
+
+	os_memset(&req, 0, sizeof(req));
+
+	fst_printf_siface(s, s->data.old_iface, MSG_INFO,
+		"initiating FST setup for %s (llt=%u ms)",
+		fst_iface_get_name(s->data.new_iface), s->data.llt_ms);
+
+	req.action = FST_ACTION_SETUP_REQUEST;
+	req.dialog_token = dialog_token;
+	req.llt = host_to_le32(FST_LLT_MS_TO_VAL(s->data.llt_ms));
+	/* 8.4.2.147 Session Transition element */
+	req.stie.element_id = WLAN_EID_SESSION_TRANSITION;
+	req.stie.length = sizeof(req.stie) - 2;
+	req.stie.fsts_id = host_to_le32(fsts_id);
+	req.stie.session_control = SESSION_CONTROL(SESSION_TYPE_BSS, 0);
+
+	req.stie.new_band_id = fst_iface_get_band_id(s->data.new_iface);
+	req.stie.new_band_op = 1;
+	req.stie.new_band_setup = 0;
+
+	req.stie.old_band_id = fst_iface_get_band_id(s->data.old_iface);
+	req.stie.old_band_op = 1;
+	req.stie.old_band_setup = 0;
+
+	res = fst_session_send_action(s, true, &req, sizeof(req),
+				      fst_iface_get_mbie(s->data.old_iface));
+	if (!res) {
+		s->data.fsts_id = fsts_id;
+		s->data.pending_setup_req_dlgt = dialog_token;
+		fst_printf_sframe(s, true, MSG_INFO, "FST Setup Request sent");
+		fst_session_set_state(s, FST_SESSION_STATE_SETUP_COMPLETION,
+				      NULL);
+
+		fst_session_stt_arm(s);
+	}
+
+	return res;
+}
+
+
+int fst_session_respond(struct fst_session *s, u8 status_code)
+{
+	struct fst_setup_res res;
+	enum hostapd_hw_mode hw_mode;
+	u8 channel;
+
+	if (!fst_session_is_ready_pending(s)) {
+		fst_printf_session(s, MSG_ERROR, "incorrect state: %s",
+				   fst_session_state_name(s->state));
+		return -EINVAL;
+	}
+
+	if (is_zero_ether_addr(s->data.old_peer_addr)) {
+		fst_printf_session(s, MSG_ERROR, "No peer MAC address");
+		return -EINVAL;
+	}
+
+	if (!s->data.old_iface) {
+		fst_printf_session(s, MSG_ERROR, "No old interface defined");
+		return -EINVAL;
+	}
+
+	if (!s->data.new_iface) {
+		fst_printf_session(s, MSG_ERROR, "No new interface defined");
+		return -EINVAL;
+	}
+
+	if (s->data.new_iface == s->data.old_iface) {
+		fst_printf_session(s, MSG_ERROR,
+				   "Same interface set as old and new");
+		return -EINVAL;
+	}
+
+	if (!fst_iface_is_connected(s->data.old_iface,
+				    s->data.old_peer_addr, false)) {
+		fst_printf_session(s, MSG_ERROR,
+				   "The preset peer address is not in the peer list");
+		return -EINVAL;
+	}
+
+	fst_session_stt_disarm(s);
+
+	os_memset(&res, 0, sizeof(res));
+
+	res.action = FST_ACTION_SETUP_RESPONSE;
+	res.dialog_token = s->data.pending_setup_req_dlgt;
+	res.status_code = status_code;
+
+	res.stie.element_id = WLAN_EID_SESSION_TRANSITION;
+	res.stie.length = sizeof(res.stie) - 2;
+
+	if (status_code == WLAN_STATUS_SUCCESS) {
+		res.stie.fsts_id = host_to_le32(s->data.fsts_id);
+		res.stie.session_control = SESSION_CONTROL(SESSION_TYPE_BSS, 0);
+
+		fst_iface_get_channel_info(s->data.new_iface, &hw_mode,
+					   &channel);
+		res.stie.new_band_id = fst_hw_mode_to_band(hw_mode);
+		res.stie.new_band_op = 1;
+		res.stie.new_band_setup = 0;
+
+		fst_iface_get_channel_info(s->data.old_iface, &hw_mode,
+					   &channel);
+		res.stie.old_band_id = fst_hw_mode_to_band(hw_mode);
+		res.stie.old_band_op = 1;
+		res.stie.old_band_setup = 0;
+
+		fst_printf_session(s, MSG_INFO,
+				   "%s: FST Setup Request accepted for %s (llt=%u)",
+				   fst_iface_get_name(s->data.old_iface),
+				   fst_iface_get_name(s->data.new_iface),
+				   s->data.llt_ms);
+	} else {
+		fst_printf_session(s, MSG_WARNING,
+				   "%s: FST Setup Request rejected with code %d",
+				   fst_iface_get_name(s->data.old_iface),
+				   status_code);
+	}
+
+	if (fst_session_send_action(s, true, &res, sizeof(res),
+				    fst_iface_get_mbie(s->data.old_iface))) {
+		fst_printf_sframe(s, true, MSG_ERROR,
+				  "cannot send FST Setup Response with code %d",
+				  status_code);
+		return -EINVAL;
+	}
+
+	fst_printf_sframe(s, true, MSG_INFO, "FST Setup Response sent");
+
+	if (status_code != WLAN_STATUS_SUCCESS) {
+		union fst_session_state_switch_extra evext = {
+			.to_initial = {
+				.reason = REASON_REJECT,
+				.reject_code = status_code,
+				.initiator = FST_INITIATOR_LOCAL,
+			},
+		};
+		fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+	}
+
+	return 0;
+}
+
+
+int fst_session_initiate_switch(struct fst_session *s)
+{
+	struct fst_ack_req req;
+	int res;
+	u8 dialog_token;
+
+	if (!fst_session_is_ready(s)) {
+		fst_printf_session(s, MSG_ERROR,
+				   "cannot initiate switch due to wrong setup state (%d)",
+				   s->state);
+		return -1;
+	}
+
+	dialog_token = fst_group_assign_dialog_token(s->group);
+
+	WPA_ASSERT(s->data.new_iface != NULL);
+	WPA_ASSERT(s->data.old_iface != NULL);
+
+	fst_printf_session(s, MSG_INFO, "initiating FST switch: %s => %s",
+			   fst_iface_get_name(s->data.old_iface),
+			   fst_iface_get_name(s->data.new_iface));
+
+	os_memset(&req, 0, sizeof(req));
+
+	req.action = FST_ACTION_ACK_REQUEST;
+	req.dialog_token = dialog_token;
+	req.fsts_id = host_to_le32(s->data.fsts_id);
+
+	res = fst_session_send_action(s, false, &req, sizeof(req), NULL);
+	if (!res) {
+		fst_printf_sframe(s, false, MSG_INFO, "FST Ack Request sent");
+		fst_session_set_state(s, FST_SESSION_STATE_TRANSITION_DONE,
+				      NULL);
+		fst_session_stt_arm(s);
+	} else {
+		fst_printf_sframe(s, false, MSG_ERROR,
+				  "Cannot send FST Ack Request");
+	}
+
+	return res;
+}
+
+
+void fst_session_handle_action(struct fst_session *s,
+			       struct fst_iface *iface,
+			       const struct ieee80211_mgmt *mgmt,
+			       size_t frame_len)
+{
+	switch (mgmt->u.action.u.fst_action.action) {
+	case FST_ACTION_SETUP_REQUEST:
+		WPA_ASSERT(0);
+		break;
+	case FST_ACTION_SETUP_RESPONSE:
+		fst_session_handle_setup_response(s, iface, mgmt, frame_len);
+		break;
+	case FST_ACTION_TEAR_DOWN:
+		fst_session_handle_tear_down(s, iface, mgmt, frame_len);
+		break;
+	case FST_ACTION_ACK_REQUEST:
+		fst_session_handle_ack_request(s, iface, mgmt, frame_len);
+		break;
+	case FST_ACTION_ACK_RESPONSE:
+		fst_session_handle_ack_response(s, iface, mgmt, frame_len);
+		break;
+	case FST_ACTION_ON_CHANNEL_TUNNEL:
+	default:
+		fst_printf_sframe(s, false, MSG_ERROR,
+				  "Unsupported FST Action frame");
+		break;
+	}
+}
+
+
+int fst_session_tear_down_setup(struct fst_session *s)
+{
+	int res;
+	union fst_session_state_switch_extra evext = {
+		.to_initial = {
+			.reason = REASON_TEARDOWN,
+			.initiator = FST_INITIATOR_LOCAL,
+		},
+	};
+
+	res = fst_session_send_tear_down(s);
+
+	fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+
+	return res;
+}
+
+
+void fst_session_reset(struct fst_session *s)
+{
+	fst_session_reset_ex(s, REASON_RESET);
+}
+
+
+void fst_session_delete(struct fst_session *s)
+{
+	fst_printf(MSG_INFO, "Session %u deleted", s->id);
+	dl_list_del(&s->global_sessions_lentry);
+	foreach_fst_ctrl_call(on_session_removed, s);
+	os_free(s);
+}
+
+
+struct fst_group * fst_session_get_group(struct fst_session *s)
+{
+	return s->group;
+}
+
+
+struct fst_iface * fst_session_get_iface(struct fst_session *s, bool is_old)
+{
+	return is_old ? s->data.old_iface : s->data.new_iface;
+}
+
+
+u32 fst_session_get_id(struct fst_session *s)
+{
+	return s->id;
+}
+
+
+const u8 * fst_session_get_peer_addr(struct fst_session *s, bool is_old)
+{
+	return is_old ? s->data.old_peer_addr : s->data.new_peer_addr;
+}
+
+
+u32 fst_session_get_llt(struct fst_session *s)
+{
+	return s->data.llt_ms;
+}
+
+
+enum fst_session_state fst_session_get_state(struct fst_session *s)
+{
+	return s->state;
+}
+
+
+struct fst_session * fst_session_get_by_id(u32 id)
+{
+	struct fst_session *s;
+
+	foreach_fst_session(s) {
+		if (id == s->id)
+			return s;
+	}
+
+	return NULL;
+}
+
+
+void fst_session_enum(struct fst_group *g, fst_session_enum_clb clb, void *ctx)
+{
+	struct fst_session *s;
+
+	foreach_fst_session(s) {
+		if (!g || s->group == g)
+			clb(s->group, s, ctx);
+	}
+}
+
+
+void fst_session_on_action_rx(struct fst_iface *iface,
+			      const struct ieee80211_mgmt *mgmt,
+			      size_t len)
+{
+	struct fst_session *s;
+
+	if (len < IEEE80211_HDRLEN + 2 ||
+	    mgmt->u.action.category != WLAN_ACTION_FST) {
+		fst_printf_iface(iface, MSG_ERROR,
+				 "invalid Action frame received");
+		return;
+	}
+
+	if (mgmt->u.action.u.fst_action.action <= FST_ACTION_MAX_SUPPORTED) {
+		fst_printf_iface(iface, MSG_DEBUG,
+				 "FST Action '%s' received!",
+				 fst_action_names[mgmt->u.action.u.fst_action.action]);
+	} else {
+		fst_printf_iface(iface, MSG_WARNING,
+				 "unknown FST Action (%u) received!",
+				 mgmt->u.action.u.fst_action.action);
+		return;
+	}
+
+	if (mgmt->u.action.u.fst_action.action == FST_ACTION_SETUP_REQUEST) {
+		fst_session_handle_setup_request(iface, mgmt, len);
+		return;
+	}
+
+	s = fst_find_session_in_progress(mgmt->sa, fst_iface_get_group(iface));
+	if (s) {
+		fst_session_handle_action(s, iface, mgmt, len);
+	} else {
+		fst_printf_iface(iface, MSG_WARNING,
+				 "FST Action '%s' dropped: no session in progress found",
+				 fst_action_names[mgmt->u.action.u.fst_action.action]);
+	}
+}
+
+
+int fst_session_set_str_ifname(struct fst_session *s, const char *ifname,
+			       bool is_old)
+{
+	struct fst_group *g = fst_session_get_group(s);
+	struct fst_iface *i;
+
+	i = fst_group_get_iface_by_name(g, ifname);
+	if (!i) {
+		fst_printf_session(s, MSG_WARNING,
+				   "Cannot set iface %s: no such iface within group '%s'",
+				   ifname, fst_group_get_id(g));
+		return -1;
+	}
+
+	fst_session_set_iface(s, i, is_old);
+
+	return 0;
+}
+
+
+int fst_session_set_str_peer_addr(struct fst_session *s, const char *mac,
+				  bool is_old)
+{
+	u8 peer_addr[ETH_ALEN];
+	int res = fst_read_peer_addr(mac, peer_addr);
+
+	if (res)
+		return res;
+
+	fst_session_set_peer_addr(s, peer_addr, is_old);
+
+	return 0;
+}
+
+
+int fst_session_set_str_llt(struct fst_session *s, const char *llt_str)
+{
+	char *endp;
+	long int llt = strtol(llt_str, &endp, 0);
+
+	if (*endp || llt < 0 || (unsigned long int) llt > FST_MAX_LLT_MS) {
+		fst_printf_session(s, MSG_WARNING,
+				   "Cannot set llt %s: Invalid llt value (1..%u expected)",
+				   llt_str, FST_MAX_LLT_MS);
+		return -1;
+	}
+	fst_session_set_llt(s, (u32) llt);
+
+	return 0;
+}
+
+
+void fst_session_global_on_iface_detached(struct fst_iface *iface)
+{
+	struct fst_session *s;
+
+	foreach_fst_session(s) {
+		if (fst_session_is_in_progress(s) &&
+		    (s->data.new_iface == iface ||
+		     s->data.old_iface == iface))
+			fst_session_reset_ex(s, REASON_DETACH_IFACE);
+	}
+}
+
+
+struct fst_session * fst_session_global_get_first_by_group(struct fst_group *g)
+{
+	struct fst_session *s;
+
+	foreach_fst_session(s) {
+		if (s->group == g)
+			return s;
+	}
+
+	return NULL;
+}
+
+
+#ifdef CONFIG_FST_TEST
+
+static int get_group_fill_session(struct fst_group **g, struct fst_session *s)
+{
+	const u8 *old_addr, *new_addr;
+	struct fst_get_peer_ctx *ctx;
+
+	os_memset(s, 0, sizeof(*s));
+	foreach_fst_group(*g) {
+		s->data.new_iface = fst_group_first_iface(*g);
+		if (s->data.new_iface)
+			break;
+	}
+	if (!s->data.new_iface)
+		return -EINVAL;
+
+	s->data.old_iface = dl_list_entry(s->data.new_iface->group_lentry.next,
+					  struct fst_iface, group_lentry);
+	if (!s->data.old_iface)
+		return -EINVAL;
+
+	old_addr = fst_iface_get_peer_first(s->data.old_iface, &ctx, true);
+	if (!old_addr)
+		return -EINVAL;
+
+	new_addr = fst_iface_get_peer_first(s->data.new_iface, &ctx, true);
+	if (!new_addr)
+		return -EINVAL;
+
+	os_memcpy(s->data.old_peer_addr, old_addr, ETH_ALEN);
+	os_memcpy(s->data.new_peer_addr, new_addr, ETH_ALEN);
+
+	return 0;
+}
+
+
+#define FST_MAX_COMMAND_WORD_NAME_LENGTH 16
+
+int fst_test_req_send_fst_request(const char *params)
+{
+	int fsts_id;
+	bool is_valid;
+	char *endp;
+	struct fst_setup_req req;
+	struct fst_session s;
+	struct fst_group *g;
+	enum hostapd_hw_mode hw_mode;
+	u8 channel;
+	char additional_param[FST_MAX_COMMAND_WORD_NAME_LENGTH];
+
+	if (params[0] != ' ')
+		return -EINVAL;
+	params++;
+	fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
+	if (!is_valid)
+		return -EINVAL;
+
+	if (get_group_fill_session(&g, &s))
+		return -EINVAL;
+
+	req.action = FST_ACTION_SETUP_REQUEST;
+	req.dialog_token = g->dialog_token;
+	req.llt = host_to_le32(FST_LLT_MS_DEFAULT);
+	/* 8.4.2.147 Session Transition element */
+	req.stie.element_id = WLAN_EID_SESSION_TRANSITION;
+	req.stie.length = sizeof(req.stie) - 2;
+	req.stie.fsts_id = host_to_le32(fsts_id);
+	req.stie.session_control = SESSION_CONTROL(SESSION_TYPE_BSS, 0);
+
+	fst_iface_get_channel_info(s.data.new_iface, &hw_mode, &channel);
+	req.stie.new_band_id = fst_hw_mode_to_band(hw_mode);
+	req.stie.new_band_op = 1;
+	req.stie.new_band_setup = 0;
+
+	fst_iface_get_channel_info(s.data.old_iface, &hw_mode, &channel);
+	req.stie.old_band_id = fst_hw_mode_to_band(hw_mode);
+	req.stie.old_band_op = 1;
+	req.stie.old_band_setup = 0;
+
+	if (!fst_read_next_text_param(endp, additional_param,
+				       sizeof(additional_param), &endp)) {
+		if (!os_strcasecmp(additional_param, FST_CTR_PVAL_BAD_NEW_BAND))
+			req.stie.new_band_id = req.stie.old_band_id;
+	}
+
+	return fst_session_send_action(&s, true, &req, sizeof(req),
+				       s.data.old_iface->mb_ie);
+}
+
+
+int fst_test_req_send_fst_response(const char *params)
+{
+	int fsts_id;
+	bool is_valid;
+	char *endp;
+	struct fst_setup_res res;
+	struct fst_session s;
+	struct fst_group *g;
+	enum hostapd_hw_mode hw_mode;
+	u8 status_code;
+	u8 channel;
+	char response[FST_MAX_COMMAND_WORD_NAME_LENGTH];
+	struct fst_session *_s;
+
+	if (params[0] != ' ')
+		return -EINVAL;
+	params++;
+	fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
+	if (!is_valid)
+		return -EINVAL;
+
+	if (get_group_fill_session(&g, &s))
+		return -EINVAL;
+
+	status_code = WLAN_STATUS_SUCCESS;
+	if (!fst_read_next_text_param(endp, response, sizeof(response),
+				      &endp)) {
+		if (!os_strcasecmp(response, FST_CS_PVAL_RESPONSE_REJECT))
+			status_code = WLAN_STATUS_PENDING_ADMITTING_FST_SESSION;
+	}
+
+	os_memset(&res, 0, sizeof(res));
+
+	res.action = FST_ACTION_SETUP_RESPONSE;
+	/*
+	 * If some session has just received an FST Setup Request, then
+	 * use the correct dialog token copied from this request.
+	 */
+	_s = fst_find_session_in_progress(fst_session_get_peer_addr(&s, true),
+					  g);
+	res.dialog_token = (_s && fst_session_is_ready_pending(_s)) ?
+		_s->data.pending_setup_req_dlgt : g->dialog_token;
+	res.status_code  = status_code;
+
+	res.stie.element_id = WLAN_EID_SESSION_TRANSITION;
+	res.stie.length = sizeof(res.stie) - 2;
+
+	if (res.status_code == WLAN_STATUS_SUCCESS) {
+		res.stie.fsts_id = host_to_le32(fsts_id);
+		res.stie.session_control = SESSION_CONTROL(SESSION_TYPE_BSS, 0);
+
+		fst_iface_get_channel_info(s.data.new_iface, &hw_mode,
+					    &channel);
+		res.stie.new_band_id = fst_hw_mode_to_band(hw_mode);
+		res.stie.new_band_op = 1;
+		res.stie.new_band_setup = 0;
+
+		fst_iface_get_channel_info(s.data.old_iface, &hw_mode,
+					   &channel);
+		res.stie.old_band_id = fst_hw_mode_to_band(hw_mode);
+		res.stie.old_band_op = 1;
+		res.stie.old_band_setup = 0;
+	}
+
+	if (!fst_read_next_text_param(endp, response, sizeof(response),
+				      &endp)) {
+		if (!os_strcasecmp(response, FST_CTR_PVAL_BAD_NEW_BAND))
+			res.stie.new_band_id = res.stie.old_band_id;
+	}
+
+	return fst_session_send_action(&s, true, &res, sizeof(res),
+				       s.data.old_iface->mb_ie);
+}
+
+
+int fst_test_req_send_ack_request(const char *params)
+{
+	int fsts_id;
+	bool is_valid;
+	char *endp;
+	struct fst_ack_req req;
+	struct fst_session s;
+	struct fst_group *g;
+
+	if (params[0] != ' ')
+		return -EINVAL;
+	params++;
+	fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
+	if (!is_valid)
+		return -EINVAL;
+
+	if (get_group_fill_session(&g, &s))
+		return -EINVAL;
+
+	os_memset(&req, 0, sizeof(req));
+	req.action = FST_ACTION_ACK_REQUEST;
+	req.dialog_token = g->dialog_token;
+	req.fsts_id = host_to_le32(fsts_id);
+
+	return fst_session_send_action(&s, false, &req, sizeof(req), NULL);
+}
+
+
+int fst_test_req_send_ack_response(const char *params)
+{
+	int fsts_id;
+	bool is_valid;
+	char *endp;
+	struct fst_ack_res res;
+	struct fst_session s;
+	struct fst_group *g;
+
+	if (params[0] != ' ')
+		return -EINVAL;
+	params++;
+	fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
+	if (!is_valid)
+		return -EINVAL;
+
+	if (get_group_fill_session(&g, &s))
+		return -EINVAL;
+
+	os_memset(&res, 0, sizeof(res));
+	res.action = FST_ACTION_ACK_RESPONSE;
+	res.dialog_token = g->dialog_token;
+	res.fsts_id = host_to_le32(fsts_id);
+
+	return fst_session_send_action(&s, false, &res, sizeof(res), NULL);
+}
+
+
+int fst_test_req_send_tear_down(const char *params)
+{
+	int fsts_id;
+	bool is_valid;
+	char *endp;
+	struct fst_tear_down td;
+	struct fst_session s;
+	struct fst_group *g;
+
+	if (params[0] != ' ')
+		return -EINVAL;
+	params++;
+	fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
+	if (!is_valid)
+		return -EINVAL;
+
+	if (get_group_fill_session(&g, &s))
+		return -EINVAL;
+
+	os_memset(&td, 0, sizeof(td));
+	td.action = FST_ACTION_TEAR_DOWN;
+	td.fsts_id = host_to_le32(fsts_id);
+
+	return fst_session_send_action(&s, true, &td, sizeof(td), NULL);
+}
+
+
+u32 fst_test_req_get_fsts_id(const char *params)
+{
+	int sid;
+	bool is_valid;
+	char *endp;
+	struct fst_session *s;
+
+	if (params[0] != ' ')
+		return FST_FSTS_ID_NOT_FOUND;
+	params++;
+	sid = fst_read_next_int_param(params, &is_valid, &endp);
+	if (!is_valid)
+		return FST_FSTS_ID_NOT_FOUND;
+
+	s = fst_session_get_by_id(sid);
+	if (!s)
+		return FST_FSTS_ID_NOT_FOUND;
+
+	return s->data.fsts_id;
+}
+
+
+int fst_test_req_get_local_mbies(const char *request, char *buf, size_t buflen)
+{
+	char *endp;
+	char ifname[FST_MAX_COMMAND_WORD_NAME_LENGTH];
+	struct fst_group *g;
+	struct fst_iface *iface;
+
+	if (request[0] != ' ')
+		return -EINVAL;
+	request++;
+	if (fst_read_next_text_param(request, ifname, sizeof(ifname), &endp) ||
+	    !*ifname)
+		goto problem;
+	g = dl_list_first(&fst_global_groups_list, struct fst_group,
+			  global_groups_lentry);
+	if (!g)
+		goto problem;
+	iface = fst_group_get_iface_by_name(g, ifname);
+	if (!iface || !iface->mb_ie)
+		goto problem;
+	return wpa_snprintf_hex(buf, buflen, wpabuf_head(iface->mb_ie),
+				wpabuf_len(iface->mb_ie));
+
+problem:
+	return os_snprintf(buf, buflen, "FAIL\n");
+}
+
+#endif /* CONFIG_FST_TEST */
diff --git a/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_session.h b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_session.h
new file mode 100644
index 0000000..e43d0ea
--- /dev/null
+++ b/package/kernel/asr-wl/asr-hostapd/asr-hostapd-2023-06-22/src/fst/fst_session.h
@@ -0,0 +1,80 @@
+/*
+ * FST module - FST Session related definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef FST_SESSION_H
+#define FST_SESSION_H
+
+#define FST_DEFAULT_SESSION_TIMEOUT_TU 255 /* u8 */
+
+struct fst_iface;
+struct fst_group;
+struct fst_session;
+enum fst_session_state;
+
+int  fst_session_global_init(void);
+void fst_session_global_deinit(void);
+void fst_session_global_on_iface_detached(struct fst_iface *iface);
+struct fst_session *
+fst_session_global_get_first_by_group(struct fst_group *g);
+
+struct fst_session * fst_session_create(struct fst_group *g);
+void fst_session_set_iface(struct fst_session *s, struct fst_iface *iface,
+			   bool is_old);
+void fst_session_set_llt(struct fst_session *s, u32 llt);
+void fst_session_set_peer_addr(struct fst_session *s, const u8 *addr,
+			       bool is_old);
+int fst_session_initiate_setup(struct fst_session *s);
+int fst_session_respond(struct fst_session *s, u8 status_code);
+int fst_session_initiate_switch(struct fst_session *s);
+void fst_session_handle_action(struct fst_session *s, struct fst_iface *iface,
+			       const struct ieee80211_mgmt *mgmt,
+			       size_t frame_len);
+int fst_session_tear_down_setup(struct fst_session *s);
+void fst_session_reset(struct fst_session *s);
+void fst_session_delete(struct fst_session *s);
+
+struct fst_group * fst_session_get_group(struct fst_session *s);
+struct fst_iface * fst_session_get_iface(struct fst_session *s, bool is_old);
+const u8 * fst_session_get_peer_addr(struct fst_session *s, bool is_old);
+u32 fst_session_get_id(struct fst_session *s);
+u32 fst_session_get_llt(struct fst_session *s);
+enum fst_session_state fst_session_get_state(struct fst_session *s);
+
+struct fst_session *fst_session_get_by_id(u32 id);
+
+typedef void (*fst_session_enum_clb)(struct fst_group *g, struct fst_session *s,
+				     void *ctx);
+
+void fst_session_enum(struct fst_group *g, fst_session_enum_clb clb, void *ctx);
+
+void fst_session_on_action_rx(struct fst_iface *iface,
+			      const struct ieee80211_mgmt *mgmt, size_t len);
+
+
+int fst_session_set_str_ifname(struct fst_session *s, const char *ifname,
+			       bool is_old);
+int fst_session_set_str_peer_addr(struct fst_session *s, const char *mac,
+				  bool is_old);
+int fst_session_set_str_llt(struct fst_session *s, const char *llt_str);
+
+#ifdef CONFIG_FST_TEST
+
+#define FST_FSTS_ID_NOT_FOUND ((u32) -1)
+
+int fst_test_req_send_fst_request(const char *params);
+int fst_test_req_send_fst_response(const char *params);
+int fst_test_req_send_ack_request(const char *params);
+int fst_test_req_send_ack_response(const char *params);
+int fst_test_req_send_tear_down(const char *params);
+u32 fst_test_req_get_fsts_id(const char *params);
+int fst_test_req_get_local_mbies(const char *request, char *buf,
+				 size_t buflen);
+
+#endif /* CONFIG_FST_TEST */
+
+#endif /* FST_SESSION_H */