Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0-only
config WFX
tristate "Silicon Labs wireless chips WF200 and further"
depends on MAC80211
depends on MMC || !MMC # do not allow WFX=y if MMC=m
depends on (SPI || MMC)
help
This is a driver for Silicons Labs WFxxx series (WF200 and further)
chipsets. This chip can be found on SPI or SDIO buses.
Silabs does not use a reliable SDIO vendor ID. So, to avoid conflicts,
the driver won't probe the device if it is not also declared in the
Device Tree.
@@ -0,0 +1,25 @@
# SPDX-License-Identifier: GPL-2.0-only
# Necessary for CREATE_TRACE_POINTS
CFLAGS_debug.o = -I$(src)
wfx-y := \
bh.o \
hwio.o \
fwio.o \
hif_tx_mib.o \
hif_tx.o \
hif_rx.o \
queue.o \
data_tx.o \
data_rx.o \
scan.o \
sta.o \
key.o \
main.o \
debug.o
wfx-$(CONFIG_SPI) += bus_spi.o
# When CONFIG_MMC == m, append to 'wfx-y' (and not to 'wfx-m')
wfx-$(subst m,y,$(CONFIG_MMC)) += bus_sdio.o
obj-$(CONFIG_WFX) += wfx.o
@@ -0,0 +1,324 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Interrupt bottom half (BH).
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#include <linux/gpio/consumer.h>
#include <net/mac80211.h>
#include "bh.h"
#include "wfx.h"
#include "hwio.h"
#include "traces.h"
#include "hif_rx.h"
#include "hif_api_cmd.h"
static void device_wakeup(struct wfx_dev *wdev)
{
int max_retry = 3;
if (!wdev->pdata.gpio_wakeup)
return;
if (gpiod_get_value_cansleep(wdev->pdata.gpio_wakeup) > 0)
return;
if (wfx_api_older_than(wdev, 1, 4)) {
gpiod_set_value_cansleep(wdev->pdata.gpio_wakeup, 1);
if (!completion_done(&wdev->hif.ctrl_ready))
usleep_range(2000, 2500);
return;
}
for (;;) {
gpiod_set_value_cansleep(wdev->pdata.gpio_wakeup, 1);
/* completion.h does not provide any function to wait completion without consume it
* (a kind of wait_for_completion_done_timeout()). So we have to emulate it.
*/
if (wait_for_completion_timeout(&wdev->hif.ctrl_ready, msecs_to_jiffies(2))) {
complete(&wdev->hif.ctrl_ready);
return;
} else if (max_retry-- > 0) {
/* Older firmwares have a race in sleep/wake-up process. Redo the process
* is sufficient to unfreeze the chip.
*/
dev_err(wdev->dev, "timeout while wake up chip\n");
gpiod_set_value_cansleep(wdev->pdata.gpio_wakeup, 0);
usleep_range(2000, 2500);
} else {
dev_err(wdev->dev, "max wake-up retries reached\n");
return;
}
}
}
static void device_release(struct wfx_dev *wdev)
{
if (!wdev->pdata.gpio_wakeup)
return;
gpiod_set_value_cansleep(wdev->pdata.gpio_wakeup, 0);
}
static int rx_helper(struct wfx_dev *wdev, size_t read_len, int *is_cnf)
{
struct sk_buff *skb;
struct wfx_hif_msg *hif;
size_t alloc_len;
size_t computed_len;
int release_count;
int piggyback = 0;
WARN(read_len > round_down(0xFFF, 2) * sizeof(u16), "request exceed the chip capability");
/* Add 2 to take into account piggyback size */
alloc_len = wdev->hwbus_ops->align_size(wdev->hwbus_priv, read_len + 2);
skb = dev_alloc_skb(alloc_len);
if (!skb)
return -ENOMEM;
if (wfx_data_read(wdev, skb->data, alloc_len))
goto err;
piggyback = le16_to_cpup((__le16 *)(skb->data + alloc_len - 2));
_trace_piggyback(piggyback, false);
hif = (struct wfx_hif_msg *)skb->data;
WARN(hif->encrypted & 0x3, "encryption is unsupported");
if (WARN(read_len < sizeof(struct wfx_hif_msg), "corrupted read"))
goto err;
computed_len = le16_to_cpu(hif->len);
computed_len = round_up(computed_len, 2);
if (computed_len != read_len) {
dev_err(wdev->dev, "inconsistent message length: %zu != %zu\n",
computed_len, read_len);
print_hex_dump(KERN_INFO, "hif: ", DUMP_PREFIX_OFFSET, 16, 1,
hif, read_len, true);
goto err;
}
if (!(hif->id & HIF_ID_IS_INDICATION)) {
(*is_cnf)++;
if (hif->id == HIF_CNF_ID_MULTI_TRANSMIT)
release_count =
((struct wfx_hif_cnf_multi_transmit *)hif->body)->num_tx_confs;
else
release_count = 1;
WARN(wdev->hif.tx_buffers_used < release_count, "corrupted buffer counter");
wdev->hif.tx_buffers_used -= release_count;
}
_trace_hif_recv(hif, wdev->hif.tx_buffers_used);
if (hif->id != HIF_IND_ID_EXCEPTION && hif->id != HIF_IND_ID_ERROR) {
if (hif->seqnum != wdev->hif.rx_seqnum)
dev_warn(wdev->dev, "wrong message sequence: %d != %d\n",
hif->seqnum, wdev->hif.rx_seqnum);
wdev->hif.rx_seqnum = (hif->seqnum + 1) % (HIF_COUNTER_MAX + 1);
}
skb_put(skb, le16_to_cpu(hif->len));
/* wfx_handle_rx takes care on SKB livetime */
wfx_handle_rx(wdev, skb);
if (!wdev->hif.tx_buffers_used)
wake_up(&wdev->hif.tx_buffers_empty);
return piggyback;
err:
if (skb)
dev_kfree_skb(skb);
return -EIO;
}
static int bh_work_rx(struct wfx_dev *wdev, int max_msg, int *num_cnf)
{
size_t len;
int i;
int ctrl_reg, piggyback;
piggyback = 0;
for (i = 0; i < max_msg; i++) {
if (piggyback & CTRL_NEXT_LEN_MASK)
ctrl_reg = piggyback;
else if (try_wait_for_completion(&wdev->hif.ctrl_ready))
ctrl_reg = atomic_xchg(&wdev->hif.ctrl_reg, 0);
else
ctrl_reg = 0;
if (!(ctrl_reg & CTRL_NEXT_LEN_MASK))
return i;
/* ctrl_reg units are 16bits words */
len = (ctrl_reg & CTRL_NEXT_LEN_MASK) * 2;
piggyback = rx_helper(wdev, len, num_cnf);
if (piggyback < 0)
return i;
if (!(piggyback & CTRL_WLAN_READY))
dev_err(wdev->dev, "unexpected piggyback value: ready bit not set: %04x\n",
piggyback);
}
if (piggyback & CTRL_NEXT_LEN_MASK) {
ctrl_reg = atomic_xchg(&wdev->hif.ctrl_reg, piggyback);
complete(&wdev->hif.ctrl_ready);
if (ctrl_reg)
dev_err(wdev->dev, "unexpected IRQ happened: %04x/%04x\n",
ctrl_reg, piggyback);
}
return i;
}
static void tx_helper(struct wfx_dev *wdev, struct wfx_hif_msg *hif)
{
int ret;
void *data;
bool is_encrypted = false;
size_t len = le16_to_cpu(hif->len);
WARN(len < sizeof(*hif), "try to send corrupted data");
hif->seqnum = wdev->hif.tx_seqnum;
wdev->hif.tx_seqnum = (wdev->hif.tx_seqnum + 1) % (HIF_COUNTER_MAX + 1);
data = hif;
WARN(len > le16_to_cpu(wdev->hw_caps.size_inp_ch_buf),
"request exceed the chip capability: %zu > %d\n",
len, le16_to_cpu(wdev->hw_caps.size_inp_ch_buf));
len = wdev->hwbus_ops->align_size(wdev->hwbus_priv, len);
ret = wfx_data_write(wdev, data, len);
if (ret)
goto end;
wdev->hif.tx_buffers_used++;
_trace_hif_send(hif, wdev->hif.tx_buffers_used);
end:
if (is_encrypted)
kfree(data);
}
static int bh_work_tx(struct wfx_dev *wdev, int max_msg)
{
struct wfx_hif_msg *hif;
int i;
for (i = 0; i < max_msg; i++) {
hif = NULL;
if (wdev->hif.tx_buffers_used < le16_to_cpu(wdev->hw_caps.num_inp_ch_bufs)) {
if (try_wait_for_completion(&wdev->hif_cmd.ready)) {
WARN(!mutex_is_locked(&wdev->hif_cmd.lock), "data locking error");
hif = wdev->hif_cmd.buf_send;
} else {
hif = wfx_tx_queues_get(wdev);
}
}
if (!hif)
return i;
tx_helper(wdev, hif);
}
return i;
}
/* In SDIO mode, it is necessary to make an access to a register to acknowledge last received
* message. It could be possible to restrict this acknowledge to SDIO mode and only if last
* operation was rx.
*/
static void ack_sdio_data(struct wfx_dev *wdev)
{
u32 cfg_reg;
wfx_config_reg_read(wdev, &cfg_reg);
if (cfg_reg & 0xFF) {
dev_warn(wdev->dev, "chip reports errors: %02x\n", cfg_reg & 0xFF);
wfx_config_reg_write_bits(wdev, 0xFF, 0x00);
}
}
static void bh_work(struct work_struct *work)
{
struct wfx_dev *wdev = container_of(work, struct wfx_dev, hif.bh);
int stats_req = 0, stats_cnf = 0, stats_ind = 0;
bool release_chip = false, last_op_is_rx = false;
int num_tx, num_rx;
device_wakeup(wdev);
do {
num_tx = bh_work_tx(wdev, 32);
stats_req += num_tx;
if (num_tx)
last_op_is_rx = false;
num_rx = bh_work_rx(wdev, 32, &stats_cnf);
stats_ind += num_rx;
if (num_rx)
last_op_is_rx = true;
} while (num_rx || num_tx);
stats_ind -= stats_cnf;
if (last_op_is_rx)
ack_sdio_data(wdev);
if (!wdev->hif.tx_buffers_used && !work_pending(work)) {
device_release(wdev);
release_chip = true;
}
_trace_bh_stats(stats_ind, stats_req, stats_cnf, wdev->hif.tx_buffers_used, release_chip);
}
/* An IRQ from chip did occur */
void wfx_bh_request_rx(struct wfx_dev *wdev)
{
u32 cur, prev;
wfx_control_reg_read(wdev, &cur);
prev = atomic_xchg(&wdev->hif.ctrl_reg, cur);
complete(&wdev->hif.ctrl_ready);
queue_work(wdev->bh_wq, &wdev->hif.bh);
if (!(cur & CTRL_NEXT_LEN_MASK))
dev_err(wdev->dev, "unexpected control register value: length field is 0: %04x\n",
cur);
if (prev != 0)
dev_err(wdev->dev, "received IRQ but previous data was not (yet) read: %04x/%04x\n",
prev, cur);
}
/* Driver want to send data */
void wfx_bh_request_tx(struct wfx_dev *wdev)
{
queue_work(wdev->bh_wq, &wdev->hif.bh);
}
/* If IRQ is not available, this function allow to manually poll the control register and simulate
* an IRQ ahen an event happened.
*
* Note that the device has a bug: If an IRQ raise while host read control register, the IRQ is
* lost. So, use this function carefully (only duing device initialisation).
*/
void wfx_bh_poll_irq(struct wfx_dev *wdev)
{
ktime_t now, start;
u32 reg;
WARN(!wdev->poll_irq, "unexpected IRQ polling can mask IRQ");
flush_workqueue(wdev->bh_wq);
start = ktime_get();
for (;;) {
wfx_control_reg_read(wdev, &reg);
now = ktime_get();
if (reg & 0xFFF)
break;
if (ktime_after(now, ktime_add_ms(start, 1000))) {
dev_err(wdev->dev, "time out while polling control register\n");
return;
}
udelay(200);
}
wfx_bh_request_rx(wdev);
}
void wfx_bh_register(struct wfx_dev *wdev)
{
INIT_WORK(&wdev->hif.bh, bh_work);
init_completion(&wdev->hif.ctrl_ready);
init_waitqueue_head(&wdev->hif.tx_buffers_empty);
}
void wfx_bh_unregister(struct wfx_dev *wdev)
{
flush_work(&wdev->hif.bh);
}
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Interrupt bottom half (BH).
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#ifndef WFX_BH_H
#define WFX_BH_H
#include <linux/atomic.h>
#include <linux/wait.h>
#include <linux/completion.h>
#include <linux/workqueue.h>
struct wfx_dev;
struct wfx_hif {
struct work_struct bh;
struct completion ctrl_ready;
wait_queue_head_t tx_buffers_empty;
atomic_t ctrl_reg;
int rx_seqnum;
int tx_seqnum;
int tx_buffers_used;
};
void wfx_bh_register(struct wfx_dev *wdev);
void wfx_bh_unregister(struct wfx_dev *wdev);
void wfx_bh_request_rx(struct wfx_dev *wdev);
void wfx_bh_request_tx(struct wfx_dev *wdev);
void wfx_bh_poll_irq(struct wfx_dev *wdev);
#endif
@@ -0,0 +1,37 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Common bus abstraction layer.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#ifndef WFX_BUS_H
#define WFX_BUS_H
#include <linux/mmc/sdio_func.h>
#include <linux/spi/spi.h>
#define WFX_REG_CONFIG 0x0
#define WFX_REG_CONTROL 0x1
#define WFX_REG_IN_OUT_QUEUE 0x2
#define WFX_REG_AHB_DPORT 0x3
#define WFX_REG_BASE_ADDR 0x4
#define WFX_REG_SRAM_DPORT 0x5
#define WFX_REG_SET_GEN_R_W 0x6
#define WFX_REG_FRAME_OUT 0x7
struct wfx_hwbus_ops {
int (*copy_from_io)(void *bus_priv, unsigned int addr, void *dst, size_t count);
int (*copy_to_io)(void *bus_priv, unsigned int addr, const void *src, size_t count);
int (*irq_subscribe)(void *bus_priv);
int (*irq_unsubscribe)(void *bus_priv);
void (*lock)(void *bus_priv);
void (*unlock)(void *bus_priv);
size_t (*align_size)(void *bus_priv, size_t size);
void (*set_wakeup)(void *priv, bool enabled);
};
extern struct sdio_driver wfx_sdio_driver;
extern struct spi_driver wfx_spi_driver;
#endif
@@ -0,0 +1,326 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* SDIO interface.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#include <linux/module.h>
#include <linux/mmc/sdio.h>
#include <linux/mmc/sdio_func.h>
#include <linux/mmc/card.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/align.h>
#include <linux/pm.h>
#include "bus.h"
#include "wfx.h"
#include "hwio.h"
#include "main.h"
#include "bh.h"
static const struct wfx_platform_data pdata_wf200 = {
.file_fw = "wfx/wfm_wf200",
.file_pds = "wfx/wf200.pds",
};
static const struct wfx_platform_data pdata_brd4001a = {
.file_fw = "wfx/wfm_wf200",
.file_pds = "wfx/brd4001a.pds",
};
static const struct wfx_platform_data pdata_brd8022a = {
.file_fw = "wfx/wfm_wf200",
.file_pds = "wfx/brd8022a.pds",
};
static const struct wfx_platform_data pdata_brd8023a = {
.file_fw = "wfx/wfm_wf200",
.file_pds = "wfx/brd8023a.pds",
};
struct wfx_sdio_priv {
struct sdio_func *func;
struct wfx_dev *core;
u8 buf_id_tx;
u8 buf_id_rx;
int of_irq;
};
static int wfx_sdio_copy_from_io(void *priv, unsigned int reg_id, void *dst, size_t count)
{
struct wfx_sdio_priv *bus = priv;
unsigned int sdio_addr = reg_id << 2;
int ret;
WARN(reg_id > 7, "chip only has 7 registers");
WARN(!IS_ALIGNED((uintptr_t)dst, 4), "unaligned buffer address");
WARN(!IS_ALIGNED(count, 4), "unaligned buffer size");
/* Use queue mode buffers */
if (reg_id == WFX_REG_IN_OUT_QUEUE)
sdio_addr |= (bus->buf_id_rx + 1) << 7;
ret = sdio_memcpy_fromio(bus->func, dst, sdio_addr, count);
if (!ret && reg_id == WFX_REG_IN_OUT_QUEUE)
bus->buf_id_rx = (bus->buf_id_rx + 1) % 4;
return ret;
}
static int wfx_sdio_copy_to_io(void *priv, unsigned int reg_id, const void *src, size_t count)
{
struct wfx_sdio_priv *bus = priv;
unsigned int sdio_addr = reg_id << 2;
int ret;
WARN(reg_id > 7, "chip only has 7 registers");
WARN(!IS_ALIGNED((uintptr_t)src, 4), "unaligned buffer address");
WARN(!IS_ALIGNED(count, 4), "unaligned buffer size");
/* Use queue mode buffers */
if (reg_id == WFX_REG_IN_OUT_QUEUE)
sdio_addr |= bus->buf_id_tx << 7;
/* FIXME: discards 'const' qualifier for src */
ret = sdio_memcpy_toio(bus->func, sdio_addr, (void *)src, count);
if (!ret && reg_id == WFX_REG_IN_OUT_QUEUE)
bus->buf_id_tx = (bus->buf_id_tx + 1) % 32;
return ret;
}
static void wfx_sdio_lock(void *priv)
{
struct wfx_sdio_priv *bus = priv;
sdio_claim_host(bus->func);
}
static void wfx_sdio_unlock(void *priv)
{
struct wfx_sdio_priv *bus = priv;
sdio_release_host(bus->func);
}
static void wfx_sdio_irq_handler(struct sdio_func *func)
{
struct wfx_sdio_priv *bus = sdio_get_drvdata(func);
wfx_bh_request_rx(bus->core);
}
static irqreturn_t wfx_sdio_irq_handler_ext(int irq, void *priv)
{
struct wfx_sdio_priv *bus = priv;
sdio_claim_host(bus->func);
wfx_bh_request_rx(bus->core);
sdio_release_host(bus->func);
return IRQ_HANDLED;
}
static int wfx_sdio_irq_subscribe(void *priv)
{
struct wfx_sdio_priv *bus = priv;
u32 flags;
int ret;
u8 cccr;
if (!bus->of_irq) {
sdio_claim_host(bus->func);
ret = sdio_claim_irq(bus->func, wfx_sdio_irq_handler);
sdio_release_host(bus->func);
return ret;
}
flags = irq_get_trigger_type(bus->of_irq);
if (!flags)
flags = IRQF_TRIGGER_HIGH;
flags |= IRQF_ONESHOT;
ret = devm_request_threaded_irq(&bus->func->dev, bus->of_irq, NULL,
wfx_sdio_irq_handler_ext, flags, "wfx", bus);
if (ret)
return ret;
sdio_claim_host(bus->func);
cccr = sdio_f0_readb(bus->func, SDIO_CCCR_IENx, NULL);
cccr |= BIT(0);
cccr |= BIT(bus->func->num);
sdio_f0_writeb(bus->func, cccr, SDIO_CCCR_IENx, NULL);
sdio_release_host(bus->func);
return 0;
}
static int wfx_sdio_irq_unsubscribe(void *priv)
{
struct wfx_sdio_priv *bus = priv;
int ret;
if (bus->of_irq)
devm_free_irq(&bus->func->dev, bus->of_irq, bus);
sdio_claim_host(bus->func);
ret = sdio_release_irq(bus->func);
sdio_release_host(bus->func);
return ret;
}
static size_t wfx_sdio_align_size(void *priv, size_t size)
{
struct wfx_sdio_priv *bus = priv;
return sdio_align_size(bus->func, size);
}
static void wfx_sdio_set_wakeup(void *priv, bool enabled)
{
struct wfx_sdio_priv *bus = priv;
device_set_wakeup_enable(&bus->func->dev, enabled);
}
static const struct wfx_hwbus_ops wfx_sdio_hwbus_ops = {
.copy_from_io = wfx_sdio_copy_from_io,
.copy_to_io = wfx_sdio_copy_to_io,
.irq_subscribe = wfx_sdio_irq_subscribe,
.irq_unsubscribe = wfx_sdio_irq_unsubscribe,
.lock = wfx_sdio_lock,
.unlock = wfx_sdio_unlock,
.align_size = wfx_sdio_align_size,
.set_wakeup = wfx_sdio_set_wakeup,
};
static const struct of_device_id wfx_sdio_of_match[] = {
{ .compatible = "silabs,wf200", .data = &pdata_wf200 },
{ .compatible = "silabs,brd4001a", .data = &pdata_brd4001a },
{ .compatible = "silabs,brd8022a", .data = &pdata_brd8022a },
{ .compatible = "silabs,brd8023a", .data = &pdata_brd8023a },
{ },
};
MODULE_DEVICE_TABLE(of, wfx_sdio_of_match);
static int wfx_sdio_suspend(struct device *dev)
{
struct sdio_func *func = dev_to_sdio_func(dev);
struct wfx_sdio_priv *bus = sdio_get_drvdata(func);
int ret;
if (!device_may_wakeup(dev))
return 0;
flush_work(&bus->core->hif.bh);
/* Either "wakeup-source" attribute or out-of-band IRQ is required for
* WoWLAN
*/
if (bus->of_irq) {
ret = enable_irq_wake(bus->of_irq);
if (ret)
return ret;
} else {
ret = sdio_set_host_pm_flags(func, MMC_PM_WAKE_SDIO_IRQ);
if (ret)
return ret;
}
return sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
}
static int wfx_sdio_resume(struct device *dev)
{
struct sdio_func *func = dev_to_sdio_func(dev);
struct wfx_sdio_priv *bus = sdio_get_drvdata(func);
if (!device_may_wakeup(dev))
return 0;
if (bus->of_irq)
return disable_irq_wake(bus->of_irq);
else
return 0;
}
static int wfx_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id)
{
const struct wfx_platform_data *pdata = of_device_get_match_data(&func->dev);
mmc_pm_flag_t pm_flag = sdio_get_host_pm_caps(func);
struct device_node *np = func->dev.of_node;
struct wfx_sdio_priv *bus;
int ret;
if (func->num != 1) {
dev_err(&func->dev, "SDIO function number is %d while it should always be 1 (unsupported chip?)\n",
func->num);
return -ENODEV;
}
if (!pdata) {
dev_warn(&func->dev, "no compatible device found in DT\n");
return -ENODEV;
}
bus = devm_kzalloc(&func->dev, sizeof(*bus), GFP_KERNEL);
if (!bus)
return -ENOMEM;
bus->func = func;
bus->of_irq = irq_of_parse_and_map(np, 0);
sdio_set_drvdata(func, bus);
sdio_claim_host(func);
ret = sdio_enable_func(func);
/* Block of 64 bytes is more efficient than 512B for frame sizes < 4k */
sdio_set_block_size(func, 64);
sdio_release_host(func);
if (ret)
return ret;
bus->core = wfx_init_common(&func->dev, pdata, &wfx_sdio_hwbus_ops, bus);
if (!bus->core) {
ret = -EIO;
goto sdio_release;
}
ret = wfx_probe(bus->core);
if (ret)
goto sdio_release;
if (pm_flag & MMC_PM_KEEP_POWER)
device_set_wakeup_capable(&func->dev, true);
return 0;
sdio_release:
sdio_claim_host(func);
sdio_disable_func(func);
sdio_release_host(func);
return ret;
}
static void wfx_sdio_remove(struct sdio_func *func)
{
struct wfx_sdio_priv *bus = sdio_get_drvdata(func);
wfx_release(bus->core);
sdio_claim_host(func);
sdio_disable_func(func);
sdio_release_host(func);
}
static const struct sdio_device_id wfx_sdio_ids[] = {
/* WF200 does not have official VID/PID */
{ SDIO_DEVICE(0x0000, 0x1000) },
{ },
};
MODULE_DEVICE_TABLE(sdio, wfx_sdio_ids);
static DEFINE_SIMPLE_DEV_PM_OPS(wfx_sdio_pm_ops, wfx_sdio_suspend, wfx_sdio_resume);
struct sdio_driver wfx_sdio_driver = {
.name = "wfx-sdio",
.id_table = wfx_sdio_ids,
.probe = wfx_sdio_probe,
.remove = wfx_sdio_remove,
.drv = {
.of_match_table = wfx_sdio_of_match,
.pm = &wfx_sdio_pm_ops,
}
};
@@ -0,0 +1,321 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* SPI interface.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2011, Sagrad Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/spi/spi.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/of.h>
#include <linux/pm.h>
#include "bus.h"
#include "wfx.h"
#include "hwio.h"
#include "main.h"
#include "bh.h"
#define SET_WRITE 0x7FFF /* usage: and operation */
#define SET_READ 0x8000 /* usage: or operation */
static const struct wfx_platform_data pdata_wf200 = {
.file_fw = "wfx/wfm_wf200",
.file_pds = "wfx/wf200.pds",
.use_rising_clk = true,
};
static const struct wfx_platform_data pdata_brd4001a = {
.file_fw = "wfx/wfm_wf200",
.file_pds = "wfx/brd4001a.pds",
.use_rising_clk = true,
};
static const struct wfx_platform_data pdata_brd8022a = {
.file_fw = "wfx/wfm_wf200",
.file_pds = "wfx/brd8022a.pds",
.use_rising_clk = true,
};
static const struct wfx_platform_data pdata_brd8023a = {
.file_fw = "wfx/wfm_wf200",
.file_pds = "wfx/brd8023a.pds",
.use_rising_clk = true,
};
struct wfx_spi_priv {
struct spi_device *func;
struct wfx_dev *core;
struct gpio_desc *gpio_reset;
bool need_swab;
};
/* The chip reads 16bits of data at time and place them directly into (little endian) CPU register.
* So, the chip expects bytes order to be "B1 B0 B3 B2" (while LE is "B0 B1 B2 B3" and BE is
* "B3 B2 B1 B0")
*
* A little endian host with bits_per_word == 16 should do the right job natively. The code below to
* support big endian host and commonly used SPI 8bits.
*/
static int wfx_spi_copy_from_io(void *priv, unsigned int addr, void *dst, size_t count)
{
struct wfx_spi_priv *bus = priv;
u16 regaddr = (addr << 12) | (count / 2) | SET_READ;
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,
};
u16 *dst16 = dst;
int ret, i;
WARN(count % 2, "buffer size must be a multiple of 2");
cpu_to_le16s(&regaddr);
if (bus->need_swab)
swab16s(&regaddr);
spi_message_init(&m);
spi_message_add_tail(&t_addr, &m);
spi_message_add_tail(&t_msg, &m);
ret = spi_sync(bus->func, &m);
if (bus->need_swab && addr == WFX_REG_CONFIG)
for (i = 0; i < count / 2; i++)
swab16s(&dst16[i]);
return ret;
}
static int wfx_spi_copy_to_io(void *priv, unsigned int addr, const void *src, size_t count)
{
struct wfx_spi_priv *bus = priv;
u16 regaddr = (addr << 12) | (count / 2);
/* FIXME: use a bounce buffer */
u16 *src16 = (void *)src;
int ret, i;
struct spi_message m;
struct spi_transfer t_addr = {
.tx_buf = &regaddr,
.len = sizeof(regaddr),
};
struct spi_transfer t_msg = {
.tx_buf = src,
.len = count,
};
WARN(count % 2, "buffer size must be a multiple of 2");
WARN(regaddr & SET_READ, "bad addr or size overflow");
cpu_to_le16s(&regaddr);
/* Register address and CONFIG content always use 16bit big endian
* ("BADC" order)
*/
if (bus->need_swab)
swab16s(&regaddr);
if (bus->need_swab && addr == WFX_REG_CONFIG)
for (i = 0; i < count / 2; i++)
swab16s(&src16[i]);
spi_message_init(&m);
spi_message_add_tail(&t_addr, &m);
spi_message_add_tail(&t_msg, &m);
ret = spi_sync(bus->func, &m);
if (bus->need_swab && addr == WFX_REG_CONFIG)
for (i = 0; i < count / 2; i++)
swab16s(&src16[i]);
return ret;
}
static void wfx_spi_lock(void *priv)
{
}
static void wfx_spi_unlock(void *priv)
{
}
static irqreturn_t wfx_spi_irq_handler(int irq, void *priv)
{
struct wfx_spi_priv *bus = priv;
wfx_bh_request_rx(bus->core);
return IRQ_HANDLED;
}
static int wfx_spi_irq_subscribe(void *priv)
{
struct wfx_spi_priv *bus = priv;
u32 flags;
flags = irq_get_trigger_type(bus->func->irq);
if (!flags)
flags = IRQF_TRIGGER_HIGH;
flags |= IRQF_ONESHOT;
return devm_request_threaded_irq(&bus->func->dev, bus->func->irq, NULL,
wfx_spi_irq_handler, flags, "wfx", bus);
}
static int wfx_spi_irq_unsubscribe(void *priv)
{
struct wfx_spi_priv *bus = priv;
devm_free_irq(&bus->func->dev, bus->func->irq, bus);
return 0;
}
static size_t wfx_spi_align_size(void *priv, size_t size)
{
/* Most of SPI controllers avoid DMA if buffer size is not 32bit aligned */
return ALIGN(size, 4);
}
static void wfx_spi_set_wakeup(void *priv, bool enabled)
{
struct wfx_spi_priv *bus = priv;
device_set_wakeup_enable(&bus->func->dev, enabled);
}
static const struct wfx_hwbus_ops wfx_spi_hwbus_ops = {
.copy_from_io = wfx_spi_copy_from_io,
.copy_to_io = wfx_spi_copy_to_io,
.irq_subscribe = wfx_spi_irq_subscribe,
.irq_unsubscribe = wfx_spi_irq_unsubscribe,
.lock = wfx_spi_lock,
.unlock = wfx_spi_unlock,
.align_size = wfx_spi_align_size,
.set_wakeup = wfx_spi_set_wakeup,
};
static int wfx_spi_suspend(struct device *dev)
{
struct spi_device *func = to_spi_device(dev);
struct wfx_spi_priv *bus = spi_get_drvdata(func);
if (!device_may_wakeup(dev))
return 0;
flush_work(&bus->core->hif.bh);
return enable_irq_wake(func->irq);
}
static int wfx_spi_resume(struct device *dev)
{
struct spi_device *func = to_spi_device(dev);
if (!device_may_wakeup(dev))
return 0;
return disable_irq_wake(func->irq);
}
static int wfx_spi_probe(struct spi_device *func)
{
struct wfx_platform_data *pdata;
struct wfx_spi_priv *bus;
int ret;
if (!func->bits_per_word)
func->bits_per_word = 16;
ret = spi_setup(func);
if (ret)
return ret;
pdata = (struct wfx_platform_data *)spi_get_device_id(func)->driver_data;
if (!pdata) {
dev_err(&func->dev, "unable to retrieve driver data (please report)\n");
return -ENODEV;
}
/* Trace below is also displayed by spi_setup() if compiled with DEBUG */
dev_dbg(&func->dev, "SPI params: CS=%d, mode=%d bits/word=%d speed=%d\n",
spi_get_chipselect(func, 0), func->mode, func->bits_per_word, func->max_speed_hz);
if (func->bits_per_word != 16 && func->bits_per_word != 8)
dev_warn(&func->dev, "unusual bits/word value: %d\n", func->bits_per_word);
if (func->max_speed_hz > 50000000)
dev_warn(&func->dev, "%dHz is a very high speed\n", func->max_speed_hz);
bus = devm_kzalloc(&func->dev, sizeof(*bus), GFP_KERNEL);
if (!bus)
return -ENOMEM;
bus->func = func;
if (func->bits_per_word == 8 || IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
bus->need_swab = true;
spi_set_drvdata(func, bus);
bus->gpio_reset = devm_gpiod_get_optional(&func->dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(bus->gpio_reset))
return PTR_ERR(bus->gpio_reset);
if (!bus->gpio_reset) {
dev_warn(&func->dev, "gpio reset is not defined, trying to load firmware anyway\n");
} else {
gpiod_set_consumer_name(bus->gpio_reset, "wfx reset");
gpiod_set_value_cansleep(bus->gpio_reset, 1);
usleep_range(100, 150);
gpiod_set_value_cansleep(bus->gpio_reset, 0);
usleep_range(2000, 2500);
}
bus->core = wfx_init_common(&func->dev, pdata, &wfx_spi_hwbus_ops, bus);
if (!bus->core)
return -EIO;
ret = wfx_probe(bus->core);
if (ret)
return ret;
device_set_wakeup_capable(&func->dev, true);
return 0;
}
static void wfx_spi_remove(struct spi_device *func)
{
struct wfx_spi_priv *bus = spi_get_drvdata(func);
wfx_release(bus->core);
}
/* For dynamic driver binding, kernel does not use OF to match driver. It only
* use modalias and modalias is a copy of 'compatible' DT node with vendor
* stripped.
*/
static const struct spi_device_id wfx_spi_id[] = {
{ "wf200", (kernel_ulong_t)&pdata_wf200 },
{ "brd4001a", (kernel_ulong_t)&pdata_brd4001a },
{ "brd8022a", (kernel_ulong_t)&pdata_brd8022a },
{ "brd8023a", (kernel_ulong_t)&pdata_brd8023a },
{ },
};
MODULE_DEVICE_TABLE(spi, wfx_spi_id);
#ifdef CONFIG_OF
static const struct of_device_id wfx_spi_of_match[] = {
{ .compatible = "silabs,wf200" },
{ .compatible = "silabs,brd4001a" },
{ .compatible = "silabs,brd8022a" },
{ .compatible = "silabs,brd8023a" },
{ },
};
MODULE_DEVICE_TABLE(of, wfx_spi_of_match);
#endif
static DEFINE_SIMPLE_DEV_PM_OPS(wfx_spi_pm_ops, wfx_spi_suspend, wfx_spi_resume);
struct spi_driver wfx_spi_driver = {
.id_table = wfx_spi_id,
.probe = wfx_spi_probe,
.remove = wfx_spi_remove,
.driver = {
.name = "wfx-spi",
.of_match_table = of_match_ptr(wfx_spi_of_match),
.pm = &wfx_spi_pm_ops,
},
};
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Data receiving implementation.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#include <linux/etherdevice.h>
#include <net/mac80211.h>
#include "data_rx.h"
#include "wfx.h"
#include "bh.h"
#include "sta.h"
static void wfx_rx_handle_ba(struct wfx_vif *wvif, struct ieee80211_mgmt *mgmt)
{
struct ieee80211_vif *vif = wvif_to_vif(wvif);
int params, tid;
if (wfx_api_older_than(wvif->wdev, 3, 6))
return;
switch (mgmt->u.action.action_code) {
case WLAN_ACTION_ADDBA_REQ:
params = le16_to_cpu(mgmt->u.action.addba_req.capab);
tid = (params & IEEE80211_ADDBA_PARAM_TID_MASK) >> 2;
ieee80211_start_rx_ba_session_offl(vif, mgmt->sa, tid);
break;
case WLAN_ACTION_DELBA:
params = le16_to_cpu(mgmt->u.action.delba.params);
tid = (params & IEEE80211_DELBA_PARAM_TID_MASK) >> 12;
ieee80211_stop_rx_ba_session_offl(vif, mgmt->sa, tid);
break;
}
}
void wfx_rx_cb(struct wfx_vif *wvif, const struct wfx_hif_ind_rx *arg, struct sk_buff *skb)
{
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;
memset(hdr, 0, sizeof(*hdr));
if (arg->status == HIF_STATUS_RX_FAIL_MIC)
hdr->flag |= RX_FLAG_MMIC_ERROR | RX_FLAG_IV_STRIPPED;
else if (arg->status)
goto drop;
if (skb->len < sizeof(struct ieee80211_pspoll)) {
dev_warn(wvif->wdev->dev, "malformed SDU received\n");
goto drop;
}
hdr->band = NL80211_BAND_2GHZ;
hdr->freq = ieee80211_channel_to_frequency(arg->channel_number, hdr->band);
if (arg->rxed_rate >= 14) {
hdr->encoding = RX_ENC_HT;
hdr->rate_idx = arg->rxed_rate - 14;
} else if (arg->rxed_rate >= 4) {
hdr->rate_idx = arg->rxed_rate - 2;
} else {
hdr->rate_idx = arg->rxed_rate;
}
if (!arg->rcpi_rssi) {
hdr->flag |= RX_FLAG_NO_SIGNAL_VAL;
dev_info(wvif->wdev->dev, "received frame without RSSI data\n");
}
hdr->signal = arg->rcpi_rssi / 2 - 110;
hdr->antenna = 0;
if (arg->encryp)
hdr->flag |= RX_FLAG_DECRYPTED;
/* Block ack negotiation is offloaded by the firmware. However, re-ordering must be done by
* the mac80211.
*/
if (ieee80211_is_action(frame->frame_control) &&
mgmt->u.action.category == WLAN_CATEGORY_BACK &&
skb->len > IEEE80211_MIN_ACTION_SIZE(action_code)) {
wfx_rx_handle_ba(wvif, mgmt);
goto drop;
}
ieee80211_rx_irqsafe(wvif->wdev->hw, skb);
return;
drop:
dev_kfree_skb(skb);
}
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Data receiving implementation.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#ifndef WFX_DATA_RX_H
#define WFX_DATA_RX_H
struct wfx_vif;
struct sk_buff;
struct wfx_hif_ind_rx;
void wfx_rx_cb(struct wfx_vif *wvif, const struct wfx_hif_ind_rx *arg, struct sk_buff *skb);
#endif
@@ -0,0 +1,594 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Data transmitting implementation.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#include <net/mac80211.h>
#include <linux/etherdevice.h>
#include "data_tx.h"
#include "wfx.h"
#include "bh.h"
#include "sta.h"
#include "queue.h"
#include "debug.h"
#include "traces.h"
#include "hif_tx_mib.h"
static int wfx_get_hw_rate(struct wfx_dev *wdev, const struct ieee80211_tx_rate *rate)
{
struct ieee80211_supported_band *band;
if (rate->idx < 0)
return -1;
if (rate->flags & IEEE80211_TX_RC_MCS) {
if (rate->idx > 7) {
WARN(1, "wrong rate->idx value: %d", rate->idx);
return -1;
}
return rate->idx + 14;
}
/* The device only support 2GHz, else band information should be retrieved from
* ieee80211_tx_info
*/
band = wdev->hw->wiphy->bands[NL80211_BAND_2GHZ];
if (rate->idx >= band->n_bitrates) {
WARN(1, "wrong rate->idx value: %d", rate->idx);
return -1;
}
return band->bitrates[rate->idx].hw_value;
}
/* TX policy cache implementation */
static void wfx_tx_policy_build(struct wfx_vif *wvif, struct wfx_tx_policy *policy,
struct ieee80211_tx_rate *rates)
{
struct wfx_dev *wdev = wvif->wdev;
int i, rateid;
u8 count;
WARN(rates[0].idx < 0, "invalid rate policy");
memset(policy, 0, sizeof(*policy));
for (i = 0; i < IEEE80211_TX_MAX_RATES; ++i) {
if (rates[i].idx < 0)
break;
WARN_ON(rates[i].count > 15);
rateid = wfx_get_hw_rate(wdev, &rates[i]);
/* Pack two values in each byte of policy->rates */
count = rates[i].count;
if (rateid % 2)
count <<= 4;
policy->rates[rateid / 2] |= count;
}
}
static bool wfx_tx_policy_is_equal(const struct wfx_tx_policy *a, const struct wfx_tx_policy *b)
{
return !memcmp(a->rates, b->rates, sizeof(a->rates));
}
static int wfx_tx_policy_find(struct wfx_tx_policy_cache *cache, struct wfx_tx_policy *wanted)
{
struct wfx_tx_policy *it;
list_for_each_entry(it, &cache->used, link)
if (wfx_tx_policy_is_equal(wanted, it))
return it - cache->cache;
list_for_each_entry(it, &cache->free, link)
if (wfx_tx_policy_is_equal(wanted, it))
return it - cache->cache;
return -1;
}
static void wfx_tx_policy_use(struct wfx_tx_policy_cache *cache, struct wfx_tx_policy *entry)
{
++entry->usage_count;
list_move(&entry->link, &cache->used);
}
static int wfx_tx_policy_release(struct wfx_tx_policy_cache *cache, struct wfx_tx_policy *entry)
{
int ret = --entry->usage_count;
if (!ret)
list_move(&entry->link, &cache->free);
return ret;
}
static int wfx_tx_policy_get(struct wfx_vif *wvif, struct ieee80211_tx_rate *rates, bool *renew)
{
int idx;
struct wfx_tx_policy_cache *cache = &wvif->tx_policy_cache;
struct wfx_tx_policy wanted;
struct wfx_tx_policy *entry;
wfx_tx_policy_build(wvif, &wanted, rates);
spin_lock_bh(&cache->lock);
if (list_empty(&cache->free)) {
WARN(1, "unable to get a valid Tx policy");
spin_unlock_bh(&cache->lock);
return HIF_TX_RETRY_POLICY_INVALID;
}
idx = wfx_tx_policy_find(cache, &wanted);
if (idx >= 0) {
*renew = false;
} else {
/* If policy is not found create a new one using the oldest entry in "free" list */
*renew = true;
entry = list_entry(cache->free.prev, struct wfx_tx_policy, link);
memcpy(entry->rates, wanted.rates, sizeof(entry->rates));
entry->uploaded = false;
entry->usage_count = 0;
idx = entry - cache->cache;
}
wfx_tx_policy_use(cache, &cache->cache[idx]);
if (list_empty(&cache->free))
ieee80211_stop_queues(wvif->wdev->hw);
spin_unlock_bh(&cache->lock);
return idx;
}
static void wfx_tx_policy_put(struct wfx_vif *wvif, int idx)
{
int usage, locked;
struct wfx_tx_policy_cache *cache = &wvif->tx_policy_cache;
if (idx == HIF_TX_RETRY_POLICY_INVALID)
return;
spin_lock_bh(&cache->lock);
locked = list_empty(&cache->free);
usage = wfx_tx_policy_release(cache, &cache->cache[idx]);
if (locked && !usage)
ieee80211_wake_queues(wvif->wdev->hw);
spin_unlock_bh(&cache->lock);
}
static int wfx_tx_policy_upload(struct wfx_vif *wvif)
{
struct wfx_tx_policy *policies = wvif->tx_policy_cache.cache;
u8 tmp_rates[12];
int i, is_used;
do {
spin_lock_bh(&wvif->tx_policy_cache.lock);
for (i = 0; i < ARRAY_SIZE(wvif->tx_policy_cache.cache); ++i) {
is_used = memzcmp(policies[i].rates, sizeof(policies[i].rates));
if (!policies[i].uploaded && is_used)
break;
}
if (i < ARRAY_SIZE(wvif->tx_policy_cache.cache)) {
policies[i].uploaded = true;
memcpy(tmp_rates, policies[i].rates, sizeof(tmp_rates));
spin_unlock_bh(&wvif->tx_policy_cache.lock);
wfx_hif_set_tx_rate_retry_policy(wvif, i, tmp_rates);
} else {
spin_unlock_bh(&wvif->tx_policy_cache.lock);
}
} while (i < ARRAY_SIZE(wvif->tx_policy_cache.cache));
return 0;
}
void wfx_tx_policy_upload_work(struct work_struct *work)
{
struct wfx_vif *wvif = container_of(work, struct wfx_vif, tx_policy_upload_work);
wfx_tx_policy_upload(wvif);
wfx_tx_unlock(wvif->wdev);
}
void wfx_tx_policy_init(struct wfx_vif *wvif)
{
struct wfx_tx_policy_cache *cache = &wvif->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 < ARRAY_SIZE(cache->cache); ++i)
list_add(&cache->cache[i].link, &cache->free);
}
/* Tx implementation */
static bool wfx_is_action_back(struct ieee80211_hdr *hdr)
{
struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)hdr;
if (!ieee80211_is_action(mgmt->frame_control))
return false;
if (mgmt->u.action.category != WLAN_CATEGORY_BACK)
return false;
return true;
}
struct wfx_tx_priv *wfx_skb_tx_priv(struct sk_buff *skb)
{
struct ieee80211_tx_info *tx_info;
if (!skb)
return NULL;
tx_info = IEEE80211_SKB_CB(skb);
return (struct wfx_tx_priv *)tx_info->rate_driver_data;
}
struct wfx_hif_req_tx *wfx_skb_txreq(struct sk_buff *skb)
{
struct wfx_hif_msg *hif = (struct wfx_hif_msg *)skb->data;
struct wfx_hif_req_tx *req = (struct wfx_hif_req_tx *)hif->body;
return req;
}
struct wfx_vif *wfx_skb_wvif(struct wfx_dev *wdev, struct sk_buff *skb)
{
struct wfx_tx_priv *tx_priv = wfx_skb_tx_priv(skb);
struct wfx_hif_msg *hif = (struct wfx_hif_msg *)skb->data;
if (tx_priv->vif_id != hif->interface && hif->interface != 2) {
dev_err(wdev->dev, "corrupted skb");
return wdev_to_wvif(wdev, hif->interface);
}
return wdev_to_wvif(wdev, tx_priv->vif_id);
}
static u8 wfx_tx_get_link_id(struct wfx_vif *wvif, struct ieee80211_sta *sta,
struct ieee80211_hdr *hdr)
{
struct wfx_sta_priv *sta_priv = sta ? (struct wfx_sta_priv *)&sta->drv_priv : NULL;
struct ieee80211_vif *vif = wvif_to_vif(wvif);
const u8 *da = ieee80211_get_DA(hdr);
if (sta_priv && sta_priv->link_id)
return sta_priv->link_id;
if (vif->type != NL80211_IFTYPE_AP)
return 0;
if (is_multicast_ether_addr(da))
return 0;
return HIF_LINK_ID_NOT_ASSOCIATED;
}
static void wfx_tx_fixup_rates(struct ieee80211_tx_rate *rates)
{
bool has_rate0 = false;
int i, j;
for (i = 1, j = 1; j < IEEE80211_TX_MAX_RATES; j++) {
if (rates[j].idx == -1)
break;
/* The device use the rates in descending order, whatever the request from minstrel.
* We have to trade off here. Most important is to respect the primary rate
* requested by minstrel. So, we drops the entries with rate higher than the
* previous.
*/
if (rates[j].idx >= rates[i - 1].idx) {
rates[i - 1].count += rates[j].count;
rates[i - 1].count = min_t(u16, 15, rates[i - 1].count);
} else {
memcpy(rates + i, rates + j, sizeof(rates[i]));
if (rates[i].idx == 0)
has_rate0 = true;
/* The device apply Short GI only on the first rate */
rates[i].flags &= ~IEEE80211_TX_RC_SHORT_GI;
i++;
}
}
/* Ensure that MCS0 or 1Mbps is present at the end of the retry list */
if (!has_rate0 && i < IEEE80211_TX_MAX_RATES) {
rates[i].idx = 0;
rates[i].count = 8; /* == hw->max_rate_tries */
rates[i].flags = rates[0].flags & IEEE80211_TX_RC_MCS;
i++;
}
for (; i < IEEE80211_TX_MAX_RATES; i++) {
memset(rates + i, 0, sizeof(rates[i]));
rates[i].idx = -1;
}
}
static u8 wfx_tx_get_retry_policy_id(struct wfx_vif *wvif, struct ieee80211_tx_info *tx_info)
{
bool tx_policy_renew = false;
u8 ret;
ret = wfx_tx_policy_get(wvif, tx_info->driver_rates, &tx_policy_renew);
if (ret == HIF_TX_RETRY_POLICY_INVALID)
dev_warn(wvif->wdev->dev, "unable to get a valid Tx policy");
if (tx_policy_renew) {
wfx_tx_lock(wvif->wdev);
if (!schedule_work(&wvif->tx_policy_upload_work))
wfx_tx_unlock(wvif->wdev);
}
return ret;
}
static int wfx_tx_get_frame_format(struct ieee80211_tx_info *tx_info)
{
if (!(tx_info->driver_rates[0].flags & IEEE80211_TX_RC_MCS))
return HIF_FRAME_FORMAT_NON_HT;
else if (!(tx_info->driver_rates[0].flags & IEEE80211_TX_RC_GREEN_FIELD))
return HIF_FRAME_FORMAT_MIXED_FORMAT_HT;
else
return HIF_FRAME_FORMAT_GF_HT_11N;
}
static int wfx_tx_get_icv_len(struct ieee80211_key_conf *hw_key)
{
int mic_space;
if (!hw_key)
return 0;
if (hw_key->cipher == WLAN_CIPHER_SUITE_AES_CMAC)
return 0;
mic_space = (hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) ? 8 : 0;
return hw_key->icv_len + mic_space;
}
static int wfx_tx_inner(struct wfx_vif *wvif, struct ieee80211_sta *sta, struct sk_buff *skb)
{
struct wfx_hif_msg *hif_msg;
struct wfx_hif_req_tx *req;
struct wfx_tx_priv *tx_priv;
struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
struct ieee80211_key_conf *hw_key = tx_info->control.hw_key;
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
int queue_id = skb_get_queue_mapping(skb);
size_t offset = (size_t)skb->data & 3;
int wmsg_len = sizeof(struct wfx_hif_msg) + sizeof(struct wfx_hif_req_tx) + offset;
WARN(queue_id >= IEEE80211_NUM_ACS, "unsupported queue_id");
wfx_tx_fixup_rates(tx_info->driver_rates);
/* From now tx_info->control is unusable */
memset(tx_info->rate_driver_data, 0, sizeof(struct wfx_tx_priv));
/* Fill tx_priv */
tx_priv = (struct wfx_tx_priv *)tx_info->rate_driver_data;
tx_priv->icv_size = wfx_tx_get_icv_len(hw_key);
tx_priv->vif_id = wvif->id;
/* Fill hif_msg */
WARN(skb_headroom(skb) < wmsg_len, "not enough space in skb");
WARN(offset & 1, "attempt to transmit an unaligned frame");
skb_put(skb, tx_priv->icv_size);
skb_push(skb, wmsg_len);
memset(skb->data, 0, wmsg_len);
hif_msg = (struct wfx_hif_msg *)skb->data;
hif_msg->len = cpu_to_le16(skb->len);
hif_msg->id = HIF_REQ_ID_TX;
if (tx_info->flags & IEEE80211_TX_CTL_TX_OFFCHAN)
hif_msg->interface = 2;
else
hif_msg->interface = wvif->id;
if (skb->len > le16_to_cpu(wvif->wdev->hw_caps.size_inp_ch_buf)) {
dev_warn(wvif->wdev->dev,
"requested frame size (%d) is larger than maximum supported (%d)\n",
skb->len, le16_to_cpu(wvif->wdev->hw_caps.size_inp_ch_buf));
skb_pull(skb, wmsg_len);
return -EIO;
}
/* Fill tx request */
req = (struct wfx_hif_req_tx *)hif_msg->body;
/* packet_id just need to be unique on device. 32bits are more than necessary for that task,
* so we take advantage of it to add some extra data for debug.
*/
req->packet_id = atomic_add_return(1, &wvif->wdev->packet_id) & 0xFFFF;
req->packet_id |= IEEE80211_SEQ_TO_SN(le16_to_cpu(hdr->seq_ctrl)) << 16;
req->packet_id |= queue_id << 28;
req->fc_offset = offset;
/* Queue index are inverted between firmware and Linux */
req->queue_id = 3 - queue_id;
if (tx_info->flags & IEEE80211_TX_CTL_TX_OFFCHAN) {
req->peer_sta_id = HIF_LINK_ID_NOT_ASSOCIATED;
req->retry_policy_index = HIF_TX_RETRY_POLICY_INVALID;
req->frame_format = HIF_FRAME_FORMAT_NON_HT;
} else {
req->peer_sta_id = wfx_tx_get_link_id(wvif, sta, hdr);
req->retry_policy_index = wfx_tx_get_retry_policy_id(wvif, tx_info);
req->frame_format = wfx_tx_get_frame_format(tx_info);
}
if (tx_info->driver_rates[0].flags & IEEE80211_TX_RC_SHORT_GI)
req->short_gi = 1;
if (tx_info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM)
req->after_dtim = 1;
/* Auxiliary operations */
wfx_tx_queues_put(wvif, skb);
if (tx_info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM)
schedule_work(&wvif->update_tim_work);
wfx_bh_request_tx(wvif->wdev);
return 0;
}
void wfx_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, struct sk_buff *skb)
{
struct wfx_dev *wdev = hw->priv;
struct wfx_vif *wvif;
struct ieee80211_sta *sta = control ? control->sta : NULL;
struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
size_t driver_data_room = sizeof_field(struct ieee80211_tx_info, rate_driver_data);
BUILD_BUG_ON_MSG(sizeof(struct wfx_tx_priv) > driver_data_room,
"struct tx_priv is too large");
WARN(skb->next || skb->prev, "skb is already member of a list");
/* control.vif can be NULL for injected frames */
if (tx_info->control.vif)
wvif = (struct wfx_vif *)tx_info->control.vif->drv_priv;
else
wvif = wvif_iterate(wdev, NULL);
if (WARN_ON(!wvif))
goto drop;
/* Because of TX_AMPDU_SETUP_IN_HW, mac80211 does not try to send any BlockAck session
* management frame. The check below exist just in case.
*/
if (wfx_is_action_back(hdr)) {
dev_info(wdev->dev, "drop BA action\n");
goto drop;
}
if (wfx_tx_inner(wvif, sta, skb))
goto drop;
return;
drop:
ieee80211_tx_status_irqsafe(wdev->hw, skb);
}
static void wfx_skb_dtor(struct wfx_vif *wvif, struct sk_buff *skb)
{
struct wfx_hif_msg *hif = (struct wfx_hif_msg *)skb->data;
struct wfx_hif_req_tx *req = (struct wfx_hif_req_tx *)hif->body;
unsigned int offset = sizeof(struct wfx_hif_msg) + sizeof(struct wfx_hif_req_tx) +
req->fc_offset;
if (!wvif) {
pr_warn("vif associated with the skb does not exist anymore\n");
return;
}
wfx_tx_policy_put(wvif, req->retry_policy_index);
skb_pull(skb, offset);
ieee80211_tx_status_irqsafe(wvif->wdev->hw, skb);
}
static void wfx_tx_fill_rates(struct wfx_dev *wdev, struct ieee80211_tx_info *tx_info,
const struct wfx_hif_cnf_tx *arg)
{
struct ieee80211_tx_rate *rate;
int tx_count;
int i;
tx_count = arg->ack_failures;
if (!arg->status || arg->ack_failures)
tx_count += 1; /* Also report success */
for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) {
rate = &tx_info->status.rates[i];
if (rate->idx < 0)
break;
if (tx_count < rate->count && arg->status == HIF_STATUS_TX_FAIL_RETRIES &&
arg->ack_failures)
dev_dbg(wdev->dev, "all retries were not consumed: %d != %d\n",
rate->count, tx_count);
if (tx_count <= rate->count && tx_count &&
arg->txed_rate != wfx_get_hw_rate(wdev, rate))
dev_dbg(wdev->dev, "inconsistent tx_info rates: %d != %d\n",
arg->txed_rate, wfx_get_hw_rate(wdev, rate));
if (tx_count > rate->count) {
tx_count -= rate->count;
} else if (!tx_count) {
rate->count = 0;
rate->idx = -1;
} else {
rate->count = tx_count;
tx_count = 0;
}
}
if (tx_count)
dev_dbg(wdev->dev, "%d more retries than expected\n", tx_count);
}
void wfx_tx_confirm_cb(struct wfx_dev *wdev, const struct wfx_hif_cnf_tx *arg)
{
const struct wfx_tx_priv *tx_priv;
struct ieee80211_tx_info *tx_info;
struct wfx_vif *wvif;
struct sk_buff *skb;
skb = wfx_pending_get(wdev, arg->packet_id);
if (!skb) {
dev_warn(wdev->dev, "received unknown packet_id (%#.8x) from chip\n",
arg->packet_id);
return;
}
tx_info = IEEE80211_SKB_CB(skb);
tx_priv = wfx_skb_tx_priv(skb);
wvif = wfx_skb_wvif(wdev, skb);
WARN_ON(!wvif);
if (!wvif)
return;
/* Note that wfx_pending_get_pkt_us_delay() get data from tx_info */
_trace_tx_stats(arg, skb, wfx_pending_get_pkt_us_delay(wdev, skb));
wfx_tx_fill_rates(wdev, tx_info, arg);
skb_trim(skb, skb->len - tx_priv->icv_size);
/* From now, you can touch to tx_info->status, but do not touch to tx_priv anymore */
/* FIXME: use ieee80211_tx_info_clear_status() */
memset(tx_info->rate_driver_data, 0, sizeof(tx_info->rate_driver_data));
memset(tx_info->pad, 0, sizeof(tx_info->pad));
if (!arg->status) {
tx_info->status.tx_time = le32_to_cpu(arg->media_delay) -
le32_to_cpu(arg->tx_queue_delay);
if (tx_info->flags & IEEE80211_TX_CTL_NO_ACK)
tx_info->flags |= IEEE80211_TX_STAT_NOACK_TRANSMITTED;
else
tx_info->flags |= IEEE80211_TX_STAT_ACK;
} else if (arg->status == HIF_STATUS_TX_FAIL_REQUEUE) {
WARN(!arg->requeue, "incoherent status and result_flags");
if (tx_info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM) {
wvif->after_dtim_tx_allowed = false; /* DTIM period elapsed */
schedule_work(&wvif->update_tim_work);
}
tx_info->flags |= IEEE80211_TX_STAT_TX_FILTERED;
}
wfx_skb_dtor(wvif, skb);
}
static void wfx_flush_vif(struct wfx_vif *wvif, u32 queues, struct sk_buff_head *dropped)
{
struct wfx_queue *queue;
int i;
for (i = 0; i < IEEE80211_NUM_ACS; i++) {
if (!(BIT(i) & queues))
continue;
queue = &wvif->tx_queue[i];
if (dropped)
wfx_tx_queue_drop(wvif, queue, dropped);
}
if (wvif->wdev->chip_frozen)
return;
for (i = 0; i < IEEE80211_NUM_ACS; i++) {
if (!(BIT(i) & queues))
continue;
queue = &wvif->tx_queue[i];
if (wait_event_timeout(wvif->wdev->tx_dequeue, wfx_tx_queue_empty(wvif, queue),
msecs_to_jiffies(1000)) <= 0)
dev_warn(wvif->wdev->dev, "frames queued while flushing tx queues?");
}
}
void wfx_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u32 queues, bool drop)
{
struct wfx_dev *wdev = hw->priv;
struct sk_buff_head dropped;
struct wfx_vif *wvif;
struct sk_buff *skb;
skb_queue_head_init(&dropped);
if (vif) {
wvif = (struct wfx_vif *)vif->drv_priv;
wfx_flush_vif(wvif, queues, drop ? &dropped : NULL);
} else {
wvif = NULL;
while ((wvif = wvif_iterate(wdev, wvif)) != NULL)
wfx_flush_vif(wvif, queues, drop ? &dropped : NULL);
}
wfx_tx_flush(wdev);
if (wdev->chip_frozen)
wfx_pending_drop(wdev, &dropped);
while ((skb = skb_dequeue(&dropped)) != NULL) {
wvif = wfx_skb_wvif(wdev, skb);
ieee80211_tx_info_clear_status(IEEE80211_SKB_CB(skb));
wfx_skb_dtor(wvif, skb);
}
}
@@ -0,0 +1,53 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Data transmitting implementation.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#ifndef WFX_DATA_TX_H
#define WFX_DATA_TX_H
#include <linux/list.h>
#include <net/mac80211.h>
#include "hif_api_cmd.h"
#include "hif_api_mib.h"
struct wfx_tx_priv;
struct wfx_dev;
struct wfx_vif;
struct wfx_tx_policy {
struct list_head link;
int usage_count;
u8 rates[12];
bool uploaded;
};
struct wfx_tx_policy_cache {
struct wfx_tx_policy cache[HIF_TX_RETRY_POLICY_MAX];
/* FIXME: use a trees and drop hash from tx_policy */
struct list_head used;
struct list_head free;
spinlock_t lock;
};
struct wfx_tx_priv {
ktime_t xmit_timestamp;
unsigned char icv_size;
unsigned char vif_id;
};
void wfx_tx_policy_init(struct wfx_vif *wvif);
void wfx_tx_policy_upload_work(struct work_struct *work);
void wfx_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, struct sk_buff *skb);
void wfx_tx_confirm_cb(struct wfx_dev *wdev, const struct wfx_hif_cnf_tx *arg);
void wfx_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u32 queues, bool drop);
struct wfx_tx_priv *wfx_skb_tx_priv(struct sk_buff *skb);
struct wfx_hif_req_tx *wfx_skb_txreq(struct sk_buff *skb);
struct wfx_vif *wfx_skb_wvif(struct wfx_dev *wdev, struct sk_buff *skb);
#endif
@@ -0,0 +1,331 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Debugfs interface.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/crc32.h>
#include "debug.h"
#include "wfx.h"
#include "sta.h"
#include "main.h"
#include "hif_tx.h"
#include "hif_tx_mib.h"
#define CREATE_TRACE_POINTS
#include "traces.h"
static const struct trace_print_flags hif_msg_print_map[] = {
hif_msg_list,
};
static const struct trace_print_flags hif_mib_print_map[] = {
hif_mib_list,
};
static const struct trace_print_flags wfx_reg_print_map[] = {
wfx_reg_list,
};
static const char *get_symbol(unsigned long val, const struct trace_print_flags *symbol_array)
{
int i;
for (i = 0; symbol_array[i].mask != -1; i++) {
if (val == symbol_array[i].mask)
return symbol_array[i].name;
}
return "unknown";
}
const char *wfx_get_hif_name(unsigned long id)
{
return get_symbol(id, hif_msg_print_map);
}
const char *wfx_get_mib_name(unsigned long id)
{
return get_symbol(id, hif_mib_print_map);
}
const char *wfx_get_reg_name(unsigned long id)
{
return get_symbol(id, wfx_reg_print_map);
}
static int wfx_counters_show(struct seq_file *seq, void *v)
{
int ret, i;
struct wfx_dev *wdev = seq->private;
struct wfx_hif_mib_extended_count_table counters[3];
for (i = 0; i < ARRAY_SIZE(counters); i++) {
ret = wfx_hif_get_counters_table(wdev, i, counters + i);
if (ret < 0)
return ret;
if (ret > 0)
return -EIO;
}
seq_printf(seq, "%-24s %12s %12s %12s\n", "", "global", "iface 0", "iface 1");
#define PUT_COUNTER(name) \
seq_printf(seq, "%-24s %12d %12d %12d\n", #name, \
le32_to_cpu(counters[2].count_##name), \
le32_to_cpu(counters[0].count_##name), \
le32_to_cpu(counters[1].count_##name))
PUT_COUNTER(tx_frames);
PUT_COUNTER(tx_frames_multicast);
PUT_COUNTER(tx_frames_success);
PUT_COUNTER(tx_frames_retried);
PUT_COUNTER(tx_frames_multi_retried);
PUT_COUNTER(tx_frames_failed);
PUT_COUNTER(ack_failed);
PUT_COUNTER(rts_success);
PUT_COUNTER(rts_failed);
PUT_COUNTER(rx_frames);
PUT_COUNTER(rx_frames_multicast);
PUT_COUNTER(rx_frames_success);
PUT_COUNTER(rx_frames_failed);
PUT_COUNTER(drop_plcp);
PUT_COUNTER(drop_fcs);
PUT_COUNTER(drop_no_key);
PUT_COUNTER(drop_decryption);
PUT_COUNTER(drop_tkip_mic);
PUT_COUNTER(drop_bip_mic);
PUT_COUNTER(drop_cmac_icv);
PUT_COUNTER(drop_cmac_replay);
PUT_COUNTER(drop_ccmp_replay);
PUT_COUNTER(drop_duplicate);
PUT_COUNTER(rx_bcn_miss);
PUT_COUNTER(rx_bcn_success);
PUT_COUNTER(rx_bcn_dtim);
PUT_COUNTER(rx_bcn_dtim_aid0_clr);
PUT_COUNTER(rx_bcn_dtim_aid0_set);
#undef PUT_COUNTER
for (i = 0; i < ARRAY_SIZE(counters[0].reserved); i++)
seq_printf(seq, "reserved[%02d]%12s %12d %12d %12d\n", i, "",
le32_to_cpu(counters[2].reserved[i]),
le32_to_cpu(counters[0].reserved[i]),
le32_to_cpu(counters[1].reserved[i]));
return 0;
}
DEFINE_SHOW_ATTRIBUTE(wfx_counters);
static const char * const channel_names[] = {
[0] = "1M",
[1] = "2M",
[2] = "5.5M",
[3] = "11M",
/* Entries 4 and 5 does not exist */
[6] = "6M",
[7] = "9M",
[8] = "12M",
[9] = "18M",
[10] = "24M",
[11] = "36M",
[12] = "48M",
[13] = "54M",
[14] = "MCS0",
[15] = "MCS1",
[16] = "MCS2",
[17] = "MCS3",
[18] = "MCS4",
[19] = "MCS5",
[20] = "MCS6",
[21] = "MCS7",
};
static int wfx_rx_stats_show(struct seq_file *seq, void *v)
{
struct wfx_dev *wdev = seq->private;
struct wfx_hif_rx_stats *st = &wdev->rx_stats;
int i;
mutex_lock(&wdev->rx_stats_lock);
seq_printf(seq, "Timestamp: %dus\n", st->date);
seq_printf(seq, "Low power clock: frequency %uHz, external %s\n",
le32_to_cpu(st->pwr_clk_freq), st->is_ext_pwr_clk ? "yes" : "no");
seq_printf(seq, "Num. of frames: %d, PER (x10e4): %d, Throughput: %dKbps/s\n",
st->nb_rx_frame, st->per_total, st->throughput);
seq_puts(seq, " Num. of PER RSSI SNR CFO\n");
seq_puts(seq, " frames (x10e4) (dBm) (dB) (kHz)\n");
for (i = 0; i < ARRAY_SIZE(channel_names); i++) {
if (channel_names[i])
seq_printf(seq, "%5s %8d %8d %8d %8d %8d\n",
channel_names[i],
le32_to_cpu(st->nb_rx_by_rate[i]),
le16_to_cpu(st->per[i]),
(s16)le16_to_cpu(st->rssi[i]) / 100,
(s16)le16_to_cpu(st->snr[i]) / 100,
(s16)le16_to_cpu(st->cfo[i]));
}
mutex_unlock(&wdev->rx_stats_lock);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(wfx_rx_stats);
static int wfx_tx_power_loop_show(struct seq_file *seq, void *v)
{
struct wfx_dev *wdev = seq->private;
struct wfx_hif_tx_power_loop_info *st = &wdev->tx_power_loop_info;
int tmp;
mutex_lock(&wdev->tx_power_loop_info_lock);
tmp = le16_to_cpu(st->tx_gain_dig);
seq_printf(seq, "Tx gain digital: %d\n", tmp);
tmp = le16_to_cpu(st->tx_gain_pa);
seq_printf(seq, "Tx gain PA: %d\n", tmp);
tmp = (s16)le16_to_cpu(st->target_pout);
seq_printf(seq, "Target Pout: %d.%02d dBm\n", tmp / 4, (tmp % 4) * 25);
tmp = (s16)le16_to_cpu(st->p_estimation);
seq_printf(seq, "FEM Pout: %d.%02d dBm\n", tmp / 4, (tmp % 4) * 25);
tmp = le16_to_cpu(st->vpdet);
seq_printf(seq, "Vpdet: %d mV\n", tmp);
seq_printf(seq, "Measure index: %d\n", st->measurement_index);
mutex_unlock(&wdev->tx_power_loop_info_lock);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(wfx_tx_power_loop);
static ssize_t wfx_send_pds_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct wfx_dev *wdev = file->private_data;
char *buf;
int ret;
if (*ppos != 0) {
dev_dbg(wdev->dev, "PDS data must be written in one transaction");
return -EBUSY;
}
buf = memdup_user(user_buf, count);
if (IS_ERR(buf))
return PTR_ERR(buf);
*ppos = *ppos + count;
ret = wfx_send_pds(wdev, buf, count);
kfree(buf);
if (ret < 0)
return ret;
return count;
}
static const struct file_operations wfx_send_pds_fops = {
.open = simple_open,
.write = wfx_send_pds_write,
};
struct dbgfs_hif_msg {
struct wfx_dev *wdev;
struct completion complete;
u8 reply[1024];
int ret;
};
static ssize_t wfx_send_hif_msg_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct dbgfs_hif_msg *context = file->private_data;
struct wfx_dev *wdev = context->wdev;
struct wfx_hif_msg *request;
if (completion_done(&context->complete)) {
dev_dbg(wdev->dev, "read previous result before start a new one\n");
return -EBUSY;
}
if (count < sizeof(struct wfx_hif_msg))
return -EINVAL;
/* wfx_cmd_send() checks that reply buffer is wide enough, but does not return precise
* length read. User have to know how many bytes should be read. Filling reply buffer with a
* memory pattern may help user.
*/
memset(context->reply, 0xFF, sizeof(context->reply));
request = memdup_user(user_buf, count);
if (IS_ERR(request))
return PTR_ERR(request);
if (le16_to_cpu(request->len) != count) {
kfree(request);
return -EINVAL;
}
context->ret = wfx_cmd_send(wdev, request, context->reply, sizeof(context->reply), false);
kfree(request);
complete(&context->complete);
return count;
}
static ssize_t wfx_send_hif_msg_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct dbgfs_hif_msg *context = file->private_data;
int ret;
if (count > sizeof(context->reply))
return -EINVAL;
ret = wait_for_completion_interruptible(&context->complete);
if (ret)
return ret;
if (context->ret < 0)
return context->ret;
/* Be careful, write() is waiting for a full message while read() only returns a payload */
if (copy_to_user(user_buf, context->reply, count))
return -EFAULT;
return count;
}
static int wfx_send_hif_msg_open(struct inode *inode, struct file *file)
{
struct dbgfs_hif_msg *context = kzalloc_obj(*context);
if (!context)
return -ENOMEM;
context->wdev = inode->i_private;
init_completion(&context->complete);
file->private_data = context;
return 0;
}
static int wfx_send_hif_msg_release(struct inode *inode, struct file *file)
{
struct dbgfs_hif_msg *context = file->private_data;
kfree(context);
return 0;
}
static const struct file_operations wfx_send_hif_msg_fops = {
.open = wfx_send_hif_msg_open,
.release = wfx_send_hif_msg_release,
.write = wfx_send_hif_msg_write,
.read = wfx_send_hif_msg_read,
};
int wfx_debug_init(struct wfx_dev *wdev)
{
struct dentry *d;
d = debugfs_create_dir("wfx", wdev->hw->wiphy->debugfsdir);
debugfs_create_file("counters", 0444, d, wdev, &wfx_counters_fops);
debugfs_create_file("rx_stats", 0444, d, wdev, &wfx_rx_stats_fops);
debugfs_create_file("tx_power_loop", 0444, d, wdev, &wfx_tx_power_loop_fops);
debugfs_create_file("send_pds", 0200, d, wdev, &wfx_send_pds_fops);
debugfs_create_file("send_hif_msg", 0600, d, wdev, &wfx_send_hif_msg_fops);
return 0;
}
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Debugfs interface.
*
* Copyright (c) 2017-2019, Silicon Laboratories, Inc.
* Copyright (c) 2011, ST-Ericsson
*/
#ifndef WFX_DEBUG_H
#define WFX_DEBUG_H
struct wfx_dev;
int wfx_debug_init(struct wfx_dev *wdev);
const char *wfx_get_hif_name(unsigned long id);
const char *wfx_get_mib_name(unsigned long id);
const char *wfx_get_reg_name(unsigned long id);
#endif
@@ -0,0 +1,389 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Firmware loading.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#include <linux/firmware.h>
#include <linux/hex.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/bitfield.h>
#include "fwio.h"
#include "wfx.h"
#include "hwio.h"
/* Addresses below are in SRAM area */
#define WFX_DNLD_FIFO 0x09004000
#define DNLD_BLOCK_SIZE 0x0400
#define DNLD_FIFO_SIZE 0x8000 /* (32 * DNLD_BLOCK_SIZE) */
/* Download Control Area (DCA) */
#define WFX_DCA_IMAGE_SIZE 0x0900C000
#define WFX_DCA_PUT 0x0900C004
#define WFX_DCA_GET 0x0900C008
#define WFX_DCA_HOST_STATUS 0x0900C00C
#define HOST_READY 0x87654321
#define HOST_INFO_READ 0xA753BD99
#define HOST_UPLOAD_PENDING 0xABCDDCBA
#define HOST_UPLOAD_COMPLETE 0xD4C64A99
#define HOST_OK_TO_JUMP 0x174FC882
#define WFX_DCA_NCP_STATUS 0x0900C010
#define NCP_NOT_READY 0x12345678
#define NCP_READY 0x87654321
#define NCP_INFO_READY 0xBD53EF99
#define NCP_DOWNLOAD_PENDING 0xABCDDCBA
#define NCP_DOWNLOAD_COMPLETE 0xCAFEFECA
#define NCP_AUTH_OK 0xD4C64A99
#define NCP_AUTH_FAIL 0x174FC882
#define NCP_PUB_KEY_RDY 0x7AB41D19
#define WFX_DCA_FW_SIGNATURE 0x0900C014
#define FW_SIGNATURE_SIZE 0x40
#define WFX_DCA_FW_HASH 0x0900C054
#define FW_HASH_SIZE 0x08
#define WFX_DCA_FW_VERSION 0x0900C05C
#define FW_VERSION_SIZE 0x04
#define WFX_DCA_RESERVED 0x0900C060
#define DCA_RESERVED_SIZE 0x20
#define WFX_STATUS_INFO 0x0900C080
#define WFX_BOOTLOADER_LABEL 0x0900C084
#define BOOTLOADER_LABEL_SIZE 0x3C
#define WFX_PTE_INFO 0x0900C0C0
#define PTE_INFO_KEYSET_IDX 0x0D
#define PTE_INFO_SIZE 0x10
#define WFX_ERR_INFO 0x0900C0D0
#define ERR_INVALID_SEC_TYPE 0x05
#define ERR_SIG_VERIF_FAILED 0x0F
#define ERR_AES_CTRL_KEY 0x10
#define ERR_ECC_PUB_KEY 0x11
#define ERR_MAC_KEY 0x18
#define DCA_TIMEOUT 50 /* milliseconds */
#define WAKEUP_TIMEOUT 200 /* milliseconds */
static const char * const fwio_errors[] = {
[ERR_INVALID_SEC_TYPE] = "Invalid section type or wrong encryption",
[ERR_SIG_VERIF_FAILED] = "Signature verification failed",
[ERR_AES_CTRL_KEY] = "AES control key not initialized",
[ERR_ECC_PUB_KEY] = "ECC public key not initialized",
[ERR_MAC_KEY] = "MAC key not initialized",
};
/* request_firmware() allocate data using vmalloc(). It is not compatible with underlying hardware
* that use DMA. Function below detect this case and allocate a bounce buffer if necessary.
*
* Notice that, in doubt, you can enable CONFIG_DEBUG_SG to ask kernel to detect this problem at
* runtime (else, kernel silently fail).
*
* NOTE: it may also be possible to use 'pages' from struct firmware and avoid bounce buffer
*/
static int wfx_sram_write_dma_safe(struct wfx_dev *wdev, u32 addr, const u8 *buf, size_t len)
{
int ret;
const u8 *tmp;
if (!virt_addr_valid(buf)) {
tmp = kmemdup(buf, len, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
} else {
tmp = buf;
}
ret = wfx_sram_buf_write(wdev, addr, tmp, len);
if (tmp != buf)
kfree(tmp);
return ret;
}
static int get_firmware(struct wfx_dev *wdev, u32 keyset_chip,
const struct firmware **fw, int *file_offset)
{
int keyset_file;
char filename[256];
const char *data;
int ret;
snprintf(filename, sizeof(filename), "%s_%02X.sec",
wdev->pdata.file_fw, keyset_chip);
ret = firmware_request_nowarn(fw, filename, wdev->dev);
if (ret) {
dev_info(wdev->dev, "can't load %s, falling back to %s.sec\n",
filename, wdev->pdata.file_fw);
snprintf(filename, sizeof(filename), "%s.sec", wdev->pdata.file_fw);
ret = request_firmware(fw, filename, wdev->dev);
if (ret) {
dev_err(wdev->dev, "can't load %s\n", filename);
*fw = NULL;
return ret;
}
}
data = (*fw)->data;
if (memcmp(data, "KEYSET", 6) != 0) {
/* Legacy firmware format */
*file_offset = 0;
keyset_file = 0x90;
} else {
*file_offset = 8;
keyset_file = (hex_to_bin(data[6]) * 16) | hex_to_bin(data[7]);
if (keyset_file < 0) {
dev_err(wdev->dev, "%s corrupted\n", filename);
release_firmware(*fw);
*fw = NULL;
return -EINVAL;
}
}
if (keyset_file != keyset_chip) {
dev_err(wdev->dev, "firmware keyset is incompatible with chip (file: 0x%02X, chip: 0x%02X)\n",
keyset_file, keyset_chip);
release_firmware(*fw);
*fw = NULL;
return -ENODEV;
}
wdev->keyset = keyset_file;
return 0;
}
static int wait_ncp_status(struct wfx_dev *wdev, u32 status)
{
ktime_t now, start;
u32 reg;
int ret;
start = ktime_get();
for (;;) {
ret = wfx_sram_reg_read(wdev, WFX_DCA_NCP_STATUS, &reg);
if (ret < 0)
return -EIO;
now = ktime_get();
if (reg == status)
break;
if (ktime_after(now, ktime_add_ms(start, DCA_TIMEOUT)))
return -ETIMEDOUT;
}
if (ktime_compare(now, start))
dev_dbg(wdev->dev, "chip answer after %lldus\n", ktime_us_delta(now, start));
else
dev_dbg(wdev->dev, "chip answer immediately\n");
return 0;
}
static int upload_firmware(struct wfx_dev *wdev, const u8 *data, size_t len)
{
int ret;
u32 offs, bytes_done = 0;
ktime_t now, start;
if (len % DNLD_BLOCK_SIZE) {
dev_err(wdev->dev, "firmware size is not aligned. Buffer overrun will occur\n");
return -EIO;
}
offs = 0;
while (offs < len) {
start = ktime_get();
for (;;) {
now = ktime_get();
if (offs + DNLD_BLOCK_SIZE - bytes_done < DNLD_FIFO_SIZE)
break;
if (ktime_after(now, ktime_add_ms(start, DCA_TIMEOUT)))
return -ETIMEDOUT;
ret = wfx_sram_reg_read(wdev, WFX_DCA_GET, &bytes_done);
if (ret < 0)
return ret;
}
if (ktime_compare(now, start))
dev_dbg(wdev->dev, "answer after %lldus\n", ktime_us_delta(now, start));
ret = wfx_sram_write_dma_safe(wdev, WFX_DNLD_FIFO + (offs % DNLD_FIFO_SIZE),
data + offs, DNLD_BLOCK_SIZE);
if (ret < 0)
return ret;
/* The device seems to not support writing 0 in this register during first loop */
offs += DNLD_BLOCK_SIZE;
ret = wfx_sram_reg_write(wdev, WFX_DCA_PUT, offs);
if (ret < 0)
return ret;
}
return 0;
}
static void print_boot_status(struct wfx_dev *wdev)
{
u32 reg;
wfx_sram_reg_read(wdev, WFX_STATUS_INFO, &reg);
if (reg == 0x12345678)
return;
wfx_sram_reg_read(wdev, WFX_ERR_INFO, &reg);
if (reg < ARRAY_SIZE(fwio_errors) && fwio_errors[reg])
dev_info(wdev->dev, "secure boot: %s\n", fwio_errors[reg]);
else
dev_info(wdev->dev, "secure boot: Error %#02x\n", reg);
}
static int load_firmware_secure(struct wfx_dev *wdev)
{
const struct firmware *fw = NULL;
int header_size;
int fw_offset;
ktime_t start;
u8 *buf;
int ret;
BUILD_BUG_ON(PTE_INFO_SIZE > BOOTLOADER_LABEL_SIZE);
buf = kmalloc(BOOTLOADER_LABEL_SIZE + 1, GFP_KERNEL);
if (!buf)
return -ENOMEM;
wfx_sram_reg_write(wdev, WFX_DCA_HOST_STATUS, HOST_READY);
ret = wait_ncp_status(wdev, NCP_INFO_READY);
if (ret)
goto error;
wfx_sram_buf_read(wdev, WFX_BOOTLOADER_LABEL, buf, BOOTLOADER_LABEL_SIZE);
buf[BOOTLOADER_LABEL_SIZE] = 0;
dev_dbg(wdev->dev, "bootloader: \"%s\"\n", buf);
wfx_sram_buf_read(wdev, WFX_PTE_INFO, buf, PTE_INFO_SIZE);
ret = get_firmware(wdev, buf[PTE_INFO_KEYSET_IDX], &fw, &fw_offset);
if (ret)
goto error;
header_size = fw_offset + FW_SIGNATURE_SIZE + FW_HASH_SIZE;
wfx_sram_reg_write(wdev, WFX_DCA_HOST_STATUS, HOST_INFO_READ);
ret = wait_ncp_status(wdev, NCP_READY);
if (ret)
goto error;
wfx_sram_reg_write(wdev, WFX_DNLD_FIFO, 0xFFFFFFFF); /* Fifo init */
wfx_sram_write_dma_safe(wdev, WFX_DCA_FW_VERSION, "\x01\x00\x00\x00", FW_VERSION_SIZE);
wfx_sram_write_dma_safe(wdev, WFX_DCA_FW_SIGNATURE, fw->data + fw_offset,
FW_SIGNATURE_SIZE);
wfx_sram_write_dma_safe(wdev, WFX_DCA_FW_HASH, fw->data + fw_offset + FW_SIGNATURE_SIZE,
FW_HASH_SIZE);
wfx_sram_reg_write(wdev, WFX_DCA_IMAGE_SIZE, fw->size - header_size);
wfx_sram_reg_write(wdev, WFX_DCA_HOST_STATUS, HOST_UPLOAD_PENDING);
ret = wait_ncp_status(wdev, NCP_DOWNLOAD_PENDING);
if (ret)
goto error;
start = ktime_get();
ret = upload_firmware(wdev, fw->data + header_size, fw->size - header_size);
if (ret)
goto error;
dev_dbg(wdev->dev, "firmware load after %lldus\n",
ktime_us_delta(ktime_get(), start));
wfx_sram_reg_write(wdev, WFX_DCA_HOST_STATUS, HOST_UPLOAD_COMPLETE);
ret = wait_ncp_status(wdev, NCP_AUTH_OK);
/* Legacy ROM support */
if (ret < 0)
ret = wait_ncp_status(wdev, NCP_PUB_KEY_RDY);
if (ret < 0)
goto error;
wfx_sram_reg_write(wdev, WFX_DCA_HOST_STATUS, HOST_OK_TO_JUMP);
error:
kfree(buf);
release_firmware(fw);
if (ret)
print_boot_status(wdev);
return ret;
}
static int init_gpr(struct wfx_dev *wdev)
{
int ret, i;
static const struct {
int index;
u32 value;
} gpr_init[] = {
{ 0x07, 0x208775 },
{ 0x08, 0x2EC020 },
{ 0x09, 0x3C3C3C },
{ 0x0B, 0x322C44 },
{ 0x0C, 0xA06497 },
};
for (i = 0; i < ARRAY_SIZE(gpr_init); i++) {
ret = wfx_igpr_reg_write(wdev, gpr_init[i].index, gpr_init[i].value);
if (ret < 0)
return ret;
dev_dbg(wdev->dev, " index %02x: %08x\n", gpr_init[i].index, gpr_init[i].value);
}
return 0;
}
int wfx_init_device(struct wfx_dev *wdev)
{
int ret;
int hw_revision, hw_type;
int wakeup_timeout = 50; /* ms */
ktime_t now, start;
u32 reg;
reg = CFG_DIRECT_ACCESS_MODE | CFG_CPU_RESET | CFG_BYTE_ORDER_ABCD;
if (wdev->pdata.use_rising_clk)
reg |= CFG_CLK_RISE_EDGE;
ret = wfx_config_reg_write(wdev, reg);
if (ret < 0) {
dev_err(wdev->dev, "bus returned an error during first write access. Host configuration error?\n");
return -EIO;
}
ret = wfx_config_reg_read(wdev, &reg);
if (ret < 0) {
dev_err(wdev->dev, "bus returned an error during first read access. Bus configuration error?\n");
return -EIO;
}
if (reg == 0 || reg == ~0) {
dev_err(wdev->dev, "chip mute. Bus configuration error or chip wasn't reset?\n");
return -EIO;
}
dev_dbg(wdev->dev, "initial config register value: %08x\n", reg);
hw_revision = FIELD_GET(CFG_DEVICE_ID_MAJOR, reg);
if (hw_revision == 0) {
dev_err(wdev->dev, "bad hardware revision number: %d\n", hw_revision);
return -ENODEV;
}
hw_type = FIELD_GET(CFG_DEVICE_ID_TYPE, reg);
if (hw_type == 1) {
dev_notice(wdev->dev, "development hardware detected\n");
wakeup_timeout = 2000;
}
ret = init_gpr(wdev);
if (ret < 0)
return ret;
ret = wfx_control_reg_write(wdev, CTRL_WLAN_WAKEUP);
if (ret < 0)
return -EIO;
start = ktime_get();
for (;;) {
ret = wfx_control_reg_read(wdev, &reg);
now = ktime_get();
if (reg & CTRL_WLAN_READY)
break;
if (ktime_after(now, ktime_add_ms(start, wakeup_timeout))) {
dev_err(wdev->dev, "chip didn't wake up. Chip wasn't reset?\n");
return -ETIMEDOUT;
}
}
dev_dbg(wdev->dev, "chip wake up after %lldus\n", ktime_us_delta(now, start));
ret = wfx_config_reg_write_bits(wdev, CFG_CPU_RESET, 0);
if (ret < 0)
return ret;
ret = load_firmware_secure(wdev);
if (ret < 0)
return ret;
return wfx_config_reg_write_bits(wdev,
CFG_DIRECT_ACCESS_MODE |
CFG_IRQ_ENABLE_DATA |
CFG_IRQ_ENABLE_WRDY,
CFG_IRQ_ENABLE_DATA);
}
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Firmware loading.
*
* Copyright (c) 2017-2019, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#ifndef WFX_FWIO_H
#define WFX_FWIO_H
struct wfx_dev;
int wfx_init_device(struct wfx_dev *wdev);
#endif
@@ -0,0 +1,553 @@
/* SPDX-License-Identifier: GPL-2.0-only or Apache-2.0 */
/*
* WF200 hardware interface definitions
*
* Copyright (c) 2018-2020, Silicon Laboratories Inc.
*/
#ifndef WFX_HIF_API_CMD_H
#define WFX_HIF_API_CMD_H
#include "hif_api_general.h"
enum wfx_hif_requests_ids {
HIF_REQ_ID_RESET = 0x0a,
HIF_REQ_ID_READ_MIB = 0x05,
HIF_REQ_ID_WRITE_MIB = 0x06,
HIF_REQ_ID_START_SCAN = 0x07,
HIF_REQ_ID_STOP_SCAN = 0x08,
HIF_REQ_ID_TX = 0x04,
HIF_REQ_ID_JOIN = 0x0b,
HIF_REQ_ID_SET_PM_MODE = 0x10,
HIF_REQ_ID_SET_BSS_PARAMS = 0x11,
HIF_REQ_ID_ADD_KEY = 0x0c,
HIF_REQ_ID_REMOVE_KEY = 0x0d,
HIF_REQ_ID_EDCA_QUEUE_PARAMS = 0x13,
HIF_REQ_ID_START = 0x17,
HIF_REQ_ID_BEACON_TRANSMIT = 0x18,
HIF_REQ_ID_UPDATE_IE = 0x1b,
HIF_REQ_ID_MAP_LINK = 0x1c,
};
enum wfx_hif_confirmations_ids {
HIF_CNF_ID_RESET = 0x0a,
HIF_CNF_ID_READ_MIB = 0x05,
HIF_CNF_ID_WRITE_MIB = 0x06,
HIF_CNF_ID_START_SCAN = 0x07,
HIF_CNF_ID_STOP_SCAN = 0x08,
HIF_CNF_ID_TX = 0x04,
HIF_CNF_ID_MULTI_TRANSMIT = 0x1e,
HIF_CNF_ID_JOIN = 0x0b,
HIF_CNF_ID_SET_PM_MODE = 0x10,
HIF_CNF_ID_SET_BSS_PARAMS = 0x11,
HIF_CNF_ID_ADD_KEY = 0x0c,
HIF_CNF_ID_REMOVE_KEY = 0x0d,
HIF_CNF_ID_EDCA_QUEUE_PARAMS = 0x13,
HIF_CNF_ID_START = 0x17,
HIF_CNF_ID_BEACON_TRANSMIT = 0x18,
HIF_CNF_ID_UPDATE_IE = 0x1b,
HIF_CNF_ID_MAP_LINK = 0x1c,
};
enum wfx_hif_indications_ids {
HIF_IND_ID_RX = 0x84,
HIF_IND_ID_SCAN_CMPL = 0x86,
HIF_IND_ID_JOIN_COMPLETE = 0x8f,
HIF_IND_ID_SET_PM_MODE_CMPL = 0x89,
HIF_IND_ID_SUSPEND_RESUME_TX = 0x8c,
HIF_IND_ID_EVENT = 0x85
};
struct wfx_hif_req_reset {
u8 reset_stat:1;
u8 reset_all_int:1;
u8 reserved1:6;
u8 reserved2[3];
} __packed;
struct wfx_hif_cnf_reset {
__le32 status;
} __packed;
struct wfx_hif_req_read_mib {
__le16 mib_id;
__le16 reserved;
} __packed;
struct wfx_hif_cnf_read_mib {
__le32 status;
__le16 mib_id;
__le16 length;
u8 mib_data[];
} __packed;
struct wfx_hif_req_write_mib {
__le16 mib_id;
__le16 length;
u8 mib_data[];
} __packed;
struct wfx_hif_cnf_write_mib {
__le32 status;
} __packed;
struct wfx_hif_req_update_ie {
u8 beacon:1;
u8 probe_resp:1;
u8 probe_req:1;
u8 reserved1:5;
u8 reserved2;
__le16 num_ies;
u8 ie[];
} __packed;
struct wfx_hif_cnf_update_ie {
__le32 status;
} __packed;
struct wfx_hif_ssid_def {
__le32 ssid_length;
u8 ssid[IEEE80211_MAX_SSID_LEN];
} __packed;
#define HIF_API_MAX_NB_SSIDS 2
#define HIF_API_MAX_NB_CHANNELS 14
struct wfx_hif_req_start_scan_alt {
u8 band;
u8 maintain_current_bss:1;
u8 periodic:1;
u8 reserved1:6;
u8 disallow_ps:1;
u8 reserved2:1;
u8 short_preamble:1;
u8 reserved3:5;
u8 max_transmit_rate;
__le16 periodic_interval;
u8 reserved4;
s8 periodic_rssi_thr;
u8 num_of_probe_requests;
u8 probe_delay;
u8 num_of_ssids;
u8 num_of_channels;
__le32 min_channel_time;
__le32 max_channel_time;
__le32 tx_power_level; /* signed value */
struct wfx_hif_ssid_def ssid_def[HIF_API_MAX_NB_SSIDS];
u8 channel_list[];
} __packed;
struct wfx_hif_cnf_start_scan {
__le32 status;
} __packed;
struct wfx_hif_cnf_stop_scan {
__le32 status;
} __packed;
enum wfx_hif_pm_mode_status {
HIF_PM_MODE_ACTIVE = 0x0,
HIF_PM_MODE_PS = 0x1,
HIF_PM_MODE_UNDETERMINED = 0x2
};
struct wfx_hif_ind_scan_cmpl {
__le32 status;
u8 pm_mode;
u8 num_channels_completed;
__le16 reserved;
} __packed;
enum wfx_hif_queue_id {
HIF_QUEUE_ID_BACKGROUND = 0x0,
HIF_QUEUE_ID_BESTEFFORT = 0x1,
HIF_QUEUE_ID_VIDEO = 0x2,
HIF_QUEUE_ID_VOICE = 0x3
};
enum wfx_hif_frame_format {
HIF_FRAME_FORMAT_NON_HT = 0x0,
HIF_FRAME_FORMAT_MIXED_FORMAT_HT = 0x1,
HIF_FRAME_FORMAT_GF_HT_11N = 0x2
};
struct wfx_hif_req_tx {
/* packet_id is not interpreted by the device, so it is not necessary to declare it little
* endian
*/
u32 packet_id;
u8 max_tx_rate;
u8 queue_id:2;
u8 peer_sta_id:4;
u8 reserved1:2;
u8 more:1;
u8 fc_offset:3;
u8 after_dtim:1;
u8 reserved2:3;
u8 start_exp:1;
u8 reserved3:3;
u8 retry_policy_index:4;
__le32 reserved4;
__le32 expire_time;
u8 frame_format:4;
u8 fec_coding:1;
u8 short_gi:1;
u8 reserved5:1;
u8 stbc:1;
u8 reserved6;
u8 aggregation:1;
u8 reserved7:7;
u8 reserved8;
u8 frame[];
} __packed;
enum wfx_hif_qos_ackplcy {
HIF_QOS_ACKPLCY_NORMAL = 0x0,
HIF_QOS_ACKPLCY_TXNOACK = 0x1,
HIF_QOS_ACKPLCY_NOEXPACK = 0x2,
HIF_QOS_ACKPLCY_BLCKACK = 0x3
};
struct wfx_hif_cnf_tx {
__le32 status;
/* packet_id is copied from struct wfx_hif_req_tx without been interpreted by the device, so
* it is not necessary to declare it little endian
*/
u32 packet_id;
u8 txed_rate;
u8 ack_failures;
u8 aggr:1;
u8 requeue:1;
u8 ack_policy:2;
u8 txop_limit:1;
u8 reserved1:3;
u8 reserved2;
__le32 media_delay;
__le32 tx_queue_delay;
} __packed;
struct wfx_hif_cnf_multi_transmit {
u8 num_tx_confs;
u8 reserved[3];
struct wfx_hif_cnf_tx tx_conf_payload[];
} __packed;
enum wfx_hif_ri_flags_encrypt {
HIF_RI_FLAGS_UNENCRYPTED = 0x0,
HIF_RI_FLAGS_WEP_ENCRYPTED = 0x1,
HIF_RI_FLAGS_TKIP_ENCRYPTED = 0x2,
HIF_RI_FLAGS_AES_ENCRYPTED = 0x3,
HIF_RI_FLAGS_WAPI_ENCRYPTED = 0x4
};
struct wfx_hif_ind_rx {
__le32 status;
u8 channel_number;
u8 reserved1;
u8 rxed_rate;
u8 rcpi_rssi;
u8 encryp:3;
u8 in_aggr:1;
u8 first_aggr:1;
u8 last_aggr:1;
u8 defrag:1;
u8 beacon:1;
u8 tim:1;
u8 bitmap:1;
u8 match_ssid:1;
u8 match_bssid:1;
u8 more:1;
u8 reserved2:1;
u8 ht:1;
u8 stbc:1;
u8 match_uc_addr:1;
u8 match_mc_addr:1;
u8 match_bc_addr:1;
u8 key_type:1;
u8 key_index:4;
u8 reserved3:1;
u8 peer_sta_id:4;
u8 reserved4:2;
u8 reserved5:1;
u8 frame[];
} __packed;
struct wfx_hif_req_edca_queue_params {
u8 queue_id;
u8 reserved1;
u8 aifsn;
u8 reserved2;
__le16 cw_min;
__le16 cw_max;
__le16 tx_op_limit;
__le16 allowed_medium_time;
__le32 reserved3;
} __packed;
struct wfx_hif_cnf_edca_queue_params {
__le32 status;
} __packed;
struct wfx_hif_req_join {
u8 infrastructure_bss_mode:1;
u8 reserved1:7;
u8 band;
u8 channel_number;
u8 reserved2;
u8 bssid[ETH_ALEN];
__le16 atim_window;
u8 short_preamble:1;
u8 reserved3:7;
u8 probe_for_join;
u8 reserved4;
u8 reserved5:2;
u8 force_no_beacon:1;
u8 force_with_ind:1;
u8 reserved6:4;
__le32 ssid_length;
u8 ssid[IEEE80211_MAX_SSID_LEN];
__le32 beacon_interval;
__le32 basic_rate_set;
} __packed;
struct wfx_hif_cnf_join {
__le32 status;
} __packed;
struct wfx_hif_ind_join_complete {
__le32 status;
} __packed;
struct wfx_hif_req_set_bss_params {
u8 lost_count_only:1;
u8 reserved:7;
u8 beacon_lost_count;
__le16 aid;
__le32 operational_rate_set;
} __packed;
struct wfx_hif_cnf_set_bss_params {
__le32 status;
} __packed;
struct wfx_hif_req_set_pm_mode {
u8 enter_psm:1;
u8 reserved:6;
u8 fast_psm:1;
u8 fast_psm_idle_period;
u8 ap_psm_change_period;
u8 min_auto_ps_poll_period;
} __packed;
struct wfx_hif_cnf_set_pm_mode {
__le32 status;
} __packed;
struct wfx_hif_ind_set_pm_mode_cmpl {
__le32 status;
u8 pm_mode;
u8 reserved[3];
} __packed;
struct wfx_hif_req_start {
u8 mode;
u8 band;
u8 channel_number;
u8 reserved1;
__le32 reserved2;
__le32 beacon_interval;
u8 dtim_period;
u8 short_preamble:1;
u8 reserved3:7;
u8 reserved4;
u8 ssid_length;
u8 ssid[IEEE80211_MAX_SSID_LEN];
__le32 basic_rate_set;
} __packed;
struct wfx_hif_cnf_start {
__le32 status;
} __packed;
struct wfx_hif_req_beacon_transmit {
u8 enable_beaconing;
u8 reserved[3];
} __packed;
struct wfx_hif_cnf_beacon_transmit {
__le32 status;
} __packed;
#define HIF_LINK_ID_MAX 14
#define HIF_LINK_ID_NOT_ASSOCIATED (HIF_LINK_ID_MAX + 1)
struct wfx_hif_req_map_link {
u8 mac_addr[ETH_ALEN];
u8 unmap:1;
u8 mfpc:1;
u8 reserved:6;
u8 peer_sta_id;
} __packed;
struct wfx_hif_cnf_map_link {
__le32 status;
} __packed;
struct wfx_hif_ind_suspend_resume_tx {
u8 resume:1;
u8 reserved1:2;
u8 bc_mc_only:1;
u8 reserved2:4;
u8 reserved3;
__le16 peer_sta_set;
} __packed;
#define MAX_KEY_ENTRIES 24
#define HIF_API_WEP_KEY_DATA_SIZE 16
#define HIF_API_TKIP_KEY_DATA_SIZE 16
#define HIF_API_RX_MIC_KEY_SIZE 8
#define HIF_API_TX_MIC_KEY_SIZE 8
#define HIF_API_AES_KEY_DATA_SIZE 16
#define HIF_API_WAPI_KEY_DATA_SIZE 16
#define HIF_API_MIC_KEY_DATA_SIZE 16
#define HIF_API_IGTK_KEY_DATA_SIZE 16
#define HIF_API_RX_SEQUENCE_COUNTER_SIZE 8
#define HIF_API_IPN_SIZE 8
enum wfx_hif_key_type {
HIF_KEY_TYPE_WEP_DEFAULT = 0x0,
HIF_KEY_TYPE_WEP_PAIRWISE = 0x1,
HIF_KEY_TYPE_TKIP_GROUP = 0x2,
HIF_KEY_TYPE_TKIP_PAIRWISE = 0x3,
HIF_KEY_TYPE_AES_GROUP = 0x4,
HIF_KEY_TYPE_AES_PAIRWISE = 0x5,
HIF_KEY_TYPE_WAPI_GROUP = 0x6,
HIF_KEY_TYPE_WAPI_PAIRWISE = 0x7,
HIF_KEY_TYPE_IGTK_GROUP = 0x8,
HIF_KEY_TYPE_NONE = 0x9
};
struct wfx_hif_wep_pairwise_key {
u8 peer_address[ETH_ALEN];
u8 reserved;
u8 key_length;
u8 key_data[HIF_API_WEP_KEY_DATA_SIZE];
} __packed;
struct wfx_hif_wep_group_key {
u8 key_id;
u8 key_length;
u8 reserved[2];
u8 key_data[HIF_API_WEP_KEY_DATA_SIZE];
} __packed;
struct wfx_hif_tkip_pairwise_key {
u8 peer_address[ETH_ALEN];
u8 reserved[2];
u8 tkip_key_data[HIF_API_TKIP_KEY_DATA_SIZE];
u8 rx_mic_key[HIF_API_RX_MIC_KEY_SIZE];
u8 tx_mic_key[HIF_API_TX_MIC_KEY_SIZE];
} __packed;
struct wfx_hif_tkip_group_key {
u8 tkip_key_data[HIF_API_TKIP_KEY_DATA_SIZE];
u8 rx_mic_key[HIF_API_RX_MIC_KEY_SIZE];
u8 key_id;
u8 reserved[3];
u8 rx_sequence_counter[HIF_API_RX_SEQUENCE_COUNTER_SIZE];
} __packed;
struct wfx_hif_aes_pairwise_key {
u8 peer_address[ETH_ALEN];
u8 reserved[2];
u8 aes_key_data[HIF_API_AES_KEY_DATA_SIZE];
} __packed;
struct wfx_hif_aes_group_key {
u8 aes_key_data[HIF_API_AES_KEY_DATA_SIZE];
u8 key_id;
u8 reserved[3];
u8 rx_sequence_counter[HIF_API_RX_SEQUENCE_COUNTER_SIZE];
} __packed;
struct wfx_hif_wapi_pairwise_key {
u8 peer_address[ETH_ALEN];
u8 key_id;
u8 reserved;
u8 wapi_key_data[HIF_API_WAPI_KEY_DATA_SIZE];
u8 mic_key_data[HIF_API_MIC_KEY_DATA_SIZE];
} __packed;
struct wfx_hif_wapi_group_key {
u8 wapi_key_data[HIF_API_WAPI_KEY_DATA_SIZE];
u8 mic_key_data[HIF_API_MIC_KEY_DATA_SIZE];
u8 key_id;
u8 reserved[3];
} __packed;
struct wfx_hif_igtk_group_key {
u8 igtk_key_data[HIF_API_IGTK_KEY_DATA_SIZE];
u8 key_id;
u8 reserved[3];
u8 ipn[HIF_API_IPN_SIZE];
} __packed;
struct wfx_hif_req_add_key {
u8 type;
u8 entry_index;
u8 int_id:2;
u8 reserved1:6;
u8 reserved2;
union {
struct wfx_hif_wep_pairwise_key wep_pairwise_key;
struct wfx_hif_wep_group_key wep_group_key;
struct wfx_hif_tkip_pairwise_key tkip_pairwise_key;
struct wfx_hif_tkip_group_key tkip_group_key;
struct wfx_hif_aes_pairwise_key aes_pairwise_key;
struct wfx_hif_aes_group_key aes_group_key;
struct wfx_hif_wapi_pairwise_key wapi_pairwise_key;
struct wfx_hif_wapi_group_key wapi_group_key;
struct wfx_hif_igtk_group_key igtk_group_key;
} key;
} __packed;
struct wfx_hif_cnf_add_key {
__le32 status;
} __packed;
struct wfx_hif_req_remove_key {
u8 entry_index;
u8 reserved[3];
} __packed;
struct wfx_hif_cnf_remove_key {
__le32 status;
} __packed;
enum wfx_hif_event_ind {
HIF_EVENT_IND_BSSLOST = 0x1,
HIF_EVENT_IND_BSSREGAINED = 0x2,
HIF_EVENT_IND_RCPI_RSSI = 0x3,
HIF_EVENT_IND_PS_MODE_ERROR = 0x4,
HIF_EVENT_IND_INACTIVITY = 0x5
};
enum wfx_hif_ps_mode_error {
HIF_PS_ERROR_NO_ERROR = 0,
HIF_PS_ERROR_AP_NOT_RESP_TO_POLL = 1,
HIF_PS_ERROR_AP_NOT_RESP_TO_UAPSD_TRIGGER = 2,
HIF_PS_ERROR_AP_SENT_UNICAST_IN_DOZE = 3,
HIF_PS_ERROR_AP_NO_DATA_AFTER_TIM = 4
};
struct wfx_hif_ind_event {
__le32 event_id;
union {
u8 rcpi_rssi;
__le32 ps_mode_error;
__le32 peer_sta_set;
} event_data;
} __packed;
#endif
@@ -0,0 +1,252 @@
/* SPDX-License-Identifier: GPL-2.0-only or Apache-2.0 */
/*
* WF200 hardware interface definitions
*
* Copyright (c) 2018-2020, Silicon Laboratories Inc.
*/
#ifndef WFX_HIF_API_GENERAL_H
#define WFX_HIF_API_GENERAL_H
#include <linux/types.h>
#include <linux/if_ether.h>
#define HIF_ID_IS_INDICATION 0x80
#define HIF_COUNTER_MAX 7
struct wfx_hif_msg {
__le16 len;
u8 id;
u8 reserved:1;
u8 interface:2;
u8 seqnum:3;
u8 encrypted:2;
u8 body[];
} __packed;
enum wfx_hif_general_requests_ids {
HIF_REQ_ID_CONFIGURATION = 0x09,
HIF_REQ_ID_CONTROL_GPIO = 0x26,
HIF_REQ_ID_SET_SL_MAC_KEY = 0x27,
HIF_REQ_ID_SL_EXCHANGE_PUB_KEYS = 0x28,
HIF_REQ_ID_SL_CONFIGURE = 0x29,
HIF_REQ_ID_PREVENT_ROLLBACK = 0x2a,
HIF_REQ_ID_PTA_SETTINGS = 0x2b,
HIF_REQ_ID_PTA_PRIORITY = 0x2c,
HIF_REQ_ID_PTA_STATE = 0x2d,
HIF_REQ_ID_SHUT_DOWN = 0x32,
};
enum wfx_hif_general_confirmations_ids {
HIF_CNF_ID_CONFIGURATION = 0x09,
HIF_CNF_ID_CONTROL_GPIO = 0x26,
HIF_CNF_ID_SET_SL_MAC_KEY = 0x27,
HIF_CNF_ID_SL_EXCHANGE_PUB_KEYS = 0x28,
HIF_CNF_ID_SL_CONFIGURE = 0x29,
HIF_CNF_ID_PREVENT_ROLLBACK = 0x2a,
HIF_CNF_ID_PTA_SETTINGS = 0x2b,
HIF_CNF_ID_PTA_PRIORITY = 0x2c,
HIF_CNF_ID_PTA_STATE = 0x2d,
HIF_CNF_ID_SHUT_DOWN = 0x32,
};
enum wfx_hif_general_indications_ids {
HIF_IND_ID_EXCEPTION = 0xe0,
HIF_IND_ID_STARTUP = 0xe1,
HIF_IND_ID_WAKEUP = 0xe2,
HIF_IND_ID_GENERIC = 0xe3,
HIF_IND_ID_ERROR = 0xe4,
HIF_IND_ID_SL_EXCHANGE_PUB_KEYS = 0xe5
};
#define HIF_STATUS_SUCCESS (cpu_to_le32(0x0000))
#define HIF_STATUS_FAIL (cpu_to_le32(0x0001))
#define HIF_STATUS_INVALID_PARAMETER (cpu_to_le32(0x0002))
#define HIF_STATUS_WARNING (cpu_to_le32(0x0003))
#define HIF_STATUS_UNKNOWN_REQUEST (cpu_to_le32(0x0004))
#define HIF_STATUS_RX_FAIL_DECRYPT (cpu_to_le32(0x0010))
#define HIF_STATUS_RX_FAIL_MIC (cpu_to_le32(0x0011))
#define HIF_STATUS_RX_FAIL_NO_KEY (cpu_to_le32(0x0012))
#define HIF_STATUS_TX_FAIL_RETRIES (cpu_to_le32(0x0013))
#define HIF_STATUS_TX_FAIL_TIMEOUT (cpu_to_le32(0x0014))
#define HIF_STATUS_TX_FAIL_REQUEUE (cpu_to_le32(0x0015))
#define HIF_STATUS_REFUSED (cpu_to_le32(0x0016))
#define HIF_STATUS_BUSY (cpu_to_le32(0x0017))
#define HIF_STATUS_SLK_SET_KEY_SUCCESS (cpu_to_le32(0x005A))
#define HIF_STATUS_SLK_SET_KEY_ALREADY_BURNED (cpu_to_le32(0x006B))
#define HIF_STATUS_SLK_SET_KEY_DISALLOWED_MODE (cpu_to_le32(0x007C))
#define HIF_STATUS_SLK_SET_KEY_UNKNOWN_MODE (cpu_to_le32(0x008D))
#define HIF_STATUS_SLK_NEGO_SUCCESS (cpu_to_le32(0x009E))
#define HIF_STATUS_SLK_NEGO_FAILED (cpu_to_le32(0x00AF))
#define HIF_STATUS_ROLLBACK_SUCCESS (cpu_to_le32(0x1234))
#define HIF_STATUS_ROLLBACK_FAIL (cpu_to_le32(0x1256))
enum wfx_hif_api_rate_index {
API_RATE_INDEX_B_1MBPS = 0,
API_RATE_INDEX_B_2MBPS = 1,
API_RATE_INDEX_B_5P5MBPS = 2,
API_RATE_INDEX_B_11MBPS = 3,
API_RATE_INDEX_PBCC_22MBPS = 4,
API_RATE_INDEX_PBCC_33MBPS = 5,
API_RATE_INDEX_G_6MBPS = 6,
API_RATE_INDEX_G_9MBPS = 7,
API_RATE_INDEX_G_12MBPS = 8,
API_RATE_INDEX_G_18MBPS = 9,
API_RATE_INDEX_G_24MBPS = 10,
API_RATE_INDEX_G_36MBPS = 11,
API_RATE_INDEX_G_48MBPS = 12,
API_RATE_INDEX_G_54MBPS = 13,
API_RATE_INDEX_N_6P5MBPS = 14,
API_RATE_INDEX_N_13MBPS = 15,
API_RATE_INDEX_N_19P5MBPS = 16,
API_RATE_INDEX_N_26MBPS = 17,
API_RATE_INDEX_N_39MBPS = 18,
API_RATE_INDEX_N_52MBPS = 19,
API_RATE_INDEX_N_58P5MBPS = 20,
API_RATE_INDEX_N_65MBPS = 21,
API_RATE_NUM_ENTRIES = 22
};
struct wfx_hif_ind_startup {
__le32 status;
__le16 hardware_id;
u8 opn[14];
u8 uid[8];
__le16 num_inp_ch_bufs;
__le16 size_inp_ch_buf;
u8 num_links_ap;
u8 num_interfaces;
u8 mac_addr[2][ETH_ALEN];
u8 api_version_minor;
u8 api_version_major;
u8 link_mode:2;
u8 reserved1:6;
u8 reserved2;
u8 reserved3;
u8 reserved4;
u8 firmware_build;
u8 firmware_minor;
u8 firmware_major;
u8 firmware_type;
u8 disabled_channel_list[2];
u8 region_sel_mode:4;
u8 reserved5:4;
u8 phy1_region:3;
u8 phy0_region:3;
u8 otp_phy_ver:2;
__le32 supported_rate_mask;
u8 firmware_label[128];
} __packed;
struct wfx_hif_ind_wakeup {
} __packed;
struct wfx_hif_req_configuration {
__le16 length;
u8 pds_data[];
} __packed;
struct wfx_hif_cnf_configuration {
__le32 status;
} __packed;
enum wfx_hif_gpio_mode {
HIF_GPIO_MODE_D0 = 0x0,
HIF_GPIO_MODE_D1 = 0x1,
HIF_GPIO_MODE_OD0 = 0x2,
HIF_GPIO_MODE_OD1 = 0x3,
HIF_GPIO_MODE_TRISTATE = 0x4,
HIF_GPIO_MODE_TOGGLE = 0x5,
HIF_GPIO_MODE_READ = 0x6
};
struct wfx_hif_req_control_gpio {
u8 gpio_label;
u8 gpio_mode;
} __packed;
struct wfx_hif_cnf_control_gpio {
__le32 status;
__le32 value;
} __packed;
enum wfx_hif_generic_indication_type {
HIF_GENERIC_INDICATION_TYPE_RAW = 0x0,
HIF_GENERIC_INDICATION_TYPE_STRING = 0x1,
HIF_GENERIC_INDICATION_TYPE_RX_STATS = 0x2,
HIF_GENERIC_INDICATION_TYPE_TX_POWER_LOOP_INFO = 0x3,
};
struct wfx_hif_rx_stats {
__le32 nb_rx_frame;
__le32 nb_crc_frame;
__le32 per_total;
__le32 throughput;
__le32 nb_rx_by_rate[API_RATE_NUM_ENTRIES];
__le16 per[API_RATE_NUM_ENTRIES];
__le16 snr[API_RATE_NUM_ENTRIES]; /* signed value */
__le16 rssi[API_RATE_NUM_ENTRIES]; /* signed value */
__le16 cfo[API_RATE_NUM_ENTRIES]; /* signed value */
__le32 date;
__le32 pwr_clk_freq;
u8 is_ext_pwr_clk;
s8 current_temp;
} __packed;
struct wfx_hif_tx_power_loop_info {
__le16 tx_gain_dig;
__le16 tx_gain_pa;
__le16 target_pout; /* signed value */
__le16 p_estimation; /* signed value */
__le16 vpdet;
u8 measurement_index;
u8 reserved;
} __packed;
struct wfx_hif_ind_generic {
__le32 type;
union {
struct wfx_hif_rx_stats rx_stats;
struct wfx_hif_tx_power_loop_info tx_power_loop_info;
} data;
} __packed;
enum wfx_hif_error {
HIF_ERROR_FIRMWARE_ROLLBACK = 0x00,
HIF_ERROR_FIRMWARE_DEBUG_ENABLED = 0x01,
HIF_ERROR_SLK_OUTDATED_SESSION_KEY = 0x02,
HIF_ERROR_SLK_SESSION_KEY = 0x03,
HIF_ERROR_OOR_VOLTAGE = 0x04,
HIF_ERROR_PDS_PAYLOAD = 0x05,
HIF_ERROR_OOR_TEMPERATURE = 0x06,
HIF_ERROR_SLK_REQ_DURING_KEY_EXCHANGE = 0x07,
HIF_ERROR_SLK_MULTI_TX_UNSUPPORTED = 0x08,
HIF_ERROR_SLK_OVERFLOW = 0x09,
HIF_ERROR_SLK_DECRYPTION = 0x0a,
HIF_ERROR_SLK_WRONG_ENCRYPTION_STATE = 0x0b,
HIF_ERROR_HIF_BUS_FREQUENCY_TOO_LOW = 0x0c,
HIF_ERROR_HIF_RX_DATA_TOO_LARGE = 0x0e,
HIF_ERROR_HIF_TX_QUEUE_FULL = 0x0d,
HIF_ERROR_HIF_BUS = 0x0f,
HIF_ERROR_PDS_TESTFEATURE = 0x10,
HIF_ERROR_SLK_UNCONFIGURED = 0x11,
};
struct wfx_hif_ind_error {
__le32 type;
u8 data[];
} __packed;
struct wfx_hif_ind_exception {
__le32 type;
u8 data[];
} __packed;
enum wfx_hif_secure_link_state {
SEC_LINK_UNAVAILABLE = 0x0,
SEC_LINK_RESERVED = 0x1,
SEC_LINK_EVAL = 0x2,
SEC_LINK_ENFORCED = 0x3
};
#endif
@@ -0,0 +1,346 @@
/* SPDX-License-Identifier: GPL-2.0-only or Apache-2.0 */
/*
* WF200 hardware interface definitions
*
* Copyright (c) 2018-2020, Silicon Laboratories Inc.
*/
#ifndef WFX_HIF_API_MIB_H
#define WFX_HIF_API_MIB_H
#include "hif_api_general.h"
#define HIF_API_IPV4_ADDRESS_SIZE 4
#define HIF_API_IPV6_ADDRESS_SIZE 16
enum wfx_hif_mib_ids {
HIF_MIB_ID_GL_OPERATIONAL_POWER_MODE = 0x2000,
HIF_MIB_ID_GL_BLOCK_ACK_INFO = 0x2001,
HIF_MIB_ID_GL_SET_MULTI_MSG = 0x2002,
HIF_MIB_ID_CCA_CONFIG = 0x2003,
HIF_MIB_ID_ETHERTYPE_DATAFRAME_CONDITION = 0x2010,
HIF_MIB_ID_PORT_DATAFRAME_CONDITION = 0x2011,
HIF_MIB_ID_MAGIC_DATAFRAME_CONDITION = 0x2012,
HIF_MIB_ID_MAC_ADDR_DATAFRAME_CONDITION = 0x2013,
HIF_MIB_ID_IPV4_ADDR_DATAFRAME_CONDITION = 0x2014,
HIF_MIB_ID_IPV6_ADDR_DATAFRAME_CONDITION = 0x2015,
HIF_MIB_ID_UC_MC_BC_DATAFRAME_CONDITION = 0x2016,
HIF_MIB_ID_CONFIG_DATA_FILTER = 0x2017,
HIF_MIB_ID_SET_DATA_FILTERING = 0x2018,
HIF_MIB_ID_ARP_IP_ADDRESSES_TABLE = 0x2019,
HIF_MIB_ID_NS_IP_ADDRESSES_TABLE = 0x201A,
HIF_MIB_ID_RX_FILTER = 0x201B,
HIF_MIB_ID_BEACON_FILTER_TABLE = 0x201C,
HIF_MIB_ID_BEACON_FILTER_ENABLE = 0x201D,
HIF_MIB_ID_GRP_SEQ_COUNTER = 0x2030,
HIF_MIB_ID_TSF_COUNTER = 0x2031,
HIF_MIB_ID_STATISTICS_TABLE = 0x2032,
HIF_MIB_ID_COUNTERS_TABLE = 0x2033,
HIF_MIB_ID_MAX_TX_POWER_LEVEL = 0x2034,
HIF_MIB_ID_EXTENDED_COUNTERS_TABLE = 0x2035,
HIF_MIB_ID_DOT11_MAC_ADDRESS = 0x2040,
HIF_MIB_ID_DOT11_MAX_TRANSMIT_MSDU_LIFETIME = 0x2041,
HIF_MIB_ID_DOT11_MAX_RECEIVE_LIFETIME = 0x2042,
HIF_MIB_ID_DOT11_WEP_DEFAULT_KEY_ID = 0x2043,
HIF_MIB_ID_DOT11_RTS_THRESHOLD = 0x2044,
HIF_MIB_ID_SLOT_TIME = 0x2045,
HIF_MIB_ID_CURRENT_TX_POWER_LEVEL = 0x2046,
HIF_MIB_ID_NON_ERP_PROTECTION = 0x2047,
HIF_MIB_ID_TEMPLATE_FRAME = 0x2048,
HIF_MIB_ID_BEACON_WAKEUP_PERIOD = 0x2049,
HIF_MIB_ID_RCPI_RSSI_THRESHOLD = 0x204A,
HIF_MIB_ID_BLOCK_ACK_POLICY = 0x204B,
HIF_MIB_ID_OVERRIDE_INTERNAL_TX_RATE = 0x204C,
HIF_MIB_ID_SET_ASSOCIATION_MODE = 0x204D,
HIF_MIB_ID_SET_UAPSD_INFORMATION = 0x204E,
HIF_MIB_ID_SET_TX_RATE_RETRY_POLICY = 0x204F,
HIF_MIB_ID_PROTECTED_MGMT_POLICY = 0x2050,
HIF_MIB_ID_SET_HT_PROTECTION = 0x2051,
HIF_MIB_ID_KEEP_ALIVE_PERIOD = 0x2052,
HIF_MIB_ID_ARP_KEEP_ALIVE_PERIOD = 0x2053,
HIF_MIB_ID_INACTIVITY_TIMER = 0x2054,
HIF_MIB_ID_INTERFACE_PROTECTION = 0x2055,
HIF_MIB_ID_BEACON_STATS = 0x2056,
};
enum wfx_hif_op_power_mode {
HIF_OP_POWER_MODE_ACTIVE = 0x0,
HIF_OP_POWER_MODE_DOZE = 0x1,
HIF_OP_POWER_MODE_QUIESCENT = 0x2
};
struct wfx_hif_mib_gl_operational_power_mode {
u8 power_mode:4;
u8 reserved1:3;
u8 wup_ind_activation:1;
u8 reserved2[3];
} __packed;
struct wfx_hif_mib_gl_set_multi_msg {
u8 enable_multi_tx_conf:1;
u8 reserved1:7;
u8 reserved2[3];
} __packed;
enum wfx_hif_arp_ns_frame_treatment {
HIF_ARP_NS_FILTERING_DISABLE = 0x0,
HIF_ARP_NS_FILTERING_ENABLE = 0x1,
HIF_ARP_NS_REPLY_ENABLE = 0x2
};
struct wfx_hif_mib_arp_ip_addr_table {
u8 condition_idx;
u8 arp_enable;
u8 reserved[2];
u8 ipv4_address[HIF_API_IPV4_ADDRESS_SIZE];
} __packed;
struct wfx_hif_mib_rx_filter {
u8 reserved1:1;
u8 bssid_filter:1;
u8 reserved2:1;
u8 fwd_probe_req:1;
u8 keep_alive_filter:1;
u8 reserved3:3;
u8 reserved4[3];
} __packed;
struct wfx_hif_ie_table_entry {
u8 ie_id;
u8 has_changed:1;
u8 no_longer:1;
u8 has_appeared:1;
u8 reserved:1;
u8 num_match_data:4;
u8 oui[3];
u8 match_data[3];
} __packed;
struct wfx_hif_mib_bcn_filter_table {
__le32 num_of_info_elmts;
struct wfx_hif_ie_table_entry ie_table[];
} __packed;
enum wfx_hif_beacon_filter {
HIF_BEACON_FILTER_DISABLE = 0x0,
HIF_BEACON_FILTER_ENABLE = 0x1,
HIF_BEACON_FILTER_AUTO_ERP = 0x2
};
struct wfx_hif_mib_bcn_filter_enable {
__le32 enable;
__le32 bcn_count;
} __packed;
struct wfx_hif_mib_extended_count_table {
__le32 count_drop_plcp;
__le32 count_drop_fcs;
__le32 count_tx_frames;
__le32 count_rx_frames;
__le32 count_rx_frames_failed;
__le32 count_drop_decryption;
__le32 count_drop_tkip_mic;
__le32 count_drop_no_key;
__le32 count_tx_frames_multicast;
__le32 count_tx_frames_success;
__le32 count_tx_frames_failed;
__le32 count_tx_frames_retried;
__le32 count_tx_frames_multi_retried;
__le32 count_drop_duplicate;
__le32 count_rts_success;
__le32 count_rts_failed;
__le32 count_ack_failed;
__le32 count_rx_frames_multicast;
__le32 count_rx_frames_success;
__le32 count_drop_cmac_icv;
__le32 count_drop_cmac_replay;
__le32 count_drop_ccmp_replay;
__le32 count_drop_bip_mic;
__le32 count_rx_bcn_success;
__le32 count_rx_bcn_miss;
__le32 count_rx_bcn_dtim;
__le32 count_rx_bcn_dtim_aid0_clr;
__le32 count_rx_bcn_dtim_aid0_set;
__le32 reserved[12];
} __packed;
struct wfx_hif_mib_count_table {
__le32 count_drop_plcp;
__le32 count_drop_fcs;
__le32 count_tx_frames;
__le32 count_rx_frames;
__le32 count_rx_frames_failed;
__le32 count_drop_decryption;
__le32 count_drop_tkip_mic;
__le32 count_drop_no_key;
__le32 count_tx_frames_multicast;
__le32 count_tx_frames_success;
__le32 count_tx_frames_failed;
__le32 count_tx_frames_retried;
__le32 count_tx_frames_multi_retried;
__le32 count_drop_duplicate;
__le32 count_rts_success;
__le32 count_rts_failed;
__le32 count_ack_failed;
__le32 count_rx_frames_multicast;
__le32 count_rx_frames_success;
__le32 count_drop_cmac_icv;
__le32 count_drop_cmac_replay;
__le32 count_drop_ccmp_replay;
__le32 count_drop_bip_mic;
} __packed;
struct wfx_hif_mib_mac_address {
u8 mac_addr[ETH_ALEN];
__le16 reserved;
} __packed;
struct wfx_hif_mib_wep_default_key_id {
u8 wep_default_key_id;
u8 reserved[3];
} __packed;
struct wfx_hif_mib_dot11_rts_threshold {
__le32 threshold;
} __packed;
struct wfx_hif_mib_slot_time {
__le32 slot_time;
} __packed;
struct wfx_hif_mib_current_tx_power_level {
__le32 power_level; /* signed value */
} __packed;
struct wfx_hif_mib_non_erp_protection {
u8 use_cts_to_self:1;
u8 reserved1:7;
u8 reserved2[3];
} __packed;
enum wfx_hif_tmplt {
HIF_TMPLT_PRBREQ = 0x0,
HIF_TMPLT_BCN = 0x1,
HIF_TMPLT_NULL = 0x2,
HIF_TMPLT_QOSNUL = 0x3,
HIF_TMPLT_PSPOLL = 0x4,
HIF_TMPLT_PRBRES = 0x5,
HIF_TMPLT_ARP = 0x6,
HIF_TMPLT_NA = 0x7
};
#define HIF_API_MAX_TEMPLATE_FRAME_SIZE 700
struct wfx_hif_mib_template_frame {
u8 frame_type;
u8 init_rate:7;
u8 mode:1;
__le16 frame_length;
u8 frame[];
} __packed;
struct wfx_hif_mib_beacon_wake_up_period {
u8 wakeup_period_min;
u8 receive_dtim:1;
u8 reserved1:7;
u8 wakeup_period_max;
u8 reserved2;
} __packed;
struct wfx_hif_mib_rcpi_rssi_threshold {
u8 detection:1;
u8 rcpi_rssi:1;
u8 upperthresh:1;
u8 lowerthresh:1;
u8 reserved:4;
u8 lower_threshold;
u8 upper_threshold;
u8 rolling_average_count;
} __packed;
#define DEFAULT_BA_MAX_RX_BUFFER_SIZE 16
struct wfx_hif_mib_block_ack_policy {
u8 block_ack_tx_tid_policy;
u8 reserved1;
u8 block_ack_rx_tid_policy;
u8 block_ack_rx_max_buffer_size;
} __packed;
enum wfx_hif_mpdu_start_spacing {
HIF_MPDU_START_SPACING_NO_RESTRIC = 0x0,
HIF_MPDU_START_SPACING_QUARTER = 0x1,
HIF_MPDU_START_SPACING_HALF = 0x2,
HIF_MPDU_START_SPACING_ONE = 0x3,
HIF_MPDU_START_SPACING_TWO = 0x4,
HIF_MPDU_START_SPACING_FOUR = 0x5,
HIF_MPDU_START_SPACING_EIGHT = 0x6,
HIF_MPDU_START_SPACING_SIXTEEN = 0x7
};
struct wfx_hif_mib_set_association_mode {
u8 preambtype_use:1;
u8 mode:1;
u8 rateset:1;
u8 spacing:1;
u8 reserved1:4;
u8 short_preamble:1;
u8 reserved2:7;
u8 greenfield:1;
u8 reserved3:7;
u8 mpdu_start_spacing;
__le32 basic_rate_set;
} __packed;
struct wfx_hif_mib_set_uapsd_information {
u8 trig_bckgrnd:1;
u8 trig_be:1;
u8 trig_video:1;
u8 trig_voice:1;
u8 reserved1:4;
u8 deliv_bckgrnd:1;
u8 deliv_be:1;
u8 deliv_video:1;
u8 deliv_voice:1;
u8 reserved2:4;
__le16 min_auto_trigger_interval;
__le16 max_auto_trigger_interval;
__le16 auto_trigger_step;
} __packed;
struct wfx_hif_tx_rate_retry_policy {
u8 policy_index;
u8 short_retry_count;
u8 long_retry_count;
u8 first_rate_sel:2;
u8 terminate:1;
u8 count_init:1;
u8 reserved1:4;
u8 rate_recovery_count;
u8 reserved2[3];
u8 rates[12];
} __packed;
#define HIF_TX_RETRY_POLICY_MAX 15
#define HIF_TX_RETRY_POLICY_INVALID HIF_TX_RETRY_POLICY_MAX
struct wfx_hif_mib_set_tx_rate_retry_policy {
u8 num_tx_rate_policies;
u8 reserved[3];
struct wfx_hif_tx_rate_retry_policy tx_rate_retry_policy[];
} __packed;
struct wfx_hif_mib_protected_mgmt_policy {
u8 pmf_enable:1;
u8 unpmf_allowed:1;
u8 host_enc_auth_frames:1;
u8 reserved1:5;
u8 reserved2[3];
} __packed;
struct wfx_hif_mib_keep_alive_period {
__le16 keep_alive_period;
u8 reserved[2];
} __packed;
#endif
@@ -0,0 +1,391 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Handling of the chip-to-host events (aka indications) of the hardware API.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#include <linux/skbuff.h>
#include <linux/etherdevice.h>
#include "hif_rx.h"
#include "wfx.h"
#include "scan.h"
#include "bh.h"
#include "sta.h"
#include "data_rx.h"
#include "hif_api_cmd.h"
static int wfx_hif_generic_confirm(struct wfx_dev *wdev,
const struct wfx_hif_msg *hif, const void *buf)
{
/* All confirm messages start with status */
int status = le32_to_cpup((__le32 *)buf);
int cmd = hif->id;
int len = le16_to_cpu(hif->len) - 4; /* drop header */
WARN(!mutex_is_locked(&wdev->hif_cmd.lock), "data locking error");
if (!wdev->hif_cmd.buf_send) {
dev_warn(wdev->dev, "unexpected confirmation: 0x%.2x\n", cmd);
return -EINVAL;
}
if (cmd != wdev->hif_cmd.buf_send->id) {
dev_warn(wdev->dev, "chip response mismatch request: 0x%.2x vs 0x%.2x\n",
cmd, wdev->hif_cmd.buf_send->id);
return -EINVAL;
}
if (wdev->hif_cmd.buf_recv) {
if (wdev->hif_cmd.len_recv >= len && len > 0)
memcpy(wdev->hif_cmd.buf_recv, buf, len);
else
status = -EIO;
}
wdev->hif_cmd.ret = status;
complete(&wdev->hif_cmd.done);
return status;
}
static int wfx_hif_tx_confirm(struct wfx_dev *wdev,
const struct wfx_hif_msg *hif, const void *buf)
{
const struct wfx_hif_cnf_tx *body = buf;
wfx_tx_confirm_cb(wdev, body);
return 0;
}
static int wfx_hif_multi_tx_confirm(struct wfx_dev *wdev,
const struct wfx_hif_msg *hif, const void *buf)
{
const struct wfx_hif_cnf_multi_transmit *body = buf;
int i;
WARN(body->num_tx_confs <= 0, "corrupted message");
for (i = 0; i < body->num_tx_confs; i++)
wfx_tx_confirm_cb(wdev, &body->tx_conf_payload[i]);
return 0;
}
static int wfx_hif_startup_indication(struct wfx_dev *wdev,
const struct wfx_hif_msg *hif, const void *buf)
{
const struct wfx_hif_ind_startup *body = buf;
if (body->status || body->firmware_type > 4) {
dev_err(wdev->dev, "received invalid startup indication");
return -EINVAL;
}
memcpy(&wdev->hw_caps, body, sizeof(struct wfx_hif_ind_startup));
complete(&wdev->firmware_ready);
return 0;
}
static int wfx_hif_wakeup_indication(struct wfx_dev *wdev,
const struct wfx_hif_msg *hif, const void *buf)
{
if (!wdev->pdata.gpio_wakeup || gpiod_get_value(wdev->pdata.gpio_wakeup) == 0) {
dev_warn(wdev->dev, "unexpected wake-up indication\n");
return -EIO;
}
return 0;
}
static int wfx_hif_receive_indication(struct wfx_dev *wdev, const struct wfx_hif_msg *hif,
const void *buf, struct sk_buff *skb)
{
struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface);
const struct wfx_hif_ind_rx *body = buf;
if (!wvif) {
dev_warn(wdev->dev, "%s: received event for non-existent vif\n", __func__);
return -EIO;
}
skb_pull(skb, sizeof(struct wfx_hif_msg) + sizeof(struct wfx_hif_ind_rx));
wfx_rx_cb(wvif, body, skb);
return 0;
}
static int wfx_hif_event_indication(struct wfx_dev *wdev,
const struct wfx_hif_msg *hif, const void *buf)
{
struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface);
const struct wfx_hif_ind_event *body = buf;
int type = le32_to_cpu(body->event_id);
if (!wvif) {
dev_warn(wdev->dev, "%s: received event for non-existent vif\n", __func__);
return -EIO;
}
switch (type) {
case HIF_EVENT_IND_RCPI_RSSI:
wfx_event_report_rssi(wvif, body->event_data.rcpi_rssi);
break;
case HIF_EVENT_IND_BSSLOST:
schedule_delayed_work(&wvif->beacon_loss_work, 0);
break;
case HIF_EVENT_IND_BSSREGAINED:
cancel_delayed_work(&wvif->beacon_loss_work);
dev_dbg(wdev->dev, "ignore BSSREGAINED indication\n");
break;
case HIF_EVENT_IND_PS_MODE_ERROR:
dev_warn(wdev->dev, "error while processing power save request: %d\n",
le32_to_cpu(body->event_data.ps_mode_error));
break;
default:
dev_warn(wdev->dev, "unhandled event indication: %.2x\n", type);
break;
}
return 0;
}
static int wfx_hif_pm_mode_complete_indication(struct wfx_dev *wdev,
const struct wfx_hif_msg *hif, const void *buf)
{
struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface);
if (!wvif) {
dev_warn(wdev->dev, "%s: received event for non-existent vif\n", __func__);
return -EIO;
}
complete(&wvif->set_pm_mode_complete);
return 0;
}
static int wfx_hif_scan_complete_indication(struct wfx_dev *wdev,
const struct wfx_hif_msg *hif, const void *buf)
{
struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface);
const struct wfx_hif_ind_scan_cmpl *body = buf;
if (!wvif) {
dev_warn(wdev->dev, "%s: received event for non-existent vif\n", __func__);
return -EIO;
}
wfx_scan_complete(wvif, body->num_channels_completed);
return 0;
}
static int wfx_hif_join_complete_indication(struct wfx_dev *wdev,
const struct wfx_hif_msg *hif, const void *buf)
{
struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface);
if (!wvif) {
dev_warn(wdev->dev, "%s: received event for non-existent vif\n", __func__);
return -EIO;
}
dev_warn(wdev->dev, "unattended JoinCompleteInd\n");
return 0;
}
static int wfx_hif_suspend_resume_indication(struct wfx_dev *wdev,
const struct wfx_hif_msg *hif, const void *buf)
{
const struct wfx_hif_ind_suspend_resume_tx *body = buf;
struct wfx_vif *wvif;
if (body->bc_mc_only) {
wvif = wdev_to_wvif(wdev, hif->interface);
if (!wvif) {
dev_warn(wdev->dev, "%s: received event for non-existent vif\n", __func__);
return -EIO;
}
if (body->resume)
wfx_suspend_resume_mc(wvif, STA_NOTIFY_AWAKE);
else
wfx_suspend_resume_mc(wvif, STA_NOTIFY_SLEEP);
} else {
WARN(body->peer_sta_set, "misunderstood indication");
WARN(hif->interface != 2, "misunderstood indication");
if (body->resume)
wfx_suspend_hot_dev(wdev, STA_NOTIFY_AWAKE);
else
wfx_suspend_hot_dev(wdev, STA_NOTIFY_SLEEP);
}
return 0;
}
static int wfx_hif_generic_indication(struct wfx_dev *wdev,
const struct wfx_hif_msg *hif, const void *buf)
{
const struct wfx_hif_ind_generic *body = buf;
int type = le32_to_cpu(body->type);
switch (type) {
case HIF_GENERIC_INDICATION_TYPE_RAW:
return 0;
case HIF_GENERIC_INDICATION_TYPE_STRING:
dev_info(wdev->dev, "firmware says: %s\n", (char *)&body->data);
return 0;
case HIF_GENERIC_INDICATION_TYPE_RX_STATS:
mutex_lock(&wdev->rx_stats_lock);
/* Older firmware send a generic indication beside RxStats */
if (!wfx_api_older_than(wdev, 1, 4))
dev_info(wdev->dev, "Rx test ongoing. Temperature: %d degrees C\n",
body->data.rx_stats.current_temp);
memcpy(&wdev->rx_stats, &body->data.rx_stats, sizeof(wdev->rx_stats));
mutex_unlock(&wdev->rx_stats_lock);
return 0;
case HIF_GENERIC_INDICATION_TYPE_TX_POWER_LOOP_INFO:
mutex_lock(&wdev->tx_power_loop_info_lock);
memcpy(&wdev->tx_power_loop_info, &body->data.tx_power_loop_info,
sizeof(wdev->tx_power_loop_info));
mutex_unlock(&wdev->tx_power_loop_info_lock);
return 0;
default:
dev_err(wdev->dev, "generic_indication: unknown indication type: %#.8x\n", type);
return -EIO;
}
}
static const struct {
int val;
const char *str;
bool has_param;
} hif_errors[] = {
{ HIF_ERROR_FIRMWARE_ROLLBACK,
"rollback status" },
{ HIF_ERROR_FIRMWARE_DEBUG_ENABLED,
"debug feature enabled" },
{ HIF_ERROR_PDS_PAYLOAD,
"PDS version is not supported" },
{ HIF_ERROR_PDS_TESTFEATURE,
"PDS ask for an unknown test mode" },
{ HIF_ERROR_OOR_VOLTAGE,
"out-of-range power supply voltage", true },
{ HIF_ERROR_OOR_TEMPERATURE,
"out-of-range temperature", true },
{ HIF_ERROR_SLK_REQ_DURING_KEY_EXCHANGE,
"secure link does not expect request during key exchange" },
{ HIF_ERROR_SLK_SESSION_KEY,
"secure link session key is invalid" },
{ HIF_ERROR_SLK_OVERFLOW,
"secure link overflow" },
{ HIF_ERROR_SLK_WRONG_ENCRYPTION_STATE,
"secure link messages list does not match message encryption" },
{ HIF_ERROR_SLK_UNCONFIGURED,
"secure link not yet configured" },
{ HIF_ERROR_HIF_BUS_FREQUENCY_TOO_LOW,
"bus clock is too slow (<1kHz)" },
{ HIF_ERROR_HIF_RX_DATA_TOO_LARGE,
"HIF message too large" },
/* Following errors only exists in old firmware versions: */
{ HIF_ERROR_HIF_TX_QUEUE_FULL,
"HIF messages queue is full" },
{ HIF_ERROR_HIF_BUS,
"HIF bus" },
{ HIF_ERROR_SLK_MULTI_TX_UNSUPPORTED,
"secure link does not support multi-tx confirmations" },
{ HIF_ERROR_SLK_OUTDATED_SESSION_KEY,
"secure link session key is outdated" },
{ HIF_ERROR_SLK_DECRYPTION,
"secure link params (nonce or tag) mismatch" },
};
static int wfx_hif_error_indication(struct wfx_dev *wdev,
const struct wfx_hif_msg *hif, const void *buf)
{
const struct wfx_hif_ind_error *body = buf;
int type = le32_to_cpu(body->type);
int param = (s8)body->data[0];
int i;
for (i = 0; i < ARRAY_SIZE(hif_errors); i++)
if (type == hif_errors[i].val)
break;
if (i < ARRAY_SIZE(hif_errors))
if (hif_errors[i].has_param)
dev_err(wdev->dev, "asynchronous error: %s: %d\n",
hif_errors[i].str, param);
else
dev_err(wdev->dev, "asynchronous error: %s\n", hif_errors[i].str);
else
dev_err(wdev->dev, "asynchronous error: unknown: %08x\n", type);
print_hex_dump(KERN_INFO, "hif: ", DUMP_PREFIX_OFFSET,
16, 1, hif, le16_to_cpu(hif->len), false);
wdev->chip_frozen = true;
return 0;
};
static int wfx_hif_exception_indication(struct wfx_dev *wdev,
const struct wfx_hif_msg *hif, const void *buf)
{
const struct wfx_hif_ind_exception *body = buf;
int type = le32_to_cpu(body->type);
if (type == 4)
dev_err(wdev->dev, "firmware assert %d\n", le32_to_cpup((__le32 *)body->data));
else
dev_err(wdev->dev, "firmware exception\n");
print_hex_dump(KERN_INFO, "hif: ", DUMP_PREFIX_OFFSET,
16, 1, hif, le16_to_cpu(hif->len), false);
wdev->chip_frozen = true;
return -1;
}
static const struct {
int msg_id;
int (*handler)(struct wfx_dev *wdev, const struct wfx_hif_msg *hif, const void *buf);
} hif_handlers[] = {
/* Confirmations */
{ HIF_CNF_ID_TX, wfx_hif_tx_confirm },
{ HIF_CNF_ID_MULTI_TRANSMIT, wfx_hif_multi_tx_confirm },
/* Indications */
{ HIF_IND_ID_STARTUP, wfx_hif_startup_indication },
{ HIF_IND_ID_WAKEUP, wfx_hif_wakeup_indication },
{ HIF_IND_ID_JOIN_COMPLETE, wfx_hif_join_complete_indication },
{ HIF_IND_ID_SET_PM_MODE_CMPL, wfx_hif_pm_mode_complete_indication },
{ HIF_IND_ID_SCAN_CMPL, wfx_hif_scan_complete_indication },
{ HIF_IND_ID_SUSPEND_RESUME_TX, wfx_hif_suspend_resume_indication },
{ HIF_IND_ID_EVENT, wfx_hif_event_indication },
{ HIF_IND_ID_GENERIC, wfx_hif_generic_indication },
{ HIF_IND_ID_ERROR, wfx_hif_error_indication },
{ HIF_IND_ID_EXCEPTION, wfx_hif_exception_indication },
/* FIXME: allocate skb_p from wfx_hif_receive_indication and make it generic */
//{ HIF_IND_ID_RX, wfx_hif_receive_indication },
};
void wfx_handle_rx(struct wfx_dev *wdev, struct sk_buff *skb)
{
int i;
const struct wfx_hif_msg *hif = (const struct wfx_hif_msg *)skb->data;
int hif_id = hif->id;
if (hif_id == HIF_IND_ID_RX) {
/* wfx_hif_receive_indication take care of skb lifetime */
wfx_hif_receive_indication(wdev, hif, hif->body, skb);
return;
}
/* Note: mutex_is_lock cause an implicit memory barrier that protect buf_send */
if (mutex_is_locked(&wdev->hif_cmd.lock) &&
wdev->hif_cmd.buf_send && wdev->hif_cmd.buf_send->id == hif_id) {
wfx_hif_generic_confirm(wdev, hif, hif->body);
goto free;
}
for (i = 0; i < ARRAY_SIZE(hif_handlers); i++) {
if (hif_handlers[i].msg_id == hif_id) {
if (hif_handlers[i].handler)
hif_handlers[i].handler(wdev, hif, hif->body);
goto free;
}
}
if (hif_id & HIF_ID_IS_INDICATION)
dev_err(wdev->dev, "unsupported HIF indication: ID %02x\n", hif_id);
else
dev_err(wdev->dev, "unexpected HIF confirmation: ID %02x\n", hif_id);
free:
dev_kfree_skb(skb);
}
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Handling of the chip-to-host events (aka indications) of the hardware API.
*
* Copyright (c) 2017-2019, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
* Copyright (C) 2010, ST-Ericsson SA
*/
#ifndef WFX_HIF_RX_H
#define WFX_HIF_RX_H
struct wfx_dev;
struct sk_buff;
void wfx_handle_rx(struct wfx_dev *wdev, struct sk_buff *skb);
#endif
@@ -0,0 +1,537 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Implementation of the host-to-chip commands (aka request/confirmation) of the
* hardware API.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#include <linux/etherdevice.h>
#include "hif_tx.h"
#include "wfx.h"
#include "bh.h"
#include "hwio.h"
#include "debug.h"
#include "sta.h"
void wfx_init_hif_cmd(struct wfx_hif_cmd *hif_cmd)
{
init_completion(&hif_cmd->ready);
init_completion(&hif_cmd->done);
mutex_init(&hif_cmd->lock);
}
static void wfx_fill_header(struct wfx_hif_msg *hif, int if_id, unsigned int cmd, size_t size)
{
if (if_id == -1)
if_id = 2;
WARN(cmd > 0x3f, "invalid hardware command %#.2x", cmd);
WARN(size > 0xFFF, "requested buffer is too large: %zu bytes", size);
WARN(if_id > 0x3, "invalid interface ID %d", if_id);
hif->len = cpu_to_le16(size + 4);
hif->id = cmd;
hif->interface = if_id;
}
static void *wfx_alloc_hif(size_t body_len, struct wfx_hif_msg **hif)
{
*hif = kzalloc(sizeof(struct wfx_hif_msg) + body_len, GFP_KERNEL);
if (*hif)
return (*hif)->body;
else
return NULL;
}
static u32 wfx_rate_mask_to_hw(struct wfx_dev *wdev, u32 rates)
{
int i;
u32 ret = 0;
/* The device only supports 2GHz */
struct ieee80211_supported_band *sband = wdev->hw->wiphy->bands[NL80211_BAND_2GHZ];
for (i = 0; i < sband->n_bitrates; i++) {
if (rates & BIT(i)) {
if (i >= sband->n_bitrates)
dev_warn(wdev->dev, "unsupported basic rate\n");
else
ret |= BIT(sband->bitrates[i].hw_value);
}
}
return ret;
}
int wfx_cmd_send(struct wfx_dev *wdev, struct wfx_hif_msg *request,
void *reply, size_t reply_len, bool no_reply)
{
const char *mib_name = "";
const char *mib_sep = "";
int cmd = request->id;
int vif = request->interface;
int ret;
/* Do not wait for any reply if chip is frozen */
if (wdev->chip_frozen)
return -ETIMEDOUT;
mutex_lock(&wdev->hif_cmd.lock);
WARN(wdev->hif_cmd.buf_send, "data locking error");
/* Note: call to complete() below has an implicit memory barrier that hopefully protect
* buf_send
*/
wdev->hif_cmd.buf_send = request;
wdev->hif_cmd.buf_recv = reply;
wdev->hif_cmd.len_recv = reply_len;
complete(&wdev->hif_cmd.ready);
wfx_bh_request_tx(wdev);
if (no_reply) {
/* Chip won't reply. Ensure the wq has send the buffer before to continue. */
flush_workqueue(wdev->bh_wq);
ret = 0;
goto end;
}
if (wdev->poll_irq)
wfx_bh_poll_irq(wdev);
ret = wait_for_completion_timeout(&wdev->hif_cmd.done, 1 * HZ);
if (!ret) {
dev_err(wdev->dev, "chip is abnormally long to answer\n");
reinit_completion(&wdev->hif_cmd.ready);
ret = wait_for_completion_timeout(&wdev->hif_cmd.done, 3 * HZ);
}
if (!ret) {
dev_err(wdev->dev, "chip did not answer\n");
wfx_pending_dump_old_frames(wdev, 3000);
wdev->chip_frozen = true;
reinit_completion(&wdev->hif_cmd.done);
ret = -ETIMEDOUT;
} else {
ret = wdev->hif_cmd.ret;
}
end:
wdev->hif_cmd.buf_send = NULL;
mutex_unlock(&wdev->hif_cmd.lock);
if (ret &&
(cmd == HIF_REQ_ID_READ_MIB || cmd == HIF_REQ_ID_WRITE_MIB)) {
mib_name = wfx_get_mib_name(((u16 *)request)[2]);
mib_sep = "/";
}
if (ret < 0)
dev_err(wdev->dev, "hardware request %s%s%s (%#.2x) on vif %d returned error %d\n",
wfx_get_hif_name(cmd), mib_sep, mib_name, cmd, vif, ret);
if (ret > 0)
dev_warn(wdev->dev, "hardware request %s%s%s (%#.2x) on vif %d returned status %d\n",
wfx_get_hif_name(cmd), mib_sep, mib_name, cmd, vif, ret);
return ret;
}
/* This function is special. After HIF_REQ_ID_SHUT_DOWN, chip won't reply to any request anymore.
* Obviously, only call this function during device unregister.
*/
int wfx_hif_shutdown(struct wfx_dev *wdev)
{
int ret;
struct wfx_hif_msg *hif;
wfx_alloc_hif(0, &hif);
if (!hif)
return -ENOMEM;
wfx_fill_header(hif, -1, HIF_REQ_ID_SHUT_DOWN, 0);
ret = wfx_cmd_send(wdev, hif, NULL, 0, true);
if (wdev->pdata.gpio_wakeup)
gpiod_set_value(wdev->pdata.gpio_wakeup, 0);
else
wfx_control_reg_write(wdev, 0);
kfree(hif);
return ret;
}
int wfx_hif_configuration(struct wfx_dev *wdev, const u8 *conf, size_t len)
{
int ret;
size_t buf_len = sizeof(struct wfx_hif_req_configuration) + len;
struct wfx_hif_msg *hif;
struct wfx_hif_req_configuration *body = wfx_alloc_hif(buf_len, &hif);
if (!hif)
return -ENOMEM;
body->length = cpu_to_le16(len);
memcpy(body->pds_data, conf, len);
wfx_fill_header(hif, -1, HIF_REQ_ID_CONFIGURATION, buf_len);
ret = wfx_cmd_send(wdev, hif, NULL, 0, false);
kfree(hif);
return ret;
}
int wfx_hif_reset(struct wfx_vif *wvif, bool reset_stat)
{
int ret;
struct wfx_hif_msg *hif;
struct wfx_hif_req_reset *body = wfx_alloc_hif(sizeof(*body), &hif);
if (!hif)
return -ENOMEM;
body->reset_stat = reset_stat;
wfx_fill_header(hif, wvif->id, HIF_REQ_ID_RESET, sizeof(*body));
ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
kfree(hif);
return ret;
}
int wfx_hif_read_mib(struct wfx_dev *wdev, int vif_id, u16 mib_id, void *val, size_t val_len)
{
int ret;
struct wfx_hif_msg *hif;
int buf_len = sizeof(struct wfx_hif_cnf_read_mib) + val_len;
struct wfx_hif_req_read_mib *body = wfx_alloc_hif(sizeof(*body), &hif);
struct wfx_hif_cnf_read_mib *reply = kmalloc(buf_len, GFP_KERNEL);
if (!body || !reply) {
ret = -ENOMEM;
goto out;
}
body->mib_id = cpu_to_le16(mib_id);
wfx_fill_header(hif, vif_id, HIF_REQ_ID_READ_MIB, sizeof(*body));
ret = wfx_cmd_send(wdev, hif, reply, buf_len, false);
if (!ret && mib_id != le16_to_cpu(reply->mib_id)) {
dev_warn(wdev->dev, "%s: confirmation mismatch request\n", __func__);
ret = -EIO;
}
if (ret == -ENOMEM)
dev_err(wdev->dev, "buffer is too small to receive %s (%zu < %d)\n",
wfx_get_mib_name(mib_id), val_len, le16_to_cpu(reply->length));
if (!ret)
memcpy(val, &reply->mib_data, le16_to_cpu(reply->length));
else
memset(val, 0xFF, val_len);
out:
kfree(hif);
kfree(reply);
return ret;
}
int wfx_hif_write_mib(struct wfx_dev *wdev, int vif_id, u16 mib_id, void *val, size_t val_len)
{
int ret;
struct wfx_hif_msg *hif;
int buf_len = sizeof(struct wfx_hif_req_write_mib) + val_len;
struct wfx_hif_req_write_mib *body = wfx_alloc_hif(buf_len, &hif);
if (!hif)
return -ENOMEM;
body->mib_id = cpu_to_le16(mib_id);
body->length = cpu_to_le16(val_len);
memcpy(&body->mib_data, val, val_len);
wfx_fill_header(hif, vif_id, HIF_REQ_ID_WRITE_MIB, buf_len);
ret = wfx_cmd_send(wdev, hif, NULL, 0, false);
kfree(hif);
return ret;
}
/* Hijack scan request to implement Remain-On-Channel */
int wfx_hif_scan_uniq(struct wfx_vif *wvif, struct ieee80211_channel *chan, int duration)
{
int ret;
struct wfx_hif_msg *hif;
size_t buf_len = sizeof(struct wfx_hif_req_start_scan_alt) + sizeof(u8);
struct wfx_hif_req_start_scan_alt *body = wfx_alloc_hif(buf_len, &hif);
if (!hif)
return -ENOMEM;
body->num_of_ssids = HIF_API_MAX_NB_SSIDS;
body->maintain_current_bss = 1;
body->disallow_ps = 1;
body->tx_power_level = cpu_to_le32(chan->max_power);
body->num_of_channels = 1;
body->channel_list[0] = chan->hw_value;
body->max_transmit_rate = API_RATE_INDEX_B_1MBPS;
body->min_channel_time = cpu_to_le32(duration);
body->max_channel_time = cpu_to_le32(duration * 110 / 100);
wfx_fill_header(hif, wvif->id, HIF_REQ_ID_START_SCAN, buf_len);
ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
kfree(hif);
return ret;
}
int wfx_hif_scan(struct wfx_vif *wvif, struct cfg80211_scan_request *req,
int chan_start_idx, int chan_num)
{
int ret, i;
struct wfx_hif_msg *hif;
size_t buf_len = sizeof(struct wfx_hif_req_start_scan_alt) + chan_num * sizeof(u8);
struct wfx_hif_req_start_scan_alt *body = wfx_alloc_hif(buf_len, &hif);
WARN(chan_num > HIF_API_MAX_NB_CHANNELS, "invalid params");
WARN(req->n_ssids > HIF_API_MAX_NB_SSIDS, "invalid params");
if (!hif)
return -ENOMEM;
for (i = 0; i < req->n_ssids; i++) {
memcpy(body->ssid_def[i].ssid, req->ssids[i].ssid, IEEE80211_MAX_SSID_LEN);
body->ssid_def[i].ssid_length = cpu_to_le32(req->ssids[i].ssid_len);
}
body->num_of_ssids = HIF_API_MAX_NB_SSIDS;
body->maintain_current_bss = 1;
body->disallow_ps = 1;
body->tx_power_level = cpu_to_le32(req->channels[chan_start_idx]->max_power);
body->num_of_channels = chan_num;
for (i = 0; i < chan_num; i++)
body->channel_list[i] = req->channels[i + chan_start_idx]->hw_value;
if (req->no_cck)
body->max_transmit_rate = API_RATE_INDEX_G_6MBPS;
else
body->max_transmit_rate = API_RATE_INDEX_B_1MBPS;
if (req->channels[chan_start_idx]->flags & IEEE80211_CHAN_NO_IR) {
body->min_channel_time = cpu_to_le32(50);
body->max_channel_time = cpu_to_le32(150);
} else {
body->min_channel_time = cpu_to_le32(10);
body->max_channel_time = cpu_to_le32(50);
body->num_of_probe_requests = 2;
body->probe_delay = 100;
}
wfx_fill_header(hif, wvif->id, HIF_REQ_ID_START_SCAN, buf_len);
ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
kfree(hif);
return ret;
}
int wfx_hif_stop_scan(struct wfx_vif *wvif)
{
int ret;
struct wfx_hif_msg *hif;
/* body associated to HIF_REQ_ID_STOP_SCAN is empty */
wfx_alloc_hif(0, &hif);
if (!hif)
return -ENOMEM;
wfx_fill_header(hif, wvif->id, HIF_REQ_ID_STOP_SCAN, 0);
ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
kfree(hif);
return ret;
}
int wfx_hif_join(struct wfx_vif *wvif, const struct ieee80211_bss_conf *conf,
struct ieee80211_channel *channel, const u8 *ssid, int ssid_len)
{
struct ieee80211_vif *vif = container_of(conf, struct ieee80211_vif,
bss_conf);
int ret;
struct wfx_hif_msg *hif;
struct wfx_hif_req_join *body = wfx_alloc_hif(sizeof(*body), &hif);
WARN_ON(!conf->beacon_int);
WARN_ON(!conf->basic_rates);
WARN_ON(sizeof(body->ssid) < ssid_len);
WARN(!vif->cfg.ibss_joined && !ssid_len, "joining an unknown BSS");
if (!hif)
return -ENOMEM;
body->infrastructure_bss_mode = !vif->cfg.ibss_joined;
body->short_preamble = conf->use_short_preamble;
body->probe_for_join = !(channel->flags & IEEE80211_CHAN_NO_IR);
body->channel_number = channel->hw_value;
body->beacon_interval = cpu_to_le32(conf->beacon_int);
body->basic_rate_set = cpu_to_le32(wfx_rate_mask_to_hw(wvif->wdev, conf->basic_rates));
memcpy(body->bssid, conf->bssid, sizeof(body->bssid));
if (ssid) {
body->ssid_length = cpu_to_le32(ssid_len);
memcpy(body->ssid, ssid, ssid_len);
}
wfx_fill_header(hif, wvif->id, HIF_REQ_ID_JOIN, sizeof(*body));
ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
kfree(hif);
return ret;
}
int wfx_hif_set_bss_params(struct wfx_vif *wvif, int aid, int beacon_lost_count)
{
int ret;
struct wfx_hif_msg *hif;
struct wfx_hif_req_set_bss_params *body = wfx_alloc_hif(sizeof(*body), &hif);
if (!hif)
return -ENOMEM;
body->aid = cpu_to_le16(aid);
body->beacon_lost_count = beacon_lost_count;
wfx_fill_header(hif, wvif->id, HIF_REQ_ID_SET_BSS_PARAMS, sizeof(*body));
ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
kfree(hif);
return ret;
}
int wfx_hif_add_key(struct wfx_dev *wdev, const struct wfx_hif_req_add_key *arg)
{
int ret;
struct wfx_hif_msg *hif;
/* FIXME: only send necessary bits */
struct wfx_hif_req_add_key *body = wfx_alloc_hif(sizeof(*body), &hif);
if (!hif)
return -ENOMEM;
/* FIXME: swap bytes as necessary in body */
memcpy(body, arg, sizeof(*body));
if (wfx_api_older_than(wdev, 1, 5))
/* Legacy firmwares expect that add_key to be sent on right interface. */
wfx_fill_header(hif, arg->int_id, HIF_REQ_ID_ADD_KEY, sizeof(*body));
else
wfx_fill_header(hif, -1, HIF_REQ_ID_ADD_KEY, sizeof(*body));
ret = wfx_cmd_send(wdev, hif, NULL, 0, false);
kfree(hif);
return ret;
}
int wfx_hif_remove_key(struct wfx_dev *wdev, int idx)
{
int ret;
struct wfx_hif_msg *hif;
struct wfx_hif_req_remove_key *body = wfx_alloc_hif(sizeof(*body), &hif);
if (!hif)
return -ENOMEM;
body->entry_index = idx;
wfx_fill_header(hif, -1, HIF_REQ_ID_REMOVE_KEY, sizeof(*body));
ret = wfx_cmd_send(wdev, hif, NULL, 0, false);
kfree(hif);
return ret;
}
int wfx_hif_set_edca_queue_params(struct wfx_vif *wvif, u16 queue,
const struct ieee80211_tx_queue_params *arg)
{
int ret;
struct wfx_hif_msg *hif;
struct wfx_hif_req_edca_queue_params *body = wfx_alloc_hif(sizeof(*body), &hif);
if (!body)
return -ENOMEM;
WARN_ON(arg->aifs > 255);
if (!hif)
return -ENOMEM;
body->aifsn = arg->aifs;
body->cw_min = cpu_to_le16(arg->cw_min);
body->cw_max = cpu_to_le16(arg->cw_max);
body->tx_op_limit = cpu_to_le16(arg->txop * USEC_PER_TXOP);
body->queue_id = 3 - queue;
/* API 2.0 has changed queue IDs values */
if (wfx_api_older_than(wvif->wdev, 2, 0) && queue == IEEE80211_AC_BE)
body->queue_id = HIF_QUEUE_ID_BACKGROUND;
if (wfx_api_older_than(wvif->wdev, 2, 0) && queue == IEEE80211_AC_BK)
body->queue_id = HIF_QUEUE_ID_BESTEFFORT;
wfx_fill_header(hif, wvif->id, HIF_REQ_ID_EDCA_QUEUE_PARAMS, sizeof(*body));
ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
kfree(hif);
return ret;
}
int wfx_hif_set_pm(struct wfx_vif *wvif, bool ps, int dynamic_ps_timeout)
{
int ret;
struct wfx_hif_msg *hif;
struct wfx_hif_req_set_pm_mode *body = wfx_alloc_hif(sizeof(*body), &hif);
if (!body)
return -ENOMEM;
if (!hif)
return -ENOMEM;
if (ps) {
body->enter_psm = 1;
/* Firmware does not support more than 128ms */
body->fast_psm_idle_period = min(dynamic_ps_timeout * 2, 255);
if (body->fast_psm_idle_period)
body->fast_psm = 1;
}
wfx_fill_header(hif, wvif->id, HIF_REQ_ID_SET_PM_MODE, sizeof(*body));
ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
kfree(hif);
return ret;
}
int wfx_hif_start(struct wfx_vif *wvif, const struct ieee80211_bss_conf *conf,
const struct ieee80211_channel *channel)
{
struct ieee80211_vif *vif = container_of(conf, struct ieee80211_vif,
bss_conf);
int ret;
struct wfx_hif_msg *hif;
struct wfx_hif_req_start *body = wfx_alloc_hif(sizeof(*body), &hif);
WARN_ON(!conf->beacon_int);
if (!hif)
return -ENOMEM;
body->dtim_period = conf->dtim_period;
body->short_preamble = conf->use_short_preamble;
body->channel_number = channel->hw_value;
body->beacon_interval = cpu_to_le32(conf->beacon_int);
body->basic_rate_set = cpu_to_le32(wfx_rate_mask_to_hw(wvif->wdev, conf->basic_rates));
body->ssid_length = vif->cfg.ssid_len;
memcpy(body->ssid, vif->cfg.ssid, vif->cfg.ssid_len);
wfx_fill_header(hif, wvif->id, HIF_REQ_ID_START, sizeof(*body));
ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
kfree(hif);
return ret;
}
int wfx_hif_beacon_transmit(struct wfx_vif *wvif, bool enable)
{
int ret;
struct wfx_hif_msg *hif;
struct wfx_hif_req_beacon_transmit *body = wfx_alloc_hif(sizeof(*body), &hif);
if (!hif)
return -ENOMEM;
body->enable_beaconing = enable ? 1 : 0;
wfx_fill_header(hif, wvif->id, HIF_REQ_ID_BEACON_TRANSMIT, sizeof(*body));
ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
kfree(hif);
return ret;
}
int wfx_hif_map_link(struct wfx_vif *wvif, bool unmap, u8 *mac_addr, int sta_id, bool mfp)
{
int ret;
struct wfx_hif_msg *hif;
struct wfx_hif_req_map_link *body = wfx_alloc_hif(sizeof(*body), &hif);
if (!hif)
return -ENOMEM;
if (mac_addr)
ether_addr_copy(body->mac_addr, mac_addr);
body->mfpc = mfp ? 1 : 0;
body->unmap = unmap ? 1 : 0;
body->peer_sta_id = sta_id;
wfx_fill_header(hif, wvif->id, HIF_REQ_ID_MAP_LINK, sizeof(*body));
ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
kfree(hif);
return ret;
}
int wfx_hif_update_ie_beacon(struct wfx_vif *wvif, const u8 *ies, size_t ies_len)
{
int ret;
struct wfx_hif_msg *hif;
int buf_len = sizeof(struct wfx_hif_req_update_ie) + ies_len;
struct wfx_hif_req_update_ie *body = wfx_alloc_hif(buf_len, &hif);
if (!hif)
return -ENOMEM;
body->beacon = 1;
body->num_ies = cpu_to_le16(1);
memcpy(body->ie, ies, ies_len);
wfx_fill_header(hif, wvif->id, HIF_REQ_ID_UPDATE_IE, buf_len);
ret = wfx_cmd_send(wvif->wdev, hif, NULL, 0, false);
kfree(hif);
return ret;
}
@@ -0,0 +1,62 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Implementation of the host-to-chip commands (aka request/confirmation) of the
* hardware API.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
* Copyright (C) 2010, ST-Ericsson SA
*/
#ifndef WFX_HIF_TX_H
#define WFX_HIF_TX_H
#include <linux/types.h>
#include <linux/mutex.h>
#include <linux/completion.h>
struct ieee80211_channel;
struct ieee80211_bss_conf;
struct ieee80211_tx_queue_params;
struct cfg80211_scan_request;
struct wfx_hif_req_add_key;
struct wfx_dev;
struct wfx_vif;
struct wfx_hif_cmd {
struct mutex lock;
struct completion ready;
struct completion done;
struct wfx_hif_msg *buf_send;
void *buf_recv;
size_t len_recv;
int ret;
};
void wfx_init_hif_cmd(struct wfx_hif_cmd *wfx_hif_cmd);
int wfx_cmd_send(struct wfx_dev *wdev, struct wfx_hif_msg *request,
void *reply, size_t reply_len, bool async);
int wfx_hif_read_mib(struct wfx_dev *wdev, int vif_id, u16 mib_id, void *buf, size_t buf_size);
int wfx_hif_write_mib(struct wfx_dev *wdev, int vif_id, u16 mib_id, void *buf, size_t buf_size);
int wfx_hif_start(struct wfx_vif *wvif, const struct ieee80211_bss_conf *conf,
const struct ieee80211_channel *channel);
int wfx_hif_reset(struct wfx_vif *wvif, bool reset_stat);
int wfx_hif_join(struct wfx_vif *wvif, const struct ieee80211_bss_conf *conf,
struct ieee80211_channel *channel, const u8 *ssid, int ssidlen);
int wfx_hif_map_link(struct wfx_vif *wvif, bool unmap, u8 *mac_addr, int sta_id, bool mfp);
int wfx_hif_add_key(struct wfx_dev *wdev, const struct wfx_hif_req_add_key *arg);
int wfx_hif_remove_key(struct wfx_dev *wdev, int idx);
int wfx_hif_set_pm(struct wfx_vif *wvif, bool ps, int dynamic_ps_timeout);
int wfx_hif_set_bss_params(struct wfx_vif *wvif, int aid, int beacon_lost_count);
int wfx_hif_set_edca_queue_params(struct wfx_vif *wvif, u16 queue,
const struct ieee80211_tx_queue_params *arg);
int wfx_hif_beacon_transmit(struct wfx_vif *wvif, bool enable);
int wfx_hif_update_ie_beacon(struct wfx_vif *wvif, const u8 *ies, size_t ies_len);
int wfx_hif_scan(struct wfx_vif *wvif, struct cfg80211_scan_request *req80211,
int chan_start, int chan_num);
int wfx_hif_scan_uniq(struct wfx_vif *wvif, struct ieee80211_channel *chan, int duration);
int wfx_hif_stop_scan(struct wfx_vif *wvif);
int wfx_hif_configuration(struct wfx_dev *wdev, const u8 *conf, size_t len);
int wfx_hif_shutdown(struct wfx_dev *wdev);
#endif
@@ -0,0 +1,307 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Implementation of the host-to-chip MIBs of the hardware API.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
* Copyright (C) 2010, ST-Ericsson SA
*/
#include <linux/etherdevice.h>
#include "wfx.h"
#include "hif_tx.h"
#include "hif_tx_mib.h"
#include "hif_api_mib.h"
int wfx_hif_set_output_power(struct wfx_vif *wvif, int val)
{
struct wfx_hif_mib_current_tx_power_level arg = {
.power_level = cpu_to_le32(val * 10),
};
return wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_CURRENT_TX_POWER_LEVEL,
&arg, sizeof(arg));
}
int wfx_hif_set_beacon_wakeup_period(struct wfx_vif *wvif,
unsigned int dtim_interval, unsigned int listen_interval)
{
struct wfx_hif_mib_beacon_wake_up_period arg = {
.wakeup_period_min = dtim_interval,
.receive_dtim = 0,
.wakeup_period_max = listen_interval,
};
if (dtim_interval > 0xFF || listen_interval > 0xFFFF)
return -EINVAL;
return wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_BEACON_WAKEUP_PERIOD,
&arg, sizeof(arg));
}
int wfx_hif_set_rcpi_rssi_threshold(struct wfx_vif *wvif, int rssi_thold, int rssi_hyst)
{
struct wfx_hif_mib_rcpi_rssi_threshold arg = {
.rolling_average_count = 8,
.detection = 1,
};
if (!rssi_thold && !rssi_hyst) {
arg.upperthresh = 1;
arg.lowerthresh = 1;
} else {
arg.upper_threshold = rssi_thold + rssi_hyst;
arg.upper_threshold = (arg.upper_threshold + 110) * 2;
arg.lower_threshold = rssi_thold;
arg.lower_threshold = (arg.lower_threshold + 110) * 2;
}
return wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_RCPI_RSSI_THRESHOLD,
&arg, sizeof(arg));
}
int wfx_hif_get_counters_table(struct wfx_dev *wdev, int vif_id,
struct wfx_hif_mib_extended_count_table *arg)
{
if (wfx_api_older_than(wdev, 1, 3)) {
/* extended_count_table is wider than count_table */
memset(arg, 0xFF, sizeof(*arg));
return wfx_hif_read_mib(wdev, vif_id, HIF_MIB_ID_COUNTERS_TABLE,
arg, sizeof(struct wfx_hif_mib_count_table));
} else {
return wfx_hif_read_mib(wdev, vif_id, HIF_MIB_ID_EXTENDED_COUNTERS_TABLE,
arg, sizeof(struct wfx_hif_mib_extended_count_table));
}
}
int wfx_hif_set_macaddr(struct wfx_vif *wvif, u8 *mac)
{
struct wfx_hif_mib_mac_address arg = { };
if (mac)
ether_addr_copy(arg.mac_addr, mac);
return wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_DOT11_MAC_ADDRESS,
&arg, sizeof(arg));
}
int wfx_hif_set_rx_filter(struct wfx_vif *wvif, bool filter_bssid, bool filter_prbreq)
{
struct wfx_hif_mib_rx_filter arg = { };
if (filter_bssid)
arg.bssid_filter = 1;
if (!filter_prbreq)
arg.fwd_probe_req = 1;
return wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_RX_FILTER, &arg, sizeof(arg));
}
int wfx_hif_set_beacon_filter_table(struct wfx_vif *wvif, int tbl_len,
const struct wfx_hif_ie_table_entry *tbl)
{
int ret;
struct wfx_hif_mib_bcn_filter_table *arg;
int buf_len = struct_size(arg, ie_table, tbl_len);
arg = kzalloc(buf_len, GFP_KERNEL);
if (!arg)
return -ENOMEM;
arg->num_of_info_elmts = cpu_to_le32(tbl_len);
memcpy(arg->ie_table, tbl, flex_array_size(arg, ie_table, tbl_len));
ret = wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_BEACON_FILTER_TABLE,
arg, buf_len);
kfree(arg);
return ret;
}
int wfx_hif_beacon_filter_control(struct wfx_vif *wvif, int enable, int beacon_count)
{
struct wfx_hif_mib_bcn_filter_enable arg = {
.enable = cpu_to_le32(enable),
.bcn_count = cpu_to_le32(beacon_count),
};
return wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_BEACON_FILTER_ENABLE,
&arg, sizeof(arg));
}
int wfx_hif_set_operational_mode(struct wfx_dev *wdev, enum wfx_hif_op_power_mode mode)
{
struct wfx_hif_mib_gl_operational_power_mode arg = {
.power_mode = mode,
.wup_ind_activation = 1,
};
return wfx_hif_write_mib(wdev, -1, HIF_MIB_ID_GL_OPERATIONAL_POWER_MODE,
&arg, sizeof(arg));
}
int wfx_hif_set_template_frame(struct wfx_vif *wvif, struct sk_buff *skb,
u8 frame_type, int init_rate)
{
struct wfx_hif_mib_template_frame *arg;
WARN(skb->len > HIF_API_MAX_TEMPLATE_FRAME_SIZE, "frame is too big");
skb_push(skb, 4);
arg = (struct wfx_hif_mib_template_frame *)skb->data;
skb_pull(skb, 4);
arg->init_rate = init_rate;
arg->frame_type = frame_type;
arg->frame_length = cpu_to_le16(skb->len);
return wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_TEMPLATE_FRAME,
arg, sizeof(*arg) + skb->len);
}
int wfx_hif_set_mfp(struct wfx_vif *wvif, bool capable, bool required)
{
struct wfx_hif_mib_protected_mgmt_policy arg = { };
WARN(required && !capable, "incoherent arguments");
if (capable) {
arg.pmf_enable = 1;
arg.host_enc_auth_frames = 1;
}
if (!required)
arg.unpmf_allowed = 1;
return wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_PROTECTED_MGMT_POLICY,
&arg, sizeof(arg));
}
int wfx_hif_set_block_ack_policy(struct wfx_vif *wvif, u8 tx_tid_policy, u8 rx_tid_policy)
{
struct wfx_hif_mib_block_ack_policy arg = {
.block_ack_tx_tid_policy = tx_tid_policy,
.block_ack_rx_tid_policy = rx_tid_policy,
};
return wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_BLOCK_ACK_POLICY,
&arg, sizeof(arg));
}
int wfx_hif_set_association_mode(struct wfx_vif *wvif, int ampdu_density,
bool greenfield, bool short_preamble)
{
struct wfx_hif_mib_set_association_mode arg = {
.preambtype_use = 1,
.mode = 1,
.spacing = 1,
.short_preamble = short_preamble,
.greenfield = greenfield,
.mpdu_start_spacing = ampdu_density,
};
return wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_SET_ASSOCIATION_MODE,
&arg, sizeof(arg));
}
int wfx_hif_set_tx_rate_retry_policy(struct wfx_vif *wvif, int policy_index, u8 *rates)
{
struct wfx_hif_mib_set_tx_rate_retry_policy *arg;
size_t size = struct_size(arg, tx_rate_retry_policy, 1);
int ret;
arg = kzalloc(size, GFP_KERNEL);
if (!arg)
return -ENOMEM;
arg->num_tx_rate_policies = 1;
arg->tx_rate_retry_policy[0].policy_index = policy_index;
arg->tx_rate_retry_policy[0].short_retry_count = 255;
arg->tx_rate_retry_policy[0].long_retry_count = 255;
arg->tx_rate_retry_policy[0].first_rate_sel = 1;
arg->tx_rate_retry_policy[0].terminate = 1;
arg->tx_rate_retry_policy[0].count_init = 1;
memcpy(&arg->tx_rate_retry_policy[0].rates, rates,
sizeof(arg->tx_rate_retry_policy[0].rates));
ret = wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_SET_TX_RATE_RETRY_POLICY,
arg, size);
kfree(arg);
return ret;
}
int wfx_hif_keep_alive_period(struct wfx_vif *wvif, int period)
{
struct wfx_hif_mib_keep_alive_period arg = {
.keep_alive_period = cpu_to_le16(period),
};
return wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_KEEP_ALIVE_PERIOD,
&arg, sizeof(arg));
};
int wfx_hif_set_arp_ipv4_filter(struct wfx_vif *wvif, int idx, __be32 *addr)
{
struct wfx_hif_mib_arp_ip_addr_table arg = {
.condition_idx = idx,
.arp_enable = HIF_ARP_NS_FILTERING_DISABLE,
};
if (addr) {
/* Caution: type of addr is __be32 */
memcpy(arg.ipv4_address, addr, sizeof(arg.ipv4_address));
arg.arp_enable = HIF_ARP_NS_FILTERING_ENABLE;
}
return wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_ARP_IP_ADDRESSES_TABLE,
&arg, sizeof(arg));
}
int wfx_hif_use_multi_tx_conf(struct wfx_dev *wdev, bool enable)
{
struct wfx_hif_mib_gl_set_multi_msg arg = {
.enable_multi_tx_conf = enable,
};
return wfx_hif_write_mib(wdev, -1, HIF_MIB_ID_GL_SET_MULTI_MSG, &arg, sizeof(arg));
}
int wfx_hif_set_uapsd_info(struct wfx_vif *wvif, unsigned long val)
{
struct wfx_hif_mib_set_uapsd_information arg = { };
if (val & BIT(IEEE80211_AC_VO))
arg.trig_voice = 1;
if (val & BIT(IEEE80211_AC_VI))
arg.trig_video = 1;
if (val & BIT(IEEE80211_AC_BE))
arg.trig_be = 1;
if (val & BIT(IEEE80211_AC_BK))
arg.trig_bckgrnd = 1;
return wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_SET_UAPSD_INFORMATION,
&arg, sizeof(arg));
}
int wfx_hif_erp_use_protection(struct wfx_vif *wvif, bool enable)
{
struct wfx_hif_mib_non_erp_protection arg = {
.use_cts_to_self = enable,
};
return wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_NON_ERP_PROTECTION,
&arg, sizeof(arg));
}
int wfx_hif_slot_time(struct wfx_vif *wvif, int val)
{
struct wfx_hif_mib_slot_time arg = {
.slot_time = cpu_to_le32(val),
};
return wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_SLOT_TIME, &arg, sizeof(arg));
}
int wfx_hif_wep_default_key_id(struct wfx_vif *wvif, int val)
{
struct wfx_hif_mib_wep_default_key_id arg = {
.wep_default_key_id = val,
};
return wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_DOT11_WEP_DEFAULT_KEY_ID,
&arg, sizeof(arg));
}
int wfx_hif_rts_threshold(struct wfx_vif *wvif, int val)
{
struct wfx_hif_mib_dot11_rts_threshold arg = {
.threshold = cpu_to_le32(val >= 0 ? val : 0xFFFF),
};
return wfx_hif_write_mib(wvif->wdev, wvif->id, HIF_MIB_ID_DOT11_RTS_THRESHOLD,
&arg, sizeof(arg));
}
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Implementation of the host-to-chip MIBs of the hardware API.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
* Copyright (C) 2010, ST-Ericsson SA
*/
#ifndef WFX_HIF_TX_MIB_H
#define WFX_HIF_TX_MIB_H
#include <linux/types.h>
struct sk_buff;
struct wfx_vif;
struct wfx_dev;
struct wfx_hif_ie_table_entry;
struct wfx_hif_mib_extended_count_table;
int wfx_hif_set_output_power(struct wfx_vif *wvif, int val);
int wfx_hif_set_beacon_wakeup_period(struct wfx_vif *wvif,
unsigned int dtim_interval, unsigned int listen_interval);
int wfx_hif_set_rcpi_rssi_threshold(struct wfx_vif *wvif, int rssi_thold, int rssi_hyst);
int wfx_hif_get_counters_table(struct wfx_dev *wdev, int vif_id,
struct wfx_hif_mib_extended_count_table *arg);
int wfx_hif_set_macaddr(struct wfx_vif *wvif, u8 *mac);
int wfx_hif_set_rx_filter(struct wfx_vif *wvif, bool filter_bssid, bool fwd_probe_req);
int wfx_hif_set_beacon_filter_table(struct wfx_vif *wvif, int tbl_len,
const struct wfx_hif_ie_table_entry *tbl);
int wfx_hif_beacon_filter_control(struct wfx_vif *wvif, int enable, int beacon_count);
int wfx_hif_set_operational_mode(struct wfx_dev *wdev, enum wfx_hif_op_power_mode mode);
int wfx_hif_set_template_frame(struct wfx_vif *wvif, struct sk_buff *skb,
u8 frame_type, int init_rate);
int wfx_hif_set_mfp(struct wfx_vif *wvif, bool capable, bool required);
int wfx_hif_set_block_ack_policy(struct wfx_vif *wvif, u8 tx_tid_policy, u8 rx_tid_policy);
int wfx_hif_set_association_mode(struct wfx_vif *wvif, int ampdu_density,
bool greenfield, bool short_preamble);
int wfx_hif_set_tx_rate_retry_policy(struct wfx_vif *wvif, int policy_index, u8 *rates);
int wfx_hif_keep_alive_period(struct wfx_vif *wvif, int period);
int wfx_hif_set_arp_ipv4_filter(struct wfx_vif *wvif, int idx, __be32 *addr);
int wfx_hif_use_multi_tx_conf(struct wfx_dev *wdev, bool enable);
int wfx_hif_set_uapsd_info(struct wfx_vif *wvif, unsigned long val);
int wfx_hif_erp_use_protection(struct wfx_vif *wvif, bool enable);
int wfx_hif_slot_time(struct wfx_vif *wvif, int val);
int wfx_hif_wep_default_key_id(struct wfx_vif *wvif, int val);
int wfx_hif_rts_threshold(struct wfx_vif *wvif, int val);
#endif
@@ -0,0 +1,332 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Low-level I/O functions.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/align.h>
#include "hwio.h"
#include "wfx.h"
#include "bus.h"
#include "traces.h"
#define WFX_HIF_BUFFER_SIZE 0x2000
static int wfx_read32(struct wfx_dev *wdev, int reg, u32 *val)
{
int ret;
__le32 *tmp = kmalloc(sizeof(u32), GFP_KERNEL);
*val = ~0; /* Never return undefined value */
if (!tmp)
return -ENOMEM;
ret = wdev->hwbus_ops->copy_from_io(wdev->hwbus_priv, reg, tmp, sizeof(u32));
if (ret >= 0)
*val = le32_to_cpu(*tmp);
kfree(tmp);
if (ret)
dev_err(wdev->dev, "%s: bus communication error: %d\n", __func__, ret);
return ret;
}
static int wfx_write32(struct wfx_dev *wdev, int reg, u32 val)
{
int ret;
__le32 *tmp = kmalloc(sizeof(u32), GFP_KERNEL);
if (!tmp)
return -ENOMEM;
*tmp = cpu_to_le32(val);
ret = wdev->hwbus_ops->copy_to_io(wdev->hwbus_priv, reg, tmp, sizeof(u32));
kfree(tmp);
if (ret)
dev_err(wdev->dev, "%s: bus communication error: %d\n", __func__, ret);
return ret;
}
static int wfx_read32_locked(struct wfx_dev *wdev, int reg, u32 *val)
{
int ret;
wdev->hwbus_ops->lock(wdev->hwbus_priv);
ret = wfx_read32(wdev, reg, val);
_trace_io_read32(reg, *val);
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
return ret;
}
static int wfx_write32_locked(struct wfx_dev *wdev, int reg, u32 val)
{
int ret;
wdev->hwbus_ops->lock(wdev->hwbus_priv);
ret = wfx_write32(wdev, reg, val);
_trace_io_write32(reg, val);
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
return ret;
}
static int wfx_write32_bits_locked(struct wfx_dev *wdev, int reg, u32 mask, u32 val)
{
int ret;
u32 val_r, val_w;
WARN_ON(~mask & val);
val &= mask;
wdev->hwbus_ops->lock(wdev->hwbus_priv);
ret = wfx_read32(wdev, reg, &val_r);
_trace_io_read32(reg, val_r);
if (ret < 0)
goto err;
val_w = (val_r & ~mask) | val;
if (val_w != val_r) {
ret = wfx_write32(wdev, reg, val_w);
_trace_io_write32(reg, val_w);
}
err:
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
return ret;
}
static int wfx_indirect_read(struct wfx_dev *wdev, int reg, u32 addr, void *buf, size_t len)
{
int ret;
int i;
u32 cfg;
u32 prefetch;
WARN_ON(len >= WFX_HIF_BUFFER_SIZE);
WARN_ON(reg != WFX_REG_AHB_DPORT && reg != WFX_REG_SRAM_DPORT);
if (reg == WFX_REG_AHB_DPORT)
prefetch = CFG_PREFETCH_AHB;
else if (reg == WFX_REG_SRAM_DPORT)
prefetch = CFG_PREFETCH_SRAM;
else
return -ENODEV;
ret = wfx_write32(wdev, WFX_REG_BASE_ADDR, addr);
if (ret < 0)
goto err;
ret = wfx_read32(wdev, WFX_REG_CONFIG, &cfg);
if (ret < 0)
goto err;
ret = wfx_write32(wdev, WFX_REG_CONFIG, cfg | prefetch);
if (ret < 0)
goto err;
for (i = 0; i < 20; i++) {
ret = wfx_read32(wdev, WFX_REG_CONFIG, &cfg);
if (ret < 0)
goto err;
if (!(cfg & prefetch))
break;
usleep_range(200, 250);
}
if (i == 20) {
ret = -ETIMEDOUT;
goto err;
}
ret = wdev->hwbus_ops->copy_from_io(wdev->hwbus_priv, reg, buf, len);
err:
if (ret < 0)
memset(buf, 0xFF, len); /* Never return undefined value */
return ret;
}
static int wfx_indirect_write(struct wfx_dev *wdev, int reg, u32 addr,
const void *buf, size_t len)
{
int ret;
WARN_ON(len >= WFX_HIF_BUFFER_SIZE);
WARN_ON(reg != WFX_REG_AHB_DPORT && reg != WFX_REG_SRAM_DPORT);
ret = wfx_write32(wdev, WFX_REG_BASE_ADDR, addr);
if (ret < 0)
return ret;
return wdev->hwbus_ops->copy_to_io(wdev->hwbus_priv, reg, buf, len);
}
static int wfx_indirect_read_locked(struct wfx_dev *wdev, int reg, u32 addr,
void *buf, size_t len)
{
int ret;
wdev->hwbus_ops->lock(wdev->hwbus_priv);
ret = wfx_indirect_read(wdev, reg, addr, buf, len);
_trace_io_ind_read(reg, addr, buf, len);
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
return ret;
}
static int wfx_indirect_write_locked(struct wfx_dev *wdev, int reg, u32 addr,
const void *buf, size_t len)
{
int ret;
wdev->hwbus_ops->lock(wdev->hwbus_priv);
ret = wfx_indirect_write(wdev, reg, addr, buf, len);
_trace_io_ind_write(reg, addr, buf, len);
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
return ret;
}
static int wfx_indirect_read32_locked(struct wfx_dev *wdev, int reg, u32 addr, u32 *val)
{
int ret;
__le32 *tmp = kmalloc(sizeof(u32), GFP_KERNEL);
if (!tmp)
return -ENOMEM;
wdev->hwbus_ops->lock(wdev->hwbus_priv);
ret = wfx_indirect_read(wdev, reg, addr, tmp, sizeof(u32));
*val = le32_to_cpu(*tmp);
_trace_io_ind_read32(reg, addr, *val);
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
kfree(tmp);
return ret;
}
static int wfx_indirect_write32_locked(struct wfx_dev *wdev, int reg, u32 addr, u32 val)
{
int ret;
__le32 *tmp = kmalloc(sizeof(u32), GFP_KERNEL);
if (!tmp)
return -ENOMEM;
*tmp = cpu_to_le32(val);
wdev->hwbus_ops->lock(wdev->hwbus_priv);
ret = wfx_indirect_write(wdev, reg, addr, tmp, sizeof(u32));
_trace_io_ind_write32(reg, addr, val);
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
kfree(tmp);
return ret;
}
int wfx_data_read(struct wfx_dev *wdev, void *buf, size_t len)
{
int ret;
WARN(!IS_ALIGNED((uintptr_t)buf, 4), "unaligned buffer");
wdev->hwbus_ops->lock(wdev->hwbus_priv);
ret = wdev->hwbus_ops->copy_from_io(wdev->hwbus_priv, WFX_REG_IN_OUT_QUEUE, buf, len);
_trace_io_read(WFX_REG_IN_OUT_QUEUE, buf, len);
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
if (ret)
dev_err(wdev->dev, "%s: bus communication error: %d\n", __func__, ret);
return ret;
}
int wfx_data_write(struct wfx_dev *wdev, const void *buf, size_t len)
{
int ret;
WARN(!IS_ALIGNED((uintptr_t)buf, 4), "unaligned buffer");
wdev->hwbus_ops->lock(wdev->hwbus_priv);
ret = wdev->hwbus_ops->copy_to_io(wdev->hwbus_priv, WFX_REG_IN_OUT_QUEUE, buf, len);
_trace_io_write(WFX_REG_IN_OUT_QUEUE, buf, len);
wdev->hwbus_ops->unlock(wdev->hwbus_priv);
if (ret)
dev_err(wdev->dev, "%s: bus communication error: %d\n", __func__, ret);
return ret;
}
int wfx_sram_buf_read(struct wfx_dev *wdev, u32 addr, void *buf, size_t len)
{
return wfx_indirect_read_locked(wdev, WFX_REG_SRAM_DPORT, addr, buf, len);
}
int wfx_ahb_buf_read(struct wfx_dev *wdev, u32 addr, void *buf, size_t len)
{
return wfx_indirect_read_locked(wdev, WFX_REG_AHB_DPORT, addr, buf, len);
}
int wfx_sram_buf_write(struct wfx_dev *wdev, u32 addr, const void *buf, size_t len)
{
return wfx_indirect_write_locked(wdev, WFX_REG_SRAM_DPORT, addr, buf, len);
}
int wfx_ahb_buf_write(struct wfx_dev *wdev, u32 addr, const void *buf, size_t len)
{
return wfx_indirect_write_locked(wdev, WFX_REG_AHB_DPORT, addr, buf, len);
}
int wfx_sram_reg_read(struct wfx_dev *wdev, u32 addr, u32 *val)
{
return wfx_indirect_read32_locked(wdev, WFX_REG_SRAM_DPORT, addr, val);
}
int wfx_ahb_reg_read(struct wfx_dev *wdev, u32 addr, u32 *val)
{
return wfx_indirect_read32_locked(wdev, WFX_REG_AHB_DPORT, addr, val);
}
int wfx_sram_reg_write(struct wfx_dev *wdev, u32 addr, u32 val)
{
return wfx_indirect_write32_locked(wdev, WFX_REG_SRAM_DPORT, addr, val);
}
int wfx_ahb_reg_write(struct wfx_dev *wdev, u32 addr, u32 val)
{
return wfx_indirect_write32_locked(wdev, WFX_REG_AHB_DPORT, addr, val);
}
int wfx_config_reg_read(struct wfx_dev *wdev, u32 *val)
{
return wfx_read32_locked(wdev, WFX_REG_CONFIG, val);
}
int wfx_config_reg_write(struct wfx_dev *wdev, u32 val)
{
return wfx_write32_locked(wdev, WFX_REG_CONFIG, val);
}
int wfx_config_reg_write_bits(struct wfx_dev *wdev, u32 mask, u32 val)
{
return wfx_write32_bits_locked(wdev, WFX_REG_CONFIG, mask, val);
}
int wfx_control_reg_read(struct wfx_dev *wdev, u32 *val)
{
return wfx_read32_locked(wdev, WFX_REG_CONTROL, val);
}
int wfx_control_reg_write(struct wfx_dev *wdev, u32 val)
{
return wfx_write32_locked(wdev, WFX_REG_CONTROL, val);
}
int wfx_control_reg_write_bits(struct wfx_dev *wdev, u32 mask, u32 val)
{
return wfx_write32_bits_locked(wdev, WFX_REG_CONTROL, mask, val);
}
int wfx_igpr_reg_read(struct wfx_dev *wdev, int index, u32 *val)
{
int ret;
*val = ~0; /* Never return undefined value */
ret = wfx_write32_locked(wdev, WFX_REG_SET_GEN_R_W, IGPR_RW | index << 24);
if (ret)
return ret;
ret = wfx_read32_locked(wdev, WFX_REG_SET_GEN_R_W, val);
if (ret)
return ret;
*val &= IGPR_VALUE;
return ret;
}
int wfx_igpr_reg_write(struct wfx_dev *wdev, int index, u32 val)
{
return wfx_write32_locked(wdev, WFX_REG_SET_GEN_R_W, index << 24 | val);
}
@@ -0,0 +1,78 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Low-level I/O functions.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#ifndef WFX_HWIO_H
#define WFX_HWIO_H
#include <linux/types.h>
struct wfx_dev;
/* Caution: in the functions below, 'buf' will used with a DMA. So, it must be kmalloc'd (do not use
* stack allocated buffers). In doubt, enable CONFIG_DEBUG_SG to detect badly located buffer.
*/
int wfx_data_read(struct wfx_dev *wdev, void *buf, size_t buf_len);
int wfx_data_write(struct wfx_dev *wdev, const void *buf, size_t buf_len);
int wfx_sram_buf_read(struct wfx_dev *wdev, u32 addr, void *buf, size_t len);
int wfx_sram_buf_write(struct wfx_dev *wdev, u32 addr, const void *buf, size_t len);
int wfx_ahb_buf_read(struct wfx_dev *wdev, u32 addr, void *buf, size_t len);
int wfx_ahb_buf_write(struct wfx_dev *wdev, u32 addr, const void *buf, size_t len);
int wfx_sram_reg_read(struct wfx_dev *wdev, u32 addr, u32 *val);
int wfx_sram_reg_write(struct wfx_dev *wdev, u32 addr, u32 val);
int wfx_ahb_reg_read(struct wfx_dev *wdev, u32 addr, u32 *val);
int wfx_ahb_reg_write(struct wfx_dev *wdev, u32 addr, u32 val);
#define CFG_ERR_SPI_FRAME 0x00000001 /* only with SPI */
#define CFG_ERR_SDIO_BUF_MISMATCH 0x00000001 /* only with SDIO */
#define CFG_ERR_BUF_UNDERRUN 0x00000002
#define CFG_ERR_DATA_IN_TOO_LARGE 0x00000004
#define CFG_ERR_HOST_NO_OUT_QUEUE 0x00000008
#define CFG_ERR_BUF_OVERRUN 0x00000010
#define CFG_ERR_DATA_OUT_TOO_LARGE 0x00000020
#define CFG_ERR_HOST_NO_IN_QUEUE 0x00000040
#define CFG_ERR_HOST_CRC_MISS 0x00000080 /* only with SDIO */
#define CFG_SPI_IGNORE_CS 0x00000080 /* only with SPI */
#define CFG_BYTE_ORDER_MASK 0x00000300 /* only writable with SPI */
#define CFG_BYTE_ORDER_BADC 0x00000000
#define CFG_BYTE_ORDER_DCBA 0x00000100
#define CFG_BYTE_ORDER_ABCD 0x00000200 /* SDIO always use this value */
#define CFG_DIRECT_ACCESS_MODE 0x00000400
#define CFG_PREFETCH_AHB 0x00000800
#define CFG_DISABLE_CPU_CLK 0x00001000
#define CFG_PREFETCH_SRAM 0x00002000
#define CFG_CPU_RESET 0x00004000
#define CFG_SDIO_DISABLE_IRQ 0x00008000 /* only with SDIO */
#define CFG_IRQ_ENABLE_DATA 0x00010000
#define CFG_IRQ_ENABLE_WRDY 0x00020000
#define CFG_CLK_RISE_EDGE 0x00040000
#define CFG_SDIO_DISABLE_CRC_CHK 0x00080000 /* only with SDIO */
#define CFG_RESERVED 0x00F00000
#define CFG_DEVICE_ID_MAJOR 0x07000000
#define CFG_DEVICE_ID_RESERVED 0x78000000
#define CFG_DEVICE_ID_TYPE 0x80000000
int wfx_config_reg_read(struct wfx_dev *wdev, u32 *val);
int wfx_config_reg_write(struct wfx_dev *wdev, u32 val);
int wfx_config_reg_write_bits(struct wfx_dev *wdev, u32 mask, u32 val);
#define CTRL_NEXT_LEN_MASK 0x00000FFF
#define CTRL_WLAN_WAKEUP 0x00001000
#define CTRL_WLAN_READY 0x00002000
int wfx_control_reg_read(struct wfx_dev *wdev, u32 *val);
int wfx_control_reg_write(struct wfx_dev *wdev, u32 val);
int wfx_control_reg_write_bits(struct wfx_dev *wdev, u32 mask, u32 val);
#define IGPR_RW 0x80000000
#define IGPR_INDEX 0x7F000000
#define IGPR_VALUE 0x00FFFFFF
int wfx_igpr_reg_read(struct wfx_dev *wdev, int index, u32 *val);
int wfx_igpr_reg_write(struct wfx_dev *wdev, int index, u32 val);
#endif
@@ -0,0 +1,227 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Key management related functions.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#include <linux/etherdevice.h>
#include <net/mac80211.h>
#include "key.h"
#include "wfx.h"
#include "hif_tx_mib.h"
static int wfx_alloc_key(struct wfx_dev *wdev)
{
int idx;
idx = ffs(~wdev->key_map) - 1;
if (idx < 0 || idx >= MAX_KEY_ENTRIES)
return -1;
wdev->key_map |= BIT(idx);
return idx;
}
static void wfx_free_key(struct wfx_dev *wdev, int idx)
{
WARN(!(wdev->key_map & BIT(idx)), "inconsistent key allocation");
wdev->key_map &= ~BIT(idx);
}
static u8 fill_wep_pair(struct wfx_hif_wep_pairwise_key *msg,
struct ieee80211_key_conf *key, u8 *peer_addr)
{
WARN(key->keylen > sizeof(msg->key_data), "inconsistent data");
msg->key_length = key->keylen;
memcpy(msg->key_data, key->key, key->keylen);
ether_addr_copy(msg->peer_address, peer_addr);
return HIF_KEY_TYPE_WEP_PAIRWISE;
}
static u8 fill_wep_group(struct wfx_hif_wep_group_key *msg,
struct ieee80211_key_conf *key)
{
WARN(key->keylen > sizeof(msg->key_data), "inconsistent data");
msg->key_id = key->keyidx;
msg->key_length = key->keylen;
memcpy(msg->key_data, key->key, key->keylen);
return HIF_KEY_TYPE_WEP_DEFAULT;
}
static u8 fill_tkip_pair(struct wfx_hif_tkip_pairwise_key *msg,
struct ieee80211_key_conf *key, u8 *peer_addr)
{
u8 *keybuf = key->key;
WARN(key->keylen != sizeof(msg->tkip_key_data) + sizeof(msg->tx_mic_key) +
sizeof(msg->rx_mic_key), "inconsistent data");
memcpy(msg->tkip_key_data, keybuf, sizeof(msg->tkip_key_data));
keybuf += sizeof(msg->tkip_key_data);
memcpy(msg->tx_mic_key, keybuf, sizeof(msg->tx_mic_key));
keybuf += sizeof(msg->tx_mic_key);
memcpy(msg->rx_mic_key, keybuf, sizeof(msg->rx_mic_key));
ether_addr_copy(msg->peer_address, peer_addr);
return HIF_KEY_TYPE_TKIP_PAIRWISE;
}
static u8 fill_tkip_group(struct wfx_hif_tkip_group_key *msg, struct ieee80211_key_conf *key,
struct ieee80211_key_seq *seq, enum nl80211_iftype iftype)
{
u8 *keybuf = key->key;
WARN(key->keylen != sizeof(msg->tkip_key_data) + 2 * sizeof(msg->rx_mic_key),
"inconsistent data");
msg->key_id = key->keyidx;
memcpy(msg->rx_sequence_counter, &seq->tkip.iv16, sizeof(seq->tkip.iv16));
memcpy(msg->rx_sequence_counter + sizeof(u16), &seq->tkip.iv32, sizeof(seq->tkip.iv32));
memcpy(msg->tkip_key_data, keybuf, sizeof(msg->tkip_key_data));
keybuf += sizeof(msg->tkip_key_data);
if (iftype == NL80211_IFTYPE_AP)
/* Use Tx MIC Key */
memcpy(msg->rx_mic_key, keybuf + 0, sizeof(msg->rx_mic_key));
else
/* Use Rx MIC Key */
memcpy(msg->rx_mic_key, keybuf + 8, sizeof(msg->rx_mic_key));
return HIF_KEY_TYPE_TKIP_GROUP;
}
static u8 fill_ccmp_pair(struct wfx_hif_aes_pairwise_key *msg,
struct ieee80211_key_conf *key, u8 *peer_addr)
{
WARN(key->keylen != sizeof(msg->aes_key_data), "inconsistent data");
ether_addr_copy(msg->peer_address, peer_addr);
memcpy(msg->aes_key_data, key->key, key->keylen);
return HIF_KEY_TYPE_AES_PAIRWISE;
}
static u8 fill_ccmp_group(struct wfx_hif_aes_group_key *msg,
struct ieee80211_key_conf *key, struct ieee80211_key_seq *seq)
{
WARN(key->keylen != sizeof(msg->aes_key_data), "inconsistent data");
memcpy(msg->aes_key_data, key->key, key->keylen);
memcpy(msg->rx_sequence_counter, seq->ccmp.pn, sizeof(seq->ccmp.pn));
memreverse(msg->rx_sequence_counter, sizeof(seq->ccmp.pn));
msg->key_id = key->keyidx;
return HIF_KEY_TYPE_AES_GROUP;
}
static u8 fill_sms4_pair(struct wfx_hif_wapi_pairwise_key *msg,
struct ieee80211_key_conf *key, u8 *peer_addr)
{
u8 *keybuf = key->key;
WARN(key->keylen != sizeof(msg->wapi_key_data) + sizeof(msg->mic_key_data),
"inconsistent data");
ether_addr_copy(msg->peer_address, peer_addr);
memcpy(msg->wapi_key_data, keybuf, sizeof(msg->wapi_key_data));
keybuf += sizeof(msg->wapi_key_data);
memcpy(msg->mic_key_data, keybuf, sizeof(msg->mic_key_data));
msg->key_id = key->keyidx;
return HIF_KEY_TYPE_WAPI_PAIRWISE;
}
static u8 fill_sms4_group(struct wfx_hif_wapi_group_key *msg,
struct ieee80211_key_conf *key)
{
u8 *keybuf = key->key;
WARN(key->keylen != sizeof(msg->wapi_key_data) + sizeof(msg->mic_key_data),
"inconsistent data");
memcpy(msg->wapi_key_data, keybuf, sizeof(msg->wapi_key_data));
keybuf += sizeof(msg->wapi_key_data);
memcpy(msg->mic_key_data, keybuf, sizeof(msg->mic_key_data));
msg->key_id = key->keyidx;
return HIF_KEY_TYPE_WAPI_GROUP;
}
static u8 fill_aes_cmac_group(struct wfx_hif_igtk_group_key *msg,
struct ieee80211_key_conf *key, struct ieee80211_key_seq *seq)
{
WARN(key->keylen != sizeof(msg->igtk_key_data), "inconsistent data");
memcpy(msg->igtk_key_data, key->key, key->keylen);
memcpy(msg->ipn, seq->aes_cmac.pn, sizeof(seq->aes_cmac.pn));
memreverse(msg->ipn, sizeof(seq->aes_cmac.pn));
msg->key_id = key->keyidx;
return HIF_KEY_TYPE_IGTK_GROUP;
}
static int wfx_add_key(struct wfx_vif *wvif, struct ieee80211_sta *sta,
struct ieee80211_key_conf *key)
{
int ret;
struct wfx_hif_req_add_key k = { };
struct ieee80211_key_seq seq;
struct wfx_dev *wdev = wvif->wdev;
int idx = wfx_alloc_key(wvif->wdev);
bool pairwise = key->flags & IEEE80211_KEY_FLAG_PAIRWISE;
struct ieee80211_vif *vif = wvif_to_vif(wvif);
WARN(key->flags & IEEE80211_KEY_FLAG_PAIRWISE && !sta, "inconsistent data");
ieee80211_get_key_rx_seq(key, 0, &seq);
if (idx < 0)
return -EINVAL;
k.int_id = wvif->id;
k.entry_index = idx;
if (key->cipher == WLAN_CIPHER_SUITE_WEP40 ||
key->cipher == WLAN_CIPHER_SUITE_WEP104) {
if (pairwise)
k.type = fill_wep_pair(&k.key.wep_pairwise_key, key, sta->addr);
else
k.type = fill_wep_group(&k.key.wep_group_key, key);
} else if (key->cipher == WLAN_CIPHER_SUITE_TKIP) {
if (pairwise)
k.type = fill_tkip_pair(&k.key.tkip_pairwise_key, key, sta->addr);
else
k.type = fill_tkip_group(&k.key.tkip_group_key, key, &seq,
vif->type);
} else if (key->cipher == WLAN_CIPHER_SUITE_CCMP) {
if (pairwise)
k.type = fill_ccmp_pair(&k.key.aes_pairwise_key, key, sta->addr);
else
k.type = fill_ccmp_group(&k.key.aes_group_key, key, &seq);
} else if (key->cipher == WLAN_CIPHER_SUITE_SMS4) {
if (pairwise)
k.type = fill_sms4_pair(&k.key.wapi_pairwise_key, key, sta->addr);
else
k.type = fill_sms4_group(&k.key.wapi_group_key, key);
} else if (key->cipher == WLAN_CIPHER_SUITE_AES_CMAC) {
k.type = fill_aes_cmac_group(&k.key.igtk_group_key, key, &seq);
key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIE;
} else {
dev_warn(wdev->dev, "unsupported key type %d\n", key->cipher);
wfx_free_key(wdev, idx);
return -EOPNOTSUPP;
}
ret = wfx_hif_add_key(wdev, &k);
if (ret) {
wfx_free_key(wdev, idx);
return -EOPNOTSUPP;
}
key->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE | IEEE80211_KEY_FLAG_RESERVE_TAILROOM;
key->hw_key_idx = idx;
return 0;
}
static int wfx_remove_key(struct wfx_vif *wvif, struct ieee80211_key_conf *key)
{
WARN(key->hw_key_idx >= MAX_KEY_ENTRIES, "corrupted hw_key_idx");
wfx_free_key(wvif->wdev, key->hw_key_idx);
return wfx_hif_remove_key(wvif->wdev, key->hw_key_idx);
}
int wfx_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, struct ieee80211_vif *vif,
struct ieee80211_sta *sta, struct ieee80211_key_conf *key)
{
int ret = -EOPNOTSUPP;
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
mutex_lock(&wvif->wdev->conf_mutex);
if (cmd == SET_KEY)
ret = wfx_add_key(wvif, sta, key);
if (cmd == DISABLE_KEY)
ret = wfx_remove_key(wvif, key);
mutex_unlock(&wvif->wdev->conf_mutex);
return ret;
}
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Key management related functions.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#ifndef WFX_KEY_H
#define WFX_KEY_H
#include <net/mac80211.h>
struct wfx_dev;
struct wfx_vif;
int wfx_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, struct ieee80211_vif *vif,
struct ieee80211_sta *sta, struct ieee80211_key_conf *key);
#endif
@@ -0,0 +1,525 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Device probe and register.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
* Copyright (c) 2008, Johannes Berg <johannes@sipsolutions.net>
* Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
* Copyright (c) 2007-2009, Christian Lamparter <chunkeey@web.de>
* Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
* Copyright (c) 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
*/
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_net.h>
#include <linux/gpio/consumer.h>
#include <linux/mmc/sdio_func.h>
#include <linux/spi/spi.h>
#include <linux/etherdevice.h>
#include <linux/firmware.h>
#include "main.h"
#include "wfx.h"
#include "fwio.h"
#include "hwio.h"
#include "bus.h"
#include "bh.h"
#include "sta.h"
#include "key.h"
#include "scan.h"
#include "debug.h"
#include "data_tx.h"
#include "hif_tx_mib.h"
#include "hif_api_cmd.h"
#define WFX_PDS_TLV_TYPE 0x4450 // "PD" (Platform Data) in ascii little-endian
#define WFX_PDS_MAX_CHUNK_SIZE 1500
MODULE_DESCRIPTION("Silicon Labs 802.11 Wireless LAN driver for WF200");
MODULE_AUTHOR("Jérôme Pouiller <jerome.pouiller@silabs.com>");
MODULE_LICENSE("GPL");
#define RATETAB_ENT(_rate, _rateid, _flags) { \
.bitrate = (_rate), \
.hw_value = (_rateid), \
.flags = (_flags), \
}
static struct ieee80211_rate wfx_rates[] = {
RATETAB_ENT(10, 0, 0),
RATETAB_ENT(20, 1, IEEE80211_RATE_SHORT_PREAMBLE),
RATETAB_ENT(55, 2, IEEE80211_RATE_SHORT_PREAMBLE),
RATETAB_ENT(110, 3, IEEE80211_RATE_SHORT_PREAMBLE),
RATETAB_ENT(60, 6, 0),
RATETAB_ENT(90, 7, 0),
RATETAB_ENT(120, 8, 0),
RATETAB_ENT(180, 9, 0),
RATETAB_ENT(240, 10, 0),
RATETAB_ENT(360, 11, 0),
RATETAB_ENT(480, 12, 0),
RATETAB_ENT(540, 13, 0),
};
#define CHAN2G(_channel, _freq, _flags) { \
.band = NL80211_BAND_2GHZ, \
.center_freq = (_freq), \
.hw_value = (_channel), \
.flags = (_flags), \
.max_antenna_gain = 0, \
.max_power = 30, \
}
static struct ieee80211_channel wfx_2ghz_chantable[] = {
CHAN2G(1, 2412, 0),
CHAN2G(2, 2417, 0),
CHAN2G(3, 2422, 0),
CHAN2G(4, 2427, 0),
CHAN2G(5, 2432, 0),
CHAN2G(6, 2437, 0),
CHAN2G(7, 2442, 0),
CHAN2G(8, 2447, 0),
CHAN2G(9, 2452, 0),
CHAN2G(10, 2457, 0),
CHAN2G(11, 2462, 0),
CHAN2G(12, 2467, 0),
CHAN2G(13, 2472, 0),
CHAN2G(14, 2484, 0),
};
static const struct ieee80211_supported_band wfx_band_2ghz = {
.channels = wfx_2ghz_chantable,
.n_channels = ARRAY_SIZE(wfx_2ghz_chantable),
.bitrates = wfx_rates,
.n_bitrates = ARRAY_SIZE(wfx_rates),
.ht_cap = {
/* Receive caps */
.cap = IEEE80211_HT_CAP_GRN_FLD | IEEE80211_HT_CAP_SGI_20 |
IEEE80211_HT_CAP_MAX_AMSDU | (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT),
.ht_supported = 1,
.ampdu_factor = IEEE80211_HT_MAX_AMPDU_16K,
.ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE,
.mcs = {
.rx_mask = { 0xFF }, /* MCS0 to MCS7 */
.rx_highest = cpu_to_le16(72),
.tx_params = IEEE80211_HT_MCS_TX_DEFINED,
},
},
};
static const struct ieee80211_iface_limit wdev_iface_limits[] = {
{ .max = 1, .types = BIT(NL80211_IFTYPE_STATION) },
{ .max = 1, .types = BIT(NL80211_IFTYPE_AP) },
};
static const struct ieee80211_iface_combination wfx_iface_combinations[] = {
{
.num_different_channels = 2,
.max_interfaces = 2,
.limits = wdev_iface_limits,
.n_limits = ARRAY_SIZE(wdev_iface_limits),
}
};
#ifdef CONFIG_PM
static const struct wiphy_wowlan_support wfx_wowlan_support = {
.flags = WIPHY_WOWLAN_ANY | WIPHY_WOWLAN_DISCONNECT,
};
#endif
static const struct ieee80211_ops wfx_ops = {
.start = wfx_start,
.stop = wfx_stop,
.add_interface = wfx_add_interface,
.remove_interface = wfx_remove_interface,
.config = wfx_config,
.tx = wfx_tx,
.wake_tx_queue = ieee80211_handle_wake_tx_queue,
.join_ibss = wfx_join_ibss,
.leave_ibss = wfx_leave_ibss,
.conf_tx = wfx_conf_tx,
.hw_scan = wfx_hw_scan,
.cancel_hw_scan = wfx_cancel_hw_scan,
.start_ap = wfx_start_ap,
.stop_ap = wfx_stop_ap,
.sta_add = wfx_sta_add,
.sta_remove = wfx_sta_remove,
.set_tim = wfx_set_tim,
.set_key = wfx_set_key,
.set_rts_threshold = wfx_set_rts_threshold,
.set_default_unicast_key = wfx_set_default_unicast_key,
.bss_info_changed = wfx_bss_info_changed,
.configure_filter = wfx_configure_filter,
.ampdu_action = wfx_ampdu_action,
.flush = wfx_flush,
.add_chanctx = wfx_add_chanctx,
.remove_chanctx = wfx_remove_chanctx,
.change_chanctx = wfx_change_chanctx,
.assign_vif_chanctx = wfx_assign_vif_chanctx,
.unassign_vif_chanctx = wfx_unassign_vif_chanctx,
.remain_on_channel = wfx_remain_on_channel,
.cancel_remain_on_channel = wfx_cancel_remain_on_channel,
#ifdef CONFIG_PM
.suspend = wfx_suspend,
.resume = wfx_resume,
.set_wakeup = wfx_set_wakeup,
#endif
};
bool wfx_api_older_than(struct wfx_dev *wdev, int major, int minor)
{
if (wdev->hw_caps.api_version_major < major)
return true;
if (wdev->hw_caps.api_version_major > major)
return false;
if (wdev->hw_caps.api_version_minor < minor)
return true;
return false;
}
/* The device needs data about the antenna configuration. This information in provided by PDS
* (Platform Data Set, this is the wording used in WF200 documentation) files. For hardware
* integrators, the full process to create PDS files is described here:
* https://github.com/SiliconLabs/wfx-firmware/blob/master/PDS/README.md
*
* The PDS file is an array of Time-Length-Value structs.
*/
int wfx_send_pds(struct wfx_dev *wdev, u8 *buf, size_t len)
{
int ret, chunk_type, chunk_len, chunk_num = 0;
if (*buf == '{') {
dev_err(wdev->dev, "PDS: malformed file (legacy format?)\n");
return -EINVAL;
}
while (len > 0) {
chunk_type = get_unaligned_le16(buf + 0);
chunk_len = get_unaligned_le16(buf + 2);
if (chunk_len < 4 || chunk_len > len) {
dev_err(wdev->dev, "PDS:%d: corrupted file\n", chunk_num);
return -EINVAL;
}
if (chunk_type != WFX_PDS_TLV_TYPE) {
dev_info(wdev->dev, "PDS:%d: skip unknown data\n", chunk_num);
goto next;
}
if (chunk_len > WFX_PDS_MAX_CHUNK_SIZE)
dev_warn(wdev->dev, "PDS:%d: unexpectedly large chunk\n", chunk_num);
if (buf[4] != '{' || buf[chunk_len - 1] != '}')
dev_warn(wdev->dev, "PDS:%d: unexpected content\n", chunk_num);
ret = wfx_hif_configuration(wdev, buf + 4, chunk_len - 4);
if (ret > 0) {
dev_err(wdev->dev, "PDS:%d: invalid data (unsupported options?)\n", chunk_num);
return -EINVAL;
}
if (ret == -ETIMEDOUT) {
dev_err(wdev->dev, "PDS:%d: chip didn't reply (corrupted file?)\n", chunk_num);
return ret;
}
if (ret) {
dev_err(wdev->dev, "PDS:%d: chip returned an unknown error\n", chunk_num);
return -EIO;
}
next:
chunk_num++;
len -= chunk_len;
buf += chunk_len;
}
return 0;
}
static int wfx_send_pdata_pds(struct wfx_dev *wdev)
{
int ret = 0;
const struct firmware *pds;
u8 *tmp_buf;
ret = request_firmware(&pds, wdev->pdata.file_pds, wdev->dev);
if (ret) {
dev_err(wdev->dev, "can't load antenna parameters (PDS file %s). The device may be unstable.\n",
wdev->pdata.file_pds);
return ret;
}
tmp_buf = kmemdup(pds->data, pds->size, GFP_KERNEL);
if (!tmp_buf) {
ret = -ENOMEM;
goto release_fw;
}
ret = wfx_send_pds(wdev, tmp_buf, pds->size);
kfree(tmp_buf);
release_fw:
release_firmware(pds);
return ret;
}
static void wfx_free_common(void *data)
{
struct wfx_dev *wdev = data;
mutex_destroy(&wdev->tx_power_loop_info_lock);
mutex_destroy(&wdev->rx_stats_lock);
mutex_destroy(&wdev->scan_lock);
mutex_destroy(&wdev->conf_mutex);
ieee80211_free_hw(wdev->hw);
}
struct wfx_dev *wfx_init_common(struct device *dev, const struct wfx_platform_data *pdata,
const struct wfx_hwbus_ops *hwbus_ops, void *hwbus_priv)
{
struct ieee80211_hw *hw;
struct wfx_dev *wdev;
hw = ieee80211_alloc_hw(sizeof(struct wfx_dev), &wfx_ops);
if (!hw)
return NULL;
SET_IEEE80211_DEV(hw, dev);
ieee80211_hw_set(hw, TX_AMPDU_SETUP_IN_HW);
ieee80211_hw_set(hw, AMPDU_AGGREGATION);
ieee80211_hw_set(hw, CONNECTION_MONITOR);
ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS);
ieee80211_hw_set(hw, SUPPORTS_DYNAMIC_PS);
ieee80211_hw_set(hw, SIGNAL_DBM);
ieee80211_hw_set(hw, SUPPORTS_PS);
ieee80211_hw_set(hw, MFP_CAPABLE);
hw->vif_data_size = sizeof(struct wfx_vif);
hw->sta_data_size = sizeof(struct wfx_sta_priv);
hw->queues = 4;
hw->max_rates = 8;
hw->max_rate_tries = 8;
hw->extra_tx_headroom = sizeof(struct wfx_hif_msg) + sizeof(struct wfx_hif_req_tx) +
4 /* alignment */ + 8 /* TKIP IV */;
hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
BIT(NL80211_IFTYPE_ADHOC) |
BIT(NL80211_IFTYPE_AP);
hw->wiphy->probe_resp_offload = NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS |
NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 |
NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P |
NL80211_PROBE_RESP_OFFLOAD_SUPPORT_80211U;
hw->wiphy->features |= NL80211_FEATURE_AP_SCAN;
#ifdef CONFIG_PM
hw->wiphy->wowlan = &wfx_wowlan_support;
#endif
hw->wiphy->flags |= WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD;
hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD;
hw->wiphy->max_remain_on_channel_duration = 5000;
hw->wiphy->max_ap_assoc_sta = HIF_LINK_ID_MAX;
hw->wiphy->max_scan_ssids = 2;
hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN;
hw->wiphy->n_iface_combinations = ARRAY_SIZE(wfx_iface_combinations);
hw->wiphy->iface_combinations = wfx_iface_combinations;
/* FIXME: also copy wfx_rates and wfx_2ghz_chantable */
hw->wiphy->bands[NL80211_BAND_2GHZ] = devm_kmemdup(dev, &wfx_band_2ghz,
sizeof(wfx_band_2ghz), GFP_KERNEL);
if (!hw->wiphy->bands[NL80211_BAND_2GHZ])
goto err;
wdev = hw->priv;
wdev->hw = hw;
wdev->dev = dev;
wdev->hwbus_ops = hwbus_ops;
wdev->hwbus_priv = hwbus_priv;
memcpy(&wdev->pdata, pdata, sizeof(*pdata));
of_property_read_string(dev->of_node, "silabs,antenna-config-file", &wdev->pdata.file_pds);
wdev->pdata.gpio_wakeup = devm_gpiod_get_optional(dev, "wakeup", GPIOD_OUT_LOW);
if (IS_ERR(wdev->pdata.gpio_wakeup))
goto err;
if (wdev->pdata.gpio_wakeup)
gpiod_set_consumer_name(wdev->pdata.gpio_wakeup, "wfx wakeup");
mutex_init(&wdev->conf_mutex);
mutex_init(&wdev->scan_lock);
mutex_init(&wdev->rx_stats_lock);
mutex_init(&wdev->tx_power_loop_info_lock);
init_completion(&wdev->firmware_ready);
INIT_DELAYED_WORK(&wdev->cooling_timeout_work, wfx_cooling_timeout_work);
skb_queue_head_init(&wdev->tx_pending);
init_waitqueue_head(&wdev->tx_dequeue);
wfx_init_hif_cmd(&wdev->hif_cmd);
if (devm_add_action_or_reset(dev, wfx_free_common, wdev))
return NULL;
return wdev;
err:
ieee80211_free_hw(hw);
return NULL;
}
int wfx_probe(struct wfx_dev *wdev)
{
int i;
int err;
struct gpio_desc *gpio_saved;
/* During first part of boot, gpio_wakeup cannot yet been used. So prevent bh() to touch
* it.
*/
gpio_saved = wdev->pdata.gpio_wakeup;
wdev->pdata.gpio_wakeup = NULL;
wdev->poll_irq = true;
wdev->bh_wq = alloc_workqueue("wfx_bh_wq", WQ_HIGHPRI | WQ_PERCPU, 0);
if (!wdev->bh_wq)
return -ENOMEM;
wfx_bh_register(wdev);
err = wfx_init_device(wdev);
if (err)
goto bh_unregister;
wfx_bh_poll_irq(wdev);
err = wait_for_completion_timeout(&wdev->firmware_ready, 1 * HZ);
if (err == 0) {
dev_err(wdev->dev, "timeout while waiting for startup indication\n");
err = -ETIMEDOUT;
goto bh_unregister;
}
/* FIXME: fill wiphy::hw_version */
dev_info(wdev->dev, "started firmware %d.%d.%d \"%s\" (API: %d.%d, keyset: %02X, caps: 0x%.8X)\n",
wdev->hw_caps.firmware_major, wdev->hw_caps.firmware_minor,
wdev->hw_caps.firmware_build, wdev->hw_caps.firmware_label,
wdev->hw_caps.api_version_major, wdev->hw_caps.api_version_minor,
wdev->keyset, wdev->hw_caps.link_mode);
snprintf(wdev->hw->wiphy->fw_version,
sizeof(wdev->hw->wiphy->fw_version),
"%d.%d.%d",
wdev->hw_caps.firmware_major,
wdev->hw_caps.firmware_minor,
wdev->hw_caps.firmware_build);
if (wfx_api_older_than(wdev, 1, 0)) {
dev_err(wdev->dev, "unsupported firmware API version (expect 1 while firmware returns %d)\n",
wdev->hw_caps.api_version_major);
err = -EOPNOTSUPP;
goto bh_unregister;
}
if (wdev->hw_caps.link_mode == SEC_LINK_ENFORCED) {
dev_err(wdev->dev, "chip require secure_link, but can't negotiate it\n");
goto bh_unregister;
}
if (wdev->hw_caps.region_sel_mode) {
wdev->hw->wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS;
wdev->hw->wiphy->bands[NL80211_BAND_2GHZ]->channels[11].flags |=
IEEE80211_CHAN_NO_IR;
wdev->hw->wiphy->bands[NL80211_BAND_2GHZ]->channels[12].flags |=
IEEE80211_CHAN_NO_IR;
wdev->hw->wiphy->bands[NL80211_BAND_2GHZ]->channels[13].flags |=
IEEE80211_CHAN_DISABLED;
}
dev_dbg(wdev->dev, "sending configuration file %s\n", wdev->pdata.file_pds);
err = wfx_send_pdata_pds(wdev);
if (err < 0 && err != -ENOENT)
goto bh_unregister;
wdev->poll_irq = false;
err = wdev->hwbus_ops->irq_subscribe(wdev->hwbus_priv);
if (err)
goto bh_unregister;
err = wfx_hif_use_multi_tx_conf(wdev, true);
if (err)
dev_err(wdev->dev, "misconfigured IRQ?\n");
wdev->pdata.gpio_wakeup = gpio_saved;
if (wdev->pdata.gpio_wakeup) {
dev_dbg(wdev->dev, "enable 'quiescent' power mode with wakeup GPIO and PDS file %s\n",
wdev->pdata.file_pds);
gpiod_set_value_cansleep(wdev->pdata.gpio_wakeup, 1);
wfx_control_reg_write(wdev, 0);
wfx_hif_set_operational_mode(wdev, HIF_OP_POWER_MODE_QUIESCENT);
} else {
wfx_hif_set_operational_mode(wdev, HIF_OP_POWER_MODE_DOZE);
}
for (i = 0; i < ARRAY_SIZE(wdev->addresses); i++) {
eth_zero_addr(wdev->addresses[i].addr);
err = of_get_mac_address(wdev->dev->of_node, wdev->addresses[i].addr);
if (!err)
wdev->addresses[i].addr[ETH_ALEN - 1] += i;
else
ether_addr_copy(wdev->addresses[i].addr, wdev->hw_caps.mac_addr[i]);
if (!is_valid_ether_addr(wdev->addresses[i].addr)) {
dev_warn(wdev->dev, "using random MAC address\n");
eth_random_addr(wdev->addresses[i].addr);
}
dev_info(wdev->dev, "MAC address %d: %pM\n", i, wdev->addresses[i].addr);
}
wdev->hw->wiphy->n_addresses = ARRAY_SIZE(wdev->addresses);
wdev->hw->wiphy->addresses = wdev->addresses;
if (!wfx_api_older_than(wdev, 3, 8))
wdev->hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS;
err = ieee80211_register_hw(wdev->hw);
if (err)
goto irq_unsubscribe;
err = wfx_debug_init(wdev);
if (err)
goto ieee80211_unregister;
return 0;
ieee80211_unregister:
ieee80211_unregister_hw(wdev->hw);
irq_unsubscribe:
wdev->hwbus_ops->irq_unsubscribe(wdev->hwbus_priv);
bh_unregister:
wfx_bh_unregister(wdev);
destroy_workqueue(wdev->bh_wq);
return err;
}
void wfx_release(struct wfx_dev *wdev)
{
ieee80211_unregister_hw(wdev->hw);
wfx_hif_shutdown(wdev);
wdev->hwbus_ops->irq_unsubscribe(wdev->hwbus_priv);
wfx_bh_unregister(wdev);
destroy_workqueue(wdev->bh_wq);
}
static int __init wfx_core_init(void)
{
int ret = 0;
if (IS_ENABLED(CONFIG_SPI)) {
ret = spi_register_driver(&wfx_spi_driver);
if (ret)
goto out;
}
if (IS_ENABLED(CONFIG_MMC)) {
ret = sdio_register_driver(&wfx_sdio_driver);
if (ret)
goto unregister_spi;
}
return 0;
unregister_spi:
if (IS_ENABLED(CONFIG_SPI))
spi_unregister_driver(&wfx_spi_driver);
out:
return ret;
}
module_init(wfx_core_init);
static void __exit wfx_core_exit(void)
{
if (IS_ENABLED(CONFIG_MMC))
sdio_unregister_driver(&wfx_sdio_driver);
if (IS_ENABLED(CONFIG_SPI))
spi_unregister_driver(&wfx_spi_driver);
}
module_exit(wfx_core_exit);
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Device probe and register.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
* Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
* Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
*/
#ifndef WFX_MAIN_H
#define WFX_MAIN_H
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include "hif_api_general.h"
struct wfx_dev;
struct wfx_hwbus_ops;
struct wfx_platform_data {
/* Keyset and ".sec" extension will be appended to this string */
const char *file_fw;
const char *file_pds;
struct gpio_desc *gpio_wakeup;
/* if true HIF D_out is sampled on the rising edge of the clock (intended to be used in
* 50Mhz SDIO)
*/
bool use_rising_clk;
};
struct wfx_dev *wfx_init_common(struct device *dev, const struct wfx_platform_data *pdata,
const struct wfx_hwbus_ops *hwbus_ops, void *hwbus_priv);
int wfx_probe(struct wfx_dev *wdev);
void wfx_release(struct wfx_dev *wdev);
bool wfx_api_older_than(struct wfx_dev *wdev, int major, int minor);
int wfx_send_pds(struct wfx_dev *wdev, u8 *buf, size_t len);
#endif
@@ -0,0 +1,322 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Queue between the tx operation and the bh workqueue.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#include <linux/sched.h>
#include <net/mac80211.h>
#include "queue.h"
#include "wfx.h"
#include "sta.h"
#include "data_tx.h"
#include "traces.h"
void wfx_tx_lock(struct wfx_dev *wdev)
{
atomic_inc(&wdev->tx_lock);
}
void wfx_tx_unlock(struct wfx_dev *wdev)
{
int tx_lock = atomic_dec_return(&wdev->tx_lock);
WARN(tx_lock < 0, "inconsistent tx_lock value");
if (!tx_lock)
wfx_bh_request_tx(wdev);
}
void wfx_tx_flush(struct wfx_dev *wdev)
{
int ret;
/* Do not wait for any reply if chip is frozen */
if (wdev->chip_frozen)
return;
wfx_tx_lock(wdev);
mutex_lock(&wdev->hif_cmd.lock);
ret = wait_event_timeout(wdev->hif.tx_buffers_empty, !wdev->hif.tx_buffers_used,
msecs_to_jiffies(3000));
if (!ret) {
dev_warn(wdev->dev, "cannot flush tx buffers (%d still busy)\n",
wdev->hif.tx_buffers_used);
wfx_pending_dump_old_frames(wdev, 3000);
/* FIXME: drop pending frames here */
wdev->chip_frozen = true;
}
mutex_unlock(&wdev->hif_cmd.lock);
wfx_tx_unlock(wdev);
}
void wfx_tx_lock_flush(struct wfx_dev *wdev)
{
wfx_tx_lock(wdev);
wfx_tx_flush(wdev);
}
void wfx_tx_queues_init(struct wfx_vif *wvif)
{
/* The device is in charge to respect the details of the QoS parameters. The driver just
* ensure that it roughtly respect the priorities to avoid any shortage.
*/
const int priorities[IEEE80211_NUM_ACS] = { 1, 2, 64, 128 };
int i;
for (i = 0; i < IEEE80211_NUM_ACS; ++i) {
skb_queue_head_init(&wvif->tx_queue[i].normal);
skb_queue_head_init(&wvif->tx_queue[i].cab);
skb_queue_head_init(&wvif->tx_queue[i].offchan);
wvif->tx_queue[i].priority = priorities[i];
}
}
bool wfx_tx_queue_empty(struct wfx_vif *wvif, struct wfx_queue *queue)
{
return skb_queue_empty_lockless(&queue->normal) &&
skb_queue_empty_lockless(&queue->cab) &&
skb_queue_empty_lockless(&queue->offchan);
}
void wfx_tx_queues_check_empty(struct wfx_vif *wvif)
{
int i;
for (i = 0; i < IEEE80211_NUM_ACS; ++i) {
WARN_ON(atomic_read(&wvif->tx_queue[i].pending_frames));
WARN_ON(!wfx_tx_queue_empty(wvif, &wvif->tx_queue[i]));
}
}
static void __wfx_tx_queue_drop(struct wfx_vif *wvif,
struct sk_buff_head *skb_queue, struct sk_buff_head *dropped)
{
struct sk_buff *skb, *tmp;
spin_lock_bh(&skb_queue->lock);
skb_queue_walk_safe(skb_queue, skb, tmp) {
__skb_unlink(skb, skb_queue);
skb_queue_head(dropped, skb);
}
spin_unlock_bh(&skb_queue->lock);
}
void wfx_tx_queue_drop(struct wfx_vif *wvif, struct wfx_queue *queue,
struct sk_buff_head *dropped)
{
__wfx_tx_queue_drop(wvif, &queue->normal, dropped);
__wfx_tx_queue_drop(wvif, &queue->cab, dropped);
__wfx_tx_queue_drop(wvif, &queue->offchan, dropped);
wake_up(&wvif->wdev->tx_dequeue);
}
void wfx_tx_queues_put(struct wfx_vif *wvif, struct sk_buff *skb)
{
struct wfx_queue *queue = &wvif->tx_queue[skb_get_queue_mapping(skb)];
struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
if (tx_info->flags & IEEE80211_TX_CTL_TX_OFFCHAN)
skb_queue_tail(&queue->offchan, skb);
else if (tx_info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM)
skb_queue_tail(&queue->cab, skb);
else
skb_queue_tail(&queue->normal, skb);
}
void wfx_pending_drop(struct wfx_dev *wdev, struct sk_buff_head *dropped)
{
struct wfx_queue *queue;
struct wfx_vif *wvif;
struct sk_buff *skb;
WARN(!wdev->chip_frozen, "%s should only be used to recover a frozen device", __func__);
while ((skb = skb_dequeue(&wdev->tx_pending)) != NULL) {
wvif = wfx_skb_wvif(wdev, skb);
if (wvif) {
queue = &wvif->tx_queue[skb_get_queue_mapping(skb)];
WARN_ON(skb_get_queue_mapping(skb) > 3);
WARN_ON(!atomic_read(&queue->pending_frames));
atomic_dec(&queue->pending_frames);
}
skb_queue_head(dropped, skb);
}
}
struct sk_buff *wfx_pending_get(struct wfx_dev *wdev, u32 packet_id)
{
struct wfx_queue *queue;
struct wfx_hif_req_tx *req;
struct wfx_vif *wvif;
struct wfx_hif_msg *hif;
struct sk_buff *skb;
spin_lock_bh(&wdev->tx_pending.lock);
skb_queue_walk(&wdev->tx_pending, skb) {
hif = (struct wfx_hif_msg *)skb->data;
req = (struct wfx_hif_req_tx *)hif->body;
if (req->packet_id != packet_id)
continue;
spin_unlock_bh(&wdev->tx_pending.lock);
wvif = wfx_skb_wvif(wdev, skb);
if (wvif) {
queue = &wvif->tx_queue[skb_get_queue_mapping(skb)];
WARN_ON(skb_get_queue_mapping(skb) > 3);
WARN_ON(!atomic_read(&queue->pending_frames));
atomic_dec(&queue->pending_frames);
}
skb_unlink(skb, &wdev->tx_pending);
return skb;
}
spin_unlock_bh(&wdev->tx_pending.lock);
WARN(1, "cannot find packet in pending queue");
return NULL;
}
void wfx_pending_dump_old_frames(struct wfx_dev *wdev, unsigned int limit_ms)
{
ktime_t now = ktime_get();
struct wfx_tx_priv *tx_priv;
struct wfx_hif_req_tx *req;
struct sk_buff *skb;
bool first = true;
spin_lock_bh(&wdev->tx_pending.lock);
skb_queue_walk(&wdev->tx_pending, skb) {
tx_priv = wfx_skb_tx_priv(skb);
req = wfx_skb_txreq(skb);
if (ktime_after(now, ktime_add_ms(tx_priv->xmit_timestamp, limit_ms))) {
if (first) {
dev_info(wdev->dev, "frames stuck in firmware since %dms or more:\n",
limit_ms);
first = false;
}
dev_info(wdev->dev, " id %08x sent %lldms ago\n",
req->packet_id, ktime_ms_delta(now, tx_priv->xmit_timestamp));
}
}
spin_unlock_bh(&wdev->tx_pending.lock);
}
unsigned int wfx_pending_get_pkt_us_delay(struct wfx_dev *wdev, struct sk_buff *skb)
{
ktime_t now = ktime_get();
struct wfx_tx_priv *tx_priv = wfx_skb_tx_priv(skb);
return ktime_us_delta(now, tx_priv->xmit_timestamp);
}
bool wfx_tx_queues_has_cab(struct wfx_vif *wvif)
{
struct ieee80211_vif *vif = wvif_to_vif(wvif);
int i;
if (vif->type != NL80211_IFTYPE_AP)
return false;
for (i = 0; i < IEEE80211_NUM_ACS; ++i)
/* Note: since only AP can have mcast frames in queue and only one vif can be AP,
* all queued frames has same interface id
*/
if (!skb_queue_empty_lockless(&wvif->tx_queue[i].cab))
return true;
return false;
}
static int wfx_tx_queue_get_weight(struct wfx_queue *queue)
{
return atomic_read(&queue->pending_frames) * queue->priority;
}
static struct sk_buff *wfx_tx_queues_get_skb(struct wfx_dev *wdev)
{
struct wfx_queue *queues[IEEE80211_NUM_ACS * ARRAY_SIZE(wdev->vif)];
int i, j, num_queues = 0;
struct wfx_vif *wvif;
struct wfx_hif_msg *hif;
struct sk_buff *skb;
/* sort the queues */
wvif = NULL;
while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
for (i = 0; i < IEEE80211_NUM_ACS; i++) {
WARN_ON(num_queues >= ARRAY_SIZE(queues));
queues[num_queues] = &wvif->tx_queue[i];
for (j = num_queues; j > 0; j--)
if (wfx_tx_queue_get_weight(queues[j]) <
wfx_tx_queue_get_weight(queues[j - 1]))
swap(queues[j - 1], queues[j]);
num_queues++;
}
}
wvif = NULL;
while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
for (i = 0; i < num_queues; i++) {
skb = skb_dequeue(&queues[i]->offchan);
if (!skb)
continue;
hif = (struct wfx_hif_msg *)skb->data;
/* Offchan frames are assigned to a special interface.
* The only interface allowed to send data during scan.
*/
WARN_ON(hif->interface != 2);
atomic_inc(&queues[i]->pending_frames);
trace_queues_stats(wdev, queues[i]);
return skb;
}
}
if (mutex_is_locked(&wdev->scan_lock))
return NULL;
wvif = NULL;
while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
if (!wvif->after_dtim_tx_allowed)
continue;
for (i = 0; i < num_queues; i++) {
skb = skb_dequeue(&queues[i]->cab);
if (!skb)
continue;
/* Note: since only AP can have mcast frames in queue and only one vif can
* be AP, all queued frames has same interface id
*/
hif = (struct wfx_hif_msg *)skb->data;
WARN_ON(hif->interface != wvif->id);
WARN_ON(queues[i] != &wvif->tx_queue[skb_get_queue_mapping(skb)]);
atomic_inc(&queues[i]->pending_frames);
trace_queues_stats(wdev, queues[i]);
return skb;
}
/* No more multicast to sent */
wvif->after_dtim_tx_allowed = false;
schedule_work(&wvif->update_tim_work);
}
for (i = 0; i < num_queues; i++) {
skb = skb_dequeue(&queues[i]->normal);
if (skb) {
atomic_inc(&queues[i]->pending_frames);
trace_queues_stats(wdev, queues[i]);
return skb;
}
}
return NULL;
}
struct wfx_hif_msg *wfx_tx_queues_get(struct wfx_dev *wdev)
{
struct wfx_tx_priv *tx_priv;
struct sk_buff *skb;
if (atomic_read(&wdev->tx_lock))
return NULL;
skb = wfx_tx_queues_get_skb(wdev);
if (!skb)
return NULL;
skb_queue_tail(&wdev->tx_pending, skb);
wake_up(&wdev->tx_dequeue);
tx_priv = wfx_skb_tx_priv(skb);
tx_priv->xmit_timestamp = ktime_get();
return (struct wfx_hif_msg *)skb->data;
}
@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Queue between the tx operation and the bh workqueue.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#ifndef WFX_QUEUE_H
#define WFX_QUEUE_H
#include <linux/skbuff.h>
#include <linux/atomic.h>
struct wfx_dev;
struct wfx_vif;
struct wfx_queue {
struct sk_buff_head normal;
struct sk_buff_head cab; /* Content After (DTIM) Beacon */
struct sk_buff_head offchan;
atomic_t pending_frames;
int priority;
};
void wfx_tx_lock(struct wfx_dev *wdev);
void wfx_tx_unlock(struct wfx_dev *wdev);
void wfx_tx_flush(struct wfx_dev *wdev);
void wfx_tx_lock_flush(struct wfx_dev *wdev);
void wfx_tx_queues_init(struct wfx_vif *wvif);
void wfx_tx_queues_check_empty(struct wfx_vif *wvif);
bool wfx_tx_queues_has_cab(struct wfx_vif *wvif);
void wfx_tx_queues_put(struct wfx_vif *wvif, struct sk_buff *skb);
struct wfx_hif_msg *wfx_tx_queues_get(struct wfx_dev *wdev);
bool wfx_tx_queue_empty(struct wfx_vif *wvif, struct wfx_queue *queue);
void wfx_tx_queue_drop(struct wfx_vif *wvif, struct wfx_queue *queue,
struct sk_buff_head *dropped);
struct sk_buff *wfx_pending_get(struct wfx_dev *wdev, u32 packet_id);
void wfx_pending_drop(struct wfx_dev *wdev, struct sk_buff_head *dropped);
unsigned int wfx_pending_get_pkt_us_delay(struct wfx_dev *wdev, struct sk_buff *skb);
void wfx_pending_dump_old_frames(struct wfx_dev *wdev, unsigned int limit_ms);
#endif
@@ -0,0 +1,209 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Scan related functions.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#include <net/mac80211.h>
#include "scan.h"
#include "wfx.h"
#include "sta.h"
#include "hif_tx_mib.h"
static void wfx_ieee80211_scan_completed_compat(struct ieee80211_hw *hw, bool aborted)
{
struct cfg80211_scan_info info = {
.aborted = aborted,
};
ieee80211_scan_completed(hw, &info);
}
static int update_probe_tmpl(struct wfx_vif *wvif, struct cfg80211_scan_request *req)
{
struct ieee80211_vif *vif = wvif_to_vif(wvif);
struct sk_buff *skb;
skb = ieee80211_probereq_get(wvif->wdev->hw, vif->addr, NULL, 0,
req->ie_len);
if (!skb)
return -ENOMEM;
skb_put_data(skb, req->ie, req->ie_len);
wfx_hif_set_template_frame(wvif, skb, HIF_TMPLT_PRBREQ, 0);
dev_kfree_skb(skb);
return 0;
}
static int send_scan_req(struct wfx_vif *wvif, struct cfg80211_scan_request *req, int start_idx)
{
struct ieee80211_vif *vif = wvif_to_vif(wvif);
struct ieee80211_channel *ch_start, *ch_cur;
int i, ret;
for (i = start_idx; i < req->n_channels; i++) {
ch_start = req->channels[start_idx];
ch_cur = req->channels[i];
WARN(ch_cur->band != NL80211_BAND_2GHZ, "band not supported");
if (ch_cur->max_power != ch_start->max_power)
break;
if ((ch_cur->flags ^ ch_start->flags) & IEEE80211_CHAN_NO_IR)
break;
}
wfx_tx_lock_flush(wvif->wdev);
wvif->scan_abort = false;
reinit_completion(&wvif->scan_complete);
ret = wfx_hif_scan(wvif, req, start_idx, i - start_idx);
if (ret) {
wfx_tx_unlock(wvif->wdev);
return -EIO;
}
ret = wait_for_completion_timeout(&wvif->scan_complete, 1 * HZ);
if (!ret) {
wfx_hif_stop_scan(wvif);
ret = wait_for_completion_timeout(&wvif->scan_complete, 1 * HZ);
dev_dbg(wvif->wdev->dev, "scan timeout (%d channels done)\n",
wvif->scan_nb_chan_done);
}
if (!ret) {
dev_err(wvif->wdev->dev, "scan didn't stop\n");
ret = -ETIMEDOUT;
} else if (wvif->scan_abort) {
dev_notice(wvif->wdev->dev, "scan abort\n");
ret = -ECONNABORTED;
} else if (wvif->scan_nb_chan_done > i - start_idx) {
ret = -EIO;
} else {
ret = wvif->scan_nb_chan_done;
}
if (req->channels[start_idx]->max_power != vif->bss_conf.txpower)
wfx_hif_set_output_power(wvif, vif->bss_conf.txpower);
wfx_tx_unlock(wvif->wdev);
return ret;
}
/* It is not really necessary to run scan request asynchronously. However,
* there is a bug in "iw scan" when ieee80211_scan_completed() is called before
* wfx_hw_scan() return
*/
void wfx_hw_scan_work(struct work_struct *work)
{
struct wfx_vif *wvif = container_of(work, struct wfx_vif, scan_work);
struct ieee80211_scan_request *hw_req = wvif->scan_req;
int chan_cur, ret, err;
mutex_lock(&wvif->wdev->conf_mutex);
mutex_lock(&wvif->wdev->scan_lock);
if (wvif->join_in_progress) {
dev_info(wvif->wdev->dev, "abort in-progress REQ_JOIN");
wfx_reset(wvif);
}
update_probe_tmpl(wvif, &hw_req->req);
chan_cur = 0;
err = 0;
do {
ret = send_scan_req(wvif, &hw_req->req, chan_cur);
if (ret > 0) {
chan_cur += ret;
err = 0;
}
if (!ret)
err++;
if (err > 2) {
dev_err(wvif->wdev->dev, "scan has not been able to start\n");
ret = -ETIMEDOUT;
}
} while (ret >= 0 && chan_cur < hw_req->req.n_channels);
mutex_unlock(&wvif->wdev->scan_lock);
mutex_unlock(&wvif->wdev->conf_mutex);
wfx_ieee80211_scan_completed_compat(wvif->wdev->hw, ret < 0);
}
int wfx_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_scan_request *hw_req)
{
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
WARN_ON(hw_req->req.n_channels > HIF_API_MAX_NB_CHANNELS);
wvif->scan_req = hw_req;
schedule_work(&wvif->scan_work);
return 0;
}
void wfx_cancel_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
{
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
wvif->scan_abort = true;
wfx_hif_stop_scan(wvif);
}
void wfx_scan_complete(struct wfx_vif *wvif, int nb_chan_done)
{
wvif->scan_nb_chan_done = nb_chan_done;
complete(&wvif->scan_complete);
}
void wfx_remain_on_channel_work(struct work_struct *work)
{
struct wfx_vif *wvif = container_of(work, struct wfx_vif, remain_on_channel_work);
struct ieee80211_channel *chan = wvif->remain_on_channel_chan;
int duration = wvif->remain_on_channel_duration;
int ret;
/* Hijack scan request to implement Remain-On-Channel */
mutex_lock(&wvif->wdev->conf_mutex);
mutex_lock(&wvif->wdev->scan_lock);
if (wvif->join_in_progress) {
dev_info(wvif->wdev->dev, "abort in-progress REQ_JOIN");
wfx_reset(wvif);
}
wfx_tx_flush(wvif->wdev);
reinit_completion(&wvif->scan_complete);
ret = wfx_hif_scan_uniq(wvif, chan, duration);
if (ret)
goto end;
ieee80211_ready_on_channel(wvif->wdev->hw);
ret = wait_for_completion_timeout(&wvif->scan_complete,
msecs_to_jiffies(duration * 120 / 100));
if (!ret) {
wfx_hif_stop_scan(wvif);
ret = wait_for_completion_timeout(&wvif->scan_complete, 1 * HZ);
dev_dbg(wvif->wdev->dev, "roc timeout\n");
}
if (!ret)
dev_err(wvif->wdev->dev, "roc didn't stop\n");
ieee80211_remain_on_channel_expired(wvif->wdev->hw);
end:
mutex_unlock(&wvif->wdev->scan_lock);
mutex_unlock(&wvif->wdev->conf_mutex);
wfx_bh_request_tx(wvif->wdev);
}
int wfx_remain_on_channel(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_channel *chan, int duration,
enum ieee80211_roc_type type)
{
struct wfx_dev *wdev = hw->priv;
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
if (wfx_api_older_than(wdev, 3, 10))
return -EOPNOTSUPP;
wvif->remain_on_channel_duration = duration;
wvif->remain_on_channel_chan = chan;
schedule_work(&wvif->remain_on_channel_work);
return 0;
}
int wfx_cancel_remain_on_channel(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
{
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
wfx_hif_stop_scan(wvif);
flush_work(&wvif->remain_on_channel_work);
return 0;
}
@@ -0,0 +1,28 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Scan related functions.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#ifndef WFX_SCAN_H
#define WFX_SCAN_H
#include <net/mac80211.h>
struct wfx_dev;
struct wfx_vif;
void wfx_hw_scan_work(struct work_struct *work);
int wfx_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_scan_request *req);
void wfx_cancel_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
void wfx_scan_complete(struct wfx_vif *wvif, int nb_chan_done);
void wfx_remain_on_channel_work(struct work_struct *work);
int wfx_remain_on_channel(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_channel *chan, int duration,
enum ieee80211_roc_type type);
int wfx_cancel_remain_on_channel(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
#endif
@@ -0,0 +1,841 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Implementation of mac80211 API.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#include <linux/etherdevice.h>
#include <net/mac80211.h>
#include "sta.h"
#include "wfx.h"
#include "bus.h"
#include "fwio.h"
#include "bh.h"
#include "key.h"
#include "scan.h"
#include "debug.h"
#include "hif_tx.h"
#include "hif_tx_mib.h"
#define HIF_MAX_ARP_IP_ADDRTABLE_ENTRIES 2
void wfx_cooling_timeout_work(struct work_struct *work)
{
struct wfx_dev *wdev = container_of(to_delayed_work(work), struct wfx_dev,
cooling_timeout_work);
wdev->chip_frozen = true;
wfx_tx_unlock(wdev);
}
void wfx_suspend_hot_dev(struct wfx_dev *wdev, enum sta_notify_cmd cmd)
{
if (cmd == STA_NOTIFY_AWAKE) {
/* Device recover normal temperature */
if (cancel_delayed_work(&wdev->cooling_timeout_work))
wfx_tx_unlock(wdev);
} else {
/* Device is too hot */
schedule_delayed_work(&wdev->cooling_timeout_work, 10 * HZ);
wfx_tx_lock(wdev);
}
}
static void wfx_filter_beacon(struct wfx_vif *wvif, bool filter_beacon)
{
static const struct wfx_hif_ie_table_entry filter_ies[] = {
{
.ie_id = WLAN_EID_VENDOR_SPECIFIC,
.has_changed = 1,
.no_longer = 1,
.has_appeared = 1,
.oui = { 0x50, 0x6F, 0x9A },
}, {
.ie_id = WLAN_EID_HT_OPERATION,
.has_changed = 1,
.no_longer = 1,
.has_appeared = 1,
}, {
.ie_id = WLAN_EID_ERP_INFO,
.has_changed = 1,
.no_longer = 1,
.has_appeared = 1,
}, {
.ie_id = WLAN_EID_CHANNEL_SWITCH,
.has_changed = 1,
.no_longer = 1,
.has_appeared = 1,
}
};
if (!filter_beacon) {
wfx_hif_beacon_filter_control(wvif, 0, 1);
} else {
wfx_hif_set_beacon_filter_table(wvif, ARRAY_SIZE(filter_ies), filter_ies);
wfx_hif_beacon_filter_control(wvif, HIF_BEACON_FILTER_ENABLE, 0);
}
}
void wfx_configure_filter(struct ieee80211_hw *hw, unsigned int changed_flags,
unsigned int *total_flags, u64 unused)
{
bool filter_bssid, filter_prbreq, filter_beacon;
struct ieee80211_vif *vif = NULL;
struct wfx_dev *wdev = hw->priv;
struct wfx_vif *wvif = NULL;
/* Notes:
* - Probe responses (FIF_BCN_PRBRESP_PROMISC) are never filtered
* - PS-Poll (FIF_PSPOLL) are never filtered
* - RTS, CTS and Ack (FIF_CONTROL) are always filtered
* - Broken frames (FIF_FCSFAIL and FIF_PLCPFAIL) are always filtered
* - Firmware does (yet) allow to forward unicast traffic sent to other stations (aka.
* promiscuous mode)
*/
*total_flags &= FIF_BCN_PRBRESP_PROMISC | FIF_ALLMULTI | FIF_OTHER_BSS |
FIF_PROBE_REQ | FIF_PSPOLL;
/* Filters are ignored during the scan. No frames are filtered. */
if (mutex_is_locked(&wdev->scan_lock))
return;
mutex_lock(&wdev->conf_mutex);
while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
/* Note: FIF_BCN_PRBRESP_PROMISC covers probe response and
* beacons from other BSS
*/
if (*total_flags & FIF_BCN_PRBRESP_PROMISC)
filter_beacon = false;
else
filter_beacon = true;
wfx_filter_beacon(wvif, filter_beacon);
if (*total_flags & FIF_OTHER_BSS)
filter_bssid = false;
else
filter_bssid = true;
vif = wvif_to_vif(wvif);
/* In AP mode, chip can reply to probe request itself */
if (*total_flags & FIF_PROBE_REQ && vif->type == NL80211_IFTYPE_AP) {
dev_dbg(wdev->dev, "do not forward probe request in AP mode\n");
*total_flags &= ~FIF_PROBE_REQ;
}
if (*total_flags & FIF_PROBE_REQ)
filter_prbreq = false;
else
filter_prbreq = true;
wfx_hif_set_rx_filter(wvif, filter_bssid, filter_prbreq);
}
mutex_unlock(&wdev->conf_mutex);
}
static int wfx_get_ps_timeout(struct wfx_vif *wvif, bool *enable_ps)
{
struct ieee80211_channel *chan0 = NULL, *chan1 = NULL;
struct ieee80211_conf *conf = &wvif->wdev->hw->conf;
struct ieee80211_vif *vif = wvif_to_vif(wvif);
WARN(!vif->cfg.assoc && enable_ps,
"enable_ps is reliable only if associated");
if (wdev_to_wvif(wvif->wdev, 0)) {
struct wfx_vif *wvif_ch0 = wdev_to_wvif(wvif->wdev, 0);
struct ieee80211_vif *vif_ch0 = wvif_to_vif(wvif_ch0);
chan0 = vif_ch0->bss_conf.chanreq.oper.chan;
}
if (wdev_to_wvif(wvif->wdev, 1)) {
struct wfx_vif *wvif_ch1 = wdev_to_wvif(wvif->wdev, 1);
struct ieee80211_vif *vif_ch1 = wvif_to_vif(wvif_ch1);
chan1 = vif_ch1->bss_conf.chanreq.oper.chan;
}
if (chan0 && chan1 && vif->type != NL80211_IFTYPE_AP) {
if (chan0->hw_value == chan1->hw_value) {
/* It is useless to enable PS if channels are the same. */
if (enable_ps)
*enable_ps = false;
if (vif->cfg.assoc && vif->cfg.ps)
dev_info(wvif->wdev->dev, "ignoring requested PS mode");
return -1;
}
/* It is necessary to enable PS if channels are different. */
if (enable_ps)
*enable_ps = true;
if (wfx_api_older_than(wvif->wdev, 3, 2))
return 0;
else
return 30;
}
if (enable_ps)
*enable_ps = vif->cfg.ps;
if (vif->cfg.assoc && vif->cfg.ps)
return conf->dynamic_ps_timeout;
else
return -1;
}
int wfx_update_pm(struct wfx_vif *wvif)
{
struct ieee80211_vif *vif = wvif_to_vif(wvif);
int ps_timeout;
bool ps;
if (!vif->cfg.assoc)
return 0;
ps_timeout = wfx_get_ps_timeout(wvif, &ps);
if (!ps)
ps_timeout = 0;
WARN_ON(ps_timeout < 0);
if (wvif->uapsd_mask)
ps_timeout = 0;
if (!wait_for_completion_timeout(&wvif->set_pm_mode_complete, TU_TO_JIFFIES(512)))
dev_warn(wvif->wdev->dev, "timeout while waiting of set_pm_mode_complete\n");
return wfx_hif_set_pm(wvif, ps, ps_timeout);
}
int wfx_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
unsigned int link_id, u16 queue,
const struct ieee80211_tx_queue_params *params)
{
struct wfx_dev *wdev = hw->priv;
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
int old_uapsd = wvif->uapsd_mask;
WARN_ON(queue >= hw->queues);
mutex_lock(&wdev->conf_mutex);
assign_bit(queue, &wvif->uapsd_mask, params->uapsd);
wfx_hif_set_edca_queue_params(wvif, queue, params);
if (vif->type == NL80211_IFTYPE_STATION &&
old_uapsd != wvif->uapsd_mask) {
wfx_hif_set_uapsd_info(wvif, wvif->uapsd_mask);
wfx_update_pm(wvif);
}
mutex_unlock(&wdev->conf_mutex);
return 0;
}
int wfx_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, u32 value)
{
struct wfx_dev *wdev = hw->priv;
struct wfx_vif *wvif = NULL;
while ((wvif = wvif_iterate(wdev, wvif)) != NULL)
wfx_hif_rts_threshold(wvif, value);
return 0;
}
void wfx_event_report_rssi(struct wfx_vif *wvif, u8 raw_rcpi_rssi)
{
/* RSSI: signed Q8.0, RCPI: unsigned Q7.1
* RSSI = RCPI / 2 - 110
*/
struct ieee80211_vif *vif = wvif_to_vif(wvif);
int rcpi_rssi;
int cqm_evt;
rcpi_rssi = raw_rcpi_rssi / 2 - 110;
if (rcpi_rssi <= vif->bss_conf.cqm_rssi_thold)
cqm_evt = NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW;
else
cqm_evt = NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH;
ieee80211_cqm_rssi_notify(vif, cqm_evt, rcpi_rssi, GFP_KERNEL);
}
static void wfx_beacon_loss_work(struct work_struct *work)
{
struct wfx_vif *wvif = container_of(to_delayed_work(work), struct wfx_vif,
beacon_loss_work);
struct ieee80211_vif *vif = wvif_to_vif(wvif);
struct ieee80211_bss_conf *bss_conf = &vif->bss_conf;
ieee80211_beacon_loss(vif);
schedule_delayed_work(to_delayed_work(work), msecs_to_jiffies(bss_conf->beacon_int));
}
void wfx_set_default_unicast_key(struct ieee80211_hw *hw, struct ieee80211_vif *vif, int idx)
{
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
wfx_hif_wep_default_key_id(wvif, idx);
}
void wfx_reset(struct wfx_vif *wvif)
{
struct wfx_dev *wdev = wvif->wdev;
wfx_tx_lock_flush(wdev);
wfx_hif_reset(wvif, false);
wfx_tx_policy_init(wvif);
if (wvif_count(wdev) <= 1)
wfx_hif_set_block_ack_policy(wvif, 0xFF, 0xFF);
wfx_tx_unlock(wdev);
wvif->join_in_progress = false;
cancel_delayed_work_sync(&wvif->beacon_loss_work);
wvif = NULL;
while ((wvif = wvif_iterate(wdev, wvif)) != NULL)
wfx_update_pm(wvif);
}
int wfx_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta)
{
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
struct wfx_sta_priv *sta_priv = (struct wfx_sta_priv *)&sta->drv_priv;
sta_priv->vif_id = wvif->id;
if (vif->type == NL80211_IFTYPE_STATION)
wfx_hif_set_mfp(wvif, sta->mfp, sta->mfp);
/* In station mode, the firmware interprets new link-id as a TDLS peer */
if (vif->type == NL80211_IFTYPE_STATION && !sta->tdls)
return 0;
sta_priv->link_id = ffz(wvif->link_id_map);
wvif->link_id_map |= BIT(sta_priv->link_id);
WARN_ON(!sta_priv->link_id);
WARN_ON(sta_priv->link_id >= HIF_LINK_ID_MAX);
wfx_hif_map_link(wvif, false, sta->addr, sta_priv->link_id, sta->mfp);
return 0;
}
int wfx_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta)
{
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
struct wfx_sta_priv *sta_priv = (struct wfx_sta_priv *)&sta->drv_priv;
/* See note in wfx_sta_add() */
if (!sta_priv->link_id)
return 0;
/* FIXME add a mutex? */
wfx_hif_map_link(wvif, true, sta->addr, sta_priv->link_id, false);
wvif->link_id_map &= ~BIT(sta_priv->link_id);
return 0;
}
static int wfx_upload_ap_templates(struct wfx_vif *wvif)
{
struct ieee80211_vif *vif = wvif_to_vif(wvif);
struct sk_buff *skb;
skb = ieee80211_beacon_get(wvif->wdev->hw, vif, 0);
if (!skb)
return -ENOMEM;
wfx_hif_set_template_frame(wvif, skb, HIF_TMPLT_BCN, API_RATE_INDEX_B_1MBPS);
dev_kfree_skb(skb);
skb = ieee80211_proberesp_get(wvif->wdev->hw, vif);
if (!skb)
return -ENOMEM;
wfx_hif_set_template_frame(wvif, skb, HIF_TMPLT_PRBRES, API_RATE_INDEX_B_1MBPS);
dev_kfree_skb(skb);
return 0;
}
static int wfx_set_mfp_ap(struct wfx_vif *wvif)
{
struct ieee80211_vif *vif = wvif_to_vif(wvif);
struct sk_buff *skb = ieee80211_beacon_get(wvif->wdev->hw, vif, 0);
const int ieoffset = offsetof(struct ieee80211_mgmt, u.beacon.variable);
const int pairwise_cipher_suite_count_offset = 8 / sizeof(u16);
const int pairwise_cipher_suite_size = 4 / sizeof(u16);
const int akm_suite_size = 4 / sizeof(u16);
int ret = -EINVAL;
const u16 *ptr;
if (unlikely(!skb))
return -ENOMEM;
ptr = (u16 *)cfg80211_find_ie(WLAN_EID_RSN, skb->data + ieoffset,
skb->len - ieoffset);
if (!ptr) {
/* No RSN IE is fine in open networks */
ret = 0;
goto free_skb;
}
ptr += pairwise_cipher_suite_count_offset;
if (WARN_ON(ptr > (u16 *)skb_tail_pointer(skb)))
goto free_skb;
ptr += 1 + pairwise_cipher_suite_size * *ptr;
if (WARN_ON(ptr > (u16 *)skb_tail_pointer(skb)))
goto free_skb;
ptr += 1 + akm_suite_size * *ptr;
if (WARN_ON(ptr > (u16 *)skb_tail_pointer(skb)))
goto free_skb;
wfx_hif_set_mfp(wvif, *ptr & BIT(7), *ptr & BIT(6));
ret = 0;
free_skb:
dev_kfree_skb(skb);
return ret;
}
int wfx_start_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link_conf)
{
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
struct wfx_dev *wdev = wvif->wdev;
int ret;
wvif = NULL;
while ((wvif = wvif_iterate(wdev, wvif)) != NULL)
wfx_update_pm(wvif);
wvif = (struct wfx_vif *)vif->drv_priv;
wfx_upload_ap_templates(wvif);
ret = wfx_hif_start(wvif, &vif->bss_conf, wvif->channel);
if (ret > 0)
return -EIO;
return wfx_set_mfp_ap(wvif);
}
void wfx_stop_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link_conf)
{
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
struct wfx_dev *wdev = wvif->wdev;
wvif = NULL;
while ((wvif = wvif_iterate(wdev, wvif)) != NULL)
wfx_update_pm(wvif);
wvif = (struct wfx_vif *)vif->drv_priv;
wfx_reset(wvif);
}
static void wfx_join(struct wfx_vif *wvif)
{
struct ieee80211_vif *vif = wvif_to_vif(wvif);
struct ieee80211_bss_conf *conf = &vif->bss_conf;
struct cfg80211_bss *bss = NULL;
u8 ssid[IEEE80211_MAX_SSID_LEN];
const u8 *ssid_ie = NULL;
int ssid_len = 0;
int ret;
wfx_tx_lock_flush(wvif->wdev);
bss = cfg80211_get_bss(wvif->wdev->hw->wiphy, wvif->channel, conf->bssid, NULL, 0,
IEEE80211_BSS_TYPE_ANY, IEEE80211_PRIVACY_ANY);
if (!bss && !vif->cfg.ibss_joined) {
wfx_tx_unlock(wvif->wdev);
return;
}
rcu_read_lock(); /* protect ssid_ie */
if (bss)
ssid_ie = ieee80211_bss_get_ie(bss, WLAN_EID_SSID);
if (ssid_ie) {
ssid_len = ssid_ie[1];
if (ssid_len > IEEE80211_MAX_SSID_LEN)
ssid_len = IEEE80211_MAX_SSID_LEN;
memcpy(ssid, &ssid_ie[2], ssid_len);
}
rcu_read_unlock();
cfg80211_put_bss(wvif->wdev->hw->wiphy, bss);
wvif->join_in_progress = true;
ret = wfx_hif_join(wvif, conf, wvif->channel, ssid, ssid_len);
if (ret) {
ieee80211_connection_loss(vif);
wfx_reset(wvif);
} else {
/* 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
*/
wfx_filter_beacon(wvif, false);
}
wfx_tx_unlock(wvif->wdev);
}
static void wfx_join_finalize(struct wfx_vif *wvif, struct ieee80211_bss_conf *info)
{
struct ieee80211_vif *vif = wvif_to_vif(wvif);
struct ieee80211_sta *sta = NULL;
int ampdu_density = 0;
bool greenfield = false;
rcu_read_lock(); /* protect sta */
if (info->bssid && !vif->cfg.ibss_joined)
sta = ieee80211_find_sta(vif, info->bssid);
if (sta && sta->deflink.ht_cap.ht_supported)
ampdu_density = sta->deflink.ht_cap.ampdu_density;
if (sta && sta->deflink.ht_cap.ht_supported &&
!(info->ht_operation_mode & IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT))
greenfield = !!(sta->deflink.ht_cap.cap & IEEE80211_HT_CAP_GRN_FLD);
rcu_read_unlock();
wvif->join_in_progress = false;
wfx_hif_set_association_mode(wvif, ampdu_density, greenfield, info->use_short_preamble);
wfx_hif_keep_alive_period(wvif, 0);
/* beacon_loss_count is defined to 7 in net/mac80211/mlme.c. Let's use the same value. */
wfx_hif_set_bss_params(wvif, vif->cfg.aid, 7);
wfx_hif_set_beacon_wakeup_period(wvif, 1, 1);
wfx_update_pm(wvif);
}
int wfx_join_ibss(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
{
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
wfx_upload_ap_templates(wvif);
wfx_join(wvif);
return 0;
}
void wfx_leave_ibss(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
{
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
wfx_reset(wvif);
}
static void wfx_enable_beacon(struct wfx_vif *wvif, bool enable)
{
/* Driver has Content After DTIM Beacon in queue. Driver is waiting for a signal from the
* firmware. Since we are going to stop to send beacons, this signal will never happens. See
* also wfx_suspend_resume_mc()
*/
if (!enable && wfx_tx_queues_has_cab(wvif)) {
wvif->after_dtim_tx_allowed = true;
wfx_bh_request_tx(wvif->wdev);
}
wfx_hif_beacon_transmit(wvif, enable);
}
void wfx_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_bss_conf *info, u64 changed)
{
struct wfx_dev *wdev = hw->priv;
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
int i;
mutex_lock(&wdev->conf_mutex);
if (changed & BSS_CHANGED_BASIC_RATES ||
changed & BSS_CHANGED_BEACON_INT ||
changed & BSS_CHANGED_BSSID) {
if (vif->type == NL80211_IFTYPE_STATION)
wfx_join(wvif);
}
if (changed & BSS_CHANGED_ASSOC) {
if (vif->cfg.assoc || vif->cfg.ibss_joined)
wfx_join_finalize(wvif, info);
else if (!vif->cfg.assoc && vif->type == NL80211_IFTYPE_STATION)
wfx_reset(wvif);
else
dev_warn(wdev->dev, "misunderstood change: ASSOC\n");
}
if (changed & BSS_CHANGED_BEACON_INFO) {
if (vif->type != NL80211_IFTYPE_STATION)
dev_warn(wdev->dev, "misunderstood change: BEACON_INFO\n");
wfx_hif_set_beacon_wakeup_period(wvif, info->dtim_period, info->dtim_period);
/* We temporary forwarded beacon for join process. It is now no more necessary. */
wfx_filter_beacon(wvif, true);
}
if (changed & BSS_CHANGED_ARP_FILTER) {
for (i = 0; i < HIF_MAX_ARP_IP_ADDRTABLE_ENTRIES; i++) {
__be32 *arp_addr = &vif->cfg.arp_addr_list[i];
if (vif->cfg.arp_addr_cnt > HIF_MAX_ARP_IP_ADDRTABLE_ENTRIES)
arp_addr = NULL;
if (i >= vif->cfg.arp_addr_cnt)
arp_addr = NULL;
wfx_hif_set_arp_ipv4_filter(wvif, i, arp_addr);
}
}
if (changed & BSS_CHANGED_AP_PROBE_RESP || changed & BSS_CHANGED_BEACON)
wfx_upload_ap_templates(wvif);
if (changed & BSS_CHANGED_BEACON_ENABLED)
wfx_enable_beacon(wvif, info->enable_beacon);
if (changed & BSS_CHANGED_KEEP_ALIVE)
wfx_hif_keep_alive_period(wvif,
info->max_idle_period * USEC_PER_TU / USEC_PER_MSEC);
if (changed & BSS_CHANGED_ERP_CTS_PROT)
wfx_hif_erp_use_protection(wvif, info->use_cts_prot);
if (changed & BSS_CHANGED_ERP_SLOT)
wfx_hif_slot_time(wvif, info->use_short_slot ? 9 : 20);
if (changed & BSS_CHANGED_CQM)
wfx_hif_set_rcpi_rssi_threshold(wvif, info->cqm_rssi_thold, info->cqm_rssi_hyst);
if (changed & BSS_CHANGED_TXPOWER)
wfx_hif_set_output_power(wvif, info->txpower);
if (changed & BSS_CHANGED_PS)
wfx_update_pm(wvif);
mutex_unlock(&wdev->conf_mutex);
}
static int wfx_update_tim(struct wfx_vif *wvif)
{
struct ieee80211_vif *vif = wvif_to_vif(wvif);
struct sk_buff *skb;
u16 tim_offset, tim_length;
u8 *tim_ptr;
skb = ieee80211_beacon_get_tim(wvif->wdev->hw, vif, &tim_offset,
&tim_length, 0);
if (!skb)
return -ENOENT;
tim_ptr = skb->data + tim_offset;
if (tim_offset && tim_length >= 6) {
/* Firmware handles DTIM counter internally */
tim_ptr[2] = 0;
/* Set/reset aid0 bit */
if (wfx_tx_queues_has_cab(wvif))
tim_ptr[4] |= 1;
else
tim_ptr[4] &= ~1;
}
wfx_hif_update_ie_beacon(wvif, tim_ptr, tim_length);
dev_kfree_skb(skb);
return 0;
}
static void wfx_update_tim_work(struct work_struct *work)
{
struct wfx_vif *wvif = container_of(work, struct wfx_vif, update_tim_work);
wfx_update_tim(wvif);
}
int wfx_set_tim(struct ieee80211_hw *hw, struct ieee80211_sta *sta, bool set)
{
struct wfx_dev *wdev = hw->priv;
struct wfx_sta_priv *sta_dev = (struct wfx_sta_priv *)&sta->drv_priv;
struct wfx_vif *wvif = wdev_to_wvif(wdev, sta_dev->vif_id);
if (!wvif) {
dev_warn(wdev->dev, "%s: received event for non-existent vif\n", __func__);
return -EIO;
}
schedule_work(&wvif->update_tim_work);
return 0;
}
void wfx_suspend_resume_mc(struct wfx_vif *wvif, enum sta_notify_cmd notify_cmd)
{
if (notify_cmd != STA_NOTIFY_AWAKE)
return;
/* Device won't be able to honor CAB if a scan is in progress on any interface. Prefer to
* skip this DTIM and wait for the next one.
*/
if (mutex_is_locked(&wvif->wdev->scan_lock))
return;
if (!wfx_tx_queues_has_cab(wvif) || wvif->after_dtim_tx_allowed)
dev_warn(wvif->wdev->dev, "incorrect sequence (%d CAB in queue)",
wfx_tx_queues_has_cab(wvif));
wvif->after_dtim_tx_allowed = true;
wfx_bh_request_tx(wvif->wdev);
}
int wfx_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_ampdu_params *params)
{
/* Aggregation is implemented fully in firmware */
switch (params->action) {
case IEEE80211_AMPDU_RX_START:
case IEEE80211_AMPDU_RX_STOP:
/* Just acknowledge it to enable frame re-ordering */
return 0;
default:
/* Leave the firmware doing its business for tx aggregation */
return -EOPNOTSUPP;
}
}
int wfx_add_chanctx(struct ieee80211_hw *hw, struct ieee80211_chanctx_conf *conf)
{
return 0;
}
void wfx_remove_chanctx(struct ieee80211_hw *hw, struct ieee80211_chanctx_conf *conf)
{
}
void wfx_change_chanctx(struct ieee80211_hw *hw, struct ieee80211_chanctx_conf *conf, u32 changed)
{
}
int wfx_assign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link_conf,
struct ieee80211_chanctx_conf *conf)
{
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
struct ieee80211_channel *ch = conf->def.chan;
WARN(wvif->channel, "channel overwrite");
wvif->channel = ch;
return 0;
}
void wfx_unassign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link_conf,
struct ieee80211_chanctx_conf *conf)
{
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
struct ieee80211_channel *ch = conf->def.chan;
WARN(wvif->channel != ch, "channel mismatch");
wvif->channel = NULL;
}
int wfx_config(struct ieee80211_hw *hw, int radio_idx, u32 changed)
{
return 0;
}
int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
{
int i;
struct wfx_dev *wdev = hw->priv;
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER |
IEEE80211_VIF_SUPPORTS_UAPSD |
IEEE80211_VIF_SUPPORTS_CQM_RSSI;
mutex_lock(&wdev->conf_mutex);
switch (vif->type) {
case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_ADHOC:
case NL80211_IFTYPE_AP:
break;
default:
mutex_unlock(&wdev->conf_mutex);
return -EOPNOTSUPP;
}
wvif->wdev = wdev;
wvif->link_id_map = 1; /* link-id 0 is reserved for multicast */
INIT_WORK(&wvif->update_tim_work, wfx_update_tim_work);
INIT_DELAYED_WORK(&wvif->beacon_loss_work, wfx_beacon_loss_work);
init_completion(&wvif->set_pm_mode_complete);
complete(&wvif->set_pm_mode_complete);
INIT_WORK(&wvif->tx_policy_upload_work, wfx_tx_policy_upload_work);
init_completion(&wvif->scan_complete);
INIT_WORK(&wvif->scan_work, wfx_hw_scan_work);
INIT_WORK(&wvif->remain_on_channel_work, wfx_remain_on_channel_work);
wfx_tx_queues_init(wvif);
wfx_tx_policy_init(wvif);
for (i = 0; i < ARRAY_SIZE(wdev->vif); i++) {
if (!wdev->vif[i]) {
wdev->vif[i] = vif;
wvif->id = i;
break;
}
}
WARN(i == ARRAY_SIZE(wdev->vif), "try to instantiate more vif than supported");
wfx_hif_set_macaddr(wvif, vif->addr);
mutex_unlock(&wdev->conf_mutex);
wvif = NULL;
while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
/* Combo mode does not support Block Acks. We can re-enable them */
if (wvif_count(wdev) == 1)
wfx_hif_set_block_ack_policy(wvif, 0xFF, 0xFF);
else
wfx_hif_set_block_ack_policy(wvif, 0x00, 0x00);
}
return 0;
}
void wfx_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
{
struct wfx_dev *wdev = hw->priv;
struct wfx_vif *wvif = (struct wfx_vif *)vif->drv_priv;
wait_for_completion_timeout(&wvif->set_pm_mode_complete, msecs_to_jiffies(300));
wfx_tx_queues_check_empty(wvif);
mutex_lock(&wdev->conf_mutex);
WARN(wvif->link_id_map != 1, "corrupted state");
wfx_hif_reset(wvif, false);
wfx_hif_set_macaddr(wvif, NULL);
wfx_tx_policy_init(wvif);
cancel_delayed_work_sync(&wvif->beacon_loss_work);
wdev->vif[wvif->id] = NULL;
mutex_unlock(&wdev->conf_mutex);
wvif = NULL;
while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
/* Combo mode does not support Block Acks. We can re-enable them */
if (wvif_count(wdev) == 1)
wfx_hif_set_block_ack_policy(wvif, 0xFF, 0xFF);
else
wfx_hif_set_block_ack_policy(wvif, 0x00, 0x00);
}
}
#ifdef CONFIG_PM
int wfx_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
{
/* FIXME: hardware also support WIPHY_WOWLAN_MAGIC_PKT and other filters */
if (!wowlan->any || !wowlan->disconnect)
return -EINVAL;
return 0;
}
int wfx_resume(struct ieee80211_hw *hw)
{
return 0;
}
void wfx_set_wakeup(struct ieee80211_hw *hw, bool enabled)
{
struct wfx_dev *wdev = hw->priv;
if (enabled)
dev_info(wdev->dev, "support for WoWLAN is experimental\n");
wdev->hwbus_ops->set_wakeup(wdev->hwbus_priv, enabled);
}
#endif
int wfx_start(struct ieee80211_hw *hw)
{
return 0;
}
void wfx_stop(struct ieee80211_hw *hw, bool suspend)
{
struct wfx_dev *wdev = hw->priv;
WARN_ON(!skb_queue_empty_lockless(&wdev->tx_pending));
}
@@ -0,0 +1,73 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Implementation of mac80211 API.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
*/
#ifndef WFX_STA_H
#define WFX_STA_H
#include <net/mac80211.h>
struct wfx_dev;
struct wfx_vif;
struct wfx_sta_priv {
int link_id;
int vif_id;
};
/* mac80211 interface */
int wfx_start(struct ieee80211_hw *hw);
void wfx_stop(struct ieee80211_hw *hw, bool suspend);
int wfx_config(struct ieee80211_hw *hw, int radio_idx, u32 changed);
int wfx_set_rts_threshold(struct ieee80211_hw *hw, int radio_idx, u32 value);
void wfx_set_default_unicast_key(struct ieee80211_hw *hw, struct ieee80211_vif *vif, int idx);
void wfx_configure_filter(struct ieee80211_hw *hw, unsigned int changed_flags,
unsigned int *total_flags, u64 unused);
int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
void wfx_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
int wfx_start_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link_conf);
void wfx_stop_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link_conf);
int wfx_join_ibss(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
void wfx_leave_ibss(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
int wfx_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
unsigned int link_id, u16 queue,
const struct ieee80211_tx_queue_params *params);
void wfx_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_bss_conf *info, u64 changed);
int wfx_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta);
int wfx_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta);
void wfx_sta_notify(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
enum sta_notify_cmd cmd, struct ieee80211_sta *sta);
int wfx_set_tim(struct ieee80211_hw *hw, struct ieee80211_sta *sta, bool set);
int wfx_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_ampdu_params *params);
int wfx_add_chanctx(struct ieee80211_hw *hw, struct ieee80211_chanctx_conf *conf);
void wfx_remove_chanctx(struct ieee80211_hw *hw, struct ieee80211_chanctx_conf *conf);
void wfx_change_chanctx(struct ieee80211_hw *hw, struct ieee80211_chanctx_conf *conf, u32 changed);
int wfx_assign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link_conf,
struct ieee80211_chanctx_conf *conf);
void wfx_unassign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link_conf,
struct ieee80211_chanctx_conf *conf);
int wfx_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan);
int wfx_resume(struct ieee80211_hw *hw);
void wfx_set_wakeup(struct ieee80211_hw *hw, bool enabled);
/* Hardware API Callbacks */
void wfx_cooling_timeout_work(struct work_struct *work);
void wfx_suspend_hot_dev(struct wfx_dev *wdev, enum sta_notify_cmd cmd);
void wfx_suspend_resume_mc(struct wfx_vif *wvif, enum sta_notify_cmd cmd);
void wfx_event_report_rssi(struct wfx_vif *wvif, u8 raw_rcpi_rssi);
int wfx_update_pm(struct wfx_vif *wvif);
/* Other Helpers */
void wfx_reset(struct wfx_vif *wvif);
#endif
@@ -0,0 +1,496 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Tracepoints definitions.
*
* Copyright (c) 2018-2020, Silicon Laboratories, Inc.
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM wfx
#if !defined(_WFX_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
#define _WFX_TRACE_H
#include <linux/tracepoint.h>
#include <net/mac80211.h>
#include "bus.h"
#include "hif_api_cmd.h"
#include "hif_api_mib.h"
/* The hell below need some explanations. For each symbolic number, we need to define it with
* TRACE_DEFINE_ENUM() and in a list for __print_symbolic.
*
* 1. Define a new macro that call TRACE_DEFINE_ENUM():
*
* #define xxx_name(sym) TRACE_DEFINE_ENUM(sym);
*
* 2. Define list of all symbols:
*
* #define list_names \
* ... \
* xxx_name(XXX) \
* ...
*
* 3. Instantiate that list_names:
*
* list_names
*
* 4. Redefine xxx_name() as an entry of array for __print_symbolic()
*
* #undef xxx_name
* #define xxx_name(msg) { msg, #msg },
*
* 5. list_name can now nearly be used with __print_symbolic() but, __print_symbolic() dislike
* last comma of list. So we define a new list with a dummy element:
*
* #define list_for_print_symbolic list_names { -1, NULL }
*/
#define _hif_msg_list \
hif_cnf_name(ADD_KEY) \
hif_cnf_name(BEACON_TRANSMIT) \
hif_cnf_name(EDCA_QUEUE_PARAMS) \
hif_cnf_name(JOIN) \
hif_cnf_name(MAP_LINK) \
hif_cnf_name(READ_MIB) \
hif_cnf_name(REMOVE_KEY) \
hif_cnf_name(RESET) \
hif_cnf_name(SET_BSS_PARAMS) \
hif_cnf_name(SET_PM_MODE) \
hif_cnf_name(START) \
hif_cnf_name(START_SCAN) \
hif_cnf_name(STOP_SCAN) \
hif_cnf_name(TX) \
hif_cnf_name(MULTI_TRANSMIT) \
hif_cnf_name(UPDATE_IE) \
hif_cnf_name(WRITE_MIB) \
hif_cnf_name(CONFIGURATION) \
hif_cnf_name(CONTROL_GPIO) \
hif_cnf_name(PREVENT_ROLLBACK) \
hif_cnf_name(SET_SL_MAC_KEY) \
hif_cnf_name(SL_CONFIGURE) \
hif_cnf_name(SL_EXCHANGE_PUB_KEYS) \
hif_cnf_name(SHUT_DOWN) \
hif_ind_name(EVENT) \
hif_ind_name(JOIN_COMPLETE) \
hif_ind_name(RX) \
hif_ind_name(SCAN_CMPL) \
hif_ind_name(SET_PM_MODE_CMPL) \
hif_ind_name(SUSPEND_RESUME_TX) \
hif_ind_name(SL_EXCHANGE_PUB_KEYS) \
hif_ind_name(ERROR) \
hif_ind_name(EXCEPTION) \
hif_ind_name(GENERIC) \
hif_ind_name(WAKEUP) \
hif_ind_name(STARTUP)
#define hif_msg_list_enum _hif_msg_list
#undef hif_cnf_name
#undef hif_ind_name
#define hif_cnf_name(msg) TRACE_DEFINE_ENUM(HIF_CNF_ID_##msg);
#define hif_ind_name(msg) TRACE_DEFINE_ENUM(HIF_IND_ID_##msg);
hif_msg_list_enum
#undef hif_cnf_name
#undef hif_ind_name
#define hif_cnf_name(msg) { HIF_CNF_ID_##msg, #msg },
#define hif_ind_name(msg) { HIF_IND_ID_##msg, #msg },
#define hif_msg_list hif_msg_list_enum { -1, NULL }
#define _hif_mib_list \
hif_mib_name(ARP_IP_ADDRESSES_TABLE) \
hif_mib_name(ARP_KEEP_ALIVE_PERIOD) \
hif_mib_name(BEACON_FILTER_ENABLE) \
hif_mib_name(BEACON_FILTER_TABLE) \
hif_mib_name(BEACON_STATS) \
hif_mib_name(BEACON_WAKEUP_PERIOD) \
hif_mib_name(BLOCK_ACK_POLICY) \
hif_mib_name(CCA_CONFIG) \
hif_mib_name(CONFIG_DATA_FILTER) \
hif_mib_name(COUNTERS_TABLE) \
hif_mib_name(CURRENT_TX_POWER_LEVEL) \
hif_mib_name(DOT11_MAC_ADDRESS) \
hif_mib_name(DOT11_MAX_RECEIVE_LIFETIME) \
hif_mib_name(DOT11_MAX_TRANSMIT_MSDU_LIFETIME) \
hif_mib_name(DOT11_RTS_THRESHOLD) \
hif_mib_name(DOT11_WEP_DEFAULT_KEY_ID) \
hif_mib_name(ETHERTYPE_DATAFRAME_CONDITION) \
hif_mib_name(EXTENDED_COUNTERS_TABLE) \
hif_mib_name(GL_BLOCK_ACK_INFO) \
hif_mib_name(GL_OPERATIONAL_POWER_MODE) \
hif_mib_name(GL_SET_MULTI_MSG) \
hif_mib_name(GRP_SEQ_COUNTER) \
hif_mib_name(INACTIVITY_TIMER) \
hif_mib_name(INTERFACE_PROTECTION) \
hif_mib_name(IPV4_ADDR_DATAFRAME_CONDITION) \
hif_mib_name(IPV6_ADDR_DATAFRAME_CONDITION) \
hif_mib_name(KEEP_ALIVE_PERIOD) \
hif_mib_name(MAC_ADDR_DATAFRAME_CONDITION) \
hif_mib_name(MAGIC_DATAFRAME_CONDITION) \
hif_mib_name(MAX_TX_POWER_LEVEL) \
hif_mib_name(NON_ERP_PROTECTION) \
hif_mib_name(NS_IP_ADDRESSES_TABLE) \
hif_mib_name(OVERRIDE_INTERNAL_TX_RATE) \
hif_mib_name(PORT_DATAFRAME_CONDITION) \
hif_mib_name(PROTECTED_MGMT_POLICY) \
hif_mib_name(RCPI_RSSI_THRESHOLD) \
hif_mib_name(RX_FILTER) \
hif_mib_name(SET_ASSOCIATION_MODE) \
hif_mib_name(SET_DATA_FILTERING) \
hif_mib_name(SET_HT_PROTECTION) \
hif_mib_name(SET_TX_RATE_RETRY_POLICY) \
hif_mib_name(SET_UAPSD_INFORMATION) \
hif_mib_name(SLOT_TIME) \
hif_mib_name(STATISTICS_TABLE) \
hif_mib_name(TEMPLATE_FRAME) \
hif_mib_name(TSF_COUNTER) \
hif_mib_name(UC_MC_BC_DATAFRAME_CONDITION)
#define hif_mib_list_enum _hif_mib_list
#undef hif_mib_name
#define hif_mib_name(mib) TRACE_DEFINE_ENUM(HIF_MIB_ID_##mib);
hif_mib_list_enum
#undef hif_mib_name
#define hif_mib_name(mib) { HIF_MIB_ID_##mib, #mib },
#define hif_mib_list hif_mib_list_enum { -1, NULL }
DECLARE_EVENT_CLASS(hif_data,
TP_PROTO(const struct wfx_hif_msg *hif, int tx_fill_level, bool is_recv),
TP_ARGS(hif, tx_fill_level, is_recv),
TP_STRUCT__entry(
__field(int, tx_fill_level)
__field(int, msg_id)
__field(const char *, msg_type)
__field(int, msg_len)
__field(int, buf_len)
__field(int, if_id)
__field(int, mib)
__array(u8, buf, 128)
),
TP_fast_assign(
int header_len;
__entry->tx_fill_level = tx_fill_level;
__entry->msg_len = le16_to_cpu(hif->len);
__entry->msg_id = hif->id;
__entry->if_id = hif->interface;
if (is_recv)
__entry->msg_type = __entry->msg_id & 0x80 ? "IND" : "CNF";
else
__entry->msg_type = "REQ";
if (!is_recv &&
(__entry->msg_id == HIF_REQ_ID_READ_MIB ||
__entry->msg_id == HIF_REQ_ID_WRITE_MIB)) {
__entry->mib = le16_to_cpup((__le16 *)hif->body);
header_len = 4;
} else {
__entry->mib = -1;
header_len = 0;
}
__entry->buf_len = min_t(int, __entry->msg_len, sizeof(__entry->buf))
- sizeof(struct wfx_hif_msg) - header_len;
memcpy(__entry->buf, hif->body + header_len, __entry->buf_len);
),
TP_printk("%d:%d:%s_%s%s%s: %s%s (%d bytes)",
__entry->tx_fill_level,
__entry->if_id,
__entry->msg_type,
__print_symbolic(__entry->msg_id, hif_msg_list),
__entry->mib != -1 ? "/" : "",
__entry->mib != -1 ? __print_symbolic(__entry->mib, hif_mib_list) : "",
__print_hex(__entry->buf, __entry->buf_len),
__entry->msg_len > sizeof(__entry->buf) ? " ..." : "",
__entry->msg_len
)
);
DEFINE_EVENT(hif_data, hif_send,
TP_PROTO(const struct wfx_hif_msg *hif, int tx_fill_level, bool is_recv),
TP_ARGS(hif, tx_fill_level, is_recv));
#define _trace_hif_send(hif, tx_fill_level)\
trace_hif_send(hif, tx_fill_level, false)
DEFINE_EVENT(hif_data, hif_recv,
TP_PROTO(const struct wfx_hif_msg *hif, int tx_fill_level, bool is_recv),
TP_ARGS(hif, tx_fill_level, is_recv));
#define _trace_hif_recv(hif, tx_fill_level)\
trace_hif_recv(hif, tx_fill_level, true)
#define wfx_reg_list_enum \
wfx_reg_name(WFX_REG_CONFIG, "CONFIG") \
wfx_reg_name(WFX_REG_CONTROL, "CONTROL") \
wfx_reg_name(WFX_REG_IN_OUT_QUEUE, "QUEUE") \
wfx_reg_name(WFX_REG_AHB_DPORT, "AHB") \
wfx_reg_name(WFX_REG_BASE_ADDR, "BASE_ADDR") \
wfx_reg_name(WFX_REG_SRAM_DPORT, "SRAM") \
wfx_reg_name(WFX_REG_SET_GEN_R_W, "SET_GEN_R_W") \
wfx_reg_name(WFX_REG_FRAME_OUT, "FRAME_OUT")
#undef wfx_reg_name
#define wfx_reg_name(sym, name) TRACE_DEFINE_ENUM(sym);
wfx_reg_list_enum
#undef wfx_reg_name
#define wfx_reg_name(sym, name) { sym, name },
#define wfx_reg_list wfx_reg_list_enum { -1, NULL }
DECLARE_EVENT_CLASS(io_data,
TP_PROTO(int reg, int addr, const void *io_buf, size_t len),
TP_ARGS(reg, addr, io_buf, len),
TP_STRUCT__entry(
__field(int, reg)
__field(int, addr)
__field(int, msg_len)
__field(int, buf_len)
__array(u8, buf, 32)
__array(u8, addr_str, 10)
),
TP_fast_assign(
__entry->reg = reg;
__entry->addr = addr;
__entry->msg_len = len;
__entry->buf_len = min_t(int, sizeof(__entry->buf), __entry->msg_len);
memcpy(__entry->buf, io_buf, __entry->buf_len);
if (addr >= 0)
snprintf(__entry->addr_str, 10, "/%08x", addr);
else
__entry->addr_str[0] = 0;
),
TP_printk("%s%s: %s%s (%d bytes)",
__print_symbolic(__entry->reg, wfx_reg_list),
__entry->addr_str,
__print_hex(__entry->buf, __entry->buf_len),
__entry->msg_len > sizeof(__entry->buf) ? " ..." : "",
__entry->msg_len
)
);
DEFINE_EVENT(io_data, io_write,
TP_PROTO(int reg, int addr, const void *io_buf, size_t len),
TP_ARGS(reg, addr, io_buf, len));
#define _trace_io_ind_write(reg, addr, io_buf, len)\
trace_io_write(reg, addr, io_buf, len)
#define _trace_io_write(reg, io_buf, len) trace_io_write(reg, -1, io_buf, len)
DEFINE_EVENT(io_data, io_read,
TP_PROTO(int reg, int addr, const void *io_buf, size_t len),
TP_ARGS(reg, addr, io_buf, len));
#define _trace_io_ind_read(reg, addr, io_buf, len)\
trace_io_read(reg, addr, io_buf, len)
#define _trace_io_read(reg, io_buf, len) trace_io_read(reg, -1, io_buf, len)
DECLARE_EVENT_CLASS(io_data32,
TP_PROTO(int reg, int addr, u32 val),
TP_ARGS(reg, addr, val),
TP_STRUCT__entry(
__field(int, reg)
__field(int, addr)
__field(int, val)
__array(u8, addr_str, 10)
),
TP_fast_assign(
__entry->reg = reg;
__entry->addr = addr;
__entry->val = val;
if (addr >= 0)
snprintf(__entry->addr_str, 10, "/%08x", addr);
else
__entry->addr_str[0] = 0;
),
TP_printk("%s%s: %08x",
__print_symbolic(__entry->reg, wfx_reg_list),
__entry->addr_str,
__entry->val
)
);
DEFINE_EVENT(io_data32, io_write32,
TP_PROTO(int reg, int addr, u32 val),
TP_ARGS(reg, addr, val));
#define _trace_io_ind_write32(reg, addr, val) trace_io_write32(reg, addr, val)
#define _trace_io_write32(reg, val) trace_io_write32(reg, -1, val)
DEFINE_EVENT(io_data32, io_read32,
TP_PROTO(int reg, int addr, u32 val),
TP_ARGS(reg, addr, val));
#define _trace_io_ind_read32(reg, addr, val) trace_io_read32(reg, addr, val)
#define _trace_io_read32(reg, val) trace_io_read32(reg, -1, val)
DECLARE_EVENT_CLASS(piggyback,
TP_PROTO(u32 val, bool ignored),
TP_ARGS(val, ignored),
TP_STRUCT__entry(
__field(int, val)
__field(bool, ignored)
),
TP_fast_assign(
__entry->val = val;
__entry->ignored = ignored;
),
TP_printk("CONTROL: %08x%s",
__entry->val,
__entry->ignored ? " (ignored)" : ""
)
);
DEFINE_EVENT(piggyback, piggyback,
TP_PROTO(u32 val, bool ignored),
TP_ARGS(val, ignored));
#define _trace_piggyback(val, ignored) trace_piggyback(val, ignored)
TRACE_EVENT(bh_stats,
TP_PROTO(int ind, int req, int cnf, int busy, bool release),
TP_ARGS(ind, req, cnf, busy, release),
TP_STRUCT__entry(
__field(int, ind)
__field(int, req)
__field(int, cnf)
__field(int, busy)
__field(bool, release)
),
TP_fast_assign(
__entry->ind = ind;
__entry->req = req;
__entry->cnf = cnf;
__entry->busy = busy;
__entry->release = release;
),
TP_printk("IND/REQ/CNF:%3d/%3d/%3d, REQ in progress:%3d, WUP: %s",
__entry->ind,
__entry->req,
__entry->cnf,
__entry->busy,
__entry->release ? "release" : "keep"
)
);
#define _trace_bh_stats(ind, req, cnf, busy, release)\
trace_bh_stats(ind, req, cnf, busy, release)
TRACE_EVENT(tx_stats,
TP_PROTO(const struct wfx_hif_cnf_tx *tx_cnf, const struct sk_buff *skb,
int delay),
TP_ARGS(tx_cnf, skb, delay),
TP_STRUCT__entry(
__field(int, pkt_id)
__field(int, delay_media)
__field(int, delay_queue)
__field(int, delay_fw)
__field(int, ack_failures)
__field(int, flags)
__array(int, rate, 4)
__array(int, tx_count, 4)
),
TP_fast_assign(
/* Keep sync with wfx_rates definition in main.c */
static const int hw_rate[] = { 0, 1, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13 };
const struct ieee80211_tx_info *tx_info =
(const struct ieee80211_tx_info *)skb->cb;
const struct ieee80211_tx_rate *rates = tx_info->driver_rates;
int i;
__entry->pkt_id = tx_cnf->packet_id;
__entry->delay_media = le32_to_cpu(tx_cnf->media_delay);
__entry->delay_queue = le32_to_cpu(tx_cnf->tx_queue_delay);
__entry->delay_fw = delay;
__entry->ack_failures = tx_cnf->ack_failures;
if (!tx_cnf->status || __entry->ack_failures)
__entry->ack_failures += 1;
for (i = 0; i < IEEE80211_NUM_ACS; i++) {
if (rates[0].flags & IEEE80211_TX_RC_MCS)
__entry->rate[i] = rates[i].idx;
else
__entry->rate[i] = hw_rate[rates[i].idx];
__entry->tx_count[i] = rates[i].count;
}
__entry->flags = 0;
if (rates[0].flags & IEEE80211_TX_RC_MCS)
__entry->flags |= 0x01;
if (rates[0].flags & IEEE80211_TX_RC_SHORT_GI)
__entry->flags |= 0x02;
if (rates[0].flags & IEEE80211_TX_RC_GREEN_FIELD)
__entry->flags |= 0x04;
if (rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS)
__entry->flags |= 0x08;
if (tx_info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM)
__entry->flags |= 0x10;
if (tx_cnf->status)
__entry->flags |= 0x20;
if (tx_cnf->status == HIF_STATUS_TX_FAIL_REQUEUE)
__entry->flags |= 0x40;
),
TP_printk("packet ID: %08x, rate policy: %s %d|%d %d|%d %d|%d %d|%d -> %d attempt, Delays media/queue/total: %4dus/%4dus/%4dus",
__entry->pkt_id,
__print_flags(__entry->flags, NULL,
{ 0x01, "M" }, { 0x02, "S" }, { 0x04, "G" }, { 0x08, "R" },
{ 0x10, "D" }, { 0x20, "F" }, { 0x40, "Q" }),
__entry->rate[0],
__entry->tx_count[0],
__entry->rate[1],
__entry->tx_count[1],
__entry->rate[2],
__entry->tx_count[2],
__entry->rate[3],
__entry->tx_count[3],
__entry->ack_failures,
__entry->delay_media,
__entry->delay_queue,
__entry->delay_fw
)
);
#define _trace_tx_stats(tx_cnf, skb, delay) trace_tx_stats(tx_cnf, skb, delay)
TRACE_EVENT(queues_stats,
TP_PROTO(struct wfx_dev *wdev, const struct wfx_queue *elected_queue),
TP_ARGS(wdev, elected_queue),
TP_STRUCT__entry(
__field(int, vif_id)
__field(int, queue_id)
__array(int, hw, IEEE80211_NUM_ACS * 2)
__array(int, drv, IEEE80211_NUM_ACS * 2)
__array(int, cab, IEEE80211_NUM_ACS * 2)
),
TP_fast_assign(
const struct wfx_queue *queue;
struct wfx_vif *wvif;
int i, j;
for (j = 0; j < IEEE80211_NUM_ACS * 2; j++) {
__entry->hw[j] = -1;
__entry->drv[j] = -1;
__entry->cab[j] = -1;
}
__entry->vif_id = -1;
__entry->queue_id = -1;
wvif = NULL;
while ((wvif = wvif_iterate(wdev, wvif)) != NULL) {
for (i = 0; i < IEEE80211_NUM_ACS; i++) {
j = wvif->id * IEEE80211_NUM_ACS + i;
WARN_ON(j >= IEEE80211_NUM_ACS * 2);
queue = &wvif->tx_queue[i];
__entry->hw[j] = atomic_read(&queue->pending_frames);
__entry->drv[j] = skb_queue_len(&queue->normal);
__entry->cab[j] = skb_queue_len(&queue->cab);
if (queue == elected_queue) {
__entry->vif_id = wvif->id;
__entry->queue_id = i;
}
}
}
),
TP_printk("got skb from %d/%d, pend. hw/norm/cab: [ %d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d ] [ %d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d ]",
__entry->vif_id, __entry->queue_id,
__entry->hw[0], __entry->drv[0], __entry->cab[0],
__entry->hw[1], __entry->drv[1], __entry->cab[1],
__entry->hw[2], __entry->drv[2], __entry->cab[2],
__entry->hw[3], __entry->drv[3], __entry->cab[3],
__entry->hw[4], __entry->drv[4], __entry->cab[4],
__entry->hw[5], __entry->drv[5], __entry->cab[5],
__entry->hw[6], __entry->drv[6], __entry->cab[6],
__entry->hw[7], __entry->drv[7], __entry->cab[7]
)
);
#endif
/* This part must be outside protection */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE traces
#include <trace/define_trace.h>
@@ -0,0 +1,169 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Common private data.
*
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
* Copyright (c) 2010, ST-Ericsson
* Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
* Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
*/
#ifndef WFX_H
#define WFX_H
#include <linux/completion.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
#include <linux/nospec.h>
#include <net/mac80211.h>
#include "bh.h"
#include "data_tx.h"
#include "main.h"
#include "queue.h"
#include "hif_tx.h"
#define USEC_PER_TXOP 32 /* see struct ieee80211_tx_queue_params */
#define USEC_PER_TU 1024
struct wfx_hwbus_ops;
struct wfx_dev {
struct wfx_platform_data pdata;
struct device *dev;
struct ieee80211_hw *hw;
struct ieee80211_vif *vif[2];
struct mac_address addresses[2];
const struct wfx_hwbus_ops *hwbus_ops;
void *hwbus_priv;
u8 keyset;
struct completion firmware_ready;
struct wfx_hif_ind_startup hw_caps;
struct wfx_hif hif;
struct delayed_work cooling_timeout_work;
bool poll_irq;
bool chip_frozen;
struct mutex scan_lock;
struct mutex conf_mutex;
struct wfx_hif_cmd hif_cmd;
struct sk_buff_head tx_pending;
wait_queue_head_t tx_dequeue;
atomic_t tx_lock;
atomic_t packet_id;
u32 key_map;
struct wfx_hif_rx_stats rx_stats;
struct mutex rx_stats_lock;
struct wfx_hif_tx_power_loop_info tx_power_loop_info;
struct mutex tx_power_loop_info_lock;
struct workqueue_struct *bh_wq;
};
struct wfx_vif {
struct wfx_dev *wdev;
struct ieee80211_channel *channel;
int id;
u32 link_id_map;
bool after_dtim_tx_allowed;
bool join_in_progress;
struct completion set_pm_mode_complete;
struct delayed_work beacon_loss_work;
struct wfx_queue tx_queue[4];
struct wfx_tx_policy_cache tx_policy_cache;
struct work_struct tx_policy_upload_work;
struct work_struct update_tim_work;
unsigned long uapsd_mask;
struct work_struct scan_work;
struct completion scan_complete;
int scan_nb_chan_done;
bool scan_abort;
struct ieee80211_scan_request *scan_req;
struct ieee80211_channel *remain_on_channel_chan;
int remain_on_channel_duration;
struct work_struct remain_on_channel_work;
};
static inline struct ieee80211_vif *wvif_to_vif(struct wfx_vif *wvif)
{
return container_of((void *)wvif, struct ieee80211_vif, drv_priv);
}
static inline struct wfx_vif *wdev_to_wvif(struct wfx_dev *wdev, int vif_id)
{
if (vif_id >= ARRAY_SIZE(wdev->vif)) {
dev_dbg(wdev->dev, "requesting non-existent vif: %d\n", vif_id);
return NULL;
}
vif_id = array_index_nospec(vif_id, ARRAY_SIZE(wdev->vif));
if (!wdev->vif[vif_id])
return NULL;
return (struct wfx_vif *)wdev->vif[vif_id]->drv_priv;
}
static inline struct wfx_vif *wvif_iterate(struct wfx_dev *wdev, struct wfx_vif *cur)
{
int i;
int mark = 0;
struct wfx_vif *tmp;
if (!cur)
mark = 1;
for (i = 0; i < ARRAY_SIZE(wdev->vif); i++) {
tmp = wdev_to_wvif(wdev, i);
if (mark && tmp)
return tmp;
if (tmp == cur)
mark = 1;
}
return NULL;
}
static inline int wvif_count(struct wfx_dev *wdev)
{
int i;
int ret = 0;
struct wfx_vif *wvif;
for (i = 0; i < ARRAY_SIZE(wdev->vif); i++) {
wvif = wdev_to_wvif(wdev, i);
if (wvif)
ret++;
}
return ret;
}
static inline void memreverse(u8 *src, u8 length)
{
u8 *lo = src;
u8 *hi = src + length - 1;
u8 swap;
while (lo < hi) {
swap = *lo;
*lo++ = *hi;
*hi-- = swap;
}
}
static inline int memzcmp(void *src, unsigned int size)
{
u8 *buf = src;
if (!size)
return 0;
if (*buf)
return 1;
return memcmp(buf, buf + 1, size - 1);
}
#endif