aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/wfx/sta.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/wfx/sta.c')
-rw-r--r--drivers/staging/wfx/sta.c1684
1 files changed, 1684 insertions, 0 deletions
diff --git a/drivers/staging/wfx/sta.c b/drivers/staging/wfx/sta.c
new file mode 100644
index 000000000000..29848a202ab4
--- /dev/null
+++ b/drivers/staging/wfx/sta.c
@@ -0,0 +1,1684 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Implementation of mac80211 API.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#include <net/mac80211.h>
+
+#include "sta.h"
+#include "wfx.h"
+#include "fwio.h"
+#include "bh.h"
+#include "key.h"
+#include "scan.h"
+#include "debug.h"
+#include "hif_tx.h"
+#include "hif_tx_mib.h"
+
+#define TXOP_UNIT 32
+#define HIF_MAX_ARP_IP_ADDRTABLE_ENTRIES 2
+
+static u32 wfx_rate_mask_to_hw(struct wfx_dev *wdev, u32 rates)
+{
+ int i;
+ u32 ret = 0;
+ // WFx only support 2GHz
+ struct ieee80211_supported_band *sband = wdev->hw->wiphy->bands[NL80211_BAND_2GHZ];
+
+ for (i = 0; i < sband->n_bitrates; i++) {
+ if (rates & BIT(i)) {
+ if (i >= sband->n_bitrates)
+ dev_warn(wdev->dev, "unsupported basic rate\n");
+ else
+ ret |= BIT(sband->bitrates[i].hw_value);
+ }
+ }
+ return ret;
+}
+
+static void __wfx_free_event_queue(struct list_head *list)
+{
+ struct wfx_hif_event *event, *tmp;
+
+ list_for_each_entry_safe(event, tmp, list, link) {
+ list_del(&event->link);
+ kfree(event);
+ }
+}
+
+static void wfx_free_event_queue(struct wfx_vif *wvif)
+{
+ LIST_HEAD(list);
+
+ spin_lock(&wvif->event_queue_lock);
+ list_splice_init(&wvif->event_queue, &list);
+ spin_unlock(&wvif->event_queue_lock);
+
+ __wfx_free_event_queue(&list);
+}
+
+void wfx_cqm_bssloss_sm(struct wfx_vif *wvif, int init, int good, int bad)
+{
+ int tx = 0;
+
+ mutex_lock(&wvif->bss_loss_lock);
+ wvif->delayed_link_loss = 0;
+ cancel_work_sync(&wvif->bss_params_work);
+
+ /* If we have a pending unjoin */
+ if (wvif->delayed_unjoin)
+ goto end;
+
+ if (init) {
+ schedule_delayed_work(&wvif->bss_loss_work, HZ);
+ wvif->bss_loss_state = 0;
+
+ if (!atomic_read(&wvif->wdev->tx_lock))
+ tx = 1;
+ } else if (good) {
+ cancel_delayed_work_sync(&wvif->bss_loss_work);
+ wvif->bss_loss_state = 0;
+ schedule_work(&wvif->bss_params_work);
+ } else if (bad) {
+ /* FIXME Should we just keep going until we time out? */
+ if (wvif->bss_loss_state < 3)
+ tx = 1;
+ } else {
+ cancel_delayed_work_sync(&wvif->bss_loss_work);
+ wvif->bss_loss_state = 0;
+ }
+
+ /* Spit out a NULL packet to our AP if necessary */
+ // FIXME: call ieee80211_beacon_loss/ieee80211_connection_loss instead
+ if (tx) {
+ struct sk_buff *skb;
+
+ wvif->bss_loss_state++;
+
+ skb = ieee80211_nullfunc_get(wvif->wdev->hw, wvif->vif, false);
+ if (!skb)
+ goto end;
+ memset(IEEE80211_SKB_CB(skb), 0,
+ sizeof(*IEEE80211_SKB_CB(skb)));
+ IEEE80211_SKB_CB(skb)->control.vif = wvif->vif;
+ IEEE80211_SKB_CB(skb)->driver_rates[0].idx = 0;
+ IEEE80211_SKB_CB(skb)->driver_rates[0].count = 1;
+ IEEE80211_SKB_CB(skb)->driver_rates[1].idx = -1;
+ wfx_tx(wvif->wdev->hw, NULL, skb);
+ }
+end:
+ mutex_unlock(&wvif->bss_loss_lock);
+}
+
+static int wfx_set_uapsd_param(struct wfx_vif *wvif,
+ const struct wfx_edca_params *arg)
+{
+ /* Here's the mapping AC [queue, bit]
+ * VO [0,3], VI [1, 2], BE [2, 1], BK [3, 0]
+ */
+
+ if (arg->uapsd_enable[IEEE80211_AC_VO])
+ wvif->uapsd_info.trig_voice = 1;
+ else
+ wvif->uapsd_info.trig_voice = 0;
+
+ if (arg->uapsd_enable[IEEE80211_AC_VI])
+ wvif->uapsd_info.trig_video = 1;
+ else
+ wvif->uapsd_info.trig_video = 0;
+
+ if (arg->uapsd_enable[IEEE80211_AC_BE])
+ wvif->uapsd_info.trig_be = 1;
+ else
+ wvif->uapsd_info.trig_be = 0;
+
+ if (arg->uapsd_enable[IEEE80211_AC_BK])
+ wvif->uapsd_info.trig_bckgrnd = 1;
+ else
+ wvif->uapsd_info.trig_bckgrnd = 0;
+
+ /* Currently pseudo U-APSD operation is not supported, so setting
+ * MinAutoTriggerInterval, MaxAutoTriggerInterval and
+ * AutoTriggerStep to 0
+ */
+ wvif->uapsd_info.min_auto_trigger_interval = 0;
+ wvif->uapsd_info.max_auto_trigger_interval = 0;
+ wvif->uapsd_info.auto_trigger_step = 0;
+
+ return hif_set_uapsd_info(wvif, &wvif->uapsd_info);
+}
+
+int wfx_fwd_probe_req(struct wfx_vif *wvif, bool enable)
+{
+ wvif->fwd_probe_req = enable;
+ return hif_set_rx_filter(wvif, wvif->filter_bssid,
+ wvif->fwd_probe_req);
+}
+
+static int wfx_set_mcast_filter(struct wfx_vif *wvif,
+ struct wfx_grp_addr_table *fp)
+{
+ int i, ret;
+ struct hif_mib_config_data_filter config = { };
+ struct hif_mib_set_data_filtering filter_data = { };
+ struct hif_mib_mac_addr_data_frame_condition filter_addr_val = { };
+ struct hif_mib_uc_mc_bc_data_frame_condition filter_addr_type = { };
+
+ // Temporary workaround for filters
+ return hif_set_data_filtering(wvif, &filter_data);
+
+ if (!fp->enable) {
+ filter_data.enable = 0;
+ return hif_set_data_filtering(wvif, &filter_data);
+ }
+
+ // A1 Address match on list
+ for (i = 0; i < fp->num_addresses; i++) {
+ filter_addr_val.condition_idx = i;
+ filter_addr_val.address_type = HIF_MAC_ADDR_A1;
+ ether_addr_copy(filter_addr_val.mac_address,
+ fp->address_list[i]);
+ ret = hif_set_mac_addr_condition(wvif,
+ &filter_addr_val);
+ if (ret)
+ return ret;
+ config.mac_cond |= 1 << i;
+ }
+
+ // Accept unicast and broadcast
+ filter_addr_type.condition_idx = 0;
+ filter_addr_type.param.bits.type_unicast = 1;
+ filter_addr_type.param.bits.type_broadcast = 1;
+ ret = hif_set_uc_mc_bc_condition(wvif, &filter_addr_type);
+ if (ret)
+ return ret;
+
+ config.uc_mc_bc_cond = 1;
+ config.filter_idx = 0; // TODO #define MULTICAST_FILTERING 0
+ config.enable = 1;
+ ret = hif_set_config_data_filter(wvif, &config);
+ if (ret)
+ return ret;
+
+ // discard all data frames except match filter
+ filter_data.enable = 1;
+ filter_data.default_filter = 1; // discard all
+ ret = hif_set_data_filtering(wvif, &filter_data);
+
+ return ret;
+}
+
+void wfx_update_filtering(struct wfx_vif *wvif)
+{
+ int ret;
+ bool is_sta = wvif->vif && NL80211_IFTYPE_STATION == wvif->vif->type;
+ bool filter_bssid = wvif->filter_bssid;
+ bool fwd_probe_req = wvif->fwd_probe_req;
+ struct hif_mib_bcn_filter_enable bf_ctrl;
+ struct hif_ie_table_entry filter_ies[] = {
+ {
+ .ie_id = WLAN_EID_VENDOR_SPECIFIC,
+ .has_changed = 1,
+ .no_longer = 1,
+ .has_appeared = 1,
+ .oui = { 0x50, 0x6F, 0x9A },
+ }, {
+ .ie_id = WLAN_EID_HT_OPERATION,
+ .has_changed = 1,
+ .no_longer = 1,
+ .has_appeared = 1,
+ }, {
+ .ie_id = WLAN_EID_ERP_INFO,
+ .has_changed = 1,
+ .no_longer = 1,
+ .has_appeared = 1,
+ }
+ };
+ int n_filter_ies;
+
+ if (wvif->state == WFX_STATE_PASSIVE)
+ return;
+
+ if (wvif->disable_beacon_filter) {
+ bf_ctrl.enable = 0;
+ bf_ctrl.bcn_count = 1;
+ n_filter_ies = 0;
+ } else if (!is_sta) {
+ bf_ctrl.enable = HIF_BEACON_FILTER_ENABLE |
+ HIF_BEACON_FILTER_AUTO_ERP;
+ bf_ctrl.bcn_count = 0;
+ n_filter_ies = 2;
+ } else {
+ bf_ctrl.enable = HIF_BEACON_FILTER_ENABLE;
+ bf_ctrl.bcn_count = 0;
+ n_filter_ies = 3;
+ }
+
+ ret = hif_set_rx_filter(wvif, filter_bssid, fwd_probe_req);
+ if (!ret)
+ ret = hif_set_beacon_filter_table(wvif, n_filter_ies,
+ filter_ies);
+ if (!ret)
+ ret = hif_beacon_filter_control(wvif, bf_ctrl.enable,
+ bf_ctrl.bcn_count);
+ if (!ret)
+ ret = wfx_set_mcast_filter(wvif, &wvif->mcast_filter);
+ if (ret)
+ dev_err(wvif->wdev->dev, "update filtering failed: %d\n", ret);
+}
+
+static void wfx_update_filtering_work(struct work_struct *work)
+{
+ struct wfx_vif *wvif = container_of(work, struct wfx_vif,
+ update_filtering_work);
+
+ wfx_update_filtering(wvif);
+}
+
+u64 wfx_prepare_multicast(struct ieee80211_hw *hw,
+ struct netdev_hw_addr_list *mc_list)
+{
+ int i;
+ struct netdev_hw_addr *ha;
+ struct wfx_vif *wvif = NULL;
+ struct wfx_dev *wdev = hw->priv;
+ int count = netdev_hw_addr_list_count(mc_list);
+
+ while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
+ memset(&wvif->mcast_filter, 0x00, sizeof(wvif->mcast_filter));
+ if (!count ||
+ count > ARRAY_SIZE(wvif->mcast_filter.address_list))
+ continue;
+
+ i = 0;
+ netdev_hw_addr_list_for_each(ha, mc_list) {
+ ether_addr_copy(wvif->mcast_filter.address_list[i],
+ ha->addr);
+ i++;
+ }
+ wvif->mcast_filter.enable = true;
+ wvif->mcast_filter.num_addresses = count;
+ }
+
+ return 0;
+}
+
+void wfx_configure_filter(struct ieee80211_hw *hw,
+ unsigned int changed_flags,
+ unsigned int *total_flags,
+ u64 unused)
+{
+ struct wfx_vif *wvif = NULL;
+ struct wfx_dev *wdev = hw->priv;
+
+ *total_flags &= FIF_OTHER_BSS | FIF_FCSFAIL | FIF_PROBE_REQ;
+
+ while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
+ down(&wvif->scan.lock);
+ wvif->filter_bssid = (*total_flags &
+ (FIF_OTHER_BSS | FIF_PROBE_REQ)) ? 0 : 1;
+ wvif->disable_beacon_filter = !(*total_flags & FIF_PROBE_REQ);
+ wfx_fwd_probe_req(wvif, true);
+ wfx_update_filtering(wvif);
+ up(&wvif->scan.lock);
+ }
+}
+
+int wfx_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ u16 queue, const struct ieee80211_tx_queue_params *params)
+{
+ struct wfx_dev *wdev = hw->priv;
+ struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+ int ret = 0;
+ /* To prevent re-applying PM request OID again and again*/
+ u16 old_uapsd_flags, new_uapsd_flags;
+ struct hif_req_edca_queue_params *edca;
+
+ mutex_lock(&wdev->conf_mutex);
+
+ if (queue < hw->queues) {
+ old_uapsd_flags = *((u16 *) &wvif->uapsd_info);
+ edca = &wvif->edca.params[queue];
+
+ wvif->edca.uapsd_enable[queue] = params->uapsd;
+ edca->aifsn = params->aifs;
+ edca->cw_min = params->cw_min;
+ edca->cw_max = params->cw_max;
+ edca->tx_op_limit = params->txop * TXOP_UNIT;
+ edca->allowed_medium_time = 0;
+ ret = hif_set_edca_queue_params(wvif, edca);
+ if (ret) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (wvif->vif->type == NL80211_IFTYPE_STATION) {
+ ret = wfx_set_uapsd_param(wvif, &wvif->edca);
+ new_uapsd_flags = *((u16 *) &wvif->uapsd_info);
+ if (!ret && wvif->setbssparams_done &&
+ wvif->state == WFX_STATE_STA &&
+ old_uapsd_flags != new_uapsd_flags)
+ ret = wfx_set_pm(wvif, &wvif->powersave_mode);
+ }
+ } else {
+ ret = -EINVAL;
+ }
+
+out:
+ mutex_unlock(&wdev->conf_mutex);
+ return ret;
+}
+
+int wfx_set_pm(struct wfx_vif *wvif, const struct hif_req_set_pm_mode *arg)
+{
+ struct hif_req_set_pm_mode pm = *arg;
+ u16 uapsd_flags;
+ int ret;
+
+ if (wvif->state != WFX_STATE_STA || !wvif->bss_params.aid)
+ return 0;
+
+ memcpy(&uapsd_flags, &wvif->uapsd_info, sizeof(uapsd_flags));
+
+ if (uapsd_flags != 0)
+ pm.pm_mode.fast_psm = 0;
+
+ // Kernel disable PowerSave when multiple vifs are in use. In contrary,
+ // it is absolutly necessary to enable PowerSave for WF200
+ if (wvif_count(wvif->wdev) > 1) {
+ pm.pm_mode.enter_psm = 1;
+ pm.pm_mode.fast_psm = 0;
+ }
+
+ if (!wait_for_completion_timeout(&wvif->set_pm_mode_complete,
+ msecs_to_jiffies(300)))
+ dev_warn(wvif->wdev->dev,
+ "timeout while waiting of set_pm_mode_complete\n");
+ ret = hif_set_pm(wvif, &pm);
+ // FIXME: why ?
+ if (-ETIMEDOUT == wvif->scan.status)
+ wvif->scan.status = 1;
+ return ret;
+}
+
+int wfx_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
+{
+ struct wfx_dev *wdev = hw->priv;
+ struct wfx_vif *wvif = NULL;
+
+ while ((wvif = wvif_iterate(wdev, wvif)) != NULL)
+ hif_rts_threshold(wvif, value);
+ return 0;
+}
+
+/* If successful, LOCKS the TX queue! */
+static int __wfx_flush(struct wfx_dev *wdev, bool drop)
+{
+ int ret;
+
+ for (;;) {
+ if (drop) {
+ wfx_tx_queues_clear(wdev);
+ } else {
+ ret = wait_event_timeout(
+ wdev->tx_queue_stats.wait_link_id_empty,
+ wfx_tx_queues_is_empty(wdev),
+ 2 * HZ);
+ }
+
+ if (!drop && ret <= 0) {
+ ret = -ETIMEDOUT;
+ break;
+ }
+ ret = 0;
+
+ wfx_tx_lock_flush(wdev);
+ if (!wfx_tx_queues_is_empty(wdev)) {
+ /* Highly unlikely: WSM requeued frames. */
+ wfx_tx_unlock(wdev);
+ continue;
+ }
+ break;
+ }
+ return ret;
+}
+
+void wfx_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ u32 queues, bool drop)
+{
+ struct wfx_dev *wdev = hw->priv;
+ struct wfx_vif *wvif;
+
+ if (vif) {
+ wvif = (struct wfx_vif *) vif->drv_priv;
+ if (wvif->vif->type == NL80211_IFTYPE_MONITOR)
+ drop = true;
+ if (wvif->vif->type == NL80211_IFTYPE_AP &&
+ !wvif->enable_beacon)
+ drop = true;
+ }
+
+ // FIXME: only flush requested vif
+ if (!__wfx_flush(wdev, drop))
+ wfx_tx_unlock(wdev);
+}
+
+/* WSM callbacks */
+
+static void wfx_event_report_rssi(struct wfx_vif *wvif, u8 raw_rcpi_rssi)
+{
+ /* RSSI: signed Q8.0, RCPI: unsigned Q7.1
+ * RSSI = RCPI / 2 - 110
+ */
+ int rcpi_rssi;
+ int cqm_evt;
+
+ rcpi_rssi = raw_rcpi_rssi / 2 - 110;
+ if (rcpi_rssi <= wvif->cqm_rssi_thold)
+ cqm_evt = NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW;
+ else
+ cqm_evt = NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH;
+ ieee80211_cqm_rssi_notify(wvif->vif, cqm_evt, rcpi_rssi, GFP_KERNEL);
+}
+
+static void wfx_event_handler_work(struct work_struct *work)
+{
+ struct wfx_vif *wvif =
+ container_of(work, struct wfx_vif, event_handler_work);
+ struct wfx_hif_event *event;
+
+ LIST_HEAD(list);
+
+ spin_lock(&wvif->event_queue_lock);
+ list_splice_init(&wvif->event_queue, &list);
+ spin_unlock(&wvif->event_queue_lock);
+
+ list_for_each_entry(event, &list, link) {
+ switch (event->evt.event_id) {
+ case HIF_EVENT_IND_BSSLOST:
+ cancel_work_sync(&wvif->unjoin_work);
+ if (!down_trylock(&wvif->scan.lock)) {
+ wfx_cqm_bssloss_sm(wvif, 1, 0, 0);
+ up(&wvif->scan.lock);
+ } else {
+ /* Scan is in progress. Delay reporting.
+ * Scan complete will trigger bss_loss_work
+ */
+ wvif->delayed_link_loss = 1;
+ /* Also start a watchdog. */
+ schedule_delayed_work(&wvif->bss_loss_work,
+ 5 * HZ);
+ }
+ break;
+ case HIF_EVENT_IND_BSSREGAINED:
+ wfx_cqm_bssloss_sm(wvif, 0, 0, 0);
+ cancel_work_sync(&wvif->unjoin_work);
+ break;
+ case HIF_EVENT_IND_RCPI_RSSI:
+ wfx_event_report_rssi(wvif,
+ event->evt.event_data.rcpi_rssi);
+ break;
+ case HIF_EVENT_IND_PS_MODE_ERROR:
+ dev_warn(wvif->wdev->dev,
+ "error while processing power save request\n");
+ break;
+ default:
+ dev_warn(wvif->wdev->dev,
+ "unhandled event indication: %.2x\n",
+ event->evt.event_id);
+ break;
+ }
+ }
+ __wfx_free_event_queue(&list);
+}
+
+static void wfx_bss_loss_work(struct work_struct *work)
+{
+ struct wfx_vif *wvif = container_of(work, struct wfx_vif,
+ bss_loss_work.work);
+
+ ieee80211_connection_loss(wvif->vif);
+}
+
+static void wfx_bss_params_work(struct work_struct *work)
+{
+ struct wfx_vif *wvif = container_of(work, struct wfx_vif,
+ bss_params_work);
+
+ mutex_lock(&wvif->wdev->conf_mutex);
+ wvif->bss_params.bss_flags.lost_count_only = 1;
+ hif_set_bss_params(wvif, &wvif->bss_params);
+ wvif->bss_params.bss_flags.lost_count_only = 0;
+ mutex_unlock(&wvif->wdev->conf_mutex);
+}
+
+static void wfx_set_beacon_wakeup_period_work(struct work_struct *work)
+{
+ struct wfx_vif *wvif = container_of(work, struct wfx_vif,
+ set_beacon_wakeup_period_work);
+
+ hif_set_beacon_wakeup_period(wvif, wvif->dtim_period,
+ wvif->dtim_period);
+}
+
+static void wfx_do_unjoin(struct wfx_vif *wvif)
+{
+ mutex_lock(&wvif->wdev->conf_mutex);
+
+ if (atomic_read(&wvif->scan.in_progress)) {
+ if (wvif->delayed_unjoin)
+ dev_dbg(wvif->wdev->dev,
+ "delayed unjoin is already scheduled\n");
+ else
+ wvif->delayed_unjoin = true;
+ goto done;
+ }
+
+ wvif->delayed_link_loss = false;
+
+ if (!wvif->state)
+ goto done;
+
+ if (wvif->state == WFX_STATE_AP)
+ goto done;
+
+ cancel_work_sync(&wvif->update_filtering_work);
+ cancel_work_sync(&wvif->set_beacon_wakeup_period_work);
+ wvif->state = WFX_STATE_PASSIVE;
+
+ /* Unjoin is a reset. */
+ wfx_tx_flush(wvif->wdev);
+ hif_keep_alive_period(wvif, 0);
+ hif_reset(wvif, false);
+ hif_set_output_power(wvif, wvif->wdev->output_power * 10);
+ wvif->dtim_period = 0;
+ hif_set_macaddr(wvif, wvif->vif->addr);
+ wfx_free_event_queue(wvif);
+ cancel_work_sync(&wvif->event_handler_work);
+ wfx_cqm_bssloss_sm(wvif, 0, 0, 0);
+
+ /* Disable Block ACKs */
+ hif_set_block_ack_policy(wvif, 0, 0);
+
+ wvif->disable_beacon_filter = false;
+ wfx_update_filtering(wvif);
+ memset(&wvif->bss_params, 0, sizeof(wvif->bss_params));
+ wvif->setbssparams_done = false;
+ memset(&wvif->ht_info, 0, sizeof(wvif->ht_info));
+
+done:
+ mutex_unlock(&wvif->wdev->conf_mutex);
+}
+
+static void wfx_set_mfp(struct wfx_vif *wvif,
+ struct cfg80211_bss *bss)
+{
+ const int pairwise_cipher_suite_count_offset = 8 / sizeof(u16);
+ const int pairwise_cipher_suite_size = 4 / sizeof(u16);
+ const int akm_suite_size = 4 / sizeof(u16);
+ const u16 *ptr = NULL;
+ bool mfpc = false;
+ bool mfpr = false;
+
+ /* 802.11w protected mgmt frames */
+
+ /* retrieve MFPC and MFPR flags from beacon or PBRSP */
+
+ rcu_read_lock();
+ if (bss)
+ ptr = (const u16 *) ieee80211_bss_get_ie(bss,
+ WLAN_EID_RSN);
+
+ if (ptr) {
+ ptr += pairwise_cipher_suite_count_offset;
+ ptr += 1 + pairwise_cipher_suite_size * *ptr;
+ ptr += 1 + akm_suite_size * *ptr;
+ mfpr = *ptr & BIT(6);
+ mfpc = *ptr & BIT(7);
+ }
+ rcu_read_unlock();
+
+ hif_set_mfp(wvif, mfpc, mfpr);
+}
+
+/* MUST be called with tx_lock held! It will be unlocked for us. */
+static void wfx_do_join(struct wfx_vif *wvif)
+{
+ const u8 *bssid;
+ struct ieee80211_bss_conf *conf = &wvif->vif->bss_conf;
+ struct cfg80211_bss *bss = NULL;
+ struct hif_req_join join = {
+ .mode = conf->ibss_joined ? HIF_MODE_IBSS : HIF_MODE_BSS,
+ .preamble_type = conf->use_short_preamble ? HIF_PREAMBLE_SHORT : HIF_PREAMBLE_LONG,
+ .probe_for_join = 1,
+ .atim_window = 0,
+ .basic_rate_set = wfx_rate_mask_to_hw(wvif->wdev,
+ conf->basic_rates),
+ };
+
+ if (wvif->channel->flags & IEEE80211_CHAN_NO_IR)
+ join.probe_for_join = 0;
+
+ if (wvif->state)
+ wfx_do_unjoin(wvif);
+
+ bssid = wvif->vif->bss_conf.bssid;
+
+ bss = cfg80211_get_bss(wvif->wdev->hw->wiphy, wvif->channel,
+ bssid, NULL, 0,
+ IEEE80211_BSS_TYPE_ANY, IEEE80211_PRIVACY_ANY);
+
+ if (!bss && !conf->ibss_joined) {
+ wfx_tx_unlock(wvif->wdev);
+ return;
+ }
+
+ mutex_lock(&wvif->wdev->conf_mutex);
+
+ /* Under the conf lock: check scan status and
+ * bail out if it is in progress.
+ */
+ if (atomic_read(&wvif->scan.in_progress)) {
+ wfx_tx_unlock(wvif->wdev);
+ goto done_put;
+ }
+
+ /* Sanity check basic rates */
+ if (!join.basic_rate_set)
+ join.basic_rate_set = 7;
+
+ /* Sanity check beacon interval */
+ if (!wvif->beacon_int)
+ wvif->beacon_int = 1;
+
+ join.beacon_interval = wvif->beacon_int;
+
+ // DTIM period will be set on first Beacon
+ wvif->dtim_period = 0;
+
+ join.channel_number = wvif->channel->hw_value;
+ memcpy(join.bssid, bssid, sizeof(join.bssid));
+
+ if (!conf->ibss_joined) {
+ const u8 *ssidie;
+
+ rcu_read_lock();
+ ssidie = ieee80211_bss_get_ie(bss, WLAN_EID_SSID);
+ if (ssidie) {
+ join.ssid_length = ssidie[1];
+ memcpy(join.ssid, &ssidie[2], join.ssid_length);
+ }
+ rcu_read_unlock();
+ }
+
+ wfx_tx_flush(wvif->wdev);
+
+ if (wvif_count(wvif->wdev) <= 1)
+ hif_set_block_ack_policy(wvif, 0xFF, 0xFF);
+
+ wfx_set_mfp(wvif, bss);
+
+ /* Perform actual join */
+ wvif->wdev->tx_burst_idx = -1;
+ if (hif_join(wvif, &join)) {
+ ieee80211_connection_loss(wvif->vif);
+ wvif->join_complete_status = -1;
+ /* Tx lock still held, unjoin will clear it. */
+ if (!schedule_work(&wvif->unjoin_work))
+ wfx_tx_unlock(wvif->wdev);
+ } else {
+ wvif->join_complete_status = 0;
+ if (wvif->vif->type == NL80211_IFTYPE_ADHOC)
+ wvif->state = WFX_STATE_IBSS;
+ else
+ wvif->state = WFX_STATE_PRE_STA;
+ wfx_tx_unlock(wvif->wdev);
+
+ /* Upload keys */
+ wfx_upload_keys(wvif);
+
+ /* Due to beacon filtering it is possible that the
+ * AP's beacon is not known for the mac80211 stack.
+ * Disable filtering temporary to make sure the stack
+ * receives at least one
+ */
+ wvif->disable_beacon_filter = true;
+ }
+ wfx_update_filtering(wvif);
+
+done_put:
+ mutex_unlock(&wvif->wdev->conf_mutex);
+ if (bss)
+ cfg80211_put_bss(wvif->wdev->hw->wiphy, bss);
+}
+
+static void wfx_unjoin_work(struct work_struct *work)
+{
+ struct wfx_vif *wvif = container_of(work, struct wfx_vif, unjoin_work);
+
+ wfx_do_unjoin(wvif);
+ wfx_tx_unlock(wvif->wdev);
+}
+
+int wfx_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta)
+{
+ struct wfx_dev *wdev = hw->priv;
+ struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+ struct wfx_sta_priv *sta_priv = (struct wfx_sta_priv *) &sta->drv_priv;
+ struct wfx_link_entry *entry;
+ struct sk_buff *skb;
+
+ if (wvif->vif->type != NL80211_IFTYPE_AP)
+ return 0;
+
+ sta_priv->vif_id = wvif->id;
+ sta_priv->link_id = wfx_find_link_id(wvif, sta->addr);
+ if (!sta_priv->link_id) {
+ dev_warn(wdev->dev, "mo more link-id available\n");
+ return -ENOENT;
+ }
+
+ entry = &wvif->link_id_db[sta_priv->link_id - 1];
+ spin_lock_bh(&wvif->ps_state_lock);
+ if ((sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK) ==
+ IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK)
+ wvif->sta_asleep_mask |= BIT(sta_priv->link_id);
+ entry->status = WFX_LINK_HARD;
+ while ((skb = skb_dequeue(&entry->rx_queue)))
+ ieee80211_rx_irqsafe(wdev->hw, skb);
+ spin_unlock_bh(&wvif->ps_state_lock);
+ return 0;
+}
+
+int wfx_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta)
+{
+ struct wfx_dev *wdev = hw->priv;
+ struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+ struct wfx_sta_priv *sta_priv = (struct wfx_sta_priv *) &sta->drv_priv;
+ struct wfx_link_entry *entry;
+
+ if (wvif->vif->type != NL80211_IFTYPE_AP || !sta_priv->link_id)
+ return 0;
+
+ entry = &wvif->link_id_db[sta_priv->link_id - 1];
+ spin_lock_bh(&wvif->ps_state_lock);
+ entry->status = WFX_LINK_RESERVE;
+ entry->timestamp = jiffies;
+ wfx_tx_lock(wdev);
+ if (!schedule_work(&wvif->link_id_work))
+ wfx_tx_unlock(wdev);
+ spin_unlock_bh(&wvif->ps_state_lock);
+ flush_work(&wvif->link_id_work);
+ return 0;
+}
+
+static void wfx_set_cts_work(struct work_struct *work)
+{
+ struct wfx_vif *wvif = container_of(work, struct wfx_vif, set_cts_work);
+ u8 erp_ie[3] = { WLAN_EID_ERP_INFO, 1, 0 };
+ struct hif_ie_flags target_frame = {
+ .beacon = 1,
+ };
+
+ mutex_lock(&wvif->wdev->conf_mutex);
+ erp_ie[2] = wvif->erp_info;
+ mutex_unlock(&wvif->wdev->conf_mutex);
+
+ hif_erp_use_protection(wvif, erp_ie[2] & WLAN_ERP_USE_PROTECTION);
+
+ if (wvif->vif->type != NL80211_IFTYPE_STATION)
+ hif_update_ie(wvif, &target_frame, erp_ie, sizeof(erp_ie));
+}
+
+static int wfx_start_ap(struct wfx_vif *wvif)
+{
+ int ret;
+ struct ieee80211_bss_conf *conf = &wvif->vif->bss_conf;
+ struct hif_req_start start = {
+ .channel_number = wvif->channel->hw_value,
+ .beacon_interval = conf->beacon_int,
+ .dtim_period = conf->dtim_period,
+ .preamble_type = conf->use_short_preamble ? HIF_PREAMBLE_SHORT : HIF_PREAMBLE_LONG,
+ .basic_rate_set = wfx_rate_mask_to_hw(wvif->wdev,
+ conf->basic_rates),
+ };
+
+ memset(start.ssid, 0, sizeof(start.ssid));
+ if (!conf->hidden_ssid) {
+ start.ssid_length = conf->ssid_len;
+ memcpy(start.ssid, conf->ssid, start.ssid_length);
+ }
+
+ wvif->beacon_int = conf->beacon_int;
+ wvif->dtim_period = conf->dtim_period;
+
+ memset(&wvif->link_id_db, 0, sizeof(wvif->link_id_db));
+
+ wvif->wdev->tx_burst_idx = -1;
+ ret = hif_start(wvif, &start);
+ if (!ret)
+ ret = wfx_upload_keys(wvif);
+ if (!ret) {
+ if (wvif_count(wvif->wdev) <= 1)
+ hif_set_block_ack_policy(wvif, 0xFF, 0xFF);
+ wvif->state = WFX_STATE_AP;
+ wfx_update_filtering(wvif);
+ }
+ return ret;
+}
+
+static int wfx_update_beaconing(struct wfx_vif *wvif)
+{
+ struct ieee80211_bss_conf *conf = &wvif->vif->bss_conf;
+
+ if (wvif->vif->type == NL80211_IFTYPE_AP) {
+ /* TODO: check if changed channel, band */
+ if (wvif->state != WFX_STATE_AP ||
+ wvif->beacon_int != conf->beacon_int) {
+ wfx_tx_lock_flush(wvif->wdev);
+ if (wvif->state != WFX_STATE_PASSIVE)
+ hif_reset(wvif, false);
+ wvif->state = WFX_STATE_PASSIVE;
+ wfx_start_ap(wvif);
+ wfx_tx_unlock(wvif->wdev);
+ } else {
+ }
+ }
+ return 0;
+}
+
+static int wfx_upload_beacon(struct wfx_vif *wvif)
+{
+ int ret = 0;
+ struct sk_buff *skb = NULL;
+ struct ieee80211_mgmt *mgmt;
+ struct hif_mib_template_frame *p;
+
+ if (wvif->vif->type == NL80211_IFTYPE_STATION ||
+ wvif->vif->type == NL80211_IFTYPE_MONITOR ||
+ wvif->vif->type == NL80211_IFTYPE_UNSPECIFIED)
+ goto done;
+
+ skb = ieee80211_beacon_get(wvif->wdev->hw, wvif->vif);
+
+ if (!skb)
+ return -ENOMEM;
+
+ p = (struct hif_mib_template_frame *) skb_push(skb, 4);
+ p->frame_type = HIF_TMPLT_BCN;
+ p->init_rate = API_RATE_INDEX_B_1MBPS; /* 1Mbps DSSS */
+ p->frame_length = cpu_to_le16(skb->len - 4);
+
+ ret = hif_set_template_frame(wvif, p);
+
+ skb_pull(skb, 4);
+
+ if (ret)
+ goto done;
+ /* TODO: Distill probe resp; remove TIM and any other beacon-specific
+ * IEs
+ */
+ mgmt = (void *)skb->data;
+ mgmt->frame_control =
+ cpu_to_le16(IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_PROBE_RESP);
+
+ p->frame_type = HIF_TMPLT_PRBRES;
+
+ ret = hif_set_template_frame(wvif, p);
+ wfx_fwd_probe_req(wvif, false);
+
+done:
+ dev_kfree_skb(skb);
+ return ret;
+}
+
+static int wfx_is_ht(const struct wfx_ht_info *ht_info)
+{
+ return ht_info->channel_type != NL80211_CHAN_NO_HT;
+}
+
+static int wfx_ht_greenfield(const struct wfx_ht_info *ht_info)
+{
+ return wfx_is_ht(ht_info) &&
+ (ht_info->ht_cap.cap & IEEE80211_HT_CAP_GRN_FLD) &&
+ !(ht_info->operation_mode &
+ IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT);
+}
+
+static int wfx_ht_ampdu_density(const struct wfx_ht_info *ht_info)
+{
+ if (!wfx_is_ht(ht_info))
+ return 0;
+ return ht_info->ht_cap.ampdu_density;
+}
+
+static void wfx_join_finalize(struct wfx_vif *wvif,
+ struct ieee80211_bss_conf *info)
+{
+ struct ieee80211_sta *sta = NULL;
+ struct hif_mib_set_association_mode association_mode = { };
+
+ if (info->dtim_period)
+ wvif->dtim_period = info->dtim_period;
+ wvif->beacon_int = info->beacon_int;
+
+ rcu_read_lock();
+ if (info->bssid && !info->ibss_joined)
+ sta = ieee80211_find_sta(wvif->vif, info->bssid);
+ if (sta) {
+ wvif->ht_info.ht_cap = sta->ht_cap;
+ wvif->bss_params.operational_rate_set =
+ wfx_rate_mask_to_hw(wvif->wdev, sta->supp_rates[wvif->channel->band]);
+ wvif->ht_info.operation_mode = info->ht_operation_mode;
+ } else {
+ memset(&wvif->ht_info, 0, sizeof(wvif->ht_info));
+ wvif->bss_params.operational_rate_set = -1;
+ }
+ rcu_read_unlock();
+
+ /* Non Greenfield stations present */
+ if (wvif->ht_info.operation_mode &
+ IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT)
+ hif_dual_cts_protection(wvif, true);
+ else
+ hif_dual_cts_protection(wvif, false);
+
+ association_mode.preambtype_use = 1;
+ association_mode.mode = 1;
+ association_mode.rateset = 1;
+ association_mode.spacing = 1;
+ association_mode.preamble_type = info->use_short_preamble ? HIF_PREAMBLE_SHORT : HIF_PREAMBLE_LONG;
+ association_mode.basic_rate_set = cpu_to_le32(wfx_rate_mask_to_hw(wvif->wdev, info->basic_rates));
+ association_mode.mixed_or_greenfield_type = wfx_ht_greenfield(&wvif->ht_info);
+ association_mode.mpdu_start_spacing = wfx_ht_ampdu_density(&wvif->ht_info);
+
+ wfx_cqm_bssloss_sm(wvif, 0, 0, 0);
+ cancel_work_sync(&wvif->unjoin_work);
+
+ wvif->bss_params.beacon_lost_count = 20;
+ wvif->bss_params.aid = info->aid;
+
+ if (wvif->dtim_period < 1)
+ wvif->dtim_period = 1;
+
+ hif_set_association_mode(wvif, &association_mode);
+
+ if (!info->ibss_joined) {
+ hif_keep_alive_period(wvif, 30 /* sec */);
+ hif_set_bss_params(wvif, &wvif->bss_params);
+ wvif->setbssparams_done = true;
+ wfx_set_beacon_wakeup_period_work(&wvif->set_beacon_wakeup_period_work);
+ wfx_set_pm(wvif, &wvif->powersave_mode);
+ }
+}
+
+void wfx_bss_info_changed(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *info,
+ u32 changed)
+{
+ struct wfx_dev *wdev = hw->priv;
+ struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+ bool do_join = false;
+ int i;
+ int nb_arp_addr;
+
+ mutex_lock(&wdev->conf_mutex);
+
+ /* TODO: BSS_CHANGED_QOS */
+ if (changed & BSS_CHANGED_ARP_FILTER) {
+ struct hif_mib_arp_ip_addr_table filter = { };
+
+ nb_arp_addr = info->arp_addr_cnt;
+ if (nb_arp_addr <= 0 || nb_arp_addr > HIF_MAX_ARP_IP_ADDRTABLE_ENTRIES)
+ nb_arp_addr = 0;
+
+ for (i = 0; i < HIF_MAX_ARP_IP_ADDRTABLE_ENTRIES; i++) {
+ filter.condition_idx = i;
+ if (i < nb_arp_addr) {
+ // Caution: type of arp_addr_list[i] is __be32
+ memcpy(filter.ipv4_address,
+ &info->arp_addr_list[i],
+ sizeof(filter.ipv4_address));
+ filter.arp_enable = HIF_ARP_NS_FILTERING_ENABLE;
+ } else {
+ filter.arp_enable = HIF_ARP_NS_FILTERING_DISABLE;
+ }
+ hif_set_arp_ipv4_filter(wvif, &filter);
+ }
+ }
+
+ if (changed &
+ (BSS_CHANGED_BEACON | BSS_CHANGED_AP_PROBE_RESP |
+ BSS_CHANGED_BSSID | BSS_CHANGED_SSID | BSS_CHANGED_IBSS)) {
+ wvif->beacon_int = info->beacon_int;
+ wfx_update_beaconing(wvif);
+ wfx_upload_beacon(wvif);
+ }
+
+ if (changed & BSS_CHANGED_BEACON_ENABLED &&
+ wvif->state != WFX_STATE_IBSS) {
+ if (wvif->enable_beacon != info->enable_beacon) {
+ hif_beacon_transmit(wvif, info->enable_beacon);
+ wvif->enable_beacon = info->enable_beacon;
+ }
+ }
+
+ /* assoc/disassoc, or maybe AID changed */
+ if (changed & BSS_CHANGED_ASSOC) {
+ wfx_tx_lock_flush(wdev);
+ wvif->wep_default_key_id = -1;
+ wfx_tx_unlock(wdev);
+ }
+
+ if (changed & BSS_CHANGED_ASSOC && !info->assoc &&
+ (wvif->state == WFX_STATE_STA || wvif->state == WFX_STATE_IBSS)) {
+ /* Shedule unjoin work */
+ wfx_tx_lock(wdev);
+ if (!schedule_work(&wvif->unjoin_work))
+ wfx_tx_unlock(wdev);
+ } else {
+ if (changed & BSS_CHANGED_BEACON_INT) {
+ if (info->ibss_joined)
+ do_join = true;
+ else if (wvif->state == WFX_STATE_AP)
+ wfx_update_beaconing(wvif);
+ }
+
+ if (changed & BSS_CHANGED_BSSID)
+ do_join = true;
+
+ if (changed &
+ (BSS_CHANGED_ASSOC | BSS_CHANGED_BSSID |
+ BSS_CHANGED_IBSS | BSS_CHANGED_BASIC_RATES |
+ BSS_CHANGED_HT)) {
+ if (info->assoc) {
+ if (wvif->state < WFX_STATE_PRE_STA) {
+ ieee80211_connection_loss(vif);
+ mutex_unlock(&wdev->conf_mutex);
+ return;
+ } else if (wvif->state == WFX_STATE_PRE_STA) {
+ wvif->state = WFX_STATE_STA;
+ }
+ } else {
+ do_join = true;
+ }
+
+ if (info->assoc || info->ibss_joined)
+ wfx_join_finalize(wvif, info);
+ else
+ memset(&wvif->bss_params, 0,
+ sizeof(wvif->bss_params));
+ }
+ }
+
+ /* ERP Protection */
+ if (changed & (BSS_CHANGED_ASSOC |
+ BSS_CHANGED_ERP_CTS_PROT |
+ BSS_CHANGED_ERP_PREAMBLE)) {
+ u32 prev_erp_info = wvif->erp_info;
+
+ if (info->use_cts_prot)
+ wvif->erp_info |= WLAN_ERP_USE_PROTECTION;
+ else if (!(prev_erp_info & WLAN_ERP_NON_ERP_PRESENT))
+ wvif->erp_info &= ~WLAN_ERP_USE_PROTECTION;
+
+ if (info->use_short_preamble)
+ wvif->erp_info |= WLAN_ERP_BARKER_PREAMBLE;
+ else
+ wvif->erp_info &= ~WLAN_ERP_BARKER_PREAMBLE;
+
+ if (prev_erp_info != wvif->erp_info)
+ schedule_work(&wvif->set_cts_work);
+ }
+
+ if (changed & (BSS_CHANGED_ASSOC | BSS_CHANGED_ERP_SLOT))
+ hif_slot_time(wvif, info->use_short_slot ? 9 : 20);
+
+ if (changed & (BSS_CHANGED_ASSOC | BSS_CHANGED_CQM)) {
+ struct hif_mib_rcpi_rssi_threshold th = {
+ .rolling_average_count = 8,
+ .detection = 1,
+ };
+
+ wvif->cqm_rssi_thold = info->cqm_rssi_thold;
+
+ if (!info->cqm_rssi_thold && !info->cqm_rssi_hyst) {
+ th.upperthresh = 1;
+ th.lowerthresh = 1;
+ } else {
+ /* FIXME It's not a correct way of setting threshold.
+ * Upper and lower must be set equal here and adjusted
+ * in callback. However current implementation is much
+ * more reliable and stable.
+ */
+ /* RSSI: signed Q8.0, RCPI: unsigned Q7.1
+ * RSSI = RCPI / 2 - 110
+ */
+ th.upper_threshold = info->cqm_rssi_thold + info->cqm_rssi_hyst;
+ th.upper_threshold = (th.upper_threshold + 110) * 2;
+ th.lower_threshold = info->cqm_rssi_thold;
+ th.lower_threshold = (th.lower_threshold + 110) * 2;
+ }
+ hif_set_rcpi_rssi_threshold(wvif, &th);
+ }
+
+ if (changed & BSS_CHANGED_TXPOWER &&
+ info->txpower != wdev->output_power) {
+ wdev->output_power = info->txpower;
+ hif_set_output_power(wvif, wdev->output_power * 10);
+ }
+ mutex_unlock(&wdev->conf_mutex);
+
+ if (do_join) {
+ wfx_tx_lock_flush(wdev);
+ wfx_do_join(wvif); /* Will unlock it for us */
+ }
+}
+
+static void wfx_ps_notify(struct wfx_vif *wvif, enum sta_notify_cmd notify_cmd,
+ int link_id)
+{
+ u32 bit, prev;
+
+ spin_lock_bh(&wvif->ps_state_lock);
+ /* Zero link id means "for all link IDs" */
+ if (link_id) {
+ bit = BIT(link_id);
+ } else if (notify_cmd != STA_NOTIFY_AWAKE) {
+ dev_warn(wvif->wdev->dev, "unsupported notify command\n");
+ bit = 0;
+ } else {
+ bit = wvif->link_id_map;
+ }
+ prev = wvif->sta_asleep_mask & bit;
+
+ switch (notify_cmd) {
+ case STA_NOTIFY_SLEEP:
+ if (!prev) {
+ if (wvif->mcast_buffered && !wvif->sta_asleep_mask)
+ schedule_work(&wvif->mcast_start_work);
+ wvif->sta_asleep_mask |= bit;
+ }
+ break;
+ case STA_NOTIFY_AWAKE:
+ if (prev) {
+ wvif->sta_asleep_mask &= ~bit;
+ wvif->pspoll_mask &= ~bit;
+ if (link_id && !wvif->sta_asleep_mask)
+ schedule_work(&wvif->mcast_stop_work);
+ wfx_bh_request_tx(wvif->wdev);
+ }
+ break;
+ }
+ spin_unlock_bh(&wvif->ps_state_lock);
+}
+
+void wfx_sta_notify(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ enum sta_notify_cmd notify_cmd, struct ieee80211_sta *sta)
+{
+ struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+ struct wfx_sta_priv *sta_priv = (struct wfx_sta_priv *) &sta->drv_priv;
+
+ wfx_ps_notify(wvif, notify_cmd, sta_priv->link_id);
+}
+
+static int wfx_set_tim_impl(struct wfx_vif *wvif, bool aid0_bit_set)
+{
+ struct sk_buff *skb;
+ struct hif_ie_flags target_frame = {
+ .beacon = 1,
+ };
+ u16 tim_offset, tim_length;
+ u8 *tim_ptr;
+
+ skb = ieee80211_beacon_get_tim(wvif->wdev->hw, wvif->vif,
+ &tim_offset, &tim_length);
+ if (!skb) {
+ if (!__wfx_flush(wvif->wdev, true))
+ wfx_tx_unlock(wvif->wdev);
+ return -ENOENT;
+ }
+ tim_ptr = skb->data + tim_offset;
+
+ if (tim_offset && tim_length >= 6) {
+ /* Ignore DTIM count from mac80211:
+ * firmware handles DTIM internally.
+ */
+ tim_ptr[2] = 0;
+
+ /* Set/reset aid0 bit */
+ if (aid0_bit_set)
+ tim_ptr[4] |= 1;
+ else
+ tim_ptr[4] &= ~1;
+ }
+
+ hif_update_ie(wvif, &target_frame, tim_ptr, tim_length);
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+static void wfx_set_tim_work(struct work_struct *work)
+{
+ struct wfx_vif *wvif = container_of(work, struct wfx_vif, set_tim_work);
+
+ wfx_set_tim_impl(wvif, wvif->aid0_bit_set);
+}
+
+int wfx_set_tim(struct ieee80211_hw *hw, struct ieee80211_sta *sta, bool set)
+{
+ struct wfx_dev *wdev = hw->priv;
+ struct wfx_sta_priv *sta_dev = (struct wfx_sta_priv *) &sta->drv_priv;
+ struct wfx_vif *wvif = wdev_to_wvif(wdev, sta_dev->vif_id);
+
+ schedule_work(&wvif->set_tim_work);
+ return 0;
+}
+
+static void wfx_mcast_start_work(struct work_struct *work)
+{
+ struct wfx_vif *wvif = container_of(work, struct wfx_vif,
+ mcast_start_work);
+ long tmo = wvif->dtim_period * TU_TO_JIFFIES(wvif->beacon_int + 20);
+
+ cancel_work_sync(&wvif->mcast_stop_work);
+ if (!wvif->aid0_bit_set) {
+ wfx_tx_lock_flush(wvif->wdev);
+ wfx_set_tim_impl(wvif, true);
+ wvif->aid0_bit_set = true;
+ mod_timer(&wvif->mcast_timeout, jiffies + tmo);
+ wfx_tx_unlock(wvif->wdev);
+ }
+}
+
+static void wfx_mcast_stop_work(struct work_struct *work)
+{
+ struct wfx_vif *wvif = container_of(work, struct wfx_vif,
+ mcast_stop_work);
+
+ if (wvif->aid0_bit_set) {
+ del_timer_sync(&wvif->mcast_timeout);
+ wfx_tx_lock_flush(wvif->wdev);
+ wvif->aid0_bit_set = false;
+ wfx_set_tim_impl(wvif, false);
+ wfx_tx_unlock(wvif->wdev);
+ }
+}
+
+static void wfx_mcast_timeout(struct timer_list *t)
+{
+ struct wfx_vif *wvif = from_timer(wvif, t, mcast_timeout);
+
+ dev_warn(wvif->wdev->dev, "multicast delivery timeout\n");
+ spin_lock_bh(&wvif->ps_state_lock);
+ wvif->mcast_tx = wvif->aid0_bit_set && wvif->mcast_buffered;
+ if (wvif->mcast_tx)
+ wfx_bh_request_tx(wvif->wdev);
+ spin_unlock_bh(&wvif->ps_state_lock);
+}
+
+int wfx_ampdu_action(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_ampdu_params *params)
+{
+ /* Aggregation is implemented fully in firmware,
+ * including block ack negotiation. Do not allow
+ * mac80211 stack to do anything: it interferes with
+ * the firmware.
+ */
+
+ /* Note that we still need this function stubbed. */
+
+ return -ENOTSUPP;
+}
+
+void wfx_suspend_resume(struct wfx_vif *wvif,
+ struct hif_ind_suspend_resume_tx *arg)
+{
+ if (arg->suspend_resume_flags.bc_mc_only) {
+ bool cancel_tmo = false;
+
+ spin_lock_bh(&wvif->ps_state_lock);
+ if (!arg->suspend_resume_flags.resume)
+ wvif->mcast_tx = false;
+ else
+ wvif->mcast_tx = wvif->aid0_bit_set &&
+ wvif->mcast_buffered;
+ if (wvif->mcast_tx) {
+ cancel_tmo = true;
+ wfx_bh_request_tx(wvif->wdev);
+ }
+ spin_unlock_bh(&wvif->ps_state_lock);
+ if (cancel_tmo)
+ del_timer_sync(&wvif->mcast_timeout);
+ } else if (arg->suspend_resume_flags.resume) {
+ // FIXME: should change each station status independently
+ wfx_ps_notify(wvif, STA_NOTIFY_AWAKE, 0);
+ wfx_bh_request_tx(wvif->wdev);
+ } else {
+ // FIXME: should change each station status independently
+ wfx_ps_notify(wvif, STA_NOTIFY_SLEEP, 0);
+ }
+}
+
+int wfx_add_chanctx(struct ieee80211_hw *hw,
+ struct ieee80211_chanctx_conf *conf)
+{
+ return 0;
+}
+
+void wfx_remove_chanctx(struct ieee80211_hw *hw,
+ struct ieee80211_chanctx_conf *conf)
+{
+}
+
+void wfx_change_chanctx(struct ieee80211_hw *hw,
+ struct ieee80211_chanctx_conf *conf,
+ u32 changed)
+{
+}
+
+int wfx_assign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ struct ieee80211_chanctx_conf *conf)
+{
+ struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+ struct ieee80211_channel *ch = conf->def.chan;
+
+ WARN(wvif->channel, "channel overwrite");
+ wvif->channel = ch;
+ wvif->ht_info.channel_type = cfg80211_get_chandef_type(&conf->def);
+
+ return 0;
+}
+
+void wfx_unassign_vif_chanctx(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_chanctx_conf *conf)
+{
+ struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+ struct ieee80211_channel *ch = conf->def.chan;
+
+ WARN(wvif->channel != ch, "channel mismatch");
+ wvif->channel = NULL;
+}
+
+int wfx_config(struct ieee80211_hw *hw, u32 changed)
+{
+ int ret = 0;
+ struct wfx_dev *wdev = hw->priv;
+ struct ieee80211_conf *conf = &hw->conf;
+ struct wfx_vif *wvif;
+
+ // FIXME: Interface id should not been hardcoded
+ wvif = wdev_to_wvif(wdev, 0);
+ if (!wvif) {
+ WARN(1, "interface 0 does not exist anymore");
+ return 0;
+ }
+
+ down(&wvif->scan.lock);
+ mutex_lock(&wdev->conf_mutex);
+ if (changed & IEEE80211_CONF_CHANGE_POWER) {
+ wdev->output_power = conf->power_level;
+ hif_set_output_power(wvif, wdev->output_power * 10);
+ }
+
+ if (changed & IEEE80211_CONF_CHANGE_PS) {
+ wvif = NULL;
+ while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
+ memset(&wvif->powersave_mode, 0,
+ sizeof(wvif->powersave_mode));
+ if (conf->flags & IEEE80211_CONF_PS) {
+ wvif->powersave_mode.pm_mode.enter_psm = 1;
+ if (conf->dynamic_ps_timeout > 0) {
+ wvif->powersave_mode.pm_mode.fast_psm = 1;
+ /*
+ * Firmware does not support more than
+ * 128ms
+ */
+ wvif->powersave_mode.fast_psm_idle_period =
+ min(conf->dynamic_ps_timeout *
+ 2, 255);
+ }
+ }
+ if (wvif->state == WFX_STATE_STA && wvif->bss_params.aid)
+ wfx_set_pm(wvif, &wvif->powersave_mode);
+ }
+ wvif = wdev_to_wvif(wdev, 0);
+ }
+
+ mutex_unlock(&wdev->conf_mutex);
+ up(&wvif->scan.lock);
+ return ret;
+}
+
+int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+{
+ int i;
+ struct wfx_dev *wdev = hw->priv;
+ struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+ // FIXME: parameters are set by kernel juste after interface_add.
+ // Keep struct hif_req_edca_queue_params blank?
+ struct hif_req_edca_queue_params default_edca_params[] = {
+ [IEEE80211_AC_VO] = {
+ .queue_id = HIF_QUEUE_ID_VOICE,
+ .aifsn = 2,
+ .cw_min = 3,
+ .cw_max = 7,
+ .tx_op_limit = TXOP_UNIT * 47,
+ },
+ [IEEE80211_AC_VI] = {
+ .queue_id = HIF_QUEUE_ID_VIDEO,
+ .aifsn = 2,
+ .cw_min = 7,
+ .cw_max = 15,
+ .tx_op_limit = TXOP_UNIT * 94,
+ },
+ [IEEE80211_AC_BE] = {
+ .queue_id = HIF_QUEUE_ID_BESTEFFORT,
+ .aifsn = 3,
+ .cw_min = 15,
+ .cw_max = 1023,
+ .tx_op_limit = TXOP_UNIT * 0,
+ },
+ [IEEE80211_AC_BK] = {
+ .queue_id = HIF_QUEUE_ID_BACKGROUND,
+ .aifsn = 7,
+ .cw_min = 15,
+ .cw_max = 1023,
+ .tx_op_limit = TXOP_UNIT * 0,
+ },
+ };
+
+ BUILD_BUG_ON(ARRAY_SIZE(default_edca_params) != ARRAY_SIZE(wvif->edca.params));
+ if (wfx_api_older_than(wdev, 2, 0)) {
+ default_edca_params[IEEE80211_AC_BE].queue_id = HIF_QUEUE_ID_BACKGROUND;
+ default_edca_params[IEEE80211_AC_BK].queue_id = HIF_QUEUE_ID_BESTEFFORT;
+ }
+
+ vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER |
+ IEEE80211_VIF_SUPPORTS_UAPSD |
+ IEEE80211_VIF_SUPPORTS_CQM_RSSI;
+
+ mutex_lock(&wdev->conf_mutex);
+
+ switch (vif->type) {
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_ADHOC:
+ case NL80211_IFTYPE_AP:
+ break;
+ default:
+ mutex_unlock(&wdev->conf_mutex);
+ return -EOPNOTSUPP;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wdev->vif); i++) {
+ if (!wdev->vif[i]) {
+ wdev->vif[i] = vif;
+ wvif->id = i;
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(wdev->vif)) {
+ mutex_unlock(&wdev->conf_mutex);
+ return -EOPNOTSUPP;
+ }
+ // FIXME: prefer use of container_of() to get vif
+ wvif->vif = vif;
+ wvif->wdev = wdev;
+
+ INIT_WORK(&wvif->link_id_work, wfx_link_id_work);
+ INIT_DELAYED_WORK(&wvif->link_id_gc_work, wfx_link_id_gc_work);
+
+ spin_lock_init(&wvif->ps_state_lock);
+ INIT_WORK(&wvif->set_tim_work, wfx_set_tim_work);
+
+ INIT_WORK(&wvif->mcast_start_work, wfx_mcast_start_work);
+ INIT_WORK(&wvif->mcast_stop_work, wfx_mcast_stop_work);
+ timer_setup(&wvif->mcast_timeout, wfx_mcast_timeout, 0);
+
+ wvif->setbssparams_done = false;
+ mutex_init(&wvif->bss_loss_lock);
+ INIT_DELAYED_WORK(&wvif->bss_loss_work, wfx_bss_loss_work);
+
+ wvif->wep_default_key_id = -1;
+ INIT_WORK(&wvif->wep_key_work, wfx_wep_key_work);
+
+ sema_init(&wvif->scan.lock, 1);
+ INIT_WORK(&wvif->scan.work, wfx_scan_work);
+ INIT_DELAYED_WORK(&wvif->scan.timeout, wfx_scan_timeout);
+
+ spin_lock_init(&wvif->event_queue_lock);
+ INIT_LIST_HEAD(&wvif->event_queue);
+ INIT_WORK(&wvif->event_handler_work, wfx_event_handler_work);
+
+ init_completion(&wvif->set_pm_mode_complete);
+ complete(&wvif->set_pm_mode_complete);
+ INIT_WORK(&wvif->set_beacon_wakeup_period_work,
+ wfx_set_beacon_wakeup_period_work);
+ INIT_WORK(&wvif->update_filtering_work, wfx_update_filtering_work);
+ INIT_WORK(&wvif->bss_params_work, wfx_bss_params_work);
+ INIT_WORK(&wvif->set_cts_work, wfx_set_cts_work);
+ INIT_WORK(&wvif->unjoin_work, wfx_unjoin_work);
+
+ mutex_unlock(&wdev->conf_mutex);
+
+ hif_set_macaddr(wvif, vif->addr);
+ for (i = 0; i < IEEE80211_NUM_ACS; i++) {
+ memcpy(&wvif->edca.params[i], &default_edca_params[i],
+ sizeof(default_edca_params[i]));
+ wvif->edca.uapsd_enable[i] = false;
+ hif_set_edca_queue_params(wvif, &wvif->edca.params[i]);
+ }
+ wfx_set_uapsd_param(wvif, &wvif->edca);
+
+ wfx_tx_policy_init(wvif);
+ wvif = NULL;
+ while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
+ // Combo mode does not support Block Acks. We can re-enable them
+ if (wvif_count(wdev) == 1)
+ hif_set_block_ack_policy(wvif, 0xFF, 0xFF);
+ else
+ hif_set_block_ack_policy(wvif, 0x00, 0x00);
+ // Combo force powersave mode. We can re-enable it now
+ wfx_set_pm(wvif, &wvif->powersave_mode);
+ }
+ return 0;
+}
+
+void wfx_remove_interface(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif)
+{
+ struct wfx_dev *wdev = hw->priv;
+ struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv;
+ int i;
+
+ // If scan is in progress, stop it
+ while (down_trylock(&wvif->scan.lock))
+ schedule();
+ up(&wvif->scan.lock);
+ wait_for_completion_timeout(&wvif->set_pm_mode_complete, msecs_to_jiffies(300));
+
+ mutex_lock(&wdev->conf_mutex);
+ switch (wvif->state) {
+ case WFX_STATE_PRE_STA:
+ case WFX_STATE_STA:
+ case WFX_STATE_IBSS:
+ wfx_tx_lock_flush(wdev);
+ if (!schedule_work(&wvif->unjoin_work))
+ wfx_tx_unlock(wdev);
+ break;
+ case WFX_STATE_AP:
+ for (i = 0; wvif->link_id_map; ++i) {
+ if (wvif->link_id_map & BIT(i)) {
+ wfx_unmap_link(wvif, i);
+ wvif->link_id_map &= ~BIT(i);
+ }
+ }
+ memset(wvif->link_id_db, 0, sizeof(wvif->link_id_db));
+ wvif->sta_asleep_mask = 0;
+ wvif->enable_beacon = false;
+ wvif->mcast_tx = false;
+ wvif->aid0_bit_set = false;
+ wvif->mcast_buffered = false;
+ wvif->pspoll_mask = 0;
+ /* reset.link_id = 0; */
+ hif_reset(wvif, false);
+ break;
+ default:
+ break;
+ }
+
+ wvif->state = WFX_STATE_PASSIVE;
+ wfx_tx_queues_wait_empty_vif(wvif);
+ wfx_tx_unlock(wdev);
+
+ /* FIXME: In add to reset MAC address, try to reset interface */
+ hif_set_macaddr(wvif, NULL);
+
+ cancel_delayed_work_sync(&wvif->scan.timeout);
+
+ wfx_cqm_bssloss_sm(wvif, 0, 0, 0);
+ cancel_work_sync(&wvif->unjoin_work);
+ cancel_delayed_work_sync(&wvif->link_id_gc_work);
+ del_timer_sync(&wvif->mcast_timeout);
+ wfx_free_event_queue(wvif);
+
+ wdev->vif[wvif->id] = NULL;
+ wvif->vif = NULL;
+
+ mutex_unlock(&wdev->conf_mutex);
+ wvif = NULL;
+ while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
+ // Combo mode does not support Block Acks. We can re-enable them
+ if (wvif_count(wdev) == 1)
+ hif_set_block_ack_policy(wvif, 0xFF, 0xFF);
+ else
+ hif_set_block_ack_policy(wvif, 0x00, 0x00);
+ // Combo force powersave mode. We can re-enable it now
+ wfx_set_pm(wvif, &wvif->powersave_mode);
+ }
+}
+
+int wfx_start(struct ieee80211_hw *hw)
+{
+ return 0;
+}
+
+void wfx_stop(struct ieee80211_hw *hw)
+{
+ struct wfx_dev *wdev = hw->priv;
+
+ wfx_tx_lock_flush(wdev);
+ mutex_lock(&wdev->conf_mutex);
+ wfx_tx_queues_clear(wdev);
+ mutex_unlock(&wdev->conf_mutex);
+ wfx_tx_unlock(wdev);
+ WARN(atomic_read(&wdev->tx_lock), "tx_lock is locked");
+}