diff options
author | andy.hu <andy.hu@starfivetech.com> | 2022-12-30 11:27:56 +0000 |
---|---|---|
committer | andy.hu <andy.hu@starfivetech.com> | 2022-12-30 11:27:56 +0000 |
commit | 74acdb506e3323caa4f2378d53a9963825353fa9 (patch) | |
tree | 180d927f2bfedab25480774bb14d2c811676bd87 | |
parent | a22ddb01946e9d464382f27c027cf7e8a1d76c96 (diff) | |
parent | f0baf5ab40f2d16d21db3ff5c1af9c82ec3242d6 (diff) | |
download | linux-starfive-74acdb506e3323caa4f2378d53a9963825353fa9.tar.gz linux-starfive-74acdb506e3323caa4f2378d53a9963825353fa9.tar.bz2 linux-starfive-74acdb506e3323caa4f2378d53a9963825353fa9.zip |
Merge branch 'CR_2506_515_usb_wifi_jianlong' into 'vf2-515-devel'
CR_2506_515 net:wireless:Support eswin usb wifi ECR6600U
See merge request sbc/linux!45
110 files changed, 52822 insertions, 0 deletions
diff --git a/arch/riscv/configs/starfive_visionfive2_defconfig b/arch/riscv/configs/starfive_visionfive2_defconfig index 39b2d914b5bf..9f988469b489 100755 --- a/arch/riscv/configs/starfive_visionfive2_defconfig +++ b/arch/riscv/configs/starfive_visionfive2_defconfig @@ -146,7 +146,9 @@ CONFIG_MOTORCOMM_PHY=y CONFIG_IWLWIFI=y CONFIG_IWLDVM=y CONFIG_IWLMVM=y +CONFIG_HOSTAP=y # CONFIG_RTL_CARDS is not set +CONFIG_USB_WIFI_ECR6600U=y CONFIG_INPUT_EVDEV=y # CONFIG_INPUT_KEYBOARD is not set # CONFIG_INPUT_MOUSE is not set diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index 7add2002ff4c..6e0124af6270 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -35,6 +35,7 @@ source "drivers/net/wireless/st/Kconfig" source "drivers/net/wireless/ti/Kconfig" source "drivers/net/wireless/zydas/Kconfig" source "drivers/net/wireless/quantenna/Kconfig" +source "drivers/net/wireless/eswin/Kconfig" config PCMCIA_RAYCS tristate "Aviator/Raytheon 2.4GHz wireless support" diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile index 80b324499786..3a8f7274587c 100644 --- a/drivers/net/wireless/Makefile +++ b/drivers/net/wireless/Makefile @@ -30,3 +30,4 @@ obj-$(CONFIG_USB_NET_RNDIS_WLAN) += rndis_wlan.o obj-$(CONFIG_MAC80211_HWSIM) += mac80211_hwsim.o obj-$(CONFIG_VIRT_WIFI) += virt_wifi.o +obj-$(CONFIG_USB_WIFI_ECR6600U) += eswin/ diff --git a/drivers/net/wireless/eswin/Kconfig b/drivers/net/wireless/eswin/Kconfig new file mode 100644 index 000000000000..94d445ed420e --- /dev/null +++ b/drivers/net/wireless/eswin/Kconfig @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +config USB_WIFI_ECR6600U + bool "USB WIFI ECR6600U" + help + usb wifi ECR6600U. + diff --git a/drivers/net/wireless/eswin/Makefile b/drivers/net/wireless/eswin/Makefile new file mode 100644 index 000000000000..26959b97291c --- /dev/null +++ b/drivers/net/wireless/eswin/Makefile @@ -0,0 +1,145 @@ +ECRNX_VERS_NUM=ECR6600U_V1.1.0B04P05 + +# config_ceva_rtos = y use ceva rtos and add task_cli id +# config_ceva_rtos = n use freertos and no task_cli id + +#export DRIVER_PATH ?= $(shell pwd) +export DRIVER_PATH = $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) +DRIVER_PATH := $(DRIVER_PATH:/=) + +#if build the host driver within linux kernel, the DRIVER_PATH should be defined to absolutly path +export product=6600u +ifeq ($(product), 6600u) + -include $(DRIVER_PATH)/feature_config/6600u_config +else + -include $(DRIVER_PATH)/feature_config/6600_config + ifeq ($(os), ) + export CONFIG_CEVA_RTOS=y + else + export CONFIG_CEVA_RTOS=n + endif +endif + +###################### Platform Related ####################### +CONFIG_PLATFORM_RTK_RTD2851D = n +CONFIG_PLATFORM_MTK_MT9255 = n +CONFIG_PLATFORM_RASPBERRY = y +CONFIG_PLATFORM_X86 = n +CONFIG_PLATFORM_AML_T963 = n +############################################################### + +ifeq ($(CONFIG_PLATFORM_RTK_RTD2851D), y) +export KERNELDIR=/work3/zhanghong/realtek/rtk10/vendor/realtek/tool/kernel/linux/linux-4.14/ +export KBUILDDIR=/work3/zhanghong/realtek/rtk10/vendor/realtek/tool/kernel/linux/linux-4.14/ +endif + +ifeq ($(CONFIG_PLATFORM_MTK_MT9255), y) +export KERNELDIR= $(DRIVER_PATH)/../../../../../../kernel/fusion/4.9 +export KBUILDDIR= $(DRIVER_PATH)/../../../../../../kernel/fusion/4.9 +export CROSS_COMPILE:=$(DRIVER_PATH)/../../../../../../prebuilts/mtk_toolchain/gcc-arm-linux-gnu-5.5.0-ubuntu/x86_64/bin/arm-linux-gnueabi- +endif + +ifeq ($(CONFIG_PLATFORM_RASPBERRY), y) +export KERNELDIR=/lib/modules/$(shell uname -r)/build +export KBUILDDIR=/lib/modules/$(shell uname -r)/build +endif + +ifeq ($(CONFIG_PLATFORM_X86), y) +export KERNELDIR=/lib/modules/$(shell uname -r)/build +export KBUILDDIR=/lib/modules/$(shell uname -r)/build +endif + +ifeq ($(CONFIG_PLATFORM_AML_T963), y) +ifeq ($(DRIVER_DIR), ) +export DRIVER_PATH ?= $(shell pwd) +else +export DRIVER_PATH ?= $(DRIVER_DIR) +endif +export KERNELDIR ?= $(DRIVER_PATH)/../../../../../../out/target/product/T963/obj/KERNEL_OBJ +export KBUILDDIR ?= $(DRIVER_PATH)/../../../../../../out/target/product/T963/obj/KERNEL_OBJ +CROSS_COMPILE ?= /opt/gcc-linaro-6.3.1-2017.02-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- +ARCH := arm +endif +# +# } // WAITING FOR KCONFIG +# + +subdir-ccflags-$(CONFIG_6600_HAL) += -DCONFIG_6600_HAL +subdir-ccflags-$(CONFIG_DEBUG_FS) += -DCONFIG_ECRNX_DEBUGFS +subdir-ccflags-$(CONFIG_DEBUG_FS) += -DCONFIG_ECRNX_UM_HELPER_DFLT=\"$(CONFIG_ECRNX_UM_HELPER_DFLT)\" +subdir-ccflags-$(CONFIG_ECRNX_P2P_DEBUGFS) += -DCONFIG_ECRNX_P2P_DEBUGFS +subdir-ccflags-$(CONFIG_CEVA_RTOS) += -DCONFIG_CEVA_RTOS +subdir-ccflags-$(CONFIG_ECRNX_DBG) += -DCONFIG_ECRNX_DBG +subdir-ccflags-$(CONFIG_ECRNX_KTHREAD) += -DCONFIG_ECRNX_KTHREAD +subdir-ccflags-$(CONFIG_ECRNX_WORKQUEUE) += -DCONFIG_ECRNX_WORKQUEUE +subdir-ccflags-$(CONFIG_ECRNX_TASKLET) += -DCONFIG_ECRNX_TASKLET +subdir-ccflags-y += -DCONFIG_ECRNX_DBG_LEVEL=$(CONFIG_ECRNX_DBG_LEVEL) + +# FW VARS +subdir-ccflags-y += -DNX_VIRT_DEV_MAX=$(NX_VIRT_DEV_MAX) +subdir-ccflags-y += -DNX_REMOTE_STA_MAX=$(NX_REMOTE_STA_MAX) +subdir-ccflags-y += -DNX_MU_GROUP_MAX=$(NX_MU_GROUP_MAX) +subdir-ccflags-y += -DNX_TXDESC_CNT=$(NX_TXDESC_CNT) +subdir-ccflags-y += -DNX_TX_MAX_RATES=$(NX_TX_MAX_RATES) +subdir-ccflags-y += -DNX_CHAN_CTXT_CNT=$(NX_CHAN_CTXT_CNT) +#subdir-ccflags-y += -DCONFIG_POWERKEY_GPIO=$(CONFIG_POWERKEY_GPIO) +# FW ARCH: +subdir-ccflags-$(CONFIG_ECRNX_SDM) += -DCONFIG_ECRNX_SDM +subdir-ccflags-$(CONFIG_ECRNX_TL4) += -DCONFIG_ECRNX_TL4 +subdir-ccflags-$(CONFIG_ECRNX_OLD_IPC) += -DCONFIG_ECRNX_OLD_IPC + +#FW VER INFO +DRIVER_BUILD_TIME = "$(shell TZ=CST date -u "+%Y-%m-%d %H:%M:%S CST")" +subdir-ccflags-y += -DECRNX_VERS_MOD=\"$(ECRNX_VERS_NUM)\" +subdir-ccflags-y += -DECRNX_VERS_BANNER=\"$(ECRNX_MODULE_NAME)-$(ECRNX_VERS_NUM)-build\:$(DRIVER_BUILD_TIME)\" + +ifeq ($(CONFIG_ECRNX_FULLMAC), m) +MAC_SRC = fullmac +else ifeq ($(CONFIG_ECRNX_SOFTMAC), m) +MAC_SRC = softmac +endif + + +obj-m := $(ECRNX_MODULE_NAME).o + +ifneq ($(KERNELRELEASE),) +include $(DRIVER_PATH)/$(MAC_SRC)/Makefile + +else +all: modules + +.PHONY: modules clean copy strip + +modules: +ifeq ($(product), 6600u) + $(warning "select chip is $(product).") +else + $(warning "select chip is 6600.") +endif + rm -rf *.ko +ifeq ($(os), ) + $(warning "select slave is used CEVA RTOS.") +else + $(warning "select slave is not used CEVA_RTOS.") +endif +ifeq ($(CONFIG_PLATFORM_MTK_MT9255), y) + $(MAKE) -C $(KERNELDIR) CROSS_COMPILE=$(CROSS_COMPILE) O=$(KBUILDDIR) M=$(DRIVER_PATH) $@ +else ifeq ($(CONFIG_PLATFORM_AML_T963), y) + $(MAKE) -C $(KERNELDIR) CROSS_COMPILE=$(CROSS_COMPILE) ARCH=$(ARCH) O=$(KBUILDDIR) M=$(DRIVER_PATH) $@ +else + $(MAKE) -C $(KERNELDIR) O=$(KBUILDDIR) M=$(DRIVER_PATH) $@ +endif + rm -rf *.o + rm -rf *.mod *.mod.c + +copy: + cp -f $(DRIVER_PATH)/fullmac/$(ECRNX_MODULE_NAME).ko $(MODDESTDIR) + +strip: + $(CROSS_COMPILE)strip --strip-unneeded $(ECRNX_MODULE_NAME).ko +clean: + rm -rf *.o + rm -rf *.ko *.mod *.mod.c + $(MAKE) -C $(KERNELDIR) O=$(KBUILDDIR) M=$(DRIVER_PATH) $@ +# @$(DRIVER_PATH)/mklink.sh clean +endif diff --git a/drivers/net/wireless/eswin/README.md b/drivers/net/wireless/eswin/README.md new file mode 100644 index 000000000000..745e42481f8d --- /dev/null +++ b/drivers/net/wireless/eswin/README.md @@ -0,0 +1,119 @@ +## **Wifi host driver编译指å—** + +## Wifi host driver框架 + +Wifi host driver文件目录如下所示; + +- ├── ble_netconfig +- ├── compile_test.sh +- ├── ecrnx_bfmer.c +- ├── ecrnx_bfmer.h +- ├── ecrnx_cfgfile.c +- ├── ecrnx_cfgfile.h +- ├── ecrnx_cmds.c +- ├── ecrnx_cmds.h +- ├── ecrnx_compat.h +- ├── ecrnx_debugfs.c +- ├── ecrnx_debugfs.h +- ├── ecrnx_events.h +- ├── ecrnx_fw_dump.c +- ├── ecrnx_fw_trace.c +- ├── ecrnx_fw_trace.h +- ├── ecrnx_iwpriv.c +- ├── ecrnx_mod_params.c +- ├── ecrnx_mod_params.h +- ├── ecrnx_msg_rx.c +- ├── ecrnx_msg_rx.h +- ├── ecrnx_msg_tx.c +- ├── ecrnx_msg_tx.h +- ├── ecrnx_mu_group.c +- ├── ecrnx_mu_group.h +- ├── ecrnx_platform.c +- ├── ecrnx_platform.h +- ├── ecrnx_prof.h +- ├── ecrnx_radar.c +- ├── ecrnx_radar.h +- ├── ecrnx_strs.c +- ├── ecrnx_strs.h +- ├── ecrnx_testmode.c +- ├── ecrnx_testmode.h +- ├── ecrnx_txq.c +- ├── ecrnx_txq.h +- ├── ecrnx_utils.c +- ├── ecrnx_utils.h +- ├── ecrnx_version.h +- ├── eswin_port +- ├── feature_config +- ├── fullmac +- ├── fw_head_check.c +- ├── fw_head_check.h +- ├── hal_desc.c +- ├── hal_desc.h +- ├── ipc_compat.h +- ├── ipc_host.c +- ├── ipc_host.h +- ├── ipc_shared.h +- ├── lmac_mac.h +- ├── lmac_msg.h +- ├── lmac_types.h +- ├── Makefile +- ├── reg_access.h +- ├── reg_ipc_app.h +- ├── sdio +- ├── softmac +- └── usb + + +Wifi host driver 主è¦ç›®å½•ç»“构说明如下表所示; + +| **文件路径** | **说明** | +| ------------ | :-------------------------------- | +| eswin_port | Host Driver相关的适é…æŽ¥å£ | +| fullmac | fullmac模å¼ä¸‹ç›¸å…³çš„é…置文件和æºç | +| softmac | softmac模å¼ä¸‹ç›¸å…³çš„é…置文件和æºç | +| sdio | sdio相关的驱动文件 | +| usb | usb相关的驱动文件 | + +## **Wifi host driver 编译å‚数说明** + +​ 6600U host driver ç›®å‰æ—¶æœ‰ä¸¤ä¸ªå‚æ•°å¯é€‰ï¼Œæ述如下: + +​ product:product å‚数分为 6600 å’Œ 6600u,用æ¥åŒºåˆ†èŠ¯ç‰‡å¹³å°ï¼Œé»˜è®¤ä¸º 6600ï¼› + +​ os:os å‚数为 ceva,æ„æ€ä¸ºæ˜¯å¦ä½¿ç”¨ ceva çš„ os,该å‚æ•°åªé’ˆå¯¹ 6600 有效,6600u ä¸éœ€è¦ os å‚数;默认为ä¸ä½¿ç”¨ ceva osï¼› + +## **6600(SDIO)é€ä¼ 版本编译指令** + +​ slave 使用 6600 iot 仓库的 6600 é€ä¼ 版本时,host driver 编译命令如下: + +​ sudo make os=true(或者 sudo make product=6600 os=true) + +​ slave 使用 6600U åˆä»“仓库的 develop 分支时,host driver 的编译命令如下: + +​ sudo make(或者 sudo make product=6600) + +​ sdio 版本 host driver ko åŠ è½½æ—¶ï¼Œå¦‚æžœéœ€è¦ä¸‹è½½å›ºä»¶çš„è¯ï¼Œåˆ™è¦å¸¦ä¸‹è½½å›ºä»¶å’Œå›ºä»¶å称的å‚数(默认ä¸ä¸‹è½½å›ºä»¶ï¼‰ï¼Œ 如下所示: + +​ sudo insmod wlan_ecr6600.ko dl_fw=1 fw_name="transport.bin" + + + +## **6600U(USB)é€ä¼ 版本编译指令** + +​ 6600U slave 侧统一使用 6600U 仓库下的 6600U é€ä¼ 版本,host driver 编译命令如下: + +​ sudo make product=6600u (ä¸éœ€è¦å¸¦ os å‚数,6600U 默认带 CEVA_OS) + +​ 6600U host ko åŠ è½½å‘½ä»¤å¦‚ä¸‹æ‰€ç¤ºï¼š + +​ sudo insmod wlan_ecr6600u_usb.ko(6600U host ko åŠ è½½æ—¶é»˜è®¤ä¼šä¸‹è½½å›ºä»¶ï¼Œé»˜è®¤åŠ è½½å›ºä»¶å称为:ECR6600U_transport.bin,如果ä¸éœ€è¦å›ºä»¶ä¸‹è½½åˆ™ä½¿ç”¨ dl_fw=0, 如果è¦è‡ªå®šä¹‰ä¸‹è½½å›ºä»¶å称则使用:fw_name="filename.bin" ); + +## cfg文件使用说明 + +å°† wifi_ecr6600u.cfg æ‹·è´åˆ° /lib/firmware 路径下,修改 cfg 文件里的å‚数值,å³å¯ä¿®æ”¹ host å’Œ slave 的相关å‚数,修改完æˆåŽé‡æ–°å¸è½½ã€åŠ è½½ ko å³å¯ç”Ÿæ•ˆï¼›ç›®å‰å¯æ”¯æŒçš„å‚数如下: + +DRIVER_LOG_LEVEL=3 //host driver log ç‰çº§ï¼Œå–值范围为 0-5 + +FW_LOG_LEVEL=2 //slave log ç‰çº§ï¼Œå–值范围为 0-4 + +FW_LOG_TYPE=0 // slave log 输出方å¼ï¼Œ 0 为通过 slave 端串å£è¾“出;1 为通过 host debugfs ä¿å˜ï¼› 2 为通过 host 侧文件ä¿å˜ï¼›
\ No newline at end of file diff --git a/drivers/net/wireless/eswin/ble_netconfig/btgatt-server.c b/drivers/net/wireless/eswin/ble_netconfig/btgatt-server.c new file mode 100644 index 000000000000..c986ee7add05 --- /dev/null +++ b/drivers/net/wireless/eswin/ble_netconfig/btgatt-server.c @@ -0,0 +1,1685 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Google Inc. + * + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdbool.h> +#include <stdint.h> +#include <time.h> +#include <stdlib.h> +#include <getopt.h> +#include <unistd.h> +#include <errno.h> + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" +#include "lib/l2cap.h" +#include "lib/uuid.h" + +#include "src/shared/mainloop.h" +#include "src/shared/util.h" +#include "src/shared/att.h" +#include "src/shared/queue.h" +#include "src/shared/timeout.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-server.h" + +#define UUID_GAP 0x1800 +#define UUID_GATT 0x1801 +#define UUID_HEART_RATE 0x180d +#define UUID_HEART_RATE_MSRMT 0x2a37 +#define UUID_HEART_RATE_BODY 0x2a38 +#define UUID_HEART_RATE_CTRL 0x2a39 + +#define ESWIN_TEST 1 + +#ifdef ESWIN_TEST +#define UUID_NETCFG 0x1920 +#define UUID_SSID 0x2b10 +#define UUID_PASSWORD 0x2b11 +#define UUID_NETSTATUS 0x2b12 +#define CFG_ITEM_LEN 40 +#define CFG_FLAG_SSID 0x01 +#define CFG_FLAG_PWD 0x02 +#define CFG_FLAG_ALL 0x03 +#endif + +#define ATT_CID 4 + +#define PRLOG(...) \ + do { \ + printf(__VA_ARGS__); \ + print_prompt(); \ + } while (0) + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#define COLOR_OFF "\x1B[0m" +#define COLOR_RED "\x1B[0;91m" +#define COLOR_GREEN "\x1B[0;92m" +#define COLOR_YELLOW "\x1B[0;93m" +#define COLOR_BLUE "\x1B[0;94m" +#define COLOR_MAGENTA "\x1B[0;95m" +#define COLOR_BOLDGRAY "\x1B[1;30m" +#define COLOR_BOLDWHITE "\x1B[1;37m" + +//static const char test_device_name[] = "Very Long Test Device Name For Testing " +// "ATT Protocol Operations On GATT Server"; +static const char test_device_name[] = "BlueZTest"; + +static bool verbose = false; + +#ifdef ESWIN_TEST +static uint8_t netcfg_ssid[CFG_ITEM_LEN] = ""; +static uint8_t netcfg_pwd[CFG_ITEM_LEN] = ""; +static uint8_t net_status[CFG_ITEM_LEN] = ""; +static uint8_t ssid_len = 0; +static uint8_t pwd_len = 0; +static uint8_t netcfg_flag = 0; +#endif + + +struct server { + int fd; + struct bt_att *att; + struct gatt_db *db; + struct bt_gatt_server *gatt; + + uint8_t *device_name; + size_t name_len; + + uint16_t gatt_svc_chngd_handle; + bool svc_chngd_enabled; + + uint16_t hr_handle; + uint16_t hr_msrmt_handle; + uint16_t hr_energy_expended; + bool hr_visible; + bool hr_msrmt_enabled; + int hr_ee_count; + unsigned int hr_timeout_id; +#ifdef ESWIN_TEST + uint16_t netstatus_handle; + bool netstatus_enabled; + unsigned int netstatus_timeout_id; +#endif +}; + +static void print_prompt(void) +{ + printf(COLOR_BLUE "[GATT server]" COLOR_OFF "# "); + fflush(stdout); +} + +static void att_disconnect_cb(int err, void *user_data) +{ + printf("Device disconnected: %s\n", strerror(err)); + + mainloop_quit(); +} + +static void att_debug_cb(const char *str, void *user_data) +{ + const char *prefix = user_data; + + PRLOG(COLOR_BOLDGRAY "%s" COLOR_BOLDWHITE "%s\n" COLOR_OFF, prefix, + str); +} + +static void gatt_debug_cb(const char *str, void *user_data) +{ + const char *prefix = user_data; + + PRLOG(COLOR_GREEN "%s%s\n" COLOR_OFF, prefix, str); +} + +static void gap_device_name_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t error = 0; + size_t len = 0; + const uint8_t *value = NULL; + + PRLOG("GAP Device Name Read called\n"); + + len = server->name_len; + + if (offset > len) { + error = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + len -= offset; + value = len ? &server->device_name[offset] : NULL; + +done: + gatt_db_attribute_read_result(attrib, id, error, value, len); +} + +static void gap_device_name_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t error = 0; + + PRLOG("GAP Device Name Write called\n"); + + /* If the value is being completely truncated, clean up and return */ + if (!(offset + len)) { + free(server->device_name); + server->device_name = NULL; + server->name_len = 0; + goto done; + } + + /* Implement this as a variable length attribute value. */ + if (offset > server->name_len) { + error = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + if (offset + len != server->name_len) { + uint8_t *name; + + name = realloc(server->device_name, offset + len); + if (!name) { + error = BT_ATT_ERROR_INSUFFICIENT_RESOURCES; + goto done; + } + + server->device_name = name; + server->name_len = offset + len; + } + + if (value) + memcpy(server->device_name + offset, value, len); + +done: + gatt_db_attribute_write_result(attrib, id, error); +} + +static void gap_device_name_ext_prop_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + uint8_t value[2]; + + PRLOG("Device Name Extended Properties Read called\n"); + + value[0] = BT_GATT_CHRC_EXT_PROP_RELIABLE_WRITE; + value[1] = 0; + + gatt_db_attribute_read_result(attrib, id, 0, value, sizeof(value)); +} + +static void gatt_service_changed_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + PRLOG("Service Changed Read called\n"); + + gatt_db_attribute_read_result(attrib, id, 0, NULL, 0); +} + +static void gatt_svc_chngd_ccc_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t value[2]; + + PRLOG("Service Changed CCC Read called\n"); + + value[0] = server->svc_chngd_enabled ? 0x02 : 0x00; + value[1] = 0x00; + + gatt_db_attribute_read_result(attrib, id, 0, value, sizeof(value)); +} + +static void gatt_svc_chngd_ccc_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t ecode = 0; + + PRLOG("Service Changed CCC Write called\n"); + + if (!value || len != 2) { + ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + goto done; + } + + if (offset) { + ecode = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + if (value[0] == 0x00) + server->svc_chngd_enabled = false; + else if (value[0] == 0x02) + server->svc_chngd_enabled = true; + else + ecode = 0x80; + + PRLOG("Service Changed Enabled: %s\n", + server->svc_chngd_enabled ? "true" : "false"); + +done: + gatt_db_attribute_write_result(attrib, id, ecode); +} + +static void hr_msrmt_ccc_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t value[2]; + + value[0] = server->hr_msrmt_enabled ? 0x01 : 0x00; + value[1] = 0x00; + + gatt_db_attribute_read_result(attrib, id, 0, value, 2); +} + +static bool hr_msrmt_cb(void *user_data) +{ + struct server *server = user_data; + bool expended_present = !(server->hr_ee_count % 10); + uint16_t len = 2; + uint8_t pdu[4]; + uint32_t cur_ee; + + pdu[0] = 0x06; + pdu[1] = 90 + (rand() % 40); + + if (expended_present) { + pdu[0] |= 0x08; + put_le16(server->hr_energy_expended, pdu + 2); + len += 2; + } + + bt_gatt_server_send_notification(server->gatt, + server->hr_msrmt_handle, + pdu, len, false); + + + cur_ee = server->hr_energy_expended; + server->hr_energy_expended = MIN(UINT16_MAX, cur_ee + 10); + server->hr_ee_count++; + + return true; +} + +static void update_hr_msrmt_simulation(struct server *server) +{ + if (!server->hr_msrmt_enabled || !server->hr_visible) { + timeout_remove(server->hr_timeout_id); + return; + } + + server->hr_timeout_id = timeout_add(1000, hr_msrmt_cb, server, NULL); +} + +static void hr_msrmt_ccc_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t ecode = 0; + + if (!value || len != 2) { + ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + goto done; + } + + if (offset) { + ecode = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + if (value[0] == 0x00) + server->hr_msrmt_enabled = false; + else if (value[0] == 0x01) { + if (server->hr_msrmt_enabled) { + PRLOG("HR Measurement Already Enabled\n"); + goto done; + } + + server->hr_msrmt_enabled = true; + } else + ecode = 0x80; + + PRLOG("HR: Measurement Enabled: %s\n", + server->hr_msrmt_enabled ? "true" : "false"); + + update_hr_msrmt_simulation(server); + +done: + gatt_db_attribute_write_result(attrib, id, ecode); +} + +static void hr_control_point_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t ecode = 0; + + if (!value || len != 1) { + ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + goto done; + } + + if (offset) { + ecode = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + if (value[0] == 1) { + PRLOG("HR: Energy Expended value reset\n"); + server->hr_energy_expended = 0; + } + +done: + gatt_db_attribute_write_result(attrib, id, ecode); +} + +static void confirm_write(struct gatt_db_attribute *attr, int err, + void *user_data) +{ + if (!err) + return; + + fprintf(stderr, "Error caching attribute %p - err: %d\n", attr, err); + exit(1); +} + +#ifdef ESWIN_TEST +static void modify_wpa_conf(void) +{ + FILE *fp; + char ssid_str[2*CFG_ITEM_LEN]={0}; + char pwd_str[2*CFG_ITEM_LEN]={0}; + PRLOG("enter\n"); + + fp = fopen("/etc/wpa_supplicant/wpa_supplicant.conf", "we"); + if (!fp){ + PRLOG("fopen fail\n"); + return; + } + snprintf(ssid_str, sizeof(ssid_str), "ssid=\"%s\"\n", netcfg_ssid); + snprintf(pwd_str, sizeof(pwd_str), "psk=\"%s\"\n", netcfg_pwd); + + fputs("ctrl_interface=DIR=/var/run/wpa_supplicant\n",fp); + fputs("update_config=1\n", fp); + fputs("network={\n", fp); + fputs(ssid_str, fp); + fputs(pwd_str, fp); + fputs("}\n", fp); + + + fclose(fp); + PRLOG("exit\n"); +} + +static int wifi_run_cmd(char *cmd) +{ + int ret = 0; + ret = system(cmd); + if(ret < 0) { + PRLOG("cmd=%s\t error:%s\n",cmd, strerror(errno)); + return -1; + } + if(WIFEXITED(ret)){ + + PRLOG("success,cmd=%s,status=%d\n",cmd,WEXITSTATUS(ret)); + return WEXITSTATUS(ret); + } + + return -1; + +} +static void update_wpa_network(void) +{ + char sys_cmd[100] = {0}; + char temp[CFG_ITEM_LEN] = {0}; + int result = -1; + + sprintf(sys_cmd, "cd /wpa_supplicant-2.10/wpa_supplicant"); + result = wifi_run_cmd(sys_cmd); + if(result < 0){ + return; + } + + sprintf(sys_cmd, "sudo killall wpa_supplicant"); + result = wifi_run_cmd(sys_cmd); + if(result < 0){ + return; + } + + sprintf(sys_cmd, "sudo /wpa_supplicant-2.10/wpa_supplicant/wpa_supplicant -B -c/etc/wpa_supplicant/wpa_supplicant.conf -iwlan0 -Dnl80211,wext"); + result = wifi_run_cmd(sys_cmd); + if(result < 0){ + return; + } + + + sprintf(sys_cmd, "sudo /wpa_supplicant-2.10/wpa_supplicant/wpa_cli -i wlan0 remove_network all"); + result = wifi_run_cmd(sys_cmd); + if(result < 0){ + return; + } + memset(sys_cmd, 0x00, 100); + sprintf(sys_cmd, "sudo /wpa_supplicant-2.10/wpa_supplicant/wpa_cli -i wlan0 add_network"); + result = wifi_run_cmd(sys_cmd); + if(result < 0){ + return; + } + memset(sys_cmd, 0x00, 100); + sprintf(sys_cmd, "sudo /wpa_supplicant-2.10/wpa_supplicant/wpa_cli -i wlan0 set_network 0 ssid "); + snprintf(temp, sizeof(temp),"'\"%s\"'", netcfg_ssid); + strcat(sys_cmd,temp); + result = wifi_run_cmd(sys_cmd); + if(result < 0){ + return; + } + + memset(sys_cmd, 0x00, 100); + memset(temp, 0x00, CFG_ITEM_LEN); + sprintf(sys_cmd, "sudo /wpa_supplicant-2.10/wpa_supplicant/wpa_cli -i wlan0 set_network 0 psk "); + snprintf(temp, sizeof(temp),"'\"%s\"'", netcfg_pwd); + strcat(sys_cmd,temp); + result = wifi_run_cmd(sys_cmd); + if(result < 0){ + return; + } + + memset(sys_cmd, 0x00, 100); + sprintf(sys_cmd, "sudo /wpa_supplicant-2.10/wpa_supplicant/wpa_cli -i wlan0 save_config"); + result = wifi_run_cmd(sys_cmd); + if(result < 0){ + return; + } + + memset(sys_cmd, 0x00, 100); + sprintf(sys_cmd, "sudo /wpa_supplicant-2.10/wpa_supplicant/wpa_cli -i wlan0 select_network 0"); + result = wifi_run_cmd(sys_cmd); + if(result < 0){ + return; + } + + PRLOG("update_wpa_network finish\n"); +} + +static void eswin_netcfg_wpa_conf(int flag) +{ + netcfg_flag |= flag; + + if(netcfg_flag == CFG_FLAG_ALL){ + netcfg_flag = 0; + //modify_wpa_conf(); + update_wpa_network(); + } +} + + + + +static void eswin_netcfg_ssid_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t error = 0; + size_t len = 0; + const uint8_t *value = NULL; + + //PRLOG("ESWIN SSID Read called ssid_len=%d,offset=%d\n",ssid_len,offset); + + if (offset > ssid_len) { + error = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + len = ssid_len - offset; + value = len ? &netcfg_ssid[offset] : NULL; + +done: + gatt_db_attribute_read_result(attrib, id, error, value, len); +} + + +static void eswin_netcfg_ssid_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t error = 0; + + //PRLOG("ESWIN SSID write called offset=%d len=%d\n",offset, len); + + memset(netcfg_ssid, 0x00, CFG_ITEM_LEN); + ssid_len = 0; + + if (offset+len >= CFG_ITEM_LEN) { + error = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + if (value) + memcpy(netcfg_ssid + offset, value, len); + + ssid_len +=len; + eswin_netcfg_wpa_conf(CFG_FLAG_SSID); +done: + gatt_db_attribute_write_result(attrib, id, error); + +} + + +static void eswin_netcfg_pwd_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t error = 0; + size_t len = 0; + const uint8_t *value = NULL; + + //PRLOG("ESWIN pwd Read called pwd_len=%d,offset=%d\n",pwd_len,offset); + + if (offset > pwd_len) { + error = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + len = pwd_len - offset; + value = len ? &netcfg_pwd[offset] : NULL; + +done: + gatt_db_attribute_read_result(attrib, id, error, value, len); +} + + +static void eswin_netcfg_pwd_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t error = 0; + + //PRLOG("ESWIN pwd write called offset=%d len=%d\n",offset, len); + + memset(netcfg_pwd, 0x00, CFG_ITEM_LEN); + pwd_len = 0; + + if (offset+len >= CFG_ITEM_LEN) { + error = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + if (value) + memcpy(netcfg_pwd + offset, value, len); + + pwd_len +=len; + eswin_netcfg_wpa_conf(CFG_FLAG_PWD); +done: + gatt_db_attribute_write_result(attrib, id, error); + +} + +static void eswin_netstatus_ccc_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t value[2]; + + value[0] = server->netstatus_enabled ? 0x01 : 0x00; + value[1] = 0x00; + + gatt_db_attribute_read_result(attrib, id, 0, value, 2); +} + +#define WAP_STATE "wpa_state=" +static bool netstatus_cb(void *user_data) +{ + struct server *server = user_data; + FILE *fp = NULL; + char buff[500] = {0}; + char *ptr = NULL, *ptr_end = NULL; + int head_len = strlen(WAP_STATE); + uint8_t cur_status[CFG_ITEM_LEN] = ""; + + fp = popen("sudo wpa_cli -i wlan0 status", "r"); + + fread(buff, 1, 499, fp); + ptr = strstr(buff, WAP_STATE); + if (ptr == NULL) { + PRLOG("no wpa_state\n"); + return false; + } + + ptr_end = strchr(ptr, '\n'); + if (ptr_end == NULL) { + PRLOG("no wpa_state end\n"); + return false; + } + strncpy(cur_status, ptr+head_len, ptr_end-ptr-head_len); + PRLOG("%s\n", cur_status); + + if(strcmp(cur_status, net_status) != 0) { + memset(net_status, 0x00, CFG_ITEM_LEN); + strcpy(net_status, cur_status); + PRLOG("bt_gatt_server_send_notification\n"); + + bt_gatt_server_send_notification(server->gatt, + server->netstatus_handle, + net_status, strlen(net_status), false); + } + return true; +} + +static void update_netstatus_simulation(struct server *server) +{ + if (!server->netstatus_enabled) { + timeout_remove(server->netstatus_timeout_id); + return; + } + + server->netstatus_timeout_id = timeout_add(1000, netstatus_cb, server, NULL); +} + + +static void eswin_netstatus_ccc_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t ecode = 0; + + if (!value || len != 2) { + ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + goto done; + } + + if (offset) { + ecode = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + if (value[0] == 0x00) + server->netstatus_enabled = false; + else if (value[0] == 0x01) { + server->netstatus_enabled = true; + } else + ecode = 0x80; + + PRLOG("netstatus Enabled: %s\n", + server->netstatus_enabled ? "true" : "false"); + update_netstatus_simulation(server); + +done: + gatt_db_attribute_write_result(attrib, id, ecode); +} + +#endif + +static void populate_gap_service(struct server *server) +{ + bt_uuid_t uuid; + struct gatt_db_attribute *service, *tmp; + uint16_t appearance; + + /* Add the GAP service */ + bt_uuid16_create(&uuid, UUID_GAP); + service = gatt_db_add_service(server->db, &uuid, true, 6); + + /* + * Device Name characteristic. Make the value dynamically read and + * written via callbacks. + */ + bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME); + gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_EXT_PROP, + gap_device_name_read_cb, + gap_device_name_write_cb, + server); + + bt_uuid16_create(&uuid, GATT_CHARAC_EXT_PROPER_UUID); + gatt_db_service_add_descriptor(service, &uuid, BT_ATT_PERM_READ, + gap_device_name_ext_prop_read_cb, + NULL, server); + + /* + * Appearance characteristic. Reads and writes should obtain the value + * from the database. + */ + bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE); + tmp = gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + NULL, NULL, server); + + /* + * Write the appearance value to the database, since we're not using a + * callback. + */ + put_le16(128, &appearance); + gatt_db_attribute_write(tmp, 0, (void *) &appearance, + sizeof(appearance), + BT_ATT_OP_WRITE_REQ, + NULL, confirm_write, + NULL); + + gatt_db_service_set_active(service, true); +} + +static void populate_gatt_service(struct server *server) +{ + bt_uuid_t uuid; + struct gatt_db_attribute *service, *svc_chngd; + + /* Add the GATT service */ + bt_uuid16_create(&uuid, UUID_GATT); + service = gatt_db_add_service(server->db, &uuid, true, 4); + + bt_uuid16_create(&uuid, GATT_CHARAC_SERVICE_CHANGED); + svc_chngd = gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_INDICATE, + gatt_service_changed_cb, + NULL, server); + server->gatt_svc_chngd_handle = gatt_db_attribute_get_handle(svc_chngd); + + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + gatt_db_service_add_descriptor(service, &uuid, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, + gatt_svc_chngd_ccc_read_cb, + gatt_svc_chngd_ccc_write_cb, server); + + gatt_db_service_set_active(service, true); +} + +static void populate_hr_service(struct server *server) +{ + bt_uuid_t uuid; + struct gatt_db_attribute *service, *hr_msrmt, *body; + uint8_t body_loc = 1; /* "Chest" */ + + /* Add Heart Rate Service */ + bt_uuid16_create(&uuid, UUID_HEART_RATE); + service = gatt_db_add_service(server->db, &uuid, true, 8); + server->hr_handle = gatt_db_attribute_get_handle(service); + + /* HR Measurement Characteristic */ + bt_uuid16_create(&uuid, UUID_HEART_RATE_MSRMT); + hr_msrmt = gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_NONE, + BT_GATT_CHRC_PROP_NOTIFY, + NULL, NULL, NULL); + server->hr_msrmt_handle = gatt_db_attribute_get_handle(hr_msrmt); + + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + gatt_db_service_add_descriptor(service, &uuid, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, + hr_msrmt_ccc_read_cb, + hr_msrmt_ccc_write_cb, server); + + /* + * Body Sensor Location Characteristic. Make reads obtain the value from + * the database. + */ + bt_uuid16_create(&uuid, UUID_HEART_RATE_BODY); + body = gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + NULL, NULL, server); + gatt_db_attribute_write(body, 0, (void *) &body_loc, sizeof(body_loc), + BT_ATT_OP_WRITE_REQ, + NULL, confirm_write, + NULL); + + /* HR Control Point Characteristic */ + bt_uuid16_create(&uuid, UUID_HEART_RATE_CTRL); + gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_WRITE, + NULL, hr_control_point_write_cb, + server); + + if (server->hr_visible) + gatt_db_service_set_active(service, true); +} + +#ifdef ESWIN_TEST +static void populate_eswin_service(struct server *server) +{ + bt_uuid_t uuid; + struct gatt_db_attribute *service, *netstatus; + + ssid_len = strlen(netcfg_ssid); + pwd_len = strlen(netcfg_pwd); + server->netstatus_enabled = true; + + /* Add Net config Service */ + bt_uuid16_create(&uuid, UUID_NETCFG); + service = gatt_db_add_service(server->db, &uuid, true, 10); + + /* SSID Characteristic */ + bt_uuid16_create(&uuid, UUID_SSID); + gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_WRITE, + eswin_netcfg_ssid_read_cb, + eswin_netcfg_ssid_write_cb, + server); + + + /* + * PASSWORD Characteristic. + */ + bt_uuid16_create(&uuid, UUID_PASSWORD); + gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_READ| BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_WRITE, + eswin_netcfg_pwd_read_cb, + eswin_netcfg_pwd_write_cb, + server); + + /* + * NETSTATUS Characteristic. + */ + bt_uuid16_create(&uuid, UUID_NETSTATUS); + netstatus = gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_NONE, + BT_GATT_CHRC_PROP_NOTIFY, + NULL, NULL, NULL); + server->netstatus_handle = gatt_db_attribute_get_handle(netstatus); + + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + gatt_db_service_add_descriptor(service, &uuid, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, + eswin_netstatus_ccc_read_cb, + eswin_netstatus_ccc_write_cb, server); + + gatt_db_service_set_active(service, true); +} + +#endif + +static void populate_db(struct server *server) +{ + populate_gap_service(server); + populate_gatt_service(server); + populate_hr_service(server); +#ifdef ESWIN_TEST + populate_eswin_service(server); +#endif +} + +static struct server *server_create(int fd, uint16_t mtu, bool hr_visible) +{ + struct server *server; + size_t name_len = strlen(test_device_name); + + server = new0(struct server, 1); + if (!server) { + fprintf(stderr, "Failed to allocate memory for server\n"); + return NULL; + } + + server->att = bt_att_new(fd, false); + if (!server->att) { + fprintf(stderr, "Failed to initialze ATT transport layer\n"); + goto fail; + } + + if (!bt_att_set_close_on_unref(server->att, true)) { + fprintf(stderr, "Failed to set up ATT transport layer\n"); + goto fail; + } + + if (!bt_att_register_disconnect(server->att, att_disconnect_cb, NULL, + NULL)) { + fprintf(stderr, "Failed to set ATT disconnect handler\n"); + goto fail; + } + + server->name_len = name_len + 1; + server->device_name = malloc(name_len + 1); + if (!server->device_name) { + fprintf(stderr, "Failed to allocate memory for device name\n"); + goto fail; + } + + memcpy(server->device_name, test_device_name, name_len); + server->device_name[name_len] = '\0'; + + server->fd = fd; + server->db = gatt_db_new(); + if (!server->db) { + fprintf(stderr, "Failed to create GATT database\n"); + goto fail; + } + + server->gatt = bt_gatt_server_new(server->db, server->att, mtu, 0); + if (!server->gatt) { + fprintf(stderr, "Failed to create GATT server\n"); + goto fail; + } + + server->hr_visible = hr_visible; + + if (verbose) { + bt_att_set_debug(server->att, BT_ATT_DEBUG_VERBOSE, + att_debug_cb, "att: ", NULL); + bt_gatt_server_set_debug(server->gatt, gatt_debug_cb, + "server: ", NULL); + } + + /* Random seed for generating fake Heart Rate measurements */ + srand(time(NULL)); + + /* bt_gatt_server already holds a reference */ + populate_db(server); + + return server; + +fail: + gatt_db_unref(server->db); + free(server->device_name); + bt_att_unref(server->att); + free(server); + + return NULL; +} + +static void server_destroy(struct server *server) +{ + timeout_remove(server->hr_timeout_id); + bt_gatt_server_unref(server->gatt); + gatt_db_unref(server->db); +} + +static void usage(void) +{ + printf("btgatt-server\n"); + printf("Usage:\n\tbtgatt-server [options]\n"); + + printf("Options:\n" + "\t-i, --index <id>\t\tSpecify adapter index, e.g. hci0\n" + "\t-m, --mtu <mtu>\t\t\tThe ATT MTU to use\n" + "\t-s, --security-level <sec>\tSet security level (low|" + "medium|high)\n" + "\t-t, --type [random|public] \t The source address type\n" + "\t-v, --verbose\t\t\tEnable extra logging\n" + "\t-r, --heart-rate\t\tEnable Heart Rate service\n" + "\t-h, --help\t\t\tDisplay help\n"); +} + +static struct option main_options[] = { + { "index", 1, 0, 'i' }, + { "mtu", 1, 0, 'm' }, + { "security-level", 1, 0, 's' }, + { "type", 1, 0, 't' }, + { "verbose", 0, 0, 'v' }, + { "heart-rate", 0, 0, 'r' }, + { "help", 0, 0, 'h' }, + { } +}; + +static int l2cap_le_att_listen_and_accept(bdaddr_t *src, int sec, + uint8_t src_type) +{ + int sk, nsk; + struct sockaddr_l2 srcaddr, addr; + socklen_t optlen; + struct bt_security btsec; + char ba[18]; + + sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sk < 0) { + perror("Failed to create L2CAP socket"); + return -1; + } + + /* Set up source address */ + memset(&srcaddr, 0, sizeof(srcaddr)); + srcaddr.l2_family = AF_BLUETOOTH; + srcaddr.l2_cid = htobs(ATT_CID); + srcaddr.l2_bdaddr_type = src_type; + bacpy(&srcaddr.l2_bdaddr, src); + + if (bind(sk, (struct sockaddr *) &srcaddr, sizeof(srcaddr)) < 0) { + perror("Failed to bind L2CAP socket"); + goto fail; + } + + /* Set the security level */ + memset(&btsec, 0, sizeof(btsec)); + btsec.level = sec; + if (setsockopt(sk, SOL_BLUETOOTH, BT_SECURITY, &btsec, + sizeof(btsec)) != 0) { + fprintf(stderr, "Failed to set L2CAP security level\n"); + goto fail; + } + + if (listen(sk, 10) < 0) { + perror("Listening on socket failed"); + goto fail; + } + + printf("Started listening on ATT channel. Waiting for connections\n"); + + memset(&addr, 0, sizeof(addr)); + optlen = sizeof(addr); + nsk = accept(sk, (struct sockaddr *) &addr, &optlen); + if (nsk < 0) { + perror("Accept failed"); + goto fail; + } + + ba2str(&addr.l2_bdaddr, ba); + printf("Connect from %s\n", ba); + close(sk); + + return nsk; + +fail: + close(sk); + return -1; +} + +static void notify_usage(void) +{ + printf("Usage: notify [options] <value_handle> <value>\n" + "Options:\n" + "\t -i, --indicate\tSend indication\n" + "e.g.:\n" + "\tnotify 0x0001 00 01 00\n"); +} + +static struct option notify_options[] = { + { "indicate", 0, 0, 'i' }, + { } +}; + +static bool parse_args(char *str, int expected_argc, char **argv, int *argc) +{ + char **ap; + + for (ap = argv; (*ap = strsep(&str, " \t")) != NULL;) { + if (**ap == '\0') + continue; + + (*argc)++; + ap++; + + if (*argc > expected_argc) + return false; + } + + return true; +} + +static void conf_cb(void *user_data) +{ + PRLOG("Received confirmation\n"); +} + +static void cmd_notify(struct server *server, char *cmd_str) +{ + int opt, i; + char *argvbuf[516]; + char **argv = argvbuf; + int argc = 1; + uint16_t handle; + char *endptr = NULL; + int length; + uint8_t *value = NULL; + bool indicate = false; + + if (!parse_args(cmd_str, 514, argv + 1, &argc)) { + printf("Too many arguments\n"); + notify_usage(); + return; + } + + optind = 0; + argv[0] = "notify"; + while ((opt = getopt_long(argc, argv, "+i", notify_options, + NULL)) != -1) { + switch (opt) { + case 'i': + indicate = true; + break; + default: + notify_usage(); + return; + } + } + + argc -= optind; + argv += optind; + + if (argc < 1) { + notify_usage(); + return; + } + + handle = strtol(argv[0], &endptr, 16); + if (!endptr || *endptr != '\0' || !handle) { + printf("Invalid handle: %s\n", argv[0]); + return; + } + + length = argc - 1; + + if (length > 0) { + if (length > UINT16_MAX) { + printf("Value too long\n"); + return; + } + + value = malloc(length); + if (!value) { + printf("Failed to construct value\n"); + return; + } + + for (i = 1; i < argc; i++) { + if (strlen(argv[i]) != 2) { + printf("Invalid value byte: %s\n", + argv[i]); + goto done; + } + + value[i-1] = strtol(argv[i], &endptr, 16); + if (endptr == argv[i] || *endptr != '\0' + || errno == ERANGE) { + printf("Invalid value byte: %s\n", + argv[i]); + goto done; + } + } + } + + if (indicate) { + if (!bt_gatt_server_send_indication(server->gatt, handle, + value, length, + conf_cb, NULL, NULL)) + printf("Failed to initiate indication\n"); + } else if (!bt_gatt_server_send_notification(server->gatt, handle, + value, length, false)) + printf("Failed to initiate notification\n"); + +done: + free(value); +} + +static void heart_rate_usage(void) +{ + printf("Usage: heart-rate on|off\n"); +} + +static void cmd_heart_rate(struct server *server, char *cmd_str) +{ + bool enable; + uint8_t pdu[4]; + struct gatt_db_attribute *attr; + + if (!cmd_str) { + heart_rate_usage(); + return; + } + + if (strcmp(cmd_str, "on") == 0) + enable = true; + else if (strcmp(cmd_str, "off") == 0) + enable = false; + else { + heart_rate_usage(); + return; + } + + if (enable == server->hr_visible) { + printf("Heart Rate Service already %s\n", + enable ? "visible" : "hidden"); + return; + } + + server->hr_visible = enable; + attr = gatt_db_get_attribute(server->db, server->hr_handle); + gatt_db_service_set_active(attr, server->hr_visible); + update_hr_msrmt_simulation(server); + + if (!server->svc_chngd_enabled) + return; + + put_le16(server->hr_handle, pdu); + put_le16(server->hr_handle + 7, pdu + 2); + + server->hr_msrmt_enabled = false; + update_hr_msrmt_simulation(server); + + bt_gatt_server_send_indication(server->gatt, + server->gatt_svc_chngd_handle, + pdu, 4, conf_cb, NULL, NULL); +} + +static void print_uuid(const bt_uuid_t *uuid) +{ + char uuid_str[MAX_LEN_UUID_STR]; + bt_uuid_t uuid128; + + bt_uuid_to_uuid128(uuid, &uuid128); + bt_uuid_to_string(&uuid128, uuid_str, sizeof(uuid_str)); + + printf("%s\n", uuid_str); +} + +static void print_incl(struct gatt_db_attribute *attr, void *user_data) +{ + struct server *server = user_data; + uint16_t handle, start, end; + struct gatt_db_attribute *service; + bt_uuid_t uuid; + + if (!gatt_db_attribute_get_incl_data(attr, &handle, &start, &end)) + return; + + service = gatt_db_get_attribute(server->db, start); + if (!service) + return; + + gatt_db_attribute_get_service_uuid(service, &uuid); + + printf("\t " COLOR_GREEN "include" COLOR_OFF " - handle: " + "0x%04x, - start: 0x%04x, end: 0x%04x," + "uuid: ", handle, start, end); + print_uuid(&uuid); +} + +static void print_desc(struct gatt_db_attribute *attr, void *user_data) +{ + printf("\t\t " COLOR_MAGENTA "descr" COLOR_OFF + " - handle: 0x%04x, uuid: ", + gatt_db_attribute_get_handle(attr)); + print_uuid(gatt_db_attribute_get_type(attr)); +} + +static void print_chrc(struct gatt_db_attribute *attr, void *user_data) +{ + uint16_t handle, value_handle; + uint8_t properties; + uint16_t ext_prop; + bt_uuid_t uuid; + + if (!gatt_db_attribute_get_char_data(attr, &handle, + &value_handle, + &properties, + &ext_prop, + &uuid)) + return; + + printf("\t " COLOR_YELLOW "charac" COLOR_OFF + " - start: 0x%04x, value: 0x%04x, " + "props: 0x%02x, ext_prop: 0x%04x, uuid: ", + handle, value_handle, properties, ext_prop); + print_uuid(&uuid); + + gatt_db_service_foreach_desc(attr, print_desc, NULL); +} + +static void print_service(struct gatt_db_attribute *attr, void *user_data) +{ + struct server *server = user_data; + uint16_t start, end; + bool primary; + bt_uuid_t uuid; + + if (!gatt_db_attribute_get_service_data(attr, &start, &end, &primary, + &uuid)) + return; + + printf(COLOR_RED "service" COLOR_OFF " - start: 0x%04x, " + "end: 0x%04x, type: %s, uuid: ", + start, end, primary ? "primary" : "secondary"); + print_uuid(&uuid); + + gatt_db_service_foreach_incl(attr, print_incl, server); + gatt_db_service_foreach_char(attr, print_chrc, NULL); + + printf("\n"); +} + +static void cmd_services(struct server *server, char *cmd_str) +{ + gatt_db_foreach_service(server->db, NULL, print_service, server); +} + +static bool convert_sign_key(char *optarg, uint8_t key[16]) +{ + int i; + + if (strlen(optarg) != 32) { + printf("sign-key length is invalid\n"); + return false; + } + + for (i = 0; i < 16; i++) { + if (sscanf(optarg + (i * 2), "%2hhx", &key[i]) != 1) + return false; + } + + return true; +} + +static void set_sign_key_usage(void) +{ + printf("Usage: set-sign-key [options]\nOptions:\n" + "\t -c, --sign-key <remote csrk>\tRemote CSRK\n" + "e.g.:\n" + "\tset-sign-key -c D8515948451FEA320DC05A2E88308188\n"); +} + +static bool remote_counter(uint32_t *sign_cnt, void *user_data) +{ + static uint32_t cnt = 0; + + if (*sign_cnt < cnt) + return false; + + cnt = *sign_cnt; + + return true; +} + +static void cmd_set_sign_key(struct server *server, char *cmd_str) +{ + char *argv[3]; + int argc = 0; + uint8_t key[16]; + + memset(key, 0, 16); + + if (!parse_args(cmd_str, 2, argv, &argc)) { + set_sign_key_usage(); + return; + } + + if (argc != 2) { + set_sign_key_usage(); + return; + } + + if (!strcmp(argv[0], "-c") || !strcmp(argv[0], "--sign-key")) { + if (convert_sign_key(argv[1], key)) + bt_att_set_remote_key(server->att, key, remote_counter, + server); + } else + set_sign_key_usage(); +} + +static void cmd_help(struct server *server, char *cmd_str); + +typedef void (*command_func_t)(struct server *server, char *cmd_str); + +static struct { + char *cmd; + command_func_t func; + char *doc; +} command[] = { + { "help", cmd_help, "\tDisplay help message" }, + { "notify", cmd_notify, "\tSend handle-value notification" }, + { "heart-rate", cmd_heart_rate, "\tHide/Unhide Heart Rate Service" }, + { "services", cmd_services, "\tEnumerate all services" }, + { "set-sign-key", cmd_set_sign_key, + "\tSet remote signing key for signed write command"}, + { } +}; + +static void cmd_help(struct server *server, char *cmd_str) +{ + int i; + + printf("Commands:\n"); + for (i = 0; command[i].cmd; i++) + printf("\t%-15s\t%s\n", command[i].cmd, command[i].doc); +} + +static void prompt_read_cb(int fd, uint32_t events, void *user_data) +{ + ssize_t read; + size_t len = 0; + char *line = NULL; + char *cmd = NULL, *args; + struct server *server = user_data; + int i; + + if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) { + mainloop_quit(); + return; + } + + read = getline(&line, &len, stdin); + if (read < 0) + return; + + if (read <= 1) { + cmd_help(server, NULL); + print_prompt(); + return; + } + + line[read-1] = '\0'; + args = line; + + while ((cmd = strsep(&args, " \t"))) + if (*cmd != '\0') + break; + + if (!cmd) + goto failed; + + for (i = 0; command[i].cmd; i++) { + if (strcmp(command[i].cmd, cmd) == 0) + break; + } + + if (command[i].cmd) + command[i].func(server, args); + else + fprintf(stderr, "Unknown command: %s\n", line); + +failed: + print_prompt(); + + free(line); +} + +static void signal_cb(int signum, void *user_data) +{ + switch (signum) { + case SIGINT: + case SIGTERM: + mainloop_quit(); + break; + default: + break; + } +} + +int main(int argc, char *argv[]) +{ + int opt; + bdaddr_t src_addr; + int dev_id = -1; + int fd; + int sec = BT_SECURITY_LOW; + uint8_t src_type = BDADDR_LE_PUBLIC; + uint16_t mtu = 0; + bool hr_visible = false; + struct server *server; + + while ((opt = getopt_long(argc, argv, "+hvrs:t:m:i:", + main_options, NULL)) != -1) { + switch (opt) { + case 'h': + usage(); + return EXIT_SUCCESS; + case 'v': + verbose = true; + break; + case 'r': + hr_visible = true; + break; + case 's': + if (strcmp(optarg, "low") == 0) + sec = BT_SECURITY_LOW; + else if (strcmp(optarg, "medium") == 0) + sec = BT_SECURITY_MEDIUM; + else if (strcmp(optarg, "high") == 0) + sec = BT_SECURITY_HIGH; + else { + fprintf(stderr, "Invalid security level\n"); + return EXIT_FAILURE; + } + break; + case 't': + if (strcmp(optarg, "random") == 0) + src_type = BDADDR_LE_RANDOM; + else if (strcmp(optarg, "public") == 0) + src_type = BDADDR_LE_PUBLIC; + else { + fprintf(stderr, + "Allowed types: random, public\n"); + return EXIT_FAILURE; + } + break; + case 'm': { + int arg; + + arg = atoi(optarg); + if (arg <= 0) { + fprintf(stderr, "Invalid MTU: %d\n", arg); + return EXIT_FAILURE; + } + + if (arg > UINT16_MAX) { + fprintf(stderr, "MTU too large: %d\n", arg); + return EXIT_FAILURE; + } + + mtu = (uint16_t) arg; + break; + } + case 'i': + dev_id = hci_devid(optarg); + if (dev_id < 0) { + perror("Invalid adapter"); + return EXIT_FAILURE; + } + + break; + default: + fprintf(stderr, "Invalid option: %c\n", opt); + return EXIT_FAILURE; + } + } + + argc -= optind; + argv -= optind; + optind = 0; + + if (argc) { + usage(); + return EXIT_SUCCESS; + } + + if (dev_id == -1) + bacpy(&src_addr, BDADDR_ANY); + else if (hci_devba(dev_id, &src_addr) < 0) { + perror("Adapter not available"); + return EXIT_FAILURE; + } + + fd = l2cap_le_att_listen_and_accept(&src_addr, sec, src_type); + if (fd < 0) { + fprintf(stderr, "Failed to accept L2CAP ATT connection\n"); + return EXIT_FAILURE; + } + + mainloop_init(); + + server = server_create(fd, mtu, hr_visible); + if (!server) { + close(fd); + return EXIT_FAILURE; + } + + if (mainloop_add_fd(fileno(stdin), + EPOLLIN | EPOLLRDHUP | EPOLLHUP | EPOLLERR, + prompt_read_cb, server, NULL) < 0) { + fprintf(stderr, "Failed to initialize console\n"); + server_destroy(server); + + return EXIT_FAILURE; + } + + printf("Running GATT server\n"); + + print_prompt(); + + mainloop_run_with_signal(signal_cb, NULL); + + printf("\n\nShutting down...\n"); + + server_destroy(server); + + return EXIT_SUCCESS; +} diff --git a/drivers/net/wireless/eswin/compile_test.sh b/drivers/net/wireless/eswin/compile_test.sh new file mode 100644 index 000000000000..faa142f88c60 --- /dev/null +++ b/drivers/net/wireless/eswin/compile_test.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +linux_dir=/net/rwlab-srv1/nx_share/linux +ARCH=${ARCH:-x86} +CROSS_COMPILE=${CROSS_COMPILE:-x86_64-poky-linux-} +error=0 + +if [ ! -d $linux_dir ] +then + echo "Invalid path: ${linux_dir}" >&2 + exit 1 +fi + +for version in $(find $linux_dir -maxdepth 2 -type d -name cevav7 | sort) +do + echo "" + echo "#####################################################" + echo "Testing $version" + echo "#####################################################" + ARCH=$ARCH CROSS_COMPILE=$CROSS_COMPILE KERNELDIR=$version \ + CONFIG_ECRNX_SOFTMAC=m CONFIG_ECRNX_FULLMAC=m CONFIG_ECRNX_FHOST=m make -j 8 + + if [ $? -ne 0 ] + then + ((error++)) + fi +done + +exit $error diff --git a/drivers/net/wireless/eswin/ecrnx_bfmer.c b/drivers/net/wireless/eswin/ecrnx_bfmer.c new file mode 100644 index 000000000000..467da01f9e4f --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_bfmer.c @@ -0,0 +1,105 @@ +/** + ****************************************************************************** + * + * @file ecrnx_bfmer.c + * + * @brief VHT Beamformer function definitions + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +/** + * INCLUDE FILES + ****************************************************************************** + */ + +#include <linux/slab.h> +#include "ecrnx_bfmer.h" + +/** + * FUNCTION DEFINITIONS + ****************************************************************************** + */ + +int ecrnx_bfmer_report_add(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *ecrnx_sta, + unsigned int length) +{ + gfp_t flags; + struct ecrnx_bfmer_report *bfm_report ; + + if (in_softirq()) + flags = GFP_ATOMIC; + else + flags = GFP_KERNEL; + + /* Allocate a structure that will contain the beamforming report */ + bfm_report = kmalloc(sizeof(*bfm_report) + length, flags); + + + /* Check report allocation */ + if (!bfm_report) { + /* Do not use beamforming */ + return -1; + } + + /* Store report length */ + bfm_report->length = length; + + /* + * Need to provide a Virtual Address to the MAC so that it can + * upload the received Beamforming Report in driver memory + */ + bfm_report->dma_addr = dma_map_single(ecrnx_hw->dev, &bfm_report->report[0], + length, DMA_FROM_DEVICE); + + /* Check DMA mapping result */ + if (dma_mapping_error(ecrnx_hw->dev, bfm_report->dma_addr)) { + /* Free allocated report */ + kfree(bfm_report); + /* And leave */ + return -1; + } + + /* Store report structure */ + ecrnx_sta->bfm_report = bfm_report; + + return 0; +} + +void ecrnx_bfmer_report_del(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *ecrnx_sta) +{ + /* Verify if a report has been allocated */ + if (ecrnx_sta->bfm_report) { + struct ecrnx_bfmer_report *bfm_report = ecrnx_sta->bfm_report; + + /* Unmap DMA region */ + dma_unmap_single(ecrnx_hw->dev, bfm_report->dma_addr, + bfm_report->length, DMA_BIDIRECTIONAL); + + /* Free allocated report structure and clean the pointer */ + kfree(bfm_report); + ecrnx_sta->bfm_report = NULL; + } +} + +#ifdef CONFIG_ECRNX_FULLMAC +u8 ecrnx_bfmer_get_rx_nss(const struct ieee80211_vht_cap *vht_capa) +{ + int i; + u8 rx_nss = 0; + u16 rx_mcs_map = le16_to_cpu(vht_capa->supp_mcs.rx_mcs_map); + + for (i = 7; i >= 0; i--) { + u8 mcs = (rx_mcs_map >> (2 * i)) & 3; + + if (mcs != IEEE80211_VHT_MCS_NOT_SUPPORTED) { + rx_nss = i + 1; + break; + } + } + + return rx_nss; +} +#endif /* CONFIG_ECRNX_FULLMAC */ diff --git a/drivers/net/wireless/eswin/ecrnx_bfmer.h b/drivers/net/wireless/eswin/ecrnx_bfmer.h new file mode 100644 index 000000000000..6bb30ef27895 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_bfmer.h @@ -0,0 +1,100 @@ +/** + ****************************************************************************** + * + * @file ecrnx_bfmer.h + * + * @brief VHT Beamformer function declarations + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef _ECRNX_BFMER_H_ +#define _ECRNX_BFMER_H_ + +/** + * INCLUDE FILES + ****************************************************************************** + */ + +#include "ecrnx_defs.h" + +/** + * DEFINES + ****************************************************************************** + */ + +/// Maximal supported report length (in bytes) +#define ECRNX_BFMER_REPORT_MAX_LEN 2048 + +/// Size of the allocated report space (twice the maximum report length) +#define ECRNX_BFMER_REPORT_SPACE_SIZE (ECRNX_BFMER_REPORT_MAX_LEN * 2) + +/** + * TYPE DEFINITIONS + ****************************************************************************** + */ + +/* + * Structure used to store a beamforming report. + */ +struct ecrnx_bfmer_report { + dma_addr_t dma_addr; /* Virtual address provided to MAC for + DMA transfer of the Beamforming Report */ + unsigned int length; /* Report Length */ + u8 report[1]; /* Report to be used for VHT TX Beamforming */ +}; + +/** + * FUNCTION DECLARATIONS + ****************************************************************************** + */ + +/** + ****************************************************************************** + * @brief Allocate memory aiming to contains the Beamforming Report received + * from a Beamformee capable capable. + * The providing length shall be large enough to contain the VHT Compressed + * Beaforming Report and the MU Exclusive part. + * It also perform a DMA Mapping providing an address to be provided to the HW + * responsible for the DMA transfer of the report. + * If successful a struct ecrnx_bfmer_report object is allocated, it's address + * is stored in ecrnx_sta->bfm_report. + * + * @param[in] ecrnx_hw PHY Information + * @param[in] ecrnx_sta Peer STA Information + * @param[in] length Memory size to be allocated + * + * @return 0 if operation is successful, else -1. + ****************************************************************************** + */ +int ecrnx_bfmer_report_add(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *ecrnx_sta, + unsigned int length); + +/** + ****************************************************************************** + * @brief Free a previously allocated memory intended to be used for + * Beamforming Reports. + * + * @param[in] ecrnx_hw PHY Information + * @param[in] ecrnx_sta Peer STA Information + * + ****************************************************************************** + */ +void ecrnx_bfmer_report_del(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *ecrnx_sta); + +#ifdef CONFIG_ECRNX_FULLMAC +/** + ****************************************************************************** + * @brief Parse a Rx VHT-MCS map in order to deduce the maximum number of + * Spatial Streams supported by a beamformee. + * + * @param[in] vht_capa Received VHT Capability field. + * + ****************************************************************************** + */ +u8 ecrnx_bfmer_get_rx_nss(const struct ieee80211_vht_cap *vht_capa); +#endif /* CONFIG_ECRNX_FULLMAC */ + +#endif /* _ECRNX_BFMER_H_ */ diff --git a/drivers/net/wireless/eswin/ecrnx_cfgfile.c b/drivers/net/wireless/eswin/ecrnx_cfgfile.c new file mode 100644 index 000000000000..c753677c5749 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_cfgfile.c @@ -0,0 +1,288 @@ +/** + **************************************************************************************** + * + * @file ecrnx_configparse.c + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ +#include <linux/firmware.h> +#include <linux/if_ether.h> + +#include "ecrnx_defs.h" +#include "ecrnx_cfgfile.h" +#include "ecrnx_debug.h" +#include "ecrnx_debugfs_func.h" + +/** + * + */ +static const char *ecrnx_find_tag(const u8 *file_data, unsigned int file_size, + const char *tag_name, unsigned int tag_len) +{ + unsigned int curr, line_start = 0, line_size; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Walk through all the lines of the configuration file */ + while (line_start < file_size) { + /* Search the end of the current line (or the end of the file) */ + for (curr = line_start; curr < file_size; curr++) + if (file_data[curr] == '\n') + break; + + /* Compute the line size */ + line_size = curr - line_start; + + /* Check if this line contains the expected tag */ + if ((line_size == (strlen(tag_name) + tag_len)) && + (!strncmp(&file_data[line_start], tag_name, strlen(tag_name)))) + return (&file_data[line_start + strlen(tag_name)]); + + /* Move to next line */ + line_start = curr + 1; + } + + /* Tag not found */ + return NULL; +} + +/** + * Parse the Config file used at init time + */ +int ecrnx_parse_configfile(struct ecrnx_hw *ecrnx_hw, const char *filename) +{ + const struct firmware *config_fw; + u8 dflt_mac[ETH_ALEN] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x5f }; + int ret; + const u8 *tag_ptr; + bool mac_flag = false, dbg_level_flag = false, fw_log_lv_flag = false, fw_log_type_flag = false; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0) + ret = firmware_request_nowarn(&config_fw, filename, ecrnx_hw->dev); //avoid the files not exit error +#else + ret = request_firmware(&config_fw, filename, ecrnx_hw->dev); +#endif + + if (ret == 0) { + /* Get MAC Address */ + tag_ptr = ecrnx_find_tag(config_fw->data, config_fw->size, "MAC_ADDR=", strlen("00:00:00:00:00:00")); + if (tag_ptr != NULL) { + u8 *addr = ecrnx_hw->conf_param.mac_addr; + if (sscanf(tag_ptr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + addr + 0, addr + 1, addr + 2, + addr + 3, addr + 4, addr + 5) == ETH_ALEN){ + mac_flag = true; + } + } + + tag_ptr = ecrnx_find_tag(config_fw->data, config_fw->size, "DRIVER_LOG_LEVEL=", strlen("0")); + if (tag_ptr != NULL){ + if(sscanf(tag_ptr, "%hhx", &ecrnx_hw->conf_param.host_driver_log_level) == 1){ + ecrnx_dbg_level = ecrnx_hw->conf_param.host_driver_log_level; + dbg_level_flag = true; + } + } + + tag_ptr = ecrnx_find_tag(config_fw->data, config_fw->size, "FW_LOG_LEVEL=", strlen("0")); + if (tag_ptr != NULL){ + if(sscanf(tag_ptr, "%hhx", &ecrnx_hw->conf_param.fw_log_level) == 1){ + fw_log_lv_flag = true; + } + } + + tag_ptr = ecrnx_find_tag(config_fw->data, config_fw->size, "FW_LOG_TYPE=", strlen("0")); + if (tag_ptr != NULL){ + if(sscanf(tag_ptr, "%hhx", &ecrnx_hw->conf_param.fw_log_type) == 1){ + fw_log_type_flag = true; + } + } + + /* Release the configuration file */ + release_firmware(config_fw); + } + + if(!mac_flag){ + memcpy(ecrnx_hw->conf_param.mac_addr, dflt_mac, ETH_ALEN); + } + + if(!dbg_level_flag){ + ecrnx_hw->conf_param.host_driver_log_level = ecrnx_dbg_level; + } + + if(!fw_log_lv_flag){ + ecrnx_hw->conf_param.fw_log_level = log_ctl.level; + } + + if(!fw_log_type_flag){ + ecrnx_hw->conf_param.fw_log_type = log_ctl.dir; + } + + ECRNX_PRINT("MAC Address is:%pM\n", ecrnx_hw->conf_param.mac_addr); + ECRNX_PRINT("host driver log level is:%d \n", ecrnx_hw->conf_param.host_driver_log_level); + ECRNX_PRINT("firmware log level is:%d \n", ecrnx_hw->conf_param.fw_log_level); + + if(0 == ecrnx_hw->conf_param.fw_log_type){ + ECRNX_PRINT("firmware log level type:%d (print to chip's uart) \n", ecrnx_hw->conf_param.fw_log_type); + }else if(1 == ecrnx_hw->conf_param.fw_log_type){ + ECRNX_PRINT("firmware log level type:%d (print to host debugfs) \n", ecrnx_hw->conf_param.fw_log_type); + }else if(2 == ecrnx_hw->conf_param.fw_log_type){ + ECRNX_PRINT("firmware log level type:%d (print to host kernel) \n", ecrnx_hw->conf_param.fw_log_type); + }else{ + ECRNX_ERR("firmware log level type error;\n"); + } + return 0; +} + +/** + * Parse the Config file used at init time + */ +int ecrnx_parse_phy_configfile(struct ecrnx_hw *ecrnx_hw, const char *filename, + struct ecrnx_phy_conf_file *config, int path) +{ + const struct firmware *config_fw; + int ret; + const u8 *tag_ptr; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + if ((ret = request_firmware(&config_fw, filename, ecrnx_hw->dev))) { + ECRNX_ERR(KERN_CRIT "%s: Failed to get %s (%d)\n", __func__, filename, ret); + return ret; + } + + /* Get Trident path mapping */ + tag_ptr = ecrnx_find_tag(config_fw->data, config_fw->size, + "TRD_PATH_MAPPING=", strlen("00")); + if (tag_ptr != NULL) { + u8 val; + if (sscanf(tag_ptr, "%hhx", &val) == 1) + config->trd.path_mapping = val; + else + config->trd.path_mapping = path; + } else + config->trd.path_mapping = path; + + ECRNX_DBG("Trident path mapping is: %d\n", config->trd.path_mapping); + + /* Get DC offset compensation */ + tag_ptr = ecrnx_find_tag(config_fw->data, config_fw->size, + "TX_DC_OFF_COMP=", strlen("00000000")); + if (tag_ptr != NULL) { + if (sscanf(tag_ptr, "%08x", &config->trd.tx_dc_off_comp) != 1) + config->trd.tx_dc_off_comp = 0; + } else + config->trd.tx_dc_off_comp = 0; + + ECRNX_DBG("TX DC offset compensation is: %08X\n", config->trd.tx_dc_off_comp); + + /* Get Karst TX IQ compensation value for path0 on 2.4GHz */ + tag_ptr = ecrnx_find_tag(config_fw->data, config_fw->size, + "KARST_TX_IQ_COMP_2_4G_PATH_0=", strlen("00000000")); + if (tag_ptr != NULL) { + if (sscanf(tag_ptr, "%08x", &config->karst.tx_iq_comp_2_4G[0]) != 1) + config->karst.tx_iq_comp_2_4G[0] = 0x01000000; + } else + config->karst.tx_iq_comp_2_4G[0] = 0x01000000; + + ECRNX_DBG("Karst TX IQ compensation for path 0 on 2.4GHz is: %08X\n", config->karst.tx_iq_comp_2_4G[0]); + + /* Get Karst TX IQ compensation value for path1 on 2.4GHz */ + tag_ptr = ecrnx_find_tag(config_fw->data, config_fw->size, + "KARST_TX_IQ_COMP_2_4G_PATH_1=", strlen("00000000")); + if (tag_ptr != NULL) { + if (sscanf(tag_ptr, "%08x", &config->karst.tx_iq_comp_2_4G[1]) != 1) + config->karst.tx_iq_comp_2_4G[1] = 0x01000000; + } else + config->karst.tx_iq_comp_2_4G[1] = 0x01000000; + + ECRNX_DBG("Karst TX IQ compensation for path 1 on 2.4GHz is: %08X\n", config->karst.tx_iq_comp_2_4G[1]); + + /* Get Karst RX IQ compensation value for path0 on 2.4GHz */ + tag_ptr = ecrnx_find_tag(config_fw->data, config_fw->size, + "KARST_RX_IQ_COMP_2_4G_PATH_0=", strlen("00000000")); + if (tag_ptr != NULL) { + if (sscanf(tag_ptr, "%08x", &config->karst.rx_iq_comp_2_4G[0]) != 1) + config->karst.rx_iq_comp_2_4G[0] = 0x01000000; + } else + config->karst.rx_iq_comp_2_4G[0] = 0x01000000; + + ECRNX_DBG("Karst RX IQ compensation for path 0 on 2.4GHz is: %08X\n", config->karst.rx_iq_comp_2_4G[0]); + + /* Get Karst RX IQ compensation value for path1 on 2.4GHz */ + tag_ptr = ecrnx_find_tag(config_fw->data, config_fw->size, + "KARST_RX_IQ_COMP_2_4G_PATH_1=", strlen("00000000")); + if (tag_ptr != NULL) { + if (sscanf(tag_ptr, "%08x", &config->karst.rx_iq_comp_2_4G[1]) != 1) + config->karst.rx_iq_comp_2_4G[1] = 0x01000000; + } else + config->karst.rx_iq_comp_2_4G[1] = 0x01000000; + + ECRNX_DBG("Karst RX IQ compensation for path 1 on 2.4GHz is: %08X\n", config->karst.rx_iq_comp_2_4G[1]); + + /* Get Karst TX IQ compensation value for path0 on 5GHz */ + tag_ptr = ecrnx_find_tag(config_fw->data, config_fw->size, + "KARST_TX_IQ_COMP_5G_PATH_0=", strlen("00000000")); + if (tag_ptr != NULL) { + if (sscanf(tag_ptr, "%08x", &config->karst.tx_iq_comp_5G[0]) != 1) + config->karst.tx_iq_comp_5G[0] = 0x01000000; + } else + config->karst.tx_iq_comp_5G[0] = 0x01000000; + + ECRNX_DBG("Karst TX IQ compensation for path 0 on 5GHz is: %08X\n", config->karst.tx_iq_comp_5G[0]); + + /* Get Karst TX IQ compensation value for path1 on 5GHz */ + tag_ptr = ecrnx_find_tag(config_fw->data, config_fw->size, + "KARST_TX_IQ_COMP_5G_PATH_1=", strlen("00000000")); + if (tag_ptr != NULL) { + if (sscanf(tag_ptr, "%08x", &config->karst.tx_iq_comp_5G[1]) != 1) + config->karst.tx_iq_comp_5G[1] = 0x01000000; + } else + config->karst.tx_iq_comp_5G[1] = 0x01000000; + + ECRNX_DBG("Karst TX IQ compensation for path 1 on 5GHz is: %08X\n", config->karst.tx_iq_comp_5G[1]); + + /* Get Karst RX IQ compensation value for path0 on 5GHz */ + tag_ptr = ecrnx_find_tag(config_fw->data, config_fw->size, + "KARST_RX_IQ_COMP_5G_PATH_0=", strlen("00000000")); + if (tag_ptr != NULL) { + if (sscanf(tag_ptr, "%08x", &config->karst.rx_iq_comp_5G[0]) != 1) + config->karst.rx_iq_comp_5G[0] = 0x01000000; + } else + config->karst.rx_iq_comp_5G[0] = 0x01000000; + + ECRNX_DBG("Karst RX IQ compensation for path 0 on 5GHz is: %08X\n", config->karst.rx_iq_comp_5G[0]); + + /* Get Karst RX IQ compensation value for path1 on 5GHz */ + tag_ptr = ecrnx_find_tag(config_fw->data, config_fw->size, + "KARST_RX_IQ_COMP_5G_PATH_1=", strlen("00000000")); + if (tag_ptr != NULL) { + if (sscanf(tag_ptr, "%08x", &config->karst.rx_iq_comp_5G[1]) != 1) + config->karst.rx_iq_comp_5G[1] = 0x01000000; + } else + config->karst.rx_iq_comp_5G[1] = 0x01000000; + + ECRNX_DBG("Karst RX IQ compensation for path 1 on 5GHz is: %08X\n", config->karst.rx_iq_comp_5G[1]); + + /* Get Karst default path */ + tag_ptr = ecrnx_find_tag(config_fw->data, config_fw->size, + "KARST_DEFAULT_PATH=", strlen("00")); + if (tag_ptr != NULL) { + u8 val; + if (sscanf(tag_ptr, "%hhx", &val) == 1) + config->karst.path_used = val; + else + config->karst.path_used = path; + } else + config->karst.path_used = path; + + ECRNX_DBG("Karst default path is: %d\n", config->karst.path_used); + + /* Release the configuration file */ + release_firmware(config_fw); + + return 0; +} + diff --git a/drivers/net/wireless/eswin/ecrnx_cfgfile.h b/drivers/net/wireless/eswin/ecrnx_cfgfile.h new file mode 100644 index 000000000000..bdf7eaa12eb7 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_cfgfile.h @@ -0,0 +1,37 @@ +/** + **************************************************************************************** + * + * @file ecrnx_cfgfile.h + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ + +#ifndef _ECRNX_CFGFILE_H_ +#define _ECRNX_CFGFILE_H_ + +/* + * Structure used to retrieve information from the Config file used at Initialization time + */ +struct ecrnx_conf_file { + u8 mac_addr[ETH_ALEN]; + u8 host_driver_log_level; + u8 fw_log_level; + u8 fw_log_type; +}; + +/* + * Structure used to retrieve information from the PHY Config file used at Initialization time + */ +struct ecrnx_phy_conf_file { + struct phy_trd_cfg_tag trd; + struct phy_karst_cfg_tag karst; + struct phy_cataxia_cfg_tag cataxia; +}; + +int ecrnx_parse_configfile(struct ecrnx_hw *ecrnx_hw, const char *filename); +int ecrnx_parse_phy_configfile(struct ecrnx_hw *ecrnx_hw, const char *filename, + struct ecrnx_phy_conf_file *config, int path); + +#endif /* _ECRNX_CFGFILE_H_ */ diff --git a/drivers/net/wireless/eswin/ecrnx_cmds.c b/drivers/net/wireless/eswin/ecrnx_cmds.c new file mode 100644 index 000000000000..5e4781af500a --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_cmds.c @@ -0,0 +1,325 @@ +/** + ****************************************************************************** + * + * ecrnx_cmds.c + * + * Handles queueing (push to IPC, ack/cfm from IPC) of commands issued to + * LMAC FW + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#include <linux/list.h> + +#include "ecrnx_cmds.h" +#include "ecrnx_defs.h" +#include "ecrnx_strs.h" +#define CREATE_TRACE_POINTS +#include "ecrnx_events.h" + +/** + * + */ +static void cmd_dump(const struct ecrnx_cmd *cmd) +{ +#ifndef CONFIG_ECRNX_FHOST + ECRNX_PRINT("tkn[%d] flags:%04x result:%3d cmd:%4d-%-24s - reqcfm(%4d-%-s)\n", + cmd->tkn, cmd->flags, cmd->result, cmd->id, ECRNX_ID2STR(cmd->id), + cmd->reqid, cmd->reqid != (lmac_msg_id_t)-1 ? ECRNX_ID2STR(cmd->reqid) : "none"); +#endif +} + +/** + * + */ +static void cmd_complete(struct ecrnx_cmd_mgr *cmd_mgr, struct ecrnx_cmd *cmd) +{ + lockdep_assert_held(&cmd_mgr->lock); + + list_del(&cmd->list); + cmd_mgr->queue_sz--; + + cmd->flags |= ECRNX_CMD_FLAG_DONE; + if (cmd->flags & ECRNX_CMD_FLAG_NONBLOCK) { + kfree(cmd); + } else { + if (ECRNX_CMD_WAIT_COMPLETE(cmd->flags)) { + cmd->result = 0; + complete(&cmd->complete); + } + } +} + +/** + * + */ +static int cmd_mgr_queue(struct ecrnx_cmd_mgr *cmd_mgr, struct ecrnx_cmd *cmd) +{ + struct ecrnx_hw *ecrnx_hw = container_of(cmd_mgr, struct ecrnx_hw, cmd_mgr); + bool defer_push = false; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + trace_msg_send(cmd->id); + + spin_lock_bh(&cmd_mgr->lock); + + if (cmd_mgr->state == ECRNX_CMD_MGR_STATE_CRASHED) { + ECRNX_PRINT(KERN_CRIT"cmd queue crashed\n"); + cmd->result = -EPIPE; + spin_unlock_bh(&cmd_mgr->lock); + return -EPIPE; + } + + #ifndef CONFIG_ECRNX_FHOST + if (!list_empty(&cmd_mgr->cmds)) { + struct ecrnx_cmd *last; + + if (cmd_mgr->queue_sz == cmd_mgr->max_queue_sz) { + ECRNX_ERR(KERN_CRIT"Too many cmds (%d) already queued\n", + cmd_mgr->max_queue_sz); + cmd->result = -ENOMEM; + spin_unlock_bh(&cmd_mgr->lock); + return -ENOMEM; + } + last = list_entry(cmd_mgr->cmds.prev, struct ecrnx_cmd, list); + if (last->flags & (ECRNX_CMD_FLAG_WAIT_ACK | ECRNX_CMD_FLAG_WAIT_PUSH)) { +#if 0 // queue even NONBLOCK command. + if (cmd->flags & ECRNX_CMD_FLAG_NONBLOCK) { + printk(KERN_CRIT"cmd queue busy\n"); + cmd->result = -EBUSY; + spin_unlock_bh(&cmd_mgr->lock); + return -EBUSY; + } +#endif + cmd->flags |= ECRNX_CMD_FLAG_WAIT_PUSH; + defer_push = true; + } + } + #endif + + cmd->flags |= ECRNX_CMD_FLAG_WAIT_ACK; + if (cmd->flags & ECRNX_CMD_FLAG_REQ_CFM) + cmd->flags |= ECRNX_CMD_FLAG_WAIT_CFM; + + cmd->tkn = cmd_mgr->next_tkn++; + cmd->result = -EINTR; + + if (!(cmd->flags & ECRNX_CMD_FLAG_NONBLOCK)) + init_completion(&cmd->complete); + + list_add_tail(&cmd->list, &cmd_mgr->cmds); + cmd_mgr->queue_sz++; + spin_unlock_bh(&cmd_mgr->lock); + + if (!defer_push) { + ecrnx_ipc_msg_push(ecrnx_hw, cmd, ECRNX_CMD_A2EMSG_LEN(cmd->a2e_msg)); + kfree(cmd->a2e_msg); + } + + if (!(cmd->flags & ECRNX_CMD_FLAG_NONBLOCK)) { + #ifdef CONFIG_ECRNX_FHOST + if (wait_for_completion_killable(&cmd->complete)) { + if (cmd->flags & ECRNX_CMD_FLAG_WAIT_ACK) + up(&ecrnx_hw->term.fw_cmd); + cmd->result = -EINTR; + spin_lock_bh(&cmd_mgr->lock); + cmd_complete(cmd_mgr, cmd); + spin_unlock_bh(&cmd_mgr->lock); + /* TODO: kill the cmd at fw level */ + } else { + if (cmd->flags & ECRNX_CMD_FLAG_WAIT_ACK) + up(&ecrnx_hw->term.fw_cmd); + } + #else + unsigned long tout = msecs_to_jiffies(ECRNX_80211_CMD_TIMEOUT_MS * cmd_mgr->queue_sz); + if (!wait_for_completion_killable_timeout(&cmd->complete, tout)) { + ECRNX_PRINT("cmd timed-out queue_sz:%d\n",cmd_mgr->queue_sz); + cmd_dump(cmd); + spin_lock_bh(&cmd_mgr->lock); + //cmd_mgr->state = ECRNX_CMD_MGR_STATE_CRASHED; + if (!(cmd->flags & ECRNX_CMD_FLAG_DONE)) { + cmd->result = -ETIMEDOUT; + cmd_complete(cmd_mgr, cmd); + } + spin_unlock_bh(&cmd_mgr->lock); + } + #endif + } else { + cmd->result = 0; + } + + return 0; +} + +/** + * + */ +static int cmd_mgr_llind(struct ecrnx_cmd_mgr *cmd_mgr, struct ecrnx_cmd *cmd) +{ + struct ecrnx_cmd *cur, *acked = NULL, *next = NULL; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + spin_lock(&cmd_mgr->lock); + list_for_each_entry(cur, &cmd_mgr->cmds, list) { + if (!acked) { + if (cur->tkn == cmd->tkn) { + if (WARN_ON_ONCE(cur != cmd)) { + cmd_dump(cmd); + } + acked = cur; + continue; + } + } + if (cur->flags & ECRNX_CMD_FLAG_WAIT_PUSH) { + next = cur; + break; + } + } + if (!acked) { + ECRNX_PRINT(KERN_CRIT "Error: acked cmd not found\n"); + } else { + cmd->flags &= ~ECRNX_CMD_FLAG_WAIT_ACK; + if (ECRNX_CMD_WAIT_COMPLETE(cmd->flags)) + cmd_complete(cmd_mgr, cmd); + } + if (next) { + struct ecrnx_hw *ecrnx_hw = container_of(cmd_mgr, struct ecrnx_hw, cmd_mgr); + next->flags &= ~ECRNX_CMD_FLAG_WAIT_PUSH; + ecrnx_ipc_msg_push(ecrnx_hw, next, ECRNX_CMD_A2EMSG_LEN(next->a2e_msg)); + kfree(next->a2e_msg); + } + spin_unlock(&cmd_mgr->lock); + + return 0; +} + + + +static int cmd_mgr_run_callback(struct ecrnx_hw *ecrnx_hw, struct ecrnx_cmd *cmd, + struct ecrnx_cmd_e2amsg *msg, msg_cb_fct cb) +{ + int res; + + if (! cb) + return 0; + + spin_lock(&ecrnx_hw->cb_lock); + res = cb(ecrnx_hw, cmd, msg); + spin_unlock(&ecrnx_hw->cb_lock); + + return res; +} + +/** + * + + */ +static int cmd_mgr_msgind(struct ecrnx_cmd_mgr *cmd_mgr, struct ecrnx_cmd_e2amsg *msg, + msg_cb_fct cb) +{ + struct ecrnx_hw *ecrnx_hw = container_of(cmd_mgr, struct ecrnx_hw, cmd_mgr); + struct ecrnx_cmd *cmd; + bool found = false; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + trace_msg_recv(msg->id); + + spin_lock(&cmd_mgr->lock); + list_for_each_entry(cmd, &cmd_mgr->cmds, list) { + if (cmd->reqid == msg->id && + (cmd->flags & ECRNX_CMD_FLAG_WAIT_CFM)) { + + if (!cmd_mgr_run_callback(ecrnx_hw, cmd, msg, cb)) { + found = true; + cmd->flags &= ~ECRNX_CMD_FLAG_WAIT_CFM; + + if (WARN((msg->param_len > ECRNX_CMD_E2AMSG_LEN_MAX), + "Unexpect E2A msg len %d > %d\n", msg->param_len, + ECRNX_CMD_E2AMSG_LEN_MAX)) { + msg->param_len = ECRNX_CMD_E2AMSG_LEN_MAX; + } + + if (cmd->e2a_msg && msg->param_len) + memcpy(cmd->e2a_msg, &msg->param, msg->param_len); + + if (ECRNX_CMD_WAIT_COMPLETE(cmd->flags)) + cmd_complete(cmd_mgr, cmd); + + break; + } + } + } + spin_unlock(&cmd_mgr->lock); + + if (!found) + cmd_mgr_run_callback(ecrnx_hw, NULL, msg, cb); + + ECRNX_DBG("%s exit!! \n", __func__); + return 0; +} + +/** + * + */ +static void cmd_mgr_print(struct ecrnx_cmd_mgr *cmd_mgr) +{ + struct ecrnx_cmd *cur; + + spin_lock_bh(&cmd_mgr->lock); + ECRNX_PRINT("q_sz/max: %2d / %2d - next tkn: %d\n", + cmd_mgr->queue_sz, cmd_mgr->max_queue_sz, + cmd_mgr->next_tkn); + list_for_each_entry(cur, &cmd_mgr->cmds, list) { + cmd_dump(cur); + } + spin_unlock_bh(&cmd_mgr->lock); +} + +/** + * + */ +static void cmd_mgr_drain(struct ecrnx_cmd_mgr *cmd_mgr) +{ + struct ecrnx_cmd *cur, *nxt; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + spin_lock_bh(&cmd_mgr->lock); + list_for_each_entry_safe(cur, nxt, &cmd_mgr->cmds, list) { + list_del(&cur->list); + cmd_mgr->queue_sz--; + if (!(cur->flags & ECRNX_CMD_FLAG_NONBLOCK)) + complete(&cur->complete); + } + spin_unlock_bh(&cmd_mgr->lock); +} + +/** + * + */ +void ecrnx_cmd_mgr_init(struct ecrnx_cmd_mgr *cmd_mgr) +{ + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + INIT_LIST_HEAD(&cmd_mgr->cmds); + spin_lock_init(&cmd_mgr->lock); + cmd_mgr->max_queue_sz = ECRNX_CMD_MAX_QUEUED; + cmd_mgr->queue = &cmd_mgr_queue; + cmd_mgr->print = &cmd_mgr_print; + cmd_mgr->drain = &cmd_mgr_drain; + cmd_mgr->llind = &cmd_mgr_llind; + cmd_mgr->msgind = &cmd_mgr_msgind; +} + +/** + * + */ +void ecrnx_cmd_mgr_deinit(struct ecrnx_cmd_mgr *cmd_mgr) +{ + cmd_mgr->print(cmd_mgr); + cmd_mgr->drain(cmd_mgr); + memset(cmd_mgr, 0, sizeof(*cmd_mgr)); +} diff --git a/drivers/net/wireless/eswin/ecrnx_cmds.h b/drivers/net/wireless/eswin/ecrnx_cmds.h new file mode 100644 index 000000000000..58533fc08d40 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_cmds.h @@ -0,0 +1,102 @@ +/** + ****************************************************************************** + * + * ecrnx_cmds.h + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef _ECRNX_CMDS_H_ +#define _ECRNX_CMDS_H_ + +#include <linux/spinlock.h> +#include <linux/completion.h> +#include "lmac_msg.h" + +#ifdef CONFIG_ECRNX_SDM +#define ECRNX_80211_CMD_TIMEOUT_MS (20 * 300) +#elif defined(CONFIG_ECRNX_FHOST) +#define ECRNX_80211_CMD_TIMEOUT_MS (10000) +#else +#define ECRNX_80211_CMD_TIMEOUT_MS (20 * 300) //300 +#endif + +#define ECRNX_CMD_FLAG_NONBLOCK BIT(0) +#define ECRNX_CMD_FLAG_REQ_CFM BIT(1) +#define ECRNX_CMD_FLAG_WAIT_PUSH BIT(2) +#define ECRNX_CMD_FLAG_WAIT_ACK BIT(3) +#define ECRNX_CMD_FLAG_WAIT_CFM BIT(4) +#define ECRNX_CMD_FLAG_DONE BIT(5) +/* ATM IPC design makes it possible to get the CFM before the ACK, + * otherwise this could have simply been a state enum */ +#define ECRNX_CMD_WAIT_COMPLETE(flags) \ + (!(flags & (ECRNX_CMD_FLAG_WAIT_ACK | ECRNX_CMD_FLAG_WAIT_CFM))) + +#define ECRNX_CMD_MAX_QUEUED 8 + +#ifdef CONFIG_ECRNX_FHOST +#include "ipc_fhost.h" +#define ecrnx_cmd_e2amsg ipc_fhost_msg +#define ecrnx_cmd_a2emsg ipc_fhost_msg +#define ECRNX_CMD_A2EMSG_LEN(m) (m->param_len) +#define ECRNX_CMD_E2AMSG_LEN_MAX IPC_FHOST_MSG_BUF_SIZE +struct ecrnx_term_stream; + +#else /* !CONFIG_ECRNX_FHOST*/ +#include "ipc_shared.h" +#define ecrnx_cmd_e2amsg ipc_e2a_msg +#define ecrnx_cmd_a2emsg lmac_msg +#define ECRNX_CMD_A2EMSG_LEN(m) (sizeof(struct lmac_msg) + m->param_len) +#define ECRNX_CMD_E2AMSG_LEN_MAX (IPC_E2A_MSG_PARAM_SIZE * 4) + +#endif /* CONFIG_ECRNX_FHOST*/ + +struct ecrnx_hw; +struct ecrnx_cmd; +typedef int (*msg_cb_fct)(struct ecrnx_hw *ecrnx_hw, struct ecrnx_cmd *cmd, + struct ecrnx_cmd_e2amsg *msg); + +enum ecrnx_cmd_mgr_state { + ECRNX_CMD_MGR_STATE_DEINIT, + ECRNX_CMD_MGR_STATE_INITED, + ECRNX_CMD_MGR_STATE_CRASHED, +}; + +struct ecrnx_cmd { + struct list_head list; + lmac_msg_id_t id; + lmac_msg_id_t reqid; + struct ecrnx_cmd_a2emsg *a2e_msg; + char *e2a_msg; + u32 tkn; + u16 flags; + + struct completion complete; + u32 result; + #ifdef CONFIG_ECRNX_FHOST + struct ecrnx_term_stream *stream; + #endif +}; + +struct ecrnx_cmd_mgr { + enum ecrnx_cmd_mgr_state state; + spinlock_t lock; + u32 next_tkn; + u32 queue_sz; + u32 max_queue_sz; + + struct list_head cmds; + + int (*queue)(struct ecrnx_cmd_mgr *, struct ecrnx_cmd *); + int (*llind)(struct ecrnx_cmd_mgr *, struct ecrnx_cmd *); + int (*msgind)(struct ecrnx_cmd_mgr *, struct ecrnx_cmd_e2amsg *, msg_cb_fct); + void (*print)(struct ecrnx_cmd_mgr *); + void (*drain)(struct ecrnx_cmd_mgr *); +}; + +void ecrnx_cmd_mgr_init(struct ecrnx_cmd_mgr *cmd_mgr); +void ecrnx_cmd_mgr_deinit(struct ecrnx_cmd_mgr *cmd_mgr); + +#endif /* _ECRNX_CMDS_H_ */ diff --git a/drivers/net/wireless/eswin/ecrnx_compat.h b/drivers/net/wireless/eswin/ecrnx_compat.h new file mode 100644 index 000000000000..beba2e7bc967 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_compat.h @@ -0,0 +1,525 @@ +/**
+ ******************************************************************************
+ *
+ * @file ecrnx_compat.h
+ *
+ * Ensure driver compilation for linux 4.4 to 5.9
+ *
+ * To avoid too many #if LINUX_VERSION_CODE if the code, when prototype change
+ * between different kernel version:
+ * - For external function, define a macro whose name is the function name with
+ * _compat suffix and prototype (actually the number of parameter) of the
+ * latest version. Then latest version this macro simply call the function
+ * and for older kernel version it call the function adapting the api.
+ * - For internal function (e.g. cfg80211_ops) do the same but the macro name
+ * doesn't need to have the _compat suffix when the function is not used
+ * directly by the driver
+ *
+ * Copyright (C) ESWIN 2020
+ *
+ ******************************************************************************
+ */
+#ifndef _ECRNX_COMPAT_H_
+#define _ECRNX_COMPAT_H_
+#include <linux/version.h>
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 8, 0)
+#error "Minimum kernel version supported is 3.8"
+#endif
+
+/******************************************************************************
+ * Generic
+ *****************************************************************************/
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0)
+#define __bf_shf(x) (__builtin_ffsll(x) - 1)
+#define FIELD_PREP(_mask, _val) \
+ (((typeof(_mask))(_val) << __bf_shf(_mask)) & (_mask))
+#else
+#include <linux/bitfield.h>
+#endif // 4.9
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(5,13,0)
+#define IEEE80211_HE_PHY_CAP3_RX_HE_MU_PPDU_FROM_NON_AP_STA IEEE80211_HE_PHY_CAP3_RX_PARTIAL_BW_SU_IN_20MHZ_MU
+#endif
+
+/******************************************************************************
+ * CFG80211
+ *****************************************************************************/
+
+ #if LINUX_VERSION_CODE > KERNEL_VERSION(5, 12, 0)
+#define regulatory_set_wiphy_regd_sync_rtnl regulatory_set_wiphy_regd_sync
+#endif
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(5, 11, 0)
+#define cfg80211_ch_switch_started_notify(dev, chandef, count) \
+ cfg80211_ch_switch_started_notify(dev, chandef, count, 1)
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 1, 0)
+#define WLAN_EXT_CAPA3_MULTI_BSSID_SUPPORT 0
+
+#define IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_242 0x00
+#define IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_484 0x40
+#define IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_996 0x80
+#define IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_2x996 0xc0
+#define IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_MASK 0xc0
+
+#define IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_0US 0x00
+#define IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_8US 0x40
+#define IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_16US 0x80
+#define IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_RESERVED 0xc0
+#define IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_MASK 0xc0
+
+#endif // 5.1
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 0, 0)
+#define cfg80211_notify_new_peer_candidate(dev, addr, ie, ie_len, sig_dbm, gfp) \
+ cfg80211_notify_new_peer_candidate(dev, addr, ie, ie_len, gfp)
+
+#define WLAN_EXT_CAPA10_TWT_REQUESTER_SUPPORT BIT(5)
+#define WLAN_EXT_CAPA10_TWT_RESPONDER_SUPPORT BIT(6)
+
+#endif // 5.0
+
+#define WLAN_EXT_CAPA5_QOS_MAP_SUPPORT BIT(0)
+
+
+struct ecrnx_element {
+ u8 id;
+ u8 datalen;
+ u8 data[];
+} __packed;
+
+
+#define for_each_ecrnx_element(_elem, _data, _datalen) \
+ for (_elem = (const struct ecrnx_element *)(_data); \
+ (const u8 *)(_data) + (_datalen) - (const u8 *)_elem >= \
+ (int)sizeof(*_elem) && \
+ (const u8 *)(_data) + (_datalen) - (const u8 *)_elem >= \
+ (int)sizeof(*_elem) + _elem->datalen; \
+ _elem = (const struct ecrnx_element *)(_elem->data + _elem->datalen))
+
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0)
+#define IEEE80211_RADIOTAP_HE 23
+#define IEEE80211_RADIOTAP_HE_MU 24
+#endif
+
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,19,0)
+struct ieee80211_radiotap_he {
+ __le16 data1, data2, data3, data4, data5, data6;
+};
+
+enum ieee80211_radiotap_he_bits {
+ IEEE80211_RADIOTAP_HE_DATA1_FORMAT_MASK = 3,
+ IEEE80211_RADIOTAP_HE_DATA1_FORMAT_SU = 0,
+ IEEE80211_RADIOTAP_HE_DATA1_FORMAT_EXT_SU = 1,
+ IEEE80211_RADIOTAP_HE_DATA1_FORMAT_MU = 2,
+ IEEE80211_RADIOTAP_HE_DATA1_FORMAT_TRIG = 3,
+
+ IEEE80211_RADIOTAP_HE_DATA1_BSS_COLOR_KNOWN = 0x0004,
+ IEEE80211_RADIOTAP_HE_DATA1_BEAM_CHANGE_KNOWN = 0x0008,
+ IEEE80211_RADIOTAP_HE_DATA1_UL_DL_KNOWN = 0x0010,
+ IEEE80211_RADIOTAP_HE_DATA1_DATA_MCS_KNOWN = 0x0020,
+ IEEE80211_RADIOTAP_HE_DATA1_DATA_DCM_KNOWN = 0x0040,
+ IEEE80211_RADIOTAP_HE_DATA1_CODING_KNOWN = 0x0080,
+ IEEE80211_RADIOTAP_HE_DATA1_LDPC_XSYMSEG_KNOWN = 0x0100,
+ IEEE80211_RADIOTAP_HE_DATA1_STBC_KNOWN = 0x0200,
+ IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE_KNOWN = 0x0400,
+ IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE2_KNOWN = 0x0800,
+ IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE3_KNOWN = 0x1000,
+ IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE4_KNOWN = 0x2000,
+ IEEE80211_RADIOTAP_HE_DATA1_BW_RU_ALLOC_KNOWN = 0x4000,
+ IEEE80211_RADIOTAP_HE_DATA1_DOPPLER_KNOWN = 0x8000,
+
+ IEEE80211_RADIOTAP_HE_DATA2_PRISEC_80_KNOWN = 0x0001,
+ IEEE80211_RADIOTAP_HE_DATA2_GI_KNOWN = 0x0002,
+ IEEE80211_RADIOTAP_HE_DATA2_NUM_LTF_SYMS_KNOWN = 0x0004,
+ IEEE80211_RADIOTAP_HE_DATA2_PRE_FEC_PAD_KNOWN = 0x0008,
+ IEEE80211_RADIOTAP_HE_DATA2_TXBF_KNOWN = 0x0010,
+ IEEE80211_RADIOTAP_HE_DATA2_PE_DISAMBIG_KNOWN = 0x0020,
+ IEEE80211_RADIOTAP_HE_DATA2_TXOP_KNOWN = 0x0040,
+ IEEE80211_RADIOTAP_HE_DATA2_MIDAMBLE_KNOWN = 0x0080,
+ IEEE80211_RADIOTAP_HE_DATA2_RU_OFFSET = 0x3f00,
+ IEEE80211_RADIOTAP_HE_DATA2_RU_OFFSET_KNOWN = 0x4000,
+ IEEE80211_RADIOTAP_HE_DATA2_PRISEC_80_SEC = 0x8000,
+
+ IEEE80211_RADIOTAP_HE_DATA3_BSS_COLOR = 0x003f,
+ IEEE80211_RADIOTAP_HE_DATA3_BEAM_CHANGE = 0x0040,
+ IEEE80211_RADIOTAP_HE_DATA3_UL_DL = 0x0080,
+ IEEE80211_RADIOTAP_HE_DATA3_DATA_MCS = 0x0f00,
+ IEEE80211_RADIOTAP_HE_DATA3_DATA_DCM = 0x1000,
+ IEEE80211_RADIOTAP_HE_DATA3_CODING = 0x2000,
+ IEEE80211_RADIOTAP_HE_DATA3_LDPC_XSYMSEG = 0x4000,
+ IEEE80211_RADIOTAP_HE_DATA3_STBC = 0x8000,
+
+ IEEE80211_RADIOTAP_HE_DATA4_SU_MU_SPTL_REUSE = 0x000f,
+ IEEE80211_RADIOTAP_HE_DATA4_MU_STA_ID = 0x7ff0,
+ IEEE80211_RADIOTAP_HE_DATA4_TB_SPTL_REUSE1 = 0x000f,
+ IEEE80211_RADIOTAP_HE_DATA4_TB_SPTL_REUSE2 = 0x00f0,
+ IEEE80211_RADIOTAP_HE_DATA4_TB_SPTL_REUSE3 = 0x0f00,
+ IEEE80211_RADIOTAP_HE_DATA4_TB_SPTL_REUSE4 = 0xf000,
+
+ IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC = 0x000f,
+ IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_20MHZ = 0,
+ IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_40MHZ = 1,
+ IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_80MHZ = 2,
+ IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_160MHZ = 3,
+ IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_26T = 4,
+ IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_52T = 5,
+ IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_106T = 6,
+ IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_242T = 7,
+ IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_484T = 8,
+ IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_996T = 9,
+ IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_2x996T = 10,
+
+ IEEE80211_RADIOTAP_HE_DATA5_GI = 0x0030,
+ IEEE80211_RADIOTAP_HE_DATA5_GI_0_8 = 0,
+ IEEE80211_RADIOTAP_HE_DATA5_GI_1_6 = 1,
+ IEEE80211_RADIOTAP_HE_DATA5_GI_3_2 = 2,
+
+ IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE = 0x00c0,
+ IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_UNKNOWN = 0,
+ IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_1X = 1,
+ IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_2X = 2,
+ IEEE80211_RADIOTAP_HE_DATA5_LTF_SIZE_4X = 3,
+ IEEE80211_RADIOTAP_HE_DATA5_NUM_LTF_SYMS = 0x0700,
+ IEEE80211_RADIOTAP_HE_DATA5_PRE_FEC_PAD = 0x3000,
+ IEEE80211_RADIOTAP_HE_DATA5_TXBF = 0x4000,
+ IEEE80211_RADIOTAP_HE_DATA5_PE_DISAMBIG = 0x8000,
+
+ IEEE80211_RADIOTAP_HE_DATA6_NSTS = 0x000f,
+ IEEE80211_RADIOTAP_HE_DATA6_DOPPLER = 0x0010,
+ IEEE80211_RADIOTAP_HE_DATA6_TXOP = 0x7f00,
+ IEEE80211_RADIOTAP_HE_DATA6_MIDAMBLE_PDCTY = 0x8000,
+ };
+
+struct ieee80211_radiotap_he_mu {
+ __le16 flags1, flags2;
+ u8 ru_ch1[4];
+ u8 ru_ch2[4];
+};
+
+enum ieee80211_radiotap_he_mu_bits {
+ IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_MCS = 0x000f,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_MCS_KNOWN = 0x0010,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_DCM = 0x0020,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_DCM_KNOWN = 0x0040,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH2_CTR_26T_RU_KNOWN = 0x0080,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH1_RU_KNOWN = 0x0100,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH2_RU_KNOWN = 0x0200,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH1_CTR_26T_RU_KNOWN = 0x1000,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH1_CTR_26T_RU = 0x2000,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_COMP_KNOWN = 0x4000,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_SYMS_USERS_KNOWN = 0x8000,
+
+ IEEE80211_RADIOTAP_HE_MU_FLAGS2_BW_FROM_SIG_A_BW = 0x0003,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS2_BW_FROM_SIG_A_BW_20MHZ = 0x0000,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS2_BW_FROM_SIG_A_BW_40MHZ = 0x0001,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS2_BW_FROM_SIG_A_BW_80MHZ = 0x0002,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS2_BW_FROM_SIG_A_BW_160MHZ = 0x0003,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS2_BW_FROM_SIG_A_BW_KNOWN = 0x0004,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS2_SIG_B_COMP = 0x0008,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS2_SIG_B_SYMS_USERS = 0x00f0,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS2_PUNC_FROM_SIG_A_BW = 0x0300,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS2_PUNC_FROM_SIG_A_BW_KNOWN= 0x0400,
+ IEEE80211_RADIOTAP_HE_MU_FLAGS2_CH2_CTR_26T_RU = 0x0800,
+};
+
+enum {
+ IEEE80211_HE_MCS_SUPPORT_0_7 = 0,
+ IEEE80211_HE_MCS_SUPPORT_0_9 = 1,
+ IEEE80211_HE_MCS_SUPPORT_0_11 = 2,
+ IEEE80211_HE_MCS_NOT_SUPPORTED = 3,
+};
+#endif // 4.19
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 17, 0)
+#define cfg80211_probe_status(ndev, addr, cookie, ack, ack_pwr, pwr_valid, gfp) \
+ cfg80211_probe_status(ndev, addr, cookie, ack, gfp)
+#endif // 4.17
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0)
+#define cfg80211_disconnected(dev, reason, ie, len, local, gfp) \
+ cfg80211_disconnected(dev, reason, ie, len, gfp)
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0)
+#define ieee80211_chandef_to_operating_class(chan_def, op_class) 0
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0)
+#define ecrnx_cfg80211_add_iface(wiphy, name, name_assign_type, type, params) \
+ ecrnx_cfg80211_add_iface(wiphy, name, name_assign_type, type, u32 *flags, params)
+
+#define ecrnx_cfg80211_change_iface(wiphy, dev, type, params) \
+ ecrnx_cfg80211_change_iface(wiphy, dev, type, u32 *flags, params)
+
+#define CCFS0(vht) vht->center_freq_seg1_idx
+#define CCFS1(vht) vht->center_freq_seg2_idx
+
+#if 0
+#define nla_parse(tb, maxtype, head, len, policy, extack) \
+ nla_parse(tb, maxtype, head, len, policy)
+#endif
+
+struct cfg80211_roam_info {
+ struct ieee80211_channel *channel;
+ struct cfg80211_bss *bss;
+ const u8 *bssid;
+ const u8 *req_ie;
+ size_t req_ie_len;
+ const u8 *resp_ie;
+ size_t resp_ie_len;
+};
+
+#define cfg80211_roamed(_dev, _info, _gfp) \
+ cfg80211_roamed(_dev, (_info)->channel, (_info)->bssid, (_info)->req_ie, \
+ (_info)->req_ie_len, (_info)->resp_ie, (_info)->resp_ie_len, _gfp)
+
+#else // 4.12
+
+#define CCFS0(vht) vht->center_freq_seg0_idx
+#define CCFS1(vht) vht->center_freq_seg1_idx
+#endif // 4.12
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0)
+#define ecrnx_cfg80211_cqm_rssi_notify(dev, event, level, gfp) \
+ cfg80211_cqm_rssi_notify(dev, event, gfp)
+#else
+#define ecrnx_cfg80211_cqm_rssi_notify(dev, event, level, gfp) \
+ cfg80211_cqm_rssi_notify(dev, event, level, gfp)
+#endif // 4.11
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0)
+#define ieee80211_amsdu_to_8023s(skb, list, addr, iftype, extra_headroom, check_da, check_sa) \
+ ieee80211_amsdu_to_8023s(skb, list, addr, iftype, extra_headroom, false)
+#endif // 4.9
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0)
+#define NUM_NL80211_BANDS IEEE80211_NUM_BANDS
+#endif // 4.7
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0)
+#define SURVEY_INFO_TIME SURVEY_INFO_CHANNEL_TIME
+#define SURVEY_INFO_TIME_BUSY SURVEY_INFO_CHANNEL_TIME_BUSY
+#define SURVEY_INFO_TIME_EXT_BUSY SURVEY_INFO_CHANNEL_TIME_EXT_BUSY
+#define SURVEY_INFO_TIME_RX SURVEY_INFO_CHANNEL_TIME_RX
+#define SURVEY_INFO_TIME_TX SURVEY_INFO_CHANNEL_TIME_TX
+
+#define SURVEY_TIME(s) s->channel_time
+#define SURVEY_TIME_BUSY(s) s->channel_time_busy
+#else
+#define SURVEY_TIME(s) s->time
+#define SURVEY_TIME_BUSY(s) s->time_busy
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0)
+#define cfg80211_ch_switch_started_notify(dev, chandef, count)
+
+#define WLAN_BSS_COEX_INFORMATION_REQUEST BIT(0)
+#define WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING BIT(2)
+#define WLAN_EXT_CAPA4_TDLS_BUFFER_STA BIT(4)
+#define WLAN_EXT_CAPA4_TDLS_PEER_PSM BIT(5)
+#define WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH BIT(6)
+#define WLAN_EXT_CAPA5_TDLS_CH_SW_PROHIBITED BIT(7)
+#define NL80211_FEATURE_TDLS_CHANNEL_SWITCH 0
+
+#define STA_TDLS_INITIATOR(sta) 0
+
+#define REGULATORY_IGNORE_STALE_KICKOFF 0
+#else
+#define STA_TDLS_INITIATOR(sta) sta->tdls_initiator
+#endif
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0)) && (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0))
+#define cfg80211_rx_mgmt(wdev, freq, rssi, buf, len, flags) \
+ cfg80211_rx_mgmt(wdev, freq, rssi, buf, len, flags, GFP_ATOMIC)
+#elif LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)
+#define cfg80211_rx_mgmt(wdev, freq, rssi, buf, len, flags) \
+ cfg80211_rx_mgmt(wdev, freq, rssi, buf, len, GFP_ATOMIC)
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0)
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0)
+#define rwnx_cfg80211_tdls_mgmt(wiphy, dev, peer, act, tok, status, peer_capability, initiator, buf, len) \
+ rwnx_cfg80211_tdls_mgmt(wiphy, dev, peer, act, tok, status, peer_capability, buf, len)
+#else
+#define rwnx_cfg80211_tdls_mgmt(wiphy, dev, peer, act, tok, status, peer_capability, initiator, buf, len) \
+ rwnx_cfg80211_tdls_mgmt(wiphy, dev, peer, act, tok, status, buf, len)
+#endif
+
+#include <linux/types.h>
+
+struct ieee80211_wmm_ac_param {
+ u8 aci_aifsn; /* AIFSN, ACM, ACI */
+ u8 cw; /* ECWmin, ECWmax (CW = 2^ECW - 1) */
+ __le16 txop_limit;
+} __packed;
+
+struct ieee80211_wmm_param_ie {
+ u8 element_id; /* Element ID: 221 (0xdd); */
+ u8 len; /* Length: 24 */
+ /* required fields for WMM version 1 */
+ u8 oui[3]; /* 00:50:f2 */
+ u8 oui_type; /* 2 */
+ u8 oui_subtype; /* 1 */
+ u8 version; /* 1 for WMM version 1.0 */
+ u8 qos_info; /* AP/STA specific QoS info */
+ u8 reserved; /* 0 */
+ /* AC_BE, AC_BK, AC_VI, AC_VO */
+ struct ieee80211_wmm_ac_param ac[4];
+} __packed;
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
+/**
+ * struct cfg80211_update_ft_ies_params - FT IE Information
+ *
+ * This structure provides information needed to update the fast transition IE
+ *
+ * @md: The Mobility Domain ID, 2 Octet value
+ * @ie: Fast Transition IEs
+ * @ie_len: Length of ft_ie in octets
+ */
+struct cfg80211_update_ft_ies_params {
+ u16 md;
+ const u8 *ie;
+ size_t ie_len;
+};
+#endif
+
+/******************************************************************************
+ * MAC80211
+ *****************************************************************************/
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 0)
+#define ecrnx_ops_cancel_remain_on_channel(hw, vif) \
+ ecrnx_ops_cancel_remain_on_channel(hw)
+#endif // 5.3
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 18, 0)
+#define ecrnx_ops_mgd_prepare_tx(hw, vif, duration) \
+ ecrnx_ops_mgd_prepare_tx(hw, vif)
+#endif // 4.18
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0)
+
+#define RX_ENC_HT(s) s->flag |= RX_FLAG_HT
+#define RX_ENC_HT_GF(s) s->flag |= (RX_FLAG_HT | RX_FLAG_HT_GF)
+#define RX_ENC_VHT(s) s->flag |= RX_FLAG_HT
+#define RX_ENC_HE(s) s->flag |= RX_FLAG_HT
+#define RX_ENC_FLAG_SHORT_GI(s) s->flag |= RX_FLAG_SHORT_GI
+#define RX_ENC_FLAG_SHORT_PRE(s) s->flag |= RX_FLAG_SHORTPRE
+#define RX_ENC_FLAG_LDPC(s) s->flag |= RX_FLAG_LDPC
+#define RX_BW_40MHZ(s) s->flag |= RX_FLAG_40MHZ
+#define RX_BW_80MHZ(s) s->vht_flag |= RX_VHT_FLAG_80MHZ
+#define RX_BW_160MHZ(s) s->vht_flag |= RX_VHT_FLAG_160MHZ
+#define RX_NSS(s) s->vht_nss
+
+#else
+#define RX_ENC_HT(s) s->encoding = RX_ENC_HT
+#define RX_ENC_HT_GF(s) { s->encoding = RX_ENC_HT; \
+ s->enc_flags |= RX_ENC_FLAG_HT_GF; }
+#define RX_ENC_VHT(s) s->encoding = RX_ENC_VHT
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0)
+#define RX_ENC_HE(s) s->encoding = RX_ENC_VHT
+#else
+#define RX_ENC_HE(s) s->encoding = RX_ENC_HE
+#endif
+#define RX_ENC_FLAG_SHORT_GI(s) s->enc_flags |= RX_ENC_FLAG_SHORT_GI
+#define RX_ENC_FLAG_SHORT_PRE(s) s->enc_flags |= RX_ENC_FLAG_SHORTPRE
+#define RX_ENC_FLAG_LDPC(s) s->enc_flags |= RX_ENC_FLAG_LDPC
+#define RX_BW_40MHZ(s) s->bw = RATE_INFO_BW_40
+#define RX_BW_80MHZ(s) s->bw = RATE_INFO_BW_80
+#define RX_BW_160MHZ(s) s->bw = RATE_INFO_BW_160
+#define RX_NSS(s) s->nss
+
+#endif // 4.12
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0)
+#define ecrnx_ieee80211_cqm_rssi_notify(vif, event, level, gfp) \
+ ieee80211_cqm_rssi_notify(vif, event, gfp)
+#else
+#define ecrnx_ieee80211_cqm_rssi_notify(vif, event, level, gfp) \
+ ieee80211_cqm_rssi_notify(vif, event, level, gfp)
+#endif // 4.11
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0)
+#define RX_FLAG_MIC_STRIPPED 0
+#endif // 4.7
+
+#ifndef CONFIG_VENDOR_ECRNX_AMSDUS_TX
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0))
+#define ecrnx_ops_ampdu_action(hw, vif, params) \
+ ecrnx_ops_ampdu_action(hw, vif, enum ieee80211_ampdu_mlme_action action, \
+ struct ieee80211_sta *sta, u16 tid, u16 *ssn, u8 buf_size, \
+ bool amsdu)
+#endif // 4.6
+#endif /* CONFIG_VENDOR_ECRNX_AMSDUS_TX */
+
+/******************************************************************************
+ * NET
+ *****************************************************************************/
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0)
+#define rwnx_select_queue(dev, skb, sb_dev) \
+ rwnx_select_queue(dev, skb)
+#elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0)
+#define ecrnx_select_queue(dev, skb, sb_dev) \
+ ecrnx_select_queue(dev, skb, void *accel_priv, select_queue_fallback_t fallback)
+#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 0, 0)
+#define ecrnx_select_queue(dev, skb, sb_dev) \
+ ecrnx_select_queue(dev, skb, sb_dev, select_queue_fallback_t fallback)
+#endif //3.13
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)) && !(defined CONFIG_VENDOR_ECRNX)
+#define sk_pacing_shift_update(sk, shift)
+#endif // 4.16
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0)
+#define alloc_netdev_mqs(size, name, assign, setup, txqs, rxqs) \
+ alloc_netdev_mqs(size, name, setup, txqs, rxqs)
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0)
+#define NET_NAME_UNKNOWN 0
+#endif
+
+/******************************************************************************
+ * TRACE
+ *****************************************************************************/
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0)
+#define trace_print_symbols_seq ftrace_print_symbols_seq
+#endif // 4.2
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0)
+#define trace_seq_buffer_ptr(p) p->buffer + p->len
+#endif
+
+/******************************************************************************
+ * TIME
+ *****************************************************************************/
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0)
+#define time64_to_tm(t, o, tm) time_to_tm((time_t)t, o, tm)
+#endif // 4.8
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0)
+#define ktime_get_real_seconds get_seconds
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0)
+typedef __s64 time64_t;
+#endif
+
+/******************************************************************************
+ * timer
+ *****************************************************************************/
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0)
+#define from_timer(var, callback_timer, timer_fieldname) \
+ container_of(callback_timer, typeof(*var), timer_fieldname)
+
+#define timer_setup(timer, callback, flags) \
+ __setup_timer(timer, (void (*)(unsigned long))callback, (unsigned long)timer, flags)
+#endif // 4.14
+
+#endif /* _ECRNX_COMPAT_H_ */
diff --git a/drivers/net/wireless/eswin/ecrnx_debug.c b/drivers/net/wireless/eswin/ecrnx_debug.c new file mode 100644 index 000000000000..d3dcd6183d5c --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_debug.c @@ -0,0 +1,46 @@ +/** + **************************************************************************************** + * + * @file ecrnx_debug.c + * + * @brief ecrnx driver debug functions; + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ +#include <stdarg.h> +#include <linux/init.h> +#include "ecrnx_defs.h" +#include "eswin_utils.h" + +#ifdef CONFIG_ECRNX_DBG_LEVEL +int ecrnx_dbg_level = CONFIG_ECRNX_DBG_LEVEL; //defined in the 6600u_feature file +#else +int ecrnx_dbg_level = DRV_DBG_TYPE_NONE; +#endif + +LOG_CTL_ST log_ctl={ + .level = 2, + .dir = 0, +}; + +#ifndef CONFIG_ECRNX_DEBUGFS_CUSTOM +int ecrnx_fw_log_level_set(u32 level, u32 dir) +{ + uint32_t dbg_info[3] = {0}; + + dbg_info[0] = 0x01; //SLAVE_LOG_LEVEL + dbg_info[1] = level; + dbg_info[2] = dir; + + ECRNX_PRINT("%s: fstype:%d, level:%d, dir:%d \n", __func__, dbg_info[0], dbg_info[1], dbg_info[2]); + ECRNX_PRINT("info_len:%d \n", sizeof(dbg_info)); + return host_send(dbg_info, sizeof(dbg_info), TX_FLAG_MSG_DEBUGFS_IE); +} + +#endif + +// #endif + + diff --git a/drivers/net/wireless/eswin/ecrnx_debug.h b/drivers/net/wireless/eswin/ecrnx_debug.h new file mode 100644 index 000000000000..b322487851c0 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_debug.h @@ -0,0 +1,106 @@ +/** + **************************************************************************************** + * + * @file ecrnx_debug.h + * + * @brief ecrnx driver debug structure declarations + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ + +#ifndef ECRNX_DEBUG_H_ +#define ECRNX_DEBUG_H_ + +#ifdef CONFIG_ECRNX_SOFTMAC +#define FW_STR "lmac" +#elif defined CONFIG_ECRNX_FULLMAC +#define FW_STR "fmac" +#endif +#if 0 +#ifdef CONFIG_ECRNX_DBG +/* #define ECRNX_DBG(format, arg...) pr_warn(format, ## arg) */ +#define ECRNX_DBG printk +#else +#define ECRNX_DBG(a...) do {} while (0) +#endif +#endif + +#ifdef CONFIG_ECRNX_DBG + +#define ECRNX_FN_ENTRY_STR "%s() enter, line:%d\n", __func__, __LINE__ +#define DBG_PREFIX "[ecrnx] " +#define DBG_PREFIX_IW_CFM "[ecrnx] iwpriv cfm:" +#define DBG_PREFIX_PAT "[ecrnx] pattern error:" +#define DBG_PREFIX_CRC_CHECK "[ecrnx] crc check:" +#define DBG_PREFIX_SDIO_RX "[ecrnx] sdio rx:" +#define DBG_PREFIX_SDIO_TX "[ecrnx] sdio tx:" + +/* driver log level*/ +enum ECRNX_DRV_DBG_TYEP{ + DRV_DBG_TYPE_NONE, + DRV_DBG_TYPE_ALWAYS, + DRV_DBG_TYPE_ERR, + DRV_DBG_TYPE_WARNING, + DRV_DBG_TYPE_INFO, + DRV_DBG_TYPE_DEBUG, + DRV_DBG_TYPE_MAX, +}; + +#define ECRNX_PRINT(fmt, arg...) \ + do {\ + if (DRV_DBG_TYPE_ALWAYS <= ecrnx_dbg_level) {\ + printk(DBG_PREFIX fmt, ##arg);\ + } \ + } while (0) + +#define ECRNX_ERR(fmt, arg...) \ + do {\ + if (DRV_DBG_TYPE_ERR <= ecrnx_dbg_level) {\ + printk(DBG_PREFIX " ERROR " fmt, ##arg);\ + } \ + } while (0) + +#define ECRNX_WARN(fmt, arg...) \ + do {\ + if (DRV_DBG_TYPE_WARNING <= ecrnx_dbg_level) {\ + printk(DBG_PREFIX " WARN " fmt, ##arg);\ + } \ + } while (0) + +#define ECRNX_INFO(fmt, arg...) \ + do {\ + if (DRV_DBG_TYPE_INFO <= ecrnx_dbg_level) {\ + printk(DBG_PREFIX fmt, ##arg);\ + } \ + } while (0) + +#define ECRNX_DBG(fmt, arg...) \ + do {\ + if (DRV_DBG_TYPE_DEBUG <= ecrnx_dbg_level) {\ + printk(DBG_PREFIX fmt, ##arg);\ + } \ + } while (0) + +#else +#define ECRNX_PRINT(...) +#define ECRNX_ERR(...) +#define ECRNX_WARN(...) +#define ECRNX_INFO(...) +#define ECRNX_DBG(...) +#endif + +typedef struct { + u32 level; + u32 dir; +} LOG_CTL_ST; + +extern int ecrnx_dbg_level; +extern LOG_CTL_ST log_ctl; + +#ifndef CONFIG_ECRNX_DEBUGFS_CUSTOM +int ecrnx_fw_log_level_set(u32 level, u32 dir); +#endif + +#endif /* ECRNX_DEBUG_H_ */ diff --git a/drivers/net/wireless/eswin/ecrnx_debugfs.c b/drivers/net/wireless/eswin/ecrnx_debugfs.c new file mode 100644 index 000000000000..a7b4232ed6b7 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_debugfs.c @@ -0,0 +1,2576 @@ +/** + ****************************************************************************** + * + * @file ecrnx_debugfs.c + * + * @brief Definition of debugfs entries + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + + +#include <linux/kernel.h> +#include <linux/kmod.h> +#include <linux/debugfs.h> +#include <linux/string.h> +#include <linux/sort.h> + +#include "ecrnx_debugfs.h" +#include "ecrnx_msg_tx.h" +#include "ecrnx_radar.h" +#include "ecrnx_tx.h" + +#define CONFIG_ECRNX_DBGFS_FW_TRACE 0 + +#ifdef CONFIG_ECRNX_SOFTMAC +static ssize_t ecrnx_dbgfs_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char *buf; + int per; + int ret; + int i, skipped; + ssize_t read; + int bufsz = (10 + NX_TX_PAYLOAD_MAX + NX_TXQ_CNT + IEEE80211_MAX_AMPDU_BUF) * 50; + + buf = kmalloc(bufsz, GFP_ATOMIC); + if (buf == NULL) + return 0; + + if (priv->stats.agg_done) + per = DIV_ROUND_UP((priv->stats.agg_retries + priv->stats.agg_died) * + 100, priv->stats.agg_done); + else + per = 0; + + ret = scnprintf(buf, min_t(size_t, bufsz - 1, count), + "agg_done %10d\n" + "agg_retries %10d\n" + "agg_retries_last %10d\n" + "agg_died %10d\n" + "ampdu_all_ko %10d\n" + "agg_PER (%%) %10d\n" + "queues_stops %10d\n\n", + priv->stats.agg_done, + priv->stats.agg_retries, + priv->stats.agg_retries_last, + priv->stats.agg_died, + priv->stats.ampdu_all_ko, + per, + priv->stats.queues_stops); + + ret += scnprintf(&buf[ret], min_t(size_t, bufsz - 1, count - ret), + "TXQs CFM balances "); + for (i = 0; i < NX_TXQ_CNT; i++) + ret += scnprintf(&buf[ret], min_t(size_t, bufsz - 1, count - ret), + " [%1d]:%3d", i, + priv->stats.cfm_balance[i]); + +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + ret += scnprintf(&buf[ret], min_t(size_t, bufsz - 1, count - ret), + "\n\nAMSDU[len] done failed(%%)\n"); + for (i = skipped = 0; i < NX_TX_PAYLOAD_MAX; i++) { + if (priv->stats.amsdus[i].done) { + per = DIV_ROUND_UP((priv->stats.amsdus[i].failed) * + 100, priv->stats.amsdus[i].done); + } else { + per = 0; + skipped = 1; + continue; + } + if (skipped) { + ret += scnprintf(&buf[ret], min_t(size_t, bufsz - 1, count - ret), + " * * * %10d %10d\n", 0, 0); + skipped = 0; + } + + ret += scnprintf(&buf[ret], min_t(size_t, bufsz - 1, count - ret), + " [%1d] %10d %10d\n", i + 1, + priv->stats.amsdus[i].done, per); + } + if (skipped) + ret += scnprintf(&buf[ret], min_t(size_t, bufsz - 1, count - ret), + " * * * %10d %10d\n", 0, 0); +#endif + + ret += scnprintf(&buf[ret], min_t(size_t, bufsz - ret - 1, count - ret), + "\nIn-AMPDU TX failures(%%) RX counts\n"); + for (i = skipped = 0; i < IEEE80211_MAX_AMPDU_BUF; i++) { + int failed; + + if (priv->stats.in_ampdu[i].done) { + failed = DIV_ROUND_UP(priv->stats.in_ampdu[i].failed * + 100, priv->stats.in_ampdu[i].done); + } else { + if (!priv->stats.rx_in_ampdu[i].cnt) { + skipped = 1; + continue; + } + failed = 0; + } + if (skipped) { + ret += scnprintf(&buf[ret], + min_t(size_t, bufsz - ret - 1, count - ret), + " * * * %10d %10d\n", 0, 0); + skipped = 0; + } + ret += scnprintf(&buf[ret], + min_t(size_t, bufsz - ret - 1, count - ret), + " mpdu#%2d %10d %10d\n", i, failed, + priv->stats.rx_in_ampdu[i].cnt); + + } + if (skipped) + ret += scnprintf(&buf[ret], + min_t(size_t, bufsz - ret - 1, count - ret), + " * * * %10d %10d\n", 0, 0); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + kfree(buf); + + return read; +} + +#else + +static ssize_t ecrnx_dbgfs_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char *buf; + int ret; + int i, skipped; + ssize_t read; + int bufsz = (NX_TXQ_CNT) * 20 + (ARRAY_SIZE(priv->stats.amsdus_rx) + 1) * 40 + + (ARRAY_SIZE(priv->stats.ampdus_tx) * 30); + + if (*ppos) + return 0; + + buf = kmalloc(bufsz, GFP_ATOMIC); + if (buf == NULL) + return 0; + + ret = scnprintf(buf, bufsz, "TXQs CFM balances "); + for (i = 0; i < NX_TXQ_CNT; i++) + ret += scnprintf(&buf[ret], bufsz - ret, + " [%1d]:%3d", i, + priv->stats.cfm_balance[i]); + + ret += scnprintf(&buf[ret], bufsz - ret, "\n"); + +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + int per = 0; + ret += scnprintf(&buf[ret], bufsz - ret, + "\nAMSDU[len] done failed received\n"); + for (i = skipped = 0; i < NX_TX_PAYLOAD_MAX; i++) { + if (priv->stats.amsdus[i].done) { + per = DIV_ROUND_UP((priv->stats.amsdus[i].failed) * + 100, priv->stats.amsdus[i].done); + } else if (priv->stats.amsdus_rx[i]) { + per = 0; + } else { + per = 0; + skipped = 1; + continue; + } + if (skipped) { + ret += scnprintf(&buf[ret], bufsz - ret, " ...\n"); + skipped = 0; + } + + ret += scnprintf(&buf[ret], bufsz - ret, + " [%2d] %10d %8d(%3d%%) %10d\n", i ? i + 1 : i, + priv->stats.amsdus[i].done, + priv->stats.amsdus[i].failed, per, + priv->stats.amsdus_rx[i]); + } + + for (; i < ARRAY_SIZE(priv->stats.amsdus_rx); i++) { + if (!priv->stats.amsdus_rx[i]) { + skipped = 1; + continue; + } + if (skipped) { + ret += scnprintf(&buf[ret], bufsz - ret, " ...\n"); + skipped = 0; + } + + ret += scnprintf(&buf[ret], bufsz - ret, + " [%2d] %10d\n", + i + 1, priv->stats.amsdus_rx[i]); + } +#else + ret += scnprintf(&buf[ret], bufsz - ret, + "\nAMSDU[len] received\n"); + for (i = skipped = 0; i < ARRAY_SIZE(priv->stats.amsdus_rx); i++) { + if (!priv->stats.amsdus_rx[i]) { + skipped = 1; + continue; + } + if (skipped) { + ret += scnprintf(&buf[ret], bufsz - ret, + " ...\n"); + skipped = 0; + } + + ret += scnprintf(&buf[ret], bufsz - ret, + " [%2d] %10d\n", + i + 1, priv->stats.amsdus_rx[i]); + } + +#endif /* CONFIG_ECRNX_SPLIT_TX_BUF */ + + ret += scnprintf(&buf[ret], bufsz - ret, + "\nAMPDU[len] done received\n"); + for (i = skipped = 0; i < ARRAY_SIZE(priv->stats.ampdus_tx); i++) { + if (!priv->stats.ampdus_tx[i] && !priv->stats.ampdus_rx[i]) { + skipped = 1; + continue; + } + if (skipped) { + ret += scnprintf(&buf[ret], bufsz - ret, + " ...\n"); + skipped = 0; + } + + ret += scnprintf(&buf[ret], bufsz - ret, + " [%2d] %9d %9d\n", i ? i + 1 : i, + priv->stats.ampdus_tx[i], priv->stats.ampdus_rx[i]); + } + + ret += scnprintf(&buf[ret], bufsz - ret, + "#mpdu missed %9d\n", + priv->stats.ampdus_rx_miss); + + ret += scnprintf(&buf[ret], bufsz - ret, + "\nmsg_tx:%d,%d; data_tx:%d,%d\n", + priv->msg_tx, priv->msg_tx_done, priv->data_tx, priv->data_tx_done); + ret += scnprintf(&buf[ret], bufsz - ret, + "usb_rx:%d, data_rx:%d, msg_rx:%d\n", + priv->usb_rx, priv->data_rx, priv->msg_rx); + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + kfree(buf); + + return read; +} +#endif /* CONFIG_ECRNX_SOFTMAC */ + +static ssize_t ecrnx_dbgfs_stats_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + + /* Prevent from interrupt preemption as these statistics are updated under + * interrupt */ + spin_lock_bh(&priv->tx_lock); + + memset(&priv->stats, 0, sizeof(priv->stats)); + + spin_unlock_bh(&priv->tx_lock); + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(stats); + +#define TXQ_STA_PREF "tid|" +#define TXQ_STA_PREF_FMT "%3d|" + +#ifdef CONFIG_ECRNX_FULLMAC +#define TXQ_VIF_PREF "type|" +#define TXQ_VIF_PREF_FMT "%4s|" +#else +#define TXQ_VIF_PREF "AC|" +#define TXQ_VIF_PREF_FMT "%2s|" +#endif /* CONFIG_ECRNX_FULLMAC */ + +#define TXQ_HDR "idx| status|credit|ready|retry|pushed" +#define TXQ_HDR_FMT "%3d|%s%s%s%s%s%s%s%s|%6d|%5d|%5d|%6d" + +#ifdef CONFIG_ECRNX_AMSDUS_TX +#ifdef CONFIG_ECRNX_FULLMAC +#define TXQ_HDR_SUFF "|amsdu" +#define TXQ_HDR_SUFF_FMT "|%5d" +#else +#define TXQ_HDR_SUFF "|amsdu-ht|amdsu-vht" +#define TXQ_HDR_SUFF_FMT "|%8d|%9d" +#endif /* CONFIG_ECRNX_FULLMAC */ +#else +#define TXQ_HDR_SUFF "" +#define TXQ_HDR_SUF_FMT "" +#endif /* CONFIG_ECRNX_AMSDUS_TX */ + +#define TXQ_HDR_MAX_LEN (sizeof(TXQ_STA_PREF) + sizeof(TXQ_HDR) + sizeof(TXQ_HDR_SUFF) + 1) + +#ifdef CONFIG_ECRNX_FULLMAC +#define PS_HDR "Legacy PS: ready=%d, sp=%d / UAPSD: ready=%d, sp=%d" +#define PS_HDR_LEGACY "Legacy PS: ready=%d, sp=%d" +#define PS_HDR_UAPSD "UAPSD: ready=%d, sp=%d" +#define PS_HDR_MAX_LEN sizeof("Legacy PS: ready=xxx, sp=xxx / UAPSD: ready=xxx, sp=xxx\n") +#else +#define PS_HDR "" +#define PS_HDR_MAX_LEN 0 +#endif /* CONFIG_ECRNX_FULLMAC */ + +#define STA_HDR "** STA %d (%pM)\n" +#define STA_HDR_MAX_LEN sizeof("- STA xx (xx:xx:xx:xx:xx:xx)\n") + PS_HDR_MAX_LEN + +#ifdef CONFIG_ECRNX_FULLMAC +#define VIF_HDR "* VIF [%d] %s\n" +#define VIF_HDR_MAX_LEN sizeof(VIF_HDR) + IFNAMSIZ +#else +#define VIF_HDR "* VIF [%d]\n" +#define VIF_HDR_MAX_LEN sizeof(VIF_HDR) +#endif + + +#ifdef CONFIG_ECRNX_AMSDUS_TX + +#ifdef CONFIG_ECRNX_FULLMAC +#define VIF_SEP "---------------------------------------\n" +#else +#define VIF_SEP "----------------------------------------------------\n" +#endif /* CONFIG_ECRNX_FULLMAC */ + +#else /* ! CONFIG_ECRNX_AMSDUS_TX */ +#define VIF_SEP "---------------------------------\n" +#endif /* CONFIG_ECRNX_AMSDUS_TX*/ + +#define VIF_SEP_LEN sizeof(VIF_SEP) + +#define CAPTION "status: L=in hwq list, F=stop full, P=stop sta PS, V=stop vif PS,\ + C=stop channel, S=stop CSA, M=stop MU, N=Ndev queue stopped" +#define CAPTION_LEN sizeof(CAPTION) + +#define STA_TXQ 0 +#define VIF_TXQ 1 + +static int ecrnx_dbgfs_txq(char *buf, size_t size, struct ecrnx_txq *txq, int type, int tid, char *name) +{ + int res, idx = 0; + int i, pushed = 0; + + if (type == STA_TXQ) { + res = scnprintf(&buf[idx], size, TXQ_STA_PREF_FMT, tid); + idx += res; + size -= res; + } else { + res = scnprintf(&buf[idx], size, TXQ_VIF_PREF_FMT, name); + idx += res; + size -= res; + } + + for (i = 0; i < CONFIG_USER_MAX; i++) { + pushed += txq->pkt_pushed[i]; + } + + res = scnprintf(&buf[idx], size, TXQ_HDR_FMT, txq->idx, + (txq->status & ECRNX_TXQ_IN_HWQ_LIST) ? "L" : " ", + (txq->status & ECRNX_TXQ_STOP_FULL) ? "F" : " ", + (txq->status & ECRNX_TXQ_STOP_STA_PS) ? "P" : " ", + (txq->status & ECRNX_TXQ_STOP_VIF_PS) ? "V" : " ", + (txq->status & ECRNX_TXQ_STOP_CHAN) ? "C" : " ", + (txq->status & ECRNX_TXQ_STOP_CSA) ? "S" : " ", + (txq->status & ECRNX_TXQ_STOP_MU_POS) ? "M" : " ", + (txq->status & ECRNX_TXQ_NDEV_FLOW_CTRL) ? "N" : " ", + txq->credits, skb_queue_len(&txq->sk_list), + txq->nb_retry, pushed); + idx += res; + size -= res; + +#ifdef CONFIG_ECRNX_AMSDUS_TX + if (type == STA_TXQ) { + res = scnprintf(&buf[idx], size, TXQ_HDR_SUFF_FMT, +#ifdef CONFIG_ECRNX_FULLMAC + txq->amsdu_len +#else + txq->amsdu_ht_len_cap, txq->amsdu_vht_len_cap +#endif /* CONFIG_ECRNX_FULLMAC */ + ); + idx += res; + size -= res; + } +#endif + + res = scnprintf(&buf[idx], size, "\n"); + idx += res; + size -= res; + + return idx; +} + +static int ecrnx_dbgfs_txq_sta(char *buf, size_t size, struct ecrnx_sta *ecrnx_sta, + struct ecrnx_hw *ecrnx_hw) +{ + int tid, res, idx = 0; + struct ecrnx_txq *txq; +#ifdef CONFIG_ECRNX_SOFTMAC + struct ieee80211_sta *sta = ecrnx_to_ieee80211_sta(ecrnx_sta); +#endif /* CONFIG_ECRNX_SOFTMAC */ + + res = scnprintf(&buf[idx], size, "\n" STA_HDR, + ecrnx_sta->sta_idx, +#ifdef CONFIG_ECRNX_SOFTMAC + sta->addr +#else + ecrnx_sta->mac_addr +#endif /* CONFIG_ECRNX_SOFTMAC */ + ); + idx += res; + size -= res; + +#ifdef CONFIG_ECRNX_FULLMAC + if (ecrnx_sta->ps.active) { + if (ecrnx_sta->uapsd_tids && + (ecrnx_sta->uapsd_tids == ((1 << NX_NB_TXQ_PER_STA) - 1))) + res = scnprintf(&buf[idx], size, PS_HDR_UAPSD "\n", + ecrnx_sta->ps.pkt_ready[UAPSD_ID], + ecrnx_sta->ps.sp_cnt[UAPSD_ID]); + else if (ecrnx_sta->uapsd_tids) + res = scnprintf(&buf[idx], size, PS_HDR "\n", + ecrnx_sta->ps.pkt_ready[LEGACY_PS_ID], + ecrnx_sta->ps.sp_cnt[LEGACY_PS_ID], + ecrnx_sta->ps.pkt_ready[UAPSD_ID], + ecrnx_sta->ps.sp_cnt[UAPSD_ID]); + else + res = scnprintf(&buf[idx], size, PS_HDR_LEGACY "\n", + ecrnx_sta->ps.pkt_ready[LEGACY_PS_ID], + ecrnx_sta->ps.sp_cnt[LEGACY_PS_ID]); + idx += res; + size -= res; + } else { + res = scnprintf(&buf[idx], size, "\n"); + idx += res; + size -= res; + } +#endif /* CONFIG_ECRNX_FULLMAC */ + + + res = scnprintf(&buf[idx], size, TXQ_STA_PREF TXQ_HDR TXQ_HDR_SUFF "\n"); + idx += res; + size -= res; + + + foreach_sta_txq(ecrnx_sta, txq, tid, ecrnx_hw) { + res = ecrnx_dbgfs_txq(&buf[idx], size, txq, STA_TXQ, tid, NULL); + idx += res; + size -= res; + } + + return idx; +} + +static int ecrnx_dbgfs_txq_vif(char *buf, size_t size, struct ecrnx_vif *ecrnx_vif, + struct ecrnx_hw *ecrnx_hw) +{ + int res, idx = 0; + struct ecrnx_txq *txq; + struct ecrnx_sta *ecrnx_sta; + +#ifdef CONFIG_ECRNX_FULLMAC + res = scnprintf(&buf[idx], size, VIF_HDR, ecrnx_vif->vif_index, ecrnx_vif->ndev->name); + idx += res; + size -= res; + if (!ecrnx_vif->up || ecrnx_vif->ndev == NULL) + return idx; + +#else + int ac; + char ac_name[2] = {'0', '\0'}; + + res = scnprintf(&buf[idx], size, VIF_HDR, ecrnx_vif->vif_index); + idx += res; + size -= res; +#endif /* CONFIG_ECRNX_FULLMAC */ + +#ifdef CONFIG_ECRNX_FULLMAC + if (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_AP || + ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_P2P_GO || + ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_MESH_POINT) { + res = scnprintf(&buf[idx], size, TXQ_VIF_PREF TXQ_HDR "\n"); + idx += res; + size -= res; + txq = ecrnx_txq_vif_get(ecrnx_vif, NX_UNK_TXQ_TYPE); + res = ecrnx_dbgfs_txq(&buf[idx], size, txq, VIF_TXQ, 0, "UNK"); + idx += res; + size -= res; + txq = ecrnx_txq_vif_get(ecrnx_vif, NX_BCMC_TXQ_TYPE); + res = ecrnx_dbgfs_txq(&buf[idx], size, txq, VIF_TXQ, 0, "BCMC"); + idx += res; + size -= res; + ecrnx_sta = &ecrnx_hw->sta_table[ecrnx_vif->ap.bcmc_index]; + if (ecrnx_sta->ps.active) { + res = scnprintf(&buf[idx], size, PS_HDR_LEGACY "\n", + ecrnx_sta->ps.sp_cnt[LEGACY_PS_ID], + ecrnx_sta->ps.sp_cnt[LEGACY_PS_ID]); + idx += res; + size -= res; + } else { + res = scnprintf(&buf[idx], size, "\n"); + idx += res; + size -= res; + } + + list_for_each_entry(ecrnx_sta, &ecrnx_vif->ap.sta_list, list) { + res = ecrnx_dbgfs_txq_sta(&buf[idx], size, ecrnx_sta, ecrnx_hw); + idx += res; + size -= res; + } + } else if (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_STATION || + ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_P2P_CLIENT) { + if (ecrnx_vif->sta.ap) { + res = ecrnx_dbgfs_txq_sta(&buf[idx], size, ecrnx_vif->sta.ap, ecrnx_hw); + idx += res; + size -= res; + } + } + +#else + res = scnprintf(&buf[idx], size, TXQ_VIF_PREF TXQ_HDR "\n"); + idx += res; + size -= res; + + foreach_vif_txq(ecrnx_vif, txq, ac) { + ac_name[0]++; + res = ecrnx_dbgfs_txq(&buf[idx], size, txq, VIF_TXQ, 0, ac_name); + idx += res; + size -= res; + } + + list_for_each_entry(ecrnx_sta, &ecrnx_vif->stations, list) { + res = ecrnx_dbgfs_txq_sta(&buf[idx], size, ecrnx_sta, ecrnx_hw); + idx += res; + size -= res; + } +#endif /* CONFIG_ECRNX_FULLMAC */ + return idx; +} + +static ssize_t ecrnx_dbgfs_txq_read(struct file *file , + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *ecrnx_hw = file->private_data; + struct ecrnx_vif *vif; + char *buf; + int idx, res; + ssize_t read; + size_t bufsz = ((NX_VIRT_DEV_MAX * (VIF_HDR_MAX_LEN + 2 * VIF_SEP_LEN)) + + (NX_REMOTE_STA_MAX * STA_HDR_MAX_LEN) + + ((NX_REMOTE_STA_MAX + NX_VIRT_DEV_MAX + NX_NB_TXQ) * + TXQ_HDR_MAX_LEN) + CAPTION_LEN); + + /* everything is read in one go */ + if (*ppos) + return 0; + + bufsz = min_t(size_t, bufsz, count); + buf = kmalloc(bufsz, GFP_ATOMIC); + if (buf == NULL) + return 0; + + bufsz--; + idx = 0; + + res = scnprintf(&buf[idx], bufsz, CAPTION); + idx += res; + bufsz -= res; + + //spin_lock_bh(&ecrnx_hw->tx_lock); + list_for_each_entry(vif, &ecrnx_hw->vifs, list) { + res = scnprintf(&buf[idx], bufsz, "\n"VIF_SEP); + idx += res; + bufsz -= res; + res = ecrnx_dbgfs_txq_vif(&buf[idx], bufsz, vif, ecrnx_hw); + idx += res; + bufsz -= res; + res = scnprintf(&buf[idx], bufsz, VIF_SEP); + idx += res; + bufsz -= res; + } + //spin_unlock_bh(&ecrnx_hw->tx_lock); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, idx); + kfree(buf); + + return read; +} +DEBUGFS_READ_FILE_OPS(txq); + +static ssize_t ecrnx_dbgfs_acsinfo_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + #ifdef CONFIG_ECRNX_SOFTMAC + struct wiphy *wiphy = priv->hw->wiphy; + #else //CONFIG_ECRNX_SOFTMAC + struct wiphy *wiphy = priv->wiphy; + #endif //CONFIG_ECRNX_SOFTMAC + ssize_t read; + char *buf = kmalloc((SCAN_CHANNEL_MAX + 1) * 43, GFP_ATOMIC); + + //char buf[(SCAN_CHANNEL_MAX + 1) * 43]; + int survey_cnt = 0; + int len = 0; + int band, chan_cnt; + + if(!buf){ + return 0; + } + + mutex_lock(&priv->dbgdump_elem.mutex); + + len += scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "FREQ TIME(ms) BUSY(ms) NOISE(dBm)\n"); + +#ifdef CONFIG_ECRNX_5G + for (band = NL80211_BAND_2GHZ; band <= NL80211_BAND_5GHZ; band++) { +#else + for (band = NL80211_BAND_2GHZ; band <= NL80211_BAND_2GHZ; band++) { +#endif + for (chan_cnt = 0; chan_cnt < wiphy->bands[band]->n_channels; chan_cnt++) { + struct ecrnx_survey_info *p_survey_info = &priv->survey[survey_cnt]; + struct ieee80211_channel *p_chan = &wiphy->bands[band]->channels[chan_cnt]; + + if (p_survey_info->filled) { + len += scnprintf(&buf[len], min_t(size_t, sizeof(buf) - len - 1, count), + "%d %03d %03d %d\n", + p_chan->center_freq, + p_survey_info->chan_time_ms, + p_survey_info->chan_time_busy_ms, + p_survey_info->noise_dbm); + } else { + len += scnprintf(&buf[len], min_t(size_t, sizeof(buf) -len -1, count), + "%d NOT AVAILABLE\n", + p_chan->center_freq); + } + + survey_cnt++; + } + } + + mutex_unlock(&priv->dbgdump_elem.mutex); + read = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return read; +} + +DEBUGFS_READ_FILE_OPS(acsinfo); + +static ssize_t ecrnx_dbgfs_fw_dbg_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + char help[]="usage: [MOD:<ALL|KE|DBG|IPC|DMA|MM|TX|RX|PHY>]* " + "[DBG:<NONE|CRT|ERR|WRN|INF|VRB>]\n"; + + return simple_read_from_buffer(user_buf, count, ppos, help, sizeof(help)); +} + + +static ssize_t ecrnx_dbgfs_fw_dbg_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int idx = 0; + u32 mod = 0; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + buf[len] = '\0'; + +#define ECRNX_MOD_TOKEN(str, val) \ + if (strncmp(&buf[idx], str, sizeof(str) - 1 ) == 0) { \ + idx += sizeof(str) - 1; \ + mod |= val; \ + continue; \ + } + +#define ECRNX_DBG_TOKEN(str, val) \ + if (strncmp(&buf[idx], str, sizeof(str) - 1) == 0) { \ + idx += sizeof(str) - 1; \ + dbg = val; \ + goto dbg_done; \ + } + + while ((idx + 4) < len) { + if (strncmp(&buf[idx], "MOD:", 4) == 0) { + idx += 4; + ECRNX_MOD_TOKEN("ALL", 0xffffffff); + ECRNX_MOD_TOKEN("KE", BIT(0)); + ECRNX_MOD_TOKEN("DBG", BIT(1)); + ECRNX_MOD_TOKEN("IPC", BIT(2)); + ECRNX_MOD_TOKEN("DMA", BIT(3)); + ECRNX_MOD_TOKEN("MM", BIT(4)); + ECRNX_MOD_TOKEN("TX", BIT(5)); + ECRNX_MOD_TOKEN("RX", BIT(6)); + ECRNX_MOD_TOKEN("PHY", BIT(7)); + idx++; + } else if (strncmp(&buf[idx], "DBG:", 4) == 0) { + u32 dbg = 0; + idx += 4; + ECRNX_DBG_TOKEN("NONE", 0); + ECRNX_DBG_TOKEN("CRT", 1); + ECRNX_DBG_TOKEN("ERR", 2); + ECRNX_DBG_TOKEN("WRN", 3); + ECRNX_DBG_TOKEN("INF", 4); + ECRNX_DBG_TOKEN("VRB", 5); + idx++; + continue; + dbg_done: + ecrnx_send_dbg_set_sev_filter_req(priv, dbg); + } else { + idx++; + } + } + + if (mod) { + ecrnx_send_dbg_set_mod_filter_req(priv, mod); + } + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(fw_dbg); + +static ssize_t ecrnx_dbgfs_sys_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[3*64]; + int len = 0; + ssize_t read; + int error = 0; + struct dbg_get_sys_stat_cfm cfm; + u32 sleep_int, sleep_frac, doze_int, doze_frac; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Get the information from the FW */ + if ((error = ecrnx_send_dbg_get_sys_stat_req(priv, &cfm))) + return error; + + if (cfm.stats_time == 0) + return 0; + + sleep_int = ((cfm.cpu_sleep_time * 100) / cfm.stats_time); + sleep_frac = (((cfm.cpu_sleep_time * 100) % cfm.stats_time) * 10) / cfm.stats_time; + doze_int = ((cfm.doze_time * 100) / cfm.stats_time); + doze_frac = (((cfm.doze_time * 100) % cfm.stats_time) * 10) / cfm.stats_time; + + len += scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "\nSystem statistics:\n"); + len += scnprintf(&buf[len], min_t(size_t, sizeof(buf) - 1, count), + " CPU sleep [%%]: %d.%d\n", sleep_int, sleep_frac); + len += scnprintf(&buf[len], min_t(size_t, sizeof(buf) - 1, count), + " Doze [%%]: %d.%d\n", doze_int, doze_frac); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + return read; +} + +DEBUGFS_READ_FILE_OPS(sys_stats); + +#ifdef CONFIG_ECRNX_MUMIMO_TX +static ssize_t ecrnx_dbgfs_mu_group_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *ecrnx_hw = file->private_data; + struct ecrnx_mu_info *mu = &ecrnx_hw->mu; + struct ecrnx_mu_group *group; + size_t bufsz = NX_MU_GROUP_MAX * sizeof("xx = (xx - xx - xx - xx)\n") + 50; + char *buf; + int j, res, idx = 0; + + if (*ppos) + return 0; + + buf = kmalloc(bufsz, GFP_ATOMIC); + if (buf == NULL) + return 0; + + res = scnprintf(&buf[idx], bufsz, "MU Group list (%d groups, %d users max)\n", + NX_MU_GROUP_MAX, CONFIG_USER_MAX); + idx += res; + bufsz -= res; + + list_for_each_entry(group, &mu->active_groups, list) { + if (group->user_cnt) { + res = scnprintf(&buf[idx], bufsz, "%2d = (", group->group_id); + idx += res; + bufsz -= res; + for (j = 0; j < (CONFIG_USER_MAX - 1) ; j++) { + if (group->users[j]) + res = scnprintf(&buf[idx], bufsz, "%2d - ", + group->users[j]->sta_idx); + else + res = scnprintf(&buf[idx], bufsz, ".. - "); + + idx += res; + bufsz -= res; + } + + if (group->users[j]) + res = scnprintf(&buf[idx], bufsz, "%2d)\n", + group->users[j]->sta_idx); + else + res = scnprintf(&buf[idx], bufsz, "..)\n"); + + idx += res; + bufsz -= res; + } + } + + res = simple_read_from_buffer(user_buf, count, ppos, buf, idx); + kfree(buf); + + return res; +} + +DEBUGFS_READ_FILE_OPS(mu_group); +#endif + +#ifdef CONFIG_ECRNX_P2P_DEBUGFS +static ssize_t ecrnx_dbgfs_oppps_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *rw_hw = file->private_data; + struct ecrnx_vif *rw_vif; + char buf[32]; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + int ctw; + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + buf[len] = '\0'; + + /* Read the written CT Window (provided in ms) value */ + if (sscanf(buf, "ctw=%d", &ctw) > 0) { + /* Check if at least one VIF is configured as P2P GO */ + list_for_each_entry(rw_vif, &rw_hw->vifs, list) { +#ifdef CONFIG_ECRNX_SOFTMAC + if ((ECRNX_VIF_TYPE(rw_vif) == NL80211_IFTYPE_AP) && rw_vif->vif->p2p) { +#else /* CONFIG_ECRNX_FULLMAC */ + if (ECRNX_VIF_TYPE(rw_vif) == NL80211_IFTYPE_P2P_GO) { +#endif /* CONFIG_ECRNX_SOFTMAC */ + struct mm_set_p2p_oppps_cfm cfm; + + /* Forward request to the embedded and wait for confirmation */ + ecrnx_send_p2p_oppps_req(rw_hw, rw_vif, (u8)ctw, &cfm); + + break; + } + } + } + + return count; +} + +DEBUGFS_WRITE_FILE_OPS(oppps); + +static ssize_t ecrnx_dbgfs_noa_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *rw_hw = file->private_data; + struct ecrnx_vif *rw_vif; + char buf[64]; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + int noa_count, interval, duration, dyn_noa; + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + buf[len] = '\0'; + + /* Read the written NOA information */ + if (sscanf(buf, "count=%d interval=%d duration=%d dyn=%d", + &noa_count, &interval, &duration, &dyn_noa) > 0) { + /* Check if at least one VIF is configured as P2P GO */ + list_for_each_entry(rw_vif, &rw_hw->vifs, list) { +#ifdef CONFIG_ECRNX_SOFTMAC + if ((ECRNX_VIF_TYPE(rw_vif) == NL80211_IFTYPE_AP) && rw_vif->vif->p2p) { +#else /* CONFIG_ECRNX_FULLMAC */ + if (ECRNX_VIF_TYPE(rw_vif) == NL80211_IFTYPE_P2P_GO) { +#endif /* CONFIG_ECRNX_SOFTMAC */ + struct mm_set_p2p_noa_cfm cfm; + + /* Forward request to the embedded and wait for confirmation */ + ecrnx_send_p2p_noa_req(rw_hw, rw_vif, noa_count, interval, + duration, (dyn_noa > 0), &cfm); + + break; + } + } + } + + return count; +} + +DEBUGFS_WRITE_FILE_OPS(noa); +#endif /* CONFIG_ECRNX_P2P_DEBUGFS */ + +struct ecrnx_dbgfs_fw_trace { + struct ecrnx_fw_trace_local_buf lbuf; + struct ecrnx_fw_trace *trace; + struct ecrnx_hw *ecrnx_hw; +}; + +static int ecrnx_dbgfs_fw_trace_open(struct inode *inode, struct file *file) +{ + struct ecrnx_dbgfs_fw_trace *ltrace = kmalloc(sizeof(*ltrace), GFP_KERNEL); + struct ecrnx_hw *priv = inode->i_private; + + if (!ltrace) + return -ENOMEM; + + if (ecrnx_fw_trace_alloc_local(<race->lbuf, 5120)) { + kfree(ltrace); + } + + ltrace->trace = &priv->debugfs.fw_trace; + ltrace->ecrnx_hw = priv; + file->private_data = ltrace; + return 0; +} + +static int ecrnx_dbgfs_fw_trace_release(struct inode *inode, struct file *file) +{ + struct ecrnx_dbgfs_fw_trace *ltrace = file->private_data; + + if (ltrace) { + ecrnx_fw_trace_free_local(<race->lbuf); + kfree(ltrace); + } + + return 0; +} + +static ssize_t ecrnx_dbgfs_fw_trace_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_dbgfs_fw_trace *ltrace = file->private_data; + bool dont_wait = ((file->f_flags & O_NONBLOCK) || + ltrace->ecrnx_hw->debugfs.unregistering); + + return ecrnx_fw_trace_read(ltrace->trace, <race->lbuf, + dont_wait, user_buf, count); +} + +static ssize_t ecrnx_dbgfs_fw_trace_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_dbgfs_fw_trace *ltrace = file->private_data; + int ret; + + ret = _ecrnx_fw_trace_reset(ltrace->trace, true); + if (ret) + return ret; + + return count; +} + +DEBUGFS_READ_WRITE_OPEN_RELEASE_FILE_OPS(fw_trace); + +static ssize_t ecrnx_dbgfs_fw_trace_level_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + return ecrnx_fw_trace_level_read(&priv->debugfs.fw_trace, user_buf, + count, ppos); +} + +static ssize_t ecrnx_dbgfs_fw_trace_level_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + return ecrnx_fw_trace_level_write(&priv->debugfs.fw_trace, user_buf, count); +} +DEBUGFS_READ_WRITE_FILE_OPS(fw_trace_level); + + +#ifdef CONFIG_ECRNX_RADAR +static ssize_t ecrnx_dbgfs_pulses_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos, + int rd_idx) +{ + struct ecrnx_hw *priv = file->private_data; + char *buf; + int len = 0; + int bufsz; + int i; + int index; + struct ecrnx_radar_pulses *p = &priv->radar.pulses[rd_idx]; + ssize_t read; + + if (*ppos != 0) + return 0; + + /* Prevent from interrupt preemption */ + spin_lock_bh(&priv->radar.lock); + bufsz = p->count * 34 + 51; + bufsz += ecrnx_radar_dump_pattern_detector(NULL, 0, &priv->radar, rd_idx); + buf = kmalloc(bufsz, GFP_ATOMIC); + if (buf == NULL) { + spin_unlock_bh(&priv->radar.lock); + return 0; + } + + if (p->count) { + len += scnprintf(&buf[len], bufsz - len, + " PRI WIDTH FOM FREQ\n"); + index = p->index; + for (i = 0; i < p->count; i++) { + struct radar_pulse *pulse; + + if (index > 0) + index--; + else + index = ECRNX_RADAR_PULSE_MAX - 1; + + pulse = (struct radar_pulse *) &p->buffer[index]; + + len += scnprintf(&buf[len], bufsz - len, + "%05dus %03dus %2d%% %+3dMHz\n", pulse->rep, + 2 * pulse->len, 6 * pulse->fom, 2*pulse->freq); + } + } + + len += ecrnx_radar_dump_pattern_detector(&buf[len], bufsz - len, + &priv->radar, rd_idx); + + spin_unlock_bh(&priv->radar.lock); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + kfree(buf); + + return read; +} + +static ssize_t ecrnx_dbgfs_pulses_prim_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + return ecrnx_dbgfs_pulses_read(file, user_buf, count, ppos, 0); +} + +DEBUGFS_READ_FILE_OPS(pulses_prim); + +static ssize_t ecrnx_dbgfs_pulses_sec_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + return ecrnx_dbgfs_pulses_read(file, user_buf, count, ppos, 1); +} + +DEBUGFS_READ_FILE_OPS(pulses_sec); + +static ssize_t ecrnx_dbgfs_detected_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char *buf; + int bufsz,len = 0; + ssize_t read; + + if (*ppos != 0) + return 0; + + bufsz = 5; // RIU:\n + bufsz += ecrnx_radar_dump_radar_detected(NULL, 0, &priv->radar, + ECRNX_RADAR_RIU); + + if (priv->phy.cnt > 1) { + bufsz += 5; // FCU:\n + bufsz += ecrnx_radar_dump_radar_detected(NULL, 0, &priv->radar, + ECRNX_RADAR_FCU); + } + + buf = kmalloc(bufsz, GFP_KERNEL); + if (buf == NULL) { + return 0; + } + + len = scnprintf(&buf[len], bufsz, "RIU:\n"); + len += ecrnx_radar_dump_radar_detected(&buf[len], bufsz - len, &priv->radar, + ECRNX_RADAR_RIU); + + if (priv->phy.cnt > 1) { + len += scnprintf(&buf[len], bufsz - len, "FCU:\n"); + len += ecrnx_radar_dump_radar_detected(&buf[len], bufsz - len, + &priv->radar, ECRNX_RADAR_FCU); + } + + read = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + kfree(buf); + + return read; +} + +DEBUGFS_READ_FILE_OPS(detected); + +static ssize_t ecrnx_dbgfs_enable_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int ret; + ssize_t read; + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "RIU=%d FCU=%d\n", priv->radar.dpd[ECRNX_RADAR_RIU]->enabled, + priv->radar.dpd[ECRNX_RADAR_FCU]->enabled); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + return read; +} + +static ssize_t ecrnx_dbgfs_enable_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int val; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + + if (sscanf(buf, "RIU=%d", &val) > 0) + ecrnx_radar_detection_enable(&priv->radar, val, ECRNX_RADAR_RIU); + + if (sscanf(buf, "FCU=%d", &val) > 0) + ecrnx_radar_detection_enable(&priv->radar, val, ECRNX_RADAR_FCU); + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(enable); + +static ssize_t ecrnx_dbgfs_band_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int ret; + ssize_t read; + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "BAND=%d\n", priv->phy.sec_chan.band); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + return read; +} + +static ssize_t ecrnx_dbgfs_band_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int val; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + +#ifdef CONFIG_ECRNX_5G + if ((sscanf(buf, "%d", &val) > 0) && (val >= 0) && (val <= NL80211_BAND_5GHZ)) +#else + if ((sscanf(buf, "%d", &val) > 0) && (val >= 0) && (val <= NL80211_BAND_2GHZ)) +#endif + priv->phy.sec_chan.band = val; + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(band); + +static ssize_t ecrnx_dbgfs_type_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int ret; + ssize_t read; + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "TYPE=%d\n", priv->phy.sec_chan.type); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + return read; +} + +static ssize_t ecrnx_dbgfs_type_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int val; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + + if ((sscanf(buf, "%d", &val) > 0) && (val >= PHY_CHNL_BW_20) && + (val <= PHY_CHNL_BW_80P80)) + priv->phy.sec_chan.type = val; + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(type); + +static ssize_t ecrnx_dbgfs_prim20_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int ret; + ssize_t read; + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "PRIM20=%dMHz\n", priv->phy.sec_chan.prim20_freq); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + return read; +} + +static ssize_t ecrnx_dbgfs_prim20_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int val; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + + if (sscanf(buf, "%d", &val) > 0) + priv->phy.sec_chan.prim20_freq = val; + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(prim20); + +static ssize_t ecrnx_dbgfs_center1_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int ret; + ssize_t read; + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "CENTER1=%dMHz\n", priv->phy.sec_chan.center1_freq); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + return read; +} + +static ssize_t ecrnx_dbgfs_center1_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int val; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + + if (sscanf(buf, "%d", &val) > 0) + priv->phy.sec_chan.center1_freq = val; + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(center1); + +static ssize_t ecrnx_dbgfs_center2_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int ret; + ssize_t read; + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "CENTER2=%dMHz\n", priv->phy.sec_chan.center2_freq); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + return read; +} + +static ssize_t ecrnx_dbgfs_center2_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[32]; + int val; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + + if (sscanf(buf, "%d", &val) > 0) + priv->phy.sec_chan.center2_freq = val; + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(center2); + + +static ssize_t ecrnx_dbgfs_set_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + return 0; +} + +static ssize_t ecrnx_dbgfs_set_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + + ecrnx_send_set_channel(priv, 1, NULL); + ecrnx_radar_detection_enable(&priv->radar, ECRNX_RADAR_DETECT_ENABLE, + ECRNX_RADAR_FCU); + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(set); +#endif /* CONFIG_ECRNX_RADAR */ + +#ifdef CONFIG_ECRNX_FULLMAC + +#define LINE_MAX_SZ 150 + +struct st { + char line[LINE_MAX_SZ + 1]; + unsigned int r_idx; +}; + +static int compare_idx(const void *st1, const void *st2) +{ + int index1 = ((struct st *)st1)->r_idx; + int index2 = ((struct st *)st2)->r_idx; + + if (index1 > index2) return 1; + if (index1 < index2) return -1; + + return 0; +} + +static const int ru_size[] = +{ + 26, + 52, + 106, + 242, + 484, + 996 +}; + +static int print_rate(char *buf, int size, int format, int nss, int mcs, int bw, + int sgi, int pre, int dcm, int *r_idx) +{ + int res = 0; + int bitrates_cck[4] = { 10, 20, 55, 110 }; + int bitrates_ofdm[8] = { 6, 9, 12, 18, 24, 36, 48, 54}; + char he_gi[3][4] = {"0.8", "1.6", "3.2"}; + + if (format < FORMATMOD_HT_MF) { + if (mcs < 4) { + if (r_idx) { + *r_idx = (mcs * 2) + pre; + res = scnprintf(buf, size - res, "%3d ", *r_idx); + } + res += scnprintf(&buf[res], size - res, "L-CCK/%cP %2u.%1uM ", + pre > 0 ? 'L' : 'S', + bitrates_cck[mcs] / 10, + bitrates_cck[mcs] % 10); + } else { + mcs -= 4; + if (r_idx) { + *r_idx = N_CCK + mcs; + res = scnprintf(buf, size - res, "%3d ", *r_idx); + } + res += scnprintf(&buf[res], size - res, "L-OFDM %2u.0M ", + bitrates_ofdm[mcs]); + } + } else if (format < FORMATMOD_VHT) { + if (r_idx) { + *r_idx = N_CCK + N_OFDM + nss * 32 + mcs * 4 + bw * 2 + sgi; + res = scnprintf(buf, size - res, "%3d ", *r_idx); + } + mcs += nss * 8; + res += scnprintf(&buf[res], size - res, "HT%d/%cGI MCS%-2d ", + 20 * (1 << bw), sgi ? 'S' : 'L', mcs); + } else if (format == FORMATMOD_VHT){ + if (r_idx) { + *r_idx = N_CCK + N_OFDM + N_HT + nss * 80 + mcs * 8 + bw * 2 + sgi; + res = scnprintf(buf, size - res, "%3d ", *r_idx); + } + res += scnprintf(&buf[res], size - res, "VHT%d/%cGI%*cMCS%d/%1d ", + 20 * (1 << bw), sgi ? 'S' : 'L', bw > 2 ? 9 : 10, ' ', + mcs, nss + 1); + } else if (format == FORMATMOD_HE_SU){ + if (r_idx) { + *r_idx = N_CCK + N_OFDM + N_HT + N_VHT + nss * 144 + mcs * 12 + bw * 3 + sgi; + res = scnprintf(buf, size - res, "%3d ", *r_idx); + } + res += scnprintf(&buf[res], size - res, "HE%d/GI%s%4s%*cMCS%d/%1d%*c", + 20 * (1 << bw), he_gi[sgi], dcm ? "/DCM" : "", + bw > 2 ? 4 : 5, ' ', mcs, nss + 1, mcs > 9 ? 1 : 2, ' '); + } else { + if (r_idx) { + *r_idx = N_CCK + N_OFDM + N_HT + N_VHT + N_HE_SU + nss * 216 + mcs * 18 + bw * 3 + sgi; + res = scnprintf(buf, size - res, "%3d ", *r_idx); + } + res += scnprintf(&buf[res], size - res, "HEMU-%d/GI%s%*cMCS%d/%1d%*c", + ru_size[bw], he_gi[sgi], bw > 1 ? 1 : 2, ' ', + mcs, nss + 1, mcs > 9 ? 1 : 2, ' '); + + } + + return res; +} + +static int print_rate_from_cfg(char *buf, int size, u32 rate_config, int *r_idx, int ru_size) +{ + union ecrnx_rate_ctrl_info *r_cfg = (union ecrnx_rate_ctrl_info *)&rate_config; + union ecrnx_mcs_index *mcs_index = (union ecrnx_mcs_index *)&rate_config; + unsigned int ft, pre, gi, bw, nss, mcs, dcm, len; + + ft = r_cfg->formatModTx; + pre = r_cfg->giAndPreTypeTx >> 1; + gi = r_cfg->giAndPreTypeTx; + bw = r_cfg->bwTx; + dcm = 0; + if (ft == FORMATMOD_HE_MU) { + mcs = mcs_index->he.mcs; + nss = mcs_index->he.nss; + bw = ru_size; + dcm = r_cfg->dcmTx; + } else if (ft == FORMATMOD_HE_SU) { + mcs = mcs_index->he.mcs; + nss = mcs_index->he.nss; + dcm = r_cfg->dcmTx; + } else if (ft == FORMATMOD_VHT) { + mcs = mcs_index->vht.mcs; + nss = mcs_index->vht.nss; + } else if (ft >= FORMATMOD_HT_MF) { + mcs = mcs_index->ht.mcs; + nss = mcs_index->ht.nss; + } else { + mcs = mcs_index->legacy; + nss = 0; + } + + len = print_rate(buf, size, ft, nss, mcs, bw, gi, pre, dcm, r_idx); + return len; +} + +static void idx_to_rate_cfg(int idx, union ecrnx_rate_ctrl_info *r_cfg, int *ru_size) +{ + r_cfg->value = 0; + if (idx < N_CCK) + { + r_cfg->formatModTx = FORMATMOD_NON_HT; + r_cfg->giAndPreTypeTx = (idx & 1) << 1; + r_cfg->mcsIndexTx = idx / 2; + } + else if (idx < (N_CCK + N_OFDM)) + { + r_cfg->formatModTx = FORMATMOD_NON_HT; + r_cfg->mcsIndexTx = idx - N_CCK + 4; + } + else if (idx < (N_CCK + N_OFDM + N_HT)) + { + union ecrnx_mcs_index *r = (union ecrnx_mcs_index *)r_cfg; + + idx -= (N_CCK + N_OFDM); + r_cfg->formatModTx = FORMATMOD_HT_MF; + r->ht.nss = idx / (8*2*2); + r->ht.mcs = (idx % (8*2*2)) / (2*2); + r_cfg->bwTx = ((idx % (8*2*2)) % (2*2)) / 2; + r_cfg->giAndPreTypeTx = idx & 1; + } + else if (idx < (N_CCK + N_OFDM + N_HT + N_VHT)) + { + union ecrnx_mcs_index *r = (union ecrnx_mcs_index *)r_cfg; + + idx -= (N_CCK + N_OFDM + N_HT); + r_cfg->formatModTx = FORMATMOD_VHT; + r->vht.nss = idx / (10*4*2); + r->vht.mcs = (idx % (10*4*2)) / (4*2); + r_cfg->bwTx = ((idx % (10*4*2)) % (4*2)) / 2; + r_cfg->giAndPreTypeTx = idx & 1; + } + else if (idx < (N_CCK + N_OFDM + N_HT + N_VHT + N_HE_SU)) + { + union ecrnx_mcs_index *r = (union ecrnx_mcs_index *)r_cfg; + + idx -= (N_CCK + N_OFDM + N_HT + N_VHT); + r_cfg->formatModTx = FORMATMOD_HE_SU; + r->vht.nss = idx / (12*4*3); + r->vht.mcs = (idx % (12*4*3)) / (4*3); + r_cfg->bwTx = ((idx % (12*4*3)) % (4*3)) / 3; + r_cfg->giAndPreTypeTx = idx % 3; + } + else + { + union ecrnx_mcs_index *r = (union ecrnx_mcs_index *)r_cfg; + + BUG_ON(ru_size == NULL); + + idx -= (N_CCK + N_OFDM + N_HT + N_VHT + N_HE_SU); + r_cfg->formatModTx = FORMATMOD_HE_MU; + r->vht.nss = idx / (12*6*3); + r->vht.mcs = (idx % (12*6*3)) / (6*3); + *ru_size = ((idx % (12*6*3)) % (6*3)) / 3; + r_cfg->giAndPreTypeTx = idx % 3; + r_cfg->bwTx = 0; + } +} + +static struct ecrnx_sta* ecrnx_dbgfs_get_sta(struct ecrnx_hw *ecrnx_hw, + char* mac_addr) +{ + u8 mac[6]; + + if (sscanf(mac_addr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) != 6) + return NULL; + return ecrnx_get_sta(ecrnx_hw, mac); +} + +static ssize_t ecrnx_dbgfs_twt_request_read(struct file *file, + char __user *user_buf, + size_t count, + loff_t *ppos) +{ + char buf[750]; + ssize_t read; + struct ecrnx_hw *priv = file->private_data; + struct ecrnx_sta *sta = NULL; + int len; + + /* Get the station index from MAC address */ + sta = ecrnx_dbgfs_get_sta(priv, file->f_path.dentry->d_parent->d_parent->d_iname); + if (sta == NULL) + return -EINVAL; + if (sta->twt_ind.sta_idx != ECRNX_INVALID_STA) + { + struct twt_conf_tag *conf = &sta->twt_ind.conf; + if (sta->twt_ind.resp_type == MAC_TWT_SETUP_ACCEPT) + len = scnprintf(buf, sizeof(buf) - 1, "Accepted configuration"); + else if (sta->twt_ind.resp_type == MAC_TWT_SETUP_ALTERNATE) + len = scnprintf(buf, sizeof(buf) - 1, "Alternate configuration proposed by AP"); + else if (sta->twt_ind.resp_type == MAC_TWT_SETUP_DICTATE) + len = scnprintf(buf, sizeof(buf) - 1, "AP dictates the following configuration"); + else if (sta->twt_ind.resp_type == MAC_TWT_SETUP_REJECT) + len = scnprintf(buf, sizeof(buf) - 1, "AP rejects the following configuration"); + else + { + len = scnprintf(buf, sizeof(buf) - 1, "Invalid response from the peer"); + goto end; + } + len += scnprintf(&buf[len], sizeof(buf) - 1 - len,":\n" + "flow_type = %d\n" + "wake interval mantissa = %d\n" + "wake interval exponent = %d\n" + "wake interval = %d us\n" + "nominal minimum wake duration = %d us\n", + conf->flow_type, conf->wake_int_mantissa, + conf->wake_int_exp, + conf->wake_int_mantissa << conf->wake_int_exp, + conf->wake_dur_unit ? + conf->min_twt_wake_dur * 1024: + conf->min_twt_wake_dur * 256); + } + else + { + len = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "setup_command = <0: request, 1: suggest, 2: demand>," + "flow_type = <0: announced, 1: unannounced>," + "wake_interval_mantissa = <0 if setup request and no constraints>," + "wake_interval_exp = <0 if setup request and no constraints>," + "nominal_min_wake_dur = <0 if setup request and no constraints>," + "wake_dur_unit = <0: 256us, 1: tu>"); + } + end: + read = simple_read_from_buffer(user_buf, count, ppos, buf, len); + return read; +} + +static ssize_t ecrnx_dbgfs_twt_request_write(struct file *file, + const char __user *user_buf, + size_t count, + loff_t *ppos) +{ + char *accepted_params[] = {"setup_command", + "flow_type", + "wake_interval_mantissa", + "wake_interval_exp", + "nominal_min_wake_dur", + "wake_dur_unit", + 0 + }; + struct twt_conf_tag twt_conf; + struct twt_setup_cfm twt_setup_cfm; + struct ecrnx_sta *sta = NULL; + struct ecrnx_hw *priv = file->private_data; + char param[30]; + char *line; + int error = 1, i, val, setup_command = -1; + bool_l found; + char *buf = kmalloc(1024, GFP_ATOMIC); + size_t len = 1024 - 1; + + if(!buf){ + return 0; + } + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + /* Get the station index from MAC address */ + sta = ecrnx_dbgfs_get_sta(priv, file->f_path.dentry->d_parent->d_parent->d_iname); + if (sta == NULL){ + kfree(buf); + return -EINVAL; + } + + /* Get the content of the file */ + if (copy_from_user(buf, user_buf, len)){ + kfree(buf); + return -EFAULT; + } + + buf[len] = '\0'; + memset(&twt_conf, 0, sizeof(twt_conf)); + + line = buf; + /* Get the content of the file */ + while (line != NULL) + { + if (sscanf(line, "%s = %d", param, &val) == 2) + { + i = 0; + found = false; + // Check if parameter is valid + while(accepted_params[i]) + { + if (strcmp(accepted_params[i], param) == 0) + { + found = true; + break; + } + i++; + } + + if (!found) + { + dev_err(priv->dev, "%s: parameter %s is not valid\n", __func__, param); + kfree(buf); + return -EINVAL; + } + + if (!strcmp(param, "setup_command")) + { + setup_command = val; + } + else if (!strcmp(param, "flow_type")) + { + twt_conf.flow_type = val; + } + else if (!strcmp(param, "wake_interval_mantissa")) + { + twt_conf.wake_int_mantissa = val; + } + else if (!strcmp(param, "wake_interval_exp")) + { + twt_conf.wake_int_exp = val; + } + else if (!strcmp(param, "nominal_min_wake_dur")) + { + twt_conf.min_twt_wake_dur = val; + } + else if (!strcmp(param, "wake_dur_unit")) + { + twt_conf.wake_dur_unit = val; + } + } + else + { + dev_err(priv->dev, "%s: Impossible to read TWT configuration option\n", __func__); + kfree(buf); + return -EFAULT; + } + line = strchr(line, ','); + if(line == NULL) + break; + line++; + } + + if (setup_command == -1) + { + dev_err(priv->dev, "%s: TWT missing setup command\n", __func__); + kfree(buf); + return -EFAULT; + } + + // Forward the request to the LMAC + if ((error = ecrnx_send_twt_request(priv, setup_command, sta->vif_idx, + &twt_conf, &twt_setup_cfm)) != 0){ + kfree(buf); + return error; + } + + // Check the status + if (twt_setup_cfm.status != CO_OK){ + kfree(buf); + return -EIO; + } + + kfree(buf); + return count; +} +DEBUGFS_READ_WRITE_FILE_OPS(twt_request); + +static ssize_t ecrnx_dbgfs_twt_teardown_read(struct file *file, + char __user *user_buf, + size_t count, + loff_t *ppos) +{ + char buf[512]; + int ret; + ssize_t read; + + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "TWT teardown format:\n\n" + "flow_id = <ID>\n"); + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + return read; +} + +static ssize_t ecrnx_dbgfs_twt_teardown_write(struct file *file, + const char __user *user_buf, + size_t count, + loff_t *ppos) +{ + struct twt_teardown_req twt_teardown; + struct twt_teardown_cfm twt_teardown_cfm; + struct ecrnx_sta *sta = NULL; + struct ecrnx_hw *priv = file->private_data; + char buf[256]; + char *line; + int error = 1; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + /* Get the station index from MAC address */ + sta = ecrnx_dbgfs_get_sta(priv, file->f_path.dentry->d_parent->d_parent->d_iname); + if (sta == NULL) + return -EINVAL; + + /* Get the content of the file */ + if (copy_from_user(buf, user_buf, len)) + return -EINVAL; + + buf[len] = '\0'; + memset(&twt_teardown, 0, sizeof(twt_teardown)); + + /* Get the content of the file */ + line = buf; + + if (sscanf(line, "flow_id = %d", (int *) &twt_teardown.id) != 1) + { + dev_err(priv->dev, "%s: Invalid TWT configuration\n", __func__); + return -EINVAL; + } + + twt_teardown.neg_type = 0; + twt_teardown.all_twt = 0; + twt_teardown.vif_idx = sta->vif_idx; + + // Forward the request to the LMAC + if ((error = ecrnx_send_twt_teardown(priv, &twt_teardown, &twt_teardown_cfm)) != 0) + return error; + + // Check the status + if (twt_teardown_cfm.status != CO_OK) + return -EIO; + + return count; +} +DEBUGFS_READ_WRITE_FILE_OPS(twt_teardown); + +static ssize_t ecrnx_dbgfs_rc_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_sta *sta = NULL; + struct ecrnx_hw *priv = file->private_data; + char *buf; + int bufsz, len = 0; + ssize_t read; + int i = 0; + int error = 0; + struct me_rc_stats_cfm me_rc_stats_cfm; + unsigned int no_samples; + struct st *st; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* everything should fit in one call */ + if (*ppos) + return 0; + + /* Get the station index from MAC address */ + sta = ecrnx_dbgfs_get_sta(priv, file->f_path.dentry->d_parent->d_parent->d_iname); + if (sta == NULL) + return -EINVAL; + + /* Forward the information to the LMAC */ + if ((error = ecrnx_send_me_rc_stats(priv, sta->sta_idx, &me_rc_stats_cfm))) + return error; + + no_samples = me_rc_stats_cfm.no_samples; + if (no_samples == 0) + return 0; + + bufsz = no_samples * LINE_MAX_SZ + 500; + + buf = kmalloc(bufsz + 1, GFP_ATOMIC); + if (buf == NULL) + return 0; + + st = kmalloc(sizeof(struct st) * no_samples, GFP_ATOMIC); + if (st == NULL) + { + kfree(buf); + return 0; + } + + for (i = 0; i < no_samples; i++) + { + unsigned int tp, eprob; + len = print_rate_from_cfg(st[i].line, LINE_MAX_SZ, + me_rc_stats_cfm.rate_stats[i].rate_config, + &st[i].r_idx, 0); + + if (me_rc_stats_cfm.sw_retry_step != 0) + { + len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len, "%c", + me_rc_stats_cfm.retry_step_idx[me_rc_stats_cfm.sw_retry_step] == i ? '*' : ' '); + } + else + { + len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len, " "); + } + len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len, "%c", + me_rc_stats_cfm.retry_step_idx[0] == i ? 'T' : ' '); + len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len, "%c", + me_rc_stats_cfm.retry_step_idx[1] == i ? 't' : ' '); + len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len, "%c ", + me_rc_stats_cfm.retry_step_idx[2] == i ? 'P' : ' '); + + tp = me_rc_stats_cfm.tp[i] / 10; + len += scnprintf(&st[i].line[len], LINE_MAX_SZ - len, " %4u.%1u", + tp / 10, tp % 10); + + eprob = ((me_rc_stats_cfm.rate_stats[i].probability * 1000) >> 16) + 1; + len += scnprintf(&st[i].line[len],LINE_MAX_SZ - len, + " %4u.%1u %5u(%6u) %6u", + eprob / 10, eprob % 10, + me_rc_stats_cfm.rate_stats[i].success, + me_rc_stats_cfm.rate_stats[i].attempts, + me_rc_stats_cfm.rate_stats[i].sample_skipped); + } + len = scnprintf(buf, bufsz , + "\nTX rate info for %02X:%02X:%02X:%02X:%02X:%02X:\n", + sta->mac_addr[0], sta->mac_addr[1], sta->mac_addr[2], + sta->mac_addr[3], sta->mac_addr[4], sta->mac_addr[5]); + + len += scnprintf(&buf[len], bufsz - len, + " # type rate tpt eprob ok( tot) skipped\n"); + + // add sorted statistics to the buffer + sort(st, no_samples, sizeof(st[0]), compare_idx, NULL); + for (i = 0; i < no_samples; i++) + { + len += scnprintf(&buf[len], bufsz - len, "%s\n", st[i].line); + } + + // display HE TB statistics if any + if (me_rc_stats_cfm.rate_stats[RC_HE_STATS_IDX].rate_config != 0) { + unsigned int tp, eprob; + struct rc_rate_stats *rate_stats = &me_rc_stats_cfm.rate_stats[RC_HE_STATS_IDX]; + int ru_index = rate_stats->ru_and_length & 0x07; + int ul_length = rate_stats->ru_and_length >> 3; + + len += scnprintf(&buf[len], bufsz - len, + "\nHE TB rate info:\n"); + + len += scnprintf(&buf[len], bufsz - len, + " type rate tpt eprob ok( tot) ul_length\n "); + len += print_rate_from_cfg(&buf[len], bufsz - len, rate_stats->rate_config, + NULL, ru_index); + + tp = me_rc_stats_cfm.tp[RC_HE_STATS_IDX] / 10; + len += scnprintf(&buf[len], bufsz - len, " %4u.%1u", + tp / 10, tp % 10); + + eprob = ((rate_stats->probability * 1000) >> 16) + 1; + len += scnprintf(&buf[len],bufsz - len, + " %4u.%1u %5u(%6u) %6u\n", + eprob / 10, eprob % 10, + rate_stats->success, + rate_stats->attempts, + ul_length); + } + + len += scnprintf(&buf[len], bufsz - len, "\n MPDUs AMPDUs AvLen trialP"); + len += scnprintf(&buf[len], bufsz - len, "\n%6u %6u %3d.%1d %6u\n", + me_rc_stats_cfm.ampdu_len, + me_rc_stats_cfm.ampdu_packets, + me_rc_stats_cfm.avg_ampdu_len >> 16, + ((me_rc_stats_cfm.avg_ampdu_len * 10) >> 16) % 10, + me_rc_stats_cfm.sample_wait); + + read = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + kfree(buf); + kfree(st); + + return read; +} + +DEBUGFS_READ_FILE_OPS(rc_stats); + +static ssize_t ecrnx_dbgfs_rc_fixed_rate_idx_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_sta *sta = NULL; + struct ecrnx_hw *priv = file->private_data; + char buf[10]; + int fixed_rate_idx = -1; + union ecrnx_rate_ctrl_info rate_config; + int error = 0; + size_t len = min_t(size_t, count, sizeof(buf) - 1); + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Get the station index from MAC address */ + sta = ecrnx_dbgfs_get_sta(priv, file->f_path.dentry->d_parent->d_parent->d_iname); + if (sta == NULL) + return -EINVAL; + + /* Get the content of the file */ + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + buf[len] = '\0'; + sscanf(buf, "%i\n", &fixed_rate_idx); + + /* Convert rate index into rate configuration */ + if ((fixed_rate_idx < 0) || (fixed_rate_idx >= (N_CCK + N_OFDM + N_HT + N_VHT + N_HE_SU))) + { + // disable fixed rate + rate_config.value = (u32)-1; + } + else + { + idx_to_rate_cfg(fixed_rate_idx, &rate_config, NULL); + } + + // Forward the request to the LMAC + if ((error = ecrnx_send_me_rc_set_rate(priv, sta->sta_idx, + (u16)rate_config.value)) != 0) + { + return error; + } + + priv->debugfs.rc_config[sta->sta_idx] = (int)rate_config.value; + return len; +} + +DEBUGFS_WRITE_FILE_OPS(rc_fixed_rate_idx); + +static ssize_t ecrnx_dbgfs_last_rx_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_sta *sta = NULL; + struct ecrnx_hw *priv = file->private_data; + struct ecrnx_rx_rate_stats *rate_stats; + char *buf; + int bufsz, i, len = 0; + ssize_t read; + unsigned int fmt, pre, bw, nss, mcs, gi, dcm = 0; + struct rx_vector_1 *last_rx; + char hist[] = "##################################################"; + int hist_len = sizeof(hist) - 1; + u8 nrx; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* everything should fit in one call */ + if (*ppos) + return 0; + + /* Get the station index from MAC address */ + sta = ecrnx_dbgfs_get_sta(priv, file->f_path.dentry->d_parent->d_parent->d_iname); + if (sta == NULL) + return -EINVAL; + + rate_stats = &sta->stats.rx_rate; + bufsz = (rate_stats->rate_cnt * ( 50 + hist_len) + 200); + buf = kmalloc(bufsz + 1, GFP_ATOMIC); + if (buf == NULL) + return 0; + + // Get number of RX paths + nrx = (priv->version_cfm.version_phy_1 & MDM_NRX_MASK) >> MDM_NRX_LSB; + + len += scnprintf(buf, bufsz, + "\nRX rate info for %02X:%02X:%02X:%02X:%02X:%02X:\n", + sta->mac_addr[0], sta->mac_addr[1], sta->mac_addr[2], + sta->mac_addr[3], sta->mac_addr[4], sta->mac_addr[5]); + + // Display Statistics + for (i = 0 ; i < rate_stats->size ; i++ ) + { + if (rate_stats->table[i]) { + union ecrnx_rate_ctrl_info rate_config; + int percent = ((/*(u64)*/rate_stats->table[i]) * 1000) / rate_stats->cpt; + int p; + int ru_size; + + idx_to_rate_cfg(i, &rate_config, &ru_size); + len += print_rate_from_cfg(&buf[len], bufsz - len, + rate_config.value, NULL, ru_size); + p = (percent * hist_len) / 1000; + len += scnprintf(&buf[len], bufsz - len, ": %9d(%2d.%1d%%)%.*s\n", + rate_stats->table[i], + percent / 10, percent % 10, p, hist); + } + } + + // Display detailed info of the last received rate + last_rx = &sta->stats.last_rx.rx_vect1; + + len += scnprintf(&buf[len], bufsz - len,"\nLast received rate\n" + " type rate LDPC STBC BEAMFM DCM DOPPLER %s\n", + (nrx > 1) ? "rssi1(dBm) rssi2(dBm)" : "rssi(dBm)"); + + fmt = last_rx->format_mod; + bw = last_rx->ch_bw; + pre = last_rx->pre_type; + if (fmt >= FORMATMOD_HE_SU) { + mcs = last_rx->he.mcs; + nss = last_rx->he.nss; + gi = last_rx->he.gi_type; + if (fmt == FORMATMOD_HE_MU) + bw = last_rx->he.ru_size; + dcm = last_rx->he.dcm; + } else if (fmt == FORMATMOD_VHT) { + mcs = last_rx->vht.mcs; + nss = last_rx->vht.nss; + gi = last_rx->vht.short_gi; + } else if (fmt >= FORMATMOD_HT_MF) { + mcs = last_rx->ht.mcs % 8; + nss = last_rx->ht.mcs / 8;; + gi = last_rx->ht.short_gi; + } else { + BUG_ON((mcs = legrates_lut[last_rx->leg_rate].idx) == -1); + nss = 0; + gi = 0; + } + + len += print_rate(&buf[len], bufsz - len, fmt, nss, mcs, bw, gi, pre, dcm, NULL); + + /* flags for HT/VHT/HE */ + if (fmt >= FORMATMOD_HE_SU) { + len += scnprintf(&buf[len], bufsz - len, " %c %c %c %c %c", + last_rx->he.fec ? 'L' : ' ', + last_rx->he.stbc ? 'S' : ' ', + last_rx->he.beamformed ? 'B' : ' ', + last_rx->he.dcm ? 'D' : ' ', + last_rx->he.doppler ? 'D' : ' '); + } else if (fmt == FORMATMOD_VHT) { + len += scnprintf(&buf[len], bufsz - len, " %c %c %c ", + last_rx->vht.fec ? 'L' : ' ', + last_rx->vht.stbc ? 'S' : ' ', + last_rx->vht.beamformed ? 'B' : ' '); + } else if (fmt >= FORMATMOD_HT_MF) { + len += scnprintf(&buf[len], bufsz - len, " %c %c ", + last_rx->ht.fec ? 'L' : ' ', + last_rx->ht.stbc ? 'S' : ' '); + } else { + len += scnprintf(&buf[len], bufsz - len, " "); + } + if (nrx > 1) { + len += scnprintf(&buf[len], bufsz - len, " %-4d %d\n", + last_rx->rssi1, last_rx->rssi1); + } else { + len += scnprintf(&buf[len], bufsz - len, " %d\n", last_rx->rssi1); + } + + read = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + kfree(buf); + return read; +} + +static ssize_t ecrnx_dbgfs_last_rx_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_sta *sta = NULL; + struct ecrnx_hw *priv = file->private_data; + + /* Get the station index from MAC address */ + sta = ecrnx_dbgfs_get_sta(priv, file->f_path.dentry->d_parent->d_parent->d_iname); + if (sta == NULL) + return -EINVAL; + + /* Prevent from interrupt preemption as these statistics are updated under + * interrupt */ + spin_lock_bh(&priv->tx_lock); + memset(sta->stats.rx_rate.table, 0, + sta->stats.rx_rate.size * sizeof(sta->stats.rx_rate.table[0])); + sta->stats.rx_rate.cpt = 0; + sta->stats.rx_rate.rate_cnt = 0; + spin_unlock_bh(&priv->tx_lock); + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(last_rx); + +#endif /* CONFIG_ECRNX_FULLMAC */ + +/* + * trace helper + */ +void ecrnx_fw_trace_dump(struct ecrnx_hw *ecrnx_hw) +{ +#ifndef CONFIG_ECRNX_ESWIN + /* may be called before ecrnx_dbgfs_register */ + if (ecrnx_hw->plat->enabled && !ecrnx_hw->debugfs.fw_trace.buf.data) { + ecrnx_fw_trace_buf_init(&ecrnx_hw->debugfs.fw_trace.buf, + ecrnx_ipc_fw_trace_desc_get(ecrnx_hw)); + } + + if (!ecrnx_hw->debugfs.fw_trace.buf.data) + return; + + _ecrnx_fw_trace_dump(&ecrnx_hw->debugfs.fw_trace.buf); +#endif +} + +void ecrnx_fw_trace_reset(struct ecrnx_hw *ecrnx_hw) +{ + _ecrnx_fw_trace_reset(&ecrnx_hw->debugfs.fw_trace, true); +} + +void ecrnx_dbgfs_trigger_fw_dump(struct ecrnx_hw *ecrnx_hw, char *reason) +{ + ecrnx_send_dbg_trigger_req(ecrnx_hw, reason); +} + +#ifdef CONFIG_ECRNX_FULLMAC +static void _ecrnx_dbgfs_register_sta(struct ecrnx_debugfs *ecrnx_debugfs, struct ecrnx_sta *sta) +{ + struct ecrnx_hw *ecrnx_hw = container_of(ecrnx_debugfs, struct ecrnx_hw, debugfs); + struct dentry *dir_sta; + char sta_name[18]; + struct dentry *dir_rc; + struct dentry *file; + struct ecrnx_rx_rate_stats *rate_stats = &sta->stats.rx_rate; + int nb_rx_rate = N_CCK + N_OFDM; + struct ecrnx_rc_config_save *rc_cfg, *next; + + if (sta->sta_idx >= NX_REMOTE_STA_MAX) { + scnprintf(sta_name, sizeof(sta_name), "bc_mc"); + } else { + scnprintf(sta_name, sizeof(sta_name), "%pM", sta->mac_addr); + } + + if (!(dir_sta = debugfs_create_dir(sta_name, ecrnx_debugfs->dir_stas))) + goto error; + ecrnx_debugfs->dir_sta[sta->sta_idx] = dir_sta; + + if (!(dir_rc = debugfs_create_dir("rc", ecrnx_debugfs->dir_sta[sta->sta_idx]))) + goto error_after_dir; + + ecrnx_debugfs->dir_rc_sta[sta->sta_idx] = dir_rc; + + file = debugfs_create_file("stats", S_IRUSR, dir_rc, ecrnx_hw, + &ecrnx_dbgfs_rc_stats_ops); + if (IS_ERR_OR_NULL(file)) + goto error_after_dir; + + file = debugfs_create_file("fixed_rate_idx", S_IWUSR , dir_rc, ecrnx_hw, + &ecrnx_dbgfs_rc_fixed_rate_idx_ops); + if (IS_ERR_OR_NULL(file)) + goto error_after_dir; + + file = debugfs_create_file("rx_rate", S_IRUSR | S_IWUSR, dir_rc, ecrnx_hw, + &ecrnx_dbgfs_last_rx_ops); + if (IS_ERR_OR_NULL(file)) + goto error_after_dir; + + if (ecrnx_hw->mod_params->ht_on) + nb_rx_rate += N_HT; + + if (ecrnx_hw->mod_params->vht_on) + nb_rx_rate += N_VHT; + + if (ecrnx_hw->mod_params->he_on) + nb_rx_rate += N_HE_SU + N_HE_MU; + + rate_stats->table = kzalloc(nb_rx_rate * sizeof(rate_stats->table[0]), + GFP_ATOMIC); + if (!rate_stats->table) + goto error_after_dir; + + rate_stats->size = nb_rx_rate; + rate_stats->cpt = 0; + rate_stats->rate_cnt = 0; + + /* By default enable rate contoller */ + ecrnx_debugfs->rc_config[sta->sta_idx] = -1; + + /* Unless we already fix the rate for this station */ + list_for_each_entry_safe(rc_cfg, next, &ecrnx_debugfs->rc_config_save, list) { + if (jiffies_to_msecs(jiffies - rc_cfg->timestamp) > RC_CONFIG_DUR) { + list_del(&rc_cfg->list); + kfree(rc_cfg); + } else if (!memcmp(rc_cfg->mac_addr, sta->mac_addr, ETH_ALEN)) { + ecrnx_debugfs->rc_config[sta->sta_idx] = rc_cfg->rate; + list_del(&rc_cfg->list); + kfree(rc_cfg); + break; + } + } + + if ((ecrnx_debugfs->rc_config[sta->sta_idx] >= 0) && + ecrnx_send_me_rc_set_rate(ecrnx_hw, sta->sta_idx, + (u16)ecrnx_debugfs->rc_config[sta->sta_idx])) + ecrnx_debugfs->rc_config[sta->sta_idx] = -1; + + if (ECRNX_VIF_TYPE(ecrnx_hw->vif_table[sta->vif_idx]) == NL80211_IFTYPE_STATION) + { + /* register the sta */ + struct dentry *dir_twt; + struct dentry *file; + + if (!(dir_twt = debugfs_create_dir("twt", ecrnx_debugfs->dir_sta[sta->sta_idx]))) + goto error_after_dir; + + ecrnx_debugfs->dir_twt_sta[sta->sta_idx] = dir_twt; + + file = debugfs_create_file("request", S_IRUSR | S_IWUSR, dir_twt, ecrnx_hw, + &ecrnx_dbgfs_twt_request_ops); + if (IS_ERR_OR_NULL(file)) + goto error_after_dir; + + file = debugfs_create_file("teardown", S_IRUSR | S_IWUSR, dir_twt, ecrnx_hw, + &ecrnx_dbgfs_twt_teardown_ops); + if (IS_ERR_OR_NULL(file)) + goto error_after_dir; + + sta->twt_ind.sta_idx = ECRNX_INVALID_STA; + } + return; + + error_after_dir: + debugfs_remove_recursive(ecrnx_debugfs->dir_sta[sta->sta_idx]); + ecrnx_debugfs->dir_sta[sta->sta_idx] = NULL; + ecrnx_debugfs->dir_rc_sta[sta->sta_idx] = NULL; + ecrnx_debugfs->dir_twt_sta[sta->sta_idx] = NULL; + error: + dev_err(ecrnx_hw->dev, + "Error while registering debug entry for sta %d\n", sta->sta_idx); +} + +static void _ecrnx_dbgfs_unregister_sta(struct ecrnx_debugfs *ecrnx_debugfs, struct ecrnx_sta *sta) +{ + debugfs_remove_recursive(ecrnx_debugfs->dir_sta[sta->sta_idx]); + /* unregister the sta */ + if (sta->stats.rx_rate.table) { + kfree(sta->stats.rx_rate.table); + sta->stats.rx_rate.table = NULL; + } + sta->stats.rx_rate.size = 0; + sta->stats.rx_rate.cpt = 0; + sta->stats.rx_rate.rate_cnt = 0; + + /* If fix rate was set for this station, save the configuration in case + we reconnect to this station within RC_CONFIG_DUR msec */ + if (ecrnx_debugfs->rc_config[sta->sta_idx] >= 0) { + struct ecrnx_rc_config_save *rc_cfg; + rc_cfg = kmalloc(sizeof(*rc_cfg), GFP_ATOMIC); + if (rc_cfg) { + rc_cfg->rate = ecrnx_debugfs->rc_config[sta->sta_idx]; + rc_cfg->timestamp = jiffies; + memcpy(rc_cfg->mac_addr, sta->mac_addr, ETH_ALEN); + list_add_tail(&rc_cfg->list, &ecrnx_debugfs->rc_config_save); + } + } + + ecrnx_debugfs->dir_sta[sta->sta_idx] = NULL; + ecrnx_debugfs->dir_rc_sta[sta->sta_idx] = NULL; + ecrnx_debugfs->dir_twt_sta[sta->sta_idx] = NULL; + sta->twt_ind.sta_idx = ECRNX_INVALID_STA; +} + +static void ecrnx_sta_work(struct work_struct *ws) +{ + struct ecrnx_debugfs *ecrnx_debugfs = container_of(ws, struct ecrnx_debugfs, sta_work); + struct ecrnx_hw *ecrnx_hw = container_of(ecrnx_debugfs, struct ecrnx_hw, debugfs); + struct ecrnx_sta *sta; + uint8_t sta_idx; + + sta_idx = ecrnx_debugfs->sta_idx; + if (sta_idx > (NX_REMOTE_STA_MAX + NX_VIRT_DEV_MAX)) { + WARN(1, "Invalid sta index %d", sta_idx); + return; + } + + ecrnx_debugfs->sta_idx = ECRNX_INVALID_STA; + sta = &ecrnx_hw->sta_table[sta_idx]; + if (!sta) { + WARN(1, "Invalid sta %d", sta_idx); + return; + } + + if (ecrnx_debugfs->dir_sta[sta_idx] == NULL) + _ecrnx_dbgfs_register_sta(ecrnx_debugfs, sta); + else + _ecrnx_dbgfs_unregister_sta(ecrnx_debugfs, sta); + + return; +} + +void _ecrnx_dbgfs_sta_write(struct ecrnx_debugfs *ecrnx_debugfs, uint8_t sta_idx) +{ + if (ecrnx_debugfs->unregistering) + return; + + ecrnx_debugfs->sta_idx = sta_idx; + schedule_work(&ecrnx_debugfs->sta_work); +} + +void ecrnx_dbgfs_unregister_sta(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_sta *sta) +{ + _ecrnx_dbgfs_sta_write(&ecrnx_hw->debugfs, sta->sta_idx); +} + +void ecrnx_dbgfs_register_sta(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_sta *sta) +{ + _ecrnx_dbgfs_sta_write(&ecrnx_hw->debugfs, sta->sta_idx); +} +#endif /* CONFIG_ECRNX_FULLMAC */ + +int ecrnx_dbgfs_register(struct ecrnx_hw *ecrnx_hw, const char *name) +{ +#ifdef CONFIG_ECRNX_SOFTMAC + struct dentry *phyd = ecrnx_hw->hw->wiphy->debugfsdir; +#else + struct dentry *phyd = ecrnx_hw->wiphy->debugfsdir; +#endif /* CONFIG_ECRNX_SOFTMAC */ + struct ecrnx_debugfs *ecrnx_debugfs = &ecrnx_hw->debugfs; + struct dentry *dir_drv, *dir_diags, *dir_stas; + + if (!(dir_drv = debugfs_create_dir(name, phyd))) + return -ENOMEM; + + ecrnx_debugfs->dir = dir_drv; + + if (!(dir_stas = debugfs_create_dir("stations", dir_drv))) + return -ENOMEM; + + ecrnx_debugfs->dir_stas = dir_stas; + ecrnx_debugfs->unregistering = false; + + if (!(dir_diags = debugfs_create_dir("diags", dir_drv))) + goto err; + +#ifdef CONFIG_ECRNX_FULLMAC + INIT_WORK(&ecrnx_debugfs->sta_work, ecrnx_sta_work); + INIT_LIST_HEAD(&ecrnx_debugfs->rc_config_save); + ecrnx_debugfs->sta_idx = ECRNX_INVALID_STA; +#endif + + DEBUGFS_ADD_U32(tcp_pacing_shift, dir_drv, &ecrnx_hw->tcp_pacing_shift, + S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(stats, dir_drv, S_IWUSR | S_IRUSR); +#if 0 + DEBUGFS_ADD_FILE(sys_stats, dir_drv, S_IRUSR); +#endif +#ifdef CONFIG_ECRNX_SOFTMAC + DEBUGFS_ADD_X64(rateidx, dir_drv, &ecrnx_hw->debugfs.rateidx); +#endif + DEBUGFS_ADD_FILE(txq, dir_drv, S_IRUSR); + DEBUGFS_ADD_FILE(acsinfo, dir_drv, S_IRUSR); +#ifdef CONFIG_ECRNX_MUMIMO_TX + DEBUGFS_ADD_FILE(mu_group, dir_drv, S_IRUSR); +#endif + +#ifdef CONFIG_ECRNX_P2P_DEBUGFS + { + /* Create a p2p directory */ + struct dentry *dir_p2p; + if (!(dir_p2p = debugfs_create_dir("p2p", dir_drv))) + goto err; + + /* Add file allowing to control Opportunistic PS */ + DEBUGFS_ADD_FILE(oppps, dir_p2p, S_IRUSR); + /* Add file allowing to control Notice of Absence */ + DEBUGFS_ADD_FILE(noa, dir_p2p, S_IRUSR); + } +#endif /* CONFIG_ECRNX_P2P_DEBUGFS */ + +#if CONFIG_ECRNX_DBGFS_FW_TRACE + if (ecrnx_dbgfs_register_fw_dump(ecrnx_hw, dir_drv, dir_diags)) + goto err; + DEBUGFS_ADD_FILE(fw_dbg, dir_diags, S_IWUSR | S_IRUSR); + + if (!ecrnx_fw_trace_init(&ecrnx_hw->debugfs.fw_trace, + ecrnx_ipc_fw_trace_desc_get(ecrnx_hw))) { + DEBUGFS_ADD_FILE(fw_trace, dir_diags, S_IWUSR | S_IRUSR); + if (ecrnx_hw->debugfs.fw_trace.buf.nb_compo) + DEBUGFS_ADD_FILE(fw_trace_level, dir_diags, S_IWUSR | S_IRUSR); + } else { + ecrnx_debugfs->fw_trace.buf.data = NULL; + } +#endif + +#ifdef CONFIG_ECRNX_RADAR + { + struct dentry *dir_radar, *dir_sec; + if (!(dir_radar = debugfs_create_dir("radar", dir_drv))) + goto err; + + DEBUGFS_ADD_FILE(pulses_prim, dir_radar, S_IRUSR); + DEBUGFS_ADD_FILE(detected, dir_radar, S_IRUSR); + DEBUGFS_ADD_FILE(enable, dir_radar, S_IRUSR); + + if (ecrnx_hw->phy.cnt == 2) { + DEBUGFS_ADD_FILE(pulses_sec, dir_radar, S_IRUSR); + + if (!(dir_sec = debugfs_create_dir("sec", dir_radar))) + goto err; + + DEBUGFS_ADD_FILE(band, dir_sec, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(type, dir_sec, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(prim20, dir_sec, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(center1, dir_sec, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(center2, dir_sec, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(set, dir_sec, S_IWUSR | S_IRUSR); + } + } +#endif /* CONFIG_ECRNX_RADAR */ + return 0; + +err: + ecrnx_dbgfs_unregister(ecrnx_hw); + return -ENOMEM; +} + +void ecrnx_dbgfs_unregister(struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_debugfs *ecrnx_debugfs = &ecrnx_hw->debugfs; + +#ifdef CONFIG_ECRNX_FULLMAC + struct ecrnx_rc_config_save *cfg, *next; + list_for_each_entry_safe(cfg, next, &ecrnx_debugfs->rc_config_save, list) { + list_del(&cfg->list); + kfree(cfg); + } +#endif /* CONFIG_ECRNX_FULLMAC */ + + if (!ecrnx_hw->debugfs.dir) + return; + + spin_lock_bh(&ecrnx_debugfs->umh_lock); + ecrnx_debugfs->unregistering = true; + spin_unlock_bh(&ecrnx_debugfs->umh_lock); + ecrnx_wait_um_helper(ecrnx_hw); +#if CONFIG_ECRNX_DBGFS_FW_TRACE + ecrnx_fw_trace_deinit(&ecrnx_hw->debugfs.fw_trace); +#endif +#ifdef CONFIG_ECRNX_FULLMAC + flush_work(&ecrnx_debugfs->sta_work); +#endif + debugfs_remove_recursive(ecrnx_hw->debugfs.dir); + ecrnx_hw->debugfs.dir = NULL; +} + diff --git a/drivers/net/wireless/eswin/ecrnx_debugfs.h b/drivers/net/wireless/eswin/ecrnx_debugfs.h new file mode 100644 index 000000000000..da0338e12e2e --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_debugfs.h @@ -0,0 +1,198 @@ +/** + ****************************************************************************** + * + * @file ecrnx_debugfs.h + * + * @brief Miscellaneous utility function definitions + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + + +#ifndef _ECRNX_DEBUGFS_H_ +#define _ECRNX_DEBUGFS_H_ + +#include <linux/workqueue.h> +#include <linux/if_ether.h> +#include "ecrnx_fw_trace.h" + +struct ecrnx_hw; +struct ecrnx_sta; + +#define DEBUGFS_ADD_FILE(name, parent, mode) do { \ + struct dentry *__tmp; \ + __tmp = debugfs_create_file(#name, mode, parent, ecrnx_hw, \ + &ecrnx_dbgfs_##name##_ops); \ + if (IS_ERR_OR_NULL(__tmp)) \ + goto err; \ + } while (0) + +#define DEBUGFS_ADD_BOOL(name, parent, ptr) do { \ + struct dentry *__tmp; \ + __tmp = debugfs_create_bool(#name, S_IWUSR | S_IRUSR, parent, ptr); \ + if (IS_ERR_OR_NULL(__tmp)) \ + goto err; \ + } while (0) + +#define DEBUGFS_ADD_X64(name, parent, ptr) do { \ + debugfs_create_x64(#name, S_IWUSR | S_IRUSR,parent, ptr); \ + } while (0) + +#define DEBUGFS_ADD_U64(name, parent, ptr, mode) do { \ + debugfs_create_u64(#name, mode, parent, ptr); \ + } while (0) + +#define DEBUGFS_ADD_X32(name, parent, ptr) do { \ + debugfs_create_x32(#name, S_IWUSR | S_IRUSR, parent, ptr); \ + } while (0) + +#define DEBUGFS_ADD_U32(name, parent, ptr, mode) do { \ + debugfs_create_u32(#name, mode, parent, ptr); \ + } while (0) + + +/* file operation */ +#define DEBUGFS_READ_FUNC(name) \ + static ssize_t ecrnx_dbgfs_##name##_read(struct file *file, \ + char __user *user_buf, \ + size_t count, loff_t *ppos); + +#define DEBUGFS_WRITE_FUNC(name) \ + static ssize_t ecrnx_dbgfs_##name##_write(struct file *file, \ + const char __user *user_buf,\ + size_t count, loff_t *ppos); + +#define DEBUGFS_OPEN_FUNC(name) \ + static int ecrnx_dbgfs_##name##_open(struct inode *inode, \ + struct file *file); + +#define DEBUGFS_RELEASE_FUNC(name) \ + static int ecrnx_dbgfs_##name##_release(struct inode *inode, \ + struct file *file); + +#define DEBUGFS_READ_FILE_OPS(name) \ + DEBUGFS_READ_FUNC(name); \ +static const struct file_operations ecrnx_dbgfs_##name##_ops = { \ + .read = ecrnx_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_WRITE_FILE_OPS(name) \ + DEBUGFS_WRITE_FUNC(name); \ +static const struct file_operations ecrnx_dbgfs_##name##_ops = { \ + .write = ecrnx_dbgfs_##name##_write, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_READ_WRITE_FILE_OPS(name) \ + DEBUGFS_READ_FUNC(name); \ + DEBUGFS_WRITE_FUNC(name); \ +static const struct file_operations ecrnx_dbgfs_##name##_ops = { \ + .write = ecrnx_dbgfs_##name##_write, \ + .read = ecrnx_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_READ_WRITE_OPEN_RELEASE_FILE_OPS(name) \ + DEBUGFS_READ_FUNC(name); \ + DEBUGFS_WRITE_FUNC(name); \ + DEBUGFS_OPEN_FUNC(name); \ + DEBUGFS_RELEASE_FUNC(name); \ +static const struct file_operations ecrnx_dbgfs_##name##_ops = { \ + .write = ecrnx_dbgfs_##name##_write, \ + .read = ecrnx_dbgfs_##name##_read, \ + .open = ecrnx_dbgfs_##name##_open, \ + .release = ecrnx_dbgfs_##name##_release, \ + .llseek = generic_file_llseek, \ +}; + + +#ifdef CONFIG_ECRNX_DEBUGFS + +struct ecrnx_debugfs { + unsigned long long rateidx; + struct dentry *dir; + struct dentry *dir_stas; + bool trace_prst; + + char helper_cmd[64]; + struct work_struct helper_work; + bool helper_scheduled; + spinlock_t umh_lock; + bool unregistering; + +#ifndef CONFIG_ECRNX_FHOST + struct ecrnx_fw_trace fw_trace; +#endif /* CONFIG_ECRNX_FHOST */ + +#ifdef CONFIG_ECRNX_FULLMAC + struct work_struct sta_work; + struct dentry *dir_sta[NX_REMOTE_STA_MAX]; + uint8_t sta_idx; + struct dentry *dir_rc; + struct dentry *dir_rc_sta[NX_REMOTE_STA_MAX]; + int rc_config[NX_REMOTE_STA_MAX]; + struct list_head rc_config_save; + struct dentry *dir_twt; + struct dentry *dir_twt_sta[NX_REMOTE_STA_MAX]; +#endif +}; + +#ifdef CONFIG_ECRNX_FULLMAC + +// Max duration in msecs to save rate config for a sta after disconnection +#define RC_CONFIG_DUR 600000 + +struct ecrnx_rc_config_save { + struct list_head list; + unsigned long timestamp; + int rate; + u8 mac_addr[ETH_ALEN]; +}; +#endif + +int ecrnx_dbgfs_register(struct ecrnx_hw *ecrnx_hw, const char *name); +void ecrnx_dbgfs_unregister(struct ecrnx_hw *ecrnx_hw); +int ecrnx_um_helper(struct ecrnx_debugfs *ecrnx_debugfs, const char *cmd); +int ecrnx_trigger_um_helper(struct ecrnx_debugfs *ecrnx_debugfs); +void ecrnx_wait_um_helper(struct ecrnx_hw *ecrnx_hw); +#ifdef CONFIG_ECRNX_FULLMAC +void ecrnx_dbgfs_register_sta(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta); +void ecrnx_dbgfs_unregister_sta(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta); +#endif + +int ecrnx_dbgfs_register_fw_dump(struct ecrnx_hw *ecrnx_hw, + struct dentry *dir_drv, + struct dentry *dir_diags); +void ecrnx_dbgfs_trigger_fw_dump(struct ecrnx_hw *ecrnx_hw, char *reason); + +void ecrnx_fw_trace_dump(struct ecrnx_hw *ecrnx_hw); +void ecrnx_fw_trace_reset(struct ecrnx_hw *ecrnx_hw); + +#else + +struct ecrnx_debugfs { +}; + +static inline int ecrnx_dbgfs_register(struct ecrnx_hw *ecrnx_hw, const char *name) { return 0; } +static inline void ecrnx_dbgfs_unregister(struct ecrnx_hw *ecrnx_hw) {} +static inline int ecrnx_um_helper(struct ecrnx_debugfs *ecrnx_debugfs, const char *cmd) { return 0; } +static inline int ecrnx_trigger_um_helper(struct ecrnx_debugfs *ecrnx_debugfs) {return 0;} +static inline void ecrnx_wait_um_helper(struct ecrnx_hw *ecrnx_hw) {} +#ifdef CONFIG_ECRNX_FULLMAC +static inline void ecrnx_dbgfs_register_sta(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta) {} +static inline void ecrnx_dbgfs_unregister_sta(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta) {} +#endif + +static inline void ecrnx_fw_trace_dump(struct ecrnx_hw *ecrnx_hw) {} +static inline void ecrnx_fw_trace_reset(struct ecrnx_hw *ecrnx_hw) {} + +#endif /* CONFIG_ECRNX_DEBUGFS */ + + +#endif /* _ECRNX_DEBUGFS_H_ */ diff --git a/drivers/net/wireless/eswin/ecrnx_events.h b/drivers/net/wireless/eswin/ecrnx_events.h new file mode 100644 index 000000000000..32f19f22e612 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_events.h @@ -0,0 +1,1308 @@ +/** + ****************************************************************************** + * + * @file ecrnx_events.h + * + * @brief Trace events definition + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM ecrnx + +#if !defined(_ECRNX_EVENTS_H) || defined(TRACE_HEADER_MULTI_READ) +#define _ECRNX_EVENTS_H + +#include <linux/tracepoint.h> +#ifndef CONFIG_ECRNX_FHOST +#include "ecrnx_tx.h" +#endif +#include "ecrnx_compat.h" + +/***************************************************************************** + * TRACE function for MGMT TX (FULLMAC) + ****************************************************************************/ +#ifdef CONFIG_ECRNX_FULLMAC +#include "linux/ieee80211.h" +#if defined(CONFIG_TRACEPOINTS) && defined(CREATE_TRACE_POINTS) +#include <linux/trace_seq.h> + +/* P2P Public Action Frames Definitions (see WiFi P2P Technical Specification, section 4.2.8) */ +/* IEEE 802.11 Public Action Usage Category - Define P2P public action frames */ +#define MGMT_ACTION_PUBLIC_CAT (0x04) +/* Offset of OUI Subtype field in P2P Action Frame format */ +#define MGMT_ACTION_OUI_SUBTYPE_OFFSET (6) +/* P2P Public Action Frame Types */ +enum p2p_action_type { + P2P_ACTION_GO_NEG_REQ = 0, /* GO Negociation Request */ + P2P_ACTION_GO_NEG_RSP, /* GO Negociation Response */ + P2P_ACTION_GO_NEG_CFM, /* GO Negociation Confirmation */ + P2P_ACTION_INVIT_REQ, /* P2P Invitation Request */ + P2P_ACTION_INVIT_RSP, /* P2P Invitation Response */ + P2P_ACTION_DEV_DISC_REQ, /* Device Discoverability Request */ + P2P_ACTION_DEV_DISC_RSP, /* Device Discoverability Response */ + P2P_ACTION_PROV_DISC_REQ, /* Provision Discovery Request */ + P2P_ACTION_PROV_DISC_RSP, /* Provision Discovery Response */ +}; + +const char *ftrace_print_mgmt_info(struct trace_seq *p, u16 frame_control, u8 cat, u8 type, u8 p2p) { + const char *ret = trace_seq_buffer_ptr(p); + + switch (frame_control & IEEE80211_FCTL_STYPE) { + case (IEEE80211_STYPE_ASSOC_REQ): trace_seq_printf(p, "Association Request"); break; + case (IEEE80211_STYPE_ASSOC_RESP): trace_seq_printf(p, "Association Response"); break; + case (IEEE80211_STYPE_REASSOC_REQ): trace_seq_printf(p, "Reassociation Request"); break; + case (IEEE80211_STYPE_REASSOC_RESP): trace_seq_printf(p, "Reassociation Response"); break; + case (IEEE80211_STYPE_PROBE_REQ): trace_seq_printf(p, "Probe Request"); break; + case (IEEE80211_STYPE_PROBE_RESP): trace_seq_printf(p, "Probe Response"); break; + case (IEEE80211_STYPE_BEACON): trace_seq_printf(p, "Beacon"); break; + case (IEEE80211_STYPE_ATIM): trace_seq_printf(p, "ATIM"); break; + case (IEEE80211_STYPE_DISASSOC): trace_seq_printf(p, "Disassociation"); break; + case (IEEE80211_STYPE_AUTH): trace_seq_printf(p, "Authentication"); break; + case (IEEE80211_STYPE_DEAUTH): trace_seq_printf(p, "Deauthentication"); break; + case (IEEE80211_STYPE_ACTION): + trace_seq_printf(p, "Action"); + if (cat == MGMT_ACTION_PUBLIC_CAT && type == 0x9) + switch (p2p) { + case (P2P_ACTION_GO_NEG_REQ): trace_seq_printf(p, ": GO Negociation Request"); break; + case (P2P_ACTION_GO_NEG_RSP): trace_seq_printf(p, ": GO Negociation Response"); break; + case (P2P_ACTION_GO_NEG_CFM): trace_seq_printf(p, ": GO Negociation Confirmation"); break; + case (P2P_ACTION_INVIT_REQ): trace_seq_printf(p, ": P2P Invitation Request"); break; + case (P2P_ACTION_INVIT_RSP): trace_seq_printf(p, ": P2P Invitation Response"); break; + case (P2P_ACTION_DEV_DISC_REQ): trace_seq_printf(p, ": Device Discoverability Request"); break; + case (P2P_ACTION_DEV_DISC_RSP): trace_seq_printf(p, ": Device Discoverability Response"); break; + case (P2P_ACTION_PROV_DISC_REQ): trace_seq_printf(p, ": Provision Discovery Request"); break; + case (P2P_ACTION_PROV_DISC_RSP): trace_seq_printf(p, ": Provision Discovery Response"); break; + default: trace_seq_printf(p, "Unknown p2p %d", p2p); break; + } + else { + switch (cat) { + case 0: trace_seq_printf(p, ":Spectrum %d", type); break; + case 1: trace_seq_printf(p, ":QOS %d", type); break; + case 2: trace_seq_printf(p, ":DLS %d", type); break; + case 3: trace_seq_printf(p, ":BA %d", type); break; + case 4: trace_seq_printf(p, ":Public %d", type); break; + case 5: trace_seq_printf(p, ":Radio Measure %d", type); break; + case 6: trace_seq_printf(p, ":Fast BSS %d", type); break; + case 7: trace_seq_printf(p, ":HT Action %d", type); break; + case 8: trace_seq_printf(p, ":SA Query %d", type); break; + case 9: trace_seq_printf(p, ":Protected Public %d", type); break; + case 10: trace_seq_printf(p, ":WNM %d", type); break; + case 11: trace_seq_printf(p, ":Unprotected WNM %d", type); break; + case 12: trace_seq_printf(p, ":TDLS %d", type); break; + case 13: trace_seq_printf(p, ":Mesh %d", type); break; + case 14: trace_seq_printf(p, ":MultiHop %d", type); break; + case 15: trace_seq_printf(p, ":Self Protected %d", type); break; + case 126: trace_seq_printf(p, ":Vendor protected"); break; + case 127: trace_seq_printf(p, ":Vendor"); break; + default: trace_seq_printf(p, ":Unknown category %d", cat); break; + } + } + break; + default: trace_seq_printf(p, "Unknown subtype %d", frame_control & IEEE80211_FCTL_STYPE); break; + } + + trace_seq_putc(p, 0); + + return ret; +} +#endif /* defined(CONFIG_TRACEPOINTS) && defined(CREATE_TRACE_POINTS) */ + +#undef __print_mgmt_info +#define __print_mgmt_info(frame_control, cat, type, p2p) ftrace_print_mgmt_info(p, frame_control, cat, type, p2p) + +TRACE_EVENT( + roc, + TP_PROTO(u8 vif_idx, u16 freq, unsigned int duration), + TP_ARGS(vif_idx, freq, duration), + TP_STRUCT__entry( + __field(u8, vif_idx) + __field(u16, freq) + __field(unsigned int, duration) + ), + TP_fast_assign( + __entry->vif_idx = vif_idx; + __entry->freq = freq; + __entry->duration = duration; + ), + TP_printk("f=%d vif=%d dur=%d", + __entry->freq, __entry->vif_idx, __entry->duration) +); + +TRACE_EVENT( + cancel_roc, + TP_PROTO(u8 vif_idx), + TP_ARGS(vif_idx), + TP_STRUCT__entry( + __field(u8, vif_idx) + ), + TP_fast_assign( + __entry->vif_idx = vif_idx; + ), + TP_printk("vif=%d", __entry->vif_idx) +); + +TRACE_EVENT( + roc_exp, + TP_PROTO(u8 vif_idx), + TP_ARGS(vif_idx), + TP_STRUCT__entry( + __field(u8, vif_idx) + ), + TP_fast_assign( + __entry->vif_idx = vif_idx; + ), + TP_printk("vif=%d", __entry->vif_idx) +); + +TRACE_EVENT( + switch_roc, + TP_PROTO(u8 vif_idx), + TP_ARGS(vif_idx), + TP_STRUCT__entry( + __field(u8, vif_idx) + ), + TP_fast_assign( + __entry->vif_idx = vif_idx; + ), + TP_printk("vif=%d", __entry->vif_idx) +); + +DECLARE_EVENT_CLASS( + mgmt_template, + TP_PROTO(u16 freq, u8 vif_idx, u8 sta_idx, struct ieee80211_mgmt *mgmt), + TP_ARGS(freq, vif_idx, sta_idx, mgmt), + TP_STRUCT__entry( + __field(u16, freq) + __field(u8, vif_idx) + __field(u8, sta_idx) + __field(u16, frame_control) + __field(u8, action_cat) + __field(u8, action_type) + __field(u8, action_p2p) + ), + TP_fast_assign( + __entry->freq = freq; + __entry->vif_idx = vif_idx; + __entry->sta_idx = sta_idx; + __entry->frame_control = mgmt->frame_control; + __entry->action_cat = mgmt->u.action.category; + __entry->action_type = mgmt->u.action.u.wme_action.action_code; + __entry->action_p2p = *((u8 *)&mgmt->u.action.category + + MGMT_ACTION_OUI_SUBTYPE_OFFSET); + ), + TP_printk("f=%d vif=%d sta=%d -> %s", + __entry->freq, __entry->vif_idx, __entry->sta_idx, + __print_mgmt_info(__entry->frame_control, __entry->action_cat, + __entry->action_type, __entry->action_p2p)) +); + +DEFINE_EVENT(mgmt_template, mgmt_tx, + TP_PROTO(u16 freq, u8 vif_idx, u8 sta_idx, struct ieee80211_mgmt *mgmt), + TP_ARGS(freq, vif_idx, sta_idx, mgmt)); + +DEFINE_EVENT(mgmt_template, mgmt_rx, + TP_PROTO(u16 freq, u8 vif_idx, u8 sta_idx, struct ieee80211_mgmt *mgmt), + TP_ARGS(freq, vif_idx, sta_idx, mgmt)); + +TRACE_EVENT( + mgmt_cfm, + TP_PROTO(u8 vif_idx, u8 sta_idx, bool acked), + TP_ARGS(vif_idx, sta_idx, acked), + TP_STRUCT__entry( + __field(u8, vif_idx) + __field(u8, sta_idx) + __field(bool, acked) + ), + TP_fast_assign( + __entry->vif_idx = vif_idx; + __entry->sta_idx = sta_idx; + __entry->acked = acked; + ), + TP_printk("vif=%d sta=%d ack=%d", + __entry->vif_idx, __entry->sta_idx, __entry->acked) +); +#endif /* CONFIG_ECRNX_FULLMAC */ + +/***************************************************************************** + * TRACE function for TXQ + ****************************************************************************/ +#ifndef CONFIG_ECRNX_FHOST +#if defined(CONFIG_TRACEPOINTS) && defined(CREATE_TRACE_POINTS) + +#include <linux/trace_seq.h> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) +#include <linux/trace_events.h> +#else +#include <linux/ftrace_event.h> +#endif + +const char * +ftrace_print_txq(struct trace_seq *p, int txq_idx) { + const char *ret = trace_seq_buffer_ptr(p); + + if (txq_idx == TXQ_INACTIVE) { + trace_seq_printf(p, "[INACTIVE]"); + } else if (txq_idx < NX_FIRST_VIF_TXQ_IDX) { + trace_seq_printf(p, "[STA %d/%d]", + txq_idx / NX_NB_TXQ_PER_STA, + txq_idx % NX_NB_TXQ_PER_STA); +#ifdef CONFIG_ECRNX_FULLMAC + } else if (txq_idx < NX_FIRST_UNK_TXQ_IDX) { + trace_seq_printf(p, "[BC/MC %d]", + txq_idx - NX_FIRST_BCMC_TXQ_IDX); + } else if (txq_idx < NX_OFF_CHAN_TXQ_IDX) { + trace_seq_printf(p, "[UNKNOWN %d]", + txq_idx - NX_FIRST_UNK_TXQ_IDX); + } else if (txq_idx == NX_OFF_CHAN_TXQ_IDX) { + trace_seq_printf(p, "[OFFCHAN]"); +#else + } else if (txq_idx < NX_NB_TXQ) { + txq_idx -= NX_FIRST_VIF_TXQ_IDX; + trace_seq_printf(p, "[VIF %d/%d]", + txq_idx / NX_NB_TXQ_PER_VIF, + txq_idx % NX_NB_TXQ_PER_VIF); +#endif + } else { + trace_seq_printf(p, "[ERROR %d]", txq_idx); + } + + trace_seq_putc(p, 0); + + return ret; +} + +const char * +ftrace_print_sta(struct trace_seq *p, int sta_idx) { + const char *ret = trace_seq_buffer_ptr(p); + + if (sta_idx < NX_REMOTE_STA_MAX) { + trace_seq_printf(p, "[STA %d]", sta_idx); + } else { + trace_seq_printf(p, "[BC/MC %d]", sta_idx - NX_REMOTE_STA_MAX); + } + + trace_seq_putc(p, 0); + + return ret; +} + +const char * +ftrace_print_hwq(struct trace_seq *p, int hwq_idx) { + + static const struct trace_print_flags symbols[] = + {{ECRNX_HWQ_BK, "BK"}, + {ECRNX_HWQ_BE, "BE"}, + {ECRNX_HWQ_VI, "VI"}, + {ECRNX_HWQ_VO, "VO"}, +#ifdef CONFIG_ECRNX_FULLMAC + {ECRNX_HWQ_BCMC, "BCMC"}, +#else + {ECRNX_HWQ_BCN, "BCN"}, +#endif + { -1, NULL }}; + return trace_print_symbols_seq(p, hwq_idx, symbols); +} + +const char * +ftrace_print_hwq_cred(struct trace_seq *p, u8 *cred) { + const char *ret = trace_seq_buffer_ptr(p); + +#if CONFIG_USER_MAX == 1 + trace_seq_printf(p, "%d", cred[0]); +#else + int i; + + for (i = 0; i < CONFIG_USER_MAX - 1; i++) + trace_seq_printf(p, "%d-", cred[i]); + trace_seq_printf(p, "%d", cred[i]); +#endif + + trace_seq_putc(p, 0); + return ret; +} + +const char * +ftrace_print_mu_info(struct trace_seq *p, u8 mu_info) { + const char *ret = trace_seq_buffer_ptr(p); + + if (mu_info) + trace_seq_printf(p, "MU: %d-%d", (mu_info & 0x3f), (mu_info >> 6)); + + trace_seq_putc(p, 0); + return ret; +} + +const char * +ftrace_print_mu_group(struct trace_seq *p, int nb_user, u8 *users) { + const char *ret = trace_seq_buffer_ptr(p); + int i; + + if (users[0] != 0xff) + trace_seq_printf(p, "(%d", users[0]); + else + trace_seq_printf(p, "(-"); + for (i = 1; i < CONFIG_USER_MAX ; i++) { + if (users[i] != 0xff) + trace_seq_printf(p, ",%d", users[i]); + else + trace_seq_printf(p, ",-"); + } + + trace_seq_printf(p, ")"); + trace_seq_putc(p, 0); + return ret; +} + +const char * +ftrace_print_amsdu(struct trace_seq *p, u16 nb_pkt) { + const char *ret = trace_seq_buffer_ptr(p); + + if (nb_pkt > 1) + trace_seq_printf(p, "(AMSDU %d)", nb_pkt); + + trace_seq_putc(p, 0); + return ret; +} +#endif /* defined(CONFIG_TRACEPOINTS) && defined(CREATE_TRACE_POINTS) */ + +#undef __print_txq +#define __print_txq(txq_idx) ftrace_print_txq(p, txq_idx) + +#undef __print_sta +#define __print_sta(sta_idx) ftrace_print_sta(p, sta_idx) + +#undef __print_hwq +#define __print_hwq(hwq) ftrace_print_hwq(p, hwq) + +#undef __print_hwq_cred +#define __print_hwq_cred(cred) ftrace_print_hwq_cred(p, cred) + +#undef __print_mu_info +#define __print_mu_info(mu_info) ftrace_print_mu_info(p, mu_info) + +#undef __print_mu_group +#define __print_mu_group(nb, users) ftrace_print_mu_group(p, nb, users) + +#undef __print_amsdu +#define __print_amsdu(nb_pkt) ftrace_print_amsdu(p, nb_pkt) + +#ifdef CONFIG_ECRNX_FULLMAC + +TRACE_EVENT( + txq_select, + TP_PROTO(int txq_idx, u16 pkt_ready_up, struct sk_buff *skb), + TP_ARGS(txq_idx, pkt_ready_up, skb), + TP_STRUCT__entry( + __field(u16, txq_idx) + __field(u16, pkt_ready) + __field(struct sk_buff *, skb) + ), + TP_fast_assign( + __entry->txq_idx = txq_idx; + __entry->pkt_ready = pkt_ready_up; + __entry->skb = skb; + ), + TP_printk("%s pkt_ready_up=%d skb=%p", __print_txq(__entry->txq_idx), + __entry->pkt_ready, __entry->skb) +); + +#endif /* CONFIG_ECRNX_FULLMAC */ + +DECLARE_EVENT_CLASS( + hwq_template, + TP_PROTO(u8 hwq_idx), + TP_ARGS(hwq_idx), + TP_STRUCT__entry( + __field(u8, hwq_idx) + ), + TP_fast_assign( + __entry->hwq_idx = hwq_idx; + ), + TP_printk("%s", __print_hwq(__entry->hwq_idx)) +); + +DEFINE_EVENT(hwq_template, hwq_flowctrl_stop, + TP_PROTO(u8 hwq_idx), + TP_ARGS(hwq_idx)); + +DEFINE_EVENT(hwq_template, hwq_flowctrl_start, + TP_PROTO(u8 hwq_idx), + TP_ARGS(hwq_idx)); + + +DECLARE_EVENT_CLASS( + txq_template, + TP_PROTO(struct ecrnx_txq *txq), + TP_ARGS(txq), + TP_STRUCT__entry( + __field(u16, txq_idx) + ), + TP_fast_assign( + __entry->txq_idx = txq->idx; + ), + TP_printk("%s", __print_txq(__entry->txq_idx)) +); + +DEFINE_EVENT(txq_template, txq_add_to_hw, + TP_PROTO(struct ecrnx_txq *txq), + TP_ARGS(txq)); + +DEFINE_EVENT(txq_template, txq_del_from_hw, + TP_PROTO(struct ecrnx_txq *txq), + TP_ARGS(txq)); + +#ifdef CONFIG_ECRNX_FULLMAC + +DEFINE_EVENT(txq_template, txq_flowctrl_stop, + TP_PROTO(struct ecrnx_txq *txq), + TP_ARGS(txq)); + +DEFINE_EVENT(txq_template, txq_flowctrl_restart, + TP_PROTO(struct ecrnx_txq *txq), + TP_ARGS(txq)); + +#endif /* CONFIG_ECRNX_FULLMAC */ + +TRACE_EVENT( + process_txq, + TP_PROTO(struct ecrnx_txq *txq), + TP_ARGS(txq), + TP_STRUCT__entry( + __field(u16, txq_idx) + __field(u16, len) + __field(u16, len_retry) + __field(s8, credit) + #ifdef CONFIG_ECRNX_FULLMAC + __field(u16, limit) + #endif /* CONFIG_ECRNX_FULLMAC*/ + ), + TP_fast_assign( + __entry->txq_idx = txq->idx; + __entry->len = skb_queue_len(&txq->sk_list); + #ifdef CONFIG_MAC80211_TXQ + __entry->len += txq->nb_ready_mac80211; + #endif + __entry->len_retry = txq->nb_retry; + __entry->credit = txq->credits; + #ifdef CONFIG_ECRNX_FULLMAC + __entry->limit = txq->push_limit; + #endif /* CONFIG_ECRNX_FULLMAC*/ + ), + + #ifdef CONFIG_ECRNX_FULLMAC + TP_printk("%s txq_credits=%d, len=%d, retry_len=%d, push_limit=%d", + __print_txq(__entry->txq_idx), __entry->credit, + __entry->len, __entry->len_retry, __entry->limit) + #else + TP_printk("%s txq_credits=%d, len=%d, retry_len=%d", + __print_txq(__entry->txq_idx), __entry->credit, + __entry->len, __entry->len_retry) + #endif /* CONFIG_ECRNX_FULLMAC*/ +); + +DECLARE_EVENT_CLASS( + txq_reason_template, + TP_PROTO(struct ecrnx_txq *txq, u16 reason), + TP_ARGS(txq, reason), + TP_STRUCT__entry( + __field(u16, txq_idx) + __field(u16, reason) + __field(u16, status) + ), + TP_fast_assign( + __entry->txq_idx = txq->idx; + __entry->reason = reason; + __entry->status = txq->status; + ), + TP_printk("%s reason=%s status=%s", + __print_txq(__entry->txq_idx), + __print_symbolic(__entry->reason, + {ECRNX_TXQ_STOP_FULL, "FULL"}, + {ECRNX_TXQ_STOP_CSA, "CSA"}, + {ECRNX_TXQ_STOP_STA_PS, "PS"}, + {ECRNX_TXQ_STOP_VIF_PS, "VPS"}, + {ECRNX_TXQ_STOP_CHAN, "CHAN"}, + {ECRNX_TXQ_STOP_MU_POS, "MU"}), + __print_flags(__entry->status, "|", + {ECRNX_TXQ_IN_HWQ_LIST, "IN LIST"}, + {ECRNX_TXQ_STOP_FULL, "FULL"}, + {ECRNX_TXQ_STOP_CSA, "CSA"}, + {ECRNX_TXQ_STOP_STA_PS, "PS"}, + {ECRNX_TXQ_STOP_VIF_PS, "VPS"}, + {ECRNX_TXQ_STOP_CHAN, "CHAN"}, + {ECRNX_TXQ_STOP_MU_POS, "MU"}, + {ECRNX_TXQ_NDEV_FLOW_CTRL, "FLW_CTRL"})) +); + +DEFINE_EVENT(txq_reason_template, txq_start, + TP_PROTO(struct ecrnx_txq *txq, u16 reason), + TP_ARGS(txq, reason)); + +DEFINE_EVENT(txq_reason_template, txq_stop, + TP_PROTO(struct ecrnx_txq *txq, u16 reason), + TP_ARGS(txq, reason)); + + +TRACE_EVENT( + push_desc, + TP_PROTO(struct sk_buff *skb, struct ecrnx_sw_txhdr *sw_txhdr, int push_flags), + + TP_ARGS(skb, sw_txhdr, push_flags), + + TP_STRUCT__entry( + __field(struct sk_buff *, skb) + __field(unsigned int, len) + __field(u16, tx_queue) + __field(u8, hw_queue) + __field(u8, push_flag) + __field(u32, flag) + __field(s8, txq_cred) + __field(u8, hwq_cred) + __field(u8, txq_length) +#ifdef CONFIG_ECRNX_SOFTMAC + __field(u16, sn) +#endif + __field(u16, pkt_cnt) + __field(u8, mu_info) + ), + TP_fast_assign( + __entry->skb = skb; + __entry->tx_queue = sw_txhdr->txq->idx; + __entry->push_flag = push_flags; + __entry->hw_queue = sw_txhdr->txq->hwq->id; + __entry->txq_cred = sw_txhdr->txq->credits; + __entry->hwq_cred = sw_txhdr->txq->hwq->credits[ECRNX_TXQ_POS_ID(sw_txhdr->txq)]; +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + __entry->pkt_cnt = sw_txhdr->desc.host.packet_cnt; +#endif + __entry->txq_length = skb_queue_len(&sw_txhdr->txq->sk_list); +#ifdef CONFIG_ECRNX_FULLMAC + __entry->flag = sw_txhdr->desc.host.flags; +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF +#ifdef CONFIG_ECRNX_AMSDUS_TX + if (sw_txhdr->amsdu.len) + __entry->len = sw_txhdr->amsdu.len; + else +#endif /* CONFIG_ECRNX_AMSDUS_TX */ + __entry->len = sw_txhdr->desc.host.packet_len[0]; +#else + __entry->len = sw_txhdr->desc.host.packet_len; +#endif /* CONFIG_ECRNX_SPLIT_TX_BUF */ + +#else /* !CONFIG_ECRNX_FULLMAC */ + __entry->flag = sw_txhdr->desc.umac.flags; + __entry->len = sw_txhdr->frame_len; + __entry->sn = sw_txhdr->sn; +#endif /* CONFIG_ECRNX_FULLMAC */ +#ifdef CONFIG_ECRNX_MUMIMO_TX + __entry->mu_info = sw_txhdr->desc.host.mumimo_info; +#else + __entry->mu_info = 0; +#endif + ), + +#ifdef CONFIG_ECRNX_FULLMAC + TP_printk("%s skb=%p (len=%d) hw_queue=%s txq_length=%d cred_txq=%d cred_hwq=%d %s flag=%s %s%s%s", + __print_txq(__entry->tx_queue), __entry->skb, __entry->len, + __print_hwq(__entry->hw_queue), __entry->txq_length, + __entry->txq_cred, __entry->hwq_cred, + __print_mu_info(__entry->mu_info), + __print_flags(__entry->flag, "|", + {TXU_CNTRL_RETRY, "RETRY"}, + {TXU_CNTRL_MORE_DATA, "MOREDATA"}, + {TXU_CNTRL_MGMT, "MGMT"}, + {TXU_CNTRL_MGMT_NO_CCK, "NO_CCK"}, + {TXU_CNTRL_MGMT_ROBUST, "ROBUST"}, + {TXU_CNTRL_AMSDU, "AMSDU"}, + {TXU_CNTRL_USE_4ADDR, "4ADDR"}, + {TXU_CNTRL_EOSP, "EOSP"}, + {TXU_CNTRL_MESH_FWD, "MESH_FWD"}, + {TXU_CNTRL_TDLS, "TDLS"}), + (__entry->push_flag & ECRNX_PUSH_IMMEDIATE) ? "(IMMEDIATE)" : "", + (!(__entry->flag & TXU_CNTRL_RETRY) && + (__entry->push_flag & ECRNX_PUSH_RETRY)) ? "(SW_RETRY)" : "", + __print_amsdu(__entry->pkt_cnt)) +#else + TP_printk("%s skb=%p (len=%d) hw_queue=%s txq_length=%d cred_txq=%d cred_hwq=%d %s flag=%x (%s) sn=%d %s", + __print_txq(__entry->tx_queue), __entry->skb, __entry->len, + __print_hwq(__entry->hw_queue), __entry->txq_length, __entry->txq_cred, + __entry->hwq_cred, + __print_mu_info(__entry->mu_info), + __entry->flag, + __print_flags(__entry->push_flag, "|", + {ECRNX_PUSH_RETRY, "RETRY"}, + {ECRNX_PUSH_IMMEDIATE, "IMMEDIATE"}), + __entry->sn, __print_amsdu(__entry->pkt_cnt)) +#endif /* CONFIG_ECRNX_FULLMAC */ +); + + +TRACE_EVENT( + txq_queue_skb, + TP_PROTO(struct sk_buff *skb, struct ecrnx_txq *txq, bool retry), + TP_ARGS(skb, txq, retry), + TP_STRUCT__entry( + __field(struct sk_buff *, skb) + __field(u16, txq_idx) + __field(s8, credit) + __field(u16, q_len) + __field(u16, q_len_retry) + __field(bool, retry) + ), + TP_fast_assign( + __entry->skb = skb; + __entry->txq_idx = txq->idx; + __entry->credit = txq->credits; + __entry->q_len = skb_queue_len(&txq->sk_list); + __entry->q_len_retry = txq->nb_retry; + __entry->retry = retry; + ), + + TP_printk("%s skb=%p retry=%d txq_credits=%d queue_len=%d (retry = %d)", + __print_txq(__entry->txq_idx), __entry->skb, __entry->retry, + __entry->credit, __entry->q_len, __entry->q_len_retry) +); +TRACE_EVENT( + txq_drop_skb, + TP_PROTO(struct sk_buff *skb, struct ecrnx_txq *txq, unsigned long queued_time), + TP_ARGS(skb, txq, queued_time), + TP_STRUCT__entry( + __field(struct sk_buff *, skb) + __field(u16, txq_idx) + __field(unsigned long, queued_time) + __field(u16, q_len) + __field(u16, q_len_retry) + ), + TP_fast_assign( + __entry->skb = skb; + __entry->txq_idx = txq->idx; + __entry->q_len = skb_queue_len(&txq->sk_list); + __entry->q_len_retry = txq->nb_retry; + __entry->queued_time = queued_time; + ), + TP_printk("%s skb=%p time_queued=%dms queue_len=%d (retry = %d)", + __print_txq(__entry->txq_idx), __entry->skb, + jiffies_to_msecs(__entry->queued_time), __entry->q_len, __entry->q_len_retry) +); + +#ifdef CONFIG_MAC80211_TXQ +TRACE_EVENT( + txq_wake, + TP_PROTO(struct ecrnx_txq *txq), + TP_ARGS(txq), + TP_STRUCT__entry( + __field(u16, txq_idx) + __field(u16, q_len) + ), + TP_fast_assign( + __entry->txq_idx = txq->idx; + __entry->q_len = txq->nb_ready_mac80211; + ), + + TP_printk("%s mac80211_queue_len=%d", __print_txq(__entry->txq_idx), __entry->q_len) +); + +TRACE_EVENT( + txq_drop, + TP_PROTO(struct ecrnx_txq *txq, unsigned long nb_drop), + TP_ARGS(txq, nb_drop), + TP_STRUCT__entry( + __field(u16, txq_idx) + __field(u16, nb_drop) + ), + TP_fast_assign( + __entry->txq_idx = txq->idx; + __entry->nb_drop = nb_drop; + ), + + TP_printk("%s %u pkt have been dropped by codel in mac80211 txq", + __print_txq(__entry->txq_idx), __entry->nb_drop) +); + +#endif + + +DECLARE_EVENT_CLASS( + idx_template, + TP_PROTO(u16 idx), + TP_ARGS(idx), + TP_STRUCT__entry( + __field(u16, idx) + ), + TP_fast_assign( + __entry->idx = idx; + ), + TP_printk("idx=%d", __entry->idx) +); + + +DEFINE_EVENT(idx_template, txq_vif_start, + TP_PROTO(u16 idx), + TP_ARGS(idx)); + +DEFINE_EVENT(idx_template, txq_vif_stop, + TP_PROTO(u16 idx), + TP_ARGS(idx)); + +TRACE_EVENT( + process_hw_queue, + TP_PROTO(struct ecrnx_hwq *hwq), + TP_ARGS(hwq), + TP_STRUCT__entry( + __field(u16, hwq) + __array(u8, credits, CONFIG_USER_MAX) + ), + TP_fast_assign( + int i; + __entry->hwq = hwq->id; + for (i=0; i < CONFIG_USER_MAX; i ++) + __entry->credits[i] = hwq->credits[i]; + ), + TP_printk("hw_queue=%s hw_credits=%s", + __print_hwq(__entry->hwq), __print_hwq_cred(__entry->credits)) +); + +DECLARE_EVENT_CLASS( + sta_idx_template, + TP_PROTO(u16 idx), + TP_ARGS(idx), + TP_STRUCT__entry( + __field(u16, idx) + ), + TP_fast_assign( + __entry->idx = idx; + ), + TP_printk("%s", __print_sta(__entry->idx)) +); + +DEFINE_EVENT(sta_idx_template, txq_sta_start, + TP_PROTO(u16 idx), + TP_ARGS(idx)); + +DEFINE_EVENT(sta_idx_template, txq_sta_stop, + TP_PROTO(u16 idx), + TP_ARGS(idx)); + +#ifdef CONFIG_ECRNX_FULLMAC + +DEFINE_EVENT(sta_idx_template, ps_disable, + TP_PROTO(u16 idx), + TP_ARGS(idx)); + +#endif /* CONFIG_ECRNX_FULLMAC */ + +TRACE_EVENT( + skb_confirm, + TP_PROTO(struct sk_buff *skb, struct ecrnx_txq *txq, struct ecrnx_hwq *hwq, +#ifdef CONFIG_ECRNX_FULLMAC + struct tx_cfm_tag *cfm +#else + u8 cfm +#endif + ), + + TP_ARGS(skb, txq, hwq, cfm), + + TP_STRUCT__entry( + __field(struct sk_buff *, skb) + __field(u16, txq_idx) + __field(u8, hw_queue) + __array(u8, hw_credit, CONFIG_USER_MAX) + __field(s8, sw_credit) + __field(s8, sw_credit_up) +#ifdef CONFIG_ECRNX_FULLMAC + __field(u8, ampdu_size) +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + __field(u16, amsdu) +#endif /* CONFIG_ECRNX_SPLIT_TX_BUF */ + __field(u16, sn) +#endif /* CONFIG_ECRNX_FULLMAC*/ + ), + + TP_fast_assign( + int i; + __entry->skb = skb; + __entry->txq_idx = txq->idx; + __entry->hw_queue = hwq->id; + for (i = 0 ; i < CONFIG_USER_MAX ; i++) + __entry->hw_credit[i] = hwq->credits[i]; + __entry->sw_credit = txq->credits; +#if defined CONFIG_ECRNX_FULLMAC + __entry->sw_credit_up = cfm->credits; + __entry->ampdu_size = cfm->ampdu_size; +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + __entry->amsdu = cfm->amsdu_size; + __entry->sn = cfm->sn; +#endif +#else + __entry->sw_credit_up = cfm +#endif /* CONFIG_ECRNX_FULLMAC */ + ), + + TP_printk("%s skb=%p hw_queue=%s, hw_credits=%s, txq_credits=%d (+%d)" +#ifdef CONFIG_ECRNX_FULLMAC + " sn=%u ampdu=%d" +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + " amsdu=%u" +#endif +#endif + , __print_txq(__entry->txq_idx), __entry->skb, + __print_hwq(__entry->hw_queue), + __print_hwq_cred(__entry->hw_credit), + __entry->sw_credit, __entry->sw_credit_up +#ifdef CONFIG_ECRNX_FULLMAC + , __entry->sn, __entry->ampdu_size +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + , __entry->amsdu +#endif +#endif + ) +); + +TRACE_EVENT( + credit_update, + TP_PROTO(struct ecrnx_txq *txq, s8_l cred_up), + + TP_ARGS(txq, cred_up), + + TP_STRUCT__entry( + __field(struct sk_buff *, skb) + __field(u16, txq_idx) + __field(s8, sw_credit) + __field(s8, sw_credit_up) + ), + + TP_fast_assign( + __entry->txq_idx = txq->idx; + __entry->sw_credit = txq->credits; + __entry->sw_credit_up = cred_up; + ), + + TP_printk("%s txq_credits=%d (%+d)", __print_txq(__entry->txq_idx), + __entry->sw_credit, __entry->sw_credit_up) +) + +#ifdef CONFIG_ECRNX_FULLMAC + +DECLARE_EVENT_CLASS( + ps_template, + TP_PROTO(struct ecrnx_sta *sta), + TP_ARGS(sta), + TP_STRUCT__entry( + __field(u16, idx) + __field(u16, ready_ps) + __field(u16, sp_ps) + __field(u16, ready_uapsd) + __field(u16, sp_uapsd) + ), + TP_fast_assign( + __entry->idx = sta->sta_idx; + __entry->ready_ps = sta->ps.pkt_ready[LEGACY_PS_ID]; + __entry->sp_ps = sta->ps.sp_cnt[LEGACY_PS_ID]; + __entry->ready_uapsd = sta->ps.pkt_ready[UAPSD_ID]; + __entry->sp_uapsd = sta->ps.sp_cnt[UAPSD_ID]; + ), + + TP_printk("%s [PS] ready=%d sp=%d [UAPSD] ready=%d sp=%d", + __print_sta(__entry->idx), __entry->ready_ps, __entry->sp_ps, + __entry->ready_uapsd, __entry->sp_uapsd) +); + +DEFINE_EVENT(ps_template, ps_queue, + TP_PROTO(struct ecrnx_sta *sta), + TP_ARGS(sta)); + +DEFINE_EVENT(ps_template, ps_drop, + TP_PROTO(struct ecrnx_sta *sta), + TP_ARGS(sta)); +DEFINE_EVENT(ps_template, ps_push, + TP_PROTO(struct ecrnx_sta *sta), + TP_ARGS(sta)); + +DEFINE_EVENT(ps_template, ps_enable, + TP_PROTO(struct ecrnx_sta *sta), + TP_ARGS(sta)); + +TRACE_EVENT( + ps_traffic_update, + TP_PROTO(u16 sta_idx, u8 traffic, bool uapsd), + + TP_ARGS(sta_idx, traffic, uapsd), + + TP_STRUCT__entry( + __field(u16, sta_idx) + __field(u8, traffic) + __field(bool, uapsd) + ), + + TP_fast_assign( + __entry->sta_idx = sta_idx; + __entry->traffic = traffic; + __entry->uapsd = uapsd; + ), + + TP_printk("%s %s%s traffic available ", __print_sta(__entry->sta_idx), + __entry->traffic ? "" : "no more ", + __entry->uapsd ? "U-APSD" : "legacy PS") +); + +TRACE_EVENT( + ps_traffic_req, + TP_PROTO(struct ecrnx_sta *sta, u16 pkt_req, u8 ps_id), + TP_ARGS(sta, pkt_req, ps_id), + TP_STRUCT__entry( + __field(u16, idx) + __field(u16, pkt_req) + __field(u8, ps_id) + __field(u16, ready) + __field(u16, sp) + ), + TP_fast_assign( + __entry->idx = sta->sta_idx; + __entry->pkt_req = pkt_req; + __entry->ps_id = ps_id; + __entry->ready = sta->ps.pkt_ready[ps_id]; + __entry->sp = sta->ps.sp_cnt[ps_id]; + ), + + TP_printk("%s %s traffic request %d pkt (ready=%d, sp=%d)", + __print_sta(__entry->idx), + __entry->ps_id == UAPSD_ID ? "U-APSD" : "legacy PS" , + __entry->pkt_req, __entry->ready, __entry->sp) +); + + +#ifdef CONFIG_ECRNX_AMSDUS_TX +TRACE_EVENT( + amsdu_subframe, + TP_PROTO(struct ecrnx_sw_txhdr *sw_txhdr), + TP_ARGS(sw_txhdr), + TP_STRUCT__entry( + __field(struct sk_buff *, skb) + __field(u16, txq_idx) + __field(u8, nb) + __field(u32, len) + ), + TP_fast_assign( + __entry->skb = sw_txhdr->skb; + __entry->nb = sw_txhdr->amsdu.nb; + __entry->len = sw_txhdr->amsdu.len; + __entry->txq_idx = sw_txhdr->txq->idx; + ), + + TP_printk("%s skb=%p %s nb_subframe=%d, len=%u", + __print_txq(__entry->txq_idx), __entry->skb, + (__entry->nb == 2) ? "Start new AMSDU" : "Add subframe", + __entry->nb, __entry->len) +); +TRACE_EVENT( + amsdu_dismantle, + TP_PROTO(struct ecrnx_sw_txhdr *sw_txhdr), + TP_ARGS(sw_txhdr), + TP_STRUCT__entry( + __field(struct sk_buff *, skb) + __field(u16, txq_idx) + __field(u8, nb) + __field(u32, len) + ), + TP_fast_assign( + __entry->skb = sw_txhdr->skb; + __entry->nb = sw_txhdr->amsdu.nb; + __entry->len = sw_txhdr->amsdu.len; + __entry->txq_idx = sw_txhdr->txq->idx; + ), + TP_printk("%s skb=%p nb_subframe=%d, len=%u", + __print_txq(__entry->txq_idx), __entry->skb, + __entry->nb, __entry->len) +); +TRACE_EVENT( + amsdu_len_update, + TP_PROTO(struct ecrnx_sta *sta, int amsdu_len), + TP_ARGS(sta, amsdu_len), + TP_STRUCT__entry( + __field(u8, sta_idx) + __field(u16, amsdu_len) + ), + TP_fast_assign( + __entry->sta_idx = sta->sta_idx; + __entry->amsdu_len = amsdu_len; + ), + TP_printk("[Sta %d] A-MSDU len = %d", __entry->sta_idx, __entry->amsdu_len) +); +#endif + +#endif /* CONFIG_ECRNX_FULLMAC */ + +#ifdef CONFIG_ECRNX_MUMIMO_TX +TRACE_EVENT( + mu_group_update, + TP_PROTO(struct ecrnx_mu_group *group), + TP_ARGS(group), + TP_STRUCT__entry( + __field(u8, nb_user) + __field(u8, group_id) + __array(u8, users, CONFIG_USER_MAX) + ), + TP_fast_assign( + int i; + __entry->nb_user = group->user_cnt; + for (i = 0; i < CONFIG_USER_MAX ; i++) { + if (group->users[i]) { + __entry->users[i] = group->users[i]->sta_idx; + } else { + __entry->users[i] = 0xff; + } + } + + __entry->group_id = group->group_id; + ), + + TP_printk("Group-id = %d, Users = %s", + __entry->group_id, + __print_mu_group(__entry->nb_user, __entry->users)) +); + +TRACE_EVENT( + mu_group_delete, + TP_PROTO(int group_id), + TP_ARGS(group_id), + TP_STRUCT__entry( + __field(u8, group_id) + ), + TP_fast_assign( + __entry->group_id = group_id; + ), + + TP_printk("Group-id = %d", __entry->group_id) +); + +TRACE_EVENT( + mu_group_selection, + TP_PROTO(struct ecrnx_sta *sta, int group_id), + TP_ARGS(sta, group_id), + TP_STRUCT__entry( + __field(u8, sta_idx) + __field(u8, group_id) + ), + TP_fast_assign( + __entry->sta_idx = sta->sta_idx; + __entry->group_id = group_id; + ), + + TP_printk("[Sta %d] Group-id = %d", __entry->sta_idx, __entry->group_id) +); + +TRACE_EVENT( + txq_select_mu_group, + TP_PROTO(struct ecrnx_txq *txq, int group_id, int pos), + + TP_ARGS(txq, group_id, pos), + + TP_STRUCT__entry( + __field(u16, txq_idx) + __field(u8, group_id) + __field(u8, pos) + ), + TP_fast_assign( + __entry->txq_idx = txq->idx; + __entry->group_id = group_id; + __entry->pos = pos; + ), + + TP_printk("%s: group=%d pos=%d", __print_txq(__entry->txq_idx), + __entry->group_id, __entry->pos) +); + +#endif /* CONFIG_ECRNX_MUMIMO_TX */ +#endif /* ! CONFIG_ECRNX_FHOST */ + +/***************************************************************************** + * TRACE functions for MESH + ****************************************************************************/ +#ifdef CONFIG_ECRNX_FULLMAC +DECLARE_EVENT_CLASS( + mesh_path_template, + TP_PROTO(struct ecrnx_mesh_path *mesh_path), + TP_ARGS(mesh_path), + TP_STRUCT__entry( + __field(u8, idx) + __field(u8, next_hop_sta) + __array(u8, tgt_mac, ETH_ALEN) + ), + + TP_fast_assign( + __entry->idx = mesh_path->path_idx; + memcpy(__entry->tgt_mac, &mesh_path->tgt_mac_addr, ETH_ALEN); + if (mesh_path->nhop_sta) + __entry->next_hop_sta = mesh_path->nhop_sta->sta_idx; + else + __entry->next_hop_sta = 0xff; + ), + + TP_printk("Mpath(%d): target=%pM next_hop=STA-%d", + __entry->idx, __entry->tgt_mac, __entry->next_hop_sta) +); + +DEFINE_EVENT(mesh_path_template, mesh_create_path, + TP_PROTO(struct ecrnx_mesh_path *mesh_path), + TP_ARGS(mesh_path)); + +DEFINE_EVENT(mesh_path_template, mesh_delete_path, + TP_PROTO(struct ecrnx_mesh_path *mesh_path), + TP_ARGS(mesh_path)); + +DEFINE_EVENT(mesh_path_template, mesh_update_path, + TP_PROTO(struct ecrnx_mesh_path *mesh_path), + TP_ARGS(mesh_path)); + +#endif /* CONFIG_ECRNX_FULLMAC */ + +/***************************************************************************** + * TRACE functions for RADAR + ****************************************************************************/ +#ifdef CONFIG_ECRNX_RADAR +TRACE_EVENT( + radar_pulse, + TP_PROTO(u8 chain, struct radar_pulse *pulse), + TP_ARGS(chain, pulse), + TP_STRUCT__entry( + __field(u8, chain) + __field(s16, freq) + __field(u16, pri) + __field(u8, len) + __field(u8, fom) + ), + TP_fast_assign( + __entry->freq = pulse->freq * 2; + __entry->len = pulse->len * 2; + __entry->fom = pulse->fom * 6; + __entry->pri = pulse->rep; + __entry->chain = chain; + ), + + TP_printk("%s: PRI=%.5d LEN=%.3d FOM=%.2d%% freq=%dMHz ", + __print_symbolic(__entry->chain, + {ECRNX_RADAR_RIU, "RIU"}, + {ECRNX_RADAR_FCU, "FCU"}), + __entry->pri, __entry->len, __entry->fom, __entry->freq) + ); + +TRACE_EVENT( + radar_detected, + TP_PROTO(u8 chain, u8 region, s16 freq, u8 type, u16 pri), + TP_ARGS(chain, region, freq, type, pri), + TP_STRUCT__entry( + __field(u8, chain) + __field(u8, region) + __field(s16, freq) + __field(u8, type) + __field(u16, pri) + ), + TP_fast_assign( + __entry->chain = chain; + __entry->region = region; + __entry->freq = freq; + __entry->type = type; + __entry->pri = pri; + ), + TP_printk("%s: region=%s type=%d freq=%dMHz (pri=%dus)", + __print_symbolic(__entry->chain, + {ECRNX_RADAR_RIU, "RIU"}, + {ECRNX_RADAR_FCU, "FCU"}), + __print_symbolic(__entry->region, + {NL80211_DFS_UNSET, "UNSET"}, + {NL80211_DFS_FCC, "FCC"}, + {NL80211_DFS_ETSI, "ETSI"}, + {NL80211_DFS_JP, "JP"}), + __entry->type, __entry->freq, __entry->pri) +); + +TRACE_EVENT( + radar_set_region, + TP_PROTO(u8 region), + TP_ARGS(region), + TP_STRUCT__entry( + __field(u8, region) + ), + TP_fast_assign( + __entry->region = region; + ), + TP_printk("region=%s", + __print_symbolic(__entry->region, + {NL80211_DFS_UNSET, "UNSET"}, + {NL80211_DFS_FCC, "FCC"}, + {NL80211_DFS_ETSI, "ETSI"}, + {NL80211_DFS_JP, "JP"})) +); + +TRACE_EVENT( + radar_enable_detection, + TP_PROTO(u8 region, u8 enable, u8 chain), + TP_ARGS(region, enable, chain), + TP_STRUCT__entry( + __field(u8, region) + __field(u8, chain) + __field(u8, enable) + ), + TP_fast_assign( + __entry->chain = chain; + __entry->enable = enable; + __entry->region = region; + ), + TP_printk("%s: %s radar detection %s", + __print_symbolic(__entry->chain, + {ECRNX_RADAR_RIU, "RIU"}, + {ECRNX_RADAR_FCU, "FCU"}), + __print_symbolic(__entry->enable, + {ECRNX_RADAR_DETECT_DISABLE, "Disable"}, + {ECRNX_RADAR_DETECT_ENABLE, "Enable (no report)"}, + {ECRNX_RADAR_DETECT_REPORT, "Enable"}), + __entry->enable == ECRNX_RADAR_DETECT_DISABLE ? "" : + __print_symbolic(__entry->region, + {NL80211_DFS_UNSET, "UNSET"}, + {NL80211_DFS_FCC, "FCC"}, + {NL80211_DFS_ETSI, "ETSI"}, + {NL80211_DFS_JP, "JP"})) +); +#endif /* CONFIG_ECRNX_RADAR */ + +/***************************************************************************** + * TRACE functions for IPC message + ****************************************************************************/ +#include "ecrnx_strs.h" + +DECLARE_EVENT_CLASS( + ipc_msg_template, + TP_PROTO(u16 id), + TP_ARGS(id), + TP_STRUCT__entry( + __field(u16, id) + ), + TP_fast_assign( + __entry->id = id; + ), + + TP_printk("%s (%d - %d)", ECRNX_ID2STR(__entry->id), + MSG_T(__entry->id), MSG_I(__entry->id)) +); + +DEFINE_EVENT(ipc_msg_template, msg_send, + TP_PROTO(u16 id), + TP_ARGS(id)); + +DEFINE_EVENT(ipc_msg_template, msg_recv, + TP_PROTO(u16 id), + TP_ARGS(id)); + + + +#endif /* !defined(_ECRNX_EVENTS_H) || defined(TRACE_HEADER_MULTI_READ) */ + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH . +#define TRACE_INCLUDE_FILE ecrnx_events +#include <trace/define_trace.h> diff --git a/drivers/net/wireless/eswin/ecrnx_fw_dump.c b/drivers/net/wireless/eswin/ecrnx_fw_dump.c new file mode 100644 index 000000000000..97ca610accb4 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_fw_dump.c @@ -0,0 +1,573 @@ +/** + ****************************************************************************** + * + * @file ecrnx_fw_dump.c + * + * @brief Definition of debug fs entries to process fw dump + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + + +#include <linux/kmod.h> +#include <linux/debugfs.h> + +#include "ecrnx_defs.h" +#include "ecrnx_debugfs.h" + +static ssize_t ecrnx_dbgfs_rhd_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + struct dbg_debug_dump_tag *dump = priv->dbgdump_elem.buf.addr; + ssize_t read; + + mutex_lock(&priv->dbgdump_elem.mutex); + if (!priv->debugfs.trace_prst) { + mutex_unlock(&priv->dbgdump_elem.mutex); + return 0; + } + + read = simple_read_from_buffer(user_buf, count, ppos, + dump->rhd_mem, + dump->dbg_info.rhd_len); + + mutex_unlock(&priv->dbgdump_elem.mutex); + return read; +} + +DEBUGFS_READ_FILE_OPS(rhd); + +static ssize_t ecrnx_dbgfs_rbd_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + struct dbg_debug_dump_tag *dump = priv->dbgdump_elem.buf.addr; + ssize_t read; + + mutex_lock(&priv->dbgdump_elem.mutex); + if (!priv->debugfs.trace_prst) { + mutex_unlock(&priv->dbgdump_elem.mutex); + return 0; + } + + read = simple_read_from_buffer(user_buf, count, ppos, + dump->rbd_mem, + dump->dbg_info.rbd_len); + + mutex_unlock(&priv->dbgdump_elem.mutex); + return read; +} + +DEBUGFS_READ_FILE_OPS(rbd); + +static ssize_t ecrnx_dbgfs_thdx_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos, int idx) +{ + struct ecrnx_hw *priv = file->private_data; + struct dbg_debug_dump_tag *dump = priv->dbgdump_elem.buf.addr; + ssize_t read; + + mutex_lock(&priv->dbgdump_elem.mutex); + if (!priv->debugfs.trace_prst) { + mutex_unlock(&priv->dbgdump_elem.mutex); + return 0; + } + + read = simple_read_from_buffer(user_buf, count, ppos, + &dump->thd_mem[idx], + dump->dbg_info.thd_len[idx]); + + mutex_unlock(&priv->dbgdump_elem.mutex); + return read; +} + +static ssize_t ecrnx_dbgfs_thd0_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + return ecrnx_dbgfs_thdx_read(file, user_buf, count, ppos, 0); +} +DEBUGFS_READ_FILE_OPS(thd0); + +static ssize_t ecrnx_dbgfs_thd1_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + return ecrnx_dbgfs_thdx_read(file, user_buf, count, ppos, 1); +} +DEBUGFS_READ_FILE_OPS(thd1); + +static ssize_t ecrnx_dbgfs_thd2_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + return ecrnx_dbgfs_thdx_read(file, user_buf, count, ppos, 2); +} +DEBUGFS_READ_FILE_OPS(thd2); + +static ssize_t ecrnx_dbgfs_thd3_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + return ecrnx_dbgfs_thdx_read(file, user_buf, count, ppos, 3); +} +DEBUGFS_READ_FILE_OPS(thd3); + +#if (NX_TXQ_CNT == 5) +static ssize_t ecrnx_dbgfs_thd4_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + return ecrnx_dbgfs_thdx_read(file, user_buf, count, ppos, 4); +} +DEBUGFS_READ_FILE_OPS(thd4); +#endif + +static ssize_t ecrnx_dbgfs_mactrace_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + struct dbg_debug_dump_tag *dump = priv->dbgdump_elem.buf.addr; + ssize_t read; + + mutex_lock(&priv->dbgdump_elem.mutex); + if (!priv->debugfs.trace_prst) { + char msg[64]; + mutex_unlock(&priv->dbgdump_elem.mutex); + scnprintf(msg, sizeof(msg), "Force trigger\n"); + ecrnx_dbgfs_trigger_fw_dump(priv, msg); + + return 0; + } + + read = simple_read_from_buffer(user_buf, count, ppos, + dump->la_mem, + dump->dbg_info.la_conf.trace_len); + + mutex_unlock(&priv->dbgdump_elem.mutex); + + return read; +} +DEBUGFS_READ_FILE_OPS(mactrace); + +static ssize_t ecrnx_dbgfs_macdiags_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + struct dbg_debug_dump_tag *dump = priv->dbgdump_elem.buf.addr; + ssize_t read; + + mutex_lock(&priv->dbgdump_elem.mutex); + if (!priv->debugfs.trace_prst) { + mutex_unlock(&priv->dbgdump_elem.mutex); + return 0; + } + + read = simple_read_from_buffer(user_buf, count, ppos, + dump->dbg_info.diags_mac, + DBG_DIAGS_MAC_MAX * 2); + + mutex_unlock(&priv->dbgdump_elem.mutex); + return read; +} + +DEBUGFS_READ_FILE_OPS(macdiags); + +static ssize_t ecrnx_dbgfs_phydiags_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + struct dbg_debug_dump_tag *dump = priv->dbgdump_elem.buf.addr; + ssize_t read; + + mutex_lock(&priv->dbgdump_elem.mutex); + if (!priv->debugfs.trace_prst) { + mutex_unlock(&priv->dbgdump_elem.mutex); + return 0; + } + + read = simple_read_from_buffer(user_buf, count, ppos, + dump->dbg_info.diags_phy, + DBG_DIAGS_PHY_MAX * 2); + + mutex_unlock(&priv->dbgdump_elem.mutex); + return read; +} + +DEBUGFS_READ_FILE_OPS(phydiags); + +static ssize_t ecrnx_dbgfs_hwdiags_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + struct dbg_debug_dump_tag *dump = priv->dbgdump_elem.buf.addr; + char buf[16]; + int ret; + + mutex_lock(&priv->dbgdump_elem.mutex); + if (!priv->debugfs.trace_prst) { + mutex_unlock(&priv->dbgdump_elem.mutex); + return 0; + } + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "%08X\n", dump->dbg_info.hw_diag); + + mutex_unlock(&priv->dbgdump_elem.mutex); + return simple_read_from_buffer(user_buf, count, ppos, buf, ret); +} + +DEBUGFS_READ_FILE_OPS(hwdiags); + +static ssize_t ecrnx_dbgfs_plfdiags_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + struct dbg_debug_dump_tag *dump = priv->dbgdump_elem.buf.addr; + char buf[16]; + int ret; + + mutex_lock(&priv->dbgdump_elem.mutex); + if (!priv->debugfs.trace_prst) { + mutex_unlock(&priv->dbgdump_elem.mutex); + return 0; + } + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "%08X\n", dump->dbg_info.la_conf.diag_conf); + + mutex_unlock(&priv->dbgdump_elem.mutex); + return simple_read_from_buffer(user_buf, count, ppos, buf, ret); +} + +DEBUGFS_READ_FILE_OPS(plfdiags); + +static ssize_t ecrnx_dbgfs_swdiags_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + struct dbg_debug_dump_tag *dump = priv->dbgdump_elem.buf.addr; + ssize_t read; + + mutex_lock(&priv->dbgdump_elem.mutex); + if (!priv->debugfs.trace_prst) { + mutex_unlock(&priv->dbgdump_elem.mutex); + return 0; + } + + read = simple_read_from_buffer(user_buf, count, ppos, + &dump->dbg_info.sw_diag, + dump->dbg_info.sw_diag_len); + + mutex_unlock(&priv->dbgdump_elem.mutex); + return read; +} + +DEBUGFS_READ_FILE_OPS(swdiags); + +static ssize_t ecrnx_dbgfs_error_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + struct dbg_debug_dump_tag *dump = priv->dbgdump_elem.buf.addr; + ssize_t read; + + mutex_lock(&priv->dbgdump_elem.mutex); + if (!priv->debugfs.trace_prst) { + mutex_unlock(&priv->dbgdump_elem.mutex); + return 0; + } + + read = simple_read_from_buffer(user_buf, count, ppos, + dump->dbg_info.error, + strlen((char *)dump->dbg_info.error)); + + mutex_unlock(&priv->dbgdump_elem.mutex); + return read; +} + +DEBUGFS_READ_FILE_OPS(error); + +static ssize_t ecrnx_dbgfs_rxdesc_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + struct dbg_debug_dump_tag *dump = priv->dbgdump_elem.buf.addr; + char buf[32]; + int ret; + ssize_t read; + + mutex_lock(&priv->dbgdump_elem.mutex); + if (!priv->debugfs.trace_prst) { + mutex_unlock(&priv->dbgdump_elem.mutex); + return 0; + } + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "%08X\n%08X\n", dump->dbg_info.rhd, + dump->dbg_info.rbd); + read = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + mutex_unlock(&priv->dbgdump_elem.mutex); + return read; +} + +DEBUGFS_READ_FILE_OPS(rxdesc); + +static ssize_t ecrnx_dbgfs_txdesc_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + struct dbg_debug_dump_tag *dump = priv->dbgdump_elem.buf.addr; + char buf[64]; + int len = 0; + int i; + + mutex_lock(&priv->dbgdump_elem.mutex); + if (!priv->debugfs.trace_prst) { + mutex_unlock(&priv->dbgdump_elem.mutex); + return 0; + } + + for (i = 0; i < NX_TXQ_CNT; i++) { + len += scnprintf(&buf[len], min_t(size_t, sizeof(buf) - len - 1, count), + "%08X\n", dump->dbg_info.thd[i]); + } + + mutex_unlock(&priv->dbgdump_elem.mutex); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +DEBUGFS_READ_FILE_OPS(txdesc); + +static ssize_t ecrnx_dbgfs_macrxptr_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + struct dbg_debug_dump_tag *dump = priv->dbgdump_elem.buf.addr; + ssize_t read; + + mutex_lock(&priv->dbgdump_elem.mutex); + if (!priv->debugfs.trace_prst) { + mutex_unlock(&priv->dbgdump_elem.mutex); + return 0; + } + + read = simple_read_from_buffer(user_buf, count, ppos, + &dump->dbg_info.rhd_hw_ptr, + 2 * sizeof(dump->dbg_info.rhd_hw_ptr)); + + mutex_unlock(&priv->dbgdump_elem.mutex); + return read; +} + +DEBUGFS_READ_FILE_OPS(macrxptr); + +static ssize_t ecrnx_dbgfs_lamacconf_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + struct dbg_debug_dump_tag *dump = priv->dbgdump_elem.buf.addr; + ssize_t read; + + mutex_lock(&priv->dbgdump_elem.mutex); + if (!priv->debugfs.trace_prst) { + mutex_unlock(&priv->dbgdump_elem.mutex); + return 0; + } + + read = simple_read_from_buffer(user_buf, count, ppos, + dump->dbg_info.la_conf.conf, + LA_CONF_LEN * 4); + + mutex_unlock(&priv->dbgdump_elem.mutex); + return read; +} +DEBUGFS_READ_FILE_OPS(lamacconf); + +static ssize_t ecrnx_dbgfs_chaninfo_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + struct dbg_debug_dump_tag *dump = priv->dbgdump_elem.buf.addr; + char buf[4 * 32]; + int ret; + + mutex_lock(&priv->dbgdump_elem.mutex); + if (!priv->debugfs.trace_prst) { + mutex_unlock(&priv->dbgdump_elem.mutex); + return 0; + } + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "type: %d\n" + "prim20_freq: %d MHz\n" + "center1_freq: %d MHz\n" + "center2_freq: %d MHz\n", + (dump->dbg_info.chan_info.info1 >> 8) & 0xFF, + (dump->dbg_info.chan_info.info1 >> 16) & 0xFFFF, + (dump->dbg_info.chan_info.info2 >> 0) & 0xFFFF, + (dump->dbg_info.chan_info.info2 >> 16) & 0xFFFF); + + mutex_unlock(&priv->dbgdump_elem.mutex); + return simple_read_from_buffer(user_buf, count, ppos, buf, ret); +} + +DEBUGFS_READ_FILE_OPS(chaninfo); + +static ssize_t ecrnx_dbgfs_um_helper_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + char buf[sizeof(priv->debugfs.helper_cmd)]; + int ret; + + ret = scnprintf(buf, min_t(size_t, sizeof(buf) - 1, count), + "%s", priv->debugfs.helper_cmd); + + return simple_read_from_buffer(user_buf, count, ppos, buf, ret); +} + +static ssize_t ecrnx_dbgfs_um_helper_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ecrnx_hw *priv = file->private_data; + int eobuf = min_t(size_t, sizeof(priv->debugfs.helper_cmd) - 1, count); + + priv->debugfs.helper_cmd[eobuf] = '\0'; + if (copy_from_user(priv->debugfs.helper_cmd, user_buf, eobuf)) + return -EFAULT; + + return count; +} + +DEBUGFS_READ_WRITE_FILE_OPS(um_helper); + +/* + * Calls a userspace pgm + */ +int ecrnx_um_helper(struct ecrnx_debugfs *ecrnx_debugfs, const char *cmd) +{ + struct ecrnx_hw *ecrnx_hw = container_of(ecrnx_debugfs, struct ecrnx_hw, + debugfs); + char *envp[] = { "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL }; + char **argv; + int argc, ret; + + if (!ecrnx_debugfs->dir || + !strlen((cmd = cmd ? cmd : ecrnx_debugfs->helper_cmd))) + return 0; + argv = argv_split(in_interrupt() ? GFP_ATOMIC : GFP_KERNEL, cmd, &argc); + if (!argc) + return PTR_ERR(argv); + + if ((ret = call_usermodehelper(argv[0], argv, envp, + UMH_WAIT_PROC | UMH_KILLABLE))) + dev_err(ecrnx_hw->dev, "Failed to call %s (%s returned %d)\n", + argv[0], cmd, ret); + argv_free(argv); + + return ret; +} + +static void ecrnx_um_helper_work(struct work_struct *ws) +{ + struct ecrnx_debugfs *ecrnx_debugfs = container_of(ws, struct ecrnx_debugfs, + helper_work); + struct ecrnx_hw *ecrnx_hw = container_of(ecrnx_debugfs, struct ecrnx_hw, + debugfs); + ecrnx_um_helper(ecrnx_debugfs, NULL); + if (!ecrnx_debugfs->unregistering) + ecrnx_umh_done(ecrnx_hw); + ecrnx_debugfs->helper_scheduled = false; +} + +int ecrnx_trigger_um_helper(struct ecrnx_debugfs *ecrnx_debugfs) +{ + struct ecrnx_hw *ecrnx_hw = container_of(ecrnx_debugfs, struct ecrnx_hw, + debugfs); + + if (ecrnx_debugfs->helper_scheduled == true) { + dev_err(ecrnx_hw->dev, "%s: Already scheduled\n", __func__); + return -EBUSY; + } + + spin_lock_bh(&ecrnx_debugfs->umh_lock); + if (ecrnx_debugfs->unregistering) { + spin_unlock_bh(&ecrnx_debugfs->umh_lock); + dev_err(ecrnx_hw->dev, "%s: unregistering\n", __func__); + return -ENOENT; + } + ecrnx_debugfs->helper_scheduled = true; + schedule_work(&ecrnx_debugfs->helper_work); + spin_unlock_bh(&ecrnx_debugfs->umh_lock); + + return 0; +} +void ecrnx_wait_um_helper(struct ecrnx_hw *ecrnx_hw) +{ + flush_work(&ecrnx_hw->debugfs.helper_work); +} + +int ecrnx_dbgfs_register_fw_dump(struct ecrnx_hw *ecrnx_hw, + struct dentry *dir_drv, + struct dentry *dir_diags) +{ + + struct ecrnx_debugfs *ecrnx_debugfs = &ecrnx_hw->debugfs; + + BUILD_BUG_ON(sizeof(CONFIG_ECRNX_UM_HELPER_DFLT) >= + sizeof(ecrnx_debugfs->helper_cmd)); + strncpy(ecrnx_debugfs->helper_cmd, + CONFIG_ECRNX_UM_HELPER_DFLT, sizeof(ecrnx_debugfs->helper_cmd)); + INIT_WORK(&ecrnx_debugfs->helper_work, ecrnx_um_helper_work); + DEBUGFS_ADD_FILE(um_helper, dir_drv, S_IWUSR | S_IRUSR); + + ecrnx_debugfs->trace_prst = ecrnx_debugfs->helper_scheduled = false; + spin_lock_init(&ecrnx_debugfs->umh_lock); + DEBUGFS_ADD_FILE(rhd, dir_diags, S_IRUSR); + DEBUGFS_ADD_FILE(rbd, dir_diags, S_IRUSR); + DEBUGFS_ADD_FILE(thd0, dir_diags, S_IRUSR); + DEBUGFS_ADD_FILE(thd1, dir_diags, S_IRUSR); + DEBUGFS_ADD_FILE(thd2, dir_diags, S_IRUSR); + DEBUGFS_ADD_FILE(thd3, dir_diags, S_IRUSR); +#if (NX_TXQ_CNT == 5) + DEBUGFS_ADD_FILE(thd4, dir_diags, S_IRUSR); +#endif + DEBUGFS_ADD_FILE(mactrace, dir_diags, S_IRUSR); + DEBUGFS_ADD_FILE(macdiags, dir_diags, S_IRUSR); + DEBUGFS_ADD_FILE(phydiags, dir_diags, S_IRUSR); + DEBUGFS_ADD_FILE(plfdiags, dir_diags, S_IRUSR); + DEBUGFS_ADD_FILE(hwdiags, dir_diags, S_IRUSR); + DEBUGFS_ADD_FILE(swdiags, dir_diags, S_IRUSR); + DEBUGFS_ADD_FILE(error, dir_diags, S_IRUSR); + DEBUGFS_ADD_FILE(rxdesc, dir_diags, S_IRUSR); + DEBUGFS_ADD_FILE(txdesc, dir_diags, S_IRUSR); + DEBUGFS_ADD_FILE(macrxptr, dir_diags, S_IRUSR); + DEBUGFS_ADD_FILE(lamacconf, dir_diags, S_IRUSR); + DEBUGFS_ADD_FILE(chaninfo, dir_diags, S_IRUSR); + + return 0; + + err: + return -1; +} diff --git a/drivers/net/wireless/eswin/ecrnx_fw_trace.c b/drivers/net/wireless/eswin/ecrnx_fw_trace.c new file mode 100644 index 000000000000..a6428e565358 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_fw_trace.c @@ -0,0 +1,895 @@ +/** + ****************************************************************************** + * + * @file ecrnx_fw_trace.c + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include "ecrnx_fw_trace.h" +#include "ecrnx_defs.h" + +#define ECRNX_FW_TRACE_HEADER_LEN 4 +#define ECRNX_FW_TRACE_HEADER_FMT "ts=%12u ID=%8d" +#define ECRNX_FW_TRACE_HEADER_ASCII_LEN (3 + 12 + 4 + 8) +#define ECRNX_FW_TRACE_PARAM_FMT ", %5d" +#define ECRNX_FW_TRACE_PARAM_ASCII_LEN (7) + +#define ECRNX_FW_TRACE_NB_PARAM(a) ((*a >> 8) & 0xff) +#define ECRNX_FW_TRACE_ID(a) (uint32_t)(((a[0] & 0xff) << 16) + a[1]) +#define ECRNX_FW_TRACE_ENTRY_SIZE(a) (ECRNX_FW_TRACE_NB_PARAM(a) + \ + ECRNX_FW_TRACE_HEADER_LEN) + +#define ECRNX_FW_TRACE_READY 0x1234 +#define ECRNX_FW_TRACE_LOCKED 0xdead +#define ECRNX_FW_TRACE_LOCKED_HOST 0x0230 +#define ECRNX_FW_TRACE_LAST_ENTRY 0xffff + +#define ECRNX_FW_TRACE_RESET "*** RESET ***\n" +#define ECRNX_FW_TRACE_RESET_SIZE sizeof(ECRNX_FW_TRACE_RESET) - 1 // don't count '\0' + +static int trace_last_reset=0; + +static const int startup_max_to = 500; + +static uint32_t *saved_filters = NULL; +static int saved_filters_cnt = 0; + +#define ECRNX_FW_TRACE_CHECK_INT_MS 1000 + + +/** + * ecrnx_fw_trace_work() - Work function to check for new traces + * process function for &struct ecrnx_fw_trace.work + * + * @ws: work structure + * + * Check if new traces are available in the shared buffer, by comparing current + * end index with end index in the last check. If so wake up pending threads, + * otherwise re-schedule the work is there are still some pending readers. + * + * Note: If between two check firmware exactly write one buffer of trace then + * those traces will be lost. Fortunately this is very unlikely to happen. + * + * Note: Even if wake_up doesn't actually wake up threads (because condition + * failed), calling waitqueue_active just after will still return false. + * Fortunately this should never happen (new trace should always trigger the + * waiting condition) otherwise it may be needed to re-schedule the work after + * wake_up. + */ +static void ecrnx_fw_trace_work(struct work_struct *ws) +{ + struct delayed_work *dw = container_of(ws, struct delayed_work, work); + struct ecrnx_fw_trace *trace = container_of(dw, struct ecrnx_fw_trace, work); + + if (trace->closing || + (!ecrnx_fw_trace_empty(&trace->buf) && + trace->last_read_index != *trace->buf.end)) { + trace->last_read_index = *trace->buf.end; + wake_up_interruptible(&trace->queue); + return; + } + + if (waitqueue_active(&trace->queue) && !delayed_work_pending(dw)) { + schedule_delayed_work(dw, msecs_to_jiffies(ECRNX_FW_TRACE_CHECK_INT_MS)); + } +} + +/** + * ecrnx_fw_trace_buf_lock() - Lock trace buffer for firmware + * + * @shared_buf: Pointer to shared buffer + * + * Very basic synchro mechanism so that fw do not update trace buffer while host + * is reading it. Not safe to race condition if host and fw read lock value at + * the "same" time. + */ +static void ecrnx_fw_trace_buf_lock(struct ecrnx_fw_trace_buf *shared_buf) +{ + wait: + while(*shared_buf->lock == ECRNX_FW_TRACE_LOCKED) {} + *shared_buf->lock &= ECRNX_FW_TRACE_LOCKED_HOST; + + /* re-read to reduce race condition window */ + if (*shared_buf->lock == ECRNX_FW_TRACE_LOCKED) + goto wait; +} + +/** + * ecrnx_fw_trace_buf_unlock() - Unlock trace buffer for firmware + * + * @shared_buf: Pointer to shared buffer + * + */ +static void ecrnx_fw_trace_buf_unlock(struct ecrnx_fw_trace_buf *shared_buf) +{ + *shared_buf->lock = ECRNX_FW_TRACE_READY; +} + +/** + * ecrnx_fw_trace_buf_init() - Initialize ecrnx_fw_trace_buf structure + * + * @shared_buf: Structure to initialize + * @ipc: Pointer to IPC shard structure that contains trace buffer info + * + * + * Return: 0 if initialization succeed, <0 otherwise. It can only fail if + * trace feature is not enabled in the firmware (or buffer is corrupted). + */ +int ecrnx_fw_trace_buf_init(struct ecrnx_fw_trace_buf *shared_buf, + struct ecrnx_fw_trace_ipc_desc *ipc) +{ + uint16_t lock_status = ipc->pattern; + + if ((lock_status != ECRNX_FW_TRACE_READY && + lock_status != ECRNX_FW_TRACE_LOCKED)) { + shared_buf->data = NULL; + return -ENOENT; + } + + /* Buffer starts <offset> bytes from the location of ipc->offset */ + shared_buf->data = (uint16_t *)((uint8_t *)(&ipc->offset) + ipc->offset); + shared_buf->lock = &ipc->pattern; + shared_buf->size = ipc->size; + shared_buf->start = &ipc->start; + shared_buf->end = &ipc->end; + shared_buf->reset_idx = ++trace_last_reset; + + /* backward compatibilty with firmware without trace activation */ + if ((ipc->nb_compo >> 16) == ECRNX_FW_TRACE_READY) { + shared_buf->nb_compo = ipc->nb_compo & 0xffff; + shared_buf->compo_table = (uint32_t *)((uint8_t *)(&ipc->offset_compo) + + ipc->offset_compo); + } else { + shared_buf->nb_compo = 0; + shared_buf->compo_table = NULL; + } + + return 0; +} + +/** + * ecrnx_fw_trace_init() - Initialize ecrnx_fw_trace structure + * + * @trace: Structure to initialize + * @ipc: Pointer to IPC shard structure that contains trace buffer info + * + * Return: 0 if initialization succeed, <0 otherwise. It can only fail if + * trace feature is not enabled in the firmware (or buffer is corrupted). + */ +int ecrnx_fw_trace_init(struct ecrnx_fw_trace *trace, + struct ecrnx_fw_trace_ipc_desc *ipc) +{ + if (ecrnx_fw_trace_buf_init(&trace->buf, ipc)) + return -ENOENT; + + INIT_DELAYED_WORK(&trace->work, ecrnx_fw_trace_work); + init_waitqueue_head(&trace->queue); + mutex_init(&trace->mutex); + trace->closing = false; + return 0; +} + +/** + * ecrnx_fw_trace_deinit() - De-initialization before releasing ecrnx_fw_trace + * + * @trace: fw trace control structure + */ +void ecrnx_fw_trace_deinit(struct ecrnx_fw_trace *trace) +{ + trace->closing = true; + flush_delayed_work(&trace->work); + trace->buf.data = NULL; +} + +/** + * ecrnx_fw_trace_reset_local() - Reset local buffer pointer/status + * + * @local_buf: structure to reset + */ +static void ecrnx_fw_trace_reset_local(struct ecrnx_fw_trace_local_buf *local_buf) +{ + local_buf->read = local_buf->data; + local_buf->write = local_buf->data; + local_buf->nb_entries = 0; + local_buf->free_space = local_buf->size; + local_buf->last_read = NULL; + local_buf->reset_idx = 0; + local_buf->show_reset = NULL; +} + +/** + * ecrnx_fw_trace_alloc_local() - Allocate a local buffer and initialize + * ecrnx_fw_trace_local_buf structure + * + * @local_buf: structure to initialize + * @size: Size of the buffer to allocate + * + * @local structure is initialized to use the allocated buffer. + * + * Return: 0 if allocation succeed and <0 otherwise. + */ +int ecrnx_fw_trace_alloc_local(struct ecrnx_fw_trace_local_buf *local_buf, + int size) +{ + local_buf->data = kmalloc(size * sizeof(uint16_t), GFP_KERNEL); + if (!local_buf->data) { + return -ENOMEM; + } + + local_buf->data_end = local_buf->data + size; + local_buf->size = size; + ecrnx_fw_trace_reset_local(local_buf); + return 0; +} + +/** + * ecrnx_fw_trace_free_local() - Free local buffer + * + * @local_buf: structure containing buffer pointer to free. + */ +void ecrnx_fw_trace_free_local(struct ecrnx_fw_trace_local_buf *local_buf) +{ + if (local_buf->data) + kfree(local_buf->data); + local_buf->data = NULL; +} + +/** + * ecrnx_fw_trace_strlen() - Return buffer size needed convert a trace entry into + * string + * + * @entry: Pointer on trace entry + * + */ +static inline int ecrnx_fw_trace_strlen(uint16_t *entry) +{ + return (ECRNX_FW_TRACE_HEADER_ASCII_LEN + + (ECRNX_FW_TRACE_NB_PARAM(entry) * ECRNX_FW_TRACE_PARAM_ASCII_LEN) + + 1); /* for \n */ +} + +/** + * ecrnx_fw_trace_to_str() - Convert one trace entry to a string + * + * @trace: Poitner to the trace entry + * @buf: Buffer for the string + * @size: Size of the string buffer, updated with the actual string size + * + * Return: pointer to the next tag entry. + */ +static uint16_t *ecrnx_fw_trace_to_str(uint16_t *trace, char *buf, size_t *size) +{ + uint32_t ts, id; + int nb_param; + int res, buf_idx = 0, left = *size; + + id = ECRNX_FW_TRACE_ID(trace); + nb_param = ECRNX_FW_TRACE_NB_PARAM(trace); + + trace +=2; + ts = *trace++; + ts <<= 16; + ts += *trace++; + + res = scnprintf(&buf[buf_idx], left, ECRNX_FW_TRACE_HEADER_FMT, ts, id); + buf_idx += res; + left -= res; + + while (nb_param > 0) { + res = scnprintf(&buf[buf_idx], left, ECRNX_FW_TRACE_PARAM_FMT, *trace++); + buf_idx += res; + left -= res; + nb_param--; + } + + res = scnprintf(&buf[buf_idx], left, "\n"); + left -= res; + *size = (*size - left); + + return trace; +} + +/** + * ecrnx_fw_trace_copy_entry() - Copy one trace entry in a local buffer + * + * @local_buf: Local buffer to copy trace into + * @trace_entry: Pointer to the trace entry (in shared memory) to copy + * @size: Size, in 16bits words, of the trace entry + * + * It is assumed that local has enough contiguous free-space available in + * local buffer (i.e. from local_buf->write) to copy this trace. + */ +static void ecrnx_fw_trace_copy_entry(struct ecrnx_fw_trace_local_buf *local_buf, + uint16_t *trace_entry, int size) +{ + uint16_t *write = local_buf->write; + uint16_t *read = trace_entry; + int i; + + for (i = 0; i < size; i++) { + *write++ = *read++; + } + + if (write >= local_buf->data_end) + local_buf->write = local_buf->data; + else + local_buf->write = write; + + local_buf->free_space -= size; + local_buf->last_read = trace_entry; + local_buf->last_read_value = *trace_entry; + local_buf->nb_entries++; +} + +/** + * ecrnx_fw_trace_copy() - Copy trace entries from shared to local buffer + * + * @trace_buf: Pointer to shard buffer + * @local_buf: Pointer to local buffer + * + * Copy has many trace entry as possible from shared buffer to local buffer + * without overwriting traces in local buffer. + * + * Return: number of trace entries copied to local buffer + */ +static int ecrnx_fw_trace_copy(struct ecrnx_fw_trace *trace, + struct ecrnx_fw_trace_local_buf *local_buf) +{ + struct ecrnx_fw_trace_buf *trace_buf = &trace->buf; + uint16_t *ptr, *ptr_end, *ptr_limit; + int entry_size, ret = 0; + + if (mutex_lock_interruptible(&trace->mutex)) + return 0; + + /* reset last_read ptr if shared buffer has been reset */ + if (local_buf->reset_idx != trace_buf->reset_idx) { + local_buf->show_reset = local_buf->write; + local_buf->reset_idx = trace_buf->reset_idx; + local_buf->last_read = NULL; + } + + ecrnx_fw_trace_buf_lock(trace_buf); + + ptr_end = trace_buf->data + *trace_buf->end; + if (ecrnx_fw_trace_empty(trace_buf) || (ptr_end == local_buf->last_read)) + goto end; + ptr_limit = trace_buf->data + trace_buf->size; + + if (local_buf->last_read && + (local_buf->last_read_value == *local_buf->last_read)) { + ptr = local_buf->last_read; + ptr += ECRNX_FW_TRACE_ENTRY_SIZE(ptr); + } else { + ptr = trace_buf->data + *trace_buf->start; + } + + while (1) { + + if ((ptr == ptr_limit) || (*ptr == ECRNX_FW_TRACE_LAST_ENTRY)) + ptr = trace_buf->data; + + entry_size = ECRNX_FW_TRACE_ENTRY_SIZE(ptr); + + if ((ptr + entry_size) > ptr_limit) { + ECRNX_ERR("Corrupted trace buffer\n"); + _ecrnx_fw_trace_reset(trace, false); + break; + } else if (entry_size > local_buf->size) { + ECRNX_ERR("FW_TRACE local buffer too small, trace skipped"); + goto next_entry; + } + + if (local_buf->free_space >= entry_size) { + int contiguous = local_buf->data_end - local_buf->write; + + if ((local_buf->write < local_buf->read) || contiguous >= entry_size) { + /* enough contiguous memory from local_buf->write */ + ecrnx_fw_trace_copy_entry(local_buf, ptr, entry_size); + ret++; + } else if ((local_buf->free_space - contiguous) >= entry_size) { + /* not enough contiguous from local_buf->write but enough + from local_buf->data */ + *local_buf->write = ECRNX_FW_TRACE_LAST_ENTRY; + if (local_buf->show_reset == local_buf->write) + local_buf->show_reset = local_buf->data; + local_buf->write = local_buf->data; + local_buf->free_space -= contiguous; + ecrnx_fw_trace_copy_entry(local_buf, ptr, entry_size); + ret++; + } else { + /* not enough contiguous memory */ + goto end; + } + } else { + goto end; + } + + if (ptr == ptr_end) + break; + + next_entry: + ptr += entry_size; + } + + end: + ecrnx_fw_trace_buf_unlock(trace_buf); + mutex_unlock(&trace->mutex); + return ret; +} + +/** + * ecrnx_fw_trace_read_local() - Read trace from local buffer and convert it to + * string in a user buffer + * + * @local_buf: Pointer to local buffer + * @user_buf: Pointer to user buffer + * @size: Size of the user buffer + * + * Read traces from shared buffer to write them in the user buffer after string + * conversion. Stop when no more space in user buffer or no more trace to read. + * + * Return: The size written in the user buffer. + */ +static size_t ecrnx_fw_trace_read_local(struct ecrnx_fw_trace_local_buf *local_buf, + char __user *user_buf, size_t size) +{ + uint16_t *ptr; + char *str = NULL; // worst case 255 params + size_t str_size; + int entry_size; + size_t res = 0 , remain = size, not_cpy = 0; + + if (!local_buf->nb_entries) + return res; + + str = kmalloc(FW_TRACE_READ_DUMP_max_SIZE, GFP_ATOMIC); + if(!str){ + return 0; + } + + ptr = local_buf->read; + while(local_buf->nb_entries && !not_cpy) { + + if (local_buf->show_reset == ptr) { + if (remain < ECRNX_FW_TRACE_RESET_SIZE) + break; + + local_buf->show_reset = NULL; + not_cpy = copy_to_user(user_buf + res, ECRNX_FW_TRACE_RESET, + ECRNX_FW_TRACE_RESET_SIZE); + res += (ECRNX_FW_TRACE_RESET_SIZE - not_cpy); + remain -= (ECRNX_FW_TRACE_RESET_SIZE - not_cpy); + } + + if (remain < ecrnx_fw_trace_strlen(ptr)) + break; + + entry_size = ECRNX_FW_TRACE_ENTRY_SIZE(ptr); + str_size = sizeof(str); + ptr = ecrnx_fw_trace_to_str(ptr, str, &str_size); + not_cpy = copy_to_user(user_buf + res, str, str_size); + str_size -= not_cpy; + res += str_size; + remain -= str_size; + + local_buf->nb_entries--; + local_buf->free_space += entry_size; + if (ptr >= local_buf->data_end) { + ptr = local_buf->data; + } else if (*ptr == ECRNX_FW_TRACE_LAST_ENTRY) { + local_buf->free_space += local_buf->data_end - ptr; + ptr = local_buf->data; + } + local_buf->read = ptr; + } + + /* read all entries reset pointer */ + if ( !local_buf->nb_entries) { + + local_buf->write = local_buf->read = local_buf->data; + local_buf->free_space = local_buf->size; + } + + kfree(str); + return res; +} + +/** + * ecrnx_fw_trace_read() - Update local buffer from shared buffer and convert + * local buffer to string in user buffer + * + * @trace: Fw trace control structure + * @local_buf: Local buffer to update and read from + * @dont_wait: Indicate whether function should wait or not for traces before + * returning + * @user_buf: Pointer to user buffer + * @size: Size of the user buffer + * + * Read traces from shared buffer to write them in the user buffer after string + * conversion. Stop when no more space in user buffer or no more trace to read. + * + * Return: The size written in the user buffer if > 0, -EAGAIN if there is no + * new traces and dont_wait is set and -ERESTARTSYS if signal has been + * received while waiting for new traces. + */ +size_t ecrnx_fw_trace_read(struct ecrnx_fw_trace *trace, + struct ecrnx_fw_trace_local_buf *local_buf, + bool dont_wait, char __user *user_buf, size_t size) +{ + size_t res = 0; + + ecrnx_fw_trace_copy(trace, local_buf); + + while(!local_buf->nb_entries) { + int last_index; + + if (dont_wait) + return -EAGAIN; + + /* no trace, schedule work to periodically check trace buffer */ + if (!delayed_work_pending(&trace->work)) { + trace->last_read_index = *trace->buf.end; + schedule_delayed_work(&trace->work, + msecs_to_jiffies(ECRNX_FW_TRACE_CHECK_INT_MS)); + } + + /* and wait for traces */ + last_index = *trace->buf.end; + if (wait_event_interruptible(trace->queue, + (trace->closing || + (last_index != *trace->buf.end)))) { + return -ERESTARTSYS; + } + + if (trace->closing) + return 0; + + ecrnx_fw_trace_copy(trace, local_buf); + } + + /* copy as many traces as possible in user buffer */ + while (1) { + size_t read; + read = ecrnx_fw_trace_read_local(local_buf, user_buf + res, size - res); + res += read; + ecrnx_fw_trace_copy(trace, local_buf); + if (!read) + break; + } + + return res; +} + + +/** + * _ecrnx_fw_trace_dump() - Dump shared trace buffer in kernel buffer + * + * @trace_buf: Pointer to shared trace buffer; + * + * Called when error is detected, output trace on dmesg directly read from + * shared memory + */ +void _ecrnx_fw_trace_dump(struct ecrnx_fw_trace_buf *trace_buf) +{ + uint16_t *ptr, *ptr_end, *ptr_limit, *next_ptr; + char *buf = NULL; // worst case 255 params + size_t size; + + if (!trace_buf->data || ecrnx_fw_trace_empty(trace_buf)) + return; + + ecrnx_fw_trace_buf_lock(trace_buf); + + ptr = trace_buf->data + *trace_buf->start; + ptr_end = trace_buf->data + *trace_buf->end; + ptr_limit = trace_buf->data + trace_buf->size; + + buf = kmalloc(FW_TRACE_READ_DUMP_max_SIZE, GFP_ATOMIC); + while (1) { + size = FW_TRACE_READ_DUMP_max_SIZE; + next_ptr = ecrnx_fw_trace_to_str(ptr, buf, &size); + ECRNX_PRINT("%s", buf); + + if (ptr == ptr_end) { + break; + } else if ((next_ptr == ptr_limit) || + (*next_ptr == ECRNX_FW_TRACE_LAST_ENTRY)) { + ptr = trace_buf->data; + } else if (next_ptr > ptr_limit) { + ECRNX_ERR("Corrupted trace buffer\n"); + break; + } else { + ptr = next_ptr; + } + } + + ecrnx_fw_trace_buf_unlock(trace_buf); + kfree(buf); +} + +/** + * _ecrnx_fw_trace_reset() - Reset trace buffer at firmware level + * + * @trace: Pointer to shared trace buffer; + * @bool: Indicate if mutex must be aquired before + */ +int _ecrnx_fw_trace_reset(struct ecrnx_fw_trace *trace, bool lock) +{ + struct ecrnx_fw_trace_buf *trace_buf = &trace->buf; + + if (lock && mutex_lock_interruptible(&trace->mutex)) + return -ERESTARTSYS; + + if (trace->buf.data) { + ecrnx_fw_trace_buf_lock(trace_buf); + *trace_buf->start = 0; + *trace_buf->end = trace_buf->size + 1; + trace_buf->reset_idx = ++trace_last_reset; + ecrnx_fw_trace_buf_unlock(trace_buf); + } + + if (lock) + mutex_unlock(&trace->mutex); + return 0; +} + +/** + * ecrnx_fw_trace_get_trace_level() - Get trace level for a given component + * + * @trace: Pointer to shared trace buffer; + * @compo_id: Index of the componetn in the table + * + * Return: The trace level set for the given component, 0 if component index + * is invalid. + */ +static uint32_t ecrnx_fw_trace_get_trace_level(struct ecrnx_fw_trace_buf *trace_buf, + unsigned int compo_id) +{ + if (compo_id >= trace_buf->nb_compo) + return 0; + return trace_buf->compo_table[compo_id]; +} + +/** + * ecrnx_fw_trace_set_trace_level() - Set trace level for a given component + * + * @trace_buf: Pointer to shared trace buffer; + * @compo_id: Index of the componetn in the table + * @level: Trace level to set + * + * Set all components if compo_id is equals to the number of component and + * does nothing if it is greater. + */ +static void ecrnx_fw_trace_set_trace_level(struct ecrnx_fw_trace_buf *trace_buf, + unsigned int compo_id, uint32_t level) +{ + if (compo_id > trace_buf->nb_compo) + return; + + if (compo_id == trace_buf->nb_compo) { + int i; + for (i = 0; i < trace_buf->nb_compo; i++) { + trace_buf->compo_table[i] = level; + } + } else { + trace_buf->compo_table[compo_id] = level; + } +} + +/** + * ecrnx_fw_trace_level_read() - Write current trace level in a user buffer + * as a string + * + * @trace: Fw trace control structure + * @user_buf: Pointer to user buffer + * @len: Size of the user buffer + * @ppos: position offset + * + * Return: Number of bytes written in user buffer if > 0, error otherwise + */ +size_t ecrnx_fw_trace_level_read(struct ecrnx_fw_trace *trace, + char __user *user_buf, size_t len, loff_t *ppos) +{ + struct ecrnx_fw_trace_buf *trace_buf = &trace->buf; + size_t res = 0; + int i, size; + char *buf; + + size = trace_buf->nb_compo * 16; + buf = kmalloc(size, GFP_KERNEL); + if (buf == NULL) + return 0; + + if (mutex_lock_interruptible(&trace->mutex)) { + kfree(buf); + return -ERESTARTSYS; + } + + for (i = 0 ; i < trace_buf->nb_compo ; i ++) { + res += scnprintf(&buf[res], size - res, "%3d:0x%08x\n", i, + ecrnx_fw_trace_get_trace_level(trace_buf, i)); + } + mutex_unlock(&trace->mutex); + + res = simple_read_from_buffer(user_buf, len, ppos, buf, res); + + kfree(buf); + return res; +} + +/** + * ecrnx_fw_trace_level_write() - Read trace level from a user buffer provided + * as a string and applyt them. + * + * @trace: Fw trace control structure + * @user_buf: Pointer to user buffer + * @len: Size of the user buffer + * + * trace level must be provided in the following form: + * <compo_id>:<trace_level> where <compo_id> is in decimal notation and + * <trace_level> in decical or hexadecimal notation. + * Several trace level can be provided, separated by space,tab or new line. + * + * Return: Number of bytes read form user buffer if > 0, error otherwise + */ +size_t ecrnx_fw_trace_level_write(struct ecrnx_fw_trace *trace, + const char __user *user_buf, size_t len) +{ + struct ecrnx_fw_trace_buf *trace_buf = &trace->buf; + char *buf, *token, *next; + + buf = kmalloc(len + 1, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + if (copy_from_user(buf, user_buf, len)) { + kfree(buf); + return -EFAULT; + } + buf[len] = '\0'; + + if (mutex_lock_interruptible(&trace->mutex)) { + kfree(buf); + return -ERESTARTSYS; + } + + next = buf; + token = strsep(&next, " \t\n"); + while (token) { + unsigned int compo, level; + if ((sscanf(token, "%d:0x%x", &compo, &level) == 2)|| + (sscanf(token, "%d:%d", &compo, &level) == 2)) { + ecrnx_fw_trace_set_trace_level(trace_buf, compo, level); + } + + token = strsep(&next, " \t"); + } + mutex_unlock(&trace->mutex); + + kfree(buf); + return len; +} + +/** + * ecrnx_fw_trace_config_filters() - Update FW trace filters + * + * @trace_buf: Pointer to shared buffer + * @ipc: Pointer to IPC shared structure that contains trace buffer info + * @ftl: Firmware trace level + * + * Return: 0 if the trace filters are successfully updated, <0 otherwise. + */ +int ecrnx_fw_trace_config_filters(struct ecrnx_fw_trace_buf *trace_buf, + struct ecrnx_fw_trace_ipc_desc *ipc, char *ftl) +{ + int to; + char *next, *token; + + to = 0; + while((ipc->pattern != ECRNX_FW_TRACE_READY) && (to < startup_max_to)) + { + msleep(50); + to += 50; + } + + if (ecrnx_fw_trace_buf_init(trace_buf, ipc)) + return -ENOENT; + + next = ftl; + token = strsep(&next, " "); + while(token) + { + unsigned int compo, ret, id, level = 0; + char action; + + if ((sscanf(token, "%d%c0x%x", &compo, &action, &id) == 3)|| + (sscanf(token, "%d%c%d", &compo, &action, &id) == 3)) + { + if(action == '=') + { + level = id; + } + else + { + ret = ecrnx_fw_trace_get_trace_level(trace_buf, compo); + if(action == '+') + level = (ret | id); + else if (action == '-') + level = (ret & ~id); + } + ecrnx_fw_trace_set_trace_level(trace_buf, compo, level); + } + + token = strsep(&next, " "); + } + + return 0; +} + +/** + * ecrnx_fw_trace_save_filters() - Save filters currently configured so that + * they can be restored with ecrnx_fw_trace_restore_filters() + * + * @trace: Fw trace control structure + * @return 0 if filters have been saved and != 0 in case on error + */ +int ecrnx_fw_trace_save_filters(struct ecrnx_fw_trace *trace) +{ + int i; + + if (saved_filters) + kfree(saved_filters); + + saved_filters_cnt = trace->buf.nb_compo; + saved_filters = kmalloc(saved_filters_cnt * sizeof(uint32_t), GFP_KERNEL); + if (!saved_filters) + return -1; + + for (i = 0; i < saved_filters_cnt; i++) { + saved_filters[i] = ecrnx_fw_trace_get_trace_level(&trace->buf, i); + } + + return 0; +} + +/** + * ecrnx_fw_trace_restore_filters() - Restore filters previoulsy saved + * by ecrnx_fw_trace_save_filters() + * + * @trace: Fw trace control structure + * @return 0 if filters have been restored and != 0 in case on error + */ +int ecrnx_fw_trace_restore_filters(struct ecrnx_fw_trace *trace) +{ + int i; + + if (!saved_filters || (trace->buf.data == NULL)) + return -1; + + if (saved_filters_cnt != trace->buf.nb_compo) { + pr_warn("Number of trace components change between saved and restore\n"); + if (saved_filters_cnt > trace->buf.nb_compo) { + saved_filters_cnt = trace->buf.nb_compo; + } + } + + for (i = 0; i < saved_filters_cnt; i++) { + ecrnx_fw_trace_set_trace_level(&trace->buf, i, saved_filters[i]); + } + + kfree(saved_filters); + saved_filters = NULL; + saved_filters_cnt = 0; + + return 0; +} diff --git a/drivers/net/wireless/eswin/ecrnx_fw_trace.h b/drivers/net/wireless/eswin/ecrnx_fw_trace.h new file mode 100644 index 000000000000..e7df3ad46a09 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_fw_trace.h @@ -0,0 +1,161 @@ +/** + ****************************************************************************** + * + * ecrnx_fw_trace.h + * + * Copyright (C) RESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef _ECRNX_FW_TRACE_H_ +#define _ECRNX_FW_TRACE_H_ + +#include <linux/mutex.h> +#include <linux/wait.h> +#include <linux/workqueue.h> + +#define FW_TRACE_READ_DUMP_max_SIZE 1824 +/** + * struct ecrnx_fw_trace_desc - Trace buffer info as provided by fw in ipc + * + * @pattern: Synchro pattern + * @start: Index of first entry in trace buffer + * @end: Index of last entry in trace buffer + * @size: Size, in 16bits words, od the trace buffer + * @offset: Offset, in bytest, to the start of the buffer from the address of + * this field. + * @nb_compo: Number of filterable component (16LSB) synchro pattern (16 MSB) + * @offset_compo: Offset, in bytest, to the start of the component activation + * table from the address of this field. + */ +struct ecrnx_fw_trace_ipc_desc { + volatile uint16_t pattern; + volatile uint32_t start; + volatile uint32_t end; + volatile uint32_t size; + volatile uint32_t offset; + volatile uint32_t nb_compo; + volatile uint32_t offset_compo; +}; + +/** + * struct ecrnx_fw_trace_buf - Info for trace buffer in shared memory + * + * @lock: Address of the synchro word + * @data: Address of the trace buffer + * @size: Size, in 16bits words, of the trace buffer + * @start: Address on the current index (in 16 bits words) of the first trace + * entry. + * @end: Address on the current index (in 16 bits words) of the last trace + * entry. If *end > size, it means that the trace buffer contains no traces. + * @reset_idx: Increased each time the trace buffer is reset + * (by calling _ecrnx_fw_trace_reset()) + * @nb_compo: Size of the compo_table + * @compo_table: Table containing component filter status. + */ +struct ecrnx_fw_trace_buf { + volatile uint16_t *lock; + uint16_t *data; + uint32_t size; + volatile uint32_t *start; + volatile uint32_t *end; + int reset_idx; + unsigned int nb_compo; + uint32_t *compo_table; +}; + +/** + * struct ecrnx_fw_trace_local_buf - Info for a local trace buffer + * + * @data: Address of the local buffer + * @data_end: Address after the end of the local buffer + * @size: Size, in 16bits words, oth the local buffer + * @read: Pointer to the next trace entry to read + * @write: Pointer to the next entry to write + * @nb_entries: Number of trace entries ready to be read + * @free_space: Free space, in 16bits words, in the buffer. + * @last_read: Address of the last entry read in the shared buffer + * @last_read_value: First word of the last trace entry read. + * @reset_idx: Reset index. If it doesn't match share buffer index then it + * means that share buffer has been resetted since last read. + */ +struct ecrnx_fw_trace_local_buf { + uint16_t *data; + uint16_t *data_end; + uint32_t size; + uint16_t *read; + uint16_t *write; + uint16_t nb_entries; + uint32_t free_space; + uint16_t *last_read; + uint16_t last_read_value; + int reset_idx; + uint16_t * show_reset; +}; + + +/** + * struct ecrnx_fw_trace - info to handle several reader of the shared buffer + * + * @buf: shared trace buffer. + * @mutex: mutex, used to prevent several reader updating shared buffer at the + * same time. + * @queue: queue, used to delay reader. + * @work: work used to periodically check for new traces in shared buffer. + * @last_read_index: Last read index from shared buffer + * @closing: Indicate whether is driver is being removed, meaning that reader + * should no longer wait no new traces + */ +struct ecrnx_fw_trace { + struct ecrnx_fw_trace_buf buf; + struct mutex mutex; + wait_queue_head_t queue; + struct delayed_work work; + int last_read_index; + bool closing; +}; + +int ecrnx_fw_trace_init(struct ecrnx_fw_trace *trace, + struct ecrnx_fw_trace_ipc_desc *ipc); +void ecrnx_fw_trace_deinit(struct ecrnx_fw_trace *trace); + +int ecrnx_fw_trace_buf_init(struct ecrnx_fw_trace_buf *shared_buf, + struct ecrnx_fw_trace_ipc_desc *ipc); + +int _ecrnx_fw_trace_reset(struct ecrnx_fw_trace *trace, bool lock); +void _ecrnx_fw_trace_dump(struct ecrnx_fw_trace_buf *trace); + +int ecrnx_fw_trace_alloc_local(struct ecrnx_fw_trace_local_buf *local, + int size); +void ecrnx_fw_trace_free_local(struct ecrnx_fw_trace_local_buf *local); + + +size_t ecrnx_fw_trace_read(struct ecrnx_fw_trace *trace, + struct ecrnx_fw_trace_local_buf *local_buf, + bool dont_wait, char __user *user_buf, size_t size); + + +size_t ecrnx_fw_trace_level_read(struct ecrnx_fw_trace *trace, + char __user *user_buf, size_t len, loff_t *ppos); +size_t ecrnx_fw_trace_level_write(struct ecrnx_fw_trace *trace, + const char __user *user_buf, size_t len); + + +int ecrnx_fw_trace_config_filters(struct ecrnx_fw_trace_buf *trace_buf, + struct ecrnx_fw_trace_ipc_desc *ipc, char *ftl); + +int ecrnx_fw_trace_save_filters(struct ecrnx_fw_trace *trace); +int ecrnx_fw_trace_restore_filters(struct ecrnx_fw_trace *trace); + +/** + * ecrnx_fw_trace_empty() - Check if shared buffer is empty + * + * @shared_buf: Pointer to shared buffer + */ +static inline bool ecrnx_fw_trace_empty(struct ecrnx_fw_trace_buf *shared_buf) +{ + return (*shared_buf->end >= shared_buf->size); +} + +#endif /* _ECRNX_FW_TRACE_H_ */ diff --git a/drivers/net/wireless/eswin/ecrnx_iwpriv.c b/drivers/net/wireless/eswin/ecrnx_iwpriv.c new file mode 100644 index 000000000000..037fe56246a3 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_iwpriv.c @@ -0,0 +1,396 @@ +/** + ****************************************************************************** + * + * @file ecrnx_iwpriv.c + * + * @brief iwpriv function definitions + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +/** + * INCLUDE FILES + ****************************************************************************** + */ +#include <net/cfg80211.h> +#include <net/iw_handler.h> +#include "ecrnx_defs.h" +#include "eswin_utils.h" + +#ifdef CONFIG_ECRNX_WIFO_CAIL +#include "ecrnx_amt.h" +#include "core.h" +#endif + +#ifdef CONFIG_WIRELESS_EXT +/** + * FUNCTION DEFINITIONS + ****************************************************************************** + */ +#define IN +#define OUT + +#ifdef CONFIG_WEXT_PRIV + /* This may be wrong. When using the new SIOCIWFIRSTPRIV range, we probably + * should use only "GET" ioctls (last bit set to 1). "SET" ioctls are root + * only and don't return the modified struct ifreq to the application which + * is usually a problem. - Jean II */ +#ifdef CONFIG_ECRNX_WIFO_CAIL +#define IOCTL_IWPRIV_AMT (SIOCIWFIRSTPRIV + 1) +#endif +#define IOCTL_IWPRIV_WD (SIOCIWFIRSTPRIV + 3) + +#if 0 +static int priv_set_int(IN struct net_device *prNetDev, + IN struct iw_request_info *prIwReqInfo, + IN union iwreq_data *prIwReqData, IN OUT char *pcExtra) +{ + int *flag = (int*)pcExtra; + ECRNX_PRINT("cmd=%x, flags=%x\n", + prIwReqInfo->cmd, prIwReqInfo->flags); + ECRNX_PRINT("mode=%x, flags=%x\n", + prIwReqData->mode, prIwReqData->data.flags); + *flag = 0x1234; + prIwReqData->param.value = 0x1230; + + return 1; +} + +/*----------------------------------------------------------------------------*/ +/*! +* \brief Private ioctl get int handler. +* +* \param[in] pDev Net device requested. +* \param[out] pIwReq Pointer to iwreq structure. +* \param[in] prIwReqData The ioctl req structure, use the field of sub-command. +* \param[out] pcExtra The buffer with put the return value +* +* \retval 0 For success. +* \retval -EOPNOTSUPP If cmd is not supported. +* \retval -EFAULT For fail. +* +*/ +/*----------------------------------------------------------------------------*/ +static int priv_get_int(IN struct net_device *prNetDev, + IN struct iw_request_info *prIwReqInfo, + IN union iwreq_data *prIwReqData, IN OUT char *pcExtra) +{ + int status = 0; + prIwReqData->mode = 0xabcd; + return status; +} /* __priv_get_int */ + +static int priv_set_struct(IN struct net_device *prNetDev, + IN struct iw_request_info *prIwReqInfo, + IN union iwreq_data *prIwReqData, IN OUT char *pcExtra) +{ + ECRNX_PRINT("cmd=%x, flags=%x\n", + prIwReqInfo->cmd, prIwReqInfo->flags); + ECRNX_PRINT("mode=%x, flags=%x\n", + prIwReqData->mode, prIwReqData->data.flags); + + return 0; + //return compat_priv(prNetDev, prIwReqInfo, + // prIwReqData, pcExtra, __priv_set_struct); +} + +static int priv_get_struct(IN struct net_device *prNetDev, + IN struct iw_request_info *prIwReqInfo, + IN union iwreq_data *prIwReqData, IN OUT char *pcExtra) +{ + ECRNX_PRINT("cmd=%x, flags=%x\n", + prIwReqInfo->cmd, prIwReqInfo->flags); + ECRNX_PRINT("mode=%x, flags=%x\n", + prIwReqData->mode, prIwReqData->data.flags); + + prIwReqData->data.length = 6; + memcpy(pcExtra, "ecrnx", 6); + return 0; + +} + +static int priv_get_mac(IN struct net_device *prNetDev, + IN struct iw_request_info *prIwReqInfo, + IN union iwreq_data *prIwReqData, IN OUT char *pcExtra) +{ + struct sockaddr *dst = (struct sockaddr *) pcExtra; + struct ecrnx_vif *vif; + dbg_req_t req; + + req.dbg_level = DBG_TYPE_D; + req.direct = 0; + + vif = netdev_priv(prNetDev); + //send cmd to slave + ECRNX_PRINT("priv_get_mac: send cmd to slave \n"); + host_send(&req, sizeof(dbg_req_t), TX_FLAG_IWPRIV_IE); + //wait for slave confirm + vif->rxdatas = 0; + wait_event_interruptible_timeout(vif->rxdataq, vif->rxdatas, 2*HZ); + + ECRNX_PRINT("priv_get_mac: rx_len:%d \n", vif->rxlen); + if (!vif->rxlen) + return -1; + + prIwReqData->data.length = vif->rxlen; + memcpy(dst->sa_data, vif->rxdata, vif->rxlen); + dst->sa_family = 1; + prIwReqData->data.length = 1; + + return 0; +} + +static int priv_get_vers(IN struct net_device *prNetDev, + IN struct iw_request_info *prIwReqInfo, + IN union iwreq_data *prIwReqData, IN OUT char *pcExtra) +{ + ECRNX_PRINT("get vers cmd=%x, flags=%x\n", prIwReqInfo->cmd, prIwReqInfo->flags); + ECRNX_PRINT("mode=%x, flags=%x\n", prIwReqData->mode, prIwReqData->data.flags); + + memcpy(pcExtra, "1.0.1", 6); + prIwReqData->data.length = 6; + + return 0; +} + + +static int priv_set_debug_level(IN struct net_device *prNetDev, + IN struct iw_request_info *prIwReqInfo, + IN union iwreq_data *prIwReqData, IN OUT char *pcExtra) +{ + ECRNX_PRINT("priv_set_debug_level cmd=%x, flags=%x\n", + prIwReqInfo->cmd, prIwReqInfo->flags); + ECRNX_PRINT("mode=%x, flags=%x\n", + prIwReqData->mode, prIwReqData->data.flags); + + ECRNX_PRINT("param_value:%d \n", prIwReqData->param.value); + + ecrnx_dbg_level = prIwReqData->param.value; + return 0; +} +#endif + +#ifdef CONFIG_ECRNX_WIFO_CAIL +static int priv_amt(IN struct net_device *prNetDev, + IN struct iw_request_info *prIwReqInfo, + IN union iwreq_data *prIwReqData, IN OUT char *pcExtra) +{ + struct sockaddr *dst = (struct sockaddr *) pcExtra; + + if (amt_mode == false) { + ECRNX_ERR(" The current mode does not support the AMT commands!!\n"); + return -1; + } +// printk("buff:%s, len:%d\n", prIwReqData->data.pointer, prIwReqData->data.length); + //send cmd to slave + char *reqdata = kzalloc(prIwReqData->data.length, GFP_KERNEL); + if (!reqdata){ + return 0; + } + if (copy_from_user(reqdata, prIwReqData->data.pointer, prIwReqData->data.length)) { + return 0; + } + host_send(reqdata, prIwReqData->data.length, TX_FLAG_AMT_IWPRIV_IE); + kfree(reqdata); + + //wait for slave confirm + amt_vif.rxdatas = 0; + amt_vif.rxlen = 0; + + wait_event_interruptible_timeout(amt_vif.rxdataq, amt_vif.rxdatas, 2*HZ); + + ECRNX_PRINT("rxdatas: rx_len:%d, rxdata:[%s]\n", amt_vif.rxlen,amt_vif.rxdata); + if (!amt_vif.rxdatas){ + return -1; + } + + prIwReqData->data.length = amt_vif.rxlen; + memcpy(dst->sa_data, amt_vif.rxdata, amt_vif.rxlen); + dst->sa_family = 1; + memcpy(pcExtra, amt_vif.rxdata, amt_vif.rxlen); + + return 0; +} +#endif + +static struct ecrnx_vif *get_priv_vif(struct ecrnx_hw *ecrnx_hw) +{ + return ecrnx_hw->vif_table[0]; +} + +void priv_copy_data_wakeup(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb) +{ + struct ecrnx_vif* ecrnx_vif = get_priv_vif(ecrnx_hw); + + ECRNX_PRINT("iw_cfm vif_start:%d, vif_monitor:%d \n", ecrnx_hw->vif_started, ecrnx_hw->monitor_vif); + //print_hex_dump(KERN_INFO, DBG_PREFIX_IW_CFM, DUMP_PREFIX_ADDRESS, 32, 1, skb->data, skb->len, false); + if (ECRNX_RXSIZE > skb->len) { + ecrnx_vif->rxlen = skb->len; + } else { + ecrnx_vif->rxlen = ECRNX_RXSIZE; + } + + memcpy(ecrnx_vif->rxdata, skb->data, ecrnx_vif->rxlen); + ecrnx_vif->rxdatas = 1; + wake_up(&ecrnx_vif->rxdataq); +} + +static int priv_wd(IN struct net_device *prNetDev, + IN struct iw_request_info *prIwReqInfo, + IN union iwreq_data *prIwReqData, IN OUT char *pcExtra) +{ + struct sockaddr *dst = (struct sockaddr *) pcExtra; + struct ecrnx_vif *vif; + + //printk("priv_wd:%s, len:%d\n", prIwReqData->data.pointer, prIwReqData->data.length); + //send cmd to slave + char *reqdata = kzalloc(prIwReqData->data.length, GFP_KERNEL); + if (!reqdata){ + return 0; + } + + if (copy_from_user(reqdata, prIwReqData->data.pointer, prIwReqData->data.length)) { + return 0; + } + host_send(reqdata, prIwReqData->data.length, TX_FLAG_IWPRIV_IE); + kfree(reqdata); + + //wait for slave confirm + vif = netdev_priv(prNetDev); + vif = get_priv_vif(vif->ecrnx_hw); + vif->rxdatas = 0; + wait_event_interruptible_timeout(vif->rxdataq, vif->rxdatas, 2*HZ); + + if (!vif->rxdatas) + return -1; + + ECRNX_PRINT("priv_wd: rx_len:%d rxdata:[%s]\n", vif->rxlen, vif->rxdata); + prIwReqData->data.length = vif->rxlen; + memcpy(dst->sa_data, vif->rxdata, vif->rxlen); + dst->sa_family = 1; + memcpy(pcExtra, vif->rxdata, vif->rxlen); + + return 0; +} + +/* + * Structures to export the Wireless Handlers + */ +static const struct iw_priv_args ecrnx_wext_private_args[] = { +#ifdef CONFIG_ECRNX_WIFO_CAIL + {IOCTL_IWPRIV_AMT, IW_PRIV_TYPE_CHAR | 2000, IW_PRIV_TYPE_CHAR | 2000, "amt"}, +#endif + {IOCTL_IWPRIV_WD, IW_PRIV_TYPE_CHAR | 2000, IW_PRIV_TYPE_CHAR | 2000, "wd"}, +}; + +const iw_handler ecrnx_wext_private_handler[] = { +#ifdef CONFIG_ECRNX_WIFO_CAIL + [IOCTL_IWPRIV_AMT - SIOCIWFIRSTPRIV] = priv_amt, +#endif + [IOCTL_IWPRIV_WD - SIOCIWFIRSTPRIV] = priv_wd, +}; + +#endif + +/*------------------------------------------------------------------*/ +/* +* Commit handler : called after a bunch of SET operations +*/ +static int ecrnx_wext_config_commit(struct net_device *dev, + struct iw_request_info *info, /* NULL */ + void *zwrq, /* NULL */ + char *extra) /* NULL */ +{ + return 1; +} + +static int ecrnx_wext_get_name(struct net_device *dev, + struct iw_request_info *info, + char *cwrq, + char *extra) +{ + strcpy(cwrq, "IEEE 802.11"); + return 0; +} + +/*------------------------------------------------------------------*/ +/* + * Wireless Handler : set frequency + */ +static int ecrnx_wext_set_freq(struct net_device *dev, + struct iw_request_info *info, + struct iw_freq *fwrq, + char *extra) +{ + int rc = -EINPROGRESS; /* Call commit handler */ + ECRNX_PRINT("fwrq->e:%d, fwrq->m:%d \n", fwrq->e, fwrq->m); + return rc; +} + +/*------------------------------------------------------------------*/ +/* + * Wireless Handler : get frequency + */ +static int ecrnx_wext_get_freq(struct net_device *dev, + struct iw_request_info *info, + struct iw_freq *fwrq, + char *extra) +{ + fwrq->m = 100000 * + 2.412; + fwrq->e = 1; + return 0; +} + +/*------------------------------------------------------------------*/ +/* + * Wireless Handler : set Mode of Operation + */ +static int ecrnx_wext_set_mode(struct net_device *dev, + struct iw_request_info *info, + __u32 *uwrq, + char *extra) +{ + ECRNX_PRINT("*uwrq:%d \n", *uwrq); + return -EINPROGRESS; /* Call commit handler */ +} + +/*------------------------------------------------------------------*/ +/* + * Wireless Handler : get Mode of Operation + */ +static int ecrnx_wext_get_mode(struct net_device *dev, + struct iw_request_info *info, + __u32 *uwrq, + char *extra) +{ + *uwrq = 0xFFEE; + return 0; +} + +static const iw_handler ecrnx_wext_handler[] = +{ + (iw_handler) ecrnx_wext_config_commit, /* SIOCSIWCOMMIT */ + (iw_handler) ecrnx_wext_get_name, /* SIOCGIWNAME */ + (iw_handler) NULL, /* SIOCSIWNWID */ + (iw_handler) NULL, /* SIOCGIWNWID */ + (iw_handler) ecrnx_wext_set_freq, /* SIOCSIWFREQ */ + (iw_handler) ecrnx_wext_get_freq, /* SIOCGIWFREQ */ + (iw_handler) ecrnx_wext_set_mode, /* SIOCSIWMODE */ + (iw_handler) ecrnx_wext_get_mode, /* SIOCGIWMODE */ +}; + +const struct iw_handler_def ecrnx_wext_handler_def = +{ + .num_standard = ARRAY_SIZE(ecrnx_wext_handler), + .standard = ecrnx_wext_handler, +#ifdef CONFIG_WEXT_PRIV + .num_private = ARRAY_SIZE(ecrnx_wext_private_handler), + .num_private_args = ARRAY_SIZE(ecrnx_wext_private_args), + .private = ecrnx_wext_private_handler, + .private_args = ecrnx_wext_private_args, +#endif +}; +#endif diff --git a/drivers/net/wireless/eswin/ecrnx_mod_params.c b/drivers/net/wireless/eswin/ecrnx_mod_params.c new file mode 100644 index 000000000000..5d6a722b7d45 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_mod_params.c @@ -0,0 +1,1408 @@ +/** +****************************************************************************** +* +* @file ecrnx_mod_params.c +* +* @brief Set configuration according to modules parameters +* +* Copyright (C) ESWIN 2015-2020 +* +****************************************************************************** +*/ +#include <linux/module.h> +#include <linux/rtnetlink.h> + +#include "ecrnx_defs.h" +#include "ecrnx_tx.h" +#include "hal_desc.h" +#include "ecrnx_cfgfile.h" +#include "reg_access.h" +#include "ecrnx_compat.h" + +#ifdef CONFIG_ECRNX_SOFTMAC +#define COMMON_PARAM(name, default_softmac, default_fullmac) \ + .name = default_softmac, +#define SOFTMAC_PARAM(name, default) .name = default, +#define FULLMAC_PARAM(name, default) +#else +#define COMMON_PARAM(name, default_softmac, default_fullmac) \ + .name = default_fullmac, +#define SOFTMAC_PARAM(name, default) +#define FULLMAC_PARAM(name, default) .name = default, +#endif /* CONFIG_ECRNX_SOFTMAC */ + +struct ecrnx_mod_params ecrnx_mod_params = { + /* common parameters */ + COMMON_PARAM(ht_on, true, true) + COMMON_PARAM(vht_on, false, false) + COMMON_PARAM(he_on, true, true) +#ifdef CONFIG_6600_HAL + COMMON_PARAM(he_mcs_map, IEEE80211_HE_MCS_SUPPORT_0_7, IEEE80211_HE_MCS_SUPPORT_0_7) +#else + COMMON_PARAM(he_mcs_map, IEEE80211_HE_MCS_SUPPORT_0_9, IEEE80211_HE_MCS_SUPPORT_0_9) +#endif + COMMON_PARAM(he_ul_on, false, false) + COMMON_PARAM(ldpc_on, false, false) + COMMON_PARAM(stbc_on, false, false) + COMMON_PARAM(gf_rx_on, true, true) + COMMON_PARAM(phy_cfg, 0, 0) + COMMON_PARAM(uapsd_timeout, 300, 300) + COMMON_PARAM(ap_uapsd_on, true, true) + COMMON_PARAM(sgi, true, true) + COMMON_PARAM(sgi80, true, true) + COMMON_PARAM(use_2040, 1, 1) + COMMON_PARAM(nss, 1, 1) + COMMON_PARAM(amsdu_rx_max, 2, 2) + COMMON_PARAM(bfmee, true, true) + COMMON_PARAM(bfmer, true, true) + COMMON_PARAM(mesh, true, true) + COMMON_PARAM(murx, true, true) + COMMON_PARAM(mutx, true, true) + COMMON_PARAM(mutx_on, true, true) + COMMON_PARAM(use_80, false, false) + COMMON_PARAM(custregd, false, false) + COMMON_PARAM(custchan, false, false) + COMMON_PARAM(roc_dur_max, 500, 500) + COMMON_PARAM(listen_itv, 0, 0) + COMMON_PARAM(listen_bcmc, true, true) + COMMON_PARAM(lp_clk_ppm, 20, 20) + COMMON_PARAM(ps_on, true, true) + COMMON_PARAM(tx_lft, ECRNX_TX_LIFETIME_MS, ECRNX_TX_LIFETIME_MS) + COMMON_PARAM(amsdu_maxnb, NX_TX_PAYLOAD_MAX, NX_TX_PAYLOAD_MAX) + // By default, only enable UAPSD for Voice queue (see IEEE80211_DEFAULT_UAPSD_QUEUE comment) + COMMON_PARAM(uapsd_queues, IEEE80211_WMM_IE_STA_QOSINFO_AC_VO, IEEE80211_WMM_IE_STA_QOSINFO_AC_VO) + COMMON_PARAM(tdls, true, true) + COMMON_PARAM(uf, true, true) + COMMON_PARAM(ftl, "", "") + COMMON_PARAM(dpsm, true, true) + COMMON_PARAM(tx_to_bk, 0, 0) + COMMON_PARAM(tx_to_be, 0, 0) + COMMON_PARAM(tx_to_vi, 0, 0) + COMMON_PARAM(tx_to_vo, 0, 0) + + /* SOFTMAC only parameters */ + SOFTMAC_PARAM(mfp_on, false) + SOFTMAC_PARAM(gf_on, false) + SOFTMAC_PARAM(bwsig_on, true) + SOFTMAC_PARAM(dynbw_on, true) + SOFTMAC_PARAM(agg_tx, true) + SOFTMAC_PARAM(amsdu_force, 0) + SOFTMAC_PARAM(rc_probes_on, false) + SOFTMAC_PARAM(cmon, true) + SOFTMAC_PARAM(hwscan, true) + SOFTMAC_PARAM(autobcn, true) + + /* FULLMAC only parameters */ + FULLMAC_PARAM(ant_div, false) +}; + +#ifdef CONFIG_ECRNX_SOFTMAC +/* SOFTMAC specific parameters */ +module_param_named(mfp_on, ecrnx_mod_params.mfp_on, bool, S_IRUGO); +MODULE_PARM_DESC(mfp_on, "Enable MFP (11w) (Default: 0)"); + +module_param_named(gf_on, ecrnx_mod_params.gf_on, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(gf_on, "Try TXing Green Field if peer supports it (Default: 0)"); + +module_param_named(bwsig_on, ecrnx_mod_params.bwsig_on, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(bwsig_on, "Enable bandwidth signaling (VHT tx) (Default: 1)"); + +module_param_named(dynbw_on, ecrnx_mod_params.dynbw_on, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(dynbw_on, "Enable dynamic bandwidth (VHT tx) (Default: 1)"); + +module_param_named(agg_tx, ecrnx_mod_params.agg_tx, bool, S_IRUGO); +MODULE_PARM_DESC(agg_tx, "Use A-MPDU in TX (Default: 1)"); + +module_param_named(amsdu_force, ecrnx_mod_params.amsdu_force, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(amsdu_force, "Use A-MSDU in TX: 0-if advertised, 1-yes, 2-no (Default: 0)"); + +module_param_named(rc_probes_on, ecrnx_mod_params.rc_probes_on, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(rc_probes_on, "IEEE80211_TX_CTL_RATE_CTRL_PROBE is 1st in AMPDU (Default: 0)"); + +module_param_named(cmon, ecrnx_mod_params.cmon, bool, S_IRUGO); +MODULE_PARM_DESC(cmon, "Connection monitoring work handled by the FW (Default: 1)"); + +module_param_named(hwscan, ecrnx_mod_params.hwscan, bool, S_IRUGO); +MODULE_PARM_DESC(hwscan, "Scan work handled by the FW (Default: 1)"); + +module_param_named(autobcn, ecrnx_mod_params.autobcn, bool, S_IRUGO); +MODULE_PARM_DESC(autobcn, "Beacon transmission done autonomously by the FW (Default: 1)"); + +#else +/* FULLMAC specific parameters*/ +module_param_named(ant_div, ecrnx_mod_params.ant_div, bool, S_IRUGO); +MODULE_PARM_DESC(ant_div, "Enable Antenna Diversity (Default: 0)"); +#endif /* CONFIG_ECRNX_SOFTMAC */ + +module_param_named(ht_on, ecrnx_mod_params.ht_on, bool, S_IRUGO); +MODULE_PARM_DESC(ht_on, "Enable HT (Default: 1)"); + +module_param_named(vht_on, ecrnx_mod_params.vht_on, bool, S_IRUGO); +MODULE_PARM_DESC(vht_on, "Enable VHT (Default: 1)"); + +module_param_named(he_on, ecrnx_mod_params.he_on, bool, S_IRUGO); +MODULE_PARM_DESC(he_on, "Enable HE (Default: 1)"); + +module_param_named(mcs_map, ecrnx_mod_params.mcs_map, int, S_IRUGO); +MODULE_PARM_DESC(mcs_map, "VHT MCS map value 0: MCS0_7, 1: MCS0_8, 2: MCS0_9" + " (Default: 2)"); + +module_param_named(he_mcs_map, ecrnx_mod_params.he_mcs_map, int, S_IRUGO); +MODULE_PARM_DESC(he_mcs_map, "HE MCS map value 0: MCS0_7, 1: MCS0_9, 2: MCS0_11" + " (Default: 2)"); + +module_param_named(he_ul_on, ecrnx_mod_params.he_ul_on, bool, S_IRUGO); +MODULE_PARM_DESC(he_ul_on, "Enable HE OFDMA UL (Default: 0)"); + +module_param_named(amsdu_maxnb, ecrnx_mod_params.amsdu_maxnb, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(amsdu_maxnb, "Maximum number of MSDUs inside an A-MSDU in TX: (Default: NX_TX_PAYLOAD_MAX)"); + +module_param_named(ps_on, ecrnx_mod_params.ps_on, bool, S_IRUGO); +MODULE_PARM_DESC(ps_on, "Enable PowerSaving (Default: 1-Enabled)"); + +module_param_named(tx_lft, ecrnx_mod_params.tx_lft, int, 0644); +MODULE_PARM_DESC(tx_lft, "Tx lifetime (ms) - setting it to 0 disables retries " + "(Default: "__stringify(ECRNX_TX_LIFETIME_MS)")"); + +module_param_named(ldpc_on, ecrnx_mod_params.ldpc_on, bool, S_IRUGO); +MODULE_PARM_DESC(ldpc_on, "Enable LDPC (Default: 1)"); + +module_param_named(stbc_on, ecrnx_mod_params.stbc_on, bool, S_IRUGO); +MODULE_PARM_DESC(stbc_on, "Enable STBC in RX (Default: 1)"); + +module_param_named(gf_rx_on, ecrnx_mod_params.gf_rx_on, bool, S_IRUGO); +MODULE_PARM_DESC(gf_rx_on, "Enable HT greenfield in reception (Default: 1)"); + +module_param_named(phycfg, ecrnx_mod_params.phy_cfg, int, S_IRUGO); +MODULE_PARM_DESC(phycfg, "Main RF Path (Default: 0)"); + +module_param_named(uapsd_timeout, ecrnx_mod_params.uapsd_timeout, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(uapsd_timeout, + "UAPSD Timer timeout, in ms (Default: 300). If 0, UAPSD is disabled"); + +module_param_named(uapsd_queues, ecrnx_mod_params.uapsd_queues, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(uapsd_queues, "UAPSD Queues, integer value, must be seen as a bitfield\n" + " Bit 0 = VO\n" + " Bit 1 = VI\n" + " Bit 2 = BK\n" + " Bit 3 = BE\n" + " -> uapsd_queues=7 will enable uapsd for VO, VI and BK queues"); + +module_param_named(ap_uapsd_on, ecrnx_mod_params.ap_uapsd_on, bool, S_IRUGO); +MODULE_PARM_DESC(ap_uapsd_on, "Enable UAPSD in AP mode (Default: 1)"); + +module_param_named(sgi, ecrnx_mod_params.sgi, bool, S_IRUGO); +MODULE_PARM_DESC(sgi, "Advertise Short Guard Interval support (Default: 1)"); + +module_param_named(sgi80, ecrnx_mod_params.sgi80, bool, S_IRUGO); +MODULE_PARM_DESC(sgi80, "Advertise Short Guard Interval support for 80MHz (Default: 1)"); + +module_param_named(use_2040, ecrnx_mod_params.use_2040, bool, S_IRUGO); +MODULE_PARM_DESC(use_2040, "Enable 40MHz (Default: 1)"); + +module_param_named(use_80, ecrnx_mod_params.use_80, bool, S_IRUGO); +MODULE_PARM_DESC(use_80, "Enable 80MHz (Default: 1)"); + +module_param_named(custregd, ecrnx_mod_params.custregd, bool, S_IRUGO); +MODULE_PARM_DESC(custregd, + "Use permissive custom regulatory rules (for testing ONLY) (Default: 0)"); + +module_param_named(custchan, ecrnx_mod_params.custchan, bool, S_IRUGO); +MODULE_PARM_DESC(custchan, + "Extend channel set to non-standard channels (for testing ONLY) (Default: 0)"); + +module_param_named(nss, ecrnx_mod_params.nss, int, S_IRUGO); +MODULE_PARM_DESC(nss, "1 <= nss <= 2 : Supported number of Spatial Streams (Default: 2)"); + +module_param_named(amsdu_rx_max, ecrnx_mod_params.amsdu_rx_max, int, S_IRUGO); +MODULE_PARM_DESC(amsdu_rx_max, "0 <= amsdu_rx_max <= 2 : Maximum A-MSDU size supported in RX\n" + " 0: 3895 bytes\n" + " 1: 7991 bytes\n" + " 2: 11454 bytes\n" + " This value might be reduced according to the FW capabilities.\n" + " Default: 2"); + +module_param_named(bfmee, ecrnx_mod_params.bfmee, bool, S_IRUGO); +MODULE_PARM_DESC(bfmee, "Enable Beamformee Capability (Default: 1-Enabled)"); + +module_param_named(bfmer, ecrnx_mod_params.bfmer, bool, S_IRUGO); +MODULE_PARM_DESC(bfmer, "Enable Beamformer Capability (Default: 1-Enabled)"); + +module_param_named(mesh, ecrnx_mod_params.mesh, bool, S_IRUGO); +MODULE_PARM_DESC(mesh, "Enable Meshing Capability (Default: 1-Enabled)"); + +module_param_named(murx, ecrnx_mod_params.murx, bool, S_IRUGO); +MODULE_PARM_DESC(murx, "Enable MU-MIMO RX Capability (Default: 1-Enabled)"); + +module_param_named(mutx, ecrnx_mod_params.mutx, bool, S_IRUGO); +MODULE_PARM_DESC(mutx, "Enable MU-MIMO TX Capability (Default: 1-Enabled)"); + +module_param_named(mutx_on, ecrnx_mod_params.mutx_on, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(mutx_on, "Enable MU-MIMO transmissions (Default: 1-Enabled)"); + +module_param_named(roc_dur_max, ecrnx_mod_params.roc_dur_max, int, S_IRUGO); +MODULE_PARM_DESC(roc_dur_max, "Maximum Remain on Channel duration"); + +module_param_named(listen_itv, ecrnx_mod_params.listen_itv, int, S_IRUGO); +MODULE_PARM_DESC(listen_itv, "Maximum listen interval"); + +module_param_named(listen_bcmc, ecrnx_mod_params.listen_bcmc, bool, S_IRUGO); +MODULE_PARM_DESC(listen_bcmc, "Wait for BC/MC traffic following DTIM beacon"); + +module_param_named(lp_clk_ppm, ecrnx_mod_params.lp_clk_ppm, int, S_IRUGO); +MODULE_PARM_DESC(lp_clk_ppm, "Low Power Clock accuracy of the local device"); + +module_param_named(tdls, ecrnx_mod_params.tdls, bool, S_IRUGO); +MODULE_PARM_DESC(tdls, "Enable TDLS (Default: 1-Enabled)"); + +module_param_named(uf, ecrnx_mod_params.uf, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(uf, "Enable Unsupported HT Frame Logging (Default: 1-Enabled)"); + +module_param_named(ftl, ecrnx_mod_params.ftl, charp, S_IRUGO); +MODULE_PARM_DESC(ftl, "Firmware trace level (Default: \"\")"); + +module_param_named(dpsm, ecrnx_mod_params.dpsm, bool, S_IRUGO); +MODULE_PARM_DESC(dpsm, "Enable Dynamic PowerSaving (Default: 1-Enabled)"); +module_param_named(tx_to_bk, ecrnx_mod_params.tx_to_bk, int, S_IRUGO); +MODULE_PARM_DESC(tx_to_bk, + "TX timeout for BK, in ms (Default: 0, Max: 65535). If 0, default value is applied"); +module_param_named(tx_to_be, ecrnx_mod_params.tx_to_be, int, S_IRUGO); +MODULE_PARM_DESC(tx_to_be, + "TX timeout for BE, in ms (Default: 0, Max: 65535). If 0, default value is applied"); +module_param_named(tx_to_vi, ecrnx_mod_params.tx_to_vi, int, S_IRUGO); +MODULE_PARM_DESC(tx_to_vi, + "TX timeout for VI, in ms (Default: 0, Max: 65535). If 0, default value is applied"); +module_param_named(tx_to_vo, ecrnx_mod_params.tx_to_vo, int, S_IRUGO); +MODULE_PARM_DESC(tx_to_vo, + "TX timeout for VO, in ms (Default: 0, Max: 65535). If 0, default value is applied"); + +/* Regulatory rules */ +static struct ieee80211_regdomain ecrnx_regdom = { + .n_reg_rules = 2, + .alpha2 = "99", + .reg_rules = { + REG_RULE(2390 - 10, 2510 + 10, 40, 0, 1000, 0), + REG_RULE(5150 - 10, 5970 + 10, 80, 0, 1000, 0), + } +}; + +static const int mcs_map_to_rate[4][3] = { + [PHY_CHNL_BW_20][IEEE80211_VHT_MCS_SUPPORT_0_7] = 65, + [PHY_CHNL_BW_20][IEEE80211_VHT_MCS_SUPPORT_0_8] = 78, + [PHY_CHNL_BW_20][IEEE80211_VHT_MCS_SUPPORT_0_9] = 78, + [PHY_CHNL_BW_40][IEEE80211_VHT_MCS_SUPPORT_0_7] = 135, + [PHY_CHNL_BW_40][IEEE80211_VHT_MCS_SUPPORT_0_8] = 162, + [PHY_CHNL_BW_40][IEEE80211_VHT_MCS_SUPPORT_0_9] = 180, + [PHY_CHNL_BW_80][IEEE80211_VHT_MCS_SUPPORT_0_7] = 292, + [PHY_CHNL_BW_80][IEEE80211_VHT_MCS_SUPPORT_0_8] = 351, + [PHY_CHNL_BW_80][IEEE80211_VHT_MCS_SUPPORT_0_9] = 390, + [PHY_CHNL_BW_160][IEEE80211_VHT_MCS_SUPPORT_0_7] = 585, + [PHY_CHNL_BW_160][IEEE80211_VHT_MCS_SUPPORT_0_8] = 702, + [PHY_CHNL_BW_160][IEEE80211_VHT_MCS_SUPPORT_0_9] = 780, +}; + +#define MAX_VHT_RATE(map, nss, bw) (mcs_map_to_rate[bw][map] * (nss)) + +#if defined(CONFIG_ECRNX_HE) +struct ieee80211_sta_he_cap ecrnx_he_cap; +#endif + +/** + * Do some sanity check + * + */ +static int ecrnx_check_fw_hw_feature(struct ecrnx_hw *ecrnx_hw, + struct wiphy *wiphy) +{ + u32_l sys_feat = ecrnx_hw->version_cfm.features; + u32_l mac_feat = ecrnx_hw->version_cfm.version_machw_1; + u32_l phy_feat = ecrnx_hw->version_cfm.version_phy_1; + u32_l phy_vers = ecrnx_hw->version_cfm.version_phy_2; + u16_l max_sta_nb = ecrnx_hw->version_cfm.max_sta_nb; + u8_l max_vif_nb = ecrnx_hw->version_cfm.max_vif_nb; + int bw, res = 0; + int amsdu_rx; + + if (!ecrnx_hw->mod_params->custregd) + ecrnx_hw->mod_params->custchan = false; + + if (ecrnx_hw->mod_params->custchan) { +#ifdef CONFIG_ECRNX_SOFTMAC + ecrnx_hw->mod_params->autobcn = false; + ecrnx_hw->mod_params->hwscan = false; + sys_feat &= ~BIT(MM_FEAT_CMON_BIT); +#endif + ecrnx_hw->mod_params->mesh = false; + ecrnx_hw->mod_params->tdls = false; + } + +#ifdef CONFIG_ECRNX_SOFTMAC + if (sys_feat & BIT(MM_FEAT_UMAC_BIT)) { + wiphy_err(wiphy, "Loading fullmac firmware with softmac driver\n"); + res = -1; + } + + if (sys_feat & BIT(MM_FEAT_AUTOBCN_BIT) && + !ecrnx_hw->mod_params->autobcn) { + wiphy_err(wiphy, + "Auto beacon enabled in firmware but disabled in driver\n"); + res = -1; + } + + if (!!(sys_feat & BIT(MM_FEAT_HWSCAN_BIT)) != + !!ecrnx_hw->mod_params->hwscan) { + wiphy_err(wiphy, + "hwscan %sabled in firmware but %sabled in driver\n", + (sys_feat & BIT(MM_FEAT_HWSCAN_BIT)) ? "en" : "dis", + ecrnx_hw->mod_params->hwscan ? "en" : "dis"); + res = -1; + } + + if (sys_feat & BIT(MM_FEAT_CMON_BIT)) { + ieee80211_hw_set(ecrnx_hw->hw, CONNECTION_MONITOR); + } + + /* AMPDU (non)support implies different shared structure definition + so insure that fw and drv have consistent compilation option */ + if (sys_feat & BIT(MM_FEAT_AMPDU_BIT)) { +#ifndef CONFIG_ECRNX_AGG_TX + wiphy_err(wiphy, + "AMPDU enabled in firmware but support not compiled in driver\n"); + res = -1; +#endif /* CONFIG_ECRNX_AGG_TX */ + } else { +#ifdef CONFIG_ECRNX_AGG_TX + wiphy_err(wiphy, + "AMPDU disabled in firmware but support compiled in driver\n"); + res = -1; +#else + ecrnx_hw->mod_params->agg_tx = false; +#endif /* CONFIG_ECRNX_AGG_TX */ + } + + if (!(sys_feat & BIT(MM_FEAT_DPSM_BIT))) { + ecrnx_hw->mod_params->dpsm = false; + } + +#else /* check for FULLMAC */ + + if (!(sys_feat & BIT(MM_FEAT_UMAC_BIT))) { + wiphy_err(wiphy, + "Loading softmac firmware with fullmac driver\n"); + res = -1; + } + + if (!(sys_feat & BIT(MM_FEAT_ANT_DIV_BIT))) { + ecrnx_hw->mod_params->ant_div = false; + } + +#endif /* CONFIG_ECRNX_SOFTMAC */ + + if (!(sys_feat & BIT(MM_FEAT_VHT_BIT))) { + ecrnx_hw->mod_params->vht_on = false; + } + + // Check if HE is supported + if (!(sys_feat & BIT(MM_FEAT_HE_BIT))) { + ecrnx_hw->mod_params->he_on = false; + ecrnx_hw->mod_params->he_ul_on = false; + } + + if (!(sys_feat & BIT(MM_FEAT_PS_BIT))) { + ecrnx_hw->mod_params->ps_on = false; + } + + /* AMSDU (non)support implies different shared structure definition + so insure that fw and drv have consistent compilation option */ + if (sys_feat & BIT(MM_FEAT_AMSDU_BIT)) { +#ifndef CONFIG_ECRNX_SPLIT_TX_BUF + wiphy_err(wiphy, + "AMSDU enabled in firmware but support not compiled in driver\n"); + res = -1; +#else + /* Adjust amsdu_maxnb so that it stays in allowed bounds */ + ecrnx_adjust_amsdu_maxnb(ecrnx_hw); +#endif /* CONFIG_ECRNX_SPLIT_TX_BUF */ + } else { +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + wiphy_err(wiphy, + "AMSDU disabled in firmware but support compiled in driver\n"); + res = -1; +#endif /* CONFIG_ECRNX_SPLIT_TX_BUF */ + } + + if (!(sys_feat & BIT(MM_FEAT_UAPSD_BIT))) { + ecrnx_hw->mod_params->uapsd_timeout = 0; + } + + if (!(sys_feat & BIT(MM_FEAT_BFMEE_BIT))) { + ecrnx_hw->mod_params->bfmee = false; + } + + if ((sys_feat & BIT(MM_FEAT_BFMER_BIT))) { +#ifndef CONFIG_ECRNX_BFMER + wiphy_err(wiphy, + "BFMER enabled in firmware but support not compiled in driver\n"); + res = -1; +#endif /* CONFIG_ECRNX_BFMER */ + // Check PHY and MAC HW BFMER support and update parameter accordingly + if (!(phy_feat & MDM_BFMER_BIT) || !(mac_feat & NXMAC_BFMER_BIT)) { + ecrnx_hw->mod_params->bfmer = false; + // Disable the feature in the bitfield so that it won't be displayed + sys_feat &= ~BIT(MM_FEAT_BFMER_BIT); + } + } else { +#ifdef CONFIG_ECRNX_BFMER + wiphy_err(wiphy, + "BFMER disabled in firmware but support compiled in driver\n"); + res = -1; +#else + ecrnx_hw->mod_params->bfmer = false; +#endif /* CONFIG_ECRNX_BFMER */ + } + + if (!(sys_feat & BIT(MM_FEAT_MESH_BIT))) { + ecrnx_hw->mod_params->mesh = false; + } + + if (!(sys_feat & BIT(MM_FEAT_TDLS_BIT))) { + ecrnx_hw->mod_params->tdls = false; + } + + if (!(sys_feat & BIT(MM_FEAT_UF_BIT))) { + ecrnx_hw->mod_params->uf = false; + } + +#ifdef CONFIG_ECRNX_FULLMAC + if ((sys_feat & BIT(MM_FEAT_MON_DATA_BIT))) { +#ifndef CONFIG_ECRNX_MON_DATA + wiphy_err(wiphy, + "Monitor+Data interface support (MON_DATA) is enabled in firmware but support not compiled in driver\n"); + res = -1; +#endif /* CONFIG_ECRNX_MON_DATA */ + } else { +#ifdef CONFIG_ECRNX_MON_DATA + wiphy_err(wiphy, + "Monitor+Data interface support (MON_DATA) disabled in firmware but support compiled in driver\n"); + res = -1; +#endif /* CONFIG_ECRNX_MON_DATA */ + } +#endif + + // Check supported AMSDU RX size + amsdu_rx = (sys_feat >> MM_AMSDU_MAX_SIZE_BIT0) & 0x03; + if (amsdu_rx < ecrnx_hw->mod_params->amsdu_rx_max) { + ecrnx_hw->mod_params->amsdu_rx_max = amsdu_rx; + } + + // Check supported BW + bw = (phy_feat & MDM_CHBW_MASK) >> MDM_CHBW_LSB; + // Check if 80MHz BW is supported + if (bw < 2) { + ecrnx_hw->mod_params->use_80 = false; + } + // Check if 40MHz BW is supported + if (bw < 1) + ecrnx_hw->mod_params->use_2040 = false; + + // 80MHz BW shall be disabled if 40MHz is not enabled + if (!ecrnx_hw->mod_params->use_2040) + ecrnx_hw->mod_params->use_80 = false; + + // Check if HT is supposed to be supported. If not, disable VHT/HE too + if (!ecrnx_hw->mod_params->ht_on) + { + ecrnx_hw->mod_params->vht_on = false; + ecrnx_hw->mod_params->he_on = false; + ecrnx_hw->mod_params->he_ul_on = false; + ecrnx_hw->mod_params->use_80 = false; + ecrnx_hw->mod_params->use_2040 = false; + } + + // LDPC is mandatory for HE40 and above, so if LDPC is not supported, then disable + // HE to use HT/VHT only + if (ecrnx_hw->mod_params->he_on && !ecrnx_hw->mod_params->ldpc_on) + { + ecrnx_hw->mod_params->use_80 = false; + /* ESWIN turns off 40M automotically when it is HE mode */ + //ecrnx_hw->mod_params->use_2040 = false; + + } + + // HT greenfield is not supported in modem >= 3.0 + if (__MDM_MAJOR_VERSION(phy_vers) > 0) { +#ifdef CONFIG_ECRNX_SOFTMAC + ecrnx_hw->mod_params->gf_on = false; +#endif + ecrnx_hw->mod_params->gf_rx_on = false; + } + + if (!(sys_feat & BIT(MM_FEAT_MU_MIMO_RX_BIT)) || + !ecrnx_hw->mod_params->bfmee) { + ecrnx_hw->mod_params->murx = false; + } + + if ((sys_feat & BIT(MM_FEAT_MU_MIMO_TX_BIT))) { +#ifndef CONFIG_ECRNX_MUMIMO_TX + wiphy_err(wiphy, + "MU-MIMO TX enabled in firmware but support not compiled in driver\n"); + res = -1; +#endif /* CONFIG_ECRNX_MUMIMO_TX */ + if (!ecrnx_hw->mod_params->bfmer) + ecrnx_hw->mod_params->mutx = false; + // Check PHY and MAC HW MU-MIMO TX support and update parameter accordingly + else if (!(phy_feat & MDM_MUMIMOTX_BIT) || !(mac_feat & NXMAC_MU_MIMO_TX_BIT)) { + ecrnx_hw->mod_params->mutx = false; + // Disable the feature in the bitfield so that it won't be displayed + sys_feat &= ~BIT(MM_FEAT_MU_MIMO_TX_BIT); + } + } else { +#ifdef CONFIG_ECRNX_MUMIMO_TX + wiphy_err(wiphy, + "MU-MIMO TX disabled in firmware but support compiled in driver\n"); + res = -1; +#else + ecrnx_hw->mod_params->mutx = false; +#endif /* CONFIG_ECRNX_MUMIMO_TX */ + } + + if (sys_feat & BIT(MM_FEAT_WAPI_BIT)) { + ecrnx_enable_wapi(ecrnx_hw); + } + +#ifdef CONFIG_ECRNX_FULLMAC + if (sys_feat & BIT(MM_FEAT_MFP_BIT)) { + ecrnx_enable_mfp(ecrnx_hw); + } + if (mac_feat & NXMAC_GCMP_BIT) { + ecrnx_enable_gcmp(ecrnx_hw); + } +#endif + +#ifdef CONFIG_ECRNX_SOFTMAC +#define QUEUE_NAME "BEACON queue " +#else +#define QUEUE_NAME "Broadcast/Multicast queue " +#endif /* CONFIG_ECRNX_SOFTMAC */ + + if (sys_feat & BIT(MM_FEAT_BCN_BIT)) { +#if NX_TXQ_CNT == 4 + wiphy_err(wiphy, QUEUE_NAME + "enabled in firmware but support not compiled in driver\n"); + res = -1; +#endif /* NX_TXQ_CNT == 4 */ + } else { +#if NX_TXQ_CNT == 5 + wiphy_err(wiphy, QUEUE_NAME + "disabled in firmware but support compiled in driver\n"); + res = -1; +#endif /* NX_TXQ_CNT == 5 */ + } +#undef QUEUE_NAME + +#ifdef CONFIG_ECRNX_RADAR + if (sys_feat & BIT(MM_FEAT_RADAR_BIT)) { + /* Enable combination with radar detection */ + wiphy->n_iface_combinations++; + } +#endif /* CONFIG_ECRNX_RADAR */ + +#ifndef CONFIG_ECRNX_SDM + switch (__MDM_PHYCFG_FROM_VERS(phy_feat)) { + case MDM_PHY_CONFIG_TRIDENT: + ecrnx_hw->mod_params->nss = 1; + if ((ecrnx_hw->mod_params->phy_cfg < 0) || (ecrnx_hw->mod_params->phy_cfg > 2)) + ecrnx_hw->mod_params->phy_cfg = 2; + break; + case MDM_PHY_CONFIG_KARST: + case MDM_PHY_CONFIG_CATAXIA: + { + int nss_supp = (phy_feat & MDM_NSS_MASK) >> MDM_NSS_LSB; + if (ecrnx_hw->mod_params->nss > nss_supp) + ecrnx_hw->mod_params->nss = nss_supp; + if ((ecrnx_hw->mod_params->phy_cfg < 0) || (ecrnx_hw->mod_params->phy_cfg > 1)) + ecrnx_hw->mod_params->phy_cfg = 0; + } + break; + default: + WARN_ON(1); + break; + } +#endif /* CONFIG_ECRNX_SDM */ + + if ((ecrnx_hw->mod_params->nss < 1) || (ecrnx_hw->mod_params->nss > 2)) + ecrnx_hw->mod_params->nss = 1; + + + if ((ecrnx_hw->mod_params->mcs_map < 0) || (ecrnx_hw->mod_params->mcs_map > 2)) + ecrnx_hw->mod_params->mcs_map = 0; + +#define PRINT_ECRNX_PHY_FEAT(feat) \ + (phy_feat & MDM_##feat##_BIT ? "["#feat"]" : "") + wiphy_info(wiphy, "PHY features: [NSS=%d][CHBW=%d]%s%s%s%s%s%s%s\n", + (phy_feat & MDM_NSS_MASK) >> MDM_NSS_LSB, + 20 * (1 << ((phy_feat & MDM_CHBW_MASK) >> MDM_CHBW_LSB)), + (phy_feat & (MDM_LDPCDEC_BIT | MDM_LDPCENC_BIT)) == + (MDM_LDPCDEC_BIT | MDM_LDPCENC_BIT) ? "[LDPC]" : "", + PRINT_ECRNX_PHY_FEAT(VHT), + PRINT_ECRNX_PHY_FEAT(HE), + PRINT_ECRNX_PHY_FEAT(BFMER), + PRINT_ECRNX_PHY_FEAT(BFMEE), + PRINT_ECRNX_PHY_FEAT(MUMIMOTX), + PRINT_ECRNX_PHY_FEAT(MUMIMORX) + ); + +#define PRINT_ECRNX_FEAT(feat) \ + (sys_feat & BIT(MM_FEAT_##feat##_BIT) ? "["#feat"]" : "") + + wiphy_info(wiphy, "FW features: %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", + PRINT_ECRNX_FEAT(BCN), + PRINT_ECRNX_FEAT(AUTOBCN), + PRINT_ECRNX_FEAT(HWSCAN), + PRINT_ECRNX_FEAT(CMON), + PRINT_ECRNX_FEAT(MROLE), + PRINT_ECRNX_FEAT(RADAR), + PRINT_ECRNX_FEAT(PS), + PRINT_ECRNX_FEAT(UAPSD), + PRINT_ECRNX_FEAT(DPSM), + PRINT_ECRNX_FEAT(AMPDU), + PRINT_ECRNX_FEAT(AMSDU), + PRINT_ECRNX_FEAT(CHNL_CTXT), + PRINT_ECRNX_FEAT(REORD), + PRINT_ECRNX_FEAT(P2P), + PRINT_ECRNX_FEAT(P2P_GO), + PRINT_ECRNX_FEAT(UMAC), + PRINT_ECRNX_FEAT(VHT), + PRINT_ECRNX_FEAT(HE), + PRINT_ECRNX_FEAT(BFMEE), + PRINT_ECRNX_FEAT(BFMER), + PRINT_ECRNX_FEAT(WAPI), + PRINT_ECRNX_FEAT(MFP), + PRINT_ECRNX_FEAT(MU_MIMO_RX), + PRINT_ECRNX_FEAT(MU_MIMO_TX), + PRINT_ECRNX_FEAT(MESH), + PRINT_ECRNX_FEAT(TDLS), + PRINT_ECRNX_FEAT(ANT_DIV), + PRINT_ECRNX_FEAT(UF), + PRINT_ECRNX_FEAT(TWT)); +#undef PRINT_ECRNX_FEAT + + if(max_sta_nb != NX_REMOTE_STA_MAX) + { + wiphy_err(wiphy, "Different number of supported stations between driver and FW (%d != %d)\n", + NX_REMOTE_STA_MAX, max_sta_nb); + res = -1; + } + + if(max_vif_nb != NX_VIRT_DEV_MAX) + { + wiphy_err(wiphy, "Different number of supported virtual interfaces between driver and FW (%d != %d)\n", + NX_VIRT_DEV_MAX, max_vif_nb); + res = -1; + } + + return res; +} + +static void ecrnx_set_ppe_threshold(struct ecrnx_hw *ecrnx_hw, + struct ieee80211_sta_he_cap *he_cap) +{ + const u8_l PPE_THRES_INFO_OFT = 7; + const u8_l PPE_THRES_INFO_BIT_LEN = 6; + struct ppe_thres_info_tag + { + u8_l ppet16 : 3; + u8_l ppet8 : 3; + }__packed; + struct ppe_thres_field_tag + { + u8_l nsts : 3; + u8_l ru_idx_bmp : 4; + }; + int nss = ecrnx_hw->mod_params->nss; + struct ppe_thres_field_tag* ppe_thres_field = (struct ppe_thres_field_tag*) he_cap->ppe_thres; + struct ppe_thres_info_tag ppe_thres_info = {.ppet16 = 0, //BSPK + .ppet8 = 7 //None + }; + u8_l* ppe_thres_info_ptr = (u8_l*) &ppe_thres_info; + u16_l* ppe_thres_ptr = (u16_l*) he_cap->ppe_thres; + u8_l i, j, cnt, offset; + if (ecrnx_hw->mod_params->use_80) + { + ppe_thres_field->ru_idx_bmp = 7; + cnt = 3; + } + else + { + ppe_thres_field->ru_idx_bmp = 1; + cnt = 1; + } + ppe_thres_field->nsts = nss - 1; + for (i = 0; i < nss ; i++) + { + for (j = 0; j < cnt; j++){ + offset = (i * cnt + j) * PPE_THRES_INFO_BIT_LEN + PPE_THRES_INFO_OFT; + ppe_thres_ptr = (u16_l*)&he_cap->ppe_thres[offset / 8]; + *ppe_thres_ptr |= *ppe_thres_info_ptr << (offset % 8); + } + } +} + + +#ifdef CONFIG_ECRNX_SOFTMAC +static void ecrnx_set_softmac_flags(struct ecrnx_hw *ecrnx_hw) +{ + struct ieee80211_hw *hw = ecrnx_hw->hw; + int nss; +#ifdef CONFIG_MAC80211_AMSDUS_TX + ieee80211_hw_set(hw, TX_AMSDU); + ieee80211_hw_set(hw, TX_FRAG_LIST); + hw->max_tx_fragments = ecrnx_hw->mod_params->amsdu_maxnb; +#endif + + if (!ecrnx_hw->mod_params->autobcn) + ieee80211_hw_set(hw, HOST_BROADCAST_PS_BUFFERING); + + if (ecrnx_hw->mod_params->agg_tx) + ieee80211_hw_set(hw, AMPDU_AGGREGATION); + + if (ecrnx_hw->mod_params->cmon) + ieee80211_hw_set(hw, CONNECTION_MONITOR); + + if (ecrnx_hw->mod_params->hwscan) + ieee80211_hw_set(hw, CHANCTX_STA_CSA); + + if (ecrnx_hw->mod_params->ps_on) { + ieee80211_hw_set(hw, SUPPORTS_PS); + } + /* To disable the dynamic PS we say to the stack that we support it in + * HW. This will force mac80211 rely on us to handle this. */ + ieee80211_hw_set(hw, SUPPORTS_DYNAMIC_PS); + + if (ecrnx_hw->mod_params->mfp_on) + ieee80211_hw_set(hw, MFP_CAPABLE); + + nss = ecrnx_hw->mod_params->nss; + ecrnx_hw->phy.ctrlinfo_1.value = 0; + ecrnx_hw->phy.ctrlinfo_2.value = 0; + if (nss == 1) { + ecrnx_hw->phy.ctrlinfo_2.antennaSet = 1; + } else { + ecrnx_hw->phy.ctrlinfo_1.fecCoding = 0; + ecrnx_hw->phy.ctrlinfo_1.nTx = 1; + ecrnx_hw->phy.ctrlinfo_2.antennaSet = 3; + ecrnx_hw->phy.ctrlinfo_2.smmIndex = 1; + } + ecrnx_hw->phy.stbc_nss = nss >> 1; +} +#endif + +#ifdef CONFIG_ECRNX_5G +static void ecrnx_set_vht_capa(struct ecrnx_hw *ecrnx_hw, struct wiphy *wiphy) +{ + struct ieee80211_supported_band *band_5GHz = wiphy->bands[NL80211_BAND_5GHZ]; + int i; + int nss = ecrnx_hw->mod_params->nss; + int mcs_map; + int mcs_map_max; + int mcs_map_max_2ss_rx = IEEE80211_VHT_MCS_SUPPORT_0_9; + int mcs_map_max_2ss_tx = IEEE80211_VHT_MCS_SUPPORT_0_9; + int bw_max; + + if (!ecrnx_hw->mod_params->vht_on) + return; + + band_5GHz->vht_cap.vht_supported = true; + if (ecrnx_hw->mod_params->sgi80) + band_5GHz->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_80; + if (ecrnx_hw->mod_params->stbc_on) + band_5GHz->vht_cap.cap |= IEEE80211_VHT_CAP_RXSTBC_1; + if (ecrnx_hw->mod_params->ldpc_on) + band_5GHz->vht_cap.cap |= IEEE80211_VHT_CAP_RXLDPC; + if (ecrnx_hw->mod_params->bfmee) { + band_5GHz->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE; + band_5GHz->vht_cap.cap |= 3 << IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT; + } + if (nss > 1) + band_5GHz->vht_cap.cap |= IEEE80211_VHT_CAP_TXSTBC; + + // Update the AMSDU max RX size (not shifted as located at offset 0 of the VHT cap) + band_5GHz->vht_cap.cap |= ecrnx_hw->mod_params->amsdu_rx_max; + + if (ecrnx_hw->mod_params->bfmer) { + band_5GHz->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE; + /* Set number of sounding dimensions */ + band_5GHz->vht_cap.cap |= (nss - 1) << IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT; + } + if (ecrnx_hw->mod_params->murx) + band_5GHz->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE; + if (ecrnx_hw->mod_params->mutx) + band_5GHz->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE; + + /* + * MCS map: + * This capabilities are filled according to the mcs_map module parameter. + * However currently we have some limitations due to FPGA clock constraints + * that prevent always using the range of MCS that is defined by the + * parameter: + * - in RX, 2SS, we support up to MCS7 + * - in TX, 2SS, we support up to MCS8 + */ + // Get max supported BW + if (ecrnx_hw->mod_params->use_80) { + bw_max = PHY_CHNL_BW_80; + mcs_map_max_2ss_rx = IEEE80211_VHT_MCS_SUPPORT_0_7; + mcs_map_max_2ss_tx = IEEE80211_VHT_MCS_SUPPORT_0_8; + } else if (ecrnx_hw->mod_params->use_2040) + bw_max = PHY_CHNL_BW_40; + else + bw_max = PHY_CHNL_BW_20; + + // Check if MCS map should be limited to MCS0_8 due to the standard. Indeed in BW20, + // MCS9 is not supported in 1 and 2 SS + if (ecrnx_hw->mod_params->use_2040) + mcs_map_max = IEEE80211_VHT_MCS_SUPPORT_0_9; + else + mcs_map_max = IEEE80211_VHT_MCS_SUPPORT_0_8; + + mcs_map = min_t(int, ecrnx_hw->mod_params->mcs_map, mcs_map_max); + band_5GHz->vht_cap.vht_mcs.rx_mcs_map = cpu_to_le16(0); + for (i = 0; i < nss; i++) { + band_5GHz->vht_cap.vht_mcs.rx_mcs_map |= cpu_to_le16(mcs_map << (i*2)); + band_5GHz->vht_cap.vht_mcs.rx_highest = MAX_VHT_RATE(mcs_map, nss, bw_max); + mcs_map = min_t(int, mcs_map, mcs_map_max_2ss_rx); + } + for (; i < 8; i++) { + band_5GHz->vht_cap.vht_mcs.rx_mcs_map |= cpu_to_le16( + IEEE80211_VHT_MCS_NOT_SUPPORTED << (i*2)); + } + + mcs_map = min_t(int, ecrnx_hw->mod_params->mcs_map, mcs_map_max); + band_5GHz->vht_cap.vht_mcs.tx_mcs_map = cpu_to_le16(0); + for (i = 0; i < nss; i++) { + band_5GHz->vht_cap.vht_mcs.tx_mcs_map |= cpu_to_le16(mcs_map << (i*2)); + band_5GHz->vht_cap.vht_mcs.tx_highest = MAX_VHT_RATE(mcs_map, nss, bw_max); + mcs_map = min_t(int, mcs_map, mcs_map_max_2ss_tx); + } + for (; i < 8; i++) { + band_5GHz->vht_cap.vht_mcs.tx_mcs_map |= cpu_to_le16( + IEEE80211_VHT_MCS_NOT_SUPPORTED << (i*2)); + } + + if (!ecrnx_hw->mod_params->use_80) { +#ifdef CONFIG_VENDOR_ECRNX_VHT_NO80 + band_5GHz->vht_cap.cap |= IEEE80211_VHT_CAP_NOT_SUP_WIDTH_80; +#endif + band_5GHz->vht_cap.cap &= ~IEEE80211_VHT_CAP_SHORT_GI_80; + } +} +#else +static void ecrnx_set_vht_capa(struct ecrnx_hw *ecrnx_hw, struct wiphy *wiphy) +{ + struct ieee80211_supported_band *band_2GHz = wiphy->bands[NL80211_BAND_2GHZ]; + int i; + int nss = ecrnx_hw->mod_params->nss; + int mcs_map; + int mcs_map_max; + int mcs_map_max_2ss_rx = IEEE80211_VHT_MCS_SUPPORT_0_9; + int mcs_map_max_2ss_tx = IEEE80211_VHT_MCS_SUPPORT_0_9; + int bw_max; + + if (!ecrnx_hw->mod_params->vht_on) + return; + + band_2GHz->vht_cap.vht_supported = true; + if (ecrnx_hw->mod_params->sgi80) + band_2GHz->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_80; + if (ecrnx_hw->mod_params->stbc_on) + band_2GHz->vht_cap.cap |= IEEE80211_VHT_CAP_RXSTBC_1; + if (ecrnx_hw->mod_params->ldpc_on) + band_2GHz->vht_cap.cap |= IEEE80211_VHT_CAP_RXLDPC; + if (ecrnx_hw->mod_params->bfmee) { + band_2GHz->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) + band_2GHz->vht_cap.cap |= 3 << IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT; +#endif + } + if (nss > 1) + band_2GHz->vht_cap.cap |= IEEE80211_VHT_CAP_TXSTBC; + + // Update the AMSDU max RX size (not shifted as located at offset 0 of the VHT cap) + band_2GHz->vht_cap.cap |= ecrnx_hw->mod_params->amsdu_rx_max; + + if (ecrnx_hw->mod_params->bfmer) { + band_2GHz->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE; + /* Set number of sounding dimensions */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) + band_2GHz->vht_cap.cap |= (nss - 1) << IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT; +#endif + } + if (ecrnx_hw->mod_params->murx) + band_2GHz->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE; + if (ecrnx_hw->mod_params->mutx) + band_2GHz->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE; + + /* + * MCS map: + * This capabilities are filled according to the mcs_map module parameter. + * However currently we have some limitations due to FPGA clock constraints + * that prevent always using the range of MCS that is defined by the + * parameter: + * - in RX, 2SS, we support up to MCS7 + * - in TX, 2SS, we support up to MCS8 + */ + // Get max supported BW + if (ecrnx_hw->mod_params->use_80) { + bw_max = PHY_CHNL_BW_80; + mcs_map_max_2ss_rx = IEEE80211_VHT_MCS_SUPPORT_0_7; + mcs_map_max_2ss_tx = IEEE80211_VHT_MCS_SUPPORT_0_8; + } else if (ecrnx_hw->mod_params->use_2040) + bw_max = PHY_CHNL_BW_40; + else + bw_max = PHY_CHNL_BW_20; + + // Check if MCS map should be limited to MCS0_8 due to the standard. Indeed in BW20, + // MCS9 is not supported in 1 and 2 SS + if (ecrnx_hw->mod_params->use_2040) + mcs_map_max = IEEE80211_VHT_MCS_SUPPORT_0_9; + else + mcs_map_max = IEEE80211_VHT_MCS_SUPPORT_0_8; + + mcs_map = min_t(int, ecrnx_hw->mod_params->mcs_map, mcs_map_max); + band_2GHz->vht_cap.vht_mcs.rx_mcs_map = cpu_to_le16(0); + for (i = 0; i < nss; i++) { + band_2GHz->vht_cap.vht_mcs.rx_mcs_map |= cpu_to_le16(mcs_map << (i*2)); + band_2GHz->vht_cap.vht_mcs.rx_highest = MAX_VHT_RATE(mcs_map, nss, bw_max); + mcs_map = min_t(int, mcs_map, mcs_map_max_2ss_rx); + } + for (; i < 8; i++) { + band_2GHz->vht_cap.vht_mcs.rx_mcs_map |= cpu_to_le16( + IEEE80211_VHT_MCS_NOT_SUPPORTED << (i*2)); + } + + mcs_map = min_t(int, ecrnx_hw->mod_params->mcs_map, mcs_map_max); + band_2GHz->vht_cap.vht_mcs.tx_mcs_map = cpu_to_le16(0); + for (i = 0; i < nss; i++) { + band_2GHz->vht_cap.vht_mcs.tx_mcs_map |= cpu_to_le16(mcs_map << (i*2)); + band_2GHz->vht_cap.vht_mcs.tx_highest = MAX_VHT_RATE(mcs_map, nss, bw_max); + mcs_map = min_t(int, mcs_map, mcs_map_max_2ss_tx); + } + for (; i < 8; i++) { + band_2GHz->vht_cap.vht_mcs.tx_mcs_map |= cpu_to_le16( + IEEE80211_VHT_MCS_NOT_SUPPORTED << (i*2)); + } + + if (!ecrnx_hw->mod_params->use_80) { +#ifdef CONFIG_VENDOR_ECRNX_VHT_NO80 + band_2GHz->vht_cap.cap |= IEEE80211_VHT_CAP_NOT_SUP_WIDTH_80; +#endif + band_2GHz->vht_cap.cap &= ~IEEE80211_VHT_CAP_SHORT_GI_80; + } +} +#endif + + +static void ecrnx_set_ht_capa(struct ecrnx_hw *ecrnx_hw, struct wiphy *wiphy) +{ +#ifdef CONFIG_ECRNX_5G + struct ieee80211_supported_band *band_5GHz = wiphy->bands[NL80211_BAND_5GHZ]; +#endif + struct ieee80211_supported_band *band_2GHz = wiphy->bands[NL80211_BAND_2GHZ]; + int i; + int nss = ecrnx_hw->mod_params->nss; + + if (!ecrnx_hw->mod_params->ht_on) { + band_2GHz->ht_cap.ht_supported = false; +#ifdef CONFIG_ECRNX_5G + band_5GHz->ht_cap.ht_supported = false; +#endif + return; + } + //JIRA438 begin by E0000550 + //if (ecrnx_hw->mod_params->stbc_on) + //JIRA438 end + band_2GHz->ht_cap.cap |= 1 << IEEE80211_HT_CAP_RX_STBC_SHIFT; + if (ecrnx_hw->mod_params->ldpc_on) + band_2GHz->ht_cap.cap |= IEEE80211_HT_CAP_LDPC_CODING; + if (ecrnx_hw->mod_params->use_2040) { + band_2GHz->ht_cap.mcs.rx_mask[4] = 0x1; /* MCS32 */ + band_2GHz->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + band_2GHz->ht_cap.mcs.rx_highest = cpu_to_le16(135 * nss); + } else { + band_2GHz->ht_cap.mcs.rx_highest = cpu_to_le16(65 * nss); + } + if (nss > 1) + band_2GHz->ht_cap.cap |= IEEE80211_HT_CAP_TX_STBC; + + // Update the AMSDU max RX size + if (ecrnx_hw->mod_params->amsdu_rx_max) + band_2GHz->ht_cap.cap |= IEEE80211_HT_CAP_MAX_AMSDU; + + if (ecrnx_hw->mod_params->sgi) { + band_2GHz->ht_cap.cap |= IEEE80211_HT_CAP_SGI_20; + if (ecrnx_hw->mod_params->use_2040) { + band_2GHz->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40; + band_2GHz->ht_cap.mcs.rx_highest = cpu_to_le16(150 * nss); + } else + band_2GHz->ht_cap.mcs.rx_highest = cpu_to_le16(72 * nss); + } + if (ecrnx_hw->mod_params->gf_rx_on) + band_2GHz->ht_cap.cap |= IEEE80211_HT_CAP_GRN_FLD; + + for (i = 0; i < nss; i++) { + band_2GHz->ht_cap.mcs.rx_mask[i] = 0xFF; + } + +#ifdef CONFIG_ECRNX_5G + band_5GHz->ht_cap = band_2GHz->ht_cap; +#endif +} + +static void ecrnx_set_he_capa(struct ecrnx_hw *ecrnx_hw, struct wiphy *wiphy) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0) +#ifdef CONFIG_ECRNX_5G + struct ieee80211_supported_band *band_5GHz = wiphy->bands[NL80211_BAND_5GHZ]; +#endif + struct ieee80211_supported_band *band_2GHz = wiphy->bands[NL80211_BAND_2GHZ]; +#endif + + int i; + int nss = ecrnx_hw->mod_params->nss; + struct ieee80211_sta_he_cap *he_cap; + int mcs_map, mcs_map_max_2ss = IEEE80211_HE_MCS_SUPPORT_0_11; + u8 dcm_max_ru = IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_242; + u32_l phy_vers = ecrnx_hw->version_cfm.version_phy_2; + + if (!ecrnx_hw->mod_params->he_on) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0) + band_2GHz->iftype_data = NULL; + band_2GHz->n_iftype_data = 0; + #ifdef CONFIG_ECRNX_5G + band_5GHz->iftype_data = NULL; + band_5GHz->n_iftype_data = 0; + #endif +#endif + return; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0) + he_cap = (struct ieee80211_sta_he_cap *) &band_2GHz->iftype_data->he_cap; +#else + he_cap = &ecrnx_he_cap; +#endif + he_cap->has_he = true; + #ifdef CONFIG_ECRNX_FULLMAC + if (ecrnx_hw->version_cfm.features & BIT(MM_FEAT_TWT_BIT)) + { + ecrnx_hw->ext_capa[9] = WLAN_EXT_CAPA10_TWT_REQUESTER_SUPPORT; + he_cap->he_cap_elem.mac_cap_info[0] |= IEEE80211_HE_MAC_CAP0_TWT_REQ; + } + #endif + he_cap->he_cap_elem.mac_cap_info[2] |= IEEE80211_HE_MAC_CAP2_ALL_ACK; + ecrnx_set_ppe_threshold(ecrnx_hw, he_cap); +#if 0 + /* ESWIN turns off 40M automotically when it is HE mode */ + if (ecrnx_hw->mod_params->use_2040) { + he_cap->he_cap_elem.phy_cap_info[0] |= + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G; + dcm_max_ru = IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_484; + } +#endif + if (ecrnx_hw->mod_params->use_80) { + he_cap->he_cap_elem.phy_cap_info[0] |= + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G; + mcs_map_max_2ss = IEEE80211_HE_MCS_SUPPORT_0_7; + dcm_max_ru = IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_996; + } + if (ecrnx_hw->mod_params->ldpc_on) { + he_cap->he_cap_elem.phy_cap_info[1] |= IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD; + } else { + // If no LDPC is supported, we have to limit to MCS0_9, as LDPC is mandatory + // for MCS 10 and 11 + ecrnx_hw->mod_params->he_mcs_map = min_t(int, ecrnx_hw->mod_params->he_mcs_map, + IEEE80211_HE_MCS_SUPPORT_0_9); + } +#if 0 + /* ESWIN: sync the he capa with 6600 standalone */ + he_cap->he_cap_elem.phy_cap_info[1] |= IEEE80211_HE_PHY_CAP1_HE_LTF_AND_GI_FOR_HE_PPDUS_0_8US | + IEEE80211_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS; + he_cap->he_cap_elem.phy_cap_info[2] |= IEEE80211_HE_PHY_CAP2_MIDAMBLE_RX_TX_MAX_NSTS | + IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US | + IEEE80211_HE_PHY_CAP2_DOPPLER_RX; +#else + he_cap->he_cap_elem.phy_cap_info[1] |= IEEE80211_HE_PHY_CAP1_HE_LTF_AND_GI_FOR_HE_PPDUS_0_8US; + he_cap->he_cap_elem.phy_cap_info[2] |= IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US | + IEEE80211_HE_PHY_CAP2_DOPPLER_RX; +#endif + if (ecrnx_hw->mod_params->stbc_on) + he_cap->he_cap_elem.phy_cap_info[2] |= IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ; + he_cap->he_cap_elem.phy_cap_info[3] |= IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_16_QAM | + IEEE80211_HE_PHY_CAP3_RX_HE_MU_PPDU_FROM_NON_AP_STA; + + /* ESWIN: sync the he capa with 6600 standalone */ + if (nss > 1) { + he_cap->he_cap_elem.phy_cap_info[3] |= IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_2; + } else { + he_cap->he_cap_elem.phy_cap_info[3] |= IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_1; + } + if (ecrnx_hw->mod_params->bfmee) { + he_cap->he_cap_elem.phy_cap_info[4] |= IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE; + he_cap->he_cap_elem.phy_cap_info[4] |= + IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_4; + } + he_cap->he_cap_elem.phy_cap_info[5] |= IEEE80211_HE_PHY_CAP5_NG16_SU_FEEDBACK | + IEEE80211_HE_PHY_CAP5_NG16_MU_FEEDBACK; + he_cap->he_cap_elem.phy_cap_info[6] |= IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_42_SU | + IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_75_MU | +#if 0 + /* ESWIN: sync the he capa with 6600 standalone */ + + IEEE80211_HE_PHY_CAP6_TRIG_SU_BEAMFORMER_FB | + IEEE80211_HE_PHY_CAP6_TRIG_MU_BEAMFORMER_FB | +#endif + IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT | + IEEE80211_HE_PHY_CAP6_PARTIAL_BANDWIDTH_DL_MUMIMO; + he_cap->he_cap_elem.phy_cap_info[7] |= IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI; + he_cap->he_cap_elem.phy_cap_info[8] |= IEEE80211_HE_PHY_CAP8_20MHZ_IN_40MHZ_HE_PPDU_IN_2G | + dcm_max_ru; + he_cap->he_cap_elem.phy_cap_info[9] |= IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_COMP_SIGB | + IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_NON_COMP_SIGB | + IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_16US; +#if 0 + if (__MDM_VERSION(phy_vers) > 30) { + he_cap->he_cap_elem.phy_cap_info[6] |= IEEE80211_HE_PHY_CAP6_PARTIAL_BW_EXT_RANGE; + he_cap->he_cap_elem.phy_cap_info[8] |= IEEE80211_HE_PHY_CAP8_HE_ER_SU_1XLTF_AND_08_US_GI | + IEEE80211_HE_PHY_CAP8_HE_ER_SU_PPDU_4XLTF_AND_08_US_GI; + } +#endif + mcs_map = ecrnx_hw->mod_params->he_mcs_map; + memset(&he_cap->he_mcs_nss_supp, 0, sizeof(he_cap->he_mcs_nss_supp)); + for (i = 0; i < nss; i++) { + __le16 unsup_for_ss = cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i*2)); + he_cap->he_mcs_nss_supp.rx_mcs_80 |= cpu_to_le16(mcs_map << (i*2)); + he_cap->he_mcs_nss_supp.rx_mcs_160 |= unsup_for_ss; + he_cap->he_mcs_nss_supp.rx_mcs_80p80 |= unsup_for_ss; + mcs_map = min_t(int, ecrnx_hw->mod_params->he_mcs_map, + mcs_map_max_2ss); + } + for (; i < 8; i++) { + __le16 unsup_for_ss = cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i*2)); + he_cap->he_mcs_nss_supp.rx_mcs_80 |= unsup_for_ss; + he_cap->he_mcs_nss_supp.rx_mcs_160 |= unsup_for_ss; + he_cap->he_mcs_nss_supp.rx_mcs_80p80 |= unsup_for_ss; + } + mcs_map = ecrnx_hw->mod_params->he_mcs_map; + for (i = 0; i < nss; i++) { + __le16 unsup_for_ss = cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i*2)); + he_cap->he_mcs_nss_supp.tx_mcs_80 |= cpu_to_le16(mcs_map << (i*2)); + he_cap->he_mcs_nss_supp.tx_mcs_160 |= unsup_for_ss; + he_cap->he_mcs_nss_supp.tx_mcs_80p80 |= unsup_for_ss; + mcs_map = min_t(int, ecrnx_hw->mod_params->he_mcs_map, + mcs_map_max_2ss); + } + for (; i < 8; i++) { + __le16 unsup_for_ss = cpu_to_le16(IEEE80211_HE_MCS_NOT_SUPPORTED << (i*2)); + he_cap->he_mcs_nss_supp.tx_mcs_80 |= unsup_for_ss; + he_cap->he_mcs_nss_supp.tx_mcs_160 |= unsup_for_ss; + he_cap->he_mcs_nss_supp.tx_mcs_80p80 |= unsup_for_ss; + } +} + +static void ecrnx_set_wiphy_params(struct ecrnx_hw *ecrnx_hw, struct wiphy *wiphy) +{ +#ifdef CONFIG_ECRNX_SOFTMAC + struct ieee80211_hw *hw = ecrnx_hw->hw; + + /* SOFTMAC specific parameters */ + if (ecrnx_hw->mod_params->hwscan) { + hw->wiphy->max_scan_ssids = SCAN_SSID_MAX; + hw->wiphy->max_scan_ie_len = SCAN_MAX_IE_LEN; + } +#else + /* FULLMAC specific parameters */ + wiphy->flags |= WIPHY_FLAG_REPORTS_OBSS; + wiphy->max_scan_ssids = SCAN_SSID_MAX; + wiphy->max_scan_ie_len = SCANU_MAX_IE_LEN; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0) + wiphy->support_mbssid = 1; +#endif +#endif /* CONFIG_ECRNX_FULLMAC */ + + if (ecrnx_hw->mod_params->tdls) { + /* TDLS support */ + wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS; +#ifdef CONFIG_ECRNX_FULLMAC + /* TDLS external setup support */ + wiphy->flags |= WIPHY_FLAG_TDLS_EXTERNAL_SETUP; +#endif + } + + if (ecrnx_hw->mod_params->ap_uapsd_on) + wiphy->flags |= WIPHY_FLAG_AP_UAPSD; + +#ifdef CONFIG_ECRNX_FULLMAC + if (ecrnx_hw->mod_params->ps_on) + wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT; + else + wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; +#endif + + if (ecrnx_hw->mod_params->custregd) { + // Apply custom regulatory. Note that for recent kernel versions we use instead the + // REGULATORY_WIPHY_SELF_MANAGED flag, along with the regulatory_set_wiphy_regd() + // function, that needs to be called after wiphy registration + // Check if custom channel set shall be enabled. In such case only monitor mode is + // supported + if (ecrnx_hw->mod_params->custchan) { + wiphy->interface_modes = BIT(NL80211_IFTYPE_MONITOR); + + // Enable "extra" channels + wiphy->bands[NL80211_BAND_2GHZ]->n_channels += 13; + #ifdef CONFIG_ECRNX_5G + wiphy->bands[NL80211_BAND_5GHZ]->n_channels += 59; + #endif + } + } +} + +static void ecrnx_set_rf_params(struct ecrnx_hw *ecrnx_hw, struct wiphy *wiphy) +{ +#ifndef CONFIG_ECRNX_SDM +#ifdef CONFIG_ECRNX_5G + struct ieee80211_supported_band *band_5GHz = wiphy->bands[NL80211_BAND_5GHZ]; +#else +#endif + u32 mdm_phy_cfg = __MDM_PHYCFG_FROM_VERS(ecrnx_hw->version_cfm.version_phy_1); + + /* + * Get configuration file depending on the RF + */ +#if 0 /* baoyong: we use the custom rf, do not need this */ + struct ecrnx_phy_conf_file phy_conf; + if (mdm_phy_cfg == MDM_PHY_CONFIG_TRIDENT) { + // Retrieve the Trident configuration + ecrnx_parse_phy_configfile(ecrnx_hw, ECRNX_PHY_CONFIG_TRD_NAME, + &phy_conf, ecrnx_hw->mod_params->phy_cfg); + memcpy(&ecrnx_hw->phy.cfg, &phy_conf.trd, sizeof(phy_conf.trd)); + } else if (mdm_phy_cfg == MDM_PHY_CONFIG_CATAXIA) { + memset(&phy_conf.cataxia, 0, sizeof(phy_conf.cataxia)); + phy_conf.cataxia.path_used = ecrnx_hw->mod_params->phy_cfg; + memcpy(&ecrnx_hw->phy.cfg, &phy_conf.cataxia, sizeof(phy_conf.cataxia)); + } else if (mdm_phy_cfg == MDM_PHY_CONFIG_KARST) { + // We use the NSS parameter as is + // Retrieve the Karst configuration + ecrnx_parse_phy_configfile(ecrnx_hw, ECRNX_PHY_CONFIG_KARST_NAME, + &phy_conf, ecrnx_hw->mod_params->phy_cfg); + + memcpy(&ecrnx_hw->phy.cfg, &phy_conf.karst, sizeof(phy_conf.karst)); + } else { + WARN_ON(1); + } +#endif + + /* + * adjust caps depending on the RF + */ + switch (mdm_phy_cfg) { + case MDM_PHY_CONFIG_TRIDENT: + { + wiphy_dbg(wiphy, "found Trident PHY .. limit BW to 40MHz\n"); + ecrnx_hw->phy.limit_bw = true; +#ifdef CONFIG_ECRNX_5G +#ifdef CONFIG_VENDOR_ECRNX_VHT_NO80 + band_5GHz->vht_cap.cap |= IEEE80211_VHT_CAP_NOT_SUP_WIDTH_80; +#endif + band_5GHz->vht_cap.cap &= ~(IEEE80211_VHT_CAP_SHORT_GI_80 | + IEEE80211_VHT_CAP_RXSTBC_MASK); +#endif + break; + } + case MDM_PHY_CONFIG_CATAXIA: + { + wiphy_dbg(wiphy, "found CATAXIA PHY\n"); + break; + } + case MDM_PHY_CONFIG_KARST: + { + wiphy_dbg(wiphy, "found KARST PHY\n"); + break; + } + default: + WARN_ON(1); + break; + } +#endif /* CONFIG_ECRNX_SDM */ +} + +int ecrnx_handle_dynparams(struct ecrnx_hw *ecrnx_hw, struct wiphy *wiphy) +{ + int ret; + + /* Check compatibility between requested parameters and HW/SW features */ + ret = ecrnx_check_fw_hw_feature(ecrnx_hw, wiphy); + if (ret) + return ret; +#ifndef CONFIG_ECRNX_ESWIN + + /* Allocate the RX buffers according to the maximum AMSDU RX size */ + ret = ecrnx_ipc_rxbuf_init(ecrnx_hw, + (4 * (ecrnx_hw->mod_params->amsdu_rx_max + 1) + 1) * 1024); + if (ret) { + wiphy_err(wiphy, "Cannot allocate the RX buffers\n"); + return ret; + } +#endif +#ifdef CONFIG_ECRNX_SOFTMAC + /* SOFTMAC specific parameters*/ + ecrnx_set_softmac_flags(ecrnx_hw); +#endif /* CONFIG_ECRNX_SOFTMAC */ + + /* Set wiphy parameters */ + ecrnx_set_wiphy_params(ecrnx_hw, wiphy); + + /* Set VHT capabilities */ + ecrnx_set_vht_capa(ecrnx_hw, wiphy); + + /* Set HE capabilities */ + ecrnx_set_he_capa(ecrnx_hw, wiphy); + + /* Set HT capabilities */ + ecrnx_set_ht_capa(ecrnx_hw, wiphy); + + /* Set RF specific parameters (shall be done last as it might change some + capabilities previously set) */ + ecrnx_set_rf_params(ecrnx_hw, wiphy); + + return 0; +} + +void ecrnx_custregd(struct ecrnx_hw *ecrnx_hw, struct wiphy *wiphy) +{ +// For older kernel version, the custom regulatory is applied before the wiphy +// registration (in ecrnx_set_wiphy_params()), so nothing has to be done here +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0) + if (!ecrnx_hw->mod_params->custregd) + return; + + wiphy->regulatory_flags |= REGULATORY_IGNORE_STALE_KICKOFF; + wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED; + + rtnl_lock(); + if (regulatory_set_wiphy_regd_sync_rtnl(wiphy, &ecrnx_regdom)) + wiphy_err(wiphy, "Failed to set custom regdomain\n"); + else + wiphy_err(wiphy,"\n" + "*******************************************************\n" + "** CAUTION: USING PERMISSIVE CUSTOM REGULATORY RULES **\n" + "*******************************************************\n"); + rtnl_unlock(); +#endif +} + +void ecrnx_adjust_amsdu_maxnb(struct ecrnx_hw *ecrnx_hw) +{ + if (ecrnx_hw->mod_params->amsdu_maxnb > NX_TX_PAYLOAD_MAX) + ecrnx_hw->mod_params->amsdu_maxnb = NX_TX_PAYLOAD_MAX; + else if (ecrnx_hw->mod_params->amsdu_maxnb == 0) + ecrnx_hw->mod_params->amsdu_maxnb = 1; +} diff --git a/drivers/net/wireless/eswin/ecrnx_mod_params.h b/drivers/net/wireless/eswin/ecrnx_mod_params.h new file mode 100644 index 000000000000..72c87dbbd76f --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_mod_params.h @@ -0,0 +1,86 @@ +/** + ****************************************************************************** + * + * @file ecrnx_mod_params.h + * + * @brief Declaration of module parameters + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef _ECRNX_MOD_PARAM_H_ +#define _ECRNX_MOD_PARAM_H_ + +struct ecrnx_mod_params { + bool ht_on; + bool vht_on; + bool he_on; + int mcs_map; + int he_mcs_map; + bool he_ul_on; + bool ldpc_on; + bool stbc_on; + bool gf_rx_on; + int phy_cfg; + int uapsd_timeout; + bool ap_uapsd_on; + bool sgi; + bool sgi80; + bool use_2040; + bool use_80; + bool custregd; + bool custchan; + int nss; + int amsdu_rx_max; + bool bfmee; + bool bfmer; + bool mesh; + bool murx; + bool mutx; + bool mutx_on; + unsigned int roc_dur_max; + int listen_itv; + bool listen_bcmc; + int lp_clk_ppm; + bool ps_on; + int tx_lft; + int amsdu_maxnb; + int uapsd_queues; + bool tdls; + bool uf; + char *ftl; + bool dpsm; + int tx_to_bk; + int tx_to_be; + int tx_to_vi; + int tx_to_vo; +#ifdef CONFIG_ECRNX_SOFTMAC + bool mfp_on; + bool gf_on; + bool bwsig_on; + bool dynbw_on; + bool agg_tx; + int amsdu_force; + bool rc_probes_on; + bool cmon; + bool hwscan; + bool autobcn; +#else + bool ant_div; +#endif /* CONFIG_ECRNX_SOFTMAC */ +}; + +extern struct ecrnx_mod_params ecrnx_mod_params; + +struct ecrnx_hw; +struct wiphy; +int ecrnx_handle_dynparams(struct ecrnx_hw *ecrnx_hw, struct wiphy *wiphy); +void ecrnx_custregd(struct ecrnx_hw *ecrnx_hw, struct wiphy *wiphy); +void ecrnx_enable_wapi(struct ecrnx_hw *ecrnx_hw); +void ecrnx_enable_mfp(struct ecrnx_hw *ecrnx_hw); +void ecrnx_enable_gcmp(struct ecrnx_hw *ecrnx_hw); +void ecrnx_adjust_amsdu_maxnb(struct ecrnx_hw *ecrnx_hw); + +#endif /* _ECRNX_MOD_PARAM_H_ */ diff --git a/drivers/net/wireless/eswin/ecrnx_msg_rx.c b/drivers/net/wireless/eswin/ecrnx_msg_rx.c new file mode 100644 index 000000000000..5e51e2c806aa --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_msg_rx.c @@ -0,0 +1,1604 @@ +/** + **************************************************************************************** + * + * @file ecrnx_msg_rx.c + * + * @brief RX function definitions + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ +#include "ecrnx_defs.h" +#include "ecrnx_prof.h" +#include "ecrnx_tx.h" +#ifdef CONFIG_ECRNX_BFMER +#include "ecrnx_bfmer.h" +#endif //(CONFIG_ECRNX_BFMER) +#ifdef CONFIG_ECRNX_FULLMAC +#include "ecrnx_debugfs.h" +#include "ecrnx_msg_tx.h" +#include "ecrnx_tdls.h" +#endif /* CONFIG_ECRNX_FULLMAC */ +#include "ecrnx_events.h" +#include "ecrnx_compat.h" +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) +#include <linux/time.h> +#endif + +#if defined(CONFIG_ECRNX_DEBUGFS_CUSTOM) +#include "ecrnx_debugfs_func.h" +#endif +static int ecrnx_freq_to_idx(struct ecrnx_hw *ecrnx_hw, int freq) +{ + struct ieee80211_supported_band *sband; + int band, ch, idx = 0; + +#ifdef CONFIG_ECRNX_5G + for (band = NL80211_BAND_2GHZ; band < NUM_NL80211_BANDS; band++) { +#else + for (band = NL80211_BAND_2GHZ; band <= NL80211_BAND_2GHZ; band++) { +#endif +#ifdef CONFIG_ECRNX_SOFTMAC + sband = ecrnx_hw->hw->wiphy->bands[band]; +#else + sband = ecrnx_hw->wiphy->bands[band]; +#endif /* CONFIG_ECRNX_SOFTMAC */ + if (!sband) { + continue; + } + + for (ch = 0; ch < sband->n_channels; ch++, idx++) { + if (sband->channels[ch].center_freq == freq) { + goto exit; + } + } + } + + ECRNX_ERR("--!!!!!!!!error freq-----%d\n", freq); + //BUG_ON(1); + +exit: + // Channel has been found, return the index + return idx; +} + +/*************************************************************************** + * Messages from MM task + **************************************************************************/ +static inline int ecrnx_rx_chan_pre_switch_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ +#ifdef CONFIG_ECRNX_SOFTMAC + struct ecrnx_chanctx *chan_ctxt; +#endif + struct ecrnx_vif *ecrnx_vif; + int chan_idx = ((struct mm_channel_pre_switch_ind *)msg->param)->chan_index; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + REG_SW_SET_PROFILING_CHAN(ecrnx_hw, SW_PROF_CHAN_CTXT_PSWTCH_BIT); + +#ifdef CONFIG_ECRNX_SOFTMAC + list_for_each_entry(chan_ctxt, &ecrnx_hw->chan_ctxts, list) { + if (chan_ctxt->index == chan_idx) { + chan_ctxt->active = false; + break; + } + } + + list_for_each_entry(ecrnx_vif, &ecrnx_hw->vifs, list) { + if (ecrnx_vif->chanctx && (ecrnx_vif->chanctx->index == chan_idx)) { + ecrnx_txq_vif_stop(ecrnx_vif, ECRNX_TXQ_STOP_CHAN, ecrnx_hw); + } + } +#else + list_for_each_entry(ecrnx_vif, &ecrnx_hw->vifs, list) { + if (ecrnx_vif->up && ecrnx_vif->ch_index == chan_idx) { + ecrnx_txq_vif_stop(ecrnx_vif, ECRNX_TXQ_STOP_CHAN, ecrnx_hw); + } + } +#endif /* CONFIG_ECRNX_SOFTMAC */ + + REG_SW_CLEAR_PROFILING_CHAN(ecrnx_hw, SW_PROF_CHAN_CTXT_PSWTCH_BIT); + + return 0; +} + +static inline int ecrnx_rx_chan_switch_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ +#ifdef CONFIG_ECRNX_SOFTMAC + struct ecrnx_chanctx *chan_ctxt; + struct ecrnx_sta *ecrnx_sta; +#endif + struct ecrnx_vif *ecrnx_vif; + int chan_idx = ((struct mm_channel_switch_ind *)msg->param)->chan_index; + bool roc_req = ((struct mm_channel_switch_ind *)msg->param)->roc; + bool roc_tdls = ((struct mm_channel_switch_ind *)msg->param)->roc_tdls; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + REG_SW_SET_PROFILING_CHAN(ecrnx_hw, SW_PROF_CHAN_CTXT_SWTCH_BIT); + +#ifdef CONFIG_ECRNX_SOFTMAC + if (roc_tdls) { + u8 vif_index = ((struct mm_channel_switch_ind *)msg->param)->vif_index; + // Enable traffic only for TDLS station + list_for_each_entry(ecrnx_vif, &ecrnx_hw->vifs, list) { + if (ecrnx_vif->vif_index == vif_index) { + list_for_each_entry(ecrnx_sta, &ecrnx_vif->stations, list) { + if (ecrnx_sta->tdls.active) { + ecrnx_vif->roc_tdls = true; + ecrnx_txq_tdls_sta_start(ecrnx_sta, ECRNX_TXQ_STOP_CHAN, ecrnx_hw); + break; + } + } + break; + } + } + } else if (!roc_req) { + list_for_each_entry(ecrnx_vif, &ecrnx_hw->vifs, list) { + if (ecrnx_vif->chanctx && (ecrnx_vif->chanctx->index == chan_idx)) { + ecrnx_txq_vif_start(ecrnx_vif, ECRNX_TXQ_STOP_CHAN, ecrnx_hw); + } + } + } else { + u8 vif_index = ((struct mm_channel_switch_ind *)msg->param)->vif_index; + + // Inform the host that the offchannel period has been started + ieee80211_ready_on_channel(ecrnx_hw->hw); + + // Enable traffic for associated VIF (roc may happen without chanctx) + list_for_each_entry(ecrnx_vif, &ecrnx_hw->vifs, list) { + if (ecrnx_vif->vif_index == vif_index) { + ecrnx_txq_vif_start(ecrnx_vif, ECRNX_TXQ_STOP_CHAN, ecrnx_hw); + } + } + } + + /* keep cur_chan up to date */ + list_for_each_entry(chan_ctxt, &ecrnx_hw->chan_ctxts, list) { + if (chan_ctxt->index == chan_idx) { + chan_ctxt->active = true; + ecrnx_hw->cur_freq = chan_ctxt->ctx->def.center_freq1; + ecrnx_hw->cur_band = chan_ctxt->ctx->def.chan->band; + if (chan_ctxt->ctx->def.chan->flags & IEEE80211_CHAN_RADAR) { + ecrnx_radar_detection_enable(&ecrnx_hw->radar, + ECRNX_RADAR_DETECT_REPORT, + ECRNX_RADAR_RIU); + } else { + ecrnx_radar_detection_enable(&ecrnx_hw->radar, + ECRNX_RADAR_DETECT_DISABLE, + ECRNX_RADAR_RIU); + } + break; + } + } + +#else + if (roc_tdls) { + u8 vif_index = ((struct mm_channel_switch_ind *)msg->param)->vif_index; + list_for_each_entry(ecrnx_vif, &ecrnx_hw->vifs, list) { + if (ecrnx_vif->vif_index == vif_index) { + ecrnx_vif->roc_tdls = true; + ecrnx_txq_tdls_sta_start(ecrnx_vif, ECRNX_TXQ_STOP_CHAN, ecrnx_hw); + } + } + } else if (!roc_req) { + list_for_each_entry(ecrnx_vif, &ecrnx_hw->vifs, list) { + if (ecrnx_vif->up && ecrnx_vif->ch_index == chan_idx) { + ecrnx_txq_vif_start(ecrnx_vif, ECRNX_TXQ_STOP_CHAN, ecrnx_hw); + } + } + } else { + /* Retrieve the allocated RoC element */ + struct ecrnx_roc *roc = ecrnx_hw->roc; + + if (roc && roc->vif) { + /* Get VIF on which RoC has been started */ + ecrnx_vif = roc->vif; + /* For debug purpose (use ftrace kernel option) */ + trace_switch_roc(ecrnx_vif->vif_index); + + if (!roc->internal) { + /* If mgmt_roc is true, remain on channel has been started by ourself */ + /* Inform the host that we have switch on the indicated off-channel */ + cfg80211_ready_on_channel(&ecrnx_vif->wdev, (u64)(ecrnx_hw->roc_cookie), + roc->chan, roc->duration, GFP_ATOMIC); + } + + /* Keep in mind that we have switched on the channel */ + roc->on_chan = true; + } + + // Enable traffic on OFF channel queue + ecrnx_txq_offchan_start(ecrnx_hw); +#if defined(CONFIG_ECRNX_P2P) + if (roc && roc->internal) { + ecrnx_hw->p2p_listen.rxdatas = 1; + wake_up(&ecrnx_hw->p2p_listen.rxdataq); + } +#endif + } + + ecrnx_hw->cur_chanctx = chan_idx; + ecrnx_radar_detection_enable_on_cur_channel(ecrnx_hw); + +#endif /* CONFIG_ECRNX_SOFTMAC */ + + REG_SW_CLEAR_PROFILING_CHAN(ecrnx_hw, SW_PROF_CHAN_CTXT_SWTCH_BIT); + + return 0; +} + +static inline int ecrnx_rx_tdls_chan_switch_cfm(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + return 0; +} + +static inline int ecrnx_rx_tdls_chan_switch_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ +#ifdef CONFIG_ECRNX_SOFTMAC + struct ecrnx_chanctx *chan_ctxt; + u8 chan_idx = ((struct tdls_chan_switch_ind *)msg->param)->chan_ctxt_index; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + // Enable channel context + list_for_each_entry(chan_ctxt, &ecrnx_hw->chan_ctxts, list) { + if (chan_ctxt->index == chan_idx) { + chan_ctxt->active = true; + ecrnx_hw->cur_freq = chan_ctxt->ctx->def.center_freq1; + ecrnx_hw->cur_band = chan_ctxt->ctx->def.chan->band; + } + } + + return 0; +#else + // Enable traffic on OFF channel queue + ecrnx_txq_offchan_start(ecrnx_hw); + + return 0; +#endif +} + +static inline int ecrnx_rx_tdls_chan_switch_base_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct ecrnx_vif *ecrnx_vif; + u8 vif_index = ((struct tdls_chan_switch_base_ind *)msg->param)->vif_index; +#ifdef CONFIG_ECRNX_SOFTMAC + struct ecrnx_sta *ecrnx_sta; +#endif + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + +#ifdef CONFIG_ECRNX_SOFTMAC + // Disable traffic for associated VIF + list_for_each_entry(ecrnx_vif, &ecrnx_hw->vifs, list) { + if (ecrnx_vif->vif_index == vif_index) { + if (ecrnx_vif->chanctx) + ecrnx_vif->chanctx->active = false; + list_for_each_entry(ecrnx_sta, &ecrnx_vif->stations, list) { + if (ecrnx_sta->tdls.active) { + ecrnx_vif->roc_tdls = false; + ecrnx_txq_tdls_sta_stop(ecrnx_sta, ECRNX_TXQ_STOP_CHAN, ecrnx_hw); + break; + } + } + break; + } + } + return 0; +#else + list_for_each_entry(ecrnx_vif, &ecrnx_hw->vifs, list) { + if (ecrnx_vif->vif_index == vif_index) { + ecrnx_vif->roc_tdls = false; + ecrnx_txq_tdls_sta_stop(ecrnx_vif, ECRNX_TXQ_STOP_CHAN, ecrnx_hw); + } + } + return 0; +#endif +} + +static inline int ecrnx_rx_tdls_peer_ps_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct ecrnx_vif *ecrnx_vif; + u8 vif_index = ((struct tdls_peer_ps_ind *)msg->param)->vif_index; + bool ps_on = ((struct tdls_peer_ps_ind *)msg->param)->ps_on; + +#ifdef CONFIG_ECRNX_SOFTMAC + u8 sta_idx = ((struct tdls_peer_ps_ind *)msg->param)->sta_idx; + struct ecrnx_sta *ecrnx_sta; + list_for_each_entry(ecrnx_vif, &ecrnx_hw->vifs, list) { + if (ecrnx_vif->vif_index == vif_index) { + list_for_each_entry(ecrnx_sta, &ecrnx_vif->stations, list) { + if (ecrnx_sta->sta_idx == sta_idx) { + ecrnx_sta->tdls.ps_on = ps_on; + if (ps_on) { + // disable TXQ for TDLS peer + ecrnx_txq_tdls_sta_stop(ecrnx_sta, ECRNX_TXQ_STOP_STA_PS, ecrnx_hw); + } else { + // Enable TXQ for TDLS peer + ecrnx_txq_tdls_sta_start(ecrnx_sta, ECRNX_TXQ_STOP_STA_PS, ecrnx_hw); + } + break; + } + } + break; + } + } + return 0; +#else + list_for_each_entry(ecrnx_vif, &ecrnx_hw->vifs, list) { + if (ecrnx_vif->vif_index == vif_index) { + ecrnx_vif->sta.tdls_sta->tdls.ps_on = ps_on; + // Update PS status for the TDLS station + ecrnx_ps_bh_enable(ecrnx_hw, ecrnx_vif->sta.tdls_sta, ps_on); + } + } + + return 0; +#endif +} + +static inline int ecrnx_rx_remain_on_channel_exp_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ +#ifdef CONFIG_ECRNX_SOFTMAC + struct ecrnx_vif *ecrnx_vif; + u8 vif_index = ((struct mm_remain_on_channel_exp_ind *)msg->param)->vif_index; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + ieee80211_remain_on_channel_expired(ecrnx_hw->hw); + + // Disable traffic for associated VIF + list_for_each_entry(ecrnx_vif, &ecrnx_hw->vifs, list) { + if (ecrnx_vif->vif_index == vif_index) { + if (ecrnx_vif->chanctx) + ecrnx_vif->chanctx->active = false; + + ecrnx_txq_vif_stop(ecrnx_vif, ECRNX_TXQ_STOP_CHAN, ecrnx_hw); + break; + } + } + + return 0; + +#else + if(!ecrnx_hw->roc || !ecrnx_hw->roc->chan){ + ECRNX_ERR("error!!!:ecrnx_hw->roc or !ecrnx_hw->roc->chan is null \n"); + return 0; + } + /* Retrieve the allocated RoC element */ + struct ecrnx_roc *roc = ecrnx_hw->roc; + /* Get VIF on which RoC has been started */ + struct ecrnx_vif *ecrnx_vif = roc->vif; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* For debug purpose (use ftrace kernel option) */ + trace_roc_exp(ecrnx_vif->vif_index); + + /* If mgmt_roc is true, remain on channel has been started by ourself */ + /* If RoC has been cancelled before we switched on channel, do not call cfg80211 */ + if (!roc->internal && roc->on_chan) { + /* Inform the host that off-channel period has expired */ + cfg80211_remain_on_channel_expired(&ecrnx_vif->wdev, (u64)(ecrnx_hw->roc_cookie), + roc->chan, GFP_ATOMIC); + } + + /* De-init offchannel TX queue */ + ecrnx_txq_offchan_deinit(ecrnx_vif); + + /* Increase the cookie counter cannot be zero */ + ecrnx_hw->roc_cookie++; + + if (ecrnx_hw->roc_cookie == 0) + ecrnx_hw->roc_cookie = 1; + +#if CONFIG_ECRNX_P2P + ecrnx_hw->p2p_listen.listen_started = 0; +#endif + + /* Free the allocated RoC element */ + kfree(roc); + ecrnx_hw->roc = NULL; + +#if defined(CONFIG_ECRNX_P2P) + wake_up(&ecrnx_hw->p2p_listen.rxdataq); +#endif + +#endif /* CONFIG_ECRNX_SOFTMAC */ + return 0; +} + +static inline int ecrnx_rx_p2p_vif_ps_change_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + int vif_idx = ((struct mm_p2p_vif_ps_change_ind *)msg->param)->vif_index; + int ps_state = ((struct mm_p2p_vif_ps_change_ind *)msg->param)->ps_state; + struct ecrnx_vif *vif_entry; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + +#ifdef CONFIG_ECRNX_SOFTMAC + // Look for VIF entry + list_for_each_entry(vif_entry, &ecrnx_hw->vifs, list) { + if (vif_entry->vif_index == vif_idx) { + goto found_vif; + } + } +#else + vif_entry = ecrnx_hw->vif_table[vif_idx]; + + if (vif_entry) { + goto found_vif; + } +#endif /* CONFIG_ECRNX_SOFTMAC */ + + goto exit; + +found_vif: + +#ifdef CONFIG_ECRNX_SOFTMAC + if (ps_state == MM_PS_MODE_OFF) { + ecrnx_txq_vif_start(vif_entry, ECRNX_TXQ_STOP_VIF_PS, ecrnx_hw); + } + else { + ecrnx_txq_vif_stop(vif_entry, ECRNX_TXQ_STOP_VIF_PS, ecrnx_hw); + } +#else + if (ps_state == MM_PS_MODE_OFF) { + // Start TX queues for provided VIF + ecrnx_txq_vif_start(vif_entry, ECRNX_TXQ_STOP_VIF_PS, ecrnx_hw); + } + else { + // Stop TX queues for provided VIF + ecrnx_txq_vif_stop(vif_entry, ECRNX_TXQ_STOP_VIF_PS, ecrnx_hw); + } +#endif /* CONFIG_ECRNX_SOFTMAC */ + +exit: + return 0; +} + +static inline int ecrnx_rx_channel_survey_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct mm_channel_survey_ind *ind = (struct mm_channel_survey_ind *)msg->param; + // Get the channel index + int idx = ecrnx_freq_to_idx(ecrnx_hw, ind->freq); + // Get the survey + struct ecrnx_survey_info *ecrnx_survey; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + if (idx > ARRAY_SIZE(ecrnx_hw->survey)) + return 0; + + ecrnx_survey = &ecrnx_hw->survey[idx]; + + // Store the received parameters + ecrnx_survey->chan_time_ms = ind->chan_time_ms; + ecrnx_survey->chan_time_busy_ms = ind->chan_time_busy_ms; + ecrnx_survey->noise_dbm = ind->noise_dbm; + ecrnx_survey->filled = (SURVEY_INFO_TIME | + SURVEY_INFO_TIME_BUSY); + + if (ind->noise_dbm != 0) { + ecrnx_survey->filled |= SURVEY_INFO_NOISE_DBM; + } + +#if defined(CONFIG_ECRNX_DEBUGFS_CUSTOM) + ecrnx_debugfs_noise_of_survey_info_update(ecrnx_hw, ecrnx_survey, idx); +#endif + + return 0; +} + +static inline int ecrnx_rx_p2p_noa_upd_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + return 0; +} + +static inline int ecrnx_rx_rssi_status_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct mm_rssi_status_ind *ind = (struct mm_rssi_status_ind *)msg->param; + int vif_idx = ind->vif_index; + bool rssi_status = ind->rssi_status; + + struct ecrnx_vif *vif_entry; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + +#ifdef CONFIG_ECRNX_SOFTMAC + list_for_each_entry(vif_entry, &ecrnx_hw->vifs, list) { + if (vif_entry->vif_index == vif_idx) { + ecrnx_ieee80211_cqm_rssi_notify(vif_entry->vif, + rssi_status ? NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW : + NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH, + ind->rssi, GFP_ATOMIC); + } + } +#else + vif_entry = ecrnx_hw->vif_table[vif_idx]; + if (vif_entry) { + ecrnx_cfg80211_cqm_rssi_notify(vif_entry->ndev, + rssi_status ? NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW : + NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH, + ind->rssi, GFP_ATOMIC); + } +#endif /* CONFIG_ECRNX_SOFTMAC */ + + return 0; +} + +static inline int ecrnx_rx_pktloss_notify_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ +#ifdef CONFIG_ECRNX_FULLMAC + struct mm_pktloss_ind *ind = (struct mm_pktloss_ind *)msg->param; + struct ecrnx_vif *vif_entry; + int vif_idx = ind->vif_index; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + vif_entry = ecrnx_hw->vif_table[vif_idx]; + if (vif_entry) { + cfg80211_cqm_pktloss_notify(vif_entry->ndev, (const u8 *)ind->mac_addr.array, + ind->num_packets, GFP_ATOMIC); + } +#endif /* CONFIG_ECRNX_FULLMAC */ + + return 0; +} + +static inline int ecrnx_rx_csa_counter_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct mm_csa_counter_ind *ind = (struct mm_csa_counter_ind *)msg->param; + struct ecrnx_vif *vif; + bool found = false; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + // Look for VIF entry + list_for_each_entry(vif, &ecrnx_hw->vifs, list) { + if (vif->vif_index == ind->vif_index) { + found=true; + break; + } + } + + if (found) { +#ifdef CONFIG_ECRNX_SOFTMAC + if (ind->csa_count == 1) + ieee80211_csa_finish(vif->vif); + else + ieee80211_csa_update_counter(vif->vif); +#else + if (vif->ap.csa) + vif->ap.csa->count = ind->csa_count; + else + netdev_err(vif->ndev, "CSA counter update but no active CSA"); + +#endif + } + + return 0; +} + +#ifdef CONFIG_ECRNX_SOFTMAC +static inline int ecrnx_rx_connection_loss_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct ecrnx_vif *ecrnx_vif; + u8 inst_nbr; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + inst_nbr = ((struct mm_connection_loss_ind *)msg->param)->inst_nbr; + + /* Search the VIF entry corresponding to the instance number */ + list_for_each_entry(ecrnx_vif, &ecrnx_hw->vifs, list) { + if (ecrnx_vif->vif_index == inst_nbr) { + ieee80211_connection_loss(ecrnx_vif->vif); + break; + } + } + + return 0; +} + + +#ifdef CONFIG_ECRNX_BCN +static inline int ecrnx_rx_prm_tbtt_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + ecrnx_tx_bcns(ecrnx_hw); + + return 0; +} +#endif + +#else /* !CONFIG_ECRNX_SOFTMAC */ +static inline int ecrnx_rx_csa_finish_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct mm_csa_finish_ind *ind = (struct mm_csa_finish_ind *)msg->param; + struct ecrnx_vif *vif; + bool found = false; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + // Look for VIF entry + list_for_each_entry(vif, &ecrnx_hw->vifs, list) { + if (vif->vif_index == ind->vif_index) { + found=true; + break; + } + } + + if (found) { + if (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_AP || + ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_P2P_GO) { + if (vif->ap.csa) { + vif->ap.csa->status = ind->status; + vif->ap.csa->ch_idx = ind->chan_idx; + schedule_work(&vif->ap.csa->work); + } else + netdev_err(vif->ndev, "CSA finish indication but no active CSA"); + } else { + if (ind->status == 0) { + ecrnx_chanctx_unlink(vif); + ecrnx_chanctx_link(vif, ind->chan_idx, NULL); + if (ecrnx_hw->cur_chanctx == ind->chan_idx) { + ecrnx_radar_detection_enable_on_cur_channel(ecrnx_hw); + ecrnx_txq_vif_start(vif, ECRNX_TXQ_STOP_CHAN, ecrnx_hw); + } else + ecrnx_txq_vif_stop(vif, ECRNX_TXQ_STOP_CHAN, ecrnx_hw); + } + } + } + + return 0; +} + +static inline int ecrnx_rx_csa_traffic_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct mm_csa_traffic_ind *ind = (struct mm_csa_traffic_ind *)msg->param; + struct ecrnx_vif *vif; + bool found = false; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + // Look for VIF entry + list_for_each_entry(vif, &ecrnx_hw->vifs, list) { + if (vif->vif_index == ind->vif_index) { + found=true; + break; + } + } + + if (found) { + if (ind->enable) + ecrnx_txq_vif_start(vif, ECRNX_TXQ_STOP_CSA, ecrnx_hw); + else + ecrnx_txq_vif_stop(vif, ECRNX_TXQ_STOP_CSA, ecrnx_hw); + } + + return 0; +} + +static inline int ecrnx_rx_ps_change_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct mm_ps_change_ind *ind = (struct mm_ps_change_ind *)msg->param; + struct ecrnx_sta *sta = &ecrnx_hw->sta_table[ind->sta_idx]; + + if (ind->sta_idx >= (NX_REMOTE_STA_MAX + NX_VIRT_DEV_MAX)) { + wiphy_err(ecrnx_hw->wiphy, "Invalid sta index reported by fw %d\n", + ind->sta_idx); + return 1; + } + + netdev_dbg(ecrnx_hw->vif_table[sta->vif_idx]->ndev, + "Sta %d, change PS mode to %s", sta->sta_idx, + ind->ps_state ? "ON" : "OFF"); + + ECRNX_DBG("Sta:0x%p, sta_idx:%d, sta->valid:%d, sta_mac:%pM, change PS mode to: %s \n",sta, sta->sta_idx, sta->valid, sta->mac_addr, ind->ps_state ? "ON" : "OFF"); + + if (sta->valid) { + ecrnx_ps_bh_enable(ecrnx_hw, sta, ind->ps_state); + } else if (test_bit(ECRNX_DEV_ADDING_STA, &ecrnx_hw->flags)) { + sta->ps.active = ind->ps_state ? true : false; + } else { + netdev_err(ecrnx_hw->vif_table[sta->vif_idx]->ndev, + "Ignore PS mode change on invalid sta\n"); + } + + return 0; +} + + +static inline int ecrnx_rx_traffic_req_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct mm_traffic_req_ind *ind = (struct mm_traffic_req_ind *)msg->param; + struct ecrnx_sta *sta = &ecrnx_hw->sta_table[ind->sta_idx]; + + ECRNX_DBG("%s-%d:Sta:0x%p, sta_idx:%d, sta->valid:%d, sta_mac:%pM \n",__func__, __LINE__, sta, ind->sta_idx, sta->valid, sta->mac_addr); + + netdev_dbg(ecrnx_hw->vif_table[sta->vif_idx]->ndev, + "Sta %d, asked for %d pkt", sta->sta_idx, ind->pkt_cnt); + + ecrnx_ps_bh_traffic_req(ecrnx_hw, sta, ind->pkt_cnt, + ind->uapsd ? UAPSD_ID : LEGACY_PS_ID); + + return 0; +} +#endif /* CONFIG_ECRNX_SOFTMAC */ + +/*************************************************************************** + * Messages from SCAN task + **************************************************************************/ +#ifdef CONFIG_ECRNX_SOFTMAC +static inline int ecrnx_rx_scan_done_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + struct cfg80211_scan_info info = { + .aborted = false, + }; +#endif + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + ecrnx_ipc_elem_var_deallocs(ecrnx_hw, &ecrnx_hw->scan_ie); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + ieee80211_scan_completed(ecrnx_hw->hw, &info); +#else + ieee80211_scan_completed(ecrnx_hw->hw, false); +#endif + + return 0; +} +#endif /* CONFIG_ECRNX_SOFTMAC */ + +/*************************************************************************** + * Messages from SCANU task + **************************************************************************/ +#ifdef CONFIG_ECRNX_FULLMAC +static inline int ecrnx_scanu_cancel_cfm(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct scanu_cancel_cfm *cfm = (struct scanu_cancel_cfm *)msg; + bool abort = false; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + struct cfg80211_scan_info info = { + .aborted = abort, + }; +#endif + + ECRNX_PRINT("%s: cfm status:%d, scan_request:0x%p \n", __func__, cfm->status, ecrnx_hw->scan_request); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + cfg80211_scan_done(ecrnx_hw->scan_request, &info); +#else + cfg80211_scan_done(ecrnx_hw->scan_request, false); +#endif + ecrnx_hw->scan_request = NULL; + + return 0; +} + +static inline int ecrnx_rx_scanu_start_cfm(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct scanu_start_cfm* cfm = (struct scanu_start_cfm*)msg->param; + u8_l abort_status; + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + ECRNX_DBG("receive scanu cfm, status:%d \n", cfm->status); + abort_status = cfm->status?true:false; + +#ifndef CONFIG_ECRNX_ESWIN + ecrnx_ipc_elem_var_deallocs(ecrnx_hw, &ecrnx_hw->scan_ie); +#endif + + spin_lock_bh(&ecrnx_hw->scan_req_lock); + if (ecrnx_hw->scan_request) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + struct cfg80211_scan_info info = { + .aborted = abort_status, + }; + + ECRNX_PRINT("%s: cfm status:%d, scan_request:0x%p \n", __func__, cfm->status, ecrnx_hw->scan_request); + cfg80211_scan_done(ecrnx_hw->scan_request, &info); +#else + cfg80211_scan_done(ecrnx_hw->scan_request, abort_status); +#endif + } + + ecrnx_hw->scan_request = NULL; + spin_unlock_bh(&ecrnx_hw->scan_req_lock); + + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) +u64_l getBootTime(void) +{ + struct timespec64 ts; + u64_l bootTime = 0; + + ts = ktime_to_timespec64(ktime_get_boottime()); + bootTime = ts.tv_sec; + bootTime *= 1000000; + bootTime += ts.tv_nsec / 1000; + return bootTime; +} +#endif +static inline int ecrnx_rx_scanu_result_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct cfg80211_bss *bss = NULL; + struct ieee80211_channel *chan; + struct scanu_result_ind *ind = (struct scanu_result_ind *)msg->param; + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)ind->payload; + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + chan = ieee80211_get_channel(ecrnx_hw->wiphy, ind->center_freq); + + if (chan != NULL) + { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) + if(ieee80211_is_beacon(mgmt->frame_control)) + { + mgmt->u.beacon.timestamp = getBootTime(); + } + if(ieee80211_is_probe_resp(mgmt->frame_control)) + { + mgmt->u.probe_resp.timestamp = getBootTime(); + } +#endif + bss = cfg80211_inform_bss_frame(ecrnx_hw->wiphy, chan, + mgmt, + ind->length, ind->rssi * 100, GFP_ATOMIC); + } + + if (bss != NULL) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + cfg80211_put_bss(ecrnx_hw->wiphy, bss); +#else + cfg80211_put_bss(bss); +#endif + +#if defined(CONFIG_ECRNX_DEBUGFS_CUSTOM) + ecrnx_debugfs_survey_info_update(ecrnx_hw, bss); +#endif + + return 0; +} +#endif /* CONFIG_ECRNX_FULLMAC */ + +/*************************************************************************** + * Messages from ME task + **************************************************************************/ +#ifdef CONFIG_ECRNX_FULLMAC +static inline int ecrnx_rx_me_tkip_mic_failure_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct me_tkip_mic_failure_ind *ind = (struct me_tkip_mic_failure_ind *)msg->param; + struct ecrnx_vif *ecrnx_vif = ecrnx_hw->vif_table[ind->vif_idx]; + struct net_device *dev = ecrnx_vif->ndev; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + cfg80211_michael_mic_failure(dev, (u8 *)&ind->addr, (ind->ga?NL80211_KEYTYPE_GROUP: + NL80211_KEYTYPE_PAIRWISE), ind->keyid, + (u8 *)&ind->tsc, GFP_ATOMIC); + + return 0; +} + +static inline int ecrnx_rx_me_tx_credits_update_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct me_tx_credits_update_ind *ind = (struct me_tx_credits_update_ind *)msg->param; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + ecrnx_txq_credit_update(ecrnx_hw, ind->sta_idx, ind->tid, ind->credits); + + return 0; +} +#endif /* CONFIG_ECRNX_FULLMAC */ + +/*************************************************************************** + * Messages from SM task + **************************************************************************/ +#ifdef CONFIG_ECRNX_FULLMAC +static inline int ecrnx_rx_sm_connect_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct sm_connect_ind *ind = (struct sm_connect_ind *)msg->param; + struct ecrnx_vif *ecrnx_vif = ecrnx_hw->vif_table[ind->vif_idx]; + struct net_device *dev = ecrnx_vif->ndev; + const u8 *req_ie, *rsp_ie; + const u8 *extcap_ie; + const struct ieee_types_extcap *extcap; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + spin_lock_bh(&ecrnx_hw->connect_req_lock); + /* Retrieve IE addresses and lengths */ + req_ie = (const u8 *)ind->assoc_ie_buf; + rsp_ie = req_ie + ind->assoc_req_ie_len; + + // Fill-in the AP information + if (ind->status_code == 0) + { + struct ecrnx_sta *sta = &ecrnx_hw->sta_table[ind->ap_idx]; + u8 txq_status; + struct ieee80211_channel *chan; + struct cfg80211_chan_def chandef; + + sta->valid = true; + memset(&sta->rx_pn, 0, TID_MAX * sizeof(uint64_t)); + memset(&ecrnx_vif->rx_pn, 0, TID_MAX * sizeof(uint64_t)); + sta->sta_idx = ind->ap_idx; + sta->ch_idx = ind->ch_idx; + sta->vif_idx = ind->vif_idx; + sta->vlan_idx = sta->vif_idx; + sta->qos = ind->qos; + sta->acm = ind->acm; + sta->ps.active = false; + sta->aid = ind->aid; + sta->band = ind->chan.band; + sta->width = ind->chan.type; + sta->center_freq = ind->chan.prim20_freq; + sta->center_freq1 = ind->chan.center1_freq; + sta->center_freq2 = ind->chan.center2_freq; + ecrnx_vif->sta.ap = sta; + ecrnx_vif->generation++; + chan = ieee80211_get_channel(ecrnx_hw->wiphy, ind->chan.prim20_freq); + cfg80211_chandef_create(&chandef, chan, NL80211_CHAN_NO_HT); + if (!ecrnx_hw->mod_params->ht_on) + chandef.width = NL80211_CHAN_WIDTH_20_NOHT; + else + chandef.width = chnl2bw[ind->chan.type]; + chandef.center_freq1 = ind->chan.center1_freq; + chandef.center_freq2 = ind->chan.center2_freq; + ecrnx_chanctx_link(ecrnx_vif, ind->ch_idx, &chandef); + memcpy(sta->mac_addr, ind->bssid.array, ETH_ALEN); + if (ind->ch_idx == ecrnx_hw->cur_chanctx) { + txq_status = 0; + } else { + txq_status = ECRNX_TXQ_STOP_CHAN; + } + memcpy(sta->ac_param, ind->ac_param, sizeof(sta->ac_param)); + ecrnx_txq_sta_init(ecrnx_hw, sta, txq_status); + ecrnx_rx_reord_sta_init(ecrnx_hw, ecrnx_hw->vif_table[sta->vif_idx], sta->sta_idx); + ecrnx_dbgfs_register_sta(ecrnx_hw, sta); + ECRNX_PRINT("ecrnx_rx_sm_connect_ind, mac[%02x:%02x:%02x:%02x:%02x:%02x], status_code:%d \n", \ + sta->mac_addr[0], sta->mac_addr[1], sta->mac_addr[2], sta->mac_addr[3], sta->mac_addr[4], sta->mac_addr[5], ind->status_code); + ecrnx_txq_tdls_vif_init(ecrnx_vif); + ecrnx_mu_group_sta_init(sta, NULL); + /* Look for TDLS Channel Switch Prohibited flag in the Extended Capability + * Information Element*/ + extcap_ie = cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, rsp_ie, ind->assoc_rsp_ie_len); + if (extcap_ie && extcap_ie[1] >= 5) { + extcap = (void *)(extcap_ie); + ecrnx_vif->tdls_chsw_prohibited = extcap->ext_capab[4] & WLAN_EXT_CAPA5_TDLS_CH_SW_PROHIBITED; + } + +#ifdef CONFIG_ECRNX_BFMER + /* If Beamformer feature is activated, check if features can be used + * with the new peer device + */ + if (ecrnx_hw->mod_params->bfmer) { + const u8 *vht_capa_ie; + const struct ieee80211_vht_cap *vht_cap; + + do { + /* Look for VHT Capability Information Element */ + vht_capa_ie = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, rsp_ie, + ind->assoc_rsp_ie_len); + + /* Stop here if peer device does not support VHT */ + if (!vht_capa_ie) { + break; + } + + vht_cap = (const struct ieee80211_vht_cap *)(vht_capa_ie + 2); + + /* Send MM_BFMER_ENABLE_REQ message if needed */ + ecrnx_send_bfmer_enable(ecrnx_hw, sta, vht_cap); + } while (0); + } +#endif //(CONFIG_ECRNX_BFMER) + +#ifdef CONFIG_ECRNX_MON_DATA + // If there are 1 sta and 1 monitor interface active at the same time then + // monitor interface channel context is always the same as the STA interface. + // This doesn't work with 2 STA interfaces but we don't want to support it. + if (ecrnx_hw->monitor_vif != ECRNX_INVALID_VIF) { + struct ecrnx_vif *ecrnx_mon_vif = ecrnx_hw->vif_table[ecrnx_hw->monitor_vif]; + ecrnx_chanctx_unlink(ecrnx_mon_vif); + ecrnx_chanctx_link(ecrnx_mon_vif, ind->ch_idx, NULL); + } +#endif + } + + if (ind->roamed) { + struct cfg80211_roam_info info; + memset(&info, 0, sizeof(info)); + if (ecrnx_vif->ch_index < NX_CHAN_CTXT_CNT) + info.channel = ecrnx_hw->chanctx_table[ecrnx_vif->ch_index].chan_def.chan; + info.bssid = (const u8 *)ind->bssid.array; + info.req_ie = req_ie; + info.req_ie_len = ind->assoc_req_ie_len; + info.resp_ie = rsp_ie; + info.resp_ie_len = ind->assoc_rsp_ie_len; + cfg80211_roamed(dev, &info, GFP_ATOMIC); + } else { + cfg80211_connect_result(dev, (const u8 *)ind->bssid.array, req_ie, + ind->assoc_req_ie_len, rsp_ie, + ind->assoc_rsp_ie_len, ind->status_code, + GFP_ATOMIC); + } + + netif_tx_start_all_queues(dev); + netif_carrier_on(dev); + spin_unlock_bh(&ecrnx_hw->connect_req_lock); + + return 0; +} + +static inline int ecrnx_rx_sm_disconnect_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct sm_disconnect_ind *ind = (struct sm_disconnect_ind *)msg->param; + struct ecrnx_vif *ecrnx_vif = ecrnx_hw->vif_table[ind->vif_idx]; + struct net_device *dev = ecrnx_vif->ndev; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + ECRNX_PRINT("%s:dev:%p, vif_up:%d, reason_code:%d \n", __func__, dev, ecrnx_vif->up, ind->reason_code); + /* if vif is not up, ecrnx_close has already been called */ + if (ecrnx_vif->up) { + if (!ind->reassoc) { + cfg80211_disconnected(dev, ind->reason_code, NULL, 0, + (ind->reason_code <= 1), GFP_ATOMIC); + if (ecrnx_vif->sta.ft_assoc_ies) { + kfree(ecrnx_vif->sta.ft_assoc_ies); + ecrnx_vif->sta.ft_assoc_ies = NULL; + ecrnx_vif->sta.ft_assoc_ies_len = 0; + } + } + netif_tx_stop_all_queues(dev); + netif_carrier_off(dev); + } + + if (ecrnx_vif->sta.ap && ecrnx_vif->sta.ap->valid) + { + ecrnx_dbgfs_unregister_sta(ecrnx_hw, ecrnx_vif->sta.ap); +#ifdef CONFIG_ECRNX_BFMER + /* Disable Beamformer if supported */ + ecrnx_bfmer_report_del(ecrnx_hw, ecrnx_vif->sta.ap); +#endif //(CONFIG_ECRNX_BFMER) + + ecrnx_txq_sta_deinit(ecrnx_hw, ecrnx_vif->sta.ap); + ecrnx_rx_reord_sta_deinit(ecrnx_hw, ecrnx_vif->sta.ap->sta_idx, true); + ecrnx_vif->sta.ap->valid = false; + ecrnx_vif->sta.ap = NULL; + } + ecrnx_txq_tdls_vif_deinit(ecrnx_vif); + //ecrnx_dbgfs_unregister_sta(ecrnx_hw, ecrnx_vif->sta.ap); + ecrnx_vif->generation++; + ecrnx_external_auth_disable(ecrnx_vif); + ecrnx_chanctx_unlink(ecrnx_vif); + + return 0; +} + +static inline int ecrnx_rx_sm_external_auth_required_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct sm_external_auth_required_ind *ind = + (struct sm_external_auth_required_ind *)msg->param; + struct ecrnx_vif *ecrnx_vif = ecrnx_hw->vif_table[ind->vif_idx]; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) + struct net_device *dev = ecrnx_vif->ndev; + struct cfg80211_external_auth_params params; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + params.action = NL80211_EXTERNAL_AUTH_START; + memcpy(params.bssid, ind->bssid.array, ETH_ALEN); + params.ssid.ssid_len = ind->ssid.length; + memcpy(params.ssid.ssid, ind->ssid.array, + min_t(size_t, ind->ssid.length, sizeof(params.ssid.ssid))); + params.key_mgmt_suite = ind->akm; + + if ((ind->vif_idx > NX_VIRT_DEV_MAX) || !ecrnx_vif->up || + (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_STATION) || + cfg80211_external_auth_request(dev, ¶ms, GFP_ATOMIC)) { + wiphy_err(ecrnx_hw->wiphy, "Failed to start external auth on vif %d", + ind->vif_idx); + ecrnx_send_sm_external_auth_required_rsp(ecrnx_hw, ecrnx_vif, + WLAN_STATUS_UNSPECIFIED_FAILURE); + return 0; + } + + ecrnx_external_auth_enable(ecrnx_vif); +#else + ecrnx_send_sm_external_auth_required_rsp(ecrnx_hw, ecrnx_vif, + WLAN_STATUS_UNSPECIFIED_FAILURE); +#endif + return 0; +} + +static inline int ecrnx_rx_sm_ft_auth_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct sm_ft_auth_ind *ind = (struct sm_ft_auth_ind *)msg->param; + struct ecrnx_vif *ecrnx_vif = ecrnx_hw->vif_table[ind->vif_idx]; + struct sk_buff *skb; + size_t data_len = (offsetof(struct ieee80211_mgmt, u.auth.variable) + + ind->ft_ie_len); + skb = dev_alloc_skb(data_len); + if (skb) { + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb_put(skb, data_len); + mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_AUTH); + memcpy(mgmt->u.auth.variable, ind->ft_ie_buf, ind->ft_ie_len); + ecrnx_rx_defer_skb(ecrnx_hw, ecrnx_vif, skb); + dev_kfree_skb(skb); + } else { + netdev_warn(ecrnx_vif->ndev, "Allocation failed for FT auth ind\n"); + } + return 0; +} +static inline int ecrnx_rx_twt_setup_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct twt_setup_ind *ind = (struct twt_setup_ind *)msg->param; + struct ecrnx_sta *ecrnx_sta = &ecrnx_hw->sta_table[ind->sta_idx]; + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + memcpy(&ecrnx_sta->twt_ind, ind, sizeof(struct twt_setup_ind)); + return 0; +} + +static inline int ecrnx_rx_mesh_path_create_cfm(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct mesh_path_create_cfm *cfm = (struct mesh_path_create_cfm *)msg->param; + struct ecrnx_vif *ecrnx_vif = ecrnx_hw->vif_table[cfm->vif_idx]; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Check we well have a Mesh Point Interface */ + if (ecrnx_vif && (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_MESH_POINT)) + ecrnx_vif->ap.flags &= ~ECRNX_AP_CREATE_MESH_PATH; + + return 0; +} + +static inline int ecrnx_rx_mesh_peer_update_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct mesh_peer_update_ind *ind = (struct mesh_peer_update_ind *)msg->param; + struct ecrnx_vif *ecrnx_vif = ecrnx_hw->vif_table[ind->vif_idx]; + struct ecrnx_sta *ecrnx_sta = &ecrnx_hw->sta_table[ind->sta_idx]; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + if ((ind->vif_idx >= (NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX)) || + (ecrnx_vif && (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT)) || + (ind->sta_idx >= NX_REMOTE_STA_MAX)) + return 1; + + if (ecrnx_vif->ap.flags & ECRNX_AP_USER_MESH_PM) + { + if (!ind->estab && ecrnx_sta->valid) { + ecrnx_sta->ps.active = false; + ecrnx_sta->valid = false; + list_del_init(&ecrnx_sta->list); + ecrnx_txq_sta_deinit(ecrnx_hw, ecrnx_sta); + ecrnx_dbgfs_unregister_sta(ecrnx_hw, ecrnx_sta); + } else { + WARN_ON(0); + } + } else { + /* Check if peer link has been established or lost */ + if (ind->estab) { + if (!ecrnx_sta->valid) { + u8 txq_status; + + ecrnx_sta->valid = true; + ecrnx_sta->sta_idx = ind->sta_idx; + ecrnx_sta->ch_idx = ecrnx_vif->ch_index; + ecrnx_sta->vif_idx = ind->vif_idx; + ecrnx_sta->vlan_idx = ecrnx_sta->vif_idx; + ecrnx_sta->ps.active = false; + ecrnx_sta->qos = true; + ecrnx_sta->aid = ind->sta_idx + 1; + //ecrnx_sta->acm = ind->acm; + memcpy(ecrnx_sta->mac_addr, ind->peer_addr.array, ETH_ALEN); + + ecrnx_chanctx_link(ecrnx_vif, ecrnx_sta->ch_idx, NULL); + + /* Add the station in the list of VIF's stations */ + INIT_LIST_HEAD(&ecrnx_sta->list); + list_add_tail(&ecrnx_sta->list, &ecrnx_vif->ap.sta_list); + + /* Initialize the TX queues */ + if (ecrnx_sta->ch_idx == ecrnx_hw->cur_chanctx) { + txq_status = 0; + } else { + txq_status = ECRNX_TXQ_STOP_CHAN; + } + + ecrnx_txq_sta_init(ecrnx_hw, ecrnx_sta, txq_status); + ecrnx_dbgfs_register_sta(ecrnx_hw, ecrnx_sta); + +#ifdef CONFIG_ECRNX_BFMER + // TODO: update indication to contains vht capabilties + if (ecrnx_hw->mod_params->bfmer) + ecrnx_send_bfmer_enable(ecrnx_hw, ecrnx_sta, NULL); + + ecrnx_mu_group_sta_init(ecrnx_sta, NULL); +#endif /* CONFIG_ECRNX_BFMER */ + + } else { + WARN_ON(0); + } + } else { + if (ecrnx_sta->valid) { + ecrnx_sta->ps.active = false; + ecrnx_sta->valid = false; + + /* Remove the station from the list of VIF's station */ + list_del_init(&ecrnx_sta->list); + + ecrnx_txq_sta_deinit(ecrnx_hw, ecrnx_sta); + ecrnx_dbgfs_unregister_sta(ecrnx_hw, ecrnx_sta); + } else { + WARN_ON(0); + } + /* There is no way to inform upper layer for lost of peer, still + clean everything in the driver */ + + /* Remove the station from the list of VIF's station */ + + } + } + + return 0; +} + +static inline int ecrnx_rx_mesh_path_update_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct mesh_path_update_ind *ind = (struct mesh_path_update_ind *)msg->param; + struct ecrnx_vif *ecrnx_vif = ecrnx_hw->vif_table[ind->vif_idx]; + struct ecrnx_mesh_path *mesh_path; + bool found = false; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + if (ind->vif_idx >= (NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX)) + return 1; + + if (!ecrnx_vif || (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT)) + return 0; + + /* Look for path with provided target address */ + list_for_each_entry(mesh_path, &ecrnx_vif->ap.mpath_list, list) { + if (mesh_path->path_idx == ind->path_idx) { + found = true; + break; + } + } + + /* Check if element has been deleted */ + if (ind->delete) { + if (found) { + trace_mesh_delete_path(mesh_path); + /* Remove element from list */ + list_del_init(&mesh_path->list); + /* Free the element */ + kfree(mesh_path); + } + } + else { + if (found) { + // Update the Next Hop STA + mesh_path->nhop_sta = &ecrnx_hw->sta_table[ind->nhop_sta_idx]; + trace_mesh_update_path(mesh_path); + } else { + // Allocate a Mesh Path structure + mesh_path = kmalloc(sizeof(struct ecrnx_mesh_path), GFP_ATOMIC); + + if (mesh_path) { + INIT_LIST_HEAD(&mesh_path->list); + + mesh_path->path_idx = ind->path_idx; + mesh_path->nhop_sta = &ecrnx_hw->sta_table[ind->nhop_sta_idx]; + memcpy(&mesh_path->tgt_mac_addr, &ind->tgt_mac_addr, MAC_ADDR_LEN); + + // Insert the path in the list of path + list_add_tail(&mesh_path->list, &ecrnx_vif->ap.mpath_list); + + trace_mesh_create_path(mesh_path); + } + } + } + + return 0; +} + +static inline int ecrnx_rx_mesh_proxy_update_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct mesh_proxy_update_ind *ind = (struct mesh_proxy_update_ind *)msg->param; + struct ecrnx_vif *ecrnx_vif = ecrnx_hw->vif_table[ind->vif_idx]; + struct ecrnx_mesh_proxy *mesh_proxy; + bool found = false; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + if (ind->vif_idx >= (NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX)) + return 1; + + if (!ecrnx_vif || (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT)) + return 0; + + /* Look for path with provided external STA address */ + list_for_each_entry(mesh_proxy, &ecrnx_vif->ap.proxy_list, list) { + if (!memcmp(&ind->ext_sta_addr, &mesh_proxy->ext_sta_addr, ETH_ALEN)) { + found = true; + break; + } + } + + if (ind->delete && found) { + /* Delete mesh path */ + list_del_init(&mesh_proxy->list); + kfree(mesh_proxy); + } else if (!ind->delete && !found) { + /* Allocate a Mesh Path structure */ + mesh_proxy = (struct ecrnx_mesh_proxy *)kmalloc(sizeof(*mesh_proxy), + GFP_ATOMIC); + + if (mesh_proxy) { + INIT_LIST_HEAD(&mesh_proxy->list); + + memcpy(&mesh_proxy->ext_sta_addr, &ind->ext_sta_addr, MAC_ADDR_LEN); + mesh_proxy->local = ind->local; + + if (!ind->local) { + memcpy(&mesh_proxy->proxy_addr, &ind->proxy_mac_addr, MAC_ADDR_LEN); + } + + /* Insert the path in the list of path */ + list_add_tail(&mesh_proxy->list, &ecrnx_vif->ap.proxy_list); + } + } + + return 0; +} + +/*************************************************************************** + * Messages from APM task + **************************************************************************/ +static inline int ecrnx_rx_apm_probe_client_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + struct apm_probe_client_ind *ind = (struct apm_probe_client_ind *)msg->param; + struct ecrnx_vif *ecrnx_vif = ecrnx_hw->vif_table[ind->vif_idx]; + struct ecrnx_sta *ecrnx_sta = &ecrnx_hw->sta_table[ind->sta_idx]; + + ecrnx_sta->stats.last_act = jiffies; + cfg80211_probe_status(ecrnx_vif->ndev, ecrnx_sta->mac_addr, (u64)ind->probe_id, + ind->client_present, 0, false, GFP_ATOMIC); + return 0; +} +#endif /* CONFIG_ECRNX_FULLMAC */ + +/*************************************************************************** + * Messages from DEBUG task + **************************************************************************/ +static inline int ecrnx_rx_dbg_error_ind(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_cmd *cmd, + struct ipc_e2a_msg *msg) +{ + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + ecrnx_error_ind(ecrnx_hw); + + return 0; +} + + + + +#ifdef CONFIG_ECRNX_SOFTMAC + +static msg_cb_fct mm_hdlrs[MSG_I(MM_MAX)] = { + [MSG_I(MM_CONNECTION_LOSS_IND)] = ecrnx_rx_connection_loss_ind, + [MSG_I(MM_CHANNEL_SWITCH_IND)] = ecrnx_rx_chan_switch_ind, + [MSG_I(MM_CHANNEL_PRE_SWITCH_IND)] = ecrnx_rx_chan_pre_switch_ind, + [MSG_I(MM_REMAIN_ON_CHANNEL_EXP_IND)] = ecrnx_rx_remain_on_channel_exp_ind, +#ifdef CONFIG_ECRNX_BCN + [MSG_I(MM_PRIMARY_TBTT_IND)] = ecrnx_rx_prm_tbtt_ind, +#endif + [MSG_I(MM_P2P_VIF_PS_CHANGE_IND)] = ecrnx_rx_p2p_vif_ps_change_ind, + [MSG_I(MM_CSA_COUNTER_IND)] = ecrnx_rx_csa_counter_ind, + [MSG_I(MM_CHANNEL_SURVEY_IND)] = ecrnx_rx_channel_survey_ind, + [MSG_I(MM_RSSI_STATUS_IND)] = ecrnx_rx_rssi_status_ind, +}; + +static msg_cb_fct scan_hdlrs[MSG_I(SCAN_MAX)] = { + [MSG_I(SCAN_DONE_IND)] = ecrnx_rx_scan_done_ind, +}; + +#else /* CONFIG_ECRNX_FULLMAC */ + +static msg_cb_fct mm_hdlrs[MSG_I(MM_MAX)] = { + [MSG_I(MM_CHANNEL_SWITCH_IND)] = ecrnx_rx_chan_switch_ind, + [MSG_I(MM_CHANNEL_PRE_SWITCH_IND)] = ecrnx_rx_chan_pre_switch_ind, + [MSG_I(MM_REMAIN_ON_CHANNEL_EXP_IND)] = ecrnx_rx_remain_on_channel_exp_ind, + [MSG_I(MM_PS_CHANGE_IND)] = ecrnx_rx_ps_change_ind, + [MSG_I(MM_TRAFFIC_REQ_IND)] = ecrnx_rx_traffic_req_ind, + [MSG_I(MM_P2P_VIF_PS_CHANGE_IND)] = ecrnx_rx_p2p_vif_ps_change_ind, + [MSG_I(MM_CSA_COUNTER_IND)] = ecrnx_rx_csa_counter_ind, + [MSG_I(MM_CSA_FINISH_IND)] = ecrnx_rx_csa_finish_ind, + [MSG_I(MM_CSA_TRAFFIC_IND)] = ecrnx_rx_csa_traffic_ind, + [MSG_I(MM_CHANNEL_SURVEY_IND)] = ecrnx_rx_channel_survey_ind, + [MSG_I(MM_P2P_NOA_UPD_IND)] = ecrnx_rx_p2p_noa_upd_ind, + [MSG_I(MM_RSSI_STATUS_IND)] = ecrnx_rx_rssi_status_ind, + [MSG_I(MM_PKTLOSS_IND)] = ecrnx_rx_pktloss_notify_ind, +}; + +static msg_cb_fct scan_hdlrs[MSG_I(SCANU_MAX)] = { + [MSG_I(SCANU_START_CFM)] = ecrnx_rx_scanu_start_cfm, + [MSG_I(SCANU_RESULT_IND)] = ecrnx_rx_scanu_result_ind, + [MSG_I(SCANU_CANCEL_CFM)] = ecrnx_scanu_cancel_cfm, +}; + +static msg_cb_fct me_hdlrs[MSG_I(ME_MAX)] = { + [MSG_I(ME_TKIP_MIC_FAILURE_IND)] = ecrnx_rx_me_tkip_mic_failure_ind, + [MSG_I(ME_TX_CREDITS_UPDATE_IND)] = ecrnx_rx_me_tx_credits_update_ind, +}; + +static msg_cb_fct sm_hdlrs[MSG_I(SM_MAX)] = { + [MSG_I(SM_CONNECT_IND)] = ecrnx_rx_sm_connect_ind, + [MSG_I(SM_DISCONNECT_IND)] = ecrnx_rx_sm_disconnect_ind, + [MSG_I(SM_EXTERNAL_AUTH_REQUIRED_IND)] = ecrnx_rx_sm_external_auth_required_ind, + [MSG_I(SM_FT_AUTH_IND)] = ecrnx_rx_sm_ft_auth_ind, +}; + +static msg_cb_fct apm_hdlrs[MSG_I(APM_MAX)] = { + [MSG_I(APM_PROBE_CLIENT_IND)] = ecrnx_rx_apm_probe_client_ind, +}; +static msg_cb_fct twt_hdlrs[MSG_I(TWT_MAX)] = { + [MSG_I(TWT_SETUP_IND)] = ecrnx_rx_twt_setup_ind, +}; + +static msg_cb_fct mesh_hdlrs[MSG_I(MESH_MAX)] = { + [MSG_I(MESH_PATH_CREATE_CFM)] = ecrnx_rx_mesh_path_create_cfm, + [MSG_I(MESH_PEER_UPDATE_IND)] = ecrnx_rx_mesh_peer_update_ind, + [MSG_I(MESH_PATH_UPDATE_IND)] = ecrnx_rx_mesh_path_update_ind, + [MSG_I(MESH_PROXY_UPDATE_IND)] = ecrnx_rx_mesh_proxy_update_ind, +}; + + +#endif /* CONFIG_ECRNX_SOFTMAC */ + +static msg_cb_fct dbg_hdlrs[MSG_I(DBG_MAX)] = { + [MSG_I(DBG_ERROR_IND)] = ecrnx_rx_dbg_error_ind, +}; + +static msg_cb_fct tdls_hdlrs[MSG_I(TDLS_MAX)] = { + [MSG_I(TDLS_CHAN_SWITCH_CFM)] = ecrnx_rx_tdls_chan_switch_cfm, + [MSG_I(TDLS_CHAN_SWITCH_IND)] = ecrnx_rx_tdls_chan_switch_ind, + [MSG_I(TDLS_CHAN_SWITCH_BASE_IND)] = ecrnx_rx_tdls_chan_switch_base_ind, + [MSG_I(TDLS_PEER_PS_IND)] = ecrnx_rx_tdls_peer_ps_ind, +}; + +static msg_cb_fct *msg_hdlrs[] = { + [TASK_MM] = mm_hdlrs, + [TASK_DBG] = dbg_hdlrs, +#ifdef CONFIG_ECRNX_SOFTMAC + [TASK_SCAN] = scan_hdlrs, + [TASK_TDLS] = tdls_hdlrs, +#else + [TASK_TDLS] = tdls_hdlrs, + [TASK_SCANU] = scan_hdlrs, + [TASK_ME] = me_hdlrs, + [TASK_SM] = sm_hdlrs, + [TASK_APM] = apm_hdlrs, + [TASK_MESH] = mesh_hdlrs, + [TASK_TWT] = twt_hdlrs, +#if 0//(CONFIG_ECRNX_P2P) + [TASK_P2P_LISTEN] = p2p_listen_hdlrs, +#endif +#endif /* CONFIG_ECRNX_SOFTMAC */ +}; + +/** + * + */ +void ecrnx_rx_handle_msg(struct ecrnx_hw *ecrnx_hw, struct ipc_e2a_msg *msg) +{ + + if(!ecrnx_hw || !msg || !(&ecrnx_hw->cmd_mgr)) + { + ECRNX_ERR("ecrnx_rx_handle_msg:receive msg info error \n"); + return; + } + +#if defined(CONFIG_ECRNX_P2P) + if (MSG_T(msg->id) != TASK_P2P_LISTEN && (MSG_T(msg->id) < TASK_MM || MSG_T(msg->id) > TASK_MESH || MSG_I(msg->id) >= MSG_I(MM_MAX))) +#else + if (MSG_T(msg->id) < TASK_MM || MSG_T(msg->id) > TASK_MESH || MSG_I(msg->id) >= MSG_I(MM_MAX)) +#endif + { + ECRNX_ERR("msg id 0x%x,%d,%d, max %d, dst:0x%x, src:0x%x\n", msg->id, MSG_T(msg->id), MSG_I(msg->id), MSG_I(MM_MAX), msg->dummy_dest_id, msg->dummy_src_id); + ECRNX_ERR("skip msg %p\n", msg); + return; + } + + if(ecrnx_hw->wiphy != NULL) + { + ecrnx_hw->cmd_mgr.msgind(&ecrnx_hw->cmd_mgr, msg, + msg_hdlrs[MSG_T(msg->id)][MSG_I(msg->id)]); + } +} diff --git a/drivers/net/wireless/eswin/ecrnx_msg_rx.h b/drivers/net/wireless/eswin/ecrnx_msg_rx.h new file mode 100644 index 000000000000..66fad20cc406 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_msg_rx.h @@ -0,0 +1,18 @@ +/** + **************************************************************************************** + * + * @file ecrnx_msg_rx.h + * + * @brief RX function declarations + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ + +#ifndef _ECRNX_MSG_RX_H_ +#define _ECRNX_MSG_RX_H_ + +void ecrnx_rx_handle_msg(struct ecrnx_hw *ecrnx_hw, struct ipc_e2a_msg *msg); + +#endif /* _ECRNX_MSG_RX_H_ */ diff --git a/drivers/net/wireless/eswin/ecrnx_msg_tx.c b/drivers/net/wireless/eswin/ecrnx_msg_tx.c new file mode 100644 index 000000000000..bc51b9b90f51 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_msg_tx.c @@ -0,0 +1,3173 @@ +/** + ****************************************************************************** + * + * @file ecrnx_msg_tx.c + * + * @brief TX function definitions + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#include "ecrnx_msg_tx.h" +#include "ecrnx_mod_params.h" +#include "reg_access.h" +#ifdef CONFIG_ECRNX_BFMER +#include "ecrnx_bfmer.h" +#endif //(CONFIG_ECRNX_BFMER) +#include "ecrnx_compat.h" +#include "ecrnx_defs.h" +#include "ecrnx_calibration_data.h" +#include "eswin_utils.h" +#include "core.h" + +const struct mac_addr mac_addr_bcst = {{0xFFFF, 0xFFFF, 0xFFFF}}; + +/* Default MAC Rx filters that can be changed by mac80211 + * (via the configure_filter() callback) */ +#define ECRNX_MAC80211_CHANGEABLE ( \ + NXMAC_ACCEPT_BA_BIT | \ + NXMAC_ACCEPT_BAR_BIT | \ + NXMAC_ACCEPT_OTHER_DATA_FRAMES_BIT | \ + NXMAC_ACCEPT_PROBE_REQ_BIT | \ + NXMAC_ACCEPT_PS_POLL_BIT \ + ) + +/* Default MAC Rx filters that cannot be changed by mac80211 */ +#define ECRNX_MAC80211_NOT_CHANGEABLE ( \ + NXMAC_ACCEPT_QO_S_NULL_BIT | \ + NXMAC_ACCEPT_Q_DATA_BIT | \ + NXMAC_ACCEPT_DATA_BIT | \ + NXMAC_ACCEPT_OTHER_MGMT_FRAMES_BIT | \ + NXMAC_ACCEPT_MY_UNICAST_BIT | \ + NXMAC_ACCEPT_BROADCAST_BIT | \ + NXMAC_ACCEPT_BEACON_BIT | \ + NXMAC_ACCEPT_PROBE_RESP_BIT \ + ) + +/* Default MAC Rx filter */ +#define ECRNX_DEFAULT_RX_FILTER (ECRNX_MAC80211_CHANGEABLE | ECRNX_MAC80211_NOT_CHANGEABLE) + +const int bw2chnl[] = { + [NL80211_CHAN_WIDTH_20_NOHT] = PHY_CHNL_BW_20, + [NL80211_CHAN_WIDTH_20] = PHY_CHNL_BW_20, + [NL80211_CHAN_WIDTH_40] = PHY_CHNL_BW_40, + [NL80211_CHAN_WIDTH_80] = PHY_CHNL_BW_80, + [NL80211_CHAN_WIDTH_160] = PHY_CHNL_BW_160, + [NL80211_CHAN_WIDTH_80P80] = PHY_CHNL_BW_80P80, +}; + +const int chnl2bw[] = { + [PHY_CHNL_BW_20] = NL80211_CHAN_WIDTH_20, + [PHY_CHNL_BW_40] = NL80211_CHAN_WIDTH_40, + [PHY_CHNL_BW_80] = NL80211_CHAN_WIDTH_80, + [PHY_CHNL_BW_160] = NL80211_CHAN_WIDTH_160, + [PHY_CHNL_BW_80P80] = NL80211_CHAN_WIDTH_80P80, +}; + +/*****************************************************************************/ +/* + * Parse the ampdu density to retrieve the value in usec, according to the + * values defined in ieee80211.h + */ +static inline u8 ecrnx_ampdudensity2usec(u8 ampdudensity) +{ + switch (ampdudensity) { + case IEEE80211_HT_MPDU_DENSITY_NONE: + return 0; + /* 1 microsecond is our granularity */ + case IEEE80211_HT_MPDU_DENSITY_0_25: + case IEEE80211_HT_MPDU_DENSITY_0_5: + case IEEE80211_HT_MPDU_DENSITY_1: + return 1; + case IEEE80211_HT_MPDU_DENSITY_2: + return 2; + case IEEE80211_HT_MPDU_DENSITY_4: + return 4; + case IEEE80211_HT_MPDU_DENSITY_8: + return 8; + case IEEE80211_HT_MPDU_DENSITY_16: + return 16; + default: + return 0; + } +} + +static inline bool use_pairwise_key(struct cfg80211_crypto_settings *crypto) +{ + if ((crypto->cipher_group == WLAN_CIPHER_SUITE_WEP40) || + (crypto->cipher_group == WLAN_CIPHER_SUITE_WEP104)) + return false; + + return true; +} + +static inline bool is_non_blocking_msg(int id) +{ + return ((id == MM_TIM_UPDATE_REQ) || (id == ME_RC_SET_RATE_REQ) || + (id == MM_BFMER_ENABLE_REQ) || (id == ME_TRAFFIC_IND_REQ) || + (id == TDLS_PEER_TRAFFIC_IND_REQ) || + (id == MESH_PATH_CREATE_REQ) || (id == MESH_PROXY_ADD_REQ) || + (id == SM_EXTERNAL_AUTH_REQUIRED_RSP)); +} + +#ifdef CONFIG_ECRNX_FULLMAC +/** + * copy_connect_ies -- Copy Association Elements in the the request buffer + * send to the firmware + * + * @vif: Vif that received the connection request + * @req: Connection request to send to the firmware + * @sme: Connection info + * + * For driver that do not use userspace SME (like this one) the host connection + * request doesn't explicitly mentions that the connection can use FT over the + * air. if FT is possible, send the FT elements (as received in update_ft_ies callback) + * to the firmware + * + * In all other cases simply copy the list povided by the user space in the + * request buffer + */ +static void copy_connect_ies(struct ecrnx_vif *vif, struct sm_connect_req *req, + struct cfg80211_connect_params *sme) +{ + if ((sme->auth_type == NL80211_AUTHTYPE_FT) && !(vif->sta.flags & ECRNX_STA_FT_OVER_DS)) + { + const struct ecrnx_element *rsne, *fte, *mde; + uint8_t *pos; + rsne = cfg80211_find_ecrnx_elem(WLAN_EID_RSN, vif->sta.ft_assoc_ies, + vif->sta.ft_assoc_ies_len); + fte = cfg80211_find_ecrnx_elem(WLAN_EID_FAST_BSS_TRANSITION, vif->sta.ft_assoc_ies, + vif->sta.ft_assoc_ies_len); + mde = cfg80211_find_ecrnx_elem(WLAN_EID_MOBILITY_DOMAIN, + vif->sta.ft_assoc_ies, vif->sta.ft_assoc_ies_len); + pos = (uint8_t *)req->ie_buf; + + // We can use FT over the air + memcpy(&vif->sta.ft_target_ap, sme->bssid, ETH_ALEN); + + if (rsne) { + memcpy(pos, rsne, sizeof(struct ecrnx_element) + rsne->datalen); + pos += sizeof(struct ecrnx_element) + rsne->datalen; + } + memcpy(pos, mde, sizeof(struct ecrnx_element) + mde->datalen); + pos += sizeof(struct ecrnx_element) + mde->datalen; + if (fte) { + memcpy(pos, fte, sizeof(struct ecrnx_element) + fte->datalen); + pos += sizeof(struct ecrnx_element) + fte->datalen; + } + + req->ie_len = pos - (uint8_t *)req->ie_buf; + } + else + { + memcpy(req->ie_buf, sme->ie, sme->ie_len); + req->ie_len = sme->ie_len; + } +} + +/** + * update_connect_req -- Return the length of the association request IEs + * + * @vif: Vif that received the connection request + * @sme: Connection info + * + * Return the ft_ie_len in case of FT. + * FT over the air is possible if: + * - auth_type = AUTOMATIC (if already set to FT then it means FT over DS) + * - already associated to a FT BSS + * - Target Mobility domain is the same as the curent one + * + * If FT is not possible return ie length of the connection info + */ +static int update_connect_req(struct ecrnx_vif *vif, struct cfg80211_connect_params *sme) +{ + if ((vif->sta.ap) && + (vif->sta.ft_assoc_ies) && + (sme->auth_type == NL80211_AUTHTYPE_AUTOMATIC)) + { + const struct ecrnx_element *rsne, *fte, *mde, *mde_req; + int ft_ie_len = 0; + + mde_req = cfg80211_find_ecrnx_elem(WLAN_EID_MOBILITY_DOMAIN, + sme->ie, sme->ie_len); + mde = cfg80211_find_ecrnx_elem(WLAN_EID_MOBILITY_DOMAIN, + vif->sta.ft_assoc_ies, vif->sta.ft_assoc_ies_len); + if (!mde || !mde_req || + memcmp(mde, mde_req, sizeof(struct ecrnx_element) + mde->datalen)) + { + return sme->ie_len; + } + + ft_ie_len += sizeof(struct ecrnx_element) + mde->datalen; + + rsne = cfg80211_find_ecrnx_elem(WLAN_EID_RSN, vif->sta.ft_assoc_ies, + vif->sta.ft_assoc_ies_len); + fte = cfg80211_find_ecrnx_elem(WLAN_EID_FAST_BSS_TRANSITION, vif->sta.ft_assoc_ies, + vif->sta.ft_assoc_ies_len); + + if (rsne && fte) + { + ft_ie_len += 2 * sizeof(struct ecrnx_element) + rsne->datalen + fte->datalen; + sme->auth_type = NL80211_AUTHTYPE_FT; + return ft_ie_len; + } + else if (rsne || fte) + { + netdev_warn(vif->ndev, "Missing RSNE or FTE element, skip FT over air"); + } + else + { + sme->auth_type = NL80211_AUTHTYPE_FT; + return ft_ie_len; + } + } + return sme->ie_len; +} +#endif +static inline u8_l get_chan_flags(uint32_t flags) +{ + u8_l chan_flags = 0; +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0) + if (flags & IEEE80211_CHAN_PASSIVE_SCAN) +#else + if (flags & IEEE80211_CHAN_NO_IR) + chan_flags |= CHAN_NO_IR; + if (flags & IEEE80211_CHAN_RADAR) + chan_flags |= CHAN_RADAR; +#endif + return chan_flags; +} + +static inline s8_l chan_to_fw_pwr(int power) +{ + return power > 127 ? 127 : (s8_l)power; +} + +static void cfg80211_to_ecrnx_chan(const struct cfg80211_chan_def *chandef, + struct mac_chan_op *chan) +{ + chan->band = chandef->chan->band; + chan->type = bw2chnl[chandef->width]; + chan->prim20_freq = chandef->chan->center_freq; + chan->center1_freq = chandef->center_freq1; + chan->center2_freq = chandef->center_freq2; + chan->flags = get_chan_flags(chandef->chan->flags); + chan->tx_power = chan_to_fw_pwr(chandef->chan->max_power); +} + +static inline void limit_chan_bw(u8_l *bw, u16_l primary, u16_l *center1) +{ + int oft, new_oft = 10; + + if (*bw <= PHY_CHNL_BW_40) + return; + + oft = *center1 - primary; + *bw = PHY_CHNL_BW_40; + + if (oft < 0) + new_oft = new_oft * -1; + if (abs(oft) == 10 || abs(oft) == 50) + new_oft = new_oft * -1; + + *center1 = primary + new_oft; +} + +/** + ****************************************************************************** + * @brief Allocate memory for a message + * + * This primitive allocates memory for a message that has to be sent. The memory + * is allocated dynamically on the heap and the length of the variable parameter + * structure has to be provided in order to allocate the correct size. + * + * Several additional parameters are provided which will be preset in the message + * and which may be used internally to choose the kind of memory to allocate. + * + * The memory allocated will be automatically freed by the kernel, after the + * pointer has been sent to ke_msg_send(). If the message is not sent, it must + * be freed explicitly with ke_msg_free(). + * + * Allocation failure is considered critical and should not happen. + * + * @param[in] id Message identifier + * @param[in] dest_id Destination Task Identifier + * @param[in] src_id Source Task Identifier + * @param[in] param_len Size of the message parameters to be allocated + * + * @return Pointer to the parameter member of the ke_msg. If the parameter + * structure is empty, the pointer will point to the end of the message + * and should not be used (except to retrieve the message pointer or to + * send the message) + ****************************************************************************** + */ +static inline void *ecrnx_msg_zalloc(lmac_msg_id_t const id, + lmac_task_id_t const dest_id, + lmac_task_id_t const src_id, + uint16_t const param_len) +{ + struct lmac_msg *msg; + gfp_t flags; + + if (is_non_blocking_msg(id) && in_softirq()) + flags = GFP_ATOMIC; + else + flags = GFP_KERNEL; + + msg = (struct lmac_msg *)kzalloc(sizeof(struct lmac_msg) + param_len, + flags); + if (msg == NULL) { + ECRNX_ERR(KERN_CRIT "%s: msg allocation failed\n", __func__); + return NULL; + } + + msg->id = id; + msg->dest_id = dest_id; + msg->src_id = src_id; + msg->param_len = param_len; + + return msg->param; +} + +static void ecrnx_msg_free(struct ecrnx_hw *ecrnx_hw, const void *msg_params) +{ + struct lmac_msg *msg = container_of((void *)msg_params, + struct lmac_msg, param); + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Free the message */ + kfree(msg); +} + +static int ecrnx_send_msg(struct ecrnx_hw *ecrnx_hw, const void *msg_params, + int reqcfm, lmac_msg_id_t reqid, void *cfm) +{ + struct lmac_msg *msg; + struct ecrnx_cmd *cmd; + bool nonblock; + int ret; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + msg = container_of((void *)msg_params, struct lmac_msg, param); + + if (!test_bit(ECRNX_DEV_STARTED, &ecrnx_hw->flags) && + reqid != MM_RESET_CFM && reqid != MM_VERSION_CFM && + reqid != MM_START_CFM && reqid != MM_SET_IDLE_CFM && + reqid != ME_CONFIG_CFM && reqid != MM_SET_PS_MODE_CFM && + reqid != ME_CHAN_CONFIG_CFM && reqid != MM_SET_GAIN_DELTA_CFM && + reqid != MM_GET_CAL_RESULT_CFM) { + ECRNX_ERR(KERN_CRIT "%s: bypassing (ECRNX_DEV_RESTARTING set) 0x%02x\n", + __func__, reqid); + kfree(msg); + return -EBUSY; + } else if (!ecrnx_hw->ipc_env) { + ECRNX_ERR(KERN_CRIT "%s: bypassing (restart must have failed)\n", __func__); + kfree(msg); + return -EBUSY; + } + + nonblock = is_non_blocking_msg(msg->id); + +#if defined(CONFIG_ECRNX_ESWIN_USB) + if(register_status == false){ + kfree(msg); + return -ENODEV; + } +#endif + + cmd = kzalloc(sizeof(struct ecrnx_cmd), nonblock ? GFP_ATOMIC : GFP_KERNEL); + cmd->result = -EINTR; + cmd->id = msg->id; + cmd->reqid = reqid; + cmd->a2e_msg = msg; + cmd->e2a_msg = cfm; + if (nonblock) + cmd->flags = ECRNX_CMD_FLAG_NONBLOCK; + if (reqcfm) + cmd->flags |= ECRNX_CMD_FLAG_REQ_CFM; + if(ecrnx_hw->wiphy != NULL) + { + ret = ecrnx_hw->cmd_mgr.queue(&ecrnx_hw->cmd_mgr, cmd); + } + if (!nonblock) + kfree(cmd); + + if (!ret) + ret = cmd->result; + + return ret; +} + +/****************************************************************************** + * Control messages handling functions (SOFTMAC and FULLMAC) + *****************************************************************************/ +int ecrnx_send_reset(struct ecrnx_hw *ecrnx_hw) +{ + void *void_param; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* RESET REQ has no parameter */ + void_param = ecrnx_msg_zalloc(MM_RESET_REQ, TASK_MM, DRV_TASK_ID, 0); + if (!void_param) + return -ENOMEM; + + return ecrnx_send_msg(ecrnx_hw, void_param, 1, MM_RESET_CFM, NULL); +} + +int ecrnx_send_start(struct ecrnx_hw *ecrnx_hw) +{ + struct mm_start_req *start_req_param; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the START REQ message */ + start_req_param = ecrnx_msg_zalloc(MM_START_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_start_req)); + if (!start_req_param) + return -ENOMEM; + + /* Set parameters for the START message */ + memcpy(&start_req_param->phy_cfg, &ecrnx_hw->phy.cfg, sizeof(ecrnx_hw->phy.cfg)); + start_req_param->uapsd_timeout = (u32_l)ecrnx_hw->mod_params->uapsd_timeout; + start_req_param->lp_clk_accuracy = (u16_l)ecrnx_hw->mod_params->lp_clk_ppm; + start_req_param->tx_timeout[AC_BK] = (u16_l)ecrnx_hw->mod_params->tx_to_bk; + start_req_param->tx_timeout[AC_BE] = (u16_l)ecrnx_hw->mod_params->tx_to_be; + start_req_param->tx_timeout[AC_VI] = (u16_l)ecrnx_hw->mod_params->tx_to_vi; + start_req_param->tx_timeout[AC_VO] = (u16_l)ecrnx_hw->mod_params->tx_to_vo; + + /* Send the START REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, start_req_param, 1, MM_START_CFM, NULL); +} + +int ecrnx_send_version_req(struct ecrnx_hw *ecrnx_hw, struct mm_version_cfm *cfm) +{ + void *void_param; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + /* VERSION REQ has no parameter */ + void_param = ecrnx_msg_zalloc(MM_VERSION_REQ, TASK_MM, DRV_TASK_ID, 0); + if (!void_param) + return -ENOMEM; + + return ecrnx_send_msg(ecrnx_hw, void_param, 1, MM_VERSION_CFM, cfm); +} + +int ecrnx_send_add_if(struct ecrnx_hw *ecrnx_hw, const unsigned char *mac, + enum nl80211_iftype iftype, bool p2p, struct mm_add_if_cfm *cfm) +{ + struct mm_add_if_req *add_if_req_param; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the ADD_IF_REQ message */ + add_if_req_param = ecrnx_msg_zalloc(MM_ADD_IF_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_add_if_req)); + if (!add_if_req_param) + return -ENOMEM; + + /* Set parameters for the ADD_IF_REQ message */ + memcpy(&(add_if_req_param->addr.array[0]), mac, ETH_ALEN); + switch (iftype) { + #ifdef CONFIG_ECRNX_FULLMAC + case NL80211_IFTYPE_P2P_CLIENT: + add_if_req_param->p2p = true; + add_if_req_param->type = MM_STA; + break; + #endif /* CONFIG_ECRNX_FULLMAC */ + case NL80211_IFTYPE_STATION: + add_if_req_param->type = MM_STA; + break; + + case NL80211_IFTYPE_ADHOC: + add_if_req_param->type = MM_IBSS; + break; + + #ifdef CONFIG_ECRNX_FULLMAC + case NL80211_IFTYPE_P2P_GO: + add_if_req_param->p2p = true; + add_if_req_param->type = MM_AP; + break; + #endif /* CONFIG_ECRNX_FULLMAC */ + case NL80211_IFTYPE_AP: + add_if_req_param->type = MM_AP; + break; + case NL80211_IFTYPE_MESH_POINT: + add_if_req_param->type = MM_MESH_POINT; + break; + case NL80211_IFTYPE_AP_VLAN: + return -1; + case NL80211_IFTYPE_MONITOR: + add_if_req_param->type = MM_MONITOR; + break; + default: + add_if_req_param->type = MM_STA; + break; + } + + #ifdef CONFIG_ECRNX_SOFTMAC + add_if_req_param->p2p = p2p; + #endif /* CONFIG_ECRNX_SOFTMAC */ + + /* Send the ADD_IF_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, add_if_req_param, 1, MM_ADD_IF_CFM, cfm); +} + +int ecrnx_send_remove_if(struct ecrnx_hw *ecrnx_hw, u8 vif_index) +{ + struct mm_remove_if_req *remove_if_req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_REMOVE_IF_REQ message */ + remove_if_req = ecrnx_msg_zalloc(MM_REMOVE_IF_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_remove_if_req)); + if (!remove_if_req) + return -ENOMEM; + + /* Set parameters for the MM_REMOVE_IF_REQ message */ + remove_if_req->inst_nbr = vif_index; + + /* Send the MM_REMOVE_IF_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, remove_if_req, 1, MM_REMOVE_IF_CFM, NULL); +} + +int ecrnx_send_set_channel(struct ecrnx_hw *ecrnx_hw, int phy_idx, + struct mm_set_channel_cfm *cfm) +{ +#ifdef CONFIG_ECRNX_SOFTMAC + struct cfg80211_chan_def *chandef = &ecrnx_hw->hw->conf.chandef; +#endif /* CONFIG_ECRNX_SOFTMAC */ + struct mm_set_channel_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + if (phy_idx >= ecrnx_hw->phy.cnt) + return -ENOTSUPP; + + req = ecrnx_msg_zalloc(MM_SET_CHANNEL_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_set_channel_req)); + if (!req) + return -ENOMEM; + + if (phy_idx == 0) { +#ifdef CONFIG_ECRNX_SOFTMAC + cfg80211_to_ecrnx_chan(chandef, &req->chan); +#else + /* On FULLMAC only setting channel of secondary chain */ + wiphy_err(ecrnx_hw->wiphy, "Trying to set channel of primary chain"); + return 0; +#endif /* CONFIG_ECRNX_SOFTMAC */ + } else { + req->chan = ecrnx_hw->phy.sec_chan; + } + + req->index = phy_idx; + + if (ecrnx_hw->phy.limit_bw) + limit_chan_bw(&req->chan.type, req->chan.prim20_freq, &req->chan.center1_freq); + + /*ECRNX_DBG("mac80211: freq=%d(c1:%d - c2:%d)/width=%d - band=%d\n" + " hw(%d): prim20=%d(c1:%d - c2:%d)/ type=%d - band=%d\n", + center_freq, center_freq1, center_freq2, width, band, + phy_idx, req->chan.prim20_freq, req->chan.center1_freq, + req->chan.center2_freq, req->chan.type, req->chan.band);*/ + + /* Send the MM_SET_CHANNEL_REQ REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MM_SET_CHANNEL_CFM, cfm); +} + + +int ecrnx_send_key_add(struct ecrnx_hw *ecrnx_hw, u8 vif_idx, u8 sta_idx, bool pairwise, + u8 *key, u8 key_len, u8 key_idx, u8 cipher_suite, + struct mm_key_add_cfm *cfm) +{ + struct mm_key_add_req *key_add_req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_KEY_ADD_REQ message */ + key_add_req = ecrnx_msg_zalloc(MM_KEY_ADD_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_key_add_req)); + if (!key_add_req) + return -ENOMEM; + + /* Set parameters for the MM_KEY_ADD_REQ message */ + if (sta_idx != 0xFF) { + /* Pairwise key */ + key_add_req->sta_idx = sta_idx; + } else { + /* Default key */ + key_add_req->sta_idx = sta_idx; + key_add_req->key_idx = (u8_l)key_idx; /* only useful for default keys */ + } + key_add_req->pairwise = pairwise; + key_add_req->inst_nbr = vif_idx; + key_add_req->key.length = key_len; + memcpy(&(key_add_req->key.array[0]), key, key_len); + + key_add_req->cipher_suite = cipher_suite; + + ECRNX_DBG("%s: sta_idx:%d key_idx:%d inst_nbr:%d cipher:%d key_len:%d\n", __func__, + key_add_req->sta_idx, key_add_req->key_idx, key_add_req->inst_nbr, + key_add_req->cipher_suite, key_add_req->key.length); +#if defined(CONFIG_ECRNX_DBG) || defined(CONFIG_DYNAMIC_DEBUG) + print_hex_dump_bytes("key: ", DUMP_PREFIX_OFFSET, key_add_req->key.array, key_add_req->key.length); +#endif + + /* Send the MM_KEY_ADD_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, key_add_req, 1, MM_KEY_ADD_CFM, cfm); +} + +int ecrnx_send_key_del(struct ecrnx_hw *ecrnx_hw, uint8_t hw_key_idx) +{ + struct mm_key_del_req *key_del_req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_KEY_DEL_REQ message */ + key_del_req = ecrnx_msg_zalloc(MM_KEY_DEL_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_key_del_req)); + if (!key_del_req) + return -ENOMEM; + + /* Set parameters for the MM_KEY_DEL_REQ message */ + key_del_req->hw_key_idx = hw_key_idx; + + /* Send the MM_KEY_DEL_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, key_del_req, 1, MM_KEY_DEL_CFM, NULL); +} + +int ecrnx_send_bcn_change(struct ecrnx_hw *ecrnx_hw, u8 vif_idx, dma_addr_t bcn_addr, + u16 bcn_len, u16 tim_oft, u16 tim_len, u16 *csa_oft) +{ + struct mm_bcn_change_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_BCN_CHANGE_REQ message */ + req = ecrnx_msg_zalloc(MM_BCN_CHANGE_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_bcn_change_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the MM_BCN_CHANGE_REQ message */ + req->bcn_ptr = bcn_addr; + req->bcn_len = bcn_len; + req->tim_oft = tim_oft; + req->tim_len = tim_len; + req->inst_nbr = vif_idx; + +#if defined(CONFIG_ECRNX_SOFTMAC) + BUILD_BUG_ON_MSG(IEEE80211_MAX_CSA_COUNTERS_NUM != BCN_MAX_CSA_CPT, + "BCN_MAX_CSA_CPT and IEEE80211_MAX_CSA_COUNTERS_NUM " + "have different value"); +#endif /* CONFIG_ECRNX_SOFTMAC */ + if (csa_oft) { + int i; + for (i = 0; i < BCN_MAX_CSA_CPT; i++) { + req->csa_oft[i] = csa_oft[i]; + } + } + + /* Send the MM_BCN_CHANGE_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MM_BCN_CHANGE_CFM, NULL); +} + +int ecrnx_send_roc(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + struct ieee80211_channel *chan, unsigned int duration) +{ + struct mm_remain_on_channel_req *req; + struct cfg80211_chan_def chandef; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Create channel definition structure */ + cfg80211_chandef_create(&chandef, chan, NL80211_CHAN_NO_HT); + + /* Build the MM_REMAIN_ON_CHANNEL_REQ message */ + req = ecrnx_msg_zalloc(MM_REMAIN_ON_CHANNEL_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_remain_on_channel_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the MM_REMAIN_ON_CHANNEL_REQ message */ + req->op_code = MM_ROC_OP_START; + req->vif_index = vif->vif_index; + req->duration_ms = duration; + cfg80211_to_ecrnx_chan(&chandef, &req->chan); + + /* Send the MM_REMAIN_ON_CHANNEL_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MM_REMAIN_ON_CHANNEL_CFM, NULL); +} + +int ecrnx_send_cancel_roc(struct ecrnx_hw *ecrnx_hw) +{ + struct mm_remain_on_channel_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_REMAIN_ON_CHANNEL_REQ message */ + req = ecrnx_msg_zalloc(MM_REMAIN_ON_CHANNEL_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_remain_on_channel_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the MM_REMAIN_ON_CHANNEL_REQ message */ + req->op_code = MM_ROC_OP_CANCEL; + + /* Send the MM_REMAIN_ON_CHANNEL_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MM_REMAIN_ON_CHANNEL_CFM, NULL); +} + +int ecrnx_send_set_power(struct ecrnx_hw *ecrnx_hw, u8 vif_idx, s8 pwr, + struct mm_set_power_cfm *cfm) +{ + struct mm_set_power_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_SET_POWER_REQ message */ + req = ecrnx_msg_zalloc(MM_SET_POWER_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_set_power_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the MM_SET_POWER_REQ message */ + req->inst_nbr = vif_idx; + req->power = pwr; + + /* Send the MM_SET_POWER_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MM_SET_POWER_CFM, cfm); +} + +int ecrnx_send_set_edca(struct ecrnx_hw *ecrnx_hw, u8 hw_queue, u32 param, + bool uapsd, u8 inst_nbr) +{ + struct mm_set_edca_req *set_edca_req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_SET_EDCA_REQ message */ + set_edca_req = ecrnx_msg_zalloc(MM_SET_EDCA_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_set_edca_req)); + if (!set_edca_req) + return -ENOMEM; + + /* Set parameters for the MM_SET_EDCA_REQ message */ + set_edca_req->ac_param = param; + set_edca_req->uapsd = uapsd; + set_edca_req->hw_queue = hw_queue; + set_edca_req->inst_nbr = inst_nbr; + + /* Send the MM_SET_EDCA_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, set_edca_req, 1, MM_SET_EDCA_CFM, NULL); +} + +#ifdef CONFIG_ECRNX_P2P_DEBUGFS +int ecrnx_send_p2p_oppps_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + u8 ctw, struct mm_set_p2p_oppps_cfm *cfm) +{ + struct mm_set_p2p_oppps_req *p2p_oppps_req; + int error; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_SET_P2P_OPPPS_REQ message */ + p2p_oppps_req = ecrnx_msg_zalloc(MM_SET_P2P_OPPPS_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_set_p2p_oppps_req)); + + if (!p2p_oppps_req) { + return -ENOMEM; + } + + /* Fill the message parameters */ + p2p_oppps_req->vif_index = ecrnx_vif->vif_index; + p2p_oppps_req->ctwindow = ctw; + + /* Send the MM_P2P_OPPPS_REQ message to LMAC FW */ + error = ecrnx_send_msg(ecrnx_hw, p2p_oppps_req, 1, MM_SET_P2P_OPPPS_CFM, cfm); + + return (error); +} + +int ecrnx_send_p2p_noa_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + int count, int interval, int duration, bool dyn_noa, + struct mm_set_p2p_noa_cfm *cfm) +{ + struct mm_set_p2p_noa_req *p2p_noa_req; + int error; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Param check */ + if (count > 255) + count = 255; + + if (duration >= interval) { + dev_err(ecrnx_hw->dev, "Invalid p2p NOA config: interval=%d <= duration=%d\n", + interval, duration); + return -EINVAL; + } + + /* Build the MM_SET_P2P_NOA_REQ message */ + p2p_noa_req = ecrnx_msg_zalloc(MM_SET_P2P_NOA_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_set_p2p_noa_req)); + + if (!p2p_noa_req) { + return -ENOMEM; + } + + /* Fill the message parameters */ + p2p_noa_req->vif_index = ecrnx_vif->vif_index; + p2p_noa_req->noa_inst_nb = 0; + p2p_noa_req->count = count; + + if (count) { + p2p_noa_req->duration_us = duration * 1024; + p2p_noa_req->interval_us = interval * 1024; + p2p_noa_req->start_offset = (interval - duration - 10) * 1024; + p2p_noa_req->dyn_noa = dyn_noa; + } + + /* Send the MM_SET_2P_NOA_REQ message to LMAC FW */ + error = ecrnx_send_msg(ecrnx_hw, p2p_noa_req, 1, MM_SET_P2P_NOA_CFM, cfm); + + return (error); +} +#endif /* CONFIG_ECRNX_P2P_DEBUGFS */ + +/****************************************************************************** + * Control messages handling functions (SOFTMAC only) + *****************************************************************************/ +#ifdef CONFIG_ECRNX_SOFTMAC +int ecrnx_send_sta_add(struct ecrnx_hw *ecrnx_hw, struct ieee80211_sta *sta, + u8 inst_nbr, struct mm_sta_add_cfm *cfm) +{ + struct mm_sta_add_req *sta_add_req; + union ecrnx_thd_phy_ctrl_info *phy; + struct ecrnx_sta *ecrnx_sta; + int error; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_STA_ADD_REQ message */ + sta_add_req = ecrnx_msg_zalloc(MM_STA_ADD_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_sta_add_req)); + if (!sta_add_req) + return -ENOMEM; + + /* Set parameters for the MM_STA_ADD_REQ message */ + memcpy(&(sta_add_req->mac_addr.array[0]), &(sta->addr[0]), ETH_ALEN); + + if (sta->wme) + sta_add_req->capa_flags |= STA_QOS_CAPA; + /* TODO: check if a density of 0 microseconds is OK or not for LMAC */ + if (sta->ht_cap.ht_supported) { + int ht_exp = sta->ht_cap.ampdu_factor; + int vht_exp = 0; + + sta_add_req->capa_flags |= STA_HT_CAPA; + sta_add_req->ampdu_size_max_ht = + (1 << (IEEE80211_HT_MAX_AMPDU_FACTOR + ht_exp)) - 1; + sta_add_req->ampdu_spacing_min = + ecrnx_ampdudensity2usec(sta->ht_cap.ampdu_density); + + if (sta->vht_cap.vht_supported) { + sta_add_req->capa_flags |= STA_VHT_CAPA; + vht_exp = (sta->vht_cap.cap & + IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK) >> + IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT; + sta_add_req->ampdu_size_max_vht = + (1 << (IEEE80211_HT_MAX_AMPDU_FACTOR + vht_exp)) - 1; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0) && defined(CONFIG_ECRNX_HE) + if (sta->he_cap.has_he) { + int he_exp_ext = (sta->he_cap.he_cap_elem.mac_cap_info[3] & + IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_MASK) >> 3; + + sta_add_req->capa_flags |= STA_HE_CAPA; + if (sta->vht_cap.vht_supported) { + if ((vht_exp == 7) && he_exp_ext) + sta_add_req->ampdu_size_max_he = + (1 << (IEEE80211_HT_MAX_AMPDU_FACTOR + vht_exp + he_exp_ext)) - 1; + else + sta_add_req->ampdu_size_max_he = sta_add_req->ampdu_size_max_vht; + } else { + if ((ht_exp == 3) && he_exp_ext) + sta_add_req->ampdu_size_max_he = + (1 << (IEEE80211_HT_MAX_AMPDU_FACTOR + ht_exp + he_exp_ext)) - 1; + else + sta_add_req->ampdu_size_max_he = sta_add_req->ampdu_size_max_ht; + } + } +#endif + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) + if (sta->mfp) + sta_add_req->capa_flags |= STA_MFP_CAPA; +#endif + + ecrnx_sta = (struct ecrnx_sta *)sta->drv_priv; + /* TODO Set the interface index from the vif structure */ + sta_add_req->inst_nbr = inst_nbr; + sta_add_req->tdls_sta = sta->tdls; + sta_add_req->tdls_sta_initiator = STA_TDLS_INITIATOR(sta); + sta_add_req->bssid_index = 0; + sta_add_req->max_bssid_ind = 0; + phy = (union ecrnx_thd_phy_ctrl_info *)&sta_add_req->paid_gid; + phy->partialAIDTx = ecrnx_sta->paid; + phy->groupIDTx = ecrnx_sta->gid; + + /* Send the MM_STA_ADD_REQ message to LMAC FW */ + error = ecrnx_send_msg(ecrnx_hw, sta_add_req, 1, MM_STA_ADD_CFM, cfm); + + return (error); +} + +int ecrnx_send_sta_del(struct ecrnx_hw *ecrnx_hw, u8 sta_idx) +{ + struct mm_sta_del_req *sta_del_req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_STA_DEL_REQ message */ + sta_del_req = ecrnx_msg_zalloc(MM_STA_DEL_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_sta_del_req)); + if (!sta_del_req) + return -ENOMEM; + + /* Set parameters for the MM_STA_DEL_REQ message */ + sta_del_req->sta_idx = sta_idx; + + /* Send the MM_STA_DEL_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, sta_del_req, 1, MM_STA_DEL_CFM, NULL); +} + +int ecrnx_send_set_filter(struct ecrnx_hw *ecrnx_hw, uint32_t filter) +{ + struct mm_set_filter_req *set_filter_req_param; + uint32_t rx_filter = 0; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_SET_FILTER_REQ message */ + set_filter_req_param = + ecrnx_msg_zalloc(MM_SET_FILTER_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_set_filter_req)); + if (!set_filter_req_param) + return -ENOMEM; + + /* Set parameters for the MM_SET_FILTER_REQ message */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0) + if (filter & FIF_PROMISC_IN_BSS) + rx_filter |= NXMAC_ACCEPT_UNICAST_BIT; +#endif + if (filter & FIF_ALLMULTI) + rx_filter |= NXMAC_ACCEPT_MULTICAST_BIT; + + if (filter & (FIF_FCSFAIL | FIF_PLCPFAIL)) + rx_filter |= NXMAC_ACCEPT_ERROR_FRAMES_BIT; + + if (filter & FIF_BCN_PRBRESP_PROMISC) + rx_filter |= NXMAC_ACCEPT_OTHER_BSSID_BIT; + + if (filter & FIF_CONTROL) + rx_filter |= NXMAC_ACCEPT_OTHER_CNTRL_FRAMES_BIT | + NXMAC_ACCEPT_CF_END_BIT | + NXMAC_ACCEPT_ACK_BIT | + NXMAC_ACCEPT_CTS_BIT | + NXMAC_ACCEPT_RTS_BIT | + NXMAC_ACCEPT_BA_BIT | NXMAC_ACCEPT_BAR_BIT; + + if (filter & FIF_OTHER_BSS) + rx_filter |= NXMAC_ACCEPT_OTHER_BSSID_BIT; + + if (filter & FIF_PSPOLL) { + /* TODO: check if the MAC filters apply to our BSSID or is general */ + rx_filter |= NXMAC_ACCEPT_PS_POLL_BIT; + } + + if (filter & FIF_PROBE_REQ) { + rx_filter |= NXMAC_ACCEPT_PROBE_REQ_BIT; + rx_filter |= NXMAC_ACCEPT_ALL_BEACON_BIT; + } + + /* Add the filter flags that are set by default and cannot be changed here */ + rx_filter |= ECRNX_MAC80211_NOT_CHANGEABLE; + + /* XXX */ + if (ieee80211_hw_check(ecrnx_hw->hw, AMPDU_AGGREGATION)) + rx_filter |= NXMAC_ACCEPT_BA_BIT; + + /* Now copy all the flags into the message parameter */ + set_filter_req_param->filter = rx_filter; + + ECRNX_DBG("new total_flags = 0x%08x\nrx filter set to 0x%08x\n", + filter, rx_filter); + + /* Send the MM_SET_FILTER_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, set_filter_req_param, 1, MM_SET_FILTER_CFM, NULL); +} + + +int ecrnx_send_add_chanctx(struct ecrnx_hw *ecrnx_hw, + struct ieee80211_chanctx_conf *ctx, + struct mm_chan_ctxt_add_cfm *cfm) +{ + struct mm_chan_ctxt_add_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_CHAN_CTXT_ADD_REQ message */ + req = ecrnx_msg_zalloc(MM_CHAN_CTXT_ADD_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_chan_ctxt_add_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the CHAN_CTXT_ADD_REQ message */ + cfg80211_to_ecrnx_chan(&ctx->def, &req->chan); + + if (ecrnx_hw->phy.limit_bw) + limit_chan_bw(&req->chan.type, req->chan.prim20_freq, + &req->chan.center1_freq); + + ECRNX_DBG("mac80211: freq=%d(c1:%d - c2:%d)/width=%d - band=%d\n" + " prim20=%d(c1:%d - c2:%d)/ type=%d - band=%d\n", + ctx->def.chan->center_freq, ctx->def.center_freq1, + ctx->def.center_freq2, ctx->def.width, ctx->def.chan->band, + req->chan.prim20_freq, req->chan.center1_freq, + req->chan.center2_freq, req->chan.type, req->chan.band); + /* Send the CHAN_CTXT_ADD_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MM_CHAN_CTXT_ADD_CFM, cfm); +} + +int ecrnx_send_del_chanctx(struct ecrnx_hw *ecrnx_hw, u8 index) +{ + struct mm_chan_ctxt_del_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_CHAN_CTXT_DEL_REQ message */ + req = ecrnx_msg_zalloc(MM_CHAN_CTXT_DEL_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_chan_ctxt_del_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the MM_REMOVE_IF_REQ message */ + req->index = index; + + /* Send the MM_CHAN_CTXT_DEL_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MM_CHAN_CTXT_DEL_CFM, NULL); +} + +int ecrnx_send_link_chanctx(struct ecrnx_hw *ecrnx_hw, u8 vif_idx, u8 chan_idx, + u8 chan_switch) +{ + struct mm_chan_ctxt_link_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_CHAN_CTXT_LINK_REQ message */ + req = ecrnx_msg_zalloc(MM_CHAN_CTXT_LINK_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_chan_ctxt_link_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the MM_CHAN_CTXT_LINK_REQ message */ + req->vif_index = vif_idx; + req->chan_index = chan_idx; + req->chan_switch = chan_switch; + + /* Send the MM_CHAN_CTXT_LINK_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MM_CHAN_CTXT_LINK_CFM, NULL); +} + +int ecrnx_send_unlink_chanctx(struct ecrnx_hw *ecrnx_hw, u8 vif_idx) +{ + struct mm_chan_ctxt_unlink_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_CHAN_CTXT_UNLINK_REQ message */ + req = ecrnx_msg_zalloc(MM_CHAN_CTXT_UNLINK_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_chan_ctxt_unlink_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the MM_CHAN_CTXT_UNLINK_REQ message */ + req->vif_index = vif_idx; + + /* Send the MM_CHAN_CTXT_UNLINK_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MM_CHAN_CTXT_UNLINK_CFM, NULL); +} + +int ecrnx_send_update_chanctx(struct ecrnx_hw *ecrnx_hw, + struct ieee80211_chanctx_conf *ctx) +{ + struct mm_chan_ctxt_update_req *req; + struct ecrnx_chanctx *ecrnx_chanctx; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_CHAN_CTXT_UPDATE_REQ message */ + req = ecrnx_msg_zalloc(MM_CHAN_CTXT_UPDATE_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_chan_ctxt_update_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the MM_CHAN_CTXT_UPDATE_REQ message */ + ecrnx_chanctx = (struct ecrnx_chanctx *)ctx->drv_priv; + req->chan_index = ecrnx_chanctx->index; + cfg80211_to_ecrnx_chan(&ctx->def, &req->chan); + + if (ecrnx_hw->phy.limit_bw) + limit_chan_bw(&req->chan.type, req->chan.prim20_freq, &req->chan.center1_freq); + + ECRNX_DBG("mac80211: freq=%d(c1:%d - c2:%d)/width=%d - band=%d\n" + " prim20=%d(c1:%d - c2:%d)/ type=%d - band=%d\n", + ctx->def.chan->center_freq, ctx->def.center_freq1, + ctx->def.center_freq2, ctx->def.width, ctx->def.chan->band, + req->chan.prim20_freq, req->chan.center1_freq, + req->chan.center2_freq, req->chan.type, req->chan.band); + /* Send the MM_CHAN_CTXT_UPDATE_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MM_CHAN_CTXT_UPDATE_CFM, NULL); +} + +int ecrnx_send_sched_chanctx(struct ecrnx_hw *ecrnx_hw, u8 vif_idx, u8 chan_idx, u8 type) +{ + struct mm_chan_ctxt_sched_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_CHAN_CTXT_SCHED_REQ message */ + req = ecrnx_msg_zalloc(MM_CHAN_CTXT_SCHED_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_chan_ctxt_sched_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the MM_CHAN_CTXT_SCHED_REQ message */ + req->vif_index = vif_idx; + req->chan_index = chan_idx; + req->type = type; + + /* Send the MM_CHAN_CTXT_SCHED_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MM_CHAN_CTXT_SCHED_CFM, NULL); +} + +int ecrnx_send_dtim_req(struct ecrnx_hw *ecrnx_hw, u8 dtim_period) +{ + struct mm_set_dtim_req *set_dtim_req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_SET_DTIM_REQ message */ + set_dtim_req = ecrnx_msg_zalloc(MM_SET_DTIM_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_set_dtim_req)); + if (!set_dtim_req) + return -ENOMEM; + + /* Set parameters for the MM_SET_DTIM_REQ message */ + set_dtim_req->dtim_period = dtim_period; + + /* Send the MM_SET_DTIM_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, set_dtim_req, 1, MM_SET_DTIM_CFM, NULL); +} + +int ecrnx_send_set_br(struct ecrnx_hw *ecrnx_hw, u32 basic_rates, u8 vif_idx, u8 band) +{ + struct mm_set_basic_rates_req *set_basic_rates_req_param; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_SET_BASIC_RATES_REQ message */ + set_basic_rates_req_param = + ecrnx_msg_zalloc(MM_SET_BASIC_RATES_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_set_basic_rates_req)); + if (!set_basic_rates_req_param) + return -ENOMEM; + + /* Set parameters for the MM_SET_BASIC_RATES_REQ message */ + set_basic_rates_req_param->rates = basic_rates; + set_basic_rates_req_param->inst_nbr = vif_idx; + set_basic_rates_req_param->band = band; + + /* Send the MM_SET_BASIC_RATES_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, set_basic_rates_req_param, 1, MM_SET_BASIC_RATES_CFM, NULL); +} + +int ecrnx_send_set_beacon_int(struct ecrnx_hw *ecrnx_hw, u16 beacon_int, u8 vif_idx) +{ + struct mm_set_beacon_int_req *set_beacon_int_req_param; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_SET_BEACON_INT_REQ message */ + set_beacon_int_req_param = + ecrnx_msg_zalloc(MM_SET_BEACON_INT_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_set_beacon_int_req)); + if (!set_beacon_int_req_param) + return -ENOMEM; + + /* Set parameters for the MM_SET_BEACON_INT_REQ message */ + set_beacon_int_req_param->beacon_int = beacon_int; + set_beacon_int_req_param->inst_nbr = vif_idx; + + /* Send the MM_SET_BEACON_INT_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, set_beacon_int_req_param, 1, + MM_SET_BEACON_INT_CFM, NULL); +} + +int ecrnx_send_set_bssid(struct ecrnx_hw *ecrnx_hw, const u8 *bssid, u8 vif_idx) +{ + struct mm_set_bssid_req *set_bssid_req_param; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_SET_BSSID_REQ message */ + set_bssid_req_param = ecrnx_msg_zalloc(MM_SET_BSSID_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_set_bssid_req)); + if (!set_bssid_req_param) + return -ENOMEM; + + /* Set parameters for the MM_SET_BSSID_REQ message */ + memcpy(&(set_bssid_req_param->bssid.array[0]), bssid, ETH_ALEN); + set_bssid_req_param->inst_nbr = vif_idx; + + /* Send the MM_SET_BSSID_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, set_bssid_req_param, 1, MM_SET_BSSID_CFM, NULL); +} + +int ecrnx_send_set_vif_state(struct ecrnx_hw *ecrnx_hw, bool active, + u16 aid, u8 vif_idx) +{ + struct mm_set_vif_state_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_SET_VIF_STATE_REQ message */ + req = ecrnx_msg_zalloc(MM_SET_VIF_STATE_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_set_vif_state_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the MM_SET_VIF_STATE_REQ message */ + req->active = active; + req->aid = aid; + req->inst_nbr = vif_idx; + + /* Send the MM_SET_VIF_STATE_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MM_SET_VIF_STATE_CFM, NULL); +} + +int ecrnx_send_set_mode(struct ecrnx_hw *ecrnx_hw, u8 abgnmode) +{ + struct mm_set_mode_req *set_mode_req_param; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_SET_MODE_REQ message */ + set_mode_req_param = ecrnx_msg_zalloc(MM_SET_MODE_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_set_mode_req)); + if (!set_mode_req_param) + return -ENOMEM; + + set_mode_req_param->abgnmode = abgnmode; + + /* Send the MM_SET_MODE_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, set_mode_req_param, 1, MM_SET_MODE_CFM, NULL); +} + +int ecrnx_send_set_idle(struct ecrnx_hw *ecrnx_hw, int idle) +{ + struct mm_set_idle_req *set_idle_req_param; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + set_idle_req_param = ecrnx_msg_zalloc(MM_SET_IDLE_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_set_idle_req)); + if (!set_idle_req_param) + return -ENOMEM; + + set_idle_req_param->hw_idle = idle; + + return ecrnx_send_msg(ecrnx_hw, set_idle_req_param, 1, MM_SET_IDLE_CFM, NULL); +} + +int ecrnx_send_set_ps_mode(struct ecrnx_hw *ecrnx_hw, u8 ps_mode) +{ + struct mm_set_ps_mode_req *set_ps_mode_req_param; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + set_ps_mode_req_param = + ecrnx_msg_zalloc(MM_SET_PS_MODE_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_set_ps_mode_req)); + if (!set_ps_mode_req_param) + return -ENOMEM; + + set_ps_mode_req_param->new_state = ps_mode; + + return ecrnx_send_msg(ecrnx_hw, set_ps_mode_req_param, 1, MM_SET_PS_MODE_CFM, NULL); +} + +int ecrnx_send_set_ps_options(struct ecrnx_hw *ecrnx_hw, bool listen_bcmc, + u16 listen_interval, u8 vif_idx) +{ + struct mm_set_ps_options_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_SET_PS_OPTIONS_REQ message */ + req = ecrnx_msg_zalloc(MM_SET_PS_OPTIONS_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_set_ps_options_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the MM_SET_VIF_STATE_REQ message */ + req->listen_interval = listen_interval; + req->dont_listen_bc_mc = !listen_bcmc; + req->vif_index = vif_idx; + + /* Send the MM_SET_PS_OPTIONS_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MM_SET_PS_OPTIONS_CFM, NULL); +} + +int ecrnx_send_set_slottime(struct ecrnx_hw *ecrnx_hw, int use_short_slot) +{ + struct mm_set_slottime_req *set_slottime_req_param; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_SET_SLOTTIME_REQ message */ + set_slottime_req_param = + ecrnx_msg_zalloc(MM_SET_SLOTTIME_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_set_slottime_req)); + if (!set_slottime_req_param) + return -ENOMEM; + + /* Set parameters for the MM_SET_SLOTTIME_REQ message */ + set_slottime_req_param->slottime = use_short_slot ? 9 : 20; + + /* Send the MM_SET_SLOTTIME_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, set_slottime_req_param, 1, MM_SET_SLOTTIME_CFM, NULL); +} + +int ecrnx_send_ba_add(struct ecrnx_hw *ecrnx_hw, uint8_t type, uint8_t sta_idx, + u16 tid, uint8_t bufsz, uint16_t ssn, + struct mm_ba_add_cfm *cfm) +{ + struct mm_ba_add_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_BA_ADD_REQ message */ + req = ecrnx_msg_zalloc(MM_BA_ADD_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_ba_add_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the MM_BA_ADD_REQ message */ + req->type = type; + req->sta_idx = sta_idx; + req->tid = (u8_l)tid; + req->bufsz = bufsz; + req->ssn = ssn; + + /* Send the MM_BA_ADD_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MM_BA_ADD_CFM, cfm); +} + +int ecrnx_send_ba_del(struct ecrnx_hw *ecrnx_hw, uint8_t sta_idx, u16 tid, + struct mm_ba_del_cfm *cfm) +{ + struct mm_ba_del_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_BA_DEL_REQ message */ + req = ecrnx_msg_zalloc(MM_BA_DEL_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_ba_del_req)); + if (!req) + return -ENOMEM; + + /* Set parameters */ + req->type = 0; + req->sta_idx = sta_idx; + req->tid = (u8_l)tid; + + /* Send the MM_BA_DEL_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MM_BA_DEL_CFM, cfm); +} + +int ecrnx_send_scan_req(struct ecrnx_hw *ecrnx_hw, struct ieee80211_vif *vif, + struct cfg80211_scan_request *param, + struct scan_start_cfm *cfm) +{ + struct scan_start_req *req; + int i; + struct ecrnx_vif *ecrnx_vif; + uint8_t chan_flags = 0; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + ecrnx_vif = (struct ecrnx_vif *)vif->drv_priv; + + /* Build the SCAN_START_REQ message */ + req = ecrnx_msg_zalloc(SCAN_START_REQ, TASK_SCAN, DRV_TASK_ID, + sizeof(struct scan_start_req)); + if (!req) + return -ENOMEM; + + /* Set parameters */ + req->vif_idx = ecrnx_vif->vif_index; + req->chan_cnt = (u8)min_t(int, SCAN_CHANNEL_MAX, param->n_channels); + req->ssid_cnt = (u8)min_t(int, SCAN_SSID_MAX, param->n_ssids); + req->bssid = mac_addr_bcst; + req->no_cck = param->no_cck; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + if (param->duration_mandatory) + req->duration = ieee80211_tu_to_usec(param->duration); +#endif + + if (req->ssid_cnt == 0) + chan_flags |= CHAN_NO_IR; + for (i = 0; i < req->ssid_cnt; i++) { + int j; + for (j = 0; j < param->ssids[i].ssid_len; j++) + req->ssid[i].array[j] = param->ssids[i].ssid[j]; + req->ssid[i].length = param->ssids[i].ssid_len; + } + + if (param->ie) { + if (ecrnx_ipc_elem_var_allocs(ecrnx_hw, &ecrnx_hw->scan_ie, + param->ie_len, DMA_TO_DEVICE, + NULL, param->ie, NULL)) + goto error; + + req->add_ie_len = param->ie_len; + req->add_ies = ecrnx_hw->scan_ie.dma_addr; + } else { + req->add_ie_len = 0; + req->add_ies = 0; + } + + for (i = 0; i < req->chan_cnt; i++) { + struct ieee80211_channel *chan = param->channels[i]; + + req->chan[i].band = chan->band; + req->chan[i].freq = chan->center_freq; + req->chan[i].flags = chan_flags | get_chan_flags(chan->flags); + req->chan[i].tx_power = chan_to_fw_pwr(chan->max_reg_power); + } + + /* Send the SCAN_START_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, SCAN_START_CFM, cfm); +error: + if (req != NULL) + ecrnx_msg_free(ecrnx_hw, req); + return -ENOMEM; +} + +int ecrnx_send_scan_cancel_req(struct ecrnx_hw *ecrnx_hw, + struct scan_cancel_cfm *cfm) +{ + struct scan_cancel_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the SCAN_CANCEL_REQ message */ + req = ecrnx_msg_zalloc(SCAN_CANCEL_REQ, TASK_SCAN, DRV_TASK_ID, + sizeof(struct scan_cancel_req)); + if (!req) + return -ENOMEM; + + /* Send the SCAN_CANCEL_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, SCAN_CANCEL_CFM, cfm); +} + +void ecrnx_send_tdls_ps(struct ecrnx_hw *ecrnx_hw, bool ps_mode) +{ + if (!ecrnx_hw->mod_params->ps_on) + return; + + ecrnx_send_set_ps_mode(ecrnx_hw, ps_mode); +} + +int ecrnx_send_tim_update(struct ecrnx_hw *ecrnx_hw, u8 vif_idx, u16 aid, + u8 tx_status) +{ + struct mm_tim_update_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_TIM_UPDATE_REQ message */ + req = ecrnx_msg_zalloc(MM_TIM_UPDATE_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_tim_update_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the MM_TIM_UPDATE_REQ message */ + req->aid = aid; + req->tx_avail = tx_status; + req->inst_nbr = vif_idx; + + /* Send the MM_TIM_UPDATE_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MM_TIM_UPDATE_CFM, NULL); +} +#endif /* CONFIG_ECRNX_SOFTMAC */ + +/****************************************************************************** + * Control messages handling functions (FULLMAC only) + *****************************************************************************/ +#ifdef CONFIG_ECRNX_FULLMAC + +int ecrnx_send_me_config_req(struct ecrnx_hw *ecrnx_hw) +{ + struct me_config_req *req; + struct wiphy *wiphy = ecrnx_hw->wiphy; +#ifdef CONFIG_ECRNX_5G + struct ieee80211_sta_ht_cap *ht_cap = &wiphy->bands[NL80211_BAND_5GHZ]->ht_cap; + struct ieee80211_sta_vht_cap *vht_cap = &wiphy->bands[NL80211_BAND_5GHZ]->vht_cap; +#else + struct ieee80211_sta_ht_cap *ht_cap = &wiphy->bands[NL80211_BAND_2GHZ]->ht_cap; + struct ieee80211_sta_vht_cap *vht_cap = &wiphy->bands[NL80211_BAND_2GHZ]->vht_cap; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) && defined(CONFIG_ECRNX_HE) + struct ieee80211_sta_he_cap const *he_cap; +#endif + uint8_t *ht_mcs = (uint8_t *)&ht_cap->mcs; + int i; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the ME_CONFIG_REQ message */ + req = ecrnx_msg_zalloc(ME_CONFIG_REQ, TASK_ME, DRV_TASK_ID, + sizeof(struct me_config_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the ME_CONFIG_REQ message */ + req->ht_supp = ht_cap->ht_supported; + req->vht_supp = vht_cap->vht_supported; + req->ht_cap.ht_capa_info = cpu_to_le16(ht_cap->cap); + req->ht_cap.a_mpdu_param = ht_cap->ampdu_factor | + (ht_cap->ampdu_density << + IEEE80211_HT_AMPDU_PARM_DENSITY_SHIFT); + for (i = 0; i < sizeof(ht_cap->mcs); i++) + req->ht_cap.mcs_rate[i] = ht_mcs[i]; + req->ht_cap.ht_extended_capa = 0; + req->ht_cap.tx_beamforming_capa = 0; + req->ht_cap.asel_capa = 0; + + req->vht_cap.vht_capa_info = cpu_to_le32(vht_cap->cap); + req->vht_cap.rx_highest = cpu_to_le16(vht_cap->vht_mcs.rx_highest); + req->vht_cap.rx_mcs_map = cpu_to_le16(vht_cap->vht_mcs.rx_mcs_map); + req->vht_cap.tx_highest = cpu_to_le16(vht_cap->vht_mcs.tx_highest); + req->vht_cap.tx_mcs_map = cpu_to_le16(vht_cap->vht_mcs.tx_mcs_map); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) && defined(CONFIG_ECRNX_HE) +#ifdef CONFIG_ECRNX_5G + if (wiphy->bands[NL80211_BAND_5GHZ]->iftype_data != NULL) { + he_cap = &wiphy->bands[NL80211_BAND_5GHZ]->iftype_data->he_cap; +#else + if (wiphy->bands[NL80211_BAND_2GHZ]->iftype_data != NULL) { + he_cap = &wiphy->bands[NL80211_BAND_2GHZ]->iftype_data->he_cap; +#endif + + req->he_supp = he_cap->has_he; + for (i = 0; i < ARRAY_SIZE(he_cap->he_cap_elem.mac_cap_info); i++) { + req->he_cap.mac_cap_info[i] = he_cap->he_cap_elem.mac_cap_info[i]; + } + for (i = 0; i < ARRAY_SIZE(he_cap->he_cap_elem.phy_cap_info); i++) { + req->he_cap.phy_cap_info[i] = he_cap->he_cap_elem.phy_cap_info[i]; + } + req->he_cap.mcs_supp.rx_mcs_80 = cpu_to_le16(he_cap->he_mcs_nss_supp.rx_mcs_80); + req->he_cap.mcs_supp.tx_mcs_80 = cpu_to_le16(he_cap->he_mcs_nss_supp.tx_mcs_80); + req->he_cap.mcs_supp.rx_mcs_160 = cpu_to_le16(he_cap->he_mcs_nss_supp.rx_mcs_160); + req->he_cap.mcs_supp.tx_mcs_160 = cpu_to_le16(he_cap->he_mcs_nss_supp.tx_mcs_160); + req->he_cap.mcs_supp.rx_mcs_80p80 = cpu_to_le16(he_cap->he_mcs_nss_supp.rx_mcs_80p80); + req->he_cap.mcs_supp.tx_mcs_80p80 = cpu_to_le16(he_cap->he_mcs_nss_supp.tx_mcs_80p80); + for (i = 0; i < MAC_HE_PPE_THRES_MAX_LEN; i++) { + req->he_cap.ppe_thres[i] = he_cap->ppe_thres[i]; + } + req->he_ul_on = ecrnx_hw->mod_params->he_ul_on; + } +#else + req->he_ul_on = false; + + req->he_supp = ecrnx_he_cap.has_he; + for (i = 0; i < ARRAY_SIZE(ecrnx_he_cap.he_cap_elem.mac_cap_info); i++) { + req->he_cap.mac_cap_info[i] = ecrnx_he_cap.he_cap_elem.mac_cap_info[i]; + } + for (i = 0; i < ARRAY_SIZE(ecrnx_he_cap.he_cap_elem.phy_cap_info); i++) { + req->he_cap.phy_cap_info[i] = ecrnx_he_cap.he_cap_elem.phy_cap_info[i]; + } + req->he_cap.mcs_supp.rx_mcs_80 = cpu_to_le16(ecrnx_he_cap.he_mcs_nss_supp.rx_mcs_80); + req->he_cap.mcs_supp.tx_mcs_80 = cpu_to_le16(ecrnx_he_cap.he_mcs_nss_supp.tx_mcs_80); + req->he_cap.mcs_supp.rx_mcs_160 = cpu_to_le16(ecrnx_he_cap.he_mcs_nss_supp.rx_mcs_160); + req->he_cap.mcs_supp.tx_mcs_160 = cpu_to_le16(ecrnx_he_cap.he_mcs_nss_supp.tx_mcs_160); + req->he_cap.mcs_supp.rx_mcs_80p80 = cpu_to_le16(ecrnx_he_cap.he_mcs_nss_supp.rx_mcs_80p80); + req->he_cap.mcs_supp.tx_mcs_80p80 = cpu_to_le16(ecrnx_he_cap.he_mcs_nss_supp.tx_mcs_80p80); + for (i = 0; i < MAC_HE_PPE_THRES_MAX_LEN; i++) { + req->he_cap.ppe_thres[i] = ecrnx_he_cap.ppe_thres[i]; + } +#endif + + req->ps_on = ecrnx_hw->mod_params->ps_on; + req->dpsm = ecrnx_hw->mod_params->dpsm; + /** + * set sleep_flag for sdio slave. + * bit0: MODEM_SLEEP + * bit1: WFI_SLEEP + * bit2: LIGHT_SLEEP + * bit3: DEEP_SLEEP + */ + req->sleep_flag = 0x5; + req->tx_lft = ecrnx_hw->mod_params->tx_lft; + req->ant_div_on = ecrnx_hw->mod_params->ant_div; + if (ecrnx_hw->mod_params->use_80) + req->phy_bw_max = PHY_CHNL_BW_80; + else if (ecrnx_hw->mod_params->use_2040) + req->phy_bw_max = PHY_CHNL_BW_40; + else + req->phy_bw_max = PHY_CHNL_BW_20; + + wiphy_info(wiphy, "HT supp %d, VHT supp %d, HE supp %d\n", req->ht_supp, + req->vht_supp, + req->he_supp); + + /* Send the ME_CONFIG_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, ME_CONFIG_CFM, NULL); +} + +int ecrnx_send_me_chan_config_req(struct ecrnx_hw *ecrnx_hw) +{ + struct me_chan_config_req *req; + struct wiphy *wiphy = ecrnx_hw->wiphy; + int i; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the ME_CHAN_CONFIG_REQ message */ + req = ecrnx_msg_zalloc(ME_CHAN_CONFIG_REQ, TASK_ME, DRV_TASK_ID, + sizeof(struct me_chan_config_req)); + if (!req) + return -ENOMEM; + + req->chan2G4_cnt= 0; + if (wiphy->bands[NL80211_BAND_2GHZ] != NULL) { + struct ieee80211_supported_band *b = wiphy->bands[NL80211_BAND_2GHZ]; + for (i = 0; i < b->n_channels; i++) { + req->chan2G4[req->chan2G4_cnt].flags = 0; + if (b->channels[i].flags & IEEE80211_CHAN_DISABLED) + req->chan2G4[req->chan2G4_cnt].flags |= CHAN_DISABLED; + req->chan2G4[req->chan2G4_cnt].flags |= get_chan_flags(b->channels[i].flags); + req->chan2G4[req->chan2G4_cnt].band = NL80211_BAND_2GHZ; + req->chan2G4[req->chan2G4_cnt].freq = b->channels[i].center_freq; + req->chan2G4[req->chan2G4_cnt].tx_power = chan_to_fw_pwr(b->channels[i].max_power); + req->chan2G4_cnt++; + if (req->chan2G4_cnt == MAC_DOMAINCHANNEL_24G_MAX) + break; + } + } + + req->chan5G_cnt = 0; +#ifdef CONFIG_ECRNX_5G + if (wiphy->bands[NL80211_BAND_5GHZ] != NULL) { + struct ieee80211_supported_band *b = wiphy->bands[NL80211_BAND_5GHZ]; + for (i = 0; i < b->n_channels; i++) { + req->chan5G[req->chan5G_cnt].flags = 0; + if (b->channels[i].flags & IEEE80211_CHAN_DISABLED) + req->chan5G[req->chan5G_cnt].flags |= CHAN_DISABLED; + req->chan5G[req->chan5G_cnt].flags |= get_chan_flags(b->channels[i].flags); + req->chan5G[req->chan5G_cnt].band = NL80211_BAND_5GHZ; + req->chan5G[req->chan5G_cnt].freq = b->channels[i].center_freq; + req->chan5G[req->chan5G_cnt].tx_power = chan_to_fw_pwr(b->channels[i].max_power); + req->chan5G_cnt++; + if (req->chan5G_cnt == MAC_DOMAINCHANNEL_5G_MAX) + break; + } + } +#endif + /* Send the ME_CHAN_CONFIG_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, ME_CHAN_CONFIG_CFM, NULL); +} + +int ecrnx_send_me_set_control_port_req(struct ecrnx_hw *ecrnx_hw, bool opened, u8 sta_idx) +{ + struct me_set_control_port_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the ME_SET_CONTROL_PORT_REQ message */ + req = ecrnx_msg_zalloc(ME_SET_CONTROL_PORT_REQ, TASK_ME, DRV_TASK_ID, + sizeof(struct me_set_control_port_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the ME_SET_CONTROL_PORT_REQ message */ + req->sta_idx = sta_idx; + req->control_port_open = opened; + + /* Send the ME_SET_CONTROL_PORT_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, ME_SET_CONTROL_PORT_CFM, NULL); +} + +int ecrnx_send_me_sta_add(struct ecrnx_hw *ecrnx_hw, struct station_parameters *params, + const u8 *mac, u8 inst_nbr, struct me_sta_add_cfm *cfm) +{ + struct me_sta_add_req *req; + u8 *ht_mcs = (u8 *)¶ms->ht_capa->mcs; + int i; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_STA_ADD_REQ message */ + req = ecrnx_msg_zalloc(ME_STA_ADD_REQ, TASK_ME, DRV_TASK_ID, + sizeof(struct me_sta_add_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the MM_STA_ADD_REQ message */ + memcpy(&(req->mac_addr.array[0]), mac, ETH_ALEN); + + req->rate_set.length = params->supported_rates_len; + for (i = 0; i < params->supported_rates_len; i++) + req->rate_set.array[i] = params->supported_rates[i]; + + req->flags = 0; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + if (params->capability & WLAN_CAPABILITY_SHORT_PREAMBLE){ + req->flags |= STA_SHORT_PREAMBLE_CAPA; + } +#endif + + if (params->ht_capa) { + const struct ieee80211_ht_cap *ht_capa = params->ht_capa; + + req->flags |= STA_HT_CAPA; + req->ht_cap.ht_capa_info = cpu_to_le16(ht_capa->cap_info); + req->ht_cap.a_mpdu_param = ht_capa->ampdu_params_info; + for (i = 0; i < sizeof(ht_capa->mcs); i++) + req->ht_cap.mcs_rate[i] = ht_mcs[i]; + req->ht_cap.ht_extended_capa = cpu_to_le16(ht_capa->extended_ht_cap_info); + req->ht_cap.tx_beamforming_capa = cpu_to_le32(ht_capa->tx_BF_cap_info); + req->ht_cap.asel_capa = ht_capa->antenna_selection_info; + } + + if (params->vht_capa) { + const struct ieee80211_vht_cap *vht_capa = params->vht_capa; + + req->flags |= STA_VHT_CAPA; + req->vht_cap.vht_capa_info = cpu_to_le32(vht_capa->vht_cap_info); + req->vht_cap.rx_highest = cpu_to_le16(vht_capa->supp_mcs.rx_highest); + req->vht_cap.rx_mcs_map = cpu_to_le16(vht_capa->supp_mcs.rx_mcs_map); + req->vht_cap.tx_highest = cpu_to_le16(vht_capa->supp_mcs.tx_highest); + req->vht_cap.tx_mcs_map = cpu_to_le16(vht_capa->supp_mcs.tx_mcs_map); + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)) && defined(CONFIG_ECRNX_HE) + if (params->he_capa) { + const struct ieee80211_he_cap_elem *he_capa = params->he_capa; + struct ieee80211_he_mcs_nss_supp *mcs_nss_supp = + (struct ieee80211_he_mcs_nss_supp *)(he_capa + 1); + + req->flags |= STA_HE_CAPA; + for (i = 0; i < ARRAY_SIZE(he_capa->mac_cap_info); i++) { + req->he_cap.mac_cap_info[i] = he_capa->mac_cap_info[i]; + } + for (i = 0; i < ARRAY_SIZE(he_capa->phy_cap_info); i++) { + req->he_cap.phy_cap_info[i] = he_capa->phy_cap_info[i]; + } + req->he_cap.mcs_supp.rx_mcs_80 = mcs_nss_supp->rx_mcs_80; + req->he_cap.mcs_supp.tx_mcs_80 = mcs_nss_supp->tx_mcs_80; + req->he_cap.mcs_supp.rx_mcs_160 = mcs_nss_supp->rx_mcs_160; + req->he_cap.mcs_supp.tx_mcs_160 = mcs_nss_supp->tx_mcs_160; + req->he_cap.mcs_supp.rx_mcs_80p80 = mcs_nss_supp->rx_mcs_80p80; + req->he_cap.mcs_supp.tx_mcs_80p80 = mcs_nss_supp->tx_mcs_80p80; + } + +#endif + + if (params->sta_flags_set & BIT(NL80211_STA_FLAG_WME)) + req->flags |= STA_QOS_CAPA; + + if (params->sta_flags_set & BIT(NL80211_STA_FLAG_MFP)) // if (sme->mfp == NL80211_MFP_REQUIRED || sme->mfp ==NL80211_MFP_OPTIONAL) //wfa must used NL80211_MFP_REQUIRED and NL80211_MFP_OPTIONAL + req->flags |= STA_MFP_CAPA; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) + if (params->opmode_notif_used) { + req->flags |= STA_OPMOD_NOTIF; + req->opmode = params->opmode_notif; + } +#endif + + req->aid = cpu_to_le16(params->aid); + req->uapsd_queues = params->uapsd_queues; + req->max_sp_len = params->max_sp * 2; + req->vif_idx = inst_nbr; + + if (params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)) { + struct ecrnx_vif *ecrnx_vif = ecrnx_hw->vif_table[inst_nbr]; + req->tdls_sta = true; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + if ((params->ext_capab[3] & WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH) && + !ecrnx_vif->tdls_chsw_prohibited) + req->tdls_chsw_allowed = true; +#endif + if (ecrnx_vif->tdls_status == TDLS_SETUP_RSP_TX) + req->tdls_sta_initiator = true; + } + + /* Send the ME_STA_ADD_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, ME_STA_ADD_CFM, cfm); +} + +int ecrnx_send_me_sta_del(struct ecrnx_hw *ecrnx_hw, u8 sta_idx, bool tdls_sta) +{ + struct me_sta_del_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_STA_DEL_REQ message */ + req = ecrnx_msg_zalloc(ME_STA_DEL_REQ, TASK_ME, DRV_TASK_ID, + sizeof(struct me_sta_del_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the MM_STA_DEL_REQ message */ + req->sta_idx = sta_idx; + req->tdls_sta = tdls_sta; + + /* Send the ME_STA_DEL_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, ME_STA_DEL_CFM, NULL); +} + +int ecrnx_send_me_traffic_ind(struct ecrnx_hw *ecrnx_hw, u8 sta_idx, bool uapsd, u8 tx_status) +{ + struct me_traffic_ind_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the ME_UTRAFFIC_IND_REQ message */ + req = ecrnx_msg_zalloc(ME_TRAFFIC_IND_REQ, TASK_ME, DRV_TASK_ID, + sizeof(struct me_traffic_ind_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the ME_TRAFFIC_IND_REQ message */ + req->sta_idx = sta_idx; + req->tx_avail = tx_status; + req->uapsd = uapsd; + + /* Send the ME_TRAFFIC_IND_REQ to UMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, ME_TRAFFIC_IND_CFM, NULL); +} + +int ecrnx_send_twt_request(struct ecrnx_hw *ecrnx_hw, + u8 setup_type, u8 vif_idx, + struct twt_conf_tag *conf, + struct twt_setup_cfm *cfm) +{ + struct twt_setup_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the TWT_SETUP_REQ message */ + req = ecrnx_msg_zalloc(TWT_SETUP_REQ, TASK_TWT, DRV_TASK_ID, + sizeof(struct twt_setup_req)); + if (!req) + return -ENOMEM; + + memcpy(&req->conf, conf, sizeof(req->conf)); + req->setup_type = setup_type; + req->vif_idx = vif_idx; + + /* Send the TWT_SETUP_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, TWT_SETUP_CFM, cfm); +} + +int ecrnx_send_twt_teardown(struct ecrnx_hw *ecrnx_hw, + struct twt_teardown_req *twt_teardown, + struct twt_teardown_cfm *cfm) +{ + struct twt_teardown_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the TWT_TEARDOWN_REQ message */ + req = ecrnx_msg_zalloc(TWT_TEARDOWN_REQ, TASK_TWT, DRV_TASK_ID, + sizeof(struct twt_teardown_req)); + if (!req) + return -ENOMEM; + + memcpy(req, twt_teardown, sizeof(struct twt_teardown_req)); + + /* Send the TWT_TEARDOWN_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, TWT_TEARDOWN_CFM, cfm); +} +int ecrnx_send_me_rc_stats(struct ecrnx_hw *ecrnx_hw, + u8 sta_idx, + struct me_rc_stats_cfm *cfm) +{ + struct me_rc_stats_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the ME_RC_STATS_REQ message */ + req = ecrnx_msg_zalloc(ME_RC_STATS_REQ, TASK_ME, DRV_TASK_ID, + sizeof(struct me_rc_stats_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the ME_RC_STATS_REQ message */ + req->sta_idx = sta_idx; + + /* Send the ME_RC_STATS_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, ME_RC_STATS_CFM, cfm); +} + +int ecrnx_send_me_rc_set_rate(struct ecrnx_hw *ecrnx_hw, + u8 sta_idx, + u16 rate_cfg) +{ + struct me_rc_set_rate_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the ME_RC_SET_RATE_REQ message */ + req = ecrnx_msg_zalloc(ME_RC_SET_RATE_REQ, TASK_ME, DRV_TASK_ID, + sizeof(struct me_rc_set_rate_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the ME_RC_SET_RATE_REQ message */ + req->sta_idx = sta_idx; + req->fixed_rate_cfg = rate_cfg; + + /* Send the ME_RC_SET_RATE_REQ message to FW */ + return ecrnx_send_msg(ecrnx_hw, req, 0, 0, NULL); +} + +int ecrnx_send_me_set_ps_mode(struct ecrnx_hw *ecrnx_hw, u8 ps_mode) +{ + struct me_set_ps_mode_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the ME_SET_PS_MODE_REQ message */ + req = ecrnx_msg_zalloc(ME_SET_PS_MODE_REQ, TASK_ME, DRV_TASK_ID, + sizeof(struct me_set_ps_mode_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the ME_SET_PS_MODE_REQ message */ + req->ps_state = ps_mode; + + /* Send the ME_SET_PS_MODE_REQ message to FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, ME_SET_PS_MODE_CFM, NULL); +} + +int ecrnx_send_sm_connect_req(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_vif *ecrnx_vif, + struct cfg80211_connect_params *sme, + struct sm_connect_cfm *cfm) +{ + struct sm_connect_req *req; + int i, ie_len; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + ie_len = update_connect_req(ecrnx_vif, sme); + /* Build the SM_CONNECT_REQ message */ + req = ecrnx_msg_zalloc(SM_CONNECT_REQ, TASK_SM, DRV_TASK_ID, + (sizeof(struct sm_connect_req) + ie_len)); + if (!req) + return -ENOMEM; + + /* Set parameters for the SM_CONNECT_REQ message */ + if (sme->crypto.n_ciphers_pairwise && + ((sme->crypto.ciphers_pairwise[0] == WLAN_CIPHER_SUITE_WEP40) || + (sme->crypto.ciphers_pairwise[0] == WLAN_CIPHER_SUITE_TKIP) || + (sme->crypto.ciphers_pairwise[0] == WLAN_CIPHER_SUITE_WEP104))) + req->flags |= DISABLE_HT; + + if (sme->crypto.control_port) + req->flags |= CONTROL_PORT_HOST; + + if (sme->crypto.control_port_no_encrypt) + req->flags |= CONTROL_PORT_NO_ENC; + + if (use_pairwise_key(&sme->crypto)) + req->flags |= WPA_WPA2_IN_USE; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + if (sme->mfp == NL80211_MFP_REQUIRED) + req->flags |= MFP_IN_USE; +#endif + + req->ctrl_port_ethertype = sme->crypto.control_port_ethertype; + + if (sme->bssid) + memcpy(&req->bssid, sme->bssid, ETH_ALEN); + else + req->bssid = mac_addr_bcst; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + if (sme->prev_bssid) + req->flags |= REASSOCIATION; +#else + if (ecrnx_vif->sta.ap) + req->flags |= REASSOCIATION; +#endif + if ((sme->auth_type == NL80211_AUTHTYPE_FT) && (ecrnx_vif->sta.flags & ECRNX_STA_FT_OVER_DS)) + req->flags |= (REASSOCIATION | FT_OVER_DS); + req->vif_idx = ecrnx_vif->vif_index; + if (sme->channel) { + req->chan.band = sme->channel->band; + req->chan.freq = sme->channel->center_freq; + req->chan.flags = get_chan_flags(sme->channel->flags); + } else { + req->chan.freq = (u16_l)-1; + } + for (i = 0; i < sme->ssid_len; i++) + req->ssid.array[i] = sme->ssid[i]; + req->ssid.length = sme->ssid_len; + + req->listen_interval = ecrnx_mod_params.listen_itv; + req->dont_wait_bcmc = !ecrnx_mod_params.listen_bcmc; + + /* Set auth_type */ + if (sme->auth_type == NL80211_AUTHTYPE_AUTOMATIC) + req->auth_type = WLAN_AUTH_OPEN; + else if (sme->auth_type == NL80211_AUTHTYPE_OPEN_SYSTEM) + req->auth_type = WLAN_AUTH_OPEN; + else if (sme->auth_type == NL80211_AUTHTYPE_SHARED_KEY) + req->auth_type = WLAN_AUTH_SHARED_KEY; + else if (sme->auth_type == NL80211_AUTHTYPE_FT) + req->auth_type = WLAN_AUTH_FT; + else if (sme->auth_type == NL80211_AUTHTYPE_SAE) + req->auth_type = WLAN_AUTH_SAE; + else + goto invalid_param; + copy_connect_ies(ecrnx_vif, req, sme); + + /* Set UAPSD queues */ + req->uapsd_queues = ecrnx_mod_params.uapsd_queues; + + /* Send the SM_CONNECT_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, SM_CONNECT_CFM, cfm); + +invalid_param: + ecrnx_msg_free(ecrnx_hw, req); + return -EINVAL; +} + +int ecrnx_send_sm_disconnect_req(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_vif *ecrnx_vif, + u16 reason) +{ + struct sm_disconnect_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the SM_DISCONNECT_REQ message */ + req = ecrnx_msg_zalloc(SM_DISCONNECT_REQ, TASK_SM, DRV_TASK_ID, + sizeof(struct sm_disconnect_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the SM_DISCONNECT_REQ message */ + req->reason_code = reason; + req->vif_idx = ecrnx_vif->vif_index; + + /* Send the SM_DISCONNECT_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, SM_DISCONNECT_CFM, NULL); +} + +int ecrnx_send_sm_external_auth_required_rsp(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_vif *ecrnx_vif, + u16 status) +{ + struct sm_external_auth_required_rsp *rsp; + + /* Build the SM_EXTERNAL_AUTH_CFM message */ + rsp = ecrnx_msg_zalloc(SM_EXTERNAL_AUTH_REQUIRED_RSP, TASK_SM, DRV_TASK_ID, + sizeof(struct sm_external_auth_required_rsp)); + if (!rsp) + return -ENOMEM; + + rsp->status = status; + rsp->vif_idx = ecrnx_vif->vif_index; + + /* send the SM_EXTERNAL_AUTH_REQUIRED_RSP message UMAC FW */ + return ecrnx_send_msg(ecrnx_hw, rsp, 0, 0, NULL); +} +int ecrnx_send_sm_ft_auth_rsp(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + uint8_t *ie, int ie_len) +{ + struct sm_connect_req *rsp; + rsp = ecrnx_msg_zalloc(SM_FT_AUTH_RSP, TASK_SM, DRV_TASK_ID, + (sizeof(struct sm_connect_req) + ie_len)); + if (!rsp) + return -ENOMEM; + rsp->vif_idx = ecrnx_vif->vif_index; + rsp->ie_len = ie_len; + memcpy(rsp->ie_buf, ie, rsp->ie_len); + return ecrnx_send_msg(ecrnx_hw, rsp, 0, 0, NULL); +} + +int ecrnx_send_apm_start_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + struct cfg80211_ap_settings *settings, + struct apm_start_cfm *cfm, + struct ecrnx_ipc_elem_var *elem) +{ + struct apm_start_req *req; + struct ecrnx_bcn *bcn = &vif->ap.bcn; + u8 *buf; + u32 flags = 0; + const u8 *rate_ie; + u8 rate_len = 0; + int var_offset = offsetof(struct ieee80211_mgmt, u.beacon.variable); + const u8 *var_pos; + int len, i, error; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the APM_START_REQ message */ + req = ecrnx_msg_zalloc(APM_START_REQ, TASK_APM, DRV_TASK_ID, + sizeof(struct apm_start_req)); + if (!req) + return -ENOMEM; + + // Build the beacon + bcn->dtim = (u8)settings->dtim_period; + buf = ecrnx_build_bcn(bcn, &settings->beacon); + if (!buf) { + ecrnx_msg_free(ecrnx_hw, req); + return -ENOMEM; + } + + // Retrieve the basic rate set from the beacon buffer + len = bcn->len - var_offset; + var_pos = buf + var_offset; + +// Assume that rate higher that 54 Mbps are BSS membership +#define IS_BASIC_RATE(r) (r & 0x80) && ((r & ~0x80) <= (54 * 2)) + + rate_ie = cfg80211_find_ie(WLAN_EID_SUPP_RATES, var_pos, len); + if (rate_ie) { + const u8 *rates = rate_ie + 2; + for (i = 0; (i < rate_ie[1]) && (rate_len < MAC_RATESET_LEN); i++) { + if (IS_BASIC_RATE(rates[i])) + req->basic_rates.array[rate_len++] = rates[i]; + } + } + rate_ie = cfg80211_find_ie(WLAN_EID_EXT_SUPP_RATES, var_pos, len); + if (rate_ie) { + const u8 *rates = rate_ie + 2; + for (i = 0; (i < rate_ie[1]) && (rate_len < MAC_RATESET_LEN); i++) { + if (IS_BASIC_RATE(rates[i])) + req->basic_rates.array[rate_len++] = rates[i]; + } + } + req->basic_rates.length = rate_len; +#undef IS_BASIC_RATE + + // Sync buffer for FW + if ((error = ecrnx_ipc_elem_var_allocs(ecrnx_hw, elem, bcn->len, + DMA_TO_DEVICE, buf, NULL, NULL))) { + return error; + } + + /* Set parameters for the APM_START_REQ message */ + req->vif_idx = vif->vif_index; + req->bcn_addr = elem->dma_addr; + req->bcn_len = bcn->len; + req->tim_oft = bcn->head_len; + req->tim_len = bcn->tim_len; + cfg80211_to_ecrnx_chan(&settings->chandef, &req->chan); + req->bcn_int = settings->beacon_interval; + if (settings->crypto.control_port) + flags |= CONTROL_PORT_HOST; + + if (settings->crypto.control_port_no_encrypt) + flags |= CONTROL_PORT_NO_ENC; + + if (use_pairwise_key(&settings->crypto)) + flags |= WPA_WPA2_IN_USE; + + if (settings->crypto.control_port_ethertype) + req->ctrl_port_ethertype = settings->crypto.control_port_ethertype; + else + req->ctrl_port_ethertype = ETH_P_PAE; + req->flags = flags; + + /* Send the APM_START_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, APM_START_CFM, cfm); +} + +int ecrnx_send_apm_stop_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif) +{ + struct apm_stop_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the APM_STOP_REQ message */ + req = ecrnx_msg_zalloc(APM_STOP_REQ, TASK_APM, DRV_TASK_ID, + sizeof(struct apm_stop_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the APM_STOP_REQ message */ + req->vif_idx = vif->vif_index; + + /* Send the APM_STOP_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, APM_STOP_CFM, NULL); +} + +int ecrnx_send_apm_probe_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + struct ecrnx_sta *sta, struct apm_probe_client_cfm *cfm) +{ + struct apm_probe_client_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + req = ecrnx_msg_zalloc(APM_PROBE_CLIENT_REQ, TASK_APM, DRV_TASK_ID, + sizeof(struct apm_probe_client_req)); + if (!req) + return -ENOMEM; + + req->vif_idx = vif->vif_index; + req->sta_idx = sta->sta_idx; + + /* Send the APM_PROBE_CLIENT_REQ message to UMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, APM_PROBE_CLIENT_CFM, cfm); +} +int ecrnx_send_scanu_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + struct cfg80211_scan_request *param) +{ + struct scanu_start_req *req; + int i, chan_num = 0; + uint8_t chan_flags = 0; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the SCANU_START_REQ message */ + req = ecrnx_msg_zalloc(SCANU_START_REQ, TASK_SCANU, DRV_TASK_ID, + sizeof(struct scanu_start_req)); + if (!req) + return -ENOMEM; + + /* Set parameters */ + req->vif_idx = ecrnx_vif->vif_index; + req->chan_cnt = (u8)min_t(int, SCAN_CHANNEL_MAX, param->n_channels); + req->ssid_cnt = (u8)min_t(int, SCAN_SSID_MAX, param->n_ssids); + req->bssid = mac_addr_bcst; + req->no_cck = param->no_cck; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + if (param->duration_mandatory) + req->duration = ieee80211_tu_to_usec(param->duration); +#endif + + if(param->n_ssids > SCAN_SSID_MAX){ + ECRNX_PRINT("%s:n_ssids: %d \n", __func__, param->n_ssids); + for (i = 0; i < param->n_ssids; i++){ + print_hex_dump_bytes("[ecrnx]scan_req: ", DUMP_PREFIX_NONE, ¶m->ssids[i], param->ssids[i].ssid_len); + ECRNX_PRINT("i:%d, ssid_len:%d \n", i, param->ssids[i].ssid_len); + } + } + + if (req->ssid_cnt == 0) + chan_flags |= CHAN_NO_IR; + for (i = 0; i < req->ssid_cnt; i++) { + int j; + for (j = 0; j < param->ssids[i].ssid_len; j++) + req->ssid[i].array[j] = param->ssids[i].ssid[j]; + req->ssid[i].length = param->ssids[i].ssid_len; + } + + if(req->ssid_cnt == 1 && param->ssids[0].ssid_len > 0) + { + if (strcmp(req->ssid[0].array, "DIRECT-")) + { + req->ssid_cnt = 2; + req->ssid[1].length = 0; + } + } + + if (param->ie) { + + if (ecrnx_ipc_elem_var_allocs(ecrnx_hw, &ecrnx_hw->scan_ie, + param->ie_len, DMA_TO_DEVICE, + NULL, param->ie, NULL)) + goto error; + req->add_ie_len = param->ie_len; + req->add_ies = ecrnx_hw->scan_ie.dma_addr; + } else { + req->add_ie_len = 0; + req->add_ies = 0; + } + + for (i = 0; i < req->chan_cnt; i++) { + struct ieee80211_channel *chan = param->channels[i]; + + if(chan->band){ + continue; + } + req->chan[chan_num].band = chan->band; + req->chan[chan_num].freq = chan->center_freq; + req->chan[chan_num].flags = chan_flags | get_chan_flags(chan->flags); + req->chan[chan_num].tx_power = chan_to_fw_pwr(chan->max_reg_power); + chan_num++; + //printk("--%d set ch, %d,%d,%d,%d\n", i, req->chan[i].band, req->chan[i].freq, req->chan[i].flags, req->chan[i].tx_power); + } + req->chan_cnt = chan_num; + /* Send the SCANU_START_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 0, 0, NULL); +error: + if (req != NULL) + ecrnx_msg_free(ecrnx_hw, req); + return -ENOMEM; +} + +int ecrnx_send_scanu_cancel_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif) +{ + struct scanu_cancel_req *req = NULL; + + /* Build the SCANU_START_REQ message */ + req = ecrnx_msg_zalloc(SCANU_CANCEL_REQ, TASK_SCANU, DRV_TASK_ID, + sizeof(struct scanu_cancel_req)); + if (!req){ + return -ENOMEM; + } + + req->vif_idx = ecrnx_vif->vif_index; + ECRNX_PRINT("%s: vif_idx:%d; \n", __func__, req->vif_idx); + return ecrnx_send_msg(ecrnx_hw, req, 0, 0, NULL); +} + +int ecrnx_send_apm_start_cac_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + struct cfg80211_chan_def *chandef, + struct apm_start_cac_cfm *cfm) +{ + struct apm_start_cac_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the APM_START_CAC_REQ message */ + req = ecrnx_msg_zalloc(APM_START_CAC_REQ, TASK_APM, DRV_TASK_ID, + sizeof(struct apm_start_cac_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the APM_START_CAC_REQ message */ + req->vif_idx = vif->vif_index; + cfg80211_to_ecrnx_chan(chandef, &req->chan); + + /* Send the APM_START_CAC_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, APM_START_CAC_CFM, cfm); +} + +int ecrnx_send_apm_stop_cac_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif) +{ + struct apm_stop_cac_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the APM_STOP_CAC_REQ message */ + req = ecrnx_msg_zalloc(APM_STOP_CAC_REQ, TASK_APM, DRV_TASK_ID, + sizeof(struct apm_stop_cac_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the APM_STOP_CAC_REQ message */ + req->vif_idx = vif->vif_index; + + /* Send the APM_STOP_CAC_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, APM_STOP_CAC_CFM, NULL); +} + +int ecrnx_send_mesh_start_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + const struct mesh_config *conf, const struct mesh_setup *setup, + struct mesh_start_cfm *cfm) +{ + // Message to send + struct mesh_start_req *req; + // Supported basic rates + struct ieee80211_supported_band *band = ecrnx_hw->wiphy->bands[setup->chandef.chan->band]; + /* Counter */ + int i; + /* Return status */ + int status; + /* DMA Address to be unmapped after confirmation reception */ + u32 dma_addr = 0; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MESH_START_REQ message */ + req = ecrnx_msg_zalloc(MESH_START_REQ, TASK_MESH, DRV_TASK_ID, + sizeof(struct mesh_start_req)); + if (!req) { + return -ENOMEM; + } + + req->vif_index = vif->vif_index; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + req->bcn_int = setup->beacon_interval; + req->dtim_period = setup->dtim_period; +#endif + req->mesh_id_len = setup->mesh_id_len; + + for (i = 0; i < setup->mesh_id_len; i++) { + req->mesh_id[i] = *(setup->mesh_id + i); + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + req->user_mpm = setup->user_mpm; +#endif + req->is_auth = setup->is_authenticated; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0) + req->auth_id = setup->auth_id; +#endif + req->ie_len = setup->ie_len; + + if (setup->ie_len) { + /* + * Need to provide a Virtual Address to the MAC so that it can download the + * additional information elements. + */ + req->ie_addr = dma_map_single(ecrnx_hw->dev, (void *)setup->ie, + setup->ie_len, DMA_FROM_DEVICE); + + /* Check DMA mapping result */ + if (dma_mapping_error(ecrnx_hw->dev, req->ie_addr)) { + ECRNX_ERR(KERN_CRIT "%s - DMA Mapping error on additional IEs\n", __func__); + + /* Consider there is no Additional IEs */ + req->ie_len = 0; + } else { + /* Store DMA Address so that we can unmap the memory section once MESH_START_CFM is received */ + dma_addr = req->ie_addr; + } + } + + /* Provide rate information */ + req->basic_rates.length = 0; + for (i = 0; i < band->n_bitrates; i++) { + u16 rate = band->bitrates[i].bitrate; + + /* Read value is in in units of 100 Kbps, provided value is in units + * of 1Mbps, and multiplied by 2 so that 5.5 becomes 11 */ + rate = (rate << 1) / 10; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0) + if (setup->basic_rates & CO_BIT(i)) { + rate |= 0x80; + } +#endif + + req->basic_rates.array[i] = (u8)rate; + req->basic_rates.length++; + } + + /* Provide channel information */ + cfg80211_to_ecrnx_chan(&setup->chandef, &req->chan); + + /* Send the MESH_START_REQ message to UMAC FW */ + status = ecrnx_send_msg(ecrnx_hw, req, 1, MESH_START_CFM, cfm); + + /* Unmap DMA area */ + if (setup->ie_len) { + dma_unmap_single(ecrnx_hw->dev, dma_addr, setup->ie_len, DMA_TO_DEVICE); + } + + /* Return the status */ + return (status); +} + +int ecrnx_send_mesh_stop_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + struct mesh_stop_cfm *cfm) +{ + // Message to send + struct mesh_stop_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MESH_STOP_REQ message */ + req = ecrnx_msg_zalloc(MESH_STOP_REQ, TASK_MESH, DRV_TASK_ID, + sizeof(struct mesh_stop_req)); + if (!req) { + return -ENOMEM; + } + + req->vif_idx = vif->vif_index; + + /* Send the MESH_STOP_REQ message to UMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MESH_STOP_CFM, cfm); +} + +int ecrnx_send_mesh_update_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + u32 mask, const struct mesh_config *p_mconf, struct mesh_update_cfm *cfm) +{ + // Message to send + struct mesh_update_req *req; + // Keep only bit for fields which can be updated + u32 supp_mask = (mask << 1) & (CO_BIT(NL80211_MESHCONF_GATE_ANNOUNCEMENTS) + | CO_BIT(NL80211_MESHCONF_HWMP_ROOTMODE) + | CO_BIT(NL80211_MESHCONF_FORWARDING) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + | CO_BIT(NL80211_MESHCONF_POWER_MODE) +#endif + ); + + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + if (!supp_mask) { + return -ENOENT; + } + + /* Build the MESH_UPDATE_REQ message */ + req = ecrnx_msg_zalloc(MESH_UPDATE_REQ, TASK_MESH, DRV_TASK_ID, + sizeof(struct mesh_update_req)); + + if (!req) { + return -ENOMEM; + } + + req->vif_idx = vif->vif_index; + + if (supp_mask & CO_BIT(NL80211_MESHCONF_GATE_ANNOUNCEMENTS)) + { + req->flags |= CO_BIT(MESH_UPDATE_FLAGS_GATE_MODE_BIT); + req->gate_announ = p_mconf->dot11MeshGateAnnouncementProtocol; + } + + if (supp_mask & CO_BIT(NL80211_MESHCONF_HWMP_ROOTMODE)) + { + req->flags |= CO_BIT(MESH_UPDATE_FLAGS_ROOT_MODE_BIT); + req->root_mode = p_mconf->dot11MeshHWMPRootMode; + } + + if (supp_mask & CO_BIT(NL80211_MESHCONF_FORWARDING)) + { + req->flags |= CO_BIT(MESH_UPDATE_FLAGS_MESH_FWD_BIT); + req->mesh_forward = p_mconf->dot11MeshForwarding; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + if (supp_mask & CO_BIT(NL80211_MESHCONF_POWER_MODE)) + { + req->flags |= CO_BIT(MESH_UPDATE_FLAGS_LOCAL_PSM_BIT); + req->local_ps_mode = p_mconf->power_mode; + } +#endif + /* Send the MESH_UPDATE_REQ message to UMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MESH_UPDATE_CFM, cfm); +} + +int ecrnx_send_mesh_peer_info_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + u8 sta_idx, struct mesh_peer_info_cfm *cfm) +{ + // Message to send + struct mesh_peer_info_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MESH_PEER_INFO_REQ message */ + req = ecrnx_msg_zalloc(MESH_PEER_INFO_REQ, TASK_MESH, DRV_TASK_ID, + sizeof(struct mesh_peer_info_req)); + if (!req) { + return -ENOMEM; + } + + req->sta_idx = sta_idx; + + /* Send the MESH_PEER_INFO_REQ message to UMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MESH_PEER_INFO_CFM, cfm); +} + +void ecrnx_send_mesh_peer_update_ntf(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + u8 sta_idx, u8 mlink_state) +{ + // Message to send + struct mesh_peer_update_ntf *ntf; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MESH_PEER_UPDATE_NTF message */ + ntf = ecrnx_msg_zalloc(MESH_PEER_UPDATE_NTF, TASK_MESH, DRV_TASK_ID, + sizeof(struct mesh_peer_update_ntf)); + + if (ntf) { + ntf->vif_idx = vif->vif_index; + ntf->sta_idx = sta_idx; + ntf->state = mlink_state; + + /* Send the MESH_PEER_INFO_REQ message to UMAC FW */ + ecrnx_send_msg(ecrnx_hw, ntf, 0, 0, NULL); + } +} + +void ecrnx_send_mesh_path_create_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, u8 *tgt_addr) +{ + struct mesh_path_create_req *req; + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Check if we are already waiting for a confirmation */ + if (vif->ap.flags & ECRNX_AP_CREATE_MESH_PATH) + return; + + /* Build the MESH_PATH_CREATE_REQ message */ + req = ecrnx_msg_zalloc(MESH_PATH_CREATE_REQ, TASK_MESH, DRV_TASK_ID, + sizeof(struct mesh_path_create_req)); + if (!req) + return; + + req->vif_idx = vif->vif_index; + memcpy(&req->tgt_mac_addr, tgt_addr, ETH_ALEN); + + vif->ap.flags |= ECRNX_AP_CREATE_MESH_PATH; + + /* Send the MESH_PATH_CREATE_REQ message to UMAC FW */ + ecrnx_send_msg(ecrnx_hw, req, 0, 0, NULL); +} + +int ecrnx_send_mesh_path_update_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, const u8 *tgt_addr, + const u8 *p_nhop_addr, struct mesh_path_update_cfm *cfm) +{ + // Message to send + struct mesh_path_update_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MESH_PATH_UPDATE_REQ message */ + req = ecrnx_msg_zalloc(MESH_PATH_UPDATE_REQ, TASK_MESH, DRV_TASK_ID, + sizeof(struct mesh_path_update_req)); + if (!req) { + return -ENOMEM; + } + + req->delete = (p_nhop_addr == NULL); + req->vif_idx = vif->vif_index; + memcpy(&req->tgt_mac_addr, tgt_addr, ETH_ALEN); + + if (p_nhop_addr) { + memcpy(&req->nhop_mac_addr, p_nhop_addr, ETH_ALEN); + } + + /* Send the MESH_PATH_UPDATE_REQ message to UMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MESH_PATH_UPDATE_CFM, cfm); +} + +void ecrnx_send_mesh_proxy_add_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, u8 *ext_addr) +{ + // Message to send + struct mesh_proxy_add_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MESH_PROXY_ADD_REQ message */ + req = ecrnx_msg_zalloc(MESH_PROXY_ADD_REQ, TASK_MESH, DRV_TASK_ID, + sizeof(struct mesh_proxy_add_req)); + + if (req) { + req->vif_idx = vif->vif_index; + memcpy(&req->ext_sta_addr, ext_addr, ETH_ALEN); + + /* Send the MESH_PROXY_ADD_REQ message to UMAC FW */ + ecrnx_send_msg(ecrnx_hw, req, 0, 0, NULL); + } +} + +int ecrnx_send_tdls_peer_traffic_ind_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif) +{ + struct tdls_peer_traffic_ind_req *tdls_peer_traffic_ind_req; + + if (!ecrnx_vif->sta.tdls_sta) + return -ENOLINK; + + /* Build the TDLS_PEER_TRAFFIC_IND_REQ message */ + tdls_peer_traffic_ind_req = ecrnx_msg_zalloc(TDLS_PEER_TRAFFIC_IND_REQ, TASK_TDLS, DRV_TASK_ID, + sizeof(struct tdls_peer_traffic_ind_req)); + + if (!tdls_peer_traffic_ind_req) + return -ENOMEM; + + /* Set parameters for the TDLS_PEER_TRAFFIC_IND_REQ message */ + tdls_peer_traffic_ind_req->vif_index = ecrnx_vif->vif_index; + tdls_peer_traffic_ind_req->sta_idx = ecrnx_vif->sta.tdls_sta->sta_idx; + memcpy(&(tdls_peer_traffic_ind_req->peer_mac_addr.array[0]), + ecrnx_vif->sta.tdls_sta->mac_addr, ETH_ALEN); + tdls_peer_traffic_ind_req->dialog_token = 0; // check dialog token value + tdls_peer_traffic_ind_req->last_tid = ecrnx_vif->sta.tdls_sta->tdls.last_tid; + tdls_peer_traffic_ind_req->last_sn = ecrnx_vif->sta.tdls_sta->tdls.last_sn; + + /* Send the TDLS_PEER_TRAFFIC_IND_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, tdls_peer_traffic_ind_req, 0, 0, NULL); +} + +int ecrnx_send_config_monitor_req(struct ecrnx_hw *ecrnx_hw, + struct cfg80211_chan_def *chandef, + struct me_config_monitor_cfm *cfm) +{ + struct me_config_monitor_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the ME_CONFIG_MONITOR_REQ message */ + req = ecrnx_msg_zalloc(ME_CONFIG_MONITOR_REQ, TASK_ME, DRV_TASK_ID, + sizeof(struct me_config_monitor_req)); + if (!req) + return -ENOMEM; + + if (chandef) { + req->chan_set = true; + cfg80211_to_ecrnx_chan(chandef, &req->chan); + + if (ecrnx_hw->phy.limit_bw) + limit_chan_bw(&req->chan.type, req->chan.prim20_freq, &req->chan.center1_freq); + } else { + req->chan_set = false; + } + + req->uf = ecrnx_hw->mod_params->uf; + + /* Send the ME_CONFIG_MONITOR_REQ message to FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, ME_CONFIG_MONITOR_CFM, cfm); +} +#endif /* CONFIG_ECRNX_FULLMAC */ + +#ifdef CONFIG_ECRNX_P2P +int ecrnx_send_p2p_start_listen_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, unsigned int duration) +{ + struct p2p_listen_start_req *req; + struct ecrnx_p2p_listen *p2p_listen = &ecrnx_hw->p2p_listen; + int rc; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + if(p2p_listen->listen_started) + { + ECRNX_ERR("P2P listen already ongoing\n"); + return -EBUSY; + } + + p2p_listen->ecrnx_vif = ecrnx_vif; + p2p_listen->listen_duration = duration; + + if(ecrnx_hw->scan_request) + { + ECRNX_ERR("Delaying p2p listen until scan done\n"); + return 0; + } + + /* Build the P2P_LISTEN_START_REQ message */ + req = ecrnx_msg_zalloc(P2P_LISTEN_START_REQ, TASK_P2P_LISTEN, DRV_TASK_ID, + sizeof(struct p2p_listen_start_req)); + if (!req) + return -ENOMEM; + + req->vif_idx = ecrnx_vif->vif_index; + + rc = ecrnx_send_msg(ecrnx_hw, req, 0, 0, NULL); + if(rc) + return rc; + + p2p_listen->listen_started = 1; + + return rc; +} + +int ecrnx_send_p2p_cancel_listen_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif) +{ + struct p2p_cancel_listen_req *req; + struct ecrnx_p2p_listen *p2p_listen = &ecrnx_hw->p2p_listen; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the P2P_CANCEL_LISTEN_REQ message */ + req = ecrnx_msg_zalloc(P2P_CANCEL_LISTEN_REQ, TASK_P2P_LISTEN, DRV_TASK_ID, + sizeof(struct p2p_cancel_listen_req)); + if (!req) + return -ENOMEM; + + req->vif_idx = ecrnx_vif->vif_index; + p2p_listen->listen_started = 0; + //return rwnx_send_msg(rwnx_hw, req, 1, P2P_CANCEL_LISTEN_CFM, NULL); + ecrnx_send_msg(ecrnx_hw, req, 0, 0, NULL); + + return 0; + +} +#endif + +int ecrnx_send_tdls_chan_switch_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + struct ecrnx_sta *ecrnx_sta, bool sta_initiator, + u8 oper_class, struct cfg80211_chan_def *chandef, + struct tdls_chan_switch_cfm *cfm) +{ + struct tdls_chan_switch_req *tdls_chan_switch_req; + +#ifdef CONFIG_ECRNX_SOFTMAC + /* Check if channel switch already enabled on a TDLS peer */ + if (ecrnx_hw->tdls_info.chsw_en) { + ECRNX_ERR("TDLS channel switch already enabled for another TDLS station\n"); + return -ENOTSUPP; + } +#endif + + /* Build the TDLS_CHAN_SWITCH_REQ message */ + tdls_chan_switch_req = ecrnx_msg_zalloc(TDLS_CHAN_SWITCH_REQ, TASK_TDLS, DRV_TASK_ID, + sizeof(struct tdls_chan_switch_req)); + + if (!tdls_chan_switch_req) + return -ENOMEM; + + /* Set parameters for the TDLS_CHAN_SWITCH_REQ message */ + tdls_chan_switch_req->vif_index = ecrnx_vif->vif_index; + tdls_chan_switch_req->sta_idx = ecrnx_sta->sta_idx; + memcpy(&(tdls_chan_switch_req->peer_mac_addr.array[0]), + ecrnx_sta_addr(ecrnx_sta), ETH_ALEN); + tdls_chan_switch_req->initiator = sta_initiator; + cfg80211_to_ecrnx_chan(chandef, &tdls_chan_switch_req->chan); + tdls_chan_switch_req->op_class = oper_class; + + /* Send the TDLS_CHAN_SWITCH_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, tdls_chan_switch_req, 1, TDLS_CHAN_SWITCH_CFM, cfm); +} + +int ecrnx_send_tdls_cancel_chan_switch_req(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_vif *ecrnx_vif, + struct ecrnx_sta *ecrnx_sta, + struct tdls_cancel_chan_switch_cfm *cfm) +{ + struct tdls_cancel_chan_switch_req *tdls_cancel_chan_switch_req; + + /* Build the TDLS_CHAN_SWITCH_REQ message */ + tdls_cancel_chan_switch_req = ecrnx_msg_zalloc(TDLS_CANCEL_CHAN_SWITCH_REQ, TASK_TDLS, DRV_TASK_ID, + sizeof(struct tdls_cancel_chan_switch_req)); + if (!tdls_cancel_chan_switch_req) + return -ENOMEM; + + /* Set parameters for the TDLS_CHAN_SWITCH_REQ message */ + tdls_cancel_chan_switch_req->vif_index = ecrnx_vif->vif_index; + tdls_cancel_chan_switch_req->sta_idx = ecrnx_sta->sta_idx; + memcpy(&(tdls_cancel_chan_switch_req->peer_mac_addr.array[0]), + ecrnx_sta_addr(ecrnx_sta), ETH_ALEN); + + /* Send the TDLS_CHAN_SWITCH_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, tdls_cancel_chan_switch_req, 1, TDLS_CANCEL_CHAN_SWITCH_CFM, cfm); +} + +#ifdef CONFIG_ECRNX_BFMER +#ifdef CONFIG_ECRNX_SOFTMAC +void ecrnx_send_bfmer_enable(struct ecrnx_hw *ecrnx_hw, struct ieee80211_sta *sta) +#else +void ecrnx_send_bfmer_enable(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *ecrnx_sta, + const struct ieee80211_vht_cap *vht_cap) +#endif /* CONFIG_ECRNX_SOFTMAC*/ +{ + struct mm_bfmer_enable_req *bfmer_en_req; +#ifdef CONFIG_ECRNX_SOFTMAC + struct ecrnx_sta *ecrnx_sta = (struct ecrnx_sta *)&sta->drv_priv; + u32 vht_capability; +#else + __le32 vht_capability; + u8 rx_nss = 0; +#endif /* CONFIG_ECRNX_SOFTMAC */ + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + +#ifdef CONFIG_ECRNX_SOFTMAC + if (!sta->vht_cap.vht_supported) { +#else + if (!vht_cap) { +#endif /* CONFIG_ECRNX_SOFTMAC */ + goto end; + } + +#ifdef CONFIG_ECRNX_SOFTMAC + vht_capability = sta->vht_cap.cap; +#else + vht_capability = vht_cap->vht_cap_info; +#endif /* CONFIG_ECRNX_SOFTMAC */ + + if (!(vht_capability & IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE)) { + goto end; + } + +#ifdef CONFIG_ECRNX_FULLMAC + rx_nss = ecrnx_bfmer_get_rx_nss(vht_cap); +#endif /* CONFIG_ECRNX_FULLMAC */ + + /* Allocate a structure that will contain the beamforming report */ + if (ecrnx_bfmer_report_add(ecrnx_hw, ecrnx_sta, ECRNX_BFMER_REPORT_SPACE_SIZE)) + { + goto end; + } + + /* Build the MM_BFMER_ENABLE_REQ message */ + bfmer_en_req = ecrnx_msg_zalloc(MM_BFMER_ENABLE_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_bfmer_enable_req)); + + /* Check message allocation */ + if (!bfmer_en_req) { + /* Free memory allocated for the report */ + ecrnx_bfmer_report_del(ecrnx_hw, ecrnx_sta); + + /* Do not use beamforming */ + goto end; + } + + /* Provide DMA address to the MAC */ + bfmer_en_req->host_bfr_addr = ecrnx_sta->bfm_report->dma_addr; + bfmer_en_req->host_bfr_size = ECRNX_BFMER_REPORT_SPACE_SIZE; + bfmer_en_req->sta_idx = ecrnx_sta->sta_idx; +#ifdef CONFIG_ECRNX_SOFTMAC + bfmer_en_req->aid = sta->aid; + bfmer_en_req->rx_nss = sta->rx_nss; +#else + bfmer_en_req->aid = ecrnx_sta->aid; + bfmer_en_req->rx_nss = rx_nss; +#endif /* CONFIG_ECRNX_SOFTMAC */ + + if (vht_capability & IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE) { + bfmer_en_req->vht_mu_bfmee = true; + } else { + bfmer_en_req->vht_mu_bfmee = false; + } + + /* Send the MM_BFMER_EN_REQ message to LMAC FW */ + ecrnx_send_msg(ecrnx_hw, bfmer_en_req, 0, 0, NULL); + +end: + return; +} + +#ifdef CONFIG_ECRNX_MUMIMO_TX +int ecrnx_send_mu_group_update_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *ecrnx_sta) +{ + struct mm_mu_group_update_req *req; + int group_id, i = 0; + u64 map; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_MU_GROUP_UPDATE_REQ message */ + req = ecrnx_msg_zalloc(MM_MU_GROUP_UPDATE_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_mu_group_update_req) + + ecrnx_sta->group_info.cnt * sizeof(req->groups[0])); + + /* Check message allocation */ + if (!req) + return -ENOMEM; + + /* Go through the groups the STA belongs to */ + group_sta_for_each(ecrnx_sta, group_id, map) { + int user_pos = ecrnx_mu_group_sta_get_pos(ecrnx_hw, ecrnx_sta, group_id); + + if (WARN((i >= ecrnx_sta->group_info.cnt), + "STA%d: Too much group (%d)\n", + ecrnx_sta->sta_idx, i + 1)) + break; + + req->groups[i].group_id = group_id; + req->groups[i].user_pos = user_pos; + + i++; + } + + req->group_cnt = ecrnx_sta->group_info.cnt; + req->sta_idx = ecrnx_sta->sta_idx; + + /* Send the MM_MU_GROUP_UPDATE_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, MM_MU_GROUP_UPDATE_CFM, NULL); +} +#endif /* CONFIG_ECRNX_MUMIMO_TX */ +#endif /* CONFIG_ECRNX_BFMER */ + +/********************************************************************** + * Debug Messages + *********************************************************************/ +int ecrnx_send_dbg_trigger_req(struct ecrnx_hw *ecrnx_hw, char *msg) +{ + struct mm_dbg_trigger_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_DBG_TRIGGER_REQ message */ + req = ecrnx_msg_zalloc(MM_DBG_TRIGGER_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_dbg_trigger_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the MM_DBG_TRIGGER_REQ message */ + strncpy(req->error, msg, sizeof(req->error)); + + /* Send the MM_DBG_TRIGGER_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 0, -1, NULL); +} + +int ecrnx_send_dbg_mem_read_req(struct ecrnx_hw *ecrnx_hw, u32 mem_addr, + struct dbg_mem_read_cfm *cfm) +{ + struct dbg_mem_read_req *mem_read_req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the DBG_MEM_READ_REQ message */ + mem_read_req = ecrnx_msg_zalloc(DBG_MEM_READ_REQ, TASK_DBG, DRV_TASK_ID, + sizeof(struct dbg_mem_read_req)); + if (!mem_read_req) + return -ENOMEM; + + /* Set parameters for the DBG_MEM_READ_REQ message */ + mem_read_req->memaddr = mem_addr; + + /* Send the DBG_MEM_READ_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, mem_read_req, 1, DBG_MEM_READ_CFM, cfm); +} + +int ecrnx_send_dbg_mem_write_req(struct ecrnx_hw *ecrnx_hw, u32 mem_addr, + u32 mem_data) +{ + struct dbg_mem_write_req *mem_write_req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the DBG_MEM_WRITE_REQ message */ + mem_write_req = ecrnx_msg_zalloc(DBG_MEM_WRITE_REQ, TASK_DBG, DRV_TASK_ID, + sizeof(struct dbg_mem_write_req)); + if (!mem_write_req) + return -ENOMEM; + + /* Set parameters for the DBG_MEM_WRITE_REQ message */ + mem_write_req->memaddr = mem_addr; + mem_write_req->memdata = mem_data; + + /* Send the DBG_MEM_WRITE_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, mem_write_req, 1, DBG_MEM_WRITE_CFM, NULL); +} + +int ecrnx_send_dbg_set_mod_filter_req(struct ecrnx_hw *ecrnx_hw, u32 filter) +{ + struct dbg_set_mod_filter_req *set_mod_filter_req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the DBG_SET_MOD_FILTER_REQ message */ + set_mod_filter_req = + ecrnx_msg_zalloc(DBG_SET_MOD_FILTER_REQ, TASK_DBG, DRV_TASK_ID, + sizeof(struct dbg_set_mod_filter_req)); + if (!set_mod_filter_req) + return -ENOMEM; + + /* Set parameters for the DBG_SET_MOD_FILTER_REQ message */ + set_mod_filter_req->mod_filter = filter; + + /* Send the DBG_SET_MOD_FILTER_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, set_mod_filter_req, 1, DBG_SET_MOD_FILTER_CFM, NULL); +} + +int ecrnx_send_dbg_set_sev_filter_req(struct ecrnx_hw *ecrnx_hw, u32 filter) +{ + struct dbg_set_sev_filter_req *set_sev_filter_req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the DBG_SET_SEV_FILTER_REQ message */ + set_sev_filter_req = + ecrnx_msg_zalloc(DBG_SET_SEV_FILTER_REQ, TASK_DBG, DRV_TASK_ID, + sizeof(struct dbg_set_sev_filter_req)); + if (!set_sev_filter_req) + return -ENOMEM; + + /* Set parameters for the DBG_SET_SEV_FILTER_REQ message */ + set_sev_filter_req->sev_filter = filter; + + /* Send the DBG_SET_SEV_FILTER_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, set_sev_filter_req, 1, DBG_SET_SEV_FILTER_CFM, NULL); +} + +int ecrnx_send_dbg_get_sys_stat_req(struct ecrnx_hw *ecrnx_hw, + struct dbg_get_sys_stat_cfm *cfm) +{ + void *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Allocate the message */ + req = ecrnx_msg_zalloc(DBG_GET_SYS_STAT_REQ, TASK_DBG, DRV_TASK_ID, 0); + if (!req) + return -ENOMEM; + + /* Send the DBG_MEM_READ_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 1, DBG_GET_SYS_STAT_CFM, cfm); +} + +int ecrnx_send_cfg_rssi_req(struct ecrnx_hw *ecrnx_hw, u8 vif_index, int rssi_thold, u32 rssi_hyst) +{ + struct mm_cfg_rssi_req *req; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_CFG_RSSI_REQ message */ + req = ecrnx_msg_zalloc(MM_CFG_RSSI_REQ, TASK_MM, DRV_TASK_ID, + sizeof(struct mm_cfg_rssi_req)); + if (!req) + return -ENOMEM; + + /* Set parameters for the MM_CFG_RSSI_REQ message */ + req->vif_index = vif_index; + req->rssi_thold = (s8)rssi_thold; + req->rssi_hyst = (u8)rssi_hyst; + + /* Send the MM_CFG_RSSI_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, req, 0, 0, NULL); +} + +extern bool set_gain; +int ecrnx_send_set_gain_delta_req(struct ecrnx_hw *ecrnx_hw) +{ + s8_l *delta; + + if (set_gain != true) + return -ENOMEM; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Build the MM_SET_GAIN_DELTA_REQ message */ + delta = ecrnx_msg_zalloc(MM_SET_GAIN_DELTA_REQ, TASK_MM, DRV_TASK_ID, + GAIN_DELTA_CFG_BUF_SIZE); + if (!delta) + return -ENOMEM; + + /* Set parameters for the MM_SET_GAIN_DELTA_REQ message */ + memset(delta, 0, GAIN_DELTA_CFG_BUF_SIZE); + memcpy(delta, gain_delta, GAIN_DELTA_CFG_BUF_SIZE); + + /* Send the MM_SET_GAIN_DELTA_REQ message to LMAC FW */ + return ecrnx_send_msg(ecrnx_hw, delta, 0, MM_SET_GAIN_DELTA_CFM, NULL); +} + +int ecrnx_send_cal_result_get_req(struct ecrnx_hw *ecrnx_hw, void *cfm) +{ + void *void_param; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + /* calibration result get REQ has no parameter */ + void_param = ecrnx_msg_zalloc(MM_GET_CAL_RESULT_REQ, TASK_MM, DRV_TASK_ID, 0); + if (!void_param) + return -ENOMEM; + + return ecrnx_send_msg(ecrnx_hw, void_param, 1, MM_GET_CAL_RESULT_CFM, cfm); +} + + diff --git a/drivers/net/wireless/eswin/ecrnx_msg_tx.h b/drivers/net/wireless/eswin/ecrnx_msg_tx.h new file mode 100644 index 000000000000..fc1946b9eff4 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_msg_tx.h @@ -0,0 +1,198 @@ +/** + **************************************************************************************** + * + * @file ecrnx_msg_tx.h + * + * @brief TX function declarations + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ + +#ifndef _ECRNX_MSG_TX_H_ +#define _ECRNX_MSG_TX_H_ + +#include "ecrnx_defs.h" + +int ecrnx_send_reset(struct ecrnx_hw *ecrnx_hw); +int ecrnx_send_start(struct ecrnx_hw *ecrnx_hw); +int ecrnx_send_version_req(struct ecrnx_hw *ecrnx_hw, struct mm_version_cfm *cfm); +int ecrnx_send_add_if(struct ecrnx_hw *ecrnx_hw, const unsigned char *mac, + enum nl80211_iftype iftype, bool p2p, struct mm_add_if_cfm *cfm); +int ecrnx_send_remove_if(struct ecrnx_hw *ecrnx_hw, u8 vif_index); +int ecrnx_send_set_channel(struct ecrnx_hw *ecrnx_hw, int phy_idx, + struct mm_set_channel_cfm *cfm); +int ecrnx_send_key_add(struct ecrnx_hw *ecrnx_hw, u8 vif_idx, u8 sta_idx, bool pairwise, + u8 *key, u8 key_len, u8 key_idx, u8 cipher_suite, + struct mm_key_add_cfm *cfm); +int ecrnx_send_key_del(struct ecrnx_hw *ecrnx_hw, uint8_t hw_key_idx); +int ecrnx_send_bcn_change(struct ecrnx_hw *ecrnx_hw, u8 vif_idx, dma_addr_t bcn_addr, + u16 bcn_len, u16 tim_oft, u16 tim_len, u16 *csa_oft); +int ecrnx_send_tim_update(struct ecrnx_hw *ecrnx_hw, u8 vif_idx, u16 aid, + u8 tx_status); +int ecrnx_send_roc(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + struct ieee80211_channel *chan, unsigned int duration); +int ecrnx_send_cancel_roc(struct ecrnx_hw *ecrnx_hw); +int ecrnx_send_set_power(struct ecrnx_hw *ecrnx_hw, u8 vif_idx, s8 pwr, + struct mm_set_power_cfm *cfm); +int ecrnx_send_set_edca(struct ecrnx_hw *ecrnx_hw, u8 hw_queue, u32 param, + bool uapsd, u8 inst_nbr); +int ecrnx_send_tdls_chan_switch_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + struct ecrnx_sta *ecrnx_sta, bool sta_initiator, + u8 oper_class, struct cfg80211_chan_def *chandef, + struct tdls_chan_switch_cfm *cfm); +int ecrnx_send_tdls_cancel_chan_switch_req(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_vif *ecrnx_vif, + struct ecrnx_sta *ecrnx_sta, + struct tdls_cancel_chan_switch_cfm *cfm); + +#ifdef CONFIG_ECRNX_P2P_DEBUGFS +int ecrnx_send_p2p_oppps_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + u8 ctw, struct mm_set_p2p_oppps_cfm *cfm); +int ecrnx_send_p2p_noa_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + int count, int interval, int duration, + bool dyn_noa, struct mm_set_p2p_noa_cfm *cfm); +#endif /* CONFIG_ECRNX_P2P_DEBUGFS */ + +#ifdef CONFIG_ECRNX_SOFTMAC +int ecrnx_send_sta_add(struct ecrnx_hw *ecrnx_hw, struct ieee80211_sta *sta, + u8 inst_nbr, struct mm_sta_add_cfm *cfm); +int ecrnx_send_sta_del(struct ecrnx_hw *ecrnx_hw, u8 sta_idx); +int ecrnx_send_set_filter(struct ecrnx_hw *ecrnx_hw, uint32_t filter); +int ecrnx_send_add_chanctx(struct ecrnx_hw *ecrnx_hw, + struct ieee80211_chanctx_conf *ctx, + struct mm_chan_ctxt_add_cfm *cfm); +int ecrnx_send_del_chanctx(struct ecrnx_hw *ecrnx_hw, u8 index); +int ecrnx_send_link_chanctx(struct ecrnx_hw *ecrnx_hw, u8 vif_idx, u8 chan_idx, + u8 chan_switch); +int ecrnx_send_unlink_chanctx(struct ecrnx_hw *ecrnx_hw, u8 vif_idx); +int ecrnx_send_update_chanctx(struct ecrnx_hw *ecrnx_hw, + struct ieee80211_chanctx_conf *ctx); +int ecrnx_send_sched_chanctx(struct ecrnx_hw *ecrnx_hw, u8 vif_idx, u8 chan_idx, + u8 type); + +int ecrnx_send_dtim_req(struct ecrnx_hw *ecrnx_hw, u8 dtim_period); +int ecrnx_send_set_br(struct ecrnx_hw *ecrnx_hw, u32 basic_rates, u8 vif_idx, u8 band); +int ecrnx_send_set_beacon_int(struct ecrnx_hw *ecrnx_hw, u16 beacon_int, u8 vif_idx); +int ecrnx_send_set_bssid(struct ecrnx_hw *ecrnx_hw, const u8 *bssid, u8 vif_idx); +int ecrnx_send_set_vif_state(struct ecrnx_hw *ecrnx_hw, bool active, + u16 aid, u8 vif_idx); +int ecrnx_send_set_mode(struct ecrnx_hw *ecrnx_hw, u8 abgmode); +int ecrnx_send_set_idle(struct ecrnx_hw *ecrnx_hw, int idle); +int ecrnx_send_set_ps_mode(struct ecrnx_hw *ecrnx_hw, u8 ps_mode); +int ecrnx_send_set_ps_options(struct ecrnx_hw *ecrnx_hw, bool listen_bcmc, + u16 listen_interval, u8 vif_idx); +int ecrnx_send_set_slottime(struct ecrnx_hw *ecrnx_hw, int use_short_slot); +int ecrnx_send_ba_add(struct ecrnx_hw *ecrnx_hw, uint8_t type, uint8_t sta_idx, + u16 tid, uint8_t bufsz, uint16_t ssn, + struct mm_ba_add_cfm *cfm); +int ecrnx_send_ba_del(struct ecrnx_hw *ecrnx_hw, uint8_t sta_idx, u16 tid, + struct mm_ba_del_cfm *cfm); +int ecrnx_send_scan_req(struct ecrnx_hw *ecrnx_hw, struct ieee80211_vif *vif, + struct cfg80211_scan_request *param, + struct scan_start_cfm *cfm); +int ecrnx_send_scan_cancel_req(struct ecrnx_hw *ecrnx_hw, + struct scan_cancel_cfm *cfm); +void ecrnx_send_tdls_ps(struct ecrnx_hw *ecrnx_hw, bool ps_mode); +#endif /* CONFIG_ECRNX_SOFTMAC */ + +#ifdef CONFIG_ECRNX_FULLMAC +int ecrnx_send_me_config_req(struct ecrnx_hw *ecrnx_hw); +int ecrnx_send_me_chan_config_req(struct ecrnx_hw *ecrnx_hw); +int ecrnx_send_me_set_control_port_req(struct ecrnx_hw *ecrnx_hw, bool opened, + u8 sta_idx); +int ecrnx_send_me_sta_add(struct ecrnx_hw *ecrnx_hw, struct station_parameters *params, + const u8 *mac, u8 inst_nbr, struct me_sta_add_cfm *cfm); +int ecrnx_send_me_sta_del(struct ecrnx_hw *ecrnx_hw, u8 sta_idx, bool tdls_sta); +int ecrnx_send_me_traffic_ind(struct ecrnx_hw *ecrnx_hw, u8 sta_idx, bool uapsd, u8 tx_status); +int ecrnx_send_twt_request(struct ecrnx_hw *ecrnx_hw, + u8 setup_type, u8 vif_idx, + struct twt_conf_tag *conf, + struct twt_setup_cfm *cfm); +int ecrnx_send_twt_teardown(struct ecrnx_hw *ecrnx_hw, + struct twt_teardown_req *twt_teardown, + struct twt_teardown_cfm *cfm); +int ecrnx_send_me_rc_stats(struct ecrnx_hw *ecrnx_hw, u8 sta_idx, + struct me_rc_stats_cfm *cfm); +int ecrnx_send_me_rc_set_rate(struct ecrnx_hw *ecrnx_hw, + u8 sta_idx, + u16 rate_idx); +int ecrnx_send_me_set_ps_mode(struct ecrnx_hw *ecrnx_hw, u8 ps_mode); +int ecrnx_send_sm_connect_req(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_vif *ecrnx_vif, + struct cfg80211_connect_params *sme, + struct sm_connect_cfm *cfm); +int ecrnx_send_sm_disconnect_req(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_vif *ecrnx_vif, + u16 reason); +int ecrnx_send_sm_external_auth_required_rsp(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_vif *ecrnx_vif, + u16 status); +int ecrnx_send_sm_ft_auth_rsp(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + uint8_t *ie, int ie_len); +int ecrnx_send_apm_start_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + struct cfg80211_ap_settings *settings, + struct apm_start_cfm *cfm, + struct ecrnx_ipc_elem_var *elem); +int ecrnx_send_apm_stop_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif); +int ecrnx_send_apm_probe_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + struct ecrnx_sta *sta, struct apm_probe_client_cfm *cfm); +int ecrnx_send_scanu_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + struct cfg80211_scan_request *param); +int ecrnx_send_scanu_cancel_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif); +int ecrnx_send_apm_start_cac_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + struct cfg80211_chan_def *chandef, + struct apm_start_cac_cfm *cfm); +int ecrnx_send_apm_stop_cac_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif); +int ecrnx_send_tdls_peer_traffic_ind_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif); +int ecrnx_send_config_monitor_req(struct ecrnx_hw *ecrnx_hw, + struct cfg80211_chan_def *chandef, + struct me_config_monitor_cfm *cfm); +int ecrnx_send_mesh_start_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + const struct mesh_config *conf, const struct mesh_setup *setup, + struct mesh_start_cfm *cfm); +int ecrnx_send_mesh_stop_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + struct mesh_stop_cfm *cfm); +int ecrnx_send_mesh_update_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + u32 mask, const struct mesh_config *p_mconf, struct mesh_update_cfm *cfm); +int ecrnx_send_mesh_peer_info_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + u8 sta_idx, struct mesh_peer_info_cfm *cfm); +void ecrnx_send_mesh_peer_update_ntf(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, + u8 sta_idx, u8 mlink_state); +void ecrnx_send_mesh_path_create_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, u8 *tgt_addr); +int ecrnx_send_mesh_path_update_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, const u8 *tgt_addr, + const u8 *p_nhop_addr, struct mesh_path_update_cfm *cfm); +void ecrnx_send_mesh_proxy_add_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *vif, u8 *ext_addr); +#if defined(CONFIG_ECRNX_P2P) +int ecrnx_send_p2p_start_listen_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, unsigned int duration); +int ecrnx_send_p2p_cancel_listen_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif); +#endif +#endif /* CONFIG_ECRNX_FULLMAC */ + +#ifdef CONFIG_ECRNX_BFMER +#ifdef CONFIG_ECRNX_SOFTMAC +void ecrnx_send_bfmer_enable(struct ecrnx_hw *ecrnx_hw, struct ieee80211_sta *sta); +#else +void ecrnx_send_bfmer_enable(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *ecrnx_sta, + const struct ieee80211_vht_cap *vht_cap); +#endif /* CONFIG_ECRNX_SOFTMAC*/ +#ifdef CONFIG_ECRNX_MUMIMO_TX +int ecrnx_send_mu_group_update_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *ecrnx_sta); +#endif /* CONFIG_ECRNX_MUMIMO_TX */ +#endif /* CONFIG_ECRNX_BFMER */ + +/* Debug messages */ +int ecrnx_send_dbg_trigger_req(struct ecrnx_hw *ecrnx_hw, char *msg); +int ecrnx_send_dbg_mem_read_req(struct ecrnx_hw *ecrnx_hw, u32 mem_addr, + struct dbg_mem_read_cfm *cfm); +int ecrnx_send_dbg_mem_write_req(struct ecrnx_hw *ecrnx_hw, u32 mem_addr, + u32 mem_data); +int ecrnx_send_dbg_set_mod_filter_req(struct ecrnx_hw *ecrnx_hw, u32 filter); +int ecrnx_send_dbg_set_sev_filter_req(struct ecrnx_hw *ecrnx_hw, u32 filter); +int ecrnx_send_dbg_get_sys_stat_req(struct ecrnx_hw *ecrnx_hw, + struct dbg_get_sys_stat_cfm *cfm); +int ecrnx_send_cfg_rssi_req(struct ecrnx_hw *ecrnx_hw, u8 vif_index, int rssi_thold, u32 rssi_hyst); +int ecrnx_send_set_gain_delta_req(struct ecrnx_hw *ecrnx_hw); +int ecrnx_send_cal_result_get_req(struct ecrnx_hw *ecrnx_hw, void *cfm); +#endif /* _ECRNX_MSG_TX_H_ */ diff --git a/drivers/net/wireless/eswin/ecrnx_mu_group.c b/drivers/net/wireless/eswin/ecrnx_mu_group.c new file mode 100644 index 000000000000..2725cbd04c2d --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_mu_group.c @@ -0,0 +1,659 @@ +/** + ****************************************************************************** + * + * @file ecrnx_mu_group.c + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#include "ecrnx_defs.h" +#include "ecrnx_msg_tx.h" +#include "ecrnx_events.h" + + +/** + * ecrnx_mu_group_sta_init - Initialize group information for a STA + * + * @sta: Sta to initialize + */ +void ecrnx_mu_group_sta_init(struct ecrnx_sta *sta, + const struct ieee80211_vht_cap *vht_cap) +{ + sta->group_info.map = 0; + sta->group_info.cnt = 0; + sta->group_info.active.next = LIST_POISON1; + sta->group_info.update.next = LIST_POISON1; + sta->group_info.last_update = 0; + sta->group_info.traffic = 0; + sta->group_info.group = 0; + + if (!vht_cap || + !(vht_cap->vht_cap_info & IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE)) { + sta->group_info.map = ECRNX_SU_GROUP; + } +} + +/** + * ecrnx_mu_group_sta_del - Remove a sta from all MU group + * + * @ecrnx_hw: main driver data + * @sta: STA to remove + * + * Remove one sta from all the MU groups it belongs to. + */ +void ecrnx_mu_group_sta_del(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta) +{ + struct ecrnx_mu_info *mu = &ecrnx_hw->mu; + int i, j, group_id; + bool lock_taken; + u64 map; + + lock_taken = (down_interruptible(&mu->lock) == 0); + + group_sta_for_each(sta, group_id, map) { + struct ecrnx_mu_group *group = ecrnx_mu_group_from_id(mu, group_id); + + for (i = 0; i < CONFIG_USER_MAX; i++) { + if (group->users[i] == sta) { + group->users[i] = NULL; + group->user_cnt --; + /* Don't keep group with only one user */ + if (group->user_cnt == 1) { + for (j = 0; j < CONFIG_USER_MAX; j++) { + if (group->users[j]) { + group->users[j]->group_info.cnt--; + group->users[j]->group_info.map &= ~BIT_ULL(group->group_id); + if (group->users[j]->group_info.group == group_id) + group->users[j]->group_info.group = 0; + group->user_cnt --; + break; + } + } + mu->group_cnt--; + trace_mu_group_delete(group->group_id); + } else { + trace_mu_group_update(group); + } + break; + } + } + + WARN((i == CONFIG_USER_MAX), "sta %d doesn't belongs to group %d", + sta->sta_idx, group_id); + } + + sta->group_info.map = 0; + sta->group_info.cnt = 0; + sta->group_info.traffic = 0; + + if (sta->group_info.active.next != LIST_POISON1) + list_del(&sta->group_info.active); + + if (sta->group_info.update.next != LIST_POISON1) + list_del(&sta->group_info.update); + + if (lock_taken) + up(&mu->lock); +} + +/** + * ecrnx_mu_group_sta_get_map - Get the list of group a STA belongs to + * + * @sta: pointer to the sta + * + * @return the list of group a STA belongs to as a bitfield + */ +u64 ecrnx_mu_group_sta_get_map(struct ecrnx_sta *sta) +{ + if (sta) + return sta->group_info.map; + return 0; +} + +/** + * ecrnx_mu_group_sta_get_pos - Get sta position in a group + * + * @ecrnx_hw: main driver data + * @sta: pointer to the sta + * @group_id: Group id + * + * @return the positon of @sta in group @group_id or -1 if the sta + * doesn't belongs to the group (or group id is invalid) + */ +int ecrnx_mu_group_sta_get_pos(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta, + int group_id) +{ + struct ecrnx_mu_group *group; + int i; + + group = ecrnx_mu_group_from_id(&ecrnx_hw->mu, group_id); + if (!group) + return -1; + + for (i = 0; i < CONFIG_USER_MAX; i++) { + if (group->users[i] == sta) + return i; + } + + WARN(1, "sta %d doesn't belongs to group %d", + sta->sta_idx, group_id); + return -1; +} + +/** + * ecrnx_mu_group_move_head - Move (or add) one element at the top of a list + * + * @list: list pointer + * @elem: element to move (or add) at the top of @list + * + */ +static inline +void ecrnx_mu_group_move_head(struct list_head *list, struct list_head *elem) +{ + if (elem->next != LIST_POISON1) { + __list_del_entry(elem); + } + list_add(elem, list); +} + +/** + * ecrnx_mu_group_remove_users - Remove all the users of a group + * + * @mu: pointer on MU info + * @group: pointer on group to remove users from + * + * Loop over all users one one group and remove this group from their + * map (and count). + * Each users is also added to the update_sta list, so that group info + * will be resent to fw for this user. + */ +static inline +void ecrnx_mu_group_remove_users(struct ecrnx_mu_info *mu, + struct ecrnx_mu_group *group) +{ + struct ecrnx_sta *sta; + int i, group_id = group->group_id; + + for (i = 0; i < CONFIG_USER_MAX; i++) { + if (group->users[i]) { + sta = group->users[i]; + group->users[i] = NULL; + sta->group_info.cnt--; + sta->group_info.map &= ~BIT_ULL(group_id); + ecrnx_mu_group_move_head(&mu->update_sta, + &sta->group_info.update); + } + } + + if (group->user_cnt) + mu->group_cnt--; + group->user_cnt = 0; + trace_mu_group_delete(group_id); +} + +/** + * ecrnx_mu_group_add_users - Add users to a group + * + * @mu: pointer on MU info + * @group: pointer on group to add users in + * @nb_user: number of users to ad + * @users: table of user to add + * + * Add @nb_users to @group (which may already have users) + * Each new users is added to the first free position. + * It is assume that @group has at least @nb_user free position. If it is not + * case it only add the number of users needed to complete the group. + * Each users (effectively added to @group) is also added to the update_sta + * list, so that group info will be resent to fw for this user. + */ +static inline +void ecrnx_mu_group_add_users(struct ecrnx_mu_info *mu, + struct ecrnx_mu_group *group, + int nb_user, struct ecrnx_sta **users) +{ + int i, j, group_id = group->group_id; + + if (!group->user_cnt) + mu->group_cnt++; + + j = 0; + for (i = 0; i < nb_user ; i++) { + for (; j < CONFIG_USER_MAX ; j++) { + if (group->users[j] == NULL) { + group->users[j] = users[i]; + users[i]->group_info.cnt ++; + users[i]->group_info.map |= BIT_ULL(group_id); + + ecrnx_mu_group_move_head(&(mu->update_sta), + &(users[i]->group_info.update)); + group->user_cnt ++; + j ++; + break; + } + + WARN(j == (CONFIG_USER_MAX - 1), + "Too many user for group %d (nb_user=%d)", + group_id, group->user_cnt + nb_user - i); + } + } + + trace_mu_group_update(group); +} + + +/** + * ecrnx_mu_group_create_one - create on group with a specific group of user + * + * @mu: pointer on MU info + * @nb_user: number of user to include in the group (<= CONFIG_USER_MAX) + * @users: table of users + * + * Try to create a new group with a specific group of users. + * 1- First it checks if a group containing all this users already exists. + * + * 2- Then it checks if it is possible to complete a group which already + * contains at least one user. + * + * 3- Finally it create a new group. To do so, it take take the last group of + * the active_groups list, remove all its current users and add the new ones + * + * In all cases, the group selected is moved at the top of the active_groups + * list + * + * @return 1 if a new group has been created and 0 otherwise + */ +static +int ecrnx_mu_group_create_one(struct ecrnx_mu_info *mu, int nb_user, + struct ecrnx_sta **users, int *nb_group_left) +{ + int i, group_id; + struct ecrnx_mu_group *group; + u64 group_match; + u64 group_avail; + + group_match = users[0]->group_info.map; + group_avail = users[0]->group_info.map; + for (i = 1; i < nb_user ; i++) { + group_match &= users[i]->group_info.map; + group_avail |= users[i]->group_info.map; + + } + + if (group_match) { + /* a group (or more) with all the users already exist */ + group_id = ECRNX_GET_FIRST_GROUP_ID(group_match); + group = ecrnx_mu_group_from_id(mu, group_id); + ecrnx_mu_group_move_head(&mu->active_groups, &group->list); + return 0; + } + +#if CONFIG_USER_MAX > 2 + if (group_avail) { + /* check if we can complete a group */ + struct ecrnx_sta *users2[CONFIG_USER_MAX]; + int nb_user2; + + group_for_each(group_id, group_avail) { + group = ecrnx_mu_group_from_id(mu, group_id); + if (group->user_cnt == CONFIG_USER_MAX) + continue; + + nb_user2 = 0; + for (i = 0; i < nb_user ; i++) { + if (!(users[i]->group_info.map & BIT_ULL(group_id))) { + users2[nb_user2] = users[i]; + nb_user2++; + } + } + + if ((group->user_cnt + nb_user2) <= CONFIG_USER_MAX) { + ecrnx_mu_group_add_users(mu, group, nb_user2, users2); + ecrnx_mu_group_move_head(&mu->active_groups, &group->list); + return 0; + } + } + } +#endif /* CONFIG_USER_MAX > 2*/ + + /* create a new group */ + group = list_last_entry(&mu->active_groups, struct ecrnx_mu_group, list); + ecrnx_mu_group_remove_users(mu, group); + ecrnx_mu_group_add_users(mu, group, nb_user, users); + ecrnx_mu_group_move_head(&mu->active_groups, &group->list); + (*nb_group_left)--; + + return 1; +} + +/** + * ecrnx_mu_group_create - Create new groups containing one specific sta + * + * @mu: pointer on MU info + * @sta: sta to add in each group + * @nb_group_left: maximum number to new group allowed. (updated on exit) + * + * This will try to create "all the possible" group with a specific sta being + * a member of all these group. + * The function simply loops over the @active_sta list (starting from @sta). + * When it has (CONFIG_USER_MAX - 1) users it try to create a new group with + * these users (plus @sta). + * Loops end when there is no more users, or no more new group is allowed + * + */ +static +void ecrnx_mu_group_create(struct ecrnx_mu_info *mu, struct ecrnx_sta *sta, + int *nb_group_left) +{ + struct ecrnx_sta *user_sta = sta; + struct ecrnx_sta *users[CONFIG_USER_MAX]; + int nb_user = 1; + + users[0] = sta; + while (*nb_group_left) { + + list_for_each_entry_continue(user_sta, &mu->active_sta, group_info.active) { + users[nb_user] = user_sta; + if (++nb_user == CONFIG_USER_MAX) { + break; + } + } + + if (nb_user > 1) { + if (ecrnx_mu_group_create_one(mu, nb_user, users, nb_group_left)) + (*nb_group_left)--; + + if (nb_user < CONFIG_USER_MAX) + break; + else + nb_user = 1; + } else + break; + } +} + +/** + * ecrnx_mu_group_work - process function of the "group_work" + * + * The work is scheduled when several sta (MU beamformee capable) are active. + * When called, the @active_sta contains the list of the active sta (starting + * from the most recent one), and @active_groups is the list of all possible + * groups ordered so that the first one is the most recently used. + * + * This function will create new groups, starting from group containing the + * most "active" sta. + * For example if the list of sta is : + * sta8 -> sta3 -> sta4 -> sta7 -> sta1 + * and the number of user per group is 3, it will create grooups : + * - sta8 / sta3 / sta4 + * - sta8 / sta7 / sta1 + * - sta3 / sta4 / sta7 + * - sta3 / sta1 + * - sta4 / sta7 / sta1 + * - sta7 / sta1 + * + * To create new group, the least used group are first selected. + * It is only allowed to create NX_MU_GROUP_MAX per iteration. + * + * Once groups have been updated, mu group information is update to the fw. + * To do so it use the @update_sta list to know which sta has been affected. + * As it is necessary to wait for fw confirmation before using this new group + * MU is temporarily disabled during group update + * + * Work is then rescheduled. + * + * At the end of the function, both @active_sta and @update_sta list are empty. + * + * Note: + * - This is still a WIP, and will require more tuning + * - not all combinations are created, to avoid to much processing. + * - reschedule delay should be adaptative + */ +void ecrnx_mu_group_work(struct work_struct *ws) +{ + struct delayed_work *dw = container_of(ws, struct delayed_work, work); + struct ecrnx_mu_info *mu = container_of(dw, struct ecrnx_mu_info, group_work); + struct ecrnx_hw *ecrnx_hw = container_of(mu, struct ecrnx_hw, mu); + struct ecrnx_sta *sta, *next; + int nb_group_left = NX_MU_GROUP_MAX; + + if (WARN(!ecrnx_hw->mod_params->mutx, + "In group formation work, but mutx disabled")) + return; + + if (down_interruptible(&mu->lock) != 0) + return; + + mu->update_count++; + if (!mu->update_count) + mu->update_count++; + + list_for_each_entry_safe(sta, next, &mu->active_sta, group_info.active) { + if (nb_group_left) + ecrnx_mu_group_create(mu, sta, &nb_group_left); + + sta->group_info.last_update = mu->update_count; + list_del(&sta->group_info.active); + } + + if (! list_empty(&mu->update_sta)) { + list_for_each_entry_safe(sta, next, &mu->update_sta, group_info.update) { + ecrnx_send_mu_group_update_req(ecrnx_hw, sta); + list_del(&sta->group_info.update); + } + } + + mu->next_group_select = jiffies; + ecrnx_mu_group_sta_select(ecrnx_hw); + up(&mu->lock); + + return; +} + +/** + * ecrnx_mu_group_init - Initialize MU groups + * + * @ecrnx_hw: main driver data + * + * Initialize all MU group + */ +void ecrnx_mu_group_init(struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_mu_info *mu = &ecrnx_hw->mu; + int i; + + INIT_LIST_HEAD(&mu->active_groups); + INIT_LIST_HEAD(&mu->active_sta); + INIT_LIST_HEAD(&mu->update_sta); + + for (i = 0; i < NX_MU_GROUP_MAX; i++) { + int j; + mu->groups[i].user_cnt = 0; + mu->groups[i].group_id = i + 1; + for (j = 0; j < CONFIG_USER_MAX; j++) { + mu->groups[i].users[j] = NULL; + } + list_add(&mu->groups[i].list, &mu->active_groups); + } + + mu->update_count = 1; + mu->group_cnt = 0; + mu->next_group_select = jiffies; + INIT_DELAYED_WORK(&mu->group_work, ecrnx_mu_group_work); + sema_init(&mu->lock, 1); +} + +/** + * ecrnx_mu_set_active_sta - mark a STA as active + * + * @ecrnx_hw: main driver data + * @sta: pointer to the sta + * @traffic: Number of buffers to add in the sta's traffic counter + * + * If @sta is MU beamformee capable (and MU-MIMO tx is enabled) move the + * sta at the top of the @active_sta list. + * It also schedule the group_work if not already scheduled and the list + * contains more than one sta. + * + * If a STA was already in the list during the last group update + * (i.e. sta->group_info.last_update == mu->update_count) it is not added + * back to the list until a sta that wasn't active during the last update is + * added. This is to avoid scheduling group update with a list of sta that + * were all already in the list during previous update. + * + * It is called with mu->lock taken. + */ +void ecrnx_mu_set_active_sta(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta, + int traffic) +{ + struct ecrnx_mu_info *mu = &ecrnx_hw->mu; + + if (!sta || (sta->group_info.map & ECRNX_SU_GROUP)) + return; + + sta->group_info.traffic += traffic; + + if ((sta->group_info.last_update != mu->update_count) || + !list_empty(&mu->active_sta)) { + + ecrnx_mu_group_move_head(&mu->active_sta, &sta->group_info.active); + + if (!delayed_work_pending(&mu->group_work) && + !list_is_singular(&mu->active_sta)) { + schedule_delayed_work(&mu->group_work, + msecs_to_jiffies(ECRNX_MU_GROUP_INTERVAL)); + } + } +} + +/** + * ecrnx_mu_set_active_group - mark a MU group as active + * + * @ecrnx_hw: main driver data + * @group_id: Group id + * + * move a group at the top of the @active_groups list + */ +void ecrnx_mu_set_active_group(struct ecrnx_hw *ecrnx_hw, int group_id) +{ + struct ecrnx_mu_info *mu = &ecrnx_hw->mu; + struct ecrnx_mu_group *group = ecrnx_mu_group_from_id(mu, group_id); + + ecrnx_mu_group_move_head(&mu->active_groups, &group->list); +} + + +/** + * ecrnx_mu_group_sta_select - Select the best group for MU stas + * + * @ecrnx_hw: main driver data + * + * For each MU capable client of AP interfaces this function tries to select + * the best group to use. + * + * In first pass, gather information from all stations to form statistics + * for each group for the previous @ECRNX_MU_GROUP_SELECT_INTERVAL interval: + * - number of buffers transmitted + * - number of user + * + * Then groups with more than 2 active users, are assigned after being ordered + * by traffic : + * - group with highest traffic is selected: set this group for all its users + * - update nb_users for all others group (as one sta may be in several groups) + * - select the next group that have still mor than 2 users and assign it. + * - continue until all group are processed + * + */ +void ecrnx_mu_group_sta_select(struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_mu_info *mu = &ecrnx_hw->mu; + int nb_users[NX_MU_GROUP_MAX + 1]; + int traffic[NX_MU_GROUP_MAX + 1]; + int order[NX_MU_GROUP_MAX + 1]; + struct ecrnx_sta *sta; + struct ecrnx_vif *vif; + struct list_head *head; + u64 map; + int i, j, update, group_id, tmp, cnt = 0; + + if (!mu->group_cnt || time_before(jiffies, mu->next_group_select)) + return; + + list_for_each_entry(vif, &ecrnx_hw->vifs, list) { + + if (ECRNX_VIF_TYPE(vif) != NL80211_IFTYPE_AP) + continue; + +#ifdef CONFIG_ECRNX_FULLMAC + head = &vif->ap.sta_list; +#else + head = &vif->stations; +#endif /* CONFIG_ECRNX_FULLMAC */ + + memset(nb_users, 0, sizeof(nb_users)); + memset(traffic, 0, sizeof(traffic)); + list_for_each_entry(sta, head, list) { + int sta_traffic = sta->group_info.traffic; + + /* reset statistics for next selection */ + sta->group_info.traffic = 0; + if (sta->group_info.group) + trace_mu_group_selection(sta, 0); + sta->group_info.group = 0; + + if (sta->group_info.cnt == 0 || + sta_traffic < ECRNX_MU_GROUP_MIN_TRAFFIC) + continue; + + group_sta_for_each(sta, group_id, map) { + nb_users[group_id]++; + traffic[group_id] += sta_traffic; + + /* list group with 2 users or more */ + if (nb_users[group_id] == 2) + order[cnt++] = group_id; + } + } + + /* reorder list of group with more that 2 users */ + update = 1; + while(update) { + update = 0; + for (i = 0; i < cnt - 1; i++) { + if (traffic[order[i]] < traffic[order[i + 1]]) { + tmp = order[i]; + order[i] = order[i + 1]; + order[i + 1] = tmp; + update = 1; + } + } + } + + /* now assign group in traffic order */ + for (i = 0; i < cnt ; i ++) { + struct ecrnx_mu_group *group; + group_id = order[i]; + + if (nb_users[group_id] < 2) + continue; + + group = ecrnx_mu_group_from_id(mu, group_id); + for (j = 0; j < CONFIG_USER_MAX ; j++) { + if (group->users[j]) { + trace_mu_group_selection(group->users[j], group_id); + group->users[j]->group_info.group = group_id; + + group_sta_for_each(group->users[j], tmp, map) { + if (group_id != tmp) + nb_users[tmp]--; + } + } + } + } + } + + mu->next_group_select = jiffies + + msecs_to_jiffies(ECRNX_MU_GROUP_SELECT_INTERVAL); + mu->next_group_select |= 1; +} diff --git a/drivers/net/wireless/eswin/ecrnx_mu_group.h b/drivers/net/wireless/eswin/ecrnx_mu_group.h new file mode 100644 index 000000000000..1251986dbc29 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_mu_group.h @@ -0,0 +1,179 @@ +/** + ****************************************************************************** + * + * @file ecrnx_mu_group.h + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ +#ifndef _ECRNX_MU_GROUP_H_ +#define _ECRNX_MU_GROUP_H_ + +#include <linux/workqueue.h> +#include <linux/semaphore.h> + +struct ecrnx_hw; +struct ecrnx_sta; + +#ifdef CONFIG_ECRNX_MUMIMO_TX + +/** + * struct ecrnx_sta_group_info - Group Information for a STA + * + * @active: node for @mu->active_sta list + * @update: node for @mu->update_sta list + * @cnt: Number of groups the STA belongs to + * @map: Bitfield of groups the sta belongs to + * @traffic: Number of buffers sent since previous group selection + * @group: Id of the group selected by previous group selection + * (cf @ecrnx_mu_group_sta_select) + */ +struct ecrnx_sta_group_info { + struct list_head active; + struct list_head update; + u16 last_update; + int cnt; + u64 map; + int traffic; + u8 group; +}; + +/** + * struct mu_group_info - Information about the users of a group + * + * @list: node for mu->active_groups + * @group_id: Group identifier + * @user_cnt: Number of the users in the group + * @users: Pointer to the sta, ordered by user position + */ +struct ecrnx_mu_group { + struct list_head list; + int group_id; + int user_cnt; + struct ecrnx_sta *users[CONFIG_USER_MAX]; +}; + +/** + * struct ecrnx_mu_info - Information about all MU group + * + * @active_groups: List of all possible groups. Ordered from the most recently + * used one to the least one (and possibly never used) + * @active_sta: List of MU beamformee sta that have been active (since previous + * group update). Ordered from the most recently active. + * @update_sta: List of sta whose group information has changed and need to be + * updated at fw level + * @groups: Table of all groups + * @group_work: Work item used to schedule group update + * @update_count: Counter used to identify the last group formation update. + * (cf ecrnx_sta_group_info.last_update) + * @lock: Lock taken during group update. If tx happens lock is taken, then tx + * will not used MU. + * @next_group_assign: Next time the group selection should be run + * (ref @ecrnx_mu_group_sta_select) + * @group_cnt: Number of group created + */ +struct ecrnx_mu_info { + struct list_head active_groups; + struct list_head active_sta; + struct list_head update_sta; + struct ecrnx_mu_group groups[NX_MU_GROUP_MAX]; + struct delayed_work group_work; + u16 update_count; + struct semaphore lock; + unsigned long next_group_select; + u8 group_cnt; +}; + +#define ECRNX_SU_GROUP BIT_ULL(0) +#define ECRNX_MU_GROUP_MASK 0x7ffffffffffffffeULL +#define ECRNX_MU_GROUP_INTERVAL 200 /* in ms */ +#define ECRNX_MU_GROUP_SELECT_INTERVAL 100 /* in ms */ +// minimum traffic in a ECRNX_MU_GROUP_SELECT_INTERVAL to consider the sta +#define ECRNX_MU_GROUP_MIN_TRAFFIC 50 /* in number of packet */ + + +#define ECRNX_GET_FIRST_GROUP_ID(map) (fls64(map) - 1) + +#define group_sta_for_each(sta, id, map) \ + map = sta->group_info.map & ECRNX_MU_GROUP_MASK; \ + for (id = (fls64(map) - 1) ; id > 0 ; \ + map &= ~(u64)BIT_ULL(id), id = (fls64(map) - 1)) + +#define group_for_each(id, map) \ + for (id = (fls64(map) - 1) ; id > 0 ; \ + map &= ~(u64)BIT_ULL(id), id = (fls64(map) - 1)) + +#define ECRNX_MUMIMO_INFO_POS_ID(info) (((info) >> 6) & 0x3) +#define ECRNX_MUMIMO_INFO_GROUP_ID(info) ((info) & 0x3f) + +static inline +struct ecrnx_mu_group *ecrnx_mu_group_from_id(struct ecrnx_mu_info *mu, int id) +{ + if (id > NX_MU_GROUP_MAX) + return NULL; + + return &mu->groups[id - 1]; +} + + +void ecrnx_mu_group_sta_init(struct ecrnx_sta *sta, + const struct ieee80211_vht_cap *vht_cap); +void ecrnx_mu_group_sta_del(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta); +u64 ecrnx_mu_group_sta_get_map(struct ecrnx_sta *sta); +int ecrnx_mu_group_sta_get_pos(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta, + int group_id); + +void ecrnx_mu_group_init(struct ecrnx_hw *ecrnx_hw); + +void ecrnx_mu_set_active_sta(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta, + int traffic); +void ecrnx_mu_set_active_group(struct ecrnx_hw *ecrnx_hw, int group_id); +void ecrnx_mu_group_sta_select(struct ecrnx_hw *ecrnx_hw); + + +#else /* ! CONFIG_ECRNX_MUMIMO_TX */ + +static inline +void ecrnx_mu_group_sta_init(struct ecrnx_sta *sta, + const struct ieee80211_vht_cap *vht_cap) +{} + +static inline +void ecrnx_mu_group_sta_del(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta) +{} + +static inline +u64 ecrnx_mu_group_sta_get_map(struct ecrnx_sta *sta) +{ + return 0; +} + +static inline +int ecrnx_mu_group_sta_get_pos(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta, + int group_id) +{ + return 0; +} + +static inline +void ecrnx_mu_group_init(struct ecrnx_hw *ecrnx_hw) +{} + +static inline +void ecrnx_mu_set_active_sta(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta, + int traffic) +{} + +static inline +void ecrnx_mu_set_active_group(struct ecrnx_hw *ecrnx_hw, int group_id) +{} + +static inline +void ecrnx_mu_group_sta_select(struct ecrnx_hw *ecrnx_hw) +{} + +#endif /* CONFIG_ECRNX_MUMIMO_TX */ + +#endif /* _ECRNX_MU_GROUP_H_ */ + diff --git a/drivers/net/wireless/eswin/ecrnx_platform.c b/drivers/net/wireless/eswin/ecrnx_platform.c new file mode 100644 index 000000000000..76295c238105 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_platform.c @@ -0,0 +1,1107 @@ +/** + ****************************************************************************** + * + * @file ecrnx_platform.c + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#include <linux/module.h> +#include <linux/firmware.h> +#include <linux/delay.h> + +#include "ecrnx_platform.h" +#include "reg_access.h" +#include "hal_desc.h" +#include "ecrnx_main.h" + +#ifndef CONFIG_ECRNX_ESWIN +#include "ecrnx_pci.h" +#ifndef CONFIG_ECRNX_FHOST +#include "ipc_host.h" +#endif /* !CONFIG_ECRNX_FHOST */ +#endif /* CONFIG_ECRNX_ESWIN */ + +#if defined(CONFIG_ECRNX_ESWIN_SDIO) +#include "sdio.h" +#include "ecrnx_sdio.h" +#elif defined(CONFIG_ECRNX_ESWIN_USB) +#include "usb.h" +#include "ecrnx_usb.h" +#endif +#ifdef CONFIG_ECRNX_WIFO_CAIL +#include "core.h" +#include "ecrnx_amt.h" +#endif + +#ifdef CONFIG_ECRNX_TL4 +/** + * ecrnx_plat_tl4_fw_upload() - Load the requested FW into embedded side. + * + * @ecrnx_plat: pointer to platform structure + * @fw_addr: Virtual address where the fw must be loaded + * @filename: Name of the fw. + * + * Load a fw, stored as a hex file, into the specified address + */ +static int ecrnx_plat_tl4_fw_upload(struct ecrnx_plat *ecrnx_plat, u8* fw_addr, + char *filename) +{ + struct device *dev = ecrnx_platform_get_dev(ecrnx_plat); + const struct firmware *fw; + int err = 0; + u32 *dst; + u8 const *file_data; + char typ0, typ1; + u32 addr0, addr1; + u32 dat0, dat1; + int remain; + + err = request_firmware(&fw, filename, dev); + if (err) { + return err; + } + file_data = fw->data; + remain = fw->size; + + /* Copy the file on the Embedded side */ + dev_dbg(dev, "\n### Now copy %s firmware, @ = %p\n", filename, fw_addr); + + /* Walk through all the lines of the configuration file */ + while (remain >= 16) { + u32 data, offset; + + if (sscanf(file_data, "%c:%08X %04X", &typ0, &addr0, &dat0) != 3) + break; + if ((addr0 & 0x01) != 0) { + addr0 = addr0 - 1; + dat0 = 0; + } else { + file_data += 16; + remain -= 16; + } + if ((remain < 16) || + (sscanf(file_data, "%c:%08X %04X", &typ1, &addr1, &dat1) != 3) || + (typ1 != typ0) || (addr1 != (addr0 + 1))) { + typ1 = typ0; + addr1 = addr0 + 1; + dat1 = 0; + } else { + file_data += 16; + remain -= 16; + } + + if (typ0 == 'C') { + offset = 0x00200000; + if ((addr1 % 4) == 3) + offset += 2*(addr1 - 3); + else + offset += 2*(addr1 + 1); + + data = dat1 | (dat0 << 16); + } else { + offset = 2*(addr1 - 1); + data = dat0 | (dat1 << 16); + } + dst = (u32 *)(fw_addr + offset); + *dst = data; + } + + release_firmware(fw); + + return err; +} +#endif + +/** + * ecrnx_plat_bin_fw_upload() - Load the requested binary FW into embedded side. + * + * @ecrnx_plat: pointer to platform structure + * @fw_addr: Virtual address where the fw must be loaded + * @filename: Name of the fw. + * + * Load a fw, stored as a binary file, into the specified address + */ +#ifndef CONFIG_ECRNX_ESWIN +static int ecrnx_plat_bin_fw_upload(struct ecrnx_plat *ecrnx_plat, u8* fw_addr, + char *filename) +{ + const struct firmware *fw; + struct device *dev = ecrnx_platform_get_dev(ecrnx_plat); + int err = 0; + unsigned int i, size; + u32 *src, *dst; + + err = request_firmware(&fw, filename, dev); + if (err) { + return err; + } + + /* Copy the file on the Embedded side */ + dev_dbg(dev, "\n### Now copy %s firmware, @ = %p\n", filename, fw_addr); + + src = (u32 *)fw->data; + dst = (u32 *)fw_addr; + size = (unsigned int)fw->size; + + /* check potential platform bug on multiple stores vs memcpy */ + for (i = 0; i < size; i += 4) { + *dst++ = *src++; + } + + release_firmware(fw); + + return err; +} +#endif + +#ifndef CONFIG_ECRNX_TL4 +#define IHEX_REC_DATA 0 +#define IHEX_REC_EOF 1 +#define IHEX_REC_EXT_SEG_ADD 2 +#define IHEX_REC_START_SEG_ADD 3 +#define IHEX_REC_EXT_LIN_ADD 4 +#define IHEX_REC_START_LIN_ADD 5 + +/** + * ecrnx_plat_ihex_fw_upload() - Load the requested intel hex 8 FW into embedded side. + * + * @ecrnx_plat: pointer to platform structure + * @fw_addr: Virtual address where the fw must be loaded + * @filename: Name of the fw. + * + * Load a fw, stored as a ihex file, into the specified address. + */ +#ifndef CONFIG_ECRNX_ESWIN +static int ecrnx_plat_ihex_fw_upload(struct ecrnx_plat *ecrnx_plat, u8* fw_addr, + char *filename) +{ + const struct firmware *fw; + struct device *dev = ecrnx_platform_get_dev(ecrnx_plat); + u8 const *src, *end; + u32 *dst; + u16 haddr, segaddr, addr; + u32 hwaddr; + u8 load_fw, byte_count, checksum, csum, rec_type; + int err, rec_idx; + char hex_buff[9]; + + err = request_firmware(&fw, filename, dev); + if (err) { + return err; + } + + /* Copy the file on the Embedded side */ + dev_dbg(dev, "\n### Now copy %s firmware, @ = %p\n", filename, fw_addr); + + src = fw->data; + end = src + (unsigned int)fw->size; + haddr = 0; + segaddr = 0; + load_fw = 1; + err = -EINVAL; + rec_idx = 0; + hwaddr = 0; + +#define IHEX_READ8(_val, _cs) { \ + hex_buff[2] = 0; \ + strncpy(hex_buff, src, 2); \ + if (kstrtou8(hex_buff, 16, &_val)) \ + goto end; \ + src += 2; \ + if (_cs) \ + csum += _val; \ + } + +#define IHEX_READ16(_val) { \ + hex_buff[4] = 0; \ + strncpy(hex_buff, src, 4); \ + if (kstrtou16(hex_buff, 16, &_val)) \ + goto end; \ + src += 4; \ + csum += (_val & 0xff) + (_val >> 8); \ + } + +#define IHEX_READ32(_val) { \ + hex_buff[8] = 0; \ + strncpy(hex_buff, src, 8); \ + if (kstrtouint(hex_buff, 16, &_val)) \ + goto end; \ + src += 8; \ + csum += (_val & 0xff) + ((_val >> 8) & 0xff) + \ + ((_val >> 16) & 0xff) + (_val >> 24); \ + } + +#define IHEX_READ32_PAD(_val, _nb) { \ + memset(hex_buff, '0', 8); \ + hex_buff[8] = 0; \ + strncpy(hex_buff, src, (2 * _nb)); \ + if (kstrtouint(hex_buff, 16, &_val)) \ + goto end; \ + src += (2 * _nb); \ + csum += (_val & 0xff) + ((_val >> 8) & 0xff) + \ + ((_val >> 16) & 0xff) + (_val >> 24); \ +} + + /* loop until end of file is read*/ + while (load_fw) { + rec_idx++; + csum = 0; + + /* Find next colon start code */ + while (*src != ':') { + src++; + if ((src + 3) >= end) /* 3 = : + rec_len */ + goto end; + } + src++; + + /* Read record len */ + IHEX_READ8(byte_count, 1); + if ((src + (byte_count * 2) + 8) >= end) /* 8 = rec_addr + rec_type + chksum */ + goto end; + + /* Read record addr */ + IHEX_READ16(addr); + + /* Read record type */ + IHEX_READ8(rec_type, 1); + + switch(rec_type) { + case IHEX_REC_DATA: + { + /* Update destination address */ + dst = (u32 *) (fw_addr + hwaddr + addr); + + while (byte_count) { + u32 val; + if (byte_count >= 4) { + IHEX_READ32(val); + byte_count -= 4; + } else { + IHEX_READ32_PAD(val, byte_count); + byte_count = 0; + } + *dst++ = __swab32(val); + } + break; + } + case IHEX_REC_EOF: + { + load_fw = 0; + err = 0; + break; + } + case IHEX_REC_EXT_SEG_ADD: /* Extended Segment Address */ + { + IHEX_READ16(segaddr); + hwaddr = (haddr << 16) + (segaddr << 4); + break; + } + case IHEX_REC_EXT_LIN_ADD: /* Extended Linear Address */ + { + IHEX_READ16(haddr); + hwaddr = (haddr << 16) + (segaddr << 4); + break; + } + case IHEX_REC_START_LIN_ADD: /* Start Linear Address */ + { + u32 val; + IHEX_READ32(val); /* need to read for checksum */ + break; + } + case IHEX_REC_START_SEG_ADD: + default: + { + dev_err(dev, "ihex: record type %d not supported\n", rec_type); + load_fw = 0; + } + } + + /* Read and compare checksum */ + IHEX_READ8(checksum, 0); + if (checksum != (u8)(~csum + 1)) + goto end; + } + +#undef IHEX_READ8 +#undef IHEX_READ16 +#undef IHEX_READ32 +#undef IHEX_READ32_PAD + + end: + release_firmware(fw); + + if (err) + dev_err(dev, "%s: Invalid ihex record around line %d\n", filename, rec_idx); + + return err; +} +#endif /* CONFIG_ECRNX_TL4 */ +#endif + +#ifndef CONFIG_ECRNX_ESWIN +#ifndef CONFIG_ECRNX_SDM +/** + * ecrnx_plat_get_rf() - Retrun the RF used in the platform + * + * @ecrnx_plat: pointer to platform structure + */ +static u32 ecrnx_plat_get_rf(struct ecrnx_plat *ecrnx_plat) +{ + u32 ver; + ver = ECRNX_REG_READ(ecrnx_plat, ECRNX_ADDR_SYSTEM, MDM_HDMCONFIG_ADDR); + + ver = __MDM_PHYCFG_FROM_VERS(ver); + WARN(((ver != MDM_PHY_CONFIG_TRIDENT) && + (ver != MDM_PHY_CONFIG_CATAXIA) && + (ver != MDM_PHY_CONFIG_KARST)), + "Unknown PHY version 0x%08x\n", ver); + + return ver; +} +#endif + +/** + * ecrnx_plat_get_clkctrl_addr() - Return the clock control register address + * + * @ecrnx_plat: platform data + */ +#ifndef CONFIG_ECRNX_SDM +#ifndef CONFIG_ECRNX_ESWIN +static u32 ecrnx_plat_get_clkctrl_addr(struct ecrnx_plat *ecrnx_plat) +{ + u32 regval; + if (ecrnx_plat_get_rf(ecrnx_plat) == MDM_PHY_CONFIG_TRIDENT) + return MDM_MEMCLKCTRL0_ADDR; + regval = ECRNX_REG_READ(ecrnx_plat, ECRNX_ADDR_SYSTEM, SYSCTRL_SIGNATURE_ADDR); + if (__FPGA_TYPE(regval) == 0xC0CA) + return CRM_CLKGATEFCTRL0_ADDR; + else + return MDM_CLKGATEFCTRL0_ADDR; +} +#endif /* CONFIG_ECRNX_SDM */ +#endif + +/** + * ecrnx_plat_stop_agcfsm() - Stop a AGC state machine + * + * @ecrnx_plat: pointer to platform structure + * @agg_reg: Address of the agccntl register (within ECRNX_ADDR_SYSTEM) + * @agcctl: Updated with value of the agccntl rgister before stop + * @memclk: Updated with value of the clock register before stop + * @agc_ver: Version of the AGC load procedure + * @clkctrladdr: Indicates which AGC clock register should be accessed + */ +#ifndef CONFIG_ECRNX_ESWIN +static void ecrnx_plat_stop_agcfsm(struct ecrnx_plat *ecrnx_plat, int agc_reg, + u32 *agcctl, u32 *memclk, u8 agc_ver, + u32 clkctrladdr) +{ + /* First read agcctnl and clock registers */ + *memclk = ECRNX_REG_READ(ecrnx_plat, ECRNX_ADDR_SYSTEM, clkctrladdr); + + /* Stop state machine : xxAGCCNTL0[AGCFSMRESET]=1 */ + *agcctl = ECRNX_REG_READ(ecrnx_plat, ECRNX_ADDR_SYSTEM, agc_reg); + ECRNX_REG_WRITE((*agcctl) | BIT(12), ecrnx_plat, ECRNX_ADDR_SYSTEM, agc_reg); + + /* Force clock */ + if (agc_ver > 0) { + /* CLKGATEFCTRL0[AGCCLKFORCE]=1 */ + ECRNX_REG_WRITE((*memclk) | BIT(29), ecrnx_plat, ECRNX_ADDR_SYSTEM, + clkctrladdr); + } else { + /* MEMCLKCTRL0[AGCMEMCLKCTRL]=0 */ + ECRNX_REG_WRITE((*memclk) & ~BIT(3), ecrnx_plat, ECRNX_ADDR_SYSTEM, + clkctrladdr); + } +} +#endif + +/** + * ecrnx_plat_start_agcfsm() - Restart a AGC state machine + * + * @ecrnx_plat: pointer to platform structure + * @agg_reg: Address of the agccntl register (within ECRNX_ADDR_SYSTEM) + * @agcctl: value of the agccntl register to restore + * @memclk: value of the clock register to restore + * @agc_ver: Version of the AGC load procedure + * @clkctrladdr: Indicates which AGC clock register should be accessed + */ +#ifndef CONFIG_ECRNX_ESWIN +static void ecrnx_plat_start_agcfsm(struct ecrnx_plat *ecrnx_plat, int agc_reg, + u32 agcctl, u32 memclk, u8 agc_ver, + u32 clkctrladdr) +{ + + /* Release clock */ + if (agc_ver > 0) + /* CLKGATEFCTRL0[AGCCLKFORCE]=0 */ + ECRNX_REG_WRITE(memclk & ~BIT(29), ecrnx_plat, ECRNX_ADDR_SYSTEM, + clkctrladdr); + else + /* MEMCLKCTRL0[AGCMEMCLKCTRL]=1 */ + ECRNX_REG_WRITE(memclk | BIT(3), ecrnx_plat, ECRNX_ADDR_SYSTEM, + clkctrladdr); + + /* Restart state machine: xxAGCCNTL0[AGCFSMRESET]=0 */ + ECRNX_REG_WRITE(agcctl & ~BIT(12), ecrnx_plat, ECRNX_ADDR_SYSTEM, agc_reg); +} +#endif +#endif + +/** + * ecrnx_plat_get_agc_load_version() - Return the agc load protocol version and the + * address of the clock control register + * + * @ecrnx_plat: platform data + * @rf: rf in used + * @clkctrladdr: returned clock control register address + * + */ +#ifndef CONFIG_ECRNX_ESWIN +#ifndef CONFIG_ECRNX_SDM +static u8 ecrnx_plat_get_agc_load_version(struct ecrnx_plat *ecrnx_plat, u32 rf, + u32 *clkctrladdr) +{ + u8 agc_load_ver = 0; + u32 agc_ver; + u32 regval; + + *clkctrladdr = ecrnx_plat_get_clkctrl_addr(ecrnx_plat); + /* Trident and Elma PHY use old method */ + if (rf == MDM_PHY_CONFIG_TRIDENT) + return 0; + + /* Get the FPGA signature */ + regval = ECRNX_REG_READ(ecrnx_plat, ECRNX_ADDR_SYSTEM, SYSCTRL_SIGNATURE_ADDR); + + + /* Read RIU version register */ + agc_ver = ECRNX_REG_READ(ecrnx_plat, ECRNX_ADDR_SYSTEM, RIU_ECRNXVERSION_ADDR); + agc_load_ver = __RIU_AGCLOAD_FROM_VERS(agc_ver); + + return agc_load_ver; +} +#endif /* CONFIG_ECRNX_SDM */ +#endif + +/** + * ecrnx_plat_agc_load() - Load AGC ucode + * + * @ecrnx_plat: platform data + */ +#ifndef CONFIG_ECRNX_ESWIN +static int ecrnx_plat_agc_load(struct ecrnx_plat *ecrnx_plat) +{ + int ret = 0; +#ifndef CONFIG_ECRNX_SDM + u32 agc = 0, agcctl, memclk; + u32 clkctrladdr; + u32 rf = ecrnx_plat_get_rf(ecrnx_plat); + u8 agc_ver; + + switch (rf) { + case MDM_PHY_CONFIG_TRIDENT: + agc = AGC_ECRNXAGCCNTL_ADDR; + break; + case MDM_PHY_CONFIG_CATAXIA: + case MDM_PHY_CONFIG_KARST: + agc = RIU_ECRNXAGCCNTL_ADDR; + break; + default: + return -1; + } + + agc_ver = ecrnx_plat_get_agc_load_version(ecrnx_plat, rf, &clkctrladdr); + + ecrnx_plat_stop_agcfsm(ecrnx_plat, agc, &agcctl, &memclk, agc_ver, clkctrladdr); + + ret = ecrnx_plat_bin_fw_upload(ecrnx_plat, + ECRNX_ADDR(ecrnx_plat, ECRNX_ADDR_SYSTEM, PHY_AGC_UCODE_ADDR), + ECRNX_AGC_FW_NAME); + + if (!ret && (agc_ver == 1)) { + /* Run BIST to ensure that the AGC RAM was correctly loaded */ + ECRNX_REG_WRITE(BIT(28), ecrnx_plat, ECRNX_ADDR_SYSTEM, + RIU_ECRNXDYNAMICCONFIG_ADDR); + while (ECRNX_REG_READ(ecrnx_plat, ECRNX_ADDR_SYSTEM, + RIU_ECRNXDYNAMICCONFIG_ADDR) & BIT(28)); + + if (!(ECRNX_REG_READ(ecrnx_plat, ECRNX_ADDR_SYSTEM, + RIU_AGCMEMBISTSTAT_ADDR) & BIT(0))) { + dev_err(ecrnx_platform_get_dev(ecrnx_plat), + "AGC RAM not loaded correctly 0x%08x\n", + ECRNX_REG_READ(ecrnx_plat, ECRNX_ADDR_SYSTEM, + RIU_AGCMEMSIGNATURESTAT_ADDR)); + ret = -EIO; + } + } + + ecrnx_plat_start_agcfsm(ecrnx_plat, agc, agcctl, memclk, agc_ver, clkctrladdr); + +#endif + return ret; +} +#endif + +/** + * ecrnx_ldpc_load() - Load LDPC RAM + * + * @ecrnx_hw: Main driver data + */ +#ifndef CONFIG_ECRNX_ESWIN +static int ecrnx_ldpc_load(struct ecrnx_hw *ecrnx_hw) +{ +#ifndef CONFIG_ECRNX_SDM + struct ecrnx_plat *ecrnx_plat = ecrnx_hw->plat; + u32 rf = ecrnx_plat_get_rf(ecrnx_plat); + u32 phy_feat = ECRNX_REG_READ(ecrnx_plat, ECRNX_ADDR_SYSTEM, MDM_HDMCONFIG_ADDR); + u32 phy_vers = ECRNX_REG_READ(ecrnx_plat, ECRNX_ADDR_SYSTEM, MDM_HDMVERSION_ADDR); + + if (((rf != MDM_PHY_CONFIG_KARST) && (rf != MDM_PHY_CONFIG_CATAXIA)) || + (phy_feat & (MDM_LDPCDEC_BIT | MDM_LDPCENC_BIT)) != + (MDM_LDPCDEC_BIT | MDM_LDPCENC_BIT)) { + goto disable_ldpc; + } + if (__MDM_VERSION(phy_vers) > 30) { + return 0; + } + + if (ecrnx_plat_bin_fw_upload(ecrnx_plat, + ECRNX_ADDR(ecrnx_plat, ECRNX_ADDR_SYSTEM, PHY_LDPC_RAM_ADDR), + ECRNX_LDPC_RAM_NAME)) { + goto disable_ldpc; + } + + return 0; + + disable_ldpc: + ecrnx_hw->mod_params->ldpc_on = false; + +#endif /* CONFIG_ECRNX_SDM */ + return 0; +} +#endif +/** + * ecrnx_plat_lmac_load() - Load FW code + * + * @ecrnx_plat: platform data + */ +#ifndef CONFIG_ECRNX_ESWIN +static int ecrnx_plat_lmac_load(struct ecrnx_plat *ecrnx_plat) +{ + int ret; + + #ifdef CONFIG_ECRNX_TL4 + ret = ecrnx_plat_tl4_fw_upload(ecrnx_plat, + ECRNX_ADDR(ecrnx_plat, ECRNX_ADDR_CPU, RAM_LMAC_FW_ADDR), + ECRNX_MAC_FW_NAME); + #else + ret = ecrnx_plat_ihex_fw_upload(ecrnx_plat, + ECRNX_ADDR(ecrnx_plat, ECRNX_ADDR_CPU, RAM_LMAC_FW_ADDR), + ECRNX_MAC_FW_NAME); + if (ret == -ENOENT) + { + ret = ecrnx_plat_bin_fw_upload(ecrnx_plat, + ECRNX_ADDR(ecrnx_plat, ECRNX_ADDR_CPU, RAM_LMAC_FW_ADDR), + ECRNX_MAC_FW_NAME2); + } + #endif + + return ret; +} +#endif + +/** + * ecrnx_rf_fw_load() - Load RF FW if any + * + * @ecrnx_hw: Main driver data + */ +#ifndef CONFIG_ECRNX_ESWIN +static int ecrnx_plat_rf_fw_load(struct ecrnx_hw *ecrnx_hw) +{ +#ifndef CONFIG_ECRNX_SDM + struct ecrnx_plat *ecrnx_plat = ecrnx_hw->plat; + u32 rf = ecrnx_plat_get_rf(ecrnx_plat); + struct device *dev = ecrnx_platform_get_dev(ecrnx_plat); + const struct firmware *fw; + int err = 0; + u8 const *file_data; + int remain; + u32 clkforce; + u32 clkctrladdr; + + // Today only Cataxia has a FW to load + if (rf != MDM_PHY_CONFIG_CATAXIA) + return 0; + + err = request_firmware(&fw, ECRNX_CATAXIA_FW_NAME, dev); + if (err) + { + dev_err(dev, "Make sure your board has up-to-date packages."); + dev_err(dev, "Run \"sudo smart update\" \"sudo smart upgrade\" commands.\n"); + return err; + } + + file_data = fw->data; + remain = fw->size; + + // Get address of clock control register + clkctrladdr = ecrnx_plat_get_clkctrl_addr(ecrnx_plat); + + // Force RC clock + clkforce = ECRNX_REG_READ(ecrnx_plat, ECRNX_ADDR_SYSTEM, clkctrladdr); + ECRNX_REG_WRITE(clkforce | BIT(27), ecrnx_plat, ECRNX_ADDR_SYSTEM, clkctrladdr); + mdelay(1); + + // Reset RC + ECRNX_REG_WRITE(0x00003100, ecrnx_plat, ECRNX_ADDR_SYSTEM, RC_SYSTEM_CONFIGURATION_ADDR); + mdelay(20); + + // Reset RF + ECRNX_REG_WRITE(0x00133100, ecrnx_plat, ECRNX_ADDR_SYSTEM, RC_SYSTEM_CONFIGURATION_ADDR); + mdelay(20); + + // Select trx 2 HB + ECRNX_REG_WRITE(0x00103100, ecrnx_plat, ECRNX_ADDR_SYSTEM, RC_SYSTEM_CONFIGURATION_ADDR); + mdelay(1); + + // Set ASP freeze + ECRNX_REG_WRITE(0xC1010001, ecrnx_plat, ECRNX_ADDR_SYSTEM, RC_ACCES_TO_CATAXIA_REG_ADDR); + mdelay(1); + + /* Walk through all the lines of the FW file */ + while (remain >= 10) { + u32 data; + + if (sscanf(file_data, "0x%08X", &data) != 1) + { + // Corrupted FW file + err = -1; + break; + } + file_data += 11; + remain -= 11; + + ECRNX_REG_WRITE(data, ecrnx_plat, ECRNX_ADDR_SYSTEM, RC_ACCES_TO_CATAXIA_REG_ADDR); + udelay(50); + } + + // Clear ASP freeze + ECRNX_REG_WRITE(0xE0010011, ecrnx_plat, ECRNX_ADDR_SYSTEM, RC_ACCES_TO_CATAXIA_REG_ADDR); + mdelay(1); + + // Unforce RC clock + ECRNX_REG_WRITE(clkforce, ecrnx_plat, ECRNX_ADDR_SYSTEM, clkctrladdr); + + release_firmware(fw); + +#endif /* CONFIG_ECRNX_SDM */ + return err; +} +#endif + +/** + * ecrnx_plat_mpif_sel() - Select the MPIF according to the FPGA signature + * + * @ecrnx_plat: platform data + */ +#ifndef CONFIG_ECRNX_ESWIN +static void ecrnx_plat_mpif_sel(struct ecrnx_plat *ecrnx_plat) +{ +#ifndef CONFIG_ECRNX_SDM + u32 regval; + u32 type; + + /* Get the FPGA signature */ + regval = ECRNX_REG_READ(ecrnx_plat, ECRNX_ADDR_SYSTEM, SYSCTRL_SIGNATURE_ADDR); + type = __FPGA_TYPE(regval); + + /* Check if we need to switch to the old MPIF or not */ + if ((type != 0xCAFE) && (type != 0XC0CA) && (regval & 0xF) < 0x3) + { + /* A old FPGA A is used, so configure the FPGA B to use the old MPIF */ + ECRNX_REG_WRITE(0x3, ecrnx_plat, ECRNX_ADDR_SYSTEM, FPGAB_MPIF_SEL_ADDR); + } +#endif +} +#endif + +/** + * ecrnx_platform_reset() - Reset the platform + * + * @ecrnx_plat: platform data + */ +#ifndef CONFIG_ECRNX_ESWIN +static int ecrnx_platform_reset(struct ecrnx_plat *ecrnx_plat) +{ + u32 regval; + + /* the doc states that SOFT implies FPGA_B_RESET + * adding FPGA_B_RESET is clearer */ + ECRNX_REG_WRITE(SOFT_RESET | FPGA_B_RESET, ecrnx_plat, + ECRNX_ADDR_SYSTEM, SYSCTRL_MISC_CNTL_ADDR); + msleep(100); + + regval = ECRNX_REG_READ(ecrnx_plat, ECRNX_ADDR_SYSTEM, SYSCTRL_MISC_CNTL_ADDR); + + if (regval & SOFT_RESET) { + dev_err(ecrnx_platform_get_dev(ecrnx_plat), "reset: failed\n"); + return -EIO; + } + + ECRNX_REG_WRITE(regval & ~FPGA_B_RESET, ecrnx_plat, + ECRNX_ADDR_SYSTEM, SYSCTRL_MISC_CNTL_ADDR); + msleep(100); + return 0; +} +#endif + +/** + * rwmx_platform_save_config() - Save hardware config before reload + * + * @ecrnx_plat: Pointer to platform data + * + * Return configuration registers values. + */ +#ifndef CONFIG_ECRNX_ESWIN +static void* ecrnx_term_save_config(struct ecrnx_plat *ecrnx_plat) +{ + const u32 *reg_list; + u32 *reg_value, *res; + int i, size = 0; + + if (ecrnx_plat->get_config_reg) { + size = ecrnx_plat->get_config_reg(ecrnx_plat, ®_list); + } + + if (size <= 0) + return NULL; + + res = kmalloc(sizeof(u32) * size, GFP_KERNEL); + if (!res) + return NULL; + + reg_value = res; + for (i = 0; i < size; i++) { + *reg_value++ = ECRNX_REG_READ(ecrnx_plat, ECRNX_ADDR_SYSTEM, + *reg_list++); + } + + return res; +} +#endif + +/** + * rwmx_platform_restore_config() - Restore hardware config after reload + * + * @ecrnx_plat: Pointer to platform data + * @reg_value: Pointer of value to restore + * (obtained with rwmx_platform_save_config()) + * + * Restore configuration registers value. + */ +#ifndef CONFIG_ECRNX_ESWIN +static void ecrnx_term_restore_config(struct ecrnx_plat *ecrnx_plat, + u32 *reg_value) +{ + const u32 *reg_list; + int i, size = 0; + + if (!reg_value || !ecrnx_plat->get_config_reg) + return; + + size = ecrnx_plat->get_config_reg(ecrnx_plat, ®_list); + + for (i = 0; i < size; i++) { + ECRNX_REG_WRITE(*reg_value++, ecrnx_plat, ECRNX_ADDR_SYSTEM, + *reg_list++); + } +} +#endif + +#ifndef CONFIG_ECRNX_ESWIN +static int ecrnx_check_fw_compatibility(struct ecrnx_hw *ecrnx_hw) +{ + int res = 0; + + struct ipc_shared_env_tag *shared = ecrnx_hw->ipc_env->shared; + #ifdef CONFIG_ECRNX_SOFTMAC + struct wiphy *wiphy = ecrnx_hw->hw->wiphy; + #else //CONFIG_ECRNX_SOFTMAC + struct wiphy *wiphy = ecrnx_hw->wiphy; + #endif //CONFIG_ECRNX_SOFTMAC + #ifdef CONFIG_ECRNX_OLD_IPC + int ipc_shared_version = 10; + #else //CONFIG_ECRNX_OLD_IPC + int ipc_shared_version = 11; + #endif //CONFIG_ECRNX_OLD_IPC + + if(shared->comp_info.ipc_shared_version != ipc_shared_version) + { + wiphy_err(wiphy, "Different versions of IPC shared version between driver and FW (%d != %d)\n ", + ipc_shared_version, shared->comp_info.ipc_shared_version); + res = -1; + } + + if(shared->comp_info.radarbuf_cnt != IPC_RADARBUF_CNT) + { + wiphy_err(wiphy, "Different number of host buffers available for Radar events handling "\ + "between driver and FW (%d != %d)\n", IPC_RADARBUF_CNT, + shared->comp_info.radarbuf_cnt); + res = -1; + } + + if(shared->comp_info.unsuprxvecbuf_cnt != IPC_UNSUPRXVECBUF_CNT) + { + wiphy_err(wiphy, "Different number of host buffers available for unsupported Rx vectors "\ + "handling between driver and FW (%d != %d)\n", IPC_UNSUPRXVECBUF_CNT, + shared->comp_info.unsuprxvecbuf_cnt); + res = -1; + } + + #ifdef CONFIG_ECRNX_FULLMAC + if(shared->comp_info.rxdesc_cnt != IPC_RXDESC_CNT) + { + wiphy_err(wiphy, "Different number of shared descriptors available for Data RX handling "\ + "between driver and FW (%d != %d)\n", IPC_RXDESC_CNT, + shared->comp_info.rxdesc_cnt); + res = -1; + } + #endif /* CONFIG_ECRNX_FULLMAC */ + + if(shared->comp_info.rxbuf_cnt != IPC_RXBUF_CNT) + { + wiphy_err(wiphy, "Different number of host buffers available for Data Rx handling "\ + "between driver and FW (%d != %d)\n", IPC_RXBUF_CNT, + shared->comp_info.rxbuf_cnt); + res = -1; + } + + if(shared->comp_info.msge2a_buf_cnt != IPC_MSGE2A_BUF_CNT) + { + wiphy_err(wiphy, "Different number of host buffers available for Emb->App MSGs "\ + "sending between driver and FW (%d != %d)\n", IPC_MSGE2A_BUF_CNT, + shared->comp_info.msge2a_buf_cnt); + res = -1; + } + + if(shared->comp_info.dbgbuf_cnt != IPC_DBGBUF_CNT) + { + wiphy_err(wiphy, "Different number of host buffers available for debug messages "\ + "sending between driver and FW (%d != %d)\n", IPC_DBGBUF_CNT, + shared->comp_info.dbgbuf_cnt); + res = -1; + } + + if(shared->comp_info.bk_txq != NX_TXDESC_CNT0) + { + wiphy_err(wiphy, "Driver and FW have different sizes of BK TX queue (%d != %d)\n", + NX_TXDESC_CNT0, shared->comp_info.bk_txq); + res = -1; + } + + if(shared->comp_info.be_txq != NX_TXDESC_CNT1) + { + wiphy_err(wiphy, "Driver and FW have different sizes of BE TX queue (%d != %d)\n", + NX_TXDESC_CNT1, shared->comp_info.be_txq); + res = -1; + } + + if(shared->comp_info.vi_txq != NX_TXDESC_CNT2) + { + wiphy_err(wiphy, "Driver and FW have different sizes of VI TX queue (%d != %d)\n", + NX_TXDESC_CNT2, shared->comp_info.vi_txq); + res = -1; + } + + if(shared->comp_info.vo_txq != NX_TXDESC_CNT3) + { + wiphy_err(wiphy, "Driver and FW have different sizes of VO TX queue (%d != %d)\n", + NX_TXDESC_CNT3, shared->comp_info.vo_txq); + res = -1; + } + + #if NX_TXQ_CNT == 5 + if(shared->comp_info.bcn_txq != NX_TXDESC_CNT4) + { + wiphy_err(wiphy, "Driver and FW have different sizes of BCN TX queue (%d != %d)\n", + NX_TXDESC_CNT4, shared->comp_info.bcn_txq); + res = -1; + } + #else + if (shared->comp_info.bcn_txq > 0) + { + wiphy_err(wiphy, "BCMC enabled in firmware but disabled in driver\n"); + res = -1; + } + #endif /* NX_TXQ_CNT == 5 */ + + if(shared->comp_info.ipc_shared_size != sizeof(ipc_shared_env)) + { + wiphy_err(wiphy, "Different sizes of IPC shared between driver and FW (%zd != %d)\n", + sizeof(ipc_shared_env), shared->comp_info.ipc_shared_size); + res = -1; + } + + if(shared->comp_info.msg_api != MSG_API_VER) + { + wiphy_err(wiphy, "Different supported message API versions between "\ + "driver and FW (%d != %d)\n", MSG_API_VER, shared->comp_info.msg_api); + res = -1; + } + + return res; +} +#endif /* !CONFIG_ECRNX_ESWIN */ + +/** + * ecrnx_platform_on() - Start the platform + * + * @ecrnx_hw: Main driver data + * @config: Config to restore (NULL if nothing to restore) + * + * It starts the platform : + * - load fw and ucodes + * - initialize IPC + * - boot the fw + * - enable link communication/IRQ + * + * Called by 802.11 part + */ +int ecrnx_platform_on(struct ecrnx_hw *ecrnx_hw, void *config) +{ + u8 *shared_ram; + int ret; + + ECRNX_DBG("%s entry!!", __func__); + shared_ram = kzalloc(sizeof(struct ipc_shared_env_tag), GFP_KERNEL); + if (!shared_ram) + return -ENOMEM; + + if ((ret = ecrnx_ipc_init(ecrnx_hw, shared_ram))) + return ret; + + ECRNX_DBG("%s exit!!", __func__); + return 0; +} + +/** + * ecrnx_platform_off() - Stop the platform + * + * @ecrnx_hw: Main driver data + * @config: Updated with pointer to config, to be able to restore it with + * ecrnx_platform_on(). It's up to the caller to free the config. Set to NULL + * if configuration is not needed. + * + * Called by 802.11 part + */ +void ecrnx_platform_off(struct ecrnx_hw *ecrnx_hw, void **config) +{ + ecrnx_ipc_deinit(ecrnx_hw); +#if defined(CONFIG_ECRNX_ESWIN_SDIO) + ecrnx_sdio_deinit(ecrnx_hw); +#elif defined(CONFIG_ECRNX_ESWIN_USB) + ecrnx_usb_deinit(ecrnx_hw); +#else + #error "config error drv"; +#endif +} + +/** + * ecrnx_platform_init() - Initialize the platform + * + * @ecrnx_plat: platform data (already updated by platform driver) + * @platform_data: Pointer to store the main driver data pointer (aka ecrnx_hw) + * That will be set as driver data for the platform driver + * Return: 0 on success, < 0 otherwise + * + * Called by the platform driver after it has been probed + */ +int ecrnx_platform_init(void *ecrnx_plat, void **platform_data) +{ + ECRNX_DBG(ECRNX_FN_ENTRY_STR); +#if defined CONFIG_ECRNX_SOFTMAC + return ecrnx_mac80211_init(ecrnx_plat, platform_data); +#elif defined CONFIG_ECRNX_FULLMAC +#ifdef CONFIG_ECRNX_WIFO_CAIL + if (amt_mode == true) { + return ecrnx_amt_init(); + } + else +#endif + return ecrnx_cfg80211_init(ecrnx_plat, platform_data); +#elif defined CONFIG_ECRNX_FHOST + return ecrnx_fhost_init(ecrnx_plat, platform_data); +#endif +} + +/** + * ecrnx_platform_deinit() - Deinitialize the platform + * + * @ecrnx_hw: main driver data + * + * Called by the platform driver after it is removed + */ +void ecrnx_platform_deinit(struct ecrnx_hw *ecrnx_hw) +{ + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + +#if defined CONFIG_ECRNX_SOFTMAC + ecrnx_mac80211_deinit(ecrnx_hw); +#elif defined CONFIG_ECRNX_FULLMAC +#ifdef CONFIG_ECRNX_WIFO_CAIL + if (amt_mode == true) { + ecrnx_amt_deinit(); + } + else +#endif + ecrnx_cfg80211_deinit(ecrnx_hw); +#elif defined CONFIG_ECRNX_FHOST + ecrnx_fhost_deinit(ecrnx_hw); +#endif +} + + +/** + * ecrnx_platform_register_drv() - Register all possible platform drivers + */ +int ecrnx_platform_register_drv(void) +{ +#if defined(CONFIG_ECRNX_ESWIN_SDIO) + return ecrnx_sdio_register_drv(); +#elif defined(CONFIG_ECRNX_ESWIN_USB) + return ecrnx_usb_register_drv(); +#else + #error "config error drv" +#endif +} + + +/** + * ecrnx_platform_unregister_drv() - Unegister all platform drivers + */ +void ecrnx_platform_unregister_drv(void) +{ +#if defined(CONFIG_ECRNX_ESWIN_SDIO) + return ecrnx_sdio_unregister_drv(); +#elif defined(CONFIG_ECRNX_ESWIN_USB) + return ecrnx_usb_unregister_drv(); +#else + #error "config error drv" +#endif +} + + +#ifndef CONFIG_ECRNX_SDM +MODULE_FIRMWARE(ECRNX_AGC_FW_NAME); +MODULE_FIRMWARE(ECRNX_FCU_FW_NAME); +MODULE_FIRMWARE(ECRNX_LDPC_RAM_NAME); +#endif +MODULE_FIRMWARE(ECRNX_MAC_FW_NAME); +#ifndef CONFIG_ECRNX_TL4 +MODULE_FIRMWARE(ECRNX_MAC_FW_NAME2); +#endif diff --git a/drivers/net/wireless/eswin/ecrnx_platform.h b/drivers/net/wireless/eswin/ecrnx_platform.h new file mode 100644 index 000000000000..2c84c5a6e3b6 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_platform.h @@ -0,0 +1,142 @@ +/** + ****************************************************************************** + * + * @file ecrnx_platorm.h + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef _ECRNX_PLAT_H_ +#define _ECRNX_PLAT_H_ + +#include <linux/pci.h> + +#define ECRNX_CONFIG_FW_NAME "wifi_ecr6600u.cfg" +#define ECRNX_PHY_CONFIG_TRD_NAME "ecrnx_trident.ini" +#define ECRNX_PHY_CONFIG_KARST_NAME "ecrnx_karst.ini" +#define ECRNX_AGC_FW_NAME "agcram.bin" +#define ECRNX_LDPC_RAM_NAME "ldpcram.bin" +#define ECRNX_CATAXIA_FW_NAME "cataxia.fw" +#ifdef CONFIG_ECRNX_SOFTMAC +#define ECRNX_MAC_FW_BASE_NAME "lmacfw" +#elif defined CONFIG_ECRNX_FULLMAC +#define ECRNX_MAC_FW_BASE_NAME "fmacfw" +#elif defined CONFIG_ECRNX_FHOST +#define ECRNX_MAC_FW_BASE_NAME "fhostfw" +#endif /* CONFIG_ECRNX_SOFTMAC */ + +#ifdef CONFIG_ECRNX_TL4 +#define ECRNX_MAC_FW_NAME ECRNX_MAC_FW_BASE_NAME".hex" +#else +#define ECRNX_MAC_FW_NAME ECRNX_MAC_FW_BASE_NAME".ihex" +#define ECRNX_MAC_FW_NAME2 ECRNX_MAC_FW_BASE_NAME".bin" +#endif + +#define ECRNX_FCU_FW_NAME "fcuram.bin" + +/** + * Type of memory to access (cf ecrnx_plat.get_address) + * + * @ECRNX_ADDR_CPU To access memory of the embedded CPU + * @ECRNX_ADDR_SYSTEM To access memory/registers of one subsystem of the + * embedded system + * + */ +enum ecrnx_platform_addr { + ECRNX_ADDR_CPU, + ECRNX_ADDR_SYSTEM, + ECRNX_ADDR_MAX, +}; + +struct ecrnx_hw; + +/** + * struct ecrnx_plat - Operation pointers for ECRNX PCI platform + * + * @pci_dev: pointer to pci dev + * @enabled: Set if embedded platform has been enabled (i.e. fw loaded and + * ipc started) + * @enable: Configure communication with the fw (i.e. configure the transfers + * enable and register interrupt) + * @disable: Stop communication with the fw + * @deinit: Free all ressources allocated for the embedded platform + * @get_address: Return the virtual address to access the requested address on + * the platform. + * @ack_irq: Acknowledge the irq at link level. + * @get_config_reg: Return the list (size + pointer) of registers to restore in + * order to reload the platform while keeping the current configuration. + * + * @priv Private data for the link driver + */ +struct ecrnx_plat { + struct pci_dev *pci_dev; + bool enabled; + + int (*enable)(struct ecrnx_hw *ecrnx_hw); + int (*disable)(struct ecrnx_hw *ecrnx_hw); + void (*deinit)(struct ecrnx_plat *ecrnx_plat); + u8* (*get_address)(struct ecrnx_plat *ecrnx_plat, int addr_name, + unsigned int offset); + void (*ack_irq)(struct ecrnx_plat *ecrnx_plat); + int (*get_config_reg)(struct ecrnx_plat *ecrnx_plat, const u32 **list); + + u8 priv[0] __aligned(sizeof(void *)); +}; + +#define ECRNX_ADDR(plat, base, offset) \ + plat->get_address(plat, base, offset) + +#define ECRNX_REG_READ(plat, base, offset) \ + readl(plat->get_address(plat, base, offset)) + +#define ECRNX_REG_WRITE(val, plat, base, offset) \ + writel(val, plat->get_address(plat, base, offset)) + +#ifdef CONFIG_ECRNX_ESWIN +int ecrnx_platform_init(void *ecrnx_plat, void **platform_data); +#else +int ecrnx_platform_init(struct ecrnx_plat *ecrnx_plat, void **platform_data); +#endif +void ecrnx_platform_deinit(struct ecrnx_hw *ecrnx_hw); + +int ecrnx_platform_on(struct ecrnx_hw *ecrnx_hw, void *config); +void ecrnx_platform_off(struct ecrnx_hw *ecrnx_hw, void **config); + +int ecrnx_platform_register_drv(void); +void ecrnx_platform_unregister_drv(void); + +#ifndef CONFIG_ECRNX_ESWIN +static inline struct device *ecrnx_platform_get_dev(struct ecrnx_plat *ecrnx_plat) +{ + return &(ecrnx_plat->pci_dev->dev); +} + +static inline unsigned int ecrnx_platform_get_irq(struct ecrnx_plat *ecrnx_plat) +{ + return ecrnx_plat->pci_dev->irq; +} +#else + + +#ifdef CONFIG_ECRNX_ESWIN_SDIO +extern struct device *eswin_sdio_get_dev(void *plat); +static inline struct device *ecrnx_platform_get_dev(void *ecrnx_plat) +{ + return eswin_sdio_get_dev(ecrnx_plat); +} +#endif + + +#ifdef CONFIG_ECRNX_ESWIN_USB +struct device *eswin_usb_get_dev(void *plat); +static inline struct device *ecrnx_platform_get_dev(void *ecrnx_plat) +{ + return eswin_usb_get_dev(ecrnx_plat); +} +#endif + + +#endif +#endif /* _ECRNX_PLAT_H_ */ diff --git a/drivers/net/wireless/eswin/ecrnx_prof.h b/drivers/net/wireless/eswin/ecrnx_prof.h new file mode 100644 index 000000000000..e828fd2437bf --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_prof.h @@ -0,0 +1,136 @@ +/** + **************************************************************************************** + * + * @file ecrnx_prof.h + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ + +#ifndef _ECRNX_PROF_H_ +#define _ECRNX_PROF_H_ + +#include "reg_access.h" +#include "ecrnx_platform.h" + +static inline void ecrnx_prof_set(struct ecrnx_hw *ecrnx_hw, int val) +{ + struct ecrnx_plat *ecrnx_plat = ecrnx_hw->plat; + ECRNX_REG_WRITE(val, ecrnx_plat, ECRNX_ADDR_SYSTEM, NXMAC_SW_SET_PROFILING_ADDR); +} + +static inline void ecrnx_prof_clear(struct ecrnx_hw *ecrnx_hw, int val) +{ + struct ecrnx_plat *ecrnx_plat = ecrnx_hw->plat; + ECRNX_REG_WRITE(val, ecrnx_plat, ECRNX_ADDR_SYSTEM, NXMAC_SW_CLEAR_PROFILING_ADDR); +} + +#if 0 +/* Defines for SW Profiling registers values */ +enum { + TX_IPC_IRQ, + TX_IPC_EVT, + TX_PREP_EVT, + TX_DMA_IRQ, + TX_MAC_IRQ, + TX_PAYL_HDL, + TX_CFM_EVT, + TX_IPC_CFM, + RX_MAC_IRQ, // 8 + RX_TRIGGER_EVT, + RX_DMA_IRQ, + RX_DMA_EVT, + RX_IPC_IND, + RX_MPDU_XFER, + DBG_PROF_MAX +}; +#endif + +enum { + SW_PROF_HOSTBUF_IDX = 12, + /****** IPC IRQs related signals ******/ + /* E2A direction */ + SW_PROF_IRQ_E2A_RXDESC = 16, // to make sure we let 16 bits available for LMAC FW + SW_PROF_IRQ_E2A_TXCFM, + SW_PROF_IRQ_E2A_DBG, + SW_PROF_IRQ_E2A_MSG, + SW_PROF_IPC_MSGPUSH, + SW_PROF_MSGALLOC, + SW_PROF_MSGIND, + SW_PROF_DBGIND, + + /* A2E direction */ + SW_PROF_IRQ_A2E_TXCFM_BACK, + + /****** Driver functions related signals ******/ + SW_PROF_WAIT_QUEUE_STOP, + SW_PROF_WAIT_QUEUE_WAKEUP, + SW_PROF_ECRNXDATAIND, + SW_PROF_ECRNX_IPC_IRQ_HDLR, + SW_PROF_ECRNX_IPC_THR_IRQ_HDLR, + SW_PROF_IEEE80211RX, + SW_PROF_ECRNX_PATTERN, + SW_PROF_MAX +}; + +// [LT]For debug purpose only +#if (0) +#define SW_PROF_CHAN_CTXT_CFM_HDL_BIT (21) +#define SW_PROF_CHAN_CTXT_CFM_BIT (22) +#define SW_PROF_CHAN_CTXT_CFM_SWDONE_BIT (23) +#define SW_PROF_CHAN_CTXT_PUSH_BIT (24) +#define SW_PROF_CHAN_CTXT_QUEUE_BIT (25) +#define SW_PROF_CHAN_CTXT_TX_BIT (26) +#define SW_PROF_CHAN_CTXT_TX_PAUSE_BIT (27) +#define SW_PROF_CHAN_CTXT_PSWTCH_BIT (28) +#define SW_PROF_CHAN_CTXT_SWTCH_BIT (29) + +// TO DO: update this + +#define REG_SW_SET_PROFILING_CHAN(env, bit) \ + ecrnx_prof_set((struct ecrnx_hw*)env, BIT(bit)) + +#define REG_SW_CLEAR_PROFILING_CHAN(env, bit) \ + ecrnx_prof_clear((struct ecrnx_hw*)env, BIT(bit)) + +#else +#define SW_PROF_CHAN_CTXT_CFM_HDL_BIT (0) +#define SW_PROF_CHAN_CTXT_CFM_BIT (0) +#define SW_PROF_CHAN_CTXT_CFM_SWDONE_BIT (0) +#define SW_PROF_CHAN_CTXT_PUSH_BIT (0) +#define SW_PROF_CHAN_CTXT_QUEUE_BIT (0) +#define SW_PROF_CHAN_CTXT_TX_BIT (0) +#define SW_PROF_CHAN_CTXT_TX_PAUSE_BIT (0) +#define SW_PROF_CHAN_CTXT_PSWTCH_BIT (0) +#define SW_PROF_CHAN_CTXT_SWTCH_BIT (0) + +#define REG_SW_SET_PROFILING_CHAN(env, bit) do {} while (0) +#define REG_SW_CLEAR_PROFILING_CHAN(env, bit) do {} while (0) +#endif + +#ifdef CONFIG_ECRNX_ESWIN +#undef CONFIG_ECRNX_SW_PROFILING +#endif +#ifdef CONFIG_ECRNX_SW_PROFILING +/* Macros for SW PRofiling registers access */ +#define REG_SW_SET_PROFILING(env, bit) \ + ecrnx_prof_set((struct ecrnx_hw*)env, BIT(bit)) + +#define REG_SW_SET_HOSTBUF_IDX_PROFILING(env, val) \ + ecrnx_prof_set((struct ecrnx_hw*)env, val<<(SW_PROF_HOSTBUF_IDX)) + +#define REG_SW_CLEAR_PROFILING(env, bit) \ + ecrnx_prof_clear((struct ecrnx_hw*)env, BIT(bit)) + +#define REG_SW_CLEAR_HOSTBUF_IDX_PROFILING(env) \ + ecrnx_prof_clear((struct ecrnx_hw*)env,0x0F<<(SW_PROF_HOSTBUF_IDX)) + +#else +#define REG_SW_SET_PROFILING(env, value) do {} while (0) +#define REG_SW_CLEAR_PROFILING(env, value) do {} while (0) +#define REG_SW_SET_HOSTBUF_IDX_PROFILING(env, val) do {} while (0) +#define REG_SW_CLEAR_HOSTBUF_IDX_PROFILING(env) do {} while (0) +#endif + +#endif /* _ECRNX_PROF_H_ */ diff --git a/drivers/net/wireless/eswin/ecrnx_radar.c b/drivers/net/wireless/eswin/ecrnx_radar.c new file mode 100644 index 000000000000..fca6ce8afa19 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_radar.c @@ -0,0 +1,1647 @@ +/** +****************************************************************************** + * + * @file ecrnx_radar.c + * + * @brief Functions to handle radar detection + * Radar detection is copied (and adapted) from ath driver source code. + * + * Copyright (c) 2012 Neratec Solutions AG + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ +#include <linux/list.h> +#include <linux/kernel.h> +#include <linux/ktime.h> +#include <net/mac80211.h> + +#include "ecrnx_radar.h" +#include "ecrnx_defs.h" +#include "ecrnx_msg_tx.h" +#include "ecrnx_events.h" +#include "ecrnx_compat.h" + +/* + * tolerated deviation of radar time stamp in usecs on both sides + * TODO: this might need to be HW-dependent + */ +#define PRI_TOLERANCE 16 + +/** + * struct radar_types - contains array of patterns defined for one DFS domain + * @domain: DFS regulatory domain + * @num_radar_types: number of radar types to follow + * @radar_types: radar types array + */ +struct radar_types { + enum nl80211_dfs_regions region; + u32 num_radar_types; + const struct radar_detector_specs *spec_riu; + const struct radar_detector_specs *spec_fcu; +}; + +/** + * Type of radar waveform: + * RADAR_WAVEFORM_SHORT : waveform defined by + * - pulse width + * - pulse interval in a burst (pri) + * - number of pulses in a burst (ppb) + * + * RADAR_WAVEFORM_WEATHER : + * same than SHORT except that ppb is dependent of pri + * + * RADAR_WAVEFORM_INTERLEAVED : + * same than SHORT except there are several value of pri (interleaved) + * + * RADAR_WAVEFORM_LONG : + * + */ +enum radar_waveform_type { + RADAR_WAVEFORM_SHORT, + RADAR_WAVEFORM_WEATHER, + RADAR_WAVEFORM_INTERLEAVED, + RADAR_WAVEFORM_LONG +}; + +/** + * struct radar_detector_specs - detector specs for a radar pattern type + * @type_id: pattern type, as defined by regulatory + * @width_min: minimum radar pulse width in [us] + * @width_max: maximum radar pulse width in [us] + * @pri_min: minimum pulse repetition interval in [us] (including tolerance) + * @pri_max: minimum pri in [us] (including tolerance) + * @num_pri: maximum number of different pri for this type + * @ppb: pulses per bursts for this type + * @ppb_thresh: number of pulses required to trigger detection + * @max_pri_tolerance: pulse time stamp tolerance on both sides [us] + * @type: Type of radar waveform + */ +struct radar_detector_specs { + u8 type_id; + u8 width_min; + u8 width_max; + u16 pri_min; + u16 pri_max; + u8 num_pri; + u8 ppb; + u8 ppb_thresh; + u8 max_pri_tolerance; + enum radar_waveform_type type; +}; + + +/* percentage on ppb threshold to trigger detection */ +#define MIN_PPB_THRESH 50 +#define PPB_THRESH(PPB) ((PPB * MIN_PPB_THRESH + 50) / 100) +#define PRF2PRI(PRF) ((1000000 + PRF / 2) / PRF) + +/* width tolerance */ +#define WIDTH_TOLERANCE 2 +#define WIDTH_LOWER(X) (X) +#define WIDTH_UPPER(X) (X) + +#define ETSI_PATTERN_SHORT(ID, WMIN, WMAX, PMIN, PMAX, PPB) \ + { \ + ID, WIDTH_LOWER(WMIN), WIDTH_UPPER(WMAX), \ + (PRF2PRI(PMAX) - PRI_TOLERANCE), \ + (PRF2PRI(PMIN) + PRI_TOLERANCE), 1, PPB, \ + PPB_THRESH(PPB), PRI_TOLERANCE, RADAR_WAVEFORM_SHORT \ + } + +#define ETSI_PATTERN_INTERLEAVED(ID, WMIN, WMAX, PMIN, PMAX, PRFMIN, PRFMAX, PPB) \ + { \ + ID, WIDTH_LOWER(WMIN), WIDTH_UPPER(WMAX), \ + (PRF2PRI(PMAX) * PRFMIN- PRI_TOLERANCE), \ + (PRF2PRI(PMIN) * PRFMAX + PRI_TOLERANCE), \ + PRFMAX, PPB * PRFMAX, \ + PPB_THRESH(PPB), PRI_TOLERANCE, RADAR_WAVEFORM_INTERLEAVED \ + } + +/* radar types as defined by ETSI EN-301-893 v1.7.1 */ +static const struct radar_detector_specs etsi_radar_ref_types_v17_riu[] = { + ETSI_PATTERN_SHORT(0, 0, 8, 700, 700, 18), + ETSI_PATTERN_SHORT(1, 0, 10, 200, 1000, 10), + ETSI_PATTERN_SHORT(2, 0, 22, 200, 1600, 15), + ETSI_PATTERN_SHORT(3, 0, 22, 2300, 4000, 25), + ETSI_PATTERN_SHORT(4, 20, 38, 2000, 4000, 20), + ETSI_PATTERN_INTERLEAVED(5, 0, 8, 300, 400, 2, 3, 10), + ETSI_PATTERN_INTERLEAVED(6, 0, 8, 400, 1200, 2, 3, 15), +}; + +static const struct radar_detector_specs etsi_radar_ref_types_v17_fcu[] = { + ETSI_PATTERN_SHORT(0, 0, 8, 700, 700, 18), + ETSI_PATTERN_SHORT(1, 0, 8, 200, 1000, 10), + ETSI_PATTERN_SHORT(2, 0, 16, 200, 1600, 15), + ETSI_PATTERN_SHORT(3, 0, 16, 2300, 4000, 25), + ETSI_PATTERN_SHORT(4, 20, 34, 2000, 4000, 20), + ETSI_PATTERN_INTERLEAVED(5, 0, 8, 300, 400, 2, 3, 10), + ETSI_PATTERN_INTERLEAVED(6, 0, 8, 400, 1200, 2, 3, 15), +}; + +static const struct radar_types etsi_radar_types_v17 = { + .region = NL80211_DFS_ETSI, + .num_radar_types = ARRAY_SIZE(etsi_radar_ref_types_v17_riu), + .spec_riu = etsi_radar_ref_types_v17_riu, + .spec_fcu = etsi_radar_ref_types_v17_fcu, +}; + +#define FCC_PATTERN(ID, WMIN, WMAX, PMIN, PMAX, PRF, PPB, TYPE) \ + { \ + ID, WIDTH_LOWER(WMIN), WIDTH_UPPER(WMAX), \ + PMIN - PRI_TOLERANCE, \ + PMAX * PRF + PRI_TOLERANCE, PRF, PPB * PRF, \ + PPB_THRESH(PPB), PRI_TOLERANCE, TYPE \ + } + +static const struct radar_detector_specs fcc_radar_ref_types_riu[] = { + FCC_PATTERN(0, 0, 8, 1428, 1428, 1, 18, RADAR_WAVEFORM_SHORT), + FCC_PATTERN(1, 0, 8, 518, 3066, 1, 102, RADAR_WAVEFORM_WEATHER), + FCC_PATTERN(2, 0, 8, 150, 230, 1, 23, RADAR_WAVEFORM_SHORT), + FCC_PATTERN(3, 6, 20, 200, 500, 1, 16, RADAR_WAVEFORM_SHORT), + FCC_PATTERN(4, 10, 28, 200, 500, 1, 12, RADAR_WAVEFORM_SHORT), + FCC_PATTERN(5, 50, 110, 1000, 2000, 1, 8, RADAR_WAVEFORM_LONG), + FCC_PATTERN(6, 0, 8, 333, 333, 1, 9, RADAR_WAVEFORM_SHORT), +}; + +static const struct radar_detector_specs fcc_radar_ref_types_fcu[] = { + FCC_PATTERN(0, 0, 8, 1428, 1428, 1, 18, RADAR_WAVEFORM_SHORT), + FCC_PATTERN(1, 0, 8, 518, 3066, 1, 102, RADAR_WAVEFORM_WEATHER), + FCC_PATTERN(2, 0, 8, 150, 230, 1, 23, RADAR_WAVEFORM_SHORT), + FCC_PATTERN(3, 6, 12, 200, 500, 1, 16, RADAR_WAVEFORM_SHORT), + FCC_PATTERN(4, 10, 22, 200, 500, 1, 12, RADAR_WAVEFORM_SHORT), + FCC_PATTERN(5, 50, 104, 1000, 2000, 1, 8, RADAR_WAVEFORM_LONG), + FCC_PATTERN(6, 0, 8, 333, 333, 1, 9, RADAR_WAVEFORM_SHORT), +}; + +static const struct radar_types fcc_radar_types = { + .region = NL80211_DFS_FCC, + .num_radar_types = ARRAY_SIZE(fcc_radar_ref_types_riu), + .spec_riu = fcc_radar_ref_types_riu, + .spec_fcu = fcc_radar_ref_types_fcu, +}; + +#define JP_PATTERN FCC_PATTERN +static const struct radar_detector_specs jp_radar_ref_types_riu[] = { + JP_PATTERN(0, 0, 8, 1428, 1428, 1, 18, RADAR_WAVEFORM_SHORT), + JP_PATTERN(1, 2, 8, 3846, 3846, 1, 18, RADAR_WAVEFORM_SHORT), + JP_PATTERN(2, 0, 8, 1388, 1388, 1, 18, RADAR_WAVEFORM_SHORT), + JP_PATTERN(3, 0, 8, 4000, 4000, 1, 18, RADAR_WAVEFORM_SHORT), + JP_PATTERN(4, 0, 8, 150, 230, 1, 23, RADAR_WAVEFORM_SHORT), + JP_PATTERN(5, 6, 20, 200, 500, 1, 16, RADAR_WAVEFORM_SHORT), + JP_PATTERN(6, 10, 28, 200, 500, 1, 12, RADAR_WAVEFORM_SHORT), + JP_PATTERN(7, 50, 110, 1000, 2000, 1, 8, RADAR_WAVEFORM_LONG), + JP_PATTERN(8, 0, 8, 333, 333, 1, 9, RADAR_WAVEFORM_SHORT), +}; + +static const struct radar_detector_specs jp_radar_ref_types_fcu[] = { + JP_PATTERN(0, 0, 8, 1428, 1428, 1, 18, RADAR_WAVEFORM_SHORT), + JP_PATTERN(1, 2, 6, 3846, 3846, 1, 18, RADAR_WAVEFORM_SHORT), + JP_PATTERN(2, 0, 8, 1388, 1388, 1, 18, RADAR_WAVEFORM_SHORT), + JP_PATTERN(3, 2, 2, 4000, 4000, 1, 18, RADAR_WAVEFORM_SHORT), + JP_PATTERN(4, 0, 8, 150, 230, 1, 23, RADAR_WAVEFORM_SHORT), + JP_PATTERN(5, 6, 12, 200, 500, 1, 16, RADAR_WAVEFORM_SHORT), + JP_PATTERN(6, 10, 22, 200, 500, 1, 12, RADAR_WAVEFORM_SHORT), + JP_PATTERN(7, 50, 104, 1000, 2000, 1, 8, RADAR_WAVEFORM_LONG), + JP_PATTERN(8, 0, 8, 333, 333, 1, 9, RADAR_WAVEFORM_SHORT), +}; + +static const struct radar_types jp_radar_types = { + .region = NL80211_DFS_JP, + .num_radar_types = ARRAY_SIZE(jp_radar_ref_types_riu), + .spec_riu = jp_radar_ref_types_riu, + .spec_fcu = jp_radar_ref_types_fcu, +}; + +static const struct radar_types *dfs_domains[] = { + &etsi_radar_types_v17, + &fcc_radar_types, + &jp_radar_types, +}; + + +/** + * struct pri_sequence - sequence of pulses matching one PRI + * @head: list_head + * @pri: pulse repetition interval (PRI) in usecs + * @dur: duration of sequence in usecs + * @count: number of pulses in this sequence + * @count_falses: number of not matching pulses in this sequence + * @first_ts: time stamp of first pulse in usecs + * @last_ts: time stamp of last pulse in usecs + * @deadline_ts: deadline when this sequence becomes invalid (first_ts + dur) + * @ppb_thresh: Number of pulses to validate detection + * (need for weather radar whose value depends of pri) + */ +struct pri_sequence { + struct list_head head; + u32 pri; + u32 dur; + u32 count; + u32 count_falses; + u64 first_ts; + u64 last_ts; + u64 deadline_ts; + u8 ppb_thresh; +}; + + +/** + * struct pulse_elem - elements in pulse queue + * @ts: time stamp in usecs + */ +struct pulse_elem { + struct list_head head; + u64 ts; +}; + +/** + * struct pri_detector - PRI detector element for a dedicated radar type + * @head: + * @rs: detector specs for this detector element + * @last_ts: last pulse time stamp considered for this element in usecs + * @sequences: list_head holding potential pulse sequences + * @pulses: list connecting pulse_elem objects + * @count: number of pulses in queue + * @max_count: maximum number of pulses to be queued + * @window_size: window size back from newest pulse time stamp in usecs + * @freq: + */ +struct pri_detector { + struct list_head head; + const struct radar_detector_specs *rs; + u64 last_ts; + struct list_head sequences; + struct list_head pulses; + u32 count; + u32 max_count; + u32 window_size; + struct pri_detector_ops *ops; + u16 freq; +}; + +/** + * struct pri_detector_ops - PRI detector ops (dependent of waveform type) + * @init : Initialize pri_detector structure + * @add_pulse : Add a pulse to the pri-detector + * @reset_on_pri_overflow : Should the pri_detector be resetted when pri overflow + */ +struct pri_detector_ops { + void (*init)(struct pri_detector *pde); + struct pri_sequence * (*add_pulse)(struct pri_detector *pde, u16 len, u64 ts, u16 pri); + int reset_on_pri_overflow; +}; + + +/****************************************************************************** + * PRI (pulse repetition interval) sequence detection + *****************************************************************************/ +/** + * Singleton Pulse and Sequence Pools + * + * Instances of pri_sequence and pulse_elem are kept in singleton pools to + * reduce the number of dynamic allocations. They are shared between all + * instances and grow up to the peak number of simultaneously used objects. + * + * Memory is freed after all references to the pools are released. + */ +static u32 singleton_pool_references; +static LIST_HEAD(pulse_pool); +static LIST_HEAD(pseq_pool); +static DEFINE_SPINLOCK(pool_lock); + +static void pool_register_ref(void) +{ + spin_lock_bh(&pool_lock); + singleton_pool_references++; + spin_unlock_bh(&pool_lock); +} + +static void pool_deregister_ref(void) +{ + spin_lock_bh(&pool_lock); + singleton_pool_references--; + if (singleton_pool_references == 0) { + /* free singleton pools with no references left */ + struct pri_sequence *ps, *ps0; + struct pulse_elem *p, *p0; + + list_for_each_entry_safe(p, p0, &pulse_pool, head) { + list_del(&p->head); + kfree(p); + } + list_for_each_entry_safe(ps, ps0, &pseq_pool, head) { + list_del(&ps->head); + kfree(ps); + } + } + spin_unlock_bh(&pool_lock); +} + +static void pool_put_pulse_elem(struct pulse_elem *pe) +{ + spin_lock_bh(&pool_lock); + list_add(&pe->head, &pulse_pool); + spin_unlock_bh(&pool_lock); +} + +static void pool_put_pseq_elem(struct pri_sequence *pse) +{ + spin_lock_bh(&pool_lock); + list_add(&pse->head, &pseq_pool); + spin_unlock_bh(&pool_lock); +} + +static struct pri_sequence *pool_get_pseq_elem(void) +{ + struct pri_sequence *pse = NULL; + spin_lock_bh(&pool_lock); + if (!list_empty(&pseq_pool)) { + pse = list_first_entry(&pseq_pool, struct pri_sequence, head); + list_del(&pse->head); + } + spin_unlock_bh(&pool_lock); + + if (pse == NULL) { + pse = kmalloc(sizeof(*pse), GFP_ATOMIC); + } + + return pse; +} + +static struct pulse_elem *pool_get_pulse_elem(void) +{ + struct pulse_elem *pe = NULL; + spin_lock_bh(&pool_lock); + if (!list_empty(&pulse_pool)) { + pe = list_first_entry(&pulse_pool, struct pulse_elem, head); + list_del(&pe->head); + } + spin_unlock_bh(&pool_lock); + return pe; +} + +static struct pulse_elem *pulse_queue_get_tail(struct pri_detector *pde) +{ + struct list_head *l = &pde->pulses; + if (list_empty(l)) + return NULL; + return list_entry(l->prev, struct pulse_elem, head); +} + +static bool pulse_queue_dequeue(struct pri_detector *pde) +{ + struct pulse_elem *p = pulse_queue_get_tail(pde); + if (p != NULL) { + list_del_init(&p->head); + pde->count--; + /* give it back to pool */ + pool_put_pulse_elem(p); + } + return (pde->count > 0); +} + +/** + * pulse_queue_check_window - remove pulses older than window + * @pde: pointer on pri_detector + * + * dequeue pulse that are too old. + */ +static +void pulse_queue_check_window(struct pri_detector *pde) +{ + u64 min_valid_ts; + struct pulse_elem *p; + + /* there is no delta time with less than 2 pulses */ + if (pde->count < 2) + return; + + if (pde->last_ts <= pde->window_size) + return; + + min_valid_ts = pde->last_ts - pde->window_size; + while ((p = pulse_queue_get_tail(pde)) != NULL) { + if (p->ts >= min_valid_ts) + return; + pulse_queue_dequeue(pde); + } +} + +/** + * pulse_queue_enqueue - Queue one pulse + * @pde: pointer on pri_detector + * + * Add one pulse to the list. If the maximum number of pulses + * if reached, remove oldest one. + */ +static +bool pulse_queue_enqueue(struct pri_detector *pde, u64 ts) +{ + struct pulse_elem *p = pool_get_pulse_elem(); + if (p == NULL) { + p = kmalloc(sizeof(*p), GFP_ATOMIC); + if (p == NULL) { + return false; + } + } + INIT_LIST_HEAD(&p->head); + p->ts = ts; + list_add(&p->head, &pde->pulses); + pde->count++; + pde->last_ts = ts; + pulse_queue_check_window(pde); + if (pde->count >= pde->max_count) + pulse_queue_dequeue(pde); + + return true; +} + + +/*************************************************************************** + * Short waveform + **************************************************************************/ +/** + * pde_get_multiple() - get number of multiples considering a given tolerance + * @return factor if abs(val - factor*fraction) <= tolerance, 0 otherwise + */ +static +u32 pde_get_multiple(u32 val, u32 fraction, u32 tolerance) +{ + u32 remainder; + u32 factor; + u32 delta; + + if (fraction == 0) + return 0; + + delta = (val < fraction) ? (fraction - val) : (val - fraction); + + if (delta <= tolerance) + /* val and fraction are within tolerance */ + return 1; + + factor = val / fraction; + remainder = val % fraction; + if (remainder > tolerance) { + /* no exact match */ + if ((fraction - remainder) <= tolerance) + /* remainder is within tolerance */ + factor++; + else + factor = 0; + } + return factor; +} + +/** + * pde_short_create_sequences - create_sequences function for + * SHORT/WEATHER/INTERLEAVED radar waveform + * @pde: pointer on pri_detector + * @ts: timestamp of the pulse + * @min_count: Minimum number of pulse to be present in the sequence. + * (With this pulse there is already a sequence with @min_count + * pulse, so if we can't create a sequence with more pulse don't + * create it) + * @return: false if an error occured (memory allocation) true otherwise + * + * For each pulses queued check if we can create a sequence with + * pri = (ts - pulse_queued.ts) which contains more than @min_count pulses. + * + */ +static +bool pde_short_create_sequences(struct pri_detector *pde, + u64 ts, u32 min_count) +{ + struct pulse_elem *p; + u16 pulse_idx = 0; + + list_for_each_entry(p, &pde->pulses, head) { + struct pri_sequence ps, *new_ps; + struct pulse_elem *p2; + u32 tmp_false_count; + u64 min_valid_ts; + u32 delta_ts = ts - p->ts; + pulse_idx++; + + if (delta_ts < pde->rs->pri_min) + /* ignore too small pri */ + continue; + + if (delta_ts > pde->rs->pri_max) + /* stop on too large pri (sorted list) */ + break; + + /* build a new sequence with new potential pri */ + ps.count = 2; + ps.count_falses = pulse_idx - 1; + ps.first_ts = p->ts; + ps.last_ts = ts; + ps.pri = ts - p->ts; + ps.dur = ps.pri * (pde->rs->ppb - 1) + + 2 * pde->rs->max_pri_tolerance; + + p2 = p; + tmp_false_count = 0; + if (ps.dur > ts) + min_valid_ts = 0; + else + min_valid_ts = ts - ps.dur; + /* check which past pulses are candidates for new sequence */ + list_for_each_entry_continue(p2, &pde->pulses, head) { + u32 factor; + if (p2->ts < min_valid_ts) + /* stop on crossing window border */ + break; + /* check if pulse match (multi)PRI */ + factor = pde_get_multiple(ps.last_ts - p2->ts, ps.pri, + pde->rs->max_pri_tolerance); + if (factor > 0) { + ps.count++; + ps.first_ts = p2->ts; + /* + * on match, add the intermediate falses + * and reset counter + */ + ps.count_falses += tmp_false_count; + tmp_false_count = 0; + } else { + /* this is a potential false one */ + tmp_false_count++; + } + } + if (ps.count <= min_count) { + /* did not reach minimum count, drop sequence */ + continue; + } + /* this is a valid one, add it */ + ps.deadline_ts = ps.first_ts + ps.dur; + if (pde->rs->type == RADAR_WAVEFORM_WEATHER) { + ps.ppb_thresh = 19000000 / (360 * ps.pri); + ps.ppb_thresh = PPB_THRESH(ps.ppb_thresh); + } else { + ps.ppb_thresh = pde->rs->ppb_thresh; + } + + new_ps = pool_get_pseq_elem(); + if (new_ps == NULL) { + return false; + } + memcpy(new_ps, &ps, sizeof(ps)); + INIT_LIST_HEAD(&new_ps->head); + list_add(&new_ps->head, &pde->sequences); + } + return true; +} + +/** + * pde_short_add_to_existing_seqs - add_to_existing_seqs function for + * SHORT/WEATHER/INTERLEAVED radar waveform + * @pde: pointer on pri_detector + * @ts: timestamp of the pulse + * + * Check all sequemces created for this pde. + * - If the sequence is too old delete it. + * - Else if the delta with the previous pulse match the pri of the sequence + * add the pulse to this sequence. If the pulse cannot be added it is added + * to the false pulses for this sequence + * + * @return the length of the longest sequence in which the pulse has been added + */ +static +u32 pde_short_add_to_existing_seqs(struct pri_detector *pde, u64 ts) +{ + u32 max_count = 0; + struct pri_sequence *ps, *ps2; + list_for_each_entry_safe(ps, ps2, &pde->sequences, head) { + u32 delta_ts; + u32 factor; + + /* first ensure that sequence is within window */ + if (ts > ps->deadline_ts) { + list_del_init(&ps->head); + pool_put_pseq_elem(ps); + continue; + } + + delta_ts = ts - ps->last_ts; + factor = pde_get_multiple(delta_ts, ps->pri, + pde->rs->max_pri_tolerance); + + if (factor > 0) { + ps->last_ts = ts; + ps->count++; + + if (max_count < ps->count) + max_count = ps->count; + } else { + ps->count_falses++; + } + } + return max_count; +} + + +/** + * pde_short_check_detection - check_detection function for + * SHORT/WEATHER/INTERLEAVED radar waveform + * @pde: pointer on pri_detector + * + * Check all sequemces created for this pde. + * - If a sequence contains more pulses than the threshold and more matching + * that false pulses. + * + * @return The first complete sequence, and NULL if no sequence is complete. + */ +static +struct pri_sequence * pde_short_check_detection(struct pri_detector *pde) +{ + struct pri_sequence *ps; + + if (list_empty(&pde->sequences)) + return NULL; + + list_for_each_entry(ps, &pde->sequences, head) { + /* + * we assume to have enough matching confidence if we + * 1) have enough pulses + * 2) have more matching than false pulses + */ + if ((ps->count >= ps->ppb_thresh) && + (ps->count * pde->rs->num_pri > ps->count_falses)) { + return ps; + } + } + return NULL; +} + +/** + * pde_short_init - init function for + * SHORT/WEATHER/INTERLEAVED radar waveform + * @pde: pointer on pri_detector + * + * Initialize pri_detector window size to the maximun size of one burst + * for the radar specification associated. + */ +static +void pde_short_init(struct pri_detector *pde) +{ + pde->window_size = pde->rs->pri_max * pde->rs->ppb * pde->rs->num_pri; + pde->max_count = pde->rs->ppb * 2; +} + +static void pri_detector_reset(struct pri_detector *pde, u64 ts); +/** + * pde_short_add_pulse - Add pulse to a pri_detector for + * SHORT/WEATHER/INTERLEAVED radar waveform + * + * @pde : pointer on pri_detector + * @len : width of the pulse + * @ts : timestamp of the pulse received + * @pri : Delta in us with the previous pulse. + * (0 means that delta in bigger than 65535 us) + * + * Process on pulse within this pri_detector + * - First try to add it to existing sequence + * - Then try to create a new and longest sequence + * - Check if this pulse complete a sequence + * - If not save this pulse in the list + */ +static +struct pri_sequence *pde_short_add_pulse(struct pri_detector *pde, + u16 len, u64 ts, u16 pri) +{ + u32 max_updated_seq; + struct pri_sequence *ps; + const struct radar_detector_specs *rs = pde->rs; + + if (pde->count == 0) { + /* This is the first pulse after reset, no need to check sequences */ + pulse_queue_enqueue(pde, ts); + return NULL; + } + + if ((ts - pde->last_ts) < rs->max_pri_tolerance) { + /* if delta to last pulse is too short, don't use this pulse */ + return NULL; + } + + max_updated_seq = pde_short_add_to_existing_seqs(pde, ts); + + if (!pde_short_create_sequences(pde, ts, max_updated_seq)) { + pri_detector_reset(pde, ts); + return NULL; + } + + ps = pde_short_check_detection(pde); + + if (ps == NULL) + pulse_queue_enqueue(pde, ts); + + return ps; +} + + + +/** + * pri detector ops to detect short radar waveform + * A Short waveform is defined by : + * The width of pulses. + * The interval between two pulses inside a burst (called pri) + * (some waveform may have or 2/3 interleaved pri) + * The number of pulses per burst (ppb) + */ +static struct pri_detector_ops pri_detector_short = { + .init = pde_short_init, + .add_pulse = pde_short_add_pulse, + .reset_on_pri_overflow = 1, +}; + + +/*************************************************************************** + * Long waveform + **************************************************************************/ +#define LONG_RADAR_DURATION 12000000 +#define LONG_RADAR_BURST_MIN_DURATION (12000000 / 20) +#define LONG_RADAR_MAX_BURST 20 + +/** + * pde_long_init - init function for LONG radar waveform + * @pde: pointer on pri_detector + * + * Initialize pri_detector window size to the long waveform radar + * waveform (ie. 12s) and max_count + */ +static +void pde_long_init(struct pri_detector *pde) +{ + pde->window_size = LONG_RADAR_DURATION; + pde->max_count = LONG_RADAR_MAX_BURST; /* only count burst not pulses */ +} + + +/** + * pde_long_add_pulse - Add pulse to a pri_detector for + * LONG radar waveform + * + * @pde : pointer on pri_detector + * @len : width of the pulse + * @ts : timestamp of the pulse received + * @pri : Delta in us with the previous pulse. + * + * + * For long pulse we only handle one sequence. Since each burst + * have a different set of parameters (number of pulse, pri) than + * the previous one we only use pulse width to add the pulse in the + * sequence. + * We only queue one pulse per burst and valid the radar when enough burst + * has been detected. + */ +static +struct pri_sequence *pde_long_add_pulse(struct pri_detector *pde, + u16 len, u64 ts, u16 pri) +{ + struct pri_sequence *ps; + const struct radar_detector_specs *rs = pde->rs; + + if (list_empty(&pde->sequences)) { + /* First pulse, create a new sequence */ + ps = pool_get_pseq_elem(); + if (ps == NULL) { + return NULL; + } + + /*For long waveform, "count" represents the number of burst detected */ + ps->count = 1; + /*"count_false" represents the number of pulse in the current burst */ + ps->count_falses = 1; + ps->first_ts = ts; + ps->last_ts = ts; + ps->deadline_ts = ts + pde->window_size; + ps->pri = 0; + INIT_LIST_HEAD(&ps->head); + list_add(&ps->head, &pde->sequences); + pulse_queue_enqueue(pde, ts); + } else { + u32 delta_ts; + + ps = (struct pri_sequence *)pde->sequences.next; + + delta_ts = ts - ps->last_ts; + ps->last_ts = ts; + + if (delta_ts < rs->pri_max) { + /* ignore pulse too close from previous one */ + } else if ((delta_ts >= rs->pri_min) && + (delta_ts <= rs->pri_max)) { + /* this is a new pulse in the current burst, ignore it + (i.e don't queue it) */ + ps->count_falses++; + } else if ((ps->count > 2) && + (ps->dur + delta_ts) < LONG_RADAR_BURST_MIN_DURATION) { + /* not enough time between burst, ignore pulse */ + } else { + /* a new burst */ + ps->count++; + ps->count_falses = 1; + + /* reset the start of the sequence if deadline reached */ + if (ts > ps->deadline_ts) { + struct pulse_elem *p; + u64 min_valid_ts; + + min_valid_ts = ts - pde->window_size; + while ((p = pulse_queue_get_tail(pde)) != NULL) { + if (p->ts >= min_valid_ts) { + ps->first_ts = p->ts; + ps->deadline_ts = p->ts + pde->window_size; + break; + } + pulse_queue_dequeue(pde); + ps->count--; + } + } + + /* valid radar if enough burst detected and delta with first burst + is at least duration/2 */ + if (ps->count > pde->rs->ppb_thresh && + (ts - ps->first_ts) > (pde->window_size / 2)) { + return ps; + } else { + pulse_queue_enqueue(pde, ts); + ps->dur = delta_ts; + } + } + } + + return NULL; +} + +/** + * pri detector ops to detect long radar waveform + */ +static struct pri_detector_ops pri_detector_long = { + .init = pde_long_init, + .add_pulse = pde_long_add_pulse, + .reset_on_pri_overflow = 0, +}; + + +/*************************************************************************** + * PRI detector init/reset/exit/get + **************************************************************************/ +/** + * pri_detector_init- Create a new pri_detector + * + * @dpd: dfs_pattern_detector instance pointer + * @radar_type: index of radar pattern + * @freq: Frequency of the pri detector + */ +struct pri_detector *pri_detector_init(struct dfs_pattern_detector *dpd, + u16 radar_type, u16 freq) +{ + struct pri_detector *pde; + + pde = kzalloc(sizeof(*pde), GFP_ATOMIC); + if (pde == NULL) + return NULL; + + INIT_LIST_HEAD(&pde->sequences); + INIT_LIST_HEAD(&pde->pulses); + INIT_LIST_HEAD(&pde->head); + list_add(&pde->head, &dpd->detectors[radar_type]); + + pde->rs = &dpd->radar_spec[radar_type]; + pde->freq = freq; + + if (pde->rs->type == RADAR_WAVEFORM_LONG) { + /* for LONG WAVEFORM */ + pde->ops = &pri_detector_long; + } else { + /* for SHORT, WEATHER and INTERLEAVED */ + pde->ops = &pri_detector_short; + } + + /* Init dependent of specs */ + pde->ops->init(pde); + + pool_register_ref(); + return pde; +} + +/** + * pri_detector_reset - Reset pri_detector + * + * @pde: pointer on pri_detector + * @ts: New ts reference for the pri_detector + * + * free pulse queue and sequences list and give objects back to pools + */ +static +void pri_detector_reset(struct pri_detector *pde, u64 ts) +{ + struct pri_sequence *ps, *ps0; + struct pulse_elem *p, *p0; + list_for_each_entry_safe(ps, ps0, &pde->sequences, head) { + list_del_init(&ps->head); + pool_put_pseq_elem(ps); + } + list_for_each_entry_safe(p, p0, &pde->pulses, head) { + list_del_init(&p->head); + pool_put_pulse_elem(p); + } + pde->count = 0; + pde->last_ts = ts; +} + +/** + * pri_detector_exit - Delete pri_detector + * + * @pde: pointer on pri_detector + */ +static +void pri_detector_exit(struct pri_detector *pde) +{ + pri_detector_reset(pde, 0); + pool_deregister_ref(); + list_del(&pde->head); + kfree(pde); +} + +/** + * pri_detector_get() - get pri detector for a given frequency and type + * @dpd: dfs_pattern_detector instance pointer + * @freq: frequency in MHz + * @radar_type: index of radar pattern + * @return pointer to pri detector on success, NULL otherwise + * + * Return existing pri detector for the given frequency or return a + * newly create one. + * Pri detector are "merged" by frequency so that if a pri detector for a freq + * of +/- 2Mhz already exists don't create a new one. + * + * Maybe will need to adapt frequency merge for pattern with chirp. + */ +static struct pri_detector * +pri_detector_get(struct dfs_pattern_detector *dpd, u16 freq, u16 radar_type) +{ + struct pri_detector *pde, *cur = NULL; + list_for_each_entry(pde, &dpd->detectors[radar_type], head) { + if (pde->freq == freq) { + if (pde->count) + return pde; + else + cur = pde; + } else if (pde->freq - 2 == freq && pde->count) { + return pde; + } else if (pde->freq + 2 == freq && pde->count) { + return pde; + } + } + + if (cur) + return cur; + else + return pri_detector_init(dpd, radar_type, freq); +} + + +/****************************************************************************** + * DFS Pattern Detector + *****************************************************************************/ +/** + * dfs_pattern_detector_reset() - reset all channel detectors + * + * @dpd: dfs_pattern_detector + */ +static void dfs_pattern_detector_reset(struct dfs_pattern_detector *dpd) +{ + struct pri_detector *pde; + int i; + + for (i = 0; i < dpd->num_radar_types; i++) { + if (!list_empty(&dpd->detectors[i])) + list_for_each_entry(pde, &dpd->detectors[i], head) + pri_detector_reset(pde, dpd->last_pulse_ts); + } + + dpd->last_pulse_ts = 0; + dpd->prev_jiffies = jiffies; +} + +/** + * dfs_pattern_detector_reset() - delete all channel detectors + * + * @dpd: dfs_pattern_detector + */ +static void dfs_pattern_detector_exit(struct dfs_pattern_detector *dpd) +{ + struct pri_detector *pde, *pde0; + int i; + + for (i = 0; i < dpd->num_radar_types; i++) { + if (!list_empty(&dpd->detectors[i])) + list_for_each_entry_safe(pde, pde0, &dpd->detectors[i], head) + pri_detector_exit(pde); + } + + kfree(dpd); +} + +/** + * dfs_pattern_detector_pri_overflow - reset all channel detectors on pri + * overflow + * @dpd: dfs_pattern_detector + */ +static void dfs_pattern_detector_pri_overflow(struct dfs_pattern_detector *dpd) +{ + struct pri_detector *pde; + int i; + + for (i = 0; i < dpd->num_radar_types; i++) { + if (!list_empty(&dpd->detectors[i])) + list_for_each_entry(pde, &dpd->detectors[i], head) + if (pde->ops->reset_on_pri_overflow) + pri_detector_reset(pde, dpd->last_pulse_ts); + } +} + +/** + * dfs_pattern_detector_add_pulse - Process one pulse + * + * @dpd: dfs_pattern_detector + * @chain: Chain that correspond to this pattern_detector (only for debug) + * @freq: frequency of the pulse + * @pri: Delta with previous pulse. (0 if delta is too big for u16) + * @len: width of the pulse + * @now: jiffies value when pulse was received + * + * Get (or create) the channel_detector for this frequency. Then add the pulse + * in each pri_detector created in this channel_detector. + * + * + * @return True is the pulse complete a radar pattern, false otherwise + */ +static bool dfs_pattern_detector_add_pulse(struct dfs_pattern_detector *dpd, + enum ecrnx_radar_chain chain, + u16 freq, u16 pri, u16 len, u32 now) +{ + u32 i; + + /* + * pulses received for a non-supported or un-initialized + * domain are treated as detected radars for fail-safety + */ + if (dpd->region == NL80211_DFS_UNSET) + return true; + + /* Compute pulse time stamp */ + if (pri == 0) { + u32 delta_jiffie; + if (unlikely(now < dpd->prev_jiffies)) { + delta_jiffie = 0xffffffff - dpd->prev_jiffies + now; + } else { + delta_jiffie = now - dpd->prev_jiffies; + } + dpd->last_pulse_ts += jiffies_to_usecs(delta_jiffie); + dpd->prev_jiffies = now; + dfs_pattern_detector_pri_overflow(dpd); + } else { + dpd->last_pulse_ts += pri; + } + + for (i = 0; i < dpd->num_radar_types; i++) { + struct pri_sequence *ps; + struct pri_detector *pde; + const struct radar_detector_specs *rs = &dpd->radar_spec[i]; + + /* no need to look up for pde if len is not within range */ + if ((rs->width_min > len) || + (rs->width_max < len)) { + continue; + } + + pde = pri_detector_get(dpd, freq, i); + ps = pde->ops->add_pulse(pde, len, dpd->last_pulse_ts, pri); + + if (ps != NULL) { + trace_radar_detected(chain, dpd->region, pde->freq, i, ps->pri); + // reset everything instead of just the channel detector + dfs_pattern_detector_reset(dpd); + return true; + } + } + + return false; +} + +/** + * get_dfs_domain_radar_types() - get radar types for a given DFS domain + * @param domain DFS domain + * @return radar_types ptr on success, NULL if DFS domain is not supported + */ +static const struct radar_types * +get_dfs_domain_radar_types(enum nl80211_dfs_regions region) +{ + u32 i; + for (i = 0; i < ARRAY_SIZE(dfs_domains); i++) { + if (dfs_domains[i]->region == region) + return dfs_domains[i]; + } + return NULL; +} + +/** + * get_dfs_max_radar_types() - get maximum radar types for all supported domain + * @return the maximum number of radar pattern supported by on region + */ +static u16 get_dfs_max_radar_types(void) +{ + u32 i; + u16 max = 0; + for (i = 0; i < ARRAY_SIZE(dfs_domains); i++) { + if (dfs_domains[i]->num_radar_types > max) + max = dfs_domains[i]->num_radar_types; + } + return max; +} + +/** + * dfs_pattern_detector_set_domain - set DFS domain + * + * @dpd: dfs_pattern_detector + * @region: DFS region + * + * set DFS domain, resets detector lines upon domain changes + */ +static +bool dfs_pattern_detector_set_domain(struct dfs_pattern_detector *dpd, + enum nl80211_dfs_regions region, u8 chain) +{ + const struct radar_types *rt; + struct pri_detector *pde, *pde0; + int i; + + if (dpd->region == region) + return true; + + dpd->region = NL80211_DFS_UNSET; + + rt = get_dfs_domain_radar_types(region); + if (rt == NULL) + return false; + + /* delete all pri detectors for previous DFS domain */ + for (i = 0; i < dpd->num_radar_types; i++) { + if (!list_empty(&dpd->detectors[i])) + list_for_each_entry_safe(pde, pde0, &dpd->detectors[i], head) + pri_detector_exit(pde); + } + + if (chain == ECRNX_RADAR_RIU) + dpd->radar_spec = rt->spec_riu; + else + dpd->radar_spec = rt->spec_fcu; + dpd->num_radar_types = rt->num_radar_types; + + dpd->region = region; + return true; +} + +/** + * dfs_pattern_detector_init - Initialize dfs_pattern_detector + * + * @region: DFS region + * @return: pointer on dfs_pattern_detector + * + */ +static struct dfs_pattern_detector * +dfs_pattern_detector_init(enum nl80211_dfs_regions region, u8 chain) +{ + struct dfs_pattern_detector *dpd; + u16 i, max_radar_type = get_dfs_max_radar_types(); + + dpd = kmalloc(sizeof(*dpd) + max_radar_type * sizeof(dpd->detectors[0]), + GFP_KERNEL); + if (dpd == NULL) + return NULL; + + dpd->region = NL80211_DFS_UNSET; + dpd->enabled = ECRNX_RADAR_DETECT_DISABLE; + dpd->last_pulse_ts = 0; + dpd->prev_jiffies = jiffies; + dpd->num_radar_types = 0; + for (i = 0; i < max_radar_type; i++) + INIT_LIST_HEAD(&dpd->detectors[i]); + + if (dfs_pattern_detector_set_domain(dpd, region, chain)) + return dpd; + + kfree(dpd); + return NULL; +} + + +/****************************************************************************** + * driver interface + *****************************************************************************/ +static u16 ecrnx_radar_get_center_freq(struct ecrnx_hw *ecrnx_hw, u8 chain) +{ + if (chain == ECRNX_RADAR_FCU) + return ecrnx_hw->phy.sec_chan.center1_freq; + + if (chain == ECRNX_RADAR_RIU) { +#ifdef CONFIG_ECRNX_SOFTMAC + return ecrnx_hw->cur_freq; +#else + if (!ecrnx_chanctx_valid(ecrnx_hw, ecrnx_hw->cur_chanctx)) { + WARN(1, "Radar pulse without channel information"); + } else + return ecrnx_hw->chanctx_table[ecrnx_hw->cur_chanctx].chan_def.center_freq1; +#endif /* CONFIG_ECRNX_SOFTMAC */ + } + + return 0; +} + +static void ecrnx_radar_detected(struct ecrnx_hw *ecrnx_hw) +{ +#ifdef CONFIG_ECRNX_SOFTMAC + ieee80211_radar_detected(ecrnx_hw->hw); +#else + struct cfg80211_chan_def chan_def; + + if (!ecrnx_chanctx_valid(ecrnx_hw, ecrnx_hw->cur_chanctx)) { + WARN(1, "Radar detected without channel information"); + return; + } + + /* + recopy chan_def in local variable because ecrnx_radar_cancel_cac may + clean the variable (if in CAC and it's the only vif using this context) + and CAC should be aborted before reporting the radar. + */ + chan_def = ecrnx_hw->chanctx_table[ecrnx_hw->cur_chanctx].chan_def; + + ecrnx_radar_cancel_cac(&ecrnx_hw->radar); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + cfg80211_radar_event(ecrnx_hw->wiphy, &chan_def, GFP_KERNEL); +#endif +#endif /* CONFIG_ECRNX_SOFTMAC */ +} + +static void ecrnx_radar_process_pulse(struct work_struct *ws) +{ + struct ecrnx_radar *radar = container_of(ws, struct ecrnx_radar, + detection_work); + struct ecrnx_hw *ecrnx_hw = container_of(radar, struct ecrnx_hw, radar); + int chain; + u32 pulses[ECRNX_RADAR_LAST][ECRNX_RADAR_PULSE_MAX]; + u16 pulses_count[ECRNX_RADAR_LAST]; + u32 now = jiffies; /* would be better to store jiffies value in IT handler */ + + /* recopy pulses locally to avoid too long spin_lock */ + spin_lock_bh(&radar->lock); + for (chain = ECRNX_RADAR_RIU; chain < ECRNX_RADAR_LAST; chain++) { + int start, count; + + count = radar->pulses[chain].count; + start = radar->pulses[chain].index - count; + if (start < 0) + start += ECRNX_RADAR_PULSE_MAX; + + pulses_count[chain] = count; + if (count == 0) + continue; + + if ((start + count) > ECRNX_RADAR_PULSE_MAX) { + u16 count1 = (ECRNX_RADAR_PULSE_MAX - start); + memcpy(&(pulses[chain][0]), + &(radar->pulses[chain].buffer[start]), + count1 * sizeof(struct radar_pulse)); + memcpy(&(pulses[chain][count1]), + &(radar->pulses[chain].buffer[0]), + (count - count1) * sizeof(struct radar_pulse)); + } else { + memcpy(&(pulses[chain][0]), + &(radar->pulses[chain].buffer[start]), + count * sizeof(struct radar_pulse)); + } + radar->pulses[chain].count = 0; + } + spin_unlock_bh(&radar->lock); + + + /* now process pulses */ + for (chain = ECRNX_RADAR_RIU; chain < ECRNX_RADAR_LAST; chain++) { + int i; + u16 freq; + + if (pulses_count[chain] == 0) + continue; + + freq = ecrnx_radar_get_center_freq(ecrnx_hw, chain); + + for (i = 0; i < pulses_count[chain] ; i++) { + struct radar_pulse *p = (struct radar_pulse *)&pulses[chain][i]; + trace_radar_pulse(chain, p); + if (dfs_pattern_detector_add_pulse(radar->dpd[chain], chain, + (s16)freq + (2 * p->freq), + p->rep, (p->len * 2), now)) { + u16 idx = radar->detected[chain].index; + + if (chain == ECRNX_RADAR_RIU) { + /* operating chain, inform upper layer to change channel */ + if (radar->dpd[chain]->enabled == ECRNX_RADAR_DETECT_REPORT) { + ecrnx_radar_detected(ecrnx_hw); + /* no need to report new radar until upper layer set a + new channel. This prevent warning if a new radar is + detected while mac80211 is changing channel */ + ecrnx_radar_detection_enable(radar, + ECRNX_RADAR_DETECT_DISABLE, + chain); + /* purge any event received since the beginning of the + function (we are sure not to interfer with tasklet + as we disable detection just before) */ + radar->pulses[chain].count = 0; + } + } else { + /* secondary radar detection chain, simply report info in + debugfs for now */ + } + + radar->detected[chain].freq[idx] = (s16)freq + (2 * p->freq); + radar->detected[chain].time[idx] = ktime_get_real_seconds(); + radar->detected[chain].index = ((idx + 1 ) % + NX_NB_RADAR_DETECTED); + radar->detected[chain].count++; + /* no need to process next pulses for this chain */ + break; + } + } + } +} + +#ifdef CONFIG_ECRNX_FULLMAC +static void ecrnx_radar_cac_work(struct work_struct *ws) +{ + struct delayed_work *dw = container_of(ws, struct delayed_work, work); + struct ecrnx_radar *radar = container_of(dw, struct ecrnx_radar, cac_work); + struct ecrnx_hw *ecrnx_hw = container_of(radar, struct ecrnx_hw, radar); + struct ecrnx_chanctx *ctxt; + + if (radar->cac_vif == NULL) { + WARN(1, "CAC finished but no vif set"); + return; + } + + ctxt = &ecrnx_hw->chanctx_table[radar->cac_vif->ch_index]; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + cfg80211_cac_event(radar->cac_vif->ndev, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) + &ctxt->chan_def, +#endif + NL80211_RADAR_CAC_FINISHED, GFP_KERNEL); +#endif + ecrnx_send_apm_stop_cac_req(ecrnx_hw, radar->cac_vif); + ecrnx_chanctx_unlink(radar->cac_vif); + + radar->cac_vif = NULL; +} +#endif /* CONFIG_ECRNX_FULLMAC */ + +bool ecrnx_radar_detection_init(struct ecrnx_radar *radar) +{ + spin_lock_init(&radar->lock); + + radar->dpd[ECRNX_RADAR_RIU] = dfs_pattern_detector_init(NL80211_DFS_UNSET, + ECRNX_RADAR_RIU); + if (radar->dpd[ECRNX_RADAR_RIU] == NULL) + return false; + + radar->dpd[ECRNX_RADAR_FCU] = dfs_pattern_detector_init(NL80211_DFS_UNSET, + ECRNX_RADAR_FCU); + if (radar->dpd[ECRNX_RADAR_FCU] == NULL) { + ecrnx_radar_detection_deinit(radar); + return false; + } + + INIT_WORK(&radar->detection_work, ecrnx_radar_process_pulse); +#ifdef CONFIG_ECRNX_FULLMAC + INIT_DELAYED_WORK(&radar->cac_work, ecrnx_radar_cac_work); + radar->cac_vif = NULL; +#endif /* CONFIG_ECRNX_FULLMAC */ + return true; +} + +void ecrnx_radar_detection_deinit(struct ecrnx_radar *radar) +{ + if (radar->dpd[ECRNX_RADAR_RIU]) { + dfs_pattern_detector_exit(radar->dpd[ECRNX_RADAR_RIU]); + radar->dpd[ECRNX_RADAR_RIU] = NULL; + } + if (radar->dpd[ECRNX_RADAR_FCU]) { + dfs_pattern_detector_exit(radar->dpd[ECRNX_RADAR_FCU]); + radar->dpd[ECRNX_RADAR_FCU] = NULL; + } +} + +bool ecrnx_radar_set_domain(struct ecrnx_radar *radar, + enum nl80211_dfs_regions region) +{ + if (radar->dpd[0] == NULL) + return false; + + trace_radar_set_region(region); + + return (dfs_pattern_detector_set_domain(radar->dpd[ECRNX_RADAR_RIU], + region, ECRNX_RADAR_RIU) && + dfs_pattern_detector_set_domain(radar->dpd[ECRNX_RADAR_FCU], + region, ECRNX_RADAR_FCU)); +} + +void ecrnx_radar_detection_enable(struct ecrnx_radar *radar, u8 enable, u8 chain) +{ + if (chain < ECRNX_RADAR_LAST ) { + trace_radar_enable_detection(radar->dpd[chain]->region, enable, chain); + spin_lock_bh(&radar->lock); + radar->dpd[chain]->enabled = enable; + spin_unlock_bh(&radar->lock); + } +} + +bool ecrnx_radar_detection_is_enable(struct ecrnx_radar *radar, u8 chain) +{ + return radar->dpd[chain]->enabled != ECRNX_RADAR_DETECT_DISABLE; +} + +#ifdef CONFIG_ECRNX_FULLMAC +void ecrnx_radar_start_cac(struct ecrnx_radar *radar, u32 cac_time_ms, + struct ecrnx_vif *vif) +{ + WARN(radar->cac_vif != NULL, "CAC already in progress"); + radar->cac_vif = vif; + schedule_delayed_work(&radar->cac_work, msecs_to_jiffies(cac_time_ms)); +} + +void ecrnx_radar_cancel_cac(struct ecrnx_radar *radar) +{ + struct ecrnx_hw *ecrnx_hw = container_of(radar, struct ecrnx_hw, radar); + + if (radar->cac_vif == NULL) { + return; + } + + if (cancel_delayed_work(&radar->cac_work)) { + struct ecrnx_chanctx *ctxt; + ctxt = &ecrnx_hw->chanctx_table[radar->cac_vif->ch_index]; + ecrnx_send_apm_stop_cac_req(ecrnx_hw, radar->cac_vif); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + cfg80211_cac_event(radar->cac_vif->ndev, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) + &ctxt->chan_def, +#endif + NL80211_RADAR_CAC_ABORTED, GFP_KERNEL); +#endif + ecrnx_chanctx_unlink(radar->cac_vif); + } + + radar->cac_vif = NULL; +} + +void ecrnx_radar_detection_enable_on_cur_channel(struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_chanctx *ctxt; + + /* If no information on current channel do nothing */ + if (!ecrnx_chanctx_valid(ecrnx_hw, ecrnx_hw->cur_chanctx)) + return; + + ctxt = &ecrnx_hw->chanctx_table[ecrnx_hw->cur_chanctx]; + if (ctxt->chan_def.chan->flags & IEEE80211_CHAN_RADAR) { + ecrnx_radar_detection_enable(&ecrnx_hw->radar, + ECRNX_RADAR_DETECT_REPORT, + ECRNX_RADAR_RIU); + } else { + ecrnx_radar_detection_enable(&ecrnx_hw->radar, + ECRNX_RADAR_DETECT_DISABLE, + ECRNX_RADAR_RIU); + } +} +#endif /* CONFIG_ECRNX_FULLMAC */ + +/***************************************************************************** + * Debug functions + *****************************************************************************/ +static +int ecrnx_radar_dump_pri_detector(char *buf, size_t len, + struct pri_detector *pde) +{ + char freq_info[] = "Freq = %3.dMhz\n"; + char seq_info[] = " pri | count | false \n"; + struct pri_sequence *seq; + int res, write = 0; + + if (list_empty(&pde->sequences)) { + return 0; + } + + if (buf == NULL) { + int nb_seq = 1; + list_for_each_entry(seq, &pde->sequences, head) { + nb_seq++; + } + + return (sizeof(freq_info) + nb_seq * sizeof(seq_info)); + } + + res = scnprintf(buf, len, freq_info, pde->freq); + write += res; + len -= res; + + res = scnprintf(&buf[write], len, "%s", seq_info); + write += res; + len -= res; + + list_for_each_entry(seq, &pde->sequences, head) { + res = scnprintf(&buf[write], len, " %6.d | %2.d | %.2d \n", + seq->pri, seq->count, seq->count_falses); + write += res; + len -= res; + } + + return write; +} + +int ecrnx_radar_dump_pattern_detector(char *buf, size_t len, + struct ecrnx_radar *radar, u8 chain) +{ + struct dfs_pattern_detector *dpd = radar->dpd[chain]; + char info[] = "Type = %3.d\n"; + struct pri_detector *pde; + int i, res, write = 0; + + /* if buf is NULL return size needed for dump */ + if (buf == NULL) { + int size_needed = 0; + + for (i = 0; i < dpd->num_radar_types; i++) { + list_for_each_entry(pde, &dpd->detectors[i], head) { + size_needed += ecrnx_radar_dump_pri_detector(NULL, 0, pde); + } + size_needed += sizeof(info); + + return size_needed; + } + } + + /* */ + for (i = 0; i < dpd->num_radar_types; i++) { + res = scnprintf(&buf[write], len, info, i); + + write += res; + len -= res; + list_for_each_entry(pde, &dpd->detectors[i], head) { + res = ecrnx_radar_dump_pri_detector(&buf[write], len, pde); + write += res; + len -= res; + } + } + + return write; +} + + +int ecrnx_radar_dump_radar_detected(char *buf, size_t len, + struct ecrnx_radar *radar, u8 chain) +{ + struct ecrnx_radar_detected *detect = &(radar->detected[chain]); + char info[] = "2001/02/02 - 02:20 5126MHz\n"; + int idx, i, res, write = 0; + int count = detect->count; + + if (count > NX_NB_RADAR_DETECTED) + count = NX_NB_RADAR_DETECTED; + + if (buf == NULL) { + return (count * sizeof(info)) + 1; + } + + idx = (detect->index - detect->count) % NX_NB_RADAR_DETECTED; + + for (i = 0; i < count; i++) { + struct tm tm; + time64_to_tm(detect->time[idx], 0, &tm); + + res = scnprintf(&buf[write], len, + "%.4d/%.2d/%.2d - %.2d:%.2d %4.4dMHz\n", + (int)tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, detect->freq[idx]); + write += res; + len -= res; + + idx = (idx + 1 ) % NX_NB_RADAR_DETECTED; + } + + return write; +} diff --git a/drivers/net/wireless/eswin/ecrnx_radar.h b/drivers/net/wireless/eswin/ecrnx_radar.h new file mode 100644 index 000000000000..78a9d1d05ecd --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_radar.h @@ -0,0 +1,160 @@ +/** + ****************************************************************************** + * + * @file ecrnx_radar.h + * + * @brief Functions to handle radar detection + * + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ +#ifndef _ECRNX_RADAR_H_ +#define _ECRNX_RADAR_H_ + +#include <linux/nl80211.h> + +struct ecrnx_vif; +struct ecrnx_hw; + +enum ecrnx_radar_chain { + ECRNX_RADAR_RIU = 0, + ECRNX_RADAR_FCU, + ECRNX_RADAR_LAST +}; + +enum ecrnx_radar_detector { + ECRNX_RADAR_DETECT_DISABLE = 0, /* Ignore radar pulses */ + ECRNX_RADAR_DETECT_ENABLE = 1, /* Process pattern detection but do not + report radar to upper layer (for test) */ + ECRNX_RADAR_DETECT_REPORT = 2 /* Process pattern detection and report + radar to upper layer. */ +}; + +#ifdef CONFIG_ECRNX_RADAR +#include <linux/workqueue.h> +#include <linux/spinlock.h> + +#define ECRNX_RADAR_PULSE_MAX 32 + +/** + * struct ecrnx_radar_pulses - List of pulses reported by HW + * @index: write index + * @count: number of valid pulses + * @buffer: buffer of pulses + */ +struct ecrnx_radar_pulses { + /* Last radar pulses received */ + int index; + int count; + u32 buffer[ECRNX_RADAR_PULSE_MAX]; +}; + +/** + * struct dfs_pattern_detector - DFS pattern detector + * @region: active DFS region, NL80211_DFS_UNSET until set + * @num_radar_types: number of different radar types + * @last_pulse_ts: time stamp of last valid pulse in usecs + * @prev_jiffies: + * @radar_detector_specs: array of radar detection specs + * @channel_detectors: list connecting channel_detector elements + */ +struct dfs_pattern_detector { + u8 enabled; + enum nl80211_dfs_regions region; + u8 num_radar_types; + u64 last_pulse_ts; + u32 prev_jiffies; + const struct radar_detector_specs *radar_spec; + struct list_head detectors[]; +}; + +#define NX_NB_RADAR_DETECTED 4 + +/** + * struct ecrnx_radar_detected - List of radar detected + */ +struct ecrnx_radar_detected { + u16 index; + u16 count; + s64 time[NX_NB_RADAR_DETECTED]; + s16 freq[NX_NB_RADAR_DETECTED]; +}; + + +struct ecrnx_radar { + struct ecrnx_radar_pulses pulses[ECRNX_RADAR_LAST]; + struct dfs_pattern_detector *dpd[ECRNX_RADAR_LAST]; + struct ecrnx_radar_detected detected[ECRNX_RADAR_LAST]; + struct work_struct detection_work; /* Work used to process radar pulses */ + spinlock_t lock; /* lock for pulses processing */ + + /* In softmac cac is handled by mac80211 */ +#ifdef CONFIG_ECRNX_FULLMAC + struct delayed_work cac_work; /* Work used to handle CAC */ + struct ecrnx_vif *cac_vif; /* vif on which we started CAC */ +#endif +}; + +bool ecrnx_radar_detection_init(struct ecrnx_radar *radar); +void ecrnx_radar_detection_deinit(struct ecrnx_radar *radar); +bool ecrnx_radar_set_domain(struct ecrnx_radar *radar, + enum nl80211_dfs_regions region); +void ecrnx_radar_detection_enable(struct ecrnx_radar *radar, u8 enable, u8 chain); +bool ecrnx_radar_detection_is_enable(struct ecrnx_radar *radar, u8 chain); +void ecrnx_radar_start_cac(struct ecrnx_radar *radar, u32 cac_time_ms, + struct ecrnx_vif *vif); +void ecrnx_radar_cancel_cac(struct ecrnx_radar *radar); +void ecrnx_radar_detection_enable_on_cur_channel(struct ecrnx_hw *ecrnx_hw); +int ecrnx_radar_dump_pattern_detector(char *buf, size_t len, + struct ecrnx_radar *radar, u8 chain); +int ecrnx_radar_dump_radar_detected(char *buf, size_t len, + struct ecrnx_radar *radar, u8 chain); + +#else + +struct ecrnx_radar { +}; + +static inline bool ecrnx_radar_detection_init(struct ecrnx_radar *radar) +{return true;} + +static inline void ecrnx_radar_detection_deinit(struct ecrnx_radar *radar) +{} + +static inline bool ecrnx_radar_set_domain(struct ecrnx_radar *radar, + enum nl80211_dfs_regions region) +{return true;} + +static inline void ecrnx_radar_detection_enable(struct ecrnx_radar *radar, + u8 enable, u8 chain) +{} + +static inline bool ecrnx_radar_detection_is_enable(struct ecrnx_radar *radar, + u8 chain) +{return false;} + +static inline void ecrnx_radar_start_cac(struct ecrnx_radar *radar, + u32 cac_time_ms, struct ecrnx_vif *vif) +{} + +static inline void ecrnx_radar_cancel_cac(struct ecrnx_radar *radar) +{} + +static inline void ecrnx_radar_detection_enable_on_cur_channel(struct ecrnx_hw *ecrnx_hw) +{} + +static inline int ecrnx_radar_dump_pattern_detector(char *buf, size_t len, + struct ecrnx_radar *radar, + u8 chain) +{return 0;} + +static inline int ecrnx_radar_dump_radar_detected(char *buf, size_t len, + struct ecrnx_radar *radar, + u8 chain) +{return 0;} + +#endif /* CONFIG_ECRNX_RADAR */ + +#endif // _ECRNX_RADAR_H_ diff --git a/drivers/net/wireless/eswin/ecrnx_strs.c b/drivers/net/wireless/eswin/ecrnx_strs.c new file mode 100644 index 000000000000..4352b9dc1dba --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_strs.c @@ -0,0 +1,252 @@ +/** + ****************************************************************************** + * + * @file ecrnx_strs.c + * + * @brief Miscellaneous debug strings + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#include "lmac_msg.h" + +static const char *const ecrnx_mmid2str[MSG_I(MM_MAX)] = { + [MSG_I(MM_RESET_REQ)] = "MM_RESET_REQ", + [MSG_I(MM_RESET_CFM)] = "MM_RESET_CFM", + [MSG_I(MM_START_REQ)] = "MM_START_REQ", + [MSG_I(MM_START_CFM)] = "MM_START_CFM", + [MSG_I(MM_VERSION_REQ)] = "MM_VERSION_REQ", + [MSG_I(MM_VERSION_CFM)] = "MM_VERSION_CFM", + [MSG_I(MM_ADD_IF_REQ)] = "MM_ADD_IF_REQ", + [MSG_I(MM_ADD_IF_CFM)] = "MM_ADD_IF_CFM", + [MSG_I(MM_REMOVE_IF_REQ)] = "MM_REMOVE_IF_REQ", + [MSG_I(MM_REMOVE_IF_CFM)] = "MM_REMOVE_IF_CFM", + [MSG_I(MM_STA_ADD_REQ)] = "MM_STA_ADD_REQ", + [MSG_I(MM_STA_ADD_CFM)] = "MM_STA_ADD_CFM", + [MSG_I(MM_STA_DEL_REQ)] = "MM_STA_DEL_REQ", + [MSG_I(MM_STA_DEL_CFM)] = "MM_STA_DEL_CFM", + [MSG_I(MM_SET_FILTER_REQ)] = "MM_SET_FILTER_REQ", + [MSG_I(MM_SET_FILTER_CFM)] = "MM_SET_FILTER_CFM", + [MSG_I(MM_SET_CHANNEL_REQ)] = "MM_SET_CHANNEL_REQ", + [MSG_I(MM_SET_CHANNEL_CFM)] = "MM_SET_CHANNEL_CFM", + [MSG_I(MM_SET_DTIM_REQ)] = "MM_SET_DTIM_REQ", + [MSG_I(MM_SET_DTIM_CFM)] = "MM_SET_DTIM_CFM", + [MSG_I(MM_SET_BEACON_INT_REQ)] = "MM_SET_BEACON_INT_REQ", + [MSG_I(MM_SET_BEACON_INT_CFM)] = "MM_SET_BEACON_INT_CFM", + [MSG_I(MM_SET_BASIC_RATES_REQ)] = "MM_SET_BASIC_RATES_REQ", + [MSG_I(MM_SET_BASIC_RATES_CFM)] = "MM_SET_BASIC_RATES_CFM", + [MSG_I(MM_SET_BSSID_REQ)] = "MM_SET_BSSID_REQ", + [MSG_I(MM_SET_BSSID_CFM)] = "MM_SET_BSSID_CFM", + [MSG_I(MM_SET_EDCA_REQ)] = "MM_SET_EDCA_REQ", + [MSG_I(MM_SET_EDCA_CFM)] = "MM_SET_EDCA_CFM", + [MSG_I(MM_SET_MODE_REQ)] = "MM_SET_MODE_REQ", + [MSG_I(MM_SET_MODE_CFM)] = "MM_SET_MODE_CFM", + [MSG_I(MM_SET_VIF_STATE_REQ)] = "MM_SET_VIF_STATE_REQ", + [MSG_I(MM_SET_VIF_STATE_CFM)] = "MM_SET_VIF_STATE_CFM", + [MSG_I(MM_SET_SLOTTIME_REQ)] = "MM_SET_SLOTTIME_REQ", + [MSG_I(MM_SET_SLOTTIME_CFM)] = "MM_SET_SLOTTIME_CFM", + [MSG_I(MM_SET_IDLE_REQ)] = "MM_SET_IDLE_REQ", + [MSG_I(MM_SET_IDLE_CFM)] = "MM_SET_IDLE_CFM", + [MSG_I(MM_KEY_ADD_REQ)] = "MM_KEY_ADD_REQ", + [MSG_I(MM_KEY_ADD_CFM)] = "MM_KEY_ADD_CFM", + [MSG_I(MM_KEY_DEL_REQ)] = "MM_KEY_DEL_REQ", + [MSG_I(MM_KEY_DEL_CFM)] = "MM_KEY_DEL_CFM", + [MSG_I(MM_BA_ADD_REQ)] = "MM_BA_ADD_REQ", + [MSG_I(MM_BA_ADD_CFM)] = "MM_BA_ADD_CFM", + [MSG_I(MM_BA_DEL_REQ)] = "MM_BA_DEL_REQ", + [MSG_I(MM_BA_DEL_CFM)] = "MM_BA_DEL_CFM", + [MSG_I(MM_PRIMARY_TBTT_IND)] = "MM_PRIMARY_TBTT_IND", + [MSG_I(MM_SECONDARY_TBTT_IND)] = "MM_SECONDARY_TBTT_IND", + [MSG_I(MM_SET_POWER_REQ)] = "MM_SET_POWER_REQ", + [MSG_I(MM_SET_POWER_CFM)] = "MM_SET_POWER_CFM", + [MSG_I(MM_DBG_TRIGGER_REQ)] = "MM_DBG_TRIGGER_REQ", + [MSG_I(MM_SET_PS_MODE_REQ)] = "MM_SET_PS_MODE_REQ", + [MSG_I(MM_SET_PS_MODE_CFM)] = "MM_SET_PS_MODE_CFM", + [MSG_I(MM_CHAN_CTXT_ADD_REQ)] = "MM_CHAN_CTXT_ADD_REQ", + [MSG_I(MM_CHAN_CTXT_ADD_CFM)] = "MM_CHAN_CTXT_ADD_CFM", + [MSG_I(MM_CHAN_CTXT_DEL_REQ)] = "MM_CHAN_CTXT_DEL_REQ", + [MSG_I(MM_CHAN_CTXT_DEL_CFM)] = "MM_CHAN_CTXT_DEL_CFM", + [MSG_I(MM_CHAN_CTXT_LINK_REQ)] = "MM_CHAN_CTXT_LINK_REQ", + [MSG_I(MM_CHAN_CTXT_LINK_CFM)] = "MM_CHAN_CTXT_LINK_CFM", + [MSG_I(MM_CHAN_CTXT_UNLINK_REQ)] = "MM_CHAN_CTXT_UNLINK_REQ", + [MSG_I(MM_CHAN_CTXT_UNLINK_CFM)] = "MM_CHAN_CTXT_UNLINK_CFM", + [MSG_I(MM_CHAN_CTXT_UPDATE_REQ)] = "MM_CHAN_CTXT_UPDATE_REQ", + [MSG_I(MM_CHAN_CTXT_UPDATE_CFM)] = "MM_CHAN_CTXT_UPDATE_CFM", + [MSG_I(MM_CHAN_CTXT_SCHED_REQ)] = "MM_CHAN_CTXT_SCHED_REQ", + [MSG_I(MM_CHAN_CTXT_SCHED_CFM)] = "MM_CHAN_CTXT_SCHED_CFM", + [MSG_I(MM_BCN_CHANGE_REQ)] = "MM_BCN_CHANGE_REQ", + [MSG_I(MM_BCN_CHANGE_CFM)] = "MM_BCN_CHANGE_CFM", + [MSG_I(MM_TIM_UPDATE_REQ)] = "MM_TIM_UPDATE_REQ", + [MSG_I(MM_TIM_UPDATE_CFM)] = "MM_TIM_UPDATE_CFM", + [MSG_I(MM_CONNECTION_LOSS_IND)] = "MM_CONNECTION_LOSS_IND", + [MSG_I(MM_CHANNEL_SWITCH_IND)] = "MM_CHANNEL_SWITCH_IND", + [MSG_I(MM_CHANNEL_PRE_SWITCH_IND)] = "MM_CHANNEL_PRE_SWITCH_IND", + [MSG_I(MM_REMAIN_ON_CHANNEL_REQ)] = "MM_REMAIN_ON_CHANNEL_REQ", + [MSG_I(MM_REMAIN_ON_CHANNEL_CFM)] = "MM_REMAIN_ON_CHANNEL_CFM", + [MSG_I(MM_REMAIN_ON_CHANNEL_EXP_IND)] = "MM_REMAIN_ON_CHANNEL_EXP_IND", + [MSG_I(MM_PS_CHANGE_IND)] = "MM_PS_CHANGE_IND", + [MSG_I(MM_TRAFFIC_REQ_IND)] = "MM_TRAFFIC_REQ_IND", + [MSG_I(MM_SET_PS_OPTIONS_REQ)] = "MM_SET_PS_OPTIONS_REQ", + [MSG_I(MM_SET_PS_OPTIONS_CFM)] = "MM_SET_PS_OPTIONS_CFM", + [MSG_I(MM_P2P_VIF_PS_CHANGE_IND)] = "MM_P2P_VIF_PS_CHANGE_IND", + [MSG_I(MM_CSA_COUNTER_IND)] = "MM_CSA_COUNTER_IND", + [MSG_I(MM_CHANNEL_SURVEY_IND)] = "MM_CHANNEL_SURVEY_IND", + [MSG_I(MM_SET_P2P_NOA_REQ)] = "MM_SET_P2P_NOA_REQ", + [MSG_I(MM_SET_P2P_OPPPS_REQ)] = "MM_SET_P2P_OPPPS_REQ", + [MSG_I(MM_SET_P2P_NOA_CFM)] = "MM_SET_P2P_NOA_CFM", + [MSG_I(MM_SET_P2P_OPPPS_CFM)] = "MM_SET_P2P_OPPPS_CFM", + [MSG_I(MM_CFG_RSSI_REQ)] = "MM_CFG_RSSI_REQ", + [MSG_I(MM_RSSI_STATUS_IND)] = "MM_RSSI_STATUS_IND", + [MSG_I(MM_CSA_FINISH_IND)] = "MM_CSA_FINISH_IND", + [MSG_I(MM_CSA_TRAFFIC_IND)] = "MM_CSA_TRAFFIC_IND", + [MSG_I(MM_MU_GROUP_UPDATE_REQ)] = "MM_MU_GROUP_UPDATE_REQ", + [MSG_I(MM_MU_GROUP_UPDATE_CFM)] = "MM_MU_GROUP_UPDATE_CFM", + [MSG_I(MM_GET_CAL_RESULT_REQ)] = "MM_GET_CAL_RESULT_REQ", + [MSG_I(MM_GET_CAL_RESULT_CFM)] = "MM_GET_CAL_RESULT_CFM", +}; + +static const char *const ecrnx_dbgid2str[MSG_I(DBG_MAX)] = { + [MSG_I(DBG_MEM_READ_REQ)] = "DBG_MEM_READ_REQ", + [MSG_I(DBG_MEM_READ_CFM)] = "DBG_MEM_READ_CFM", + [MSG_I(DBG_MEM_WRITE_REQ)] = "DBG_MEM_WRITE_REQ", + [MSG_I(DBG_MEM_WRITE_CFM)] = "DBG_MEM_WRITE_CFM", + [MSG_I(DBG_SET_MOD_FILTER_REQ)] = "DBG_SET_MOD_FILTER_REQ", + [MSG_I(DBG_SET_MOD_FILTER_CFM)] = "DBG_SET_MOD_FILTER_CFM", + [MSG_I(DBG_SET_SEV_FILTER_REQ)] = "DBG_SET_SEV_FILTER_REQ", + [MSG_I(DBG_SET_SEV_FILTER_CFM)] = "DBG_SET_SEV_FILTER_CFM", + [MSG_I(DBG_ERROR_IND)] = "DBG_ERROR_IND", + [MSG_I(DBG_GET_SYS_STAT_REQ)] = "DBG_GET_SYS_STAT_REQ", + [MSG_I(DBG_GET_SYS_STAT_CFM)] = "DBG_GET_SYS_STAT_CFM", +}; + +static const char *const ecrnx_scanid2str[MSG_I(SCAN_MAX)] = { + [MSG_I(SCAN_START_REQ)] = "SCAN_START_REQ", + [MSG_I(SCAN_START_CFM)] = "SCAN_START_CFM", + [MSG_I(SCAN_DONE_IND)] = "SCAN_DONE_IND", +}; + +static const char *const ecrnx_tdlsid2str[MSG_I(TDLS_MAX)] = { + [MSG_I(TDLS_CHAN_SWITCH_CFM)] = "TDLS_CHAN_SWITCH_CFM", + [MSG_I(TDLS_CHAN_SWITCH_REQ)] = "TDLS_CHAN_SWITCH_REQ", + [MSG_I(TDLS_CHAN_SWITCH_IND)] = "TDLS_CHAN_SWITCH_IND", + [MSG_I(TDLS_CHAN_SWITCH_BASE_IND)] = "TDLS_CHAN_SWITCH_BASE_IND", + [MSG_I(TDLS_CANCEL_CHAN_SWITCH_REQ)] = "TDLS_CANCEL_CHAN_SWITCH_REQ", + [MSG_I(TDLS_CANCEL_CHAN_SWITCH_CFM)] = "TDLS_CANCEL_CHAN_SWITCH_CFM", + [MSG_I(TDLS_PEER_PS_IND)] = "TDLS_PEER_PS_IND", + [MSG_I(TDLS_PEER_TRAFFIC_IND_REQ)] = "TDLS_PEER_TRAFFIC_IND_REQ", + [MSG_I(TDLS_PEER_TRAFFIC_IND_CFM)] = "TDLS_PEER_TRAFFIC_IND_CFM", +}; + +#ifdef CONFIG_ECRNX_FULLMAC + +static const char *const ecrnx_scanuid2str[MSG_I(SCANU_MAX)] = { + [MSG_I(SCANU_START_REQ)] = "SCANU_START_REQ", + [MSG_I(SCANU_START_CFM)] = "SCANU_START_CFM", + [MSG_I(SCANU_JOIN_REQ)] = "SCANU_JOIN_REQ", + [MSG_I(SCANU_JOIN_CFM)] = "SCANU_JOIN_CFM", + [MSG_I(SCANU_RESULT_IND)] = "SCANU_RESULT_IND", + [MSG_I(SCANU_FAST_REQ)] = "SCANU_FAST_REQ", + [MSG_I(SCANU_FAST_CFM)] = "SCANU_FAST_CFM", + [MSG_I(SCANU_CANCEL_REQ)] = "SCANU_CANCEL_REQ", + [MSG_I(SCANU_CANCEL_CFM)] = "SCANU_CANCEL_CFM", +}; + +static const char *const ecrnx_meid2str[MSG_I(ME_MAX)] = { + [MSG_I(ME_CONFIG_REQ)] = "ME_CONFIG_REQ", + [MSG_I(ME_CONFIG_CFM)] = "ME_CONFIG_CFM", + [MSG_I(ME_CHAN_CONFIG_REQ)] = "ME_CHAN_CONFIG_REQ", + [MSG_I(ME_CHAN_CONFIG_CFM)] = "ME_CHAN_CONFIG_CFM", + [MSG_I(ME_SET_CONTROL_PORT_REQ)] = "ME_SET_CONTROL_PORT_REQ", + [MSG_I(ME_SET_CONTROL_PORT_CFM)] = "ME_SET_CONTROL_PORT_CFM", + [MSG_I(ME_TKIP_MIC_FAILURE_IND)] = "ME_TKIP_MIC_FAILURE_IND", + [MSG_I(ME_STA_ADD_REQ)] = "ME_STA_ADD_REQ", + [MSG_I(ME_STA_ADD_CFM)] = "ME_STA_ADD_CFM", + [MSG_I(ME_STA_DEL_REQ)] = "ME_STA_DEL_REQ", + [MSG_I(ME_STA_DEL_CFM)] = "ME_STA_DEL_CFM", + [MSG_I(ME_TX_CREDITS_UPDATE_IND)]= "ME_TX_CREDITS_UPDATE_IND", + [MSG_I(ME_RC_STATS_REQ)] = "ME_RC_STATS_REQ", + [MSG_I(ME_RC_STATS_CFM)] = "ME_RC_STATS_CFM", + [MSG_I(ME_RC_SET_RATE_REQ)] = "ME_RC_SET_RATE_REQ", + [MSG_I(ME_TRAFFIC_IND_REQ)] = "ME_TRAFFIC_IND_REQ", + [MSG_I(ME_TRAFFIC_IND_CFM)] = "ME_TRAFFIC_IND_CFM", + [MSG_I(ME_SET_PS_MODE_REQ)] = "ME_SET_PS_MODE_REQ", + [MSG_I(ME_SET_PS_MODE_CFM)] = "ME_SET_PS_MODE_CFM", +}; + +static const char *const ecrnx_smid2str[MSG_I(SM_MAX)] = { + [MSG_I(SM_CONNECT_REQ)] = "SM_CONNECT_REQ", + [MSG_I(SM_CONNECT_CFM)] = "SM_CONNECT_CFM", + [MSG_I(SM_CONNECT_IND)] = "SM_CONNECT_IND", + [MSG_I(SM_DISCONNECT_REQ)] = "SM_DISCONNECT_REQ", + [MSG_I(SM_DISCONNECT_CFM)] = "SM_DISCONNECT_CFM", + [MSG_I(SM_DISCONNECT_IND)] = "SM_DISCONNECT_IND", + [MSG_I(SM_EXTERNAL_AUTH_REQUIRED_IND)] = "SM_EXTERNAL_AUTH_REQUIRED_IND", + [MSG_I(SM_EXTERNAL_AUTH_REQUIRED_RSP)] = "SM_EXTERNAL_AUTH_REQUIRED_RSP", +}; + +static const char *const ecrnx_apmid2str[MSG_I(APM_MAX)] = { + [MSG_I(APM_START_REQ)] = "APM_START_REQ", + [MSG_I(APM_START_CFM)] = "APM_START_CFM", + [MSG_I(APM_STOP_REQ)] = "APM_STOP_REQ", + [MSG_I(APM_STOP_CFM)] = "APM_STOP_CFM", + [MSG_I(APM_START_CAC_REQ)] = "APM_START_CAC_REQ", + [MSG_I(APM_START_CAC_CFM)] = "APM_START_CAC_CFM", + [MSG_I(APM_STOP_CAC_REQ)] = "APM_STOP_CAC_REQ", + [MSG_I(APM_STOP_CAC_CFM)] = "APM_STOP_CAC_CFM", +}; + +static const char *const ecrnx_meshid2str[MSG_I(MESH_MAX)] = { + [MSG_I(MESH_START_REQ)] = "MESH_START_REQ", + [MSG_I(MESH_START_CFM)] = "MESH_START_CFM", + [MSG_I(MESH_STOP_REQ)] = "MESH_STOP_REQ", + [MSG_I(MESH_STOP_CFM)] = "MESH_STOP_CFM", + [MSG_I(MESH_UPDATE_REQ)] = "MESH_UPDATE_REQ", + [MSG_I(MESH_UPDATE_CFM)] = "MESH_UPDATE_CFM", + [MSG_I(MESH_PATH_CREATE_REQ)] = "MESH_PATH_CREATE_REQ", + [MSG_I(MESH_PATH_CREATE_CFM)] = "MESH_PATH_CREATE_CFM", + [MSG_I(MESH_PATH_UPDATE_REQ)] = "MESH_PATH_UPDATE_REQ", + [MSG_I(MESH_PATH_UPDATE_CFM)] = "MESH_PATH_UPDATE_CFM", + [MSG_I(MESH_PROXY_ADD_REQ)] = "MESH_PROXY_ADD_REQ", + [MSG_I(MESH_PEER_UPDATE_IND)] = "MESH_PEER_UPDATE_IND", + [MSG_I(MESH_PATH_UPDATE_IND)] = "MESH_PATH_UPDATE_IND", + [MSG_I(MESH_PROXY_UPDATE_IND)] = "MESH_PROXY_UPDATE_IND", +}; + +static const char *const ecrnx_twtid2str[MSG_I(TWT_MAX)] = { + [MSG_I(TWT_SETUP_REQ)] = "TWT_SETUP_REQ", + [MSG_I(TWT_SETUP_CFM)] = "TWT_SETUP_CFM", + [MSG_I(TWT_SETUP_IND)] = "TWT_SETUP_IND", + [MSG_I(TWT_TEARDOWN_REQ)] = "TWT_TEARDOWN_REQ", + [MSG_I(TWT_TEARDOWN_CFM)] = "TWT_TEARDOWN_CFM", +}; + +#if defined(CONFIG_ECRNX_P2P) +static const char *const rwnx_p2plistenid2str[MSG_I(P2P_LISTEN_MAX)] = { + [MSG_I(P2P_LISTEN_START_REQ)] = "P2P_LISTEN_START_REQ", + [MSG_I(P2P_LISTEN_START_CFM)] = "P2P_LISTEN_START_CFM", + [MSG_I(P2P_CANCEL_LISTEN_REQ)] = "P2P_CANCEL_LISTEN_REQ", + [MSG_I(P2P_CANCEL_LISTEN_CFM)] = "P2P_CANCEL_LISTEN_CFM", +}; +#endif + +#endif /* CONFIG_ECRNX_FULLMAC */ + +const char *const *ecrnx_id2str[TASK_LAST_EMB + 1] = { + [TASK_MM] = ecrnx_mmid2str, + [TASK_DBG] = ecrnx_dbgid2str, + [TASK_SCAN] = ecrnx_scanid2str, + [TASK_TDLS] = ecrnx_tdlsid2str, +#ifdef CONFIG_ECRNX_FULLMAC + [TASK_SCANU] = ecrnx_scanuid2str, + [TASK_ME] = ecrnx_meid2str, + [TASK_SM] = ecrnx_smid2str, + [TASK_APM] = ecrnx_apmid2str, + [TASK_MESH] = ecrnx_meshid2str, +#if defined(CONFIG_ECRNX_P2P) + [TASK_P2P_LISTEN] = rwnx_p2plistenid2str, +#endif + [TASK_TWT] = ecrnx_twtid2str, +#endif +}; diff --git a/drivers/net/wireless/eswin/ecrnx_strs.h b/drivers/net/wireless/eswin/ecrnx_strs.h new file mode 100644 index 000000000000..aa0d12915bd1 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_strs.h @@ -0,0 +1,31 @@ +/** + **************************************************************************************** + * + * @file ecrnx_strs.h + * + * @brief Miscellaneous debug strings + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ + +#ifndef _ECRNX_STRS_H_ +#define _ECRNX_STRS_H_ + +#ifdef CONFIG_ECRNX_FHOST + +#define ECRNX_ID2STR(tag) "Cmd" + +#else +#include "lmac_msg.h" + +#define ECRNX_ID2STR(tag) (((MSG_T(tag) < ARRAY_SIZE(ecrnx_id2str)) && \ + (ecrnx_id2str[MSG_T(tag)]) && \ + ((ecrnx_id2str[MSG_T(tag)])[MSG_I(tag)])) ? \ + (ecrnx_id2str[MSG_T(tag)])[MSG_I(tag)] : "unknown") + +extern const char *const *ecrnx_id2str[TASK_LAST_EMB + 1]; +#endif /* CONFIG_ECRNX_FHOST */ + +#endif /* _ECRNX_STRS_H_ */ diff --git a/drivers/net/wireless/eswin/ecrnx_testmode.c b/drivers/net/wireless/eswin/ecrnx_testmode.c new file mode 100644 index 000000000000..7ce995e9040e --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_testmode.c @@ -0,0 +1,226 @@ +/** + **************************************************************************************** + * + * @file ecrnx_testmode.c + * + * @brief Test mode function definitions + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ + +#include <net/mac80211.h> +#include <net/netlink.h> + +#include "ecrnx_testmode.h" +#include "ecrnx_msg_tx.h" +#include "ecrnx_dini.h" +#include "reg_access.h" + +/* + * This function handles the user application commands for register access. + * + * It retrieves command ID carried with ECRNX_TM_ATTR_COMMAND and calls to the + * handlers respectively. + * + * If it's an unknown commdn ID, -ENOSYS is returned; or -ENOMSG if the + * mandatory fields(ECRNX_TM_ATTR_REG_OFFSET,ECRNX_TM_ATTR_REG_VALUE32) + * are missing; Otherwise 0 is replied indicating the success of the command execution. + * + * If ECRNX_TM_ATTR_COMMAND is ECRNX_TM_CMD_APP2DEV_REG_READ, the register read + * value is returned with ECRNX_TM_ATTR_REG_VALUE32. + * + * @hw: ieee80211_hw object that represents the device + * @tb: general message fields from the user space + */ +int ecrnx_testmode_reg(struct ieee80211_hw *hw, struct nlattr **tb) +{ + struct ecrnx_hw *ecrnx_hw = hw->priv; + u32 mem_addr, val32; + struct sk_buff *skb; + int status = 0; + + /* First check if register address is there */ + if (!tb[ECRNX_TM_ATTR_REG_OFFSET]) { + ECRNX_ERR("Error finding register offset\n"); + return -ENOMSG; + } + + mem_addr = nla_get_u32(tb[ECRNX_TM_ATTR_REG_OFFSET]); + + switch (nla_get_u32(tb[ECRNX_TM_ATTR_COMMAND])) { + case ECRNX_TM_CMD_APP2DEV_REG_READ: + { + struct dbg_mem_read_cfm mem_read_cfm; + + /*** Send the command to the LMAC ***/ + if ((status = ecrnx_send_dbg_mem_read_req(ecrnx_hw, mem_addr, &mem_read_cfm))) + return status; + + /* Allocate the answer message */ + skb = cfg80211_testmode_alloc_reply_skb(hw->wiphy, 20); + if (!skb) { + ECRNX_ERR("Error allocating memory\n"); + return -ENOMEM; + } + + val32 = mem_read_cfm.memdata; + if (nla_put_u32(skb, ECRNX_TM_ATTR_REG_VALUE32, val32)) + goto nla_put_failure; + + /* Send the answer to upper layer */ + status = cfg80211_testmode_reply(skb); + if (status < 0) + ECRNX_ERR("Error sending msg : %d\n", status); + } + break; + + case ECRNX_TM_CMD_APP2DEV_REG_WRITE: + { + if (!tb[ECRNX_TM_ATTR_REG_VALUE32]) { + ECRNX_ERR("Error finding value to write\n"); + return -ENOMSG; + } else { + val32 = nla_get_u32(tb[ECRNX_TM_ATTR_REG_VALUE32]); + /* Send the command to the LMAC */ + if ((status = ecrnx_send_dbg_mem_write_req(ecrnx_hw, mem_addr, val32))) + return status; + } + } + break; + + default: + ECRNX_ERR("Unknown testmode register command ID\n"); + return -ENOSYS; + } + + return status; + +nla_put_failure: + kfree_skb(skb); + return -EMSGSIZE; +} + +/* + * This function handles the user application commands for Debug filter settings. + * + * @hw: ieee80211_hw object that represents the device + * @tb: general message fields from the user space + */ +int ecrnx_testmode_dbg_filter(struct ieee80211_hw *hw, struct nlattr **tb) +{ + struct ecrnx_hw *ecrnx_hw = hw->priv; + u32 filter; + int status = 0; + + /* First check if the filter is there */ + if (!tb[ECRNX_TM_ATTR_REG_FILTER]) { + ECRNX_ERR("Error finding filter value\n"); + return -ENOMSG; + } + + filter = nla_get_u32(tb[ECRNX_TM_ATTR_REG_FILTER]); + ECRNX_DBG("testmode debug filter, setting: 0x%x\n", filter); + + switch (nla_get_u32(tb[ECRNX_TM_ATTR_COMMAND])) { + case ECRNX_TM_CMD_APP2DEV_SET_DBGMODFILTER: + { + /* Send the command to the LMAC */ + if ((status = ecrnx_send_dbg_set_mod_filter_req(ecrnx_hw, filter))) + return status; + } + break; + case ECRNX_TM_CMD_APP2DEV_SET_DBGSEVFILTER: + { + /* Send the command to the LMAC */ + if ((status = ecrnx_send_dbg_set_sev_filter_req(ecrnx_hw, filter))) + return status; + } + break; + + default: + ECRNX_ERR("Unknown testmode register command ID\n"); + return -ENOSYS; + } + + return status; +} + +/* + * This function handles the user application commands for register access without using + * the normal LMAC messaging way. + * This time register access will be done through direct PCI BAR windows. This can be used + * to access registers even when the :AMC FW is stuck. + * + * @hw: ieee80211_hw object that represents the device + * @tb: general message fields from the user space + */ +int ecrnx_testmode_reg_dbg(struct ieee80211_hw *hw, struct nlattr **tb) +{ + struct ecrnx_hw *ecrnx_hw = hw->priv; + struct ecrnx_plat *ecrnx_plat = ecrnx_hw->plat; + u32 mem_addr; + struct sk_buff *skb; + int status = 0; + volatile unsigned int reg_value = 0; + unsigned int offset; + + /* First check if register address is there */ + if (!tb[ECRNX_TM_ATTR_REG_OFFSET]) { + ECRNX_ERR("Error finding register offset\n"); + return -ENOMSG; + } + + mem_addr = nla_get_u32(tb[ECRNX_TM_ATTR_REG_OFFSET]); + offset = mem_addr & 0x00FFFFFF; + + switch (nla_get_u32(tb[ECRNX_TM_ATTR_COMMAND])) { + case ECRNX_TM_CMD_APP2DEV_REG_READ_DBG: + { + /*** Send the command to the LMAC ***/ + reg_value = ECRNX_REG_READ(ecrnx_plat, ECRNX_ADDR_SYSTEM, offset); + + /* Allocate the answer message */ + skb = cfg80211_testmode_alloc_reply_skb(hw->wiphy, 20); + if (!skb) { + ECRNX_ERR("Error allocating memory\n"); + return -ENOMEM; + } + + if (nla_put_u32(skb, ECRNX_TM_ATTR_REG_VALUE32, reg_value)) + goto nla_put_failure; + + /* Send the answer to upper layer */ + status = cfg80211_testmode_reply(skb); + if (status < 0) + ECRNX_ERR("Error sending msg : %d\n", status); + } + break; + + case ECRNX_TM_CMD_APP2DEV_REG_WRITE_DBG: + { + if (!tb[ECRNX_TM_ATTR_REG_VALUE32]) { + ECRNX_ERR("Error finding value to write\n"); + return -ENOMSG; + } else { + reg_value = nla_get_u32(tb[ECRNX_TM_ATTR_REG_VALUE32]); + + /* Send the command to the LMAC */ + ECRNX_REG_WRITE(reg_value, ecrnx_plat, ECRNX_ADDR_SYSTEM, + offset); + } + } + break; + + default: + ECRNX_ERR("Unknown testmode register command ID\n"); + return -ENOSYS; + } + + return status; + +nla_put_failure: + kfree_skb(skb); + return -EMSGSIZE; +} diff --git a/drivers/net/wireless/eswin/ecrnx_testmode.h b/drivers/net/wireless/eswin/ecrnx_testmode.h new file mode 100644 index 000000000000..36d85620cea1 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_testmode.h @@ -0,0 +1,64 @@ +/** + **************************************************************************************** + * + * @file ecrnx_testmode.h + * + * @brief Test mode function declarations + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ + +#ifndef ECRNX_TESTMODE_H_ +#define ECRNX_TESTMODE_H_ + +#include <net/mac80211.h> +#include <net/netlink.h> + +/* Commands from user space to kernel space(ECRNX_TM_CMD_APP2DEV_XX) and + * from and kernel space to user space(ECRNX_TM_CMD_DEV2APP_XX). + * The command ID is carried with ECRNX_TM_ATTR_COMMAND. + */ +enum ecrnx_tm_cmd_t { + /* commands from user application to access register */ + ECRNX_TM_CMD_APP2DEV_REG_READ = 1, + ECRNX_TM_CMD_APP2DEV_REG_WRITE, + + /* commands from user application to select the Debug levels */ + ECRNX_TM_CMD_APP2DEV_SET_DBGMODFILTER, + ECRNX_TM_CMD_APP2DEV_SET_DBGSEVFILTER, + + /* commands to access registers without sending messages to LMAC layer, + * this must be used when LMAC FW is stuck. */ + ECRNX_TM_CMD_APP2DEV_REG_READ_DBG, + ECRNX_TM_CMD_APP2DEV_REG_WRITE_DBG, + + ECRNX_TM_CMD_MAX, +}; + +enum ecrnx_tm_attr_t { + ECRNX_TM_ATTR_NOT_APPLICABLE = 0, + + ECRNX_TM_ATTR_COMMAND, + + /* When ECRNX_TM_ATTR_COMMAND is ECRNX_TM_CMD_APP2DEV_REG_XXX, + * The mandatory fields are: + * ECRNX_TM_ATTR_REG_OFFSET for the offset of the target register; + * ECRNX_TM_ATTR_REG_VALUE32 for value */ + ECRNX_TM_ATTR_REG_OFFSET, + ECRNX_TM_ATTR_REG_VALUE32, + + /* When ECRNX_TM_ATTR_COMMAND is ECRNX_TM_CMD_APP2DEV_SET_DBGXXXFILTER, + * The mandatory field is ECRNX_TM_ATTR_REG_FILTER. */ + ECRNX_TM_ATTR_REG_FILTER, + + ECRNX_TM_ATTR_MAX, +}; + +/***********************************************************************/ +int ecrnx_testmode_reg(struct ieee80211_hw *hw, struct nlattr **tb); +int ecrnx_testmode_dbg_filter(struct ieee80211_hw *hw, struct nlattr **tb); +int ecrnx_testmode_reg_dbg(struct ieee80211_hw *hw, struct nlattr **tb); + +#endif /* ECRNX_TESTMODE_H_ */ diff --git a/drivers/net/wireless/eswin/ecrnx_txq.c b/drivers/net/wireless/eswin/ecrnx_txq.c new file mode 100644 index 000000000000..80536b907e7c --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_txq.c @@ -0,0 +1,1772 @@ +/** + ****************************************************************************** + * + * @file ecrnx_txq.c + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#include "ecrnx_defs.h" +#include "ecrnx_tx.h" +#include "ipc_host.h" +#include "ecrnx_events.h" + +/****************************************************************************** + * Utils functions + *****************************************************************************/ +#ifdef CONFIG_ECRNX_SOFTMAC +const int nx_tid_prio[NX_NB_TID_PER_STA] = {15, 7, 14, 6, 13, 5, 12, 4, + 11, 3, 8, 0, 10, 2, 9, 1}; + +static inline int ecrnx_txq_sta_idx(struct ecrnx_sta *sta, u8 tid) +{ + return sta->sta_idx * NX_NB_TXQ_PER_STA; +} + +static inline int ecrnx_txq_vif_idx(struct ecrnx_vif *vif, u8 ac) +{ + return vif->vif_index * NX_NB_TXQ_PER_VIF + ac + NX_FIRST_VIF_TXQ_IDX; +} + +#ifdef CONFIG_MAC80211_TXQ +struct ecrnx_txq *ecrnx_txq_sta_get(struct ecrnx_sta *ecrnx_sta, u8 tid) +{ + struct ieee80211_sta *sta = ecrnx_to_ieee80211_sta(ecrnx_sta); + struct ieee80211_txq *mac_txq = sta->txq[tid]; + + return (struct ecrnx_txq *)mac_txq->drv_priv; +} + +struct ecrnx_txq *ecrnx_txq_vif_get(struct ecrnx_vif *ecrnx_vif, u8 ac) +{ + struct ieee80211_vif *vif = ecrnx_to_ieee80211_vif(ecrnx_vif); + + /* mac80211 only allocate one txq per vif for Best Effort */ + if (ac == ECRNX_HWQ_BE) { + struct ieee80211_txq *mac_txq = vif->txq; + if (!mac_txq) + return NULL; + return (struct ecrnx_txq *)mac_txq->drv_priv; + } + + if (ac > NX_TXQ_CNT) + ac = ECRNX_HWQ_BK; + + return &ecrnx_vif->txqs[ac]; +} + +#else /* ! CONFIG_MAC80211_TXQ */ +struct ecrnx_txq *ecrnx_txq_sta_get(struct ecrnx_sta *sta, u8 tid) +{ + if (tid >= NX_NB_TXQ_PER_STA) + tid = 0; + + return &sta->txqs[tid]; +} + +struct ecrnx_txq *ecrnx_txq_vif_get(struct ecrnx_vif *vif, u8 ac) +{ + if (ac > NX_TXQ_CNT) + ac = ECRNX_HWQ_BK; + + return &vif->txqs[ac]; +} + +#endif /* CONFIG_MAC80211_TXQ */ + +static inline struct ecrnx_sta *ecrnx_txq_2_sta(struct ecrnx_txq *txq) +{ + if (txq->sta) + return (struct ecrnx_sta *)txq->sta->drv_priv; + return NULL; +} + +#else /* CONFIG_ECRNX_FULLMAC */ +const int nx_tid_prio[NX_NB_TID_PER_STA] = {7, 6, 5, 4, 3, 0, 2, 1}; + +static inline int ecrnx_txq_sta_idx(struct ecrnx_sta *sta, u8 tid) +{ + if (is_multicast_sta(sta->sta_idx)) + return NX_FIRST_VIF_TXQ_IDX + sta->vif_idx; + else + return (sta->sta_idx * NX_NB_TXQ_PER_STA) + tid; +} + +static inline int ecrnx_txq_vif_idx(struct ecrnx_vif *vif, u8 type) +{ + return NX_FIRST_VIF_TXQ_IDX + master_vif_idx(vif) + (type * NX_VIRT_DEV_MAX); +} + +struct ecrnx_txq *ecrnx_txq_sta_get(struct ecrnx_sta *sta, u8 tid, + struct ecrnx_hw * ecrnx_hw) +{ + if (tid >= NX_NB_TXQ_PER_STA) + tid = 0; + + return &ecrnx_hw->txq[ecrnx_txq_sta_idx(sta, tid)]; +} + +struct ecrnx_txq *ecrnx_txq_vif_get(struct ecrnx_vif *vif, u8 type) +{ + if (type > NX_UNK_TXQ_TYPE) + type = NX_BCMC_TXQ_TYPE; + + return &vif->ecrnx_hw->txq[ecrnx_txq_vif_idx(vif, type)]; +} + +static inline struct ecrnx_sta *ecrnx_txq_2_sta(struct ecrnx_txq *txq) +{ + return txq->sta; +} + +#endif /* CONFIG_ECRNX_SOFTMAC */ + + +/****************************************************************************** + * Init/Deinit functions + *****************************************************************************/ +/** + * ecrnx_txq_init - Initialize a TX queue + * + * @txq: TX queue to be initialized + * @idx: TX queue index + * @status: TX queue initial status + * @hwq: Associated HW queue + * @ndev: Net device this queue belongs to + * (may be null for non netdev txq) + * + * Each queue is initialized with the credit of @NX_TXQ_INITIAL_CREDITS. + */ +static void ecrnx_txq_init(struct ecrnx_txq *txq, int idx, u8 status, + struct ecrnx_hwq *hwq, int tid, +#ifdef CONFIG_ECRNX_SOFTMAC + struct ieee80211_sta *sta +#else + struct ecrnx_sta *sta, struct net_device *ndev +#endif + ) +{ + int i; + + txq->idx = idx; + txq->status = status; + txq->credits = NX_TXQ_INITIAL_CREDITS; + txq->pkt_sent = 0; + skb_queue_head_init(&txq->sk_list); + txq->last_retry_skb = NULL; + txq->nb_retry = 0; + txq->hwq = hwq; + txq->sta = sta; + for (i = 0; i < CONFIG_USER_MAX ; i++) + txq->pkt_pushed[i] = 0; + txq->push_limit = 0; + txq->tid = tid; +#ifdef CONFIG_MAC80211_TXQ + txq->nb_ready_mac80211 = 0; +#endif +#ifdef CONFIG_ECRNX_SOFTMAC + txq->baw.agg_on = false; +#ifdef CONFIG_ECRNX_AMSDUS_TX + txq->amsdu_anchor = NULL; + txq->amsdu_ht_len_cap = 0; + txq->amsdu_vht_len_cap = 0; +#endif /* CONFIG_ECRNX_AMSDUS_TX */ + +#else /* ! CONFIG_ECRNX_SOFTMAC */ + txq->ps_id = LEGACY_PS_ID; + if (idx < NX_FIRST_VIF_TXQ_IDX) { + int sta_idx = sta->sta_idx; + int tid = idx - (sta_idx * NX_NB_TXQ_PER_STA); + if (tid < NX_NB_TID_PER_STA) + txq->ndev_idx = NX_STA_NDEV_IDX(tid, sta_idx); + else + txq->ndev_idx = NDEV_NO_TXQ; + } else if (idx < NX_FIRST_UNK_TXQ_IDX) { + txq->ndev_idx = NX_BCMC_TXQ_NDEV_IDX; + } else { + txq->ndev_idx = NDEV_NO_TXQ; + } + txq->ndev = ndev; +#ifdef CONFIG_ECRNX_AMSDUS_TX + txq->amsdu = NULL; + txq->amsdu_len = 0; +#endif /* CONFIG_ECRNX_AMSDUS_TX */ +#endif /* CONFIG_ECRNX_SOFTMAC */ +} + +/** + * ecrnx_txq_flush - Flush all buffers queued for a TXQ + * + * @ecrnx_hw: main driver data + * @txq: txq to flush + */ +void ecrnx_txq_drop_skb(struct ecrnx_txq *txq, struct sk_buff *skb, struct ecrnx_hw *ecrnx_hw, bool retry_packet) +{ + struct ecrnx_sw_txhdr *sw_txhdr; + unsigned long queued_time = 0; + + skb_unlink(skb, &txq->sk_list); + + sw_txhdr = ((struct ecrnx_txhdr *)skb->data)->sw_hdr; + /* hwq->len doesn't count skb to retry */ +#ifdef CONFIG_ECRNX_FULLMAC + queued_time = jiffies - sw_txhdr->jiffies; +#endif /* CONFIG_ECRNX_SOFTMAC*/ + trace_txq_drop_skb(skb, txq, queued_time); + +#ifdef CONFIG_ECRNX_AMSDUS_TX + if (sw_txhdr->desc.host.packet_cnt > 1) { + struct ecrnx_amsdu_txhdr *amsdu_txhdr; + list_for_each_entry(amsdu_txhdr, &sw_txhdr->amsdu.hdrs, list) { +#ifndef CONFIG_ECRNX_ESWIN + dma_unmap_single(ecrnx_hw->dev, amsdu_txhdr->dma_addr, + amsdu_txhdr->map_len, DMA_TO_DEVICE); +#endif + dev_kfree_skb_any(amsdu_txhdr->skb); + } +#ifdef CONFIG_ECRNX_FULLMAC + if (txq->amsdu == sw_txhdr) + txq->amsdu = NULL; +#endif + } +#endif + kmem_cache_free(ecrnx_hw->sw_txhdr_cache, sw_txhdr); +#ifndef CONFIG_ECRNX_ESWIN + dma_unmap_single(ecrnx_hw->dev, sw_txhdr->dma_addr, sw_txhdr->map_len, + DMA_TO_DEVICE); +#endif + if (retry_packet) { + txq->nb_retry--; + if (txq->nb_retry == 0) { + WARN(skb != txq->last_retry_skb, + "last dropped retry buffer is not the expected one"); + txq->last_retry_skb = NULL; + } + } +#ifdef CONFIG_ECRNX_SOFTMAC + else + txq->hwq->len --; // hwq->len doesn't count skb to retry + ieee80211_free_txskb(ecrnx_hw->hw, skb); +#else + dev_kfree_skb_any(skb); +#endif /* CONFIG_ECRNX_SOFTMAC */ +} +/** + * ecrnx_txq_flush - Flush all buffers queued for a TXQ + * + * @ecrnx_hw: main driver data + * @txq: txq to flush + */ +void ecrnx_txq_flush(struct ecrnx_hw *ecrnx_hw, struct ecrnx_txq *txq) +{ + int i, pushed = 0; + + while(!skb_queue_empty(&txq->sk_list)) { + ecrnx_txq_drop_skb(txq, skb_peek(&txq->sk_list), ecrnx_hw, txq->nb_retry); + } + + for (i = 0; i < CONFIG_USER_MAX; i++) { + pushed += txq->pkt_pushed[i]; + } + + if (pushed) + dev_warn(ecrnx_hw->dev, "TXQ[%d]: %d skb still pushed to the FW", + txq->idx, pushed); +} + +/** + * ecrnx_txq_deinit - De-initialize a TX queue + * + * @ecrnx_hw: Driver main data + * @txq: TX queue to be de-initialized + * Any buffer stuck in a queue will be freed. + */ +static void ecrnx_txq_deinit(struct ecrnx_hw *ecrnx_hw, struct ecrnx_txq *txq) +{ + if (txq->idx == TXQ_INACTIVE) + return; + + spin_lock_bh(&ecrnx_hw->tx_lock); + ecrnx_txq_del_from_hw_list(txq); + txq->idx = TXQ_INACTIVE; + spin_unlock_bh(&ecrnx_hw->tx_lock); + + ecrnx_txq_flush(ecrnx_hw, txq); +} + +/** + * ecrnx_txq_vif_init - Initialize all TXQ linked to a vif + * + * @ecrnx_hw: main driver data + * @ecrnx_vif: Pointer on VIF + * @status: Intial txq status + * + * Softmac : 1 VIF TXQ per HWQ + * + * Fullmac : 1 VIF TXQ for BC/MC + * 1 VIF TXQ for MGMT to unknown STA + */ +void ecrnx_txq_vif_init(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + u8 status) +{ + struct ecrnx_txq *txq; + int idx; + +#ifdef CONFIG_ECRNX_SOFTMAC + int ac; + + idx = ecrnx_txq_vif_idx(ecrnx_vif, 0); + foreach_vif_txq(ecrnx_vif, txq, ac) { + if (txq) { + ecrnx_txq_init(txq, idx, status, &ecrnx_hw->hwq[ac], 0, NULL); +#ifdef CONFIG_MAC80211_TXQ + if (ac != ECRNX_HWQ_BE) + txq->nb_ready_mac80211 = NOT_MAC80211_TXQ; +#endif + } + idx++; + } + +#else + txq = ecrnx_txq_vif_get(ecrnx_vif, NX_BCMC_TXQ_TYPE); + idx = ecrnx_txq_vif_idx(ecrnx_vif, NX_BCMC_TXQ_TYPE); + ecrnx_txq_init(txq, idx, status, &ecrnx_hw->hwq[ECRNX_HWQ_BE], 0, + &ecrnx_hw->sta_table[ecrnx_vif->ap.bcmc_index], ecrnx_vif->ndev); + + txq = ecrnx_txq_vif_get(ecrnx_vif, NX_UNK_TXQ_TYPE); + idx = ecrnx_txq_vif_idx(ecrnx_vif, NX_UNK_TXQ_TYPE); + ecrnx_txq_init(txq, idx, status, &ecrnx_hw->hwq[ECRNX_HWQ_VO], TID_MGT, + NULL, ecrnx_vif->ndev); + +#endif /* CONFIG_ECRNX_SOFTMAC */ +} + +/** + * ecrnx_txq_vif_deinit - Deinitialize all TXQ linked to a vif + * + * @ecrnx_hw: main driver data + * @ecrnx_vif: Pointer on VIF + */ +void ecrnx_txq_vif_deinit(struct ecrnx_hw * ecrnx_hw, struct ecrnx_vif *ecrnx_vif) +{ + struct ecrnx_txq *txq; + +#ifdef CONFIG_ECRNX_SOFTMAC + int ac; + + foreach_vif_txq(ecrnx_vif, txq, ac) { + if (txq) + ecrnx_txq_deinit(ecrnx_hw, txq); + } + +#else + txq = ecrnx_txq_vif_get(ecrnx_vif, NX_BCMC_TXQ_TYPE); + ecrnx_txq_deinit(ecrnx_hw, txq); + + txq = ecrnx_txq_vif_get(ecrnx_vif, NX_UNK_TXQ_TYPE); + ecrnx_txq_deinit(ecrnx_hw, txq); + +#endif /* CONFIG_ECRNX_SOFTMAC */ +} + + +/** + * ecrnx_txq_sta_init - Initialize TX queues for a STA + * + * @ecrnx_hw: Main driver data + * @ecrnx_sta: STA for which tx queues need to be initialized + * @status: Intial txq status + * + * This function initialize all the TXQ associated to a STA. + * Softmac : 1 TXQ per TID + * + * Fullmac : 1 TXQ per TID (limited to 8) + * 1 TXQ for MGMT + */ +void ecrnx_txq_sta_init(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *ecrnx_sta, + u8 status) +{ + struct ecrnx_txq *txq; + int tid, idx; + +#ifdef CONFIG_ECRNX_SOFTMAC + struct ieee80211_sta *sta = ecrnx_to_ieee80211_sta(ecrnx_sta); + idx = ecrnx_txq_sta_idx(ecrnx_sta, 0); + + foreach_sta_txq(ecrnx_sta, txq, tid, ecrnx_hw) { + ecrnx_txq_init(txq, idx, status, &ecrnx_hw->hwq[ecrnx_tid2hwq[tid]], tid, sta); + idx++; + } + +#else + struct ecrnx_vif *ecrnx_vif = ecrnx_hw->vif_table[ecrnx_sta->vif_idx]; + idx = ecrnx_txq_sta_idx(ecrnx_sta, 0); + + foreach_sta_txq(ecrnx_sta, txq, tid, ecrnx_hw) { + ecrnx_txq_init(txq, idx, status, &ecrnx_hw->hwq[ecrnx_tid2hwq[tid]], tid, + ecrnx_sta, ecrnx_vif->ndev); + txq->ps_id = ecrnx_sta->uapsd_tids & (1 << tid) ? UAPSD_ID : LEGACY_PS_ID; + idx++; + } + +#endif /* CONFIG_ECRNX_SOFTMAC*/ +#ifndef CONFIG_ECRNX_ESWIN + ecrnx_ipc_sta_buffer_init(ecrnx_hw, ecrnx_sta->sta_idx); +#endif +} + +/** + * ecrnx_txq_sta_deinit - Deinitialize TX queues for a STA + * + * @ecrnx_hw: Main driver data + * @ecrnx_sta: STA for which tx queues need to be deinitialized + */ +void ecrnx_txq_sta_deinit(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *ecrnx_sta) +{ + struct ecrnx_txq *txq; + int tid; + + foreach_sta_txq(ecrnx_sta, txq, tid, ecrnx_hw) { + ecrnx_txq_deinit(ecrnx_hw, txq); + } +} + +#ifdef CONFIG_ECRNX_FULLMAC +/** + * ecrnx_txq_unk_vif_init - Initialize TXQ for unknown STA linked to a vif + * + * @ecrnx_vif: Pointer on VIF + */ +void ecrnx_txq_unk_vif_init(struct ecrnx_vif *ecrnx_vif) +{ + struct ecrnx_hw *ecrnx_hw = ecrnx_vif->ecrnx_hw; + struct ecrnx_txq *txq; + int idx; + + txq = ecrnx_txq_vif_get(ecrnx_vif, NX_UNK_TXQ_TYPE); + idx = ecrnx_txq_vif_idx(ecrnx_vif, NX_UNK_TXQ_TYPE); + ecrnx_txq_init(txq, idx, 0, &ecrnx_hw->hwq[ECRNX_HWQ_VO], TID_MGT, NULL, ecrnx_vif->ndev); +} + +/** + * ecrnx_txq_tdls_vif_deinit - Deinitialize TXQ for unknown STA linked to a vif + * + * @ecrnx_vif: Pointer on VIF + */ +void ecrnx_txq_unk_vif_deinit(struct ecrnx_vif *ecrnx_vif) +{ + struct ecrnx_txq *txq; + + txq = ecrnx_txq_vif_get(ecrnx_vif, NX_UNK_TXQ_TYPE); + ecrnx_txq_deinit(ecrnx_vif->ecrnx_hw, txq); +} + +/** + * ecrnx_init_unk_txq - Initialize TX queue for the transmission on a offchannel + * + * @vif: Interface for which the queue has to be initialized + * + * NOTE: Offchannel txq is only active for the duration of the ROC + */ +void ecrnx_txq_offchan_init(struct ecrnx_vif *ecrnx_vif) +{ + struct ecrnx_hw *ecrnx_hw = ecrnx_vif->ecrnx_hw; + struct ecrnx_txq *txq; + + txq = &ecrnx_hw->txq[NX_OFF_CHAN_TXQ_IDX]; + ecrnx_txq_init(txq, NX_OFF_CHAN_TXQ_IDX, ECRNX_TXQ_STOP_CHAN, + &ecrnx_hw->hwq[ECRNX_HWQ_VO], TID_MGT, NULL, ecrnx_vif->ndev); +} + +/** + * ecrnx_deinit_offchan_txq - Deinitialize TX queue for offchannel + * + * @vif: Interface that manages the STA + * + * This function deintialize txq for one STA. + * Any buffer stuck in a queue will be freed. + */ +void ecrnx_txq_offchan_deinit(struct ecrnx_vif *ecrnx_vif) +{ + struct ecrnx_txq *txq; + + txq = &ecrnx_vif->ecrnx_hw->txq[NX_OFF_CHAN_TXQ_IDX]; + ecrnx_txq_deinit(ecrnx_vif->ecrnx_hw, txq); +} + + +/** + * ecrnx_txq_tdls_vif_init - Initialize TXQ vif for TDLS + * + * @ecrnx_vif: Pointer on VIF + */ +void ecrnx_txq_tdls_vif_init(struct ecrnx_vif *ecrnx_vif) +{ + struct ecrnx_hw *ecrnx_hw = ecrnx_vif->ecrnx_hw; + + if (!(ecrnx_hw->wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS)) + return; + + ecrnx_txq_unk_vif_init(ecrnx_vif); +} + +/** + * ecrnx_txq_tdls_vif_deinit - Deinitialize TXQ vif for TDLS + * + * @ecrnx_vif: Pointer on VIF + */ +void ecrnx_txq_tdls_vif_deinit(struct ecrnx_vif *ecrnx_vif) +{ + struct ecrnx_hw *ecrnx_hw = ecrnx_vif->ecrnx_hw; + + if (!(ecrnx_hw->wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS)) + return; + + ecrnx_txq_unk_vif_deinit(ecrnx_vif); +} +/** + * ecrnx_txq_drop_old_traffic - Drop pkt queued for too long in a TXQ + * + * @txq: TXQ to process + * @ecrnx_hw: Driver main data + * @skb_timeout: Max queue duration, in jiffies, for this queue + * @dropped: Updated to inidicate if at least one skb was dropped + * + * @return Whether there is still pkt queued in this queue. + */ +static bool ecrnx_txq_drop_old_traffic(struct ecrnx_txq *txq, struct ecrnx_hw *ecrnx_hw, + unsigned long skb_timeout, bool *dropped) +{ + struct sk_buff *skb, *skb_next; + bool pkt_queued = false; + int retry_packet = txq->nb_retry; + + if (txq->idx == TXQ_INACTIVE) + return false; + + spin_lock(&ecrnx_hw->tx_lock); + + skb_queue_walk_safe(&txq->sk_list, skb, skb_next) { + + struct ecrnx_sw_txhdr *sw_txhdr; + + if (retry_packet) { + // Don't drop retry packets + retry_packet--; + continue; + } + + sw_txhdr = ((struct ecrnx_txhdr *)skb->data)->sw_hdr; + + if (!time_after(jiffies, sw_txhdr->jiffies + skb_timeout)) { + pkt_queued = true; + break; + } + + *dropped = true; + ECRNX_WARN("%s:skb:0x%08x,txq:0x%p, txq_idx:%d, ps_active:%d, ps_id:%d \n", __func__, skb, txq, txq->idx, txq->sta->ps.active, txq->ps_id); + ecrnx_txq_drop_skb(txq, skb, ecrnx_hw, false); + if (txq->sta && txq->sta->ps.active) { + txq->sta->ps.pkt_ready[txq->ps_id]--; + if (txq->sta->ps.pkt_ready[txq->ps_id] == 0) + ecrnx_set_traffic_status(ecrnx_hw, txq->sta, false, txq->ps_id); + + // drop packet during PS service period + if (txq->sta->ps.sp_cnt[txq->ps_id]) { + txq->sta->ps.sp_cnt[txq->ps_id] --; + if (txq->push_limit) + txq->push_limit--; + if (WARN(((txq->ps_id == UAPSD_ID) && + (txq->sta->ps.sp_cnt[txq->ps_id] == 0)), + "Drop last packet of UAPSD service period")) { + // TODO: inform FW to end SP + } + } + trace_ps_drop(txq->sta); + } + } + + if (skb_queue_empty(&txq->sk_list)) { + ecrnx_txq_del_from_hw_list(txq); + txq->pkt_sent = 0; + } + + spin_unlock(&ecrnx_hw->tx_lock); + +#ifdef CONFIG_ECRNX_FULLMAC + /* restart netdev queue if number no more queued buffer */ + if (unlikely(txq->status & ECRNX_TXQ_NDEV_FLOW_CTRL) && + skb_queue_empty(&txq->sk_list)) { + txq->status &= ~ECRNX_TXQ_NDEV_FLOW_CTRL; + netif_wake_subqueue(txq->ndev, txq->ndev_idx); + trace_txq_flowctrl_restart(txq); + } +#endif /* CONFIG_ECRNX_FULLMAC */ + + return pkt_queued; +} + +/** + * ecrnx_txq_drop_ap_vif_old_traffic - Drop pkt queued for too long in TXQs + * linked to an "AP" vif (AP, MESH, P2P_GO) + * + * @vif: Vif to process + * @return Whether there is still pkt queued in any TXQ. + */ +static bool ecrnx_txq_drop_ap_vif_old_traffic(struct ecrnx_vif *vif) +{ + struct ecrnx_sta *sta; + unsigned long timeout = (vif->ap.bcn_interval * HZ * 3) >> 10; + bool pkt_queued = false; + bool pkt_dropped = false; + + // Should never be needed but still check VIF queues + ecrnx_txq_drop_old_traffic(ecrnx_txq_vif_get(vif, NX_BCMC_TXQ_TYPE), + vif->ecrnx_hw, ECRNX_TXQ_MAX_QUEUE_JIFFIES, &pkt_dropped); + ecrnx_txq_drop_old_traffic(ecrnx_txq_vif_get(vif, NX_UNK_TXQ_TYPE), + vif->ecrnx_hw, ECRNX_TXQ_MAX_QUEUE_JIFFIES, &pkt_dropped); + ECRNX_WARN("Dropped packet in BCMC/UNK queue. \n"); + + list_for_each_entry(sta, &vif->ap.sta_list, list) { + struct ecrnx_txq *txq; + int tid; + foreach_sta_txq(sta, txq, tid, vif->ecrnx_hw) { + pkt_queued |= ecrnx_txq_drop_old_traffic(txq, vif->ecrnx_hw, + timeout * sta->listen_interval, + &pkt_dropped); + } + } + + return pkt_queued; +} + +/** + * ecrnx_txq_drop_sta_vif_old_traffic - Drop pkt queued for too long in TXQs + * linked to a "STA" vif. In theory this should not be required as there is no + * case where traffic can accumulate in a STA interface. + * + * @vif: Vif to process + * @return Whether there is still pkt queued in any TXQ. + */ +static bool ecrnx_txq_drop_sta_vif_old_traffic(struct ecrnx_vif *vif) +{ + struct ecrnx_txq *txq; + bool pkt_queued = false, pkt_dropped = false; + int tid; + + if (vif->tdls_status == TDLS_LINK_ACTIVE) { + txq = ecrnx_txq_vif_get(vif, NX_UNK_TXQ_TYPE); + pkt_queued |= ecrnx_txq_drop_old_traffic(txq, vif->ecrnx_hw, + ECRNX_TXQ_MAX_QUEUE_JIFFIES, + &pkt_dropped); + foreach_sta_txq(vif->sta.tdls_sta, txq, tid, vif->ecrnx_hw) { + pkt_queued |= ecrnx_txq_drop_old_traffic(txq, vif->ecrnx_hw, + ECRNX_TXQ_MAX_QUEUE_JIFFIES, + &pkt_dropped); + } + } + + if (vif->sta.ap) { + foreach_sta_txq(vif->sta.ap, txq, tid, vif->ecrnx_hw) { + pkt_queued |= ecrnx_txq_drop_old_traffic(txq, vif->ecrnx_hw, + ECRNX_TXQ_MAX_QUEUE_JIFFIES, + &pkt_dropped); + } + } + + if (pkt_dropped) { + ECRNX_WARN("Dropped packet in STA interface TXQs. \n"); + } + return pkt_queued; +} + +/** + * ecrnx_txq_cleanup_timer_cb - callack for TXQ cleaup timer + * Used to prevent pkt to accumulate in TXQ. The main use case is for AP + * interface with client in Power Save mode but just in case all TXQs are + * checked. + * + * @t: timer structure + */ +static void ecrnx_txq_cleanup_timer_cb(struct timer_list *t) +{ + struct ecrnx_hw *ecrnx_hw = from_timer(ecrnx_hw, t, txq_cleanup); + struct ecrnx_vif *vif; + bool pkt_queue = false; + + list_for_each_entry(vif, &ecrnx_hw->vifs, list) { + switch (ECRNX_VIF_TYPE(vif)) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + case NL80211_IFTYPE_MESH_POINT: + pkt_queue |= ecrnx_txq_drop_ap_vif_old_traffic(vif); + break; + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + pkt_queue |= ecrnx_txq_drop_sta_vif_old_traffic(vif); + break; + case NL80211_IFTYPE_AP_VLAN: + case NL80211_IFTYPE_MONITOR: + default: + continue; + } + } + + if (pkt_queue) + mod_timer(t, jiffies + ECRNX_TXQ_CLEANUP_INTERVAL); +} + +/** + * ecrnx_txq_start_cleanup_timer - Start 'cleanup' timer if not started + * + * @ecrnx_hw: Driver main data + */ +void ecrnx_txq_start_cleanup_timer(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta) +{ + if (sta && !is_multicast_sta(sta->sta_idx) && + !timer_pending(&ecrnx_hw->txq_cleanup)) + mod_timer(&ecrnx_hw->txq_cleanup, jiffies + ECRNX_TXQ_CLEANUP_INTERVAL); +} + +/** + * ecrnx_txq_prepare - Global initialization of txq + * + * @ecrnx_hw: Driver main data + */ +void ecrnx_txq_prepare(struct ecrnx_hw *ecrnx_hw) +{ + int i; + + for (i = 0; i < NX_NB_TXQ; i++) { + ecrnx_hw->txq[i].idx = TXQ_INACTIVE; + } + + timer_setup(&ecrnx_hw->txq_cleanup, ecrnx_txq_cleanup_timer_cb, 0); +} +#endif + +/****************************************************************************** + * Start/Stop functions + *****************************************************************************/ +/** + * ecrnx_txq_add_to_hw_list - Add TX queue to a HW queue schedule list. + * + * @txq: TX queue to add + * + * Add the TX queue if not already present in the HW queue list. + * To be called with tx_lock hold + */ +void ecrnx_txq_add_to_hw_list(struct ecrnx_txq *txq) +{ + if (!(txq->status & ECRNX_TXQ_IN_HWQ_LIST)) { + trace_txq_add_to_hw(txq); + txq->status |= ECRNX_TXQ_IN_HWQ_LIST; + list_add_tail(&txq->sched_list, &txq->hwq->list); + txq->hwq->need_processing = true; + } +} + +/** + * ecrnx_txq_del_from_hw_list - Delete TX queue from a HW queue schedule list. + * + * @txq: TX queue to delete + * + * Remove the TX queue from the HW queue list if present. + * To be called with tx_lock hold + */ +void ecrnx_txq_del_from_hw_list(struct ecrnx_txq *txq) +{ + if (txq->status & ECRNX_TXQ_IN_HWQ_LIST) { + trace_txq_del_from_hw(txq); + txq->status &= ~ECRNX_TXQ_IN_HWQ_LIST; + list_del(&txq->sched_list); + } +} + +/** + * ecrnx_txq_skb_ready - Check if skb are available for the txq + * + * @txq: Pointer on txq + * @return True if there are buffer ready to be pushed on this txq, + * false otherwise + */ +static inline bool ecrnx_txq_skb_ready(struct ecrnx_txq *txq) +{ +#ifdef CONFIG_MAC80211_TXQ + if (txq->nb_ready_mac80211 != NOT_MAC80211_TXQ) + return ((txq->nb_ready_mac80211 > 0) || !skb_queue_empty(&txq->sk_list)); + else +#endif + return !skb_queue_empty(&txq->sk_list); +} + +/** + * ecrnx_txq_start - Try to Start one TX queue + * + * @txq: TX queue to start + * @reason: reason why the TX queue is started (among ECRNX_TXQ_STOP_xxx) + * + * Re-start the TX queue for one reason. + * If after this the txq is no longer stopped and some buffers are ready, + * the TX queue is also added to HW queue list. + * To be called with tx_lock hold + */ +void ecrnx_txq_start(struct ecrnx_txq *txq, u16 reason) +{ + BUG_ON(txq==NULL); + if (txq->idx != TXQ_INACTIVE && (txq->status & reason)) + { + trace_txq_start(txq, reason); + txq->status &= ~reason; + if (!ecrnx_txq_is_stopped(txq) && ecrnx_txq_skb_ready(txq)) + ecrnx_txq_add_to_hw_list(txq); + } +} + +/** + * ecrnx_txq_stop - Stop one TX queue + * + * @txq: TX queue to stop + * @reason: reason why the TX queue is stopped (among ECRNX_TXQ_STOP_xxx) + * + * Stop the TX queue. It will remove the TX queue from HW queue list + * To be called with tx_lock hold + */ +void ecrnx_txq_stop(struct ecrnx_txq *txq, u16 reason) +{ + BUG_ON(txq==NULL); + if (txq->idx != TXQ_INACTIVE) + { + trace_txq_stop(txq, reason); + txq->status |= reason; + ecrnx_txq_del_from_hw_list(txq); + } +} + + +/** + * ecrnx_txq_sta_start - Start all the TX queue linked to a STA + * + * @sta: STA whose TX queues must be re-started + * @reason: Reason why the TX queue are restarted (among ECRNX_TXQ_STOP_xxx) + * @ecrnx_hw: Driver main data + * + * This function will re-start all the TX queues of the STA for the reason + * specified. It can be : + * - ECRNX_TXQ_STOP_STA_PS: the STA is no longer in power save mode + * - ECRNX_TXQ_STOP_VIF_PS: the VIF is in power save mode (p2p absence) + * - ECRNX_TXQ_STOP_CHAN: the STA's VIF is now on the current active channel + * + * Any TX queue with buffer ready and not Stopped for other reasons, will be + * added to the HW queue list + * To be called with tx_lock hold + */ +void ecrnx_txq_sta_start(struct ecrnx_sta *ecrnx_sta, u16 reason +#ifdef CONFIG_ECRNX_FULLMAC + , struct ecrnx_hw *ecrnx_hw +#endif + ) +{ + struct ecrnx_txq *txq; + int tid; + + trace_txq_sta_start(ecrnx_sta->sta_idx); + ECRNX_DBG("%s-%d:ecrnx_txq_stop,reaosn:0x%x,sta:0x%08x,sta_index:0x%08x \n", __func__, __LINE__, reason, ecrnx_sta, ecrnx_sta->sta_idx); + foreach_sta_txq(ecrnx_sta, txq, tid, ecrnx_hw) { + ecrnx_txq_start(txq, reason); + + if (txq->idx != TXQ_INACTIVE && !skb_queue_empty(&txq->sk_list)) + { + ecrnx_hwq_process(ecrnx_hw, txq->hwq); + } + } +} + + +/** + * ecrnx_stop_sta_txq - Stop all the TX queue linked to a STA + * + * @sta: STA whose TX queues must be stopped + * @reason: Reason why the TX queue are stopped (among ECRNX_TX_STOP_xxx) + * @ecrnx_hw: Driver main data + * + * This function will stop all the TX queues of the STA for the reason + * specified. It can be : + * - ECRNX_TXQ_STOP_STA_PS: the STA is in power save mode + * - ECRNX_TXQ_STOP_VIF_PS: the VIF is in power save mode (p2p absence) + * - ECRNX_TXQ_STOP_CHAN: the STA's VIF is not on the current active channel + * + * Any TX queue present in a HW queue list will be removed from this list. + * To be called with tx_lock hold + */ +void ecrnx_txq_sta_stop(struct ecrnx_sta *ecrnx_sta, u16 reason +#ifdef CONFIG_ECRNX_FULLMAC + , struct ecrnx_hw *ecrnx_hw +#endif + ) +{ + struct ecrnx_txq *txq; + int tid; + + if (!ecrnx_sta) + return; + + trace_txq_sta_stop(ecrnx_sta->sta_idx); + foreach_sta_txq(ecrnx_sta, txq, tid, ecrnx_hw) { + ECRNX_DBG("%s-%d:stop_reaosn:0x%x,sta:0x%08x,sta_index:0x%08x ,txq:0x%p,tid:%d \n", __func__, __LINE__, reason, ecrnx_sta, ecrnx_sta->sta_idx, txq, tid); + ecrnx_txq_stop(txq, reason); + } +} + +#ifdef CONFIG_ECRNX_SOFTMAC +void ecrnx_txq_tdls_sta_start(struct ecrnx_sta *ecrnx_sta, u16 reason, + struct ecrnx_hw *ecrnx_hw) +#else +void ecrnx_txq_tdls_sta_start(struct ecrnx_vif *ecrnx_vif, u16 reason, + struct ecrnx_hw *ecrnx_hw) +#endif +{ +#ifdef CONFIG_ECRNX_SOFTMAC + trace_txq_vif_start(ecrnx_sta->vif_idx); + spin_lock_bh(&ecrnx_hw->tx_lock); + + if (ecrnx_sta->tdls.active) { + ecrnx_txq_sta_start(ecrnx_sta, reason); + } + + spin_unlock_bh(&ecrnx_hw->tx_lock); +#else + trace_txq_vif_start(ecrnx_vif->vif_index); + spin_lock_bh(&ecrnx_hw->tx_lock); + + if (ecrnx_vif->sta.tdls_sta) + ecrnx_txq_sta_start(ecrnx_vif->sta.tdls_sta, reason, ecrnx_hw); + + spin_unlock_bh(&ecrnx_hw->tx_lock); +#endif +} + +#ifdef CONFIG_ECRNX_SOFTMAC +void ecrnx_txq_tdls_sta_stop(struct ecrnx_sta *ecrnx_sta, u16 reason, + struct ecrnx_hw *ecrnx_hw) +#else +void ecrnx_txq_tdls_sta_stop(struct ecrnx_vif *ecrnx_vif, u16 reason, + struct ecrnx_hw *ecrnx_hw) +#endif +{ +#ifdef CONFIG_ECRNX_SOFTMAC + trace_txq_vif_stop(ecrnx_sta->vif_idx); + + spin_lock_bh(&ecrnx_hw->tx_lock); + + if (ecrnx_sta->tdls.active) { + ecrnx_txq_sta_stop(ecrnx_sta, reason); + } + + spin_unlock_bh(&ecrnx_hw->tx_lock); +#else + trace_txq_vif_stop(ecrnx_vif->vif_index); + + spin_lock_bh(&ecrnx_hw->tx_lock); + + if (ecrnx_vif->sta.tdls_sta) + ecrnx_txq_sta_stop(ecrnx_vif->sta.tdls_sta, reason, ecrnx_hw); + + spin_unlock_bh(&ecrnx_hw->tx_lock); +#endif +} + +#ifdef CONFIG_ECRNX_FULLMAC +static inline +void ecrnx_txq_vif_for_each_sta(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + void (*f)(struct ecrnx_sta *, u16, struct ecrnx_hw *), + u16 reason) +{ + switch (ECRNX_VIF_TYPE(ecrnx_vif)) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + { + if (ecrnx_vif->tdls_status == TDLS_LINK_ACTIVE) + f(ecrnx_vif->sta.tdls_sta, reason, ecrnx_hw); +#ifdef CONFIG_ECRNX_P2P + if (!(ecrnx_vif->sta.ap == NULL)) +#else + if (!WARN_ON(ecrnx_vif->sta.ap == NULL)) +#endif + f(ecrnx_vif->sta.ap, reason, ecrnx_hw); + break; + } + case NL80211_IFTYPE_AP_VLAN: + { + ecrnx_vif = ecrnx_vif->ap_vlan.master; + struct ecrnx_sta *sta; + list_for_each_entry(sta, &ecrnx_vif->ap.sta_list, list) { + f(sta, reason, ecrnx_hw); + } + break; + } + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_MESH_POINT: + case NL80211_IFTYPE_P2P_GO: + { + struct ecrnx_sta *sta; + list_for_each_entry(sta, &ecrnx_vif->ap.sta_list, list) { + f(sta, reason, ecrnx_hw); + } + break; + } + default: + BUG(); + break; + } +} + +#endif + +/** + * ecrnx_txq_vif_start - START TX queues of all STA associated to the vif + * and vif's TXQ + * + * @vif: Interface to start + * @reason: Start reason (ECRNX_TXQ_STOP_CHAN or ECRNX_TXQ_STOP_VIF_PS) + * @ecrnx_hw: Driver main data + * + * Iterate over all the STA associated to the vif and re-start them for the + * reason @reason + * Take tx_lock + */ +void ecrnx_txq_vif_start(struct ecrnx_vif *ecrnx_vif, u16 reason, + struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_txq *txq; +#ifdef CONFIG_ECRNX_SOFTMAC + struct ecrnx_sta *ecrnx_sta; + int ac; +#endif + + trace_txq_vif_start(ecrnx_vif->vif_index); + + spin_lock_bh(&ecrnx_hw->tx_lock); + +#ifdef CONFIG_ECRNX_SOFTMAC + list_for_each_entry(ecrnx_sta, &ecrnx_vif->stations, list) { + if ((!ecrnx_vif->roc_tdls) || + (ecrnx_sta->tdls.active && ecrnx_vif->roc_tdls && ecrnx_sta->tdls.chsw_en)) + ecrnx_txq_sta_start(ecrnx_sta, reason); + } + + foreach_vif_txq(ecrnx_vif, txq, ac) { + if (txq) + ecrnx_txq_start(txq, reason); + } +#else + //Reject if monitor interface + if (ecrnx_vif->wdev.iftype == NL80211_IFTYPE_MONITOR) + goto end; + + if (ecrnx_vif->roc_tdls && ecrnx_vif->sta.tdls_sta && ecrnx_vif->sta.tdls_sta->tdls.chsw_en) { + ecrnx_txq_sta_start(ecrnx_vif->sta.tdls_sta, reason, ecrnx_hw); + } + if (!ecrnx_vif->roc_tdls) { + ecrnx_txq_vif_for_each_sta(ecrnx_hw, ecrnx_vif, ecrnx_txq_sta_start, reason); + } + + txq = ecrnx_txq_vif_get(ecrnx_vif, NX_BCMC_TXQ_TYPE); + ecrnx_txq_start(txq, reason); + txq = ecrnx_txq_vif_get(ecrnx_vif, NX_UNK_TXQ_TYPE); + ecrnx_txq_start(txq, reason); + +end: +#endif /* CONFIG_ECRNX_SOFTMAC */ + + spin_unlock_bh(&ecrnx_hw->tx_lock); +} + + +/** + * ecrnx_txq_vif_stop - STOP TX queues of all STA associated to the vif + * + * @vif: Interface to stop + * @arg: Stop reason (ECRNX_TXQ_STOP_CHAN or ECRNX_TXQ_STOP_VIF_PS) + * @ecrnx_hw: Driver main data + * + * Iterate over all the STA associated to the vif and stop them for the + * reason ECRNX_TXQ_STOP_CHAN or ECRNX_TXQ_STOP_VIF_PS + * Take tx_lock + */ +void ecrnx_txq_vif_stop(struct ecrnx_vif *ecrnx_vif, u16 reason, + struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_txq *txq; +#ifdef CONFIG_ECRNX_SOFTMAC + struct ecrnx_sta *sta; + int ac; +#endif + + trace_txq_vif_stop(ecrnx_vif->vif_index); + spin_lock_bh(&ecrnx_hw->tx_lock); + +#ifdef CONFIG_ECRNX_SOFTMAC + list_for_each_entry(sta, &ecrnx_vif->stations, list) { + ecrnx_txq_sta_stop(sta, reason); + } + + foreach_vif_txq(ecrnx_vif, txq, ac) { + if (txq) + { + ecrnx_txq_stop(txq, reason); + } + } + +#else + //Reject if monitor interface + if (ecrnx_vif->wdev.iftype == NL80211_IFTYPE_MONITOR) + goto end; + + ecrnx_txq_vif_for_each_sta(ecrnx_hw, ecrnx_vif, ecrnx_txq_sta_stop, reason); + + txq = ecrnx_txq_vif_get(ecrnx_vif, NX_BCMC_TXQ_TYPE); + ecrnx_txq_stop(txq, reason); + txq = ecrnx_txq_vif_get(ecrnx_vif, NX_UNK_TXQ_TYPE); + ecrnx_txq_stop(txq, reason); + +end: +#endif /* CONFIG_ECRNX_SOFTMAC*/ + + spin_unlock_bh(&ecrnx_hw->tx_lock); +} + +#ifdef CONFIG_ECRNX_FULLMAC + +/** + * ecrnx_start_offchan_txq - START TX queue for offchannel frame + * + * @ecrnx_hw: Driver main data + */ +void ecrnx_txq_offchan_start(struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_txq *txq; + + txq = &ecrnx_hw->txq[NX_OFF_CHAN_TXQ_IDX]; + spin_lock_bh(&ecrnx_hw->tx_lock); + ecrnx_txq_start(txq, ECRNX_TXQ_STOP_CHAN); + spin_unlock_bh(&ecrnx_hw->tx_lock); + ECRNX_DBG("%s-%d:ecrnx_txq_start,reaosn:0x%x \n", __func__, __LINE__, ECRNX_TXQ_STOP_CHAN); +} + +/** + * ecrnx_switch_vif_sta_txq - Associate TXQ linked to a STA to a new vif + * + * @sta: STA whose txq must be switched + * @old_vif: Vif currently associated to the STA (may no longer be active) + * @new_vif: vif which should be associated to the STA for now on + * + * This function will switch the vif (i.e. the netdev) associated to all STA's + * TXQ. This is used when AP_VLAN interface are created. + * If one STA is associated to an AP_vlan vif, it will be moved from the master + * AP vif to the AP_vlan vif. + * If an AP_vlan vif is removed, then STA will be moved back to mastert AP vif. + * + */ +void ecrnx_txq_sta_switch_vif(struct ecrnx_sta *sta, struct ecrnx_vif *old_vif, + struct ecrnx_vif *new_vif) +{ + struct ecrnx_hw *ecrnx_hw = new_vif->ecrnx_hw; + struct ecrnx_txq *txq; + int i; + + /* start TXQ on the new interface, and update ndev field in txq */ + if (!netif_carrier_ok(new_vif->ndev)) + netif_carrier_on(new_vif->ndev); + txq = ecrnx_txq_sta_get(sta, 0, ecrnx_hw); + for (i = 0; i < NX_NB_TID_PER_STA; i++, txq++) { + txq->ndev = new_vif->ndev; + netif_wake_subqueue(txq->ndev, txq->ndev_idx); + } +} +#endif /* CONFIG_ECRNX_FULLMAC */ + +/****************************************************************************** + * TXQ queue/schedule functions + *****************************************************************************/ +/** + * ecrnx_txq_queue_skb - Queue a buffer in a TX queue + * + * @skb: Buffer to queue + * @txq: TX Queue in which the buffer must be added + * @ecrnx_hw: Driver main data + * @retry: Should it be queued in the retry list + * + * @return: Retrun 1 if txq has been added to hwq list, 0 otherwise + * + * Add a buffer in the buffer list of the TX queue + * and add this TX queue in the HW queue list if the txq is not stopped. + * If this is a retry packet it is added after the last retry packet or at the + * beginning if there is no retry packet queued. + * + * If the STA is in PS mode and this is the first packet queued for this txq + * update TIM. + * + * To be called with tx_lock hold + */ +int ecrnx_txq_queue_skb(struct sk_buff *skb, struct ecrnx_txq *txq, + struct ecrnx_hw *ecrnx_hw, bool retry, + struct sk_buff *skb_prev) +{ + +#ifdef CONFIG_ECRNX_FULLMAC + if (unlikely(txq->sta && txq->sta->ps.active)) { + txq->sta->ps.pkt_ready[txq->ps_id]++; + trace_ps_queue(txq->sta); + + if (txq->sta->ps.pkt_ready[txq->ps_id] == 1) { + ecrnx_set_traffic_status(ecrnx_hw, txq->sta, true, txq->ps_id); + } + } +#endif + + if (!retry) { + /* add buffer in the sk_list */ + if (skb_prev) + skb_append(skb_prev, skb, &txq->sk_list); + else + skb_queue_tail(&txq->sk_list, skb); +#ifdef CONFIG_ECRNX_FULLMAC + // to update for SOFTMAC + ecrnx_ipc_sta_buffer(ecrnx_hw, txq->sta, txq->tid, + ((struct ecrnx_txhdr *)skb->data)->sw_hdr->frame_len); + ecrnx_txq_start_cleanup_timer(ecrnx_hw, txq->sta); +#endif + } else { + if (txq->last_retry_skb) + skb_append(txq->last_retry_skb, skb, &txq->sk_list); + else + skb_queue_head(&txq->sk_list, skb); + + txq->last_retry_skb = skb; + txq->nb_retry++; + } + + trace_txq_queue_skb(skb, txq, retry); + + /* Flowctrl corresponding netdev queue if needed */ +#ifdef CONFIG_ECRNX_FULLMAC + /* If too many buffer are queued for this TXQ stop netdev queue */ + if ((txq->ndev_idx != NDEV_NO_TXQ) && + (skb_queue_len(&txq->sk_list) > ECRNX_NDEV_FLOW_CTRL_STOP)) { + txq->status |= ECRNX_TXQ_NDEV_FLOW_CTRL; + netif_stop_subqueue(txq->ndev, txq->ndev_idx); + trace_txq_flowctrl_stop(txq); + } +#else /* ! CONFIG_ECRNX_FULLMAC */ + + if (!retry && ++txq->hwq->len == txq->hwq->len_stop) { + trace_hwq_flowctrl_stop(txq->hwq->id); + ieee80211_stop_queue(ecrnx_hw->hw, txq->hwq->id); + ecrnx_hw->stats.queues_stops++; + } +#endif /* CONFIG_ECRNX_FULLMAC */ + + //ECRNX_DBG("txq status: 0x%x \n", txq->status); + /* add it in the hwq list if not stopped and not yet present */ + if (!ecrnx_txq_is_stopped(txq)) { + ecrnx_txq_add_to_hw_list(txq); + return 1; + } + + return 0; +} + +/** + * ecrnx_txq_confirm_any - Process buffer confirmed by fw + * + * @ecrnx_hw: Driver main data + * @txq: TX Queue + * @hwq: HW Queue + * @sw_txhdr: software descriptor of the confirmed packet + * + * Process a buffer returned by the fw. It doesn't check buffer status + * and only does systematic counter update: + * - hw credit + * - buffer pushed to fw + * + * To be called with tx_lock hold + */ +void ecrnx_txq_confirm_any(struct ecrnx_hw *ecrnx_hw, struct ecrnx_txq *txq, + struct ecrnx_hwq *hwq, struct ecrnx_sw_txhdr *sw_txhdr) +{ + int user = 0; + +#ifdef CONFIG_ECRNX_MUMIMO_TX + int group_id; + + user = ECRNX_MUMIMO_INFO_POS_ID(sw_txhdr->desc.host.mumimo_info); + group_id = ECRNX_MUMIMO_INFO_GROUP_ID(sw_txhdr->desc.host.mumimo_info); + + if ((txq->idx != TXQ_INACTIVE) && + (txq->pkt_pushed[user] == 1) && + (txq->status & ECRNX_TXQ_STOP_MU_POS)){ + ecrnx_txq_start(txq, ECRNX_TXQ_STOP_MU_POS); + ECRNX_DBG("%s-%d:ecrnx_txq_start,reaosn:0x%x \n", __func__, __LINE__, ECRNX_TXQ_STOP_MU_POS); + } + +#endif /* CONFIG_ECRNX_MUMIMO_TX */ + + if (txq->pkt_pushed[user]) + txq->pkt_pushed[user]--; + + hwq->credits[user]++; + hwq->need_processing = true; + ecrnx_hw->stats.cfm_balance[hwq->id]--; +} + +/****************************************************************************** + * HWQ processing + *****************************************************************************/ +static inline +bool ecrnx_txq_take_mu_lock(struct ecrnx_hw *ecrnx_hw) +{ + bool res = false; +#ifdef CONFIG_ECRNX_MUMIMO_TX + if (ecrnx_hw->mod_params->mutx) + res = (down_trylock(&ecrnx_hw->mu.lock) == 0); +#endif /* CONFIG_ECRNX_MUMIMO_TX */ + return res; +} + +static inline +void ecrnx_txq_release_mu_lock(struct ecrnx_hw *ecrnx_hw) +{ +#ifdef CONFIG_ECRNX_MUMIMO_TX + up(&ecrnx_hw->mu.lock); +#endif /* CONFIG_ECRNX_MUMIMO_TX */ +} + +static inline +void ecrnx_txq_set_mu_info(struct ecrnx_hw *ecrnx_hw, struct ecrnx_txq *txq, + int group_id, int pos) +{ +#ifdef CONFIG_ECRNX_MUMIMO_TX + trace_txq_select_mu_group(txq, group_id, pos); + if (group_id) { + txq->mumimo_info = group_id | (pos << 6); + ecrnx_mu_set_active_group(ecrnx_hw, group_id); + } else + txq->mumimo_info = 0; +#endif /* CONFIG_ECRNX_MUMIMO_TX */ +} + +static inline +s8 ecrnx_txq_get_credits(struct ecrnx_txq *txq) +{ + s8 cred = txq->credits; + /* if destination is in PS mode, push_limit indicates the maximum + number of packet that can be pushed on this txq. */ + if (txq->push_limit && (cred > txq->push_limit)) { + cred = txq->push_limit; + } + return cred; +} + +/** + * skb_queue_extract - Extract buffer from skb list + * + * @list: List of skb to extract from + * @head: List of skb to append to + * @nb_elt: Number of skb to extract + * + * extract the first @nb_elt of @list and append them to @head + * It is assume that: + * - @list contains more that @nb_elt + * - There is no need to take @list nor @head lock to modify them + */ +static inline void skb_queue_extract(struct sk_buff_head *list, + struct sk_buff_head *head, int nb_elt) +{ + int i; + struct sk_buff *first, *last, *ptr; + + first = ptr = list->next; + for (i = 0; i < nb_elt; i++) { + ptr = ptr->next; + } + last = ptr->prev; + + /* unlink nb_elt in list */ + list->qlen -= nb_elt; + list->next = ptr; + ptr->prev = (struct sk_buff *)list; + + /* append nb_elt at end of head */ + head->qlen += nb_elt; + last->next = (struct sk_buff *)head; + head->prev->next = first; + first->prev = head->prev; + head->prev = last; +} + + +#ifdef CONFIG_MAC80211_TXQ +/** + * ecrnx_txq_mac80211_dequeue - Dequeue buffer from mac80211 txq and + * add them to push list + * + * @ecrnx_hw: Main driver data + * @sk_list: List of buffer to push (initialized without lock) + * @txq: TXQ to dequeue buffers from + * @max: Max number of buffer to dequeue + * + * Dequeue buffer from mac80211 txq, prepare them for transmission and chain them + * to the list of buffer to push. + * + * @return true if no more buffer are queued in mac80211 txq and false otherwise. + */ +static bool ecrnx_txq_mac80211_dequeue(struct ecrnx_hw *ecrnx_hw, + struct sk_buff_head *sk_list, + struct ecrnx_txq *txq, int max) +{ + struct ieee80211_txq *mac_txq; + struct sk_buff *skb; + unsigned long mac_txq_len; + + if (txq->nb_ready_mac80211 == NOT_MAC80211_TXQ) + return true; + + mac_txq = container_of((void *)txq, struct ieee80211_txq, drv_priv); + + for (; max > 0; max--) { + skb = ecrnx_tx_dequeue_prep(ecrnx_hw, mac_txq); + if (skb == NULL) + return true; + + __skb_queue_tail(sk_list, skb); + } + + /* re-read mac80211 txq current length. + It is mainly for debug purpose to trace dropped packet. There is no + problems to have nb_ready_mac80211 != actual mac80211 txq length */ + ieee80211_txq_get_depth(mac_txq, &mac_txq_len, NULL); + if (txq->nb_ready_mac80211 > mac_txq_len) + trace_txq_drop(txq, txq->nb_ready_mac80211 - mac_txq_len); + txq->nb_ready_mac80211 = mac_txq_len; + + return (txq->nb_ready_mac80211 == 0); +} +#endif + +/** + * ecrnx_txq_get_skb_to_push - Get list of buffer to push for one txq + * + * @ecrnx_hw: main driver data + * @hwq: HWQ on wich buffers will be pushed + * @txq: TXQ to get buffers from + * @user: user postion to use + * @sk_list_push: list to update + * + * + * This function will returned a list of buffer to push for one txq. + * It will take into account the number of credit of the HWQ for this user + * position and TXQ (and push_limit). + * This allow to get a list that can be pushed without having to test for + * hwq/txq status after each push + * + * If a MU group has been selected for this txq, it will also update the + * counter for the group + * + * @return true if txq no longer have buffer ready after the ones returned. + * false otherwise + */ +static +bool ecrnx_txq_get_skb_to_push(struct ecrnx_hw *ecrnx_hw, struct ecrnx_hwq *hwq, + struct ecrnx_txq *txq, int user, + struct sk_buff_head *sk_list_push) +{ + int nb_ready = skb_queue_len(&txq->sk_list); + int credits = min_t(int, ecrnx_txq_get_credits(txq), hwq->credits[user]); + bool res = false; + + __skb_queue_head_init(sk_list_push); + + if (credits >= nb_ready) { + skb_queue_splice_init(&txq->sk_list, sk_list_push); +#ifdef CONFIG_MAC80211_TXQ + res = ecrnx_txq_mac80211_dequeue(ecrnx_hw, sk_list_push, txq, credits - nb_ready); + credits = skb_queue_len(sk_list_push); +#else + res = true; + credits = nb_ready; +#endif + } else { + skb_queue_extract(&txq->sk_list, sk_list_push, credits); + + /* When processing PS service period (i.e. push_limit != 0), no longer + process this txq if the buffers extracted will complete the SP for + this txq */ + if (txq->push_limit && (credits == txq->push_limit)) + res = true; + } + + ecrnx_mu_set_active_sta(ecrnx_hw, ecrnx_txq_2_sta(txq), credits); + + return res; +} + +/** + * ecrnx_txq_select_user - Select User queue for a txq + * + * @ecrnx_hw: main driver data + * @mu_lock: true is MU lock is taken + * @txq: TXQ to select MU group for + * @hwq: HWQ for the TXQ + * @user: Updated with user position selected + * + * @return false if it is no possible to process this txq. + * true otherwise + * + * This function selects the MU group to use for a TXQ. + * The selection is done as follow: + * + * - return immediately for STA that don't belongs to any group and select + * group 0 / user 0 + * + * - If MU tx is disabled (by user mutx_on, or because mu group are being + * updated !mu_lock), select group 0 / user 0 + * + * - Use the best group selected by @ecrnx_mu_group_sta_select. + * + * Each time a group is selected (except for the first case where sta + * doesn't belongs to a MU group), the function checks that no buffer is + * pending for this txq on another user position. If this is the case stop + * the txq (ECRNX_TXQ_STOP_MU_POS) and return false. + * + */ +static +bool ecrnx_txq_select_user(struct ecrnx_hw *ecrnx_hw, bool mu_lock, + struct ecrnx_txq *txq, struct ecrnx_hwq *hwq, int *user) +{ + int pos = 0; +#ifdef CONFIG_ECRNX_MUMIMO_TX + int id, group_id = 0; + struct ecrnx_sta *sta = ecrnx_txq_2_sta(txq); + + /* for sta that belong to no group return immediately */ + if (!sta || !sta->group_info.cnt) + goto end; + + /* If MU is disabled, need to check user */ + if (!ecrnx_hw->mod_params->mutx_on || !mu_lock) + goto check_user; + + /* Use the "best" group selected */ + group_id = sta->group_info.group; + + if (group_id > 0) + pos = ecrnx_mu_group_sta_get_pos(ecrnx_hw, sta, group_id); + + check_user: + /* check that we can push on this user position */ +#if CONFIG_USER_MAX == 2 + id = (pos + 1) & 0x1; + if (txq->pkt_pushed[id]) { + ecrnx_txq_stop(txq, ECRNX_TXQ_STOP_MU_POS); + ECRNX_DBG("%s-%d:ecrnx_txq_stop,reaosn:0x%x \n", __func__, __LINE__, ECRNX_TXQ_STOP_MU_POS); + return false; + } + +#else + for (id = 0 ; id < CONFIG_USER_MAX ; id++) { + if (id != pos && txq->pkt_pushed[id]) { + ecrnx_txq_stop(txq, ECRNX_TXQ_STOP_MU_POS); + ECRNX_DBG("%s-%d:ecrnx_txq_stop,reaosn:0x%x \n", __func__, __LINE__, ECRNX_TXQ_STOP_MU_POS); + return false; + } + } +#endif + + end: + ecrnx_txq_set_mu_info(ecrnx_hw, txq, group_id, pos); +#endif /* CONFIG_ECRNX_MUMIMO_TX */ + + *user = pos; + return true; +} + + +/** + * ecrnx_hwq_process - Process one HW queue list + * + * @ecrnx_hw: Driver main data + * @hw_queue: HW queue index to process + * + * The function will iterate over all the TX queues linked in this HW queue + * list. For each TX queue, push as many buffers as possible in the HW queue. + * (NB: TX queue have at least 1 buffer, otherwise it wouldn't be in the list) + * - If TX queue no longer have buffer, remove it from the list and check next + * TX queue + * - If TX queue no longer have credits or has a push_limit (PS mode) and it + * is reached , remove it from the list and check next TX queue + * - If HW queue is full, update list head to start with the next TX queue on + * next call if current TX queue already pushed "too many" pkt in a row, and + * return + * + * To be called when HW queue list is modified: + * - when a buffer is pushed on a TX queue + * - when new credits are received + * - when a STA returns from Power Save mode or receives traffic request. + * - when Channel context change + * + * To be called with tx_lock hold + */ +#define ALL_HWQ_MASK ((1 << CONFIG_USER_MAX) - 1) + +void ecrnx_hwq_process(struct ecrnx_hw *ecrnx_hw, struct ecrnx_hwq *hwq) +{ + struct ecrnx_txq *txq, *next; + int user, credit_map = 0; + bool mu_enable; + + trace_process_hw_queue(hwq); + + hwq->need_processing = false; + + mu_enable = ecrnx_txq_take_mu_lock(ecrnx_hw); + if (!mu_enable) + credit_map = ALL_HWQ_MASK - 1; + + list_for_each_entry_safe(txq, next, &hwq->list, sched_list) { + struct ecrnx_txhdr *txhdr = NULL; + struct sk_buff_head sk_list_push; + struct sk_buff *skb; + bool txq_empty; + + trace_process_txq(txq); + + /* sanity check for debug */ + BUG_ON(!(txq->status & ECRNX_TXQ_IN_HWQ_LIST)); + BUG_ON(txq->idx == TXQ_INACTIVE); + BUG_ON(txq->credits <= 0); + BUG_ON(!ecrnx_txq_skb_ready(txq)); + + if (!ecrnx_txq_select_user(ecrnx_hw, mu_enable, txq, hwq, &user)) + continue; + + if (!hwq->credits[user]) { + credit_map |= BIT(user); + if (credit_map == ALL_HWQ_MASK) + break; + continue; + } + + txq_empty = ecrnx_txq_get_skb_to_push(ecrnx_hw, hwq, txq, user, + &sk_list_push); + + while ((skb = __skb_dequeue(&sk_list_push)) != NULL) { + txhdr = (struct ecrnx_txhdr *)skb->data; + ecrnx_tx_push(ecrnx_hw, txhdr, 0); + } + + if (txq_empty) { + ecrnx_txq_del_from_hw_list(txq); + txq->pkt_sent = 0; +#if defined CONFIG_ECRNX_SOFTMAC && defined CONFIG_ECRNX_AMSDUS_TX + if (txq->amsdu_ht_len_cap) + ieee80211_amsdu_ctl(ecrnx_hw->hw, txq->sta, txq->tid, NULL, + 0, 0, false); +#endif + } else if ((hwq->credits[user] == 0) && + ecrnx_txq_is_scheduled(txq)) { + /* txq not empty, + - To avoid starving need to process other txq in the list + - For better aggregation, need to send "as many consecutive + pkt as possible" for he same txq + ==> Add counter to trigger txq switch + */ + if (txq->pkt_sent > hwq->size) { + txq->pkt_sent = 0; + list_rotate_left(&hwq->list); + } + } + +#ifdef CONFIG_ECRNX_FULLMAC + /* Unable to complete PS traffic request because of hwq credit */ + if (txq->push_limit && txq->sta) { + if (txq->ps_id == LEGACY_PS_ID) { + /* for legacy PS abort SP and wait next ps-poll */ + txq->sta->ps.sp_cnt[txq->ps_id] -= txq->push_limit; + txq->push_limit = 0; + } + /* for u-apsd need to complete the SP to send EOSP frame */ + } + + /* restart netdev queue if number of queued buffer is below threshold */ + if (unlikely(txq->status & ECRNX_TXQ_NDEV_FLOW_CTRL) && + skb_queue_len(&txq->sk_list) < ECRNX_NDEV_FLOW_CTRL_RESTART) { + txq->status &= ~ECRNX_TXQ_NDEV_FLOW_CTRL; + netif_wake_subqueue(txq->ndev, txq->ndev_idx); + trace_txq_flowctrl_restart(txq); + } +#endif /* CONFIG_ECRNX_FULLMAC */ + + } + +#ifdef CONFIG_ECRNX_SOFTMAC + if (hwq->len < hwq->len_start && + ieee80211_queue_stopped(ecrnx_hw->hw, hwq->id)) { + trace_hwq_flowctrl_start(hwq->id); + ieee80211_wake_queue(ecrnx_hw->hw, hwq->id); + } +#endif /* CONFIG_ECRNX_SOFTMAC */ + + if (mu_enable) + ecrnx_txq_release_mu_lock(ecrnx_hw); +} + +/** + * ecrnx_hwq_process_all - Process all HW queue list + * + * @ecrnx_hw: Driver main data + * + * Loop over all HWQ, and process them if needed + * To be called with tx_lock hold + */ +void ecrnx_hwq_process_all(struct ecrnx_hw *ecrnx_hw) +{ + int id; + + ecrnx_mu_group_sta_select(ecrnx_hw); + + for (id = ARRAY_SIZE(ecrnx_hw->hwq) - 1; id >= 0 ; id--) { + if (ecrnx_hw->hwq[id].need_processing) { + ecrnx_hwq_process(ecrnx_hw, &ecrnx_hw->hwq[id]); + } + } +} + +/** + * ecrnx_hwq_init - Initialize all hwq structures + * + * @ecrnx_hw: Driver main data + * + */ +void ecrnx_hwq_init(struct ecrnx_hw *ecrnx_hw) +{ + int i, j; + + for (i = 0; i < ARRAY_SIZE(ecrnx_hw->hwq); i++) { + struct ecrnx_hwq *hwq = &ecrnx_hw->hwq[i]; + + for (j = 0 ; j < CONFIG_USER_MAX; j++) + hwq->credits[j] = nx_txdesc_cnt[i]; + hwq->id = i; + hwq->size = nx_txdesc_cnt[i]; + INIT_LIST_HEAD(&hwq->list); + +#ifdef CONFIG_ECRNX_SOFTMAC + hwq->len = 0; + hwq->len_stop = nx_txdesc_cnt[i] * 2; + hwq->len_start = hwq->len_stop / 4; +#endif + } +} diff --git a/drivers/net/wireless/eswin/ecrnx_txq.h b/drivers/net/wireless/eswin/ecrnx_txq.h new file mode 100644 index 000000000000..250e1d38c3d6 --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_txq.h @@ -0,0 +1,506 @@ +/** + **************************************************************************************** + * + * @file ecrnx_txq.h + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ +#ifndef _ECRNX_TXQ_H_ +#define _ECRNX_TXQ_H_ + +#include <linux/types.h> +#include <linux/bitops.h> +#include <linux/ieee80211.h> + +#ifdef CONFIG_ECRNX_SOFTMAC +#include <net/mac80211.h> +#include "ecrnx_baws.h" + +/** + * Softmac TXQ configuration + * - STA have one TXQ per TID + * - VIF have one TXQ per HW queue + * + * Txq mapping looks like + * for NX_REMOTE_STA_MAX=10 and NX_VIRT_DEV_MAX=4 + * + * | TXQ | VIF | STA | TID | HWQ | + * |-----+-----+-------+------+-----|- + * | 0 | | 0 | 0 | 1 | 16 TXQ per STA + * | 1 | | 0 | 1 | 0 | + * | 2 | | 0 | 2 | 0 | + * | 3 | | 0 | 3 | 1 | + * | 4 | | 0 | 4 | 2 | + * | 5 | | 0 | 5 | 2 | + * | 6 | | 0 | 6 | 3 | + * | 7 | | 0 | 7 | 3 | + * | 8 | | 0 | 8 | 1 | + * | ...| | | | | + * | 16 | | 0 | 16 | 1 | + * |-----+-----+-------+------+-----|- + * | ... | | | | | same for all STAs + * |-----+-----+-------+------+-----|- + * | 160 | 0 | | | 0 | 5 TXQ per VIF + * | ... | | | | | + * | 164 | 0 | | | 4 | + * |-----+-----+-------+------+-----|- + * | ... | | | | | same for all VIFs + * |-----+-----+-------+------+-----|- + * + * NOTE: When using CONFIG_MAC80211_TXQ only one TXQ is allocated by mac80211 + * for the VIF (associated to BE ac). To avoid too much differences with case + * where TXQ are allocated by the driver the "missing" VIF TXQs are allocated + * by the driver. Actually driver also allocates txq for BE (to avoid having + * modify ac parameter to access the TXQ) but this one is never used. + * Driver check if nb_ready_mac80211 field is equal to NOT_MAC80211_TXQ in + * order to distinguish non mac80211 txq. + * When the txq interface (.wake_tx_queue) is used only the TXQ + * allocated by mac80211 will be used and thus BE access category will always + * be used. When "VIF" frames needs to be pushed on different access category + * mac80211 will use the tx interface (.tx) and in this case driver will select + * the txq associated to the requested access category. + */ +#define NX_NB_TID_PER_STA IEEE80211_NUM_TIDS +#define NX_NB_TXQ_PER_STA NX_NB_TID_PER_STA +#define NX_NB_TXQ_PER_VIF NX_TXQ_CNT +#define NX_NB_TXQ ((NX_NB_TXQ_PER_STA * NX_REMOTE_STA_MAX) + \ + (NX_NB_TXQ_PER_VIF * NX_VIRT_DEV_MAX)) + +#define NX_FIRST_VIF_TXQ_IDX (NX_REMOTE_STA_MAX * NX_NB_TXQ_PER_STA) + +#define NOT_MAC80211_TXQ ULONG_MAX + +#else /* i.e. #ifdef CONFIG_ECRNX_FULLMAC */ +/** + * Fullmac TXQ configuration: + * - STA: 1 TXQ per TID (limited to 8) + * 1 TXQ for bufferable MGT frames + * - VIF: 1 TXQ for Multi/Broadcast + + * 1 TXQ for MGT for unknown STAs or non-bufferable MGT frames + * - 1 TXQ for offchannel transmissions + * + * + * Txq mapping looks like + * for NX_REMOTE_STA_MAX=10 and NX_VIRT_DEV_MAX=4 + * + * | TXQ | NDEV_ID | VIF | STA | TID | HWQ | + * |-----+---------+-----+-------+------+-----|- + * | 0 | 0 | | 0 | 0 | 1 | 9 TXQ per STA + * | 1 | 1 | | 0 | 1 | 0 | (8 data + 1 mgmt) + * | 2 | 2 | | 0 | 2 | 0 | + * | 3 | 3 | | 0 | 3 | 1 | + * | 4 | 4 | | 0 | 4 | 2 | + * | 5 | 5 | | 0 | 5 | 2 | + * | 6 | 6 | | 0 | 6 | 3 | + * | 7 | 7 | | 0 | 7 | 3 | + * | 8 | N/A | | 0 | MGMT | 3 | + * |-----+---------+-----+-------+------+-----|- + * | ... | | | | | | Same for all STAs + * |-----+---------+-----+-------+------+-----|- + * | 90 | 80 | 0 | BC/MC | 0 | 1/4 | 1 TXQ for BC/MC per VIF + * | ... | | | | | | + * | 93 | 80 | 3 | BC/MC | 0 | 1/4 | + * |-----+---------+-----+-------+------+-----|- + * | 94 | N/A | 0 | N/A | MGMT | 3 | 1 TXQ for unknown STA per VIF + * | ... | | | | | | + * | 97 | N/A | 3 | N/A | MGMT | 3 | + * |-----+---------+-----+-------+------+-----|- + * | 98 | N/A | | N/A | MGMT | 3 | 1 TXQ for offchannel frame + */ +#define NX_NB_TID_PER_STA 8 +#define NX_NB_TXQ_PER_STA (NX_NB_TID_PER_STA + 1) +#define NX_NB_TXQ_PER_VIF 2 +#define NX_NB_TXQ ((NX_NB_TXQ_PER_STA * NX_REMOTE_STA_MAX) + \ + (NX_NB_TXQ_PER_VIF * NX_VIRT_DEV_MAX) + 1) + +#define NX_FIRST_VIF_TXQ_IDX (NX_REMOTE_STA_MAX * NX_NB_TXQ_PER_STA) +#define NX_FIRST_BCMC_TXQ_IDX NX_FIRST_VIF_TXQ_IDX +#define NX_FIRST_UNK_TXQ_IDX (NX_FIRST_BCMC_TXQ_IDX + NX_VIRT_DEV_MAX) + +#define NX_OFF_CHAN_TXQ_IDX (NX_FIRST_VIF_TXQ_IDX + \ + (NX_VIRT_DEV_MAX * NX_NB_TXQ_PER_VIF)) +#define NX_BCMC_TXQ_TYPE 0 +#define NX_UNK_TXQ_TYPE 1 + +/** + * Each data TXQ is a netdev queue. TXQ to send MGT are not data TXQ as + * they did not recieved buffer from netdev interface. + * Need to allocate the maximum case. + * AP : all STAs + 1 BC/MC + */ +#define NX_NB_NDEV_TXQ ((NX_NB_TID_PER_STA * NX_REMOTE_STA_MAX) + 1 ) +#define NX_BCMC_TXQ_NDEV_IDX (NX_NB_TID_PER_STA * NX_REMOTE_STA_MAX) +#define NX_STA_NDEV_IDX(tid, sta_idx) ((tid) + (sta_idx) * NX_NB_TID_PER_STA) +#define NDEV_NO_TXQ 0xffff +#if (NX_NB_NDEV_TXQ >= NDEV_NO_TXQ) +#error("Need to increase struct ecrnx_txq->ndev_idx size") +#endif + +/* stop netdev queue when number of queued buffers if greater than this */ +#define ECRNX_NDEV_FLOW_CTRL_STOP 200 +/* restart netdev queue when number of queued buffers is lower than this */ +#define ECRNX_NDEV_FLOW_CTRL_RESTART 100 + +#endif /* CONFIG_ECRNX_SOFTMAC */ + +#define TXQ_INACTIVE 0xffff +#if (NX_NB_TXQ >= TXQ_INACTIVE) +#error("Need to increase struct ecrnx_txq->idx size") +#endif + +#define NX_TXQ_INITIAL_CREDITS 20 //4 + +#define ECRNX_TXQ_CLEANUP_INTERVAL (10 * HZ) //10s in jiffies +#define ECRNX_TXQ_MAX_QUEUE_JIFFIES (20 * HZ) +/** + * TXQ tid sorted by decreasing priority + */ +extern const int nx_tid_prio[NX_NB_TID_PER_STA]; + +/** + * struct ecrnx_hwq - Structure used to save information relative to + * an AC TX queue (aka HW queue) + * @list: List of TXQ, that have buffers ready for this HWQ + * @credits: available credit for the queue (i.e. nb of buffers that + * can be pushed to FW ) + * @id Id of the HWQ among ECRNX_HWQ_.... + * @size size of the queue + * @need_processing Indicate if hwq should be processed + * @len number of packet ready to be pushed to fw for this HW queue + * @len_stop threshold to stop mac80211(i.e. netdev) queues. Stop queue when + * driver has more than @len_stop packets ready. + * @len_start threshold to wake mac8011 queues. Wake queue when driver has + * less than @len_start packets ready. + */ +struct ecrnx_hwq { + struct list_head list; + u8 credits[CONFIG_USER_MAX]; + u8 size; + u8 id; + bool need_processing; +#ifdef CONFIG_ECRNX_SOFTMAC + u8 len; + u8 len_stop; + u8 len_start; +#endif /* CONFIG_ECRNX_SOFTMAC */ +}; + +/** + * enum ecrnx_push_flags - Flags of pushed buffer + * + * @ECRNX_PUSH_RETRY Pushing a buffer for retry + * @ECRNX_PUSH_IMMEDIATE Pushing a buffer without queuing it first + */ +enum ecrnx_push_flags { + ECRNX_PUSH_RETRY = BIT(0), + ECRNX_PUSH_IMMEDIATE = BIT(1), +}; + +/** + * enum ecrnx_txq_flags - TXQ status flag + * + * @ECRNX_TXQ_IN_HWQ_LIST: The queue is scheduled for transmission + * @ECRNX_TXQ_STOP_FULL: No more credits for the queue + * @ECRNX_TXQ_STOP_CSA: CSA is in progress + * @ECRNX_TXQ_STOP_STA_PS: Destiniation sta is currently in power save mode + * @ECRNX_TXQ_STOP_VIF_PS: Vif owning this queue is currently in power save mode + * @ECRNX_TXQ_STOP_CHAN: Channel of this queue is not the current active channel + * @ECRNX_TXQ_STOP_MU_POS: TXQ is stopped waiting for all the buffers pushed to + * fw to be confirmed + * @ECRNX_TXQ_STOP: All possible reason to have a txq stopped + * @ECRNX_TXQ_NDEV_FLOW_CTRL: associated netdev queue is currently stopped. + * Note: when a TXQ is flowctrl it is NOT stopped + */ +enum ecrnx_txq_flags { + ECRNX_TXQ_IN_HWQ_LIST = BIT(0), + ECRNX_TXQ_STOP_FULL = BIT(1), + ECRNX_TXQ_STOP_CSA = BIT(2), + ECRNX_TXQ_STOP_STA_PS = BIT(3), + ECRNX_TXQ_STOP_VIF_PS = BIT(4), + ECRNX_TXQ_STOP_CHAN = BIT(5), + ECRNX_TXQ_STOP_MU_POS = BIT(6), + ECRNX_TXQ_STOP = (ECRNX_TXQ_STOP_FULL | ECRNX_TXQ_STOP_CSA | + ECRNX_TXQ_STOP_STA_PS | ECRNX_TXQ_STOP_VIF_PS | + ECRNX_TXQ_STOP_CHAN) , + ECRNX_TXQ_NDEV_FLOW_CTRL = BIT(7), +}; + + +/** + * struct ecrnx_txq - Structure used to save information relative to + * a RA/TID TX queue + * + * @idx: Unique txq idx. Set to TXQ_INACTIVE if txq is not used. + * @status: bitfield of @ecrnx_txq_flags. + * @credits: available credit for the queue (i.e. nb of buffers that + * can be pushed to FW). + * @pkt_sent: number of consecutive pkt sent without leaving HW queue list + * @pkt_pushed: number of pkt currently pending for transmission confirmation + * @sched_list: list node for HW queue schedule list (ecrnx_hwq.list) + * @sk_list: list of buffers to push to fw + * @last_retry_skb: pointer on the last skb in @sk_list that is a retry. + * (retry skb are stored at the beginning of the list) + * NULL if no retry skb is queued in @sk_list + * @nb_retry: Number of retry packet queued. + * @hwq: Pointer on the associated HW queue. + * @push_limit: number of packet to push before removing the txq from hwq list. + * (we always have push_limit < skb_queue_len(sk_list)) + * @tid: TID + * + * SOFTMAC specific: + * @baw: Block Ack window information + * @amsdu_anchor: pointer to ecrnx_sw_txhdr of the first subframe of the A-MSDU. + * NULL if no A-MSDU frame is in construction + * @amsdu_ht_len_cap: + * @amsdu_vht_len_cap: + * @nb_ready_mac80211: Number of buffer ready in mac80211 txq + * + * FULLMAC specific + * @ps_id: Index to use for Power save mode (LEGACY or UAPSD) + * @ndev_idx: txq idx from netdev point of view (0xFF for non netdev queue) + * @ndev: pointer to ndev of the corresponding vif + * @amsdu: pointer to ecrnx_sw_txhdr of the first subframe of the A-MSDU. + * NULL if no A-MSDU frame is in construction + * @amsdu_len: Maximum size allowed for an A-MSDU. 0 means A-MSDU not allowed + */ +struct ecrnx_txq { + u16 idx; + u8 status; + s8 credits; + u8 pkt_sent; + u8 pkt_pushed[CONFIG_USER_MAX]; + struct list_head sched_list; + struct sk_buff_head sk_list; + struct sk_buff *last_retry_skb; + struct ecrnx_hwq *hwq; + int nb_retry; + u8 push_limit; + u8 tid; +#ifdef CONFIG_MAC80211_TXQ + unsigned long nb_ready_mac80211; +#endif +#ifdef CONFIG_ECRNX_SOFTMAC + struct ecrnx_baw baw; + struct ieee80211_sta *sta; +#ifdef CONFIG_ECRNX_AMSDUS_TX + struct ecrnx_sw_txhdr *amsdu_anchor; + u16 amsdu_ht_len_cap; + u16 amsdu_vht_len_cap; +#endif /* CONFIG_ECRNX_AMSDUS_TX */ +#else /* ! CONFIG_ECRNX_SOFTMAC */ + struct ecrnx_sta *sta; + u8 ps_id; + u16 ndev_idx; + struct net_device *ndev; +#ifdef CONFIG_ECRNX_AMSDUS_TX + struct ecrnx_sw_txhdr *amsdu; + u16 amsdu_len; +#endif /* CONFIG_ECRNX_AMSDUS_TX */ +#endif /* CONFIG_ECRNX_SOFTMAC */ +#ifdef CONFIG_ECRNX_MUMIMO_TX + u8 mumimo_info; +#endif +}; + +struct ecrnx_sta; +struct ecrnx_vif; +struct ecrnx_hw; +struct ecrnx_sw_txhdr; + +#ifdef CONFIG_ECRNX_MUMIMO_TX +#define ECRNX_TXQ_GROUP_ID(txq) ((txq)->mumimo_info & 0x3f) +#define ECRNX_TXQ_POS_ID(txq) (((txq)->mumimo_info >> 6) & 0x3) +#else +#define ECRNX_TXQ_GROUP_ID(txq) 0 +#define ECRNX_TXQ_POS_ID(txq) 0 +#endif /* CONFIG_ECRNX_MUMIMO_TX */ + +static inline bool ecrnx_txq_is_stopped(struct ecrnx_txq *txq) +{ + return (txq->status & ECRNX_TXQ_STOP); +} + +static inline bool ecrnx_txq_is_full(struct ecrnx_txq *txq) +{ + return (txq->status & ECRNX_TXQ_STOP_FULL); +} + +static inline bool ecrnx_txq_is_scheduled(struct ecrnx_txq *txq) +{ + return (txq->status & ECRNX_TXQ_IN_HWQ_LIST); +} + +/** + * ecrnx_txq_is_ready_for_push - Check if a TXQ is ready for push + * + * @txq: txq pointer + * + * if + * - txq is not stopped + * - and hwq has credits + * - and there is no buffer queued + * then a buffer can be immediately pushed without having to queue it first + * @return: true if the 3 conditions are met and false otherwise. + */ +static inline bool ecrnx_txq_is_ready_for_push(struct ecrnx_txq *txq) +{ + return (!ecrnx_txq_is_stopped(txq) && + txq->hwq->credits[ECRNX_TXQ_POS_ID(txq)] > 0 && + skb_queue_empty(&txq->sk_list)); +} + +/** + * foreach_sta_txq - Macro to iterate over all TXQ of a STA in increasing + * TID order + * + * @sta: pointer to ecrnx_sta + * @txq: pointer to ecrnx_txq updated with the next TXQ at each iteration + * @tid: int updated with the TXQ tid at each iteration + * @ecrnx_hw: main driver data + */ +#ifdef CONFIG_MAC80211_TXQ +#define foreach_sta_txq(sta, txq, tid, ecrnx_hw) \ + for (tid = 0, txq = ecrnx_txq_sta_get(sta, 0); \ + tid < NX_NB_TXQ_PER_STA; \ + tid++, txq = ecrnx_txq_sta_get(sta, tid)) + +#elif defined(CONFIG_ECRNX_SOFTMAC) +#define foreach_sta_txq(sta, txq, tid, ecrnx_hw) \ + for (tid = 0, txq = &sta->txqs[0]; \ + tid < NX_NB_TXQ_PER_STA; \ + tid++, txq++) + +#else /* CONFIG_ECRNX_FULLMAC */ +#define foreach_sta_txq(sta, txq, tid, ecrnx_hw) \ + for (tid = 0, txq = ecrnx_txq_sta_get(sta, 0, ecrnx_hw); \ + tid < (is_multicast_sta(sta->sta_idx) ? 1 : NX_NB_TXQ_PER_STA); \ + tid++, txq++) + +#endif + +/** + * foreach_sta_txq_prio - Macro to iterate over all TXQ of a STA in + * decreasing priority order + * + * @sta: pointer to ecrnx_sta + * @txq: pointer to ecrnx_txq updated with the next TXQ at each iteration + * @tid: int updated with the TXQ tid at each iteration + * @i: int updated with ieration count + * @ecrnx_hw: main driver data + * + * Note: For fullmac txq for mgmt frame is skipped + */ +#ifdef CONFIG_ECRNX_SOFTMAC +#define foreach_sta_txq_prio(sta, txq, tid, i, ecrnx_hw) \ + for (i = 0, tid = nx_tid_prio[0], txq = ecrnx_txq_sta_get(sta, tid); \ + i < NX_NB_TID_PER_STA; \ + i++, tid = nx_tid_prio[i], txq = ecrnx_txq_sta_get(sta, tid)) +#else /* CONFIG_ECRNX_FULLMAC */ +#define foreach_sta_txq_prio(sta, txq, tid, i, ecrnx_hw) \ + for (i = 0, tid = nx_tid_prio[0], txq = ecrnx_txq_sta_get(sta, tid, ecrnx_hw); \ + i < NX_NB_TID_PER_STA; \ + i++, tid = nx_tid_prio[i], txq = ecrnx_txq_sta_get(sta, tid, ecrnx_hw)) +#endif + +/** + * foreach_vif_txq - Macro to iterate over all TXQ of a VIF (in AC order) + * + * @vif: pointer to ecrnx_vif + * @txq: pointer to ecrnx_txq updated with the next TXQ at each iteration + * @ac: int updated with the TXQ ac at each iteration + */ +#ifdef CONFIG_MAC80211_TXQ +#define foreach_vif_txq(vif, txq, ac) \ + for (ac = ECRNX_HWQ_BK, txq = ecrnx_txq_vif_get(vif, ac); \ + ac < NX_NB_TXQ_PER_VIF; \ + ac++, txq = ecrnx_txq_vif_get(vif, ac)) + +#else +#define foreach_vif_txq(vif, txq, ac) \ + for (ac = ECRNX_HWQ_BK, txq = &vif->txqs[0]; \ + ac < NX_NB_TXQ_PER_VIF; \ + ac++, txq++) +#endif + +#ifdef CONFIG_ECRNX_SOFTMAC +struct ecrnx_txq *ecrnx_txq_sta_get(struct ecrnx_sta *sta, u8 tid); +struct ecrnx_txq *ecrnx_txq_vif_get(struct ecrnx_vif *vif, u8 ac); +#else +struct ecrnx_txq *ecrnx_txq_sta_get(struct ecrnx_sta *sta, u8 tid, + struct ecrnx_hw * ecrnx_hw); +struct ecrnx_txq *ecrnx_txq_vif_get(struct ecrnx_vif *vif, u8 type); +#endif /* CONFIG_ECRNX_SOFTMAC */ + +/** + * ecrnx_txq_vif_get_status - return status bits related to the vif + * + * @ecrnx_vif: Pointer to vif structure + */ +static inline u8 ecrnx_txq_vif_get_status(struct ecrnx_vif *ecrnx_vif) +{ + struct ecrnx_txq *txq = ecrnx_txq_vif_get(ecrnx_vif, 0); + return (txq->status & (ECRNX_TXQ_STOP_CHAN | ECRNX_TXQ_STOP_VIF_PS)); +} + +void ecrnx_txq_vif_init(struct ecrnx_hw * ecrnx_hw, struct ecrnx_vif *vif, + u8 status); +void ecrnx_txq_vif_deinit(struct ecrnx_hw * ecrnx_hw, struct ecrnx_vif *vif); +void ecrnx_txq_sta_init(struct ecrnx_hw * ecrnx_hw, struct ecrnx_sta *ecrnx_sta, + u8 status); +void ecrnx_txq_sta_deinit(struct ecrnx_hw * ecrnx_hw, struct ecrnx_sta *ecrnx_sta); +#ifdef CONFIG_ECRNX_FULLMAC +void ecrnx_txq_unk_vif_init(struct ecrnx_vif *ecrnx_vif); +void ecrnx_txq_unk_vif_deinit(struct ecrnx_vif *vif); +void ecrnx_txq_offchan_init(struct ecrnx_vif *ecrnx_vif); +void ecrnx_txq_offchan_deinit(struct ecrnx_vif *ecrnx_vif); +void ecrnx_txq_tdls_vif_init(struct ecrnx_vif *ecrnx_vif); +void ecrnx_txq_tdls_vif_deinit(struct ecrnx_vif *vif); +void ecrnx_txq_tdls_sta_start(struct ecrnx_vif *ecrnx_vif, u16 reason, + struct ecrnx_hw *ecrnx_hw); +void ecrnx_txq_tdls_sta_stop(struct ecrnx_vif *ecrnx_vif, u16 reason, + struct ecrnx_hw *ecrnx_hw); +void ecrnx_txq_prepare(struct ecrnx_hw *ecrnx_hw); +#endif + + +void ecrnx_txq_add_to_hw_list(struct ecrnx_txq *txq); +void ecrnx_txq_del_from_hw_list(struct ecrnx_txq *txq); +void ecrnx_txq_stop(struct ecrnx_txq *txq, u16 reason); +void ecrnx_txq_start(struct ecrnx_txq *txq, u16 reason); +void ecrnx_txq_vif_start(struct ecrnx_vif *vif, u16 reason, + struct ecrnx_hw *ecrnx_hw); +void ecrnx_txq_vif_stop(struct ecrnx_vif *vif, u16 reason, + struct ecrnx_hw *ecrnx_hw); + +#ifdef CONFIG_ECRNX_SOFTMAC +void ecrnx_txq_sta_start(struct ecrnx_sta *sta, u16 reason); +void ecrnx_txq_sta_stop(struct ecrnx_sta *sta, u16 reason); +void ecrnx_txq_tdls_sta_start(struct ecrnx_sta *ecrnx_sta, u16 reason, + struct ecrnx_hw *ecrnx_hw); +void ecrnx_txq_tdls_sta_stop(struct ecrnx_sta *ecrnx_sta, u16 reason, + struct ecrnx_hw *ecrnx_hw); +#else +void ecrnx_txq_sta_start(struct ecrnx_sta *sta, u16 reason, + struct ecrnx_hw *ecrnx_hw); +void ecrnx_txq_sta_stop(struct ecrnx_sta *sta, u16 reason, + struct ecrnx_hw *ecrnx_hw); +void ecrnx_txq_offchan_start(struct ecrnx_hw *ecrnx_hw); +void ecrnx_txq_sta_switch_vif(struct ecrnx_sta *sta, struct ecrnx_vif *old_vif, + struct ecrnx_vif *new_vif); + +#endif /* CONFIG_ECRNX_SOFTMAC */ + +int ecrnx_txq_queue_skb(struct sk_buff *skb, struct ecrnx_txq *txq, + struct ecrnx_hw *ecrnx_hw, bool retry, + struct sk_buff *skb_prev); +void ecrnx_txq_confirm_any(struct ecrnx_hw *ecrnx_hw, struct ecrnx_txq *txq, + struct ecrnx_hwq *hwq, struct ecrnx_sw_txhdr *sw_txhdr); +void ecrnx_txq_drop_skb(struct ecrnx_txq *txq, struct sk_buff *skb, struct ecrnx_hw *ecrnx_hw, bool retry_packet); + +void ecrnx_hwq_init(struct ecrnx_hw *ecrnx_hw); +void ecrnx_hwq_process(struct ecrnx_hw *ecrnx_hw, struct ecrnx_hwq *hwq); +void ecrnx_hwq_process_all(struct ecrnx_hw *ecrnx_hw); + +#endif /* _ECRNX_TXQ_H_ */ diff --git a/drivers/net/wireless/eswin/ecrnx_utils.c b/drivers/net/wireless/eswin/ecrnx_utils.c new file mode 100644 index 000000000000..e60785c66f4d --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_utils.c @@ -0,0 +1,1333 @@ +/** + * ecrnx_utils.c + * + * IPC utility function definitions + * + * Copyright (C) ESWIN 2015-2020 + */ +#include "ecrnx_utils.h" +#include "ecrnx_defs.h" +#include "ecrnx_rx.h" +#include "ecrnx_tx.h" +#include "ecrnx_msg_rx.h" +#include "ecrnx_debugfs.h" +#include "ecrnx_prof.h" +#include "ipc_host.h" + +#ifdef CONFIG_ECRNX_ESWIN_SDIO +#include "eswin_utils.h" +#include "ecrnx_sdio.h" +#include "sdio.h" +#elif defined(CONFIG_ECRNX_ESWIN_USB) +#include "eswin_utils.h" +#include "ecrnx_usb.h" +#include "usb.h" +#endif + + +/** + * ecrnx_ipc_elem_pool_allocs() - Allocate and push to fw a pool of buffer. + * + * @ecrnx_hw: Main driver structure + * @pool: Pool to allocate + * @nb: Size of the pool to allocate + * @elem_size: SIze of one pool element + * @pool_name: Name of the pool + * @push: Function to push one pool element to fw + * + * This function will allocate an array to store the list of element addresses, + * a dma pool and @nb element in the dma pool. + * Each element is set with '0' and then push to fw using the @push function. + * It assumes that pointer inside @ipc parameter are set to NULL at start. + * + * Return: 0 on success and <0 upon error. If error is returned any allocated + * memory is NOT freed and ecrnx_ipc_elem_pool_deallocs() must be called. + */ +#ifndef CONFIG_ECRNX_ESWIN +static int ecrnx_ipc_elem_pool_allocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_elem_pool *pool, + int nb, size_t elem_size, char *pool_name, + int (*push)(struct ipc_host_env_tag *, + void *, uint32_t)) +{ + struct ecrnx_ipc_elem *buf; + int i; + + pool->nb = 0; + + /* allocate buf array */ + pool->buf = kmalloc(nb * sizeof(struct ecrnx_ipc_elem), GFP_KERNEL); + if (!pool->buf) { + dev_err(ecrnx_hw->dev, "Allocation of buffer array for %s failed\n", + pool_name); + return -ENOMEM; + } + + /* allocate dma pool */ + pool->pool = dma_pool_create(pool_name, ecrnx_hw->dev, elem_size, + cache_line_size(), 0); + if (!pool->pool) { + dev_err(ecrnx_hw->dev, "Allocation of dma pool %s failed\n", + pool_name); + return -ENOMEM; + } + + for (i = 0, buf = pool->buf; i < nb; buf++, i++) { + + /* allocate an elem */ + buf->addr = dma_pool_alloc(pool->pool, GFP_KERNEL, &buf->dma_addr); + if (!buf->addr) { + dev_err(ecrnx_hw->dev, "Allocation of block %d/%d in %s failed\n", + (i + 1), nb, pool_name); + return -ENOMEM; + } + pool->nb++; + + /* reset the element */ + memset(buf->addr, 0, elem_size); + + /* push it to FW */ + push(ecrnx_hw->ipc_env, buf, (uint32_t)buf->dma_addr); + } + + return 0; +} +#endif +/** + * ecrnx_ipc_elem_pool_deallocs() - Free all memory allocated for a pool + * + * @pool: Pool to free + * + * Must be call once after ecrnx_ipc_elem_pool_allocs(), even if it returned + * an error + */ +#ifndef CONFIG_ECRNX_ESWIN +static void ecrnx_ipc_elem_pool_deallocs(struct ecrnx_ipc_elem_pool *pool) +{ + struct ecrnx_ipc_elem *buf; + int i; + + for (i = 0, buf = pool->buf; i < pool->nb ; buf++, i++) { + dma_pool_free(pool->pool, buf->addr, buf->dma_addr); + } + pool->nb = 0; + + if (pool->pool) + dma_pool_destroy(pool->pool); + pool->pool = NULL; + + if (pool->buf) + kfree(pool->buf); + pool->buf = NULL; +} +#endif +/** + * ecrnx_ipc_elem_var_allocs - Alloc a single ipc buffer and push it to fw + * + * @ecrnx_hw: Main driver structure + * @elem: Element to allocate + * @elem_size: Size of the element to allcoate + * @dir: DMA direction + * @buf: If not NULL, used this buffer instead of allocating a new one. It must + * be @elem_size long and be allocated by kmalloc as kfree will be called. + * @init: Pointer to initial data to write in buffer before DMA sync. Needed + * only if direction is DMA_TO_DEVICE. If set it is assume that its size is + * @elem_size. + * @push: Function to push the element to fw. May be set to NULL. + * + * It allocates a buffer (or use the one provided with @buf), initializes it if + * @init is set, map buffer for DMA transfer, initializes @elem and push buffer + * to FW if @push is seet. + * + * Return: 0 on success and <0 upon error. If error is returned any allocated + * memory has been freed (including @buf if set). + */ +int ecrnx_ipc_elem_var_allocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_elem_var *elem, size_t elem_size, + enum dma_data_direction dir, + void *buf, const void *init, + void (*push)(struct ipc_host_env_tag *, uint32_t)) +{ + if (buf) { + elem->addr = buf; + } else { + elem->addr = kmalloc(elem_size, GFP_KERNEL); + if (!elem->addr) { + dev_err(ecrnx_hw->dev, "Allocation of ipc buffer failed\n"); + return -ENOMEM; + } + } + elem->size = elem_size; + + if ((dir == DMA_TO_DEVICE) && init) { + memcpy(elem->addr, init, elem_size); + } + +#ifdef CONFIG_ECRNX_ESWIN + elem->dma_addr = (ptr_addr)elem->addr; +#else + elem->dma_addr = dma_map_single(ecrnx_hw->dev, elem->addr, elem_size, dir); + if (dma_mapping_error(ecrnx_hw->dev, elem->dma_addr)) { + dev_err(ecrnx_hw->dev, "DMA mapping failed\n"); + kfree(elem->addr); + elem->addr = NULL; + return -EIO; + } + + if (push) + push(ecrnx_hw->ipc_env, elem->dma_addr); +#endif + return 0; +} + +/** + * ecrnx_ipc_elem_var_deallocs() - Free memory allocated for a single ipc buffer + * + * @ecrnx_hw: Main driver structure + * @elem: Element to free + */ +void ecrnx_ipc_elem_var_deallocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_elem_var *elem) +{ + if (!elem->addr) + return; +#ifndef CONFIG_ECRNX_ESWIN + dma_unmap_single(ecrnx_hw->dev, elem->dma_addr, elem->size, DMA_TO_DEVICE); +#endif + kfree(elem->addr); + elem->addr = NULL; +} + +/** + * ecrnx_ipc_skb_elem_allocs() - Allocate and push a skb buffer for the FW + * + * @ecrnx_hw: Main driver data + * @elem: Pointer to the skb elem that will contain the address of the buffer + */ +int ecrnx_ipc_skb_elem_allocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_skb_elem *elem, size_t skb_size, + enum dma_data_direction dir, + int (*push)(struct ipc_host_env_tag *, + void *, uint32_t)) +{ + elem->skb = dev_alloc_skb(skb_size); + if (unlikely(!elem->skb)) { + dev_err(ecrnx_hw->dev, "Allocation of ipc skb failed\n"); + return -ENOMEM; + } + + elem->dma_addr = dma_map_single(ecrnx_hw->dev, elem->skb->data, skb_size, dir); + if (unlikely(dma_mapping_error(ecrnx_hw->dev, elem->dma_addr))) { + dev_err(ecrnx_hw->dev, "DMA mapping failed\n"); + dev_kfree_skb(elem->skb); + elem->skb = NULL; + return -EIO; + } + + if (push){ + push(ecrnx_hw->ipc_env, elem, elem->dma_addr); + } + return 0; +} + +/** + * ecrnx_ipc_skb_elem_deallocs() - Free a skb buffer allocated for the FW + * + * @ecrnx_hw: Main driver data + * @elem: Pointer to the skb elem that contains the address of the buffer + * @skb_size: size of the skb buffer data + * @dir: DMA direction + */ +#ifndef CONFIG_ECRNX_ESWIN +static void ecrnx_ipc_skb_elem_deallocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_skb_elem *elem, + size_t skb_size, enum dma_data_direction dir) +{ + if (elem->skb) { + dma_unmap_single(ecrnx_hw->dev, elem->dma_addr, skb_size, dir); + dev_kfree_skb(elem->skb); + elem->skb = NULL; + } +} +#endif +/** + * ecrnx_ipc_unsup_rx_vec_elem_allocs() - Allocate and push an unsupported + * RX vector buffer for the FW + * + * @ecrnx_hw: Main driver data + * @elem: Pointer to the skb elem that will contain the address of the buffer + */ +#ifndef CONFIG_ECRNX_ESWIN +int ecrnx_ipc_unsup_rx_vec_elem_allocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_skb_elem *elem) +{ + struct rx_vector_desc *rxdesc; + + if (ecrnx_ipc_skb_elem_allocs(ecrnx_hw, elem, + ecrnx_hw->ipc_env->unsuprxvec_bufsz, DMA_FROM_DEVICE, NULL)) + return -ENOMEM; + + rxdesc = (struct rx_vector_desc *) elem->skb->data; + rxdesc->pattern = 0; + dma_sync_single_for_device(ecrnx_hw->dev, + elem->dma_addr + offsetof(struct rx_vector_desc, pattern), + sizeof(rxdesc->pattern), DMA_BIDIRECTIONAL); + + ipc_host_unsup_rx_vec_buf_push(ecrnx_hw->ipc_env, elem, (u32) elem->dma_addr); + + return 0; +} +#endif + +/** + * ecrnx_ipc_rxbuf_elems_deallocs() - Free all unsupported rx vector buffer + * allocated for the FW + * + * @ecrnx_hw: Main driver data + */ +#ifndef CONFIG_ECRNX_ESWIN +static void ecrnx_ipc_unsup_rx_vec_elems_deallocs(struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_ipc_skb_elem *elem; + int i, nb = ecrnx_hw->ipc_env->unsuprxvec_bufnb; + + if (!ecrnx_hw->e2aunsuprxvec_elems) + return; + + for (i = 0, elem = ecrnx_hw->e2aunsuprxvec_elems; i < nb; i++, elem++) { + ecrnx_ipc_skb_elem_deallocs(ecrnx_hw, elem, ecrnx_hw->ipc_env->unsuprxvec_bufsz, DMA_FROM_DEVICE); + } + + kfree(ecrnx_hw->e2aunsuprxvec_elems); + ecrnx_hw->e2aunsuprxvec_elems = NULL; +} +#endif + +/** +* ecrnx_ipc_unsup_rx_vec_elems_allocs() - Allocate and push all unsupported RX +* vector buffer for the FW +* +* @ecrnx_hw: Main driver data +*/ +#ifndef CONFIG_ECRNX_ESWIN +static int ecrnx_ipc_unsup_rx_vec_elems_allocs(struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_ipc_skb_elem *elem; + int i, nb = ecrnx_hw->ipc_env->unsuprxvec_bufnb; + + ecrnx_hw->e2aunsuprxvec_elems = kzalloc(nb * sizeof(struct ecrnx_ipc_skb_elem), + GFP_KERNEL); + if (!ecrnx_hw->e2aunsuprxvec_elems) { + dev_err(ecrnx_hw->dev, "Failed to allocate unsuprxvec_elems\n"); + return -ENOMEM; + } + + for (i = 0, elem = ecrnx_hw->e2aunsuprxvec_elems; i < nb; i++, elem++) + { + if (ecrnx_ipc_unsup_rx_vec_elem_allocs(ecrnx_hw, elem)) { + dev_err(ecrnx_hw->dev, "Failed to allocate unsuprxvec buf %d/%d\n", + i + 1, nb); + return -ENOMEM; + } + } + return 0; +} +#endif + +#ifdef CONFIG_ECRNX_SOFTMAC +/** + * ecrnx_ipc_rxbuf_elem_allocs() - Allocate and push a rx buffer for the FW + * + * @ecrnx_hw: Main driver data + * @elem: Pointer to the skb elem that will contain the address of the buffer + */ +#ifndef CONFIG_ECRNX_ESWIN +int ecrnx_ipc_rxbuf_elem_allocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_skb_elem *elem) +{ + struct hw_rxhdr *hw_rxhdr; + + if (ecrnx_ipc_skb_elem_allocs(ecrnx_hw, elem, + ecrnx_hw->ipc_env->rx_bufsz, DMA_FROM_DEVICE, NULL)) + return -ENOMEM; + + hw_rxhdr = (struct hw_rxhdr *) elem->skb->data; + hw_rxhdr->pattern = 0; + dma_sync_single_for_device(ecrnx_hw->dev, + elem->dma_addr + offsetof(struct hw_rxhdr, pattern), + sizeof(hw_rxhdr->pattern), DMA_BIDIRECTIONAL); + + ipc_host_rxbuf_push(ecrnx_hw->ipc_env, elem, (u32) elem->dma_addr); + + return 0; +} +#endif + +/** + * ecrnx_ipc_rxbuf_elem_repush() - Reset and repush an already allocated RX buffer + * + * @ecrnx_hw: Main driver data + * @elem: Pointer to the skb elem that contains the address of the buffer + */ +#ifndef CONFIG_ECRNX_ESWIN +void ecrnx_ipc_rxbuf_elem_repush(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_skb_elem *elem) +{ + struct sk_buff *skb = elem->skb; + int pattern_offset = sizeof(struct hw_rxhdr); + + ((struct hw_rxhdr *)skb->data)->pattern = 0; + dma_sync_single_for_device(ecrnx_hw->dev, elem->dma_addr, + pattern_offset, DMA_BIDIRECTIONAL); + ipc_host_rxbuf_push(ecrnx_hw->ipc_env, elem, (u32)elem->dma_addr); +} +#endif + +/** + * ecrnx_ipc_rxbuf_elems_allocs() - Allocate and push all RX buffer for the FW + * + * @ecrnx_hw: Main driver data + */ +#ifndef CONFIG_ECRNX_ESWIN +static int ecrnx_ipc_rxbuf_elems_allocs(struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_ipc_skb_elem *elem; + int i, nb = ecrnx_hw->ipc_env->rx_bufnb; + + ecrnx_hw->rxbuf_elems = kzalloc(nb * sizeof(struct ecrnx_ipc_skb_elem), + GFP_KERNEL); + if (!ecrnx_hw->rxbuf_elems) { + dev_err(ecrnx_hw->dev, "Failed to allocate rx_elems\n"); + return -ENOMEM; + } + + for (i = 0, elem = ecrnx_hw->rxbuf_elems; i < nb; i++, elem++) { + if (ecrnx_ipc_rxbuf_elem_allocs(ecrnx_hw, elem)) { + dev_err(ecrnx_hw->dev, "Failed to allocate rx buf %d/%d\n", + i + 1, nb); + return -ENOMEM; + } + } + + return 0; +} +#endif + +/** + * ecrnx_ipc_rxbuf_elems_deallocs() - Free all RX buffer allocated for the FW + * + * @ecrnx_hw: Main driver data + */ +#ifndef CONFIG_ECRNX_ESWIN +static void ecrnx_ipc_rxbuf_elems_deallocs(struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_ipc_skb_elem *elem; + int i, nb = ecrnx_hw->ipc_env->rx_bufnb; + + if (!ecrnx_hw->rxbuf_elems) + return; + + for (i = 0, elem = ecrnx_hw->rxbuf_elems; i < nb; i++, elem++) { + ecrnx_ipc_skb_elem_deallocs(ecrnx_hw, elem, ecrnx_hw->ipc_env->rx_bufsz, DMA_FROM_DEVICE); + } + + kfree(ecrnx_hw->rxbuf_elems); + ecrnx_hw->rxbuf_elems = NULL; +} +#endif + +#else /* ! CONFIG_ECRNX_SOFTMAC */ + +/** + * ecrnx_ipc_rxdesc_elem_repush() - Repush a rxdesc to FW + * + * @ecrnx_hw: Main driver data + * @elem: Rx desc to repush + * + * Once rx buffer has been received, the rxdesc used by FW to upload this + * buffer can be re-used for another rx buffer. + */ +#ifndef CONFIG_ECRNX_ESWIN +void ecrnx_ipc_rxdesc_elem_repush(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_elem *elem) +{ + struct rxdesc_tag *rxdesc = elem->addr; + rxdesc->status = 0; + dma_sync_single_for_device(ecrnx_hw->dev, elem->dma_addr, + sizeof(struct rxdesc_tag), DMA_BIDIRECTIONAL); + ipc_host_rxdesc_push(ecrnx_hw->ipc_env, elem, (u32)elem->dma_addr); +} +#endif +/** + * ecrnx_ipc_rxbuf_elem_allocs() - Allocate and push a RX buffer for the FW + * + * @ecrnx_hw: Main driver data + */ +#ifndef CONFIG_ECRNX_ESWIN +int ecrnx_ipc_rxbuf_elem_allocs(struct ecrnx_hw *ecrnx_hw) +{ + struct sk_buff *skb; + struct hw_rxhdr *hw_rxhdr; + dma_addr_t dma_addr; + int size = ecrnx_hw->ipc_env->rx_bufsz; + int nb, idx; + + skb = dev_alloc_skb(size); + if (unlikely(!skb)) { + dev_err(ecrnx_hw->dev, "Failed to allocate rx buffer\n"); + return -ENOMEM; + } + + dma_addr = dma_map_single(ecrnx_hw->dev, skb->data, size, DMA_FROM_DEVICE); + + if (unlikely(dma_mapping_error(ecrnx_hw->dev, dma_addr))) { + dev_err(ecrnx_hw->dev, "Failed to map rx buffer\n"); + goto err_skb; + } + + hw_rxhdr = (struct hw_rxhdr *)skb->data; + hw_rxhdr->pattern = 0; + dma_sync_single_for_device(ecrnx_hw->dev, + dma_addr + offsetof(struct hw_rxhdr, pattern), + sizeof(hw_rxhdr->pattern), DMA_BIDIRECTIONAL); + + /* Find first free slot */ + nb = 0; + idx = ecrnx_hw->rxbuf_elems.idx; + while (ecrnx_hw->rxbuf_elems.skb[idx] && nb < ECRNX_RXBUFF_MAX) { + idx = ( idx + 1 ) % ECRNX_RXBUFF_MAX; + nb++; + } + + if (WARN((nb == ECRNX_RXBUFF_MAX), "No more free space for rxbuff")) { + goto err_dma; + } + + ecrnx_hw->rxbuf_elems.skb[idx] = skb; + + /* Save info in skb control buffer */ + ECRNX_RXBUFF_DMA_ADDR_SET(skb, dma_addr); + ECRNX_RXBUFF_PATTERN_SET(skb, ecrnx_rxbuff_pattern); + ECRNX_RXBUFF_IDX_SET(skb, idx); + + /* Push buffer to FW */ + ipc_host_rxbuf_push(ecrnx_hw->ipc_env, ECRNX_RXBUFF_IDX_TO_HOSTID(idx), + dma_addr); + + /* Save idx so that on next push the free slot will be found quicker */ + ecrnx_hw->rxbuf_elems.idx = ( idx + 1 ) % ECRNX_RXBUFF_MAX; + + return 0; + + err_dma: + dma_unmap_single(ecrnx_hw->dev, dma_addr, size, DMA_FROM_DEVICE); + err_skb: + dev_kfree_skb(skb); + return -ENOMEM; + + return 0; +} +#endif + +/** + * ecrnx_ipc_rxbuf_elem_repush() - Repush a rxbuf to FW + * + * @ecrnx_hw: Main driver data + * @skb: Skb to repush + * + * In case a skb is not forwarded to upper layer it can be re-used. + * It is assumed that @skb has been verified before calling this function and + * that it is a valid rx buffer + * (i.e. skb == ecrnx_hw->rxbuf_elems.skb[ECRNX_RXBUFF_IDX_GET(skb)]) + */ +#ifndef CONFIG_ECRNX_ESWIN +void ecrnx_ipc_rxbuf_elem_repush(struct ecrnx_hw *ecrnx_hw, + struct sk_buff *skb) +{ + dma_addr_t dma_addr; + struct hw_rxhdr *hw_rxhdr = (struct hw_rxhdr *)skb->data; + int idx; + + /* reset pattern */ + hw_rxhdr->pattern = 0; + dma_addr = ECRNX_RXBUFF_DMA_ADDR_GET(skb); + dma_sync_single_for_device(ecrnx_hw->dev, + dma_addr + offsetof(struct hw_rxhdr, pattern), + sizeof(hw_rxhdr->pattern), DMA_BIDIRECTIONAL); + + /* re-push buffer to FW */ + idx = ECRNX_RXBUFF_IDX_GET(skb); + + ipc_host_rxbuf_push(ecrnx_hw->ipc_env, ECRNX_RXBUFF_IDX_TO_HOSTID(idx), + dma_addr); +} +#endif + +/** + * ecrnx_ipc_rxbuf_elems_allocs() - Allocate and push all RX buffer for the FW + * + * @ecrnx_hw: Main driver data + */ +#ifndef CONFIG_ECRNX_ESWIN +static int ecrnx_ipc_rxbuf_elems_allocs(struct ecrnx_hw *ecrnx_hw) +{ + //int i, nb = ecrnx_hw->ipc_env->rx_bufnb; + int i, nb = 0; + + for (i = 0; i < ECRNX_RXBUFF_MAX; i++) { + ecrnx_hw->rxbuf_elems.skb[i] = NULL; + } + ecrnx_hw->rxbuf_elems.idx = 0; + + for (i = 0; i < nb; i++) { + if (ecrnx_ipc_rxbuf_elem_allocs(ecrnx_hw)) { + dev_err(ecrnx_hw->dev, "Failed to allocate rx buf %d/%d\n", + i + 1, nb); + return -ENOMEM; + } + } + return 0; +} +#endif + +/** + * ecrnx_ipc_rxbuf_elems_deallocs() - Free all RX buffer allocated for the FW + * + * @ecrnx_hw: Main driver data + */ +#ifndef CONFIG_ECRNX_ESWIN +static void ecrnx_ipc_rxbuf_elems_deallocs(struct ecrnx_hw *ecrnx_hw) +{ + struct sk_buff *skb; + int i; + + for (i = 0; i < ECRNX_RXBUFF_MAX; i++) { + if (ecrnx_hw->rxbuf_elems.skb[i]) { + skb = ecrnx_hw->rxbuf_elems.skb[i]; + dma_unmap_single(ecrnx_hw->dev, ECRNX_RXBUFF_DMA_ADDR_GET(skb), + ecrnx_hw->ipc_env->rx_bufsz, DMA_FROM_DEVICE); + dev_kfree_skb(skb); + ecrnx_hw->rxbuf_elems.skb[i] = NULL; + } + } +} +#endif + +/** + * ecrnx_ipc_rxbuf_elem_pull() - Extract a skb from local table + * + * @ecrnx_hw: Main driver data + * @skb: SKb to extract for table + * + * After checking that skb is actually a pointer of local table, extract it + * from the table. + * When buffer is removed, DMA mapping is remove which has the effect to + * synchronize the buffer for the cpu. + * To be called before passing skb to upper layer. + */ +#ifndef CONFIG_ECRNX_ESWIN +void ecrnx_ipc_rxbuf_elem_pull(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb) +{ + unsigned int idx = ECRNX_RXBUFF_IDX_GET(skb); + + if (ECRNX_RXBUFF_VALID_IDX(idx) && (ecrnx_hw->rxbuf_elems.skb[idx] == skb)) { + dma_addr_t dma_addr = ECRNX_RXBUFF_DMA_ADDR_GET(skb); + ecrnx_hw->rxbuf_elems.skb[idx] = NULL; + dma_unmap_single(ecrnx_hw->dev, dma_addr, + ecrnx_hw->ipc_env->rx_bufsz, DMA_FROM_DEVICE); + } else { + WARN(1, "Incorrect rxbuff idx skb=%p table[%u]=%p", skb, idx, + idx < ECRNX_RXBUFF_MAX ? ecrnx_hw->rxbuf_elems.skb[idx] : NULL); + } + + /* Reset the pattern and idx */ + ECRNX_RXBUFF_PATTERN_SET(skb, 0); + ECRNX_RXBUFF_IDX_SET(skb, ECRNX_RXBUFF_MAX); +} +#endif + +/** + * ecrnx_ipc_rxbuf_elem_sync() - Sync part of a RX buffer + * + * @ecrnx_hw: Main driver data + * @skb: SKb to sync + * @len: Len to sync + * + * After checking that skb is actually a pointer of local table, sync @p len + * bytes of the buffer for CPU. Buffer is not removed from the table + */ +#ifndef CONFIG_ECRNX_ESWIN +void ecrnx_ipc_rxbuf_elem_sync(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb, + int len) +{ + unsigned int idx = ECRNX_RXBUFF_IDX_GET(skb); + + if (ECRNX_RXBUFF_VALID_IDX(idx) && (ecrnx_hw->rxbuf_elems.skb[idx] == skb)) { + dma_addr_t dma_addr = ECRNX_RXBUFF_DMA_ADDR_GET(skb); + dma_sync_single_for_cpu(ecrnx_hw->dev, dma_addr, len, DMA_FROM_DEVICE); + } else { + WARN(1, "Incorrect rxbuff idx skb=%p table[%u]=%p", skb, idx, + idx < ECRNX_RXBUFF_MAX ? ecrnx_hw->rxbuf_elems.skb[idx] : NULL); + } +} +#endif +#endif /* ! CONFIG_ECRNX_SOFTMAC */ + +/** + * ecrnx_elems_deallocs() - Deallocate IPC storage elements. + * @ecrnx_hw: Main driver data + * + * This function deallocates all the elements required for communications with + * LMAC, such as Rx Data elements, MSGs elements, ... + * This function should be called in correspondence with the allocation function. + */ +#ifndef CONFIG_ECRNX_ESWIN +static void ecrnx_elems_deallocs(struct ecrnx_hw *ecrnx_hw) +{ + ecrnx_ipc_rxbuf_elems_deallocs(ecrnx_hw); + ecrnx_ipc_unsup_rx_vec_elems_deallocs(ecrnx_hw); +#ifdef CONFIG_ECRNX_FULLMAC + ecrnx_ipc_elem_pool_deallocs(&ecrnx_hw->e2arxdesc_pool); +#endif + ecrnx_ipc_elem_pool_deallocs(&ecrnx_hw->e2amsgs_pool); + ecrnx_ipc_elem_pool_deallocs(&ecrnx_hw->dbgmsgs_pool); + ecrnx_ipc_elem_pool_deallocs(&ecrnx_hw->e2aradars_pool); + ecrnx_ipc_elem_var_deallocs(ecrnx_hw, &ecrnx_hw->pattern_elem); + ecrnx_ipc_elem_var_deallocs(ecrnx_hw, &ecrnx_hw->dbgdump_elem.buf); +} +#endif + +/** + * ecrnx_elems_allocs() - Allocate IPC storage elements. + * @ecrnx_hw: Main driver data + * + * This function allocates all the elements required for communications with + * LMAC, such as Rx Data elements, MSGs elements, ... + * This function should be called in correspondence with the deallocation function. + */ +static int ecrnx_elems_allocs(struct ecrnx_hw *ecrnx_hw) +{ + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + +#ifndef CONFIG_ECRNX_ESWIN + if (dma_set_coherent_mask(ecrnx_hw->dev, DMA_BIT_MASK(32))) + goto err_alloc; + if (ecrnx_ipc_elem_pool_allocs(ecrnx_hw, &ecrnx_hw->e2amsgs_pool, + ecrnx_hw->ipc_env->ipc_e2amsg_bufnb, + ecrnx_hw->ipc_env->ipc_e2amsg_bufsz, + "ecrnx_ipc_e2amsgs_pool", + ipc_host_msgbuf_push)) + goto err_alloc; + + if (ecrnx_ipc_elem_pool_allocs(ecrnx_hw, &ecrnx_hw->dbgmsgs_pool, + ecrnx_hw->ipc_env->ipc_dbg_bufnb, + ecrnx_hw->ipc_env->ipc_dbg_bufsz, + "ecrnx_ipc_dbgmsgs_pool", + ipc_host_dbgbuf_push)) + goto err_alloc; + + if (ecrnx_ipc_elem_pool_allocs(ecrnx_hw, &ecrnx_hw->e2aradars_pool, + ecrnx_hw->ipc_env->radar_bufnb, + ecrnx_hw->ipc_env->radar_bufsz, + "ecrnx_ipc_e2aradars_pool", + ipc_host_radarbuf_push)) + goto err_alloc; + + if (ecrnx_ipc_unsup_rx_vec_elems_allocs(ecrnx_hw)) + #error 1111 + goto err_alloc; + + if (ecrnx_ipc_elem_var_allocs(ecrnx_hw, &ecrnx_hw->pattern_elem, + sizeof(u32), DMA_TO_DEVICE, + NULL, &ecrnx_rxbuff_pattern, + ipc_host_patt_addr_push)) + goto err_alloc; + + if (ecrnx_ipc_elem_var_allocs(ecrnx_hw, &ecrnx_hw->dbgdump_elem.buf, + sizeof(struct dbg_debug_dump_tag), + DMA_FROM_DEVICE, NULL, NULL, + ipc_host_dbginfobuf_push)) + goto err_alloc; + + /* + * Note that the RX buffers are no longer allocated here as their size depends on the + * FW configuration, which is not available at that time. + * They will be allocated when checking the parameter compatibility between the driver + * and the underlying components (i.e. during the ecrnx_handle_dynparams() execution) + */ + +#ifdef CONFIG_ECRNX_FULLMAC + if (ecrnx_ipc_elem_pool_allocs(ecrnx_hw, &ecrnx_hw->e2arxdesc_pool, + ecrnx_hw->ipc_env->rxdesc_nb, + sizeof(struct rxdesc_tag), + "ecrnx_ipc_e2arxdesc_pool", + ipc_host_rxdesc_push)) + goto err_alloc; + +#endif /* CONFIG_ECRNX_FULLMAC */ + + return 0; + +err_alloc: + ecrnx_elems_deallocs(ecrnx_hw); + return -ENOMEM; +#else + return 0; +#endif +} + +/** + * ecrnx_ipc_msg_push() - Push a msg to IPC queue + * + * @ecrnx_hw: Main driver data + * @msg_buf: Pointer to message + * @len: Size, in bytes, of message + */ +void ecrnx_ipc_msg_push(struct ecrnx_hw *ecrnx_hw, void *msg_buf, uint16_t len) +{ + ecrnx_hw->msg_tx++; + ipc_host_msg_push(ecrnx_hw->ipc_env, msg_buf, len); +} + +/** + * ecrnx_ipc_txdesc_push() - Push a txdesc to FW + * + * @ecrnx_hw: Main driver data + * @tx_desc: Pointer on &struct txdesc_api to push to FW + * @hostid: Pointer save in ipc env to retrieve tx buffer upon confirmation. + * @hw_queue: Hw queue to push txdesc to + * @user: User position to push the txdesc to. It must be set to 0 if MU-MIMMO + * is not used. + */ +void ecrnx_ipc_txdesc_push(struct ecrnx_hw *ecrnx_hw, void *tx_desc, + void *hostid, int hw_queue, int user) +{ + +#if !defined(CONFIG_ECRNX_ESWIN_SDIO) && !defined(CONFIG_ECRNX_ESWIN_USB) + volatile struct txdesc_host *txdesc_host; + u32 *src, *dst; + int i; + + txdesc_host = ipc_host_txdesc_get(ecrnx_hw->ipc_env, hw_queue, user); + BUG_ON(!txdesc_host); + + dst = (typeof(dst))&txdesc_host->api; + src = (typeof(src))tx_desc; + for (i = 0; i < sizeof(txdesc_host->api) / sizeof(*src); i++) + *dst++ = *src++; + + wmb(); /* vs desc */ + + ipc_host_txdesc_push(ecrnx_hw->ipc_env, hw_queue, user, hostid); +#else + ecrnx_frame_send(ecrnx_hw, tx_desc, hostid, hw_queue, user); +#endif +} + +/** + * ecrnx_ipc_fw_trace_desc_get() - Return pointer to the start of trace + * description in IPC environment + * + * @ecrnx_hw: Main driver data + */ +void *ecrnx_ipc_fw_trace_desc_get(struct ecrnx_hw *ecrnx_hw) +{ +#ifndef CONFIG_ECRNX_ESWIN + return (void *)&(ecrnx_hw->ipc_env->shared->trace_pattern); +#else + return NULL; +#endif +} + +#ifndef CONFIG_ECRNX_ESWIN +/** + * ecrnx_ipc_sta_buffer_init - Initialize counter of bufferred data for a given sta + * + * @ecrnx_hw: Main driver data + * @sta_idx: Index of the station to initialize + */ +void ecrnx_ipc_sta_buffer_init(struct ecrnx_hw *ecrnx_hw, int sta_idx) +{ + int i; + volatile u32_l *buffered; + + if (sta_idx >= NX_REMOTE_STA_MAX) + return; + + buffered = ecrnx_hw->ipc_env->shared->buffered[sta_idx]; + + for (i = 0; i < TID_MAX; i++) { + *buffered++ = 0; + } +} +#endif +/** + * ecrnx_ipc_sta_buffer - Update counter of bufferred data for a given sta + * + * @ecrnx_hw: Main driver data + * @sta: Managed station + * @tid: TID on which data has been added or removed + * @size: Size of data to add (or remove if < 0) to STA buffer. + */ +void ecrnx_ipc_sta_buffer(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta, int tid, int size) +{ +#ifndef CONFIG_ECRNX_ESWIN + u32_l *buffered; + + if (!sta) + return; + + if ((sta->sta_idx >= NX_REMOTE_STA_MAX) || (tid >= TID_MAX)) + return; + + buffered = &ecrnx_hw->ipc_env->shared->buffered[sta->sta_idx][tid]; + + if (size < 0) { + size = -size; + if (*buffered < size) + *buffered = 0; + else + *buffered -= size; + } else { + // no test on overflow + *buffered += size; + } +#endif +} + +/** + * ecrnx_msgind() - IRQ handler callback for %IPC_IRQ_E2A_MSG + * + * @pthis: Pointer to main driver data + * @hostid: Pointer to IPC elem from e2amsgs_pool + */ +static u8 ecrnx_msgind(void *pthis, void *hostid) +{ + struct ecrnx_hw *ecrnx_hw = pthis; + u8 ret = 0; +#ifndef CONFIG_ECRNX_ESWIN + struct ecrnx_ipc_elem *elem = hostid; + struct ipc_e2a_msg *msg = elem->addr; + + REG_SW_SET_PROFILING(ecrnx_hw, SW_PROF_MSGIND); + + /* Look for pattern which means that this hostbuf has been used for a MSG */ + if (msg->pattern != IPC_MSGE2A_VALID_PATTERN) { + ret = -1; + goto msg_no_push; + } +#else + struct ipc_e2a_msg *msg = NULL; + + ecrnx_hw->msg_rx++; + ECRNX_DBG("%s enter 0x%x, 0x%x!!\n", __func__, pthis, hostid); + if(!pthis || !hostid){ + ECRNX_ERR(" %s input param error!! \n", __func__); + return ret; + } + msg = hostid; +#endif + /* Relay further actions to the msg parser */ + ecrnx_rx_handle_msg(ecrnx_hw, msg); + +#ifndef CONFIG_ECRNX_ESWIN + /* Reset the msg element and re-use it */ + msg->pattern = 0; + wmb(); + + /* Push back the buffer to the LMAC */ + ipc_host_msgbuf_push(ecrnx_hw->ipc_env, elem, elem->dma_addr); + +msg_no_push: + REG_SW_CLEAR_PROFILING(ecrnx_hw, SW_PROF_MSGIND); +#endif + ECRNX_DBG("%s exit!!", __func__); + return ret; +} + +/** + * ecrnx_msgackind() - IRQ handler callback for %IPC_IRQ_E2A_MSG_ACK + * + * @pthis: Pointer to main driver data + * @hostid: Pointer to command acknoledged + */ +static u8 ecrnx_msgackind(void *pthis, void *hostid) +{ + struct ecrnx_hw *ecrnx_hw = (struct ecrnx_hw *)pthis; + + ecrnx_hw->msg_tx_done++; + ecrnx_hw->cmd_mgr.llind(&ecrnx_hw->cmd_mgr, (struct ecrnx_cmd *)hostid); + return -1; +} + +/** + * ecrnx_radarind() - IRQ handler callback for %IPC_IRQ_E2A_RADAR + * + * @pthis: Pointer to main driver data + * @hostid: Pointer to IPC elem from e2aradars_pool + */ +static u8 ecrnx_radarind(void *pthis, void *hostid) +{ +#ifdef CONFIG_ECRNX_RADAR + struct ecrnx_hw *ecrnx_hw = pthis; + struct ecrnx_ipc_elem *elem = hostid; + struct radar_pulse_array_desc *pulses = elem->addr; + u8 ret = 0; + int i; + + /* Look for pulse count meaning that this hostbuf contains RADAR pulses */ + if (pulses->cnt == 0) { + ret = -1; + goto radar_no_push; + } + + if (ecrnx_radar_detection_is_enable(&ecrnx_hw->radar, pulses->idx)) { + /* Save the received pulses only if radar detection is enabled */ + for (i = 0; i < pulses->cnt; i++) { + struct ecrnx_radar_pulses *p = &ecrnx_hw->radar.pulses[pulses->idx]; + + p->buffer[p->index] = pulses->pulse[i]; + p->index = (p->index + 1) % ECRNX_RADAR_PULSE_MAX; + if (p->count < ECRNX_RADAR_PULSE_MAX) + p->count++; + } + + /* Defer pulse processing in separate work */ + if (! work_pending(&ecrnx_hw->radar.detection_work)) + schedule_work(&ecrnx_hw->radar.detection_work); + } + + /* Reset the radar element and re-use it */ + pulses->cnt = 0; +#ifndef CONFIG_ECRNX_ESWIN + wmb(); + + /* Push back the buffer to the LMAC */ + ipc_host_radarbuf_push(ecrnx_hw->ipc_env, elem, (u32)elem->dma_addr); +#endif +radar_no_push: + return ret; +#else + return -1; +#endif +} + +/** + * ecrnx_prim_tbtt_ind() - IRQ handler callback for %IPC_IRQ_E2A_TBTT_PRIM + * + * @pthis: Pointer to main driver data + */ +static void ecrnx_prim_tbtt_ind(void *pthis) +{ +#ifdef CONFIG_ECRNX_SOFTMAC + struct ecrnx_hw *ecrnx_hw = (struct ecrnx_hw *)pthis; + ecrnx_tx_bcns(ecrnx_hw); +#endif /* CONFIG_ECRNX_SOFTMAC */ +} + +/** + * ecrnx_sec_tbtt_ind() - IRQ handler callback for %IPC_IRQ_E2A_TBTT_SEC + * + * @pthis: Pointer to main driver data + */ +static void ecrnx_sec_tbtt_ind(void *pthis) +{ +} + +/** + * ecrnx_dbgind() - IRQ handler callback for %IPC_IRQ_E2A_DBG + * + * @pthis: Pointer to main driver data + * @hostid: Pointer to IPC elem from dbgmsgs_pool + */ + +#ifdef CONFIG_ECRNX_ESWIN_USB + extern void usb_dbg_printf(void * data, int len); +#endif +static u8 ecrnx_dbgind(void *pthis, void *hostid) +{ + u8 ret = 0; +#ifndef CONFIG_ECRNX_ESWIN + struct ecrnx_hw *ecrnx_hw = (struct ecrnx_hw *)pthis; + struct ecrnx_ipc_elem *elem = hostid; + struct ipc_dbg_msg *dbg_msg = elem->addr; + + REG_SW_SET_PROFILING(ecrnx_hw, SW_PROF_DBGIND); + + /* Look for pattern which means that this hostbuf has been used for a MSG */ + if (dbg_msg->pattern != IPC_DBG_VALID_PATTERN) { + ret = -1; + goto dbg_no_push; + } + + /* Display the string */ + //printk("%s %s", (char *)FW_STR, (char *)dbg_msg->string); + + /* Reset the msg element and re-use it */ + dbg_msg->pattern = 0; + wmb(); + + /* Push back the buffer to the LMAC */ + ipc_host_dbgbuf_push(ecrnx_hw->ipc_env, elem, (u32)elem->dma_addr); + +dbg_no_push: + REG_SW_CLEAR_PROFILING(ecrnx_hw, SW_PROF_DBGIND); + +#else + struct sk_buff *skb = (struct sk_buff *)hostid; +#ifdef CONFIG_ECRNX_ESWIN_USB + usb_dbg_printf(skb->data, skb->len); +#else + uint8_t string[IPC_DBG_PARAM_SIZE] = {0}; + if(skb->len < IPC_DBG_PARAM_SIZE) + { + memcpy(string, skb->data, skb->len); + } + else + { + printk("waring: string buff no enough \n"); + memcpy(string, skb->data, IPC_DBG_PARAM_SIZE-1); + } + ECRNX_PRINT("%s %s", (char *)FW_STR, (char *)string); +#endif +#endif + + return ret; +} + +/** + * ecrnx_ipc_rxbuf_init() - Allocate and initialize RX buffers. + * + * @ecrnx_hw: Main driver data + * @rx_bufsz: Size of the buffer to be allocated + * + * This function updates the RX buffer size according to the parameter and allocates the + * RX buffers + */ +#ifndef CONFIG_ECRNX_ESWIN +int ecrnx_ipc_rxbuf_init(struct ecrnx_hw *ecrnx_hw, uint32_t rx_bufsz) +{ + ecrnx_hw->ipc_env->rx_bufsz = rx_bufsz; + return(ecrnx_ipc_rxbuf_elems_allocs(ecrnx_hw)); +} +#endif +/** + * ecrnx_ipc_init() - Initialize IPC interface. + * + * @ecrnx_hw: Main driver data + * @shared_ram: Pointer to shared memory that contains IPC shared struct + * + * This function initializes IPC interface by registering callbacks, setting + * shared memory area and calling IPC Init function. + * It should be called only once during driver's lifetime. + */ +int ecrnx_ipc_init(struct ecrnx_hw *ecrnx_hw, u8 *shared_ram) +{ + struct ipc_host_cb_tag cb; + int res; + ECRNX_DBG("%s entry!!", __func__); + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* initialize the API interface */ + cb.recv_data_ind = ecrnx_rxdataind; + cb.recv_radar_ind = ecrnx_radarind; + cb.recv_msg_ind = ecrnx_msgind; + cb.recv_msgack_ind = ecrnx_msgackind; + cb.recv_dbg_ind = ecrnx_dbgind; + cb.send_data_cfm = ecrnx_txdatacfm; + cb.handle_data_cfm = ecrnx_handle_tx_datacfm; + + cb.prim_tbtt_ind = ecrnx_prim_tbtt_ind; + cb.sec_tbtt_ind = ecrnx_sec_tbtt_ind; + cb.recv_unsup_rx_vec_ind = ecrnx_unsup_rx_vec_ind; + + /* set the IPC environment */ + ecrnx_hw->ipc_env = (struct ipc_host_env_tag *) + kzalloc(sizeof(struct ipc_host_env_tag), GFP_KERNEL); + + if (!ecrnx_hw->ipc_env) + return -ENOMEM; + + /* call the initialization of the IPC */ + ipc_host_init(ecrnx_hw->ipc_env, &cb, + (struct ipc_shared_env_tag *)shared_ram, ecrnx_hw); + + ecrnx_cmd_mgr_init(&ecrnx_hw->cmd_mgr); + + ecrnx_rx_reord_init(ecrnx_hw); +#ifdef CONFIG_ECRNX_ESWIN_SDIO + ecrnx_sdio_init(ecrnx_hw); +#elif defined(CONFIG_ECRNX_ESWIN_USB) + ecrnx_usb_init(ecrnx_hw); +#endif + + res = ecrnx_elems_allocs(ecrnx_hw); + if (res) { + + kfree(ecrnx_hw->ipc_env); + ecrnx_hw->ipc_env = NULL; + } + ECRNX_DBG("%s exit!!", __func__); + return res; +} + +/** + * ecrnx_ipc_deinit() - Release IPC interface + * + * @ecrnx_hw: Main driver data + */ +void ecrnx_ipc_deinit(struct ecrnx_hw *ecrnx_hw) +{ + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + ecrnx_ipc_tx_drain(ecrnx_hw); + ecrnx_cmd_mgr_deinit(&ecrnx_hw->cmd_mgr); +#ifndef CONFIG_ECRNX_ESWIN + ecrnx_elems_deallocs(ecrnx_hw); +#endif + + ecrnx_rx_reord_deinit(ecrnx_hw); +#ifdef CONFIG_ECRNX_ESWIN_SDIO + if (ecrnx_hw->ipc_env->shared) { + kfree(ecrnx_hw->ipc_env->shared); + ecrnx_hw->ipc_env->shared = NULL; + } + ecrnx_sdio_deinit(ecrnx_hw); +#elif defined(CONFIG_ECRNX_ESWIN_USB) + ecrnx_usb_deinit(ecrnx_hw); +#endif + + if (ecrnx_hw->ipc_env) { + kfree(ecrnx_hw->ipc_env); + ecrnx_hw->ipc_env = NULL; + } +} + +/** + * ecrnx_ipc_start() - Start IPC interface + * + * @ecrnx_hw: Main driver data + */ +void ecrnx_ipc_start(struct ecrnx_hw *ecrnx_hw) +{ + ipc_host_enable_irq(ecrnx_hw->ipc_env, IPC_IRQ_E2A_ALL); +} + +/** + * ecrnx_ipc_stop() - Stop IPC interface + * + * @ecrnx_hw: Main driver data + */ +void ecrnx_ipc_stop(struct ecrnx_hw *ecrnx_hw) +{ + ipc_host_disable_irq(ecrnx_hw->ipc_env, IPC_IRQ_E2A_ALL); +} + +/** + * ecrnx_ipc_tx_drain() - Flush IPC TX buffers + * + * @ecrnx_hw: Main driver data + * + * This assumes LMAC is still (tx wise) and there's no TX race until LMAC is up + * tx wise. + * This also lets both IPC sides remain in sync before resetting the LMAC, + * e.g with ecrnx_send_reset. + */ +void ecrnx_ipc_tx_drain(struct ecrnx_hw *ecrnx_hw) +{ + int i, j; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + if (!ecrnx_hw->ipc_env) { + printk(KERN_CRIT "%s: bypassing (restart must have failed)\n", __func__); + return; + } + + for (i = 0; i < ECRNX_HWQ_NB; i++) { + for (j = 0; j < nx_txuser_cnt[i]; j++) { + struct sk_buff *skb; + while ((skb = (struct sk_buff *)ipc_host_tx_flush(ecrnx_hw->ipc_env, i, j))) { + struct ecrnx_sw_txhdr *sw_txhdr = + ((struct ecrnx_txhdr *)skb->data)->sw_hdr; +#ifndef CONFIG_ECRNX_ESWIN + #ifdef CONFIG_ECRNX_AMSDUS_TX + if (sw_txhdr->desc.host.packet_cnt > 1) { + struct ecrnx_amsdu_txhdr *amsdu_txhdr; + list_for_each_entry(amsdu_txhdr, &sw_txhdr->amsdu.hdrs, list) { + dma_unmap_single(ecrnx_hw->dev, amsdu_txhdr->dma_addr, + amsdu_txhdr->map_len, DMA_TO_DEVICE); + dev_kfree_skb_any(amsdu_txhdr->skb); + } + } + #endif + kmem_cache_free(ecrnx_hw->sw_txhdr_cache, sw_txhdr); + dma_unmap_single(ecrnx_hw->dev, sw_txhdr->dma_addr, + sw_txhdr->map_len, DMA_TO_DEVICE); +#endif + skb_pull(skb, sw_txhdr->headroom); +#ifdef CONFIG_ECRNX_SOFTMAC + ieee80211_free_txskb(ecrnx_hw->hw, skb); +#else + dev_kfree_skb_any(skb); +#endif /* CONFIG_ECRNX_SOFTMAC */ + } + } + } +} + +/** + * ecrnx_ipc_tx_pending() - Check if TX pframes are pending at FW level + * + * @ecrnx_hw: Main driver data + */ +bool ecrnx_ipc_tx_pending(struct ecrnx_hw *ecrnx_hw) +{ + return ipc_host_tx_frames_pending(ecrnx_hw->ipc_env); +} + +/** + * ecrnx_error_ind() - %DBG_ERROR_IND message callback + * + * @ecrnx_hw: Main driver data + * + * This function triggers the UMH script call that will indicate to the user + * space the error that occurred and stored the debug dump. Once the UMH script + * is executed, the ecrnx_umh_done() function has to be called. + */ +void ecrnx_error_ind(struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_ipc_elem_var *elem = &ecrnx_hw->dbgdump_elem.buf; + struct dbg_debug_dump_tag *dump = elem->addr; + + dma_sync_single_for_device(ecrnx_hw->dev, elem->dma_addr, elem->size, + DMA_FROM_DEVICE); + dev_err(ecrnx_hw->dev, "(type %d): dump received\n", + dump->dbg_info.error_type); + +#ifdef CONFIG_ECRNX_DEBUGFS + ecrnx_hw->debugfs.trace_prst = true; + ecrnx_trigger_um_helper(&ecrnx_hw->debugfs); +#endif +} + +/** + * ecrnx_umh_done() - Indicate User Mode helper finished + * + * @ecrnx_hw: Main driver data + * + */ +void ecrnx_umh_done(struct ecrnx_hw *ecrnx_hw) +{ + if (!test_bit(ECRNX_DEV_STARTED, &ecrnx_hw->flags)) + return; + + /* this assumes error_ind won't trigger before ipc_host_dbginfobuf_push + is called and so does not irq protect (TODO) against error_ind */ +#ifdef CONFIG_ECRNX_DEBUGFS + ecrnx_hw->debugfs.trace_prst = false; +#ifndef CONFIG_ECRNX_ESWIN + ipc_host_dbginfobuf_push(ecrnx_hw->ipc_env, ecrnx_hw->dbgdump_elem.buf.dma_addr); +#endif +#endif +} diff --git a/drivers/net/wireless/eswin/ecrnx_utils.h b/drivers/net/wireless/eswin/ecrnx_utils.h new file mode 100644 index 000000000000..09315b248d8a --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_utils.h @@ -0,0 +1,209 @@ +/** + * ecrnx_ipc_utils.h + * + * IPC utility function declarations + * + * Copyright (C) ESWIN 2015-2020 + */ +#ifndef _ECRNX_IPC_UTILS_H_ +#define _ECRNX_IPC_UTILS_H_ + +#include <linux/dma-mapping.h> +#include <linux/dmapool.h> +#include <linux/skbuff.h> + +#include "lmac_msg.h" + +enum ecrnx_dev_flag { + ECRNX_DEV_RESTARTING, + ECRNX_DEV_STACK_RESTARTING, + ECRNX_DEV_STARTED, + ECRNX_DEV_ADDING_STA, +}; + +struct ecrnx_hw; +struct ecrnx_sta; + +/** + * struct ecrnx_ipc_elem - Generic IPC buffer of fixed size + * + * @addr: Host address of the buffer. + * @dma_addr: DMA address of the buffer. + */ +struct ecrnx_ipc_elem { + void *addr; + dma_addr_t dma_addr; +}; + +/** + * struct ecrnx_ipc_elem_pool - Generic pool of IPC buffers of fixed size + * + * @nb: Number of buffers currenlty allocated in the pool + * @buf: Array of buffers (size of array is @nb) + * @pool: DMA pool in which buffers have been allocated + */ +struct ecrnx_ipc_elem_pool { + int nb; + struct ecrnx_ipc_elem *buf; + struct dma_pool *pool; +}; + +/** + * struct ecrnx_ipc_elem - Generic IPC buffer of variable size + * + * @addr: Host address of the buffer. + * @dma_addr: DMA address of the buffer. + * @size: Size, in bytes, of the buffer + */ +struct ecrnx_ipc_elem_var { + void *addr; + dma_addr_t dma_addr; + size_t size; +}; + +/** + * struct ecrnx_ipc_dbgdump_elem - IPC buffer for debug dump + * + * @mutex: Mutex to protect access to debug dump + * @buf: IPC buffer + */ +struct ecrnx_ipc_dbgdump_elem { + struct mutex mutex; + struct ecrnx_ipc_elem_var buf; +}; + +//static const u32 ecrnx_rxbuff_pattern = 0xCAFEFADE; +static const u32 ecrnx_rxbuff_pattern = 0xAAAAAA00; + + +/* + * Maximum Length of Radiotap header vendor specific data(in bytes) + */ +#define RADIOTAP_HDR_VEND_MAX_LEN 16 + +/* + * Maximum Radiotap Header Length without vendor specific data (in bytes) + */ +#define RADIOTAP_HDR_MAX_LEN 80 + +/* + * Unsupported HT Frame data length (in bytes) + */ +#define UNSUP_RX_VEC_DATA_LEN 2 + +/** + * struct ecrnx_ipc_skb_elem - IPC buffer for SKB element + * + * @skb: Pointer to the skb buffer allocated + * @dma_addr: DMA address of the data buffer fo skb + * + */ +struct ecrnx_ipc_skb_elem { + struct sk_buff *skb; + dma_addr_t dma_addr; +}; + +#ifdef CONFIG_ECRNX_FULLMAC + +/* Maximum number of rx buffer the fw may use at the same time */ +#define ECRNX_RXBUFF_MAX (64 * NX_REMOTE_STA_MAX) + +/** + * struct ecrnx_ipc_rxbuf_elems - IPC buffers for RX + * + * @skb: Array of buffer push to FW. + * @idx: Index of the last pushed skb.(Use to find the next free entry quicker) + * + * Note: contrary to softmac version, dma_addr are stored inside skb->cb. + * (cf &struct ecrnx_skb_cb) + */ +struct ecrnx_ipc_rxbuf_elems { + struct sk_buff *skb[ECRNX_RXBUFF_MAX]; + int idx; +}; + +/** + * struct ecrnx_skb_cb - Control Buffer structure for RX buffer + * + * @dma_addr: DMA address of the data buffer + * @pattern: Known pattern (used to check pointer on skb) + * @idx: Index in &struct ecrnx_hw.rxbuff_table that contains address of this + * buffer + */ +struct ecrnx_skb_cb { + dma_addr_t dma_addr; + uint32_t pattern; + uint32_t idx; +}; + +#define ECRNX_RXBUFF_DMA_ADDR_SET(skbuff, addr) \ + ((struct ecrnx_skb_cb *)(skbuff->cb))->dma_addr = addr +#define ECRNX_RXBUFF_DMA_ADDR_GET(skbuff) \ + ((struct ecrnx_skb_cb *)(skbuff->cb))->dma_addr + +#define ECRNX_RXBUFF_PATTERN_SET(skbuff, pat) \ + ((struct ecrnx_skb_cb *)(skbuff->cb))->pattern = pat +#define ECRNX_RXBUFF_PATTERN_GET(skbuff) \ + ((struct ecrnx_skb_cb *)(skbuff->cb))->pattern + +#define ECRNX_RXBUFF_IDX_SET(skbuff, val) \ + ((struct ecrnx_skb_cb *)(skbuff->cb))->idx = val +#define ECRNX_RXBUFF_IDX_GET(skbuff) \ + ((struct ecrnx_skb_cb *)(skbuff->cb))->idx + +#define ECRNX_RXBUFF_VALID_IDX(idx) ((idx) < ECRNX_RXBUFF_MAX) + +/* Used to ensure that hostid set to fw is never 0 */ +#define ECRNX_RXBUFF_IDX_TO_HOSTID(idx) ((idx) + 1) +#define ECRNX_RXBUFF_HOSTID_TO_IDX(hostid) ((hostid) - 1) + +#endif /* CONFIG_ECRNX_FULLMAC */ + + +#ifdef CONFIG_ECRNX_SOFTMAC +int ecrnx_ipc_rxbuf_elem_allocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_skb_elem *elem); +void ecrnx_ipc_rxbuf_elem_repush(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_skb_elem *elem); +#else +int ecrnx_ipc_rxbuf_elem_allocs(struct ecrnx_hw *ecrnx_hw); +void ecrnx_ipc_rxbuf_elem_pull(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb); +void ecrnx_ipc_rxbuf_elem_sync(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb, + int len); +void ecrnx_ipc_rxdesc_elem_repush(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_elem *elem); +void ecrnx_ipc_rxbuf_elem_repush(struct ecrnx_hw *ecrnx_hw, + struct sk_buff *skb); +#endif /* CONFIG_ECRNX_SOFTMAC */ + +void ecrnx_printf(const char *fmt, ...); +void ecrnx_ipc_msg_push(struct ecrnx_hw *ecrnx_hw, void *msg_buf, uint16_t len); +void ecrnx_ipc_txdesc_push(struct ecrnx_hw *ecrnx_hw, void *tx_desc, + void *hostid, int hw_queue, int user); +void *ecrnx_ipc_fw_trace_desc_get(struct ecrnx_hw *ecrnx_hw); +int ecrnx_ipc_rxbuf_init(struct ecrnx_hw *ecrnx_hw, uint32_t rx_bufsz); +int ecrnx_ipc_init(struct ecrnx_hw *ecrnx_hw, u8 *shared_ram); +void ecrnx_ipc_deinit(struct ecrnx_hw *ecrnx_hw); +void ecrnx_ipc_start(struct ecrnx_hw *ecrnx_hw); +void ecrnx_ipc_stop(struct ecrnx_hw *ecrnx_hw); +void ecrnx_ipc_tx_drain(struct ecrnx_hw *ecrnx_hw); +bool ecrnx_ipc_tx_pending(struct ecrnx_hw *ecrnx_hw); + +struct ipc_host_env_tag; +int ecrnx_ipc_elem_var_allocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_elem_var *elem, size_t elem_size, + enum dma_data_direction dir, + void *buf, const void *init, + void (*push)(struct ipc_host_env_tag *, uint32_t)); +void ecrnx_ipc_elem_var_deallocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_elem_var *elem); +int ecrnx_ipc_unsup_rx_vec_elem_allocs(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_ipc_skb_elem *elem); + +void ecrnx_error_ind(struct ecrnx_hw *ecrnx_hw); +void ecrnx_umh_done(struct ecrnx_hw *ecrnx_hw); + +void ecrnx_ipc_sta_buffer_init(struct ecrnx_hw *ecrnx_hw, int sta_idx); +void ecrnx_ipc_sta_buffer(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta, int tid, int size); + +#endif /* _ECRNX_IPC_UTILS_H_ */ diff --git a/drivers/net/wireless/eswin/ecrnx_version.h b/drivers/net/wireless/eswin/ecrnx_version.h new file mode 100644 index 000000000000..c56e94253bac --- /dev/null +++ b/drivers/net/wireless/eswin/ecrnx_version.h @@ -0,0 +1,10 @@ +#ifndef _ECRNX_VERSION_H_ +#define _ECRNX_VERSION_H_ + + +static inline void ecrnx_print_version(void) +{ + printk(ECRNX_VERS_BANNER"\n"); +} + +#endif /* _ECRNX_VERSION_H_ */ diff --git a/drivers/net/wireless/eswin/eswin_port/eswin_utils.c b/drivers/net/wireless/eswin/eswin_port/eswin_utils.c new file mode 100644 index 000000000000..bd75cc6c6505 --- /dev/null +++ b/drivers/net/wireless/eswin/eswin_port/eswin_utils.c @@ -0,0 +1,194 @@ +/** + ****************************************************************************** + * + * @file eswin_utils.c + * + *@brief msg and management frame send functions + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#include <linux/dma-mapping.h> +#include <linux/etherdevice.h> +#include <net/sock.h> + +#include "ecrnx_defs.h" +#include "ecrnx_tx.h" +#include "ecrnx_msg_tx.h" +#ifdef CONFIG_ECRNX_FULLMAC +#include "ecrnx_mesh.h" +#endif +#include "ecrnx_events.h" +#include "ecrnx_compat.h" + +#include "eswin_utils.h" + +#if 0 +#if defined(CONFIG_ECRNX_ESWIN_SDIO) +#include "sdio_host_interface.h" +#elif defined(CONFIG_ECRNX_ESWIN_USB) +#include "usb_host_interface.h" +#endif +#endif + +typedef int (*fn_send_t)(void * buff, int len, int flag); + + +fn_send_t ecrnx_host_send; + + +void ecrnx_send_handle_register(void * fn) +{ + ecrnx_host_send = (fn_send_t)fn; +} + + +int host_send(void *buff, int len, int flag) +{ + //ECRNX_DBG("%s:len:%d,flag %#x \n", __func__, len, flag); + return ecrnx_host_send(buff, len, flag); +} + +void ecrnx_frame_send(struct ecrnx_hw *ecrnx_hw, struct txdesc_api *tx_desc, + struct sk_buff *skb, int hw_queue, int user) +{ + u32_l tx_flag; + //struct ecrnx_txhdr *txhdr = NULL; + struct ecrnx_sw_txhdr *sw_txhdr = NULL; + + ecrnx_hw->data_tx++; + //send tx desc + tx_flag = ((user << FLAG_TXDESC_USER_SHIFT) & FLAG_TXDESC_USER_MASK) | ((hw_queue << FLAG_TXDESC_QUEUE_SHIFT) & FLAG_TXDESC_QUEUE_MASK); + tx_flag |= TX_FLAG_TX_DESC & FLAG_MSG_TYPE_MASK; +#if 0 + ECRNX_DBG("%s, user %d, queue %d, flag %#x, hostid 0x%08x, skb 0x%08x\n", __func__, +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + user, hw_queue, tx_flag, tx_desc->host.packet_addr[0], (u32_l)skb); + tx_desc->host.packet_addr[0] = (u32_l)skb; +#else + user, hw_queue, tx_flag, tx_desc->host.packet_addr, (u32_l)skb); + tx_desc->host.packet_addr = (u32_l)skb; +#endif +#endif + +#if defined(CONFIG_ECRNX_ESWIN_SDIO) + uint8_t *data = skb->data; + sw_txhdr = container_of(tx_desc, struct ecrnx_sw_txhdr, desc); + data += sw_txhdr->offset; + data -= ECRNX_TX_TXDESC_API_ALIGN; + memcpy(data, tx_desc, sizeof(struct txdesc_api)); + + host_send((void*)data, ECRNX_TX_TXDESC_API_ALIGN + sw_txhdr->frame_len, tx_flag); + +#elif defined(CONFIG_ECRNX_ESWIN_USB) + uint8_t *data; + struct ecrnx_txhdr *txhdr = (struct ecrnx_txhdr *)skb->data; + + sw_txhdr = txhdr->sw_hdr; + skb_pull(skb, sw_txhdr->offset - ECRNX_TX_TXDESC_API_ALIGN - sizeof(u32_l)); + memcpy(skb->data, (char*)&tx_flag, sizeof(u32_l)); + memcpy((uint8_t *)skb->data + sizeof(u32_l), (char*)tx_desc, sizeof(struct txdesc_api)); + + data = skb->data - sizeof(ptr_addr); + memcpy(data, (char*)&txhdr, sizeof(void *)); //store the ecrnx thhdr to the skb, cfm need use it + host_send((void *)skb, skb->len, tx_flag); +#endif + +#ifdef CONFIG_ECRNX_AMSDUS_TX + //send amsdu sub frame + if (sw_txhdr->desc.host.flags & TXU_CNTRL_AMSDU) { + struct ecrnx_amsdu_txhdr *amsdu_txhdr; + printk("...........amsdu tx\n"); + list_for_each_entry(amsdu_txhdr, &sw_txhdr->amsdu.hdrs, list) { + printk("...........amsdu tx: %x, %u\n", amsdu_txhdr->send_pos, amsdu_txhdr->map_len); +#if defined(CONFIG_ECRNX_ESWIN_SDIO) + host_send((void*)amsdu_txhdr->send_pos, amsdu_txhdr->map_len, tx_flag); +#elif defined(CONFIG_ECRNX_ESWIN_USB) + struct sk_buff *amsdu_skb = dev_alloc_skb(amsdu_txhdr->map_len + sizeof(int)); + + memcpy(amsdu_skb->data, &tx_flag, sizeof(int)); + memcpy((char*)amsdu_skb->data + sizeof(int), amsdu_txhdr->send_pos, amsdu_txhdr->map_len); + amsdu_skb->len = amsdu_txhdr->map_len + sizeof(int); + host_send((void *)amsdu_skb, amsdu_skb->len, tx_flag); +#endif + } + } +#endif + return; +} + +static inline void ecrnx_bcn_param_send(struct mm_bcn_change_req *req) +{ + if (req->bcn_ptr && req->bcn_len) { + host_send((void*)req->bcn_ptr, req->bcn_len, TX_FLAG_MSG_BEACON_IE); + } +} + +static inline void ecrnx_scanie_param_send(struct scan_start_req *req) +{ + if (req->add_ies && req->add_ie_len) { + host_send((void*)req->add_ies, req->add_ie_len, TX_FLAG_MSG_SCAN_IE); + } +} + +static inline void ecrnx_scanuie_param_send(struct scanu_start_req *req) +{ + if (req->add_ies && req->add_ie_len) { + host_send((void*)req->add_ies, req->add_ie_len, TX_FLAG_MSG_SCANU_IE); + } +} + +static inline void ecrnx_apm_param_send(struct apm_start_req *req) +{ + if (req->bcn_addr && req->bcn_len) { + host_send((void*)req->bcn_addr, req->bcn_len, TX_FLAG_MSG_SOFTAP_IE); + } +} + +void ecrnx_msg_send(struct ecrnx_cmd *cmd, uint16_t len) +{ + void *param = cmd->a2e_msg->param; + int param_len = cmd->a2e_msg->param_len; + + //cmd->a2e_msg->hostid = (u64_l)cmd; + memcpy(cmd->a2e_msg->hostid, &cmd, sizeof(struct ecrnx_cmd *)); + + switch (cmd->a2e_msg->id) { + case MM_BCN_CHANGE_REQ: + if (param_len >= sizeof(struct mm_bcn_change_req)) { + ecrnx_bcn_param_send((struct mm_bcn_change_req *)param); + } + break; + case SCAN_START_REQ: + if (param_len >= sizeof(struct scan_start_req)) { + ecrnx_scanie_param_send((struct scan_start_req *)param); + } + break; + case SCANU_START_REQ: + if (param_len >= sizeof(struct scanu_start_req)) { + ecrnx_scanuie_param_send((struct scanu_start_req *)param); + } + break; + case APM_START_REQ: + if (param_len >= sizeof(struct apm_start_req)) { + ecrnx_apm_param_send((struct apm_start_req *)param); + } + break; + default: + break; + } + + host_send((void*)cmd->a2e_msg, len, TX_FLAG_MSG_E); +} + +const struct ecrnx_element *cfg80211_find_ecrnx_elem(u8 eid, const u8 *ies, int len) +{ + const struct ecrnx_element *elem; + for_each_ecrnx_element(elem, ies, len) { + if (elem->id == (eid)) + return elem; + } + return NULL; +} diff --git a/drivers/net/wireless/eswin/eswin_port/eswin_utils.h b/drivers/net/wireless/eswin/eswin_port/eswin_utils.h new file mode 100644 index 000000000000..1258c889ad47 --- /dev/null +++ b/drivers/net/wireless/eswin/eswin_port/eswin_utils.h @@ -0,0 +1,123 @@ +/** + ****************************************************************************** + * + * @file eswin_utils.h + * + * @brief File containing the definition of tx/rx info and debug descriptors. + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef _ESWIN_UTILS_H_ +#define _ESWIN_UTILS_H_ + +#if defined(CONFIG_ECRNX_ESWIN_SDIO) +// for param in sdio irq flag +#define FLAG_MSG_TYPE_MASK 0x0f + +// queue in sdio irq flag +#define FLAG_TXDESC_QUEUE_SHIFT 5 +#define FLAG_TXDESC_QUEUE_MASK 0xe0 +#define FLAG_TXDESC_USER_SHIFT 4 +#define FLAG_TXDESC_USER_MASK 0x10 + +// tag in tx payload param +//#define FLAG_TXPAYLOAD_PARAM_SHIFT 6 +//#define FLAG_TXPAYLOAD_PARAM_MASK 0xffc0 //10 bits +#define TX_DESC_TAG_LEN 492 //SLAVE fhost_tx_desc_tag_len + +#elif defined(CONFIG_ECRNX_ESWIN_USB) +// for param in sdio irq flag +#define FLAG_MSG_TYPE_MASK 0x00ff + +// queue in sdio irq flag +#define FLAG_TXDESC_USER_SHIFT 8 +#define FLAG_TXDESC_USER_MASK 0x0f00 +#define FLAG_TXDESC_QUEUE_SHIFT 12 +#define FLAG_TXDESC_QUEUE_MASK 0xf000 +#endif + +typedef enum { + TX_FLAG_INVALID, + TX_FLAG_MSG_E, + TX_FLAG_MSG_SCAN_IE, + TX_FLAG_MSG_SCANU_IE, + TX_FLAG_MSG_BEACON_IE, + TX_FLAG_MSG_PROBE_IE, + TX_FLAG_MSG_SOFTAP_IE, + TX_FLAG_TX_DESC, + TX_FLAG_MSG_DEBUGFS_IE, + TX_FLAG_IWPRIV_IE, +#ifdef CONFIG_ECRNX_WIFO_CAIL + TX_FLAG_AMT_IWPRIV_IE, //10 +#endif + + TX_FLAG_MAX, +}host_tx_flag_e; + +typedef enum { + DBG_TYPE_D, //debug msg type + DBG_TYPE_I, //info msg type + DBG_TYPE_W, //warning msg type + DBG_TYPE_E, //error msg type + DBG_TYPE_O, //this type means always output + DBG_TYPE_MAX +}dbg_print_type_e; + +typedef struct { + uint8_t direct; // slave log director, 0 is print by uart, 1 is send to host + dbg_print_type_e dbg_level; //slave debug level +}dbg_req_t; + +typedef struct { + uint32_t hostid; + uint32_t tag; +}data_req_msg_t; + +typedef struct { +#ifdef CONFIG_ECRNX_ESWIN_SDIO + uint32_t resvd; /* for sdio */ + uint32_t rlen; /* for sdio */ +#endif + uint32_t frm_type; +}dispatch_hdr_t; + +/** + * struct pulse_elem - elements in pulse queue + * @ts: time stamp in usecs + */ +struct agg_elem { + struct list_head head; + struct sk_buff* skb; + /// Traffic Index + uint8_t tid; + uint16_t real_offset; /* dma address align 4, real data - real_offset */ + u32 sn; + /*bitmaps of agg frame to upaold*/ + uint64_t agg_upload_idx; + /*bitmaps of agg frame upaold status, 0 is forward, 1 is delete*/ + uint64_t agg_upload_status; +}; + +/** + * struct pulse_elem - elements in pulse queue + * @ts: time stamp in usecs + */ +struct defrag_elem { + struct list_head head; + struct sk_buff* skb; + /// Traffic Index + uint8_t tid; + uint16_t real_offset; /* dma address align 4, real data - real_offset */ + u32 sn; +}; + +void ecrnx_frame_send(struct ecrnx_hw *ecrnx_hw, struct txdesc_api *tx_desc, + struct sk_buff *skb, int hw_queue, int user); +void ecrnx_msg_send(struct ecrnx_cmd *cmd, uint16_t len); +int host_send(void *buff, int len, int flag); +const struct ecrnx_element *cfg80211_find_ecrnx_elem(u8 eid, const u8 *ies, int len); + +#endif diff --git a/drivers/net/wireless/eswin/feature_config/6600_config b/drivers/net/wireless/eswin/feature_config/6600_config new file mode 100644 index 000000000000..4d24d336b645 --- /dev/null +++ b/drivers/net/wireless/eswin/feature_config/6600_config @@ -0,0 +1,87 @@ +export ECRNX_MODULE_NAME=wlan_ecr6600 + +# Enable 6600 hal config +export CONFIG_6600_HAL=y +# Enable A-MSDU support (need FW support) +## Select this if FW is compiled with AMSDU support +export CONFIG_ECRNX_SPLIT_TX_BUF=n + +## Select this TO send AMSDU +export CONFIG_ECRNX_AMSDUS_TX=n + +# Enable BFMER support (need FW support) +export CONFIG_ECRNX_BFMER=n +CONFIG_ECRNX_MUMIMO_TX=n + +# Enable handling of radar event +export CONFIG_ECRNX_RADAR=y + +# Enable HW queue for Broadcast/Multicast traffic (need FW support) +export CONFIG_ECRNX_BCMC=y + +# Enable Monitor+Data interface support (need FW support) +export CONFIG_ECRNX_MON_DATA=n + +# extra DEBUG config +export CONFIG_ECRNX_SW_PROFILING=n +export CONFIG_ECRNX_DBG=y +export CONFIG_ECRNX_DBG_LEVEL=3 +export CONFIG_DEBUG_FS=y +export CONFIG_ECRNX_DEBUGFS_CUSTOM ?= y +# CONFIG PLATFORM +export CONFIG_ECRNX_ESWIN=y +export CONFIG_ECRNX_ESWIN_SDIO=y + +export CONFIG_ECRNX_ESWIN_USB=n + +# CONFIG SYSTERM TEST +export CONFIG_TEST_ESWIN_SDIO=n + +# CONFIG SYSTERM TEST +export CONFIG_STANDALONE_WIFI=n + +# CONFIG SYSTERM TEST +export CONFIG_STANDALONE_WIFI_BLE=y +# Enable BFMER support (need FW support) +export CONFIG_ECRNX_HE=y + +# Enable P2P Listen +export CONFIG_ECRNX_P2P=y +# Enable 5G +export CONFIG_ECRNX_5G=n + +#CONFIG SDIO WIFI CALIBRATION +export CONFIG_ECRNX_WIFO_CAIL=y + +# +# WAITING FOR KCONFIG { +# +export CONFIG_ECRNX_SOFTMAC=n +export CONFIG_ECRNX_FULLMAC=m +export CONFIG_ECRNX_FHOST=n + +# +# DEBUG OPTIONS +export CONFIG_ECRNX_UM_HELPER_DFLT="/dini/dini_bin/ecrnx_umh.sh" + +# +# FW ARCH: +export CONFIG_ECRNX_SDM=n +export CONFIG_ECRNX_TL4=n + +# IPC version +export CONFIG_ECRNX_OLD_IPC=n + +# Support of P2P DebugFS for enabling/disabling NoA and OppPS +export CONFIG_ECRNX_P2P_DEBUGFS=n + +# config_ceva_rtos = y use ceva rtos and add task_cli id +# config_ceva_rtos = n use freertos and no task_cli id +#export CONFIG_CEVA_RTOS=n + +export NX_VIRT_DEV_MAX=3 +export NX_REMOTE_STA_MAX=2 +export NX_MU_GROUP_MAX=62 +export NX_TXDESC_CNT=4 +export NX_TX_MAX_RATES=4 +export NX_CHAN_CTXT_CNT=3 diff --git a/drivers/net/wireless/eswin/feature_config/6600u_config b/drivers/net/wireless/eswin/feature_config/6600u_config new file mode 100644 index 000000000000..2ec3c83616d2 --- /dev/null +++ b/drivers/net/wireless/eswin/feature_config/6600u_config @@ -0,0 +1,98 @@ +export ECRNX_MODULE_NAME=wlan_ecr6600u_usb + +# Enable A-MSDU support (need FW support) +## Select this if FW is compiled with AMSDU support +export CONFIG_ECRNX_SPLIT_TX_BUF=n + +## Select this TO send AMSDU +export CONFIG_ECRNX_AMSDUS_TX=n + +# Enable BFMER support (need FW support) +export CONFIG_ECRNX_BFMER=n +CONFIG_ECRNX_MUMIMO_TX=n + +# Enable handling of radar event +export CONFIG_ECRNX_RADAR=y + +# Enable HW queue for Broadcast/Multicast traffic (need FW support) +export CONFIG_ECRNX_BCMC=y + +# Enable Monitor+Data interface support (need FW support) +export CONFIG_ECRNX_MON_DATA=n + +#Tx and RX data processing method +export CONFIG_ECRNX_WORKQUEUE=n +export CONFIG_ECRNX_TASKLET=n +export CONFIG_ECRNX_KTHREAD=y + +# extra DEBUG config +export CONFIG_ECRNX_SW_PROFILING=n +export CONFIG_ECRNX_DBG=y +export CONFIG_ECRNX_DBG_LEVEL=3 +export CONFIG_DEBUG_FS=n +export CONFIG_ECRNX_DEBUGFS_CUSTOM ?= n +# Support of P2P DebugFS for enabling/disabling NoA and OppPS +export CONFIG_ECRNX_P2P_DEBUGFS=n + +# CONFIG PLATFORM +export CONFIG_ECRNX_ESWIN=y +export CONFIG_ECRNX_ESWIN_SDIO=n + +export CONFIG_ECRNX_ESWIN_USB=y + +# CONFIG SYSTERM TEST +export CONFIG_TEST_ESWIN_SDIO=n + +# CONFIG SYSTERM TEST +export CONFIG_STANDALONE_WIFI=n + +# CONFIG SYSTERM TEST +export CONFIG_STANDALONE_WIFI_BLE=y +# Enable BFMER support (need FW support) +export CONFIG_ECRNX_HE=y + +# Enable P2P Listen +export CONFIG_ECRNX_P2P=y +# Enable 5G +export CONFIG_ECRNX_5G=n + +#CONFIG SDIO WIFI CALIBRATION +export CONFIG_ECRNX_WIFO_CAIL=y + +# +# WAITING FOR KCONFIG { +# +export CONFIG_ECRNX_SOFTMAC=n +export CONFIG_ECRNX_FULLMAC=m +export CONFIG_ECRNX_FHOST=n + +# +#Build for Android Platform +export CONFIG_ECRNX_ANDRIOD=y + +# +# DEBUG OPTIONS +export CONFIG_ECRNX_UM_HELPER_DFLT="/dini/dini_bin/ecrnx_umh.sh" + +# +# FW ARCH: +export CONFIG_ECRNX_SDM=n +export CONFIG_ECRNX_TL4=n + +# IPC version +export CONFIG_ECRNX_OLD_IPC=n + +# config_ceva_rtos = y use ceva rtos and add task_cli id +# config_ceva_rtos = n use freertos and no task_cli id +export CONFIG_CEVA_RTOS=y + +export NX_VIRT_DEV_MAX=3 +export NX_REMOTE_STA_MAX=4 +export NX_MU_GROUP_MAX=62 +export NX_TXDESC_CNT=4 +export NX_TX_MAX_RATES=4 +export NX_CHAN_CTXT_CNT=3 + +# config gpio for 6600u power key +#export CONFIG_POWERKEY_GPIO=4 + diff --git a/drivers/net/wireless/eswin/fullmac/Makefile b/drivers/net/wireless/eswin/fullmac/Makefile new file mode 100644 index 000000000000..8479e35ac1bc --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/Makefile @@ -0,0 +1,167 @@ +$(ECRNX_MODULE_NAME)-y := ecrnx_msg_tx.o \ + ecrnx_msg_rx.o \ + ecrnx_utils.o \ + ecrnx_cmds.o \ + ecrnx_cfgfile.o \ + ecrnx_strs.o \ + ecrnx_txq.o \ + ecrnx_mod_params.o \ + ecrnx_platform.o \ + ipc_host.o \ + hal_desc.o \ + ecrnx_iwpriv.o \ + fw_head_check.o \ + slave_log_buf.o \ + $(MAC_SRC)/ecrnx_tdls.o \ + $(MAC_SRC)/ecrnx_mesh.o \ + $(MAC_SRC)/ecrnx_main.o \ + $(MAC_SRC)/ecrnx_rx.o \ + $(MAC_SRC)/ecrnx_tx.o \ + $(MAC_SRC)/ecrnx_calibration_data.o + +ifeq ($(CONFIG_ECRNX_ESWIN_SDIO), y) +$(ECRNX_MODULE_NAME)-y += sdio/sdio.o \ + sdio/ecrnx_sdio.o \ + sdio/core.o \ + sdio/fw.o \ + eswin_port/eswin_utils.o +ifeq ($(CONFIG_ECRNX_ESWIN_SDIO), y) +#$(ECRNX_MODULE_NAME)-y += sdio/debug.o +endif +endif + +ifeq ($(CONFIG_ECRNX_ESWIN_USB), y) +$(ECRNX_MODULE_NAME)-y += usb/usb.o \ + usb/ecrnx_usb.o \ + usb/core.o \ + usb/fw.o \ + eswin_port/eswin_utils.o + +endif + +ifeq ($(CONFIG_ECRNX_WIFO_CAIL), y) +$(ECRNX_MODULE_NAME)-y += $(MAC_SRC)/ecrnx_amt.o +endif + +$(ECRNX_MODULE_NAME)-$(CONFIG_ECRNX_RADAR) += ecrnx_radar.o +$(ECRNX_MODULE_NAME)-$(CONFIG_DEBUG_FS) += ecrnx_debugfs.o +$(ECRNX_MODULE_NAME)-$(CONFIG_DEBUG_FS) += ecrnx_fw_dump.o +$(ECRNX_MODULE_NAME)-$(CONFIG_DEBUG_FS) += ecrnx_fw_trace.o +$(ECRNX_MODULE_NAME)-$(CONFIG_ECRNX_NL80211_TESTMODE) += ecrnx_testmode.o +$(ECRNX_MODULE_NAME)-$(CONFIG_ECRNX_BFMER) += ecrnx_bfmer.o +$(ECRNX_MODULE_NAME)-$(CONFIG_ECRNX_MUMIMO_TX) += ecrnx_mu_group.o +$(ECRNX_MODULE_NAME)-$(CONFIG_ECRNX_DBG) += ecrnx_debug.o +$(ECRNX_MODULE_NAME)-$(CONFIG_ECRNX_P2P) += $(MAC_SRC)/ecrnx_p2p.o +$(ECRNX_MODULE_NAME)-$(CONFIG_ECRNX_DEBUGFS_CUSTOM) += $(MAC_SRC)/ecrnx_debugfs_func.o +$(ECRNX_MODULE_NAME)-$(CONFIG_ECRNX_DEBUGFS_CUSTOM) += $(MAC_SRC)/ecrnx_debugfs_custom.o + + +EXTRA_CFLAGS+=-Wno-date-time +EXTRA_CFLAGS+=-Wno-declaration-after-statement +EXTRA_CFLAGS+=-Wno-format +EXTRA_CFLAGS+=-Wno-unused-variable +EXTRA_CFLAGS+=-Wno-misleading-indentation +EXTRA_CFLAGS+=-Wno-maybe-uninitialized +#EXTRA_CFLAGS+=-Wno-int-conversion +#EXTRA_CFLAGS+=-Wno-unused-function +#EXTRA_CFLAGS+=-Wno-undef +#EXTRA_CFLAGS+=-Wno-discarded-qualifiers +#EXTRA_CFLAGS+=-Wno-unused-label + + +ccflags-y := -DCONFIG_ECRNX_FULLMAC + +ccflags-y += -I$(DRIVER_PATH) +ccflags-y += -I$(DRIVER_PATH)/$(MAC_SRC) +ccflags-y += -I$(DRIVER_PATH)/eswin_port/ + +ifeq ($(CONFIG_ECRNX_ESWIN_SDIO), y) +ccflags-y += -I$(DRIVER_PATH)/sdio/ +else +ccflags-y += -I$(DRIVER_PATH)/usb/ +endif + +ccflags-$(CONFIG_ECRNX_RADAR) += -DCONFIG_ECRNX_RADAR +ccflags-$(CONFIG_ECRNX_MON_DATA) += -DCONFIG_ECRNX_MON_DATA +ccflags-$(CONFIG_ECRNX_BFMER) += -DCONFIG_ECRNX_BFMER +ccflags-$(CONFIG_ECRNX_SPLIT_TX_BUF) += -DCONFIG_ECRNX_SPLIT_TX_BUF +ifeq ($(CONFIG_ECRNX_SPLIT_TX_BUF), y) +ccflags-$(CONFIG_ECRNX_AMSDUS_TX) += -DCONFIG_ECRNX_AMSDUS_TX +endif +ccflags-$(CONFIG_ECRNX_SW_PROFILING) += -DCONFIG_ECRNX_SW_PROFILING +ccflags-$(CONFIG_ECRNX_MUMIMO_TX) += -DCONFIG_ECRNX_MUMIMO_TX + +ifeq ($(CONFIG_ECRNX_MUMIMO_TX), y) +ccflags-y += -DCONFIG_USER_MAX=2 +else +ccflags-y += -DCONFIG_USER_MAX=1 +endif + +ifeq ($(CONFIG_ECRNX_BCMC), y) +ccflags-y += -DNX_TXQ_CNT=5 +else +ccflags-y += -DNX_TXQ_CNT=4 +endif + +# FW PLATFORM +ifeq ($(CONFIG_ECRNX_DEBUGFS_CUSTOM), y) +ccflags-$(CONFIG_ECRNX_DEBUGFS_CUSTOM) += -DCONFIG_ECRNX_DEBUGFS_CUSTOM +endif + +ifeq ($(CONFIG_ECRNX_ESWIN), y) +ccflags-$(CONFIG_ECRNX_ESWIN) += -DCONFIG_ECRNX_ESWIN +endif + +ifeq ($(CONFIG_ECRNX_ESWIN_SDIO), y) +ccflags-$(CONFIG_ECRNX_ESWIN_SDIO) += -DCONFIG_ECRNX_ESWIN_SDIO +endif + +# SDIO TEST +ifeq ($(CONFIG_TEST_ESWIN_SDIO), y) +ccflags-$(CONFIG_TEST_ESWIN_SDIO) += -DCONFIG_TEST_ESWIN_SDIO +endif + +ifeq ($(CONFIG_ECRNX_ESWIN_USB), y) +ccflags-$(CONFIG_ECRNX_ESWIN_USB) += -DCONFIG_ECRNX_ESWIN_USB +endif + + +# standalone wifi +ifeq ($(CONFIG_STANDALONE_WIFI), y) +ccflags-$(CONFIG_STANDALONE_WIFI) += -DCONFIG_STANDALONE_WIFI +endif + +# standalone wifi+ble +ifeq ($(CONFIG_STANDALONE_WIFI_BLE), y) +ccflags-$(CONFIG_STANDALONE_WIFI_BLE) += -DCONFIG_STANDALONE_WIFI_BLE +endif + +# HE config +ifeq ($(CONFIG_ECRNX_HE), y) +ccflags-$(CONFIG_ECRNX_HE) += -DCONFIG_ECRNX_HE +endif + +# P2P config +ifeq ($(CONFIG_ECRNX_P2P), y) +ccflags-$(CONFIG_ECRNX_P2P) += -DCONFIG_ECRNX_P2P +endif + +# 5G config +ifeq ($(CONFIG_ECRNX_5G), y) +ccflags-$(CONFIG_ECRNX_5G) += -DCONFIG_ECRNX_5G +endif + +# Android platform config +ifeq ($(CONFIG_ECRNX_ANDRIOD), y) +ccflags-$(CONFIG_ECRNX_ANDRIOD) += -DCONFIG_ECRNX_ANDRIOD +endif + +# For old kernel (<=3.19) +ifeq ($(shell test $(VERSION) -lt 4 -a "$(CONFIG_VENDOR_ECRNX)" = y ; echo $$?),0) +ccflags-y += -DCONFIG_VENDOR_ECRNX_VHT_NO80 +endif + +#CONFIG SDIO WIFI CALIBRATION +ifeq ($(CONFIG_ECRNX_WIFO_CAIL), y) +ccflags-$(CONFIG_ECRNX_WIFO_CAIL) += -DCONFIG_ECRNX_WIFO_CAIL +endif diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_amt.c b/drivers/net/wireless/eswin/fullmac/ecrnx_amt.c new file mode 100644 index 000000000000..5aa4384f99a8 --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_amt.c @@ -0,0 +1,122 @@ +/** + ****************************************************************************** + * + * @file ecrnx_amt.c + * + * @brief Entry point of the ECRNX driver + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#include "ecrnx_main.h" +#include "eswin_utils.h" +#include "ecrnx_calibration_data.h" + +#ifdef CONFIG_ECRNX_WIFO_CAIL +#define AMT_GAIN_DELTA_MSG_FLAG ("gaindelta=") +#define AMT_GAIN_DELTA_MSG_BUFF_LEN 60 //strlen(AMT_GAIN_DELTA_MSG_FLAG) + sizeof(gain_delta) +static char gain_delta_msg_buf[AMT_GAIN_DELTA_MSG_BUFF_LEN]; +extern bool set_gain; + +struct ecrnx_iwpriv_amt_vif amt_vif; +#if defined(CONFIG_WIRELESS_EXT) && defined(CONFIG_WEXT_PRIV) +extern const struct iw_handler_def ecrnx_wext_private_handler; +#endif + +static int eswin_netdev_amt_open(struct net_device *ndev) +{ + ECRNX_DBG("amt netdev open\n"); + + return 0; +} + +static int eswin_netdev_amt_stop(struct net_device *ndev) +{ + ECRNX_DBG("amt netdev stop\n"); + + return 0; +} + +static netdev_tx_t eswin_netdev_amt_start_xmit(struct sk_buff *skb, + struct net_device *ndev) +{ + if (skb) + dev_kfree_skb_any(skb); + + return NETDEV_TX_OK; +} + +static const struct net_device_ops eswin_netdev_ops_amt = { + .ndo_open = eswin_netdev_amt_open, + .ndo_stop = eswin_netdev_amt_stop, + .ndo_start_xmit = eswin_netdev_amt_start_xmit +}; + +static int ecrnx_amt_gain_delta_msg_send(void) +{ + if (set_gain != true) + return -1; + + memcpy(gain_delta_msg_buf, AMT_GAIN_DELTA_MSG_FLAG, strlen(AMT_GAIN_DELTA_MSG_FLAG)); + memcpy((gain_delta_msg_buf + strlen(AMT_GAIN_DELTA_MSG_FLAG)), gain_delta, GAIN_DELTA_CFG_BUF_SIZE); + + host_send(gain_delta_msg_buf, sizeof(gain_delta_msg_buf), TX_FLAG_AMT_IWPRIV_IE); + + return 0; +} + +int ecrnx_amt_init(void) +{ + struct net_device *ndev; +#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0)) + ndev = alloc_netdev(0, "amt0", ether_setup); +#else + ndev = alloc_netdev(0, "amt0", NET_NAME_UNKNOWN, ether_setup); +#endif + if (!ndev) { + ECRNX_DBG("alloc netdev fail !!"); + return -1; + } + + amt_vif.ndev = ndev; + amt_vif.rxlen = 0; + init_waitqueue_head(&amt_vif.rxdataq); + + ndev->netdev_ops = &eswin_netdev_ops_amt; +#if defined(CONFIG_WIRELESS_EXT) && defined(CONFIG_WEXT_PRIV) + ndev->wireless_handlers = &ecrnx_wext_private_handler; +#endif + /* clear the mac address */ + memset(ndev->dev_addr, 0, ETH_ALEN); + + if (register_netdev(ndev) != 0) + goto err_dev; + + ecrnx_amt_gain_delta_msg_send(); + + ECRNX_PRINT(" %s:register the amt net device success\n", __func__); + return 0; + +err_dev: + ECRNX_ERR(" %s couldn't register the amt net device!!", __func__); + free_netdev(ndev); + amt_vif.ndev = NULL; + return -1; +} + +void ecrnx_amt_deinit(void) +{ + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + if (amt_vif.ndev) + unregister_netdev(amt_vif.ndev); + + amt_vif.ndev = NULL; +} +#endif + + + + diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_amt.h b/drivers/net/wireless/eswin/fullmac/ecrnx_amt.h new file mode 100644 index 000000000000..dc06e78c519a --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_amt.h @@ -0,0 +1,21 @@ +/** + ****************************************************************************** + * + * @file ecrnx_amt.h + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef _ECRNX_AMT_H_ +#define _ECRNX_AMT_H_ + +#ifdef CONFIG_ECRNX_WIFO_CAIL +int ecrnx_amt_init(void); +void ecrnx_amt_deinit(void); + +extern struct ecrnx_iwpriv_amt_vif amt_vif; +#endif + +#endif /* _ECRNX_AMT_H_ */ diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_calibration_data.c b/drivers/net/wireless/eswin/fullmac/ecrnx_calibration_data.c new file mode 100644 index 000000000000..1c845ee7ee29 --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_calibration_data.c @@ -0,0 +1,27 @@ +/** + **************************************************************************************** + * + * @file ecrnx_calibration_data.c + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ + +/** + * INCLUDE FILES + **************************************************************************************** + */ +#include "ecrnx_calibration_data.h" + +/** + * TYPE DEFINITIONS + **************************************************************************************** + */ +wifi_cal_data_t cal_result; +s8 gain_delta[GAIN_DELTA_CFG_BUF_SIZE] = { //base on 11b 11M, ht20/40 mcs7 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 11b */ + 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, /* 11g */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 11n_20M mcs 0 - 7 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 11n_40M mcs 0 - 7 */ + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2}; /* 11ax mcs 0 - 9 */ diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_calibration_data.h b/drivers/net/wireless/eswin/fullmac/ecrnx_calibration_data.h new file mode 100644 index 000000000000..ddf8301e9ce4 --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_calibration_data.h @@ -0,0 +1,80 @@ +/** + **************************************************************************************** + * + * @file ecrnx_calibration_data.h + * + * @brief Calibration Data function declarations + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ + +#ifndef _ECRNX_CALIBRATION_H_ +#define _ECRNX_CALIBRATION_H_ +#include "lmac_types.h" +/** + * INCLUDE FILES + **************************************************************************************** + */ + +/** + * DEFINES + **************************************************************************************** + */ +#define CAL_MAC_ADDR_LEN 6 +#define CAL_FORMAT_CLASS 3 +#define CAL_MCS_CNT 10 +#define CAL_CHAN_CNT 3 +#define IS_LOW_CHAN(ch) ((ch) >= 1 && (ch) <= 4) +#define IS_MID_CHAN(ch) ((ch) >= 5 && (ch) <= 8) +#define IS_HIG_CHAN(ch) ((ch) >= 9 && (ch) <= 14) +#define GAIN_DELTA_CFG_BUF_SIZE (CAL_FORMAT_MAX * CAL_MCS_CNT) + + +typedef enum { + CHAN_LEVEL_LOW, + CHAN_LEVEL_MID, + CHAN_LEVEL_HIGH, + CHAN_LEVEL_MAX, +} chan_level_e; + +typedef enum { + CAL_FORMAT_11B, + CAL_FORMAT_11G, + CAL_FORMAT_11N, + CAL_FORMAT_11N_40M, + CAL_FORMAT_11AX, + CAL_FORMAT_MAX +} wifi_cal_format_e; + +typedef struct { + uint8_t gain[CHAN_LEVEL_MAX][CAL_FORMAT_MAX][CAL_MCS_CNT]; +}tx_gain_cal_t; + +typedef struct { + uint8_t fine; + uint8_t corase; + uint8_t swl; +}cfo_cal_t; + +typedef struct { + tx_gain_cal_t tx_gain; + cfo_cal_t cfo_cal; + uint8_t mac_addr[CAL_MAC_ADDR_LEN]; + uint32_t lol; +}wifi_cal_data_t; + +/** + * TYPE DEFINITIONS + **************************************************************************************** + */ + +extern s8 gain_delta[]; +extern wifi_cal_data_t cal_result; +/** + * FUNCTION DECLARATIONS + **************************************************************************************** + */ + +#endif /* _ECRNX_CALIBRATION_H_ */ diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_debugfs_custom.c b/drivers/net/wireless/eswin/fullmac/ecrnx_debugfs_custom.c new file mode 100644 index 000000000000..d5ec98ca4bf6 --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_debugfs_custom.c @@ -0,0 +1,1171 @@ +#include "ecrnx_debugfs_custom.h" +#include "ecrnx_debugfs_func.h" +#include "slave_log_buf.h" +#include <linux/file.h> + +#ifdef CONFIG_ECRNX_DEBUGFS +#define DEFINE_SHOW_ATTRIBUTE(__name) \ +static int __name ## _open(struct inode *inode, struct file *file) \ +{ \ + return single_open(file, __name ## _show, inode->i_private); \ +} \ + \ +static const struct file_operations __name ## _fops = { \ + .owner = THIS_MODULE, \ + .open = __name ## _open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = single_release, \ +} + +#define DEFINE_SHOW_ATTRIBUTE_EX(__name) \ +static int __name ## _open(struct inode *inode, struct file *file) \ +{ \ + return single_open(file, __name ## _show, inode->i_private); \ +} \ + \ +static const struct file_operations __name ## _fops = { \ + .owner = THIS_MODULE, \ + .open = __name ## _open, \ + .read = seq_read, \ + .write = __name ## _write, \ + .llseek = seq_lseek, \ + .release = single_release, \ +} + +static long custom_sys_write(unsigned int fd, const char __user *buf, size_t count) +{ + long ret = -EBADF; + struct file *file = NULL; + mm_segment_t status; + loff_t offset; + + if (!buf) { + printk("Write buffer was empty.\n"); + return ret; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + file = fget(fd); + if (file) { + offset = file->f_pos; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) + ret = kernel_write(file, buf, count, (loff_t *)&offset); +#else + ret = kernel_write(file, buf, count, 0); +#endif + file->f_pos = offset; + fput(file); + } +#else + print_hex_dump(KERN_DEBUG, DBG_PREFIX, DUMP_PREFIX_NONE, 16, 1, buf, count, false); +#endif + return ret; +} + + +void seq_reg_display_u32(struct seq_file *seq, u32 addr, u32 *reg, u32 len) +{ + u32 i; + + for (i=0; i<len; i++) + { + if (i%4 == 0) + seq_printf(seq, "0x%08X ", (addr+ 4*i)); + + seq_printf(seq, " 0x%08X", reg[i]); + + if (i%4 == 3) + seq_printf(seq, "\n"); + } + + if (len % 4) + seq_printf(seq, "\n"); +} + +void seq_reg_display_u8(struct seq_file *seq, u32 addr, u8 *reg, u32 len) +{ + u32 i; + + for (i=0; i<len; i++) + { + if (i%16 == 0) + seq_printf(seq, "0x%04X ", (addr+i)&0xFFFF); + + seq_printf(seq, " %02X", reg[i]); + + if (i%16 == 7) + seq_printf(seq, " "); + + if (i%16 == 15) + seq_printf(seq, "\n"); + } + + if (len % 16) + seq_printf(seq, "\n"); +} +void read_reg_display_u32(u32 addr, u32 *reg, u32 len) +{ + u32 i; + u32 size = len*11 + (len + 3)/4*16 + len/4; + u32 point = 0; + char * buf = NULL; + + buf = kmalloc(size, GFP_ATOMIC); + if(buf == NULL) + { + return; + } + for (i=0; i<len; i++) + { + if (i%4 == 0) + { + sprintf(buf+point,"addr:0x%08X", (addr+ 4*i)); + point += 15; + buf[point] = '\n'; + point += 1; + + } + + sprintf(buf+point,"0x%08X", reg[i]); + point += 10; + buf[point] = '\n'; + point += 1; + + + if (i%4 == 3) + { + buf[point] = '\n'; + point += 1; + } + } + custom_sys_write(0,buf,size); + if(buf != NULL) + { + kfree(buf); + buf = NULL; + } + +} +/* +void reg_display_u32(u32 addr, u32 *reg, u32 len) +{ + u32 i; + + for (i=0; i<len; i++) + { + if (i%4 == 0) + ECRNX_PRINT("0x%08X", (addr+ 4*i)); + + ECRNX_PRINT("0x%08X", reg[i]); + + if (i%4 == 3) + printk("\n"); + } + + if (len % 4) + printk("\n"); +} +*/ +void reg_display_u8(u32 addr, u8 *reg, u32 len) +{ + u32 i; + + for (i=0; i<len; i++) + { + if (i%16 == 0) + ECRNX_PRINT("0x%04X ", (addr+i)&0xFFFF); + + ECRNX_PRINT(" %02X", reg[i]); + + if (i%16 == 7) + printk(" "); + + if (i%16 == 15) + printk("\n"); + } + + if (len % 16) + printk("\n"); +} + +static int drv_cfg_show(struct seq_file *seq, void *v) +{ +//#define DRV_CFG_DETAILS + + u8 host_ver[32]; + + ecrnx_host_ver_get(host_ver); + + seq_printf(seq, "Kernel Version : %d.%d.%d\n", LINUX_VERSION_CODE >> 16, (LINUX_VERSION_CODE >> 8)&0xFF, LINUX_VERSION_CODE&0xFF); + seq_printf(seq, "Driver Version : %s\n", host_ver); + seq_printf(seq, "------------------------------------------------\n"); + +#ifdef CONFIG_ECRNX_ESWIN + seq_printf(seq, "CONFIG_ECRNX_ESWIN=1\n"); +#else + #ifdef DRV_CFG_DETAILS + seq_printf(seq, "CONFIG_ECRNX_ESWIN=0\n"); + #endif +#endif + + +#ifdef CONFIG_ECRNX_FULLMAC + seq_printf(seq, "CONFIG_ECRNX_FULLMAC=1\n"); +#else + #ifdef DRV_CFG_DETAILS + seq_printf(seq, "CONFIG_ECRNX_FULLMAC=0\n"); + #endif +#endif + + +#ifdef CONFIG_ECRNX_SOFTMAC + seq_printf(seq, "CONFIG_ECRNX_SOFTMAC=1\n"); +#else + #ifdef DRV_CFG_DETAILS + seq_printf(seq, "CONFIG_ECRNX_SOFTMAC=0\n"); + #endif +#endif + + +#ifdef CONFIG_ECRNX_ESWIN_USB + seq_printf(seq, "CONFIG_ECRNX_ESWIN_USB=1\n"); +#else + #ifdef DRV_CFG_DETAILS + seq_printf(seq, "CONFIG_ECRNX_ESWIN_USB=0\n"); + #endif +#endif + + +#ifdef CONFIG_ECRNX_ESWIN_SDIO + seq_printf(seq, "CONFIG_ECRNX_ESWIN_SDIO=1\n"); +#else + #ifdef DRV_CFG_DETAILS + seq_printf(seq, "CONFIG_ECRNX_ESWIN_SDIO=0\n"); + #endif +#endif + + +#ifdef CONFIG_ECRNX_HE + seq_printf(seq, "CONFIG_ECRNX_HE=1\n"); +#else + #ifdef DRV_CFG_DETAILS + seq_printf(seq, "CONFIG_ECRNX_HE=0\n"); + #endif +#endif + + +#ifdef CONFIG_ECRNX_P2P + seq_printf(seq, "CONFIG_ECRNX_P2P=1\n"); +#else + #ifdef DRV_CFG_DETAILS + seq_printf(seq, "CONFIG_ECRNX_P2P=0\n"); + #endif +#endif + + +#ifdef CONFIG_ECRNX_FHOST + seq_printf(seq, "CONFIG_ECRNX_FHOST=1\n"); +#else + #ifdef DRV_CFG_DETAILS + seq_printf(seq, "CONFIG_ECRNX_FHOST=0\n"); + #endif +#endif + + +#ifdef CONFIG_ECRNX_SDM + seq_printf(seq, "CONFIG_ECRNX_SDM=1\n"); +#else + #ifdef DRV_CFG_DETAILS + seq_printf(seq, "CONFIG_ECRNX_SDM=0\n"); + #endif +#endif + + +#ifdef CONFIG_ECRNX_TL4 + seq_printf(seq, "CONFIG_ECRNX_TL4=1\n"); +#else + #ifdef DRV_CFG_DETAILS + seq_printf(seq, "CONFIG_ECRNX_TL4=0\n"); + #endif +#endif + + +#ifdef CONFIG_ECRNX_MUMIMO_TX + seq_printf(seq, "CONFIG_ECRNX_MUMIMO_TX=1\n"); +#else + #ifdef DRV_CFG_DETAILS + seq_printf(seq, "CONFIG_ECRNX_MUMIMO_TX=0\n"); + #endif +#endif + + +#ifdef CONFIG_ECRNX_RADAR + seq_printf(seq, "CONFIG_ECRNX_RADAR=1\n"); +#else + #ifdef DRV_CFG_DETAILS + seq_printf(seq, "CONFIG_ECRNX_RADAR=0\n"); + #endif +#endif + + +#ifdef CONFIG_ECRNX_BCMC + seq_printf(seq, "CONFIG_ECRNX_BCMC=1\n"); +#else + #ifdef DRV_CFG_DETAILS + seq_printf(seq, "CONFIG_ECRNX_BCMC=0\n"); + #endif +#endif + + +#ifdef CONFIG_ECRNX_MON_DATA + seq_printf(seq, "CONFIG_ECRNX_MON_DATA=1\n"); +#else + #ifdef DRV_CFG_DETAILS + seq_printf(seq, "CONFIG_ECRNX_MON_DATA=0\n"); + #endif +#endif + + +#ifdef CONFIG_ECRNX_SW_PROFILING + seq_printf(seq, "CONFIG_ECRNX_SW_PROFILING=1\n"); +#else + #ifdef DRV_CFG_DETAILS + seq_printf(seq, "CONFIG_ECRNX_SW_PROFILING=0\n"); + #endif +#endif + + +#ifdef CONFIG_ECRNX_DBG + seq_printf(seq, "CONFIG_ECRNX_DBG=1\n"); +#else + #ifdef DRV_CFG_DETAILS + seq_printf(seq, "CONFIG_ECRNX_DBG=0\n"); + #endif +#endif + + +#ifdef CFG_WOW_SUPPORT + seq_printf(seq, "CFG_WOW_SUPPORT=1\n"); +#else + #ifdef DRV_CFG_DETAILS + seq_printf(seq, "CFG_WOW_SUPPORT=0\n"); + #endif +#endif + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(drv_cfg); + + +static int log_level_show(struct seq_file *seq, void *v) +{ + LOG_CTL_ST log; + + if (0 == ecrnx_log_level_get(&log)) + seq_printf(seq, "log_level:%d dir:%d\n", log.level, log.dir); + else + seq_printf(seq, "\n"); + + return 0; +} + +static ssize_t log_level_write(struct file *filp, const char __user *buffer, + size_t count, loff_t *ppos) +{ + u8 kbuf[128]={0}; + u32 level,dir; + + if (count > 6) // 255:0\n + return -1; + + if (0 != copy_from_user(kbuf, buffer, count)) + return -1; + sscanf(kbuf, "%d:%d\n", &level, &dir); + + if (dir > 1) + return -1; + + ecrnx_fw_log_level_set(level, dir); + + return count; +} +DEFINE_SHOW_ATTRIBUTE_EX(log_level); + +static ssize_t mac_log_read(struct file *file , char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ring_buffer *buf_handle; + int len = 0; + char buf[512] = {0}; + if (false == ecrnx_log_host_enable()) + return 0; +#ifdef CONFIG_ECRNX_ESWIN_USB + buf_handle = usb_dbg_buf_get(); + ring_buffer_scrolling_display(buf_handle,true); + while(buf_handle->show) + { + len = ring_buffer_len(buf_handle); + len = min(len, 512); + //ECRNX_PRINT("mac_log_read len:%d %d %d", len,buf_handle->write_point, buf_handle->read_point); + if(len > 0) + { + ring_buffer_get(buf_handle,buf,len); + custom_sys_write(0,buf,len); + msleep(35); + } + else + { + msleep(500); + } + cond_resched(); + } +#endif + + return count; +} + +static ssize_t mac_log_write(struct file *filp, const char __user *buffer, + size_t count, loff_t *ppos) +{ + u8 kbuf[128]={0}; + u32 show; + struct ring_buffer *buf_handle; + + if (count > 2) // 255:0\n + return -1; + + if (0 != copy_from_user(kbuf, buffer, count)) + return -1; + sscanf(kbuf, "%d:%d\n", &show); +#ifdef CONFIG_ECRNX_ESWIN_USB + if (show == 0) + { + buf_handle = usb_dbg_buf_get(); + ring_buffer_scrolling_display(buf_handle, false); + } +#endif + + return count; +} + +static const struct file_operations new_mac_log_fops = { \ + .read = mac_log_read, \ + .write = mac_log_write, \ +}; + +static int ver_info_show(struct seq_file *seq, void *v) +{ + u8 host_ver[32]; + u8 build_time[32]; + + ecrnx_host_ver_get(host_ver); + ecrnx_build_time_get(build_time); + + seq_printf(seq, "%s\n", host_ver); + seq_printf(seq, "build time: %s\n", build_time); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ver_info); + +extern bin_head_data head; +static int fw_info_show(struct seq_file *seq, void *v) +{ + struct tm timenow = {0}; + + localtime(&timenow, head.UTC_time); + if(head.fw_Info != NULL) + { + seq_printf(seq, "sdk_verson:%s\n", head.fw_Info); + } + seq_printf(seq, "fw_crc:0x%8x\n", head.crc32); + seq_printf(seq, "fw_build_time: %04d-%02d-%02d %02d:%02d:%02d\n",(int)timenow.tm_year,timenow.tm_mon,timenow.tm_mday,timenow.tm_hour,timenow.tm_min,timenow.tm_sec); + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(fw_info); + +struct dentry *ecr6600u_dir = NULL; +struct dentry *drv_cfg, *log_level, *mac_log, *ver_info, *fw_info; + +int ecrnx_debugfs_info_init(void *private_data) +{ + ecr6600u_dir = debugfs_create_dir("ecr6600u", NULL); + if (!ecr6600u_dir) + goto Fail; + + drv_cfg = debugfs_create_file("drv_cfg", 0444, ecr6600u_dir, private_data, &drv_cfg_fops); + if (!drv_cfg) + goto Fail; + + log_level = debugfs_create_file("log_level", 0666, ecr6600u_dir, private_data, &log_level_fops); + if (!log_level) + goto Fail; + + mac_log = debugfs_create_file("maclog", 0444, ecr6600u_dir, private_data, &new_mac_log_fops); + if (!mac_log) + goto Fail; + + ver_info = debugfs_create_file("ver_info", 0444, ecr6600u_dir, private_data, &ver_info_fops); + if (!ver_info) + goto Fail; + + fw_info = debugfs_create_file("fw_info", 0444, ecr6600u_dir, private_data, &fw_info_fops); + if (!fw_info) + goto Fail; + return 0; + +Fail: + return -ENOENT; +} + + +static int rf_info_show(struct seq_file *seq, void *v) +{ + RF_INFO_ST cur, oper; + + if (0 == ecrnx_rf_info_get(seq, IF_STA, &cur, &oper)) + { + seq_printf(seq, "cur_ch=%d, cur_bw=%d, cur_ch_offset=%d\n", cur.ch, cur.bw, cur.ch_offset); + // seq_printf(seq, "oper_ch=%d, oper_bw=%d, oper_ch_offset=%d\n", oper.ch, oper.bw, oper.ch_offset); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(rf_info); + +static int mac_reg_dump_show(struct seq_file *seq, void *v) +{ + ecrnx_mac_reg_dump(seq); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(mac_reg_dump); + +static int rf_reg_dump_show(struct seq_file *seq, void *v) +{ + ecrnx_rf_reg_dump(seq); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(rf_reg_dump); + +static int bb_reg_dump_show(struct seq_file *seq, void *v) +{ + ecrnx_bb_reg_dump(seq); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(bb_reg_dump); + +static int country_code_show(struct seq_file *seq, void *v) +{ + char country_code[3]; + if (0 == ecrnx_country_code_get(seq, country_code)) + { + seq_printf(seq, "%s\n", country_code); + } + else + { + seq_printf(seq, "UNSPECIFIED\n"); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(country_code); + +static int mac_addr_show(struct seq_file *seq, void *v) +{ + u8 mac_addr_info[128]; + + if (0 == ecrnx_mac_addr_get_ex(seq, mac_addr_info)) + { + seq_printf(seq, "%s", mac_addr_info); + } + else + { + seq_printf(seq, "--\n"); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(mac_addr); + +static int efuse_map_show(struct seq_file *seq, void *v) +{ + ecrnx_efuse_map_dump(seq); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(efuse_map); + +static int read_reg_show(struct seq_file *seq, void *v) +{ + return 0; +} +static ssize_t read_reg_write(struct file *filp, const char __user *buffer, + size_t count, loff_t *ppos) +{ + u8 kbuf[128]={0}; + u32 addr, len; + int ret_code; + char * read_reg_buf = NULL; + + if (count > sizeof(kbuf)) + return -1; + + if (0 != copy_from_user(kbuf, buffer, count)) + return -1; + + ret_code = sscanf(kbuf, "0x%x %d\n", &addr, &len); + if (ret_code != 2) + { + ECRNX_ERR("Parameter error.\n"); + return -1; + } + + if ( addr%4 ) + { + ECRNX_ERR("Address is invalid.\n"); + return -1; + } + + if (0 == ecrnx_slave_reg_read(addr, reg_buf, len * sizeof(reg_buf[0]))) + read_reg_display_u32(addr, reg_buf, len); + return count; +} +DEFINE_SHOW_ATTRIBUTE_EX(read_reg); +static int write_reg_show(struct seq_file *seq, void *v) +{ + return 0; +} + +static ssize_t write_reg_write(struct file *filp, const char __user *buffer, + size_t count, loff_t *ppos) +{ + u8 kbuf[128]={0}; + u32 addr, data; + int ret_code,ret = count; + char* write_reg_result = NULL; + + if (count > sizeof(kbuf)) + ret = -1; + + if (0 != copy_from_user(kbuf, buffer, count)) + ret = -1; + + ret_code = sscanf(kbuf, "0x%x 0x%x\n", &addr, &data); + if (ret_code != 2) + { + write_reg_result = "Parameter error.\n"; + ret = -1; + } + + if ( addr%4 ) + { + write_reg_result = "Address is invalid.\n"; + ret = -1; + } + + if (0 != ecrnx_slave_reg_write(addr, data, 1)) + { + write_reg_result = "Write error.\n"; + ret = -1; + } + else + write_reg_result = "Write success.\n"; + if(write_reg_result != NULL) + { + custom_sys_write(0,write_reg_result,strlen(write_reg_result)); + } + return ret; +} +DEFINE_SHOW_ATTRIBUTE_EX(write_reg); + +static int rf_tx_rx_info_show(struct seq_file *seq, void *v) +{ +// u8 *resp = NULL; +// u8 cli[]="rf_get_rssi 0 1 2"; + +// ecrnx_slave_cli_send(cli, resp); + + return 0; +} + +static ssize_t rf_tx_rx_info_write(struct file *filp, const char __user *buffer, + size_t count, loff_t *ppos) +{ + u8 kbuf[128]={0}; + + if (count > sizeof(kbuf)) + return -1; + + if (0 != copy_from_user(kbuf, buffer, count)) + return -1; + + cli_cmd_parse(kbuf); + + return count; +} + +DEFINE_SHOW_ATTRIBUTE_EX(rf_tx_rx_info); + +struct dentry *hw_dir = NULL; +struct dentry *hw_rf_info, *hw_mac_reg_dump, *hw_rf_reg_dump, *hw_bb_reg_dump, *hw_country_code, *hw_mac_addr; +struct dentry *hw_efuse_map, *hw_read_reg, *hw_write_reg, *hw_rf_tx_rx_info; + +int ecrnx_debugfs_hw_init(void *private_data) +{ + if (!ecr6600u_dir) + return -ENOENT; + + hw_dir = debugfs_create_dir("hw", ecr6600u_dir); + if (!hw_dir) + return -ENOENT; + + hw_rf_info = debugfs_create_file("rf_info", 0444, hw_dir, private_data, &rf_info_fops); + if (!hw_rf_info) + goto Fail; + + hw_mac_reg_dump = debugfs_create_file("mac_reg_dump", 0444, hw_dir, private_data, &mac_reg_dump_fops); + if (!hw_mac_reg_dump) + goto Fail; + + hw_rf_reg_dump = debugfs_create_file("rf_reg_dump", 0444, hw_dir, private_data, &rf_reg_dump_fops); + if (!hw_rf_reg_dump) + goto Fail; + + hw_bb_reg_dump = debugfs_create_file("bb_reg_dump", 0444, hw_dir, private_data, &bb_reg_dump_fops); + if (!hw_bb_reg_dump) + goto Fail; + + hw_country_code = debugfs_create_file("country_code", 0444, hw_dir, private_data, &country_code_fops); + if (!hw_country_code) + goto Fail; + + hw_mac_addr = debugfs_create_file("mac_addr", 0444, hw_dir, private_data, &mac_addr_fops); + if (!hw_mac_addr) + goto Fail; + + hw_efuse_map = debugfs_create_file("efuse_map", 0444, hw_dir, private_data, &efuse_map_fops); + if (!hw_efuse_map) + goto Fail; + + hw_read_reg = debugfs_create_file("read_reg", 0222, hw_dir, private_data, &read_reg_fops); + if (!hw_read_reg) + goto Fail; + + hw_write_reg = debugfs_create_file("write_reg", 0222, hw_dir, private_data, &write_reg_fops); + if (!hw_write_reg) + goto Fail; + + hw_rf_tx_rx_info = debugfs_create_file("rf_tx_rx_info", 0666, hw_dir, private_data, &rf_tx_rx_info_fops); + if (!hw_rf_tx_rx_info) + goto Fail; + + return 0; + +Fail: + debugfs_remove_recursive(hw_dir); + hw_dir = NULL; + return -ENOENT; +} + +#ifndef MAC_FMT +#define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x" +#endif +#ifndef MAC_ARG +#define MAC_ARG(x) ((u8 *)(x))[0], ((u8 *)(x))[1], ((u8 *)(x))[2], ((u8 *)(x))[3], ((u8 *)(x))[4], ((u8 *)(x))[5] +#endif + +static int survey_info_show(struct seq_file *seq, void *v) +{ + int index = 0; + struct ecrnx_hw *ecrnx_hw; + struct ecrnx_debugfs_survey_info_tbl *entry; + + if (seq->private != NULL) + ecrnx_hw = seq->private; + else + return -1; + +//seq_printf(seq, "index bssid ch RSSI SdBm Noise age flag ssid \n"); + seq_printf(seq, "%5s %-17s %3s %-3s %-4s %-4s %5s %32s %32s\n", "index", "bssid", "ch", "RSSI", "SdBm", "Noise", "age", "flag", "ssid"); + + list_for_each_entry(entry, &ecrnx_hw->debugfs_survey_info_tbl_ptr, list) { + seq_printf(seq, "%5d "MAC_FMT" %3d %3d %4d %4d %5d %32s %32s\n", + ++index, + MAC_ARG(entry->bssid), + entry->ch, + entry->rssi, + entry->sdbm, + entry->noise, + entry->age, + entry->flag, + entry->ssid); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(survey_info); + +static int sta_info_show(struct seq_file *seq, void *v) +{ + s8 noise_dbm; + struct cfg80211_chan_def chandef; + u32 bw_array[]={20, 20, 40, 80, 80, 160, 5, 10}; + + if (0 == ecrnx_signal_level_get(seq, IF_STA, &noise_dbm)) + seq_printf(seq, "signal level : %d dBm\n", noise_dbm); + else + seq_printf(seq, "signal level : \n"); + + if (0 == ecrnx_channel_get(seq, IF_STA, &chandef)) + { + seq_printf(seq, "frequency : %d MHz\n", chandef.center_freq1); + seq_printf(seq, "bandwidth : %d MHz\n", bw_array[chandef.width]); + } + else + { + seq_printf(seq, "frequency : \n"); + seq_printf(seq, "bandwidth : \n"); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(sta_info); + +static int ht_info_show(struct seq_file *seq, void *v) +{ + struct cfg80211_chan_def chandef; + u32 bw_array[]={20, 20, 40, 80, 80, 160, 5, 10}; + + if (0 == ecrnx_channel_get(seq, IF_STA, &chandef)) + seq_printf(seq, "bandwidth : %d MHz\n", bw_array[chandef.width]); + else + seq_printf(seq, "bandwidth : \n"); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ht_info); + + +struct dentry *wlan0_dir = NULL; +struct dentry *wlan0_survey_info, *wlan0_sta_info, *wlan0_ht_info;; + +int ecrnx_debugfs_wlan0_init(void *private_data) +{ + if (!ecr6600u_dir) + return -ENOENT; + + wlan0_dir = debugfs_create_dir("wlan0", ecr6600u_dir); + if (!wlan0_dir) + return -ENOENT; + + wlan0_survey_info = debugfs_create_file("survey_info", 0444, wlan0_dir, private_data, &survey_info_fops); + if (!wlan0_survey_info) + goto Fail; + + wlan0_sta_info = debugfs_create_file("sta_info", 0444, wlan0_dir, private_data, &sta_info_fops); + if (!wlan0_sta_info) + goto Fail; + + wlan0_ht_info = debugfs_create_file("ht_info", 0444, wlan0_dir, private_data, &ht_info_fops); + if (!wlan0_ht_info) + goto Fail; + + return 0; + +Fail: + debugfs_remove_recursive(wlan0_dir); + wlan0_dir = NULL; + return -ENOENT; +} + +static int ap_info_show(struct seq_file *seq, void *v) +{ + struct cfg80211_chan_def chandef; + u32 bw_array[]={20, 20, 40, 80, 80, 160, 5, 10}; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 sta_mac[NX_REMOTE_STA_MAX][ETH_ALEN+1]; + u32 i; + + seq_printf(seq, "AP Vendor ESWIN\n"); + + seq_printf(seq, "ap info\n"); + if (0 == ecrnx_ssid_get(seq, IF_AP, ssid)) + seq_printf(seq, "ssid : %s\n", ssid); + else + seq_printf(seq, "ssid : \n"); + + if (0 == ecrnx_channel_get(seq, IF_AP, &chandef)) + { + seq_printf(seq, "cur_channel=%d, cur_bwmode=%d(%dMHz), cur_ch_offset=0\n", + (chandef.center_freq1 - 2412)/5 + 1, chandef.width, bw_array[chandef.width]); + } + else + { + seq_printf(seq, "cur_channel=, cur_bwmode=(MHz), cur_ch_offset=\n"); + } +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)) + if ((chandef.width <= NL80211_CHAN_WIDTH_10) && (chandef.width >= NL80211_CHAN_WIDTH_20_NOHT)) + { + if (NL80211_CHAN_WIDTH_20_NOHT == chandef.width) + { + seq_printf(seq, "ht_en=0\n"); + } + else + { + seq_printf(seq, "ht_en=1\n"); + } + } + else + { + seq_printf(seq, "ht_en=0\n"); + } +#endif + + seq_printf(seq, "wireless_mode=0xb(B/G/N), rtsen=0, cts2slef=0\n"); + seq_printf(seq, "state=0x10, aid=0, macid=32, raid=0\n"); + seq_printf(seq, "qos_en=1, init_rate=0\n"); + seq_printf(seq, "bwmode=0, ch_offset=0, sgi_20m=1,sgi_40m=1\n"); + seq_printf(seq, "ampdu_enable = 1\n"); + seq_printf(seq, "agg_enable_bitmap=0, candidate_tid_bitmap=0\n"); + seq_printf(seq, "ldpc_cap=0x0, stbc_cap=0x0, beamform_cap=0x0\n"); + + + seq_printf(seq, "\n"); + seq_printf(seq, "station info\n"); + memset(sta_mac, 0x00, sizeof(sta_mac)); + if (0 == ecrnx_sta_mac_get(seq, IF_AP, sta_mac)) + { + for (i=0; i<NX_REMOTE_STA_MAX; i++) + { + if (sta_mac[i][0]) + seq_printf(seq, "sta's macaddr:%02X:%02X:%02X:%02X:%02X:%02X\n", sta_mac[i][1], sta_mac[i][2], + sta_mac[i][3], sta_mac[i][4], sta_mac[i][5], sta_mac[i][6]); + } + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ap_info); + +static int sta_info_in_ap_show(struct seq_file *seq, void *v) +{ + struct ecrnx_debugfs_sta *sta; + + if (seq->private == NULL) + ECRNX_ERR("error sta_info_in_ap_show\n"); + + sta = seq->private; + + seq_printf(seq, "cur_channel=%d, cur_bwmode=%d\n", sta->ch_idx, sta->width); + //seq_printf(m, "wireless_mode=0x%x(%s), rtsen=%d, cts2slef=%d\n", psta->wireless_mode, wl_mode, psta->rtsen, psta->cts2self); + seq_printf(seq, "aid=%d\n", sta->aid); + + seq_printf(seq, "qos_en=%d, ht_en=%d\n", sta->qos, sta->ht); + seq_printf(seq, "sgi_20m=%d,sgi_40m=%d\n", sta->sgi_20m, sta->sgi_40m); + seq_printf(seq, "ampdu_enable = %d\n", sta->ampdu_enable); + seq_printf(seq, "agg_enable_bitmap=%x, candidate_tid_bitmap=%x\n", sta->agg_enable_bitmap, sta->candidate_tid_bitmap); + seq_printf(seq, "ldpc_cap=0x%x, stbc_cap=0x%x, beamform_cap=0x%x\n", sta->ldpc_cap, sta->stbc_cap, sta->beamform_cap); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(sta_info_in_ap); + +struct dentry *ap0_dir = NULL; +struct dentry *ap0_ap_info; +struct dentry *stas[NX_REMOTE_STA_MAX]; + +int ecrnx_debugfs_ap0_init(void *private_data) +{ + if (!ecr6600u_dir) + return -ENOENT; + + ap0_dir = debugfs_create_dir("ap0", ecr6600u_dir); + if (!ap0_dir) + return -ENOENT; + + ap0_ap_info = debugfs_create_file("ap_info", 0444, ap0_dir, private_data, &ap_info_fops); + if (!ap0_ap_info) + goto Fail; + + return 0; + +Fail: + debugfs_remove_recursive(ap0_dir); + ap0_dir = NULL; + return -ENOENT; +} + +void ecrnx_debugfs_sta_in_ap_init(void *private_data) +{ + int index; + char file_name[19]; + struct ecrnx_debugfs_sta *sta; + + sta = private_data; + index = sta->sta_idx; + + sprintf(file_name, "%02x-%02x-%02x-%02x-%02x-%02x", MAC_ARG(sta->mac_addr)); + stas[index] = debugfs_create_file(file_name, 0444, ap0_dir, private_data, &sta_info_in_ap_fops); + ECRNX_PRINT("%s:file_name: %s, sta idx:%d,stas:0x%p \n", __func__, file_name, sta->sta_idx, stas[index]); +} + +void ecrnx_debugfs_sta_in_ap_del(u8 sta_idx) +{ + struct dentry *dentry_sta; + struct ecrnx_debugfs_sta *debugfs_sta; + + ECRNX_PRINT("%s: sta_idx:%d, stas:0x%p \n", __func__, sta_idx, stas[sta_idx]); + dentry_sta = stas[sta_idx]; + if(!dentry_sta) + { + ECRNX_ERR("error sta_idx!!!\n"); + return; + } + + if (dentry_sta->d_inode) + { + debugfs_sta = dentry_sta->d_inode->i_private; + ECRNX_DBG("%s: dentry_sta->d_inode:0x%p, debugfs_sta:0x%p \n", __func__, dentry_sta->d_inode, debugfs_sta); + if (debugfs_sta) + { + debugfs_remove(dentry_sta); + kfree(debugfs_sta); + } + } + + stas[sta_idx] = NULL; +} + +static int p2p0_survey_info_show(struct seq_file *seq, void *v) +{ + survey_info_show(seq, v); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(p2p0_survey_info); + +static int p2p_info_show(struct seq_file *seq, void *v) +{ + s8 noise_dbm; + struct cfg80211_chan_def chandef; + u32 bw_array[]={20, 20, 40, 80, 80, 160, 5, 10}; + + if (0 == ecrnx_signal_level_get(seq, IF_P2P, &noise_dbm)) + seq_printf(seq, "signal level : %d dBm\n", noise_dbm); + else + seq_printf(seq, "signal level : \n"); + + if (0 == ecrnx_channel_get(seq, IF_P2P, &chandef)) + { + seq_printf(seq, "frequency : %d MHz\n", chandef.center_freq1); + seq_printf(seq, "bandwidth : %d MHz\n", bw_array[chandef.width]); + } + else + { + seq_printf(seq, "frequency : \n"); + seq_printf(seq, "bandwidth : \n"); + } + + if (NL80211_IFTYPE_P2P_CLIENT == ecrnx_p2p_role_get(seq, IF_P2P)) + { + seq_printf(seq, "type : P2P-client\n"); + } + else if(NL80211_IFTYPE_P2P_GO == ecrnx_p2p_role_get(seq, IF_P2P)) + { + seq_printf(seq, "type : P2P-GO\n"); + } + else + { + seq_printf(seq, "type : \n"); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(p2p_info); + +static int p2p0_ht_info_show(struct seq_file *seq, void *v) +{ + struct cfg80211_chan_def chandef; + u32 bw_array[]={20, 20, 40, 80, 80, 160, 5, 10}; + + if (0 == ecrnx_channel_get(seq, IF_P2P, &chandef)) + seq_printf(seq, "bandwidth : %d MHz\n", bw_array[chandef.width]); + else + seq_printf(seq, "bandwidth : \n"); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(p2p0_ht_info); + +struct dentry *p2p0_dir = NULL; +struct dentry *p2p0_survey_info, *p2p0_p2p_info, *p2p0_ht_info; + +int ecrnx_debugfs_p2p0_init(void *private_data) +{ + if (!ecr6600u_dir) + return -ENOENT; + + p2p0_dir = debugfs_create_dir("p2p0", ecr6600u_dir); + if (!p2p0_dir) + return -ENOENT; + + p2p0_survey_info = debugfs_create_file("survey_info", 0444, p2p0_dir, private_data, &p2p0_survey_info_fops); + if (!p2p0_survey_info) + goto Fail; + + p2p0_p2p_info = debugfs_create_file("p2p_info", 0444, p2p0_dir, private_data, &p2p_info_fops); + if (!p2p0_p2p_info) + goto Fail; + + p2p0_ht_info = debugfs_create_file("ht_info", 0444, p2p0_dir, private_data, &p2p0_ht_info_fops); + if (!p2p0_ht_info) + goto Fail; + + return 0; + +Fail: + debugfs_remove_recursive(p2p0_dir); + p2p0_dir = NULL; + return -ENOENT; +} + +void exrnx_debugfs_sruvey_info_tbl_init(void *private_data) +{ + struct ecrnx_hw *ecrnx_hw = (struct ecrnx_hw*)private_data; + INIT_LIST_HEAD(&ecrnx_hw->debugfs_survey_info_tbl_ptr); +} + +int ecrnx_debugfs_init(void *private_data) +{ + ecrnx_debugfs_info_init(private_data); + ecrnx_debugfs_hw_init(private_data); + ecrnx_debugfs_wlan0_init(private_data); + ecrnx_debugfs_ap0_init(private_data); + ecrnx_debugfs_p2p0_init(private_data); + exrnx_debugfs_sruvey_info_tbl_init(private_data); + + memset(&debugfs_info, 0, sizeof(debugfs_info_t)); + init_waitqueue_head(&debugfs_resp.rxdataq); + + return 0; +} +#ifdef CONFIG_ECRNX_ESWIN_USB +extern struct ring_buffer buf_handle; +#endif + +void ecrnx_debugfs_exit(void) +{ + if (ecr6600u_dir != NULL) + debugfs_remove_recursive(ecr6600u_dir); +#ifdef CONFIG_ECRNX_ESWIN_USB + ring_buffer_deinit(&buf_handle); +#endif + +} +#endif + diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_debugfs_custom.h b/drivers/net/wireless/eswin/fullmac/ecrnx_debugfs_custom.h new file mode 100644 index 000000000000..244464a094bb --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_debugfs_custom.h @@ -0,0 +1,20 @@ +#ifndef __ECRNX_DEBUGFS_CUSTOM_H_ +#define __ECRNX_DEBUGFS_CUSTOM_H_ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/seq_file.h> + +#include "lmac_types.h" +#include "ecrnx_debugfs_func.h" +#include "ecrnx_compat.h" + + +int ecrnx_debugfs_init(void *private_data); +void ecrnx_debugfs_exit(void); +void ecrnx_debugfs_sta_in_ap_init(void *private_data); +void ecrnx_debugfs_sta_in_ap_del(u8 sta_idx); + +#endif + + diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_debugfs_func.c b/drivers/net/wireless/eswin/fullmac/ecrnx_debugfs_func.c new file mode 100644 index 000000000000..f9a27a39f555 --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_debugfs_func.c @@ -0,0 +1,995 @@ +#include "ecrnx_debugfs_func.h" +#include "ecrnx_debugfs_custom.h" + +#ifdef CONFIG_ECRNX_DEBUGFS + +debugfs_info_t debugfs_info; +debugfs_resp_t debugfs_resp; + +u32 reg_buf[512]={0}; + +REG_MAP_ST mac_reg_table[]={ + {"INTC_IRQ_STATUS_ADDR", 0x00510000}, + {"INTC_IRQ_UNMASK_SET_ADDR", 0x00510010}, + {"INTC_IRQ_INDEX_ADDR", 0x00510040}, + {"NXMAC_MAC_ADDR_LOW_ADDR", 0x00610010}, + {"NXMAC_MAC_ADDR_HI_ADDR", 0x00610014}, + {"NXMAC_STATE_CNTRL_ADDR", 0x00610038}, + {"NXMAC_MAC_CNTRL_1_ADDR", 0x0061004c}, + {"NXMAC_RX_CNTRL_ADDR", 0x00610060}, + {"NXMAC_MAX_POWER_LEVEL_ADDR", 0x006100a0}, + {"NXMAC_TIMINGS_1_ADDR", 0x006100e4}, + {"NXMAC_RX_CNTRL_2_ADDR", 0x0061010c}, + {"NXMAC_MONOTONIC_COUNTER_2_LO_ADDR", 0x00610120}, + {"NXMAC_MONOTONIC_COUNTER_2_HI_ADDR", 0x00610124}, + {"NXMAC_MAX_RX_LENGTH_ADDR", 0x00610150}, + {"NXMAC_GEN_INT_STATUS_ADDR", 0x0061806c}, + {"NXMAC_GEN_INT_ENABLE_ADDR", 0x00618074}, + {"NXMAC_TX_RX_INT_STATUS_ADDR", 0x00618078}, + {"NXMAC_TX_RX_INT_ENABLE_ADDR", 0x00618080}, + {"NXMAC_DMA_CNTRL_SET_ADDR", 0x00618180}, + {"NXMAC_DMA_STATUS_1_ADDR", 0x00618188}, + {"NXMAC_DMA_STATUS_2_ADDR", 0x0061818c}, + {"NXMAC_DMA_STATUS_3_ADDR", 0x00618190}, + {"NXMAC_DMA_STATUS_4_ADDR", 0x00618194}, + {"NXMAC_TX_BCN_HEAD_PTR_ADDR", 0x00618198}, + {"NXMAC_TX_AC_0_HEAD_PTR_ADDR", 0x0061819c}, + {"NXMAC_TX_AC_1_HEAD_PTR_ADDR", 0x006181a0}, + {"NXMAC_TX_AC_2_HEAD_PTR_ADDR", 0x006181a4}, + {"NXMAC_TX_AC_3_HEAD_PTR_ADDR", 0x006181a8}, + {"NXMAC_RX_BUF_1_START_PTR_ADDR", 0x006181c8}, + {"NXMAC_RX_BUF_1_END_PTR_ADDR", 0x006181cc}, + {"NXMAC_RX_BUF_1_RD_PTR_ADDR", 0x006181d0}, + {"NXMAC_RX_BUF_1_WR_PTR_ADDR", 0x006181d4}, + {"NXMAC_RX_BUF_CONFIG_ADDR", 0x006181e8}, + {"MDM_HDMCONFIG_ADDR", 0x00700000}, +}; + +REG_MAP_ST rf_reg_table[]={ + {"RF_REG_EXAMPLE", 0x00510000}, +}; + +REG_MAP_ST bb_reg_table[]={ + {"BB_REG_EXAMPLE", 0x00510000}, +}; + +REG_MAP_ST efuse_map_table[]={ + {"EFUSE_REG_EXAMPLE", 0x00510000}, +}; + +int argc; +char *argv[MAX_ARGV]; +char slave_cli_cmd[50]; +int arg_val[MAX_ARGV]; + + +ECRNX_CLI_TABLE_ST ecrnx_cli_tab[]={ + {"macbyp start ", "macbyp_tx_start", 6, {{0,32},{0,32},{0,32},{0,32},{0,32},{0,32}}, cli_macbyp_start}, + {"macbyp stop", "macbyp_tx_stop", 0, {}, cli_macbyp_stop}, + {"rf txgain ", "rf_set_txgain", 1, {{0,32}}, cli_rf_txgain}, + {"rf rxgain ", "rf_set_rxgain", 4, {{0,32},{0,32},{0,32},{0,32}}, cli_rf_rxgain}, + {"rf chan ", "rf_set_chan", 2, {{0,32},{0,32}}, cli_rf_chan}, +}; + + +void ecrnx_debugfs_param_send(debugfs_info_t *req) +{ + if(debugfs_info.debugfs_type == SLAVE_LOG_LEVEL){ + if (req->u.slave_log_level_t.debug_level < DBG_TYPE_D || req->u.slave_log_level_t.debug_level > DBG_TYPE_O) { + ECRNX_ERR("ecrnx debug param error!!!\n"); + } + } + + ECRNX_DBG("%s: fstype:%d, level:%d, dir:%d \n", __func__, req->debugfs_type, req->u.slave_log_level_t.debug_level, req->u.slave_log_level_t.debug_dir); + //print_hex_dump(KERN_INFO, "ecrnx_debugfs_send ", DUMP_PREFIX_ADDRESS, 32, 1, + // (u8*)req, sizeof(debugfs_info_t), false); + host_send(req, sizeof(debugfs_info_t), TX_FLAG_MSG_DEBUGFS_IE); +} + +int ecrnx_log_level_get(LOG_CTL_ST *log) +{ + if (log == NULL) + return -1; + + *log = log_ctl; + + return 0; +} + +int ecrnx_fw_log_level_set(u32 level, u32 dir) +{ + log_ctl.level = level; + log_ctl.dir = dir; + + debugfs_info.debugfs_type = SLAVE_LOG_LEVEL; + debugfs_info.u.slave_log_level_t.debug_level = level; + debugfs_info.u.slave_log_level_t.debug_dir = dir; + + ecrnx_debugfs_param_send(&debugfs_info); + + return 0; +} + +bool ecrnx_log_host_enable(void) +{ + if (log_ctl.dir) + return true; + + return false; +} + +int ecrnx_host_ver_get(u8 *ver) +{ + if (ver == NULL) + return -1; + + sprintf(ver, "v%s", HOST_DRIVER_VERSION); + + return 0; +} + +int ecrnx_fw_ver_get(u8 *ver) +{ + if (ver == NULL) + return -1; + + debugfs_info.debugfs_type = SLAVE_FW_INFO; + ecrnx_debugfs_param_send(&debugfs_info); + + //wait for confirm + debugfs_resp.rxdatas = 0; + wait_event_interruptible_timeout(debugfs_resp.rxdataq, debugfs_resp.rxdatas, 1*HZ); + + if (debugfs_resp.rxdatas) + memcpy(ver, debugfs_resp.rxdata, debugfs_resp.rxlen); + else + return -1; + + return 0; +} + +int ecrnx_build_time_get(u8 *build_time) +{ + if (build_time == NULL) + return -1; + + sprintf(build_time, "%s %s", __DATE__, __TIME__); + + return 0; +} + +static int ecrnx_wdevs_get(struct seq_file *seq, struct wireless_dev **wdev) +{ + struct ecrnx_hw *ecrnx_hw; + u32 i; + + if (seq->private == NULL) + return -1; + + ecrnx_hw = seq->private; + + for (i=0; i<NX_VIRT_STA_MAX; i++) + { + if (ecrnx_hw->vif_table[i] != NULL) + wdev[i] = &ecrnx_hw->vif_table[i]->wdev; + else + wdev[i] = NULL; + } + + return 0; +} + +static int ecrnx_wdev_match(struct wireless_dev **wdev, IF_TYPE_EN iftype, u32 *index) +{ + struct ecrnx_vif *ecrnx_vif; + u32 i; + + for (i=0; i<NX_VIRT_STA_MAX; i++) + { + // 1. valid + if (wdev[i] == NULL) + continue; + + // 2. up + ecrnx_vif = netdev_priv(wdev[i]->netdev); + if (ecrnx_vif->up == false) + continue; + + // 3. type + switch(wdev[i]->iftype) + { + case NL80211_IFTYPE_STATION: + { + if (iftype == IF_STA) + { + *index = i; + return 0; + } + break; + } + + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_AP_VLAN: + { + if (iftype == IF_AP) + { + *index = i; + return 0; + } + break; + } + + case NL80211_IFTYPE_P2P_CLIENT: + case NL80211_IFTYPE_P2P_GO: + case NL80211_IFTYPE_P2P_DEVICE: + { + if (iftype == IF_P2P) + { + *index = i; + return 0; + } + break; + } + + default : + break; + } + } + + return -1; +} + +int ecrnx_rf_info_get(struct seq_file *seq, IF_TYPE_EN iftype,RF_INFO_ST *cur, RF_INFO_ST *oper) +{ + struct wireless_dev *wdev[NX_VIRT_STA_MAX]; + struct cfg80211_chan_def chandef; + u32 index; + + if (0 != ecrnx_wdevs_get(seq, wdev)) + return -1; + + if ((cur == NULL) || (oper == NULL)) + return -1; + + if (0 != ecrnx_wdev_match(wdev, iftype, &index)) + return -1; + + if (0 != ecrnx_cfg80211_get_channel(wdev[index]->wiphy, wdev[index], &chandef)) + return -1; + + cur->ch = (chandef.center_freq1 - 2412)/5 + 1; + cur->bw = chandef.width; + cur->ch_offset = 0; + + oper->ch = (chandef.center_freq1 - 2412)/5 + 1; + oper->bw = chandef.width; + oper->ch_offset = 0; + + return 0; +} + +int ecrnx_country_code_get(struct seq_file *seq, char *alpha2) +{ + struct ecrnx_hw *ecrnx_hw; + + if (seq->private != NULL) + ecrnx_hw = seq->private; + else + return -1; + + if (alpha2 == NULL) + return -1; + + if (ecrnx_hw->wiphy == NULL) + return -1; + + if (ecrnx_hw->wiphy->regd == NULL) + return -1; + + memcpy(alpha2, ecrnx_hw->wiphy->regd->alpha2, sizeof(ecrnx_hw->wiphy->regd->alpha2)); + alpha2[2] = '\0'; + + return 0; +} + +int ecrnx_mac_addr_get(struct seq_file *seq, IF_TYPE_EN iftype, u8 *mac_addr) +{ + struct wireless_dev *wdev[NX_VIRT_STA_MAX]; + u32 index; + + if (0 != ecrnx_wdevs_get(seq, wdev)) + return -1; + + if (mac_addr == NULL) + return -1; + + if (0 != ecrnx_wdev_match(wdev, iftype, &index)) + return -1; + + memcpy(mac_addr, wdev[index]->netdev->perm_addr, ETH_ALEN); + + return 0; +} + +int ecrnx_mac_addr_get_ex(struct seq_file *seq, u8 *mac_addr_info) +{ + struct wireless_dev *wdev[NX_VIRT_STA_MAX]; + u32 i; + + if (0 != ecrnx_wdevs_get(seq, wdev)) + return -1; + + if (mac_addr_info == NULL) + return -1; + + mac_addr_info[0] = '\0'; + for (i=0; i<NX_VIRT_STA_MAX; i++) + { + if (wdev[i] != NULL) + { + sprintf(mac_addr_info+strlen(mac_addr_info), "%s hw_port(%d) mac_addr=%02X:%02X:%02X:%02X:%02X:%02X\n", wdev[i]->netdev->name, wdev[i]->netdev->ifindex, + wdev[i]->netdev->perm_addr[0], wdev[i]->netdev->perm_addr[1], wdev[i]->netdev->perm_addr[2], + wdev[i]->netdev->perm_addr[3], wdev[i]->netdev->perm_addr[4], wdev[i]->netdev->perm_addr[5]); + } + } + + return 0; +} + +int ecrnx_channel_get(struct seq_file *seq, IF_TYPE_EN iftype, struct cfg80211_chan_def *chandef) +{ + struct wireless_dev *wdev[NX_VIRT_STA_MAX]; + u32 index; + + if (0 != ecrnx_wdevs_get(seq, wdev)) + return -1; + + if (chandef == NULL) + return -1; + + if (0 != ecrnx_wdev_match(wdev, iftype, &index)) + return -1; + + return ecrnx_cfg80211_get_channel(wdev[index]->wiphy, wdev[index], chandef); +} + +int ecrnx_p2p_role_get(struct seq_file *seq, IF_TYPE_EN iftype) +{ + struct wireless_dev *wdev[NX_VIRT_STA_MAX]; + u32 index; + + if (0 != ecrnx_wdevs_get(seq, wdev)) + return -1; + + if (0 != ecrnx_wdev_match(wdev, iftype, &index)) + return -1; + + return wdev[index]->iftype; +} + +int ecrnx_bssid_get(struct seq_file *seq, IF_TYPE_EN iftype, u8 *bssid) +{ + struct station_info sinfo; + struct wireless_dev *wdev[NX_VIRT_STA_MAX]; + u32 index; + + if (0 != ecrnx_wdevs_get(seq, wdev)) + return -1; + + if (bssid == NULL) + return -1; + + if (0 != ecrnx_wdev_match(wdev, iftype, &index)) + return -1; + + return ecrnx_cfg80211_dump_station(wdev[index]->wiphy, wdev[index]->netdev, 0, bssid, &sinfo); +} + +int ecrnx_signal_level_get(struct seq_file *seq, IF_TYPE_EN iftype, s8 *noise_dbm) +{ + struct wireless_dev *wdev[NX_VIRT_STA_MAX]; + struct ecrnx_hw *ecrnx_hw; + u32 index; + + if (0 != ecrnx_wdevs_get(seq, wdev)) + return -1; + + if (noise_dbm == NULL) + return -1; + + if (0 != ecrnx_wdev_match(wdev, iftype, &index)) + return -1; + + ecrnx_hw = wdev_priv(wdev[index]); + *noise_dbm = ecrnx_hw->survey[0].noise_dbm; + + return 0; +} + +int ecrnx_flags_get(struct seq_file *seq, IF_TYPE_EN iftype, u32 *flags) +{ + struct wireless_dev *wdev[NX_VIRT_STA_MAX]; + u32 index; + + if (0 != ecrnx_wdevs_get(seq, wdev)) + return -1; + + if (flags == NULL) + return -1; + + if (0 != ecrnx_wdev_match(wdev, iftype, &index)) + return -1; + + *flags = wdev[index]->wiphy->flags; + + return 0; +} + +int ecrnx_ssid_get(struct seq_file *seq, IF_TYPE_EN iftype, char *ssid) +{ + struct wireless_dev *wdev[NX_VIRT_STA_MAX]; + u32 index; + + if (0 != ecrnx_wdevs_get(seq, wdev)) + return -1; + + if (ssid == NULL) + return -1; + + if (0 != ecrnx_wdev_match(wdev, iftype, &index)) + return -1; + + memcpy(ssid, wdev[index]->ssid, IEEE80211_MAX_SSID_LEN); + + return 0; +} + +int ecrnx_sta_mac_get(struct seq_file *seq, IF_TYPE_EN iftype, u8 sta_mac[][ETH_ALEN+1]) +{ + struct ecrnx_hw *ecrnx_hw; + u32 i; + u8 mac[ETH_ALEN] = {0}; + + if (seq->private != NULL) + ecrnx_hw = seq->private; + else + return -1; + + // station table + for (i=0; i<NX_REMOTE_STA_MAX; i++) + { + if (ecrnx_hw->sta_table[i].valid == true) + { + if (0 == memcmp(mac, ecrnx_hw->sta_table[i].mac_addr, ETH_ALEN)) + { + sta_mac[i][0] = 0xFF; + continue; + } + + sta_mac[i][0] = 0xFF; + memcpy(&sta_mac[i][1], ecrnx_hw->sta_table[i].mac_addr, ETH_ALEN); + + } + } + + return 0; + +} + +int ecrnx_mac_reg_dump(struct seq_file *seq) +{ + u32 i, data; + + seq_printf(seq, "%-34s %-11s %-10s\n", "name", "addr", "value"); + + for (i=0; i<(sizeof(mac_reg_table)/sizeof(mac_reg_table[0])); i++) + { + ecrnx_slave_reg_read(mac_reg_table[i].addr, &data, 1); + seq_printf(seq, "%-34s 0x%08X: 0x%08X\n", mac_reg_table[i].name, mac_reg_table[i].addr, data); + } + + return 0; +} + +int ecrnx_rf_reg_dump(struct seq_file *seq) +{ + u32 i, data; + + seq_printf(seq, "%-34s %-11s %-10s\n", "name", "addr", "value"); + + for (i=0; i<(sizeof(rf_reg_table)/sizeof(rf_reg_table[0])); i++) + { + ecrnx_slave_reg_read(rf_reg_table[i].addr, &data, 1); + seq_printf(seq, "%-34s 0x%08X: 0x%08X\n", rf_reg_table[i].name, rf_reg_table[i].addr, data); + } + + return 0; +} + +int ecrnx_bb_reg_dump(struct seq_file *seq) +{ + u32 i, data; + + seq_printf(seq, "%-34s %-11s %-10s\n", "name", "addr", "value"); + + for (i=0; i<(sizeof(bb_reg_table)/sizeof(bb_reg_table[0])); i++) + { + ecrnx_slave_reg_read(bb_reg_table[i].addr, &data, 1); + seq_printf(seq, "%-34s 0x%08X: 0x%08X\n", bb_reg_table[i].name, bb_reg_table[i].addr, data); + } + + return 0; +} + +int ecrnx_efuse_map_dump(struct seq_file *seq) +{ + u32 i, data; + + seq_printf(seq, "%-34s %-11s %-10s\n", "name", "addr", "value"); + + for (i=0; i<(sizeof(efuse_map_table)/sizeof(efuse_map_table[0])); i++) + { + ecrnx_slave_reg_read(efuse_map_table[i].addr, &data, 1); + seq_printf(seq, "%-34s 0x%08X: 0x%08X\n", efuse_map_table[i].name, efuse_map_table[i].addr, data); + } + + return 0; +} + +int ecrnx_slave_reg_read(u32 addr, u32 *data, u32 len) +{ + if (data == NULL) + return -1; + + debugfs_info.debugfs_type = SLAVE_READ_REG; + debugfs_info.u.slave_read_reg_t.reg = addr; + debugfs_info.u.slave_read_reg_t.len = len; + ecrnx_debugfs_param_send(&debugfs_info); + + //wait for confirm + debugfs_resp.rxdatas = 0; + wait_event_interruptible_timeout(debugfs_resp.rxdataq, debugfs_resp.rxdatas, 1*HZ); + + if (debugfs_resp.rxdatas) + memcpy((u8 *)data, (u8 *)debugfs_resp.rxdata, debugfs_resp.rxlen); + + return 0; +} + +int ecrnx_slave_reg_write(u32 addr, u32 data, u32 len) +{ + debugfs_info.debugfs_type = SLAVE_WRITE_REG; + debugfs_info.u.slave_write_reg_t.reg = addr; + debugfs_info.u.slave_write_reg_t.value = data; + ecrnx_debugfs_param_send(&debugfs_info); + + //wait for confirm + debugfs_resp.rxdatas = 0; + wait_event_interruptible_timeout(debugfs_resp.rxdataq, debugfs_resp.rxdatas, 1*HZ); + + return 0; +} + +int ecrnx_slave_cli_send(u8 *cli, u8 *resp) +{ + if (cli == NULL) + return -1; + + debugfs_info.debugfs_type = SLAVE_FUNC_INFO; + strcpy(debugfs_info.u.slave_cli_func_info_t.func_and_param, cli); + ecrnx_debugfs_param_send(&debugfs_info); + + //wait for confirm + debugfs_resp.rxdatas = 0; + wait_event_interruptible_timeout(debugfs_resp.rxdataq, debugfs_resp.rxdatas, 1*HZ); + + if ((debugfs_resp.rxdatas) && (debugfs_resp.rxlen)) + { + ECRNX_PRINT("%s\n", debugfs_resp.rxdata); + } + + return 0; +} + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0)) +/** + * ether_addr_copy - Copy an Ethernet address + * @dst: Pointer to a six-byte array Ethernet address destination + * @src: Pointer to a six-byte array Ethernet address source + * + * Please note: dst & src must both be aligned to u16. + */ +static inline void ether_addr_copy(u8 *dst, const u8 *src) +{ +#if defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) + *(u32 *)dst = *(const u32 *)src; + *(u16 *)(dst + 4) = *(const u16 *)(src + 4); +#else + u16 *a = (u16 *)dst; + const u16 *b = (const u16 *)src; + + a[0] = b[0]; + a[1] = b[1]; + a[2] = b[2]; +#endif +} + +#endif +void ecrnx_debugfs_survey_info_update(struct ecrnx_hw *ecrnx_hw, struct cfg80211_bss *bss) +{ + __le16 ie_cap = 0; + const u8 *ssid_elm; + const u8 *ie_wpa = NULL, *ie_wpa2 = NULL, *ie_wps = NULL; + const u8 *ie_p2p = NULL; + + struct ecrnx_debugfs_survey_info_tbl *new_node, *entry, *tmp; + new_node = kzalloc(sizeof(struct ecrnx_debugfs_survey_info_tbl), + GFP_ATOMIC); + + if (bss) { + const struct cfg80211_bss_ies *ies; + + new_node->ch = ieee80211_frequency_to_channel(bss->channel->center_freq); + ether_addr_copy(new_node->bssid, bss->bssid); + new_node->rssi = bss->signal/100; + + rcu_read_lock(); + + ie_wpa2 = ieee80211_bss_get_ie(bss, WLAN_EID_RSN); + + ies = rcu_dereference(bss->ies); + + ie_wpa = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPA, + ies->data, + ies->len); + + ie_wps = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPS, + ies->data, + ies->len); + + ie_p2p = cfg80211_find_vendor_ie(WLAN_OUI_WFA, + WLAN_OUI_TYPE_WFA_P2P, + ies->data, + ies->len); + + ie_cap = cpu_to_le16(bss->capability); + + ssid_elm = cfg80211_find_ie(WLAN_EID_SSID, ies->data, ies->len); + if (ssid_elm) { + if (ssid_elm[1] <= IEEE80211_MAX_SSID_LEN) + memcpy(new_node->ssid, ssid_elm + 2, ssid_elm[1]); + } + + rcu_read_unlock(); + } + + sprintf(new_node->flag, "%s%s%s%s%s%s", + (ie_wpa) ? "[WPA]" : "", + (ie_wpa2) ? "[WPA2]" : "", + (!ie_wpa && !ie_wpa && ie_cap & BIT(4)) ? "[WEP]" : "", + (ie_wps) ? "[WPS]" : "", + (ie_cap & BIT(0)) ? "[ESS]" : "", + (ie_p2p) ? "[P2P]" : ""); + + INIT_LIST_HEAD(&new_node->list); + + //if (!list_empty(&ecrnx_hw->debugfs_survey_info_tbl_ptr)) { + list_for_each_entry_safe(entry, tmp, &ecrnx_hw->debugfs_survey_info_tbl_ptr, list) { + if(memcmp(entry->bssid, new_node->bssid, ETH_ALEN) == 0) { + list_del(&entry->list); + kfree(entry); + } + } + //} + list_add_tail(&new_node->list, &ecrnx_hw->debugfs_survey_info_tbl_ptr); +} + +void ecrnx_debugfs_noise_of_survey_info_update(struct ecrnx_hw *ecrnx_hw, struct ecrnx_survey_info *ecrnx_survey, int chan_no) +{ + struct ecrnx_debugfs_survey_info_tbl *entry; + + list_for_each_entry(entry, &ecrnx_hw->debugfs_survey_info_tbl_ptr, list) { + if(entry->ch == chan_no) { + entry->noise = ecrnx_survey->noise_dbm; + } + } +} + +void ecrnx_debugfs_add_station_in_ap_mode(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_sta *sta, struct station_parameters *params) +{ + struct ecrnx_debugfs_sta *debugfs_sta; + //char file_name[18]; + + if(!sta || !params) + { + ECRNX_ERR("%s-%d:sta(%p) or params(%p) is null, return!\n", __func__, __LINE__, sta, params); + return; + } + + debugfs_sta = kzalloc(sizeof(struct ecrnx_debugfs_sta), GFP_KERNEL); + + if (!debugfs_sta) + ECRNX_ERR("error debugfs_sta kzalloc!\n"); + + + debugfs_sta->sta_idx = sta->sta_idx; + debugfs_sta->aid = sta->aid; + debugfs_sta->ch_idx = sta->ch_idx; + debugfs_sta->ht = sta->ht; + ether_addr_copy(debugfs_sta->mac_addr, sta->mac_addr); + debugfs_sta->qos = sta->qos; + debugfs_sta->vht = sta->vht; + debugfs_sta->width = sta->width; + + if(sta->ht) + { + if (params->ht_capa->cap_info & IEEE80211_HT_CAP_TX_STBC) + debugfs_sta->stbc_cap = 1; + if (params->ht_capa->cap_info & IEEE80211_HT_CAP_LDPC_CODING) + debugfs_sta->ldpc_cap = 1; + if (params->ht_capa->cap_info & IEEE80211_HT_CAP_SGI_20) + debugfs_sta->sgi_20m = 1; + if (params->ht_capa->cap_info & IEEE80211_HT_CAP_SGI_40) + debugfs_sta->sgi_40m = 1; + } + + ecrnx_debugfs_sta_in_ap_init(debugfs_sta); +} + +int cli_parse_args(uint8_t* str, char* argv[]) +{ + int i = 0; + char* ch = (char *)str; + + while(*ch == ' ') ch++; + if (*ch == '\n') + return 0; + + while(*ch != '\0') + { + i++; + + if (i > MAX_ARGV) + return 0; + + argv[i-1] = ch; + while(*ch != '\0') + { + if(*ch == ' ') + break; + else + ch++; + } + + *ch = '\0'; + ch++; + while(*ch == ' ') { + ch++; + } + } + + return i; +} + +void argv_display(uint32_t argc, char* argv[]) +{ + uint32_t i; + + ECRNX_PRINT("argc is %d\n", argc); + for (i=0; i<argc; i++) + { + ECRNX_PRINT("param %d is %s\n", i, argv[i]); + } +} + +int _atoi(char *pstr) +{ + int ret_integer = 0; + int integer_sign = 1; + + if (pstr == NULL) + { + //printk("Pointer is NULL\n"); + return 0; + } + + while (*pstr == ' ') + { + pstr++; + } + + if (*pstr == '-') + { + integer_sign = -1; + } + if (*pstr == '-' || *pstr == '+') + { + pstr++; + } + + while (*pstr >= '0' && *pstr <= '9') + { + ret_integer = ret_integer * 10 + *pstr - '0'; + pstr++; + } + ret_integer = integer_sign * ret_integer; + + return ret_integer; +} + +int cli_check_parm_num(uint32_t index, uint8_t *param) +{ + argc = cli_parse_args(param, argv); + //argv_display(argc, argv); + + if (argc != ecrnx_cli_tab[index].param_num) + return -1; + + return 0; +} + +int cli_check_parm_range(uint32_t start_index, uint32_t index, uint32_t argc, char* argv[]) +{ + uint32_t i; + + for (i=start_index; i<argc; i++) + { + arg_val[i] = _atoi(argv[i]); + if ((arg_val[i] < ecrnx_cli_tab[index].param_range[i].range_min) + || (arg_val[i] > ecrnx_cli_tab[index].param_range[i].range_max)) + { + ECRNX_PRINT("param %d is out of range! current %d, min %d, max %d\n", + i, arg_val[i], ecrnx_cli_tab[index].param_range[i].range_min, ecrnx_cli_tab[index].param_range[i].range_max); + return -1; + } + } + + return 0; +} + +int cli_macbyp_start(uint32_t index, uint8_t *param) +{ + uint32_t i; + + // parse parameters + if (0 != cli_check_parm_num(index, param)) + return -1; + + // check range value + if (0 != strcmp(argv[0], "11a") + && 0 != strcmp(argv[0], "11b") + && 0 != strcmp(argv[0], "11g") + && 0 != strcmp(argv[0], "11n") + && 0 != strcmp(argv[0], "11ac") + && 0 != strcmp(argv[0], "11ax")) + return -1; + + if (0 != cli_check_parm_range(1, index, argc, argv)) + return -1; + + // package command + sprintf(slave_cli_cmd, "%s %s", ecrnx_cli_tab[index].map_cmd, argv[0]); + for (i=1; i<argc; i++) + sprintf(slave_cli_cmd+strlen(slave_cli_cmd), " %d", arg_val[i]); + + //printk("--->The final command is: %s<-\n", slave_cli_cmd); + ecrnx_slave_cli_send(slave_cli_cmd, NULL); + + return 0; +} + +int cli_macbyp_stop(uint32_t index, uint8_t *param) +{ + // parse parameters + if (0 != cli_check_parm_num(index, param)) + return -1; + + // package command + sprintf(slave_cli_cmd, "%s", ecrnx_cli_tab[index].map_cmd); + + //printk("---> The final command is: %s<-\n", slave_cli_cmd); + ecrnx_slave_cli_send(slave_cli_cmd, NULL); + + return 0; +} + +int cli_rf_txgain(uint32_t index, uint8_t *param) +{ + uint32_t i; + + // parse parameters + if (0 != cli_check_parm_num(index, param)) + return -1; + + // check range value + if (0 != cli_check_parm_range(0, index, argc, argv)) + return -1; + + // package command + sprintf(slave_cli_cmd, "%s", ecrnx_cli_tab[index].map_cmd); + for (i=0; i<argc; i++) + sprintf(slave_cli_cmd+strlen(slave_cli_cmd), " %d", arg_val[i]); + + //printk("---> The final command is: %s<-\n", slave_cli_cmd); + ecrnx_slave_cli_send(slave_cli_cmd, NULL); + + return 0; +} + +int cli_rf_rxgain(uint32_t index, uint8_t *param) +{ + uint32_t i; + + // parse parameters + if (0 != cli_check_parm_num(index, param)) + return -1; + + // check range value + if (0 != cli_check_parm_range(0, index, argc, argv)) + return -1; + + // package command + sprintf(slave_cli_cmd, "%s", ecrnx_cli_tab[index].map_cmd); + for (i=0; i<argc; i++) + sprintf(slave_cli_cmd+strlen(slave_cli_cmd), " %d", arg_val[i]); + + //printk("---> The final command is: %s<-\n", slave_cli_cmd); + ecrnx_slave_cli_send(slave_cli_cmd, NULL); + + return 0; +} + +int cli_rf_chan(uint32_t index, uint8_t *param) +{ + uint32_t i; + + // parse parameters + if (0 != cli_check_parm_num(index, param)) + return -1; + + // check range value + if (0 != cli_check_parm_range(0, index, argc, argv)) + return -1; + + // package command + sprintf(slave_cli_cmd, "%s", ecrnx_cli_tab[index].map_cmd); + for (i=0; i<argc; i++) + sprintf(slave_cli_cmd+strlen(slave_cli_cmd), " %d", arg_val[i]); + + //printk("---> The final command is: %s<-\n", slave_cli_cmd); + ecrnx_slave_cli_send(slave_cli_cmd, NULL); + + return 0; +} + +int cli_cmd_parse(uint8_t *cmd) +{ + uint32_t i; + int ret; + + if (cmd == NULL) + return -1; + + //printk("cmd is :%s<-\n", cmd); + for (i=0; i<sizeof(ecrnx_cli_tab)/sizeof(ecrnx_cli_tab[0]); i++) + { + if (0 == memcmp(cmd, ecrnx_cli_tab[i].cmd, strlen(ecrnx_cli_tab[i].cmd))) + { + //printk("index %d, cmd %s\n", i, ecrnx_cli_tab[i].cmd); + ret = ecrnx_cli_tab[i].cmd_func(i, cmd + strlen(ecrnx_cli_tab[i].cmd)); + goto exit; + } + } + + ECRNX_ERR("No matching commands!\n"); + + return -2; + +exit: + return ret; +} +#endif + diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_debugfs_func.h b/drivers/net/wireless/eswin/fullmac/ecrnx_debugfs_func.h new file mode 100644 index 000000000000..905d3686e520 --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_debugfs_func.h @@ -0,0 +1,211 @@ +#ifndef __ECRNX_DEBUGFS_FUNC_H_ +#define __ECRNX_DEBUGFS_FUNC_H_ + +#include "ecrnx_defs.h" +#include "ecrnx_utils.h" +#include "eswin_utils.h" +#include "fw_head_check.h" + +#include <linux/etherdevice.h> + + + +#define HOST_DRIVER_VERSION "1.0.0" + +#define NX_VIRT_STA_MAX (NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX) + +typedef enum { + IF_STA = 0, + IF_AP, + IF_P2P +} IF_TYPE_EN; + +typedef struct { + u8 ch; + u8 bw; + u32 ch_offset; +} RF_INFO_ST; + +typedef struct { + u8 name[48]; + u32 addr; +}REG_MAP_ST; + +struct ecrnx_debugfs_survey_info_tbl +{ + struct list_head list; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 bssid[ETH_ALEN]; + u32 ch; + int rssi; + s32 sdbm; + s16 noise; + u32 age; + char flag[64]; +}; + +struct ecrnx_debugfs_sta +{ + u8 mac_addr[ETH_ALEN]; + u16 aid; /* association ID */ + u8 sta_idx; /* Identifier of the station */ + enum nl80211_chan_width width; /* Channel width */ + u8 ch_idx; /* Identifier of the channel + context the station belongs to */ + bool qos; /* Flag indicating if the station + supports QoS */ + bool ht; /* Flag indicating if the station + supports HT */ + bool vht; /* Flag indicating if the station + supports VHT */ + + bool sgi_20m; + bool sgi_40m; + + u8 ampdu_enable;/* for enable Tx A-MPDU */ + + /* for processing Tx A-MPDU */ + u8 agg_enable_bitmap; + /* u8 ADDBA_retry_count; */ + u8 candidate_tid_bitmap; + + u8 ldpc_cap; + u8 stbc_cap; + u8 beamform_cap; +}; + +#define MAX_ARGV 10 + +typedef int (*_cmd_func)(uint32_t index, uint8_t *param); + +typedef struct{ + uint32_t range_min; + uint32_t range_max; +}ECRNX_CLI_PARAM_RANGE_ST; + +typedef struct { + char cmd[20]; + char map_cmd[20]; + uint32_t param_num; + ECRNX_CLI_PARAM_RANGE_ST param_range[MAX_ARGV]; + _cmd_func cmd_func; +}ECRNX_CLI_TABLE_ST; + +extern ECRNX_CLI_TABLE_ST ecrnx_cli_tab[]; + +enum debugfs_type +{ + SLAVE_LOG_LEVEL = 1, + SLAVE_FW_INFO, + SLAVE_RF_INFO, + SLAVE_MAC_REG_DUMP, + SLAVE_RF_REG_DUMP, + SLAVE_BB_REG_DUMP, + SLAVE_COUNTRY_CODE, + SLAVE_EFUSE_MAP, + SLAVE_WOW_ENABLE, + SLAVE_WOW_WLAN_GOPI_INFO, + SLAVE_READ_REG, + SLAVE_WRITE_REG, + SLAVE_FUNC_INFO, + SLAVE_DEBUGFS_MAX, +}; + +typedef struct +{ + uint32_t debugfs_type; + + union + { + struct + { + uint32_t debug_level; + uint32_t debug_dir; + }slave_log_level_t; + + struct + { + uint32_t info; + uint32_t info_len; + }slave_fw_info_t; + + struct + { + uint32_t reg; + uint32_t len; + }slave_read_reg_t; + + struct + { + uint32_t reg; + uint32_t value; + }slave_write_reg_t; + + struct + { + uint8_t func_and_param[56]; + }slave_cli_func_info_t; + + }u; +}debugfs_info_t; + +typedef struct +{ + uint32_t debugfs_type; + unsigned char rxdata[ECRNX_RXSIZE]; + int rxlen; + wait_queue_head_t rxdataq; + int rxdatas; +}debugfs_resp_t; + +extern debugfs_info_t debugfs_info; +extern debugfs_resp_t debugfs_resp; + +extern u32 reg_buf[512]; + +void ecrnx_debug_param_send(dbg_req_t *req); + +struct ring_buffer *usb_dbg_buf_get(void); + +int ecrnx_cfg80211_dump_station(struct wiphy *wiphy, struct net_device *dev, + int idx, u8 *mac, struct station_info *sinfo); +int ecrnx_cfg80211_get_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + struct cfg80211_chan_def *chandef); +int ecrnx_log_level_get(LOG_CTL_ST *log); +int ecrnx_fw_log_level_set(u32 level, u32 dir); +bool ecrnx_log_host_enable(void); +int ecrnx_host_ver_get(u8 *ver); +int ecrnx_fw_ver_get(u8 *ver); +int ecrnx_build_time_get(u8 *build_time); +int ecrnx_rf_info_get(struct seq_file *seq, IF_TYPE_EN iftype,RF_INFO_ST *cur, RF_INFO_ST *oper); +int ecrnx_country_code_get(struct seq_file *seq, char *alpha2); +int ecrnx_mac_addr_get(struct seq_file *seq, IF_TYPE_EN iftype, u8 *mac_addr); +int ecrnx_mac_addr_get_ex(struct seq_file *seq, u8 *mac_addr_info); +int ecrnx_channel_get(struct seq_file *seq, IF_TYPE_EN iftype, struct cfg80211_chan_def *chandef); +int ecrnx_p2p_role_get(struct seq_file *seq, IF_TYPE_EN iftype); +int ecrnx_bssid_get(struct seq_file *seq, IF_TYPE_EN iftype, u8 *bssid); +int ecrnx_signal_level_get(struct seq_file *seq, IF_TYPE_EN iftype, s8 *noise_dbm); +int ecrnx_flags_get(struct seq_file *seq, IF_TYPE_EN iftype, u32 *flags); +int ecrnx_ssid_get(struct seq_file *seq, IF_TYPE_EN iftype, char *ssid); +int ecrnx_sta_mac_get(struct seq_file *seq, IF_TYPE_EN iftype, u8 sta_mac[][ETH_ALEN+1]); +int ecrnx_mac_reg_dump(struct seq_file *seq); +int ecrnx_rf_reg_dump(struct seq_file *seq); +int ecrnx_bb_reg_dump(struct seq_file *seq); +int ecrnx_efuse_map_dump(struct seq_file *seq); +int ecrnx_slave_reg_read(u32 addr, u32 *data, u32 len); +int ecrnx_slave_reg_write(u32 addr, u32 data, u32 len); +int ecrnx_slave_cli_send(u8 *cli, u8 *resp); +void ecrnx_debugfs_survey_info_update(struct ecrnx_hw *ecrnx_hw, struct cfg80211_bss *bss); +void ecrnx_debugfs_noise_of_survey_info_update(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_survey_info *ecrnx_survey, int chan_no); +void ecrnx_debugfs_add_station_in_ap_mode(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_sta *sta, struct station_parameters *params); +int cli_macbyp_start(uint32_t index, uint8_t *param); +int cli_macbyp_stop(uint32_t index, uint8_t *param); +int cli_rf_txgain(uint32_t index, uint8_t *param); +int cli_rf_rxgain(uint32_t index, uint8_t *param); +int cli_rf_chan(uint32_t index, uint8_t *param); +int cli_cmd_parse(uint8_t *cmd); + +#endif diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_defs.h b/drivers/net/wireless/eswin/fullmac/ecrnx_defs.h new file mode 100644 index 000000000000..3c015b18a636 --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_defs.h @@ -0,0 +1,1093 @@ +/** + ****************************************************************************** + * + * @file ecrnx_defs.h + * + * @brief Main driver structure declarations for fullmac driver + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef _ECRNX_DEFS_H_ +#define _ECRNX_DEFS_H_ + +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/dmapool.h> +#include <linux/skbuff.h> +#include <net/cfg80211.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/version.h> +#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0)) +#include <linux/vmalloc.h> +#include <linux/module.h> +#endif +#include "ecrnx_mod_params.h" +#include "ecrnx_debugfs.h" +#include "ecrnx_tx.h" +#include "ecrnx_rx.h" +#include "ecrnx_radar.h" +#include "ecrnx_utils.h" +#include "ecrnx_mu_group.h" +#include "ecrnx_platform.h" +#include "ecrnx_cmds.h" + +#include "ecrnx_p2p.h" +#include "ecrnx_debug.h" +#include "ecrnx_cfgfile.h" + + +#define WPI_HDR_LEN 18 +#define WPI_PN_LEN 16 +#define WPI_PN_OFST 2 +#define WPI_MIC_LEN 16 +#define WPI_KEY_LEN 32 +#define WPI_SUBKEY_LEN 16 // WPI key is actually two 16bytes key + +#define LEGACY_PS_ID 0 +#define UAPSD_ID 1 + +#define PS_SP_INTERRUPTED 255 +#define ECRNX_RXSIZE 1024 + +#define CONFIG_ESWIN_RX_REORDER 1 + +#if defined(CONFIG_ECRNX_HE) +extern struct ieee80211_sta_he_cap ecrnx_he_cap; +#endif + +/** + * struct ecrnx_bcn - Information of the beacon in used (AP mode) + * + * @head: head portion of beacon (before TIM IE) + * @tail: tail portion of beacon (after TIM IE) + * @ies: extra IEs (not used ?) + * @head_len: length of head data + * @tail_len: length of tail data + * @ies_len: length of extra IEs data + * @tim_len: length of TIM IE + * @len: Total beacon len (head + tim + tail + extra) + * @dtim: dtim period + */ +struct ecrnx_bcn { + u8 *head; + u8 *tail; + u8 *ies; + size_t head_len; + size_t tail_len; + size_t ies_len; + size_t tim_len; + size_t len; + u8 dtim; +}; + +/** + * struct ecrnx_key - Key information + * + * @hw_idx: Idx of the key from hardware point of view + */ +struct ecrnx_key { + u8 hw_idx; +}; + +/** + * Structure containing information about a Mesh Path + */ +struct ecrnx_mesh_path { + struct list_head list; /* For ecrnx_vif.mesh_paths */ + u8 path_idx; /* Path Index */ + struct mac_addr tgt_mac_addr; /* Target MAC Address */ + struct ecrnx_sta *nhop_sta; /* Pointer to the Next Hop STA */ +}; + +struct ecrnx_mesh_proxy { + struct list_head list; /* For ecrnx_vif.mesh_proxy */ + struct mac_addr ext_sta_addr; /* Address of the External STA */ + struct mac_addr proxy_addr; /* Proxy MAC Address */ + bool local; /* Indicate if interface is a proxy for the device */ +}; + +/** + * struct ecrnx_csa - Information for CSA (Channel Switch Announcement) + * + * @vif: Pointer to the vif doing the CSA + * @bcn: Beacon to use after CSA + * @elem: IPC buffer to send the new beacon to the fw + * @chandef: defines the channel to use after the switch + * @count: Current csa counter + * @status: Status of the CSA at fw level + * @ch_idx: Index of the new channel context + * @work: work scheduled at the end of CSA + */ +struct ecrnx_csa { + struct ecrnx_vif *vif; + struct ecrnx_bcn bcn; + struct ecrnx_ipc_elem_var elem; + struct cfg80211_chan_def chandef; + int count; + int status; + int ch_idx; + struct work_struct work; +}; + +/// Possible States of the TDLS link. +enum tdls_status_tag { + /// TDLS link is not active (no TDLS peer connected) + TDLS_LINK_IDLE, + /// TDLS Setup Request transmitted + TDLS_SETUP_REQ_TX, + /// TDLS Setup Response transmitted + TDLS_SETUP_RSP_TX, + /// TDLS link is active (TDLS peer connected) + TDLS_LINK_ACTIVE, + /// TDLS Max Number of states. + TDLS_STATE_MAX +}; + +/* + * Structure used to save information relative to the TDLS peer. + * This is also linked within the ecrnx_hw vifs list. + * + */ +struct ecrnx_tdls { + bool active; /* Indicate if TDLS link is active */ + bool initiator; /* Indicate if TDLS peer is the TDLS initiator */ + bool chsw_en; /* Indicate if channel switch is enabled */ + u8 last_tid; /* TID of the latest MPDU transmitted over the + TDLS direct link to the TDLS STA */ + u16 last_sn; /* Sequence number of the latest MPDU transmitted + over the TDLS direct link to the TDLS STA */ + bool ps_on; /* Indicate if the power save is enabled on the + TDLS STA */ + bool chsw_allowed; /* Indicate if TDLS channel switch is allowed */ +}; + + +/** + * enum ecrnx_ap_flags - AP flags + * + * @ECRNX_AP_ISOLATE Isolate clients (i.e. Don't brige packets transmitted by + * one client for another one) + */ +enum ecrnx_ap_flags { + ECRNX_AP_ISOLATE = BIT(0), + ECRNX_AP_USER_MESH_PM = BIT(1), + ECRNX_AP_CREATE_MESH_PATH = BIT(2), +}; + +/** + * enum ecrnx_sta_flags - STATION flags + * + * @ECRNX_STA_EXT_AUTH: External authentication is in progress + */ +enum ecrnx_sta_flags { + ECRNX_STA_EXT_AUTH = BIT(0), + ECRNX_STA_FT_OVER_DS = BIT(1), + ECRNX_STA_FT_OVER_AIR = BIT(2), +}; + +#ifdef CONFIG_ECRNX_WIFO_CAIL +/** + * struct ecrnx_iwpriv_amt_vif - iwpriv amt VIF information + * + * @ndev: Pointer to the associated net device + */ +struct ecrnx_iwpriv_amt_vif { + struct net_device *ndev; + + unsigned char rxdata[ECRNX_RXSIZE]; + int rxlen; + wait_queue_head_t rxdataq; + int rxdatas; +}; +#endif + +#define ECRNX_REORD_RX_MSDU_CNT (256) +#define ECRNX_REORD_TIMEOUT (50) +#define ECRNX_REORD_WINSIZE (64) +#define SN_LESS(a, b) (((a-b)&0x800)!=0) +#define SN_EQUAL(a, b) (a == b) + + +struct reord_cntrl { + bool active; + bool valid; + u16 win_size; + u16 win_start; + struct ecrnx_hw *ecrnx_hw; + struct ecrnx_vif *ecrnx_vif; + spinlock_t reord_list_lock; + struct list_head reord_list; + struct timer_list reord_timer; + struct work_struct reord_timer_work; +}; + +struct reord_msdu_info { + struct sk_buff *skb; + u8 tid; + u8 need_pn_check; + u8 is_ga; + u16 sn; + struct hw_rxhdr *hw_rxhdr; + struct list_head reord_pending_list; + struct list_head rx_msdu_list; + struct reord_cntrl *preorder_ctrl; +}; + +/** + * struct ecrnx_vif - VIF information + * + * @list: List element for ecrnx_hw->vifs + * @ecrnx_hw: Pointer to driver main data + * @wdev: Wireless device + * @ndev: Pointer to the associated net device + * @net_stats: Stats structure for the net device + * @key: Conversion table between protocol key index and MACHW key index + * @drv_vif_index: VIF index at driver level (only use to identify active + * vifs in ecrnx_hw->avail_idx_map) + * @vif_index: VIF index at fw level (used to index ecrnx_hw->vif_table, and + * ecrnx_sta->vif_idx) + * @ch_index: Channel context index (within ecrnx_hw->chanctx_table) + * @up: Indicate if associated netdev is up (i.e. Interface is created at fw level) + * @use_4addr: Whether 4address mode should be use or not + * @is_resending: Whether a frame is being resent on this interface + * @roc_tdls: Indicate if the ROC has been called by a TDLS station + * @tdls_status: Status of the TDLS link + * @tdls_chsw_prohibited: Whether TDLS Channel Switch is prohibited or not + * @generation: Generation ID. Increased each time a sta is added/removed + * + * STA / P2P_CLIENT interfaces + * @flags: see ecrnx_sta_flags + * @ap: Pointer to the peer STA entry allocated for the AP + * @tdls_sta: Pointer to the TDLS station + * @ft_assoc_ies: Association Request Elements (only allocated for FT connection) + * @ft_assoc_ies_len: Size, in bytes, of the Association request elements. + * @ft_target_ap: Target AP for a BSS transition for FT over DS + * + * AP/P2P GO/ MESH POINT interfaces + * @flags: see ecrnx_ap_flags + * @sta_list: List of station connected to the interface + * @bcn: Beacon data + * @bcn_interval: beacon interval in TU + * @bcmc_index: Index of the BroadCast/MultiCast station + * @csa: Information about current Channel Switch Announcement (NULL if no CSA) + * @mpath_list: List of Mesh Paths (MESH Point only) + * @proxy_list: List of Proxies Information (MESH Point only) + * @mesh_pm: Mesh power save mode currently set in firmware + * @next_mesh_pm: Mesh power save mode for next peer + * + * AP_VLAN interfaces + * @mater: Pointer to the master interface + * @sta_4a: When AP_VLAN interface are used for WDS (i.e. wireless connection + * between several APs) this is the 'gateway' sta to 'master' AP + */ +struct ecrnx_vif { + struct list_head list; + struct ecrnx_hw *ecrnx_hw; + struct wireless_dev wdev; + struct net_device *ndev; + struct net_device_stats net_stats; + struct ecrnx_key key[6]; + u8 drv_vif_index; /* Identifier of the VIF in driver */ + u8 vif_index; /* Identifier of the station in FW */ + u8 ch_index; /* Channel context identifier */ + bool up; /* Indicate if associated netdev is up + (i.e. Interface is created at fw level) */ + bool use_4addr; /* Should we use 4addresses mode */ + bool is_resending; /* Indicate if a frame is being resent on this interface */ + bool roc_tdls; /* Indicate if the ROC has been called by a + TDLS station */ + u8 tdls_status; /* Status of the TDLS link */ + bool tdls_chsw_prohibited; /* Indicate if TDLS Channel Switch is prohibited */ + int generation; + + unsigned char rxdata[ECRNX_RXSIZE]; + int rxlen; + wait_queue_head_t rxdataq; + int rxdatas; + + u32 mgmt_reg_stypes; //GavinGao + + union + { + struct + { + u32 flags; + struct ecrnx_sta *ap; /* Pointer to the peer STA entry allocated for + the AP */ + struct ecrnx_sta *tdls_sta; /* Pointer to the TDLS station */ + u8 *ft_assoc_ies; + int ft_assoc_ies_len; + u8 ft_target_ap[ETH_ALEN]; + } sta; + struct + { + u32 flags; + struct list_head sta_list; /* List of STA connected to the AP */ + struct ecrnx_bcn bcn; /* beacon */ + int bcn_interval; + u8 bcmc_index; /* Index of the BCMC sta to use */ + struct ecrnx_csa *csa; + + struct list_head mpath_list; /* List of Mesh Paths used on this interface */ + struct list_head proxy_list; /* List of Proxies Information used on this interface */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + enum nl80211_mesh_power_mode mesh_pm; /* mesh power save mode currently set in firmware */ + enum nl80211_mesh_power_mode next_mesh_pm; /* mesh power save mode for next peer */ +#endif + } ap; + struct + { + struct ecrnx_vif *master; /* pointer on master interface */ + struct ecrnx_sta *sta_4a; + } ap_vlan; + }; + uint64_t rx_pn[TID_MAX]; +}; + +#define ECRNX_INVALID_VIF 0xFF +#define ECRNX_VIF_TYPE(vif) (vif->wdev.iftype) + +/** + * Structure used to store information relative to PS mode. + * + * @active: True when the sta is in PS mode. + * If false, other values should be ignored + * @pkt_ready: Number of packets buffered for the sta in drv's txq + * (1 counter for Legacy PS and 1 for U-APSD) + * @sp_cnt: Number of packets that remain to be pushed in the service period. + * 0 means that no service period is in progress + * (1 counter for Legacy PS and 1 for U-APSD) + */ +struct ecrnx_sta_ps { + bool active; + u16 pkt_ready[2]; + u16 sp_cnt[2]; +}; + +/** + * struct ecrnx_rx_rate_stats - Store statistics for RX rates + * + * @table: Table indicating how many frame has been receive which each + * rate index. Rate index is the same as the one used by RC algo for TX + * @size: Size of the table array + * @cpt: number of frames received + */ +struct ecrnx_rx_rate_stats { + int *table; + int size; + int cpt; + int rate_cnt; +}; + +/** + * struct ecrnx_sta_stats - Structure Used to store statistics specific to a STA + * + * @last_rx: Hardware vector of the last received frame + * @rx_rate: Statistics of the received rates + */ +struct ecrnx_sta_stats { + u32 rx_pkts; + u32 tx_pkts; + u64 rx_bytes; + u64 tx_bytes; + unsigned long last_act; + struct hw_vect last_rx; +#ifdef CONFIG_ECRNX_DEBUGFS + struct ecrnx_rx_rate_stats rx_rate; +#endif +}; + +/* + * Structure used to save information relative to the managed stations. + */ +struct ecrnx_sta { + struct list_head list; + bool valid; + u8 mac_addr[ETH_ALEN]; + u16 aid; /* association ID */ + u8 sta_idx; /* Identifier of the station */ + u8 vif_idx; /* Identifier of the VIF (fw id) the station + belongs to */ + u8 vlan_idx; /* Identifier of the VLAN VIF (fw id) the station + belongs to (= vif_idx if no vlan in used) */ + enum nl80211_band band; /* Band */ + enum nl80211_chan_width width; /* Channel width */ + u16 center_freq; /* Center frequency */ + u32 center_freq1; /* Center frequency 1 */ + u32 center_freq2; /* Center frequency 2 */ + u8 ch_idx; /* Identifier of the channel + context the station belongs to */ + bool qos; /* Flag indicating if the station + supports QoS */ + u8 acm; /* Bitfield indicating which queues + have AC mandatory */ + u16 uapsd_tids; /* Bitfield indicating which tids are subject to + UAPSD */ + struct ecrnx_key key; + struct ecrnx_sta_ps ps; /* Information when STA is in PS (AP only) */ +#ifdef CONFIG_ECRNX_BFMER + struct ecrnx_bfmer_report *bfm_report; /* Beamforming report to be used for + VHT TX Beamforming */ +#ifdef CONFIG_ECRNX_MUMIMO_TX + struct ecrnx_sta_group_info group_info; /* MU grouping information for the STA */ +#endif /* CONFIG_ECRNX_MUMIMO_TX */ +#endif /* CONFIG_ECRNX_BFMER */ + + bool ht; /* Flag indicating if the station + supports HT */ + bool vht; /* Flag indicating if the station + supports VHT */ + u32 ac_param[AC_MAX]; /* EDCA parameters */ + struct ecrnx_tdls tdls; /* TDLS station information */ + struct ecrnx_sta_stats stats; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + enum nl80211_mesh_power_mode mesh_pm; /* link-specific mesh power save mode */ +#endif + int listen_interval; + struct twt_setup_ind twt_ind; /*TWT Setup indication*/ + uint64_t rx_pn[TID_MAX]; + struct reord_cntrl reord_cntrl[TID_MAX]; +}; + +#define ECRNX_INVALID_STA 0xFF +static inline const u8 *ecrnx_sta_addr(struct ecrnx_sta *sta) { + return sta->mac_addr; +} + +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF +struct ecrnx_amsdu_stats { + int done; + int failed; +}; +#endif + +struct ecrnx_stats { + int cfm_balance[NX_TXQ_CNT]; + unsigned long last_rx, last_tx; /* jiffies */ + int ampdus_tx[IEEE80211_MAX_AMPDU_BUF]; + int ampdus_rx[IEEE80211_MAX_AMPDU_BUF]; + int ampdus_rx_map[4]; + int ampdus_rx_miss; + int ampdus_rx_last; +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + struct ecrnx_amsdu_stats amsdus[NX_TX_PAYLOAD_MAX]; +#endif + int amsdus_rx[64]; +}; + +/** + * struct ecrnx_roc - Remain On Channel information + * + + Structure that will contains all RoC information received from cfg80211 */ +struct ecrnx_roc { + struct ecrnx_vif *vif; + struct ieee80211_channel *chan; + unsigned int duration; + /* Used to avoid call of CFG80211 callback upon expiration of RoC */ + bool internal; + /* Indicate if we have switch on the RoC channel */ + bool on_chan; +}; + +/* Structure containing channel survey information received from MAC */ +struct ecrnx_survey_info { + // Filled + u32 filled; + // Amount of time in ms the radio spent on the channel + u32 chan_time_ms; + // Amount of time the primary channel was sensed busy + u32 chan_time_busy_ms; + // Noise in dbm + s8 noise_dbm; +}; + + +/* Structure containing channel context information */ +struct ecrnx_chanctx { + struct cfg80211_chan_def chan_def; /* channel description */ + u8 count; /* number of vif using this ctxt */ +}; + +#define ECRNX_CH_NOT_SET 0xFF +/** + * ecrnx_phy_info - Phy information + * + * @phy_cnt: Number of phy interface + * @cfg: Configuration send to firmware + * @sec_chan: Channel configuration of the second phy interface (if phy_cnt > 1) + * @limit_bw: Set to true to limit BW on requested channel. Only set to use + * VHT with old radio that don't support 80MHz (deprecated) + */ +struct ecrnx_phy_info { + u8 cnt; + struct phy_cfg_tag cfg; + struct mac_chan_op sec_chan; + bool limit_bw; +}; + +struct ecrnx_hw { + struct device *dev; + // Hardware info + +#ifdef CONFIG_ECRNX_ESWIN + void *plat; +#else + struct ecrnx_plat *plat; +#endif + + struct ecrnx_phy_info phy; + struct mm_version_cfm version_cfm; + int machw_type; + struct ecrnx_mod_params *mod_params; + unsigned long flags; + struct wiphy *wiphy; + u8 ext_capa[10]; + struct list_head vifs; + struct ecrnx_vif *vif_table[NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX]; /* indexed with fw id */ + u8 vif_started; + u8 avail_idx_map; + u8 monitor_vif; + struct ecrnx_sta sta_table[NX_REMOTE_STA_MAX + NX_VIRT_DEV_MAX]; + + // Channels + struct ecrnx_chanctx chanctx_table[NX_CHAN_CTXT_CNT]; + u8 cur_chanctx; + struct ecrnx_survey_info survey[SCAN_CHANNEL_MAX]; + + /* RoC Management */ + struct ecrnx_roc *roc; + u32 roc_cookie; + struct cfg80211_scan_request *scan_request; + spinlock_t scan_req_lock; + spinlock_t connect_req_lock; + struct ecrnx_radar radar; + +#ifdef CONFIG_ECRNX_P2P + struct ecrnx_p2p_listen p2p_listen; +#endif + + // TX path + spinlock_t tx_lock; + struct ecrnx_txq txq[NX_NB_TXQ]; + struct ecrnx_hwq hwq[NX_TXQ_CNT]; + struct timer_list txq_cleanup; + struct kmem_cache *sw_txhdr_cache; + u32 tcp_pacing_shift; +#ifdef CONFIG_ECRNX_MUMIMO_TX + struct ecrnx_mu_info mu; +#endif + + // RX path + struct ecrnx_defer_rx defer_rx; + spinlock_t rx_lock; + + struct tasklet_struct task; + + + /* IPC */ + struct ipc_host_env_tag *ipc_env; + struct ecrnx_cmd_mgr cmd_mgr; + spinlock_t cb_lock; + struct ecrnx_ipc_elem_pool e2amsgs_pool; + struct ecrnx_ipc_elem_pool dbgmsgs_pool; + struct ecrnx_ipc_elem_pool e2aradars_pool; + struct ecrnx_ipc_elem_var pattern_elem; + struct ecrnx_ipc_dbgdump_elem dbgdump_elem; + struct ecrnx_ipc_elem_pool e2arxdesc_pool; + struct ecrnx_ipc_skb_elem *e2aunsuprxvec_elems; + struct ecrnx_ipc_rxbuf_elems rxbuf_elems; + struct ecrnx_ipc_elem_var scan_ie; + +#ifdef CONFIG_ECRNX_DEBUGFS + struct ecrnx_debugfs debugfs; +#endif + + struct ecrnx_stats stats; + struct ecrnx_conf_file conf_param; + +#ifdef CONFIG_ECRNX_ESWIN + struct list_head agg_rx_list; + struct list_head defrag_rx_list; +#endif +#ifdef CONFIG_ESWIN_RX_REORDER + spinlock_t rx_msdu_free_lock; + struct list_head rx_msdu_free_list; + struct list_head rx_reord_list; + spinlock_t rx_reord_lock; + struct reord_msdu_info * rx_reord_buf; +#endif + + /* extended capabilities supported */ +#if defined(CONFIG_ECRNX_DEBUGFS_CUSTOM) + struct list_head debugfs_survey_info_tbl_ptr; +#endif + u32 msg_tx; + u32 msg_tx_done; + u32 data_tx; + u32 data_tx_done; + u32 usb_rx; + u32 msg_rx; + u32 data_rx; +}; + +u8 *ecrnx_build_bcn(struct ecrnx_bcn *bcn, struct cfg80211_beacon_data *new); + +void ecrnx_chanctx_link(struct ecrnx_vif *vif, u8 idx, + struct cfg80211_chan_def *chandef); +void ecrnx_chanctx_unlink(struct ecrnx_vif *vif); +int ecrnx_chanctx_valid(struct ecrnx_hw *ecrnx_hw, u8 idx); + +static inline bool is_multicast_sta(int sta_idx) +{ + return (sta_idx >= NX_REMOTE_STA_MAX); +} +struct ecrnx_sta *ecrnx_get_sta(struct ecrnx_hw *ecrnx_hw, const u8 *mac_addr); + +static inline uint8_t master_vif_idx(struct ecrnx_vif *vif) +{ + if (unlikely(vif->wdev.iftype == NL80211_IFTYPE_AP_VLAN)) { + return vif->ap_vlan.master->vif_index; + } else { + return vif->vif_index; + } +} + +static inline void *ecrnx_get_shared_trace_buf(struct ecrnx_hw *ecrnx_hw) +{ +#ifdef CONFIG_ECRNX_DEBUGFS + return (void *)&(ecrnx_hw->debugfs.fw_trace.buf); +#else + return NULL; +#endif +} + +void ecrnx_external_auth_enable(struct ecrnx_vif *vif); +void ecrnx_external_auth_disable(struct ecrnx_vif *vif); + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0) +/* 802.11ax HE MAC capabilities */ +#define IEEE80211_HE_MAC_CAP0_HTC_HE 0x01 +#define IEEE80211_HE_MAC_CAP0_TWT_REQ 0x02 +#define IEEE80211_HE_MAC_CAP0_TWT_RES 0x04 +#define IEEE80211_HE_MAC_CAP0_DYNAMIC_FRAG_NOT_SUPP 0x00 +#define IEEE80211_HE_MAC_CAP0_DYNAMIC_FRAG_LEVEL_1 0x08 +#define IEEE80211_HE_MAC_CAP0_DYNAMIC_FRAG_LEVEL_2 0x10 +#define IEEE80211_HE_MAC_CAP0_DYNAMIC_FRAG_LEVEL_3 0x18 +#define IEEE80211_HE_MAC_CAP0_DYNAMIC_FRAG_MASK 0x18 +#define IEEE80211_HE_MAC_CAP0_MAX_NUM_FRAG_MSDU_1 0x00 +#define IEEE80211_HE_MAC_CAP0_MAX_NUM_FRAG_MSDU_2 0x20 +#define IEEE80211_HE_MAC_CAP0_MAX_NUM_FRAG_MSDU_4 0x40 +#define IEEE80211_HE_MAC_CAP0_MAX_NUM_FRAG_MSDU_8 0x60 +#define IEEE80211_HE_MAC_CAP0_MAX_NUM_FRAG_MSDU_16 0x80 +#define IEEE80211_HE_MAC_CAP0_MAX_NUM_FRAG_MSDU_32 0xa0 +#define IEEE80211_HE_MAC_CAP0_MAX_NUM_FRAG_MSDU_64 0xc0 +#define IEEE80211_HE_MAC_CAP0_MAX_NUM_FRAG_MSDU_UNLIMITED 0xe0 +#define IEEE80211_HE_MAC_CAP0_MAX_NUM_FRAG_MSDU_MASK 0xe0 + +#define IEEE80211_HE_MAC_CAP1_MIN_FRAG_SIZE_UNLIMITED 0x00 +#define IEEE80211_HE_MAC_CAP1_MIN_FRAG_SIZE_128 0x01 +#define IEEE80211_HE_MAC_CAP1_MIN_FRAG_SIZE_256 0x02 +#define IEEE80211_HE_MAC_CAP1_MIN_FRAG_SIZE_512 0x03 +#define IEEE80211_HE_MAC_CAP1_MIN_FRAG_SIZE_MASK 0x03 +#define IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_0US 0x00 +#define IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_8US 0x04 +#define IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_16US 0x08 +#define IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_MASK 0x0c +#define IEEE80211_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_1 0x00 +#define IEEE80211_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_2 0x10 +#define IEEE80211_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_3 0x20 +#define IEEE80211_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_4 0x30 +#define IEEE80211_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_5 0x40 +#define IEEE80211_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_6 0x50 +#define IEEE80211_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_7 0x60 +#define IEEE80211_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_8 0x70 +#define IEEE80211_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_MASK 0x70 + +/* Link adaptation is split between byte HE_MAC_CAP1 and + * HE_MAC_CAP2. It should be set only if IEEE80211_HE_MAC_CAP0_HTC_HE + * in which case the following values apply: + * 0 = No feedback. + * 1 = reserved. + * 2 = Unsolicited feedback. + * 3 = both + */ +#define IEEE80211_HE_MAC_CAP1_LINK_ADAPTATION 0x80 + +#define IEEE80211_HE_MAC_CAP2_LINK_ADAPTATION 0x01 +#define IEEE80211_HE_MAC_CAP2_ALL_ACK 0x02 +#define IEEE80211_HE_MAC_CAP2_TRS 0x04 +#define IEEE80211_HE_MAC_CAP2_BSR 0x08 +#define IEEE80211_HE_MAC_CAP2_BCAST_TWT 0x10 +#define IEEE80211_HE_MAC_CAP2_32BIT_BA_BITMAP 0x20 +#define IEEE80211_HE_MAC_CAP2_MU_CASCADING 0x40 +#define IEEE80211_HE_MAC_CAP2_ACK_EN 0x80 + +#define IEEE80211_HE_MAC_CAP3_OMI_CONTROL 0x02 +#define IEEE80211_HE_MAC_CAP3_OFDMA_RA 0x04 + +/* The maximum length of an A-MDPU is defined by the combination of the Maximum + * A-MDPU Length Exponent field in the HT capabilities, VHT capabilities and the + * same field in the HE capabilities. + */ +#define IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_USE_VHT 0x00 +#define IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_VHT_1 0x08 +#define IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_VHT_2 0x10 +#define IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_RESERVED 0x18 +#define IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_MASK 0x18 +#define IEEE80211_HE_MAC_CAP3_AMSDU_FRAG 0x20 +#define IEEE80211_HE_MAC_CAP3_FLEX_TWT_SCHED 0x40 +#define IEEE80211_HE_MAC_CAP3_RX_CTRL_FRAME_TO_MULTIBSS 0x80 + +#define IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_SHIFT 3 + +#define IEEE80211_HE_MAC_CAP4_BSRP_BQRP_A_MPDU_AGG 0x01 +#define IEEE80211_HE_MAC_CAP4_QTP 0x02 +#define IEEE80211_HE_MAC_CAP4_BQR 0x04 +#define IEEE80211_HE_MAC_CAP4_SRP_RESP 0x08 +#define IEEE80211_HE_MAC_CAP4_NDP_FB_REP 0x10 +#define IEEE80211_HE_MAC_CAP4_OPS 0x20 +#define IEEE80211_HE_MAC_CAP4_AMDSU_IN_AMPDU 0x40 +/* Multi TID agg TX is split between byte #4 and #5 + * The value is a combination of B39,B40,B41 + */ +#define IEEE80211_HE_MAC_CAP4_MULTI_TID_AGG_TX_QOS_B39 0x80 + +#define IEEE80211_HE_MAC_CAP5_MULTI_TID_AGG_TX_QOS_B40 0x01 +#define IEEE80211_HE_MAC_CAP5_MULTI_TID_AGG_TX_QOS_B41 0x02 +#define IEEE80211_HE_MAC_CAP5_SUBCHAN_SELECVITE_TRANSMISSION 0x04 +#define IEEE80211_HE_MAC_CAP5_UL_2x996_TONE_RU 0x08 +#define IEEE80211_HE_MAC_CAP5_OM_CTRL_UL_MU_DATA_DIS_RX 0x10 +#define IEEE80211_HE_MAC_CAP5_HE_DYNAMIC_SM_PS 0x20 +#define IEEE80211_HE_MAC_CAP5_PUNCTURED_SOUNDING 0x40 +#define IEEE80211_HE_MAC_CAP5_HT_VHT_TRIG_FRAME_RX 0x80 + +#define IEEE80211_HE_VHT_MAX_AMPDU_FACTOR 20 +#define IEEE80211_HE_HT_MAX_AMPDU_FACTOR 16 + +/* 802.11ax HE PHY capabilities */ +#define IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G 0x02 +#define IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G 0x04 +#define IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G 0x08 +#define IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G 0x10 +#define IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_RU_MAPPING_IN_2G 0x20 +#define IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_RU_MAPPING_IN_5G 0x40 +#define IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_MASK 0xfe + +#define IEEE80211_HE_PHY_CAP1_PREAMBLE_PUNC_RX_80MHZ_ONLY_SECOND_20MHZ 0x01 +#define IEEE80211_HE_PHY_CAP1_PREAMBLE_PUNC_RX_80MHZ_ONLY_SECOND_40MHZ 0x02 +#define IEEE80211_HE_PHY_CAP1_PREAMBLE_PUNC_RX_160MHZ_ONLY_SECOND_20MHZ 0x04 +#define IEEE80211_HE_PHY_CAP1_PREAMBLE_PUNC_RX_160MHZ_ONLY_SECOND_40MHZ 0x08 +#define IEEE80211_HE_PHY_CAP1_PREAMBLE_PUNC_RX_MASK 0x0f +#define IEEE80211_HE_PHY_CAP1_DEVICE_CLASS_A 0x10 +#define IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD 0x20 +#define IEEE80211_HE_PHY_CAP1_HE_LTF_AND_GI_FOR_HE_PPDUS_0_8US 0x40 +/* Midamble RX/TX Max NSTS is split between byte #2 and byte #3 */ +#define IEEE80211_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS 0x80 + +#define IEEE80211_HE_PHY_CAP2_MIDAMBLE_RX_TX_MAX_NSTS 0x01 +#define IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US 0x02 +#define IEEE80211_HE_PHY_CAP2_STBC_TX_UNDER_80MHZ 0x04 +#define IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ 0x08 +#define IEEE80211_HE_PHY_CAP2_DOPPLER_TX 0x10 +#define IEEE80211_HE_PHY_CAP2_DOPPLER_RX 0x20 + +/* Note that the meaning of UL MU below is different between an AP and a non-AP + * sta, where in the AP case it indicates support for Rx and in the non-AP sta + * case it indicates support for Tx. + */ +#define IEEE80211_HE_PHY_CAP2_UL_MU_FULL_MU_MIMO 0x40 +#define IEEE80211_HE_PHY_CAP2_UL_MU_PARTIAL_MU_MIMO 0x80 + +#define IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_NO_DCM 0x00 +#define IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_BPSK 0x01 +#define IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_QPSK 0x02 +#define IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_16_QAM 0x03 +#define IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_MASK 0x03 +#define IEEE80211_HE_PHY_CAP3_DCM_MAX_TX_NSS_1 0x00 +#define IEEE80211_HE_PHY_CAP3_DCM_MAX_TX_NSS_2 0x04 +#define IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_NO_DCM 0x00 +#define IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_BPSK 0x08 +#define IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_QPSK 0x10 +#define IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_16_QAM 0x18 +#define IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_MASK 0x18 +#define IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_1 0x00 +#define IEEE80211_HE_PHY_CAP3_DCM_MAX_RX_NSS_2 0x20 +#define IEEE80211_HE_PHY_CAP3_RX_HE_MU_PPDU_FROM_NON_AP_STA 0x40 +#define IEEE80211_HE_PHY_CAP3_SU_BEAMFORMER 0x80 + +#define IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE 0x01 +#define IEEE80211_HE_PHY_CAP4_MU_BEAMFORMER 0x02 + +/* Minimal allowed value of Max STS under 80MHz is 3 */ +#define IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_4 0x0c +#define IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_5 0x10 +#define IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_6 0x14 +#define IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_7 0x18 +#define IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_8 0x1c +#define IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_MASK 0x1c + +/* Minimal allowed value of Max STS above 80MHz is 3 */ +#define IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_ABOVE_80MHZ_4 0x60 +#define IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_ABOVE_80MHZ_5 0x80 +#define IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_ABOVE_80MHZ_6 0xa0 +#define IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_ABOVE_80MHZ_7 0xc0 +#define IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_ABOVE_80MHZ_8 0xe0 +#define IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_ABOVE_80MHZ_MASK 0xe0 + +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_1 0x00 +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_2 0x01 +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_3 0x02 +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_4 0x03 +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_5 0x04 +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_6 0x05 +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_7 0x06 +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_8 0x07 +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_MASK 0x07 + +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_ABOVE_80MHZ_1 0x00 +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_ABOVE_80MHZ_2 0x08 +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_ABOVE_80MHZ_3 0x10 +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_ABOVE_80MHZ_4 0x18 +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_ABOVE_80MHZ_5 0x20 +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_ABOVE_80MHZ_6 0x28 +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_ABOVE_80MHZ_7 0x30 +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_ABOVE_80MHZ_8 0x38 +#define IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_ABOVE_80MHZ_MASK 0x38 + +#define IEEE80211_HE_PHY_CAP5_NG16_SU_FEEDBACK 0x40 +#define IEEE80211_HE_PHY_CAP5_NG16_MU_FEEDBACK 0x80 + +#define IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_42_SU 0x01 +#define IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_75_MU 0x02 +#define IEEE80211_HE_PHY_CAP6_TRIG_SU_BEAMFORMER_FB 0x04 +#define IEEE80211_HE_PHY_CAP6_TRIG_MU_BEAMFORMER_FB 0x08 +#define IEEE80211_HE_PHY_CAP6_TRIG_CQI_FB 0x10 +#define IEEE80211_HE_PHY_CAP6_PARTIAL_BW_EXT_RANGE 0x20 +#define IEEE80211_HE_PHY_CAP6_PARTIAL_BANDWIDTH_DL_MUMIMO 0x40 +#define IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT 0x80 + +#define IEEE80211_HE_PHY_CAP7_SRP_BASED_SR 0x01 +#define IEEE80211_HE_PHY_CAP7_POWER_BOOST_FACTOR_AR 0x02 +#define IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI 0x04 +#define IEEE80211_HE_PHY_CAP7_MAX_NC_1 0x08 +#define IEEE80211_HE_PHY_CAP7_MAX_NC_2 0x10 +#define IEEE80211_HE_PHY_CAP7_MAX_NC_3 0x18 +#define IEEE80211_HE_PHY_CAP7_MAX_NC_4 0x20 +#define IEEE80211_HE_PHY_CAP7_MAX_NC_5 0x28 +#define IEEE80211_HE_PHY_CAP7_MAX_NC_6 0x30 +#define IEEE80211_HE_PHY_CAP7_MAX_NC_7 0x38 +#define IEEE80211_HE_PHY_CAP7_MAX_NC_MASK 0x38 +#define IEEE80211_HE_PHY_CAP7_STBC_TX_ABOVE_80MHZ 0x40 +#define IEEE80211_HE_PHY_CAP7_STBC_RX_ABOVE_80MHZ 0x80 + +#define IEEE80211_HE_PHY_CAP8_HE_ER_SU_PPDU_4XLTF_AND_08_US_GI 0x01 +#define IEEE80211_HE_PHY_CAP8_20MHZ_IN_40MHZ_HE_PPDU_IN_2G 0x02 +#define IEEE80211_HE_PHY_CAP8_20MHZ_IN_160MHZ_HE_PPDU 0x04 +#define IEEE80211_HE_PHY_CAP8_80MHZ_IN_160MHZ_HE_PPDU 0x08 +#define IEEE80211_HE_PHY_CAP8_HE_ER_SU_1XLTF_AND_08_US_GI 0x10 +#define IEEE80211_HE_PHY_CAP8_MIDAMBLE_RX_TX_2X_AND_1XLTF 0x20 +#define IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_242 0x00 +#define IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_484 0x40 +#define IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_996 0x80 +#define IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_2x996 0xc0 +#define IEEE80211_HE_PHY_CAP8_DCM_MAX_RU_MASK 0xc0 + +#define IEEE80211_HE_PHY_CAP9_LONGER_THAN_16_SIGB_OFDM_SYM 0x01 +#define IEEE80211_HE_PHY_CAP9_NON_TRIGGERED_CQI_FEEDBACK 0x02 +#define IEEE80211_HE_PHY_CAP9_TX_1024_QAM_LESS_THAN_242_TONE_RU 0x04 +#define IEEE80211_HE_PHY_CAP9_RX_1024_QAM_LESS_THAN_242_TONE_RU 0x08 + +/* 802.11ax HE TX/RX MCS NSS Support */ +#define IEEE80211_TX_RX_MCS_NSS_SUPP_HIGHEST_MCS_POS (3) +#define IEEE80211_TX_RX_MCS_NSS_SUPP_TX_BITMAP_POS (6) +#define IEEE80211_TX_RX_MCS_NSS_SUPP_RX_BITMAP_POS (11) +#define IEEE80211_TX_RX_MCS_NSS_SUPP_TX_BITMAP_MASK 0x07c0 +#define IEEE80211_TX_RX_MCS_NSS_SUPP_RX_BITMAP_MASK 0xf800 + + +/* TX/RX HE MCS Support field Highest MCS subfield encoding */ +enum ieee80211_he_highest_mcs_supported_subfield_enc { + HIGHEST_MCS_SUPPORTED_MCS7 = 0, + HIGHEST_MCS_SUPPORTED_MCS8, + HIGHEST_MCS_SUPPORTED_MCS9, + HIGHEST_MCS_SUPPORTED_MCS10, + HIGHEST_MCS_SUPPORTED_MCS11, +}; + +#define IEEE80211_HE_PPE_THRES_MAX_LEN 25 + +/** + * struct ieee80211_he_mcs_nss_supp - HE Tx/Rx HE MCS NSS Support Field + * + * This structure holds the data required for the Tx/Rx HE MCS NSS Support Field + * described in P802.11ax_D2.0 section 9.4.2.237.4 + * + * @rx_mcs_80: Rx MCS map 2 bits for each stream, total 8 streams, for channel + * widths less than 80MHz. + * @tx_mcs_80: Tx MCS map 2 bits for each stream, total 8 streams, for channel + * widths less than 80MHz. + * @rx_mcs_160: Rx MCS map 2 bits for each stream, total 8 streams, for channel + * width 160MHz. + * @tx_mcs_160: Tx MCS map 2 bits for each stream, total 8 streams, for channel + * width 160MHz. + * @rx_mcs_80p80: Rx MCS map 2 bits for each stream, total 8 streams, for + * channel width 80p80MHz. + * @tx_mcs_80p80: Tx MCS map 2 bits for each stream, total 8 streams, for + * channel width 80p80MHz. + */ +struct ieee80211_he_mcs_nss_supp { + __le16 rx_mcs_80; + __le16 tx_mcs_80; + __le16 rx_mcs_160; + __le16 tx_mcs_160; + __le16 rx_mcs_80p80; + __le16 tx_mcs_80p80; +} __packed; + +/** + * struct ieee80211_he_cap_elem - HE capabilities element + * + * This structure is the "HE capabilities element" fixed fields as + * described in P802.11ax_D2.0 section 9.4.2.237.2 and 9.4.2.237.3 + */ +struct ieee80211_he_cap_elem { + u8 mac_cap_info[6]; + u8 phy_cap_info[11]; +} __packed; + +/** + * struct ieee80211_sta_he_cap - STA's HE capabilities + * + * This structure describes most essential parameters needed + * to describe 802.11ax HE capabilities for a STA. + * + * @has_he: true iff HE data is valid. + * @he_cap_elem: Fixed portion of the HE capabilities element. + * @he_mcs_nss_supp: The supported NSS/MCS combinations. + * @ppe_thres: Holds the PPE Thresholds data. + */ +struct ieee80211_sta_he_cap { + bool has_he; + struct ieee80211_he_cap_elem he_cap_elem; + struct ieee80211_he_mcs_nss_supp he_mcs_nss_supp; + u8 ppe_thres[IEEE80211_HE_PPE_THRES_MAX_LEN]; +}; + +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 20, 0) +#define IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_COMP_SIGB 0x10 +#define IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_NON_COMP_SIGB 0x20 +#define IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_0US 0x00 +#define IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_8US 0x40 +#define IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_16US 0x80 +#define IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_RESERVED 0xc0 +#define IEEE80211_HE_PHY_CAP9_NOMIMAL_PKT_PADDING_MASK 0xc0 +#endif + + +#if defined(CONFIG_ECRNX_HE) +extern struct ieee80211_sta_he_cap ecrnx_he_cap; +#endif + +#define RW_DRV_DESCRIPTION "ESWIN 11nac driver for Linux cfg80211" +#define RW_DRV_COPYRIGHT "Copyright(c) 2015-2017 ESWIN" +#define RW_DRV_AUTHOR "ESWIN S.A.S" + +#define ECRNX_PRINT_CFM_ERR(req) \ + printk(KERN_CRIT "%s: Status Error(%d)\n", #req, (&req##_cfm)->status) + +#define ECRNX_HT_CAPABILITIES \ +{ \ + .ht_supported = true, \ + .cap = 0, \ + .ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K, \ + .ampdu_density = IEEE80211_HT_MPDU_DENSITY_16, \ + .mcs = { \ + .rx_mask = { 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, \ + .rx_highest = cpu_to_le16(65), \ + .tx_params = IEEE80211_HT_MCS_TX_DEFINED, \ + }, \ +} + +#define ECRNX_VHT_CAPABILITIES \ +{ \ + .vht_supported = false, \ + .cap = \ + (7 << IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT),\ + .vht_mcs = { \ + .rx_mcs_map = cpu_to_le16( \ + IEEE80211_VHT_MCS_SUPPORT_0_9 << 0 | \ + IEEE80211_VHT_MCS_NOT_SUPPORTED << 2 | \ + IEEE80211_VHT_MCS_NOT_SUPPORTED << 4 | \ + IEEE80211_VHT_MCS_NOT_SUPPORTED << 6 | \ + IEEE80211_VHT_MCS_NOT_SUPPORTED << 8 | \ + IEEE80211_VHT_MCS_NOT_SUPPORTED << 10 | \ + IEEE80211_VHT_MCS_NOT_SUPPORTED << 12 | \ + IEEE80211_VHT_MCS_NOT_SUPPORTED << 14), \ + .tx_mcs_map = cpu_to_le16( \ + IEEE80211_VHT_MCS_SUPPORT_0_9 << 0 | \ + IEEE80211_VHT_MCS_NOT_SUPPORTED << 2 | \ + IEEE80211_VHT_MCS_NOT_SUPPORTED << 4 | \ + IEEE80211_VHT_MCS_NOT_SUPPORTED << 6 | \ + IEEE80211_VHT_MCS_NOT_SUPPORTED << 8 | \ + IEEE80211_VHT_MCS_NOT_SUPPORTED << 10 | \ + IEEE80211_VHT_MCS_NOT_SUPPORTED << 12 | \ + IEEE80211_VHT_MCS_NOT_SUPPORTED << 14), \ + } \ +} + +#if CONFIG_ECRNX_HE +#define ECRNX_HE_CAPABILITIES \ +{ \ + .has_he = false, \ + .he_cap_elem = { \ + .mac_cap_info[0] = 0, \ + .mac_cap_info[1] = 0, \ + .mac_cap_info[2] = 0, \ + .mac_cap_info[3] = 0, \ + .mac_cap_info[4] = 0, \ + .mac_cap_info[5] = 0, \ + .phy_cap_info[0] = 0, \ + .phy_cap_info[1] = 0, \ + .phy_cap_info[2] = 0, \ + .phy_cap_info[3] = 0, \ + .phy_cap_info[4] = 0, \ + .phy_cap_info[5] = 0, \ + .phy_cap_info[6] = 0, \ + .phy_cap_info[7] = 0, \ + .phy_cap_info[8] = 0, \ + .phy_cap_info[9] = 0, \ + .phy_cap_info[10] = 0, \ + }, \ + .he_mcs_nss_supp = { \ + .rx_mcs_80 = cpu_to_le16(0xfffa), \ + .tx_mcs_80 = cpu_to_le16(0xfffa), \ + .rx_mcs_160 = cpu_to_le16(0xffff), \ + .tx_mcs_160 = cpu_to_le16(0xffff), \ + .rx_mcs_80p80 = cpu_to_le16(0xffff), \ + .tx_mcs_80p80 = cpu_to_le16(0xffff), \ + }, \ + .ppe_thres = {0x00}, \ +} +#endif + +#define RATE(_bitrate, _hw_rate, _flags) { \ + .bitrate = (_bitrate), \ + .flags = (_flags), \ + .hw_value = (_hw_rate), \ +} + +#define CHAN(_freq) { \ + .center_freq = (_freq), \ + .max_power = 30, /* FIXME */ \ +} + +#endif /* _ECRNX_DEFS_H_*/ diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_main.c b/drivers/net/wireless/eswin/fullmac/ecrnx_main.c new file mode 100644 index 000000000000..f3a87f38908e --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_main.c @@ -0,0 +1,4467 @@ +/** + ****************************************************************************** + * + * @file ecrnx_main.c + * + * @brief Entry point of the ECRNX driver + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/inetdevice.h> +#include <net/cfg80211.h> +#include <net/ip.h> +#include <linux/etherdevice.h> + +#include "ecrnx_defs.h" +#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0)) +#include <linux/if_arp.h> +#include <linux/ieee80211.h> +#endif +#include "ecrnx_msg_tx.h" +#include "ecrnx_tx.h" +#include "reg_access.h" +#include "hal_desc.h" +#include "ecrnx_debugfs.h" +#include "ecrnx_cfgfile.h" +#include "ecrnx_radar.h" +#include "ecrnx_version.h" +#ifdef CONFIG_ECRNX_BFMER +#include "ecrnx_bfmer.h" +#endif //(CONFIG_ECRNX_BFMER) +#include "ecrnx_tdls.h" +#include "ecrnx_events.h" +#include "ecrnx_compat.h" +#include "ecrnx_rx.h" + +#include "ecrnx_p2p.h" +#include "ecrnx_debugfs_custom.h" +#include "ecrnx_calibration_data.h" +#include "eswin_utils.h" +#include "ecrnx_debugfs_func.h" + + +static struct ieee80211_rate ecrnx_ratetable[] = { + RATE(10, 0x00, 0), + RATE(20, 0x01, IEEE80211_RATE_SHORT_PREAMBLE), + RATE(55, 0x02, IEEE80211_RATE_SHORT_PREAMBLE), + RATE(110, 0x03, IEEE80211_RATE_SHORT_PREAMBLE), + RATE(60, 0x04, 0), + RATE(90, 0x05, 0), + RATE(120, 0x06, 0), + RATE(180, 0x07, 0), + RATE(240, 0x08, 0), + RATE(360, 0x09, 0), + RATE(480, 0x0A, 0), + RATE(540, 0x0B, 0), +}; + +/* The channels indexes here are not used anymore */ +static struct ieee80211_channel ecrnx_2ghz_channels[] = { + CHAN(2412), + CHAN(2417), + CHAN(2422), + CHAN(2427), + CHAN(2432), + CHAN(2437), + CHAN(2442), + CHAN(2447), + CHAN(2452), + CHAN(2457), + CHAN(2462), + CHAN(2467), + CHAN(2472), + CHAN(2484), + // Extra channels defined only to be used for PHY measures. + // Enabled only if custregd and custchan parameters are set + CHAN(2390), + CHAN(2400), + CHAN(2410), + CHAN(2420), + CHAN(2430), + CHAN(2440), + CHAN(2450), + CHAN(2460), + CHAN(2470), + CHAN(2480), + CHAN(2490), + CHAN(2500), + CHAN(2510), +}; + +#ifdef CONFIG_ECRNX_5G +static struct ieee80211_channel ecrnx_5ghz_channels[] = { + CHAN(5180), // 36 - 20MHz + CHAN(5200), // 40 - 20MHz + CHAN(5220), // 44 - 20MHz + CHAN(5240), // 48 - 20MHz + CHAN(5260), // 52 - 20MHz + CHAN(5280), // 56 - 20MHz + CHAN(5300), // 60 - 20MHz + CHAN(5320), // 64 - 20MHz + CHAN(5500), // 100 - 20MHz + CHAN(5520), // 104 - 20MHz + CHAN(5540), // 108 - 20MHz + CHAN(5560), // 112 - 20MHz + CHAN(5580), // 116 - 20MHz + CHAN(5600), // 120 - 20MHz + CHAN(5620), // 124 - 20MHz + CHAN(5640), // 128 - 20MHz + CHAN(5660), // 132 - 20MHz + CHAN(5680), // 136 - 20MHz + CHAN(5700), // 140 - 20MHz + CHAN(5720), // 144 - 20MHz + CHAN(5745), // 149 - 20MHz + CHAN(5765), // 153 - 20MHz + CHAN(5785), // 157 - 20MHz + CHAN(5805), // 161 - 20MHz + CHAN(5825), // 165 - 20MHz + // Extra channels defined only to be used for PHY measures. + // Enabled only if custregd and custchan parameters are set + CHAN(5190), + CHAN(5210), + CHAN(5230), + CHAN(5250), + CHAN(5270), + CHAN(5290), + CHAN(5310), + CHAN(5330), + CHAN(5340), + CHAN(5350), + CHAN(5360), + CHAN(5370), + CHAN(5380), + CHAN(5390), + CHAN(5400), + CHAN(5410), + CHAN(5420), + CHAN(5430), + CHAN(5440), + CHAN(5450), + CHAN(5460), + CHAN(5470), + CHAN(5480), + CHAN(5490), + CHAN(5510), + CHAN(5530), + CHAN(5550), + CHAN(5570), + CHAN(5590), + CHAN(5610), + CHAN(5630), + CHAN(5650), + CHAN(5670), + CHAN(5690), + CHAN(5710), + CHAN(5730), + CHAN(5750), + CHAN(5760), + CHAN(5770), + CHAN(5780), + CHAN(5790), + CHAN(5800), + CHAN(5810), + CHAN(5820), + CHAN(5830), + CHAN(5840), + CHAN(5850), + CHAN(5860), + CHAN(5870), + CHAN(5880), + CHAN(5890), + CHAN(5900), + CHAN(5910), + CHAN(5920), + CHAN(5930), + CHAN(5940), + CHAN(5950), + CHAN(5960), + CHAN(5970), +}; +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0) +#if CONFIG_ECRNX_HE +static struct ieee80211_sband_iftype_data ecrnx_he_capa = { + .types_mask = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP), + .he_cap = ECRNX_HE_CAPABILITIES, +}; +#endif +#endif + +static struct ieee80211_supported_band ecrnx_band_2GHz = { + .channels = ecrnx_2ghz_channels, + .n_channels = ARRAY_SIZE(ecrnx_2ghz_channels) - 13, // -13 to exclude extra channels + .bitrates = ecrnx_ratetable, + .n_bitrates = ARRAY_SIZE(ecrnx_ratetable), + .ht_cap = ECRNX_HT_CAPABILITIES, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0) +#if CONFIG_ECRNX_HE + .iftype_data = &ecrnx_he_capa, + .n_iftype_data = 1, +#endif +#endif +}; + +#ifdef CONFIG_ECRNX_5G +static struct ieee80211_supported_band ecrnx_band_5GHz = { + .channels = ecrnx_5ghz_channels, + .n_channels = ARRAY_SIZE(ecrnx_5ghz_channels) - 59, // -59 to exclude extra channels + .bitrates = &ecrnx_ratetable[4], + .n_bitrates = ARRAY_SIZE(ecrnx_ratetable) - 4, + .ht_cap = ECRNX_HT_CAPABILITIES, + .vht_cap = ECRNX_VHT_CAPABILITIES, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0) +#if CONFIG_ECRNX_HE + .iftype_data = &ecrnx_he_capa, + .n_iftype_data = 1, +#endif +#endif +}; +#endif + +static struct ieee80211_iface_limit ecrnx_limits[] = { + { .max = NX_VIRT_DEV_MAX, .types = BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_STATION)} +}; + +static struct ieee80211_iface_limit ecrnx_limits_dfs[] = { + { .max = NX_VIRT_DEV_MAX, .types = BIT(NL80211_IFTYPE_AP)} +}; + +static const struct ieee80211_iface_combination ecrnx_combinations[] = { + { + .limits = ecrnx_limits, + .n_limits = ARRAY_SIZE(ecrnx_limits), + .num_different_channels = NX_CHAN_CTXT_CNT, + .max_interfaces = NX_VIRT_DEV_MAX, + }, + /* Keep this combination as the last one */ + { + .limits = ecrnx_limits_dfs, + .n_limits = ARRAY_SIZE(ecrnx_limits_dfs), + .num_different_channels = 1, + .max_interfaces = NX_VIRT_DEV_MAX, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + .radar_detect_widths = (BIT(NL80211_CHAN_WIDTH_20_NOHT) | + BIT(NL80211_CHAN_WIDTH_20) | + BIT(NL80211_CHAN_WIDTH_40) | + BIT(NL80211_CHAN_WIDTH_80)), +#endif + } +}; + +/* There isn't a lot of sense in it, but you can transmit anything you like */ +static struct ieee80211_txrx_stypes +ecrnx_default_mgmt_stypes[NUM_NL80211_IFTYPES] = { + [NL80211_IFTYPE_STATION] = { + .tx = 0xffff, + .rx = (BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4)), + }, + [NL80211_IFTYPE_AP] = { + .tx = 0xffff, + .rx = (BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | + BIT(IEEE80211_STYPE_DISASSOC >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4) | + BIT(IEEE80211_STYPE_DEAUTH >> 4) | + BIT(IEEE80211_STYPE_ACTION >> 4)), + }, + [NL80211_IFTYPE_AP_VLAN] = { + /* copy AP */ + .tx = 0xffff, + .rx = (BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | + BIT(IEEE80211_STYPE_DISASSOC >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4) | + BIT(IEEE80211_STYPE_DEAUTH >> 4) | + BIT(IEEE80211_STYPE_ACTION >> 4)), + }, + [NL80211_IFTYPE_P2P_CLIENT] = { + .tx = 0xffff, + .rx = (BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4)), + }, + [NL80211_IFTYPE_P2P_GO] = { + .tx = 0xffff, + .rx = (BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | + BIT(IEEE80211_STYPE_DISASSOC >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4) | + BIT(IEEE80211_STYPE_DEAUTH >> 4) | + BIT(IEEE80211_STYPE_ACTION >> 4)), + }, + [NL80211_IFTYPE_P2P_DEVICE] = { + .tx = 0xffff, + .rx = (BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4)), + }, + [NL80211_IFTYPE_MESH_POINT] = { + .tx = 0xffff, + .rx = (BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4) | + BIT(IEEE80211_STYPE_DEAUTH >> 4)), + }, +}; + + +static u32 cipher_suites[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, + 0, // reserved entries to enable AES-CMAC and/or SMS4 + 0, + 0, + 0, + 0, +}; +#define NB_RESERVED_CIPHER 5; + +static const int ecrnx_ac2hwq[1][NL80211_NUM_ACS] = { + { + [NL80211_TXQ_Q_VO] = ECRNX_HWQ_VO, + [NL80211_TXQ_Q_VI] = ECRNX_HWQ_VI, + [NL80211_TXQ_Q_BE] = ECRNX_HWQ_BE, + [NL80211_TXQ_Q_BK] = ECRNX_HWQ_BK + } +}; + +const int ecrnx_tid2hwq[IEEE80211_NUM_TIDS] = { + ECRNX_HWQ_BE, + ECRNX_HWQ_BK, + ECRNX_HWQ_BK, + ECRNX_HWQ_BE, + ECRNX_HWQ_VI, + ECRNX_HWQ_VI, + ECRNX_HWQ_VO, + ECRNX_HWQ_VO, + /* TID_8 is used for management frames */ + ECRNX_HWQ_VO, + /* At the moment, all others TID are mapped to BE */ + ECRNX_HWQ_BE, + ECRNX_HWQ_BE, + ECRNX_HWQ_BE, + ECRNX_HWQ_BE, + ECRNX_HWQ_BE, + ECRNX_HWQ_BE, + ECRNX_HWQ_BE, +}; + +static const int ecrnx_hwq2uapsd[NL80211_NUM_ACS] = { + [ECRNX_HWQ_VO] = IEEE80211_WMM_IE_STA_QOSINFO_AC_VO, + [ECRNX_HWQ_VI] = IEEE80211_WMM_IE_STA_QOSINFO_AC_VI, + [ECRNX_HWQ_BE] = IEEE80211_WMM_IE_STA_QOSINFO_AC_BE, + [ECRNX_HWQ_BK] = IEEE80211_WMM_IE_STA_QOSINFO_AC_BK, +}; + +/********************************************************************* + * helper + *********************************************************************/ +struct ecrnx_sta *ecrnx_get_sta(struct ecrnx_hw *ecrnx_hw, const u8 *mac_addr) +{ + int i; + + for (i = 0; i < NX_REMOTE_STA_MAX; i++) { + struct ecrnx_sta *sta = &ecrnx_hw->sta_table[i]; + if (sta->valid && (memcmp(mac_addr, &sta->mac_addr, 6) == 0)) + return sta; + } + + return NULL; +} + +void ecrnx_enable_wapi(struct ecrnx_hw *ecrnx_hw) +{ + cipher_suites[ecrnx_hw->wiphy->n_cipher_suites] = WLAN_CIPHER_SUITE_SMS4; + ecrnx_hw->wiphy->n_cipher_suites ++; + ecrnx_hw->wiphy->flags |= WIPHY_FLAG_CONTROL_PORT_PROTOCOL; +} + +void ecrnx_enable_mfp(struct ecrnx_hw *ecrnx_hw) +{ + cipher_suites[ecrnx_hw->wiphy->n_cipher_suites] = WLAN_CIPHER_SUITE_AES_CMAC; + ecrnx_hw->wiphy->n_cipher_suites ++; +} + +void ecrnx_enable_gcmp(struct ecrnx_hw *ecrnx_hw) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)) + cipher_suites[ecrnx_hw->wiphy->n_cipher_suites++] = WLAN_CIPHER_SUITE_CCMP_256; + cipher_suites[ecrnx_hw->wiphy->n_cipher_suites++] = WLAN_CIPHER_SUITE_GCMP; + cipher_suites[ecrnx_hw->wiphy->n_cipher_suites++] = WLAN_CIPHER_SUITE_GCMP_256; +#endif +} +u8 *ecrnx_build_bcn(struct ecrnx_bcn *bcn, struct cfg80211_beacon_data *new) +{ + u8 *buf, *pos; + + if (new->head) { + u8 *head = kmalloc(new->head_len, GFP_KERNEL); + + if (!head) + return NULL; + + if (bcn->head) + kfree(bcn->head); + + bcn->head = head; + bcn->head_len = new->head_len; + memcpy(bcn->head, new->head, new->head_len); + } + if (new->tail) { + u8 *tail = kmalloc(new->tail_len, GFP_KERNEL); + + if (!tail) + return NULL; + + if (bcn->tail) + kfree(bcn->tail); + + bcn->tail = tail; + bcn->tail_len = new->tail_len; + memcpy(bcn->tail, new->tail, new->tail_len); + } + + if (!bcn->head) + return NULL; + + bcn->tim_len = 6; + bcn->len = bcn->head_len + bcn->tail_len + bcn->ies_len + bcn->tim_len; + + buf = kmalloc(bcn->len, GFP_KERNEL); + if (!buf) + return NULL; + + // Build the beacon buffer + pos = buf; + memcpy(pos, bcn->head, bcn->head_len); + pos += bcn->head_len; + *pos++ = WLAN_EID_TIM; + *pos++ = 4; + *pos++ = 0; + *pos++ = bcn->dtim; + *pos++ = 0; + *pos++ = 0; + if (bcn->tail) { + memcpy(pos, bcn->tail, bcn->tail_len); + pos += bcn->tail_len; + } + if (bcn->ies) { + memcpy(pos, bcn->ies, bcn->ies_len); + } + + return buf; +} + + +static void ecrnx_del_bcn(struct ecrnx_bcn *bcn) +{ + if (bcn->head) { + kfree(bcn->head); + bcn->head = NULL; + } + bcn->head_len = 0; + + if (bcn->tail) { + kfree(bcn->tail); + bcn->tail = NULL; + } + bcn->tail_len = 0; + + if (bcn->ies) { + kfree(bcn->ies); + bcn->ies = NULL; + } + bcn->ies_len = 0; + bcn->tim_len = 0; + bcn->dtim = 0; + bcn->len = 0; +} + +/** + * Link channel ctxt to a vif and thus increments count for this context. + */ +void ecrnx_chanctx_link(struct ecrnx_vif *vif, u8 ch_idx, + struct cfg80211_chan_def *chandef) +{ + struct ecrnx_chanctx *ctxt; + + if (ch_idx >= NX_CHAN_CTXT_CNT) { + WARN(1, "Invalid channel ctxt id %d", ch_idx); + return; + } + + vif->ch_index = ch_idx; + ctxt = &vif->ecrnx_hw->chanctx_table[ch_idx]; + ctxt->count++; + + // For now chandef is NULL for STATION interface + if (chandef) { + if (!ctxt->chan_def.chan) + ctxt->chan_def = *chandef; + else { + // TODO. check that chandef is the same as the one already + // set for this ctxt + } + } +} + +/** + * Unlink channel ctxt from a vif and thus decrements count for this context + */ +void ecrnx_chanctx_unlink(struct ecrnx_vif *vif) +{ + struct ecrnx_chanctx *ctxt; + + if (vif->ch_index == ECRNX_CH_NOT_SET) + return; + + ctxt = &vif->ecrnx_hw->chanctx_table[vif->ch_index]; + + if (ctxt->count == 0) { + WARN(1, "Chan ctxt ref count is already 0"); + } else { + ctxt->count--; + } + + if (ctxt->count == 0) { + if (vif->ch_index == vif->ecrnx_hw->cur_chanctx) { + /* If current chan ctxt is no longer linked to a vif + disable radar detection (no need to check if it was activated) */ + ecrnx_radar_detection_enable(&vif->ecrnx_hw->radar, + ECRNX_RADAR_DETECT_DISABLE, + ECRNX_RADAR_RIU); + } + /* set chan to null, so that if this ctxt is relinked to a vif that + don't have channel information, don't use wrong information */ + ctxt->chan_def.chan = NULL; + } + vif->ch_index = ECRNX_CH_NOT_SET; +} + +int ecrnx_chanctx_valid(struct ecrnx_hw *ecrnx_hw, u8 ch_idx) +{ + if (ch_idx >= NX_CHAN_CTXT_CNT || + ecrnx_hw->chanctx_table[ch_idx].chan_def.chan == NULL) { + return 0; + } + + return 1; +} + +static void ecrnx_del_csa(struct ecrnx_vif *vif) +{ + struct ecrnx_hw *ecrnx_hw = vif->ecrnx_hw; + struct ecrnx_csa *csa = vif->ap.csa; + + if (!csa) + return; + + ecrnx_ipc_elem_var_deallocs(ecrnx_hw, &csa->elem); + ecrnx_del_bcn(&csa->bcn); + kfree(csa); + vif->ap.csa = NULL; +} + +static void ecrnx_csa_finish(struct work_struct *ws) +{ + struct ecrnx_csa *csa = container_of(ws, struct ecrnx_csa, work); + struct ecrnx_vif *vif = csa->vif; + struct ecrnx_hw *ecrnx_hw = vif->ecrnx_hw; + int error = csa->status; + + if (!error) + error = ecrnx_send_bcn_change(ecrnx_hw, vif->vif_index, csa->elem.dma_addr, + csa->bcn.len, csa->bcn.head_len, + csa->bcn.tim_len, NULL); + + if (error){ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)) + cfg80211_stop_iface(ecrnx_hw->wiphy, &vif->wdev, GFP_KERNEL); +#else + cfg80211_disconnected(vif->ndev, 0, NULL, 0, 0, GFP_KERNEL); +#endif + } + else { + mutex_lock(&vif->wdev.mtx); + __acquire(&vif->wdev.mtx); + spin_lock_bh(&ecrnx_hw->cb_lock); + ecrnx_chanctx_unlink(vif); + ecrnx_chanctx_link(vif, csa->ch_idx, &csa->chandef); + if (ecrnx_hw->cur_chanctx == csa->ch_idx) { + ecrnx_radar_detection_enable_on_cur_channel(ecrnx_hw); + ecrnx_txq_vif_start(vif, ECRNX_TXQ_STOP_CHAN, ecrnx_hw); + } else + ecrnx_txq_vif_stop(vif, ECRNX_TXQ_STOP_CHAN, ecrnx_hw); + spin_unlock_bh(&ecrnx_hw->cb_lock); + cfg80211_ch_switch_notify(vif->ndev, &csa->chandef); + mutex_unlock(&vif->wdev.mtx); + __release(&vif->wdev.mtx); + } + ecrnx_del_csa(vif); +} + +/** + * ecrnx_external_auth_enable - Enable external authentication on a vif + * + * @vif: VIF on which external authentication must be enabled + * + * External authentication requires to start TXQ for unknown STA in + * order to send auth frame pusehd by user space. + * Note: It is assumed that fw is on the correct channel. + */ +void ecrnx_external_auth_enable(struct ecrnx_vif *vif) +{ + vif->sta.flags |= ECRNX_STA_EXT_AUTH; + ecrnx_txq_unk_vif_init(vif); + ecrnx_txq_start(ecrnx_txq_vif_get(vif, NX_UNK_TXQ_TYPE), 0); +} + +/** + * ecrnx_external_auth_disable - Disable external authentication on a vif + * + * @vif: VIF on which external authentication must be disabled + */ +void ecrnx_external_auth_disable(struct ecrnx_vif *vif) +{ + if (!(vif->sta.flags & ECRNX_STA_EXT_AUTH)) + return; + + vif->sta.flags &= ~ECRNX_STA_EXT_AUTH; + ecrnx_txq_unk_vif_deinit(vif); +} + +/** + * ecrnx_update_mesh_power_mode - + * + * @vif: mesh VIF for which power mode is updated + * + * Does nothing if vif is not a mesh point interface. + * Since firmware doesn't support one power save mode per link select the + * most "active" power mode among all mesh links. + * Indeed as soon as we have to be active on one link we might as well be + * active on all links. + * + * If there is no link then the power mode for next peer is used; + */ +void ecrnx_update_mesh_power_mode(struct ecrnx_vif *vif) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + enum nl80211_mesh_power_mode mesh_pm; + struct ecrnx_sta *sta; + struct mesh_config mesh_conf; + struct mesh_update_cfm cfm; + u32 mask; + + if (ECRNX_VIF_TYPE(vif) != NL80211_IFTYPE_MESH_POINT) + return; + + if (list_empty(&vif->ap.sta_list)) { + mesh_pm = vif->ap.next_mesh_pm; + } else { + mesh_pm = NL80211_MESH_POWER_DEEP_SLEEP; + list_for_each_entry(sta, &vif->ap.sta_list, list) { + if (sta->valid && (sta->mesh_pm < mesh_pm)) { + mesh_pm = sta->mesh_pm; + } + } + } + + if (mesh_pm == vif->ap.mesh_pm) + return; + + mask = BIT(NL80211_MESHCONF_POWER_MODE - 1); + mesh_conf.power_mode = mesh_pm; + if (ecrnx_send_mesh_update_req(vif->ecrnx_hw, vif, mask, &mesh_conf, &cfm) || + cfm.status) + return; + + vif->ap.mesh_pm = mesh_pm; +#endif +} + +void ecrnx_save_assoc_info_for_ft(struct ecrnx_vif *vif, + struct cfg80211_connect_params *sme) +{ + int ies_len = sme->ie_len + sme->ssid_len + 2; + u8 *pos; + if (!vif->sta.ft_assoc_ies) { + if (!cfg80211_find_ie(WLAN_EID_MOBILITY_DOMAIN, sme->ie, sme->ie_len)) + return; + vif->sta.ft_assoc_ies_len = ies_len; + vif->sta.ft_assoc_ies = kmalloc(ies_len, GFP_KERNEL); + } else if (vif->sta.ft_assoc_ies_len < ies_len) { + kfree(vif->sta.ft_assoc_ies); + vif->sta.ft_assoc_ies = kmalloc(ies_len, GFP_KERNEL); + } + if (!vif->sta.ft_assoc_ies) + return; + pos = vif->sta.ft_assoc_ies; + *pos++ = WLAN_EID_SSID; + *pos++ = sme->ssid_len; + memcpy(pos, sme->ssid, sme->ssid_len); + pos += sme->ssid_len; + memcpy(pos, sme->ie, sme->ie_len); + vif->sta.ft_assoc_ies_len = ies_len; +} +/** + * ecrnx_rsne_to_connect_params - Initialise cfg80211_connect_params from + * RSN element. + * + * @rsne: RSN element + * @sme: Structure cfg80211_connect_params to initialize + * + * The goal is only to initialize enough for ecrnx_send_sm_connect_req + */ +int ecrnx_rsne_to_connect_params(const struct ecrnx_element *rsne, + struct cfg80211_connect_params *sme) +{ + int len = rsne->datalen; + int clen; + const u8 *pos = rsne->data ; + if (len < 8) + return 1; + + sme->crypto.control_port_no_encrypt = false; + sme->crypto.control_port = true; + sme->crypto.control_port_ethertype = cpu_to_be16(ETH_P_PAE); + + pos += 2; + sme->crypto.cipher_group = ntohl(*((u32 *)pos)); + pos += 4; + clen = le16_to_cpu(*((u16 *)pos)) * 4; + pos += 2; + len -= 8; + if (len < clen + 2) + return 1; + // only need one cipher suite + sme->crypto.n_ciphers_pairwise = 1; + sme->crypto.ciphers_pairwise[0] = ntohl(*((u32 *)pos)); + pos += clen; + len -= clen; + + // no need for AKM + clen = le16_to_cpu(*((u16 *)pos)) * 4; + pos += 2; + len -= 2; + if (len < clen) + return 1; + pos += clen; + len -= clen; + + if (len < 4) + return 0; + + pos += 2; + clen = le16_to_cpu(*((u16 *)pos)) * 16; + len -= 4; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + if (len > clen) + sme->mfp = NL80211_MFP_REQUIRED; +#endif + + return 0; +} + +/********************************************************************* + * netdev callbacks + ********************************************************************/ +/** + * int (*ndo_open)(struct net_device *dev); + * This function is called when network device transistions to the up + * state. + * + * - Start FW if this is the first interface opened + * - Add interface at fw level + */ +static int ecrnx_open(struct net_device *dev) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct ecrnx_hw *ecrnx_hw = ecrnx_vif->ecrnx_hw; + struct mm_add_if_cfm add_if_cfm; + int error = 0; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + // Check if it is the first opened VIF + if (ecrnx_hw->vif_started == 0) + { + // Start the FW + if ((error = ecrnx_send_start(ecrnx_hw))) + return error; + + /* Device is now started */ + set_bit(ECRNX_DEV_STARTED, &ecrnx_hw->flags); + } + + if (ecrnx_vif->up) { + netdev_info(dev, "Started repeatedly"); + return error; + } + + if (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_AP_VLAN) { + /* For AP_vlan use same fw and drv indexes. We ensure that this index + will not be used by fw for another vif by taking index >= NX_VIRT_DEV_MAX */ + add_if_cfm.inst_nbr = ecrnx_vif->drv_vif_index; + netif_tx_stop_all_queues(dev); + } else { + /* Forward the information to the LMAC, + * p2p value not used in FMAC configuration, iftype is sufficient */ + if ((error = ecrnx_send_add_if(ecrnx_hw, dev->dev_addr, + ECRNX_VIF_TYPE(ecrnx_vif), false, &add_if_cfm))) + return error; + + if (add_if_cfm.status != 0) { + ECRNX_PRINT_CFM_ERR(add_if); + return -EIO; + } + } + + /* Save the index retrieved from LMAC */ + spin_lock_bh(&ecrnx_hw->cb_lock); + ecrnx_vif->vif_index = add_if_cfm.inst_nbr; + ecrnx_vif->up = true; + ecrnx_hw->vif_started++; + ecrnx_hw->vif_table[add_if_cfm.inst_nbr] = ecrnx_vif; + memset(ecrnx_hw->vif_table[add_if_cfm.inst_nbr]->rx_pn, 0, TID_MAX * sizeof(uint64_t)); + spin_unlock_bh(&ecrnx_hw->cb_lock); + + if (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_MONITOR) { + ecrnx_hw->monitor_vif = ecrnx_vif->vif_index; + if (ecrnx_vif->ch_index != ECRNX_CH_NOT_SET) { + //Configure the monitor channel + error = ecrnx_send_config_monitor_req(ecrnx_hw, + &ecrnx_hw->chanctx_table[ecrnx_vif->ch_index].chan_def, + NULL); + } + } + + netif_carrier_off(dev); + + return error; +} + +/** + * int (*ndo_stop)(struct net_device *dev); + * This function is called when network device transistions to the down + * state. + * + * - Remove interface at fw level + * - Reset FW if this is the last interface opened + */ +static int ecrnx_close(struct net_device *dev) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct ecrnx_hw *ecrnx_hw = ecrnx_vif->ecrnx_hw; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + netdev_info(dev, "CLOSE"); + + ecrnx_radar_cancel_cac(&ecrnx_hw->radar); + + spin_lock_bh(&ecrnx_hw->scan_req_lock); + /* Abort scan request on the vif */ + if (ecrnx_hw->scan_request && + ecrnx_hw->scan_request->wdev == &ecrnx_vif->wdev) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) + struct cfg80211_scan_info info = { + .aborted = true, + }; + + cfg80211_scan_done(ecrnx_hw->scan_request, &info); +#else + cfg80211_scan_done(ecrnx_hw->scan_request, true); +#endif + ecrnx_hw->scan_request = NULL; + } + + spin_unlock_bh(&ecrnx_hw->scan_req_lock); + ecrnx_send_remove_if(ecrnx_hw, ecrnx_vif->vif_index); + + if (ecrnx_hw->roc && (ecrnx_hw->roc->vif == ecrnx_vif)) { + kfree(ecrnx_hw->roc); + /* Initialize RoC element pointer to NULL, indicate that RoC can be started */ + ecrnx_hw->roc = NULL; + } + + /* Ensure that we won't process disconnect ind */ + spin_lock_bh(&ecrnx_hw->cb_lock); + + ecrnx_vif->up = false; + if (netif_carrier_ok(dev)) { + if (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_STATION || + ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_P2P_CLIENT) { + if (ecrnx_vif->sta.ft_assoc_ies) { + kfree(ecrnx_vif->sta.ft_assoc_ies); + ecrnx_vif->sta.ft_assoc_ies = NULL; + ecrnx_vif->sta.ft_assoc_ies_len = 0; + } + cfg80211_disconnected(dev, WLAN_REASON_DEAUTH_LEAVING, + NULL, 0, true, GFP_ATOMIC); + if (ecrnx_vif->sta.ap) { + ecrnx_txq_sta_deinit(ecrnx_hw, ecrnx_vif->sta.ap); + ecrnx_txq_tdls_vif_deinit(ecrnx_vif); + } + netif_tx_stop_all_queues(dev); + netif_carrier_off(dev); + } else if (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_AP_VLAN) { + netif_carrier_off(dev); + } else { + netdev_warn(dev, "AP not stopped when disabling interface"); + } + } + + ecrnx_hw->vif_table[ecrnx_vif->vif_index] = NULL; + spin_unlock_bh(&ecrnx_hw->cb_lock); + + ecrnx_chanctx_unlink(ecrnx_vif); + + if (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_MONITOR) + ecrnx_hw->monitor_vif = ECRNX_INVALID_VIF; + + ecrnx_hw->vif_started--; + if (ecrnx_hw->vif_started == 0) { +#ifndef CONFIG_ECRNX_ESWIN + /* This also lets both ipc sides remain in sync before resetting */ + ecrnx_ipc_tx_drain(ecrnx_hw); +#endif + ecrnx_send_reset(ecrnx_hw); + + // Set parameters to firmware + ecrnx_send_me_config_req(ecrnx_hw); + + // Set channel parameters to firmware + ecrnx_send_me_chan_config_req(ecrnx_hw); + + clear_bit(ECRNX_DEV_STARTED, &ecrnx_hw->flags); + } + + return 0; +} + +/** + * struct net_device_stats* (*ndo_get_stats)(struct net_device *dev); + * Called when a user wants to get the network device usage + * statistics. Drivers must do one of the following: + * 1. Define @ndo_get_stats64 to fill in a zero-initialised + * rtnl_link_stats64 structure passed by the caller. + * 2. Define @ndo_get_stats to update a net_device_stats structure + * (which should normally be dev->stats) and return a pointer to + * it. The structure may be changed asynchronously only if each + * field is written atomically. + * 3. Update dev->stats asynchronously and atomically, and define + * neither operation. + */ +static struct net_device_stats *ecrnx_get_stats(struct net_device *dev) +{ + struct ecrnx_vif *vif = netdev_priv(dev); + + return &vif->net_stats; +} + +/** + * u16 (*ndo_select_queue)(struct net_device *dev, struct sk_buff *skb, + * struct net_device *sb_dev); + * Called to decide which queue to when device supports multiple + * transmit queues. + */ +u16 ecrnx_select_queue(struct net_device *dev, struct sk_buff *skb, + struct net_device *sb_dev) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + return ecrnx_select_txq(ecrnx_vif, skb); +} + +/** + * int (*ndo_set_mac_address)(struct net_device *dev, void *addr); + * This function is called when the Media Access Control address + * needs to be changed. If this interface is not defined, the + * mac address can not be changed. + */ +static int ecrnx_set_mac_address(struct net_device *dev, void *addr) +{ + struct sockaddr *sa = addr; + int ret; + + ret = eth_mac_addr(dev, sa); + + return ret; +} + +int ecrnx_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + //struct iwreq *wrq = (struct iwreq *)rq; + int ret = 0; + + switch (cmd) { + case (SIOCDEVPRIVATE+1): + //ret = ecrnx_android_priv_cmd(dev, rq, cmd); + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} + +static const struct net_device_ops ecrnx_netdev_ops = { + .ndo_open = ecrnx_open, + .ndo_stop = ecrnx_close, + .ndo_start_xmit = ecrnx_start_xmit, + .ndo_get_stats = ecrnx_get_stats, + .ndo_select_queue = ecrnx_select_queue, + .ndo_set_mac_address = ecrnx_set_mac_address, + .ndo_do_ioctl = ecrnx_ioctl, +// .ndo_set_features = ecrnx_set_features, +// .ndo_set_rx_mode = ecrnx_set_multicast_list, +}; + +static const struct net_device_ops ecrnx_netdev_monitor_ops = { + .ndo_open = ecrnx_open, + .ndo_stop = ecrnx_close, + .ndo_get_stats = ecrnx_get_stats, + .ndo_set_mac_address = ecrnx_set_mac_address, +}; + +#ifdef CONFIG_WIRELESS_EXT +extern const struct iw_handler_def ecrnx_wext_handler_def; +#endif + +static void ecrnx_netdev_setup(struct net_device *dev) +{ + ether_setup(dev); + dev->priv_flags &= ~IFF_TX_SKB_SHARING; + dev->netdev_ops = &ecrnx_netdev_ops; +#ifdef CONFIG_WIRELESS_EXT + dev->wireless_handlers = &ecrnx_wext_handler_def; +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0) + dev->destructor = free_netdev; +#else + dev->needs_free_netdev = true; +#endif + dev->watchdog_timeo = ECRNX_TX_LIFETIME_MS; + dev->needed_headroom = ECRNX_TX_MAX_HEADROOM; + +#ifdef CONFIG_ECRNX_AMSDUS_TX + dev->needed_headroom = max(dev->needed_headroom, + (unsigned short)(sizeof(struct ecrnx_amsdu_txhdr) + + sizeof(struct ethhdr) + 4 + + sizeof(rfc1042_header) + 2)); +#endif /* CONFIG_ECRNX_AMSDUS_TX */ + + dev->hw_features = 0; +} + +/********************************************************************* + * Cfg80211 callbacks (and helper) + *********************************************************************/ +static struct wireless_dev *ecrnx_interface_add(struct ecrnx_hw *ecrnx_hw, + const char *name, + unsigned char name_assign_type, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct net_device *ndev; + struct ecrnx_vif *vif; + int min_idx, max_idx; + int vif_idx = -1; + int i; + + // Look for an available VIF + if (type == NL80211_IFTYPE_AP_VLAN) { + min_idx = NX_VIRT_DEV_MAX; + max_idx = NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX; + } else { + min_idx = 0; + max_idx = NX_VIRT_DEV_MAX; + } + + for (i = min_idx; i < max_idx; i++) { + if ((ecrnx_hw->avail_idx_map) & BIT(i)) { + vif_idx = i; + break; + } + } + if (vif_idx < 0) + return NULL; + + #ifndef CONFIG_ECRNX_MON_DATA + list_for_each_entry(vif, &ecrnx_hw->vifs, list) { + // Check if monitor interface already exists or type is monitor + if ((ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_MONITOR) || + (type == NL80211_IFTYPE_MONITOR)) { + wiphy_err(ecrnx_hw->wiphy, + "Monitor+Data interface support (MON_DATA) disabled\n"); + return NULL; + } + } + #endif + + ndev = alloc_netdev_mqs(sizeof(*vif), name, name_assign_type, + ecrnx_netdev_setup, NX_NB_NDEV_TXQ, 1); + if (!ndev) + return NULL; + + vif = netdev_priv(ndev); + ndev->ieee80211_ptr = &vif->wdev; + vif->wdev.wiphy = ecrnx_hw->wiphy; + vif->ecrnx_hw = ecrnx_hw; + vif->ndev = ndev; + vif->drv_vif_index = vif_idx; + SET_NETDEV_DEV(ndev, wiphy_dev(vif->wdev.wiphy)); + vif->wdev.netdev = ndev; + vif->wdev.iftype = type; + vif->up = false; + vif->ch_index = ECRNX_CH_NOT_SET; + vif->generation = 0; + memset(&vif->net_stats, 0, sizeof(vif->net_stats)); + memset(vif->rx_pn, 0, TID_MAX * sizeof(uint64_t)); + + switch (type) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + vif->sta.flags = 0; + vif->sta.ap = NULL; + vif->sta.tdls_sta = NULL; + vif->sta.ft_assoc_ies = NULL; + vif->sta.ft_assoc_ies_len = 0; + break; + case NL80211_IFTYPE_MESH_POINT: + INIT_LIST_HEAD(&vif->ap.mpath_list); + INIT_LIST_HEAD(&vif->ap.proxy_list); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + vif->ap.mesh_pm = NL80211_MESH_POWER_ACTIVE; + vif->ap.next_mesh_pm = NL80211_MESH_POWER_ACTIVE; +#endif + break; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + INIT_LIST_HEAD(&vif->ap.sta_list); + memset(&vif->ap.bcn, 0, sizeof(vif->ap.bcn)); + vif->ap.flags = 0; + break; + case NL80211_IFTYPE_AP_VLAN: + { + struct ecrnx_vif *master_vif; + bool found = false; + list_for_each_entry(master_vif, &ecrnx_hw->vifs, list) { + if ((ECRNX_VIF_TYPE(master_vif) == NL80211_IFTYPE_AP) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + && !(!memcmp(master_vif->ndev->dev_addr, params->macaddr, + ETH_ALEN)) +#endif + ) { + found=true; + break; + } + } + + if (!found) + goto err; + + vif->ap_vlan.master = master_vif; + vif->ap_vlan.sta_4a = NULL; + break; + } + case NL80211_IFTYPE_MONITOR: + ndev->type = ARPHRD_IEEE80211_RADIOTAP; + ndev->netdev_ops = &ecrnx_netdev_monitor_ops; + break; + default: + break; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + if (type == NL80211_IFTYPE_AP_VLAN) + memcpy(ndev->dev_addr, params->macaddr, ETH_ALEN); + else +#endif + { + memcpy(ndev->dev_addr, ecrnx_hw->wiphy->perm_addr, ETH_ALEN); + ndev->dev_addr[5] ^= vif_idx; + } + + if (params) { + vif->use_4addr = params->use_4addr; + ndev->ieee80211_ptr->use_4addr = params->use_4addr; + } else + vif->use_4addr = false; + + + if (register_netdevice(ndev)) + goto err; + + spin_lock_bh(&ecrnx_hw->cb_lock); + list_add_tail(&vif->list, &ecrnx_hw->vifs); + spin_unlock_bh(&ecrnx_hw->cb_lock); + ecrnx_hw->avail_idx_map &= ~BIT(vif_idx); + +//#if defined(CONFIG_ECRNX_ESWIN_SDIO) || defined(CONFIG_ECRNX_ESWIN_USB) + init_waitqueue_head(&vif->rxdataq); +//#endif + + return &vif->wdev; + +err: + free_netdev(ndev); + return NULL; +} + + +/* + * @brief Retrieve the ecrnx_sta object allocated for a given MAC address + * and a given role. + */ +static struct ecrnx_sta *ecrnx_retrieve_sta(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_vif *ecrnx_vif, u8 *addr, + __le16 fc, bool ap) +{ + if (ap) { + /* only deauth, disassoc and action are bufferable MMPDUs */ + bool bufferable = ieee80211_is_deauth(fc) || + ieee80211_is_disassoc(fc) || + ieee80211_is_action(fc); + + /* Check if the packet is bufferable or not */ + if (bufferable) + { + /* Check if address is a broadcast or a multicast address */ + if (is_broadcast_ether_addr(addr) || is_multicast_ether_addr(addr)) { + /* Returned STA pointer */ + struct ecrnx_sta *ecrnx_sta = &ecrnx_hw->sta_table[ecrnx_vif->ap.bcmc_index]; + + if (ecrnx_sta->valid) + return ecrnx_sta; + } else { + /* Returned STA pointer */ + struct ecrnx_sta *ecrnx_sta; + + /* Go through list of STAs linked with the provided VIF */ + list_for_each_entry(ecrnx_sta, &ecrnx_vif->ap.sta_list, list) { + if (ecrnx_sta->valid && + ether_addr_equal(ecrnx_sta->mac_addr, addr)) { + /* Return the found STA */ + return ecrnx_sta; + } + } + } + } + } else { + return ecrnx_vif->sta.ap; + } + + return NULL; +} + +/** + * @add_virtual_intf: create a new virtual interface with the given name, + * must set the struct wireless_dev's iftype. Beware: You must create + * the new netdev in the wiphy's network namespace! Returns the struct + * wireless_dev, or an ERR_PTR. For P2P device wdevs, the driver must + * also set the address member in the wdev. + */ +static struct wireless_dev *ecrnx_cfg80211_add_iface(struct wiphy *wiphy, + const char *name, + unsigned char name_assign_type, + enum nl80211_iftype type, + struct vif_params *params) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct wireless_dev *wdev; + + wdev = ecrnx_interface_add(ecrnx_hw, name, name_assign_type, type, params); + + if (!wdev) + return ERR_PTR(-EINVAL); + + return wdev; +} + +/** + * @del_virtual_intf: remove the virtual interface + */ +static int ecrnx_cfg80211_del_iface(struct wiphy *wiphy, struct wireless_dev *wdev) +{ + struct net_device *dev = wdev->netdev; + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + + netdev_info(dev, "Remove Interface"); + + if (dev->reg_state == NETREG_REGISTERED) { + ECRNX_DBG("%s-%d:unregister_netdevice \n", __func__, __LINE__); + /* Will call ecrnx_close if interface is UP */ + unregister_netdevice(dev); + } + + spin_lock_bh(&ecrnx_hw->cb_lock); + list_del(&ecrnx_vif->list); + spin_unlock_bh(&ecrnx_hw->cb_lock); + ecrnx_hw->avail_idx_map |= BIT(ecrnx_vif->drv_vif_index); + ecrnx_vif->ndev = NULL; + + /* Clear the priv in adapter */ + dev->ieee80211_ptr = NULL; + + return 0; +} + +static int ecrnx_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *dev); +static int ecrnx_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *dev, + u16 reason_code); + +/** + * @change_virtual_intf: change type/configuration of virtual interface, + * keep the struct wireless_dev's iftype updated. + */ +static int ecrnx_cfg80211_change_iface(struct wiphy *wiphy, + struct net_device *dev, + enum nl80211_iftype type, + struct vif_params *params) +{ +#ifndef CONFIG_ECRNX_MON_DATA + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); +#endif + struct ecrnx_vif *vif = netdev_priv(dev); + + + ECRNX_PRINT("%s:dev:0x%p, type:%d, vif->up:%d \n", __func__, dev, type, vif->up); + + if (vif->up) + { + if((ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_AP) || (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_P2P_GO)) + { + ecrnx_cfg80211_stop_ap(wiphy, dev); + } + else if((ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_STATION) || (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_P2P_CLIENT)) + { + ecrnx_cfg80211_disconnect(wiphy, dev, WLAN_REASON_DEAUTH_LEAVING); + } + ECRNX_ERR("ecrnx_cfg80211_change_iface: -EBUSY \n"); + ecrnx_close(dev); + //return (-EBUSY); + } + +#ifndef CONFIG_ECRNX_MON_DATA + if ((type == NL80211_IFTYPE_MONITOR) && + (ECRNX_VIF_TYPE(vif) != NL80211_IFTYPE_MONITOR)) { + struct ecrnx_vif *vif_el; + list_for_each_entry(vif_el, &ecrnx_hw->vifs, list) { + // Check if data interface already exists + if ((vif_el != vif) && + (ECRNX_VIF_TYPE(vif) != NL80211_IFTYPE_MONITOR)) { + wiphy_err(ecrnx_hw->wiphy, + "Monitor+Data interface support (MON_DATA) disabled\n"); + return -EIO; + } + } + } +#endif + + // Reset to default case (i.e. not monitor) + dev->type = ARPHRD_ETHER; + dev->netdev_ops = &ecrnx_netdev_ops; + + switch (type) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + vif->sta.flags = 0; + vif->sta.ap = NULL; + vif->sta.tdls_sta = NULL; + vif->sta.ft_assoc_ies = NULL; + vif->sta.ft_assoc_ies_len = 0; + break; + case NL80211_IFTYPE_MESH_POINT: + INIT_LIST_HEAD(&vif->ap.mpath_list); + INIT_LIST_HEAD(&vif->ap.proxy_list); + break; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + INIT_LIST_HEAD(&vif->ap.sta_list); + memset(&vif->ap.bcn, 0, sizeof(vif->ap.bcn)); + vif->ap.flags = 0; + break; + case NL80211_IFTYPE_AP_VLAN: + return -EPERM; + case NL80211_IFTYPE_MONITOR: + dev->type = ARPHRD_IEEE80211_RADIOTAP; + dev->netdev_ops = &ecrnx_netdev_monitor_ops; + break; + default: + break; + } + + vif->generation = 0; + vif->wdev.iftype = type; + if (params->use_4addr != -1) + vif->use_4addr = params->use_4addr; + + if (!vif->up) + { + ecrnx_open(dev); + } + + return 0; +} + +/* + * GavinGao + * Used as P2P_DEVICE mode + */ +static int ecrnx_cfg80211_start_p2p_device(struct wiphy *wiphy, + struct wireless_dev *wdev) +{ + ECRNX_PRINT("rwnx_cfg80211_start_p2p_device\n"); + return 0; +} + +static void ecrnx_cfg80211_stop_p2p_device(struct wiphy *wiphy, + struct wireless_dev *wdev) +{ + //TODO +} +/* Used as P2P_DEVICE mode*/ + + +/** + * @scan: Request to do a scan. If returning zero, the scan request is given + * the driver, and will be valid until passed to cfg80211_scan_done(). + * For scan results, call cfg80211_inform_bss(); you can call this outside + * the scan/scan_done bracket too. + */ +static int ecrnx_cfg80211_scan(struct wiphy *wiphy, + struct cfg80211_scan_request *request) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = container_of(request->wdev, struct ecrnx_vif, + wdev); + int error; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + if(!ecrnx_hw->scan_request){ + ecrnx_hw->scan_request = request; + ECRNX_PRINT("%s:scan_request:0x%p \n", __func__, request); + if ((error = ecrnx_send_scanu_req(ecrnx_hw, ecrnx_vif, request))){ + ECRNX_PRINT("scan message send error!!\n"); + ecrnx_hw->scan_request = NULL; + return error; + } + }else{ + ECRNX_PRINT("scan is already running!!\n"); + } + ECRNX_DBG("send finish:ecrnx_cfg80211_scan \n"); + + return 0; +} + +static void ecrnx_cfg80211_abort_scan(struct wiphy *wiphy, + struct wireless_dev *wdev) +{ + u8_l ret = 0; + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = container_of(wdev, struct ecrnx_vif, + wdev); + + //mutex_lock(&ecrnx_hw->mutex); + ECRNX_PRINT("%s:ecrnx_hw->scan_request:0x%p \n", __func__, ecrnx_hw->scan_request); + + if(!ecrnx_hw->scan_request){ + ECRNX_ERR("no scan is running, don't need abort! \n"); + goto out; + } + + if(wdev->iftype != NL80211_IFTYPE_STATION){ + ECRNX_ERR("abort scan ignored, iftype(%d)\n", wdev->iftype); + goto out; + } + + if(wdev != ecrnx_hw->scan_request->wdev){ + ECRNX_ERR("abort scan was called on the wrong iface\n"); + goto out; + } + + ret = ecrnx_send_scanu_cancel_req(ecrnx_hw, ecrnx_vif); + +out: + //mutex_unlock(&ecrnx_hw->mutex); + return; +} + +/** + * @add_key: add a key with the given parameters. @mac_addr will be %NULL + * when adding a group key. + */ +static int ecrnx_cfg80211_add_key(struct wiphy *wiphy, struct net_device *netdev, + u8 key_index, bool pairwise, const u8 *mac_addr, + struct key_params *params) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *vif = netdev_priv(netdev); + int i, error = 0; + struct mm_key_add_cfm key_add_cfm; + u8_l cipher = 0; + struct ecrnx_sta *sta = NULL; + struct ecrnx_key *ecrnx_key; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + if (mac_addr) { + sta = ecrnx_get_sta(ecrnx_hw, mac_addr); + if (!sta) + return -EINVAL; + ecrnx_key = &sta->key; + } + else + ecrnx_key = &vif->key[key_index]; + + /* Retrieve the cipher suite selector */ + switch (params->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + cipher = MAC_CIPHER_WEP40; + break; + case WLAN_CIPHER_SUITE_WEP104: + cipher = MAC_CIPHER_WEP104; + break; + case WLAN_CIPHER_SUITE_TKIP: + cipher = MAC_CIPHER_TKIP; + break; + case WLAN_CIPHER_SUITE_CCMP: + cipher = MAC_CIPHER_CCMP; + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + cipher = MAC_CIPHER_BIP_CMAC_128; + break; + case WLAN_CIPHER_SUITE_SMS4: + { + // Need to reverse key order + u8 tmp, *key = (u8 *)params->key; + cipher = MAC_CIPHER_WPI_SMS4; + for (i = 0; i < WPI_SUBKEY_LEN/2; i++) { + tmp = key[i]; + key[i] = key[WPI_SUBKEY_LEN - 1 - i]; + key[WPI_SUBKEY_LEN - 1 - i] = tmp; + } + for (i = 0; i < WPI_SUBKEY_LEN/2; i++) { + tmp = key[i + WPI_SUBKEY_LEN]; + key[i + WPI_SUBKEY_LEN] = key[WPI_KEY_LEN - 1 - i]; + key[WPI_KEY_LEN - 1 - i] = tmp; + } + break; + } + case WLAN_CIPHER_SUITE_GCMP: + cipher = MAC_CIPHER_GCMP_128; + break; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0) + case WLAN_CIPHER_SUITE_GCMP_256: + cipher = MAC_CIPHER_GCMP_256; + break; + case WLAN_CIPHER_SUITE_CCMP_256: + cipher = MAC_CIPHER_CCMP_256; + break; +#endif + default: + return -EINVAL; + } + + if ((error = ecrnx_send_key_add(ecrnx_hw, vif->vif_index, + (sta ? sta->sta_idx : 0xFF), pairwise, + (u8 *)params->key, params->key_len, + key_index, cipher, &key_add_cfm))) + return error; + + if (key_add_cfm.status != 0) { + ECRNX_PRINT_CFM_ERR(key_add); + return -EIO; + } + + /* Save the index retrieved from LMAC */ + ecrnx_key->hw_idx = key_add_cfm.hw_key_idx; + + return 0; +} + +/** + * @get_key: get information about the key with the given parameters. + * @mac_addr will be %NULL when requesting information for a group + * key. All pointers given to the @callback function need not be valid + * after it returns. This function should return an error if it is + * not possible to retrieve the key, -ENOENT if it doesn't exist. + * + */ +static int ecrnx_cfg80211_get_key(struct wiphy *wiphy, struct net_device *netdev, + u8 key_index, bool pairwise, const u8 *mac_addr, + void *cookie, + void (*callback)(void *cookie, struct key_params*)) +{ + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + return -1; +} + + +/** + * @del_key: remove a key given the @mac_addr (%NULL for a group key) + * and @key_index, return -ENOENT if the key doesn't exist. + */ +static int ecrnx_cfg80211_del_key(struct wiphy *wiphy, struct net_device *netdev, + u8 key_index, bool pairwise, const u8 *mac_addr) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *vif = netdev_priv(netdev); + int error; + struct ecrnx_sta *sta = NULL; + struct ecrnx_key *ecrnx_key; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + if (mac_addr) { + sta = ecrnx_get_sta(ecrnx_hw, mac_addr); + if (!sta) + return -EINVAL; + ecrnx_key = &sta->key; + } + else + ecrnx_key = &vif->key[key_index]; + + error = ecrnx_send_key_del(ecrnx_hw, ecrnx_key->hw_idx); + + return error; +} + +/** + * @set_default_key: set the default key on an interface + */ +static int ecrnx_cfg80211_set_default_key(struct wiphy *wiphy, + struct net_device *netdev, + u8 key_index, bool unicast, bool multicast) +{ + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + return 0; +} + +/** + * @set_default_mgmt_key: set the default management frame key on an interface + */ +static int ecrnx_cfg80211_set_default_mgmt_key(struct wiphy *wiphy, + struct net_device *netdev, + u8 key_index) +{ + return 0; +} + +/** + * @connect: Connect to the ESS with the specified parameters. When connected, + * call cfg80211_connect_result() with status code %WLAN_STATUS_SUCCESS. + * If the connection fails for some reason, call cfg80211_connect_result() + * with the status from the AP. + * (invoked with the wireless_dev mutex held) + */ +static int ecrnx_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_connect_params *sme) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct sm_connect_cfm sm_connect_cfm; + int error = 0; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* For SHARED-KEY authentication, must install key first */ + if (sme->auth_type == NL80211_AUTHTYPE_SHARED_KEY && sme->key) + { + struct key_params key_params; + key_params.key = sme->key; + key_params.seq = NULL; + key_params.key_len = sme->key_len; + key_params.seq_len = 0; + key_params.cipher = sme->crypto.cipher_group; + ecrnx_cfg80211_add_key(wiphy, dev, sme->key_idx, false, NULL, &key_params); + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) + else if ((sme->auth_type == NL80211_AUTHTYPE_SAE) && + !(sme->flags & CONNECT_REQ_EXTERNAL_AUTH_SUPPORT)) { + netdev_err(dev, "Doesn't support SAE without external authentication\n"); + return -EINVAL; + } +#endif + + /* Forward the information to the LMAC */ + if ((error = ecrnx_send_sm_connect_req(ecrnx_hw, ecrnx_vif, sme, &sm_connect_cfm))) + return error; + ECRNX_PRINT("%s:bssid:%pM, send status:%d\n", __func__, sme->bssid, sm_connect_cfm.status); + + // Check the status + switch (sm_connect_cfm.status) + { + case CO_OK: + ecrnx_save_assoc_info_for_ft(ecrnx_vif, sme); + error = 0; + break; + case CO_BUSY: + error = -EINPROGRESS; + break; + case CO_BAD_PARAM: + error = -EINVAL; + break; + case CO_OP_IN_PROGRESS: + error = -EALREADY; + break; + default: + error = -EIO; + break; + } + + return error; +} + +/** + * @disconnect: Disconnect from the BSS/ESS. + * (invoked with the wireless_dev mutex held) + */ +static int ecrnx_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *dev, + u16 reason_code) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + + ECRNX_PRINT("%s:dev:0x%p, vif_index:%d, reason_code:%d \n", __func__, dev, ecrnx_vif->vif_index, reason_code); + + return(ecrnx_send_sm_disconnect_req(ecrnx_hw, ecrnx_vif, reason_code)); +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) +/** + * @external_auth: indicates result of offloaded authentication processing from + * user space + */ +static int ecrnx_cfg80211_external_auth(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_external_auth_params *params) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + + if (!(ecrnx_vif->sta.flags & ECRNX_STA_EXT_AUTH)) + return -EINVAL; + + ecrnx_external_auth_disable(ecrnx_vif); + return ecrnx_send_sm_external_auth_required_rsp(ecrnx_hw, ecrnx_vif, + params->status); +} +#endif + +/** + * @add_station: Add a new station. + */ +static int ecrnx_cfg80211_add_station(struct wiphy *wiphy, struct net_device *dev, + const u8 *mac, struct station_parameters *params) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct me_sta_add_cfm me_sta_add_cfm; + int error = 0; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + WARN_ON(ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_AP_VLAN); + + /* Do not add TDLS station */ + if (params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)) + return 0; + + /* Indicate we are in a STA addition process - This will allow handling + * potential PS mode change indications correctly + */ + set_bit(ECRNX_DEV_ADDING_STA, &ecrnx_hw->flags); + + /* Forward the information to the LMAC */ + if ((error = ecrnx_send_me_sta_add(ecrnx_hw, params, mac, ecrnx_vif->vif_index, + &me_sta_add_cfm))) + return error; + + // Check the status + switch (me_sta_add_cfm.status) + { + case CO_OK: + { + struct ecrnx_sta *sta = &ecrnx_hw->sta_table[me_sta_add_cfm.sta_idx]; + + ECRNX_PRINT("%s-%d:sta:0x%p, sta_idx:%d \n", __func__, __LINE__, sta, me_sta_add_cfm.sta_idx); + int tid; + sta->aid = params->aid; + memset(sta->rx_pn, 0, TID_MAX * sizeof(uint64_t)); + sta->sta_idx = me_sta_add_cfm.sta_idx; + sta->ch_idx = ecrnx_vif->ch_index; + sta->vif_idx = ecrnx_vif->vif_index; + sta->vlan_idx = sta->vif_idx; + sta->qos = (params->sta_flags_set & BIT(NL80211_STA_FLAG_WME)) != 0; + sta->ht = params->ht_capa ? 1 : 0; + sta->vht = params->vht_capa ? 1 : 0; + sta->acm = 0; + sta->listen_interval = params->listen_interval; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + if (params->local_pm != NL80211_MESH_POWER_UNKNOWN) + sta->mesh_pm = params->local_pm; + else + sta->mesh_pm = ecrnx_vif->ap.next_mesh_pm; +#endif + ecrnx_update_mesh_power_mode(ecrnx_vif); + + for (tid = 0; tid < NX_NB_TXQ_PER_STA; tid++) { + int uapsd_bit = ecrnx_hwq2uapsd[ecrnx_tid2hwq[tid]]; + if (params->uapsd_queues & uapsd_bit) + sta->uapsd_tids |= 1 << tid; + else + sta->uapsd_tids &= ~(1 << tid); + } + memcpy(sta->mac_addr, mac, ETH_ALEN); + ecrnx_dbgfs_register_sta(ecrnx_hw, sta); + + /* Ensure that we won't process PS change or channel switch ind*/ + spin_lock_bh(&ecrnx_hw->cb_lock); + ecrnx_txq_sta_init(ecrnx_hw, sta, ecrnx_txq_vif_get_status(ecrnx_vif)); + ecrnx_rx_reord_sta_init(ecrnx_hw, ecrnx_vif, sta->sta_idx); + list_add_tail(&sta->list, &ecrnx_vif->ap.sta_list); + ecrnx_vif->generation++; + sta->valid = true; + ecrnx_ps_bh_enable(ecrnx_hw, sta, sta->ps.active || me_sta_add_cfm.pm_state); + spin_unlock_bh(&ecrnx_hw->cb_lock); + +#ifdef CONFIG_ECRNX_ANDRIOD + if((ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_AP) || (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_P2P_GO)) { + struct station_info sinfo; + u8 ie_offset; + + if((!is_multicast_sta(sta->sta_idx)) && (sta->mac_addr)) { + memset(&sinfo, 0, sizeof(sinfo)); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0) + sinfo.filled = STATION_INFO_ASSOC_REQ_IES; +#endif + sinfo.assoc_req_ies = NULL; + sinfo.assoc_req_ies_len = 0; + ECRNX_PRINT("%s-%d:sta:0x%x,sta->mac_addr:%pM \n", __func__, __LINE__, sta, sta->mac_addr); + cfg80211_new_sta(ecrnx_vif->ndev, sta->mac_addr, &sinfo, GFP_ATOMIC); + } + } +#endif + + error = 0; + +#ifdef CONFIG_ECRNX_BFMER + if (ecrnx_hw->mod_params->bfmer) + ecrnx_send_bfmer_enable(ecrnx_hw, sta, params->vht_capa); + + ecrnx_mu_group_sta_init(sta, params->vht_capa); +#endif /* CONFIG_ECRNX_BFMER */ + + #define PRINT_STA_FLAG(f) \ + (params->sta_flags_set & BIT(NL80211_STA_FLAG_##f) ? "["#f"]" : "") + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + netdev_info(dev, "Add sta %d (%pM) flags=%s%s%s%s%s%s%s", + sta->sta_idx, mac, + PRINT_STA_FLAG(AUTHORIZED), + PRINT_STA_FLAG(SHORT_PREAMBLE), + PRINT_STA_FLAG(WME), + PRINT_STA_FLAG(MFP), + PRINT_STA_FLAG(AUTHENTICATED), + PRINT_STA_FLAG(TDLS_PEER), + PRINT_STA_FLAG(ASSOCIATED)); +#else + netdev_info(dev, "Add sta %d (%pM) flags=%s%s%s%s%s%s", + sta->sta_idx, mac, + PRINT_STA_FLAG(AUTHORIZED), + PRINT_STA_FLAG(SHORT_PREAMBLE), + PRINT_STA_FLAG(WME), + PRINT_STA_FLAG(MFP), + PRINT_STA_FLAG(AUTHENTICATED), + PRINT_STA_FLAG(TDLS_PEER)); +#endif + + #undef PRINT_STA_FLAG +#if defined(CONFIG_ECRNX_DEBUGFS_CUSTOM) + ecrnx_debugfs_add_station_in_ap_mode(ecrnx_hw, sta, params); +#endif + break; + } + default: + error = -EBUSY; + break; + } + + clear_bit(ECRNX_DEV_ADDING_STA, &ecrnx_hw->flags); + + return error; +} + +/** + * @del_station: Remove a station + */ +static int ecrnx_cfg80211_del_station(struct wiphy *wiphy, + struct net_device *dev, +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0)) + u8 *mac +#elif (LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0)) + const u8 *mac +#else + struct station_del_parameters *params +#endif +) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct ecrnx_sta *cur, *tmp; + int error = 0, found = 0; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) + const u8 *mac = NULL; + if (params) + mac = params->mac; +#endif + + if(list_empty(&ecrnx_vif->ap.sta_list)) { + goto end; + } + + list_for_each_entry_safe(cur, tmp, &ecrnx_vif->ap.sta_list, list) { + if ((!mac) || (!memcmp(cur->mac_addr, mac, ETH_ALEN))) { + netdev_info(dev, "Del sta %d (%pM)", cur->sta_idx, cur->mac_addr); + ECRNX_PRINT("%s-%d:cur_list:0x%p, vif_list:0x%p, mac:%pM\n", __func__, __LINE__, &cur->list, &ecrnx_vif->ap.sta_list, mac); + /* Ensure that we won't process PS change ind */ + spin_lock_bh(&ecrnx_hw->cb_lock); + cur->ps.active = false; + cur->valid = false; + spin_unlock_bh(&ecrnx_hw->cb_lock); + + if (cur->vif_idx != cur->vlan_idx) { + struct ecrnx_vif *vlan_vif; + vlan_vif = ecrnx_hw->vif_table[cur->vlan_idx]; + if (vlan_vif->up) { + if ((ECRNX_VIF_TYPE(vlan_vif) == NL80211_IFTYPE_AP_VLAN) && + (vlan_vif->use_4addr)) { + vlan_vif->ap_vlan.sta_4a = NULL; + } else { + WARN(1, "Deleting sta belonging to VLAN other than AP_VLAN 4A"); + } + } + } + + ecrnx_txq_sta_deinit(ecrnx_hw, cur); + ecrnx_rx_reord_sta_deinit(ecrnx_hw, cur->sta_idx, true); + + error = ecrnx_send_me_sta_del(ecrnx_hw, cur->sta_idx, false); + if ((error != 0) && (error != -EPIPE)){ + ECRNX_WARN("del sta msg send fail, error code:%d \n", error); + } + +#ifdef CONFIG_ECRNX_ANDRIOD + if((ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_AP) || (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_P2P_GO)) { + if((!is_multicast_sta(cur->sta_idx)) && params) { + if(params->mac){ + ECRNX_PRINT("%s-%d:vif:%d, mac:%pM \n", __func__, __LINE__, ECRNX_VIF_TYPE(ecrnx_vif), params->mac); + cfg80211_del_sta(ecrnx_vif->ndev, params->mac, GFP_ATOMIC); + } + } + } +#endif + + +#ifdef CONFIG_ECRNX_BFMER + // Disable Beamformer if supported + ecrnx_bfmer_report_del(ecrnx_hw, cur); + ecrnx_mu_group_sta_del(ecrnx_hw, cur); +#endif /* CONFIG_ECRNX_BFMER */ + + list_del(&cur->list); + ecrnx_vif->generation++; + ecrnx_dbgfs_unregister_sta(ecrnx_hw, cur); +#if defined(CONFIG_ECRNX_DEBUGFS_CUSTOM) + ecrnx_debugfs_sta_in_ap_del(cur->sta_idx); +#endif + found ++; + break; + } + } + +end: + if ((!found) && (mac)) + return -ENOENT; + + ecrnx_update_mesh_power_mode(ecrnx_vif); + + return 0; +} + +/** + * @change_station: Modify a given station. Note that flags changes are not much + * validated in cfg80211, in particular the auth/assoc/authorized flags + * might come to the driver in invalid combinations -- make sure to check + * them, also against the existing state! Drivers must call + * cfg80211_check_station_change() to validate the information. + */ +static int ecrnx_cfg80211_change_station(struct wiphy *wiphy, struct net_device *dev, + const u8 *mac, struct station_parameters *params) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *vif = netdev_priv(dev); + struct ecrnx_sta *sta; + + sta = ecrnx_get_sta(ecrnx_hw, mac); + if (!sta) + { + /* Add the TDLS station */ + if (params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)) + { + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct me_sta_add_cfm me_sta_add_cfm; + int error = 0; + + /* Indicate we are in a STA addition process - This will allow handling + * potential PS mode change indications correctly + */ + set_bit(ECRNX_DEV_ADDING_STA, &ecrnx_hw->flags); + + /* Forward the information to the LMAC */ + if ((error = ecrnx_send_me_sta_add(ecrnx_hw, params, mac, ecrnx_vif->vif_index, + &me_sta_add_cfm))) + return error; + + // Check the status + switch (me_sta_add_cfm.status) + { + case CO_OK: + { + int tid; + sta = &ecrnx_hw->sta_table[me_sta_add_cfm.sta_idx]; + memset(&sta->rx_pn, 0, TID_MAX * sizeof(uint64_t)); + sta->aid = params->aid; + sta->sta_idx = me_sta_add_cfm.sta_idx; + sta->ch_idx = ecrnx_vif->ch_index; + sta->vif_idx = ecrnx_vif->vif_index; + sta->vlan_idx = sta->vif_idx; + sta->qos = (params->sta_flags_set & BIT(NL80211_STA_FLAG_WME)) != 0; + sta->ht = params->ht_capa ? 1 : 0; + sta->vht = params->vht_capa ? 1 : 0; + sta->acm = 0; + for (tid = 0; tid < NX_NB_TXQ_PER_STA; tid++) { + int uapsd_bit = ecrnx_hwq2uapsd[ecrnx_tid2hwq[tid]]; + if (params->uapsd_queues & uapsd_bit) + sta->uapsd_tids |= 1 << tid; + else + sta->uapsd_tids &= ~(1 << tid); + } + memcpy(sta->mac_addr, mac, ETH_ALEN); + ecrnx_dbgfs_register_sta(ecrnx_hw, sta); + + /* Ensure that we won't process PS change or channel switch ind*/ + spin_lock_bh(&ecrnx_hw->cb_lock); + ecrnx_txq_sta_init(ecrnx_hw, sta, ecrnx_txq_vif_get_status(ecrnx_vif)); + ecrnx_rx_reord_sta_init(ecrnx_hw, ecrnx_vif, sta->sta_idx); + if (ecrnx_vif->tdls_status == TDLS_SETUP_RSP_TX) { + ecrnx_vif->tdls_status = TDLS_LINK_ACTIVE; + sta->tdls.initiator = true; + sta->tdls.active = true; + } + /* Set TDLS channel switch capability */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + if ((params->ext_capab[3] & WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH) && + !ecrnx_vif->tdls_chsw_prohibited) +#else + if (!ecrnx_vif->tdls_chsw_prohibited) +#endif + sta->tdls.chsw_allowed = true; + ecrnx_vif->sta.tdls_sta = sta; + sta->valid = true; + spin_unlock_bh(&ecrnx_hw->cb_lock); +#ifdef CONFIG_ECRNX_BFMER + if (ecrnx_hw->mod_params->bfmer) + ecrnx_send_bfmer_enable(ecrnx_hw, sta, params->vht_capa); + + ecrnx_mu_group_sta_init(sta, NULL); +#endif /* CONFIG_ECRNX_BFMER */ + + #define PRINT_STA_FLAG(f) \ + (params->sta_flags_set & BIT(NL80211_STA_FLAG_##f) ? "["#f"]" : "") + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + netdev_info(dev, "Add %s TDLS sta %d (%pM) flags=%s%s%s%s%s%s%s", + sta->tdls.initiator ? "initiator" : "responder", + sta->sta_idx, mac, + PRINT_STA_FLAG(AUTHORIZED), + PRINT_STA_FLAG(SHORT_PREAMBLE), + PRINT_STA_FLAG(WME), + PRINT_STA_FLAG(MFP), + PRINT_STA_FLAG(AUTHENTICATED), + PRINT_STA_FLAG(TDLS_PEER), + PRINT_STA_FLAG(ASSOCIATED)); +#else + netdev_info(dev, "Add %s TDLS sta %d (%pM) flags=%s%s%s%s%s%s", + sta->tdls.initiator ? "initiator" : "responder", + sta->sta_idx, mac, + PRINT_STA_FLAG(AUTHORIZED), + PRINT_STA_FLAG(SHORT_PREAMBLE), + PRINT_STA_FLAG(WME), + PRINT_STA_FLAG(MFP), + PRINT_STA_FLAG(AUTHENTICATED), + PRINT_STA_FLAG(TDLS_PEER)); +#endif + #undef PRINT_STA_FLAG + + break; + } + default: + error = -EBUSY; + break; + } + + clear_bit(ECRNX_DEV_ADDING_STA, &ecrnx_hw->flags); + } else { + return -EINVAL; + } + } + + if (params->sta_flags_mask & BIT(NL80211_STA_FLAG_AUTHORIZED)) + ecrnx_send_me_set_control_port_req(ecrnx_hw, + (params->sta_flags_set & BIT(NL80211_STA_FLAG_AUTHORIZED)) != 0, + sta->sta_idx); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + if (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_MESH_POINT) { + if (params->sta_modify_mask & STATION_PARAM_APPLY_PLINK_STATE) { + if (params->plink_state < NUM_NL80211_PLINK_STATES) { + ecrnx_send_mesh_peer_update_ntf(ecrnx_hw, vif, sta->sta_idx, params->plink_state); + } + } + + if (params->local_pm != NL80211_MESH_POWER_UNKNOWN) { + sta->mesh_pm = params->local_pm; + ecrnx_update_mesh_power_mode(vif); + } + } +#endif + + if (params->vlan) { + uint8_t vlan_idx; + + vif = netdev_priv(params->vlan); + vlan_idx = vif->vif_index; + + if (sta->vlan_idx != vlan_idx) { + struct ecrnx_vif *old_vif; + old_vif = ecrnx_hw->vif_table[sta->vlan_idx]; + ecrnx_txq_sta_switch_vif(sta, old_vif, vif); + sta->vlan_idx = vlan_idx; + + if ((ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_AP_VLAN) && + (vif->use_4addr)) { + WARN((vif->ap_vlan.sta_4a), + "4A AP_VLAN interface with more than one sta"); + vif->ap_vlan.sta_4a = sta; + } + + if ((ECRNX_VIF_TYPE(old_vif) == NL80211_IFTYPE_AP_VLAN) && + (old_vif->use_4addr)) { + old_vif->ap_vlan.sta_4a = NULL; + } + } + } + + return 0; +} + +/** + * @start_ap: Start acting in AP mode defined by the parameters. + */ +static int ecrnx_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_ap_settings *settings) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct apm_start_cfm apm_start_cfm; + struct ecrnx_ipc_elem_var elem; + struct ecrnx_sta *sta; + int error = 0; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* Forward the information to the LMAC */ + if ((error = ecrnx_send_apm_start_req(ecrnx_hw, ecrnx_vif, settings, + &apm_start_cfm, &elem))) + goto end; + + // Check the status + switch (apm_start_cfm.status) + { + case CO_OK: + { + u8 txq_status = 0; + ecrnx_vif->ap.bcmc_index = apm_start_cfm.bcmc_idx; + ecrnx_vif->ap.flags = 0; + ecrnx_vif->ap.bcn_interval = settings->beacon_interval; + sta = &ecrnx_hw->sta_table[apm_start_cfm.bcmc_idx]; + sta->valid = true; + sta->aid = 0; + sta->sta_idx = apm_start_cfm.bcmc_idx; + sta->ch_idx = apm_start_cfm.ch_idx; + sta->vif_idx = ecrnx_vif->vif_index; + sta->qos = false; + sta->acm = 0; + sta->ps.active = false; + sta->listen_interval = 5; + ecrnx_mu_group_sta_init(sta, NULL); + spin_lock_bh(&ecrnx_hw->cb_lock); + ecrnx_chanctx_link(ecrnx_vif, apm_start_cfm.ch_idx, + &settings->chandef); + if (ecrnx_hw->cur_chanctx != apm_start_cfm.ch_idx) { + txq_status = ECRNX_TXQ_STOP_CHAN; + } + ecrnx_txq_vif_init(ecrnx_hw, ecrnx_vif, txq_status); + spin_unlock_bh(&ecrnx_hw->cb_lock); + + netif_tx_start_all_queues(dev); + netif_carrier_on(dev); + error = 0; + /* If the AP channel is already the active, we probably skip radar + activation on MM_CHANNEL_SWITCH_IND (unless another vif use this + ctxt). In anycase retest if radar detection must be activated + */ + if (txq_status == 0) { + ecrnx_radar_detection_enable_on_cur_channel(ecrnx_hw); + } + break; + } + case CO_BUSY: + error = -EINPROGRESS; + break; + case CO_OP_IN_PROGRESS: + error = -EALREADY; + break; + default: + error = -EIO; + break; + } + + if (error) { + netdev_info(dev, "Failed to start AP (%d)", error); + } else { + netdev_info(dev, "AP started: ch=%d, bcmc_idx=%d", + ecrnx_vif->ch_index, ecrnx_vif->ap.bcmc_index); + } + + end: + ecrnx_ipc_elem_var_deallocs(ecrnx_hw, &elem); + + return error; +} + + +/** + * @change_beacon: Change the beacon parameters for an access point mode + * interface. This should reject the call when AP mode wasn't started. + */ +static int ecrnx_cfg80211_change_beacon(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_beacon_data *info) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *vif = netdev_priv(dev); + struct ecrnx_bcn *bcn = &vif->ap.bcn; + struct ecrnx_ipc_elem_var elem; + u8 *buf; + int error = 0; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + // Build the beacon + buf = ecrnx_build_bcn(bcn, info); + if (!buf) + return -ENOMEM; + + // Sync buffer for FW + if ((error = ecrnx_ipc_elem_var_allocs(ecrnx_hw, &elem, bcn->len, DMA_TO_DEVICE, + buf, NULL, NULL))) + return error; + + // Forward the information to the LMAC + error = ecrnx_send_bcn_change(ecrnx_hw, vif->vif_index, elem.dma_addr, + bcn->len, bcn->head_len, bcn->tim_len, NULL); + + ecrnx_ipc_elem_var_deallocs(ecrnx_hw, &elem); + + return error; +} + +/** + * * @stop_ap: Stop being an AP, including stopping beaconing. + */ +static int ecrnx_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *dev) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct ecrnx_sta *sta; + + ecrnx_radar_cancel_cac(&ecrnx_hw->radar); + ecrnx_send_apm_stop_req(ecrnx_hw, ecrnx_vif); + spin_lock_bh(&ecrnx_hw->cb_lock); + ecrnx_chanctx_unlink(ecrnx_vif); + spin_unlock_bh(&ecrnx_hw->cb_lock); + + /* delete any remaining STA*/ + while (!list_empty(&ecrnx_vif->ap.sta_list)) { + ecrnx_cfg80211_del_station(wiphy, dev, NULL); + } + + /* delete BC/MC STA */ + sta = &ecrnx_hw->sta_table[ecrnx_vif->ap.bcmc_index]; + ecrnx_txq_vif_deinit(ecrnx_hw, ecrnx_vif); + ecrnx_del_bcn(&ecrnx_vif->ap.bcn); + ecrnx_del_csa(ecrnx_vif); + + netif_tx_stop_all_queues(dev); + netif_carrier_off(dev); + + netdev_info(dev, "AP Stopped"); + + return 0; +} + +/** + * @set_monitor_channel: Set the monitor mode channel for the device. If other + * interfaces are active this callback should reject the configuration. + * If no interfaces are active or the device is down, the channel should + * be stored for when a monitor interface becomes active. + * + * Also called internaly with chandef set to NULL simply to retrieve the channel + * configured at firmware level. + */ +static int ecrnx_cfg80211_set_monitor_channel(struct wiphy *wiphy, + struct cfg80211_chan_def *chandef) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif; + struct me_config_monitor_cfm cfm; + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + if (ecrnx_hw->monitor_vif == ECRNX_INVALID_VIF) + return -EINVAL; + + ecrnx_vif = ecrnx_hw->vif_table[ecrnx_hw->monitor_vif]; + + // Do nothing if monitor interface is already configured with the requested channel + if (ecrnx_chanctx_valid(ecrnx_hw, ecrnx_vif->ch_index)) { + struct ecrnx_chanctx *ctxt; + ctxt = &ecrnx_vif->ecrnx_hw->chanctx_table[ecrnx_vif->ch_index]; + if (chandef && cfg80211_chandef_identical(&ctxt->chan_def, chandef)) + return 0; + } + + // Always send command to firmware. It allows to retrieve channel context index + // and its configuration. + if (ecrnx_send_config_monitor_req(ecrnx_hw, chandef, &cfm)) + return -EIO; + + // Always re-set channel context info + ecrnx_chanctx_unlink(ecrnx_vif); + + + + // If there is also a STA interface not yet connected then monitor interface + // will only have a channel context after the connection of the STA interface. + if (cfm.chan_index != ECRNX_CH_NOT_SET) + { + struct cfg80211_chan_def mon_chandef; + + if (ecrnx_hw->vif_started > 1) { + // In this case we just want to update the channel context index not + // the channel configuration + ecrnx_chanctx_link(ecrnx_vif, cfm.chan_index, NULL); + return -EBUSY; + } + + mon_chandef.chan = ieee80211_get_channel(wiphy, cfm.chan.prim20_freq); + mon_chandef.center_freq1 = cfm.chan.center1_freq; + mon_chandef.center_freq2 = cfm.chan.center2_freq; + mon_chandef.width = chnl2bw[cfm.chan.type]; + ecrnx_chanctx_link(ecrnx_vif, cfm.chan_index, &mon_chandef); + } + + return 0; +} + +/** + * @probe_client: probe an associated client, must return a cookie that it + * later passes to cfg80211_probe_status(). + */ +int ecrnx_cfg80211_probe_client(struct wiphy *wiphy, struct net_device *dev, + const u8 *peer, u64 *cookie) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *vif = netdev_priv(dev); + struct ecrnx_sta *sta = NULL; + struct apm_probe_client_cfm cfm; + if ((ECRNX_VIF_TYPE(vif) != NL80211_IFTYPE_AP) && + (ECRNX_VIF_TYPE(vif) != NL80211_IFTYPE_AP_VLAN) && + (ECRNX_VIF_TYPE(vif) != NL80211_IFTYPE_P2P_GO) && + (ECRNX_VIF_TYPE(vif) != NL80211_IFTYPE_MESH_POINT)) + return -EINVAL; + list_for_each_entry(sta, &vif->ap.sta_list, list) { + if (sta->valid && ether_addr_equal(sta->mac_addr, peer)) + break; +} + + if (!sta) + return -EINVAL; + + ecrnx_send_apm_probe_req(ecrnx_hw, vif, sta, &cfm); + + if (cfm.status != CO_OK) + return -EINVAL; + + *cookie = (u64)cfm.probe_id; + return 0; +} + +/** + * @set_wiphy_params: Notify that wiphy parameters have changed; + * @changed bitfield (see &enum wiphy_params_flags) describes which values + * have changed. The actual parameter values are available in + * struct wiphy. If returning an error, no value should be changed. + */ +static int ecrnx_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) +{ + return 0; +} + + +/** + * @set_tx_power: set the transmit power according to the parameters, + * the power passed is in mBm, to get dBm use MBM_TO_DBM(). The + * wdev may be %NULL if power was set for the wiphy, and will + * always be %NULL unless the driver supports per-vif TX power + * (as advertised by the nl80211 feature flag.) + */ +static int ecrnx_cfg80211_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, + enum nl80211_tx_power_setting type, int mbm) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *vif; + s8 pwr; + int res = 0; + + if (type == NL80211_TX_POWER_AUTOMATIC) { + pwr = 0x7f; + } else { + pwr = MBM_TO_DBM(mbm); + } + + if (wdev) { + vif = container_of(wdev, struct ecrnx_vif, wdev); + res = ecrnx_send_set_power(ecrnx_hw, vif->vif_index, pwr, NULL); + } else { + list_for_each_entry(vif, &ecrnx_hw->vifs, list) { + res = ecrnx_send_set_power(ecrnx_hw, vif->vif_index, pwr, NULL); + if (res) + break; + } + } + + return res; +} + +/** + * @set_power_mgmt: set the power save to one of those two modes: + * Power-save off + * Power-save on - Dynamic mode + */ +static int ecrnx_cfg80211_set_power_mgmt(struct wiphy *wiphy, + struct net_device *dev, + bool enabled, int timeout) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + u8 ps_mode; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + if (timeout >= 0) + netdev_info(dev, "Ignore timeout value %d", timeout); + + if (!(ecrnx_hw->version_cfm.features & BIT(MM_FEAT_PS_BIT))) + enabled = false; + + if (enabled) { + /* Switch to Dynamic Power Save */ + ps_mode = MM_PS_MODE_ON_DYN; + } else { + /* Exit Power Save */ + ps_mode = MM_PS_MODE_OFF; + } + + return ecrnx_send_me_set_ps_mode(ecrnx_hw, ps_mode); +} + +static int ecrnx_cfg80211_set_txq_params(struct wiphy *wiphy, struct net_device *dev, + struct ieee80211_txq_params *params) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + u8 hw_queue, aifs, cwmin, cwmax; + u32 param; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + hw_queue = ecrnx_ac2hwq[0][params->ac]; + + aifs = params->aifs; + cwmin = fls(params->cwmin); + cwmax = fls(params->cwmax); + + /* Store queue information in general structure */ + param = (u32) (aifs << 0); + param |= (u32) (cwmin << 4); + param |= (u32) (cwmax << 8); + param |= (u32) (params->txop) << 12; + + /* Send the MM_SET_EDCA_REQ message to the FW */ + return ecrnx_send_set_edca(ecrnx_hw, hw_queue, param, false, ecrnx_vif->vif_index); +} + + +/** + * @remain_on_channel: Request the driver to remain awake on the specified + * channel for the specified duration to complete an off-channel + * operation (e.g., public action frame exchange). When the driver is + * ready on the requested channel, it must indicate this with an event + * notification by calling cfg80211_ready_on_channel(). + */ +static int +ecrnx_cfg80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, + struct ieee80211_channel *chan, + unsigned int duration, u64 *cookie) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = netdev_priv(wdev->netdev); + struct ecrnx_roc *roc; + int error; + unsigned long timer = 0; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* For debug purpose (use ftrace kernel option) */ + trace_roc(ecrnx_vif->vif_index, chan->center_freq, duration); + + /* Check that no other RoC procedure has been launched */ + if (ecrnx_hw->roc) + { + ECRNX_ERR("%s-%d,statu error!!!, duration:%d \n", __func__, __LINE__, duration); + //exp_report(ecrnx_hw); + //wait for slave confirm + //ecrnx_hw->p2p_listen.rxdatas = 0; +#ifdef CONFIG_ECRNX_P2P + timer = wait_event_interruptible_timeout(ecrnx_hw->p2p_listen.rxdataq, (ecrnx_hw->roc == NULL), HZ/2); + if (timer) + ECRNX_PRINT("wait_event: wake up!!! timer:%ld \n", timer); + else + ECRNX_PRINT("wait_event: timout!!!\n"); +#endif + //return -EBUSY; + } + + /* Allocate a temporary RoC element */ + roc = kmalloc(sizeof(struct ecrnx_roc), GFP_KERNEL); + + /* Verify that element has well been allocated */ + if (!roc) + return -ENOMEM; + + /* Initialize the RoC information element */ + roc->vif = ecrnx_vif; + roc->chan = chan; + roc->duration = duration; + roc->internal = false; + roc->on_chan = false; + + /* Initialize the OFFCHAN TX queue to allow off-channel transmissions */ + ecrnx_txq_offchan_init(ecrnx_vif); + + /* Forward the information to the FMAC */ + ecrnx_hw->roc = roc; + error = ecrnx_send_roc(ecrnx_hw, ecrnx_vif, chan, duration); + + /* If no error, keep all the information for handling of end of procedure */ + if (error == 0) { + + /* Set the cookie value */ + *cookie = (u64)(ecrnx_hw->roc_cookie); + +#ifdef CONFIG_ECRNX_P2P + if(ecrnx_vif->mgmt_reg_stypes & BIT(IEEE80211_STYPE_PROBE_REQ >> 4)) + { + if(ecrnx_send_p2p_start_listen_req(ecrnx_hw, ecrnx_vif, duration)) + ECRNX_ERR("P2P: start_listen failed\n"); + } +#endif + } else { + kfree(roc); + ecrnx_hw->roc = NULL; + ecrnx_txq_offchan_deinit(ecrnx_vif); + } + + return error; +} + +/** + * @cancel_remain_on_channel: Cancel an on-going remain-on-channel operation. + * This allows the operation to be terminated prior to timeout based on + * the duration value. + */ +static int ecrnx_cfg80211_cancel_remain_on_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + u64 cookie) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = netdev_priv(wdev->netdev); + int error; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + /* For debug purpose (use ftrace kernel option) */ + trace_cancel_roc(ecrnx_vif->vif_index); + + /* Check if a RoC procedure is pending */ + if (!ecrnx_hw->roc) + return 0; +#ifdef CONFIG_ECRNX_P2P + //if(ecrnx_vif->mgmt_reg_stypes & BIT(IEEE80211_STYPE_PROBE_REQ >> 4)) + { + error = ecrnx_send_p2p_cancel_listen_req(ecrnx_hw, ecrnx_vif); + if(error == 0) + ECRNX_PRINT("P2P: cancel_listen OK!!!\n"); + else + ECRNX_ERR("P2P: cancel_listen failed, error=%d\n", error); + } +#endif + /* Forward the information to the FMAC */ + return ecrnx_send_cancel_roc(ecrnx_hw); +} + +/** + * @dump_survey: get site survey information. + */ +static int ecrnx_cfg80211_dump_survey(struct wiphy *wiphy, struct net_device *netdev, + int idx, struct survey_info *info) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ieee80211_supported_band *sband; + struct ecrnx_survey_info *ecrnx_survey; + + //ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + if (idx >= ARRAY_SIZE(ecrnx_hw->survey)) + return -ENOENT; + + ecrnx_survey = &ecrnx_hw->survey[idx]; + + // Check if provided index matches with a supported 2.4GHz channel + sband = wiphy->bands[NL80211_BAND_2GHZ]; + if (sband && idx >= sband->n_channels) { + idx -= sband->n_channels; + sband = NULL; + } + + if (!sband) { +#ifdef CONFIG_ECRNX_5G + // Check if provided index matches with a supported 5GHz channel + sband = wiphy->bands[NL80211_BAND_5GHZ]; +#endif + if (!sband || idx >= sband->n_channels) + return -ENOENT; + } + + // Fill the survey + info->channel = &sband->channels[idx]; + info->filled = ecrnx_survey->filled; + + if (ecrnx_survey->filled != 0) { + SURVEY_TIME(info) = (u64)ecrnx_survey->chan_time_ms; + SURVEY_TIME(info) = (u64)ecrnx_survey->chan_time_busy_ms; + info->noise = ecrnx_survey->noise_dbm; + + // Set the survey report as not used + ecrnx_survey->filled = 0; + } + + return 0; +} + +/** + * @get_channel: Get the current operating channel for the virtual interface. + * For monitor interfaces, it should return %NULL unless there's a single + * current monitoring channel. + */ +int ecrnx_cfg80211_get_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + struct cfg80211_chan_def *chandef) { + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = container_of(wdev, struct ecrnx_vif, wdev); + struct ecrnx_chanctx *ctxt; + + if (!ecrnx_vif->up) { + return -ENODATA; + } + + if (ecrnx_vif->vif_index == ecrnx_hw->monitor_vif) + { + //retrieve channel from firmware + ecrnx_cfg80211_set_monitor_channel(wiphy, NULL); + } + + //Check if channel context is valid + if(!ecrnx_chanctx_valid(ecrnx_hw, ecrnx_vif->ch_index)){ + return -ENODATA; + } + + ctxt = &ecrnx_hw->chanctx_table[ecrnx_vif->ch_index]; + *chandef = ctxt->chan_def; + + return 0; +} + +/** + * @mgmt_tx: Transmit a management frame. + */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) +static int ecrnx_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, + struct cfg80211_mgmt_tx_params *params, + u64 *cookie) +#else +static int ecrnx_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, + struct ieee80211_channel *channel, bool offchan, + unsigned int wait, const u8* buf, size_t len, + #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0)) + bool no_cck, + #endif + #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 3, 0)) + bool dont_wait_for_ack, + #endif + u64 *cookie) +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) */ +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = netdev_priv(wdev->netdev); + struct ecrnx_sta *ecrnx_sta; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) + struct ieee80211_channel *channel = params->chan; + const u8 *buf = params->buf; + bool offchan = false; +#endif + struct ieee80211_mgmt *mgmt = (void *)buf; + bool ap = false; + + /* Check if provided VIF is an AP or a STA one */ + switch (ECRNX_VIF_TYPE(ecrnx_vif)) { + case NL80211_IFTYPE_AP_VLAN: + ecrnx_vif = ecrnx_vif->ap_vlan.master; + break; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + case NL80211_IFTYPE_MESH_POINT: + ap = true; + break; + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + default: + break; + } + + /* Get STA on which management frame has to be sent */ + ecrnx_sta = ecrnx_retrieve_sta(ecrnx_hw, ecrnx_vif, mgmt->da, + mgmt->frame_control, ap); + + trace_mgmt_tx((channel) ? channel->center_freq : 0, + ecrnx_vif->vif_index, (ecrnx_sta) ? ecrnx_sta->sta_idx : 0xFF, + mgmt); + + if (ap || ecrnx_sta) + goto send_frame; + + /* Not an AP interface sending frame to unknown STA: + * This is allowed for external authetication */ + if ((ecrnx_vif->sta.flags & ECRNX_STA_EXT_AUTH) && ieee80211_is_auth(mgmt->frame_control)) + goto send_frame; + + if(ieee80211_is_probe_resp(mgmt->frame_control)) + goto p2p_send_frame; + + /* Otherwise ROC is needed */ + if (!channel) + return -EINVAL; + + /* Check that a RoC is already pending */ + if (ecrnx_hw->roc) { + /* Get VIF used for current ROC */ + + /* Check if RoC channel is the same than the required one */ + if ((ecrnx_hw->roc->vif != ecrnx_vif) || + (ecrnx_hw->roc->chan->center_freq != channel->center_freq)) + return -EINVAL; + + } else { + u64 cookie; + int error; + + /* Start a ROC procedure for 30ms */ + error = ecrnx_cfg80211_remain_on_channel(wiphy, wdev, channel, + 30, &cookie); + if (error) + return error; + + /* Need to keep in mind that RoC has been launched internally in order to + * avoid to call the cfg80211 callback once expired */ + ecrnx_hw->roc->internal = true; +#ifdef CONFIG_ECRNX_P2P + ecrnx_hw->p2p_listen.rxdatas = 0; + wait_event_interruptible_timeout(ecrnx_hw->p2p_listen.rxdataq, ecrnx_hw->p2p_listen.rxdatas, HZ); +#endif + } + +p2p_send_frame: +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) + offchan = true; +#endif + +send_frame: +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) + return ecrnx_start_mgmt_xmit(ecrnx_vif, ecrnx_sta, params, offchan, cookie); +#else + return ecrnx_start_mgmt_xmit(ecrnx_vif, ecrnx_sta, channel, offchan, wait, buf, len, no_cck, dont_wait_for_ack, cookie); +#endif +} + +/** + * @start_radar_detection: Start radar detection in the driver. + */ +static int ecrnx_cfg80211_start_radar_detection(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_chan_def *chandef, + u32 cac_time_ms) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct apm_start_cac_cfm cfm; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0)) + ecrnx_radar_start_cac(&ecrnx_hw->radar, cac_time_ms, ecrnx_vif); +#endif + ecrnx_send_apm_start_cac_req(ecrnx_hw, ecrnx_vif, chandef, &cfm); + + if (cfm.status == CO_OK) { + spin_lock_bh(&ecrnx_hw->cb_lock); + ecrnx_chanctx_link(ecrnx_vif, cfm.ch_idx, chandef); + if (ecrnx_hw->cur_chanctx == ecrnx_vif->ch_index) + ecrnx_radar_detection_enable(&ecrnx_hw->radar, + ECRNX_RADAR_DETECT_REPORT, + ECRNX_RADAR_RIU); + spin_unlock_bh(&ecrnx_hw->cb_lock); + } else { + return -EIO; + } + + return 0; +} + +/** + * @update_ft_ies: Provide updated Fast BSS Transition information to the + * driver. If the SME is in the driver/firmware, this information can be + * used in building Authentication and Reassociation Request frames. + */ +static int ecrnx_cfg80211_update_ft_ies(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_update_ft_ies_params *ftie) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *vif = netdev_priv(dev); + const struct ecrnx_element *rsne = NULL, *mde = NULL, *fte = NULL, *elem; + bool ft_in_non_rsn = false; + int fties_len = 0; + u8 *ft_assoc_ies, *pos; + if ((ECRNX_VIF_TYPE(vif) != NL80211_IFTYPE_STATION) || + (vif->sta.ft_assoc_ies == NULL)) + return 0; + for_each_ecrnx_element(elem, ftie->ie, ftie->ie_len) { + if (elem->id == WLAN_EID_RSN) + rsne = elem; + else if (elem->id == WLAN_EID_MOBILITY_DOMAIN) + mde = elem; + else if (elem->id == WLAN_EID_FAST_BSS_TRANSITION) + fte = elem; + else + netdev_warn(dev, "Unexpected FT element %d\n", elem->id); + } + if (!mde) { + netdev_warn(dev, "Didn't find Mobility_Domain Element\n"); + return 0; + } else if (!rsne && !fte) { + ft_in_non_rsn = true; + } else if (!rsne || !fte) { + netdev_warn(dev, "Didn't find RSN or Fast Transition Element\n"); + return 0; + } + for_each_ecrnx_element(elem, vif->sta.ft_assoc_ies, vif->sta.ft_assoc_ies_len) { + if ((elem->id == WLAN_EID_RSN) || + (elem->id == WLAN_EID_MOBILITY_DOMAIN) || + (elem->id == WLAN_EID_FAST_BSS_TRANSITION)) + fties_len += elem->datalen + sizeof(struct ecrnx_element); + } + ft_assoc_ies = kmalloc(vif->sta.ft_assoc_ies_len - fties_len + ftie->ie_len, + GFP_KERNEL); + if (!ft_assoc_ies) { + netdev_warn(dev, "Fail to allocate buffer for association elements"); + } + pos = ft_assoc_ies; + for_each_ecrnx_element(elem, vif->sta.ft_assoc_ies, vif->sta.ft_assoc_ies_len) { + if (elem->id == WLAN_EID_RSN) { + if (ft_in_non_rsn) { + netdev_warn(dev, "Found RSN element in non RSN FT"); + goto abort; + } else if (!rsne) { + netdev_warn(dev, "Found several RSN element"); + goto abort; + } else { + memcpy(pos, rsne, sizeof(*rsne) + rsne->datalen); + pos += sizeof(*rsne) + rsne->datalen; + rsne = NULL; + } + } else if (elem->id == WLAN_EID_MOBILITY_DOMAIN) { + if (!mde) { + netdev_warn(dev, "Found several Mobility Domain element"); + goto abort; + } else { + memcpy(pos, mde, sizeof(*mde) + mde->datalen); + pos += sizeof(*mde) + mde->datalen; + mde = NULL; + } + } + else if (elem->id == WLAN_EID_FAST_BSS_TRANSITION) { + if (ft_in_non_rsn) { + netdev_warn(dev, "Found Fast Transition element in non RSN FT"); + goto abort; + } else if (!fte) { + netdev_warn(dev, "found several Fast Transition element"); + goto abort; + } else { + memcpy(pos, fte, sizeof(*fte) + fte->datalen); + pos += sizeof(*fte) + fte->datalen; + fte = NULL; + } + } + else { + if (fte && !mde) { + memcpy(pos, fte, sizeof(*fte) + fte->datalen); + pos += sizeof(*fte) + fte->datalen; + fte = NULL; + } + memcpy(pos, elem, sizeof(*elem) + elem->datalen); + pos += sizeof(*elem) + elem->datalen; + } + } + if (fte) { + memcpy(pos, fte, sizeof(*fte) + fte->datalen); + pos += sizeof(*fte) + fte->datalen; + fte = NULL; + } + kfree(vif->sta.ft_assoc_ies); + vif->sta.ft_assoc_ies = ft_assoc_ies; + vif->sta.ft_assoc_ies_len = pos - ft_assoc_ies; + if (vif->sta.flags & ECRNX_STA_FT_OVER_DS) { + struct sm_connect_cfm sm_connect_cfm; + struct cfg80211_connect_params sme; + memset(&sme, 0, sizeof(sme)); + rsne = cfg80211_find_ecrnx_elem(WLAN_EID_RSN, vif->sta.ft_assoc_ies, + vif->sta.ft_assoc_ies_len); + if (rsne && ecrnx_rsne_to_connect_params(rsne, &sme)) { + netdev_warn(dev, "FT RSN parsing failed\n"); + return 0; + } + sme.ssid_len = vif->sta.ft_assoc_ies[1]; + sme.ssid = &vif->sta.ft_assoc_ies[2]; + sme.bssid = vif->sta.ft_target_ap; + sme.ie = &vif->sta.ft_assoc_ies[2 + sme.ssid_len]; + sme.ie_len = vif->sta.ft_assoc_ies_len - (2 + sme.ssid_len); + sme.auth_type = NL80211_AUTHTYPE_FT; + ecrnx_send_sm_connect_req(ecrnx_hw, vif, &sme, &sm_connect_cfm); + vif->sta.flags &= ~ECRNX_STA_FT_OVER_DS; + } else if (vif->sta.flags & ECRNX_STA_FT_OVER_AIR) { + uint8_t ssid_len; + vif->sta.flags &= ~ECRNX_STA_FT_OVER_AIR; + ssid_len = vif->sta.ft_assoc_ies[1] + 2; + if (ecrnx_send_sm_ft_auth_rsp(ecrnx_hw, vif, &vif->sta.ft_assoc_ies[ssid_len], + vif->sta.ft_assoc_ies_len - ssid_len)) + netdev_err(dev, "FT Over Air: Failed to send updated assoc elem\n"); + } + return 0; +abort: + kfree(ft_assoc_ies); + return 0; +} + +/** + * @set_cqm_rssi_config: Configure connection quality monitor RSSI threshold. + */ +static int ecrnx_cfg80211_set_cqm_rssi_config(struct wiphy *wiphy, + struct net_device *dev, + int32_t rssi_thold, uint32_t rssi_hyst) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + + return ecrnx_send_cfg_rssi_req(ecrnx_hw, ecrnx_vif->vif_index, rssi_thold, rssi_hyst); +} + +/** + * + * @channel_switch: initiate channel-switch procedure (with CSA). Driver is + * responsible for veryfing if the switch is possible. Since this is + * inherently tricky driver may decide to disconnect an interface later + * with cfg80211_stop_iface(). This doesn't mean driver can accept + * everything. It should do it's best to verify requests and reject them + * as soon as possible. + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) +static int ecrnx_cfg80211_channel_switch(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_csa_settings *params) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *vif = netdev_priv(dev); + struct ecrnx_ipc_elem_var elem; + struct ecrnx_bcn *bcn, *bcn_after; + struct ecrnx_csa *csa; + u16 csa_oft[BCN_MAX_CSA_CPT]; + u8 *buf; + int i, error = 0; + + + if (vif->ap.csa) + return -EBUSY; + + if (params->n_counter_offsets_beacon > BCN_MAX_CSA_CPT) + return -EINVAL; + + /* Build the new beacon with CSA IE */ + bcn = &vif->ap.bcn; + buf = ecrnx_build_bcn(bcn, ¶ms->beacon_csa); + if (!buf) + return -ENOMEM; + + memset(csa_oft, 0, sizeof(csa_oft)); + for (i = 0; i < params->n_counter_offsets_beacon; i++) + { + csa_oft[i] = params->counter_offsets_beacon[i] + bcn->head_len + + bcn->tim_len; + } + + /* If count is set to 0 (i.e anytime after this beacon) force it to 2 */ + if (params->count == 0) { + params->count = 2; + for (i = 0; i < params->n_counter_offsets_beacon; i++) + { + buf[csa_oft[i]] = 2; + } + } + + if ((error = ecrnx_ipc_elem_var_allocs(ecrnx_hw, &elem, bcn->len, + DMA_TO_DEVICE, buf, NULL, NULL))) { + goto end; + } + + /* Build the beacon to use after CSA. It will only be sent to fw once + CSA is over, but do it before sending the beacon as it must be ready + when CSA is finished. */ + csa = kzalloc(sizeof(struct ecrnx_csa), GFP_KERNEL); + if (!csa) { + error = -ENOMEM; + goto end; + } + + bcn_after = &csa->bcn; + buf = ecrnx_build_bcn(bcn_after, ¶ms->beacon_after); + if (!buf) { + error = -ENOMEM; + ecrnx_del_csa(vif); + goto end; + } + + if ((error = ecrnx_ipc_elem_var_allocs(ecrnx_hw, &csa->elem, bcn_after->len, + DMA_TO_DEVICE, buf, NULL, NULL))) { + goto end; + } + + vif->ap.csa = csa; + csa->vif = vif; + csa->chandef = params->chandef; + + /* Send new Beacon. FW will extract channel and count from the beacon */ + error = ecrnx_send_bcn_change(ecrnx_hw, vif->vif_index, elem.dma_addr, + bcn->len, bcn->head_len, bcn->tim_len, csa_oft); + + if (error) { + ecrnx_del_csa(vif); + goto end; + } else { + INIT_WORK(&csa->work, ecrnx_csa_finish); + cfg80211_ch_switch_started_notify(dev, &csa->chandef, params->count); + } + + end: + ecrnx_ipc_elem_var_deallocs(ecrnx_hw, &elem); + return error; +} +#endif + +/* + * @tdls_mgmt: prepare TDLS action frame packets and forward them to FW + */ +static int ecrnx_cfg80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, + const u8 *peer, u8 action_code, u8 dialog_token, + u16 status_code, u32 peer_capability, + bool initiator, const u8 *buf, size_t len) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + int ret = 0; + + /* make sure we support TDLS */ + if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS)) + return -ENOTSUPP; + + /* make sure we are in station mode (and connected) */ + if ((ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_STATION) || + (!ecrnx_vif->up) || (!ecrnx_vif->sta.ap)) + return -ENOTSUPP; + + /* only one TDLS link is supported */ + if ((action_code == WLAN_TDLS_SETUP_REQUEST) && + (ecrnx_vif->sta.tdls_sta) && + (ecrnx_vif->tdls_status == TDLS_LINK_ACTIVE)) { + ECRNX_ERR("%s: only one TDLS link is supported!\n", __func__); + return -ENOTSUPP; + } + + if ((action_code == WLAN_TDLS_DISCOVERY_REQUEST) && + (ecrnx_hw->mod_params->ps_on)) { + ECRNX_ERR("%s: discovery request is not supported when " + "power-save is enabled!\n", __func__); + return -ENOTSUPP; + } + + switch (action_code) { + case WLAN_TDLS_SETUP_RESPONSE: + /* only one TDLS link is supported */ + if ((status_code == 0) && + (ecrnx_vif->sta.tdls_sta) && + (ecrnx_vif->tdls_status == TDLS_LINK_ACTIVE)) { + ECRNX_ERR("%s: only one TDLS link is supported!\n", __func__); + status_code = WLAN_STATUS_REQUEST_DECLINED; + } + /* fall-through */ + case WLAN_TDLS_SETUP_REQUEST: + case WLAN_TDLS_TEARDOWN: + case WLAN_TDLS_DISCOVERY_REQUEST: + case WLAN_TDLS_SETUP_CONFIRM: + case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: + ret = ecrnx_tdls_send_mgmt_packet_data(ecrnx_hw, ecrnx_vif, peer, action_code, + dialog_token, status_code, peer_capability, initiator, buf, len, 0, NULL); + break; + + default: + ECRNX_ERR("%s: Unknown TDLS mgmt/action frame %pM\n", + __func__, peer); + ret = -EOPNOTSUPP; + break; + } + + if (action_code == WLAN_TDLS_SETUP_REQUEST) { + ecrnx_vif->tdls_status = TDLS_SETUP_REQ_TX; + } else if (action_code == WLAN_TDLS_SETUP_RESPONSE) { + ecrnx_vif->tdls_status = TDLS_SETUP_RSP_TX; + } else if ((action_code == WLAN_TDLS_SETUP_CONFIRM) && (ret == CO_OK)) { + ecrnx_vif->tdls_status = TDLS_LINK_ACTIVE; + /* Set TDLS active */ + ecrnx_vif->sta.tdls_sta->tdls.active = true; + } + + return ret; +} + +/* + * @tdls_oper: execute TDLS operation + */ +static int ecrnx_cfg80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, + const u8 *peer, enum nl80211_tdls_operation oper) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + int error; + + if (oper != NL80211_TDLS_DISABLE_LINK) + return 0; + + if (!ecrnx_vif->sta.tdls_sta) { + ECRNX_ERR("%s: TDLS station %pM does not exist\n", __func__, peer); + return -ENOLINK; + } + + if (memcmp(ecrnx_vif->sta.tdls_sta->mac_addr, peer, ETH_ALEN) == 0) { + /* Disable Channel Switch */ + if (!ecrnx_send_tdls_cancel_chan_switch_req(ecrnx_hw, ecrnx_vif, + ecrnx_vif->sta.tdls_sta, + NULL)) + ecrnx_vif->sta.tdls_sta->tdls.chsw_en = false; + + netdev_info(dev, "Del TDLS sta %d (%pM)", + ecrnx_vif->sta.tdls_sta->sta_idx, + ecrnx_vif->sta.tdls_sta->mac_addr); + /* Ensure that we won't process PS change ind */ + spin_lock_bh(&ecrnx_hw->cb_lock); + ecrnx_vif->sta.tdls_sta->ps.active = false; + ecrnx_vif->sta.tdls_sta->valid = false; + spin_unlock_bh(&ecrnx_hw->cb_lock); + ecrnx_txq_sta_deinit(ecrnx_hw, ecrnx_vif->sta.tdls_sta); + error = ecrnx_send_me_sta_del(ecrnx_hw, ecrnx_vif->sta.tdls_sta->sta_idx, true); + if ((error != 0) && (error != -EPIPE)) + return error; + +#ifdef CONFIG_ECRNX_BFMER + // Disable Beamformer if supported + ecrnx_bfmer_report_del(ecrnx_hw, ecrnx_vif->sta.tdls_sta); + ecrnx_mu_group_sta_del(ecrnx_hw, ecrnx_vif->sta.tdls_sta); +#endif /* CONFIG_ECRNX_BFMER */ + + /* Set TDLS not active */ + ecrnx_vif->sta.tdls_sta->tdls.active = false; + ecrnx_dbgfs_unregister_sta(ecrnx_hw, ecrnx_vif->sta.tdls_sta); + // Remove TDLS station + ecrnx_vif->tdls_status = TDLS_LINK_IDLE; + ecrnx_vif->sta.tdls_sta = NULL; + } + + return 0; +} + +/* + * @tdls_channel_switch: enable TDLS channel switch + */ +static int ecrnx_cfg80211_tdls_channel_switch(struct wiphy *wiphy, + struct net_device *dev, + const u8 *addr, u8 oper_class, + struct cfg80211_chan_def *chandef) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_sta *ecrnx_sta = ecrnx_vif->sta.tdls_sta; + struct tdls_chan_switch_cfm cfm; + int error; + + if ((!ecrnx_sta) || (memcmp(addr, ecrnx_sta->mac_addr, ETH_ALEN))) { + ECRNX_ERR("%s: TDLS station %pM doesn't exist\n", __func__, addr); + return -ENOLINK; + } + + if (!ecrnx_sta->tdls.chsw_allowed) { + ECRNX_ERR("%s: TDLS station %pM does not support TDLS channel switch\n", __func__, addr); + return -ENOTSUPP; + } + + error = ecrnx_send_tdls_chan_switch_req(ecrnx_hw, ecrnx_vif, ecrnx_sta, + ecrnx_sta->tdls.initiator, + oper_class, chandef, &cfm); + if (error) + return error; + + if (!cfm.status) { + ecrnx_sta->tdls.chsw_en = true; + return 0; + } else { + ECRNX_ERR("%s: TDLS channel switch already enabled and only one is supported\n", __func__); + return -EALREADY; + } +} + +/* + * @tdls_cancel_channel_switch: disable TDLS channel switch + */ +static void ecrnx_cfg80211_tdls_cancel_channel_switch(struct wiphy *wiphy, + struct net_device *dev, + const u8 *addr) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_sta *ecrnx_sta = ecrnx_vif->sta.tdls_sta; + struct tdls_cancel_chan_switch_cfm cfm; + + if (!ecrnx_sta) + return; + + if (!ecrnx_send_tdls_cancel_chan_switch_req(ecrnx_hw, ecrnx_vif, + ecrnx_sta, &cfm)) + ecrnx_sta->tdls.chsw_en = false; +} + +/** + * @change_bss: Modify parameters for a given BSS (mainly for AP mode). + */ +static int ecrnx_cfg80211_change_bss(struct wiphy *wiphy, struct net_device *dev, + struct bss_parameters *params) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + int res = -EOPNOTSUPP; + + if (((ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_AP) || + (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_P2P_GO)) && + (params->ap_isolate > -1)) { + + if (params->ap_isolate) + ecrnx_vif->ap.flags |= ECRNX_AP_ISOLATE; + else + ecrnx_vif->ap.flags &= ~ECRNX_AP_ISOLATE; + + res = 0; + } + + return res; +} + + +/** + * @get_station: get station information for the station identified by @mac + */ +static int ecrnx_fill_station_info(struct ecrnx_sta *sta, struct ecrnx_vif *vif, + struct station_info *sinfo) +{ + struct ecrnx_sta_stats *stats = &sta->stats; + struct rx_vector_1 *rx_vect1 = &stats->last_rx.rx_vect1; + + // Generic info + sinfo->generation = vif->generation; + + //sinfo->inactive_time = jiffies_to_msecs(jiffies - stats->last_act); + sinfo->rx_bytes = stats->rx_bytes; + sinfo->tx_bytes = stats->tx_bytes; + sinfo->tx_packets = stats->tx_pkts; + sinfo->rx_packets = stats->rx_pkts; + sinfo->signal = rx_vect1->rssi1; + sinfo->tx_failed = 1; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0) + switch (rx_vect1->ch_bw) { + case PHY_CHNL_BW_20: + sinfo->rxrate.bw = RATE_INFO_BW_20; + break; + case PHY_CHNL_BW_40: + sinfo->rxrate.bw = RATE_INFO_BW_40; + break; + case PHY_CHNL_BW_80: + sinfo->rxrate.bw = RATE_INFO_BW_80; + break; + case PHY_CHNL_BW_160: + sinfo->rxrate.bw = RATE_INFO_BW_160; + break; + default: +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) + sinfo->rxrate.bw = RATE_INFO_BW_HE_RU; +#else + sinfo->rxrate.bw = RATE_INFO_BW_160; +#endif + break; + } +#endif + + switch (rx_vect1->format_mod) { + case FORMATMOD_NON_HT: + case FORMATMOD_NON_HT_DUP_OFDM: + sinfo->rxrate.flags = 0; + sinfo->rxrate.legacy = legrates_lut[rx_vect1->leg_rate].rate; + break; + case FORMATMOD_HT_MF: + case FORMATMOD_HT_GF: + sinfo->rxrate.flags = RATE_INFO_FLAGS_MCS; + if (rx_vect1->ht.short_gi) + sinfo->rxrate.flags |= RATE_INFO_FLAGS_SHORT_GI; + sinfo->rxrate.mcs = rx_vect1->ht.mcs; + break; + case FORMATMOD_VHT: + sinfo->rxrate.flags = RATE_INFO_FLAGS_VHT_MCS; + if (rx_vect1->vht.short_gi) + sinfo->rxrate.flags |= RATE_INFO_FLAGS_SHORT_GI; + sinfo->rxrate.mcs = rx_vect1->vht.mcs; + sinfo->rxrate.nss = rx_vect1->vht.nss; + break; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) +#if CONFIG_ECRNX_HE + case FORMATMOD_HE_MU: + sinfo->rxrate.he_ru_alloc = rx_vect1->he.ru_size; + break; + case FORMATMOD_HE_SU: + case FORMATMOD_HE_ER: + case FORMATMOD_HE_TB: + sinfo->rxrate.flags = RATE_INFO_FLAGS_HE_MCS; + sinfo->rxrate.mcs = rx_vect1->he.mcs; + sinfo->rxrate.nss = rx_vect1->he.nss; + sinfo->rxrate.he_gi = rx_vect1->he.gi_type; + sinfo->rxrate.he_dcm = rx_vect1->he.dcm; + break; +#endif +#endif + default : + return -EINVAL; + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0) + sinfo->filled |= (STATION_INFO_INACTIVE_TIME | +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + STATION_INFO_RX_BYTES64 | + STATION_INFO_TX_BYTES64 | +#endif + STATION_INFO_RX_PACKETS | + STATION_INFO_TX_PACKETS | + STATION_INFO_SIGNAL | + STATION_INFO_RX_BITRATE); +#else + sinfo->filled = (BIT(NL80211_STA_INFO_RX_BYTES64) | + BIT(NL80211_STA_INFO_TX_BYTES64) | + BIT(NL80211_STA_INFO_RX_PACKETS) | + BIT(NL80211_STA_INFO_TX_PACKETS) | + BIT(NL80211_STA_INFO_SIGNAL) | + BIT(NL80211_STA_INFO_TX_BITRATE) | + BIT(NL80211_STA_INFO_TX_FAILED) | + BIT(NL80211_STA_INFO_RX_BITRATE)); +#endif + + // Mesh specific info + if (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_MESH_POINT) + { + struct mesh_peer_info_cfm peer_info_cfm; + if (ecrnx_send_mesh_peer_info_req(vif->ecrnx_hw, vif, sta->sta_idx, + &peer_info_cfm)) + return -ENOMEM; + + peer_info_cfm.last_bcn_age = peer_info_cfm.last_bcn_age / 1000; + if (peer_info_cfm.last_bcn_age < sinfo->inactive_time) + sinfo->inactive_time = peer_info_cfm.last_bcn_age; + + sinfo->llid = peer_info_cfm.local_link_id; + sinfo->plid = peer_info_cfm.peer_link_id; + sinfo->plink_state = peer_info_cfm.link_state; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + sinfo->local_pm = peer_info_cfm.local_ps_mode; + sinfo->peer_pm = peer_info_cfm.peer_ps_mode; + sinfo->nonpeer_pm = peer_info_cfm.non_peer_ps_mode; +#endif + sinfo->filled |= (BIT(NL80211_STA_INFO_LLID) | + BIT(NL80211_STA_INFO_PLID) | + BIT(NL80211_STA_INFO_PLINK_STATE) | +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + BIT(NL80211_STA_INFO_LOCAL_PM) | + BIT(NL80211_STA_INFO_PEER_PM) | + BIT(NL80211_STA_INFO_NONPEER_PM)| +#endif + 0); + } + + sinfo->txrate.legacy = 0x6818; + + return 0; +} + +static int ecrnx_cfg80211_get_station(struct wiphy *wiphy, struct net_device *dev, +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0)) + u8 *mac, +#else + const u8 *mac, +#endif + struct station_info *sinfo) +{ + struct ecrnx_vif *vif = netdev_priv(dev); + struct ecrnx_sta *sta = NULL; + + if (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_MONITOR) + return -EINVAL; + else if ((ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_STATION) || + (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_P2P_CLIENT)) { + if (vif->sta.ap && ether_addr_equal(vif->sta.ap->mac_addr, mac)) + sta = vif->sta.ap; + } + else + { + struct ecrnx_sta *sta_iter; + list_for_each_entry(sta_iter, &vif->ap.sta_list, list) { + if (sta_iter->valid && ether_addr_equal(sta_iter->mac_addr, mac)) { + sta = sta_iter; + break; + } + } + } + + if (sta) + return ecrnx_fill_station_info(sta, vif, sinfo); + + return -EINVAL; +} + +int ecrnx_cfg80211_dump_station(struct wiphy *wiphy, struct net_device *dev, + int idx, u8 *mac, struct station_info *sinfo) +{ + struct ecrnx_vif *vif = netdev_priv(dev); + struct ecrnx_sta *sta = NULL; + + if (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_MONITOR) + return -EINVAL; + else if ((ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_STATION) || + (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_P2P_CLIENT)) { + if ((idx == 0) && vif->sta.ap && vif->sta.ap->valid) + sta = vif->sta.ap; + } else { + struct ecrnx_sta *sta_iter; + int i = 0; + list_for_each_entry(sta_iter, &vif->ap.sta_list, list) { + if (i == idx) { + sta = sta_iter; + break; + } + i++; + } + } + + if (sta == NULL) + return -ENOENT; + + + memcpy(mac, &sta->mac_addr, ETH_ALEN); + + return ecrnx_fill_station_info(sta, vif, sinfo); +} + +/** + * @add_mpath: add a fixed mesh path + */ +static int ecrnx_cfg80211_add_mpath(struct wiphy *wiphy, struct net_device *dev, + const u8 *dst, const u8 *next_hop) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct mesh_path_update_cfm cfm; + + if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) + return -ENOTSUPP; + + return ecrnx_send_mesh_path_update_req(ecrnx_hw, ecrnx_vif, dst, next_hop, &cfm); +} + +/** + * @del_mpath: delete a given mesh path + */ +static int ecrnx_cfg80211_del_mpath(struct wiphy *wiphy, struct net_device *dev, + const u8 *dst) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct mesh_path_update_cfm cfm; + + if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) + return -ENOTSUPP; + + return ecrnx_send_mesh_path_update_req(ecrnx_hw, ecrnx_vif, dst, NULL, &cfm); +} + +/** + * @change_mpath: change a given mesh path + */ +static int ecrnx_cfg80211_change_mpath(struct wiphy *wiphy, struct net_device *dev, + const u8 *dst, const u8 *next_hop) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct mesh_path_update_cfm cfm; + + if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) + return -ENOTSUPP; + + return ecrnx_send_mesh_path_update_req(ecrnx_hw, ecrnx_vif, dst, next_hop, &cfm); +} + +/** + * @get_mpath: get a mesh path for the given parameters + */ +static int ecrnx_cfg80211_get_mpath(struct wiphy *wiphy, struct net_device *dev, + u8 *dst, u8 *next_hop, struct mpath_info *pinfo) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct ecrnx_mesh_path *mesh_path = NULL; + struct ecrnx_mesh_path *cur; + + if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) + return -ENOTSUPP; + + list_for_each_entry(cur, &ecrnx_vif->ap.mpath_list, list) { + /* Compare the path target address and the provided destination address */ + if (memcmp(dst, &cur->tgt_mac_addr, ETH_ALEN)) { + continue; + } + + mesh_path = cur; + break; + } + + if (mesh_path == NULL) + return -ENOENT; + + /* Copy next HOP MAC address */ + if (mesh_path->nhop_sta) + memcpy(next_hop, &mesh_path->nhop_sta->mac_addr, ETH_ALEN); + + /* Fill path information */ + pinfo->filled = 0; + pinfo->generation = ecrnx_vif->generation; + + return 0; +} + +/** + * @dump_mpath: dump mesh path callback -- resume dump at index @idx + */ +static int ecrnx_cfg80211_dump_mpath(struct wiphy *wiphy, struct net_device *dev, + int idx, u8 *dst, u8 *next_hop, + struct mpath_info *pinfo) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct ecrnx_mesh_path *mesh_path = NULL; + struct ecrnx_mesh_path *cur; + int i = 0; + + if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) + return -ENOTSUPP; + + list_for_each_entry(cur, &ecrnx_vif->ap.mpath_list, list) { + if (i < idx) { + i++; + continue; + } + + mesh_path = cur; + break; + } + + if (mesh_path == NULL) + return -ENOENT; + + /* Copy target and next hop MAC address */ + memcpy(dst, &mesh_path->tgt_mac_addr, ETH_ALEN); + if (mesh_path->nhop_sta) + memcpy(next_hop, &mesh_path->nhop_sta->mac_addr, ETH_ALEN); + + /* Fill path information */ + pinfo->filled = 0; + pinfo->generation = ecrnx_vif->generation; + + return 0; +} + +/** + * @get_mpp: get a mesh proxy path for the given parameters + */ +static int ecrnx_cfg80211_get_mpp(struct wiphy *wiphy, struct net_device *dev, + u8 *dst, u8 *mpp, struct mpath_info *pinfo) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct ecrnx_mesh_proxy *mesh_proxy = NULL; + struct ecrnx_mesh_proxy *cur; + + if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) + return -ENOTSUPP; + + list_for_each_entry(cur, &ecrnx_vif->ap.proxy_list, list) { + if (cur->local) { + continue; + } + + /* Compare the path target address and the provided destination address */ + if (memcmp(dst, &cur->ext_sta_addr, ETH_ALEN)) { + continue; + } + + mesh_proxy = cur; + break; + } + + if (mesh_proxy == NULL) + return -ENOENT; + + memcpy(mpp, &mesh_proxy->proxy_addr, ETH_ALEN); + + /* Fill path information */ + pinfo->filled = 0; + pinfo->generation = ecrnx_vif->generation; + + return 0; +} + +/** + * @dump_mpp: dump mesh proxy path callback -- resume dump at index @idx + */ +static int ecrnx_cfg80211_dump_mpp(struct wiphy *wiphy, struct net_device *dev, + int idx, u8 *dst, u8 *mpp, struct mpath_info *pinfo) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct ecrnx_mesh_proxy *mesh_proxy = NULL; + struct ecrnx_mesh_proxy *cur; + int i = 0; + + if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) + return -ENOTSUPP; + + list_for_each_entry(cur, &ecrnx_vif->ap.proxy_list, list) { + if (cur->local) { + continue; + } + + if (i < idx) { + i++; + continue; + } + + mesh_proxy = cur; + break; + } + + if (mesh_proxy == NULL) + return -ENOENT; + + /* Copy target MAC address */ + memcpy(dst, &mesh_proxy->ext_sta_addr, ETH_ALEN); + memcpy(mpp, &mesh_proxy->proxy_addr, ETH_ALEN); + + /* Fill path information */ + pinfo->filled = 0; + pinfo->generation = ecrnx_vif->generation; + + return 0; +} + +/** + * @get_mesh_config: Get the current mesh configuration + */ +static int ecrnx_cfg80211_get_mesh_config(struct wiphy *wiphy, struct net_device *dev, + struct mesh_config *conf) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + + if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) + return -ENOTSUPP; + + return 0; +} + +/** + * @update_mesh_config: Update mesh parameters on a running mesh. + */ +static int ecrnx_cfg80211_update_mesh_config(struct wiphy *wiphy, struct net_device *dev, + u32 mask, const struct mesh_config *nconf) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct mesh_update_cfm cfm; + int status; + + if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) + return -ENOTSUPP; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + if (mask & CO_BIT(NL80211_MESHCONF_POWER_MODE - 1)) { + ecrnx_vif->ap.next_mesh_pm = nconf->power_mode; + + if (!list_empty(&ecrnx_vif->ap.sta_list)) { + // If there are mesh links we don't want to update the power mode + // It will be updated with ecrnx_update_mesh_power_mode() when the + // ps mode of a link is updated or when a new link is added/removed + mask &= ~BIT(NL80211_MESHCONF_POWER_MODE - 1); + + if (!mask) + return 0; + } + } +#endif + + status = ecrnx_send_mesh_update_req(ecrnx_hw, ecrnx_vif, mask, nconf, &cfm); + + if (!status && (cfm.status != 0)) + status = -EINVAL; + + return status; +} + +/** + * @join_mesh: join the mesh network with the specified parameters + * (invoked with the wireless_dev mutex held) + */ +static int ecrnx_cfg80211_join_mesh(struct wiphy *wiphy, struct net_device *dev, + const struct mesh_config *conf, const struct mesh_setup *setup) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct mesh_start_cfm mesh_start_cfm; + int error = 0; + u8 txq_status = 0; + /* STA for BC/MC traffic */ + struct ecrnx_sta *sta; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) + return -ENOTSUPP; + + /* Forward the information to the UMAC */ + if ((error = ecrnx_send_mesh_start_req(ecrnx_hw, ecrnx_vif, conf, setup, &mesh_start_cfm))) { + return error; + } + + /* Check the status */ + switch (mesh_start_cfm.status) { + case CO_OK: +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + ecrnx_vif->ap.bcmc_index = mesh_start_cfm.bcmc_idx; + ecrnx_vif->ap.bcn_interval = setup->beacon_interval; +#endif + ecrnx_vif->ap.flags = 0; + ecrnx_vif->use_4addr = true; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + if (setup->user_mpm) + ecrnx_vif->ap.flags |= ECRNX_AP_USER_MESH_PM; +#endif + + sta = &ecrnx_hw->sta_table[mesh_start_cfm.bcmc_idx]; + sta->valid = true; + sta->aid = 0; + sta->sta_idx = mesh_start_cfm.bcmc_idx; + sta->ch_idx = mesh_start_cfm.ch_idx; + sta->vif_idx = ecrnx_vif->vif_index; + sta->qos = true; + sta->acm = 0; + sta->ps.active = false; + sta->listen_interval = 5; + ecrnx_mu_group_sta_init(sta, NULL); + spin_lock_bh(&ecrnx_hw->cb_lock); + ecrnx_chanctx_link(ecrnx_vif, mesh_start_cfm.ch_idx, + (struct cfg80211_chan_def *)(&setup->chandef)); + if (ecrnx_hw->cur_chanctx != mesh_start_cfm.ch_idx) { + txq_status = ECRNX_TXQ_STOP_CHAN; + } + ecrnx_txq_vif_init(ecrnx_hw, ecrnx_vif, txq_status); + spin_unlock_bh(&ecrnx_hw->cb_lock); + + netif_tx_start_all_queues(dev); + netif_carrier_on(dev); + + /* If the AP channel is already the active, we probably skip radar + activation on MM_CHANNEL_SWITCH_IND (unless another vif use this + ctxt). In anycase retest if radar detection must be activated + */ + if (ecrnx_hw->cur_chanctx == mesh_start_cfm.ch_idx) { + ecrnx_radar_detection_enable_on_cur_channel(ecrnx_hw); + } + break; + + case CO_BUSY: + error = -EINPROGRESS; + break; + + default: + error = -EIO; + break; + } + + /* Print information about the operation */ + if (error) { + netdev_info(dev, "Failed to start MP (%d)", error); + } else { + netdev_info(dev, "MP started: ch=%d, bcmc_idx=%d", + ecrnx_vif->ch_index, ecrnx_vif->ap.bcmc_index); + } + + return error; +} + +/** + * @leave_mesh: leave the current mesh network + * (invoked with the wireless_dev mutex held) + */ +static int ecrnx_cfg80211_leave_mesh(struct wiphy *wiphy, struct net_device *dev) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct mesh_stop_cfm mesh_stop_cfm; + int error = 0; + + error = ecrnx_send_mesh_stop_req(ecrnx_hw, ecrnx_vif, &mesh_stop_cfm); + + if (error == 0) { + /* Check the status */ + switch (mesh_stop_cfm.status) { + case CO_OK: + spin_lock_bh(&ecrnx_hw->cb_lock); + ecrnx_chanctx_unlink(ecrnx_vif); + ecrnx_radar_cancel_cac(&ecrnx_hw->radar); + spin_unlock_bh(&ecrnx_hw->cb_lock); + /* delete BC/MC STA */ + ecrnx_txq_vif_deinit(ecrnx_hw, ecrnx_vif); + ecrnx_del_bcn(&ecrnx_vif->ap.bcn); + + netif_tx_stop_all_queues(dev); + netif_carrier_off(dev); + + break; + + default: + error = -EIO; + break; + } + } + + if (error) { + netdev_info(dev, "Failed to stop MP"); + } else { + netdev_info(dev, "MP Stopped"); + } + + return 0; +} + +#ifdef CONFIG_ECRNX_P2P +#if LINUX_VERSION_CODE > KERNEL_VERSION(5, 8, 0) +static void ecrnx_cfg80211_update_mgmt_frame_registrations(struct wiphy *wiphy, + struct wireless_dev *wdev, + struct mgmt_frame_regs *upd) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(wdev->netdev); + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + if (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_STATION) + { + ecrnx_vif->mgmt_reg_stypes = upd->interface_stypes & BIT(IEEE80211_STYPE_PROBE_REQ >> 4); + } +} +#else +static void ecrnx_cfg80211_mgmt_frame_register(struct wiphy *wiphy, + struct wireless_dev *wdev, + u16 frame_type, bool reg) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(wdev->netdev); + + u16 mgmt_type; + + mgmt_type = (frame_type & IEEE80211_FCTL_STYPE) >> 4; + + if (reg) + ecrnx_vif->mgmt_reg_stypes |= BIT(mgmt_type); + else + ecrnx_vif->mgmt_reg_stypes &= ~BIT(mgmt_type); +} +#endif +#endif + +static struct cfg80211_ops ecrnx_cfg80211_ops = { + .add_virtual_intf = ecrnx_cfg80211_add_iface, + .del_virtual_intf = ecrnx_cfg80211_del_iface, + .change_virtual_intf = ecrnx_cfg80211_change_iface, + .start_p2p_device = ecrnx_cfg80211_start_p2p_device, + .stop_p2p_device = ecrnx_cfg80211_stop_p2p_device, + .scan = ecrnx_cfg80211_scan, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0)) + .abort_scan = ecrnx_cfg80211_abort_scan, +#endif + .connect = ecrnx_cfg80211_connect, + .disconnect = ecrnx_cfg80211_disconnect, + .add_key = ecrnx_cfg80211_add_key, + .get_key = ecrnx_cfg80211_get_key, + .del_key = ecrnx_cfg80211_del_key, + .set_default_key = ecrnx_cfg80211_set_default_key, + .set_default_mgmt_key = ecrnx_cfg80211_set_default_mgmt_key, + .add_station = ecrnx_cfg80211_add_station, + .del_station = ecrnx_cfg80211_del_station, + .change_station = ecrnx_cfg80211_change_station, + .mgmt_tx = ecrnx_cfg80211_mgmt_tx, + .start_ap = ecrnx_cfg80211_start_ap, + .change_beacon = ecrnx_cfg80211_change_beacon, + .stop_ap = ecrnx_cfg80211_stop_ap, + .set_monitor_channel = ecrnx_cfg80211_set_monitor_channel, + .probe_client = ecrnx_cfg80211_probe_client, + .set_wiphy_params = ecrnx_cfg80211_set_wiphy_params, + .set_txq_params = ecrnx_cfg80211_set_txq_params, + .set_tx_power = ecrnx_cfg80211_set_tx_power, +// .get_tx_power = ecrnx_cfg80211_get_tx_power, + .set_power_mgmt = ecrnx_cfg80211_set_power_mgmt, + .get_station = ecrnx_cfg80211_get_station, + .remain_on_channel = ecrnx_cfg80211_remain_on_channel, + .cancel_remain_on_channel = ecrnx_cfg80211_cancel_remain_on_channel, + .dump_survey = ecrnx_cfg80211_dump_survey, + .get_channel = ecrnx_cfg80211_get_channel, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + .start_radar_detection = ecrnx_cfg80211_start_radar_detection, + .update_ft_ies = ecrnx_cfg80211_update_ft_ies, +#endif + .set_cqm_rssi_config = ecrnx_cfg80211_set_cqm_rssi_config, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) + .channel_switch = ecrnx_cfg80211_channel_switch, +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) + .tdls_channel_switch = ecrnx_cfg80211_tdls_channel_switch, + .tdls_cancel_channel_switch = ecrnx_cfg80211_tdls_cancel_channel_switch, +#endif + .tdls_mgmt = ecrnx_cfg80211_tdls_mgmt, + .tdls_oper = ecrnx_cfg80211_tdls_oper, + .change_bss = ecrnx_cfg80211_change_bss, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) + .external_auth = ecrnx_cfg80211_external_auth, +#endif + +#ifdef CONFIG_ECRNX_P2P +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) + .update_mgmt_frame_registrations = + ecrnx_cfg80211_update_mgmt_frame_registrations, +#else + .mgmt_frame_register = ecrnx_cfg80211_mgmt_frame_register, +#endif +#endif + +}; + + +/********************************************************************* + * Init/Exit functions + *********************************************************************/ +static void ecrnx_wdev_unregister(struct ecrnx_hw *ecrnx_hw) +{ + struct ecrnx_vif *ecrnx_vif, *tmp; + + rtnl_lock(); + list_for_each_entry_safe(ecrnx_vif, tmp, &ecrnx_hw->vifs, list) { + ecrnx_cfg80211_del_iface(ecrnx_hw->wiphy, &ecrnx_vif->wdev); + } + rtnl_unlock(); +} + +static void ecrnx_set_vers(struct ecrnx_hw *ecrnx_hw) +{ + u32 vers = ecrnx_hw->version_cfm.version_lmac; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + snprintf(ecrnx_hw->wiphy->fw_version, + sizeof(ecrnx_hw->wiphy->fw_version), "%d.%d.%d.%d", + (vers & (0xff << 24)) >> 24, (vers & (0xff << 16)) >> 16, + (vers & (0xff << 8)) >> 8, (vers & (0xff << 0)) >> 0); + ecrnx_hw->machw_type = ecrnx_machw_type(ecrnx_hw->version_cfm.version_machw_2); +} + +static void ecrnx_reg_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); + + // For now trust all initiator + ecrnx_radar_set_domain(&ecrnx_hw->radar, request->dfs_region); + ecrnx_send_me_chan_config_req(ecrnx_hw); +} + +static void ecrnx_enable_mesh(struct ecrnx_hw *ecrnx_hw) +{ + struct wiphy *wiphy = ecrnx_hw->wiphy; + + if (!ecrnx_mod_params.mesh) + return; + + ecrnx_cfg80211_ops.add_mpath = ecrnx_cfg80211_add_mpath; + ecrnx_cfg80211_ops.del_mpath = ecrnx_cfg80211_del_mpath; + ecrnx_cfg80211_ops.change_mpath = ecrnx_cfg80211_change_mpath; + ecrnx_cfg80211_ops.get_mpath = ecrnx_cfg80211_get_mpath; + ecrnx_cfg80211_ops.dump_mpath = ecrnx_cfg80211_dump_mpath; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) + ecrnx_cfg80211_ops.get_mpp = ecrnx_cfg80211_get_mpp; + ecrnx_cfg80211_ops.dump_mpp = ecrnx_cfg80211_dump_mpp; +#endif + ecrnx_cfg80211_ops.get_mesh_config = ecrnx_cfg80211_get_mesh_config; + ecrnx_cfg80211_ops.update_mesh_config = ecrnx_cfg80211_update_mesh_config; + ecrnx_cfg80211_ops.join_mesh = ecrnx_cfg80211_join_mesh; + ecrnx_cfg80211_ops.leave_mesh = ecrnx_cfg80211_leave_mesh; + + wiphy->flags |= (WIPHY_FLAG_MESH_AUTH | WIPHY_FLAG_IBSS_RSN); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + wiphy->features |= NL80211_FEATURE_USERSPACE_MPM; +#endif + wiphy->interface_modes |= BIT(NL80211_IFTYPE_MESH_POINT); + + ecrnx_limits[0].types |= BIT(NL80211_IFTYPE_MESH_POINT); + ecrnx_limits_dfs[0].types |= BIT(NL80211_IFTYPE_MESH_POINT); +} + +int ecrnx_get_cal_result(struct ecrnx_hw *ecrnx_hw) +{ + int ret; + wifi_cal_data_t *result = &cal_result; + + ret = ecrnx_send_cal_result_get_req(ecrnx_hw, result); + + return ret; +} + +void ecrnx_he_init(void) +{ + ecrnx_he_cap.has_he = true; + memset(&ecrnx_he_cap.he_cap_elem, 0, sizeof(struct ieee80211_he_cap_elem)); + + ecrnx_he_cap.he_mcs_nss_supp.rx_mcs_80 = cpu_to_le16(0xfffa); + ecrnx_he_cap.he_mcs_nss_supp.tx_mcs_80 = cpu_to_le16(0xfffa); + ecrnx_he_cap.he_mcs_nss_supp.rx_mcs_160 = cpu_to_le16(0xffff); + ecrnx_he_cap.he_mcs_nss_supp.tx_mcs_160 = cpu_to_le16(0xffff); + ecrnx_he_cap.he_mcs_nss_supp.rx_mcs_80p80 = cpu_to_le16(0xffff); + ecrnx_he_cap.he_mcs_nss_supp.tx_mcs_80p80 = cpu_to_le16(0xffff); + memset(ecrnx_he_cap.ppe_thres, 0, sizeof(u8)*IEEE80211_HE_PPE_THRES_MAX_LEN); +} + +/** + * + */ +bool register_drv_done = false; +int ecrnx_cfg80211_init(void *ecrnx_plat, void **platform_data) +{ + struct ecrnx_hw *ecrnx_hw; + int ret = 0; + struct wiphy *wiphy; + struct wireless_dev *wdev; + int i; + + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + /* create a new wiphy for use with cfg80211 */ + wiphy = wiphy_new(&ecrnx_cfg80211_ops, sizeof(struct ecrnx_hw)); + + if (!wiphy) { + dev_err(ecrnx_platform_get_dev(ecrnx_plat), "Failed to create new wiphy\n"); + ret = -ENOMEM; + goto err_out; + } + + ecrnx_hw = wiphy_priv(wiphy); + ecrnx_hw->wiphy = wiphy; + ecrnx_hw->plat = ecrnx_plat; + ecrnx_hw->dev = ecrnx_platform_get_dev(ecrnx_plat); + ecrnx_hw->mod_params = &ecrnx_mod_params; + ecrnx_hw->tcp_pacing_shift = 7; + *platform_data = ecrnx_hw; + + /* set device pointer for wiphy */ + set_wiphy_dev(wiphy, ecrnx_hw->dev); + /* Create cache to allocate sw_txhdr */ + ecrnx_hw->sw_txhdr_cache = KMEM_CACHE(ecrnx_sw_txhdr, 0); + if (!ecrnx_hw->sw_txhdr_cache) { + wiphy_err(wiphy, "Cannot allocate cache for sw TX header\n"); + ret = -ENOMEM; + goto err_cache; + } + + if ((ret = ecrnx_parse_configfile(ecrnx_hw, ECRNX_CONFIG_FW_NAME))) { + wiphy_err(wiphy, "ecrnx_parse_configfile failed\n"); + goto err_config; + } + + ecrnx_hw->vif_started = 0; + ecrnx_hw->monitor_vif = ECRNX_INVALID_VIF; + + ecrnx_hw->scan_ie.addr = NULL; + + for (i = 0; i < NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX; i++) + ecrnx_hw->avail_idx_map |= BIT(i); + + ecrnx_hwq_init(ecrnx_hw); + ecrnx_txq_prepare(ecrnx_hw); + + ecrnx_mu_group_init(ecrnx_hw); + + /* Initialize RoC element pointer to NULL, indicate that RoC can be started */ + ecrnx_hw->roc = NULL; + /* Cookie can not be 0 */ + ecrnx_hw->roc_cookie = 1; + + wiphy->mgmt_stypes = ecrnx_default_mgmt_stypes; + + wiphy->bands[NL80211_BAND_2GHZ] = &ecrnx_band_2GHz; +#ifdef CONFIG_ECRNX_5G + wiphy->bands[NL80211_BAND_5GHZ] = &ecrnx_band_5GHz; +#endif + wiphy->interface_modes = + BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_AP_VLAN) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_MONITOR); + wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL | +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0)) + WIPHY_FLAG_HAS_CHANNEL_SWITCH | +#endif + WIPHY_FLAG_4ADDR_STATION | + WIPHY_FLAG_4ADDR_AP; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) + wiphy->max_num_csa_counters = BCN_MAX_CSA_CPT; +#endif + + wiphy->max_remain_on_channel_duration = ecrnx_hw->mod_params->roc_dur_max; + +#if 0 /* eswin:rm the feature of OBSS_SCAN which can cause the uplink stream shutdown */ + wiphy->features |= NL80211_FEATURE_NEED_OBSS_SCAN | + NL80211_FEATURE_SK_TX_STATUS | +#else + wiphy->features |= NL80211_FEATURE_SK_TX_STATUS | +#endif + NL80211_FEATURE_VIF_TXPOWER | +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0) + NL80211_FEATURE_ACTIVE_MONITOR | +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) + NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE | +#endif + 0; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) + wiphy->features |= NL80211_FEATURE_SAE; +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 20, 0) + ecrnx_he_init(); +#endif + + if (ecrnx_mod_params.tdls) + /* TDLS support */ + wiphy->features |= NL80211_FEATURE_TDLS_CHANNEL_SWITCH; + + wiphy->iface_combinations = ecrnx_combinations; + /* -1 not to include combination with radar detection, will be re-added in + ecrnx_handle_dynparams if supported */ + wiphy->n_iface_combinations = ARRAY_SIZE(ecrnx_combinations) - 1; + wiphy->reg_notifier = ecrnx_reg_notifier; + + wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + + wiphy->cipher_suites = cipher_suites; + wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites) - NB_RESERVED_CIPHER; + + ecrnx_hw->ext_capa[0] = WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING; + ecrnx_hw->ext_capa[2] = WLAN_EXT_CAPA3_MULTI_BSSID_SUPPORT; + ecrnx_hw->ext_capa[4] = WLAN_EXT_CAPA5_QOS_MAP_SUPPORT; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + ecrnx_hw->ext_capa[7] = WLAN_EXT_CAPA8_OPMODE_NOTIF; + wiphy->extended_capabilities = ecrnx_hw->ext_capa; + wiphy->extended_capabilities_mask = ecrnx_hw->ext_capa; + wiphy->extended_capabilities_len = ARRAY_SIZE(ecrnx_hw->ext_capa); +#endif + +#ifndef CONFIG_ECRNX_ESWIN + tasklet_init(&ecrnx_hw->task, ecrnx_task, (unsigned long)ecrnx_hw); +#endif + + INIT_LIST_HEAD(&ecrnx_hw->vifs); +#ifdef CONFIG_ECRNX_ESWIN + INIT_LIST_HEAD(&ecrnx_hw->agg_rx_list); + INIT_LIST_HEAD(&ecrnx_hw->defrag_rx_list); +#endif + + mutex_init(&ecrnx_hw->dbgdump_elem.mutex); + spin_lock_init(&ecrnx_hw->tx_lock); + spin_lock_init(&ecrnx_hw->cb_lock); + spin_lock_init(&ecrnx_hw->rx_lock); + spin_lock_init(&ecrnx_hw->scan_req_lock); + spin_lock_init(&ecrnx_hw->connect_req_lock); + + if ((ret = ecrnx_platform_on(ecrnx_hw, NULL))) + goto err_platon; + + if ((ret = ecrnx_get_cal_result(ecrnx_hw))) { + wiphy_err(wiphy, "get cal result failed\n"); + goto err_lmac_reqs; + } else { + if ((0 == (cal_result.mac_addr[0] & 0x1)) && (cal_result.mac_addr[0] || cal_result.mac_addr[1] + || cal_result.mac_addr[2] || cal_result.mac_addr[3] || cal_result.mac_addr[4] + || cal_result.mac_addr[5])) { + memcpy(ecrnx_hw->conf_param.mac_addr, cal_result.mac_addr, ETH_ALEN); + } + } + memcpy(wiphy->perm_addr, ecrnx_hw->conf_param.mac_addr, ETH_ALEN); + + /* Reset FW */ + if ((ret = ecrnx_send_reset(ecrnx_hw))) + goto err_lmac_reqs; + if ((ret = ecrnx_send_version_req(ecrnx_hw, &ecrnx_hw->version_cfm))) + goto err_lmac_reqs; + ecrnx_set_vers(ecrnx_hw); + + if ((ret = ecrnx_handle_dynparams(ecrnx_hw, ecrnx_hw->wiphy))) + goto err_lmac_reqs; + + ecrnx_enable_mesh(ecrnx_hw); + ecrnx_radar_detection_init(&ecrnx_hw->radar); + +#ifdef CONFIG_ECRNX_P2P + ecrnx_p2p_listen_init(&ecrnx_hw->p2p_listen); +#endif + + /* Set parameters to firmware */ + ecrnx_send_me_config_req(ecrnx_hw); + + /* Only monitor mode supported when custom channels are enabled */ + if (ecrnx_mod_params.custchan) { + ecrnx_limits[0].types = BIT(NL80211_IFTYPE_MONITOR); + ecrnx_limits_dfs[0].types = BIT(NL80211_IFTYPE_MONITOR); + } + + if ((ret = wiphy_register(wiphy))) { + wiphy_err(wiphy, "Could not register wiphy device\n"); + goto err_register_wiphy; + } + + INIT_WORK(&ecrnx_hw->defer_rx.work, ecrnx_rx_deferred); + skb_queue_head_init(&ecrnx_hw->defer_rx.sk_list); + /* Update regulatory (if needed) and set channel parameters to firmware + (must be done after WiPHY registration) */ + ecrnx_fw_log_level_set((u32)ecrnx_hw->conf_param.fw_log_level, (u32)ecrnx_hw->conf_param.fw_log_type); + ecrnx_custregd(ecrnx_hw, wiphy); + ecrnx_send_me_chan_config_req(ecrnx_hw); + + /* config gain delta */ + ecrnx_send_set_gain_delta_req(ecrnx_hw); + +#ifdef CONFIG_ECRNX_DEBUGFS + if ((ret = ecrnx_dbgfs_register(ecrnx_hw, "ecrnx"))) { + ECRNX_DBG(" ecrnx_dbgfs_register error \n"); + wiphy_err(wiphy, "Failed to register debugfs entries"); + goto err_debugfs; + } +#endif + + rtnl_lock(); + + /* Add an initial interface */ + wdev = ecrnx_interface_add(ecrnx_hw, "wlan%d", NET_NAME_UNKNOWN, + ecrnx_mod_params.custchan ? NL80211_IFTYPE_MONITOR : NL80211_IFTYPE_STATION, + NULL); +#if defined(CONFIG_ECRNX_P2P) + wdev = ecrnx_interface_add(ecrnx_hw, "p2p%d", NET_NAME_UNKNOWN, NL80211_IFTYPE_STATION, + NULL); +#endif + rtnl_unlock(); + + if (!wdev) { + wiphy_err(wiphy, "Failed to instantiate a network device\n"); + ret = -ENOMEM; + goto err_add_interface; + } + + wiphy_info(wiphy, "New interface create %s", wdev->netdev->name); + +#if defined(CONFIG_ECRNX_DEBUGFS_CUSTOM) + ecrnx_debugfs_init(ecrnx_hw); +#endif + register_drv_done = true; + return 0; + +err_add_interface: +#ifdef CONFIG_ECRNX_DEBUGFS +err_debugfs: +#endif + wiphy_unregister(ecrnx_hw->wiphy); +err_register_wiphy: +err_lmac_reqs: + ecrnx_fw_trace_dump(ecrnx_hw); + ecrnx_platform_off(ecrnx_hw, NULL); +err_platon: +err_config: + kmem_cache_destroy(ecrnx_hw->sw_txhdr_cache); +err_cache: + wiphy_free(wiphy); +err_out: + ECRNX_DBG(" %s cfg80211 init failed %d!!", __func__, ret); + return ret; +} + +/** + * + */ +void ecrnx_cfg80211_deinit(struct ecrnx_hw *ecrnx_hw) +{ + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + if(!register_drv_done) + { + return; + } +#if 0 + ecrnx_dbgfs_unregister(ecrnx_hw); +#endif + register_drv_done = false; + + del_timer_sync(&ecrnx_hw->txq_cleanup); + ecrnx_wdev_unregister(ecrnx_hw); + if(ecrnx_hw->wiphy) + { + ECRNX_DBG("%s wiphy_unregister \n", __func__); + wiphy_unregister(ecrnx_hw->wiphy); + wiphy_free(ecrnx_hw->wiphy); + ecrnx_hw->wiphy = NULL; + } + ecrnx_radar_detection_deinit(&ecrnx_hw->radar); + ecrnx_platform_off(ecrnx_hw, NULL); + kmem_cache_destroy(ecrnx_hw->sw_txhdr_cache); +} + +/** + * + */ +static int __init ecrnx_mod_init(void) +{ + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + ecrnx_print_version(); + return ecrnx_platform_register_drv(); +} + +/** + * + */ +static void __exit ecrnx_mod_exit(void) +{ + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + +#if defined(CONFIG_ECRNX_DEBUGFS_CUSTOM) + ecrnx_debugfs_exit(); +#endif + + ecrnx_platform_unregister_drv(); +} + +module_init(ecrnx_mod_init); +module_exit(ecrnx_mod_exit); + +MODULE_FIRMWARE(ECRNX_CONFIG_FW_NAME); + +MODULE_DESCRIPTION(RW_DRV_DESCRIPTION); +MODULE_VERSION(ECRNX_VERS_MOD); +MODULE_AUTHOR(RW_DRV_COPYRIGHT " " RW_DRV_AUTHOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_main.h b/drivers/net/wireless/eswin/fullmac/ecrnx_main.h new file mode 100644 index 000000000000..b1c81e94afb1 --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_main.h @@ -0,0 +1,19 @@ +/** + ****************************************************************************** + * + * @file ecrnx_main.h + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef _ECRNX_MAIN_H_ +#define _ECRNX_MAIN_H_ + +#include "ecrnx_defs.h" + +int ecrnx_cfg80211_init(struct ecrnx_plat *ecrnx_plat, void **platform_data); +void ecrnx_cfg80211_deinit(struct ecrnx_hw *ecrnx_hw); + +#endif /* _ECRNX_MAIN_H_ */ diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_mesh.c b/drivers/net/wireless/eswin/fullmac/ecrnx_mesh.c new file mode 100644 index 000000000000..c027d6712993 --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_mesh.c @@ -0,0 +1,42 @@ +/** + **************************************************************************************** + * + * @file ecrnx_mesh.c + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ + +/** + * INCLUDE FILES + **************************************************************************************** + */ + +#include "ecrnx_mesh.h" + +/** + * FUNCTION DEFINITIONS + **************************************************************************************** + */ + +struct ecrnx_mesh_proxy *ecrnx_get_mesh_proxy_info(struct ecrnx_vif *p_ecrnx_vif, u8 *p_sta_addr, bool local) +{ + struct ecrnx_mesh_proxy *p_mesh_proxy = NULL; + struct ecrnx_mesh_proxy *p_cur_proxy; + + /* Look for proxied devices with provided address */ + list_for_each_entry(p_cur_proxy, &p_ecrnx_vif->ap.proxy_list, list) { + if (p_cur_proxy->local != local) { + continue; + } + + if (!memcmp(&p_cur_proxy->ext_sta_addr, p_sta_addr, ETH_ALEN)) { + p_mesh_proxy = p_cur_proxy; + break; + } + } + + /* Return the found information */ + return p_mesh_proxy; +} diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_mesh.h b/drivers/net/wireless/eswin/fullmac/ecrnx_mesh.h new file mode 100644 index 000000000000..e27ec9ba8d8d --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_mesh.h @@ -0,0 +1,45 @@ +/** + **************************************************************************************** + * + * @file ecrnx_mesh.h + * + * @brief VHT Beamformer function declarations + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ + +#ifndef _ECRNX_MESH_H_ +#define _ECRNX_MESH_H_ + +/** + * INCLUDE FILES + **************************************************************************************** + */ + +#include "ecrnx_defs.h" + +/** + * DEFINES + **************************************************************************************** + */ + +/** + * TYPE DEFINITIONS + **************************************************************************************** + */ + +/** + * FUNCTION DECLARATIONS + **************************************************************************************** + */ + +/** + **************************************************************************************** + * @brief TODO [LT] + **************************************************************************************** + */ +struct ecrnx_mesh_proxy *ecrnx_get_mesh_proxy_info(struct ecrnx_vif *p_ecrnx_vif, u8 *p_sta_addr, bool local); + +#endif /* _ECRNX_MESH_H_ */ diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_p2p.c b/drivers/net/wireless/eswin/fullmac/ecrnx_p2p.c new file mode 100644 index 000000000000..bab577c121b2 --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_p2p.c @@ -0,0 +1,72 @@ +/** + **************************************************************************************** + * + * @file rwnx_p2p.c + * + * + * + **************************************************************************************** + */ + +/** + * INCLUDE FILES + **************************************************************************************** + */ + +#include "ecrnx_p2p.h" +#include "ecrnx_msg_tx.h" + + +/** + * FUNCTION DEFINITIONS + **************************************************************************************** + */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) +static void ecrnx_p2p_discovery_timer_fn(struct timer_list *t) +#else +static void ecrnx_p2p_discovery_timer_fn(ulong x) +#endif +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) + struct ecrnx_hw *ecrnx_hw = from_timer(ecrnx_hw, t, p2p_listen.listen_timer); +#else + struct ecrnx_hw *ecrnx_hw = (void *)x; +#endif + ECRNX_PRINT("ecrnx_p2p_discovery_timer_fn\n"); + + schedule_work(&ecrnx_hw->p2p_listen.listen_expired_work); + +} + +void ecrnx_p2p_listen_init(struct ecrnx_p2p_listen *p2p_listen) +{ + struct ecrnx_hw *ecrnx_hw = container_of(p2p_listen, struct ecrnx_hw, p2p_listen); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) + timer_setup(&p2p_listen->listen_timer, ecrnx_p2p_discovery_timer_fn, 0); +#else + setup_timer(&p2p_listen->listen_timer, ecrnx_p2p_discovery_timer_fn, (ulong)ecrnx_hw); +#endif + INIT_WORK(&p2p_listen->listen_expired_work, ecrnx_p2p_listen_expired); + + init_waitqueue_head(&p2p_listen->rxdataq); +} + +void ecrnx_p2p_listen_expired(struct work_struct *work) +{ + struct ecrnx_p2p_listen *p2p_listen = container_of(work, struct ecrnx_p2p_listen, listen_expired_work); + struct ecrnx_hw *ecrnx_hw = container_of(p2p_listen, struct ecrnx_hw, p2p_listen); + + ECRNX_PRINT("p2p_listen_expired\n"); + + if (p2p_listen->listen_started) + { + del_timer_sync(&p2p_listen->listen_timer); + ecrnx_send_p2p_cancel_listen_req(ecrnx_hw, p2p_listen->ecrnx_vif); + } + else + { + return; + } +} + diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_p2p.h b/drivers/net/wireless/eswin/fullmac/ecrnx_p2p.h new file mode 100644 index 000000000000..390b95002855 --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_p2p.h @@ -0,0 +1,60 @@ +/** + **************************************************************************************** + * + * @file rwnx_p2p.h + * + * @brief P2P Listen function declarations + * + * + * + **************************************************************************************** + */ + +#ifndef _ECRNX_P2P_H_ +#define _ECRNX_P2P_H_ + +/** + * INCLUDE FILES + **************************************************************************************** + */ +#include <net/cfg80211.h> + +/** + * DEFINES + **************************************************************************************** + */ + +/** + * TYPE DEFINITIONS + **************************************************************************************** + */ +struct ecrnx_p2p_listen { + struct ecrnx_vif *ecrnx_vif; + struct ieee80211_channel listen_chan; + bool listen_started; + bool pending_req; + u64 cookie; + struct wireless_dev *pending_listen_wdev; + unsigned int listen_duration; + struct timer_list listen_timer; /* listen duration */ + struct work_struct listen_expired_work; /* listen expire */ + + wait_queue_head_t rxdataq; + int rxdatas; +}; + +/** + * FUNCTION DECLARATIONS + **************************************************************************************** + */ +void ecrnx_p2p_listen_init(struct ecrnx_p2p_listen *p2p_listen); +void ecrnx_p2p_listen_expired(struct work_struct *work); + +/** + **************************************************************************************** + * @brief TODO [LT] + **************************************************************************************** + */ + + +#endif /* _RWNX_P2P_H_ */ diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_rx.c b/drivers/net/wireless/eswin/fullmac/ecrnx_rx.c new file mode 100644 index 000000000000..b6cf0d52fb38 --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_rx.c @@ -0,0 +1,2541 @@ +/** + ****************************************************************************** + * + * @file ecrnx_rx.c + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ +#include <linux/dma-mapping.h> +#include <linux/ieee80211.h> +#include <linux/etherdevice.h> +#include <net/ieee80211_radiotap.h> + +#include "ecrnx_defs.h" +#include "ecrnx_rx.h" +#include "ecrnx_tx.h" +#include "ecrnx_prof.h" +#include "ecrnx_events.h" +#include "ecrnx_compat.h" +#include "ipc_host.h" +#if defined(CONFIG_ECRNX_ESWIN_SDIO) +#include "ecrnx_sdio.h" +#include "sdio.h" +#elif defined(CONFIG_ECRNX_ESWIN_USB) +#include "ecrnx_usb.h" +#include "usb.h" +#endif +#ifdef CONFIG_ECRNX_WIFO_CAIL +#include "core.h" +#endif + +#ifndef IEEE80211_MAX_CHAINS +#define IEEE80211_MAX_CHAINS 4 +#endif + +struct vendor_radiotap_hdr { + u8 oui[3]; + u8 subns; + u16 len; + u8 data[]; +}; + +/** + * ecrnx_rx_get_vif - Return pointer to the destination vif + * + * @ecrnx_hw: main driver data + * @vif_idx: vif index present in rx descriptor + * + * Select the vif that should receive this frame. Returns NULL if the destination + * vif is not active or vif is not specified in the descriptor. + */ +static inline +struct ecrnx_vif *ecrnx_rx_get_vif(struct ecrnx_hw *ecrnx_hw, int vif_idx) +{ + struct ecrnx_vif *ecrnx_vif = NULL; + + if (vif_idx < NX_VIRT_DEV_MAX) { + ecrnx_vif = ecrnx_hw->vif_table[vif_idx]; + //ECRNX_DBG("%s, index : %d, up: %d \n", __func__, vif_idx, ecrnx_vif->up); + if (!ecrnx_vif || !ecrnx_vif->up) + return NULL; + } + + return ecrnx_vif; +} + +/** + * ecrnx_rx_statistic - save some statistics about received frames + * + * @ecrnx_hw: main driver data. + * @hw_rxhdr: Rx Hardware descriptor of the received frame. + * @sta: STA that sent the frame. + */ +static void ecrnx_rx_statistic(struct ecrnx_hw *ecrnx_hw, struct hw_rxhdr *hw_rxhdr, + struct ecrnx_sta *sta) +{ + struct ecrnx_stats *stats = &ecrnx_hw->stats; +#ifdef CONFIG_ECRNX_DEBUGFS + struct ecrnx_rx_rate_stats *rate_stats = &sta->stats.rx_rate; + struct rx_vector_1 *rxvect = &hw_rxhdr->hwvect.rx_vect1; + int mpdu, ampdu, mpdu_prev, rate_idx; + + /* save complete hwvect */ + + /* update ampdu rx stats */ + mpdu = hw_rxhdr->hwvect.mpdu_cnt; + ampdu = hw_rxhdr->hwvect.ampdu_cnt; + mpdu_prev = stats->ampdus_rx_map[ampdu]; + + if (mpdu == 63) { + if (ampdu == stats->ampdus_rx_last) + mpdu = mpdu_prev + 1; + else + mpdu = 0; + } + if (ampdu != stats->ampdus_rx_last) { + stats->ampdus_rx[mpdu_prev]++; + stats->ampdus_rx_miss += mpdu; + } else { + if (mpdu <= mpdu_prev) { + stats->ampdus_rx_miss += mpdu; + } else { + stats->ampdus_rx_miss += mpdu - mpdu_prev - 1; + } + } + stats->ampdus_rx_map[ampdu] = mpdu; + stats->ampdus_rx_last = ampdu; + + /* update rx rate statistic */ + if (!rate_stats->size) + return; + + if (rxvect->format_mod > FORMATMOD_NON_HT_DUP_OFDM) { + int mcs; + int bw = rxvect->ch_bw; + int sgi; + int nss; + switch (rxvect->format_mod) { + case FORMATMOD_HT_MF: + case FORMATMOD_HT_GF: + mcs = rxvect->ht.mcs % 8; + nss = rxvect->ht.mcs / 8; + sgi = rxvect->ht.short_gi; + rate_idx = N_CCK + N_OFDM + nss * 32 + mcs * 4 + bw * 2 + sgi; + break; + case FORMATMOD_VHT: + mcs = rxvect->vht.mcs; + nss = rxvect->vht.nss; + sgi = rxvect->vht.short_gi; + rate_idx = N_CCK + N_OFDM + N_HT + nss * 80 + mcs * 8 + bw * 2 + sgi; + break; + case FORMATMOD_HE_SU: + mcs = rxvect->he.mcs; + nss = rxvect->he.nss; + sgi = rxvect->he.gi_type; + rate_idx = N_CCK + N_OFDM + N_HT + N_VHT + nss * 144 + mcs * 12 + bw * 3 + sgi; + break; + default: + mcs = rxvect->he.mcs; + nss = rxvect->he.nss; + sgi = rxvect->he.gi_type; + rate_idx = N_CCK + N_OFDM + N_HT + N_VHT + N_HE_SU + + nss * 216 + mcs * 18 + rxvect->he.ru_size * 3 + sgi; + break; + } + } else { + int idx = legrates_lut[rxvect->leg_rate].idx; + if (idx < 4) { + rate_idx = idx * 2 + rxvect->pre_type; + } else { + rate_idx = N_CCK + idx - 4; + } + } + if (rate_idx < rate_stats->size) { + if (!rate_stats->table[rate_idx]) + rate_stats->rate_cnt++; + rate_stats->table[rate_idx]++; + rate_stats->cpt++; + } else { + wiphy_err(ecrnx_hw->wiphy, "RX: Invalid index conversion => %d/%d\n", + rate_idx, rate_stats->size); + } +#endif + sta->stats.last_rx = hw_rxhdr->hwvect; + sta->stats.rx_pkts ++; + sta->stats.rx_bytes += hw_rxhdr->hwvect.len; + sta->stats.last_act = stats->last_rx; +} +void ecrnx_rx_defer_skb(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + struct sk_buff *skb) +{ + struct ecrnx_defer_rx_cb *rx_cb = (struct ecrnx_defer_rx_cb *)skb->cb; + if (skb_shared(skb)) + return; + skb_get(skb); + rx_cb->vif = ecrnx_vif; + skb_queue_tail(&ecrnx_hw->defer_rx.sk_list, skb); + schedule_work(&ecrnx_hw->defer_rx.work); +} + +/** + * ecrnx_rx_data_skb - Process one data frame + * + * @ecrnx_hw: main driver data + * @ecrnx_vif: vif that received the buffer + * @skb: skb received + * @rxhdr: HW rx descriptor + * @return: true if buffer has been forwarded to upper layer + * + * If buffer is amsdu , it is first split into a list of skb. + * Then each skb may be: + * - forwarded to upper layer + * - resent on wireless interface + * + * When vif is a STA interface, every skb is only forwarded to upper layer. + * When vif is an AP interface, multicast skb are forwarded and resent, whereas + * skb for other BSS's STA are only resent. + */ +static bool ecrnx_rx_data_skb(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + struct sk_buff *skb, struct hw_rxhdr *rxhdr) +{ + struct sk_buff_head list; + struct sk_buff *rx_skb; + bool amsdu = rxhdr->flags_is_amsdu; + bool resend = false, forward = true; + int skip_after_eth_hdr = 0; + + skb->dev = ecrnx_vif->ndev; + + __skb_queue_head_init(&list); + + if (amsdu) { + int count; + ieee80211_amsdu_to_8023s(skb, &list, ecrnx_vif->ndev->dev_addr, + ECRNX_VIF_TYPE(ecrnx_vif), 0, NULL, NULL); + + count = skb_queue_len(&list); + if (count == 0) + { + ECRNX_PRINT("amsdu decode fail!!\n"); + //return false; + print_hex_dump(KERN_INFO, "amsdu:", DUMP_PREFIX_ADDRESS, 16, 1, + skb->data, skb->len>64 ? 64 : skb->len, false); + } + + if (count > ARRAY_SIZE(ecrnx_hw->stats.amsdus_rx)) + count = ARRAY_SIZE(ecrnx_hw->stats.amsdus_rx); + if(count > 0) + ecrnx_hw->stats.amsdus_rx[count - 1]++; + } else { + ecrnx_hw->stats.amsdus_rx[0]++; + __skb_queue_head(&list, skb); + } + + if (((ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_AP) || + (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_AP_VLAN) || + (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_P2P_GO)) && + !(ecrnx_vif->ap.flags & ECRNX_AP_ISOLATE)) { + const struct ethhdr *eth; + rx_skb = skb_peek(&list); + skb_reset_mac_header(rx_skb); + eth = eth_hdr(rx_skb); + + if (unlikely(is_multicast_ether_addr(eth->h_dest))) { + /* broadcast pkt need to be forwared to upper layer and resent + on wireless interface */ + resend = true; + } else { + /* unicast pkt for STA inside the BSS, no need to forward to upper + layer simply resend on wireless interface */ + if (rxhdr->flags_dst_idx != ECRNX_INVALID_STA) + { + struct ecrnx_sta *sta = &ecrnx_hw->sta_table[rxhdr->flags_dst_idx]; + if (sta->valid && (sta->vlan_idx == ecrnx_vif->vif_index)) + { + forward = false; + resend = true; + } + } + } + } else if (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_MESH_POINT) { + const struct ethhdr *eth; + rx_skb = skb_peek(&list); + skb_reset_mac_header(rx_skb); + eth = eth_hdr(rx_skb); + + /* unicast pkt for STA inside the BSS, no need to forward to upper + layer simply resend on wireless interface */ + if (rxhdr->flags_dst_idx != ECRNX_INVALID_STA) + { + resend = true; + if (is_multicast_ether_addr(eth->h_dest)) { + uint8_t *mesh_ctrl = (uint8_t *)(eth + 1); + skip_after_eth_hdr = 8 + 6; + if ((*mesh_ctrl & MESH_FLAGS_AE) == MESH_FLAGS_AE_A4) + skip_after_eth_hdr += ETH_ALEN; + else if ((*mesh_ctrl & MESH_FLAGS_AE) == MESH_FLAGS_AE_A5_A6) + skip_after_eth_hdr += 2 * ETH_ALEN; + } else { + forward = false; + } + } + } + + while (!skb_queue_empty(&list)) { + rx_skb = __skb_dequeue(&list); + + /* resend pkt on wireless interface */ + if (resend) { + struct sk_buff *skb_copy; + /* always need to copy buffer when forward=0 to get enough headrom for tsdesc */ + skb_copy = skb_copy_expand(rx_skb, ECRNX_TX_MAX_HEADROOM, 0, GFP_ATOMIC); + + if (skb_copy) { + int res; + skb_copy->protocol = htons(ETH_P_802_3); + skb_reset_network_header(skb_copy); + skb_reset_mac_header(skb_copy); + + ecrnx_vif->is_resending = true; + res = dev_queue_xmit(skb_copy); + ecrnx_vif->is_resending = false; + /* note: buffer is always consummed by dev_queue_xmit */ + if (res == NET_XMIT_DROP) { + ecrnx_vif->net_stats.rx_dropped++; + ecrnx_vif->net_stats.tx_dropped++; + } else if (res != NET_XMIT_SUCCESS) { + netdev_err(ecrnx_vif->ndev, + "Failed to re-send buffer to driver (res=%d)", + res); + ecrnx_vif->net_stats.tx_errors++; + } + } else { + netdev_err(ecrnx_vif->ndev, "Failed to copy skb"); + } + } + + /* forward pkt to upper layer */ + if (forward) { + rx_skb->protocol = eth_type_trans(rx_skb, ecrnx_vif->ndev); + memset(rx_skb->cb, 0, sizeof(rx_skb->cb)); + + if (unlikely(skip_after_eth_hdr)) { + memmove(skb_mac_header(rx_skb) + skip_after_eth_hdr, + skb_mac_header(rx_skb), sizeof(struct ethhdr)); + __skb_pull(rx_skb, skip_after_eth_hdr); + skb_reset_mac_header(rx_skb); + skip_after_eth_hdr = 0; + } + REG_SW_SET_PROFILING(ecrnx_hw, SW_PROF_IEEE80211RX); + ECRNX_DBG("netif_receive_skb enter!! \n"); + /* netif_receive_skb only be called from softirq context, replace it with netif_rx */ + netif_rx(rx_skb); + REG_SW_CLEAR_PROFILING(ecrnx_hw, SW_PROF_IEEE80211RX); + + /* Update statistics */ + ecrnx_vif->net_stats.rx_packets++; + ecrnx_vif->net_stats.rx_bytes += rx_skb->len; + } + } + + return forward; +} + +/** + * ecrnx_rx_mgmt - Process one 802.11 management frame + * + * @ecrnx_hw: main driver data + * @ecrnx_vif: vif to upload the buffer to + * @skb: skb received + * @rxhdr: HW rx descriptor + * + * Forward the management frame to a given interface. + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) +extern u64_l getBootTime(void); +#endif +static void ecrnx_rx_mgmt(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + struct sk_buff *skb, struct hw_rxhdr *hw_rxhdr) +{ + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data; + struct rx_vector_1 *rxvect = &hw_rxhdr->hwvect.rx_vect1; + + if (ieee80211_is_beacon(mgmt->frame_control)) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) + mgmt->u.beacon.timestamp = getBootTime(); +#endif + if ((ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_MESH_POINT) && + hw_rxhdr->flags_new_peer) { + cfg80211_notify_new_peer_candidate(ecrnx_vif->ndev, mgmt->sa, + mgmt->u.beacon.variable, + skb->len - offsetof(struct ieee80211_mgmt, + u.beacon.variable), + rxvect->rssi1, GFP_ATOMIC); + } else { + //ECRNX_DBG("%s:%d beacon\n", __func__, __LINE__); + cfg80211_report_obss_beacon(ecrnx_hw->wiphy, skb->data, skb->len, + hw_rxhdr->phy_info.phy_prim20_freq, + rxvect->rssi1); + } + } else if ((ieee80211_is_deauth(mgmt->frame_control) || + ieee80211_is_disassoc(mgmt->frame_control)) && + (mgmt->u.deauth.reason_code == WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA || + mgmt->u.deauth.reason_code == WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA)) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0) // TODO: process unprot mgmt + cfg80211_rx_unprot_mlme_mgmt(ecrnx_vif->ndev, skb->data, skb->len); +#endif + } else if ((ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_STATION) && + (ieee80211_is_action(mgmt->frame_control) && + (mgmt->u.action.category == 6))) { + // Wpa_supplicant will ignore the FT action frame if reported via cfg80211_rx_mgmt + // and cannot call cfg80211_ft_event from atomic context so defer message processing + ecrnx_rx_defer_skb(ecrnx_hw, ecrnx_vif, skb); + } else { + cfg80211_rx_mgmt(&ecrnx_vif->wdev, hw_rxhdr->phy_info.phy_prim20_freq, + rxvect->rssi1, skb->data, skb->len, 0); + } +} + +#if 0 +static void dump_mgmt_rx(struct hw_rxhdr *hw_rxhdr) +{ + ECRNX_DBG("%s, amsdu:%d, 80211_mpdu:%d, 4addr:%d, vif_idx:%d, sta_idx:%d, dst_idx:%d \n", \ + __func__, \ + hw_rxhdr->flags_is_amsdu, \ + hw_rxhdr->flags_is_80211_mpdu, \ + hw_rxhdr->flags_is_4addr, \ + hw_rxhdr->flags_vif_idx, \ + hw_rxhdr->flags_sta_idx, \ + hw_rxhdr->flags_dst_idx); + + ECRNX_DBG("phy_prim20_freq: 0x%x \n", hw_rxhdr->phy_info.phy_prim20_freq); +} +#endif + +/** + * ecrnx_rx_mgmt_any - Process one 802.11 management frame + * + * @ecrnx_hw: main driver data + * @skb: skb received + * @rxhdr: HW rx descriptor + * + * Process the management frame and free the corresponding skb. + * If vif is not specified in the rx descriptor, the the frame is uploaded + * on all active vifs. + */ +static void ecrnx_rx_mgmt_any(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb, + struct hw_rxhdr *hw_rxhdr) +{ + struct ecrnx_vif *ecrnx_vif; + int vif_idx = hw_rxhdr->flags_vif_idx; + + //ECRNX_DBG("%s:%d \n", __func__, __LINE__); + trace_mgmt_rx(hw_rxhdr->phy_info.phy_prim20_freq, vif_idx, + hw_rxhdr->flags_sta_idx, (struct ieee80211_mgmt *)skb->data); + + if (vif_idx == ECRNX_INVALID_VIF) { + //ECRNX_DBG("search the list \n"); + list_for_each_entry(ecrnx_vif, &ecrnx_hw->vifs, list) { + //ECRNX_DBG("find a item, vif_up:%d \n", ecrnx_vif->up); + if (! ecrnx_vif->up) + continue; + //ECRNX_DBG("wpa up \n"); + ecrnx_rx_mgmt(ecrnx_hw, ecrnx_vif, skb, hw_rxhdr); + } + } else { + ecrnx_vif = ecrnx_rx_get_vif(ecrnx_hw, vif_idx); + if (ecrnx_vif) + ecrnx_rx_mgmt(ecrnx_hw, ecrnx_vif, skb, hw_rxhdr); + } + + dev_kfree_skb(skb); +} + +/** + * ecrnx_rx_rtap_hdrlen - Return radiotap header length + * + * @rxvect: Rx vector used to fill the radiotap header + * @has_vend_rtap: boolean indicating if vendor specific data is present + * + * Compute the length of the radiotap header based on @rxvect and vendor + * specific data (if any). + */ +static u8 ecrnx_rx_rtap_hdrlen(struct rx_vector_1 *rxvect, + bool has_vend_rtap) +{ + u8 rtap_len; + + /* Compute radiotap header length */ + rtap_len = sizeof(struct ieee80211_radiotap_header) + 8; + + // Check for multiple antennas + if (hweight32(rxvect->antenna_set) > 1) + // antenna and antenna signal fields + rtap_len += 4 * hweight8(rxvect->antenna_set); + + // TSFT + if (!has_vend_rtap) { + rtap_len = ALIGN(rtap_len, 8); + rtap_len += 8; + } + + // IEEE80211_HW_SIGNAL_DBM + rtap_len++; + + // Check if single antenna + if (hweight32(rxvect->antenna_set) == 1) + rtap_len++; //Single antenna + + // padding for RX FLAGS + rtap_len = ALIGN(rtap_len, 2); + + // Check for HT frames + if ((rxvect->format_mod == FORMATMOD_HT_MF) || + (rxvect->format_mod == FORMATMOD_HT_GF)) + rtap_len += 3; + + // Check for AMPDU + if (!(has_vend_rtap) && ((rxvect->format_mod >= FORMATMOD_VHT) || + ((rxvect->format_mod > FORMATMOD_NON_HT_DUP_OFDM) && + (rxvect->ht.aggregation)))) { + rtap_len = ALIGN(rtap_len, 4); + rtap_len += 8; + } + + // Check for VHT frames + if (rxvect->format_mod == FORMATMOD_VHT) { + rtap_len = ALIGN(rtap_len, 2); + rtap_len += 12; + } + + // Check for HE frames + if (rxvect->format_mod == FORMATMOD_HE_SU) { + rtap_len = ALIGN(rtap_len, 2); + rtap_len += sizeof(struct ieee80211_radiotap_he); + } + + // Check for multiple antennas + if (hweight32(rxvect->antenna_set) > 1) { + // antenna and antenna signal fields + rtap_len += 2 * hweight8(rxvect->antenna_set); + } + + // Check for vendor specific data + if (has_vend_rtap) { + /* vendor presence bitmap */ + rtap_len += 4; + /* alignment for fixed 6-byte vendor data header */ + rtap_len = ALIGN(rtap_len, 2); + } + + return rtap_len; +} + +/** + * ecrnx_rx_add_rtap_hdr - Add radiotap header to sk_buff + * + * @ecrnx_hw: main driver data + * @skb: skb received (will include the radiotap header) + * @rxvect: Rx vector + * @phy_info: Information regarding the phy + * @hwvect: HW Info (NULL if vendor specific data is available) + * @rtap_len: Length of the radiotap header + * @vend_rtap_len: radiotap vendor length (0 if not present) + * @vend_it_present: radiotap vendor present + * + * Builds a radiotap header and add it to @skb. + */ +static void ecrnx_rx_add_rtap_hdr(struct ecrnx_hw* ecrnx_hw, + struct sk_buff *skb, + struct rx_vector_1 *rxvect, + struct phy_channel_info_desc *phy_info, + struct hw_vect *hwvect, + int rtap_len, + u8 vend_rtap_len, + u32 vend_it_present) +{ + struct ieee80211_radiotap_header *rtap; + u8 *pos, rate_idx; + __le32 *it_present; + u32 it_present_val = 0; + bool fec_coding = false; + bool short_gi = false; + bool stbc = false; + bool aggregation = false; + + rtap = (struct ieee80211_radiotap_header *)skb_push(skb, rtap_len); + memset((u8*) rtap, 0, rtap_len); + + rtap->it_version = 0; + rtap->it_pad = 0; + rtap->it_len = cpu_to_le16(rtap_len + vend_rtap_len); + + it_present = (__le32*)&rtap->it_present; + + // Check for multiple antennas + if (hweight32(rxvect->antenna_set) > 1) { + int chain; + unsigned long chains = rxvect->antenna_set; + + for_each_set_bit(chain, &chains, IEEE80211_MAX_CHAINS) { + it_present_val |= + BIT(IEEE80211_RADIOTAP_EXT) | + BIT(IEEE80211_RADIOTAP_RADIOTAP_NAMESPACE); + put_unaligned_le32(it_present_val, it_present); + it_present++; + it_present_val = BIT(IEEE80211_RADIOTAP_ANTENNA) | + BIT(IEEE80211_RADIOTAP_DBM_ANTSIGNAL); + } + } + + // Check if vendor specific data is present + if (vend_rtap_len) { + it_present_val |= BIT(IEEE80211_RADIOTAP_VENDOR_NAMESPACE) | + BIT(IEEE80211_RADIOTAP_EXT); + put_unaligned_le32(it_present_val, it_present); + it_present++; + it_present_val = vend_it_present; + } + + put_unaligned_le32(it_present_val, it_present); + pos = (void *)(it_present + 1); + + // IEEE80211_RADIOTAP_TSFT + if (hwvect) { + rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_TSFT); + // padding + while ((pos - (u8 *)rtap) & 7) + *pos++ = 0; + put_unaligned_le64((((u64)le32_to_cpu(hwvect->tsf_hi) << 32) + + (u64)le32_to_cpu(hwvect->tsf_lo)), pos); + pos += 8; + } + + // IEEE80211_RADIOTAP_FLAGS + rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_FLAGS); + if (hwvect && (!hwvect->status.frm_successful_rx)) + *pos |= IEEE80211_RADIOTAP_F_BADFCS; + if (!rxvect->pre_type + && (rxvect->format_mod <= FORMATMOD_NON_HT_DUP_OFDM)) + *pos |= IEEE80211_RADIOTAP_F_SHORTPRE; + pos++; + + // IEEE80211_RADIOTAP_RATE + // check for HT, VHT or HE frames + if (rxvect->format_mod >= FORMATMOD_HE_SU) { + rate_idx = rxvect->he.mcs; + fec_coding = rxvect->he.fec; + stbc = rxvect->he.stbc; + aggregation = true; + *pos = 0; + } else if (rxvect->format_mod == FORMATMOD_VHT) { + rate_idx = rxvect->vht.mcs; + fec_coding = rxvect->vht.fec; + short_gi = rxvect->vht.short_gi; + stbc = rxvect->vht.stbc; + aggregation = true; + *pos = 0; + } else if (rxvect->format_mod > FORMATMOD_NON_HT_DUP_OFDM) { + rate_idx = rxvect->ht.mcs; + fec_coding = rxvect->ht.fec; + short_gi = rxvect->ht.short_gi; + stbc = rxvect->ht.stbc; + aggregation = rxvect->ht.aggregation; + *pos = 0; + } else { + struct ieee80211_supported_band* band = + ecrnx_hw->wiphy->bands[phy_info->phy_band]; + rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_RATE); + BUG_ON((rate_idx = legrates_lut[rxvect->leg_rate].idx) == -1); +#ifdef CONFIG_ECRNX_5G + if (phy_info->phy_band == NL80211_BAND_5GHZ) + rate_idx -= 4; /* ecrnx_ratetable_5ghz[0].hw_value == 4 */ +#endif + *pos = DIV_ROUND_UP(band->bitrates[rate_idx].bitrate, 5); + } + pos++; + + // IEEE80211_RADIOTAP_CHANNEL + rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_CHANNEL); + put_unaligned_le16(phy_info->phy_prim20_freq, pos); + pos += 2; + +#ifdef CONFIG_ECRNX_5G + if (phy_info->phy_band == NL80211_BAND_5GHZ) + put_unaligned_le16(IEEE80211_CHAN_OFDM | IEEE80211_CHAN_5GHZ, pos); + else if (rxvect->format_mod > FORMATMOD_NON_HT_DUP_OFDM) +#else + if (rxvect->format_mod > FORMATMOD_NON_HT_DUP_OFDM) +#endif + put_unaligned_le16(IEEE80211_CHAN_DYN | IEEE80211_CHAN_2GHZ, pos); + else + put_unaligned_le16(IEEE80211_CHAN_CCK | IEEE80211_CHAN_2GHZ, pos); + pos += 2; + + if (hweight32(rxvect->antenna_set) == 1) { + // IEEE80211_RADIOTAP_DBM_ANTSIGNAL + rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL); + *pos++ = rxvect->rssi1; + + // IEEE80211_RADIOTAP_ANTENNA + rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_ANTENNA); + *pos++ = rxvect->antenna_set; + } + + // IEEE80211_RADIOTAP_LOCK_QUALITY is missing + // IEEE80211_RADIOTAP_DB_ANTNOISE is missing + + // IEEE80211_RADIOTAP_RX_FLAGS + rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_RX_FLAGS); + // 2 byte alignment + if ((pos - (u8 *)rtap) & 1) + *pos++ = 0; + put_unaligned_le16(0, pos); + //Right now, we only support fcs error (no RX_FLAG_FAILED_PLCP_CRC) + pos += 2; + + // Check if HT + if ((rxvect->format_mod == FORMATMOD_HT_MF) || + (rxvect->format_mod == FORMATMOD_HT_GF)) { + rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_MCS); + *pos++ = (IEEE80211_RADIOTAP_MCS_HAVE_MCS | + IEEE80211_RADIOTAP_MCS_HAVE_GI | + IEEE80211_RADIOTAP_MCS_HAVE_BW | + IEEE80211_RADIOTAP_MCS_HAVE_FMT | + IEEE80211_RADIOTAP_MCS_HAVE_FEC | +#if LINUX_VERSION_CODE > KERNEL_VERSION(3, 17, 0) + IEEE80211_RADIOTAP_MCS_HAVE_STBC | +#endif + 0); + pos++; + *pos = 0; + if (short_gi) + *pos |= IEEE80211_RADIOTAP_MCS_SGI; + if (rxvect->ch_bw == PHY_CHNL_BW_40) + *pos |= IEEE80211_RADIOTAP_MCS_BW_40; + if (rxvect->format_mod == FORMATMOD_HT_GF) + *pos |= IEEE80211_RADIOTAP_MCS_FMT_GF; + if (fec_coding) + *pos |= IEEE80211_RADIOTAP_MCS_FEC_LDPC; +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) + *pos++ |= stbc << 5; +#else + *pos++ |= stbc << IEEE80211_RADIOTAP_MCS_STBC_SHIFT; +#endif + *pos++ = rate_idx; + } + + // check for HT or VHT frames + if (aggregation && hwvect) { + // 4 byte alignment + while ((pos - (u8 *)rtap) & 3) + pos++; + rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_AMPDU_STATUS); + put_unaligned_le32(hwvect->ampdu_cnt, pos); + pos += 4; + put_unaligned_le32(0, pos); + pos += 4; + } + + // Check for VHT frames + if (rxvect->format_mod == FORMATMOD_VHT) { + u16 vht_details = IEEE80211_RADIOTAP_VHT_KNOWN_GI | + IEEE80211_RADIOTAP_VHT_KNOWN_BANDWIDTH; + u8 vht_nss = rxvect->vht.nss + 1; + + rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_VHT); + + if ((rxvect->ch_bw == PHY_CHNL_BW_160) + && phy_info->phy_center2_freq) + vht_details &= ~IEEE80211_RADIOTAP_VHT_KNOWN_BANDWIDTH; + put_unaligned_le16(vht_details, pos); + pos += 2; + + // flags + if (short_gi) + *pos |= IEEE80211_RADIOTAP_VHT_FLAG_SGI; + if (stbc) + *pos |= IEEE80211_RADIOTAP_VHT_FLAG_STBC; + pos++; + + // bandwidth + if (rxvect->ch_bw == PHY_CHNL_BW_40) + *pos++ = 1; + if (rxvect->ch_bw == PHY_CHNL_BW_80) + *pos++ = 4; + else if ((rxvect->ch_bw == PHY_CHNL_BW_160) + && phy_info->phy_center2_freq) + *pos++ = 0; //80P80 + else if (rxvect->ch_bw == PHY_CHNL_BW_160) + *pos++ = 11; + else // 20 MHz + *pos++ = 0; + + // MCS/NSS + *pos++ = (rate_idx << 4) | vht_nss; + *pos++ = 0; + *pos++ = 0; + *pos++ = 0; + if (fec_coding){ +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 15, 0) + *pos |= 0x01; +#else + *pos |= IEEE80211_RADIOTAP_CODING_LDPC_USER0; +#endif + } + pos++; + // group ID + pos++; + // partial_aid + pos += 2; + } + + // Check for HE frames + if (rxvect->format_mod >= FORMATMOD_HE_SU) { + struct ieee80211_radiotap_he he; + memset(&he, 0, sizeof(struct ieee80211_radiotap_he)); + #define HE_PREP(f, val) cpu_to_le16(FIELD_PREP(IEEE80211_RADIOTAP_HE_##f, val)) + #define D1_KNOWN(f) cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_##f##_KNOWN) + #define D2_KNOWN(f) cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA2_##f##_KNOWN) + + he.data1 = D1_KNOWN(BSS_COLOR) | D1_KNOWN(BEAM_CHANGE) | + D1_KNOWN(UL_DL) | D1_KNOWN(STBC) | + D1_KNOWN(DOPPLER) | D1_KNOWN(DATA_DCM); + he.data2 = D2_KNOWN(GI) | D2_KNOWN(TXBF) | D2_KNOWN(TXOP); + + he.data3 |= HE_PREP(DATA3_BSS_COLOR, rxvect->he.bss_color); + he.data3 |= HE_PREP(DATA3_BEAM_CHANGE, rxvect->he.beam_change); + he.data3 |= HE_PREP(DATA3_UL_DL, rxvect->he.uplink_flag); + he.data3 |= HE_PREP(DATA3_BSS_COLOR, rxvect->he.bss_color); + he.data3 |= HE_PREP(DATA3_DATA_DCM, rxvect->he.dcm); + he.data5 |= HE_PREP(DATA5_GI, rxvect->he.gi_type); + he.data5 |= HE_PREP(DATA5_TXBF, rxvect->he.beamformed); + he.data5 |= HE_PREP(DATA5_LTF_SIZE, rxvect->he.he_ltf_type + 1); + he.data6 |= HE_PREP(DATA6_DOPPLER, rxvect->he.doppler); + he.data6 |= HE_PREP(DATA6_TXOP, rxvect->he.txop_duration); + if (rxvect->format_mod != FORMATMOD_HE_TB) { + he.data1 |= (D1_KNOWN(DATA_MCS) | D1_KNOWN(CODING) | + D1_KNOWN(SPTL_REUSE) | D1_KNOWN(BW_RU_ALLOC)); + if (stbc) { + he.data6 |= HE_PREP(DATA6_NSTS, 2); + he.data3 |= HE_PREP(DATA3_STBC, 1); + } else { + he.data6 |= HE_PREP(DATA6_NSTS, rxvect->he.nss); + } + + he.data3 |= HE_PREP(DATA3_DATA_MCS, rxvect->he.mcs); + he.data3 |= HE_PREP(DATA3_CODING, rxvect->he.fec); + + he.data4 = HE_PREP(DATA4_SU_MU_SPTL_REUSE, rxvect->he.spatial_reuse); + + if (rxvect->format_mod == FORMATMOD_HE_MU) { + he.data1 |= IEEE80211_RADIOTAP_HE_DATA1_FORMAT_MU; + he.data5 |= HE_PREP(DATA5_DATA_BW_RU_ALLOC, + rxvect->he.ru_size + + IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_26T); + } else { + if (rxvect->format_mod == FORMATMOD_HE_SU) + he.data1 |= IEEE80211_RADIOTAP_HE_DATA1_FORMAT_SU; + else + he.data1 |= IEEE80211_RADIOTAP_HE_DATA1_FORMAT_EXT_SU; + + switch (rxvect->ch_bw) { + case PHY_CHNL_BW_20: + he.data5 |= HE_PREP(DATA5_DATA_BW_RU_ALLOC, + IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_20MHZ); + break; + case PHY_CHNL_BW_40: + he.data5 |= HE_PREP(DATA5_DATA_BW_RU_ALLOC, + IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_40MHZ); + break; + case PHY_CHNL_BW_80: + he.data5 |= HE_PREP(DATA5_DATA_BW_RU_ALLOC, + IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_80MHZ); + break; + case PHY_CHNL_BW_160: + he.data5 |= HE_PREP(DATA5_DATA_BW_RU_ALLOC, + IEEE80211_RADIOTAP_HE_DATA5_DATA_BW_RU_ALLOC_160MHZ); + break; + default: + WARN_ONCE(1, "Invalid SU BW %d\n", rxvect->ch_bw); + } + } + } else { + he.data1 |= IEEE80211_RADIOTAP_HE_DATA1_FORMAT_TRIG; + } + + /* ensure 2 byte alignment */ + while ((pos - (u8 *)rtap) & 1) + pos++; + rtap->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_HE); + memcpy(pos, &he, sizeof(he)); + pos += sizeof(he); + } + + // Rx Chains + if (hweight32(rxvect->antenna_set) > 1) { + int chain; + unsigned long chains = rxvect->antenna_set; + u8 rssis[4] = {rxvect->rssi1, rxvect->rssi1, rxvect->rssi1, rxvect->rssi1}; + + for_each_set_bit(chain, &chains, IEEE80211_MAX_CHAINS) { + *pos++ = rssis[chain]; + *pos++ = chain; + } + } +} + +/** + * ecrnx_rx_monitor - Build radiotap header for skb an send it to netdev + * + * @ecrnx_hw: main driver data + * @ecrnx_vif: vif that received the buffer + * @skb: sk_buff received + * @hw_rxhdr_ptr: Pointer to HW RX header + * @rtap_len: Radiotap Header length + * + * Add radiotap header to the receved skb and send it to netdev + */ +static int ecrnx_rx_monitor(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + struct sk_buff *skb, struct hw_rxhdr *hw_rxhdr_ptr, + u8 rtap_len) +{ + skb->dev = ecrnx_vif->ndev; + + ECRNX_DBG("%s enter!!", __func__); + if (ecrnx_vif->wdev.iftype != NL80211_IFTYPE_MONITOR) { + netdev_err(ecrnx_vif->ndev, "not a monitor vif\n"); + return -1; + } + + /* Add RadioTap Header */ + ecrnx_rx_add_rtap_hdr(ecrnx_hw, skb, &hw_rxhdr_ptr->hwvect.rx_vect1, + &hw_rxhdr_ptr->phy_info, &hw_rxhdr_ptr->hwvect, + rtap_len, 0, 0); + + skb_reset_mac_header(skb); + skb->ip_summed = CHECKSUM_UNNECESSARY; + skb->pkt_type = PACKET_OTHERHOST; + skb->protocol = htons(ETH_P_802_2); + + netif_receive_skb(skb); + ECRNX_DBG("%s exit!!", __func__); + return 0; +} + +/** +* ecrnx_rx_deferred - Work function to defer processing of buffer that cannot be +* done in ecrnx_rxdataind (that is called in atomic context) +* +* @ws: work field within struct ecrnx_defer_rx +*/ +void ecrnx_rx_deferred(struct work_struct *ws) +{ + struct ecrnx_defer_rx *rx = container_of(ws, struct ecrnx_defer_rx, work); + struct sk_buff *skb; + + while ((skb = skb_dequeue(&rx->sk_list)) != NULL) { + // Currently only management frame can be deferred + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data; + struct ecrnx_defer_rx_cb *rx_cb = (struct ecrnx_defer_rx_cb *)skb->cb; + + if (ieee80211_is_action(mgmt->frame_control) && + (mgmt->u.action.category == 6)) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + struct cfg80211_ft_event_params ft_event; + struct ecrnx_vif *vif = rx_cb->vif; + u8 *action_frame = (u8 *)&mgmt->u.action; + u8 action_code = action_frame[1]; + u16 status_code = *((u16 *)&action_frame[2 + 2 * ETH_ALEN]); + + if ((action_code == 2) && (status_code == 0)) { + ft_event.target_ap = action_frame + 2 + ETH_ALEN; + ft_event.ies = action_frame + 2 + 2 * ETH_ALEN + 2; + ft_event.ies_len = skb->len - (ft_event.ies - (u8 *)mgmt); + ft_event.ric_ies = NULL; + ft_event.ric_ies_len = 0; + cfg80211_ft_event(rx_cb->vif->ndev, &ft_event); + vif->sta.flags |= ECRNX_STA_FT_OVER_DS; + memcpy(vif->sta.ft_target_ap, ft_event.target_ap, ETH_ALEN); + + } +#endif + } else if (ieee80211_is_auth(mgmt->frame_control)) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) + struct cfg80211_ft_event_params ft_event; + struct ecrnx_vif *vif = rx_cb->vif; + ft_event.target_ap = vif->sta.ft_target_ap; + ft_event.ies = mgmt->u.auth.variable; + ft_event.ies_len = (skb->len - + offsetof(struct ieee80211_mgmt, u.auth.variable)); + ft_event.ric_ies = NULL; + ft_event.ric_ies_len = 0; + cfg80211_ft_event(rx_cb->vif->ndev, &ft_event); + vif->sta.flags |= ECRNX_STA_FT_OVER_AIR; +#endif + } else { + netdev_warn(rx_cb->vif->ndev, "Unexpected deferred frame fctl=0x%04x", + mgmt->frame_control); + } + + dev_kfree_skb(skb); + } +} + +/** + * ecrnx_unsup_rx_vec_ind() - IRQ handler callback for %IPC_IRQ_E2A_UNSUP_RX_VEC + * + * LMAC has triggered an IT saying that a rx vector of an unsupported frame has been + * captured and sent to upper layer. Then we need to fill the rx status, create a vendor + * specific header and fill it with the HT packet length. Finally, we need to specify at + * least 2 bytes of data and send the sk_buff to mac80211. + * + * @pthis: Pointer to main driver data + * @hostid: Pointer to IPC elem from e2aradars_pool + */ +u8 ecrnx_unsup_rx_vec_ind(void *pthis, void *hostid) { + struct ecrnx_hw *ecrnx_hw = pthis; + struct ecrnx_ipc_skb_elem *elem = hostid; + struct rx_vector_desc *rx_desc; + struct sk_buff *skb; + struct rx_vector_1 *rx_vect1; + struct phy_channel_info_desc *phy_info; + struct vendor_radiotap_hdr *rtap; + u16 ht_length; + struct ecrnx_vif *ecrnx_vif; + struct rx_vector_desc rx_vect_desc; + u8 rtap_len, vend_rtap_len = sizeof(*rtap); + + dma_sync_single_for_cpu(ecrnx_hw->dev, elem->dma_addr, + sizeof(struct rx_vector_desc), DMA_FROM_DEVICE); + + skb = elem->skb; + if (((struct rx_vector_desc *) (skb->data))->pattern == 0) { + /*sync is needed even if the driver did not modify the memory*/ + dma_sync_single_for_device(ecrnx_hw->dev, elem->dma_addr, + sizeof(struct rx_vector_desc), DMA_FROM_DEVICE); + return -1; + } + + if (ecrnx_hw->monitor_vif == ECRNX_INVALID_VIF) { + /* Unmap will synchronize buffer for CPU */ +#ifndef CONFIG_ECRNX_ESWIN + dma_unmap_single(ecrnx_hw->dev, elem->dma_addr, ecrnx_hw->ipc_env->unsuprxvec_bufsz, + DMA_FROM_DEVICE); +#endif + elem->skb = NULL; + + /* Free skb */ + dev_kfree_skb(skb); + +#ifndef CONFIG_ECRNX_ESWIN + /* Allocate and push a new buffer to fw to replace this one */ + if (ecrnx_ipc_unsup_rx_vec_elem_allocs(ecrnx_hw, elem)) + dev_err(ecrnx_hw->dev, "Failed to alloc new unsupported rx vector buf\n"); + return -1; +#endif + } + + ecrnx_vif = ecrnx_hw->vif_table[ecrnx_hw->monitor_vif]; + skb->dev = ecrnx_vif->ndev; + memcpy(&rx_vect_desc, skb->data, sizeof(rx_vect_desc)); + rx_desc = &rx_vect_desc; + + rx_vect1 = (struct rx_vector_1 *) (rx_desc->rx_vect1); + ecrnx_rx_vector_convert(ecrnx_hw->machw_type, rx_vect1, NULL); + phy_info = (struct phy_channel_info_desc *) (&rx_desc->phy_info); + if (rx_vect1->format_mod >= FORMATMOD_VHT) + ht_length = 0; + else + ht_length = (u16) le32_to_cpu(rx_vect1->ht.length); + + // Reserve space for radiotap + skb_reserve(skb, RADIOTAP_HDR_MAX_LEN); + + /* Fill vendor specific header with fake values */ + rtap = (struct vendor_radiotap_hdr *) skb->data; + rtap->oui[0] = 0x00; + rtap->oui[1] = 0x25; + rtap->oui[2] = 0x3A; + rtap->subns = 0; + rtap->len = sizeof(ht_length); + put_unaligned_le16(ht_length, rtap->data); + vend_rtap_len += rtap->len; + skb_put(skb, vend_rtap_len); + + /* Copy fake data */ + put_unaligned_le16(0, skb->data + vend_rtap_len); + skb_put(skb, UNSUP_RX_VEC_DATA_LEN); + + /* Get RadioTap Header length */ + rtap_len = ecrnx_rx_rtap_hdrlen(rx_vect1, true); + + /* Check headroom space */ + if (skb_headroom(skb) < rtap_len) { + netdev_err(ecrnx_vif->ndev, "not enough headroom %d need %d\n", skb_headroom(skb), rtap_len); + return -1; + } + + /* Add RadioTap Header */ + ecrnx_rx_add_rtap_hdr(ecrnx_hw, skb, rx_vect1, phy_info, NULL, + rtap_len, vend_rtap_len, BIT(0)); + + skb_reset_mac_header(skb); + skb->ip_summed = CHECKSUM_UNNECESSARY; + skb->pkt_type = PACKET_OTHERHOST; + skb->protocol = htons(ETH_P_802_2); + + /* Unmap will synchronize buffer for CPU */ +#ifndef CONFIG_ECRNX_ESWIN + dma_unmap_single(ecrnx_hw->dev, elem->dma_addr, ecrnx_hw->ipc_env->unsuprxvec_bufsz, + DMA_FROM_DEVICE); +#endif + elem->skb = NULL; + + netif_receive_skb(skb); + +#ifndef CONFIG_ECRNX_ESWIN + /* Allocate and push a new buffer to fw to replace this one */ + if (ecrnx_ipc_unsup_rx_vec_elem_allocs(ecrnx_hw, elem)) + netdev_err(ecrnx_vif->ndev, "Failed to alloc new unsupported rx vector buf\n"); +#endif + return 0; +} + +/** + * ecrnx_rxdataind - Process rx buffer + * + * @pthis: Pointer to the object attached to the IPC structure + * (points to struct ecrnx_hw is this case) + * @hostid: Address of the RX descriptor + * + * This function is called for each buffer received by the fw + * + */ +#ifndef CONFIG_ECRNX_ESWIN +u8 ecrnx_rxdataind(void *pthis, void *hostid) +{ + struct ecrnx_hw *ecrnx_hw = pthis; + struct ecrnx_ipc_elem *elem = hostid; + struct hw_rxhdr *hw_rxhdr; + struct rxdesc_tag *rxdesc; + struct ecrnx_vif *ecrnx_vif; + struct sk_buff *skb = NULL; + int rx_buff_idx; + int msdu_offset = sizeof(struct hw_rxhdr) + 2; + + int peek_len = msdu_offset + sizeof(struct ethhdr); + u16_l status; + + REG_SW_SET_PROFILING(ecrnx_hw, SW_PROF_ECRNXDATAIND); + + if(!pthis || !hostid) + { + return -1; + } + + /* Get the ownership of the descriptor */ + dma_sync_single_for_cpu(ecrnx_hw->dev, elem->dma_addr, + sizeof(struct rxdesc_tag), DMA_FROM_DEVICE); + + rxdesc = elem->addr; + status = rxdesc->status; + + /* check that frame is completely uploaded */ + if (!status){ + /* Get the ownership of the descriptor */ + dma_sync_single_for_device(ecrnx_hw->dev, elem->dma_addr, + sizeof(struct rxdesc_tag), DMA_FROM_DEVICE); + return -1; + } + + /* Get the buffer linked with the received descriptor */ + rx_buff_idx = ECRNX_RXBUFF_HOSTID_TO_IDX(rxdesc->host_id); + if (ECRNX_RXBUFF_VALID_IDX(rx_buff_idx)) + skb = ecrnx_hw->rxbuf_elems.skb[rx_buff_idx]; + + if (!skb){ + dev_err(ecrnx_hw->dev, "RX Buff invalid idx [%d]\n", rx_buff_idx); + return -1; + } + + /* Check the pattern */ + if (ECRNX_RXBUFF_PATTERN_GET(skb) != ecrnx_rxbuff_pattern) { + dev_err(ecrnx_hw->dev, "RX Buff Pattern not correct\n"); + BUG(); + } + + /* Check if we need to delete the buffer */ + if (status & RX_STAT_DELETE) { + /* Remove the SK buffer from the rxbuf_elems table */ + ecrnx_ipc_rxbuf_elem_pull(ecrnx_hw, skb); + /* Free the buffer */ + dev_kfree_skb(skb); + goto end; + } + + /* Check if we need to forward the buffer coming from a monitor interface */ + if (status & RX_STAT_MONITOR) { + struct sk_buff *skb_monitor; + struct hw_rxhdr hw_rxhdr_copy; + u8 rtap_len; + u16 frm_len; + + //Check if monitor interface exists and is open + ecrnx_vif = ecrnx_rx_get_vif(ecrnx_hw, ecrnx_hw->monitor_vif); + if (!ecrnx_vif) { + dev_err(ecrnx_hw->dev, "Received monitor frame but there is no monitor interface open\n"); + goto check_len_update; + } + + hw_rxhdr = (struct hw_rxhdr *)skb->data; + ecrnx_rx_vector_convert(ecrnx_hw->machw_type, + &hw_rxhdr->hwvect.rx_vect1, + &hw_rxhdr->hwvect.rx_vect2); + rtap_len = ecrnx_rx_rtap_hdrlen(&hw_rxhdr->hwvect.rx_vect1, false); + + // Move skb->data pointer to MAC Header or Ethernet header + skb->data += msdu_offset; + + //Save frame length + frm_len = le32_to_cpu(hw_rxhdr->hwvect.len); + + // Reserve space for frame + skb->len = frm_len; + + if (status == RX_STAT_MONITOR) { + /* Remove the SK buffer from the rxbuf_elems table. It will also + unmap the buffer and then sync the buffer for the cpu */ + ecrnx_ipc_rxbuf_elem_pull(ecrnx_hw, skb); + + //Check if there is enough space to add the radiotap header + if (skb_headroom(skb) > rtap_len) { + + skb_monitor = skb; + + //Duplicate the HW Rx Header to override with the radiotap header + memcpy(&hw_rxhdr_copy, hw_rxhdr, sizeof(hw_rxhdr_copy)); + + hw_rxhdr = &hw_rxhdr_copy; + } else { + //Duplicate the skb and extend the headroom + skb_monitor = skb_copy_expand(skb, rtap_len, 0, GFP_ATOMIC); + + //Reset original skb->data pointer + skb->data = (void*) hw_rxhdr; + } + } + else + { + #ifdef CONFIG_ECRNX_MON_DATA + // Check if MSDU + if (!hw_rxhdr->flags_is_80211_mpdu) { + // MSDU + //Extract MAC header + u16 machdr_len = hw_rxhdr->mac_hdr_backup.buf_len; + u8* machdr_ptr = hw_rxhdr->mac_hdr_backup.buffer; + + //Pull Ethernet header from skb + skb_pull(skb, sizeof(struct ethhdr)); + + // Copy skb and extend for adding the radiotap header and the MAC header + skb_monitor = skb_copy_expand(skb, + rtap_len + machdr_len, + 0, GFP_ATOMIC); + + //Reserve space for the MAC Header + skb_push(skb_monitor, machdr_len); + + //Copy MAC Header + memcpy(skb_monitor->data, machdr_ptr, machdr_len); + + //Update frame length + frm_len += machdr_len - sizeof(struct ethhdr); + } else { + // MPDU + skb_monitor = skb_copy_expand(skb, rtap_len, 0, GFP_ATOMIC); + } + + //Reset original skb->data pointer + skb->data = (void*) hw_rxhdr; + #else + //Reset original skb->data pointer + skb->data = (void*) hw_rxhdr; + + wiphy_err(ecrnx_hw->wiphy, "RX status %d is invalid when MON_DATA is disabled\n", status); + goto check_len_update; + #endif + } + + skb_reset_tail_pointer(skb); + skb->len = 0; + skb_reset_tail_pointer(skb_monitor); + skb_monitor->len = 0; + + skb_put(skb_monitor, frm_len); + if (ecrnx_rx_monitor(ecrnx_hw, ecrnx_vif, skb_monitor, hw_rxhdr, rtap_len)) + dev_kfree_skb(skb_monitor); + + if (status == RX_STAT_MONITOR) { + status |= RX_STAT_ALLOC; + if (skb_monitor != skb) { + dev_kfree_skb(skb); + } + } + } + +check_len_update: + /* Check if we need to update the length */ + if (status & RX_STAT_LEN_UPDATE) { + dma_addr_t dma_addr = ECRNX_RXBUFF_DMA_ADDR_GET(skb); + dma_sync_single_for_cpu(ecrnx_hw->dev, dma_addr, + peek_len, DMA_FROM_DEVICE); + + hw_rxhdr = (struct hw_rxhdr *)skb->data; + + hw_rxhdr->hwvect.len = rxdesc->frame_len; + + if (status & RX_STAT_ETH_LEN_UPDATE) { + /* Update Length Field inside the Ethernet Header */ + struct ethhdr *hdr = (struct ethhdr *)((u8 *)hw_rxhdr + msdu_offset); + + hdr->h_proto = htons(rxdesc->frame_len - sizeof(struct ethhdr)); + } + + dma_sync_single_for_device(ecrnx_hw->dev, dma_addr, + peek_len, DMA_BIDIRECTIONAL); + goto end; + } + + /* Check if it must be discarded after informing upper layer */ + if (status & RX_STAT_SPURIOUS) { + struct ieee80211_hdr *hdr; + + /* Read mac header to obtain Transmitter Address */ + ecrnx_ipc_rxbuf_elem_sync(ecrnx_hw, skb, msdu_offset + sizeof(*hdr)); + + hw_rxhdr = (struct hw_rxhdr *)skb->data; + hdr = (struct ieee80211_hdr *)(skb->data + msdu_offset); + ecrnx_vif = ecrnx_rx_get_vif(ecrnx_hw, hw_rxhdr->flags_vif_idx); + if (ecrnx_vif) { + cfg80211_rx_spurious_frame(ecrnx_vif->ndev, hdr->addr2, GFP_ATOMIC); + } + ecrnx_ipc_rxbuf_elem_repush(ecrnx_hw, skb); + goto end; + } + + /* Check if we need to forward the buffer */ + if (status & RX_STAT_FORWARD) { + struct ecrnx_sta *sta = NULL; + + /* Remove the SK buffer from the rxbuf_elems table. It will also + unmap the buffer and then sync the buffer for the cpu */ + ecrnx_ipc_rxbuf_elem_pull(ecrnx_hw, skb); + + hw_rxhdr = (struct hw_rxhdr *)skb->data; + ecrnx_rx_vector_convert(ecrnx_hw->machw_type, + &hw_rxhdr->hwvect.rx_vect1, + &hw_rxhdr->hwvect.rx_vect2); + skb_reserve(skb, msdu_offset); + skb_put(skb, le32_to_cpu(hw_rxhdr->hwvect.len)); + if (hw_rxhdr->flags_sta_idx != ECRNX_INVALID_STA) { + sta = &ecrnx_hw->sta_table[hw_rxhdr->flags_sta_idx]; + ecrnx_rx_statistic(ecrnx_hw, hw_rxhdr, sta); + } + + if (hw_rxhdr->flags_is_80211_mpdu) { + ecrnx_rx_mgmt_any(ecrnx_hw, skb, hw_rxhdr); + } else { + ecrnx_vif = ecrnx_rx_get_vif(ecrnx_hw, hw_rxhdr->flags_vif_idx); + + if (!ecrnx_vif) { + dev_err(ecrnx_hw->dev, "Frame received but no active vif (%d)", + hw_rxhdr->flags_vif_idx); + dev_kfree_skb(skb); + goto check_alloc; + } + + if (sta) { + + if (sta->vlan_idx != ecrnx_vif->vif_index) { + ecrnx_vif = ecrnx_hw->vif_table[sta->vlan_idx]; + if (!ecrnx_vif) { + dev_kfree_skb(skb); + goto check_alloc; + } + } + + if (hw_rxhdr->flags_is_4addr && !ecrnx_vif->use_4addr) { + cfg80211_rx_unexpected_4addr_frame(ecrnx_vif->ndev, + sta->mac_addr, GFP_ATOMIC); + } + } + + skb->priority = 256 + hw_rxhdr->flags_user_prio; + if (!ecrnx_rx_data_skb(ecrnx_hw, ecrnx_vif, skb, hw_rxhdr)) + dev_kfree_skb(skb); + } + } + +check_alloc: + /* Check if we need to allocate a new buffer */ + if ((status & RX_STAT_ALLOC) && + ecrnx_ipc_rxbuf_elem_allocs(ecrnx_hw)) { + dev_err(ecrnx_hw->dev, "Failed to alloc new RX buf\n"); + } + +end: + REG_SW_CLEAR_PROFILING(ecrnx_hw, SW_PROF_ECRNXDATAIND); + + /* Reset and repush descriptor to FW */ + ecrnx_ipc_rxdesc_elem_repush(ecrnx_hw, elem); + + return 0; +} + +#else + +#ifdef CONFIG_ESWIN_RX_REORDER + +static bool ecrnx_rx_data_pn_check(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb, u8_l vif_idx, u8_l sta_idx, u8_l tid, u8_l is_ga) +{ + struct ecrnx_sta *sta; + struct ecrnx_vif *vif; + u64_l *last_pn; + u64_l pn; + + memcpy(&pn, skb->data, sizeof(u64_l)); + + if (is_ga) { + last_pn = &ecrnx_hw->vif_table[vif_idx]->rx_pn[tid]; + } else if(ECRNX_INVALID_STA != sta_idx) { + last_pn = &ecrnx_hw->sta_table[sta_idx].rx_pn[tid]; + } else + { + return true; + } + + //printk("sta_idx:%d tid:%d pn:%llu last:%llu\n ", sta_idx, tid, pn, *last_pn); + if (pn > (*last_pn)){ + *last_pn = pn; + return true; + } + + return false; +} + +void ecrnx_rx_reord_msdu_free(spinlock_t *lock, struct list_head *q, struct list_head *list) +{ + spin_lock_bh(lock); + list_add(list, q); + spin_unlock_bh(lock); +} + +struct reord_msdu_info *ecrnx_rx_reord_msdu_alloc(spinlock_t *lock, struct list_head *q) +{ + struct reord_msdu_info *pmsdu; + + spin_lock_bh(lock); + if (list_empty(q)) { + spin_unlock_bh(lock); + return NULL; + } + + pmsdu = list_entry(q->next, struct reord_msdu_info, rx_msdu_list); + list_del_init(q->next); + spin_unlock_bh(lock); + + return pmsdu; +} + +int ecrnx_rx_reord_single_msdu(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, struct reord_msdu_info *pmsdu) +{ + struct list_head *rx_msdu_free_list = NULL; + struct sk_buff *skb = NULL; + struct hw_rxhdr* hw_rxhdr; + //static u16_l last_sn[TID_MAX] = {0}; + + rx_msdu_free_list = &ecrnx_hw->rx_msdu_free_list; + skb = pmsdu->skb; + if (skb == NULL) { + ECRNX_ERR("skb is NULL\n"); + return -1; + } + + if (pmsdu->need_pn_check) { + if (ecrnx_rx_data_pn_check(ecrnx_hw, skb, pmsdu->hw_rxhdr->flags_vif_idx, pmsdu->hw_rxhdr->flags_sta_idx, pmsdu->tid, pmsdu->is_ga)){ + skb_pull(skb, 8); + } else { + ECRNX_DBG("rx_data_check_pn error\n"); + dev_kfree_skb(skb); + goto end; + } + } +#if 0 + if ((last_sn[pmsdu->tid] != pmsdu->sn) && ((last_sn[pmsdu->tid] + 1) % 4096 != pmsdu->sn)) + { + ECRNX_PRINT("miss[%d] last:%d sn=%d\n",pmsdu->tid,last_sn[pmsdu->tid], pmsdu->sn); + } + + last_sn[pmsdu->tid] = pmsdu->sn; +#endif + if (!ecrnx_rx_data_skb(ecrnx_hw, ecrnx_vif, skb, pmsdu->hw_rxhdr)) + dev_kfree_skb(skb); + +end: + pmsdu->skb = NULL; + ecrnx_rx_reord_msdu_free(&ecrnx_hw->rx_msdu_free_lock, rx_msdu_free_list, &pmsdu->rx_msdu_list); + + return 0; +} + + +void ecrnx_rx_reord_timer_update(struct ecrnx_hw *ecrnx_hw, struct reord_cntrl *reord_cntrl, int force) +{ + struct list_head *phead, *plist; + struct reord_msdu_info *pmsdu; + bool update = false; + + if (force == true) { + phead = &reord_cntrl->reord_list; + if (list_empty(phead)) { + goto end; + } + + plist = phead->next; + pmsdu = list_entry(plist, struct reord_msdu_info, reord_pending_list); + reord_cntrl->win_start = pmsdu->sn; + } + + phead = &reord_cntrl->reord_list; + if (list_empty(phead)) { + goto end; + } + + list_for_each_entry(pmsdu, phead, reord_pending_list) { + if (!SN_LESS(reord_cntrl->win_start, pmsdu->sn)) { + if (SN_EQUAL(reord_cntrl->win_start, pmsdu->sn)) { + reord_cntrl->win_start = (reord_cntrl->win_start + 1) & 0xFFF; + } + } else { + update = true; + break; + } + } + +end: + if (update == true) { + if (!timer_pending(&reord_cntrl->reord_timer)) { + mod_timer(&reord_cntrl->reord_timer, jiffies + msecs_to_jiffies(ECRNX_REORD_TIMEOUT)); + } + } else { + if(timer_pending(&reord_cntrl->reord_timer)) { + del_timer(&reord_cntrl->reord_timer); + } + } + +} + +void ecrnx_rx_reord_list_flush(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, struct reord_cntrl *reord_cntrl) +{ + struct list_head *phead, *plist; + struct reord_msdu_info *pmsdu; + + phead = &reord_cntrl->reord_list; + while (1) { + if (list_empty(phead)) { + break; + } + + plist = phead->next; + pmsdu = list_entry(plist, struct reord_msdu_info, reord_pending_list); + + if (!SN_LESS(reord_cntrl->win_start, pmsdu->sn)) { + list_del_init(&(pmsdu->reord_pending_list)); + ecrnx_rx_reord_single_msdu(ecrnx_hw, ecrnx_vif, pmsdu); + } else { + break; + } + } +} + + +void ecrnx_rx_reord_timeout_handler(struct timer_list *t) +{ + struct reord_cntrl *reord_cntrl = from_timer(reord_cntrl, t, reord_timer); + + if(!reord_cntrl->valid || !reord_cntrl->active) + return ; + + if(!work_pending(&reord_cntrl->reord_timer_work)) + schedule_work(&reord_cntrl->reord_timer_work); + +} + +void ecrnx_rx_reord_timeout_worker(struct work_struct *work) +{ + struct reord_cntrl *reord_cntrl = container_of(work, struct reord_cntrl, reord_timer_work); + struct ecrnx_hw *ecrnx_hw = reord_cntrl->ecrnx_hw; + struct ecrnx_vif *ecrnx_vif = reord_cntrl->ecrnx_vif; + + if(!reord_cntrl->valid || !reord_cntrl->active) + return ; + + spin_lock_bh(&reord_cntrl->reord_list_lock); + + ecrnx_rx_reord_timer_update(ecrnx_hw, reord_cntrl, true); + + ecrnx_rx_reord_list_flush(ecrnx_hw, ecrnx_vif, reord_cntrl); + spin_unlock_bh(&reord_cntrl->reord_list_lock); + + return ; +} + +void ecrnx_rx_reord_sta_init(struct ecrnx_hw* ecrnx_hw, struct ecrnx_vif *ecrnx_vif, u8 sta_idx) +{ + struct reord_cntrl *reord_cntrl = NULL; + u32_l i = 0; + + ECRNX_DBG("%s sta_idx:%d\n", __func__, sta_idx); + for (i = 0; i < TID_MAX; i++) { + reord_cntrl = &ecrnx_hw->sta_table[sta_idx].reord_cntrl[i]; + if (!reord_cntrl->valid) { + reord_cntrl->active = true; + reord_cntrl->win_start = 0xffff; + reord_cntrl->win_size = ECRNX_REORD_WINSIZE; + reord_cntrl->ecrnx_hw = ecrnx_hw; + reord_cntrl->ecrnx_vif = ecrnx_vif; + INIT_LIST_HEAD(&reord_cntrl->reord_list); + spin_lock_init(&reord_cntrl->reord_list_lock); + timer_setup(&reord_cntrl->reord_timer, ecrnx_rx_reord_timeout_handler, 0); + INIT_WORK(&reord_cntrl->reord_timer_work, ecrnx_rx_reord_timeout_worker); + reord_cntrl->valid = true; + } + } + +} + +int ecrnx_rx_reord_tid_flush(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, struct sk_buff *skb, u8 sta_idx, u8 tid) +{ + struct reord_cntrl *reord_cntrl; + struct list_head *phead, *plist; + struct reord_msdu_info *pmsdu; + + if (sta_idx == ECRNX_INVALID_STA) + return -1; + + reord_cntrl = &ecrnx_hw->sta_table[sta_idx].reord_cntrl[tid]; + + if(!reord_cntrl->valid || !reord_cntrl->active) + return -1; + + spin_lock(&reord_cntrl->reord_list_lock); + phead = &reord_cntrl->reord_list; + while (1) { + if (list_empty(phead)) { + break; + } + plist = phead->next; + pmsdu = list_entry(plist, struct reord_msdu_info, reord_pending_list); + ecrnx_rx_reord_single_msdu(ecrnx_hw, ecrnx_vif, pmsdu); + list_del_init(&(pmsdu->reord_pending_list)); + } + + //printk("flush:sta_idx:%d tid=%d \n", sta_idx,tid); + reord_cntrl->active = false; + spin_unlock(&reord_cntrl->reord_list_lock); + if (timer_pending(&reord_cntrl->reord_timer)) + del_timer_sync(&reord_cntrl->reord_timer); + //cancel_work_sync(&reord_cntrl->reord_timer_work); + + return 0; +} + +void ecrnx_rx_reord_sta_deinit(struct ecrnx_hw* ecrnx_hw, u8 sta_idx, bool is_del) +{ + struct reord_cntrl *reord_cntrl = NULL; + u32_l i = 0; + + if (ecrnx_hw == NULL || sta_idx >= (NX_REMOTE_STA_MAX + NX_VIRT_DEV_MAX)) { + return; + } + ECRNX_DBG("%s sta_idx:%d\n", __func__, sta_idx); + + for (i=0; i < TID_MAX; i++) { + struct reord_msdu_info *req, *next; + reord_cntrl = &ecrnx_hw->sta_table[sta_idx].reord_cntrl[i]; + if (reord_cntrl->valid) { + reord_cntrl->valid = false; + if(reord_cntrl->active){ + reord_cntrl->active = false; + if (timer_pending(&reord_cntrl->reord_timer)) { + del_timer_sync(&reord_cntrl->reord_timer); + } + + if (!is_del) { + cancel_work_sync(&reord_cntrl->reord_timer_work); + } + } + + spin_lock(&reord_cntrl->reord_list_lock); + list_for_each_entry_safe(req, next, &reord_cntrl->reord_list, reord_pending_list) { + list_del_init(&req->reord_pending_list); + if(req->skb != NULL) + dev_kfree_skb(req->skb); + req->skb = NULL; + ecrnx_rx_reord_msdu_free(&ecrnx_hw->rx_msdu_free_lock, &ecrnx_hw->rx_msdu_free_list, &req->rx_msdu_list); + } + spin_unlock(&reord_cntrl->reord_list_lock); + } + } + +} + +static struct reord_msdu_info *ecrnx_rx_reord_queue_init(struct list_head *q, int qsize) +{ + int i; + struct reord_msdu_info *req, *reqs; + + reqs = vmalloc(qsize*sizeof(struct reord_msdu_info)); + if (reqs == NULL) + return NULL; + + req = reqs; + for (i = 0; i < qsize; i++) + { + INIT_LIST_HEAD(&req->rx_msdu_list); + list_add(&req->rx_msdu_list, q); + req++; + } + + return reqs; +} + +void ecrnx_rx_reord_deinit(struct ecrnx_hw *ecrnx_hw) +{ + struct reord_msdu_info *req, *next; + u32_l sta_idx; + + ECRNX_DBG("%s\n", __func__); + for (sta_idx = 0; sta_idx < NX_REMOTE_STA_MAX + NX_VIRT_DEV_MAX; sta_idx++) + { + if (ecrnx_hw->sta_table[sta_idx].valid) + { + ecrnx_rx_reord_sta_deinit(ecrnx_hw, sta_idx, false); + } + } + list_for_each_entry_safe(req, next, &ecrnx_hw->rx_msdu_free_list, rx_msdu_list) { + list_del_init(&req->rx_msdu_list); + } + + if (ecrnx_hw->rx_reord_buf) + vfree(ecrnx_hw->rx_reord_buf); +} + +void ecrnx_rx_reord_init(struct ecrnx_hw *ecrnx_hw) +{ + ECRNX_DBG("%s\n", __func__); + INIT_LIST_HEAD(&ecrnx_hw->rx_msdu_free_list); + spin_lock_init(&ecrnx_hw->rx_msdu_free_lock); + ecrnx_hw->rx_reord_buf = ecrnx_rx_reord_queue_init(&ecrnx_hw->rx_msdu_free_list, ECRNX_REORD_RX_MSDU_CNT); + spin_lock_init(&ecrnx_hw->rx_reord_lock); + INIT_LIST_HEAD(&ecrnx_hw->rx_reord_list); + +} + +int ecrnx_rx_reord_sn_check(struct reord_cntrl *reord_cntrl, u16 sn) +{ + u16 win_size = reord_cntrl->win_size; + u16 win_end = (reord_cntrl->win_start + win_size -1) & 0xFFF; + + if (reord_cntrl->win_start == 0xFFFF) { + reord_cntrl->win_start = sn; + } + + if (SN_LESS(sn, reord_cntrl->win_start)) { + return -1; + } + + if (SN_EQUAL(sn, reord_cntrl->win_start)){ + reord_cntrl->win_start = (reord_cntrl->win_start + 1) & 0xFFF; + } else if (SN_LESS(win_end, sn)) { + if (sn >= (win_size-1)) + reord_cntrl->win_start = sn-(win_size-1); + else + reord_cntrl->win_start = 0xFFF - (win_size - (sn + 1)) + 1; + } + + return 0; +} + +int ecrnx_rx_reord_msdu_insert(struct reord_cntrl *reord_cntrl, struct reord_msdu_info *pmsdu) +{ + struct list_head *preord_list = &reord_cntrl->reord_list; + struct list_head *phead, *plist; + struct reord_msdu_info *nextmsdu; + +//first time:not any prframe in preord_list, so phead = phead->next + phead = preord_list; + plist = phead->next; + + while(phead != plist) { + nextmsdu = list_entry(plist, struct reord_msdu_info, reord_pending_list); + if (SN_LESS(nextmsdu->sn, pmsdu->sn)) { + plist = plist->next; + } else if (SN_EQUAL(nextmsdu->sn, pmsdu->sn)){ + return -1; + } else { + break; + } + } + + list_add_tail(&(pmsdu->reord_pending_list), plist); + + return 0; +} + +void ecrnx_rx_reord_check(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, struct hw_rxhdr *hw_rxhdr, struct sk_buff *skb, struct rxu_stat_mm* orignal_rxu_state) +{ + int ret=0; + struct reord_msdu_info *pmsdu; + struct reord_cntrl *reord_cntrl; + + if (ecrnx_vif == NULL || skb->len <= 14) { + dev_kfree_skb(skb); + return ; + } + + pmsdu = ecrnx_rx_reord_msdu_alloc(&ecrnx_hw->rx_msdu_free_lock, &ecrnx_hw->rx_msdu_free_list); + if (!pmsdu) { + dev_err(ecrnx_hw->dev, "ecrnx_rx_reord_msdu_alloc fail\n"); + dev_kfree_skb(skb); + return ; + } + + INIT_LIST_HEAD(&pmsdu->reord_pending_list); + pmsdu->hw_rxhdr = hw_rxhdr; + pmsdu->sn = orignal_rxu_state->sn; + pmsdu->tid = orignal_rxu_state->tid; + pmsdu->is_ga = orignal_rxu_state->is_ga; + pmsdu->skb = skb; + pmsdu->need_pn_check = orignal_rxu_state->need_pn_check; + + if (hw_rxhdr->flags_sta_idx != ECRNX_INVALID_STA) { + reord_cntrl = &ecrnx_hw->sta_table[hw_rxhdr->flags_sta_idx].reord_cntrl[orignal_rxu_state->tid]; + pmsdu->preorder_ctrl = reord_cntrl; + if(reord_cntrl->valid) { + if (!reord_cntrl->active) { + reord_cntrl->active = true; + reord_cntrl->win_start = 0xffff; + //reord_cntrl->win_size = ECRNX_REORD_WINSIZE; + //reord_cntrl->ecrnx_hw = ecrnx_hw; + //reord_cntrl->ecrnx_vif = ecrnx_vif; + } + } else { + ECRNX_PRINT("reord_cntrl invalid sta:%d sn:%d start:%d \n", hw_rxhdr->flags_sta_idx , pmsdu->sn, reord_cntrl->win_start); + ecrnx_rx_reord_single_msdu(ecrnx_hw, ecrnx_vif, pmsdu); + return ; + } + } else { + ECRNX_PRINT("sta_idx invalid sta:%d sn:%d start:%d \n", hw_rxhdr->flags_sta_idx , pmsdu->sn, reord_cntrl->win_start); + ecrnx_rx_reord_single_msdu(ecrnx_hw, ecrnx_vif, pmsdu); + return ; + } + + spin_lock(&reord_cntrl->reord_list_lock); + if (ecrnx_rx_reord_sn_check(reord_cntrl, pmsdu->sn)) { + //printk("%s discard sn:%d s:%d \n", __func__, pmsdu->sn, reord_cntrl->win_start); + //ecrnx_rx_reord_single_msdu(ecrnx_hw, ecrnx_vif, pmsdu); + spin_unlock(&reord_cntrl->reord_list_lock); + goto discard; + } + //printk("start:%d %d\n",reord_cntrl->win_start, pmsdu->sn); + if (ecrnx_rx_reord_msdu_insert(reord_cntrl, pmsdu)) { + spin_unlock(&reord_cntrl->reord_list_lock); + goto discard; + } + + ecrnx_rx_reord_timer_update(ecrnx_hw, reord_cntrl, false); + + ecrnx_rx_reord_list_flush(ecrnx_hw, ecrnx_vif, reord_cntrl); + + spin_unlock(&reord_cntrl->reord_list_lock); + + return ; + +discard: + if (pmsdu->skb) { + dev_kfree_skb(pmsdu->skb); + pmsdu->skb = NULL; + } + + ecrnx_rx_reord_msdu_free(&ecrnx_hw->rx_msdu_free_lock, &ecrnx_hw->rx_msdu_free_list, &pmsdu->rx_msdu_list); +} +#endif + + +u8 ecrnx_rx_agg_data_ind(struct ecrnx_hw *ecrnx_hw, u16_l status, struct sk_buff* skb, int msdu_offset) +{ + struct hw_rxhdr *hw_rxhdr = NULL; + struct ecrnx_vif *ecrnx_vif = NULL; + hw_rxhdr = (struct hw_rxhdr*)skb->data; + +#if 0 + ECRNX_DBG("[eswin_agg] hw_vect_len: %d , rxu_stat_mm: %d \n", sizeof(struct hw_rxhdr), sizeof(struct rxu_stat_mm)); + ECRNX_DBG("[eswin_agg] rcv_frm_len: %d \n", hw_rxhdr->hwvect.len); + ECRNX_DBG("%s, parttern:0x%2x, status:%d !!", __func__, hw_rxhdr->pattern, status); +#endif + /* Check the pattern */ + if (hw_rxhdr->pattern != ecrnx_rxbuff_pattern) { + dev_err(ecrnx_hw->dev, "RX Buff Pattern not correct, pattern (%x), status %d\n", hw_rxhdr->pattern, status); + print_hex_dump(KERN_DEBUG, DBG_PREFIX_PAT, DUMP_PREFIX_NONE, 16, 1, hw_rxhdr, skb->len, false); + BUG(); + } + + /* Check if we need to delete the buffer */ + if (status & RX_STAT_DELETE) { + dev_kfree_skb(skb); + goto end; + } + /* Check if we need to forward the buffer */ + else if (status & RX_STAT_FORWARD) { + + /* Remove the SK buffer from the rxbuf_elems table. It will also + unmap the buffer and then sync the buffer for the cpu */ + //ecrnx_ipc_rxbuf_elem_pull(ecrnx_hw, skb); + + hw_rxhdr = (struct hw_rxhdr *)skb->data; + ecrnx_rx_vector_convert(ecrnx_hw->machw_type, + &hw_rxhdr->hwvect.rx_vect1, + &hw_rxhdr->hwvect.rx_vect2); + + //skb_reserve(skb, msdu_offset); + //ECRNX_DBG("[eswin_agg]before pull skb len: %d \n", skb->len); + skb_pull(skb, msdu_offset); + //ECRNX_DBG("[eswin_agg]after pull skb len: %d \n", skb->len); + + if (hw_rxhdr->flags_is_80211_mpdu) { + //ECRNX_DBG("[eswin_agg]recv mgmt\n"); + ecrnx_rx_mgmt_any(ecrnx_hw, skb, hw_rxhdr); + } else { + ecrnx_vif = ecrnx_rx_get_vif(ecrnx_hw, hw_rxhdr->flags_vif_idx); + //ECRNX_DBG("[eswin_agg] flags vif idx1:%d, vif: 0x%x \n", hw_rxhdr->flags_vif_idx, ecrnx_vif); + if (!ecrnx_vif) { + dev_err(ecrnx_hw->dev, "Frame received but no active vif (%d)", + hw_rxhdr->flags_vif_idx); + dev_kfree_skb(skb); + ECRNX_ERR("[agg] check_alloc, %d !!", __LINE__); + goto exit_no_free; + } + + if (hw_rxhdr->flags_sta_idx != ECRNX_INVALID_STA) { + struct ecrnx_sta *sta; + + sta = &ecrnx_hw->sta_table[hw_rxhdr->flags_sta_idx]; + ecrnx_rx_statistic(ecrnx_hw, hw_rxhdr, sta); + //ECRNX_DBG("[eswin_agg] sta idx:%d, vif idx: %d \n", sta->vlan_idx, ecrnx_vif->vif_index); + if (sta->vlan_idx != ecrnx_vif->vif_index) { + ecrnx_vif = ecrnx_hw->vif_table[sta->vlan_idx]; + if (!ecrnx_vif) { + dev_kfree_skb(skb); + ECRNX_ERR("[agg] check_alloc, %d !!", __LINE__); + goto exit_no_free; + } + } + + if (hw_rxhdr->flags_is_4addr && !ecrnx_vif->use_4addr) { + cfg80211_rx_unexpected_4addr_frame(ecrnx_vif->ndev, + sta->mac_addr, GFP_ATOMIC); + } + } + + skb->priority = 256 + hw_rxhdr->flags_user_prio; + if (!ecrnx_rx_data_skb(ecrnx_hw, ecrnx_vif, skb, hw_rxhdr)){ + dev_kfree_skb(skb); + dev_err(ecrnx_hw->dev, "ecrnx_rx_data_skb_sdio error \n"); + } + } + } + +exit_no_free: + /* Check if we need to allocate a new buffer */ + //dev_err(ecrnx_hw->dev, "Failed to alloc new RX buf\n"); + +end: + /* Reset and repush descriptor to FW */ + //sdio_rx_buf_repush(&orignal_rxu_state, &orignal_rx_hd, skb); + + return 0; +} + +static int ecrnx_set_station_info(struct ecrnx_vif *vif, const u8 *mac, struct rx_vector_1 *rx_vect1) +{ + struct ecrnx_sta *sta = NULL; + static u32 rx_pkts = 0x1ff; + static u32 tx_pkts = 0x2ff; + static u64 rx_bytes = 0x3ff; + static u64 tx_bytes = 0x4ff; + + if (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_MONITOR) + return -EINVAL; + else if ((ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_STATION) || + (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_P2P_CLIENT)) { + if (vif->sta.ap && ether_addr_equal(vif->sta.ap->mac_addr, mac)) + sta = vif->sta.ap; + } + else + { + struct ecrnx_sta *sta_iter; + list_for_each_entry(sta_iter, &vif->ap.sta_list, list) { + if (sta_iter->valid && ether_addr_equal(sta_iter->mac_addr, mac)) { + sta = sta_iter; + break; + } + } + } + + if (sta) + { + struct ecrnx_sta_stats *stats = &sta->stats; + + stats->rx_bytes = rx_bytes++; + stats->tx_bytes = tx_bytes++; + stats->rx_pkts = rx_pkts++; + stats->tx_pkts = tx_pkts++; + memcpy(&stats->last_rx.rx_vect1, rx_vect1, sizeof(struct rx_vector_1)); + } + + return 0; +} + +u8 ecrnx_rxdataind(void *pthis, void *hostid) +{ + struct ecrnx_hw *ecrnx_hw = pthis; + struct hw_rxhdr *hw_rxhdr = NULL; + struct ecrnx_vif *ecrnx_vif = NULL; + int msdu_offset = sizeof(struct hw_rxhdr); + u16_l status; + + struct rxu_stat_mm* orignal_rxu_state = NULL; + struct sk_buff *skb = NULL, *frg_ctrl_skb = NULL; + + if(!pthis || !hostid){ + ECRNX_ERR("ecrnx_rxdataind error! \n"); + return 0; + } + + spin_lock_bh(&ecrnx_hw->rx_lock); + ecrnx_hw->data_rx++; + skb = (struct sk_buff *)hostid; + orignal_rxu_state = (struct rxu_stat_mm*)skb->data; + skb_pull(skb, sizeof(struct rxu_stat_mm)); + +#ifdef CONFIG_ECRNX_ESWIN + if (orignal_rxu_state->real_offset) + { + msdu_offset += orignal_rxu_state->real_offset; + //ECRNX_DBG("orignal_rxu_state->real_offset = %hu, msdu_offset:%d \n", orignal_rxu_state->real_offset, msdu_offset); + } + + if(orignal_rxu_state->fragment_flag && ((orignal_rxu_state->status & RX_STAT_FORWARD) || (orignal_rxu_state->status & RX_STAT_DELETE))) { //defrag frame + struct defrag_elem *defrag_elem_rx = NULL, *next = NULL; + + if(list_empty(&ecrnx_hw->defrag_rx_list)){ + ECRNX_ERR("error defrag_rx_list is epmty!!! \n"); + dev_kfree_skb(skb); + goto end; + } + + ECRNX_DBG("[eswin_agg]%s enter, tid:0x%x, fragment_flag:0x%llx, status:0x%x \n", __func__, orignal_rxu_state->tid, orignal_rxu_state->fragment_flag, orignal_rxu_state->status); + if(orignal_rxu_state->status & RX_STAT_DELETE){ + /*delete all the same tid frame*/ + list_for_each_entry_safe(defrag_elem_rx, next, &ecrnx_hw->defrag_rx_list, head) { + ECRNX_DBG("[eswin_agg]delete: sn1:%d, sn2:%d, tid1:%d, skb:0x%08x \n",defrag_elem_rx->sn, orignal_rxu_state->sn, defrag_elem_rx->tid, defrag_elem_rx->skb); + if((defrag_elem_rx->tid == orignal_rxu_state->tid) && (defrag_elem_rx->sn == orignal_rxu_state->sn)){ ///==sn + dev_kfree_skb(defrag_elem_rx->skb); + list_del(&defrag_elem_rx->head); + kfree(defrag_elem_rx); + } + } + dev_kfree_skb(skb); + goto end; + } + else if(orignal_rxu_state->status & RX_STAT_FORWARD){ + struct defrag_elem *defrag_elem_rx = NULL, *next = NULL; + u32_l recv_len = 0, offset_len = 0, hw_rxhdr_len = sizeof(*hw_rxhdr); + unsigned char* skb_ptr = NULL; + int fg_first = 1; + int fg_total_len = 0; + + /*caculate the defrag frames total lens*/ + list_for_each_entry_safe(defrag_elem_rx, next, &ecrnx_hw->defrag_rx_list, head) { + if((defrag_elem_rx->tid == orignal_rxu_state->tid) && (defrag_elem_rx->sn == orignal_rxu_state->sn)){ + recv_len += defrag_elem_rx->skb->len; + } + } + + frg_ctrl_skb = skb; + /*alloc a new skb, and put the same tid frame to the new skb*/ + skb = dev_alloc_skb(recv_len); + + list_for_each_entry_safe(defrag_elem_rx, next, &ecrnx_hw->defrag_rx_list, head) { + ECRNX_DBG("[eswin_agg]forward: sn1:%d, sn2:%d, tid1:%d, skb:0x%p \n",defrag_elem_rx->sn, orignal_rxu_state->sn, defrag_elem_rx->tid, defrag_elem_rx->skb); + if((defrag_elem_rx->tid == orignal_rxu_state->tid) && (defrag_elem_rx->sn == orignal_rxu_state->sn)){ + if (fg_first) { + offset_len = 0; + } else { + /*first skb should include the hw_rxhdr, other's not need*/ + offset_len = hw_rxhdr_len; + offset_len += sizeof(struct ethhdr); + } + offset_len += defrag_elem_rx->real_offset; + skb_ptr = skb_put(skb, defrag_elem_rx->skb->len - offset_len); + if (fg_first) { + memcpy(skb_ptr, defrag_elem_rx->skb->data, hw_rxhdr_len); + memcpy(skb_ptr + hw_rxhdr_len, defrag_elem_rx->skb->data + hw_rxhdr_len + offset_len, defrag_elem_rx->skb->len - hw_rxhdr_len - offset_len); + fg_total_len = defrag_elem_rx->skb->len - hw_rxhdr_len - offset_len; + hw_rxhdr = (struct hw_rxhdr*)skb_ptr; + } else { + memcpy(skb_ptr, defrag_elem_rx->skb->data + offset_len, defrag_elem_rx->skb->len - offset_len); + fg_total_len += defrag_elem_rx->skb->len - offset_len; + } + dev_kfree_skb(defrag_elem_rx->skb); + list_del(&defrag_elem_rx->head); + kfree(defrag_elem_rx); + fg_first = 0; + } + } + + if (!fg_first) { + hw_rxhdr->hwvect.len = fg_total_len;//update len + msdu_offset = hw_rxhdr_len; + //printk("[llm] sn %d: total %d, skb %x (%d-%x-%x)\n", orignal_rxu_state->sn, fg_total_len, skb, skb->len, skb->data, skb->tail); + } + } + } +#endif + + hw_rxhdr = (struct hw_rxhdr*)skb->data; + +#if 0 + ECRNX_DBG(" hw_vect_len: %d , rxu_stat_mm: %d \n", sizeof(struct hw_rxhdr), sizeof(struct rxu_stat_mm)); + ECRNX_DBG(" rcv_frm_len: %d \n", hw_rxhdr->hwvect.len); +#endif + + status = orignal_rxu_state->status; + //ECRNX_DBG("%s, parttern:0x%2x, status:%d !!", __func__, hw_rxhdr->pattern, status); + /* Check the pattern */ + if (hw_rxhdr->pattern != ecrnx_rxbuff_pattern) { + dev_err(ecrnx_hw->dev, "RX Buff Pattern not correct, pattern (%x), status %d\n", hw_rxhdr->pattern, status); + ECRNX_ERR("RX Buff Pattern not correct, pattern (%x), status %d, skb (%p), skb_len %d\n", hw_rxhdr->pattern, status, skb->data, skb->len); + print_hex_dump(KERN_DEBUG, DBG_PREFIX_PAT, DUMP_PREFIX_NONE, 16, 1, hw_rxhdr, skb->len, false); + BUG(); + } + + /* Check if we need to delete the buffer */ + if (status & RX_STAT_DELETE) { + dev_kfree_skb(skb); + goto end; + } + + /* Check if we need to forward the buffer coming from a monitor interface */ + if (status & RX_STAT_MONITOR) { + struct sk_buff *skb_monitor; + struct hw_rxhdr hw_rxhdr_copy; + u8 rtap_len; + u16 frm_len; + + //Check if monitor interface exists and is open + ECRNX_DBG("monitor_vif: %d \n", ecrnx_hw->monitor_vif); + ecrnx_vif = ecrnx_rx_get_vif(ecrnx_hw, ecrnx_hw->monitor_vif); + if (!ecrnx_vif) { + dev_err(ecrnx_hw->dev, "Received monitor frame but there is no monitor interface open\n"); + dev_kfree_skb(skb); + goto check_len_update; + } + + ecrnx_rx_vector_convert(ecrnx_hw->machw_type, + &hw_rxhdr->hwvect.rx_vect1, + &hw_rxhdr->hwvect.rx_vect2); + rtap_len = ecrnx_rx_rtap_hdrlen(&hw_rxhdr->hwvect.rx_vect1, false); + + // Move skb->data pointer to MAC Header or Ethernet header + skb->data += msdu_offset; + + //Save frame length + frm_len = le32_to_cpu(hw_rxhdr->hwvect.len) - msdu_offset; + + // Reserve space for frame + skb->len = frm_len; + + if (status == RX_STAT_MONITOR) { + //Check if there is enough space to add the radiotap header + if (skb_headroom(skb) > rtap_len) { + + skb_monitor = skb; + + //Duplicate the HW Rx Header to override with the radiotap header + memcpy(&hw_rxhdr_copy, hw_rxhdr, sizeof(hw_rxhdr_copy)); + + } else { + //Duplicate the skb and extend the headroom + skb_monitor = skb_copy_expand(skb, rtap_len, 0, GFP_ATOMIC); + + //Reset original skb->data pointer + skb->data = (void*) hw_rxhdr; + } + } + else + { +#ifdef CONFIG_ECRNX_MON_DATA + // Check if MSDU + if (!hw_rxhdr->flags_is_80211_mpdu) { + // MSDU + //Extract MAC header + u16 machdr_len = hw_rxhdr->mac_hdr_backup.buf_len; + u8* machdr_ptr = hw_rxhdr->mac_hdr_backup.buffer; + + //Pull Ethernet header from skb + skb_pull(skb, sizeof(struct ethhdr)); + + // Copy skb and extend for adding the radiotap header and the MAC header + skb_monitor = skb_copy_expand(skb, + rtap_len + machdr_len, + 0, GFP_ATOMIC); + + //Reserve space for the MAC Header + skb_push(skb_monitor, machdr_len); + + //Copy MAC Header + memcpy(skb_monitor->data, machdr_ptr, machdr_len); + + //Update frame length + frm_len += machdr_len - sizeof(struct ethhdr); + } else { + // MPDU + skb_monitor = skb_copy_expand(skb, rtap_len, 0, GFP_ATOMIC); + } + + //Reset original skb->data pointer + skb->data = (void*) hw_rxhdr; +#else + //Reset original skb->data pointer + skb->data = (void*) hw_rxhdr; + + wiphy_err(ecrnx_hw->wiphy, "RX status %d is invalid when MON_DATA is disabled\n", status); + dev_kfree_skb(skb); + goto check_len_update; +#endif + } + + skb_reset_tail_pointer(skb); + skb->len = 0; + skb_reset_tail_pointer(skb_monitor); + skb_monitor->len = 0; + + skb_put(skb_monitor, frm_len); + if (ecrnx_rx_monitor(ecrnx_hw, ecrnx_vif, skb_monitor, hw_rxhdr, rtap_len)){ + dev_kfree_skb(skb); + dev_err(ecrnx_hw->dev, "skb monitor handle error \n"); + } + + if (status == RX_STAT_MONITOR) { + status |= RX_STAT_ALLOC; + if (skb_monitor != skb) { + dev_kfree_skb(skb); + dev_err(ecrnx_hw->dev, "skb handle status error \n"); + } + } + } + +check_len_update: + /* Check if we need to update the length */ + if (status & RX_STAT_LEN_UPDATE) { + + hw_rxhdr = (struct hw_rxhdr *)skb->data; + + hw_rxhdr->hwvect.len = orignal_rxu_state->frame_len - msdu_offset; + + if (status & RX_STAT_ETH_LEN_UPDATE) { + /* Update Length Field inside the Ethernet Header */ + struct ethhdr *hdr = (struct ethhdr *)((u8 *)hw_rxhdr + msdu_offset); + + hdr->h_proto = htons(orignal_rxu_state->frame_len - sizeof(struct ethhdr)); + } + dev_kfree_skb(skb); + goto end; + } + + /* Check if it must be discarded after informing upper layer */ + if (status & RX_STAT_SPURIOUS) { + struct ieee80211_hdr *hdr; + + /* Read mac header to obtain Transmitter Address */ + //ecrnx_ipc_rxbuf_elem_sync(ecrnx_hw, skb, msdu_offset + sizeof(*hdr)); + + hw_rxhdr = (struct hw_rxhdr *)skb->data; + hdr = (struct ieee80211_hdr *)(skb->data + msdu_offset); + ecrnx_vif = ecrnx_rx_get_vif(ecrnx_hw, hw_rxhdr->flags_vif_idx); + ECRNX_DBG(" flags vif idx:%d, vif: 0x%x \n", hw_rxhdr->flags_vif_idx, ecrnx_vif); + if (ecrnx_vif) { + cfg80211_rx_spurious_frame(ecrnx_vif->ndev, hdr->addr2, GFP_ATOMIC); + }else{ + dev_kfree_skb(skb); + } + + goto end; + } + + /* Check if we need to forward the buffer */ + if (status & RX_STAT_FORWARD) { + + /* Remove the SK buffer from the rxbuf_elems table. It will also + unmap the buffer and then sync the buffer for the cpu */ + //ecrnx_ipc_rxbuf_elem_pull(ecrnx_hw, skb); + + hw_rxhdr = (struct hw_rxhdr *)skb->data; + ecrnx_rx_vector_convert(ecrnx_hw->machw_type, + &hw_rxhdr->hwvect.rx_vect1, + &hw_rxhdr->hwvect.rx_vect2); + + //skb_reserve(skb, msdu_offset); + //ECRNX_DBG("before pull skb len: %d, msdu_offset:%d \n", skb->len, msdu_offset); + skb_pull(skb, msdu_offset); + //ECRNX_DBG("after pull skb len: %d \n", skb->len); + + if (hw_rxhdr->flags_is_80211_mpdu) { + //ECRNX_DBG("recv mgmt\n"); + ecrnx_rx_mgmt_any(ecrnx_hw, skb, hw_rxhdr); + } else { + ecrnx_vif = ecrnx_rx_get_vif(ecrnx_hw, hw_rxhdr->flags_vif_idx); + //ECRNX_DBG(" flags vif idx1:%d, vif: 0x%x \n", hw_rxhdr->flags_vif_idx, ecrnx_vif); + if (!ecrnx_vif) { + dev_err(ecrnx_hw->dev, "Frame received but no active vif (%d)", + hw_rxhdr->flags_vif_idx); + dev_kfree_skb(skb); + ECRNX_ERR(" check_alloc, %d !!", __LINE__); + goto end; + } + + if (hw_rxhdr->flags_sta_idx != ECRNX_INVALID_STA) { + struct ecrnx_sta *sta; + + sta = &ecrnx_hw->sta_table[hw_rxhdr->flags_sta_idx]; + ecrnx_rx_statistic(ecrnx_hw, hw_rxhdr, sta); + //ECRNX_DBG(" sta idx:%d, vif idx: %d \n", sta->vlan_idx, ecrnx_vif->vif_index); + if (sta->vlan_idx != ecrnx_vif->vif_index) { + ecrnx_vif = ecrnx_hw->vif_table[sta->vlan_idx]; + if (!ecrnx_vif) { + dev_kfree_skb(skb); + ECRNX_ERR(" check_alloc, %d !!", __LINE__); + goto end; + } + } + + ecrnx_set_station_info(ecrnx_vif, (const u8*)sta->mac_addr, &hw_rxhdr->hwvect.rx_vect1); + if (hw_rxhdr->flags_is_4addr && !ecrnx_vif->use_4addr) { + cfg80211_rx_unexpected_4addr_frame(ecrnx_vif->ndev, + sta->mac_addr, GFP_ATOMIC); + } + } + + skb->priority = 256 + hw_rxhdr->flags_user_prio; +#ifdef CONFIG_ESWIN_RX_REORDER + //printk("sn:%d %d %d %d l:%d\n",orignal_rxu_state->is_qos, orignal_rxu_state->need_reord, orignal_rxu_state->need_pn_check,orignal_rxu_state->sn, skb->len -8); + if(orignal_rxu_state->is_qos && orignal_rxu_state->need_reord) { + ecrnx_rx_reord_check(ecrnx_hw, ecrnx_vif, hw_rxhdr, skb, orignal_rxu_state); + }else if(orignal_rxu_state->is_qos && !orignal_rxu_state->need_reord) { + ecrnx_rx_reord_tid_flush(ecrnx_hw, ecrnx_vif, skb, hw_rxhdr->flags_sta_idx,orignal_rxu_state->tid); + if (orignal_rxu_state->need_pn_check) + { + if (!ecrnx_rx_data_pn_check(ecrnx_hw, skb, hw_rxhdr->flags_vif_idx, hw_rxhdr->flags_sta_idx, orignal_rxu_state->tid, orignal_rxu_state->is_ga)) + { + ECRNX_DBG("rx_data_check_pn error\n"); + dev_kfree_skb(skb); + goto end; + } + skb_pull(skb, 8); + } + if (!ecrnx_rx_data_skb(ecrnx_hw, ecrnx_vif, skb, hw_rxhdr)) + dev_kfree_skb(skb); + }else { + if (orignal_rxu_state->need_pn_check) + { + if (!ecrnx_rx_data_pn_check(ecrnx_hw, skb, hw_rxhdr->flags_vif_idx, hw_rxhdr->flags_sta_idx, orignal_rxu_state->tid, orignal_rxu_state->is_ga)) + { + ECRNX_DBG("rx_data_check_pn error\n"); + dev_kfree_skb(skb); + goto end; + } + skb_pull(skb, 8); + } + if (!ecrnx_rx_data_skb(ecrnx_hw, ecrnx_vif, skb, hw_rxhdr)) + dev_kfree_skb(skb); + } +#else + if (!ecrnx_rx_data_skb(ecrnx_hw, ecrnx_vif, skb, hw_rxhdr)){ + dev_kfree_skb(skb); + dev_err(ecrnx_hw->dev, "ecrnx_rx_data_skb_sdio error \n"); + } +#endif + } + goto end; + } + + else if (status & RX_STAT_ALLOC) { + /*agg frame*/ + if (hw_rxhdr->flags_sta_idx != ECRNX_INVALID_STA) { + if (orignal_rxu_state->need_pn_check) + { + if (!ecrnx_rx_data_pn_check(ecrnx_hw, skb, hw_rxhdr->flags_vif_idx, hw_rxhdr->flags_sta_idx, orignal_rxu_state->tid, orignal_rxu_state->is_ga)) + { + dev_err(ecrnx_hw->dev, "rx_data_check_pn error \n"); + dev_kfree_skb(skb); + goto end; + } + skb_pull(skb, 8); + } + } + + if(orignal_rxu_state->fragment_flag){ + struct defrag_elem* defrag_elem_rx = (struct defrag_elem*)kzalloc(sizeof(struct defrag_elem), GFP_ATOMIC); + + if(defrag_elem_rx){ + defrag_elem_rx->tid = orignal_rxu_state->tid; + defrag_elem_rx->sn = orignal_rxu_state->sn; + defrag_elem_rx->real_offset = orignal_rxu_state->real_offset; + defrag_elem_rx->skb = skb; + ECRNX_DBG("ecrnx_rxdataind:insert_skb:0x%08x, sn:%d, tid:%d, sn:%d \n", skb, orignal_rxu_state->tid, orignal_rxu_state->sn); + list_add_tail(&defrag_elem_rx->head, &ecrnx_hw->defrag_rx_list); + }else{ + ECRNX_ERR("no buffer !!!! \n"); + } + } + //ECRNX_DBG("status set alloc\n"); + } + +end: + if(frg_ctrl_skb){ + dev_kfree_skb(frg_ctrl_skb); + } + spin_unlock_bh(&ecrnx_hw->rx_lock); + return 0; +} +#endif + +#if 0 +static void sdio_rx_debug(struct sk_buff *skb) +{ + int i; + + if(!skb || !skb->len){ + ECRNX_DBG("skb error \n"); + } + + ECRNX_DBG("%s, len: 0x%x \n", __func__, skb->len); + for(i = 0; i< skb->len; i++){ + printk("0x%02x ", skb->data[i]); + if (i && (i % 16) == 0) { + printk("\n"); + } + } +} +#endif + +int ecrnx_data_cfm_callback(void *priv, void *host_id) +{ + struct ecrnx_hw *ecrnx_hw = (struct ecrnx_hw*)priv; + struct ipc_host_env_tag *env; + env = ecrnx_hw->ipc_env; + + ecrnx_hw->data_tx_done++; + if(!env || !env->pthis || host_id == 0) + { + ECRNX_ERR("ecrnx_data_cfm_callback input param error!! \n!"); + return -1; + } + spin_lock_bh(&((struct ecrnx_hw *)env->pthis)->tx_lock); + env->cb.handle_data_cfm(env->pthis, host_id); + spin_unlock_bh(&((struct ecrnx_hw *)env->pthis)->tx_lock); + return 0; +} + +int ecrnx_msg_cfm_callback(void *priv, void *host_id) +{ + struct ecrnx_hw *ecrnx_hw = (struct ecrnx_hw*)priv; + struct ipc_host_env_tag *env; + env = ecrnx_hw->ipc_env; + + if(!env || !env->pthis || host_id == 0) + { + ECRNX_ERR("ecrnx_msg_cfm_callback input param error!! \n!"); + return -1; + } + + env->msga2e_hostid = NULL; + ecrnx_hw->cmd_mgr.llind(&ecrnx_hw->cmd_mgr, (struct ecrnx_cmd *)host_id); + return 0; +} + +#ifdef CONFIG_ECRNX_ESWIN_SDIO +int ecrnx_rx_callback(void *priv, struct sk_buff *skb) +#elif defined(CONFIG_ECRNX_ESWIN_USB) +extern void usb_skb_debug(struct sk_buff *skb); +int ecrnx_rx_callback(void *priv, struct sk_buff *skb, uint8_t endpoint) +#endif +{ + //struct sk_buff *ret_skb = NULL; + struct ecrnx_hw *ecrnx_hw = (struct ecrnx_hw*)priv; + uint32_t frm_type = 0; + //ECRNX_DBG("%s enter, skb_len: %d, skb: 0x%x !!", __func__, skb->len, skb); + + //print_hex_dump(KERN_DEBUG, "rx skb: ", DUMP_PREFIX_NONE, 16, 1, skb->data, skb->len, false); + + if(!skb || !skb->data) + { + ECRNX_PRINT("sdio rx err error \n"); + } + + //usb_rx_debug(skb); +#ifdef CONFIG_ECRNX_ESWIN_SDIO + frm_type = (skb->data[11] << 24) | (skb->data[10] << 16) | (skb->data[9] << 8) | skb->data[8]; +#elif defined(CONFIG_ECRNX_ESWIN_USB) + frm_type = (skb->data[3] << 24) | (skb->data[2] << 16) | (skb->data[1] << 8) | skb->data[0]; +#endif + + //ECRNX_DBG(" frame_type: 0x%x, frame_len: %d, ecrnx_hw: 0x%x", frm_type, skb->len, ecrnx_hw); + +#if defined(CONFIG_ECRNX_ESWIN_SDIO) +#ifdef CONFIG_ECRNX_WIFO_CAIL + if (amt_mode == true) { + sdio_host_amt_rx_handler(frm_type, skb); + } + else +#endif + sdio_host_rx_handler( frm_type, ecrnx_hw->ipc_env, skb); +#elif defined(CONFIG_ECRNX_ESWIN_USB) + if(MSG_TYPE_DATA == endpoint) + { + //msg is USB_FRM_TYPE_RXDESC + usb_host_rx_handler( USB_FRM_TYPE_RXDESC, ecrnx_hw->ipc_env, skb); //current just used a point + } + else if(MSG_TYPE_CTRL == endpoint) + { +#ifdef CONFIG_ECRNX_WIFO_CAIL + if (amt_mode == true) { + usb_host_amt_rx_handler(frm_type, skb); + } + else +#endif + usb_host_rx_handler( frm_type, ecrnx_hw->ipc_env, skb); + } + else + { + ECRNX_ERR("endopint error \n"); + } +#endif + //ECRNX_DBG("%s exit!!", __func__); + return 0; +} diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_rx.h b/drivers/net/wireless/eswin/fullmac/ecrnx_rx.h new file mode 100644 index 000000000000..c84cd8b871ed --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_rx.h @@ -0,0 +1,125 @@ +/** + ****************************************************************************** + * + * @file ecrnx_rx.h + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ +#ifndef _ECRNX_RX_H_ +#define _ECRNX_RX_H_ + +#include <linux/workqueue.h> +#include "hal_desc.h" +enum rx_status_bits +{ + /// The buffer can be forwarded to the networking stack + RX_STAT_FORWARD = 1 << 0, + /// A new buffer has to be allocated + RX_STAT_ALLOC = 1 << 1, + /// The buffer has to be deleted + RX_STAT_DELETE = 1 << 2, + /// The length of the buffer has to be updated + RX_STAT_LEN_UPDATE = 1 << 3, + /// The length in the Ethernet header has to be updated + RX_STAT_ETH_LEN_UPDATE = 1 << 4, + /// Simple copy + RX_STAT_COPY = 1 << 5, + /// Spurious frame (inform upper layer and discard) + RX_STAT_SPURIOUS = 1 << 6, + /// packet for monitor interface + RX_STAT_MONITOR = 1 << 7, +}; + +#if defined(CONFIG_ECRNX_ESWIN_USB) +typedef enum +{ + MSG_TYPE_DATA = 1, + MSG_TYPE_CTRL +}MSG_TYPE_E; +#endif + +/* + * Decryption status subfields. + * { + */ +// @} + +#define _ASOCREQ_IE_OFFSET_ 4 /* excluding wlan_hdr */ +#define _REASOCREQ_IE_OFFSET_ 10 +#define STATION_INFO_ASSOC_REQ_IES 0 +#define WLAN_HDR_A3_LEN 24 +#define get_addr2_ptr(pbuf) ((unsigned char *)((unsigned int)(pbuf) + 10)) + +/* keep it same with the FW */ +#define RX_CNTRL_REORD_WIN_SIZE 42 +#ifdef CONFIG_ECRNX_MON_DATA +#define RX_MACHDR_BACKUP_LEN 64 +/// MAC header backup descriptor +struct mon_machdrdesc +{ + /// Length of the buffer + u32 buf_len; + /// Buffer containing mac header, LLC and SNAP + u8 buffer[RX_MACHDR_BACKUP_LEN]; +}; +#endif + +struct hw_rxhdr { + /** RX vector */ + struct hw_vect hwvect; + + /** PHY channel information */ + struct phy_channel_info_desc phy_info; + + /** RX flags */ + u32 flags_is_amsdu : 1; + u32 flags_is_80211_mpdu: 1; + u32 flags_is_4addr : 1; + u32 flags_new_peer : 1; + u32 flags_user_prio : 3; + u32 flags_rsvd0 : 1; + u32 flags_vif_idx : 8; // 0xFF if invalid VIF index + u32 flags_sta_idx : 8; // 0xFF if invalid STA index + u32 flags_dst_idx : 8; // 0xFF if unknown destination STA +#ifdef CONFIG_ECRNX_MON_DATA + /// MAC header backup descriptor (used only for MSDU when there is a monitor and a data interface) + struct mon_machdrdesc mac_hdr_backup; +#endif + /** Pattern indicating if the buffer is available for the driver */ + u32 pattern; +}; + +struct ecrnx_defer_rx { + struct sk_buff_head sk_list; + struct work_struct work; +}; + +/** + * struct ecrnx_defer_rx_cb - Control buffer for deferred buffers + * + * @vif: VIF that received the buffer + */ +struct ecrnx_defer_rx_cb { + struct ecrnx_vif *vif; +}; + +u8 ecrnx_unsup_rx_vec_ind(void *pthis, void *hostid); +u8 ecrnx_rxdataind(void *pthis, void *hostid); +void ecrnx_rx_deferred(struct work_struct *ws); +void ecrnx_rx_defer_skb(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + struct sk_buff *skb); +#ifdef CONFIG_ECRNX_ESWIN_SDIO +int ecrnx_rx_callback(void *priv, struct sk_buff *skb); +#elif defined(CONFIG_ECRNX_ESWIN_USB) +int ecrnx_rx_callback(void *priv, struct sk_buff *skb, uint8_t endpoint); +#endif + +void ecrnx_rx_reord_deinit(struct ecrnx_hw *ecrnx_hw); +void ecrnx_rx_reord_init(struct ecrnx_hw *ecrnx_hw); +void ecrnx_rx_reord_sta_init(struct ecrnx_hw* ecrnx_hw, struct ecrnx_vif *ecrnx_vif, u8 sta_idx); +void ecrnx_rx_reord_sta_deinit(struct ecrnx_hw* ecrnx_hw, u8 sta_idx, bool is_del); + + +#endif /* _ECRNX_RX_H_ */ diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_tdls.c b/drivers/net/wireless/eswin/fullmac/ecrnx_tdls.c new file mode 100644 index 000000000000..7fe1ff2ce736 --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_tdls.c @@ -0,0 +1,788 @@ +/** + ****************************************************************************** + * + * @file ecrnx_tx.c + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +/** + * INCLUDE FILES + ****************************************************************************** + */ + +#include "ecrnx_tdls.h" +#include "ecrnx_compat.h" + +/** + * FUNCTION DEFINITIONS + ****************************************************************************** + */ + +static u16 +ecrnx_get_tdls_sta_capab(struct ecrnx_vif *ecrnx_vif, u16 status_code) +{ + u16 capab = 0; + + /* The capability will be 0 when sending a failure code */ + if (status_code != 0) + return capab; + + if (ecrnx_vif->sta.ap->band != NL80211_BAND_2GHZ) + return capab; + + capab |= WLAN_CAPABILITY_SHORT_SLOT_TIME; + capab |= WLAN_CAPABILITY_SHORT_PREAMBLE; + + return capab; +} + +static int +ecrnx_tdls_prepare_encap_data(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + const u8 *peer, u8 action_code, u8 dialog_token, + u16 status_code, struct sk_buff *skb) +{ + struct ieee80211_tdls_data *tf; + tf = (void *)skb_put(skb, sizeof(struct ieee80211_tdls_data) - sizeof(tf->u)); + + // set eth header + memcpy(tf->da, peer, ETH_ALEN); + memcpy(tf->sa, ecrnx_hw->wiphy->perm_addr, ETH_ALEN); + tf->ether_type = cpu_to_be16(ETH_P_TDLS); + + // set common TDLS info + tf->payload_type = WLAN_TDLS_SNAP_RFTYPE; + tf->category = WLAN_CATEGORY_TDLS; + tf->action_code = action_code; + + // set action specific TDLS info + switch (action_code) { + case WLAN_TDLS_SETUP_REQUEST: + skb_put(skb, sizeof(tf->u.setup_req)); + tf->u.setup_req.dialog_token = dialog_token; + tf->u.setup_req.capability = + cpu_to_le16(ecrnx_get_tdls_sta_capab(ecrnx_vif, status_code)); + break; + + case WLAN_TDLS_SETUP_RESPONSE: + skb_put(skb, sizeof(tf->u.setup_resp)); + tf->u.setup_resp.status_code = cpu_to_le16(status_code); + tf->u.setup_resp.dialog_token = dialog_token; + tf->u.setup_resp.capability = + cpu_to_le16(ecrnx_get_tdls_sta_capab(ecrnx_vif, status_code)); + break; + + case WLAN_TDLS_SETUP_CONFIRM: + skb_put(skb, sizeof(tf->u.setup_cfm)); + tf->u.setup_cfm.status_code = cpu_to_le16(status_code); + tf->u.setup_cfm.dialog_token = dialog_token; + break; + + case WLAN_TDLS_TEARDOWN: + skb_put(skb, sizeof(tf->u.teardown)); + tf->u.teardown.reason_code = cpu_to_le16(status_code); + break; + + case WLAN_TDLS_DISCOVERY_REQUEST: + skb_put(skb, sizeof(tf->u.discover_req)); + tf->u.discover_req.dialog_token = dialog_token; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int +ecrnx_prep_tdls_direct(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + const u8 *peer, u8 action_code, u8 dialog_token, + u16 status_code, struct sk_buff *skb) +{ + struct ieee80211_mgmt *mgmt; + + mgmt = (void *)skb_put(skb, 24); + memset(mgmt, 0, 24); + memcpy(mgmt->da, peer, ETH_ALEN); + memcpy(mgmt->sa, ecrnx_hw->wiphy->perm_addr, ETH_ALEN); + memcpy(mgmt->bssid, ecrnx_vif->sta.ap->mac_addr, ETH_ALEN); + + mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_ACTION); + + switch (action_code) { + case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: + skb_put(skb, 1 + sizeof(mgmt->u.action.u.tdls_discover_resp)); + mgmt->u.action.category = WLAN_CATEGORY_PUBLIC; + mgmt->u.action.u.tdls_discover_resp.action_code = WLAN_PUB_ACTION_TDLS_DISCOVER_RES; + mgmt->u.action.u.tdls_discover_resp.dialog_token = dialog_token; + mgmt->u.action.u.tdls_discover_resp.capability = + cpu_to_le16(ecrnx_get_tdls_sta_capab(ecrnx_vif, status_code)); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int +ecrnx_add_srates_ie(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb) +{ + u8 i, rates, *pos; + int rate; + struct ieee80211_supported_band *ecrnx_band_2GHz = ecrnx_hw->wiphy->bands[NL80211_BAND_2GHZ]; + + rates = 8; + + if (skb_tailroom(skb) < rates + 2) + return -ENOMEM; + + pos = skb_put(skb, rates + 2); + *pos++ = WLAN_EID_SUPP_RATES; + *pos++ = rates; + for (i = 0; i < rates; i++) { + rate = ecrnx_band_2GHz->bitrates[i].bitrate; + rate = DIV_ROUND_UP(rate, 5); + *pos++ = (u8)rate; + } + + return 0; +} + +static int +ecrnx_add_ext_srates_ie(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb) +{ + u8 i, exrates, *pos; + int rate; + struct ieee80211_supported_band *ecrnx_band_2GHz = ecrnx_hw->wiphy->bands[NL80211_BAND_2GHZ]; + + exrates = ecrnx_band_2GHz->n_bitrates - 8; + + if (skb_tailroom(skb) < exrates + 2) + return -ENOMEM; + + pos = skb_put(skb, exrates + 2); + *pos++ = WLAN_EID_EXT_SUPP_RATES; + *pos++ = exrates; + for (i = 8; i < (8+exrates); i++) { + rate = ecrnx_band_2GHz->bitrates[i].bitrate; + rate = DIV_ROUND_UP(rate, 5); + *pos++ = (u8)rate; + } + + return 0; +} + +static void +ecrnx_tdls_add_supp_channels(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb) +{ + /* + * Add possible channels for TDLS. These are channels that are allowed + * to be active. + */ + u8 subband_cnt = 0; + u8 *pos_subband; + u8 *pos = skb_put(skb, 2); + struct ieee80211_supported_band *ecrnx_band_2GHz = ecrnx_hw->wiphy->bands[NL80211_BAND_2GHZ]; +#ifdef CONFIG_ECRNX_5G + struct ieee80211_supported_band *ecrnx_band_5GHz = ecrnx_hw->wiphy->bands[NL80211_BAND_5GHZ]; +#endif + + *pos++ = WLAN_EID_SUPPORTED_CHANNELS; + + /* + * 5GHz and 2GHz channels numbers can overlap. Ignore this for now, as + * this doesn't happen in real world scenarios. + */ + + /* 2GHz, with 5MHz spacing */ + pos_subband = skb_put(skb, 2); + if (ecrnx_band_2GHz->n_channels > 0) + { + *pos_subband++ = ieee80211_frequency_to_channel(ecrnx_band_2GHz->channels[0].center_freq); + *pos_subband++ = ecrnx_band_2GHz->n_channels; + subband_cnt++; + } + +#ifdef CONFIG_ECRNX_5G + /* 5GHz, with 20MHz spacing */ + pos_subband = skb_put(skb, 2); + if (ecrnx_band_5GHz->n_channels > 0) + { + *pos_subband++ = ieee80211_frequency_to_channel(ecrnx_band_5GHz->channels[0].center_freq); + *pos_subband++ = ecrnx_band_5GHz->n_channels; + subband_cnt++; + } +#endif + + /* length */ + *pos = 2 * subband_cnt; +} + +static void +ecrnx_tdls_add_ext_capab(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb) +{ + u8 *pos = (void *)skb_put(skb, 7); + bool chan_switch = ecrnx_hw->wiphy->features & + NL80211_FEATURE_TDLS_CHANNEL_SWITCH; + + *pos++ = WLAN_EID_EXT_CAPABILITY; + *pos++ = 5; /* len */ + *pos++ = 0x0; + *pos++ = 0x0; + *pos++ = 0x0; + *pos++ = WLAN_EXT_CAPA4_TDLS_BUFFER_STA | + (chan_switch ? WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH : 0); + *pos++ = WLAN_EXT_CAPA5_TDLS_ENABLED; +} + +static void +ecrnx_add_wmm_info_ie(struct sk_buff *skb, u8 qosinfo) +{ + u8 *pos = (void *)skb_put(skb, 9); + + *pos++ = WLAN_EID_VENDOR_SPECIFIC; + *pos++ = 7; /* len */ + *pos++ = 0x00; /* Microsoft OUI 00:50:F2 */ + *pos++ = 0x50; + *pos++ = 0xf2; + *pos++ = 2; /* WME */ + *pos++ = 0; /* WME info */ + *pos++ = 1; /* WME ver */ + *pos++ = qosinfo; /* U-APSD no in use */ +} + +/* translate numbering in the WMM parameter IE to the mac80211 notation */ +static u8 ecrnx_ac_from_wmm(int ac) +{ + switch (ac) { + case 0: + return AC_BE; + case 1: + return AC_BK; + case 2: + return AC_VI; + case 3: + return AC_VO; + default: + WARN_ON_ONCE(1); + } + return -1; +} + +static void +ecrnx_add_wmm_param_ie(struct sk_buff *skb, u8 acm_bits, u32 *ac_params) +{ + struct ieee80211_wmm_param_ie *wmm; + int i, j; + u8 cw_min, cw_max; + bool acm; + + wmm = (void *)skb_put(skb, sizeof(struct ieee80211_wmm_param_ie)); + memset(wmm, 0, sizeof(*wmm)); + + wmm->element_id = WLAN_EID_VENDOR_SPECIFIC; + wmm->len = sizeof(*wmm) - 2; + + wmm->oui[0] = 0x00; /* Microsoft OUI 00:50:F2 */ + wmm->oui[1] = 0x50; + wmm->oui[2] = 0xf2; + wmm->oui_type = 2; /* WME */ + wmm->oui_subtype = 1; /* WME param */ + wmm->version = 1; /* WME ver */ + wmm->qos_info = 0; /* U-APSD not in use */ + + /* + * Use the EDCA parameters defined for the BSS, or default if the AP + * doesn't support it, as mandated by 802.11-2012 section 10.22.4 + */ + for (i = 0; i < AC_MAX; i++) { + j = ecrnx_ac_from_wmm(i); + cw_min = (ac_params[j] & 0xF0 ) >> 4; + cw_max = (ac_params[j] & 0xF00 ) >> 8; + acm = (acm_bits & (1 << j)) != 0; + + wmm->ac[i].aci_aifsn = (i << 5) | (acm << 4) | (ac_params[j] & 0xF); + wmm->ac[i].cw = (cw_max << 4) | cw_min; + wmm->ac[i].txop_limit = (ac_params[j] & 0x0FFFF000 ) >> 12; + } +} + +static void +ecrnx_tdls_add_oper_classes(struct ecrnx_vif *ecrnx_vif, struct sk_buff *skb) +{ + u8 *pos; + u8 op_class; + struct cfg80211_chan_def chan_def; + struct ieee80211_channel chan; + + chan.band = ecrnx_vif->sta.ap->band; + chan.center_freq = ecrnx_vif->sta.ap->center_freq; + chan_def.chan = &chan; + chan_def.width = ecrnx_vif->sta.ap->width; + chan_def.center_freq1 = ecrnx_vif->sta.ap->center_freq1; + chan_def.center_freq2 = ecrnx_vif->sta.ap->center_freq2; + + if (!ieee80211_chandef_to_operating_class(&chan_def, &op_class)) + return; + + pos = skb_put(skb, 4); + *pos++ = WLAN_EID_SUPPORTED_REGULATORY_CLASSES; + *pos++ = 2; /* len */ + + // current op class + *pos++ = op_class; + *pos++ = op_class; /* give current operating class as alternate too */ + + // need to add 5GHz classes? +} + +static void +ecrnx_ie_build_ht_cap(struct sk_buff *skb, struct ieee80211_sta_ht_cap *ht_cap, + u16 cap) +{ + u8 *pos; + __le16 tmp; + + pos = skb_put(skb, sizeof(struct ieee80211_ht_cap) + 2); + *pos++ = WLAN_EID_HT_CAPABILITY; + *pos++ = sizeof(struct ieee80211_ht_cap); + memset(pos, 0, sizeof(struct ieee80211_ht_cap)); + + /* capability flags */ + tmp = cpu_to_le16(cap); + memcpy(pos, &tmp, sizeof(u16)); + pos += sizeof(u16); + + /* AMPDU parameters */ + *pos++ = ht_cap->ampdu_factor | + (ht_cap->ampdu_density << + IEEE80211_HT_AMPDU_PARM_DENSITY_SHIFT); + + /* MCS set */ + memcpy(pos, &ht_cap->mcs, sizeof(ht_cap->mcs)); + pos += sizeof(ht_cap->mcs); + + /* extended capabilities */ + pos += sizeof(__le16); + + /* BF capabilities */ + pos += sizeof(__le32); + + /* antenna selection */ + pos += sizeof(u8); +} + +static void +ecrnx_ie_build_vht_cap(struct sk_buff *skb, struct ieee80211_sta_vht_cap *vht_cap, + u32 cap) +{ + u8 *pos; + __le32 tmp; + + pos = skb_put(skb, 14); + + *pos++ = WLAN_EID_VHT_CAPABILITY; + *pos++ = sizeof(struct ieee80211_vht_cap); + memset(pos, 0, sizeof(struct ieee80211_vht_cap)); + + /* capability flags */ + tmp = cpu_to_le32(cap); + memcpy(pos, &tmp, sizeof(u32)); + pos += sizeof(u32); + + /* VHT MCS set */ + memcpy(pos, &vht_cap->vht_mcs, sizeof(vht_cap->vht_mcs)); + pos += sizeof(vht_cap->vht_mcs); +} + +static void +ecrnx_tdls_add_bss_coex_ie(struct sk_buff *skb) +{ + u8 *pos = (void *)skb_put(skb, 3); + + *pos++ = WLAN_EID_BSS_COEX_2040; + *pos++ = 1; /* len */ + + *pos++ = WLAN_BSS_COEX_INFORMATION_REQUEST; +} + +static void +ecrnx_tdls_add_link_ie(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + struct sk_buff *skb, const u8 *peer, + bool initiator) +{ + struct ieee80211_tdls_lnkie *lnkid; + const u8 *init_addr, *rsp_addr; + + if (initiator) { + init_addr = ecrnx_hw->wiphy->perm_addr; + rsp_addr = peer; + } else { + init_addr = peer; + rsp_addr = ecrnx_hw->wiphy->perm_addr; + } + + lnkid = (void *)skb_put(skb, sizeof(struct ieee80211_tdls_lnkie)); + + lnkid->ie_type = WLAN_EID_LINK_ID; + lnkid->ie_len = sizeof(struct ieee80211_tdls_lnkie) - 2; + + memcpy(lnkid->bssid, ecrnx_vif->sta.ap->mac_addr, ETH_ALEN); + memcpy(lnkid->init_sta, init_addr, ETH_ALEN); + memcpy(lnkid->resp_sta, rsp_addr, ETH_ALEN); +} + +static void +ecrnx_tdls_add_aid_ie(struct ecrnx_vif *ecrnx_vif, struct sk_buff *skb) +{ + u8 *pos = (void *)skb_put(skb, 4); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0) + *pos++ = WLAN_EID_AID; +#else + *pos++ = 197; +#endif + *pos++ = 2; /* len */ + *pos++ = ecrnx_vif->sta.ap->aid; +} + +static u8 * +ecrnx_ie_build_ht_oper(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + u8 *pos, struct ieee80211_sta_ht_cap *ht_cap, + u16 prot_mode) +{ + struct ieee80211_ht_operation *ht_oper; + /* Build HT Information */ + *pos++ = WLAN_EID_HT_OPERATION; + *pos++ = sizeof(struct ieee80211_ht_operation); + ht_oper = (struct ieee80211_ht_operation *)pos; + ht_oper->primary_chan = ieee80211_frequency_to_channel( + ecrnx_vif->sta.ap->center_freq); + switch (ecrnx_vif->sta.ap->width) { + case NL80211_CHAN_WIDTH_160: + case NL80211_CHAN_WIDTH_80P80: + case NL80211_CHAN_WIDTH_80: + case NL80211_CHAN_WIDTH_40: + if (ecrnx_vif->sta.ap->center_freq1 > ecrnx_vif->sta.ap->center_freq) + ht_oper->ht_param = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + else + ht_oper->ht_param = IEEE80211_HT_PARAM_CHA_SEC_BELOW; + break; + default: + ht_oper->ht_param = IEEE80211_HT_PARAM_CHA_SEC_NONE; + break; + } + if (ht_cap->cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 && + ecrnx_vif->sta.ap->width != NL80211_CHAN_WIDTH_20_NOHT && + ecrnx_vif->sta.ap->width != NL80211_CHAN_WIDTH_20) + ht_oper->ht_param |= IEEE80211_HT_PARAM_CHAN_WIDTH_ANY; + + ht_oper->operation_mode = cpu_to_le16(prot_mode); + ht_oper->stbc_param = 0x0000; + + /* It seems that Basic MCS set and Supported MCS set + are identical for the first 10 bytes */ + memset(&ht_oper->basic_set, 0, 16); + memcpy(&ht_oper->basic_set, &ht_cap->mcs, 10); + + return pos + sizeof(struct ieee80211_ht_operation); +} + +static u8 * +ecrnx_ie_build_vht_oper(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + u8 *pos, struct ieee80211_sta_ht_cap *ht_cap, + u16 prot_mode) +{ + struct ieee80211_vht_operation *vht_oper; + /* Build HT Information */ + *pos++ = WLAN_EID_VHT_OPERATION; + *pos++ = sizeof(struct ieee80211_vht_operation); + vht_oper = (struct ieee80211_vht_operation *)pos; + + switch (ecrnx_vif->sta.ap->width) { + case NL80211_CHAN_WIDTH_80: + vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_80MHZ; // Channel Width + CCFS0(vht_oper) = + ieee80211_frequency_to_channel(ecrnx_vif->sta.ap->center_freq); // Channel Center Frequency Segment 0 + CCFS1(vht_oper) = 0; // Channel Center Frequency Segment 1 (N.A.) + break; + case NL80211_CHAN_WIDTH_160: + vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_160MHZ; // Channel Width + CCFS0(vht_oper) = + ieee80211_frequency_to_channel(ecrnx_vif->sta.ap->center_freq); // Channel Center Frequency Segment 0 + CCFS1(vht_oper) = 0; // Channel Center Frequency Segment 1 (N.A.) + break; + case NL80211_CHAN_WIDTH_80P80: + vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_80P80MHZ; // Channel Width + CCFS0(vht_oper) = + ieee80211_frequency_to_channel(ecrnx_vif->sta.ap->center_freq1); // Channel Center Frequency Segment 0 + CCFS1(vht_oper) = + ieee80211_frequency_to_channel(ecrnx_vif->sta.ap->center_freq2); // Channel Center Frequency Segment 1 + break; + default: + vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_USE_HT; + CCFS0(vht_oper) = 0; + CCFS1(vht_oper) = 0; + break; + } + + vht_oper->basic_mcs_set = cpu_to_le16(ecrnx_hw->mod_params->mcs_map); + + return pos + sizeof(struct ieee80211_vht_operation); + +} + +static void +ecrnx_tdls_add_setup_start_ies(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + struct sk_buff *skb, const u8 *peer, + u8 action_code, bool initiator, + const u8 *extra_ies, size_t extra_ies_len) +{ + enum nl80211_band band = ecrnx_vif->sta.ap->band; + struct ieee80211_supported_band *sband; + struct ieee80211_sta_ht_cap ht_cap; + struct ieee80211_sta_vht_cap vht_cap; + size_t offset = 0, noffset; + u8 *pos; + + rcu_read_lock(); + + ecrnx_add_srates_ie(ecrnx_hw, skb); + ecrnx_add_ext_srates_ie(ecrnx_hw, skb); + ecrnx_tdls_add_supp_channels(ecrnx_hw, skb); + ecrnx_tdls_add_ext_capab(ecrnx_hw, skb); + + /* add the QoS element if we support it */ + if (/*local->hw.queues >= IEEE80211_NUM_ACS &&*/ + action_code != WLAN_PUB_ACTION_TDLS_DISCOVER_RES) + ecrnx_add_wmm_info_ie(skb, 0); /* no U-APSD */ + + ecrnx_tdls_add_oper_classes(ecrnx_vif, skb); + + /* + * with TDLS we can switch channels, and HT-caps are not necessarily + * the same on all bands. The specification limits the setup to a + * single HT-cap, so use the current band for now. + */ + sband = ecrnx_hw->wiphy->bands[band]; + memcpy(&ht_cap, &sband->ht_cap, sizeof(ht_cap)); + if (((action_code == WLAN_TDLS_SETUP_REQUEST) || + (action_code == WLAN_TDLS_SETUP_RESPONSE) || + (action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES)) && + ht_cap.ht_supported /* (!sta || sta->sta.ht_cap.ht_supported)*/) { + ecrnx_ie_build_ht_cap(skb, &ht_cap, ht_cap.cap); + } + + if (ht_cap.ht_supported && + (ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40)) + ecrnx_tdls_add_bss_coex_ie(skb); + + ecrnx_tdls_add_link_ie(ecrnx_hw, ecrnx_vif, skb, peer, initiator); + + memcpy(&vht_cap, &sband->vht_cap, sizeof(vht_cap)); + if (vht_cap.vht_supported) { + ecrnx_tdls_add_aid_ie(ecrnx_vif, skb); + ecrnx_ie_build_vht_cap(skb, &vht_cap, vht_cap.cap); + // Operating mode Notification (optional) + } + + /* add any remaining IEs */ + if (extra_ies_len) { + noffset = extra_ies_len; + pos = skb_put(skb, noffset - offset); + memcpy(pos, extra_ies + offset, noffset - offset); + } + + rcu_read_unlock(); +} + + +static void +ecrnx_tdls_add_setup_cfm_ies(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + struct sk_buff *skb, const u8 *peer, bool initiator, + const u8 *extra_ies, size_t extra_ies_len) +{ + struct ieee80211_supported_band *sband; + enum nl80211_band band = ecrnx_vif->sta.ap->band; + struct ieee80211_sta_ht_cap ht_cap; + struct ieee80211_sta_vht_cap vht_cap; + + size_t offset = 0, noffset; + struct ecrnx_sta *sta, *ap_sta; + u8 *pos; + + rcu_read_lock(); + + sta = ecrnx_get_sta(ecrnx_hw, peer); + ap_sta = ecrnx_vif->sta.ap; + if (WARN_ON_ONCE(!sta || !ap_sta)) { + rcu_read_unlock(); + return; + } + + /* add the QoS param IE if both the peer and we support it */ + if (sta->qos) + ecrnx_add_wmm_param_ie(skb, ap_sta->acm, ap_sta->ac_param); + + /* if HT support is only added in TDLS, we need an HT-operation IE */ + sband = ecrnx_hw->wiphy->bands[band]; + memcpy(&ht_cap, &sband->ht_cap, sizeof(ht_cap)); + if (ht_cap.ht_supported && !ap_sta->ht && sta->ht) { + pos = skb_put(skb, 2 + sizeof(struct ieee80211_ht_operation)); + /* send an empty HT operation IE */ + ecrnx_ie_build_ht_oper(ecrnx_hw, ecrnx_vif, pos, &ht_cap, 0); + } + + ecrnx_tdls_add_link_ie(ecrnx_hw, ecrnx_vif, skb, peer, initiator); + + memcpy(&vht_cap, &sband->vht_cap, sizeof(vht_cap)); + if (vht_cap.vht_supported && !ap_sta->vht && sta->vht) { + pos = skb_put(skb, 2 + sizeof(struct ieee80211_vht_operation)); + ecrnx_ie_build_vht_oper(ecrnx_hw, ecrnx_vif, pos, &ht_cap, 0); + // Operating mode Notification (optional) + } + + /* add any remaining IEs */ + if (extra_ies_len) { + noffset = extra_ies_len; + pos = skb_put(skb, noffset - offset); + memcpy(pos, extra_ies + offset, noffset - offset); + } + + rcu_read_unlock(); +} + +static void +ecrnx_tdls_add_ies(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + struct sk_buff *skb, const u8 *peer, + u8 action_code, u16 status_code, + bool initiator, const u8 *extra_ies, + size_t extra_ies_len, u8 oper_class, + struct cfg80211_chan_def *chandef) +{ + switch (action_code) { + case WLAN_TDLS_SETUP_REQUEST: + case WLAN_TDLS_SETUP_RESPONSE: + case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: + if (status_code == 0) + ecrnx_tdls_add_setup_start_ies(ecrnx_hw, ecrnx_vif, skb, peer, action_code, + initiator, extra_ies, extra_ies_len); + break; + case WLAN_TDLS_SETUP_CONFIRM: + if (status_code == 0) + ecrnx_tdls_add_setup_cfm_ies(ecrnx_hw, ecrnx_vif, skb, peer, initiator, + extra_ies, extra_ies_len); + break; + + case WLAN_TDLS_TEARDOWN: + case WLAN_TDLS_DISCOVERY_REQUEST: + if (extra_ies_len) + memcpy(skb_put(skb, extra_ies_len), extra_ies, + extra_ies_len); + if (status_code == 0 || action_code == WLAN_TDLS_TEARDOWN) + ecrnx_tdls_add_link_ie(ecrnx_hw, ecrnx_vif, skb, peer, initiator); + break; + } +} + +int +ecrnx_tdls_send_mgmt_packet_data(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + const u8 *peer, u8 action_code, u8 dialog_token, + u16 status_code, u32 peer_capability, bool initiator, + const u8 *extra_ies, size_t extra_ies_len, u8 oper_class, + struct cfg80211_chan_def *chandef) +{ + struct sk_buff *skb; + int ret = 0; + struct ieee80211_supported_band *ecrnx_band_2GHz = ecrnx_hw->wiphy->bands[NL80211_BAND_2GHZ]; +#ifdef CONFIG_ECRNX_5G + struct ieee80211_supported_band *ecrnx_band_5GHz = ecrnx_hw->wiphy->bands[NL80211_BAND_5GHZ]; +#endif + skb = netdev_alloc_skb(ecrnx_vif->ndev, + sizeof(struct ieee80211_tdls_data) + // ethhdr + TDLS info + 10 + /* supported rates */ + 6 + /* extended supported rates */ + #ifdef CONFIG_ECRNX_5G + (2 + ecrnx_band_2GHz->n_channels + ecrnx_band_5GHz->n_channels) + /* supported channels */ + #else + (2 + ecrnx_band_2GHz->n_channels) + + #endif + sizeof(struct ieee_types_extcap) + + sizeof(struct ieee80211_wmm_param_ie) + + 4 + /* oper classes */ + 28 + //sizeof(struct ieee80211_ht_cap) + + sizeof(struct ieee_types_bss_co_2040) + + sizeof(struct ieee80211_tdls_lnkie) + + (2 + sizeof(struct ieee80211_vht_cap)) + + 4 + /*AID*/ + (2 + sizeof(struct ieee80211_ht_operation)) + + extra_ies_len); + + if (!skb) + return 0; + + switch (action_code) { + case WLAN_TDLS_SETUP_REQUEST: + case WLAN_TDLS_SETUP_RESPONSE: + case WLAN_TDLS_SETUP_CONFIRM: + case WLAN_TDLS_TEARDOWN: + case WLAN_TDLS_DISCOVERY_REQUEST: + ret = ecrnx_tdls_prepare_encap_data(ecrnx_hw, ecrnx_vif, peer, action_code, + dialog_token, status_code, skb); + break; + + case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: + ret = ecrnx_prep_tdls_direct(ecrnx_hw, ecrnx_vif, peer, action_code, + dialog_token, status_code, skb); + break; + + default: + ret = -ENOTSUPP; + break; + } + + if (ret < 0) + goto fail; + + ecrnx_tdls_add_ies(ecrnx_hw, ecrnx_vif, skb, peer, action_code, status_code, + initiator, extra_ies, extra_ies_len, oper_class, chandef); + + if (action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES) { + u64 cookie; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) + struct cfg80211_mgmt_tx_params params; + + params.len = skb->len; + params.buf = skb->data; + ret = ecrnx_start_mgmt_xmit(ecrnx_vif, NULL, ¶ms, false, &cookie); +#else + ret = ecrnx_start_mgmt_xmit(ecrnx_vif, NULL, NULL, false, 0, skb->data, skb->len, false, false, &cookie); +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) */ + return ret; + } + + switch (action_code) { + case WLAN_TDLS_SETUP_REQUEST: + case WLAN_TDLS_SETUP_RESPONSE: + case WLAN_TDLS_SETUP_CONFIRM: + skb->priority = 2; + break; + default: + skb->priority = 5; + break; + } + + ret = ecrnx_select_txq(ecrnx_vif, skb); + ret = ecrnx_start_xmit(skb, ecrnx_vif->ndev); + + return ret; + +fail: + dev_kfree_skb(skb); + return ret; +} diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_tdls.h b/drivers/net/wireless/eswin/fullmac/ecrnx_tdls.h new file mode 100644 index 000000000000..4bec85dddea1 --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_tdls.h @@ -0,0 +1,54 @@ +/** + ****************************************************************************** + * + * @file ecrnx_tdls.h + * + * @brief TDLS function declarations + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef ECRNX_TDLS_H_ +#define ECRNX_TDLS_H_ + +#include "ecrnx_defs.h" + +struct ieee_types_header { + u8 element_id; + u8 len; +} __packed; + +struct ieee_types_bss_co_2040 { + struct ieee_types_header ieee_hdr; + u8 bss_2040co; +} __packed; + +struct ieee_types_extcap { + struct ieee_types_header ieee_hdr; + u8 ext_capab[8]; +} __packed; + +struct ieee_types_vht_cap { + struct ieee_types_header ieee_hdr; + struct ieee80211_vht_cap vhtcap; +} __packed; + +struct ieee_types_vht_oper { + struct ieee_types_header ieee_hdr; + struct ieee80211_vht_operation vhtoper; +} __packed; + +struct ieee_types_aid { + struct ieee_types_header ieee_hdr; + u16 aid; +} __packed; + +int ecrnx_tdls_send_mgmt_packet_data(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, + const u8 *peer, u8 action_code, u8 dialog_token, + u16 status_code, u32 peer_capability, bool initiator, + const u8 *extra_ies, size_t extra_ies_len, u8 oper_class, + struct cfg80211_chan_def *chandef); + +#endif /* ECRNX_TDLS_H_ */ diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_tx.c b/drivers/net/wireless/eswin/fullmac/ecrnx_tx.c new file mode 100644 index 000000000000..459e8185e984 --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_tx.c @@ -0,0 +1,1877 @@ +/** + ****************************************************************************** + * + * @file ecrnx_tx.c + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ +#include <linux/dma-mapping.h> +#include <linux/etherdevice.h> +#include <net/sock.h> + +#include "ecrnx_defs.h" +#include "ecrnx_tx.h" +#include "ecrnx_msg_tx.h" +#include "ecrnx_mesh.h" +#include "ecrnx_events.h" +#include "ecrnx_compat.h" + +#ifdef CONFIG_ECRNX_ESWIN +#include "eswin_utils.h" +#endif +/****************************************************************************** + * Power Save functions + *****************************************************************************/ +/** + * ecrnx_set_traffic_status - Inform FW if traffic is available for STA in PS + * + * @ecrnx_hw: Driver main data + * @sta: Sta in PS mode + * @available: whether traffic is buffered for the STA + * @ps_id: type of PS data requested (@LEGACY_PS_ID or @UAPSD_ID) + */ +void ecrnx_set_traffic_status(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_sta *sta, + bool available, + u8 ps_id) +{ + if (sta->tdls.active) { + ecrnx_send_tdls_peer_traffic_ind_req(ecrnx_hw, + ecrnx_hw->vif_table[sta->vif_idx]); + } else { + bool uapsd = (ps_id != LEGACY_PS_ID); + ecrnx_send_me_traffic_ind(ecrnx_hw, sta->sta_idx, uapsd, available); + trace_ps_traffic_update(sta->sta_idx, available, uapsd); + } +} + +/** + * ecrnx_ps_bh_enable - Enable/disable PS mode for one STA + * + * @ecrnx_hw: Driver main data + * @sta: Sta which enters/leaves PS mode + * @enable: PS mode status + * + * This function will enable/disable PS mode for one STA. + * When enabling PS mode: + * - Stop all STA's txq for ECRNX_TXQ_STOP_STA_PS reason + * - Count how many buffers are already ready for this STA + * - For BC/MC sta, update all queued SKB to use hw_queue BCMC + * - Update TIM if some packet are ready + * + * When disabling PS mode: + * - Start all STA's txq for ECRNX_TXQ_STOP_STA_PS reason + * - For BC/MC sta, update all queued SKB to use hw_queue AC_BE + * - Update TIM if some packet are ready (otherwise fw will not update TIM + * in beacon for this STA) + * + * All counter/skb updates are protected from TX path by taking tx_lock + * + * NOTE: _bh_ in function name indicates that this function is called + * from a bottom_half tasklet. + */ +void ecrnx_ps_bh_enable(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta, + bool enable) +{ + struct ecrnx_txq *txq; + + if (enable) { + trace_ps_enable(sta); + + spin_lock(&ecrnx_hw->tx_lock); + sta->ps.active = true; + sta->ps.sp_cnt[LEGACY_PS_ID] = 0; + sta->ps.sp_cnt[UAPSD_ID] = 0; + ecrnx_txq_sta_stop(sta, ECRNX_TXQ_STOP_STA_PS, ecrnx_hw); + + if (is_multicast_sta(sta->sta_idx)) { + txq = ecrnx_txq_sta_get(sta, 0, ecrnx_hw); + sta->ps.pkt_ready[LEGACY_PS_ID] = skb_queue_len(&txq->sk_list); + sta->ps.pkt_ready[UAPSD_ID] = 0; + txq->hwq = &ecrnx_hw->hwq[ECRNX_HWQ_BCMC]; + } else { + int i; + sta->ps.pkt_ready[LEGACY_PS_ID] = 0; + sta->ps.pkt_ready[UAPSD_ID] = 0; + foreach_sta_txq(sta, txq, i, ecrnx_hw) { + sta->ps.pkt_ready[txq->ps_id] += skb_queue_len(&txq->sk_list); + } + } + + spin_unlock(&ecrnx_hw->tx_lock); + + if (sta->ps.pkt_ready[LEGACY_PS_ID]) + ecrnx_set_traffic_status(ecrnx_hw, sta, true, LEGACY_PS_ID); + + if (sta->ps.pkt_ready[UAPSD_ID]) + ecrnx_set_traffic_status(ecrnx_hw, sta, true, UAPSD_ID); + } else { + trace_ps_disable(sta->sta_idx); + + spin_lock(&ecrnx_hw->tx_lock); + sta->ps.active = false; + + if (is_multicast_sta(sta->sta_idx)) { + txq = ecrnx_txq_sta_get(sta, 0, ecrnx_hw); + txq->hwq = &ecrnx_hw->hwq[ECRNX_HWQ_BE]; + txq->push_limit = 0; + } else { + int i; + foreach_sta_txq(sta, txq, i, ecrnx_hw) { + txq->push_limit = 0; + } + } + + ecrnx_txq_sta_start(sta, ECRNX_TXQ_STOP_STA_PS, ecrnx_hw); + spin_unlock(&ecrnx_hw->tx_lock); + + if (sta->ps.pkt_ready[LEGACY_PS_ID]) + ecrnx_set_traffic_status(ecrnx_hw, sta, false, LEGACY_PS_ID); + + if (sta->ps.pkt_ready[UAPSD_ID]) + ecrnx_set_traffic_status(ecrnx_hw, sta, false, UAPSD_ID); + } +} + +/** + * ecrnx_ps_bh_traffic_req - Handle traffic request for STA in PS mode + * + * @ecrnx_hw: Driver main data + * @sta: Sta which enters/leaves PS mode + * @pkt_req: number of pkt to push + * @ps_id: type of PS data requested (@LEGACY_PS_ID or @UAPSD_ID) + * + * This function will make sure that @pkt_req are pushed to fw + * whereas the STA is in PS mode. + * If request is 0, send all traffic + * If request is greater than available pkt, reduce request + * Note: request will also be reduce if txq credits are not available + * + * All counter updates are protected from TX path by taking tx_lock + * + * NOTE: _bh_ in function name indicates that this function is called + * from the bottom_half tasklet. + */ +void ecrnx_ps_bh_traffic_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta, + u16 pkt_req, u8 ps_id) +{ + int pkt_ready_all; + u16 txq_len; + struct ecrnx_txq *txq; + +#ifndef CONFIG_ECRNX_ESWIN + if (WARN(!sta->ps.active, "sta %pM is not in Power Save mode", + sta->mac_addr)) + return; +#else + if (!sta->ps.active) + { + ECRNX_DBG(" sta is not in Power Save mode %02x:%02x:%02x:%02x:%02x:%02x %d %d \n", sta->mac_addr[0], sta->mac_addr[1], sta->mac_addr[2], \ + sta->mac_addr[3], sta->mac_addr[4], sta->mac_addr[5],pkt_req, ps_id); + return; + } +#endif + + trace_ps_traffic_req(sta, pkt_req, ps_id); + + spin_lock(&ecrnx_hw->tx_lock); + + /* Fw may ask to stop a service period with PS_SP_INTERRUPTED. This only + happens for p2p-go interface if NOA starts during a service period */ + if ((pkt_req == PS_SP_INTERRUPTED) && (ps_id == UAPSD_ID)) { + int tid; + sta->ps.sp_cnt[ps_id] = 0; + foreach_sta_txq(sta, txq, tid, ecrnx_hw) { + txq->push_limit = 0; + } + goto done; + } + + pkt_ready_all = (sta->ps.pkt_ready[ps_id] - sta->ps.sp_cnt[ps_id]); + + /* Don't start SP until previous one is finished or we don't have + packet ready (which must not happen for U-APSD) */ + if (sta->ps.sp_cnt[ps_id] || pkt_ready_all <= 0) { + goto done; + } + + /* Adapt request to what is available. */ + if (pkt_req == 0 || pkt_req > pkt_ready_all) { + pkt_req = pkt_ready_all; + } + + /* Reset the SP counter */ + sta->ps.sp_cnt[ps_id] = 0; + + /* "dispatch" the request between txq */ + if (is_multicast_sta(sta->sta_idx)) { + txq = ecrnx_txq_sta_get(sta, 0, ecrnx_hw); + if (txq->credits <= 0) + goto done; + if (pkt_req > txq->credits) + pkt_req = txq->credits; + txq->push_limit = pkt_req; + sta->ps.sp_cnt[ps_id] = pkt_req; + ECRNX_DBG("%s-%d:sta:0x%p, sta_idx:%d, txq:0x%p, txq status:%d \n", __func__, __LINE__, sta, sta->sta_idx, txq, txq->status); + ecrnx_txq_add_to_hw_list(txq); + ecrnx_txq_sta_start(sta, ECRNX_TXQ_STOP_STA_PS, ecrnx_hw); + } else { + int i, tid; + + for (i = 0; i < NX_NB_TID_PER_STA; i++) { + tid = nx_tid_prio[i]; + +#ifdef CONFIG_ECRNX_SOFTMAC + txq = ecrnx_txq_sta_get(sta, tid); +#else + txq = ecrnx_txq_sta_get(sta, tid, ecrnx_hw); +#endif + + txq_len = skb_queue_len(&txq->sk_list); + + if (txq->ps_id != ps_id) + continue; + + if (txq_len > txq->credits) + txq_len = txq->credits; + + if (txq_len == 0) + continue; + + if (txq_len < pkt_req) { + /* Not enough pkt queued in this txq, add this + txq to hwq list and process next txq */ + pkt_req -= txq_len; + txq->push_limit = txq_len; + sta->ps.sp_cnt[ps_id] += txq_len; + ecrnx_txq_add_to_hw_list(txq); + } else { + /* Enough pkt in this txq to comlete the request + add this txq to hwq list and stop processing txq */ + txq->push_limit = pkt_req; + sta->ps.sp_cnt[ps_id] += pkt_req; + ecrnx_txq_add_to_hw_list(txq); + break; + } + } + } + + done: + spin_unlock(&ecrnx_hw->tx_lock); +} + +/****************************************************************************** + * TX functions + *****************************************************************************/ +#define PRIO_STA_NULL 0xAA + +static const int ecrnx_down_hwq2tid[3] = { + [ECRNX_HWQ_BK] = 2, + [ECRNX_HWQ_BE] = 3, + [ECRNX_HWQ_VI] = 5, +}; + +static void ecrnx_downgrade_ac(struct ecrnx_sta *sta, struct sk_buff *skb) +{ + int8_t ac = ecrnx_tid2hwq[skb->priority]; + + if (WARN((ac > ECRNX_HWQ_VO), + "Unexepcted ac %d for skb before downgrade", ac)) + ac = ECRNX_HWQ_VO; + + while (sta->acm & BIT(ac)) { + if (ac == ECRNX_HWQ_BK) { + skb->priority = 1; + return; + } + ac--; + skb->priority = ecrnx_down_hwq2tid[ac]; + } +} + +static void ecrnx_tx_statistic(struct ecrnx_hw *ecrnx_hw, struct ecrnx_txq *txq, + union ecrnx_hw_txstatus ecrnx_txst, unsigned int data_len) +{ + struct ecrnx_sta *sta = txq->sta; + if (!sta || !ecrnx_txst.acknowledged) + return; + sta->stats.tx_pkts ++; + sta->stats.tx_bytes += data_len; + sta->stats.last_act = ecrnx_hw->stats.last_tx; +} +u16 ecrnx_select_txq(struct ecrnx_vif *ecrnx_vif, struct sk_buff *skb) +{ + struct ecrnx_hw *ecrnx_hw = ecrnx_vif->ecrnx_hw; + struct wireless_dev *wdev = &ecrnx_vif->wdev; + struct ecrnx_sta *sta = NULL; + struct ecrnx_txq *txq; + u16 netdev_queue; + bool tdls_mgmgt_frame = false; + + switch (wdev->iftype) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + { + struct ethhdr *eth; + eth = (struct ethhdr *)skb->data; + if (eth->h_proto == cpu_to_be16(ETH_P_TDLS)) { + tdls_mgmgt_frame = true; + } + if ((ecrnx_vif->tdls_status == TDLS_LINK_ACTIVE) && + (ecrnx_vif->sta.tdls_sta != NULL) && + (memcmp(eth->h_dest, ecrnx_vif->sta.tdls_sta->mac_addr, ETH_ALEN) == 0)) + sta = ecrnx_vif->sta.tdls_sta; + else + sta = ecrnx_vif->sta.ap; + break; + } + case NL80211_IFTYPE_AP_VLAN: + { + struct ecrnx_sta *cur; + struct ethhdr *eth = (struct ethhdr *)skb->data; + + if (ecrnx_vif->ap_vlan.sta_4a) { + sta = ecrnx_vif->ap_vlan.sta_4a; + break; + } + + /* AP_VLAN interface is not used for a 4A STA, + fallback searching sta amongs all AP's clients */ + ecrnx_vif = ecrnx_vif->ap_vlan.master; + + if (is_multicast_ether_addr(eth->h_dest)) { + sta = &ecrnx_hw->sta_table[ecrnx_vif->ap.bcmc_index]; + } else { + list_for_each_entry(cur, &ecrnx_vif->ap.sta_list, list) { + if (!memcmp(cur->mac_addr, eth->h_dest, ETH_ALEN)) { + sta = cur; + break; + } + } + } + + break; + } + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + { + struct ecrnx_sta *cur; + struct ethhdr *eth = (struct ethhdr *)skb->data; + + if (is_multicast_ether_addr(eth->h_dest)) { + sta = &ecrnx_hw->sta_table[ecrnx_vif->ap.bcmc_index]; + } else { + list_for_each_entry(cur, &ecrnx_vif->ap.sta_list, list) { + if (!memcmp(cur->mac_addr, eth->h_dest, ETH_ALEN)) { + sta = cur; + break; + } + } + } + + break; + } + case NL80211_IFTYPE_MESH_POINT: + { + struct ethhdr *eth = (struct ethhdr *)skb->data; + + if (!ecrnx_vif->is_resending) { + /* + * If ethernet source address is not the address of a mesh wireless interface, we are proxy for + * this address and have to inform the HW + */ + if (memcmp(ð->h_source[0], &ecrnx_vif->ndev->perm_addr[0], ETH_ALEN)) { + /* Check if LMAC is already informed */ + if (!ecrnx_get_mesh_proxy_info(ecrnx_vif, (u8 *)ð->h_source, true)) { + ecrnx_send_mesh_proxy_add_req(ecrnx_hw, ecrnx_vif, (u8 *)ð->h_source); + } + } + } + + if (is_multicast_ether_addr(eth->h_dest)) { + sta = &ecrnx_hw->sta_table[ecrnx_vif->ap.bcmc_index]; + } else { + /* Path to be used */ + struct ecrnx_mesh_path *p_mesh_path = NULL; + struct ecrnx_mesh_path *p_cur_path; + /* Check if destination is proxied by a peer Mesh STA */ + struct ecrnx_mesh_proxy *p_mesh_proxy = ecrnx_get_mesh_proxy_info(ecrnx_vif, (u8 *)ð->h_dest, false); + /* Mesh Target address */ + struct mac_addr *p_tgt_mac_addr; + + if (p_mesh_proxy) { + p_tgt_mac_addr = &p_mesh_proxy->proxy_addr; + } else { + p_tgt_mac_addr = (struct mac_addr *)ð->h_dest; + } + + /* Look for path with provided target address */ + list_for_each_entry(p_cur_path, &ecrnx_vif->ap.mpath_list, list) { + if (!memcmp(&p_cur_path->tgt_mac_addr, p_tgt_mac_addr, ETH_ALEN)) { + p_mesh_path = p_cur_path; + break; + } + } + + if (p_mesh_path) { + sta = p_mesh_path->nhop_sta; + } else { + ecrnx_send_mesh_path_create_req(ecrnx_hw, ecrnx_vif, (u8 *)p_tgt_mac_addr); + } + } + + break; + } + default: + break; + } + + if (sta && sta->qos) + { + if (tdls_mgmgt_frame) { + skb_set_queue_mapping(skb, NX_STA_NDEV_IDX(skb->priority, sta->sta_idx)); + } else { + /* use the data classifier to determine what 802.1d tag the + * data frame has */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0) + skb->priority = cfg80211_classify8021d(skb) & IEEE80211_QOS_CTL_TAG1D_MASK; +#else + skb->priority = cfg80211_classify8021d(skb, NULL) & IEEE80211_QOS_CTL_TAG1D_MASK; +#endif + } + if (sta->acm) + ecrnx_downgrade_ac(sta, skb); + + txq = ecrnx_txq_sta_get(sta, skb->priority, ecrnx_hw); + netdev_queue = txq->ndev_idx; + } + else if (sta) + { + skb->priority = 0xFF; + txq = ecrnx_txq_sta_get(sta, 0, ecrnx_hw); + netdev_queue = txq->ndev_idx; + } + else + { + /* This packet will be dropped in xmit function, still need to select + an active queue for xmit to be called. As it most likely to happen + for AP interface, select BCMC queue + (TODO: select another queue if BCMC queue is stopped) */ + skb->priority = PRIO_STA_NULL; + netdev_queue = NX_BCMC_TXQ_NDEV_IDX; + } + + BUG_ON(netdev_queue >= NX_NB_NDEV_TXQ); + + return netdev_queue; +} + +/** + * ecrnx_set_more_data_flag - Update MORE_DATA flag in tx sw desc + * + * @ecrnx_hw: Driver main data + * @sw_txhdr: Header for pkt to be pushed + * + * If STA is in PS mode + * - Set EOSP in case the packet is the last of the UAPSD service period + * - Set MORE_DATA flag if more pkt are ready for this sta + * - Update TIM if this is the last pkt buffered for this sta + * + * note: tx_lock already taken. + */ +static inline void ecrnx_set_more_data_flag(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_sw_txhdr *sw_txhdr) +{ + struct ecrnx_sta *sta = sw_txhdr->ecrnx_sta; + struct ecrnx_vif *vif = sw_txhdr->ecrnx_vif; + struct ecrnx_txq *txq = sw_txhdr->txq; + + if (unlikely(sta->ps.active)) { + sta->ps.pkt_ready[txq->ps_id]--; + sta->ps.sp_cnt[txq->ps_id]--; + + trace_ps_push(sta); + + if (((txq->ps_id == UAPSD_ID) || (vif->wdev.iftype == NL80211_IFTYPE_MESH_POINT) || (sta->tdls.active)) + && !sta->ps.sp_cnt[txq->ps_id]) { + sw_txhdr->desc.host.flags |= TXU_CNTRL_EOSP; + } + + if (sta->ps.pkt_ready[txq->ps_id]) { + sw_txhdr->desc.host.flags |= TXU_CNTRL_MORE_DATA; + } else { + ecrnx_set_traffic_status(ecrnx_hw, sta, false, txq->ps_id); + } + } +} + +/** + * ecrnx_get_tx_info - Get STA and tid for one skb + * + * @ecrnx_vif: vif ptr + * @skb: skb + * @tid: pointer updated with the tid to use for this skb + * + * @return: pointer on the destination STA (may be NULL) + * + * skb has already been parsed in ecrnx_select_queue function + * simply re-read information form skb. + */ +static struct ecrnx_sta *ecrnx_get_tx_info(struct ecrnx_vif *ecrnx_vif, + struct sk_buff *skb, + u8 *tid) +{ + struct ecrnx_hw *ecrnx_hw = ecrnx_vif->ecrnx_hw; + struct ecrnx_sta *sta; + int sta_idx; + + *tid = skb->priority; + if (unlikely(skb->priority == PRIO_STA_NULL)) { + return NULL; + } else { + int ndev_idx = skb_get_queue_mapping(skb); + + if (ndev_idx == NX_BCMC_TXQ_NDEV_IDX) + sta_idx = NX_REMOTE_STA_MAX + master_vif_idx(ecrnx_vif); + else + sta_idx = ndev_idx / NX_NB_TID_PER_STA; + + sta = &ecrnx_hw->sta_table[sta_idx]; + } + + return sta; +} + +#ifndef CONFIG_ECRNX_ESWIN +/** + * ecrnx_prep_tx - Prepare buffer for DMA transmission + * + * @ecrnx_hw: Driver main data + * @txhdr: Tx descriptor + * + * Maps hw_txhdr and buffer data for transmission via DMA. + * - Data buffer with be downloaded by embebded side. + * - hw_txhdr will be uploaded by embedded side when buffer has been + * transmitted over the air. + */ +static int ecrnx_prep_dma_tx(struct ecrnx_hw *ecrnx_hw, struct ecrnx_txhdr *txhdr, bool eth_hdr) +{ + struct ecrnx_sw_txhdr *sw_txhdr = txhdr->sw_hdr; + struct ecrnx_hw_txhdr *hw_txhdr = &txhdr->hw_hdr; + struct txdesc_api *desc = &sw_txhdr->desc; + dma_addr_t dma_addr; + + txhdr->hw_hdr.cfm.status.value = 0; + /* MAP (and sync) memory for DMA */ + dma_addr = dma_map_single(ecrnx_hw->dev, hw_txhdr, + sw_txhdr->map_len, DMA_BIDIRECTIONAL); + if (WARN_ON(dma_mapping_error(ecrnx_hw->dev, dma_addr))) + return -1; + + sw_txhdr->dma_addr = dma_addr; + + desc->host.status_desc_addr = dma_addr; + dma_addr += ECRNX_TX_DATA_OFT(sw_txhdr); + if (eth_hdr) + dma_addr += sizeof(struct ethhdr); +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + desc->host.packet_len[0] = sw_txhdr->frame_len; + desc->host.packet_addr[0] = dma_addr; + desc->host.packet_cnt = 1; +#else + desc->host.packet_len = sw_txhdr->frame_len; + desc->host.packet_addr = dma_addr; +#endif + return 0; +} +#endif + +/** + * ecrnx_tx_push - Push one packet to fw + * + * @ecrnx_hw: Driver main data + * @txhdr: tx desc of the buffer to push + * @flags: push flags (see @ecrnx_push_flags) + * + * Push one packet to fw. Sw desc of the packet has already been updated. + * Only MORE_DATA flag will be set if needed. + */ +void ecrnx_tx_push(struct ecrnx_hw *ecrnx_hw, struct ecrnx_txhdr *txhdr, int flags) +{ + struct ecrnx_sw_txhdr *sw_txhdr = txhdr->sw_hdr; + struct sk_buff *skb = sw_txhdr->skb; + struct ecrnx_txq *txq = sw_txhdr->txq; + u16 hw_queue = txq->hwq->id; + int user = 0; + + lockdep_assert_held(&ecrnx_hw->tx_lock); + + /* RETRY flag is not always set so retest here */ + if (txq->nb_retry) { + flags |= ECRNX_PUSH_RETRY; + txq->nb_retry--; + if (txq->nb_retry == 0) { + WARN(skb != txq->last_retry_skb, + "last retry buffer is not the expected one"); + txq->last_retry_skb = NULL; + } + } else if (!(flags & ECRNX_PUSH_RETRY)) { + txq->pkt_sent++; + } + +#ifdef CONFIG_ECRNX_AMSDUS_TX + if (txq->amsdu == sw_txhdr) { + WARN((flags & ECRNX_PUSH_RETRY), "End A-MSDU on a retry"); + ecrnx_hw->stats.amsdus[sw_txhdr->amsdu.nb - 1].done++; + txq->amsdu = NULL; + } else if (!(flags & ECRNX_PUSH_RETRY) && + !(sw_txhdr->desc.host.flags & TXU_CNTRL_AMSDU)) { + ecrnx_hw->stats.amsdus[0].done++; + } +#endif /* CONFIG_ECRNX_AMSDUS_TX */ + + /* Wait here to update hw_queue, as for multicast STA hwq may change + between queue and push (because of PS) */ + sw_txhdr->hw_queue = hw_queue; + +#ifdef CONFIG_ECRNX_MUMIMO_TX + /* MU group is only selected during hwq processing */ + sw_txhdr->desc.host.mumimo_info = txq->mumimo_info; + user = ECRNX_TXQ_POS_ID(txq); +#endif /* CONFIG_ECRNX_MUMIMO_TX */ + + if (sw_txhdr->ecrnx_sta) { + /* only for AP mode */ + ecrnx_set_more_data_flag(ecrnx_hw, sw_txhdr); + } + + trace_push_desc(skb, sw_txhdr, flags); + txq->credits--; + txq->pkt_pushed[user]++; + if (txq->credits <= 0){ + ECRNX_DBG("%s-%d:ecrnx_txq_stop,reaosn:0x%x \n", __func__, __LINE__, ECRNX_TXQ_STOP_FULL); + ecrnx_txq_stop(txq, ECRNX_TXQ_STOP_FULL); + } + + if (txq->push_limit) + txq->push_limit--; + + ecrnx_ipc_txdesc_push(ecrnx_hw, &sw_txhdr->desc, skb, hw_queue, user); + txq->hwq->credits[user]--; + ecrnx_hw->stats.cfm_balance[hw_queue]++; +} + + + +/** + * ecrnx_tx_retry - Push an AMPDU pkt that need to be retried + * + * @ecrnx_hw: Driver main data + * @skb: pkt to re-push + * @txhdr: tx desc of the pkt to re-push + * @sw_retry: Indicates if fw decide to retry this buffer + * (i.e. it has never been transmitted over the air) + * + * Called when a packet needs to be repushed to the firmware. + * First update sw descriptor and then queue it in the retry list. + */ +static void ecrnx_tx_retry(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb, + struct ecrnx_txhdr *txhdr, bool sw_retry) +{ + struct ecrnx_sw_txhdr *sw_txhdr = txhdr->sw_hdr; + struct tx_cfm_tag *cfm = &txhdr->hw_hdr.cfm; + struct ecrnx_txq *txq = sw_txhdr->txq; +#ifndef CONFIG_ECRNX_ESWIN + dma_addr_t cfm_dma_addr; +#endif + + if (!sw_retry) { + /* update sw desc */ + sw_txhdr->desc.host.sn = cfm->sn; + sw_txhdr->desc.host.pn[0] = cfm->pn[0]; + sw_txhdr->desc.host.pn[1] = cfm->pn[1]; + sw_txhdr->desc.host.pn[2] = cfm->pn[2]; + sw_txhdr->desc.host.pn[3] = cfm->pn[3]; + sw_txhdr->desc.host.timestamp = cfm->timestamp; + sw_txhdr->desc.host.flags |= TXU_CNTRL_RETRY; + + #ifdef CONFIG_ECRNX_AMSDUS_TX + if (sw_txhdr->desc.host.flags & TXU_CNTRL_AMSDU) + ecrnx_hw->stats.amsdus[sw_txhdr->amsdu.nb - 1].failed++; + #endif + } + + /* MORE_DATA will be re-set if needed when pkt will be repushed */ + sw_txhdr->desc.host.flags &= ~TXU_CNTRL_MORE_DATA; + + cfm->status.value = 0; +//TODO:need to check here. +#ifndef CONFIG_ECRNX_ESWIN + cfm_dma_addr = (ptr_addr)sw_txhdr->desc.host.status_desc_addr; + dma_sync_single_for_device(ecrnx_hw->dev, cfm_dma_addr, sizeof(cfm), DMA_BIDIRECTIONAL); +#endif + txq->credits++; + if (txq->credits > 0){ + ecrnx_txq_start(txq, ECRNX_TXQ_STOP_FULL); + ECRNX_DBG("%s-%d:ecrnx_txq_start,reaosn:0x%x \n", __func__, __LINE__, ECRNX_TXQ_STOP_FULL); + } + /* Queue the buffer */ + if (ecrnx_txq_queue_skb(skb, txq, ecrnx_hw, true, NULL)) + { + /* baoyong:we need to send this AMPDU retry pkt asap, so process it now */ + ecrnx_hwq_process(ecrnx_hw, txq->hwq); + } + + return; +} + + +#ifdef CONFIG_ECRNX_AMSDUS_TX +/* return size of subframe (including header) */ +static inline int ecrnx_amsdu_subframe_length(struct ethhdr *eth, int eth_len) +{ + /* ethernet header is replaced with amdsu header that have the same size + Only need to check if LLC/SNAP header will be added */ + int len = eth_len; + + if (ntohs(eth->h_proto) >= ETH_P_802_3_MIN) { + len += sizeof(rfc1042_header) + 2; + } + + return len; +} + +static inline bool ecrnx_amsdu_is_aggregable(struct sk_buff *skb) +{ + /* need to add some check on buffer to see if it can be aggregated ? */ + return true; +} + + +/** + * ecrnx_amsdu_del_subframe_header - remove AMSDU header + * + * amsdu_txhdr: amsdu tx descriptor + * + * Move back the ethernet header at the "beginning" of the data buffer. + * (which has been moved in @ecrnx_amsdu_add_subframe_header) + */ +static void ecrnx_amsdu_del_subframe_header(struct ecrnx_amsdu_txhdr *amsdu_txhdr) +{ + struct sk_buff *skb = amsdu_txhdr->skb; + struct ethhdr *eth; + u8 *pos; + + pos = skb->data; + pos += sizeof(struct ecrnx_amsdu_txhdr); + eth = (struct ethhdr*)pos; + pos += amsdu_txhdr->pad + sizeof(struct ethhdr); + + if (ntohs(eth->h_proto) >= ETH_P_802_3_MIN) { + pos += sizeof(rfc1042_header) + 2; + } + + memmove(pos, eth, sizeof(*eth)); + skb_pull(skb, (pos - skb->data)); +} + +/** + * ecrnx_amsdu_add_subframe_header - Add AMSDU header and link subframe + * + * @ecrnx_hw Driver main data + * @skb Buffer to aggregate + * @sw_txhdr Tx descriptor for the first A-MSDU subframe + * + * return 0 on sucess, -1 otherwise + * + * This functions Add A-MSDU header and LLC/SNAP header in the buffer + * and update sw_txhdr of the first subframe to link this buffer. + * If an error happens, the buffer will be queued as a normal buffer. + * + * + * Before After + * +-------------+ +-------------+ + * | HEADROOM | | HEADROOM | + * | | +-------------+ <- data + * | | | amsdu_txhdr | + * | | | * pad size | + * | | +-------------+ + * | | | ETH hdr | keep original eth hdr + * | | | | to restore it once transmitted + * | | +-------------+ <- packet_addr[x] + * | | | Pad | + * | | +-------------+ + * data -> +-------------+ | AMSDU HDR | + * | ETH hdr | +-------------+ + * | | | LLC/SNAP | + * +-------------+ +-------------+ + * | DATA | | DATA | + * | | | | + * +-------------+ +-------------+ + * + * Called with tx_lock hold + */ +static int ecrnx_amsdu_add_subframe_header(struct ecrnx_hw *ecrnx_hw, + struct sk_buff *skb, + struct ecrnx_sw_txhdr *sw_txhdr) +{ + struct ecrnx_amsdu *amsdu = &sw_txhdr->amsdu; + struct ecrnx_amsdu_txhdr *amsdu_txhdr; + struct ethhdr *amsdu_hdr, *eth = (struct ethhdr *)skb->data; + int headroom_need, map_len, msdu_len, amsdu_len, map_oft = 0; +#ifndef CONFIG_ECRNX_ESWIN + dma_addr_t dma_addr; +#endif + u8 *pos, *map_start; + + map_len = ECRNX_TX_DMA_MAP_LEN(skb); + msdu_len = skb->len - sizeof(*eth); + headroom_need = sizeof(*amsdu_txhdr) + amsdu->pad + + sizeof(*amsdu_hdr); + if (ntohs(eth->h_proto) >= ETH_P_802_3_MIN) { + headroom_need += sizeof(rfc1042_header) + 2; + msdu_len += sizeof(rfc1042_header) + 2; + } + amsdu_len = msdu_len + sizeof(*amsdu_hdr) + amsdu->pad; + + /* we should have enough headroom (checked in xmit) */ + if (WARN_ON(skb_headroom(skb) < headroom_need)) { + return -1; + } + + /* allocate headroom */ + pos = skb_push(skb, headroom_need); + amsdu_txhdr = (struct ecrnx_amsdu_txhdr *)pos; + pos += sizeof(*amsdu_txhdr); + + /* move eth header */ + memmove(pos, eth, sizeof(*eth)); + eth = (struct ethhdr *)pos; + pos += sizeof(*eth); + + /* Add padding from previous subframe */ + map_start = pos; + memset(pos, 0, amsdu->pad); + pos += amsdu->pad; + + /* Add AMSDU hdr */ + amsdu_hdr = (struct ethhdr *)pos; + memcpy(amsdu_hdr->h_dest, eth->h_dest, ETH_ALEN); + memcpy(amsdu_hdr->h_source, eth->h_source, ETH_ALEN); + amsdu_hdr->h_proto = htons(msdu_len); + pos += sizeof(*amsdu_hdr); + + if (ntohs(eth->h_proto) >= ETH_P_802_3_MIN) { + memcpy(pos, rfc1042_header, sizeof(rfc1042_header)); + pos += sizeof(rfc1042_header) + 2; + } + + if (amsdu_len < map_len) { + map_oft = map_len - amsdu_len; + map_start -= map_oft; + } + /* MAP (and sync) memory for DMA */ +#ifndef CONFIG_ECRNX_ESWIN + dma_addr = dma_map_single(ecrnx_hw->dev, map_start, map_len, DMA_BIDIRECTIONAL); + if (WARN_ON(dma_mapping_error(ecrnx_hw->dev, dma_addr))) { + pos -= sizeof(*eth); + memmove(pos, eth, sizeof(*eth)); + skb_pull(skb, headroom_need); + return -1; + } +#endif + + /* update amdsu_txhdr */ + amsdu_txhdr->map_len = map_len; +#ifdef CONFIG_ECRNX_ESWIN + amsdu_txhdr->send_pos = map_start; +#else + amsdu_txhdr->dma_addr = dma_addr; +#endif + amsdu_txhdr->skb = skb; + amsdu_txhdr->pad = amsdu->pad; + amsdu_txhdr->msdu_len = msdu_len; + + /* update ecrnx_sw_txhdr (of the first subframe) */ + BUG_ON(amsdu->nb != sw_txhdr->desc.host.packet_cnt); +#ifdef CONFIG_ECRNX_ESWIN + sw_txhdr->desc.host.packet_addr[amsdu->nb] = skb; +#else + sw_txhdr->desc.host.packet_addr[amsdu->nb] = dma_addr + map_oft; +#endif + sw_txhdr->desc.host.packet_len[amsdu->nb] = amsdu_len; + sw_txhdr->desc.host.packet_cnt++; + amsdu->nb++; + + amsdu->pad = AMSDU_PADDING(amsdu_len - amsdu->pad); + list_add_tail(&amsdu_txhdr->list, &amsdu->hdrs); + amsdu->len += amsdu_len; + + ecrnx_ipc_sta_buffer(ecrnx_hw, sw_txhdr->txq->sta, + sw_txhdr->txq->tid, msdu_len); + + trace_amsdu_subframe(sw_txhdr); + return 0; +} + +/** + * ecrnx_amsdu_add_subframe - Add this buffer as an A-MSDU subframe if possible + * + * @ecrnx_hw Driver main data + * @skb Buffer to aggregate if possible + * @sta Destination STA + * @txq sta's txq used for this buffer + * + * Tyr to aggregate the buffer in an A-MSDU. If it succeed then the + * buffer is added as a new A-MSDU subframe with AMSDU and LLC/SNAP + * headers added (so FW won't have to modify this subframe). + * + * To be added as subframe : + * - sta must allow amsdu + * - buffer must be aggregable (to be defined) + * - at least one other aggregable buffer is pending in the queue + * or an a-msdu (with enough free space) is currently in progress + * + * returns true if buffer has been added as A-MDSP subframe, false otherwise + * + */ +static bool ecrnx_amsdu_add_subframe(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb, + struct ecrnx_sta *sta, struct ecrnx_txq *txq) +{ + bool res = false; + struct ethhdr *eth; + ecrnx_adjust_amsdu_maxnb(ecrnx_hw); + + /* immediately return if amsdu are not allowed for this sta */ + if (!txq->amsdu_len || ecrnx_hw->mod_params->amsdu_maxnb < 2 || + !ecrnx_amsdu_is_aggregable(skb) + ) + return false; + + spin_lock_bh(&ecrnx_hw->tx_lock); + if (txq->amsdu) { + /* aggreagation already in progress, add this buffer if enough space + available, otherwise end the current amsdu */ + struct ecrnx_sw_txhdr *sw_txhdr = txq->amsdu; + eth = (struct ethhdr *)(skb->data); + + if (((sw_txhdr->amsdu.len + sw_txhdr->amsdu.pad + + ecrnx_amsdu_subframe_length(eth, skb->len)) > txq->amsdu_len) || + ecrnx_amsdu_add_subframe_header(ecrnx_hw, skb, sw_txhdr)) { + txq->amsdu = NULL; + goto end; + } + + if (sw_txhdr->amsdu.nb >= ecrnx_hw->mod_params->amsdu_maxnb) { + ecrnx_hw->stats.amsdus[sw_txhdr->amsdu.nb - 1].done++; + /* max number of subframes reached */ + txq->amsdu = NULL; + } + } else { + /* Check if a new amsdu can be started with the previous buffer + (if any) and this one */ + struct sk_buff *skb_prev = skb_peek_tail(&txq->sk_list); + struct ecrnx_txhdr *txhdr; + struct ecrnx_sw_txhdr *sw_txhdr; + int len1, len2; + + if (!skb_prev || !ecrnx_amsdu_is_aggregable(skb_prev)) + goto end; + + txhdr = (struct ecrnx_txhdr *)skb_prev->data; + sw_txhdr = txhdr->sw_hdr; + if ((sw_txhdr->amsdu.len) || + (sw_txhdr->desc.host.flags & TXU_CNTRL_RETRY)) + /* previous buffer is already a complete amsdu or a retry */ + goto end; + + eth = (struct ethhdr *)(skb_prev->data + sw_txhdr->headroom); + len1 = ecrnx_amsdu_subframe_length(eth, (sw_txhdr->frame_len + + sizeof(struct ethhdr))); + + eth = (struct ethhdr *)(skb->data); + len2 = ecrnx_amsdu_subframe_length(eth, skb->len); + + if (len1 + AMSDU_PADDING(len1) + len2 > txq->amsdu_len) + /* not enough space to aggregate those two buffers */ + goto end; + + /* Add subframe header. + Note: Fw will take care of adding AMDSU header for the first + subframe while generating 802.11 MAC header */ + INIT_LIST_HEAD(&sw_txhdr->amsdu.hdrs); + sw_txhdr->amsdu.len = len1; + sw_txhdr->amsdu.nb = 1; + sw_txhdr->amsdu.pad = AMSDU_PADDING(len1); + if (ecrnx_amsdu_add_subframe_header(ecrnx_hw, skb, sw_txhdr)) + goto end; + + sw_txhdr->desc.host.flags |= TXU_CNTRL_AMSDU; + + if (sw_txhdr->amsdu.nb < ecrnx_hw->mod_params->amsdu_maxnb) + txq->amsdu = sw_txhdr; + else + ecrnx_hw->stats.amsdus[sw_txhdr->amsdu.nb - 1].done++; + } + + res = true; + + end: + spin_unlock_bh(&ecrnx_hw->tx_lock); + return res; +} +/** + * ecrnx_amsdu_dismantle - Dismantle an already formatted A-MSDU + * + * @ecrnx_hw Driver main data + * @sw_txhdr_main Software descriptor of the A-MSDU to dismantle. + * + * The a-mdsu is always fully dismantled (i.e don't try to reduce it's size to + * fit the new limit). + * The DMA mapping can be re-used as ecrnx_amsdu_add_subframe_header ensure that + * enough data in the skb bufer are 'DMA mapped'. + * It would have been slightly simple to unmap/re-map but it is a little faster like this + * and not that much more complicated to read. + */ +static void ecrnx_amsdu_dismantle(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sw_txhdr *sw_txhdr_main) +{ + struct ecrnx_amsdu_txhdr *amsdu_txhdr, *next; + struct sk_buff *skb_prev = sw_txhdr_main->skb; + struct ecrnx_txq *txq = sw_txhdr_main->txq; + trace_amsdu_dismantle(sw_txhdr_main); + ecrnx_hw->stats.amsdus[sw_txhdr_main->amsdu.nb - 1].done--; + sw_txhdr_main->amsdu.len = 0; + sw_txhdr_main->amsdu.nb = 0; + sw_txhdr_main->desc.host.flags &= ~TXU_CNTRL_AMSDU; + sw_txhdr_main->desc.host.packet_cnt = 1; + list_for_each_entry_safe(amsdu_txhdr, next, &sw_txhdr_main->amsdu.hdrs, list) { + struct ecrnx_txhdr *txhdr; + struct ecrnx_sw_txhdr *sw_txhdr; + dma_addr_t dma_addr = amsdu_txhdr->dma_addr; + size_t map_len = amsdu_txhdr->map_len; + size_t tx_map_len; + size_t data_oft, cfm_oft = 0; + struct sk_buff *skb = amsdu_txhdr->skb; + int headroom; + list_del(&amsdu_txhdr->list); + ecrnx_ipc_sta_buffer(ecrnx_hw, txq->sta, txq->tid, -amsdu_txhdr->msdu_len); + ecrnx_amsdu_del_subframe_header(amsdu_txhdr); + headroom = ECRNX_TX_HEADROOM(skb); + tx_map_len = ECRNX_TX_DMA_MAP_LEN(skb); + sw_txhdr = kmem_cache_alloc(ecrnx_hw->sw_txhdr_cache, GFP_ATOMIC); + if (unlikely((skb_headroom(skb) < headroom) || + (sw_txhdr == NULL) || (tx_map_len > map_len))) { + if (sw_txhdr) + kmem_cache_free(ecrnx_hw->sw_txhdr_cache, sw_txhdr); + dma_unmap_single(ecrnx_hw->dev, dma_addr, map_len, DMA_TO_DEVICE); + dev_kfree_skb_any(skb); + continue; + } + sw_txhdr->headroom = headroom; + cfm_oft = map_len - tx_map_len; + data_oft = sizeof(struct ethhdr) + ECRNX_TX_DATA_OFT(sw_txhdr) + cfm_oft; + txhdr = skb_push(skb, headroom); + txhdr->sw_hdr = sw_txhdr; + memcpy(sw_txhdr, sw_txhdr_main, sizeof(*sw_txhdr)); + sw_txhdr->frame_len = map_len - data_oft; + sw_txhdr->skb = skb; + sw_txhdr->headroom = headroom; + txhdr->hw_hdr.cfm.status.value = 0; + sw_txhdr->map_len = map_len; + sw_txhdr->dma_addr = dma_addr; + sw_txhdr->desc.host.packet_addr[0] = dma_addr + data_oft; + sw_txhdr->desc.host.status_desc_addr = dma_addr + cfm_oft; + sw_txhdr->desc.host.packet_len[0] = sw_txhdr->frame_len; + sw_txhdr->desc.host.packet_cnt = 1; + ecrnx_txq_queue_skb(skb, sw_txhdr->txq, ecrnx_hw, false, skb_prev); + skb_prev = skb; + } +} +/** + * ecrnx_amsdu_update_len - Update length allowed for A-MSDU on a TXQ + * + * @ecrnx_hw Driver main data. + * @txq The TXQ. + * @amsdu_len New length allowed ofr A-MSDU. + * + * If this is a TXQ linked to a STA and the allowed A-MSDU size is reduced it is + * then necessary to disassemble all A-MSDU currently queued on all STA' txq that + * are larger than this new limit. + * Does nothing if the A-MSDU limit increase or stay the same. + */ +static void ecrnx_amsdu_update_len(struct ecrnx_hw *ecrnx_hw, struct ecrnx_txq *txq, + u16 amsdu_len) +{ + struct ecrnx_sta *sta = txq->sta; + int tid; + + if (amsdu_len != txq->amsdu_len) + trace_amsdu_len_update(txq->sta, amsdu_len); + + if (amsdu_len >= txq->amsdu_len) { + txq->amsdu_len = amsdu_len; + return; + } + + if (!sta) { + netdev_err(txq->ndev, "Non STA txq(%d) with a-amsdu len %d\n", + txq->idx, amsdu_len); + txq->amsdu_len = 0; + return; + } + + /* A-MSDU size has been reduced by the firmware, need to dismantle all + queued a-msdu that are too large. Need to do this for all txq of the STA. */ + foreach_sta_txq(sta, txq, tid, ecrnx_hw) { + struct sk_buff *skb, *skb_next; + + if (txq->amsdu_len <= amsdu_len) + continue; + + if (txq->last_retry_skb) + skb = txq->last_retry_skb->next; + else + skb = txq->sk_list.next; + + skb_queue_walk_from_safe(&txq->sk_list, skb, skb_next) { + struct ecrnx_txhdr *txhdr = (struct ecrnx_txhdr *)skb->data; + struct ecrnx_sw_txhdr *sw_txhdr = txhdr->sw_hdr; + if ((sw_txhdr->desc.host.flags & TXU_CNTRL_AMSDU) && + (sw_txhdr->amsdu.len > amsdu_len)) + ecrnx_amsdu_dismantle(ecrnx_hw, sw_txhdr); + + if (txq->amsdu == sw_txhdr) + txq->amsdu = NULL; + } + + txq->amsdu_len = amsdu_len; + } +} +#endif /* CONFIG_ECRNX_AMSDUS_TX */ + +/** + * netdev_tx_t (*ndo_start_xmit)(struct sk_buff *skb, + * struct net_device *dev); + * Called when a packet needs to be transmitted. + * Must return NETDEV_TX_OK , NETDEV_TX_BUSY. + * (can also return NETDEV_TX_LOCKED if NETIF_F_LLTX) + * + * - Initialize the desciptor for this pkt (stored in skb before data) + * - Push the pkt in the corresponding Txq + * - If possible (i.e. credit available and not in PS) the pkt is pushed + * to fw + */ +netdev_tx_t ecrnx_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); + struct ecrnx_hw *ecrnx_hw = ecrnx_vif->ecrnx_hw; + struct ecrnx_txhdr *txhdr; + struct ecrnx_sw_txhdr *sw_txhdr = NULL; + struct ethhdr *eth; + struct txdesc_api *desc; + struct ecrnx_sta *sta; + struct ecrnx_txq *txq; + int headroom = 0, hdr_pads = 0; + u16 frame_len; + u64_l skb_addr; + + u8 tid; + + sk_pacing_shift_update(skb->sk, ecrnx_hw->tcp_pacing_shift); + + /* check whether the current skb can be used */ + if (skb_shared(skb) || (skb_headroom(skb) < ECRNX_TX_MAX_HEADROOM) || + (skb_cloned(skb) && (dev->priv_flags & IFF_BRIDGE_PORT))) { + struct sk_buff *newskb = skb_copy_expand(skb, ECRNX_TX_MAX_HEADROOM, 0, GFP_ATOMIC); + if (unlikely(newskb == NULL)) + goto free; + + dev_kfree_skb_any(skb); + + skb = newskb; + } + + /* Get the STA id and TID information */ + sta = ecrnx_get_tx_info(ecrnx_vif, skb, &tid); + if (!sta) + goto free; + + txq = ecrnx_txq_sta_get(sta, tid, ecrnx_hw); + ECRNX_DBG("%s-%d:sta:0x%p,sta_idx:%d, sta_mac:%pM, tid:%d, ecrnx_hw:0x%p, txq:0x%p \n", __func__, __LINE__, sta, sta->sta_idx, sta->mac_addr, tid, ecrnx_hw, txq); + if (txq->idx == TXQ_INACTIVE) + goto free; + +#ifdef CONFIG_ECRNX_AMSDUS_TX + if (ecrnx_amsdu_add_subframe(ecrnx_hw, skb, sta, txq)) + return NETDEV_TX_OK; +#endif + + sw_txhdr = kmem_cache_alloc(ecrnx_hw->sw_txhdr_cache, GFP_ATOMIC); + if (unlikely(sw_txhdr == NULL)) + goto free; + + /* Retrieve the pointer to the Ethernet data */ + eth = (struct ethhdr *)skb->data; + + +#if 0 + if ((skb->data[0] & 0x1) && (skb->data[0] != 0xff)) { + printk("drop mc pkt 0x%x\n", skb->data[0]); + goto free; + } +#endif + +#if 1 + if (0xDD86 == eth->h_proto) { //ipv6 + //printk("%s-%d: eapol\n", __func__, __LINE__); + goto free; + //dump_xxx_buf(skb->data, skb->len); + } +#endif +#if 0 + if (0x8e88 == eth->h_proto) { //icmp + printk("%s-%d: eapol\n", __func__, __LINE__); + //dump_xxx_buf(skb->data, skb->len); + } +#endif + +#if 0 + + if (8 == eth->h_proto && 0x1 == skb->data[23]) { //icmp + memset(skb->data + 14, 0xff, skb->len - 14); + } + if (8 == eth->h_proto && 0x11 == skb->data[23]) { + printk("---drop udp pkt\n"); + goto free; + } +#endif + //no_encrypt = check_eapol_dont_encrypt(skb); + + hdr_pads = ECRNX_SWTXHDR_ALIGN_PADS((long)eth); + /* Use headroom to store struct ecrnx_txhdr */ + headroom = ECRNX_TX_HEADROOM(skb); + + txhdr = (struct ecrnx_txhdr *)skb_push(skb, headroom); + txhdr->sw_hdr = sw_txhdr; + frame_len = (u16)skb->len - headroom - sizeof(*eth); + + sw_txhdr->txq = txq; + sw_txhdr->frame_len = frame_len; + sw_txhdr->ecrnx_sta = sta; + sw_txhdr->ecrnx_vif = ecrnx_vif; + sw_txhdr->skb = skb; + sw_txhdr->headroom = headroom; +#ifdef CONFIG_ECRNX_ESWIN + sw_txhdr->offset = headroom + sizeof(*eth); +#else + sw_txhdr->map_len = skb->len - offsetof(struct ecrnx_txhdr, hw_hdr); +#endif + sw_txhdr->jiffies = jiffies; +#ifdef CONFIG_ECRNX_AMSDUS_TX + sw_txhdr->amsdu.len = 0; + sw_txhdr->amsdu.nb = 0; +#endif + // Fill-in the descriptor + desc = &sw_txhdr->desc; + memcpy(&desc->host.eth_dest_addr, eth->h_dest, ETH_ALEN); + memcpy(&desc->host.eth_src_addr, eth->h_source, ETH_ALEN); + desc->host.ethertype = eth->h_proto; + desc->host.staid = sta->sta_idx; + desc->host.tid = tid; + if (unlikely(ecrnx_vif->wdev.iftype == NL80211_IFTYPE_AP_VLAN)) + desc->host.vif_idx = ecrnx_vif->ap_vlan.master->vif_index; + else + desc->host.vif_idx = ecrnx_vif->vif_index; + desc->host.flags = 0; + + if (ecrnx_vif->use_4addr && (sta->sta_idx < NX_REMOTE_STA_MAX)) + desc->host.flags |= TXU_CNTRL_USE_4ADDR; + + if ((ecrnx_vif->tdls_status == TDLS_LINK_ACTIVE) && + ecrnx_vif->sta.tdls_sta && + (memcmp(desc->host.eth_dest_addr.array, ecrnx_vif->sta.tdls_sta->mac_addr, ETH_ALEN) == 0)) { + desc->host.flags |= TXU_CNTRL_TDLS; + ecrnx_vif->sta.tdls_sta->tdls.last_tid = desc->host.tid; + ecrnx_vif->sta.tdls_sta->tdls.last_sn = desc->host.sn; + } + + if ((ecrnx_vif->wdev.iftype == NL80211_IFTYPE_MESH_POINT) && + (ecrnx_vif->is_resending)) + desc->host.flags |= TXU_CNTRL_MESH_FWD; + +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + desc->host.packet_len[0] = frame_len; +#else + desc->host.packet_len = frame_len; +#endif + + txhdr->hw_hdr.cfm.status.value = 0; + +#ifdef CONFIG_ECRNX_ESWIN + skb_addr = (ptr_addr)skb; +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + desc->host.packet_addr[0] = (u64_l)skb; + desc->host.packet_cnt = 1; +#else + //desc->host.packet_addr = (u64_l)skb; + desc->host.packet_addr[0] = (u32_l)skb_addr; + desc->host.packet_addr[1] = (u32_l)(skb_addr >> 32); +#endif + //desc->host.status_desc_addr = (u64_l)skb; + desc->host.status_desc_addr[0] = (u32_l)skb_addr; + desc->host.status_desc_addr[1] = (u32_l)(skb_addr >> 32); + + /*if (no_encrypt) { + desc->host.flags |= TXU_CNTRL_NO_ENCRYPT; + }*/ +#else //CONFIG_ECRNX_ESWIN_SDIO + if (unlikely(ecrnx_prep_dma_tx(ecrnx_hw, txhdr, true))) + goto free; +#endif //CONFIG_ECRNX_ESWIN_SDIO + //ECRNX_DBG("%s:desc:0x%08x, vif_idx:%d, skb:0x%08x, headroom:%d !!! \n", __func__, desc, desc->host.vif_idx, skb, headroom); + spin_lock_bh(&ecrnx_hw->tx_lock); + if (ecrnx_txq_queue_skb(skb, txq, ecrnx_hw, false, NULL)) + { + ECRNX_DBG("%s-%d:txdesc:0x%x, skb:0x%08x, skb->len:%d \n", __func__, __LINE__, desc, skb, skb->len); + ecrnx_hwq_process(ecrnx_hw, txq->hwq); + } + else + { + ECRNX_DBG("%s-%d: delay send(put txq), txq:0x%p, queue status 0x%x, skb:0x%08x, skb->len:%d !!! \n", __func__, __LINE__, txq, txq->status, skb, skb->len); + } + spin_unlock_bh(&ecrnx_hw->tx_lock); + + return NETDEV_TX_OK; + +free: + if (sw_txhdr) + kmem_cache_free(ecrnx_hw->sw_txhdr_cache, sw_txhdr); + if (headroom) + skb_pull(skb, headroom); + dev_kfree_skb_any(skb); + + return NETDEV_TX_OK; +} + +/** + * ecrnx_start_mgmt_xmit - Transmit a management frame + * + * @vif: Vif that send the frame + * @sta: Destination of the frame. May be NULL if the destiantion is unknown + * to the AP. + * @params: Mgmt frame parameters + * @offchan: Indicate whether the frame must be send via the offchan TXQ. + * (is is redundant with params->offchan ?) + * @cookie: updated with a unique value to identify the frame with upper layer + * + */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) +int ecrnx_start_mgmt_xmit(struct ecrnx_vif *vif, struct ecrnx_sta *sta, + struct cfg80211_mgmt_tx_params *params, bool offchan, + u64 *cookie) +#else +int ecrnx_start_mgmt_xmit(struct ecrnx_vif *vif, struct ecrnx_sta *sta, + struct ieee80211_channel *channel, bool offchan, + unsigned int wait, const u8* buf, size_t len, + #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0)) + bool no_cck, + #endif + #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 3, 0)) + bool dont_wait_for_ack, + #endif + u64 *cookie) +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) */ +{ + struct ecrnx_hw *ecrnx_hw = vif->ecrnx_hw; + struct ecrnx_txhdr *txhdr; + struct ecrnx_sw_txhdr *sw_txhdr; + struct txdesc_api *desc; + struct sk_buff *skb; + u16 frame_len, headroom; + u8 *data; + struct ecrnx_txq *txq; + bool robust; + u64_l skb_addr; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) + const u8 *buf = params->buf; + size_t len = params->len; + bool no_cck = params->no_cck; +#endif + + +#ifdef CONFIG_ECRNX_ESWIN + headroom = sizeof(struct ecrnx_txhdr) + ECRNX_TX_TXDESC_API_ALIGN; +#else + headroom = sizeof(struct ecrnx_txhdr); +#endif + frame_len = len; + + //---------------------------------------------------------------------- + + /* Set TID and Queues indexes */ + if (sta) { + txq = ecrnx_txq_sta_get(sta, 8, ecrnx_hw); + } else { + if (offchan) + txq = &ecrnx_hw->txq[NX_OFF_CHAN_TXQ_IDX]; + else + txq = ecrnx_txq_vif_get(vif, NX_UNK_TXQ_TYPE); + } + + /* Ensure that TXQ is active */ + if (txq->idx == TXQ_INACTIVE) { + if(sta){ + netdev_dbg(vif->ndev, "TXQ inactive\n"); + return -EBUSY; + }else{ + return 0; + } + } + + /* + * Create a SK Buff object that will contain the provided data + */ + skb = dev_alloc_skb(headroom + frame_len); + + if (!skb) { + return -ENOMEM; + } + + *cookie = (unsigned long)skb; + + sw_txhdr = kmem_cache_alloc(ecrnx_hw->sw_txhdr_cache, GFP_ATOMIC); + if (unlikely(sw_txhdr == NULL)) { + dev_kfree_skb(skb); + return -ENOMEM; + } + /* + * Move skb->data pointer in order to reserve room for ecrnx_txhdr + * headroom value will be equal to sizeof(struct ecrnx_txhdr) + */ + skb_reserve(skb, headroom); + + /* + * Extend the buffer data area in order to contain the provided packet + * len value (for skb) will be equal to param->len + */ + data = skb_put(skb, frame_len); + /* Copy the provided data */ + memcpy(data, buf, frame_len); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0)) + robust = ieee80211_is_robust_mgmt_frame(skb); +#else + if (skb->len < 25){ + robust = false; + } + robust = ieee80211_is_robust_mgmt_frame((void *)skb->data); +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0) */ + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)) + /* Update CSA counter if present */ + if (unlikely(params->n_csa_offsets) && + vif->wdev.iftype == NL80211_IFTYPE_AP && + vif->ap.csa) { + int i; + + data = skb->data; + for (i = 0; i < params->n_csa_offsets ; i++) { + data[params->csa_offsets[i]] = vif->ap.csa->count; + } + } +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) */ + + /* + * Go back to the beginning of the allocated data area + * skb->data pointer will move backward + */ + txhdr = (struct ecrnx_txhdr *)skb_push(skb, headroom); + + //---------------------------------------------------------------------- + + /* Fill the TX Header */ + + + //---------------------------------------------------------------------- + + /* Fill the SW TX Header */ + txhdr->sw_hdr = sw_txhdr; + + sw_txhdr->txq = txq; + sw_txhdr->frame_len = frame_len; + sw_txhdr->ecrnx_sta = sta; + sw_txhdr->ecrnx_vif = vif; + sw_txhdr->skb = skb; + sw_txhdr->headroom = headroom; +#ifdef CONFIG_ECRNX_ESWIN + sw_txhdr->offset = headroom; //sizeof(struct ecrnx_txhdr) + sizeof(struct txdesc_api); +#else + sw_txhdr->map_len = skb->len - offsetof(struct ecrnx_txhdr, hw_hdr); +#endif + sw_txhdr->jiffies = jiffies; +#ifdef CONFIG_ECRNX_AMSDUS_TX + sw_txhdr->amsdu.len = 0; + sw_txhdr->amsdu.nb = 0; +#endif + //---------------------------------------------------------------------- + + /* Fill the Descriptor to be provided to the MAC SW */ + desc = &sw_txhdr->desc; + + desc->host.staid = (sta) ? sta->sta_idx : 0xFF; + desc->host.vif_idx = vif->vif_index; + desc->host.tid = 0xFF; + desc->host.flags = TXU_CNTRL_MGMT; + if (robust) + desc->host.flags |= TXU_CNTRL_MGMT_ROBUST; + + + if (no_cck) { + desc->host.flags |= TXU_CNTRL_MGMT_NO_CCK; + } + /* baoyong */ +#ifdef CONFIG_RWNX_SPLIT_TX_BUF + desc->host.packet_len[0] = frame_len; +#else + desc->host.packet_len = frame_len; +#endif + + txhdr->hw_hdr.cfm.status.value = 0; +#ifdef CONFIG_ECRNX_ESWIN + skb_addr = (ptr_addr)skb; +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + desc->host.packet_addr[0] = skb; + desc->host.packet_cnt = 1; +#else + //desc->host.packet_addr = (u64_l)skb; + desc->host.packet_addr[0] = (u32_l)skb_addr; + desc->host.packet_addr[1] = (u32_l)(skb_addr >> 32); +#endif + //desc->host.status_desc_addr = (u64_l)skb; + desc->host.packet_addr[0] = (u32_l)skb_addr; + desc->host.packet_addr[1] = (u32_l)(skb_addr >> 32); +#else //CONFIG_ECRNX_ESWIN_SDIO + + /* Get DMA Address */ + if (unlikely(ecrnx_prep_dma_tx(ecrnx_hw, txhdr, false))) { + kmem_cache_free(ecrnx_hw->sw_txhdr_cache, sw_txhdr); + dev_kfree_skb(skb); + return -EBUSY; + } +#endif +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF +#endif + + //---------------------------------------------------------------------- + + spin_lock_bh(&ecrnx_hw->tx_lock); + if (ecrnx_txq_queue_skb(skb, txq, ecrnx_hw, false, NULL)) { + ECRNX_DBG("%s-%d:txdesc:0x%x, skb:0x%08x, skb->len:%d \n", __func__, __LINE__, desc, skb, skb->len); + ecrnx_hwq_process(ecrnx_hw, txq->hwq); + } else { + ECRNX_DBG("%s-%d: delay send(put txq), queue status 0x%x, skb:0x%08x, skb->len:%d !!! \n", __func__, __LINE__, txq->status, skb, skb->len); + } + spin_unlock_bh(&ecrnx_hw->tx_lock); + + return 0; +} + +int ecrnx_handle_tx_datacfm(void *priv, void *host_id) +{ + struct ecrnx_hw *ecrnx_hw = (struct ecrnx_hw *)priv; + struct sk_buff *skb = host_id; + struct ecrnx_hwq *hwq; + struct ecrnx_txq *txq; + struct ecrnx_sta *sta; + struct ecrnx_txhdr *txhdr; + +#if defined(CONFIG_ECRNX_ESWIN_USB) + txhdr = (struct ecrnx_txhdr *)(*((ptr_addr*)skb->data - 1)); +#else + txhdr = (struct ecrnx_txhdr *)skb->data; +#endif + + struct ecrnx_sw_txhdr *sw_txhdr = txhdr->sw_hdr; + + /* Check status in the header. If status is null, it means that the buffer + * was not transmitted and we have to return immediately */ + ECRNX_DBG("%s:hostid(tx_skb):0x%08x\n", __func__, skb); + + txq = sw_txhdr->txq; + /* don't use txq->hwq as it may have changed between push and confirm */ + hwq = &ecrnx_hw->hwq[sw_txhdr->hw_queue]; + ecrnx_txq_confirm_any(ecrnx_hw, txq, hwq, sw_txhdr); + + if (txq->idx != TXQ_INACTIVE) { + + txq->credits += 1; + //printk("finish_cfm: txq->credits %d 0x%08x\n", txq->credits,skb); + if (txq->credits <= 0){ + ecrnx_txq_stop(txq, ECRNX_TXQ_STOP_FULL); + } + else if (txq->credits > 0) + { + ecrnx_txq_start(txq, ECRNX_TXQ_STOP_FULL); + /* baoyong:handle the pkts in sk_list right now */ + if (txq->idx != TXQ_INACTIVE && !skb_queue_empty(&txq->sk_list)) + { + ecrnx_hwq_process(ecrnx_hw, txq->hwq); + } + } + + /* continue service period */ + if (unlikely(txq->push_limit && !ecrnx_txq_is_full(txq))) { + ecrnx_txq_add_to_hw_list(txq); + } + } + + /* Update statistics */ + sw_txhdr->ecrnx_vif->net_stats.tx_packets++; + sw_txhdr->ecrnx_vif->net_stats.tx_bytes += sw_txhdr->frame_len; + + sta = txq->sta; + if(sta) + { + sta = txq->sta; + sta->stats.tx_pkts ++; + sta->stats.tx_bytes += sw_txhdr->frame_len; + sta->stats.last_act = ecrnx_hw->stats.last_tx; + } + //printk("sta->stats.tx_pkts=%d sta->stats.tx_bytes =%d\n", sta->stats.tx_pkts, sta->stats.tx_bytes); + + kmem_cache_free(ecrnx_hw->sw_txhdr_cache, sw_txhdr); + skb_pull(skb, sw_txhdr->headroom); + consume_skb(skb); + return 0; +} + +/** + * ecrnx_txdatacfm - FW callback for TX confirmation + * + * called with tx_lock hold + */ +int ecrnx_txdatacfm(void *pthis, void *host_id) +{ + struct ecrnx_hw *ecrnx_hw = (struct ecrnx_hw *)pthis; + struct sk_buff *skb = host_id; + struct ecrnx_txhdr *txhdr; + union ecrnx_hw_txstatus ecrnx_txst; + struct ecrnx_sw_txhdr *sw_txhdr; + struct ecrnx_hwq *hwq; + struct ecrnx_txq *txq; +#ifndef CONFIG_ECRNX_ESWIN + dma_addr_t cfm_dma_addr; +#endif + size_t cfm_len; + +#if defined(CONFIG_ECRNX_ESWIN_USB) + txhdr = (struct ecrnx_txhdr *)host_id; + skb = txhdr->sw_hdr->skb; + skb_push(skb, sizeof(struct ecrnx_txhdr) - sizeof(u32_l)); +#else + txhdr = (struct ecrnx_txhdr *)skb->data; +#endif + sw_txhdr = txhdr->sw_hdr; + cfm_len = sizeof(txhdr->hw_hdr.cfm); + + //ECRNX_DBG("%s-%d: skb:0x%08x, skb->len:%d \n", __func__, __LINE__, skb, skb->len); +#ifndef CONFIG_ECRNX_ESWIN + cfm_dma_addr = (ptr_addr)sw_txhdr->desc.host.status_desc_addr; + dma_sync_single_for_cpu(ecrnx_hw->dev, cfm_dma_addr, cfm_len, DMA_FROM_DEVICE); +#endif + /* Read status in the TX control header */ + ecrnx_txst = txhdr->hw_hdr.cfm.status; + + /* Check status in the header. If status is null, it means that the buffer + * was not transmitted and we have to return immediately */ + if (ecrnx_txst.value == 0) { +#ifndef CONFIG_ECRNX_ESWIN + dma_sync_single_for_device(ecrnx_hw->dev, cfm_dma_addr, cfm_len, DMA_FROM_DEVICE); +#endif + return -1; + } + + txq = sw_txhdr->txq; + /* don't use txq->hwq as it may have changed between push and confirm */ + hwq = &ecrnx_hw->hwq[sw_txhdr->hw_queue]; + ecrnx_txq_confirm_any(ecrnx_hw, txq, hwq, sw_txhdr); + + /* Update txq and HW queue credits */ + if (sw_txhdr->desc.host.flags & TXU_CNTRL_MGMT) { + trace_mgmt_cfm(sw_txhdr->ecrnx_vif->vif_index, + (sw_txhdr->ecrnx_sta) ? sw_txhdr->ecrnx_sta->sta_idx : 0xFF, + ecrnx_txst.acknowledged); + + /* Confirm transmission to CFG80211 */ + cfg80211_mgmt_tx_status(&sw_txhdr->ecrnx_vif->wdev, + (unsigned long)skb, + (skb->data + sw_txhdr->headroom), + sw_txhdr->frame_len, + ecrnx_txst.acknowledged, + GFP_ATOMIC); + } else if ((txq->idx != TXQ_INACTIVE) && + (ecrnx_txst.retry_required || ecrnx_txst.sw_retry_required)) { + bool sw_retry = (ecrnx_txst.sw_retry_required) ? true : false; + + /* Reset the status */ + txhdr->hw_hdr.cfm.status.value = 0; + + /* The confirmed packet was part of an AMPDU and not acked + * correctly, so reinject it in the TX path to be retried */ + ecrnx_tx_retry(ecrnx_hw, skb, txhdr, sw_retry); + return 0; + } + + trace_skb_confirm(skb, txq, hwq, &txhdr->hw_hdr.cfm); + + /* STA may have disconnect (and txq stopped) when buffers were stored + in fw. In this case do nothing when they're returned */ + if (txq->idx != TXQ_INACTIVE) { + if (txhdr->hw_hdr.cfm.credits) { + txq->credits += txhdr->hw_hdr.cfm.credits; + if (txq->credits <= 0) + ecrnx_txq_stop(txq, ECRNX_TXQ_STOP_FULL); + else if (txq->credits > 0) + { + ecrnx_txq_start(txq, ECRNX_TXQ_STOP_FULL); + /* baoyong:handle the pkts in sk_list right now */ + if (txq->idx != TXQ_INACTIVE && !skb_queue_empty(&txq->sk_list)) + { + ecrnx_hwq_process(ecrnx_hw, txq->hwq); + } + + } + } + + /* continue service period */ + if (unlikely(txq->push_limit && !ecrnx_txq_is_full(txq))) { + ecrnx_txq_add_to_hw_list(txq); + } + } + + if (txhdr->hw_hdr.cfm.ampdu_size && + txhdr->hw_hdr.cfm.ampdu_size < IEEE80211_MAX_AMPDU_BUF) + ecrnx_hw->stats.ampdus_tx[txhdr->hw_hdr.cfm.ampdu_size - 1]++; + +#ifdef CONFIG_ECRNX_AMSDUS_TX + ecrnx_amsdu_update_len(ecrnx_hw, txq, txhdr->hw_hdr.cfm.amsdu_size); +#endif + + /* Update statistics */ + sw_txhdr->ecrnx_vif->net_stats.tx_packets++; + sw_txhdr->ecrnx_vif->net_stats.tx_bytes += sw_txhdr->frame_len; + + /* Release SKBs */ +#ifdef CONFIG_ECRNX_AMSDUS_TX + if (sw_txhdr->desc.host.flags & TXU_CNTRL_AMSDU) { + struct ecrnx_amsdu_txhdr *amsdu_txhdr; + list_for_each_entry(amsdu_txhdr, &sw_txhdr->amsdu.hdrs, list) { + ecrnx_amsdu_del_subframe_header(amsdu_txhdr); +#ifndef CONFIG_ECRNX_ESWIN + dma_unmap_single(ecrnx_hw->dev, amsdu_txhdr->dma_addr, + amsdu_txhdr->map_len, DMA_TO_DEVICE); +#endif + ecrnx_ipc_sta_buffer(ecrnx_hw, txq->sta, txq->tid, + -amsdu_txhdr->msdu_len); + ecrnx_tx_statistic(ecrnx_hw, txq, ecrnx_txst, amsdu_txhdr->msdu_len); + consume_skb(amsdu_txhdr->skb); + } + } +#endif /* CONFIG_ECRNX_AMSDUS_TX */ + +#ifndef CONFIG_ECRNX_ESWIN + /* unmap with the least costly DMA_TO_DEVICE since we don't need to inval */ + dma_unmap_single(ecrnx_hw->dev, sw_txhdr->dma_addr, sw_txhdr->map_len, + DMA_TO_DEVICE); +#endif + ecrnx_ipc_sta_buffer(ecrnx_hw, txq->sta, txq->tid, -sw_txhdr->frame_len); + ecrnx_tx_statistic(ecrnx_hw, txq, ecrnx_txst, sw_txhdr->frame_len); + + kmem_cache_free(ecrnx_hw->sw_txhdr_cache, sw_txhdr); + skb_pull(skb, sw_txhdr->headroom); + consume_skb(skb); + + return 0; +} + +/** + * ecrnx_txq_credit_update - Update credit for one txq + * + * @ecrnx_hw: Driver main data + * @sta_idx: STA idx + * @tid: TID + * @update: offset to apply in txq credits + * + * Called when fw send ME_TX_CREDITS_UPDATE_IND message. + * Apply @update to txq credits, and stop/start the txq if needed + */ +void ecrnx_txq_credit_update(struct ecrnx_hw *ecrnx_hw, int sta_idx, u8 tid, s8 update) +{ +#ifndef CONFIG_ECRNX_ESWIN + struct ecrnx_sta *sta = &ecrnx_hw->sta_table[sta_idx]; + struct ecrnx_txq *txq; + + txq = ecrnx_txq_sta_get(sta, tid, ecrnx_hw); + + spin_lock_bh(&ecrnx_hw->tx_lock); + + if (txq->idx != TXQ_INACTIVE) { + txq->credits += update; + trace_credit_update(txq, update); + if (txq->credits <= 0){ + ECRNX_DBG("%s-%d:ecrnx_txq_stop,reaosn:0x%x \n", __func__, __LINE__, ECRNX_TXQ_STOP_FULL); + ecrnx_txq_stop(txq, ECRNX_TXQ_STOP_FULL); + } + else{ + ecrnx_txq_start(txq, ECRNX_TXQ_STOP_FULL); + ECRNX_DBG("%s-%d:ecrnx_txq_start,reaosn:0x%x \n", __func__, __LINE__, ECRNX_TXQ_STOP_FULL); + } + } + +// Drop all the retry packets of a BA that was deleted + if (update < NX_TXQ_INITIAL_CREDITS) { + int packet; + + for (packet = 0; packet < txq->nb_retry; packet++) { + ecrnx_txq_drop_skb(txq, skb_peek(&txq->sk_list), ecrnx_hw, true); + } + } + + spin_unlock_bh(&ecrnx_hw->tx_lock); +#endif +} + + +#ifdef CONFIG_ECRNX_ESWIN_SDIO +void ecrnx_tx_retry_sdio(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb, + struct ecrnx_txhdr *txhdr, bool sw_retry) +{ + ecrnx_tx_retry(ecrnx_hw, skb, txhdr, sw_retry); +} + + +#ifdef CONFIG_ECRNX_AMSDUS_TX +void ecrnx_amsdu_del_subframe_header_sdio(struct ecrnx_amsdu_txhdr *amsdu_txhdr) +{ + ecrnx_amsdu_del_subframe_header(amsdu_txhdr); +} +#endif +#endif + +#ifdef CONFIG_ECRNX_ESWIN_USB +void ecrnx_tx_retry_usb(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb, + struct ecrnx_txhdr *txhdr, bool sw_retry) +{ + ecrnx_tx_retry(ecrnx_hw, skb, txhdr, sw_retry); +} + + +#ifdef CONFIG_ECRNX_AMSDUS_TX +void ecrnx_amsdu_del_subframe_header_sdio(struct ecrnx_amsdu_txhdr *amsdu_txhdr) +{ + ecrnx_amsdu_del_subframe_header(amsdu_txhdr); +} +#endif +#endif + diff --git a/drivers/net/wireless/eswin/fullmac/ecrnx_tx.h b/drivers/net/wireless/eswin/fullmac/ecrnx_tx.h new file mode 100644 index 000000000000..03d1177114bb --- /dev/null +++ b/drivers/net/wireless/eswin/fullmac/ecrnx_tx.h @@ -0,0 +1,285 @@ +/** + ****************************************************************************** + * + * @file ecrnx_tx.h + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ +#ifndef _ECRNX_TX_H_ +#define _ECRNX_TX_H_ + +#include <linux/ieee80211.h> +#include <net/cfg80211.h> +#include <linux/netdevice.h> +#include "lmac_types.h" +#include "ipc_shared.h" +#include "ecrnx_txq.h" +#include "hal_desc.h" + +#define ECRNX_HWQ_BK 0 +#define ECRNX_HWQ_BE 1 +#define ECRNX_HWQ_VI 2 +#define ECRNX_HWQ_VO 3 +#define ECRNX_HWQ_BCMC 4 +#define ECRNX_HWQ_NB NX_TXQ_CNT +#define ECRNX_HWQ_ALL_ACS (ECRNX_HWQ_BK | ECRNX_HWQ_BE | ECRNX_HWQ_VI | ECRNX_HWQ_VO) +#define ECRNX_HWQ_ALL_ACS_BIT ( BIT(ECRNX_HWQ_BK) | BIT(ECRNX_HWQ_BE) | \ + BIT(ECRNX_HWQ_VI) | BIT(ECRNX_HWQ_VO) ) + +#define ECRNX_TX_LIFETIME_MS 100 +#define ECRNX_TX_MAX_RATES NX_TX_MAX_RATES + + +#define AMSDU_PADDING(x) ((4 - ((x) & 0x3)) & 0x3) + +#define TXU_CNTRL_RETRY BIT(0) +#define TXU_CNTRL_MORE_DATA BIT(2) +#define TXU_CNTRL_MGMT BIT(3) +#define TXU_CNTRL_MGMT_NO_CCK BIT(4) +#define TXU_CNTRL_AMSDU BIT(6) +#define TXU_CNTRL_MGMT_ROBUST BIT(7) +#define TXU_CNTRL_USE_4ADDR BIT(8) +#define TXU_CNTRL_EOSP BIT(9) +#define TXU_CNTRL_MESH_FWD BIT(10) +#define TXU_CNTRL_TDLS BIT(11) +#define TXU_CNTRL_NO_ENCRYPT BIT(15) + +extern const int ecrnx_tid2hwq[IEEE80211_NUM_TIDS]; + +/** + * struct ecrnx_amsdu_txhdr - Structure added in skb headroom (instead of + * ecrnx_txhdr) for amsdu subframe buffer (except for the first subframe + * that has a normal ecrnx_txhdr) + * + * @list List of other amsdu subframe (ecrnx_sw_txhdr.amsdu.hdrs) + * @map_len Length to be downloaded for this subframe + * @dma_addr Buffer address form embedded point of view + * @skb skb + * @pad padding added before this subframe + * (only use when amsdu must be dismantled) + * @msdu_len Size, in bytes, of the MSDU (without padding nor amsdu header) + */ +struct ecrnx_amsdu_txhdr { + struct list_head list; + size_t map_len; +#ifdef CONFIG_ECRNX_ESWIN + u8 *send_pos; // offset from skb->data for send to slave. +#else + dma_addr_t dma_addr; +#endif + struct sk_buff *skb; + u16 pad; + u16 msdu_len; +}; + +/** + * struct ecrnx_amsdu - Structure to manage creation of an A-MSDU, updated + * only In the first subframe of an A-MSDU + * + * @hdrs List of subframe of ecrnx_amsdu_txhdr + * @len Current size for this A-MDSU (doesn't take padding into account) + * 0 means that no amsdu is in progress + * @nb Number of subframe in the amsdu + * @pad Padding to add before adding a new subframe + */ +struct ecrnx_amsdu { + struct list_head hdrs; + u16 len; + u8 nb; + u8 pad; +}; + +/** + * struct ecrnx_sw_txhdr - Software part of tx header + * + * @ecrnx_sta sta to which this buffer is addressed + * @ecrnx_vif vif that send the buffer + * @txq pointer to TXQ used to send the buffer + * @hw_queue Index of the HWQ used to push the buffer. + * May be different than txq->hwq->id on confirmation. + * @frame_len Size of the frame (doesn't not include mac header) + * (Only used to update stat, can't we use skb->len instead ?) + * @headroom Headroom added in skb to add ecrnx_txhdr + * (Only used to remove it before freeing skb, is it needed ?) + * @amsdu Description of amsdu whose first subframe is this buffer + * (amsdu.nb = 0 means this buffer is not part of amsdu) + * @skb skb received from transmission + * @map_len Length mapped for DMA (only ecrnx_hw_txhdr and data are mapped) + * @dma_addr DMA address after mapping + * @desc Buffer description that will be copied in shared mem for FW + */ +struct ecrnx_sw_txhdr { + struct ecrnx_sta *ecrnx_sta; + struct ecrnx_vif *ecrnx_vif; + struct ecrnx_txq *txq; + u8 hw_queue; + u16 frame_len; + u16 headroom; +#ifdef CONFIG_ECRNX_AMSDUS_TX + struct ecrnx_amsdu amsdu; +#endif + struct sk_buff *skb; + +#ifdef CONFIG_ECRNX_ESWIN + u32 offset; // offset from skb->data for send to slave. +#else + size_t map_len; + dma_addr_t dma_addr; +#endif + struct txdesc_api desc; + unsigned long jiffies; +}; + +/** + * struct ecrnx_txhdr - Stucture to control transimission of packet + * (Added in skb headroom) + * + * @sw_hdr: Information from driver + * @cache_guard: + * @hw_hdr: Information for/from hardware + */ +struct ecrnx_txhdr { + struct ecrnx_sw_txhdr *sw_hdr; + char cache_guard[L1_CACHE_BYTES]; + struct ecrnx_hw_txhdr hw_hdr; +}; +#define ECRNX_TX_ALIGN_SIZE 4 +#define ECRNX_TX_ALIGN_MASK (ECRNX_TX_ALIGN_SIZE - 1) +#define ECRNX_SWTXHDR_ALIGN_PADS(x) \ + ((ECRNX_TX_ALIGN_SIZE - ((x) & ECRNX_TX_ALIGN_MASK)) \ + & ECRNX_TX_ALIGN_MASK) + +#define ECRNX_TX_TXDESC_API_ALIGN ((sizeof(struct txdesc_api) + 3) & (~ECRNX_TX_ALIGN_MASK)) +/** + * ECRNX_TX_MAX_HEADROOM - Maximum size needed in skb headroom to prepare a buffer + * for transmission + * The headroom is used to store the 'struct ecrnx_txhdr' and moreover the part that is used + * by the firmware to provide tx status (i.e. struct ecrnx_hw_txhdr) must be aligned on + * 32bits because firmware used DMA to update it. + */ +#ifdef CONFIG_ECRNX_ESWIN +#define ECRNX_TX_MAX_HEADROOM (ECRNX_TX_TXDESC_API_ALIGN + sizeof(struct ecrnx_txhdr) + ECRNX_TX_ALIGN_SIZE) +#else +#define ECRNX_TX_MAX_HEADROOM (sizeof(struct ecrnx_txhdr) + ECRNX_TX_ALIGN_SIZE) +#endif + +/** + * ECRNX_TX_HEADROOM - Headroom to use to store struct ecrnx_txhdr + * + * Takes into account current aligment of data buffer to ensure that struct ecrnx_txhdr + * (and as a consequence its field hw_hdr) will be aligned on 32bits boundary. + */ +#ifdef CONFIG_ECRNX_ESWIN +#define ECRNX_TX_HEADROOM(skb) (ECRNX_TX_TXDESC_API_ALIGN + sizeof(struct ecrnx_txhdr) + ((long)skb->data & ECRNX_TX_ALIGN_MASK)) +#else +#define ECRNX_TX_HEADROOM(skb) (sizeof(struct ecrnx_txhdr) + ((long)skb->data & ECRNX_TX_ALIGN_MASK)) +#endif + +/** + * ECRNX_TX_DMA_MAP_LEN - Length, in bytes, to map for DMA transfer + * To be called with skb BEFORE reserving headroom to store struct ecrnx_txhdr. + */ +#define ECRNX_TX_DMA_MAP_LEN(skb) ( \ + (sizeof(struct ecrnx_txhdr) - offsetof(struct ecrnx_txhdr, hw_hdr)) + \ + ((long)skb->data & ECRNX_TX_ALIGN_MASK) + skb->len) + +/** + * ECRNX_TX_DATA_OFT - Offset, in bytes, between the location of the 'struct ecrnx_hw_txhdr' + * and the beginning of frame (i.e. ethernet of 802.11 header). Cannot simply use + * sizeof(struct ecrnx_hw_txhdr) because of padding added by compiler to align fields + * in structure. + */ +#define ECRNX_TX_DATA_OFT(sw_txhdr) ( \ + (sizeof(struct ecrnx_txhdr) - offsetof(struct ecrnx_txhdr, hw_hdr)) \ + + (sw_txhdr->headroom & ECRNX_TX_ALIGN_MASK)) + +/** + * SKB buffer format before it is pushed to MACSW + * + * For DATA frame + * |--------------------| + * | headroom | + * skb->data ----> |--------------------| <------ skb->data + * | struct ecrnx_txhdr | + * | * ecrnx_sw_txhdr * | + * | * [L1 guard] | + * +--> | * ecrnx_hw_txhdr | <---- desc.host.status_desc_addr + * : | | + * memory : |--------------------| + * mapped : | padding (optional) | + * for DMA : |--------------------| + * : | Ethernet Header | + * : |--------------------| <---- desc.host.packet_addr[0] + * : | Data | + * : | | + * : | | + * : | | + * +--> |--------------------| + * | tailroom | + * |--------------------| + * + * + * For MGMT frame (skb is created by the driver so buffer is always aligned + * with no headroom/tailroom) + * + * skb->data ----> |--------------------| <------ skb->data + * | struct ecrnx_txhdr | + * | * ecrnx_sw_txhdr * | + * | * [L1 guard] | + * +--> | * ecrnx_hw_txhdr | <---- desc.host.status_desc_addr + * memory : | | + * mapped : |--------------------| <---- desc.host.packet_addr[0] + * for DMA : | 802.11 HDR | + * : |--------------------| + * : | Data | + * : | | + * +--> |--------------------| + * + */ + +u16 ecrnx_select_txq(struct ecrnx_vif *ecrnx_vif, struct sk_buff *skb); +int ecrnx_start_xmit(struct sk_buff *skb, struct net_device *dev); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) +int ecrnx_start_mgmt_xmit(struct ecrnx_vif *vif, struct ecrnx_sta *sta, + struct cfg80211_mgmt_tx_params *params, bool offchan, + u64 *cookie); +#else +int ecrnx_start_mgmt_xmit(struct ecrnx_vif *vif, struct ecrnx_sta *sta, + struct ieee80211_channel *channel, bool offchan, + unsigned int wait, const u8* buf, size_t len, + #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0)) + bool no_cck, + #endif + #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 3, 0)) + bool dont_wait_for_ack, + #endif + u64 *cookie); +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) */ + +int ecrnx_txdatacfm(void *pthis, void *host_id); +int ecrnx_handle_tx_datacfm(void *priv, void *host_id); + +struct ecrnx_hw; +struct ecrnx_sta; +void ecrnx_set_traffic_status(struct ecrnx_hw *ecrnx_hw, + struct ecrnx_sta *sta, + bool available, + u8 ps_id); +void ecrnx_ps_bh_enable(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta, + bool enable); +void ecrnx_ps_bh_traffic_req(struct ecrnx_hw *ecrnx_hw, struct ecrnx_sta *sta, + u16 pkt_req, u8 ps_id); + +void ecrnx_switch_vif_sta_txq(struct ecrnx_sta *sta, struct ecrnx_vif *old_vif, + struct ecrnx_vif *new_vif); + +int ecrnx_dbgfs_print_sta(char *buf, size_t size, struct ecrnx_sta *sta, + struct ecrnx_hw *ecrnx_hw); +void ecrnx_txq_credit_update(struct ecrnx_hw *ecrnx_hw, int sta_idx, u8 tid, + s8 update); +void ecrnx_tx_push(struct ecrnx_hw *ecrnx_hw, struct ecrnx_txhdr *txhdr, int flags); + +#endif /* _ECRNX_TX_H_ */ diff --git a/drivers/net/wireless/eswin/fw_head_check.c b/drivers/net/wireless/eswin/fw_head_check.c new file mode 100644 index 000000000000..739b0a121f5a --- /dev/null +++ b/drivers/net/wireless/eswin/fw_head_check.c @@ -0,0 +1,258 @@ +/** +****************************************************************************** +* +* @file fw_head_check.c +* +* @brief ecrnx usb firmware validity check functions +* +* Copyright (C) ESWIN 2015-2020 +* +****************************************************************************** +*/ + +#include <linux/firmware.h> +#include <linux/version.h> +#include "core.h" +#include "fw_head_check.h" +#include "ecrnx_defs.h" + +extern char *fw_name; + +#define MSB_MODE 1 +#define LSB_MODE 0 +#define DSE_FIRST (2039) +#define SECONDS_PER_DAY (86400) + +bin_head_data head = {0}; +unsigned int offset = 0; + +static const crc32_Table_TypeDef CRC32_Table[]={ + {0xFFFFFFFF, 0x04C11DB7, 0xFFFFFFFF, LSB_MODE, "CRC32"}, + {0xFFFFFFFF, 0x04C11DB7, 0x00000000, MSB_MODE, "CRC32_MPEG-2"} +}; + +static const u_int16_t days_since_year[] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, +}; + +static const u_int16_t days_since_leapyear[] = { + 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, +}; + +static const u_int16_t days_since_epoch[] = { + /* 2039 - 2030 */ + 25202, 24837, 24472, 24106, 23741, 23376, 23011, 22645, 22280, 21915, + /* 2029 - 2020 */ + 21550, 21184, 20819, 20454, 20089, 19723, 19358, 18993, 18628, 18262, + /* 2019 - 2010 */ + 17897, 17532, 17167, 16801, 16436, 16071, 15706, 15340, 14975, 14610, + /* 2009 - 2000 */ + 14245, 13879, 13514, 13149, 12784, 12418, 12053, 11688, 11323, 10957, + /* 1999 - 1990 */ + 10592, 10227, 9862, 9496, 9131, 8766, 8401, 8035, 7670, 7305, + /* 1989 - 1980 */ + 6940, 6574, 6209, 5844, 5479, 5113, 4748, 4383, 4018, 3652, + /* 1979 - 1970 */ + 3287, 2922, 2557, 2191, 1826, 1461, 1096, 730, 365, 0, +}; + +static inline bool is_leap(unsigned int y) +{ + return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0); +} + +void localtime(struct tm *stTm, unsigned int time) +{ + unsigned int year, transition_value, i, days = time / SECONDS_PER_DAY,secs = time % SECONDS_PER_DAY; + + stTm->tm_wday = (4 + days - 1) % 7 + 1; + stTm->tm_sec = secs % 60; + transition_value = secs / 60; + stTm->tm_min = transition_value % 60; + stTm->tm_hour = transition_value / 60; + + for (i = 0, year = DSE_FIRST; days_since_epoch[i] > days; ++i, --year); + + days -= days_since_epoch[i]; + stTm->tm_year = DSE_FIRST - i; + stTm->tm_yday = days; + if (is_leap(year)) + { + for (i = ARRAY_SIZE(days_since_leapyear) - 1;i > 0 && days_since_leapyear[i] > days; --i); + stTm->tm_mday = days - days_since_leapyear[i] + 1; + } + else + { + for (i = ARRAY_SIZE(days_since_year) - 1;i > 0 && days_since_year[i] > days; --i); + stTm->tm_mday = days - days_since_year[i] + 1; + } + + stTm->tm_mon = i + 1; +} + +static void _InvertU8(uint8_t *dBuf, uint8_t *srcBuf) +{ + uint8_t tmp[4] = {0}; + uint8_t i=0; + for(i=0;i<8;i++) + { + if(srcBuf[0] & (1<<i)) + { + tmp[0] |= 1<<(7-i); + } + } + dBuf[0] = tmp[0]; +} + +static void _InvertU32(uint32_t *dBuf, uint32_t *srcBuf) +{ + uint32_t tmp[4] = {0}; + uint8_t i=0; + for(i=0;i<32;i++) + { + if(srcBuf[0] & (1<<i)) + { + tmp[0] |= 1<<(31-i); + } + } + dBuf[0] = tmp[0]; +} + +uint32_t calc_crc32(uint8_t Mode, uint8_t *pMsg, uint32_t Len) +{ + if(Mode > sizeof(CRC32_Table)/sizeof(crc32_Table_TypeDef)) + return 0; + + uint32_t CRCin = CRC32_Table[Mode].initVal; + uint32_t tmp = 0; + uint32_t i=0; + uint8_t j=0; + + for(i=0;i<Len;i++) + { + tmp = *(pMsg++); + if(CRC32_Table[Mode].bits == LSB_MODE) + { + _InvertU8((uint8_t*)&tmp, (uint8_t*)&tmp); + } + CRCin ^= (tmp <<24); + for(j=0;j<8;j++) + { + if(CRCin & 0x80000000) + { + CRCin = (CRCin << 1) ^ CRC32_Table[Mode].POLY; + } + else + { + CRCin <<= 1; + } + } + } + if(CRC32_Table[Mode].bits == LSB_MODE) + { + _InvertU32(&CRCin, &CRCin); + } + return (CRCin ^ CRC32_Table[Mode].sub); +} + + +unsigned long long parse_data(struct eswin *tr,unsigned char size) +{ + unsigned long long value = 0; + if(size == 1) + { + value = * (unsigned char*)(tr->fw->data + offset); + } + else if(size == 2) + { + value = * (unsigned short*)(tr->fw->data + offset); + } + else if(size == 4) + { + value = * (unsigned int*)(tr->fw->data + offset); + } + else if(size == 8) + { + value = * (unsigned long long*)(tr->fw->data + offset); + } + else + { + return value; + } + offset += size; + return value; +} + +uint8_t parse_fw_info(struct eswin *tr, bin_head_data *phead) +{ + unsigned int len = INFO_SIZE; + + phead->fw_Info = vmalloc(len+1); + memcpy(phead->fw_Info,(unsigned char*)(tr->fw->data + offset),len); + + offset += len; + return len; +} + +bool fw_crc_check(struct eswin *tr,bin_head_data head) +{ + unsigned int now_crc32 = 0; + unsigned char crc_shift = ((unsigned char)((void *)(&head.head_crc32) - (void *)(&head))) + sizeof(unsigned int); + now_crc32 = calc_crc32(LSB_MODE, (unsigned char *)(tr->fw->data + crc_shift), HEAD_SIZE - crc_shift); + if(now_crc32 == head.head_crc32) + { + return true; + } + else + { + ECRNX_PRINT("%s,firmware CRC check error , (%s) ,crc total size(%d)\n", __func__, fw_name,HEAD_SIZE - crc_shift); + print_hex_dump(KERN_ERR, DBG_PREFIX_CRC_CHECK, DUMP_PREFIX_ADDRESS, 32, 1, &(head.crc32), 4, false); + print_hex_dump(KERN_ERR, DBG_PREFIX_CRC_CHECK, DUMP_PREFIX_ADDRESS, 32, 1, &now_crc32, 4, false); + return false; + } +} +bool fw_magic_check(struct eswin *tr,bin_head_data head) +{ + if(head.magic == 0xEF40) + { + return true; + } + else if(head.magic == 0xCE56) + { + return true; + } + else + { + ECRNX_PRINT("%s,firmware magic check error , (%s) ,magic(%x)\n", __func__, fw_name,head.magic); + return false; + } +} + +bool fw_check_head(struct eswin *tr) +{ + struct tm timenow = {0}; + + head.head_crc32 = parse_data(tr, sizeof(unsigned int)); + head.crc32 = parse_data(tr, sizeof(unsigned int)); + head.magic = parse_data(tr, sizeof(unsigned int)); + head.UTC_time = parse_data(tr, sizeof(unsigned int)); + + if(fw_magic_check(tr,head) == false) + { + return false; + } + if(fw_crc_check(tr,head) == false) + { + return false; + } + + parse_fw_info(tr, &head); + + localtime(&timenow, head.UTC_time); + ECRNX_PRINT("%s,firmware build time: %04d-%02d-%02d %02d:%02d:%02d\n", __func__,(int)timenow.tm_year,timenow.tm_mon,timenow.tm_mday,timenow.tm_hour,timenow.tm_min,timenow.tm_sec); + if(head.fw_Info != NULL) + { + ECRNX_PRINT("%s,firmware information: (%s)\n", __func__, head.fw_Info); + } + return true; +} diff --git a/drivers/net/wireless/eswin/fw_head_check.h b/drivers/net/wireless/eswin/fw_head_check.h new file mode 100644 index 000000000000..65a60c7d506c --- /dev/null +++ b/drivers/net/wireless/eswin/fw_head_check.h @@ -0,0 +1,43 @@ +/** +****************************************************************************** +* +* @file fw_head_check.h +* +* @brief ecrnx usb firmware validity check functions +* +* Copyright (C) ESWIN 2015-2020 +* +****************************************************************************** +*/ + +#ifndef _FW_HEAD_CHECK_H_ +#define _FW_HEAD_CHECK_H_ + +#include "core.h" + +#define HEAD_SIZE (64) +#define INFO_SIZE (48) + +typedef struct _bin_head_data { + unsigned int head_crc32; + unsigned int crc32; + unsigned int magic; + unsigned int UTC_time; + unsigned char *fw_Info; +}bin_head_data; + +typedef struct{ + uint32_t initVal; + uint32_t POLY; + uint32_t sub; + uint8_t bits; + char *funcName; +}crc32_Table_TypeDef; + + +extern unsigned int offset; + +void localtime(struct tm *stTm, unsigned int time); +bool fw_check_head(struct eswin *tr); + +#endif diff --git a/drivers/net/wireless/eswin/hal_desc.c b/drivers/net/wireless/eswin/hal_desc.c new file mode 100644 index 000000000000..6f040fa147ae --- /dev/null +++ b/drivers/net/wireless/eswin/hal_desc.c @@ -0,0 +1,189 @@ +/** + ***************************************************************************** + * + * @file hal_desc.c + * + * Copyright (C) ESWIN 2015-2020 + * + ***************************************************************************** + */ + +#include <linux/string.h> +#include "hal_desc.h" + +const struct ecrnx_legrate legrates_lut[] = { + [0] = { .idx = 0, .rate = 10 }, + [1] = { .idx = 1, .rate = 20 }, + [2] = { .idx = 2, .rate = 55 }, + [3] = { .idx = 3, .rate = 110 }, + [4] = { .idx = -1, .rate = 0 }, + [5] = { .idx = -1, .rate = 0 }, + [6] = { .idx = -1, .rate = 0 }, + [7] = { .idx = -1, .rate = 0 }, + [8] = { .idx = 10, .rate = 480 }, + [9] = { .idx = 8, .rate = 240 }, + [10] = { .idx = 6, .rate = 120 }, + [11] = { .idx = 4, .rate = 60 }, + [12] = { .idx = 11, .rate = 540 }, + [13] = { .idx = 9, .rate = 360 }, + [14] = { .idx = 7, .rate = 180 }, + [15] = { .idx = 5, .rate = 90 }, +}; + +/** + * ecrnx_machw_type - Return type (NX or HE) MAC HW is used + * + */ +int ecrnx_machw_type(uint32_t machw_version_2) +{ + uint32_t machw_um_ver_maj = (machw_version_2 >> 4) & 0x7; + + if (machw_um_ver_maj >= 4) + return ECRNX_MACHW_HE; + else + return ECRNX_MACHW_NX; +} + +/** + * ecrnx_rx_vector_convert - Convert in place a RX vector from NX hardware into + * a RX vector formatted by HE hardware. + * + * @machw_type: Type of MACHW in use. + * @rx_vect1: Rx vector 1 descriptor of the received frame. + * @rx_vect2: Rx vector 2 descriptor of the received frame. + */ +void ecrnx_rx_vector_convert(int machw_type, + struct rx_vector_1 *rx_vect1, + struct rx_vector_2 *rx_vect2) +{ + struct rx_vector_1_nx rx_vect1_nx; + struct rx_vector_2_nx rx_vect2_nx; + + // Check if we need to do the conversion. Only if old modem is used + if (machw_type == ECRNX_MACHW_HE) { + rx_vect1->rssi1 = rx_vect1->rssi_leg; + return; + } + + // Copy the received vector locally + memcpy(&rx_vect1_nx, rx_vect1, sizeof(struct rx_vector_1_nx)); + + // Reset it + memset(rx_vect1, 0, sizeof(struct rx_vector_1)); + + // Perform the conversion + rx_vect1->format_mod = rx_vect1_nx.format_mod; + rx_vect1->ch_bw = rx_vect1_nx.ch_bw; + rx_vect1->pre_type = rx_vect1_nx.pre_type; + rx_vect1->antenna_set = rx_vect1_nx.antenna_set; + rx_vect1->leg_length = rx_vect1_nx.leg_length; + rx_vect1->leg_rate = rx_vect1_nx.leg_rate; + rx_vect1->rssi1 = rx_vect1_nx.rssi1; + + switch (rx_vect1->format_mod) { + case FORMATMOD_NON_HT: + case FORMATMOD_NON_HT_DUP_OFDM: + rx_vect1->leg.dyn_bw_in_non_ht = rx_vect1_nx.dyn_bw; + rx_vect1->leg.chn_bw_in_non_ht = rx_vect1_nx.ch_bw; + rx_vect1->leg.lsig_valid = rx_vect1_nx.lsig_valid; + break; + case FORMATMOD_HT_MF: + case FORMATMOD_HT_GF: + rx_vect1->ht.sounding = rx_vect1_nx.sounding; + rx_vect1->ht.smoothing = rx_vect1_nx.smoothing; + rx_vect1->ht.short_gi = rx_vect1_nx.short_gi; + rx_vect1->ht.aggregation = rx_vect1_nx.aggregation; + rx_vect1->ht.stbc = rx_vect1_nx.stbc; + rx_vect1->ht.num_extn_ss = rx_vect1_nx.num_extn_ss; + rx_vect1->ht.lsig_valid = rx_vect1_nx.lsig_valid; + rx_vect1->ht.mcs = rx_vect1_nx.mcs; + rx_vect1->ht.fec = rx_vect1_nx.fec_coding; + rx_vect1->ht.length = rx_vect1_nx.ht_length; + break; + case FORMATMOD_VHT: + rx_vect1->vht.sounding = rx_vect1_nx.sounding; + rx_vect1->vht.beamformed = !rx_vect1_nx.smoothing; + rx_vect1->vht.short_gi = rx_vect1_nx.short_gi; + rx_vect1->vht.stbc = rx_vect1_nx.stbc; + rx_vect1->vht.doze_not_allowed = rx_vect1_nx.doze_not_allowed; + rx_vect1->vht.first_user = rx_vect1_nx.first_user; + rx_vect1->vht.partial_aid = rx_vect1_nx.partial_aid; + rx_vect1->vht.group_id = rx_vect1_nx.group_id; + rx_vect1->vht.mcs = rx_vect1_nx.mcs; + rx_vect1->vht.nss = rx_vect1_nx.stbc ? rx_vect1_nx.n_sts/2 : rx_vect1_nx.n_sts; + rx_vect1->vht.fec = rx_vect1_nx.fec_coding; + rx_vect1->vht.length = (rx_vect1_nx._ht_length << 16) | rx_vect1_nx.ht_length; + break; + } + + if (!rx_vect2) + return; + + // Copy the received vector 2 locally + memcpy(&rx_vect2_nx, rx_vect2, sizeof(struct rx_vector_2_nx)); + + // Reset it + memset(rx_vect2, 0, sizeof(struct rx_vector_2)); + + rx_vect2->rcpi1 = rx_vect2_nx.rcpi; + rx_vect2->rcpi2 = rx_vect2_nx.rcpi; + rx_vect2->rcpi3 = rx_vect2_nx.rcpi; + rx_vect2->rcpi4 = rx_vect2_nx.rcpi; + + rx_vect2->evm1 = rx_vect2_nx.evm1; + rx_vect2->evm2 = rx_vect2_nx.evm2; + rx_vect2->evm3 = rx_vect2_nx.evm3; + rx_vect2->evm4 = rx_vect2_nx.evm4; +} + + +/** + * ecrnx_rx_status_convert - Convert in place a legacy MPDU status from NX hardware + * into a MPDU status formatted by HE hardware. + * + * @machw_type: Type of MACHW in use. + * @status: Rx MPDU status of the received frame. + */ +void ecrnx_rx_status_convert(int machw_type, struct mpdu_status *status) +{ + struct mpdu_status_nx *status_nx; + + if (machw_type == ECRNX_MACHW_HE) + return; + + status_nx = (struct mpdu_status_nx *)status; + status->undef_err = status_nx->undef_err; + + switch (status_nx->decr_status) { + case ECRNX_RX_HD_NX_DECR_UNENC: + status->decr_type = ECRNX_RX_HD_DECR_UNENC; + status->decr_err = 0; + break; + case ECRNX_RX_HD_NX_DECR_ICVFAIL: + status->decr_type = ECRNX_RX_HD_DECR_WEP; + status->decr_err = 1; + break; + case ECRNX_RX_HD_NX_DECR_CCMPFAIL: + case ECRNX_RX_HD_NX_DECR_AMSDUDISCARD: + status->decr_type = ECRNX_RX_HD_DECR_CCMP128; + status->decr_err = 1; + break; + case ECRNX_RX_HD_NX_DECR_NULLKEY: + status->decr_type = ECRNX_RX_HD_DECR_NULLKEY; + status->decr_err = 1; + break; + case ECRNX_RX_HD_NX_DECR_WEPSUCCESS: + status->decr_type = ECRNX_RX_HD_DECR_WEP; + status->decr_err = 0; + break; + case ECRNX_RX_HD_NX_DECR_TKIPSUCCESS: + status->decr_type = ECRNX_RX_HD_DECR_TKIP; + status->decr_err = 0; + break; + case ECRNX_RX_HD_NX_DECR_CCMPSUCCESS: + status->decr_type = ECRNX_RX_HD_DECR_CCMP128; + status->decr_err = 0; + break; + } +} + diff --git a/drivers/net/wireless/eswin/hal_desc.h b/drivers/net/wireless/eswin/hal_desc.h new file mode 100644 index 000000000000..647961a5311a --- /dev/null +++ b/drivers/net/wireless/eswin/hal_desc.h @@ -0,0 +1,670 @@ +/** + ****************************************************************************** + * + * @file hal_desc.h + * + * @brief File containing the definition of HW descriptors. + * + * Contains the definition and structures used by HW + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef _HAL_DESC_H_ +#define _HAL_DESC_H_ + +#include "lmac_types.h" + +#define ECRNX_MACHW_NX 1 +#define ECRNX_MACHW_HE 2 +/* Rate and policy table */ + +#define N_CCK 8 +#define N_OFDM 8 +#define N_HT (8 * 2 * 2 * 4) +#define N_VHT (10 * 4 * 2 * 8) +#define N_HE_SU (12 * 4 * 3 * 8) +#define N_HE_MU (12 * 6 * 3 * 8) + +/* conversion table from NL80211 to MACHW enum */ +extern const int chnl2bw[]; + +/* conversion table from MACHW to NL80211 enum */ +extern const int bw2chnl[]; + +struct ecrnx_legrate { + s16 idx; + u16 rate; // in 100Kbps +}; +extern const struct ecrnx_legrate legrates_lut[]; +/* Values for formatModTx */ +#define FORMATMOD_NON_HT 0 +#define FORMATMOD_NON_HT_DUP_OFDM 1 +#define FORMATMOD_HT_MF 2 +#define FORMATMOD_HT_GF 3 +#define FORMATMOD_VHT 4 +#define FORMATMOD_HE_SU 5 +#define FORMATMOD_HE_MU 6 +#define FORMATMOD_HE_ER 7 +#define FORMATMOD_HE_TB 8 + +/* Values for navProtFrmEx */ +#define NAV_PROT_NO_PROT_BIT 0 +#define NAV_PROT_SELF_CTS_BIT 1 +#define NAV_PROT_RTS_CTS_BIT 2 +#define NAV_PROT_RTS_CTS_WITH_QAP_BIT 3 +#define NAV_PROT_STBC_BIT 4 + +/* THD MACCTRLINFO2 fields, used in struct umacdesc umac.flags */ +/// WhichDescriptor definition - contains aMPDU bit and position value +/// Offset of WhichDescriptor field in the MAC CONTROL INFO 2 word +#define WHICHDESC_OFT 19 +/// Mask of the WhichDescriptor field +#define WHICHDESC_MSK (0x07 << WHICHDESC_OFT) +/// Only 1 THD possible, describing an unfragmented MSDU +#define WHICHDESC_UNFRAGMENTED_MSDU (0x00 << WHICHDESC_OFT) +/// THD describing the first MPDU of a fragmented MSDU +#define WHICHDESC_FRAGMENTED_MSDU_FIRST (0x01 << WHICHDESC_OFT) +/// THD describing intermediate MPDUs of a fragmented MSDU +#define WHICHDESC_FRAGMENTED_MSDU_INT (0x02 << WHICHDESC_OFT) +/// THD describing the last MPDU of a fragmented MSDU +#define WHICHDESC_FRAGMENTED_MSDU_LAST (0x03 << WHICHDESC_OFT) +/// THD for extra descriptor starting an AMPDU +#define WHICHDESC_AMPDU_EXTRA (0x04 << WHICHDESC_OFT) +/// THD describing the first MPDU of an A-MPDU +#define WHICHDESC_AMPDU_FIRST (0x05 << WHICHDESC_OFT) +/// THD describing intermediate MPDUs of an A-MPDU +#define WHICHDESC_AMPDU_INT (0x06 << WHICHDESC_OFT) +/// THD describing the last MPDU of an A-MPDU +#define WHICHDESC_AMPDU_LAST (0x07 << WHICHDESC_OFT) + +/// aMPDU bit offset +#define AMPDU_OFT 21 +/// aMPDU bit +#define AMPDU_BIT CO_BIT(AMPDU_OFT) + +union ecrnx_mcs_index { + struct { + u32 mcs : 3; + u32 nss : 2; + } ht; + struct { + u32 mcs : 4; + u32 nss : 3; + } vht; + struct { + u32 mcs : 4; + u32 nss : 3; + } he; + u32 legacy : 7; +}; + +union ecrnx_rate_ctrl_info { + struct { + u32 mcsIndexTx : 7; + u32 bwTx : 2; + u32 giAndPreTypeTx : 2; + u32 formatModTx : 3; + #ifdef CONFIG_ECRNX_FULLMAC + u32 dcmTx : 1; + #else + u32 navProtFrmEx : 3; + u32 mcsIndexProtTx : 7; + u32 bwProtTx : 2; + u32 formatModProtTx : 3; + u32 nRetry : 3; + #endif + }; + u32 value; +}; + +struct ecrnx_power_ctrl_info { + u32 txPwrLevelPT : 8; + u32 txPwrLevelProtPT : 8; + u32 reserved :16; +}; + +union ecrnx_pol_phy_ctrl_info_1 { + struct { + u32 rsvd1 : 3; + u32 bfFrmEx : 1; + u32 numExtnSS : 2; + u32 fecCoding : 1; + u32 stbc : 2; + u32 rsvd2 : 5; + u32 nTx : 3; + u32 nTxProt : 3; + }; + u32 value; +}; + +union ecrnx_pol_phy_ctrl_info_2 { + struct { + u32 antennaSet : 8; + u32 smmIndex : 8; + u32 beamFormed : 1; + }; + u32 value; +}; + +union ecrnx_pol_mac_ctrl_info_1 { + struct { + u32 keySRamIndex : 10; + u32 keySRamIndexRA : 10; + }; + u32 value; +}; + +union ecrnx_pol_mac_ctrl_info_2 { + struct { + u32 longRetryLimit : 8; + u32 shortRetryLimit : 8; + u32 rtsThreshold : 12; + }; + u32 value; +}; + +#define POLICY_TABLE_PATTERN 0xBADCAB1E + +struct tx_policy_tbl { + /* Unique Pattern at the start of Policy Table */ + u32 upatterntx; + /* PHY Control 1 Information used by MAC HW */ + union ecrnx_pol_phy_ctrl_info_1 phyctrlinfo_1; + /* PHY Control 2 Information used by MAC HW */ + union ecrnx_pol_phy_ctrl_info_2 phyctrlinfo_2; + /* MAC Control 1 Information used by MAC HW */ + union ecrnx_pol_mac_ctrl_info_1 macctrlinfo_1; + /* MAC Control 2 Information used by MAC HW */ + union ecrnx_pol_mac_ctrl_info_2 macctrlinfo_2; + + union ecrnx_rate_ctrl_info ratectrlinfos[NX_TX_MAX_RATES]; + struct ecrnx_power_ctrl_info powerctrlinfos[NX_TX_MAX_RATES]; +}; + +#ifdef CONFIG_ECRNX_SOFTMAC + +union ecrnx_hw_txstatus { + struct { + u32 num_rts_retries : 8; + u32 num_mpdu_retries : 8; + u32 retry_limit_reached : 1; + u32 lifetime_expired : 1; + u32 baFrameReceived : 1; + u32 reserved2 : 4; + u32 frm_successful_tx : 1; + u32 transmission_bw : 2; + u32 which_descriptor_sw : 4; + u32 descriptor_done_swtx : 1; + u32 descriptor_done_hwtx : 1; + }; + u32 value; +}; + +// WhichDescriptor for AMPDUs (_under BA Policy_) +#define __WD_AMPDU_BAPOL 0xC +#define __WD_AMPDU_EXTRA 0xC +#define __WD_AMPDU_FIRST 0xD +#define __WD_AMPDU_INT 0xE +#define __WD_AMPDU_LAST 0xF + +#define ECRNX_WD_IS_AMPDU(whichdesc) \ + (((whichdesc) & __WD_AMPDU_BAPOL) == __WD_AMPDU_BAPOL) +#define ECRNX_WD_IS_FIRST(whichdesc) \ + ((whichdesc) == __WD_AMPDU_FIRST) +#define ECRNX_WD_IS_LAST(whichdesc) \ + ((whichdesc) == __WD_AMPDU_LAST) + +union ecrnx_thd_phy_ctrl_info { + struct { + u32 soundingTx : 1; + u32 smoothingTx : 1; + u32 smoothingProtTx : 1; + u32 useBWSignalingTx : 1; + u32 dynBWTx : 1; + u32 dozeNotAllowedTx : 1; + u32 continuousTx : 1; + u32 rsvd : 1; + u32 PTITx : 4; + u32 userPositionTx : 2; + u32 useRIFSTx : 1; + u32 muMIMOTx : 1; + u32 groupIDTx : 6; + u32 partialAIDTx : 9; + }; + u32 value; +}; + +#define EXPECTED_ACK_NO_ACK 0 +#define EXPECTED_ACK_NORMAL_ACK 1 +#define EXPECTED_ACK_BLOCK_ACK 2 +#define EXPECTED_ACK_COMPRESSED_BLOCK_ACK 3 + +union ecrnx_thd_mac_ctrl_info_1 { + struct { + u32 rsvd1 : 9; + u32 expectedAck : 2; + u32 lstp : 1; + u32 lstpProt : 1; + u32 lowRateRetry : 1; + u32 writeACK : 1; + u32 rsvd2 : 1; + u32 protFrmDur : 16; + }; + u32 value; +}; + +/** + * struct ecrnx_hw_txhdr - Hardware part of tx header + * + * @policy: Policy table to use for transmission + * @mac_ctrl_info: MAC configuration to use for transmission + * @phy_ctrl_info: PHY configuration to use for transmission + * + * @status: Status updated by fw/hardware after transmission + */ +struct ecrnx_hw_txhdr { + struct tx_policy_tbl policy; + union ecrnx_thd_mac_ctrl_info_1 mac_ctrl_info; + union ecrnx_thd_phy_ctrl_info phy_ctrl_info; + union ecrnx_hw_txstatus status; +}; + +#else /* !CONFIG_ECRNX_SOFTMAC */ + +/** + * struct ecrnx_hw_txstatus - Bitfield of confirmation status + * + * @tx_done: packet has been processed by the firmware. + * @retry_required: packet has been transmitted but not acknoledged. + * Driver must repush it. + * @sw_retry_required: packet has not been transmitted (FW wasn't able to push + * it when it received it: not active channel ...). Driver must repush it. + * @acknowledged: packet has been acknowledged by peer + */ +union ecrnx_hw_txstatus { + struct { + u32 tx_done : 1; + u32 retry_required : 1; + u32 sw_retry_required : 1; + u32 acknowledged : 1; + u32 reserved :28; + }; + u32 value; +}; + +/** + * struct tx_cfm_tag - Structure indicating the status and other + * information about the transmission + * + * @pn: PN that was used for the transmission + * @sn: Sequence number of the packet + * @timestamp: Timestamp of first transmission of this MPDU + * @credits: Number of credits to be reallocated for the txq that push this + * buffer (can be 0 or 1) + * @ampdu_size: Size of the ampdu in which the frame has been transmitted if + * this was the last frame of the a-mpdu, and 0 if the frame is not the last + * frame on a a-mdpu. + * 1 means that the frame has been transmitted as a singleton. + * @amsdu_size: Size, in bytes, allowed to create a-msdu. + * @status: transmission status + */ +struct tx_cfm_tag +{ + u16_l pn[4]; + u16_l sn; + u16_l timestamp; + s8_l credits; + u8_l ampdu_size; +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + u16_l amsdu_size; +#endif + +#ifdef CONFIG_ECRNX_ESWIN + uint32_t hostid[2]; +#endif + + union ecrnx_hw_txstatus status; +}; + +/** + * struct ecrnx_hw_txhdr - Hardware part of tx header + * + * @cfm: Information updated by fw/hardware after sending a frame + */ +struct ecrnx_hw_txhdr { + struct tx_cfm_tag cfm; +}; + +#endif /* CONFIG_ECRNX_SOFTMAC */ + +#define ECRNX_RX_HD_NX_DECR_UNENC 0 // Frame unencrypted +#define ECRNX_RX_HD_NX_DECR_ICVFAIL 1 // WEP/TKIP ICV failure +#define ECRNX_RX_HD_NX_DECR_CCMPFAIL 2 // CCMP failure +#define ECRNX_RX_HD_NX_DECR_AMSDUDISCARD 3 // A-MSDU discarded at HW +#define ECRNX_RX_HD_NX_DECR_NULLKEY 4 // NULL key found +#define ECRNX_RX_HD_NX_DECR_WEPSUCCESS 5 // Security type WEP +#define ECRNX_RX_HD_NX_DECR_TKIPSUCCESS 6 // Security type TKIP +#define ECRNX_RX_HD_NX_DECR_CCMPSUCCESS 7 // Security type CCMP (or WPI) +#define ECRNX_RX_HD_DECR_UNENC 0 // Frame unencrypted +#define ECRNX_RX_HD_DECR_WEP 1 // Security type WEP +#define ECRNX_RX_HD_DECR_TKIP 2 // Security type TKIP +#define ECRNX_RX_HD_DECR_CCMP128 3 // Security type CCMP (128 bits) +#define ECRNX_RX_HD_DECR_CCMP256 4 // Security type CCMP (256 bits) +#define ECRNX_RX_HD_DECR_GCMP128 5 // Security type GCMP (128 bits) +#define ECRNX_RX_HD_DECR_GCMP256 6 // Security type GCMP (256 bits) +#define ECRNX_RX_HD_DECR_WAPI 7 // Security type WAPI +#define ECRNX_RX_HD_DECR_NULLKEY 15 // NULL key found +struct rx_vector_1_nx { + u32 leg_length :12; + u32 leg_rate : 4; + u32 ht_length :16; + u32 _ht_length : 4; // FIXME + u32 short_gi : 1; + u32 stbc : 2; + u32 smoothing : 1; + u32 mcs : 7; + u32 pre_type : 1; + u32 format_mod : 3; + u32 ch_bw : 2; + u32 n_sts : 3; + u32 lsig_valid : 1; + u32 sounding : 1; + u32 num_extn_ss : 2; + u32 aggregation : 1; + u32 fec_coding : 1; + u32 dyn_bw : 1; + u32 doze_not_allowed : 1; + u32 antenna_set : 8; + u32 partial_aid : 9; + u32 group_id : 6; + u32 first_user : 1; + s32 rssi1 : 8; + s32 rssi2 : 8; + s32 rssi3 : 8; + s32 rssi4 : 8; + u32 reserved_1d : 8; +}; +struct rx_vector_2_nx { + u32 rcpi : 8; + u32 evm1 : 8; + u32 evm2 : 8; + u32 evm3 : 8; + u32 evm4 : 8; + u32 reserved2b_1 : 8; + u32 reserved2b_2 : 8; + u32 reserved2b_3 : 8; +}; +struct mpdu_status_nx { + u32 rx_vect2_valid : 1; + u32 resp_frame : 1; + u32 decr_status : 3; + u32 rx_fifo_oflow : 1; + u32 undef_err : 1; + u32 phy_err : 1; + u32 fcs_err : 1; + u32 addr_mismatch : 1; + u32 ga_frame : 1; + u32 current_ac : 2; + u32 frm_successful_rx : 1; + u32 desc_done_rx : 1; + u32 key_sram_index : 10; + u32 key_sram_valid : 1; + u32 type : 2; + u32 subtype : 4; +}; +struct rx_leg_vect +{ + u8 dyn_bw_in_non_ht : 1; + u8 chn_bw_in_non_ht : 2; + u8 rsvd_nht : 4; + u8 lsig_valid : 1; +} __packed; +struct rx_ht_vect +{ + u16 sounding : 1; + u16 smoothing : 1; + u16 short_gi : 1; + u16 aggregation : 1; + u16 stbc : 1; + u16 num_extn_ss : 2; + u16 lsig_valid : 1; + u16 mcs : 7; + u16 fec : 1; + u16 length :16; +} __packed; +struct rx_vht_vect +{ + u8 sounding : 1; + u8 beamformed : 1; + u8 short_gi : 1; + u8 rsvd_vht1 : 1; + u8 stbc : 1; + u8 doze_not_allowed : 1; + u8 first_user : 1; + u8 rsvd_vht2 : 1; + u16 partial_aid : 9; + u16 group_id : 6; + u16 rsvd_vht3 : 1; + u32 mcs : 4; + u32 nss : 3; + u32 fec : 1; + u32 length :20; + u32 rsvd_vht4 : 4; +} __packed; +struct rx_he_vect +{ + u8 sounding : 1; + u8 beamformed : 1; + u8 gi_type : 2; + u8 stbc : 1; + u8 rsvd_he1 : 3; + u8 uplink_flag : 1; + u8 beam_change : 1; + u8 dcm : 1; + u8 he_ltf_type : 2; + u8 doppler : 1; + u8 rsvd_he2 : 2; + u8 bss_color : 6; + u8 rsvd_he3 : 2; + u8 txop_duration : 7; + u8 rsvd_he4 : 1; + u8 pe_duration : 4; + u8 spatial_reuse : 4; + u8 sig_b_comp_mode : 1; + u8 dcm_sig_b : 1; + u8 mcs_sig_b : 3; + u8 ru_size : 3; + u32 mcs : 4; + u32 nss : 3; + u32 fec : 1; + u32 length :20; + u32 rsvd_he6 : 4; +} __packed; +struct rx_vector_1 { + u8 format_mod : 4; + u8 ch_bw : 3; + u8 pre_type : 1; + u8 antenna_set : 8; + s32 rssi_leg : 8; + u32 leg_length :12; + u32 leg_rate : 4; + s32 rssi1 : 8; + union + { + struct rx_leg_vect leg; + struct rx_ht_vect ht; + struct rx_vht_vect vht; + struct rx_he_vect he; + }; +} __packed; +struct rx_vector_2 { + u32 rcpi1 : 8; + u32 rcpi2 : 8; + u32 rcpi3 : 8; + u32 rcpi4 : 8; + u32 evm1 : 8; + u32 evm2 : 8; + u32 evm3 : 8; + u32 evm4 : 8; +}; +struct mpdu_status { + u32 rx_vect2_valid : 1; + u32 resp_frame : 1; + u32 decr_type : 4; + u32 decr_err : 1; + u32 undef_err : 1; + u32 fcs_err : 1; + u32 addr_mismatch : 1; + u32 ga_frame : 1; + u32 current_ac : 2; + u32 frm_successful_rx : 1; + u32 desc_done_rx : 1; + u32 key_sram_index : 10; + u32 key_sram_v : 1; + u32 type : 2; + u32 subtype : 4; +}; +struct hw_vect { + u32 len :16; + u32 reserved : 8; + u32 mpdu_cnt : 6; + u32 ampdu_cnt : 2; + __le32 tsf_lo; + /** TSF High */ + __le32 tsf_hi; + + /** Receive Vector 1 */ + struct rx_vector_1 rx_vect1; + /** Receive Vector 2 */ + struct rx_vector_2 rx_vect2; + + /** MPDU status information */ + struct mpdu_status status; +}; + +struct phy_channel_info_desc { + /** PHY channel information 1 */ + u32 phy_band : 8; + u32 phy_channel_type : 8; + u32 phy_prim20_freq : 16; + + /** PHY channel information 2 */ + u32 phy_center1_freq : 16; + u32 phy_center2_freq : 16; +}; + +int ecrnx_machw_type(uint32_t machw_version_2); +void ecrnx_rx_vector_convert(int machw_type, struct rx_vector_1 *rx_vect1, + struct rx_vector_2 *rx_vect2); +void ecrnx_rx_status_convert(int machw_type, struct mpdu_status *status); + +/****************************************************************************** + * Modem + ******************************************************************************/ +#define MDM_PHY_CONFIG_TRIDENT 0 +#define MDM_PHY_CONFIG_CATAXIA 1 +#define MDM_PHY_CONFIG_KARST 2 + +// MODEM features (from reg_mdm_stat.h) +/// MUMIMOTX field bit +#define MDM_MUMIMOTX_BIT ((u32)0x80000000) +/// MUMIMOTX field position +#define MDM_MUMIMOTX_POS 31 +/// MUMIMORX field bit +#define MDM_MUMIMORX_BIT ((u32)0x40000000) +/// MUMIMORX field position +#define MDM_MUMIMORX_POS 30 +/// BFMER field bit +#define MDM_BFMER_BIT ((u32)0x20000000) +/// BFMER field position +#define MDM_BFMER_POS 29 +/// BFMEE field bit +#define MDM_BFMEE_BIT ((u32)0x10000000) +/// BFMEE field position +#define MDM_BFMEE_POS 28 +/// LDPCDEC field bit +#define MDM_LDPCDEC_BIT ((u32)0x08000000) +/// LDPCDEC field position +#define MDM_LDPCDEC_POS 27 +/// LDPCENC field bit +#define MDM_LDPCENC_BIT ((u32)0x04000000) +/// LDPCENC field position +#define MDM_LDPCENC_POS 26 +/// CHBW field mask +#define MDM_CHBW_MASK ((u32)0x03000000) +/// CHBW field LSB position +#define MDM_CHBW_LSB 24 +/// CHBW field width +#define MDM_CHBW_WIDTH ((u32)0x00000002) +/// DSSSCCK field bit +#define MDM_DSSSCCK_BIT ((u32)0x00800000) +/// DSSSCCK field position +#define MDM_DSSSCCK_POS 23 +/// VHT field bit +#define MDM_VHT_BIT ((u32)0x00400000) +/// VHT field position +#define MDM_VHT_POS 22 +/// HE field bit +#define MDM_HE_BIT ((u32)0x00200000) +/// HE field position +#define MDM_HE_POS 21 +/// ESS field bit +#define MDM_ESS_BIT ((u32)0x00100000) +/// ESS field position +#define MDM_ESS_POS 20 +/// RFMODE field mask +#define MDM_RFMODE_MASK ((u32)0x000F0000) +/// RFMODE field LSB position +#define MDM_RFMODE_LSB 16 +/// RFMODE field width +#define MDM_RFMODE_WIDTH ((u32)0x00000004) +/// NSTS field mask +#define MDM_NSTS_MASK ((u32)0x0000F000) +/// NSTS field LSB position +#define MDM_NSTS_LSB 12 +/// NSTS field width +#define MDM_NSTS_WIDTH ((u32)0x00000004) +/// NSS field mask +#define MDM_NSS_MASK ((u32)0x00000F00) +/// NSS field LSB position +#define MDM_NSS_LSB 8 +/// NSS field width +#define MDM_NSS_WIDTH ((u32)0x00000004) +/// NTX field mask +#define MDM_NTX_MASK ((u32)0x000000F0) +/// NTX field LSB position +#define MDM_NTX_LSB 4 +/// NTX field width +#define MDM_NTX_WIDTH ((u32)0x00000004) +/// NRX field mask +#define MDM_NRX_MASK ((u32)0x0000000F) +/// NRX field LSB position +#define MDM_NRX_LSB 0 +/// NRX field width +#define MDM_NRX_WIDTH ((u32)0x00000004) + +#define __MDM_PHYCFG_FROM_VERS(v) (((v) & MDM_RFMODE_MASK) >> MDM_RFMODE_LSB) + +#define RIU_FCU_PRESENT_MASK ((u32)0xFF000000) +#define RIU_FCU_PRESENT_LSB 24 + +#define __RIU_FCU_PRESENT(v) (((v) & RIU_FCU_PRESENT_MASK) >> RIU_FCU_PRESENT_LSB == 5) + +/// AGC load version field mask +#define RIU_AGC_LOAD_MASK ((u32)0x00C00000) +/// AGC load version field LSB position +#define RIU_AGC_LOAD_LSB 22 + +#define __RIU_AGCLOAD_FROM_VERS(v) (((v) & RIU_AGC_LOAD_MASK) >> RIU_AGC_LOAD_LSB) + +#define __FPGA_TYPE(v) (((v) & 0xFFFF0000) >> 16) + +#define __MDM_MAJOR_VERSION(v) (((v) & 0xFF000000) >> 24) +#define __MDM_MINOR_VERSION(v) (((v) & 0x00FF0000) >> 16) +#define __MDM_VERSION(v) ((__MDM_MAJOR_VERSION(v) + 2) * 10 + __MDM_MINOR_VERSION(v)) + + +#endif // _HAL_DESC_H_ diff --git a/drivers/net/wireless/eswin/ipc_compat.h b/drivers/net/wireless/eswin/ipc_compat.h new file mode 100644 index 000000000000..3d0e71a31c35 --- /dev/null +++ b/drivers/net/wireless/eswin/ipc_compat.h @@ -0,0 +1,25 @@ +/** + **************************************************************************************** + * + * @file ipc_compat.h + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ + +#ifndef _IPC_H_ +#define _IPC_H_ + +#define __INLINE static __attribute__((__always_inline__)) inline + +#define __ALIGN4 __aligned(4) + +#define ASSERT_ERR(condition) \ + do { \ + if (unlikely(!(condition))) { \ + printk(DBG_PREFIX KERN_ERR "%s:%d:ASSERT_ERR(" #condition ")\n", __FILE__, __LINE__); \ + } \ + } while(0) + +#endif /* _IPC_H_ */ diff --git a/drivers/net/wireless/eswin/ipc_host.c b/drivers/net/wireless/eswin/ipc_host.c new file mode 100644 index 000000000000..17ef5c150041 --- /dev/null +++ b/drivers/net/wireless/eswin/ipc_host.c @@ -0,0 +1,784 @@ +/** + ****************************************************************************** + * + * @file ipc_host.c + * + * @brief IPC module. + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +/* + * INCLUDE FILES + ****************************************************************************** + */ +#ifndef __KERNEL__ +#include <stdio.h> +#define REG_SW_SET_PROFILING(env, value) do{ }while(0) +#define REG_SW_CLEAR_PROFILING(env, value) do{ }while(0) +#define REG_SW_CLEAR_HOSTBUF_IDX_PROFILING(env) do{ }while(0) +#define REG_SW_SET_HOSTBUF_IDX_PROFILING(env, val) do{ }while(0) +#else +#include <linux/spinlock.h> +#include "ecrnx_defs.h" +#include "ecrnx_prof.h" +#endif + +#include "reg_ipc_app.h" +#include "ipc_host.h" + +#ifdef CONFIG_ECRNX_ESWIN +#include "eswin_utils.h" +#endif + +/* + * TYPES DEFINITION + ****************************************************************************** + */ + +const int nx_txdesc_cnt[] = +{ + NX_TXDESC_CNT0, + NX_TXDESC_CNT1, + NX_TXDESC_CNT2, + NX_TXDESC_CNT3, + #if NX_TXQ_CNT == 5 + NX_TXDESC_CNT4, + #endif +}; + +const int nx_txdesc_cnt_msk[] = +{ + NX_TXDESC_CNT0 - 1, + NX_TXDESC_CNT1 - 1, + NX_TXDESC_CNT2 - 1, + NX_TXDESC_CNT3 - 1, + #if NX_TXQ_CNT == 5 + NX_TXDESC_CNT4 - 1, + #endif +}; + +const int nx_txuser_cnt[] = +{ + CONFIG_USER_MAX, + CONFIG_USER_MAX, + CONFIG_USER_MAX, + CONFIG_USER_MAX, + #if NX_TXQ_CNT == 5 + 1, + #endif +}; + + +/* + * FUNCTIONS DEFINITIONS + ****************************************************************************** + */ +/** + * ipc_host_rxdesc_handler() - Handle the reception of a Rx Descriptor + * + * @env: pointer to the IPC Host environment + * + * Called from general IRQ handler when status %IPC_IRQ_E2A_RXDESC is set + */ +static void ipc_host_rxdesc_handler(struct ipc_host_env_tag *env) +{ + // For profiling + REG_SW_SET_PROFILING(env->pthis, SW_PROF_IRQ_E2A_RXDESC); + + // LMAC has triggered an IT saying that a reception has occurred. + // Then we first need to check the validity of the current hostbuf, and the validity + // of the next hostbufs too, because it is likely that several hostbufs have been + // filled within the time needed for this irq handling + do { + #ifdef CONFIG_ECRNX_FULLMAC + // call the external function to indicate that a RX descriptor is received + if (env->cb.recv_data_ind(env->pthis, + env->ipc_host_rxdesc_array[env->ipc_host_rxdesc_idx].hostid) != 0) + #else + // call the external function to indicate that a RX packet is received + if (env->cb.recv_data_ind(env->pthis, + env->ipc_host_rxbuf_array[env->ipc_host_rxbuf_idx].hostid) != 0) + #endif //(CONFIG_ECRNX_FULLMAC) + break; + + }while(1); + + // For profiling + REG_SW_CLEAR_PROFILING(env->pthis, SW_PROF_IRQ_E2A_RXDESC); +} + +/** + * ipc_host_radar_handler() - Handle the reception of radar events + * + * @env: pointer to the IPC Host environment + * + * Called from general IRQ handler when status %IPC_IRQ_E2A_RADAR is set + */ +static void ipc_host_radar_handler(struct ipc_host_env_tag *env) +{ +#ifdef CONFIG_ECRNX_RADAR + // LMAC has triggered an IT saying that a radar event has been sent to upper layer. + // Then we first need to check the validity of the current msg buf, and the validity + // of the next buffers too, because it is likely that several buffers have been + // filled within the time needed for this irq handling + // call the external function to indicate that a RX packet is received + spin_lock(&((struct ecrnx_hw *)env->pthis)->radar.lock); + while (env->cb.recv_radar_ind(env->pthis, + env->ipc_host_radarbuf_array[env->ipc_host_radarbuf_idx].hostid) == 0) + ; + spin_unlock(&((struct ecrnx_hw *)env->pthis)->radar.lock); +#endif /* CONFIG_ECRNX_RADAR */ +} + +/** + * ipc_host_unsup_rx_vec_handler() - Handle the reception of unsupported rx vector + * + * @env: pointer to the IPC Host environment + * + * Called from general IRQ handler when status %IPC_IRQ_E2A_UNSUP_RX_VEC is set + */ +static void ipc_host_unsup_rx_vec_handler(struct ipc_host_env_tag *env) +{ + while (env->cb.recv_unsup_rx_vec_ind(env->pthis, + env->ipc_host_unsuprxvecbuf_array[env->ipc_host_unsuprxvecbuf_idx].hostid) == 0) + ; +} + +/** + * ipc_host_msg_handler() - Handler for firmware message + * + * @env: pointer to the IPC Host environment + * + * Called from general IRQ handler when status %IPC_IRQ_E2A_MSG is set + */ +static void ipc_host_msg_handler(struct ipc_host_env_tag *env) +{ + // For profiling + REG_SW_SET_PROFILING(env->pthis, SW_PROF_IRQ_E2A_MSG); + + // LMAC has triggered an IT saying that a message has been sent to upper layer. + // Then we first need to check the validity of the current msg buf, and the validity + // of the next buffers too, because it is likely that several buffers have been + // filled within the time needed for this irq handling + // call the external function to indicate that a RX packet is received + while (env->cb.recv_msg_ind(env->pthis, + env->ipc_host_msgbuf_array[env->ipc_host_msge2a_idx].hostid) == 0) + ; + + + // For profiling + REG_SW_CLEAR_PROFILING(env->pthis, SW_PROF_IRQ_E2A_MSG); + +} + +/** + * ipc_host_msgack_handler() - Handle the reception of message acknowledgement + * + * @env: pointer to the IPC Host environment + * + * Called from general IRQ handler when status %IPC_IRQ_E2A_MSG_ACK is set + */ +static void ipc_host_msgack_handler(struct ipc_host_env_tag *env) +{ + void *hostid = env->msga2e_hostid; + + ASSERT_ERR(hostid); + ASSERT_ERR(env->msga2e_cnt == (((struct lmac_msg *)(&env->shared->msg_a2e_buf.msg))->src_id & 0xFF)); + + env->msga2e_hostid = NULL; + env->msga2e_cnt++; + env->cb.recv_msgack_ind(env->pthis, hostid); +} + +/** + * ipc_host_dbg_handler() - Handle the reception of Debug event + * + * @env: pointer to the IPC Host environment + * + * Called from general IRQ handler when status %IPC_IRQ_E2A_DBG is set + */ +static void ipc_host_dbg_handler(struct ipc_host_env_tag *env) +{ + // For profiling + REG_SW_SET_PROFILING(env->pthis, SW_PROF_IRQ_E2A_DBG); + + // LMAC has triggered an IT saying that a DBG message has been sent to upper layer. + // Then we first need to check the validity of the current buffer, and the validity + // of the next buffers too, because it is likely that several buffers have been + // filled within the time needed for this irq handling + // call the external function to indicate that a RX packet is received + while(env->cb.recv_dbg_ind(env->pthis, + env->ipc_host_dbgbuf_array[env->ipc_host_dbg_idx].hostid) == 0) + ; + + // For profiling + REG_SW_CLEAR_PROFILING(env->pthis, SW_PROF_IRQ_E2A_DBG); +} + +/** + * ipc_host_tx_cfm_handler() - Handle the reception of TX confirmation + * + * @env: pointer to the IPC Host environment + * @queue_idx: index of the hardware on which the confirmation has been received + * @user_pos: index of the user position + * + * Called from general IRQ handler when status %IPC_IRQ_E2A_TXCFM is set + */ +static void ipc_host_tx_cfm_handler(struct ipc_host_env_tag *env, + const int queue_idx, const int user_pos) +{ + // TX confirmation descriptors have been received + REG_SW_SET_PROFILING(env->pthis, SW_PROF_IRQ_E2A_TXCFM); + while (1) + { + // Get the used index and increase it. We do the increase before knowing if the + // current buffer is confirmed because the callback function may call the + // ipc_host_txdesc_get() in case flow control was enabled and the index has to be + // already at the good value to ensure that the test of FIFO full is correct + uint32_t used_idx = env->txdesc_used_idx[queue_idx][user_pos]++; + uint32_t used_idx_mod = used_idx & nx_txdesc_cnt_msk[queue_idx]; + void *host_id = env->tx_host_id[queue_idx][user_pos][used_idx_mod]; + + // Reset the host id in the array + env->tx_host_id[queue_idx][user_pos][used_idx_mod] = 0; + + // call the external function to indicate that a TX packet is freed + if (host_id == 0) + { + // No more confirmations, so put back the used index at its initial value + env->txdesc_used_idx[queue_idx][user_pos] = used_idx; + break; + } + + if (env->cb.send_data_cfm(env->pthis, host_id) != 0) + { + // No more confirmations, so put back the used index at its initial value + env->txdesc_used_idx[queue_idx][user_pos] = used_idx; + env->tx_host_id[queue_idx][user_pos][used_idx_mod] = host_id; + // and exit the loop + break; + } + + REG_SW_SET_PROFILING_CHAN(env->pthis, SW_PROF_CHAN_CTXT_CFM_HDL_BIT); + REG_SW_CLEAR_PROFILING_CHAN(env->pthis, SW_PROF_CHAN_CTXT_CFM_HDL_BIT); + } + + REG_SW_CLEAR_PROFILING(env->pthis, SW_PROF_IRQ_E2A_TXCFM); +} + +/** + ****************************************************************************** + */ +bool ipc_host_tx_frames_pending(struct ipc_host_env_tag *env) +{ + int i, j; + bool tx_frames_pending = false; + + for (i = 0; (i < IPC_TXQUEUE_CNT) && !tx_frames_pending; i++) + { + for (j = 0; j < nx_txuser_cnt[i]; j++) + { + uint32_t used_idx = env->txdesc_used_idx[i][j]; + uint32_t free_idx = env->txdesc_free_idx[i][j]; + + // Check if this queue is empty or not + if (used_idx != free_idx) + { + // The queue is not empty, update the flag and exit + tx_frames_pending = true; + break; + } + } + } + + return (tx_frames_pending); +} + +/** + ****************************************************************************** + */ +void *ipc_host_tx_flush(struct ipc_host_env_tag *env, const int queue_idx, const int user_pos) +{ + uint32_t used_idx = env->txdesc_used_idx[queue_idx][user_pos]; + void *host_id = env->tx_host_id[queue_idx][user_pos][used_idx & nx_txdesc_cnt_msk[queue_idx]]; + + // call the external function to indicate that a TX packet is freed + if (host_id != 0) + { + // Reset the host id in the array + env->tx_host_id[queue_idx][user_pos][used_idx & nx_txdesc_cnt_msk[queue_idx]] = 0; + + // Increment the used index + env->txdesc_used_idx[queue_idx][user_pos]++; + } + + return (host_id); +} + +/** + ****************************************************************************** + */ +void ipc_host_init(struct ipc_host_env_tag *env, + struct ipc_host_cb_tag *cb, + struct ipc_shared_env_tag *shared_env_ptr, + void *pthis) +{ + unsigned int i; + unsigned int size; + unsigned int * dst; + + // Reset the environments + // Reset the IPC Shared memory +#if 0 + /* check potential platform bug on multiple stores */ + memset(shared_env_ptr, 0, sizeof(struct ipc_shared_env_tag)); +#else + dst = (unsigned int *)shared_env_ptr; + size = (unsigned int)sizeof(struct ipc_shared_env_tag); + for (i=0; i < size; i+=4) + { + *dst++ = 0; + } +#endif + // Reset the IPC Host environment + memset(env, 0, sizeof(struct ipc_host_env_tag)); + + // Initialize the shared environment pointer + env->shared = shared_env_ptr; + + // Save the callbacks in our own environment + env->cb = *cb; + + // Save the pointer to the register base + env->pthis = pthis; + + // Initialize buffers numbers and buffers sizes needed for DMA Receptions + env->rx_bufnb = IPC_RXBUF_CNT; + #ifdef CONFIG_ECRNX_FULLMAC + env->rxdesc_nb = IPC_RXDESC_CNT; + #endif //(CONFIG_ECRNX_FULLMAC) + env->radar_bufnb = IPC_RADARBUF_CNT; + env->radar_bufsz = sizeof(struct radar_pulse_array_desc); + env->unsuprxvec_bufnb = IPC_UNSUPRXVECBUF_CNT; + env->unsuprxvec_bufsz = max(sizeof(struct rx_vector_desc), (size_t) RADIOTAP_HDR_MAX_LEN) + + RADIOTAP_HDR_VEND_MAX_LEN + UNSUP_RX_VEC_DATA_LEN; + env->ipc_e2amsg_bufnb = IPC_MSGE2A_BUF_CNT; + env->ipc_e2amsg_bufsz = sizeof(struct ipc_e2a_msg); + env->ipc_dbg_bufnb = IPC_DBGBUF_CNT; + env->ipc_dbg_bufsz = sizeof(struct ipc_dbg_msg); + + for (i = 0; i < CONFIG_USER_MAX; i++) + { + // Initialize the pointers to the hostid arrays + env->tx_host_id[0][i] = env->tx_host_id0[i]; + env->tx_host_id[1][i] = env->tx_host_id1[i]; + env->tx_host_id[2][i] = env->tx_host_id2[i]; + env->tx_host_id[3][i] = env->tx_host_id3[i]; + #if NX_TXQ_CNT == 5 + env->tx_host_id[4][i] = NULL; + #endif + + // Initialize the pointers to the TX descriptor arrays + env->txdesc[0][i] = shared_env_ptr->txdesc0[i]; + env->txdesc[1][i] = shared_env_ptr->txdesc1[i]; + env->txdesc[2][i] = shared_env_ptr->txdesc2[i]; + env->txdesc[3][i] = shared_env_ptr->txdesc3[i]; + #if NX_TXQ_CNT == 5 + env->txdesc[4][i] = NULL; + #endif + } + + #if NX_TXQ_CNT == 5 + env->tx_host_id[4][0] = env->tx_host_id4[0]; + env->txdesc[4][0] = shared_env_ptr->txdesc4[0]; + #endif +} + +/** + ****************************************************************************** + */ +void ipc_host_patt_addr_push(struct ipc_host_env_tag *env, uint32_t addr) +{ + struct ipc_shared_env_tag *shared_env_ptr = env->shared; + + // Copy the address + shared_env_ptr->pattern_addr = addr; +} + +/** + ****************************************************************************** + */ +int ipc_host_rxbuf_push(struct ipc_host_env_tag *env, +#ifdef CONFIG_ECRNX_SOFTMAC + void *hostid, +#else + uint32_t hostid, +#endif + uint32_t hostbuf) +{ + struct ipc_shared_env_tag *shared_env_ptr = env->shared; + + REG_SW_CLEAR_HOSTBUF_IDX_PROFILING(env->pthis); + REG_SW_SET_HOSTBUF_IDX_PROFILING(env->pthis, env->ipc_host_rxbuf_idx); + +#ifdef CONFIG_ECRNX_FULLMAC + // Copy the hostbuf (DMA address) in the ipc shared memory + shared_env_ptr->host_rxbuf[env->ipc_host_rxbuf_idx].hostid = hostid; + shared_env_ptr->host_rxbuf[env->ipc_host_rxbuf_idx].dma_addr = hostbuf; +#else + // Save the hostid and the hostbuf in global array + env->ipc_host_rxbuf_array[env->ipc_host_rxbuf_idx].hostid = hostid; + env->ipc_host_rxbuf_array[env->ipc_host_rxbuf_idx].dma_addr = hostbuf; + + shared_env_ptr->host_rxbuf[env->ipc_host_rxbuf_idx] = hostbuf; +#endif //(CONFIG_ECRNX_FULLMAC) + + // Signal to the embedded CPU that at least one buffer is available + ipc_app2emb_trigger_set(shared_env_ptr, IPC_IRQ_A2E_RXBUF_BACK); + + // Increment the array index + env->ipc_host_rxbuf_idx = (env->ipc_host_rxbuf_idx +1)%IPC_RXBUF_CNT; + + return (0); +} + +#ifdef CONFIG_ECRNX_FULLMAC +/** + ****************************************************************************** + */ +int ipc_host_rxdesc_push(struct ipc_host_env_tag *env, void *hostid, + uint32_t hostbuf) +{ + struct ipc_shared_env_tag *shared_env_ptr = env->shared; + + // Reset the RX Descriptor DMA Address and increment the counter + env->ipc_host_rxdesc_array[env->ipc_host_rxdesc_idx].dma_addr = hostbuf; + env->ipc_host_rxdesc_array[env->ipc_host_rxdesc_idx].hostid = hostid; + + shared_env_ptr->host_rxdesc[env->ipc_host_rxdesc_idx].dma_addr = hostbuf; + + // Signal to the embedded CPU that at least one descriptor is available + ipc_app2emb_trigger_set(shared_env_ptr, IPC_IRQ_A2E_RXDESC_BACK); + + env->ipc_host_rxdesc_idx = (env->ipc_host_rxdesc_idx + 1) % IPC_RXDESC_CNT; + + return (0); +} +#endif /* CONFIG_ECRNX_FULLMAC */ + +/** + ****************************************************************************** + */ +int ipc_host_radarbuf_push(struct ipc_host_env_tag *env, void *hostid, + uint32_t hostbuf) +{ + struct ipc_shared_env_tag *shared_env_ptr = env->shared; + + // Save the hostid and the hostbuf in global array + env->ipc_host_radarbuf_array[env->ipc_host_radarbuf_idx].hostid = hostid; + env->ipc_host_radarbuf_array[env->ipc_host_radarbuf_idx].dma_addr = hostbuf; + + // Copy the hostbuf (DMA address) in the ipc shared memory + shared_env_ptr->radarbuf_hostbuf[env->ipc_host_radarbuf_idx] = hostbuf; + + // Increment the array index + env->ipc_host_radarbuf_idx = (env->ipc_host_radarbuf_idx +1)%IPC_RADARBUF_CNT; + + return (0); +} + +/** + ****************************************************************************** + */ + +int ipc_host_unsup_rx_vec_buf_push(struct ipc_host_env_tag *env, + void *hostid, + uint32_t hostbuf) +{ + struct ipc_shared_env_tag *shared_env_ptr = env->shared; + + env->ipc_host_unsuprxvecbuf_array[env->ipc_host_unsuprxvecbuf_idx].hostid = hostid; + env->ipc_host_unsuprxvecbuf_array[env->ipc_host_unsuprxvecbuf_idx].dma_addr = hostbuf; + + // Copy the hostbuf (DMA address) in the ipc shared memory + shared_env_ptr->unsuprxvecbuf_hostbuf[env->ipc_host_unsuprxvecbuf_idx] = hostbuf; + + // Increment the array index + env->ipc_host_unsuprxvecbuf_idx = (env->ipc_host_unsuprxvecbuf_idx + 1)%IPC_UNSUPRXVECBUF_CNT; + + return (0); +} + +/** + ****************************************************************************** + */ +int ipc_host_msgbuf_push(struct ipc_host_env_tag *env, void *hostid, + uint64_t hostbuf) +{ + struct ipc_shared_env_tag *shared_env_ptr = env->shared; + + // Save the hostid and the hostbuf in global array + env->ipc_host_msgbuf_array[env->ipc_host_msge2a_idx].hostid = hostid; + env->ipc_host_msgbuf_array[env->ipc_host_msge2a_idx].dma_addr = hostbuf; + + // Copy the hostbuf (DMA address) in the ipc shared memory + shared_env_ptr->msg_e2a_hostbuf_addr[env->ipc_host_msge2a_idx] = hostbuf; + + // Increment the array index + env->ipc_host_msge2a_idx = (env->ipc_host_msge2a_idx +1)%IPC_MSGE2A_BUF_CNT; + + return (0); +} + +/** + ****************************************************************************** + */ +int ipc_host_dbgbuf_push(struct ipc_host_env_tag *env, void *hostid, + uint64_t hostbuf) +{ + struct ipc_shared_env_tag *shared_env_ptr = env->shared; + + // Save the hostid and the hostbuf in global array + env->ipc_host_dbgbuf_array[env->ipc_host_dbg_idx].hostid = hostid; + env->ipc_host_dbgbuf_array[env->ipc_host_dbg_idx].dma_addr = hostbuf; + + // Copy the hostbuf (DMA address) in the ipc shared memory + shared_env_ptr->dbg_hostbuf_addr[env->ipc_host_dbg_idx] = hostbuf; + + // Increment the array index + env->ipc_host_dbg_idx = (env->ipc_host_dbg_idx +1)%IPC_DBGBUF_CNT; + + return (0); +} + +/** + ****************************************************************************** + */ +void ipc_host_dbginfobuf_push(struct ipc_host_env_tag *env, uint32_t infobuf) +{ + struct ipc_shared_env_tag *shared_env_ptr = env->shared; + + // Copy the hostbuf (DMA address) in the ipc shared memory + shared_env_ptr->la_dbginfo_addr = infobuf; +} + +/** + ****************************************************************************** + */ +volatile struct txdesc_host *ipc_host_txdesc_get(struct ipc_host_env_tag *env, const int queue_idx, const int user_pos) +{ + volatile struct txdesc_host *txdesc_free; + uint32_t used_idx = env->txdesc_used_idx[queue_idx][user_pos]; + uint32_t free_idx = env->txdesc_free_idx[queue_idx][user_pos]; + + ASSERT_ERR(queue_idx < IPC_TXQUEUE_CNT); + ASSERT_ERR((free_idx - used_idx) <= nx_txdesc_cnt[queue_idx]); + + // Check if a free descriptor is available + if (free_idx != (used_idx + nx_txdesc_cnt[queue_idx])) + { + // Get the pointer to the first free descriptor + txdesc_free = env->txdesc[queue_idx][user_pos] + (free_idx & nx_txdesc_cnt_msk[queue_idx]); + } + else + { + txdesc_free = NULL; + } + + return txdesc_free; +} + +/** + ****************************************************************************** + */ +void ipc_host_txdesc_push(struct ipc_host_env_tag *env, const int queue_idx, + const int user_pos, void *host_id) +{ + uint32_t free_idx = env->txdesc_free_idx[queue_idx][user_pos] & nx_txdesc_cnt_msk[queue_idx]; + volatile struct txdesc_host *txdesc_pushed = env->txdesc[queue_idx][user_pos] + free_idx; + + + // Descriptor is now ready + txdesc_pushed->ready = 0xFFFFFFFF; + + // Save the host id in the environment + env->tx_host_id[queue_idx][user_pos][free_idx] = host_id; + + // Increment the index + env->txdesc_free_idx[queue_idx][user_pos]++; + + // trigger interrupt!!! + //REG_SW_SET_PROFILING(env->pthis, CO_BIT(queue_idx+SW_PROF_IRQ_A2E_TXDESC_FIRSTBIT)); + ipc_app2emb_trigger_setf(env->shared, CO_BIT(user_pos + queue_idx * CONFIG_USER_MAX + + IPC_IRQ_A2E_TXDESC_FIRSTBIT)); +} + +/** + ****************************************************************************** + */ +void ipc_host_irq(struct ipc_host_env_tag *env, uint32_t status) +{ + // Acknowledge the pending interrupts + ipc_emb2app_ack_clear(env->shared, status); + // And re-read the status, just to be sure that the acknowledgment is + // effective when we start the interrupt handling + ipc_emb2app_status_get(env->shared); + + // Optimized for only one IRQ at a time + if (status & IPC_IRQ_E2A_RXDESC) + { + // handle the RX descriptor reception + ipc_host_rxdesc_handler(env); + } + if (status & IPC_IRQ_E2A_MSG_ACK) + { + ipc_host_msgack_handler(env); + } + if (status & IPC_IRQ_E2A_MSG) + { + ipc_host_msg_handler(env); + } + if (status & IPC_IRQ_E2A_TXCFM) + { + int i; + +#ifdef __KERNEL__ + spin_lock(&((struct ecrnx_hw *)env->pthis)->tx_lock); +#endif + // handle the TX confirmation reception + for (i = 0; i < IPC_TXQUEUE_CNT; i++) + { + int j = 0; +#ifdef CONFIG_ECRNX_MUMIMO_TX + for (; j < nx_txuser_cnt[i]; j++) +#endif + { + uint32_t q_bit = CO_BIT(j + i * CONFIG_USER_MAX + IPC_IRQ_E2A_TXCFM_POS); + if (status & q_bit) + { + // handle the confirmation + ipc_host_tx_cfm_handler(env, i, j); + } + } + } +#ifdef __KERNEL__ + spin_unlock(&((struct ecrnx_hw *)env->pthis)->tx_lock); +#endif + } + if (status & IPC_IRQ_E2A_RADAR) + { + // handle the radar event reception + ipc_host_radar_handler(env); + } + + if (status & IPC_IRQ_E2A_UNSUP_RX_VEC) + { + // handle the unsupported rx vector reception + ipc_host_unsup_rx_vec_handler(env); + } + + if (status & IPC_IRQ_E2A_DBG) + { + ipc_host_dbg_handler(env); + } + + if (status & IPC_IRQ_E2A_TBTT_PRIM) + { + env->cb.prim_tbtt_ind(env->pthis); + } + + if (status & IPC_IRQ_E2A_TBTT_SEC) + { + env->cb.sec_tbtt_ind(env->pthis); + } +} + +/** + ****************************************************************************** + */ +int ipc_host_msg_push(struct ipc_host_env_tag *env, void *msg_buf, uint16_t len) +{ +#ifndef CONFIG_ECRNX_ESWIN + int i; + uint32_t *src, *dst; +#endif + + REG_SW_SET_PROFILING(env->pthis, SW_PROF_IPC_MSGPUSH); + + ASSERT_ERR(!env->msga2e_hostid); + ASSERT_ERR(round_up(len, 4) <= sizeof(env->shared->msg_a2e_buf.msg)); + +#ifndef CONFIG_ECRNX_ESWIN + // Copy the message into the IPC MSG buffer +#ifdef __KERNEL__ + src = (uint32_t*)((struct ecrnx_cmd *)msg_buf)->a2e_msg; +#else + src = (uint32_t*) msg_buf; +#endif + dst = (uint32_t*)&(env->shared->msg_a2e_buf.msg); + + // Copy the message in the IPC queue + for (i=0; i<len; i+=4) + { + *dst++ = *src++; + } + + env->msga2e_hostid = msg_buf; + + // Trigger the irq to send the message to EMB + ipc_app2emb_trigger_set(env->shared, IPC_IRQ_A2E_MSG); +#else + env->msga2e_hostid = msg_buf; + ecrnx_msg_send(msg_buf, len); +#endif + + REG_SW_CLEAR_PROFILING(env->pthis, SW_PROF_IPC_MSGPUSH); + + return (0); +} + +/** + ****************************************************************************** + */ +void ipc_host_enable_irq(struct ipc_host_env_tag *env, uint32_t value) +{ + // Enable the handled interrupts + ipc_emb2app_unmask_set(env->shared, value); +} + +/** + ****************************************************************************** + */ +void ipc_host_disable_irq(struct ipc_host_env_tag *env, uint32_t value) +{ + // Enable the handled interrupts + ipc_emb2app_unmask_clear(env->shared, value); +} + +/** + ****************************************************************************** + */ +uint32_t ipc_host_get_status(struct ipc_host_env_tag *env) +{ + volatile uint32_t status; + + status = ipc_emb2app_status_get(env->shared); + + return status; +} + +/** + ****************************************************************************** + */ +uint32_t ipc_host_get_rawstatus(struct ipc_host_env_tag *env) +{ + volatile uint32_t rawstatus; + + rawstatus = ipc_emb2app_rawstatus_get(env->shared); + + return rawstatus; +} + diff --git a/drivers/net/wireless/eswin/ipc_host.h b/drivers/net/wireless/eswin/ipc_host.h new file mode 100644 index 000000000000..127be2ac7036 --- /dev/null +++ b/drivers/net/wireless/eswin/ipc_host.h @@ -0,0 +1,517 @@ +/** + ****************************************************************************** + * + * @file ipc_host.h + * + * @brief IPC module. + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ +#ifndef _IPC_HOST_H_ +#define _IPC_HOST_H_ + +/* + * INCLUDE FILES + ****************************************************************************** + */ +#include "ipc_shared.h" +#ifndef __KERNEL__ +#include "arch.h" +#else +#include "ipc_compat.h" +#endif + +/* + * ENUMERATION + ****************************************************************************** + */ + +enum ipc_host_desc_status +{ + /// Descriptor is IDLE + IPC_HOST_DESC_IDLE = 0, + /// Data can be forwarded + IPC_HOST_DESC_FORWARD, + /// Data has to be kept in UMAC memory + IPC_HOST_DESC_KEEP, + /// Delete stored packet + IPC_HOST_DESC_DELETE, + /// Update Frame Length status + IPC_HOST_DESC_LEN_UPDATE, +}; + +/** + ****************************************************************************** + * @brief This structure is used to initialize the MAC SW + * + * The WLAN device driver provides functions call-back with this structure + ****************************************************************************** + */ +struct ipc_host_cb_tag +{ + /// WLAN driver call-back function: send_data_cfm + int (*send_data_cfm)(void *pthis, void *host_id); + + /// WLAN driver call-back function: handle_data_cfm + int (*handle_data_cfm)(void *pthis, void *host_id); + + /// WLAN driver call-back function: recv_data_ind + uint8_t (*recv_data_ind)(void *pthis, void *host_id); + + /// WLAN driver call-back function: recv_radar_ind + uint8_t (*recv_radar_ind)(void *pthis, void *host_id); + + /// WLAN driver call-back function: recv_unsup_rx_vec_ind + uint8_t (*recv_unsup_rx_vec_ind)(void *pthis, void *host_id); + + /// WLAN driver call-back function: recv_msg_ind + uint8_t (*recv_msg_ind)(void *pthis, void *host_id); + + /// WLAN driver call-back function: recv_msgack_ind + uint8_t (*recv_msgack_ind)(void *pthis, void *host_id); + + /// WLAN driver call-back function: recv_dbg_ind + uint8_t (*recv_dbg_ind)(void *pthis, void *host_id); + + /// WLAN driver call-back function: prim_tbtt_ind + void (*prim_tbtt_ind)(void *pthis); + + /// WLAN driver call-back function: sec_tbtt_ind + void (*sec_tbtt_ind)(void *pthis); + +}; + +/* + * Struct used to store information about host buffers (DMA Address and local pointer) + */ +struct ipc_hostbuf +{ + void *hostid; ///< ptr to hostbuf client (ipc_host client) structure + uint64_t dma_addr; ///< ptr to real hostbuf dma address +}; + +/// Definition of the IPC Host environment structure. +struct ipc_host_env_tag +{ + /// Structure containing the callback pointers + struct ipc_host_cb_tag cb; + + /// Pointer to the shared environment + struct ipc_shared_env_tag *shared; + + #ifdef CONFIG_ECRNX_FULLMAC + // Array used to store the descriptor addresses + struct ipc_hostbuf ipc_host_rxdesc_array[IPC_RXDESC_CNT]; + // Index of the host RX descriptor array (ipc_shared environment) + uint8_t ipc_host_rxdesc_idx; + /// Store the number of RX Descriptors + uint8_t rxdesc_nb; + #endif //(CONFIG_ECRNX_FULLMAC) + + /// Fields for Data Rx handling + #ifdef CONFIG_ECRNX_SOFTMAC + // Global array used to store the hostid and hostbuf addresses + struct ipc_hostbuf ipc_host_rxbuf_array[IPC_RXBUF_CNT]; + #endif // CONFIG_ECRNX_SOFTMAC + // Index used for ipc_host_rxbuf_array to point to current buffer + uint8_t ipc_host_rxbuf_idx; + // Store the number of Rx Data buffers + uint32_t rx_bufnb; + // Store the size of the Rx Data buffers + uint32_t rx_bufsz; + + /// Fields for Radar events handling + // Global array used to store the hostid and hostbuf addresses + struct ipc_hostbuf ipc_host_radarbuf_array[IPC_RADARBUF_CNT]; + // Index used for ipc_host_rxbuf_array to point to current buffer + uint8_t ipc_host_radarbuf_idx; + // Store the number of radar event buffers + uint32_t radar_bufnb; + // Store the size of the radar event buffers + uint32_t radar_bufsz; + + ///Fields for Unsupported frame handling + // Global array used to store the hostid and hostbuf addresses + struct ipc_hostbuf ipc_host_unsuprxvecbuf_array[IPC_UNSUPRXVECBUF_CNT]; + // Index used for ipc_host_unsuprxvecbuf_array to point to current buffer + uint8_t ipc_host_unsuprxvecbuf_idx; + // Store the number of unsupported rx vector buffers + uint32_t unsuprxvec_bufnb; + // Store the size of unsupported rx vector buffers + uint32_t unsuprxvec_bufsz; + + // Index used that points to the first free TX desc + uint32_t txdesc_free_idx[IPC_TXQUEUE_CNT][CONFIG_USER_MAX]; + // Index used that points to the first used TX desc + uint32_t txdesc_used_idx[IPC_TXQUEUE_CNT][CONFIG_USER_MAX]; + // Array storing the currently pushed host ids for the BK queue + void *tx_host_id0[CONFIG_USER_MAX][NX_TXDESC_CNT0]; + // Array storing the currently pushed host ids for the BE queue + void *tx_host_id1[CONFIG_USER_MAX][NX_TXDESC_CNT1]; + // Array storing the currently pushed host ids for the VI queue + void *tx_host_id2[CONFIG_USER_MAX][NX_TXDESC_CNT2]; + // Array storing the currently pushed host ids for the VO queue + void *tx_host_id3[CONFIG_USER_MAX][NX_TXDESC_CNT3]; + #if NX_TXQ_CNT == 5 + // Array storing the currently pushed host ids for the BCN queue + void *tx_host_id4[1][NX_TXDESC_CNT4]; + #endif + // Pointer to the different host ids arrays, per IPC queue + void **tx_host_id[IPC_TXQUEUE_CNT][CONFIG_USER_MAX]; + // Pointer to the different TX descriptor arrays, per IPC queue + volatile struct txdesc_host *txdesc[IPC_TXQUEUE_CNT][CONFIG_USER_MAX]; + + /// Fields for Emb->App MSGs handling + // Global array used to store the hostid and hostbuf addresses for msg/ind + struct ipc_hostbuf ipc_host_msgbuf_array[IPC_MSGE2A_BUF_CNT]; + // Index of the MSG E2A buffers array to point to current buffer + uint8_t ipc_host_msge2a_idx; + // Store the number of E2A MSG buffers + uint32_t ipc_e2amsg_bufnb; + // Store the size of the E2A MSG buffers + uint32_t ipc_e2amsg_bufsz; + + /// E2A ACKs of A2E MSGs + uint8_t msga2e_cnt; + void *msga2e_hostid; + + /// Fields for Debug MSGs handling + // Global array used to store the hostid and hostbuf addresses for Debug messages + struct ipc_hostbuf ipc_host_dbgbuf_array[IPC_DBGBUF_CNT]; + // Index of the Debug messages buffers array to point to current buffer + uint8_t ipc_host_dbg_idx; + // Store the number of Debug messages buffers + uint32_t ipc_dbg_bufnb; + // Store the size of the Debug messages buffers + uint32_t ipc_dbg_bufsz; + + /// Pointer to the attached object (used in callbacks and register accesses) + void *pthis; +}; + +extern const int nx_txdesc_cnt[]; +extern const int nx_txuser_cnt[]; + +/** + ****************************************************************************** + * @brief Returns the full/not full status of the queue the index of which is + * passed as parameter. + * + * @param[in] env Pointer to the IPC host environment + * @param[in] queue_idx Index of the queue to be checked + * + * @return true if the queue is full, false otherwise + * + ****************************************************************************** + */ +__INLINE bool ipc_host_queue_full(struct ipc_host_env_tag *env, + const int queue_idx) +{ + return (env->txdesc_free_idx[queue_idx] == + (env->txdesc_used_idx[queue_idx] + nx_txdesc_cnt[queue_idx])); +} + +/** + ****************************************************************************** + * @brief Initialize the IPC running on the Application CPU. + * + * This function: + * - initializes the IPC software environments + * - enables the interrupts in the IPC block + * + * @param[in] env Pointer to the IPC host environment + * + * @warning Since this function resets the IPC Shared memory, it must be called + * before the LMAC FW is launched because LMAC sets some init values in IPC + * Shared memory at boot. + * + ****************************************************************************** + */ +void ipc_host_init(struct ipc_host_env_tag *env, + struct ipc_host_cb_tag *cb, + struct ipc_shared_env_tag *shared_env_ptr, + void *pthis); + +/** @addtogroup IPC_TX + * @{ + */ + +/** + ****************************************************************************** + * @brief Retrieve a new free Tx descriptor (host side). + * + * This function returns a pointer to the next Tx descriptor available from the + * queue queue_idx to the host driver. The driver will have to fill it with the + * appropriate endianness and to send it to the + * emb side with ipc_host_txdesc_push(). + * + * This function should only be called once until ipc_host_txdesc_push() is called. + * + * This function will return NULL if the queue is full. + * + * @param[in] env Pointer to the IPC host environment + * @param[in] queue_idx Queue index. The index can be inferred from the + * user priority of the incoming packet. + * @param[in] user_pos User position. If MU-MIMO is not used, this value + * shall be 0. + * @return Pointer to the next Tx descriptor free. This can + * point to the host memory or to shared memory, + * depending on IPC implementation. + * + ****************************************************************************** + */ +volatile struct txdesc_host *ipc_host_txdesc_get(struct ipc_host_env_tag *env, + const int queue_idx, + const int user_pos); + + +/** + ****************************************************************************** + * @brief Push a filled Tx descriptor (host side). + * + * This function sets the next Tx descriptor available by the host side: + * - as used for the host side + * - as available for the emb side. + * The Tx descriptor must be correctly filled before calling this function. + * + * This function may trigger an IRQ to the emb CPU depending on the interrupt + * mitigation policy and on the push count. + * + * @param[in] env Pointer to the IPC host environment + * @param[in] queue_idx Queue index. Same value than ipc_host_txdesc_get() + * @param[in] user_pos User position. If MU-MIMO is not used, this value + * shall be 0. + * @param[in] host_id Parameter indicated by the IPC at TX confirmation, + * that allows the driver finding the buffer + * + ****************************************************************************** + */ +void ipc_host_txdesc_push(struct ipc_host_env_tag *env, const int queue_idx, + const int user_pos, void *host_id); + + +/** + ****************************************************************************** + * @brief Check if there are TX frames pending in the TX queues. + * + * @param[in] env Pointer to the IPC host environment + * + * @return true if there are frames pending, false otherwise. + * + ****************************************************************************** + */ +bool ipc_host_tx_frames_pending(struct ipc_host_env_tag *env); + +/** + ****************************************************************************** + * @brief Get and flush a packet from the IPC queue passed as parameter. + * + * @param[in] env Pointer to the IPC host environment + * @param[in] queue_idx Index of the queue to flush + * @param[in] user_pos User position to flush + * + * @return The flushed hostid if there is one, 0 otherwise. + * + ****************************************************************************** + */ +void *ipc_host_tx_flush(struct ipc_host_env_tag *env, const int queue_idx, + const int user_pos); + +/// @} IPC_TX + +/** @addtogroup IPC_RX + * @{ + */ +void ipc_host_patt_addr_push(struct ipc_host_env_tag *env, uint32_t addr); + +/** + ****************************************************************************** + * @brief Push a pre-allocated buffer descriptor for Rx packet (host side) + * + * This function should be called by the host IRQ handler to supply the + * embedded side with new empty buffer. + * + * @param[in] env Pointer to the IPC host environment + * @param[in] hostid Packet ID used by the host (skbuff pointer on Linux) + * @param[in] hostbuf Pointer to the start of the buffer payload in the + * host memory (this may be inferred from the skbuff?) + * The length of this buffer should be predefined + * between host and emb statically (constant needed?). + * + ****************************************************************************** + */ +int ipc_host_rxbuf_push(struct ipc_host_env_tag *env, +#ifdef CONFIG_ECRNX_SOFTMAC + void *hostid, +#else + uint32_t hostid, +#endif + uint32_t hostbuf); + +/** + ****************************************************************************** + * @brief Push a pre-allocated Descriptor + * + * This function should be called by the host IRQ handler to supply the + * embedded side with new empty buffer. + * + * @param[in] env Pointer to the IPC host environment + * @param[in] hostid Address of packet for host + * @param[in] hostbuf Pointer to the start of the buffer payload in the + * host memory. The length of this buffer should be + * predefined between host and emb statically. + * + ****************************************************************************** + */ +int ipc_host_rxdesc_push(struct ipc_host_env_tag *env, void *hostid, + uint32_t hostbuf); + +/** + ****************************************************************************** + * @brief Push a pre-allocated radar event buffer descriptor + * + * This function is called at Init time to initialize all radar event buffers. + * Then each time embedded send a radar event, this function is used to push + * back the same buffer once it has been handled. + * + * @param[in] env Pointer to the IPC host environment + * @param[in] hostid Address of packet for host + * @param[in] hostbuf Pointer to the start of the buffer payload in the + * host memory. The length of this buffer should be + * predefined between host and emb statically. + * + ****************************************************************************** + */ +int ipc_host_radarbuf_push(struct ipc_host_env_tag *env, void *hostid, + uint32_t hostbuf); + +/** + ****************************************************************************** + * @brief Push a pre-allocated unsupported rx vector buffer descriptor + * + * This function is called at Init time to initialize all unsupported rx vector + * buffers. Then each time the embedded sends a unsupported rx vector, this + * function is used to push a new unsupported rx vector buffer. + * + * @param[in] env Pointer to the IPC host environment + * @param[in] hostid Address of packet for host + * @param[in] hostbuf Pointer to the start of the buffer payload in the + * host memory. The length of this buffer should be + * predefined between host and emb statically. + * + ****************************************************************************** + */ +int ipc_host_unsup_rx_vec_buf_push(struct ipc_host_env_tag *env, void *hostid, + uint32_t hostbuf); + +/** + ****************************************************************************** + * @brief Push a pre-allocated buffer descriptor for IPC MSGs (host side) + * + * This function is called at Init time to initialize all Emb2App messages + * buffers. Then each time embedded send a IPC message, this function is used + * to push back the same buffer once it has been handled. + * + * @param[in] env Pointer to the IPC host environment + * @param[in] hostid Address of buffer for host + * @param[in] hostbuf Address of buffer for embedded + * The length of this buffer should be predefined + * between host and emb statically. + * + ****************************************************************************** + */ +int ipc_host_msgbuf_push(struct ipc_host_env_tag *env, void *hostid, + uint64_t hostbuf); + +/** + ****************************************************************************** + * @brief Push a pre-allocated buffer descriptor for Debug messages (host side) + * + * This function is called at Init time to initialize all debug messages. + * Then each time embedded send a debug message, this function is used to push + * back the same buffer once it has been handled. + * + * @param[in] env Pointer to the IPC host environment + * @param[in] hostid Address of buffer for host + * @param[in] hostbuf Address of buffer for embedded + * The length of this buffer should be predefined + * between host and emb statically. + * + ****************************************************************************** + */ +int ipc_host_dbgbuf_push(struct ipc_host_env_tag *env, void *hostid, + uint64_t hostbuf); + +/** + ****************************************************************************** + * @brief Push the pre-allocated logic analyzer and debug information buffer + * + * @param[in] env Pointer to the IPC host environment + * @param[in] infobuf Address of buffer for embedded + * The length of this buffer should be predefined + * between host and emb statically. + * + ****************************************************************************** + */ +void ipc_host_dbginfobuf_push(struct ipc_host_env_tag *env, uint32_t infobuf); + +/// @} IPC_RX + + + +/** @addtogroup IPC_MISC + * @{ + */ + +/** + ****************************************************************************** + * @brief Handle all IPC interrupts on the host side. + * + * The following interrupts should be handled: + * Tx confirmation, Rx buffer requests, Rx packet ready and kernel messages + * + * @param[in] env Pointer to the IPC host environment + * + ****************************************************************************** + */ +void ipc_host_irq(struct ipc_host_env_tag *env, uint32_t status); + +/** + ****************************************************************************** + * @brief Send a message to the embedded side + * + * @param[in] env Pointer to the IPC host environment + * @param[in] msg_buf Pointer to the message buffer + * @param[in] msg_len Length of the message to be transmitted + * + * @return Non-null value on failure + * + ****************************************************************************** + */ +int ipc_host_msg_push(struct ipc_host_env_tag *env, void *msg_buf, uint16_t len); + +/** + ****************************************************************************** + * @brief Enable IPC interrupts + * + * @param[in] env Global ipc_host environment pointer + * @param[in] value Bitfield of the interrupts to enable + * + * @warning After calling this function, IPC interrupts can be triggered at any + * time. Potentially, an interrupt could happen even before returning from the + * function if there is a request pending from the embedded side. + * + ****************************************************************************** + */ +void ipc_host_enable_irq(struct ipc_host_env_tag *env, uint32_t value); +void ipc_host_disable_irq(struct ipc_host_env_tag *env, uint32_t value); + +uint32_t ipc_host_get_status(struct ipc_host_env_tag *env); +uint32_t ipc_host_get_rawstatus(struct ipc_host_env_tag *env); + +/// @} IPC_MISC + + +#endif // _IPC_HOST_H_ diff --git a/drivers/net/wireless/eswin/ipc_shared.h b/drivers/net/wireless/eswin/ipc_shared.h new file mode 100644 index 000000000000..e6d7ebf17609 --- /dev/null +++ b/drivers/net/wireless/eswin/ipc_shared.h @@ -0,0 +1,836 @@ +/** + **************************************************************************************** + * + * @file ipc_shared.h + * + * @brief Shared data between both IPC modules. + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ + +#ifndef _IPC_SHARED_H_ +#define _IPC_SHARED_H_ + +/* + * INCLUDE FILES + **************************************************************************************** + */ +#include "ipc_compat.h" +#include "lmac_mac.h" + +/* + * DEFINES AND MACROS + **************************************************************************************** + */ +#define CO_BIT(pos) (1U<<(pos)) + +#define IPC_TXQUEUE_CNT NX_TXQ_CNT +#define NX_TXDESC_CNT0 8 +#define NX_TXDESC_CNT1 64 +#define NX_TXDESC_CNT2 64 +#define NX_TXDESC_CNT3 32 +#if NX_TXQ_CNT == 5 +#define NX_TXDESC_CNT4 8 +#endif + +/* + * Number of Host buffers available for Data Rx handling (through DMA) + */ +#define IPC_RXBUF_CNT 128 + +/* + * Number of shared descriptors available for Data RX handling + */ +#define IPC_RXDESC_CNT 128 + +/* + * Number of Host buffers available for Radar events handling (through DMA) + */ +#define IPC_RADARBUF_CNT 16 + +/* + * Number of Host buffers available for unsupported Rx vectors handling (through DMA) + */ +#define IPC_UNSUPRXVECBUF_CNT 8 + +/* + * Size of RxVector + */ +#define IPC_RXVEC_SIZE 16 + +/* + * Number of Host buffers available for Emb->App MSGs sending (through DMA) + */ +#ifdef CONFIG_ECRNX_SOFTMAC +#define IPC_MSGE2A_BUF_CNT 4 +#else +#define IPC_MSGE2A_BUF_CNT 64 +#endif +/* + * Number of Host buffers available for Debug Messages sending (through DMA) + */ +#define IPC_DBGBUF_CNT 32 + +/* + * Length used in MSGs structures + */ +#define IPC_A2E_MSG_BUF_SIZE 127 // size in 4-byte words +#ifdef CONFIG_ECRNX_SOFTMAC +#define IPC_E2A_MSG_SIZE_BASE 15 // size in 4-byte words +#else +#define IPC_E2A_MSG_SIZE_BASE 256 // size in 4-byte words +#endif + +#ifdef CONFIG_ECRNX_TL4 +#define IPC_E2A_MSG_PARAM_SIZE (IPC_E2A_MSG_SIZE_BASE + (IPC_E2A_MSG_SIZE_BASE / 2)) +#else +#define IPC_E2A_MSG_PARAM_SIZE IPC_E2A_MSG_SIZE_BASE +#endif + +/* + * Debug messages buffers size (in bytes) + */ +#define IPC_DBG_PARAM_SIZE 256 + +/* + * Define used for Rx hostbuf validity. + * This value should appear only when hostbuf was used for a Reception. + */ +#define RX_DMA_OVER_PATTERN 0xAAAAAA00 + +/* + * Define used for MSG buffers validity. + * This value will be written only when a MSG buffer is used for sending from Emb to App. + */ +#define IPC_MSGE2A_VALID_PATTERN 0xADDEDE2A + +/* + * Define used for Debug messages buffers validity. + * This value will be written only when a DBG buffer is used for sending from Emb to App. + */ +#define IPC_DBG_VALID_PATTERN 0x000CACA0 + +/* + * Length of the receive vectors, in bytes + */ +#define DMA_HDR_PHYVECT_LEN 36 + +/* + * Maximum number of payload addresses and lengths present in the descriptor + */ +#define NX_TX_PAYLOAD_MAX 6 + +/* + * Message struct/ID API version + */ +#define MSG_API_VER 25 + +/* + **************************************************************************************** + */ +// c.f LMAC/src/tx/tx_swdesc.h +/// Descriptor filled by the Host +struct hostdesc +{ +#ifdef CONFIG_ECRNX_SPLIT_TX_BUF + /// Pointers to packet payloads + u32_l packet_addr[NX_TX_PAYLOAD_MAX]; + /// Sizes of the MPDU/MSDU payloads + u16_l packet_len[NX_TX_PAYLOAD_MAX]; + /// Number of payloads forming the MPDU + u8_l packet_cnt; +#else + /// Pointer to packet payload + u32_l packet_addr[2]; + /// Size of the payload + u16_l packet_len; +#endif //(NX_AMSDU_TX) + +#ifdef CONFIG_ECRNX_FULLMAC + /// Address of the status descriptor in host memory (used for confirmation upload) + u32_l status_desc_addr[2]; + /// Destination Address + struct mac_addr eth_dest_addr; + /// Source Address + struct mac_addr eth_src_addr; + /// Ethernet Type + u16_l ethertype; + /// Buffer containing the PN to be used for this packet + u16_l pn[4]; + /// Sequence Number used for transmission of this MPDU + u16_l sn; + /// Timestamp of first transmission of this MPDU + u16_l timestamp; + /// TX flags + u16_l flags; +#else /* ! CONFIG_ECRNX_FULLMAC */ +#ifdef CONFIG_ECRNX_AGG_TX + ///Sequence Number for AMPDU MPDUs - for quick check if it's allowed within window + u16_l sn; +#endif /* CONFIG_ECRNX_AGG_TX */ + /// Padding between the buffer control structure and the MPDU in host memory + u8_l padding; +#endif /* CONFIG_ECRNX_FULLMAC */ + /// Packet TID (0xFF if not a QoS frame) + u8_l tid; + /// Interface Id + u8_l vif_idx; + /// Station Id (0xFF if station is unknown) + u8_l staid; +#ifdef CONFIG_ECRNX_MUMIMO_TX + /// MU-MIMO information (GroupId and User Position in the group) - The GroupId + /// is located on bits 0-5 and the User Position on bits 6-7. The GroupId value is set + /// to 63 if MU-MIMO shall not be used + u8_l mumimo_info; +#endif /* CONFIG_ECRNX_MUMIMO_TX */ +}; + +/// Descriptor filled by the UMAC +struct umacdesc +{ +#if defined CONFIG_ECRNX_SOFTMAC && defined CONFIG_ECRNX_BFMER + /** + * Flags from UMAC which do not fit with tx_hd.macctrlinfo2 format + * Bit 0 - Indicate if frame can be beamformed when sent as a singleton + */ + u16_l tx_flags; +#endif // defined CONFIG_ECRNX_SOFTMAC && defined CONFIG_ECRNX_BFMER +#ifdef CONFIG_ECRNX_AGG_TX + ///First Sequence Number of the BlockAck window + u16_l sn_win; + /// Flags from UMAC (match tx_hd.macctrlinfo2 format) + u32_l flags; + /// PHY related flags field - rate, GI type, BW type - filled by driver + u32_l phy_flags; +#endif //(CONFIG_ECRNX_AGG_TX) +}; + +struct txdesc_api +{ + /// Information provided by Host + struct hostdesc host; +#ifdef CONFIG_ECRNX_SOFTMAC + /// Information provided by UMAC + struct umacdesc umac; +#endif +}; + + +struct txdesc_host +{ + u32_l ready; + + /// API of the embedded part + struct txdesc_api api; +}; + +/// Comes from ipc_dma.h +/// Element in the pool of TX DMA bridge descriptors. +struct dma_desc +{ +#ifdef CONFIG_ECRNX_ESWIN + uint8_t split; +#endif + + /** Application subsystem address which is used as source address for DMA payload + * transfer*/ + u32_l src; + /** Points to the start of the embedded data buffer associated with this descriptor. + * This address acts as the destination address for the DMA payload transfer*/ + u32_l dest; + /// Complete length of the buffer in memory + u16_l length; + /// Control word for the DMA engine (e.g. for interrupt generation) + u16_l ctrl; + /// Pointer to the next element of the chained list + u32_l next; +}; + +// Comes from la.h +/// Length of the configuration data of a logic analyzer +#define LA_CONF_LEN 10 + +/// Structure containing the configuration data of a logic analyzer +struct la_conf_tag +{ + u32_l conf[LA_CONF_LEN]; + u32_l trace_len; + u32_l diag_conf; +}; + +/// Size of a logic analyzer memory +#define LA_MEM_LEN (1024 * 1024) + +/// Type of errors +enum +{ + /// Recoverable error, not requiring any action from Upper MAC + DBG_ERROR_RECOVERABLE = 0, + /// Fatal error, requiring Upper MAC to reset Lower MAC and HW and restart operation + DBG_ERROR_FATAL +}; + +/// Maximum length of the SW diag trace +#define DBG_SW_DIAG_MAX_LEN 1024 + +/// Maximum length of the error trace +#define DBG_ERROR_TRACE_SIZE 256 + +/// Number of MAC diagnostic port banks +#define DBG_DIAGS_MAC_MAX 48 + +/// Number of PHY diagnostic port banks +#define DBG_DIAGS_PHY_MAX 32 + +/// Maximum size of the RX header descriptor information in the debug dump +#define DBG_RHD_MEM_LEN (5 * 1024) + +/// Maximum size of the RX buffer descriptor information in the debug dump +#define DBG_RBD_MEM_LEN (5 * 1024) + +/// Maximum size of the TX header descriptor information in the debug dump +#define DBG_THD_MEM_LEN (10 * 1024) + +/// Structure containing the information about the PHY channel that is used +struct phy_channel_info +{ + /// PHY channel information 1 + u32_l info1; + /// PHY channel information 2 + u32_l info2; +}; + +/// Debug information forwarded to host when an error occurs +struct dbg_debug_info_tag +{ + /// Type of error (0: recoverable, 1: fatal) + u32_l error_type; + /// Pointer to the first RX Header Descriptor chained to the MAC HW + u32_l rhd; + /// Size of the RX header descriptor buffer + u32_l rhd_len; + /// Pointer to the first RX Buffer Descriptor chained to the MAC HW + u32_l rbd; + /// Size of the RX buffer descriptor buffer + u32_l rbd_len; + /// Pointer to the first TX Header Descriptors chained to the MAC HW + u32_l thd[NX_TXQ_CNT]; + /// Size of the TX header descriptor buffer + u32_l thd_len[NX_TXQ_CNT]; + /// MAC HW diag configuration + u32_l hw_diag; + /// Error message + u32_l error[DBG_ERROR_TRACE_SIZE/4]; + /// SW diag configuration length + u32_l sw_diag_len; + /// SW diag configuration + u32_l sw_diag[DBG_SW_DIAG_MAX_LEN/4]; + /// PHY channel information + struct phy_channel_info chan_info; + /// Embedded LA configuration + struct la_conf_tag la_conf; + /// MAC diagnostic port state + u16_l diags_mac[DBG_DIAGS_MAC_MAX]; + /// PHY diagnostic port state + u16_l diags_phy[DBG_DIAGS_PHY_MAX]; + /// MAC HW RX Header descriptor pointer + u32_l rhd_hw_ptr; + /// MAC HW RX Buffer descriptor pointer + u32_l rbd_hw_ptr; +}; + +/// Full debug dump that is forwarded to host in case of error +struct dbg_debug_dump_tag +{ + /// Debug information + struct dbg_debug_info_tag dbg_info; + + /// RX header descriptor memory + u32_l rhd_mem[DBG_RHD_MEM_LEN/4]; + + /// RX buffer descriptor memory + u32_l rbd_mem[DBG_RBD_MEM_LEN/4]; + + /// TX header descriptor memory + u32_l thd_mem[NX_TXQ_CNT][DBG_THD_MEM_LEN/4]; + + /// Logic analyzer memory + u32_l la_mem[LA_MEM_LEN/4]; +}; + + +/// Number of pulses in a radar event structure +#define RADAR_PULSE_MAX 4 + +/// Definition of an array of radar pulses +struct radar_pulse_array_desc +{ + /// Buffer containing the radar pulses + u32_l pulse[RADAR_PULSE_MAX]; + /// Index of the radar detection chain that detected those pulses + u32_l idx; + /// Number of valid pulses in the buffer + u32_l cnt; +}; + +/// Bit mapping inside a radar pulse element +struct radar_pulse { + s32_l freq:6; /** Freq (resolution is 2Mhz range is [-Fadc/4 .. Fadc/4]) */ + u32_l fom:4; /** Figure of Merit */ + u32_l len:6; /** Length of the current radar pulse (resolution is 2us) */ + u32_l rep:16; /** Time interval between the previous radar event + and the current one (in us) */ +}; + +/// Definition of a RX vector descriptor +struct rx_vector_desc +{ + /// PHY channel information + struct phy_channel_info phy_info; + + /// RX vector 1 + u32_l rx_vect1[IPC_RXVEC_SIZE/4]; + + /// Used to print a valid rx vector + u32_l pattern; +}; + +/// +struct rxdesc_tag +{ + /// Host Buffer Address + u32_l host_id; + /// Length + u32_l frame_len; + /// Status + u16_l status; +}; + +/** + **************************************************************************************** + * @defgroup IPC IPC + * @ingroup NXMAC + * @brief Inter Processor Communication module. + * + * The IPC module implements the protocol to communicate between the Host CPU + * and the Embedded CPU. + * + * @see http://en.wikipedia.org/wiki/Circular_buffer + * For more information about the ring buffer typical use and difficulties. + **************************************************************************************** + */ + + +/** + **************************************************************************************** + * @addtogroup IPC_TX IPC Tx path + * @ingroup IPC + * @brief IPC Tx path structures and functions + * + * A typical use case of the IPC Tx path API: + * @msc + * hscale = "2"; + * + * a [label=Driver], + * b [label="IPC host"], + * c [label="IPC emb"], + * d [label=Firmware]; + * + * --- [label="Tx descriptor queue example"]; + * a=>a [label="Driver receives a Tx packet from OS"]; + * a=>b [label="ipc_host_txdesc_get()"]; + * a<<b [label="struct txdesc_host *"]; + * a=>a [label="Driver fill the descriptor"]; + * a=>b [label="ipc_host_txdesc_push()"]; + * ... [label="(several Tx desc can be pushed)"]; + * b:>c [label="Tx desc queue filled IRQ"]; + * c=>>d [label="EDCA sub-scheduler callback"]; + * c<<d [label="Tx desc queue to pop"]; + * c=>>d [label="UMAC Tx desc callback"]; + * ... [label="(several Tx desc can be popped)"]; + * d=>d [label="Packets are sent or discarded"]; + * --- [label="Tx confirm queue example"]; + * c<=d [label="ipc_emb_txcfm_push()"]; + * c>>d [label="Request accepted"]; + * ... [label="(several Tx cfm can be pushed)"]; + * b<:c [label="Tx cfm queue filled IRQ"]; + * a<<=b [label="Driver's Tx Confirm callback"]; + * a=>b [label="ipc_host_txcfm_pop()"]; + * a<<b [label="struct ipc_txcfm"]; + * a<=a [label="Packets are freed by the driver"]; + * @endmsc + * + * @{ + **************************************************************************************** + */ + +/// @} IPC_TX + +/** + **************************************************************************************** + * @defgroup IPC_RX IPC Rx path + * @ingroup IPC + * @brief IPC Rx path functions and structures + * + * A typical use case of the IPC Rx path API: + * @msc + * hscale = "2"; + * + * a [label=Firmware], + * b [label="IPC emb"], + * c [label="IPC host"], + * d [label=Driver]; + * + * --- [label="Rx buffer and desc queues usage example"]; + * d=>c [label="ipc_host_rxbuf_push()"]; + * d=>c [label="ipc_host_rxbuf_push()"]; + * d=>c [label="ipc_host_rxbuf_push()"]; + * ... [label="(several Rx buffer are pushed)"]; + * a=>a [label=" Frame is received\n from the medium"]; + * a<<b [label="struct ipc_rxbuf"]; + * a=>a [label=" Firmware fill the buffer\n with received frame"]; + * a<<b [label="Push accepted"]; + * ... [label="(several Rx desc can be pushed)"]; + * b:>c [label="Rx desc queue filled IRQ"]; + * c=>>d [label="Driver Rx packet callback"]; + * c<=d [label="ipc_host_rxdesc_pop()"]; + * d=>d [label="Rx packet is handed \nover to the OS "]; + * ... [label="(several Rx desc can be poped)"]; + * --- [label="Rx buffer request exemple"]; + * b:>c [label="Low Rx buffer count IRQ"]; + * a<<b [label="struct ipc_rxbuf"]; + * c=>>d [label="Driver Rx buffer callback"]; + * d=>c [label="ipc_host_rxbuf_push()"]; + * d=>c [label="ipc_host_rxbuf_push()"]; + * d=>c [label="ipc_host_rxbuf_push()"]; + * ... [label="(several Rx buffer are pushed)"]; + * @endmsc + * + * @addtogroup IPC_RX + * @{ + **************************************************************************************** + */ + +/// @} IPC_RX + + + +/** + **************************************************************************************** + * @defgroup IPC_MISC IPC Misc + * @ingroup IPC + * @brief IPC miscellaneous functions + **************************************************************************************** + */ +/** IPC header structure. This structure is stored at the beginning of every IPC message. + * @warning This structure's size must NOT exceed 4 bytes in length. + */ +struct ipc_header +{ + /// IPC message type. + u16_l type; + /// IPC message size in number of bytes. + u16_l size; +}; + +struct ipc_msg_elt +{ + /// Message header (alignment forced on word size, see allocation in shared env). + struct ipc_header header __ALIGN4; +}; + +/// Message structure for MSGs from Emb to App +struct ipc_e2a_msg +{ + u16_l id; ///< Message id. + u16_l dummy_dest_id; + u16_l dummy_src_id; +#ifdef CONFIG_ECRNX_ESWIN + u8_l hostid[8]; +#endif + + u16_l param_len; ///< Parameter embedded struct length. + u32_l param[IPC_E2A_MSG_PARAM_SIZE]; ///< Parameter embedded struct. Must be word-aligned. + u32_l pattern; ///< Used to stamp a valid MSG buffer +}; + +/// Message structure for Debug messages from Emb to App +struct ipc_dbg_msg +{ + u32_l string[IPC_DBG_PARAM_SIZE/4]; ///< Debug string + u32_l pattern; ///< Used to stamp a valid buffer +}; + +/// Message structure for MSGs from App to Emb. +/// Actually a sub-structure will be used when filling the messages. +struct ipc_a2e_msg +{ + u32_l dummy_word; // used to cope with kernel message structure + u32_l msg[IPC_A2E_MSG_BUF_SIZE]; // body of the msg +}; + +struct ipc_shared_rx_buf +{ + /// < ptr to hostbuf client (ipc_host client) structure + u64_l hostid; + /// < ptr to real hostbuf dma address + u64_l dma_addr; +}; + +struct ipc_shared_rx_desc +{ + /// DMA Address + u64_l dma_addr; +}; + +/// Structure containing FW characteristics for compatibility checking +struct compatibility_tag { + /// Size of IPC shared memory + u16_l ipc_shared_size; + /// Message struct/ID API version + u16_l msg_api; + /// Version of IPC shared + u8_l ipc_shared_version; + /// Number of host buffers available for Emb->App MSGs sending + u8_l msge2a_buf_cnt; + /// Number of host buffers available for Debug Messages sending + u8_l dbgbuf_cnt; + /// Number of host buffers available for Radar events handling + u8_l radarbuf_cnt; + /// Number of host buffers available for unsupported Rx vectors handling + u8_l unsuprxvecbuf_cnt; + /// Number of shared descriptors available for Data RX handling + u8_l rxdesc_cnt; + /// Number of host buffers available for Data Rx handling + u8_l rxbuf_cnt; + /// Number of descriptors in BK TX queue (power of 2, min 4, max 64) + u8_l bk_txq; + /// Number of descriptors in BE TX queue (power of 2, min 4, max 64) + u8_l be_txq; + /// Number of descriptors in VI TX queue (power of 2, min 4, max 64) + u8_l vi_txq; + /// Number of descriptors in VO TX queue (power of 2, min 4, max 64) + u8_l vo_txq; + /// Number of descriptors in BCN TX queue (power of 2, min 4, max 64) + u8_l bcn_txq; +}; + +/* + * TYPE and STRUCT DEFINITIONS + **************************************************************************************** + */ + + +// Indexes are defined in the MIB shared structure +struct ipc_shared_env_tag +{ + volatile struct compatibility_tag comp_info; //FW characteristics + + volatile struct ipc_a2e_msg msg_a2e_buf; // room for MSG to be sent from App to Emb + + // Fields for MSGs sending from Emb to App + volatile struct ipc_e2a_msg msg_e2a_buf; // room to build the MSG to be DMA Xferred + volatile struct dma_desc msg_dma_desc; // DMA descriptor for Emb->App MSGs Xfers + volatile u32_l msg_e2a_hostbuf_addr [IPC_MSGE2A_BUF_CNT]; // buffers @ for DMA Xfers + + // Fields for Debug MSGs sending from Emb to App + volatile struct ipc_dbg_msg dbg_buf; // room to build the MSG to be DMA Xferred + volatile struct dma_desc dbg_dma_desc; // DMA descriptor for Emb->App MSGs Xfers + volatile u32_l dbg_hostbuf_addr [IPC_DBGBUF_CNT]; // buffers @ for MSGs DMA Xfers + volatile u32_l la_dbginfo_addr; // Host buffer address for the debug information + volatile u32_l pattern_addr; + volatile u32_l radarbuf_hostbuf [IPC_RADARBUF_CNT]; // buffers @ for Radar Events + volatile u32_l unsuprxvecbuf_hostbuf [IPC_UNSUPRXVECBUF_CNT]; // buffers @ for unsupported Rx vectors + volatile struct txdesc_host txdesc0[CONFIG_USER_MAX][NX_TXDESC_CNT0]; + volatile struct txdesc_host txdesc1[CONFIG_USER_MAX][NX_TXDESC_CNT1]; + volatile struct txdesc_host txdesc2[CONFIG_USER_MAX][NX_TXDESC_CNT2]; + volatile struct txdesc_host txdesc3[CONFIG_USER_MAX][NX_TXDESC_CNT3]; + #if NX_TXQ_CNT == 5 + volatile struct txdesc_host txdesc4[1][NX_TXDESC_CNT4]; + #endif + #ifdef CONFIG_ECRNX_FULLMAC + // RX Descriptors Array + volatile struct ipc_shared_rx_desc host_rxdesc[IPC_RXDESC_CNT]; + // RX Buffers Array + volatile struct ipc_shared_rx_buf host_rxbuf[IPC_RXBUF_CNT]; + #else + // buffers @ for Data Rx + volatile u32_l host_rxbuf[IPC_RXBUF_CNT]; + #endif /* CONFIG_ECRNX_FULLMAC */ + + u32_l buffered[NX_REMOTE_STA_MAX][TID_MAX]; + + volatile uint16_t trace_pattern; + volatile uint32_t trace_start; + volatile uint32_t trace_end; + volatile uint32_t trace_size; + volatile uint32_t trace_offset; + volatile uint32_t trace_nb_compo; + volatile uint32_t trace_offset_compo; +}; + +extern struct ipc_shared_env_tag ipc_shared_env; + + +/* + * TYPE and STRUCT DEFINITIONS + **************************************************************************************** + */ + +// IRQs from app to emb +/// Interrupts bits used for the TX descriptors of the AC queues +#ifdef CONFIG_ECRNX_MUMIMO_TX +#ifdef CONFIG_ECRNX_OLD_IPC +#error "MU-MIMO cannot be compiled for old IPC" +#endif +/// Interrupts bits used +#if CONFIG_USER_MAX > 3 +#define IPC_IRQ_A2E_USER_MSK 0xF +#elif CONFIG_USER_MAX > 2 +#define IPC_IRQ_A2E_USER_MSK 0x7 +#else +#define IPC_IRQ_A2E_USER_MSK 0x3 +#endif + +/// Offset of the interrupts for AC0 +#define IPC_IRQ_A2E_AC0_OFT 8 +/// Mask of the interrupts for AC0 +#define IPC_IRQ_A2E_AC0_MSK (IPC_IRQ_A2E_USER_MSK << IPC_IRQ_A2E_AC0_OFT) +/// Offset of the interrupts for AC1 +#define IPC_IRQ_A2E_AC1_OFT (IPC_IRQ_A2E_AC0_OFT + CONFIG_USER_MAX) +/// Mask of the interrupts for AC1 +#define IPC_IRQ_A2E_AC1_MSK (IPC_IRQ_A2E_USER_MSK << IPC_IRQ_A2E_AC1_OFT) +/// Offset of the interrupts for AC2 +#define IPC_IRQ_A2E_AC2_OFT (IPC_IRQ_A2E_AC1_OFT + CONFIG_USER_MAX) +/// Mask of the interrupts for AC2 +#define IPC_IRQ_A2E_AC2_MSK (IPC_IRQ_A2E_USER_MSK << IPC_IRQ_A2E_AC2_OFT) +/// Offset of the interrupts for AC3 +#define IPC_IRQ_A2E_AC3_OFT (IPC_IRQ_A2E_AC2_OFT + CONFIG_USER_MAX) +/// Mask of the interrupts for AC3 +#define IPC_IRQ_A2E_AC3_MSK (IPC_IRQ_A2E_USER_MSK << IPC_IRQ_A2E_AC3_OFT) +/// Offset of the interrupts for BCN +#define IPC_IRQ_A2E_BCN_OFT (IPC_IRQ_A2E_AC3_OFT + CONFIG_USER_MAX) +/// Mask of the interrupts for BCN +#define IPC_IRQ_A2E_BCN_MSK CO_BIT(IPC_IRQ_A2E_BCN_OFT) + +#define IPC_IRQ_A2E_AC_TXDESC (IPC_IRQ_A2E_AC0_MSK | IPC_IRQ_A2E_AC1_MSK | \ + IPC_IRQ_A2E_AC2_MSK | IPC_IRQ_A2E_AC3_MSK) + +/// Interrupts bits used for the TX descriptors of the BCN queue +#if NX_TXQ_CNT < 5 +#define IPC_IRQ_A2E_BCN_TXDESC 0 +#else +#define IPC_IRQ_A2E_BCN_TXDESC (0x01 << IPC_IRQ_A2E_BCN_OFT) +#endif + +/// IPC TX descriptor interrupt mask +#define IPC_IRQ_A2E_TXDESC (IPC_IRQ_A2E_AC_TXDESC | IPC_IRQ_A2E_BCN_TXDESC) +#else +/// IPC TX descriptor interrupt mask +#define IPC_IRQ_A2E_TXDESC 0xFF00 +#endif + +#define IPC_IRQ_A2E_TXDESC_FIRSTBIT (8) +#define IPC_IRQ_A2E_RXBUF_BACK CO_BIT(5) +#define IPC_IRQ_A2E_RXDESC_BACK CO_BIT(4) + +#define IPC_IRQ_A2E_MSG CO_BIT(1) +#define IPC_IRQ_A2E_DBG CO_BIT(0) + +#define IPC_IRQ_A2E_ALL (IPC_IRQ_A2E_TXDESC|IPC_IRQ_A2E_MSG|IPC_IRQ_A2E_DBG) + +// IRQs from emb to app +#define IPC_IRQ_E2A_TXCFM_POS 7 + +#ifdef CONFIG_ECRNX_MUMIMO_TX +#ifdef CONFIG_ECRNX_OLD_IPC +#error "MU-MIMO cannot be compiled for old IPC" +#endif +/// Interrupts bits used +#if CONFIG_USER_MAX > 3 +#define IPC_IRQ_E2A_USER_MSK 0xF +#elif CONFIG_USER_MAX > 2 +#define IPC_IRQ_E2A_USER_MSK 0x7 +#else +#define IPC_IRQ_E2A_USER_MSK 0x3 +#endif + +/// Offset of the interrupts for AC0 +#define IPC_IRQ_E2A_AC0_OFT IPC_IRQ_E2A_TXCFM_POS +/// Mask of the interrupts for AC0 +#define IPC_IRQ_E2A_AC0_MSK (IPC_IRQ_E2A_USER_MSK << IPC_IRQ_E2A_AC0_OFT) +/// Offset of the interrupts for AC1 +#define IPC_IRQ_E2A_AC1_OFT (IPC_IRQ_E2A_AC0_OFT + CONFIG_USER_MAX) +/// Mask of the interrupts for AC1 +#define IPC_IRQ_E2A_AC1_MSK (IPC_IRQ_E2A_USER_MSK << IPC_IRQ_E2A_AC1_OFT) +/// Offset of the interrupts for AC2 +#define IPC_IRQ_E2A_AC2_OFT (IPC_IRQ_E2A_AC1_OFT + CONFIG_USER_MAX) +/// Mask of the interrupts for AC2 +#define IPC_IRQ_E2A_AC2_MSK (IPC_IRQ_E2A_USER_MSK << IPC_IRQ_E2A_AC2_OFT) +/// Offset of the interrupts for AC3 +#define IPC_IRQ_E2A_AC3_OFT (IPC_IRQ_E2A_AC2_OFT + CONFIG_USER_MAX) +/// Mask of the interrupts for AC3 +#define IPC_IRQ_E2A_AC3_MSK (IPC_IRQ_E2A_USER_MSK << IPC_IRQ_E2A_AC3_OFT) +/// Offset of the interrupts for BCN +#define IPC_IRQ_E2A_BCN_OFT (IPC_IRQ_E2A_AC3_OFT + CONFIG_USER_MAX) +/// Mask of the interrupts for BCN +#define IPC_IRQ_E2A_BCN_MSK CO_BIT(IPC_IRQ_E2A_BCN_OFT) + +#define IPC_IRQ_E2A_AC_TXCFM (IPC_IRQ_E2A_AC0_MSK | IPC_IRQ_E2A_AC1_MSK | \ + IPC_IRQ_E2A_AC2_MSK | IPC_IRQ_E2A_AC3_MSK) + +/// Interrupts bits used for the TX descriptors of the BCN queue +#if NX_TXQ_CNT < 5 +#define IPC_IRQ_E2A_BCN_TXCFM 0 +#else +#define IPC_IRQ_E2A_BCN_TXCFM (0x01 << IPC_IRQ_E2A_BCN_OFT) +#endif + +/// IPC TX descriptor interrupt mask +#define IPC_IRQ_E2A_TXCFM (IPC_IRQ_E2A_AC_TXCFM | IPC_IRQ_E2A_BCN_TXCFM) + +#else + +#define IPC_IRQ_E2A_TXCFM ((1 << NX_TXQ_CNT) - 1 ) << IPC_IRQ_E2A_TXCFM_POS + +#endif /* CONFIG_ECRNX_MUMIMO_TX */ + +#define IPC_IRQ_E2A_UNSUP_RX_VEC CO_BIT(7) +#define IPC_IRQ_E2A_RADAR CO_BIT(6) +#define IPC_IRQ_E2A_TBTT_SEC CO_BIT(5) +#define IPC_IRQ_E2A_TBTT_PRIM CO_BIT(4) +#define IPC_IRQ_E2A_RXDESC CO_BIT(3) +#define IPC_IRQ_E2A_MSG_ACK CO_BIT(2) +#define IPC_IRQ_E2A_MSG CO_BIT(1) +#define IPC_IRQ_E2A_DBG CO_BIT(0) + +#define IPC_IRQ_E2A_ALL ( IPC_IRQ_E2A_TXCFM \ + | IPC_IRQ_E2A_RXDESC \ + | IPC_IRQ_E2A_MSG_ACK \ + | IPC_IRQ_E2A_MSG \ + | IPC_IRQ_E2A_DBG \ + | IPC_IRQ_E2A_TBTT_PRIM \ + | IPC_IRQ_E2A_TBTT_SEC \ + | IPC_IRQ_E2A_RADAR \ + | IPC_IRQ_E2A_UNSUP_RX_VEC) + +// FLAGS for RX desc +#define IPC_RX_FORWARD CO_BIT(1) +#define IPC_RX_INTRABSS CO_BIT(0) + + +// IPC message TYPE +enum +{ + IPC_MSG_NONE = 0, + IPC_MSG_WRAP, + IPC_MSG_KMSG, + + IPC_DBG_STRING, + +}; + +#endif // _IPC_SHARED_H_ + diff --git a/drivers/net/wireless/eswin/lmac_mac.h b/drivers/net/wireless/eswin/lmac_mac.h new file mode 100644 index 000000000000..bb31e8be83f4 --- /dev/null +++ b/drivers/net/wireless/eswin/lmac_mac.h @@ -0,0 +1,467 @@ +/** + **************************************************************************************** + * + * @file lmac_mac_types.h + * + * @brief MAC related definitions. + * + * Adapted from mac_types.h to used lmac_types.h instead of standard types + * eg: perl -pi -e '$_ =~ s/uint(\d{1,2})_t/u$1_l/g; \ + * $_ =~ s/int(\d{1,2})_t/s$1_l/g; \ + * $_ =~ s/CO_BIT/BIT/g;' lmac_mac.h + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ + +#ifndef LMAC_MAC_H_ +#define LMAC_MAC_H_ + +#include "lmac_types.h" + +/// Interface types +enum mac_vif_type +{ + /// ESS STA interface + VIF_STA, + /// IBSS STA interface + VIF_IBSS, + /// AP interface + VIF_AP, + /// Mesh Point interface + VIF_MESH_POINT, + /// Monitor interface + VIF_MONITOR, + /// Unknown type + VIF_UNKNOWN +}; + +/// MAC address length in bytes. +#define MAC_ADDR_LEN 6 + +/// MAC address structure. +struct mac_addr +{ + /// Array of 16-bit words that make up the MAC address. + u16_l array[MAC_ADDR_LEN/2]; +}; + +/// SSID maximum length. +#define MAC_SSID_LEN 32 + +/// SSID. +struct mac_ssid +{ + /// Actual length of the SSID. + u8_l length; + /// Array containing the SSID name. + u8_l array[MAC_SSID_LEN]; +}; + +/// BSS type +enum mac_bss_type +{ + INFRASTRUCTURE_MODE = 1, + INDEPENDENT_BSS_MODE, + ANY_BSS_MODE +}; + +/// Channel Band +enum mac_chan_band +{ + /// 2.4GHz Band + PHY_BAND_2G4, + /// 5GHz band + PHY_BAND_5G, + /// Number of bands + PHY_BAND_MAX, +}; + +/// Operating Channel Bandwidth +enum mac_chan_bandwidth +{ + /// 20MHz BW + PHY_CHNL_BW_20, + /// 40MHz BW + PHY_CHNL_BW_40, + /// 80MHz BW + PHY_CHNL_BW_80, + /// 160MHz BW + PHY_CHNL_BW_160, + /// 80+80MHz BW + PHY_CHNL_BW_80P80, + /// Reserved BW + PHY_CHNL_BW_OTHER, +}; + +/// max number of channels in the 2.4 GHZ band +#define MAC_DOMAINCHANNEL_24G_MAX 14 + +/// max number of channels in the 5 GHZ band +#define MAC_DOMAINCHANNEL_5G_MAX 28 + +/// Channel Flag +enum mac_chan_flags +{ + /// Cannot initiate radiation on this channel + CHAN_NO_IR = BIT(0), + /// Channel is not allowed + CHAN_DISABLED = BIT(1), + /// Radar detection required on this channel + CHAN_RADAR = BIT(2), +}; + +/// Primary Channel definition +struct mac_chan_def +{ + /// Frequency of the channel (in MHz) + u16_l freq; + /// RF band (@ref mac_chan_band) + u8_l band; + /// Additional information (@ref mac_chan_flags) + u8_l flags; + /// Max transmit power allowed on this channel (dBm) + s8_l tx_power; +}; + +/// Operating Channel +struct mac_chan_op +{ + /// Band (@ref mac_chan_band) + u8_l band; + /// Channel type (@ref mac_chan_bandwidth) + u8_l type; + /// Frequency for Primary 20MHz channel (in MHz) + u16_l prim20_freq; + /// Frequency center of the contiguous channel or center of Primary 80+80 (in MHz) + u16_l center1_freq; + /// Frequency center of the non-contiguous secondary 80+80 (in MHz) + u16_l center2_freq; + /// Max transmit power allowed on this channel (dBm) + s8_l tx_power; + /// Additional information (@ref mac_chan_flags) + u8_l flags; +}; + +/// Cipher suites (order is important as it is used by MACHW) +enum mac_cipher_suite +{ + /// 00-0F-AC 1 + MAC_CIPHER_WEP40 = 0, + /// 00-0F-AC 2 + MAC_CIPHER_TKIP = 1, + /// 00-0F-AC 4 + MAC_CIPHER_CCMP = 2, + /// 00-0F-AC 5 + MAC_CIPHER_WEP104 = 3, + /// 00-14-72 1 + MAC_CIPHER_WPI_SMS4 = 4, + /// 00-0F-AC 6 (aka AES_CMAC) + MAC_CIPHER_BIP_CMAC_128 = 5, + + // following cipher are not supported by MACHW + /// 00-0F-AC 08 + MAC_CIPHER_GCMP_128, + /// 00-0F-AC 09 + MAC_CIPHER_GCMP_256, + /// 00-0F-AC 10 + MAC_CIPHER_CCMP_256, + /// 00-0F-AC 11 + MAC_CIPHER_BIP_GMAC_128, + /// 00-0F-AC 12 + MAC_CIPHER_BIP_GMAC_256, + /// 00-0F-AC 13 + MAC_CIPHER_BIP_CMAC_256, + + MAC_CIPHER_INVALID = 0xFF +}; + +/// Authentication and Key Management suite +enum mac_akm_suite +{ + /// No security + MAC_AKM_NONE, + /// Pre RSN (WEP or WPA) + MAC_AKM_PRE_RSN, + /// 00-0F-AC 1 + MAC_AKM_8021X, + /// 00-0F-AC 2 + MAC_AKM_PSK, + /// 00-0F-AC 3 + MAC_AKM_FT_8021X, + /// 00-0F-AC 4 + MAC_AKM_FT_PSK, + /// 00-0F-AC 5 + MAC_AKM_8021X_SHA256, + /// 00-0F-AC 6 + MAC_AKM_PSK_SHA256, + /// 00-0F-AC 7 + MAC_AKM_TDLS, + /// 00-0F-AC 8 + MAC_AKM_SAE, + /// 00-0F-AC 9 + MAC_AKM_FT_OVER_SAE, + /// 00-0F-AC 11 + MAC_AKM_8021X_SUITE_B, + /// 00-0F-AC 12 + MAC_AKM_8021X_SUITE_B_192, + /// 00-0F-AC 14 + MAC_AKM_FILS_SHA256, + /// 00-0F-AC 15 + MAC_AKM_FILS_SHA384, + /// 00-0F-AC 16 + MAC_AKM_FT_FILS_SHA256, + /// 00-0F-AC 17 + MAC_AKM_FT_FILS_SHA384, + /// 00-0F-AC 18 + MAC_AKM_OWE, + + /// 00-14-72 1 + MAC_AKM_WAPI_CERT, + /// 00-14-72 2 + MAC_AKM_WAPI_PSK, +}; + +/// Scan result element, parsed from beacon or probe response frames. +struct mac_scan_result +{ + /// Scan result is valid + bool valid_flag; + /// Network BSSID. + struct mac_addr bssid; + /// Network name. + struct mac_ssid ssid; + /// Network type (@ref mac_bss_type). + u16_l bsstype; + /// Network channel. + struct mac_chan_def *chan; + /// Network beacon period (in TU). + u16_l beacon_period; + /// Capability information + u16_l cap_info; + /// Supported AKM (bit-field of @ref mac_akm_suite) + u32_l akm; + /// Group cipher (bit-field of @ref mac_cipher_suite) + u16_l group_cipher; + /// Group cipher (bit-field of @ref mac_cipher_suite) + u16_l pairwise_cipher; + /// RSSI of the scanned BSS (in dBm) + s8_l rssi; + /// Multi-BSSID index (0 if this is the reference (i.e. transmitted) BSSID) + u8_l multi_bssid_index; + /// Maximum BSSID indicator + u8_l max_bssid_indicator; +}; + +/// Legacy rate 802.11 definitions +enum mac_legacy_rates +{ + /// DSSS/CCK 1Mbps + MAC_RATE_1MBPS = 2, + /// DSSS/CCK 2Mbps + MAC_RATE_2MBPS = 4, + /// DSSS/CCK 5.5Mbps + MAC_RATE_5_5MBPS = 11, + /// OFDM 6Mbps + MAC_RATE_6MBPS = 12, + /// OFDM 9Mbps + MAC_RATE_9MBPS = 18, + /// DSSS/CCK 11Mbps + MAC_RATE_11MBPS = 22, + /// OFDM 12Mbps + MAC_RATE_12MBPS = 24, + /// OFDM 18Mbps + MAC_RATE_18MBPS = 36, + /// OFDM 24Mbps + MAC_RATE_24MBPS = 48, + /// OFDM 36Mbps + MAC_RATE_36MBPS = 72, + /// OFDM 48Mbps + MAC_RATE_48MBPS = 96, + /// OFDM 54Mbps + MAC_RATE_54MBPS = 108 +}; + +/// BSS Membership Selector definitions +enum mac_bss_membership +{ + /// HT PHY + MAC_BSS_MEMBERSHIP_HT_PHY = 127, + /// VHT PHY + MAC_BSS_MEMBERSHIP_VHT_PHY = 126, +}; + +/// MAC rateset maximum length +#define MAC_RATESET_LEN 12 + +/// Structure containing the legacy rateset of a station +struct mac_rateset +{ + /// Number of legacy rates supported + u8_l length; + /// Array of legacy rates + u8_l array[MAC_RATESET_LEN]; +}; + +/// MAC Security Key maximum length +#define MAC_SEC_KEY_LEN 32 // TKIP keys 256 bits (max length) with MIC keys + +/// Structure defining a security key +struct mac_sec_key +{ + /// Key material length + u8_l length; + /// Key material + u32_l array[MAC_SEC_KEY_LEN/4]; +}; + +/// Access Category enumeration +enum mac_ac +{ + /// Background + AC_BK = 0, + /// Best-effort + AC_BE, + /// Video + AC_VI, + /// Voice + AC_VO, + /// Number of access categories + AC_MAX +}; + +/// Traffic ID enumeration +enum mac_tid +{ + /// TID_0. Mapped to @ref AC_BE as per 802.11 standard. + TID_0, + /// TID_1. Mapped to @ref AC_BK as per 802.11 standard. + TID_1, + /// TID_2. Mapped to @ref AC_BK as per 802.11 standard. + TID_2, + /// TID_3. Mapped to @ref AC_BE as per 802.11 standard. + TID_3, + /// TID_4. Mapped to @ref AC_VI as per 802.11 standard. + TID_4, + /// TID_5. Mapped to @ref AC_VI as per 802.11 standard. + TID_5, + /// TID_6. Mapped to @ref AC_VO as per 802.11 standard. + TID_6, + /// TID_7. Mapped to @ref AC_VO as per 802.11 standard. + TID_7, + /// Non standard Management TID used internally + TID_MGT, + /// Number of TID supported + TID_MAX +}; + +/// MCS bitfield maximum size (in bytes) +#define MAX_MCS_LEN 16 // 16 * 8 = 128 + +/// MAC HT capability information element +struct mac_htcapability +{ + /// HT capability information + u16_l ht_capa_info; + /// A-MPDU parameters + u8_l a_mpdu_param; + /// Supported MCS + u8_l mcs_rate[MAX_MCS_LEN]; + /// HT extended capability information + u16_l ht_extended_capa; + /// Beamforming capability information + u32_l tx_beamforming_capa; + /// Antenna selection capability information + u8_l asel_capa; +}; + +/// MAC VHT capability information element +struct mac_vhtcapability +{ + /// VHT capability information + u32_l vht_capa_info; + /// RX MCS map + u16_l rx_mcs_map; + /// RX highest data rate + u16_l rx_highest; + /// TX MCS map + u16_l tx_mcs_map; + /// TX highest data rate + u16_l tx_highest; +}; + +/// Length (in bytes) of the MAC HE capability field +#define MAC_HE_MAC_CAPA_LEN 6 +/// Length (in bytes) of the PHY HE capability field +#define MAC_HE_PHY_CAPA_LEN 11 +/// Maximum length (in bytes) of the PPE threshold data +#define MAC_HE_PPE_THRES_MAX_LEN 25 + +/// Structure listing the per-NSS, per-BW supported MCS combinations +struct mac_he_mcs_nss_supp +{ + /// per-NSS supported MCS in RX, for BW <= 80MHz + u16_l rx_mcs_80; + /// per-NSS supported MCS in TX, for BW <= 80MHz + u16_l tx_mcs_80; + /// per-NSS supported MCS in RX, for BW = 160MHz + u16_l rx_mcs_160; + /// per-NSS supported MCS in TX, for BW = 160MHz + u16_l tx_mcs_160; + /// per-NSS supported MCS in RX, for BW = 80+80MHz + u16_l rx_mcs_80p80; + /// per-NSS supported MCS in TX, for BW = 80+80MHz + u16_l tx_mcs_80p80; +}; + +/// MAC HE capability information element +struct mac_hecapability +{ + /// MAC HE capabilities + u8_l mac_cap_info[MAC_HE_MAC_CAPA_LEN]; + /// PHY HE capabilities + u8_l phy_cap_info[MAC_HE_PHY_CAPA_LEN]; + /// Supported MCS combinations + struct mac_he_mcs_nss_supp mcs_supp; + /// PPE Thresholds data + u8_l ppe_thres[MAC_HE_PPE_THRES_MAX_LEN]; +}; + +/// Station flags +enum mac_sta_flags +{ + /// Bit indicating that a STA has QoS (WMM) capability + STA_QOS_CAPA = BIT(0), + /// Bit indicating that a STA has HT capability + STA_HT_CAPA = BIT(1), + /// Bit indicating that a STA has VHT capability + STA_VHT_CAPA = BIT(2), + /// Bit indicating that a STA has MFP capability + STA_MFP_CAPA = BIT(3), + /// Bit indicating that the STA included the Operation Notification IE + STA_OPMOD_NOTIF = BIT(4), + /// Bit indicating that a STA has HE capability + STA_HE_CAPA = BIT(5), + /// Bit Inidcating supprot for short Preamble in ERP + STA_SHORT_PREAMBLE_CAPA = BIT(6), +}; + +/// Connection flags +enum mac_connection_flags +{ + /// Flag indicating whether the control port is controlled by host or not + CONTROL_PORT_HOST = BIT(0), + /// Flag indicating whether the control port frame shall be sent unencrypted + CONTROL_PORT_NO_ENC = BIT(1), + /// Flag indicating whether HT and VHT shall be disabled or not + DISABLE_HT = BIT(2), + /// Flag indicating whether WPA or WPA2 authentication is in use + WPA_WPA2_IN_USE = BIT(3), + /// Flag indicating whether MFP is in use + MFP_IN_USE = BIT(4), + REASSOCIATION = BIT(5), + FT_OVER_DS = BIT(6), +}; + +#endif // LMAC_MAC_H_ diff --git a/drivers/net/wireless/eswin/lmac_msg.h b/drivers/net/wireless/eswin/lmac_msg.h new file mode 100644 index 000000000000..1e1fe7fca757 --- /dev/null +++ b/drivers/net/wireless/eswin/lmac_msg.h @@ -0,0 +1,2767 @@ +/** + **************************************************************************************** + * + * @file lmac_msg.h + * + * @brief Main definitions for message exchanges with LMAC + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ + +#ifndef LMAC_MSG_H_ +#define LMAC_MSG_H_ + +/* + * INCLUDE FILES + **************************************************************************************** + */ +// for MAC related elements (mac_addr, mac_ssid...) +#include "lmac_mac.h" + + +#if defined(CONFIG_STANDALONE_WIFI) && defined(CONFIG_STANDALONE_WIFI_BLE) +#error biuld mode error, wifi or wifi_ble must choice one! +#endif + +/* + **************************************************************************************** + */ +///////////////////////////////////////////////////////////////////////////////// +// COMMUNICATION WITH LMAC LAYER +///////////////////////////////////////////////////////////////////////////////// +/* Task identifiers for communication between LMAC and DRIVER */ +enum +{ + TASK_NONE = (u8_l) -1, +#if defined(CONFIG_STANDALONE_WIFI) + // MAC Management task. + TASK_MM = 0, +#elif defined(CONFIG_STANDALONE_WIFI_BLE) + TASK_MM = 32, +#else +#error #error not defined biuld mode! +#endif + // DEBUG task + TASK_DBG, + /// SCAN task + TASK_SCAN, +#ifdef CONFIG_CEVA_RTOS + TASK_CLI, +#endif + /// TDLS task + TASK_TDLS, + /// SCANU task + TASK_SCANU, + /// ME task + TASK_ME, + /// SM task + TASK_SM, + /// APM task + TASK_APM, + /// BAM task + TASK_BAM, + /// MESH task + TASK_MESH, + /// RXU task + TASK_RXU, + TASK_RM, +#if defined(CONFIG_ECRNX_P2P) + TASK_P2P_LISTEN, +#endif + TASK_TWT, +#if defined CONFIG_ECRNX_SOFTMAC + // This is used to define the last task that is running on the EMB processor + TASK_LAST_EMB = TASK_TDLS, +#elif defined CONFIG_ECRNX_FULLMAC || defined CONFIG_ECRNX_FHOST + // This is used to define the last task that is running on the EMB processor + TASK_LAST_EMB = TASK_TWT, +#else +#error "Need to define SOFTMAC or FULLMAC" +#endif + // nX API task + TASK_API, + TASK_MAX, +}; + + +/// For MAC HW States copied from "hal_machw.h" +enum +{ + /// MAC HW IDLE State. + HW_IDLE = 0, + /// MAC HW RESERVED State. + HW_RESERVED, + /// MAC HW DOZE State. + HW_DOZE, + /// MAC HW ACTIVE State. + HW_ACTIVE +}; + +/// Power Save mode setting +enum mm_ps_mode_state +{ + MM_PS_MODE_OFF, + MM_PS_MODE_ON, + MM_PS_MODE_ON_DYN, +}; + +/// Status/error codes used in the MAC software. +enum +{ + CO_OK, + CO_FAIL, + CO_EMPTY, + CO_FULL, + CO_BAD_PARAM, + CO_NOT_FOUND, + CO_NO_MORE_ELT_AVAILABLE, + CO_NO_ELT_IN_USE, + CO_BUSY, + CO_OP_IN_PROGRESS, +}; + +/// Remain on channel operation codes +enum mm_remain_on_channel_op +{ + MM_ROC_OP_START = 0, + MM_ROC_OP_CANCEL, +}; + +#define DRV_TASK_ID 100 + +/// Message Identifier. The number of messages is limited to 0xFFFF. +/// The message ID is divided in two parts: +/// - bits[15..10] : task index (no more than 64 tasks supported). +/// - bits[9..0] : message index (no more that 1024 messages per task). +typedef u16 lmac_msg_id_t; + +typedef u16 lmac_task_id_t; + +/// Build the first message ID of a task. +#if defined(CONFIG_STANDALONE_WIFI) +#define LMAC_FIRST_MSG(task) ((lmac_msg_id_t)((task) << 10)) + +#define MSG_T(msg) ((lmac_task_id_t)((msg) >> 10)) +#define MSG_I(msg) ((msg) & ((1<<10)-1)) + +#elif defined(CONFIG_STANDALONE_WIFI_BLE) +#define LMAC_FIRST_MSG(task) ((lmac_msg_id_t)((task) << 8)) + +#define MSG_T(msg) ((lmac_task_id_t)((msg) >> 8)) +#define MSG_I(msg) ((msg) & ((1<<8)-1)) +#else +#error not defined biuld mode! +#endif + +/// Message structure. +struct lmac_msg +{ + lmac_msg_id_t id; ///< Message id. + lmac_task_id_t dest_id; ///< Destination kernel identifier. + lmac_task_id_t src_id; ///< Source kernel identifier. +#ifdef CONFIG_ECRNX_ESWIN + uint8_t hostid[8]; +#endif + + u16 param_len; ///< Parameter embedded struct length. + u32 param[]; ///< Parameter embedded struct. Must be word-aligned. +}; + +/// List of messages related to the task. +enum mm_msg_tag +{ + /// RESET Request. + MM_RESET_REQ = LMAC_FIRST_MSG(TASK_MM), + /// RESET Confirmation. + MM_RESET_CFM, + /// START Request. + MM_START_REQ, + /// START Confirmation. + MM_START_CFM, + /// Read Version Request. + MM_VERSION_REQ, + /// Read Version Confirmation. + MM_VERSION_CFM, + /// ADD INTERFACE Request. + MM_ADD_IF_REQ, + /// ADD INTERFACE Confirmation. + MM_ADD_IF_CFM, + /// REMOVE INTERFACE Request. + MM_REMOVE_IF_REQ, + /// REMOVE INTERFACE Confirmation. + MM_REMOVE_IF_CFM, + /// STA ADD Request. + MM_STA_ADD_REQ, + /// STA ADD Confirm. + MM_STA_ADD_CFM, + /// STA DEL Request. + MM_STA_DEL_REQ, + /// STA DEL Confirm. + MM_STA_DEL_CFM, + /// RX FILTER CONFIGURATION Request. + MM_SET_FILTER_REQ, + /// RX FILTER CONFIGURATION Confirmation. + MM_SET_FILTER_CFM, + /// CHANNEL CONFIGURATION Request. + MM_SET_CHANNEL_REQ, + /// CHANNEL CONFIGURATION Confirmation. + MM_SET_CHANNEL_CFM, + /// DTIM PERIOD CONFIGURATION Request. + MM_SET_DTIM_REQ, + /// DTIM PERIOD CONFIGURATION Confirmation. + MM_SET_DTIM_CFM, + /// BEACON INTERVAL CONFIGURATION Request. + MM_SET_BEACON_INT_REQ, + /// BEACON INTERVAL CONFIGURATION Confirmation. + MM_SET_BEACON_INT_CFM, + /// BASIC RATES CONFIGURATION Request. + MM_SET_BASIC_RATES_REQ, + /// BASIC RATES CONFIGURATION Confirmation. + MM_SET_BASIC_RATES_CFM, + /// BSSID CONFIGURATION Request. + MM_SET_BSSID_REQ, + /// BSSID CONFIGURATION Confirmation. + MM_SET_BSSID_CFM, + /// EDCA PARAMETERS CONFIGURATION Request. + MM_SET_EDCA_REQ, + /// EDCA PARAMETERS CONFIGURATION Confirmation. + MM_SET_EDCA_CFM, + /// ABGN MODE CONFIGURATION Request. + MM_SET_MODE_REQ, + /// ABGN MODE CONFIGURATION Confirmation. + MM_SET_MODE_CFM, + /// Request setting the VIF active state (i.e associated or AP started) + MM_SET_VIF_STATE_REQ, + /// Confirmation of the @ref MM_SET_VIF_STATE_REQ message. + MM_SET_VIF_STATE_CFM, + /// SLOT TIME PARAMETERS CONFIGURATION Request. + MM_SET_SLOTTIME_REQ, + /// SLOT TIME PARAMETERS CONFIGURATION Confirmation. + MM_SET_SLOTTIME_CFM, + /// Power Mode Change Request. + MM_SET_IDLE_REQ, + /// Power Mode Change Confirm. + MM_SET_IDLE_CFM, + /// KEY ADD Request. + MM_KEY_ADD_REQ, + /// KEY ADD Confirm. + MM_KEY_ADD_CFM, + /// KEY DEL Request. + MM_KEY_DEL_REQ, + /// KEY DEL Confirm. + MM_KEY_DEL_CFM, + /// Block Ack agreement info addition + MM_BA_ADD_REQ, + /// Block Ack agreement info addition confirmation + MM_BA_ADD_CFM, + /// Block Ack agreement info deletion + MM_BA_DEL_REQ, + /// Block Ack agreement info deletion confirmation + MM_BA_DEL_CFM, + /// Indication of the primary TBTT to the upper MAC. Upon the reception of this + // message the upper MAC has to push the beacon(s) to the beacon transmission queue. + MM_PRIMARY_TBTT_IND, + /// Indication of the secondary TBTT to the upper MAC. Upon the reception of this + // message the upper MAC has to push the beacon(s) to the beacon transmission queue. + MM_SECONDARY_TBTT_IND, + /// Request for changing the TX power + MM_SET_POWER_REQ, + /// Confirmation of the TX power change + MM_SET_POWER_CFM, + /// Request to the LMAC to trigger the embedded logic analyzer and forward the debug + /// dump. + MM_DBG_TRIGGER_REQ, + /// Set Power Save mode + MM_SET_PS_MODE_REQ, + /// Set Power Save mode confirmation + MM_SET_PS_MODE_CFM, + /// Request to add a channel context + MM_CHAN_CTXT_ADD_REQ, + /// Confirmation of the channel context addition + MM_CHAN_CTXT_ADD_CFM, + /// Request to delete a channel context + MM_CHAN_CTXT_DEL_REQ, + /// Confirmation of the channel context deletion + MM_CHAN_CTXT_DEL_CFM, + /// Request to link a channel context to a VIF + MM_CHAN_CTXT_LINK_REQ, + /// Confirmation of the channel context link + MM_CHAN_CTXT_LINK_CFM, + /// Request to unlink a channel context from a VIF + MM_CHAN_CTXT_UNLINK_REQ, + /// Confirmation of the channel context unlink + MM_CHAN_CTXT_UNLINK_CFM, + /// Request to update a channel context + MM_CHAN_CTXT_UPDATE_REQ, + /// Confirmation of the channel context update + MM_CHAN_CTXT_UPDATE_CFM, + /// Request to schedule a channel context + MM_CHAN_CTXT_SCHED_REQ, + /// Confirmation of the channel context scheduling + MM_CHAN_CTXT_SCHED_CFM, + /// Request to change the beacon template in LMAC + MM_BCN_CHANGE_REQ, + /// Confirmation of the beacon change + MM_BCN_CHANGE_CFM, + /// Request to update the TIM in the beacon (i.e to indicate traffic bufferized at AP) + MM_TIM_UPDATE_REQ, + /// Confirmation of the TIM update + MM_TIM_UPDATE_CFM, + /// Connection loss indication + MM_CONNECTION_LOSS_IND, + /// Channel context switch indication to the upper layers + MM_CHANNEL_SWITCH_IND, + /// Channel context pre-switch indication to the upper layers + MM_CHANNEL_PRE_SWITCH_IND, + /// Request to remain on channel or cancel remain on channel + MM_REMAIN_ON_CHANNEL_REQ, + /// Confirmation of the (cancel) remain on channel request + MM_REMAIN_ON_CHANNEL_CFM, + /// Remain on channel expired indication + MM_REMAIN_ON_CHANNEL_EXP_IND, + /// Indication of a PS state change of a peer device + MM_PS_CHANGE_IND, + /// Indication that some buffered traffic should be sent to the peer device + MM_TRAFFIC_REQ_IND, + /// Request to modify the STA Power-save mode options + MM_SET_PS_OPTIONS_REQ, + /// Confirmation of the PS options setting + MM_SET_PS_OPTIONS_CFM, + /// Indication of PS state change for a P2P VIF + MM_P2P_VIF_PS_CHANGE_IND, + /// Indication that CSA counter has been updated + MM_CSA_COUNTER_IND, + /// Channel occupation report indication + MM_CHANNEL_SURVEY_IND, + /// Message containing Beamformer Information + MM_BFMER_ENABLE_REQ, + /// Request to Start/Stop/Update NOA - GO Only + MM_SET_P2P_NOA_REQ, + /// Request to Start/Stop/Update Opportunistic PS - GO Only + MM_SET_P2P_OPPPS_REQ, + /// Start/Stop/Update NOA Confirmation + MM_SET_P2P_NOA_CFM, + /// Start/Stop/Update Opportunistic PS Confirmation + MM_SET_P2P_OPPPS_CFM, + /// P2P NoA Update Indication - GO Only + MM_P2P_NOA_UPD_IND, + /// Request to set RSSI threshold and RSSI hysteresis + MM_CFG_RSSI_REQ, + /// Indication that RSSI level is below or above the threshold + MM_RSSI_STATUS_IND, + /// Indication that CSA is done + MM_CSA_FINISH_IND, + /// Indication that CSA is in prorgess (resp. done) and traffic must be stopped (resp. restarted) + MM_CSA_TRAFFIC_IND, + /// Request to update the group information of a station + MM_MU_GROUP_UPDATE_REQ, + /// Confirmation of the @ref MM_MU_GROUP_UPDATE_REQ message + MM_MU_GROUP_UPDATE_CFM, + /// Request to initialize the antenna diversity algorithm + MM_ANT_DIV_INIT_REQ, + /// Request to stop the antenna diversity algorithm + MM_ANT_DIV_STOP_REQ, + /// Request to update the antenna switch status + MM_ANT_DIV_UPDATE_REQ, + /// Request to switch the antenna connected to path_0 + MM_SWITCH_ANTENNA_REQ, + /// Indication that a packet loss has occurred + MM_PKTLOSS_IND, + /// MU EDCA PARAMETERS Configuration Request. + MM_SET_MU_EDCA_REQ, + /// MU EDCA PARAMETERS Configuration Confirmation. + MM_SET_MU_EDCA_CFM, + /// UORA PARAMETERS Configuration Request. + MM_SET_UORA_REQ, + /// UORA PARAMETERS Configuration Confirmation. + MM_SET_UORA_CFM, + /// TXOP RTS THRESHOLD Configuration Request. + MM_SET_TXOP_RTS_THRES_REQ, + /// TXOP RTS THRESHOLD Configuration Confirmation. + MM_SET_TXOP_RTS_THRES_CFM, + /// HE BSS Color Configuration Request. + MM_SET_BSS_COLOR_REQ, + /// HE BSS Color Configuration Confirmation. + MM_SET_BSS_COLOR_CFM, + /// Calibration Gain Delta Configuration Request. + MM_SET_GAIN_DELTA_REQ, + /// Calibration Gain Delta Configuration Confirmation. + MM_SET_GAIN_DELTA_CFM, + /// Calibration result get Request. + MM_GET_CAL_RESULT_REQ, + /// Calibration result get Confirmation. + MM_GET_CAL_RESULT_CFM, + + /* + * Section of internal MM messages. No MM API messages should be defined below this point + */ + /// MAX number of messages + MM_MAX, +}; + +/// Interface types +enum +{ + /// ESS STA interface + MM_STA, + /// IBSS STA interface + MM_IBSS, + /// AP interface + MM_AP, + // Mesh Point interface + MM_MESH_POINT, + // Monitor interface + MM_MONITOR, +}; + +///BA agreement types +enum +{ + ///BlockAck agreement for TX + BA_AGMT_TX, + ///BlockAck agreement for RX + BA_AGMT_RX, +}; + +///BA agreement related status +enum +{ + ///Correct BA agreement establishment + BA_AGMT_ESTABLISHED, + ///BA agreement already exists for STA+TID requested, cannot override it (should have been deleted first) + BA_AGMT_ALREADY_EXISTS, + ///Correct BA agreement deletion + BA_AGMT_DELETED, + ///BA agreement for the (STA, TID) doesn't exist so nothing to delete + BA_AGMT_DOESNT_EXIST, +}; + +/// Features supported by LMAC - Positions +enum mm_features +{ + /// Beaconing + MM_FEAT_BCN_BIT = 0, + /// Autonomous Beacon Transmission + MM_FEAT_AUTOBCN_BIT, + /// Scan in LMAC + MM_FEAT_HWSCAN_BIT, + /// Connection Monitoring + MM_FEAT_CMON_BIT, + /// Multi Role + MM_FEAT_MROLE_BIT, + /// Radar Detection + MM_FEAT_RADAR_BIT, + /// Power Save + MM_FEAT_PS_BIT, + /// UAPSD + MM_FEAT_UAPSD_BIT, + /// DPSM + MM_FEAT_DPSM_BIT, + /// A-MPDU + MM_FEAT_AMPDU_BIT, + /// A-MSDU + MM_FEAT_AMSDU_BIT, + /// Channel Context + MM_FEAT_CHNL_CTXT_BIT, + /// Packet reordering + MM_FEAT_REORD_BIT, + /// P2P + MM_FEAT_P2P_BIT, + /// P2P Go + MM_FEAT_P2P_GO_BIT, + /// UMAC Present + MM_FEAT_UMAC_BIT, + /// VHT support + MM_FEAT_VHT_BIT, + /// Beamformee + MM_FEAT_BFMEE_BIT, + /// Beamformer + MM_FEAT_BFMER_BIT, + /// WAPI + MM_FEAT_WAPI_BIT, + /// MFP + MM_FEAT_MFP_BIT, + /// Mu-MIMO RX support + MM_FEAT_MU_MIMO_RX_BIT, + /// Mu-MIMO TX support + MM_FEAT_MU_MIMO_TX_BIT, + /// Wireless Mesh Networking + MM_FEAT_MESH_BIT, + /// TDLS support + MM_FEAT_TDLS_BIT, + /// Antenna Diversity support + MM_FEAT_ANT_DIV_BIT, + /// UF support + MM_FEAT_UF_BIT, + /// A-MSDU maximum size (bit0) + MM_AMSDU_MAX_SIZE_BIT0, + /// A-MSDU maximum size (bit1) + MM_AMSDU_MAX_SIZE_BIT1, + /// MON_DATA support + MM_FEAT_MON_DATA_BIT, + /// HE (802.11ax) support + MM_FEAT_HE_BIT, + MM_FEAT_TWT_BIT, +}; + +/// Maximum number of words in the configuration buffer +#define PHY_CFG_BUF_SIZE 16 + +/// Structure containing the parameters of the PHY configuration +struct phy_cfg_tag +{ + /// Buffer containing the parameters specific for the PHY used + u32_l parameters[PHY_CFG_BUF_SIZE]; +}; + +/// Structure containing the parameters of the Trident PHY configuration +struct phy_trd_cfg_tag +{ + /// MDM type(nxm)(upper nibble) and MDM2RF path mapping(lower nibble) + u8_l path_mapping; + /// TX DC offset compensation + u32_l tx_dc_off_comp; +}; + +/// Structure containing the parameters of the Karst PHY configuration +struct phy_karst_cfg_tag +{ + /// TX IQ mismatch compensation in 2.4GHz + u32_l tx_iq_comp_2_4G[2]; + /// RX IQ mismatch compensation in 2.4GHz + u32_l rx_iq_comp_2_4G[2]; + /// TX IQ mismatch compensation in 5GHz + u32_l tx_iq_comp_5G[2]; + /// RX IQ mismatch compensation in 5GHz + u32_l rx_iq_comp_5G[2]; + /// RF path used by default (0 or 1) + u8_l path_used; +}; + +struct phy_cataxia_cfg_tag +{ + u8_l path_used; +}; +/// Structure containing the parameters of the @ref MM_START_REQ message +struct mm_start_req +{ + /// PHY configuration + struct phy_cfg_tag phy_cfg; + /// UAPSD timeout + u32_l uapsd_timeout; + /// Local LP clock accuracy (in ppm) + u16_l lp_clk_accuracy; + u16_l tx_timeout[AC_MAX]; +}; + +/// Structure containing the parameters of the @ref MM_SET_CHANNEL_REQ message +struct mm_set_channel_req +{ + /// Channel information + struct mac_chan_op chan; + /// Index of the RF for which the channel has to be set (0: operating (primary), 1: secondary + /// RF (used for additional radar detection). This parameter is reserved if no secondary RF + /// is available in the system + u8_l index; +}; + +/// Structure containing the parameters of the @ref MM_SET_CHANNEL_CFM message +struct mm_set_channel_cfm +{ + /// Radio index to be used in policy table + u8_l radio_idx; + /// TX power configured (in dBm) + s8_l power; +}; + +/// Structure containing the parameters of the @ref MM_SET_DTIM_REQ message +struct mm_set_dtim_req +{ + /// DTIM period + u8_l dtim_period; +}; + +/// Structure containing the parameters of the @ref MM_SET_POWER_REQ message +struct mm_set_power_req +{ + /// Index of the interface for which the parameter is configured + u8_l inst_nbr; + /// TX power (in dBm) + s8_l power; +}; + +/// Structure containing the parameters of the @ref MM_SET_POWER_CFM message +struct mm_set_power_cfm +{ + /// Radio index to be used in policy table + u8_l radio_idx; + /// TX power configured (in dBm) + s8_l power; +}; + +/// Structure containing the parameters of the @ref MM_SET_BEACON_INT_REQ message +struct mm_set_beacon_int_req +{ + /// Beacon interval + u16_l beacon_int; + /// Index of the interface for which the parameter is configured + u8_l inst_nbr; +}; + +/// Structure containing the parameters of the @ref MM_SET_BASIC_RATES_REQ message +struct mm_set_basic_rates_req +{ + /// Basic rate set (as expected by bssBasicRateSet field of Rates MAC HW register) + u32_l rates; + /// Index of the interface for which the parameter is configured + u8_l inst_nbr; + /// Band on which the interface will operate + u8_l band; +}; + +/// Structure containing the parameters of the @ref MM_SET_BSSID_REQ message +struct mm_set_bssid_req +{ + /// BSSID to be configured in HW + struct mac_addr bssid; + /// Index of the interface for which the parameter is configured + u8_l inst_nbr; +}; + +/// Structure containing the parameters of the @ref MM_SET_FILTER_REQ message +struct mm_set_filter_req +{ + /// RX filter to be put into rxCntrlReg HW register + u32_l filter; +}; + +/// Structure containing the parameters of the @ref MM_ADD_IF_REQ message. +struct mm_add_if_req +{ + /// Type of the interface (AP, STA, ADHOC, ...) + u8_l type; + /// MAC ADDR of the interface to start + struct mac_addr addr; + /// P2P Interface + bool_l p2p; +}; + +/// Structure containing the parameters of the @ref MM_SET_EDCA_REQ message +struct mm_set_edca_req +{ + /// EDCA parameters of the queue (as expected by edcaACxReg HW register) + u32_l ac_param; + /// Flag indicating if UAPSD can be used on this queue + bool_l uapsd; + /// HW queue for which the parameters are configured + u8_l hw_queue; + /// Index of the interface for which the parameters are configured + u8_l inst_nbr; +}; + +/// Structure containing the parameters of the @ref MM_SET_MU_EDCA_REQ message +struct mm_set_mu_edca_req +{ + /// MU EDCA parameters of the different HE queues + u32_l param[AC_MAX]; +}; + +/// Structure containing the parameters of the @ref MM_SET_UORA_REQ message +struct mm_set_uora_req +{ + /// Minimum exponent of OFDMA Contention Window. + u8_l eocw_min; + /// Maximum exponent of OFDMA Contention Window. + u8_l eocw_max; +}; + +/// Structure containing the parameters of the @ref MM_SET_TXOP_RTS_THRES_REQ message +struct mm_set_txop_rts_thres_req +{ + /// TXOP RTS threshold + u16_l txop_dur_rts_thres; + /// Index of the interface for which the parameter is configured + u8_l inst_nbr; +}; + +/// Structure containing the parameters of the @ref MM_SET_BSS_COLOR_REQ message +struct mm_set_bss_color_req +{ + /// HE BSS color, formatted as per BSS_COLOR MAC HW register + u32_l bss_color; +}; + +struct mm_set_idle_req +{ + u8_l hw_idle; +}; + +/// Structure containing the parameters of the @ref MM_SET_SLOTTIME_REQ message +struct mm_set_slottime_req +{ + /// Slot time expressed in us + u8_l slottime; +}; + +/// Structure containing the parameters of the @ref MM_SET_MODE_REQ message +struct mm_set_mode_req +{ + /// abgnMode field of macCntrl1Reg register + u8_l abgnmode; +}; + +/// Structure containing the parameters of the @ref MM_SET_VIF_STATE_REQ message +struct mm_set_vif_state_req +{ + /// Association Id received from the AP (valid only if the VIF is of STA type) + u16_l aid; + /// Flag indicating if the VIF is active or not + bool_l active; + /// Interface index + u8_l inst_nbr; +}; + +/// Structure containing the parameters of the @ref MM_ADD_IF_CFM message. +struct mm_add_if_cfm +{ + /// Status of operation (different from 0 if unsuccessful) + u8_l status; + /// Interface index assigned by the LMAC + u8_l inst_nbr; +}; + +/// Structure containing the parameters of the @ref MM_REMOVE_IF_REQ message. +struct mm_remove_if_req +{ + /// Interface index assigned by the LMAC + u8_l inst_nbr; +}; + +/// Structure containing the parameters of the @ref MM_VERSION_CFM message. +struct mm_version_cfm +{ + /// Version of the LMAC FW + u32_l version_lmac; + /// Version1 of the MAC HW (as encoded in version1Reg MAC HW register) + u32_l version_machw_1; + /// Version2 of the MAC HW (as encoded in version2Reg MAC HW register) + u32_l version_machw_2; + /// Version1 of the PHY (depends on actual PHY) + u32_l version_phy_1; + /// Version2 of the PHY (depends on actual PHY) + u32_l version_phy_2; + /// Supported Features + u32_l features; + /// Maximum number of supported stations + u16_l max_sta_nb; + /// Maximum number of supported virtual interfaces + u8_l max_vif_nb; +}; + +/// Structure containing the parameters of the @ref MM_STA_ADD_REQ message. +struct mm_sta_add_req +{ + u32_l capa_flags; + /// Maximum A-MPDU size, in bytes, for HE frames + u32_l ampdu_size_max_he; + /// Maximum A-MPDU size, in bytes, for VHT frames + u32_l ampdu_size_max_vht; + /// PAID/GID + u32_l paid_gid; + /// Maximum A-MPDU size, in bytes, for HT frames + u16_l ampdu_size_max_ht; + /// MAC address of the station to be added + struct mac_addr mac_addr; + /// A-MPDU spacing, in us + u8_l ampdu_spacing_min; + /// Interface index + u8_l inst_nbr; + /// TDLS station + bool_l tdls_sta; + /// Indicate if the station is TDLS link initiator station + bool_l tdls_sta_initiator; + /// Indicate if the TDLS Channel Switch is allowed + bool_l tdls_chsw_allowed; + /// nonTransmitted BSSID index, set to the BSSID index in case the STA added is an AP + /// that is a nonTransmitted BSSID. Should be set to 0 otherwise + u8_l bssid_index; + /// Maximum BSSID indicator, valid if the STA added is an AP that is a nonTransmitted + /// BSSID + u8_l max_bssid_ind; +}; + +/// Structure containing the parameters of the @ref MM_STA_ADD_CFM message. +struct mm_sta_add_cfm +{ + /// Status of the operation (different from 0 if unsuccessful) + u8_l status; + /// Index assigned by the LMAC to the newly added station + u8_l sta_idx; + /// MAC HW index of the newly added station + u8_l hw_sta_idx; +}; + +/// Structure containing the parameters of the @ref MM_STA_DEL_REQ message. +struct mm_sta_del_req +{ + /// Index of the station to be deleted + u8_l sta_idx; +}; + +/// Structure containing the parameters of the @ref MM_STA_DEL_CFM message. +struct mm_sta_del_cfm +{ + /// Status of the operation (different from 0 if unsuccessful) + u8_l status; +}; + +/// Structure containing the parameters of the SET_POWER_MODE REQ message. +struct mm_setpowermode_req +{ + u8_l mode; + u8_l sta_idx; +}; + +/// Structure containing the parameters of the SET_POWER_MODE CFM message. +struct mm_setpowermode_cfm +{ + u8_l status; +}; + +/// Structure containing the parameters of the @ref MM_KEY_ADD REQ message. +struct mm_key_add_req +{ + /// Key index (valid only for default keys) + u8_l key_idx; + /// STA index (valid only for pairwise or mesh group keys) + u8_l sta_idx; + /// Key material + struct mac_sec_key key; + /// Cipher suite (WEP64, WEP128, TKIP, CCMP) + u8_l cipher_suite; + /// Index of the interface for which the key is set (valid only for default keys or mesh group keys) + u8_l inst_nbr; + /// A-MSDU SPP parameter + u8_l spp; + /// Indicate if provided key is a pairwise key or not + bool_l pairwise; +}; + +/// Structure containing the parameters of the @ref MM_KEY_ADD_CFM message. +struct mm_key_add_cfm +{ + /// Status of the operation (different from 0 if unsuccessful) + u8_l status; + /// HW index of the key just added + u8_l hw_key_idx; +}; + +/// Structure containing the parameters of the @ref MM_KEY_DEL_REQ message. +struct mm_key_del_req +{ + /// HW index of the key to be deleted + u8_l hw_key_idx; +}; + +/// Structure containing the parameters of the @ref MM_BA_ADD_REQ message. +struct mm_ba_add_req +{ + ///Type of agreement (0: TX, 1: RX) + u8_l type; + ///Index of peer station with which the agreement is made + u8_l sta_idx; + ///TID for which the agreement is made with peer station + u8_l tid; + ///Buffer size - number of MPDUs that can be held in its buffer per TID + u8_l bufsz; + /// Start sequence number negotiated during BA setup - the one in first aggregated MPDU counts more + u16_l ssn; +}; + +/// Structure containing the parameters of the @ref MM_BA_ADD_CFM message. +struct mm_ba_add_cfm +{ + ///Index of peer station for which the agreement is being confirmed + u8_l sta_idx; + ///TID for which the agreement is being confirmed + u8_l tid; + /// Status of ba establishment + u8_l status; +}; + +/// Structure containing the parameters of the @ref MM_BA_DEL_REQ message. +struct mm_ba_del_req +{ + ///Type of agreement (0: TX, 1: RX) + u8_l type; + ///Index of peer station for which the agreement is being deleted + u8_l sta_idx; + ///TID for which the agreement is being deleted + u8_l tid; +}; + +/// Structure containing the parameters of the @ref MM_BA_DEL_CFM message. +struct mm_ba_del_cfm +{ + ///Index of peer station for which the agreement deletion is being confirmed + u8_l sta_idx; + ///TID for which the agreement deletion is being confirmed + u8_l tid; + /// Status of ba deletion + u8_l status; +}; + +/// Structure containing the parameters of the @ref MM_CHAN_CTXT_ADD_REQ message +struct mm_chan_ctxt_add_req +{ + /// Operating channel + struct mac_chan_op chan; +}; + +/// Structure containing the parameters of the @ref MM_CHAN_CTXT_ADD_REQ message +struct mm_chan_ctxt_add_cfm +{ + /// Status of the addition + u8_l status; + /// Index of the new channel context + u8_l index; +}; + + +/// Structure containing the parameters of the @ref MM_CHAN_CTXT_DEL_REQ message +struct mm_chan_ctxt_del_req +{ + /// Index of the new channel context to be deleted + u8_l index; +}; + + +/// Structure containing the parameters of the @ref MM_CHAN_CTXT_LINK_REQ message +struct mm_chan_ctxt_link_req +{ + /// VIF index + u8_l vif_index; + /// Channel context index + u8_l chan_index; + /// Indicate if this is a channel switch (unlink current ctx first if true) + u8_l chan_switch; +}; + +/// Structure containing the parameters of the @ref MM_CHAN_CTXT_UNLINK_REQ message +struct mm_chan_ctxt_unlink_req +{ + /// VIF index + u8_l vif_index; +}; + +/// Structure containing the parameters of the @ref MM_CHAN_CTXT_UPDATE_REQ message +struct mm_chan_ctxt_update_req +{ + /// Channel context index + u8_l chan_index; + /// New channel information + struct mac_chan_op chan; +}; + +/// Structure containing the parameters of the @ref MM_CHAN_CTXT_SCHED_REQ message +struct mm_chan_ctxt_sched_req +{ + /// VIF index + u8_l vif_index; + /// Channel context index + u8_l chan_index; + /// Type of the scheduling request (0: normal scheduling, 1: derogatory + /// scheduling) + u8_l type; +}; + +/// Structure containing the parameters of the @ref MM_CHANNEL_SWITCH_IND message +struct mm_channel_switch_ind +{ + /// Index of the channel context we will switch to + u8_l chan_index; + /// Indicate if the switch has been triggered by a Remain on channel request + bool_l roc; + /// VIF on which remain on channel operation has been started (if roc == 1) + u8_l vif_index; + /// Indicate if the switch has been triggered by a TDLS Remain on channel request + bool_l roc_tdls; +}; + +/// Structure containing the parameters of the @ref MM_CHANNEL_PRE_SWITCH_IND message +struct mm_channel_pre_switch_ind +{ + /// Index of the channel context we will switch to + u8_l chan_index; +}; + +/// Structure containing the parameters of the @ref MM_CONNECTION_LOSS_IND message. +struct mm_connection_loss_ind +{ + /// VIF instance number + u8_l inst_nbr; +}; + + +/// Structure containing the parameters of the @ref MM_DBG_TRIGGER_REQ message. +struct mm_dbg_trigger_req +{ + /// Error trace to be reported by the LMAC + char error[64]; +}; + +/// Structure containing the parameters of the @ref MM_SET_PS_MODE_REQ message. +struct mm_set_ps_mode_req +{ + /// Power Save is activated or deactivated + u8_l new_state; +}; + +/// Structure containing the parameters of the @ref MM_BCN_CHANGE_REQ message. +#define BCN_MAX_CSA_CPT 2 +struct mm_bcn_change_req +{ + /// Offset of the TIM IE in the beacon + u16_l tim_oft; + /// Length of the TIM IE + u8_l tim_len; + /// Index of the VIF for which the beacon is updated + u8_l inst_nbr; + /// Offset of CSA (channel switch announcement) counters (0 means no counter) + u8_l csa_oft[BCN_MAX_CSA_CPT]; + /// Length of the beacon template + u16_l bcn_len; + /// Pointer, in host memory, to the new beacon template + ptr_addr bcn_ptr; +}; + + +/// Structure containing the parameters of the @ref MM_TIM_UPDATE_REQ message. +struct mm_tim_update_req +{ + /// Association ID of the STA the bit of which has to be updated (0 for BC/MC traffic) + u16_l aid; + /// Flag indicating the availability of data packets for the given STA + u8_l tx_avail; + /// Index of the VIF for which the TIM is updated + u8_l inst_nbr; +}; + +/// Structure containing the parameters of the @ref MM_REMAIN_ON_CHANNEL_REQ message. +struct mm_remain_on_channel_req +{ + /// Operation Code + u8_l op_code; + /// VIF Index + u8_l vif_index; + /// Channel parameter + struct mac_chan_op chan; + /// Duration (in ms) + u32_l duration_ms; +}; + +/// Structure containing the parameters of the @ref MM_REMAIN_ON_CHANNEL_CFM message +struct mm_remain_on_channel_cfm +{ + /// Operation Code + u8_l op_code; + /// Status of the operation + u8_l status; + /// Channel Context index + u8_l chan_ctxt_index; +}; + +/// Structure containing the parameters of the @ref MM_REMAIN_ON_CHANNEL_EXP_IND message +struct mm_remain_on_channel_exp_ind +{ + /// VIF Index + u8_l vif_index; + /// Channel Context index + u8_l chan_ctxt_index; +}; + +/// Structure containing the parameters of the @ref MM_SET_UAPSD_TMR_REQ message. +struct mm_set_uapsd_tmr_req +{ + /// action: Start or Stop the timer + u8_l action; + /// timeout value, in milliseconds + u32_l timeout; +}; + +/// Structure containing the parameters of the @ref MM_SET_UAPSD_TMR_CFM message. +struct mm_set_uapsd_tmr_cfm +{ + /// Status of the operation (different from 0 if unsuccessful) + u8_l status; +}; + + +/// Structure containing the parameters of the @ref MM_PS_CHANGE_IND message +struct mm_ps_change_ind +{ + /// Index of the peer device that is switching its PS state + u8_l sta_idx; + /// New PS state of the peer device (0: active, 1: sleeping) + u8_l ps_state; +}; + +/// Structure containing the parameters of the @ref MM_P2P_VIF_PS_CHANGE_IND message +struct mm_p2p_vif_ps_change_ind +{ + /// Index of the P2P VIF that is switching its PS state + u8_l vif_index; + /// New PS state of the P2P VIF interface (0: active, 1: sleeping) + u8_l ps_state; +}; + +/// Structure containing the parameters of the @ref MM_TRAFFIC_REQ_IND message +struct mm_traffic_req_ind +{ + /// Index of the peer device that needs traffic + u8_l sta_idx; + /// Number of packets that need to be sent (if 0, all buffered traffic shall be sent and + /// if set to @ref PS_SP_INTERRUPTED, it means that current service period has been interrupted) + u8_l pkt_cnt; + /// Flag indicating if the traffic request concerns U-APSD queues or not + bool_l uapsd; +}; + +/// Structure containing the parameters of the @ref MM_SET_PS_OPTIONS_REQ message. +struct mm_set_ps_options_req +{ + /// VIF Index + u8_l vif_index; + /// Listen interval (0 if wake up shall be based on DTIM period) + u16_l listen_interval; + /// Flag indicating if we shall listen the BC/MC traffic or not + bool_l dont_listen_bc_mc; +}; + +/// Structure containing the parameters of the @ref MM_CSA_COUNTER_IND message +struct mm_csa_counter_ind +{ + /// Index of the VIF + u8_l vif_index; + /// Updated CSA counter value + u8_l csa_count; +}; + +/// Structure containing the parameters of the @ref MM_CHANNEL_SURVEY_IND message +struct mm_channel_survey_ind +{ + /// Frequency of the channel + u16_l freq; + /// Noise in dbm + s8_l noise_dbm; + /// Amount of time spent of the channel (in ms) + u32_l chan_time_ms; + /// Amount of time the primary channel was sensed busy + u32_l chan_time_busy_ms; +}; + +/// Structure containing the parameters of the @ref MM_BFMER_ENABLE_REQ message. +struct mm_bfmer_enable_req +{ + /** + * Address of the beamforming report space allocated in host memory + * (Valid only if vht_su_bfmee is true) + */ + u32_l host_bfr_addr; + /** + * Size of the beamforming report space allocated in host memory. This space should + * be twice the maximum size of the expected beamforming reports as the FW will + * divide it in two in order to be able to upload a new report while another one is + * used in transmission + */ + u16_l host_bfr_size; + /// AID + u16_l aid; + /// Station Index + u8_l sta_idx; + /// Maximum number of spatial streams the station can receive + u8_l rx_nss; + /** + * Indicate if peer STA is MU Beamformee (VHT) capable + * (Valid only if vht_su_bfmee is true) + */ + bool_l vht_mu_bfmee; +}; + +/// Structure containing the parameters of the @ref MM_SET_P2P_NOA_REQ message. +struct mm_set_p2p_noa_req +{ + /// VIF Index + u8_l vif_index; + /// Allocated NOA Instance Number - Valid only if count = 0 + u8_l noa_inst_nb; + /// Count + u8_l count; + /// Indicate if NoA can be paused for traffic reason + bool_l dyn_noa; + /// Duration (in us) + u32_l duration_us; + /// Interval (in us) + u32_l interval_us; + /// Start Time offset from next TBTT (in us) + u32_l start_offset; +}; + +/// Structure containing the parameters of the @ref MM_SET_P2P_OPPPS_REQ message. +struct mm_set_p2p_oppps_req +{ + /// VIF Index + u8_l vif_index; + /// CTWindow + u8_l ctwindow; +}; + +/// Structure containing the parameters of the @ref MM_SET_P2P_NOA_CFM message. +struct mm_set_p2p_noa_cfm +{ + /// Request status + u8_l status; +}; + +/// Structure containing the parameters of the @ref MM_SET_P2P_OPPPS_CFM message. +struct mm_set_p2p_oppps_cfm +{ + /// Request status + u8_l status; +}; + +/// Structure containing the parameters of the @ref MM_P2P_NOA_UPD_IND message. +struct mm_p2p_noa_upd_ind +{ + /// VIF Index + u8_l vif_index; + /// NOA Instance Number + u8_l noa_inst_nb; + /// NoA Type + u8_l noa_type; + /// Count + u8_l count; + /// Duration (in us) + u32_l duration_us; + /// Interval (in us) + u32_l interval_us; + /// Start Time + u32_l start_time; +}; + +/// Structure containing the parameters of the @ref MM_CFG_RSSI_REQ message +struct mm_cfg_rssi_req +{ + /// Index of the VIF + u8_l vif_index; + /// RSSI threshold + s8_l rssi_thold; + /// RSSI hysteresis + u8_l rssi_hyst; +}; + +/// Structure containing the parameters of the @ref MM_RSSI_STATUS_IND message +struct mm_rssi_status_ind +{ + /// Index of the VIF + u8_l vif_index; + /// Status of the RSSI + bool_l rssi_status; + /// Current RSSI + s8_l rssi; +}; + +/// Structure containing the parameters of the @ref MM_PKTLOSS_IND message +struct mm_pktloss_ind +{ + /// Index of the VIF + u8_l vif_index; + /// Address of the STA for which there is a packet loss + struct mac_addr mac_addr; + /// Number of packets lost + u32 num_packets; +}; + +/// Structure containing the parameters of the @ref MM_CSA_FINISH_IND message +struct mm_csa_finish_ind +{ + /// Index of the VIF + u8_l vif_index; + /// Status of the operation + u8_l status; + /// New channel ctx index + u8_l chan_idx; +}; + +/// Structure containing the parameters of the @ref MM_CSA_TRAFFIC_IND message +struct mm_csa_traffic_ind +{ + /// Index of the VIF + u8_l vif_index; + /// Is tx traffic enable or disable + bool_l enable; +}; + +/// Structure containing the parameters of the @ref MM_MU_GROUP_UPDATE_REQ message. +/// Size allocated for the structure depends of the number of group +struct mm_mu_group_update_req +{ + /// Station index + u8_l sta_idx; + /// Number of groups the STA belongs to + u8_l group_cnt; + /// Group information + struct + { + /// Group Id + u8_l group_id; + /// User position + u8_l user_pos; + } groups[0]; +}; + +/////////////////////////////////////////////////////////////////////////////// +/////////// For Scan messages +/////////////////////////////////////////////////////////////////////////////// +enum scan_msg_tag +{ + /// Scanning start Request. + SCAN_START_REQ = LMAC_FIRST_MSG(TASK_SCAN), + /// Scanning start Confirmation. + SCAN_START_CFM, + /// End of scanning indication. + SCAN_DONE_IND, + /// Cancel scan request + SCAN_CANCEL_REQ, + /// Cancel scan confirmation + SCAN_CANCEL_CFM, + + /// MAX number of messages + SCAN_MAX, +}; + +/// Maximum number of SSIDs in a scan request +#define SCAN_SSID_MAX 4 + +/// Maximum number of channels in a scan request +#define SCAN_CHANNEL_MAX (MAC_DOMAINCHANNEL_24G_MAX + MAC_DOMAINCHANNEL_5G_MAX) + +/// Maximum length of the ProbeReq IEs (SoftMAC mode) +#define SCAN_MAX_IE_LEN 300 + +/// Maximum number of PHY bands supported +#define SCAN_BAND_MAX 2 + +/// Structure containing the parameters of the @ref SCAN_START_REQ message +struct scan_start_req +{ + /// List of channel to be scanned + struct mac_chan_def chan[SCAN_CHANNEL_MAX]; + /// List of SSIDs to be scanned + struct mac_ssid ssid[SCAN_SSID_MAX]; + /// BSSID to be scanned + struct mac_addr bssid; + /// Index of the VIF that is scanning + u8_l vif_idx; + /// Number of channels to scan + u8_l chan_cnt; + /// Number of SSIDs to scan for + u8_l ssid_cnt; + /// no CCK - For P2P frames not being sent at CCK rate in 2GHz band. + bool no_cck; + u32_l duration; + /// Length of the additional IEs + u16_l add_ie_len; + /// Pointer (in host memory) to the additional IEs that need to be added to the ProbeReq + /// (following the SSID element) + ptr_addr add_ies; +}; + +/// Structure containing the parameters of the @ref SCAN_START_CFM message +struct scan_start_cfm +{ + /// Status of the request + u8_l status; +}; + +/// Structure containing the parameters of the @ref SCAN_CANCEL_REQ message +struct scan_cancel_req +{ +}; + +/// Structure containing the parameters of the @ref SCAN_START_CFM message +struct scan_cancel_cfm +{ + /// Status of the request + u8_l status; +}; + +/////////////////////////////////////////////////////////////////////////////// +/////////// For Scanu messages +/////////////////////////////////////////////////////////////////////////////// +/// Messages that are logically related to the task. +enum +{ + /// Scan request from host. + SCANU_START_REQ = LMAC_FIRST_MSG(TASK_SCANU), + /// Scanning start Confirmation. + SCANU_START_CFM, + /// Join request + SCANU_JOIN_REQ, + /// Join confirmation. + SCANU_JOIN_CFM, + /// Scan result indication. + SCANU_RESULT_IND, + /// Fast scan request from any other module. + SCANU_FAST_REQ, + /// Confirmation of fast scan request. + SCANU_FAST_CFM, + ///Scan cancel from host. + SCANU_CANCEL_REQ, + ///Scan cancel confirmation + SCANU_CANCEL_CFM, + + /// MAX number of messages + SCANU_MAX, +}; + +/// Maximum length of the additional ProbeReq IEs (FullMAC mode) +#define SCANU_MAX_IE_LEN 200 + +/// Structure containing the parameters of the @ref SCANU_START_REQ message +struct scanu_start_req +{ + /// List of channel to be scanned + struct mac_chan_def chan[SCAN_CHANNEL_MAX]; + /// List of SSIDs to be scanned + struct mac_ssid ssid[SCAN_SSID_MAX]; + /// BSSID to be scanned (or WILDCARD BSSID if no BSSID is searched in particular) + struct mac_addr bssid; + /// Index of the VIF that is scanning + u8_l vif_idx; + /// Number of channels to scan + u8_l chan_cnt; + /// Number of SSIDs to scan for + u8_l ssid_cnt; + /// no CCK - For P2P frames not being sent at CCK rate in 2GHz band. + bool no_cck; + u32_l duration; + /// Length of the additional IEs + u16_l add_ie_len; + /// Address (in host memory) of the additional IEs that need to be added to the ProbeReq + /// (following the SSID element) + ptr_addr add_ies; +}; + +/// Structure containing the parameters of the @ref SCANU_START_CFM message +struct scanu_start_cfm +{ + /// Index of the VIF that was scanning + u8_l vif_idx; + /// Status of the request + u8_l status; + /// Number of scan results available + u8_l result_cnt; +}; + +/// Parameters of the @SCANU_RESULT_IND message +struct scanu_result_ind +{ + /// Length of the frame + u16_l length; + /// Frame control field of the frame. + u16_l framectrl; + /// Center frequency on which we received the packet + u16_l center_freq; + /// PHY band + u8_l band; + /// Index of the station that sent the frame. 0xFF if unknown. + u8_l sta_idx; + /// Index of the VIF that received the frame. 0xFF if unknown. + u8_l inst_nbr; + /// RSSI of the received frame. + s8_l rssi; + /// Frame payload. + u32_l payload[]; +}; + +/// Structure containing the parameters of the message. +struct scanu_fast_req +{ + /// The SSID to scan in the channel. + struct mac_ssid ssid; + /// BSSID. + struct mac_addr bssid; + /// Probe delay. + u16_l probe_delay; + /// Minimum channel time. + u16_l minch_time; + /// Maximum channel time. + u16_l maxch_time; + /// The channel number to scan. + u16_l ch_nbr; +}; +/// Structure containing the parameters of the @ref SCANU_CANCEL_REQ message +struct scanu_cancel_req +{ + /// index of the scan element + uint8_t vif_idx; +}; + +/// Structure containing the parameters of the @ref SCANU_CANCEL_CFM and +/// @ref SCANU_CANCEL_CFM messages +struct scanu_cancel_cfm +{ + /// Index of the VIF that was scanning + uint8_t vif_idx; + /// Status of the request + uint8_t status; +}; + + +/////////////////////////////////////////////////////////////////////////////// +/////////// For ME messages +/////////////////////////////////////////////////////////////////////////////// +/// Messages that are logically related to the task. +enum +{ + /// Configuration request from host. + ME_CONFIG_REQ = LMAC_FIRST_MSG(TASK_ME), + /// Configuration confirmation. + ME_CONFIG_CFM, + /// Configuration request from host. + ME_CHAN_CONFIG_REQ, + /// Configuration confirmation. + ME_CHAN_CONFIG_CFM, + /// Set control port state for a station. + ME_SET_CONTROL_PORT_REQ, + /// Control port setting confirmation. + ME_SET_CONTROL_PORT_CFM, + /// TKIP MIC failure indication. + ME_TKIP_MIC_FAILURE_IND, + /// Add a station to the FW (AP mode) + ME_STA_ADD_REQ, + /// Confirmation of the STA addition + ME_STA_ADD_CFM, + /// Delete a station from the FW (AP mode) + ME_STA_DEL_REQ, + /// Confirmation of the STA deletion + ME_STA_DEL_CFM, + /// Indication of a TX RA/TID queue credit update + ME_TX_CREDITS_UPDATE_IND, + /// Request indicating to the FW that there is traffic buffered on host + ME_TRAFFIC_IND_REQ, + /// Confirmation that the @ref ME_TRAFFIC_IND_REQ has been executed + ME_TRAFFIC_IND_CFM, + /// Request of RC statistics to a station + ME_RC_STATS_REQ, + /// RC statistics confirmation + ME_RC_STATS_CFM, + /// RC fixed rate request + ME_RC_SET_RATE_REQ, + /// Configure monitor interface + ME_CONFIG_MONITOR_REQ, + /// Configure monitor interface response + ME_CONFIG_MONITOR_CFM, + /// Setting power Save mode request from host + ME_SET_PS_MODE_REQ, + /// Set power Save mode confirmation + ME_SET_PS_MODE_CFM, + /// MAX number of messages + ME_MAX, +}; + +/// Structure containing the parameters of the @ref ME_START_REQ message +struct me_config_req +{ + /// HT Capabilities + struct mac_htcapability ht_cap; + /// VHT Capabilities + struct mac_vhtcapability vht_cap; + /// HE capabilities + struct mac_hecapability he_cap; + /// Lifetime of packets sent under a BlockAck agreement (expressed in TUs) + u16_l tx_lft; + /// Maximum supported BW + u8_l phy_bw_max; + /// Boolean indicating if HT is supported or not + bool_l ht_supp; + /// Boolean indicating if VHT is supported or not + bool_l vht_supp; + /// Boolean indicating if HE is supported or not + bool_l he_supp; + /// Boolean indicating if HE OFDMA UL is enabled or not + bool_l he_ul_on; + /// Boolean indicating if PS mode shall be enabled or not + bool_l ps_on; + /// Boolean indicating if Antenna Diversity shall be enabled or not + bool_l ant_div_on; + /// Boolean indicating if Dynamic PS mode shall be used or not + bool_l dpsm; + unsigned char sleep_flag; +}; + +/// Structure containing the parameters of the @ref ME_CHAN_CONFIG_REQ message +struct me_chan_config_req +{ + /// List of 2.4GHz supported channels + struct mac_chan_def chan2G4[MAC_DOMAINCHANNEL_24G_MAX]; + /// List of 5GHz supported channels + struct mac_chan_def chan5G[MAC_DOMAINCHANNEL_5G_MAX]; + /// Number of 2.4GHz channels in the list + u8_l chan2G4_cnt; + /// Number of 5GHz channels in the list + u8_l chan5G_cnt; +}; + +/// Structure containing the parameters of the @ref ME_SET_CONTROL_PORT_REQ message +struct me_set_control_port_req +{ + /// Index of the station for which the control port is opened + u8_l sta_idx; + /// Control port state + bool_l control_port_open; +}; + +/// Structure containing the parameters of the @ref ME_TKIP_MIC_FAILURE_IND message +struct me_tkip_mic_failure_ind +{ + /// Address of the sending STA + struct mac_addr addr; + /// TSC value + u64_l tsc; + /// Boolean indicating if the packet was a group or unicast one (true if group) + bool_l ga; + /// Key Id + u8_l keyid; + /// VIF index + u8_l vif_idx; +}; + +/// Structure containing the parameters of the @ref ME_STA_ADD_REQ message +struct me_sta_add_req +{ + /// MAC address of the station to be added + struct mac_addr mac_addr; + /// Supported legacy rates + struct mac_rateset rate_set; + /// HT Capabilities + struct mac_htcapability ht_cap; + /// VHT Capabilities + struct mac_vhtcapability vht_cap; + /// HE capabilities + struct mac_hecapability he_cap; + /// Flags giving additional information about the station (@ref mac_sta_flags) + u32_l flags; + /// Association ID of the station + u16_l aid; + /// Bit field indicating which queues have U-APSD enabled + u8_l uapsd_queues; + /// Maximum size, in frames, of a APSD service period + u8_l max_sp_len; + /// Operation mode information (valid if bit @ref STA_OPMOD_NOTIF is + /// set in the flags) + u8_l opmode; + /// Index of the VIF the station is attached to + u8_l vif_idx; + /// Whether the the station is TDLS station + bool_l tdls_sta; + /// Indicate if the station is TDLS link initiator station + bool_l tdls_sta_initiator; + /// Indicate if the TDLS Channel Switch is allowed + bool_l tdls_chsw_allowed; +}; + +/// Structure containing the parameters of the @ref ME_STA_ADD_CFM message +struct me_sta_add_cfm +{ + /// Station index + u8_l sta_idx; + /// Status of the station addition + u8_l status; + /// PM state of the station + u8_l pm_state; +}; + +/// Structure containing the parameters of the @ref ME_STA_DEL_REQ message. +struct me_sta_del_req +{ + /// Index of the station to be deleted + u8_l sta_idx; + /// Whether the the station is TDLS station + bool_l tdls_sta; +}; + +/// Structure containing the parameters of the @ref ME_TX_CREDITS_UPDATE_IND message. +struct me_tx_credits_update_ind +{ + /// Index of the station for which the credits are updated + u8_l sta_idx; + /// TID for which the credits are updated + u8_l tid; + /// Offset to be applied on the credit count + s8_l credits; +}; + +/// Structure containing the parameters of the @ref ME_TRAFFIC_IND_REQ message. +struct me_traffic_ind_req +{ + /// Index of the station for which UAPSD traffic is available on host + u8_l sta_idx; + /// Flag indicating the availability of UAPSD packets for the given STA + u8_l tx_avail; + /// Indicate if traffic is on uapsd-enabled queues + bool_l uapsd; +}; + +/// Structure containing the parameters of the @ref ME_RC_STATS_REQ message. +struct me_rc_stats_req +{ + /// Index of the station for which the RC statistics are requested + u8_l sta_idx; +}; + +/// Structure containing the rate control statistics +struct rc_rate_stats +{ + /// Number of attempts (per sampling interval) + u16_l attempts; + /// Number of success (per sampling interval) + u16_l success; + /// Estimated probability of success (EWMA) + u16_l probability; + /// Rate configuration of the sample + u16_l rate_config; + union + { + struct { + /// Number of times the sample has been skipped (per sampling interval) + u8_l sample_skipped; + /// Whether the old probability is available + bool_l old_prob_available; + /// Whether the rate can be used in the retry chain + bool_l rate_allowed; + }; + struct { + /// RU size and UL length received in the latest HE trigger frame + u16_l ru_and_length; + }; + }; +}; + +/// Number of RC samples +#define RC_MAX_N_SAMPLE 10 +/// Index of the HE statistics element in the table +#define RC_HE_STATS_IDX RC_MAX_N_SAMPLE + +/// Structure containing the parameters of the @ref ME_RC_STATS_CFM message. +struct me_rc_stats_cfm +{ + /// Index of the station for which the RC statistics are provided + u8_l sta_idx; + /// Number of samples used in the RC algorithm + u16_l no_samples; + /// Number of MPDUs transmitted (per sampling interval) + u16_l ampdu_len; + /// Number of AMPDUs transmitted (per sampling interval) + u16_l ampdu_packets; + /// Average number of MPDUs in each AMPDU frame (EWMA) + u32_l avg_ampdu_len; + // Current step 0 of the retry chain + u8_l sw_retry_step; + /// Trial transmission period + u8_l sample_wait; + /// Retry chain steps + u16_l retry_step_idx[4]; + /// RC statistics - Max number of RC samples, plus one for the HE TB statistics + struct rc_rate_stats rate_stats[RC_MAX_N_SAMPLE + 1]; + /// Throughput - Max number of RC samples, plus one for the HE TB statistics + u32_l tp[RC_MAX_N_SAMPLE + 1]; +}; + +/// Structure containing the parameters of the @ref ME_RC_SET_RATE_REQ message. +struct me_rc_set_rate_req +{ + /// Index of the station for which the fixed rate is set + u8_l sta_idx; + /// Rate configuration to be set + u16_l fixed_rate_cfg; +}; + +/// Structure containing the parameters of the @ref ME_CONFIG_MONITOR_REQ message. +struct me_config_monitor_req +{ + /// Channel to configure + struct mac_chan_op chan; + /// Is channel data valid + bool_l chan_set; + /// Enable report of unsupported HT frames + bool_l uf; +}; + +/// Structure containing the parameters of the @ref ME_CONFIG_MONITOR_CFM message. +struct me_config_monitor_cfm +{ + /// Channel context index + u8_l chan_index; + /// Channel parameters + struct mac_chan_op chan; +}; + +/// Structure containing the parameters of the @ref ME_SET_PS_MODE_REQ message. +struct me_set_ps_mode_req +{ + /// Power Save is activated or deactivated + u8_l ps_state; +}; + +/////////////////////////////////////////////////////////////////////////////// +/////////// For TWT messages +/////////////////////////////////////////////////////////////////////////////// +/// Message API of the TWT task +enum +{ + /// Request Individual TWT Establishment + TWT_SETUP_REQ = LMAC_FIRST_MSG(TASK_TWT), + /// Confirm Individual TWT Establishment + TWT_SETUP_CFM, + /// Indicate TWT Setup response from peer + TWT_SETUP_IND, + /// Request to destroy a TWT Establishment or all of them + TWT_TEARDOWN_REQ, + /// Confirm to destroy a TWT Establishment or all of them + TWT_TEARDOWN_CFM, + + /// MAX number of messages + TWT_MAX, +}; + +///TWT Group assignment +struct twt_grp_assignment_tag +{ + /// TWT Group assignment byte array + u8_l grp_assignment[9]; +}; + +///TWT Flow configuration +struct twt_conf_tag +{ + /// Flow Type (0: Announced, 1: Unannounced) + u8_l flow_type; + /// Wake interval Exponent + u8_l wake_int_exp; + /// Unit of measurement of TWT Minimum Wake Duration (0:256us, 1:tu) + bool_l wake_dur_unit; + /// Nominal Minimum TWT Wake Duration + u8_l min_twt_wake_dur; + /// TWT Wake Interval Mantissa + u16_l wake_int_mantissa; +}; + +///TWT Setup request message structure +struct twt_setup_req +{ + /// VIF Index + u8_l vif_idx; + /// Setup request type + u8_l setup_type; + /// TWT Setup configuration + struct twt_conf_tag conf; +}; + +///TWT Setup confirmation message structure +struct twt_setup_cfm +{ + /// Status (0 = TWT Setup Request has been transmitted to peer) + u8_l status; +}; + +/// TWT Setup command +enum twt_setup_types +{ + MAC_TWT_SETUP_REQ = 0, + MAC_TWT_SETUP_SUGGEST, + MAC_TWT_SETUP_DEMAND, + MAC_TWT_SETUP_GROUPING, + MAC_TWT_SETUP_ACCEPT, + MAC_TWT_SETUP_ALTERNATE, + MAC_TWT_SETUP_DICTATE, + MAC_TWT_SETUP_REJECT, +}; + +///TWT Setup indication message structure +struct twt_setup_ind +{ + /// Response type + u8_l resp_type; + /// STA Index + u8_l sta_idx; + /// TWT Setup configuration + struct twt_conf_tag conf; +}; + +/// TWT Teardown request message structure +struct twt_teardown_req +{ + /// TWT Negotiation type + u8_l neg_type; + /// All TWT + u8_l all_twt; + /// TWT flow ID + u8_l id; + /// VIF Index + u8_l vif_idx; +}; + +///TWT Teardown confirmation message structure +struct twt_teardown_cfm +{ + /// Status (0 = TWT Teardown Request has been transmitted to peer) + u8_l status; +}; + +/////////////////////////////////////////////////////////////////////////////// +/////////// For SM messages +/////////////////////////////////////////////////////////////////////////////// +/// Message API of the SM task +enum sm_msg_tag +{ + /// Request to connect to an AP + SM_CONNECT_REQ = LMAC_FIRST_MSG(TASK_SM), + /// Confirmation of connection + SM_CONNECT_CFM, + /// Indicates that the SM associated to the AP + SM_CONNECT_IND, + /// Request to disconnect + SM_DISCONNECT_REQ, + /// Confirmation of disconnection + SM_DISCONNECT_CFM, + /// Indicates that the SM disassociated the AP + SM_DISCONNECT_IND, + /// Request to start external authentication + SM_EXTERNAL_AUTH_REQUIRED_IND, + /// Response to external authentication request + SM_EXTERNAL_AUTH_REQUIRED_RSP, + SM_FT_AUTH_IND, + SM_FT_AUTH_RSP, + + /// MAX number of messages + SM_MAX, +}; + +/// Structure containing the parameters of @ref SM_CONNECT_REQ message. +struct sm_connect_req +{ + /// SSID to connect to + struct mac_ssid ssid; + /// BSSID to connect to (if not specified, set this field to WILDCARD BSSID) + struct mac_addr bssid; + /// Channel on which we have to connect (if not specified, set -1 in the chan.freq field) + struct mac_chan_def chan; + /// Connection flags (see @ref mac_connection_flags) + u32_l flags; + /// Control port Ethertype (in network endianness) + u16_l ctrl_port_ethertype; + /// Length of the association request IEs + /// Listen interval to be used for this connection + u16_l listen_interval; + /// Flag indicating if the we have to wait for the BC/MC traffic after beacon or not + bool_l dont_wait_bcmc; + /// Authentication type + u8_l auth_type; + /// UAPSD queues (bit0: VO, bit1: VI, bit2: BE, bit3: BK) + u8_l uapsd_queues; + /// VIF index + u8_l vif_idx; + u16_l ie_len; + /// Buffer containing the additional information elements to be put in the + /// association request + u32_l ie_buf[0]; +}; + +/// Structure containing the parameters of the @ref SM_CONNECT_CFM message. +struct sm_connect_cfm +{ + /// Status. If 0, it means that the connection procedure will be performed and that + /// a subsequent @ref SM_CONNECT_IND message will be forwarded once the procedure is + /// completed + u8_l status; +}; + +#define SM_ASSOC_IE_LEN 800 +/// Structure containing the parameters of the @ref SM_CONNECT_IND message. +struct sm_connect_ind +{ + /// Status code of the connection procedure + u16_l status_code; + /// BSSID + struct mac_addr bssid; + /// Flag indicating if the indication refers to an internal roaming or from a host request + bool_l roamed; + /// Index of the VIF for which the association process is complete + u8_l vif_idx; + /// Index of the STA entry allocated for the AP + u8_l ap_idx; + /// Index of the LMAC channel context the connection is attached to + u8_l ch_idx; + /// Flag indicating if the AP is supporting QoS + bool_l qos; + /// ACM bits set in the AP WMM parameter element + u8_l acm; + /// Length of the AssocReq IEs + u16_l assoc_req_ie_len; + /// Length of the AssocRsp IEs + u16_l assoc_rsp_ie_len; + /// IE buffer + /// Association Id allocated by the AP for this connection + u16_l aid; + /// AP operating channel + struct mac_chan_op chan; + /// EDCA parameters + u32_l ac_param[AC_MAX]; + u32_l assoc_ie_buf[0]; +}; + +/// Structure containing the parameters of the @ref SM_DISCONNECT_REQ message. +struct sm_disconnect_req +{ + /// Reason of the deauthentication. + u16_l reason_code; + /// Index of the VIF. + u8_l vif_idx; +}; + +/// Structure containing the parameters of SM_ASSOCIATION_IND the message +struct sm_association_ind +{ + // MAC ADDR of the STA + struct mac_addr me_mac_addr; +}; + + +/// Structure containing the parameters of the @ref SM_DISCONNECT_IND message. +struct sm_disconnect_ind +{ + /// Reason of the disconnection. + u16_l reason_code; + /// Index of the VIF. + u8_l vif_idx; + /// FT over DS is ongoing + bool_l reassoc; +}; + +/// Structure containing the parameters of the @ref SM_EXTERNAL_AUTH_REQUIRED_IND +struct sm_external_auth_required_ind +{ + /// Index of the VIF. + u8_l vif_idx; + /// SSID to authenticate to + struct mac_ssid ssid; + /// BSSID to authenticate to + struct mac_addr bssid; + /// AKM suite of the respective authentication + u32_l akm; +}; + +/// Structure containing the parameters of the @ref SM_EXTERNAL_AUTH_REQUIRED_RSP +struct sm_external_auth_required_rsp +{ + /// Index of the VIF. + u8_l vif_idx; + /// Authentication status + u16_l status; +}; + +/// Structure containing the parameters of the @ref SM_FT_AUTH_IND +struct sm_ft_auth_ind +{ + /// Index of the VIF. + u8_l vif_idx; + /// Size of the FT elements + u16_l ft_ie_len; + /// Fast Transition elements in the authentication + u32_l ft_ie_buf[0]; +}; +/////////////////////////////////////////////////////////////////////////////// +/////////// For APM messages +/////////////////////////////////////////////////////////////////////////////// +/// Message API of the APM task +enum apm_msg_tag +{ + /// Request to start the AP. + APM_START_REQ = LMAC_FIRST_MSG(TASK_APM), + /// Confirmation of the AP start. + APM_START_CFM, + /// Request to stop the AP. + APM_STOP_REQ, + /// Confirmation of the AP stop. + APM_STOP_CFM, + /// Request to start CAC + APM_START_CAC_REQ, + /// Confirmation of the CAC start + APM_START_CAC_CFM, + /// Request to stop CAC + APM_STOP_CAC_REQ, + /// Confirmation of the CAC stop + APM_STOP_CAC_CFM, + APM_PROBE_CLIENT_REQ, + APM_PROBE_CLIENT_CFM, + APM_PROBE_CLIENT_IND, + + /// MAX number of messages + APM_MAX, +}; + +/// Structure containing the parameters of the @ref APM_START_REQ message. +struct apm_start_req +{ + /// Basic rate set + struct mac_rateset basic_rates; + /// Operating channel on which we have to enable the AP + struct mac_chan_op chan; + /// Offset of the TIM IE in the beacon + u16_l tim_oft; + /// Beacon interval + u16_l bcn_int; + /// Flags (@ref mac_connection_flags) + u32_l flags; + /// Control port Ethertype + u16_l ctrl_port_ethertype; + /// Length of the TIM IE + u8_l tim_len; + /// Index of the VIF for which the AP is started + u8_l vif_idx; + /// Length of the beacon template + u16_l bcn_len; + /// Address, in host memory, to the beacon template + ptr_addr bcn_addr; +}; + +/// Structure containing the parameters of the @ref APM_START_CFM message. +struct apm_start_cfm +{ + /// Status of the AP starting procedure + u8_l status; + /// Index of the VIF for which the AP is started + u8_l vif_idx; + /// Index of the channel context attached to the VIF + u8_l ch_idx; + /// Index of the STA used for BC/MC traffic + u8_l bcmc_idx; +}; + +/// Structure containing the parameters of the @ref APM_STOP_REQ message. +struct apm_stop_req +{ + /// Index of the VIF for which the AP has to be stopped + u8_l vif_idx; +}; + +/// Structure containing the parameters of the @ref APM_START_CAC_REQ message. +struct apm_start_cac_req +{ + /// Channel configuration + struct mac_chan_op chan; + /// Index of the VIF for which the CAC is started + u8_l vif_idx; +}; + +/// Structure containing the parameters of the @ref APM_START_CAC_CFM message. +struct apm_start_cac_cfm +{ + /// Status of the CAC starting procedure + u8_l status; + /// Index of the channel context attached to the VIF for CAC + u8_l ch_idx; +}; + +/// Structure containing the parameters of the @ref APM_STOP_CAC_REQ message. +struct apm_stop_cac_req +{ + /// Index of the VIF for which the CAC has to be stopped + u8_l vif_idx; +}; + +/// Structure containing the parameters of the @ref APM_PROBE_CLIENT_REQ message. +struct apm_probe_client_req +{ + /// Index of the VIF + u8_l vif_idx; + /// Index of the Station to probe + u8_l sta_idx; +}; + +/// Structure containing the parameters of the @ref APM_PROBE_CLIENT_CFM message. +struct apm_probe_client_cfm +{ + /// Status of the probe request + u8_l status; + /// Unique ID to distinguish @ref APM_PROBE_CLIENT_IND message + u32_l probe_id; +}; + +/// Structure containing the parameters of the @ref APM_PROBE_CLIENT_CFM message. +struct apm_probe_client_ind +{ + /// Index of the VIF + u8_l vif_idx; + /// Index of the Station to probe + u8_l sta_idx; + /// Whether client is still present or not + bool_l client_present; + /// Unique ID as returned in @ref APM_PROBE_CLIENT_CFM + u32_l probe_id; +}; + +/////////////////////////////////////////////////////////////////////////////// +/////////// For MESH messages +/////////////////////////////////////////////////////////////////////////////// + +/// Maximum length of the Mesh ID +#define MESH_MESHID_MAX_LEN (32) + +/// Message API of the MESH task +enum mesh_msg_tag +{ + /// Request to start the MP + MESH_START_REQ = LMAC_FIRST_MSG(TASK_MESH), + /// Confirmation of the MP start. + MESH_START_CFM, + + /// Request to stop the MP. + MESH_STOP_REQ, + /// Confirmation of the MP stop. + MESH_STOP_CFM, + + // Request to update the MP + MESH_UPDATE_REQ, + /// Confirmation of the MP update + MESH_UPDATE_CFM, + + /// Request information about a given link + MESH_PEER_INFO_REQ, + /// Response to the MESH_PEER_INFO_REQ message + MESH_PEER_INFO_CFM, + + /// Request automatic establishment of a path with a given mesh STA + MESH_PATH_CREATE_REQ, + /// Confirmation to the MESH_PATH_CREATE_REQ message + MESH_PATH_CREATE_CFM, + + /// Request a path update (delete path, modify next hop mesh STA) + MESH_PATH_UPDATE_REQ, + /// Confirmation to the MESH_PATH_UPDATE_REQ message + MESH_PATH_UPDATE_CFM, + + /// Indication from Host that the indicated Mesh Interface is a proxy for an external STA + MESH_PROXY_ADD_REQ, + + /// Indicate that a connection has been established or lost + MESH_PEER_UPDATE_IND, + /// Notification that a connection has been established or lost (when MPM handled by userspace) + MESH_PEER_UPDATE_NTF = MESH_PEER_UPDATE_IND, + + /// Indicate that a path is now active or inactive + MESH_PATH_UPDATE_IND, + /// Indicate that proxy information have been updated + MESH_PROXY_UPDATE_IND, + + /// MAX number of messages + MESH_MAX, +}; + +/// Structure containing the parameters of the @ref MESH_START_REQ message. +struct mesh_start_req +{ + /// Basic rate set + struct mac_rateset basic_rates; + /// Operating channel on which we have to enable the AP + struct mac_chan_op chan; + /// DTIM Period + u8_l dtim_period; + /// Beacon Interval + u16_l bcn_int; + /// Index of the VIF for which the MP is started + u8_l vif_index; + /// Length of the Mesh ID + u8_l mesh_id_len; + /// Mesh ID + u8_l mesh_id[MESH_MESHID_MAX_LEN]; + /// Address of the IEs to download + u32_l ie_addr; + /// Length of the provided IEs + u8_l ie_len; + /// Indicate if Mesh Peering Management (MPM) protocol is handled in userspace + bool_l user_mpm; + /// Indicate if Mesh Point is using authentication + bool_l is_auth; + /// Indicate which authentication method is used + u8_l auth_id; +}; + +/// Structure containing the parameters of the @ref MESH_START_CFM message. +struct mesh_start_cfm +{ + /// Status of the MP starting procedure + u8_l status; + /// Index of the VIF for which the MP is started + u8_l vif_idx; + /// Index of the channel context attached to the VIF + u8_l ch_idx; + /// Index of the STA used for BC/MC traffic + u8_l bcmc_idx; +}; + +/// Structure containing the parameters of the @ref MESH_STOP_REQ message. +struct mesh_stop_req +{ + /// Index of the VIF for which the MP has to be stopped + u8_l vif_idx; +}; + +/// Structure containing the parameters of the @ref MESH_STOP_CFM message. +struct mesh_stop_cfm +{ + /// Index of the VIF for which the MP has to be stopped + u8_l vif_idx; + /// Status + u8_l status; +}; + +/// Bit fields for mesh_update_req message's flags value +enum mesh_update_flags_bit +{ + /// Root Mode + MESH_UPDATE_FLAGS_ROOT_MODE_BIT = 0, + /// Gate Mode + MESH_UPDATE_FLAGS_GATE_MODE_BIT, + /// Mesh Forwarding + MESH_UPDATE_FLAGS_MESH_FWD_BIT, + /// Local Power Save Mode + MESH_UPDATE_FLAGS_LOCAL_PSM_BIT, +}; + +/// Structure containing the parameters of the @ref MESH_UPDATE_REQ message. +struct mesh_update_req +{ + /// Flags, indicate fields which have been updated + u8_l flags; + /// VIF Index + u8_l vif_idx; + /// Root Mode + u8_l root_mode; + /// Gate Announcement + bool_l gate_announ; + /// Mesh Forwarding + bool_l mesh_forward; + /// Local PS Mode + u8_l local_ps_mode; +}; + +/// Structure containing the parameters of the @ref MESH_UPDATE_CFM message. +struct mesh_update_cfm +{ + /// Status + u8_l status; +}; + +/// Structure containing the parameters of the @ref MESH_PEER_INFO_REQ message. +struct mesh_peer_info_req +{ + ///Index of the station allocated for the peer + u8_l sta_idx; +}; + +/// Structure containing the parameters of the @ref MESH_PEER_INFO_CFM message. +struct mesh_peer_info_cfm +{ + /// Response status + u8_l status; + /// Index of the station allocated for the peer + u8_l sta_idx; + /// Local Link ID + u16_l local_link_id; + /// Peer Link ID + u16_l peer_link_id; + /// Local PS Mode + u8_l local_ps_mode; + /// Peer PS Mode + u8_l peer_ps_mode; + /// Non-peer PS Mode + u8_l non_peer_ps_mode; + /// Link State + u8_l link_state; + u32_l last_bcn_age; +}; + +/// Structure containing the parameters of the @ref MESH_PATH_CREATE_REQ message. +struct mesh_path_create_req +{ + /// Index of the interface on which path has to be created + u8_l vif_idx; + /// Indicate if originator MAC Address is provided + bool_l has_orig_addr; + /// Path Target MAC Address + struct mac_addr tgt_mac_addr; + /// Originator MAC Address + struct mac_addr orig_mac_addr; +}; + +/// Structure containing the parameters of the @ref MESH_PATH_CREATE_CFM message. +struct mesh_path_create_cfm +{ + /// Confirmation status + u8_l status; + /// VIF Index + u8_l vif_idx; +}; + +/// Structure containing the parameters of the @ref MESH_PATH_UPDATE_REQ message. +struct mesh_path_update_req +{ + /// Indicate if path must be deleted + bool_l delete; + /// Index of the interface on which path has to be created + u8_l vif_idx; + /// Path Target MAC Address + struct mac_addr tgt_mac_addr; + /// Next Hop MAC Address + struct mac_addr nhop_mac_addr; +}; + +/// Structure containing the parameters of the @ref MESH_PATH_UPDATE_CFM message. +struct mesh_path_update_cfm +{ + /// Confirmation status + u8_l status; + /// VIF Index + u8_l vif_idx; +}; + +/// Structure containing the parameters of the @ref MESH_PROXY_ADD_REQ message. +struct mesh_proxy_add_req +{ + /// VIF Index + u8_l vif_idx; + /// MAC Address of the External STA + struct mac_addr ext_sta_addr; +}; + +/// Structure containing the parameters of the @ref MESH_PROXY_UPDATE_IND +struct mesh_proxy_update_ind +{ + /// Indicate if proxy information has been added or deleted + bool_l delete; + /// Indicate if we are a proxy for the external STA + bool_l local; + /// VIF Index + u8_l vif_idx; + /// MAC Address of the External STA + struct mac_addr ext_sta_addr; + /// MAC Address of the proxy (only valid if local is false) + struct mac_addr proxy_mac_addr; +}; + +/// Structure containing the parameters of the @ref MESH_PEER_UPDATE_IND message. +struct mesh_peer_update_ind +{ + /// Indicate if connection has been established or lost + bool_l estab; + /// VIF Index + u8_l vif_idx; + /// STA Index + u8_l sta_idx; + /// Peer MAC Address + struct mac_addr peer_addr; +}; + +/// Structure containing the parameters of the @ref MESH_PEER_UPDATE_NTF message. +struct mesh_peer_update_ntf +{ + /// VIF Index + u8_l vif_idx; + /// STA Index + u8_l sta_idx; + /// Mesh Link State + u8_l state; +}; + +/// Structure containing the parameters of the @ref MESH_PATH_UPDATE_IND message. +struct mesh_path_update_ind +{ + /// Indicate if path is deleted or not + bool_l delete; + /// Indicate if path is towards an external STA (not part of MBSS) + bool_l ext_sta; + /// VIF Index + u8_l vif_idx; + /// Path Index + u8_l path_idx; + /// Target MAC Address + struct mac_addr tgt_mac_addr; + /// External STA MAC Address (only if ext_sta is true) + struct mac_addr ext_sta_mac_addr; + /// Next Hop STA Index + u8_l nhop_sta_idx; +}; + +/////////////////////////////////////////////////////////////////////////////// +/////////// For Debug messages +/////////////////////////////////////////////////////////////////////////////// + +/// Messages related to Debug Task +enum dbg_msg_tag +{ + /// Memory read request + DBG_MEM_READ_REQ = LMAC_FIRST_MSG(TASK_DBG), + /// Memory read confirm + DBG_MEM_READ_CFM, + /// Memory write request + DBG_MEM_WRITE_REQ, + /// Memory write confirm + DBG_MEM_WRITE_CFM, + /// Module filter request + DBG_SET_MOD_FILTER_REQ, + /// Module filter confirm + DBG_SET_MOD_FILTER_CFM, + /// Severity filter request + DBG_SET_SEV_FILTER_REQ, + /// Severity filter confirm + DBG_SET_SEV_FILTER_CFM, + /// LMAC/MAC HW fatal error indication + DBG_ERROR_IND, + /// Request to get system statistics + DBG_GET_SYS_STAT_REQ, + /// COnfirmation of system statistics + DBG_GET_SYS_STAT_CFM, + /// Max number of Debug messages + DBG_MAX, +}; + +/// Structure containing the parameters of the @ref DBG_MEM_READ_REQ message. +struct dbg_mem_read_req +{ + u32_l memaddr; +}; + +/// Structure containing the parameters of the @ref DBG_MEM_READ_CFM message. +struct dbg_mem_read_cfm +{ + u32_l memaddr; + u32_l memdata; +}; + +/// Structure containing the parameters of the @ref DBG_MEM_WRITE_REQ message. +struct dbg_mem_write_req +{ + u32_l memaddr; + u32_l memdata; +}; + +/// Structure containing the parameters of the @ref DBG_MEM_WRITE_CFM message. +struct dbg_mem_write_cfm +{ + u32_l memaddr; + u32_l memdata; +}; + +/// Structure containing the parameters of the @ref DBG_SET_MOD_FILTER_REQ message. +struct dbg_set_mod_filter_req +{ + /// Bit field indicating for each module if the traces are enabled or not + u32_l mod_filter; +}; + +/// Structure containing the parameters of the @ref DBG_SEV_MOD_FILTER_REQ message. +struct dbg_set_sev_filter_req +{ + /// Bit field indicating the severity threshold for the traces + u32_l sev_filter; +}; + +/// Structure containing the parameters of the @ref DBG_GET_SYS_STAT_CFM message. +struct dbg_get_sys_stat_cfm +{ + /// Time spent in CPU sleep since last reset of the system statistics + u32_l cpu_sleep_time; + /// Time spent in DOZE since last reset of the system statistics + u32_l doze_time; + /// Total time spent since last reset of the system statistics + u32_l stats_time; +}; + +/////////////////////////////////////////////////////////////////////////////// +/////////// For TDLS messages +/////////////////////////////////////////////////////////////////////////////// + +/// List of messages related to the task. +enum tdls_msg_tag +{ + /// TDLS channel Switch Request. + TDLS_CHAN_SWITCH_REQ = LMAC_FIRST_MSG(TASK_TDLS), + /// TDLS channel switch confirmation. + TDLS_CHAN_SWITCH_CFM, + /// TDLS channel switch indication. + TDLS_CHAN_SWITCH_IND, + /// TDLS channel switch to base channel indication. + TDLS_CHAN_SWITCH_BASE_IND, + /// TDLS cancel channel switch request. + TDLS_CANCEL_CHAN_SWITCH_REQ, + /// TDLS cancel channel switch confirmation. + TDLS_CANCEL_CHAN_SWITCH_CFM, + /// TDLS peer power save indication. + TDLS_PEER_PS_IND, + /// TDLS peer traffic indication request. + TDLS_PEER_TRAFFIC_IND_REQ, + /// TDLS peer traffic indication confirmation. + TDLS_PEER_TRAFFIC_IND_CFM, + /// MAX number of messages + TDLS_MAX +}; + +/// Structure containing the parameters of the @ref TDLS_CHAN_SWITCH_REQ message +struct tdls_chan_switch_req +{ + /// Index of the VIF + u8_l vif_index; + /// STA Index + u8_l sta_idx; + /// MAC address of the TDLS station + struct mac_addr peer_mac_addr; + /// Flag to indicate if the TDLS peer is the TDLS link initiator + bool_l initiator; + /// Channel configuration + struct mac_chan_op chan; + /// Operating class + u8_l op_class; +}; + +/// Structure containing the parameters of the @ref TDLS_CANCEL_CHAN_SWITCH_REQ message +struct tdls_cancel_chan_switch_req +{ + /// Index of the VIF + u8_l vif_index; + /// STA Index + u8_l sta_idx; + /// MAC address of the TDLS station + struct mac_addr peer_mac_addr; +}; + + +/// Structure containing the parameters of the @ref TDLS_CHAN_SWITCH_CFM message +struct tdls_chan_switch_cfm +{ + /// Status of the operation + u8_l status; +}; + +/// Structure containing the parameters of the @ref TDLS_CANCEL_CHAN_SWITCH_CFM message +struct tdls_cancel_chan_switch_cfm +{ + /// Status of the operation + u8_l status; +}; + +/// Structure containing the parameters of the @ref TDLS_CHAN_SWITCH_IND message +struct tdls_chan_switch_ind +{ + /// VIF Index + u8_l vif_index; + /// Channel Context Index + u8_l chan_ctxt_index; + /// Status of the operation + u8_l status; +}; + +/// Structure containing the parameters of the @ref TDLS_CHAN_SWITCH_BASE_IND message +struct tdls_chan_switch_base_ind +{ + /// VIF Index + u8_l vif_index; + /// Channel Context index + u8_l chan_ctxt_index; +}; + +/// Structure containing the parameters of the @ref TDLS_PEER_PS_IND message +struct tdls_peer_ps_ind +{ + /// VIF Index + u8_l vif_index; + /// STA Index + u8_l sta_idx; + /// MAC ADDR of the TDLS STA + struct mac_addr peer_mac_addr; + /// Flag to indicate if the TDLS peer is going to sleep + bool ps_on; +}; + +/// Structure containing the parameters of the @ref TDLS_PEER_TRAFFIC_IND_REQ message +struct tdls_peer_traffic_ind_req +{ + /// VIF Index + u8_l vif_index; + /// STA Index + u8_l sta_idx; + // MAC ADDR of the TDLS STA + struct mac_addr peer_mac_addr; + /// Dialog token + u8_l dialog_token; + /// TID of the latest MPDU transmitted over the TDLS direct link to the TDLS STA + u8_l last_tid; + /// Sequence number of the latest MPDU transmitted over the TDLS direct link + /// to the TDLS STA + u16_l last_sn; +}; + +/// Structure containing the parameters of the @ref TDLS_PEER_TRAFFIC_IND_CFM message +struct tdls_peer_traffic_ind_cfm +{ + /// Status of the operation + u8_l status; +}; + +#if defined(CONFIG_ECRNX_P2P) +enum p2p_listen_msg_tag +{ + /// Scan request from host. + P2P_LISTEN_START_REQ = LMAC_FIRST_MSG(TASK_P2P_LISTEN), + P2P_LISTEN_START_CFM, + P2P_CANCEL_LISTEN_REQ, + P2P_CANCEL_LISTEN_CFM, + + P2P_LISTEN_MAX, +}; + +struct p2p_listen_start_req +{ + /// Index of the interface on which path has to be created + u8_l vif_idx; +}; + +struct p2p_listen_start_cfm +{ + /// p2p listen task status + bool_l status; + /// VIF Index + u8_l vif_idx; +}; + + +struct p2p_cancel_listen_req +{ + /// Index of the interface on which path has to be created + u8_l vif_idx; +}; + +struct p2p_cancel_listen_cfm +{ + /// p2p listen task status + bool_l status; + /// VIF Index + u8_l vif_idx; +}; +#endif + +#endif // LMAC_MSG_H_ diff --git a/drivers/net/wireless/eswin/lmac_types.h b/drivers/net/wireless/eswin/lmac_types.h new file mode 100644 index 000000000000..b8dcd2b1d9f9 --- /dev/null +++ b/drivers/net/wireless/eswin/lmac_types.h @@ -0,0 +1,69 @@ +/** + **************************************************************************************** + * + * @file co_types.h + * + * @brief This file replaces the need to include stdint or stdbool typical headers, + * which may not be available in all toolchains, and adds new types + * + * Copyright (C) ESWIN 2015-2020 + * + * $Rev: $ + * + **************************************************************************************** + */ + +#ifndef _LMAC_INT_H_ +#define _LMAC_INT_H_ + + +/** + **************************************************************************************** + * @addtogroup CO_INT + * @ingroup COMMON + * @brief Common integer standard types (removes use of stdint) + * + * @{ + **************************************************************************************** + */ + + +/* + * DEFINES + **************************************************************************************** + */ + +#include <linux/version.h> +#include <linux/types.h> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) +#include <linux/bits.h> +#else +#include <linux/bitops.h> +#endif + +#ifdef CONFIG_ECRNX_TL4 +typedef uint16_t u8_l; +typedef int16_t s8_l; +typedef uint16_t bool_l; +#else +typedef uint8_t u8_l; +typedef int8_t s8_l; +typedef bool bool_l; +#endif +typedef uint16_t u16_l; +typedef int16_t s16_l; +typedef uint32_t u32_l; +typedef int32_t s32_l; +typedef uint64_t u64_l; + +#define ALIGNED(n) __attribute__((aligned (n))) + +#if (__SIZEOF_POINTER__ == 8) +#define ptr_addr u64_l +#else +#define ptr_addr u32_l +#endif + + +/// @} CO_INT +#endif // _LMAC_INT_H_ diff --git a/drivers/net/wireless/eswin/reg_access.h b/drivers/net/wireless/eswin/reg_access.h new file mode 100644 index 000000000000..16a02eb3ba29 --- /dev/null +++ b/drivers/net/wireless/eswin/reg_access.h @@ -0,0 +1,156 @@ +/** + ****************************************************************************** + * + * @file reg_access.h + * + * @brief Definitions and macros for MAC HW and platform register accesses + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef REG_ACCESS_H_ +#define REG_ACCESS_H_ + +/***************************************************************************** + * Addresses within ECRNX_ADDR_CPU + *****************************************************************************/ +#define RAM_LMAC_FW_ADDR 0x00000000 + +/***************************************************************************** + * Addresses within ECRNX_ADDR_SYSTEM + *****************************************************************************/ +/* Shard RAM */ +#define SHARED_RAM_START_ADDR 0x00000000 + +/* IPC registers */ +#define IPC_REG_BASE_ADDR 0x00800000 + +/* System Controller Registers */ +#define SYSCTRL_SIGNATURE_ADDR 0x00900000 +// old diag register name +#define SYSCTRL_DIAG_CONF_ADDR 0x00900068 +#define SYSCTRL_PHYDIAG_CONF_ADDR 0x00900074 +#define SYSCTRL_RIUDIAG_CONF_ADDR 0x00900078 +// new diag register name +#define SYSCTRL_DIAG_CONF0 0x00900064 +#define SYSCTRL_DIAG_CONF1 0x00900068 +#define SYSCTRL_DIAG_CONF2 0x00900074 +#define SYSCTRL_DIAG_CONF3 0x00900078 +#define SYSCTRL_MISC_CNTL_ADDR 0x009000E0 +#define BOOTROM_ENABLE BIT(4) +#define FPGA_B_RESET BIT(1) +#define SOFT_RESET BIT(0) + +/* MAC platform */ +#define NXMAC_VERSION_1_ADDR 0x00B00004 +#define NXMAC_MU_MIMO_TX_BIT BIT(19) +#define NXMAC_BFMER_BIT BIT(18) +#define NXMAC_BFMEE_BIT BIT(17) +#define NXMAC_MAC_80211MH_FORMAT_BIT BIT(16) +#define NXMAC_COEX_BIT BIT(14) +#define NXMAC_WAPI_BIT BIT(13) +#define NXMAC_TPC_BIT BIT(12) +#define NXMAC_VHT_BIT BIT(11) +#define NXMAC_HT_BIT BIT(10) +#define NXMAC_GCMP_BIT BIT(9) +#define NXMAC_RCE_BIT BIT(8) +#define NXMAC_CCMP_BIT BIT(7) +#define NXMAC_TKIP_BIT BIT(6) +#define NXMAC_WEP_BIT BIT(5) +#define NXMAC_SECURITY_BIT BIT(4) +#define NXMAC_SME_BIT BIT(3) +#define NXMAC_HCCA_BIT BIT(2) +#define NXMAC_EDCA_BIT BIT(1) +#define NXMAC_QOS_BIT BIT(0) + +#define NXMAC_RX_CNTRL_ADDR 0x00B00060 +#define NXMAC_EN_DUPLICATE_DETECTION_BIT BIT(31) +#define NXMAC_ACCEPT_UNKNOWN_BIT BIT(30) +#define NXMAC_ACCEPT_OTHER_DATA_FRAMES_BIT BIT(29) +#define NXMAC_ACCEPT_QO_S_NULL_BIT BIT(28) +#define NXMAC_ACCEPT_QCFWO_DATA_BIT BIT(27) +#define NXMAC_ACCEPT_Q_DATA_BIT BIT(26) +#define NXMAC_ACCEPT_CFWO_DATA_BIT BIT(25) +#define NXMAC_ACCEPT_DATA_BIT BIT(24) +#define NXMAC_ACCEPT_OTHER_CNTRL_FRAMES_BIT BIT(23) +#define NXMAC_ACCEPT_CF_END_BIT BIT(22) +#define NXMAC_ACCEPT_ACK_BIT BIT(21) +#define NXMAC_ACCEPT_CTS_BIT BIT(20) +#define NXMAC_ACCEPT_RTS_BIT BIT(19) +#define NXMAC_ACCEPT_PS_POLL_BIT BIT(18) +#define NXMAC_ACCEPT_BA_BIT BIT(17) +#define NXMAC_ACCEPT_BAR_BIT BIT(16) +#define NXMAC_ACCEPT_OTHER_MGMT_FRAMES_BIT BIT(15) +#define NXMAC_ACCEPT_BFMEE_FRAMES_BIT BIT(14) +#define NXMAC_ACCEPT_ALL_BEACON_BIT BIT(13) +#define NXMAC_ACCEPT_NOT_EXPECTED_BA_BIT BIT(12) +#define NXMAC_ACCEPT_DECRYPT_ERROR_FRAMES_BIT BIT(11) +#define NXMAC_ACCEPT_BEACON_BIT BIT(10) +#define NXMAC_ACCEPT_PROBE_RESP_BIT BIT(9) +#define NXMAC_ACCEPT_PROBE_REQ_BIT BIT(8) +#define NXMAC_ACCEPT_MY_UNICAST_BIT BIT(7) +#define NXMAC_ACCEPT_UNICAST_BIT BIT(6) +#define NXMAC_ACCEPT_ERROR_FRAMES_BIT BIT(5) +#define NXMAC_ACCEPT_OTHER_BSSID_BIT BIT(4) +#define NXMAC_ACCEPT_BROADCAST_BIT BIT(3) +#define NXMAC_ACCEPT_MULTICAST_BIT BIT(2) +#define NXMAC_DONT_DECRYPT_BIT BIT(1) +#define NXMAC_EXC_UNENCRYPTED_BIT BIT(0) + +#define NXMAC_DEBUG_PORT_SEL_ADDR 0x00B00510 +#define NXMAC_SW_SET_PROFILING_ADDR 0x00B08564 +#define NXMAC_SW_CLEAR_PROFILING_ADDR 0x00B08568 + +/* Modem Status */ +#define MDM_HDMCONFIG_ADDR 0x00C00000 +#define MDM_HDMVERSION_ADDR 0x00C0003C + +/* Clock gating configuration */ +#define MDM_MEMCLKCTRL0_ADDR 0x00C00848 +#define MDM_CLKGATEFCTRL0_ADDR 0x00C00874 +#define CRM_CLKGATEFCTRL0_ADDR 0x00940010 + +/* AGC (trident) */ +#define AGC_ECRNXAGCCNTL_ADDR 0x00C02060 + +/* LDPC RAM*/ +#define PHY_LDPC_RAM_ADDR 0x00C09000 + +/* FCU (elma )*/ +#define FCU_ECRNXFCAGCCNTL_ADDR 0x00C09034 + +/* AGC RAM */ +#define PHY_AGC_UCODE_ADDR 0x00C0A000 + +/* RIU */ +#define RIU_ECRNXVERSION_ADDR 0x00C0B000 +#define RIU_ECRNXDYNAMICCONFIG_ADDR 0x00C0B008 +#define RIU_AGCMEMBISTSTAT_ADDR 0x00C0B238 +#define RIU_AGCMEMSIGNATURESTAT_ADDR 0x00C0B23C +#define RIU_ECRNXAGCCNTL_ADDR 0x00C0B390 + +/* FCU RAM */ +#define RC_SYSTEM_CONFIGURATION_ADDR 0x00C0C000 +#define RC_ACCES_TO_CATAXIA_REG_ADDR 0x00C0C004 + +/* RF ITF */ +#define FPGAB_MPIF_SEL_ADDR 0x00C10030 +#define RF_V6_DIAGPORT_CONF1_ADDR 0x00C10010 +#define RF_v6_PHYDIAG_CONF1_ADDR 0x00C10018 + +#define RF_V7_DIAGPORT_CONF1_ADDR 0x00F10010 +#define RF_v7_PHYDIAG_CONF1_ADDR 0x00F10018 + +/***************************************************************************** + * Macros for generated register files + *****************************************************************************/ +/* Macros for IPC registers access (used in reg_ipc_app.h) */ +#define REG_IPC_APP_RD(env, INDEX) \ + (*(volatile u32*)((u8*)env + IPC_REG_BASE_ADDR + 4*(INDEX))) + +#define REG_IPC_APP_WR(env, INDEX, value) \ + (*(volatile u32*)((u8*)env + IPC_REG_BASE_ADDR + 4*(INDEX)) = value) + +#endif /* REG_ACCESS_H_ */ diff --git a/drivers/net/wireless/eswin/reg_ipc_app.h b/drivers/net/wireless/eswin/reg_ipc_app.h new file mode 100644 index 000000000000..9be4151a7c28 --- /dev/null +++ b/drivers/net/wireless/eswin/reg_ipc_app.h @@ -0,0 +1,299 @@ +/** + **************************************************************************************** + * + * @file ecrnx_ipc_app.h + * + * @brief IPC module register definitions + * + * Copyright (C) ESWIN 2015-2020 + * + **************************************************************************************** + */ + +#ifndef _REG_IPC_APP_H_ +#define _REG_IPC_APP_H_ + +#ifndef __KERNEL__ +#include <stdint.h> +#include "arch.h" +#else +#include "ipc_compat.h" +#endif +#include "reg_access.h" + +#define REG_IPC_APP_DECODING_MASK 0x0000007F + +/** + * @brief APP2EMB_TRIGGER register definition + * <pre> + * Bits Field Name Reset Value + * ----- ------------------ ----------- + * 31:00 APP2EMB_TRIGGER 0x0 + * </pre> + */ +#define IPC_APP2EMB_TRIGGER_ADDR 0x12000000 +#define IPC_APP2EMB_TRIGGER_OFFSET 0x00000000 +#define IPC_APP2EMB_TRIGGER_INDEX 0x00000000 +#define IPC_APP2EMB_TRIGGER_RESET 0x00000000 + +__INLINE u32 ipc_app2emb_trigger_get(void *env) +{ + return REG_IPC_APP_RD(env, IPC_APP2EMB_TRIGGER_INDEX); +} + +__INLINE void ipc_app2emb_trigger_set(void *env, u32 value) +{ + REG_IPC_APP_WR(env, IPC_APP2EMB_TRIGGER_INDEX, value); +} + +// field definitions +#define IPC_APP2EMB_TRIGGER_MASK ((u32)0xFFFFFFFF) +#define IPC_APP2EMB_TRIGGER_LSB 0 +#define IPC_APP2EMB_TRIGGER_WIDTH ((u32)0x00000020) + +#define IPC_APP2EMB_TRIGGER_RST 0x0 + +__INLINE u32 ipc_app2emb_trigger_getf(void *env) +{ + u32 localVal = REG_IPC_APP_RD(env, IPC_APP2EMB_TRIGGER_INDEX); + ASSERT_ERR((localVal & ~((u32)0xFFFFFFFF)) == 0); + return (localVal >> 0); +} + +__INLINE void ipc_app2emb_trigger_setf(void *env, u32 app2embtrigger) +{ + ASSERT_ERR((((u32)app2embtrigger << 0) & ~((u32)0xFFFFFFFF)) == 0); + REG_IPC_APP_WR(env, IPC_APP2EMB_TRIGGER_INDEX, (u32)app2embtrigger << 0); +} + +/** + * @brief EMB2APP_RAWSTATUS register definition + * <pre> + * Bits Field Name Reset Value + * ----- ------------------ ----------- + * 31:00 EMB2APP_RAWSTATUS 0x0 + * </pre> + */ +#define IPC_EMB2APP_RAWSTATUS_ADDR 0x12000004 +#define IPC_EMB2APP_RAWSTATUS_OFFSET 0x00000004 +#define IPC_EMB2APP_RAWSTATUS_INDEX 0x00000001 +#define IPC_EMB2APP_RAWSTATUS_RESET 0x00000000 + +__INLINE u32 ipc_emb2app_rawstatus_get(void *env) +{ + return REG_IPC_APP_RD(env, IPC_EMB2APP_RAWSTATUS_INDEX); +} + +__INLINE void ipc_emb2app_rawstatus_set(void *env, u32 value) +{ + REG_IPC_APP_WR(env, IPC_EMB2APP_RAWSTATUS_INDEX, value); +} + +// field definitions +#define IPC_EMB2APP_RAWSTATUS_MASK ((u32)0xFFFFFFFF) +#define IPC_EMB2APP_RAWSTATUS_LSB 0 +#define IPC_EMB2APP_RAWSTATUS_WIDTH ((u32)0x00000020) + +#define IPC_EMB2APP_RAWSTATUS_RST 0x0 + +__INLINE u32 ipc_emb2app_rawstatus_getf(void *env) +{ + u32 localVal = REG_IPC_APP_RD(env, IPC_EMB2APP_RAWSTATUS_INDEX); + ASSERT_ERR((localVal & ~((u32)0xFFFFFFFF)) == 0); + return (localVal >> 0); +} + +/** + * @brief EMB2APP_ACK register definition + * <pre> + * Bits Field Name Reset Value + * ----- ------------------ ----------- + * 31:00 EMB2APP_ACK 0x0 + * </pre> + */ +#define IPC_EMB2APP_ACK_ADDR 0x12000008 +#define IPC_EMB2APP_ACK_OFFSET 0x00000008 +#define IPC_EMB2APP_ACK_INDEX 0x00000002 +#define IPC_EMB2APP_ACK_RESET 0x00000000 + +__INLINE u32 ipc_emb2app_ack_get(void *env) +{ + return REG_IPC_APP_RD(env, IPC_EMB2APP_ACK_INDEX); +} + +__INLINE void ipc_emb2app_ack_clear(void *env, u32 value) +{ + REG_IPC_APP_WR(env, IPC_EMB2APP_ACK_INDEX, value); +} + +// field definitions +#define IPC_EMB2APP_ACK_MASK ((u32)0xFFFFFFFF) +#define IPC_EMB2APP_ACK_LSB 0 +#define IPC_EMB2APP_ACK_WIDTH ((u32)0x00000020) + +#define IPC_EMB2APP_ACK_RST 0x0 + +__INLINE u32 ipc_emb2app_ack_getf(void *env) +{ + u32 localVal = REG_IPC_APP_RD(env, IPC_EMB2APP_ACK_INDEX); + ASSERT_ERR((localVal & ~((u32)0xFFFFFFFF)) == 0); + return (localVal >> 0); +} + +__INLINE void ipc_emb2app_ack_clearf(void *env, u32 emb2appack) +{ + ASSERT_ERR((((u32)emb2appack << 0) & ~((u32)0xFFFFFFFF)) == 0); + REG_IPC_APP_WR(env, IPC_EMB2APP_ACK_INDEX, (u32)emb2appack << 0); +} + +/** + * @brief EMB2APP_UNMASK_SET register definition + * <pre> + * Bits Field Name Reset Value + * ----- ------------------ ----------- + * 31:00 EMB2APP_UNMASK 0x0 + * </pre> + */ +#define IPC_EMB2APP_UNMASK_SET_ADDR 0x1200000C +#define IPC_EMB2APP_UNMASK_SET_OFFSET 0x0000000C +#define IPC_EMB2APP_UNMASK_SET_INDEX 0x00000003 +#define IPC_EMB2APP_UNMASK_SET_RESET 0x00000000 + +__INLINE u32 ipc_emb2app_unmask_get(void *env) +{ + return REG_IPC_APP_RD(env, IPC_EMB2APP_UNMASK_SET_INDEX); +} + +__INLINE void ipc_emb2app_unmask_set(void *env, u32 value) +{ + REG_IPC_APP_WR(env, IPC_EMB2APP_UNMASK_SET_INDEX, value); +} + +// field definitions +#define IPC_EMB2APP_UNMASK_MASK ((u32)0xFFFFFFFF) +#define IPC_EMB2APP_UNMASK_LSB 0 +#define IPC_EMB2APP_UNMASK_WIDTH ((u32)0x00000020) + +#define IPC_EMB2APP_UNMASK_RST 0x0 + +__INLINE u32 ipc_emb2app_unmask_getf(void *env) +{ + u32 localVal = REG_IPC_APP_RD(env, IPC_EMB2APP_UNMASK_SET_INDEX); + ASSERT_ERR((localVal & ~((u32)0xFFFFFFFF)) == 0); + return (localVal >> 0); +} + +__INLINE void ipc_emb2app_unmask_setf(void *env, u32 emb2appunmask) +{ + ASSERT_ERR((((u32)emb2appunmask << 0) & ~((u32)0xFFFFFFFF)) == 0); + REG_IPC_APP_WR(env, IPC_EMB2APP_UNMASK_SET_INDEX, (u32)emb2appunmask << 0); +} + +/** + * @brief EMB2APP_UNMASK_CLEAR register definition + * <pre> + * Bits Field Name Reset Value + * ----- ------------------ ----------- + * 31:00 EMB2APP_UNMASK 0x0 + * </pre> + */ +#define IPC_EMB2APP_UNMASK_CLEAR_ADDR 0x12000010 +#define IPC_EMB2APP_UNMASK_CLEAR_OFFSET 0x00000010 +#define IPC_EMB2APP_UNMASK_CLEAR_INDEX 0x00000004 +#define IPC_EMB2APP_UNMASK_CLEAR_RESET 0x00000000 + +__INLINE void ipc_emb2app_unmask_clear(void *env, u32 value) +{ + REG_IPC_APP_WR(env, IPC_EMB2APP_UNMASK_CLEAR_INDEX, value); +} + +// fields defined in symmetrical set/clear register +__INLINE void ipc_emb2app_unmask_clearf(void *env, u32 emb2appunmask) +{ + ASSERT_ERR((((u32)emb2appunmask << 0) & ~((u32)0xFFFFFFFF)) == 0); + REG_IPC_APP_WR(env, IPC_EMB2APP_UNMASK_CLEAR_INDEX, (u32)emb2appunmask << 0); +} + +/** + * @brief EMB2APP_STATUS register definition + * <pre> + * Bits Field Name Reset Value + * ----- ------------------ ----------- + * 31:00 EMB2APP_STATUS 0x0 + * </pre> + */ +#ifdef CONFIG_ECRNX_OLD_IPC +#define IPC_EMB2APP_STATUS_ADDR 0x12000014 +#define IPC_EMB2APP_STATUS_OFFSET 0x00000014 +#define IPC_EMB2APP_STATUS_INDEX 0x00000005 +#else +#define IPC_EMB2APP_STATUS_ADDR 0x1200001C +#define IPC_EMB2APP_STATUS_OFFSET 0x0000001C +#define IPC_EMB2APP_STATUS_INDEX 0x00000007 +#endif +#define IPC_EMB2APP_STATUS_RESET 0x00000000 + +__INLINE u32 ipc_emb2app_status_get(void *env) +{ + return REG_IPC_APP_RD(env, IPC_EMB2APP_STATUS_INDEX); +} + +__INLINE void ipc_emb2app_status_set(void *env, u32 value) +{ + REG_IPC_APP_WR(env, IPC_EMB2APP_STATUS_INDEX, value); +} + +// field definitions +#define IPC_EMB2APP_STATUS_MASK ((u32)0xFFFFFFFF) +#define IPC_EMB2APP_STATUS_LSB 0 +#define IPC_EMB2APP_STATUS_WIDTH ((u32)0x00000020) + +#define IPC_EMB2APP_STATUS_RST 0x0 + +__INLINE u32 ipc_emb2app_status_getf(void *env) +{ + u32 localVal = REG_IPC_APP_RD(env, IPC_EMB2APP_STATUS_INDEX); + ASSERT_ERR((localVal & ~((u32)0xFFFFFFFF)) == 0); + return (localVal >> 0); +} + +/** + * @brief APP_SIGNATURE register definition + * <pre> + * Bits Field Name Reset Value + * ----- ------------------ ---------- + * 31:00 APP_SIGNATURE 0x0 + * </pre> + */ +#define IPC_APP_SIGNATURE_ADDR 0x12000040 +#define IPC_APP_SIGNATURE_OFFSET 0x00000040 +#define IPC_APP_SIGNATURE_INDEX 0x00000010 +#define IPC_APP_SIGNATURE_RESET 0x00000000 + +__INLINE u32 ipc_app_signature_get(void *env) +{ + return REG_IPC_APP_RD(env, IPC_APP_SIGNATURE_INDEX); +} + +__INLINE void ipc_app_signature_set(void *env, u32 value) +{ + REG_IPC_APP_WR(env, IPC_APP_SIGNATURE_INDEX, value); +} + +// field definitions +#define IPC_APP_SIGNATURE_MASK ((u32)0xFFFFFFFF) +#define IPC_APP_SIGNATURE_LSB 0 +#define IPC_APP_SIGNATURE_WIDTH ((u32)0x00000020) + +#define IPC_APP_SIGNATURE_RST 0x0 + +__INLINE u32 ipc_app_signature_getf(void *env) +{ + u32 localVal = REG_IPC_APP_RD(env, IPC_APP_SIGNATURE_INDEX); + ASSERT_ERR((localVal & ~((u32)0xFFFFFFFF)) == 0); + return (localVal >> 0); +} + + +#endif // _REG_IPC_APP_H_ + diff --git a/drivers/net/wireless/eswin/sdio/core.c b/drivers/net/wireless/eswin/sdio/core.c new file mode 100644 index 000000000000..a7da3695b1a0 --- /dev/null +++ b/drivers/net/wireless/eswin/sdio/core.c @@ -0,0 +1,774 @@ +/**
+ ******************************************************************************
+ *
+ * @file core.c
+ *
+ * @brief sdio core function definitions
+ *
+ * Copyright (C) ESWIN 2015-2020
+ *
+ ******************************************************************************
+ */
+#include <linux/firmware.h>
+#include <linux/kthread.h>
+#include "core.h"
+#include "fw.h"
+#include <uapi/linux/sched/types.h>
+//#include "debug.h"
+#include "ecrnx_platform.h"
+#include "sdio.h"
+#include "ecrnx_rx.h"
+#include "sdio_host_interface.h"
+#include "eswin_utils.h"
+
+bool loopback;
+module_param(loopback, bool, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(loopback, "HIF loopback");
+
+int power_save;
+module_param(power_save, int, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(power_save, "Power Save(0: disable, 1:enable)");
+
+int disable_cqm = 0;
+module_param(disable_cqm, int, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(disable_cqm, "Disable CQM (0: disable, 1:enable)");
+
+
+int listen_interval = 0;
+module_param(listen_interval, int, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(listen_interval, "Listen Interval");
+
+int bss_max_idle = 0;
+module_param(bss_max_idle, int, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(bss_max_idle, "BSS Max Idle");
+
+
+bool dl_fw;
+module_param(dl_fw, bool, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(dl_fw, "download firmware");
+
+
+#ifdef CONFIG_ECRNX_WIFO_CAIL
+bool amt_mode;
+module_param(amt_mode, bool, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(amt_mode, "calibrate mode");
+#endif
+
+bool set_gain;
+module_param(set_gain, bool, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(set_gain, "set gain delta");
+
+char *fw_name;
+struct eswin *pEswin;
+
+module_param(fw_name, charp, S_IRUGO);
+MODULE_PARM_DESC(fw_name, "Firmware file name");
+
+#if 0
+static void eswin_fw_ready(struct sk_buff *skb, struct eswin * tr)
+{
+ struct ieee80211_hw *hw = tr->hw;
+ struct wim_ready *ready;
+ struct wim *wim = (struct wim *)skb->data;
+
+ ECRNX_PRINT(" %s entry!!", __func__);
+ ready = (struct wim_ready *) (wim + 1);
+
+ ECRNX_PRINT(" %s -- version: 0x%x", __func__, ready->v.version);
+ ECRNX_PRINT(" %s -- rx_head_size: %d", __func__, ready->v.rx_head_size);
+ ECRNX_PRINT(" %s -- tx_head_size: %d", __func__, ready->v.tx_head_size);
+ ECRNX_PRINT(" %s -- buffer_size: %d", __func__, ready->v.buffer_size);
+
+ tr->fwinfo.ready = 2;
+ tr->fwinfo.version = ready->v.version;
+ tr->fwinfo.rx_head_size = ready->v.rx_head_size;
+ tr->fwinfo.tx_head_size = ready->v.tx_head_size;
+ tr->fwinfo.payload_align = ready->v.payload_align;
+ tr->fwinfo.buffer_size = ready->v.buffer_size;
+
+ ECRNX_PRINT(" %s -- cap_mask: 0x%llx", __func__, ready->v.cap.cap);
+ ECRNX_PRINT(" %s -- cap_li: %d, %d", __func__, ready->v.cap.listen_interval, listen_interval);
+ ECRNX_PRINT(" %s -- cap_idle: %d, %d", __func__, ready->v.cap.bss_max_idle, bss_max_idle);
+
+ tr->cap.cap_mask = ready->v.cap.cap;
+ tr->cap.listen_interval = ready->v.cap.listen_interval;
+ tr->cap.bss_max_idle = ready->v.cap.bss_max_idle;
+
+ if (listen_interval) {
+ hw->max_listen_interval = listen_interval;
+ tr->cap.listen_interval = listen_interval;
+ }
+
+ if (bss_max_idle) {
+ tr->cap.bss_max_idle = bss_max_idle;
+ }
+
+ dev_kfree_skb(skb);
+ ECRNX_PRINT(" %s exit!!", __func__);
+}
+#endif
+static unsigned int sdio_tx_packets = 0; +static struct timer_list sdio_tx_timer = {0}; +
+#define SDIO_TX_TIMER_TIMEOUT_US (200) +
+void sdio_tx_queue_init(struct tx_buff_queue * queue)
+{
+ queue->head = NULL;
+ queue->tail = NULL;
+ queue->count = 0;
+ spin_lock_init(&queue->lock);
+}
+
+void sdio_tx_queue_push(struct tx_buff_queue * queue, struct tx_buff_node *node)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&queue->lock, flags);
+ if (queue->head) {
+ queue->tail->next = node;
+ } else {
+ queue->head = node;
+ }
+
+ queue->tail = node;;
+ queue->tail->next = NULL;
+ queue->count++;
+
+ spin_unlock_irqrestore(&queue->lock, flags);
+
+ //ECRNX_PRINT(" queue push count: %d\n", queue->count);
+ //ECRNX_PRINT(" queue push head: %#x\n", queue->head);
+}
+
+struct tx_buff_node *sdio_tx_queue_pop(struct tx_buff_queue *queue)
+{
+ unsigned long flags;
+ struct tx_buff_node *res = NULL;
+
+ //ECRNX_PRINT(" queue pop count: %d\n", queue->count);
+ //ECRNX_PRINT(" queue pop head: %#x\n", queue->head);
+
+ spin_lock_irqsave(&queue->lock, flags);
+
+ if (queue->count) {
+ res = queue->head;
+ queue->head = res->next;
+ res->next = NULL;
+ queue->count--;
+ }
+
+ spin_unlock_irqrestore(&queue->lock, flags);
+ return res;
+}
+
+struct tx_buff_node *sdio_tx_queue_peek(struct tx_buff_queue *queue)
+{
+ unsigned long flags;
+ struct tx_buff_node *res = NULL;
+
+ spin_lock_irqsave(&queue->lock, flags);
+
+ if (queue->count) {
+ res = queue->head;
+ }
+
+ spin_unlock_irqrestore(&queue->lock, flags);
+ return res;
+}
+
+struct tx_buff_node * sdio_tx_node_alloc(struct eswin *tr)
+{
+ struct tx_buff_node * res;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tr->tx_lock,flags);
+ res = tr->tx_node_head;
+ if(res == NULL)
+ {
+ spin_unlock_irqrestore(&tr->tx_lock, flags);
+ return NULL;
+ }
+ tr->tx_node_head = tr->tx_node_head->next;
+ res->next = NULL;
+ tr->tx_node_num--;
+ spin_unlock_irqrestore(&tr->tx_lock, flags);
+
+ return res;
+}
+
+void sdio_tx_node_free(struct eswin *tr, struct tx_buff_node * node)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&tr->tx_lock,flags);
+ kfree(node->buff);
+ node->buff = NULL;
+ node->next = tr->tx_node_head;
+ tr->tx_node_head = node;
+ tr->tx_node_num++;
+ spin_unlock_irqrestore(&tr->tx_lock, flags);
+}
+
+void sdio_tx_pkg_queue_init(struct tx_buff_pkg_queue * queue)
+{
+ queue->head = NULL;
+ queue->tail = NULL;
+ queue->count = 0;
+ spin_lock_init(&queue->lock);
+}
+
+void sdio_tx_pkg_queue_push(struct tx_buff_pkg_queue * queue, struct tx_buff_pkg_node *node)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&queue->lock, flags);
+ if (queue->head) {
+ queue->tail->next = node;
+ } else {
+ queue->head = node;
+ }
+
+ queue->tail = node;;
+ queue->tail->next = NULL;
+ queue->count++;
+
+ spin_unlock_irqrestore(&queue->lock, flags);
+}
+
+struct tx_buff_pkg_node *sdio_tx_pkg_queue_pop(struct tx_buff_pkg_queue *queue)
+{
+ unsigned long flags;
+ struct tx_buff_pkg_node *res = NULL;
+
+ spin_lock_irqsave(&queue->lock, flags);
+
+ if (queue->count) {
+ res = queue->head;
+ queue->head = res->next;
+ res->next = NULL;
+ queue->count--;
+ }
+
+ spin_unlock_irqrestore(&queue->lock, flags);
+ return res;
+}
+
+struct tx_buff_pkg_node * sdio_tx_pkg_node_alloc(struct eswin *tr)
+{
+ struct tx_buff_pkg_node * res;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tr->tx_pkg_lock,flags);
+ res = tr->tx_pkg_node_head;
+ if(res == NULL)
+ {
+ spin_unlock_irqrestore(&tr->tx_pkg_lock, flags);
+ return NULL;
+ }
+ tr->tx_pkg_node_head = tr->tx_pkg_node_head->next;
+ res->next = NULL;
+ tr->tx_pkg_node_num--;
+ spin_unlock_irqrestore(&tr->tx_pkg_lock, flags);
+
+ return res;
+}
+
+void sdio_tx_pkg_node_free(struct eswin *tr, struct tx_buff_pkg_node * node)
+{
+ unsigned long flags;
+ int i;
+ spin_lock_irqsave(&tr->tx_pkg_lock,flags);
+ if((node->flag & FLAG_MSG_TYPE_MASK) == TX_FLAG_TX_DESC)
+ {
+ kfree(node->buff);
+ }
+ node->buff = NULL;
+ node->next = tr->tx_pkg_node_head;
+ tr->tx_pkg_node_head = node;
+ tr->tx_pkg_node_num++;
+ /*
+ if (node->node_cnt > 1)
+ {
+ printk("%s,count :%d\n",__func__,node->node_cnt);
+ }
+ */
+
+ for (i = 0; i < node->node_cnt; ++i)
+ {
+ sdio_tx_node_free(tr, node->tx_node[i]);
+ }
+ spin_unlock_irqrestore(&tr->tx_pkg_lock, flags);
+}
+
+
+void eswin_sdio_register_rx_cb(struct eswin *tr, sdio_rx_cb_t cb)
+{
+ tr->rx_callback = cb;
+}
+
+extern int ecrnx_data_cfm_callback(void *priv, void *host_id);
+extern int ecrnx_msg_cfm_callback(void *priv, void *host_id);
+static void eswin_core_register_work(struct work_struct *work)
+{
+ //struct sk_buff *skb_resp;
+ int ret;
+ struct eswin *tr = container_of(work, struct eswin, register_work.work);
+
+ ECRNX_PRINT(" %s entry, dl_fw = %d!!", __func__, dl_fw);
+
+ if (dl_fw && eswin_fw_file_chech(tr)) {
+ eswin_fw_file_download(tr);
+ release_firmware(tr->fw);
+ dl_fw = false;
+ schedule_delayed_work(&tr->register_work, msecs_to_jiffies(1000));
+ return;
+ }
+
+#ifdef CONFIG_ECRNX_WIFO_CAIL
+ ECRNX_PRINT(" %s entry, amt_mode = %d!!", __func__, amt_mode);
+#endif
+
+ tr->rx_callback = ecrnx_rx_callback;
+ tr->data_cfm_callback = ecrnx_data_cfm_callback;
+ tr->msg_cfm_callback = ecrnx_msg_cfm_callback;
+
+ ret = ecrnx_platform_init(tr, &tr->umac_priv);
+ set_bit(ESWIN_FLAG_CORE_REGISTERED, &tr->dev_flags);
+ ECRNX_DBG("%s exit!!", __func__);
+
+ return;
+}
+
+int eswin_core_register(struct eswin *tr)
+{
+ ECRNX_PRINT("%s entry!!", __func__);
+ tr->ops->start(tr);
+
+ //schedule_delayed_work(&tr->register_work, msecs_to_jiffies(10));
+ schedule_delayed_work(&tr->register_work, msecs_to_jiffies(1));
+ ECRNX_PRINT("%s exit!!", __func__);
+ return 0;
+}
+
+void eswin_core_unregister(struct eswin *tr)
+{
+ struct eswin_sdio *tr_sdio = (struct eswin_sdio *)tr->drv_priv;
+ ECRNX_PRINT("%s entry!!", __func__);
+
+ cancel_delayed_work(&tr->register_work);
+
+ if (!test_bit(ESWIN_FLAG_CORE_REGISTERED, &tr->dev_flags))
+ return;
+
+ ecrnx_platform_deinit(tr->umac_priv);
+}
+
+static int eswin_sdio_tx_thread(void *data) +{ + struct eswin *tr = (struct eswin *)data; + struct tx_buff_pkg_node *node;
+ int i, ret = 0, cb_per = 0;
+ +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) + struct sched_param param = { .sched_priority = 1 }; + param.sched_priority = 56; + sched_setscheduler(get_current(), SCHED_FIFO, ¶m); +#else + sched_set_fifo(get_current()); +#endif + ECRNX_PRINT("sdio pkg thread entry\n");
+ + while (!kthread_should_stop()) + { + ret = wait_event_interruptible(tr->wait_tx, tr->tx_pkg_queue.count != 0 || kthread_should_stop()); + if (ret < 0) + { + ECRNX_ERR("sdio pkg thread error!\n");
+ return 0;
+ } + if(kthread_should_stop())
+ {
+ continue;
+ }
+ while (tr->tx_pkg_queue.count != 0) + { + node = sdio_tx_pkg_queue_pop(&tr->tx_pkg_queue);
+ if (!node) {
+ cb_per = 0;
+ wake_up_interruptible(&tr->wait_cb);
+ break;
+ }
+
+ if (tr->ops->xmit) {
+ ret = tr->ops->xmit(tr, node);
+ WARN_ON(ret < 0);
+ if((node->flag & FLAG_MSG_TYPE_MASK) == TX_FLAG_TX_DESC || (node->flag & FLAG_MSG_TYPE_MASK) == TX_FLAG_MSG_E)
+ {
+ sdio_tx_pkg_queue_push(&tr->tx_c_queue,node);
+ }
+ else
+ {
+ sdio_tx_pkg_node_free(tr, node);
+ }
+ cb_per++;
+ //if (cb_per % 4 == 0)
+ {
+ cb_per = 0;
+ wake_up_interruptible(&tr->wait_cb);
+ }
+ } else {
+ ECRNX_ERR(" eswin_sdio_work, ops->xmit is null\n");
+ }
+ } + } + ECRNX_PRINT("sdio tx thread exit\n");
+ return 0; +} +
+static int eswin_sdio_callback_thread(void *data) +{ + struct eswin *tr = (struct eswin *)data; + struct tx_buff_pkg_node *node;
+ int i, ret = 0;
+ struct txdesc_api *tx_desc;
+ ptr_addr host_id;
+ +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) + struct sched_param param = { .sched_priority = 1 }; + param.sched_priority = 56; + sched_setscheduler(get_current(), SCHED_FIFO, ¶m); +#else + sched_set_fifo(get_current()); +#endif + ECRNX_PRINT("sdio callback thread entry\n");
+ + while (!kthread_should_stop()) + { + ret = wait_event_interruptible(tr->wait_cb, tr->tx_c_queue.count != 0 || kthread_should_stop()); + if (ret < 0) + { + ECRNX_ERR("sdio callback thread error!\n");
+ return 0;
+ } + if(kthread_should_stop())
+ {
+ continue;
+ }
+ while (tr->tx_c_queue.count != 0) + { + node = sdio_tx_pkg_queue_pop(&tr->tx_c_queue);
+ if((node->flag & FLAG_MSG_TYPE_MASK) == TX_FLAG_TX_DESC && (tr->data_cfm_callback))
+ {
+ for (i = 0; i < node->node_cnt; ++i)
+ {
+ tx_desc = (struct txdesc_api *)node->tx_node[i]->buff;
+ if (tx_desc->host.flags & TXU_CNTRL_MGMT)
+ {
+ continue;
+ }
+ memcpy(&host_id, tx_desc->host.packet_addr, sizeof(ptr_addr));
+ tr->data_cfm_callback(tr->umac_priv, (void*)host_id);
+ }
+ }
+ //else if((node->flag & FLAG_MSG_TYPE_MASK) == TX_FLAG_MSG_E && (tr->msg_cfm_callback))
+ //{
+ // for (i = 0; i < node->node_cnt; ++i)
+ // {
+ // struct ecrnx_cmd_a2emsg *msg = (struct ecrnx_cmd_a2emsg *)node->tx_node[i]->buff;
+ // tr->msg_cfm_callback(tr->umac_priv, msg->hostid);
+ // }
+ //}
+ sdio_tx_pkg_node_free(tr, node);
+ } + } + ECRNX_PRINT("rx callback thread exit\n");
+ return 0; +} +
+static int eswin_sdio_tx_pkg_thread(void *data) +{ + struct eswin *tr = (struct eswin *)data; + struct tx_buff_node *node;
+ struct txdesc_api *tx_desc;
+ struct tx_buff_pkg_node * pkg_node = NULL;
+ struct tx_buff_pkg_head tx_pkg_head;
+ unsigned int offset = 0;
+ int i, ret, pkg_cnt = 0;
+ +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) + struct sched_param param = { .sched_priority = 1 }; + param.sched_priority = 56; + sched_setscheduler(get_current(), SCHED_FIFO, ¶m); +#else + sched_set_fifo(get_current()); +#endif + ECRNX_PRINT("sdio tx pkg thread entry\n");
+ + while (!kthread_should_stop()) + { + ret = wait_event_interruptible(tr->wait_pkg, tr->tx_queue.count != 0 || kthread_should_stop()); + if (ret < 0) + { + ECRNX_ERR("sdio tx pkg thread error!\n");
+ return 0;
+ } + if(kthread_should_stop())
+ {
+ continue;
+ }
+ while (tr->tx_queue.count != 0) + { + pkg_cnt = 0;
+ offset = 0;
+ memset(&tx_pkg_head,0,sizeof(tx_pkg_head));
+ pkg_node = sdio_tx_pkg_node_alloc(tr);
+ memset(pkg_node,0,sizeof(struct tx_buff_pkg_node));
+ if (!pkg_node) {
+ ECRNX_PRINT(" sdio pkg failed, no node!!\n");
+ break;
+ }
+ node = sdio_tx_queue_peek(&tr->tx_queue);
+ pkg_node->flag = node->flag;
+
+ if((node->flag & FLAG_MSG_TYPE_MASK) != TX_FLAG_TX_DESC)
+ {
+ node = sdio_tx_queue_pop(&tr->tx_queue);
+ pkg_node->buff = node->buff;
+ pkg_node->len = node->len;
+ pkg_node->tx_node[pkg_cnt] = node;
+ pkg_cnt++;
+ }
+ else
+ {
+ pkg_node->buff = (void *)kzalloc(ALIGN(SDIO_PKG_MAX_DATA*SDIO_PKG_MAX_CNT + sizeof(tx_pkg_head), 512), GFP_ATOMIC);
+ if(!pkg_node->buff){
+ ECRNX_PRINT("pkg_node buff malloc error! \n");
+ }
+ pkg_node->len = sizeof(tx_pkg_head);
+
+ while (tr->tx_queue.count)
+ {
+ offset = pkg_node->len;
+ node = sdio_tx_queue_peek(&tr->tx_queue);
+
+ if (((node->flag & FLAG_MSG_TYPE_MASK) != TX_FLAG_TX_DESC) || (pkg_cnt > (SDIO_PKG_MAX_CNT-1)))
+ {
+ break;
+ }
+ //ECRNX_DBG("tx count 2 %d,node %x",tr->tx_queue.count,node);
+ node = sdio_tx_queue_pop(&tr->tx_queue);
+ if(ALIGN(node->len, SDIO_PKG_PAD_GRN) < (SDIO_PKG_DIV_MSZ+1))
+ {
+ pkg_node->len += ALIGN(node->len, SDIO_PKG_PAD_GRN);
+ pkg_node->flag |= (ALIGN(node->len, SDIO_PKG_PAD_GRN)/SDIO_PKG_PAD_GRN) << (8+SDIO_PKG_BIT_SHIFT*pkg_cnt);
+ }
+ else
+ {
+ pkg_node->len += SDIO_PKG_MAX_DATA;
+ pkg_node->flag |= ((1 << SDIO_PKG_BIT_SHIFT) - 1) << (8+SDIO_PKG_BIT_SHIFT*pkg_cnt);
+ }
+ memcpy(pkg_node->buff + offset, node->buff, node->len);
+ pkg_node->tx_node[pkg_cnt] = node;
+ tx_pkg_head.len[pkg_cnt] = node->len;
+ pkg_cnt++;
+ }
+ pkg_node->len = ALIGN(pkg_node->len, 512);
+ memcpy(pkg_node->buff, &tx_pkg_head, sizeof(tx_pkg_head));
+ }
+ pkg_node->node_cnt = pkg_cnt;
+ sdio_tx_pkg_queue_push(&tr->tx_pkg_queue, pkg_node);
+ wake_up_interruptible(&tr->wait_tx);
+ } + } + ECRNX_PRINT("tx pkg thread exit\n");
+ return 0; +}
+
+void eswin_sdio_ops_init(struct eswin * tr, const struct sdio_ops * ops)
+{
+ int i;
+
+ tr->ops = ops;
+
+ sdio_tx_queue_init(&tr->tx_queue);
+ sdio_tx_pkg_queue_init(&tr->tx_c_queue);
+ sdio_tx_pkg_queue_init(&tr->tx_pkg_queue);
+
+ for (i=1; i<ESWIN_TX_NODE_CNT; i++) {
+ tr->tx_node[i-1].next = &tr->tx_node[i];
+ }
+
+ tr->tx_node[i-1].next = NULL;
+ tr->tx_node_head = &tr->tx_node[0];
+ tr->tx_node_num = ESWIN_TX_NODE_CNT;
+ spin_lock_init(&tr->tx_lock);
+
+ for (i=1; i<ESWIN_TX_NODE_CNT; i++) {
+ tr->tx_pkg_node[i-1].next = &tr->tx_pkg_node[i];
+ }
+
+ tr->tx_pkg_node[i-1].next = NULL;
+ tr->tx_pkg_node_head = &tr->tx_pkg_node[0];
+ tr->tx_pkg_node_num = ESWIN_TX_NODE_CNT;
+ spin_lock_init(&tr->tx_pkg_lock);
+}
+
+int tx_desc_count = 0;
+int sdio_host_send(void *buff, int len, int flag)
+{
+ struct eswin * tr= pEswin;
+ struct tx_buff_node * node = sdio_tx_node_alloc(tr);
+
+ ECRNX_DBG("%s enter, data len :%d ", __func__, len);
+
+ if (!node) {
+ ECRNX_PRINT(" sdio send failed, no node!!\n");
+ return -1;
+ }
+
+ node->buff = (struct lmac_msg *)kzalloc(len, GFP_ATOMIC);
+ if(!node->buff){
+ ECRNX_PRINT("buff malloc error! \n");
+ }
+
+ memcpy(node->buff, buff, len);
+ node->len = len;
+ node->flag = flag & 0xFF;
+
+ if ((len > 512) && (len%512) && ((node->flag & FLAG_MSG_TYPE_MASK) != TX_FLAG_TX_DESC)) {
+ node->flag |= (len%512)<<8;
+ }
+ else
+ {
+ }
+
+ sdio_tx_queue_push(&tr->tx_queue, node);
+
+ if((node->flag & FLAG_MSG_TYPE_MASK) != TX_FLAG_TX_DESC)
+ {
+ tx_desc_count = 0;
+ //queue_work(tr->workqueue_pkg,&tr->work_pkg);
+ wake_up_interruptible(&tr->wait_pkg);
+ }
+ else
+ {
+ tx_desc_count++;
+ if(tx_desc_count%SDIO_PKG_MAX_CNT == 0)
+ {
+ //queue_work(tr->workqueue_pkg,&tr->work_pkg);
+ wake_up_interruptible(&tr->wait_pkg);
+ }
+ else
+ {
+ mod_timer(&sdio_tx_timer, jiffies + usecs_to_jiffies(SDIO_TX_TIMER_TIMEOUT_US));
+ }
+ }
+ return 0;
+}
+
+void sdio_tx_timer_handle(struct timer_list *time) +{ + struct eswin * tr = pEswin;
+ if (tx_desc_count)
+ {
+ tx_desc_count = 0;
+ wake_up_interruptible(&tr->wait_pkg);
+ }
+} +
+extern void ecrnx_send_handle_register(void * fn);
+
+struct eswin * eswin_core_create(size_t priv_size, struct device *dev,
+ const struct sdio_ops * ops)
+{
+ struct eswin * tr;
+
+ tr = (struct eswin *)kzalloc(sizeof(struct eswin) + priv_size, GFP_KERNEL);
+ if(!tr) {
+ return NULL;
+ }
+
+ pEswin = tr;
+
+ tr->dev = dev;
+ tr->loopback = loopback;
+ //tr->loopback = 1;
+ eswin_sdio_ops_init(tr, ops);
+ ecrnx_send_handle_register(sdio_host_send);
+
+ //init_completion(&tr->wim_responded);
+ init_waitqueue_head(&tr->wait_pkg);
+ init_waitqueue_head(&tr->wait_tx); + init_waitqueue_head(&tr->wait_cb);
+
+ tr->kthread_pkg = kthread_run(eswin_sdio_tx_pkg_thread, tr, "sdio-tx-pkg"); + tr->kthread_tx = kthread_run(eswin_sdio_tx_thread, tr, "sdio-tx");
+ tr->kthread_cb = kthread_run(eswin_sdio_callback_thread, tr, "sdio-tx-callback");
+
+ INIT_DELAYED_WORK(&tr->register_work, eswin_core_register_work);
+ timer_setup(&sdio_tx_timer, sdio_tx_timer_handle, 0);
+
+ tr->state = ESWIN_STATE_INIT;
+
+ //eswin_init_debugfs(tr);
+
+ ECRNX_PRINT(" %s exit!!", __func__);
+ return tr;
+
+err_free_mac:
+ eswin_core_destroy(tr);
+ return NULL;
+}
+
+void eswin_core_destroy(struct eswin *tr)
+{
+ unsigned long flags;
+ int i;
+
+ ECRNX_PRINT("%s entry!!", __func__);
+ tr->state = ESWIN_STATE_CLOSEED;
+
+
+ //flush_workqueue(tr->workqueue);
+ //destroy_workqueue(tr->workqueue);
+ //tr->workqueue = NULL;
+
+ ECRNX_PRINT("%s node_num %d\n", __func__, tr->tx_node_num);
+ spin_lock_irqsave(&tr->tx_lock,flags);
+ for (i=0; i<64; i++)
+ {
+ if (tr->tx_node[i].buff)
+ {
+ kfree(tr->tx_node[i].buff);
+ }
+ }
+ spin_unlock_irqrestore(&tr->tx_lock, flags);
+
+ spin_lock_irqsave(&tr->tx_pkg_lock,flags);
+ for (i=0; i<64; i++)
+ {
+ if (tr->tx_pkg_node[i].buff)
+ {
+ kfree(tr->tx_pkg_node[i].buff);
+ }
+ }
+ spin_unlock_irqrestore(&tr->tx_pkg_lock, flags);
+
+ kthread_stop(tr->kthread_pkg);
+ wake_up_interruptible(&tr->wait_pkg);
+ kthread_stop(tr->kthread_cb);
+ wake_up_interruptible(&tr->wait_cb);
+ kthread_stop(tr->kthread_tx);
+ wake_up_interruptible(&tr->wait_tx);
+
+ kfree(tr);
+ tr = NULL;
+ //TODO:
+ //eswin_mac_destroy(tr);
+ ECRNX_PRINT("%s exit!!", __func__);
+}
+
+
+//MODULE_AUTHOR("Transa-Semi");
+//MODULE_LICENSE("Dual BSD/GPL");
+//MODULE_DESCRIPTION("Core module for Transa-Semi 802.11 WLAN SDIO driver");
diff --git a/drivers/net/wireless/eswin/sdio/core.h b/drivers/net/wireless/eswin/sdio/core.h new file mode 100644 index 000000000000..58aa81237530 --- /dev/null +++ b/drivers/net/wireless/eswin/sdio/core.h @@ -0,0 +1,364 @@ +/** + ****************************************************************************** + * + * @file core.h + * + * @brief sdio core definitions + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef _CORE_H_ +#define _CORE_H_ + + +#include <linux/completion.h> +#include <linux/if_ether.h> + +#include <net/mac80211.h> +#include "ecrnx_compat.h" + + +#define ESWIN_FLAG_CORE_REGISTERED 1 + +#define ESWIN_NR_VIF 2 +#define ESWIN_NR_VIF_HW_QUEUE 4 + + +#define ESWIN_STATE_BOOT 1 +#define ESWIN_STATE_INIT 2 +#define ESWIN_STATE_STOP 3 +#define ESWIN_STATE_CLOSING 4 +#define ESWIN_STATE_CLOSEED 5 +#define ESWIN_STATE_START 6 +#define ESWIN_STATE_RUNNING 7 + +#define SDIO_PKG_MAX_BIT 9 +#define SDIO_PKG_MAX_CNT 4//4/3 +#define SDIO_PKG_BIT_SHIFT (SDIO_PKG_MAX_BIT/SDIO_PKG_MAX_CNT) +#define SDIO_PKG_PAD_GRN (512)//512/256 +#define SDIO_PKG_DIV_MSZ (1024)//1024/1536 +#define SDIO_DATA_MTU (1500) +#define SDIO_PKG_MAX_DATA (SDIO_DATA_MTU + ECRNX_TX_TXDESC_API_ALIGN) + + +#define WIM_RESP_TIMEOUT (msecs_to_jiffies(100)) + +enum ESWIN_SCAN_MODE { + ESWIN_SCAN_MODE_IDLE = 0, + ESWIN_SCAN_MODE_SCANNING, + ESWIN_SCAN_MODE_ABORTING, +}; + + + +struct fwinfo_t { + uint32_t ready; + uint32_t version; + uint32_t tx_head_size; + uint32_t rx_head_size; + uint32_t payload_align; + uint32_t buffer_size; +}; + +struct eswin_capabilities { + uint64_t cap_mask; + uint16_t listen_interval; + uint16_t bss_max_idle; + uint8_t bss_max_idle_options; +}; + +struct eswin_max_idle { + bool enable; + u16 period; + u16 scale_factor; + u8 options; + struct timer_list keep_alive_timer; + + unsigned long idle_period; /* jiffies */ + struct timer_list timer; +}; + + +/* Private txq driver data structure */ +struct eswin_txq { + u16 hw_queue; /* 0: AC_BK, 1: AC_BE, 2: AC_VI, 3: AC_VO */ + struct list_head list; + struct sk_buff_head queue; /* own queue */ + unsigned long nr_fw_queueud; + unsigned long nr_push_allowed; + struct ieee80211_vif vif; + struct ieee80211_sta sta; +}; + +struct tx_buff_node { + struct tx_buff_node * next; + void * buff; + int len; + int flag; +}; + +struct tx_buff_queue { + struct tx_buff_node * head; + struct tx_buff_node * tail; + int count; + spinlock_t lock; +}; + +struct tx_buff_pkg_node { + struct tx_buff_pkg_node * next; + void * buff; + int len; + int flag; + struct tx_buff_node * tx_node[SDIO_PKG_MAX_CNT]; + int node_cnt; +}; + +struct tx_buff_pkg_queue { + struct tx_buff_pkg_node * head; + struct tx_buff_pkg_node * tail; + int count; + spinlock_t lock; +}; + +struct tx_buff_pkg_head { + unsigned int len[SDIO_PKG_MAX_CNT]; +}; + +#define ESWIN_QUEUE_MAX (ESWIN_NR_VIF_HW_QUEUE*ESWIN_NR_VIF + 3) +#define ESWIN_TX_NODE_CNT 64 + +typedef int (*sdio_rx_cb_t)(void *priv, struct sk_buff *skb); +typedef int (*sdio_data_cfm_cb_t)(void *priv, void *host_id); +struct eswin { + + void *umac_priv; //mac drv data. + struct ieee80211_hw *hw; + struct ieee80211_vif *vif[ESWIN_NR_VIF]; + struct device *dev; + int nr_active_vif; + int state; + bool promisc; + + bool loopback; + bool ampdu_supported; + int lb_count; + bool amsdu_supported; + bool block_frame; + bool ampdu_reject; + + char alpha2[2]; + u64 tsf_offset; + + const struct sdio_ops *ops; + sdio_rx_cb_t rx_callback; + sdio_data_cfm_cb_t data_cfm_callback; + sdio_data_cfm_cb_t msg_cfm_callback; + + //struct sx_buff_queue queue[ESWIN_NR_VIF]; /* 0: frame, 1: wim */ + struct tx_buff_queue tx_queue; + struct tx_buff_pkg_queue tx_c_queue; + struct tx_buff_pkg_queue tx_pkg_queue; + struct tx_buff_node tx_node[ESWIN_TX_NODE_CNT]; + struct tx_buff_node * tx_node_head; + spinlock_t tx_lock; + int tx_node_num; + + struct tx_buff_pkg_node tx_pkg_node[ESWIN_TX_NODE_CNT]; + struct tx_buff_pkg_node * tx_pkg_node_head; + spinlock_t tx_pkg_lock; + int tx_pkg_node_num; + + struct work_struct work; + struct work_struct work_c; + struct work_struct work_pkg; + + //struct task_struct *kthread_pkg; + //wait_queue_head_t wait_pkg; /* wait queue */ + + struct eswin_txq ntxq[ESWIN_QUEUE_MAX]; + + struct mutex state_mtx; + enum ESWIN_SCAN_MODE scan_mode; + + struct task_struct *kthread_pkg; + struct task_struct *kthread_tx; + struct task_struct *kthread_cb; + + wait_queue_head_t wait_pkg; + wait_queue_head_t wait_tx; + wait_queue_head_t wait_cb; + + struct delayed_work scan_timeout; + + //struct work_struct register_work; + struct delayed_work register_work; + + + unsigned long dev_flags; + struct mac_address mac_addr[ESWIN_NR_VIF]; + struct ieee80211_supported_band bands[NUM_NL80211_BANDS]; + + + + /* Move to vif or sta driver data */ + u8 frame_seqno; + u8 wim_seqno; + u8 band; + u16 center_freq; + u16 aid; + u32 cipher_pairwise; + u32 cipher_group; + + struct fwinfo_t fwinfo; + struct eswin_capabilities cap; + + /* power management */ + enum ps_mode { + PS_DISABLED, + PS_ENABLED, + PS_AUTO_POLL, + PS_MANUAL_POLL + } ps; + bool ps_poll_pending; + bool ps_enabled; + + + + /* tx */ + spinlock_t txq_lock; + struct list_head txq; + /* 0: AC_BK, 1: AC_BE, 2: AC_VI, 3: AC_VO */ + atomic_t tx_credit[IEEE80211_NUM_ACS*3]; + atomic_t tx_pend[IEEE80211_NUM_ACS*3]; + +// struct completion wim_responded; +// struct sk_buff *last_wim_responded; + + + struct delayed_work roc_finish; + + struct firmware *fw; + + struct dentry *debugfs; + + /* must be last */ + u8 drv_priv[0] __aligned(sizeof(void *)); +}; + + +/* vif driver data structure */ +struct eswin_vif { + struct eswin *tr; + int index; + struct net_device *dev; + + /* scan */ + struct delayed_work scan_timeout; + + /* power save */ + bool ps_polling; + + /* MLME */ + spinlock_t preassoc_sta_lock; + struct list_head preassoc_sta_list; + + /* inactivity */ + u16 max_idle_period; +}; + +#define to_ieee80211_vif(v) \ + container_of((void *)v, struct ieee80211_vif, drv_priv) + +#define to_i_vif(v) ((struct eswin_vif *) (v)->drv_priv) + +static inline int hw_vifindex(struct ieee80211_vif *vif) +{ + struct eswin_vif *i_vif; + + if (vif == NULL) + return 0; + + i_vif = to_i_vif(vif); + return i_vif->index; +} + +/* sta driver data structure */ +struct eswin_sta { + struct eswin *tr; + struct ieee80211_vif *vif; + /*struct ieee80211_sta *sta;*/ + + enum ieee80211_sta_state state; + struct list_head list; + + /* keys */ + struct ieee80211_key_conf *ptk; + struct ieee80211_key_conf *gtk; + + /* BSS max idle period */ + struct eswin_capabilities cap; + struct eswin_max_idle max_idle; +}; + +#define to_ieee80211_sta(s) \ + container_of((void *)s, struct ieee80211_sta, drv_priv) + +#define to_i_sta(s) ((struct eswin_sta *) (s)->drv_priv) + + + +struct eswin_sta_handler { + int (*sta_state)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + enum ieee80211_sta_state old_state, + enum ieee80211_sta_state new_state); + +}; + +#if 0 +#define STAH(fn) \ + static struct eswin_sta_handler __stah_ ## fn \ + __attribute((__used__)) \ + __attribute((__section__("nrc.sta"))) = { \ + .sta_state = fn, \ + } + +extern struct eswin_sta_handler __sta_h_start, __sta_h_end; +#endif + + +/* trx */ + +struct eswin_trx_data { + struct eswin *tr; + struct ieee80211_vif *vif; + struct ieee80211_sta *sta; + struct sk_buff *skb; + int result; +}; + + +#define NL80211_IFTYPE_ALL (BIT(NUM_NL80211_IFTYPES)-1) + + + +int eswin_core_register(struct eswin *tr); +void eswin_core_unregister(struct eswin *tr); + +struct eswin * eswin_core_create(size_t priv_size, struct device *dev, + const struct sdio_ops * ops); + +void eswin_core_destroy(struct eswin *tr); + + +extern int power_save; +extern int disable_cqm; +#ifdef CONFIG_ECRNX_WIFO_CAIL +extern bool amt_mode; +#endif +extern bool set_gain; + +#endif diff --git a/drivers/net/wireless/eswin/sdio/debug.c b/drivers/net/wireless/eswin/sdio/debug.c new file mode 100644 index 000000000000..83420fab3016 --- /dev/null +++ b/drivers/net/wireless/eswin/sdio/debug.c @@ -0,0 +1,202 @@ +/** + ****************************************************************************** + * + * @file debug.c + * + * @brief sdio driver debug function definitions + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#include <linux/timer.h> +#include "ecrnx_utils.h" +#include "ecrnx_defs.h" +#include "ecrnx_msg_rx.h" +#include "ecrnx_sdio.h" +#include "core.h" +#include "debug.h" +#include "sdio.h" + + +extern int ecrnx_rx_callback(void *priv, struct sk_buff *skb); + +//kernal timer param +static struct timer_list tm; +static int counter = 0; + +//rx param +struct sk_buff *skb = NULL; +struct ecrnx_hw *g_ecrnx_hw = NULL; + +//tx param +struct ecrnx_vif vif; +struct ecrnx_sta sta; +struct cfg80211_mgmt_tx_params params; + +static void test_timer_handler(struct timer_list * lt); + +void ecrnx_hw_set(void* init_ecrnx_hw) +{ + g_ecrnx_hw = (struct ecrnx_hw *)init_ecrnx_hw; +} + +static int sdio_rx_param_init(void) +{ + struct rxu_stat_mm rxu_state; + struct rx_hd rx_head; + struct ethhdr eth_hd; + int res = 0, index = 0; + uint8_t *ptr = NULL; + uint16_t head_len = sizeof(struct ethhdr); + ECRNX_DBG("%s entry!!", __func__); + memset(&rxu_state, 0, sizeof(struct rxu_stat_mm)); + memset(&rx_head, 0, sizeof(struct rx_hd)); + memset(ð_hd, 0, sizeof(struct ethhdr)); + + rxu_state.comm_hd.frm_type = SDIO_FRM_TYPE_RXDESC; + //rxu_state.comm_hd.frm_type = SDIO_FRM_TYPE_MSG; + if(rxu_state.comm_hd.frm_type == SDIO_FRM_TYPE_RXDESC) + { + head_len += sizeof(struct rxu_stat_mm) + sizeof(struct rx_hd); + } + else + { + head_len += sizeof(dispatch_hdr_t); + } + + skb = dev_alloc_skb(FRAME_SIZE + head_len); + skb_reserve(skb, head_len); + ptr = skb_put(skb, FRAME_SIZE); //ptr is skb tail + memset(skb->data, 0x0f, FRAME_SIZE); //payload + skb_push(skb, sizeof(struct ethhdr)); + + for( index = 0; index < ETH_ALEN; index++) + { + eth_hd.h_dest[index] = index; + eth_hd.h_source[index] = index; + } + + eth_hd.h_proto = ETH_P_80221; //ETHERTYPE_IP; + memcpy(skb->data, ð_hd, sizeof(struct ethhdr)); + + if(rxu_state.comm_hd.frm_type == SDIO_FRM_TYPE_RXDESC) + { + //data frame, need header, rxu state + //rx head + skb_push(skb, sizeof(struct rx_hd)); + rx_head.frmlen = FRAME_SIZE + head_len; + rx_head.ampdu_stat_info = 0; + //... + memcpy(skb->data , &rx_head, sizeof(struct rx_hd)); + + //rxu state + skb_push(skb, sizeof(struct rxu_stat_mm)); + rxu_state.msdu_mode = 0x01; + rxu_state.host_id = 0x0001; + rxu_state.frame_len = rx_head.frmlen; + rxu_state.status = RX_STAT_MONITOR; + //rxu_state.phy_info.info1 = 1 | (1 << 8) | (2450 << 16); + //rxu_state.phy_info.info2 = 2450 | (2450 << 16); +#ifdef CONFIG_ECRNX_FULLMAC + //rxu_state.flags = 0; +#endif + //rxu_state.pattern = ecrnx_rxbuff_pattern; + memcpy(skb->data, &rxu_state, sizeof(struct rxu_stat_mm)); + } + else + { + //message frame, don't need header + skb_push(skb, sizeof(dispatch_hdr_t)); //rxu state + memcpy(skb->data, &rxu_state.comm_hd, sizeof(dispatch_hdr_t)); + } + + for(index = 0; index < head_len; index++) + { + ECRNX_DBG("0x%x ", skb->data[index]); + } + + ECRNX_DBG("%s exit, skb_len:%d, type: %d!!", __func__, skb->len, (skb->data[1] << 8) | skb->data[0]); + + return res; +} + +extern int ecrnx_start_mgmt_xmit(struct ecrnx_vif *vif, \ + struct ecrnx_sta *sta, \ + struct cfg80211_mgmt_tx_params *params, \ + bool offchan, \ + u64 *cookie); + +/* +struct cfg80211_mgmt_tx_params { + struct ieee80211_channel *chan; + bool offchan; + unsigned int wait; + const u8 *buf; + size_t len; + bool no_cck; + bool dont_wait_for_ack; + int n_csa_offsets; + const u16 *csa_offsets; +}; +*/ +static void sdio_tx_param_init(void) +{ + u8 send_buf[FRAME_SIZE] = {0x00, 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09}; + + params.len = FRAME_SIZE; + params.buf = (const u8 *)send_buf; + params.n_csa_offsets = 10; + params.csa_offsets = (const u16 *)send_buf; + params.no_cck = 0; + + vif.ecrnx_hw = g_ecrnx_hw; +} + +void sdio_rx_tx_test_schedule(void) +{ + ECRNX_DBG("%s entry!!", __func__); + + //wangc add + //tm.function = test_timer_handler; + tm.expires = jiffies + HZ * 10; + add_timer(&tm); + + sdio_rx_param_init(); + sdio_tx_param_init(); + ECRNX_DBG("%s exit!!", __func__); +} + +static void test_timer_handler(struct timer_list * lt) +{ + ECRNX_DBG("%s, counter:%d\n", __FUNCTION__, counter); + + if(counter%2) + { + u64 cookie; + //ecrnx_start_mgmt_xmit(&vif, NULL, ¶ms, false, &cookie); + } + else + { + ecrnx_rx_callback(g_ecrnx_hw, skb); + } + + if(lt) + { + counter++; + } + + if(counter < 5) + { + //tm.expires = jiffies +1 * HZ / 1000/10; //100us + tm.expires = jiffies +1 * HZ; + add_timer(&tm); + } + else + { + counter = 0; + del_timer(&tm); + } +} + diff --git a/drivers/net/wireless/eswin/sdio/debug.h b/drivers/net/wireless/eswin/sdio/debug.h new file mode 100644 index 000000000000..ec53824115fc --- /dev/null +++ b/drivers/net/wireless/eswin/sdio/debug.h @@ -0,0 +1,31 @@ +/** + ****************************************************************************** + * + * @file debug.h + * + * @brief sdio driver debug definitions + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef _ECRNX_DBG_ +#define _ECRNX_DBG_ +#include <net/mac80211.h> + +//#define CONFIG_HIF_PRINT_TX_DATA +#define CONFIG_USE_TXQ + +//#define CONFIG_NRC_HIF_PRINT_FLOW_CONTROL +//#define CONFIG_SHOW_TX_SPEED +//#define CONFIG_SHOW_RX_SPEED + +#define FRAME_SIZE 512 + +void eswin_dump_wim(struct sk_buff *skb); +void eswin_init_debugfs(struct eswin *tr); +void sdio_rx_tx_test_schedule(void); +void ecrnx_hw_set(void* init_ecrnx_hw); + +#endif diff --git a/drivers/net/wireless/eswin/sdio/ecrnx_sdio.c b/drivers/net/wireless/eswin/sdio/ecrnx_sdio.c new file mode 100644 index 000000000000..fc8cd1f864f8 --- /dev/null +++ b/drivers/net/wireless/eswin/sdio/ecrnx_sdio.c @@ -0,0 +1,559 @@ +/** + ****************************************************************************** + * + * @file ecrnx_sdio.c + * + * @brief ECRNX sdio init and management function + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/mmc/sdio_func.h> +#include <linux/mmc/sdio_ids.h> +#include "core.h" +//#include "debug.h" +#include "ecrnx_utils.h" +#include "ecrnx_cmds.h" +#include "ecrnx_defs.h" +#include "ecrnx_msg_rx.h" +#include "ipc_host.h" +#include "ipc_shared.h" +#include "ecrnx_events.h" +#include "ecrnx_sdio.h" +#ifdef CONFIG_TEST_ESWIN_SDIO +#include "debug.h" +#endif +#ifdef CONFIG_ECRNX_WIFO_CAIL +#include "ecrnx_amt.h" +#endif + + +#define SDIO_ADDR_DATA (unsigned int)(0x200) + +#ifdef CONFIG_ECRNX_SOFTMAC +#define FW_STR "lmac" +#elif defined CONFIG_ECRNX_FULLMAC +#define FW_STR "fmac" +#endif + +sdio_rx_buf_t sdio_rx_buff; + +extern const int nx_txdesc_cnt_msk[]; + +struct vendor_radiotap_hdr { + u8 oui[3]; + u8 subns; + u16 len; + u8 data[]; +}; + +/** + * @brief: sdio_rx_buf_init: Initialization the sdio rx buffer + * @param {void} void + * @return: none + */ +static void sdio_rx_buf_init(void) +{ + uint8_t i = 0; + ECRNX_DBG("%s entry!!", __func__); + memset(&sdio_rx_buff, 0, sizeof(sdio_rx_buf_t)); + + for(i = 0; i < SDIO_RXBUF_CNT; i++) + { + sdio_rx_buff.ecrnx_sdio_rx_skb[i].skb = dev_alloc_skb(SDIO_RXBUF_SIZE); + skb_put(sdio_rx_buff.ecrnx_sdio_rx_skb[i].skb, SDIO_RXBUF_SIZE); + } +#ifdef CONFIG_TEST_ESWIN_SDIO + sdio_rx_tx_test_schedule(); +#endif + ECRNX_DBG("%s exit!!", __func__); +} + +/** + * @brief: sdio_rx_buf_deinit: Deinitialization the sdio rx buffer + * @param {void} void + * @return: none + */ +static void sdio_rx_buf_deinit(void) +{ + uint8_t i = 0; + + memset(&sdio_rx_buff, 0, sizeof(sdio_rx_buf_t)); + + for(i = 0; i < SDIO_RXBUF_CNT; i++) + { + if(sdio_rx_buff.ecrnx_sdio_rx_skb[i].skb) + { + dev_kfree_skb(sdio_rx_buff.ecrnx_sdio_rx_skb[i].skb); + } + } +} + +/** + * @brief: sdio_rx_buf_push: push a skb element to sdio rx buffer + * @param {skb} skb data need to push + * @param {recv_len} skb data length + * @return: sk_buff + */ +struct sk_buff * sdio_rx_buf_push(struct sk_buff *skb, uint16_t recv_len) +{ + uint8_t index = 0; + + ECRNX_DBG("%s enter, skb: %d, skb_data:%d, skb_len:%d", __func__, skb, skb->data, recv_len); + if(atomic_read(&sdio_rx_buff.suspend)) + { + return NULL; + } + + if(sdio_rx_buff.sdio_host_rx_buff_used >= SDIO_RXBUF_CNT) + { + ECRNX_PRINT("no enough space \n"); + return NULL; + } + + if((!skb) || (!recv_len) || (recv_len > SDIO_RXBUF_SIZE)) + { + ECRNX_PRINT("rx data is error \n"); + return NULL; + } + + atomic_set(&sdio_rx_buff.suspend, 1); + + do + { + if(!sdio_rx_buff.ecrnx_sdio_rx_skb[index].flag) //find a index to store the data + { + break; + } + index++; + }while(index < SDIO_RXBUF_CNT); + + sdio_rx_buff.sdio_host_rx_buff_used++; + sdio_rx_buff.ecrnx_sdio_rx_skb[index].flag = true; + sdio_rx_buff.ecrnx_sdio_rx_skb[index].data_len = recv_len; + memcpy(sdio_rx_buff.ecrnx_sdio_rx_skb[index].skb->data, skb->data, recv_len); + atomic_set(&sdio_rx_buff.suspend, 0); + + return sdio_rx_buff.ecrnx_sdio_rx_skb[index].skb; +} + +/** + * @brief: sdio_rx_buf_pop: pop a skb element from sdio rx buffer + * @param {void} + * @return: sk_buff + */ +struct sk_buff* sdio_rx_buf_pop(void) +{ + uint8_t index = 0; + struct sk_buff *skb= NULL; + + if(atomic_read(&sdio_rx_buff.suspend)) + { + ECRNX_PRINT("sdio suspend! \n"); + return NULL; + } + + if(sdio_rx_buff.sdio_host_rx_buff_used <= 0) + { + ECRNX_PRINT("no data in memory \n"); + return NULL; + } + + atomic_set(&sdio_rx_buff.suspend, 1); + + do + { + if(sdio_rx_buff.ecrnx_sdio_rx_skb[index].flag) //find a index to pop the data + { + break; + } + index++; + }while(index < SDIO_RXBUF_CNT); + + if(index != SDIO_RXBUF_CNT) + { + skb = sdio_rx_buff.ecrnx_sdio_rx_skb[index].skb; + sdio_rx_buff.ecrnx_sdio_rx_skb[index].flag = false; + sdio_rx_buff.sdio_host_rx_buff_used = (sdio_rx_buff.sdio_host_rx_buff_used > 0)? sdio_rx_buff.sdio_host_rx_buff_used-1: 0; + } + atomic_set(&sdio_rx_buff.suspend, 0); + + return skb; +} + +/** + * @brief: ipc_host_rxdesc_handler: Handle the reception of a Rx Descriptor + * Called from general IRQ handler when status %IPC_IRQ_E2A_RXDESC is set + * @param {env} pointer to the sdio Host environment + * @param {skb} received skb data + * @return: none + */ +static int sdio_host_rxdesc_handler(struct ipc_host_env_tag *env, struct sk_buff *skb) +{ + u8 ret = 0; + // LMAC has triggered an IT saying that a reception has occurred. + // Then we first need to check the validity of the current hostbuf, and the validity + // of the next hostbufs too, because it is likely that several hostbufs have been + // filled within the time needed for this irq handling +#ifdef CONFIG_ECRNX_FULLMAC + // call the external function to indicate that a RX descriptor is received + ret = env->cb.recv_data_ind(env->pthis, skb); +#else + // call the external function to indicate that a RX packet is received + ret = env->cb.recv_data_ind(env->pthis, skb); +#endif //(CONFIG_ECRNX_FULLMAC) + ECRNX_DBG("%s exit!!", __func__); + return ret; +} + +/** + * @brief: sdio_host_radar_handler Handle the reception of radar events + * @param {env} pointer to the sdio Host environment + * @param {skb} received skb data + * @return: none + */ +static int sdio_host_radar_handler(struct ipc_host_env_tag *env, struct sk_buff *skb) +{ + int ret = 0; + +#ifdef CONFIG_ECRNX_RADAR + // LMAC has triggered an IT saying that a radar event has been sent to upper layer. + // Then we first need to check the validity of the current msg buf, and the validity + // of the next buffers too, because it is likely that several buffers have been + // filled within the time needed for this irq handling + // call the external function to indicate that a RX packet is received + spin_lock(&((struct ecrnx_hw *)env->pthis)->radar.lock); + ret = env->cb.recv_radar_ind(env->pthis, skb); + spin_unlock(&((struct ecrnx_hw *)env->pthis)->radar.lock); +#endif /* CONFIG_ECRNX_RADAR */ + return ret; +} + +/** + * @brief: sdio_host_unsup_rx_vec_handler Handle the reception of unsupported rx vector + * @param {env} pointer to the sdio Host environment + * @param {skb} received skb data + * @return: none + */ +static int sdio_host_unsup_rx_vec_handler(struct ipc_host_env_tag *env, struct sk_buff *skb) +{ + return env->cb.recv_unsup_rx_vec_ind(env->pthis, skb); +} + +/** + * @brief: sdio_host_msg_handler Handler for firmware message + * @param {env} pointer to the sdio Host environment + * @param {skb} received skb data + * @return: none + */ +static int sdio_host_msg_handler(struct ipc_host_env_tag *env, struct sk_buff *skb) +{ + // LMAC has triggered an IT saying that a message has been sent to upper layer. + // Then we first need to check the validity of the current msg buf, and the validity + // of the next buffers too, because it is likely that several buffers have been + // filled within the time needed for this irq handling + // call the external function to indicate that a RX packet is received + return env->cb.recv_msg_ind(env->pthis, skb->data); +} + +/** + * @brief: sdio_host_msgack_handler Handle the reception of message acknowledgement + * @param {env} pointer to the sdio Host environment + * @param {skb} received skb data + * @return: none + */ +static int sdio_host_msgack_handler(struct ipc_host_env_tag *env, struct sk_buff *skb) +{ + uint64_t hostid = *(uint64_t *)skb->data; + + ASSERT_ERR(hostid); + + env->msga2e_hostid = NULL; + env->cb.recv_msgack_ind(env->pthis, hostid); + + return 0; +} + +/** + * @brief: sdio_host_dbg_handler Handle the reception of Debug event + * @param {env} pointer to the sdio Host environment + * @param {skb} received skb data + * @return: none + */ +static int sdio_host_dbg_handler(struct ipc_host_env_tag *env, struct sk_buff *skb) +{ + // LMAC has triggered an IT saying that a DBG message has been sent to upper layer. + // Then we first need to check the validity of the current buffer, and the validity + // of the next buffers too, because it is likely that several buffers have been + // filled within the time needed for this irq handling + // call the external function to indicate that a RX packet is received + return env->cb.recv_dbg_ind(env->pthis, skb); +} + +/** + * @brief: sdio_host_tx_cfm_handler Handle the reception of TX confirmation + * @param {env} pointer to the sdio Host environment + * @param {queue_idx} index of the hardware on which the confirmation has been received + * @param {user_pos} index of the user position + * @return: none + */ +static int sdio_host_tx_cfm_handler(struct ipc_host_env_tag *env, struct sk_buff *skb) +{ + ptr_addr host_id; + struct sk_buff *skb_cfm; + struct ecrnx_txhdr *txhdr; + + ECRNX_DBG("%s, skb: 0x%08x \n", __func__, skb); +#ifdef CONFIG_ECRNX_FULLMAC + struct tx_cfm_tag *cfm; + + cfm = (struct tx_cfm_tag *)skb->data; + memcpy((uint8_t *)&host_id, (uint8_t *)cfm->hostid, sizeof(ptr_addr)); + ECRNX_DBG("--%s--hostid 0x%08x, skb: 0x%x \n", __func__, host_id, skb); + if (host_id == 0) { + return 0; + } + + skb_cfm = (struct sk_buff *)host_id; + txhdr = (struct ecrnx_txhdr *)skb_cfm->data; + memcpy(&txhdr->hw_hdr.cfm, cfm, sizeof(*cfm)); +#elif defined CONFIG_ECRNX_SOFTMAC + //TODO: +#endif + + return env->cb.send_data_cfm(env->pthis, host_id); + //return 0; +} + +#ifdef CONFIG_ECRNX_WIFO_CAIL +/** + * @brief: sdio_host_rx_handler Handle the reception of rx frame + * @param {frm_type} received frame type + * @param {skb} received skb data + * @return: none +**/ +void sdio_host_amt_rx_handler(uint32_t frm_type, struct sk_buff *skb) +{ + int need_free = 0; + + ECRNX_PRINT("%s enter, frame type: %d, len %d.!!", __func__, frm_type, skb->len); + if (frm_type != SDIO_FRM_TYPE_RXDESC) + { + skb_pull(skb, SKB_DATA_COM_HD_OFFSET); //delete the frame common header + } + + switch (frm_type) + { + case SDIO_FRM_TYPE_IWPRIV: + { + /*print_hex_dump(KERN_INFO, "iwpriv-cfm:", DUMP_PREFIX_ADDRESS, 32, 1, + skb->data, skb->len, false);*/ + amt_vif.rxlen = skb->len; + memset(amt_vif.rxdata, 0, ECRNX_RXSIZE); + memcpy(amt_vif.rxdata, skb->data, skb->len > ECRNX_RXSIZE ? ECRNX_RXSIZE : skb->len); + amt_vif.rxdatas = 1; + wake_up(&amt_vif.rxdataq); + need_free = 1; + break; + } + + default: + need_free = 1; + break; + } + + if (need_free && skb) { // free the skb + ECRNX_DBG("skb free: 0x%x, \n", skb); + dev_kfree_skb(skb); + } + ECRNX_DBG("%s exit!!", __func__); + return; +} +#endif + +/** + * @brief: sdio_host_rx_handler Handle the reception of rx frame + * @param {frm_type} received frame type + * @param {env} pointer to the sdio Host environment + * @param {skb} received skb data + * @return: none + */ +void sdio_host_rx_handler(uint32_t frm_type, struct ipc_host_env_tag *env, struct sk_buff *skb) +{ + int ret = 1; + + ECRNX_DBG("%s enter, frame type: %d!!", __func__, frm_type); + if (frm_type != SDIO_FRM_TYPE_RXDESC) + { + skb_pull(skb, SKB_DATA_COM_HD_OFFSET); //delete the frame common header + } + + switch (frm_type) + { + case SDIO_FRM_TYPE_RXDESC: + { + sdio_host_rxdesc_handler(env, skb); + break; + } + + case SDIO_FRM_TYPE_MSG_ACK: + { + ret = sdio_host_msgack_handler(env, skb); + break; + } + + case SDIO_FRM_TYPE_MSG: + { + ret = sdio_host_msg_handler(env, skb); + break; + } + + case SDIO_FRM_TYPE_TXCFM: + { + /* add the spinlock which was missed during porting. */ + spin_lock_bh(&((struct ecrnx_hw *)env->pthis)->tx_lock); + while(skb->len > sizeof(struct tx_cfm_tag)) + { + ret = sdio_host_tx_cfm_handler(env, skb); + skb_pull(skb, sizeof(struct tx_cfm_tag)); + } + ret = sdio_host_tx_cfm_handler(env, skb); + spin_unlock_bh(&((struct ecrnx_hw *)env->pthis)->tx_lock); + break; + } + + case SDIO_FRM_TYPE_UNSUP_RX_VEC: + { + // handle the unsupported rx vector reception + ret = sdio_host_unsup_rx_vec_handler(env, skb); + break; + } + + case SDIO_FRM_TYPE_RADAR: + { + // handle the radar event reception + ret = sdio_host_radar_handler(env, skb); + break; + } + + case SDIO_FRM_TYPE_TBTT_SEC: + { + env->cb.sec_tbtt_ind(env->pthis); + break; + } + + case SDIO_FRM_TYPE_TBTT_PRIM: + { + env->cb.prim_tbtt_ind(env->pthis); + break; + } + + case SDIO_FRM_TYPE_DBG: + { + ret = sdio_host_dbg_handler(env, skb); + break; + } + + case SDIO_FRM_TYPE_UPDATE: + { + ret = 0; + break; + } + default: + ret = 0; + break; + } + + if (!ret && skb) { // free the skb + ECRNX_DBG("skb free: 0x%x, ret: %d!! \n", skb, ret); + dev_kfree_skb(skb); + } + ECRNX_DBG("%s exit!!", __func__); + return; +} + +/** + * @brief: rcv_skb_convert convert the sdio received skb to the ecrnx handled skb. + * @param {src_rxu_state} received rxu state in skb + * @param {src_rxu_state} received rx header in skb + * @param {src_rxu_state} handled hw rxhdr in skb + * @param {src_rxu_state} handled rx desc tag in skb + * @return: u8 + */ +u8 rcv_skb_convert(struct rxu_stat_mm* src_rxu_state, \ + struct rx_hd* src_rx_hd, \ + struct sk_buff *skb, \ + struct hw_rxhdr* dst_hw_rxhdr,\ + struct rxdesc_tag* dst_rxdesc_tag) +{ + ECRNX_DBG("%s enter!!", __func__); +#if 0 + uint16_t index = 0; + + if (!skb || !skb->data){ + ECRNX_PRINT("RX data invalid \n"); + return -1; + } + + //copy the rxu state and rx header, repush process need to used them + memcpy(src_rxu_state, skb->data, sizeof(struct rxu_stat_mm)); + memcpy(src_rx_hd, skb->data + sizeof(struct rxu_stat_mm), sizeof(struct rx_hd)); + + /*copy the hw vector and rxu state */ + memcpy(dst_hw_rxhdr, skb->data + sizeof(struct rxu_stat_mm), sizeof(struct rx_hd)); + memcpy(&dst_hw_rxhdr->phy_info, \ + skb->data + SKB_DATA_TAG_OFFSET, \ + sizeof(struct rxu_stat_mm) - SKB_DATA_TAG_OFFSET); + + memcpy(dst_rxdesc_tag, skb->data + SKB_DATA_COM_HD_OFFSET + 2, sizeof(struct rxdesc_tag)); //two byte msdu_mode + /*recove the hdr to skb data*/ + skb_pull(skb, SKB_DATA_HD_OFFSET); //delete the frame type and amsdu flag + skb_push(skb, sizeof(struct hw_rxhdr)); + memcpy(skb->data, dst_hw_rxhdr, sizeof(struct hw_rxhdr)); //put hw header to skb + skb_push(skb, sizeof(struct rxdesc_tag)); + memcpy(skb->data, dst_rxdesc_tag, sizeof(struct rxdesc_tag)); //put rx desc tag to skb + + for(index = 0; index < sizeof(struct hw_rxhdr) + sizeof(struct rxdesc_tag); index++) + { + ECRNX_DBG("0x%x ", skb->data[index]); + } +#endif + ECRNX_DBG("%s exit!!", __func__); + return 0; +} + +/** + * @brief: ecrnx_sdio_init Initialize sdio interface. + * @param {ecrnx_hw} Main driver data + * @return: u8 + */ +u8 ecrnx_sdio_init(struct ecrnx_hw *ecrnx_hw) +{ + ECRNX_DBG("%s entry!!", __func__); + // Save the pointer to the register base + ecrnx_hw->ipc_env->pthis = (void*)ecrnx_hw; + +#ifdef CONFIG_TEST_ESWIN_SDIO + ecrnx_hw_set((void*)ecrnx_hw); +#endif + ECRNX_DBG("%s exit!!", __func__); + return 0; +} + +/** + * @brief: ecrnx_sdio_deinit DeInitialize sdio interface. + * @param {ecrnx_hw} Main driver data + * @return: none + */ +void ecrnx_sdio_deinit(struct ecrnx_hw *ecrnx_hw) +{ + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + sdio_rx_buf_deinit(); + memset(ecrnx_hw, 0, sizeof(struct ecrnx_hw)); + memset(&sdio_rx_buff, 0, sizeof(sdio_rx_buf_t)); +} diff --git a/drivers/net/wireless/eswin/sdio/ecrnx_sdio.h b/drivers/net/wireless/eswin/sdio/ecrnx_sdio.h new file mode 100644 index 000000000000..d2b210bf0bde --- /dev/null +++ b/drivers/net/wireless/eswin/sdio/ecrnx_sdio.h @@ -0,0 +1,173 @@ + /** + ****************************************************************************** + * + * @file ecrnx_sdio.h + * + * @brief ecrnx sdio header file + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef _ECRNX_SDIO_H_ +#define _ECRNX_SDIO_H_ + +#include "ecrnx_rx.h" +#include "eswin_utils.h" +#include "ecrnx_defs.h" +/* + * Number of Host buffers available for Data Rx handling + */ +#define SDIO_RXBUF_CNT 32 +#define SDIO_RXBUF_SIZE 12288 + +enum{ + SDIO_FRM_TYPE_RXDESC =1, + SDIO_FRM_TYPE_MSG, + SDIO_FRM_TYPE_DBG, + SDIO_FRM_TYPE_UNSUP_RX_VEC, + SDIO_FRM_TYPE_RADAR, + SDIO_FRM_TYPE_TBTT_SEC, + SDIO_FRM_TYPE_TBTT_PRIM, + SDIO_FRM_TYPE_MSG_ACK, + SDIO_FRM_TYPE_TXCFM, + SDIO_FRM_TYPE_IWPRIV, + SDIO_FRM_TYPE_UPDATE +}; + + +#define SKB_DATA_COM_HD_OFFSET sizeof(dispatch_hdr_t) +//#define SKB_DATA_TAG_OFFSET offsetof(struct rxu_stat_mm, phy_info) //two byte msdu flag +//#define SKB_DATA_HD_OFFSET sizeof(struct rxu_stat_mm) + sizeof(struct rx_hd) + + +struct sdio_rx_skb{ + bool flag; + u16 data_len; + struct sk_buff *skb; +}; + +typedef struct{ + atomic_t suspend; + uint8_t sdio_host_rx_buff_used; + struct sdio_rx_skb ecrnx_sdio_rx_skb[SDIO_RXBUF_CNT]; +}sdio_rx_buf_t; + +/// Structure containing the information about the PHY channel that is used +// typedef uint32_t u32_l; +struct phy_channel_info_sdio +{ + /// PHY channel information 1 + uint32_t info1; + /// PHY channel information 2 + uint32_t info2; +}; + +/*调整phy info〠patternï¼Œå› ä¸ºè¿™äº›ä¿¡æ¯å¿…é¡»è¦æ”¾åˆ°å¦å¤–一个buffä¸ä¼ 输*/ +/// Element in the pool of RX header descriptor. +struct rx_hd +{ + /// Total length of the received MPDU + uint16_t frmlen; + /// AMPDU status information + uint16_t ampdu_stat_info; + /// TSF Low + uint32_t tsflo; + /// TSF High + uint32_t tsfhi; + /// Rx Vector 1 + struct rx_vector_1 rx_vec_1; + /// Rx Vector 2 + struct rx_vector_2 rx_vec_2; + /// MPDU status information + uint32_t statinfo; +}; + +/*调整phy info〠patternï¼Œå› ä¸ºè¿™äº›ä¿¡æ¯å¿…é¡»è¦æ”¾åˆ°å¦å¤–一个buffä¸ä¼ 输*/ +struct rxu_stat_mm +{ + /*type of msg/dbg/frm etc.*/ + dispatch_hdr_t frm_type; + uint32_t fragment_flag : 1; + uint32_t is_qos : 1; + uint32_t need_reord : 1; + uint32_t need_pn_check : 1; + uint32_t is_ga : 1; + uint32_t flags_rsvd0 : 3; + uint32_t tid : 8; + uint16_t sn; + /// Length + uint16_t frame_len; + /// Status (@ref rx_status_bits) + uint16_t status; + uint16_t real_offset; /* dma address align 4, real data - real_offset */ +}; + + +extern sdio_rx_buf_t sdio_rx_buff; + +/** + * @brief: ecrnx_sdio_init Initialize sdio interface. + * @param {ecrnx_hw} Main driver data + * @return: u8 + */ +u8 ecrnx_sdio_init(struct ecrnx_hw *ecrnx_hw); + +/** + * @brief: ecrnx_sdio_deinit DeInitialize sdio interface. + * @param {ecrnx_hw} Main driver data + * @return: none + */ +void ecrnx_sdio_deinit(struct ecrnx_hw *ecrnx_hw); + +/** + * @brief: sdio_rx_buf_push: push a skb element to sdio rx buffer + * @param {skb} skb data need to push + * @param {recv_len} skb data length + * @return: sk_buff + */ +struct sk_buff * sdio_rx_buf_push(struct sk_buff *skb, uint16_t recv_len); + + +/** + * @brief: sdio_rx_buf_pop: pop a skb element from sdio rx buffer + * @param {void} + * @return: sk_buff + */ +struct sk_buff* sdio_rx_buf_pop(void); + +#ifdef CONFIG_ECRNX_WIFO_CAIL +/** + * @brief: sdio_host_rx_handler Handle the reception of rx frame + * @param {frm_type} received frame type + * @param {skb} received skb data + * @return: none + */ +void sdio_host_amt_rx_handler(uint32_t frm_type, struct sk_buff *skb); +#endif + +/** + * @brief: sdio_host_rx_handler Handle the reception of rx frame + * @param {frm_type} received frame type + * @param {env} pointer to the sdio Host environment + * @param {skb} received skb data + * @return: none + */ +void sdio_host_rx_handler(uint32_t frm_type, struct ipc_host_env_tag *env, struct sk_buff *skb); + +/** + * @brief: rcv_skb_convert convert the sdio received skb to the ecrnx handled skb. + * @param {src_rxu_state} received rxu state in skb + * @param {src_rxu_state} received rx header in skb + * @param {src_rxu_state} handled hw rxhdr in skb + * @param {src_rxu_state} handled rx desc tag in skb + * @return: u8 + */ +u8 rcv_skb_convert(struct rxu_stat_mm* src_rxu_state, \ + struct rx_hd* src_rx_hd, \ + struct sk_buff *skb, \ + struct hw_rxhdr* dst_hw_rxhdr,\ + struct rxdesc_tag* dst_rxdesc_tag); + +#endif /* __ECRNX_SDIO_H */ diff --git a/drivers/net/wireless/eswin/sdio/fw.c b/drivers/net/wireless/eswin/sdio/fw.c new file mode 100644 index 000000000000..9d6bab28d7e8 --- /dev/null +++ b/drivers/net/wireless/eswin/sdio/fw.c @@ -0,0 +1,158 @@ +/** +****************************************************************************** +* +* @file fw.c +* +* @brief ecrnx sdio firmware download functions +* +* Copyright (C) ESWIN 2015-2020 +* +****************************************************************************** +*/ + +#include <linux/firmware.h> +#include "core.h" +#include "sdio.h" +#include "fw_head_check.h" + +extern char *fw_name; + + +void eswin_fw_file_download(struct eswin *tr) +{ + int ret; + unsigned int length_all; + unsigned char length_str[9]={0}; + unsigned int lengthLeft, lengthSend, offset = HEAD_SIZE; + const u8 * dataAddr; + struct sk_buff *skb; + int file_num = 0; + unsigned int file_load_addr[3] = {0x10000U, 0x60800U, 0x80000U}; // ilm addr; dlm addr offset 0x800 for bootrom log; iram0 addr + + char str_sync[4] = {0x63, 0x6E, 0x79, 0x73}; + char str_cfg[] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00}; // default for sync + + skb = dev_alloc_skb(1024); + + ECRNX_PRINT("%s entry!!", __func__); + + + /* 1 sync */ + memcpy(skb->data, str_sync, 4); + tr->ops->write(tr, skb->data, 4); + ret = tr->ops->wait_ack(tr); + ECRNX_PRINT("dl-fw >> sync, ret: %d\n", ret); + + + dataAddr = tr->fw->data; + length_all = tr->fw->size - offset; + + while(length_all) + { + memcpy(length_str, dataAddr + offset, 8); + ECRNX_PRINT("-------------------------------------%s\n", length_str); + offset+=8; + length_all-=8; + ret = kstrtol(length_str, 10, (long*)&lengthLeft); + //ECRNX_PRINT("dl-fw >> file len, ret: %d len:%d\n", ret, lengthLeft); + if(ret==0 && lengthLeft) + { + length_all-=lengthLeft; + + /* 2 cfg addr and length */ + str_cfg[4] = (char)((file_load_addr[file_num]) & 0xFF); + str_cfg[5] = (char)(((file_load_addr[file_num])>>8) & 0xFF); + str_cfg[6] = (char)(((file_load_addr[file_num])>>16) & 0xFF); + str_cfg[7] = (char)(((file_load_addr[file_num])>>24) & 0xFF); + file_num++; + str_cfg[8] = (char)((lengthLeft) & 0xFF); + str_cfg[9] = (char)(((lengthLeft)>>8) & 0xFF); + str_cfg[10] = (char)(((lengthLeft)>>16) & 0xFF); + str_cfg[11] = (char)(((lengthLeft)>>24) & 0xFF); + + + memcpy(skb->data, &str_cfg[0], 12); + tr->ops->write(tr, skb->data, 12); + ret = tr->ops->wait_ack(tr); + //ECRNX_PRINT("dl-fw >> cfg, ret: %d\n", ret); + + + /* 3 load fw */ + do { + lengthSend = (lengthLeft >= 1024) ? 1024 : lengthLeft; //ECO3 supprot 64K buff + if(lengthLeft%512==0) + { + memcpy(skb->data, dataAddr + offset, lengthSend); + tr->ops->write(tr, skb->data, lengthSend); + + //ECRNX_PRINT("dl-fw >> ld(%d), ret: %d\n", offset/1024, ret); + + ret = tr->ops->wait_ack(tr); + } + else + { + memcpy(skb->data, dataAddr + offset, lengthSend&0xFFFFFE00U); + tr->ops->write(tr, skb->data, lengthSend&0xFFFFFE00U); + //ECRNX_PRINT("dl-fw >> ld(%d), ret: %d\n", offset/1024, ret); + ret = tr->ops->wait_ack(tr); + + memcpy(skb->data, dataAddr + offset + (int)(lengthLeft&0xFFFFFE00U), lengthSend&0x1FFU); + tr->ops->write(tr, skb->data, lengthSend&0x1FFU); + //ECRNX_PRINT("dl-fw >> ld(%d), ret: %d\n", offset/1024, ret); + ret = tr->ops->wait_ack(tr); + } + + //ECRNX_PRINT("dl-fw >> ld-ack(%d), ret: %d\n", offset/1024, ret); + offset += lengthSend; + lengthLeft -= lengthSend; + } while(lengthLeft); + //ECRNX_PRINT("dl-fw >> ld, ret: %d\n", ret); + } + } + + /* 4 start up */ + memset(skb->data, 0, 12); + tr->ops->write(tr, skb->data, 12); + tr->ops->wait_ack(tr); + + dev_kfree_skb(skb); +} + + + + + +bool eswin_fw_file_chech(struct eswin *tr) +{ + int status; + + if (fw_name == NULL) + goto err_fw; + + if (tr->fw) + return true; + + ECRNX_PRINT("%s, Checking firmware... (%s)\n", __func__, fw_name); + + status = request_firmware((const struct firmware **)&tr->fw, fw_name, tr->dev); + if (status != 0) { + ECRNX_PRINT("%s, error status = %d\n", __func__, status); + goto err_fw; + } + + ECRNX_PRINT("%s, request fw OK and size is %d\n", + __func__, tr->fw->size); + + if(fw_check_head(tr) == false) + { + goto err_fw; + } + return true; + + +err_fw: + tr->fw = NULL; + return false; +} + + diff --git a/drivers/net/wireless/eswin/sdio/fw.h b/drivers/net/wireless/eswin/sdio/fw.h new file mode 100644 index 000000000000..f3af4b91b7e5 --- /dev/null +++ b/drivers/net/wireless/eswin/sdio/fw.h @@ -0,0 +1,19 @@ +/** +****************************************************************************** +* +* @file fw.h +* +* @brief ecrnx sdio firmware download definitions +* +* Copyright (C) ESWIN 2015-2020 +* +****************************************************************************** +*/ + +#ifndef _FW_H_ +#define _FW_H_ + +void eswin_fw_file_download(struct eswin *tr); +bool eswin_fw_file_chech(struct eswin *tr); + +#endif diff --git a/drivers/net/wireless/eswin/sdio/sdio.c b/drivers/net/wireless/eswin/sdio/sdio.c new file mode 100644 index 000000000000..94a8b26b5ae9 --- /dev/null +++ b/drivers/net/wireless/eswin/sdio/sdio.c @@ -0,0 +1,925 @@ +/** + ****************************************************************************** + * + * @file sdio.c + * + * @brief sdio driver function definitions + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/mmc/sdio_func.h> +#include <linux/mmc/sdio_ids.h> +#include "core.h" +#include <uapi/linux/sched/types.h> +//#include "debug.h" +#include "ecrnx_defs.h" +#include "ecrnx_sdio.h" +#include "sdio.h" + +#include "sdio_host_interface.h" + +#define SDIO_DEBUG 1 + + +#define SDIO_ADDR_INFO (unsigned int)(0x081) +#define SDIO_ADDR_INFO_ASYNC (unsigned int)(0x082) +#define SDIO_ADDR_DATA (unsigned int)(0x080) +#define NEXT_BUF_SZ_OFFSET (0) +#define SLAVE_BUF_SZ_OFFSET (4) +#define SDIO_AVL_NOTIFY_FLAG (0x5A5A5A5A) + +static atomic_t suspend; +int stop_tx= 0; + +#ifdef CONFIG_SHOW_TX_SPEED +unsigned long sdio_tx_last_jiffies; +unsigned long sdio_tx_len_totol; +unsigned long sdio_tx_error_cnt; +#endif + +#ifdef CONFIG_SHOW_RX_SPEED +unsigned long sdio_rx_last_jiffies; +unsigned long sdio_rx_len_totol; +unsigned long sdio_rx_error_cnt; +#endif + + +struct eswin_sdio * g_sdio; + +#if SDIO_DEBUG +static struct eswin_sdio *trS; +static struct dentry *p_debugfs_sdio; + +static int debugfs_sdio_read(void *data, u64 *val) +{ + struct eswin_sdio *tr_sdio = trS; + + int pdata[10]; + + ECRNX_PRINT("%s\n", __func__); + ECRNX_PRINT("%s, func: 0x%x!!", __func__, tr_sdio->func); + ECRNX_PRINT("%s, func: 0x%x!!", __func__, tr_sdio->slave_avl_buf); + ECRNX_PRINT("%s, data addr: 0x%x, sdio_info: 0x%x, 0x%x !!", __func__, pdata, &tr_sdio->sdio_info, &(tr_sdio->sdio_info)); +#if 0 + sdio_claim_host(tr_sdio->func); + /* replace the sdio_memcpy_fromio with sdio_readsb. in which the op code is 0, the addr will not change during transmittion */ + ret = sdio_readsb(tr_sdio->func, pdata/* &tr_sdio->sdio_info */, SDIO_ADDR_INFO, 4); + //ret = 0; + if (ret < 0) { + ECRNX_PRINT(" debugfs_sdio_read failde, ret: %d\n", ret); + //print_hex_dump(KERN_DEBUG, "status - 1 ", DUMP_PREFIX_NONE, 16, 1, &priv->sdio_info, 32, false); + sdio_release_host(tr_sdio->func); + return ret; + } + print_hex_dump(KERN_DEBUG, "status - 1 ", DUMP_PREFIX_NONE, 16, 1, pdata, 16, false); + sdio_release_host(tr_sdio->func); +#endif + return 0; +} + +static int debugfs_sdio_write(void *data1, u64 val) +{ + int ret = 0; + //int cmd = (int)val; + char cmd[16] = {1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7}; + struct sk_buff * skb; + char * p; + +#if 1 + struct eswin_sdio *tr_sdio = trS; + + ECRNX_PRINT(" %s, tr_sdio->func: %x\n", __func__, g_sdio->func); + + sdio_claim_host(tr_sdio->func); + + /* replace the sdio_memcpy_xxio with sdio_xxxxsb. in sdio_xxxxsb the op code is 0, the addr will not change during transmittion */ + ret = sdio_writesb(tr_sdio->func, 0x100, &cmd, 16); + if (ret < 0) { + ECRNX_PRINT(" debugfs_sdio_write failde, ret: %d\n", ret); + } + + sdio_release_host(tr_sdio->func); +#else + + skb = dev_alloc_skb(4096); + memset(skb->data, 0x34, 4096); + + p = (char *)(skb->data); + for (ret=0; ret<4096; ret++) { + *p++ = (char)ret; + } + + sdio_host_send(skb->data, 4096, 0x100); +#endif + return ret; +} + +DEFINE_SIMPLE_ATTRIBUTE(debugfs_sdio, + debugfs_sdio_read, + debugfs_sdio_write, + "%llu\n"); + +void debugfs_sdio_init(void) +{ + ECRNX_PRINT("%s\n", __func__); + p_debugfs_sdio = debugfs_create_file("sdiod", 0777, NULL, NULL, &debugfs_sdio); +} + +void debug_sdio_rx_callback(struct sk_buff *skb) +{ + ECRNX_PRINT("rx-callback: %d\n", skb->len); + print_hex_dump(KERN_DEBUG, DBG_PREFIX_SDIO_RX, DUMP_PREFIX_NONE, 16, 1, skb->data, skb->len, false); + dev_kfree_skb(skb); +} +#endif + + +static inline u16 sdio_num_slots(struct eswin_sdio *tr_sdio, int dir) +{ + return (tr_sdio->slot[dir].head - tr_sdio->slot[dir].tail); +} + +#if 0 +static void sdio_credit_skb(struct eswin_sdio *tr_sdio) +{ + struct sk_buff *skb; + struct hif *hif; + struct wim *wim; + struct wim_credit_report *cr; + u8 *p; + int i; + int size = sizeof(*hif) + sizeof(struct wim) + sizeof(*cr); + + if (!once) { + once = true; + return; + } + + skb = dev_alloc_skb(size); + + p = skb->data; + hif = (void *)p; + hif->type = HIF_TYPE_WIM; + hif->subtype = HIF_WIM_SUB_EVENT; + hif->vifindex = 0; + hif->len = sizeof(*wim) + sizeof(*cr); + + p += sizeof(*hif); + wim = (void *)p; + wim->event = WIM_EVENT_CREDIT_REPORT; + + p += sizeof(*wim); + cr = (void *)p; + cr->h.type = WIM_TLV_AC_CREDIT_REPORT; + cr->h.len = sizeof(struct wim_credit_report_param); + + cr->v.change_index = 0; + + for (i = 0; i < CREDIT_QUEUE_MAX; i++) { + u8 room = tr_sdio->front[i] - tr_sdio->rear[i]; + + room = min(tr_sdio->credit_max[i], room); + cr->v.ac[i] = tr_sdio->credit_max[i] - room; + +#ifdef CONFIG_NRC_HIF_PRINT_FLOW_CONTROL + ECRNX_PRINT(" credit[%d] %d f:%d, r:%d\n", + i, cr->v.ac[i], tr_sdio->front[i], + tr_sdio->rear[i]); +#endif + } + + skb_put(skb, hif->len+sizeof(*hif)); + + if (tr_sdio->tr->rx_callback) { + tr_sdio->tr->rx_callback(tr_sdio->tr->umac_priv, skb); + } else { + dev_kfree_skb(skb); + } +} +#endif + +static int sdio_update_status(struct eswin_sdio *tr_sdio) +{ + u32 rear; + int ac, ret; + u8 * buf = kmalloc(4,GFP_ATOMIC); + + //ECRNX_PRINT("%s\n", __func__); + sdio_claim_host(tr_sdio->func); + trS = tr_sdio; + /* replace the sdio_memcpy_fromio with sdio_readsb. in which the op code is 0, the addr will not change during transmittion */ + ret = sdio_readsb(tr_sdio->func, buf, SDIO_ADDR_INFO_ASYNC, 4); + if (ret < 0) { + ECRNX_PRINT(" sdio_update_status, ret: %d\n", ret); + kfree(buf); + //print_hex_dump(KERN_DEBUG, "status - 1 ", DUMP_PREFIX_NONE, 16, 1, &priv->sdio_info, 32, false); + sdio_release_host(tr_sdio->func); + return ret; + } + + /* + if (slave_avl_buf < tr_sdio->curr_tx_size) + { + sdio_release_host(tr_sdio->func); + return -1; + } + */ + spin_lock(&tr_sdio->lock); + tr_sdio->slave_avl_buf = *(unsigned int*)buf; + spin_unlock(&tr_sdio->lock); + kfree(buf); + sdio_release_host(tr_sdio->func); + + return 0; +} + +static void sdio_poll_status(struct work_struct *work) +{ + struct eswin_sdio *tr_sdio = container_of(to_delayed_work(work), struct eswin_sdio, work); + + if( sdio_update_status(tr_sdio)) { + schedule_delayed_work(&tr_sdio->work, msecs_to_jiffies(1000)); + } else { + wake_up_interruptible(&tr_sdio->wait); + } +} + +int continue_transfer = 0; + +static struct sk_buff *sdio_rx_skb(struct eswin_sdio *tr_sdio) +{ + struct sk_buff *skb = NULL; + int ret; + int recv_len; + unsigned int slave_avl_buf; + //unsigned int slave_avl_buf_last; + //unsigned int real_length; + + if(tr_sdio->next_rx_size) + { + //clear next rx buffer size + tr_sdio->next_rx_size = 0; + } + else + { + /* Wait until at least one rx slot is non-empty */ + ret = wait_event_interruptible(tr_sdio->wait, (tr_sdio->recv_len > 1 || kthread_should_stop())); + if (ret < 0) + goto fail; + } + + + + if (kthread_should_stop()) + goto fail; + + recv_len = tr_sdio->recv_len; + skb = dev_alloc_skb(recv_len); + + sdio_claim_host(tr_sdio->func); + + /* replace the sdio_memcpy_fromio with sdio_readsb. in which the op code is 0, the addr will not change during transmittion */ + ret = sdio_readsb(tr_sdio->func, skb->data, SDIO_ADDR_DATA, recv_len); + if (ret){ + print_hex_dump(KERN_DEBUG, DBG_PREFIX_SDIO_RX, DUMP_PREFIX_NONE, 16, 1, skb->data, recv_len, false); + ECRNX_PRINT("[eswin-err] rx-len: %d, ret %d\n", recv_len, ret); + stop_tx = 1; + sdio_release_host(tr_sdio->func); + goto fail; + } + + //get next rx size + tr_sdio->next_rx_size=*(unsigned int *)&skb->data[NEXT_BUF_SZ_OFFSET]; + if(tr_sdio->next_rx_size > 1) + { + tr_sdio->recv_len = tr_sdio->next_rx_size; + } + else + { + tr_sdio->recv_len = 1; + } + //get slave avl buf cnt + slave_avl_buf = *(unsigned int *)&skb->data[SLAVE_BUF_SZ_OFFSET] >> 16; + spin_lock(&tr_sdio->lock); + tr_sdio->slave_avl_buf = slave_avl_buf; + spin_unlock(&tr_sdio->lock); + + skb_put(skb, recv_len); + skb_queue_tail(&tr_sdio->skb_rx_list,skb); + + sdio_release_host(tr_sdio->func); + + if (slave_avl_buf > (SDIO_PKG_MAX_CNT-1)) + { + if (atomic_read(&tr_sdio->slave_buf_suspend)) + { + wake_up_interruptible(&tr_sdio->wait); + } + } + + //ECRNX_PRINT("rx-len: %d, skb: 0x%x \n", skb->len, skb); + return skb; + +fail: + if (skb) + dev_kfree_skb(skb); + return NULL; +} + + +#include <linux/sched.h> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) +#include <uapi/linux/sched/types.h> +#endif +//extern int sched_setscheduler(struct task_struct *, int, const struct sched_param *); + +static int sdio_rx_unpack_thread(void *data) +{ + struct eswin_sdio *tr_sdio = (struct eswin_sdio *)data; + struct eswin *tr = tr_sdio->tr; + struct sk_buff *skb; + struct sk_buff *skb_frag; + struct sdio_rx_head_t * rx_head; + int ret; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) + struct sched_param param = { .sched_priority = 1 }; + param.sched_priority = 56; + sched_setscheduler(get_current(), SCHED_FIFO, ¶m); +#else + sched_set_fifo(get_current()); +#endif + ECRNX_PRINT("rx unpack thread entry\n"); + + while (!kthread_should_stop()) + { + ret = wait_event_interruptible(tr_sdio->wait_unpack, skb_peek(&tr_sdio->skb_rx_list) + || kthread_should_stop() ); + if(kthread_should_stop()) + continue; + if (ret < 0) + { + ECRNX_ERR("rx unpack thread error!\n"); + return 0; + } + while(NULL != (skb = skb_dequeue(&tr_sdio->skb_rx_list))) + { + while (skb->len > 8)//valid data must contain 8 byte head + { + rx_head = (struct sdio_rx_head_t *)skb->data; + if(rx_head->data_len == 0) + break; + if (*(unsigned int *)&skb->data[8] == SDIO_AVL_NOTIFY_FLAG && rx_head->data_len == 12) + { + skb_pull(skb, ALIGN(rx_head->data_len, 4)); + continue; + } + skb_frag = dev_alloc_skb(rx_head->data_len); + memcpy(skb_frag->data,skb->data,rx_head->data_len); + skb_put(skb_frag,rx_head->data_len); + + if (skb_frag->len > skb->len) + { + ECRNX_ERR("skb len error!!! frag len:%d skb->len:%d\n",skb_frag->len,skb->len); + print_hex_dump(KERN_DEBUG, DBG_PREFIX_SDIO_RX, DUMP_PREFIX_NONE, 16, 1, skb->data, skb->len, false); + BUG_ON(1); + } + tr->rx_callback(tr->umac_priv, skb_frag); + skb_pull(skb,ALIGN(rx_head->data_len, 4)); + } + dev_kfree_skb(skb); + } + } + ECRNX_PRINT("rx unpack thread exit\n"); + return 0; +} + +static int sdio_rx_thread(void *data) +{ + struct eswin_sdio *tr_sdio = (struct eswin_sdio *)data; + struct eswin *tr = tr_sdio->tr; + struct sk_buff *skb; + struct sk_buff *skb_frag; + struct sdio_rx_head_t * rx_head; + int is_suspend; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) + struct sched_param param = { .sched_priority = 1 }; + param.sched_priority = 56; + sched_setscheduler(get_current(), SCHED_FIFO, ¶m); +#else + sched_set_fifo(get_current()); +#endif + ECRNX_PRINT("rx thread entry, loopbakc: %d\n", tr->loopback); + + while (!kthread_should_stop()) + { + skb = sdio_rx_skb(tr_sdio); + is_suspend = atomic_read(&suspend); + ECRNX_DBG("rx_cb: 0x%x, skb: 0x%x, is_suspend: 0x%x \n", tr->rx_callback, skb, is_suspend); + if ((!tr->rx_callback) || (skb && is_suspend)){ + dev_kfree_skb(skb); + } + else if (skb){ + wake_up_interruptible(&tr_sdio->wait_unpack); +#if 0 + while (skb->len > 8)//valid data must contain 8 byte head + { + //printk("skb len 0:%d",skb->len); + rx_head = (struct sdio_rx_head_t *)skb->data; + if(rx_head->data_len == 0) + break; + skb_frag = dev_alloc_skb(rx_head->data_len); + memcpy(skb_frag->data,skb->data,rx_head->data_len); + skb_put(skb_frag,rx_head->data_len); + //printk("skb_frag len 1:%d",skb_frag->len); + + if (skb_frag->len > skb->len) + { + printk("skb len error!!! frag len:%d skb->len:%d\n",skb_frag->len,skb->len); + print_hex_dump(KERN_DEBUG, "eswin-rx: ", DUMP_PREFIX_NONE, 16, 1, skb->data, skb->len, false); + BUG_ON(1); + } + + tr->rx_callback(tr->umac_priv, skb_frag); + skb_pull(skb,ALIGN(rx_head->data_len, 4)); + //printk("skb len 1:%d",skb->len); + //break; + + } + dev_kfree_skb(skb); + //dev_alloc_skb + //tr->rx_callback(tr->umac_priv, skb); + //debug_sdio_rx_callback(skb); +#endif + } + else { + ECRNX_ERR("rx-head: %d, rx-tail: %d, rx-cnt: %d\n", + tr_sdio->slot[RX_SLOT].head, tr_sdio->slot[RX_SLOT].tail, tr_sdio->sdio_info.info_rd); + break; + } + } + ECRNX_PRINT("rx thread exit\n"); + + return 0; +} + +#if 0 +static int sdio_noop(struct eswin *tr, struct sk_buff *skb) +{ + ECRNX_PRINT("%s exit!!", __func__); + return 0; +} +#endif + +void dump_sdio_buf(struct tx_buff_node *node) +{ + int i = 0; + + ECRNX_PRINT("sdio tx len %d\n", node?node->len:-1); + /*for (i = 0; i < node->len; ++i) { + if (i % 16 == 0 && i) { + printk("\n"); + } + printk("%02x ", ((unsigned char *)(node->buff))[i]); + }*/ + +} + +static int sdio_xmit(struct eswin *tr, struct tx_buff_pkg_node *node) +{ + struct eswin_sdio *tr_sdio = (struct eswin_sdio *)tr->drv_priv; + struct eswin * tr1 = tr_sdio->tr; + int ret,flag; + unsigned int slave_avl_buf = 0; + + //dump_sdio_buf(node); + spin_lock(&tr_sdio->lock); + slave_avl_buf = tr_sdio->slave_avl_buf; + spin_unlock(&tr_sdio->lock); + if(slave_avl_buf < node->node_cnt && (node->flag & FLAG_MSG_TYPE_MASK) == TX_FLAG_TX_DESC) + { + atomic_set(&tr_sdio->slave_buf_suspend, 1); + ret = wait_event_interruptible(tr_sdio->wait, node->node_cnt < tr_sdio->slave_avl_buf); + if (ret < 0) + { + ECRNX_PRINT("[transa] sdio_xmit, wait_event_interruptible fail, ret = %d slave_avl_buf=%d\n", ret, tr_sdio->slave_avl_buf); + return ret; + } + cancel_delayed_work_sync(&tr_sdio->work); + atomic_set(&tr_sdio->slave_buf_suspend, 0); + } + sdio_claim_host(tr_sdio->func); + /* replace the sdio_memcpy_xxio with sdio_xxxxsb. in sdio_xxxxsb the op code is 0, the addr will not change during transmittion */ + ret = sdio_writesb(tr_sdio->func, node->flag, node->buff, node->len); + if(ret) { + //stop_tx = 1; + ECRNX_ERR("sdio_xmit error, len = %d, ret:%d slave_avl_buf=%d ,node->node_cnt :%d\n",\ + node->len, ret, tr_sdio->slave_avl_buf,node->node_cnt); + print_hex_dump(KERN_DEBUG, DBG_PREFIX_SDIO_TX, DUMP_PREFIX_NONE, 32, 1, + node->buff, 64, false); + } + + spin_lock(&tr_sdio->lock); + if ((node->flag & FLAG_MSG_TYPE_MASK) == TX_FLAG_TX_DESC) + { + tr_sdio->slave_avl_buf -= node->node_cnt; + } + spin_unlock(&tr_sdio->lock); + + sdio_release_host(tr_sdio->func); + ECRNX_DBG(" sdio_xmit ok, len = %d, flag: 0x%02x!\n", node->len, node->flag); + return ret; +} + +static int sdio_start(struct eswin *tr) +{ + int ret=0; + + struct eswin_sdio *tr_sdio = (struct eswin_sdio *)tr->drv_priv; + + ECRNX_PRINT("%s\n", __func__); + + /* Start rx thread */ + //if(tr->loopback == 1) + // return 0; + tr_sdio->curr_tx_size = 0; + ret = sdio_update_status(tr_sdio); + INIT_DELAYED_WORK(&tr_sdio->work, sdio_poll_status); + + tr_sdio->kthread = kthread_run(sdio_rx_thread, tr_sdio, "sdio-rx"); + tr_sdio->kthread_unpack = kthread_run(sdio_rx_unpack_thread, tr_sdio, "sdio-rx-unpack"); + + atomic_set(&suspend, 0); + atomic_set(&tr_sdio->slave_buf_suspend, 0); + spin_lock_init(&tr_sdio->lock); + + return ret; +} + + +static int sdio_suspend(struct eswin *tr) +{ + atomic_set(&suspend, 1); + return 0; +} + +static int sdio_resume(struct eswin *tr) +{ + atomic_set(&suspend, 0); + return 0; +} + + +static int sdio_raw_write(struct eswin *tr, const void *data, const u32 len) +{ + int ret; + struct eswin_sdio *tr_sdio = (struct eswin_sdio *)tr->drv_priv; + + ECRNX_PRINT(" %s, entry~", __func__); + + sdio_claim_host(tr_sdio->func); + /* replace the sdio_memcpy_xxio with sdio_xxxxsb. in sdio_xxxxsb the op code is 0, the addr will not change during transmittion */ + ret = sdio_writesb(tr_sdio->func, SDIO_ADDR_DATA, data, len); + sdio_release_host(tr_sdio->func); + + return ret; +} + + +static int sdio_wait_ack(struct eswin *tr) +{ + int data = 0; + u8 * buf = kmalloc(4,GFP_ATOMIC); + struct eswin_sdio *tr_sdio = (struct eswin_sdio *)tr->drv_priv; + + ECRNX_PRINT(" %s, entry~", __func__); + + sdio_claim_host(tr_sdio->func); + /* replace the sdio_memcpy_xxio with sdio_xxxxsb. in sdio_xxxxsb the op code is 0, the addr will not change during transmittion */ + sdio_readsb(tr_sdio->func, buf, SDIO_ADDR_DATA, 1); + sdio_release_host(tr_sdio->func); + data = *(int *)buf; + kfree(buf); + + return data; +} + + + +static struct sdio_ops eswin_sdio_ops = { + .start = sdio_start, + .xmit = sdio_xmit, + .suspend = sdio_suspend, + .resume = sdio_resume, + .write = sdio_raw_write, + .wait_ack = sdio_wait_ack, +}; + + + +static void eswin_sdio_irq_handler(struct sdio_func *func) +{ + struct eswin_sdio *tr_sdio = sdio_get_drvdata(func); + u32 rear; + unsigned char lowbyte, highbyte; + int ret, ac; + + //printk(" %s, entry~\n", __func__); + + sdio_claim_host(tr_sdio->func); + lowbyte = sdio_readb(tr_sdio->func, 0x00, &ret); + highbyte = sdio_readb(tr_sdio->func, 0x01, &ret); + tr_sdio->recv_len = (highbyte << 8) | lowbyte; + //printk("%s %u, %hhu, %hhu!", __func__, tr_sdio->recv_len, highbyte, lowbyte); + +#if 0 + //_info(" eswin_sdio_irq_handler, len: %d\n", tr_sdio->recv_len); + if(tr_sdio->recv_len == 1) { + + ret = sdio_memcpy_fromio(tr_sdio->func, &tr_sdio->sdio_info, SDIO_ADDR_INFO, 0x10 /*priv->recv_len*/); + if (ret < 0) { + ECRNX_PRINT(" eswin_sdio_irq_handler, info-ret: %d\n", ret); + //print_hex_dump(KERN_DEBUG, "status - 2 ", DUMP_PREFIX_NONE, 16, 1, &priv->sdio_info, 32, false); + sdio_release_host(tr_sdio->func); + return; + } + //ECRNX_PRINT(" get info\n"); + //ECRNX_PRINT(" eswin_sdio_irq_handler, info-wr: %#x, info-rd: %#x\n", priv->sdio_info.info_wr, priv->sdio_info.info_rd); + + tr_sdio->slot[TX_SLOT].head = tr_sdio->sdio_info.info_wr; + tr_sdio->slot[RX_SLOT].head = tr_sdio->sdio_info.info_rd; + + + //if (hdev->nw->loopback) + // return; + + if((tr_sdio->sdio_info.credit_vif0 != tr_sdio->credit_vif0) + ||(tr_sdio->sdio_info.credit_vif1 != tr_sdio->credit_vif1)){ + /* Update VIF0 credit */ + rear = tr_sdio->sdio_info.credit_vif0; + tr_sdio->credit_vif0 = rear; + for (ac = 0; ac < 4; ac++) + tr_sdio->rear[ac] = (rear >> 8*ac) & 0xff; + + + /* Update VIF1 credit */ + rear = tr_sdio->sdio_info.credit_vif1; + tr_sdio->credit_vif1 = rear; + for (ac = 0; ac < 4; ac++) + tr_sdio->rear[6+ac] = (rear >> 8*ac) & 0xff; + + //need_credit = 1; + //sdio_release_host(tr_sdio->func); + //sdio_credit_skb(tr_sdio); + //sdio_claim_host(func); + } +#if 0 + printk("irq: wr: %d, rd: %d, credit:%d/%d, %d/%d, %d/%d, %d/%d\n", + priv->sdio_info.info_wr, priv->sdio_info.info_rd, + priv->rear[3], priv->front[3], + priv->rear[2], priv->front[2], + priv->rear[1], priv->front[1], + priv->rear[0], priv->front[0]); +#endif + +#ifdef CONFIG_NRC_HIF_PRINT_FLOW_CONTROL + //nrc_dbg(NRC_DBG_HIF, "-%s\n", __func__); +#endif + + } + else + { + //ECRNX_PRINT(" get data len: %d\n", priv->recv_len); + } +#endif + sdio_release_host(tr_sdio->func); + wake_up_interruptible(&tr_sdio->wait); +} + +static void eswin_sdio_irq2_handler(struct sdio_func *func) +{ + struct eswin_sdio *tr_sdio = g_sdio; + unsigned char lowbyte, highbyte; + int ret, recv_len; + struct sk_buff * skb; + + ECRNX_PRINT(" %s, entry~\n", __func__); + + sdio_claim_host(func); + lowbyte = sdio_readb(func, 0x00, &ret); + highbyte = sdio_readb(func, 0x01, &ret); + sdio_release_host(func); + + recv_len = (highbyte << 8) | lowbyte; + skb = dev_alloc_skb(recv_len); + skb_put(skb, recv_len); + + sdio_claim_host(func); + /* replace the sdio_memcpy_fromio with sdio_readsb. in which the op code is 0, the addr will not change during transmittion */ + ret = sdio_readsb(func, skb->data, SDIO_ADDR_DATA, recv_len); + if (ret){ + ECRNX_ERR("[eswin-err] rx-len: %d\n", skb->len); + } + + sdio_release_host(func); + + print_hex_dump(KERN_DEBUG, DBG_PREFIX_SDIO_RX, DUMP_PREFIX_NONE, 16, 1, skb->data, skb->len, false); + dev_kfree_skb(skb); +} + +struct device *eswin_sdio_get_dev(void *plat) +{ + struct eswin* tr = (struct eswin*)plat; + + return tr->dev; +} + + +static int eswin_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id) +{ + int ret; + struct eswin_sdio * tr_sdio; + struct eswin* tr; + + ECRNX_PRINT("%s entry, func: %d!!", __func__, func->num); + + if (func->num == 1) { + tr = eswin_core_create(sizeof(* tr_sdio), &func->dev, &eswin_sdio_ops); + if(!tr) { + dev_err(&func->dev, "failed to allocate core\n"); + return -ENOMEM; + } + + tr_sdio = (struct eswin_sdio *)tr->drv_priv; + g_sdio = tr_sdio; + tr_sdio->tr = tr; + tr_sdio->func = func; + } else { + g_sdio->func2 = func; + + sdio_claim_host(func); + sdio_enable_func(func); + func->max_blksize = ESWIN_SDIO_BLK_SIZE; + sdio_set_block_size(func, func->max_blksize); + sdio_claim_irq(func, eswin_sdio_irq2_handler); + sdio_release_host(func); + return 0; + } + + tr_sdio->slot[TX_SLOT].size = 456; + tr_sdio->slot[RX_SLOT].size = 492; + + tr_sdio->credit_max[0] = CREDIT_AC0; + tr_sdio->credit_max[1] = CREDIT_AC1; + tr_sdio->credit_max[2] = CREDIT_AC2; + tr_sdio->credit_max[3] = CREDIT_AC3; + + tr_sdio->credit_max[6] = CREDIT_AC0; + tr_sdio->credit_max[7] = CREDIT_AC1; + tr_sdio->credit_max[8] = CREDIT_AC2; + tr_sdio->credit_max[9] = CREDIT_AC3; + + init_waitqueue_head(&tr_sdio->wait); + init_waitqueue_head(&tr_sdio->wait_unpack); + + func->max_blksize = ESWIN_SDIO_BLK_SIZE; + + skb_queue_head_init(&tr_sdio->skb_rx_list); + //skb_queue_head_init(tr_sdio->skb_rx_unpack_list); + + sdio_claim_host(func); + + ret = sdio_enable_func(func); + if(ret) { + ECRNX_PRINT("failed to enable sdio func\n"); + goto release; + } + + ret = sdio_set_block_size(func, func->max_blksize); + if(ret) { + ECRNX_PRINT("failed to set sdio func block size\n"); + goto release; + } + + ret = sdio_claim_irq(func, eswin_sdio_irq_handler); + if(ret) { + ECRNX_PRINT("failed to claim sdio irq\n"); + goto release; + } + + sdio_release_host(func); + sdio_set_drvdata(func, tr_sdio); + +#ifdef CONFIG_SHOW_TX_SPEED + sdio_tx_last_jiffies = jiffies; + sdio_tx_len_totol = 0; + sdio_tx_error_cnt = 0; +#endif + +#ifdef CONFIG_SHOW_RX_SPEED + sdio_rx_last_jiffies = jiffies; + sdio_rx_len_totol = 0; + sdio_rx_error_cnt = 0; +#endif + + + ret = eswin_core_register(tr); + if(ret) { + ECRNX_PRINT("failed to register core\n"); + + } + + debugfs_sdio_init(); + + ECRNX_PRINT("%s exit!!", __func__); + return 0; + +release: + sdio_release_host(func); + return ret; +} + +static void eswin_sdio_remove(struct sdio_func *func) +{ + struct eswin_sdio *tr_sdio = sdio_get_drvdata(func); + struct eswin *tr = tr_sdio->tr; + + + ECRNX_PRINT(" %s entry!!\n", __func__); + debugfs_remove_recursive(p_debugfs_sdio); + + eswin_core_unregister(tr); + + sdio_claim_host(func); + sdio_release_irq(func); + sdio_disable_func(func); + sdio_release_host(func); + + kthread_stop(tr_sdio->kthread); + wake_up_interruptible(&tr_sdio->wait); + kthread_stop(tr_sdio->kthread_unpack); + wake_up_interruptible(&tr_sdio->wait_unpack); + + eswin_core_destroy(tr); + ECRNX_PRINT(" %s exit!!\n", __func__); +} + + +static const struct sdio_device_id eswin_sdio_dev[] = +{ + { SDIO_DEVICE(ESWIN_SDIO_VENDER, ESWIN_SDIO_DEVICE) }, + {}, +}; + +static struct sdio_driver eswin_sdio_driver = +{ + .name = "eswin_sdio", + .id_table = eswin_sdio_dev, + .probe = eswin_sdio_probe, + .remove = eswin_sdio_remove, +}; + +static int __init eswin_sdio_init(void) +{ + int ret; + ECRNX_PRINT(" %s entry!!", __func__); + + ret = sdio_register_driver(&eswin_sdio_driver); + if (ret) + ECRNX_PRINT("sdio driver registration failed: %d\n", ret); + + ECRNX_PRINT(" %s exit!!", __func__); + return ret; +} + +static void __exit eswin_sdio_exit(void) +{ + ECRNX_PRINT(" %s entry!!", __func__); + sdio_unregister_driver(&eswin_sdio_driver); + ECRNX_PRINT(" %s exit!!", __func__); +} + +int ecrnx_sdio_register_drv(void) +{ + return eswin_sdio_init(); +} + +void ecrnx_sdio_unregister_drv(void) +{ + return eswin_sdio_exit(); +} + +//module_init(eswin_sdio_init); +//module_exit(eswin_sdio_exit); + +//MODULE_AUTHOR("Transa-Semi"); +//MODULE_LICENSE("Dual BSD/GPL"); +//MODULE_DESCRIPTION("Driver support for Transa-Semi 802.11 WLAN SDIO driver"); +//MODULE_SUPPORTED_DEVICE("Transa-Semi 802.11 devices"); + diff --git a/drivers/net/wireless/eswin/sdio/sdio.h b/drivers/net/wireless/eswin/sdio/sdio.h new file mode 100644 index 000000000000..6dde7c1e546d --- /dev/null +++ b/drivers/net/wireless/eswin/sdio/sdio.h @@ -0,0 +1,156 @@ +/** + ****************************************************************************** + * + * @file sdio.h + * + * @brief sdio driver definitions + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef __SDIO_H +#define __SDIO_H + +#include "ecrnx_defs.h" +#include "core.h" + +#define ESWIN_SDIO_VENDER 0x0296 +#define ESWIN_SDIO_DEVICE 0x5347 + +#define ESWIN_SDIO_BLK_SIZE 512 + +#define TX_SLOT 0 +#define RX_SLOT 1 + +#define CREDIT_QUEUE_MAX (12) + + +#define TCN (3*2) +#define TCNE (0) + +#define CREDIT_AC0 4//(TCN*4+TCNE) /* BK */ +#define CREDIT_AC1 30//(TCN*3+TCNE) /* BE */ +#define CREDIT_AC2 4//(TCN*2+TCNE) /* VI */ +#define CREDIT_AC3 4//(TCN*1+TCNE) /* VO */ + +struct sdio_sys_reg { + u8 wakeup; /* 0x0 */ + u8 status; /* 0x1 */ + u16 chip_id; /* 0x2-0x3 */ + u32 modem_id; /* 0x4-0x7 */ + u32 sw_id; /* 0x8-0xb */ + u32 board_id; /* 0xc-0xf */ +} __packed; + +struct sdio_status_reg { + struct { + u8 mode; + u8 enable; + u8 latched_status; + u8 status; + } eirq; + u8 txq_status[6]; + u8 rxq_status[6]; + u32 msg[4]; + +#define EIRQ_IO_ENABLE (1<<2) +#define EIRQ_EDGE (1<<1) +#define EIRQ_ACTIVE_LO (1<<0) + +#define EIRQ_DEV_SLEEP (1<<3) +#define EIRQ_DEV_READY (1<<2) +#define EIRQ_RXQ (1<<1) +#define EIRQ_TXQ (1<<0) + +#define TXQ_ERROR (1<<7) +#define TXQ_SLOT_COUNT (0x7F) +#define RXQ_SLOT_COUNT (0x7F) + +} __packed; + +struct sdio_rx_head_t { + unsigned int next_rx_len; + unsigned short data_len; + unsigned short avl_len; +}; + +struct sdio_data_t { + unsigned int credit_vif0; + unsigned int credit_vif1; + unsigned int info_wr; + unsigned int info_rd; +}; + +struct eswin_sdio { + struct eswin * tr; + struct sdio_func *func; + struct sdio_func *func2; + + /* work, kthread, ... */ + struct delayed_work work; + struct task_struct *kthread; + wait_queue_head_t wait; /* wait queue */ + + struct task_struct *kthread_unpack; + wait_queue_head_t wait_unpack; + + struct { + struct sdio_sys_reg sys; + struct sdio_status_reg status; + } hw; + + spinlock_t lock; + struct { + unsigned int head; + unsigned int tail; + unsigned int size; + unsigned int count; + } slot[2]; + /* VIF0(AC0~AC3), BCN, CONC, VIF1(AC0~AC3), padding*/ + u8 front[CREDIT_QUEUE_MAX]; + u8 rear[CREDIT_QUEUE_MAX]; + u8 credit_max[CREDIT_QUEUE_MAX]; + +/* + unsigned long loopback_prev_cnt; + unsigned long loopback_total_cnt; + unsigned long loopback_last_jiffies; + unsigned long loopback_read_usec; + unsigned long loopback_write_usec; + unsigned long loopback_measure_cnt; +*/ + //struct eswin_sdio_ops_t *ops; + unsigned int recv_len; + unsigned int recv_num; +// struct dentry *debugfs; + + unsigned int credit_vif0; + unsigned int credit_vif1; + + struct sdio_data_t sdio_info; + unsigned int slave_avl_buf; + atomic_t slave_buf_suspend; + unsigned int curr_tx_size; + unsigned int next_rx_size; +// struct sk_buff *skb_tx_last; + + struct sk_buff_head skb_rx_list; + //struct sk_buff_head *skb_rx_unpack_list; + +}; + +struct sdio_ops { + int (*start)(struct eswin *tr); + int (*xmit)(struct eswin *tr, struct tx_buff_pkg_node * node); + int (*suspend)(struct eswin *tr); + int (*resume)(struct eswin *tr); + int (*write)(struct eswin *tr, const void* data, const u32 len); + int (*wait_ack)(struct eswin *tr); +}; + +extern int ecrnx_sdio_register_drv(void); +extern void ecrnx_sdio_unregister_drv(void); + +#endif /* __SDIO_H */ diff --git a/drivers/net/wireless/eswin/sdio/sdio_host_interface.h b/drivers/net/wireless/eswin/sdio/sdio_host_interface.h new file mode 100644 index 000000000000..146047976868 --- /dev/null +++ b/drivers/net/wireless/eswin/sdio/sdio_host_interface.h @@ -0,0 +1,29 @@ +/** + ****************************************************************************** + * + * @file sdio_host_interface.h + * + * @brief sdio host interface definitions + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef _SDIO_HOST_INTERFACE_H +#define _SDIO_HOST_INTERFACE_H +/******************************************************************************* + * Function: sdio_xmit + * Description:send buff from host to slave + * Parameters: + * Input: void *buff, int len, int flag + * + * Output: + * + * Returns: 0 + * + * + * Others: + ********************************************************************************/ +int sdio_host_send(void *buff, int len, int flag); +#endif
\ No newline at end of file diff --git a/drivers/net/wireless/eswin/slave_log_buf.c b/drivers/net/wireless/eswin/slave_log_buf.c new file mode 100644 index 000000000000..99009e4372d8 --- /dev/null +++ b/drivers/net/wireless/eswin/slave_log_buf.c @@ -0,0 +1,149 @@ +#include "slave_log_buf.h" + +spinlock_t lock; + +uint32_t ring_buffer_init(struct ring_buffer* ring_buf, uint32_t size) +{ + if(ring_buf== NULL) + return false; + + if (!is_power_of_2(size)) + { + printk("size must be power of 2.\n"); + return false; + } + + memset(ring_buf, 0, sizeof(struct ring_buffer)); + spin_lock_init(&lock); + ring_buf->buffer = kmalloc(size,GFP_KERNEL); + ring_buf->size = size; + ring_buf->write_point = 0; + ring_buf->read_point = 0; + ring_buf->f_lock = &lock; + ring_buf->init = true; + ring_buf->cover = false; + ring_buf->show = false; + return true; +} + +void ring_buffer_deinit(struct ring_buffer *ring_buf) +{ + memset(ring_buf, 0, sizeof(struct ring_buffer)); + if(ring_buf->buffer != NULL) + { + kfree(ring_buf->buffer); + ring_buf->buffer = NULL; + } + +} + + +uint32_t __ring_buffer_len(const struct ring_buffer *ring_buf) +{ + if(ring_buf->cover == false) + { + return ring_buf->write_point; + } + if(ring_buf->show == true) + { + if(ring_buf->write_point < ring_buf->read_point) + return (ring_buf->write_point + ring_buf->size - ring_buf->read_point); + else + return (ring_buf->write_point - ring_buf->read_point); + } + + return ring_buf->size; +} + + +uint32_t __ring_buffer_get(struct ring_buffer *ring_buf, void * buffer, uint32_t size) +{ + if((ring_buf== NULL) || (buffer== NULL)) + return 0; + + uint32_t copy_len = 0; + uint32_t read_len = 0; + if(ring_buf->write_point < ring_buf->read_point) + read_len = (ring_buf->write_point + ring_buf->size - ring_buf->read_point); + else + read_len = (ring_buf->write_point - ring_buf->read_point); + + size = min(size, read_len); + /* first get the data from fifo->read_point until the end of the buffer */ + copy_len = min(size, ring_buf->size - ring_buf->read_point); + memcpy(buffer, ring_buf->buffer + ring_buf->read_point, copy_len); + /* then get the rest (if any) from the beginning of the buffer */ + if(size - copy_len > 0) + { + memcpy(buffer + copy_len, ring_buf->buffer, size - copy_len); + } + + ring_buf->read_point += size; + ring_buf->read_point = (ring_buf->read_point & (ring_buf->size - 1)); + + return size; +} +//Ïò»º³åÇøÖдæ·ÅÊý¾Ý +uint32_t __ring_buffer_put(struct ring_buffer *ring_buf, void *buffer, uint32_t size) +{ + if((ring_buf == NULL) || (buffer == NULL)) + { + return 0; + } + uint32_t copy_len = 0; + + /* first put the data starting from fifo->write_point to buffer end */ + copy_len = min(size, ring_buf->size - ring_buf->write_point); + + memcpy(ring_buf->buffer + ring_buf->write_point, buffer, copy_len); + /* then put the rest (if any) at the beginning of the buffer */ + if(size - copy_len > 0) + { + memcpy(ring_buf->buffer, buffer + copy_len, size - copy_len); + ring_buf->cover = true; + } + + ring_buf->write_point += size; + ring_buf->write_point = (ring_buf->write_point & (ring_buf->size - 1)); + return size; +} + +uint32_t ring_buffer_len(const struct ring_buffer *ring_buf) +{ + uint32_t len = 0; + spin_lock_irq(ring_buf->f_lock); + len = __ring_buffer_len(ring_buf); + spin_unlock_irq(ring_buf->f_lock); + return len; +} + +uint32_t ring_buffer_get(struct ring_buffer *ring_buf, void *buffer, uint32_t size) +{ + uint32_t ret; + spin_lock_irq(ring_buf->f_lock); + ret = __ring_buffer_get(ring_buf, buffer, size); + spin_unlock_irq(ring_buf->f_lock); + return ret; +} + +uint32_t ring_buffer_put(struct ring_buffer *ring_buf, void *buffer, uint32_t size) +{ + uint32_t ret; + spin_lock_irq(ring_buf->f_lock); + ret = __ring_buffer_put(ring_buf, buffer, size); + spin_unlock_irq(ring_buf->f_lock); + return ret; +} +uint32_t ring_buffer_scrolling_display(struct ring_buffer *ring_buf, char show) +{ + uint32_t ret = true; + spin_lock_irq(ring_buf->f_lock); + ring_buf->show = show; + if((ring_buf->cover == true)&&(show == true)) + { + ring_buf->read_point = (ring_buf->write_point & (ring_buf->size - 1)) + 1; + } + spin_unlock_irq(ring_buf->f_lock); + return ret; +} + diff --git a/drivers/net/wireless/eswin/slave_log_buf.h b/drivers/net/wireless/eswin/slave_log_buf.h new file mode 100644 index 000000000000..bd756e7278c7 --- /dev/null +++ b/drivers/net/wireless/eswin/slave_log_buf.h @@ -0,0 +1,37 @@ +#ifndef _SLAVE_LOG_BUF_H_ +#define _SLAVE_LOG_BUF_H_ +#include <linux/firmware.h> +#include "core.h" +#include "ecrnx_debug.h" + + +#ifndef uint32_t +#define uint32_t unsigned int +#endif + + +#define is_power_of_2(x) ((x) != 0 && (((x) & ((x) - 1)) == 0)) + +//#define min(a, b) (((a) < (b)) ? (a) : (b)) + +struct ring_buffer +{ + void *buffer; + uint32_t size; + uint32_t write_point; + uint32_t read_point; + char cover; + char show; + char init; + spinlock_t *f_lock; +}; + +uint32_t ring_buffer_get(struct ring_buffer *ring_buf, void *buffer, uint32_t size); +uint32_t ring_buffer_put(struct ring_buffer *ring_buf, void *buffer, uint32_t size); +uint32_t ring_buffer_init(struct ring_buffer* ring_buf, uint32_t size); +void ring_buffer_deinit(struct ring_buffer *ring_buf); +uint32_t ring_buffer_scrolling_display(struct ring_buffer *ring_buf, char show); +uint32_t ring_buffer_len(const struct ring_buffer *ring_buf); + + +#endif diff --git a/drivers/net/wireless/eswin/usb/core.c b/drivers/net/wireless/eswin/usb/core.c new file mode 100644 index 000000000000..a778105df720 --- /dev/null +++ b/drivers/net/wireless/eswin/usb/core.c @@ -0,0 +1,212 @@ +#include <linux/firmware.h>
+#include "core.h"
+#include "fw.h"
+//#include "debug.h"
+#include "ecrnx_platform.h"
+#include "usb.h"
+#include "ecrnx_rx.h"
+#include "eswin_utils.h"
+#include "ecrnx_defs.h"
+#include "usb_host_interface.h"
+
+bool loopback;
+module_param(loopback, bool, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(loopback, "HIF loopback");
+
+int power_save;
+module_param(power_save, int, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(power_save, "Power Save(0: disable, 1:enable)");
+
+int disable_cqm = 0;
+module_param(disable_cqm, int, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(disable_cqm, "Disable CQM (0: disable, 1:enable)");
+
+
+int listen_interval = 0;
+module_param(listen_interval, int, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(listen_interval, "Listen Interval");
+
+int bss_max_idle = 0;
+module_param(bss_max_idle, int, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(bss_max_idle, "BSS Max Idle");
+
+
+bool dl_fw = true;
+module_param(dl_fw, bool, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(dl_fw, "download firmware");
+
+
+#ifdef CONFIG_ECRNX_WIFO_CAIL
+bool amt_mode;
+module_param(amt_mode, bool, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(amt_mode, "calibrate mode");
+#endif
+
+bool set_gain;
+module_param(set_gain, bool, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(set_gain, "set gain delta");
+
+char *fw_name = NULL;
+struct eswin *pEswin = NULL;
+bool usb_status = false;
+
+module_param(fw_name, charp, S_IRUGO);
+MODULE_PARM_DESC(fw_name, "Firmware file name");
+
+extern int ecrnx_data_cfm_callback(void *priv, void *host_id);
+extern int ecrnx_msg_cfm_callback(void *priv, void *host_id);
+static void eswin_core_register_work(struct work_struct *work)
+{
+ int ret;
+ struct eswin *tr = container_of(work, struct eswin, register_work.work);
+
+ ECRNX_DBG("%s entry, dl_fw = %d!!", __func__, dl_fw);
+
+ if(dl_fw){
+ if (fw_name) {
+ ECRNX_PRINT("fw file name: %s\n",fw_name);
+ }
+ else {
+ fw_name = "ECR6600U_transport.bin";
+ }
+ }
+
+ if (dl_fw && eswin_fw_file_chech(tr)) {
+ ECRNX_DBG("%s entry, start fw download!!", __func__);
+ if( eswin_fw_file_download(tr) < 0)
+ {
+ release_firmware(tr->fw);
+ return;
+ }
+ release_firmware(tr->fw);
+ dl_fw = false;
+ ECRNX_DBG("%s entry, finish and stop fw download!!", __func__);
+ schedule_delayed_work(&tr->register_work, msecs_to_jiffies(1000));
+ return;
+ }
+
+#ifdef CONFIG_ECRNX_WIFO_CAIL
+ ECRNX_DBG("%s entry, amt_mode = %d!!", __func__, amt_mode);
+#endif
+
+ tr->rx_callback = ecrnx_rx_callback;
+ tr->data_cfm_callback = ecrnx_data_cfm_callback;
+ tr->msg_cfm_callback = ecrnx_msg_cfm_callback;
+ tr->ops->start(tr);
+ ret = ecrnx_platform_init(tr, &tr->umac_priv);
+ set_bit(ESWIN_FLAG_CORE_REGISTERED, &tr->dev_flags);
+
+ ECRNX_DBG("%s exit!!", __func__);
+
+ return;
+}
+bool register_status = false;
+
+int eswin_core_register(struct eswin *tr)
+{
+ if(register_status == true)
+ {
+ return 0;
+ }
+ register_status = true;
+ ECRNX_DBG("%s entry!!", __func__);
+ schedule_delayed_work(&tr->register_work, msecs_to_jiffies(1));
+ return 0;
+}
+
+void eswin_core_unregister(struct eswin *tr)
+{
+ ECRNX_DBG("%s entry!!", __func__);
+ if(register_status == false)
+ {
+ return;
+ }
+ register_status = false;
+ msleep(20);
+ cancel_delayed_work_sync(&tr->register_work);
+
+ if (!test_bit(ESWIN_FLAG_CORE_REGISTERED, &tr->dev_flags))
+ return;
+ tr->rx_callback = NULL;
+ ecrnx_platform_deinit(tr->umac_priv);
+ eswin_core_destroy(tr);
+}
+
+int usb_host_send(void *buff, int len, int flag)
+{
+ int ret = -1;
+ struct eswin * tr= pEswin;
+ struct sk_buff *skb = NULL;
+
+ //ECRNX_DBG("%s-%d: flag:0x%08x, mask:0x%x, desc:0x%x, type:0x%x \n", __func__, __LINE__, flag, FLAG_MSG_TYPE_MASK, TX_FLAG_TX_DESC, (flag & FLAG_MSG_TYPE_MASK));
+ if((u8_l)(flag & FLAG_MSG_TYPE_MASK) == TX_FLAG_TX_DESC)
+ {
+ skb = (struct sk_buff*)buff;
+ }
+ else
+ {
+ skb = dev_alloc_skb(len + sizeof(int)); //add the flag length (len + 4)
+
+ memcpy(skb->data, (char*)&flag, sizeof(int));
+ memcpy((char*)skb->data + sizeof(int), buff, len); //put rx desc tag to skb
+ skb->len = len + sizeof(int);
+ }
+
+ ECRNX_DBG("usb_host_send, skb:0x%08x, skb_len:%d, frame_len:%d, flag:0x%08x \n", skb, skb->len, len, flag);
+
+ if (tr->ops->xmit) {
+ ret = tr->ops->xmit(tr, skb);
+ } else {
+ ECRNX_ERR("eswin_sdio_work error, ops->xmit is null\n");
+ }
+
+ return ret;
+}
+
+extern void ecrnx_send_handle_register(void * fn);
+struct eswin * eswin_core_create(size_t priv_size, struct device *dev, struct usb_ops * ops)
+{
+ struct eswin * tr;
+
+ tr = (struct eswin *)kzalloc(sizeof(struct eswin) + priv_size, GFP_KERNEL);
+ if(!tr) {
+ return NULL;
+ }
+
+ pEswin = tr;
+
+ tr->dev = dev;
+ tr->ops = ops;
+
+ ecrnx_send_handle_register(usb_host_send);
+
+ if(usb_status == false)
+ {
+ INIT_DELAYED_WORK(&tr->register_work, eswin_core_register_work);
+ usb_status = true;
+ }
+
+ tr->state = ESWIN_STATE_INIT;
+
+ ECRNX_DBG(" %s exit!!", __func__);
+ return tr;
+
+}
+
+void eswin_core_destroy(struct eswin *tr)
+{
+ tr->state = ESWIN_STATE_CLOSEED;
+
+ ECRNX_DBG("%s entry!!", __func__);
+ usb_status = false;
+
+ //flush_workqueue(tr->workqueue);
+ //destroy_workqueue(tr->workqueue);
+ //TODO:
+ //eswin_mac_destroy(tr);
+}
+
+
+//MODULE_AUTHOR("Transa-Semi");
+//MODULE_LICENSE("Dual BSD/GPL");
+//MODULE_DESCRIPTION("Core module for Transa-Semi 802.11 WLAN SDIO driver");
diff --git a/drivers/net/wireless/eswin/usb/core.h b/drivers/net/wireless/eswin/usb/core.h new file mode 100644 index 000000000000..e99074fdf0f4 --- /dev/null +++ b/drivers/net/wireless/eswin/usb/core.h @@ -0,0 +1,297 @@ +#ifndef _CORE_H_ +#define _CORE_H_ + + +#include <linux/completion.h> +#include <linux/if_ether.h> + +#include <net/mac80211.h> +#include "ecrnx_compat.h" + + +#define ESWIN_FLAG_CORE_REGISTERED 1 + +#define ESWIN_NR_VIF 2 +#define ESWIN_NR_VIF_HW_QUEUE 4 + + +#define ESWIN_STATE_BOOT 1 +#define ESWIN_STATE_INIT 2 +#define ESWIN_STATE_STOP 3 +#define ESWIN_STATE_CLOSING 4 +#define ESWIN_STATE_CLOSEED 5 +#define ESWIN_STATE_START 6 +#define ESWIN_STATE_RUNNING 7 + + +#define WIM_RESP_TIMEOUT (msecs_to_jiffies(100)) + +enum ESWIN_SCAN_MODE { + ESWIN_SCAN_MODE_IDLE = 0, + ESWIN_SCAN_MODE_SCANNING, + ESWIN_SCAN_MODE_ABORTING, +}; + + + +struct fwinfo_t { + uint32_t ready; + uint32_t version; + uint32_t tx_head_size; + uint32_t rx_head_size; + uint32_t payload_align; + uint32_t buffer_size; +}; + +struct eswin_capabilities { + uint64_t cap_mask; + uint16_t listen_interval; + uint16_t bss_max_idle; + uint8_t bss_max_idle_options; +}; + +struct eswin_max_idle { + bool enable; + u16 period; + u16 scale_factor; + u8 options; + struct timer_list keep_alive_timer; + + unsigned long idle_period; /* jiffies */ + struct timer_list timer; +}; + + +/* Private txq driver data structure */ +struct eswin_txq { + u16 hw_queue; /* 0: AC_BK, 1: AC_BE, 2: AC_VI, 3: AC_VO */ + struct list_head list; + struct sk_buff_head queue; /* own queue */ + unsigned long nr_fw_queueud; + unsigned long nr_push_allowed; + struct ieee80211_vif vif; + struct ieee80211_sta sta; +}; + +struct tx_buff_node { + struct tx_buff_node * next; + void * buff; + int len; + int flag; +}; +struct tx_buff_queue { + struct tx_buff_node * head; + struct tx_buff_node * tail; + int count; + spinlock_t lock; +}; + +#define ESWIN_QUEUE_MAX (ESWIN_NR_VIF_HW_QUEUE*ESWIN_NR_VIF + 3) + +typedef int (*usb_rx_cb_t)(void *priv, struct sk_buff *skb, unsigned char ep); +typedef int (*usb_data_cfm_cb_t)(void *priv, void *host_id); +struct eswin { + + void *umac_priv; //mac drv data. + struct ieee80211_hw *hw; + struct ieee80211_vif *vif[ESWIN_NR_VIF]; + struct device *dev; + int nr_active_vif; + int state; + bool promisc; + + bool loopback; + bool ampdu_supported; + int lb_count; + bool amsdu_supported; + bool block_frame; + bool ampdu_reject; + + char alpha2[2]; + u64 tsf_offset; + + struct usb_ops *ops; + usb_rx_cb_t rx_callback; + usb_data_cfm_cb_t data_cfm_callback; + usb_data_cfm_cb_t msg_cfm_callback; + + //struct sx_buff_queue queue[ESWIN_NR_VIF]; /* 0: frame, 1: wim */ + struct tx_buff_queue tx_queue; + struct tx_buff_node tx_node[64]; + struct tx_buff_node * tx_node_head; + spinlock_t tx_lock; + int tx_node_num; + struct work_struct work; + + struct eswin_txq ntxq[ESWIN_QUEUE_MAX]; + + + struct mutex state_mtx; + enum ESWIN_SCAN_MODE scan_mode; + struct workqueue_struct *workqueue; + struct delayed_work scan_timeout; + + //struct work_struct register_work; + struct delayed_work register_work; + + + unsigned long dev_flags; + struct mac_address mac_addr[ESWIN_NR_VIF]; + struct ieee80211_supported_band bands[NUM_NL80211_BANDS]; + + + + /* Move to vif or sta driver data */ + u8 frame_seqno; + u8 wim_seqno; + u8 band; + u16 center_freq; + u16 aid; + u32 cipher_pairwise; + u32 cipher_group; + + struct fwinfo_t fwinfo; + struct eswin_capabilities cap; + + /* power management */ + enum ps_mode { + PS_DISABLED, + PS_ENABLED, + PS_AUTO_POLL, + PS_MANUAL_POLL + } ps; + bool ps_poll_pending; + bool ps_enabled; + + + + /* tx */ + spinlock_t txq_lock; + struct list_head txq; + /* 0: AC_BK, 1: AC_BE, 2: AC_VI, 3: AC_VO */ + atomic_t tx_credit[IEEE80211_NUM_ACS*3]; + atomic_t tx_pend[IEEE80211_NUM_ACS*3]; + +// struct completion wim_responded; +// struct sk_buff *last_wim_responded; + + + struct delayed_work roc_finish; + + struct firmware *fw; + + struct dentry *debugfs; + + /* must be last */ + u8 drv_priv[0] __aligned(sizeof(void *)); +}; + + +/* vif driver data structure */ +struct eswin_vif { + struct eswin *tr; + int index; + struct net_device *dev; + + /* scan */ + struct delayed_work scan_timeout; + + /* power save */ + bool ps_polling; + + /* MLME */ + spinlock_t preassoc_sta_lock; + struct list_head preassoc_sta_list; + + /* inactivity */ + u16 max_idle_period; +}; + +#define to_ieee80211_vif(v) \ + container_of((void *)v, struct ieee80211_vif, drv_priv) + +#define to_i_vif(v) ((struct eswin_vif *) (v)->drv_priv) + +static inline int hw_vifindex(struct ieee80211_vif *vif) +{ + struct eswin_vif *i_vif; + + if (vif == NULL) + return 0; + + i_vif = to_i_vif(vif); + return i_vif->index; +} + +/* sta driver data structure */ +struct eswin_sta { + struct eswin *tr; + struct ieee80211_vif *vif; + /*struct ieee80211_sta *sta;*/ + + enum ieee80211_sta_state state; + struct list_head list; + + /* keys */ + struct ieee80211_key_conf *ptk; + struct ieee80211_key_conf *gtk; + + /* BSS max idle period */ + struct eswin_capabilities cap; + struct eswin_max_idle max_idle; +}; + +#define to_ieee80211_sta(s) \ + container_of((void *)s, struct ieee80211_sta, drv_priv) + +#define to_i_sta(s) ((struct eswin_sta *) (s)->drv_priv) + + + +struct eswin_sta_handler { + int (*sta_state)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + enum ieee80211_sta_state old_state, + enum ieee80211_sta_state new_state); + +}; + +#if 0 +#define STAH(fn) \ + static struct eswin_sta_handler __stah_ ## fn \ + __attribute((__used__)) \ + __attribute((__section__("nrc.sta"))) = { \ + .sta_state = fn, \ + } + +extern struct eswin_sta_handler __sta_h_start, __sta_h_end; +#endif + + +/* trx */ + +struct eswin_trx_data { + struct eswin *tr; + struct ieee80211_vif *vif; + struct ieee80211_sta *sta; + struct sk_buff *skb; + int result; +}; + + +#define NL80211_IFTYPE_ALL (BIT(NUM_NL80211_IFTYPES)-1) +int eswin_core_register(struct eswin *tr); +void eswin_core_unregister(struct eswin *tr); +struct eswin * eswin_core_create(size_t priv_size, struct device *dev, struct usb_ops * ops); +void eswin_core_destroy(struct eswin *tr); + +extern int power_save; +extern int disable_cqm; +#ifdef CONFIG_ECRNX_WIFO_CAIL +extern bool amt_mode; +#endif +extern bool set_gain; +extern bool dl_fw; +extern bool register_status; + +#endif diff --git a/drivers/net/wireless/eswin/usb/debug.c b/drivers/net/wireless/eswin/usb/debug.c new file mode 100644 index 000000000000..f5a3469d1d6a --- /dev/null +++ b/drivers/net/wireless/eswin/usb/debug.c @@ -0,0 +1,187 @@ +#include <linux/timer.h> +#include "ecrnx_utils.h" +#include "ecrnx_defs.h" +#include "ecrnx_msg_rx.h" +#include "ecrnx_usb.h" +#include "core.h" +#include "debug.h" +#include "usb.h" +#include "ecrnx_rx.h" + + +//kernal timer param +static struct timer_list tm; +static int counter = 0; + +//rx param +struct sk_buff *skb = NULL; +struct ecrnx_hw *g_ecrnx_hw = NULL; + +//tx param +struct ecrnx_vif vif; +struct ecrnx_sta sta; +struct cfg80211_mgmt_tx_params params; + +static void test_timer_handler(struct timer_list * lt); + +void ecrnx_hw_set(void* init_ecrnx_hw) +{ + g_ecrnx_hw = (struct ecrnx_hw *)init_ecrnx_hw; +} + +static int sdio_rx_param_init(void) +{ + struct rxu_stat_mm rxu_state; + struct rx_hd rx_head; + struct ethhdr eth_hd; + int res, index = 0; + uint8_t *ptr = NULL; + uint16_t head_len = sizeof(struct ethhdr); + ECRNX_DBG("%s entry!!", __func__); + memset(&rxu_state, 0, sizeof(struct rxu_stat_mm)); + memset(&rx_head, 0, sizeof(struct rx_hd)); + memset(ð_hd, 0, sizeof(struct ethhdr)); + + rxu_state.comm_hd.frm_type = USB_FRM_TYPE_RXDESC; + //rxu_state.comm_hd.frm_type = USB_FRM_TYPE_MSG; + if(rxu_state.comm_hd.frm_type == USB_FRM_TYPE_RXDESC) + { + head_len += sizeof(struct rxu_stat_mm) + sizeof(struct rx_hd); + } + else + { + head_len += sizeof(dispatch_hdr_t); + } + + skb = dev_alloc_skb(FRAME_SIZE + head_len); + skb_reserve(skb, head_len); + ptr = skb_put(skb, FRAME_SIZE); //ptr is skb tail + memset(skb->data, 0x0f, FRAME_SIZE); //payload + skb_push(skb, sizeof(struct ethhdr)); + + for( index = 0; index < ETH_ALEN; index++) + { + eth_hd.h_dest[index] = index; + eth_hd.h_source[index] = index; + } + + eth_hd.h_proto = ETH_P_80221; //ETHERTYPE_IP; + memcpy(skb->data, ð_hd, sizeof(struct ethhdr)); + + if(rxu_state.comm_hd.frm_type == USB_FRM_TYPE_RXDESC) + { + //data frame, need header, rxu state + //rx head + skb_push(skb, sizeof(struct rx_hd)); + rx_head.frmlen = FRAME_SIZE + head_len; + rx_head.ampdu_stat_info = 0; + //... + memcpy(skb->data , &rx_head, sizeof(struct rx_hd)); + + //rxu state + skb_push(skb, sizeof(struct rxu_stat_mm)); + rxu_state.msdu_mode = 0x01; + rxu_state.host_id = 0x0001; + rxu_state.frame_len = rx_head.frmlen; + rxu_state.status = RX_STAT_MONITOR; + //rxu_state.phy_info.info1 = 1 | (1 << 8) | (2450 << 16); + //rxu_state.phy_info.info2 = 2450 | (2450 << 16); +#ifdef CONFIG_ECRNX_FULLMAC + //rxu_state.flags = 0; +#endif + //rxu_state.pattern = ecrnx_rxbuff_pattern; + memcpy(skb->data, &rxu_state, sizeof(struct rxu_stat_mm)); + } + else + { + //message frame, don't need header + skb_push(skb, sizeof(dispatch_hdr_t)); //rxu state + memcpy(skb->data, &rxu_state.comm_hd, sizeof(dispatch_hdr_t)); + } + + for(index = 0; index < head_len; index++) + { + ECRNX_DBG("0x%x ", skb->data[index]); + } + + ECRNX_DBG("%s exit, skb_len:%d, type: %d!!", __func__, skb->len, (skb->data[1] << 8) | skb->data[0]); + + return res; +} + +extern int ecrnx_start_mgmt_xmit(struct ecrnx_vif *vif, \ + struct ecrnx_sta *sta, \ + struct cfg80211_mgmt_tx_params *params, \ + bool offchan, \ + u64 *cookie); + +/* +struct cfg80211_mgmt_tx_params { + struct ieee80211_channel *chan; + bool offchan; + unsigned int wait; + const u8 *buf; + size_t len; + bool no_cck; + bool dont_wait_for_ack; + int n_csa_offsets; + const u16 *csa_offsets; +}; +*/ +static void sdio_tx_param_init(void) +{ + u8 send_buf[FRAME_SIZE] = {0x00, 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09}; + + params.len = FRAME_SIZE; + params.buf = (const u8 *)send_buf; + params.n_csa_offsets = 10; + params.csa_offsets = (const u16 *)send_buf; + params.no_cck = 0; + + vif.ecrnx_hw = g_ecrnx_hw; +} + +void sdio_rx_tx_test_schedule(void) +{ + ECRNX_DBG("%s entry!!", __func__); + tm.function = test_timer_handler; + tm.expires = jiffies + HZ * 10; + add_timer(&tm); + + sdio_rx_param_init(); + sdio_tx_param_init(); + ECRNX_DBG("%s exit!!", __func__); +} + +static void test_timer_handler(struct timer_list * lt) +{ + ECRNX_DBG("%s, counter:%d\n", __FUNCTION__, counter); + + if(counter%2) + { + u64 cookie; + //ecrnx_start_mgmt_xmit(&vif, NULL, ¶ms, false, &cookie); + } + else + { + ecrnx_rx_callback(g_ecrnx_hw, skb, 1); + } + + if(lt) + { + counter++; + } + + if(counter < 5) + { + //tm.expires = jiffies +1 * HZ / 1000/10; //100us + tm.expires = jiffies +1 * HZ; + add_timer(&tm); + } + else + { + counter = 0; + del_timer(&tm); + } +} + diff --git a/drivers/net/wireless/eswin/usb/debug.h b/drivers/net/wireless/eswin/usb/debug.h new file mode 100644 index 000000000000..1f5025842d58 --- /dev/null +++ b/drivers/net/wireless/eswin/usb/debug.h @@ -0,0 +1,21 @@ + + +#ifndef _ECRNX_DBG +#define _ECRNX_DBG +#include <net/mac80211.h> + +//#define CONFIG_HIF_PRINT_TX_DATA +#define CONFIG_USE_TXQ + +//#define CONFIG_NRC_HIF_PRINT_FLOW_CONTROL +//#define CONFIG_SHOW_TX_SPEED +//#define CONFIG_SHOW_RX_SPEED + +#define FRAME_SIZE 512 + +void eswin_dump_wim(struct sk_buff *skb); +void eswin_init_debugfs(struct eswin *tr); +void sdio_rx_tx_test_schedule(void); +void ecrnx_hw_set(void* init_ecrnx_hw); + +#endif diff --git a/drivers/net/wireless/eswin/usb/ecrnx_usb.c b/drivers/net/wireless/eswin/usb/ecrnx_usb.c new file mode 100644 index 000000000000..4bfe655e5505 --- /dev/null +++ b/drivers/net/wireless/eswin/usb/ecrnx_usb.c @@ -0,0 +1,447 @@ +/** + ****************************************************************************** + * + * @file ecrnx_usb.c + * + * @brief ECRNX usb init and management function + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#include <linux/module.h> +#include <linux/kthread.h> + +#include "core.h" +//#include "debug.h" +#include "ecrnx_utils.h" +#include "ecrnx_cmds.h" +#include "ecrnx_defs.h" +#include "ecrnx_msg_rx.h" +#include "ipc_host.h" +#include "ipc_shared.h" +#include "ecrnx_events.h" +#include "ecrnx_usb.h" +#ifdef CONFIG_TEST_ESWIN_USB +#include "debug.h" +#endif +#ifdef CONFIG_ECRNX_WIFO_CAIL +#include "ecrnx_amt.h" +#endif + + +#define USB_ADDR_DATA (unsigned int)(0x200) + +#ifdef CONFIG_ECRNX_SOFTMAC +#define FW_STR "lmac" +#elif defined CONFIG_ECRNX_FULLMAC +#define FW_STR "fmac" +#endif + +#if defined(CONFIG_ECRNX_DEBUGFS_CUSTOM) +#include "ecrnx_debugfs_func.h" +#endif + +extern const int nx_txdesc_cnt_msk[]; + +struct vendor_radiotap_hdr { + u8 oui[3]; + u8 subns; + u16 len; + u8 data[]; +}; + +#ifdef CONFIG_WEXT_PRIV +extern void priv_copy_data_wakeup(struct ecrnx_hw *ecrnx_hw, struct sk_buff *skb); +#endif + +/** + * @brief: ipc_host_rxdesc_handler: Handle the reception of a Rx Descriptor + * Called from general IRQ handler when status %IPC_IRQ_E2A_RXDESC is set + * @param {env} pointer to the usb Host environment + * @param {skb} received skb data + * @return: none + */ +static int usb_host_rxdesc_handler(struct ipc_host_env_tag *env, struct sk_buff *skb) +{ + u8 ret = 0; + // LMAC has triggered an IT saying that a reception has occurred. + // Then we first need to check the validity of the current hostbuf, and the validity + // of the next hostbufs too, because it is likely that several hostbufs have been + // filled within the time needed for this irq handling +#ifdef CONFIG_ECRNX_FULLMAC + // call the external function to indicate that a RX descriptor is received + ret = env->cb.recv_data_ind(env->pthis, skb); +#else + // call the external function to indicate that a RX packet is received + ret = env->cb.recv_data_ind(env->pthis, skb); +#endif //(CONFIG_ECRNX_FULLMAC) + //ECRNX_DBG("%s exit!!", __func__); + return ret; +} + +/** + * @brief: usb_host_radar_handler Handle the reception of radar events + * @param {env} pointer to the usb Host environment + * @param {skb} received skb data + * @return: none + */ +static int usb_host_radar_handler(struct ipc_host_env_tag *env, struct sk_buff *skb) +{ + int ret = 0; + +#ifdef CONFIG_ECRNX_RADAR + // LMAC has triggered an IT saying that a radar event has been sent to upper layer. + // Then we first need to check the validity of the current msg buf, and the validity + // of the next buffers too, because it is likely that several buffers have been + // filled within the time needed for this irq handling + // call the external function to indicate that a RX packet is received + spin_lock(&((struct ecrnx_hw *)env->pthis)->radar.lock); + ret = env->cb.recv_radar_ind(env->pthis, skb); + spin_unlock(&((struct ecrnx_hw *)env->pthis)->radar.lock); +#endif /* CONFIG_ECRNX_RADAR */ + return ret; +} + +/** + * @brief: usb_host_unsup_rx_vec_handler Handle the reception of unsupported rx vector + * @param {env} pointer to the usb Host environment + * @param {skb} received skb data + * @return: none + */ +static int usb_host_unsup_rx_vec_handler(struct ipc_host_env_tag *env, struct sk_buff *skb) +{ + return env->cb.recv_unsup_rx_vec_ind(env->pthis, skb); +} + +/** + * @brief: usb_host_msg_handler Handler for firmware message + * @param {env} pointer to the usb Host environment + * @param {skb} received skb data + * @return: none + */ +static int usb_host_msg_handler(struct ipc_host_env_tag *env, struct sk_buff *skb) +{ + // LMAC has triggered an IT saying that a message has been sent to upper layer. + // Then we first need to check the validity of the current msg buf, and the validity + // of the next buffers too, because it is likely that several buffers have been + // filled within the time needed for this irq handling + // call the external function to indicate that a RX packet is received + return env->cb.recv_msg_ind(env->pthis, skb->data); +} + +/** + * @brief: usb_host_msgack_handler Handle the reception of message acknowledgement + * @param {env} pointer to the usb Host environment + * @param {skb} received skb data + * @return: none + */ +static int usb_host_msgack_handler(struct ipc_host_env_tag *env, struct sk_buff *skb) +{ + ptr_addr hostid = *(ptr_addr *)skb->data; + + ASSERT_ERR(hostid); + + env->msga2e_hostid = NULL; + env->cb.recv_msgack_ind(env->pthis, (void*)hostid); + + return 0; +} + +/** + * @brief: usb_host_dbg_handler Handle the reception of Debug event + * @param {env} pointer to the usb Host environment + * @param {skb} received skb data + * @return: none + */ +static int usb_host_dbg_handler(struct ipc_host_env_tag *env, struct sk_buff *skb) +{ + // LMAC has triggered an IT saying that a DBG message has been sent to upper layer. + // Then we first need to check the validity of the current buffer, and the validity + // of the next buffers too, because it is likely that several buffers have been + // filled within the time needed for this irq handling + // call the external function to indicate that a RX packet is received + return env->cb.recv_dbg_ind(env->pthis, skb); +} + +/** + * @brief: usb_host_tx_cfm_handler Handle the reception of TX confirmation + * @param {env} pointer to the usb Host environment + * @param {queue_idx} index of the hardware on which the confirmation has been received + * @param {user_pos} index of the user position + * @return: none + */ +static int usb_host_tx_cfm_handler(struct ipc_host_env_tag *env, struct sk_buff *skb) +{ + ptr_addr host_id; + struct sk_buff *skb_cfm; + struct ecrnx_txhdr *txhdr; + +#ifdef CONFIG_ECRNX_FULLMAC + struct tx_cfm_tag *cfm; + + cfm = (struct tx_cfm_tag *)skb->data; + //host_id = cfm->hostid; + memcpy((uint8_t *)&host_id, (uint8_t *)cfm->hostid, sizeof(ptr_addr)); + if (host_id == 0) { + return 0; + } + + ECRNX_DBG("%s:hostid(tx_skb):0x%08x, rx_skb: 0x%x \n", __func__, host_id, skb); + skb_cfm = (struct sk_buff *)host_id; + txhdr = (struct ecrnx_txhdr *)(*((ptr_addr*)skb_cfm->data - 1)); + memcpy(&txhdr->hw_hdr.cfm, cfm, sizeof(*cfm)); +#elif defined CONFIG_ECRNX_SOFTMAC + //TODO: +#endif + + return env->cb.send_data_cfm(env->pthis, (void*)txhdr); +} + +#ifdef CONFIG_ECRNX_WIFO_CAIL +/** + * @brief: usb_host_amt_rx_handler Handle the reception of rx frame + * @param {frm_type} received frame type + * @param {skb} received skb data + * @return: none + */ +void usb_host_amt_rx_handler(uint32_t frm_type, struct sk_buff *skb) +{ + int need_free = 0; + + ECRNX_DBG("%s enter, frame type: %d!!", __func__, frm_type); + if(!skb) + { + ECRNX_ERR("usb_host_amt_rx_handler input param error!! \n!"); + return; + } + + if (frm_type != USB_FRM_TYPE_RXDESC) + { + skb_pull(skb, SKB_DATA_COM_HD_OFFSET); //delete the frame common header + } + + ECRNX_DBG("skb:0x%08x, skb_len:%d, frame type: %d!!", skb->data,skb->len, frm_type); + + switch (frm_type) + { + case USB_FRM_TYPE_IWPRIV: + { + /*printk("vif_start:%d, vif_monitor:%d \n", ecrnx_hw->vif_started, ecrnx_hw->monitor_vif); + print_hex_dump(KERN_INFO, "iwpriv-cfm:", DUMP_PREFIX_ADDRESS, 32, 1, + skb->data, skb->len, false);*/ + amt_vif.rxlen = skb->len; + memset(amt_vif.rxdata, 0, ECRNX_RXSIZE); + memcpy(amt_vif.rxdata, skb->data, skb->len); + amt_vif.rxdatas = 1; + wake_up(&amt_vif.rxdataq); + need_free = 1; + break; + } + default: + need_free = 1; + break; + } + + if (need_free && skb) { // free the skb + ECRNX_DBG("skb free: 0x%x !! \n", skb); + dev_kfree_skb(skb); + } + ECRNX_DBG("%s exit!!", __func__); + return; +} +#endif + + +/** + * @brief: usb_host_rx_handler Handle the reception of rx frame + * @param {frm_type} received frame type + * @param {env} pointer to the usb Host environment + * @param {skb} received skb data + * @return: none + */ +void usb_host_rx_handler(uint32_t frm_type, struct ipc_host_env_tag *env, struct sk_buff *skb) +{ + int ret = 1; + + //ECRNX_DBG("%s enter, frame type: %d!!", __func__, frm_type); + if(!env || !skb) + { + ECRNX_ERR("usb_host_rx_handler input param error!! \n!"); + return; + } + + ((struct ecrnx_hw *)env->pthis)->usb_rx++; + + if (frm_type != USB_FRM_TYPE_RXDESC) + { + skb_pull(skb, SKB_DATA_COM_HD_OFFSET); //delete the frame common header + } + + //ECRNX_DBG("skb:0x%08x, skb_len:%d, frame type: %d!!", skb->data,skb->len, frm_type); + + switch (frm_type) + { + case USB_FRM_TYPE_RXDESC: + { + // handle the RX descriptor reception + usb_host_rxdesc_handler(env, skb); //just for current only one endpoint test + break; + } + + case USB_FRM_TYPE_MSG_ACK: + { + if(1 == skb->len) + { + ECRNX_PRINT("MSG_ACK len: 1"); + break; + } + ret = usb_host_msgack_handler(env, skb); + break; + } + + case USB_FRM_TYPE_MSG: + { + ret = usb_host_msg_handler(env, skb); + break; + } + + case USB_FRM_TYPE_TXCFM: + { + if(!env->pthis) + { + ECRNX_ERR("env->pthis ptr error!! \n!"); + break; + } + + /* add the spinlock which was missed during porting. + when skb->len more than 24 and skb contans more than one data cfm. + data cfm structure length is 24 byte. + */ + spin_lock_bh(&((struct ecrnx_hw *)env->pthis)->tx_lock); + while(skb->len > sizeof(struct tx_cfm_tag)) + { + ret = usb_host_tx_cfm_handler(env, skb); + skb_pull(skb, sizeof(struct tx_cfm_tag)); + } + ret = usb_host_tx_cfm_handler(env, skb); + spin_unlock_bh(&((struct ecrnx_hw *)env->pthis)->tx_lock); + break; + } + + case USB_FRM_TYPE_UNSUP_RX_VEC: + { + // handle the unsupported rx vector reception + ret = usb_host_unsup_rx_vec_handler(env, skb); + break; + } + + case USB_FRM_TYPE_RADAR: + { + // handle the radar event reception + ret = usb_host_radar_handler(env, skb); + break; + } + + case USB_FRM_TYPE_TBTT_SEC: + { + env->cb.sec_tbtt_ind(env->pthis); + break; + } + + case USB_FRM_TYPE_TBTT_PRIM: + { + env->cb.prim_tbtt_ind(env->pthis); + break; + } + + case USB_FRM_TYPE_DBG: + { + ECRNX_DBG("--%s:USB_FRM_TYPE_DBG, len:%d, slave:%s \n", __func__, skb->len, skb->data); + ret = usb_host_dbg_handler(env, skb); + break; + } + case USB_FRM_TYPE_IWPRIV: + { +#ifdef CONFIG_WEXT_PRIV +#if 0 + struct ecrnx_hw *ecrnx_hw = (struct ecrnx_hw *)env->pthis; + struct ecrnx_vif* ecrnx_vif = ecrnx_hw->vif_table[0]; + + printk("vif_start:%d, vif_monitor:%d \n", ecrnx_hw->vif_started, ecrnx_hw->monitor_vif); + print_hex_dump(KERN_INFO, "iwpriv-cfm:", DUMP_PREFIX_ADDRESS, 32, 1, + skb->data, skb->len, false); + ecrnx_vif->rxlen = skb->len; + memcpy(ecrnx_vif->rxdata, skb->data, skb->len); + ecrnx_vif->rxdatas = 1; + wake_up(&ecrnx_vif->rxdataq); +#else + priv_copy_data_wakeup((struct ecrnx_hw *)env->pthis, skb); +#endif + +#endif + ret = 0; + break; + } + +#if defined(CONFIG_ECRNX_DEBUGFS_CUSTOM) + case USB_FRM_DEBUG_FS: + { + uint32_t debugfs_type = ((uint32_t*)skb->data)[0]; + debugfs_resp.debugfs_type = debugfs_type; + + + if((debugfs_type != SLAVE_LOG_LEVEL) && \ + (debugfs_type < SLAVE_DEBUGFS_MAX)){ + + debugfs_resp.rxlen = skb->len-4; + memcpy(debugfs_resp.rxdata, skb->data+4, debugfs_resp.rxlen); + + ECRNX_DBG("%s - wake_up()\n", __func__); + debugfs_resp.rxdatas = 1; + wake_up(&debugfs_resp.rxdataq); + } + + break; + } +#endif + default: + ret = 0; + break; + } + + if (!ret && skb) { // free the skb + ECRNX_DBG("skb free: 0x%x, ret: %d!! \n", skb, ret); + dev_kfree_skb(skb); + } + //ECRNX_DBG("%s exit!!", __func__); + return; +} + +/** + * @brief: ecrnx_usb_init Initialize usb interface. + * @param {ecrnx_hw} Main driver data + * @return: u8 + */ +u8 ecrnx_usb_init(struct ecrnx_hw *ecrnx_hw) +{ + ECRNX_DBG("%s entry!!", __func__); + // Save the pointer to the register base + ecrnx_hw->ipc_env->pthis = (void*)ecrnx_hw; + + ECRNX_DBG("%s exit!!", __func__); + return 0; +} + +/** + * @brief: ecrnx_usb_deinit DeInitialize usb interface. + * @param {ecrnx_hw} Main driver data + * @return: none + */ +void ecrnx_usb_deinit(struct ecrnx_hw *ecrnx_hw) +{ + ECRNX_DBG(ECRNX_FN_ENTRY_STR); + + memset(ecrnx_hw, 0, sizeof(struct ecrnx_hw)); +} diff --git a/drivers/net/wireless/eswin/usb/ecrnx_usb.h b/drivers/net/wireless/eswin/usb/ecrnx_usb.h new file mode 100644 index 000000000000..98178a788360 --- /dev/null +++ b/drivers/net/wireless/eswin/usb/ecrnx_usb.h @@ -0,0 +1,110 @@ +/** +****************************************************************************** +* +* @file ecrnx_usb.h +* +* @brief ecrnx usb header file +* +* Copyright (C) ESWIN 2015-2020 +* +****************************************************************************** +*/ + +#ifndef _ECRNX_USB_H_ +#define _ECRNX_USB_H_ + +#include "ecrnx_rx.h" +#include "eswin_utils.h" + +enum{ + USB_FRM_TYPE_RXDESC =1, + USB_FRM_TYPE_MSG, + USB_FRM_TYPE_DBG, + USB_FRM_TYPE_UNSUP_RX_VEC, + USB_FRM_TYPE_RADAR, + USB_FRM_TYPE_TBTT_SEC, + USB_FRM_TYPE_TBTT_PRIM, + USB_FRM_TYPE_MSG_ACK, + USB_FRM_TYPE_TXCFM, + USB_FRM_TYPE_IWPRIV, + USB_FRM_DEBUG_FS, +}; + + +#define SKB_DATA_COM_HD_OFFSET sizeof(dispatch_hdr_t) + +/// Element in the pool of RX header descriptor. +struct rx_hd +{ + /// Total length of the received MPDU + uint16_t frmlen; + /// AMPDU status information + uint16_t ampdu_stat_info; + /// TSF Low + uint32_t tsflo; + /// TSF High + uint32_t tsfhi; + /// Rx Vector 1 + struct rx_vector_1 rx_vec_1; + /// Rx Vector 2 + struct rx_vector_2 rx_vec_2; + /// MPDU status information + uint32_t statinfo; +}; + +/*adjust phy info,pattern, these infomation should be put to another buffer to trnasfer*/ +struct rxu_stat_mm +{ + /*type of msg/dbg/frm etc.*/ + dispatch_hdr_t frm_type; + uint32_t fragment_flag : 1; + uint32_t is_qos : 1; + uint32_t need_reord : 1; + uint32_t need_pn_check : 1; + uint32_t is_ga : 1; + uint32_t flags_rsvd0 : 3; + uint32_t tid : 8; + uint16_t sn; + /// Length + uint16_t frame_len; + /// Status (@ref rx_status_bits) + uint16_t status; + uint16_t real_offset; /* dma address align 4, real data - real_offset */ +}; + + + +/** + * @brief: ecrnx_usb_init Initialize usb interface. + * @param {ecrnx_hw} Main driver data + * @return: u8 + */ +u8 ecrnx_usb_init(struct ecrnx_hw *ecrnx_hw); + +/** + * @brief: ecrnx_usb_deinit DeInitialize usb interface. + * @param {ecrnx_hw} Main driver data + * @return: none + */ +void ecrnx_usb_deinit(struct ecrnx_hw *ecrnx_hw); + +#ifdef CONFIG_ECRNX_WIFO_CAIL +/** + * @brief: usb_host_rx_handler Handle the reception of rx frame + * @param {frm_type} received frame type + * @param {skb} received skb data + * @return: none + */ +void usb_host_amt_rx_handler(uint32_t frm_type, struct sk_buff *skb); +#endif /* CONFIG_ECRNX_WIFO_CAIL */ + +/** + * @brief: usb_host_rx_handler Handle the reception of rx frame + * @param {frm_type} received frame type + * @param {env} pointer to the usb Host environment + * @param {skb} received skb data + * @return: none + */ +void usb_host_rx_handler(uint32_t frm_type, struct ipc_host_env_tag *env, struct sk_buff *skb); + +#endif /* __ECRNX_USB_H */ diff --git a/drivers/net/wireless/eswin/usb/fw.c b/drivers/net/wireless/eswin/usb/fw.c new file mode 100644 index 000000000000..66254d8f6a64 --- /dev/null +++ b/drivers/net/wireless/eswin/usb/fw.c @@ -0,0 +1,266 @@ +/** +****************************************************************************** +* +* @file fw.c +* +* @brief ecrnx usb firmware download functions +* +* Copyright (C) ESWIN 2015-2020 +* +****************************************************************************** +*/ + +#include <linux/firmware.h> +#include "core.h" +#include "usb.h" +#include "fw_head_check.h" + +extern char *fw_name; + +#define MAX_PACKET_SIZE 512 +#define DATA_ADDR_O 0x20000 + +#define DATA_ADDR_O_1 0x10000 + +struct cfg_sync +{ + char magic; + char type; + short length; + char crc8; +}; + +struct cfg_msg +{ + char magic; + char type; + short length; + unsigned int data_addr; + unsigned int data_len; + char crc8; +}; + +typedef struct _ack_msg +{ + unsigned char magic; + unsigned char type; + unsigned short length; + char data[0]; +} ack_msg; + +typedef enum +{ + STATU_SUCCESS = 0, + STATU_ERR +} ACK_STATUS; + +unsigned char eswin_crc8(unsigned char * buf, unsigned short length) +{ + unsigned char crc = 0, i = 0; + while(length--) + { + crc ^= *buf++; + for(i = 8; i > 0; i--) + { + if(crc & 0x80) + { + crc = (crc << 1) ^ 0x31; + } + else + { + crc <<= 1; + } + } + } + return crc; +} + +char eswin_fw_ack_check(u8 * buff) +{ + ack_msg* msg = (ack_msg*)buff; + if(msg->magic != 0x5a) + { + ECRNX_PRINT("dl-fw ack fail, magic: %d\n", msg->magic); + return -1; + } + if(msg->data[0] != STATU_SUCCESS) + { + ECRNX_PRINT("dl-fw ack fail, status: %d\n", msg->data[0]); + return -1; + } + return 0; +} + +char eswin_fw_file_download(struct eswin *tr) +{ + int ret,i; + unsigned int lengthLeft, lengthSend, offset = HEAD_SIZE, total_send_len = 0; + unsigned char length_str[9]={0}; + const u8 * dataAddr; + u8 * buff; + struct sk_buff *skb; + //char str_msg[16]; + char str_sync[4] = {0x63,0x6e,0x79,0x73}; + unsigned int file_load_addr[3] = {0x20000U, 0x40000U, 0x50000U}; // ilm addr(start addr); dlm addr; iram0 addr + + struct cfg_msg * p_msg; + struct cfg_sync * p_sync; + u8 prev_dl_pst = 0; + + skb = dev_alloc_skb(MAX_PACKET_SIZE + 16); + buff = (u8 *)skb->data; + + p_msg = (struct cfg_msg *)buff; + p_sync = (struct cfg_sync *)buff; + + lengthLeft = tr->fw->size; + dataAddr = tr->fw->data; + + /* 0 download sync*/ + memcpy(buff,str_sync,4); + tr->ops->write(tr,buff,4); + ret = tr->ops->wait_ack(tr,buff,1); + ECRNX_PRINT("dl-fw >> sync, ret: %d\n", ret); + if((buff[0] != '3')||(ret < 0)) + { + ECRNX_PRINT("dl-fw >> download sync fail, ack: %d\n", buff[0]); + dev_kfree_skb(skb); + return -1; + } + /* 1 usb sync */ + p_sync->magic = 0xA5; + p_sync->type = 0x00; + p_sync->length = 0x00; + p_sync->crc8 = eswin_crc8(buff,sizeof(struct cfg_msg)-1); + + tr->ops->write(tr, buff, sizeof(struct cfg_sync)); + ret = tr->ops->wait_ack(tr, buff, 6); + ECRNX_PRINT("dl-fw >> sync, ret: %d\n", ret); + if((eswin_fw_ack_check(buff) < 0)||(ret < 0)) + { + dev_kfree_skb(skb); + ECRNX_PRINT("fimeware download fail! \n"); + return -1; + } + + for(i=0;i<3;i++) + { + memcpy(length_str, dataAddr + offset, 8); + ret = kstrtoint(length_str, 10, (int*)&lengthLeft); + offset+=8; + /* 2 cfg */ + p_msg->magic = 0xA5; + p_msg->type = 0x01; + p_msg->length = 0x08; + p_msg->data_addr = file_load_addr[i]; + p_msg->data_len = lengthLeft; + p_msg->crc8 = 0x00; + + tr->ops->write(tr, buff, sizeof(struct cfg_msg)); + ret = tr->ops->wait_ack(tr, buff, 6); + if((eswin_fw_ack_check(buff) < 0)||(ret < 0)) + { + dev_kfree_skb(skb); + ECRNX_PRINT("fimeware download fail! \n"); + return -1; + } + + buff[0] = 0xA5; + buff[1] = 0x01; + + /* 3 load fw */ + do { + lengthSend = (lengthLeft >= MAX_PACKET_SIZE) ? MAX_PACKET_SIZE : lengthLeft; + total_send_len += lengthSend; + + buff[0] = 0xA5; + buff[1] = 0x01; + buff[2] = lengthSend & 0xFF; + buff[3] = (lengthSend>>8) & 0xFF; + + memcpy(&buff[4], dataAddr + offset, lengthSend); + tr->ops->write(tr, buff, lengthSend + 5); + + if(lengthSend >= MAX_PACKET_SIZE) + { + if(prev_dl_pst < total_send_len/(tr->fw->size/100)) + { + prev_dl_pst = total_send_len/(tr->fw->size/100); + ECRNX_PRINT("firmware downloading %d%% \n", prev_dl_pst); + } + } + else if(i == 2) + { + ECRNX_PRINT("firmware downloading 100%% \n"); + } + + ret = tr->ops->wait_ack(tr, buff, 6); + if((eswin_fw_ack_check(buff) < 0)||(ret < 0)) + { + dev_kfree_skb(skb); + ECRNX_PRINT("fimeware download fail! \n"); + return -1; + } + offset += lengthSend; + lengthLeft -= lengthSend; + } while(lengthLeft); + } + + /* 4 start up */ + buff[0] = 0xA5; + buff[1] = 0x06; + buff[2] = 0x02; + buff[3] = 0x00; + + buff[4] = (char)((file_load_addr[0]) & 0xFF); + buff[5] = (char)(((file_load_addr[0])>>8) & 0xFF); + buff[6] = (char)(((file_load_addr[0])>>16) & 0xFF); + buff[7] = (char)(((file_load_addr[0])>>24) & 0xFF); + + tr->ops->write(tr, skb->data, 9); + tr->ops->wait_ack(tr, buff, 6); + if((eswin_fw_ack_check(buff) < 0)||(ret < 0)) + { + dev_kfree_skb(skb); + ECRNX_PRINT("fimeware download fail! \n"); + return -1; + } + + ECRNX_PRINT("fimeware download successfully! \n"); + + dev_kfree_skb(skb); + return 0; +} + +bool eswin_fw_file_chech(struct eswin *tr) +{ + int status; + + if (fw_name == NULL) + goto err_fw; + + + //if (tr->fw) + // return true; + + ECRNX_PRINT("%s, Checking firmware... (%s)\n", __func__, fw_name); + + status = request_firmware((const struct firmware **)&tr->fw, fw_name, tr->dev); + if (status != 0) { + ECRNX_PRINT("%s, error status = %d\n", __func__, status); + goto err_fw; + } + + ECRNX_PRINT("%s, request fw OK and size is %d\n", __func__, tr->fw->size); + + if(fw_check_head(tr) == false) + { + goto err_fw; + } + return true; + +err_fw: + tr->fw = NULL; + return false; +} + diff --git a/drivers/net/wireless/eswin/usb/fw.h b/drivers/net/wireless/eswin/usb/fw.h new file mode 100644 index 000000000000..ab329e884d1e --- /dev/null +++ b/drivers/net/wireless/eswin/usb/fw.h @@ -0,0 +1,19 @@ +/** +****************************************************************************** +* +* @file fw.h +* +* @brief ecrnx usb firmware download definitions +* +* Copyright (C) ESWIN 2015-2020 +* +****************************************************************************** +*/ + +#ifndef _FW_H_ +#define _FW_H_ + +char eswin_fw_file_download(struct eswin *tr); +bool eswin_fw_file_chech(struct eswin *tr); + +#endif diff --git a/drivers/net/wireless/eswin/usb/usb.c b/drivers/net/wireless/eswin/usb/usb.c new file mode 100644 index 000000000000..ddfda8e338b1 --- /dev/null +++ b/drivers/net/wireless/eswin/usb/usb.c @@ -0,0 +1,1041 @@ +/** + ****************************************************************************** + * + * @file usb.c + * + * @brief usb driver function definitions + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ +#include <linux/version.h> +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/gpio.h> +#include <linux/timer.h> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) +#include <uapi/linux/sched/types.h> +#endif +//#include <linux/usb.h> +#include "usb.h" + +#include "core.h" +//#include "debug.h" +#include "ecrnx_usb.h" + +#include "usb_host_interface.h" +#include "ecrnx_compat.h" +#include "fw_head_check.h" +#include "slave_log_buf.h" + +#define USB_RX_TIMER_TIMEOUT_US (200) +//TCP PKG MAX LEN:1594; UDP PKG MAX LEN:1582 +#define USB_RX_LENGTH_THD (1594) +#define USB_RX_UPLOAD_THD (12) + +static struct eswin_usb * g_usb; +static unsigned int rx_packets =0; +static struct timer_list usb_rx_timer = {0}; + +static void usb_refill_recv_transfer(struct usb_infac_pipe * pipe); + + +#define USB_LOG_MEM_SIZE (512*8)//(512*16) +#define USB_LOG_MAX_SIZE 512 + +struct ring_buffer buf_handle = {0}; + +#if defined(CONFIG_ECRNX_DEBUGFS_CUSTOM) +struct ring_buffer *usb_dbg_buf_get(void) +{ + if(buf_handle.init == false) + { + ring_buffer_init(&buf_handle, USB_LOG_MEM_SIZE); + } + return &buf_handle; +} +#endif + +void usb_dbg_printf(void * data, int len) +{ + if(buf_handle.init == false) + { + ring_buffer_init(&buf_handle, USB_LOG_MEM_SIZE); + } + ring_buffer_put(&buf_handle, data, len); +} + + +static struct usb_urb_context * +usb_alloc_urb_from_pipe(struct usb_infac_pipe *pipe) +{ + struct usb_urb_context *urb_context = NULL; + unsigned long flags; + + spin_lock_irqsave(&g_usb->cs_lock, flags); + if (!list_empty(&pipe->urb_list_head)) { + urb_context = list_first_entry(&pipe->urb_list_head, + struct usb_urb_context, link); + list_del(&urb_context->link); + pipe->urb_cnt--; + } + spin_unlock_irqrestore(&g_usb->cs_lock, flags); + + return urb_context; +} + + + +static void usb_free_urb_to_infac(struct usb_infac_pipe * pipe, + struct usb_urb_context *urb_context) +{ + unsigned long flags; + + spin_lock_irqsave(&g_usb->cs_lock, flags); + pipe->urb_cnt++; + list_add(&urb_context->link, &pipe->urb_list_head); + if(urb_context->urb) + { + usb_unanchor_urb(urb_context->urb); + usb_free_urb(urb_context->urb); + urb_context->urb = NULL; + } + spin_unlock_irqrestore(&g_usb->cs_lock, flags); +} + +static void usb_cleanup_recv_urb(struct usb_urb_context *urb_context) +{ + dev_kfree_skb(urb_context->skb); + urb_context->skb = NULL; + + usb_free_urb_to_infac(urb_context->pipe, urb_context); +} + +static void usb_free_pipe_resources(struct usb_infac_pipe *pipe) +{ + struct usb_urb_context *urb_context; + + for (;;) { + urb_context = usb_alloc_urb_from_pipe(pipe); + + if (!urb_context) + break; + if(urb_context->urb) + { + usb_unanchor_urb(urb_context->urb); + usb_free_urb(urb_context->urb); + urb_context->urb = NULL; + } + kfree(urb_context); + } +} + +static void usb_transmit_complete(struct urb *urb) +{ + struct usb_urb_context *urb_context = urb->context; + struct usb_infac_pipe *pipe = urb_context->pipe; + struct sk_buff *skb; + struct txdesc_api *tx_desc; + u32_l flag = 0; + + //ECRNX_DBG(" %s entry, dir: %d!!", __func__, pipe->dir); + + if (urb->status != 0) { + pipe->err_count++; + if(pipe->err_count%100 == 1) + { + ECRNX_PRINT( "pipe-dir: %d, failed:%d\n", + pipe->dir, urb->status); + } + if((pipe->err_status != urb->status)||(pipe->err_count == 10000)) + { + pipe->err_status = urb->status; + pipe->err_count = 0; + } + } + + skb = urb_context->skb; + urb_context->skb = NULL; + usb_free_urb_to_infac(urb_context->pipe, urb_context); + + flag = *(u32_l *)skb->data; + if((u8_l)(flag & FLAG_MSG_TYPE_MASK) == TX_FLAG_TX_DESC) + { + tx_desc = (struct txdesc_api *)((u32_l *)skb->data + 1); + if (tx_desc->host.flags & TXU_CNTRL_MGMT) + { + return; + } + } + + skb_queue_tail(&pipe->io_comp_queue, skb); +#ifdef CONFIG_ECRNX_KTHREAD + wake_up_interruptible(&g_usb->wait_tx_comp); +#endif +#ifdef CONFIG_ECRNX_WORKQUEUE + schedule_work(&pipe->io_complete_work); +#endif +#ifdef CONFIG_ECRNX_TASKLET + tasklet_schedule(&pipe->tx_tasklet); +#endif +} + + +void usb_rx_timer_handle(struct timer_list *time) +{ + if (rx_packets) + { + rx_packets = 0; + #ifdef CONFIG_ECRNX_KTHREAD + wake_up_interruptible(&g_usb->wait_rx_comp); + #endif + #ifdef CONFIG_ECRNX_WORKQUEUE + schedule_work(&g_usb->infac_data.pipe_rx.io_complete_work); + #endif + #ifdef CONFIG_ECRNX_TASKLET + tasklet_schedule(&g_usb->infac_data.pipe_rx.rx_tasklet); + #endif + } +} + +static void usb_recv_complete(struct urb *urb) +{ + struct usb_urb_context *urb_context = urb->context; + struct usb_infac_pipe *pipe = urb_context->pipe; + struct sk_buff *skb; + int status = 0; + + //ECRNX_PRINT(" %s entry, dir: %d!!", __func__, pipe->dir); + + + //ECRNX_PRINT( " usb recv pipe-dir %d stat %d len %d urb 0x%pK\n", + // pipe->dir, urb->status, urb->actual_length, + // urb); + + if (urb->status != 0) { + status = -EIO; + switch (urb->status) { + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* no need to spew these errors when device + * removed or urb killed due to driver shutdown + */ + status = -ECANCELED; + break; + default: + pipe->err_count++; + if(pipe->err_count%100 == 1) + { + ECRNX_PRINT("usb redcv pipe-dir %d failed: %d\n", + pipe->dir, urb->status); + } + if((pipe->err_status != status)||(pipe->err_count == 10000)) + { + pipe->err_status = status; + pipe->err_count = 0; + } + break; + } + goto cleanup_recv_urb; + } + + if (urb->actual_length == 0) + goto cleanup_recv_urb; + + skb = urb_context->skb; + + /* we are going to pass it up */ + urb_context->skb = NULL; + skb_put(skb, urb->actual_length); + + /* note: queue implements a lock */ + skb_queue_tail(&pipe->io_comp_queue, skb); + usb_free_urb_to_infac(pipe, urb_context); + +#if 0 + usb_rx_cnt++; + //printk("skb->len:%d %d %d\n", skb->len, usb_rx_cnt, urb->actual_length); + if (skb->len < 1500 ||usb_rx_cnt > 12) + { + usb_rx_cnt = 0; + schedule_work(&pipe->io_complete_work); + } +#else + + rx_packets++; + if (skb->len < USB_RX_LENGTH_THD || rx_packets > USB_RX_UPLOAD_THD) + { + rx_packets = 0; + #ifdef CONFIG_ECRNX_KTHREAD + wake_up_interruptible(&g_usb->wait_rx_comp); + #endif + #ifdef CONFIG_ECRNX_WORKQUEUE + schedule_work(&pipe->io_complete_work); + #endif + #ifdef CONFIG_ECRNX_TASKLET + tasklet_schedule(&pipe->rx_tasklet); + #endif + } + else + { + mod_timer(&usb_rx_timer, jiffies + usecs_to_jiffies(USB_RX_TIMER_TIMEOUT_US)); + } +#endif + + //ECRNX_DBG(" entry %s, len: %d\n", __func__, urb->actual_length); + //print_hex_dump(KERN_INFO, "READ:", DUMP_PREFIX_ADDRESS, 32, 1, skb->data, urb->actual_length, false); + + usb_refill_recv_transfer(pipe); + return; + +cleanup_recv_urb: + usb_cleanup_recv_urb(urb_context); +} + +#ifdef CONFIG_ECRNX_TASKLET +static void usb_tx_comp_tasklet(unsigned long data) +{ + struct usb_infac_pipe *pipe = (struct usb_infac_pipe *)data; + struct sk_buff *skb; + struct txdesc_api *tx_desc; + u32_l flag = 0; + ptr_addr host_id; + + while ((skb = skb_dequeue(&pipe->io_comp_queue))) { + //ECRNX_DBG(" %s skb dequeue, skb:0x%08x, skb len: %d!!", __func__, skb, skb->len); + + flag = *(u32_l *)skb->data; + if((u8_l)(flag & FLAG_MSG_TYPE_MASK) != TX_FLAG_TX_DESC) + { + dev_kfree_skb(skb); + continue; + } + tx_desc = (struct txdesc_api *)((u32_l *)skb->data + 1); + if (g_usb->p_eswin->data_cfm_callback && ((u8_l)(flag & FLAG_MSG_TYPE_MASK) == TX_FLAG_TX_DESC) && !(tx_desc->host.flags & TXU_CNTRL_MGMT)) + { + memcpy(&host_id, tx_desc->host.packet_addr, sizeof(ptr_addr)); + g_usb->p_eswin->data_cfm_callback(g_usb->p_eswin->umac_priv, (void*)host_id); + } + } +} +#endif + +#ifdef CONFIG_ECRNX_WORKQUEUE +static void usb_tx_comp_work(struct work_struct *work) +{ + struct usb_infac_pipe *pipe = container_of(work, + struct usb_infac_pipe, + io_complete_work); + struct sk_buff *skb; + struct txdesc_api *tx_desc; + u32_l flag = 0; + ptr_addr host_id; + + while ((skb = skb_dequeue(&pipe->io_comp_queue))) { + //ECRNX_DBG(" %s skb dequeue, skb:0x%08x, skb len: %d!!", __func__, skb, skb->len); + + flag = *(u32_l *)skb->data; + if((u8_l)(flag & FLAG_MSG_TYPE_MASK) != TX_FLAG_TX_DESC) + { + dev_kfree_skb(skb); + continue; + } + tx_desc = (struct txdesc_api *)((u32_l *)skb->data + 1); + if (g_usb->p_eswin->data_cfm_callback && ((u8_l)(flag & FLAG_MSG_TYPE_MASK) == TX_FLAG_TX_DESC) && !(tx_desc->host.flags & TXU_CNTRL_MGMT)) + { + memcpy(&host_id, tx_desc->host.packet_addr, sizeof(ptr_addr)); + g_usb->p_eswin->data_cfm_callback(g_usb->p_eswin->umac_priv, (void*)host_id); + } + } +} +#endif + +#ifdef CONFIG_ECRNX_KTHREAD +void usb_tx_comp_cb(struct sk_buff_head *queue) +{ + struct sk_buff *skb; + struct txdesc_api *tx_desc; + u32_l flag = 0; + ptr_addr host_id; + + while ((skb = skb_dequeue(queue))) { + //ECRNX_DBG(" %s skb dequeue, skb:0x%08x, skb len: %d!!", __func__, skb, skb->len); + + flag = *(u32_l *)skb->data; + if((u8_l)(flag & FLAG_MSG_TYPE_MASK) != TX_FLAG_TX_DESC) + { + dev_kfree_skb(skb); + continue; + } + tx_desc = (struct txdesc_api *)((u32_l *)skb->data + 1); + if (g_usb->p_eswin->data_cfm_callback && ((u8_l)(flag & FLAG_MSG_TYPE_MASK) == TX_FLAG_TX_DESC) && !(tx_desc->host.flags & TXU_CNTRL_MGMT)) + { + memcpy(&host_id, tx_desc->host.packet_addr, sizeof(ptr_addr)); + g_usb->p_eswin->data_cfm_callback(g_usb->p_eswin->umac_priv, (void*)host_id); + } + } +} + +void usb_rx_comp_cb(struct usb_infac_pipe *pipe) +{ + struct sk_buff *skb; + while ((skb = skb_dequeue(&pipe->io_comp_queue))) { + if (g_usb->p_eswin->rx_callback) + { + g_usb->p_eswin->rx_callback(g_usb->p_eswin->umac_priv, skb, usb_pipeendpoint(pipe->usb_pipe_handle)); + } + else + { + if (skb) + { // free the skb + ECRNX_DBG("%s, skb free: 0x%x !! \n",__func__); + dev_kfree_skb(skb); + } + + } + } +} + +static int eswin_tx_comp_thread(void *data) +{ + struct eswin_usb * p_usb = (struct eswin_usb *)data; + int ret; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) + struct sched_param param = { .sched_priority = 1 }; + param.sched_priority = 56; + sched_setscheduler(get_current(), SCHED_FIFO, ¶m); +#else + sched_set_fifo(get_current()); +#endif + ECRNX_PRINT("eswin_tx_comp_thread entry\n"); + + while (!kthread_should_stop()) + { + ret = wait_event_interruptible(p_usb->wait_tx_comp, skb_queue_len(&g_usb->infac_data.pipe_tx.io_comp_queue) != 0 || + skb_queue_len(&g_usb->infac_msg.pipe_tx.io_comp_queue) != 0 || kthread_should_stop()); + if (ret < 0) + { + ECRNX_ERR("usb tx pkg thread error!\n"); + return 0; + } + if(kthread_should_stop()) + { + continue; + } + usb_tx_comp_cb(&g_usb->infac_msg.pipe_tx.io_comp_queue); + usb_tx_comp_cb(&g_usb->infac_data.pipe_tx.io_comp_queue); + } + ECRNX_PRINT("tx pkg thread exit\n"); + return 0; +} + +static int eswin_rx_comp_thread(void *data) +{ + struct eswin_usb * p_usb = (struct eswin_usb *)data; + int ret; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) + struct sched_param param = { .sched_priority = 1 }; + param.sched_priority = 56; + sched_setscheduler(get_current(), SCHED_FIFO, ¶m); +#else + sched_set_fifo(get_current()); +#endif + ECRNX_PRINT("eswin_rx_comp_thread entry\n"); + + //ECRNX_DBG(" %s entry, dir: %d!!", __func__, pipe->dir); + while (!kthread_should_stop()) + { + ret = wait_event_interruptible(p_usb->wait_rx_comp, skb_queue_len(&g_usb->infac_data.pipe_rx.io_comp_queue) != 0 || + skb_queue_len(&g_usb->infac_msg.pipe_rx.io_comp_queue) != 0|| kthread_should_stop()); + + if (ret < 0) + { + ECRNX_ERR("usb tx pkg thread error!\n"); + return 0; + } + if(kthread_should_stop()) + { + continue; + } + usb_rx_comp_cb(&g_usb->infac_msg.pipe_rx); + usb_rx_comp_cb(&g_usb->infac_data.pipe_rx); + } + ECRNX_PRINT("rx pkg thread exit\n"); + return 0; +} +#endif + +#ifdef CONFIG_ECRNX_TASKLET +static void usb_rx_comp_tasklet(unsigned long data) +{ + struct usb_infac_pipe *pipe = (struct usb_infac_pipe *)data; + struct sk_buff *skb; + + //ECRNX_DBG(" %s entry, dir: %d!!", __func__, pipe->dir); + + while ((skb = skb_dequeue(&pipe->io_comp_queue))) { + if (g_usb->p_eswin->rx_callback) + { + g_usb->p_eswin->rx_callback(g_usb->p_eswin->umac_priv, skb, usb_pipeendpoint(pipe->usb_pipe_handle)); + } + else + { + if (skb) + { // free the skb + ECRNX_DBG("%s, skb free: 0x%x !! \n",__func__); + dev_kfree_skb(skb); + } + + } + + } +} + +#endif + +#ifdef CONFIG_ECRNX_WORKQUEUE +static void usb_rx_comp_work(struct work_struct *work) +{ + struct usb_infac_pipe *pipe = container_of(work, + struct usb_infac_pipe, + io_complete_work); + struct sk_buff *skb; + //ECRNX_DBG(" %s entry, dir: %d!!", __func__, pipe->dir); + + while ((skb = skb_dequeue(&pipe->io_comp_queue))) { + if (g_usb->p_eswin->rx_callback) + { + g_usb->p_eswin->rx_callback(g_usb->p_eswin->umac_priv, skb, usb_pipeendpoint(pipe->usb_pipe_handle)); + } + else + { + if (skb) + { // free the skb + ECRNX_DBG("%s, skb free: 0x%x !! \n",__func__); + dev_kfree_skb(skb); + } + + } + } +} +#endif + +static void usb_flush_pipe(struct usb_infac_pipe * pipe) +{ + usb_kill_anchored_urbs(&pipe->urb_submitted); + if (pipe->dir == USB_DIR_TX) { + #ifdef CONFIG_ECRNX_KTHREAD + if(g_usb->kthread_tx_comp) + { + kthread_stop(g_usb->kthread_tx_comp); + wake_up_interruptible(&g_usb->wait_tx_comp); + g_usb->kthread_tx_comp = NULL; + } + #endif + #ifdef CONFIG_ECRNX_TASKLET + tasklet_kill(&pipe->tx_tasklet); + #endif + #ifdef CONFIG_ECRNX_WORKQUEUE + cancel_work_sync(&pipe->io_complete_work); + #endif + } else { + #ifdef CONFIG_ECRNX_KTHREAD + if(g_usb->kthread_rx_comp) + { + kthread_stop(g_usb->kthread_rx_comp); + wake_up_interruptible(&g_usb->wait_rx_comp); + g_usb->kthread_rx_comp = NULL; + } + #endif + #ifdef CONFIG_ECRNX_TASKLET + tasklet_kill(&pipe->rx_tasklet); + #endif + #ifdef CONFIG_ECRNX_WORKQUEUE + cancel_work_sync(&pipe->io_complete_work); + #endif + } +} + +static void usb_flush_all(struct eswin_usb * p_usb) +{ + usb_flush_pipe(&p_usb->infac_data.pipe_rx); + usb_flush_pipe(&p_usb->infac_data.pipe_tx); + usb_flush_pipe(&p_usb->infac_msg.pipe_rx); + usb_flush_pipe(&p_usb->infac_msg.pipe_tx); +} + + +static void usb_refill_recv_transfer(struct usb_infac_pipe * pipe) +{ + struct usb_urb_context *urb_context; + struct urb *urb; + int usb_status; + + for ( ;; ) { + urb_context = usb_alloc_urb_from_pipe(pipe); + if (!urb_context) + break; + + urb_context->skb = dev_alloc_skb(USB_RX_MAX_BUF_SIZE); + if (!urb_context->skb) + goto err; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) + goto err; + urb_context->urb = urb; + + usb_fill_bulk_urb(urb, + pipe->infac->udev, + pipe->usb_pipe_handle, + urb_context->skb->data, + USB_RX_MAX_BUF_SIZE, + usb_recv_complete, urb_context); + + usb_anchor_urb(urb, &pipe->urb_submitted); + usb_status = usb_submit_urb(urb, GFP_ATOMIC); + + if (usb_status) { + ECRNX_PRINT("usb_refill_recv_transfer, usb bulk recv failed: %d\n", + usb_status); + //usb_unanchor_urb(urb); + //usb_free_urb(urb); + goto err; + } + //usb_free_urb(urb); + } + + return; + +err: + usb_cleanup_recv_urb(urb_context); +} + + + +static int usb_hif_start(struct eswin *tr) +{ + struct eswin_usb * p_usb = (struct eswin_usb *)tr->drv_priv; + + ECRNX_DBG("%s entry!!", __func__); + + usb_refill_recv_transfer(&p_usb->infac_data.pipe_rx); + usb_refill_recv_transfer(&p_usb->infac_msg.pipe_rx); + timer_setup(&usb_rx_timer, usb_rx_timer_handle, 0); + + return 0; +} + + +int usb_hif_xmit(struct eswin *tr, struct sk_buff *skb) +{ + + unsigned int req_type = TX_FLAG_MAX; + struct eswin_usb * p_usb = (struct eswin_usb *)tr->drv_priv; + struct usb_infac_data_t * infac_data = NULL; + struct usb_infac_pipe * pipe = NULL; + struct usb_urb_context *urb_context; + struct urb *urb; + int ret = -1; + + req_type = *((unsigned int*)(skb->data)); + if((req_type & FLAG_MSG_TYPE_MASK) == TX_FLAG_TX_DESC) + { + infac_data = &p_usb->infac_data; + pipe = &infac_data->pipe_tx; + } + else + { + infac_data = &p_usb->infac_msg; + pipe = &infac_data->pipe_tx; + } + + urb_context = usb_alloc_urb_from_pipe(pipe); + if (!urb_context) { + ret = -ENOMEM; + goto err; + } + + urb_context->skb = skb; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + ret = -ENOMEM; + goto err_free_urb_to_pipe; + } + + urb_context->urb = urb; + usb_fill_bulk_urb(urb, + infac_data->udev, + pipe->usb_pipe_handle, + skb->data, + skb->len, + usb_transmit_complete, urb_context); + + if (!(skb->len % infac_data->max_packet_size)) { + /* hit a max packet boundary on this pipe */ + urb->transfer_flags |= URB_ZERO_PACKET; + } + + usb_anchor_urb(urb, &pipe->urb_submitted); + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) { + ECRNX_PRINT("usb_hif_xmit, usb bulk transmit failed: %d\n", ret); + //usb_unanchor_urb(urb); + ret = -EINVAL; + goto err_free_urb_to_pipe; + } + + //usb_free_urb(urb); + + return 0; + +err_free_urb_to_pipe: + usb_free_urb_to_infac(urb_context->pipe, urb_context); +err: + ECRNX_PRINT("usb_hif_xmit, pkg miss due to urb.\n"); + skb_queue_tail(&pipe->io_comp_queue, skb); + #ifdef CONFIG_ECRNX_KTHREAD + wake_up_interruptible(&g_usb->wait_tx_comp); + #endif + #ifdef CONFIG_ECRNX_WORKQUEUE + schedule_work(&pipe->io_complete_work); + #endif + #ifdef CONFIG_ECRNX_TASKLET + tasklet_schedule(&pipe->tx_tasklet); + #endif + return ret; +} + +static int usb_hif_write_raw(struct eswin *tr, const void* data, const u32 len) +{ + struct eswin_usb * p_usb = (struct eswin_usb *)tr->drv_priv; + struct usb_infac_data_t * infac_data = &p_usb->infac_data; + struct usb_infac_pipe * pipe = &infac_data->pipe_tx; + + return usb_bulk_msg(infac_data->udev, pipe->usb_pipe_handle, (void*)data, len, NULL, 20000); +} + +static int usb_hif_wait_ack(struct eswin *tr, void* data, const u32 len) +{ + struct eswin_usb * p_usb = (struct eswin_usb *)tr->drv_priv; + struct usb_infac_data_t * infac_data = &p_usb->infac_data; + struct usb_infac_pipe * pipe = &infac_data->pipe_rx; + + return usb_bulk_msg(infac_data->udev, pipe->usb_pipe_handle, data, len, NULL, 20000); +} + +#ifdef CONFIG_PM +static int usb_hif_suspend(struct eswin *tr) +{ + return -EOPNOTSUPP; +} + +static int usb_hif_resume(struct eswin *tr) +{ + return -EOPNOTSUPP; +} +#endif + +static struct usb_ops eswin_usb_hif_ops = { + .xmit = usb_hif_xmit, + .start = usb_hif_start, + .write = usb_hif_write_raw, + .wait_ack = usb_hif_wait_ack, + .suspend = usb_hif_suspend, + .resume = usb_hif_resume, +}; + +static int usb_create_pipe(struct usb_infac_pipe * pipe, int dir, bool flag) +{ + int i; + struct usb_urb_context *urb_context; + ECRNX_DBG("%s entry, dir: %d!!", __func__, dir); + + pipe->dir = dir; + pipe->err_count = 0; + init_usb_anchor(&pipe->urb_submitted); + INIT_LIST_HEAD(&pipe->urb_list_head); + + for (i = 0; i < (flag ? USB_MSG_URB_NUM : USB_DATA_URB_NUM); i++) { + urb_context = kzalloc(sizeof(*urb_context), GFP_KERNEL); + if (!urb_context) + return -ENOMEM; + + urb_context->pipe = pipe; + //pipe->urb_cnt++; + + usb_free_urb_to_infac(pipe, urb_context); + } + + if (dir) { + #ifdef CONFIG_ECRNX_WORKQUEUE + INIT_WORK(&pipe->io_complete_work, usb_tx_comp_work); + #endif + #ifdef CONFIG_ECRNX_TASKLET + tasklet_init(&pipe->tx_tasklet, usb_tx_comp_tasklet, (unsigned long) pipe); + #endif + } else { + #ifdef CONFIG_ECRNX_WORKQUEUE + INIT_WORK(&pipe->io_complete_work, usb_rx_comp_work); + #endif + #ifdef CONFIG_ECRNX_TASKLET + tasklet_init(&pipe->rx_tasklet, usb_rx_comp_tasklet, (unsigned long) pipe); + #endif + } + + skb_queue_head_init(&pipe->io_comp_queue); + return 0; +} + +static int usb_create_infac(struct usb_interface *interface, struct usb_infac_data_t * p_infac, bool flag) +{ + struct usb_device *dev = interface_to_usbdev(interface); + struct usb_host_interface *iface_desc = interface->cur_altsetting; + struct usb_endpoint_descriptor *endpoint = NULL; + + int i; + ECRNX_DBG("%s entry %d!!", __func__, iface_desc->desc.bNumEndpoints); + + usb_get_dev(dev); + usb_set_intfdata(interface, p_infac); + + p_infac->udev = dev; + p_infac->interface = interface; + + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + if (endpoint->bEndpointAddress & USB_DIR_MASK) { + p_infac->pipe_rx.usb_pipe_handle = usb_rcvbulkpipe(dev, endpoint->bEndpointAddress); + p_infac->pipe_rx.infac = p_infac; + usb_create_pipe(&p_infac->pipe_rx, USB_DIR_RX, flag); + } else { + p_infac->pipe_tx.usb_pipe_handle = usb_sndbulkpipe(dev, endpoint->bEndpointAddress); + p_infac->pipe_tx.infac = p_infac; + usb_create_pipe(&p_infac->pipe_tx, USB_DIR_TX, flag); + } + } + p_infac->max_packet_size = le16_to_cpu(endpoint->wMaxPacketSize);; + + return 0; +} + +extern bool usb_status; +extern struct eswin *pEswin; +static int eswin_usb_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + int ret; + struct eswin_usb * p_usb; + struct eswin* p_eswin; + struct usb_host_interface *iface_desc = interface->cur_altsetting; + struct usb_device *dev = interface_to_usbdev(interface); + + if((usb_status == false) && dl_fw) + { + ECRNX_PRINT("%s entry, reset slave !!", __func__); + usb_control_msg(dev, + usb_sndctrlpipe(dev, 0), + 0x2, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, + 0, + NULL, 0,10); + msleep(200); + } + + ECRNX_PRINT("%s entry, func: %d, g_usb: %p !!", __func__, iface_desc->desc.bInterfaceNumber, g_usb); + + if (iface_desc->desc.bInterfaceNumber == USB_INFAC_DATA) { + if (g_usb) { + p_usb = g_usb; + pEswin->dev = &interface->dev; + } else { + p_eswin = eswin_core_create(sizeof(struct eswin_usb), &interface->dev, &eswin_usb_hif_ops); + if(!p_eswin) { + dev_err(&interface->dev, "failed to allocate core\n"); + return -ENOMEM; + } + + p_usb = (struct eswin_usb *)p_eswin->drv_priv; + p_usb->p_eswin = p_eswin; + g_usb = p_usb; + spin_lock_init(&p_usb->cs_lock); + } + + p_usb->dev = &interface->dev; + + usb_create_infac(interface, &p_usb->infac_data, 0); + } else { + usb_create_infac(interface, &g_usb->infac_msg, 1); + //usb_hif_start(g_usb->p_eswin); + + if (!g_usb->usb_enum_already) { + ret = eswin_core_register(g_usb->p_eswin); + if(ret) { + ECRNX_PRINT("failed to register core\n"); + return ret; + } + } else { + g_usb->usb_enum_already = 1; + } + return 0; + } + + #ifdef CONFIG_ECRNX_KTHREAD + if(iface_desc->desc.bInterfaceNumber == USB_INFAC_DATA) + { + init_waitqueue_head(&g_usb->wait_tx_comp); + init_waitqueue_head(&g_usb->wait_rx_comp); + + g_usb->kthread_tx_comp = kthread_run(eswin_tx_comp_thread, g_usb, "tx_comp"); + g_usb->kthread_rx_comp = kthread_run(eswin_rx_comp_thread, g_usb, "rx_comp"); + + if (IS_ERR(g_usb->kthread_tx_comp)) { + g_usb->kthread_tx_comp = NULL; + ECRNX_PRINT("kthread_tx_comp run fail\n"); + } + + if (IS_ERR(g_usb->kthread_rx_comp)) { + g_usb->kthread_rx_comp = NULL; + ECRNX_PRINT("kthread_rx_comp run fail\n"); + } + ECRNX_PRINT("%s kthread_run success.", __func__); + } + #endif + ECRNX_PRINT("%s exit!!", __func__); + return 0; +} + +static void eswin_usb_remove(struct usb_interface *interface) +{ + struct usb_infac_data_t * p_infac; + struct usb_host_interface *iface_desc = interface->cur_altsetting; + + ECRNX_PRINT("%s entry!!", __func__); + + p_infac = usb_get_intfdata(interface); + + if (!p_infac) { + ECRNX_PRINT("%s, p_infa is null!!", __func__); + return; + } + + if (iface_desc->desc.bInterfaceNumber == USB_INFAC_DATA) { + eswin_core_unregister(g_usb->p_eswin); + } + + del_timer(&usb_rx_timer); + + usb_flush_pipe(&p_infac->pipe_rx); + usb_flush_pipe(&p_infac->pipe_tx); + usb_free_pipe_resources(&p_infac->pipe_rx); + usb_free_pipe_resources(&p_infac->pipe_tx); + + usb_set_intfdata(interface, NULL); + usb_put_dev(interface_to_usbdev(interface)); + dl_fw = true; + offset = 0; +} + + +#ifdef CONFIG_PM +static int eswin_usb_pm_suspend(struct usb_interface *interface, pm_message_t message) +{ + ECRNX_PRINT("entry %s\n", __func__); + usb_flush_all(g_usb); + return 0; +} + +static int eswin_usb_pm_resume(struct usb_interface *interface) +{ + ECRNX_PRINT("entry %s\n", __func__); + usb_refill_recv_transfer(&g_usb->infac_data.pipe_rx); + usb_refill_recv_transfer(&g_usb->infac_msg.pipe_rx); + return 0; +} + +#else +#define eswin_usb_pm_suspend NULL +#define eswin_usb_pm_resume NULL +#endif + +/* table of devices that work with this driver */ +static struct usb_device_id eswin_usb_ids[] = { + {USB_DEVICE(0x3452, 0x6600)}, + { /* Terminating entry */ }, +}; + +MODULE_DEVICE_TABLE(usb, eswin_usb_ids); +static struct usb_driver eswin_usb_driver = { + .name = "eswin_usb", + .probe = eswin_usb_probe, + .suspend = eswin_usb_pm_suspend, + .resume = eswin_usb_pm_resume, + .disconnect = eswin_usb_remove, + .id_table = eswin_usb_ids, + .supports_autosuspend = true, +}; + +static int __init eswin_usb_init(void) +{ + int ret; + + #ifdef CONFIG_POWERKEY_GPIO + int pk_gpio = 4; + pk_gpio = CONFIG_POWERKEY_GPIO; + gpio_direction_output(pk_gpio,0); + msleep(1000); + gpio_direction_output(pk_gpio,1); + #endif + + ECRNX_PRINT("%s entry !!\n", __func__); + ret = usb_register(&eswin_usb_driver); + if (ret) + ECRNX_PRINT("sdio driver registration failed: %d\n", ret); + + ECRNX_PRINT("%s exit !!\n", __func__); + return ret; +} + +static void __exit eswin_usb_exit(void) +{ + ECRNX_PRINT("%s entry !!\n", __func__); + usb_deregister(&eswin_usb_driver); + ECRNX_PRINT("%s exit !!\n", __func__); +} + +void ecrnx_usb_reset_sync_fw(void) +{ + usb_control_msg(g_usb->infac_msg.udev, + usb_sndctrlpipe(g_usb->infac_msg.udev, 0), + 0x2, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, + 0, + NULL, 0,10); +} + +int ecrnx_usb_register_drv(void) +{ + return eswin_usb_init(); +} + +void ecrnx_usb_unregister_drv(void) +{ + eswin_core_unregister(g_usb->p_eswin); + ecrnx_usb_reset_sync_fw(); + return eswin_usb_exit(); +} + +struct device *eswin_usb_get_dev(void *plat) +{ + struct eswin* tr = (struct eswin*)plat; + + return tr->dev; +} + diff --git a/drivers/net/wireless/eswin/usb/usb.h b/drivers/net/wireless/eswin/usb/usb.h new file mode 100644 index 000000000000..27e9592bf30c --- /dev/null +++ b/drivers/net/wireless/eswin/usb/usb.h @@ -0,0 +1,101 @@ +/** + ****************************************************************************** + * + * @file usb.h + * + * @brief usb driver definitions + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef __USB_H +#define __USB_H + +#include "ecrnx_defs.h" +#include <linux/usb.h> + +#define USB_INFAC_DATA 0 +#define USB_INFAC_MSG 1 + +#define USB_DIR_MASK 0x80 +#define USB_NUM_MASK 0x7F + +#define USB_DATA_URB_NUM 64 +#define USB_MSG_URB_NUM 16 + +#define USB_DIR_RX 0 +#define USB_DIR_TX 1 + +#define USB_RX_MAX_BUF_SIZE 4096 + +struct usb_infac_pipe { + int dir; + u32 urb_cnt; + struct usb_infac_data_t *infac; + + struct usb_anchor urb_submitted; + unsigned int usb_pipe_handle; + struct list_head urb_list_head; + #ifdef CONFIG_ECRNX_WORKQUEUE + struct work_struct io_complete_work; + #endif + #ifdef CONFIG_ECRNX_TASKLET + struct tasklet_struct tx_tasklet; + struct tasklet_struct rx_tasklet; + #endif + struct sk_buff_head io_comp_queue; + unsigned int err_count; + int err_status; +}; + + +struct usb_infac_data_t { + struct usb_interface *interface; + struct usb_device *udev; + u16 max_packet_size; + + struct usb_infac_pipe pipe_rx; + struct usb_infac_pipe pipe_tx; +}; + +struct eswin_usb { + struct eswin * p_eswin; + struct device * dev; + struct usb_infac_data_t infac_data; + struct usb_infac_data_t infac_msg; + #ifdef CONFIG_ECRNX_KTHREAD + struct task_struct *kthread_tx_comp; + struct task_struct *kthread_rx_comp; + + wait_queue_head_t wait_tx_comp; + wait_queue_head_t wait_rx_comp; + #endif + spinlock_t cs_lock; + u8 usb_enum_already; +}; + +struct usb_urb_context { + struct list_head link; + struct sk_buff *skb; + struct urb *urb; + struct usb_infac_pipe * pipe; +}; + + +struct usb_ops { + int (*start)(struct eswin *tr); + int (*xmit)(struct eswin *tr, struct sk_buff *skb); + int (*suspend)(struct eswin *tr); + int (*resume)(struct eswin *tr); + int (*write)(struct eswin *tr, const void* data, const u32 len); + int (*wait_ack)(struct eswin *tr, void* data, const u32 len); +}; + + + +extern int ecrnx_usb_register_drv(void); +extern void ecrnx_usb_unregister_drv(void); + +#endif /* __SDIO_H */ diff --git a/drivers/net/wireless/eswin/usb/usb_host_interface.h b/drivers/net/wireless/eswin/usb/usb_host_interface.h new file mode 100644 index 000000000000..195753ea4c66 --- /dev/null +++ b/drivers/net/wireless/eswin/usb/usb_host_interface.h @@ -0,0 +1,29 @@ +/** + ****************************************************************************** + * + * @file usb_host_interface.h + * + * @brief usb host interface definitions + * + * Copyright (C) ESWIN 2015-2020 + * + ****************************************************************************** + */ + +#ifndef _USB_HOST_INTERFACE_H +#define _USB_HOST_INTERFACE_H +/******************************************************************************* + * Function: usb_host_send + * Description:send buff from host to slave + * Parameters: + * Input: void *buff, int len, int flag + * + * Output: + * + * Returns: 0 + * + * + * Others: + ********************************************************************************/ +int usb_host_send(void *buff, int len, int flag); +#endif diff --git a/drivers/net/wireless/eswin/wifi_ecr6600u.cfg b/drivers/net/wireless/eswin/wifi_ecr6600u.cfg new file mode 100644 index 000000000000..df5912e6190b --- /dev/null +++ b/drivers/net/wireless/eswin/wifi_ecr6600u.cfg @@ -0,0 +1,3 @@ +DRIVER_LOG_LEVEL=3 +FW_LOG_LEVEL=2 +FW_LOG_TYPE=0 |