mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-21 18:17:12 +07:00
77be2c54c5
This will allow the low level driver to make decision based on the vif such as queues etc... Since the vif might be NULL, we can't add it to the tracing functions. Signed-off-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com> [fix staging rtl8821ae driver] Signed-off-by: Johannes Berg <johannes.berg@intel.com>
1704 lines
42 KiB
C
1704 lines
42 KiB
C
/*
|
|
* Copyright (c) 2010 Broadcom Corporation
|
|
* Copyright (c) 2013 Hauke Mehrtens <hauke@hauke-m.de>
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#define __UNDEF_NO_VERSION__
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
#include <linux/bcma/bcma.h>
|
|
#include <net/mac80211.h>
|
|
#include <defs.h>
|
|
#include "phy/phy_int.h"
|
|
#include "d11.h"
|
|
#include "channel.h"
|
|
#include "scb.h"
|
|
#include "pub.h"
|
|
#include "ucode_loader.h"
|
|
#include "mac80211_if.h"
|
|
#include "main.h"
|
|
#include "debug.h"
|
|
#include "led.h"
|
|
|
|
#define N_TX_QUEUES 4 /* #tx queues on mac80211<->driver interface */
|
|
#define BRCMS_FLUSH_TIMEOUT 500 /* msec */
|
|
|
|
/* Flags we support */
|
|
#define MAC_FILTERS (FIF_PROMISC_IN_BSS | \
|
|
FIF_ALLMULTI | \
|
|
FIF_FCSFAIL | \
|
|
FIF_CONTROL | \
|
|
FIF_OTHER_BSS | \
|
|
FIF_BCN_PRBRESP_PROMISC | \
|
|
FIF_PSPOLL)
|
|
|
|
#define CHAN2GHZ(channel, freqency, chflags) { \
|
|
.band = IEEE80211_BAND_2GHZ, \
|
|
.center_freq = (freqency), \
|
|
.hw_value = (channel), \
|
|
.flags = chflags, \
|
|
.max_antenna_gain = 0, \
|
|
.max_power = 19, \
|
|
}
|
|
|
|
#define CHAN5GHZ(channel, chflags) { \
|
|
.band = IEEE80211_BAND_5GHZ, \
|
|
.center_freq = 5000 + 5*(channel), \
|
|
.hw_value = (channel), \
|
|
.flags = chflags, \
|
|
.max_antenna_gain = 0, \
|
|
.max_power = 21, \
|
|
}
|
|
|
|
#define RATE(rate100m, _flags) { \
|
|
.bitrate = (rate100m), \
|
|
.flags = (_flags), \
|
|
.hw_value = (rate100m / 5), \
|
|
}
|
|
|
|
struct firmware_hdr {
|
|
__le32 offset;
|
|
__le32 len;
|
|
__le32 idx;
|
|
};
|
|
|
|
static const char * const brcms_firmwares[MAX_FW_IMAGES] = {
|
|
"brcm/bcm43xx",
|
|
NULL
|
|
};
|
|
|
|
static int n_adapters_found;
|
|
|
|
MODULE_AUTHOR("Broadcom Corporation");
|
|
MODULE_DESCRIPTION("Broadcom 802.11n wireless LAN driver.");
|
|
MODULE_SUPPORTED_DEVICE("Broadcom 802.11n WLAN cards");
|
|
MODULE_LICENSE("Dual BSD/GPL");
|
|
/* This needs to be adjusted when brcms_firmwares changes */
|
|
MODULE_FIRMWARE("brcm/bcm43xx-0.fw");
|
|
MODULE_FIRMWARE("brcm/bcm43xx_hdr-0.fw");
|
|
|
|
/* recognized BCMA Core IDs */
|
|
static struct bcma_device_id brcms_coreid_table[] = {
|
|
BCMA_CORE(BCMA_MANUF_BCM, BCMA_CORE_80211, 17, BCMA_ANY_CLASS),
|
|
BCMA_CORE(BCMA_MANUF_BCM, BCMA_CORE_80211, 23, BCMA_ANY_CLASS),
|
|
BCMA_CORE(BCMA_MANUF_BCM, BCMA_CORE_80211, 24, BCMA_ANY_CLASS),
|
|
BCMA_CORETABLE_END
|
|
};
|
|
MODULE_DEVICE_TABLE(bcma, brcms_coreid_table);
|
|
|
|
#if defined(CONFIG_BRCMDBG)
|
|
/*
|
|
* Module parameter for setting the debug message level. Available
|
|
* flags are specified by the BRCM_DL_* macros in
|
|
* drivers/net/wireless/brcm80211/include/defs.h.
|
|
*/
|
|
module_param_named(debug, brcm_msg_level, uint, S_IRUGO | S_IWUSR);
|
|
#endif
|
|
|
|
static struct ieee80211_channel brcms_2ghz_chantable[] = {
|
|
CHAN2GHZ(1, 2412, IEEE80211_CHAN_NO_HT40MINUS),
|
|
CHAN2GHZ(2, 2417, IEEE80211_CHAN_NO_HT40MINUS),
|
|
CHAN2GHZ(3, 2422, IEEE80211_CHAN_NO_HT40MINUS),
|
|
CHAN2GHZ(4, 2427, IEEE80211_CHAN_NO_HT40MINUS),
|
|
CHAN2GHZ(5, 2432, 0),
|
|
CHAN2GHZ(6, 2437, 0),
|
|
CHAN2GHZ(7, 2442, 0),
|
|
CHAN2GHZ(8, 2447, IEEE80211_CHAN_NO_HT40PLUS),
|
|
CHAN2GHZ(9, 2452, IEEE80211_CHAN_NO_HT40PLUS),
|
|
CHAN2GHZ(10, 2457, IEEE80211_CHAN_NO_HT40PLUS),
|
|
CHAN2GHZ(11, 2462, IEEE80211_CHAN_NO_HT40PLUS),
|
|
CHAN2GHZ(12, 2467,
|
|
IEEE80211_CHAN_NO_IR |
|
|
IEEE80211_CHAN_NO_HT40PLUS),
|
|
CHAN2GHZ(13, 2472,
|
|
IEEE80211_CHAN_NO_IR |
|
|
IEEE80211_CHAN_NO_HT40PLUS),
|
|
CHAN2GHZ(14, 2484,
|
|
IEEE80211_CHAN_NO_IR |
|
|
IEEE80211_CHAN_NO_HT40PLUS | IEEE80211_CHAN_NO_HT40MINUS |
|
|
IEEE80211_CHAN_NO_OFDM)
|
|
};
|
|
|
|
static struct ieee80211_channel brcms_5ghz_nphy_chantable[] = {
|
|
/* UNII-1 */
|
|
CHAN5GHZ(36, IEEE80211_CHAN_NO_HT40MINUS),
|
|
CHAN5GHZ(40, IEEE80211_CHAN_NO_HT40PLUS),
|
|
CHAN5GHZ(44, IEEE80211_CHAN_NO_HT40MINUS),
|
|
CHAN5GHZ(48, IEEE80211_CHAN_NO_HT40PLUS),
|
|
/* UNII-2 */
|
|
CHAN5GHZ(52,
|
|
IEEE80211_CHAN_RADAR |
|
|
IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40MINUS),
|
|
CHAN5GHZ(56,
|
|
IEEE80211_CHAN_RADAR |
|
|
IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40PLUS),
|
|
CHAN5GHZ(60,
|
|
IEEE80211_CHAN_RADAR |
|
|
IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40MINUS),
|
|
CHAN5GHZ(64,
|
|
IEEE80211_CHAN_RADAR |
|
|
IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40PLUS),
|
|
/* MID */
|
|
CHAN5GHZ(100,
|
|
IEEE80211_CHAN_RADAR |
|
|
IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40MINUS),
|
|
CHAN5GHZ(104,
|
|
IEEE80211_CHAN_RADAR |
|
|
IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40PLUS),
|
|
CHAN5GHZ(108,
|
|
IEEE80211_CHAN_RADAR |
|
|
IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40MINUS),
|
|
CHAN5GHZ(112,
|
|
IEEE80211_CHAN_RADAR |
|
|
IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40PLUS),
|
|
CHAN5GHZ(116,
|
|
IEEE80211_CHAN_RADAR |
|
|
IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40MINUS),
|
|
CHAN5GHZ(120,
|
|
IEEE80211_CHAN_RADAR |
|
|
IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40PLUS),
|
|
CHAN5GHZ(124,
|
|
IEEE80211_CHAN_RADAR |
|
|
IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40MINUS),
|
|
CHAN5GHZ(128,
|
|
IEEE80211_CHAN_RADAR |
|
|
IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40PLUS),
|
|
CHAN5GHZ(132,
|
|
IEEE80211_CHAN_RADAR |
|
|
IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40MINUS),
|
|
CHAN5GHZ(136,
|
|
IEEE80211_CHAN_RADAR |
|
|
IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40PLUS),
|
|
CHAN5GHZ(140,
|
|
IEEE80211_CHAN_RADAR |
|
|
IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40PLUS |
|
|
IEEE80211_CHAN_NO_HT40MINUS),
|
|
/* UNII-3 */
|
|
CHAN5GHZ(149, IEEE80211_CHAN_NO_HT40MINUS),
|
|
CHAN5GHZ(153, IEEE80211_CHAN_NO_HT40PLUS),
|
|
CHAN5GHZ(157, IEEE80211_CHAN_NO_HT40MINUS),
|
|
CHAN5GHZ(161, IEEE80211_CHAN_NO_HT40PLUS),
|
|
CHAN5GHZ(165, IEEE80211_CHAN_NO_HT40PLUS | IEEE80211_CHAN_NO_HT40MINUS)
|
|
};
|
|
|
|
/*
|
|
* The rate table is used for both 2.4G and 5G rates. The
|
|
* latter being a subset as it does not support CCK rates.
|
|
*/
|
|
static struct ieee80211_rate legacy_ratetable[] = {
|
|
RATE(10, 0),
|
|
RATE(20, IEEE80211_RATE_SHORT_PREAMBLE),
|
|
RATE(55, IEEE80211_RATE_SHORT_PREAMBLE),
|
|
RATE(110, IEEE80211_RATE_SHORT_PREAMBLE),
|
|
RATE(60, 0),
|
|
RATE(90, 0),
|
|
RATE(120, 0),
|
|
RATE(180, 0),
|
|
RATE(240, 0),
|
|
RATE(360, 0),
|
|
RATE(480, 0),
|
|
RATE(540, 0),
|
|
};
|
|
|
|
static const struct ieee80211_supported_band brcms_band_2GHz_nphy_template = {
|
|
.band = IEEE80211_BAND_2GHZ,
|
|
.channels = brcms_2ghz_chantable,
|
|
.n_channels = ARRAY_SIZE(brcms_2ghz_chantable),
|
|
.bitrates = legacy_ratetable,
|
|
.n_bitrates = ARRAY_SIZE(legacy_ratetable),
|
|
.ht_cap = {
|
|
/* from include/linux/ieee80211.h */
|
|
.cap = IEEE80211_HT_CAP_GRN_FLD |
|
|
IEEE80211_HT_CAP_SGI_20 | IEEE80211_HT_CAP_SGI_40,
|
|
.ht_supported = true,
|
|
.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K,
|
|
.ampdu_density = AMPDU_DEF_MPDU_DENSITY,
|
|
.mcs = {
|
|
/* placeholders for now */
|
|
.rx_mask = {0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
.rx_highest = cpu_to_le16(500),
|
|
.tx_params = IEEE80211_HT_MCS_TX_DEFINED}
|
|
}
|
|
};
|
|
|
|
static const struct ieee80211_supported_band brcms_band_5GHz_nphy_template = {
|
|
.band = IEEE80211_BAND_5GHZ,
|
|
.channels = brcms_5ghz_nphy_chantable,
|
|
.n_channels = ARRAY_SIZE(brcms_5ghz_nphy_chantable),
|
|
.bitrates = legacy_ratetable + BRCMS_LEGACY_5G_RATE_OFFSET,
|
|
.n_bitrates = ARRAY_SIZE(legacy_ratetable) -
|
|
BRCMS_LEGACY_5G_RATE_OFFSET,
|
|
.ht_cap = {
|
|
.cap = IEEE80211_HT_CAP_GRN_FLD | IEEE80211_HT_CAP_SGI_20 |
|
|
IEEE80211_HT_CAP_SGI_40,
|
|
.ht_supported = true,
|
|
.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K,
|
|
.ampdu_density = AMPDU_DEF_MPDU_DENSITY,
|
|
.mcs = {
|
|
/* placeholders for now */
|
|
.rx_mask = {0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
.rx_highest = cpu_to_le16(500),
|
|
.tx_params = IEEE80211_HT_MCS_TX_DEFINED}
|
|
}
|
|
};
|
|
|
|
/* flags the given rate in rateset as requested */
|
|
static void brcms_set_basic_rate(struct brcm_rateset *rs, u16 rate, bool is_br)
|
|
{
|
|
u32 i;
|
|
|
|
for (i = 0; i < rs->count; i++) {
|
|
if (rate != (rs->rates[i] & 0x7f))
|
|
continue;
|
|
|
|
if (is_br)
|
|
rs->rates[i] |= BRCMS_RATE_FLAG;
|
|
else
|
|
rs->rates[i] &= BRCMS_RATE_MASK;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function frees the WL per-device resources.
|
|
*
|
|
* This function frees resources owned by the WL device pointed to
|
|
* by the wl parameter.
|
|
*
|
|
* precondition: can both be called locked and unlocked
|
|
*
|
|
*/
|
|
static void brcms_free(struct brcms_info *wl)
|
|
{
|
|
struct brcms_timer *t, *next;
|
|
|
|
/* free ucode data */
|
|
if (wl->fw.fw_cnt)
|
|
brcms_ucode_data_free(&wl->ucode);
|
|
if (wl->irq)
|
|
free_irq(wl->irq, wl);
|
|
|
|
/* kill dpc */
|
|
tasklet_kill(&wl->tasklet);
|
|
|
|
if (wl->pub) {
|
|
brcms_debugfs_detach(wl->pub);
|
|
brcms_c_module_unregister(wl->pub, "linux", wl);
|
|
}
|
|
|
|
/* free common resources */
|
|
if (wl->wlc) {
|
|
brcms_c_detach(wl->wlc);
|
|
wl->wlc = NULL;
|
|
wl->pub = NULL;
|
|
}
|
|
|
|
/* virtual interface deletion is deferred so we cannot spinwait */
|
|
|
|
/* wait for all pending callbacks to complete */
|
|
while (atomic_read(&wl->callbacks) > 0)
|
|
schedule();
|
|
|
|
/* free timers */
|
|
for (t = wl->timers; t; t = next) {
|
|
next = t->next;
|
|
#ifdef DEBUG
|
|
kfree(t->name);
|
|
#endif
|
|
kfree(t);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* called from both kernel as from this kernel module (error flow on attach)
|
|
* precondition: perimeter lock is not acquired.
|
|
*/
|
|
static void brcms_remove(struct bcma_device *pdev)
|
|
{
|
|
struct ieee80211_hw *hw = bcma_get_drvdata(pdev);
|
|
struct brcms_info *wl = hw->priv;
|
|
|
|
if (wl->wlc) {
|
|
brcms_led_unregister(wl);
|
|
wiphy_rfkill_set_hw_state(wl->pub->ieee_hw->wiphy, false);
|
|
wiphy_rfkill_stop_polling(wl->pub->ieee_hw->wiphy);
|
|
ieee80211_unregister_hw(hw);
|
|
}
|
|
|
|
brcms_free(wl);
|
|
|
|
bcma_set_drvdata(pdev, NULL);
|
|
ieee80211_free_hw(hw);
|
|
}
|
|
|
|
/*
|
|
* Precondition: Since this function is called in brcms_pci_probe() context,
|
|
* no locking is required.
|
|
*/
|
|
static void brcms_release_fw(struct brcms_info *wl)
|
|
{
|
|
int i;
|
|
for (i = 0; i < MAX_FW_IMAGES; i++) {
|
|
release_firmware(wl->fw.fw_bin[i]);
|
|
release_firmware(wl->fw.fw_hdr[i]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Precondition: Since this function is called in brcms_pci_probe() context,
|
|
* no locking is required.
|
|
*/
|
|
static int brcms_request_fw(struct brcms_info *wl, struct bcma_device *pdev)
|
|
{
|
|
int status;
|
|
struct device *device = &pdev->dev;
|
|
char fw_name[100];
|
|
int i;
|
|
|
|
memset(&wl->fw, 0, sizeof(struct brcms_firmware));
|
|
for (i = 0; i < MAX_FW_IMAGES; i++) {
|
|
if (brcms_firmwares[i] == NULL)
|
|
break;
|
|
sprintf(fw_name, "%s-%d.fw", brcms_firmwares[i],
|
|
UCODE_LOADER_API_VER);
|
|
status = request_firmware(&wl->fw.fw_bin[i], fw_name, device);
|
|
if (status) {
|
|
wiphy_err(wl->wiphy, "%s: fail to load firmware %s\n",
|
|
KBUILD_MODNAME, fw_name);
|
|
return status;
|
|
}
|
|
sprintf(fw_name, "%s_hdr-%d.fw", brcms_firmwares[i],
|
|
UCODE_LOADER_API_VER);
|
|
status = request_firmware(&wl->fw.fw_hdr[i], fw_name, device);
|
|
if (status) {
|
|
wiphy_err(wl->wiphy, "%s: fail to load firmware %s\n",
|
|
KBUILD_MODNAME, fw_name);
|
|
return status;
|
|
}
|
|
wl->fw.hdr_num_entries[i] =
|
|
wl->fw.fw_hdr[i]->size / (sizeof(struct firmware_hdr));
|
|
}
|
|
wl->fw.fw_cnt = i;
|
|
status = brcms_ucode_data_init(wl, &wl->ucode);
|
|
brcms_release_fw(wl);
|
|
return status;
|
|
}
|
|
|
|
static void brcms_ops_tx(struct ieee80211_hw *hw,
|
|
struct ieee80211_tx_control *control,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct brcms_info *wl = hw->priv;
|
|
struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
|
|
|
|
spin_lock_bh(&wl->lock);
|
|
if (!wl->pub->up) {
|
|
brcms_err(wl->wlc->hw->d11core, "ops->tx called while down\n");
|
|
kfree_skb(skb);
|
|
goto done;
|
|
}
|
|
if (brcms_c_sendpkt_mac80211(wl->wlc, skb, hw))
|
|
tx_info->rate_driver_data[0] = control->sta;
|
|
done:
|
|
spin_unlock_bh(&wl->lock);
|
|
}
|
|
|
|
static int brcms_ops_start(struct ieee80211_hw *hw)
|
|
{
|
|
struct brcms_info *wl = hw->priv;
|
|
bool blocked;
|
|
int err;
|
|
|
|
if (!wl->ucode.bcm43xx_bomminor) {
|
|
err = brcms_request_fw(wl, wl->wlc->hw->d11core);
|
|
if (err)
|
|
return -ENOENT;
|
|
}
|
|
|
|
ieee80211_wake_queues(hw);
|
|
spin_lock_bh(&wl->lock);
|
|
blocked = brcms_rfkill_set_hw_state(wl);
|
|
spin_unlock_bh(&wl->lock);
|
|
if (!blocked)
|
|
wiphy_rfkill_stop_polling(wl->pub->ieee_hw->wiphy);
|
|
|
|
spin_lock_bh(&wl->lock);
|
|
/* avoid acknowledging frames before a non-monitor device is added */
|
|
wl->mute_tx = true;
|
|
|
|
if (!wl->pub->up)
|
|
if (!blocked)
|
|
err = brcms_up(wl);
|
|
else
|
|
err = -ERFKILL;
|
|
else
|
|
err = -ENODEV;
|
|
spin_unlock_bh(&wl->lock);
|
|
|
|
if (err != 0)
|
|
brcms_err(wl->wlc->hw->d11core, "%s: brcms_up() returned %d\n",
|
|
__func__, err);
|
|
|
|
bcma_core_pci_power_save(wl->wlc->hw->d11core->bus, true);
|
|
return err;
|
|
}
|
|
|
|
static void brcms_ops_stop(struct ieee80211_hw *hw)
|
|
{
|
|
struct brcms_info *wl = hw->priv;
|
|
int status;
|
|
|
|
ieee80211_stop_queues(hw);
|
|
|
|
if (wl->wlc == NULL)
|
|
return;
|
|
|
|
spin_lock_bh(&wl->lock);
|
|
status = brcms_c_chipmatch(wl->wlc->hw->d11core);
|
|
spin_unlock_bh(&wl->lock);
|
|
if (!status) {
|
|
brcms_err(wl->wlc->hw->d11core,
|
|
"wl: brcms_ops_stop: chipmatch failed\n");
|
|
return;
|
|
}
|
|
|
|
bcma_core_pci_power_save(wl->wlc->hw->d11core->bus, false);
|
|
|
|
/* put driver in down state */
|
|
spin_lock_bh(&wl->lock);
|
|
brcms_down(wl);
|
|
spin_unlock_bh(&wl->lock);
|
|
}
|
|
|
|
static int
|
|
brcms_ops_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
|
|
{
|
|
struct brcms_info *wl = hw->priv;
|
|
|
|
/* Just STA, AP and ADHOC for now */
|
|
if (vif->type != NL80211_IFTYPE_STATION &&
|
|
vif->type != NL80211_IFTYPE_AP &&
|
|
vif->type != NL80211_IFTYPE_ADHOC) {
|
|
brcms_err(wl->wlc->hw->d11core,
|
|
"%s: Attempt to add type %d, only STA, AP and AdHoc for now\n",
|
|
__func__, vif->type);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
spin_lock_bh(&wl->lock);
|
|
wl->mute_tx = false;
|
|
brcms_c_mute(wl->wlc, false);
|
|
if (vif->type == NL80211_IFTYPE_STATION)
|
|
brcms_c_start_station(wl->wlc, vif->addr);
|
|
else if (vif->type == NL80211_IFTYPE_AP)
|
|
brcms_c_start_ap(wl->wlc, vif->addr, vif->bss_conf.bssid,
|
|
vif->bss_conf.ssid, vif->bss_conf.ssid_len);
|
|
else if (vif->type == NL80211_IFTYPE_ADHOC)
|
|
brcms_c_start_adhoc(wl->wlc, vif->addr);
|
|
spin_unlock_bh(&wl->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
brcms_ops_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
|
|
{
|
|
}
|
|
|
|
static int brcms_ops_config(struct ieee80211_hw *hw, u32 changed)
|
|
{
|
|
struct ieee80211_conf *conf = &hw->conf;
|
|
struct brcms_info *wl = hw->priv;
|
|
struct bcma_device *core = wl->wlc->hw->d11core;
|
|
int err = 0;
|
|
int new_int;
|
|
|
|
spin_lock_bh(&wl->lock);
|
|
if (changed & IEEE80211_CONF_CHANGE_LISTEN_INTERVAL) {
|
|
brcms_c_set_beacon_listen_interval(wl->wlc,
|
|
conf->listen_interval);
|
|
}
|
|
if (changed & IEEE80211_CONF_CHANGE_MONITOR)
|
|
brcms_dbg_info(core, "%s: change monitor mode: %s\n",
|
|
__func__, conf->flags & IEEE80211_CONF_MONITOR ?
|
|
"true" : "false");
|
|
if (changed & IEEE80211_CONF_CHANGE_PS)
|
|
brcms_err(core, "%s: change power-save mode: %s (implement)\n",
|
|
__func__, conf->flags & IEEE80211_CONF_PS ?
|
|
"true" : "false");
|
|
|
|
if (changed & IEEE80211_CONF_CHANGE_POWER) {
|
|
err = brcms_c_set_tx_power(wl->wlc, conf->power_level);
|
|
if (err < 0) {
|
|
brcms_err(core, "%s: Error setting power_level\n",
|
|
__func__);
|
|
goto config_out;
|
|
}
|
|
new_int = brcms_c_get_tx_power(wl->wlc);
|
|
if (new_int != conf->power_level)
|
|
brcms_err(core,
|
|
"%s: Power level req != actual, %d %d\n",
|
|
__func__, conf->power_level,
|
|
new_int);
|
|
}
|
|
if (changed & IEEE80211_CONF_CHANGE_CHANNEL) {
|
|
if (conf->chandef.width == NL80211_CHAN_WIDTH_20 ||
|
|
conf->chandef.width == NL80211_CHAN_WIDTH_20_NOHT)
|
|
err = brcms_c_set_channel(wl->wlc,
|
|
conf->chandef.chan->hw_value);
|
|
else
|
|
err = -ENOTSUPP;
|
|
}
|
|
if (changed & IEEE80211_CONF_CHANGE_RETRY_LIMITS)
|
|
err = brcms_c_set_rate_limit(wl->wlc,
|
|
conf->short_frame_max_tx_count,
|
|
conf->long_frame_max_tx_count);
|
|
|
|
config_out:
|
|
spin_unlock_bh(&wl->lock);
|
|
return err;
|
|
}
|
|
|
|
static void
|
|
brcms_ops_bss_info_changed(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_bss_conf *info, u32 changed)
|
|
{
|
|
struct brcms_info *wl = hw->priv;
|
|
struct bcma_device *core = wl->wlc->hw->d11core;
|
|
|
|
if (changed & BSS_CHANGED_ASSOC) {
|
|
/* association status changed (associated/disassociated)
|
|
* also implies a change in the AID.
|
|
*/
|
|
brcms_err(core, "%s: %s: %sassociated\n", KBUILD_MODNAME,
|
|
__func__, info->assoc ? "" : "dis");
|
|
spin_lock_bh(&wl->lock);
|
|
brcms_c_associate_upd(wl->wlc, info->assoc);
|
|
spin_unlock_bh(&wl->lock);
|
|
}
|
|
if (changed & BSS_CHANGED_ERP_SLOT) {
|
|
s8 val;
|
|
|
|
/* slot timing changed */
|
|
if (info->use_short_slot)
|
|
val = 1;
|
|
else
|
|
val = 0;
|
|
spin_lock_bh(&wl->lock);
|
|
brcms_c_set_shortslot_override(wl->wlc, val);
|
|
spin_unlock_bh(&wl->lock);
|
|
}
|
|
|
|
if (changed & BSS_CHANGED_HT) {
|
|
/* 802.11n parameters changed */
|
|
u16 mode = info->ht_operation_mode;
|
|
|
|
spin_lock_bh(&wl->lock);
|
|
brcms_c_protection_upd(wl->wlc, BRCMS_PROT_N_CFG,
|
|
mode & IEEE80211_HT_OP_MODE_PROTECTION);
|
|
brcms_c_protection_upd(wl->wlc, BRCMS_PROT_N_NONGF,
|
|
mode & IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT);
|
|
brcms_c_protection_upd(wl->wlc, BRCMS_PROT_N_OBSS,
|
|
mode & IEEE80211_HT_OP_MODE_NON_HT_STA_PRSNT);
|
|
spin_unlock_bh(&wl->lock);
|
|
}
|
|
if (changed & BSS_CHANGED_BASIC_RATES) {
|
|
struct ieee80211_supported_band *bi;
|
|
u32 br_mask, i;
|
|
u16 rate;
|
|
struct brcm_rateset rs;
|
|
int error;
|
|
|
|
/* retrieve the current rates */
|
|
spin_lock_bh(&wl->lock);
|
|
brcms_c_get_current_rateset(wl->wlc, &rs);
|
|
spin_unlock_bh(&wl->lock);
|
|
|
|
br_mask = info->basic_rates;
|
|
bi = hw->wiphy->bands[brcms_c_get_curband(wl->wlc)];
|
|
for (i = 0; i < bi->n_bitrates; i++) {
|
|
/* convert to internal rate value */
|
|
rate = (bi->bitrates[i].bitrate << 1) / 10;
|
|
|
|
/* set/clear basic rate flag */
|
|
brcms_set_basic_rate(&rs, rate, br_mask & 1);
|
|
br_mask >>= 1;
|
|
}
|
|
|
|
/* update the rate set */
|
|
spin_lock_bh(&wl->lock);
|
|
error = brcms_c_set_rateset(wl->wlc, &rs);
|
|
spin_unlock_bh(&wl->lock);
|
|
if (error)
|
|
brcms_err(core, "changing basic rates failed: %d\n",
|
|
error);
|
|
}
|
|
if (changed & BSS_CHANGED_BEACON_INT) {
|
|
/* Beacon interval changed */
|
|
spin_lock_bh(&wl->lock);
|
|
brcms_c_set_beacon_period(wl->wlc, info->beacon_int);
|
|
spin_unlock_bh(&wl->lock);
|
|
}
|
|
if (changed & BSS_CHANGED_BSSID) {
|
|
/* BSSID changed, for whatever reason (IBSS and managed mode) */
|
|
spin_lock_bh(&wl->lock);
|
|
brcms_c_set_addrmatch(wl->wlc, RCM_BSSID_OFFSET, info->bssid);
|
|
spin_unlock_bh(&wl->lock);
|
|
}
|
|
if (changed & BSS_CHANGED_SSID) {
|
|
/* BSSID changed, for whatever reason (IBSS and managed mode) */
|
|
spin_lock_bh(&wl->lock);
|
|
brcms_c_set_ssid(wl->wlc, info->ssid, info->ssid_len);
|
|
spin_unlock_bh(&wl->lock);
|
|
}
|
|
if (changed & BSS_CHANGED_BEACON) {
|
|
/* Beacon data changed, retrieve new beacon (beaconing modes) */
|
|
struct sk_buff *beacon;
|
|
u16 tim_offset = 0;
|
|
|
|
spin_lock_bh(&wl->lock);
|
|
beacon = ieee80211_beacon_get_tim(hw, vif, &tim_offset, NULL);
|
|
brcms_c_set_new_beacon(wl->wlc, beacon, tim_offset,
|
|
info->dtim_period);
|
|
spin_unlock_bh(&wl->lock);
|
|
}
|
|
|
|
if (changed & BSS_CHANGED_AP_PROBE_RESP) {
|
|
struct sk_buff *probe_resp;
|
|
|
|
spin_lock_bh(&wl->lock);
|
|
probe_resp = ieee80211_proberesp_get(hw, vif);
|
|
brcms_c_set_new_probe_resp(wl->wlc, probe_resp);
|
|
spin_unlock_bh(&wl->lock);
|
|
}
|
|
|
|
if (changed & BSS_CHANGED_BEACON_ENABLED) {
|
|
/* Beaconing should be enabled/disabled (beaconing modes) */
|
|
brcms_err(core, "%s: Beacon enabled: %s\n", __func__,
|
|
info->enable_beacon ? "true" : "false");
|
|
if (info->enable_beacon &&
|
|
hw->wiphy->flags & WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD) {
|
|
brcms_c_enable_probe_resp(wl->wlc, true);
|
|
} else {
|
|
brcms_c_enable_probe_resp(wl->wlc, false);
|
|
}
|
|
}
|
|
|
|
if (changed & BSS_CHANGED_CQM) {
|
|
/* Connection quality monitor config changed */
|
|
brcms_err(core, "%s: cqm change: threshold %d, hys %d "
|
|
" (implement)\n", __func__, info->cqm_rssi_thold,
|
|
info->cqm_rssi_hyst);
|
|
}
|
|
|
|
if (changed & BSS_CHANGED_IBSS) {
|
|
/* IBSS join status changed */
|
|
brcms_err(core, "%s: IBSS joined: %s (implement)\n",
|
|
__func__, info->ibss_joined ? "true" : "false");
|
|
}
|
|
|
|
if (changed & BSS_CHANGED_ARP_FILTER) {
|
|
/* Hardware ARP filter address list or state changed */
|
|
brcms_err(core, "%s: arp filtering: %d addresses"
|
|
" (implement)\n", __func__, info->arp_addr_cnt);
|
|
}
|
|
|
|
if (changed & BSS_CHANGED_QOS) {
|
|
/*
|
|
* QoS for this association was enabled/disabled.
|
|
* Note that it is only ever disabled for station mode.
|
|
*/
|
|
brcms_err(core, "%s: qos enabled: %s (implement)\n",
|
|
__func__, info->qos ? "true" : "false");
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
brcms_ops_configure_filter(struct ieee80211_hw *hw,
|
|
unsigned int changed_flags,
|
|
unsigned int *total_flags, u64 multicast)
|
|
{
|
|
struct brcms_info *wl = hw->priv;
|
|
struct bcma_device *core = wl->wlc->hw->d11core;
|
|
|
|
changed_flags &= MAC_FILTERS;
|
|
*total_flags &= MAC_FILTERS;
|
|
|
|
if (changed_flags & FIF_PROMISC_IN_BSS)
|
|
brcms_dbg_info(core, "FIF_PROMISC_IN_BSS\n");
|
|
if (changed_flags & FIF_ALLMULTI)
|
|
brcms_dbg_info(core, "FIF_ALLMULTI\n");
|
|
if (changed_flags & FIF_FCSFAIL)
|
|
brcms_dbg_info(core, "FIF_FCSFAIL\n");
|
|
if (changed_flags & FIF_CONTROL)
|
|
brcms_dbg_info(core, "FIF_CONTROL\n");
|
|
if (changed_flags & FIF_OTHER_BSS)
|
|
brcms_dbg_info(core, "FIF_OTHER_BSS\n");
|
|
if (changed_flags & FIF_PSPOLL)
|
|
brcms_dbg_info(core, "FIF_PSPOLL\n");
|
|
if (changed_flags & FIF_BCN_PRBRESP_PROMISC)
|
|
brcms_dbg_info(core, "FIF_BCN_PRBRESP_PROMISC\n");
|
|
|
|
spin_lock_bh(&wl->lock);
|
|
brcms_c_mac_promisc(wl->wlc, *total_flags);
|
|
spin_unlock_bh(&wl->lock);
|
|
return;
|
|
}
|
|
|
|
static void brcms_ops_sw_scan_start(struct ieee80211_hw *hw)
|
|
{
|
|
struct brcms_info *wl = hw->priv;
|
|
spin_lock_bh(&wl->lock);
|
|
brcms_c_scan_start(wl->wlc);
|
|
spin_unlock_bh(&wl->lock);
|
|
return;
|
|
}
|
|
|
|
static void brcms_ops_sw_scan_complete(struct ieee80211_hw *hw)
|
|
{
|
|
struct brcms_info *wl = hw->priv;
|
|
spin_lock_bh(&wl->lock);
|
|
brcms_c_scan_stop(wl->wlc);
|
|
spin_unlock_bh(&wl->lock);
|
|
return;
|
|
}
|
|
|
|
static int
|
|
brcms_ops_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u16 queue,
|
|
const struct ieee80211_tx_queue_params *params)
|
|
{
|
|
struct brcms_info *wl = hw->priv;
|
|
|
|
spin_lock_bh(&wl->lock);
|
|
brcms_c_wme_setparams(wl->wlc, queue, params, true);
|
|
spin_unlock_bh(&wl->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
brcms_ops_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
|
|
struct ieee80211_sta *sta)
|
|
{
|
|
struct brcms_info *wl = hw->priv;
|
|
struct scb *scb = &wl->wlc->pri_scb;
|
|
|
|
brcms_c_init_scb(scb);
|
|
|
|
wl->pub->global_ampdu = &(scb->scb_ampdu);
|
|
wl->pub->global_ampdu->scb = scb;
|
|
wl->pub->global_ampdu->max_pdu = 16;
|
|
|
|
/*
|
|
* minstrel_ht initiates addBA on our behalf by calling
|
|
* ieee80211_start_tx_ba_session()
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
brcms_ops_ampdu_action(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
enum ieee80211_ampdu_mlme_action action,
|
|
struct ieee80211_sta *sta, u16 tid, u16 *ssn,
|
|
u8 buf_size)
|
|
{
|
|
struct brcms_info *wl = hw->priv;
|
|
struct scb *scb = &wl->wlc->pri_scb;
|
|
int status;
|
|
|
|
if (WARN_ON(scb->magic != SCB_MAGIC))
|
|
return -EIDRM;
|
|
switch (action) {
|
|
case IEEE80211_AMPDU_RX_START:
|
|
break;
|
|
case IEEE80211_AMPDU_RX_STOP:
|
|
break;
|
|
case IEEE80211_AMPDU_TX_START:
|
|
spin_lock_bh(&wl->lock);
|
|
status = brcms_c_aggregatable(wl->wlc, tid);
|
|
spin_unlock_bh(&wl->lock);
|
|
if (!status) {
|
|
brcms_err(wl->wlc->hw->d11core,
|
|
"START: tid %d is not agg\'able\n", tid);
|
|
return -EINVAL;
|
|
}
|
|
ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid);
|
|
break;
|
|
|
|
case IEEE80211_AMPDU_TX_STOP_CONT:
|
|
case IEEE80211_AMPDU_TX_STOP_FLUSH:
|
|
case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
|
|
spin_lock_bh(&wl->lock);
|
|
brcms_c_ampdu_flush(wl->wlc, sta, tid);
|
|
spin_unlock_bh(&wl->lock);
|
|
ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
|
|
break;
|
|
case IEEE80211_AMPDU_TX_OPERATIONAL:
|
|
/*
|
|
* BA window size from ADDBA response ('buf_size') defines how
|
|
* many outstanding MPDUs are allowed for the BA stream by
|
|
* recipient and traffic class. 'ampdu_factor' gives maximum
|
|
* AMPDU size.
|
|
*/
|
|
spin_lock_bh(&wl->lock);
|
|
brcms_c_ampdu_tx_operational(wl->wlc, tid, buf_size,
|
|
(1 << (IEEE80211_HT_MAX_AMPDU_FACTOR +
|
|
sta->ht_cap.ampdu_factor)) - 1);
|
|
spin_unlock_bh(&wl->lock);
|
|
/* Power save wakeup */
|
|
break;
|
|
default:
|
|
brcms_err(wl->wlc->hw->d11core,
|
|
"%s: Invalid command, ignoring\n", __func__);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void brcms_ops_rfkill_poll(struct ieee80211_hw *hw)
|
|
{
|
|
struct brcms_info *wl = hw->priv;
|
|
bool blocked;
|
|
|
|
spin_lock_bh(&wl->lock);
|
|
blocked = brcms_c_check_radio_disabled(wl->wlc);
|
|
spin_unlock_bh(&wl->lock);
|
|
|
|
wiphy_rfkill_set_hw_state(wl->pub->ieee_hw->wiphy, blocked);
|
|
}
|
|
|
|
static bool brcms_tx_flush_completed(struct brcms_info *wl)
|
|
{
|
|
bool result;
|
|
|
|
spin_lock_bh(&wl->lock);
|
|
result = brcms_c_tx_flush_completed(wl->wlc);
|
|
spin_unlock_bh(&wl->lock);
|
|
return result;
|
|
}
|
|
|
|
static void brcms_ops_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
|
|
u32 queues, bool drop)
|
|
{
|
|
struct brcms_info *wl = hw->priv;
|
|
int ret;
|
|
|
|
no_printk("%s: drop = %s\n", __func__, drop ? "true" : "false");
|
|
|
|
ret = wait_event_timeout(wl->tx_flush_wq,
|
|
brcms_tx_flush_completed(wl),
|
|
msecs_to_jiffies(BRCMS_FLUSH_TIMEOUT));
|
|
|
|
brcms_dbg_mac80211(wl->wlc->hw->d11core,
|
|
"ret=%d\n", jiffies_to_msecs(ret));
|
|
}
|
|
|
|
static u64 brcms_ops_get_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
|
|
{
|
|
struct brcms_info *wl = hw->priv;
|
|
u64 tsf;
|
|
|
|
spin_lock_bh(&wl->lock);
|
|
tsf = brcms_c_tsf_get(wl->wlc);
|
|
spin_unlock_bh(&wl->lock);
|
|
|
|
return tsf;
|
|
}
|
|
|
|
static void brcms_ops_set_tsf(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif, u64 tsf)
|
|
{
|
|
struct brcms_info *wl = hw->priv;
|
|
|
|
spin_lock_bh(&wl->lock);
|
|
brcms_c_tsf_set(wl->wlc, tsf);
|
|
spin_unlock_bh(&wl->lock);
|
|
}
|
|
|
|
static const struct ieee80211_ops brcms_ops = {
|
|
.tx = brcms_ops_tx,
|
|
.start = brcms_ops_start,
|
|
.stop = brcms_ops_stop,
|
|
.add_interface = brcms_ops_add_interface,
|
|
.remove_interface = brcms_ops_remove_interface,
|
|
.config = brcms_ops_config,
|
|
.bss_info_changed = brcms_ops_bss_info_changed,
|
|
.configure_filter = brcms_ops_configure_filter,
|
|
.sw_scan_start = brcms_ops_sw_scan_start,
|
|
.sw_scan_complete = brcms_ops_sw_scan_complete,
|
|
.conf_tx = brcms_ops_conf_tx,
|
|
.sta_add = brcms_ops_sta_add,
|
|
.ampdu_action = brcms_ops_ampdu_action,
|
|
.rfkill_poll = brcms_ops_rfkill_poll,
|
|
.flush = brcms_ops_flush,
|
|
.get_tsf = brcms_ops_get_tsf,
|
|
.set_tsf = brcms_ops_set_tsf,
|
|
};
|
|
|
|
void brcms_dpc(unsigned long data)
|
|
{
|
|
struct brcms_info *wl;
|
|
|
|
wl = (struct brcms_info *) data;
|
|
|
|
spin_lock_bh(&wl->lock);
|
|
|
|
/* call the common second level interrupt handler */
|
|
if (wl->pub->up) {
|
|
if (wl->resched) {
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&wl->isr_lock, flags);
|
|
brcms_c_intrsupd(wl->wlc);
|
|
spin_unlock_irqrestore(&wl->isr_lock, flags);
|
|
}
|
|
|
|
wl->resched = brcms_c_dpc(wl->wlc, true);
|
|
}
|
|
|
|
/* brcms_c_dpc() may bring the driver down */
|
|
if (!wl->pub->up)
|
|
goto done;
|
|
|
|
/* re-schedule dpc */
|
|
if (wl->resched)
|
|
tasklet_schedule(&wl->tasklet);
|
|
else
|
|
/* re-enable interrupts */
|
|
brcms_intrson(wl);
|
|
|
|
done:
|
|
spin_unlock_bh(&wl->lock);
|
|
wake_up(&wl->tx_flush_wq);
|
|
}
|
|
|
|
static irqreturn_t brcms_isr(int irq, void *dev_id)
|
|
{
|
|
struct brcms_info *wl;
|
|
irqreturn_t ret = IRQ_NONE;
|
|
|
|
wl = (struct brcms_info *) dev_id;
|
|
|
|
spin_lock(&wl->isr_lock);
|
|
|
|
/* call common first level interrupt handler */
|
|
if (brcms_c_isr(wl->wlc)) {
|
|
/* schedule second level handler */
|
|
tasklet_schedule(&wl->tasklet);
|
|
ret = IRQ_HANDLED;
|
|
}
|
|
|
|
spin_unlock(&wl->isr_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* is called in brcms_pci_probe() context, therefore no locking required.
|
|
*/
|
|
static int ieee_hw_rate_init(struct ieee80211_hw *hw)
|
|
{
|
|
struct brcms_info *wl = hw->priv;
|
|
struct brcms_c_info *wlc = wl->wlc;
|
|
struct ieee80211_supported_band *band;
|
|
int has_5g = 0;
|
|
u16 phy_type;
|
|
|
|
hw->wiphy->bands[IEEE80211_BAND_2GHZ] = NULL;
|
|
hw->wiphy->bands[IEEE80211_BAND_5GHZ] = NULL;
|
|
|
|
phy_type = brcms_c_get_phy_type(wl->wlc, 0);
|
|
if (phy_type == PHY_TYPE_N || phy_type == PHY_TYPE_LCN) {
|
|
band = &wlc->bandstate[BAND_2G_INDEX]->band;
|
|
*band = brcms_band_2GHz_nphy_template;
|
|
if (phy_type == PHY_TYPE_LCN) {
|
|
/* Single stream */
|
|
band->ht_cap.mcs.rx_mask[1] = 0;
|
|
band->ht_cap.mcs.rx_highest = cpu_to_le16(72);
|
|
}
|
|
hw->wiphy->bands[IEEE80211_BAND_2GHZ] = band;
|
|
} else {
|
|
return -EPERM;
|
|
}
|
|
|
|
/* Assume all bands use the same phy. True for 11n devices. */
|
|
if (wl->pub->_nbands > 1) {
|
|
has_5g++;
|
|
if (phy_type == PHY_TYPE_N || phy_type == PHY_TYPE_LCN) {
|
|
band = &wlc->bandstate[BAND_5G_INDEX]->band;
|
|
*band = brcms_band_5GHz_nphy_template;
|
|
hw->wiphy->bands[IEEE80211_BAND_5GHZ] = band;
|
|
} else {
|
|
return -EPERM;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* is called in brcms_pci_probe() context, therefore no locking required.
|
|
*/
|
|
static int ieee_hw_init(struct ieee80211_hw *hw)
|
|
{
|
|
hw->flags = IEEE80211_HW_SIGNAL_DBM
|
|
/* | IEEE80211_HW_CONNECTION_MONITOR What is this? */
|
|
| IEEE80211_HW_REPORTS_TX_ACK_STATUS
|
|
| IEEE80211_HW_AMPDU_AGGREGATION;
|
|
|
|
hw->extra_tx_headroom = brcms_c_get_header_len();
|
|
hw->queues = N_TX_QUEUES;
|
|
hw->max_rates = 2; /* Primary rate and 1 fallback rate */
|
|
|
|
/* channel change time is dependent on chip and band */
|
|
hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
|
|
BIT(NL80211_IFTYPE_AP) |
|
|
BIT(NL80211_IFTYPE_ADHOC);
|
|
|
|
/*
|
|
* deactivate sending probe responses by ucude, because this will
|
|
* cause problems when WPS is used.
|
|
*
|
|
* hw->wiphy->flags |= WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD;
|
|
*/
|
|
|
|
hw->rate_control_algorithm = "minstrel_ht";
|
|
|
|
hw->sta_data_size = 0;
|
|
return ieee_hw_rate_init(hw);
|
|
}
|
|
|
|
/**
|
|
* attach to the WL device.
|
|
*
|
|
* Attach to the WL device identified by vendor and device parameters.
|
|
* regs is a host accessible memory address pointing to WL device registers.
|
|
*
|
|
* is called in brcms_bcma_probe() context, therefore no locking required.
|
|
*/
|
|
static struct brcms_info *brcms_attach(struct bcma_device *pdev)
|
|
{
|
|
struct brcms_info *wl = NULL;
|
|
int unit, err;
|
|
struct ieee80211_hw *hw;
|
|
u8 perm[ETH_ALEN];
|
|
|
|
unit = n_adapters_found;
|
|
err = 0;
|
|
|
|
if (unit < 0)
|
|
return NULL;
|
|
|
|
/* allocate private info */
|
|
hw = bcma_get_drvdata(pdev);
|
|
if (hw != NULL)
|
|
wl = hw->priv;
|
|
if (WARN_ON(hw == NULL) || WARN_ON(wl == NULL))
|
|
return NULL;
|
|
wl->wiphy = hw->wiphy;
|
|
|
|
atomic_set(&wl->callbacks, 0);
|
|
|
|
init_waitqueue_head(&wl->tx_flush_wq);
|
|
|
|
/* setup the bottom half handler */
|
|
tasklet_init(&wl->tasklet, brcms_dpc, (unsigned long) wl);
|
|
|
|
spin_lock_init(&wl->lock);
|
|
spin_lock_init(&wl->isr_lock);
|
|
|
|
/* common load-time initialization */
|
|
wl->wlc = brcms_c_attach((void *)wl, pdev, unit, false, &err);
|
|
if (!wl->wlc) {
|
|
wiphy_err(wl->wiphy, "%s: attach() failed with code %d\n",
|
|
KBUILD_MODNAME, err);
|
|
goto fail;
|
|
}
|
|
wl->pub = brcms_c_pub(wl->wlc);
|
|
|
|
wl->pub->ieee_hw = hw;
|
|
|
|
/* register our interrupt handler */
|
|
if (request_irq(pdev->irq, brcms_isr,
|
|
IRQF_SHARED, KBUILD_MODNAME, wl)) {
|
|
wiphy_err(wl->wiphy, "wl%d: request_irq() failed\n", unit);
|
|
goto fail;
|
|
}
|
|
wl->irq = pdev->irq;
|
|
|
|
/* register module */
|
|
brcms_c_module_register(wl->pub, "linux", wl, NULL);
|
|
|
|
if (ieee_hw_init(hw)) {
|
|
wiphy_err(wl->wiphy, "wl%d: %s: ieee_hw_init failed!\n", unit,
|
|
__func__);
|
|
goto fail;
|
|
}
|
|
|
|
brcms_c_regd_init(wl->wlc);
|
|
|
|
memcpy(perm, &wl->pub->cur_etheraddr, ETH_ALEN);
|
|
if (WARN_ON(!is_valid_ether_addr(perm)))
|
|
goto fail;
|
|
SET_IEEE80211_PERM_ADDR(hw, perm);
|
|
|
|
err = ieee80211_register_hw(hw);
|
|
if (err)
|
|
wiphy_err(wl->wiphy, "%s: ieee80211_register_hw failed, status"
|
|
"%d\n", __func__, err);
|
|
|
|
if (wl->pub->srom_ccode[0] &&
|
|
regulatory_hint(wl->wiphy, wl->pub->srom_ccode))
|
|
wiphy_err(wl->wiphy, "%s: regulatory hint failed\n", __func__);
|
|
|
|
brcms_debugfs_attach(wl->pub);
|
|
brcms_debugfs_create_files(wl->pub);
|
|
n_adapters_found++;
|
|
return wl;
|
|
|
|
fail:
|
|
brcms_free(wl);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* determines if a device is a WL device, and if so, attaches it.
|
|
*
|
|
* This function determines if a device pointed to by pdev is a WL device,
|
|
* and if so, performs a brcms_attach() on it.
|
|
*
|
|
* Perimeter lock is initialized in the course of this function.
|
|
*/
|
|
static int brcms_bcma_probe(struct bcma_device *pdev)
|
|
{
|
|
struct brcms_info *wl;
|
|
struct ieee80211_hw *hw;
|
|
|
|
dev_info(&pdev->dev, "mfg %x core %x rev %d class %d irq %d\n",
|
|
pdev->id.manuf, pdev->id.id, pdev->id.rev, pdev->id.class,
|
|
pdev->irq);
|
|
|
|
if ((pdev->id.manuf != BCMA_MANUF_BCM) ||
|
|
(pdev->id.id != BCMA_CORE_80211))
|
|
return -ENODEV;
|
|
|
|
hw = ieee80211_alloc_hw(sizeof(struct brcms_info), &brcms_ops);
|
|
if (!hw) {
|
|
pr_err("%s: ieee80211_alloc_hw failed\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
SET_IEEE80211_DEV(hw, &pdev->dev);
|
|
|
|
bcma_set_drvdata(pdev, hw);
|
|
|
|
memset(hw->priv, 0, sizeof(*wl));
|
|
|
|
wl = brcms_attach(pdev);
|
|
if (!wl) {
|
|
pr_err("%s: brcms_attach failed!\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
brcms_led_register(wl);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int brcms_suspend(struct bcma_device *pdev)
|
|
{
|
|
struct brcms_info *wl;
|
|
struct ieee80211_hw *hw;
|
|
|
|
hw = bcma_get_drvdata(pdev);
|
|
wl = hw->priv;
|
|
if (!wl) {
|
|
pr_err("%s: %s: no driver private struct!\n", KBUILD_MODNAME,
|
|
__func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* only need to flag hw is down for proper resume */
|
|
spin_lock_bh(&wl->lock);
|
|
wl->pub->hw_up = false;
|
|
spin_unlock_bh(&wl->lock);
|
|
|
|
brcms_dbg_info(wl->wlc->hw->d11core, "brcms_suspend ok\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int brcms_resume(struct bcma_device *pdev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static struct bcma_driver brcms_bcma_driver = {
|
|
.name = KBUILD_MODNAME,
|
|
.probe = brcms_bcma_probe,
|
|
.suspend = brcms_suspend,
|
|
.resume = brcms_resume,
|
|
.remove = brcms_remove,
|
|
.id_table = brcms_coreid_table,
|
|
};
|
|
|
|
/**
|
|
* This is the main entry point for the brcmsmac driver.
|
|
*
|
|
* This function is scheduled upon module initialization and
|
|
* does the driver registration, which result in brcms_bcma_probe()
|
|
* call resulting in the driver bringup.
|
|
*/
|
|
static void brcms_driver_init(struct work_struct *work)
|
|
{
|
|
int error;
|
|
|
|
error = bcma_driver_register(&brcms_bcma_driver);
|
|
if (error)
|
|
pr_err("%s: register returned %d\n", __func__, error);
|
|
}
|
|
|
|
static DECLARE_WORK(brcms_driver_work, brcms_driver_init);
|
|
|
|
static int __init brcms_module_init(void)
|
|
{
|
|
brcms_debugfs_init();
|
|
if (!schedule_work(&brcms_driver_work))
|
|
return -EBUSY;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* This function unloads the brcmsmac driver from the system.
|
|
*
|
|
* This function unconditionally unloads the brcmsmac driver module from the
|
|
* system.
|
|
*
|
|
*/
|
|
static void __exit brcms_module_exit(void)
|
|
{
|
|
cancel_work_sync(&brcms_driver_work);
|
|
bcma_driver_unregister(&brcms_bcma_driver);
|
|
brcms_debugfs_exit();
|
|
}
|
|
|
|
module_init(brcms_module_init);
|
|
module_exit(brcms_module_exit);
|
|
|
|
/*
|
|
* precondition: perimeter lock has been acquired
|
|
*/
|
|
void brcms_txflowcontrol(struct brcms_info *wl, struct brcms_if *wlif,
|
|
bool state, int prio)
|
|
{
|
|
brcms_err(wl->wlc->hw->d11core, "Shouldn't be here %s\n", __func__);
|
|
}
|
|
|
|
/*
|
|
* precondition: perimeter lock has been acquired
|
|
*/
|
|
void brcms_init(struct brcms_info *wl)
|
|
{
|
|
brcms_dbg_info(wl->wlc->hw->d11core, "Initializing wl%d\n",
|
|
wl->pub->unit);
|
|
brcms_reset(wl);
|
|
brcms_c_init(wl->wlc, wl->mute_tx);
|
|
}
|
|
|
|
/*
|
|
* precondition: perimeter lock has been acquired
|
|
*/
|
|
uint brcms_reset(struct brcms_info *wl)
|
|
{
|
|
brcms_dbg_info(wl->wlc->hw->d11core, "Resetting wl%d\n", wl->pub->unit);
|
|
brcms_c_reset(wl->wlc);
|
|
|
|
/* dpc will not be rescheduled */
|
|
wl->resched = false;
|
|
|
|
/* inform publicly that interface is down */
|
|
wl->pub->up = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void brcms_fatal_error(struct brcms_info *wl)
|
|
{
|
|
brcms_err(wl->wlc->hw->d11core, "wl%d: fatal error, reinitializing\n",
|
|
wl->wlc->pub->unit);
|
|
brcms_reset(wl);
|
|
ieee80211_restart_hw(wl->pub->ieee_hw);
|
|
}
|
|
|
|
/*
|
|
* These are interrupt on/off entry points. Disable interrupts
|
|
* during interrupt state transition.
|
|
*/
|
|
void brcms_intrson(struct brcms_info *wl)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&wl->isr_lock, flags);
|
|
brcms_c_intrson(wl->wlc);
|
|
spin_unlock_irqrestore(&wl->isr_lock, flags);
|
|
}
|
|
|
|
u32 brcms_intrsoff(struct brcms_info *wl)
|
|
{
|
|
unsigned long flags;
|
|
u32 status;
|
|
|
|
spin_lock_irqsave(&wl->isr_lock, flags);
|
|
status = brcms_c_intrsoff(wl->wlc);
|
|
spin_unlock_irqrestore(&wl->isr_lock, flags);
|
|
return status;
|
|
}
|
|
|
|
void brcms_intrsrestore(struct brcms_info *wl, u32 macintmask)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&wl->isr_lock, flags);
|
|
brcms_c_intrsrestore(wl->wlc, macintmask);
|
|
spin_unlock_irqrestore(&wl->isr_lock, flags);
|
|
}
|
|
|
|
/*
|
|
* precondition: perimeter lock has been acquired
|
|
*/
|
|
int brcms_up(struct brcms_info *wl)
|
|
{
|
|
int error = 0;
|
|
|
|
if (wl->pub->up)
|
|
return 0;
|
|
|
|
error = brcms_c_up(wl->wlc);
|
|
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* precondition: perimeter lock has been acquired
|
|
*/
|
|
void brcms_down(struct brcms_info *wl)
|
|
{
|
|
uint callbacks, ret_val = 0;
|
|
|
|
/* call common down function */
|
|
ret_val = brcms_c_down(wl->wlc);
|
|
callbacks = atomic_read(&wl->callbacks) - ret_val;
|
|
|
|
/* wait for down callbacks to complete */
|
|
spin_unlock_bh(&wl->lock);
|
|
|
|
/* For HIGH_only driver, it's important to actually schedule other work,
|
|
* not just spin wait since everything runs at schedule level
|
|
*/
|
|
SPINWAIT((atomic_read(&wl->callbacks) > callbacks), 100 * 1000);
|
|
|
|
spin_lock_bh(&wl->lock);
|
|
}
|
|
|
|
/*
|
|
* precondition: perimeter lock is not acquired
|
|
*/
|
|
static void _brcms_timer(struct work_struct *work)
|
|
{
|
|
struct brcms_timer *t = container_of(work, struct brcms_timer,
|
|
dly_wrk.work);
|
|
|
|
spin_lock_bh(&t->wl->lock);
|
|
|
|
if (t->set) {
|
|
if (t->periodic) {
|
|
atomic_inc(&t->wl->callbacks);
|
|
ieee80211_queue_delayed_work(t->wl->pub->ieee_hw,
|
|
&t->dly_wrk,
|
|
msecs_to_jiffies(t->ms));
|
|
} else {
|
|
t->set = false;
|
|
}
|
|
|
|
t->fn(t->arg);
|
|
}
|
|
|
|
atomic_dec(&t->wl->callbacks);
|
|
|
|
spin_unlock_bh(&t->wl->lock);
|
|
}
|
|
|
|
/*
|
|
* Adds a timer to the list. Caller supplies a timer function.
|
|
* Is called from wlc.
|
|
*
|
|
* precondition: perimeter lock has been acquired
|
|
*/
|
|
struct brcms_timer *brcms_init_timer(struct brcms_info *wl,
|
|
void (*fn) (void *arg),
|
|
void *arg, const char *name)
|
|
{
|
|
struct brcms_timer *t;
|
|
|
|
t = kzalloc(sizeof(struct brcms_timer), GFP_ATOMIC);
|
|
if (!t)
|
|
return NULL;
|
|
|
|
INIT_DELAYED_WORK(&t->dly_wrk, _brcms_timer);
|
|
t->wl = wl;
|
|
t->fn = fn;
|
|
t->arg = arg;
|
|
t->next = wl->timers;
|
|
wl->timers = t;
|
|
|
|
#ifdef DEBUG
|
|
t->name = kmalloc(strlen(name) + 1, GFP_ATOMIC);
|
|
if (t->name)
|
|
strcpy(t->name, name);
|
|
#endif
|
|
|
|
return t;
|
|
}
|
|
|
|
/*
|
|
* adds only the kernel timer since it's going to be more accurate
|
|
* as well as it's easier to make it periodic
|
|
*
|
|
* precondition: perimeter lock has been acquired
|
|
*/
|
|
void brcms_add_timer(struct brcms_timer *t, uint ms, int periodic)
|
|
{
|
|
struct ieee80211_hw *hw = t->wl->pub->ieee_hw;
|
|
|
|
#ifdef DEBUG
|
|
if (t->set)
|
|
brcms_dbg_info(t->wl->wlc->hw->d11core,
|
|
"%s: Already set. Name: %s, per %d\n",
|
|
__func__, t->name, periodic);
|
|
#endif
|
|
t->ms = ms;
|
|
t->periodic = (bool) periodic;
|
|
if (!t->set) {
|
|
t->set = true;
|
|
atomic_inc(&t->wl->callbacks);
|
|
}
|
|
|
|
ieee80211_queue_delayed_work(hw, &t->dly_wrk, msecs_to_jiffies(ms));
|
|
}
|
|
|
|
/*
|
|
* return true if timer successfully deleted, false if still pending
|
|
*
|
|
* precondition: perimeter lock has been acquired
|
|
*/
|
|
bool brcms_del_timer(struct brcms_timer *t)
|
|
{
|
|
if (t->set) {
|
|
t->set = false;
|
|
if (!cancel_delayed_work(&t->dly_wrk))
|
|
return false;
|
|
|
|
atomic_dec(&t->wl->callbacks);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* precondition: perimeter lock has been acquired
|
|
*/
|
|
void brcms_free_timer(struct brcms_timer *t)
|
|
{
|
|
struct brcms_info *wl = t->wl;
|
|
struct brcms_timer *tmp;
|
|
|
|
/* delete the timer in case it is active */
|
|
brcms_del_timer(t);
|
|
|
|
if (wl->timers == t) {
|
|
wl->timers = wl->timers->next;
|
|
#ifdef DEBUG
|
|
kfree(t->name);
|
|
#endif
|
|
kfree(t);
|
|
return;
|
|
|
|
}
|
|
|
|
tmp = wl->timers;
|
|
while (tmp) {
|
|
if (tmp->next == t) {
|
|
tmp->next = t->next;
|
|
#ifdef DEBUG
|
|
kfree(t->name);
|
|
#endif
|
|
kfree(t);
|
|
return;
|
|
}
|
|
tmp = tmp->next;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* precondition: perimeter lock has been acquired
|
|
*/
|
|
int brcms_ucode_init_buf(struct brcms_info *wl, void **pbuf, u32 idx)
|
|
{
|
|
int i, entry;
|
|
const u8 *pdata;
|
|
struct firmware_hdr *hdr;
|
|
for (i = 0; i < wl->fw.fw_cnt; i++) {
|
|
hdr = (struct firmware_hdr *)wl->fw.fw_hdr[i]->data;
|
|
for (entry = 0; entry < wl->fw.hdr_num_entries[i];
|
|
entry++, hdr++) {
|
|
u32 len = le32_to_cpu(hdr->len);
|
|
if (le32_to_cpu(hdr->idx) == idx) {
|
|
pdata = wl->fw.fw_bin[i]->data +
|
|
le32_to_cpu(hdr->offset);
|
|
*pbuf = kmemdup(pdata, len, GFP_ATOMIC);
|
|
if (*pbuf == NULL)
|
|
goto fail;
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
brcms_err(wl->wlc->hw->d11core,
|
|
"ERROR: ucode buf tag:%d can not be found!\n", idx);
|
|
*pbuf = NULL;
|
|
fail:
|
|
return -ENODATA;
|
|
}
|
|
|
|
/*
|
|
* Precondition: Since this function is called in brcms_bcma_probe() context,
|
|
* no locking is required.
|
|
*/
|
|
int brcms_ucode_init_uint(struct brcms_info *wl, size_t *n_bytes, u32 idx)
|
|
{
|
|
int i, entry;
|
|
const u8 *pdata;
|
|
struct firmware_hdr *hdr;
|
|
for (i = 0; i < wl->fw.fw_cnt; i++) {
|
|
hdr = (struct firmware_hdr *)wl->fw.fw_hdr[i]->data;
|
|
for (entry = 0; entry < wl->fw.hdr_num_entries[i];
|
|
entry++, hdr++) {
|
|
if (le32_to_cpu(hdr->idx) == idx) {
|
|
pdata = wl->fw.fw_bin[i]->data +
|
|
le32_to_cpu(hdr->offset);
|
|
if (le32_to_cpu(hdr->len) != 4) {
|
|
brcms_err(wl->wlc->hw->d11core,
|
|
"ERROR: fw hdr len\n");
|
|
return -ENOMSG;
|
|
}
|
|
*n_bytes = le32_to_cpu(*((__le32 *) pdata));
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
brcms_err(wl->wlc->hw->d11core,
|
|
"ERROR: ucode tag:%d can not be found!\n", idx);
|
|
return -ENOMSG;
|
|
}
|
|
|
|
/*
|
|
* precondition: can both be called locked and unlocked
|
|
*/
|
|
void brcms_ucode_free_buf(void *p)
|
|
{
|
|
kfree(p);
|
|
}
|
|
|
|
/*
|
|
* checks validity of all firmware images loaded from user space
|
|
*
|
|
* Precondition: Since this function is called in brcms_bcma_probe() context,
|
|
* no locking is required.
|
|
*/
|
|
int brcms_check_firmwares(struct brcms_info *wl)
|
|
{
|
|
int i;
|
|
int entry;
|
|
int rc = 0;
|
|
const struct firmware *fw;
|
|
const struct firmware *fw_hdr;
|
|
struct firmware_hdr *ucode_hdr;
|
|
for (i = 0; i < MAX_FW_IMAGES && rc == 0; i++) {
|
|
fw = wl->fw.fw_bin[i];
|
|
fw_hdr = wl->fw.fw_hdr[i];
|
|
if (fw == NULL && fw_hdr == NULL) {
|
|
break;
|
|
} else if (fw == NULL || fw_hdr == NULL) {
|
|
wiphy_err(wl->wiphy, "%s: invalid bin/hdr fw\n",
|
|
__func__);
|
|
rc = -EBADF;
|
|
} else if (fw_hdr->size % sizeof(struct firmware_hdr)) {
|
|
wiphy_err(wl->wiphy, "%s: non integral fw hdr file "
|
|
"size %zu/%zu\n", __func__, fw_hdr->size,
|
|
sizeof(struct firmware_hdr));
|
|
rc = -EBADF;
|
|
} else if (fw->size < MIN_FW_SIZE || fw->size > MAX_FW_SIZE) {
|
|
wiphy_err(wl->wiphy, "%s: out of bounds fw file size %zu\n",
|
|
__func__, fw->size);
|
|
rc = -EBADF;
|
|
} else {
|
|
/* check if ucode section overruns firmware image */
|
|
ucode_hdr = (struct firmware_hdr *)fw_hdr->data;
|
|
for (entry = 0; entry < wl->fw.hdr_num_entries[i] &&
|
|
!rc; entry++, ucode_hdr++) {
|
|
if (le32_to_cpu(ucode_hdr->offset) +
|
|
le32_to_cpu(ucode_hdr->len) >
|
|
fw->size) {
|
|
wiphy_err(wl->wiphy,
|
|
"%s: conflicting bin/hdr\n",
|
|
__func__);
|
|
rc = -EBADF;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (rc == 0 && wl->fw.fw_cnt != i) {
|
|
wiphy_err(wl->wiphy, "%s: invalid fw_cnt=%d\n", __func__,
|
|
wl->fw.fw_cnt);
|
|
rc = -EBADF;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* precondition: perimeter lock has been acquired
|
|
*/
|
|
bool brcms_rfkill_set_hw_state(struct brcms_info *wl)
|
|
{
|
|
bool blocked = brcms_c_check_radio_disabled(wl->wlc);
|
|
|
|
spin_unlock_bh(&wl->lock);
|
|
wiphy_rfkill_set_hw_state(wl->pub->ieee_hw->wiphy, blocked);
|
|
if (blocked)
|
|
wiphy_rfkill_start_polling(wl->pub->ieee_hw->wiphy);
|
|
spin_lock_bh(&wl->lock);
|
|
return blocked;
|
|
}
|