* drivers/net/wireless/mwl8k.c
* Driver for Marvell TOPDOG 802.11 Wireless cards
*
- * Copyright (C) 2008 Marvell Semiconductor Inc.
+ * Copyright (C) 2008-2009 Marvell Semiconductor Inc.
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
+#include <linux/sched.h>
#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/pci.h>
#define MWL8K_DESC "Marvell TOPDOG(R) 802.11 Wireless Network Driver"
#define MWL8K_NAME KBUILD_MODNAME
-#define MWL8K_VERSION "0.9.1"
-
-MODULE_DESCRIPTION(MWL8K_DESC);
-MODULE_VERSION(MWL8K_VERSION);
-MODULE_AUTHOR("Lennert Buytenhek <buytenh@marvell.com>");
-MODULE_LICENSE("GPL");
+#define MWL8K_VERSION "0.10"
static DEFINE_PCI_DEVICE_TABLE(mwl8k_table) = {
{ PCI_VDEVICE(MARVELL, 0x2a2b), .driver_data = 8687, },
MWL8K_A2H_INT_RX_READY | \
MWL8K_A2H_INT_TX_DONE)
-/* WME stream classes */
-#define WME_AC_BE 0 /* best effort */
-#define WME_AC_BK 1 /* background */
-#define WME_AC_VI 2 /* video */
-#define WME_AC_VO 3 /* voice */
-
#define MWL8K_RX_QUEUES 1
#define MWL8K_TX_QUEUES 4
struct ieee80211_hw *hw;
struct pci_dev *pdev;
- u8 name[16];
/* firmware files and meta data */
struct mwl8k_firmware fw;
struct mutex fw_mutex;
struct task_struct *fw_mutex_owner;
int fw_mutex_depth;
- struct completion *tx_wait;
struct completion *hostcmd_wait;
/* lock held over TX and TX reap */
spinlock_t tx_lock;
+ /* TX quiesce completion, protected by fw_mutex and tx_lock */
+ struct completion *tx_wait;
+
struct ieee80211_vif *vif;
struct ieee80211_channel *current_channel;
u16 num_mcaddrs;
u8 hw_rev;
- __le32 fw_rev;
+ u32 fw_rev;
/*
* Running count of TX packets in flight, to avoid
/* PHY parameters */
struct ieee80211_supported_band band;
struct ieee80211_channel channels[14];
- struct ieee80211_rate rates[12];
+ struct ieee80211_rate rates[13];
bool radio_on;
bool radio_short_preamble;
+ bool sniffer_enabled;
bool wmm_enabled;
/* XXX need to convert this to handle multiple interfaces */
/* Tasklet to reclaim TX descriptors and buffers after tx */
struct tasklet_struct tx_reclaim_task;
-
- /* Work thread to serialize configuration requests */
- struct workqueue_struct *config_wq;
};
/* Per interface specific private data */
* Subset of supported legacy rates.
* Intersection of AP and STA supported rates.
*/
- struct ieee80211_rate legacy_rates[12];
+ struct ieee80211_rate legacy_rates[13];
/* number of supported legacy rates */
u8 legacy_nrates;
{ .bitrate = 10, .hw_value = 2, },
{ .bitrate = 20, .hw_value = 4, },
{ .bitrate = 55, .hw_value = 11, },
+ { .bitrate = 110, .hw_value = 22, },
+ { .bitrate = 220, .hw_value = 44, },
{ .bitrate = 60, .hw_value = 12, },
{ .bitrate = 90, .hw_value = 18, },
- { .bitrate = 110, .hw_value = 22, },
{ .bitrate = 120, .hw_value = 24, },
{ .bitrate = 180, .hw_value = 36, },
{ .bitrate = 240, .hw_value = 48, },
#define MWL8K_CMD_MIMO_CONFIG 0x0125
#define MWL8K_CMD_USE_FIXED_RATE 0x0126
#define MWL8K_CMD_ENABLE_SNIFFER 0x0150
+#define MWL8K_CMD_SET_MAC_ADDR 0x0202
#define MWL8K_CMD_SET_RATEADAPT_MODE 0x0203
#define MWL8K_CMD_UPDATE_STADB 0x1123
MWL8K_CMDNAME(MIMO_CONFIG);
MWL8K_CMDNAME(USE_FIXED_RATE);
MWL8K_CMDNAME(ENABLE_SNIFFER);
+ MWL8K_CMDNAME(SET_MAC_ADDR);
MWL8K_CMDNAME(SET_RATEADAPT_MODE);
MWL8K_CMDNAME(UPDATE_STADB);
default:
/* Request fw image */
static int mwl8k_request_fw(struct mwl8k_priv *priv,
- const char *fname, struct firmware **fw)
+ const char *fname, struct firmware **fw)
{
/* release current image */
if (*fw != NULL)
mwl8k_release_fw(fw);
return request_firmware((const struct firmware **)fw,
- fname, &priv->pdev->dev);
+ fname, &priv->pdev->dev);
}
static int mwl8k_request_firmware(struct mwl8k_priv *priv, u32 part_num)
rc = mwl8k_request_fw(priv, filename, &priv->fw.helper);
if (rc) {
- printk(KERN_ERR
- "%s Error requesting helper firmware file %s\n",
- pci_name(priv->pdev), filename);
+ printk(KERN_ERR "%s: Error requesting helper firmware "
+ "file %s\n", pci_name(priv->pdev), filename);
return rc;
}
rc = mwl8k_request_fw(priv, filename, &priv->fw.ucode);
if (rc) {
- printk(KERN_ERR "%s Error requesting firmware file %s\n",
- pci_name(priv->pdev), filename);
+ printk(KERN_ERR "%s: Error requesting firmware file %s\n",
+ pci_name(priv->pdev), filename);
mwl8k_release_fw(&priv->fw.helper);
return rc;
}
break;
}
+ cond_resched();
udelay(1);
} while (--loops);
return rc;
}
-static int mwl8k_load_firmware(struct mwl8k_priv *priv)
+static int mwl8k_load_firmware(struct ieee80211_hw *hw)
{
- int loops, rc;
+ struct mwl8k_priv *priv = hw->priv;
+ struct firmware *fw = priv->fw.ucode;
+ int rc;
+ int loops;
- const u8 *ucode = priv->fw.ucode->data;
- size_t ucode_len = priv->fw.ucode->size;
- const u8 *helper = priv->fw.helper->data;
- size_t helper_len = priv->fw.helper->size;
+ if (!memcmp(fw->data, "\x01\x00\x00\x00", 4)) {
+ struct firmware *helper = priv->fw.helper;
- if (!memcmp(ucode, "\x01\x00\x00\x00", 4)) {
- rc = mwl8k_load_fw_image(priv, helper, helper_len);
+ if (helper == NULL) {
+ printk(KERN_ERR "%s: helper image needed but none "
+ "given\n", pci_name(priv->pdev));
+ return -EINVAL;
+ }
+
+ rc = mwl8k_load_fw_image(priv, helper->data, helper->size);
if (rc) {
printk(KERN_ERR "%s: unable to load firmware "
- "helper image\n", pci_name(priv->pdev));
+ "helper image\n", pci_name(priv->pdev));
return rc;
}
msleep(1);
- rc = mwl8k_feed_fw_image(priv, ucode, ucode_len);
+ rc = mwl8k_feed_fw_image(priv, fw->data, fw->size);
} else {
- rc = mwl8k_load_fw_image(priv, ucode, ucode_len);
+ rc = mwl8k_load_fw_image(priv, fw->data, fw->size);
}
if (rc) {
- printk(KERN_ERR "%s: unable to load firmware data\n",
- pci_name(priv->pdev));
+ printk(KERN_ERR "%s: unable to load firmware image\n",
+ pci_name(priv->pdev));
return rc;
}
/* Peer Entry flags - used to define the type of the peer node */
#define MWL8K_PEER_TYPE_ACCESSPOINT 2
-#define MWL8K_IEEE_LEGACY_DATA_RATES 12
+#define MWL8K_IEEE_LEGACY_DATA_RATES 13
#define MWL8K_MCS_BITMAP_SIZE 16
struct peer_capability_info {
#define MWL8K_RX_DESCS 256
#define MWL8K_RX_MAXSZ 3800
+#define RATE_INFO_SHORTPRE 0x8000
+#define RATE_INFO_ANTSELECT(x) (((x) >> 11) & 0x3)
+#define RATE_INFO_RATEID(x) (((x) >> 3) & 0x3f)
+#define RATE_INFO_40MHZ 0x0004
+#define RATE_INFO_SHORTGI 0x0002
+#define RATE_INFO_MCS_FORMAT 0x0001
+
static int mwl8k_rxq_init(struct ieee80211_hw *hw, int index)
{
struct mwl8k_priv *priv = hw->priv;
pci_alloc_consistent(priv->pdev, size, &rxq->rx_desc_dma);
if (rxq->rx_desc_area == NULL) {
printk(KERN_ERR "%s: failed to alloc RX descriptors\n",
- priv->name);
+ wiphy_name(hw->wiphy));
return -ENOMEM;
}
memset(rxq->rx_desc_area, 0, size);
sizeof(*rxq->rx_skb), GFP_KERNEL);
if (rxq->rx_skb == NULL) {
printk(KERN_ERR "%s: failed to alloc RX skbuff list\n",
- priv->name);
+ wiphy_name(hw->wiphy));
pci_free_consistent(priv->pdev, size,
rxq->rx_desc_area, rxq->rx_desc_dma);
return -ENOMEM;
!compare_ether_addr(wh->addr3, priv->capture_bssid);
}
-static inline void mwl8k_save_beacon(struct mwl8k_priv *priv,
- struct sk_buff *skb)
+static inline void mwl8k_save_beacon(struct ieee80211_hw *hw,
+ struct sk_buff *skb)
{
+ struct mwl8k_priv *priv = hw->priv;
+
priv->capture_beacon = false;
memset(priv->capture_bssid, 0, ETH_ALEN);
*/
priv->beacon_skb = skb_copy(skb, GFP_ATOMIC);
if (priv->beacon_skb != NULL)
- queue_work(priv->config_wq,
- &priv->finalize_join_worker);
+ ieee80211_queue_work(hw, &priv->finalize_join_worker);
}
static int rxq_process(struct ieee80211_hw *hw, int index, int limit)
struct ieee80211_rx_status status;
unsigned long addr;
struct ieee80211_hdr *wh;
+ u16 rate_info;
rx_desc = rxq->rx_desc_area + rxq->rx_head;
if (!(rx_desc->rx_ctrl & MWL8K_RX_CTRL_OWNED_BY_HOST))
wh = (struct ieee80211_hdr *)skb->data;
/*
- * Check for pending join operation. save a copy of
- * the beacon and schedule a tasklet to send finalize
- * join command to the firmware.
+ * Check for a pending join operation. Save a
+ * copy of the beacon and schedule a tasklet to
+ * send a FINALIZE_JOIN command to the firmware.
*/
if (mwl8k_capture_bssid(priv, wh))
- mwl8k_save_beacon(priv, skb);
+ mwl8k_save_beacon(hw, skb);
+
+ rate_info = le16_to_cpu(rx_desc->rate_info);
memset(&status, 0, sizeof(status));
status.mactime = 0;
status.signal = -rx_desc->rssi;
status.noise = -rx_desc->noise_level;
status.qual = rx_desc->link_quality;
- status.antenna = 1;
- status.rate_idx = 1;
+ status.antenna = RATE_INFO_ANTSELECT(rate_info);
+ status.rate_idx = RATE_INFO_RATEID(rate_info);
status.flag = 0;
+ if (rate_info & RATE_INFO_SHORTPRE)
+ status.flag |= RX_FLAG_SHORTPRE;
+ if (rate_info & RATE_INFO_40MHZ)
+ status.flag |= RX_FLAG_40MHZ;
+ if (rate_info & RATE_INFO_SHORTGI)
+ status.flag |= RX_FLAG_SHORT_GI;
+ if (rate_info & RATE_INFO_MCS_FORMAT)
+ status.flag |= RX_FLAG_HT;
status.band = IEEE80211_BAND_2GHZ;
status.freq = ieee80211_channel_to_frequency(rx_desc->channel);
memcpy(IEEE80211_SKB_RXCB(skb), &status, sizeof(status));
* Packet transmission.
*/
-/* Transmit queue assignment. */
-enum {
- MWL8K_WME_AC_BK = 0, /* background access */
- MWL8K_WME_AC_BE = 1, /* best effort access */
- MWL8K_WME_AC_VI = 2, /* video access */
- MWL8K_WME_AC_VO = 3, /* voice access */
-};
-
/* Transmit packet ACK policy */
#define MWL8K_TXD_ACK_POLICY_NORMAL 0
#define MWL8K_TXD_ACK_POLICY_BLOCKACK 3
-#define GET_TXQ(_ac) (\
- ((_ac) == WME_AC_VO) ? MWL8K_WME_AC_VO : \
- ((_ac) == WME_AC_VI) ? MWL8K_WME_AC_VI : \
- ((_ac) == WME_AC_BK) ? MWL8K_WME_AC_BK : \
- MWL8K_WME_AC_BE)
-
#define MWL8K_TXD_STATUS_OK 0x00000001
#define MWL8K_TXD_STATUS_OK_RETRY 0x00000002
#define MWL8K_TXD_STATUS_OK_MORE_RETRY 0x00000004
pci_alloc_consistent(priv->pdev, size, &txq->tx_desc_dma);
if (txq->tx_desc_area == NULL) {
printk(KERN_ERR "%s: failed to alloc TX descriptors\n",
- priv->name);
+ wiphy_name(hw->wiphy));
return -ENOMEM;
}
memset(txq->tx_desc_area, 0, size);
GFP_KERNEL);
if (txq->tx_skb == NULL) {
printk(KERN_ERR "%s: failed to alloc TX skbuff list\n",
- priv->name);
+ wiphy_name(hw->wiphy));
pci_free_consistent(priv->pdev, size,
txq->tx_desc_area, txq->tx_desc_dma);
return -ENOMEM;
ioread32(priv->regs + MWL8K_HIU_INT_CODE);
}
-static inline int mwl8k_txq_busy(struct mwl8k_priv *priv)
-{
- return priv->pending_tx_pkts;
-}
-
struct mwl8k_txq_info {
u32 fw_owned;
u32 drv_owned;
memset(txinfo, 0, MWL8K_TX_QUEUES * sizeof(struct mwl8k_txq_info));
- spin_lock_bh(&priv->tx_lock);
for (count = 0; count < MWL8K_TX_QUEUES; count++) {
txq = priv->txq + count;
txinfo[count].len = txq->tx_stats.len;
txinfo[count].unused++;
}
}
- spin_unlock_bh(&priv->tx_lock);
return ndescs;
}
/*
- * Must be called with hw->fw_mutex held and tx queues stopped.
+ * Must be called with priv->fw_mutex held and tx queues stopped.
*/
static int mwl8k_tx_wait_empty(struct ieee80211_hw *hw)
{
struct mwl8k_priv *priv = hw->priv;
- DECLARE_COMPLETION_ONSTACK(cmd_wait);
+ DECLARE_COMPLETION_ONSTACK(tx_wait);
u32 count;
unsigned long timeout;
might_sleep();
spin_lock_bh(&priv->tx_lock);
- count = mwl8k_txq_busy(priv);
- if (count) {
- priv->tx_wait = &cmd_wait;
- if (priv->radio_on)
- mwl8k_tx_start(priv);
- }
+ count = priv->pending_tx_pkts;
+ if (count)
+ priv->tx_wait = &tx_wait;
spin_unlock_bh(&priv->tx_lock);
if (count) {
int index;
int newcount;
- timeout = wait_for_completion_timeout(&cmd_wait,
+ timeout = wait_for_completion_timeout(&tx_wait,
msecs_to_jiffies(5000));
if (timeout)
return 0;
spin_lock_bh(&priv->tx_lock);
priv->tx_wait = NULL;
- newcount = mwl8k_txq_busy(priv);
+ newcount = priv->pending_tx_pkts;
+ mwl8k_scan_tx_ring(priv, txinfo);
spin_unlock_bh(&priv->tx_lock);
printk(KERN_ERR "%s(%u) TIMEDOUT:5000ms Pend:%u-->%u\n",
__func__, __LINE__, count, newcount);
- mwl8k_scan_tx_ring(priv, txinfo);
for (index = 0; index < MWL8K_TX_QUEUES; index++)
- printk(KERN_ERR
- "TXQ:%u L:%u H:%u T:%u FW:%u DRV:%u U:%u\n",
+ printk(KERN_ERR "TXQ:%u L:%u H:%u T:%u FW:%u "
+ "DRV:%u U:%u\n",
index,
txinfo[index].len,
txinfo[index].head,
if (pci_dma_mapping_error(priv->pdev, dma)) {
printk(KERN_DEBUG "%s: failed to dma map skb, "
- "dropping TX frame.\n", priv->name);
+ "dropping TX frame.\n", wiphy_name(hw->wiphy));
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
unsigned long timeout = 0;
u8 buf[32];
- cmd->result = 0xFFFF;
+ cmd->result = 0xffff;
dma_size = le16_to_cpu(cmd->length);
dma_addr = pci_map_single(priv->pdev, cmd, dma_size,
PCI_DMA_BIDIRECTIONAL);
return -ENOMEM;
rc = mwl8k_fw_lock(hw);
- if (rc)
+ if (rc) {
+ pci_unmap_single(priv->pdev, dma_addr, dma_size,
+ PCI_DMA_BIDIRECTIONAL);
return rc;
+ }
priv->hostcmd_wait = &cmd_wait;
iowrite32(dma_addr, regs + MWL8K_HIU_GEN_PTR);
if (!timeout) {
printk(KERN_ERR "%s: Command %s timeout after %u ms\n",
- priv->name,
+ wiphy_name(hw->wiphy),
mwl8k_cmd_name(cmd->code, buf, sizeof(buf)),
MWL8K_CMD_TIMEOUT_MS);
rc = -ETIMEDOUT;
rc = cmd->result ? -EINVAL : 0;
if (rc)
printk(KERN_ERR "%s: Command %s error 0x%x\n",
- priv->name,
+ wiphy_name(hw->wiphy),
mwl8k_cmd_name(cmd->code, buf, sizeof(buf)),
- cmd->result);
+ le16_to_cpu(cmd->result));
}
return rc;
__u8 addr[0][ETH_ALEN];
};
-#define MWL8K_ENABLE_RX_MULTICAST 0x000F
+#define MWL8K_ENABLE_RX_DIRECTED 0x0001
+#define MWL8K_ENABLE_RX_MULTICAST 0x0002
+#define MWL8K_ENABLE_RX_ALL_MULTICAST 0x0004
+#define MWL8K_ENABLE_RX_BROADCAST 0x0008
static struct mwl8k_cmd_pkt *
-__mwl8k_cmd_mac_multicast_adr(struct ieee80211_hw *hw,
+__mwl8k_cmd_mac_multicast_adr(struct ieee80211_hw *hw, int allmulti,
int mc_count, struct dev_addr_list *mclist)
{
struct mwl8k_priv *priv = hw->priv;
struct mwl8k_cmd_mac_multicast_adr *cmd;
int size;
- int i;
- if (mc_count > priv->num_mcaddrs)
- mc_count = priv->num_mcaddrs;
+ if (allmulti || mc_count > priv->num_mcaddrs) {
+ allmulti = 1;
+ mc_count = 0;
+ }
size = sizeof(*cmd) + mc_count * ETH_ALEN;
cmd->header.code = cpu_to_le16(MWL8K_CMD_MAC_MULTICAST_ADR);
cmd->header.length = cpu_to_le16(size);
- cmd->action = cpu_to_le16(MWL8K_ENABLE_RX_MULTICAST);
- cmd->numaddr = cpu_to_le16(mc_count);
-
- for (i = 0; i < mc_count && mclist; i++) {
- if (mclist->da_addrlen != ETH_ALEN) {
- kfree(cmd);
- return NULL;
+ cmd->action = cpu_to_le16(MWL8K_ENABLE_RX_DIRECTED |
+ MWL8K_ENABLE_RX_BROADCAST);
+
+ if (allmulti) {
+ cmd->action |= cpu_to_le16(MWL8K_ENABLE_RX_ALL_MULTICAST);
+ } else if (mc_count) {
+ int i;
+
+ cmd->action |= cpu_to_le16(MWL8K_ENABLE_RX_MULTICAST);
+ cmd->numaddr = cpu_to_le16(mc_count);
+ for (i = 0; i < mc_count && mclist; i++) {
+ if (mclist->da_addrlen != ETH_ALEN) {
+ kfree(cmd);
+ return NULL;
+ }
+ memcpy(cmd->addr[i], mclist->da_addr, ETH_ALEN);
+ mclist = mclist->next;
}
- memcpy(cmd->addr[i], mclist->da_addr, ETH_ALEN);
- mclist = mclist->next;
}
return &cmd->header;
*/
struct mwl8k_cmd_802_11_get_stat {
struct mwl8k_cmd_pkt header;
- __le16 action;
__le32 stats[64];
} __attribute__((packed));
cmd->header.code = cpu_to_le16(MWL8K_CMD_GET_STAT);
cmd->header.length = cpu_to_le16(sizeof(*cmd));
- cmd->action = cpu_to_le16(MWL8K_CMD_GET);
rc = mwl8k_post_cmd(hw, &cmd->header);
if (!rc) {
}
/*
+ * CMD_SET_MAC_ADDR.
+ */
+struct mwl8k_cmd_set_mac_addr {
+ struct mwl8k_cmd_pkt header;
+ __u8 mac_addr[ETH_ALEN];
+} __attribute__((packed));
+
+static int mwl8k_set_mac_addr(struct ieee80211_hw *hw, u8 *mac)
+{
+ struct mwl8k_cmd_set_mac_addr *cmd;
+ int rc;
+
+ cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+ if (cmd == NULL)
+ return -ENOMEM;
+
+ cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_MAC_ADDR);
+ cmd->header.length = cpu_to_le16(sizeof(*cmd));
+ memcpy(cmd->mac_addr, mac, ETH_ALEN);
+
+ rc = mwl8k_post_cmd(hw, &cmd->header);
+ kfree(cmd);
+
+ return rc;
+}
+
+
+/*
* CMD_SET_RATEADAPT_MODE.
*/
struct mwl8k_cmd_set_rate_adapt_mode {
if (cmd == NULL)
return -ENOMEM;
+ /*
+ * Queues 0 (BE) and 1 (BK) are swapped in hardware for
+ * this call.
+ */
+ qnum ^= !(qnum >> 1);
+
cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_EDCA_PARAMS);
cmd->header.length = cpu_to_le16(sizeof(*cmd));
cmd->action = cpu_to_le16(MWL8K_SET_EDCA_ALL);
/* XXX TBD Might just have to abort and return an error */
if (payload_len > MWL8K_FJ_BEACON_MAXLEN)
printk(KERN_ERR "%s(): WARNING: Incomplete beacon "
- "sent to firmware. Sz=%u MAX=%u\n", __func__,
- payload_len, MWL8K_FJ_BEACON_MAXLEN);
+ "sent to firmware. Sz=%u MAX=%u\n", __func__,
+ payload_len, MWL8K_FJ_BEACON_MAXLEN);
if (payload_len > MWL8K_FJ_BEACON_MAXLEN)
payload_len = MWL8K_FJ_BEACON_MAXLEN;
cmd->rate_type = cpu_to_le32(rate_type);
if (rate_table != NULL) {
- /* Copy over each field manually so
- * that bitflipping can be done
- */
+ /*
+ * Copy over each field manually so that endian
+ * conversion can be done.
+ */
cmd->rate_table.allow_rate_drop =
cpu_to_le32(rate_table->allow_rate_drop);
cmd->rate_table.num_rates =
if (status & MWL8K_A2H_INT_QUEUE_EMPTY) {
if (!mutex_is_locked(&priv->fw_mutex) &&
- priv->radio_on && mwl8k_txq_busy(priv))
+ priv->radio_on && priv->pending_tx_pkts)
mwl8k_tx_start(priv);
}
if (priv->current_channel == NULL) {
printk(KERN_DEBUG "%s: dropped TX frame since radio "
- "disabled\n", priv->name);
+ "disabled\n", wiphy_name(hw->wiphy));
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
return rc;
}
-struct mwl8k_work_struct {
- /* Initialized by mwl8k_queue_work(). */
- struct work_struct wt;
-
- /* Required field passed in to mwl8k_queue_work(). */
- struct ieee80211_hw *hw;
-
- /* Required field passed in to mwl8k_queue_work(). */
- int (*wfunc)(struct work_struct *w);
-
- /* Initialized by mwl8k_queue_work(). */
- struct completion *cmd_wait;
-
- /* Result code. */
- int rc;
-};
-
-static void mwl8k_config_thread(struct work_struct *wt)
-{
- struct mwl8k_work_struct *worker = (struct mwl8k_work_struct *)wt;
- struct ieee80211_hw *hw = worker->hw;
- int rc = 0;
-
- rc = mwl8k_fw_lock(hw);
- if (!rc) {
- rc = worker->wfunc(wt);
- mwl8k_fw_unlock(hw);
- }
-
- worker->rc = rc;
- complete(worker->cmd_wait);
-}
-
-static int mwl8k_queue_work(struct ieee80211_hw *hw,
- struct mwl8k_work_struct *worker,
- int (*wfunc)(struct work_struct *w))
-{
- struct mwl8k_priv *priv = hw->priv;
- unsigned long timeout = 0;
- int rc = 0;
-
- DECLARE_COMPLETION_ONSTACK(cmd_wait);
-
- worker->hw = hw;
- worker->cmd_wait = &cmd_wait;
- worker->rc = 1;
- worker->wfunc = wfunc;
-
- INIT_WORK(&worker->wt, mwl8k_config_thread);
- queue_work(priv->config_wq, &worker->wt);
-
- timeout = wait_for_completion_timeout(&cmd_wait,
- msecs_to_jiffies(10000));
-
- if (timeout)
- rc = worker->rc;
- else {
- cancel_work_sync(&worker->wt);
- rc = -ETIMEDOUT;
- }
-
- return rc;
-}
-
static int mwl8k_start(struct ieee80211_hw *hw)
{
struct mwl8k_priv *priv = hw->priv;
IRQF_SHARED, MWL8K_NAME, hw);
if (rc) {
printk(KERN_ERR "%s: failed to register IRQ handler\n",
- priv->name);
+ wiphy_name(hw->wiphy));
return -EIO;
}
/* Stop tx reclaim tasklet */
tasklet_disable(&priv->tx_reclaim_task);
- /* Stop config thread */
- flush_workqueue(priv->config_wq);
-
/* Return all skbs to mac80211 */
for (i = 0; i < MWL8K_TX_QUEUES; i++)
mwl8k_txq_reclaim(hw, i, 1);
if (conf->type != NL80211_IFTYPE_STATION)
return -EINVAL;
+ /*
+ * Reject interface creation if sniffer mode is active, as
+ * STA operation is mutually exclusive with hardware sniffer
+ * mode.
+ */
+ if (priv->sniffer_enabled) {
+ printk(KERN_INFO "%s: unable to create STA "
+ "interface due to sniffer mode being enabled\n",
+ wiphy_name(hw->wiphy));
+ return -EINVAL;
+ }
+
/* Clean out driver private area */
mwl8k_vif = MWL8K_VIF(conf->vif);
memset(mwl8k_vif, 0, sizeof(*mwl8k_vif));
- /* Save the mac address */
+ /* Set and save the mac address */
+ mwl8k_set_mac_addr(hw, conf->mac_addr);
memcpy(mwl8k_vif->mac_addr, conf->mac_addr, ETH_ALEN);
/* Back pointer to parent config block */
if (priv->vif == NULL)
return;
+ mwl8k_set_mac_addr(hw, "\x00\x00\x00\x00\x00\x00");
+
priv->vif = NULL;
}
priv->capture_beacon = false;
rc = mwl8k_fw_lock(hw);
- if (!rc)
+ if (rc)
return;
if (info->assoc) {
{
struct mwl8k_cmd_pkt *cmd;
- cmd = __mwl8k_cmd_mac_multicast_adr(hw, mc_count, mclist);
+ /*
+ * Synthesize and return a command packet that programs the
+ * hardware multicast address filter. At this point we don't
+ * know whether FIF_ALLMULTI is being requested, but if it is,
+ * we'll end up throwing this packet away and creating a new
+ * one in mwl8k_configure_filter().
+ */
+ cmd = __mwl8k_cmd_mac_multicast_adr(hw, 0, mc_count, mclist);
return (unsigned long)cmd;
}
-struct mwl8k_configure_filter_worker {
- struct mwl8k_work_struct header;
- unsigned int changed_flags;
- unsigned int total_flags;
- struct mwl8k_cmd_pkt *multicast_adr_cmd;
-};
-
-#define MWL8K_SUPPORTED_IF_FLAGS FIF_BCN_PRBRESP_PROMISC
-
-static int mwl8k_configure_filter_wt(struct work_struct *wt)
+static int
+mwl8k_configure_filter_sniffer(struct ieee80211_hw *hw,
+ unsigned int changed_flags,
+ unsigned int *total_flags)
{
- struct mwl8k_configure_filter_worker *worker =
- (struct mwl8k_configure_filter_worker *)wt;
- struct ieee80211_hw *hw = worker->header.hw;
struct mwl8k_priv *priv = hw->priv;
- int rc = 0;
- if (worker->changed_flags & FIF_BCN_PRBRESP_PROMISC) {
- if (worker->total_flags & FIF_BCN_PRBRESP_PROMISC)
- rc = mwl8k_cmd_set_pre_scan(hw);
- else {
- u8 *bssid;
-
- bssid = "\x00\x00\x00\x00\x00\x00";
- if (priv->vif != NULL)
- bssid = MWL8K_VIF(priv->vif)->bssid;
+ /*
+ * Hardware sniffer mode is mutually exclusive with STA
+ * operation, so refuse to enable sniffer mode if a STA
+ * interface is active.
+ */
+ if (priv->vif != NULL) {
+ if (net_ratelimit())
+ printk(KERN_INFO "%s: not enabling sniffer "
+ "mode because STA interface is active\n",
+ wiphy_name(hw->wiphy));
+ return 0;
+ }
- rc = mwl8k_cmd_set_post_scan(hw, bssid);
- }
+ if (!priv->sniffer_enabled) {
+ if (mwl8k_enable_sniffer(hw, 1))
+ return 0;
+ priv->sniffer_enabled = true;
}
- if (!rc && worker->multicast_adr_cmd != NULL)
- rc = mwl8k_post_cmd(hw, worker->multicast_adr_cmd);
- kfree(worker->multicast_adr_cmd);
+ *total_flags &= FIF_PROMISC_IN_BSS | FIF_ALLMULTI |
+ FIF_BCN_PRBRESP_PROMISC | FIF_CONTROL |
+ FIF_OTHER_BSS;
- return rc;
+ return 1;
}
static void mwl8k_configure_filter(struct ieee80211_hw *hw,
unsigned int *total_flags,
u64 multicast)
{
- struct mwl8k_configure_filter_worker *worker;
+ struct mwl8k_priv *priv = hw->priv;
+ struct mwl8k_cmd_pkt *cmd = (void *)(unsigned long)multicast;
+
+ /*
+ * Enable hardware sniffer mode if FIF_CONTROL or
+ * FIF_OTHER_BSS is requested.
+ */
+ if (*total_flags & (FIF_CONTROL | FIF_OTHER_BSS) &&
+ mwl8k_configure_filter_sniffer(hw, changed_flags, total_flags)) {
+ kfree(cmd);
+ return;
+ }
/* Clear unsupported feature flags */
- *total_flags &= MWL8K_SUPPORTED_IF_FLAGS;
+ *total_flags &= FIF_ALLMULTI | FIF_BCN_PRBRESP_PROMISC;
- if (!(changed_flags & MWL8K_SUPPORTED_IF_FLAGS))
+ if (mwl8k_fw_lock(hw))
return;
- worker = kzalloc(sizeof(*worker), GFP_ATOMIC);
- if (worker == NULL)
- return;
+ if (priv->sniffer_enabled) {
+ mwl8k_enable_sniffer(hw, 0);
+ priv->sniffer_enabled = false;
+ }
+
+ if (changed_flags & FIF_BCN_PRBRESP_PROMISC) {
+ if (*total_flags & FIF_BCN_PRBRESP_PROMISC) {
+ /*
+ * Disable the BSS filter.
+ */
+ mwl8k_cmd_set_pre_scan(hw);
+ } else {
+ u8 *bssid;
+
+ /*
+ * Enable the BSS filter.
+ *
+ * If there is an active STA interface, use that
+ * interface's BSSID, otherwise use a dummy one
+ * (where the OUI part needs to be nonzero for
+ * the BSSID to be accepted by POST_SCAN).
+ */
+ bssid = "\x01\x00\x00\x00\x00\x00";
+ if (priv->vif != NULL)
+ bssid = MWL8K_VIF(priv->vif)->bssid;
- worker->changed_flags = changed_flags;
- worker->total_flags = *total_flags;
- worker->multicast_adr_cmd = (void *)(unsigned long)multicast;
+ mwl8k_cmd_set_post_scan(hw, bssid);
+ }
+ }
- mwl8k_queue_work(hw, &worker->header, mwl8k_configure_filter_wt);
+ /*
+ * If FIF_ALLMULTI is being requested, throw away the command
+ * packet that ->prepare_multicast() built and replace it with
+ * a command packet that enables reception of all multicast
+ * packets.
+ */
+ if (*total_flags & FIF_ALLMULTI) {
+ kfree(cmd);
+ cmd = __mwl8k_cmd_mac_multicast_adr(hw, 1, 0, NULL);
+ }
+
+ if (cmd != NULL) {
+ mwl8k_post_cmd(hw, cmd);
+ kfree(cmd);
+ }
+
+ mwl8k_fw_unlock(hw);
}
static int mwl8k_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
for (i = 0; i < MWL8K_TX_QUEUES; i++)
mwl8k_txq_reclaim(hw, i, 0);
- if (priv->tx_wait != NULL && mwl8k_txq_busy(priv) == 0) {
+ if (priv->tx_wait != NULL && !priv->pending_tx_pkts) {
complete(priv->tx_wait);
priv->tx_wait = NULL;
}
static int __devinit mwl8k_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
+ static int printed_version = 0;
struct ieee80211_hw *hw;
struct mwl8k_priv *priv;
int rc;
int i;
- u8 *fw;
+
+ if (!printed_version) {
+ printk(KERN_INFO "%s version %s\n", MWL8K_DESC, MWL8K_VERSION);
+ printed_version = 1;
+ }
rc = pci_enable_device(pdev);
if (rc) {
priv = hw->priv;
priv->hw = hw;
priv->pdev = pdev;
+ priv->sniffer_enabled = false;
priv->wmm_enabled = false;
priv->pending_tx_pkts = 0;
- strncpy(priv->name, MWL8K_NAME, sizeof(priv->name));
SET_IEEE80211_DEV(hw, &pdev->dev);
pci_set_drvdata(pdev, hw);
priv->regs = pci_iomap(pdev, 1, 0x10000);
if (priv->regs == NULL) {
- printk(KERN_ERR "%s: Cannot map device memory\n", priv->name);
+ printk(KERN_ERR "%s: Cannot map device memory\n",
+ wiphy_name(hw->wiphy));
goto err_iounmap;
}
mwl8k_tx_reclaim_handler, (unsigned long)hw);
tasklet_disable(&priv->tx_reclaim_task);
- /* Config workthread */
- priv->config_wq = create_singlethread_workqueue("mwl8k_config");
- if (priv->config_wq == NULL)
- goto err_iounmap;
-
/* Power management cookie */
priv->cookie = pci_alloc_consistent(priv->pdev, 4, &priv->cookie_dma);
if (priv->cookie == NULL)
mutex_init(&priv->fw_mutex);
priv->fw_mutex_owner = NULL;
priv->fw_mutex_depth = 0;
- priv->tx_wait = NULL;
priv->hostcmd_wait = NULL;
spin_lock_init(&priv->tx_lock);
+ priv->tx_wait = NULL;
+
for (i = 0; i < MWL8K_TX_QUEUES; i++) {
rc = mwl8k_txq_init(hw, i);
if (rc)
IRQF_SHARED, MWL8K_NAME, hw);
if (rc) {
printk(KERN_ERR "%s: failed to register IRQ handler\n",
- priv->name);
+ wiphy_name(hw->wiphy));
goto err_free_queues;
}
/* Ask userland hotplug daemon for the device firmware */
rc = mwl8k_request_firmware(priv, (u32)id->driver_data);
if (rc) {
- printk(KERN_ERR "%s: Firmware files not found\n", priv->name);
+ printk(KERN_ERR "%s: Firmware files not found\n",
+ wiphy_name(hw->wiphy));
goto err_free_irq;
}
/* Load firmware into hardware */
- rc = mwl8k_load_firmware(priv);
+ rc = mwl8k_load_firmware(hw);
if (rc) {
- printk(KERN_ERR "%s: Cannot start firmware\n", priv->name);
+ printk(KERN_ERR "%s: Cannot start firmware\n",
+ wiphy_name(hw->wiphy));
goto err_stop_firmware;
}
/* Get config data, mac addrs etc */
rc = mwl8k_cmd_get_hw_spec(hw);
if (rc) {
- printk(KERN_ERR "%s: Cannot initialise firmware\n", priv->name);
+ printk(KERN_ERR "%s: Cannot initialise firmware\n",
+ wiphy_name(hw->wiphy));
goto err_stop_firmware;
}
/* Turn radio off */
rc = mwl8k_cmd_802_11_radio_disable(hw);
if (rc) {
- printk(KERN_ERR "%s: Cannot disable\n", priv->name);
+ printk(KERN_ERR "%s: Cannot disable\n", wiphy_name(hw->wiphy));
+ goto err_stop_firmware;
+ }
+
+ /* Clear MAC address */
+ rc = mwl8k_set_mac_addr(hw, "\x00\x00\x00\x00\x00\x00");
+ if (rc) {
+ printk(KERN_ERR "%s: Cannot clear MAC address\n",
+ wiphy_name(hw->wiphy));
goto err_stop_firmware;
}
rc = ieee80211_register_hw(hw);
if (rc) {
- printk(KERN_ERR "%s: Cannot register device\n", priv->name);
+ printk(KERN_ERR "%s: Cannot register device\n",
+ wiphy_name(hw->wiphy));
goto err_stop_firmware;
}
- fw = (u8 *)&priv->fw_rev;
- printk(KERN_INFO "%s: 88W%u %s\n", priv->name, priv->part_num,
- MWL8K_DESC);
- printk(KERN_INFO "%s: Driver Ver:%s Firmware Ver:%u.%u.%u.%u\n",
- priv->name, MWL8K_VERSION, fw[3], fw[2], fw[1], fw[0]);
- printk(KERN_INFO "%s: MAC Address: %pM\n", priv->name,
- hw->wiphy->perm_addr);
+ printk(KERN_INFO "%s: 88w%u v%d, %pM, firmware version %u.%u.%u.%u\n",
+ wiphy_name(hw->wiphy), priv->part_num, priv->hw_rev,
+ hw->wiphy->perm_addr,
+ (priv->fw_rev >> 24) & 0xff, (priv->fw_rev >> 16) & 0xff,
+ (priv->fw_rev >> 8) & 0xff, priv->fw_rev & 0xff);
return 0;
if (priv->regs != NULL)
pci_iounmap(pdev, priv->regs);
- if (priv->config_wq != NULL)
- destroy_workqueue(priv->config_wq);
-
pci_set_drvdata(pdev, NULL);
ieee80211_free_hw(hw);
/* Remove tx reclaim tasklet */
tasklet_kill(&priv->tx_reclaim_task);
- /* Stop config thread */
- destroy_workqueue(priv->config_wq);
-
/* Stop hardware */
mwl8k_hw_reset(priv);
mwl8k_rxq_deinit(hw, 0);
- pci_free_consistent(priv->pdev, 4,
- priv->cookie, priv->cookie_dma);
+ pci_free_consistent(priv->pdev, 4, priv->cookie, priv->cookie_dma);
pci_iounmap(pdev, priv->regs);
pci_set_drvdata(pdev, NULL);
module_init(mwl8k_init);
module_exit(mwl8k_exit);
+
+MODULE_DESCRIPTION(MWL8K_DESC);
+MODULE_VERSION(MWL8K_VERSION);
+MODULE_AUTHOR("Lennert Buytenhek <buytenh@marvell.com>");
+MODULE_LICENSE("GPL");