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,56 @@
# SPDX-License-Identifier: ISC
config WIL6210
tristate "Wilocity 60g WiFi card wil6210 support"
select WANT_DEV_COREDUMP
select CRC32
depends on CFG80211
depends on PCI
default n
help
This module adds support for wireless adapter based on
wil6210 chip by Wilocity. It supports operation on the
60 GHz band, covered by the IEEE802.11ad standard.
https://wireless.wiki.kernel.org/en/users/Drivers/wil6210
If you choose to build it as a module, it will be called
wil6210
config WIL6210_ISR_COR
bool "Use Clear-On-Read mode for ISR registers for wil6210"
depends on WIL6210
default y
help
ISR registers on wil6210 chip may operate in either
COR (Clear-On-Read) or W1C (Write-1-to-Clear) mode.
For production code, use COR (say y); is default since
it saves extra target transaction;
For ISR debug, use W1C (say n); is allows to monitor ISR
registers with debugfs. If COR were used, ISR would
self-clear when accessed for debug purposes, it makes
such monitoring impossible.
Say y unless you debug interrupts
config WIL6210_TRACING
bool "wil6210 tracing support"
depends on WIL6210
depends on EVENT_TRACING
default n
help
Say Y here to enable tracepoints for the wil6210 driver
using the kernel tracing infrastructure. Select this
option if you are interested in debugging the driver.
If unsure, say Y to make it easier to debug problems.
config WIL6210_DEBUGFS
bool "wil6210 debugfs support"
depends on WIL6210
depends on DEBUG_FS
default y
help
Say Y here to enable wil6210 debugfs support, using the
kernel debugfs infrastructure. Select this
option if you are interested in debugging the driver.
If unsure, say Y to make it easier to debug problems.
@@ -0,0 +1,25 @@
# SPDX-License-Identifier: ISC
obj-$(CONFIG_WIL6210) += wil6210.o
wil6210-y := main.o
wil6210-y += netdev.o
wil6210-y += cfg80211.o
wil6210-y += pcie_bus.o
wil6210-$(CONFIG_WIL6210_DEBUGFS) += debugfs.o
wil6210-y += wmi.o
wil6210-y += interrupt.o
wil6210-y += txrx.o
wil6210-y += txrx_edma.o
wil6210-y += debug.o
wil6210-y += rx_reorder.o
wil6210-y += fw.o
wil6210-y += pm.o
wil6210-y += pmc.o
wil6210-$(CONFIG_WIL6210_TRACING) += trace.o
wil6210-y += wil_platform.o
wil6210-y += ethtool.o
wil6210-y += wil_crash_dump.o
wil6210-y += p2p.o
# for tracing framework to find trace.h
CFLAGS_trace.o := -I$(src)
@@ -0,0 +1,57 @@
/* SPDX-License-Identifier: ISC */
/* Copyright (c) 2015 Qualcomm Atheros, Inc.
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
*/
/* This file contains the definitions for the boot loader
* for the Qualcomm "Sparrow" 60 Gigabit wireless solution.
*/
#ifndef BOOT_LOADER_EXPORT_H_
#define BOOT_LOADER_EXPORT_H_
struct bl_dedicated_registers_v1 {
__le32 boot_loader_ready; /* 0x880A3C driver will poll
* this Dword until BL will
* set it to 1 (initial value
* should be 0)
*/
__le32 boot_loader_struct_version; /* 0x880A40 BL struct ver. */
__le16 rf_type; /* 0x880A44 connected RF ID */
__le16 rf_status; /* 0x880A46 RF status,
* 0 is OK else error
*/
__le32 baseband_type; /* 0x880A48 board type ID */
u8 mac_address[6]; /* 0x880A4c BL mac address */
u8 bl_version_major; /* 0x880A52 BL ver. major */
u8 bl_version_minor; /* 0x880A53 BL ver. minor */
__le16 bl_version_subminor; /* 0x880A54 BL ver. subminor */
__le16 bl_version_build; /* 0x880A56 BL ver. build */
/* valid only for version 2 and above */
__le32 bl_assert_code; /* 0x880A58 BL Assert code */
__le32 bl_assert_blink; /* 0x880A5C BL Assert Branch */
__le32 bl_shutdown_handshake; /* 0x880A60 BL cleaner shutdown */
__le32 bl_reserved[21]; /* 0x880A64 - 0x880AB4 */
__le32 bl_magic_number; /* 0x880AB8 BL Magic number */
} __packed;
/* the following struct is the version 0 struct */
struct bl_dedicated_registers_v0 {
__le32 boot_loader_ready; /* 0x880A3C driver will poll
* this Dword until BL will
* set it to 1 (initial value
* should be 0)
*/
#define BL_READY (1) /* ready indication */
__le32 boot_loader_struct_version; /* 0x880A40 BL struct ver. */
__le32 rf_type; /* 0x880A44 connected RF ID */
__le32 baseband_type; /* 0x880A48 board type ID */
u8 mac_address[6]; /* 0x880A4c BL mac address */
} __packed;
/* bits for bl_shutdown_handshake */
#define BL_SHUTDOWN_HS_GRTD BIT(0)
#define BL_SHUTDOWN_HS_RTD BIT(1)
#define BL_SHUTDOWN_HS_PROT_VER(x) WIL_GET_BITS(x, 28, 31)
#endif /* BOOT_LOADER_EXPORT_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2013,2016 Qualcomm Atheros, Inc.
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
*/
#include "wil6210.h"
#include "trace.h"
void __wil_err(struct wil6210_priv *wil, const char *fmt, ...)
{
struct va_format vaf;
va_list args;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
netdev_err(wil->main_ndev, "%pV", &vaf);
trace_wil6210_log_err(&vaf);
va_end(args);
}
void __wil_err_ratelimited(struct wil6210_priv *wil, const char *fmt, ...)
{
struct va_format vaf;
va_list args;
if (!net_ratelimit())
return;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
netdev_err(wil->main_ndev, "%pV", &vaf);
trace_wil6210_log_err(&vaf);
va_end(args);
}
void wil_dbg_ratelimited(const struct wil6210_priv *wil, const char *fmt, ...)
{
struct va_format vaf;
va_list args;
if (!net_ratelimit())
return;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
netdev_dbg(wil->main_ndev, "%pV", &vaf);
trace_wil6210_log_dbg(&vaf);
va_end(args);
}
void __wil_info(struct wil6210_priv *wil, const char *fmt, ...)
{
struct va_format vaf;
va_list args;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
netdev_info(wil->main_ndev, "%pV", &vaf);
trace_wil6210_log_info(&vaf);
va_end(args);
}
void wil_dbg_trace(struct wil6210_priv *wil, const char *fmt, ...)
{
struct va_format vaf;
va_list args;
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
trace_wil6210_log_dbg(&vaf);
va_end(args);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2014,2017 Qualcomm Atheros, Inc.
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
*/
#include <linux/etherdevice.h>
#include <linux/pci.h>
#include <linux/rtnetlink.h>
#include <net/cfg80211.h>
#include "wil6210.h"
static int
wil_ethtoolops_get_coalesce(struct net_device *ndev,
struct ethtool_coalesce *cp,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct wil6210_priv *wil = ndev_to_wil(ndev);
u32 tx_itr_en, tx_itr_val = 0;
u32 rx_itr_en, rx_itr_val = 0;
int ret;
mutex_lock(&wil->mutex);
wil_dbg_misc(wil, "ethtoolops_get_coalesce\n");
ret = wil_pm_runtime_get(wil);
if (ret < 0)
goto out;
tx_itr_en = wil_r(wil, RGF_DMA_ITR_TX_CNT_CTL);
if (tx_itr_en & BIT_DMA_ITR_TX_CNT_CTL_EN)
tx_itr_val = wil_r(wil, RGF_DMA_ITR_TX_CNT_TRSH);
rx_itr_en = wil_r(wil, RGF_DMA_ITR_RX_CNT_CTL);
if (rx_itr_en & BIT_DMA_ITR_RX_CNT_CTL_EN)
rx_itr_val = wil_r(wil, RGF_DMA_ITR_RX_CNT_TRSH);
wil_pm_runtime_put(wil);
cp->tx_coalesce_usecs = tx_itr_val;
cp->rx_coalesce_usecs = rx_itr_val;
ret = 0;
out:
mutex_unlock(&wil->mutex);
return ret;
}
static int
wil_ethtoolops_set_coalesce(struct net_device *ndev,
struct ethtool_coalesce *cp,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct wil6210_priv *wil = ndev_to_wil(ndev);
struct wireless_dev *wdev = ndev->ieee80211_ptr;
int ret;
mutex_lock(&wil->mutex);
wil_dbg_misc(wil, "ethtoolops_set_coalesce: rx %d usec, tx %d usec\n",
cp->rx_coalesce_usecs, cp->tx_coalesce_usecs);
if (wdev->iftype == NL80211_IFTYPE_MONITOR) {
wil_dbg_misc(wil, "No IRQ coalescing in monitor mode\n");
ret = -EINVAL;
goto out;
}
/* only @rx_coalesce_usecs and @tx_coalesce_usecs supported,
* ignore other parameters
*/
if (cp->rx_coalesce_usecs > WIL6210_ITR_TRSH_MAX ||
cp->tx_coalesce_usecs > WIL6210_ITR_TRSH_MAX)
goto out_bad;
wil->tx_max_burst_duration = cp->tx_coalesce_usecs;
wil->rx_max_burst_duration = cp->rx_coalesce_usecs;
ret = wil_pm_runtime_get(wil);
if (ret < 0)
goto out;
wil->txrx_ops.configure_interrupt_moderation(wil);
wil_pm_runtime_put(wil);
ret = 0;
out:
mutex_unlock(&wil->mutex);
return ret;
out_bad:
wil_dbg_misc(wil, "Unsupported coalescing params. Raw command:\n");
print_hex_dump_debug("DBG[MISC] coal ", DUMP_PREFIX_OFFSET, 16, 4,
cp, sizeof(*cp), false);
mutex_unlock(&wil->mutex);
return -EINVAL;
}
static const struct ethtool_ops wil_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS,
.get_drvinfo = cfg80211_get_drvinfo,
.get_coalesce = wil_ethtoolops_get_coalesce,
.set_coalesce = wil_ethtoolops_set_coalesce,
};
void wil_set_ethtoolops(struct net_device *ndev)
{
ndev->ethtool_ops = &wil_ethtool_ops;
}
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2014-2015,2017 Qualcomm Atheros, Inc.
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
*/
#include <linux/firmware.h>
#include <linux/module.h>
#include <linux/crc32.h>
#include "wil6210.h"
#include "fw.h"
MODULE_FIRMWARE(WIL_FW_NAME_DEFAULT);
MODULE_FIRMWARE(WIL_FW_NAME_SPARROW_PLUS);
MODULE_FIRMWARE(WIL_BOARD_FILE_NAME);
MODULE_FIRMWARE(WIL_FW_NAME_TALYN);
MODULE_FIRMWARE(WIL_BRD_NAME_TALYN);
static
void wil_memset_toio_32(volatile void __iomem *dst, u32 val,
size_t count)
{
volatile u32 __iomem *d = dst;
for (count += 4; count > 4; count -= 4)
__raw_writel(val, d++);
}
#include "fw_inc.c"
@@ -0,0 +1,206 @@
/* SPDX-License-Identifier: ISC */
/*
* Copyright (c) 2014,2016 Qualcomm Atheros, Inc.
* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
*/
#ifndef __WIL_FW_H__
#define __WIL_FW_H__
#define WIL_FW_SIGNATURE (0x36323130) /* '0126' */
#define WIL_FW_FMT_VERSION (1) /* format version driver supports */
enum wil_fw_record_type {
wil_fw_type_comment = 1,
wil_fw_type_data = 2,
wil_fw_type_fill = 3,
wil_fw_type_action = 4,
wil_fw_type_verify = 5,
wil_fw_type_file_header = 6,
wil_fw_type_direct_write = 7,
wil_fw_type_gateway_data = 8,
wil_fw_type_gateway_data4 = 9,
};
struct wil_fw_record_head {
__le16 type; /* enum wil_fw_record_type */
__le16 flags; /* to be defined */
__le32 size; /* whole record, bytes after head */
} __packed;
/* data block. write starting from @addr
* data_size inferred from the @head.size. For this case,
* data_size = @head.size - offsetof(struct wil_fw_record_data, data)
*/
struct wil_fw_record_data { /* type == wil_fw_type_data */
__le32 addr;
__le32 data[]; /* [data_size], see above */
} __packed;
/* fill with constant @value, @size bytes starting from @addr */
struct wil_fw_record_fill { /* type == wil_fw_type_fill */
__le32 addr;
__le32 value;
__le32 size;
} __packed;
/* free-form comment
* for informational purpose, data_size is @head.size from record header
*/
struct wil_fw_record_comment { /* type == wil_fw_type_comment */
DECLARE_FLEX_ARRAY(u8, data); /* free-form data [data_size], see above */
} __packed;
/* Comment header - common for all comment record types */
struct wil_fw_record_comment_hdr {
__le32 magic;
};
/* FW capabilities encoded inside a comment record */
#define WIL_FW_CAPABILITIES_MAGIC (0xabcddcba)
struct wil_fw_record_capabilities { /* type == wil_fw_type_comment */
/* identifies capabilities record */
struct wil_fw_record_comment_hdr hdr;
/* capabilities (variable size), see enum wmi_fw_capability */
u8 capabilities[];
} __packed;
/* FW VIF concurrency encoded inside a comment record
* Format is similar to wiphy->iface_combinations
*/
#define WIL_FW_CONCURRENCY_MAGIC (0xfedccdef)
#define WIL_FW_CONCURRENCY_REC_VER 1
struct wil_fw_concurrency_limit {
__le16 max; /* maximum number of interfaces of these types */
__le16 types; /* interface types (bit mask of enum nl80211_iftype) */
} __packed;
struct wil_fw_concurrency_combo {
u8 n_limits; /* number of wil_fw_concurrency_limit entries */
u8 max_interfaces; /* max number of concurrent interfaces allowed */
u8 n_diff_channels; /* total number of different channels allowed */
u8 same_bi; /* for APs, 1 if all APs must have same BI */
/* keep last - concurrency limits, variable size by n_limits */
struct wil_fw_concurrency_limit limits[];
} __packed;
struct wil_fw_record_concurrency { /* type == wil_fw_type_comment */
/* identifies concurrency record */
__le32 magic;
/* structure version, currently always 1 */
u8 version;
/* maximum number of supported MIDs _in addition_ to MID 0 */
u8 n_mids;
/* number of concurrency combinations that follow */
__le16 n_combos;
/* keep last - combinations, variable size by n_combos */
} __packed;
/* brd file info encoded inside a comment record */
#define WIL_BRD_FILE_MAGIC (0xabcddcbb)
struct brd_info {
__le32 base_addr;
__le32 max_size_bytes;
} __packed;
struct wil_fw_record_brd_file { /* type == wil_fw_type_comment */
/* identifies brd file record */
struct wil_fw_record_comment_hdr hdr;
__le32 version;
struct brd_info brd_info[];
} __packed;
/* perform action
* data_size = @head.size - offsetof(struct wil_fw_record_action, data)
*/
struct wil_fw_record_action { /* type == wil_fw_type_action */
__le32 action; /* action to perform: reset, wait for fw ready etc. */
__le32 data[]; /* action specific, [data_size], see above */
} __packed;
/* data block for struct wil_fw_record_direct_write */
struct wil_fw_data_dwrite {
__le32 addr;
__le32 value;
__le32 mask;
} __packed;
/* write @value to the @addr,
* preserve original bits accordingly to the @mask
* data_size is @head.size where @head is record header
*/
struct wil_fw_record_direct_write { /* type == wil_fw_type_direct_write */
DECLARE_FLEX_ARRAY(struct wil_fw_data_dwrite, data);
} __packed;
/* verify condition: [@addr] & @mask == @value
* if condition not met, firmware download fails
*/
struct wil_fw_record_verify { /* type == wil_fw_verify */
__le32 addr; /* read from this address */
__le32 value; /* reference value */
__le32 mask; /* mask for verification */
} __packed;
/* file header
* First record of every file
*/
/* the FW version prefix in the comment */
#define WIL_FW_VERSION_PREFIX "FW version: "
#define WIL_FW_VERSION_PREFIX_LEN (sizeof(WIL_FW_VERSION_PREFIX) - 1)
struct wil_fw_record_file_header {
__le32 signature ; /* Wilocity signature */
__le32 reserved;
__le32 crc; /* crc32 of the following data */
__le32 version; /* format version */
__le32 data_len; /* total data in file, including this record */
u8 comment[32]; /* short description */
} __packed;
/* 1-dword gateway */
/* data block for the struct wil_fw_record_gateway_data */
struct wil_fw_data_gw {
__le32 addr;
__le32 value;
} __packed;
/* gateway write block.
* write starting address and values from the data buffer
* through the gateway
* data_size inferred from the @head.size. For this case,
* data_size = @head.size - offsetof(struct wil_fw_record_gateway_data, data)
*/
struct wil_fw_record_gateway_data { /* type == wil_fw_type_gateway_data */
__le32 gateway_addr_addr;
__le32 gateway_value_addr;
__le32 gateway_cmd_addr;
__le32 gateway_ctrl_address;
#define WIL_FW_GW_CTL_BUSY BIT(29) /* gateway busy performing operation */
#define WIL_FW_GW_CTL_RUN BIT(30) /* start gateway operation */
__le32 command;
struct wil_fw_data_gw data[]; /* total size [data_size], see above */
} __packed;
/* 4-dword gateway */
/* data block for the struct wil_fw_record_gateway_data4 */
struct wil_fw_data_gw4 {
__le32 addr;
__le32 value[4];
} __packed;
/* gateway write block.
* write starting address and values from the data buffer
* through the gateway
* data_size inferred from the @head.size. For this case,
* data_size = @head.size - offsetof(struct wil_fw_record_gateway_data4, data)
*/
struct wil_fw_record_gateway_data4 { /* type == wil_fw_type_gateway_data4 */
__le32 gateway_addr_addr;
__le32 gateway_value_addr[4];
__le32 gateway_cmd_addr;
__le32 gateway_ctrl_address; /* same logic as for 1-dword gw */
__le32 command;
struct wil_fw_data_gw4 data[]; /* total size [data_size], see above */
} __packed;
#endif /* __WIL_FW_H__ */
@@ -0,0 +1,828 @@
// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2014-2017 Qualcomm Atheros, Inc.
* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
*/
/* Algorithmic part of the firmware download.
* To be included in the container file providing framework
*/
#define wil_err_fw(wil, fmt, arg...) wil_err(wil, "ERR[ FW ]" fmt, ##arg)
#define wil_dbg_fw(wil, fmt, arg...) wil_dbg(wil, "DBG[ FW ]" fmt, ##arg)
#define wil_hex_dump_fw(prefix_str, prefix_type, rowsize, \
groupsize, buf, len, ascii) \
print_hex_dump_debug("DBG[ FW ]" prefix_str, \
prefix_type, rowsize, \
groupsize, buf, len, ascii)
static bool wil_fw_addr_check(struct wil6210_priv *wil,
void __iomem **ioaddr, __le32 val,
u32 size, const char *msg)
{
*ioaddr = wmi_buffer_block(wil, val, size);
if (!(*ioaddr)) {
wil_err_fw(wil, "bad %s: 0x%08x\n", msg, le32_to_cpu(val));
return false;
}
return true;
}
/**
* wil_fw_verify - verify firmware file validity
*
* perform various checks for the firmware file header.
* records are not validated.
*
* Return file size or negative error
*/
static int wil_fw_verify(struct wil6210_priv *wil, const u8 *data, size_t size)
{
const struct wil_fw_record_head *hdr = (const void *)data;
struct wil_fw_record_file_header fh;
const struct wil_fw_record_file_header *fh_;
u32 crc;
u32 dlen;
if (size % 4) {
wil_err_fw(wil, "image size not aligned: %zu\n", size);
return -EINVAL;
}
/* have enough data for the file header? */
if (size < sizeof(*hdr) + sizeof(fh)) {
wil_err_fw(wil, "file too short: %zu bytes\n", size);
return -EINVAL;
}
/* start with the file header? */
if (le16_to_cpu(hdr->type) != wil_fw_type_file_header) {
wil_err_fw(wil, "no file header\n");
return -EINVAL;
}
/* data_len */
fh_ = (struct wil_fw_record_file_header *)&hdr[1];
dlen = le32_to_cpu(fh_->data_len);
if (dlen % 4) {
wil_err_fw(wil, "data length not aligned: %lu\n", (ulong)dlen);
return -EINVAL;
}
if (size < dlen) {
wil_err_fw(wil, "file truncated at %zu/%lu\n",
size, (ulong)dlen);
return -EINVAL;
}
if (dlen < sizeof(*hdr) + sizeof(fh)) {
wil_err_fw(wil, "data length too short: %lu\n", (ulong)dlen);
return -EINVAL;
}
/* signature */
if (le32_to_cpu(fh_->signature) != WIL_FW_SIGNATURE) {
wil_err_fw(wil, "bad header signature: 0x%08x\n",
le32_to_cpu(fh_->signature));
return -EINVAL;
}
/* version */
if (le32_to_cpu(fh_->version) > WIL_FW_FMT_VERSION) {
wil_err_fw(wil, "unsupported header version: %d\n",
le32_to_cpu(fh_->version));
return -EINVAL;
}
/* checksum. ~crc32(~0, data, size) when fh.crc set to 0*/
fh = *fh_;
fh.crc = 0;
crc = crc32_le(~0, (unsigned char const *)hdr, sizeof(*hdr));
crc = crc32_le(crc, (unsigned char const *)&fh, sizeof(fh));
crc = crc32_le(crc, (unsigned char const *)&fh_[1],
dlen - sizeof(*hdr) - sizeof(fh));
crc = ~crc;
if (crc != le32_to_cpu(fh_->crc)) {
wil_err_fw(wil, "checksum mismatch:"
" calculated for %lu bytes 0x%08x != 0x%08x\n",
(ulong)dlen, crc, le32_to_cpu(fh_->crc));
return -EINVAL;
}
return (int)dlen;
}
static int fw_ignore_section(struct wil6210_priv *wil, const void *data,
size_t size)
{
return 0;
}
static int
fw_handle_capabilities(struct wil6210_priv *wil, const void *data,
size_t size)
{
const struct wil_fw_record_capabilities *rec = data;
size_t capa_size;
if (size < sizeof(*rec)) {
wil_err_fw(wil, "capabilities record too short: %zu\n", size);
/* let the FW load anyway */
return 0;
}
capa_size = size - offsetof(struct wil_fw_record_capabilities,
capabilities);
bitmap_zero(wil->fw_capabilities, WMI_FW_CAPABILITY_MAX);
memcpy(wil->fw_capabilities, rec->capabilities,
min_t(size_t, sizeof(wil->fw_capabilities), capa_size));
wil_hex_dump_fw("CAPA", DUMP_PREFIX_OFFSET, 16, 1,
rec->capabilities, capa_size, false);
return 0;
}
static int
fw_handle_brd_file(struct wil6210_priv *wil, const void *data,
size_t size)
{
const struct wil_fw_record_brd_file *rec = data;
u32 max_num_ent, i, ent_size;
if (size <= offsetof(struct wil_fw_record_brd_file, brd_info)) {
wil_err(wil, "board record too short, size %zu\n", size);
return -EINVAL;
}
ent_size = size - offsetof(struct wil_fw_record_brd_file, brd_info);
max_num_ent = ent_size / sizeof(struct brd_info);
if (!max_num_ent) {
wil_err(wil, "brd info entries are missing\n");
return -EINVAL;
}
wil->brd_info = kzalloc_objs(struct wil_brd_info, max_num_ent);
if (!wil->brd_info)
return -ENOMEM;
for (i = 0; i < max_num_ent; i++) {
wil->brd_info[i].file_addr =
le32_to_cpu(rec->brd_info[i].base_addr);
wil->brd_info[i].file_max_size =
le32_to_cpu(rec->brd_info[i].max_size_bytes);
if (!wil->brd_info[i].file_addr)
break;
wil_dbg_fw(wil,
"brd info %d: file_addr 0x%x, file_max_size %d\n",
i, wil->brd_info[i].file_addr,
wil->brd_info[i].file_max_size);
}
wil->num_of_brd_entries = i;
if (wil->num_of_brd_entries == 0) {
kfree(wil->brd_info);
wil->brd_info = NULL;
wil_dbg_fw(wil,
"no valid brd info entries, using brd file addr\n");
} else {
wil_dbg_fw(wil, "num of brd info entries %d\n",
wil->num_of_brd_entries);
}
return 0;
}
static int
fw_handle_concurrency(struct wil6210_priv *wil, const void *data,
size_t size)
{
const struct wil_fw_record_concurrency *rec = data;
const struct wil_fw_concurrency_combo *combo;
const struct wil_fw_concurrency_limit *limit;
size_t remain, lsize;
int i, n_combos;
if (size < sizeof(*rec)) {
wil_err_fw(wil, "concurrency record too short: %zu\n", size);
/* continue, let the FW load anyway */
return 0;
}
n_combos = le16_to_cpu(rec->n_combos);
remain = size - sizeof(struct wil_fw_record_concurrency);
combo = (const struct wil_fw_concurrency_combo *)(rec + 1);
for (i = 0; i < n_combos; i++) {
if (remain < sizeof(*combo))
goto out_short;
remain -= sizeof(*combo);
limit = combo->limits;
lsize = combo->n_limits * sizeof(*limit);
if (remain < lsize)
goto out_short;
remain -= lsize;
limit += combo->n_limits;
combo = (struct wil_fw_concurrency_combo *)limit;
}
return wil_cfg80211_iface_combinations_from_fw(wil, rec);
out_short:
wil_err_fw(wil, "concurrency record truncated\n");
return 0;
}
static int
fw_handle_comment(struct wil6210_priv *wil, const void *data,
size_t size)
{
const struct wil_fw_record_comment_hdr *hdr = data;
u32 magic;
int rc = 0;
if (size < sizeof(*hdr))
return 0;
magic = le32_to_cpu(hdr->magic);
switch (magic) {
case WIL_FW_CAPABILITIES_MAGIC:
wil_dbg_fw(wil, "magic is WIL_FW_CAPABILITIES_MAGIC\n");
rc = fw_handle_capabilities(wil, data, size);
break;
case WIL_BRD_FILE_MAGIC:
wil_dbg_fw(wil, "magic is WIL_BRD_FILE_MAGIC\n");
rc = fw_handle_brd_file(wil, data, size);
break;
case WIL_FW_CONCURRENCY_MAGIC:
wil_dbg_fw(wil, "magic is WIL_FW_CONCURRENCY_MAGIC\n");
rc = fw_handle_concurrency(wil, data, size);
break;
default:
wil_hex_dump_fw("", DUMP_PREFIX_OFFSET, 16, 1,
data, size, true);
}
return rc;
}
static int __fw_handle_data(struct wil6210_priv *wil, const void *data,
size_t size, __le32 addr)
{
const struct wil_fw_record_data *d = data;
void __iomem *dst;
size_t s = size - sizeof(*d);
if (size < sizeof(*d) + sizeof(u32)) {
wil_err_fw(wil, "data record too short: %zu\n", size);
return -EINVAL;
}
if (!wil_fw_addr_check(wil, &dst, addr, s, "address"))
return -EINVAL;
wil_dbg_fw(wil, "write [0x%08x] <== %zu bytes\n", le32_to_cpu(addr), s);
wil_memcpy_toio_32(dst, d->data, s);
wmb(); /* finish before processing next record */
return 0;
}
static int fw_handle_data(struct wil6210_priv *wil, const void *data,
size_t size)
{
const struct wil_fw_record_data *d = data;
return __fw_handle_data(wil, data, size, d->addr);
}
static int fw_handle_fill(struct wil6210_priv *wil, const void *data,
size_t size)
{
const struct wil_fw_record_fill *d = data;
void __iomem *dst;
u32 v;
size_t s = (size_t)le32_to_cpu(d->size);
if (size != sizeof(*d)) {
wil_err_fw(wil, "bad size for fill record: %zu\n", size);
return -EINVAL;
}
if (s < sizeof(u32)) {
wil_err_fw(wil, "fill size too short: %zu\n", s);
return -EINVAL;
}
if (s % sizeof(u32)) {
wil_err_fw(wil, "fill size not aligned: %zu\n", s);
return -EINVAL;
}
if (!wil_fw_addr_check(wil, &dst, d->addr, s, "address"))
return -EINVAL;
v = le32_to_cpu(d->value);
wil_dbg_fw(wil, "fill [0x%08x] <== 0x%08x, %zu bytes\n",
le32_to_cpu(d->addr), v, s);
wil_memset_toio_32(dst, v, s);
wmb(); /* finish before processing next record */
return 0;
}
static int fw_handle_file_header(struct wil6210_priv *wil, const void *data,
size_t size)
{
const struct wil_fw_record_file_header *d = data;
if (size != sizeof(*d)) {
wil_err_fw(wil, "file header length incorrect: %zu\n", size);
return -EINVAL;
}
wil_dbg_fw(wil, "new file, ver. %d, %i bytes\n",
d->version, d->data_len);
wil_hex_dump_fw("", DUMP_PREFIX_OFFSET, 16, 1, d->comment,
sizeof(d->comment), true);
if (!memcmp(d->comment, WIL_FW_VERSION_PREFIX,
WIL_FW_VERSION_PREFIX_LEN))
memcpy(wil->fw_version,
d->comment + WIL_FW_VERSION_PREFIX_LEN,
min(sizeof(d->comment) - WIL_FW_VERSION_PREFIX_LEN,
sizeof(wil->fw_version) - 1));
return 0;
}
static int fw_handle_direct_write(struct wil6210_priv *wil, const void *data,
size_t size)
{
const struct wil_fw_record_direct_write *d = data;
const struct wil_fw_data_dwrite *block = d->data;
int n, i;
if (size % sizeof(*block)) {
wil_err_fw(wil, "record size not aligned on %zu: %zu\n",
sizeof(*block), size);
return -EINVAL;
}
n = size / sizeof(*block);
for (i = 0; i < n; i++) {
void __iomem *dst;
u32 m = le32_to_cpu(block[i].mask);
u32 v = le32_to_cpu(block[i].value);
u32 x, y;
if (!wil_fw_addr_check(wil, &dst, block[i].addr, 0, "address"))
return -EINVAL;
x = readl(dst);
y = (x & m) | (v & ~m);
wil_dbg_fw(wil, "write [0x%08x] <== 0x%08x "
"(old 0x%08x val 0x%08x mask 0x%08x)\n",
le32_to_cpu(block[i].addr), y, x, v, m);
writel(y, dst);
wmb(); /* finish before processing next record */
}
return 0;
}
static int gw_write(struct wil6210_priv *wil, void __iomem *gwa_addr,
void __iomem *gwa_cmd, void __iomem *gwa_ctl, u32 gw_cmd,
u32 a)
{
unsigned delay = 0;
writel(a, gwa_addr);
writel(gw_cmd, gwa_cmd);
wmb(); /* finish before activate gw */
writel(WIL_FW_GW_CTL_RUN, gwa_ctl); /* activate gw */
do {
udelay(1); /* typical time is few usec */
if (delay++ > 100) {
wil_err_fw(wil, "gw timeout\n");
return -EINVAL;
}
} while (readl(gwa_ctl) & WIL_FW_GW_CTL_BUSY); /* gw done? */
return 0;
}
static int fw_handle_gateway_data(struct wil6210_priv *wil, const void *data,
size_t size)
{
const struct wil_fw_record_gateway_data *d = data;
const struct wil_fw_data_gw *block = d->data;
void __iomem *gwa_addr;
void __iomem *gwa_val;
void __iomem *gwa_cmd;
void __iomem *gwa_ctl;
u32 gw_cmd;
int n, i;
if (size < sizeof(*d) + sizeof(*block)) {
wil_err_fw(wil, "gateway record too short: %zu\n", size);
return -EINVAL;
}
if ((size - sizeof(*d)) % sizeof(*block)) {
wil_err_fw(wil, "gateway record data size"
" not aligned on %zu: %zu\n",
sizeof(*block), size - sizeof(*d));
return -EINVAL;
}
n = (size - sizeof(*d)) / sizeof(*block);
gw_cmd = le32_to_cpu(d->command);
wil_dbg_fw(wil, "gw write record [%3d] blocks, cmd 0x%08x\n",
n, gw_cmd);
if (!wil_fw_addr_check(wil, &gwa_addr, d->gateway_addr_addr, 0,
"gateway_addr_addr") ||
!wil_fw_addr_check(wil, &gwa_val, d->gateway_value_addr, 0,
"gateway_value_addr") ||
!wil_fw_addr_check(wil, &gwa_cmd, d->gateway_cmd_addr, 0,
"gateway_cmd_addr") ||
!wil_fw_addr_check(wil, &gwa_ctl, d->gateway_ctrl_address, 0,
"gateway_ctrl_address"))
return -EINVAL;
wil_dbg_fw(wil, "gw addresses: addr 0x%08x val 0x%08x"
" cmd 0x%08x ctl 0x%08x\n",
le32_to_cpu(d->gateway_addr_addr),
le32_to_cpu(d->gateway_value_addr),
le32_to_cpu(d->gateway_cmd_addr),
le32_to_cpu(d->gateway_ctrl_address));
for (i = 0; i < n; i++) {
int rc;
u32 a = le32_to_cpu(block[i].addr);
u32 v = le32_to_cpu(block[i].value);
wil_dbg_fw(wil, " gw write[%3d] [0x%08x] <== 0x%08x\n",
i, a, v);
writel(v, gwa_val);
rc = gw_write(wil, gwa_addr, gwa_cmd, gwa_ctl, gw_cmd, a);
if (rc)
return rc;
}
return 0;
}
static int fw_handle_gateway_data4(struct wil6210_priv *wil, const void *data,
size_t size)
{
const struct wil_fw_record_gateway_data4 *d = data;
const struct wil_fw_data_gw4 *block = d->data;
void __iomem *gwa_addr;
void __iomem *gwa_val[ARRAY_SIZE(block->value)];
void __iomem *gwa_cmd;
void __iomem *gwa_ctl;
u32 gw_cmd;
int n, i, k;
if (size < sizeof(*d) + sizeof(*block)) {
wil_err_fw(wil, "gateway4 record too short: %zu\n", size);
return -EINVAL;
}
if ((size - sizeof(*d)) % sizeof(*block)) {
wil_err_fw(wil, "gateway4 record data size"
" not aligned on %zu: %zu\n",
sizeof(*block), size - sizeof(*d));
return -EINVAL;
}
n = (size - sizeof(*d)) / sizeof(*block);
gw_cmd = le32_to_cpu(d->command);
wil_dbg_fw(wil, "gw4 write record [%3d] blocks, cmd 0x%08x\n",
n, gw_cmd);
if (!wil_fw_addr_check(wil, &gwa_addr, d->gateway_addr_addr, 0,
"gateway_addr_addr"))
return -EINVAL;
for (k = 0; k < ARRAY_SIZE(block->value); k++)
if (!wil_fw_addr_check(wil, &gwa_val[k],
d->gateway_value_addr[k],
0, "gateway_value_addr"))
return -EINVAL;
if (!wil_fw_addr_check(wil, &gwa_cmd, d->gateway_cmd_addr, 0,
"gateway_cmd_addr") ||
!wil_fw_addr_check(wil, &gwa_ctl, d->gateway_ctrl_address, 0,
"gateway_ctrl_address"))
return -EINVAL;
wil_dbg_fw(wil, "gw4 addresses: addr 0x%08x cmd 0x%08x ctl 0x%08x\n",
le32_to_cpu(d->gateway_addr_addr),
le32_to_cpu(d->gateway_cmd_addr),
le32_to_cpu(d->gateway_ctrl_address));
wil_hex_dump_fw("val addresses: ", DUMP_PREFIX_NONE, 16, 4,
d->gateway_value_addr, sizeof(d->gateway_value_addr),
false);
for (i = 0; i < n; i++) {
int rc;
u32 a = le32_to_cpu(block[i].addr);
u32 v[ARRAY_SIZE(block->value)];
for (k = 0; k < ARRAY_SIZE(block->value); k++)
v[k] = le32_to_cpu(block[i].value[k]);
wil_dbg_fw(wil, " gw4 write[%3d] [0x%08x] <==\n", i, a);
wil_hex_dump_fw(" val ", DUMP_PREFIX_NONE, 16, 4, v,
sizeof(v), false);
for (k = 0; k < ARRAY_SIZE(block->value); k++)
writel(v[k], gwa_val[k]);
rc = gw_write(wil, gwa_addr, gwa_cmd, gwa_ctl, gw_cmd, a);
if (rc)
return rc;
}
return 0;
}
static const struct {
int type;
int (*load_handler)(struct wil6210_priv *wil, const void *data,
size_t size);
int (*parse_handler)(struct wil6210_priv *wil, const void *data,
size_t size);
} wil_fw_handlers[] = {
{wil_fw_type_comment, fw_handle_comment, fw_handle_comment},
{wil_fw_type_data, fw_handle_data, fw_ignore_section},
{wil_fw_type_fill, fw_handle_fill, fw_ignore_section},
/* wil_fw_type_action */
/* wil_fw_type_verify */
{wil_fw_type_file_header, fw_handle_file_header,
fw_handle_file_header},
{wil_fw_type_direct_write, fw_handle_direct_write, fw_ignore_section},
{wil_fw_type_gateway_data, fw_handle_gateway_data, fw_ignore_section},
{wil_fw_type_gateway_data4, fw_handle_gateway_data4,
fw_ignore_section},
};
static int wil_fw_handle_record(struct wil6210_priv *wil, int type,
const void *data, size_t size, bool load)
{
int i;
for (i = 0; i < ARRAY_SIZE(wil_fw_handlers); i++)
if (wil_fw_handlers[i].type == type)
return load ?
wil_fw_handlers[i].load_handler(
wil, data, size) :
wil_fw_handlers[i].parse_handler(
wil, data, size);
wil_err_fw(wil, "unknown record type: %d\n", type);
return -EINVAL;
}
/**
* wil_fw_process - process section from FW file
* if load is true: Load the FW and uCode code and data to the
* corresponding device memory regions,
* otherwise only parse and look for capabilities
*
* Return error code
*/
static int wil_fw_process(struct wil6210_priv *wil, const void *data,
size_t size, bool load)
{
int rc = 0;
const struct wil_fw_record_head *hdr;
size_t s, hdr_sz;
for (hdr = data;; hdr = (const void *)hdr + s, size -= s) {
if (size < sizeof(*hdr))
break;
hdr_sz = le32_to_cpu(hdr->size);
s = sizeof(*hdr) + hdr_sz;
if (s > size)
break;
if (hdr_sz % 4) {
wil_err_fw(wil, "unaligned record size: %zu\n",
hdr_sz);
return -EINVAL;
}
rc = wil_fw_handle_record(wil, le16_to_cpu(hdr->type),
&hdr[1], hdr_sz, load);
if (rc)
return rc;
}
if (size) {
wil_err_fw(wil, "unprocessed bytes: %zu\n", size);
if (size >= sizeof(*hdr)) {
wil_err_fw(wil, "Stop at offset %ld"
" record type %d [%zd bytes]\n",
(long)((const void *)hdr - data),
le16_to_cpu(hdr->type), hdr_sz);
}
return -EINVAL;
}
return rc;
}
/**
* wil_request_firmware - Request firmware
*
* Request firmware image from the file
* If load is true, load firmware to device, otherwise
* only parse and extract capabilities
*
* Return error code
*/
int wil_request_firmware(struct wil6210_priv *wil, const char *name,
bool load)
{
int rc, rc1;
const struct firmware *fw;
size_t sz;
const void *d;
rc = request_firmware(&fw, name, wil_to_dev(wil));
if (rc) {
wil_err_fw(wil, "Failed to load firmware %s rc %d\n", name, rc);
return rc;
}
wil_dbg_fw(wil, "Loading <%s>, %zu bytes\n", name, fw->size);
/* re-initialize board info params */
wil->num_of_brd_entries = 0;
kfree(wil->brd_info);
wil->brd_info = NULL;
for (sz = fw->size, d = fw->data; sz; sz -= rc1, d += rc1) {
rc1 = wil_fw_verify(wil, d, sz);
if (rc1 < 0) {
rc = rc1;
goto out;
}
rc = wil_fw_process(wil, d, rc1, load);
if (rc < 0)
goto out;
}
out:
release_firmware(fw);
if (rc)
wil_err_fw(wil, "Loading <%s> failed, rc %d\n", name, rc);
return rc;
}
/**
* wil_brd_process - process section from BRD file
*
* Return error code
*/
static int wil_brd_process(struct wil6210_priv *wil, const void *data,
size_t size)
{
int rc = 0;
const struct wil_fw_record_head *hdr = data;
size_t s, hdr_sz = 0;
u16 type;
int i = 0;
/* Assuming the board file includes only one file header
* and one or several data records.
* Each record starts with wil_fw_record_head.
*/
if (size < sizeof(*hdr))
return -EINVAL;
s = sizeof(*hdr) + le32_to_cpu(hdr->size);
if (s > size)
return -EINVAL;
/* Skip the header record and handle the data records */
size -= s;
for (hdr = data + s;; hdr = (const void *)hdr + s, size -= s, i++) {
if (size < sizeof(*hdr))
break;
if (i >= wil->num_of_brd_entries) {
wil_err_fw(wil,
"Too many brd records: %d, num of expected entries %d\n",
i, wil->num_of_brd_entries);
break;
}
hdr_sz = le32_to_cpu(hdr->size);
s = sizeof(*hdr) + hdr_sz;
if (wil->brd_info[i].file_max_size &&
hdr_sz > wil->brd_info[i].file_max_size)
return -EINVAL;
if (sizeof(*hdr) + hdr_sz > size)
return -EINVAL;
if (hdr_sz % 4) {
wil_err_fw(wil, "unaligned record size: %zu\n",
hdr_sz);
return -EINVAL;
}
type = le16_to_cpu(hdr->type);
if (type != wil_fw_type_data) {
wil_err_fw(wil,
"invalid record type for board file: %d\n",
type);
return -EINVAL;
}
if (hdr_sz < sizeof(struct wil_fw_record_data)) {
wil_err_fw(wil, "data record too short: %zu\n", hdr_sz);
return -EINVAL;
}
wil_dbg_fw(wil,
"using info from fw file for record %d: addr[0x%08x], max size %d\n",
i, wil->brd_info[i].file_addr,
wil->brd_info[i].file_max_size);
rc = __fw_handle_data(wil, &hdr[1], hdr_sz,
cpu_to_le32(wil->brd_info[i].file_addr));
if (rc)
return rc;
}
if (size) {
wil_err_fw(wil, "unprocessed bytes: %zu\n", size);
if (size >= sizeof(*hdr)) {
wil_err_fw(wil,
"Stop at offset %ld record type %d [%zd bytes]\n",
(long)((const void *)hdr - data),
le16_to_cpu(hdr->type), hdr_sz);
}
return -EINVAL;
}
return 0;
}
/**
* wil_request_board - Request board file
*
* Request board image from the file
* board file address and max size are read from FW file
* during initialization.
* brd file shall include one header and one data section.
*
* Return error code
*/
int wil_request_board(struct wil6210_priv *wil, const char *name)
{
int rc, dlen;
const struct firmware *brd;
rc = request_firmware(&brd, name, wil_to_dev(wil));
if (rc) {
wil_err_fw(wil, "Failed to load brd %s\n", name);
return rc;
}
wil_dbg_fw(wil, "Loading <%s>, %zu bytes\n", name, brd->size);
/* Verify the header */
dlen = wil_fw_verify(wil, brd->data, brd->size);
if (dlen < 0) {
rc = dlen;
goto out;
}
/* Process the data records */
rc = wil_brd_process(wil, brd->data, dlen);
out:
release_firmware(brd);
if (rc)
wil_err_fw(wil, "Loading <%s> failed, rc %d\n", name, rc);
return rc;
}
/**
* wil_fw_verify_file_exists - checks if firmware file exist
*
* @wil: driver context
* @name: firmware file name
*
* return value - boolean, true for success, false for failure
*/
bool wil_fw_verify_file_exists(struct wil6210_priv *wil, const char *name)
{
const struct firmware *fw;
int rc;
rc = request_firmware(&fw, name, wil_to_dev(wil));
if (!rc)
release_firmware(fw);
else
wil_dbg_fw(wil, "<%s> not available: %d\n", name, rc);
return !rc;
}
@@ -0,0 +1,915 @@
// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2012-2017 Qualcomm Atheros, Inc.
* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
*/
#include <linux/interrupt.h>
#include "wil6210.h"
#include "trace.h"
/*
* Theory of operation:
*
* There is ISR pseudo-cause register,
* dma_rgf->DMA_RGF.PSEUDO_CAUSE.PSEUDO_CAUSE
* Its bits represents OR'ed bits from 3 real ISR registers:
* TX, RX, and MISC.
*
* Registers may be configured to either "write 1 to clear" or
* "clear on read" mode
*
* When handling interrupt, one have to mask/unmask interrupts for the
* real ISR registers, or hardware may malfunction.
*
*/
#define WIL6210_IRQ_DISABLE (0xFFFFFFFFUL)
#define WIL6210_IRQ_DISABLE_NO_HALP (0xF7FFFFFFUL)
#define WIL6210_IMC_RX (BIT_DMA_EP_RX_ICR_RX_DONE | \
BIT_DMA_EP_RX_ICR_RX_HTRSH)
#define WIL6210_IMC_RX_NO_RX_HTRSH (WIL6210_IMC_RX & \
(~(BIT_DMA_EP_RX_ICR_RX_HTRSH)))
#define WIL6210_IMC_TX (BIT_DMA_EP_TX_ICR_TX_DONE | \
BIT_DMA_EP_TX_ICR_TX_DONE_N(0))
#define WIL6210_IMC_TX_EDMA BIT_TX_STATUS_IRQ
#define WIL6210_IMC_RX_EDMA BIT_RX_STATUS_IRQ
#define WIL6210_IMC_MISC_NO_HALP (ISR_MISC_FW_READY | \
ISR_MISC_MBOX_EVT | \
ISR_MISC_FW_ERROR)
#define WIL6210_IMC_MISC (WIL6210_IMC_MISC_NO_HALP | \
BIT_DMA_EP_MISC_ICR_HALP)
#define WIL6210_IRQ_PSEUDO_MASK (u32)(~(BIT_DMA_PSEUDO_CAUSE_RX | \
BIT_DMA_PSEUDO_CAUSE_TX | \
BIT_DMA_PSEUDO_CAUSE_MISC))
#if defined(CONFIG_WIL6210_ISR_COR)
/* configure to Clear-On-Read mode */
#define WIL_ICR_ICC_VALUE (0xFFFFFFFFUL)
#define WIL_ICR_ICC_MISC_VALUE (0xF7FFFFFFUL)
static inline void wil_icr_clear(u32 x, void __iomem *addr)
{
}
#else /* defined(CONFIG_WIL6210_ISR_COR) */
/* configure to Write-1-to-Clear mode */
#define WIL_ICR_ICC_VALUE (0UL)
#define WIL_ICR_ICC_MISC_VALUE (0UL)
static inline void wil_icr_clear(u32 x, void __iomem *addr)
{
writel(x, addr);
}
#endif /* defined(CONFIG_WIL6210_ISR_COR) */
static inline u32 wil_ioread32_and_clear(void __iomem *addr)
{
u32 x = readl(addr);
wil_icr_clear(x, addr);
return x;
}
static void wil6210_mask_irq_tx(struct wil6210_priv *wil)
{
wil_w(wil, RGF_DMA_EP_TX_ICR + offsetof(struct RGF_ICR, IMS),
WIL6210_IRQ_DISABLE);
}
static void wil6210_mask_irq_tx_edma(struct wil6210_priv *wil)
{
wil_w(wil, RGF_INT_GEN_TX_ICR + offsetof(struct RGF_ICR, IMS),
WIL6210_IRQ_DISABLE);
}
static void wil6210_mask_irq_rx(struct wil6210_priv *wil)
{
wil_w(wil, RGF_DMA_EP_RX_ICR + offsetof(struct RGF_ICR, IMS),
WIL6210_IRQ_DISABLE);
}
static void wil6210_mask_irq_rx_edma(struct wil6210_priv *wil)
{
wil_w(wil, RGF_INT_GEN_RX_ICR + offsetof(struct RGF_ICR, IMS),
WIL6210_IRQ_DISABLE);
}
static void wil6210_mask_irq_misc(struct wil6210_priv *wil, bool mask_halp)
{
wil_dbg_irq(wil, "mask_irq_misc: mask_halp(%s)\n",
mask_halp ? "true" : "false");
wil_w(wil, RGF_DMA_EP_MISC_ICR + offsetof(struct RGF_ICR, IMS),
mask_halp ? WIL6210_IRQ_DISABLE : WIL6210_IRQ_DISABLE_NO_HALP);
}
void wil6210_mask_halp(struct wil6210_priv *wil)
{
wil_dbg_irq(wil, "mask_halp\n");
wil_w(wil, RGF_DMA_EP_MISC_ICR + offsetof(struct RGF_ICR, IMS),
BIT_DMA_EP_MISC_ICR_HALP);
}
static void wil6210_mask_irq_pseudo(struct wil6210_priv *wil)
{
wil_dbg_irq(wil, "mask_irq_pseudo\n");
wil_w(wil, RGF_DMA_PSEUDO_CAUSE_MASK_SW, WIL6210_IRQ_DISABLE);
clear_bit(wil_status_irqen, wil->status);
}
void wil6210_unmask_irq_tx(struct wil6210_priv *wil)
{
wil_w(wil, RGF_DMA_EP_TX_ICR + offsetof(struct RGF_ICR, IMC),
WIL6210_IMC_TX);
}
void wil6210_unmask_irq_tx_edma(struct wil6210_priv *wil)
{
wil_w(wil, RGF_INT_GEN_TX_ICR + offsetof(struct RGF_ICR, IMC),
WIL6210_IMC_TX_EDMA);
}
void wil6210_unmask_irq_rx(struct wil6210_priv *wil)
{
bool unmask_rx_htrsh = atomic_read(&wil->connected_vifs) > 0;
wil_w(wil, RGF_DMA_EP_RX_ICR + offsetof(struct RGF_ICR, IMC),
unmask_rx_htrsh ? WIL6210_IMC_RX : WIL6210_IMC_RX_NO_RX_HTRSH);
}
void wil6210_unmask_irq_rx_edma(struct wil6210_priv *wil)
{
wil_w(wil, RGF_INT_GEN_RX_ICR + offsetof(struct RGF_ICR, IMC),
WIL6210_IMC_RX_EDMA);
}
static void wil6210_unmask_irq_misc(struct wil6210_priv *wil, bool unmask_halp)
{
wil_dbg_irq(wil, "unmask_irq_misc: unmask_halp(%s)\n",
unmask_halp ? "true" : "false");
wil_w(wil, RGF_DMA_EP_MISC_ICR + offsetof(struct RGF_ICR, IMC),
unmask_halp ? WIL6210_IMC_MISC : WIL6210_IMC_MISC_NO_HALP);
}
static void wil6210_unmask_halp(struct wil6210_priv *wil)
{
wil_dbg_irq(wil, "unmask_halp\n");
wil_w(wil, RGF_DMA_EP_MISC_ICR + offsetof(struct RGF_ICR, IMC),
BIT_DMA_EP_MISC_ICR_HALP);
}
static void wil6210_unmask_irq_pseudo(struct wil6210_priv *wil)
{
wil_dbg_irq(wil, "unmask_irq_pseudo\n");
set_bit(wil_status_irqen, wil->status);
wil_w(wil, RGF_DMA_PSEUDO_CAUSE_MASK_SW, WIL6210_IRQ_PSEUDO_MASK);
}
void wil_mask_irq(struct wil6210_priv *wil)
{
wil_dbg_irq(wil, "mask_irq\n");
wil6210_mask_irq_tx(wil);
if (wil->use_enhanced_dma_hw)
wil6210_mask_irq_tx_edma(wil);
wil6210_mask_irq_rx(wil);
if (wil->use_enhanced_dma_hw)
wil6210_mask_irq_rx_edma(wil);
wil6210_mask_irq_misc(wil, true);
wil6210_mask_irq_pseudo(wil);
}
void wil_unmask_irq(struct wil6210_priv *wil)
{
wil_dbg_irq(wil, "unmask_irq\n");
if (wil->use_enhanced_dma_hw) {
wil_w(wil, RGF_DMA_EP_RX_ICR + offsetof(struct RGF_ICR, ICC),
WIL_ICR_ICC_VALUE);
wil_w(wil, RGF_DMA_EP_TX_ICR + offsetof(struct RGF_ICR, ICC),
WIL_ICR_ICC_VALUE);
}
wil_w(wil, RGF_DMA_EP_MISC_ICR + offsetof(struct RGF_ICR, ICC),
WIL_ICR_ICC_MISC_VALUE);
wil_w(wil, RGF_INT_GEN_TX_ICR + offsetof(struct RGF_ICR, ICC),
WIL_ICR_ICC_VALUE);
wil_w(wil, RGF_INT_GEN_RX_ICR + offsetof(struct RGF_ICR, ICC),
WIL_ICR_ICC_VALUE);
wil6210_unmask_irq_pseudo(wil);
if (wil->use_enhanced_dma_hw) {
wil6210_unmask_irq_tx_edma(wil);
wil6210_unmask_irq_rx_edma(wil);
} else {
wil6210_unmask_irq_tx(wil);
wil6210_unmask_irq_rx(wil);
}
wil6210_unmask_irq_misc(wil, true);
}
void wil_configure_interrupt_moderation_edma(struct wil6210_priv *wil)
{
u32 moderation;
wil_s(wil, RGF_INT_GEN_IDLE_TIME_LIMIT, WIL_EDMA_IDLE_TIME_LIMIT_USEC);
wil_s(wil, RGF_INT_GEN_TIME_UNIT_LIMIT, WIL_EDMA_TIME_UNIT_CLK_CYCLES);
/* Update RX and TX moderation */
moderation = wil->rx_max_burst_duration |
(WIL_EDMA_AGG_WATERMARK << WIL_EDMA_AGG_WATERMARK_POS);
wil_w(wil, RGF_INT_CTRL_INT_GEN_CFG_0, moderation);
wil_w(wil, RGF_INT_CTRL_INT_GEN_CFG_1, moderation);
/* Treat special events as regular
* (set bit 0 to 0x1 and clear bits 1-8)
*/
wil_c(wil, RGF_INT_COUNT_ON_SPECIAL_EVT, 0x1FE);
wil_s(wil, RGF_INT_COUNT_ON_SPECIAL_EVT, 0x1);
}
void wil_configure_interrupt_moderation(struct wil6210_priv *wil)
{
struct wireless_dev *wdev = wil->main_ndev->ieee80211_ptr;
wil_dbg_irq(wil, "configure_interrupt_moderation\n");
/* disable interrupt moderation for monitor
* to get better timestamp precision
*/
if (wdev->iftype == NL80211_IFTYPE_MONITOR)
return;
/* Disable and clear tx counter before (re)configuration */
wil_w(wil, RGF_DMA_ITR_TX_CNT_CTL, BIT_DMA_ITR_TX_CNT_CTL_CLR);
wil_w(wil, RGF_DMA_ITR_TX_CNT_TRSH, wil->tx_max_burst_duration);
wil_info(wil, "set ITR_TX_CNT_TRSH = %d usec\n",
wil->tx_max_burst_duration);
/* Configure TX max burst duration timer to use usec units */
wil_w(wil, RGF_DMA_ITR_TX_CNT_CTL,
BIT_DMA_ITR_TX_CNT_CTL_EN | BIT_DMA_ITR_TX_CNT_CTL_EXT_TIC_SEL);
/* Disable and clear tx idle counter before (re)configuration */
wil_w(wil, RGF_DMA_ITR_TX_IDL_CNT_CTL, BIT_DMA_ITR_TX_IDL_CNT_CTL_CLR);
wil_w(wil, RGF_DMA_ITR_TX_IDL_CNT_TRSH, wil->tx_interframe_timeout);
wil_info(wil, "set ITR_TX_IDL_CNT_TRSH = %d usec\n",
wil->tx_interframe_timeout);
/* Configure TX max burst duration timer to use usec units */
wil_w(wil, RGF_DMA_ITR_TX_IDL_CNT_CTL, BIT_DMA_ITR_TX_IDL_CNT_CTL_EN |
BIT_DMA_ITR_TX_IDL_CNT_CTL_EXT_TIC_SEL);
/* Disable and clear rx counter before (re)configuration */
wil_w(wil, RGF_DMA_ITR_RX_CNT_CTL, BIT_DMA_ITR_RX_CNT_CTL_CLR);
wil_w(wil, RGF_DMA_ITR_RX_CNT_TRSH, wil->rx_max_burst_duration);
wil_info(wil, "set ITR_RX_CNT_TRSH = %d usec\n",
wil->rx_max_burst_duration);
/* Configure TX max burst duration timer to use usec units */
wil_w(wil, RGF_DMA_ITR_RX_CNT_CTL,
BIT_DMA_ITR_RX_CNT_CTL_EN | BIT_DMA_ITR_RX_CNT_CTL_EXT_TIC_SEL);
/* Disable and clear rx idle counter before (re)configuration */
wil_w(wil, RGF_DMA_ITR_RX_IDL_CNT_CTL, BIT_DMA_ITR_RX_IDL_CNT_CTL_CLR);
wil_w(wil, RGF_DMA_ITR_RX_IDL_CNT_TRSH, wil->rx_interframe_timeout);
wil_info(wil, "set ITR_RX_IDL_CNT_TRSH = %d usec\n",
wil->rx_interframe_timeout);
/* Configure TX max burst duration timer to use usec units */
wil_w(wil, RGF_DMA_ITR_RX_IDL_CNT_CTL, BIT_DMA_ITR_RX_IDL_CNT_CTL_EN |
BIT_DMA_ITR_RX_IDL_CNT_CTL_EXT_TIC_SEL);
}
static irqreturn_t wil6210_irq_rx(int irq, void *cookie)
{
struct wil6210_priv *wil = cookie;
u32 isr;
bool need_unmask = true;
wil6210_mask_irq_rx(wil);
isr = wil_ioread32_and_clear(wil->csr +
HOSTADDR(RGF_DMA_EP_RX_ICR) +
offsetof(struct RGF_ICR, ICR));
trace_wil6210_irq_rx(isr);
wil_dbg_irq(wil, "ISR RX 0x%08x\n", isr);
if (unlikely(!isr)) {
wil_err_ratelimited(wil, "spurious IRQ: RX\n");
wil6210_unmask_irq_rx(wil);
return IRQ_NONE;
}
/* RX_DONE and RX_HTRSH interrupts are the same if interrupt
* moderation is not used. Interrupt moderation may cause RX
* buffer overflow while RX_DONE is delayed. The required
* action is always the same - should empty the accumulated
* packets from the RX ring.
*/
if (likely(isr & (BIT_DMA_EP_RX_ICR_RX_DONE |
BIT_DMA_EP_RX_ICR_RX_HTRSH))) {
wil_dbg_irq(wil, "RX done / RX_HTRSH received, ISR (0x%x)\n",
isr);
isr &= ~(BIT_DMA_EP_RX_ICR_RX_DONE |
BIT_DMA_EP_RX_ICR_RX_HTRSH);
if (likely(test_bit(wil_status_fwready, wil->status))) {
if (likely(test_bit(wil_status_napi_en, wil->status))) {
wil_dbg_txrx(wil, "NAPI(Rx) schedule\n");
need_unmask = false;
napi_schedule(&wil->napi_rx);
} else {
wil_err_ratelimited(
wil,
"Got Rx interrupt while stopping interface\n");
}
} else {
wil_err_ratelimited(wil, "Got Rx interrupt while in reset\n");
}
}
if (unlikely(isr))
wil_err(wil, "un-handled RX ISR bits 0x%08x\n", isr);
/* Rx IRQ will be enabled when NAPI processing finished */
atomic_inc(&wil->isr_count_rx);
if (unlikely(need_unmask))
wil6210_unmask_irq_rx(wil);
return IRQ_HANDLED;
}
static irqreturn_t wil6210_irq_rx_edma(int irq, void *cookie)
{
struct wil6210_priv *wil = cookie;
u32 isr;
bool need_unmask = true;
wil6210_mask_irq_rx_edma(wil);
isr = wil_ioread32_and_clear(wil->csr +
HOSTADDR(RGF_INT_GEN_RX_ICR) +
offsetof(struct RGF_ICR, ICR));
trace_wil6210_irq_rx(isr);
wil_dbg_irq(wil, "ISR RX 0x%08x\n", isr);
if (unlikely(!isr)) {
wil_err(wil, "spurious IRQ: RX\n");
wil6210_unmask_irq_rx_edma(wil);
return IRQ_NONE;
}
if (likely(isr & BIT_RX_STATUS_IRQ)) {
wil_dbg_irq(wil, "RX status ring\n");
isr &= ~BIT_RX_STATUS_IRQ;
if (likely(test_bit(wil_status_fwready, wil->status))) {
if (likely(test_bit(wil_status_napi_en, wil->status))) {
wil_dbg_txrx(wil, "NAPI(Rx) schedule\n");
need_unmask = false;
napi_schedule(&wil->napi_rx);
} else {
wil_err(wil,
"Got Rx interrupt while stopping interface\n");
}
} else {
wil_err(wil, "Got Rx interrupt while in reset\n");
}
}
if (unlikely(isr))
wil_err(wil, "un-handled RX ISR bits 0x%08x\n", isr);
/* Rx IRQ will be enabled when NAPI processing finished */
atomic_inc(&wil->isr_count_rx);
if (unlikely(need_unmask))
wil6210_unmask_irq_rx_edma(wil);
return IRQ_HANDLED;
}
static irqreturn_t wil6210_irq_tx_edma(int irq, void *cookie)
{
struct wil6210_priv *wil = cookie;
u32 isr;
bool need_unmask = true;
wil6210_mask_irq_tx_edma(wil);
isr = wil_ioread32_and_clear(wil->csr +
HOSTADDR(RGF_INT_GEN_TX_ICR) +
offsetof(struct RGF_ICR, ICR));
trace_wil6210_irq_tx(isr);
wil_dbg_irq(wil, "ISR TX 0x%08x\n", isr);
if (unlikely(!isr)) {
wil_err(wil, "spurious IRQ: TX\n");
wil6210_unmask_irq_tx_edma(wil);
return IRQ_NONE;
}
if (likely(isr & BIT_TX_STATUS_IRQ)) {
wil_dbg_irq(wil, "TX status ring\n");
isr &= ~BIT_TX_STATUS_IRQ;
if (likely(test_bit(wil_status_fwready, wil->status))) {
wil_dbg_txrx(wil, "NAPI(Tx) schedule\n");
need_unmask = false;
napi_schedule(&wil->napi_tx);
} else {
wil_err(wil, "Got Tx status ring IRQ while in reset\n");
}
}
if (unlikely(isr))
wil_err(wil, "un-handled TX ISR bits 0x%08x\n", isr);
/* Tx IRQ will be enabled when NAPI processing finished */
atomic_inc(&wil->isr_count_tx);
if (unlikely(need_unmask))
wil6210_unmask_irq_tx_edma(wil);
return IRQ_HANDLED;
}
static irqreturn_t wil6210_irq_tx(int irq, void *cookie)
{
struct wil6210_priv *wil = cookie;
u32 isr;
bool need_unmask = true;
wil6210_mask_irq_tx(wil);
isr = wil_ioread32_and_clear(wil->csr +
HOSTADDR(RGF_DMA_EP_TX_ICR) +
offsetof(struct RGF_ICR, ICR));
trace_wil6210_irq_tx(isr);
wil_dbg_irq(wil, "ISR TX 0x%08x\n", isr);
if (unlikely(!isr)) {
wil_err_ratelimited(wil, "spurious IRQ: TX\n");
wil6210_unmask_irq_tx(wil);
return IRQ_NONE;
}
if (likely(isr & BIT_DMA_EP_TX_ICR_TX_DONE)) {
wil_dbg_irq(wil, "TX done\n");
isr &= ~BIT_DMA_EP_TX_ICR_TX_DONE;
/* clear also all VRING interrupts */
isr &= ~(BIT(25) - 1UL);
if (likely(test_bit(wil_status_fwready, wil->status))) {
wil_dbg_txrx(wil, "NAPI(Tx) schedule\n");
need_unmask = false;
napi_schedule(&wil->napi_tx);
} else {
wil_err_ratelimited(wil, "Got Tx interrupt while in reset\n");
}
}
if (unlikely(isr))
wil_err_ratelimited(wil, "un-handled TX ISR bits 0x%08x\n",
isr);
/* Tx IRQ will be enabled when NAPI processing finished */
atomic_inc(&wil->isr_count_tx);
if (unlikely(need_unmask))
wil6210_unmask_irq_tx(wil);
return IRQ_HANDLED;
}
static void wil_notify_fw_error(struct wil6210_priv *wil)
{
struct device *dev = &wil->main_ndev->dev;
char *envp[3] = {
[0] = "SOURCE=wil6210",
[1] = "EVENT=FW_ERROR",
[2] = NULL,
};
wil_err(wil, "Notify about firmware error\n");
kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
}
static void wil_cache_mbox_regs(struct wil6210_priv *wil)
{
/* make shadow copy of registers that should not change on run time */
wil_memcpy_fromio_32(&wil->mbox_ctl, wil->csr + HOST_MBOX,
sizeof(struct wil6210_mbox_ctl));
wil_mbox_ring_le2cpus(&wil->mbox_ctl.rx);
wil_mbox_ring_le2cpus(&wil->mbox_ctl.tx);
}
static bool wil_validate_mbox_regs(struct wil6210_priv *wil)
{
size_t min_size = sizeof(struct wil6210_mbox_hdr) +
sizeof(struct wmi_cmd_hdr);
if (wil->mbox_ctl.rx.entry_size < min_size) {
wil_err(wil, "rx mbox entry too small (%d)\n",
wil->mbox_ctl.rx.entry_size);
return false;
}
if (wil->mbox_ctl.tx.entry_size < min_size) {
wil_err(wil, "tx mbox entry too small (%d)\n",
wil->mbox_ctl.tx.entry_size);
return false;
}
return true;
}
static irqreturn_t wil6210_irq_misc(int irq, void *cookie)
{
struct wil6210_priv *wil = cookie;
u32 isr;
wil6210_mask_irq_misc(wil, false);
isr = wil_ioread32_and_clear(wil->csr +
HOSTADDR(RGF_DMA_EP_MISC_ICR) +
offsetof(struct RGF_ICR, ICR));
trace_wil6210_irq_misc(isr);
wil_dbg_irq(wil, "ISR MISC 0x%08x\n", isr);
if (!isr) {
wil_err(wil, "spurious IRQ: MISC\n");
wil6210_unmask_irq_misc(wil, false);
return IRQ_NONE;
}
if (isr & ISR_MISC_FW_ERROR) {
u32 fw_assert_code = wil_r(wil, wil->rgf_fw_assert_code_addr);
u32 ucode_assert_code =
wil_r(wil, wil->rgf_ucode_assert_code_addr);
wil_err(wil,
"Firmware error detected, assert codes FW 0x%08x, UCODE 0x%08x\n",
fw_assert_code, ucode_assert_code);
clear_bit(wil_status_fwready, wil->status);
/*
* do not clear @isr here - we do 2-nd part in thread
* there, user space get notified, and it should be done
* in non-atomic context
*/
}
if (isr & ISR_MISC_FW_READY) {
wil_dbg_irq(wil, "IRQ: FW ready\n");
wil_cache_mbox_regs(wil);
if (wil_validate_mbox_regs(wil))
set_bit(wil_status_mbox_ready, wil->status);
/**
* Actual FW ready indicated by the
* WMI_FW_READY_EVENTID
*/
isr &= ~ISR_MISC_FW_READY;
}
if (isr & BIT_DMA_EP_MISC_ICR_HALP) {
isr &= ~BIT_DMA_EP_MISC_ICR_HALP;
if (wil->halp.handle_icr) {
/* no need to handle HALP ICRs until next vote */
wil->halp.handle_icr = false;
wil_dbg_irq(wil, "irq_misc: HALP IRQ invoked\n");
wil6210_mask_irq_misc(wil, true);
complete(&wil->halp.comp);
}
}
wil->isr_misc = isr;
if (isr) {
return IRQ_WAKE_THREAD;
} else {
wil6210_unmask_irq_misc(wil, false);
return IRQ_HANDLED;
}
}
static irqreturn_t wil6210_irq_misc_thread(int irq, void *cookie)
{
struct wil6210_priv *wil = cookie;
u32 isr = wil->isr_misc;
trace_wil6210_irq_misc_thread(isr);
wil_dbg_irq(wil, "Thread ISR MISC 0x%08x\n", isr);
if (isr & ISR_MISC_FW_ERROR) {
wil->recovery_state = fw_recovery_pending;
wil_fw_core_dump(wil);
wil_notify_fw_error(wil);
isr &= ~ISR_MISC_FW_ERROR;
if (wil->platform_ops.notify) {
wil_err(wil, "notify platform driver about FW crash");
wil->platform_ops.notify(wil->platform_handle,
WIL_PLATFORM_EVT_FW_CRASH);
} else {
wil_fw_error_recovery(wil);
}
}
if (isr & ISR_MISC_MBOX_EVT) {
wil_dbg_irq(wil, "MBOX event\n");
wmi_recv_cmd(wil);
isr &= ~ISR_MISC_MBOX_EVT;
}
if (isr)
wil_dbg_irq(wil, "un-handled MISC ISR bits 0x%08x\n", isr);
wil->isr_misc = 0;
wil6210_unmask_irq_misc(wil, false);
/* in non-triple MSI case, this is done inside wil6210_thread_irq
* because it has to be done after unmasking the pseudo.
*/
if (wil->n_msi == 3 && wil->suspend_resp_rcvd) {
wil_dbg_irq(wil, "set suspend_resp_comp to true\n");
wil->suspend_resp_comp = true;
wake_up_interruptible(&wil->wq);
}
return IRQ_HANDLED;
}
/* thread IRQ handler */
static irqreturn_t wil6210_thread_irq(int irq, void *cookie)
{
struct wil6210_priv *wil = cookie;
wil_dbg_irq(wil, "Thread IRQ\n");
/* Discover real IRQ cause */
if (wil->isr_misc)
wil6210_irq_misc_thread(irq, cookie);
wil6210_unmask_irq_pseudo(wil);
if (wil->suspend_resp_rcvd) {
wil_dbg_irq(wil, "set suspend_resp_comp to true\n");
wil->suspend_resp_comp = true;
wake_up_interruptible(&wil->wq);
}
return IRQ_HANDLED;
}
/* DEBUG
* There is subtle bug in hardware that causes IRQ to raise when it should be
* masked. It is quite rare and hard to debug.
*
* Catch irq issue if it happens and print all I can.
*/
static int wil6210_debug_irq_mask(struct wil6210_priv *wil, u32 pseudo_cause)
{
u32 icm_rx, icr_rx, imv_rx;
u32 icm_tx, icr_tx, imv_tx;
u32 icm_misc, icr_misc, imv_misc;
if (!test_bit(wil_status_irqen, wil->status)) {
if (wil->use_enhanced_dma_hw) {
icm_rx = wil_ioread32_and_clear(wil->csr +
HOSTADDR(RGF_INT_GEN_RX_ICR) +
offsetof(struct RGF_ICR, ICM));
icr_rx = wil_ioread32_and_clear(wil->csr +
HOSTADDR(RGF_INT_GEN_RX_ICR) +
offsetof(struct RGF_ICR, ICR));
imv_rx = wil_r(wil, RGF_INT_GEN_RX_ICR +
offsetof(struct RGF_ICR, IMV));
icm_tx = wil_ioread32_and_clear(wil->csr +
HOSTADDR(RGF_INT_GEN_TX_ICR) +
offsetof(struct RGF_ICR, ICM));
icr_tx = wil_ioread32_and_clear(wil->csr +
HOSTADDR(RGF_INT_GEN_TX_ICR) +
offsetof(struct RGF_ICR, ICR));
imv_tx = wil_r(wil, RGF_INT_GEN_TX_ICR +
offsetof(struct RGF_ICR, IMV));
} else {
icm_rx = wil_ioread32_and_clear(wil->csr +
HOSTADDR(RGF_DMA_EP_RX_ICR) +
offsetof(struct RGF_ICR, ICM));
icr_rx = wil_ioread32_and_clear(wil->csr +
HOSTADDR(RGF_DMA_EP_RX_ICR) +
offsetof(struct RGF_ICR, ICR));
imv_rx = wil_r(wil, RGF_DMA_EP_RX_ICR +
offsetof(struct RGF_ICR, IMV));
icm_tx = wil_ioread32_and_clear(wil->csr +
HOSTADDR(RGF_DMA_EP_TX_ICR) +
offsetof(struct RGF_ICR, ICM));
icr_tx = wil_ioread32_and_clear(wil->csr +
HOSTADDR(RGF_DMA_EP_TX_ICR) +
offsetof(struct RGF_ICR, ICR));
imv_tx = wil_r(wil, RGF_DMA_EP_TX_ICR +
offsetof(struct RGF_ICR, IMV));
}
icm_misc = wil_ioread32_and_clear(wil->csr +
HOSTADDR(RGF_DMA_EP_MISC_ICR) +
offsetof(struct RGF_ICR, ICM));
icr_misc = wil_ioread32_and_clear(wil->csr +
HOSTADDR(RGF_DMA_EP_MISC_ICR) +
offsetof(struct RGF_ICR, ICR));
imv_misc = wil_r(wil, RGF_DMA_EP_MISC_ICR +
offsetof(struct RGF_ICR, IMV));
/* HALP interrupt can be unmasked when misc interrupts are
* masked
*/
if (icr_misc & BIT_DMA_EP_MISC_ICR_HALP)
return 0;
wil_err(wil, "IRQ when it should be masked: pseudo 0x%08x\n"
"Rx icm:icr:imv 0x%08x 0x%08x 0x%08x\n"
"Tx icm:icr:imv 0x%08x 0x%08x 0x%08x\n"
"Misc icm:icr:imv 0x%08x 0x%08x 0x%08x\n",
pseudo_cause,
icm_rx, icr_rx, imv_rx,
icm_tx, icr_tx, imv_tx,
icm_misc, icr_misc, imv_misc);
return -EINVAL;
}
return 0;
}
static irqreturn_t wil6210_hardirq(int irq, void *cookie)
{
irqreturn_t rc = IRQ_HANDLED;
struct wil6210_priv *wil = cookie;
u32 pseudo_cause = wil_r(wil, RGF_DMA_PSEUDO_CAUSE);
/**
* pseudo_cause is Clear-On-Read, no need to ACK
*/
if (unlikely((pseudo_cause == 0) || ((pseudo_cause & 0xff) == 0xff)))
return IRQ_NONE;
/* IRQ mask debug */
if (unlikely(wil6210_debug_irq_mask(wil, pseudo_cause)))
return IRQ_NONE;
trace_wil6210_irq_pseudo(pseudo_cause);
wil_dbg_irq(wil, "Pseudo IRQ 0x%08x\n", pseudo_cause);
wil6210_mask_irq_pseudo(wil);
/* Discover real IRQ cause
* There are 2 possible phases for every IRQ:
* - hard IRQ handler called right here
* - threaded handler called later
*
* Hard IRQ handler reads and clears ISR.
*
* If threaded handler requested, hard IRQ handler
* returns IRQ_WAKE_THREAD and saves ISR register value
* for the threaded handler use.
*
* voting for wake thread - need at least 1 vote
*/
if ((pseudo_cause & BIT_DMA_PSEUDO_CAUSE_RX) &&
(wil->txrx_ops.irq_rx(irq, cookie) == IRQ_WAKE_THREAD))
rc = IRQ_WAKE_THREAD;
if ((pseudo_cause & BIT_DMA_PSEUDO_CAUSE_TX) &&
(wil->txrx_ops.irq_tx(irq, cookie) == IRQ_WAKE_THREAD))
rc = IRQ_WAKE_THREAD;
if ((pseudo_cause & BIT_DMA_PSEUDO_CAUSE_MISC) &&
(wil6210_irq_misc(irq, cookie) == IRQ_WAKE_THREAD))
rc = IRQ_WAKE_THREAD;
/* if thread is requested, it will unmask IRQ */
if (rc != IRQ_WAKE_THREAD)
wil6210_unmask_irq_pseudo(wil);
return rc;
}
static int wil6210_request_3msi(struct wil6210_priv *wil, int irq)
{
int rc;
/* IRQ's are in the following order:
* - Tx
* - Rx
* - Misc
*/
rc = request_irq(irq, wil->txrx_ops.irq_tx, IRQF_SHARED,
WIL_NAME "_tx", wil);
if (rc)
return rc;
rc = request_irq(irq + 1, wil->txrx_ops.irq_rx, IRQF_SHARED,
WIL_NAME "_rx", wil);
if (rc)
goto free0;
rc = request_threaded_irq(irq + 2, wil6210_irq_misc,
wil6210_irq_misc_thread,
IRQF_SHARED, WIL_NAME "_misc", wil);
if (rc)
goto free1;
return 0;
free1:
free_irq(irq + 1, wil);
free0:
free_irq(irq, wil);
return rc;
}
/* can't use wil_ioread32_and_clear because ICC value is not set yet */
static inline void wil_clear32(void __iomem *addr)
{
u32 x = readl(addr);
writel(x, addr);
}
void wil6210_clear_irq(struct wil6210_priv *wil)
{
wil_clear32(wil->csr + HOSTADDR(RGF_DMA_EP_RX_ICR) +
offsetof(struct RGF_ICR, ICR));
wil_clear32(wil->csr + HOSTADDR(RGF_DMA_EP_TX_ICR) +
offsetof(struct RGF_ICR, ICR));
if (wil->use_enhanced_dma_hw) {
wil_clear32(wil->csr + HOSTADDR(RGF_INT_GEN_RX_ICR) +
offsetof(struct RGF_ICR, ICR));
wil_clear32(wil->csr + HOSTADDR(RGF_INT_GEN_TX_ICR) +
offsetof(struct RGF_ICR, ICR));
}
wil_clear32(wil->csr + HOSTADDR(RGF_DMA_EP_MISC_ICR) +
offsetof(struct RGF_ICR, ICR));
wmb(); /* make sure write completed */
}
void wil6210_set_halp(struct wil6210_priv *wil)
{
wil_dbg_irq(wil, "set_halp\n");
wil_w(wil, RGF_DMA_EP_MISC_ICR + offsetof(struct RGF_ICR, ICS),
BIT_DMA_EP_MISC_ICR_HALP);
}
void wil6210_clear_halp(struct wil6210_priv *wil)
{
wil_dbg_irq(wil, "clear_halp\n");
wil_w(wil, RGF_DMA_EP_MISC_ICR + offsetof(struct RGF_ICR, ICR),
BIT_DMA_EP_MISC_ICR_HALP);
wil6210_unmask_halp(wil);
}
int wil6210_init_irq(struct wil6210_priv *wil, int irq)
{
int rc;
wil_dbg_misc(wil, "init_irq: %s, n_msi=%d\n",
wil->n_msi ? "MSI" : "INTx", wil->n_msi);
if (wil->use_enhanced_dma_hw) {
wil->txrx_ops.irq_tx = wil6210_irq_tx_edma;
wil->txrx_ops.irq_rx = wil6210_irq_rx_edma;
} else {
wil->txrx_ops.irq_tx = wil6210_irq_tx;
wil->txrx_ops.irq_rx = wil6210_irq_rx;
}
if (wil->n_msi == 3)
rc = wil6210_request_3msi(wil, irq);
else
rc = request_threaded_irq(irq, wil6210_hardirq,
wil6210_thread_irq,
wil->n_msi ? 0 : IRQF_SHARED,
WIL_NAME, wil);
return rc;
}
void wil6210_fini_irq(struct wil6210_priv *wil, int irq)
{
wil_dbg_misc(wil, "fini_irq:\n");
wil_mask_irq(wil);
free_irq(irq, wil);
if (wil->n_msi == 3) {
free_irq(irq + 1, wil);
free_irq(irq + 2, wil);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,568 @@
// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2012-2017 Qualcomm Atheros, Inc.
* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
*/
#include <linux/etherdevice.h>
#include <linux/rtnetlink.h>
#include "wil6210.h"
#include "txrx.h"
bool wil_has_other_active_ifaces(struct wil6210_priv *wil,
struct net_device *ndev, bool up, bool ok)
{
int i;
struct wil6210_vif *vif;
struct net_device *ndev_i;
for (i = 0; i < GET_MAX_VIFS(wil); i++) {
vif = wil->vifs[i];
if (vif) {
ndev_i = vif_to_ndev(vif);
if (ndev_i != ndev)
if ((up && (ndev_i->flags & IFF_UP)) ||
(ok && netif_carrier_ok(ndev_i)))
return true;
}
}
return false;
}
bool wil_has_active_ifaces(struct wil6210_priv *wil, bool up, bool ok)
{
/* use NULL ndev argument to check all interfaces */
return wil_has_other_active_ifaces(wil, NULL, up, ok);
}
static int wil_open(struct net_device *ndev)
{
struct wil6210_priv *wil = ndev_to_wil(ndev);
int rc = 0;
wil_dbg_misc(wil, "open\n");
if (debug_fw ||
test_bit(WMI_FW_CAPABILITY_WMI_ONLY, wil->fw_capabilities)) {
wil_err(wil, "while in debug_fw or wmi_only mode\n");
return -EINVAL;
}
if (!wil_has_other_active_ifaces(wil, ndev, true, false)) {
wil_dbg_misc(wil, "open, first iface\n");
rc = wil_pm_runtime_get(wil);
if (rc < 0)
return rc;
rc = wil_up(wil);
if (rc)
wil_pm_runtime_put(wil);
}
return rc;
}
static int wil_stop(struct net_device *ndev)
{
struct wil6210_priv *wil = ndev_to_wil(ndev);
int rc = 0;
wil_dbg_misc(wil, "stop\n");
if (!wil_has_other_active_ifaces(wil, ndev, true, false)) {
wil_dbg_misc(wil, "stop, last iface\n");
rc = wil_down(wil);
if (!rc)
wil_pm_runtime_put(wil);
}
return rc;
}
static const struct net_device_ops wil_netdev_ops = {
.ndo_open = wil_open,
.ndo_stop = wil_stop,
.ndo_start_xmit = wil_start_xmit,
.ndo_set_mac_address = eth_mac_addr,
.ndo_validate_addr = eth_validate_addr,
};
static int wil6210_netdev_poll_rx(struct napi_struct *napi, int budget)
{
struct wil6210_priv *wil = container_of(napi, struct wil6210_priv,
napi_rx);
int quota = budget;
int done;
wil_rx_handle(wil, &quota);
done = budget - quota;
if (done < budget) {
napi_complete_done(napi, done);
wil6210_unmask_irq_rx(wil);
wil_dbg_txrx(wil, "NAPI RX complete\n");
}
wil_dbg_txrx(wil, "NAPI RX poll(%d) done %d\n", budget, done);
return done;
}
static int wil6210_netdev_poll_rx_edma(struct napi_struct *napi, int budget)
{
struct wil6210_priv *wil = container_of(napi, struct wil6210_priv,
napi_rx);
int quota = budget;
int done;
wil_rx_handle_edma(wil, &quota);
done = budget - quota;
if (done < budget) {
napi_complete_done(napi, done);
wil6210_unmask_irq_rx_edma(wil);
wil_dbg_txrx(wil, "NAPI RX complete\n");
}
wil_dbg_txrx(wil, "NAPI RX poll(%d) done %d\n", budget, done);
return done;
}
static int wil6210_netdev_poll_tx(struct napi_struct *napi, int budget)
{
struct wil6210_priv *wil = container_of(napi, struct wil6210_priv,
napi_tx);
int tx_done = 0;
uint i;
/* always process ALL Tx complete, regardless budget - it is fast */
for (i = 0; i < WIL6210_MAX_TX_RINGS; i++) {
struct wil_ring *ring = &wil->ring_tx[i];
struct wil_ring_tx_data *txdata = &wil->ring_tx_data[i];
struct wil6210_vif *vif;
if (!ring->va || !txdata->enabled ||
txdata->mid >= GET_MAX_VIFS(wil))
continue;
vif = wil->vifs[txdata->mid];
if (unlikely(!vif)) {
wil_dbg_txrx(wil, "Invalid MID %d\n", txdata->mid);
continue;
}
tx_done += wil_tx_complete(vif, i);
}
if (tx_done < budget) {
napi_complete(napi);
wil6210_unmask_irq_tx(wil);
wil_dbg_txrx(wil, "NAPI TX complete\n");
}
wil_dbg_txrx(wil, "NAPI TX poll(%d) done %d\n", budget, tx_done);
return min(tx_done, budget);
}
static int wil6210_netdev_poll_tx_edma(struct napi_struct *napi, int budget)
{
struct wil6210_priv *wil = container_of(napi, struct wil6210_priv,
napi_tx);
int tx_done;
/* There is only one status TX ring */
struct wil_status_ring *sring = &wil->srings[wil->tx_sring_idx];
if (!sring->va)
return 0;
tx_done = wil_tx_sring_handler(wil, sring);
if (tx_done < budget) {
napi_complete(napi);
wil6210_unmask_irq_tx_edma(wil);
wil_dbg_txrx(wil, "NAPI TX complete\n");
}
wil_dbg_txrx(wil, "NAPI TX poll(%d) done %d\n", budget, tx_done);
return min(tx_done, budget);
}
static void wil_dev_setup(struct net_device *dev)
{
ether_setup(dev);
dev->max_mtu = mtu_max;
dev->tx_queue_len = WIL_TX_Q_LEN_DEFAULT;
}
static void wil_vif_deinit(struct wil6210_vif *vif)
{
timer_delete_sync(&vif->scan_timer);
timer_delete_sync(&vif->p2p.discovery_timer);
cancel_work_sync(&vif->disconnect_worker);
cancel_work_sync(&vif->p2p.discovery_expired_work);
cancel_work_sync(&vif->p2p.delayed_listen_work);
wil_probe_client_flush(vif);
cancel_work_sync(&vif->probe_client_worker);
cancel_work_sync(&vif->enable_tx_key_worker);
}
void wil_vif_free(struct wil6210_vif *vif)
{
struct net_device *ndev = vif_to_ndev(vif);
wil_vif_deinit(vif);
free_netdev(ndev);
}
static void wil_ndev_destructor(struct net_device *ndev)
{
struct wil6210_vif *vif = ndev_to_vif(ndev);
wil_vif_deinit(vif);
}
static void wil_connect_timer_fn(struct timer_list *t)
{
struct wil6210_vif *vif = timer_container_of(vif, t, connect_timer);
struct wil6210_priv *wil = vif_to_wil(vif);
bool q;
wil_err(wil, "Connect timeout detected, disconnect station\n");
/* reschedule to thread context - disconnect won't
* run from atomic context.
* queue on wmi_wq to prevent race with connect event.
*/
q = queue_work(wil->wmi_wq, &vif->disconnect_worker);
wil_dbg_wmi(wil, "queue_work of disconnect_worker -> %d\n", q);
}
static void wil_scan_timer_fn(struct timer_list *t)
{
struct wil6210_vif *vif = timer_container_of(vif, t, scan_timer);
struct wil6210_priv *wil = vif_to_wil(vif);
clear_bit(wil_status_fwready, wil->status);
wil_err(wil, "Scan timeout detected, start fw error recovery\n");
wil_fw_error_recovery(wil);
}
static void wil_p2p_discovery_timer_fn(struct timer_list *t)
{
struct wil6210_vif *vif = timer_container_of(vif, t,
p2p.discovery_timer);
struct wil6210_priv *wil = vif_to_wil(vif);
wil_dbg_misc(wil, "p2p_discovery_timer_fn\n");
schedule_work(&vif->p2p.discovery_expired_work);
}
static void wil_vif_init(struct wil6210_vif *vif)
{
vif->bcast_ring = -1;
mutex_init(&vif->probe_client_mutex);
timer_setup(&vif->connect_timer, wil_connect_timer_fn, 0);
timer_setup(&vif->scan_timer, wil_scan_timer_fn, 0);
timer_setup(&vif->p2p.discovery_timer, wil_p2p_discovery_timer_fn, 0);
INIT_WORK(&vif->probe_client_worker, wil_probe_client_worker);
INIT_WORK(&vif->disconnect_worker, wil_disconnect_worker);
INIT_WORK(&vif->p2p.discovery_expired_work, wil_p2p_listen_expired);
INIT_WORK(&vif->p2p.delayed_listen_work, wil_p2p_delayed_listen_work);
INIT_WORK(&vif->enable_tx_key_worker, wil_enable_tx_key_worker);
INIT_LIST_HEAD(&vif->probe_client_pending);
vif->net_queue_stopped = 1;
}
static u8 wil_vif_find_free_mid(struct wil6210_priv *wil)
{
u8 i;
for (i = 0; i < GET_MAX_VIFS(wil); i++) {
if (!wil->vifs[i])
return i;
}
return U8_MAX;
}
struct wil6210_vif *
wil_vif_alloc(struct wil6210_priv *wil, const char *name,
unsigned char name_assign_type, enum nl80211_iftype iftype)
{
struct net_device *ndev;
struct wireless_dev *wdev;
struct wil6210_vif *vif;
u8 mid;
mid = wil_vif_find_free_mid(wil);
if (mid == U8_MAX) {
wil_err(wil, "no available virtual interface\n");
return ERR_PTR(-EINVAL);
}
ndev = alloc_netdev(sizeof(*vif), name, name_assign_type,
wil_dev_setup);
if (!ndev) {
dev_err(wil_to_dev(wil), "alloc_netdev failed\n");
return ERR_PTR(-ENOMEM);
}
if (mid == 0) {
wil->main_ndev = ndev;
} else {
ndev->priv_destructor = wil_ndev_destructor;
ndev->needs_free_netdev = true;
}
vif = ndev_to_vif(ndev);
vif->ndev = ndev;
vif->wil = wil;
vif->mid = mid;
wil_vif_init(vif);
wdev = &vif->wdev;
wdev->wiphy = wil->wiphy;
wdev->iftype = iftype;
ndev->netdev_ops = &wil_netdev_ops;
wil_set_ethtoolops(ndev);
ndev->ieee80211_ptr = wdev;
ndev->hw_features = NETIF_F_HW_CSUM | NETIF_F_RXCSUM |
NETIF_F_SG | NETIF_F_GRO |
NETIF_F_TSO | NETIF_F_TSO6;
ndev->features |= ndev->hw_features;
SET_NETDEV_DEV(ndev, wiphy_dev(wdev->wiphy));
wdev->netdev = ndev;
return vif;
}
void *wil_if_alloc(struct device *dev)
{
struct wil6210_priv *wil;
struct wil6210_vif *vif;
int rc = 0;
wil = wil_cfg80211_init(dev);
if (IS_ERR(wil)) {
dev_err(dev, "wil_cfg80211_init failed\n");
return wil;
}
rc = wil_priv_init(wil);
if (rc) {
dev_err(dev, "wil_priv_init failed\n");
goto out_cfg;
}
wil_dbg_misc(wil, "if_alloc\n");
vif = wil_vif_alloc(wil, "wlan%d", NET_NAME_UNKNOWN,
NL80211_IFTYPE_STATION);
if (IS_ERR(vif)) {
dev_err(dev, "wil_vif_alloc failed\n");
rc = -ENOMEM;
goto out_priv;
}
wil->radio_wdev = vif_to_wdev(vif);
return wil;
out_priv:
wil_priv_deinit(wil);
out_cfg:
wil_cfg80211_deinit(wil);
return ERR_PTR(rc);
}
void wil_if_free(struct wil6210_priv *wil)
{
struct net_device *ndev = wil->main_ndev;
wil_dbg_misc(wil, "if_free\n");
if (!ndev)
return;
wil_priv_deinit(wil);
wil->main_ndev = NULL;
wil_ndev_destructor(ndev);
free_netdev(ndev);
wil_cfg80211_deinit(wil);
}
int wil_vif_add(struct wil6210_priv *wil, struct wil6210_vif *vif)
{
struct net_device *ndev = vif_to_ndev(vif);
struct wireless_dev *wdev = vif_to_wdev(vif);
bool any_active = wil_has_active_ifaces(wil, true, false);
int rc;
ASSERT_RTNL();
if (wil->vifs[vif->mid]) {
dev_err(&ndev->dev, "VIF with mid %d already in use\n",
vif->mid);
return -EEXIST;
}
if (any_active && vif->mid != 0) {
rc = wmi_port_allocate(wil, vif->mid, ndev->dev_addr,
wdev->iftype);
if (rc)
return rc;
}
rc = cfg80211_register_netdevice(ndev);
if (rc < 0) {
dev_err(&ndev->dev, "Failed to register netdev: %d\n", rc);
if (any_active && vif->mid != 0)
wmi_port_delete(wil, vif->mid);
return rc;
}
wil->vifs[vif->mid] = vif;
return 0;
}
int wil_if_add(struct wil6210_priv *wil)
{
struct wiphy *wiphy = wil->wiphy;
struct net_device *ndev = wil->main_ndev;
struct wil6210_vif *vif = ndev_to_vif(ndev);
int rc;
wil_dbg_misc(wil, "entered");
strscpy(wiphy->fw_version, wil->fw_version, sizeof(wiphy->fw_version));
rc = wiphy_register(wiphy);
if (rc < 0) {
wil_err(wil, "failed to register wiphy, err %d\n", rc);
return rc;
}
wil->napi_ndev = alloc_netdev_dummy(0);
if (!wil->napi_ndev) {
wil_err(wil, "failed to allocate dummy netdev");
rc = -ENOMEM;
goto out_wiphy;
}
if (wil->use_enhanced_dma_hw) {
netif_napi_add(wil->napi_ndev, &wil->napi_rx,
wil6210_netdev_poll_rx_edma);
netif_napi_add_tx(wil->napi_ndev,
&wil->napi_tx, wil6210_netdev_poll_tx_edma);
} else {
netif_napi_add(wil->napi_ndev, &wil->napi_rx,
wil6210_netdev_poll_rx);
netif_napi_add_tx(wil->napi_ndev,
&wil->napi_tx, wil6210_netdev_poll_tx);
}
wil_update_net_queues_bh(wil, vif, NULL, true);
rtnl_lock();
wiphy_lock(wiphy);
rc = wil_vif_add(wil, vif);
wiphy_unlock(wiphy);
rtnl_unlock();
if (rc < 0)
goto free_dummy;
return 0;
free_dummy:
free_netdev(wil->napi_ndev);
out_wiphy:
wiphy_unregister(wiphy);
return rc;
}
void wil_vif_remove(struct wil6210_priv *wil, u8 mid)
{
struct wil6210_vif *vif;
struct net_device *ndev;
bool any_active = wil_has_active_ifaces(wil, true, false);
ASSERT_RTNL();
if (mid >= GET_MAX_VIFS(wil)) {
wil_err(wil, "invalid MID: %d\n", mid);
return;
}
vif = wil->vifs[mid];
if (!vif) {
wil_err(wil, "MID %d not registered\n", mid);
return;
}
mutex_lock(&wil->mutex);
wil6210_disconnect(vif, NULL, WLAN_REASON_DEAUTH_LEAVING);
mutex_unlock(&wil->mutex);
ndev = vif_to_ndev(vif);
/* during unregister_netdevice cfg80211_leave may perform operations
* such as stop AP, disconnect, so we only clear the VIF afterwards
*/
cfg80211_unregister_netdevice(ndev);
if (any_active && vif->mid != 0)
wmi_port_delete(wil, vif->mid);
/* make sure no one is accessing the VIF before removing */
mutex_lock(&wil->vif_mutex);
wil->vifs[mid] = NULL;
/* ensure NAPI code will see the NULL VIF */
wmb();
if (test_bit(wil_status_napi_en, wil->status)) {
napi_synchronize(&wil->napi_rx);
napi_synchronize(&wil->napi_tx);
}
mutex_unlock(&wil->vif_mutex);
flush_work(&wil->wmi_event_worker);
timer_delete_sync(&vif->connect_timer);
cancel_work_sync(&vif->disconnect_worker);
wil_probe_client_flush(vif);
cancel_work_sync(&vif->probe_client_worker);
cancel_work_sync(&vif->enable_tx_key_worker);
/* for VIFs, ndev will be freed by destructor after RTNL is unlocked.
* the main interface will be freed in wil_if_free, we need to keep it
* a bit longer so logging macros will work.
*/
}
void wil_if_remove(struct wil6210_priv *wil)
{
struct net_device *ndev = wil->main_ndev;
struct wireless_dev *wdev = ndev->ieee80211_ptr;
struct wiphy *wiphy = wdev->wiphy;
wil_dbg_misc(wil, "if_remove\n");
rtnl_lock();
wiphy_lock(wiphy);
wil_vif_remove(wil, 0);
wiphy_unlock(wiphy);
rtnl_unlock();
netif_napi_del(&wil->napi_tx);
netif_napi_del(&wil->napi_rx);
free_netdev(wil->napi_ndev);
wiphy_unregister(wiphy);
}
@@ -0,0 +1,379 @@
// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2014-2017 Qualcomm Atheros, Inc.
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
*/
#include "wil6210.h"
#include "wmi.h"
#define P2P_WILDCARD_SSID "DIRECT-"
#define P2P_DMG_SOCIAL_CHANNEL 2
#define P2P_SEARCH_DURATION_MS 500
#define P2P_DEFAULT_BI 100
static int wil_p2p_start_listen(struct wil6210_vif *vif)
{
struct wil6210_priv *wil = vif_to_wil(vif);
struct wil_p2p_info *p2p = &vif->p2p;
u8 channel = p2p->listen_chan.hw_value;
int rc;
lockdep_assert_held(&wil->mutex);
rc = wmi_p2p_cfg(vif, channel, P2P_DEFAULT_BI);
if (rc) {
wil_err(wil, "wmi_p2p_cfg failed\n");
goto out;
}
rc = wmi_set_ssid(vif, strlen(P2P_WILDCARD_SSID), P2P_WILDCARD_SSID);
if (rc) {
wil_err(wil, "wmi_set_ssid failed\n");
goto out_stop;
}
rc = wmi_start_listen(vif);
if (rc) {
wil_err(wil, "wmi_start_listen failed\n");
goto out_stop;
}
INIT_WORK(&p2p->discovery_expired_work, wil_p2p_listen_expired);
mod_timer(&p2p->discovery_timer,
jiffies + msecs_to_jiffies(p2p->listen_duration));
out_stop:
if (rc)
wmi_stop_discovery(vif);
out:
return rc;
}
bool wil_p2p_is_social_scan(struct cfg80211_scan_request *request)
{
return (request->n_channels == 1) &&
(request->channels[0]->hw_value == P2P_DMG_SOCIAL_CHANNEL);
}
int wil_p2p_search(struct wil6210_vif *vif,
struct cfg80211_scan_request *request)
{
struct wil6210_priv *wil = vif_to_wil(vif);
int rc;
struct wil_p2p_info *p2p = &vif->p2p;
wil_dbg_misc(wil, "p2p_search: channel %d\n", P2P_DMG_SOCIAL_CHANNEL);
lockdep_assert_held(&wil->mutex);
if (p2p->discovery_started) {
wil_err(wil, "search failed. discovery already ongoing\n");
rc = -EBUSY;
goto out;
}
rc = wmi_p2p_cfg(vif, P2P_DMG_SOCIAL_CHANNEL, P2P_DEFAULT_BI);
if (rc) {
wil_err(wil, "wmi_p2p_cfg failed\n");
goto out;
}
rc = wmi_set_ssid(vif, strlen(P2P_WILDCARD_SSID), P2P_WILDCARD_SSID);
if (rc) {
wil_err(wil, "wmi_set_ssid failed\n");
goto out_stop;
}
/* Set application IE to probe request and probe response */
rc = wmi_set_ie(vif, WMI_FRAME_PROBE_REQ,
request->ie_len, request->ie);
if (rc) {
wil_err(wil, "wmi_set_ie(WMI_FRAME_PROBE_REQ) failed\n");
goto out_stop;
}
/* supplicant doesn't provide Probe Response IEs. As a workaround -
* re-use Probe Request IEs
*/
rc = wmi_set_ie(vif, WMI_FRAME_PROBE_RESP,
request->ie_len, request->ie);
if (rc) {
wil_err(wil, "wmi_set_ie(WMI_FRAME_PROBE_RESP) failed\n");
goto out_stop;
}
rc = wmi_start_search(vif);
if (rc) {
wil_err(wil, "wmi_start_search failed\n");
goto out_stop;
}
p2p->discovery_started = 1;
INIT_WORK(&p2p->discovery_expired_work, wil_p2p_search_expired);
mod_timer(&p2p->discovery_timer,
jiffies + msecs_to_jiffies(P2P_SEARCH_DURATION_MS));
out_stop:
if (rc)
wmi_stop_discovery(vif);
out:
return rc;
}
int wil_p2p_listen(struct wil6210_priv *wil, struct wireless_dev *wdev,
unsigned int duration, struct ieee80211_channel *chan,
u64 *cookie)
{
struct wil6210_vif *vif = wdev_to_vif(wil, wdev);
struct wil_p2p_info *p2p = &vif->p2p;
int rc;
if (!chan)
return -EINVAL;
wil_dbg_misc(wil, "p2p_listen: duration %d\n", duration);
mutex_lock(&wil->mutex);
if (p2p->discovery_started) {
wil_err(wil, "discovery already ongoing\n");
rc = -EBUSY;
goto out;
}
memcpy(&p2p->listen_chan, chan, sizeof(*chan));
*cookie = ++p2p->cookie;
p2p->listen_duration = duration;
mutex_lock(&wil->vif_mutex);
if (vif->scan_request) {
wil_dbg_misc(wil, "Delaying p2p listen until scan done\n");
p2p->pending_listen_wdev = wdev;
p2p->discovery_started = 1;
rc = 0;
mutex_unlock(&wil->vif_mutex);
goto out;
}
mutex_unlock(&wil->vif_mutex);
rc = wil_p2p_start_listen(vif);
if (rc)
goto out;
p2p->discovery_started = 1;
if (vif->mid == 0)
wil->radio_wdev = wdev;
cfg80211_ready_on_channel(wdev, *cookie, chan, duration,
GFP_KERNEL);
out:
mutex_unlock(&wil->mutex);
return rc;
}
u8 wil_p2p_stop_discovery(struct wil6210_vif *vif)
{
struct wil_p2p_info *p2p = &vif->p2p;
u8 started = p2p->discovery_started;
if (p2p->discovery_started) {
if (p2p->pending_listen_wdev) {
/* discovery not really started, only pending */
p2p->pending_listen_wdev = NULL;
} else {
timer_delete_sync(&p2p->discovery_timer);
wmi_stop_discovery(vif);
}
p2p->discovery_started = 0;
}
return started;
}
int wil_p2p_cancel_listen(struct wil6210_vif *vif, u64 cookie)
{
struct wil6210_priv *wil = vif_to_wil(vif);
struct wil_p2p_info *p2p = &vif->p2p;
u8 started;
mutex_lock(&wil->mutex);
if (cookie != p2p->cookie) {
wil_info(wil, "Cookie mismatch: 0x%016llx vs. 0x%016llx\n",
p2p->cookie, cookie);
mutex_unlock(&wil->mutex);
return -ENOENT;
}
started = wil_p2p_stop_discovery(vif);
mutex_unlock(&wil->mutex);
if (!started) {
wil_err(wil, "listen not started\n");
return -ENOENT;
}
mutex_lock(&wil->vif_mutex);
cfg80211_remain_on_channel_expired(vif_to_radio_wdev(wil, vif),
p2p->cookie,
&p2p->listen_chan,
GFP_KERNEL);
if (vif->mid == 0)
wil->radio_wdev = wil->main_ndev->ieee80211_ptr;
mutex_unlock(&wil->vif_mutex);
return 0;
}
void wil_p2p_listen_expired(struct work_struct *work)
{
struct wil_p2p_info *p2p = container_of(work,
struct wil_p2p_info, discovery_expired_work);
struct wil6210_vif *vif = container_of(p2p,
struct wil6210_vif, p2p);
struct wil6210_priv *wil = vif_to_wil(vif);
u8 started;
wil_dbg_misc(wil, "p2p_listen_expired\n");
mutex_lock(&wil->mutex);
started = wil_p2p_stop_discovery(vif);
mutex_unlock(&wil->mutex);
if (!started)
return;
mutex_lock(&wil->vif_mutex);
cfg80211_remain_on_channel_expired(vif_to_radio_wdev(wil, vif),
p2p->cookie,
&p2p->listen_chan,
GFP_KERNEL);
if (vif->mid == 0)
wil->radio_wdev = wil->main_ndev->ieee80211_ptr;
mutex_unlock(&wil->vif_mutex);
}
void wil_p2p_search_expired(struct work_struct *work)
{
struct wil_p2p_info *p2p = container_of(work,
struct wil_p2p_info, discovery_expired_work);
struct wil6210_vif *vif = container_of(p2p,
struct wil6210_vif, p2p);
struct wil6210_priv *wil = vif_to_wil(vif);
u8 started;
wil_dbg_misc(wil, "p2p_search_expired\n");
mutex_lock(&wil->mutex);
started = wil_p2p_stop_discovery(vif);
mutex_unlock(&wil->mutex);
if (started) {
struct cfg80211_scan_info info = {
.aborted = false,
};
mutex_lock(&wil->vif_mutex);
if (vif->scan_request) {
cfg80211_scan_done(vif->scan_request, &info);
vif->scan_request = NULL;
if (vif->mid == 0)
wil->radio_wdev =
wil->main_ndev->ieee80211_ptr;
}
mutex_unlock(&wil->vif_mutex);
}
}
void wil_p2p_delayed_listen_work(struct work_struct *work)
{
struct wil_p2p_info *p2p = container_of(work,
struct wil_p2p_info, delayed_listen_work);
struct wil6210_vif *vif = container_of(p2p,
struct wil6210_vif, p2p);
struct wil6210_priv *wil = vif_to_wil(vif);
int rc;
mutex_lock(&wil->mutex);
wil_dbg_misc(wil, "Checking delayed p2p listen\n");
if (!p2p->discovery_started || !p2p->pending_listen_wdev)
goto out;
mutex_lock(&wil->vif_mutex);
if (vif->scan_request) {
/* another scan started, wait again... */
mutex_unlock(&wil->vif_mutex);
goto out;
}
mutex_unlock(&wil->vif_mutex);
rc = wil_p2p_start_listen(vif);
mutex_lock(&wil->vif_mutex);
if (rc) {
cfg80211_remain_on_channel_expired(p2p->pending_listen_wdev,
p2p->cookie,
&p2p->listen_chan,
GFP_KERNEL);
if (vif->mid == 0)
wil->radio_wdev = wil->main_ndev->ieee80211_ptr;
} else {
cfg80211_ready_on_channel(p2p->pending_listen_wdev, p2p->cookie,
&p2p->listen_chan,
p2p->listen_duration, GFP_KERNEL);
if (vif->mid == 0)
wil->radio_wdev = p2p->pending_listen_wdev;
}
p2p->pending_listen_wdev = NULL;
mutex_unlock(&wil->vif_mutex);
out:
mutex_unlock(&wil->mutex);
}
void wil_p2p_stop_radio_operations(struct wil6210_priv *wil)
{
struct wil6210_vif *vif = ndev_to_vif(wil->main_ndev);
struct wil_p2p_info *p2p = &vif->p2p;
struct cfg80211_scan_info info = {
.aborted = true,
};
lockdep_assert_held(&wil->mutex);
lockdep_assert_held(&wil->vif_mutex);
if (wil->radio_wdev != wil->p2p_wdev)
goto out;
if (!p2p->discovery_started) {
/* Regular scan on the p2p device */
if (vif->scan_request &&
vif->scan_request->wdev == wil->p2p_wdev)
wil_abort_scan(vif, true);
goto out;
}
/* Search or listen on p2p device */
mutex_unlock(&wil->vif_mutex);
wil_p2p_stop_discovery(vif);
mutex_lock(&wil->vif_mutex);
if (vif->scan_request) {
/* search */
cfg80211_scan_done(vif->scan_request, &info);
vif->scan_request = NULL;
} else {
/* listen */
cfg80211_remain_on_channel_expired(wil->radio_wdev,
p2p->cookie,
&p2p->listen_chan,
GFP_KERNEL);
}
out:
wil->radio_wdev = wil->main_ndev->ieee80211_ptr;
}
@@ -0,0 +1,688 @@
// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2012-2017 Qualcomm Atheros, Inc.
* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/moduleparam.h>
#include <linux/interrupt.h>
#include <linux/suspend.h>
#include "wil6210.h"
#include <linux/rtnetlink.h>
#include <linux/pm_runtime.h>
static int n_msi = 3;
module_param(n_msi, int, 0444);
MODULE_PARM_DESC(n_msi, " Use MSI interrupt: 0 - use INTx, 1 - single, or 3 - (default) ");
bool ftm_mode;
module_param(ftm_mode, bool, 0444);
MODULE_PARM_DESC(ftm_mode, " Set factory test mode, default - false");
static int wil6210_pm_notify(struct notifier_block *notify_block,
unsigned long mode, void *unused);
static
int wil_set_capabilities(struct wil6210_priv *wil)
{
const char *wil_fw_name;
u32 jtag_id = wil_r(wil, RGF_USER_JTAG_DEV_ID);
u8 chip_revision = (wil_r(wil, RGF_USER_REVISION_ID) &
RGF_USER_REVISION_ID_MASK);
int platform_capa;
struct fw_map *iccm_section, *sct;
bitmap_zero(wil->hw_capa, hw_capa_last);
bitmap_zero(wil->fw_capabilities, WMI_FW_CAPABILITY_MAX);
bitmap_zero(wil->platform_capa, WIL_PLATFORM_CAPA_MAX);
wil->wil_fw_name = ftm_mode ? WIL_FW_NAME_FTM_DEFAULT :
WIL_FW_NAME_DEFAULT;
wil->chip_revision = chip_revision;
switch (jtag_id) {
case JTAG_DEV_ID_SPARROW:
memcpy(fw_mapping, sparrow_fw_mapping,
sizeof(sparrow_fw_mapping));
switch (chip_revision) {
case REVISION_ID_SPARROW_D0:
wil->hw_name = "Sparrow D0";
wil->hw_version = HW_VER_SPARROW_D0;
wil_fw_name = ftm_mode ? WIL_FW_NAME_FTM_SPARROW_PLUS :
WIL_FW_NAME_SPARROW_PLUS;
if (wil_fw_verify_file_exists(wil, wil_fw_name))
wil->wil_fw_name = wil_fw_name;
sct = wil_find_fw_mapping("mac_rgf_ext");
if (!sct) {
wil_err(wil, "mac_rgf_ext section not found in fw_mapping\n");
return -EINVAL;
}
memcpy(sct, &sparrow_d0_mac_rgf_ext, sizeof(*sct));
break;
case REVISION_ID_SPARROW_B0:
wil->hw_name = "Sparrow B0";
wil->hw_version = HW_VER_SPARROW_B0;
break;
default:
wil->hw_name = "Unknown";
wil->hw_version = HW_VER_UNKNOWN;
break;
}
wil->rgf_fw_assert_code_addr = SPARROW_RGF_FW_ASSERT_CODE;
wil->rgf_ucode_assert_code_addr = SPARROW_RGF_UCODE_ASSERT_CODE;
break;
case JTAG_DEV_ID_TALYN:
wil->hw_name = "Talyn-MA";
wil->hw_version = HW_VER_TALYN;
memcpy(fw_mapping, talyn_fw_mapping, sizeof(talyn_fw_mapping));
wil->rgf_fw_assert_code_addr = TALYN_RGF_FW_ASSERT_CODE;
wil->rgf_ucode_assert_code_addr = TALYN_RGF_UCODE_ASSERT_CODE;
if (wil_r(wil, RGF_USER_OTP_HW_RD_MACHINE_1) &
BIT_NO_FLASH_INDICATION)
set_bit(hw_capa_no_flash, wil->hw_capa);
wil_fw_name = ftm_mode ? WIL_FW_NAME_FTM_TALYN :
WIL_FW_NAME_TALYN;
if (wil_fw_verify_file_exists(wil, wil_fw_name))
wil->wil_fw_name = wil_fw_name;
break;
case JTAG_DEV_ID_TALYN_MB:
wil->hw_name = "Talyn-MB";
wil->hw_version = HW_VER_TALYN_MB;
memcpy(fw_mapping, talyn_mb_fw_mapping,
sizeof(talyn_mb_fw_mapping));
wil->rgf_fw_assert_code_addr = TALYN_RGF_FW_ASSERT_CODE;
wil->rgf_ucode_assert_code_addr = TALYN_RGF_UCODE_ASSERT_CODE;
set_bit(hw_capa_no_flash, wil->hw_capa);
wil->use_enhanced_dma_hw = true;
wil->use_rx_hw_reordering = true;
wil->use_compressed_rx_status = true;
wil_fw_name = ftm_mode ? WIL_FW_NAME_FTM_TALYN :
WIL_FW_NAME_TALYN;
if (wil_fw_verify_file_exists(wil, wil_fw_name))
wil->wil_fw_name = wil_fw_name;
break;
default:
wil_err(wil, "Unknown board hardware, chip_id 0x%08x, chip_revision 0x%08x\n",
jtag_id, chip_revision);
wil->hw_name = "Unknown";
wil->hw_version = HW_VER_UNKNOWN;
return -EINVAL;
}
wil_init_txrx_ops(wil);
iccm_section = wil_find_fw_mapping("fw_code");
if (!iccm_section) {
wil_err(wil, "fw_code section not found in fw_mapping\n");
return -EINVAL;
}
wil->iccm_base = iccm_section->host;
wil_info(wil, "Board hardware is %s, flash %sexist\n", wil->hw_name,
test_bit(hw_capa_no_flash, wil->hw_capa) ? "doesn't " : "");
/* Get platform capabilities */
if (wil->platform_ops.get_capa) {
platform_capa =
wil->platform_ops.get_capa(wil->platform_handle);
memcpy(wil->platform_capa, &platform_capa,
min(sizeof(wil->platform_capa), sizeof(platform_capa)));
}
wil_info(wil, "platform_capa 0x%lx\n", *wil->platform_capa);
/* extract FW capabilities from file without loading the FW */
wil_request_firmware(wil, wil->wil_fw_name, false);
wil_refresh_fw_capabilities(wil);
return 0;
}
void wil_disable_irq(struct wil6210_priv *wil)
{
int irq = wil->pdev->irq;
disable_irq(irq);
if (wil->n_msi == 3) {
disable_irq(irq + 1);
disable_irq(irq + 2);
}
}
void wil_enable_irq(struct wil6210_priv *wil)
{
int irq = wil->pdev->irq;
enable_irq(irq);
if (wil->n_msi == 3) {
enable_irq(irq + 1);
enable_irq(irq + 2);
}
}
static void wil_remove_all_additional_vifs(struct wil6210_priv *wil)
{
struct wil6210_vif *vif;
int i;
for (i = 1; i < GET_MAX_VIFS(wil); i++) {
vif = wil->vifs[i];
if (vif) {
wil_vif_prepare_stop(vif);
wil_vif_remove(wil, vif->mid);
}
}
}
/* Bus ops */
static int wil_if_pcie_enable(struct wil6210_priv *wil)
{
struct pci_dev *pdev = wil->pdev;
int rc;
/* on platforms with buggy ACPI, pdev->msi_enabled may be set to
* allow pci_enable_device to work. This indicates INTx was not routed
* and only MSI should be used
*/
int msi_only = pdev->msi_enabled;
wil_dbg_misc(wil, "if_pcie_enable\n");
pci_set_master(pdev);
/* how many MSI interrupts to request? */
switch (n_msi) {
case 3:
case 1:
wil_dbg_misc(wil, "Setup %d MSI interrupts\n", n_msi);
break;
case 0:
wil_dbg_misc(wil, "MSI interrupts disabled, use INTx\n");
break;
default:
wil_err(wil, "Invalid n_msi=%d, default to 1\n", n_msi);
n_msi = 1;
}
if (n_msi == 3 &&
pci_alloc_irq_vectors(pdev, n_msi, n_msi, PCI_IRQ_MSI) < n_msi) {
wil_err(wil, "3 MSI mode failed, try 1 MSI\n");
n_msi = 1;
}
if (n_msi == 1 && pci_enable_msi(pdev)) {
wil_err(wil, "pci_enable_msi failed, use INTx\n");
n_msi = 0;
}
wil->n_msi = n_msi;
if (wil->n_msi == 0 && msi_only) {
wil_err(wil, "Interrupt pin not routed, unable to use INTx\n");
rc = -ENODEV;
goto stop_master;
}
rc = wil6210_init_irq(wil, pdev->irq);
if (rc)
goto release_vectors;
/* need reset here to obtain MAC */
mutex_lock(&wil->mutex);
rc = wil_reset(wil, false);
mutex_unlock(&wil->mutex);
if (rc)
goto release_irq;
return 0;
release_irq:
wil6210_fini_irq(wil, pdev->irq);
release_vectors:
/* safe to call if no allocation */
pci_free_irq_vectors(pdev);
stop_master:
pci_clear_master(pdev);
return rc;
}
static int wil_if_pcie_disable(struct wil6210_priv *wil)
{
struct pci_dev *pdev = wil->pdev;
wil_dbg_misc(wil, "if_pcie_disable\n");
pci_clear_master(pdev);
/* disable and release IRQ */
wil6210_fini_irq(wil, pdev->irq);
/* safe to call if no MSI */
pci_disable_msi(pdev);
/* TODO: disable HW */
return 0;
}
static int wil_platform_rop_ramdump(void *wil_handle, void *buf, uint32_t size)
{
struct wil6210_priv *wil = wil_handle;
if (!wil)
return -EINVAL;
return wil_fw_copy_crash_dump(wil, buf, size);
}
static int wil_platform_rop_fw_recovery(void *wil_handle)
{
struct wil6210_priv *wil = wil_handle;
if (!wil)
return -EINVAL;
wil_fw_error_recovery(wil);
return 0;
}
static void wil_platform_ops_uninit(struct wil6210_priv *wil)
{
if (wil->platform_ops.uninit)
wil->platform_ops.uninit(wil->platform_handle);
memset(&wil->platform_ops, 0, sizeof(wil->platform_ops));
}
static int wil_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct wil6210_priv *wil;
struct device *dev = &pdev->dev;
int rc;
const struct wil_platform_rops rops = {
.ramdump = wil_platform_rop_ramdump,
.fw_recovery = wil_platform_rop_fw_recovery,
};
u32 bar_size = pci_resource_len(pdev, 0);
int dma_addr_size[] = {64, 48, 40, 32}; /* keep descending order */
int i, start_idx;
/* check HW */
dev_info(&pdev->dev, WIL_NAME
" device found [%04x:%04x] (rev %x) bar size 0x%x\n",
(int)pdev->vendor, (int)pdev->device, (int)pdev->revision,
bar_size);
if ((bar_size < WIL6210_MIN_MEM_SIZE) ||
(bar_size > WIL6210_MAX_MEM_SIZE)) {
dev_err(&pdev->dev, "Unexpected BAR0 size 0x%x\n",
bar_size);
return -ENODEV;
}
wil = wil_if_alloc(dev);
if (IS_ERR(wil)) {
rc = (int)PTR_ERR(wil);
dev_err(dev, "wil_if_alloc failed: %d\n", rc);
return rc;
}
wil->pdev = pdev;
pci_set_drvdata(pdev, wil);
wil->bar_size = bar_size;
/* rollback to if_free */
wil->platform_handle =
wil_platform_init(&pdev->dev, &wil->platform_ops, &rops, wil);
if (!wil->platform_handle) {
rc = -ENODEV;
wil_err(wil, "wil_platform_init failed\n");
goto if_free;
}
/* rollback to err_plat */
rc = pci_enable_device(pdev);
if (rc && pdev->msi_enabled == 0) {
wil_err(wil,
"pci_enable_device failed, retry with MSI only\n");
/* Work around for platforms that can't allocate IRQ:
* retry with MSI only
*/
pdev->msi_enabled = 1;
rc = pci_enable_device(pdev);
}
if (rc) {
wil_err(wil,
"pci_enable_device failed, even with MSI only\n");
goto err_plat;
}
/* rollback to err_disable_pdev */
pci_set_power_state(pdev, PCI_D0);
rc = pci_request_region(pdev, 0, WIL_NAME);
if (rc) {
wil_err(wil, "pci_request_region failed\n");
goto err_disable_pdev;
}
/* rollback to err_release_reg */
wil->csr = pci_ioremap_bar(pdev, 0);
if (!wil->csr) {
wil_err(wil, "pci_ioremap_bar failed\n");
rc = -ENODEV;
goto err_release_reg;
}
/* rollback to err_iounmap */
wil_info(wil, "CSR at %pR -> 0x%p\n", &pdev->resource[0], wil->csr);
rc = wil_set_capabilities(wil);
if (rc) {
wil_err(wil, "wil_set_capabilities failed, rc %d\n", rc);
goto err_iounmap;
}
/* device supports >32bit addresses.
* for legacy DMA start from 48 bit.
*/
start_idx = wil->use_enhanced_dma_hw ? 0 : 1;
for (i = start_idx; i < ARRAY_SIZE(dma_addr_size); i++) {
rc = dma_set_mask_and_coherent(dev,
DMA_BIT_MASK(dma_addr_size[i]));
if (rc) {
dev_err(dev, "dma_set_mask_and_coherent(%d) failed: %d\n",
dma_addr_size[i], rc);
continue;
}
dev_info(dev, "using dma mask %d", dma_addr_size[i]);
wil->dma_addr_size = dma_addr_size[i];
break;
}
if (wil->dma_addr_size == 0)
goto err_iounmap;
wil6210_clear_irq(wil);
/* FW should raise IRQ when ready */
rc = wil_if_pcie_enable(wil);
if (rc) {
wil_err(wil, "Enable device failed\n");
goto err_iounmap;
}
/* rollback to bus_disable */
wil_clear_fw_log_addr(wil);
rc = wil_if_add(wil);
if (rc) {
wil_err(wil, "wil_if_add failed: %d\n", rc);
goto bus_disable;
}
/* in case of WMI-only FW, perform full reset and FW loading */
if (test_bit(WMI_FW_CAPABILITY_WMI_ONLY, wil->fw_capabilities)) {
wil_dbg_misc(wil, "Loading WMI only FW\n");
mutex_lock(&wil->mutex);
rc = wil_reset(wil, true);
mutex_unlock(&wil->mutex);
if (rc) {
wil_err(wil, "failed to load WMI only FW\n");
/* ignore the error to allow debugging */
}
}
if (IS_ENABLED(CONFIG_PM))
wil->pm_notify.notifier_call = wil6210_pm_notify;
rc = register_pm_notifier(&wil->pm_notify);
if (rc)
/* Do not fail the driver initialization, as suspend can
* be prevented in a later phase if needed
*/
wil_err(wil, "register_pm_notifier failed: %d\n", rc);
wil6210_debugfs_init(wil);
wil_pm_runtime_allow(wil);
return 0;
bus_disable:
wil_if_pcie_disable(wil);
err_iounmap:
pci_iounmap(pdev, wil->csr);
err_release_reg:
pci_release_region(pdev, 0);
err_disable_pdev:
pci_disable_device(pdev);
err_plat:
wil_platform_ops_uninit(wil);
if_free:
wil_if_free(wil);
return rc;
}
static void wil_pcie_remove(struct pci_dev *pdev)
{
struct wil6210_priv *wil = pci_get_drvdata(pdev);
void __iomem *csr = wil->csr;
wil_dbg_misc(wil, "pcie_remove\n");
unregister_pm_notifier(&wil->pm_notify);
wil_pm_runtime_forbid(wil);
wil6210_debugfs_remove(wil);
rtnl_lock();
wiphy_lock(wil->wiphy);
wil_p2p_wdev_free(wil);
wil_remove_all_additional_vifs(wil);
wiphy_unlock(wil->wiphy);
rtnl_unlock();
wil_if_remove(wil);
wil_if_pcie_disable(wil);
pci_iounmap(pdev, csr);
pci_release_region(pdev, 0);
pci_disable_device(pdev);
wil_platform_ops_uninit(wil);
wil_if_free(wil);
}
static const struct pci_device_id wil6210_pcie_ids[] = {
{ PCI_DEVICE(0x1ae9, 0x0310) },
{ PCI_DEVICE(0x1ae9, 0x0302) }, /* same as above, firmware broken */
{ PCI_DEVICE(0x17cb, 0x1201) }, /* Talyn */
{ /* end: all zeroes */ },
};
MODULE_DEVICE_TABLE(pci, wil6210_pcie_ids);
static int wil6210_suspend(struct device *dev, bool is_runtime)
{
int rc = 0;
struct pci_dev *pdev = to_pci_dev(dev);
struct wil6210_priv *wil = pci_get_drvdata(pdev);
bool keep_radio_on, active_ifaces;
wil_dbg_pm(wil, "suspend: %s\n", is_runtime ? "runtime" : "system");
mutex_lock(&wil->vif_mutex);
active_ifaces = wil_has_active_ifaces(wil, true, false);
mutex_unlock(&wil->vif_mutex);
keep_radio_on = active_ifaces && wil->keep_radio_on_during_sleep;
rc = wil_can_suspend(wil, is_runtime);
if (rc)
goto out;
rc = wil_suspend(wil, is_runtime, keep_radio_on);
if (!rc) {
/* In case radio stays on, platform device will control
* PCIe master
*/
if (!keep_radio_on) {
/* disable bus mastering */
pci_clear_master(pdev);
wil->suspend_stats.r_off.successful_suspends++;
} else {
wil->suspend_stats.r_on.successful_suspends++;
}
}
out:
return rc;
}
static int wil6210_resume(struct device *dev, bool is_runtime)
{
int rc = 0;
struct pci_dev *pdev = to_pci_dev(dev);
struct wil6210_priv *wil = pci_get_drvdata(pdev);
bool keep_radio_on, active_ifaces;
wil_dbg_pm(wil, "resume: %s\n", is_runtime ? "runtime" : "system");
mutex_lock(&wil->vif_mutex);
active_ifaces = wil_has_active_ifaces(wil, true, false);
mutex_unlock(&wil->vif_mutex);
keep_radio_on = active_ifaces && wil->keep_radio_on_during_sleep;
/* In case radio stays on, platform device will control
* PCIe master
*/
if (!keep_radio_on)
/* allow master */
pci_set_master(pdev);
rc = wil_resume(wil, is_runtime, keep_radio_on);
if (rc) {
wil_err(wil, "device failed to resume (%d)\n", rc);
if (!keep_radio_on) {
pci_clear_master(pdev);
wil->suspend_stats.r_off.failed_resumes++;
} else {
wil->suspend_stats.r_on.failed_resumes++;
}
} else {
if (keep_radio_on)
wil->suspend_stats.r_on.successful_resumes++;
else
wil->suspend_stats.r_off.successful_resumes++;
}
return rc;
}
static int wil6210_pm_notify(struct notifier_block *notify_block,
unsigned long mode, void *unused)
{
struct wil6210_priv *wil = container_of(
notify_block, struct wil6210_priv, pm_notify);
int rc = 0;
enum wil_platform_event evt;
wil_dbg_pm(wil, "pm_notify: mode (%ld)\n", mode);
switch (mode) {
case PM_HIBERNATION_PREPARE:
case PM_SUSPEND_PREPARE:
case PM_RESTORE_PREPARE:
rc = wil_can_suspend(wil, false);
if (rc)
break;
evt = WIL_PLATFORM_EVT_PRE_SUSPEND;
if (wil->platform_ops.notify)
rc = wil->platform_ops.notify(wil->platform_handle,
evt);
break;
case PM_POST_SUSPEND:
case PM_POST_HIBERNATION:
case PM_POST_RESTORE:
evt = WIL_PLATFORM_EVT_POST_SUSPEND;
if (wil->platform_ops.notify)
rc = wil->platform_ops.notify(wil->platform_handle,
evt);
break;
default:
wil_dbg_pm(wil, "unhandled notify mode %ld\n", mode);
break;
}
wil_dbg_pm(wil, "notification mode %ld: rc (%d)\n", mode, rc);
return rc;
}
static int __maybe_unused wil6210_pm_suspend(struct device *dev)
{
return wil6210_suspend(dev, false);
}
static int __maybe_unused wil6210_pm_resume(struct device *dev)
{
return wil6210_resume(dev, false);
}
static int __maybe_unused wil6210_pm_runtime_idle(struct device *dev)
{
struct wil6210_priv *wil = dev_get_drvdata(dev);
wil_dbg_pm(wil, "Runtime idle\n");
return wil_can_suspend(wil, true);
}
static int __maybe_unused wil6210_pm_runtime_resume(struct device *dev)
{
return wil6210_resume(dev, true);
}
static int __maybe_unused wil6210_pm_runtime_suspend(struct device *dev)
{
struct wil6210_priv *wil = dev_get_drvdata(dev);
if (test_bit(wil_status_suspended, wil->status)) {
wil_dbg_pm(wil, "trying to suspend while suspended\n");
return 1;
}
return wil6210_suspend(dev, true);
}
static const struct dev_pm_ops wil6210_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(wil6210_pm_suspend, wil6210_pm_resume)
SET_RUNTIME_PM_OPS(wil6210_pm_runtime_suspend,
wil6210_pm_runtime_resume,
wil6210_pm_runtime_idle)
};
static struct pci_driver wil6210_driver = {
.probe = wil_pcie_probe,
.remove = wil_pcie_remove,
.id_table = wil6210_pcie_ids,
.name = WIL_NAME,
.driver = {
.pm = &wil6210_pm_ops,
},
};
static int __init wil6210_driver_init(void)
{
int rc;
rc = wil_platform_modinit();
if (rc)
return rc;
rc = pci_register_driver(&wil6210_driver);
if (rc)
wil_platform_modexit();
return rc;
}
module_init(wil6210_driver_init);
static void __exit wil6210_driver_exit(void)
{
pci_unregister_driver(&wil6210_driver);
wil_platform_modexit();
}
module_exit(wil6210_driver_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Qualcomm Atheros <wil6210@qca.qualcomm.com>");
MODULE_DESCRIPTION("Driver for 60g WiFi WIL6210 card");
@@ -0,0 +1,462 @@
// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2014,2017 Qualcomm Atheros, Inc.
* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
*/
#include "wil6210.h"
#include <linux/jiffies.h>
#include <linux/pm_runtime.h>
#define WIL6210_AUTOSUSPEND_DELAY_MS (1000)
static void wil_pm_wake_connected_net_queues(struct wil6210_priv *wil)
{
int i;
mutex_lock(&wil->vif_mutex);
for (i = 0; i < GET_MAX_VIFS(wil); i++) {
struct wil6210_vif *vif = wil->vifs[i];
if (vif && test_bit(wil_vif_fwconnected, vif->status))
wil_update_net_queues_bh(wil, vif, NULL, false);
}
mutex_unlock(&wil->vif_mutex);
}
static void wil_pm_stop_all_net_queues(struct wil6210_priv *wil)
{
int i;
mutex_lock(&wil->vif_mutex);
for (i = 0; i < GET_MAX_VIFS(wil); i++) {
struct wil6210_vif *vif = wil->vifs[i];
if (vif)
wil_update_net_queues_bh(wil, vif, NULL, true);
}
mutex_unlock(&wil->vif_mutex);
}
static bool
wil_can_suspend_vif(struct wil6210_priv *wil, struct wil6210_vif *vif,
bool is_runtime)
{
struct wireless_dev *wdev = vif_to_wdev(vif);
switch (wdev->iftype) {
case NL80211_IFTYPE_MONITOR:
wil_dbg_pm(wil, "Sniffer\n");
return false;
/* for STA-like interface, don't runtime suspend */
case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_P2P_CLIENT:
if (test_bit(wil_vif_fwconnecting, vif->status)) {
wil_dbg_pm(wil, "Delay suspend when connecting\n");
return false;
}
if (is_runtime) {
wil_dbg_pm(wil, "STA-like interface\n");
return false;
}
break;
/* AP-like interface - can't suspend */
default:
wil_dbg_pm(wil, "AP-like interface\n");
return false;
}
return true;
}
int wil_can_suspend(struct wil6210_priv *wil, bool is_runtime)
{
int rc = 0, i;
bool wmi_only = test_bit(WMI_FW_CAPABILITY_WMI_ONLY,
wil->fw_capabilities);
bool active_ifaces;
wil_dbg_pm(wil, "can_suspend: %s\n", is_runtime ? "runtime" : "system");
if (wmi_only || debug_fw) {
wil_dbg_pm(wil, "Deny any suspend - %s mode\n",
wmi_only ? "wmi_only" : "debug_fw");
rc = -EBUSY;
goto out;
}
if (is_runtime && !wil->platform_ops.suspend) {
rc = -EBUSY;
goto out;
}
mutex_lock(&wil->vif_mutex);
active_ifaces = wil_has_active_ifaces(wil, true, false);
mutex_unlock(&wil->vif_mutex);
if (!active_ifaces) {
/* can always sleep when down */
wil_dbg_pm(wil, "Interface is down\n");
goto out;
}
if (test_bit(wil_status_resetting, wil->status)) {
wil_dbg_pm(wil, "Delay suspend when resetting\n");
rc = -EBUSY;
goto out;
}
if (wil->recovery_state != fw_recovery_idle) {
wil_dbg_pm(wil, "Delay suspend during recovery\n");
rc = -EBUSY;
goto out;
}
/* interface is running */
mutex_lock(&wil->vif_mutex);
for (i = 0; i < GET_MAX_VIFS(wil); i++) {
struct wil6210_vif *vif = wil->vifs[i];
if (!vif)
continue;
if (!wil_can_suspend_vif(wil, vif, is_runtime)) {
rc = -EBUSY;
mutex_unlock(&wil->vif_mutex);
goto out;
}
}
mutex_unlock(&wil->vif_mutex);
out:
wil_dbg_pm(wil, "can_suspend: %s => %s (%d)\n",
is_runtime ? "runtime" : "system", rc ? "No" : "Yes", rc);
if (rc)
wil->suspend_stats.rejected_by_host++;
return rc;
}
static int wil_resume_keep_radio_on(struct wil6210_priv *wil)
{
int rc = 0;
/* wil_status_resuming will be cleared when getting
* WMI_TRAFFIC_RESUME_EVENTID
*/
set_bit(wil_status_resuming, wil->status);
clear_bit(wil_status_suspended, wil->status);
wil_c(wil, RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD);
wil_unmask_irq(wil);
wil6210_bus_request(wil, wil->bus_request_kbps_pre_suspend);
/* Send WMI resume request to the device */
rc = wmi_resume(wil);
if (rc) {
wil_err(wil, "device failed to resume (%d)\n", rc);
if (no_fw_recovery)
goto out;
rc = wil_down(wil);
if (rc) {
wil_err(wil, "wil_down failed (%d)\n", rc);
goto out;
}
rc = wil_up(wil);
if (rc) {
wil_err(wil, "wil_up failed (%d)\n", rc);
goto out;
}
}
/* Wake all queues */
wil_pm_wake_connected_net_queues(wil);
out:
if (rc)
set_bit(wil_status_suspended, wil->status);
return rc;
}
static int wil_suspend_keep_radio_on(struct wil6210_priv *wil)
{
int rc = 0;
unsigned long data_comp_to;
wil_dbg_pm(wil, "suspend keep radio on\n");
/* Prevent handling of new tx and wmi commands */
rc = down_write_trylock(&wil->mem_lock);
if (!rc) {
wil_err(wil,
"device is busy. down_write_trylock failed, returned (0x%x)\n",
rc);
wil->suspend_stats.rejected_by_host++;
return -EBUSY;
}
set_bit(wil_status_suspending, wil->status);
up_write(&wil->mem_lock);
wil_pm_stop_all_net_queues(wil);
if (!wil_is_tx_idle(wil)) {
wil_dbg_pm(wil, "Pending TX data, reject suspend\n");
wil->suspend_stats.rejected_by_host++;
goto reject_suspend;
}
if (!wil->txrx_ops.is_rx_idle(wil)) {
wil_dbg_pm(wil, "Pending RX data, reject suspend\n");
wil->suspend_stats.rejected_by_host++;
goto reject_suspend;
}
if (!wil_is_wmi_idle(wil)) {
wil_dbg_pm(wil, "Pending WMI events, reject suspend\n");
wil->suspend_stats.rejected_by_host++;
goto reject_suspend;
}
/* Send WMI suspend request to the device */
rc = wmi_suspend(wil);
if (rc) {
wil_dbg_pm(wil, "wmi_suspend failed, reject suspend (%d)\n",
rc);
goto reject_suspend;
}
/* Wait for completion of the pending RX packets */
data_comp_to = jiffies + msecs_to_jiffies(WIL_DATA_COMPLETION_TO_MS);
if (test_bit(wil_status_napi_en, wil->status)) {
while (!wil->txrx_ops.is_rx_idle(wil)) {
if (time_after(jiffies, data_comp_to)) {
if (wil->txrx_ops.is_rx_idle(wil))
break;
wil_err(wil,
"TO waiting for idle RX, suspend failed\n");
wil->suspend_stats.r_on.failed_suspends++;
goto resume_after_fail;
}
wil_dbg_ratelimited(wil, "rx vring is not empty -> NAPI\n");
napi_synchronize(&wil->napi_rx);
msleep(20);
}
}
/* In case of pending WMI events, reject the suspend
* and resume the device.
* This can happen if the device sent the WMI events before
* approving the suspend.
*/
if (!wil_is_wmi_idle(wil)) {
wil_err(wil, "suspend failed due to pending WMI events\n");
wil->suspend_stats.r_on.failed_suspends++;
goto resume_after_fail;
}
wil_mask_irq(wil);
/* Disable device reset on PERST */
wil_s(wil, RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD);
if (wil->platform_ops.suspend) {
rc = wil->platform_ops.suspend(wil->platform_handle, true);
if (rc) {
wil_err(wil, "platform device failed to suspend (%d)\n",
rc);
wil->suspend_stats.r_on.failed_suspends++;
wil_c(wil, RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD);
wil_unmask_irq(wil);
goto resume_after_fail;
}
}
/* Save the current bus request to return to the same in resume */
wil->bus_request_kbps_pre_suspend = wil->bus_request_kbps;
wil6210_bus_request(wil, 0);
set_bit(wil_status_suspended, wil->status);
clear_bit(wil_status_suspending, wil->status);
return rc;
resume_after_fail:
set_bit(wil_status_resuming, wil->status);
clear_bit(wil_status_suspending, wil->status);
rc = wmi_resume(wil);
/* if resume succeeded, reject the suspend */
if (!rc) {
rc = -EBUSY;
wil_pm_wake_connected_net_queues(wil);
}
return rc;
reject_suspend:
clear_bit(wil_status_suspending, wil->status);
wil_pm_wake_connected_net_queues(wil);
return -EBUSY;
}
static int wil_suspend_radio_off(struct wil6210_priv *wil)
{
int rc = 0;
bool active_ifaces;
wil_dbg_pm(wil, "suspend radio off\n");
rc = down_write_trylock(&wil->mem_lock);
if (!rc) {
wil_err(wil,
"device is busy. down_write_trylock failed, returned (0x%x)\n",
rc);
wil->suspend_stats.rejected_by_host++;
return -EBUSY;
}
set_bit(wil_status_suspending, wil->status);
up_write(&wil->mem_lock);
/* if netif up, hardware is alive, shut it down */
mutex_lock(&wil->vif_mutex);
active_ifaces = wil_has_active_ifaces(wil, true, false);
mutex_unlock(&wil->vif_mutex);
if (active_ifaces) {
rc = wil_down(wil);
if (rc) {
wil_err(wil, "wil_down : %d\n", rc);
wil->suspend_stats.r_off.failed_suspends++;
goto out;
}
}
/* Disable PCIe IRQ to prevent sporadic IRQs when PCIe is suspending */
wil_dbg_pm(wil, "Disabling PCIe IRQ before suspending\n");
wil_disable_irq(wil);
if (wil->platform_ops.suspend) {
rc = wil->platform_ops.suspend(wil->platform_handle, false);
if (rc) {
wil_enable_irq(wil);
wil->suspend_stats.r_off.failed_suspends++;
goto out;
}
}
set_bit(wil_status_suspended, wil->status);
out:
clear_bit(wil_status_suspending, wil->status);
wil_dbg_pm(wil, "suspend radio off: %d\n", rc);
return rc;
}
static int wil_resume_radio_off(struct wil6210_priv *wil)
{
int rc = 0;
bool active_ifaces;
wil_dbg_pm(wil, "Enabling PCIe IRQ\n");
wil_enable_irq(wil);
/* if any netif up, bring hardware up
* During open(), IFF_UP set after actual device method
* invocation. This prevent recursive call to wil_up()
* wil_status_suspended will be cleared in wil_reset
*/
mutex_lock(&wil->vif_mutex);
active_ifaces = wil_has_active_ifaces(wil, true, false);
mutex_unlock(&wil->vif_mutex);
if (active_ifaces)
rc = wil_up(wil);
else
clear_bit(wil_status_suspended, wil->status);
return rc;
}
int wil_suspend(struct wil6210_priv *wil, bool is_runtime, bool keep_radio_on)
{
int rc = 0;
wil_dbg_pm(wil, "suspend: %s\n", is_runtime ? "runtime" : "system");
if (test_bit(wil_status_suspended, wil->status)) {
wil_dbg_pm(wil, "trying to suspend while suspended\n");
return 0;
}
if (!keep_radio_on)
rc = wil_suspend_radio_off(wil);
else
rc = wil_suspend_keep_radio_on(wil);
wil_dbg_pm(wil, "suspend: %s => %d\n",
is_runtime ? "runtime" : "system", rc);
return rc;
}
int wil_resume(struct wil6210_priv *wil, bool is_runtime, bool keep_radio_on)
{
int rc = 0;
wil_dbg_pm(wil, "resume: %s\n", is_runtime ? "runtime" : "system");
if (wil->platform_ops.resume) {
rc = wil->platform_ops.resume(wil->platform_handle,
keep_radio_on);
if (rc) {
wil_err(wil, "platform_ops.resume : %d\n", rc);
goto out;
}
}
if (keep_radio_on)
rc = wil_resume_keep_radio_on(wil);
else
rc = wil_resume_radio_off(wil);
out:
wil_dbg_pm(wil, "resume: %s => %d\n", is_runtime ? "runtime" : "system",
rc);
return rc;
}
void wil_pm_runtime_allow(struct wil6210_priv *wil)
{
struct device *dev = wil_to_dev(wil);
pm_runtime_put_noidle(dev);
pm_runtime_set_autosuspend_delay(dev, WIL6210_AUTOSUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(dev);
pm_runtime_allow(dev);
}
void wil_pm_runtime_forbid(struct wil6210_priv *wil)
{
struct device *dev = wil_to_dev(wil);
pm_runtime_forbid(dev);
pm_runtime_get_noresume(dev);
}
int wil_pm_runtime_get(struct wil6210_priv *wil)
{
int rc;
struct device *dev = wil_to_dev(wil);
rc = pm_runtime_resume_and_get(dev);
if (rc < 0) {
wil_err(wil, "pm_runtime_resume_and_get() failed, rc = %d\n", rc);
return rc;
}
return 0;
}
void wil_pm_runtime_put(struct wil6210_priv *wil)
{
struct device *dev = wil_to_dev(wil);
pm_runtime_put_autosuspend(dev);
}
@@ -0,0 +1,442 @@
// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2012-2015,2017 Qualcomm Atheros, Inc.
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/seq_file.h>
#include "wmi.h"
#include "wil6210.h"
#include "txrx.h"
#include "pmc.h"
struct desc_alloc_info {
dma_addr_t pa;
void *va;
};
static int wil_is_pmc_allocated(struct pmc_ctx *pmc)
{
return !!pmc->pring_va;
}
void wil_pmc_init(struct wil6210_priv *wil)
{
memset(&wil->pmc, 0, sizeof(struct pmc_ctx));
mutex_init(&wil->pmc.lock);
}
/* Allocate the physical ring (p-ring) and the required
* number of descriptors of required size.
* Initialize the descriptors as required by pmc dma.
* The descriptors' buffers dwords are initialized to hold
* dword's serial number in the lsw and reserved value
* PCM_DATA_INVALID_DW_VAL in the msw.
*/
void wil_pmc_alloc(struct wil6210_priv *wil,
int num_descriptors,
int descriptor_size)
{
u32 i;
struct pmc_ctx *pmc = &wil->pmc;
struct device *dev = wil_to_dev(wil);
struct wil6210_vif *vif = ndev_to_vif(wil->main_ndev);
struct wmi_pmc_cmd pmc_cmd = {0};
int last_cmd_err = -ENOMEM;
mutex_lock(&pmc->lock);
if (wil_is_pmc_allocated(pmc)) {
/* sanity check */
wil_err(wil, "ERROR pmc is already allocated\n");
goto no_release_err;
}
if ((num_descriptors <= 0) || (descriptor_size <= 0)) {
wil_err(wil,
"Invalid params num_descriptors(%d), descriptor_size(%d)\n",
num_descriptors, descriptor_size);
last_cmd_err = -EINVAL;
goto no_release_err;
}
if (num_descriptors > (1 << WIL_RING_SIZE_ORDER_MAX)) {
wil_err(wil,
"num_descriptors(%d) exceeds max ring size %d\n",
num_descriptors, 1 << WIL_RING_SIZE_ORDER_MAX);
last_cmd_err = -EINVAL;
goto no_release_err;
}
if (num_descriptors > INT_MAX / descriptor_size) {
wil_err(wil,
"Overflow in num_descriptors(%d)*descriptor_size(%d)\n",
num_descriptors, descriptor_size);
last_cmd_err = -EINVAL;
goto no_release_err;
}
pmc->num_descriptors = num_descriptors;
pmc->descriptor_size = descriptor_size;
wil_dbg_misc(wil, "pmc_alloc: %d descriptors x %d bytes each\n",
num_descriptors, descriptor_size);
/* allocate descriptors info list in pmc context*/
pmc->descriptors = kzalloc_objs(struct desc_alloc_info, num_descriptors);
if (!pmc->descriptors) {
wil_err(wil, "ERROR allocating pmc skb list\n");
goto no_release_err;
}
wil_dbg_misc(wil, "pmc_alloc: allocated descriptors info list %p\n",
pmc->descriptors);
/* Allocate pring buffer and descriptors.
* vring->va should be aligned on its size rounded up to power of 2
* This is granted by the dma_alloc_coherent.
*
* HW has limitation that all vrings addresses must share the same
* upper 16 msb bits part of 48 bits address. To workaround that,
* if we are using more than 32 bit addresses switch to 32 bit
* allocation before allocating vring memory.
*
* There's no check for the return value of dma_set_mask_and_coherent,
* since we assume if we were able to set the mask during
* initialization in this system it will not fail if we set it again
*/
if (wil->dma_addr_size > 32)
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
pmc->pring_va = dma_alloc_coherent(dev,
sizeof(struct vring_tx_desc) * num_descriptors,
&pmc->pring_pa,
GFP_KERNEL);
if (wil->dma_addr_size > 32)
dma_set_mask_and_coherent(dev,
DMA_BIT_MASK(wil->dma_addr_size));
wil_dbg_misc(wil,
"pmc_alloc: allocated pring %p => %pad. %zd x %d = total %zd bytes\n",
pmc->pring_va, &pmc->pring_pa,
sizeof(struct vring_tx_desc),
num_descriptors,
sizeof(struct vring_tx_desc) * num_descriptors);
if (!pmc->pring_va) {
wil_err(wil, "ERROR allocating pmc pring\n");
goto release_pmc_skb_list;
}
/* initially, all descriptors are SW owned
* For Tx, Rx, and PMC, ownership bit is at the same location, thus
* we can use any
*/
for (i = 0; i < num_descriptors; i++) {
struct vring_tx_desc *_d = &pmc->pring_va[i];
struct vring_tx_desc dd = {}, *d = &dd;
int j = 0;
pmc->descriptors[i].va = dma_alloc_coherent(dev,
descriptor_size,
&pmc->descriptors[i].pa,
GFP_KERNEL);
if (unlikely(!pmc->descriptors[i].va)) {
wil_err(wil, "ERROR allocating pmc descriptor %d", i);
goto release_pmc_skbs;
}
for (j = 0; j < descriptor_size / sizeof(u32); j++) {
u32 *p = (u32 *)pmc->descriptors[i].va + j;
*p = PCM_DATA_INVALID_DW_VAL | j;
}
/* configure dma descriptor */
d->dma.addr.addr_low =
cpu_to_le32(lower_32_bits(pmc->descriptors[i].pa));
d->dma.addr.addr_high =
cpu_to_le16((u16)upper_32_bits(pmc->descriptors[i].pa));
d->dma.status = 0; /* 0 = HW_OWNED */
d->dma.length = cpu_to_le16(descriptor_size);
d->dma.d0 = BIT(9) | RX_DMA_D0_CMD_DMA_IT;
*_d = *d;
}
wil_dbg_misc(wil, "pmc_alloc: allocated successfully\n");
pmc_cmd.op = WMI_PMC_ALLOCATE;
pmc_cmd.ring_size = cpu_to_le16(pmc->num_descriptors);
pmc_cmd.mem_base = cpu_to_le64(pmc->pring_pa);
wil_dbg_misc(wil, "pmc_alloc: send WMI_PMC_CMD with ALLOCATE op\n");
pmc->last_cmd_status = wmi_send(wil,
WMI_PMC_CMDID,
vif->mid,
&pmc_cmd,
sizeof(pmc_cmd));
if (pmc->last_cmd_status) {
wil_err(wil,
"WMI_PMC_CMD with ALLOCATE op failed with status %d",
pmc->last_cmd_status);
goto release_pmc_skbs;
}
mutex_unlock(&pmc->lock);
return;
release_pmc_skbs:
wil_err(wil, "exit on error: Releasing skbs...\n");
for (i = 0; i < num_descriptors && pmc->descriptors[i].va; i++) {
dma_free_coherent(dev,
descriptor_size,
pmc->descriptors[i].va,
pmc->descriptors[i].pa);
pmc->descriptors[i].va = NULL;
}
wil_err(wil, "exit on error: Releasing pring...\n");
dma_free_coherent(dev,
sizeof(struct vring_tx_desc) * num_descriptors,
pmc->pring_va,
pmc->pring_pa);
pmc->pring_va = NULL;
release_pmc_skb_list:
wil_err(wil, "exit on error: Releasing descriptors info list...\n");
kfree(pmc->descriptors);
pmc->descriptors = NULL;
no_release_err:
pmc->last_cmd_status = last_cmd_err;
mutex_unlock(&pmc->lock);
}
/* Traverse the p-ring and release all buffers.
* At the end release the p-ring memory
*/
void wil_pmc_free(struct wil6210_priv *wil, int send_pmc_cmd)
{
struct pmc_ctx *pmc = &wil->pmc;
struct device *dev = wil_to_dev(wil);
struct wil6210_vif *vif = ndev_to_vif(wil->main_ndev);
struct wmi_pmc_cmd pmc_cmd = {0};
mutex_lock(&pmc->lock);
pmc->last_cmd_status = 0;
if (!wil_is_pmc_allocated(pmc)) {
wil_dbg_misc(wil,
"pmc_free: Error, can't free - not allocated\n");
pmc->last_cmd_status = -EPERM;
mutex_unlock(&pmc->lock);
return;
}
if (send_pmc_cmd) {
wil_dbg_misc(wil, "send WMI_PMC_CMD with RELEASE op\n");
pmc_cmd.op = WMI_PMC_RELEASE;
pmc->last_cmd_status =
wmi_send(wil, WMI_PMC_CMDID, vif->mid,
&pmc_cmd, sizeof(pmc_cmd));
if (pmc->last_cmd_status) {
wil_err(wil,
"WMI_PMC_CMD with RELEASE op failed, status %d",
pmc->last_cmd_status);
/* There's nothing we can do with this error.
* Normally, it should never occur.
* Continue to freeing all memory allocated for pmc.
*/
}
}
if (pmc->pring_va) {
size_t buf_size = sizeof(struct vring_tx_desc) *
pmc->num_descriptors;
wil_dbg_misc(wil, "pmc_free: free pring va %p\n",
pmc->pring_va);
dma_free_coherent(dev, buf_size, pmc->pring_va, pmc->pring_pa);
pmc->pring_va = NULL;
} else {
pmc->last_cmd_status = -ENOENT;
}
if (pmc->descriptors) {
int i;
for (i = 0;
i < pmc->num_descriptors && pmc->descriptors[i].va; i++) {
dma_free_coherent(dev,
pmc->descriptor_size,
pmc->descriptors[i].va,
pmc->descriptors[i].pa);
pmc->descriptors[i].va = NULL;
}
wil_dbg_misc(wil, "pmc_free: free descriptor info %d/%d\n", i,
pmc->num_descriptors);
wil_dbg_misc(wil,
"pmc_free: free pmc descriptors info list %p\n",
pmc->descriptors);
kfree(pmc->descriptors);
pmc->descriptors = NULL;
} else {
pmc->last_cmd_status = -ENOENT;
}
mutex_unlock(&pmc->lock);
}
/* Status of the last operation requested via debugfs: alloc/free/read.
* 0 - success or negative errno
*/
int wil_pmc_last_cmd_status(struct wil6210_priv *wil)
{
wil_dbg_misc(wil, "pmc_last_cmd_status: status %d\n",
wil->pmc.last_cmd_status);
return wil->pmc.last_cmd_status;
}
/* Read from required position up to the end of current descriptor,
* depends on descriptor size configured during alloc request.
*/
ssize_t wil_pmc_read(struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
struct wil6210_priv *wil = filp->private_data;
struct pmc_ctx *pmc = &wil->pmc;
size_t retval = 0;
unsigned long long idx;
loff_t offset;
size_t pmc_size;
mutex_lock(&pmc->lock);
if (!wil_is_pmc_allocated(pmc)) {
wil_err(wil, "error, pmc is not allocated!\n");
pmc->last_cmd_status = -EPERM;
mutex_unlock(&pmc->lock);
return -EPERM;
}
pmc_size = pmc->descriptor_size * pmc->num_descriptors;
wil_dbg_misc(wil,
"pmc_read: size %u, pos %lld\n",
(u32)count, *f_pos);
pmc->last_cmd_status = 0;
idx = *f_pos;
do_div(idx, pmc->descriptor_size);
offset = *f_pos - (idx * pmc->descriptor_size);
if (*f_pos >= pmc_size) {
wil_dbg_misc(wil,
"pmc_read: reached end of pmc buf: %lld >= %u\n",
*f_pos, (u32)pmc_size);
pmc->last_cmd_status = -ERANGE;
goto out;
}
wil_dbg_misc(wil,
"pmc_read: read from pos %lld (descriptor %llu, offset %llu) %zu bytes\n",
*f_pos, idx, offset, count);
/* if no errors, return the copied byte count */
retval = simple_read_from_buffer(buf,
count,
&offset,
pmc->descriptors[idx].va,
pmc->descriptor_size);
*f_pos += retval;
out:
mutex_unlock(&pmc->lock);
return retval;
}
loff_t wil_pmc_llseek(struct file *filp, loff_t off, int whence)
{
loff_t newpos;
struct wil6210_priv *wil = filp->private_data;
struct pmc_ctx *pmc = &wil->pmc;
size_t pmc_size;
mutex_lock(&pmc->lock);
if (!wil_is_pmc_allocated(pmc)) {
wil_err(wil, "error, pmc is not allocated!\n");
pmc->last_cmd_status = -EPERM;
mutex_unlock(&pmc->lock);
return -EPERM;
}
pmc_size = pmc->descriptor_size * pmc->num_descriptors;
switch (whence) {
case 0: /* SEEK_SET */
newpos = off;
break;
case 1: /* SEEK_CUR */
newpos = filp->f_pos + off;
break;
case 2: /* SEEK_END */
newpos = pmc_size;
break;
default: /* can't happen */
newpos = -EINVAL;
goto out;
}
if (newpos < 0) {
newpos = -EINVAL;
goto out;
}
if (newpos > pmc_size)
newpos = pmc_size;
filp->f_pos = newpos;
out:
mutex_unlock(&pmc->lock);
return newpos;
}
int wil_pmcring_read(struct seq_file *s, void *data)
{
struct wil6210_priv *wil = s->private;
struct pmc_ctx *pmc = &wil->pmc;
size_t pmc_ring_size =
sizeof(struct vring_rx_desc) * pmc->num_descriptors;
mutex_lock(&pmc->lock);
if (!wil_is_pmc_allocated(pmc)) {
wil_err(wil, "error, pmc is not allocated!\n");
pmc->last_cmd_status = -EPERM;
mutex_unlock(&pmc->lock);
return -EPERM;
}
wil_dbg_misc(wil, "pmcring_read: size %zu\n", pmc_ring_size);
seq_write(s, pmc->pring_va, pmc_ring_size);
mutex_unlock(&pmc->lock);
return 0;
}
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: ISC */
/* Copyright (c) 2012-2015 Qualcomm Atheros, Inc. */
#include <linux/types.h>
#define PCM_DATA_INVALID_DW_VAL (0xB0BA0000)
void wil_pmc_init(struct wil6210_priv *wil);
void wil_pmc_alloc(struct wil6210_priv *wil,
int num_descriptors, int descriptor_size);
void wil_pmc_free(struct wil6210_priv *wil, int send_pmc_cmd);
int wil_pmc_last_cmd_status(struct wil6210_priv *wil);
ssize_t wil_pmc_read(struct file *, char __user *, size_t, loff_t *);
loff_t wil_pmc_llseek(struct file *filp, loff_t off, int whence);
int wil_pmcring_read(struct seq_file *s, void *data);
@@ -0,0 +1,402 @@
// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2014-2017 Qualcomm Atheros, Inc.
* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
*/
#include "wil6210.h"
#include "txrx.h"
#define SEQ_MODULO 0x1000
#define SEQ_MASK 0xfff
static inline int seq_less(u16 sq1, u16 sq2)
{
return ((sq1 - sq2) & SEQ_MASK) > (SEQ_MODULO >> 1);
}
static inline u16 seq_inc(u16 sq)
{
return (sq + 1) & SEQ_MASK;
}
static inline u16 seq_sub(u16 sq1, u16 sq2)
{
return (sq1 - sq2) & SEQ_MASK;
}
static inline int reorder_index(struct wil_tid_ampdu_rx *r, u16 seq)
{
return seq_sub(seq, r->ssn) % r->buf_size;
}
static void wil_release_reorder_frame(struct net_device *ndev,
struct wil_tid_ampdu_rx *r,
int index)
{
struct sk_buff *skb = r->reorder_buf[index];
if (!skb)
goto no_frame;
/* release the frame from the reorder ring buffer */
r->stored_mpdu_num--;
r->reorder_buf[index] = NULL;
wil_netif_rx_any(skb, ndev);
no_frame:
r->head_seq_num = seq_inc(r->head_seq_num);
}
static void wil_release_reorder_frames(struct net_device *ndev,
struct wil_tid_ampdu_rx *r,
u16 hseq)
{
int index;
/* note: this function is never called with
* hseq preceding r->head_seq_num, i.e it is always true
* !seq_less(hseq, r->head_seq_num)
* and thus on loop exit it should be
* r->head_seq_num == hseq
*/
while (seq_less(r->head_seq_num, hseq) && r->stored_mpdu_num) {
index = reorder_index(r, r->head_seq_num);
wil_release_reorder_frame(ndev, r, index);
}
r->head_seq_num = hseq;
}
static void wil_reorder_release(struct net_device *ndev,
struct wil_tid_ampdu_rx *r)
{
int index = reorder_index(r, r->head_seq_num);
while (r->reorder_buf[index]) {
wil_release_reorder_frame(ndev, r, index);
index = reorder_index(r, r->head_seq_num);
}
}
/* called in NAPI context */
void wil_rx_reorder(struct wil6210_priv *wil, struct sk_buff *skb)
__acquires(&sta->tid_rx_lock) __releases(&sta->tid_rx_lock)
{
struct wil6210_vif *vif;
struct net_device *ndev;
int tid, cid, mid, mcast, retry;
u16 seq;
struct wil_sta_info *sta;
struct wil_tid_ampdu_rx *r;
u16 hseq;
int index;
wil->txrx_ops.get_reorder_params(wil, skb, &tid, &cid, &mid, &seq,
&mcast, &retry);
sta = &wil->sta[cid];
wil_dbg_txrx(wil, "MID %d CID %d TID %d Seq 0x%03x mcast %01x\n",
mid, cid, tid, seq, mcast);
vif = wil->vifs[mid];
if (unlikely(!vif)) {
wil_dbg_txrx(wil, "invalid VIF, mid %d\n", mid);
dev_kfree_skb(skb);
return;
}
ndev = vif_to_ndev(vif);
spin_lock(&sta->tid_rx_lock);
r = sta->tid_rx[tid];
if (!r) {
wil_netif_rx_any(skb, ndev);
goto out;
}
if (unlikely(mcast)) {
if (retry && seq == r->mcast_last_seq) {
r->drop_dup_mcast++;
wil_dbg_txrx(wil, "Rx drop: dup mcast seq 0x%03x\n",
seq);
dev_kfree_skb(skb);
goto out;
}
r->mcast_last_seq = seq;
wil_netif_rx_any(skb, ndev);
goto out;
}
r->total++;
hseq = r->head_seq_num;
/** Due to the race between WMI events, where BACK establishment
* reported, and data Rx, few packets may be pass up before reorder
* buffer get allocated. Catch up by pretending SSN is what we
* see in the 1-st Rx packet
*
* Another scenario, Rx get delayed and we got packet from before
* BACK. Pass it to the stack and wait.
*/
if (r->first_time) {
r->first_time = false;
if (seq != r->head_seq_num) {
if (seq_less(seq, r->head_seq_num)) {
wil_err(wil,
"Error: frame with early sequence 0x%03x, should be 0x%03x. Waiting...\n",
seq, r->head_seq_num);
r->first_time = true;
wil_netif_rx_any(skb, ndev);
goto out;
}
wil_err(wil,
"Error: 1-st frame with wrong sequence 0x%03x, should be 0x%03x. Fixing...\n",
seq, r->head_seq_num);
r->head_seq_num = seq;
r->ssn = seq;
}
}
/* frame with out of date sequence number */
if (seq_less(seq, r->head_seq_num)) {
r->ssn_last_drop = seq;
r->drop_old++;
wil_dbg_txrx(wil, "Rx drop: old seq 0x%03x head 0x%03x\n",
seq, r->head_seq_num);
dev_kfree_skb(skb);
goto out;
}
/*
* If frame the sequence number exceeds our buffering window
* size release some previous frames to make room for this one.
*/
if (!seq_less(seq, r->head_seq_num + r->buf_size)) {
hseq = seq_inc(seq_sub(seq, r->buf_size));
/* release stored frames up to new head to stack */
wil_release_reorder_frames(ndev, r, hseq);
}
/* Now the new frame is always in the range of the reordering buffer */
index = reorder_index(r, seq);
/* check if we already stored this frame */
if (r->reorder_buf[index]) {
r->drop_dup++;
wil_dbg_txrx(wil, "Rx drop: dup seq 0x%03x\n", seq);
dev_kfree_skb(skb);
goto out;
}
/*
* If the current MPDU is in the right order and nothing else
* is stored we can process it directly, no need to buffer it.
* If it is first but there's something stored, we may be able
* to release frames after this one.
*/
if (seq == r->head_seq_num && r->stored_mpdu_num == 0) {
r->head_seq_num = seq_inc(r->head_seq_num);
wil_netif_rx_any(skb, ndev);
goto out;
}
/* put the frame in the reordering buffer */
r->reorder_buf[index] = skb;
r->stored_mpdu_num++;
wil_reorder_release(ndev, r);
out:
spin_unlock(&sta->tid_rx_lock);
}
/* process BAR frame, called in NAPI context */
void wil_rx_bar(struct wil6210_priv *wil, struct wil6210_vif *vif,
u8 cid, u8 tid, u16 seq)
{
struct wil_sta_info *sta = &wil->sta[cid];
struct net_device *ndev = vif_to_ndev(vif);
struct wil_tid_ampdu_rx *r;
spin_lock(&sta->tid_rx_lock);
r = sta->tid_rx[tid];
if (!r) {
wil_err(wil, "BAR for non-existing CID %d TID %d\n", cid, tid);
goto out;
}
if (seq_less(seq, r->head_seq_num)) {
wil_err(wil, "BAR Seq 0x%03x preceding head 0x%03x\n",
seq, r->head_seq_num);
goto out;
}
wil_dbg_txrx(wil, "BAR: CID %d MID %d TID %d Seq 0x%03x head 0x%03x\n",
cid, vif->mid, tid, seq, r->head_seq_num);
wil_release_reorder_frames(ndev, r, seq);
out:
spin_unlock(&sta->tid_rx_lock);
}
struct wil_tid_ampdu_rx *wil_tid_ampdu_rx_alloc(struct wil6210_priv *wil,
int size, u16 ssn)
{
struct wil_tid_ampdu_rx *r = kzalloc_obj(*r);
if (!r)
return NULL;
r->reorder_buf =
kzalloc_objs(struct sk_buff *, size);
if (!r->reorder_buf) {
kfree(r);
return NULL;
}
r->ssn = ssn;
r->head_seq_num = ssn;
r->buf_size = size;
r->stored_mpdu_num = 0;
r->first_time = true;
r->mcast_last_seq = U16_MAX;
return r;
}
void wil_tid_ampdu_rx_free(struct wil6210_priv *wil,
struct wil_tid_ampdu_rx *r)
{
int i;
if (!r)
return;
/* Do not pass remaining frames to the network stack - it may be
* not expecting to get any more Rx. Rx from here may lead to
* kernel OOPS since some per-socket accounting info was already
* released.
*/
for (i = 0; i < r->buf_size; i++)
kfree_skb(r->reorder_buf[i]);
kfree(r->reorder_buf);
kfree(r);
}
/* ADDBA processing */
static u16 wil_agg_size(struct wil6210_priv *wil, u16 req_agg_wsize)
{
u16 max_agg_size = min_t(u16, wil->max_agg_wsize, wil->max_ampdu_size /
(mtu_max + WIL_MAX_MPDU_OVERHEAD));
if (!req_agg_wsize)
return max_agg_size;
return min(max_agg_size, req_agg_wsize);
}
/* Block Ack - Rx side (recipient) */
int wil_addba_rx_request(struct wil6210_priv *wil, u8 mid, u8 cid, u8 tid,
u8 dialog_token, __le16 ba_param_set,
__le16 ba_timeout, __le16 ba_seq_ctrl)
__acquires(&sta->tid_rx_lock) __releases(&sta->tid_rx_lock)
{
u16 param_set = le16_to_cpu(ba_param_set);
u16 agg_timeout = le16_to_cpu(ba_timeout);
u16 seq_ctrl = le16_to_cpu(ba_seq_ctrl);
struct wil_sta_info *sta;
u16 agg_wsize;
/* bit 0: A-MSDU supported
* bit 1: policy (should be 0 for us)
* bits 2..5: TID
* bits 6..15: buffer size
*/
u16 req_agg_wsize = WIL_GET_BITS(param_set, 6, 15);
bool agg_amsdu = wil->use_enhanced_dma_hw &&
wil->use_rx_hw_reordering &&
test_bit(WMI_FW_CAPABILITY_AMSDU, wil->fw_capabilities) &&
wil->amsdu_en && (param_set & BIT(0));
int ba_policy = param_set & BIT(1);
u16 ssn = seq_ctrl >> 4;
struct wil_tid_ampdu_rx *r;
int rc = 0;
might_sleep();
/* sanity checks */
if (cid >= wil->max_assoc_sta) {
wil_err(wil, "BACK: invalid CID %d\n", cid);
rc = -EINVAL;
goto out;
}
sta = &wil->sta[cid];
if (sta->status != wil_sta_connected) {
wil_err(wil, "BACK: CID %d not connected\n", cid);
rc = -EINVAL;
goto out;
}
wil_dbg_wmi(wil,
"ADDBA request for CID %d %pM TID %d size %d timeout %d AMSDU%s policy %d token %d SSN 0x%03x\n",
cid, sta->addr, tid, req_agg_wsize, agg_timeout,
agg_amsdu ? "+" : "-", !!ba_policy, dialog_token, ssn);
/* apply policies */
if (req_agg_wsize == 0) {
wil_dbg_misc(wil, "Suggest BACK wsize %d\n",
wil->max_agg_wsize);
agg_wsize = wil->max_agg_wsize;
} else {
agg_wsize = min_t(u16, wil->max_agg_wsize, req_agg_wsize);
}
rc = wil->txrx_ops.wmi_addba_rx_resp(wil, mid, cid, tid, dialog_token,
WLAN_STATUS_SUCCESS, agg_amsdu,
agg_wsize, agg_timeout);
if (rc) {
wil_err(wil, "do not apply ba, rc(%d)\n", rc);
goto out;
}
/* apply */
if (!wil->use_rx_hw_reordering) {
r = wil_tid_ampdu_rx_alloc(wil, agg_wsize, ssn);
spin_lock_bh(&sta->tid_rx_lock);
wil_tid_ampdu_rx_free(wil, sta->tid_rx[tid]);
sta->tid_rx[tid] = r;
spin_unlock_bh(&sta->tid_rx_lock);
}
out:
return rc;
}
/* BACK - Tx side (originator) */
int wil_addba_tx_request(struct wil6210_priv *wil, u8 ringid, u16 wsize)
{
u8 agg_wsize = wil_agg_size(wil, wsize);
u16 agg_timeout = 0;
struct wil_ring_tx_data *txdata = &wil->ring_tx_data[ringid];
int rc = 0;
if (txdata->addba_in_progress) {
wil_dbg_misc(wil, "ADDBA for vring[%d] already in progress\n",
ringid);
goto out;
}
if (txdata->agg_wsize) {
wil_dbg_misc(wil,
"ADDBA for vring[%d] already done for wsize %d\n",
ringid, txdata->agg_wsize);
goto out;
}
txdata->addba_in_progress = true;
rc = wmi_addba(wil, txdata->mid, ringid, agg_wsize, agg_timeout);
if (rc) {
wil_err(wil, "wmi_addba failed, rc (%d)", rc);
txdata->addba_in_progress = false;
}
out:
return rc;
}
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2013 Qualcomm Atheros, Inc.
*/
#include <linux/module.h>
#define CREATE_TRACE_POINTS
#include "trace.h"
@@ -0,0 +1,286 @@
/* SPDX-License-Identifier: ISC */
/*
* Copyright (c) 2013-2016 Qualcomm Atheros, Inc.
* Copyright (c) 2019, The Linux Foundation. All rights reserved.
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM wil6210
#if !defined(WIL6210_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
#define WIL6210_TRACE_H
#include <linux/tracepoint.h>
#include "wil6210.h"
#include "txrx.h"
/* create empty functions when tracing is disabled */
#if !defined(CONFIG_WIL6210_TRACING) || defined(__CHECKER__)
#undef TRACE_EVENT
#define TRACE_EVENT(name, proto, ...) \
static inline void trace_ ## name(proto) {}
#undef DECLARE_EVENT_CLASS
#define DECLARE_EVENT_CLASS(...)
#undef DEFINE_EVENT
#define DEFINE_EVENT(evt_class, name, proto, ...) \
static inline void trace_ ## name(proto) {}
#endif /* !CONFIG_WIL6210_TRACING || defined(__CHECKER__) */
DECLARE_EVENT_CLASS(wil6210_wmi,
TP_PROTO(struct wmi_cmd_hdr *wmi, void *buf, u16 buf_len),
TP_ARGS(wmi, buf, buf_len),
TP_STRUCT__entry(
__field(u8, mid)
__field(u16, command_id)
__field(u32, fw_timestamp)
__field(u16, buf_len)
__dynamic_array(u8, buf, buf_len)
),
TP_fast_assign(
__entry->mid = wmi->mid;
__entry->command_id = le16_to_cpu(wmi->command_id);
__entry->fw_timestamp = le32_to_cpu(wmi->fw_timestamp);
__entry->buf_len = buf_len;
memcpy(__get_dynamic_array(buf), buf, buf_len);
),
TP_printk(
"MID %d id 0x%04x len %d timestamp %d",
__entry->mid, __entry->command_id, __entry->buf_len,
__entry->fw_timestamp
)
);
DEFINE_EVENT(wil6210_wmi, wil6210_wmi_cmd,
TP_PROTO(struct wmi_cmd_hdr *wmi, void *buf, u16 buf_len),
TP_ARGS(wmi, buf, buf_len)
);
DEFINE_EVENT(wil6210_wmi, wil6210_wmi_event,
TP_PROTO(struct wmi_cmd_hdr *wmi, void *buf, u16 buf_len),
TP_ARGS(wmi, buf, buf_len)
);
#define WIL6210_MSG_MAX (200)
DECLARE_EVENT_CLASS(wil6210_log_event,
TP_PROTO(struct va_format *vaf),
TP_ARGS(vaf),
TP_STRUCT__entry(
__vstring(msg, vaf->fmt, vaf->va)
),
TP_fast_assign(
__assign_vstr(msg, vaf->fmt, vaf->va);
),
TP_printk("%s", __get_str(msg))
);
DEFINE_EVENT(wil6210_log_event, wil6210_log_err,
TP_PROTO(struct va_format *vaf),
TP_ARGS(vaf)
);
DEFINE_EVENT(wil6210_log_event, wil6210_log_info,
TP_PROTO(struct va_format *vaf),
TP_ARGS(vaf)
);
DEFINE_EVENT(wil6210_log_event, wil6210_log_dbg,
TP_PROTO(struct va_format *vaf),
TP_ARGS(vaf)
);
#define wil_pseudo_irq_cause(x) __print_flags(x, "|", \
{BIT_DMA_PSEUDO_CAUSE_RX, "Rx" }, \
{BIT_DMA_PSEUDO_CAUSE_TX, "Tx" }, \
{BIT_DMA_PSEUDO_CAUSE_MISC, "Misc" })
TRACE_EVENT(wil6210_irq_pseudo,
TP_PROTO(u32 x),
TP_ARGS(x),
TP_STRUCT__entry(
__field(u32, x)
),
TP_fast_assign(
__entry->x = x;
),
TP_printk("cause 0x%08x : %s", __entry->x,
wil_pseudo_irq_cause(__entry->x))
);
DECLARE_EVENT_CLASS(wil6210_irq,
TP_PROTO(u32 x),
TP_ARGS(x),
TP_STRUCT__entry(
__field(u32, x)
),
TP_fast_assign(
__entry->x = x;
),
TP_printk("cause 0x%08x", __entry->x)
);
DEFINE_EVENT(wil6210_irq, wil6210_irq_rx,
TP_PROTO(u32 x),
TP_ARGS(x)
);
DEFINE_EVENT(wil6210_irq, wil6210_irq_tx,
TP_PROTO(u32 x),
TP_ARGS(x)
);
DEFINE_EVENT(wil6210_irq, wil6210_irq_misc,
TP_PROTO(u32 x),
TP_ARGS(x)
);
DEFINE_EVENT(wil6210_irq, wil6210_irq_misc_thread,
TP_PROTO(u32 x),
TP_ARGS(x)
);
TRACE_EVENT(wil6210_rx,
TP_PROTO(u16 index, struct vring_rx_desc *d),
TP_ARGS(index, d),
TP_STRUCT__entry(
__field(u16, index)
__field(unsigned int, len)
__field(u8, mid)
__field(u8, cid)
__field(u8, tid)
__field(u8, type)
__field(u8, subtype)
__field(u16, seq)
__field(u8, mcs)
),
TP_fast_assign(
__entry->index = index;
__entry->len = d->dma.length;
__entry->mid = wil_rxdesc_mid(d);
__entry->cid = wil_rxdesc_cid(d);
__entry->tid = wil_rxdesc_tid(d);
__entry->type = wil_rxdesc_ftype(d);
__entry->subtype = wil_rxdesc_subtype(d);
__entry->seq = wil_rxdesc_seq(d);
__entry->mcs = wil_rxdesc_mcs(d);
),
TP_printk("index %d len %d mid %d cid (%%8) %d tid %d mcs %d seq 0x%03x"
" type 0x%1x subtype 0x%1x", __entry->index, __entry->len,
__entry->mid, __entry->cid, __entry->tid, __entry->mcs,
__entry->seq, __entry->type, __entry->subtype)
);
TRACE_EVENT(wil6210_rx_status,
TP_PROTO(struct wil6210_priv *wil, u8 use_compressed, u16 buff_id,
void *msg),
TP_ARGS(wil, use_compressed, buff_id, msg),
TP_STRUCT__entry(__field(u8, use_compressed)
__field(u16, buff_id)
__field(unsigned int, len)
__field(u8, mid)
__field(u8, cid)
__field(u8, tid)
__field(u8, type)
__field(u8, subtype)
__field(u16, seq)
__field(u8, mcs)
),
TP_fast_assign(__entry->use_compressed = use_compressed;
__entry->buff_id = buff_id;
__entry->len = wil_rx_status_get_length(msg);
__entry->mid = wil_rx_status_get_mid(msg);
__entry->cid = wil_rx_status_get_cid(msg);
__entry->tid = wil_rx_status_get_tid(msg);
__entry->type = wil_rx_status_get_frame_type(wil,
msg);
__entry->subtype = wil_rx_status_get_fc1(wil, msg);
__entry->seq = wil_rx_status_get_seq(wil, msg);
__entry->mcs = wil_rx_status_get_mcs(msg);
),
TP_printk(
"compressed %d buff_id %d len %d mid %d cid %d tid %d mcs %d seq 0x%03x type 0x%1x subtype 0x%1x",
__entry->use_compressed, __entry->buff_id, __entry->len,
__entry->mid, __entry->cid, __entry->tid, __entry->mcs,
__entry->seq, __entry->type, __entry->subtype)
);
TRACE_EVENT(wil6210_tx,
TP_PROTO(u8 vring, u16 index, unsigned int len, u8 frags),
TP_ARGS(vring, index, len, frags),
TP_STRUCT__entry(
__field(u8, vring)
__field(u8, frags)
__field(u16, index)
__field(unsigned int, len)
),
TP_fast_assign(
__entry->vring = vring;
__entry->frags = frags;
__entry->index = index;
__entry->len = len;
),
TP_printk("vring %d index %d len %d frags %d",
__entry->vring, __entry->index, __entry->len, __entry->frags)
);
TRACE_EVENT(wil6210_tx_done,
TP_PROTO(u8 vring, u16 index, unsigned int len, u8 err),
TP_ARGS(vring, index, len, err),
TP_STRUCT__entry(
__field(u8, vring)
__field(u8, err)
__field(u16, index)
__field(unsigned int, len)
),
TP_fast_assign(
__entry->vring = vring;
__entry->index = index;
__entry->len = len;
__entry->err = err;
),
TP_printk("vring %d index %d len %d err 0x%02x",
__entry->vring, __entry->index, __entry->len,
__entry->err)
);
TRACE_EVENT(wil6210_tx_status,
TP_PROTO(struct wil_ring_tx_status *msg, u16 index,
unsigned int len),
TP_ARGS(msg, index, len),
TP_STRUCT__entry(__field(u16, index)
__field(unsigned int, len)
__field(u8, num_descs)
__field(u8, ring_id)
__field(u8, status)
__field(u8, mcs)
),
TP_fast_assign(__entry->index = index;
__entry->len = len;
__entry->num_descs = msg->num_descriptors;
__entry->ring_id = msg->ring_id;
__entry->status = msg->status;
__entry->mcs = wil_tx_status_get_mcs(msg);
),
TP_printk(
"ring_id %d swtail 0x%x len %d num_descs %d status 0x%x mcs %d",
__entry->ring_id, __entry->index, __entry->len,
__entry->num_descs, __entry->status, __entry->mcs)
);
#endif /* WIL6210_TRACE_H || TRACE_HEADER_MULTI_READ*/
#if defined(CONFIG_WIL6210_TRACING) && !defined(__CHECKER__)
/* we don't want to use include/trace/events */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace
/* This part must be outside protection */
#include <trace/define_trace.h>
#endif /* defined(CONFIG_WIL6210_TRACING) && !defined(__CHECKER__) */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,694 @@
/* SPDX-License-Identifier: ISC */
/*
* Copyright (c) 2012-2016 Qualcomm Atheros, Inc.
* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
*/
#ifndef WIL6210_TXRX_H
#define WIL6210_TXRX_H
#include <net/sock.h>
#include "wil6210.h"
#include "txrx_edma.h"
#define BUF_SW_OWNED (1)
#define BUF_HW_OWNED (0)
/* default size of MAC Tx/Rx buffers */
#define TXRX_BUF_LEN_DEFAULT (2048)
/* how many bytes to reserve for rtap header? */
#define WIL6210_RTAP_SIZE (128)
/* Tx/Rx path */
static inline dma_addr_t wil_desc_addr(struct wil_ring_dma_addr *addr)
{
return le32_to_cpu(addr->addr_low) |
((u64)le16_to_cpu(addr->addr_high) << 32);
}
static inline void wil_desc_addr_set(struct wil_ring_dma_addr *addr,
dma_addr_t pa)
{
addr->addr_low = cpu_to_le32(lower_32_bits(pa));
addr->addr_high = cpu_to_le16((u16)upper_32_bits(pa));
}
/* Tx descriptor - MAC part
* [dword 0]
* bit 0.. 9 : lifetime_expiry_value:10
* bit 10 : interrupt_en:1
* bit 11 : status_en:1
* bit 12..13 : txss_override:2
* bit 14 : timestamp_insertion:1
* bit 15 : duration_preserve:1
* bit 16..21 : reserved0:6
* bit 22..26 : mcs_index:5
* bit 27 : mcs_en:1
* bit 28..30 : reserved1:3
* bit 31 : sn_preserved:1
* [dword 1]
* bit 0.. 3 : pkt_mode:4
* bit 4 : pkt_mode_en:1
* bit 5 : mac_id_en:1
* bit 6..7 : mac_id:2
* bit 8..14 : reserved0:7
* bit 15 : ack_policy_en:1
* bit 16..19 : dst_index:4
* bit 20 : dst_index_en:1
* bit 21..22 : ack_policy:2
* bit 23 : lifetime_en:1
* bit 24..30 : max_retry:7
* bit 31 : max_retry_en:1
* [dword 2]
* bit 0.. 7 : num_of_descriptors:8
* bit 8..17 : reserved:10
* bit 18..19 : l2_translation_type:2 00 - bypass, 01 - 802.3, 10 - 802.11
* bit 20 : snap_hdr_insertion_en:1
* bit 21 : vlan_removal_en:1
* bit 22..31 : reserved0:10
* [dword 3]
* bit 0.. 31: ucode_cmd:32
*/
struct vring_tx_mac {
u32 d[3];
u32 ucode_cmd;
} __packed;
/* TX MAC Dword 0 */
#define MAC_CFG_DESC_TX_0_LIFETIME_EXPIRY_VALUE_POS 0
#define MAC_CFG_DESC_TX_0_LIFETIME_EXPIRY_VALUE_LEN 10
#define MAC_CFG_DESC_TX_0_LIFETIME_EXPIRY_VALUE_MSK 0x3FF
#define MAC_CFG_DESC_TX_0_INTERRUP_EN_POS 10
#define MAC_CFG_DESC_TX_0_INTERRUP_EN_LEN 1
#define MAC_CFG_DESC_TX_0_INTERRUP_EN_MSK 0x400
#define MAC_CFG_DESC_TX_0_STATUS_EN_POS 11
#define MAC_CFG_DESC_TX_0_STATUS_EN_LEN 1
#define MAC_CFG_DESC_TX_0_STATUS_EN_MSK 0x800
#define MAC_CFG_DESC_TX_0_TXSS_OVERRIDE_POS 12
#define MAC_CFG_DESC_TX_0_TXSS_OVERRIDE_LEN 2
#define MAC_CFG_DESC_TX_0_TXSS_OVERRIDE_MSK 0x3000
#define MAC_CFG_DESC_TX_0_TIMESTAMP_INSERTION_POS 14
#define MAC_CFG_DESC_TX_0_TIMESTAMP_INSERTION_LEN 1
#define MAC_CFG_DESC_TX_0_TIMESTAMP_INSERTION_MSK 0x4000
#define MAC_CFG_DESC_TX_0_DURATION_PRESERVE_POS 15
#define MAC_CFG_DESC_TX_0_DURATION_PRESERVE_LEN 1
#define MAC_CFG_DESC_TX_0_DURATION_PRESERVE_MSK 0x8000
#define MAC_CFG_DESC_TX_0_MCS_INDEX_POS 22
#define MAC_CFG_DESC_TX_0_MCS_INDEX_LEN 5
#define MAC_CFG_DESC_TX_0_MCS_INDEX_MSK 0x7C00000
#define MAC_CFG_DESC_TX_0_MCS_EN_POS 27
#define MAC_CFG_DESC_TX_0_MCS_EN_LEN 1
#define MAC_CFG_DESC_TX_0_MCS_EN_MSK 0x8000000
#define MAC_CFG_DESC_TX_0_SN_PRESERVED_POS 31
#define MAC_CFG_DESC_TX_0_SN_PRESERVED_LEN 1
#define MAC_CFG_DESC_TX_0_SN_PRESERVED_MSK 0x80000000
/* TX MAC Dword 1 */
#define MAC_CFG_DESC_TX_1_PKT_MODE_POS 0
#define MAC_CFG_DESC_TX_1_PKT_MODE_LEN 4
#define MAC_CFG_DESC_TX_1_PKT_MODE_MSK 0xF
#define MAC_CFG_DESC_TX_1_PKT_MODE_EN_POS 4
#define MAC_CFG_DESC_TX_1_PKT_MODE_EN_LEN 1
#define MAC_CFG_DESC_TX_1_PKT_MODE_EN_MSK 0x10
#define MAC_CFG_DESC_TX_1_MAC_ID_EN_POS 5
#define MAC_CFG_DESC_TX_1_MAC_ID_EN_LEN 1
#define MAC_CFG_DESC_TX_1_MAC_ID_EN_MSK 0x20
#define MAC_CFG_DESC_TX_1_MAC_ID_POS 6
#define MAC_CFG_DESC_TX_1_MAC_ID_LEN 2
#define MAC_CFG_DESC_TX_1_MAC_ID_MSK 0xc0
#define MAC_CFG_DESC_TX_1_ACK_POLICY_EN_POS 15
#define MAC_CFG_DESC_TX_1_ACK_POLICY_EN_LEN 1
#define MAC_CFG_DESC_TX_1_ACK_POLICY_EN_MSK 0x8000
#define MAC_CFG_DESC_TX_1_DST_INDEX_POS 16
#define MAC_CFG_DESC_TX_1_DST_INDEX_LEN 4
#define MAC_CFG_DESC_TX_1_DST_INDEX_MSK 0xF0000
#define MAC_CFG_DESC_TX_1_DST_INDEX_EN_POS 20
#define MAC_CFG_DESC_TX_1_DST_INDEX_EN_LEN 1
#define MAC_CFG_DESC_TX_1_DST_INDEX_EN_MSK 0x100000
#define MAC_CFG_DESC_TX_1_ACK_POLICY_POS 21
#define MAC_CFG_DESC_TX_1_ACK_POLICY_LEN 2
#define MAC_CFG_DESC_TX_1_ACK_POLICY_MSK 0x600000
#define MAC_CFG_DESC_TX_1_LIFETIME_EN_POS 23
#define MAC_CFG_DESC_TX_1_LIFETIME_EN_LEN 1
#define MAC_CFG_DESC_TX_1_LIFETIME_EN_MSK 0x800000
#define MAC_CFG_DESC_TX_1_MAX_RETRY_POS 24
#define MAC_CFG_DESC_TX_1_MAX_RETRY_LEN 7
#define MAC_CFG_DESC_TX_1_MAX_RETRY_MSK 0x7F000000
#define MAC_CFG_DESC_TX_1_MAX_RETRY_EN_POS 31
#define MAC_CFG_DESC_TX_1_MAX_RETRY_EN_LEN 1
#define MAC_CFG_DESC_TX_1_MAX_RETRY_EN_MSK 0x80000000
/* TX MAC Dword 2 */
#define MAC_CFG_DESC_TX_2_NUM_OF_DESCRIPTORS_POS 0
#define MAC_CFG_DESC_TX_2_NUM_OF_DESCRIPTORS_LEN 8
#define MAC_CFG_DESC_TX_2_NUM_OF_DESCRIPTORS_MSK 0xFF
#define MAC_CFG_DESC_TX_2_RESERVED_POS 8
#define MAC_CFG_DESC_TX_2_RESERVED_LEN 10
#define MAC_CFG_DESC_TX_2_RESERVED_MSK 0x3FF00
#define MAC_CFG_DESC_TX_2_L2_TRANSLATION_TYPE_POS 18
#define MAC_CFG_DESC_TX_2_L2_TRANSLATION_TYPE_LEN 2
#define MAC_CFG_DESC_TX_2_L2_TRANSLATION_TYPE_MSK 0xC0000
#define MAC_CFG_DESC_TX_2_SNAP_HDR_INSERTION_EN_POS 20
#define MAC_CFG_DESC_TX_2_SNAP_HDR_INSERTION_EN_LEN 1
#define MAC_CFG_DESC_TX_2_SNAP_HDR_INSERTION_EN_MSK 0x100000
#define MAC_CFG_DESC_TX_2_VLAN_REMOVAL_EN_POS 21
#define MAC_CFG_DESC_TX_2_VLAN_REMOVAL_EN_LEN 1
#define MAC_CFG_DESC_TX_2_VLAN_REMOVAL_EN_MSK 0x200000
/* TX MAC Dword 3 */
#define MAC_CFG_DESC_TX_3_UCODE_CMD_POS 0
#define MAC_CFG_DESC_TX_3_UCODE_CMD_LEN 32
#define MAC_CFG_DESC_TX_3_UCODE_CMD_MSK 0xFFFFFFFF
/* TX DMA Dword 0 */
#define DMA_CFG_DESC_TX_0_L4_LENGTH_POS 0
#define DMA_CFG_DESC_TX_0_L4_LENGTH_LEN 8
#define DMA_CFG_DESC_TX_0_L4_LENGTH_MSK 0xFF
#define DMA_CFG_DESC_TX_0_CMD_EOP_POS 8
#define DMA_CFG_DESC_TX_0_CMD_EOP_LEN 1
#define DMA_CFG_DESC_TX_0_CMD_EOP_MSK 0x100
#define DMA_CFG_DESC_TX_0_CMD_MARK_WB_POS 9
#define DMA_CFG_DESC_TX_0_CMD_MARK_WB_LEN 1
#define DMA_CFG_DESC_TX_0_CMD_MARK_WB_MSK 0x200
#define DMA_CFG_DESC_TX_0_CMD_DMA_IT_POS 10
#define DMA_CFG_DESC_TX_0_CMD_DMA_IT_LEN 1
#define DMA_CFG_DESC_TX_0_CMD_DMA_IT_MSK 0x400
#define DMA_CFG_DESC_TX_0_SEGMENT_BUF_DETAILS_POS 11
#define DMA_CFG_DESC_TX_0_SEGMENT_BUF_DETAILS_LEN 2
#define DMA_CFG_DESC_TX_0_SEGMENT_BUF_DETAILS_MSK 0x1800
#define DMA_CFG_DESC_TX_0_TCP_SEG_EN_POS 13
#define DMA_CFG_DESC_TX_0_TCP_SEG_EN_LEN 1
#define DMA_CFG_DESC_TX_0_TCP_SEG_EN_MSK 0x2000
#define DMA_CFG_DESC_TX_0_IPV4_CHECKSUM_EN_POS 14
#define DMA_CFG_DESC_TX_0_IPV4_CHECKSUM_EN_LEN 1
#define DMA_CFG_DESC_TX_0_IPV4_CHECKSUM_EN_MSK 0x4000
#define DMA_CFG_DESC_TX_0_TCP_UDP_CHECKSUM_EN_POS 15
#define DMA_CFG_DESC_TX_0_TCP_UDP_CHECKSUM_EN_LEN 1
#define DMA_CFG_DESC_TX_0_TCP_UDP_CHECKSUM_EN_MSK 0x8000
#define DMA_CFG_DESC_TX_0_QID_POS 16
#define DMA_CFG_DESC_TX_0_QID_LEN 5
#define DMA_CFG_DESC_TX_0_QID_MSK 0x1F0000
#define DMA_CFG_DESC_TX_0_PSEUDO_HEADER_CALC_EN_POS 21
#define DMA_CFG_DESC_TX_0_PSEUDO_HEADER_CALC_EN_LEN 1
#define DMA_CFG_DESC_TX_0_PSEUDO_HEADER_CALC_EN_MSK 0x200000
#define DMA_CFG_DESC_TX_0_L4_TYPE_POS 30
#define DMA_CFG_DESC_TX_0_L4_TYPE_LEN 2
#define DMA_CFG_DESC_TX_0_L4_TYPE_MSK 0xC0000000 /* L4 type: 0-UDP, 2-TCP */
#define DMA_CFG_DESC_TX_OFFLOAD_CFG_MAC_LEN_POS 0
#define DMA_CFG_DESC_TX_OFFLOAD_CFG_MAC_LEN_LEN 7
#define DMA_CFG_DESC_TX_OFFLOAD_CFG_MAC_LEN_MSK 0x7F /* MAC hdr len */
#define DMA_CFG_DESC_TX_OFFLOAD_CFG_L3T_IPV4_POS 7
#define DMA_CFG_DESC_TX_OFFLOAD_CFG_L3T_IPV4_LEN 1
#define DMA_CFG_DESC_TX_OFFLOAD_CFG_L3T_IPV4_MSK 0x80 /* 1-IPv4, 0-IPv6 */
#define TX_DMA_STATUS_DU BIT(0)
/* Tx descriptor - DMA part
* [dword 0]
* bit 0.. 7 : l4_length:8 layer 4 length
* bit 8 : cmd_eop:1 This descriptor is the last one in the packet
* bit 9 : reserved
* bit 10 : cmd_dma_it:1 immediate interrupt
* bit 11..12 : SBD - Segment Buffer Details
* 00 - Header Segment
* 01 - First Data Segment
* 10 - Medium Data Segment
* 11 - Last Data Segment
* bit 13 : TSE - TCP Segmentation Enable
* bit 14 : IIC - Directs the HW to Insert IPv4 Checksum
* bit 15 : ITC - Directs the HW to Insert TCP/UDP Checksum
* bit 16..20 : QID - The target QID that the packet should be stored
* in the MAC.
* bit 21 : PO - Pseudo header Offload:
* 0 - Use the pseudo header value from the TCP checksum field
* 1- Calculate Pseudo header Checksum
* bit 22 : NC - No UDP Checksum
* bit 23..29 : reserved
* bit 30..31 : L4T - Layer 4 Type: 00 - UDP , 10 - TCP , 10, 11 - Reserved
* If L4Len equal 0, no L4 at all
* [dword 1]
* bit 0..31 : addr_low:32 The payload buffer low address
* [dword 2]
* bit 0..15 : addr_high:16 The payload buffer high address
* bit 16..23 : ip_length:8 The IP header length for the TX IP checksum
* offload feature
* bit 24..30 : mac_length:7
* bit 31 : ip_version:1 1 - IPv4, 0 - IPv6
* [dword 3]
* [byte 12] error
* bit 0 2 : mac_status:3
* bit 3 7 : reserved:5
* [byte 13] status
* bit 0 : DU:1 Descriptor Used
* bit 1 7 : reserved:7
* [word 7] length
*/
struct vring_tx_dma {
u32 d0;
struct wil_ring_dma_addr addr;
u8 ip_length;
u8 b11; /* 0..6: mac_length; 7:ip_version */
u8 error; /* 0..2: err; 3..7: reserved; */
u8 status; /* 0: used; 1..7; reserved */
__le16 length;
} __packed;
/* TSO type used in dma descriptor d0 bits 11-12 */
enum {
wil_tso_type_hdr = 0,
wil_tso_type_first = 1,
wil_tso_type_mid = 2,
wil_tso_type_lst = 3,
};
/* Rx descriptor - MAC part
* [dword 0]
* bit 0.. 3 : tid:4 The QoS (b3-0) TID Field
* bit 4.. 6 : cid:3 The Source index that was found during parsing the TA.
* This field is used to define the source of the packet
* bit 7 : MAC_id_valid:1, 1 if MAC virtual number is valid.
* bit 8.. 9 : mid:2 The MAC virtual number
* bit 10..11 : frame_type:2 : The FC (b3-2) - MPDU Type
* (management, data, control and extension)
* bit 12..15 : frame_subtype:4 : The FC (b7-4) - Frame Subtype
* bit 16..27 : seq_number:12 The received Sequence number field
* bit 28..31 : extended:4 extended subtype
* [dword 1]
* bit 0.. 3 : reserved
* bit 4.. 5 : key_id:2
* bit 6 : decrypt_bypass:1
* bit 7 : security:1 FC (b14)
* bit 8.. 9 : ds_bits:2 FC (b9-8)
* bit 10 : a_msdu_present:1 QoS (b7)
* bit 11 : a_msdu_type:1 QoS (b8)
* bit 12 : a_mpdu:1 part of AMPDU aggregation
* bit 13 : broadcast:1
* bit 14 : mutlicast:1
* bit 15 : reserved:1
* bit 16..20 : rx_mac_qid:5 The Queue Identifier that the packet
* is received from
* bit 21..24 : mcs:4
* bit 25..28 : mic_icr:4 this signal tells the DMA to assert an interrupt
* after it writes the packet
* bit 29..31 : reserved:3
* [dword 2]
* bit 0.. 2 : time_slot:3 The timeslot that the MPDU is received
* bit 3.. 4 : fc_protocol_ver:1 The FC (b1-0) - Protocol Version
* bit 5 : fc_order:1 The FC Control (b15) -Order
* bit 6.. 7 : qos_ack_policy:2 The QoS (b6-5) ack policy Field
* bit 8 : esop:1 The QoS (b4) ESOP field
* bit 9 : qos_rdg_more_ppdu:1 The QoS (b9) RDG field
* bit 10..14 : qos_reserved:5 The QoS (b14-10) Reserved field
* bit 15 : qos_ac_constraint:1 QoS (b15)
* bit 16..31 : pn_15_0:16 low 2 bytes of PN
* [dword 3]
* bit 0..31 : pn_47_16:32 high 4 bytes of PN
*/
struct vring_rx_mac {
u32 d0;
u32 d1;
u16 w4;
struct_group_attr(pn, __packed,
u16 pn_15_0;
u32 pn_47_16;
);
} __packed;
/* Rx descriptor - DMA part
* [dword 0]
* bit 0.. 7 : l4_length:8 layer 4 length. The field is only valid if
* L4I bit is set
* bit 8 : cmd_eop:1 set to 1
* bit 9 : cmd_rt:1 set to 1
* bit 10 : cmd_dma_it:1 immediate interrupt
* bit 11..15 : reserved:5
* bit 16..29 : phy_info_length:14 It is valid when the PII is set.
* When the FFM bit is set bits 29-27 are used for
* Flex Filter Match. Matching Index to one of the L2
* EtherType Flex Filter
* bit 30..31 : l4_type:2 valid if the L4I bit is set in the status field
* 00 - UDP, 01 - TCP, 10, 11 - reserved
* [dword 1]
* bit 0..31 : addr_low:32 The payload buffer low address
* [dword 2]
* bit 0..15 : addr_high:16 The payload buffer high address
* bit 16..23 : ip_length:8 The filed is valid only if the L3I bit is set
* bit 24..30 : mac_length:7
* bit 31 : ip_version:1 1 - IPv4, 0 - IPv6
* [dword 3]
* [byte 12] error
* bit 0 : FCS:1
* bit 1 : MIC:1
* bit 2 : Key miss:1
* bit 3 : Replay:1
* bit 4 : L3:1 IPv4 checksum
* bit 5 : L4:1 TCP/UDP checksum
* bit 6 7 : reserved:2
* [byte 13] status
* bit 0 : DU:1 Descriptor Used
* bit 1 : EOP:1 The descriptor indicates the End of Packet
* bit 2 : error:1
* bit 3 : MI:1 MAC Interrupt is asserted (according to parser decision)
* bit 4 : L3I:1 L3 identified and checksum calculated
* bit 5 : L4I:1 L4 identified and checksum calculated
* bit 6 : PII:1 PHY Info Included in the packet
* bit 7 : FFM:1 EtherType Flex Filter Match
* [word 7] length
*/
#define RX_DMA_D0_CMD_DMA_EOP BIT(8)
#define RX_DMA_D0_CMD_DMA_RT BIT(9) /* always 1 */
#define RX_DMA_D0_CMD_DMA_IT BIT(10) /* interrupt */
#define RX_MAC_D0_MAC_ID_VALID BIT(7)
/* Error field */
#define RX_DMA_ERROR_FCS BIT(0)
#define RX_DMA_ERROR_MIC BIT(1)
#define RX_DMA_ERROR_KEY BIT(2) /* Key missing */
#define RX_DMA_ERROR_REPLAY BIT(3)
#define RX_DMA_ERROR_L3_ERR BIT(4)
#define RX_DMA_ERROR_L4_ERR BIT(5)
/* Status field */
#define RX_DMA_STATUS_DU BIT(0)
#define RX_DMA_STATUS_EOP BIT(1)
#define RX_DMA_STATUS_ERROR BIT(2)
#define RX_DMA_STATUS_MI BIT(3) /* MAC Interrupt is asserted */
#define RX_DMA_STATUS_L3I BIT(4)
#define RX_DMA_STATUS_L4I BIT(5)
#define RX_DMA_STATUS_PHY_INFO BIT(6)
#define RX_DMA_STATUS_FFM BIT(7) /* EtherType Flex Filter Match */
/* IEEE 802.11, 8.5.2 EAPOL-Key frames */
#define WIL_KEY_INFO_KEY_TYPE BIT(3) /* val of 1 = Pairwise, 0 = Group key */
#define WIL_KEY_INFO_MIC BIT(8)
#define WIL_KEY_INFO_ENCR_KEY_DATA BIT(12) /* for rsn only */
#define WIL_EAP_NONCE_LEN 32
#define WIL_EAP_KEY_RSC_LEN 8
#define WIL_EAP_REPLAY_COUNTER_LEN 8
#define WIL_EAP_KEY_IV_LEN 16
#define WIL_EAP_KEY_ID_LEN 8
enum {
WIL_1X_TYPE_EAP_PACKET = 0,
WIL_1X_TYPE_EAPOL_START = 1,
WIL_1X_TYPE_EAPOL_LOGOFF = 2,
WIL_1X_TYPE_EAPOL_KEY = 3,
};
#define WIL_EAPOL_KEY_TYPE_RSN 2
#define WIL_EAPOL_KEY_TYPE_WPA 254
struct wil_1x_hdr {
u8 version;
u8 type;
__be16 length;
/* followed by data */
} __packed;
struct wil_eapol_key {
u8 type;
__be16 key_info;
__be16 key_length;
u8 replay_counter[WIL_EAP_REPLAY_COUNTER_LEN];
u8 key_nonce[WIL_EAP_NONCE_LEN];
u8 key_iv[WIL_EAP_KEY_IV_LEN];
u8 key_rsc[WIL_EAP_KEY_RSC_LEN];
u8 key_id[WIL_EAP_KEY_ID_LEN];
} __packed;
struct vring_rx_dma {
u32 d0;
struct wil_ring_dma_addr addr;
u8 ip_length;
u8 b11;
u8 error;
u8 status;
__le16 length;
} __packed;
struct vring_tx_desc {
struct vring_tx_mac mac;
struct vring_tx_dma dma;
} __packed;
union wil_tx_desc {
struct vring_tx_desc legacy;
struct wil_tx_enhanced_desc enhanced;
} __packed;
struct vring_rx_desc {
struct vring_rx_mac mac;
struct vring_rx_dma dma;
} __packed;
union wil_rx_desc {
struct vring_rx_desc legacy;
struct wil_rx_enhanced_desc enhanced;
} __packed;
union wil_ring_desc {
union wil_tx_desc tx;
union wil_rx_desc rx;
} __packed;
struct packet_rx_info {
u8 cid;
};
/* this struct will be stored in the skb cb buffer
* max length of the struct is limited to 48 bytes
*/
struct skb_rx_info {
struct vring_rx_desc rx_desc;
struct packet_rx_info rx_info;
};
static inline int wil_rxdesc_tid(struct vring_rx_desc *d)
{
return WIL_GET_BITS(d->mac.d0, 0, 3);
}
static inline int wil_rxdesc_cid(struct vring_rx_desc *d)
{
return WIL_GET_BITS(d->mac.d0, 4, 6);
}
static inline int wil_rxdesc_mid(struct vring_rx_desc *d)
{
return (d->mac.d0 & RX_MAC_D0_MAC_ID_VALID) ?
WIL_GET_BITS(d->mac.d0, 8, 9) : 0;
}
static inline int wil_rxdesc_ftype(struct vring_rx_desc *d)
{
return WIL_GET_BITS(d->mac.d0, 10, 11);
}
static inline int wil_rxdesc_subtype(struct vring_rx_desc *d)
{
return WIL_GET_BITS(d->mac.d0, 12, 15);
}
/* 1-st byte (with frame type/subtype) of FC field */
static inline u8 wil_rxdesc_fc1(struct vring_rx_desc *d)
{
return (u8)(WIL_GET_BITS(d->mac.d0, 10, 15) << 2);
}
static inline int wil_rxdesc_seq(struct vring_rx_desc *d)
{
return WIL_GET_BITS(d->mac.d0, 16, 27);
}
static inline int wil_rxdesc_ext_subtype(struct vring_rx_desc *d)
{
return WIL_GET_BITS(d->mac.d0, 28, 31);
}
static inline int wil_rxdesc_retry(struct vring_rx_desc *d)
{
return WIL_GET_BITS(d->mac.d0, 31, 31);
}
static inline int wil_rxdesc_key_id(struct vring_rx_desc *d)
{
return WIL_GET_BITS(d->mac.d1, 4, 5);
}
static inline int wil_rxdesc_security(struct vring_rx_desc *d)
{
return WIL_GET_BITS(d->mac.d1, 7, 7);
}
static inline int wil_rxdesc_ds_bits(struct vring_rx_desc *d)
{
return WIL_GET_BITS(d->mac.d1, 8, 9);
}
static inline int wil_rxdesc_mcs(struct vring_rx_desc *d)
{
return WIL_GET_BITS(d->mac.d1, 21, 24);
}
static inline int wil_rxdesc_mcast(struct vring_rx_desc *d)
{
return WIL_GET_BITS(d->mac.d1, 13, 14);
}
static inline struct vring_rx_desc *wil_skb_rxdesc(struct sk_buff *skb)
{
return (void *)skb->cb;
}
static inline int wil_ring_is_empty(struct wil_ring *ring)
{
return ring->swhead == ring->swtail;
}
static inline u32 wil_ring_next_tail(struct wil_ring *ring)
{
return (ring->swtail + 1) % ring->size;
}
static inline void wil_ring_advance_head(struct wil_ring *ring, int n)
{
ring->swhead = (ring->swhead + n) % ring->size;
}
static inline int wil_ring_is_full(struct wil_ring *ring)
{
return wil_ring_next_tail(ring) == ring->swhead;
}
static inline u8 *wil_skb_get_da(struct sk_buff *skb)
{
struct ethhdr *eth = (void *)skb->data;
return eth->h_dest;
}
static inline u8 *wil_skb_get_sa(struct sk_buff *skb)
{
struct ethhdr *eth = (void *)skb->data;
return eth->h_source;
}
static inline bool wil_need_txstat(struct sk_buff *skb)
{
const u8 *da = wil_skb_get_da(skb);
return is_unicast_ether_addr(da) && sk_requests_wifi_status(skb->sk);
}
static inline void wil_consume_skb(struct sk_buff *skb, bool acked)
{
if (unlikely(wil_need_txstat(skb)))
skb_complete_wifi_ack(skb, acked);
else
acked ? dev_consume_skb_any(skb) : dev_kfree_skb_any(skb);
}
/* Used space in Tx ring */
static inline int wil_ring_used_tx(struct wil_ring *ring)
{
u32 swhead = ring->swhead;
u32 swtail = ring->swtail;
return (ring->size + swhead - swtail) % ring->size;
}
/* Available space in Tx ring */
static inline int wil_ring_avail_tx(struct wil_ring *ring)
{
return ring->size - wil_ring_used_tx(ring) - 1;
}
static inline int wil_get_min_tx_ring_id(struct wil6210_priv *wil)
{
/* In Enhanced DMA ring 0 is reserved for RX */
return wil->use_enhanced_dma_hw ? 1 : 0;
}
/* similar to ieee80211_ version, but FC contain only 1-st byte */
static inline int wil_is_back_req(u8 fc)
{
return (fc & (IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE)) ==
(IEEE80211_FTYPE_CTL | IEEE80211_STYPE_BACK_REQ);
}
/* wil_val_in_range - check if value in [min,max) */
static inline bool wil_val_in_range(int val, int min, int max)
{
return val >= min && val < max;
}
static inline u8 wil_skb_get_cid(struct sk_buff *skb)
{
struct skb_rx_info *skb_rx_info = (void *)skb->cb;
return skb_rx_info->rx_info.cid;
}
static inline void wil_skb_set_cid(struct sk_buff *skb, u8 cid)
{
struct skb_rx_info *skb_rx_info = (void *)skb->cb;
skb_rx_info->rx_info.cid = cid;
}
void wil_netif_rx_any(struct sk_buff *skb, struct net_device *ndev);
void wil_netif_rx(struct sk_buff *skb, struct net_device *ndev, int cid,
struct wil_net_stats *stats, bool gro);
void wil_rx_reorder(struct wil6210_priv *wil, struct sk_buff *skb);
void wil_rx_bar(struct wil6210_priv *wil, struct wil6210_vif *vif,
u8 cid, u8 tid, u16 seq);
struct wil_tid_ampdu_rx *wil_tid_ampdu_rx_alloc(struct wil6210_priv *wil,
int size, u16 ssn);
void wil_tid_ampdu_rx_free(struct wil6210_priv *wil,
struct wil_tid_ampdu_rx *r);
void wil_tx_data_init(struct wil_ring_tx_data *txdata);
void wil_init_txrx_ops_legacy_dma(struct wil6210_priv *wil);
void wil_tx_latency_calc(struct wil6210_priv *wil, struct sk_buff *skb,
struct wil_sta_info *sta);
#endif /* WIL6210_TXRX_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,604 @@
/* SPDX-License-Identifier: ISC */
/*
* Copyright (c) 2012-2016,2018-2019, The Linux Foundation. All rights reserved.
*/
#ifndef WIL6210_TXRX_EDMA_H
#define WIL6210_TXRX_EDMA_H
#include "wil6210.h"
/* limit status ring size in range [ring size..max ring size] */
#define WIL_SRING_SIZE_ORDER_MIN (WIL_RING_SIZE_ORDER_MIN)
#define WIL_SRING_SIZE_ORDER_MAX (WIL_RING_SIZE_ORDER_MAX)
/* RX sring order should be bigger than RX ring order */
#define WIL_RX_SRING_SIZE_ORDER_DEFAULT (12)
#define WIL_TX_SRING_SIZE_ORDER_DEFAULT (14)
#define WIL_RX_BUFF_ARR_SIZE_DEFAULT (2600)
#define WIL_DEFAULT_RX_STATUS_RING_ID 0
#define WIL_RX_DESC_RING_ID 0
#define WIL_RX_STATUS_IRQ_IDX 0
#define WIL_TX_STATUS_IRQ_IDX 1
#define WIL_EDMA_AGG_WATERMARK (0xffff)
#define WIL_EDMA_AGG_WATERMARK_POS (16)
#define WIL_EDMA_IDLE_TIME_LIMIT_USEC (50)
#define WIL_EDMA_TIME_UNIT_CLK_CYCLES (330) /* fits 1 usec */
/* Error field */
#define WIL_RX_EDMA_ERROR_MIC (1)
#define WIL_RX_EDMA_ERROR_KEY (2) /* Key missing */
#define WIL_RX_EDMA_ERROR_REPLAY (3)
#define WIL_RX_EDMA_ERROR_AMSDU (4)
#define WIL_RX_EDMA_ERROR_FCS (7)
#define WIL_RX_EDMA_ERROR_L3_ERR (BIT(0) | BIT(1))
#define WIL_RX_EDMA_ERROR_L4_ERR (BIT(0) | BIT(1))
#define WIL_RX_EDMA_DLPF_LU_MISS_BIT BIT(11)
#define WIL_RX_EDMA_DLPF_LU_MISS_CID_TID_MASK 0x7
#define WIL_RX_EDMA_DLPF_LU_HIT_CID_TID_MASK 0xf
#define WIL_RX_EDMA_DLPF_LU_MISS_CID_POS 2
#define WIL_RX_EDMA_DLPF_LU_HIT_CID_POS 4
#define WIL_RX_EDMA_DLPF_LU_MISS_TID_POS 5
#define WIL_RX_EDMA_MID_VALID_BIT BIT(20)
#define WIL_EDMA_DESC_TX_MAC_CFG_0_QID_POS 16
#define WIL_EDMA_DESC_TX_MAC_CFG_0_QID_LEN 6
#define WIL_EDMA_DESC_TX_CFG_EOP_POS 0
#define WIL_EDMA_DESC_TX_CFG_EOP_LEN 1
#define WIL_EDMA_DESC_TX_CFG_TSO_DESC_TYPE_POS 3
#define WIL_EDMA_DESC_TX_CFG_TSO_DESC_TYPE_LEN 2
#define WIL_EDMA_DESC_TX_CFG_SEG_EN_POS 5
#define WIL_EDMA_DESC_TX_CFG_SEG_EN_LEN 1
#define WIL_EDMA_DESC_TX_CFG_INSERT_IP_CHKSUM_POS 6
#define WIL_EDMA_DESC_TX_CFG_INSERT_IP_CHKSUM_LEN 1
#define WIL_EDMA_DESC_TX_CFG_INSERT_TCP_CHKSUM_POS 7
#define WIL_EDMA_DESC_TX_CFG_INSERT_TCP_CHKSUM_LEN 1
#define WIL_EDMA_DESC_TX_CFG_L4_TYPE_POS 15
#define WIL_EDMA_DESC_TX_CFG_L4_TYPE_LEN 1
#define WIL_EDMA_DESC_TX_CFG_PSEUDO_HEADER_CALC_EN_POS 5
#define WIL_EDMA_DESC_TX_CFG_PSEUDO_HEADER_CALC_EN_LEN 1
/* Enhanced Rx descriptor - MAC part
* [dword 0] : Reserved
* [dword 1] : Reserved
* [dword 2] : Reserved
* [dword 3]
* bit 0..15 : Buffer ID
* bit 16..31 : Reserved
*/
struct wil_ring_rx_enhanced_mac {
u32 d[3];
__le16 buff_id;
u16 reserved;
} __packed;
/* Enhanced Rx descriptor - DMA part
* [dword 0] - Reserved
* [dword 1]
* bit 0..31 : addr_low:32 The payload buffer address, bits 0-31
* [dword 2]
* bit 0..15 : addr_high_low:16 The payload buffer address, bits 32-47
* bit 16..31 : Reserved
* [dword 3]
* bit 0..15 : addr_high_high:16 The payload buffer address, bits 48-63
* bit 16..31 : length
*/
struct wil_ring_rx_enhanced_dma {
u32 d0;
struct wil_ring_dma_addr addr;
u16 w5;
__le16 addr_high_high;
__le16 length;
} __packed;
struct wil_rx_enhanced_desc {
struct wil_ring_rx_enhanced_mac mac;
struct wil_ring_rx_enhanced_dma dma;
} __packed;
/* Enhanced Tx descriptor - DMA part
* [dword 0]
* Same as legacy
* [dword 1]
* bit 0..31 : addr_low:32 The payload buffer address, bits 0-31
* [dword 2]
* bit 0..15 : addr_high_low:16 The payload buffer address, bits 32-47
* bit 16..23 : ip_length:8 The IP header length for the TX IP checksum
* offload feature
* bit 24..30 : mac_length:7
* bit 31 : ip_version:1 1 - IPv4, 0 - IPv6
* [dword 3]
* bit 0..15 : addr_high_high:16 The payload buffer address, bits 48-63
* bit 16..31 : length
*/
struct wil_ring_tx_enhanced_dma {
u8 l4_hdr_len;
u8 cmd;
u16 w1;
struct wil_ring_dma_addr addr;
u8 ip_length;
u8 b11; /* 0..6: mac_length; 7:ip_version */
__le16 addr_high_high;
__le16 length;
} __packed;
/* Enhanced Tx descriptor - MAC part
* [dword 0]
* bit 0.. 9 : lifetime_expiry_value:10
* bit 10 : interrupt_en:1
* bit 11 : status_en:1
* bit 12..13 : txss_override:2
* bit 14 : timestamp_insertion:1
* bit 15 : duration_preserve:1
* bit 16..21 : reserved0:6
* bit 22..26 : mcs_index:5
* bit 27 : mcs_en:1
* bit 28..30 : reserved1:3
* bit 31 : sn_preserved:1
* [dword 1]
* bit 0.. 3 : pkt_mode:4
* bit 4 : pkt_mode_en:1
* bit 5..14 : reserved0:10
* bit 15 : ack_policy_en:1
* bit 16..19 : dst_index:4
* bit 20 : dst_index_en:1
* bit 21..22 : ack_policy:2
* bit 23 : lifetime_en:1
* bit 24..30 : max_retry:7
* bit 31 : max_retry_en:1
* [dword 2]
* bit 0.. 7 : num_of_descriptors:8
* bit 8..17 : reserved:10
* bit 18..19 : l2_translation_type:2 00 - bypass, 01 - 802.3, 10 - 802.11
* bit 20 : snap_hdr_insertion_en:1
* bit 21 : vlan_removal_en:1
* bit 22..23 : reserved0:2
* bit 24 : Dest ID extension:1
* bit 25..31 : reserved0:7
* [dword 3]
* bit 0..15 : tso_mss:16
* bit 16..31 : descriptor_scratchpad:16 - mailbox between driver and ucode
*/
struct wil_ring_tx_enhanced_mac {
u32 d[3];
__le16 tso_mss;
u16 scratchpad;
} __packed;
struct wil_tx_enhanced_desc {
struct wil_ring_tx_enhanced_mac mac;
struct wil_ring_tx_enhanced_dma dma;
} __packed;
#define TX_STATUS_DESC_READY_POS 7
/* Enhanced TX status message
* [dword 0]
* bit 0.. 7 : Number of Descriptor:8 - The number of descriptors that
* are used to form the packets. It is needed for WB when
* releasing the packet
* bit 8..15 : tx_ring_id:8 The transmission ring ID that is related to
* the message
* bit 16..23 : Status:8 - The TX status Code
* 0x0 - A successful transmission
* 0x1 - Retry expired
* 0x2 - Lifetime Expired
* 0x3 - Released
* 0x4-0xFF - Reserved
* bit 24..30 : Reserved:7
* bit 31 : Descriptor Ready bit:1 - It is initiated to
* zero by the driver when the ring is created. It is set by the HW
* to one for each completed status message. Each wrap around,
* the DR bit value is flipped.
* [dword 1]
* bit 0..31 : timestamp:32 - Set when MPDU is transmitted.
* [dword 2]
* bit 0.. 4 : MCS:5 - The transmitted MCS value
* bit 5 : Reserved:1
* bit 6.. 7 : CB mode:2 - 0-DMG 1-EDMG 2-Wide
* bit 8..12 : QID:5 - The QID that was used for the transmission
* bit 13..15 : Reserved:3
* bit 16..20 : Num of MSDUs:5 - Number of MSDUs in the aggregation
* bit 21..22 : Reserved:2
* bit 23 : Retry:1 - An indication that the transmission was retried
* bit 24..31 : TX-Sector:8 - the antenna sector that was used for
* transmission
* [dword 3]
* bit 0..11 : Sequence number:12 - The Sequence Number that was used
* for the MPDU transmission
* bit 12..31 : Reserved:20
*/
struct wil_ring_tx_status {
u8 num_descriptors;
u8 ring_id;
u8 status;
u8 desc_ready; /* Only the last bit should be set */
u32 timestamp;
u32 d2;
u16 seq_number; /* Only the first 12 bits */
u16 w7;
} __packed;
/* Enhanced Rx status message - compressed part
* [dword 0]
* bit 0.. 2 : L2 Rx Status:3 - The L2 packet reception Status
* 0-Success, 1-MIC Error, 2-Key Error, 3-Replay Error,
* 4-A-MSDU Error, 5-Reserved, 6-Reserved, 7-FCS Error
* bit 3.. 4 : L3 Rx Status:2 - Bit0 - L3I - L3 identified and checksum
* calculated, Bit1- L3Err - IPv4 Checksum Error
* bit 5.. 6 : L4 Rx Status:2 - Bit0 - L4I - L4 identified and checksum
* calculated, Bit1- L4Err - TCP/UDP Checksum Error
* bit 7 : Reserved:1
* bit 8..19 : Flow ID:12 - MSDU flow ID
* bit 20 : MID_V:1 - The MAC ID field is valid
* bit 21..22 : MID:2 - The MAC ID
* bit 23 : L3T:1 - IP types: 0-IPv6, 1-IPv4
* bit 24 : L4T:1 - Layer 4 Type: 0-UDP, 1-TCP
* bit 25 : BC:1 - The received MPDU is broadcast
* bit 26 : MC:1 - The received MPDU is multicast
* bit 27 : Raw:1 - The MPDU received with no translation
* bit 28 : Sec:1 - The FC control (b14) - Frame Protected
* bit 29 : Error:1 - An error is set when (L2 status != 0) ||
* (L3 status == 3) || (L4 status == 3)
* bit 30 : EOP:1 - End of MSDU signaling. It is set to mark the end
* of the transfer, otherwise the status indicates buffer
* only completion.
* bit 31 : Descriptor Ready bit:1 - It is initiated to
* zero by the driver when the ring is created. It is set
* by the HW to one for each completed status message.
* Each wrap around, the DR bit value is flipped.
* [dword 1]
* bit 0.. 5 : MAC Len:6 - The number of bytes that are used for L2 header
* bit 6..11 : IPLEN:6 - The number of DW that are used for L3 header
* bit 12..15 : I4Len:4 - The number of DW that are used for L4 header
* bit 16..21 : MCS:6 - The received MCS field from the PLCP Header
* bit 22..23 : CB mode:2 - The CB Mode: 0-DMG, 1-EDMG, 2-Wide
* bit 24..27 : Data Offset:4 - The data offset, a code that describe the
* payload shift from the beginning of the buffer:
* 0 - 0 Bytes, 3 - 2 Bytes
* bit 28 : A-MSDU Present:1 - The QoS (b7) A-MSDU present field
* bit 29 : A-MSDU Type:1 The QoS (b8) A-MSDU Type field
* bit 30 : A-MPDU:1 - Packet is part of aggregated MPDU
* bit 31 : Key ID:1 - The extracted Key ID from the encryption header
* [dword 2]
* bit 0..15 : Buffer ID:16 - The Buffer Identifier
* bit 16..31 : Length:16 - It indicates the valid bytes that are stored
* in the current descriptor buffer. For multiple buffer
* descriptor, SW need to sum the total descriptor length
* in all buffers to produce the packet length
* [dword 3]
* bit 0..31 : timestamp:32 - The MPDU Timestamp.
*/
struct wil_rx_status_compressed {
u32 d0;
u32 d1;
__le16 buff_id;
__le16 length;
u32 timestamp;
} __packed;
/* Enhanced Rx status message - extension part
* [dword 0]
* bit 0.. 4 : QID:5 - The Queue Identifier that the packet is received
* from
* bit 5.. 7 : Reserved:3
* bit 8..11 : TID:4 - The QoS (b3-0) TID Field
* bit 12..15 Source index:4 - The Source index that was found
during Parsing the TA. This field is used to define the
source of the packet
* bit 16..18 : Destination index:3 - The Destination index that
was found during Parsing the RA.
* bit 19..20 : DS Type:2 - The FC Control (b9-8) - From / To DS
* bit 21..22 : MIC ICR:2 - this signal tells the DMA to assert an
interrupt after it writes the packet
* bit 23 : ESOP:1 - The QoS (b4) ESOP field
* bit 24 : RDG:1
* bit 25..31 : Reserved:7
* [dword 1]
* bit 0.. 1 : Frame Type:2 - The FC Control (b3-2) - MPDU Type
(management, data, control and extension)
* bit 2.. 5 : Syb type:4 - The FC Control (b7-4) - Frame Subtype
* bit 6..11 : Ext sub type:6 - The FC Control (b11-8) - Frame Extended
* Subtype
* bit 12..13 : ACK Policy:2 - The QoS (b6-5) ACK Policy fields
* bit 14 : DECRYPT_BYP:1 - The MPDU is bypass by the decryption unit
* bit 15..23 : Reserved:9
* bit 24..31 : RSSI/SNR:8 - The RSSI / SNR measurement for the received
* MPDU
* [dword 2]
* bit 0..11 : SN:12 - The received Sequence number field
* bit 12..15 : Reserved:4
* bit 16..31 : PN bits [15:0]:16
* [dword 3]
* bit 0..31 : PN bits [47:16]:32
*/
struct wil_rx_status_extension {
u32 d0;
u32 d1;
__le16 seq_num; /* only lower 12 bits */
struct_group_attr(pn, __packed,
u16 pn_15_0;
u32 pn_47_16;
);
} __packed;
struct wil_rx_status_extended {
struct wil_rx_status_compressed comp;
struct wil_rx_status_extension ext;
} __packed;
static inline void *wil_skb_rxstatus(struct sk_buff *skb)
{
return (void *)skb->cb;
}
static inline __le16 wil_rx_status_get_length(void *msg)
{
return ((struct wil_rx_status_compressed *)msg)->length;
}
static inline u8 wil_rx_status_get_mcs(void *msg)
{
return WIL_GET_BITS(((struct wil_rx_status_compressed *)msg)->d1,
16, 21);
}
static inline u8 wil_rx_status_get_cb_mode(void *msg)
{
return WIL_GET_BITS(((struct wil_rx_status_compressed *)msg)->d1,
22, 23);
}
static inline u16 wil_rx_status_get_flow_id(void *msg)
{
return WIL_GET_BITS(((struct wil_rx_status_compressed *)msg)->d0,
8, 19);
}
static inline u8 wil_rx_status_get_mcast(void *msg)
{
return WIL_GET_BITS(((struct wil_rx_status_compressed *)msg)->d0,
26, 26);
}
/**
* In case of DLPF miss the parsing of flow Id should be as follows:
* dest_id:2
* src_id :3 - cid
* tid:3
* Otherwise:
* tid:4
* cid:4
*/
static inline u8 wil_rx_status_get_cid(void *msg)
{
u16 val = wil_rx_status_get_flow_id(msg);
if (val & WIL_RX_EDMA_DLPF_LU_MISS_BIT)
/* CID is in bits 2..4 */
return (val >> WIL_RX_EDMA_DLPF_LU_MISS_CID_POS) &
WIL_RX_EDMA_DLPF_LU_MISS_CID_TID_MASK;
else
/* CID is in bits 4..7 */
return (val >> WIL_RX_EDMA_DLPF_LU_HIT_CID_POS) &
WIL_RX_EDMA_DLPF_LU_HIT_CID_TID_MASK;
}
static inline u8 wil_rx_status_get_tid(void *msg)
{
u16 val = wil_rx_status_get_flow_id(msg);
if (val & WIL_RX_EDMA_DLPF_LU_MISS_BIT)
/* TID is in bits 5..7 */
return (val >> WIL_RX_EDMA_DLPF_LU_MISS_TID_POS) &
WIL_RX_EDMA_DLPF_LU_MISS_CID_TID_MASK;
else
/* TID is in bits 0..3 */
return val & WIL_RX_EDMA_DLPF_LU_MISS_CID_TID_MASK;
}
static inline int wil_rx_status_get_eop(void *msg) /* EoP = End of Packet */
{
return WIL_GET_BITS(((struct wil_rx_status_compressed *)msg)->d0,
30, 30);
}
static inline void wil_rx_status_reset_buff_id(struct wil_status_ring *s)
{
((struct wil_rx_status_compressed *)
(s->va + (s->elem_size * s->swhead)))->buff_id = 0;
}
static inline __le16 wil_rx_status_get_buff_id(void *msg)
{
return ((struct wil_rx_status_compressed *)msg)->buff_id;
}
static inline u8 wil_rx_status_get_data_offset(void *msg)
{
u8 val = WIL_GET_BITS(((struct wil_rx_status_compressed *)msg)->d1,
24, 27);
switch (val) {
case 0: return 0;
case 3: return 2;
default: return 0xFF;
}
}
static inline int wil_rx_status_get_frame_type(struct wil6210_priv *wil,
void *msg)
{
if (wil->use_compressed_rx_status)
return IEEE80211_FTYPE_DATA;
return WIL_GET_BITS(((struct wil_rx_status_extended *)msg)->ext.d1,
0, 1) << 2;
}
static inline int wil_rx_status_get_fc1(struct wil6210_priv *wil, void *msg)
{
if (wil->use_compressed_rx_status)
return 0;
return WIL_GET_BITS(((struct wil_rx_status_extended *)msg)->ext.d1,
0, 5) << 2;
}
static inline __le16 wil_rx_status_get_seq(struct wil6210_priv *wil, void *msg)
{
if (wil->use_compressed_rx_status)
return 0;
return ((struct wil_rx_status_extended *)msg)->ext.seq_num;
}
static inline u8 wil_rx_status_get_retry(void *msg)
{
/* retry bit is missing in EDMA HW. return 1 to be on the safe side */
return 1;
}
static inline int wil_rx_status_get_mid(void *msg)
{
if (!(((struct wil_rx_status_compressed *)msg)->d0 &
WIL_RX_EDMA_MID_VALID_BIT))
return 0; /* use the default MID */
return WIL_GET_BITS(((struct wil_rx_status_compressed *)msg)->d0,
21, 22);
}
static inline int wil_rx_status_get_error(void *msg)
{
return WIL_GET_BITS(((struct wil_rx_status_compressed *)msg)->d0,
29, 29);
}
static inline int wil_rx_status_get_l2_rx_status(void *msg)
{
return WIL_GET_BITS(((struct wil_rx_status_compressed *)msg)->d0,
0, 2);
}
static inline int wil_rx_status_get_l3_rx_status(void *msg)
{
return WIL_GET_BITS(((struct wil_rx_status_compressed *)msg)->d0,
3, 4);
}
static inline int wil_rx_status_get_l4_rx_status(void *msg)
{
return WIL_GET_BITS(((struct wil_rx_status_compressed *)msg)->d0,
5, 6);
}
/* L4 L3 Expected result
* 0 0 Ok. No L3 and no L4 known protocols found.
* Treated as L2 packet. (no offloads on this packet)
* 0 1 Ok. It means that L3 was found, and checksum check passed.
* No known L4 protocol was found.
* 0 2 It means that L3 protocol was found, and checksum check failed.
* No L4 known protocol was found.
* 1 any Ok. It means that L4 was found, and checksum check passed.
* 3 0 Not a possible scenario.
* 3 1 Recalculate. It means that L3 protocol was found, and checksum
* passed. But L4 checksum failed. Need to see if really failed,
* or due to fragmentation.
* 3 2 Both L3 and L4 checksum check failed.
*/
static inline int wil_rx_status_get_checksum(void *msg,
struct wil_net_stats *stats)
{
int l3_rx_status = wil_rx_status_get_l3_rx_status(msg);
int l4_rx_status = wil_rx_status_get_l4_rx_status(msg);
if (l4_rx_status == 1)
return CHECKSUM_UNNECESSARY;
if (l4_rx_status == 0 && l3_rx_status == 1)
return CHECKSUM_UNNECESSARY;
if (l3_rx_status == 0 && l4_rx_status == 0)
/* L2 packet */
return CHECKSUM_NONE;
/* If HW reports bad checksum, let IP stack re-check it
* For example, HW doesn't understand Microsoft IP stack that
* mis-calculates TCP checksum - if it should be 0x0,
* it writes 0xffff in violation of RFC 1624
*/
stats->rx_csum_err++;
return CHECKSUM_NONE;
}
static inline int wil_rx_status_get_security(void *msg)
{
return WIL_GET_BITS(((struct wil_rx_status_compressed *)msg)->d0,
28, 28);
}
static inline u8 wil_rx_status_get_key_id(void *msg)
{
return WIL_GET_BITS(((struct wil_rx_status_compressed *)msg)->d1,
31, 31);
}
static inline u8 wil_tx_status_get_mcs(struct wil_ring_tx_status *msg)
{
return WIL_GET_BITS(msg->d2, 0, 4);
}
static inline u32 wil_ring_next_head(struct wil_ring *ring)
{
return (ring->swhead + 1) % ring->size;
}
static inline void wil_desc_set_addr_edma(struct wil_ring_dma_addr *addr,
__le16 *addr_high_high,
dma_addr_t pa)
{
addr->addr_low = cpu_to_le32(lower_32_bits(pa));
addr->addr_high = cpu_to_le16((u16)upper_32_bits(pa));
*addr_high_high = cpu_to_le16((u16)(upper_32_bits(pa) >> 16));
}
static inline
dma_addr_t wil_tx_desc_get_addr_edma(struct wil_ring_tx_enhanced_dma *dma)
{
return le32_to_cpu(dma->addr.addr_low) |
((u64)le16_to_cpu(dma->addr.addr_high) << 32) |
((u64)le16_to_cpu(dma->addr_high_high) << 48);
}
static inline
dma_addr_t wil_rx_desc_get_addr_edma(struct wil_ring_rx_enhanced_dma *dma)
{
return le32_to_cpu(dma->addr.addr_low) |
((u64)le16_to_cpu(dma->addr.addr_high) << 32) |
((u64)le16_to_cpu(dma->addr_high_high) << 48);
}
void wil_configure_interrupt_moderation_edma(struct wil6210_priv *wil);
int wil_tx_sring_handler(struct wil6210_priv *wil,
struct wil_status_ring *sring);
void wil_rx_handle_edma(struct wil6210_priv *wil, int *quota);
void wil_init_txrx_ops_edma(struct wil6210_priv *wil);
#endif /* WIL6210_TXRX_EDMA_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,122 @@
// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2015,2017 Qualcomm Atheros, Inc.
* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
*/
#include "wil6210.h"
#include <linux/devcoredump.h>
static int wil_fw_get_crash_dump_bounds(struct wil6210_priv *wil,
u32 *out_dump_size, u32 *out_host_min)
{
int i;
const struct fw_map *map;
u32 host_min, host_max, tmp_max;
if (!out_dump_size)
return -EINVAL;
/* calculate the total size of the unpacked crash dump */
BUILD_BUG_ON(ARRAY_SIZE(fw_mapping) == 0);
map = &fw_mapping[0];
host_min = map->host;
host_max = map->host + (map->to - map->from);
for (i = 1; i < ARRAY_SIZE(fw_mapping); i++) {
map = &fw_mapping[i];
if (!map->crash_dump)
continue;
if (map->host < host_min)
host_min = map->host;
tmp_max = map->host + (map->to - map->from);
if (tmp_max > host_max)
host_max = tmp_max;
}
*out_dump_size = host_max - host_min;
if (out_host_min)
*out_host_min = host_min;
return 0;
}
int wil_fw_copy_crash_dump(struct wil6210_priv *wil, void *dest, u32 size)
{
int i;
const struct fw_map *map;
void *data;
u32 host_min, dump_size, offset, len;
if (wil_fw_get_crash_dump_bounds(wil, &dump_size, &host_min)) {
wil_err(wil, "fail to obtain crash dump size\n");
return -EINVAL;
}
if (dump_size > size) {
wil_err(wil, "not enough space for dump. Need %d have %d\n",
dump_size, size);
return -EINVAL;
}
down_write(&wil->mem_lock);
if (test_bit(wil_status_suspending, wil->status) ||
test_bit(wil_status_suspended, wil->status)) {
wil_err(wil,
"suspend/resume in progress. cannot copy crash dump\n");
up_write(&wil->mem_lock);
return -EBUSY;
}
/* copy to crash dump area */
for (i = 0; i < ARRAY_SIZE(fw_mapping); i++) {
map = &fw_mapping[i];
if (!map->crash_dump)
continue;
data = (void * __force)wil->csr + HOSTADDR(map->host);
len = map->to - map->from;
offset = map->host - host_min;
wil_dbg_misc(wil,
"fw_copy_crash_dump: - dump %s, size %d, offset %d\n",
fw_mapping[i].name, len, offset);
wil_memcpy_fromio_32((void * __force)(dest + offset),
(const void __iomem * __force)data, len);
}
up_write(&wil->mem_lock);
return 0;
}
void wil_fw_core_dump(struct wil6210_priv *wil)
{
void *fw_dump_data;
u32 fw_dump_size;
if (wil_fw_get_crash_dump_bounds(wil, &fw_dump_size, NULL)) {
wil_err(wil, "fail to get fw dump size\n");
return;
}
fw_dump_data = vzalloc(fw_dump_size);
if (!fw_dump_data)
return;
if (wil_fw_copy_crash_dump(wil, fw_dump_data, fw_dump_size)) {
vfree(fw_dump_data);
return;
}
/* fw_dump_data will be free in device coredump release function
* after 5 min
*/
dev_coredumpv(wil_to_dev(wil), fw_dump_data, fw_dump_size, GFP_KERNEL);
wil_info(wil, "fw core dumped, size %d bytes\n", fw_dump_size);
}
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: ISC
/*
* Copyright (c) 2014-2016 Qualcomm Atheros, Inc.
*/
#include <linux/device.h>
#include "wil_platform.h"
int __init wil_platform_modinit(void)
{
return 0;
}
void wil_platform_modexit(void)
{
}
/* wil_platform_init() - wil6210 platform module init
*
* The function must be called before all other functions in this module.
* It returns a handle which is used with the rest of the API
*
*/
void *wil_platform_init(struct device *dev, struct wil_platform_ops *ops,
const struct wil_platform_rops *rops, void *wil_handle)
{
void *handle = ops; /* to return some non-NULL for 'void' impl. */
if (!ops) {
dev_err(dev,
"Invalid parameter. Cannot init platform module\n");
return NULL;
}
/* platform specific init functions should be called here */
return handle;
}
@@ -0,0 +1,81 @@
/* SPDX-License-Identifier: ISC */
/*
* Copyright (c) 2014-2017 Qualcomm Atheros, Inc.
*/
#ifndef __WIL_PLATFORM_H__
#define __WIL_PLATFORM_H__
struct device;
enum wil_platform_event {
WIL_PLATFORM_EVT_FW_CRASH = 0,
WIL_PLATFORM_EVT_PRE_RESET = 1,
WIL_PLATFORM_EVT_FW_RDY = 2,
WIL_PLATFORM_EVT_PRE_SUSPEND = 3,
WIL_PLATFORM_EVT_POST_SUSPEND = 4,
};
enum wil_platform_features {
WIL_PLATFORM_FEATURE_FW_EXT_CLK_CONTROL = 0,
WIL_PLATFORM_FEATURE_TRIPLE_MSI = 1,
WIL_PLATFORM_FEATURE_MAX,
};
enum wil_platform_capa {
WIL_PLATFORM_CAPA_RADIO_ON_IN_SUSPEND = 0,
WIL_PLATFORM_CAPA_T_PWR_ON_0 = 1,
WIL_PLATFORM_CAPA_EXT_CLK = 2,
WIL_PLATFORM_CAPA_MAX,
};
/**
* struct wil_platform_ops - wil platform module calls from this
* driver to platform driver
*/
struct wil_platform_ops {
int (*bus_request)(void *handle, uint32_t kbps /* KBytes/Sec */);
int (*suspend)(void *handle, bool keep_device_power);
int (*resume)(void *handle, bool device_powered_on);
void (*uninit)(void *handle);
int (*notify)(void *handle, enum wil_platform_event evt);
int (*get_capa)(void *handle);
void (*set_features)(void *handle, int features);
};
/**
* struct wil_platform_rops - wil platform module callbacks from
* platform driver to this driver
* @ramdump: store a ramdump from the wil firmware. The platform
* driver may add additional data to the ramdump to
* generate the final crash dump.
* @fw_recovery: start a firmware recovery process. Called as
* part of a crash recovery process which may include other
* related platform subsystems.
*/
struct wil_platform_rops {
int (*ramdump)(void *wil_handle, void *buf, uint32_t size);
int (*fw_recovery)(void *wil_handle);
};
/**
* wil_platform_init - initialize the platform driver
*
* @dev - pointer to the wil6210 device
* @ops - structure with platform driver operations. Platform
* driver will fill this structure with function pointers.
* @rops - structure with callbacks from platform driver to
* this driver. The platform driver copies the structure to
* its own storage. Can be NULL if this driver does not
* support crash recovery.
* @wil_handle - context for this driver that will be passed
* when platform driver invokes one of the callbacks in
* rops. May be NULL if rops is NULL.
*/
void *wil_platform_init(struct device *dev, struct wil_platform_ops *ops,
const struct wil_platform_rops *rops, void *wil_handle);
int __init wil_platform_modinit(void);
void wil_platform_modexit(void);
#endif /* __WIL_PLATFORM_H__ */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff