From: Jérôme Pouiller <jerome.pouiller@xxxxxxxxxx> Signed-off-by: Jérôme Pouiller <jerome.pouiller@xxxxxxxxxx> --- drivers/staging/wfx/data_rx.c | 26 + drivers/staging/wfx/data_tx.c | 16 + drivers/staging/wfx/debug.c | 2 + drivers/staging/wfx/hif_rx.c | 53 ++ drivers/staging/wfx/hif_tx.c | 1 + drivers/staging/wfx/main.c | 133 +++ drivers/staging/wfx/queue.c | 80 ++ drivers/staging/wfx/scan.c | 40 + drivers/staging/wfx/sta.c | 1443 ++++++++++++++++++++++++++++++++- drivers/staging/wfx/sta.h | 65 ++ drivers/staging/wfx/wfx.h | 51 ++ 11 files changed, 1907 insertions(+), 3 deletions(-) diff --git a/drivers/staging/wfx/data_rx.c b/drivers/staging/wfx/data_rx.c index 3b3117b2edac..3a79089c8501 100644 --- a/drivers/staging/wfx/data_rx.c +++ b/drivers/staging/wfx/data_rx.c @@ -21,6 +21,8 @@ static int wfx_handle_pspoll(struct wfx_vif *wvif, struct sk_buff *skb) u32 pspoll_mask = 0; int i; + if (wvif->state != WFX_STATE_AP) + return 1; if (!ether_addr_equal(wvif->vif->addr, pspoll->bssid)) return 1; @@ -162,6 +164,30 @@ void wfx_rx_cb(struct wfx_vif *wvif, struct hif_ind_rx *arg, struct sk_buff *skb && arg->rx_flags.match_uc_addr && mgmt->u.action.category == WLAN_CATEGORY_BACK) goto drop; + if (ieee80211_is_beacon(frame->frame_control) + && !arg->status && wvif->vif + && ether_addr_equal(ieee80211_get_SA(frame), wvif->vif->bss_conf.bssid)) { + const u8 *tim_ie; + u8 *ies = mgmt->u.beacon.variable; + size_t ies_len = skb->len - (ies - skb->data); + + tim_ie = cfg80211_find_ie(WLAN_EID_TIM, ies, ies_len); + if (tim_ie) { + struct ieee80211_tim_ie *tim = (struct ieee80211_tim_ie *) &tim_ie[2]; + + if (wvif->dtim_period != tim->dtim_period) { + wvif->dtim_period = tim->dtim_period; + schedule_work(&wvif->set_beacon_wakeup_period_work); + } + } + + /* Disable beacon filter once we're associated... */ + if (wvif->disable_beacon_filter && + (wvif->vif->bss_conf.assoc || wvif->vif->bss_conf.ibss_joined)) { + wvif->disable_beacon_filter = false; + schedule_work(&wvif->update_filtering_work); + } + } if (early_data) { spin_lock_bh(&wvif->ps_state_lock); diff --git a/drivers/staging/wfx/data_tx.c b/drivers/staging/wfx/data_tx.c index 217d3c270706..7f2799fbdafe 100644 --- a/drivers/staging/wfx/data_tx.c +++ b/drivers/staging/wfx/data_tx.c @@ -10,6 +10,7 @@ #include "data_tx.h" #include "wfx.h" #include "bh.h" +#include "sta.h" #include "queue.h" #include "debug.h" #include "traces.h" @@ -359,6 +360,9 @@ void wfx_link_id_gc_work(struct work_struct *work) u32 mask; int i; + if (wvif->state != WFX_STATE_AP) + return; + wfx_tx_lock_flush(wvif->wdev); spin_lock_bh(&wvif->ps_state_lock); for (i = 0; i < WFX_MAX_STA_IN_AP_MODE; ++i) { @@ -729,14 +733,26 @@ void wfx_tx_confirm_cb(struct wfx_vif *wvif, struct hif_cnf_tx *arg) memset(tx_info->pad, 0, sizeof(tx_info->pad)); if (!arg->status) { + if (wvif->bss_loss_state && arg->packet_id == wvif->bss_loss_confirm_id) + wfx_cqm_bssloss_sm(wvif, 0, 1, 0); tx_info->status.tx_time = arg->media_delay - arg->tx_queue_delay; if (tx_info->flags & IEEE80211_TX_CTL_NO_ACK) tx_info->flags |= IEEE80211_TX_STAT_NOACK_TRANSMITTED; else tx_info->flags |= IEEE80211_TX_STAT_ACK; } else if (arg->status == HIF_REQUEUE) { + /* "REQUEUE" means "implicit suspend" */ + struct hif_ind_suspend_resume_tx suspend = { + .suspend_resume_flags.resume = 0, + .suspend_resume_flags.bc_mc_only = 1, + }; + WARN(!arg->tx_result_flags.requeue, "incoherent status and result_flags"); + wfx_suspend_resume(wvif, &suspend); tx_info->flags |= IEEE80211_TX_STAT_TX_FILTERED; + } else { + if (wvif->bss_loss_state && arg->packet_id == wvif->bss_loss_confirm_id) + wfx_cqm_bssloss_sm(wvif, 0, 0, 1); } wfx_pending_remove(wvif->wdev, skb); } diff --git a/drivers/staging/wfx/debug.c b/drivers/staging/wfx/debug.c index 1e23bb5bde3e..3261b267c385 100644 --- a/drivers/staging/wfx/debug.c +++ b/drivers/staging/wfx/debug.c @@ -11,7 +11,9 @@ #include "debug.h" #include "wfx.h" +#include "sta.h" #include "main.h" +#include "hif_tx.h" #include "hif_tx_mib.h" #define CREATE_TRACE_POINTS diff --git a/drivers/staging/wfx/hif_rx.c b/drivers/staging/wfx/hif_rx.c index d386fab0a90f..52db02d3aa41 100644 --- a/drivers/staging/wfx/hif_rx.c +++ b/drivers/staging/wfx/hif_rx.c @@ -12,6 +12,8 @@ #include "hif_rx.h" #include "wfx.h" #include "scan.h" +#include "bh.h" +#include "sta.h" #include "data_rx.h" #include "secure_link.h" #include "hif_api_cmd.h" @@ -144,6 +146,43 @@ static int hif_receive_indication(struct wfx_dev *wdev, struct hif_msg *hif, voi return 0; } +static int hif_event_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf) +{ + struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface); + struct hif_ind_event *body = buf; + struct wfx_hif_event *event; + int first; + + WARN_ON(!wvif); + if (!wvif) + return 0; + + event = kzalloc(sizeof(*event), GFP_KERNEL); + if (!event) + return -ENOMEM; + + memcpy(&event->evt, body, sizeof(struct hif_ind_event)); + spin_lock(&wvif->event_queue_lock); + first = list_empty(&wvif->event_queue); + list_add_tail(&event->link, &wvif->event_queue); + spin_unlock(&wvif->event_queue_lock); + + if (first) + schedule_work(&wvif->event_handler_work); + + return 0; +} + +static int hif_pm_mode_complete_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf) +{ + struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface); + + WARN_ON(!wvif); + complete(&wvif->set_pm_mode_complete); + + return 0; +} + static int hif_scan_complete_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf) { struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface); @@ -165,6 +204,17 @@ static int hif_join_complete_indication(struct wfx_dev *wdev, struct hif_msg *hi return 0; } +static int hif_suspend_resume_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf) +{ + struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface); + struct hif_ind_suspend_resume_tx *body = buf; + + WARN_ON(!wvif); + wfx_suspend_resume(wvif, body); + + return 0; +} + static int hif_error_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf) { struct hif_ind_error *body = buf; @@ -242,8 +292,11 @@ static const struct { { HIF_IND_ID_STARTUP, hif_startup_indication }, { HIF_IND_ID_WAKEUP, hif_wakeup_indication }, { HIF_IND_ID_JOIN_COMPLETE, hif_join_complete_indication }, + { HIF_IND_ID_SET_PM_MODE_CMPL, hif_pm_mode_complete_indication }, { HIF_IND_ID_SCAN_CMPL, hif_scan_complete_indication }, + { HIF_IND_ID_SUSPEND_RESUME_TX, hif_suspend_resume_indication }, { HIF_IND_ID_SL_EXCHANGE_PUB_KEYS, hif_keys_indication }, + { HIF_IND_ID_EVENT, hif_event_indication }, { HIF_IND_ID_GENERIC, hif_generic_indication }, { HIF_IND_ID_ERROR, hif_error_indication }, { HIF_IND_ID_EXCEPTION, hif_exception_indication }, diff --git a/drivers/staging/wfx/hif_tx.c b/drivers/staging/wfx/hif_tx.c index 157ab177b73f..2d40225a0fce 100644 --- a/drivers/staging/wfx/hif_tx.c +++ b/drivers/staging/wfx/hif_tx.c @@ -14,6 +14,7 @@ #include "bh.h" #include "hwio.h" #include "debug.h" +#include "sta.h" void wfx_init_hif_cmd(struct wfx_hif_cmd *hif_cmd) { diff --git a/drivers/staging/wfx/main.c b/drivers/staging/wfx/main.c index e7bba24aae0b..fe9a89703897 100644 --- a/drivers/staging/wfx/main.c +++ b/drivers/staging/wfx/main.c @@ -50,14 +50,112 @@ static char *slk_key; module_param(slk_key, charp, 0600); MODULE_PARM_DESC(slk_key, "secret key for secure link (expect 64 hexdecimal digits)."); +#define RATETAB_ENT(_rate, _rateid, _flags) { \ + .bitrate = (_rate), \ + .hw_value = (_rateid), \ + .flags = (_flags), \ +} + +static struct ieee80211_rate wfx_rates[] = { + RATETAB_ENT(10, 0, 0), + RATETAB_ENT(20, 1, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(55, 2, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(110, 3, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(60, 6, 0), + RATETAB_ENT(90, 7, 0), + RATETAB_ENT(120, 8, 0), + RATETAB_ENT(180, 9, 0), + RATETAB_ENT(240, 10, 0), + RATETAB_ENT(360, 11, 0), + RATETAB_ENT(480, 12, 0), + RATETAB_ENT(540, 13, 0), +}; + +#define CHAN2G(_channel, _freq, _flags) { \ + .band = NL80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_channel), \ + .flags = (_flags), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +static struct ieee80211_channel wfx_2ghz_chantable[] = { + CHAN2G(1, 2412, 0), + CHAN2G(2, 2417, 0), + CHAN2G(3, 2422, 0), + CHAN2G(4, 2427, 0), + CHAN2G(5, 2432, 0), + CHAN2G(6, 2437, 0), + CHAN2G(7, 2442, 0), + CHAN2G(8, 2447, 0), + CHAN2G(9, 2452, 0), + CHAN2G(10, 2457, 0), + CHAN2G(11, 2462, 0), + CHAN2G(12, 2467, 0), + CHAN2G(13, 2472, 0), + CHAN2G(14, 2484, 0), +}; + +static const struct ieee80211_supported_band wfx_band_2ghz = { + .channels = wfx_2ghz_chantable, + .n_channels = ARRAY_SIZE(wfx_2ghz_chantable), + .bitrates = wfx_rates, + .n_bitrates = ARRAY_SIZE(wfx_rates), + .ht_cap = { + // Receive caps + .cap = IEEE80211_HT_CAP_GRN_FLD | IEEE80211_HT_CAP_SGI_20 | + IEEE80211_HT_CAP_MAX_AMSDU | (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT), + .ht_supported = 1, + .ampdu_factor = IEEE80211_HT_MAX_AMPDU_16K, + .ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE, + .mcs = { + .rx_mask = { 0xFF }, // MCS0 to MCS7 + .rx_highest = 65, + .tx_params = IEEE80211_HT_MCS_TX_DEFINED, + }, + }, +}; + +static const struct ieee80211_iface_limit wdev_iface_limits[] = { + { .max = 1, .types = BIT(NL80211_IFTYPE_STATION) }, + { .max = 1, .types = BIT(NL80211_IFTYPE_AP) }, +}; + +static const struct ieee80211_iface_combination wfx_iface_combinations[] = { + { + .num_different_channels = 2, + .max_interfaces = 2, + .limits = wdev_iface_limits, + .n_limits = ARRAY_SIZE(wdev_iface_limits), + } +}; + static const struct ieee80211_ops wfx_ops = { .start = wfx_start, .stop = wfx_stop, .add_interface = wfx_add_interface, .remove_interface = wfx_remove_interface, + .config = wfx_config, .tx = wfx_tx, + .conf_tx = wfx_conf_tx, .hw_scan = wfx_hw_scan, + .sta_add = wfx_sta_add, + .sta_remove = wfx_sta_remove, + .sta_notify = wfx_sta_notify, + .set_tim = wfx_set_tim, .set_key = wfx_set_key, + .set_rts_threshold = wfx_set_rts_threshold, + .bss_info_changed = wfx_bss_info_changed, + .prepare_multicast = wfx_prepare_multicast, + .configure_filter = wfx_configure_filter, + .ampdu_action = wfx_ampdu_action, + .flush = wfx_flush, + .add_chanctx = wfx_add_chanctx, + .remove_chanctx = wfx_remove_chanctx, + .change_chanctx = wfx_change_chanctx, + .assign_vif_chanctx = wfx_assign_vif_chanctx, + .unassign_vif_chanctx = wfx_unassign_vif_chanctx, }; bool wfx_api_older_than(struct wfx_dev *wdev, int major, int minor) @@ -198,6 +296,16 @@ struct wfx_dev *wfx_init_common(struct device *dev, SET_IEEE80211_DEV(hw, dev); + ieee80211_hw_set(hw, NEED_DTIM_BEFORE_ASSOC); + ieee80211_hw_set(hw, TX_AMPDU_SETUP_IN_HW); + ieee80211_hw_set(hw, AMPDU_AGGREGATION); + ieee80211_hw_set(hw, CONNECTION_MONITOR); + ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS); + ieee80211_hw_set(hw, SUPPORTS_DYNAMIC_PS); + ieee80211_hw_set(hw, SIGNAL_DBM); + ieee80211_hw_set(hw, SUPPORTS_PS); + ieee80211_hw_set(hw, MFP_CAPABLE); + hw->vif_data_size = sizeof(struct wfx_vif); hw->sta_data_size = sizeof(struct wfx_sta_priv); hw->queues = 4; @@ -206,8 +314,19 @@ struct wfx_dev *wfx_init_common(struct device *dev, hw->extra_tx_headroom = sizeof(struct hif_sl_msg_hdr) + sizeof(struct hif_msg) + sizeof(struct hif_req_tx) + 4 /* alignment */ + 8 /* TKIP IV */; + hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_AP); + hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD; + hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + hw->wiphy->max_ap_assoc_sta = WFX_MAX_STA_IN_AP_MODE; hw->wiphy->max_scan_ssids = 2; hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN; + hw->wiphy->n_iface_combinations = ARRAY_SIZE(wfx_iface_combinations); + hw->wiphy->iface_combinations = wfx_iface_combinations; + hw->wiphy->bands[NL80211_BAND_2GHZ] = devm_kmalloc(dev, sizeof(wfx_band_2ghz), GFP_KERNEL); + // FIXME: also copy wfx_rates and wfx_2ghz_chantable + memcpy(hw->wiphy->bands[NL80211_BAND_2GHZ], &wfx_band_2ghz, sizeof(wfx_band_2ghz)); wdev = hw->priv; wdev->hw = hw; @@ -290,6 +409,12 @@ int wfx_probe(struct wfx_dev *wdev) goto err1; } + if (wdev->hw_caps.regul_sel_mode_info.region_sel_mode) { + wdev->hw->wiphy->bands[NL80211_BAND_2GHZ]->channels[11].flags |= IEEE80211_CHAN_NO_IR; + wdev->hw->wiphy->bands[NL80211_BAND_2GHZ]->channels[12].flags |= IEEE80211_CHAN_NO_IR; + wdev->hw->wiphy->bands[NL80211_BAND_2GHZ]->channels[13].flags |= IEEE80211_CHAN_DISABLED; + } + dev_dbg(wdev->dev, "sending configuration file %s\n", wdev->pdata.file_pds); err = wfx_send_pdata_pds(wdev); if (err < 0) @@ -322,6 +447,12 @@ int wfx_probe(struct wfx_dev *wdev) } dev_info(wdev->dev, "MAC address %d: %pM\n", i, wdev->addresses[i].addr); } + wdev->hw->wiphy->n_addresses = ARRAY_SIZE(wdev->addresses); + wdev->hw->wiphy->addresses = wdev->addresses; + + err = ieee80211_register_hw(wdev->hw); + if (err) + goto err1; err = wfx_debug_init(wdev); if (err) @@ -330,6 +461,7 @@ int wfx_probe(struct wfx_dev *wdev) return 0; err2: + ieee80211_unregister_hw(wdev->hw); ieee80211_free_hw(wdev->hw); err1: wfx_bh_unregister(wdev); @@ -338,6 +470,7 @@ int wfx_probe(struct wfx_dev *wdev) void wfx_release(struct wfx_dev *wdev) { + ieee80211_unregister_hw(wdev->hw); hif_shutdown(wdev); wfx_bh_unregister(wdev); wfx_sl_deinit(wdev); diff --git a/drivers/staging/wfx/queue.c b/drivers/staging/wfx/queue.c index aa438be21d37..6f1be4f6f463 100644 --- a/drivers/staging/wfx/queue.c +++ b/drivers/staging/wfx/queue.c @@ -351,6 +351,83 @@ bool wfx_tx_queues_is_empty(struct wfx_dev *wdev) return ret; } +static bool hif_handle_tx_data(struct wfx_vif *wvif, struct sk_buff *skb, + struct wfx_queue *queue) +{ + bool handled = false; + struct wfx_tx_priv *tx_priv = wfx_skb_tx_priv(skb); + struct hif_req_tx *req = wfx_skb_txreq(skb); + struct ieee80211_hdr *frame = (struct ieee80211_hdr *) (req->frame + req->data_flags.fc_offset); + + enum { + do_probe, + do_drop, + do_wep, + do_tx, + } action = do_tx; + + switch (wvif->vif->type) { + case NL80211_IFTYPE_STATION: + if (wvif->state < WFX_STATE_PRE_STA) + action = do_drop; + break; + case NL80211_IFTYPE_AP: + if (!wvif->state) { + action = do_drop; + } else if (!(BIT(tx_priv->raw_link_id) & (BIT(0) | wvif->link_id_map))) { + dev_warn(wvif->wdev->dev, "a frame with expired link-id is dropped\n"); + action = do_drop; + } + break; + case NL80211_IFTYPE_ADHOC: + if (wvif->state != WFX_STATE_IBSS) + action = do_drop; + break; + case NL80211_IFTYPE_MONITOR: + default: + action = do_drop; + break; + } + + if (action == do_tx) { + if (ieee80211_is_nullfunc(frame->frame_control)) { + mutex_lock(&wvif->bss_loss_lock); + if (wvif->bss_loss_state) { + wvif->bss_loss_confirm_id = req->packet_id; + req->queue_id.queue_id = HIF_QUEUE_ID_VOICE; + } + mutex_unlock(&wvif->bss_loss_lock); + } else if (ieee80211_has_protected(frame->frame_control) && + tx_priv->hw_key && + tx_priv->hw_key->keyidx != wvif->wep_default_key_id && + (tx_priv->hw_key->cipher == WLAN_CIPHER_SUITE_WEP40 || + tx_priv->hw_key->cipher == WLAN_CIPHER_SUITE_WEP104)) { + action = do_wep; + } + } + + switch (action) { + case do_drop: + BUG_ON(wfx_pending_remove(wvif->wdev, skb)); + handled = true; + break; + case do_wep: + wfx_tx_lock(wvif->wdev); + wvif->wep_default_key_id = tx_priv->hw_key->keyidx; + wvif->wep_pending_skb = skb; + if (!schedule_work(&wvif->wep_key_work)) + wfx_tx_unlock(wvif->wdev); + handled = true; + break; + case do_tx: + break; + default: + /* Do nothing */ + break; + } + return handled; +} + static int wfx_get_prio_queue(struct wfx_vif *wvif, u32 tx_allowed_mask, int *total) { @@ -498,6 +575,9 @@ struct hif_msg *wfx_tx_queues_get(struct wfx_dev *wdev) wvif = wdev_to_wvif(wdev, hif->interface); WARN_ON(!wvif); + if (hif_handle_tx_data(wvif, skb, queue)) + continue; /* Handled by WSM */ + wvif->pspoll_mask &= ~BIT(tx_priv->raw_link_id); /* allow bursting if txop is set */ diff --git a/drivers/staging/wfx/scan.c b/drivers/staging/wfx/scan.c index 207b26ebc9fd..ea5001c915f6 100644 --- a/drivers/staging/wfx/scan.c +++ b/drivers/staging/wfx/scan.c @@ -21,11 +21,26 @@ static void __ieee80211_scan_completed_compat(struct ieee80211_hw *hw, bool abor ieee80211_scan_completed(hw, &info); } +static void wfx_scan_restart_delayed(struct wfx_vif *wvif) +{ + if (wvif->delayed_unjoin) { + wvif->delayed_unjoin = false; + if (!schedule_work(&wvif->unjoin_work)) + wfx_tx_unlock(wvif->wdev); + } else if (wvif->delayed_link_loss) { + wvif->delayed_link_loss = 0; + wfx_cqm_bssloss_sm(wvif, 1, 0, 0); + } +} + static int wfx_scan_start(struct wfx_vif *wvif, struct wfx_scan_params *scan) { int ret; int tmo = 500; + if (wvif->state == WFX_STATE_PRE_STA) + return -EBUSY; + tmo += scan->scan_req.num_of_channels * ((20 * (scan->scan_req.max_channel_time)) + 10); atomic_set(&wvif->scan.in_progress, 1); @@ -38,6 +53,7 @@ static int wfx_scan_start(struct wfx_vif *wvif, struct wfx_scan_params *scan) atomic_set(&wvif->scan.in_progress, 0); atomic_set(&wvif->wdev->scan_in_progress, 0); cancel_delayed_work_sync(&wvif->scan.timeout); + wfx_scan_restart_delayed(wvif); } return ret; } @@ -56,6 +72,9 @@ int wfx_hw_scan(struct ieee80211_hw *hw, if (!wvif) return -EINVAL; + if (wvif->state == WFX_STATE_AP) + return -EOPNOTSUPP; + if (req->n_ssids == 1 && !req->ssids[0].ssid_len) req->n_ssids = 0; @@ -121,11 +140,23 @@ void wfx_scan_work(struct work_struct *work) .scan_req.scan_type.type = 0, /* Foreground */ }; struct ieee80211_channel *first; + bool first_run = (wvif->scan.begin == wvif->scan.curr && + wvif->scan.begin != wvif->scan.end); int i; down(&wvif->scan.lock); mutex_lock(&wvif->wdev->conf_mutex); + if (first_run) { + if (wvif->state == WFX_STATE_STA && + !(wvif->powersave_mode.pm_mode.enter_psm)) { + struct hif_req_set_pm_mode pm = wvif->powersave_mode; + + pm.pm_mode.enter_psm = 1; + wfx_set_pm(wvif, &pm); + } + } + if (!wvif->scan.req || wvif->scan.curr == wvif->scan.end) { if (wvif->scan.output_power != wvif->wdev->output_power) hif_set_output_power(wvif, wvif->wdev->output_power * 10); @@ -138,10 +169,14 @@ void wfx_scan_work(struct work_struct *work) dev_dbg(wvif->wdev->dev, "scan canceled\n"); wvif->scan.req = NULL; + wfx_scan_restart_delayed(wvif); wfx_tx_unlock(wvif->wdev); mutex_unlock(&wvif->wdev->conf_mutex); __ieee80211_scan_completed_compat(wvif->wdev->hw, wvif->scan.status ? 1 : 0); up(&wvif->scan.lock); + if (wvif->state == WFX_STATE_STA && + !(wvif->powersave_mode.pm_mode.enter_psm)) + wfx_set_pm(wvif, &wvif->powersave_mode); return; } first = *wvif->scan.curr; @@ -170,6 +205,11 @@ void wfx_scan_work(struct work_struct *work) scan.ssids = &wvif->scan.ssids[0]; scan.scan_req.num_of_channels = it - wvif->scan.curr; scan.scan_req.probe_delay = 100; + // FIXME: Check if FW can do active scan while joined. + if (wvif->state == WFX_STATE_STA) { + scan.scan_req.scan_type.type = 1; + scan.scan_req.scan_flags.fbg = 1; + } scan.ch = kcalloc(scan.scan_req.num_of_channels, sizeof(u8), GFP_KERNEL); diff --git a/drivers/staging/wfx/sta.c b/drivers/staging/wfx/sta.c index 2e709b8a3bf4..2855d14a709c 100644 --- a/drivers/staging/wfx/sta.c +++ b/drivers/staging/wfx/sta.c @@ -9,11 +9,148 @@ #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) +{ + int ret; + + /* 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; + + ret = hif_set_uapsd_info(wvif, &wvif->uapsd_info); + return ret; +} int wfx_fwd_probe_req(struct wfx_vif *wvif, bool enable) { @@ -22,6 +159,1044 @@ int wfx_fwd_probe_req(struct wfx_vif *wvif, bool enable) 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_mib_bcn_filter_table *bf_tbl; + struct hif_ie_table_entry ie_tbl[] = { + { + .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, + } + }; + + if (wvif->state == WFX_STATE_PASSIVE) + return; + + bf_tbl = kmalloc(sizeof(struct hif_mib_bcn_filter_table) + sizeof(ie_tbl), GFP_KERNEL); + memcpy(bf_tbl->ie_table, ie_tbl, sizeof(ie_tbl)); + if (wvif->disable_beacon_filter) { + bf_ctrl.enable = 0; + bf_ctrl.bcn_count = 1; + bf_tbl->num_of_info_elmts = 0; + } else if (!is_sta) { + bf_ctrl.enable = HIF_BEACON_FILTER_ENABLE | HIF_BEACON_FILTER_AUTO_ERP; + bf_ctrl.bcn_count = 0; + bf_tbl->num_of_info_elmts = 2; + } else { + bf_ctrl.enable = HIF_BEACON_FILTER_ENABLE; + bf_ctrl.bcn_count = 0; + bf_tbl->num_of_info_elmts = 3; + } + + ret = hif_set_rx_filter(wvif, filter_bssid, fwd_probe_req); + if (!ret) + ret = hif_set_beacon_filter_table(wvif, bf_tbl); + 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); + kfree(bf_tbl); +} + +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 = 1; + 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, uint8_t 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); +} + +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); +} + +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); +} + +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); +} + +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(uint16_t); + const int pairwise_cipher_suite_size = 4 / sizeof(uint16_t); + const int akm_suite_size = 4 / sizeof(uint16_t); + const uint16_t *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 uint16_t *) 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); +} + +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; +} + +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: + if (!skb) + 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; @@ -33,8 +1208,11 @@ static int wfx_set_tim_impl(struct wfx_vif *wvif, bool aid0_bit_set) skb = ieee80211_beacon_get_tim(wvif->wdev->hw, wvif->vif, &tim_offset, &tim_length); - if (!skb) + 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) { @@ -56,16 +1234,34 @@ static int wfx_set_tim_impl(struct wfx_vif *wvif, bool aid0_bit_set) return 0; } +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, TU_TO_JIFFIES(1000)); + mod_timer(&wvif->mcast_timeout, jiffies + tmo); wfx_tx_unlock(wvif->wdev); } } @@ -95,6 +1291,134 @@ static void wfx_mcast_timeout(struct timer_list *t) 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; @@ -138,8 +1462,22 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) 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; @@ -151,6 +1489,7 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) mutex_unlock(&wdev->conf_mutex); return -EOPNOTSUPP; } + // FIXME: prefer use of container_of() to get vif wvif->vif = vif; wvif->wdev = wdev; @@ -158,11 +1497,16 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) 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); @@ -170,22 +1514,115 @@ int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) 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); BUG_ON(ARRAY_SIZE(default_edca_params) != ARRAY_SIZE(wvif->edca.params)); - for (i = 0; i < IEEE80211_NUM_ACS; i++) + 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); + 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) diff --git a/drivers/staging/wfx/sta.h b/drivers/staging/wfx/sta.h index dd1b6b3fc2f1..307ed0196110 100644 --- a/drivers/staging/wfx/sta.h +++ b/drivers/staging/wfx/sta.h @@ -12,14 +12,40 @@ #include "hif_api_cmd.h" +struct wfx_dev; struct wfx_vif; +enum wfx_state { + WFX_STATE_PASSIVE = 0, + WFX_STATE_PRE_STA, + WFX_STATE_STA, + WFX_STATE_IBSS, + WFX_STATE_AP, +}; + +struct wfx_ht_info { + struct ieee80211_sta_ht_cap ht_cap; + enum nl80211_channel_type channel_type; + uint16_t operation_mode; +}; + +struct wfx_hif_event { + struct list_head link; + struct hif_ind_event evt; +}; + struct wfx_edca_params { /* NOTE: index is a linux queue id. */ struct hif_req_edca_queue_params params[IEEE80211_NUM_ACS]; bool uapsd_enable[IEEE80211_NUM_ACS]; }; +struct wfx_grp_addr_table { + bool enable; + int num_addresses; + u8 address_list[8][ETH_ALEN]; +}; + struct wfx_sta_priv { int link_id; int vif_id; @@ -28,9 +54,48 @@ struct wfx_sta_priv { // mac80211 interface int wfx_start(struct ieee80211_hw *hw); void wfx_stop(struct ieee80211_hw *hw); +int wfx_config(struct ieee80211_hw *hw, u32 changed); +int wfx_set_rts_threshold(struct ieee80211_hw *hw, u32 value); +u64 wfx_prepare_multicast(struct ieee80211_hw *hw, + struct netdev_hw_addr_list *mc_list); +void wfx_configure_filter(struct ieee80211_hw *hw, unsigned int changed_flags, + unsigned int *total_flags, u64 unused); + int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif); void wfx_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif); +void wfx_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + u32 queues, bool drop); +int wfx_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + u16 queue, const struct ieee80211_tx_queue_params *params); +void wfx_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, u32 changed); +int wfx_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +int wfx_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +void wfx_sta_notify(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + enum sta_notify_cmd cmd, struct ieee80211_sta *sta); +int wfx_set_tim(struct ieee80211_hw *hw, struct ieee80211_sta *sta, bool set); +int wfx_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_ampdu_params *params); +int wfx_add_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *conf); +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); +void wfx_unassign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *conf); +// WSM Callbacks +void wfx_suspend_resume(struct wfx_vif *wvif, struct hif_ind_suspend_resume_tx *arg); + +// Other Helpers +void wfx_cqm_bssloss_sm(struct wfx_vif *wvif, int init, int good, int bad); +void wfx_update_filtering(struct wfx_vif *wvif); +int wfx_set_pm(struct wfx_vif *wvif, const struct hif_req_set_pm_mode *arg); int wfx_fwd_probe_req(struct wfx_vif *wvif, bool enable); #endif /* WFX_STA_H */ diff --git a/drivers/staging/wfx/wfx.h b/drivers/staging/wfx/wfx.h index a86ddfaed825..489836837b0a 100644 --- a/drivers/staging/wfx/wfx.h +++ b/drivers/staging/wfx/wfx.h @@ -11,6 +11,8 @@ #define WFX_H #include <linux/completion.h> +#include <linux/workqueue.h> +#include <linux/mutex.h> #include <net/mac80211.h> #include "bh.h" @@ -61,8 +63,15 @@ struct wfx_dev { struct wfx_vif { struct wfx_dev *wdev; struct ieee80211_vif *vif; + struct ieee80211_channel *channel; int id; + enum wfx_state state; + int delayed_link_loss; + int bss_loss_state; + u32 bss_loss_confirm_id; + struct mutex bss_loss_lock; + struct delayed_work bss_loss_work; u32 link_id_map; struct wfx_link_entry link_id_db[WFX_MAX_STA_IN_AP_MODE]; @@ -72,6 +81,7 @@ struct wfx_vif { bool aid0_bit_set; bool mcast_tx; bool mcast_buffered; + struct wfx_grp_addr_table mcast_filter; struct timer_list mcast_timeout; struct work_struct mcast_start_work; struct work_struct mcast_stop_work; @@ -86,13 +96,40 @@ struct wfx_vif { u32 sta_asleep_mask; u32 pspoll_mask; spinlock_t ps_state_lock; + struct work_struct set_tim_work; + + int dtim_period; + int beacon_int; + bool enable_beacon; + struct work_struct set_beacon_wakeup_period_work; bool filter_bssid; bool fwd_probe_req; + bool disable_beacon_filter; + struct work_struct update_filtering_work; + u32 erp_info; + int cqm_rssi_thold; + bool setbssparams_done; + struct wfx_ht_info ht_info; struct wfx_edca_params edca; + struct hif_mib_set_uapsd_information uapsd_info; + struct hif_req_set_bss_params bss_params; + struct work_struct bss_params_work; + struct work_struct set_cts_work; + + int join_complete_status; + bool delayed_unjoin; + struct work_struct unjoin_work; struct wfx_scan scan; + + struct hif_req_set_pm_mode powersave_mode; + struct completion set_pm_mode_complete; + + struct list_head event_queue; + spinlock_t event_queue_lock; + struct work_struct event_handler_work; }; static inline struct wfx_vif *wdev_to_wvif(struct wfx_dev *wdev, int vif_id) @@ -126,6 +163,20 @@ static inline struct wfx_vif *wvif_iterate(struct wfx_dev *wdev, struct wfx_vif return NULL; } +static inline int wvif_count(struct wfx_dev *wdev) +{ + int i; + int ret = 0; + struct wfx_vif *wvif; + + for (i = 0; i < ARRAY_SIZE(wdev->vif); i++) { + wvif = wdev_to_wvif(wdev, i); + if (wvif) + ret++; + } + return ret; +} + static inline void memreverse(uint8_t *src, uint8_t length) { uint8_t *lo = src; -- 2.20.1