Commit a910e4a9 authored by Solomon Peachy's avatar Solomon Peachy Committed by John W. Linville

cw1200: add driver for the ST-E CW1100 & CW1200 WLAN chipsets

Signed-off-by: default avatarSolomon Peachy <pizza@shaftnet.org>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 5f07d15a
......@@ -2299,6 +2299,11 @@ M: Jaya Kumar <jayakumar.alsa@gmail.com>
S: Maintained
F: sound/pci/cs5535audio/
CW1200 WLAN driver
M: Solomon Peachy <pizza@shaftnet.org>
S: Maintained
F: drivers/net/wireless/cw1200/
CX18 VIDEO4LINUX DRIVER
M: Andy Walls <awalls@md.metrocast.net>
L: ivtv-devel@ivtvdriver.org (moderated for non-subscribers)
......
......@@ -280,5 +280,6 @@ source "drivers/net/wireless/rtlwifi/Kconfig"
source "drivers/net/wireless/ti/Kconfig"
source "drivers/net/wireless/zd1211rw/Kconfig"
source "drivers/net/wireless/mwifiex/Kconfig"
source "drivers/net/wireless/cw1200/Kconfig"
endif # WLAN
......@@ -57,3 +57,5 @@ obj-$(CONFIG_MWIFIEX) += mwifiex/
obj-$(CONFIG_BRCMFMAC) += brcm80211/
obj-$(CONFIG_BRCMSMAC) += brcm80211/
obj-$(CONFIG_CW1200) += cw1200/
config CW1200
tristate "CW1200 WLAN support"
depends on MAC80211 && CFG80211
help
This is a driver for the ST-E CW1100 & CW1200 WLAN chipsets.
This option just enables the driver core, see below for
specific bus support.
if CW1200
config CW1200_WLAN_SDIO
tristate "Support SDIO platforms"
depends on CW1200 && MMC
help
Enable support for the CW1200 connected via an SDIO bus.
config CW1200_WLAN_SPI
tristate "Support SPI platforms"
depends on CW1200 && SPI
help
Enables support for the CW1200 connected via a SPI bus.
config CW1200_WLAN_SAGRAD
tristate "Support Sagrad SG901-1091/1098 modules"
depends on CW1200_WLAN_SDIO
help
This provides the platform data glue to support the
Sagrad SG901-1091/1098 modules in their standard SDIO EVK.
It also includes example SPI platform data.
menu "Driver debug features"
depends on CW1200 && DEBUG_FS
config CW1200_ETF
bool "Enable CW1200 Engineering Test Framework hooks"
help
If you don't know what this is, just say N.
config CW1200_ITP
bool "Enable ITP access"
help
If you don't know what this is, just say N.
endmenu
endif
cw1200_core-y := \
fwio.o \
txrx.o \
main.o \
queue.o \
hwio.o \
bh.o \
wsm.o \
sta.o \
scan.o \
pm.o \
debug.o
cw1200_core-$(CONFIG_CW1200_ITP) += itp.o
# CFLAGS_sta.o += -DDEBUG
cw1200_wlan_sdio-y := cw1200_sdio.o
cw1200_wlan_spi-y := cw1200_spi.o
cw1200_wlan_sagrad-y := cw1200_sagrad.o
obj-$(CONFIG_CW1200) += cw1200_core.o
obj-$(CONFIG_CW1200_WLAN_SDIO) += cw1200_wlan_sdio.o
obj-$(CONFIG_CW1200_WLAN_SPI) += cw1200_wlan_spi.o
obj-$(CONFIG_CW1200_WLAN_SAGRAD) += cw1200_wlan_sagrad.o
/*
* Device handling thread implementation for mac80211 ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Based on:
* ST-Ericsson UMAC CW1200 driver, which is
* Copyright (c) 2010, ST-Ericsson
* Author: Ajitpal Singh <ajitpal.singh@stericsson.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <net/mac80211.h>
#include <linux/kthread.h>
#include <linux/timer.h>
#include "cw1200.h"
#include "bh.h"
#include "hwio.h"
#include "wsm.h"
#include "sbus.h"
#include "debug.h"
#include "fwio.h"
static int cw1200_bh(void *arg);
#define DOWNLOAD_BLOCK_SIZE_WR (0x1000 - 4)
/* an SPI message cannot be bigger than (2"12-1)*2 bytes
* "*2" to cvt to bytes */
#define MAX_SZ_RD_WR_BUFFERS (DOWNLOAD_BLOCK_SIZE_WR*2)
#define PIGGYBACK_CTRL_REG (2)
#define EFFECTIVE_BUF_SIZE (MAX_SZ_RD_WR_BUFFERS - PIGGYBACK_CTRL_REG)
/* Suspend state privates */
enum cw1200_bh_pm_state {
CW1200_BH_RESUMED = 0,
CW1200_BH_SUSPEND,
CW1200_BH_SUSPENDED,
CW1200_BH_RESUME,
};
typedef int (*cw1200_wsm_handler)(struct cw1200_common *priv,
u8 *data, size_t size);
static void cw1200_bh_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, bh_work);
cw1200_bh(priv);
}
int cw1200_register_bh(struct cw1200_common *priv)
{
int err = 0;
/* Realtime workqueue */
priv->bh_workqueue = alloc_workqueue("cw1200_bh",
WQ_MEM_RECLAIM | WQ_HIGHPRI
| WQ_CPU_INTENSIVE, 1);
if (!priv->bh_workqueue)
return -ENOMEM;
INIT_WORK(&priv->bh_work, cw1200_bh_work);
pr_debug("[BH] register.\n");
atomic_set(&priv->bh_rx, 0);
atomic_set(&priv->bh_tx, 0);
atomic_set(&priv->bh_term, 0);
atomic_set(&priv->bh_suspend, CW1200_BH_RESUMED);
priv->bh_error = 0;
priv->hw_bufs_used = 0;
priv->buf_id_tx = 0;
priv->buf_id_rx = 0;
init_waitqueue_head(&priv->bh_wq);
init_waitqueue_head(&priv->bh_evt_wq);
err = !queue_work(priv->bh_workqueue, &priv->bh_work);
WARN_ON(err);
return err;
}
void cw1200_unregister_bh(struct cw1200_common *priv)
{
atomic_add(1, &priv->bh_term);
wake_up(&priv->bh_wq);
flush_workqueue(priv->bh_workqueue);
destroy_workqueue(priv->bh_workqueue);
priv->bh_workqueue = NULL;
pr_debug("[BH] unregistered.\n");
}
void cw1200_irq_handler(struct cw1200_common *priv)
{
pr_debug("[BH] irq.\n");
/* Disable Interrupts! */
/* NOTE: sbus_ops->lock already held */
__cw1200_irq_enable(priv, 0);
if (/* WARN_ON */(priv->bh_error))
return;
if (atomic_add_return(1, &priv->bh_rx) == 1)
wake_up(&priv->bh_wq);
}
EXPORT_SYMBOL_GPL(cw1200_irq_handler);
void cw1200_bh_wakeup(struct cw1200_common *priv)
{
pr_debug("[BH] wakeup.\n");
if (priv->bh_error) {
pr_err("[BH] wakeup failed (BH error)\n");
return;
}
if (atomic_add_return(1, &priv->bh_tx) == 1)
wake_up(&priv->bh_wq);
}
int cw1200_bh_suspend(struct cw1200_common *priv)
{
pr_debug("[BH] suspend.\n");
if (priv->bh_error) {
wiphy_warn(priv->hw->wiphy, "BH error -- can't suspend\n");
return -EINVAL;
}
atomic_set(&priv->bh_suspend, CW1200_BH_SUSPEND);
wake_up(&priv->bh_wq);
return wait_event_timeout(priv->bh_evt_wq, priv->bh_error ||
(CW1200_BH_SUSPENDED == atomic_read(&priv->bh_suspend)),
1 * HZ) ? 0 : -ETIMEDOUT;
}
int cw1200_bh_resume(struct cw1200_common *priv)
{
pr_debug("[BH] resume.\n");
if (priv->bh_error) {
wiphy_warn(priv->hw->wiphy, "BH error -- can't resume\n");
return -EINVAL;
}
atomic_set(&priv->bh_suspend, CW1200_BH_RESUME);
wake_up(&priv->bh_wq);
return wait_event_timeout(priv->bh_evt_wq, priv->bh_error ||
(CW1200_BH_RESUMED == atomic_read(&priv->bh_suspend)),
1 * HZ) ? 0 : -ETIMEDOUT;
}
static inline void wsm_alloc_tx_buffer(struct cw1200_common *priv)
{
++priv->hw_bufs_used;
}
int wsm_release_tx_buffer(struct cw1200_common *priv, int count)
{
int ret = 0;
int hw_bufs_used = priv->hw_bufs_used;
priv->hw_bufs_used -= count;
if (WARN_ON(priv->hw_bufs_used < 0))
ret = -1;
else if (hw_bufs_used >= priv->wsm_caps.input_buffers)
ret = 1;
if (!priv->hw_bufs_used)
wake_up(&priv->bh_evt_wq);
return ret;
}
static int cw1200_bh_read_ctrl_reg(struct cw1200_common *priv,
u16 *ctrl_reg)
{
int ret;
ret = cw1200_reg_read_16(priv,
ST90TDS_CONTROL_REG_ID, ctrl_reg);
if (ret) {
ret = cw1200_reg_read_16(priv,
ST90TDS_CONTROL_REG_ID, ctrl_reg);
if (ret)
pr_err("[BH] Failed to read control register.\n");
}
return ret;
}
static int cw1200_device_wakeup(struct cw1200_common *priv)
{
u16 ctrl_reg;
int ret;
pr_debug("[BH] Device wakeup.\n");
/* First, set the dpll register */
ret = cw1200_reg_write_32(priv, ST90TDS_TSET_GEN_R_W_REG_ID,
cw1200_dpll_from_clk(priv->hw_refclk));
if (WARN_ON(ret))
return ret;
/* To force the device to be always-on, the host sets WLAN_UP to 1 */
ret = cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID,
ST90TDS_CONT_WUP_BIT);
if (WARN_ON(ret))
return ret;
ret = cw1200_bh_read_ctrl_reg(priv, &ctrl_reg);
if (WARN_ON(ret))
return ret;
/* If the device returns WLAN_RDY as 1, the device is active and will
* remain active. */
if (ctrl_reg & ST90TDS_CONT_RDY_BIT) {
pr_debug("[BH] Device awake.\n");
return 1;
}
return 0;
}
/* Must be called from BH thraed. */
void cw1200_enable_powersave(struct cw1200_common *priv,
bool enable)
{
pr_debug("[BH] Powerave is %s.\n",
enable ? "enabled" : "disabled");
priv->powersave_enabled = enable;
}
static int cw1200_bh_rx_helper(struct cw1200_common *priv,
uint16_t *ctrl_reg,
int *tx)
{
size_t read_len = 0;
struct sk_buff *skb_rx = NULL;
struct wsm_hdr *wsm;
size_t wsm_len;
u16 wsm_id;
u8 wsm_seq;
int rx_resync = 1;
size_t alloc_len;
u8 *data;
read_len = (*ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) * 2;
if (!read_len)
return 0; /* No more work */
if (WARN_ON((read_len < sizeof(struct wsm_hdr)) ||
(read_len > EFFECTIVE_BUF_SIZE))) {
pr_debug("Invalid read len: %zu (%04x)",
read_len, *ctrl_reg);
goto err;
}
/* Add SIZE of PIGGYBACK reg (CONTROL Reg)
* to the NEXT Message length + 2 Bytes for SKB */
read_len = read_len + 2;
alloc_len = priv->sbus_ops->align_size(
priv->sbus_priv, read_len);
/* Check if not exceeding CW1200 capabilities */
if (WARN_ON_ONCE(alloc_len > EFFECTIVE_BUF_SIZE)) {
pr_debug("Read aligned len: %zu\n",
alloc_len);
}
skb_rx = dev_alloc_skb(alloc_len);
if (WARN_ON(!skb_rx))
goto err;
skb_trim(skb_rx, 0);
skb_put(skb_rx, read_len);
data = skb_rx->data;
if (WARN_ON(!data))
goto err;
if (WARN_ON(cw1200_data_read(priv, data, alloc_len))) {
pr_err("rx blew up, len %zu\n", alloc_len);
goto err;
}
/* Piggyback */
*ctrl_reg = __le16_to_cpu(
((__le16 *)data)[alloc_len / 2 - 1]);
wsm = (struct wsm_hdr *)data;
wsm_len = __le16_to_cpu(wsm->len);
if (WARN_ON(wsm_len > read_len))
goto err;
if (priv->wsm_enable_wsm_dumps)
print_hex_dump_bytes("<-- ",
DUMP_PREFIX_NONE,
data, wsm_len);
wsm_id = __le16_to_cpu(wsm->id) & 0xFFF;
wsm_seq = (__le16_to_cpu(wsm->id) >> 13) & 7;
skb_trim(skb_rx, wsm_len);
if (wsm_id == 0x0800) {
wsm_handle_exception(priv,
&data[sizeof(*wsm)],
wsm_len - sizeof(*wsm));
goto err;
} else if (!rx_resync) {
if (WARN_ON(wsm_seq != priv->wsm_rx_seq))
goto err;
}
priv->wsm_rx_seq = (wsm_seq + 1) & 7;
rx_resync = 0;
if (wsm_id & 0x0400) {
int rc = wsm_release_tx_buffer(priv, 1);
if (WARN_ON(rc < 0))
return rc;
else if (rc > 0)
*tx = 1;
}
/* cw1200_wsm_rx takes care on SKB livetime */
if (WARN_ON(wsm_handle_rx(priv, wsm_id, wsm, &skb_rx)))
goto err;
if (skb_rx) {
dev_kfree_skb(skb_rx);
skb_rx = NULL;
}
return 0;
err:
if (skb_rx) {
dev_kfree_skb(skb_rx);
skb_rx = NULL;
}
return -1;
}
static int cw1200_bh_tx_helper(struct cw1200_common *priv,
int *pending_tx,
int *tx_burst)
{
size_t tx_len;
u8 *data;
int ret;
struct wsm_hdr *wsm;
if (priv->device_can_sleep) {
ret = cw1200_device_wakeup(priv);
if (WARN_ON(ret < 0)) { /* Error in wakeup */
*pending_tx = 1;
return 0;
} else if (ret) { /* Woke up */
priv->device_can_sleep = false;
} else { /* Did not awake */
*pending_tx = 1;
return 0;
}
}
wsm_alloc_tx_buffer(priv);
ret = wsm_get_tx(priv, &data, &tx_len, tx_burst);
if (ret <= 0) {
wsm_release_tx_buffer(priv, 1);
if (WARN_ON(ret < 0))
return ret; /* Error */
return 0; /* No work */
}
wsm = (struct wsm_hdr *)data;
BUG_ON(tx_len < sizeof(*wsm));
BUG_ON(__le16_to_cpu(wsm->len) != tx_len);
atomic_add(1, &priv->bh_tx);
tx_len = priv->sbus_ops->align_size(
priv->sbus_priv, tx_len);
/* Check if not exceeding CW1200 capabilities */
if (WARN_ON_ONCE(tx_len > EFFECTIVE_BUF_SIZE))
pr_debug("Write aligned len: %zu\n", tx_len);
wsm->id &= __cpu_to_le16(0xffff ^ WSM_TX_SEQ(WSM_TX_SEQ_MAX));
wsm->id |= __cpu_to_le16(WSM_TX_SEQ(priv->wsm_tx_seq));
if (WARN_ON(cw1200_data_write(priv, data, tx_len))) {
pr_err("tx blew up, len %zu\n", tx_len);
wsm_release_tx_buffer(priv, 1);
return -1; /* Error */
}
if (priv->wsm_enable_wsm_dumps)
print_hex_dump_bytes("--> ",
DUMP_PREFIX_NONE,
data,
__le16_to_cpu(wsm->len));
wsm_txed(priv, data);
priv->wsm_tx_seq = (priv->wsm_tx_seq + 1) & WSM_TX_SEQ_MAX;
if (*tx_burst > 1) {
cw1200_debug_tx_burst(priv);
return 1; /* Work remains */
}
return 0;
}
static int cw1200_bh(void *arg)
{
struct cw1200_common *priv = arg;
int rx, tx, term, suspend;
u16 ctrl_reg = 0;
int tx_allowed;
int pending_tx = 0;
int tx_burst;
long status;
u32 dummy;
int ret;
for (;;) {
if (!priv->hw_bufs_used &&
priv->powersave_enabled &&
!priv->device_can_sleep &&
!atomic_read(&priv->recent_scan)) {
status = 1 * HZ;
pr_debug("[BH] Device wakedown. No data.\n");
cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID, 0);
priv->device_can_sleep = true;
} else if (priv->hw_bufs_used) {
/* Interrupt loss detection */
status = 1 * HZ;
} else {
status = MAX_SCHEDULE_TIMEOUT;
}
/* Dummy Read for SDIO retry mechanism*/
if ((priv->hw_type != -1) &&
(atomic_read(&priv->bh_rx) == 0) &&
(atomic_read(&priv->bh_tx) == 0))
cw1200_reg_read(priv, ST90TDS_CONFIG_REG_ID,
&dummy, sizeof(dummy));
pr_debug("[BH] waiting ...\n");
status = wait_event_interruptible_timeout(priv->bh_wq, ({
rx = atomic_xchg(&priv->bh_rx, 0);
tx = atomic_xchg(&priv->bh_tx, 0);
term = atomic_xchg(&priv->bh_term, 0);
suspend = pending_tx ?
0 : atomic_read(&priv->bh_suspend);
(rx || tx || term || suspend || priv->bh_error);
}), status);
pr_debug("[BH] - rx: %d, tx: %d, term: %d, suspend: %d, status: %ld\n",
rx, tx, term, suspend, status);
/* Did an error occur? */
if ((status < 0 && status != -ERESTARTSYS) ||
term || priv->bh_error) {
break;
}
if (!status) { /* wait_event timed out */
unsigned long timestamp = jiffies;
long timeout;
int pending = 0;
int i;
/* Check to see if we have any outstanding frames */
if (priv->hw_bufs_used && (!rx || !tx)) {
wiphy_warn(priv->hw->wiphy,
"Missed interrupt? (%d frames outstanding)\n",
priv->hw_bufs_used);
rx = 1;
/* Get a timestamp of "oldest" frame */
for (i = 0; i < 4; ++i)
pending += cw1200_queue_get_xmit_timestamp(
&priv->tx_queue[i],
&timestamp,
priv->pending_frame_id);
/* Check if frame transmission is timed out.
* Add an extra second with respect to possible
* interrupt loss.
*/
timeout = timestamp +
WSM_CMD_LAST_CHANCE_TIMEOUT +
1 * HZ -
jiffies;
/* And terminate BH thread if the frame is "stuck" */
if (pending && timeout < 0) {
wiphy_warn(priv->hw->wiphy,
"Timeout waiting for TX confirm (%d/%d pending, %ld vs %lu).\n",
priv->hw_bufs_used, pending,
timestamp, jiffies);
break;
}
} else if (!priv->device_can_sleep &&
!atomic_read(&priv->recent_scan)) {
pr_debug("[BH] Device wakedown. Timeout.\n");
cw1200_reg_write_16(priv,
ST90TDS_CONTROL_REG_ID, 0);
priv->device_can_sleep = true;
}
goto done;
} else if (suspend) {
pr_debug("[BH] Device suspend.\n");
if (priv->powersave_enabled) {
pr_debug("[BH] Device wakedown. Suspend.\n");
cw1200_reg_write_16(priv,
ST90TDS_CONTROL_REG_ID, 0);
priv->device_can_sleep = true;
}
atomic_set(&priv->bh_suspend, CW1200_BH_SUSPENDED);
wake_up(&priv->bh_evt_wq);
status = wait_event_interruptible(priv->bh_wq,
CW1200_BH_RESUME == atomic_read(&priv->bh_suspend));
if (status < 0) {
wiphy_err(priv->hw->wiphy,
"Failed to wait for resume: %ld.\n",
status);
break;
}
pr_debug("[BH] Device resume.\n");
atomic_set(&priv->bh_suspend, CW1200_BH_RESUMED);
wake_up(&priv->bh_evt_wq);
atomic_add(1, &priv->bh_rx);
goto done;
}
rx:
tx += pending_tx;
pending_tx = 0;
if (cw1200_bh_read_ctrl_reg(priv, &ctrl_reg))
break;
/* Don't bother trying to rx unless we have data to read */
if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) {
ret = cw1200_bh_rx_helper(priv, &ctrl_reg, &tx);
if (ret < 0)
break;
/* Double up here if there's more data.. */
if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) {
ret = cw1200_bh_rx_helper(priv, &ctrl_reg, &tx);
if (ret < 0)
break;
}
}
tx:
if (tx) {
tx = 0;
BUG_ON(priv->hw_bufs_used > priv->wsm_caps.input_buffers);
tx_burst = priv->wsm_caps.input_buffers - priv->hw_bufs_used;
tx_allowed = tx_burst > 0;
if (!tx_allowed) {
/* Buffers full. Ensure we process tx
* after we handle rx..
*/
pending_tx = tx;
goto done_rx;
}
ret = cw1200_bh_tx_helper(priv, &pending_tx, &tx_burst);
if (ret < 0)
break;
if (ret > 0) /* More to transmit */
tx = ret;
/* Re-read ctrl reg */
if (cw1200_bh_read_ctrl_reg(priv, &ctrl_reg))
break;
}
done_rx:
if (priv->bh_error)
break;
if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK)
goto rx;
if (tx)
goto tx;
done:
/* Re-enable device interrupts */
priv->sbus_ops->lock(priv->sbus_priv);
__cw1200_irq_enable(priv, 1);
priv->sbus_ops->unlock(priv->sbus_priv);
}
/* Explicitly disable device interrupts */
priv->sbus_ops->lock(priv->sbus_priv);
__cw1200_irq_enable(priv, 0);
priv->sbus_ops->unlock(priv->sbus_priv);
if (!term) {
pr_err("[BH] Fatal error, exiting.\n");
priv->bh_error = 1;
/* TODO: schedule_work(recovery) */
}
return 0;
}
/*
* Device handling thread interface for mac80211 ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef CW1200_BH_H
#define CW1200_BH_H
/* extern */ struct cw1200_common;
int cw1200_register_bh(struct cw1200_common *priv);
void cw1200_unregister_bh(struct cw1200_common *priv);
void cw1200_irq_handler(struct cw1200_common *priv);
void cw1200_bh_wakeup(struct cw1200_common *priv);
int cw1200_bh_suspend(struct cw1200_common *priv);
int cw1200_bh_resume(struct cw1200_common *priv);
/* Must be called from BH thread. */
void cw1200_enable_powersave(struct cw1200_common *priv,
bool enable);
int wsm_release_tx_buffer(struct cw1200_common *priv, int count);
#endif /* CW1200_BH_H */
/*
* Common private data for ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Based on the mac80211 Prism54 code, which is
* Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
*
* Based on the islsm (softmac prism54) driver, which is:
* Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef CW1200_H
#define CW1200_H
#include <linux/wait.h>
#include <linux/version.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <net/mac80211.h>
#include "queue.h"
#include "wsm.h"
#include "scan.h"
#include "txrx.h"
#include "pm.h"
/* Forward declarations */
struct sbus_ops;
struct task_struct;
struct cw1200_debug_priv;
struct firmware;
#ifdef CONFIG_CW1200_ETF
extern int etf_mode;
extern char *etf_firmware;
#endif
#define CW1200_MAX_CTRL_FRAME_LEN (0x1000)
#define CW1200_MAX_STA_IN_AP_MODE (5)
#define CW1200_LINK_ID_AFTER_DTIM (CW1200_MAX_STA_IN_AP_MODE + 1)
#define CW1200_LINK_ID_UAPSD (CW1200_MAX_STA_IN_AP_MODE + 2)
#define CW1200_LINK_ID_MAX (CW1200_MAX_STA_IN_AP_MODE + 3)
#define CW1200_MAX_REQUEUE_ATTEMPTS (5)
#define CW1200_MAX_TID (8)
#define CW1200_BLOCK_ACK_CNT (30)
#define CW1200_BLOCK_ACK_THLD (800)
#define CW1200_BLOCK_ACK_HIST (3)
#define CW1200_BLOCK_ACK_INTERVAL (1 * HZ / CW1200_BLOCK_ACK_HIST)
#define CW1200_JOIN_TIMEOUT (1 * HZ)
#define CW1200_AUTH_TIMEOUT (5 * HZ)
struct cw1200_ht_info {
struct ieee80211_sta_ht_cap ht_cap;
enum nl80211_channel_type channel_type;
u16 operation_mode;
};
/* Please keep order */
enum cw1200_join_status {
CW1200_JOIN_STATUS_PASSIVE = 0,
CW1200_JOIN_STATUS_MONITOR,
CW1200_JOIN_STATUS_JOINING,
CW1200_JOIN_STATUS_PRE_STA,
CW1200_JOIN_STATUS_STA,
CW1200_JOIN_STATUS_IBSS,
CW1200_JOIN_STATUS_AP,
};
enum cw1200_link_status {
CW1200_LINK_OFF,
CW1200_LINK_RESERVE,
CW1200_LINK_SOFT,
CW1200_LINK_HARD,
CW1200_LINK_RESET,
CW1200_LINK_RESET_REMAP,
};
extern int cw1200_power_mode;
extern const char * const cw1200_fw_types[];
struct cw1200_link_entry {
unsigned long timestamp;
enum cw1200_link_status status;
enum cw1200_link_status prev_status;
u8 mac[ETH_ALEN];
u8 buffered[CW1200_MAX_TID];
struct sk_buff_head rx_queue;
};
struct cw1200_common {
/* interfaces to the rest of the stack */
struct ieee80211_hw *hw;
struct ieee80211_vif *vif;
struct device *pdev;
/* Statistics */
struct ieee80211_low_level_stats stats;
/* Our macaddr */
u8 mac_addr[ETH_ALEN];
/* Hardware interface */
const struct sbus_ops *sbus_ops;
struct sbus_priv *sbus_priv;
/* Hardware information */
enum {
HIF_9000_SILICON_VERSATILE = 0,
HIF_8601_VERSATILE,
HIF_8601_SILICON,
} hw_type;
enum {
CW1200_HW_REV_CUT10 = 10,
CW1200_HW_REV_CUT11 = 11,
CW1200_HW_REV_CUT20 = 20,
CW1200_HW_REV_CUT22 = 22,
CW1X60_HW_REV = 40,
} hw_revision;
int hw_refclk;
bool hw_have_5ghz;
const struct firmware *sdd;
char *sdd_path;
struct cw1200_debug_priv *debug;
struct workqueue_struct *workqueue;
struct mutex conf_mutex;
struct cw1200_queue tx_queue[4];
struct cw1200_queue_stats tx_queue_stats;
int tx_burst_idx;
/* firmware/hardware info */
unsigned int tx_hdr_len;
/* Radio data */
int output_power;
/* BBP/MAC state */
struct ieee80211_rate *rates;
struct ieee80211_rate *mcs_rates;
struct ieee80211_channel *channel;
struct wsm_edca_params edca;
struct wsm_tx_queue_params tx_queue_params;
struct wsm_mib_association_mode association_mode;
struct wsm_set_bss_params bss_params;
struct cw1200_ht_info ht_info;
struct wsm_set_pm powersave_mode;
struct wsm_set_pm firmware_ps_mode;
int cqm_rssi_thold;
unsigned cqm_rssi_hyst;
bool cqm_use_rssi;
int cqm_beacon_loss_count;
int channel_switch_in_progress;
wait_queue_head_t channel_switch_done;
u8 long_frame_max_tx_count;
u8 short_frame_max_tx_count;
int mode;
bool enable_beacon;
int beacon_int;
bool listening;
struct wsm_rx_filter rx_filter;
struct wsm_mib_multicast_filter multicast_filter;
bool has_multicast_subscription;
bool disable_beacon_filter;
struct work_struct update_filtering_work;
struct work_struct set_beacon_wakeup_period_work;
u8 ba_rx_tid_mask;
u8 ba_tx_tid_mask;
struct cw1200_pm_state pm_state;
struct wsm_p2p_ps_modeinfo p2p_ps_modeinfo;
struct wsm_uapsd_info uapsd_info;
bool setbssparams_done;
bool bt_present;
u8 conf_listen_interval;
u32 listen_interval;
u32 erp_info;
u32 rts_threshold;
/* BH */
atomic_t bh_rx;
atomic_t bh_tx;
atomic_t bh_term;
atomic_t bh_suspend;
struct workqueue_struct *bh_workqueue;
struct work_struct bh_work;
int bh_error;
wait_queue_head_t bh_wq;
wait_queue_head_t bh_evt_wq;
u8 buf_id_tx;
u8 buf_id_rx;
u8 wsm_rx_seq;
u8 wsm_tx_seq;
int hw_bufs_used;
bool powersave_enabled;
bool device_can_sleep;
/* Scan status */
struct cw1200_scan scan;
/* Keep cw1200 awake (WUP = 1) 1 second after each scan to avoid
* FW issue with sleeping/waking up. */
atomic_t recent_scan;
struct delayed_work clear_recent_scan_work;
/* WSM */
struct wsm_startup_ind wsm_caps;
struct mutex wsm_cmd_mux;
struct wsm_buf wsm_cmd_buf;
struct wsm_cmd wsm_cmd;
wait_queue_head_t wsm_cmd_wq;
wait_queue_head_t wsm_startup_done;
int firmware_ready;
atomic_t tx_lock;
/* WSM debug */
int wsm_enable_wsm_dumps;
/* WSM Join */
enum cw1200_join_status join_status;
u32 pending_frame_id;
bool join_pending;
struct delayed_work join_timeout;
struct work_struct unjoin_work;
struct work_struct join_complete_work;
int join_complete_status;
int join_dtim_period;
bool delayed_unjoin;
/* TX/RX and security */
s8 wep_default_key_id;
struct work_struct wep_key_work;
u32 key_map;
struct wsm_add_key keys[WSM_KEY_MAX_INDEX + 1];
/* AP powersave */
u32 link_id_map;
struct cw1200_link_entry link_id_db[CW1200_MAX_STA_IN_AP_MODE];
struct work_struct link_id_work;
struct delayed_work link_id_gc_work;
u32 sta_asleep_mask;
u32 pspoll_mask;
bool aid0_bit_set;
spinlock_t ps_state_lock; /* Protect power save state */
bool buffered_multicasts;
bool tx_multicast;
struct work_struct set_tim_work;
struct work_struct set_cts_work;
struct work_struct multicast_start_work;
struct work_struct multicast_stop_work;
struct timer_list mcast_timeout;
/* WSM events and CQM implementation */
spinlock_t event_queue_lock; /* Protect event queue */
struct list_head event_queue;
struct work_struct event_handler;
struct delayed_work bss_loss_work;
spinlock_t bss_loss_lock; /* Protect BSS loss state */
int bss_loss_state;
int bss_loss_confirm_id;
int delayed_link_loss;
struct work_struct bss_params_work;
/* TX rate policy cache */
struct tx_policy_cache tx_policy_cache;
struct work_struct tx_policy_upload_work;
/* legacy PS mode switch in suspend */
int ps_mode_switch_in_progress;
wait_queue_head_t ps_mode_switch_done;
/* Workaround for WFD testcase 6.1.10*/
struct work_struct linkid_reset_work;
u8 action_frame_sa[ETH_ALEN];
u8 action_linkid;
#ifdef CONFIG_CW1200_ETF
struct sk_buff_head etf_q;
#endif
};
struct cw1200_sta_priv {
int link_id;
};
/* interfaces for the drivers */
int cw1200_core_probe(const struct sbus_ops *sbus_ops,
struct sbus_priv *sbus,
struct device *pdev,
struct cw1200_common **pself,
int ref_clk, const u8 *macaddr,
const char *sdd_path, bool have_5ghz);
void cw1200_core_release(struct cw1200_common *self);
#define FWLOAD_BLOCK_SIZE (1024)
static inline int cw1200_is_ht(const struct cw1200_ht_info *ht_info)
{
return ht_info->channel_type != NL80211_CHAN_NO_HT;
}
static inline int cw1200_ht_greenfield(const struct cw1200_ht_info *ht_info)
{
return cw1200_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 inline int cw1200_ht_ampdu_density(const struct cw1200_ht_info *ht_info)
{
if (!cw1200_is_ht(ht_info))
return 0;
return ht_info->ht_cap.ampdu_density;
}
#endif /* CW1200_H */
/*
* Platform glue data for ST-Ericsson CW1200 driver
*
* Copyright (c) 2013, Sagrad, Inc
* Author: Solomon Peachy <speachy@sagrad.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/cw1200_platform.h>
MODULE_AUTHOR("Solomon Peachy <speachy@sagrad.com>");
MODULE_DESCRIPTION("ST-Ericsson CW1200 Platform glue driver");
MODULE_LICENSE("GPL");
/* Define just one of these. Feel free to customize as needed */
#define SAGRAD_1091_1098_EVK_SDIO
/* #define SAGRAD_1091_1098_EVK_SPI */
#ifdef SAGRAD_1091_1098_EVK_SDIO
#if 0
static struct resource cw1200_href_resources[] = {
{
.start = 215, /* fix me as appropriate */
.end = 215, /* ditto */
.flags = IORESOURCE_IO,
.name = "cw1200_wlan_reset",
},
{
.start = 216, /* fix me as appropriate */
.end = 216, /* ditto */
.flags = IORESOURCE_IO,
.name = "cw1200_wlan_powerup",
},
{
.start = NOMADIK_GPIO_TO_IRQ(216), /* fix me as appropriate */
.end = NOMADIK_GPIO_TO_IRQ(216), /* ditto */
.flags = IORESOURCE_IRQ,
.name = "cw1200_wlan_irq",
},
};
#endif
static int cw1200_power_ctrl(const struct cw1200_platform_data_sdio *pdata,
bool enable)
{
/* Control 3v3 and 1v8 to hardware as appropriate */
/* Note this is not needed if it's controlled elsewhere or always on */
/* May require delay for power to stabilize */
return 0;
}
static int cw1200_clk_ctrl(const struct cw1200_platform_data_sdio *pdata,
bool enable)
{
/* Turn CLK_32K off and on as appropriate. */
/* Note this is not needed if it's always on */
/* May require delay for clock to stabilize */
return 0;
}
static struct cw1200_platform_data_sdio cw1200_platform_data = {
.ref_clk = 38400,
.have_5ghz = false,
#if 0
.reset = &cw1200_href_resources[0],
.powerup = &cw1200_href_resources[1],
.irq = &cw1200_href_resources[2],
#endif
.power_ctrl = cw1200_power_ctrl,
.clk_ctrl = cw1200_clk_ctrl,
/* .macaddr = ??? */
.sdd_file = "sdd_sagrad_1091_1098.bin",
};
#endif
#ifdef SAGRAD_1091_1098_EVK_SPI
/* Note that this is an example of integrating into your board support file */
static struct resource cw1200_href_resources[] = {
{
.start = GPIO_RF_RESET,
.end = GPIO_RF_RESET,
.flags = IORESOURCE_IO,
.name = "cw1200_wlan_reset",
},
{
.start = GPIO_RF_POWERUP,
.end = GPIO_RF_POWERUP,
.flags = IORESOURCE_IO,
.name = "cw1200_wlan_powerup",
},
};
static int cw1200_power_ctrl(const struct cw1200_platform_data_spi *pdata,
bool enable)
{
/* Control 3v3 and 1v8 to hardware as appropriate */
/* Note this is not needed if it's controlled elsewhere or always on */
/* May require delay for power to stabilize */
return 0;
}
static int cw1200_clk_ctrl(const struct cw1200_platform_data_spi *pdata,
bool enable)
{
/* Turn CLK_32K off and on as appropriate. */
/* Note this is not needed if it's always on */
/* May require delay for clock to stabilize */
return 0;
}
static struct cw1200_platform_data_spi cw1200_platform_data = {
.ref_clk = 38400,
.spi_bits_per_word = 16,
.reset = &cw1200_href_resources[0],
.powerup = &cw1200_href_resources[1],
.power_ctrl = cw1200_power_ctrl,
.clk_ctrl = cw1200_clk_ctrl,
/* .macaddr = ??? */
.sdd_file = "sdd_sagrad_1091_1098.bin",
};
static struct spi_board_info myboard_spi_devices[] __initdata = {
{
.modalias = "cw1200_wlan_spi",
.max_speed_hz = 10000000, /* 52MHz Max */
.bus_num = 0,
.irq = WIFI_IRQ,
.platform_data = &cw1200_platform_data,
.chip_select = 0,
},
};
#endif
const void *cw1200_get_platform_data(void)
{
return &cw1200_platform_data;
}
EXPORT_SYMBOL_GPL(cw1200_get_platform_data);
/*
* Mac80211 SDIO driver for ST-Ericsson CW1200 device
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/version.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/mmc/host.h>
#include <linux/mmc/sdio_func.h>
#include <linux/mmc/card.h>
#include <linux/mmc/sdio.h>
#include <net/mac80211.h>
#include "cw1200.h"
#include "sbus.h"
#include <linux/cw1200_platform.h>
#include "hwio.h"
MODULE_AUTHOR("Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>");
MODULE_DESCRIPTION("mac80211 ST-Ericsson CW1200 SDIO driver");
MODULE_LICENSE("GPL");
#define SDIO_BLOCK_SIZE (512)
struct sbus_priv {
struct sdio_func *func;
struct cw1200_common *core;
const struct cw1200_platform_data_sdio *pdata;
};
#ifndef SDIO_VENDOR_ID_STE
#define SDIO_VENDOR_ID_STE 0x0020
#endif
#ifndef SDIO_DEVICE_ID_STE_CW1200
#define SDIO_DEVICE_ID_STE_CW1200 0x2280
#endif
static const struct sdio_device_id cw1200_sdio_ids[] = {
{ SDIO_DEVICE(SDIO_VENDOR_ID_STE, SDIO_DEVICE_ID_STE_CW1200) },
{ /* end: all zeroes */ },
};
/* sbus_ops implemetation */
static int cw1200_sdio_memcpy_fromio(struct sbus_priv *self,
unsigned int addr,
void *dst, int count)
{
return sdio_memcpy_fromio(self->func, dst, addr, count);
}
static int cw1200_sdio_memcpy_toio(struct sbus_priv *self,
unsigned int addr,
const void *src, int count)
{
return sdio_memcpy_toio(self->func, addr, (void *)src, count);
}
static void cw1200_sdio_lock(struct sbus_priv *self)
{
sdio_claim_host(self->func);
}
static void cw1200_sdio_unlock(struct sbus_priv *self)
{
sdio_release_host(self->func);
}
static void cw1200_sdio_irq_handler(struct sdio_func *func)
{
struct sbus_priv *self = sdio_get_drvdata(func);
/* note: sdio_host already claimed here. */
if (self->core)
cw1200_irq_handler(self->core);
}
static irqreturn_t cw1200_gpio_hardirq(int irq, void *dev_id)
{
return IRQ_WAKE_THREAD;
}
static irqreturn_t cw1200_gpio_irq(int irq, void *dev_id)
{
struct sbus_priv *self = dev_id;
if (self->core) {
sdio_claim_host(self->func);
cw1200_irq_handler(self->core);
sdio_release_host(self->func);
return IRQ_HANDLED;
} else {
return IRQ_NONE;
}
}
static int cw1200_request_irq(struct sbus_priv *self)
{
int ret;
const struct resource *irq = self->pdata->irq;
u8 cccr;
cccr = sdio_f0_readb(self->func, SDIO_CCCR_IENx, &ret);
if (WARN_ON(ret))
goto err;
/* Master interrupt enable ... */
cccr |= BIT(0);
/* ... for our function */
cccr |= BIT(self->func->num);
sdio_f0_writeb(self->func, cccr, SDIO_CCCR_IENx, &ret);
if (WARN_ON(ret))
goto err;
ret = enable_irq_wake(irq->start);
if (WARN_ON(ret))
goto err;
/* Request the IRQ */
ret = request_threaded_irq(irq->start, cw1200_gpio_hardirq,
cw1200_gpio_irq,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
irq->name, self);
if (WARN_ON(ret))
goto err;
return 0;
err:
return ret;
}
static int cw1200_sdio_irq_subscribe(struct sbus_priv *self)
{
int ret = 0;
pr_debug("SW IRQ subscribe\n");
sdio_claim_host(self->func);
if (self->pdata->irq)
ret = cw1200_request_irq(self);
else
ret = sdio_claim_irq(self->func, cw1200_sdio_irq_handler);
sdio_release_host(self->func);
return ret;
}
static int cw1200_sdio_irq_unsubscribe(struct sbus_priv *self)
{
int ret = 0;
pr_debug("SW IRQ unsubscribe\n");
if (self->pdata->irq) {
disable_irq_wake(self->pdata->irq->start);
free_irq(self->pdata->irq->start, self);
} else {
sdio_claim_host(self->func);
ret = sdio_release_irq(self->func);
sdio_release_host(self->func);
}
return ret;
}
static int cw1200_sdio_off(const struct cw1200_platform_data_sdio *pdata)
{
const struct resource *reset = pdata->reset;
if (reset) {
gpio_set_value(reset->start, 0);
msleep(30); /* Min is 2 * CLK32K cycles */
gpio_free(reset->start);
}
if (pdata->power_ctrl)
pdata->power_ctrl(pdata, false);
if (pdata->clk_ctrl)
pdata->clk_ctrl(pdata, false);
return 0;
}
static int cw1200_sdio_on(const struct cw1200_platform_data_sdio *pdata)
{
const struct resource *reset = pdata->reset;
const struct resource *powerup = pdata->reset;
/* Ensure I/Os are pulled low */
if (reset) {
gpio_request(reset->start, reset->name);
gpio_direction_output(reset->start, 0);
}
if (powerup) {
gpio_request(powerup->start, powerup->name);
gpio_direction_output(powerup->start, 0);
}
if (reset || powerup)
msleep(50); /* Settle time */
/* Enable 3v3 and 1v8 to hardware */
if (pdata->power_ctrl) {
if (pdata->power_ctrl(pdata, true)) {
pr_err("power_ctrl() failed!\n");
return -1;
}
}
/* Enable CLK32K */
if (pdata->clk_ctrl) {
if (pdata->clk_ctrl(pdata, true)) {
pr_err("clk_ctrl() failed!\n");
return -1;
}
msleep(10); /* Delay until clock is stable for 2 cycles */
}
/* Enable POWERUP signal */
if (powerup) {
gpio_set_value(powerup->start, 1);
msleep(250); /* or more..? */
}
/* Enable RSTn signal */
if (reset) {
gpio_set_value(reset->start, 1);
msleep(50); /* Or more..? */
}
return 0;
}
static size_t cw1200_sdio_align_size(struct sbus_priv *self, size_t size)
{
if (self->pdata->no_nptb)
size = round_up(size, SDIO_BLOCK_SIZE);
else
size = sdio_align_size(self->func, size);
return size;
}
static int cw1200_sdio_pm(struct sbus_priv *self, bool suspend)
{
int ret = 0;
if (self->pdata->irq)
ret = irq_set_irq_wake(self->pdata->irq->start, suspend);
return ret;
}
static struct sbus_ops cw1200_sdio_sbus_ops = {
.sbus_memcpy_fromio = cw1200_sdio_memcpy_fromio,
.sbus_memcpy_toio = cw1200_sdio_memcpy_toio,
.lock = cw1200_sdio_lock,
.unlock = cw1200_sdio_unlock,
.align_size = cw1200_sdio_align_size,
.power_mgmt = cw1200_sdio_pm,
};
/* Probe Function to be called by SDIO stack when device is discovered */
static int cw1200_sdio_probe(struct sdio_func *func,
const struct sdio_device_id *id)
{
struct sbus_priv *self;
int status;
pr_info("cw1200_wlan_sdio: Probe called\n");
/* We are only able to handle the wlan function */
if (func->num != 0x01)
return -ENODEV;
self = kzalloc(sizeof(*self), GFP_KERNEL);
if (!self) {
pr_err("Can't allocate SDIO sbus_priv.\n");
return -ENOMEM;
}
func->card->quirks |= MMC_QUIRK_LENIENT_FN0;
self->pdata = cw1200_get_platform_data();
self->func = func;
sdio_set_drvdata(func, self);
sdio_claim_host(func);
sdio_enable_func(func);
sdio_release_host(func);
status = cw1200_sdio_irq_subscribe(self);
status = cw1200_core_probe(&cw1200_sdio_sbus_ops,
self, &func->dev, &self->core,
self->pdata->ref_clk,
self->pdata->macaddr,
self->pdata->sdd_file,
self->pdata->have_5ghz);
if (status) {
cw1200_sdio_irq_unsubscribe(self);
sdio_claim_host(func);
sdio_disable_func(func);
sdio_release_host(func);
sdio_set_drvdata(func, NULL);
kfree(self);
}
return status;
}
/* Disconnect Function to be called by SDIO stack when
* device is disconnected */
static void cw1200_sdio_disconnect(struct sdio_func *func)
{
struct sbus_priv *self = sdio_get_drvdata(func);
if (self) {
cw1200_sdio_irq_unsubscribe(self);
if (self->core) {
cw1200_core_release(self->core);
self->core = NULL;
}
sdio_claim_host(func);
sdio_disable_func(func);
sdio_release_host(func);
sdio_set_drvdata(func, NULL);
kfree(self);
}
}
static int cw1200_sdio_suspend(struct device *dev)
{
int ret;
struct sdio_func *func = dev_to_sdio_func(dev);
struct sbus_priv *self = sdio_get_drvdata(func);
if (!cw1200_can_suspend(self->core))
return -EAGAIN;
/* Notify SDIO that CW1200 will remain powered during suspend */
ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
if (ret)
pr_err("Error setting SDIO pm flags: %i\n", ret);
return ret;
}
static int cw1200_sdio_resume(struct device *dev)
{
return 0;
}
static const struct dev_pm_ops cw1200_pm_ops = {
.suspend = cw1200_sdio_suspend,
.resume = cw1200_sdio_resume,
};
static struct sdio_driver sdio_driver = {
.name = "cw1200_wlan_sdio",
.id_table = cw1200_sdio_ids,
.probe = cw1200_sdio_probe,
.remove = cw1200_sdio_disconnect,
.drv = {
.pm = &cw1200_pm_ops,
}
};
/* Init Module function -> Called by insmod */
static int __init cw1200_sdio_init(void)
{
const struct cw1200_platform_data_sdio *pdata;
int ret;
pdata = cw1200_get_platform_data();
if (cw1200_sdio_on(pdata)) {
ret = -1;
goto err;
}
ret = sdio_register_driver(&sdio_driver);
if (ret)
goto err;
return 0;
err:
cw1200_sdio_off(pdata);
return ret;
}
/* Called at Driver Unloading */
static void __exit cw1200_sdio_exit(void)
{
const struct cw1200_platform_data_sdio *pdata;
pdata = cw1200_get_platform_data();
sdio_unregister_driver(&sdio_driver);
cw1200_sdio_off(pdata);
}
module_init(cw1200_sdio_init);
module_exit(cw1200_sdio_exit);
/*
* Mac80211 SPI driver for ST-Ericsson CW1200 device
*
* Copyright (c) 2011, Sagrad Inc.
* Author: Solomon Peachy <speachy@sagrad.com>
*
* Based on cw1200_sdio.c
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/version.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <net/mac80211.h>
#include <linux/spi/spi.h>
#include <linux/device.h>
#include "cw1200.h"
#include "sbus.h"
#include <linux/cw1200_platform.h>
#include "hwio.h"
MODULE_AUTHOR("Solomon Peachy <speachy@sagrad.com>");
MODULE_DESCRIPTION("mac80211 ST-Ericsson CW1200 SPI driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("spi:cw1200_wlan_spi");
/* #define SPI_DEBUG */
struct sbus_priv {
struct spi_device *func;
struct cw1200_common *core;
const struct cw1200_platform_data_spi *pdata;
spinlock_t lock; /* Serialize all bus operations */
int claimed;
};
#define SDIO_TO_SPI_ADDR(addr) ((addr & 0x1f)>>2)
#define SET_WRITE 0x7FFF /* usage: and operation */
#define SET_READ 0x8000 /* usage: or operation */
/*
Notes on byte ordering:
LE: B0 B1 B2 B3
BE: B3 B2 B1 B0
Hardware expects 32-bit data to be written as 16-bit BE words:
B1 B0 B3 B2
*/
static int cw1200_spi_memcpy_fromio(struct sbus_priv *self,
unsigned int addr,
void *dst, int count)
{
int ret, i;
uint16_t regaddr;
struct spi_message m;
struct spi_transfer t_addr = {
.tx_buf = &regaddr,
.len = sizeof(regaddr),
};
struct spi_transfer t_msg = {
.rx_buf = dst,
.len = count,
};
regaddr = (SDIO_TO_SPI_ADDR(addr))<<12;
regaddr |= SET_READ;
regaddr |= (count>>1);
regaddr = cpu_to_le16(regaddr);
#ifdef SPI_DEBUG
pr_info("READ : %04d from 0x%02x (%04x)\n", count, addr,
le16_to_cpu(regaddr));
#endif
#if defined(__LITTLE_ENDIAN)
/* We have to byteswap if the SPI bus is limited to 8b operation */
if (self->func->bits_per_word == 8)
#endif
regaddr = swab16(regaddr);
spi_message_init(&m);
spi_message_add_tail(&t_addr, &m);
spi_message_add_tail(&t_msg, &m);
ret = spi_sync(self->func, &m);
#ifdef SPI_DEBUG
pr_info("READ : ");
for (i = 0; i < t_addr.len; i++)
printk("%02x ", ((u8 *)t_addr.tx_buf)[i]);
printk(" : ");
for (i = 0; i < t_msg.len; i++)
printk("%02x ", ((u8 *)t_msg.rx_buf)[i]);
printk("\n");
#endif
#if defined(__LITTLE_ENDIAN)
/* We have to byteswap if the SPI bus is limited to 8b operation */
if (self->func->bits_per_word == 8)
#endif
{
uint16_t *buf = (uint16_t *)dst;
for (i = 0; i < ((count + 1) >> 1); i++)
buf[i] = swab16(buf[i]);
}
return ret;
}
static int cw1200_spi_memcpy_toio(struct sbus_priv *self,
unsigned int addr,
const void *src, int count)
{
int rval, i;
uint16_t regaddr;
struct spi_transfer t_addr = {
.tx_buf = &regaddr,
.len = sizeof(regaddr),
};
struct spi_transfer t_msg = {
.tx_buf = src,
.len = count,
};
struct spi_message m;
regaddr = (SDIO_TO_SPI_ADDR(addr))<<12;
regaddr &= SET_WRITE;
regaddr |= (count>>1);
regaddr = cpu_to_le16(regaddr);
#ifdef SPI_DEBUG
pr_info("WRITE: %04d to 0x%02x (%04x)\n", count, addr,
le16_to_cpu(regaddr));
#endif
#if defined(__LITTLE_ENDIAN)
/* We have to byteswap if the SPI bus is limited to 8b operation */
if (self->func->bits_per_word == 8)
#endif
{
uint16_t *buf = (uint16_t *)src;
regaddr = swab16(regaddr);
for (i = 0; i < ((count + 1) >> 1); i++)
buf[i] = swab16(buf[i]);
}
#ifdef SPI_DEBUG
pr_info("WRITE: ");
for (i = 0; i < t_addr.len; i++)
printk("%02x ", ((u8 *)t_addr.tx_buf)[i]);
printk(" : ");
for (i = 0; i < t_msg.len; i++)
printk("%02x ", ((u8 *)t_msg.tx_buf)[i]);
printk("\n");
#endif
spi_message_init(&m);
spi_message_add_tail(&t_addr, &m);
spi_message_add_tail(&t_msg, &m);
rval = spi_sync(self->func, &m);
#ifdef SPI_DEBUG
pr_info("WROTE: %d\n", m.actual_length);
#endif
#if defined(__LITTLE_ENDIAN)
/* We have to byteswap if the SPI bus is limited to 8b operation */
if (self->func->bits_per_word == 8)
#endif
{
uint16_t *buf = (uint16_t *)src;
for (i = 0; i < ((count + 1) >> 1); i++)
buf[i] = swab16(buf[i]);
}
return rval;
}
static void cw1200_spi_lock(struct sbus_priv *self)
{
unsigned long flags;
might_sleep();
spin_lock_irqsave(&self->lock, flags);
while (1) {
set_current_state(TASK_UNINTERRUPTIBLE);
if (!self->claimed)
break;
spin_unlock_irqrestore(&self->lock, flags);
schedule();
spin_lock_irqsave(&self->lock, flags);
}
set_current_state(TASK_RUNNING);
self->claimed = 1;
spin_unlock_irqrestore(&self->lock, flags);
return;
}
static void cw1200_spi_unlock(struct sbus_priv *self)
{
unsigned long flags;
spin_lock_irqsave(&self->lock, flags);
self->claimed = 0;
spin_unlock_irqrestore(&self->lock, flags);
return;
}
static irqreturn_t cw1200_spi_irq_handler(int irq, void *dev_id)
{
struct sbus_priv *self = dev_id;
if (self->core) {
cw1200_irq_handler(self->core);
return IRQ_HANDLED;
} else {
return IRQ_NONE;
}
}
static int cw1200_spi_irq_subscribe(struct sbus_priv *self)
{
int ret;
pr_debug("SW IRQ subscribe\n");
ret = request_any_context_irq(self->func->irq, cw1200_spi_irq_handler,
IRQF_TRIGGER_HIGH,
"cw1200_wlan_irq", self);
if (WARN_ON(ret < 0))
goto exit;
ret = enable_irq_wake(self->func->irq);
if (WARN_ON(ret))
goto free_irq;
return 0;
free_irq:
free_irq(self->func->irq, self);
exit:
return ret;
}
static int cw1200_spi_irq_unsubscribe(struct sbus_priv *self)
{
int ret = 0;
pr_debug("SW IRQ unsubscribe\n");
disable_irq_wake(self->func->irq);
free_irq(self->func->irq, self);
return ret;
}
static int cw1200_spi_off(const struct cw1200_platform_data_spi *pdata)
{
const struct resource *reset = pdata->reset;
if (reset) {
gpio_set_value(reset->start, 0);
msleep(30); /* Min is 2 * CLK32K cycles */
gpio_free(reset->start);
}
if (pdata->power_ctrl)
pdata->power_ctrl(pdata, false);
if (pdata->clk_ctrl)
pdata->clk_ctrl(pdata, false);
return 0;
}
static int cw1200_spi_on(const struct cw1200_platform_data_spi *pdata)
{
const struct resource *reset = pdata->reset;
const struct resource *powerup = pdata->reset;
/* Ensure I/Os are pulled low */
if (reset) {
gpio_request(reset->start, reset->name);
gpio_direction_output(reset->start, 0);
}
if (powerup) {
gpio_request(powerup->start, powerup->name);
gpio_direction_output(powerup->start, 0);
}
if (reset || powerup)
msleep(10); /* Settle time? */
/* Enable 3v3 and 1v8 to hardware */
if (pdata->power_ctrl) {
if (pdata->power_ctrl(pdata, true)) {
pr_err("power_ctrl() failed!\n");
return -1;
}
}
/* Enable CLK32K */
if (pdata->clk_ctrl) {
if (pdata->clk_ctrl(pdata, true)) {
pr_err("clk_ctrl() failed!\n");
return -1;
}
msleep(10); /* Delay until clock is stable for 2 cycles */
}
/* Enable POWERUP signal */
if (powerup) {
gpio_set_value(powerup->start, 1);
msleep(250); /* or more..? */
}
/* Enable RSTn signal */
if (reset) {
gpio_set_value(reset->start, 1);
msleep(50); /* Or more..? */
}
return 0;
}
static size_t cw1200_spi_align_size(struct sbus_priv *self, size_t size)
{
return size & 1 ? size + 1 : size;
}
static int cw1200_spi_pm(struct sbus_priv *self, bool suspend)
{
return irq_set_irq_wake(self->func->irq, suspend);
}
static struct sbus_ops cw1200_spi_sbus_ops = {
.sbus_memcpy_fromio = cw1200_spi_memcpy_fromio,
.sbus_memcpy_toio = cw1200_spi_memcpy_toio,
.lock = cw1200_spi_lock,
.unlock = cw1200_spi_unlock,
.align_size = cw1200_spi_align_size,
.power_mgmt = cw1200_spi_pm,
};
/* Probe Function to be called by SPI stack when device is discovered */
static int cw1200_spi_probe(struct spi_device *func)
{
const struct cw1200_platform_data_spi *plat_data =
func->dev.platform_data;
struct sbus_priv *self;
int status;
/* Sanity check speed */
if (func->max_speed_hz > 52000000)
func->max_speed_hz = 52000000;
if (func->max_speed_hz < 1000000)
func->max_speed_hz = 1000000;
/* Fix up transfer size */
if (plat_data->spi_bits_per_word)
func->bits_per_word = plat_data->spi_bits_per_word;
if (!func->bits_per_word)
func->bits_per_word = 16;
/* And finally.. */
func->mode = SPI_MODE_0;
pr_info("cw1200_wlan_spi: Probe called (CS %d M %d BPW %d CLK %d)\n",
func->chip_select, func->mode, func->bits_per_word,
func->max_speed_hz);
if (cw1200_spi_on(plat_data)) {
pr_err("spi_on() failed!\n");
return -1;
}
if (spi_setup(func)) {
pr_err("spi_setup() failed!\n");
return -1;
}
self = kzalloc(sizeof(*self), GFP_KERNEL);
if (!self) {
pr_err("Can't allocate SPI sbus_priv.");
return -ENOMEM;
}
self->pdata = plat_data;
self->func = func;
spin_lock_init(&self->lock);
spi_set_drvdata(func, self);
status = cw1200_spi_irq_subscribe(self);
status = cw1200_core_probe(&cw1200_spi_sbus_ops,
self, &func->dev, &self->core,
self->pdata->ref_clk,
self->pdata->macaddr,
self->pdata->sdd_file,
self->pdata->have_5ghz);
if (status) {
cw1200_spi_irq_unsubscribe(self);
cw1200_spi_off(plat_data);
kfree(self);
}
return status;
}
/* Disconnect Function to be called by SPI stack when device is disconnected */
static int cw1200_spi_disconnect(struct spi_device *func)
{
struct sbus_priv *self = spi_get_drvdata(func);
if (self) {
cw1200_spi_irq_unsubscribe(self);
if (self->core) {
cw1200_core_release(self->core);
self->core = NULL;
}
kfree(self);
}
cw1200_spi_off(func->dev.platform_data);
return 0;
}
static int cw1200_spi_suspend(struct device *dev, pm_message_t state)
{
struct sbus_priv *self = spi_get_drvdata(to_spi_device(dev));
if (!cw1200_can_suspend(self->core))
return -EAGAIN;
/* XXX notify host that we have to keep CW1200 powered on? */
return 0;
}
static int cw1200_spi_resume(struct device *dev)
{
return 0;
}
static struct spi_driver spi_driver = {
.probe = cw1200_spi_probe,
.remove = cw1200_spi_disconnect,
.driver = {
.name = "cw1200_wlan_spi",
.bus = &spi_bus_type,
.owner = THIS_MODULE,
.suspend = cw1200_spi_suspend,
.resume = cw1200_spi_resume,
},
};
/* Init Module function -> Called by insmod */
static int __init cw1200_spi_init(void)
{
return spi_register_driver(&spi_driver);
}
/* Called at Driver Unloading */
static void __exit cw1200_spi_exit(void)
{
spi_unregister_driver(&spi_driver);
}
module_init(cw1200_spi_init);
module_exit(cw1200_spi_exit);
/*
* mac80211 glue code for mac80211 ST-Ericsson CW1200 drivers
* DebugFS code
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include "cw1200.h"
#include "debug.h"
#include "fwio.h"
/* join_status */
static const char * const cw1200_debug_join_status[] = {
"passive",
"monitor",
"station (joining)",
"station (not authenticated yet)",
"station",
"adhoc",
"access point",
};
/* WSM_JOIN_PREAMBLE_... */
static const char * const cw1200_debug_preamble[] = {
"long",
"short",
"long on 1 and 2 Mbps",
};
static const char * const cw1200_debug_link_id[] = {
"OFF",
"REQ",
"SOFT",
"HARD",
};
static const char *cw1200_debug_mode(int mode)
{
switch (mode) {
case NL80211_IFTYPE_UNSPECIFIED:
return "unspecified";
case NL80211_IFTYPE_MONITOR:
return "monitor";
case NL80211_IFTYPE_STATION:
return "station";
case NL80211_IFTYPE_ADHOC:
return "adhoc";
case NL80211_IFTYPE_MESH_POINT:
return "mesh point";
case NL80211_IFTYPE_AP:
return "access point";
case NL80211_IFTYPE_P2P_CLIENT:
return "p2p client";
case NL80211_IFTYPE_P2P_GO:
return "p2p go";
default:
return "unsupported";
}
}
static void cw1200_queue_status_show(struct seq_file *seq,
struct cw1200_queue *q)
{
int i;
seq_printf(seq, "Queue %d:\n", q->queue_id);
seq_printf(seq, " capacity: %zu\n", q->capacity);
seq_printf(seq, " queued: %zu\n", q->num_queued);
seq_printf(seq, " pending: %zu\n", q->num_pending);
seq_printf(seq, " sent: %zu\n", q->num_sent);
seq_printf(seq, " locked: %s\n", q->tx_locked_cnt ? "yes" : "no");
seq_printf(seq, " overfull: %s\n", q->overfull ? "yes" : "no");
seq_puts(seq, " link map: 0-> ");
for (i = 0; i < q->stats->map_capacity; ++i)
seq_printf(seq, "%.2d ", q->link_map_cache[i]);
seq_printf(seq, "<-%zu\n", q->stats->map_capacity);
}
static void cw1200_debug_print_map(struct seq_file *seq,
struct cw1200_common *priv,
const char *label,
u32 map)
{
int i;
seq_printf(seq, "%s0-> ", label);
for (i = 0; i < priv->tx_queue_stats.map_capacity; ++i)
seq_printf(seq, "%s ", (map & BIT(i)) ? "**" : "..");
seq_printf(seq, "<-%zu\n", priv->tx_queue_stats.map_capacity - 1);
}
static int cw1200_status_show(struct seq_file *seq, void *v)
{
int i;
struct list_head *item;
struct cw1200_common *priv = seq->private;
struct cw1200_debug_priv *d = priv->debug;
seq_puts(seq, "CW1200 Wireless LAN driver status\n");
seq_printf(seq, "Hardware: %d.%d\n",
priv->wsm_caps.hw_id,
priv->wsm_caps.hw_subid);
seq_printf(seq, "Firmware: %s %d.%d\n",
cw1200_fw_types[priv->wsm_caps.fw_type],
priv->wsm_caps.fw_ver,
priv->wsm_caps.fw_build);
seq_printf(seq, "FW API: %d\n",
priv->wsm_caps.fw_api);
seq_printf(seq, "FW caps: 0x%.4X\n",
priv->wsm_caps.fw_cap);
seq_printf(seq, "FW label: '%s'\n",
priv->wsm_caps.fw_label);
seq_printf(seq, "Mode: %s%s\n",
cw1200_debug_mode(priv->mode),
priv->listening ? " (listening)" : "");
seq_printf(seq, "Join state: %s\n",
cw1200_debug_join_status[priv->join_status]);
if (priv->channel)
seq_printf(seq, "Channel: %d%s\n",
priv->channel->hw_value,
priv->channel_switch_in_progress ?
" (switching)" : "");
if (priv->rx_filter.promiscuous)
seq_puts(seq, "Filter: promisc\n");
else if (priv->rx_filter.fcs)
seq_puts(seq, "Filter: fcs\n");
if (priv->rx_filter.bssid)
seq_puts(seq, "Filter: bssid\n");
if (!priv->disable_beacon_filter)
seq_puts(seq, "Filter: beacons\n");
if (priv->enable_beacon ||
priv->mode == NL80211_IFTYPE_AP ||
priv->mode == NL80211_IFTYPE_ADHOC ||
priv->mode == NL80211_IFTYPE_MESH_POINT ||
priv->mode == NL80211_IFTYPE_P2P_GO)
seq_printf(seq, "Beaconing: %s\n",
priv->enable_beacon ?
"enabled" : "disabled");
for (i = 0; i < 4; ++i)
seq_printf(seq, "EDCA(%d): %d, %d, %d, %d, %d\n", i,
priv->edca.params[i].cwmin,
priv->edca.params[i].cwmax,
priv->edca.params[i].aifns,
priv->edca.params[i].txop_limit,
priv->edca.params[i].max_rx_lifetime);
if (priv->join_status == CW1200_JOIN_STATUS_STA) {
static const char *pm_mode = "unknown";
switch (priv->powersave_mode.mode) {
case WSM_PSM_ACTIVE:
pm_mode = "off";
break;
case WSM_PSM_PS:
pm_mode = "on";
break;
case WSM_PSM_FAST_PS:
pm_mode = "dynamic";
break;
}
seq_printf(seq, "Preamble: %s\n",
cw1200_debug_preamble[priv->association_mode.preamble]);
seq_printf(seq, "AMPDU spcn: %d\n",
priv->association_mode.mpdu_start_spacing);
seq_printf(seq, "Basic rate: 0x%.8X\n",
le32_to_cpu(priv->association_mode.basic_rate_set));
seq_printf(seq, "Bss lost: %d beacons\n",
priv->bss_params.beacon_lost_count);
seq_printf(seq, "AID: %d\n",
priv->bss_params.aid);
seq_printf(seq, "Rates: 0x%.8X\n",
priv->bss_params.operational_rate_set);
seq_printf(seq, "Powersave: %s\n", pm_mode);
}
seq_printf(seq, "HT: %s\n",
cw1200_is_ht(&priv->ht_info) ? "on" : "off");
if (cw1200_is_ht(&priv->ht_info)) {
seq_printf(seq, "Greenfield: %s\n",
cw1200_ht_greenfield(&priv->ht_info) ? "yes" : "no");
seq_printf(seq, "AMPDU dens: %d\n",
cw1200_ht_ampdu_density(&priv->ht_info));
}
seq_printf(seq, "RSSI thold: %d\n",
priv->cqm_rssi_thold);
seq_printf(seq, "RSSI hyst: %d\n",
priv->cqm_rssi_hyst);
seq_printf(seq, "Long retr: %d\n",
priv->long_frame_max_tx_count);
seq_printf(seq, "Short retr: %d\n",
priv->short_frame_max_tx_count);
spin_lock_bh(&priv->tx_policy_cache.lock);
i = 0;
list_for_each(item, &priv->tx_policy_cache.used)
++i;
spin_unlock_bh(&priv->tx_policy_cache.lock);
seq_printf(seq, "RC in use: %d\n", i);
seq_puts(seq, "\n");
for (i = 0; i < 4; ++i) {
cw1200_queue_status_show(seq, &priv->tx_queue[i]);
seq_puts(seq, "\n");
}
cw1200_debug_print_map(seq, priv, "Link map: ",
priv->link_id_map);
cw1200_debug_print_map(seq, priv, "Asleep map: ",
priv->sta_asleep_mask);
cw1200_debug_print_map(seq, priv, "PSPOLL map: ",
priv->pspoll_mask);
seq_puts(seq, "\n");
for (i = 0; i < CW1200_MAX_STA_IN_AP_MODE; ++i) {
if (priv->link_id_db[i].status) {
seq_printf(seq, "Link %d: %s, %pM\n",
i + 1,
cw1200_debug_link_id[priv->link_id_db[i].status],
priv->link_id_db[i].mac);
}
}
seq_puts(seq, "\n");
seq_printf(seq, "BH status: %s\n",
atomic_read(&priv->bh_term) ? "terminated" : "alive");
seq_printf(seq, "Pending RX: %d\n",
atomic_read(&priv->bh_rx));
seq_printf(seq, "Pending TX: %d\n",
atomic_read(&priv->bh_tx));
if (priv->bh_error)
seq_printf(seq, "BH errcode: %d\n",
priv->bh_error);
seq_printf(seq, "TX bufs: %d x %d bytes\n",
priv->wsm_caps.input_buffers,
priv->wsm_caps.input_buffer_size);
seq_printf(seq, "Used bufs: %d\n",
priv->hw_bufs_used);
seq_printf(seq, "Powermgmt: %s\n",
priv->powersave_enabled ? "on" : "off");
seq_printf(seq, "Device: %s\n",
priv->device_can_sleep ? "asleep" : "awake");
spin_lock(&priv->wsm_cmd.lock);
seq_printf(seq, "WSM status: %s\n",
priv->wsm_cmd.done ? "idle" : "active");
seq_printf(seq, "WSM cmd: 0x%.4X (%td bytes)\n",
priv->wsm_cmd.cmd, priv->wsm_cmd.len);
seq_printf(seq, "WSM retval: %d\n",
priv->wsm_cmd.ret);
spin_unlock(&priv->wsm_cmd.lock);
seq_printf(seq, "Datapath: %s\n",
atomic_read(&priv->tx_lock) ? "locked" : "unlocked");
if (atomic_read(&priv->tx_lock))
seq_printf(seq, "TXlock cnt: %d\n",
atomic_read(&priv->tx_lock));
seq_printf(seq, "TXed: %d\n",
d->tx);
seq_printf(seq, "AGG TXed: %d\n",
d->tx_agg);
seq_printf(seq, "MULTI TXed: %d (%d)\n",
d->tx_multi, d->tx_multi_frames);
seq_printf(seq, "RXed: %d\n",
d->rx);
seq_printf(seq, "AGG RXed: %d\n",
d->rx_agg);
seq_printf(seq, "TX miss: %d\n",
d->tx_cache_miss);
seq_printf(seq, "TX align: %d\n",
d->tx_align);
seq_printf(seq, "TX burst: %d\n",
d->tx_burst);
seq_printf(seq, "TX TTL: %d\n",
d->tx_ttl);
seq_printf(seq, "Scan: %s\n",
atomic_read(&priv->scan.in_progress) ? "active" : "idle");
return 0;
}
static int cw1200_status_open(struct inode *inode, struct file *file)
{
return single_open(file, &cw1200_status_show,
inode->i_private);
}
static const struct file_operations fops_status = {
.open = cw1200_status_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.owner = THIS_MODULE,
};
static int cw1200_counters_show(struct seq_file *seq, void *v)
{
int ret;
struct cw1200_common *priv = seq->private;
struct wsm_mib_counters_table counters;
ret = wsm_get_counters_table(priv, &counters);
if (ret)
return ret;
#define PUT_COUNTER(tab, name) \
seq_printf(seq, "%s:" tab "%d\n", #name, \
__le32_to_cpu(counters.name))
PUT_COUNTER("\t\t", plcp_errors);
PUT_COUNTER("\t\t", fcs_errors);
PUT_COUNTER("\t\t", tx_packets);
PUT_COUNTER("\t\t", rx_packets);
PUT_COUNTER("\t\t", rx_packet_errors);
PUT_COUNTER("\t", rx_decryption_failures);
PUT_COUNTER("\t\t", rx_mic_failures);
PUT_COUNTER("\t", rx_no_key_failures);
PUT_COUNTER("\t", tx_multicast_frames);
PUT_COUNTER("\t", tx_frames_success);
PUT_COUNTER("\t", tx_frame_failures);
PUT_COUNTER("\t", tx_frames_retried);
PUT_COUNTER("\t", tx_frames_multi_retried);
PUT_COUNTER("\t", rx_frame_duplicates);
PUT_COUNTER("\t\t", rts_success);
PUT_COUNTER("\t\t", rts_failures);
PUT_COUNTER("\t\t", ack_failures);
PUT_COUNTER("\t", rx_multicast_frames);
PUT_COUNTER("\t", rx_frames_success);
PUT_COUNTER("\t", rx_cmac_icv_errors);
PUT_COUNTER("\t\t", rx_cmac_replays);
PUT_COUNTER("\t", rx_mgmt_ccmp_replays);
#undef PUT_COUNTER
return 0;
}
static int cw1200_counters_open(struct inode *inode, struct file *file)
{
return single_open(file, &cw1200_counters_show,
inode->i_private);
}
static const struct file_operations fops_counters = {
.open = cw1200_counters_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.owner = THIS_MODULE,
};
static int cw1200_generic_open(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
return 0;
}
#ifdef CONFIG_CW1200_ETF
static int cw1200_etf_out_show(struct seq_file *seq, void *v)
{
struct cw1200_common *priv = seq->private;
struct sk_buff *skb;
u32 len = 0;
skb = skb_dequeue(&priv->etf_q);
if (skb)
len = skb->len;
seq_write(seq, &len, sizeof(len));
if (skb) {
seq_write(seq, skb->data, len);
kfree_skb(skb);
}
return 0;
}
static int cw1200_etf_out_open(struct inode *inode, struct file *file)
{
return single_open(file, &cw1200_etf_out_show,
inode->i_private);
}
static const struct file_operations fops_etf_out = {
.open = cw1200_etf_out_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.owner = THIS_MODULE,
};
struct etf_req_msg;
static int etf_request(struct cw1200_common *priv,
struct etf_req_msg *msg, u32 len);
#define MAX_RX_SZE 2600
struct etf_in_state {
struct cw1200_common *priv;
u32 total_len;
u8 buf[MAX_RX_SZE];
u32 written;
};
static int cw1200_etf_in_open(struct inode *inode, struct file *file)
{
struct etf_in_state *etf = kmalloc(sizeof(struct etf_in_state),
GFP_KERNEL);
if (!etf)
return -ENOMEM;
etf->written = 0;
etf->total_len = 0;
etf->priv = inode->i_private;
file->private_data = etf;
return 0;
}
static int cw1200_etf_in_release(struct inode *inode, struct file *file)
{
kfree(file->private_data);
return 0;
}
static ssize_t cw1200_etf_in_write(struct file *file,
const char __user *user_buf, size_t count, loff_t *ppos)
{
struct etf_in_state *etf = file->private_data;
ssize_t written = 0;
if (!etf->total_len) {
if (count < sizeof(etf->total_len)) {
pr_err("count < sizeof(total_len)\n");
return -EINVAL;
}
if (copy_from_user(&etf->total_len, user_buf,
sizeof(etf->total_len))) {
pr_err("copy_from_user (len) failed\n");
return -EFAULT;
}
written += sizeof(etf->total_len);
count -= sizeof(etf->total_len);
}
if (!count)
goto done;
if (copy_from_user(etf->buf + etf->written, user_buf + written,
count)) {
pr_err("copy_from_user (payload %zu) failed\n", count);
return -EFAULT;
}
written += count;
etf->written += count;
if (etf->written >= etf->total_len) {
if (etf_request(etf->priv, (struct etf_req_msg *)etf->buf,
etf->total_len)) {
pr_err("etf_request failed\n");
return -EIO;
}
}
done:
return written;
}
static const struct file_operations fops_etf_in = {
.open = cw1200_etf_in_open,
.release = cw1200_etf_in_release,
.write = cw1200_etf_in_write,
.llseek = default_llseek,
.owner = THIS_MODULE,
};
#endif /* CONFIG_CW1200_ETF */
static ssize_t cw1200_wsm_dumps(struct file *file,
const char __user *user_buf, size_t count, loff_t *ppos)
{
struct cw1200_common *priv = file->private_data;
char buf[1];
if (!count)
return -EINVAL;
if (copy_from_user(buf, user_buf, 1))
return -EFAULT;
if (buf[0] == '1')
priv->wsm_enable_wsm_dumps = 1;
else
priv->wsm_enable_wsm_dumps = 0;
return count;
}
static const struct file_operations fops_wsm_dumps = {
.open = cw1200_generic_open,
.write = cw1200_wsm_dumps,
.llseek = default_llseek,
};
int cw1200_debug_init(struct cw1200_common *priv)
{
int ret = -ENOMEM;
struct cw1200_debug_priv *d = kzalloc(sizeof(struct cw1200_debug_priv),
GFP_KERNEL);
priv->debug = d;
if (!d)
return ret;
d->debugfs_phy = debugfs_create_dir("cw1200",
priv->hw->wiphy->debugfsdir);
if (!d->debugfs_phy)
goto err;
if (!debugfs_create_file("status", S_IRUSR, d->debugfs_phy,
priv, &fops_status))
goto err;
if (!debugfs_create_file("counters", S_IRUSR, d->debugfs_phy,
priv, &fops_counters))
goto err;
#ifdef CONFIG_CW1200_ETF
if (etf_mode) {
skb_queue_head_init(&priv->etf_q);
if (!debugfs_create_file("etf_out", S_IRUSR, d->debugfs_phy,
priv, &fops_etf_out))
goto err;
if (!debugfs_create_file("etf_in", S_IWUSR, d->debugfs_phy,
priv, &fops_etf_in))
goto err;
}
#endif /* CONFIG_CW1200_ETF */
if (!debugfs_create_file("wsm_dumps", S_IWUSR, d->debugfs_phy,
priv, &fops_wsm_dumps))
goto err;
ret = cw1200_itp_init(priv);
if (ret)
goto err;
return 0;
err:
priv->debug = NULL;
debugfs_remove_recursive(d->debugfs_phy);
kfree(d);
return ret;
}
void cw1200_debug_release(struct cw1200_common *priv)
{
struct cw1200_debug_priv *d = priv->debug;
if (d) {
cw1200_itp_release(priv);
priv->debug = NULL;
kfree(d);
}
}
#ifdef CONFIG_CW1200_ETF
struct cw1200_sdd {
u8 id;
u8 len;
u8 data[];
};
struct etf_req_msg {
u32 id;
u32 len;
u8 data[];
};
static int parse_sdd_file(struct cw1200_common *priv, u8 *data, u32 length)
{
struct cw1200_sdd *ie;
while (length > 0) {
ie = (struct cw1200_sdd *)data;
if (ie->id == SDD_REFERENCE_FREQUENCY_ELT_ID) {
priv->hw_refclk = cpu_to_le16(*((u16 *)ie->data));
pr_info("Using Reference clock frequency %d KHz\n",
priv->hw_refclk);
break;
}
length -= ie->len + sizeof(*ie);
data += ie->len + sizeof(*ie);
}
return 0;
}
char *etf_firmware;
#define ST90TDS_START_ADAPTER 0x09 /* Loads firmware too */
#define ST90TDS_STOP_ADAPTER 0x0A
#define ST90TDS_CONFIG_ADAPTER 0x0E /* Send configuration params */
#define ST90TDS_SBUS_READ 0x13
#define ST90TDS_SBUS_WRITE 0x14
#define ST90TDS_GET_DEVICE_OPTION 0x19
#define ST90TDS_SET_DEVICE_OPTION 0x1A
#define ST90TDS_SEND_SDD 0x1D /* SDD File used to find DPLL */
#include "fwio.h"
static int etf_request(struct cw1200_common *priv,
struct etf_req_msg *msg,
u32 len)
{
int rval = -1;
switch (msg->id) {
case ST90TDS_START_ADAPTER:
etf_firmware = "cw1200_etf.bin";
pr_info("ETF_START (len %d, '%s')\n", len, etf_firmware);
rval = cw1200_load_firmware(priv);
break;
case ST90TDS_STOP_ADAPTER:
pr_info("ETF_STOP (unhandled)\n");
break;
case ST90TDS_SEND_SDD:
pr_info("ETF_SDD\n");
rval = parse_sdd_file(priv, msg->data, msg->len);
break;
case ST90TDS_CONFIG_ADAPTER:
pr_info("ETF_CONFIG_ADAP (unhandled)\n");
break;
case ST90TDS_SBUS_READ:
pr_info("ETF_SBUS_READ (unhandled)\n");
break;
case ST90TDS_SBUS_WRITE:
pr_info("ETF_SBUS_WRITE (unhandled)\n");
break;
case ST90TDS_SET_DEVICE_OPTION:
pr_info("ETF_SET_DEV_OPT (unhandled)\n");
break;
default:
pr_info("ETF_PASSTHRU (0x%08x)\n", msg->id);
rval = wsm_raw_cmd(priv, (u8 *)msg, len);
break;
}
return rval;
}
#endif /* CONFIG_CW1200_ETF */
/*
* DebugFS code for ST-Ericsson CW1200 mac80211 driver
*
* Copyright (c) 2011, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef CW1200_DEBUG_H_INCLUDED
#define CW1200_DEBUG_H_INCLUDED
#include "itp.h"
struct cw1200_debug_priv {
struct dentry *debugfs_phy;
int tx;
int tx_agg;
int rx;
int rx_agg;
int tx_multi;
int tx_multi_frames;
int tx_cache_miss;
int tx_align;
int tx_ttl;
int tx_burst;
int ba_cnt;
int ba_acc;
int ba_cnt_rx;
int ba_acc_rx;
#ifdef CONFIG_CW1200_ITP
struct cw1200_itp itp;
#endif /* CONFIG_CW1200_ITP */
};
int cw1200_debug_init(struct cw1200_common *priv);
void cw1200_debug_release(struct cw1200_common *priv);
static inline void cw1200_debug_txed(struct cw1200_common *priv)
{
++priv->debug->tx;
}
static inline void cw1200_debug_txed_agg(struct cw1200_common *priv)
{
++priv->debug->tx_agg;
}
static inline void cw1200_debug_txed_multi(struct cw1200_common *priv,
int count)
{
++priv->debug->tx_multi;
priv->debug->tx_multi_frames += count;
}
static inline void cw1200_debug_rxed(struct cw1200_common *priv)
{
++priv->debug->rx;
}
static inline void cw1200_debug_rxed_agg(struct cw1200_common *priv)
{
++priv->debug->rx_agg;
}
static inline void cw1200_debug_tx_cache_miss(struct cw1200_common *priv)
{
++priv->debug->tx_cache_miss;
}
static inline void cw1200_debug_tx_align(struct cw1200_common *priv)
{
++priv->debug->tx_align;
}
static inline void cw1200_debug_tx_ttl(struct cw1200_common *priv)
{
++priv->debug->tx_ttl;
}
static inline void cw1200_debug_tx_burst(struct cw1200_common *priv)
{
++priv->debug->tx_burst;
}
static inline void cw1200_debug_ba(struct cw1200_common *priv,
int ba_cnt, int ba_acc,
int ba_cnt_rx, int ba_acc_rx)
{
priv->debug->ba_cnt = ba_cnt;
priv->debug->ba_acc = ba_acc;
priv->debug->ba_cnt_rx = ba_cnt_rx;
priv->debug->ba_acc_rx = ba_acc_rx;
}
#endif /* CW1200_DEBUG_H_INCLUDED */
/*
* Firmware I/O code for mac80211 ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Based on:
* ST-Ericsson UMAC CW1200 driver which is
* Copyright (c) 2010, ST-Ericsson
* Author: Ajitpal Singh <ajitpal.singh@stericsson.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/vmalloc.h>
#include <linux/sched.h>
#include <linux/firmware.h>
#include "cw1200.h"
#include "fwio.h"
#include "hwio.h"
#include "sbus.h"
#include "bh.h"
static int cw1200_get_hw_type(u32 config_reg_val, int *major_revision)
{
int hw_type = -1;
u32 silicon_type = (config_reg_val >> 24) & 0x7;
u32 silicon_vers = (config_reg_val >> 31) & 0x1;
switch (silicon_type) {
case 0x00:
*major_revision = 1;
hw_type = HIF_9000_SILICON_VERSATILE;
break;
case 0x01:
case 0x02: /* CW1x00 */
case 0x04: /* CW1x60 */
*major_revision = silicon_type;
if (silicon_vers)
hw_type = HIF_8601_VERSATILE;
else
hw_type = HIF_8601_SILICON;
break;
default:
break;
}
return hw_type;
}
static int cw1200_load_firmware_cw1200(struct cw1200_common *priv)
{
int ret, block, num_blocks;
unsigned i;
u32 val32;
u32 put = 0, get = 0;
u8 *buf = NULL;
const char *fw_path;
const struct firmware *firmware = NULL;
/* Macroses are local. */
#define APB_WRITE(reg, val) \
do { \
ret = cw1200_apb_write_32(priv, CW1200_APB(reg), (val)); \
if (ret < 0) \
goto error; \
} while (0)
#define APB_READ(reg, val) \
do { \
ret = cw1200_apb_read_32(priv, CW1200_APB(reg), &(val)); \
if (ret < 0) \
goto error; \
} while (0)
#define REG_WRITE(reg, val) \
do { \
ret = cw1200_reg_write_32(priv, (reg), (val)); \
if (ret < 0) \
goto error; \
} while (0)
#define REG_READ(reg, val) \
do { \
ret = cw1200_reg_read_32(priv, (reg), &(val)); \
if (ret < 0) \
goto error; \
} while (0)
switch (priv->hw_revision) {
case CW1200_HW_REV_CUT10:
fw_path = FIRMWARE_CUT10;
if (!priv->sdd_path)
priv->sdd_path = SDD_FILE_10;
break;
case CW1200_HW_REV_CUT11:
fw_path = FIRMWARE_CUT11;
if (!priv->sdd_path)
priv->sdd_path = SDD_FILE_11;
break;
case CW1200_HW_REV_CUT20:
fw_path = FIRMWARE_CUT20;
if (!priv->sdd_path)
priv->sdd_path = SDD_FILE_20;
break;
case CW1200_HW_REV_CUT22:
fw_path = FIRMWARE_CUT22;
if (!priv->sdd_path)
priv->sdd_path = SDD_FILE_22;
break;
case CW1X60_HW_REV:
fw_path = FIRMWARE_CW1X60;
if (!priv->sdd_path)
priv->sdd_path = SDD_FILE_CW1X60;
break;
default:
pr_err("Invalid silicon revision %d.\n", priv->hw_revision);
return -EINVAL;
}
/* Initialize common registers */
APB_WRITE(DOWNLOAD_IMAGE_SIZE_REG, DOWNLOAD_ARE_YOU_HERE);
APB_WRITE(DOWNLOAD_PUT_REG, 0);
APB_WRITE(DOWNLOAD_GET_REG, 0);
APB_WRITE(DOWNLOAD_STATUS_REG, DOWNLOAD_PENDING);
APB_WRITE(DOWNLOAD_FLAGS_REG, 0);
/* Write the NOP Instruction */
REG_WRITE(ST90TDS_SRAM_BASE_ADDR_REG_ID, 0xFFF20000);
REG_WRITE(ST90TDS_AHB_DPORT_REG_ID, 0xEAFFFFFE);
/* Release CPU from RESET */
REG_READ(ST90TDS_CONFIG_REG_ID, val32);
val32 &= ~ST90TDS_CONFIG_CPU_RESET_BIT;
REG_WRITE(ST90TDS_CONFIG_REG_ID, val32);
/* Enable Clock */
val32 &= ~ST90TDS_CONFIG_CPU_CLK_DIS_BIT;
REG_WRITE(ST90TDS_CONFIG_REG_ID, val32);
#ifdef CONFIG_CW1200_ETF
if (etf_mode)
fw_path = etf_firmware;
#endif
/* Load a firmware file */
ret = request_firmware(&firmware, fw_path, priv->pdev);
if (ret) {
pr_err("Can't load firmware file %s.\n", fw_path);
goto error;
}
buf = kmalloc(DOWNLOAD_BLOCK_SIZE, GFP_KERNEL | GFP_DMA);
if (!buf) {
pr_err("Can't allocate firmware load buffer.\n");
ret = -ENOMEM;
goto error;
}
/* Check if the bootloader is ready */
for (i = 0; i < 100; i += 1 + i / 2) {
APB_READ(DOWNLOAD_IMAGE_SIZE_REG, val32);
if (val32 == DOWNLOAD_I_AM_HERE)
break;
mdelay(i);
} /* End of for loop */
if (val32 != DOWNLOAD_I_AM_HERE) {
pr_err("Bootloader is not ready.\n");
ret = -ETIMEDOUT;
goto error;
}
/* Calculcate number of download blocks */
num_blocks = (firmware->size - 1) / DOWNLOAD_BLOCK_SIZE + 1;
/* Updating the length in Download Ctrl Area */
val32 = firmware->size; /* Explicit cast from size_t to u32 */
APB_WRITE(DOWNLOAD_IMAGE_SIZE_REG, val32);
/* Firmware downloading loop */
for (block = 0; block < num_blocks; block++) {
size_t tx_size;
size_t block_size;
/* check the download status */
APB_READ(DOWNLOAD_STATUS_REG, val32);
if (val32 != DOWNLOAD_PENDING) {
pr_err("Bootloader reported error %d.\n", val32);
ret = -EIO;
goto error;
}
/* loop until put - get <= 24K */
for (i = 0; i < 100; i++) {
APB_READ(DOWNLOAD_GET_REG, get);
if ((put - get) <=
(DOWNLOAD_FIFO_SIZE - DOWNLOAD_BLOCK_SIZE))
break;
mdelay(i);
}
if ((put - get) > (DOWNLOAD_FIFO_SIZE - DOWNLOAD_BLOCK_SIZE)) {
pr_err("Timeout waiting for FIFO.\n");
ret = -ETIMEDOUT;
goto error;
}
/* calculate the block size */
tx_size = block_size = min((size_t)(firmware->size - put),
(size_t)DOWNLOAD_BLOCK_SIZE);
memcpy(buf, &firmware->data[put], block_size);
if (block_size < DOWNLOAD_BLOCK_SIZE) {
memset(&buf[block_size], 0,
DOWNLOAD_BLOCK_SIZE - block_size);
tx_size = DOWNLOAD_BLOCK_SIZE;
}
/* send the block to sram */
ret = cw1200_apb_write(priv,
CW1200_APB(DOWNLOAD_FIFO_OFFSET +
(put & (DOWNLOAD_FIFO_SIZE - 1))),
buf, tx_size);
if (ret < 0) {
pr_err("Can't write firmware block @ %d!\n",
put & (DOWNLOAD_FIFO_SIZE - 1));
goto error;
}
/* update the put register */
put += block_size;
APB_WRITE(DOWNLOAD_PUT_REG, put);
} /* End of firmware download loop */
/* Wait for the download completion */
for (i = 0; i < 300; i += 1 + i / 2) {
APB_READ(DOWNLOAD_STATUS_REG, val32);
if (val32 != DOWNLOAD_PENDING)
break;
mdelay(i);
}
if (val32 != DOWNLOAD_SUCCESS) {
pr_err("Wait for download completion failed: 0x%.8X\n", val32);
ret = -ETIMEDOUT;
goto error;
} else {
pr_info("Firmware download completed.\n");
ret = 0;
}
error:
kfree(buf);
if (firmware)
release_firmware(firmware);
return ret;
#undef APB_WRITE
#undef APB_READ
#undef REG_WRITE
#undef REG_READ
}
static int config_reg_read(struct cw1200_common *priv, u32 *val)
{
switch (priv->hw_type) {
case HIF_9000_SILICON_VERSATILE: {
u16 val16;
int ret = cw1200_reg_read_16(priv,
ST90TDS_CONFIG_REG_ID,
&val16);
if (ret < 0)
return ret;
*val = val16;
return 0;
}
case HIF_8601_VERSATILE:
case HIF_8601_SILICON:
default:
cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, val);
break;
}
return 0;
}
static int config_reg_write(struct cw1200_common *priv, u32 val)
{
switch (priv->hw_type) {
case HIF_9000_SILICON_VERSATILE:
return cw1200_reg_write_16(priv,
ST90TDS_CONFIG_REG_ID,
(u16)val);
case HIF_8601_VERSATILE:
case HIF_8601_SILICON:
default:
return cw1200_reg_write_32(priv, ST90TDS_CONFIG_REG_ID, val);
break;
}
return 0;
}
int cw1200_load_firmware(struct cw1200_common *priv)
{
int ret;
int i;
u32 val32;
u16 val16;
int major_revision = -1;
/* Read CONFIG Register */
ret = cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32);
if (ret < 0) {
pr_err("Can't read config register.\n");
goto out;
}
if (val32 == 0 || val32 == 0xffffffff) {
pr_err("Bad config register value (0x%08x)\n", val32);
ret = -EIO;
goto out;
}
priv->hw_type = cw1200_get_hw_type(val32, &major_revision);
if (priv->hw_type < 0) {
pr_err("Can't deduce hardware type.\n");
ret = -ENOTSUPP;
goto out;
}
/* Set DPLL Reg value, and read back to confirm writes work */
ret = cw1200_reg_write_32(priv, ST90TDS_TSET_GEN_R_W_REG_ID,
cw1200_dpll_from_clk(priv->hw_refclk));
if (ret < 0) {
pr_err("Can't write DPLL register.\n");
goto out;
}
msleep(20);
ret = cw1200_reg_read_32(priv,
ST90TDS_TSET_GEN_R_W_REG_ID, &val32);
if (ret < 0) {
pr_err("Can't read DPLL register.\n");
goto out;
}
if (val32 != cw1200_dpll_from_clk(priv->hw_refclk)) {
pr_err("Unable to initialise DPLL register. Wrote 0x%.8X, Read 0x%.8X.\n",
cw1200_dpll_from_clk(priv->hw_refclk), val32);
ret = -EIO;
goto out;
}
/* Set wakeup bit in device */
ret = cw1200_reg_read_16(priv, ST90TDS_CONTROL_REG_ID, &val16);
if (ret < 0) {
pr_err("set_wakeup: can't read control register.\n");
goto out;
}
ret = cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID,
val16 | ST90TDS_CONT_WUP_BIT);
if (ret < 0) {
pr_err("set_wakeup: can't write control register.\n");
goto out;
}
/* Wait for wakeup */
for (i = 0; i < 300; i += (1 + i / 2)) {
ret = cw1200_reg_read_16(priv,
ST90TDS_CONTROL_REG_ID, &val16);
if (ret < 0) {
pr_err("wait_for_wakeup: can't read control register.\n");
goto out;
}
if (val16 & ST90TDS_CONT_RDY_BIT)
break;
msleep(i);
}
if ((val16 & ST90TDS_CONT_RDY_BIT) == 0) {
pr_err("wait_for_wakeup: device is not responding.\n");
ret = -ETIMEDOUT;
goto out;
}
switch (major_revision) {
case 1:
/* CW1200 Hardware detection logic : Check for CUT1.1 */
ret = cw1200_ahb_read_32(priv, CW1200_CUT_ID_ADDR, &val32);
if (ret) {
pr_err("HW detection: can't read CUT ID.\n");
goto out;
}
switch (val32) {
case CW1200_CUT_11_ID_STR:
pr_info("CW1x00 Cut 1.1 silicon detected.\n");
priv->hw_revision = CW1200_HW_REV_CUT11;
break;
default:
pr_info("CW1x00 Cut 1.0 silicon detected.\n");
priv->hw_revision = CW1200_HW_REV_CUT10;
break;
}
/* According to ST-E, CUT<2.0 has busted BA TID0-3.
Just disable it entirely...
*/
priv->ba_rx_tid_mask = 0;
priv->ba_tx_tid_mask = 0;
break;
case 2: {
u32 ar1, ar2, ar3;
ret = cw1200_ahb_read_32(priv, CW1200_CUT2_ID_ADDR, &ar1);
if (ret) {
pr_err("(1) HW detection: can't read CUT ID\n");
goto out;
}
ret = cw1200_ahb_read_32(priv, CW1200_CUT2_ID_ADDR + 4, &ar2);
if (ret) {
pr_err("(2) HW detection: can't read CUT ID.\n");
goto out;
}
ret = cw1200_ahb_read_32(priv, CW1200_CUT2_ID_ADDR + 8, &ar3);
if (ret) {
pr_err("(3) HW detection: can't read CUT ID.\n");
goto out;
}
if (ar1 == CW1200_CUT_22_ID_STR1 &&
ar2 == CW1200_CUT_22_ID_STR2 &&
ar3 == CW1200_CUT_22_ID_STR3) {
pr_info("CW1x00 Cut 2.2 silicon detected.\n");
priv->hw_revision = CW1200_HW_REV_CUT22;
} else {
pr_info("CW1x00 Cut 2.0 silicon detected.\n");
priv->hw_revision = CW1200_HW_REV_CUT20;
}
break;
}
case 4:
pr_info("CW1x60 silicon detected.\n");
priv->hw_revision = CW1X60_HW_REV;
break;
default:
pr_err("Unsupported silicon major revision %d.\n",
major_revision);
ret = -ENOTSUPP;
goto out;
}
/* Checking for access mode */
ret = config_reg_read(priv, &val32);
if (ret < 0) {
pr_err("Can't read config register.\n");
goto out;
}
if (!(val32 & ST90TDS_CONFIG_ACCESS_MODE_BIT)) {
pr_err("Device is already in QUEUE mode!\n");
ret = -EINVAL;
goto out;
}
switch (priv->hw_type) {
case HIF_8601_SILICON:
if (priv->hw_revision == CW1X60_HW_REV) {
pr_err("Can't handle CW1160/1260 firmware load yet.\n");
ret = -ENOTSUPP;
goto out;
}
ret = cw1200_load_firmware_cw1200(priv);
break;
default:
pr_err("Can't perform firmware load for hw type %d.\n",
priv->hw_type);
ret = -ENOTSUPP;
goto out;
}
if (ret < 0) {
pr_err("Firmware load error.\n");
goto out;
}
/* Enable interrupt signalling */
priv->sbus_ops->lock(priv->sbus_priv);
ret = __cw1200_irq_enable(priv, 1);
priv->sbus_ops->unlock(priv->sbus_priv);
if (ret < 0)
goto unsubscribe;
/* Configure device for MESSSAGE MODE */
ret = config_reg_read(priv, &val32);
if (ret < 0) {
pr_err("Can't read config register.\n");
goto unsubscribe;
}
ret = config_reg_write(priv, val32 & ~ST90TDS_CONFIG_ACCESS_MODE_BIT);
if (ret < 0) {
pr_err("Can't write config register.\n");
goto unsubscribe;
}
/* Unless we read the CONFIG Register we are
* not able to get an interrupt
*/
mdelay(10);
config_reg_read(priv, &val32);
out:
return ret;
unsubscribe:
/* Disable interrupt signalling */
priv->sbus_ops->lock(priv->sbus_priv);
ret = __cw1200_irq_enable(priv, 0);
priv->sbus_ops->unlock(priv->sbus_priv);
return ret;
}
/*
* Firmware API for mac80211 ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Based on:
* ST-Ericsson UMAC CW1200 driver which is
* Copyright (c) 2010, ST-Ericsson
* Author: Ajitpal Singh <ajitpal.singh@stericsson.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef FWIO_H_INCLUDED
#define FWIO_H_INCLUDED
#define BOOTLOADER_CW1X60 "boot_cw1x60.bin"
#define FIRMWARE_CW1X60 "wsm_cw1x60.bin"
#define FIRMWARE_CUT22 "wsm_22.bin"
#define FIRMWARE_CUT20 "wsm_20.bin"
#define FIRMWARE_CUT11 "wsm_11.bin"
#define FIRMWARE_CUT10 "wsm_10.bin"
#define SDD_FILE_CW1X60 "sdd_cw1x60.bin"
#define SDD_FILE_22 "sdd_22.bin"
#define SDD_FILE_20 "sdd_20.bin"
#define SDD_FILE_11 "sdd_11.bin"
#define SDD_FILE_10 "sdd_10.bin"
int cw1200_load_firmware(struct cw1200_common *priv);
/* SDD definitions */
#define SDD_PTA_CFG_ELT_ID 0xEB
#define SDD_REFERENCE_FREQUENCY_ELT_ID 0xc5
u32 cw1200_dpll_from_clk(u16 clk);
#endif
/*
* Low-level device IO routines for ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Based on:
* ST-Ericsson UMAC CW1200 driver, which is
* Copyright (c) 2010, ST-Ericsson
* Author: Ajitpal Singh <ajitpal.singh@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/types.h>
#include "cw1200.h"
#include "hwio.h"
#include "sbus.h"
/* Sdio addr is 4*spi_addr */
#define SPI_REG_ADDR_TO_SDIO(spi_reg_addr) ((spi_reg_addr) << 2)
#define SDIO_ADDR17BIT(buf_id, mpf, rfu, reg_id_ofs) \
((((buf_id) & 0x1F) << 7) \
| (((mpf) & 1) << 6) \
| (((rfu) & 1) << 5) \
| (((reg_id_ofs) & 0x1F) << 0))
#define MAX_RETRY 3
static int __cw1200_reg_read(struct cw1200_common *priv, u16 addr,
void *buf, size_t buf_len, int buf_id)
{
u16 addr_sdio;
u32 sdio_reg_addr_17bit;
/* Check if buffer is aligned to 4 byte boundary */
if (WARN_ON(((unsigned long)buf & 3) && (buf_len > 4))) {
pr_err("buffer is not aligned.\n");
return -EINVAL;
}
/* Convert to SDIO Register Address */
addr_sdio = SPI_REG_ADDR_TO_SDIO(addr);
sdio_reg_addr_17bit = SDIO_ADDR17BIT(buf_id, 0, 0, addr_sdio);
return priv->sbus_ops->sbus_memcpy_fromio(priv->sbus_priv,
sdio_reg_addr_17bit,
buf, buf_len);
}
static int __cw1200_reg_write(struct cw1200_common *priv, u16 addr,
const void *buf, size_t buf_len, int buf_id)
{
u16 addr_sdio;
u32 sdio_reg_addr_17bit;
/* Convert to SDIO Register Address */
addr_sdio = SPI_REG_ADDR_TO_SDIO(addr);
sdio_reg_addr_17bit = SDIO_ADDR17BIT(buf_id, 0, 0, addr_sdio);
return priv->sbus_ops->sbus_memcpy_toio(priv->sbus_priv,
sdio_reg_addr_17bit,
buf, buf_len);
}
static inline int __cw1200_reg_read_32(struct cw1200_common *priv,
u16 addr, u32 *val)
{
int i = __cw1200_reg_read(priv, addr, val, sizeof(*val), 0);
*val = le32_to_cpu(*val);
return i;
}
static inline int __cw1200_reg_write_32(struct cw1200_common *priv,
u16 addr, u32 val)
{
val = cpu_to_le32(val);
return __cw1200_reg_write(priv, addr, &val, sizeof(val), 0);
}
static inline int __cw1200_reg_read_16(struct cw1200_common *priv,
u16 addr, u16 *val)
{
int i = __cw1200_reg_read(priv, addr, val, sizeof(*val), 0);
*val = le16_to_cpu(*val);
return i;
}
static inline int __cw1200_reg_write_16(struct cw1200_common *priv,
u16 addr, u16 val)
{
val = cpu_to_le16(val);
return __cw1200_reg_write(priv, addr, &val, sizeof(val), 0);
}
int cw1200_reg_read(struct cw1200_common *priv, u16 addr, void *buf,
size_t buf_len)
{
int ret;
priv->sbus_ops->lock(priv->sbus_priv);
ret = __cw1200_reg_read(priv, addr, buf, buf_len, 0);
priv->sbus_ops->unlock(priv->sbus_priv);
return ret;
}
int cw1200_reg_write(struct cw1200_common *priv, u16 addr, const void *buf,
size_t buf_len)
{
int ret;
priv->sbus_ops->lock(priv->sbus_priv);
ret = __cw1200_reg_write(priv, addr, buf, buf_len, 0);
priv->sbus_ops->unlock(priv->sbus_priv);
return ret;
}
int cw1200_data_read(struct cw1200_common *priv, void *buf, size_t buf_len)
{
int ret, retry = 1;
int buf_id_rx = priv->buf_id_rx;
priv->sbus_ops->lock(priv->sbus_priv);
while (retry <= MAX_RETRY) {
ret = __cw1200_reg_read(priv,
ST90TDS_IN_OUT_QUEUE_REG_ID, buf,
buf_len, buf_id_rx + 1);
if (!ret) {
buf_id_rx = (buf_id_rx + 1) & 3;
priv->buf_id_rx = buf_id_rx;
break;
} else {
retry++;
mdelay(1);
pr_err("error :[%d]\n", ret);
}
}
priv->sbus_ops->unlock(priv->sbus_priv);
return ret;
}
int cw1200_data_write(struct cw1200_common *priv, const void *buf,
size_t buf_len)
{
int ret, retry = 1;
int buf_id_tx = priv->buf_id_tx;
priv->sbus_ops->lock(priv->sbus_priv);
while (retry <= MAX_RETRY) {
ret = __cw1200_reg_write(priv,
ST90TDS_IN_OUT_QUEUE_REG_ID, buf,
buf_len, buf_id_tx);
if (!ret) {
buf_id_tx = (buf_id_tx + 1) & 31;
priv->buf_id_tx = buf_id_tx;
break;
} else {
retry++;
mdelay(1);
pr_err("error :[%d]\n", ret);
}
}
priv->sbus_ops->unlock(priv->sbus_priv);
return ret;
}
int cw1200_indirect_read(struct cw1200_common *priv, u32 addr, void *buf,
size_t buf_len, u32 prefetch, u16 port_addr)
{
u32 val32 = 0;
int i, ret;
if ((buf_len / 2) >= 0x1000) {
pr_err("Can't read more than 0xfff words.\n");
return -EINVAL;
goto out;
}
priv->sbus_ops->lock(priv->sbus_priv);
/* Write address */
ret = __cw1200_reg_write_32(priv, ST90TDS_SRAM_BASE_ADDR_REG_ID, addr);
if (ret < 0) {
pr_err("Can't write address register.\n");
goto out;
}
/* Read CONFIG Register Value - We will read 32 bits */
ret = __cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32);
if (ret < 0) {
pr_err("Can't read config register.\n");
goto out;
}
/* Set PREFETCH bit */
ret = __cw1200_reg_write_32(priv, ST90TDS_CONFIG_REG_ID,
val32 | prefetch);
if (ret < 0) {
pr_err("Can't write prefetch bit.\n");
goto out;
}
/* Check for PRE-FETCH bit to be cleared */
for (i = 0; i < 20; i++) {
ret = __cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32);
if (ret < 0) {
pr_err("Can't check prefetch bit.\n");
goto out;
}
if (!(val32 & prefetch))
break;
mdelay(i);
}
if (val32 & prefetch) {
pr_err("Prefetch bit is not cleared.\n");
goto out;
}
/* Read data port */
ret = __cw1200_reg_read(priv, port_addr, buf, buf_len, 0);
if (ret < 0) {
pr_err("Can't read data port.\n");
goto out;
}
out:
priv->sbus_ops->unlock(priv->sbus_priv);
return ret;
}
int cw1200_apb_write(struct cw1200_common *priv, u32 addr, const void *buf,
size_t buf_len)
{
int ret;
if ((buf_len / 2) >= 0x1000) {
pr_err("Can't write more than 0xfff words.\n");
return -EINVAL;
}
priv->sbus_ops->lock(priv->sbus_priv);
/* Write address */
ret = __cw1200_reg_write_32(priv, ST90TDS_SRAM_BASE_ADDR_REG_ID, addr);
if (ret < 0) {
pr_err("Can't write address register.\n");
goto out;
}
/* Write data port */
ret = __cw1200_reg_write(priv, ST90TDS_SRAM_DPORT_REG_ID,
buf, buf_len, 0);
if (ret < 0) {
pr_err("Can't write data port.\n");
goto out;
}
out:
priv->sbus_ops->unlock(priv->sbus_priv);
return ret;
}
int __cw1200_irq_enable(struct cw1200_common *priv, int enable)
{
u32 val32;
u16 val16;
int ret;
if (HIF_8601_SILICON == priv->hw_type) {
ret = __cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32);
if (ret < 0) {
pr_err("Can't read config register.\n");
return ret;
}
if (enable)
val32 |= ST90TDS_CONF_IRQ_RDY_ENABLE;
else
val32 &= ~ST90TDS_CONF_IRQ_RDY_ENABLE;
ret = __cw1200_reg_write_32(priv, ST90TDS_CONFIG_REG_ID, val32);
if (ret < 0) {
pr_err("Can't write config register.\n");
return ret;
}
} else {
ret = __cw1200_reg_read_16(priv, ST90TDS_CONFIG_REG_ID, &val16);
if (ret < 0) {
pr_err("Can't read control register.\n");
return ret;
}
if (enable)
val16 |= ST90TDS_CONT_IRQ_RDY_ENABLE;
else
val16 &= ~ST90TDS_CONT_IRQ_RDY_ENABLE;
ret = __cw1200_reg_write_16(priv, ST90TDS_CONFIG_REG_ID, val16);
if (ret < 0) {
pr_err("Can't write control register.\n");
return ret;
}
}
return 0;
}
/*
* Low-level API for mac80211 ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Based on:
* ST-Ericsson UMAC CW1200 driver which is
* Copyright (c) 2010, ST-Ericsson
* Author: Ajitpal Singh <ajitpal.singh@stericsson.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef CW1200_HWIO_H_INCLUDED
#define CW1200_HWIO_H_INCLUDED
/* extern */ struct cw1200_common;
#define CW1200_CUT_11_ID_STR (0x302E3830)
#define CW1200_CUT_22_ID_STR1 (0x302e3132)
#define CW1200_CUT_22_ID_STR2 (0x32302e30)
#define CW1200_CUT_22_ID_STR3 (0x3335)
#define CW1200_CUT_ID_ADDR (0xFFF17F90)
#define CW1200_CUT2_ID_ADDR (0xFFF1FF90)
/* Download control area */
/* boot loader start address in SRAM */
#define DOWNLOAD_BOOT_LOADER_OFFSET (0x00000000)
/* 32K, 0x4000 to 0xDFFF */
#define DOWNLOAD_FIFO_OFFSET (0x00004000)
/* 32K */
#define DOWNLOAD_FIFO_SIZE (0x00008000)
/* 128 bytes, 0xFF80 to 0xFFFF */
#define DOWNLOAD_CTRL_OFFSET (0x0000FF80)
#define DOWNLOAD_CTRL_DATA_DWORDS (32-6)
struct download_cntl_t {
/* size of whole firmware file (including Cheksum), host init */
u32 image_size;
/* downloading flags */
u32 flags;
/* No. of bytes put into the download, init & updated by host */
u32 put;
/* last traced program counter, last ARM reg_pc */
u32 trace_pc;
/* No. of bytes read from the download, host init, device updates */
u32 get;
/* r0, boot losader status, host init to pending, device updates */
u32 status;
/* Extra debug info, r1 to r14 if status=r0=DOWNLOAD_EXCEPTION */
u32 debug_data[DOWNLOAD_CTRL_DATA_DWORDS];
};
#define DOWNLOAD_IMAGE_SIZE_REG \
(DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, image_size))
#define DOWNLOAD_FLAGS_REG \
(DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, flags))
#define DOWNLOAD_PUT_REG \
(DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, put))
#define DOWNLOAD_TRACE_PC_REG \
(DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, trace_pc))
#define DOWNLOAD_GET_REG \
(DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, get))
#define DOWNLOAD_STATUS_REG \
(DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, status))
#define DOWNLOAD_DEBUG_DATA_REG \
(DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, debug_data))
#define DOWNLOAD_DEBUG_DATA_LEN (108)
#define DOWNLOAD_BLOCK_SIZE (1024)
/* For boot loader detection */
#define DOWNLOAD_ARE_YOU_HERE (0x87654321)
#define DOWNLOAD_I_AM_HERE (0x12345678)
/* Download error code */
#define DOWNLOAD_PENDING (0xFFFFFFFF)
#define DOWNLOAD_SUCCESS (0)
#define DOWNLOAD_EXCEPTION (1)
#define DOWNLOAD_ERR_MEM_1 (2)
#define DOWNLOAD_ERR_MEM_2 (3)
#define DOWNLOAD_ERR_SOFTWARE (4)
#define DOWNLOAD_ERR_FILE_SIZE (5)
#define DOWNLOAD_ERR_CHECKSUM (6)
#define DOWNLOAD_ERR_OVERFLOW (7)
#define DOWNLOAD_ERR_IMAGE (8)
#define DOWNLOAD_ERR_HOST (9)
#define DOWNLOAD_ERR_ABORT (10)
#define SYS_BASE_ADDR_SILICON (0)
#define PAC_BASE_ADDRESS_SILICON (SYS_BASE_ADDR_SILICON + 0x09000000)
#define PAC_SHARED_MEMORY_SILICON (PAC_BASE_ADDRESS_SILICON)
#define CW1200_APB(addr) (PAC_SHARED_MEMORY_SILICON + (addr))
/* ***************************************************************
*Device register definitions
*************************************************************** */
/* WBF - SPI Register Addresses */
#define ST90TDS_ADDR_ID_BASE (0x0000)
/* 16/32 bits */
#define ST90TDS_CONFIG_REG_ID (0x0000)
/* 16/32 bits */
#define ST90TDS_CONTROL_REG_ID (0x0001)
/* 16 bits, Q mode W/R */
#define ST90TDS_IN_OUT_QUEUE_REG_ID (0x0002)
/* 32 bits, AHB bus R/W */
#define ST90TDS_AHB_DPORT_REG_ID (0x0003)
/* 16/32 bits */
#define ST90TDS_SRAM_BASE_ADDR_REG_ID (0x0004)
/* 32 bits, APB bus R/W */
#define ST90TDS_SRAM_DPORT_REG_ID (0x0005)
/* 32 bits, t_settle/general */
#define ST90TDS_TSET_GEN_R_W_REG_ID (0x0006)
/* 16 bits, Q mode read, no length */
#define ST90TDS_FRAME_OUT_REG_ID (0x0007)
#define ST90TDS_ADDR_ID_MAX (ST90TDS_FRAME_OUT_REG_ID)
/* WBF - Control register bit set */
/* next o/p length, bit 11 to 0 */
#define ST90TDS_CONT_NEXT_LEN_MASK (0x0FFF)
#define ST90TDS_CONT_WUP_BIT (BIT(12))
#define ST90TDS_CONT_RDY_BIT (BIT(13))
#define ST90TDS_CONT_IRQ_ENABLE (BIT(14))
#define ST90TDS_CONT_RDY_ENABLE (BIT(15))
#define ST90TDS_CONT_IRQ_RDY_ENABLE (BIT(14)|BIT(15))
/* SPI Config register bit set */
#define ST90TDS_CONFIG_FRAME_BIT (BIT(2))
#define ST90TDS_CONFIG_WORD_MODE_BITS (BIT(3)|BIT(4))
#define ST90TDS_CONFIG_WORD_MODE_1 (BIT(3))
#define ST90TDS_CONFIG_WORD_MODE_2 (BIT(4))
#define ST90TDS_CONFIG_ERROR_0_BIT (BIT(5))
#define ST90TDS_CONFIG_ERROR_1_BIT (BIT(6))
#define ST90TDS_CONFIG_ERROR_2_BIT (BIT(7))
/* TBD: Sure??? */
#define ST90TDS_CONFIG_CSN_FRAME_BIT (BIT(7))
#define ST90TDS_CONFIG_ERROR_3_BIT (BIT(8))
#define ST90TDS_CONFIG_ERROR_4_BIT (BIT(9))
/* QueueM */
#define ST90TDS_CONFIG_ACCESS_MODE_BIT (BIT(10))
/* AHB bus */
#define ST90TDS_CONFIG_AHB_PRFETCH_BIT (BIT(11))
#define ST90TDS_CONFIG_CPU_CLK_DIS_BIT (BIT(12))
/* APB bus */
#define ST90TDS_CONFIG_PRFETCH_BIT (BIT(13))
/* cpu reset */
#define ST90TDS_CONFIG_CPU_RESET_BIT (BIT(14))
#define ST90TDS_CONFIG_CLEAR_INT_BIT (BIT(15))
/* For CW1200 the IRQ Enable and Ready Bits are in CONFIG register */
#define ST90TDS_CONF_IRQ_ENABLE (BIT(16))
#define ST90TDS_CONF_RDY_ENABLE (BIT(17))
#define ST90TDS_CONF_IRQ_RDY_ENABLE (BIT(16)|BIT(17))
int cw1200_data_read(struct cw1200_common *priv,
void *buf, size_t buf_len);
int cw1200_data_write(struct cw1200_common *priv,
const void *buf, size_t buf_len);
int cw1200_reg_read(struct cw1200_common *priv, u16 addr,
void *buf, size_t buf_len);
int cw1200_reg_write(struct cw1200_common *priv, u16 addr,
const void *buf, size_t buf_len);
static inline int cw1200_reg_read_16(struct cw1200_common *priv,
u16 addr, u16 *val)
{
u32 tmp;
int i;
i = cw1200_reg_read(priv, addr, &tmp, sizeof(tmp));
tmp = le32_to_cpu(tmp);
*val = tmp & 0xffff;
return i;
}
static inline int cw1200_reg_write_16(struct cw1200_common *priv,
u16 addr, u16 val)
{
u32 tmp = val;
tmp = cpu_to_le32(tmp);
return cw1200_reg_write(priv, addr, &tmp, sizeof(tmp));
}
static inline int cw1200_reg_read_32(struct cw1200_common *priv,
u16 addr, u32 *val)
{
int i = cw1200_reg_read(priv, addr, val, sizeof(*val));
*val = le32_to_cpu(*val);
return i;
}
static inline int cw1200_reg_write_32(struct cw1200_common *priv,
u16 addr, u32 val)
{
val = cpu_to_le32(val);
return cw1200_reg_write(priv, addr, &val, sizeof(val));
}
int cw1200_indirect_read(struct cw1200_common *priv, u32 addr, void *buf,
size_t buf_len, u32 prefetch, u16 port_addr);
int cw1200_apb_write(struct cw1200_common *priv, u32 addr, const void *buf,
size_t buf_len);
static inline int cw1200_apb_read(struct cw1200_common *priv, u32 addr,
void *buf, size_t buf_len)
{
return cw1200_indirect_read(priv, addr, buf, buf_len,
ST90TDS_CONFIG_PRFETCH_BIT,
ST90TDS_SRAM_DPORT_REG_ID);
}
static inline int cw1200_ahb_read(struct cw1200_common *priv, u32 addr,
void *buf, size_t buf_len)
{
return cw1200_indirect_read(priv, addr, buf, buf_len,
ST90TDS_CONFIG_AHB_PRFETCH_BIT,
ST90TDS_AHB_DPORT_REG_ID);
}
static inline int cw1200_apb_read_32(struct cw1200_common *priv,
u32 addr, u32 *val)
{
int i = cw1200_apb_read(priv, addr, val, sizeof(*val));
*val = le32_to_cpu(*val);
return i;
}
static inline int cw1200_apb_write_32(struct cw1200_common *priv,
u32 addr, u32 val)
{
val = cpu_to_le32(val);
return cw1200_apb_write(priv, addr, &val, sizeof(val));
}
static inline int cw1200_ahb_read_32(struct cw1200_common *priv,
u32 addr, u32 *val)
{
int i = cw1200_ahb_read(priv, addr, val, sizeof(*val));
*val = le32_to_cpu(*val);
return i;
}
#endif /* CW1200_HWIO_H_INCLUDED */
/*
* mac80211 glue code for mac80211 ST-Ericsson CW1200 drivers
* ITP code
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/poll.h>
#include <linux/time.h>
#include <linux/random.h>
#include <linux/kallsyms.h>
#include <net/mac80211.h>
#include "cw1200.h"
#include "debug.h"
#include "itp.h"
#include "sta.h"
static int __cw1200_itp_open(struct cw1200_common *priv);
static int __cw1200_itp_close(struct cw1200_common *priv);
static void cw1200_itp_rx_start(struct cw1200_common *priv);
static void cw1200_itp_rx_stop(struct cw1200_common *priv);
static void cw1200_itp_rx_stats(struct cw1200_common *priv);
static void cw1200_itp_rx_reset(struct cw1200_common *priv);
static void cw1200_itp_tx_stop(struct cw1200_common *priv);
static void cw1200_itp_handle(struct cw1200_common *priv,
struct sk_buff *skb);
static void cw1200_itp_err(struct cw1200_common *priv,
int err,
int arg);
static void __cw1200_itp_tx_stop(struct cw1200_common *priv);
static ssize_t cw1200_itp_read(struct file *file,
char __user *user_buf, size_t count, loff_t *ppos)
{
struct cw1200_common *priv = file->private_data;
struct cw1200_itp *itp = &priv->debug->itp;
struct sk_buff *skb;
int ret;
if (skb_queue_empty(&itp->log_queue))
return 0;
skb = skb_dequeue(&itp->log_queue);
ret = copy_to_user(user_buf, skb->data, skb->len);
*ppos += skb->len;
skb->data[skb->len] = 0;
pr_debug("[ITP] >>> %s", skb->data);
consume_skb(skb);
return skb->len - ret;
}
static ssize_t cw1200_itp_write(struct file *file,
const char __user *user_buf, size_t count, loff_t *ppos)
{
struct cw1200_common *priv = file->private_data;
struct sk_buff *skb;
if (!count || count > 1024)
return -EINVAL;
skb = dev_alloc_skb(count + 1);
if (!skb)
return -ENOMEM;
skb_trim(skb, 0);
skb_put(skb, count + 1);
if (copy_from_user(skb->data, user_buf, count)) {
kfree_skb(skb);
return -EFAULT;
}
skb->data[count] = 0;
cw1200_itp_handle(priv, skb);
consume_skb(skb);
return count;
}
static unsigned int cw1200_itp_poll(struct file *file, poll_table *wait)
{
struct cw1200_common *priv = file->private_data;
struct cw1200_itp *itp = &priv->debug->itp;
unsigned int mask = 0;
poll_wait(file, &itp->read_wait, wait);
if (!skb_queue_empty(&itp->log_queue))
mask |= POLLIN | POLLRDNORM;
mask |= POLLOUT | POLLWRNORM;
return mask;
}
static int cw1200_itp_open(struct inode *inode, struct file *file)
{
struct cw1200_common *priv = inode->i_private;
struct cw1200_itp *itp = &priv->debug->itp;
int ret = 0;
file->private_data = priv;
if (atomic_inc_return(&itp->open_count) == 1) {
ret = __cw1200_itp_open(priv);
if (ret && !atomic_dec_return(&itp->open_count))
__cw1200_itp_close(priv);
} else {
atomic_dec(&itp->open_count);
ret = -EBUSY;
}
return ret;
}
static int cw1200_itp_close(struct inode *inode, struct file *file)
{
struct cw1200_common *priv = file->private_data;
struct cw1200_itp *itp = &priv->debug->itp;
if (!atomic_dec_return(&itp->open_count)) {
__cw1200_itp_close(priv);
wake_up(&itp->close_wait);
}
return 0;
}
static const struct file_operations fops_itp = {
.open = cw1200_itp_open,
.read = cw1200_itp_read,
.write = cw1200_itp_write,
.poll = cw1200_itp_poll,
.release = cw1200_itp_close,
.llseek = default_llseek,
.owner = THIS_MODULE,
};
static void cw1200_itp_fill_pattern(u8 *data, int size,
enum cw1200_itp_data_modes mode)
{
if (size <= 0)
return;
switch (mode) {
default:
case ITP_DATA_ZEROS:
memset(data, 0x0, size);
break;
case ITP_DATA_ONES:
memset(data, 0xff, size);
break;
case ITP_DATA_ZERONES:
memset(data, 0x55, size);
break;
case ITP_DATA_RANDOM:
get_random_bytes(data, size);
break;
}
return;
}
static void cw1200_itp_tx_work(struct work_struct *work)
{
struct cw1200_itp *itp = container_of(work, struct cw1200_itp,
tx_work.work);
struct cw1200_common *priv = itp->priv;
atomic_set(&priv->bh_tx, 1);
wake_up(&priv->bh_wq);
}
static void cw1200_itp_tx_finish(struct work_struct *work)
{
struct cw1200_itp *itp = container_of(work, struct cw1200_itp,
tx_finish.work);
__cw1200_itp_tx_stop(itp->priv);
}
int cw1200_itp_init(struct cw1200_common *priv)
{
struct cw1200_itp *itp = &priv->debug->itp;
itp->priv = priv;
atomic_set(&itp->open_count, 0);
atomic_set(&itp->stop_tx, 0);
atomic_set(&itp->awaiting_confirm, 0);
skb_queue_head_init(&itp->log_queue);
spin_lock_init(&itp->tx_lock);
init_waitqueue_head(&itp->read_wait);
init_waitqueue_head(&itp->write_wait);
init_waitqueue_head(&itp->close_wait);
INIT_DELAYED_WORK(&itp->tx_work, cw1200_itp_tx_work);
INIT_DELAYED_WORK(&itp->tx_finish, cw1200_itp_tx_finish);
itp->data = NULL;
itp->hdr_len = WSM_TX_EXTRA_HEADROOM +
sizeof(struct ieee80211_hdr_3addr);
if (!debugfs_create_file("itp", S_IRUSR | S_IWUSR,
priv->debug->debugfs_phy, priv, &fops_itp))
return -ENOMEM;
return 0;
}
void cw1200_itp_release(struct cw1200_common *priv)
{
struct cw1200_itp *itp = &priv->debug->itp;
wait_event_interruptible(itp->close_wait,
!atomic_read(&itp->open_count));
WARN_ON(atomic_read(&itp->open_count));
skb_queue_purge(&itp->log_queue);
cw1200_itp_tx_stop(priv);
}
static int __cw1200_itp_open(struct cw1200_common *priv)
{
struct cw1200_itp *itp = &priv->debug->itp;
if (!priv->vif)
return -EINVAL;
if (priv->join_status)
return -EINVAL;
itp->saved_channel = priv->channel;
if (!priv->channel)
priv->channel = &priv->hw->wiphy->bands[IEEE80211_BAND_2GHZ]->channels[0];
wsm_set_bssid_filtering(priv, false);
cw1200_itp_rx_reset(priv);
return 0;
}
static int __cw1200_itp_close(struct cw1200_common *priv)
{
struct cw1200_itp *itp = &priv->debug->itp;
if (atomic_read(&itp->test_mode) == TEST_MODE_RX_TEST)
cw1200_itp_rx_stop(priv);
cw1200_itp_tx_stop(priv);
cw1200_disable_listening(priv);
cw1200_update_filtering(priv);
priv->channel = itp->saved_channel;
return 0;
}
bool cw1200_is_itp(struct cw1200_common *priv)
{
struct cw1200_itp *itp = &priv->debug->itp;
return atomic_read(&itp->open_count) != 0;
}
static void cw1200_itp_rx_reset(struct cw1200_common *priv)
{
struct cw1200_itp *itp = &priv->debug->itp;
itp->rx_cnt = 0;
itp->rx_rssi = 0;
itp->rx_rssi_max = -1000;
itp->rx_rssi_min = 1000;
}
static void cw1200_itp_rx_start(struct cw1200_common *priv)
{
struct cw1200_itp *itp = &priv->debug->itp;
pr_debug("[ITP] RX start, band = %d, ch = %d\n",
itp->band, itp->ch);
atomic_set(&itp->test_mode, TEST_MODE_RX_TEST);
cw1200_update_listening(priv, false);
priv->channel = &priv->hw->
wiphy->bands[itp->band]->channels[itp->ch];
cw1200_update_listening(priv, true);
wsm_set_bssid_filtering(priv, false);
}
static void cw1200_itp_rx_stop(struct cw1200_common *priv)
{
struct cw1200_itp *itp = &priv->debug->itp;
pr_debug("[ITP] RX stop\n");
atomic_set(&itp->test_mode, TEST_MODE_NO_TEST);
cw1200_itp_rx_reset(priv);
}
static void cw1200_itp_rx_stats(struct cw1200_common *priv)
{
struct cw1200_itp *itp = &priv->debug->itp;
struct sk_buff *skb;
char buf[128];
int len, ret;
struct wsm_mib_counters_table counters;
ret = wsm_get_counters_table(priv, &counters);
if (ret)
cw1200_itp_err(priv, -EBUSY, 20);
if (!itp->rx_cnt)
len = snprintf(buf, sizeof(buf), "1,0,0,0,0,%d\n",
counters.rx_packet_errors);
else
len = snprintf(buf, sizeof(buf), "1,%d,%ld,%d,%d,%d\n",
itp->rx_cnt,
itp->rx_cnt ? itp->rx_rssi / itp->rx_cnt : 0,
itp->rx_rssi_min, itp->rx_rssi_max,
counters.rx_packet_errors);
if (len <= 0) {
cw1200_itp_err(priv, -EBUSY, 21);
return;
}
skb = dev_alloc_skb(len);
if (!skb) {
cw1200_itp_err(priv, -ENOMEM, 22);
return;
}
itp->rx_cnt = 0;
itp->rx_rssi = 0;
itp->rx_rssi_max = -1000;
itp->rx_rssi_min = 1000;
skb_trim(skb, 0);
skb_put(skb, len);
memcpy(skb->data, buf, len);
skb_queue_tail(&itp->log_queue, skb);
wake_up(&itp->read_wait);
}
static void cw1200_itp_tx_start(struct cw1200_common *priv)
{
struct wsm_tx *tx;
struct ieee80211_hdr_3addr *hdr;
struct cw1200_itp *itp = &priv->debug->itp;
struct wsm_mib_association_mode assoc_mode = {
.flags = WSM_ASSOCIATION_MODE_USE_PREAMBLE_TYPE,
.preamble = itp->preamble,
};
int len;
u8 da_addr[6] = ITP_DEFAULT_DA_ADDR;
/* Rates index 4 and 5 are not supported */
if (itp->rate > 3)
itp->rate += 2;
pr_debug("[ITP] TX start: band = %d, ch = %d, rate = %d, preamble = %d, number = %d, data_mode = %d, interval = %d, power = %d, data_len = %d\n",
itp->band, itp->ch, itp->rate, itp->preamble,
itp->number, itp->data_mode, itp->interval_us,
itp->power, itp->data_len);
len = itp->hdr_len + itp->data_len;
itp->data = kmalloc(len, GFP_KERNEL);
tx = (struct wsm_tx *)itp->data;
tx->hdr.len = itp->data_len + itp->hdr_len;
tx->hdr.id = __cpu_to_le16(0x0004 | 1 << 6);
tx->max_tx_rate = itp->rate;
tx->queue_id = 3;
tx->more = 0;
tx->flags = 0xc;
tx->packet_id = 0x55ff55;
tx->reserved = 0;
tx->expire_time = 1;
if (itp->preamble == ITP_PREAMBLE_GREENFIELD)
tx->ht_tx_parameters = WSM_HT_TX_GREENFIELD;
else if (itp->preamble == ITP_PREAMBLE_MIXED)
tx->ht_tx_parameters = WSM_HT_TX_MIXED;
hdr = (struct ieee80211_hdr_3addr *)&itp->data[sizeof(struct wsm_tx)];
memset(hdr, 0, sizeof(*hdr));
hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_FCTL_TODS);
memcpy(hdr->addr1, da_addr, ETH_ALEN);
memcpy(hdr->addr2, priv->vif->addr, ETH_ALEN);
memcpy(hdr->addr3, da_addr, ETH_ALEN);
cw1200_itp_fill_pattern(&itp->data[itp->hdr_len],
itp->data_len, itp->data_mode);
cw1200_update_listening(priv, false);
priv->channel = &priv->hw->wiphy->bands[itp->band]->channels[itp->ch];
WARN_ON(wsm_set_output_power(priv, itp->power));
if (itp->preamble == ITP_PREAMBLE_SHORT ||
itp->preamble == ITP_PREAMBLE_LONG)
WARN_ON(wsm_set_association_mode(priv,
&assoc_mode));
wsm_set_bssid_filtering(priv, false);
cw1200_update_listening(priv, true);
spin_lock_bh(&itp->tx_lock);
atomic_set(&itp->test_mode, TEST_MODE_TX_TEST);
atomic_set(&itp->awaiting_confirm, 0);
atomic_set(&itp->stop_tx, 0);
atomic_set(&priv->bh_tx, 1);
ktime_get_ts(&itp->last_sent);
wake_up(&priv->bh_wq);
spin_unlock_bh(&itp->tx_lock);
}
void __cw1200_itp_tx_stop(struct cw1200_common *priv)
{
struct cw1200_itp *itp = &priv->debug->itp;
spin_lock_bh(&itp->tx_lock);
kfree(itp->data);
itp->data = NULL;
atomic_set(&itp->test_mode, TEST_MODE_NO_TEST);
spin_unlock_bh(&itp->tx_lock);
}
static void cw1200_itp_tx_stop(struct cw1200_common *priv)
{
struct cw1200_itp *itp = &priv->debug->itp;
pr_debug("[ITP] TX stop\n");
atomic_set(&itp->stop_tx, 1);
flush_workqueue(priv->workqueue);
/* time for FW to confirm all tx requests */
msleep(500);
__cw1200_itp_tx_stop(priv);
}
static int cw1200_print_fw_version(struct cw1200_common *priv,
u8 *buf, size_t len)
{
return snprintf(buf, len, "%s %d.%d",
cw1200_fw_types[priv->wsm_caps.fw_type],
priv->wsm_caps.fw_ver,
priv->wsm_caps.fw_build);
}
static void cw1200_itp_get_version(struct cw1200_common *priv,
enum cw1200_itp_version_type type)
{
struct cw1200_itp *itp = &priv->debug->itp;
struct sk_buff *skb;
char buf[ITP_BUF_SIZE];
size_t size = 0;
int len;
pr_debug("[ITP] print %s version\n",
type == ITP_CHIP_ID ? "chip" : "firmware");
len = snprintf(buf, ITP_BUF_SIZE, "2,");
if (len <= 0) {
cw1200_itp_err(priv, -EINVAL, 40);
return;
}
size += len;
switch (type) {
case ITP_CHIP_ID:
len = cw1200_print_fw_version(priv, buf+size,
ITP_BUF_SIZE - size);
if (len <= 0) {
cw1200_itp_err(priv, -EINVAL, 41);
return;
}
size += len;
break;
case ITP_FW_VER:
len = snprintf(buf+size, ITP_BUF_SIZE - size,
"%d.%d", priv->wsm_caps.hw_id,
priv->wsm_caps.hw_subid);
if (len <= 0) {
cw1200_itp_err(priv, -EINVAL, 42);
return;
}
size += len;
break;
default:
cw1200_itp_err(priv, -EINVAL, 43);
break;
}
len = snprintf(buf+size, ITP_BUF_SIZE-size, "\n");
if (len <= 0) {
cw1200_itp_err(priv, -EINVAL, 44);
return;
}
size += len;
skb = dev_alloc_skb(size);
if (!skb) {
cw1200_itp_err(priv, -ENOMEM, 45);
return;
}
skb_trim(skb, 0);
skb_put(skb, size);
memcpy(skb->data, buf, size);
skb_queue_tail(&itp->log_queue, skb);
wake_up(&itp->read_wait);
}
int cw1200_itp_get_tx(struct cw1200_common *priv, u8 **data,
size_t *tx_len, int *burst)
{
struct cw1200_itp *itp;
struct timespec now;
int time_left_us;
if (!priv->debug)
return 0;
itp = &priv->debug->itp;
if (!itp)
return 0;
spin_lock_bh(&itp->tx_lock);
if (atomic_read(&itp->test_mode) != TEST_MODE_TX_TEST)
goto out;
if (atomic_read(&itp->stop_tx))
goto out;
if (itp->number == 0) {
atomic_set(&itp->stop_tx, 1);
queue_delayed_work(priv->workqueue, &itp->tx_finish, HZ/10);
goto out;
}
if (!itp->data)
goto out;
if (priv->hw_bufs_used >= 2) {
if (!atomic_read(&priv->bh_rx))
atomic_set(&priv->bh_rx, 1);
atomic_set(&priv->bh_tx, 1);
goto out;
}
ktime_get_ts(&now);
time_left_us = (itp->last_sent.tv_sec - now.tv_sec)*1000000 +
(itp->last_sent.tv_nsec - now.tv_nsec)/1000 +
itp->interval_us;
if (time_left_us > ITP_TIME_THRES_US) {
queue_delayed_work(priv->workqueue, &itp->tx_work,
ITP_US_TO_MS(time_left_us)*HZ/1000);
goto out;
}
if (time_left_us > 50)
udelay(time_left_us);
if (itp->number > 0)
itp->number--;
*data = itp->data;
*tx_len = itp->data_len + itp->hdr_len;
if (itp->data_mode == ITP_DATA_RANDOM)
cw1200_itp_fill_pattern(&itp->data[itp->hdr_len],
itp->data_len, itp->data_mode);
*burst = 2;
atomic_set(&priv->bh_tx, 1);
ktime_get_ts(&itp->last_sent);
atomic_add(1, &itp->awaiting_confirm);
spin_unlock_bh(&itp->tx_lock);
return 1;
out:
spin_unlock_bh(&itp->tx_lock);
return 0;
}
bool cw1200_itp_rxed(struct cw1200_common *priv, struct sk_buff *skb)
{
struct cw1200_itp *itp = &priv->debug->itp;
struct ieee80211_rx_status *rx = IEEE80211_SKB_RXCB(skb);
int signal;
if (atomic_read(&itp->test_mode) != TEST_MODE_RX_TEST)
return cw1200_is_itp(priv);
if (rx->freq != priv->channel->center_freq)
return true;
signal = rx->signal;
itp->rx_cnt++;
itp->rx_rssi += signal;
if (itp->rx_rssi_min > rx->signal)
itp->rx_rssi_min = rx->signal;
if (itp->rx_rssi_max < rx->signal)
itp->rx_rssi_max = rx->signal;
return true;
}
void cw1200_itp_wake_up_tx(struct cw1200_common *priv)
{
wake_up(&priv->debug->itp.write_wait);
}
bool cw1200_itp_tx_running(struct cw1200_common *priv)
{
if (atomic_read(&priv->debug->itp.awaiting_confirm) ||
atomic_read(&priv->debug->itp.test_mode) ==
TEST_MODE_TX_TEST) {
atomic_sub(1, &priv->debug->itp.awaiting_confirm);
return true;
}
return false;
}
static void cw1200_itp_handle(struct cw1200_common *priv,
struct sk_buff *skb)
{
struct cw1200_itp *itp = &priv->debug->itp;
const struct wiphy *wiphy = priv->hw->wiphy;
int cmd;
int ret;
pr_debug("[ITP] <<< %s", skb->data);
if (sscanf(skb->data, "%d", &cmd) != 1) {
cw1200_itp_err(priv, -EINVAL, 1);
return;
}
switch (cmd) {
case 1: /* RX test */
if (atomic_read(&itp->test_mode)) {
cw1200_itp_err(priv, -EBUSY, 0);
return;
}
ret = sscanf(skb->data, "%d,%d,%d",
&cmd, &itp->band, &itp->ch);
if (ret != 3) {
cw1200_itp_err(priv, -EINVAL, ret + 1);
return;
}
if (itp->band >= 2) {
cw1200_itp_err(priv, -EINVAL, 2);
} else if (!wiphy->bands[itp->band]) {
cw1200_itp_err(priv, -EINVAL, 2);
} else if (itp->ch >= wiphy->bands[itp->band]->n_channels) {
cw1200_itp_err(priv, -EINVAL, 3);
} else {
cw1200_itp_rx_stats(priv);
cw1200_itp_rx_start(priv);
}
break;
case 2: /* RX stat */
cw1200_itp_rx_stats(priv);
break;
case 3: /* RX/TX stop */
if (atomic_read(&itp->test_mode) == TEST_MODE_RX_TEST) {
cw1200_itp_rx_stats(priv);
cw1200_itp_rx_stop(priv);
} else if (atomic_read(&itp->test_mode) == TEST_MODE_TX_TEST) {
cw1200_itp_tx_stop(priv);
} else {
cw1200_itp_err(priv, -EBUSY, 0);
}
break;
case 4: /* TX start */
if (atomic_read(&itp->test_mode) != TEST_MODE_NO_TEST) {
cw1200_itp_err(priv, -EBUSY, 0);
return;
}
ret = sscanf(skb->data, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
&cmd, &itp->band, &itp->ch, &itp->rate,
&itp->preamble, &itp->number, &itp->data_mode,
&itp->interval_us, &itp->power, &itp->data_len);
if (ret != 10) {
cw1200_itp_err(priv, -EINVAL, ret + 1);
return;
}
if (itp->band >= 2) {
cw1200_itp_err(priv, -EINVAL, 2);
} else if (!wiphy->bands[itp->band]) {
cw1200_itp_err(priv, -EINVAL, 2);
} else if (itp->ch >= wiphy->bands[itp->band]->n_channels) {
cw1200_itp_err(priv, -EINVAL, 3);
} else if (itp->rate >= 20) {
cw1200_itp_err(priv, -EINVAL, 4);
} else if (itp->preamble >= ITP_PREAMBLE_MAX) {
cw1200_itp_err(priv, -EINVAL, 5);
} else if (itp->data_mode >= ITP_DATA_MAX_MODE) {
cw1200_itp_err(priv, -EINVAL, 7);
} else if (itp->data_len < ITP_MIN_DATA_SIZE ||
itp->data_len > (priv->wsm_caps.input_buffer_size - itp->hdr_len)) {
cw1200_itp_err(priv, -EINVAL, 8);
} else {
cw1200_itp_tx_start(priv);
}
break;
case 5:
cw1200_itp_get_version(priv, ITP_CHIP_ID);
break;
case 6:
cw1200_itp_get_version(priv, ITP_FW_VER);
break;
}
}
static void cw1200_itp_err(struct cw1200_common *priv,
int err, int arg)
{
struct cw1200_itp *itp = &priv->debug->itp;
struct sk_buff *skb;
static char buf[255];
int len;
len = snprintf(buf, sizeof(buf), "%d,%d\n",
err, arg);
if (len <= 0)
return;
skb = dev_alloc_skb(len);
if (!skb)
return;
skb_trim(skb, 0);
skb_put(skb, len);
memcpy(skb->data, buf, len);
skb_queue_tail(&itp->log_queue, skb);
wake_up(&itp->read_wait);
len = sprint_symbol(buf,
(unsigned long)__builtin_return_address(0));
if (len <= 0)
return;
pr_debug("[ITP] error %d,%d from %s\n",
err, arg, buf);
}
/*
* ITP code for ST-Ericsson CW1200 mac80211 driver
*
* Copyright (c) 2011, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef CW1200_ITP_H_INCLUDED
#define CW1200_ITP_H_INCLUDED
struct cw200_common;
struct wsm_tx_confirm;
struct dentry;
#ifdef CONFIG_CW1200_ITP
/*extern*/ struct ieee80211_channel;
#define TEST_MODE_NO_TEST (0)
#define TEST_MODE_RX_TEST (1)
#define TEST_MODE_TX_TEST (2)
#define ITP_DEFAULT_DA_ADDR {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
#define ITP_MIN_DATA_SIZE 6
#define ITP_MAX_DATA_SIZE 1600
#define ITP_TIME_THRES_US 10000
#define ITP_US_TO_MS(x) ((x)/1000)
#define ITP_MS_TO_US(x) ((x)*1000)
#define ITP_BUF_SIZE 255
enum cw1200_itp_data_modes {
ITP_DATA_ZEROS,
ITP_DATA_ONES,
ITP_DATA_ZERONES,
ITP_DATA_RANDOM,
ITP_DATA_MAX_MODE,
};
enum cw1200_itp_version_type {
ITP_CHIP_ID,
ITP_FW_VER,
};
enum cw1200_itp_preamble_type {
ITP_PREAMBLE_LONG,
ITP_PREAMBLE_SHORT,
ITP_PREAMBLE_OFDM,
ITP_PREAMBLE_MIXED,
ITP_PREAMBLE_GREENFIELD,
ITP_PREAMBLE_MAX,
};
struct cw1200_itp {
struct cw1200_common *priv;
atomic_t open_count;
atomic_t awaiting_confirm;
struct sk_buff_head log_queue;
wait_queue_head_t read_wait;
wait_queue_head_t write_wait;
wait_queue_head_t close_wait;
struct ieee80211_channel *saved_channel;
atomic_t stop_tx;
struct delayed_work tx_work;
struct delayed_work tx_finish;
spinlock_t tx_lock;
struct timespec last_sent;
atomic_t test_mode;
int rx_cnt;
long rx_rssi;
int rx_rssi_max;
int rx_rssi_min;
unsigned band;
unsigned ch;
unsigned rate;
unsigned preamble;
unsigned int number;
unsigned data_mode;
int interval_us;
int power;
u8 *data;
int hdr_len;
int data_len;
};
int cw1200_itp_init(struct cw1200_common *priv);
void cw1200_itp_release(struct cw1200_common *priv);
bool cw1200_is_itp(struct cw1200_common *priv);
bool cw1200_itp_rxed(struct cw1200_common *priv, struct sk_buff *skb);
void cw1200_itp_wake_up_tx(struct cw1200_common *priv);
int cw1200_itp_get_tx(struct cw1200_common *priv, u8 **data,
size_t *tx_len, int *burst);
bool cw1200_itp_tx_running(struct cw1200_common *priv);
#else /* CONFIG_CW1200_ITP */
static inline int cw1200_itp_init(struct cw1200_common *priv)
{
return 0;
}
static inline void cw1200_itp_release(struct cw1200_common *priv)
{
}
static inline bool cw1200_is_itp(struct cw1200_common *priv)
{
return false;
}
static inline bool cw1200_itp_rxed(struct cw1200_common *priv,
struct sk_buff *skb)
{
return false;
}
static inline void cw1200_itp_consume_txed(struct cw1200_common *priv)
{
}
static inline void cw1200_itp_wake_up_tx(struct cw1200_common *priv)
{
}
static inline int cw1200_itp_get_tx(struct cw1200_common *priv, u8 **data,
size_t *tx_len, int *burst)
{
return 0;
}
static inline bool cw1200_itp_tx_running(struct cw1200_common *priv)
{
return false;
}
#endif /* CONFIG_CW1200_ITP */
#endif /* CW1200_ITP_H_INCLUDED */
/*
* mac80211 glue code for mac80211 ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Based on:
* Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
* Copyright (c) 2007-2009, Christian Lamparter <chunkeey@web.de>
* Copyright 2008, Johannes Berg <johannes@sipsolutions.net>
*
* Based on:
* - the islsm (softmac prism54) driver, which is:
* Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
* - stlc45xx driver
* Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/firmware.h>
#include <linux/etherdevice.h>
#include <linux/vmalloc.h>
#include <linux/random.h>
#include <linux/sched.h>
#include <net/mac80211.h>
#include "cw1200.h"
#include "txrx.h"
#include "sbus.h"
#include "fwio.h"
#include "hwio.h"
#include "bh.h"
#include "sta.h"
#include "scan.h"
#include "debug.h"
#include "pm.h"
MODULE_AUTHOR("Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>");
MODULE_DESCRIPTION("Softmac ST-Ericsson CW1200 common code");
MODULE_LICENSE("GPL");
MODULE_ALIAS("cw1200_core");
/* Accept MAC address of the form macaddr=0x00,0x80,0xE1,0x30,0x40,0x50 */
static u8 cw1200_mac_template[ETH_ALEN] = {0x02, 0x80, 0xe1, 0x00, 0x00, 0x00};
module_param_array_named(macaddr, cw1200_mac_template, byte, NULL, S_IRUGO);
MODULE_PARM_DESC(macaddr, "Override platform_data MAC address");
static char *cw1200_sdd_path;
module_param(cw1200_sdd_path, charp, 0644);
MODULE_PARM_DESC(cw1200_sdd_path, "Override platform_data SDD file");
static int cw1200_refclk;
module_param(cw1200_refclk, int, 0644);
MODULE_PARM_DESC(cw1200_refclk, "Override platform_data reference clock");
int cw1200_power_mode = wsm_power_mode_quiescent;
module_param(cw1200_power_mode, int, 0644);
MODULE_PARM_DESC(cw1200_power_mode, "WSM power mode. 0 == active, 1 == doze, 2 == quiescent (default)");
#ifdef CONFIG_CW1200_ETF
int etf_mode;
module_param(etf_mode, int, 0644);
MODULE_PARM_DESC(etf_mode, "Enable EngineeringTestingFramework operation");
#endif
#define RATETAB_ENT(_rate, _rateid, _flags) \
{ \
.bitrate = (_rate), \
.hw_value = (_rateid), \
.flags = (_flags), \
}
static struct ieee80211_rate cw1200_rates[] = {
RATETAB_ENT(10, 0, 0),
RATETAB_ENT(20, 1, 0),
RATETAB_ENT(55, 2, 0),
RATETAB_ENT(110, 3, 0),
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),
};
static struct ieee80211_rate cw1200_mcs_rates[] = {
RATETAB_ENT(65, 14, IEEE80211_TX_RC_MCS),
RATETAB_ENT(130, 15, IEEE80211_TX_RC_MCS),
RATETAB_ENT(195, 16, IEEE80211_TX_RC_MCS),
RATETAB_ENT(260, 17, IEEE80211_TX_RC_MCS),
RATETAB_ENT(390, 18, IEEE80211_TX_RC_MCS),
RATETAB_ENT(520, 19, IEEE80211_TX_RC_MCS),
RATETAB_ENT(585, 20, IEEE80211_TX_RC_MCS),
RATETAB_ENT(650, 21, IEEE80211_TX_RC_MCS),
};
#define cw1200_a_rates (cw1200_rates + 4)
#define cw1200_a_rates_size (ARRAY_SIZE(cw1200_rates) - 4)
#define cw1200_g_rates (cw1200_rates + 0)
#define cw1200_g_rates_size (ARRAY_SIZE(cw1200_rates))
#define cw1200_n_rates (cw1200_mcs_rates)
#define cw1200_n_rates_size (ARRAY_SIZE(cw1200_mcs_rates))
#define CHAN2G(_channel, _freq, _flags) { \
.band = IEEE80211_BAND_2GHZ, \
.center_freq = (_freq), \
.hw_value = (_channel), \
.flags = (_flags), \
.max_antenna_gain = 0, \
.max_power = 30, \
}
#define CHAN5G(_channel, _flags) { \
.band = IEEE80211_BAND_5GHZ, \
.center_freq = 5000 + (5 * (_channel)), \
.hw_value = (_channel), \
.flags = (_flags), \
.max_antenna_gain = 0, \
.max_power = 30, \
}
static struct ieee80211_channel cw1200_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 struct ieee80211_channel cw1200_5ghz_chantable[] = {
CHAN5G(34, 0), CHAN5G(36, 0),
CHAN5G(38, 0), CHAN5G(40, 0),
CHAN5G(42, 0), CHAN5G(44, 0),
CHAN5G(46, 0), CHAN5G(48, 0),
CHAN5G(52, 0), CHAN5G(56, 0),
CHAN5G(60, 0), CHAN5G(64, 0),
CHAN5G(100, 0), CHAN5G(104, 0),
CHAN5G(108, 0), CHAN5G(112, 0),
CHAN5G(116, 0), CHAN5G(120, 0),
CHAN5G(124, 0), CHAN5G(128, 0),
CHAN5G(132, 0), CHAN5G(136, 0),
CHAN5G(140, 0), CHAN5G(149, 0),
CHAN5G(153, 0), CHAN5G(157, 0),
CHAN5G(161, 0), CHAN5G(165, 0),
CHAN5G(184, 0), CHAN5G(188, 0),
CHAN5G(192, 0), CHAN5G(196, 0),
CHAN5G(200, 0), CHAN5G(204, 0),
CHAN5G(208, 0), CHAN5G(212, 0),
CHAN5G(216, 0),
};
static struct ieee80211_supported_band cw1200_band_2ghz = {
.channels = cw1200_2ghz_chantable,
.n_channels = ARRAY_SIZE(cw1200_2ghz_chantable),
.bitrates = cw1200_g_rates,
.n_bitrates = cw1200_g_rates_size,
.ht_cap = {
.cap = IEEE80211_HT_CAP_GRN_FLD |
(1 << IEEE80211_HT_CAP_RX_STBC_SHIFT) |
IEEE80211_HT_CAP_MAX_AMSDU,
.ht_supported = 1,
.ampdu_factor = IEEE80211_HT_MAX_AMPDU_8K,
.ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE,
.mcs = {
.rx_mask[0] = 0xFF,
.rx_highest = __cpu_to_le16(0x41),
.tx_params = IEEE80211_HT_MCS_TX_DEFINED,
},
},
};
static struct ieee80211_supported_band cw1200_band_5ghz = {
.channels = cw1200_5ghz_chantable,
.n_channels = ARRAY_SIZE(cw1200_5ghz_chantable),
.bitrates = cw1200_a_rates,
.n_bitrates = cw1200_a_rates_size,
.ht_cap = {
.cap = IEEE80211_HT_CAP_GRN_FLD |
(1 << IEEE80211_HT_CAP_RX_STBC_SHIFT) |
IEEE80211_HT_CAP_MAX_AMSDU,
.ht_supported = 1,
.ampdu_factor = IEEE80211_HT_MAX_AMPDU_8K,
.ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE,
.mcs = {
.rx_mask[0] = 0xFF,
.rx_highest = __cpu_to_le16(0x41),
.tx_params = IEEE80211_HT_MCS_TX_DEFINED,
},
},
};
static const unsigned long cw1200_ttl[] = {
1 * HZ, /* VO */
2 * HZ, /* VI */
5 * HZ, /* BE */
10 * HZ /* BK */
};
static const struct ieee80211_ops cw1200_ops = {
.start = cw1200_start,
.stop = cw1200_stop,
.add_interface = cw1200_add_interface,
.remove_interface = cw1200_remove_interface,
.change_interface = cw1200_change_interface,
.tx = cw1200_tx,
.hw_scan = cw1200_hw_scan,
.set_tim = cw1200_set_tim,
.sta_notify = cw1200_sta_notify,
.sta_add = cw1200_sta_add,
.sta_remove = cw1200_sta_remove,
.set_key = cw1200_set_key,
.set_rts_threshold = cw1200_set_rts_threshold,
.config = cw1200_config,
.bss_info_changed = cw1200_bss_info_changed,
.prepare_multicast = cw1200_prepare_multicast,
.configure_filter = cw1200_configure_filter,
.conf_tx = cw1200_conf_tx,
.get_stats = cw1200_get_stats,
.ampdu_action = cw1200_ampdu_action,
.flush = cw1200_flush,
.suspend = cw1200_wow_suspend,
.resume = cw1200_wow_resume,
/* Intentionally not offloaded: */
/*.channel_switch = cw1200_channel_switch, */
/*.remain_on_channel = cw1200_remain_on_channel, */
/*.cancel_remain_on_channel = cw1200_cancel_remain_on_channel, */
};
int cw1200_ba_rx_tids = -1;
int cw1200_ba_tx_tids = -1;
module_param(cw1200_ba_rx_tids, int, 0644);
module_param(cw1200_ba_tx_tids, int, 0644);
MODULE_PARM_DESC(cw1200_ba_rx_tids, "Block ACK RX TIDs");
MODULE_PARM_DESC(cw1200_ba_tx_tids, "Block ACK TX TIDs");
static struct ieee80211_hw *cw1200_init_common(const u8 *macaddr,
const bool have_5ghz)
{
int i, band;
struct ieee80211_hw *hw;
struct cw1200_common *priv;
hw = ieee80211_alloc_hw(sizeof(struct cw1200_common), &cw1200_ops);
if (!hw)
return NULL;
priv = hw->priv;
priv->hw = hw;
priv->hw_type = -1;
priv->mode = NL80211_IFTYPE_UNSPECIFIED;
priv->rates = cw1200_rates; /* TODO: fetch from FW */
priv->mcs_rates = cw1200_n_rates;
if (cw1200_ba_rx_tids != -1)
priv->ba_rx_tid_mask = cw1200_ba_rx_tids;
else
priv->ba_rx_tid_mask = 0xFF; /* Enable RX BLKACK for all TIDs */
if (cw1200_ba_tx_tids != -1)
priv->ba_tx_tid_mask = cw1200_ba_tx_tids;
else
priv->ba_tx_tid_mask = 0xff; /* Enable TX BLKACK for all TIDs */
hw->flags = IEEE80211_HW_SIGNAL_DBM |
IEEE80211_HW_SUPPORTS_PS |
IEEE80211_HW_SUPPORTS_DYNAMIC_PS |
IEEE80211_HW_REPORTS_TX_ACK_STATUS |
IEEE80211_HW_SUPPORTS_UAPSD |
IEEE80211_HW_CONNECTION_MONITOR |
IEEE80211_HW_AMPDU_AGGREGATION |
IEEE80211_HW_TX_AMPDU_SETUP_IN_HW |
IEEE80211_HW_NEED_DTIM_BEFORE_ASSOC;
hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
BIT(NL80211_IFTYPE_ADHOC) |
BIT(NL80211_IFTYPE_AP) |
BIT(NL80211_IFTYPE_MESH_POINT) |
BIT(NL80211_IFTYPE_P2P_CLIENT) |
BIT(NL80211_IFTYPE_P2P_GO);
/* Support only for limited wowlan functionalities */
hw->wiphy->wowlan.flags = WIPHY_WOWLAN_ANY |
WIPHY_WOWLAN_DISCONNECT;
hw->wiphy->wowlan.n_patterns = 0;
hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD;
hw->channel_change_time = 1000; /* TODO: find actual value */
hw->queues = 4;
priv->rts_threshold = -1;
hw->max_rates = 8;
hw->max_rate_tries = 15;
hw->extra_tx_headroom = WSM_TX_EXTRA_HEADROOM +
8; /* TKIP IV */
hw->sta_data_size = sizeof(struct cw1200_sta_priv);
hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &cw1200_band_2ghz;
if (have_5ghz)
hw->wiphy->bands[IEEE80211_BAND_5GHZ] = &cw1200_band_5ghz;
/* Channel params have to be cleared before registering wiphy again */
for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
struct ieee80211_supported_band *sband = hw->wiphy->bands[band];
if (!sband)
continue;
for (i = 0; i < sband->n_channels; i++) {
sband->channels[i].flags = 0;
sband->channels[i].max_antenna_gain = 0;
sband->channels[i].max_power = 30;
}
}
hw->wiphy->max_scan_ssids = 2;
hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN;
if (macaddr)
SET_IEEE80211_PERM_ADDR(hw, (u8 *)macaddr);
else
SET_IEEE80211_PERM_ADDR(hw, cw1200_mac_template);
/* Fix up mac address if necessary */
if (hw->wiphy->perm_addr[3] == 0 &&
hw->wiphy->perm_addr[4] == 0 &&
hw->wiphy->perm_addr[5] == 0) {
get_random_bytes(&hw->wiphy->perm_addr[3], 3);
}
mutex_init(&priv->wsm_cmd_mux);
mutex_init(&priv->conf_mutex);
priv->workqueue = create_singlethread_workqueue("cw1200_wq");
sema_init(&priv->scan.lock, 1);
INIT_WORK(&priv->scan.work, cw1200_scan_work);
INIT_DELAYED_WORK(&priv->scan.probe_work, cw1200_probe_work);
INIT_DELAYED_WORK(&priv->scan.timeout, cw1200_scan_timeout);
INIT_DELAYED_WORK(&priv->clear_recent_scan_work,
cw1200_clear_recent_scan_work);
INIT_DELAYED_WORK(&priv->join_timeout, cw1200_join_timeout);
INIT_WORK(&priv->unjoin_work, cw1200_unjoin_work);
INIT_WORK(&priv->join_complete_work, cw1200_join_complete_work);
INIT_WORK(&priv->wep_key_work, cw1200_wep_key_work);
INIT_WORK(&priv->tx_policy_upload_work, tx_policy_upload_work);
spin_lock_init(&priv->event_queue_lock);
INIT_LIST_HEAD(&priv->event_queue);
INIT_WORK(&priv->event_handler, cw1200_event_handler);
INIT_DELAYED_WORK(&priv->bss_loss_work, cw1200_bss_loss_work);
INIT_WORK(&priv->bss_params_work, cw1200_bss_params_work);
spin_lock_init(&priv->bss_loss_lock);
spin_lock_init(&priv->ps_state_lock);
INIT_WORK(&priv->set_cts_work, cw1200_set_cts_work);
INIT_WORK(&priv->set_tim_work, cw1200_set_tim_work);
INIT_WORK(&priv->multicast_start_work, cw1200_multicast_start_work);
INIT_WORK(&priv->multicast_stop_work, cw1200_multicast_stop_work);
INIT_WORK(&priv->link_id_work, cw1200_link_id_work);
INIT_DELAYED_WORK(&priv->link_id_gc_work, cw1200_link_id_gc_work);
INIT_WORK(&priv->linkid_reset_work, cw1200_link_id_reset);
INIT_WORK(&priv->update_filtering_work, cw1200_update_filtering_work);
INIT_WORK(&priv->set_beacon_wakeup_period_work,
cw1200_set_beacon_wakeup_period_work);
init_timer(&priv->mcast_timeout);
priv->mcast_timeout.data = (unsigned long)priv;
priv->mcast_timeout.function = cw1200_mcast_timeout;
if (cw1200_queue_stats_init(&priv->tx_queue_stats,
CW1200_LINK_ID_MAX,
cw1200_skb_dtor,
priv)) {
ieee80211_free_hw(hw);
return NULL;
}
for (i = 0; i < 4; ++i) {
if (cw1200_queue_init(&priv->tx_queue[i],
&priv->tx_queue_stats, i, 16,
cw1200_ttl[i])) {
for (; i > 0; i--)
cw1200_queue_deinit(&priv->tx_queue[i - 1]);
cw1200_queue_stats_deinit(&priv->tx_queue_stats);
ieee80211_free_hw(hw);
return NULL;
}
}
init_waitqueue_head(&priv->channel_switch_done);
init_waitqueue_head(&priv->wsm_cmd_wq);
init_waitqueue_head(&priv->wsm_startup_done);
init_waitqueue_head(&priv->ps_mode_switch_done);
wsm_buf_init(&priv->wsm_cmd_buf);
spin_lock_init(&priv->wsm_cmd.lock);
priv->wsm_cmd.done = 1;
tx_policy_init(priv);
return hw;
}
static int cw1200_register_common(struct ieee80211_hw *dev)
{
struct cw1200_common *priv = dev->priv;
int err;
#ifdef CONFIG_CW1200_ETF
if (etf_mode)
goto done;
#endif
err = cw1200_pm_init(&priv->pm_state, priv);
if (err) {
pr_err("Cannot init PM. (%d).\n",
err);
return err;
}
err = ieee80211_register_hw(dev);
if (err) {
pr_err("Cannot register device (%d).\n",
err);
cw1200_pm_deinit(&priv->pm_state);
return err;
}
#ifdef CONFIG_CW1200_ETF
done:
#endif
cw1200_debug_init(priv);
pr_info("Registered as '%s'\n", wiphy_name(dev->wiphy));
return 0;
}
static void cw1200_free_common(struct ieee80211_hw *dev)
{
ieee80211_free_hw(dev);
}
static void cw1200_unregister_common(struct ieee80211_hw *dev)
{
struct cw1200_common *priv = dev->priv;
int i;
#ifdef CONFIG_CW1200_ETF
if (!etf_mode) {
#endif
ieee80211_unregister_hw(dev);
#ifdef CONFIG_CW1200_ETF
}
#endif
del_timer_sync(&priv->mcast_timeout);
cw1200_unregister_bh(priv);
cw1200_debug_release(priv);
mutex_destroy(&priv->conf_mutex);
wsm_buf_deinit(&priv->wsm_cmd_buf);
destroy_workqueue(priv->workqueue);
priv->workqueue = NULL;
if (priv->sdd) {
release_firmware(priv->sdd);
priv->sdd = NULL;
}
for (i = 0; i < 4; ++i)
cw1200_queue_deinit(&priv->tx_queue[i]);
cw1200_queue_stats_deinit(&priv->tx_queue_stats);
cw1200_pm_deinit(&priv->pm_state);
}
/* Clock is in KHz */
u32 cw1200_dpll_from_clk(u16 clk_khz)
{
switch (clk_khz) {
case 0x32C8: /* 13000 KHz */
return 0x1D89D241;
case 0x3E80: /* 16000 KHz */
return 0x000001E1;
case 0x41A0: /* 16800 KHz */
return 0x124931C1;
case 0x4B00: /* 19200 KHz */
return 0x00000191;
case 0x5DC0: /* 24000 KHz */
return 0x00000141;
case 0x6590: /* 26000 KHz */
return 0x0EC4F121;
case 0x8340: /* 33600 KHz */
return 0x092490E1;
case 0x9600: /* 38400 KHz */
return 0x100010C1;
case 0x9C40: /* 40000 KHz */
return 0x000000C1;
case 0xBB80: /* 48000 KHz */
return 0x000000A1;
case 0xCB20: /* 52000 KHz */
return 0x07627091;
default:
pr_err("Unknown Refclk freq (0x%04x), using 2600KHz\n",
clk_khz);
return 0x0EC4F121;
}
}
int cw1200_core_probe(const struct sbus_ops *sbus_ops,
struct sbus_priv *sbus,
struct device *pdev,
struct cw1200_common **core,
int ref_clk, const u8 *macaddr,
const char *sdd_path, bool have_5ghz)
{
int err = -EINVAL;
struct ieee80211_hw *dev;
struct cw1200_common *priv;
struct wsm_operational_mode mode = {
.power_mode = cw1200_power_mode,
.disable_more_flag_usage = true,
};
dev = cw1200_init_common(macaddr, have_5ghz);
if (!dev)
goto err;
priv = dev->priv;
priv->hw_refclk = ref_clk;
if (cw1200_refclk)
priv->hw_refclk = cw1200_refclk;
priv->sdd_path = (char *)sdd_path;
if (cw1200_sdd_path)
priv->sdd_path = cw1200_sdd_path;
priv->sbus_ops = sbus_ops;
priv->sbus_priv = sbus;
priv->pdev = pdev;
SET_IEEE80211_DEV(priv->hw, pdev);
/* Pass struct cw1200_common back up */
*core = priv;
err = cw1200_register_bh(priv);
if (err)
goto err1;
#ifdef CONFIG_CW1200_ETF
if (etf_mode)
goto skip_fw;
#endif
err = cw1200_load_firmware(priv);
if (err)
goto err2;
if (wait_event_interruptible_timeout(priv->wsm_startup_done,
priv->firmware_ready,
3*HZ) <= 0) {
/* TODO: Need to find how to reset device
in QUEUE mode properly.
*/
pr_err("Timeout waiting on device startup\n");
err = -ETIMEDOUT;
goto err2;
}
/* Set low-power mode. */
wsm_set_operational_mode(priv, &mode);
/* Enable multi-TX confirmation */
wsm_use_multi_tx_conf(priv, true);
#ifdef CONFIG_CW1200_ETF
skip_fw:
#endif
err = cw1200_register_common(dev);
if (err)
goto err2;
return err;
err2:
cw1200_unregister_bh(priv);
err1:
cw1200_free_common(dev);
err:
*core = NULL;
return err;
}
EXPORT_SYMBOL_GPL(cw1200_core_probe);
void cw1200_core_release(struct cw1200_common *self)
{
/* Disable device interrupts */
self->sbus_ops->lock(self->sbus_priv);
__cw1200_irq_enable(self, 0);
self->sbus_ops->unlock(self->sbus_priv);
/* And then clean up */
cw1200_unregister_common(self->hw);
cw1200_free_common(self->hw);
return;
}
EXPORT_SYMBOL_GPL(cw1200_core_release);
/*
* Mac80211 power management API for ST-Ericsson CW1200 drivers
*
* Copyright (c) 2011, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/if_ether.h>
#include "cw1200.h"
#include "pm.h"
#include "sta.h"
#include "bh.h"
#include "sbus.h"
#define CW1200_BEACON_SKIPPING_MULTIPLIER 3
struct cw1200_udp_port_filter {
struct wsm_udp_port_filter_hdr hdr;
/* Up to 4 filters are allowed. */
struct wsm_udp_port_filter filters[WSM_MAX_FILTER_ELEMENTS];
} __packed;
struct cw1200_ether_type_filter {
struct wsm_ether_type_filter_hdr hdr;
/* Up to 4 filters are allowed. */
struct wsm_ether_type_filter filters[WSM_MAX_FILTER_ELEMENTS];
} __packed;
static struct cw1200_udp_port_filter cw1200_udp_port_filter_on = {
.hdr.num = 2,
.filters = {
[0] = {
.action = WSM_FILTER_ACTION_FILTER_OUT,
.type = WSM_FILTER_PORT_TYPE_DST,
.port = __cpu_to_le16(67), /* DHCP Bootps */
},
[1] = {
.action = WSM_FILTER_ACTION_FILTER_OUT,
.type = WSM_FILTER_PORT_TYPE_DST,
.port = __cpu_to_le16(68), /* DHCP Bootpc */
},
}
};
static struct wsm_udp_port_filter_hdr cw1200_udp_port_filter_off = {
.num = 0,
};
#ifndef ETH_P_WAPI
#define ETH_P_WAPI 0x88B4
#endif
static struct cw1200_ether_type_filter cw1200_ether_type_filter_on = {
.hdr.num = 4,
.filters = {
[0] = {
.action = WSM_FILTER_ACTION_FILTER_IN,
.type = __cpu_to_le16(ETH_P_IP),
},
[1] = {
.action = WSM_FILTER_ACTION_FILTER_IN,
.type = __cpu_to_le16(ETH_P_PAE),
},
[2] = {
.action = WSM_FILTER_ACTION_FILTER_IN,
.type = __cpu_to_le16(ETH_P_WAPI),
},
[3] = {
.action = WSM_FILTER_ACTION_FILTER_IN,
.type = __cpu_to_le16(ETH_P_ARP),
},
},
};
static struct wsm_ether_type_filter_hdr cw1200_ether_type_filter_off = {
.num = 0,
};
/* private */
struct cw1200_suspend_state {
unsigned long bss_loss_tmo;
unsigned long join_tmo;
unsigned long direct_probe;
unsigned long link_id_gc;
bool beacon_skipping;
u8 prev_ps_mode;
};
static void cw1200_pm_stay_awake_tmo(unsigned long arg)
{
/* XXX what's the point of this ? */
}
int cw1200_pm_init(struct cw1200_pm_state *pm,
struct cw1200_common *priv)
{
spin_lock_init(&pm->lock);
init_timer(&pm->stay_awake);
pm->stay_awake.data = (unsigned long)pm;
pm->stay_awake.function = cw1200_pm_stay_awake_tmo;
return 0;
}
void cw1200_pm_deinit(struct cw1200_pm_state *pm)
{
del_timer_sync(&pm->stay_awake);
}
void cw1200_pm_stay_awake(struct cw1200_pm_state *pm,
unsigned long tmo)
{
long cur_tmo;
spin_lock_bh(&pm->lock);
cur_tmo = pm->stay_awake.expires - jiffies;
if (!timer_pending(&pm->stay_awake) || cur_tmo < (long)tmo)
mod_timer(&pm->stay_awake, jiffies + tmo);
spin_unlock_bh(&pm->lock);
}
static long cw1200_suspend_work(struct delayed_work *work)
{
int ret = cancel_delayed_work(work);
long tmo;
if (ret > 0) {
/* Timer is pending */
tmo = work->timer.expires - jiffies;
if (tmo < 0)
tmo = 0;
} else {
tmo = -1;
}
return tmo;
}
static int cw1200_resume_work(struct cw1200_common *priv,
struct delayed_work *work,
unsigned long tmo)
{
if ((long)tmo < 0)
return 1;
return queue_delayed_work(priv->workqueue, work, tmo);
}
int cw1200_can_suspend(struct cw1200_common *priv)
{
if (atomic_read(&priv->bh_rx)) {
wiphy_dbg(priv->hw->wiphy, "Suspend interrupted.\n");
return 0;
}
return 1;
}
EXPORT_SYMBOL_GPL(cw1200_can_suspend);
int cw1200_wow_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
{
struct cw1200_common *priv = hw->priv;
struct cw1200_pm_state *pm_state = &priv->pm_state;
struct cw1200_suspend_state *state;
int ret;
spin_lock_bh(&pm_state->lock);
ret = timer_pending(&pm_state->stay_awake);
spin_unlock_bh(&pm_state->lock);
if (ret)
return -EAGAIN;
/* Do not suspend when datapath is not idle */
if (priv->tx_queue_stats.num_queued)
return -EBUSY;
/* Make sure there is no configuration requests in progress. */
if (!mutex_trylock(&priv->conf_mutex))
return -EBUSY;
/* Ensure pending operations are done.
* Note also that wow_suspend must return in ~2.5sec, before
* watchdog is triggered.
*/
if (priv->channel_switch_in_progress)
goto revert1;
/* Do not suspend when join is pending */
if (priv->join_pending)
goto revert1;
/* Do not suspend when scanning */
if (down_trylock(&priv->scan.lock))
goto revert1;
/* Lock TX. */
wsm_lock_tx_async(priv);
/* Wait to avoid possible race with bh code.
* But do not wait too long...
*/
if (wait_event_timeout(priv->bh_evt_wq,
!priv->hw_bufs_used, HZ / 10) <= 0)
goto revert2;
/* Set UDP filter */
wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_on.hdr);
/* Set ethernet frame type filter */
wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_on.hdr);
/* Allocate state */
state = kzalloc(sizeof(struct cw1200_suspend_state), GFP_KERNEL);
if (!state)
goto revert3;
/* Change to legacy PS while going to suspend */
if (!priv->vif->p2p &&
priv->join_status == CW1200_JOIN_STATUS_STA &&
priv->powersave_mode.mode != WSM_PSM_PS) {
state->prev_ps_mode = priv->powersave_mode.mode;
priv->powersave_mode.mode = WSM_PSM_PS;
cw1200_set_pm(priv, &priv->powersave_mode);
if (wait_event_interruptible_timeout(priv->ps_mode_switch_done,
!priv->ps_mode_switch_in_progress, 1*HZ) <= 0) {
goto revert3;
}
}
/* Store delayed work states. */
state->bss_loss_tmo =
cw1200_suspend_work(&priv->bss_loss_work);
state->join_tmo =
cw1200_suspend_work(&priv->join_timeout);
state->direct_probe =
cw1200_suspend_work(&priv->scan.probe_work);
state->link_id_gc =
cw1200_suspend_work(&priv->link_id_gc_work);
cancel_delayed_work_sync(&priv->clear_recent_scan_work);
atomic_set(&priv->recent_scan, 0);
/* Enable beacon skipping */
if (priv->join_status == CW1200_JOIN_STATUS_STA &&
priv->join_dtim_period &&
!priv->has_multicast_subscription) {
state->beacon_skipping = true;
wsm_set_beacon_wakeup_period(priv,
priv->join_dtim_period,
CW1200_BEACON_SKIPPING_MULTIPLIER * priv->join_dtim_period);
}
/* Stop serving thread */
if (cw1200_bh_suspend(priv))
goto revert4;
ret = timer_pending(&priv->mcast_timeout);
if (ret)
goto revert5;
/* Store suspend state */
pm_state->suspend_state = state;
/* Enable IRQ wake */
ret = priv->sbus_ops->power_mgmt(priv->sbus_priv, true);
if (ret) {
wiphy_err(priv->hw->wiphy,
"PM request failed: %d. WoW is disabled.\n", ret);
cw1200_wow_resume(hw);
return -EBUSY;
}
/* Force resume if event is coming from the device. */
if (atomic_read(&priv->bh_rx)) {
cw1200_wow_resume(hw);
return -EAGAIN;
}
return 0;
revert5:
WARN_ON(cw1200_bh_resume(priv));
revert4:
cw1200_resume_work(priv, &priv->bss_loss_work,
state->bss_loss_tmo);
cw1200_resume_work(priv, &priv->join_timeout,
state->join_tmo);
cw1200_resume_work(priv, &priv->scan.probe_work,
state->direct_probe);
cw1200_resume_work(priv, &priv->link_id_gc_work,
state->link_id_gc);
kfree(state);
revert3:
wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_off);
wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_off);
revert2:
wsm_unlock_tx(priv);
up(&priv->scan.lock);
revert1:
mutex_unlock(&priv->conf_mutex);
return -EBUSY;
}
int cw1200_wow_resume(struct ieee80211_hw *hw)
{
struct cw1200_common *priv = hw->priv;
struct cw1200_pm_state *pm_state = &priv->pm_state;
struct cw1200_suspend_state *state;
state = pm_state->suspend_state;
pm_state->suspend_state = NULL;
/* Disable IRQ wake */
priv->sbus_ops->power_mgmt(priv->sbus_priv, false);
/* Scan.lock must be released before BH is resumed other way
* in case when BSS_LOST command arrived the processing of the
* command will be delayed.
*/
up(&priv->scan.lock);
/* Resume BH thread */
WARN_ON(cw1200_bh_resume(priv));
/* Restores previous PS mode */
if (!priv->vif->p2p && priv->join_status == CW1200_JOIN_STATUS_STA) {
priv->powersave_mode.mode = state->prev_ps_mode;
cw1200_set_pm(priv, &priv->powersave_mode);
}
if (state->beacon_skipping) {
wsm_set_beacon_wakeup_period(priv, priv->beacon_int *
priv->join_dtim_period >
MAX_BEACON_SKIP_TIME_MS ? 1 :
priv->join_dtim_period, 0);
state->beacon_skipping = false;
}
/* Resume delayed work */
cw1200_resume_work(priv, &priv->bss_loss_work,
state->bss_loss_tmo);
cw1200_resume_work(priv, &priv->join_timeout,
state->join_tmo);
cw1200_resume_work(priv, &priv->scan.probe_work,
state->direct_probe);
cw1200_resume_work(priv, &priv->link_id_gc_work,
state->link_id_gc);
/* Remove UDP port filter */
wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_off);
/* Remove ethernet frame type filter */
wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_off);
/* Unlock datapath */
wsm_unlock_tx(priv);
/* Unlock configuration mutex */
mutex_unlock(&priv->conf_mutex);
/* Free memory */
kfree(state);
return 0;
}
/*
* Mac80211 power management interface for ST-Ericsson CW1200 mac80211 drivers
*
* Copyright (c) 2011, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef PM_H_INCLUDED
#define PM_H_INCLUDED
/* ******************************************************************** */
/* mac80211 API */
/* extern */ struct cw1200_common;
/* private */ struct cw1200_suspend_state;
struct cw1200_pm_state {
struct cw1200_suspend_state *suspend_state;
struct timer_list stay_awake;
struct platform_device *pm_dev;
spinlock_t lock; /* Protect access */
};
int cw1200_pm_init(struct cw1200_pm_state *pm,
struct cw1200_common *priv);
void cw1200_pm_deinit(struct cw1200_pm_state *pm);
void cw1200_pm_stay_awake(struct cw1200_pm_state *pm,
unsigned long tmo);
int cw1200_wow_suspend(struct ieee80211_hw *hw,
struct cfg80211_wowlan *wowlan);
int cw1200_wow_resume(struct ieee80211_hw *hw);
int cw1200_can_suspend(struct cw1200_common *priv);
#endif
/*
* O(1) TX queue with built-in allocator for ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <net/mac80211.h>
#include <linux/sched.h>
#include "queue.h"
#include "cw1200.h"
#include "debug.h"
/* private */ struct cw1200_queue_item
{
struct list_head head;
struct sk_buff *skb;
u32 packet_id;
unsigned long queue_timestamp;
unsigned long xmit_timestamp;
struct cw1200_txpriv txpriv;
u8 generation;
};
static inline void __cw1200_queue_lock(struct cw1200_queue *queue)
{
struct cw1200_queue_stats *stats = queue->stats;
if (queue->tx_locked_cnt++ == 0) {
pr_debug("[TX] Queue %d is locked.\n",
queue->queue_id);
ieee80211_stop_queue(stats->priv->hw, queue->queue_id);
}
}
static inline void __cw1200_queue_unlock(struct cw1200_queue *queue)
{
struct cw1200_queue_stats *stats = queue->stats;
BUG_ON(!queue->tx_locked_cnt);
if (--queue->tx_locked_cnt == 0) {
pr_debug("[TX] Queue %d is unlocked.\n",
queue->queue_id);
ieee80211_wake_queue(stats->priv->hw, queue->queue_id);
}
}
static inline void cw1200_queue_parse_id(u32 packet_id, u8 *queue_generation,
u8 *queue_id, u8 *item_generation,
u8 *item_id)
{
*item_id = (packet_id >> 0) & 0xFF;
*item_generation = (packet_id >> 8) & 0xFF;
*queue_id = (packet_id >> 16) & 0xFF;
*queue_generation = (packet_id >> 24) & 0xFF;
}
static inline u32 cw1200_queue_mk_packet_id(u8 queue_generation, u8 queue_id,
u8 item_generation, u8 item_id)
{
return ((u32)item_id << 0) |
((u32)item_generation << 8) |
((u32)queue_id << 16) |
((u32)queue_generation << 24);
}
static void cw1200_queue_post_gc(struct cw1200_queue_stats *stats,
struct list_head *gc_list)
{
struct cw1200_queue_item *item, *tmp;
list_for_each_entry_safe(item, tmp, gc_list, head) {
list_del(&item->head);
stats->skb_dtor(stats->priv, item->skb, &item->txpriv);
kfree(item);
}
}
static void cw1200_queue_register_post_gc(struct list_head *gc_list,
struct cw1200_queue_item *item)
{
struct cw1200_queue_item *gc_item;
gc_item = kmalloc(sizeof(struct cw1200_queue_item),
GFP_ATOMIC);
BUG_ON(!gc_item);
memcpy(gc_item, item, sizeof(struct cw1200_queue_item));
list_add_tail(&gc_item->head, gc_list);
}
static void __cw1200_queue_gc(struct cw1200_queue *queue,
struct list_head *head,
bool unlock)
{
struct cw1200_queue_stats *stats = queue->stats;
struct cw1200_queue_item *item = NULL, *tmp;
bool wakeup_stats = false;
list_for_each_entry_safe(item, tmp, &queue->queue, head) {
if (jiffies - item->queue_timestamp < queue->ttl)
break;
--queue->num_queued;
--queue->link_map_cache[item->txpriv.link_id];
spin_lock_bh(&stats->lock);
--stats->num_queued;
if (!--stats->link_map_cache[item->txpriv.link_id])
wakeup_stats = true;
spin_unlock_bh(&stats->lock);
cw1200_debug_tx_ttl(stats->priv);
cw1200_queue_register_post_gc(head, item);
item->skb = NULL;
list_move_tail(&item->head, &queue->free_pool);
}
if (wakeup_stats)
wake_up(&stats->wait_link_id_empty);
if (queue->overfull) {
if (queue->num_queued <= (queue->capacity >> 1)) {
queue->overfull = false;
if (unlock)
__cw1200_queue_unlock(queue);
} else if (item) {
unsigned long tmo = item->queue_timestamp + queue->ttl;
mod_timer(&queue->gc, tmo);
cw1200_pm_stay_awake(&stats->priv->pm_state,
tmo - jiffies);
}
}
}
static void cw1200_queue_gc(unsigned long arg)
{
LIST_HEAD(list);
struct cw1200_queue *queue =
(struct cw1200_queue *)arg;
spin_lock_bh(&queue->lock);
__cw1200_queue_gc(queue, &list, true);
spin_unlock_bh(&queue->lock);
cw1200_queue_post_gc(queue->stats, &list);
}
int cw1200_queue_stats_init(struct cw1200_queue_stats *stats,
size_t map_capacity,
cw1200_queue_skb_dtor_t skb_dtor,
struct cw1200_common *priv)
{
memset(stats, 0, sizeof(*stats));
stats->map_capacity = map_capacity;
stats->skb_dtor = skb_dtor;
stats->priv = priv;
spin_lock_init(&stats->lock);
init_waitqueue_head(&stats->wait_link_id_empty);
stats->link_map_cache = kzalloc(sizeof(int) * map_capacity,
GFP_KERNEL);
if (!stats->link_map_cache)
return -ENOMEM;
return 0;
}
int cw1200_queue_init(struct cw1200_queue *queue,
struct cw1200_queue_stats *stats,
u8 queue_id,
size_t capacity,
unsigned long ttl)
{
size_t i;
memset(queue, 0, sizeof(*queue));
queue->stats = stats;
queue->capacity = capacity;
queue->queue_id = queue_id;
queue->ttl = ttl;
INIT_LIST_HEAD(&queue->queue);
INIT_LIST_HEAD(&queue->pending);
INIT_LIST_HEAD(&queue->free_pool);
spin_lock_init(&queue->lock);
init_timer(&queue->gc);
queue->gc.data = (unsigned long)queue;
queue->gc.function = cw1200_queue_gc;
queue->pool = kzalloc(sizeof(struct cw1200_queue_item) * capacity,
GFP_KERNEL);
if (!queue->pool)
return -ENOMEM;
queue->link_map_cache = kzalloc(sizeof(int) * stats->map_capacity,
GFP_KERNEL);
if (!queue->link_map_cache) {
kfree(queue->pool);
queue->pool = NULL;
return -ENOMEM;
}
for (i = 0; i < capacity; ++i)
list_add_tail(&queue->pool[i].head, &queue->free_pool);
return 0;
}
int cw1200_queue_clear(struct cw1200_queue *queue)
{
int i;
LIST_HEAD(gc_list);
struct cw1200_queue_stats *stats = queue->stats;
struct cw1200_queue_item *item, *tmp;
spin_lock_bh(&queue->lock);
queue->generation++;
list_splice_tail_init(&queue->queue, &queue->pending);
list_for_each_entry_safe(item, tmp, &queue->pending, head) {
WARN_ON(!item->skb);
cw1200_queue_register_post_gc(&gc_list, item);
item->skb = NULL;
list_move_tail(&item->head, &queue->free_pool);
}
queue->num_queued = 0;
queue->num_pending = 0;
spin_lock_bh(&stats->lock);
for (i = 0; i < stats->map_capacity; ++i) {
stats->num_queued -= queue->link_map_cache[i];
stats->link_map_cache[i] -= queue->link_map_cache[i];
queue->link_map_cache[i] = 0;
}
spin_unlock_bh(&stats->lock);
if (queue->overfull) {
queue->overfull = false;
__cw1200_queue_unlock(queue);
}
spin_unlock_bh(&queue->lock);
wake_up(&stats->wait_link_id_empty);
cw1200_queue_post_gc(stats, &gc_list);
return 0;
}
void cw1200_queue_stats_deinit(struct cw1200_queue_stats *stats)
{
kfree(stats->link_map_cache);
stats->link_map_cache = NULL;
}
void cw1200_queue_deinit(struct cw1200_queue *queue)
{
cw1200_queue_clear(queue);
del_timer_sync(&queue->gc);
INIT_LIST_HEAD(&queue->free_pool);
kfree(queue->pool);
kfree(queue->link_map_cache);
queue->pool = NULL;
queue->link_map_cache = NULL;
queue->capacity = 0;
}
size_t cw1200_queue_get_num_queued(struct cw1200_queue *queue,
u32 link_id_map)
{
size_t ret;
int i, bit;
size_t map_capacity = queue->stats->map_capacity;
if (!link_id_map)
return 0;
spin_lock_bh(&queue->lock);
if (link_id_map == (u32)-1) {
ret = queue->num_queued - queue->num_pending;
} else {
ret = 0;
for (i = 0, bit = 1; i < map_capacity; ++i, bit <<= 1) {
if (link_id_map & bit)
ret += queue->link_map_cache[i];
}
}
spin_unlock_bh(&queue->lock);
return ret;
}
int cw1200_queue_put(struct cw1200_queue *queue,
struct sk_buff *skb,
struct cw1200_txpriv *txpriv)
{
int ret = 0;
LIST_HEAD(gc_list);
struct cw1200_queue_stats *stats = queue->stats;
if (txpriv->link_id >= queue->stats->map_capacity)
return -EINVAL;
spin_lock_bh(&queue->lock);
if (!WARN_ON(list_empty(&queue->free_pool))) {
struct cw1200_queue_item *item = list_first_entry(
&queue->free_pool, struct cw1200_queue_item, head);
BUG_ON(item->skb);
list_move_tail(&item->head, &queue->queue);
item->skb = skb;
item->txpriv = *txpriv;
item->generation = 0;
item->packet_id = cw1200_queue_mk_packet_id(queue->generation,
queue->queue_id,
item->generation,
item - queue->pool);
item->queue_timestamp = jiffies;
++queue->num_queued;
++queue->link_map_cache[txpriv->link_id];
spin_lock_bh(&stats->lock);
++stats->num_queued;
++stats->link_map_cache[txpriv->link_id];
spin_unlock_bh(&stats->lock);
/* TX may happen in parallel sometimes.
* Leave extra queue slots so we don't overflow.
*/
if (queue->overfull == false &&
queue->num_queued >=
(queue->capacity - (num_present_cpus() - 1))) {
queue->overfull = true;
__cw1200_queue_lock(queue);
mod_timer(&queue->gc, jiffies);
}
} else {
ret = -ENOENT;
}
spin_unlock_bh(&queue->lock);
return ret;
}
int cw1200_queue_get(struct cw1200_queue *queue,
u32 link_id_map,
struct wsm_tx **tx,
struct ieee80211_tx_info **tx_info,
const struct cw1200_txpriv **txpriv)
{
int ret = -ENOENT;
struct cw1200_queue_item *item;
struct cw1200_queue_stats *stats = queue->stats;
bool wakeup_stats = false;
spin_lock_bh(&queue->lock);
list_for_each_entry(item, &queue->queue, head) {
if (link_id_map & BIT(item->txpriv.link_id)) {
ret = 0;
break;
}
}
if (!WARN_ON(ret)) {
*tx = (struct wsm_tx *)item->skb->data;
*tx_info = IEEE80211_SKB_CB(item->skb);
*txpriv = &item->txpriv;
(*tx)->packet_id = __cpu_to_le32(item->packet_id);
list_move_tail(&item->head, &queue->pending);
++queue->num_pending;
--queue->link_map_cache[item->txpriv.link_id];
item->xmit_timestamp = jiffies;
spin_lock_bh(&stats->lock);
--stats->num_queued;
if (!--stats->link_map_cache[item->txpriv.link_id])
wakeup_stats = true;
spin_unlock_bh(&stats->lock);
}
spin_unlock_bh(&queue->lock);
if (wakeup_stats)
wake_up(&stats->wait_link_id_empty);
return ret;
}
int cw1200_queue_requeue(struct cw1200_queue *queue, u32 packet_id)
{
int ret = 0;
u8 queue_generation, queue_id, item_generation, item_id;
struct cw1200_queue_item *item;
struct cw1200_queue_stats *stats = queue->stats;
cw1200_queue_parse_id(packet_id, &queue_generation, &queue_id,
&item_generation, &item_id);
item = &queue->pool[item_id];
spin_lock_bh(&queue->lock);
BUG_ON(queue_id != queue->queue_id);
if (queue_generation != queue->generation) {
ret = -ENOENT;
} else if (item_id >= (unsigned) queue->capacity) {
WARN_ON(1);
ret = -EINVAL;
} else if (item->generation != item_generation) {
WARN_ON(1);
ret = -ENOENT;
} else {
--queue->num_pending;
++queue->link_map_cache[item->txpriv.link_id];
spin_lock_bh(&stats->lock);
++stats->num_queued;
++stats->link_map_cache[item->txpriv.link_id];
spin_unlock_bh(&stats->lock);
item->generation = ++item_generation;
item->packet_id = cw1200_queue_mk_packet_id(queue_generation,
queue_id,
item_generation,
item_id);
list_move(&item->head, &queue->queue);
}
spin_unlock_bh(&queue->lock);
return ret;
}
int cw1200_queue_requeue_all(struct cw1200_queue *queue)
{
struct cw1200_queue_item *item, *tmp;
struct cw1200_queue_stats *stats = queue->stats;
spin_lock_bh(&queue->lock);
list_for_each_entry_safe_reverse(item, tmp, &queue->pending, head) {
--queue->num_pending;
++queue->link_map_cache[item->txpriv.link_id];
spin_lock_bh(&stats->lock);
++stats->num_queued;
++stats->link_map_cache[item->txpriv.link_id];
spin_unlock_bh(&stats->lock);
++item->generation;
item->packet_id = cw1200_queue_mk_packet_id(queue->generation,
queue->queue_id,
item->generation,
item - queue->pool);
list_move(&item->head, &queue->queue);
}
spin_unlock_bh(&queue->lock);
return 0;
}
int cw1200_queue_remove(struct cw1200_queue *queue, u32 packet_id)
{
int ret = 0;
u8 queue_generation, queue_id, item_generation, item_id;
struct cw1200_queue_item *item;
struct cw1200_queue_stats *stats = queue->stats;
struct sk_buff *gc_skb = NULL;
struct cw1200_txpriv gc_txpriv;
cw1200_queue_parse_id(packet_id, &queue_generation, &queue_id,
&item_generation, &item_id);
item = &queue->pool[item_id];
spin_lock_bh(&queue->lock);
BUG_ON(queue_id != queue->queue_id);
if (queue_generation != queue->generation) {
ret = -ENOENT;
} else if (item_id >= (unsigned) queue->capacity) {
WARN_ON(1);
ret = -EINVAL;
} else if (item->generation != item_generation) {
WARN_ON(1);
ret = -ENOENT;
} else {
gc_txpriv = item->txpriv;
gc_skb = item->skb;
item->skb = NULL;
--queue->num_pending;
--queue->num_queued;
++queue->num_sent;
++item->generation;
/* Do not use list_move_tail here, but list_move:
* try to utilize cache row.
*/
list_move(&item->head, &queue->free_pool);
if (queue->overfull &&
(queue->num_queued <= (queue->capacity >> 1))) {
queue->overfull = false;
__cw1200_queue_unlock(queue);
}
}
spin_unlock_bh(&queue->lock);
if (gc_skb)
stats->skb_dtor(stats->priv, gc_skb, &gc_txpriv);
return ret;
}
int cw1200_queue_get_skb(struct cw1200_queue *queue, u32 packet_id,
struct sk_buff **skb,
const struct cw1200_txpriv **txpriv)
{
int ret = 0;
u8 queue_generation, queue_id, item_generation, item_id;
struct cw1200_queue_item *item;
cw1200_queue_parse_id(packet_id, &queue_generation, &queue_id,
&item_generation, &item_id);
item = &queue->pool[item_id];
spin_lock_bh(&queue->lock);
BUG_ON(queue_id != queue->queue_id);
if (queue_generation != queue->generation) {
ret = -ENOENT;
} else if (item_id >= (unsigned) queue->capacity) {
WARN_ON(1);
ret = -EINVAL;
} else if (item->generation != item_generation) {
WARN_ON(1);
ret = -ENOENT;
} else {
*skb = item->skb;
*txpriv = &item->txpriv;
}
spin_unlock_bh(&queue->lock);
return ret;
}
void cw1200_queue_lock(struct cw1200_queue *queue)
{
spin_lock_bh(&queue->lock);
__cw1200_queue_lock(queue);
spin_unlock_bh(&queue->lock);
}
void cw1200_queue_unlock(struct cw1200_queue *queue)
{
spin_lock_bh(&queue->lock);
__cw1200_queue_unlock(queue);
spin_unlock_bh(&queue->lock);
}
bool cw1200_queue_get_xmit_timestamp(struct cw1200_queue *queue,
unsigned long *timestamp,
u32 pending_frame_id)
{
struct cw1200_queue_item *item;
bool ret;
spin_lock_bh(&queue->lock);
ret = !list_empty(&queue->pending);
if (ret) {
list_for_each_entry(item, &queue->pending, head) {
if (item->packet_id != pending_frame_id)
if (time_before(item->xmit_timestamp,
*timestamp))
*timestamp = item->xmit_timestamp;
}
}
spin_unlock_bh(&queue->lock);
return ret;
}
bool cw1200_queue_stats_is_empty(struct cw1200_queue_stats *stats,
u32 link_id_map)
{
bool empty = true;
spin_lock_bh(&stats->lock);
if (link_id_map == (u32)-1) {
empty = stats->num_queued == 0;
} else {
int i;
for (i = 0; i < stats->map_capacity; ++i) {
if (link_id_map & BIT(i)) {
if (stats->link_map_cache[i]) {
empty = false;
break;
}
}
}
}
spin_unlock_bh(&stats->lock);
return empty;
}
/*
* O(1) TX queue with built-in allocator for ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef CW1200_QUEUE_H_INCLUDED
#define CW1200_QUEUE_H_INCLUDED
/* private */ struct cw1200_queue_item;
/* extern */ struct sk_buff;
/* extern */ struct wsm_tx;
/* extern */ struct cw1200_common;
/* extern */ struct ieee80211_tx_queue_stats;
/* extern */ struct cw1200_txpriv;
/* forward */ struct cw1200_queue_stats;
typedef void (*cw1200_queue_skb_dtor_t)(struct cw1200_common *priv,
struct sk_buff *skb,
const struct cw1200_txpriv *txpriv);
struct cw1200_queue {
struct cw1200_queue_stats *stats;
size_t capacity;
size_t num_queued;
size_t num_pending;
size_t num_sent;
struct cw1200_queue_item *pool;
struct list_head queue;
struct list_head free_pool;
struct list_head pending;
int tx_locked_cnt;
int *link_map_cache;
bool overfull;
spinlock_t lock; /* Protect queue entry */
u8 queue_id;
u8 generation;
struct timer_list gc;
unsigned long ttl;
};
struct cw1200_queue_stats {
spinlock_t lock; /* Protect stats entry */
int *link_map_cache;
int num_queued;
size_t map_capacity;
wait_queue_head_t wait_link_id_empty;
cw1200_queue_skb_dtor_t skb_dtor;
struct cw1200_common *priv;
};
struct cw1200_txpriv {
u8 link_id;
u8 raw_link_id;
u8 tid;
u8 rate_id;
u8 offset;
};
int cw1200_queue_stats_init(struct cw1200_queue_stats *stats,
size_t map_capacity,
cw1200_queue_skb_dtor_t skb_dtor,
struct cw1200_common *priv);
int cw1200_queue_init(struct cw1200_queue *queue,
struct cw1200_queue_stats *stats,
u8 queue_id,
size_t capacity,
unsigned long ttl);
int cw1200_queue_clear(struct cw1200_queue *queue);
void cw1200_queue_stats_deinit(struct cw1200_queue_stats *stats);
void cw1200_queue_deinit(struct cw1200_queue *queue);
size_t cw1200_queue_get_num_queued(struct cw1200_queue *queue,
u32 link_id_map);
int cw1200_queue_put(struct cw1200_queue *queue,
struct sk_buff *skb,
struct cw1200_txpriv *txpriv);
int cw1200_queue_get(struct cw1200_queue *queue,
u32 link_id_map,
struct wsm_tx **tx,
struct ieee80211_tx_info **tx_info,
const struct cw1200_txpriv **txpriv);
int cw1200_queue_requeue(struct cw1200_queue *queue, u32 packet_id);
int cw1200_queue_requeue_all(struct cw1200_queue *queue);
int cw1200_queue_remove(struct cw1200_queue *queue,
u32 packet_id);
int cw1200_queue_get_skb(struct cw1200_queue *queue, u32 packet_id,
struct sk_buff **skb,
const struct cw1200_txpriv **txpriv);
void cw1200_queue_lock(struct cw1200_queue *queue);
void cw1200_queue_unlock(struct cw1200_queue *queue);
bool cw1200_queue_get_xmit_timestamp(struct cw1200_queue *queue,
unsigned long *timestamp,
u32 pending_frame_id);
bool cw1200_queue_stats_is_empty(struct cw1200_queue_stats *stats,
u32 link_id_map);
static inline u8 cw1200_queue_get_queue_id(u32 packet_id)
{
return (packet_id >> 16) & 0xFF;
}
static inline u8 cw1200_queue_get_generation(u32 packet_id)
{
return (packet_id >> 8) & 0xFF;
}
#endif /* CW1200_QUEUE_H_INCLUDED */
/*
* Common sbus abstraction layer interface for cw1200 wireless driver
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef CW1200_SBUS_H
#define CW1200_SBUS_H
/*
* sbus priv forward definition.
* Implemented and instantiated in particular modules.
*/
struct sbus_priv;
void cw1200_irq_handler(struct cw1200_common *priv);
/* This MUST be wrapped with sbus_ops->lock/unlock! */
int __cw1200_irq_enable(struct cw1200_common *priv, int enable);
struct sbus_ops {
int (*sbus_memcpy_fromio)(struct sbus_priv *self, unsigned int addr,
void *dst, int count);
int (*sbus_memcpy_toio)(struct sbus_priv *self, unsigned int addr,
const void *src, int count);
void (*lock)(struct sbus_priv *self);
void (*unlock)(struct sbus_priv *self);
size_t (*align_size)(struct sbus_priv *self, size_t size);
int (*power_mgmt)(struct sbus_priv *self, bool suspend);
};
#endif /* CW1200_SBUS_H */
/*
* Scan implementation for ST-Ericsson CW1200 mac80211 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/sched.h>
#include "cw1200.h"
#include "scan.h"
#include "sta.h"
#include "pm.h"
static void cw1200_scan_restart_delayed(struct cw1200_common *priv);
static int cw1200_scan_start(struct cw1200_common *priv, struct wsm_scan *scan)
{
int ret, i;
int tmo = 2000;
switch (priv->join_status) {
case CW1200_JOIN_STATUS_PRE_STA:
case CW1200_JOIN_STATUS_JOINING:
return -EBUSY;
default:
break;
}
wiphy_dbg(priv->hw->wiphy, "[SCAN] hw req, type %d, %d channels, flags: 0x%x.\n",
scan->type, scan->num_channels, scan->flags);
for (i = 0; i < scan->num_channels; ++i)
tmo += scan->ch[i].max_chan_time + 10;
cancel_delayed_work_sync(&priv->clear_recent_scan_work);
atomic_set(&priv->scan.in_progress, 1);
atomic_set(&priv->recent_scan, 1);
cw1200_pm_stay_awake(&priv->pm_state, tmo * HZ / 1000);
queue_delayed_work(priv->workqueue, &priv->scan.timeout,
tmo * HZ / 1000);
ret = wsm_scan(priv, scan);
if (ret) {
atomic_set(&priv->scan.in_progress, 0);
cancel_delayed_work_sync(&priv->scan.timeout);
cw1200_scan_restart_delayed(priv);
}
return ret;
}
int cw1200_hw_scan(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct cfg80211_scan_request *req)
{
struct cw1200_common *priv = hw->priv;
struct wsm_template_frame frame = {
.frame_type = WSM_FRAME_TYPE_PROBE_REQUEST,
};
int i, ret;
if (!priv->vif)
return -EINVAL;
/* Scan when P2P_GO corrupt firmware MiniAP mode */
if (priv->join_status == CW1200_JOIN_STATUS_AP)
return -EOPNOTSUPP;
if (req->n_ssids == 1 && !req->ssids[0].ssid_len)
req->n_ssids = 0;
wiphy_dbg(hw->wiphy, "[SCAN] Scan request for %d SSIDs.\n",
req->n_ssids);
if (req->n_ssids > WSM_SCAN_MAX_NUM_OF_SSIDS)
return -EINVAL;
frame.skb = ieee80211_probereq_get(hw, priv->vif, NULL, 0,
req->ie_len);
if (!frame.skb)
return -ENOMEM;
if (req->ie_len)
memcpy(skb_put(frame.skb, req->ie_len), req->ie, req->ie_len);
/* will be unlocked in cw1200_scan_work() */
down(&priv->scan.lock);
mutex_lock(&priv->conf_mutex);
ret = wsm_set_template_frame(priv, &frame);
if (!ret) {
/* Host want to be the probe responder. */
ret = wsm_set_probe_responder(priv, true);
}
if (ret) {
mutex_unlock(&priv->conf_mutex);
up(&priv->scan.lock);
dev_kfree_skb(frame.skb);
return ret;
}
wsm_lock_tx(priv);
BUG_ON(priv->scan.req);
priv->scan.req = req;
priv->scan.n_ssids = 0;
priv->scan.status = 0;
priv->scan.begin = &req->channels[0];
priv->scan.curr = priv->scan.begin;
priv->scan.end = &req->channels[req->n_channels];
priv->scan.output_power = priv->output_power;
for (i = 0; i < req->n_ssids; ++i) {
struct wsm_ssid *dst = &priv->scan.ssids[priv->scan.n_ssids];
memcpy(&dst->ssid[0], req->ssids[i].ssid, sizeof(dst->ssid));
dst->length = req->ssids[i].ssid_len;
++priv->scan.n_ssids;
}
mutex_unlock(&priv->conf_mutex);
if (frame.skb)
dev_kfree_skb(frame.skb);
queue_work(priv->workqueue, &priv->scan.work);
return 0;
}
void cw1200_scan_work(struct work_struct *work)
{
struct cw1200_common *priv = container_of(work, struct cw1200_common,
scan.work);
struct ieee80211_channel **it;
struct wsm_scan scan = {
.type = WSM_SCAN_TYPE_FOREGROUND,
.flags = WSM_SCAN_FLAG_SPLIT_METHOD,
};
bool first_run = (priv->scan.begin == priv->scan.curr &&
priv->scan.begin != priv->scan.end);
int i;
if (first_run) {
/* Firmware gets crazy if scan request is sent
* when STA is joined but not yet associated.
* Force unjoin in this case.
*/
if (cancel_delayed_work_sync(&priv->join_timeout) > 0)
cw1200_join_timeout(&priv->join_timeout.work);
}
mutex_lock(&priv->conf_mutex);
if (first_run) {
if (priv->join_status == CW1200_JOIN_STATUS_STA &&
!(priv->powersave_mode.mode & WSM_PSM_PS)) {
struct wsm_set_pm pm = priv->powersave_mode;
pm.mode = WSM_PSM_PS;
cw1200_set_pm(priv, &pm);
} else if (priv->join_status == CW1200_JOIN_STATUS_MONITOR) {
/* FW bug: driver has to restart p2p-dev mode
* after scan
*/
cw1200_disable_listening(priv);
}
}
if (!priv->scan.req || (priv->scan.curr == priv->scan.end)) {
if (priv->scan.output_power != priv->output_power)
wsm_set_output_power(priv, priv->output_power * 10);
if (priv->join_status == CW1200_JOIN_STATUS_STA &&
!(priv->powersave_mode.mode & WSM_PSM_PS))
cw1200_set_pm(priv, &priv->powersave_mode);
if (priv->scan.status < 0)
wiphy_dbg(priv->hw->wiphy, "[SCAN] Scan failed (%d).\n",
priv->scan.status);
else if (priv->scan.req)
wiphy_dbg(priv->hw->wiphy,
"[SCAN] Scan completed.\n");
else
wiphy_dbg(priv->hw->wiphy,
"[SCAN] Scan canceled.\n");
priv->scan.req = NULL;
cw1200_scan_restart_delayed(priv);
wsm_unlock_tx(priv);
mutex_unlock(&priv->conf_mutex);
ieee80211_scan_completed(priv->hw, priv->scan.status ? 1 : 0);
up(&priv->scan.lock);
return;
} else {
struct ieee80211_channel *first = *priv->scan.curr;
for (it = priv->scan.curr + 1, i = 1;
it != priv->scan.end && i < WSM_SCAN_MAX_NUM_OF_CHANNELS;
++it, ++i) {
if ((*it)->band != first->band)
break;
if (((*it)->flags ^ first->flags) &
IEEE80211_CHAN_PASSIVE_SCAN)
break;
if (!(first->flags & IEEE80211_CHAN_PASSIVE_SCAN) &&
(*it)->max_power != first->max_power)
break;
}
scan.band = first->band;
if (priv->scan.req->no_cck)
scan.max_tx_rate = WSM_TRANSMIT_RATE_6;
else
scan.max_tx_rate = WSM_TRANSMIT_RATE_1;
scan.num_probes =
(first->flags & IEEE80211_CHAN_PASSIVE_SCAN) ? 0 : 2;
scan.num_ssids = priv->scan.n_ssids;
scan.ssids = &priv->scan.ssids[0];
scan.num_channels = it - priv->scan.curr;
/* TODO: Is it optimal? */
scan.probe_delay = 100;
/* It is not stated in WSM specification, however
* FW team says that driver may not use FG scan
* when joined.
*/
if (priv->join_status == CW1200_JOIN_STATUS_STA) {
scan.type = WSM_SCAN_TYPE_BACKGROUND;
scan.flags = WSM_SCAN_FLAG_FORCE_BACKGROUND;
}
scan.ch = kzalloc(
sizeof(struct wsm_scan_ch) * (it - priv->scan.curr),
GFP_KERNEL);
if (!scan.ch) {
priv->scan.status = -ENOMEM;
goto fail;
}
for (i = 0; i < scan.num_channels; ++i) {
scan.ch[i].number = priv->scan.curr[i]->hw_value;
if (priv->scan.curr[i]->flags & IEEE80211_CHAN_PASSIVE_SCAN) {
scan.ch[i].min_chan_time = 50;
scan.ch[i].max_chan_time = 100;
} else {
scan.ch[i].min_chan_time = 10;
scan.ch[i].max_chan_time = 25;
}
}
if (!(first->flags & IEEE80211_CHAN_PASSIVE_SCAN) &&
priv->scan.output_power != first->max_power) {
priv->scan.output_power = first->max_power;
wsm_set_output_power(priv,
priv->scan.output_power * 10);
}
priv->scan.status = cw1200_scan_start(priv, &scan);
kfree(scan.ch);
if (priv->scan.status)
goto fail;
priv->scan.curr = it;
}
mutex_unlock(&priv->conf_mutex);
return;
fail:
priv->scan.curr = priv->scan.end;
mutex_unlock(&priv->conf_mutex);
queue_work(priv->workqueue, &priv->scan.work);
return;
}
static void cw1200_scan_restart_delayed(struct cw1200_common *priv)
{
/* FW bug: driver has to restart p2p-dev mode after scan. */
if (priv->join_status == CW1200_JOIN_STATUS_MONITOR) {
cw1200_enable_listening(priv);
cw1200_update_filtering(priv);
}
if (priv->delayed_unjoin) {
priv->delayed_unjoin = false;
if (queue_work(priv->workqueue, &priv->unjoin_work) <= 0)
wsm_unlock_tx(priv);
} else if (priv->delayed_link_loss) {
wiphy_dbg(priv->hw->wiphy, "[CQM] Requeue BSS loss.\n");
priv->delayed_link_loss = 0;
cw1200_cqm_bssloss_sm(priv, 1, 0, 0);
}
}
static void cw1200_scan_complete(struct cw1200_common *priv)
{
queue_delayed_work(priv->workqueue, &priv->clear_recent_scan_work, HZ);
if (priv->scan.direct_probe) {
wiphy_dbg(priv->hw->wiphy, "[SCAN] Direct probe complete.\n");
cw1200_scan_restart_delayed(priv);
priv->scan.direct_probe = 0;
up(&priv->scan.lock);
wsm_unlock_tx(priv);
} else {
cw1200_scan_work(&priv->scan.work);
}
}
void cw1200_scan_failed_cb(struct cw1200_common *priv)
{
if (priv->mode == NL80211_IFTYPE_UNSPECIFIED)
/* STA is stopped. */
return;
if (cancel_delayed_work_sync(&priv->scan.timeout) > 0) {
priv->scan.status = -EIO;
queue_delayed_work(priv->workqueue, &priv->scan.timeout, 0);
}
}
void cw1200_scan_complete_cb(struct cw1200_common *priv,
struct wsm_scan_complete *arg)
{
if (priv->mode == NL80211_IFTYPE_UNSPECIFIED)
/* STA is stopped. */
return;
if (cancel_delayed_work_sync(&priv->scan.timeout) > 0) {
priv->scan.status = 1;
queue_delayed_work(priv->workqueue, &priv->scan.timeout, 0);
}
}
void cw1200_clear_recent_scan_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common,
clear_recent_scan_work.work);
atomic_xchg(&priv->recent_scan, 0);
}
void cw1200_scan_timeout(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, scan.timeout.work);
if (atomic_xchg(&priv->scan.in_progress, 0)) {
if (priv->scan.status > 0) {
priv->scan.status = 0;
} else if (!priv->scan.status) {
wiphy_warn(priv->hw->wiphy,
"Timeout waiting for scan complete notification.\n");
priv->scan.status = -ETIMEDOUT;
priv->scan.curr = priv->scan.end;
wsm_stop_scan(priv);
}
cw1200_scan_complete(priv);
}
}
void cw1200_probe_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, scan.probe_work.work);
u8 queue_id = cw1200_queue_get_queue_id(priv->pending_frame_id);
struct cw1200_queue *queue = &priv->tx_queue[queue_id];
const struct cw1200_txpriv *txpriv;
struct wsm_tx *wsm;
struct wsm_template_frame frame = {
.frame_type = WSM_FRAME_TYPE_PROBE_REQUEST,
};
struct wsm_ssid ssids[1] = {{
.length = 0,
} };
struct wsm_scan_ch ch[1] = {{
.min_chan_time = 0,
.max_chan_time = 10,
} };
struct wsm_scan scan = {
.type = WSM_SCAN_TYPE_FOREGROUND,
.num_probes = 1,
.probe_delay = 0,
.num_channels = 1,
.ssids = ssids,
.ch = ch,
};
u8 *ies;
size_t ies_len;
int ret;
wiphy_dbg(priv->hw->wiphy, "[SCAN] Direct probe work.\n");
mutex_lock(&priv->conf_mutex);
if (down_trylock(&priv->scan.lock)) {
/* Scan is already in progress. Requeue self. */
schedule();
queue_delayed_work(priv->workqueue,
&priv->scan.probe_work, HZ / 10);
mutex_unlock(&priv->conf_mutex);
return;
}
/* Make sure we still have a pending probe req */
if (cw1200_queue_get_skb(queue, priv->pending_frame_id,
&frame.skb, &txpriv)) {
up(&priv->scan.lock);
mutex_unlock(&priv->conf_mutex);
wsm_unlock_tx(priv);
return;
}
wsm = (struct wsm_tx *)frame.skb->data;
scan.max_tx_rate = wsm->max_tx_rate;
scan.band = (priv->channel->band == IEEE80211_BAND_5GHZ) ?
WSM_PHY_BAND_5G : WSM_PHY_BAND_2_4G;
if (priv->join_status == CW1200_JOIN_STATUS_STA ||
priv->join_status == CW1200_JOIN_STATUS_IBSS) {
scan.type = WSM_SCAN_TYPE_BACKGROUND;
scan.flags = WSM_SCAN_FLAG_FORCE_BACKGROUND;
}
ch[0].number = priv->channel->hw_value;
skb_pull(frame.skb, txpriv->offset);
ies = &frame.skb->data[sizeof(struct ieee80211_hdr_3addr)];
ies_len = frame.skb->len - sizeof(struct ieee80211_hdr_3addr);
if (ies_len) {
u8 *ssidie =
(u8 *)cfg80211_find_ie(WLAN_EID_SSID, ies, ies_len);
if (ssidie && ssidie[1] && ssidie[1] <= sizeof(ssids[0].ssid)) {
u8 *nextie = &ssidie[2 + ssidie[1]];
/* Remove SSID from the IE list. It has to be provided
* as a separate argument in cw1200_scan_start call
*/
/* Store SSID localy */
ssids[0].length = ssidie[1];
memcpy(ssids[0].ssid, &ssidie[2], ssids[0].length);
scan.num_ssids = 1;
/* Remove SSID from IE list */
ssidie[1] = 0;
memmove(&ssidie[2], nextie, &ies[ies_len] - nextie);
skb_trim(frame.skb, frame.skb->len - ssids[0].length);
}
}
/* FW bug: driver has to restart p2p-dev mode after scan */
if (priv->join_status == CW1200_JOIN_STATUS_MONITOR)
cw1200_disable_listening(priv);
ret = wsm_set_template_frame(priv, &frame);
priv->scan.direct_probe = 1;
if (!ret) {
wsm_flush_tx(priv);
ret = cw1200_scan_start(priv, &scan);
}
mutex_unlock(&priv->conf_mutex);
skb_push(frame.skb, txpriv->offset);
if (!ret)
IEEE80211_SKB_CB(frame.skb)->flags |= IEEE80211_TX_STAT_ACK;
BUG_ON(cw1200_queue_remove(queue, priv->pending_frame_id));
if (ret) {
priv->scan.direct_probe = 0;
up(&priv->scan.lock);
wsm_unlock_tx(priv);
}
return;
}
/*
* Scan interface for ST-Ericsson CW1200 mac80211 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef SCAN_H_INCLUDED
#define SCAN_H_INCLUDED
#include <linux/semaphore.h>
#include "wsm.h"
/* external */ struct sk_buff;
/* external */ struct cfg80211_scan_request;
/* external */ struct ieee80211_channel;
/* external */ struct ieee80211_hw;
/* external */ struct work_struct;
struct cw1200_scan {
struct semaphore lock;
struct work_struct work;
struct delayed_work timeout;
struct cfg80211_scan_request *req;
struct ieee80211_channel **begin;
struct ieee80211_channel **curr;
struct ieee80211_channel **end;
struct wsm_ssid ssids[WSM_SCAN_MAX_NUM_OF_SSIDS];
int output_power;
int n_ssids;
int status;
atomic_t in_progress;
/* Direct probe requests workaround */
struct delayed_work probe_work;
int direct_probe;
};
int cw1200_hw_scan(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct cfg80211_scan_request *req);
void cw1200_scan_work(struct work_struct *work);
void cw1200_scan_timeout(struct work_struct *work);
void cw1200_clear_recent_scan_work(struct work_struct *work);
void cw1200_scan_complete_cb(struct cw1200_common *priv,
struct wsm_scan_complete *arg);
void cw1200_scan_failed_cb(struct cw1200_common *priv);
/* ******************************************************************** */
/* Raw probe requests TX workaround */
void cw1200_probe_work(struct work_struct *work);
#endif
/*
* Mac80211 STA API for ST-Ericsson CW1200 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/vmalloc.h>
#include <linux/sched.h>
#include <linux/firmware.h>
#include <linux/module.h>
#include "cw1200.h"
#include "sta.h"
#include "fwio.h"
#include "bh.h"
#include "debug.h"
#ifndef ERP_INFO_BYTE_OFFSET
#define ERP_INFO_BYTE_OFFSET 2
#endif
static void cw1200_do_join(struct cw1200_common *priv);
static void cw1200_do_unjoin(struct cw1200_common *priv);
static int cw1200_upload_beacon(struct cw1200_common *priv);
static int cw1200_upload_pspoll(struct cw1200_common *priv);
static int cw1200_upload_null(struct cw1200_common *priv);
static int cw1200_upload_qosnull(struct cw1200_common *priv);
static int cw1200_start_ap(struct cw1200_common *priv);
static int cw1200_update_beaconing(struct cw1200_common *priv);
static int cw1200_enable_beaconing(struct cw1200_common *priv,
bool enable);
static void __cw1200_sta_notify(struct ieee80211_hw *dev,
struct ieee80211_vif *vif,
enum sta_notify_cmd notify_cmd,
int link_id);
static int __cw1200_flush(struct cw1200_common *priv, bool drop);
static inline void __cw1200_free_event_queue(struct list_head *list)
{
struct cw1200_wsm_event *event, *tmp;
list_for_each_entry_safe(event, tmp, list, link) {
list_del(&event->link);
kfree(event);
}
}
/* ******************************************************************** */
/* STA API */
int cw1200_start(struct ieee80211_hw *dev)
{
struct cw1200_common *priv = dev->priv;
int ret = 0;
cw1200_pm_stay_awake(&priv->pm_state, HZ);
mutex_lock(&priv->conf_mutex);
/* default EDCA */
WSM_EDCA_SET(&priv->edca, 0, 0x0002, 0x0003, 0x0007, 47, 0xc8, false);
WSM_EDCA_SET(&priv->edca, 1, 0x0002, 0x0007, 0x000f, 94, 0xc8, false);
WSM_EDCA_SET(&priv->edca, 2, 0x0003, 0x000f, 0x03ff, 0, 0xc8, false);
WSM_EDCA_SET(&priv->edca, 3, 0x0007, 0x000f, 0x03ff, 0, 0xc8, false);
ret = wsm_set_edca_params(priv, &priv->edca);
if (ret)
goto out;
ret = cw1200_set_uapsd_param(priv, &priv->edca);
if (ret)
goto out;
priv->setbssparams_done = false;
memcpy(priv->mac_addr, dev->wiphy->perm_addr, ETH_ALEN);
priv->mode = NL80211_IFTYPE_MONITOR;
priv->wep_default_key_id = -1;
priv->cqm_beacon_loss_count = 10;
ret = cw1200_setup_mac(priv);
if (ret)
goto out;
out:
mutex_unlock(&priv->conf_mutex);
return ret;
}
void cw1200_stop(struct ieee80211_hw *dev)
{
struct cw1200_common *priv = dev->priv;
LIST_HEAD(list);
int i;
wsm_lock_tx(priv);
while (down_trylock(&priv->scan.lock)) {
/* Scan is in progress. Force it to stop. */
priv->scan.req = NULL;
schedule();
}
up(&priv->scan.lock);
cancel_delayed_work_sync(&priv->scan.probe_work);
cancel_delayed_work_sync(&priv->scan.timeout);
cancel_delayed_work_sync(&priv->clear_recent_scan_work);
cancel_delayed_work_sync(&priv->join_timeout);
cw1200_cqm_bssloss_sm(priv, 0, 0, 0);
cancel_work_sync(&priv->unjoin_work);
cancel_delayed_work_sync(&priv->link_id_gc_work);
flush_workqueue(priv->workqueue);
del_timer_sync(&priv->mcast_timeout);
mutex_lock(&priv->conf_mutex);
priv->mode = NL80211_IFTYPE_UNSPECIFIED;
priv->listening = false;
spin_lock(&priv->event_queue_lock);
list_splice_init(&priv->event_queue, &list);
spin_unlock(&priv->event_queue_lock);
__cw1200_free_event_queue(&list);
priv->join_status = CW1200_JOIN_STATUS_PASSIVE;
priv->join_pending = false;
for (i = 0; i < 4; i++)
cw1200_queue_clear(&priv->tx_queue[i]);
mutex_unlock(&priv->conf_mutex);
tx_policy_clean(priv);
/* HACK! */
if (atomic_xchg(&priv->tx_lock, 1) != 1)
pr_debug("[STA] TX is force-unlocked due to stop request.\n");
wsm_unlock_tx(priv);
atomic_xchg(&priv->tx_lock, 0); /* for recovery to work */
}
static int cw1200_bssloss_mitigation = 1;
module_param(cw1200_bssloss_mitigation, int, 0644);
MODULE_PARM_DESC(cw1200_bssloss_mitigation, "BSS Loss mitigation. 0 == disabled, 1 == enabled (default)");
void __cw1200_cqm_bssloss_sm(struct cw1200_common *priv,
int init, int good, int bad)
{
int tx = 0;
priv->delayed_link_loss = 0;
cancel_work_sync(&priv->bss_params_work);
pr_debug("[STA] CQM BSSLOSS_SM: state: %d init %d good %d bad: %d txlock: %d uj: %d\n",
priv->bss_loss_state,
init, good, bad,
atomic_read(&priv->tx_lock),
priv->delayed_unjoin);
/* If we have a pending unjoin */
if (priv->delayed_unjoin)
return;
if (init) {
queue_delayed_work(priv->workqueue,
&priv->bss_loss_work,
HZ);
priv->bss_loss_state = 0;
/* Skip the confimration procedure in P2P case */
if (!priv->vif->p2p && !atomic_read(&priv->tx_lock))
tx = 1;
} else if (good) {
cancel_delayed_work_sync(&priv->bss_loss_work);
priv->bss_loss_state = 0;
queue_work(priv->workqueue, &priv->bss_params_work);
} else if (bad) {
/* XXX Should we just keep going until we time out? */
if (priv->bss_loss_state < 3)
tx = 1;
} else {
cancel_delayed_work_sync(&priv->bss_loss_work);
priv->bss_loss_state = 0;
}
/* Bypass mitigation if it's disabled */
if (!cw1200_bssloss_mitigation)
tx = 0;
/* Spit out a NULL packet to our AP if necessary */
if (tx) {
struct sk_buff *skb;
priv->bss_loss_state++;
skb = ieee80211_nullfunc_get(priv->hw, priv->vif);
WARN_ON(!skb);
if (skb)
cw1200_tx(priv->hw, NULL, skb);
}
}
int cw1200_add_interface(struct ieee80211_hw *dev,
struct ieee80211_vif *vif)
{
int ret;
struct cw1200_common *priv = dev->priv;
/* __le32 auto_calibration_mode = __cpu_to_le32(1); */
vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER |
IEEE80211_VIF_SUPPORTS_CQM_RSSI;
mutex_lock(&priv->conf_mutex);
if (priv->mode != NL80211_IFTYPE_MONITOR) {
mutex_unlock(&priv->conf_mutex);
return -EOPNOTSUPP;
}
switch (vif->type) {
case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_ADHOC:
case NL80211_IFTYPE_MESH_POINT:
case NL80211_IFTYPE_AP:
priv->mode = vif->type;
break;
default:
mutex_unlock(&priv->conf_mutex);
return -EOPNOTSUPP;
}
priv->vif = vif;
memcpy(priv->mac_addr, vif->addr, ETH_ALEN);
ret = cw1200_setup_mac(priv);
/* Enable auto-calibration */
/* Exception in subsequent channel switch; disabled.
* wsm_write_mib(priv, WSM_MIB_ID_SET_AUTO_CALIBRATION_MODE,
* &auto_calibration_mode, sizeof(auto_calibration_mode));
*/
mutex_unlock(&priv->conf_mutex);
return ret;
}
void cw1200_remove_interface(struct ieee80211_hw *dev,
struct ieee80211_vif *vif)
{
struct cw1200_common *priv = dev->priv;
struct wsm_reset reset = {
.reset_statistics = true,
};
int i;
mutex_lock(&priv->conf_mutex);
switch (priv->join_status) {
case CW1200_JOIN_STATUS_JOINING:
case CW1200_JOIN_STATUS_PRE_STA:
case CW1200_JOIN_STATUS_STA:
case CW1200_JOIN_STATUS_IBSS:
wsm_lock_tx(priv);
if (queue_work(priv->workqueue, &priv->unjoin_work) <= 0)
wsm_unlock_tx(priv);
break;
case CW1200_JOIN_STATUS_AP:
for (i = 0; priv->link_id_map; ++i) {
if (priv->link_id_map & BIT(i)) {
reset.link_id = i;
wsm_reset(priv, &reset);
priv->link_id_map &= ~BIT(i);
}
}
memset(priv->link_id_db, 0, sizeof(priv->link_id_db));
priv->sta_asleep_mask = 0;
priv->enable_beacon = false;
priv->tx_multicast = false;
priv->aid0_bit_set = false;
priv->buffered_multicasts = false;
priv->pspoll_mask = 0;
reset.link_id = 0;
wsm_reset(priv, &reset);
break;
case CW1200_JOIN_STATUS_MONITOR:
cw1200_update_listening(priv, false);
break;
default:
break;
}
priv->vif = NULL;
priv->mode = NL80211_IFTYPE_MONITOR;
memset(priv->mac_addr, 0, ETH_ALEN);
memset(&priv->p2p_ps_modeinfo, 0, sizeof(priv->p2p_ps_modeinfo));
cw1200_free_keys(priv);
cw1200_setup_mac(priv);
priv->listening = false;
priv->join_status = CW1200_JOIN_STATUS_PASSIVE;
if (!__cw1200_flush(priv, true))
wsm_unlock_tx(priv);
mutex_unlock(&priv->conf_mutex);
}
int cw1200_change_interface(struct ieee80211_hw *dev,
struct ieee80211_vif *vif,
enum nl80211_iftype new_type,
bool p2p)
{
int ret = 0;
pr_debug("change_interface new: %d (%d), old: %d (%d)\n", new_type,
p2p, vif->type, vif->p2p);
if (new_type != vif->type || vif->p2p != p2p) {
cw1200_remove_interface(dev, vif);
vif->type = new_type;
vif->p2p = p2p;
ret = cw1200_add_interface(dev, vif);
}
return ret;
}
int cw1200_config(struct ieee80211_hw *dev, u32 changed)
{
int ret = 0;
struct cw1200_common *priv = dev->priv;
struct ieee80211_conf *conf = &dev->conf;
pr_debug("CONFIG CHANGED: %08x\n", changed);
down(&priv->scan.lock);
mutex_lock(&priv->conf_mutex);
/* TODO: IEEE80211_CONF_CHANGE_QOS */
/* TODO: IEEE80211_CONF_CHANGE_LISTEN_INTERVAL */
if (changed & IEEE80211_CONF_CHANGE_POWER) {
priv->output_power = conf->power_level;
pr_debug("[STA] TX power: %d\n", priv->output_power);
wsm_set_output_power(priv, priv->output_power * 10);
}
if ((changed & IEEE80211_CONF_CHANGE_CHANNEL) &&
(priv->channel != conf->chandef.chan)) {
struct ieee80211_channel *ch = conf->chandef.chan;
struct wsm_switch_channel channel = {
.channel_number = ch->hw_value,
};
pr_debug("[STA] Freq %d (wsm ch: %d).\n",
ch->center_freq, ch->hw_value);
/* __cw1200_flush() implicitly locks tx, if successful */
if (!__cw1200_flush(priv, false)) {
if (!wsm_switch_channel(priv, &channel)) {
ret = wait_event_timeout(priv->channel_switch_done,
!priv->channel_switch_in_progress,
3 * HZ);
if (ret) {
/* Already unlocks if successful */
priv->channel = ch;
ret = 0;
} else {
ret = -ETIMEDOUT;
}
} else {
/* Unlock if switch channel fails */
wsm_unlock_tx(priv);
}
}
}
if (changed & IEEE80211_CONF_CHANGE_PS) {
if (!(conf->flags & IEEE80211_CONF_PS))
priv->powersave_mode.mode = WSM_PSM_ACTIVE;
else if (conf->dynamic_ps_timeout <= 0)
priv->powersave_mode.mode = WSM_PSM_PS;
else
priv->powersave_mode.mode = WSM_PSM_FAST_PS;
/* Firmware requires that value for this 1-byte field must
* be specified in units of 500us. Values above the 128ms
* threshold are not supported.
*/
if (conf->dynamic_ps_timeout >= 0x80)
priv->powersave_mode.fast_psm_idle_period = 0xFF;
else
priv->powersave_mode.fast_psm_idle_period =
conf->dynamic_ps_timeout << 1;
if (priv->join_status == CW1200_JOIN_STATUS_STA &&
priv->bss_params.aid)
cw1200_set_pm(priv, &priv->powersave_mode);
}
if (changed & IEEE80211_CONF_CHANGE_MONITOR) {
/* TBD: It looks like it's transparent
* there's a monitor interface present -- use this
* to determine for example whether to calculate
* timestamps for packets or not, do not use instead
* of filter flags!
*/
}
if (changed & IEEE80211_CONF_CHANGE_IDLE) {
struct wsm_operational_mode mode = {
.power_mode = cw1200_power_mode,
.disable_more_flag_usage = true,
};
wsm_lock_tx(priv);
/* Disable p2p-dev mode forced by TX request */
if ((priv->join_status == CW1200_JOIN_STATUS_MONITOR) &&
(conf->flags & IEEE80211_CONF_IDLE) &&
!priv->listening) {
cw1200_disable_listening(priv);
priv->join_status = CW1200_JOIN_STATUS_PASSIVE;
}
wsm_set_operational_mode(priv, &mode);
wsm_unlock_tx(priv);
}
if (changed & IEEE80211_CONF_CHANGE_RETRY_LIMITS) {
pr_debug("[STA] Retry limits: %d (long), %d (short).\n",
conf->long_frame_max_tx_count,
conf->short_frame_max_tx_count);
spin_lock_bh(&priv->tx_policy_cache.lock);
priv->long_frame_max_tx_count = conf->long_frame_max_tx_count;
priv->short_frame_max_tx_count =
(conf->short_frame_max_tx_count < 0x0F) ?
conf->short_frame_max_tx_count : 0x0F;
priv->hw->max_rate_tries = priv->short_frame_max_tx_count;
spin_unlock_bh(&priv->tx_policy_cache.lock);
}
mutex_unlock(&priv->conf_mutex);
up(&priv->scan.lock);
return ret;
}
void cw1200_update_filtering(struct cw1200_common *priv)
{
int ret;
bool bssid_filtering = !priv->rx_filter.bssid;
bool is_p2p = priv->vif && priv->vif->p2p;
bool is_sta = priv->vif && NL80211_IFTYPE_STATION == priv->vif->type;
static struct wsm_beacon_filter_control bf_ctrl;
static struct wsm_mib_beacon_filter_table bf_tbl = {
.entry[0].ie_id = WLAN_EID_VENDOR_SPECIFIC,
.entry[0].flags = WSM_BEACON_FILTER_IE_HAS_CHANGED |
WSM_BEACON_FILTER_IE_NO_LONGER_PRESENT |
WSM_BEACON_FILTER_IE_HAS_APPEARED,
.entry[0].oui[0] = 0x50,
.entry[0].oui[1] = 0x6F,
.entry[0].oui[2] = 0x9A,
.entry[1].ie_id = WLAN_EID_HT_OPERATION,
.entry[1].flags = WSM_BEACON_FILTER_IE_HAS_CHANGED |
WSM_BEACON_FILTER_IE_NO_LONGER_PRESENT |
WSM_BEACON_FILTER_IE_HAS_APPEARED,
.entry[2].ie_id = WLAN_EID_ERP_INFO,
.entry[2].flags = WSM_BEACON_FILTER_IE_HAS_CHANGED |
WSM_BEACON_FILTER_IE_NO_LONGER_PRESENT |
WSM_BEACON_FILTER_IE_HAS_APPEARED,
};
if (priv->join_status == CW1200_JOIN_STATUS_PASSIVE)
return;
else if (priv->join_status == CW1200_JOIN_STATUS_MONITOR)
bssid_filtering = false;
if (priv->disable_beacon_filter) {
bf_ctrl.enabled = 0;
bf_ctrl.bcn_count = 1;
bf_tbl.num = __cpu_to_le32(0);
} else if (is_p2p || !is_sta) {
bf_ctrl.enabled = WSM_BEACON_FILTER_ENABLE |
WSM_BEACON_FILTER_AUTO_ERP;
bf_ctrl.bcn_count = 0;
bf_tbl.num = __cpu_to_le32(2);
} else {
bf_ctrl.enabled = WSM_BEACON_FILTER_ENABLE;
bf_ctrl.bcn_count = 0;
bf_tbl.num = __cpu_to_le32(3);
}
/*
* When acting as p2p client being connected to p2p GO, in order to
* receive frames from a different p2p device, turn off bssid filter.
*
* WARNING: FW dependency!
* This can only be used with FW WSM371 and its successors.
* In that FW version even with bssid filter turned off,
* device will block most of the unwanted frames.
*/
if (is_p2p)
bssid_filtering = false;
ret = wsm_set_rx_filter(priv, &priv->rx_filter);
if (!ret)
ret = wsm_set_beacon_filter_table(priv, &bf_tbl);
if (!ret)
ret = wsm_beacon_filter_control(priv, &bf_ctrl);
if (!ret)
ret = wsm_set_bssid_filtering(priv, bssid_filtering);
if (!ret)
ret = wsm_set_multicast_filter(priv, &priv->multicast_filter);
if (ret)
wiphy_err(priv->hw->wiphy,
"Update filtering failed: %d.\n", ret);
return;
}
void cw1200_update_filtering_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common,
update_filtering_work);
cw1200_update_filtering(priv);
}
void cw1200_set_beacon_wakeup_period_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common,
set_beacon_wakeup_period_work);
wsm_set_beacon_wakeup_period(priv,
priv->beacon_int * priv->join_dtim_period >
MAX_BEACON_SKIP_TIME_MS ? 1 :
priv->join_dtim_period, 0);
}
u64 cw1200_prepare_multicast(struct ieee80211_hw *hw,
struct netdev_hw_addr_list *mc_list)
{
static u8 broadcast_ipv6[ETH_ALEN] = {
0x33, 0x33, 0x00, 0x00, 0x00, 0x01
};
static u8 broadcast_ipv4[ETH_ALEN] = {
0x01, 0x00, 0x5e, 0x00, 0x00, 0x01
};
struct cw1200_common *priv = hw->priv;
struct netdev_hw_addr *ha;
int count = 0;
/* Disable multicast filtering */
priv->has_multicast_subscription = false;
memset(&priv->multicast_filter, 0x00, sizeof(priv->multicast_filter));
if (netdev_hw_addr_list_count(mc_list) > WSM_MAX_GRP_ADDRTABLE_ENTRIES)
return 0;
/* Enable if requested */
netdev_hw_addr_list_for_each(ha, mc_list) {
pr_debug("[STA] multicast: %pM\n", ha->addr);
memcpy(&priv->multicast_filter.macaddrs[count],
ha->addr, ETH_ALEN);
if (memcmp(ha->addr, broadcast_ipv4, ETH_ALEN) &&
memcmp(ha->addr, broadcast_ipv6, ETH_ALEN))
priv->has_multicast_subscription = true;
count++;
}
if (count) {
priv->multicast_filter.enable = __cpu_to_le32(1);
priv->multicast_filter.num_addrs = __cpu_to_le32(count);
}
return netdev_hw_addr_list_count(mc_list);
}
void cw1200_configure_filter(struct ieee80211_hw *dev,
unsigned int changed_flags,
unsigned int *total_flags,
u64 multicast)
{
struct cw1200_common *priv = dev->priv;
bool listening = !!(*total_flags &
(FIF_PROMISC_IN_BSS |
FIF_OTHER_BSS |
FIF_BCN_PRBRESP_PROMISC |
FIF_PROBE_REQ));
*total_flags &= FIF_PROMISC_IN_BSS |
FIF_OTHER_BSS |
FIF_FCSFAIL |
FIF_BCN_PRBRESP_PROMISC |
FIF_PROBE_REQ;
down(&priv->scan.lock);
mutex_lock(&priv->conf_mutex);
priv->rx_filter.promiscuous = (*total_flags & FIF_PROMISC_IN_BSS)
? 1 : 0;
priv->rx_filter.bssid = (*total_flags & (FIF_OTHER_BSS |
FIF_PROBE_REQ)) ? 1 : 0;
priv->rx_filter.fcs = (*total_flags & FIF_FCSFAIL) ? 1 : 0;
priv->disable_beacon_filter = !(*total_flags &
(FIF_BCN_PRBRESP_PROMISC |
FIF_PROMISC_IN_BSS |
FIF_PROBE_REQ));
if (priv->listening != listening) {
priv->listening = listening;
wsm_lock_tx(priv);
cw1200_update_listening(priv, listening);
wsm_unlock_tx(priv);
}
cw1200_update_filtering(priv);
mutex_unlock(&priv->conf_mutex);
up(&priv->scan.lock);
}
int cw1200_conf_tx(struct ieee80211_hw *dev, struct ieee80211_vif *vif,
u16 queue, const struct ieee80211_tx_queue_params *params)
{
struct cw1200_common *priv = dev->priv;
int ret = 0;
/* To prevent re-applying PM request OID again and again*/
bool old_uapsd_flags;
mutex_lock(&priv->conf_mutex);
if (queue < dev->queues) {
old_uapsd_flags = priv->uapsd_info.uapsd_flags;
WSM_TX_QUEUE_SET(&priv->tx_queue_params, queue, 0, 0, 0);
ret = wsm_set_tx_queue_params(priv,
&priv->tx_queue_params.params[queue], queue);
if (ret) {
ret = -EINVAL;
goto out;
}
WSM_EDCA_SET(&priv->edca, queue, params->aifs,
params->cw_min, params->cw_max,
params->txop, 0xc8,
params->uapsd);
ret = wsm_set_edca_params(priv, &priv->edca);
if (ret) {
ret = -EINVAL;
goto out;
}
if (priv->mode == NL80211_IFTYPE_STATION) {
ret = cw1200_set_uapsd_param(priv, &priv->edca);
if (!ret && priv->setbssparams_done &&
(priv->join_status == CW1200_JOIN_STATUS_STA) &&
(old_uapsd_flags != priv->uapsd_info.uapsd_flags))
ret = cw1200_set_pm(priv, &priv->powersave_mode);
}
} else {
ret = -EINVAL;
}
out:
mutex_unlock(&priv->conf_mutex);
return ret;
}
int cw1200_get_stats(struct ieee80211_hw *dev,
struct ieee80211_low_level_stats *stats)
{
struct cw1200_common *priv = dev->priv;
memcpy(stats, &priv->stats, sizeof(*stats));
return 0;
}
int cw1200_set_pm(struct cw1200_common *priv, const struct wsm_set_pm *arg)
{
struct wsm_set_pm pm = *arg;
if (priv->uapsd_info.uapsd_flags != 0)
pm.mode &= ~WSM_PSM_FAST_PS_FLAG;
if (memcmp(&pm, &priv->firmware_ps_mode,
sizeof(struct wsm_set_pm))) {
priv->firmware_ps_mode = pm;
return wsm_set_pm(priv, &pm);
} else {
return 0;
}
}
int cw1200_set_key(struct ieee80211_hw *dev, enum set_key_cmd cmd,
struct ieee80211_vif *vif, struct ieee80211_sta *sta,
struct ieee80211_key_conf *key)
{
int ret = -EOPNOTSUPP;
struct cw1200_common *priv = dev->priv;
struct ieee80211_key_seq seq;
mutex_lock(&priv->conf_mutex);
if (cmd == SET_KEY) {
u8 *peer_addr = NULL;
int pairwise = (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ?
1 : 0;
int idx = cw1200_alloc_key(priv);
struct wsm_add_key *wsm_key = &priv->keys[idx];
if (idx < 0) {
ret = -EINVAL;
goto finally;
}
if (sta)
peer_addr = sta->addr;
key->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE;
switch (key->cipher) {
case WLAN_CIPHER_SUITE_WEP40:
case WLAN_CIPHER_SUITE_WEP104:
if (key->keylen > 16) {
cw1200_free_key(priv, idx);
ret = -EINVAL;
goto finally;
}
if (pairwise) {
wsm_key->type = WSM_KEY_TYPE_WEP_PAIRWISE;
memcpy(wsm_key->wep_pairwise.peer,
peer_addr, ETH_ALEN);
memcpy(wsm_key->wep_pairwise.keydata,
&key->key[0], key->keylen);
wsm_key->wep_pairwise.keylen = key->keylen;
} else {
wsm_key->type = WSM_KEY_TYPE_WEP_DEFAULT;
memcpy(wsm_key->wep_group.keydata,
&key->key[0], key->keylen);
wsm_key->wep_group.keylen = key->keylen;
wsm_key->wep_group.keyid = key->keyidx;
}
break;
case WLAN_CIPHER_SUITE_TKIP:
ieee80211_get_key_rx_seq(key, 0, &seq);
if (pairwise) {
wsm_key->type = WSM_KEY_TYPE_TKIP_PAIRWISE;
memcpy(wsm_key->tkip_pairwise.peer,
peer_addr, ETH_ALEN);
memcpy(wsm_key->tkip_pairwise.keydata,
&key->key[0], 16);
memcpy(wsm_key->tkip_pairwise.tx_mic_key,
&key->key[16], 8);
memcpy(wsm_key->tkip_pairwise.rx_mic_key,
&key->key[24], 8);
} else {
size_t mic_offset =
(priv->mode == NL80211_IFTYPE_AP) ?
16 : 24;
wsm_key->type = WSM_KEY_TYPE_TKIP_GROUP;
memcpy(wsm_key->tkip_group.keydata,
&key->key[0], 16);
memcpy(wsm_key->tkip_group.rx_mic_key,
&key->key[mic_offset], 8);
wsm_key->tkip_group.rx_seqnum[0] = seq.tkip.iv16 & 0xff;
wsm_key->tkip_group.rx_seqnum[1] = (seq.tkip.iv16 >> 8) & 0xff;
wsm_key->tkip_group.rx_seqnum[2] = seq.tkip.iv32 & 0xff;
wsm_key->tkip_group.rx_seqnum[3] = (seq.tkip.iv32 >> 8) & 0xff;
wsm_key->tkip_group.rx_seqnum[4] = (seq.tkip.iv32 >> 16) & 0xff;
wsm_key->tkip_group.rx_seqnum[5] = (seq.tkip.iv32 >> 24) & 0xff;
wsm_key->tkip_group.rx_seqnum[6] = 0;
wsm_key->tkip_group.rx_seqnum[7] = 0;
wsm_key->tkip_group.keyid = key->keyidx;
}
break;
case WLAN_CIPHER_SUITE_CCMP:
ieee80211_get_key_rx_seq(key, 0, &seq);
if (pairwise) {
wsm_key->type = WSM_KEY_TYPE_AES_PAIRWISE;
memcpy(wsm_key->aes_pairwise.peer,
peer_addr, ETH_ALEN);
memcpy(wsm_key->aes_pairwise.keydata,
&key->key[0], 16);
} else {
wsm_key->type = WSM_KEY_TYPE_AES_GROUP;
memcpy(wsm_key->aes_group.keydata,
&key->key[0], 16);
wsm_key->aes_group.rx_seqnum[0] = seq.ccmp.pn[5];
wsm_key->aes_group.rx_seqnum[1] = seq.ccmp.pn[4];
wsm_key->aes_group.rx_seqnum[2] = seq.ccmp.pn[3];
wsm_key->aes_group.rx_seqnum[3] = seq.ccmp.pn[2];
wsm_key->aes_group.rx_seqnum[4] = seq.ccmp.pn[1];
wsm_key->aes_group.rx_seqnum[5] = seq.ccmp.pn[0];
wsm_key->aes_group.rx_seqnum[6] = 0;
wsm_key->aes_group.rx_seqnum[7] = 0;
wsm_key->aes_group.keyid = key->keyidx;
}
break;
case WLAN_CIPHER_SUITE_SMS4:
if (pairwise) {
wsm_key->type = WSM_KEY_TYPE_WAPI_PAIRWISE;
memcpy(wsm_key->wapi_pairwise.peer,
peer_addr, ETH_ALEN);
memcpy(wsm_key->wapi_pairwise.keydata,
&key->key[0], 16);
memcpy(wsm_key->wapi_pairwise.mic_key,
&key->key[16], 16);
wsm_key->wapi_pairwise.keyid = key->keyidx;
} else {
wsm_key->type = WSM_KEY_TYPE_WAPI_GROUP;
memcpy(wsm_key->wapi_group.keydata,
&key->key[0], 16);
memcpy(wsm_key->wapi_group.mic_key,
&key->key[16], 16);
wsm_key->wapi_group.keyid = key->keyidx;
}
break;
default:
pr_warn("Unhandled key type %d\n", key->cipher);
cw1200_free_key(priv, idx);
ret = -EOPNOTSUPP;
goto finally;
}
ret = wsm_add_key(priv, wsm_key);
if (!ret)
key->hw_key_idx = idx;
else
cw1200_free_key(priv, idx);
} else if (cmd == DISABLE_KEY) {
struct wsm_remove_key wsm_key = {
.index = key->hw_key_idx,
};
if (wsm_key.index > WSM_KEY_MAX_INDEX) {
ret = -EINVAL;
goto finally;
}
cw1200_free_key(priv, wsm_key.index);
ret = wsm_remove_key(priv, &wsm_key);
} else {
pr_warn("Unhandled key command %d\n", cmd);
}
finally:
mutex_unlock(&priv->conf_mutex);
return ret;
}
void cw1200_wep_key_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, wep_key_work);
u8 queue_id = cw1200_queue_get_queue_id(priv->pending_frame_id);
struct cw1200_queue *queue = &priv->tx_queue[queue_id];
__le32 wep_default_key_id = __cpu_to_le32(
priv->wep_default_key_id);
pr_debug("[STA] Setting default WEP key: %d\n",
priv->wep_default_key_id);
wsm_flush_tx(priv);
wsm_write_mib(priv, WSM_MIB_ID_DOT11_WEP_DEFAULT_KEY_ID,
&wep_default_key_id, sizeof(wep_default_key_id));
cw1200_queue_requeue(queue, priv->pending_frame_id);
wsm_unlock_tx(priv);
}
int cw1200_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
{
int ret = 0;
__le32 val32;
struct cw1200_common *priv = hw->priv;
if (priv->mode == NL80211_IFTYPE_UNSPECIFIED)
return 0;
if (value != (u32) -1)
val32 = __cpu_to_le32(value);
else
val32 = 0; /* disabled */
if (priv->mode == NL80211_IFTYPE_UNSPECIFIED) {
/* device is down, can _not_ set threshold */
ret = -ENODEV;
goto out;
}
if (priv->rts_threshold == value)
goto out;
pr_debug("[STA] Setting RTS threshold: %d\n",
priv->rts_threshold);
/* mutex_lock(&priv->conf_mutex); */
ret = wsm_write_mib(priv, WSM_MIB_ID_DOT11_RTS_THRESHOLD,
&val32, sizeof(val32));
if (!ret)
priv->rts_threshold = value;
/* mutex_unlock(&priv->conf_mutex); */
out:
return ret;
}
/* If successful, LOCKS the TX queue! */
static int __cw1200_flush(struct cw1200_common *priv, bool drop)
{
int i, ret;
for (;;) {
/* TODO: correct flush handling is required when dev_stop.
* Temporary workaround: 2s
*/
if (drop) {
for (i = 0; i < 4; ++i)
cw1200_queue_clear(&priv->tx_queue[i]);
} else {
ret = wait_event_timeout(
priv->tx_queue_stats.wait_link_id_empty,
cw1200_queue_stats_is_empty(
&priv->tx_queue_stats, -1),
2 * HZ);
}
if (!drop && ret <= 0) {
ret = -ETIMEDOUT;
break;
} else {
ret = 0;
}
wsm_lock_tx(priv);
if (!cw1200_queue_stats_is_empty(&priv->tx_queue_stats, -1)) {
/* Highly unlikely: WSM requeued frames. */
wsm_unlock_tx(priv);
continue;
}
break;
}
return ret;
}
void cw1200_flush(struct ieee80211_hw *hw, u32 queues, bool drop)
{
struct cw1200_common *priv = hw->priv;
switch (priv->mode) {
case NL80211_IFTYPE_MONITOR:
drop = true;
break;
case NL80211_IFTYPE_AP:
if (!priv->enable_beacon)
drop = true;
break;
}
if (!__cw1200_flush(priv, drop))
wsm_unlock_tx(priv);
return;
}
/* ******************************************************************** */
/* WSM callbacks */
void cw1200_free_event_queue(struct cw1200_common *priv)
{
LIST_HEAD(list);
spin_lock(&priv->event_queue_lock);
list_splice_init(&priv->event_queue, &list);
spin_unlock(&priv->event_queue_lock);
__cw1200_free_event_queue(&list);
}
void cw1200_event_handler(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, event_handler);
struct cw1200_wsm_event *event;
LIST_HEAD(list);
spin_lock(&priv->event_queue_lock);
list_splice_init(&priv->event_queue, &list);
spin_unlock(&priv->event_queue_lock);
list_for_each_entry(event, &list, link) {
switch (event->evt.id) {
case WSM_EVENT_ERROR:
pr_err("Unhandled WSM Error from LMAC\n");
break;
case WSM_EVENT_BSS_LOST:
pr_debug("[CQM] BSS lost.\n");
cancel_work_sync(&priv->unjoin_work);
if (!down_trylock(&priv->scan.lock)) {
cw1200_cqm_bssloss_sm(priv, 1, 0, 0);
up(&priv->scan.lock);
} else {
/* Scan is in progress. Delay reporting.
* Scan complete will trigger bss_loss_work
*/
priv->delayed_link_loss = 1;
/* Also start a watchdog. */
queue_delayed_work(priv->workqueue,
&priv->bss_loss_work, 5*HZ);
}
break;
case WSM_EVENT_BSS_REGAINED:
pr_debug("[CQM] BSS regained.\n");
cw1200_cqm_bssloss_sm(priv, 0, 0, 0);
cancel_work_sync(&priv->unjoin_work);
break;
case WSM_EVENT_RADAR_DETECTED:
wiphy_info(priv->hw->wiphy, "radar pulse detected\n");
break;
case WSM_EVENT_RCPI_RSSI:
{
/* RSSI: signed Q8.0, RCPI: unsigned Q7.1
* RSSI = RCPI / 2 - 110
*/
int rcpiRssi = (int)(event->evt.data & 0xFF);
int cqm_evt;
if (priv->cqm_use_rssi)
rcpiRssi = (s8)rcpiRssi;
else
rcpiRssi = rcpiRssi / 2 - 110;
cqm_evt = (rcpiRssi <= priv->cqm_rssi_thold) ?
NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW :
NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH;
pr_debug("[CQM] RSSI event: %d.\n", rcpiRssi);
ieee80211_cqm_rssi_notify(priv->vif, cqm_evt,
GFP_KERNEL);
break;
}
case WSM_EVENT_BT_INACTIVE:
pr_warn("Unhandled BT INACTIVE from LMAC\n");
break;
case WSM_EVENT_BT_ACTIVE:
pr_warn("Unhandled BT ACTIVE from LMAC\n");
break;
}
}
__cw1200_free_event_queue(&list);
}
void cw1200_bss_loss_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, bss_loss_work.work);
pr_debug("[CQM] Reporting connection loss.\n");
wsm_lock_tx(priv);
if (queue_work(priv->workqueue, &priv->unjoin_work) <= 0)
wsm_unlock_tx(priv);
}
void cw1200_bss_params_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, bss_params_work);
mutex_lock(&priv->conf_mutex);
priv->bss_params.reset_beacon_loss = 1;
wsm_set_bss_params(priv, &priv->bss_params);
priv->bss_params.reset_beacon_loss = 0;
mutex_unlock(&priv->conf_mutex);
}
/* ******************************************************************** */
/* Internal API */
/*
* This function is called to Parse the SDD file
* to extract listen_interval and PTA related information
* sdd is a TLV: u8 id, u8 len, u8 data[]
*/
static int cw1200_parse_sdd_file(struct cw1200_common *priv)
{
const u8 *p = priv->sdd->data;
int ret = 0;
while (p + 2 <= priv->sdd->data + priv->sdd->size) {
if (p + p[1] + 2 > priv->sdd->data + priv->sdd->size) {
pr_warn("Malformed sdd structure\n");
return -1;
}
switch (p[0]) {
case SDD_PTA_CFG_ELT_ID: {
u16 v;
if (p[1] < 4) {
pr_warn("SDD_PTA_CFG_ELT_ID malformed\n");
ret = -1;
break;
}
v = le16_to_cpu(*((u16 *)(p + 2)));
if (!v) /* non-zero means this is enabled */
break;
v = le16_to_cpu(*((u16 *)(p + 4)));
priv->conf_listen_interval = (v >> 7) & 0x1F;
pr_debug("PTA found; Listen Interval %d\n",
priv->conf_listen_interval);
break;
}
case SDD_REFERENCE_FREQUENCY_ELT_ID: {
u16 clk = le16_to_cpu(*((u16 *)(p + 2)));
if (clk != priv->hw_refclk)
pr_warn("SDD file doesn't match configured refclk (%d vs %d)\n",
clk, priv->hw_refclk);
break;
}
default:
break;
}
p += p[1] + 2;
}
if (!priv->bt_present) {
pr_debug("PTA element NOT found.\n");
priv->conf_listen_interval = 0;
}
return ret;
}
int cw1200_setup_mac(struct cw1200_common *priv)
{
int ret = 0;
/* NOTE: There is a bug in FW: it reports signal
* as RSSI if RSSI subscription is enabled.
* It's not enough to set WSM_RCPI_RSSI_USE_RSSI.
*
* NOTE2: RSSI based reports have been switched to RCPI, since
* FW has a bug and RSSI reported values are not stable,
* what can leads to signal level oscilations in user-end applications
*/
struct wsm_rcpi_rssi_threshold threshold = {
.rssiRcpiMode = WSM_RCPI_RSSI_THRESHOLD_ENABLE |
WSM_RCPI_RSSI_DONT_USE_UPPER |
WSM_RCPI_RSSI_DONT_USE_LOWER,
.rollingAverageCount = 16,
};
struct wsm_configuration cfg = {
.dot11StationId = &priv->mac_addr[0],
};
/* Remember the decission here to make sure, we will handle
* the RCPI/RSSI value correctly on WSM_EVENT_RCPI_RSS
*/
if (threshold.rssiRcpiMode & WSM_RCPI_RSSI_USE_RSSI)
priv->cqm_use_rssi = true;
if (!priv->sdd) {
ret = request_firmware(&priv->sdd, priv->sdd_path, priv->pdev);
if (ret) {
pr_err("Can't load sdd file %s.\n", priv->sdd_path);
return ret;
}
cw1200_parse_sdd_file(priv);
}
cfg.dpdData = priv->sdd->data;
cfg.dpdData_size = priv->sdd->size;
ret = wsm_configuration(priv, &cfg);
if (ret)
return ret;
/* Configure RSSI/SCPI reporting as RSSI. */
wsm_set_rcpi_rssi_threshold(priv, &threshold);
return 0;
}
static void cw1200_join_complete(struct cw1200_common *priv)
{
pr_debug("[STA] Join complete (%d)\n", priv->join_complete_status);
priv->join_pending = false;
if (priv->join_complete_status) {
priv->join_status = CW1200_JOIN_STATUS_PASSIVE;
cw1200_update_listening(priv, priv->listening);
cw1200_do_unjoin(priv);
ieee80211_connection_loss(priv->vif);
} else {
if (priv->mode == NL80211_IFTYPE_ADHOC)
priv->join_status = CW1200_JOIN_STATUS_IBSS;
else
priv->join_status = CW1200_JOIN_STATUS_PRE_STA;
}
wsm_unlock_tx(priv); /* Clearing the lock held before do_join() */
}
void cw1200_join_complete_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, join_complete_work);
mutex_lock(&priv->conf_mutex);
cw1200_join_complete(priv);
mutex_unlock(&priv->conf_mutex);
}
void cw1200_join_complete_cb(struct cw1200_common *priv,
struct wsm_join_complete *arg)
{
pr_debug("[STA] cw1200_join_complete_cb called, status=%d.\n",
arg->status);
if (cancel_delayed_work(&priv->join_timeout)) {
priv->join_complete_status = arg->status;
queue_work(priv->workqueue, &priv->join_complete_work);
}
}
/* MUST be called with tx_lock held! It will be unlocked for us. */
static void cw1200_do_join(struct cw1200_common *priv)
{
const u8 *bssid;
struct ieee80211_bss_conf *conf = &priv->vif->bss_conf;
struct cfg80211_bss *bss = NULL;
struct wsm_protected_mgmt_policy mgmt_policy;
struct wsm_join join = {
.mode = conf->ibss_joined ?
WSM_JOIN_MODE_IBSS : WSM_JOIN_MODE_BSS,
.preamble_type = WSM_JOIN_PREAMBLE_LONG,
.probe_for_join = 1,
.atim_window = 0,
.basic_rate_set = cw1200_rate_mask_to_wsm(priv,
conf->basic_rates),
};
if (delayed_work_pending(&priv->join_timeout)) {
pr_warn("[STA] - Join request already pending, skipping..\n");
wsm_unlock_tx(priv);
return;
}
if (priv->join_status)
cw1200_do_unjoin(priv);
bssid = priv->vif->bss_conf.bssid;
bss = cfg80211_get_bss(priv->hw->wiphy, priv->channel,
bssid, NULL, 0, 0, 0);
if (!bss && !conf->ibss_joined) {
wsm_unlock_tx(priv);
return;
}
mutex_lock(&priv->conf_mutex);
/* Under the conf lock: check scan status and
* bail out if it is in progress.
*/
if (atomic_read(&priv->scan.in_progress)) {
wsm_unlock_tx(priv);
goto done_put;
}
priv->join_pending = true;
/* Sanity check basic rates */
if (!join.basic_rate_set)
join.basic_rate_set = 7;
/* Sanity check beacon interval */
if (!priv->beacon_int)
priv->beacon_int = 1;
join.beacon_interval = priv->beacon_int;
/* BT Coex related changes */
if (priv->bt_present) {
if (((priv->conf_listen_interval * 100) %
priv->beacon_int) == 0)
priv->listen_interval =
((priv->conf_listen_interval * 100) /
priv->beacon_int);
else
priv->listen_interval =
((priv->conf_listen_interval * 100) /
priv->beacon_int + 1);
}
if (priv->hw->conf.ps_dtim_period)
priv->join_dtim_period = priv->hw->conf.ps_dtim_period;
join.dtim_period = priv->join_dtim_period;
join.channel_number = priv->channel->hw_value;
join.band = (priv->channel->band == IEEE80211_BAND_5GHZ) ?
WSM_PHY_BAND_5G : WSM_PHY_BAND_2_4G;
memcpy(join.bssid, bssid, sizeof(join.bssid));
pr_debug("[STA] Join BSSID: %pM DTIM: %d, interval: %d\n",
join.bssid,
join.dtim_period, priv->beacon_int);
if (!conf->ibss_joined) {
const u8 *ssidie;
rcu_read_lock();
ssidie = ieee80211_bss_get_ie(bss, WLAN_EID_SSID);
if (ssidie) {
join.ssid_len = ssidie[1];
memcpy(join.ssid, &ssidie[2], join.ssid_len);
}
rcu_read_unlock();
}
if (priv->vif->p2p) {
join.flags |= WSM_JOIN_FLAGS_P2P_GO;
join.basic_rate_set =
cw1200_rate_mask_to_wsm(priv, 0xFF0);
}
/* Enable asynchronous join calls */
if (!conf->ibss_joined) {
join.flags |= WSM_JOIN_FLAGS_FORCE;
join.flags |= WSM_JOIN_FLAGS_FORCE_WITH_COMPLETE_IND;
}
wsm_flush_tx(priv);
/* Stay Awake for Join and Auth Timeouts and a bit more */
cw1200_pm_stay_awake(&priv->pm_state,
CW1200_JOIN_TIMEOUT + CW1200_AUTH_TIMEOUT);
cw1200_update_listening(priv, false);
/* Turn on Block ACKs */
wsm_set_block_ack_policy(priv, priv->ba_tx_tid_mask,
priv->ba_rx_tid_mask);
/* Set up timeout */
if (join.flags & WSM_JOIN_FLAGS_FORCE_WITH_COMPLETE_IND) {
priv->join_status = CW1200_JOIN_STATUS_JOINING;
queue_delayed_work(priv->workqueue,
&priv->join_timeout,
CW1200_JOIN_TIMEOUT);
}
/* 802.11w protected mgmt frames */
mgmt_policy.protectedMgmtEnable = 0;
mgmt_policy.unprotectedMgmtFramesAllowed = 1;
mgmt_policy.encryptionForAuthFrame = 1;
wsm_set_protected_mgmt_policy(priv, &mgmt_policy);
/* Perform actual join */
if (wsm_join(priv, &join)) {
pr_err("[STA] cw1200_join_work: wsm_join failed!\n");
cancel_delayed_work_sync(&priv->join_timeout);
cw1200_update_listening(priv, priv->listening);
/* Tx lock still held, unjoin will clear it. */
if (queue_work(priv->workqueue, &priv->unjoin_work) <= 0)
wsm_unlock_tx(priv);
} else {
if (!(join.flags & WSM_JOIN_FLAGS_FORCE_WITH_COMPLETE_IND))
cw1200_join_complete(priv); /* Will clear tx_lock */
/* Upload keys */
cw1200_upload_keys(priv);
/* 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
*/
priv->disable_beacon_filter = true;
}
cw1200_update_filtering(priv);
done_put:
mutex_unlock(&priv->conf_mutex);
if (bss)
cfg80211_put_bss(priv->hw->wiphy, bss);
}
void cw1200_join_timeout(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, join_timeout.work);
pr_debug("[WSM] Join timed out.\n");
wsm_lock_tx(priv);
if (queue_work(priv->workqueue, &priv->unjoin_work) <= 0)
wsm_unlock_tx(priv);
}
static void cw1200_do_unjoin(struct cw1200_common *priv)
{
struct wsm_reset reset = {
.reset_statistics = true,
};
cancel_delayed_work_sync(&priv->join_timeout);
mutex_lock(&priv->conf_mutex);
priv->join_pending = false;
if (atomic_read(&priv->scan.in_progress)) {
if (priv->delayed_unjoin)
wiphy_dbg(priv->hw->wiphy, "Delayed unjoin is already scheduled.\n");
else
priv->delayed_unjoin = true;
goto done;
}
priv->delayed_link_loss = false;
if (!priv->join_status)
goto done;
if (priv->join_status > CW1200_JOIN_STATUS_IBSS) {
wiphy_err(priv->hw->wiphy, "Unexpected: join status: %d\n",
priv->join_status);
BUG_ON(1);
}
cancel_work_sync(&priv->update_filtering_work);
cancel_work_sync(&priv->set_beacon_wakeup_period_work);
priv->join_status = CW1200_JOIN_STATUS_PASSIVE;
/* Unjoin is a reset. */
wsm_flush_tx(priv);
wsm_keep_alive_period(priv, 0);
wsm_reset(priv, &reset);
wsm_set_output_power(priv, priv->output_power * 10);
priv->join_dtim_period = 0;
cw1200_setup_mac(priv);
cw1200_free_event_queue(priv);
cancel_work_sync(&priv->event_handler);
cw1200_update_listening(priv, priv->listening);
cw1200_cqm_bssloss_sm(priv, 0, 0, 0);
/* Disable Block ACKs */
wsm_set_block_ack_policy(priv, 0, 0);
priv->disable_beacon_filter = false;
cw1200_update_filtering(priv);
memset(&priv->association_mode, 0,
sizeof(priv->association_mode));
memset(&priv->bss_params, 0, sizeof(priv->bss_params));
priv->setbssparams_done = false;
memset(&priv->firmware_ps_mode, 0,
sizeof(priv->firmware_ps_mode));
pr_debug("[STA] Unjoin completed.\n");
done:
mutex_unlock(&priv->conf_mutex);
}
void cw1200_unjoin_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, unjoin_work);
cw1200_do_unjoin(priv);
/* Tell the stack we're dead */
ieee80211_connection_loss(priv->vif);
wsm_unlock_tx(priv);
}
int cw1200_enable_listening(struct cw1200_common *priv)
{
struct wsm_start start = {
.mode = WSM_START_MODE_P2P_DEV,
.band = WSM_PHY_BAND_2_4G,
.beacon_interval = 100,
.dtim_period = 1,
.probe_delay = 0,
.basic_rate_set = 0x0F,
};
if (priv->channel) {
start.band = priv->channel->band == IEEE80211_BAND_5GHZ ?
WSM_PHY_BAND_5G : WSM_PHY_BAND_2_4G;
start.channel_number = priv->channel->hw_value;
} else {
start.band = WSM_PHY_BAND_2_4G;
start.channel_number = 1;
}
return wsm_start(priv, &start);
}
int cw1200_disable_listening(struct cw1200_common *priv)
{
int ret;
struct wsm_reset reset = {
.reset_statistics = true,
};
ret = wsm_reset(priv, &reset);
return ret;
}
void cw1200_update_listening(struct cw1200_common *priv, bool enabled)
{
if (enabled) {
if (priv->join_status == CW1200_JOIN_STATUS_PASSIVE) {
if (!cw1200_enable_listening(priv))
priv->join_status = CW1200_JOIN_STATUS_MONITOR;
wsm_set_probe_responder(priv, true);
}
} else {
if (priv->join_status == CW1200_JOIN_STATUS_MONITOR) {
if (!cw1200_disable_listening(priv))
priv->join_status = CW1200_JOIN_STATUS_PASSIVE;
wsm_set_probe_responder(priv, false);
}
}
}
int cw1200_set_uapsd_param(struct cw1200_common *priv,
const struct wsm_edca_params *arg)
{
int ret;
u16 uapsd_flags = 0;
/* Here's the mapping AC [queue, bit]
* VO [0,3], VI [1, 2], BE [2, 1], BK [3, 0]
*/
if (arg->uapsd_enable[0])
uapsd_flags |= 1 << 3;
if (arg->uapsd_enable[1])
uapsd_flags |= 1 << 2;
if (arg->uapsd_enable[2])
uapsd_flags |= 1 << 1;
if (arg->uapsd_enable[3])
uapsd_flags |= 1;
/* Currently pseudo U-APSD operation is not supported, so setting
* MinAutoTriggerInterval, MaxAutoTriggerInterval and
* AutoTriggerStep to 0
*/
priv->uapsd_info.uapsd_flags = cpu_to_le16(uapsd_flags);
priv->uapsd_info.min_auto_trigger_interval = 0;
priv->uapsd_info.max_auto_trigger_interval = 0;
priv->uapsd_info.auto_trigger_step = 0;
ret = wsm_set_uapsd_info(priv, &priv->uapsd_info);
return ret;
}
/* ******************************************************************** */
/* AP API */
int cw1200_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_sta *sta)
{
struct cw1200_common *priv = hw->priv;
struct cw1200_sta_priv *sta_priv =
(struct cw1200_sta_priv *)&sta->drv_priv;
struct cw1200_link_entry *entry;
struct sk_buff *skb;
if (priv->mode != NL80211_IFTYPE_AP)
return 0;
sta_priv->link_id = cw1200_find_link_id(priv, sta->addr);
if (WARN_ON(!sta_priv->link_id)) {
wiphy_info(priv->hw->wiphy,
"[AP] No more link IDs available.\n");
return -ENOENT;
}
entry = &priv->link_id_db[sta_priv->link_id - 1];
spin_lock_bh(&priv->ps_state_lock);
if ((sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK) ==
IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK)
priv->sta_asleep_mask |= BIT(sta_priv->link_id);
entry->status = CW1200_LINK_HARD;
while ((skb = skb_dequeue(&entry->rx_queue)))
ieee80211_rx_irqsafe(priv->hw, skb);
spin_unlock_bh(&priv->ps_state_lock);
return 0;
}
int cw1200_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_sta *sta)
{
struct cw1200_common *priv = hw->priv;
struct cw1200_sta_priv *sta_priv =
(struct cw1200_sta_priv *)&sta->drv_priv;
struct cw1200_link_entry *entry;
if (priv->mode != NL80211_IFTYPE_AP || !sta_priv->link_id)
return 0;
entry = &priv->link_id_db[sta_priv->link_id - 1];
spin_lock_bh(&priv->ps_state_lock);
entry->status = CW1200_LINK_RESERVE;
entry->timestamp = jiffies;
wsm_lock_tx_async(priv);
if (queue_work(priv->workqueue, &priv->link_id_work) <= 0)
wsm_unlock_tx(priv);
spin_unlock_bh(&priv->ps_state_lock);
flush_workqueue(priv->workqueue);
return 0;
}
static void __cw1200_sta_notify(struct ieee80211_hw *dev,
struct ieee80211_vif *vif,
enum sta_notify_cmd notify_cmd,
int link_id)
{
struct cw1200_common *priv = dev->priv;
u32 bit, prev;
/* Zero link id means "for all link IDs" */
if (link_id)
bit = BIT(link_id);
else if (WARN_ON_ONCE(notify_cmd != STA_NOTIFY_AWAKE))
bit = 0;
else
bit = priv->link_id_map;
prev = priv->sta_asleep_mask & bit;
switch (notify_cmd) {
case STA_NOTIFY_SLEEP:
if (!prev) {
if (priv->buffered_multicasts &&
!priv->sta_asleep_mask)
queue_work(priv->workqueue,
&priv->multicast_start_work);
priv->sta_asleep_mask |= bit;
}
break;
case STA_NOTIFY_AWAKE:
if (prev) {
priv->sta_asleep_mask &= ~bit;
priv->pspoll_mask &= ~bit;
if (priv->tx_multicast && link_id &&
!priv->sta_asleep_mask)
queue_work(priv->workqueue,
&priv->multicast_stop_work);
cw1200_bh_wakeup(priv);
}
break;
}
}
void cw1200_sta_notify(struct ieee80211_hw *dev,
struct ieee80211_vif *vif,
enum sta_notify_cmd notify_cmd,
struct ieee80211_sta *sta)
{
struct cw1200_common *priv = dev->priv;
struct cw1200_sta_priv *sta_priv =
(struct cw1200_sta_priv *)&sta->drv_priv;
spin_lock_bh(&priv->ps_state_lock);
__cw1200_sta_notify(dev, vif, notify_cmd, sta_priv->link_id);
spin_unlock_bh(&priv->ps_state_lock);
}
static void cw1200_ps_notify(struct cw1200_common *priv,
int link_id, bool ps)
{
if (link_id > CW1200_MAX_STA_IN_AP_MODE)
return;
pr_debug("%s for LinkId: %d. STAs asleep: %.8X\n",
ps ? "Stop" : "Start",
link_id, priv->sta_asleep_mask);
__cw1200_sta_notify(priv->hw, priv->vif,
ps ? STA_NOTIFY_SLEEP : STA_NOTIFY_AWAKE, link_id);
}
static int cw1200_set_tim_impl(struct cw1200_common *priv, bool aid0_bit_set)
{
struct sk_buff *skb;
struct wsm_update_ie update_ie = {
.what = WSM_UPDATE_IE_BEACON,
.count = 1,
};
u16 tim_offset, tim_length;
pr_debug("[AP] mcast: %s.\n", aid0_bit_set ? "ena" : "dis");
skb = ieee80211_beacon_get_tim(priv->hw, priv->vif,
&tim_offset, &tim_length);
if (!skb) {
if (!__cw1200_flush(priv, true))
wsm_unlock_tx(priv);
return -ENOENT;
}
if (tim_offset && tim_length >= 6) {
/* Ignore DTIM count from mac80211:
* firmware handles DTIM internally.
*/
skb->data[tim_offset + 2] = 0;
/* Set/reset aid0 bit */
if (aid0_bit_set)
skb->data[tim_offset + 4] |= 1;
else
skb->data[tim_offset + 4] &= ~1;
}
update_ie.ies = &skb->data[tim_offset];
update_ie.length = tim_length;
wsm_update_ie(priv, &update_ie);
dev_kfree_skb(skb);
return 0;
}
void cw1200_set_tim_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, set_tim_work);
(void)cw1200_set_tim_impl(priv, priv->aid0_bit_set);
}
int cw1200_set_tim(struct ieee80211_hw *dev, struct ieee80211_sta *sta,
bool set)
{
struct cw1200_common *priv = dev->priv;
queue_work(priv->workqueue, &priv->set_tim_work);
return 0;
}
void cw1200_set_cts_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, set_cts_work);
u8 erp_ie[3] = {WLAN_EID_ERP_INFO, 0x1, 0};
struct wsm_update_ie update_ie = {
.what = WSM_UPDATE_IE_BEACON,
.count = 1,
.ies = erp_ie,
.length = 3,
};
u32 erp_info;
__le32 use_cts_prot;
mutex_lock(&priv->conf_mutex);
erp_info = priv->erp_info;
mutex_unlock(&priv->conf_mutex);
use_cts_prot =
erp_info & WLAN_ERP_USE_PROTECTION ?
__cpu_to_le32(1) : 0;
erp_ie[ERP_INFO_BYTE_OFFSET] = erp_info;
pr_debug("[STA] ERP information 0x%x\n", erp_info);
wsm_write_mib(priv, WSM_MIB_ID_NON_ERP_PROTECTION,
&use_cts_prot, sizeof(use_cts_prot));
wsm_update_ie(priv, &update_ie);
return;
}
static int cw1200_set_btcoexinfo(struct cw1200_common *priv)
{
struct wsm_override_internal_txrate arg;
int ret = 0;
if (priv->mode == NL80211_IFTYPE_STATION) {
/* Plumb PSPOLL and NULL template */
cw1200_upload_pspoll(priv);
cw1200_upload_null(priv);
cw1200_upload_qosnull(priv);
} else {
return 0;
}
memset(&arg, 0, sizeof(struct wsm_override_internal_txrate));
if (!priv->vif->p2p) {
/* STATION mode */
if (priv->bss_params.operational_rate_set & ~0xF) {
pr_debug("[STA] STA has ERP rates\n");
/* G or BG mode */
arg.internalTxRate = (__ffs(
priv->bss_params.operational_rate_set & ~0xF));
} else {
pr_debug("[STA] STA has non ERP rates\n");
/* B only mode */
arg.internalTxRate = (__ffs(priv->association_mode.basic_rate_set));
}
arg.nonErpInternalTxRate = (__ffs(priv->association_mode.basic_rate_set));
} else {
/* P2P mode */
arg.internalTxRate = (__ffs(priv->bss_params.operational_rate_set & ~0xF));
arg.nonErpInternalTxRate = (__ffs(priv->bss_params.operational_rate_set & ~0xF));
}
pr_debug("[STA] BTCOEX_INFO MODE %d, internalTxRate : %x, nonErpInternalTxRate: %x\n",
priv->mode,
arg.internalTxRate,
arg.nonErpInternalTxRate);
ret = wsm_write_mib(priv, WSM_MIB_ID_OVERRIDE_INTERNAL_TX_RATE,
&arg, sizeof(arg));
return ret;
}
void cw1200_bss_info_changed(struct ieee80211_hw *dev,
struct ieee80211_vif *vif,
struct ieee80211_bss_conf *info,
u32 changed)
{
struct cw1200_common *priv = dev->priv;
bool do_join = false;
mutex_lock(&priv->conf_mutex);
pr_debug("BSS CHANGED: %08x\n", changed);
/* TODO: BSS_CHANGED_QOS */
/* TODO: BSS_CHANGED_TXPOWER */
if (changed & BSS_CHANGED_ARP_FILTER) {
struct wsm_mib_arp_ipv4_filter filter = {0};
int i;
pr_debug("[STA] BSS_CHANGED_ARP_FILTER cnt: %d\n",
info->arp_addr_cnt);
/* Currently only one IP address is supported by firmware.
* In case of more IPs arp filtering will be disabled.
*/
if (info->arp_addr_cnt > 0 &&
info->arp_addr_cnt <= WSM_MAX_ARP_IP_ADDRTABLE_ENTRIES) {
for (i = 0; i < info->arp_addr_cnt; i++) {
filter.ipv4addrs[i] = info->arp_addr_list[i];
pr_debug("[STA] addr[%d]: 0x%X\n",
i, filter.ipv4addrs[i]);
}
filter.enable = __cpu_to_le32(1);
}
pr_debug("[STA] arp ip filter enable: %d\n",
__le32_to_cpu(filter.enable));
wsm_set_arp_ipv4_filter(priv, &filter);
}
if (changed &
(BSS_CHANGED_BEACON |
BSS_CHANGED_AP_PROBE_RESP |
BSS_CHANGED_BSSID |
BSS_CHANGED_SSID |
BSS_CHANGED_IBSS)) {
pr_debug("BSS_CHANGED_BEACON\n");
priv->beacon_int = info->beacon_int;
cw1200_update_beaconing(priv);
cw1200_upload_beacon(priv);
}
if (changed & BSS_CHANGED_BEACON_ENABLED) {
pr_debug("BSS_CHANGED_BEACON_ENABLED (%d)\n", info->enable_beacon);
if (priv->enable_beacon != info->enable_beacon) {
cw1200_enable_beaconing(priv, info->enable_beacon);
priv->enable_beacon = info->enable_beacon;
}
}
if (changed & BSS_CHANGED_BEACON_INT) {
pr_debug("CHANGED_BEACON_INT\n");
if (info->ibss_joined)
do_join = true;
else if (priv->join_status == CW1200_JOIN_STATUS_AP)
cw1200_update_beaconing(priv);
}
/* assoc/disassoc, or maybe AID changed */
if (changed & BSS_CHANGED_ASSOC) {
wsm_lock_tx(priv);
priv->wep_default_key_id = -1;
wsm_unlock_tx(priv);
}
if (changed & BSS_CHANGED_BSSID) {
pr_debug("BSS_CHANGED_BSSID\n");
do_join = true;
}
if (changed &
(BSS_CHANGED_ASSOC |
BSS_CHANGED_BSSID |
BSS_CHANGED_IBSS |
BSS_CHANGED_BASIC_RATES |
BSS_CHANGED_HT)) {
pr_debug("BSS_CHANGED_ASSOC\n");
if (info->assoc) {
if (priv->join_status < CW1200_JOIN_STATUS_PRE_STA) {
ieee80211_connection_loss(vif);
mutex_unlock(&priv->conf_mutex);
return;
} else if (priv->join_status == CW1200_JOIN_STATUS_PRE_STA) {
priv->join_status = CW1200_JOIN_STATUS_STA;
}
} else {
do_join = true;
}
if (info->assoc || info->ibss_joined) {
struct ieee80211_sta *sta = NULL;
u32 val = 0;
if (info->dtim_period)
priv->join_dtim_period = info->dtim_period;
priv->beacon_int = info->beacon_int;
rcu_read_lock();
if (info->bssid && !info->ibss_joined)
sta = ieee80211_find_sta(vif, info->bssid);
if (sta) {
priv->ht_info.ht_cap = sta->ht_cap;
priv->bss_params.operational_rate_set =
cw1200_rate_mask_to_wsm(priv,
sta->supp_rates[priv->channel->band]);
priv->ht_info.channel_type = cfg80211_get_chandef_type(&dev->conf.chandef);
priv->ht_info.operation_mode = info->ht_operation_mode;
} else {
memset(&priv->ht_info, 0,
sizeof(priv->ht_info));
priv->bss_params.operational_rate_set = -1;
}
rcu_read_unlock();
/* Non Greenfield stations present */
if (priv->ht_info.operation_mode &
IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT)
val |= WSM_NON_GREENFIELD_STA_PRESENT;
/* Set HT protection method */
val |= (priv->ht_info.operation_mode & IEEE80211_HT_OP_MODE_PROTECTION) << 2;
/* TODO:
* STBC_param.dual_cts
* STBC_param.LSIG_TXOP_FILL
*/
val = cpu_to_le32(val);
wsm_write_mib(priv, WSM_MIB_ID_SET_HT_PROTECTION,
&val, sizeof(val));
priv->association_mode.greenfield =
cw1200_ht_greenfield(&priv->ht_info);
priv->association_mode.flags =
WSM_ASSOCIATION_MODE_SNOOP_ASSOC_FRAMES |
WSM_ASSOCIATION_MODE_USE_PREAMBLE_TYPE |
WSM_ASSOCIATION_MODE_USE_HT_MODE |
WSM_ASSOCIATION_MODE_USE_BASIC_RATE_SET |
WSM_ASSOCIATION_MODE_USE_MPDU_START_SPACING;
priv->association_mode.preamble =
info->use_short_preamble ?
WSM_JOIN_PREAMBLE_SHORT :
WSM_JOIN_PREAMBLE_LONG;
priv->association_mode.basic_rate_set = __cpu_to_le32(
cw1200_rate_mask_to_wsm(priv,
info->basic_rates));
priv->association_mode.mpdu_start_spacing =
cw1200_ht_ampdu_density(&priv->ht_info);
cw1200_cqm_bssloss_sm(priv, 0, 0, 0);
cancel_work_sync(&priv->unjoin_work);
priv->bss_params.beacon_lost_count = priv->cqm_beacon_loss_count;
priv->bss_params.aid = info->aid;
if (priv->join_dtim_period < 1)
priv->join_dtim_period = 1;
pr_debug("[STA] DTIM %d, interval: %d\n",
priv->join_dtim_period, priv->beacon_int);
pr_debug("[STA] Preamble: %d, Greenfield: %d, Aid: %d, Rates: 0x%.8X, Basic: 0x%.8X\n",
priv->association_mode.preamble,
priv->association_mode.greenfield,
priv->bss_params.aid,
priv->bss_params.operational_rate_set,
priv->association_mode.basic_rate_set);
wsm_set_association_mode(priv, &priv->association_mode);
if (!info->ibss_joined) {
wsm_keep_alive_period(priv, 30 /* sec */);
wsm_set_bss_params(priv, &priv->bss_params);
priv->setbssparams_done = true;
cw1200_set_beacon_wakeup_period_work(&priv->set_beacon_wakeup_period_work);
cw1200_set_pm(priv, &priv->powersave_mode);
}
if (priv->vif->p2p) {
pr_debug("[STA] Setting p2p powersave configuration.\n");
wsm_set_p2p_ps_modeinfo(priv,
&priv->p2p_ps_modeinfo);
}
if (priv->bt_present)
cw1200_set_btcoexinfo(priv);
} else {
memset(&priv->association_mode, 0,
sizeof(priv->association_mode));
memset(&priv->bss_params, 0, sizeof(priv->bss_params));
}
}
/* ERP Protection */
if (changed & (BSS_CHANGED_ASSOC |
BSS_CHANGED_ERP_CTS_PROT |
BSS_CHANGED_ERP_PREAMBLE)) {
u32 prev_erp_info = priv->erp_info;
if (info->use_cts_prot)
priv->erp_info |= WLAN_ERP_USE_PROTECTION;
else if (!(prev_erp_info & WLAN_ERP_NON_ERP_PRESENT))
priv->erp_info &= ~WLAN_ERP_USE_PROTECTION;
if (info->use_short_preamble)
priv->erp_info |= WLAN_ERP_BARKER_PREAMBLE;
else
priv->erp_info &= ~WLAN_ERP_BARKER_PREAMBLE;
pr_debug("[STA] ERP Protection: %x\n", priv->erp_info);
if (prev_erp_info != priv->erp_info)
queue_work(priv->workqueue, &priv->set_cts_work);
}
/* ERP Slottime */
if (changed & (BSS_CHANGED_ASSOC | BSS_CHANGED_ERP_SLOT)) {
__le32 slot_time = info->use_short_slot ?
__cpu_to_le32(9) : __cpu_to_le32(20);
pr_debug("[STA] Slot time: %d us.\n",
__le32_to_cpu(slot_time));
wsm_write_mib(priv, WSM_MIB_ID_DOT11_SLOT_TIME,
&slot_time, sizeof(slot_time));
}
if (changed & (BSS_CHANGED_ASSOC | BSS_CHANGED_CQM)) {
struct wsm_rcpi_rssi_threshold threshold = {
.rollingAverageCount = 8,
};
pr_debug("[CQM] RSSI threshold subscribe: %d +- %d\n",
info->cqm_rssi_thold, info->cqm_rssi_hyst);
priv->cqm_rssi_thold = info->cqm_rssi_thold;
priv->cqm_rssi_hyst = info->cqm_rssi_hyst;
if (info->cqm_rssi_thold || info->cqm_rssi_hyst) {
/* RSSI subscription enabled */
/* TODO: 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 relaible and stable.
*/
/* RSSI: signed Q8.0, RCPI: unsigned Q7.1
* RSSI = RCPI / 2 - 110
*/
if (priv->cqm_use_rssi) {
threshold.upperThreshold =
info->cqm_rssi_thold + info->cqm_rssi_hyst;
threshold.lowerThreshold =
info->cqm_rssi_thold;
threshold.rssiRcpiMode |= WSM_RCPI_RSSI_USE_RSSI;
} else {
threshold.upperThreshold = (info->cqm_rssi_thold + info->cqm_rssi_hyst + 110) * 2;
threshold.lowerThreshold = (info->cqm_rssi_thold + 110) * 2;
}
threshold.rssiRcpiMode |= WSM_RCPI_RSSI_THRESHOLD_ENABLE;
} else {
/* There is a bug in FW, see sta.c. We have to enable
* dummy subscription to get correct RSSI values.
*/
threshold.rssiRcpiMode |=
WSM_RCPI_RSSI_THRESHOLD_ENABLE |
WSM_RCPI_RSSI_DONT_USE_UPPER |
WSM_RCPI_RSSI_DONT_USE_LOWER;
if (priv->cqm_use_rssi)
threshold.rssiRcpiMode |= WSM_RCPI_RSSI_USE_RSSI;
}
wsm_set_rcpi_rssi_threshold(priv, &threshold);
}
mutex_unlock(&priv->conf_mutex);
if (do_join) {
wsm_lock_tx(priv);
cw1200_do_join(priv); /* Will unlock it for us */
}
}
void cw1200_multicast_start_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, multicast_start_work);
long tmo = priv->join_dtim_period *
(priv->beacon_int + 20) * HZ / 1024;
cancel_work_sync(&priv->multicast_stop_work);
if (!priv->aid0_bit_set) {
wsm_lock_tx(priv);
cw1200_set_tim_impl(priv, true);
priv->aid0_bit_set = true;
mod_timer(&priv->mcast_timeout, jiffies + tmo);
wsm_unlock_tx(priv);
}
}
void cw1200_multicast_stop_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, multicast_stop_work);
if (priv->aid0_bit_set) {
del_timer_sync(&priv->mcast_timeout);
wsm_lock_tx(priv);
priv->aid0_bit_set = false;
cw1200_set_tim_impl(priv, false);
wsm_unlock_tx(priv);
}
}
void cw1200_mcast_timeout(unsigned long arg)
{
struct cw1200_common *priv =
(struct cw1200_common *)arg;
wiphy_warn(priv->hw->wiphy,
"Multicast delivery timeout.\n");
spin_lock_bh(&priv->ps_state_lock);
priv->tx_multicast = priv->aid0_bit_set &&
priv->buffered_multicasts;
if (priv->tx_multicast)
cw1200_bh_wakeup(priv);
spin_unlock_bh(&priv->ps_state_lock);
}
int cw1200_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)
{
/* 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;
}
/* ******************************************************************** */
/* WSM callback */
void cw1200_suspend_resume(struct cw1200_common *priv,
struct wsm_suspend_resume *arg)
{
pr_debug("[AP] %s: %s\n",
arg->stop ? "stop" : "start",
arg->multicast ? "broadcast" : "unicast");
if (arg->multicast) {
bool cancel_tmo = false;
spin_lock_bh(&priv->ps_state_lock);
if (arg->stop) {
priv->tx_multicast = false;
} else {
/* Firmware sends this indication every DTIM if there
* is a STA in powersave connected. There is no reason
* to suspend, following wakeup will consume much more
* power than it could be saved.
*/
cw1200_pm_stay_awake(&priv->pm_state,
priv->join_dtim_period *
(priv->beacon_int + 20) * HZ / 1024);
priv->tx_multicast = (priv->aid0_bit_set &&
priv->buffered_multicasts);
if (priv->tx_multicast) {
cancel_tmo = true;
cw1200_bh_wakeup(priv);
}
}
spin_unlock_bh(&priv->ps_state_lock);
if (cancel_tmo)
del_timer_sync(&priv->mcast_timeout);
} else {
spin_lock_bh(&priv->ps_state_lock);
cw1200_ps_notify(priv, arg->link_id, arg->stop);
spin_unlock_bh(&priv->ps_state_lock);
if (!arg->stop)
cw1200_bh_wakeup(priv);
}
return;
}
/* ******************************************************************** */
/* AP privates */
static int cw1200_upload_beacon(struct cw1200_common *priv)
{
int ret = 0;
struct ieee80211_mgmt *mgmt;
struct wsm_template_frame frame = {
.frame_type = WSM_FRAME_TYPE_BEACON,
};
u16 tim_offset;
u16 tim_len;
if (priv->mode == NL80211_IFTYPE_STATION ||
priv->mode == NL80211_IFTYPE_MONITOR ||
priv->mode == NL80211_IFTYPE_UNSPECIFIED)
goto done;
if (priv->vif->p2p)
frame.rate = WSM_TRANSMIT_RATE_6;
frame.skb = ieee80211_beacon_get_tim(priv->hw, priv->vif,
&tim_offset, &tim_len);
if (!frame.skb)
return -ENOMEM;
ret = wsm_set_template_frame(priv, &frame);
if (ret)
goto done;
/* TODO: Distill probe resp; remove TIM
* and any other beacon-specific IEs
*/
mgmt = (void *)frame.skb->data;
mgmt->frame_control =
__cpu_to_le16(IEEE80211_FTYPE_MGMT |
IEEE80211_STYPE_PROBE_RESP);
frame.frame_type = WSM_FRAME_TYPE_PROBE_RESPONSE;
if (priv->vif->p2p) {
ret = wsm_set_probe_responder(priv, true);
} else {
ret = wsm_set_template_frame(priv, &frame);
wsm_set_probe_responder(priv, false);
}
done:
dev_kfree_skb(frame.skb);
return ret;
}
static int cw1200_upload_pspoll(struct cw1200_common *priv)
{
int ret = 0;
struct wsm_template_frame frame = {
.frame_type = WSM_FRAME_TYPE_PS_POLL,
.rate = 0xFF,
};
frame.skb = ieee80211_pspoll_get(priv->hw, priv->vif);
if (!frame.skb)
return -ENOMEM;
ret = wsm_set_template_frame(priv, &frame);
dev_kfree_skb(frame.skb);
return ret;
}
static int cw1200_upload_null(struct cw1200_common *priv)
{
int ret = 0;
struct wsm_template_frame frame = {
.frame_type = WSM_FRAME_TYPE_NULL,
.rate = 0xFF,
};
frame.skb = ieee80211_nullfunc_get(priv->hw, priv->vif);
if (!frame.skb)
return -ENOMEM;
ret = wsm_set_template_frame(priv, &frame);
dev_kfree_skb(frame.skb);
return ret;
}
static int cw1200_upload_qosnull(struct cw1200_common *priv)
{
int ret = 0;
/* TODO: This needs to be implemented
struct wsm_template_frame frame = {
.frame_type = WSM_FRAME_TYPE_QOS_NULL,
.rate = 0xFF,
};
frame.skb = ieee80211_qosnullfunc_get(priv->hw, priv->vif);
if (!frame.skb)
return -ENOMEM;
ret = wsm_set_template_frame(priv, &frame);
dev_kfree_skb(frame.skb);
*/
return ret;
}
static int cw1200_enable_beaconing(struct cw1200_common *priv,
bool enable)
{
struct wsm_beacon_transmit transmit = {
.enable_beaconing = enable,
};
return wsm_beacon_transmit(priv, &transmit);
}
static int cw1200_start_ap(struct cw1200_common *priv)
{
int ret;
struct ieee80211_bss_conf *conf = &priv->vif->bss_conf;
struct wsm_start start = {
.mode = priv->vif->p2p ?
WSM_START_MODE_P2P_GO : WSM_START_MODE_AP,
.band = (priv->channel->band == IEEE80211_BAND_5GHZ) ?
WSM_PHY_BAND_5G : WSM_PHY_BAND_2_4G,
.channel_number = priv->channel->hw_value,
.beacon_interval = conf->beacon_int,
.dtim_period = conf->dtim_period,
.preamble = conf->use_short_preamble ?
WSM_JOIN_PREAMBLE_SHORT :
WSM_JOIN_PREAMBLE_LONG,
.probe_delay = 100,
.basic_rate_set = cw1200_rate_mask_to_wsm(priv,
conf->basic_rates),
};
struct wsm_operational_mode mode = {
.power_mode = cw1200_power_mode,
.disable_more_flag_usage = true,
};
memset(start.ssid, 0, sizeof(start.ssid));
if (!conf->hidden_ssid) {
start.ssid_len = conf->ssid_len;
memcpy(start.ssid, conf->ssid, start.ssid_len);
}
priv->beacon_int = conf->beacon_int;
priv->join_dtim_period = conf->dtim_period;
memset(&priv->link_id_db, 0, sizeof(priv->link_id_db));
pr_debug("[AP] ch: %d(%d), bcn: %d(%d), brt: 0x%.8X, ssid: %.*s.\n",
start.channel_number, start.band,
start.beacon_interval, start.dtim_period,
start.basic_rate_set,
start.ssid_len, start.ssid);
ret = wsm_start(priv, &start);
if (!ret)
ret = cw1200_upload_keys(priv);
if (!ret && priv->vif->p2p) {
pr_debug("[AP] Setting p2p powersave configuration.\n");
wsm_set_p2p_ps_modeinfo(priv, &priv->p2p_ps_modeinfo);
}
if (!ret) {
wsm_set_block_ack_policy(priv, 0, 0);
priv->join_status = CW1200_JOIN_STATUS_AP;
cw1200_update_filtering(priv);
}
wsm_set_operational_mode(priv, &mode);
return ret;
}
static int cw1200_update_beaconing(struct cw1200_common *priv)
{
struct ieee80211_bss_conf *conf = &priv->vif->bss_conf;
struct wsm_reset reset = {
.link_id = 0,
.reset_statistics = true,
};
if (priv->mode == NL80211_IFTYPE_AP) {
/* TODO: check if changed channel, band */
if (priv->join_status != CW1200_JOIN_STATUS_AP ||
priv->beacon_int != conf->beacon_int) {
pr_debug("ap restarting\n");
wsm_lock_tx(priv);
if (priv->join_status != CW1200_JOIN_STATUS_PASSIVE)
wsm_reset(priv, &reset);
priv->join_status = CW1200_JOIN_STATUS_PASSIVE;
cw1200_start_ap(priv);
wsm_unlock_tx(priv);
} else
pr_debug("ap started join_status: %d\n",
priv->join_status);
}
return 0;
}
/*
* Mac80211 STA interface for ST-Ericsson CW1200 mac80211 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef STA_H_INCLUDED
#define STA_H_INCLUDED
/* ******************************************************************** */
/* mac80211 API */
int cw1200_start(struct ieee80211_hw *dev);
void cw1200_stop(struct ieee80211_hw *dev);
int cw1200_add_interface(struct ieee80211_hw *dev,
struct ieee80211_vif *vif);
void cw1200_remove_interface(struct ieee80211_hw *dev,
struct ieee80211_vif *vif);
int cw1200_change_interface(struct ieee80211_hw *dev,
struct ieee80211_vif *vif,
enum nl80211_iftype new_type,
bool p2p);
int cw1200_config(struct ieee80211_hw *dev, u32 changed);
void cw1200_configure_filter(struct ieee80211_hw *dev,
unsigned int changed_flags,
unsigned int *total_flags,
u64 multicast);
int cw1200_conf_tx(struct ieee80211_hw *dev, struct ieee80211_vif *vif,
u16 queue, const struct ieee80211_tx_queue_params *params);
int cw1200_get_stats(struct ieee80211_hw *dev,
struct ieee80211_low_level_stats *stats);
int cw1200_set_key(struct ieee80211_hw *dev, enum set_key_cmd cmd,
struct ieee80211_vif *vif, struct ieee80211_sta *sta,
struct ieee80211_key_conf *key);
int cw1200_set_rts_threshold(struct ieee80211_hw *hw, u32 value);
void cw1200_flush(struct ieee80211_hw *hw, u32 queues, bool drop);
u64 cw1200_prepare_multicast(struct ieee80211_hw *hw,
struct netdev_hw_addr_list *mc_list);
int cw1200_set_pm(struct cw1200_common *priv, const struct wsm_set_pm *arg);
/* ******************************************************************** */
/* WSM callbacks */
void cw1200_join_complete_cb(struct cw1200_common *priv,
struct wsm_join_complete *arg);
/* ******************************************************************** */
/* WSM events */
void cw1200_free_event_queue(struct cw1200_common *priv);
void cw1200_event_handler(struct work_struct *work);
void cw1200_bss_loss_work(struct work_struct *work);
void cw1200_bss_params_work(struct work_struct *work);
void cw1200_keep_alive_work(struct work_struct *work);
void cw1200_tx_failure_work(struct work_struct *work);
void __cw1200_cqm_bssloss_sm(struct cw1200_common *priv, int init, int good,
int bad);
static inline void cw1200_cqm_bssloss_sm(struct cw1200_common *priv,
int init, int good, int bad)
{
spin_lock(&priv->bss_loss_lock);
__cw1200_cqm_bssloss_sm(priv, init, good, bad);
spin_unlock(&priv->bss_loss_lock);
}
/* ******************************************************************** */
/* Internal API */
int cw1200_setup_mac(struct cw1200_common *priv);
void cw1200_join_timeout(struct work_struct *work);
void cw1200_unjoin_work(struct work_struct *work);
void cw1200_join_complete_work(struct work_struct *work);
void cw1200_wep_key_work(struct work_struct *work);
void cw1200_update_listening(struct cw1200_common *priv, bool enabled);
void cw1200_update_filtering(struct cw1200_common *priv);
void cw1200_update_filtering_work(struct work_struct *work);
void cw1200_set_beacon_wakeup_period_work(struct work_struct *work);
int cw1200_enable_listening(struct cw1200_common *priv);
int cw1200_disable_listening(struct cw1200_common *priv);
int cw1200_set_uapsd_param(struct cw1200_common *priv,
const struct wsm_edca_params *arg);
void cw1200_ba_work(struct work_struct *work);
void cw1200_ba_timer(unsigned long arg);
/* AP stuffs */
int cw1200_set_tim(struct ieee80211_hw *dev, struct ieee80211_sta *sta,
bool set);
int cw1200_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_sta *sta);
int cw1200_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_sta *sta);
void cw1200_sta_notify(struct ieee80211_hw *dev, struct ieee80211_vif *vif,
enum sta_notify_cmd notify_cmd,
struct ieee80211_sta *sta);
void cw1200_bss_info_changed(struct ieee80211_hw *dev,
struct ieee80211_vif *vif,
struct ieee80211_bss_conf *info,
u32 changed);
int cw1200_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);
void cw1200_suspend_resume(struct cw1200_common *priv,
struct wsm_suspend_resume *arg);
void cw1200_set_tim_work(struct work_struct *work);
void cw1200_set_cts_work(struct work_struct *work);
void cw1200_multicast_start_work(struct work_struct *work);
void cw1200_multicast_stop_work(struct work_struct *work);
void cw1200_mcast_timeout(unsigned long arg);
#endif
/*
* Datapath implementation for ST-Ericsson CW1200 mac80211 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <net/mac80211.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include "cw1200.h"
#include "wsm.h"
#include "bh.h"
#include "sta.h"
#include "debug.h"
#define CW1200_INVALID_RATE_ID (0xFF)
static int cw1200_handle_action_rx(struct cw1200_common *priv,
struct sk_buff *skb);
static const struct ieee80211_rate *
cw1200_get_tx_rate(const struct cw1200_common *priv,
const struct ieee80211_tx_rate *rate);
/* ******************************************************************** */
/* TX queue lock / unlock */
static inline void cw1200_tx_queues_lock(struct cw1200_common *priv)
{
int i;
for (i = 0; i < 4; ++i)
cw1200_queue_lock(&priv->tx_queue[i]);
}
static inline void cw1200_tx_queues_unlock(struct cw1200_common *priv)
{
int i;
for (i = 0; i < 4; ++i)
cw1200_queue_unlock(&priv->tx_queue[i]);
}
/* ******************************************************************** */
/* TX policy cache implementation */
static void tx_policy_dump(struct tx_policy *policy)
{
pr_debug("[TX policy] %.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X %.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X %.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X: %d\n",
policy->raw[0] & 0x0F, policy->raw[0] >> 4,
policy->raw[1] & 0x0F, policy->raw[1] >> 4,
policy->raw[2] & 0x0F, policy->raw[2] >> 4,
policy->raw[3] & 0x0F, policy->raw[3] >> 4,
policy->raw[4] & 0x0F, policy->raw[4] >> 4,
policy->raw[5] & 0x0F, policy->raw[5] >> 4,
policy->raw[6] & 0x0F, policy->raw[6] >> 4,
policy->raw[7] & 0x0F, policy->raw[7] >> 4,
policy->raw[8] & 0x0F, policy->raw[8] >> 4,
policy->raw[9] & 0x0F, policy->raw[9] >> 4,
policy->raw[10] & 0x0F, policy->raw[10] >> 4,
policy->raw[11] & 0x0F, policy->raw[11] >> 4,
policy->defined);
}
static void tx_policy_build(const struct cw1200_common *priv,
/* [out] */ struct tx_policy *policy,
struct ieee80211_tx_rate *rates, size_t count)
{
int i, j;
unsigned limit = priv->short_frame_max_tx_count;
unsigned total = 0;
BUG_ON(rates[0].idx < 0);
memset(policy, 0, sizeof(*policy));
/* minstrel is buggy a little bit, so distille
* incoming rates first. */
/* Sort rates in descending order. */
for (i = 1; i < count; ++i) {
if (rates[i].idx < 0) {
count = i;
break;
}
if (rates[i].idx > rates[i - 1].idx) {
struct ieee80211_tx_rate tmp = rates[i - 1];
rates[i - 1] = rates[i];
rates[i] = tmp;
}
}
/* Eliminate duplicates. */
total = rates[0].count;
for (i = 0, j = 1; j < count; ++j) {
if (rates[j].idx == rates[i].idx) {
rates[i].count += rates[j].count;
} else if (rates[j].idx > rates[i].idx) {
break;
} else {
++i;
if (i != j)
rates[i] = rates[j];
}
total += rates[j].count;
}
count = i + 1;
/* Re-fill policy trying to keep every requested rate and with
* respect to the global max tx retransmission count. */
if (limit < count)
limit = count;
if (total > limit) {
for (i = 0; i < count; ++i) {
int left = count - i - 1;
if (rates[i].count > limit - left)
rates[i].count = limit - left;
limit -= rates[i].count;
}
}
/* HACK!!! Device has problems (at least) switching from
* 54Mbps CTS to 1Mbps. This switch takes enormous amount
* of time (100-200 ms), leading to valuable throughput drop.
* As a workaround, additional g-rates are injected to the
* policy.
*/
if (count == 2 && !(rates[0].flags & IEEE80211_TX_RC_MCS) &&
rates[0].idx > 4 && rates[0].count > 2 &&
rates[1].idx < 2) {
/* ">> 1" is an equivalent of "/ 2", but faster */
int mid_rate = (rates[0].idx + 4) >> 1;
/* Decrease number of retries for the initial rate */
rates[0].count -= 2;
if (mid_rate != 4) {
/* Keep fallback rate at 1Mbps. */
rates[3] = rates[1];
/* Inject 1 transmission on lowest g-rate */
rates[2].idx = 4;
rates[2].count = 1;
rates[2].flags = rates[1].flags;
/* Inject 1 transmission on mid-rate */
rates[1].idx = mid_rate;
rates[1].count = 1;
/* Fallback to 1 Mbps is a really bad thing,
* so let's try to increase probability of
* successful transmission on the lowest g rate
* even more */
if (rates[0].count >= 3) {
--rates[0].count;
++rates[2].count;
}
/* Adjust amount of rates defined */
count += 2;
} else {
/* Keep fallback rate at 1Mbps. */
rates[2] = rates[1];
/* Inject 2 transmissions on lowest g-rate */
rates[1].idx = 4;
rates[1].count = 2;
/* Adjust amount of rates defined */
count += 1;
}
}
policy->defined = cw1200_get_tx_rate(priv, &rates[0])->hw_value + 1;
for (i = 0; i < count; ++i) {
register unsigned rateid, off, shift, retries;
rateid = cw1200_get_tx_rate(priv, &rates[i])->hw_value;
off = rateid >> 3; /* eq. rateid / 8 */
shift = (rateid & 0x07) << 2; /* eq. (rateid % 8) * 4 */
retries = rates[i].count;
if (retries > 0x0F) {
rates[i].count = 0x0f;
retries = 0x0F;
}
policy->tbl[off] |= __cpu_to_le32(retries << shift);
policy->retry_count += retries;
}
pr_debug("[TX policy] Policy (%zu): %d:%d, %d:%d, %d:%d, %d:%d, %d:%d\n",
count,
rates[0].idx, rates[0].count,
rates[1].idx, rates[1].count,
rates[2].idx, rates[2].count,
rates[3].idx, rates[3].count,
rates[4].idx, rates[4].count);
}
static inline bool tx_policy_is_equal(const struct tx_policy *wanted,
const struct tx_policy *cached)
{
size_t count = wanted->defined >> 1;
if (wanted->defined > cached->defined)
return false;
if (count) {
if (memcmp(wanted->raw, cached->raw, count))
return false;
}
if (wanted->defined & 1) {
if ((wanted->raw[count] & 0x0F) != (cached->raw[count] & 0x0F))
return false;
}
return true;
}
static int tx_policy_find(struct tx_policy_cache *cache,
const struct tx_policy *wanted)
{
/* O(n) complexity. Not so good, but there's only 8 entries in
* the cache.
* Also lru helps to reduce search time. */
struct tx_policy_cache_entry *it;
/* First search for policy in "used" list */
list_for_each_entry(it, &cache->used, link) {
if (tx_policy_is_equal(wanted, &it->policy))
return it - cache->cache;
}
/* Then - in "free list" */
list_for_each_entry(it, &cache->free, link) {
if (tx_policy_is_equal(wanted, &it->policy))
return it - cache->cache;
}
return -1;
}
static inline void tx_policy_use(struct tx_policy_cache *cache,
struct tx_policy_cache_entry *entry)
{
++entry->policy.usage_count;
list_move(&entry->link, &cache->used);
}
static inline int tx_policy_release(struct tx_policy_cache *cache,
struct tx_policy_cache_entry *entry)
{
int ret = --entry->policy.usage_count;
if (!ret)
list_move(&entry->link, &cache->free);
return ret;
}
void tx_policy_clean(struct cw1200_common *priv)
{
int idx, locked;
struct tx_policy_cache *cache = &priv->tx_policy_cache;
struct tx_policy_cache_entry *entry;
cw1200_tx_queues_lock(priv);
spin_lock_bh(&cache->lock);
locked = list_empty(&cache->free);
for (idx = 0; idx < TX_POLICY_CACHE_SIZE; idx++) {
entry = &cache->cache[idx];
/* Policy usage count should be 0 at this time as all queues
should be empty */
if (WARN_ON(entry->policy.usage_count)) {
entry->policy.usage_count = 0;
list_move(&entry->link, &cache->free);
}
memset(&entry->policy, 0, sizeof(entry->policy));
}
if (locked)
cw1200_tx_queues_unlock(priv);
cw1200_tx_queues_unlock(priv);
spin_unlock_bh(&cache->lock);
}
/* ******************************************************************** */
/* External TX policy cache API */
void tx_policy_init(struct cw1200_common *priv)
{
struct tx_policy_cache *cache = &priv->tx_policy_cache;
int i;
memset(cache, 0, sizeof(*cache));
spin_lock_init(&cache->lock);
INIT_LIST_HEAD(&cache->used);
INIT_LIST_HEAD(&cache->free);
for (i = 0; i < TX_POLICY_CACHE_SIZE; ++i)
list_add(&cache->cache[i].link, &cache->free);
}
static int tx_policy_get(struct cw1200_common *priv,
struct ieee80211_tx_rate *rates,
size_t count, bool *renew)
{
int idx;
struct tx_policy_cache *cache = &priv->tx_policy_cache;
struct tx_policy wanted;
tx_policy_build(priv, &wanted, rates, count);
spin_lock_bh(&cache->lock);
if (WARN_ON_ONCE(list_empty(&cache->free))) {
spin_unlock_bh(&cache->lock);
return CW1200_INVALID_RATE_ID;
}
idx = tx_policy_find(cache, &wanted);
if (idx >= 0) {
pr_debug("[TX policy] Used TX policy: %d\n", idx);
*renew = false;
} else {
struct tx_policy_cache_entry *entry;
*renew = true;
/* If policy is not found create a new one
* using the oldest entry in "free" list */
entry = list_entry(cache->free.prev,
struct tx_policy_cache_entry, link);
entry->policy = wanted;
idx = entry - cache->cache;
pr_debug("[TX policy] New TX policy: %d\n", idx);
tx_policy_dump(&entry->policy);
}
tx_policy_use(cache, &cache->cache[idx]);
if (list_empty(&cache->free)) {
/* Lock TX queues. */
cw1200_tx_queues_lock(priv);
}
spin_unlock_bh(&cache->lock);
return idx;
}
static void tx_policy_put(struct cw1200_common *priv, int idx)
{
int usage, locked;
struct tx_policy_cache *cache = &priv->tx_policy_cache;
spin_lock_bh(&cache->lock);
locked = list_empty(&cache->free);
usage = tx_policy_release(cache, &cache->cache[idx]);
if (locked && !usage) {
/* Unlock TX queues. */
cw1200_tx_queues_unlock(priv);
}
spin_unlock_bh(&cache->lock);
}
static int tx_policy_upload(struct cw1200_common *priv)
{
struct tx_policy_cache *cache = &priv->tx_policy_cache;
int i;
struct wsm_set_tx_rate_retry_policy arg = {
.num = 0,
};
spin_lock_bh(&cache->lock);
/* Upload only modified entries. */
for (i = 0; i < TX_POLICY_CACHE_SIZE; ++i) {
struct tx_policy *src = &cache->cache[i].policy;
if (src->retry_count && !src->uploaded) {
struct wsm_tx_rate_retry_policy *dst =
&arg.tbl[arg.num];
dst->index = i;
dst->short_retries = priv->short_frame_max_tx_count;
dst->long_retries = priv->long_frame_max_tx_count;
dst->flags = WSM_TX_RATE_POLICY_FLAG_TERMINATE_WHEN_FINISHED |
WSM_TX_RATE_POLICY_FLAG_COUNT_INITIAL_TRANSMIT;
memcpy(dst->rate_count_indices, src->tbl,
sizeof(dst->rate_count_indices));
src->uploaded = 1;
++arg.num;
}
}
spin_unlock_bh(&cache->lock);
cw1200_debug_tx_cache_miss(priv);
pr_debug("[TX policy] Upload %d policies\n", arg.num);
return wsm_set_tx_rate_retry_policy(priv, &arg);
}
void tx_policy_upload_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, tx_policy_upload_work);
pr_debug("[TX] TX policy upload.\n");
tx_policy_upload(priv);
wsm_unlock_tx(priv);
cw1200_tx_queues_unlock(priv);
}
/* ******************************************************************** */
/* cw1200 TX implementation */
struct cw1200_txinfo {
struct sk_buff *skb;
unsigned queue;
struct ieee80211_tx_info *tx_info;
const struct ieee80211_rate *rate;
struct ieee80211_hdr *hdr;
size_t hdrlen;
const u8 *da;
struct cw1200_sta_priv *sta_priv;
struct ieee80211_sta *sta;
struct cw1200_txpriv txpriv;
};
u32 cw1200_rate_mask_to_wsm(struct cw1200_common *priv, u32 rates)
{
u32 ret = 0;
int i;
for (i = 0; i < 32; ++i) {
if (rates & BIT(i))
ret |= BIT(priv->rates[i].hw_value);
}
return ret;
}
static const struct ieee80211_rate *
cw1200_get_tx_rate(const struct cw1200_common *priv,
const struct ieee80211_tx_rate *rate)
{
if (rate->idx < 0)
return NULL;
if (rate->flags & IEEE80211_TX_RC_MCS)
return &priv->mcs_rates[rate->idx];
return &priv->hw->wiphy->bands[priv->channel->band]->
bitrates[rate->idx];
}
static int
cw1200_tx_h_calc_link_ids(struct cw1200_common *priv,
struct cw1200_txinfo *t)
{
if (t->sta && t->sta_priv->link_id)
t->txpriv.raw_link_id =
t->txpriv.link_id =
t->sta_priv->link_id;
else if (priv->mode != NL80211_IFTYPE_AP)
t->txpriv.raw_link_id =
t->txpriv.link_id = 0;
else if (is_multicast_ether_addr(t->da)) {
if (priv->enable_beacon) {
t->txpriv.raw_link_id = 0;
t->txpriv.link_id = CW1200_LINK_ID_AFTER_DTIM;
} else {
t->txpriv.raw_link_id = 0;
t->txpriv.link_id = 0;
}
} else {
t->txpriv.link_id = cw1200_find_link_id(priv, t->da);
if (!t->txpriv.link_id)
t->txpriv.link_id = cw1200_alloc_link_id(priv, t->da);
if (!t->txpriv.link_id) {
wiphy_err(priv->hw->wiphy,
"No more link IDs available.\n");
return -ENOENT;
}
t->txpriv.raw_link_id = t->txpriv.link_id;
}
if (t->txpriv.raw_link_id)
priv->link_id_db[t->txpriv.raw_link_id - 1].timestamp =
jiffies;
if (t->sta && (t->sta->uapsd_queues & BIT(t->queue)))
t->txpriv.link_id = CW1200_LINK_ID_UAPSD;
return 0;
}
static void
cw1200_tx_h_pm(struct cw1200_common *priv,
struct cw1200_txinfo *t)
{
if (ieee80211_is_auth(t->hdr->frame_control)) {
u32 mask = ~BIT(t->txpriv.raw_link_id);
spin_lock_bh(&priv->ps_state_lock);
priv->sta_asleep_mask &= mask;
priv->pspoll_mask &= mask;
spin_unlock_bh(&priv->ps_state_lock);
}
}
static void
cw1200_tx_h_calc_tid(struct cw1200_common *priv,
struct cw1200_txinfo *t)
{
if (ieee80211_is_data_qos(t->hdr->frame_control)) {
u8 *qos = ieee80211_get_qos_ctl(t->hdr);
t->txpriv.tid = qos[0] & IEEE80211_QOS_CTL_TID_MASK;
} else if (ieee80211_is_data(t->hdr->frame_control)) {
t->txpriv.tid = 0;
}
}
static int
cw1200_tx_h_crypt(struct cw1200_common *priv,
struct cw1200_txinfo *t)
{
if (!t->tx_info->control.hw_key ||
!ieee80211_has_protected(t->hdr->frame_control))
return 0;
t->hdrlen += t->tx_info->control.hw_key->iv_len;
skb_put(t->skb, t->tx_info->control.hw_key->icv_len);
if (t->tx_info->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP)
skb_put(t->skb, 8); /* MIC space */
return 0;
}
static int
cw1200_tx_h_align(struct cw1200_common *priv,
struct cw1200_txinfo *t,
u8 *flags)
{
size_t offset = (size_t)t->skb->data & 3;
if (!offset)
return 0;
if (offset & 1) {
wiphy_err(priv->hw->wiphy,
"Bug: attempt to transmit a frame with wrong alignment: %zu\n",
offset);
return -EINVAL;
}
if (skb_headroom(t->skb) < offset) {
wiphy_err(priv->hw->wiphy,
"Bug: no space allocated for DMA alignment. headroom: %d\n",
skb_headroom(t->skb));
return -ENOMEM;
}
skb_push(t->skb, offset);
t->hdrlen += offset;
t->txpriv.offset += offset;
*flags |= WSM_TX_2BYTES_SHIFT;
cw1200_debug_tx_align(priv);
return 0;
}
static int
cw1200_tx_h_action(struct cw1200_common *priv,
struct cw1200_txinfo *t)
{
struct ieee80211_mgmt *mgmt =
(struct ieee80211_mgmt *)t->hdr;
if (ieee80211_is_action(t->hdr->frame_control) &&
mgmt->u.action.category == WLAN_CATEGORY_BACK)
return 1;
else
return 0;
}
/* Add WSM header */
static struct wsm_tx *
cw1200_tx_h_wsm(struct cw1200_common *priv,
struct cw1200_txinfo *t)
{
struct wsm_tx *wsm;
if (skb_headroom(t->skb) < sizeof(struct wsm_tx)) {
wiphy_err(priv->hw->wiphy,
"Bug: no space allocated for WSM header. headroom: %d\n",
skb_headroom(t->skb));
return NULL;
}
wsm = (struct wsm_tx *)skb_push(t->skb, sizeof(struct wsm_tx));
t->txpriv.offset += sizeof(struct wsm_tx);
memset(wsm, 0, sizeof(*wsm));
wsm->hdr.len = __cpu_to_le16(t->skb->len);
wsm->hdr.id = __cpu_to_le16(0x0004);
wsm->queue_id = wsm_queue_id_to_wsm(t->queue);
return wsm;
}
/* BT Coex specific handling */
static void
cw1200_tx_h_bt(struct cw1200_common *priv,
struct cw1200_txinfo *t,
struct wsm_tx *wsm)
{
u8 priority = 0;
if (!priv->bt_present)
return;
if (ieee80211_is_nullfunc(t->hdr->frame_control)) {
priority = WSM_EPTA_PRIORITY_MGT;
} else if (ieee80211_is_data(t->hdr->frame_control)) {
/* Skip LLC SNAP header (+6) */
u8 *payload = &t->skb->data[t->hdrlen];
u16 *ethertype = (u16 *)&payload[6];
if (*ethertype == __be16_to_cpu(ETH_P_PAE))
priority = WSM_EPTA_PRIORITY_EAPOL;
} else if (ieee80211_is_assoc_req(t->hdr->frame_control) ||
ieee80211_is_reassoc_req(t->hdr->frame_control)) {
struct ieee80211_mgmt *mgt_frame =
(struct ieee80211_mgmt *)t->hdr;
if (mgt_frame->u.assoc_req.listen_interval <
priv->listen_interval) {
pr_debug("Modified Listen Interval to %d from %d\n",
priv->listen_interval,
mgt_frame->u.assoc_req.listen_interval);
/* Replace listen interval derieved from
* the one read from SDD */
mgt_frame->u.assoc_req.listen_interval =
priv->listen_interval;
}
}
if (!priority) {
if (ieee80211_is_action(t->hdr->frame_control))
priority = WSM_EPTA_PRIORITY_ACTION;
else if (ieee80211_is_mgmt(t->hdr->frame_control))
priority = WSM_EPTA_PRIORITY_MGT;
else if ((wsm->queue_id == WSM_QUEUE_VOICE))
priority = WSM_EPTA_PRIORITY_VOICE;
else if ((wsm->queue_id == WSM_QUEUE_VIDEO))
priority = WSM_EPTA_PRIORITY_VIDEO;
else
priority = WSM_EPTA_PRIORITY_DATA;
}
pr_debug("[TX] EPTA priority %d.\n", priority);
wsm->flags |= priority << 1;
}
static int
cw1200_tx_h_rate_policy(struct cw1200_common *priv,
struct cw1200_txinfo *t,
struct wsm_tx *wsm)
{
bool tx_policy_renew = false;
t->txpriv.rate_id = tx_policy_get(priv,
t->tx_info->control.rates, IEEE80211_TX_MAX_RATES,
&tx_policy_renew);
if (t->txpriv.rate_id == CW1200_INVALID_RATE_ID)
return -EFAULT;
wsm->flags |= t->txpriv.rate_id << 4;
t->rate = cw1200_get_tx_rate(priv,
&t->tx_info->control.rates[0]),
wsm->max_tx_rate = t->rate->hw_value;
if (t->rate->flags & IEEE80211_TX_RC_MCS) {
if (cw1200_ht_greenfield(&priv->ht_info))
wsm->ht_tx_parameters |=
__cpu_to_le32(WSM_HT_TX_GREENFIELD);
else
wsm->ht_tx_parameters |=
__cpu_to_le32(WSM_HT_TX_MIXED);
}
if (tx_policy_renew) {
pr_debug("[TX] TX policy renew.\n");
/* It's not so optimal to stop TX queues every now and then.
* Better to reimplement task scheduling with
* a counter. TODO. */
wsm_lock_tx_async(priv);
cw1200_tx_queues_lock(priv);
if (queue_work(priv->workqueue,
&priv->tx_policy_upload_work) <= 0) {
cw1200_tx_queues_unlock(priv);
wsm_unlock_tx(priv);
}
}
return 0;
}
static bool
cw1200_tx_h_pm_state(struct cw1200_common *priv,
struct cw1200_txinfo *t)
{
int was_buffered = 1;
if (t->txpriv.link_id == CW1200_LINK_ID_AFTER_DTIM &&
!priv->buffered_multicasts) {
priv->buffered_multicasts = true;
if (priv->sta_asleep_mask)
queue_work(priv->workqueue,
&priv->multicast_start_work);
}
if (t->txpriv.raw_link_id && t->txpriv.tid < CW1200_MAX_TID)
was_buffered = priv->link_id_db[t->txpriv.raw_link_id - 1].buffered[t->txpriv.tid]++;
return !was_buffered;
}
/* ******************************************************************** */
void cw1200_tx(struct ieee80211_hw *dev,
struct ieee80211_tx_control *control,
struct sk_buff *skb)
{
struct cw1200_common *priv = dev->priv;
struct cw1200_txinfo t = {
.skb = skb,
.queue = skb_get_queue_mapping(skb),
.tx_info = IEEE80211_SKB_CB(skb),
.hdr = (struct ieee80211_hdr *)skb->data,
.txpriv.tid = CW1200_MAX_TID,
.txpriv.rate_id = CW1200_INVALID_RATE_ID,
};
struct ieee80211_sta *sta;
struct wsm_tx *wsm;
bool tid_update = 0;
u8 flags = 0;
int ret;
if (priv->bh_error)
goto drop;
t.hdrlen = ieee80211_hdrlen(t.hdr->frame_control);
t.da = ieee80211_get_DA(t.hdr);
if (control) {
t.sta = control->sta;
t.sta_priv = (struct cw1200_sta_priv *)&t.sta->drv_priv;
}
if (WARN_ON(t.queue >= 4))
goto drop;
ret = cw1200_tx_h_calc_link_ids(priv, &t);
if (ret)
goto drop;
pr_debug("[TX] TX %d bytes (queue: %d, link_id: %d (%d)).\n",
skb->len, t.queue, t.txpriv.link_id,
t.txpriv.raw_link_id);
cw1200_tx_h_pm(priv, &t);
cw1200_tx_h_calc_tid(priv, &t);
ret = cw1200_tx_h_crypt(priv, &t);
if (ret)
goto drop;
ret = cw1200_tx_h_align(priv, &t, &flags);
if (ret)
goto drop;
ret = cw1200_tx_h_action(priv, &t);
if (ret)
goto drop;
wsm = cw1200_tx_h_wsm(priv, &t);
if (!wsm) {
ret = -ENOMEM;
goto drop;
}
wsm->flags |= flags;
cw1200_tx_h_bt(priv, &t, wsm);
ret = cw1200_tx_h_rate_policy(priv, &t, wsm);
if (ret)
goto drop;
rcu_read_lock();
sta = rcu_dereference(t.sta);
spin_lock_bh(&priv->ps_state_lock);
{
tid_update = cw1200_tx_h_pm_state(priv, &t);
BUG_ON(cw1200_queue_put(&priv->tx_queue[t.queue],
t.skb, &t.txpriv));
}
spin_unlock_bh(&priv->ps_state_lock);
if (tid_update && sta)
ieee80211_sta_set_buffered(sta, t.txpriv.tid, true);
rcu_read_unlock();
cw1200_bh_wakeup(priv);
return;
drop:
cw1200_skb_dtor(priv, skb, &t.txpriv);
return;
}
/* ******************************************************************** */
static int cw1200_handle_action_rx(struct cw1200_common *priv,
struct sk_buff *skb)
{
struct ieee80211_mgmt *mgmt = (void *)skb->data;
/* Filter block ACK negotiation: fully controlled by firmware */
if (mgmt->u.action.category == WLAN_CATEGORY_BACK)
return 1;
return 0;
}
static int cw1200_handle_pspoll(struct cw1200_common *priv,
struct sk_buff *skb)
{
struct ieee80211_sta *sta;
struct ieee80211_pspoll *pspoll = (struct ieee80211_pspoll *)skb->data;
int link_id = 0;
u32 pspoll_mask = 0;
int drop = 1;
int i;
if (priv->join_status != CW1200_JOIN_STATUS_AP)
goto done;
if (memcmp(priv->vif->addr, pspoll->bssid, ETH_ALEN))
goto done;
rcu_read_lock();
sta = ieee80211_find_sta(priv->vif, pspoll->ta);
if (sta) {
struct cw1200_sta_priv *sta_priv;
sta_priv = (struct cw1200_sta_priv *)&sta->drv_priv;
link_id = sta_priv->link_id;
pspoll_mask = BIT(sta_priv->link_id);
}
rcu_read_unlock();
if (!link_id)
goto done;
priv->pspoll_mask |= pspoll_mask;
drop = 0;
/* Do not report pspols if data for given link id is
* queued already. */
for (i = 0; i < 4; ++i) {
if (cw1200_queue_get_num_queued(&priv->tx_queue[i],
pspoll_mask)) {
cw1200_bh_wakeup(priv);
drop = 1;
break;
}
}
pr_debug("[RX] PSPOLL: %s\n", drop ? "local" : "fwd");
done:
return drop;
}
/* ******************************************************************** */
void cw1200_tx_confirm_cb(struct cw1200_common *priv,
int link_id,
struct wsm_tx_confirm *arg)
{
u8 queue_id = cw1200_queue_get_queue_id(arg->packet_id);
struct cw1200_queue *queue = &priv->tx_queue[queue_id];
struct sk_buff *skb;
const struct cw1200_txpriv *txpriv;
pr_debug("[TX] TX confirm: %d, %d.\n",
arg->status, arg->ack_failures);
if (cw1200_itp_tx_running(priv))
return;
if (priv->mode == NL80211_IFTYPE_UNSPECIFIED) {
/* STA is stopped. */
return;
}
if (WARN_ON(queue_id >= 4))
return;
if (arg->status)
pr_debug("TX failed: %d.\n", arg->status);
if ((arg->status == WSM_REQUEUE) &&
(arg->flags & WSM_TX_STATUS_REQUEUE)) {
/* "Requeue" means "implicit suspend" */
struct wsm_suspend_resume suspend = {
.link_id = link_id,
.stop = 1,
.multicast = !link_id,
};
cw1200_suspend_resume(priv, &suspend);
wiphy_warn(priv->hw->wiphy, "Requeue for link_id %d (try %d). STAs asleep: 0x%.8X\n",
link_id,
cw1200_queue_get_generation(arg->packet_id) + 1,
priv->sta_asleep_mask);
cw1200_queue_requeue(queue, arg->packet_id);
spin_lock_bh(&priv->ps_state_lock);
if (!link_id) {
priv->buffered_multicasts = true;
if (priv->sta_asleep_mask) {
queue_work(priv->workqueue,
&priv->multicast_start_work);
}
}
spin_unlock_bh(&priv->ps_state_lock);
} else if (!cw1200_queue_get_skb(queue, arg->packet_id,
&skb, &txpriv)) {
struct ieee80211_tx_info *tx = IEEE80211_SKB_CB(skb);
int tx_count = arg->ack_failures;
u8 ht_flags = 0;
int i;
if (cw1200_ht_greenfield(&priv->ht_info))
ht_flags |= IEEE80211_TX_RC_GREEN_FIELD;
spin_lock(&priv->bss_loss_lock);
if (priv->bss_loss_state &&
arg->packet_id == priv->bss_loss_confirm_id) {
if (arg->status) {
/* Recovery failed */
__cw1200_cqm_bssloss_sm(priv, 0, 0, 1);
} else {
/* Recovery succeeded */
__cw1200_cqm_bssloss_sm(priv, 0, 1, 0);
}
}
spin_unlock(&priv->bss_loss_lock);
if (!arg->status) {
tx->flags |= IEEE80211_TX_STAT_ACK;
++tx_count;
cw1200_debug_txed(priv);
if (arg->flags & WSM_TX_STATUS_AGGREGATION) {
/* Do not report aggregation to mac80211:
* it confuses minstrel a lot. */
/* tx->flags |= IEEE80211_TX_STAT_AMPDU; */
cw1200_debug_txed_agg(priv);
}
} else {
if (tx_count)
++tx_count;
}
for (i = 0; i < IEEE80211_TX_MAX_RATES; ++i) {
if (tx->status.rates[i].count >= tx_count) {
tx->status.rates[i].count = tx_count;
break;
}
tx_count -= tx->status.rates[i].count;
if (tx->status.rates[i].flags & IEEE80211_TX_RC_MCS)
tx->status.rates[i].flags |= ht_flags;
}
for (++i; i < IEEE80211_TX_MAX_RATES; ++i) {
tx->status.rates[i].count = 0;
tx->status.rates[i].idx = -1;
}
/* Pull off any crypto trailers that we added on */
if (tx->control.hw_key) {
skb_trim(skb, skb->len - tx->control.hw_key->icv_len);
if (tx->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP)
skb_trim(skb, skb->len - 8); /* MIC space */
}
cw1200_queue_remove(queue, arg->packet_id);
}
/* XXX TODO: Only wake if there are pending transmits.. */
cw1200_bh_wakeup(priv);
}
static void cw1200_notify_buffered_tx(struct cw1200_common *priv,
struct sk_buff *skb, int link_id, int tid)
{
struct ieee80211_sta *sta;
struct ieee80211_hdr *hdr;
u8 *buffered;
u8 still_buffered = 0;
if (link_id && tid < CW1200_MAX_TID) {
buffered = priv->link_id_db
[link_id - 1].buffered;
spin_lock_bh(&priv->ps_state_lock);
if (!WARN_ON(!buffered[tid]))
still_buffered = --buffered[tid];
spin_unlock_bh(&priv->ps_state_lock);
if (!still_buffered && tid < CW1200_MAX_TID) {
hdr = (struct ieee80211_hdr *)skb->data;
rcu_read_lock();
sta = ieee80211_find_sta(priv->vif, hdr->addr1);
if (sta)
ieee80211_sta_set_buffered(sta, tid, false);
rcu_read_unlock();
}
}
}
void cw1200_skb_dtor(struct cw1200_common *priv,
struct sk_buff *skb,
const struct cw1200_txpriv *txpriv)
{
skb_pull(skb, txpriv->offset);
if (txpriv->rate_id != CW1200_INVALID_RATE_ID) {
cw1200_notify_buffered_tx(priv, skb,
txpriv->raw_link_id, txpriv->tid);
tx_policy_put(priv, txpriv->rate_id);
}
if (!cw1200_is_itp(priv))
ieee80211_tx_status(priv->hw, skb);
}
void cw1200_rx_cb(struct cw1200_common *priv,
struct wsm_rx *arg,
int link_id,
struct sk_buff **skb_p)
{
struct sk_buff *skb = *skb_p;
struct ieee80211_rx_status *hdr = IEEE80211_SKB_RXCB(skb);
struct ieee80211_hdr *frame = (struct ieee80211_hdr *)skb->data;
struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
struct cw1200_link_entry *entry = NULL;
unsigned long grace_period;
bool early_data = false;
bool p2p = priv->vif && priv->vif->p2p;
size_t hdrlen;
hdr->flag = 0;
if (priv->mode == NL80211_IFTYPE_UNSPECIFIED) {
/* STA is stopped. */
goto drop;
}
if (link_id && link_id <= CW1200_MAX_STA_IN_AP_MODE) {
entry = &priv->link_id_db[link_id - 1];
if (entry->status == CW1200_LINK_SOFT &&
ieee80211_is_data(frame->frame_control))
early_data = true;
entry->timestamp = jiffies;
} else if (p2p &&
ieee80211_is_action(frame->frame_control) &&
(mgmt->u.action.category == WLAN_CATEGORY_PUBLIC)) {
pr_debug("[RX] Going to MAP&RESET link ID\n");
WARN_ON(work_pending(&priv->linkid_reset_work));
memcpy(&priv->action_frame_sa[0],
ieee80211_get_SA(frame), ETH_ALEN);
priv->action_linkid = 0;
schedule_work(&priv->linkid_reset_work);
}
if (link_id && p2p &&
ieee80211_is_action(frame->frame_control) &&
(mgmt->u.action.category == WLAN_CATEGORY_PUBLIC)) {
/* Link ID already exists for the ACTION frame.
* Reset and Remap */
WARN_ON(work_pending(&priv->linkid_reset_work));
memcpy(&priv->action_frame_sa[0],
ieee80211_get_SA(frame), ETH_ALEN);
priv->action_linkid = link_id;
schedule_work(&priv->linkid_reset_work);
}
if (arg->status) {
if (arg->status == WSM_STATUS_MICFAILURE) {
pr_debug("[RX] MIC failure.\n");
hdr->flag |= RX_FLAG_MMIC_ERROR;
} else if (arg->status == WSM_STATUS_NO_KEY_FOUND) {
pr_debug("[RX] No key found.\n");
goto drop;
} else {
pr_debug("[RX] Receive failure: %d.\n",
arg->status);
goto drop;
}
}
if (skb->len < sizeof(struct ieee80211_pspoll)) {
wiphy_warn(priv->hw->wiphy, "Mailformed SDU rx'ed. Size is lesser than IEEE header.\n");
goto drop;
}
if (ieee80211_is_pspoll(frame->frame_control))
if (cw1200_handle_pspoll(priv, skb))
goto drop;
hdr->mactime = 0; /* Not supported by WSM */
hdr->band = ((arg->channel_number & 0xff00) ||
(arg->channel_number > 14)) ?
IEEE80211_BAND_5GHZ : IEEE80211_BAND_2GHZ;
hdr->freq = ieee80211_channel_to_frequency(
arg->channel_number,
hdr->band);
if (arg->rx_rate >= 14) {
hdr->flag |= RX_FLAG_HT;
hdr->rate_idx = arg->rx_rate - 14;
} else if (arg->rx_rate >= 4) {
hdr->rate_idx = arg->rx_rate - 2;
} else {
hdr->rate_idx = arg->rx_rate;
}
hdr->signal = (s8)arg->rcpi_rssi;
hdr->antenna = 0;
hdrlen = ieee80211_hdrlen(frame->frame_control);
if (WSM_RX_STATUS_ENCRYPTION(arg->flags)) {
size_t iv_len = 0, icv_len = 0;
hdr->flag |= RX_FLAG_DECRYPTED | RX_FLAG_IV_STRIPPED;
/* Oops... There is no fast way to ask mac80211 about
* IV/ICV lengths. Even defineas are not exposed.*/
switch (WSM_RX_STATUS_ENCRYPTION(arg->flags)) {
case WSM_RX_STATUS_WEP:
iv_len = 4 /* WEP_IV_LEN */;
icv_len = 4 /* WEP_ICV_LEN */;
break;
case WSM_RX_STATUS_TKIP:
iv_len = 8 /* TKIP_IV_LEN */;
icv_len = 4 /* TKIP_ICV_LEN */
+ 8 /*MICHAEL_MIC_LEN*/;
hdr->flag |= RX_FLAG_MMIC_STRIPPED;
break;
case WSM_RX_STATUS_AES:
iv_len = 8 /* CCMP_HDR_LEN */;
icv_len = 8 /* CCMP_MIC_LEN */;
break;
case WSM_RX_STATUS_WAPI:
iv_len = 18 /* WAPI_HDR_LEN */;
icv_len = 16 /* WAPI_MIC_LEN */;
break;
default:
pr_warn("Unknown encryption type %d\n",
WSM_RX_STATUS_ENCRYPTION(arg->flags));
goto drop;
}
/* Firmware strips ICV in case of MIC failure. */
if (arg->status == WSM_STATUS_MICFAILURE)
icv_len = 0;
if (skb->len < hdrlen + iv_len + icv_len) {
wiphy_warn(priv->hw->wiphy, "Malformed SDU rx'ed. Size is lesser than crypto headers.\n");
goto drop;
}
/* Remove IV, ICV and MIC */
skb_trim(skb, skb->len - icv_len);
memmove(skb->data + iv_len, skb->data, hdrlen);
skb_pull(skb, iv_len);
}
/* Remove TSF from the end of frame */
if (arg->flags & WSM_RX_STATUS_TSF_INCLUDED) {
memcpy(&hdr->mactime, skb->data + skb->len - 8, 8);
hdr->mactime = le64_to_cpu(hdr->mactime);
if (skb->len >= 8)
skb_trim(skb, skb->len - 8);
}
cw1200_debug_rxed(priv);
if (arg->flags & WSM_RX_STATUS_AGGREGATE)
cw1200_debug_rxed_agg(priv);
if (ieee80211_is_action(frame->frame_control) &&
(arg->flags & WSM_RX_STATUS_ADDRESS1)) {
if (cw1200_handle_action_rx(priv, skb))
return;
} else if (ieee80211_is_beacon(frame->frame_control) &&
!arg->status &&
!memcmp(ieee80211_get_SA(frame), priv->vif->bss_conf.bssid,
ETH_ALEN)) {
const u8 *tim_ie;
u8 *ies = ((struct ieee80211_mgmt *)
(skb->data))->u.beacon.variable;
size_t ies_len = skb->len - (ies - (u8 *)(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 (priv->join_dtim_period != tim->dtim_period) {
priv->join_dtim_period = tim->dtim_period;
queue_work(priv->workqueue,
&priv->set_beacon_wakeup_period_work);
}
}
/* Disable beacon filter once we're associated... */
if (priv->disable_beacon_filter &&
(priv->vif->bss_conf.assoc ||
priv->vif->bss_conf.ibss_joined)) {
priv->disable_beacon_filter = false;
queue_work(priv->workqueue,
&priv->update_filtering_work);
}
}
/* Stay awake after frame is received to give
* userspace chance to react and acquire appropriate
* wakelock. */
if (ieee80211_is_auth(frame->frame_control))
grace_period = 5 * HZ;
else if (ieee80211_is_deauth(frame->frame_control))
grace_period = 5 * HZ;
else
grace_period = 1 * HZ;
cw1200_pm_stay_awake(&priv->pm_state, grace_period);
if (cw1200_itp_rxed(priv, skb)) {
consume_skb(skb);
} else if (early_data) {
spin_lock_bh(&priv->ps_state_lock);
/* Double-check status with lock held */
if (entry->status == CW1200_LINK_SOFT)
skb_queue_tail(&entry->rx_queue, skb);
else
ieee80211_rx_irqsafe(priv->hw, skb);
spin_unlock_bh(&priv->ps_state_lock);
} else {
ieee80211_rx_irqsafe(priv->hw, skb);
}
*skb_p = NULL;
return;
drop:
/* TODO: update failure counters */
return;
}
/* ******************************************************************** */
/* Security */
int cw1200_alloc_key(struct cw1200_common *priv)
{
int idx;
idx = ffs(~priv->key_map) - 1;
if (idx < 0 || idx > WSM_KEY_MAX_INDEX)
return -1;
priv->key_map |= BIT(idx);
priv->keys[idx].index = idx;
return idx;
}
void cw1200_free_key(struct cw1200_common *priv, int idx)
{
BUG_ON(!(priv->key_map & BIT(idx)));
memset(&priv->keys[idx], 0, sizeof(priv->keys[idx]));
priv->key_map &= ~BIT(idx);
}
void cw1200_free_keys(struct cw1200_common *priv)
{
memset(&priv->keys, 0, sizeof(priv->keys));
priv->key_map = 0;
}
int cw1200_upload_keys(struct cw1200_common *priv)
{
int idx, ret = 0;
for (idx = 0; idx <= WSM_KEY_MAX_INDEX; ++idx)
if (priv->key_map & BIT(idx)) {
ret = wsm_add_key(priv, &priv->keys[idx]);
if (ret < 0)
break;
}
return ret;
}
/* Workaround for WFD test case 6.1.10 */
void cw1200_link_id_reset(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, linkid_reset_work);
int temp_linkid;
if (!priv->action_linkid) {
/* In GO mode we can receive ACTION frames without a linkID */
temp_linkid = cw1200_alloc_link_id(priv,
&priv->action_frame_sa[0]);
WARN_ON(!temp_linkid);
if (temp_linkid) {
/* Make sure we execute the WQ */
flush_workqueue(priv->workqueue);
/* Release the link ID */
spin_lock_bh(&priv->ps_state_lock);
priv->link_id_db[temp_linkid - 1].prev_status =
priv->link_id_db[temp_linkid - 1].status;
priv->link_id_db[temp_linkid - 1].status =
CW1200_LINK_RESET;
spin_unlock_bh(&priv->ps_state_lock);
wsm_lock_tx_async(priv);
if (queue_work(priv->workqueue,
&priv->link_id_work) <= 0)
wsm_unlock_tx(priv);
}
} else {
spin_lock_bh(&priv->ps_state_lock);
priv->link_id_db[priv->action_linkid - 1].prev_status =
priv->link_id_db[priv->action_linkid - 1].status;
priv->link_id_db[priv->action_linkid - 1].status =
CW1200_LINK_RESET_REMAP;
spin_unlock_bh(&priv->ps_state_lock);
wsm_lock_tx_async(priv);
if (queue_work(priv->workqueue, &priv->link_id_work) <= 0)
wsm_unlock_tx(priv);
flush_workqueue(priv->workqueue);
}
}
int cw1200_find_link_id(struct cw1200_common *priv, const u8 *mac)
{
int i, ret = 0;
spin_lock_bh(&priv->ps_state_lock);
for (i = 0; i < CW1200_MAX_STA_IN_AP_MODE; ++i) {
if (!memcmp(mac, priv->link_id_db[i].mac, ETH_ALEN) &&
priv->link_id_db[i].status) {
priv->link_id_db[i].timestamp = jiffies;
ret = i + 1;
break;
}
}
spin_unlock_bh(&priv->ps_state_lock);
return ret;
}
int cw1200_alloc_link_id(struct cw1200_common *priv, const u8 *mac)
{
int i, ret = 0;
unsigned long max_inactivity = 0;
unsigned long now = jiffies;
spin_lock_bh(&priv->ps_state_lock);
for (i = 0; i < CW1200_MAX_STA_IN_AP_MODE; ++i) {
if (!priv->link_id_db[i].status) {
ret = i + 1;
break;
} else if (priv->link_id_db[i].status != CW1200_LINK_HARD &&
!priv->tx_queue_stats.link_map_cache[i + 1]) {
unsigned long inactivity =
now - priv->link_id_db[i].timestamp;
if (inactivity < max_inactivity)
continue;
max_inactivity = inactivity;
ret = i + 1;
}
}
if (ret) {
struct cw1200_link_entry *entry = &priv->link_id_db[ret - 1];
pr_debug("[AP] STA added, link_id: %d\n", ret);
entry->status = CW1200_LINK_RESERVE;
memcpy(&entry->mac, mac, ETH_ALEN);
memset(&entry->buffered, 0, CW1200_MAX_TID);
skb_queue_head_init(&entry->rx_queue);
wsm_lock_tx_async(priv);
if (queue_work(priv->workqueue, &priv->link_id_work) <= 0)
wsm_unlock_tx(priv);
} else {
wiphy_info(priv->hw->wiphy,
"[AP] Early: no more link IDs available.\n");
}
spin_unlock_bh(&priv->ps_state_lock);
return ret;
}
void cw1200_link_id_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, link_id_work);
wsm_flush_tx(priv);
cw1200_link_id_gc_work(&priv->link_id_gc_work.work);
wsm_unlock_tx(priv);
}
void cw1200_link_id_gc_work(struct work_struct *work)
{
struct cw1200_common *priv =
container_of(work, struct cw1200_common, link_id_gc_work.work);
struct wsm_reset reset = {
.reset_statistics = false,
};
struct wsm_map_link map_link = {
.link_id = 0,
};
unsigned long now = jiffies;
unsigned long next_gc = -1;
long ttl;
bool need_reset;
u32 mask;
int i;
if (priv->join_status != CW1200_JOIN_STATUS_AP)
return;
wsm_lock_tx(priv);
spin_lock_bh(&priv->ps_state_lock);
for (i = 0; i < CW1200_MAX_STA_IN_AP_MODE; ++i) {
need_reset = false;
mask = BIT(i + 1);
if (priv->link_id_db[i].status == CW1200_LINK_RESERVE ||
(priv->link_id_db[i].status == CW1200_LINK_HARD &&
!(priv->link_id_map & mask))) {
if (priv->link_id_map & mask) {
priv->sta_asleep_mask &= ~mask;
priv->pspoll_mask &= ~mask;
need_reset = true;
}
priv->link_id_map |= mask;
if (priv->link_id_db[i].status != CW1200_LINK_HARD)
priv->link_id_db[i].status = CW1200_LINK_SOFT;
memcpy(map_link.mac_addr, priv->link_id_db[i].mac,
ETH_ALEN);
spin_unlock_bh(&priv->ps_state_lock);
if (need_reset) {
reset.link_id = i + 1;
wsm_reset(priv, &reset);
}
map_link.link_id = i + 1;
wsm_map_link(priv, &map_link);
next_gc = min(next_gc, CW1200_LINK_ID_GC_TIMEOUT);
spin_lock_bh(&priv->ps_state_lock);
} else if (priv->link_id_db[i].status == CW1200_LINK_SOFT) {
ttl = priv->link_id_db[i].timestamp - now +
CW1200_LINK_ID_GC_TIMEOUT;
if (ttl <= 0) {
need_reset = true;
priv->link_id_db[i].status = CW1200_LINK_OFF;
priv->link_id_map &= ~mask;
priv->sta_asleep_mask &= ~mask;
priv->pspoll_mask &= ~mask;
memset(map_link.mac_addr, 0, ETH_ALEN);
spin_unlock_bh(&priv->ps_state_lock);
reset.link_id = i + 1;
wsm_reset(priv, &reset);
spin_lock_bh(&priv->ps_state_lock);
} else {
next_gc = min_t(unsigned long, next_gc, ttl);
}
} else if (priv->link_id_db[i].status == CW1200_LINK_RESET ||
priv->link_id_db[i].status ==
CW1200_LINK_RESET_REMAP) {
int status = priv->link_id_db[i].status;
priv->link_id_db[i].status =
priv->link_id_db[i].prev_status;
priv->link_id_db[i].timestamp = now;
reset.link_id = i + 1;
spin_unlock_bh(&priv->ps_state_lock);
wsm_reset(priv, &reset);
if (status == CW1200_LINK_RESET_REMAP) {
memcpy(map_link.mac_addr,
priv->link_id_db[i].mac,
ETH_ALEN);
map_link.link_id = i + 1;
wsm_map_link(priv, &map_link);
next_gc = min(next_gc,
CW1200_LINK_ID_GC_TIMEOUT);
}
spin_lock_bh(&priv->ps_state_lock);
}
if (need_reset) {
skb_queue_purge(&priv->link_id_db[i].rx_queue);
pr_debug("[AP] STA removed, link_id: %d\n",
reset.link_id);
}
}
spin_unlock_bh(&priv->ps_state_lock);
if (next_gc != -1)
queue_delayed_work(priv->workqueue,
&priv->link_id_gc_work, next_gc);
wsm_unlock_tx(priv);
}
/*
* Datapath interface for ST-Ericsson CW1200 mac80211 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef CW1200_TXRX_H
#define CW1200_TXRX_H
#include <linux/list.h>
/* extern */ struct ieee80211_hw;
/* extern */ struct sk_buff;
/* extern */ struct wsm_tx;
/* extern */ struct wsm_rx;
/* extern */ struct wsm_tx_confirm;
/* extern */ struct cw1200_txpriv;
struct tx_policy {
union {
__le32 tbl[3];
u8 raw[12];
};
u8 defined;
u8 usage_count;
u8 retry_count;
u8 uploaded;
};
struct tx_policy_cache_entry {
struct tx_policy policy;
struct list_head link;
};
#define TX_POLICY_CACHE_SIZE (8)
struct tx_policy_cache {
struct tx_policy_cache_entry cache[TX_POLICY_CACHE_SIZE];
struct list_head used;
struct list_head free;
spinlock_t lock; /* Protect policy cache */
};
/* ******************************************************************** */
/* TX policy cache */
/* Intention of TX policy cache is an overcomplicated WSM API.
* Device does not accept per-PDU tx retry sequence.
* It uses "tx retry policy id" instead, so driver code has to sync
* linux tx retry sequences with a retry policy table in the device.
*/
void tx_policy_init(struct cw1200_common *priv);
void tx_policy_upload_work(struct work_struct *work);
void tx_policy_clean(struct cw1200_common *priv);
/* ******************************************************************** */
/* TX implementation */
u32 cw1200_rate_mask_to_wsm(struct cw1200_common *priv,
u32 rates);
void cw1200_tx(struct ieee80211_hw *dev,
struct ieee80211_tx_control *control,
struct sk_buff *skb);
void cw1200_skb_dtor(struct cw1200_common *priv,
struct sk_buff *skb,
const struct cw1200_txpriv *txpriv);
/* ******************************************************************** */
/* WSM callbacks */
void cw1200_tx_confirm_cb(struct cw1200_common *priv,
int link_id,
struct wsm_tx_confirm *arg);
void cw1200_rx_cb(struct cw1200_common *priv,
struct wsm_rx *arg,
int link_id,
struct sk_buff **skb_p);
/* ******************************************************************** */
/* Timeout */
void cw1200_tx_timeout(struct work_struct *work);
/* ******************************************************************** */
/* Security */
int cw1200_alloc_key(struct cw1200_common *priv);
void cw1200_free_key(struct cw1200_common *priv, int idx);
void cw1200_free_keys(struct cw1200_common *priv);
int cw1200_upload_keys(struct cw1200_common *priv);
/* ******************************************************************** */
/* Workaround for WFD test case 6.1.10 */
void cw1200_link_id_reset(struct work_struct *work);
#define CW1200_LINK_ID_GC_TIMEOUT ((unsigned long)(10 * HZ))
int cw1200_find_link_id(struct cw1200_common *priv, const u8 *mac);
int cw1200_alloc_link_id(struct cw1200_common *priv, const u8 *mac);
void cw1200_link_id_work(struct work_struct *work);
void cw1200_link_id_gc_work(struct work_struct *work);
#endif /* CW1200_TXRX_H */
/*
* WSM host interface (HI) implementation for
* ST-Ericsson CW1200 mac80211 drivers.
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/skbuff.h>
#include <linux/wait.h>
#include <linux/skbuff.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/random.h>
#include "cw1200.h"
#include "wsm.h"
#include "bh.h"
#include "sta.h"
#include "debug.h"
#include "itp.h"
#define WSM_CMD_TIMEOUT (2 * HZ) /* With respect to interrupt loss */
#define WSM_CMD_START_TIMEOUT (7 * HZ)
#define WSM_CMD_RESET_TIMEOUT (3 * HZ) /* 2 sec. timeout was observed. */
#define WSM_CMD_MAX_TIMEOUT (3 * HZ)
#define WSM_SKIP(buf, size) \
do { \
if ((buf)->data + size > (buf)->end) \
goto underflow; \
(buf)->data += size; \
} while (0)
#define WSM_GET(buf, ptr, size) \
do { \
if ((buf)->data + size > (buf)->end) \
goto underflow; \
memcpy(ptr, (buf)->data, size); \
(buf)->data += size; \
} while (0)
#define __WSM_GET(buf, type, cvt) \
({ \
type val; \
if ((buf)->data + sizeof(type) > (buf)->end) \
goto underflow; \
val = cvt(*(type *)(buf)->data); \
(buf)->data += sizeof(type); \
val; \
})
#define WSM_GET8(buf) __WSM_GET(buf, u8, (u8))
#define WSM_GET16(buf) __WSM_GET(buf, u16, __le16_to_cpu)
#define WSM_GET32(buf) __WSM_GET(buf, u32, __le32_to_cpu)
#define WSM_PUT(buf, ptr, size) \
do { \
if ((buf)->data + size > (buf)->end) \
if (wsm_buf_reserve((buf), size)) \
goto nomem; \
memcpy((buf)->data, ptr, size); \
(buf)->data += size; \
} while (0)
#define __WSM_PUT(buf, val, type, cvt) \
do { \
if ((buf)->data + sizeof(type) > (buf)->end) \
if (wsm_buf_reserve((buf), sizeof(type))) \
goto nomem; \
*(type *)(buf)->data = cvt(val); \
(buf)->data += sizeof(type); \
} while (0)
#define WSM_PUT8(buf, val) __WSM_PUT(buf, val, u8, (u8))
#define WSM_PUT16(buf, val) __WSM_PUT(buf, val, u16, __cpu_to_le16)
#define WSM_PUT32(buf, val) __WSM_PUT(buf, val, u32, __cpu_to_le32)
static void wsm_buf_reset(struct wsm_buf *buf);
static int wsm_buf_reserve(struct wsm_buf *buf, size_t extra_size);
static int wsm_cmd_send(struct cw1200_common *priv,
struct wsm_buf *buf,
void *arg, u16 cmd, long tmo);
#define wsm_cmd_lock(__priv) mutex_lock(&((__priv)->wsm_cmd_mux))
#define wsm_cmd_unlock(__priv) mutex_unlock(&((__priv)->wsm_cmd_mux))
/* ******************************************************************** */
/* WSM API implementation */
static int wsm_generic_confirm(struct cw1200_common *priv,
void *arg,
struct wsm_buf *buf)
{
u32 status = WSM_GET32(buf);
if (status != WSM_STATUS_SUCCESS)
return -EINVAL;
return 0;
underflow:
WARN_ON(1);
return -EINVAL;
}
int wsm_configuration(struct cw1200_common *priv, struct wsm_configuration *arg)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
wsm_cmd_lock(priv);
WSM_PUT32(buf, arg->dot11MaxTransmitMsduLifeTime);
WSM_PUT32(buf, arg->dot11MaxReceiveLifeTime);
WSM_PUT32(buf, arg->dot11RtsThreshold);
/* DPD block. */
WSM_PUT16(buf, arg->dpdData_size + 12);
WSM_PUT16(buf, 1); /* DPD version */
WSM_PUT(buf, arg->dot11StationId, ETH_ALEN);
WSM_PUT16(buf, 5); /* DPD flags */
WSM_PUT(buf, arg->dpdData, arg->dpdData_size);
ret = wsm_cmd_send(priv, buf, arg,
WSM_CONFIGURATION_REQ_ID, WSM_CMD_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
static int wsm_configuration_confirm(struct cw1200_common *priv,
struct wsm_configuration *arg,
struct wsm_buf *buf)
{
int i;
int status;
status = WSM_GET32(buf);
if (WARN_ON(status != WSM_STATUS_SUCCESS))
return -EINVAL;
WSM_GET(buf, arg->dot11StationId, ETH_ALEN);
arg->dot11FrequencyBandsSupported = WSM_GET8(buf);
WSM_SKIP(buf, 1);
arg->supportedRateMask = WSM_GET32(buf);
for (i = 0; i < 2; ++i) {
arg->txPowerRange[i].min_power_level = WSM_GET32(buf);
arg->txPowerRange[i].max_power_level = WSM_GET32(buf);
arg->txPowerRange[i].stepping = WSM_GET32(buf);
}
return 0;
underflow:
WARN_ON(1);
return -EINVAL;
}
/* ******************************************************************** */
int wsm_reset(struct cw1200_common *priv, const struct wsm_reset *arg)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
u16 cmd = WSM_RESET_REQ_ID | WSM_TX_LINK_ID(arg->link_id);
wsm_cmd_lock(priv);
WSM_PUT32(buf, arg->reset_statistics ? 0 : 1);
ret = wsm_cmd_send(priv, buf, NULL, cmd, WSM_CMD_RESET_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
/* ******************************************************************** */
struct wsm_mib {
u16 mib_id;
void *buf;
size_t buf_size;
};
int wsm_read_mib(struct cw1200_common *priv, u16 mib_id, void *_buf,
size_t buf_size)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
struct wsm_mib mib_buf = {
.mib_id = mib_id,
.buf = _buf,
.buf_size = buf_size,
};
wsm_cmd_lock(priv);
WSM_PUT16(buf, mib_id);
WSM_PUT16(buf, 0);
ret = wsm_cmd_send(priv, buf, &mib_buf,
WSM_READ_MIB_REQ_ID, WSM_CMD_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
static int wsm_read_mib_confirm(struct cw1200_common *priv,
struct wsm_mib *arg,
struct wsm_buf *buf)
{
u16 size;
if (WARN_ON(WSM_GET32(buf) != WSM_STATUS_SUCCESS))
return -EINVAL;
if (WARN_ON(WSM_GET16(buf) != arg->mib_id))
return -EINVAL;
size = WSM_GET16(buf);
if (size > arg->buf_size)
size = arg->buf_size;
WSM_GET(buf, arg->buf, size);
arg->buf_size = size;
return 0;
underflow:
WARN_ON(1);
return -EINVAL;
}
/* ******************************************************************** */
int wsm_write_mib(struct cw1200_common *priv, u16 mib_id, void *_buf,
size_t buf_size)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
struct wsm_mib mib_buf = {
.mib_id = mib_id,
.buf = _buf,
.buf_size = buf_size,
};
wsm_cmd_lock(priv);
WSM_PUT16(buf, mib_id);
WSM_PUT16(buf, buf_size);
WSM_PUT(buf, _buf, buf_size);
ret = wsm_cmd_send(priv, buf, &mib_buf,
WSM_WRITE_MIB_REQ_ID, WSM_CMD_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
static int wsm_write_mib_confirm(struct cw1200_common *priv,
struct wsm_mib *arg,
struct wsm_buf *buf)
{
int ret;
ret = wsm_generic_confirm(priv, arg, buf);
if (ret)
return ret;
if (arg->mib_id == WSM_MIB_ID_OPERATIONAL_POWER_MODE) {
/* OperationalMode: update PM status. */
const char *p = arg->buf;
cw1200_enable_powersave(priv, (p[0] & 0x0F) ? true : false);
}
return 0;
}
/* ******************************************************************** */
int wsm_scan(struct cw1200_common *priv, const struct wsm_scan *arg)
{
int i;
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
if (arg->num_channels > 48)
return -EINVAL;
if (arg->num_ssids > 2)
return -EINVAL;
if (arg->band > 1)
return -EINVAL;
wsm_cmd_lock(priv);
WSM_PUT8(buf, arg->band);
WSM_PUT8(buf, arg->type);
WSM_PUT8(buf, arg->flags);
WSM_PUT8(buf, arg->max_tx_rate);
WSM_PUT32(buf, arg->auto_scan_interval);
WSM_PUT8(buf, arg->num_probes);
WSM_PUT8(buf, arg->num_channels);
WSM_PUT8(buf, arg->num_ssids);
WSM_PUT8(buf, arg->probe_delay);
for (i = 0; i < arg->num_channels; ++i) {
WSM_PUT16(buf, arg->ch[i].number);
WSM_PUT16(buf, 0);
WSM_PUT32(buf, arg->ch[i].min_chan_time);
WSM_PUT32(buf, arg->ch[i].max_chan_time);
WSM_PUT32(buf, 0);
}
for (i = 0; i < arg->num_ssids; ++i) {
WSM_PUT32(buf, arg->ssids[i].length);
WSM_PUT(buf, &arg->ssids[i].ssid[0],
sizeof(arg->ssids[i].ssid));
}
ret = wsm_cmd_send(priv, buf, NULL,
WSM_START_SCAN_REQ_ID, WSM_CMD_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
/* ******************************************************************** */
int wsm_stop_scan(struct cw1200_common *priv)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
wsm_cmd_lock(priv);
ret = wsm_cmd_send(priv, buf, NULL,
WSM_STOP_SCAN_REQ_ID, WSM_CMD_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
}
static int wsm_tx_confirm(struct cw1200_common *priv,
struct wsm_buf *buf,
int link_id)
{
struct wsm_tx_confirm tx_confirm;
tx_confirm.packet_id = WSM_GET32(buf);
tx_confirm.status = WSM_GET32(buf);
tx_confirm.tx_rate = WSM_GET8(buf);
tx_confirm.ack_failures = WSM_GET8(buf);
tx_confirm.flags = WSM_GET16(buf);
tx_confirm.media_delay = WSM_GET32(buf);
tx_confirm.tx_queue_delay = WSM_GET32(buf);
cw1200_tx_confirm_cb(priv, link_id, &tx_confirm);
return 0;
underflow:
WARN_ON(1);
return -EINVAL;
}
static int wsm_multi_tx_confirm(struct cw1200_common *priv,
struct wsm_buf *buf, int link_id)
{
int ret;
int count;
int i;
count = WSM_GET32(buf);
if (WARN_ON(count <= 0))
return -EINVAL;
if (count > 1) {
/* We already released one buffer, now for the rest */
ret = wsm_release_tx_buffer(priv, count - 1);
if (ret < 0)
return ret;
else if (ret > 0)
cw1200_bh_wakeup(priv);
}
cw1200_debug_txed_multi(priv, count);
for (i = 0; i < count; ++i) {
ret = wsm_tx_confirm(priv, buf, link_id);
if (ret)
return ret;
}
return ret;
underflow:
WARN_ON(1);
return -EINVAL;
}
/* ******************************************************************** */
static int wsm_join_confirm(struct cw1200_common *priv,
struct wsm_join_cnf *arg,
struct wsm_buf *buf)
{
arg->status = WSM_GET32(buf);
if (WARN_ON(arg->status) != WSM_STATUS_SUCCESS)
return -EINVAL;
arg->min_power_level = WSM_GET32(buf);
arg->max_power_level = WSM_GET32(buf);
return 0;
underflow:
WARN_ON(1);
return -EINVAL;
}
int wsm_join(struct cw1200_common *priv, struct wsm_join *arg)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
struct wsm_join_cnf resp;
wsm_cmd_lock(priv);
WSM_PUT8(buf, arg->mode);
WSM_PUT8(buf, arg->band);
WSM_PUT16(buf, arg->channel_number);
WSM_PUT(buf, &arg->bssid[0], sizeof(arg->bssid));
WSM_PUT16(buf, arg->atim_window);
WSM_PUT8(buf, arg->preamble_type);
WSM_PUT8(buf, arg->probe_for_join);
WSM_PUT8(buf, arg->dtim_period);
WSM_PUT8(buf, arg->flags);
WSM_PUT32(buf, arg->ssid_len);
WSM_PUT(buf, &arg->ssid[0], sizeof(arg->ssid));
WSM_PUT32(buf, arg->beacon_interval);
WSM_PUT32(buf, arg->basic_rate_set);
priv->tx_burst_idx = -1;
ret = wsm_cmd_send(priv, buf, &resp,
WSM_JOIN_REQ_ID, WSM_CMD_TIMEOUT);
/* TODO: Update state based on resp.min|max_power_level */
priv->join_complete_status = resp.status;
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
/* ******************************************************************** */
int wsm_set_bss_params(struct cw1200_common *priv,
const struct wsm_set_bss_params *arg)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
wsm_cmd_lock(priv);
WSM_PUT8(buf, (arg->reset_beacon_loss ? 0x1 : 0));
WSM_PUT8(buf, arg->beacon_lost_count);
WSM_PUT16(buf, arg->aid);
WSM_PUT32(buf, arg->operational_rate_set);
ret = wsm_cmd_send(priv, buf, NULL,
WSM_SET_BSS_PARAMS_REQ_ID, WSM_CMD_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
/* ******************************************************************** */
int wsm_add_key(struct cw1200_common *priv, const struct wsm_add_key *arg)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
wsm_cmd_lock(priv);
WSM_PUT(buf, arg, sizeof(*arg));
ret = wsm_cmd_send(priv, buf, NULL,
WSM_ADD_KEY_REQ_ID, WSM_CMD_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
/* ******************************************************************** */
int wsm_remove_key(struct cw1200_common *priv, const struct wsm_remove_key *arg)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
wsm_cmd_lock(priv);
WSM_PUT8(buf, arg->index);
WSM_PUT8(buf, 0);
WSM_PUT16(buf, 0);
ret = wsm_cmd_send(priv, buf, NULL,
WSM_REMOVE_KEY_REQ_ID, WSM_CMD_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
/* ******************************************************************** */
int wsm_set_tx_queue_params(struct cw1200_common *priv,
const struct wsm_set_tx_queue_params *arg, u8 id)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
u8 queue_id_to_wmm_aci[] = {3, 2, 0, 1};
wsm_cmd_lock(priv);
WSM_PUT8(buf, queue_id_to_wmm_aci[id]);
WSM_PUT8(buf, 0);
WSM_PUT8(buf, arg->ackPolicy);
WSM_PUT8(buf, 0);
WSM_PUT32(buf, arg->maxTransmitLifetime);
WSM_PUT16(buf, arg->allowedMediumTime);
WSM_PUT16(buf, 0);
ret = wsm_cmd_send(priv, buf, NULL, 0x0012, WSM_CMD_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
/* ******************************************************************** */
int wsm_set_edca_params(struct cw1200_common *priv,
const struct wsm_edca_params *arg)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
wsm_cmd_lock(priv);
/* Implemented according to specification. */
WSM_PUT16(buf, arg->params[3].cwmin);
WSM_PUT16(buf, arg->params[2].cwmin);
WSM_PUT16(buf, arg->params[1].cwmin);
WSM_PUT16(buf, arg->params[0].cwmin);
WSM_PUT16(buf, arg->params[3].cwmax);
WSM_PUT16(buf, arg->params[2].cwmax);
WSM_PUT16(buf, arg->params[1].cwmax);
WSM_PUT16(buf, arg->params[0].cwmax);
WSM_PUT8(buf, arg->params[3].aifns);
WSM_PUT8(buf, arg->params[2].aifns);
WSM_PUT8(buf, arg->params[1].aifns);
WSM_PUT8(buf, arg->params[0].aifns);
WSM_PUT16(buf, arg->params[3].txop_limit);
WSM_PUT16(buf, arg->params[2].txop_limit);
WSM_PUT16(buf, arg->params[1].txop_limit);
WSM_PUT16(buf, arg->params[0].txop_limit);
WSM_PUT32(buf, arg->params[3].max_rx_lifetime);
WSM_PUT32(buf, arg->params[2].max_rx_lifetime);
WSM_PUT32(buf, arg->params[1].max_rx_lifetime);
WSM_PUT32(buf, arg->params[0].max_rx_lifetime);
ret = wsm_cmd_send(priv, buf, NULL,
WSM_EDCA_PARAMS_REQ_ID, WSM_CMD_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
/* ******************************************************************** */
int wsm_switch_channel(struct cw1200_common *priv,
const struct wsm_switch_channel *arg)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
wsm_cmd_lock(priv);
WSM_PUT8(buf, arg->mode);
WSM_PUT8(buf, arg->switch_count);
WSM_PUT16(buf, arg->channel_number);
priv->channel_switch_in_progress = 1;
ret = wsm_cmd_send(priv, buf, NULL,
WSM_SWITCH_CHANNEL_REQ_ID, WSM_CMD_TIMEOUT);
if (ret)
priv->channel_switch_in_progress = 0;
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
/* ******************************************************************** */
int wsm_set_pm(struct cw1200_common *priv, const struct wsm_set_pm *arg)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
priv->ps_mode_switch_in_progress = 1;
wsm_cmd_lock(priv);
WSM_PUT8(buf, arg->mode);
WSM_PUT8(buf, arg->fast_psm_idle_period);
WSM_PUT8(buf, arg->ap_psm_change_period);
WSM_PUT8(buf, arg->min_auto_pspoll_period);
ret = wsm_cmd_send(priv, buf, NULL,
WSM_SET_PM_REQ_ID, WSM_CMD_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
/* ******************************************************************** */
int wsm_start(struct cw1200_common *priv, const struct wsm_start *arg)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
wsm_cmd_lock(priv);
WSM_PUT8(buf, arg->mode);
WSM_PUT8(buf, arg->band);
WSM_PUT16(buf, arg->channel_number);
WSM_PUT32(buf, arg->ct_window);
WSM_PUT32(buf, arg->beacon_interval);
WSM_PUT8(buf, arg->dtim_period);
WSM_PUT8(buf, arg->preamble);
WSM_PUT8(buf, arg->probe_delay);
WSM_PUT8(buf, arg->ssid_len);
WSM_PUT(buf, arg->ssid, sizeof(arg->ssid));
WSM_PUT32(buf, arg->basic_rate_set);
priv->tx_burst_idx = -1;
ret = wsm_cmd_send(priv, buf, NULL,
WSM_START_REQ_ID, WSM_CMD_START_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
/* ******************************************************************** */
int wsm_beacon_transmit(struct cw1200_common *priv,
const struct wsm_beacon_transmit *arg)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
wsm_cmd_lock(priv);
WSM_PUT32(buf, arg->enable_beaconing ? 1 : 0);
ret = wsm_cmd_send(priv, buf, NULL,
WSM_BEACON_TRANSMIT_REQ_ID, WSM_CMD_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
/* ******************************************************************** */
int wsm_start_find(struct cw1200_common *priv)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
wsm_cmd_lock(priv);
ret = wsm_cmd_send(priv, buf, NULL, 0x0019, WSM_CMD_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
}
/* ******************************************************************** */
int wsm_stop_find(struct cw1200_common *priv)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
wsm_cmd_lock(priv);
ret = wsm_cmd_send(priv, buf, NULL, 0x001A, WSM_CMD_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
}
/* ******************************************************************** */
int wsm_map_link(struct cw1200_common *priv, const struct wsm_map_link *arg)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
u16 cmd = 0x001C | WSM_TX_LINK_ID(arg->link_id);
wsm_cmd_lock(priv);
WSM_PUT(buf, &arg->mac_addr[0], sizeof(arg->mac_addr));
WSM_PUT16(buf, 0);
ret = wsm_cmd_send(priv, buf, NULL, cmd, WSM_CMD_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
/* ******************************************************************** */
int wsm_update_ie(struct cw1200_common *priv,
const struct wsm_update_ie *arg)
{
int ret;
struct wsm_buf *buf = &priv->wsm_cmd_buf;
wsm_cmd_lock(priv);
WSM_PUT16(buf, arg->what);
WSM_PUT16(buf, arg->count);
WSM_PUT(buf, arg->ies, arg->length);
ret = wsm_cmd_send(priv, buf, NULL, 0x001B, WSM_CMD_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
/* ******************************************************************** */
int wsm_set_probe_responder(struct cw1200_common *priv, bool enable)
{
priv->rx_filter.probeResponder = enable;
return wsm_set_rx_filter(priv, &priv->rx_filter);
}
/* ******************************************************************** */
/* WSM indication events implementation */
const char * const cw1200_fw_types[] = {
"ETF",
"WFM",
"WSM",
"HI test",
"Platform test"
};
static int wsm_startup_indication(struct cw1200_common *priv,
struct wsm_buf *buf)
{
priv->wsm_caps.input_buffers = WSM_GET16(buf);
priv->wsm_caps.input_buffer_size = WSM_GET16(buf);
priv->wsm_caps.hw_id = WSM_GET16(buf);
priv->wsm_caps.hw_subid = WSM_GET16(buf);
priv->wsm_caps.status = WSM_GET16(buf);
priv->wsm_caps.fw_cap = WSM_GET16(buf);
priv->wsm_caps.fw_type = WSM_GET16(buf);
priv->wsm_caps.fw_api = WSM_GET16(buf);
priv->wsm_caps.fw_build = WSM_GET16(buf);
priv->wsm_caps.fw_ver = WSM_GET16(buf);
WSM_GET(buf, priv->wsm_caps.fw_label, sizeof(priv->wsm_caps.fw_label));
priv->wsm_caps.fw_label[sizeof(priv->wsm_caps.fw_label) - 1] = 0; /* Do not trust FW too much... */
if (WARN_ON(priv->wsm_caps.status))
return -EINVAL;
if (WARN_ON(priv->wsm_caps.fw_type > 4))
return -EINVAL;
pr_info("CW1200 WSM init done.\n"
" Input buffers: %d x %d bytes\n"
" Hardware: %d.%d\n"
" %s firmware [%s], ver: %d, build: %d,"
" api: %d, cap: 0x%.4X\n",
priv->wsm_caps.input_buffers,
priv->wsm_caps.input_buffer_size,
priv->wsm_caps.hw_id, priv->wsm_caps.hw_subid,
cw1200_fw_types[priv->wsm_caps.fw_type],
priv->wsm_caps.fw_label, priv->wsm_caps.fw_ver,
priv->wsm_caps.fw_build,
priv->wsm_caps.fw_api, priv->wsm_caps.fw_cap);
/* Disable unsupported frequency bands */
if (!(priv->wsm_caps.fw_cap & 0x1))
priv->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = NULL;
if (!(priv->wsm_caps.fw_cap & 0x2))
priv->hw->wiphy->bands[IEEE80211_BAND_5GHZ] = NULL;
priv->firmware_ready = 1;
wake_up(&priv->wsm_startup_done);
return 0;
underflow:
WARN_ON(1);
return -EINVAL;
}
static int wsm_receive_indication(struct cw1200_common *priv,
int link_id,
struct wsm_buf *buf,
struct sk_buff **skb_p)
{
struct wsm_rx rx;
struct ieee80211_hdr *hdr;
size_t hdr_len;
__le16 fctl;
rx.status = WSM_GET32(buf);
rx.channel_number = WSM_GET16(buf);
rx.rx_rate = WSM_GET8(buf);
rx.rcpi_rssi = WSM_GET8(buf);
rx.flags = WSM_GET32(buf);
/* FW Workaround: Drop probe resp or
beacon when RSSI is 0
*/
hdr = (struct ieee80211_hdr *)(*skb_p)->data;
if (!rx.rcpi_rssi &&
(ieee80211_is_probe_resp(hdr->frame_control) ||
ieee80211_is_beacon(hdr->frame_control)))
return 0;
/* If no RSSI subscription has been made,
* convert RCPI to RSSI here
*/
if (!priv->cqm_use_rssi)
rx.rcpi_rssi = rx.rcpi_rssi / 2 - 110;
fctl = *(__le16 *)buf->data;
hdr_len = buf->data - buf->begin;
skb_pull(*skb_p, hdr_len);
if (!rx.status && ieee80211_is_deauth(fctl)) {
if (priv->join_status == CW1200_JOIN_STATUS_STA) {
/* Shedule unjoin work */
pr_debug("[WSM] Issue unjoin command (RX).\n");
wsm_lock_tx_async(priv);
if (queue_work(priv->workqueue,
&priv->unjoin_work) <= 0)
wsm_unlock_tx(priv);
}
}
cw1200_rx_cb(priv, &rx, link_id, skb_p);
if (*skb_p)
skb_push(*skb_p, hdr_len);
return 0;
underflow:
return -EINVAL;
}
static int wsm_event_indication(struct cw1200_common *priv, struct wsm_buf *buf)
{
int first;
struct cw1200_wsm_event *event;
if (priv->mode == NL80211_IFTYPE_UNSPECIFIED) {
/* STA is stopped. */
return 0;
}
event = kzalloc(sizeof(struct cw1200_wsm_event), GFP_KERNEL);
event->evt.id = __le32_to_cpu(WSM_GET32(buf));
event->evt.data = __le32_to_cpu(WSM_GET32(buf));
pr_debug("[WSM] Event: %d(%d)\n",
event->evt.id, event->evt.data);
spin_lock(&priv->event_queue_lock);
first = list_empty(&priv->event_queue);
list_add_tail(&event->link, &priv->event_queue);
spin_unlock(&priv->event_queue_lock);
if (first)
queue_work(priv->workqueue, &priv->event_handler);
return 0;
underflow:
kfree(event);
return -EINVAL;
}
static int wsm_channel_switch_indication(struct cw1200_common *priv,
struct wsm_buf *buf)
{
WARN_ON(WSM_GET32(buf));
priv->channel_switch_in_progress = 0;
wake_up(&priv->channel_switch_done);
wsm_unlock_tx(priv);
return 0;
underflow:
return -EINVAL;
}
static int wsm_set_pm_indication(struct cw1200_common *priv,
struct wsm_buf *buf)
{
/* TODO: Check buf (struct wsm_set_pm_complete) for validity */
if (priv->ps_mode_switch_in_progress) {
priv->ps_mode_switch_in_progress = 0;
wake_up(&priv->ps_mode_switch_done);
}
return 0;
}
static int wsm_scan_started(struct cw1200_common *priv, void *arg,
struct wsm_buf *buf)
{
u32 status = WSM_GET32(buf);
if (status != WSM_STATUS_SUCCESS) {
cw1200_scan_failed_cb(priv);
return -EINVAL;
}
return 0;
underflow:
WARN_ON(1);
return -EINVAL;
}
static int wsm_scan_complete_indication(struct cw1200_common *priv,
struct wsm_buf *buf)
{
struct wsm_scan_complete arg;
arg.status = WSM_GET32(buf);
arg.psm = WSM_GET8(buf);
arg.num_channels = WSM_GET8(buf);
cw1200_scan_complete_cb(priv, &arg);
return 0;
underflow:
return -EINVAL;
}
static int wsm_join_complete_indication(struct cw1200_common *priv,
struct wsm_buf *buf)
{
struct wsm_join_complete arg;
arg.status = WSM_GET32(buf);
pr_debug("[WSM] Join complete indication, status: %d\n", arg.status);
cw1200_join_complete_cb(priv, &arg);
return 0;
underflow:
return -EINVAL;
}
static int wsm_find_complete_indication(struct cw1200_common *priv,
struct wsm_buf *buf)
{
pr_warn("Implement find_complete_indication\n");
return 0;
}
static int wsm_ba_timeout_indication(struct cw1200_common *priv,
struct wsm_buf *buf)
{
u32 dummy;
u8 tid;
u8 dummy2;
u8 addr[ETH_ALEN];
dummy = WSM_GET32(buf);
tid = WSM_GET8(buf);
dummy2 = WSM_GET8(buf);
WSM_GET(buf, addr, ETH_ALEN);
pr_info("BlockACK timeout, tid %d, addr %pM\n",
tid, addr);
return 0;
underflow:
return -EINVAL;
}
static int wsm_suspend_resume_indication(struct cw1200_common *priv,
int link_id, struct wsm_buf *buf)
{
u32 flags;
struct wsm_suspend_resume arg;
flags = WSM_GET32(buf);
arg.link_id = link_id;
arg.stop = !(flags & 1);
arg.multicast = !!(flags & 8);
arg.queue = (flags >> 1) & 3;
cw1200_suspend_resume(priv, &arg);
return 0;
underflow:
return -EINVAL;
}
/* ******************************************************************** */
/* WSM TX */
static int wsm_cmd_send(struct cw1200_common *priv,
struct wsm_buf *buf,
void *arg, u16 cmd, long tmo)
{
size_t buf_len = buf->data - buf->begin;
int ret;
/* Don't bother if we're dead. */
if (priv->bh_error) {
ret = 0;
goto done;
}
/* Block until the cmd buffer is completed. Tortuous. */
spin_lock(&priv->wsm_cmd.lock);
while (!priv->wsm_cmd.done) {
spin_unlock(&priv->wsm_cmd.lock);
spin_lock(&priv->wsm_cmd.lock);
}
priv->wsm_cmd.done = 0;
spin_unlock(&priv->wsm_cmd.lock);
if (cmd == WSM_WRITE_MIB_REQ_ID ||
cmd == WSM_READ_MIB_REQ_ID)
pr_debug("[WSM] >>> 0x%.4X [MIB: 0x%.4X] (%zu)\n",
cmd, __le16_to_cpu(((__le16 *)buf->begin)[2]),
buf_len);
else
pr_debug("[WSM] >>> 0x%.4X (%zu)\n", cmd, buf_len);
/*
* Due to buggy SPI on CW1200, we need to
* pad the message by a few bytes to ensure
* that it's completely received.
*/
#ifdef CONFIG_CW1200_ETF
if (!etf_mode)
#endif
buf_len += 4;
/* Fill HI message header */
/* BH will add sequence number */
((__le16 *)buf->begin)[0] = __cpu_to_le16(buf_len);
((__le16 *)buf->begin)[1] = __cpu_to_le16(cmd);
spin_lock(&priv->wsm_cmd.lock);
BUG_ON(priv->wsm_cmd.ptr);
priv->wsm_cmd.ptr = buf->begin;
priv->wsm_cmd.len = buf_len;
priv->wsm_cmd.arg = arg;
priv->wsm_cmd.cmd = cmd;
spin_unlock(&priv->wsm_cmd.lock);
cw1200_bh_wakeup(priv);
/* Wait for command completion */
ret = wait_event_timeout(priv->wsm_cmd_wq,
priv->wsm_cmd.done, tmo);
if (!ret && !priv->wsm_cmd.done) {
spin_lock(&priv->wsm_cmd.lock);
priv->wsm_cmd.done = 1;
priv->wsm_cmd.ptr = NULL;
spin_unlock(&priv->wsm_cmd.lock);
if (priv->bh_error) {
/* Return ok to help system cleanup */
ret = 0;
} else {
pr_err("CMD req (0x%04x) stuck in firmware, killing BH\n", priv->wsm_cmd.cmd);
print_hex_dump_bytes("REQDUMP: ", DUMP_PREFIX_NONE,
buf->begin, buf_len);
pr_err("Outstanding outgoing frames: %d\n", priv->hw_bufs_used);
/* Kill BH thread to report the error to the top layer. */
atomic_add(1, &priv->bh_term);
wake_up(&priv->bh_wq);
ret = -ETIMEDOUT;
}
} else {
spin_lock(&priv->wsm_cmd.lock);
BUG_ON(!priv->wsm_cmd.done);
ret = priv->wsm_cmd.ret;
spin_unlock(&priv->wsm_cmd.lock);
}
done:
wsm_buf_reset(buf);
return ret;
}
#ifdef CONFIG_CW1200_ETF
int wsm_raw_cmd(struct cw1200_common *priv, u8 *data, size_t len)
{
struct wsm_buf *buf = &priv->wsm_cmd_buf;
int ret;
u16 *cmd = (u16 *)(data + 2);
wsm_cmd_lock(priv);
WSM_PUT(buf, data + 4, len - 4); /* Skip over header (u16+u16) */
ret = wsm_cmd_send(priv, buf, NULL, __le16_to_cpu(*cmd), WSM_CMD_TIMEOUT);
wsm_cmd_unlock(priv);
return ret;
nomem:
wsm_cmd_unlock(priv);
return -ENOMEM;
}
#endif /* CONFIG_CW1200_ETF */
/* ******************************************************************** */
/* WSM TX port control */
void wsm_lock_tx(struct cw1200_common *priv)
{
wsm_cmd_lock(priv);
if (atomic_add_return(1, &priv->tx_lock) == 1) {
if (wsm_flush_tx(priv))
pr_debug("[WSM] TX is locked.\n");
}
wsm_cmd_unlock(priv);
}
void wsm_lock_tx_async(struct cw1200_common *priv)
{
if (atomic_add_return(1, &priv->tx_lock) == 1)
pr_debug("[WSM] TX is locked (async).\n");
}
bool wsm_flush_tx(struct cw1200_common *priv)
{
unsigned long timestamp = jiffies;
bool pending = false;
long timeout;
int i;
/* Flush must be called with TX lock held. */
BUG_ON(!atomic_read(&priv->tx_lock));
/* First check if we really need to do something.
* It is safe to use unprotected access, as hw_bufs_used
* can only decrements.
*/
if (!priv->hw_bufs_used)
return true;
if (priv->bh_error) {
/* In case of failure do not wait for magic. */
pr_err("[WSM] Fatal error occured, will not flush TX.\n");
return false;
} else {
/* Get a timestamp of "oldest" frame */
for (i = 0; i < 4; ++i)
pending |= cw1200_queue_get_xmit_timestamp(
&priv->tx_queue[i],
&timestamp, 0xffffffff);
/* If there's nothing pending, we're good */
if (!pending)
return true;
timeout = timestamp + WSM_CMD_LAST_CHANCE_TIMEOUT - jiffies;
if (timeout < 0 || wait_event_timeout(priv->bh_evt_wq,
!priv->hw_bufs_used,
timeout) <= 0) {
/* Hmmm... Not good. Frame had stuck in firmware. */
priv->bh_error = 1;
wiphy_err(priv->hw->wiphy, "[WSM] TX Frames (%d) stuck in firmware, killing BH\n", priv->hw_bufs_used);
wake_up(&priv->bh_wq);
return false;
}
/* Ok, everything is flushed. */
return true;
}
}
void wsm_unlock_tx(struct cw1200_common *priv)
{
int tx_lock;
tx_lock = atomic_sub_return(1, &priv->tx_lock);
BUG_ON(tx_lock < 0);
if (tx_lock == 0) {
if (!priv->bh_error)
cw1200_bh_wakeup(priv);
pr_debug("[WSM] TX is unlocked.\n");
}
}
/* ******************************************************************** */
/* WSM RX */
int wsm_handle_exception(struct cw1200_common *priv, u8 *data, size_t len)
{
struct wsm_buf buf;
u32 reason;
u32 reg[18];
char fname[48];
unsigned int i;
static const char * const reason_str[] = {
"undefined instruction",
"prefetch abort",
"data abort",
"unknown error",
};
buf.begin = buf.data = data;
buf.end = &buf.begin[len];
reason = WSM_GET32(&buf);
for (i = 0; i < ARRAY_SIZE(reg); ++i)
reg[i] = WSM_GET32(&buf);
WSM_GET(&buf, fname, sizeof(fname));
if (reason < 4)
wiphy_err(priv->hw->wiphy,
"Firmware exception: %s.\n",
reason_str[reason]);
else
wiphy_err(priv->hw->wiphy,
"Firmware assert at %.*s, line %d\n",
(int) sizeof(fname), fname, reg[1]);
for (i = 0; i < 12; i += 4)
wiphy_err(priv->hw->wiphy,
"R%d: 0x%.8X, R%d: 0x%.8X, R%d: 0x%.8X, R%d: 0x%.8X,\n",
i + 0, reg[i + 0], i + 1, reg[i + 1],
i + 2, reg[i + 2], i + 3, reg[i + 3]);
wiphy_err(priv->hw->wiphy,
"R12: 0x%.8X, SP: 0x%.8X, LR: 0x%.8X, PC: 0x%.8X,\n",
reg[i + 0], reg[i + 1], reg[i + 2], reg[i + 3]);
i += 4;
wiphy_err(priv->hw->wiphy,
"CPSR: 0x%.8X, SPSR: 0x%.8X\n",
reg[i + 0], reg[i + 1]);
print_hex_dump_bytes("R1: ", DUMP_PREFIX_NONE,
fname, sizeof(fname));
return 0;
underflow:
wiphy_err(priv->hw->wiphy, "Firmware exception.\n");
print_hex_dump_bytes("Exception: ", DUMP_PREFIX_NONE,
data, len);
return -EINVAL;
}
int wsm_handle_rx(struct cw1200_common *priv, u16 id,
struct wsm_hdr *wsm, struct sk_buff **skb_p)
{
int ret = 0;
struct wsm_buf wsm_buf;
int link_id = (id >> 6) & 0x0F;
/* Strip link id. */
id &= ~WSM_TX_LINK_ID(WSM_TX_LINK_ID_MAX);
wsm_buf.begin = (u8 *)&wsm[0];
wsm_buf.data = (u8 *)&wsm[1];
wsm_buf.end = &wsm_buf.begin[__le32_to_cpu(wsm->len)];
pr_debug("[WSM] <<< 0x%.4X (%td)\n", id,
wsm_buf.end - wsm_buf.begin);
#ifdef CONFIG_CW1200_ETF
if (etf_mode) {
struct sk_buff *skb = alloc_skb(wsm_buf.end - wsm_buf.begin, GFP_KERNEL);
/* Strip out Sequence num before passing up */
wsm->id = __le16_to_cpu(wsm->id);
wsm->id &= 0x0FFF;
wsm->id = __cpu_to_le16(wsm->id);
memcpy(skb_put(skb, wsm_buf.end - wsm_buf.begin),
wsm_buf.begin,
wsm_buf.end - wsm_buf.begin);
skb_queue_tail(&priv->etf_q, skb);
/* Special case for startup */
if (id == WSM_STARTUP_IND_ID) {
wsm_startup_indication(priv, &wsm_buf);
} else if (id & 0x0400) {
spin_lock(&priv->wsm_cmd.lock);
priv->wsm_cmd.done = 1;
spin_unlock(&priv->wsm_cmd.lock);
wake_up(&priv->wsm_cmd_wq);
}
goto out;
}
#endif
if (id == WSM_TX_CONFIRM_IND_ID) {
ret = wsm_tx_confirm(priv, &wsm_buf, link_id);
} else if (id == WSM_MULTI_TX_CONFIRM_ID) {
ret = wsm_multi_tx_confirm(priv, &wsm_buf, link_id);
} else if (id & 0x0400) {
void *wsm_arg;
u16 wsm_cmd;
/* Do not trust FW too much. Protection against repeated
* response and race condition removal (see above).
*/
spin_lock(&priv->wsm_cmd.lock);
wsm_arg = priv->wsm_cmd.arg;
wsm_cmd = priv->wsm_cmd.cmd &
~WSM_TX_LINK_ID(WSM_TX_LINK_ID_MAX);
priv->wsm_cmd.cmd = 0xFFFF;
spin_unlock(&priv->wsm_cmd.lock);
if (WARN_ON((id & ~0x0400) != wsm_cmd)) {
/* Note that any non-zero is a fatal retcode. */
ret = -EINVAL;
goto out;
}
/* Note that wsm_arg can be NULL in case of timeout in
* wsm_cmd_send().
*/
switch (id) {
case WSM_READ_MIB_RESP_ID:
if (wsm_arg)
ret = wsm_read_mib_confirm(priv, wsm_arg,
&wsm_buf);
break;
case WSM_WRITE_MIB_RESP_ID:
if (wsm_arg)
ret = wsm_write_mib_confirm(priv, wsm_arg,
&wsm_buf);
break;
case WSM_START_SCAN_RESP_ID:
if (wsm_arg)
ret = wsm_scan_started(priv, wsm_arg, &wsm_buf);
break;
case WSM_CONFIGURATION_RESP_ID:
if (wsm_arg)
ret = wsm_configuration_confirm(priv, wsm_arg,
&wsm_buf);
break;
case WSM_JOIN_RESP_ID:
if (wsm_arg)
ret = wsm_join_confirm(priv, wsm_arg, &wsm_buf);
break;
case WSM_STOP_SCAN_RESP_ID:
case WSM_RESET_RESP_ID:
case WSM_ADD_KEY_RESP_ID:
case WSM_REMOVE_KEY_RESP_ID:
case WSM_SET_PM_RESP_ID:
case WSM_SET_BSS_PARAMS_RESP_ID:
case 0x0412: /* set_tx_queue_params */
case WSM_EDCA_PARAMS_RESP_ID:
case WSM_SWITCH_CHANNEL_RESP_ID:
case WSM_START_RESP_ID:
case WSM_BEACON_TRANSMIT_RESP_ID:
case 0x0419: /* start_find */
case 0x041A: /* stop_find */
case 0x041B: /* update_ie */
case 0x041C: /* map_link */
WARN_ON(wsm_arg != NULL);
ret = wsm_generic_confirm(priv, wsm_arg, &wsm_buf);
if (ret) {
wiphy_warn(priv->hw->wiphy,
"wsm_generic_confirm failed for request 0x%04x.\n",
id & ~0x0400);
/* often 0x407 and 0x410 occur, this means we're dead.. */
if (priv->join_status >= CW1200_JOIN_STATUS_JOINING) {
wsm_lock_tx(priv);
if (queue_work(priv->workqueue, &priv->unjoin_work) <= 0)
wsm_unlock_tx(priv);
}
}
break;
default:
wiphy_warn(priv->hw->wiphy,
"Unrecognized confirmation 0x%04x\n",
id & ~0x0400);
}
spin_lock(&priv->wsm_cmd.lock);
priv->wsm_cmd.ret = ret;
priv->wsm_cmd.done = 1;
spin_unlock(&priv->wsm_cmd.lock);
ret = 0; /* Error response from device should ne stop BH. */
wake_up(&priv->wsm_cmd_wq);
} else if (id & 0x0800) {
switch (id) {
case WSM_STARTUP_IND_ID:
ret = wsm_startup_indication(priv, &wsm_buf);
break;
case WSM_RECEIVE_IND_ID:
ret = wsm_receive_indication(priv, link_id,
&wsm_buf, skb_p);
break;
case 0x0805:
ret = wsm_event_indication(priv, &wsm_buf);
break;
case WSM_SCAN_COMPLETE_IND_ID:
ret = wsm_scan_complete_indication(priv, &wsm_buf);
break;
case 0x0808:
ret = wsm_ba_timeout_indication(priv, &wsm_buf);
break;
case 0x0809:
ret = wsm_set_pm_indication(priv, &wsm_buf);
break;
case 0x080A:
ret = wsm_channel_switch_indication(priv, &wsm_buf);
break;
case 0x080B:
ret = wsm_find_complete_indication(priv, &wsm_buf);
break;
case 0x080C:
ret = wsm_suspend_resume_indication(priv,
link_id, &wsm_buf);
break;
case 0x080F:
ret = wsm_join_complete_indication(priv, &wsm_buf);
break;
default:
pr_warn("Unrecognised WSM ID %04x\n", id);
}
} else {
WARN_ON(1);
ret = -EINVAL;
}
out:
return ret;
}
static bool wsm_handle_tx_data(struct cw1200_common *priv,
struct wsm_tx *wsm,
const struct ieee80211_tx_info *tx_info,
const struct cw1200_txpriv *txpriv,
struct cw1200_queue *queue)
{
bool handled = false;
const struct ieee80211_hdr *frame =
(struct ieee80211_hdr *)&((u8 *)wsm)[txpriv->offset];
__le16 fctl = frame->frame_control;
enum {
do_probe,
do_drop,
do_wep,
do_tx,
} action = do_tx;
switch (priv->mode) {
case NL80211_IFTYPE_STATION:
if (priv->join_status == CW1200_JOIN_STATUS_MONITOR)
action = do_tx;
else if (priv->join_status < CW1200_JOIN_STATUS_PRE_STA)
action = do_drop;
break;
case NL80211_IFTYPE_AP:
if (!priv->join_status) {
action = do_drop;
} else if (!(BIT(txpriv->raw_link_id) &
(BIT(0) | priv->link_id_map))) {
wiphy_warn(priv->hw->wiphy,
"A frame with expired link id is dropped.\n");
action = do_drop;
}
if (cw1200_queue_get_generation(wsm->packet_id) >
CW1200_MAX_REQUEUE_ATTEMPTS) {
/* HACK!!! WSM324 firmware has tendency to requeue
* multicast frames in a loop, causing performance
* drop and high power consumption of the driver.
* In this situation it is better just to drop
* the problematic frame.
*/
wiphy_warn(priv->hw->wiphy,
"Too many attempts to requeue a frame; dropped.\n");
action = do_drop;
}
break;
case NL80211_IFTYPE_ADHOC:
if (priv->join_status != CW1200_JOIN_STATUS_IBSS)
action = do_drop;
break;
case NL80211_IFTYPE_MESH_POINT:
action = do_tx; /* TODO: Test me! */
break;
case NL80211_IFTYPE_MONITOR:
default:
action = do_drop;
break;
}
if (action == do_tx) {
if (ieee80211_is_nullfunc(fctl)) {
spin_lock(&priv->bss_loss_lock);
if (priv->bss_loss_state) {
priv->bss_loss_confirm_id = wsm->packet_id;
wsm->queue_id = WSM_QUEUE_VOICE;
}
spin_unlock(&priv->bss_loss_lock);
} else if (ieee80211_is_probe_req(fctl)) {
action = do_probe;
} else if (ieee80211_is_deauth(fctl) &&
priv->mode != NL80211_IFTYPE_AP) {
pr_debug("[WSM] Issue unjoin command due to tx deauth.\n");
wsm_lock_tx_async(priv);
if (queue_work(priv->workqueue,
&priv->unjoin_work) <= 0)
wsm_unlock_tx(priv);
} else if (ieee80211_has_protected(fctl) &&
tx_info->control.hw_key &&
tx_info->control.hw_key->keyidx != priv->wep_default_key_id &&
(tx_info->control.hw_key->cipher == WLAN_CIPHER_SUITE_WEP40 ||
tx_info->control.hw_key->cipher == WLAN_CIPHER_SUITE_WEP104)) {
action = do_wep;
}
}
switch (action) {
case do_probe:
/* An interesting FW "feature". Device filters probe responses.
* The easiest way to get it back is to convert
* probe request into WSM start_scan command.
*/
pr_debug("[WSM] Convert probe request to scan.\n");
wsm_lock_tx_async(priv);
priv->pending_frame_id = __le32_to_cpu(wsm->packet_id);
if (queue_delayed_work(priv->workqueue,
&priv->scan.probe_work, 0) <= 0)
wsm_unlock_tx(priv);
handled = true;
break;
case do_drop:
pr_debug("[WSM] Drop frame (0x%.4X).\n", fctl);
BUG_ON(cw1200_queue_remove(queue,
__le32_to_cpu(wsm->packet_id)));
handled = true;
break;
case do_wep:
pr_debug("[WSM] Issue set_default_wep_key.\n");
wsm_lock_tx_async(priv);
priv->wep_default_key_id = tx_info->control.hw_key->keyidx;
priv->pending_frame_id = __le32_to_cpu(wsm->packet_id);
if (queue_work(priv->workqueue, &priv->wep_key_work) <= 0)
wsm_unlock_tx(priv);
handled = true;
break;
case do_tx:
pr_debug("[WSM] Transmit frame.\n");
break;
default:
/* Do nothing */
break;
}
return handled;
}
static int cw1200_get_prio_queue(struct cw1200_common *priv,
u32 link_id_map, int *total)
{
static const int urgent = BIT(CW1200_LINK_ID_AFTER_DTIM) |
BIT(CW1200_LINK_ID_UAPSD);
struct wsm_edca_queue_params *edca;
unsigned score, best = -1;
int winner = -1;
int queued;
int i;
/* search for a winner using edca params */
for (i = 0; i < 4; ++i) {
queued = cw1200_queue_get_num_queued(&priv->tx_queue[i],
link_id_map);
if (!queued)
continue;
*total += queued;
edca = &priv->edca.params[i];
score = ((edca->aifns + edca->cwmin) << 16) +
((edca->cwmax - edca->cwmin) *
(get_random_int() & 0xFFFF));
if (score < best && (winner < 0 || i != 3)) {
best = score;
winner = i;
}
}
/* override winner if bursting */
if (winner >= 0 && priv->tx_burst_idx >= 0 &&
winner != priv->tx_burst_idx &&
!cw1200_queue_get_num_queued(
&priv->tx_queue[winner],
link_id_map & urgent) &&
cw1200_queue_get_num_queued(
&priv->tx_queue[priv->tx_burst_idx],
link_id_map))
winner = priv->tx_burst_idx;
return winner;
}
static int wsm_get_tx_queue_and_mask(struct cw1200_common *priv,
struct cw1200_queue **queue_p,
u32 *tx_allowed_mask_p,
bool *more)
{
int idx;
u32 tx_allowed_mask;
int total = 0;
/* Search for a queue with multicast frames buffered */
if (priv->tx_multicast) {
tx_allowed_mask = BIT(CW1200_LINK_ID_AFTER_DTIM);
idx = cw1200_get_prio_queue(priv,
tx_allowed_mask, &total);
if (idx >= 0) {
*more = total > 1;
goto found;
}
}
/* Search for unicast traffic */
tx_allowed_mask = ~priv->sta_asleep_mask;
tx_allowed_mask |= BIT(CW1200_LINK_ID_UAPSD);
if (priv->sta_asleep_mask) {
tx_allowed_mask |= priv->pspoll_mask;
tx_allowed_mask &= ~BIT(CW1200_LINK_ID_AFTER_DTIM);
} else {
tx_allowed_mask |= BIT(CW1200_LINK_ID_AFTER_DTIM);
}
idx = cw1200_get_prio_queue(priv,
tx_allowed_mask, &total);
if (idx < 0)
return -ENOENT;
found:
*queue_p = &priv->tx_queue[idx];
*tx_allowed_mask_p = tx_allowed_mask;
return 0;
}
int wsm_get_tx(struct cw1200_common *priv, u8 **data,
size_t *tx_len, int *burst)
{
struct wsm_tx *wsm = NULL;
struct ieee80211_tx_info *tx_info;
struct cw1200_queue *queue = NULL;
int queue_num;
u32 tx_allowed_mask = 0;
const struct cw1200_txpriv *txpriv = NULL;
int count = 0;
/* More is used only for broadcasts. */
bool more = false;
#ifdef CONFIG_CW1200_ITP
count = cw1200_itp_get_tx(priv, data, tx_len, burst);
if (count)
return count;
#endif
if (priv->wsm_cmd.ptr) { /* CMD request */
++count;
spin_lock(&priv->wsm_cmd.lock);
BUG_ON(!priv->wsm_cmd.ptr);
*data = priv->wsm_cmd.ptr;
*tx_len = priv->wsm_cmd.len;
*burst = 1;
spin_unlock(&priv->wsm_cmd.lock);
} else {
for (;;) {
int ret;
if (atomic_add_return(0, &priv->tx_lock))
break;
spin_lock_bh(&priv->ps_state_lock);
ret = wsm_get_tx_queue_and_mask(priv, &queue,
&tx_allowed_mask, &more);
queue_num = queue - priv->tx_queue;
if (priv->buffered_multicasts &&
(ret || !more) &&
(priv->tx_multicast || !priv->sta_asleep_mask)) {
priv->buffered_multicasts = false;
if (priv->tx_multicast) {
priv->tx_multicast = false;
queue_work(priv->workqueue,
&priv->multicast_stop_work);
}
}
spin_unlock_bh(&priv->ps_state_lock);
if (ret)
break;
if (cw1200_queue_get(queue,
tx_allowed_mask,
&wsm, &tx_info, &txpriv))
continue;
if (wsm_handle_tx_data(priv, wsm,
tx_info, txpriv, queue))
continue; /* Handled by WSM */
wsm->hdr.id &= __cpu_to_le16(
~WSM_TX_LINK_ID(WSM_TX_LINK_ID_MAX));
wsm->hdr.id |= cpu_to_le16(
WSM_TX_LINK_ID(txpriv->raw_link_id));
priv->pspoll_mask &= ~BIT(txpriv->raw_link_id);
*data = (u8 *)wsm;
*tx_len = __le16_to_cpu(wsm->hdr.len);
/* allow bursting if txop is set */
if (priv->edca.params[queue_num].txop_limit)
*burst = min(*burst,
(int)cw1200_queue_get_num_queued(queue, tx_allowed_mask) + 1);
else
*burst = 1;
/* store index of bursting queue */
if (*burst > 1)
priv->tx_burst_idx = queue_num;
else
priv->tx_burst_idx = -1;
if (more) {
struct ieee80211_hdr *hdr =
(struct ieee80211_hdr *)
&((u8 *)wsm)[txpriv->offset];
/* more buffered multicast/broadcast frames
* ==> set MoreData flag in IEEE 802.11 header
* to inform PS STAs
*/
hdr->frame_control |=
cpu_to_le16(IEEE80211_FCTL_MOREDATA);
}
pr_debug("[WSM] >>> 0x%.4X (%zu) %p %c\n",
0x0004, *tx_len, *data,
wsm->more ? 'M' : ' ');
++count;
break;
}
}
return count;
}
void wsm_txed(struct cw1200_common *priv, u8 *data)
{
if (data == priv->wsm_cmd.ptr) {
spin_lock(&priv->wsm_cmd.lock);
priv->wsm_cmd.ptr = NULL;
spin_unlock(&priv->wsm_cmd.lock);
}
}
/* ******************************************************************** */
/* WSM buffer */
void wsm_buf_init(struct wsm_buf *buf)
{
BUG_ON(buf->begin);
buf->begin = kmalloc(FWLOAD_BLOCK_SIZE, GFP_KERNEL | GFP_DMA);
buf->end = buf->begin ? &buf->begin[FWLOAD_BLOCK_SIZE] : buf->begin;
wsm_buf_reset(buf);
}
void wsm_buf_deinit(struct wsm_buf *buf)
{
kfree(buf->begin);
buf->begin = buf->data = buf->end = NULL;
}
static void wsm_buf_reset(struct wsm_buf *buf)
{
if (buf->begin) {
buf->data = &buf->begin[4];
*(u32 *)buf->begin = 0;
} else {
buf->data = buf->begin;
}
}
static int wsm_buf_reserve(struct wsm_buf *buf, size_t extra_size)
{
size_t pos = buf->data - buf->begin;
size_t size = pos + extra_size;
size = round_up(size, FWLOAD_BLOCK_SIZE);
buf->begin = krealloc(buf->begin, size, GFP_KERNEL | GFP_DMA);
if (buf->begin) {
buf->data = &buf->begin[pos];
buf->end = &buf->begin[size];
return 0;
} else {
buf->end = buf->data = buf->begin;
return -ENOMEM;
}
}
/*
* WSM host interface (HI) interface for ST-Ericsson CW1200 mac80211 drivers
*
* Copyright (c) 2010, ST-Ericsson
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
*
* Based on CW1200 UMAC WSM API, which is
* Copyright (C) ST-Ericsson SA 2010
* Author: Stewart Mathers <stewart.mathers@stericsson.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef CW1200_WSM_H_INCLUDED
#define CW1200_WSM_H_INCLUDED
#include <linux/spinlock.h>
struct cw1200_common;
/* Bands */
/* Radio band 2.412 -2.484 GHz. */
#define WSM_PHY_BAND_2_4G (0)
/* Radio band 4.9375-5.8250 GHz. */
#define WSM_PHY_BAND_5G (1)
/* Transmit rates */
/* 1 Mbps ERP-DSSS */
#define WSM_TRANSMIT_RATE_1 (0)
/* 2 Mbps ERP-DSSS */
#define WSM_TRANSMIT_RATE_2 (1)
/* 5.5 Mbps ERP-CCK */
#define WSM_TRANSMIT_RATE_5 (2)
/* 11 Mbps ERP-CCK */
#define WSM_TRANSMIT_RATE_11 (3)
/* 22 Mbps ERP-PBCC (Not supported) */
/* #define WSM_TRANSMIT_RATE_22 (4) */
/* 33 Mbps ERP-PBCC (Not supported) */
/* #define WSM_TRANSMIT_RATE_33 (5) */
/* 6 Mbps (3 Mbps) ERP-OFDM, BPSK coding rate 1/2 */
#define WSM_TRANSMIT_RATE_6 (6)
/* 9 Mbps (4.5 Mbps) ERP-OFDM, BPSK coding rate 3/4 */
#define WSM_TRANSMIT_RATE_9 (7)
/* 12 Mbps (6 Mbps) ERP-OFDM, QPSK coding rate 1/2 */
#define WSM_TRANSMIT_RATE_12 (8)
/* 18 Mbps (9 Mbps) ERP-OFDM, QPSK coding rate 3/4 */
#define WSM_TRANSMIT_RATE_18 (9)
/* 24 Mbps (12 Mbps) ERP-OFDM, 16QAM coding rate 1/2 */
#define WSM_TRANSMIT_RATE_24 (10)
/* 36 Mbps (18 Mbps) ERP-OFDM, 16QAM coding rate 3/4 */
#define WSM_TRANSMIT_RATE_36 (11)
/* 48 Mbps (24 Mbps) ERP-OFDM, 64QAM coding rate 1/2 */
#define WSM_TRANSMIT_RATE_48 (12)
/* 54 Mbps (27 Mbps) ERP-OFDM, 64QAM coding rate 3/4 */
#define WSM_TRANSMIT_RATE_54 (13)
/* 6.5 Mbps HT-OFDM, BPSK coding rate 1/2 */
#define WSM_TRANSMIT_RATE_HT_6 (14)
/* 13 Mbps HT-OFDM, QPSK coding rate 1/2 */
#define WSM_TRANSMIT_RATE_HT_13 (15)
/* 19.5 Mbps HT-OFDM, QPSK coding rate 3/4 */
#define WSM_TRANSMIT_RATE_HT_19 (16)
/* 26 Mbps HT-OFDM, 16QAM coding rate 1/2 */
#define WSM_TRANSMIT_RATE_HT_26 (17)
/* 39 Mbps HT-OFDM, 16QAM coding rate 3/4 */
#define WSM_TRANSMIT_RATE_HT_39 (18)
/* 52 Mbps HT-OFDM, 64QAM coding rate 2/3 */
#define WSM_TRANSMIT_RATE_HT_52 (19)
/* 58.5 Mbps HT-OFDM, 64QAM coding rate 3/4 */
#define WSM_TRANSMIT_RATE_HT_58 (20)
/* 65 Mbps HT-OFDM, 64QAM coding rate 5/6 */
#define WSM_TRANSMIT_RATE_HT_65 (21)
/* Scan types */
/* Foreground scan */
#define WSM_SCAN_TYPE_FOREGROUND (0)
/* Background scan */
#define WSM_SCAN_TYPE_BACKGROUND (1)
/* Auto scan */
#define WSM_SCAN_TYPE_AUTO (2)
/* Scan flags */
/* Forced background scan means if the station cannot */
/* enter the power-save mode, it shall force to perform a */
/* background scan. Only valid when ScanType is */
/* background scan. */
#define WSM_SCAN_FLAG_FORCE_BACKGROUND (BIT(0))
/* The WLAN device scans one channel at a time so */
/* that disturbance to the data traffic is minimized. */
#define WSM_SCAN_FLAG_SPLIT_METHOD (BIT(1))
/* Preamble Type. Long if not set. */
#define WSM_SCAN_FLAG_SHORT_PREAMBLE (BIT(2))
/* 11n Tx Mode. Mixed if not set. */
#define WSM_SCAN_FLAG_11N_GREENFIELD (BIT(3))
/* Scan constraints */
/* Maximum number of channels to be scanned. */
#define WSM_SCAN_MAX_NUM_OF_CHANNELS (48)
/* The maximum number of SSIDs that the device can scan for. */
#define WSM_SCAN_MAX_NUM_OF_SSIDS (2)
/* Power management modes */
/* 802.11 Active mode */
#define WSM_PSM_ACTIVE (0)
/* 802.11 PS mode */
#define WSM_PSM_PS BIT(0)
/* Fast Power Save bit */
#define WSM_PSM_FAST_PS_FLAG BIT(7)
/* Dynamic aka Fast power save */
#define WSM_PSM_FAST_PS (BIT(0) | BIT(7))
/* Undetermined */
/* Note : Undetermined status is reported when the */
/* NULL data frame used to advertise the PM mode to */
/* the AP at Pre or Post Background Scan is not Acknowledged */
#define WSM_PSM_UNKNOWN BIT(1)
/* Queue IDs */
/* best effort/legacy */
#define WSM_QUEUE_BEST_EFFORT (0)
/* background */
#define WSM_QUEUE_BACKGROUND (1)
/* video */
#define WSM_QUEUE_VIDEO (2)
/* voice */
#define WSM_QUEUE_VOICE (3)
/* HT TX parameters */
/* Non-HT */
#define WSM_HT_TX_NON_HT (0)
/* Mixed format */
#define WSM_HT_TX_MIXED (1)
/* Greenfield format */
#define WSM_HT_TX_GREENFIELD (2)
/* STBC allowed */
#define WSM_HT_TX_STBC (BIT(7))
/* EPTA prioirty flags for BT Coex */
/* default epta priority */
#define WSM_EPTA_PRIORITY_DEFAULT 4
/* use for normal data */
#define WSM_EPTA_PRIORITY_DATA 4
/* use for connect/disconnect/roaming*/
#define WSM_EPTA_PRIORITY_MGT 5
/* use for action frames */
#define WSM_EPTA_PRIORITY_ACTION 5
/* use for AC_VI data */
#define WSM_EPTA_PRIORITY_VIDEO 5
/* use for AC_VO data */
#define WSM_EPTA_PRIORITY_VOICE 6
/* use for EAPOL exchange */
#define WSM_EPTA_PRIORITY_EAPOL 7
/* TX status */
/* Frame was sent aggregated */
/* Only valid for WSM_SUCCESS status. */
#define WSM_TX_STATUS_AGGREGATION (BIT(0))
/* Host should requeue this frame later. */
/* Valid only when status is WSM_REQUEUE. */
#define WSM_TX_STATUS_REQUEUE (BIT(1))
/* Normal Ack */
#define WSM_TX_STATUS_NORMAL_ACK (0<<2)
/* No Ack */
#define WSM_TX_STATUS_NO_ACK (1<<2)
/* No explicit acknowledgement */
#define WSM_TX_STATUS_NO_EXPLICIT_ACK (2<<2)
/* Block Ack */
/* Only valid for WSM_SUCCESS status. */
#define WSM_TX_STATUS_BLOCK_ACK (3<<2)
/* RX status */
/* Unencrypted */
#define WSM_RX_STATUS_UNENCRYPTED (0<<0)
/* WEP */
#define WSM_RX_STATUS_WEP (1<<0)
/* TKIP */
#define WSM_RX_STATUS_TKIP (2<<0)
/* AES */
#define WSM_RX_STATUS_AES (3<<0)
/* WAPI */
#define WSM_RX_STATUS_WAPI (4<<0)
/* Macro to fetch encryption subfield. */
#define WSM_RX_STATUS_ENCRYPTION(status) ((status) & 0x07)
/* Frame was part of an aggregation */
#define WSM_RX_STATUS_AGGREGATE (BIT(3))
/* Frame was first in the aggregation */
#define WSM_RX_STATUS_AGGREGATE_FIRST (BIT(4))
/* Frame was last in the aggregation */
#define WSM_RX_STATUS_AGGREGATE_LAST (BIT(5))
/* Indicates a defragmented frame */
#define WSM_RX_STATUS_DEFRAGMENTED (BIT(6))
/* Indicates a Beacon frame */
#define WSM_RX_STATUS_BEACON (BIT(7))
/* Indicates STA bit beacon TIM field */
#define WSM_RX_STATUS_TIM (BIT(8))
/* Indicates Beacon frame's virtual bitmap contains multicast bit */
#define WSM_RX_STATUS_MULTICAST (BIT(9))
/* Indicates frame contains a matching SSID */
#define WSM_RX_STATUS_MATCHING_SSID (BIT(10))
/* Indicates frame contains a matching BSSI */
#define WSM_RX_STATUS_MATCHING_BSSI (BIT(11))
/* Indicates More bit set in Framectl field */
#define WSM_RX_STATUS_MORE_DATA (BIT(12))
/* Indicates frame received during a measurement process */
#define WSM_RX_STATUS_MEASUREMENT (BIT(13))
/* Indicates frame received as an HT packet */
#define WSM_RX_STATUS_HT (BIT(14))
/* Indicates frame received with STBC */
#define WSM_RX_STATUS_STBC (BIT(15))
/* Indicates Address 1 field matches dot11StationId */
#define WSM_RX_STATUS_ADDRESS1 (BIT(16))
/* Indicates Group address present in the Address 1 field */
#define WSM_RX_STATUS_GROUP (BIT(17))
/* Indicates Broadcast address present in the Address 1 field */
#define WSM_RX_STATUS_BROADCAST (BIT(18))
/* Indicates group key used with encrypted frames */
#define WSM_RX_STATUS_GROUP_KEY (BIT(19))
/* Macro to fetch encryption key index. */
#define WSM_RX_STATUS_KEY_IDX(status) (((status >> 20)) & 0x0F)
/* Indicates TSF inclusion after 802.11 frame body */
#define WSM_RX_STATUS_TSF_INCLUDED (BIT(24))
/* Frame Control field starts at Frame offset + 2 */
#define WSM_TX_2BYTES_SHIFT (BIT(7))
/* Join mode */
/* IBSS */
#define WSM_JOIN_MODE_IBSS (0)
/* BSS */
#define WSM_JOIN_MODE_BSS (1)
/* PLCP preamble type */
/* For long preamble */
#define WSM_JOIN_PREAMBLE_LONG (0)
/* For short preamble (Long for 1Mbps) */
#define WSM_JOIN_PREAMBLE_SHORT (1)
/* For short preamble (Long for 1 and 2Mbps) */
#define WSM_JOIN_PREAMBLE_SHORT_2 (2)
/* Join flags */
/* Unsynchronized */
#define WSM_JOIN_FLAGS_UNSYNCRONIZED BIT(0)
/* The BSS owner is a P2P GO */
#define WSM_JOIN_FLAGS_P2P_GO BIT(1)
/* Force to join BSS with the BSSID and the
* SSID specified without waiting for beacons. The
* ProbeForJoin parameter is ignored. */
#define WSM_JOIN_FLAGS_FORCE BIT(2)
/* Give probe request/response higher
* priority over the BT traffic */
#define WSM_JOIN_FLAGS_PRIO BIT(3)
/* Issue immediate join confirmation and use
* join complete to notify about completion */
#define WSM_JOIN_FLAGS_FORCE_WITH_COMPLETE_IND BIT(5)
/* Key types */
#define WSM_KEY_TYPE_WEP_DEFAULT (0)
#define WSM_KEY_TYPE_WEP_PAIRWISE (1)
#define WSM_KEY_TYPE_TKIP_GROUP (2)
#define WSM_KEY_TYPE_TKIP_PAIRWISE (3)
#define WSM_KEY_TYPE_AES_GROUP (4)
#define WSM_KEY_TYPE_AES_PAIRWISE (5)
#define WSM_KEY_TYPE_WAPI_GROUP (6)
#define WSM_KEY_TYPE_WAPI_PAIRWISE (7)
/* Key indexes */
#define WSM_KEY_MAX_INDEX (10)
/* ACK policy */
#define WSM_ACK_POLICY_NORMAL (0)
#define WSM_ACK_POLICY_NO_ACK (1)
/* Start modes */
#define WSM_START_MODE_AP (0) /* Mini AP */
#define WSM_START_MODE_P2P_GO (1) /* P2P GO */
#define WSM_START_MODE_P2P_DEV (2) /* P2P device */
/* SetAssociationMode MIB flags */
#define WSM_ASSOCIATION_MODE_USE_PREAMBLE_TYPE (BIT(0))
#define WSM_ASSOCIATION_MODE_USE_HT_MODE (BIT(1))
#define WSM_ASSOCIATION_MODE_USE_BASIC_RATE_SET (BIT(2))
#define WSM_ASSOCIATION_MODE_USE_MPDU_START_SPACING (BIT(3))
#define WSM_ASSOCIATION_MODE_SNOOP_ASSOC_FRAMES (BIT(4))
/* RcpiRssiThreshold MIB flags */
#define WSM_RCPI_RSSI_THRESHOLD_ENABLE (BIT(0))
#define WSM_RCPI_RSSI_USE_RSSI (BIT(1))
#define WSM_RCPI_RSSI_DONT_USE_UPPER (BIT(2))
#define WSM_RCPI_RSSI_DONT_USE_LOWER (BIT(3))
/* Update-ie constants */
#define WSM_UPDATE_IE_BEACON (BIT(0))
#define WSM_UPDATE_IE_PROBE_RESP (BIT(1))
#define WSM_UPDATE_IE_PROBE_REQ (BIT(2))
/* WSM events */
/* Error */
#define WSM_EVENT_ERROR (0)
/* BSS lost */
#define WSM_EVENT_BSS_LOST (1)
/* BSS regained */
#define WSM_EVENT_BSS_REGAINED (2)
/* Radar detected */
#define WSM_EVENT_RADAR_DETECTED (3)
/* RCPI or RSSI threshold triggered */
#define WSM_EVENT_RCPI_RSSI (4)
/* BT inactive */
#define WSM_EVENT_BT_INACTIVE (5)
/* BT active */
#define WSM_EVENT_BT_ACTIVE (6)
/* MIB IDs */
/* 4.1 dot11StationId */
#define WSM_MIB_ID_DOT11_STATION_ID 0x0000
/* 4.2 dot11MaxtransmitMsduLifeTime */
#define WSM_MIB_ID_DOT11_MAX_TRANSMIT_LIFTIME 0x0001
/* 4.3 dot11MaxReceiveLifeTime */
#define WSM_MIB_ID_DOT11_MAX_RECEIVE_LIFETIME 0x0002
/* 4.4 dot11SlotTime */
#define WSM_MIB_ID_DOT11_SLOT_TIME 0x0003
/* 4.5 dot11GroupAddressesTable */
#define WSM_MIB_ID_DOT11_GROUP_ADDRESSES_TABLE 0x0004
#define WSM_MAX_GRP_ADDRTABLE_ENTRIES 8
/* 4.6 dot11WepDefaultKeyId */
#define WSM_MIB_ID_DOT11_WEP_DEFAULT_KEY_ID 0x0005
/* 4.7 dot11CurrentTxPowerLevel */
#define WSM_MIB_ID_DOT11_CURRENT_TX_POWER_LEVEL 0x0006
/* 4.8 dot11RTSThreshold */
#define WSM_MIB_ID_DOT11_RTS_THRESHOLD 0x0007
/* 4.9 NonErpProtection */
#define WSM_MIB_ID_NON_ERP_PROTECTION 0x1000
/* 4.10 ArpIpAddressesTable */
#define WSM_MIB_ID_ARP_IP_ADDRESSES_TABLE 0x1001
#define WSM_MAX_ARP_IP_ADDRTABLE_ENTRIES 1
/* 4.11 TemplateFrame */
#define WSM_MIB_ID_TEMPLATE_FRAME 0x1002
/* 4.12 RxFilter */
#define WSM_MIB_ID_RX_FILTER 0x1003
/* 4.13 BeaconFilterTable */
#define WSM_MIB_ID_BEACON_FILTER_TABLE 0x1004
/* 4.14 BeaconFilterEnable */
#define WSM_MIB_ID_BEACON_FILTER_ENABLE 0x1005
/* 4.15 OperationalPowerMode */
#define WSM_MIB_ID_OPERATIONAL_POWER_MODE 0x1006
/* 4.16 BeaconWakeUpPeriod */
#define WSM_MIB_ID_BEACON_WAKEUP_PERIOD 0x1007
/* 4.17 RcpiRssiThreshold */
#define WSM_MIB_ID_RCPI_RSSI_THRESHOLD 0x1009
/* 4.18 StatisticsTable */
#define WSM_MIB_ID_STATISTICS_TABLE 0x100A
/* 4.19 IbssPsConfig */
#define WSM_MIB_ID_IBSS_PS_CONFIG 0x100B
/* 4.20 CountersTable */
#define WSM_MIB_ID_COUNTERS_TABLE 0x100C
/* 4.21 BlockAckPolicy */
#define WSM_MIB_ID_BLOCK_ACK_POLICY 0x100E
/* 4.22 OverrideInternalTxRate */
#define WSM_MIB_ID_OVERRIDE_INTERNAL_TX_RATE 0x100F
/* 4.23 SetAssociationMode */
#define WSM_MIB_ID_SET_ASSOCIATION_MODE 0x1010
/* 4.24 UpdateEptaConfigData */
#define WSM_MIB_ID_UPDATE_EPTA_CONFIG_DATA 0x1011
/* 4.25 SelectCcaMethod */
#define WSM_MIB_ID_SELECT_CCA_METHOD 0x1012
/* 4.26 SetUpasdInformation */
#define WSM_MIB_ID_SET_UAPSD_INFORMATION 0x1013
/* 4.27 SetAutoCalibrationMode WBF00004073 */
#define WSM_MIB_ID_SET_AUTO_CALIBRATION_MODE 0x1015
/* 4.28 SetTxRateRetryPolicy */
#define WSM_MIB_ID_SET_TX_RATE_RETRY_POLICY 0x1016
/* 4.29 SetHostMessageTypeFilter */
#define WSM_MIB_ID_SET_HOST_MSG_TYPE_FILTER 0x1017
/* 4.30 P2PFindInfo */
#define WSM_MIB_ID_P2P_FIND_INFO 0x1018
/* 4.31 P2PPsModeInfo */
#define WSM_MIB_ID_P2P_PS_MODE_INFO 0x1019
/* 4.32 SetEtherTypeDataFrameFilter */
#define WSM_MIB_ID_SET_ETHERTYPE_DATAFRAME_FILTER 0x101A
/* 4.33 SetUDPPortDataFrameFilter */
#define WSM_MIB_ID_SET_UDPPORT_DATAFRAME_FILTER 0x101B
/* 4.34 SetMagicDataFrameFilter */
#define WSM_MIB_ID_SET_MAGIC_DATAFRAME_FILTER 0x101C
/* 4.35 P2PDeviceInfo */
#define WSM_MIB_ID_P2P_DEVICE_INFO 0x101D
/* 4.36 SetWCDMABand */
#define WSM_MIB_ID_SET_WCDMA_BAND 0x101E
/* 4.37 GroupTxSequenceCounter */
#define WSM_MIB_ID_GRP_SEQ_COUNTER 0x101F
/* 4.38 ProtectedMgmtPolicy */
#define WSM_MIB_ID_PROTECTED_MGMT_POLICY 0x1020
/* 4.39 SetHtProtection */
#define WSM_MIB_ID_SET_HT_PROTECTION 0x1021
/* 4.40 GPIO Command */
#define WSM_MIB_ID_GPIO_COMMAND 0x1022
/* 4.41 TSF Counter Value */
#define WSM_MIB_ID_TSF_COUNTER 0x1023
/* Test Purposes Only */
#define WSM_MIB_ID_BLOCK_ACK_INFO 0x100D
/* 4.42 UseMultiTxConfMessage */
#define WSM_MIB_USE_MULTI_TX_CONF 0x1024
/* 4.43 Keep-alive period */
#define WSM_MIB_ID_KEEP_ALIVE_PERIOD 0x1025
/* 4.44 Disable BSSID filter */
#define WSM_MIB_ID_DISABLE_BSSID_FILTER 0x1026
/* Frame template types */
#define WSM_FRAME_TYPE_PROBE_REQUEST (0)
#define WSM_FRAME_TYPE_BEACON (1)
#define WSM_FRAME_TYPE_NULL (2)
#define WSM_FRAME_TYPE_QOS_NULL (3)
#define WSM_FRAME_TYPE_PS_POLL (4)
#define WSM_FRAME_TYPE_PROBE_RESPONSE (5)
#define WSM_FRAME_GREENFIELD (0x80) /* See 4.11 */
/* Status */
/* The WSM firmware has completed a request */
/* successfully. */
#define WSM_STATUS_SUCCESS (0)
/* This is a generic failure code if other error codes do */
/* not apply. */
#define WSM_STATUS_FAILURE (1)
/* A request contains one or more invalid parameters. */
#define WSM_INVALID_PARAMETER (2)
/* The request cannot perform because the device is in */
/* an inappropriate mode. */
#define WSM_ACCESS_DENIED (3)
/* The frame received includes a decryption error. */
#define WSM_STATUS_DECRYPTFAILURE (4)
/* A MIC failure is detected in the received packets. */
#define WSM_STATUS_MICFAILURE (5)
/* The transmit request failed due to retry limit being */
/* exceeded. */
#define WSM_STATUS_RETRY_EXCEEDED (6)
/* The transmit request failed due to MSDU life time */
/* being exceeded. */
#define WSM_STATUS_TX_LIFETIME_EXCEEDED (7)
/* The link to the AP is lost. */
#define WSM_STATUS_LINK_LOST (8)
/* No key was found for the encrypted frame */
#define WSM_STATUS_NO_KEY_FOUND (9)
/* Jammer was detected when transmitting this frame */
#define WSM_STATUS_JAMMER_DETECTED (10)
/* The message should be requeued later. */
/* This is applicable only to Transmit */
#define WSM_REQUEUE (11)
/* Advanced filtering options */
#define WSM_MAX_FILTER_ELEMENTS (4)
#define WSM_FILTER_ACTION_IGNORE (0)
#define WSM_FILTER_ACTION_FILTER_IN (1)
#define WSM_FILTER_ACTION_FILTER_OUT (2)
#define WSM_FILTER_PORT_TYPE_DST (0)
#define WSM_FILTER_PORT_TYPE_SRC (1)
/* Actual header of WSM messages */
struct wsm_hdr {
__le16 len;
__le16 id;
};
#define WSM_TX_SEQ_MAX (7)
#define WSM_TX_SEQ(seq) \
((seq & WSM_TX_SEQ_MAX) << 13)
#define WSM_TX_LINK_ID_MAX (0x0F)
#define WSM_TX_LINK_ID(link_id) \
((link_id & WSM_TX_LINK_ID_MAX) << 6)
#define MAX_BEACON_SKIP_TIME_MS 1000
#define WSM_CMD_LAST_CHANCE_TIMEOUT (HZ * 3 / 2)
/* ******************************************************************** */
/* WSM capability */
#define WSM_STARTUP_IND_ID 0x0801
struct wsm_startup_ind {
u16 input_buffers;
u16 input_buffer_size;
u16 status;
u16 hw_id;
u16 hw_subid;
u16 fw_cap;
u16 fw_type;
u16 fw_api;
u16 fw_build;
u16 fw_ver;
char fw_label[128];
u32 config[4];
};
/* ******************************************************************** */
/* WSM commands */
/* 3.1 */
#define WSM_CONFIGURATION_REQ_ID 0x0009
#define WSM_CONFIGURATION_RESP_ID 0x0409
struct wsm_tx_power_range {
int min_power_level;
int max_power_level;
u32 stepping;
};
struct wsm_configuration {
/* [in] */ u32 dot11MaxTransmitMsduLifeTime;
/* [in] */ u32 dot11MaxReceiveLifeTime;
/* [in] */ u32 dot11RtsThreshold;
/* [in, out] */ u8 *dot11StationId;
/* [in] */ const void *dpdData;
/* [in] */ size_t dpdData_size;
/* [out] */ u8 dot11FrequencyBandsSupported;
/* [out] */ u32 supportedRateMask;
/* [out] */ struct wsm_tx_power_range txPowerRange[2];
};
int wsm_configuration(struct cw1200_common *priv,
struct wsm_configuration *arg);
/* 3.3 */
#define WSM_RESET_REQ_ID 0x000A
#define WSM_RESET_RESP_ID 0x040A
struct wsm_reset {
/* [in] */ int link_id;
/* [in] */ bool reset_statistics;
};
int wsm_reset(struct cw1200_common *priv, const struct wsm_reset *arg);
/* 3.5 */
#define WSM_READ_MIB_REQ_ID 0x0005
#define WSM_READ_MIB_RESP_ID 0x0405
int wsm_read_mib(struct cw1200_common *priv, u16 mib_id, void *buf,
size_t buf_size);
/* 3.7 */
#define WSM_WRITE_MIB_REQ_ID 0x0006
#define WSM_WRITE_MIB_RESP_ID 0x0406
int wsm_write_mib(struct cw1200_common *priv, u16 mib_id, void *buf,
size_t buf_size);
/* 3.9 */
#define WSM_START_SCAN_REQ_ID 0x0007
#define WSM_START_SCAN_RESP_ID 0x0407
struct wsm_ssid {
u8 ssid[32];
u32 length;
};
struct wsm_scan_ch {
u16 number;
u32 min_chan_time;
u32 max_chan_time;
u32 tx_power_level;
};
struct wsm_scan {
/* WSM_PHY_BAND_... */
u8 band;
/* WSM_SCAN_TYPE_... */
u8 type;
/* WSM_SCAN_FLAG_... */
u8 flags;
/* WSM_TRANSMIT_RATE_... */
u8 max_tx_rate;
/* Interval period in TUs that the device shall the re- */
/* execute the requested scan. Max value supported by the device */
/* is 256s. */
u32 auto_scan_interval;
/* Number of probe requests (per SSID) sent to one (1) */
/* channel. Zero (0) means that none is send, which */
/* means that a passive scan is to be done. Value */
/* greater than zero (0) means that an active scan is to */
/* be done. */
u32 num_probes;
/* Number of channels to be scanned. */
/* Maximum value is WSM_SCAN_MAX_NUM_OF_CHANNELS. */
u8 num_channels;
/* Number of SSID provided in the scan command (this */
/* is zero (0) in broadcast scan) */
/* The maximum number of SSIDs is WSM_SCAN_MAX_NUM_OF_SSIDS. */
u8 num_ssids;
/* The delay time (in microseconds) period */
/* before sending a probe-request. */
u8 probe_delay;
/* SSIDs to be scanned [numOfSSIDs]; */
struct wsm_ssid *ssids;
/* Channels to be scanned [numOfChannels]; */
struct wsm_scan_ch *ch;
};
int wsm_scan(struct cw1200_common *priv, const struct wsm_scan *arg);
/* 3.11 */
#define WSM_STOP_SCAN_REQ_ID 0x0008
#define WSM_STOP_SCAN_RESP_ID 0x0408
int wsm_stop_scan(struct cw1200_common *priv);
/* 3.13 */
#define WSM_SCAN_COMPLETE_IND_ID 0x0806
struct wsm_scan_complete {
/* WSM_STATUS_... */
u32 status;
/* WSM_PSM_... */
u8 psm;
/* Number of channels that the scan operation completed. */
u8 num_channels;
};
/* 3.14 */
#define WSM_TX_CONFIRM_IND_ID 0x0404
#define WSM_MULTI_TX_CONFIRM_ID 0x041E
struct wsm_tx_confirm {
/* Packet identifier used in wsm_tx. */
u32 packet_id;
/* WSM_STATUS_... */
u32 status;
/* WSM_TRANSMIT_RATE_... */
u8 tx_rate;
/* The number of times the frame was transmitted */
/* without receiving an acknowledgement. */
u8 ack_failures;
/* WSM_TX_STATUS_... */
u16 flags;
/* The total time in microseconds that the frame spent in */
/* the WLAN device before transmission as completed. */
u32 media_delay;
/* The total time in microseconds that the frame spent in */
/* the WLAN device before transmission was started. */
u32 tx_queue_delay;
};
/* 3.15 */
typedef void (*wsm_tx_confirm_cb) (struct cw1200_common *priv,
struct wsm_tx_confirm *arg);
/* Note that ideology of wsm_tx struct is different against the rest of
* WSM API. wsm_hdr is /not/ a caller-adapted struct to be used as an input
* argument for WSM call, but a prepared bytestream to be sent to firmware.
* It is filled partly in cw1200_tx, partly in low-level WSM code.
* Please pay attention once again: ideology is different.
*
* Legend:
* - [in]: cw1200_tx must fill this field.
* - [wsm]: the field is filled by low-level WSM.
*/
struct wsm_tx {
/* common WSM header */
struct wsm_hdr hdr;
/* Packet identifier that meant to be used in completion. */
__le32 packet_id;
/* WSM_TRANSMIT_RATE_... */
u8 max_tx_rate;
/* WSM_QUEUE_... */
u8 queue_id;
/* True: another packet is pending on the host for transmission. */
u8 more;
/* Bit 0 = 0 - Start expiry time from first Tx attempt (default) */
/* Bit 0 = 1 - Start expiry time from receipt of Tx Request */
/* Bits 3:1 - PTA Priority */
/* Bits 6:4 - Tx Rate Retry Policy */
/* Bit 7 - Reserved */
u8 flags;
/* Should be 0. */
__le32 reserved;
/* The elapsed time in TUs, after the initial transmission */
/* of an MSDU, after which further attempts to transmit */
/* the MSDU shall be terminated. Overrides the global */
/* dot11MaxTransmitMsduLifeTime setting [optional] */
/* Device will set the default value if this is 0. */
__le32 expire_time;
/* WSM_HT_TX_... */
__le32 ht_tx_parameters;
};
/* = sizeof(generic hi hdr) + sizeof(wsm hdr) + sizeof(alignment) */
#define WSM_TX_EXTRA_HEADROOM (28)
/* 3.16 */
#define WSM_RECEIVE_IND_ID 0x0804
struct wsm_rx {
/* WSM_STATUS_... */
__le32 status;
/* Specifies the channel of the received packet. */
__le16 channel_number;
/* WSM_TRANSMIT_RATE_... */
u8 rx_rate;
/* This value is expressed in signed Q8.0 format for */
/* RSSI and unsigned Q7.1 format for RCPI. */
u8 rcpi_rssi;
/* WSM_RX_STATUS_... */
__le32 flags;
/* Payload */
u8 data[0];
} __packed;
/* = sizeof(generic hi hdr) + sizeof(wsm hdr) */
#define WSM_RX_EXTRA_HEADROOM (16)
typedef void (*wsm_rx_cb) (struct cw1200_common *priv, struct wsm_rx *arg,
struct sk_buff **skb_p);
/* 3.17 */
struct wsm_event {
/* WSM_STATUS_... */
/* [out] */ u32 id;
/* Indication parameters. */
/* For error indication, this shall be a 32-bit WSM status. */
/* For RCPI or RSSI indication, this should be an 8-bit */
/* RCPI or RSSI value. */
/* [out] */ u32 data;
};
struct cw1200_wsm_event {
struct list_head link;
struct wsm_event evt;
};
/* 3.18 - 3.22 */
/* Measurement. Skipped for now. Irrelevent. */
typedef void (*wsm_event_cb) (struct cw1200_common *priv,
struct wsm_event *arg);
/* 3.23 */
#define WSM_JOIN_REQ_ID 0x000B
#define WSM_JOIN_RESP_ID 0x040B
struct wsm_join {
/* WSM_JOIN_MODE_... */
u8 mode;
/* WSM_PHY_BAND_... */
u8 band;
/* Specifies the channel number to join. The channel */
/* number will be mapped to an actual frequency */
/* according to the band */
u16 channel_number;
/* Specifies the BSSID of the BSS or IBSS to be joined */
/* or the IBSS to be started. */
u8 bssid[6];
/* ATIM window of IBSS */
/* When ATIM window is zero the initiated IBSS does */
/* not support power saving. */
u16 atim_window;
/* WSM_JOIN_PREAMBLE_... */
u8 preamble_type;
/* Specifies if a probe request should be send with the */
/* specified SSID when joining to the network. */
u8 probe_for_join;
/* DTIM Period (In multiples of beacon interval) */
u8 dtim_period;
/* WSM_JOIN_FLAGS_... */
u8 flags;
/* Length of the SSID */
u32 ssid_len;
/* Specifies the SSID of the IBSS to join or start */
u8 ssid[32];
/* Specifies the time between TBTTs in TUs */
u32 beacon_interval;
/* A bit mask that defines the BSS basic rate set. */
u32 basic_rate_set;
};
struct wsm_join_cnf {
u32 status;
/* Minimum transmission power level in units of 0.1dBm */
u32 min_power_level;
/* Maximum transmission power level in units of 0.1dBm */
u32 max_power_level;
};
int wsm_join(struct cw1200_common *priv, struct wsm_join *arg);
/* 3.24 */
struct wsm_join_complete {
/* WSM_STATUS_... */
u32 status;
};
/* 3.25 */
#define WSM_SET_PM_REQ_ID 0x0010
#define WSM_SET_PM_RESP_ID 0x0410
struct wsm_set_pm {
/* WSM_PSM_... */
u8 mode;
/* in unit of 500us; 0 to use default */
u8 fast_psm_idle_period;
/* in unit of 500us; 0 to use default */
u8 ap_psm_change_period;
/* in unit of 500us; 0 to disable auto-pspoll */
u8 min_auto_pspoll_period;
};
int wsm_set_pm(struct cw1200_common *priv, const struct wsm_set_pm *arg);
/* 3.27 */
struct wsm_set_pm_complete {
u8 psm; /* WSM_PSM_... */
};
/* 3.28 */
#define WSM_SET_BSS_PARAMS_REQ_ID 0x0011
#define WSM_SET_BSS_PARAMS_RESP_ID 0x0411
struct wsm_set_bss_params {
/* This resets the beacon loss counters only */
u8 reset_beacon_loss;
/* The number of lost consecutive beacons after which */
/* the WLAN device should indicate the BSS-Lost event */
/* to the WLAN host driver. */
u8 beacon_lost_count;
/* The AID received during the association process. */
u16 aid;
/* The operational rate set mask */
u32 operational_rate_set;
};
int wsm_set_bss_params(struct cw1200_common *priv,
const struct wsm_set_bss_params *arg);
/* 3.30 */
#define WSM_ADD_KEY_REQ_ID 0x000C
#define WSM_ADD_KEY_RESP_ID 0x040C
struct wsm_add_key {
u8 type; /* WSM_KEY_TYPE_... */
u8 index; /* Key entry index: 0 -- WSM_KEY_MAX_INDEX */
u16 reserved;
union {
struct {
u8 peer[6]; /* MAC address of the
* peer station */
u8 reserved;
u8 keylen; /* Key length in bytes */
u8 keydata[16]; /* Key data */
} __packed wep_pairwise;
struct {
u8 keyid; /* Unique per key identifier
* (0..3) */
u8 keylen; /* Key length in bytes */
u16 reserved;
u8 keydata[16]; /* Key data */
} __packed wep_group;
struct {
u8 peer[6]; /* MAC address of the
* peer station */
u16 reserved;
u8 keydata[16]; /* TKIP key data */
u8 rx_mic_key[8]; /* Rx MIC key */
u8 tx_mic_key[8]; /* Tx MIC key */
} __packed tkip_pairwise;
struct {
u8 keydata[16]; /* TKIP key data */
u8 rx_mic_key[8]; /* Rx MIC key */
u8 keyid; /* Key ID */
u8 reserved[3];
u8 rx_seqnum[8]; /* Receive Sequence Counter */
} __packed tkip_group;
struct {
u8 peer[6]; /* MAC address of the
* peer station */
u16 reserved;
u8 keydata[16]; /* AES key data */
} __packed aes_pairwise;
struct {
u8 keydata[16]; /* AES key data */
u8 keyid; /* Key ID */
u8 reserved[3];
u8 rx_seqnum[8]; /* Receive Sequence Counter */
} __packed aes_group;
struct {
u8 peer[6]; /* MAC address of the
* peer station */
u8 keyid; /* Key ID */
u8 reserved;
u8 keydata[16]; /* WAPI key data */
u8 mic_key[16]; /* MIC key data */
} __packed wapi_pairwise;
struct {
u8 keydata[16]; /* WAPI key data */
u8 mic_key[16]; /* MIC key data */
u8 keyid; /* Key ID */
u8 reserved[3];
} __packed wapi_group;
} __packed;
} __packed;
int wsm_add_key(struct cw1200_common *priv, const struct wsm_add_key *arg);
/* 3.32 */
#define WSM_REMOVE_KEY_REQ_ID 0x000D
#define WSM_REMOVE_KEY_RESP_ID 0x040D
struct wsm_remove_key {
u8 index; /* Key entry index : 0-10 */
};
int wsm_remove_key(struct cw1200_common *priv,
const struct wsm_remove_key *arg);
/* 3.34 */
struct wsm_set_tx_queue_params {
/* WSM_ACK_POLICY_... */
u8 ackPolicy;
/* Medium Time of TSPEC (in 32us units) allowed per */
/* One Second Averaging Period for this queue. */
u16 allowedMediumTime;
/* dot11MaxTransmitMsduLifetime to be used for the */
/* specified queue. */
u32 maxTransmitLifetime;
};
struct wsm_tx_queue_params {
/* NOTE: index is a linux queue id. */
struct wsm_set_tx_queue_params params[4];
};
#define WSM_TX_QUEUE_SET(queue_params, queue, ack_policy, allowed_time,\
max_life_time) \
do { \
struct wsm_set_tx_queue_params *p = &(queue_params)->params[queue]; \
p->ackPolicy = (ack_policy); \
p->allowedMediumTime = (allowed_time); \
p->maxTransmitLifetime = (max_life_time); \
} while (0)
int wsm_set_tx_queue_params(struct cw1200_common *priv,
const struct wsm_set_tx_queue_params *arg, u8 id);
/* 3.36 */
#define WSM_EDCA_PARAMS_REQ_ID 0x0013
#define WSM_EDCA_PARAMS_RESP_ID 0x0413
struct wsm_edca_queue_params {
/* CWmin (in slots) for the access class. */
__le16 cwmin;
/* CWmax (in slots) for the access class. */
__le16 cwmax;
/* AIFS (in slots) for the access class. */
__le16 aifns;
/* TX OP Limit (in microseconds) for the access class. */
__le16 txop_limit;
/* dot11MaxReceiveLifetime to be used for the specified */
/* the access class. Overrides the global */
/* dot11MaxReceiveLifetime value */
__le32 max_rx_lifetime;
} __packed;
struct wsm_edca_params {
/* NOTE: index is a linux queue id. */
struct wsm_edca_queue_params params[4];
bool uapsd_enable[4];
};
#define TXOP_UNIT 32
#define WSM_EDCA_SET(__edca, __queue, __aifs, __cw_min, __cw_max, __txop, __lifetime,\
__uapsd) \
do { \
struct wsm_edca_queue_params *p = &(__edca)->params[__queue]; \
p->cwmin = (__cw_min); \
p->cwmax = (__cw_max); \
p->aifns = (__aifs); \
p->txop_limit = ((__txop) * TXOP_UNIT); \
p->max_rx_lifetime = (__lifetime); \
(__edca)->uapsd_enable[__queue] = (__uapsd); \
} while (0)
int wsm_set_edca_params(struct cw1200_common *priv,
const struct wsm_edca_params *arg);
int wsm_set_uapsd_param(struct cw1200_common *priv,
const struct wsm_edca_params *arg);
/* 3.38 */
/* Set-System info. Skipped for now. Irrelevent. */
/* 3.40 */
#define WSM_SWITCH_CHANNEL_REQ_ID 0x0016
#define WSM_SWITCH_CHANNEL_RESP_ID 0x0416
struct wsm_switch_channel {
/* 1 - means the STA shall not transmit any further */
/* frames until the channel switch has completed */
u8 mode;
/* Number of TBTTs until channel switch occurs. */
/* 0 - indicates switch shall occur at any time */
/* 1 - occurs immediately before the next TBTT */
u8 switch_count;
/* The new channel number to switch to. */
/* Note this is defined as per section 2.7. */
u16 channel_number;
};
int wsm_switch_channel(struct cw1200_common *priv,
const struct wsm_switch_channel *arg);
typedef void (*wsm_channel_switch_cb) (struct cw1200_common *priv);
#define WSM_START_REQ_ID 0x0017
#define WSM_START_RESP_ID 0x0417
struct wsm_start {
/* WSM_START_MODE_... */
/* [in] */ u8 mode;
/* WSM_PHY_BAND_... */
/* [in] */ u8 band;
/* Channel number */
/* [in] */ u16 channel_number;
/* Client Traffic window in units of TU */
/* Valid only when mode == ..._P2P */
/* [in] */ u32 ct_window;
/* Interval between two consecutive */
/* beacon transmissions in TU. */
/* [in] */ u32 beacon_interval;
/* DTIM period in terms of beacon intervals */
/* [in] */ u8 dtim_period;
/* WSM_JOIN_PREAMBLE_... */
/* [in] */ u8 preamble;
/* The delay time (in microseconds) period */
/* before sending a probe-request. */
/* [in] */ u8 probe_delay;
/* Length of the SSID */
/* [in] */ u8 ssid_len;
/* SSID of the BSS or P2P_GO to be started now. */
/* [in] */ u8 ssid[32];
/* The basic supported rates for the MiniAP. */
/* [in] */ u32 basic_rate_set;
};
int wsm_start(struct cw1200_common *priv, const struct wsm_start *arg);
#define WSM_BEACON_TRANSMIT_REQ_ID 0x0018
#define WSM_BEACON_TRANSMIT_RESP_ID 0x0418
struct wsm_beacon_transmit {
/* 1: enable; 0: disable */
/* [in] */ u8 enable_beaconing;
};
int wsm_beacon_transmit(struct cw1200_common *priv,
const struct wsm_beacon_transmit *arg);
int wsm_start_find(struct cw1200_common *priv);
int wsm_stop_find(struct cw1200_common *priv);
typedef void (*wsm_find_complete_cb) (struct cw1200_common *priv, u32 status);
struct wsm_suspend_resume {
/* See 3.52 */
/* Link ID */
/* [out] */ int link_id;
/* Stop sending further Tx requests down to device for this link */
/* [out] */ bool stop;
/* Transmit multicast Frames */
/* [out] */ bool multicast;
/* The AC on which Tx to be suspended /resumed. */
/* This is applicable only for U-APSD */
/* WSM_QUEUE_... */
/* [out] */ int queue;
};
typedef void (*wsm_suspend_resume_cb) (struct cw1200_common *priv,
struct wsm_suspend_resume *arg);
/* 3.54 Update-IE request. */
struct wsm_update_ie {
/* WSM_UPDATE_IE_... */
/* [in] */ u16 what;
/* [in] */ u16 count;
/* [in] */ u8 *ies;
/* [in] */ size_t length;
};
int wsm_update_ie(struct cw1200_common *priv,
const struct wsm_update_ie *arg);
/* 3.56 */
struct wsm_map_link {
/* MAC address of the remote device */
/* [in] */ u8 mac_addr[6];
/* [in] */ u8 link_id;
};
int wsm_map_link(struct cw1200_common *priv, const struct wsm_map_link *arg);
/* ******************************************************************** */
/* MIB shortcats */
static inline int wsm_set_output_power(struct cw1200_common *priv,
int power_level)
{
__le32 val = __cpu_to_le32(power_level);
return wsm_write_mib(priv, WSM_MIB_ID_DOT11_CURRENT_TX_POWER_LEVEL,
&val, sizeof(val));
}
static inline int wsm_set_beacon_wakeup_period(struct cw1200_common *priv,
unsigned dtim_interval,
unsigned listen_interval)
{
struct {
u8 numBeaconPeriods;
u8 reserved;
__le16 listenInterval;
} val = {
dtim_interval, 0, __cpu_to_le16(listen_interval)
};
if (dtim_interval > 0xFF || listen_interval > 0xFFFF)
return -EINVAL;
else
return wsm_write_mib(priv, WSM_MIB_ID_BEACON_WAKEUP_PERIOD,
&val, sizeof(val));
}
struct wsm_rcpi_rssi_threshold {
u8 rssiRcpiMode; /* WSM_RCPI_RSSI_... */
u8 lowerThreshold;
u8 upperThreshold;
u8 rollingAverageCount;
};
static inline int wsm_set_rcpi_rssi_threshold(struct cw1200_common *priv,
struct wsm_rcpi_rssi_threshold *arg)
{
return wsm_write_mib(priv, WSM_MIB_ID_RCPI_RSSI_THRESHOLD, arg,
sizeof(*arg));
}
struct wsm_mib_counters_table {
__le32 plcp_errors;
__le32 fcs_errors;
__le32 tx_packets;
__le32 rx_packets;
__le32 rx_packet_errors;
__le32 rx_decryption_failures;
__le32 rx_mic_failures;
__le32 rx_no_key_failures;
__le32 tx_multicast_frames;
__le32 tx_frames_success;
__le32 tx_frame_failures;
__le32 tx_frames_retried;
__le32 tx_frames_multi_retried;
__le32 rx_frame_duplicates;
__le32 rts_success;
__le32 rts_failures;
__le32 ack_failures;
__le32 rx_multicast_frames;
__le32 rx_frames_success;
__le32 rx_cmac_icv_errors;
__le32 rx_cmac_replays;
__le32 rx_mgmt_ccmp_replays;
} __packed;
static inline int wsm_get_counters_table(struct cw1200_common *priv,
struct wsm_mib_counters_table *arg)
{
return wsm_read_mib(priv, WSM_MIB_ID_COUNTERS_TABLE,
arg, sizeof(*arg));
}
static inline int wsm_get_station_id(struct cw1200_common *priv, u8 *mac)
{
return wsm_read_mib(priv, WSM_MIB_ID_DOT11_STATION_ID, mac, ETH_ALEN);
}
struct wsm_rx_filter {
bool promiscuous;
bool bssid;
bool fcs;
bool probeResponder;
};
static inline int wsm_set_rx_filter(struct cw1200_common *priv,
const struct wsm_rx_filter *arg)
{
__le32 val = 0;
if (arg->promiscuous)
val |= __cpu_to_le32(BIT(0));
if (arg->bssid)
val |= __cpu_to_le32(BIT(1));
if (arg->fcs)
val |= __cpu_to_le32(BIT(2));
if (arg->probeResponder)
val |= __cpu_to_le32(BIT(3));
return wsm_write_mib(priv, WSM_MIB_ID_RX_FILTER, &val, sizeof(val));
}
int wsm_set_probe_responder(struct cw1200_common *priv, bool enable);
#define WSM_BEACON_FILTER_IE_HAS_CHANGED BIT(0)
#define WSM_BEACON_FILTER_IE_NO_LONGER_PRESENT BIT(1)
#define WSM_BEACON_FILTER_IE_HAS_APPEARED BIT(2)
struct wsm_beacon_filter_table_entry {
u8 ie_id;
u8 flags;
u8 oui[3];
u8 match_data[3];
} __packed;
struct wsm_mib_beacon_filter_table {
__le32 num;
struct wsm_beacon_filter_table_entry entry[10];
} __packed;
static inline int wsm_set_beacon_filter_table(struct cw1200_common *priv,
struct wsm_mib_beacon_filter_table *ft)
{
size_t size = __le32_to_cpu(ft->num) *
sizeof(struct wsm_beacon_filter_table_entry) +
sizeof(__le32);
return wsm_write_mib(priv, WSM_MIB_ID_BEACON_FILTER_TABLE, ft, size);
}
#define WSM_BEACON_FILTER_ENABLE BIT(0) /* Enable/disable beacon filtering */
#define WSM_BEACON_FILTER_AUTO_ERP BIT(1) /* If 1 FW will handle ERP IE changes internally */
struct wsm_beacon_filter_control {
int enabled;
int bcn_count;
};
static inline int wsm_beacon_filter_control(struct cw1200_common *priv,
struct wsm_beacon_filter_control *arg)
{
struct {
__le32 enabled;
__le32 bcn_count;
} val;
val.enabled = __cpu_to_le32(arg->enabled);
val.bcn_count = __cpu_to_le32(arg->bcn_count);
return wsm_write_mib(priv, WSM_MIB_ID_BEACON_FILTER_ENABLE, &val,
sizeof(val));
}
enum wsm_power_mode {
wsm_power_mode_active = 0,
wsm_power_mode_doze = 1,
wsm_power_mode_quiescent = 2,
};
struct wsm_operational_mode {
enum wsm_power_mode power_mode;
int disable_more_flag_usage;
int perform_ant_diversity;
};
static inline int wsm_set_operational_mode(struct cw1200_common *priv,
const struct wsm_operational_mode *arg)
{
u8 val = arg->power_mode;
if (arg->disable_more_flag_usage)
val |= BIT(4);
if (arg->perform_ant_diversity)
val |= BIT(5);
return wsm_write_mib(priv, WSM_MIB_ID_OPERATIONAL_POWER_MODE, &val,
sizeof(val));
}
struct wsm_template_frame {
u8 frame_type;
u8 rate;
struct sk_buff *skb;
};
static inline int wsm_set_template_frame(struct cw1200_common *priv,
struct wsm_template_frame *arg)
{
int ret;
u8 *p = skb_push(arg->skb, 4);
p[0] = arg->frame_type;
p[1] = arg->rate;
((u16 *)p)[1] = __cpu_to_le16(arg->skb->len - 4);
ret = wsm_write_mib(priv, WSM_MIB_ID_TEMPLATE_FRAME, p, arg->skb->len);
skb_pull(arg->skb, 4);
return ret;
}
struct wsm_protected_mgmt_policy {
bool protectedMgmtEnable;
bool unprotectedMgmtFramesAllowed;
bool encryptionForAuthFrame;
};
static inline int wsm_set_protected_mgmt_policy(struct cw1200_common *priv,
struct wsm_protected_mgmt_policy *arg)
{
__le32 val = 0;
int ret;
if (arg->protectedMgmtEnable)
val |= __cpu_to_le32(BIT(0));
if (arg->unprotectedMgmtFramesAllowed)
val |= __cpu_to_le32(BIT(1));
if (arg->encryptionForAuthFrame)
val |= __cpu_to_le32(BIT(2));
ret = wsm_write_mib(priv, WSM_MIB_ID_PROTECTED_MGMT_POLICY,
&val, sizeof(val));
return ret;
}
struct wsm_mib_block_ack_policy {
u8 tx_tid;
u8 reserved1;
u8 rx_tid;
u8 reserved2;
} __packed;
static inline int wsm_set_block_ack_policy(struct cw1200_common *priv,
u8 tx_tid_policy,
u8 rx_tid_policy)
{
struct wsm_mib_block_ack_policy val = {
.tx_tid = tx_tid_policy,
.rx_tid = rx_tid_policy,
};
return wsm_write_mib(priv, WSM_MIB_ID_BLOCK_ACK_POLICY, &val,
sizeof(val));
}
struct wsm_mib_association_mode {
u8 flags; /* WSM_ASSOCIATION_MODE_... */
u8 preamble; /* WSM_JOIN_PREAMBLE_... */
u8 greenfield; /* 1 for greenfield */
u8 mpdu_start_spacing;
__le32 basic_rate_set;
} __packed;
static inline int wsm_set_association_mode(struct cw1200_common *priv,
struct wsm_mib_association_mode *arg)
{
return wsm_write_mib(priv, WSM_MIB_ID_SET_ASSOCIATION_MODE, arg,
sizeof(*arg));
}
#define WSM_TX_RATE_POLICY_FLAG_TERMINATE_WHEN_FINISHED BIT(2)
#define WSM_TX_RATE_POLICY_FLAG_COUNT_INITIAL_TRANSMIT BIT(3)
struct wsm_tx_rate_retry_policy {
u8 index;
u8 short_retries;
u8 long_retries;
/* BIT(2) - Terminate retries when Tx rate retry policy
* finishes.
* BIT(3) - Count initial frame transmission as part of
* rate retry counting but not as a retry
* attempt */
u8 flags;
u8 rate_recoveries;
u8 reserved[3];
__le32 rate_count_indices[3];
} __packed;
struct wsm_set_tx_rate_retry_policy {
u8 num;
u8 reserved[3];
struct wsm_tx_rate_retry_policy tbl[8];
} __packed;
static inline int wsm_set_tx_rate_retry_policy(struct cw1200_common *priv,
struct wsm_set_tx_rate_retry_policy *arg)
{
size_t size = 4 + arg->num * sizeof(struct wsm_tx_rate_retry_policy);
return wsm_write_mib(priv, WSM_MIB_ID_SET_TX_RATE_RETRY_POLICY, arg,
size);
}
/* 4.32 SetEtherTypeDataFrameFilter */
struct wsm_ether_type_filter_hdr {
u8 num; /* Up to WSM_MAX_FILTER_ELEMENTS */
u8 reserved[3];
} __packed;
struct wsm_ether_type_filter {
u8 action; /* WSM_FILTER_ACTION_XXX */
u8 reserved;
__le16 type; /* Type of ethernet frame */
} __packed;
static inline int wsm_set_ether_type_filter(struct cw1200_common *priv,
struct wsm_ether_type_filter_hdr *arg)
{
size_t size = sizeof(struct wsm_ether_type_filter_hdr) +
arg->num * sizeof(struct wsm_ether_type_filter);
return wsm_write_mib(priv, WSM_MIB_ID_SET_ETHERTYPE_DATAFRAME_FILTER,
arg, size);
}
/* 4.33 SetUDPPortDataFrameFilter */
struct wsm_udp_port_filter_hdr {
u8 num; /* Up to WSM_MAX_FILTER_ELEMENTS */
u8 reserved[3];
} __packed;
struct wsm_udp_port_filter {
u8 action; /* WSM_FILTER_ACTION_XXX */
u8 type; /* WSM_FILTER_PORT_TYPE_XXX */
__le16 port; /* Port number */
} __packed;
static inline int wsm_set_udp_port_filter(struct cw1200_common *priv,
struct wsm_udp_port_filter_hdr *arg)
{
size_t size = sizeof(struct wsm_udp_port_filter_hdr) +
arg->num * sizeof(struct wsm_udp_port_filter);
return wsm_write_mib(priv, WSM_MIB_ID_SET_UDPPORT_DATAFRAME_FILTER,
arg, size);
}
/* Undocumented MIBs: */
/* 4.35 P2PDeviceInfo */
#define D11_MAX_SSID_LEN (32)
struct wsm_p2p_device_type {
__le16 categoryId;
u8 oui[4];
__le16 subCategoryId;
} __packed;
struct wsm_p2p_device_info {
struct wsm_p2p_device_type primaryDevice;
u8 reserved1[3];
u8 devNameSize;
u8 localDevName[D11_MAX_SSID_LEN];
u8 reserved2[3];
u8 numSecDevSupported;
struct wsm_p2p_device_type secondaryDevices[0];
} __packed;
/* 4.36 SetWCDMABand - WO */
struct wsm_cdma_band {
u8 WCDMA_Band;
u8 reserved[3];
} __packed;
/* 4.37 GroupTxSequenceCounter - RO */
struct wsm_group_tx_seq {
__le32 bits_47_16;
__le16 bits_15_00;
__le16 reserved;
} __packed;
/* 4.39 SetHtProtection - WO */
#define WSM_DUAL_CTS_PROT_ENB (1 << 0)
#define WSM_NON_GREENFIELD_STA_PRESENT (1 << 1)
#define WSM_HT_PROT_MODE__NO_PROT (0 << 2)
#define WSM_HT_PROT_MODE__NON_MEMBER (1 << 2)
#define WSM_HT_PROT_MODE__20_MHZ (2 << 2)
#define WSM_HT_PROT_MODE__NON_HT_MIXED (3 << 2)
#define WSM_LSIG_TXOP_PROT_FULL (1 << 4)
#define WSM_LARGE_L_LENGTH_PROT (1 << 5)
struct wsm_ht_protection {
__le32 flags;
} __packed;
/* 4.40 GPIO Command - R/W */
#define WSM_GPIO_COMMAND_SETUP 0
#define WSM_GPIO_COMMAND_READ 1
#define WSM_GPIO_COMMAND_WRITE 2
#define WSM_GPIO_COMMAND_RESET 3
#define WSM_GPIO_ALL_PINS 0xFF
struct wsm_gpio_command {
u8 GPIO_Command;
u8 pin;
__le16 config;
} __packed;
/* 4.41 TSFCounter - RO */
struct wsm_tsf_counter {
__le64 TSF_Counter;
} __packed;
/* 4.43 Keep alive period */
struct wsm_keep_alive_period {
__le16 keepAlivePeriod;
u8 reserved[2];
} __packed;
static inline int wsm_keep_alive_period(struct cw1200_common *priv,
int period)
{
struct wsm_keep_alive_period arg = {
.keepAlivePeriod = __cpu_to_le16(period),
};
return wsm_write_mib(priv, WSM_MIB_ID_KEEP_ALIVE_PERIOD,
&arg, sizeof(arg));
};
/* BSSID filtering */
struct wsm_set_bssid_filtering {
u8 filter;
u8 reserved[3];
} __packed;
static inline int wsm_set_bssid_filtering(struct cw1200_common *priv,
bool enabled)
{
struct wsm_set_bssid_filtering arg = {
.filter = !enabled,
};
return wsm_write_mib(priv, WSM_MIB_ID_DISABLE_BSSID_FILTER,
&arg, sizeof(arg));
}
/* Multicast filtering - 4.5 */
struct wsm_mib_multicast_filter {
__le32 enable;
__le32 num_addrs;
u8 macaddrs[WSM_MAX_GRP_ADDRTABLE_ENTRIES][ETH_ALEN];
} __packed;
static inline int wsm_set_multicast_filter(struct cw1200_common *priv,
struct wsm_mib_multicast_filter *fp)
{
return wsm_write_mib(priv, WSM_MIB_ID_DOT11_GROUP_ADDRESSES_TABLE,
fp, sizeof(*fp));
}
/* ARP IPv4 filtering - 4.10 */
struct wsm_mib_arp_ipv4_filter {
__le32 enable;
__be32 ipv4addrs[WSM_MAX_ARP_IP_ADDRTABLE_ENTRIES];
} __packed;
static inline int wsm_set_arp_ipv4_filter(struct cw1200_common *priv,
struct wsm_mib_arp_ipv4_filter *fp)
{
return wsm_write_mib(priv, WSM_MIB_ID_ARP_IP_ADDRESSES_TABLE,
fp, sizeof(*fp));
}
/* P2P Power Save Mode Info - 4.31 */
struct wsm_p2p_ps_modeinfo {
u8 oppPsCTWindow;
u8 count;
u8 reserved;
u8 dtimCount;
__le32 duration;
__le32 interval;
__le32 startTime;
} __packed;
static inline int wsm_set_p2p_ps_modeinfo(struct cw1200_common *priv,
struct wsm_p2p_ps_modeinfo *mi)
{
return wsm_write_mib(priv, WSM_MIB_ID_P2P_PS_MODE_INFO,
mi, sizeof(*mi));
}
static inline int wsm_get_p2p_ps_modeinfo(struct cw1200_common *priv,
struct wsm_p2p_ps_modeinfo *mi)
{
return wsm_read_mib(priv, WSM_MIB_ID_P2P_PS_MODE_INFO,
mi, sizeof(*mi));
}
/* UseMultiTxConfMessage */
static inline int wsm_use_multi_tx_conf(struct cw1200_common *priv,
bool enabled)
{
__le32 arg = enabled ? __cpu_to_le32(1) : 0;
return wsm_write_mib(priv, WSM_MIB_USE_MULTI_TX_CONF,
&arg, sizeof(arg));
}
/* 4.26 SetUpasdInformation */
struct wsm_uapsd_info {
__le16 uapsd_flags;
__le16 min_auto_trigger_interval;
__le16 max_auto_trigger_interval;
__le16 auto_trigger_step;
};
static inline int wsm_set_uapsd_info(struct cw1200_common *priv,
struct wsm_uapsd_info *arg)
{
return wsm_write_mib(priv, WSM_MIB_ID_SET_UAPSD_INFORMATION,
arg, sizeof(*arg));
}
/* 4.22 OverrideInternalTxRate */
struct wsm_override_internal_txrate {
u8 internalTxRate;
u8 nonErpInternalTxRate;
u8 reserved[2];
} __packed;
static inline int wsm_set_override_internal_txrate(struct cw1200_common *priv,
struct wsm_override_internal_txrate *arg)
{
return wsm_write_mib(priv, WSM_MIB_ID_OVERRIDE_INTERNAL_TX_RATE,
arg, sizeof(*arg));
}
/* ******************************************************************** */
/* WSM TX port control */
void wsm_lock_tx(struct cw1200_common *priv);
void wsm_lock_tx_async(struct cw1200_common *priv);
bool wsm_flush_tx(struct cw1200_common *priv);
void wsm_unlock_tx(struct cw1200_common *priv);
/* ******************************************************************** */
/* WSM / BH API */
int wsm_handle_exception(struct cw1200_common *priv, u8 *data, size_t len);
int wsm_handle_rx(struct cw1200_common *priv, u16 id, struct wsm_hdr *wsm,
struct sk_buff **skb_p);
/* ******************************************************************** */
/* wsm_buf API */
struct wsm_buf {
u8 *begin;
u8 *data;
u8 *end;
};
void wsm_buf_init(struct wsm_buf *buf);
void wsm_buf_deinit(struct wsm_buf *buf);
/* ******************************************************************** */
/* wsm_cmd API */
struct wsm_cmd {
spinlock_t lock; /* Protect structure from multiple access */
int done;
u8 *ptr;
size_t len;
void *arg;
int ret;
u16 cmd;
};
/* ******************************************************************** */
/* WSM TX buffer access */
int wsm_get_tx(struct cw1200_common *priv, u8 **data,
size_t *tx_len, int *burst);
void wsm_txed(struct cw1200_common *priv, u8 *data);
/* ******************************************************************** */
/* Queue mapping: WSM <---> linux */
/* Linux: VO VI BE BK */
/* WSM: BE BK VI VO */
static inline u8 wsm_queue_id_to_linux(u8 queue_id)
{
static const u8 queue_mapping[] = {
2, 3, 1, 0
};
return queue_mapping[queue_id];
}
static inline u8 wsm_queue_id_to_wsm(u8 queue_id)
{
static const u8 queue_mapping[] = {
3, 2, 0, 1
};
return queue_mapping[queue_id];
}
#ifdef CONFIG_CW1200_ETF
int wsm_raw_cmd(struct cw1200_common *priv, u8 *data, size_t len);
#endif
#endif /* CW1200_HWIO_H_INCLUDED */
/*
* Copyright (C) ST-Ericsson SA 2011
*
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@stericsson.com>
* License terms: GNU General Public License (GPL) version 2
*/
#ifndef CW1200_PLAT_H_INCLUDED
#define CW1200_PLAT_H_INCLUDED
struct cw1200_platform_data_spi {
u8 spi_bits_per_word; /* REQUIRED */
u16 ref_clk; /* REQUIRED (in KHz) */
/* All others are optional */
bool have_5ghz;
const struct resource *reset; /* GPIO to RSTn signal */
const struct resource *powerup; /* GPIO to POWERUP signal */
int (*power_ctrl)(const struct cw1200_platform_data_spi *pdata,
bool enable); /* Control 3v3 / 1v8 supply */
int (*clk_ctrl)(const struct cw1200_platform_data_spi *pdata,
bool enable); /* Control CLK32K */
const u8 *macaddr; /* if NULL, use cw1200_mac_template module parameter */
const char *sdd_file; /* if NULL, will use default for detected hw type */
};
struct cw1200_platform_data_sdio {
u16 ref_clk; /* REQUIRED (in KHz) */
/* All others are optional */
const struct resource *irq; /* if using GPIO for IRQ */
bool have_5ghz;
bool no_nptb; /* SDIO hardware does not support non-power-of-2-blocksizes */
const struct resource *reset; /* GPIO to RSTn signal */
const struct resource *powerup; /* GPIO to POWERUP signal */
int (*power_ctrl)(const struct cw1200_platform_data_sdio *pdata,
bool enable); /* Control 3v3 / 1v8 supply */
int (*clk_ctrl)(const struct cw1200_platform_data_sdio *pdata,
bool enable); /* Control CLK32K */
const u8 *macaddr; /* if NULL, use cw1200_mac_template module parameter */
const char *sdd_file; /* if NULL, will use default for detected hw type */
};
const void *cw1200_get_platform_data(void);
#endif /* CW1200_PLAT_H_INCLUDED */
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