diff mbox series

[RFC,v1,123/256] cl8k: add ops.c

Message ID 20210617160223.160998-124-viktor.barna@celeno.com
State New
Headers show
Series wireless: cl8k driver for Celeno IEEE 802.11ax devices | expand

Commit Message

Viktor Barna June 17, 2021, 4 p.m. UTC
From: Viktor Barna <viktor.barna@celeno.com>

(Part of the split. Please, take a look at the cover letter for more
details).

Signed-off-by: Viktor Barna <viktor.barna@celeno.com>
---
 drivers/net/wireless/celeno/cl8k/ops.c | 889 +++++++++++++++++++++++++
 1 file changed, 889 insertions(+)
 create mode 100644 drivers/net/wireless/celeno/cl8k/ops.c

--
2.30.0
diff mbox series

Patch

diff --git a/drivers/net/wireless/celeno/cl8k/ops.c b/drivers/net/wireless/celeno/cl8k/ops.c
new file mode 100644
index 000000000000..16934984b7cd
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/ops.c
@@ -0,0 +1,889 @@ 
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include <linux/log2.h>
+#include "ops.h"
+#include "utils/ip.h"
+#include "chip.h"
+#include "ampdu.h"
+#include "fw/msg_tx.h"
+#include "tx/tx.h"
+#include "tx/tx_queue.h"
+#include "radio.h"
+#include "recovery.h"
+#include "rate_ctrl.h"
+#include "temperature.h"
+#include "band.h"
+#include "rx/rx.h"
+#include "edca.h"
+#include "utils/math.h"
+#include "utils/utils.h"
+#include "ext/dyn_mcast_rate.h"
+#include "ext/dyn_bcast_rate.h"
+#include "vns.h"
+#include "dfs/dfs.h"
+#include "key.h"
+#include "temperature.h"
+#include "calib.h"
+#include "wrs/wrs_api.h"
+#include "chandef.h"
+#include "version.h"
+#include "power.h"
+#include "tx/tx_inject.h"
+#include "stats.h"
+#include "netlink.h"
+#include "calib.h"
+#ifdef CONFIG_CL_PCIE
+#include "bus/pci/ipc.h"
+#endif
+
+static const int cl_ac2hwq[AC_MAX] = {
+       [NL80211_TXQ_Q_VO] = CL_HWQ_VO,
+       [NL80211_TXQ_Q_VI] = CL_HWQ_VI,
+       [NL80211_TXQ_Q_BE] = CL_HWQ_BE,
+       [NL80211_TXQ_Q_BK] = CL_HWQ_BK
+};
+
+static const int cl_ac2edca[AC_MAX] = {
+       [NL80211_TXQ_Q_VO] = EDCA_AC_VO,
+       [NL80211_TXQ_Q_VI] = EDCA_AC_VI,
+       [NL80211_TXQ_Q_BE] = EDCA_AC_BE,
+       [NL80211_TXQ_Q_BK] = EDCA_AC_BK
+};
+
+static void cl_ops_tx_agg(struct cl_hw *cl_hw,
+                         struct sk_buff *skb,
+                         struct ieee80211_tx_info *tx_info,
+                         struct cl_sta *cl_sta)
+{
+       cl_hw->tx_packet_cntr.forward.from_mac_agg++;
+
+       if (!cl_sta) {
+               kfree_skb(skb);
+               cl_dbg_err(cl_hw, "cl_sta null in agg packet\n");
+               cl_hw->tx_packet_cntr.drop.sta_null_in_agg++;
+               return;
+       }
+
+       /* AMSDU in HW can work only with header conversion. */
+       tx_info->control.flags &= ~IEEE80211_TX_CTRL_AMSDU;
+       cl_tx_agg(cl_hw, cl_sta, skb, false, true);
+}
+
+static void cl_ops_tx_single(struct cl_hw *cl_hw,
+                            struct sk_buff *skb,
+                            struct ieee80211_tx_info *tx_info,
+                            struct cl_sta *cl_sta)
+{
+       bool is_vns = cl_vns_is_very_near(cl_hw, cl_sta, skb);
+
+       cl_hw->tx_packet_cntr.forward.from_mac_single++;
+
+       cl_tx_single(cl_hw, cl_sta, skb, is_vns, true);
+}
+
+void cl_ops_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, struct sk_buff *skb)
+{
+       /*
+        * Almost all traffic passing here is singles.
+        * Only when opening a BA session some packets with
+        * IEEE80211_TX_CTL_AMPDU set can pass here.
+        * All skbs passing here did header conversion.
+        */
+       struct cl_hw *cl_hw = (struct cl_hw *)hw->priv;
+       struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
+       struct ieee80211_sta *sta = control->sta;
+       struct cl_sta *cl_sta = NULL;
+
+       if (sta) {
+               cl_sta = IEEE80211_STA_TO_CL_STA(sta);
+
+               /*
+                * Prior to STA connection sta can be set but we don't
+                * want cl_sta to be used since it's not initialized yet
+                */
+               if (cl_sta->sta_idx == STA_IDX_INVALID)
+                       cl_sta = NULL;
+       }
+
+       if (cl_recovery_in_progress(cl_hw)) {
+               cl_hw->tx_packet_cntr.drop.in_recovery++;
+               cl_tx_drop_dkb(skb);
+               return;
+       }
+
+       if (tx_info->flags & IEEE80211_TX_CTL_AMPDU)
+               cl_ops_tx_agg(cl_hw, skb, tx_info, cl_sta);
+       else
+               cl_ops_tx_single(cl_hw, skb, tx_info, cl_sta);
+}
+
+int cl_ops_start(struct ieee80211_hw *hw)
+{
+       /*
+        * Called before the first netdevice attached to the hardware
+        * is enabled. This should turn on the hardware and must turn on
+        * frame reception (for possibly enabled monitor interfaces.)
+        * Returns negative error codes, these may be seen in userspace,
+        * or zero.
+        * When the device is started it should not have a MAC address
+        * to avoid acknowledging frames before a non-monitor device
+        * is added.
+        * Must be implemented and can sleep.
+        * It does not return until the firmware is up and running.
+        */
+       int error = 0;
+       struct cl_hw *cl_hw = hw->priv;
+       struct cl_tcv_conf *conf = cl_hw->conf;
+
+#ifdef CONFIG_CL_PCIE
+       if (!cl_hw->ipc_env) {
+               CL_DBG_ERROR(cl_hw, "ipc_env is NULL! 'no_dhcpcd' is missing in nvram folder\n");
+               return -1;
+       }
+#endif
+
+       /* Exits if device is already started */
+       if (WARN_ON(test_bit(CL_DEV_STARTED, &cl_hw->drv_flags)))
+               return -EBUSY;
+
+       /* Start firmware */
+       error = cl_msg_tx_start(cl_hw);
+       if (error)
+               return error;
+
+       /* Device is now started.
+        * Set CL_DEV_STARTED bit before the calls to other messages sent to
+        * firmware, to prevent them from being blocked*
+        */
+       set_bit(CL_DEV_STARTED, &cl_hw->drv_flags);
+
+       if (!cl_recovery_in_progress(cl_hw)) {
+               /* Read version */
+               error = cl_version_update(cl_hw);
+               if (error)
+                       return error;
+
+               error = cl_temperature_diff_e2p_read(cl_hw);
+               if (error)
+                       return error;
+       }
+
+       /* Set firmware debug module filter */
+       error = cl_msg_tx_dbg_set_ce_mod_filter(cl_hw, conf->ci_fw_dbg_module);
+       if (error)
+               return error;
+
+       /* Set firmware debug severity level */
+       error = cl_msg_tx_dbg_set_sev_filter(cl_hw, conf->ci_fw_dbg_severity);
+       if (error)
+               return error;
+
+       /* Set firmware rate fallbacks */
+       error = cl_msg_tx_set_rate_fallback(cl_hw);
+       if (error)
+               return error;
+
+       error = cl_msg_tx_ndp_tx_control(cl_hw,
+                                        conf->ci_ndp_tx_chain_mask,
+                                        conf->ci_ndp_tx_bw,
+                                        conf->ci_ndp_tx_format,
+                                        conf->ci_ndp_tx_num_ltf);
+       if (error)
+               return error;
+
+       /* Set default, multicast, broadcast rate */
+       cl_rate_ctrl_set_default(cl_hw);
+       cl_dyn_mcast_rate_set(cl_hw);
+       cl_dyn_bcast_rate_set(cl_hw, 0);
+
+       ieee80211_wake_queues(hw);
+
+       cl_calib_start(cl_hw);
+
+       clear_bit(CL_DEV_INIT, &cl_hw->drv_flags);
+
+       cl_edca_hw_conf(cl_hw);
+
+       return error;
+}
+
+void cl_ops_stop(struct ieee80211_hw *hw)
+{
+       /*
+        * Called after last netdevice attached to the hardware
+        * is disabled. This should turn off the hardware (at least
+        * it must turn off frame reception.)
+        * May be called right after add_interface if that rejects
+        * an interface. If you added any work onto the mac80211 workqueue
+        * you should ensure to cancel it on this callback.
+        * Must be implemented and can sleep.
+        */
+       struct cl_hw *cl_hw = hw->priv;
+
+       /* Go to idle */
+       cl_msg_tx_set_idle(cl_hw, MAC_IDLE_SYNC);
+
+       /*
+        * Clear CL_DEV_STARTED to prevent message to be sent (besides reset and start).
+        * It also blocks transmission of new packets
+        */
+       clear_bit(CL_DEV_STARTED, &cl_hw->drv_flags);
+
+       /* Stop mac80211 queues */
+       ieee80211_stop_queues(hw);
+
+       /* Send reset message to firmware */
+       cl_msg_tx_reset(cl_hw);
+
+#ifdef CONFIG_CL_PCIE
+       /* Reset IPC */
+       cl_ipc_reset(cl_hw);
+#endif
+
+       cl_hw->num_ap_started = 0;
+       cl_hw->radio_status = RADIO_STATUS_OFF;
+}
+
+static int add_interface_to_firmware(struct cl_hw *cl_hw, struct ieee80211_vif *vif, u8 vif_index)
+{
+       struct mm_add_if_cfm *add_if_cfm;
+       int ret = 0;
+
+       /* Forward the information to the firmware */
+       ret = cl_msg_tx_add_if(cl_hw, vif, vif_index);
+       if (ret)
+               return ret;
+
+       add_if_cfm = (struct mm_add_if_cfm *)(cl_hw->msg_cfm_params[MM_ADD_IF_CFM]);
+       if (!add_if_cfm)
+               return -ENOMSG;
+
+       if (add_if_cfm->status != 0) {
+               cl_dbg_verbose(cl_hw, "Status Error (%u)\n", add_if_cfm->status);
+               ret = -EIO;
+       }
+
+       cl_msg_tx_free_cfm_params(cl_hw, MM_ADD_IF_CFM);
+
+       return ret;
+}
+
+int cl_ops_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+{
+       /*
+        * Called when a netdevice attached to the hardware is
+        * enabled. Because it is not called for monitor mode devices, start
+        * and stop must be implemented.
+        * The driver should perform any initialization it needs before
+        * the device can be enabled. The initial configuration for the
+        * interface is given in the conf parameter.
+        * The callback may refuse to add an interface by returning a
+        * negative error code (which will be seen in userspace.)
+        * Must be implemented and can sleep.
+        */
+       struct cl_hw *cl_hw = hw->priv;
+       struct cl_chip *chip = cl_hw->chip;
+       struct cl_vif *cl_vif = (struct cl_vif *)vif->drv_priv;
+       struct ieee80211_sub_if_data *sdata = container_of(vif, struct ieee80211_sub_if_data, vif);
+       struct net_device *dev = sdata->dev;
+       u8 ac;
+
+       if (!dev)
+               return -1;
+
+       /*
+        * In recovery just send the message to firmware and exit
+        * (also make sure cl_vif already exists).
+        */
+       if (cl_recovery_in_progress(cl_hw) && cl_vif_get_by_dev(cl_hw, dev))
+               return add_interface_to_firmware(cl_hw, vif, cl_vif->vif_index);
+
+       cl_vif->cl_hw = cl_hw;
+       cl_vif->vif = vif;
+       cl_vif->dev = dev;
+
+       if (chip->conf->ce_production_mode)
+               cl_vif->tx_en = true;
+
+       /* Copy dev ops and change ndo_start_xmit to point at cl_tx_start() */
+       cl_vif->orig_dev_ops = dev->netdev_ops;
+       memcpy(&cl_vif->dev_ops, dev->netdev_ops, sizeof(struct net_device_ops));
+       cl_vif->dev_ops.ndo_start_xmit = cl_tx_start;
+       dev->netdev_ops = &cl_vif->dev_ops;
+
+       if (chip->idx == CHIP0) {
+               if (cl_hw_is_tcv0(cl_hw))
+                       sscanf(dev->name, CL_IFACE_PREFIX "0_%hhu", &cl_vif->vif_index);
+               else
+                       sscanf(dev->name, CL_IFACE_PREFIX "1_%hhu", &cl_vif->vif_index);
+       } else {
+               if (cl_hw_is_tcv0(cl_hw))
+                       sscanf(dev->name, CL_IFACE_PREFIX "2_%hhu", &cl_vif->vif_index);
+               else
+                       sscanf(dev->name, CL_IFACE_PREFIX "3_%hhu", &cl_vif->vif_index);
+       }
+
+       if (add_interface_to_firmware(cl_hw, vif, cl_vif->vif_index))
+               return -1;
+
+       if (vif->type != NL80211_IFTYPE_STATION)
+               vif->cab_queue = CL_HWQ_VO;
+
+       cl_vif_add(cl_hw, cl_vif);
+
+       for (ac = 0; ac < AC_MAX; ac++)
+               vif->hw_queue[ac] = cl_ac2hwq[ac];
+
+       if (vif->type == NL80211_IFTYPE_MESH_POINT) {
+               tasklet_init(&cl_hw->tx_mesh_bcn_task, cl_tx_bcn_mesh_task,
+                            (unsigned long)cl_vif);
+               cl_radio_on(cl_hw);
+               cl_vif->tx_en = true;
+       }
+
+       /* Set active state in station mode after ifconfig down and up */
+       if (cl_radio_is_on(cl_hw) && vif->type == NL80211_IFTYPE_STATION)
+               cl_msg_tx_set_idle(cl_hw, MAC_ACTIVE);
+
+       return 0;
+}
+
+void cl_ops_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+{
+       /*
+        * Notifies a driver that an interface is going down.
+        * The stop callback is called after this if it is the last interface
+        * and no monitor interfaces are present.
+        * When all interfaces are removed, the MAC address in the hardware
+        * must be cleared so the device no longer acknowledges packets,
+        * the mac_addr member of the conf structure is, however, set to the
+        * MAC address of the device going away.
+        * Hence, this callback must be implemented. It can sleep.
+        */
+       struct cl_hw *cl_hw = hw->priv;
+       struct cl_vif *cl_vif = (struct cl_vif *)vif->drv_priv;
+
+       if (vif->type == NL80211_IFTYPE_MESH_POINT)
+               tasklet_kill(&cl_hw->tx_mesh_bcn_task);
+
+       if (!cl_recovery_in_progress(cl_hw)) {
+               cl_vif_remove(cl_hw, cl_vif);
+               cl_msg_tx_remove_if(cl_hw, cl_vif->vif_index);
+       } else {
+               cl_vif_remove(cl_hw, cl_vif);
+       }
+
+       /* Return netdev_ops back to it's original configuration */
+       cl_vif->dev->netdev_ops = cl_vif->orig_dev_ops;
+
+       cl_vif->cl_hw = NULL;
+       cl_vif->vif = NULL;
+       cl_vif->dev = NULL;
+}
+
+static int cl_ops_conf_change_channel(struct ieee80211_hw *hw)
+{
+       struct cl_hw *cl_hw = hw->priv;
+       struct cl_chip *chip = cl_hw->chip;
+       struct cfg80211_chan_def *chandef = &hw->conf.chandef;
+       enum nl80211_chan_width width = chandef->width;
+       u32 primary = chandef->chan->center_freq;
+       u32 center = chandef->center_freq1;
+       u8 channel = ieee80211_frequency_to_channel(primary);
+       u8 bw;
+
+       if (!test_bit(CL_DEV_STARTED, &cl_hw->drv_flags) ||
+           test_bit(CL_DEV_INIT, &cl_hw->drv_flags))
+               return -EBUSY;
+
+       /* WA: for the first set-channel in production mode use the nvram values */
+       if (chip->conf->ce_production_mode && !cl_hw->chandef_set) {
+               cl_hw->chandef_set = true;
+               cl_chandef_get_default(cl_hw, &width, &primary, &center);
+               channel = cl_hw->conf->ha_channel;
+       }
+
+       bw = width_to_bw(width);
+
+       if (cl_hw->channel == channel &&
+           cl_hw->bw == bw &&
+           cl_hw->primary_freq == primary &&
+           cl_hw->center_freq == center)
+               goto check_cac;
+
+       /*
+        * Flush the pending data to ensure that we will finish the pending
+        * transmissions before changing the channel
+        */
+       cl_ops_flush(hw, NULL, -1, false);
+
+       if (cl_band_is_6g(cl_hw))
+               cl_netlink_send_event_co_locate_update(cl_hw);
+
+       if (cl_msg_tx_set_channel(cl_hw, channel, bw, primary, center))
+               return -EIO;
+
+check_cac:
+       /*
+        * TODO: This callback is being spawned even in STA mode, moreover,
+        * "start_ap" comes later - it is unclear whether we are an AP at this
+        * stage. Likely, may be solved by moving "force_cac_*" states to beginning
+        * of "start_ap", but the request should stay in current callback
+        */
+       if (!cl_band_is_5g(cl_hw))
+               return 0;
+
+       /*
+        * Radar listening may occur at DFS channels during in-service mode,
+        * so CAC may clear the channels, but radar listening should be
+        * still active, and should start it as soon as we can.
+        */
+       if (hw->conf.radar_enabled) {
+               /* If channel policy demans to be in CAC - need to request it */
+               if (!cl_dfs_is_in_cac(cl_hw) &&
+                   chandef->chan->dfs_state == NL80211_DFS_USABLE)
+                       cl_dfs_request_cac(cl_hw, true);
+
+               if (!cl_dfs_radar_listening(cl_hw))
+                       cl_dfs_radar_listen_start(cl_hw);
+       } else {
+               /*
+                * No sense to continue be in silent mode if the channel was
+                * cleared
+                */
+               if (cl_dfs_is_in_cac(cl_hw) &&
+                   chandef->chan->dfs_state == NL80211_DFS_AVAILABLE)
+                       cl_dfs_request_cac(cl_hw, false);
+
+               if (cl_dfs_radar_listening(cl_hw))
+                       cl_dfs_radar_listen_end(cl_hw);
+       }
+
+       /*
+        * We have just finished channel switch.
+        * Now, check what to do with CAC.
+        */
+       if (cl_dfs_requested_cac(cl_hw))
+               cl_dfs_force_cac_start(cl_hw);
+       else if (cl_dfs_is_in_cac(cl_hw))
+               cl_dfs_force_cac_end(cl_hw);
+
+       return 0;
+}
+
+int cl_ops_config(struct ieee80211_hw *hw, u32 changed)
+{
+       /*
+        * Handler for configuration requests. IEEE 802.11 code calls this
+        * function to change hardware configuration, e.g., channel.
+        * This function should never fail but returns a negative error code
+        * if it does. The callback can sleep
+        */
+       int error = 0;
+
+       if (changed & IEEE80211_CONF_CHANGE_CHANNEL)
+               error = cl_ops_conf_change_channel(hw);
+
+       return error;
+}
+
+/*
+ * @bss_info_changed: Handler for configuration requests related to BSS
+ *  parameters that may vary during BSS's lifespan, and may affect low
+ *  level driver (e.g. assoc/disassoc status, erp parameters).
+ *  This function should not be used if no BSS has been set, unless
+ *  for association indication. The @changed parameter indicates which
+ *  of the bss parameters has changed when a call is made. The callback
+ *  can sleep.
+ */
+void cl_ops_bss_info_changed(struct ieee80211_hw *hw,
+                            struct ieee80211_vif *vif,
+                            struct ieee80211_bss_conf *info,
+                            u32 changed)
+{
+       struct cl_hw *cl_hw = hw->priv;
+       struct cl_vif *cl_vif = (struct cl_vif *)vif->drv_priv;
+
+       if (changed & BSS_CHANGED_ASSOC) {
+               if (cl_msg_tx_set_associated(cl_hw, info))
+                       return;
+       }
+
+       if (changed & BSS_CHANGED_BSSID) {
+               if (cl_msg_tx_set_bssid(cl_hw, info->bssid, cl_vif->vif_index))
+                       return;
+       }
+
+       if (changed & BSS_CHANGED_BEACON_INT) {
+               if (vif->type == NL80211_IFTYPE_AP ||
+                   cl_hw->iface_conf == CL_IFCONF_MESH_ONLY) {
+                       if (cl_msg_tx_set_beacon_int(cl_hw, info->beacon_int,
+                                                    cl_vif->vif_index))
+                               return;
+                       if (cl_msg_tx_dtim(cl_hw, info->dtim_period))
+                               return;
+               }
+
+               if (vif->type == NL80211_IFTYPE_MESH_POINT &&
+                   cl_hw->iface_conf == CL_IFCONF_MESH_AP)
+                       cl_hw->mesh_tbtt_div = (info->beacon_int /
+                                           cl_hw->conf->ha_beacon_int);
+       }
+
+       if (changed & BSS_CHANGED_BASIC_RATES) {
+               int shift = hw->wiphy->bands[hw->conf.chandef.chan->band]->bitrates[0].hw_value;
+
+               if (cl_msg_tx_set_basic_rates(cl_hw, info->basic_rates << shift))
+                       return;
+               /* TODO: check if cl_msg_tx_set_mode() should be called */
+       }
+
+       if (changed & BSS_CHANGED_ERP_SLOT) {
+               /*
+                * We must be in 11g mode here
+                * TODO: we can add a check on the mode
+                */
+               if (cl_msg_tx_set_slottime(cl_hw, info->use_short_slot))
+                       return;
+       }
+
+       if (changed & BSS_CHANGED_BANDWIDTH)
+               cl_wrs_api_bss_set_bw(cl_hw, width_to_bw(info->chandef.width));
+}
+
+int cl_ops_start_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+{
+       struct cl_hw *cl_hw = hw->priv;
+       u8 num_ap = cl_tcv_config_get_num_ap(cl_hw);
+
+       /*
+        * Increase num_ap_started counter and turn radio on only after
+        * all AP's were started.
+        */
+       cl_hw->num_ap_started++;
+
+       if (num_ap == cl_hw->num_ap_started &&
+           cl_hw->conf->ce_radio_on) {
+               cl_radio_on(cl_hw);
+
+               return 0;
+       }
+
+       /*
+        * Set active state when cl_ops_start_ap() is called not during first driver start
+        * but rather after removing all interfaces and then doing up again to one interface.
+        */
+       if (cl_radio_is_on(cl_hw) && !cl_recovery_in_progress(cl_hw))
+               cl_msg_tx_set_idle(cl_hw, MAC_ACTIVE);
+
+       return 0;
+}
+
+void cl_ops_stop_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+{
+       struct cl_hw *cl_hw = hw->priv;
+
+       cl_hw->num_ap_started--;
+}
+
+u64 cl_ops_prepare_multicast(struct ieee80211_hw *hw, struct netdev_hw_addr_list *mc_list)
+{
+       return netdev_hw_addr_list_count(mc_list);
+}
+
+void cl_ops_configure_filter(struct ieee80211_hw *hw, u32 changed_flags,
+                            u32 *total_flags, u64 multicast)
+{
+       /*
+        * configure_filter: Configure the device's RX filter.
+        * See the section "Frame filtering" for more information.
+        * This callback must be implemented and can sleep.
+        */
+       struct cl_hw *cl_hw = hw->priv;
+
+       cl_dbg_trace(cl_hw, "total_flags = 0x%08x\n", *total_flags);
+
+       /*
+        * Reset our filter flags since our start/stop ops reset
+        * the programmed settings
+        */
+       if (!test_bit(CL_DEV_STARTED, &cl_hw->drv_flags)) {
+               *total_flags = 0;
+               return;
+       }
+
+       if (multicast)
+               *total_flags |= FIF_ALLMULTI;
+       else
+               *total_flags &= ~FIF_ALLMULTI;
+
+       /* TODO optimize with changed_flags vs multicast */
+       cl_msg_tx_set_filter(cl_hw, *total_flags, false);
+
+       /* TODO update total_flags with truly set flags */
+       *total_flags &= ~(1 << 31);
+}
+
+int cl_ops_set_key(struct ieee80211_hw *hw,
+                  enum set_key_cmd cmd,
+                  struct ieee80211_vif *vif,
+                  struct ieee80211_sta *sta,
+                  struct ieee80211_key_conf *key)
+{
+       struct cl_hw *cl_hw = hw->priv;
+
+       return cl_key_set(cl_hw, cmd, vif, sta, key);
+}
+
+void cl_ops_sw_scan_start(struct ieee80211_hw *hw,
+                         struct ieee80211_vif *vif,
+                         const u8 *mac_addr)
+{
+       struct cl_hw *cl_hw = hw->priv;
+
+       if (cl_hw->conf->ce_radio_on &&
+           cl_radio_is_off(cl_hw) &&
+           vif->type == NL80211_IFTYPE_STATION)
+               cl_radio_on(cl_hw);
+}
+
+int cl_ops_sta_state(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta,
+                    enum ieee80211_sta_state old_state, enum ieee80211_sta_state new_state)
+{
+       struct cl_hw *cl_hw = hw->priv;
+       int error = 0;
+
+       if (old_state == new_state)
+               return 0;
+
+       if (old_state == IEEE80211_STA_NOTEXIST &&
+           new_state == IEEE80211_STA_NONE) {
+               struct sta_info *stainfo = container_of(sta, struct sta_info, sta);
+
+               cl_sta_init_stainfo(cl_hw, stainfo);
+       } else if (old_state == IEEE80211_STA_AUTH &&
+                  new_state == IEEE80211_STA_ASSOC) {
+               error = cl_sta_add(cl_hw, vif, sta);
+       } else if (old_state == IEEE80211_STA_ASSOC &&
+                  new_state == IEEE80211_STA_AUTH) {
+               cl_sta_remove(cl_hw, vif, sta);
+       } else if (old_state == IEEE80211_STA_ASSOC &&
+                  new_state == IEEE80211_STA_AUTHORIZED) {
+               /* Do nothing, yet */
+       }
+
+       return error;
+}
+
+void cl_ops_sta_notify(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+                      enum sta_notify_cmd cmd, struct ieee80211_sta *sta)
+{
+       struct cl_hw *cl_hw = (struct cl_hw *)hw->priv;
+       struct cl_sta *cl_sta = IEEE80211_STA_TO_CL_STA(sta);
+       bool is_ps = (bool)!cmd;
+
+       cl_sta_ps_notify(cl_hw, cl_sta, is_ps);
+}
+
+int cl_ops_conf_tx(struct ieee80211_hw *hw,
+                  struct ieee80211_vif *vif,
+                  u16 ac_queue,
+                  const struct ieee80211_tx_queue_params *params)
+{
+       /*
+        * Configure TX queue parameters (EDCF (aifs, cw_min, cw_max),
+        * bursting) for a hardware TX queue.
+        * Returns a negative error code on failure.
+        * The callback can sleep.
+        */
+
+       /* We only handle STA edca here */
+       if (vif->type == NL80211_IFTYPE_STATION) {
+               struct cl_hw *cl_hw = hw->priv;
+               struct ieee80211_he_mu_edca_param_ac_rec mu_edca = {0};
+               struct edca_params edca_params = {
+                       .aifsn = (u8)(params->aifs),
+                       .cw_min = (u8)(ilog2(params->cw_min + 1)),
+                       .cw_max = (u8)(ilog2(params->cw_max + 1)),
+                       .txop = (u8)(params->txop)
+               };
+
+               if (cl_hw->conf->ce_wireless_mode > WIRELESS_MODE_HT_VHT)
+                       memcpy(&mu_edca, &params->mu_edca_param_rec, sizeof(mu_edca));
+
+               cl_edca_set(cl_hw, cl_ac2edca[ac_queue], &edca_params, &mu_edca);
+       }
+       return 0;
+}
+
+void cl_ops_sta_rc_update(struct ieee80211_hw *hw,
+                         struct ieee80211_vif *vif,
+                         struct ieee80211_sta *sta,
+                         u32 changed)
+{
+       struct cl_hw *cl_hw = (struct cl_hw *)hw->priv;
+
+       if (changed & IEEE80211_RC_BW_CHANGED)
+               cl_wrs_api_bw_changed(cl_hw, sta, sta->bandwidth);
+
+       if (changed & IEEE80211_RC_SMPS_CHANGED) {
+               struct sta_info *stainfo = container_of(sta, struct sta_info, sta);
+
+               cl_wrs_api_set_smps_mode(cl_hw, sta, stainfo->cur_max_bandwidth);
+       }
+
+       WARN_ON(sta->rx_nss == 0);
+       if (changed & IEEE80211_RC_NSS_CHANGED) {
+               u8 nss = min_t(u8, sta->rx_nss, WRS_SS_MAX) - 1;
+
+               cl_wrs_api_nss_changed(cl_hw, sta, nss);
+       }
+}
+
+int cl_ops_ampdu_action(struct ieee80211_hw *hw,
+                       struct ieee80211_vif *vif,
+                       struct ieee80211_ampdu_params *params)
+{
+       struct cl_hw *cl_hw = (struct cl_hw *)hw->priv;
+       struct cl_sta *cl_sta = IEEE80211_STA_TO_CL_STA(params->sta);
+       int ret = 0;
+
+       switch (params->action) {
+       case IEEE80211_AMPDU_RX_START:
+               ret = cl_ampdu_rx_start(cl_hw, cl_sta, params->tid,
+                                       params->ssn, params->buf_size);
+               break;
+       case IEEE80211_AMPDU_RX_STOP:
+               cl_ampdu_rx_stop(cl_hw, cl_sta, params->tid);
+               break;
+       case IEEE80211_AMPDU_TX_START:
+               ret = cl_ampdu_tx_start(cl_hw, vif, cl_sta, params->tid,
+                                       params->ssn);
+               break;
+       case IEEE80211_AMPDU_TX_OPERATIONAL:
+               ret = cl_ampdu_tx_operational(cl_hw, cl_sta, params->tid,
+                                             params->buf_size, params->amsdu);
+               break;
+       case IEEE80211_AMPDU_TX_STOP_CONT:
+       case IEEE80211_AMPDU_TX_STOP_FLUSH:
+       case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
+               ret = cl_ampdu_tx_stop(cl_hw, vif, params->action, cl_sta,
+                                      params->tid);
+               break;
+       default:
+               pr_warn("Error: Unknown AMPDU action (%d)\n", params->action);
+       }
+
+       return ret;
+}
+
+int cl_ops_post_channel_switch(struct ieee80211_hw *hw,
+                              struct ieee80211_vif *vif)
+{
+       /* TODO: Handle event */
+
+       return 0;
+}
+
+void cl_ops_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u32 queues, bool drop)
+{
+       struct cl_hw *cl_hw = hw->priv;
+       int flush_duration;
+
+       if (test_bit(CL_DEV_HW_RESTART, &cl_hw->drv_flags)) {
+               cl_dbg_verbose(cl_hw, ": bypassing (CL_DEV_HW_RESTART set)\n");
+               return;
+       }
+
+       /* Wait for a maximum time of 200ms until all pending frames are flushed */
+       for (flush_duration = 0; flush_duration < 200; flush_duration++) {
+               if (!cl_txq_frames_pending(cl_hw))
+                       return;
+
+               /* Lets sleep and hope for the best */
+               usleep_range(1000, 2000);
+       }
+}
+
+bool cl_ops_tx_frames_pending(struct ieee80211_hw *hw)
+{
+       struct cl_hw *cl_hw = hw->priv;
+
+       return cl_txq_frames_pending(cl_hw);
+}
+
+void cl_ops_reconfig_complete(struct ieee80211_hw *hw,
+                             enum ieee80211_reconfig_type reconfig_type)
+{
+       struct cl_hw *cl_hw = hw->priv;
+
+       cl_recovery_reconfig_complete(cl_hw);
+}
+
+int cl_ops_get_txpower(struct ieee80211_hw *hw, struct ieee80211_vif *vif, int *dbm)
+{
+       struct cl_hw *cl_hw = hw->priv;
+
+       if (cl_hw->phy_data_info.data)
+               *dbm = cl_power_get_max(cl_hw);
+       else
+               *dbm = 0;
+
+       return 0;
+}
+
+int cl_ops_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
+{
+       return 0;
+}
+
+static void cl_ops_mgd_assoc(struct cl_hw *cl_hw, struct ieee80211_vif *vif)
+{
+       struct ieee80211_sub_if_data *sdata = container_of(vif, struct ieee80211_sub_if_data, vif);
+       struct cl_vif *cl_vif = (struct cl_vif *)vif->drv_priv;
+       struct ieee80211_sta *sta = ieee80211_find_sta(vif, sdata->u.mgd.bssid);
+
+       if (!sta) {
+               /* Should never happen */
+               cl_dbg_verbose(cl_hw, "sta is NULL !!!\n");
+               return;
+       }
+
+       cl_sta_mgd_add(cl_hw, cl_vif, sta);
+
+       if (cl_hw->iface_conf == CL_IFCONF_REPEATER) {
+               cl_vif_ap_tx_enable(cl_hw, true);
+               set_bit(CL_DEV_REPEATER, &cl_hw->drv_flags);
+       }
+}
+
+static void cl_ops_mgd_disassoc(struct cl_hw *cl_hw)
+{
+       if (cl_hw->iface_conf == CL_IFCONF_REPEATER) {
+               cl_sta_disassociate_ap_iface(cl_hw);
+               cl_vif_ap_tx_enable(cl_hw, false);
+               clear_bit(CL_DEV_REPEATER, &cl_hw->drv_flags);
+       }
+}
+
+void cl_ops_event_callback(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+                          const struct ieee80211_event *event)
+{
+       struct cl_hw *cl_hw = hw->priv;
+
+       if (event->type == MLME_EVENT) {
+               if (event->u.mlme.data == ASSOC_EVENT &&
+                   event->u.mlme.status == MLME_SUCCESS)
+                       cl_ops_mgd_assoc(cl_hw, vif);
+               else if (event->u.mlme.data == DEAUTH_TX_EVENT ||
+                        event->u.mlme.data == DEAUTH_RX_EVENT)
+                       cl_ops_mgd_disassoc(cl_hw);
+       }
+}
+
+/* This function is required for PS flow - do not remove */
+int cl_ops_set_tim(struct ieee80211_hw *hw, struct ieee80211_sta *sta, bool set)
+{
+       return 0;
+}