| // 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"); |
| } |