Commit 40115bbc authored by Jérôme Pouiller's avatar Jérôme Pouiller Committed by Greg Kroah-Hartman

staging: wfx: implement the rest of mac80211 API

Finish to fill struct ieee80211_ops with necessary callbacks. Driver is
now ready to be registered to mac80211.
Signed-off-by: default avatarJérôme Pouiller <jerome.pouiller@silabs.com>
Link: https://lore.kernel.org/r/20190919142527.31797-21-Jerome.Pouiller@silabs.comSigned-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent fb2490f6
......@@ -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);
......
......@@ -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);
}
......
......@@ -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
......
......@@ -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 },
......
......@@ -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)
{
......
......@@ -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);
......
......@@ -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 */
......
......@@ -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);
......
......@@ -9,17 +9,1192 @@
#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)
{
wvif->fwd_probe_req = enable;
return hif_set_rx_filter(wvif, wvif->filter_bssid,
wvif->fwd_probe_req);
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_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)
......@@ -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)
......
......@@ -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 */
......@@ -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;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment