Merge "qca-wifi: Multi-Link Repeater Functionality"
diff --git a/qca_multi_link/inc/qca_multi_link.h b/qca_multi_link/inc/qca_multi_link.h
new file mode 100644
index 0000000..b997b32
--- /dev/null
+++ b/qca_multi_link/inc/qca_multi_link.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __qca_multi_link_H_
+#define __qca_multi_link_H_
+
+#include <qdf_nbuf.h>
+#include <qdf_module.h>
+#include <qdf_list.h>
+#include <qdf_util.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/wireless.h>
+#include <net/cfg80211.h>
+#include <br_private.h>
+
+#include <ieee80211.h>
+#include <ieee80211_defines.h>
+#include <qca_multi_link_tbl.h>
+#include "if_upperproto.h"
+
+
+#define QCA_MULTI_LINK_FAST_LANE_LIST_SIZE 6
+#define QCA_MULTI_LINK_NO_BACKHAUL_LIST_SIZE 32
+
+/**
+ * qca_multi_link_needs_enq - rptr return status
+ * @QCA_MULTI_LINK_ALLOW_VAP_ENQ: Allow the packet to be enqueued
+ * @QCA_MULTI_LINK_SKIP_VAP_ENQ: SKIP the enqueue
+ */
+typedef enum qca_multi_link_needs_enq {
+ QCA_MULTI_LINK_ALLOW_VAP_ENQ = 0,
+ QCA_MULTI_LINK_SKIP_VAP_ENQ,
+} qca_multi_link_needs_enq_t;
+
+/**
+ * enum qca_multi_link_status - rptr return status
+ * @qca_multi_link_PKT_ALLOW: Allow the packet to be received or transmitted
+ * @qca_multi_link_PKT_DROP: Drop the packet
+ * @qca_multi_link_PKT_CONSUMED: The packet is consumed in the repeater processing
+ */
+typedef enum qca_multi_link_status {
+ QCA_MULTI_LINK_PKT_NONE = 0,
+ QCA_MULTI_LINK_PKT_ALLOW,
+ QCA_MULTI_LINK_PKT_DROP,
+ QCA_MULTI_LINK_PKT_CONSUMED
+} qca_multi_link_status_t;
+
+/**
+ * struct qca_multi_link_list_node - rptr list node
+ * @list: linked list node
+ * @wiphy: wiphy pointer
+ */
+struct qca_multi_link_list_node {
+ qdf_list_node_t node;
+ struct wiphy *wiphy;
+};
+
+typedef struct qca_multi_link_parameters {
+ bool rptr_processing_enable;
+ bool loop_detected;
+ bool always_primary;
+ bool force_client_mcast_traffic;
+ bool drop_secondary_mcast;
+ struct wiphy *primary_wiphy;
+ uint8_t total_stavaps_up;
+ qdf_list_t no_backhaul_list;
+ qdf_list_t fast_lane_list;
+} qca_multi_link_parameters_t;
+
+void qca_multi_link_init_module(void);
+void qca_multi_link_deinit_module(void);
+uint8_t qca_multi_link_get_num_sta(void);
+void qca_multi_link_append_num_sta(bool inc_or_dec);
+bool qca_multi_link_is_dbdc_processing_reqd(struct net_device *net_dev);
+void qca_multi_link_set_drop_sec_mcast(bool val);
+void qca_multi_link_set_force_client_mcast(bool val);
+void qca_multi_link_set_always_primary(bool val);
+void qca_multi_link_set_dbdc_enable(bool val);
+struct wiphy *qca_multi_link_get_primary_radio(void);
+void qca_multi_link_set_primary_radio(struct wiphy *primary_wiphy);
+bool qca_multi_link_add_fastlane_radio(struct wiphy *fl_wiphy);
+bool qca_multi_link_remove_fastlane_radio(struct wiphy *fl_wiphy);
+bool qca_multi_link_add_no_backhaul_radio(struct wiphy *no_bl_wiphy);
+bool qca_multi_link_remove_no_backhaul_radio(struct wiphy *no_bl_wiphy);
+bool qca_multi_link_ap_rx(struct net_device *net_dev, qdf_nbuf_t nbuf);
+bool qca_multi_link_sta_rx(struct net_device *net_dev, qdf_nbuf_t nbuf,
+ qca_multi_link_needs_enq_t allow_vap_enq);
+bool qca_multi_link_sta_tx(struct net_device *net_dev, qdf_nbuf_t nbuf);
+#endif
diff --git a/qca_multi_link/inc/qca_multi_link_tbl.h b/qca_multi_link/inc/qca_multi_link_tbl.h
new file mode 100644
index 0000000..4893ec2
--- /dev/null
+++ b/qca_multi_link/inc/qca_multi_link_tbl.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * DOC: qca_multi_link_tbl (QAL Bridge)
+ * QCA driver framework for OS notifier handlers
+ */
+
+#ifndef __qca_multi_link_tbl_H
+#define __qca_multi_link_tbl_H
+
+#include "qdf_types.h"
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/wireless.h>
+#include <net/cfg80211.h>
+#include <br_private.h>
+
+typedef struct qca_multi_link_tbl_entry {
+ struct wireless_dev *qal_fdb_ieee80211_ptr;
+ struct net_device *qal_fdb_dev;
+ uint8_t qal_fdb_is_local;
+ unsigned char qal_mac_addr[6];
+} qca_multi_link_tbl_entry_t;
+
+/**
+ *
+ * qca_multi_link_tbl_get_eth_entries() - Get ethernet bridge entries
+ *
+ * To be called from the code with a valid netdevice
+ *
+ * Return: Number of entries copied
+ */
+int qca_multi_link_tbl_get_eth_entries(struct net_device *net_dev,
+ void *fill_buff, int buff_size);
+
+/**
+ *
+ * qca_multi_link_tbl_find_sta_or_ap() - Get the AP or Station
+ * from bridge on the same radio
+ *
+ * To be called from the code with a valid netdevice
+ *
+ * Return: struct net_device
+ */
+struct net_device *qca_multi_link_tbl_find_sta_or_ap(struct net_device *net_dev, uint8_t dev_type);
+
+/**
+ *
+ * qca_multi_link_tbl_delete_entry() - Delete a non-local bridge fdb entry
+ *
+ * To be called from the code with a valid netdevice
+ *
+ * Return: QDF_STATUS
+ */
+QDF_STATUS qca_multi_link_tbl_delete_entry(struct net_device *net_dev, uint8_t *addr);
+
+/**
+ *
+ * qca_multi_link_tbl_has_entry() - check if there is fdb entry for the mac-address
+ *
+ * To be called from the code with a valid qal_entry pointer
+ *
+ * Return: QDF_STATUS
+ */
+QDF_STATUS qca_multi_link_tbl_has_entry(struct net_device *net_dev,
+ const char *addr, uint16_t vlan_id,
+ qca_multi_link_tbl_entry_t *qca_ml_entry);
+/**
+ *
+ * qca_multi_link_tbl_register_update_notifier() - register to br_fdb update notifier chain
+ *
+ * To be called from each callback with its own handle
+ *
+ * Return: None
+ */
+QDF_STATUS qca_multi_link_tbl_register_update_notifier(void *nb);
+
+/**
+ * qca_multi_link_tbl_unregister_update_notifier() - unregister linux br_fdb update notifier chain
+ *
+ * To be called from each callback with its own handle
+ *
+ * Return: None
+ */
+QDF_STATUS qca_multi_link_tbl_unregister_update_notifier(void *nb);
+
+/**
+ *
+ * qca_multi_link_tbl_register_notifier() - register to br_fdb notifier chain
+ *
+ * To be called from each callback with its own handle
+ *
+ * Return: None
+ */
+QDF_STATUS qca_multi_link_tbl_register_notifier(void *nb);
+
+/**
+ * qca_multi_link_tbl_unregister_notifier() - unregister linux br_fdb notifier chain
+ *
+ * To be called from each callback with its own handle
+ *
+ * Return: None
+ */
+QDF_STATUS qca_multi_link_tbl_unregister_notifier(void *nb);
+
+/**
+ *
+ * qca_multi_link_tbl_get_bridge_dev() - Get bridge netdevice from port
+ *
+ * To be called from the code with a valid port netdevice
+ *
+ * Return: Bridge netdevice
+ */
+struct net_device *qca_multi_link_tbl_get_bridge_dev(struct net_device *port_dev);
+#endif /* __qca_multi_link_tbl_H */
diff --git a/qca_multi_link/src/qca_multi_link.c b/qca_multi_link/src/qca_multi_link.c
new file mode 100644
index 0000000..003d58a
--- /dev/null
+++ b/qca_multi_link/src/qca_multi_link.c
@@ -0,0 +1,1219 @@
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "qca_multi_link.h"
+
+static bool is_initialized;
+qca_multi_link_parameters_t qca_multi_link_cfg;
+
+static inline bool is_fast_lane_radio(struct wiphy *fl_wiphy)
+{
+ qdf_list_node_t *node = NULL, *next_node = NULL;
+
+ if (!fl_wiphy) {
+ return false;
+ }
+
+ if (qdf_list_empty(&qca_multi_link_cfg.fast_lane_list)) {
+ return false;
+ }
+
+ qdf_list_peek_front(&qca_multi_link_cfg.fast_lane_list,
+ (qdf_list_node_t **)&next_node);
+ while (next_node) {
+ struct qca_multi_link_list_node *fast_lane_node
+ = (struct qca_multi_link_list_node *)next_node;
+ if (fast_lane_node->wiphy == fl_wiphy) {
+ return true;
+ } else {
+ node = next_node;
+ next_node = NULL;
+ if ((qdf_list_peek_next(&qca_multi_link_cfg.fast_lane_list, node, &next_node))
+ != QDF_STATUS_SUCCESS) {
+ return false;
+ }
+ }
+ }
+ return false;
+}
+
+static inline bool is_no_backhaul_radio(struct wiphy *no_bl_wiphy)
+{
+ qdf_list_node_t *node = NULL, *next_node = NULL;
+
+ if (!no_bl_wiphy) {
+ return false;
+ }
+
+ if (qdf_list_empty(&qca_multi_link_cfg.no_backhaul_list)) {
+ return false;
+ }
+
+ qdf_list_peek_front(&qca_multi_link_cfg.no_backhaul_list,
+ (qdf_list_node_t **)&next_node);
+ while (next_node) {
+ struct qca_multi_link_list_node *no_bl_node
+ = (struct qca_multi_link_list_node *)next_node;
+ if (no_bl_node->wiphy == no_bl_wiphy) {
+ return true;
+ } else {
+ node = next_node;
+ next_node = NULL;
+ if ((qdf_list_peek_next(&qca_multi_link_cfg.no_backhaul_list, node, &next_node))
+ != QDF_STATUS_SUCCESS) {
+ return false;
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * qca_multi_link_is_primary_radio() - Check if this is a primary radio
+ *
+ * Return: true: if it primary radio
+ * false: if it is secondary radio
+ */
+static inline bool qca_multi_link_is_primary_radio(struct wiphy *dev_wiphy)
+{
+ bool is_primary = false;
+
+ if (!qca_multi_link_cfg.primary_wiphy || !dev_wiphy) {
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG,
+ FL("\nprimary_wiphy is NULL\n"));
+ is_primary = false;
+ } else {
+ is_primary = (dev_wiphy == qca_multi_link_cfg.primary_wiphy)?true:false;
+ }
+ return is_primary;
+}
+
+/**
+ * qca_multi_link_need_procesing() - Check if repeater processing is required
+ *
+ * Return: true: processing is required
+ * false: processing is not required
+ */
+static inline bool qca_multi_link_need_procesing(void)
+{
+ if ((!qca_multi_link_cfg.rptr_processing_enable)
+ || (qca_multi_link_cfg.total_stavaps_up < 2)) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * qca_multi_link_pktfrom_ownsrc() - Check if packet is from same device
+ *
+ * Return: true: packet is from same device
+ * false: packet is not from same device
+ */
+static inline bool qca_multi_link_pktfrom_ownsrc(struct net_device *net_dev, qdf_nbuf_t nbuf)
+{
+ qdf_ether_header_t *eh = (qdf_ether_header_t *) qdf_nbuf_data(nbuf);
+
+ if (qdf_is_macaddr_equal((struct qdf_mac_addr *)net_dev->dev_addr,
+ (struct qdf_mac_addr *)eh->ether_shost)) {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * qca_multi_link_drop_secondary_mcast() - Check if mcast to be dropped on secondary
+ *
+ * Return: true: Drop the packet
+ * false: Do not drop
+ */
+static inline bool qca_multi_link_drop_secondary_mcast(qdf_nbuf_t nbuf)
+{
+ qdf_ether_header_t *eh = (qdf_ether_header_t *) qdf_nbuf_data(nbuf);
+ uint8_t is_mcast = IEEE80211_IS_MULTICAST(eh->ether_dhost);
+
+ if (is_mcast && qca_multi_link_cfg.drop_secondary_mcast) {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * qca_multi_link_drop_always_primary() - Check if packet to be dropped for always_primary
+ *
+ * Return: true: Drop the packet
+ * false: Do not drop
+ */
+static inline bool qca_multi_link_drop_always_primary(bool is_primary, qdf_nbuf_t nbuf)
+{
+ qdf_ether_header_t *eh = (qdf_ether_header_t *) qdf_nbuf_data(nbuf);
+
+ if (qca_multi_link_cfg.always_primary) {
+ if (is_primary) {
+ return false;
+ } else {
+ if (eh->ether_type != qdf_htons(ETHERTYPE_PAE)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * qca_multi_link_deinit_module() - De-initialize the repeater base structute
+ * Return: void
+ */
+void qca_multi_link_deinit_module(void)
+{
+ if (!is_initialized)
+ return;
+
+ qca_multi_link_cfg.total_stavaps_up = 0;
+ qca_multi_link_cfg.loop_detected = 0;
+ qca_multi_link_cfg.primary_wiphy = NULL;
+ qdf_list_destroy(&qca_multi_link_cfg.fast_lane_list);
+ qdf_list_destroy(&qca_multi_link_cfg.no_backhaul_list);
+ is_initialized = false;
+
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_INFO,
+ FL("\n******QCA RPtr De-Init Done***********\n"));
+}
+
+qdf_export_symbol(qca_multi_link_deinit_module);
+
+/**
+ * qca_multi_link_init_module() - Initialize the repeater base structute
+ *
+ * Return: void
+ */
+void qca_multi_link_init_module(void)
+{
+ if (is_initialized)
+ return;
+
+ is_initialized = true;
+ qca_multi_link_cfg.total_stavaps_up = 0;
+ qca_multi_link_cfg.loop_detected = 0;
+ qca_multi_link_cfg.primary_wiphy = NULL;
+ qdf_list_create(&qca_multi_link_cfg.fast_lane_list, QCA_MULTI_LINK_FAST_LANE_LIST_SIZE);
+ qdf_list_create(&qca_multi_link_cfg.no_backhaul_list, QCA_MULTI_LINK_NO_BACKHAUL_LIST_SIZE);
+
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_INFO,
+ FL("\n******QCA Repeater Initialization Done***********\n"));
+}
+
+qdf_export_symbol(qca_multi_link_init_module);
+
+/**
+ * qca_multi_link_get_num_sta() - Get the total number of sta vaps up.
+ *
+ * Return: int
+ */
+uint8_t qca_multi_link_get_num_sta(void)
+{
+ return qca_multi_link_cfg.total_stavaps_up;
+}
+
+qdf_export_symbol(qca_multi_link_get_num_sta);
+
+/**
+ * qca_multi_link_append_num_sta() - Append the total number of sta vaps up.
+ * @inc_or_dec: true to increment and false to decrement
+ *
+ * Return: void
+ */
+void qca_multi_link_append_num_sta(bool inc_or_dec)
+{
+ if (inc_or_dec) {
+ qca_multi_link_cfg.total_stavaps_up++;
+ if (qca_multi_link_cfg.total_stavaps_up > 1) {
+ qca_multi_link_set_drop_sec_mcast(true);
+ }
+ } else {
+ if (qca_multi_link_cfg.total_stavaps_up == 0) {
+ return;
+ }
+
+ qca_multi_link_cfg.total_stavaps_up--;
+ if (qca_multi_link_cfg.total_stavaps_up <= 1) {
+ qca_multi_link_cfg.loop_detected = 0;
+ }
+ }
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_NONE,
+ FL("\nStation vap number in Repeater is val=%d***********\n"),
+ qca_multi_link_cfg.total_stavaps_up);
+}
+
+qdf_export_symbol(qca_multi_link_append_num_sta);
+
+/**
+ * qca_multi_link_is_dbdc_processing_reqd() - Check if dbdc processing is required
+ * @net_dev: current net device
+ *
+ * Return: true: loop is detected
+ * false: loop not detected
+ */
+bool qca_multi_link_is_dbdc_processing_reqd(struct net_device *net_dev)
+{
+ if (qca_multi_link_cfg.total_stavaps_up > 2)
+ return (qca_multi_link_cfg.loop_detected && qca_multi_link_cfg.rptr_processing_enable);
+ else
+ return false;
+}
+
+qdf_export_symbol(qca_multi_link_is_dbdc_processing_reqd);
+
+/**
+ * qca_multi_link_set_drop_sec_mcast() - set the drop secondary mcast flag
+ * @val: boolean true or false
+ *
+ */
+void qca_multi_link_set_drop_sec_mcast(bool val)
+{
+ qca_multi_link_cfg.drop_secondary_mcast = val;
+}
+
+qdf_export_symbol(qca_multi_link_set_drop_sec_mcast);
+
+/**
+ * qca_multi_link_set_force_client_mcast() - set the flag to force client mcast traffic
+ * @val: boolean true or false
+ *
+ */
+void qca_multi_link_set_force_client_mcast(bool val)
+{
+ qca_multi_link_cfg.force_client_mcast_traffic = val;
+}
+
+qdf_export_symbol(qca_multi_link_set_force_client_mcast);
+
+/**
+ * qca_multi_link_set_always_primary() - set the flag for always primary flag
+ * @val: boolean true or false
+ *
+ */
+void qca_multi_link_set_always_primary(bool val)
+{
+ qca_multi_link_cfg.always_primary = val;
+}
+
+qdf_export_symbol(qca_multi_link_set_always_primary);
+
+/**
+ * qca_multi_link_set_dbdc_enable() - set the dbdc enable flag
+ * @val: boolean true or false
+ */
+void qca_multi_link_set_dbdc_enable(bool val)
+{
+ qca_multi_link_cfg.rptr_processing_enable = val;
+ if (!val) {
+ qca_multi_link_cfg.loop_detected = 0;
+ }
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG,
+ FL("\nSetting DBDC enable = val%d\n"), val);
+}
+
+qdf_export_symbol(qca_multi_link_set_dbdc_enable);
+
+/**
+ * qca_multi_link_get_primary_radio() - set the dbdc enable flag
+ * @primary_wiphy: wiphy pointer of primary radio device
+ */
+struct wiphy *qca_multi_link_get_primary_radio(void)
+{
+ return qca_multi_link_cfg.primary_wiphy;
+}
+
+qdf_export_symbol(qca_multi_link_get_primary_radio);
+
+/**
+ * qca_multi_link_set_primary_radio() - set the primary radio
+ * @primary_wiphy: wiphy pointer of primary radio device
+ */
+void qca_multi_link_set_primary_radio(struct wiphy *primary_wiphy)
+{
+ if (!primary_wiphy) {
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG,
+ FL("\nNull wiphy in Setting primary radio\n"));
+ return;
+ }
+ qca_multi_link_cfg.primary_wiphy = primary_wiphy;
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_INFO,
+ FL("\nSetting primary radio for wiphy%p\n"), primary_wiphy);
+}
+
+qdf_export_symbol(qca_multi_link_set_primary_radio);
+
+/**
+ * qca_multi_link_add_fastlane_radio() - add the fast lane radio pointer to list
+ * @primary_wiphy: wiphy pointer of fast-lane radio device
+ *
+ * Return: false: addition not successful
+ * true: addition is successful
+ */
+bool qca_multi_link_add_fastlane_radio(struct wiphy *fl_wiphy)
+{
+ struct qca_multi_link_list_node *fast_lane_node;
+
+ if (!fl_wiphy) {
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_WARN,
+ FL(" Fast lane radio could not be set - wiphy is NULL\n"));
+ return false;
+ }
+
+ fast_lane_node = qdf_mem_malloc(sizeof(struct qca_multi_link_list_node));
+ if (!fast_lane_node) {
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG,
+ FL("Could not allocate fast-lane node for wiphy%p\n"), fl_wiphy);
+ return false;
+ }
+
+ fast_lane_node->wiphy = fl_wiphy;
+ if (qdf_list_insert_front(&qca_multi_link_cfg.fast_lane_list, &fast_lane_node->node)
+ == QDF_STATUS_SUCCESS) {
+ return true;
+ }
+ return false;
+}
+
+qdf_export_symbol(qca_multi_link_add_fastlane_radio);
+
+/**
+ * qca_multi_link_remove_fastlane_radio() - remove the fast lane radio pointer from list
+ * @primary_wiphy: wiphy pointer of fast-lane radio device
+ *
+ * Return: false: addition not successful
+ * true: addition is successful
+ */
+bool qca_multi_link_remove_fastlane_radio(struct wiphy *fl_wiphy)
+{
+ qdf_list_node_t *node = NULL, *next_node = NULL;
+
+ if (!fl_wiphy) {
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_WARN,
+ FL(" Fast lane radio could not be removed - wiphy is NULL\n"));
+ return false;
+ }
+
+ qdf_list_peek_front(&qca_multi_link_cfg.fast_lane_list,
+ (qdf_list_node_t **)&next_node);
+ while (next_node) {
+ struct qca_multi_link_list_node *fast_lane_node
+ = (struct qca_multi_link_list_node *)next_node;
+ if (fast_lane_node->wiphy == fl_wiphy) {
+ qdf_list_remove_node(&qca_multi_link_cfg.fast_lane_list, next_node);
+ qdf_mem_free(fast_lane_node);
+ return true;
+ } else {
+ node = next_node;
+ next_node = NULL;
+ if ((qdf_list_peek_next(&qca_multi_link_cfg.fast_lane_list, node,
+ (qdf_list_node_t **)&next_node)) != QDF_STATUS_SUCCESS) {
+ return false;
+ }
+ }
+ }
+ return false;
+}
+
+qdf_export_symbol(qca_multi_link_remove_fastlane_radio);
+
+/**
+ * qca_multi_link_add_no_backhaul_radio() - add the no backhaul radio pointer to list
+ * @primary_wiphy: wiphy pointer of fast-lane radio device
+ *
+ * Return: false: addition not successful
+ * true: addition is successful
+ */
+bool qca_multi_link_add_no_backhaul_radio(struct wiphy *no_bl_wiphy)
+{
+ struct qca_multi_link_list_node *no_bl_node;
+
+ if (!no_bl_wiphy) {
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_WARN,
+ FL(" No backhaul radio could not be set - wiphy is NULL\n"));
+ return false;
+ }
+
+ no_bl_node = qdf_mem_malloc(sizeof(struct qca_multi_link_list_node));
+ if (!no_bl_node) {
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG,
+ FL("Could not allocate back-haul node for wiphy%p\n"), no_bl_node);
+ return false;
+ }
+
+ no_bl_node->wiphy = no_bl_wiphy;
+ if (qdf_list_insert_front(&qca_multi_link_cfg.no_backhaul_list, &no_bl_node->node)
+ == QDF_STATUS_SUCCESS) {
+ return true;
+ }
+ return false;
+}
+
+qdf_export_symbol(qca_multi_link_add_no_backhaul_radio);
+
+/**
+ * qca_multi_link_remove_no_backhaul_radio() - remove no-backhaul radio pointer from list
+ * @primary_wiphy: wiphy pointer of no-backhaul radio device
+ *
+ * Return: false: addition not successful
+ * true: addition is successful
+ */
+bool qca_multi_link_remove_no_backhaul_radio(struct wiphy *no_bl_wiphy)
+{
+ qdf_list_node_t *node = NULL, *next_node = NULL;
+
+ if (!no_bl_wiphy) {
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_WARN,
+ FL(" No backhaul radio could not be removed - wiphy is NULL\n"));
+ return false;
+ }
+
+ qdf_list_peek_front(&qca_multi_link_cfg.no_backhaul_list,
+ (qdf_list_node_t **)&next_node);
+ while (next_node) {
+ struct qca_multi_link_list_node *no_bl_node
+ = (struct qca_multi_link_list_node *)next_node;
+ if (no_bl_node->wiphy == no_bl_wiphy) {
+ qdf_list_remove_node(&qca_multi_link_cfg.no_backhaul_list, next_node);
+ qdf_mem_free(no_bl_node);
+ return true;
+ } else {
+ node = next_node;
+ next_node = NULL;
+ if ((qdf_list_peek_next(&qca_multi_link_cfg.no_backhaul_list, node, &next_node))
+ != QDF_STATUS_SUCCESS) {
+ return false;
+ }
+ }
+ }
+ return false;
+}
+
+qdf_export_symbol(qca_multi_link_remove_no_backhaul_radio);
+
+/**
+ * qca_multi_link_secondary_ap_rx() - Processing for frames recieved on Secondary AP VAP
+ * @net_device: net device handle
+ * @nbuf: frame
+ *
+ * Return: qca_multi_link_status_t
+ * QCA_MULTI_LINK_PKT_ALLOW: frame should be processed further by caller.
+ * QCA_MULTI_LINK_PKT_DROP: frame to be dropped.
+ * QCA_MULTI_LINK_PKT_CONSUMED: frame is consumed.
+ */
+static qca_multi_link_status_t qca_multi_link_secondary_ap_rx(struct net_device *ap_dev, qdf_nbuf_t nbuf)
+{
+ struct wiphy *ap_wiphy = NULL;
+ struct net_device *sta_dev = NULL;
+ qca_multi_link_tbl_entry_t qca_ml_entry;
+
+ QDF_STATUS qal_status = QDF_STATUS_E_FAILURE;
+ bool enqueue_to_sta_vap = false;
+ qdf_ether_header_t *eh = (qdf_ether_header_t *) qdf_nbuf_data(nbuf);
+
+ ap_wiphy = ap_dev->ieee80211_ptr->wiphy;
+
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG, FL("Secondary AP Rx: always_primary=%d, loop_detected=%d,\
+ drop_secondary_mcast=%d, shost %pM dhost %pM\n"),
+ qca_multi_link_cfg.always_primary, qca_multi_link_cfg.loop_detected,
+ qca_multi_link_cfg.drop_secondary_mcast, eh->ether_shost, eh->ether_dhost);
+
+ /*
+ *If the AP is on a fast lane radio, always give the packet to bridge.
+ */
+ if (is_fast_lane_radio(ap_wiphy)) {
+ return QCA_MULTI_LINK_PKT_ALLOW;
+ }
+
+ qca_ml_entry.qal_fdb_ieee80211_ptr = NULL;
+ qca_ml_entry.qal_fdb_dev = NULL;
+ qca_ml_entry.qal_fdb_is_local = 0;
+ qal_status = qca_multi_link_tbl_has_entry(ap_dev, eh->ether_dhost, 0,
+ &qca_ml_entry);
+ if (qal_status == QDF_STATUS_SUCCESS) {
+
+ /*
+ * Check the FDB entry type, if the mac-address is learnt on a port which is
+ * of type station, then it is a source on the RootAP side and enqueue the
+ * packet to the corresponding station vap. Else give the packet to bridge.
+ */
+ if (qca_ml_entry.qal_fdb_ieee80211_ptr
+ && (qca_ml_entry.qal_fdb_ieee80211_ptr->iftype == NL80211_IFTYPE_STATION)) {
+ enqueue_to_sta_vap = true;
+ }
+ } else {
+
+ /*
+ * If there is no fdb entry, then also the destination might be on the RootAP side,
+ * enqueue the packet to the corresponding station vap.
+ */
+ enqueue_to_sta_vap = true;
+ }
+
+ if (enqueue_to_sta_vap) {
+
+ /*
+ * Find the station vap corresponding to the AP vap.
+ */
+ sta_dev = qca_multi_link_tbl_find_sta_or_ap(ap_dev, 1);
+ if (!sta_dev) {
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG,
+ FL("Null STA device found %pM - Give to bridge\n"), eh->ether_shost);
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+
+ dev_hold(sta_dev);
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG,
+ FL("shost %pM dhost %pM \n"), eh->ether_shost, eh->ether_dhost);
+
+ /*
+ * For packets destined to sources on RootAP directly enq to STA vap.
+ */
+ sta_dev->netdev_ops->ndo_start_xmit(nbuf, sta_dev);
+ dev_put(sta_dev);
+ return QCA_MULTI_LINK_PKT_CONSUMED;
+ }
+
+ return QCA_MULTI_LINK_PKT_ALLOW;
+}
+
+/**
+ * qca_multi_link_ap_rx() - Processing for frames recieved on AP VAP
+ * @net_device: net device handle
+ * @nbuf: frame
+ *
+ * Return: false: frame not consumed and should be processed further by caller
+ * true: frame consumed
+ */
+bool qca_multi_link_ap_rx(struct net_device *net_dev, qdf_nbuf_t nbuf)
+{
+ uint8_t is_mcast;
+ uint8_t is_eapol;
+ struct net_device *ap_dev = net_dev;
+ qca_multi_link_status_t status = QCA_MULTI_LINK_PKT_NONE;
+ struct wiphy *ap_wiphy = NULL;
+ bool drop_packet = false;
+ bool is_primary = false;
+ qdf_ether_header_t *eh = (qdf_ether_header_t *) qdf_nbuf_data(nbuf);
+
+ if (!qca_multi_link_need_procesing()) {
+ goto end;
+ }
+
+ if (!qca_multi_link_cfg.loop_detected) {
+ goto end;
+ }
+
+ /*
+ * If it is mcast/broadcast frame, AST search cannot be done, so give
+ * the frame up the stack
+ * If it is EAPOL frame, just give the frame up the stack
+ */
+ is_mcast = IEEE80211_IS_MULTICAST(eh->ether_dhost);
+ is_eapol = (eh->ether_type == htons(ETHERTYPE_PAE));
+ if (is_mcast || is_eapol) {
+ goto end;
+ }
+
+ ap_wiphy = ap_dev->ieee80211_ptr->wiphy;
+ is_primary = qca_multi_link_is_primary_radio(ap_wiphy);
+
+ if (is_primary) {
+ goto end;
+ } else {
+ dev_hold(ap_dev);
+ status = qca_multi_link_secondary_ap_rx(ap_dev, nbuf);
+ dev_put(ap_dev);
+ }
+
+ if (status == QCA_MULTI_LINK_PKT_ALLOW) {
+ goto end;
+ } else if (status == QCA_MULTI_LINK_PKT_CONSUMED) {
+ return true;
+ } else if (status == QCA_MULTI_LINK_PKT_DROP) {
+ drop_packet = true;
+ }
+
+end:
+ if (drop_packet) {
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG,
+ FL("\n STA TX - Drop Packet for Mac=%pM\n"), eh->ether_shost);
+ qdf_nbuf_free(nbuf);
+ return true;
+ }
+ return false;
+}
+
+qdf_export_symbol(qca_multi_link_ap_rx);
+
+/**
+ * qca_multi_link_secondary_sta_rx() - Processing for frames recieved on secondary station vap
+ * @net_dev: station net device
+ * @nbuf: frame
+ *
+ * Return: @qca_multi_link_status_t
+ * QCA_MULTI_LINK_PKT_ALLOW: frame should be processed further by caller
+ * QCA_MULTI_LINK_PKT_DROP: frame to be dropped.
+ * QCA_MULTI_LINK_PKT_CONSUMED: frame is consumed.
+ */
+static qca_multi_link_status_t qca_multi_link_secondary_sta_rx(struct net_device *net_dev,
+ qdf_nbuf_t nbuf,
+ qca_multi_link_needs_enq_t allow_vap_enq)
+{
+ uint8_t is_mcast;
+ qca_multi_link_tbl_entry_t qca_ml_entry;
+ struct wiphy *sta_wiphy = NULL;
+ struct net_device *sta_dev = net_dev;
+ struct net_device *ap_dev = NULL;
+ QDF_STATUS qal_status = QDF_STATUS_E_FAILURE;
+ qdf_ether_header_t *eh = (qdf_ether_header_t *) qdf_nbuf_data(nbuf);
+
+ sta_wiphy = sta_dev->ieee80211_ptr->wiphy;
+ is_mcast = IEEE80211_IS_MULTICAST(eh->ether_dhost);
+
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG, FL("Secondary STA Rx:always_primary=%d, loop_detected=%d,\
+ drop_secondary_mcast=%d, shost %pM dhost %pM is_mcast=%d\n"),
+ qca_multi_link_cfg.always_primary, qca_multi_link_cfg.loop_detected,
+ qca_multi_link_cfg.drop_secondary_mcast, eh->ether_shost, eh->ether_dhost, is_mcast);
+
+ /*
+ * Mcast packets handling.
+ */
+ if (is_mcast) {
+
+ /*
+ * Always drop mcast packets on secondary radio when loop has been detected.
+ */
+ if (qca_multi_link_cfg.loop_detected) {
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+
+ qca_ml_entry.qal_fdb_ieee80211_ptr = NULL;
+ qca_ml_entry.qal_fdb_dev = NULL;
+ qca_ml_entry.qal_fdb_is_local = 0;
+ qal_status = qca_multi_link_tbl_has_entry(sta_dev, eh->ether_shost, 0,
+ &qca_ml_entry);
+ if (qal_status != QDF_STATUS_SUCCESS) {
+ if (!qca_multi_link_cfg.loop_detected
+ && !qca_multi_link_cfg.drop_secondary_mcast) {
+ /*
+ * This condition is to allow packets on Secondary Station
+ * when stations are connected to different RootAPs and loop is not
+ * detected.
+ */
+ return QCA_MULTI_LINK_PKT_ALLOW;
+ } else {
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+ }
+
+ /*
+ * Case 1:
+ * ieee80211_ptr pointer being NULL indicates that the port
+ * corresponding to the fdb entry is a non-wireless/ethernet
+ * device behind the repeater and the packet is a mcast looped packet.
+ * Case 2:
+ * ieee80211_ptr pointer being non NULL indicates that the source
+ * corresponding to the fdb entry is a wireless device
+ * behind the repeater and the packet is a mcast looped packet.
+ */
+
+ if (qca_ml_entry.qal_fdb_ieee80211_ptr && (qca_ml_entry.qal_fdb_ieee80211_ptr->wiphy != sta_wiphy)) {
+ if (!qca_multi_link_cfg.loop_detected) {
+ if (qca_ml_entry.qal_fdb_is_local
+ && (qca_ml_entry.qal_fdb_ieee80211_ptr->iftype == NL80211_IFTYPE_STATION)) {
+ qca_multi_link_cfg.loop_detected = true;
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_INFO, FL("\n****Wifi Rptr Loop Detected****\n"));
+ }
+ }
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+
+ if (qca_multi_link_drop_secondary_mcast(nbuf)) {
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+
+ /*
+ * If the mac-address is learnt on the station in the bridge,
+ * then the mcast packet is from a source on the RootAP side and we
+ * should allow the packet.
+ * This check on secondary will take of the case where stations are connected to different RootAPs
+ * and loop is not detected.
+ */
+ if (qca_ml_entry.qal_fdb_dev == sta_dev) {
+ return QCA_MULTI_LINK_PKT_ALLOW;
+ }
+
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+
+ /*
+ * Unicast packets handling received on secondary Stations.
+ */
+ if (qca_multi_link_cfg.loop_detected) {
+
+ if (allow_vap_enq == QCA_MULTI_LINK_SKIP_VAP_ENQ) {
+ return QCA_MULTI_LINK_PKT_ALLOW;
+ }
+
+ qca_ml_entry.qal_fdb_ieee80211_ptr = NULL;
+ qca_ml_entry.qal_fdb_dev = NULL;
+ qca_ml_entry.qal_fdb_is_local = 0;
+ qal_status = qca_multi_link_tbl_has_entry(sta_dev, eh->ether_dhost, 0,
+ &qca_ml_entry);
+ if (qal_status == QDF_STATUS_SUCCESS) {
+
+ /*
+ * Unicast packets destined to ethernets or bridge should never come
+ * on secondary stations.
+ */
+ if (!qca_ml_entry.qal_fdb_ieee80211_ptr) {
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+
+ /*
+ * Compare the physical device and check if the destination is a client
+ * on the same radio, then enqueue directly to AP vap.
+ */
+ if ((qca_ml_entry.qal_fdb_ieee80211_ptr->iftype == NL80211_IFTYPE_AP)
+ && (qca_ml_entry.qal_fdb_ieee80211_ptr->wiphy == sta_wiphy)) {
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG, FL("Unicast Sec STA to AP direct enq for\
+ shost %pM dhost %pM \n"), eh->ether_shost, eh->ether_dhost);
+ /*
+ * Holding the AP dev so that it cannot be brought down
+ * while we are enqueueing.
+ */
+ dev_hold(qca_ml_entry.qal_fdb_dev);
+ qca_ml_entry.qal_fdb_dev->netdev_ops->ndo_start_xmit(nbuf, qca_ml_entry.qal_fdb_dev);
+ dev_put(qca_ml_entry.qal_fdb_dev);
+ return QCA_MULTI_LINK_PKT_CONSUMED;
+ }
+
+ return QCA_MULTI_LINK_PKT_DROP;
+ } else {
+
+ /*
+ * If there is no bridge fdb entry for unicast packets received on secondary
+ * station, give the packet to the first found AP vap entry in the bridge table.
+ */
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG, FL("No Fdb entry on sec radio\
+ for ucast pkt with dhost %pM \n"), eh->ether_dhost);
+ /*
+ * Find the AP vap corresponding to the station vap.
+ */
+ ap_dev = qca_multi_link_tbl_find_sta_or_ap(sta_dev, 0);
+ if (!ap_dev) {
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG,
+ FL("Null AP device found %pM - Give to bridge\n"), eh->ether_shost);
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG,
+ FL("shost %pM dhost %pM \n"), eh->ether_shost, eh->ether_dhost);
+
+ /*
+ * For packets destined to sources on RootAP directly enq to STA vap.
+ */
+ dev_hold(ap_dev);
+ ap_dev->netdev_ops->ndo_start_xmit(nbuf, ap_dev);
+ dev_put(ap_dev);
+ return QCA_MULTI_LINK_PKT_CONSUMED;
+ }
+ }
+ return QCA_MULTI_LINK_PKT_ALLOW;
+}
+
+/**
+ * qca_multi_link_primary_sta_rx() - Processing for frames recieved on primary station vap
+ * @net_dev: station net device
+ * @nbuf: frame
+ *
+ * Return: @qca_multi_link_status_t
+ * QCA_MULTI_LINK_PKT_ALLOW: frame should be processed further by caller
+ * QCA_MULTI_LINK_PKT_DROP: frame to be dropped
+ * QCA_MULTI_LINK_PKT_CONSUMED: frame is consumed.
+ */
+static qca_multi_link_status_t qca_multi_link_primary_sta_rx(struct net_device *net_dev, qdf_nbuf_t nbuf)
+{
+ uint8_t is_mcast;
+ qca_multi_link_tbl_entry_t qca_ml_entry;
+ struct wiphy *sta_wiphy = NULL;
+ struct net_device *sta_dev = net_dev;
+ QDF_STATUS qal_status = QDF_STATUS_E_FAILURE;
+ qdf_ether_header_t *eh = (qdf_ether_header_t *) qdf_nbuf_data(nbuf);
+
+ sta_wiphy = sta_dev->ieee80211_ptr->wiphy;
+ is_mcast = IEEE80211_IS_MULTICAST(eh->ether_dhost);
+
+ /*
+ * Unicast Packets are allowed without any processing on Primary Station Vap.
+ */
+ if (!is_mcast) {
+ return QCA_MULTI_LINK_PKT_ALLOW;
+ }
+
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG, FL("Primary STA Rx: always_primary=%d, loop_detected=%d,\
+ drop_secondary_mcast=%d, shost %pM dhost %pM is_mcast=%d\n"),
+ qca_multi_link_cfg.always_primary, qca_multi_link_cfg.loop_detected,
+ qca_multi_link_cfg.drop_secondary_mcast, eh->ether_shost, eh->ether_dhost, is_mcast);
+
+ /*
+ * Mcast packet handling on Primary Station Vap Interface.
+ */
+ qca_ml_entry.qal_fdb_ieee80211_ptr = NULL;
+ qca_ml_entry.qal_fdb_dev = NULL;
+ qca_ml_entry.qal_fdb_is_local = 0;
+ qal_status = qca_multi_link_tbl_has_entry(sta_dev, eh->ether_shost, 0,
+ &qca_ml_entry);
+ if (qal_status != QDF_STATUS_SUCCESS) {
+ if (qca_multi_link_cfg.loop_detected) {
+ /*
+ * If there is no fdb entry, the we allow the packet to go to
+ * bridge as this might be the first packet from any device
+ * on the RootAP side.
+ */
+ return QCA_MULTI_LINK_PKT_ALLOW;
+ }
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+
+ /*
+ * Case 1:
+ * ieee80211_ptr pointer being NULL indicates that the port
+ * corresponding to the fdb entry is a non-wireless/ethernet
+ * device behind the repeater and the packet is a mcast looped packet.
+ * Case 2:
+ * ieee80211_ptr pointer being non NULL indicates that the source
+ * corresponding to the fdb entry is a wireless device
+ * behind the repeater and the packet is a mcast looped packet.
+ */
+
+ /*
+ * Drop the loopback mcast packets from ethernet devices behind the repeater.
+ */
+ if (!qca_ml_entry.qal_fdb_ieee80211_ptr) {
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+
+ if (qca_ml_entry.qal_fdb_ieee80211_ptr->wiphy != sta_wiphy) {
+ if (!qca_multi_link_cfg.loop_detected) {
+ if (qca_ml_entry.qal_fdb_is_local
+ && (qca_ml_entry.qal_fdb_ieee80211_ptr->iftype == NL80211_IFTYPE_STATION)) {
+ qca_multi_link_cfg.loop_detected = true;
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_INFO,
+ FL("\n****Wifi Rptr Loop Detected****\n"));
+ }
+ }
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+
+ /*
+ * If the mac-address is learnt on the Primary station in the bridge,
+ * then the mcast packet is from a source on the RootAP side and we
+ * should allow the packet.
+ */
+ if (qca_ml_entry.qal_fdb_dev == sta_dev) {
+ return QCA_MULTI_LINK_PKT_ALLOW;
+ }
+
+ return QCA_MULTI_LINK_PKT_DROP;
+}
+
+/**
+ * qca_multi_link_sta_rx() - Processing for frames recieved on STA VAP
+ * @net_dev: station net device
+ * @nbuf: frame
+ *
+ * Return: false: frame not consumed and should be processed further by caller
+ * true: frame dropped/enqueued.
+ */
+bool qca_multi_link_sta_rx(struct net_device *net_dev, qdf_nbuf_t nbuf,
+ qca_multi_link_needs_enq_t allow_vap_enq)
+{
+ uint8_t is_eapol;
+ bool is_primary = false;
+ struct wiphy *sta_wiphy = NULL;
+ struct net_device *sta_dev = net_dev;
+ qdf_ether_header_t *eh = (qdf_ether_header_t *) qdf_nbuf_data(nbuf);
+ bool drop_packet = false;
+ qca_multi_link_status_t status = QCA_MULTI_LINK_PKT_NONE;
+
+ if (!qca_multi_link_need_procesing()) {
+ goto end;
+ }
+
+ is_eapol = (eh->ether_type == htons(ETHERTYPE_PAE));
+ if (is_eapol) {
+ goto end;
+ }
+
+ sta_wiphy = sta_dev->ieee80211_ptr->wiphy;
+ is_primary = qca_multi_link_is_primary_radio(sta_wiphy);
+
+ if (qca_multi_link_drop_always_primary(is_primary, nbuf)) {
+ drop_packet = true;
+ goto end;
+ }
+
+ dev_hold(sta_dev);
+ if (is_primary) {
+ status = qca_multi_link_primary_sta_rx(sta_dev, nbuf);
+ } else {
+ status = qca_multi_link_secondary_sta_rx(sta_dev, nbuf, allow_vap_enq);
+ }
+ dev_put(sta_dev);
+
+ if (status == QCA_MULTI_LINK_PKT_ALLOW) {
+ goto end;
+ } else if (status == QCA_MULTI_LINK_PKT_CONSUMED) {
+ return true;
+ } else if (status == QCA_MULTI_LINK_PKT_DROP) {
+ drop_packet = true;
+ }
+
+end:
+ if (drop_packet) {
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG,
+ FL("\n STA TX - Drop Packet for Mac=%pM\n"), eh->ether_shost);
+ qdf_nbuf_free(nbuf);
+ return true;
+ }
+ return false;
+}
+
+qdf_export_symbol(qca_multi_link_sta_rx);
+
+/**
+ * qca_multi_link_secondary_sta_tx() - Repeater TX processing for Secondary
+ * @net_device: station net device handle
+ * @nbuf: frame
+ *
+ * Return: @qca_multi_link_status_t
+ * QCA_MULTI_LINK_PKT_ALLOW: frame should be processed further by caller
+ * QCA_MULTI_LINK_PKT_DROP: frame to be dropped
+ * QCA_MULTI_LINK_PKT_CONSUMED: frame is consumed.
+ */
+static qca_multi_link_status_t qca_multi_link_secondary_sta_tx(struct net_device *net_dev, qdf_nbuf_t nbuf)
+{
+ uint8_t is_mcast;
+ struct wiphy *sta_wiphy = NULL;
+ struct net_device *sta_dev = net_dev;
+ qca_multi_link_tbl_entry_t qca_ml_entry;
+ QDF_STATUS qal_status = QDF_STATUS_E_FAILURE;
+ qdf_ether_header_t *eh = (qdf_ether_header_t *) qdf_nbuf_data(nbuf);
+
+ if (qca_multi_link_drop_always_primary(false, nbuf)) {
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+
+ sta_wiphy = sta_dev->ieee80211_ptr->wiphy;
+
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG, FL("STA Secondary Tx: always_primary=%d, loop_detected=%d,\
+ drop_secondary_mcast=%d, shost %pM dhost %pM \n"),
+ qca_multi_link_cfg.always_primary, qca_multi_link_cfg.loop_detected, qca_multi_link_cfg.drop_secondary_mcast,
+ eh->ether_shost, eh->ether_dhost);
+
+ /*
+ * For a Secondary station, only Packets from clients on the same band are allowed for transmit.
+ */
+ is_mcast = IEEE80211_IS_MULTICAST(eh->ether_dhost);
+ qca_ml_entry.qal_fdb_ieee80211_ptr = NULL;
+ qca_ml_entry.qal_fdb_dev = NULL;
+ qca_ml_entry.qal_fdb_is_local = 0;
+ qal_status = qca_multi_link_tbl_has_entry(sta_dev, eh->ether_shost, 0,
+ &qca_ml_entry);
+ if (qal_status != QDF_STATUS_SUCCESS) {
+ if (!is_mcast) {
+ return QCA_MULTI_LINK_PKT_ALLOW;
+ }
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+
+ if (!qca_ml_entry.qal_fdb_ieee80211_ptr) {
+ /*
+ * ieee80211_ptr pointer will be NULL for ethernet devices.
+ * Packets from ethernet devices or bridge are allowed only on Primary radio.
+ */
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+
+ /*
+ * Do the DBDC Fast Lane processing at the beginning and then fall back to normal DBDC STA TX
+ * if either fast-lane is disabled or the TX is for non-fast lane radio.
+ */
+ if (is_fast_lane_radio(sta_wiphy) && is_fast_lane_radio(qca_ml_entry.qal_fdb_ieee80211_ptr->wiphy)) {
+ return QCA_MULTI_LINK_PKT_ALLOW;
+ }
+
+ if (qca_multi_link_drop_secondary_mcast(nbuf)) {
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+
+ /*
+ * Compare the physical device and check if the source is a client
+ * on the same radio.
+ */
+ if (qca_ml_entry.qal_fdb_ieee80211_ptr->wiphy != sta_dev->ieee80211_ptr->wiphy) {
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG, FL("STA TX: Diff Band Primary drop\
+ shost %pM dhost %pM \n"), eh->ether_shost, eh->ether_dhost);
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+
+ return QCA_MULTI_LINK_PKT_ALLOW;
+}
+
+/**
+ * qca_multi_link_primary_sta_tx() - Repeater TX processing for Primary
+ * @net_device: station net device handle
+ * @nbuf: frame
+ *
+ * Return: @qca_multi_link_status_t
+ * QCA_MULTI_LINK_PKT_ALLOW: frame should be processed further by caller
+ * QCA_MULTI_LINK_PKT_DROP: frame to be dropped
+ * QCA_MULTI_LINK_PKT_CONSUMED: frame is consumed.
+ */
+static qca_multi_link_status_t qca_multi_link_primary_sta_tx(struct net_device *net_dev, qdf_nbuf_t nbuf)
+{
+ struct wiphy *sta_wiphy = NULL;
+ struct net_device *sta_dev = net_dev;
+ qca_multi_link_tbl_entry_t qca_ml_entry;
+ QDF_STATUS qal_status = QDF_STATUS_E_FAILURE;
+ qdf_ether_header_t *eh = (qdf_ether_header_t *) qdf_nbuf_data(nbuf);
+
+ sta_wiphy = sta_dev->ieee80211_ptr->wiphy;
+
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG, FL("Primary STA Tx: always_primary=%d, loop_detected=%d,\
+ drop_secondary_mcast=%d, shost %pM dhost %pM \n"),
+ qca_multi_link_cfg.always_primary, qca_multi_link_cfg.loop_detected,
+ qca_multi_link_cfg.drop_secondary_mcast, eh->ether_shost, eh->ether_dhost);
+
+ /*
+ * For Primary station, packets allowed for transmission are:
+ * 1) Packets from ethernet devices.
+ * 2) Packets from radios which are not participating in backhaul.
+ * 3) Packets from clients on the same band.
+ */
+ qca_ml_entry.qal_fdb_ieee80211_ptr = NULL;
+ qca_ml_entry.qal_fdb_dev = NULL;
+ qca_ml_entry.qal_fdb_is_local = 0;
+ qal_status = qca_multi_link_tbl_has_entry(sta_dev, eh->ether_shost, 0,
+ &qca_ml_entry);
+ /*
+ * All packets coming to Primary station has to have a bridge fdb entry
+ * as they will be received from bridge only.
+ */
+ if (qal_status != QDF_STATUS_SUCCESS) {
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+
+ if (!qca_ml_entry.qal_fdb_ieee80211_ptr) {
+ /*
+ * ieee80211_ptr pointer will be NULL for ethernet devices.
+ * Packets from ethernet devices or bridge are allowed only on Primary radio.
+ */
+ return QCA_MULTI_LINK_PKT_ALLOW;
+ }
+
+ /*
+ * Do the DBDC Fast Lane processing at the beginning and then fall back to normal DBDC STA TX
+ * if either fast-lane is disabled or the TX is for non-fast lane radio.
+ */
+ if (is_fast_lane_radio(sta_wiphy)
+ && is_fast_lane_radio(qca_ml_entry.qal_fdb_ieee80211_ptr->wiphy)) {
+ return QCA_MULTI_LINK_PKT_ALLOW;
+ }
+
+ /*
+ * This flag will be set for radios which does not particpate in backhaul.
+ */
+ if (is_no_backhaul_radio(qca_ml_entry.qal_fdb_ieee80211_ptr->wiphy)) {
+ return QCA_MULTI_LINK_PKT_ALLOW;
+ }
+
+ /*
+ * Compare the physical device and check if the source is a client
+ * on the same radio.
+ */
+ if (qca_ml_entry.qal_fdb_ieee80211_ptr->wiphy != sta_dev->ieee80211_ptr->wiphy) {
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG, FL("STA TX: Diff Band Primary drop\
+ shost %pM dhost %pM \n"), eh->ether_shost, eh->ether_dhost);
+ return QCA_MULTI_LINK_PKT_DROP;
+ }
+
+ return QCA_MULTI_LINK_PKT_ALLOW;
+}
+
+/**
+ * qca_multi_link_sta_tx() - Repeater TX processing
+ * @net_device: station net device handle
+ * @nbuf: frame
+ *
+ * Return: false: frame not consumed and should be processed further by caller
+ * true: frame consumed
+ */
+bool qca_multi_link_sta_tx(struct net_device *net_dev, qdf_nbuf_t nbuf)
+{
+ bool drop_packet = false;
+ bool is_primary = false;
+ struct wiphy *sta_wiphy = NULL;
+ struct net_device *sta_dev = net_dev;
+ qdf_ether_header_t *eh = (qdf_ether_header_t *) qdf_nbuf_data(nbuf);
+ qca_multi_link_status_t status = QCA_MULTI_LINK_PKT_NONE;
+
+ if (!qca_multi_link_need_procesing()) {
+ goto end;
+ }
+
+ if (!qca_multi_link_cfg.loop_detected) {
+ goto end;
+ }
+
+ if (qca_multi_link_pktfrom_ownsrc(sta_dev, nbuf)) {
+ goto end;
+ }
+
+ sta_wiphy = sta_dev->ieee80211_ptr->wiphy;
+ is_primary = qca_multi_link_is_primary_radio(sta_wiphy);
+
+ dev_hold(net_dev);
+ if (is_primary) {
+ status = qca_multi_link_primary_sta_tx(sta_dev, nbuf);
+ } else {
+ status = qca_multi_link_secondary_sta_tx(sta_dev, nbuf);
+ }
+ dev_put(net_dev);
+
+ if (status == QCA_MULTI_LINK_PKT_ALLOW) {
+ goto end;
+ } else if (status == QCA_MULTI_LINK_PKT_CONSUMED) {
+ return true;
+ } else if (status == QCA_MULTI_LINK_PKT_DROP) {
+ drop_packet = true;
+ }
+
+end:
+ if (drop_packet) {
+ QDF_TRACE(QDF_MODULE_ID_RPTR, QDF_TRACE_LEVEL_DEBUG,
+ FL("\n STA TX - Drop Packet for Mac=%pM\n"), eh->ether_shost);
+ qdf_nbuf_free(nbuf);
+ return true;
+ }
+
+ return false;
+}
+
+qdf_export_symbol(qca_multi_link_sta_tx);
diff --git a/qca_multi_link/src/qca_multi_link_tbl.c b/qca_multi_link/src/qca_multi_link_tbl.c
new file mode 100644
index 0000000..7d7d98e
--- /dev/null
+++ b/qca_multi_link/src/qca_multi_link_tbl.c
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "qca_multi_link_tbl.h"
+#include "qdf_module.h"
+#include "qdf_trace.h"
+
+int qca_multi_link_tbl_get_eth_entries(struct net_device *net_dev,
+ void *fill_buff, int buff_size)
+{
+ struct net_bridge_fdb_entry *search_fdb = NULL;
+ int i;
+ struct net_bridge_port *p = br_port_get_rcu(net_dev);
+ struct net_device *ndev = NULL;
+ struct wireless_dev *wdev = NULL;
+ int num_of_entries = 0;
+ qca_multi_link_tbl_entry_t *qfdb = (qca_multi_link_tbl_entry_t *)fill_buff;
+ int fdb_entry_size = sizeof(qca_multi_link_tbl_entry_t);
+
+ if (!p) {
+ qdf_err("bridge port is NULL or disabled for dev %p \n", net_dev);
+ return 0;
+ }
+
+ if (buff_size < fdb_entry_size) {
+ qdf_err("Buffer size cannot be less than size of entry:%d dev:%p\n", fdb_entry_size, net_dev);
+ return 0;
+ }
+
+ /*
+ * Traverse the bridge hah to get all ethernet interface entries.
+ */
+ rcu_read_lock();
+ for (i = 0; i < BR_HASH_SIZE ; i++) {
+ hlist_for_each_entry_rcu(search_fdb, &p->br->hash[i], hlist) {
+ ndev = search_fdb->dst ? search_fdb->dst->dev : NULL;
+ wdev = ndev ? ndev->ieee80211_ptr : NULL;
+ if (!wdev && ndev) {
+ memcpy(qfdb->qal_mac_addr, search_fdb->addr.addr, 6);
+ qfdb->qal_fdb_dev = ndev;
+ qfdb->qal_fdb_is_local = search_fdb->is_local;
+ num_of_entries++;
+ qfdb += 1;
+ buff_size -= fdb_entry_size;
+ if (buff_size < fdb_entry_size) {
+ rcu_read_unlock();
+ return num_of_entries;
+ }
+ }
+ }
+ }
+ rcu_read_unlock();
+
+ return num_of_entries;
+}
+
+qdf_export_symbol(qca_multi_link_tbl_get_eth_entries);
+
+struct net_device *qca_multi_link_tbl_find_sta_or_ap(struct net_device *net_dev,
+ uint8_t dev_type)
+{
+ struct net_bridge_fdb_entry *search_fdb = NULL;
+ struct net_device *search_dev = NULL;
+ struct wireless_dev *ieee80211_ptr = NULL;
+ int i;
+ enum nl80211_iftype search_if_type;
+ struct net_bridge_port *p = br_port_get_rcu(net_dev);
+
+ if (!p || (p && p->state == BR_STATE_DISABLED))
+ return NULL;
+
+ if (!dev_type)
+ search_if_type = NL80211_IFTYPE_AP;
+ else
+ search_if_type = NL80211_IFTYPE_STATION;
+
+ rcu_read_lock();
+ for (i = 0; i < BR_HASH_SIZE; i++) {
+ hlist_for_each_entry_rcu(search_fdb, &p->br->hash[i], hlist) {
+ if (!search_fdb->is_local)
+ continue;
+
+ search_dev = search_fdb->dst->dev;
+ ieee80211_ptr = search_dev->ieee80211_ptr;
+ if (ieee80211_ptr
+ && (ieee80211_ptr->iftype == search_if_type)
+ && (ieee80211_ptr->wiphy == net_dev->ieee80211_ptr->wiphy)) {
+ rcu_read_unlock();
+ return search_dev;
+ }
+ }
+ }
+
+ rcu_read_unlock();
+ return NULL;
+}
+
+qdf_export_symbol(qca_multi_link_tbl_find_sta_or_ap);
+
+QDF_STATUS qca_multi_link_tbl_delete_entry(struct net_device *net_dev, uint8_t *addr)
+{
+ int status;
+ struct net_bridge_fdb_entry *fdb_entry = NULL;
+
+ fdb_entry = br_fdb_has_entry(net_dev, addr, 0);
+ if (!fdb_entry) {
+ return QDF_STATUS_E_FAILURE;
+ }
+
+ if (fdb_entry->is_local) {
+ return QDF_STATUS_SUCCESS;
+ }
+
+ status = br_fdb_delete_by_netdev(net_dev, addr, 0);
+ if (status < 0) {
+ return QDF_STATUS_E_FAILURE;
+ }
+ return QDF_STATUS_SUCCESS;
+}
+
+qdf_export_symbol(qca_multi_link_tbl_delete_entry);
+
+QDF_STATUS qca_multi_link_tbl_has_entry(struct net_device *net_dev,
+ const char *addr, uint16_t vlan_id,
+ qca_multi_link_tbl_entry_t *qca_ml_entry)
+{
+ struct net_bridge_fdb_entry *fdb_entry = NULL;
+ struct net_bridge_port *fdb_port = NULL;
+ struct net_device *fdb_dev = NULL;
+
+ if (!qca_ml_entry)
+ return QDF_STATUS_E_FAILURE;
+
+ fdb_entry = br_fdb_has_entry(net_dev, addr, vlan_id);
+ if (!fdb_entry)
+ return QDF_STATUS_E_FAILURE;
+
+ fdb_port = fdb_entry->dst;
+ if (!fdb_port) {
+ qdf_err("bridge port is NULL for mac-addr %pM\n", addr);
+ return QDF_STATUS_E_FAILURE;
+ }
+
+ fdb_dev = fdb_port->dev;
+ if (!fdb_dev) {
+ qdf_err("bridge netdev is NULL for mac-addr %pM\n", addr);
+ return QDF_STATUS_E_FAILURE;
+ }
+
+ qca_ml_entry->qal_fdb_ieee80211_ptr = fdb_dev->ieee80211_ptr;
+ qca_ml_entry->qal_fdb_dev = fdb_dev;
+ qca_ml_entry->qal_fdb_is_local = fdb_entry->is_local;
+ return QDF_STATUS_SUCCESS;
+}
+
+qdf_export_symbol(qca_multi_link_tbl_has_entry);
+
+QDF_STATUS qca_multi_link_tbl_register_update_notifier(void *nb)
+{
+ struct notifier_block *notifier = (struct notifier_block *)nb;
+
+ if (!notifier) {
+ qdf_err("Bridge Notifier is NULL");
+ return QDF_STATUS_E_FAILURE;
+ }
+
+ br_fdb_update_register_notify(notifier);
+
+ return QDF_STATUS_SUCCESS;
+}
+
+qdf_export_symbol(qca_multi_link_tbl_register_update_notifier);
+
+QDF_STATUS qca_multi_link_tbl_unregister_update_notifier(void *nb)
+{
+ struct notifier_block *notifier = (struct notifier_block *)nb;
+
+ if (!notifier) {
+ qdf_err("Bridge Notifier is NULL");
+ return QDF_STATUS_E_FAILURE;
+ }
+
+ br_fdb_update_unregister_notify(notifier);
+
+ return QDF_STATUS_SUCCESS;
+}
+
+qdf_export_symbol(qca_multi_link_tbl_unregister_update_notifier);
+
+QDF_STATUS qca_multi_link_tbl_register_notifier(void *nb)
+{
+ struct notifier_block *notifier = (struct notifier_block *)nb;
+
+ if (!notifier) {
+ qdf_err("Bridge Notifier is NULL");
+ return QDF_STATUS_E_FAILURE;
+ }
+
+ br_fdb_register_notify(notifier);
+
+ return QDF_STATUS_SUCCESS;
+}
+
+qdf_export_symbol(qca_multi_link_tbl_register_notifier);
+
+QDF_STATUS qca_multi_link_tbl_unregister_notifier(void *nb)
+{
+ struct notifier_block *notifier = (struct notifier_block *)nb;
+
+ if (!notifier) {
+ qdf_err("Bridge Notifier is NULL");
+ return QDF_STATUS_E_FAILURE;
+ }
+
+ br_fdb_unregister_notify(notifier);
+
+ return QDF_STATUS_SUCCESS;
+}
+
+qdf_export_symbol(qca_multi_link_tbl_unregister_notifier);
+
+struct net_device *qca_multi_link_tbl_get_bridge_dev(struct net_device *port_dev)
+{
+ struct net_bridge_port *fdb_port = NULL;
+ struct net_bridge *br = NULL;
+
+ if (!port_dev) {
+ qdf_err("Port dev is NULL");
+ return NULL;
+ }
+
+ fdb_port = br_port_get_rcu(port_dev);
+ if (!fdb_port) {
+ qdf_err("fdb port is NULL");
+ return NULL;
+ }
+
+ br = fdb_port->br;
+ if (!br) {
+ qdf_err("bridge dev is NULL");
+ return NULL;
+ }
+
+ return br->dev;
+}
+
+qdf_export_symbol(qca_multi_link_tbl_get_bridge_dev);
diff --git a/tools/linux/cfg80211_ven_cmd.h b/tools/linux/cfg80211_ven_cmd.h
index 3857182..c02ef39 100644
--- a/tools/linux/cfg80211_ven_cmd.h
+++ b/tools/linux/cfg80211_ven_cmd.h
@@ -1188,6 +1188,9 @@
OL_ATH_PARAM_NON_INHERIT_ENABLE = 443,
/* Set/Get next frequency for radar */
OL_ATH_PARAM_NXT_RDR_FREQ = 444,
+ /* set the flag for a radio with no backhaul */
+ OL_ATH_PARAM_NO_BACKHAUL_RADIO = 445,
+
};
#ifdef CONFIG_SUPPORT_LIBROXML
@@ -3071,6 +3074,10 @@
OL_ATH_PARAM_SHIFT | OL_ATH_PARAM_NON_INHERIT_ENABLE, SET_PARAM, 1},
{"g_non_inherit_enable",
OL_ATH_PARAM_SHIFT | OL_ATH_PARAM_NON_INHERIT_ENABLE, GET_PARAM, 0},
+ {"nobckhlradio",
+ OL_ATH_PARAM_SHIFT | OL_ATH_PARAM_NO_BACKHAUL_RADIO, SET_PARAM, 1},
+ {"g_nobckhlradio",
+ OL_ATH_PARAM_SHIFT | OL_ATH_PARAM_NO_BACKHAUL_RADIO, GET_PARAM, 0},
};
#endif
#endif