[][atenl: add package]
[Description]
Add atenl package, a userspace daemon for mt76 testmode.
atenl acts as an intermediate for HQADLL command and mt76 testmode
(implemented with NL80211_CMD_TESTMODE), which provides transparency for
the usage of QA-tool and Litepoint on mt76.
[Release-log]
N/A
Change-Id: If11e67b36dd7c3ef9629e824bc26ed4f16f34dca
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/5553443
diff --git a/feed/atenl/Makefile b/feed/atenl/Makefile
new file mode 100644
index 0000000..d1c9d36
--- /dev/null
+++ b/feed/atenl/Makefile
@@ -0,0 +1,35 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=atenl
+PKG_RELEASE=1
+
+PKG_LICENSE:=GPLv2
+PKG_LICENSE_FILES:=
+
+PKG_MAINTAINER:=Shayne Chen <shayne.chen@mediatek.com>
+PKG_BUILD_PARALLEL:=1
+
+include $(INCLUDE_DIR)/package.mk
+include $(INCLUDE_DIR)/cmake.mk
+
+CMAKE_SOURCE_DIR:=$(PKG_BUILD_DIR)
+CMAKE_BINARY_DIR:=$(PKG_BUILD_DIR)
+
+define Package/atenl
+ SECTION:=MTK Properties
+ CATEGORY:=MTK Properties
+ TITLE:=testmode daemon for nl80211
+ SUBMENU:=Applications
+ DEPENDS:=+libnl-tiny
+endef
+
+TARGET_CFLAGS += -I$(STAGING_DIR)/usr/include/libnl-tiny
+
+define Package/atenl/install
+ mkdir -p $(1)/usr/sbin
+ $(INSTALL_BIN) $(PKG_BUILD_DIR)/atenl $(1)/usr/sbin
+ $(INSTALL_BIN) ./files/ated.sh $(1)/usr/sbin/ated
+ $(INSTALL_BIN) ./files/iwpriv.sh $(1)/usr/sbin/iwpriv
+endef
+
+$(eval $(call BuildPackage,atenl))
diff --git a/feed/atenl/files/ated.sh b/feed/atenl/files/ated.sh
new file mode 100755
index 0000000..13786d6
--- /dev/null
+++ b/feed/atenl/files/ated.sh
@@ -0,0 +1,27 @@
+#!/bin/ash
+# This script is used for wrapping atenl daemon to ated
+
+# 0 is normal mode, 1 is used for specific commands
+mode="0"
+add_quote="0"
+cmd="atenl"
+
+for i in "$@"
+do
+ if [ "$i" = "-c" ]; then
+ cmd="${cmd} -c"
+ mode="1"
+ add_quote="1"
+ elif [ "${add_quote}" = "1" ]; then
+ cmd="${cmd} \"${i}\""
+ add_quote="0"
+ else
+ cmd="${cmd} ${i}"
+ fi
+done
+
+if [ "$mode" = "0" ]; then
+ killall atenl
+fi
+
+eval "${cmd}"
diff --git a/feed/atenl/files/iwpriv.sh b/feed/atenl/files/iwpriv.sh
new file mode 100755
index 0000000..be5a2ac
--- /dev/null
+++ b/feed/atenl/files/iwpriv.sh
@@ -0,0 +1,329 @@
+#!/bin/ash
+
+interface=$1
+cmd_type=$2
+full_cmd=$3
+
+work_mode="RUN" # RUN/PRINT/DEBUG
+tmp_file="$HOME/.tmp_ate_config"
+phy_idx=$(echo ${interface} | tr -dc '0-9')
+
+cmd=$(echo ${full_cmd} | sed s/=/' '/g | cut -d " " -f 1)
+param=$(echo ${full_cmd} | sed s/=/' '/g | cut -d " " -f 2)
+
+function do_cmd() {
+ case ${work_mode} in
+ "RUN")
+ eval "$1"
+ ;;
+ "PRINT")
+ echo "$1"
+ ;;
+ "DEBUG")
+ eval "$1"
+ echo "$1"
+ ;;
+ esac
+}
+
+function record_config() {
+ if [ -f ${tmp_file} ]; then
+ if grep -q $1 ${tmp_file}; then
+ sed -i "/$1/c\\$1=$2" ${tmp_file}
+ else
+ echo "$1=$2" >> ${tmp_file}
+ fi
+ else
+ echo "$1=$2" >> ${tmp_file}
+ fi
+}
+
+function get_config() {
+ echo "$(cat ${tmp_file} | grep $1 | sed s/=/' '/g | cut -d " " -f 2)"
+}
+
+function do_ate_work() {
+ local ate_cmd=$1
+
+ case ${ate_cmd} in
+ "ATESTART")
+ local if_str=$(ifconfig | grep mon${phy_idx})
+
+ if [ ! -z "${if_str}" -a "${if_str}" != " " ]; then
+ echo "ATE already starts."
+ else
+ do_cmd "iw phy ${interface} interface add mon${phy_idx} type monitor"
+ do_cmd "iw dev wlan${phy_idx} del"
+ do_cmd "ifconfig mon${phy_idx} up"
+ fi
+ ;;
+ "ATESTOP")
+ local if_str=$(ifconfig | grep mon${phy_idx})
+
+ if [ -z "${if_str}" -a "${if_str}" != " " ]; then
+ echo "ATE does not start."
+ else
+ do_cmd "mt76-test ${interface} set state=off"
+ do_cmd "iw dev mon${phy_idx} del"
+ do_cmd "iw phy ${interface} interface add wlan${phy_idx} type managed"
+ do_cmd "ifconfig wlan${phy_idx} up"
+ fi
+ ;;
+ "TXFRAME")
+ do_cmd "mt76-test ${interface} set state=tx_frames"
+ ;;
+ "TXSTOP")
+ do_cmd "mt76-test ${interface} set state=idle"
+ ;;
+ "RXFRAME")
+ do_cmd "mt76-test ${interface} set state=rx_frames"
+ ;;
+ "RXSTOP")
+ do_cmd "mt76-test ${interface} set state=idle"
+ ;;
+ "TXCONT")
+ do_cmd "mt76-test ${interface} set state=tx_cont"
+ ;;
+ esac
+}
+
+function simple_convert() {
+ if [ "$1" = "ATETXCNT" ]; then
+ echo "tx_count"
+ elif [ "$1" = "ATETXLEN" ]; then
+ echo "tx_length"
+ elif [ "$1" = "ATETXMCS" ]; then
+ echo "tx_rate_idx"
+ elif [ "$1" = "ATEVHTNSS" ]; then
+ echo "tx_rate_nss"
+ elif [ "$1" = "ATETXLDPC" ]; then
+ echo "tx_rate_ldpc"
+ elif [ "$1" = "ATETXSTBC" ]; then
+ echo "tx_rate_stbc"
+ elif [ "$1" = "ATEPKTTXTIME" ]; then
+ echo "tx_time"
+ elif [ "$1" = "ATEIPG" ]; then
+ echo "tx_ipg"
+ elif [ "$1" = "ATEDUTYCYCLE" ]; then
+ echo "tx_duty_cycle"
+ elif [ "$1" = "ATETXFREQOFFSET" ]; then
+ echo "freq_offset"
+ else
+ echo "unknown"
+ fi
+}
+
+function convert_tx_mode() {
+ if [ "$1" = "0" ]; then
+ echo "cck"
+ elif [ "$1" = "1" ]; then
+ echo "ofdm"
+ elif [ "$1" = "2" ]; then
+ echo "ht"
+ elif [ "$1" = "4" ]; then
+ echo "vht"
+ elif [ "$1" = "8" ]; then
+ echo "he_su"
+ elif [ "$1" = "9" ]; then
+ echo "he_er"
+ elif [ "$1" = "10" ]; then
+ echo "he_tb"
+ elif [ "$1" = "11" ]; then
+ echo "he_mu"
+ else
+ echo "unknown"
+ fi
+}
+
+function convert_gi {
+ local tx_mode=$1
+ local val=$2
+ local sgi="0"
+ local he_ltf="0"
+
+ case ${tx_mode} in
+ "ht"|"vht")
+ sgi=${val}
+ ;;
+ "he_su"|"he_er")
+ case ${val} in
+ "0")
+ ;;
+ "1")
+ he_ltf="1"
+ ;;
+ "2")
+ sgi="1"
+ he_ltf="1"
+ ;;
+ "3")
+ sgi="2"
+ he_ltf="2"
+ ;;
+ "4")
+ he_ltf="2"
+ ;;
+ *)
+ echo "unknown gi"
+ esac
+ ;;
+ "he_mu")
+ case ${val} in
+ "0")
+ he_ltf="2"
+ ;;
+ "1")
+ he_ltf="1"
+ ;;
+ "2")
+ sgi="1"
+ he_ltf="1"
+ ;;
+ "3")
+ sgi="2"
+ he_ltf="2"
+ ;;
+ *)
+ echo "unknown gi"
+ esac
+ ;;
+ "he_tb")
+ case ${val} in
+ "0")
+ sgi="1"
+ ;;
+ "1")
+ sgi="1"
+ he_ltf="1"
+ ;;
+ "2")
+ sgi="2"
+ he_ltf="2"
+ ;;
+ *)
+ echo "unknown gi"
+ esac
+ ;;
+ *)
+ echo "unknown tx_rate_mode, can't transform gi"
+ esac
+
+ do_cmd "mt76-test ${interface} set tx_rate_sgi=${sgi} tx_ltf=${he_ltf}"
+}
+
+function convert_channel {
+ local band=$(echo $1 | sed s/:/' '/g | cut -d " " -f 2)
+ local ch=$(echo $1 | sed s/:/' '/g | cut -d " " -f 1)
+ local bw=$(get_config "ATETXBW" | cut -d ":" -f 1)
+
+ if [ "${band}" = "0" ]; then
+ case ${bw} in
+ "1")
+ if [ "${ch}" -ge "1" ] && [ "${ch}" -le "7" ]; then
+ local bw_str="HT40+"
+ else
+ local bw_str="HT40-"
+ fi
+ ;;
+ "0")
+ local bw_str="HT20"
+ ;;
+ esac
+ else
+ case ${bw} in
+ "2")
+ local bw_str="80MHz"
+ ;;
+ "1")
+ if [ "${ch}" == "36" ] || [ "${ch}" == "44" ] || [ "${ch}" == "52" ] || [ "${ch}" == "60" ] || \
+ [ "${ch}" == "100" ] || [ "${ch}" == "108" ] || [ "${ch}" == "116" ] || [ "${ch}" == "124" ] || \
+ [ "${ch}" == "132" ] || [ "${ch}" == "132" ] || [ "${ch}" == "140" ] || [ "${ch}" == "149" ] || \
+ [ "${ch}" == "157" ]
+ then
+ local bw_str="HT40+"
+ else
+ local bw_str="HT40-"
+ fi
+ ;;
+ "0")
+ local bw_str="HT20"
+ ;;
+ esac
+ fi
+
+ do_cmd "iw dev mon${phy_idx} set channel ${ch} ${bw_str}"
+}
+
+if [ "${cmd_type}" = "set" ]; then
+ skip=0
+ use_ated=0
+ case ${cmd} in
+ "ATE")
+ do_ate_work ${param}
+
+ skip=1
+ ;;
+ "ATETXCNT"|"ATETXLEN"|"ATETXMCS"|"ATEVHTNSS"|"ATETXLDPC"|"ATETXSTBC"| \
+ "ATEPKTTXTIME"|"ATEIPG"|"ATEDUTYCYCLE"|"ATETXFREQOFFSET")
+ cmd_new=$(simple_convert ${cmd})
+ param_new=${param}
+ ;;
+ "ATETXANT"|"ATERXANT")
+ cmd_new="tx_antenna"
+ param_new=${param}
+ ;;
+ "ATETXGI")
+ tx_mode=$(convert_tx_mode $(get_config "ATETXMODE"))
+ convert_gi ${tx_mode} ${param}
+ skip=1
+ ;;
+ "ATETXMODE")
+ cmd_new="tx_rate_mode"
+ param_new=$(convert_tx_mode ${param})
+ record_config ${cmd} ${param}
+ ;;
+ "ATETXPOW0"|"ATETXPOW1"|"ATETXPOW2"|"ATETXPOW3")
+ cmd_new="tx_power"
+ param_new="${param},0,0,0"
+ ;;
+ "ATETXBW")
+ record_config ${cmd} ${param}
+ skip=1
+ ;;
+ "ATECHANNEL")
+ convert_channel ${param}
+ skip=1
+ ;;
+ "ATECTRLBANDIDX")
+ echo "Unused command, please use phy0/phy1 to switch"
+ skip=1
+ ;;
+ "bufferMode")
+ if [ "${param}" = "2" ]; then
+ do_cmd "ated -i ${interface} -c \"eeprom update\""
+ fi
+ skip=1
+ ;;
+ *)
+ echo "Unknown command to set"
+ skip=1
+ esac
+
+ if [ "${skip}" != "1" ]; then
+ do_cmd "mt76-test ${interface} set ${cmd_new}=${param_new}"
+ fi
+elif [ "${cmd_type}" = "show" ]; then
+ do_cmd "mt76-test ${interface} dump"
+ do_cmd "mt76-test ${interface} dump stats"
+elif [ "${cmd_type}" = "e2p" ]; then
+ v1=$(do_cmd "ated -i ${interface} -c \"eeprom read ${param}\"")
+ v1=$(echo "${v1}" | grep "val =" | cut -d '(' -f 2 | grep -o -E '[0-9]+')
+
+ param2=$(expr ${param} + "1")
+ v2=$(do_cmd "ated -i ${interface} -c \"eeprom read ${param2}\"")
+ v2=$(echo "${v2}" | grep "val =" | cut -d '(' -f 2 | grep -o -E '[0-9]+')
+ printf "[0x%04x]:0x%02x%02x\n" ${param} ${v2} ${v1}
+else
+ echo "Unknown command"
+fi
+
diff --git a/feed/atenl/src/CMakeLists.txt b/feed/atenl/src/CMakeLists.txt
new file mode 100644
index 0000000..cc91b05
--- /dev/null
+++ b/feed/atenl/src/CMakeLists.txt
@@ -0,0 +1,13 @@
+cmake_minimum_required(VERSION 2.8)
+
+PROJECT(atenl C)
+ADD_DEFINITIONS(-Os -Wall --std=gnu99 -g3)
+
+ADD_EXECUTABLE(atenl main.c eth.c hqa.c nl.c eeprom.c util.c)
+TARGET_LINK_LIBRARIES(atenl nl-tiny)
+
+SET(CMAKE_INSTALL_PREFIX /usr)
+
+INSTALL(TARGETS atenl
+ RUNTIME DESTINATION sbin
+)
diff --git a/feed/atenl/src/atenl.h b/feed/atenl/src/atenl.h
new file mode 100644
index 0000000..ea8e4a0
--- /dev/null
+++ b/feed/atenl/src/atenl.h
@@ -0,0 +1,397 @@
+/* Copyright (C) 2021-2022 Mediatek Inc. */
+#ifndef __ATENL_H
+#define __ATENL_H
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/nl80211.h>
+#include <net/if.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "nl.h"
+#include "util.h"
+
+/* #define CONFIG_ATENL_DEBUG 1 */
+/* #define CONFIG_ATENL_DEBUG_VERBOSE 1 */
+#define BRIDGE_NAME "br-lan"
+#define ETH_P_RACFG 0x2880
+#define RACFG_PKT_MAX_SIZE 1600
+#define RACFG_HLEN 12
+#define RACFG_MAGIC_NO 0x18142880
+
+#define RACFG_CMD_TYPE_MASK GENMASK(14, 0)
+#define RACFG_CMD_TYPE_ETHREQ BIT(3)
+#define RACFG_CMD_TYPE_PLATFORM_MODULE GENMASK(4, 3)
+
+#define atenl_info(fmt, ...) printf(fmt, __VA_ARGS__)
+#define atenl_err(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
+#ifdef CONFIG_ATENL_DEBUG
+#define atenl_dbg(fmt, ...) atenl_info(fmt, __VA_ARGS__)
+#else
+#define atenl_dbg(fmt, ...)
+#endif
+
+#define set_band_val(_an, _band, _field, _val) \
+ _an->anb[_band]._field = (_val)
+#define get_band_val(_an, _band, _field) \
+ (_an->anb[_band]._field)
+
+enum atenl_rf_mode {
+ ATENL_RF_MODE_NORMAL,
+ ATENL_RF_MODE_TEST,
+ ATENL_RF_MODE_ICAP,
+ ATENL_RF_MODE_ICAP_OVERLAP,
+
+ __ATENL_RF_MODE_MAX,
+};
+
+struct atenl_rx_stat {
+ u64 total;
+ u64 ok_cnt;
+ u64 err_cnt;
+ u64 len_mismatch;
+};
+
+struct atenl_band {
+ bool valid;
+ u8 phy_idx;
+ u8 cap;
+ u8 chainmask;
+
+ enum mt76_testmode_state cur_state;
+ s8 tx_power;
+ enum atenl_rf_mode rf_mode;
+
+ bool use_tx_time;
+
+ bool reset_tx_cnt;
+ bool reset_rx_cnt;
+
+ /* history */
+ struct atenl_rx_stat rx_stat;
+};
+
+#define MAX_BAND_NUM 4
+
+struct atenl {
+ struct atenl_band anb[MAX_BAND_NUM];
+ u16 chip_id;
+
+ u8 cur_band;
+
+ u8 mac_addr[ETH_ALEN];
+ bool unicast;
+ int sock_eth;
+ int pipefd[2];
+ int child_pid;
+
+ const char *mtd_part;
+ u32 mtd_offset;
+ u8 *eeprom_data;
+ int eeprom_fd;
+ u16 eeprom_size;
+ bool eeprom_exist;
+
+ bool cmd_mode;
+};
+
+struct atenl_cmd_hdr {
+ __be32 magic_no;
+ __be16 cmd_type;
+ __be16 cmd_id;
+ __be16 len;
+ __be16 seq;
+ u8 data[2048];
+} __attribute__((packed));
+
+enum atenl_cmd {
+ HQA_CMD_UNKNOWN,
+ HQA_CMD_LEGACY, /* legacy or deprecated */
+
+ HQA_CMD_OPEN_ADAPTER,
+ HQA_CMD_CLOSE_ADAPTER,
+ HQA_CMD_GET_CHIP_ID,
+ HQA_CMD_GET_SUB_CHIP_ID,
+ HQA_CMD_SET_TX_BW,
+ HQA_CMD_SET_TX_PKT_BW,
+ HQA_CMD_SET_TX_PRI_BW,
+ HQA_CMD_GET_TX_INFO,
+ HQA_CMD_SET_TX_PATH,
+ HQA_CMD_SET_TX_POWER,
+ HQA_CMD_SET_TX_POWER_MANUAL,
+ HQA_CMD_SET_RF_MODE,
+ HQA_CMD_SET_RX_PATH,
+ HQA_CMD_SET_RX_PKT_LEN,
+ HQA_CMD_SET_FREQ_OFFSET,
+ HQA_CMD_SET_TSSI,
+ HQA_CMD_SET_CFG,
+ HQA_CMD_SET_RU,
+ HQA_CMD_SET_BAND,
+ HQA_CMD_READ_MAC_BBP_REG,
+ HQA_CMD_READ_RF_REG,
+ HQA_CMD_READ_EEPROM_BULK,
+ HQA_CMD_READ_TEMPERATURE,
+ HQA_CMD_WRITE_MAC_BBP_REG,
+ HQA_CMD_WRITE_RF_REG,
+ HQA_CMD_WRITE_EEPROM_BULK,
+ HQA_CMD_WRITE_BUFFER_DONE,
+ HQA_CMD_GET_BAND,
+ HQA_CMD_GET_CFG,
+ HQA_CMD_GET_TX_POWER,
+ HQA_CMD_GET_TX_TONE_POWER,
+ HQA_CMD_GET_EFUSE_FREE_BLOCK,
+ HQA_CMD_GET_FREQ_OFFSET,
+ HQA_CMD_GET_FW_INFO,
+ HQA_CMD_GET_RX_INFO,
+ HQA_CMD_GET_RF_CAP,
+ HQA_CMD_CHECK_EFUSE_MODE,
+ HQA_CMD_CHECK_EFUSE_MODE_TYPE,
+ HQA_CMD_CHECK_EFUSE_MODE_NATIVE,
+ HQA_CMD_ANT_SWAP_CAP,
+ HQA_CMD_RESET_TX_RX_COUNTER,
+ HQA_CMD_CONTINUOUS_TX,
+
+ HQA_CMD_EXT,
+ HQA_CMD_ERR,
+
+ __HQA_CMD_MAX_NUM,
+};
+
+enum atenl_ext_cmd {
+ HQA_EXT_CMD_UNSPEC,
+
+ HQA_EXT_CMD_SET_CHANNEL,
+ HQA_EXT_CMD_SET_TX,
+ HQA_EXT_CMD_START_TX,
+ HQA_EXT_CMD_START_RX,
+ HQA_EXT_CMD_STOP_TX,
+ HQA_EXT_CMD_STOP_RX,
+ HQA_EXT_CMD_SET_TX_TIME_OPT,
+
+ HQA_EXT_CMD_OFF_CH_SCAN,
+
+ HQA_EXT_CMD_IBF_SET_VAL,
+ HQA_EXT_CMD_IBF_GET_STATUS,
+ HQA_EXT_CMD_IBF_PROF_UPDATE_ALL,
+
+ HQA_EXT_CMD_ERR,
+
+ __HQA_EXT_CMD_MAX_NUM,
+};
+
+struct atenl_data {
+ u8 buf[RACFG_PKT_MAX_SIZE];
+ int len;
+ enum atenl_cmd cmd;
+ u32 ext_id;
+ enum atenl_ext_cmd ext_cmd;
+};
+
+struct atenl_cmd_ops {
+ u16 resp_len;
+ int (*ops)(struct atenl *an, struct atenl_data *data);
+};
+
+static inline struct atenl_cmd_hdr * atenl_hdr(struct atenl_data *data)
+{
+ u8 *hqa_data = (u8 *)data->buf + ETH_HLEN;
+
+ return (struct atenl_cmd_hdr *)hqa_data;
+}
+
+static inline void
+atenl_dbg_print_data(struct atenl_data *data, const char *func_name, u32 len)
+{
+#ifdef CONFIG_ATENL_DEBUG_VERBOSE
+ u32 *tmp = (u32 *)data->buf;
+ int i;
+
+ for (i = 0; i < DIV_ROUND_UP(len, 4); i++)
+ atenl_dbg("%s: [%d] = 0x%08x\n", func_name, i, tmp[i]);
+#endif
+}
+
+enum atenl_phy_type {
+ ATENL_PHY_TYPE_CCK,
+ ATENL_PHY_TYPE_OFDM,
+ ATENL_PHY_TYPE_HT,
+ ATENL_PHY_TYPE_HT_GF,
+ ATENL_PHY_TYPE_VHT,
+ ATENL_PHY_TYPE_HE_SU = 8,
+ ATENL_PHY_TYPE_HE_EXT_SU,
+ ATENL_PHY_TYPE_HE_TB,
+ ATENL_PHY_TYPE_HE_MU,
+};
+
+enum atenl_e2p_mode {
+ E2P_EFUSE_MODE = 1,
+ E2P_FLASH_MODE,
+ E2P_EEPROM_MODE,
+ E2P_BIN_MODE,
+};
+
+enum atenl_band_type {
+ BAND_TYPE_UNUSE,
+ BAND_TYPE_2G,
+ BAND_TYPE_5G,
+ BAND_TYPE_2G_5G,
+ BAND_TYPE_6G,
+ BAND_TYPE_2G_6G,
+ BAND_TYPE_5G_6G,
+ BAND_TYPE_2G_5G_6G,
+};
+
+enum atenl_ch_band {
+ CH_BAND_2GHZ,
+ CH_BAND_5GHZ,
+ CH_BAND_6GHZ,
+};
+
+/* for mt7915 */
+enum {
+ MT_EE_BAND_SEL_DEFAULT,
+ MT_EE_BAND_SEL_5GHZ,
+ MT_EE_BAND_SEL_2GHZ,
+ MT_EE_BAND_SEL_DUAL,
+};
+
+/* for mt7916/mt7986 */
+enum {
+ MT_EE_BAND_SEL_2G,
+ MT_EE_BAND_SEL_5G,
+ MT_EE_BAND_SEL_6G,
+ MT_EE_BAND_SEL_5G_6G,
+};
+
+#define MT_EE_WIFI_CONF 0x190
+#define MT_EE_WIFI_CONF0_BAND_SEL GENMASK(7, 6)
+
+enum {
+ MT7976_ONE_ADIE_DBDC = 0x7,
+ MT7975_ONE_ADIE_SINGLE_BAND = 0x8, /* AX7800 */
+ MT7976_ONE_ADIE_SINGLE_BAND = 0xa, /* AX7800 */
+ MT7975_DUAL_ADIE_DBDC = 0xd, /* AX6000 */
+ MT7976_DUAL_ADIE_DBDC = 0xf, /* AX6000 */
+};
+
+enum {
+ TEST_CBW_20MHZ,
+ TEST_CBW_40MHZ,
+ TEST_CBW_80MHZ,
+ TEST_CBW_10MHZ,
+ TEST_CBW_5MHZ,
+ TEST_CBW_160MHZ,
+ TEST_CBW_8080MHZ,
+
+ TEST_CBW_MAX = TEST_CBW_8080MHZ - 1,
+};
+
+struct atenl_rx_info_hdr {
+ __be32 type;
+ __be32 ver;
+ __be32 val;
+ __be32 len;
+} __attribute__((packed));
+
+struct atenl_rx_info_band {
+ __be32 mac_rx_fcs_err_cnt;
+ __be32 mac_rx_mdrdy_cnt;
+ __be32 mac_rx_len_mismatch;
+ __be32 mac_rx_fcs_ok_cnt;
+ __be32 phy_rx_fcs_err_cnt_cck;
+ __be32 phy_rx_fcs_err_cnt_ofdm;
+ __be32 phy_rx_pd_cck;
+ __be32 phy_rx_pd_ofdm;
+ __be32 phy_rx_sig_err_cck;
+ __be32 phy_rx_sfd_err_cck;
+ __be32 phy_rx_sig_err_ofdm;
+ __be32 phy_rx_tag_err_ofdm;
+ __be32 phy_rx_mdrdy_cnt_cck;
+ __be32 phy_rx_mdrdy_cnt_ofdm;
+} __attribute__((packed));
+
+struct atenl_rx_info_path {
+ __be32 rcpi;
+ __be32 rssi;
+ __be32 fagc_ib_rssi;
+ __be32 fagc_wb_rssi;
+ __be32 inst_ib_rssi;
+ __be32 inst_wb_rssi;
+} __attribute__((packed));
+
+struct atenl_rx_info_user {
+ __be32 freq_offset;
+ __be32 snr;
+ __be32 fcs_error_cnt;
+} __attribute__((packed));
+
+struct atenl_rx_info_comm {
+ __be32 rx_fifo_full;
+ __be32 aci_hit_low;
+ __be32 aci_hit_high;
+ __be32 mu_pkt_count;
+ __be32 sig_mcs;
+ __be32 sinr;
+ __be32 driver_rx_count;
+} __attribute__((packed));
+
+enum atenl_ibf_action {
+ TXBF_ACT_INIT = 1,
+ TXBF_ACT_CHANNEL,
+ TXBF_ACT_MCS,
+ TXBF_ACT_POWER,
+ TXBF_ACT_TX_ANT,
+ TXBF_ACT_RX_START,
+ TXBF_ACT_RX_ANT,
+ TXBF_ACT_LNA_GAIN,
+ TXBF_ACT_IBF_PHASE_COMP,
+ TXBF_ACT_TX_PKT,
+ TXBF_ACT_IBF_PROF_UPDATE,
+ TXBF_ACT_EBF_PROF_UPDATE,
+ TXBF_ACT_IBF_PHASE_CAL,
+ TXBF_ACT_IBF_PHASE_E2P_UPDATE = 16,
+};
+
+static inline bool is_mt7915(struct atenl *an)
+{
+ return an->chip_id == 0x7915;
+}
+
+static inline bool is_mt7916(struct atenl *an)
+{
+ return (an->chip_id == 0x7916) || (an->chip_id == 0x7906);
+}
+
+static inline bool is_mt7986(struct atenl *an)
+{
+ return an->chip_id == 0x7986;
+}
+
+int atenl_eth_init(struct atenl *an);
+int atenl_eth_recv(struct atenl *an, struct atenl_data *data);
+int atenl_eth_send(struct atenl *an, struct atenl_data *data);
+int atenl_hqa_recv(struct atenl *an, struct atenl_data *data);
+int atenl_hqa_proc_cmd(struct atenl *an, struct atenl_data *data);
+int atenl_nl_process(struct atenl *an, struct atenl_data *data);
+int atenl_nl_process_many(struct atenl *an, struct atenl_data *data);
+int atenl_nl_check_mtd(struct atenl *an);
+int atenl_nl_write_eeprom(struct atenl *an, u32 offset, u8 *val, int len);
+int atenl_nl_write_efuse_all(struct atenl *an, struct atenl_data *data);
+int atenl_nl_update_buffer_mode(struct atenl *an);
+int atenl_nl_set_state(struct atenl *an, u8 band,
+ enum mt76_testmode_state state);
+int atenl_eeprom_init(struct atenl *an, u8 phy_idx);
+void atenl_eeprom_close(struct atenl *an);
+int atenl_eeprom_write_mtd(struct atenl *an);
+int atenl_eeprom_read_from_driver(struct atenl *an, u32 offset, int len);
+void atenl_eeprom_cmd_handler(struct atenl *an, u8 phy_idx, char *cmd);
+u16 atenl_get_center_channel(u8 bw, u8 ch_band, u16 ctrl_ch);
+int atenl_reg_read(struct atenl *an, u32 offset, u32 *res);
+int atenl_reg_write(struct atenl *an, u32 offset, u32 val);
+
+#endif
diff --git a/feed/atenl/src/eeprom.c b/feed/atenl/src/eeprom.c
new file mode 100644
index 0000000..2a46971
--- /dev/null
+++ b/feed/atenl/src/eeprom.c
@@ -0,0 +1,456 @@
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include "atenl.h"
+
+#define EEPROM_PART_SIZE 20480
+#define EEPROM_FILE "/tmp/atenl-eeprom"
+
+static FILE *mtd_open(const char *mtd)
+{
+ char line[128], name[64];
+ FILE *fp;
+ int i;
+
+ fp = fopen("/proc/mtd", "r");
+ if (!fp)
+ return NULL;
+
+ snprintf(name, sizeof(name), "\"%s\"", mtd);
+ while (fgets(line, sizeof(line), fp)) {
+ if (!sscanf(line, "mtd%d:", &i) || !strstr(line, name))
+ continue;
+
+ snprintf(line, sizeof(line), "/dev/mtd%d", i);
+ fclose(fp);
+ return fopen(line, "r");
+ }
+ fclose(fp);
+
+ return NULL;
+}
+
+static int
+atenl_flash_create_file(struct atenl *an)
+{
+ char buf[1024];
+ ssize_t len;
+ FILE *f;
+ int fd, ret;
+
+ f = mtd_open(an->mtd_part);
+ if (!f) {
+ fprintf(stderr, "Failed to open MTD device\n");
+ return -1;
+ }
+
+ fd = open(EEPROM_FILE, O_RDWR | O_CREAT | O_EXCL, 00644);
+ if (fd < 0)
+ goto out;
+
+ while ((len = fread(buf, 1, sizeof(buf), f)) > 0) {
+ ssize_t w;
+
+retry:
+ w = write(fd, buf, len);
+ if (w > 0)
+ continue;
+
+ if (errno == EINTR)
+ goto retry;
+
+ perror("write");
+ unlink(EEPROM_FILE);
+ close(fd);
+ fd = -1;
+ goto out;
+ }
+
+ ret = lseek(fd, 0, SEEK_SET);
+ if (ret) {
+ fclose(f);
+ close(fd);
+ return ret;
+ }
+
+out:
+ fclose(f);
+ return fd;
+}
+
+static int
+atenl_efuse_create_file(struct atenl *an)
+{
+ char fname[64], buf[1024];
+ ssize_t len;
+ int fd_ori, fd, ret;
+
+ snprintf(fname, sizeof(fname),
+ "/sys/kernel/debug/ieee80211/phy%d/mt76/eeprom", get_band_val(an, 0, phy_idx));
+ fd_ori = open(fname, O_RDONLY);
+ if (fd_ori < 0)
+ return -1;
+
+ fd = open(EEPROM_FILE, O_RDWR | O_CREAT | O_EXCL, 00644);
+ if (fd < 0)
+ goto out;
+
+ while ((len = read(fd_ori, buf, sizeof(buf))) > 0) {
+ ssize_t w;
+
+retry:
+ w = write(fd, buf, len);
+ if (w > 0)
+ continue;
+
+ if (errno == EINTR)
+ goto retry;
+
+ perror("write");
+ unlink(EEPROM_FILE);
+ close(fd);
+ fd = -1;
+ goto out;
+ }
+
+ ret = lseek(fd, 0, SEEK_SET);
+ if (ret) {
+ close(fd_ori);
+ close(fd);
+ return ret;
+ }
+
+out:
+ close(fd_ori);
+ return fd;
+}
+
+static bool
+atenl_eeprom_file_exists(void)
+{
+ struct stat st;
+
+ return stat(EEPROM_FILE, &st) == 0;
+}
+
+static int
+atenl_eeprom_init_file(struct atenl *an, bool flash_mode)
+{
+ int fd;
+
+ if (!atenl_eeprom_file_exists()) {
+ if (flash_mode)
+ atenl_dbg("[%d]%s: init eeprom with flash mode\n", getpid(), __func__);
+ else
+ atenl_dbg("[%d]%s: init eeprom with efuse mode\n", getpid(), __func__);
+
+ if (flash_mode)
+ return atenl_flash_create_file(an);
+
+ return atenl_efuse_create_file(an);
+ }
+
+ fd = open(EEPROM_FILE, O_RDWR);
+ if (fd < 0)
+ perror("open");
+
+ an->eeprom_exist = true;
+
+ return fd;
+}
+
+static void
+atenl_eeprom_init_max_size(struct atenl *an)
+{
+ switch (an->chip_id) {
+ case 0x7915:
+ an->eeprom_size = 3584;
+ break;
+ case 0x7906:
+ case 0x7916:
+ case 0x7986:
+ an->eeprom_size = 4096;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+atenl_eeprom_init_band_cap(struct atenl *an)
+{
+ u8 *eeprom = an->eeprom_data;
+
+ if (is_mt7915(an)) {
+ u8 val = eeprom[MT_EE_WIFI_CONF];
+ u8 band_sel = FIELD_GET(MT_EE_WIFI_CONF0_BAND_SEL, val);
+ struct atenl_band *anb = &an->anb[0];
+
+ /* MT7915A */
+ if (band_sel == MT_EE_BAND_SEL_DEFAULT) {
+ anb->valid = true;
+ anb->cap = BAND_TYPE_2G_5G;
+ return;
+ }
+
+ /* MT7915D */
+ if (band_sel == MT_EE_BAND_SEL_2GHZ) {
+ anb->valid = true;
+ anb->cap = BAND_TYPE_2G;
+ }
+
+ val = eeprom[MT_EE_WIFI_CONF + 1];
+ band_sel = FIELD_GET(MT_EE_WIFI_CONF0_BAND_SEL, val);
+ anb++;
+
+ if (band_sel == MT_EE_BAND_SEL_5GHZ) {
+ anb->valid = true;
+ anb->cap = BAND_TYPE_5G;
+ }
+ } else if (is_mt7916(an) || is_mt7986(an)) {
+ struct atenl_band *anb;
+ u8 val, band_sel;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ val = eeprom[MT_EE_WIFI_CONF + i];
+ band_sel = FIELD_GET(MT_EE_WIFI_CONF0_BAND_SEL, val);
+ anb = &an->anb[i];
+
+ anb->valid = true;
+ switch (band_sel) {
+ case MT_EE_BAND_SEL_2G:
+ anb->cap = BAND_TYPE_2G;
+ break;
+ case MT_EE_BAND_SEL_5G:
+ anb->cap = BAND_TYPE_5G;
+ break;
+ case MT_EE_BAND_SEL_6G:
+ anb->cap = BAND_TYPE_6G;
+ break;
+ case MT_EE_BAND_SEL_5G_6G:
+ anb->cap = BAND_TYPE_5G_6G;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
+static void
+atenl_eeprom_init_antenna_cap(struct atenl *an)
+{
+ if (is_mt7915(an)) {
+ if (an->anb[0].cap == BAND_TYPE_2G_5G)
+ an->anb[0].chainmask = 0xf;
+ else {
+ an->anb[0].chainmask = 0x3;
+ an->anb[1].chainmask = 0xc;
+ }
+ } else if (is_mt7916(an)) {
+ an->anb[0].chainmask = 0x3;
+ an->anb[1].chainmask = 0x3;
+ } else if (is_mt7986(an)) {
+ an->anb[0].chainmask = 0xf;
+ an->anb[1].chainmask = 0xf;
+ }
+}
+
+int atenl_eeprom_init(struct atenl *an, u8 phy_idx)
+{
+ bool flash_mode;
+ int eeprom_fd;
+
+ set_band_val(an, 0, phy_idx, phy_idx);
+
+ atenl_nl_check_mtd(an);
+ flash_mode = an->mtd_part != NULL;
+
+ eeprom_fd = atenl_eeprom_init_file(an, flash_mode);
+ if (eeprom_fd < 0)
+ return -1;
+
+ an->eeprom_data = mmap(NULL, EEPROM_PART_SIZE, PROT_READ | PROT_WRITE,
+ MAP_SHARED, eeprom_fd, an->mtd_offset);
+ if (!an->eeprom_data) {
+ perror("mmap");
+ close(eeprom_fd);
+ return -1;
+ }
+
+ an->eeprom_fd = eeprom_fd;
+ an->chip_id = *(u16 *)an->eeprom_data;
+ atenl_eeprom_init_max_size(an);
+ atenl_eeprom_init_band_cap(an);
+ atenl_eeprom_init_antenna_cap(an);
+
+ if (get_band_val(an, 1, valid))
+ set_band_val(an, 1, phy_idx, phy_idx + 1);
+
+ return 0;
+}
+
+void atenl_eeprom_close(struct atenl *an)
+{
+ msync(an->eeprom_data, EEPROM_PART_SIZE, MS_SYNC);
+ munmap(an->eeprom_data, EEPROM_PART_SIZE);
+ close(an->eeprom_fd);
+
+ if (!an->eeprom_exist && (an->child_pid || an->cmd_mode))
+ if (remove(EEPROM_FILE))
+ perror("remove");
+}
+
+int atenl_eeprom_write_mtd(struct atenl *an)
+{
+ bool flash_mode = an->mtd_part != NULL;
+ pid_t pid;
+
+ if (!flash_mode)
+ return 0;
+
+ pid = fork();
+ if (pid < 0) {
+ perror("Fork");
+ return EXIT_FAILURE;
+ } else if (pid == 0) {
+ char *part = strdup(an->mtd_part);
+ char *cmd[] = {"mtd", "write", EEPROM_FILE, part, NULL};
+ int ret;
+
+ ret = execvp("mtd", cmd);
+ if (ret < 0) {
+ fprintf(stderr, "%s: execl error\n", __func__);
+ exit(0);
+ }
+ } else {
+ wait(&pid);
+ }
+
+ return 0;
+}
+
+/* Directly read some value from driver's eeprom.
+ * It's usally used to get calibrated data from driver.
+ */
+int atenl_eeprom_read_from_driver(struct atenl *an, u32 offset, int len)
+{
+ u8 *eeprom_data = an->eeprom_data + offset;
+ char fname[64], buf[1024];
+ int fd_ori, ret;
+ ssize_t rd;
+
+ snprintf(fname, sizeof(fname),
+ "/sys/kernel/debug/ieee80211/phy%d/mt76/eeprom",
+ get_band_val(an, 0, phy_idx));
+ fd_ori = open(fname, O_RDONLY);
+ if (fd_ori < 0)
+ return -1;
+
+ ret = lseek(fd_ori, offset, SEEK_SET);
+ if (ret < 0)
+ goto out;
+
+ while ((rd = read(fd_ori, buf, sizeof(buf))) > 0 && len) {
+ if (len < rd) {
+ memcpy(eeprom_data, buf, len);
+ break;
+ } else {
+ memcpy(eeprom_data, buf, rd);
+ eeprom_data += rd;
+ len -= rd;
+ }
+ }
+
+ ret = 0;
+out:
+ close(fd_ori);
+ return ret;
+}
+
+/* Update all eeprom values to driver before writing efuse */
+static void
+atenl_eeprom_sync_to_driver(struct atenl *an)
+{
+ int i;
+
+ for (i = 0; i < 3584; i += 16)
+ atenl_nl_write_eeprom(an, i, &an->eeprom_data[i], 16);
+}
+
+void atenl_eeprom_cmd_handler(struct atenl *an, u8 phy_idx, char *cmd)
+{
+ bool flash_mode;
+
+ an->cmd_mode = true;
+
+ atenl_eeprom_init(an, phy_idx);
+ flash_mode = an->mtd_part != NULL;
+
+ if (!strncmp(cmd, "sync eeprom all", 15)) {
+ atenl_eeprom_write_mtd(an);
+ } else if (!strncmp(cmd, "eeprom", 6)) {
+ char *s = strchr(cmd, ' ');
+
+ if (!s) {
+ fprintf(stderr, "eeprom: please type a correct command\n");
+ return;
+ }
+
+ s++;
+ if (!strncmp(s, "reset", 5)) {
+ unlink(EEPROM_FILE);
+ } else if (!strncmp(s, "file", 4)) {
+ atenl_info("%s\n", EEPROM_FILE);
+ atenl_info("Flash mode: %d\n", flash_mode);
+ } else if (!strncmp(s, "set", 3)) {
+ u32 offset, val;
+
+ s = strchr(s, ' ');
+ if (!s)
+ return;
+ s++;
+
+ if (!sscanf(s, "%x=%x", &offset, &val) ||
+ offset > EEPROM_PART_SIZE)
+ return;
+
+ an->eeprom_data[offset] = val;
+ atenl_info("set offset 0x%x to 0x%x\n", offset, val);
+ } else if (!strncmp(s, "update", 6)) {
+ atenl_nl_update_buffer_mode(an);
+ } else if (!strncmp(s, "write", 5)) {
+ s = strchr(s, ' ');
+ if (!s)
+ return;
+ s++;
+
+ if (!strncmp(s, "flash", 5)) {
+ atenl_eeprom_write_mtd(an);
+ } else if (!strncmp(s, "efuse", 5)) {
+ atenl_eeprom_sync_to_driver(an);
+ atenl_nl_write_efuse_all(an, NULL);
+ }
+ } else if (!strncmp(s, "read", 4)) {
+ u32 offset;
+
+ s = strchr(s, ' ');
+ if (!s)
+ return;
+ s++;
+
+ if (!sscanf(s, "%x", &offset) ||
+ offset > EEPROM_PART_SIZE)
+ return;
+
+ atenl_info("val = 0x%x (%u)\n", an->eeprom_data[offset],
+ an->eeprom_data[offset]);
+ }
+ } else {
+ atenl_err("Unknown command: %s\n", cmd);
+ }
+}
diff --git a/feed/atenl/src/eth.c b/feed/atenl/src/eth.c
new file mode 100644
index 0000000..a58add3
--- /dev/null
+++ b/feed/atenl/src/eth.c
@@ -0,0 +1,109 @@
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include "atenl.h"
+
+int atenl_eth_init(struct atenl *an)
+{
+ struct sockaddr_ll addr = {};
+ struct ifreq ifr = {};
+ int ret;
+
+ memcpy(ifr.ifr_name, BRIDGE_NAME, strlen(BRIDGE_NAME));
+ ret = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_RACFG));
+ if (ret < 0) {
+ perror("socket");
+ goto out;
+ }
+ an->sock_eth = ret;
+
+ addr.sll_family = AF_PACKET;
+ addr.sll_ifindex = if_nametoindex(BRIDGE_NAME);
+
+ ret = bind(an->sock_eth, (struct sockaddr *)&addr, sizeof(addr));
+ if (ret < 0) {
+ perror("bind");
+ goto out;
+ }
+
+ ret = ioctl(an->sock_eth, SIOCGIFHWADDR, &ifr);
+ if (ret < 0) {
+ perror("ioctl(SIOCGIFHWADDR)");
+ goto out;
+ }
+
+ memcpy(an->mac_addr, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
+ atenl_info("Open Ethernet socket success on %s, mac addr = " MACSTR "\n",
+ BRIDGE_NAME, MAC2STR(an->mac_addr));
+
+ ret = 0;
+out:
+ return ret;
+}
+
+int atenl_eth_recv(struct atenl *an, struct atenl_data *data)
+{
+ char buf[RACFG_PKT_MAX_SIZE];
+ int len = recvfrom(an->sock_eth, buf, sizeof(buf), 0, NULL, NULL);
+ struct ethhdr *hdr = (struct ethhdr *)buf;
+
+ atenl_dbg("[%d]%s: recv len = %d\n", getpid(), __func__, len);
+
+ if (len >= ETH_HLEN + RACFG_HLEN) {
+ if (hdr->h_proto == htons(ETH_P_RACFG) &&
+ (ether_addr_equal(an->mac_addr, hdr->h_dest) ||
+ is_broadcast_ether_addr(hdr->h_dest))) {
+ data->len = len;
+ memcpy(data->buf, buf, len);
+
+ return 0;
+ }
+ }
+
+ atenl_err("%s: packet len is too short\n", __func__);
+ return -EINVAL;
+}
+
+int atenl_eth_send(struct atenl *an, struct atenl_data *data)
+{
+ struct ethhdr *ehdr = (struct ethhdr *)data->buf;
+ struct sockaddr_ll addr = {};
+ int ret, len = data->len;
+
+ if (an->unicast)
+ ether_addr_copy(ehdr->h_dest, ehdr->h_source);
+ else
+ eth_broadcast_addr(ehdr->h_dest);
+
+ ether_addr_copy(ehdr->h_source, an->mac_addr);
+ ehdr->h_proto = htons(ETH_P_RACFG);
+
+ if (len < 60)
+ len = 60;
+ else if (len > 1514) {
+ atenl_err("%s: response ethernet length is too long\n", __func__);
+ return -1;
+ }
+
+ atenl_dbg_print_data(data, __func__, len);
+
+ addr.sll_family = PF_PACKET;
+ addr.sll_protocol = htons(ETH_P_RACFG);
+ addr.sll_ifindex = if_nametoindex(BRIDGE_NAME);
+ addr.sll_pkttype = PACKET_BROADCAST;
+ addr.sll_hatype = ARPHRD_ETHER;
+ addr.sll_halen = ETH_ALEN;
+ memset(addr.sll_addr, 0, 8);
+ eth_broadcast_addr(addr.sll_addr);
+
+ ret = sendto(an->sock_eth, data->buf, len, 0,
+ (struct sockaddr *)&addr, sizeof(addr));
+ if (ret < 0) {
+ perror("sendto");
+ return ret;
+ }
+
+ atenl_dbg("[%d]%s: send length = %d\n", getpid(), __func__, len);
+
+ return 0;
+}
diff --git a/feed/atenl/src/hqa.c b/feed/atenl/src/hqa.c
new file mode 100644
index 0000000..24a6e9b
--- /dev/null
+++ b/feed/atenl/src/hqa.c
@@ -0,0 +1,1034 @@
+/* Copyright (C) 2021-2022 Mediatek Inc. */
+#include "atenl.h"
+
+#define CHAN(_ch, _freq, _ch_80, _ch_160, ...) { \
+ .ch = _ch, \
+ .freq = _freq, \
+ .ch_80 = _ch_80, \
+ .ch_160 = _ch_160, \
+ __VA_ARGS__ \
+}
+
+struct atenl_channel {
+ /* ctrl ch */
+ u16 ch;
+ u16 freq;
+ /* center ch */
+ u16 ch_80;
+ u16 ch_160;
+ /* only use for channels that don't have 80 but has 40 */
+ u16 ch_40;
+};
+
+static const struct atenl_channel atenl_channels_5ghz[] = {
+ CHAN(8, 5040, 0, 0),
+ CHAN(12, 5060, 0, 0),
+ CHAN(16, 5080, 0, 0),
+
+ CHAN(36, 5180, 42, 50),
+ CHAN(40, 5200, 42, 50),
+ CHAN(44, 5220, 42, 50),
+ CHAN(48, 5240, 42, 50),
+
+ CHAN(52, 5260, 58, 50),
+ CHAN(56, 5280, 58, 50),
+ CHAN(60, 5300, 58, 50),
+ CHAN(64, 5320, 58, 50),
+
+ CHAN(68, 5340, 0, 0),
+ CHAN(80, 5400, 0, 0),
+ CHAN(84, 5420, 0, 0),
+ CHAN(88, 5440, 0, 0),
+ CHAN(92, 5460, 0, 0),
+ CHAN(96, 5480, 0, 0),
+
+ CHAN(100, 5500, 106, 114),
+ CHAN(104, 5520, 106, 114),
+ CHAN(108, 5540, 106, 114),
+ CHAN(112, 5560, 106, 114),
+ CHAN(116, 5580, 122, 114),
+ CHAN(120, 5600, 122, 114),
+ CHAN(124, 5620, 122, 114),
+ CHAN(128, 5640, 122, 114),
+
+ CHAN(132, 5660, 138, 0),
+ CHAN(136, 5680, 138, 0),
+ CHAN(140, 5700, 138, 0),
+ CHAN(144, 5720, 138, 0),
+
+ CHAN(149, 5745, 155, 0),
+ CHAN(153, 5765, 155, 0),
+ CHAN(157, 5785, 155, 0),
+ CHAN(161, 5805, 155, 0),
+ CHAN(165, 5825, 0, 0, .ch_40 = 167),
+ CHAN(169, 5845, 0, 0, .ch_40 = 167),
+ CHAN(173, 5865, 0, 0),
+
+ CHAN(184, 4920, 0, 0),
+ CHAN(188, 4940, 0, 0),
+ CHAN(192, 4960, 0, 0),
+ CHAN(196, 4980, 0, 0),
+};
+
+static const struct atenl_channel atenl_channels_6ghz[] = {
+ /* UNII-5 */
+ CHAN(1, 5955, 7, 15),
+ CHAN(5, 5975, 7, 15),
+ CHAN(9, 5995, 7, 15),
+ CHAN(13, 6015, 7, 15),
+ CHAN(17, 6035, 23, 15),
+ CHAN(21, 6055, 23, 15),
+ CHAN(25, 6075, 23, 15),
+ CHAN(29, 6095, 23, 15),
+ CHAN(33, 6115, 39, 47),
+ CHAN(37, 6135, 39, 47),
+ CHAN(41, 6155, 39, 47),
+ CHAN(45, 6175, 39, 47),
+ CHAN(49, 6195, 55, 47),
+ CHAN(53, 6215, 55, 47),
+ CHAN(57, 6235, 55, 47),
+ CHAN(61, 6255, 55, 47),
+ CHAN(65, 6275, 71, 79),
+ CHAN(69, 6295, 71, 79),
+ CHAN(73, 6315, 71, 79),
+ CHAN(77, 6335, 71, 79),
+ CHAN(81, 6355, 87, 79),
+ CHAN(85, 6375, 87, 79),
+ CHAN(89, 6395, 87, 79),
+ CHAN(93, 6415, 87, 79),
+ /* UNII-6 */
+ CHAN(97, 6435, 103, 111),
+ CHAN(101, 6455, 103, 111),
+ CHAN(105, 6475, 103, 111),
+ CHAN(109, 6495, 103, 111),
+ CHAN(113, 6515, 119, 111),
+ CHAN(117, 6535, 119, 111),
+ /* UNII-7 */
+ CHAN(121, 6555, 119, 111),
+ CHAN(125, 6575, 119, 111),
+ CHAN(129, 6595, 135, 143),
+ CHAN(133, 6615, 135, 143),
+ CHAN(137, 6635, 135, 143),
+ CHAN(141, 6655, 135, 143),
+ CHAN(145, 6675, 151, 143),
+ CHAN(149, 6695, 151, 143),
+ CHAN(153, 6715, 151, 143),
+ CHAN(157, 6735, 151, 143),
+ CHAN(161, 6755, 167, 175),
+ CHAN(165, 6775, 167, 175),
+ CHAN(169, 6795, 167, 175),
+ CHAN(173, 6815, 167, 175),
+ CHAN(177, 6835, 183, 175),
+ CHAN(181, 6855, 183, 175),
+ CHAN(185, 6875, 183, 175),
+ /* UNII-8 */
+ CHAN(189, 6895, 183, 175),
+ CHAN(193, 6915, 199, 207),
+ CHAN(197, 6935, 199, 207),
+ CHAN(201, 6955, 199, 207),
+ CHAN(205, 6975, 199, 207),
+ CHAN(209, 6995, 215, 207),
+ CHAN(213, 7015, 215, 207),
+ CHAN(217, 7035, 215, 207),
+ CHAN(221, 7055, 215, 207),
+ CHAN(225, 7075, 0, 0, .ch_40 = 227),
+ CHAN(229, 7095, 0, 0, .ch_40 = 227),
+ CHAN(233, 7115, 0, 0),
+};
+
+static int
+atenl_hqa_adapter(struct atenl *an, struct atenl_data *data)
+{
+ char cmd[64];
+ u8 i;
+
+ for (i = 0; i < MAX_BAND_NUM; i++) {
+ u8 phy = get_band_val(an, i, phy_idx);
+
+ if (!get_band_val(an, i, valid))
+ continue;
+
+ if (data->cmd == HQA_CMD_OPEN_ADAPTER) {
+ sprintf(cmd, "iw phy phy%u interface add mon%u type monitor", phy, phy);
+ system(cmd);
+ sprintf(cmd, "iw dev wlan%u del", phy);
+ system(cmd);
+ sprintf(cmd, "ifconfig mon%u up", phy);
+ system(cmd);
+ /* set a special-defined country */
+ sprintf(cmd, "iw reg set VV");
+ system(cmd);
+ atenl_nl_set_state(an, i, MT76_TM_STATE_IDLE);
+ } else {
+ atenl_nl_set_state(an, i, MT76_TM_STATE_OFF);
+ sprintf(cmd, "iw reg set 00");
+ system(cmd);
+ sprintf(cmd, "iw dev mon%u del", phy);
+ system(cmd);
+ sprintf(cmd, "iw phy phy%u interface add wlan%u type managed", phy, phy);
+ system(cmd);
+ }
+ }
+
+ return 0;
+}
+
+static int
+atenl_hqa_set_rf_mode(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+
+ /* The testmode rf mode change applies to all bands */
+ set_band_val(an, 0, rf_mode, ntohl(*(u32 *)hdr->data));
+ set_band_val(an, 1, rf_mode, ntohl(*(u32 *)hdr->data));
+
+ return 0;
+}
+
+static int
+atenl_hqa_get_chip_id(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+
+ *(u32 *)(hdr->data + 2) = htonl(an->chip_id);
+
+ return 0;
+}
+
+static int
+atenl_hqa_get_sub_chip_id(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+
+ if (is_mt7986(an)) {
+ u32 sub_id, val;
+ int ret;
+
+ ret = atenl_reg_read(an, 0x18050000, &val);
+ if (ret)
+ return ret;
+
+ switch (val & 0xf) {
+ case MT7975_ONE_ADIE_SINGLE_BAND:
+ case MT7976_ONE_ADIE_SINGLE_BAND:
+ sub_id = htonl(0xa);
+ break;
+ case MT7976_ONE_ADIE_DBDC:
+ sub_id = htonl(0x7);
+ break;
+ case MT7975_DUAL_ADIE_DBDC:
+ case MT7976_DUAL_ADIE_DBDC:
+ default:
+ sub_id = htonl(0xf);
+ break;
+ }
+
+ memcpy(hdr->data + 2, &sub_id, 4);
+ }
+
+ return 0;
+}
+
+static int
+atenl_hqa_get_rf_cap(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ u32 band = ntohl(*(u32 *)hdr->data);
+ struct atenl_band *anb;
+
+ if (band >= MAX_BAND_NUM)
+ return -EINVAL;
+
+ anb = &an->anb[band];
+ /* fill tx and rx ant */
+ *(u32 *)(hdr->data + 2) = htonl(__builtin_popcount(anb->chainmask));
+ *(u32 *)(hdr->data + 2 + 4) = *(u32 *)(hdr->data + 2);
+
+ return 0;
+}
+
+static int
+atenl_hqa_reset_counter(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_band *anb = &an->anb[an->cur_band];
+
+ anb->reset_tx_cnt = true;
+ anb->reset_rx_cnt = true;
+
+ memset(&anb->rx_stat, 0, sizeof(anb->rx_stat));
+
+ return 0;
+}
+
+static int
+atenl_hqa_mac_bbp_reg(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ enum atenl_cmd cmd = data->cmd;
+ u32 *v = (u32 *)hdr->data;
+ u32 offset = ntohl(v[0]);
+ int ret;
+
+ if (cmd == HQA_CMD_READ_MAC_BBP_REG) {
+ u16 num = ntohs(*(u16 *)(hdr->data + 4));
+ u32 *ptr = (u32 *)(hdr->data + 2);
+ u32 res;
+ int i;
+
+ if (num > SHRT_MAX) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ hdr->len = htons(2 + num * 4);
+ for (i = 0; i < num && i < sizeof(hdr->data) / 4; i++) {
+ ret = atenl_reg_read(an, offset + i * 4, &res);
+ if (ret)
+ goto out;
+
+ res = htonl(res);
+ memcpy(ptr + i, &res, 4);
+ }
+ } else {
+ u32 val = ntohl(v[1]);
+
+ ret = atenl_reg_write(an, offset, val);
+ if (ret)
+ goto out;
+ }
+
+ ret = 0;
+out:
+ memset(hdr->data, 0, 2);
+
+ return ret;
+}
+
+static int
+atenl_hqa_eeprom_bulk(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ enum atenl_cmd cmd = data->cmd;
+
+ if (cmd == HQA_CMD_WRITE_BUFFER_DONE) {
+ u32 buf_mode = ntohl(*(u32 *)hdr->data);
+
+ switch (buf_mode) {
+ case E2P_EFUSE_MODE:
+ atenl_nl_write_efuse_all(an, data);
+ break;
+ default:
+ break;
+ }
+ } else {
+ u16 *v = (u16 *)hdr->data;
+ u16 offset = ntohs(v[0]), len = ntohs(v[1]);
+ u16 val;
+ size_t i;
+
+ if (offset >= an->eeprom_size || (len > sizeof(hdr->data) - 2))
+ return -EINVAL;
+
+ if (cmd == HQA_CMD_READ_EEPROM_BULK) {
+ hdr->len = htons(len + 2);
+ for (i = 0; i < len; i += 2) {
+ if (offset + i >= an->eeprom_size)
+ val = 0;
+ else
+ val = ntohs(*(u16 *)(an->eeprom_data + offset + i));
+ *(u16 *)(hdr->data + 2 + i) = val;
+ }
+ } else { /* write eeprom */
+ u16 offset_a;
+
+ for (i = 0; i < DIV_ROUND_UP(len, 2); i++) {
+ val = ntohs(v[i + 2]);
+ memcpy(&an->eeprom_data[offset + i * 2], &val, 2);
+ }
+
+ offset_a = offset - (offset % 16);
+ len += (offset - offset_a);
+ for (i = offset_a; i < offset_a + len; i += 16)
+ atenl_nl_write_eeprom(an, i, &an->eeprom_data[i], 16);
+
+ atenl_eeprom_write_mtd(an);
+ }
+ }
+
+ return 0;
+}
+
+static int
+atenl_hqa_get_efuse_free_block(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ u32 free_block = htonl(0x14);
+
+ /* TODO */
+ *(u32 *)(hdr->data + 2) = free_block;
+
+ return 0;
+}
+
+static int
+atenl_hqa_get_band(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ u32 band = ntohl(*(u32 *)hdr->data);
+
+ if (band >= MAX_BAND_NUM)
+ return -EINVAL;
+
+ *(u32 *)(hdr->data + 2) = htonl(an->anb[band].cap);
+
+ return 0;
+}
+
+static int
+atenl_hqa_get_tx_power(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ u32 tx_power = htonl(28);
+
+ memcpy(hdr->data + 6, &tx_power, 4);
+
+ return 0;
+}
+
+static int
+atenl_hqa_get_freq_offset(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ u32 freq_offset = htonl(10);
+
+ /* TODO */
+ memcpy(hdr->data + 2, &freq_offset, 4);
+
+ return 0;
+}
+
+static int
+atenl_hqa_get_cfg(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ u32 val = htonl(1);
+
+ /* TODO */
+ memcpy(hdr->data + 2, &val, 4);
+
+ return 0;
+}
+
+static int
+atenl_hqa_read_temperature(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ char buf[64], *str;
+ int fd, ret;
+ u32 temp;
+ u8 phy_idx = get_band_val(an, an->cur_band, phy_idx);
+
+ ret = snprintf(buf, sizeof(buf),
+ "/sys/class/ieee80211/phy%u/hwmon%u/temp1_input",
+ phy_idx, phy_idx);
+ if (snprintf_error(sizeof(buf), ret))
+ return -1;
+
+ fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ ret = read(fd, buf, sizeof(buf) - 1);
+ if (ret < 0)
+ goto out;
+ buf[ret] = 0;
+
+ str = strchr(buf, ':');
+ str += 2;
+ temp = strtol(str, NULL, 10);
+ /* unit conversion */
+ *(u32 *)(hdr->data + 2) = htonl(temp / 1000);
+
+ ret = 0;
+out:
+ close(fd);
+
+ return ret;
+}
+
+static int
+atenl_hqa_skip(struct atenl *an, struct atenl_data *data)
+{
+ return 0;
+}
+
+static int
+atenl_hqa_check_efuse_mode(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ bool flash_mode = an->mtd_part != NULL;
+ enum atenl_cmd cmd = data->cmd;
+ u32 mode;
+
+ switch (cmd) {
+ case HQA_CMD_CHECK_EFUSE_MODE:
+ mode = flash_mode ? 0 : 1;
+ break;
+ case HQA_CMD_CHECK_EFUSE_MODE_TYPE:
+ mode = flash_mode ? E2P_FLASH_MODE : E2P_BIN_MODE;
+ break;
+ case HQA_CMD_CHECK_EFUSE_MODE_NATIVE:
+ mode = flash_mode ? E2P_FLASH_MODE : E2P_EFUSE_MODE;
+ break;
+ default:
+ mode = E2P_BIN_MODE;
+ break;
+ }
+
+ *(u32 *)(hdr->data + 2) = htonl(mode);
+
+ return 0;
+}
+
+static inline u16
+atenl_get_freq_by_channel(u8 ch_band, u16 ch)
+{
+ u16 base;
+
+ if (ch_band == CH_BAND_6GHZ) {
+ base = 5950;
+ } else if (ch_band == CH_BAND_5GHZ) {
+ if (ch >= 184)
+ return 4920 + (ch - 184) * 5;
+
+ base = 5000;
+ } else {
+ base = 2407;
+ }
+
+ return base + ch * 5;
+}
+
+u16 atenl_get_center_channel(u8 bw, u8 ch_band, u16 ctrl_ch)
+{
+ const struct atenl_channel *chan = NULL;
+ const struct atenl_channel *ch_list;
+ u16 center_ch;
+ u8 ch_num;
+ int i;
+
+ if (ch_band == CH_BAND_2GHZ || bw <= TEST_CBW_40MHZ)
+ return 0;
+
+ if (ch_band == CH_BAND_6GHZ) {
+ ch_list = atenl_channels_6ghz;
+ ch_num = ARRAY_SIZE(atenl_channels_6ghz);
+ } else {
+ ch_list = atenl_channels_5ghz;
+ ch_num = ARRAY_SIZE(atenl_channels_5ghz);
+ }
+
+ for (i = 0; i < ch_num; i++) {
+ if (ctrl_ch == ch_list[i].ch) {
+ chan = &ch_list[i];
+ break;
+ }
+ }
+
+ if (!chan)
+ return 0;
+
+ switch (bw) {
+ case TEST_CBW_160MHZ:
+ center_ch = chan->ch_160;
+ break;
+ case TEST_CBW_80MHZ:
+ center_ch = chan->ch_80;
+ break;
+ default:
+ center_ch = 0;
+ break;
+ }
+
+ return center_ch;
+}
+
+static void atenl_get_bw_string(u8 bw, char *buf)
+{
+ switch (bw) {
+ case TEST_CBW_160MHZ:
+ sprintf(buf, "160");
+ break;
+ case TEST_CBW_80MHZ:
+ sprintf(buf, "80");
+ break;
+ case TEST_CBW_40MHZ:
+ sprintf(buf, "40");
+ break;
+ default:
+ sprintf(buf, "20");
+ break;
+ }
+}
+
+void atenl_set_channel(struct atenl *an, u8 bw, u8 ch_band,
+ u16 ch, u16 center_ch1, u16 center_ch2)
+{
+ char bw_str[8] = {};
+ char cmd[128];
+ u16 freq = atenl_get_freq_by_channel(ch_band, ch);
+ u16 freq_center1 = atenl_get_freq_by_channel(ch_band, center_ch1);
+ int ret;
+
+ if (bw > TEST_CBW_MAX)
+ return;
+
+ atenl_get_bw_string(bw, bw_str);
+
+ if (bw == TEST_CBW_20MHZ)
+ ret = snprintf(cmd, sizeof(cmd), "iw dev mon%d set freq %u %s",
+ get_band_val(an, an->cur_band, phy_idx),
+ freq, bw_str);
+ else
+ ret = snprintf(cmd, sizeof(cmd), "iw dev mon%d set freq %u %s %u",
+ get_band_val(an, an->cur_band, phy_idx),
+ freq, bw_str, freq_center1);
+ if (snprintf_error(sizeof(cmd), ret))
+ return;
+
+ atenl_dbg("[%d]%s: cmd: %s\n", getpid(), __func__, cmd);
+
+ system(cmd);
+}
+
+static int
+atenl_hqa_set_channel(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ u32 *v = (u32 *)hdr->data;
+ u8 band = ntohl(v[2]);
+ u16 ch1 = ntohl(v[3]); /* center */
+ u16 ch2 = ntohl(v[4]);
+ u8 bw = ntohl(v[5]);
+ u8 pri_sel = ntohl(v[7]);
+ u8 ch_band = ntohl(v[9]);
+ u16 ctrl_ch = 0;
+
+ if (band >= MAX_BAND_NUM)
+ return -EINVAL;
+
+ if ((bw == TEST_CBW_160MHZ && pri_sel > 7) ||
+ (bw == TEST_CBW_80MHZ && pri_sel > 3) ||
+ (bw == TEST_CBW_40MHZ && pri_sel > 1)) {
+ atenl_err("%s: ctrl channel select error\n", __func__);
+ return -EINVAL;
+ }
+
+ an->cur_band = band;
+
+ if (ch_band == CH_BAND_2GHZ) {
+ ctrl_ch = ch1;
+ switch (bw) {
+ case TEST_CBW_40MHZ:
+ if (pri_sel == 1)
+ ctrl_ch += 2;
+ else
+ ctrl_ch -= 2;
+ break;
+ default:
+ break;
+ }
+
+ atenl_set_channel(an, bw, CH_BAND_2GHZ, ctrl_ch, ch1, 0);
+ } else {
+ const struct atenl_channel *chan = NULL;
+ const struct atenl_channel *ch_list;
+ u8 ch_num;
+ int i;
+
+ if (ch_band == CH_BAND_6GHZ) {
+ ch_list = atenl_channels_6ghz;
+ ch_num = ARRAY_SIZE(atenl_channels_6ghz);
+ } else {
+ ch_list = atenl_channels_5ghz;
+ ch_num = ARRAY_SIZE(atenl_channels_5ghz);
+ }
+
+ if (bw == TEST_CBW_160MHZ) {
+ for (i = 0; i < ch_num; i++) {
+ if (ch1 == ch_list[i].ch_160) {
+ chan = &ch_list[i];
+ break;
+ } else if (ch1 < ch_list[i].ch_160) {
+ chan = &ch_list[i - 1];
+ break;
+ }
+ }
+ } else if (bw == TEST_CBW_80MHZ) {
+ for (i = 0; i < ch_num; i++) {
+ if (ch1 == ch_list[i].ch_80) {
+ chan = &ch_list[i];
+ break;
+ } else if (ch1 < ch_list[i].ch_80) {
+ chan = &ch_list[i - 1];
+ break;
+ }
+ }
+ } else {
+ for (i = 0; i < ch_num; i++) {
+ if (ch1 <= ch_list[i].ch) {
+ if (ch1 == ch_list[i].ch)
+ chan = &ch_list[i];
+ else
+ chan = &ch_list[i - 1];
+ break;
+ }
+ }
+ }
+
+ if (!chan)
+ return -EINVAL;
+
+ if (bw != TEST_CBW_20MHZ) {
+ chan += pri_sel;
+ if (chan > &ch_list[ch_num - 1])
+ return -EINVAL;
+ }
+ ctrl_ch = chan->ch;
+
+ atenl_set_channel(an, bw, ch_band, ctrl_ch, ch1, ch2);
+ }
+
+ *(u32 *)(hdr->data + 2) = data->ext_id;
+
+ return 0;
+}
+
+static int
+atenl_hqa_tx_time_option(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ u32 *v = (u32 *)hdr->data;
+ u8 band = ntohl(v[1]);
+ u32 option = ntohl(v[2]);
+
+ if (band >= MAX_BAND_NUM)
+ return -EINVAL;
+
+ set_band_val(an, band, use_tx_time, !!option);
+ *(u32 *)(hdr->data + 2) = data->ext_id;
+
+ return 0;
+}
+
+static inline enum atenl_cmd atenl_get_cmd_by_id(u16 cmd_idx)
+{
+#define CMD_ID_GROUP GENMASK(15, 8)
+ u8 group = FIELD_GET(CMD_ID_GROUP, cmd_idx);
+
+ if (cmd_idx == 0x1600)
+ return HQA_CMD_EXT;
+
+ if (group == 0x10) {
+ switch (cmd_idx) {
+ case 0x1000:
+ return HQA_CMD_OPEN_ADAPTER;
+ case 0x1001:
+ return HQA_CMD_CLOSE_ADAPTER;
+ case 0x100b:
+ return HQA_CMD_SET_TX_PATH;
+ case 0x100c:
+ return HQA_CMD_SET_RX_PATH;
+ case 0x1011:
+ return HQA_CMD_SET_TX_POWER;
+ case 0x1018:
+ return HQA_CMD_SET_TX_POWER_MANUAL;
+ case 0x100d:
+ return HQA_CMD_LEGACY;
+ default:
+ break;
+ }
+ } else if (group == 0x11) {
+ switch (cmd_idx) {
+ case 0x1104:
+ return HQA_CMD_SET_TX_BW;
+ case 0x1105:
+ return HQA_CMD_SET_TX_PKT_BW;
+ case 0x1106:
+ return HQA_CMD_SET_TX_PRI_BW;
+ case 0x1107:
+ return HQA_CMD_SET_FREQ_OFFSET;
+ case 0x1109:
+ return HQA_CMD_SET_TSSI;
+ case 0x110d:
+ return HQA_CMD_ANT_SWAP_CAP;
+ case 0x1101:
+ case 0x1102:
+ return HQA_CMD_LEGACY;
+ default:
+ break;
+ }
+ } else if (group == 0x12) {
+ switch (cmd_idx) {
+ case 0x1200:
+ return HQA_CMD_RESET_TX_RX_COUNTER;
+ default:
+ break;
+ }
+ } else if (group == 0x13) {
+ switch (cmd_idx) {
+ case 0x1301:
+ return HQA_CMD_WRITE_MAC_BBP_REG;
+ case 0x1302:
+ return HQA_CMD_READ_MAC_BBP_REG;
+ case 0x1307:
+ return HQA_CMD_READ_EEPROM_BULK;
+ case 0x1306:
+ case 0x1308:
+ return HQA_CMD_WRITE_EEPROM_BULK;
+ case 0x1309:
+ return HQA_CMD_CHECK_EFUSE_MODE;
+ case 0x130a:
+ return HQA_CMD_GET_EFUSE_FREE_BLOCK;
+ case 0x130d:
+ return HQA_CMD_GET_TX_POWER;
+ case 0x130e:
+ return HQA_CMD_SET_CFG;
+ case 0x130f:
+ return HQA_CMD_GET_FREQ_OFFSET;
+ case 0x1311:
+ return HQA_CMD_CONTINUOUS_TX;
+ case 0x1312:
+ return HQA_CMD_SET_RX_PKT_LEN;
+ case 0x1313:
+ return HQA_CMD_GET_TX_INFO;
+ case 0x1314:
+ return HQA_CMD_GET_CFG;
+ case 0x131f:
+ return HQA_CMD_UNKNOWN;
+ case 0x131a:
+ return HQA_CMD_GET_TX_TONE_POWER;
+ default:
+ break;
+ }
+ } else if (group == 0x14) {
+ switch (cmd_idx) {
+ case 0x1401:
+ return HQA_CMD_READ_TEMPERATURE;
+ default:
+ break;
+ }
+ } else if (group == 0x15) {
+ switch (cmd_idx) {
+ case 0x1500:
+ return HQA_CMD_GET_FW_INFO;
+ case 0x1505:
+ return HQA_CMD_SET_TSSI;
+ case 0x1509:
+ return HQA_CMD_SET_RF_MODE;
+ case 0x1511:
+ return HQA_CMD_WRITE_BUFFER_DONE;
+ case 0x1514:
+ return HQA_CMD_GET_CHIP_ID;
+ case 0x151b:
+ return HQA_CMD_GET_SUB_CHIP_ID;
+ case 0x151c:
+ return HQA_CMD_GET_RX_INFO;
+ case 0x151e:
+ return HQA_CMD_GET_RF_CAP;
+ case 0x1522:
+ return HQA_CMD_CHECK_EFUSE_MODE_TYPE;
+ case 0x1523:
+ return HQA_CMD_CHECK_EFUSE_MODE_NATIVE;
+ case 0x152d:
+ return HQA_CMD_GET_BAND;
+ case 0x1594:
+ return HQA_CMD_SET_RU;
+ case 0x1502:
+ case 0x150b:
+ return HQA_CMD_LEGACY;
+ default:
+ break;
+ }
+ }
+
+ return HQA_CMD_ERR;
+}
+
+static inline enum atenl_ext_cmd atenl_get_ext_cmd(u16 ext_cmd_idx)
+{
+#define EXT_CMD_ID_GROUP GENMASK(7, 4)
+ u8 ext_group = FIELD_GET(EXT_CMD_ID_GROUP, ext_cmd_idx);
+
+ if (ext_group == 0) {
+ switch (ext_cmd_idx) {
+ case 0x1:
+ return HQA_EXT_CMD_SET_CHANNEL;
+ case 0x2:
+ return HQA_EXT_CMD_SET_TX;
+ case 0x3:
+ return HQA_EXT_CMD_START_TX;
+ case 0x4:
+ return HQA_EXT_CMD_START_RX;
+ case 0x5:
+ return HQA_EXT_CMD_STOP_TX;
+ case 0x6:
+ return HQA_EXT_CMD_STOP_RX;
+ case 0x8:
+ return HQA_EXT_CMD_IBF_SET_VAL;
+ case 0x9:
+ return HQA_EXT_CMD_IBF_GET_STATUS;
+ case 0xc:
+ return HQA_EXT_CMD_IBF_PROF_UPDATE_ALL;
+ default:
+ break;
+ }
+ } else if (ext_group == 1) {
+ } else if (ext_group == 2) {
+ switch (ext_cmd_idx) {
+ case 0x26:
+ return HQA_EXT_CMD_SET_TX_TIME_OPT;
+ case 0x27:
+ return HQA_EXT_CMD_OFF_CH_SCAN;
+ default:
+ break;
+ }
+ }
+
+ return HQA_EXT_CMD_UNSPEC;
+}
+
+#define ATENL_GROUP(_cmd, _resp_len, _ops) \
+ [HQA_CMD_##_cmd] = { .resp_len=_resp_len, .ops=_ops }
+static const struct atenl_cmd_ops atenl_ops[] = {
+ ATENL_GROUP(OPEN_ADAPTER, 2, atenl_hqa_adapter),
+ ATENL_GROUP(CLOSE_ADAPTER, 2, atenl_hqa_adapter),
+ ATENL_GROUP(SET_TX_PATH, 2, atenl_nl_process),
+ ATENL_GROUP(SET_RX_PATH, 2, atenl_nl_process),
+ ATENL_GROUP(SET_TX_POWER, 2, atenl_nl_process),
+ ATENL_GROUP(SET_TX_POWER_MANUAL, 2, atenl_hqa_skip),
+ ATENL_GROUP(SET_TX_BW, 2, atenl_hqa_skip),
+ ATENL_GROUP(SET_TX_PKT_BW, 2, atenl_hqa_skip),
+ ATENL_GROUP(SET_TX_PRI_BW, 2, atenl_hqa_skip),
+ ATENL_GROUP(SET_FREQ_OFFSET, 2, atenl_nl_process),
+ ATENL_GROUP(ANT_SWAP_CAP, 6, atenl_hqa_skip),
+ ATENL_GROUP(RESET_TX_RX_COUNTER, 2, atenl_hqa_reset_counter),
+ ATENL_GROUP(WRITE_MAC_BBP_REG, 2, atenl_hqa_mac_bbp_reg),
+ ATENL_GROUP(READ_MAC_BBP_REG, 0, atenl_hqa_mac_bbp_reg),
+ ATENL_GROUP(READ_EEPROM_BULK, 0, atenl_hqa_eeprom_bulk),
+ ATENL_GROUP(WRITE_EEPROM_BULK, 2, atenl_hqa_eeprom_bulk),
+ ATENL_GROUP(CHECK_EFUSE_MODE, 6, atenl_hqa_check_efuse_mode),
+ ATENL_GROUP(GET_EFUSE_FREE_BLOCK, 6, atenl_hqa_get_efuse_free_block),
+ ATENL_GROUP(GET_TX_POWER, 10, atenl_hqa_get_tx_power),
+ ATENL_GROUP(GET_FREQ_OFFSET, 6, atenl_hqa_get_freq_offset), /*TODO: MCU CMD, read eeprom?*/
+ ATENL_GROUP(CONTINUOUS_TX, 6, atenl_nl_process),
+ ATENL_GROUP(SET_RX_PKT_LEN, 2, atenl_hqa_skip),
+ ATENL_GROUP(GET_TX_INFO, 10, atenl_nl_process),
+ ATENL_GROUP(GET_CFG, 6, atenl_hqa_get_cfg), /*TODO*/
+ ATENL_GROUP(GET_TX_TONE_POWER, 6, atenl_hqa_skip),
+ ATENL_GROUP(SET_CFG, 2, atenl_nl_process),
+ ATENL_GROUP(READ_TEMPERATURE, 6, atenl_hqa_read_temperature),
+ ATENL_GROUP(GET_FW_INFO, 32, atenl_hqa_skip), /* TODO: check format */
+ ATENL_GROUP(SET_TSSI, 2, atenl_nl_process),
+ ATENL_GROUP(SET_RF_MODE, 2, atenl_hqa_set_rf_mode),
+ ATENL_GROUP(WRITE_BUFFER_DONE, 2, atenl_hqa_eeprom_bulk),
+ ATENL_GROUP(GET_CHIP_ID, 6, atenl_hqa_get_chip_id),
+ ATENL_GROUP(GET_SUB_CHIP_ID, 6, atenl_hqa_get_sub_chip_id),
+ ATENL_GROUP(GET_RX_INFO, 0, atenl_nl_process),
+ ATENL_GROUP(GET_RF_CAP, 10, atenl_hqa_get_rf_cap),
+ ATENL_GROUP(CHECK_EFUSE_MODE_TYPE, 6, atenl_hqa_check_efuse_mode),
+ ATENL_GROUP(CHECK_EFUSE_MODE_NATIVE, 6, atenl_hqa_check_efuse_mode),
+ ATENL_GROUP(GET_BAND, 6, atenl_hqa_get_band),
+ ATENL_GROUP(SET_RU, 2, atenl_nl_process_many),
+
+ ATENL_GROUP(LEGACY, 2, atenl_hqa_skip),
+ ATENL_GROUP(UNKNOWN, 1024, atenl_hqa_skip),
+};
+#undef ATENL_GROUP
+
+#define ATENL_EXT(_cmd, _resp_len, _ops) \
+ [HQA_EXT_CMD_##_cmd] = { .resp_len=_resp_len, .ops=_ops }
+static const struct atenl_cmd_ops atenl_ext_ops[] = {
+ ATENL_EXT(SET_CHANNEL, 6, atenl_hqa_set_channel),
+ ATENL_EXT(SET_TX, 6, atenl_nl_process),
+ ATENL_EXT(START_TX, 6, atenl_nl_process),
+ ATENL_EXT(STOP_TX, 6, atenl_nl_process),
+ ATENL_EXT(START_RX, 6, atenl_nl_process),
+ ATENL_EXT(STOP_RX, 6, atenl_nl_process),
+ ATENL_EXT(SET_TX_TIME_OPT, 6, atenl_hqa_tx_time_option),
+ ATENL_EXT(OFF_CH_SCAN, 6, atenl_nl_process),
+ ATENL_EXT(IBF_SET_VAL, 6, atenl_nl_process),
+ ATENL_EXT(IBF_GET_STATUS, 10, atenl_nl_process),
+ ATENL_EXT(IBF_PROF_UPDATE_ALL, 6, atenl_nl_process_many),
+};
+#undef ATENL_EXT
+
+int atenl_hqa_recv(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ u16 cmd_type = ntohs(hdr->cmd_type);
+ int fd = an->pipefd[PIPE_WRITE];
+ int ret;
+
+ if (ntohl(hdr->magic_no) != RACFG_MAGIC_NO)
+ return -EINVAL;
+
+ if (FIELD_GET(RACFG_CMD_TYPE_MASK, cmd_type) != RACFG_CMD_TYPE_ETHREQ &&
+ FIELD_GET(RACFG_CMD_TYPE_MASK, cmd_type) != RACFG_CMD_TYPE_PLATFORM_MODULE) {
+ atenl_err("[%d]%s: cmd type error = 0x%x\n", getpid(), __func__, cmd_type);
+ return -EINVAL;
+ }
+
+ atenl_dbg("[%d]%s: recv cmd type = 0x%x, id = 0x%x\n",
+ getpid(), __func__, cmd_type, ntohs(hdr->cmd_id));
+
+ ret = write(fd, data, data->len);
+ if (ret < 0) {
+ perror("pipe write");
+ return ret;
+ }
+
+ return 0;
+}
+
+int atenl_hqa_proc_cmd(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ const struct atenl_cmd_ops *ops;
+ u16 cmd_id = ntohs(hdr->cmd_id);
+ u16 status = 0;
+
+ data->cmd = atenl_get_cmd_by_id(cmd_id);
+ if (data->cmd == HQA_CMD_ERR) {
+ atenl_err("Unknown command id: 0x%04x\n", cmd_id);
+ goto done;
+ }
+
+ if (data->cmd == HQA_CMD_EXT) {
+ data->ext_id = ntohl(*(u32 *)hdr->data);
+ data->ext_cmd = atenl_get_ext_cmd(data->ext_id);
+ if (data->ext_cmd == HQA_EXT_CMD_UNSPEC) {
+ atenl_err("Unknown ext command id: 0x%04x\n", data->ext_id);
+ goto done;
+ }
+
+ ops = &atenl_ext_ops[data->ext_cmd];
+ } else {
+ ops = &atenl_ops[data->cmd];
+ }
+
+ atenl_dbg_print_data(data, __func__,
+ ntohs(hdr->len) + ETH_HLEN + RACFG_HLEN);
+ if (ops->ops)
+ status = htons(ops->ops(an, data));
+ if (ops->resp_len)
+ hdr->len = htons(ops->resp_len);
+
+ *(u16 *)hdr->data = status;
+
+done:
+ data->len = ntohs(hdr->len) + ETH_HLEN + RACFG_HLEN;
+ hdr->cmd_type |= ~htons(RACFG_CMD_TYPE_MASK);
+
+ return 0;
+}
diff --git a/feed/atenl/src/main.c b/feed/atenl/src/main.c
new file mode 100644
index 0000000..ec0a320
--- /dev/null
+++ b/feed/atenl/src/main.c
@@ -0,0 +1,274 @@
+/* Copyright (C) 2021-2022 Mediatek Inc. */
+
+#include <signal.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+#include "atenl.h"
+
+static const char *progname;
+bool atenl_enable;
+
+void sig_handler(int signum)
+{
+ atenl_enable = false;
+}
+
+void atenl_init_signals()
+{
+ if (signal(SIGINT, sig_handler) == SIG_ERR)
+ goto out;
+ if (signal(SIGTERM, sig_handler) == SIG_ERR)
+ goto out;
+ if (signal(SIGABRT, sig_handler) == SIG_ERR)
+ goto out;
+ if (signal(SIGUSR1, sig_handler) == SIG_ERR)
+ goto out;
+ if (signal(SIGUSR2, sig_handler) == SIG_ERR)
+ goto out;
+
+ return;
+out:
+ perror("signal");
+}
+
+static int phy_lookup_idx(const char *name)
+{
+ char buf[128];
+ FILE *f;
+ size_t len;
+ int ret;
+
+ ret = snprintf(buf, sizeof(buf), "/sys/class/ieee80211/%s/index", name);
+ if (snprintf_error(sizeof(buf), ret))
+ return -1;
+
+ f = fopen(buf, "r");
+ if (!f)
+ return -1;
+
+ len = fread(buf, 1, sizeof(buf) - 1, f);
+ fclose(f);
+
+ if (!len)
+ return -1;
+
+ buf[len] = 0;
+ return atoi(buf);
+}
+
+static void usage(void)
+{
+ fprintf(stderr, "Usage:\n");
+ printf(" %s [-u] [-i phyX]\n", progname);
+ printf("options:\n"
+ " -h = show help text\n"
+ " -i = phy name of driver interface, please use first phy for dbdc\n"
+ " -u = use unicast to respond to HQADLL\n");
+ printf("examples:\n"
+ " %s -u -i phy0\n", progname);
+
+ exit(EXIT_FAILURE);
+}
+
+static int atenl_parent_work(struct atenl *an)
+{
+ int sock_eth = an->sock_eth;
+ int count, ret = 0;
+ fd_set readfds;
+
+ atenl_info("[%d]%s: start for receiving HQA commands\n", getpid(), __func__);
+
+ while (atenl_enable) {
+ FD_ZERO(&readfds);
+ FD_SET(sock_eth, &readfds);
+ count = select(sock_eth + 1, &readfds, NULL, NULL, NULL);
+
+ if (count < 0) {
+ atenl_err("%s: select failed, %s\n", __func__, strerror(errno));
+ continue;
+ } else if (count == 0) {
+ usleep(1000);
+ continue;
+ } else {
+ if (FD_ISSET(sock_eth, &readfds)) {
+ struct atenl_data *data = calloc(1, sizeof(struct atenl_data));
+
+ ret = atenl_eth_recv(an, data);
+ if (ret) {
+ kill(an->child_pid, SIGUSR1);
+ return ret;
+ }
+
+ ret = atenl_hqa_recv(an, data);
+ if (ret < 0) {
+ kill(an->child_pid, SIGUSR1);
+ return ret;
+ }
+
+ free(data);
+ }
+ }
+ }
+
+ atenl_info("[%d]%s: parent work end\n", getpid(), __func__);
+
+ return ret;
+}
+
+static int atenl_child_work(struct atenl *an)
+{
+ int rfd = an->pipefd[PIPE_READ], count;
+ int ret = 0;
+ fd_set readfds;
+
+ atenl_info("[%d]%s: start for sending back results\n", getpid(), __func__);
+
+ while (atenl_enable) {
+ struct atenl_data *data = calloc(1, sizeof(struct atenl_data));
+
+ FD_ZERO(&readfds);
+ FD_SET(rfd, &readfds);
+
+ count = select(FD_SETSIZE, &readfds, NULL, NULL, NULL);
+
+ if (count < 0) {
+ atenl_err("%s: select failed, %s\n", __func__, strerror(errno));
+ continue;
+ } else if (count == 0) {
+ usleep(1000);
+ continue;
+ } else {
+ if (FD_ISSET(rfd, &readfds)) {
+ count = read(rfd, data->buf, RACFG_PKT_MAX_SIZE);
+ atenl_dbg("[%d]PIPE Read %d bytes\n", getpid(), count);
+
+ if (count < 0) {
+ atenl_info("%s: %s\n", __func__, strerror(errno));
+ } else if (count == 0) {
+ continue;
+ } else {
+ int ret;
+
+ ret = atenl_hqa_proc_cmd(an, data);
+ if (ret) {
+ kill(getppid(), SIGUSR2);
+ goto out;
+ }
+
+ ret = atenl_eth_send(an, data);
+ if (ret) {
+ kill(getppid(), SIGUSR2);
+ goto out;
+ }
+ }
+ }
+ }
+ }
+
+out:
+ atenl_info("[%d]%s: child work end\n", getpid(), __func__);
+
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ int opt, phy_idx, ret = 0;
+ char *phy = "phy0", *cmd = NULL;
+ struct atenl *an;
+
+ progname = argv[0];
+
+ an = calloc(1, sizeof(struct atenl));
+ if (!an)
+ return -ENOMEM;
+
+ while(1) {
+ opt = getopt(argc, argv, "hi:uc:");
+ if (opt == -1)
+ break;
+
+ switch (opt) {
+ case 'h':
+ usage();
+ free(an);
+ return 0;
+ case 'i':
+ phy = optarg;
+ break;
+ case 'u':
+ an->unicast = true;
+ printf("Opt: use unicast to send response\n");
+ break;
+ case 'c':
+ cmd = optarg;
+ break;
+ default:
+ fprintf(stderr, "Not supported option\n");
+ break;
+ }
+ }
+
+ phy_idx = phy_lookup_idx(phy);
+ if (phy_idx < 0 || phy_idx > UCHAR_MAX) {
+ fprintf(stderr, "Could not find phy '%s'\n", phy);
+ free(an);
+ return 2;
+ }
+
+ if (cmd) {
+ atenl_eeprom_cmd_handler(an, phy_idx, cmd);
+ goto out;
+ }
+
+ atenl_enable = true;
+ atenl_init_signals();
+
+ /* background ourself */
+ if (!fork()) {
+ pid_t pid;
+
+ ret = atenl_eeprom_init(an, phy_idx);
+ if (ret)
+ goto out;
+
+ ret = atenl_eth_init(an);
+ if (ret)
+ goto out;
+
+ ret = pipe(an->pipefd);
+ if (ret) {
+ perror("Pipe");
+ goto out;
+ }
+
+ pid = fork();
+ an->child_pid = pid;
+ if (pid < 0) {
+ perror("Fork");
+ ret = pid;
+ goto out;
+ } else if (pid == 0) {
+ close(an->pipefd[PIPE_WRITE]);
+ atenl_child_work(an);
+ } else {
+ int status;
+
+ close(an->pipefd[PIPE_READ]);
+ atenl_parent_work(an);
+ waitpid(pid, &status, 0);
+ }
+ } else {
+ usleep(800000);
+ }
+
+ ret = 0;
+out:
+ if (an->sock_eth)
+ close(an->sock_eth);
+ if (an->eeprom_fd || an->eeprom_data)
+ atenl_eeprom_close(an);
+ free(an);
+
+ return ret;
+}
diff --git a/feed/atenl/src/nl.c b/feed/atenl/src/nl.c
new file mode 100644
index 0000000..47e3d76
--- /dev/null
+++ b/feed/atenl/src/nl.c
@@ -0,0 +1,1175 @@
+/* Copyright (C) 2021-2022 Mediatek Inc. */
+#define _GNU_SOURCE
+
+#include <unl.h>
+
+#include "atenl.h"
+
+#define to_rssi(_rcpi) ((_rcpi - 220) / 2)
+
+struct atenl_nl_priv {
+ struct atenl *an;
+ struct unl unl;
+ struct nl_msg *msg;
+ int attr;
+ void *res;
+};
+
+struct atenl_nl_ops {
+ int set;
+ int dump;
+ int (*ops)(struct atenl *an, struct atenl_data *data,
+ struct atenl_nl_priv *nl_priv);
+};
+
+static struct nla_policy testdata_policy[NUM_MT76_TM_ATTRS] = {
+ [MT76_TM_ATTR_STATE] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_MTD_PART] = { .type = NLA_STRING },
+ [MT76_TM_ATTR_MTD_OFFSET] = { .type = NLA_U32 },
+ [MT76_TM_ATTR_TX_COUNT] = { .type = NLA_U32 },
+ [MT76_TM_ATTR_TX_LENGTH] = { .type = NLA_U32 },
+ [MT76_TM_ATTR_TX_RATE_MODE] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_RATE_NSS] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_RATE_IDX] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_RATE_SGI] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_RATE_LDPC] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_RATE_STBC] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_LTF] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_POWER_CONTROL] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_TX_ANTENNA] = { .type = NLA_U8 },
+ [MT76_TM_ATTR_FREQ_OFFSET] = { .type = NLA_U32 },
+ [MT76_TM_ATTR_STATS] = { .type = NLA_NESTED },
+};
+
+static struct nla_policy stats_policy[NUM_MT76_TM_STATS_ATTRS] = {
+ [MT76_TM_STATS_ATTR_TX_PENDING] = { .type = NLA_U32 },
+ [MT76_TM_STATS_ATTR_TX_QUEUED] = { .type = NLA_U32 },
+ [MT76_TM_STATS_ATTR_TX_DONE] = { .type = NLA_U32 },
+ [MT76_TM_STATS_ATTR_RX_PACKETS] = { .type = NLA_U64 },
+ [MT76_TM_STATS_ATTR_RX_FCS_ERROR] = { .type = NLA_U64 },
+};
+
+static struct nla_policy rx_policy[NUM_MT76_TM_RX_ATTRS] = {
+ [MT76_TM_RX_ATTR_FREQ_OFFSET] = { .type = NLA_U32 },
+ [MT76_TM_RX_ATTR_RCPI] = { .type = NLA_NESTED },
+ [MT76_TM_RX_ATTR_IB_RSSI] = { .type = NLA_NESTED },
+ [MT76_TM_RX_ATTR_WB_RSSI] = { .type = NLA_NESTED },
+ [MT76_TM_RX_ATTR_SNR] = { .type = NLA_U8 },
+};
+
+struct he_sgi {
+ enum mt76_testmode_tx_mode tx_mode;
+ u8 sgi;
+ u8 tx_ltf;
+};
+
+#define HE_SGI_GROUP(_tx_mode, _sgi, _tx_ltf) \
+ { .tx_mode = MT76_TM_TX_MODE_##_tx_mode, .sgi = _sgi, .tx_ltf = _tx_ltf }
+static const struct he_sgi he_sgi_groups[] = {
+ HE_SGI_GROUP(HE_SU, 0, 0),
+ HE_SGI_GROUP(HE_SU, 0, 1),
+ HE_SGI_GROUP(HE_SU, 1, 1),
+ HE_SGI_GROUP(HE_SU, 2, 2),
+ HE_SGI_GROUP(HE_SU, 0, 2),
+ HE_SGI_GROUP(HE_EXT_SU, 0, 0),
+ HE_SGI_GROUP(HE_EXT_SU, 0, 1),
+ HE_SGI_GROUP(HE_EXT_SU, 1, 1),
+ HE_SGI_GROUP(HE_EXT_SU, 2, 2),
+ HE_SGI_GROUP(HE_EXT_SU, 0, 2),
+ HE_SGI_GROUP(HE_TB, 1, 0),
+ HE_SGI_GROUP(HE_TB, 1, 1),
+ HE_SGI_GROUP(HE_TB, 2, 2),
+ HE_SGI_GROUP(HE_MU, 0, 2),
+ HE_SGI_GROUP(HE_MU, 0, 1),
+ HE_SGI_GROUP(HE_MU, 1, 1),
+ HE_SGI_GROUP(HE_MU, 2, 2),
+};
+#undef HE_SGI_LTF_GROUP
+
+static u8 phy_type_to_attr(u8 phy_type)
+{
+ static const u8 phy_type_to_attr[] = {
+ [ATENL_PHY_TYPE_CCK] = MT76_TM_TX_MODE_CCK,
+ [ATENL_PHY_TYPE_OFDM] = MT76_TM_TX_MODE_OFDM,
+ [ATENL_PHY_TYPE_HT] = MT76_TM_TX_MODE_HT,
+ [ATENL_PHY_TYPE_HT_GF] = MT76_TM_TX_MODE_HT,
+ [ATENL_PHY_TYPE_VHT] = MT76_TM_TX_MODE_VHT,
+ [ATENL_PHY_TYPE_HE_SU] = MT76_TM_TX_MODE_HE_SU,
+ [ATENL_PHY_TYPE_HE_EXT_SU] = MT76_TM_TX_MODE_HE_EXT_SU,
+ [ATENL_PHY_TYPE_HE_TB] = MT76_TM_TX_MODE_HE_TB,
+ [ATENL_PHY_TYPE_HE_MU] = MT76_TM_TX_MODE_HE_MU,
+ };
+
+ if (phy_type >= ARRAY_SIZE(phy_type_to_attr))
+ return 0;
+
+ return phy_type_to_attr[phy_type];
+}
+
+static void
+atenl_set_attr_state(struct atenl *an, struct nl_msg *msg,
+ u8 band, enum mt76_testmode_state state)
+{
+ if (get_band_val(an, band, cur_state) == state)
+ return;
+
+ nla_put_u8(msg, MT76_TM_ATTR_STATE, state);
+ set_band_val(an, band, cur_state, state);
+}
+
+static void
+atenl_set_attr_antenna(struct atenl *an, struct nl_msg *msg, u8 tx_antenna)
+{
+ if (!tx_antenna)
+ return;
+
+ if (is_mt7915(an))
+ nla_put_u8(msg, MT76_TM_ATTR_TX_ANTENNA,
+ tx_antenna << (2 * an->cur_band));
+ else if (is_mt7916(an) || is_mt7986(an))
+ nla_put_u8(msg, MT76_TM_ATTR_TX_ANTENNA, tx_antenna);
+}
+
+static int
+atenl_nl_set_attr(struct atenl *an, struct atenl_data *data,
+ struct atenl_nl_priv *nl_priv)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ struct nl_msg *msg = nl_priv->msg;
+ u32 val = ntohl(*(u32 *)hdr->data);
+ int attr = nl_priv->attr;
+ void *ptr, *a;
+
+ ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+ if (!ptr)
+ return -ENOMEM;
+
+ switch (attr) {
+ case MT76_TM_ATTR_TX_ANTENNA:
+ atenl_set_attr_antenna(an, msg, val);
+ break;
+ case MT76_TM_ATTR_FREQ_OFFSET:
+ nla_put_u32(msg, attr, val);
+ break;
+ case MT76_TM_ATTR_TX_POWER:
+ a = nla_nest_start(msg, MT76_TM_ATTR_TX_POWER);
+ if (!a)
+ return -ENOMEM;
+ nla_put_u8(msg, 0, val);
+ nla_nest_end(msg, a);
+ break;
+ default:
+ nla_put_u8(msg, attr, val);
+ break;
+ }
+
+ nla_nest_end(msg, ptr);
+
+ return unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+}
+
+static int
+atenl_nl_set_cfg(struct atenl *an, struct atenl_data *data,
+ struct atenl_nl_priv *nl_priv)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ struct nl_msg *msg = nl_priv->msg;
+ enum atenl_cmd cmd = data->cmd;
+ u32 *v = (u32 *)hdr->data;
+ u8 type = ntohl(v[0]);
+ u8 enable = ntohl(v[1]);
+ void *ptr, *cfg;
+
+ if (cmd == HQA_CMD_SET_TSSI) {
+ type = 0;
+ enable = 1;
+ }
+
+ ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+ if (!ptr)
+ return -ENOMEM;
+
+ cfg = nla_nest_start(msg, MT76_TM_ATTR_CFG);
+ if (!cfg)
+ return -ENOMEM;
+
+ if (nla_put_u8(msg, 0, type) ||
+ nla_put_u8(msg, 1, enable))
+ return -EINVAL;
+
+ nla_nest_end(msg, cfg);
+
+ nla_nest_end(msg, ptr);
+
+ return unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+}
+
+static int
+atenl_nl_set_tx(struct atenl *an, struct atenl_data *data,
+ struct atenl_nl_priv *nl_priv)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ struct nl_msg *msg = nl_priv->msg;
+ u32 *v = (u32 *)hdr->data;
+ u8 *addr1 = hdr->data + 36;
+ u8 *addr2 = addr1 + ETH_ALEN;
+ u8 *addr3 = addr2 + ETH_ALEN;
+ u8 default_addr[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
+ void *ptr, *a;
+
+ ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+ if (!ptr)
+ return -ENOMEM;
+
+ if (get_band_val(an, an->cur_band, use_tx_time))
+ nla_put_u32(msg, MT76_TM_ATTR_TX_TIME, ntohl(v[7]));
+ else
+ nla_put_u32(msg, MT76_TM_ATTR_TX_LENGTH, ntohl(v[7]));
+
+ a = nla_nest_start(msg, MT76_TM_ATTR_MAC_ADDRS);
+ if (!a)
+ return -ENOMEM;
+
+ if (is_multicast_ether_addr(addr1))
+ nla_put(msg, 0, ETH_ALEN, default_addr);
+ else
+ nla_put(msg, 0, ETH_ALEN, addr1);
+ nla_put(msg, 1, ETH_ALEN, addr2);
+ nla_put(msg, 2, ETH_ALEN, addr3);
+
+ nla_nest_end(msg, a);
+
+ nla_nest_end(msg, ptr);
+
+ *(u32 *)(hdr->data + 2) = data->ext_id;
+
+ return unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+}
+
+static int
+atenl_nl_tx(struct atenl *an, struct atenl_data *data, struct atenl_nl_priv *nl_priv)
+{
+#define USE_SPE_IDX BIT(31)
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ struct nl_msg *msg = nl_priv->msg;
+ u32 *v = (u32 *)hdr->data;
+ u8 band = ntohl(v[2]);
+ void *ptr;
+ int ret;
+
+ if (band >= MAX_BAND_NUM)
+ return -EINVAL;
+
+ ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+ if (!ptr)
+ return -ENOMEM;
+
+ if (data->ext_cmd == HQA_EXT_CMD_STOP_TX) {
+ atenl_set_attr_state(an, msg, band, MT76_TM_STATE_IDLE);
+ } else {
+ u32 tx_count = ntohl(v[3]);
+ u8 tx_rate_mode = phy_type_to_attr(ntohl(v[4]));
+ u8 aid = ntohl(v[11]);
+ u8 sgi = ntohl(v[13]);
+ u32 tx_antenna = ntohl(v[14]);
+ void *a;
+
+ if (sgi > 5)
+ return -EINVAL;
+
+ if (!tx_count)
+ tx_count = 10000000;
+
+ nla_put_u32(msg, MT76_TM_ATTR_TX_COUNT, tx_count);
+ nla_put_u32(msg, MT76_TM_ATTR_TX_IPG, ntohl(v[12]));
+ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_MODE, tx_rate_mode);
+ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_IDX, ntohl(v[5]));
+ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_STBC, ntohl(v[7]));
+ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_LDPC, ntohl(v[8]));
+ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_NSS, ntohl(v[15]));
+
+ /* for chips after 7915, tx need to use at least wcid = 1 */
+ if (!is_mt7915(an) && !aid)
+ aid = 1;
+ nla_put_u8(msg, MT76_TM_ATTR_AID, aid);
+
+ if (tx_antenna & USE_SPE_IDX) {
+ nla_put_u8(msg, MT76_TM_ATTR_TX_SPE_IDX,
+ tx_antenna & ~USE_SPE_IDX);
+ } else {
+ nla_put_u8(msg, MT76_TM_ATTR_TX_SPE_IDX, 0);
+ atenl_set_attr_antenna(an, msg, tx_antenna);
+ }
+
+ if (tx_rate_mode >= MT76_TM_TX_MODE_HE_SU) {
+ u8 ofs = sgi;
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(he_sgi_groups); i++)
+ if (he_sgi_groups[i].tx_mode == tx_rate_mode)
+ break;
+
+ if ((i + ofs) >= ARRAY_SIZE(he_sgi_groups))
+ return -EINVAL;
+
+ sgi = he_sgi_groups[i + ofs].sgi;
+ nla_put_u8(msg, MT76_TM_ATTR_TX_LTF,
+ he_sgi_groups[i + ofs].tx_ltf);
+ }
+ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_SGI, sgi);
+
+ a = nla_nest_start(msg, MT76_TM_ATTR_TX_POWER);
+ if (!a)
+ return -ENOMEM;
+ nla_put_u8(msg, 0, ntohl(v[6]));
+ nla_nest_end(msg, a);
+
+ atenl_set_attr_state(an, msg, band, MT76_TM_STATE_TX_FRAMES);
+ }
+
+ nla_nest_end(msg, ptr);
+
+ ret = unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+ if (ret)
+ return ret;
+
+ *(u32 *)(hdr->data + 2) = data->ext_id;
+
+ return 0;
+}
+
+static int
+atenl_nl_rx(struct atenl *an, struct atenl_data *data, struct atenl_nl_priv *nl_priv)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ struct atenl_band *anb = &an->anb[an->cur_band];
+ struct nl_msg *msg = nl_priv->msg;
+ u32 *v = (u32 *)hdr->data;
+ u8 band = ntohl(v[2]);
+ void *ptr;
+
+ if (band >= MAX_BAND_NUM)
+ return -EINVAL;
+
+ ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+ if (!ptr)
+ return -ENOMEM;
+
+ if (data->ext_cmd == HQA_EXT_CMD_STOP_RX) {
+ atenl_set_attr_state(an, msg, band, MT76_TM_STATE_IDLE);
+ } else {
+ v = (u32 *)(hdr->data + 18);
+
+ atenl_set_attr_antenna(an, msg, ntohl(v[0]));
+ nla_put_u8(msg, MT76_TM_ATTR_AID, ntohl(v[1]));
+ atenl_set_attr_state(an, msg, band, MT76_TM_STATE_RX_FRAMES);
+
+ anb->reset_rx_cnt = false;
+
+ /* clear history buffer */
+ memset(&anb->rx_stat, 0, sizeof(anb->rx_stat));
+ }
+
+ nla_nest_end(msg, ptr);
+
+ *(u32 *)(hdr->data + 2) = data->ext_id;
+
+ return unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+}
+
+static int
+atenl_off_ch_scan(struct atenl *an, struct atenl_data *data,
+ struct atenl_nl_priv *nl_priv)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ struct nl_msg *msg = nl_priv->msg;
+ u32 *v = (u32 *)hdr->data;
+ u8 ch = ntohl(v[2]);
+ u8 bw = ntohl(v[4]);
+ u8 tx_path = ntohl(v[5]);
+ u8 status = ntohl(v[6]);
+ void *ptr;
+
+ if (!status)
+ ch = 0; /* stop */
+
+ ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+ if (!ptr)
+ return -ENOMEM;
+
+ nla_put_u8(msg, MT76_TM_ATTR_OFF_CH_SCAN_CH, ch);
+ nla_put_u8(msg, MT76_TM_ATTR_OFF_CH_SCAN_CENTER_CH,
+ atenl_get_center_channel(bw, CH_BAND_5GHZ, ch));
+ nla_put_u8(msg, MT76_TM_ATTR_OFF_CH_SCAN_BW, bw);
+ nla_put_u8(msg, MT76_TM_ATTR_OFF_CH_SCAN_PATH, tx_path);
+
+ nla_nest_end(msg, ptr);
+
+ memcpy(hdr->data + 2, &data->ext_id, 4);
+
+ return 0;
+}
+
+static int atenl_nl_dump_cb(struct nl_msg *msg, void *arg)
+{
+ struct atenl_nl_priv *nl_priv = (struct atenl_nl_priv *)arg;
+ struct nlattr *tb1[NUM_MT76_TM_ATTRS];
+ struct nlattr *tb2[NUM_MT76_TM_STATS_ATTRS];
+ struct nlattr *nl_attr;
+ int attr = nl_priv->attr;
+ u64 *res = nl_priv->res;
+
+ nl_attr = unl_find_attr(&nl_priv->unl, msg, NL80211_ATTR_TESTDATA);
+ if (!nl_attr) {
+ fprintf(stderr, "Testdata attribute not found\n");
+ return NL_SKIP;
+ }
+
+ nla_parse_nested(tb1, MT76_TM_ATTR_MAX, nl_attr, testdata_policy);
+ nla_parse_nested(tb2, MT76_TM_STATS_ATTR_MAX,
+ tb1[MT76_TM_ATTR_STATS], stats_policy);
+
+ if (attr == MT76_TM_STATS_ATTR_TX_DONE)
+ *res = nla_get_u32(tb2[MT76_TM_STATS_ATTR_TX_DONE]);
+
+ return NL_SKIP;
+}
+
+static int
+atenl_nl_dump_attr(struct atenl *an, struct atenl_data *data,
+ struct atenl_nl_priv *nl_priv)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ struct nl_msg *msg = nl_priv->msg;
+ void *ptr;
+ u64 res = 0;
+
+ nl_priv->res = (void *)&res;
+
+ ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+ if (!ptr)
+ return -ENOMEM;
+ nla_put_flag(msg, MT76_TM_ATTR_STATS);
+ nla_nest_end(msg, ptr);
+
+ unl_genl_request(&nl_priv->unl, msg, atenl_nl_dump_cb, (void *)nl_priv);
+
+ if (nl_priv->attr == MT76_TM_STATS_ATTR_TX_DONE)
+ *(u32 *)(hdr->data + 2 + 4 * an->cur_band) = htonl(res);
+
+ return 0;
+}
+
+static int atenl_nl_continuous_tx(struct atenl *an,
+ struct atenl_data *data,
+ struct atenl_nl_priv *nl_priv)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ struct nl_msg *msg = nl_priv->msg;
+ u32 *v = (u32 *)hdr->data;
+ u8 band = ntohl(v[0]);
+ bool enable = ntohl(v[1]);
+ void *ptr;
+
+ ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+ if (!ptr)
+ return -ENOMEM;
+
+ if (band >= MAX_BAND_NUM)
+ return -EINVAL;
+
+ if (!enable) {
+ int phy = get_band_val(an, band, phy_idx);
+ char cmd[64];
+
+ atenl_set_attr_state(an, msg, band, MT76_TM_STATE_IDLE);
+ nla_nest_end(msg, ptr);
+ unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+
+ sprintf(cmd, "iw dev mon%d del", phy);
+ system(cmd);
+ sprintf(cmd, "iw phy phy%d interface add mon%d type monitor", phy, phy);
+ system(cmd);
+ sprintf(cmd, "ifconfig mon%d up", phy);
+ system(cmd);
+
+ return 0;
+ }
+
+ if (get_band_val(an, band, rf_mode) != ATENL_RF_MODE_TEST)
+ return 0;
+
+ nla_put_u8(msg, MT76_TM_ATTR_TX_ANTENNA, ntohl(v[2]));
+ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_MODE, phy_type_to_attr(ntohl(v[3])));
+ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_IDX, ntohl(v[6]));
+
+ atenl_dbg("%s: enable = %d, ant=%u, tx_rate_mode=%u, rate_idx=%u\n",
+ __func__, enable, ntohl(v[2]), ntohl(v[3]), ntohl(v[6]));
+
+ atenl_set_attr_state(an, msg, band, MT76_TM_STATE_TX_CONT);
+
+ nla_nest_end(msg, ptr);
+
+ return unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+}
+
+static int atenl_nl_get_rx_info_cb(struct nl_msg *msg, void *arg)
+{
+ struct atenl_nl_priv *nl_priv = (struct atenl_nl_priv *)arg;
+ struct atenl *an = nl_priv->an;
+ struct atenl_band *anb = &an->anb[an->cur_band];
+ struct atenl_data *data = nl_priv->res;
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ struct atenl_rx_info_hdr *rx_hdr;
+ struct atenl_rx_info_band *rx_band;
+ struct atenl_rx_info_user *rx_user;
+ struct atenl_rx_info_path *rx_path;
+ struct atenl_rx_info_comm *rx_comm;
+ struct nlattr *tb1[NUM_MT76_TM_ATTRS];
+ struct nlattr *tb2[NUM_MT76_TM_STATS_ATTRS];
+ struct nlattr *tb3[NUM_MT76_TM_RX_ATTRS];
+ struct nlattr *nl_attr, *cur;
+ struct atenl_rx_stat rx_cur, rx_diff = {};
+ u32 rcpi[4] = {};
+ u32 type_num = htonl(4);
+ s32 ib_rssi[4] = {}, wb_rssi[4] = {};
+ u8 path = an->anb[an->cur_band].chainmask;
+ u8 path_num = __builtin_popcount(path);
+ u8 *buf = hdr->data + 2;
+ int i, rem;
+
+ *(u32 *)buf = type_num;
+ buf += sizeof(type_num);
+
+#define RX_PUT_HDR(_hdr, _type, _val, _size) do { \
+ _hdr->type = htonl(_type); \
+ _hdr->val = htonl(_val); \
+ _hdr->len = htonl(_size); \
+ buf += sizeof(*_hdr); \
+ } while (0)
+
+ rx_hdr = (struct atenl_rx_info_hdr *)buf;
+ RX_PUT_HDR(rx_hdr, 0, BIT(an->cur_band), sizeof(*rx_band));
+ rx_band = (struct atenl_rx_info_band *)buf;
+ buf += sizeof(*rx_band);
+
+ rx_hdr = (struct atenl_rx_info_hdr *)buf;
+ RX_PUT_HDR(rx_hdr, 1, path, path_num * sizeof(*rx_path));
+ rx_path = (struct atenl_rx_info_path *)buf;
+ buf += path_num * sizeof(*rx_path);
+
+ rx_hdr = (struct atenl_rx_info_hdr *)buf;
+ RX_PUT_HDR(rx_hdr, 2, GENMASK(15, 0), 16 * sizeof(*rx_user));
+ rx_user = (struct atenl_rx_info_user *)buf;
+ buf += 16 * sizeof(*rx_user);
+
+ rx_hdr = (struct atenl_rx_info_hdr *)buf;
+ RX_PUT_HDR(rx_hdr, 3, BIT(0), sizeof(*rx_comm));
+ rx_comm = (struct atenl_rx_info_comm *)buf;
+ buf += sizeof(*rx_comm);
+
+ hdr->len = htons(buf - hdr->data);
+
+ nl_attr = unl_find_attr(&nl_priv->unl, msg, NL80211_ATTR_TESTDATA);
+ if (!nl_attr) {
+ fprintf(stderr, "Testdata attribute not found\n");
+ return NL_SKIP;
+ }
+
+ nla_parse_nested(tb1, MT76_TM_ATTR_MAX, nl_attr, testdata_policy);
+ nla_parse_nested(tb2, MT76_TM_STATS_ATTR_MAX,
+ tb1[MT76_TM_ATTR_STATS], stats_policy);
+
+ rx_cur.total = nla_get_u64(tb2[MT76_TM_STATS_ATTR_RX_PACKETS]);
+ rx_cur.err_cnt = nla_get_u64(tb2[MT76_TM_STATS_ATTR_RX_FCS_ERROR]);
+ rx_cur.len_mismatch = nla_get_u64(tb2[MT76_TM_STATS_ATTR_RX_LEN_MISMATCH]);
+ rx_cur.ok_cnt = rx_cur.total - rx_cur.err_cnt - rx_cur.len_mismatch;
+
+ if (!anb->reset_rx_cnt) {
+#define RX_COUNT_DIFF(_field) \
+ rx_diff._field = (rx_cur._field) - (anb->rx_stat._field)
+ RX_COUNT_DIFF(total);
+ RX_COUNT_DIFF(err_cnt);
+ RX_COUNT_DIFF(len_mismatch);
+ RX_COUNT_DIFF(ok_cnt);
+#undef RX_COUNT_DIFF
+
+ memcpy(&anb->rx_stat, &rx_cur, sizeof(anb->rx_stat));
+ }
+
+ rx_band->mac_rx_mdrdy_cnt = htonl((u32)rx_diff.total);
+ rx_band->mac_rx_fcs_err_cnt = htonl((u32)rx_diff.err_cnt);
+ rx_band->mac_rx_fcs_ok_cnt = htonl((u32)rx_diff.ok_cnt);
+ rx_band->mac_rx_len_mismatch = htonl((u32)rx_diff.len_mismatch);
+ rx_user->fcs_error_cnt = htonl((u32)rx_diff.err_cnt);
+
+ nla_parse_nested(tb3, MT76_TM_RX_ATTR_MAX,
+ tb2[MT76_TM_STATS_ATTR_LAST_RX], rx_policy);
+
+ rx_user->freq_offset = htonl(nla_get_u32(tb3[MT76_TM_RX_ATTR_FREQ_OFFSET]));
+ rx_user->snr = htonl(nla_get_u8(tb3[MT76_TM_RX_ATTR_SNR]));
+
+ i = 0;
+ nla_for_each_nested(cur, tb3[MT76_TM_RX_ATTR_RCPI], rem) {
+ if (nla_len(cur) != 1 || i >= 4)
+ break;
+
+ rcpi[i++] = nla_get_u8(cur);
+ }
+
+ i = 0;
+ nla_for_each_nested(cur, tb3[MT76_TM_RX_ATTR_IB_RSSI], rem) {
+ if (nla_len(cur) != 1 || i >= 4)
+ break;
+
+ ib_rssi[i++] = (s8)nla_get_u8(cur);
+ }
+
+ i = 0;
+ nla_for_each_nested(cur, tb3[MT76_TM_RX_ATTR_WB_RSSI], rem) {
+ if (nla_len(cur) != 1 || i >= 4)
+ break;
+
+ wb_rssi[i++] = (s8)nla_get_u8(cur);
+ }
+
+ for (i = 0; i < 4; i++) {
+ struct atenl_rx_info_path *path = &rx_path[i];
+
+ path->rcpi = htonl(rcpi[i]);
+ path->rssi = htonl(to_rssi((u8)rcpi[i]));
+ path->fagc_ib_rssi = htonl(ib_rssi[i]);
+ path->fagc_wb_rssi = htonl(wb_rssi[i]);
+ }
+
+ return NL_SKIP;
+}
+
+static int atenl_nl_get_rx_info(struct atenl *an, struct atenl_data *data,
+ struct atenl_nl_priv *nl_priv)
+{
+ struct nl_msg *msg = nl_priv->msg;
+ void *ptr;
+
+ nl_priv->an = an;
+ nl_priv->res = (void *)data;
+
+ ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+ if (!ptr)
+ return -ENOMEM;
+
+ nla_put_flag(msg, MT76_TM_ATTR_STATS);
+
+ nla_nest_end(msg, ptr);
+
+ return unl_genl_request(&nl_priv->unl, msg, atenl_nl_get_rx_info_cb,
+ (void *)nl_priv);
+}
+
+static int
+atenl_nl_set_ru(struct atenl *an, struct atenl_data *data,
+ struct atenl_nl_priv *nl_priv)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ struct nl_msg *msg;
+ u32 *v = (u32 *)(hdr->data + 4);
+ u32 seg0_num = ntohl(v[0]); /* v[1] seg1_num unused */
+ void *ptr;
+ int i, ret;
+
+ if (seg0_num > 8)
+ return -EINVAL;
+
+ for (i = 0, v = &v[2]; i < seg0_num; i++, v += 11) {
+ u32 ru_alloc = ntohl(v[1]);
+ u32 aid = ntohl(v[2]);
+ u32 ru_idx = ntohl(v[3]);
+ u32 mcs = ntohl(v[4]);
+ u32 ldpc = ntohl(v[5]);
+ u32 nss = ntohl(v[6]);
+ u32 tx_length = ntohl(v[8]);
+ char buf[10];
+
+ if (unl_genl_init(&nl_priv->unl, "nl80211") < 0) {
+ fprintf(stderr, "Failed to connect to nl80211\n");
+ return 2;
+ }
+
+ msg = unl_genl_msg(&nl_priv->unl, NL80211_CMD_TESTMODE, false);
+ nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, an->cur_band, phy_idx));
+
+ ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+ if (!ptr)
+ return -ENOMEM;
+
+ if (i == 0)
+ atenl_set_attr_state(an, msg, an->cur_band, MT76_TM_STATE_IDLE);
+
+ nla_put_u8(msg, MT76_TM_ATTR_AID, aid);
+ nla_put_u8(msg, MT76_TM_ATTR_RU_IDX, ru_idx);
+ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_IDX, mcs);
+ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_LDPC, ldpc);
+ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_NSS, nss);
+ nla_put_u32(msg, MT76_TM_ATTR_TX_LENGTH, tx_length);
+
+ ret = snprintf(buf, sizeof(buf), "%x", ru_alloc);
+ if (snprintf_error(sizeof(buf), ret))
+ return -EINVAL;
+
+ nla_put_u8(msg, MT76_TM_ATTR_RU_ALLOC, strtol(buf, NULL, 2));
+
+ nla_nest_end(msg, ptr);
+
+ unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+
+ unl_free(&nl_priv->unl);
+ }
+
+ return 0;
+}
+
+static int atenl_nl_ibf_set_val(struct atenl *an, struct atenl_data *data,
+ struct atenl_nl_priv *nl_priv)
+{
+#define MT_IBF(_act) MT76_TM_TXBF_ACT_##_act
+ static const u8 bf_act_map[] = {
+ [TXBF_ACT_IBF_PHASE_COMP] = MT_IBF(PHASE_COMP),
+ [TXBF_ACT_IBF_PROF_UPDATE] = MT_IBF(IBF_PROF_UPDATE),
+ [TXBF_ACT_EBF_PROF_UPDATE] = MT_IBF(EBF_PROF_UPDATE),
+ [TXBF_ACT_IBF_PHASE_CAL] = MT_IBF(PHASE_CAL),
+ };
+#undef MT_IBF
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ struct nl_msg *msg = nl_priv->msg;
+ u32 *v = (u32 *)(hdr->data + 4);
+ u32 action = ntohl(v[0]);
+ u16 val[8];
+ void *ptr, *a;
+ char cmd[64];
+ int i;
+
+ for (i = 0; i < 8; i++)
+ val[i] = ntohl(v[i + 1]);
+
+ atenl_dbg("%s: action = %u, val = %u, %u, %u, %u, %u\n",
+ __func__, action, val[0], val[1], val[2], val[3], val[4]);
+
+ ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+ if (!ptr)
+ return -ENOMEM;
+
+ switch (action) {
+ case TXBF_ACT_INIT:
+ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_MODE, MT76_TM_TX_MODE_HT);
+ nla_put_u8(msg, MT76_TM_ATTR_AID, 1);
+ nla_put_u8(msg, MT76_TM_ATTR_TXBF_ACT, MT76_TM_TXBF_ACT_INIT);
+
+ a = nla_nest_start(msg, MT76_TM_ATTR_TXBF_PARAM);
+ if (!a)
+ return -ENOMEM;
+ nla_put_u16(msg, 0, 1);
+ if (!val[0])
+ nla_put_u16(msg, 1, 1); /* init */
+ nla_nest_end(msg, a);
+ break;
+ case TXBF_ACT_CHANNEL:
+ sprintf(cmd, "iw dev mon%d set channel %u HT20",
+ get_band_val(an, an->cur_band, phy_idx), val[0]);
+ system(cmd);
+ printf("%s: %s", __func__, cmd);
+ nla_put_u8(msg, MT76_TM_ATTR_TXBF_ACT, MT76_TM_TXBF_ACT_UPDATE_CH);
+ a = nla_nest_start(msg, MT76_TM_ATTR_TXBF_PARAM);
+ if (!a)
+ return -ENOMEM;
+ nla_put_u16(msg, 0, 0);
+ nla_nest_end(msg, a);
+ break;
+ case TXBF_ACT_MCS:
+ nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_IDX, val[0]);
+ nla_put_u8(msg, MT76_TM_ATTR_TX_ANTENNA, (1 << DIV_ROUND_UP(val[0], 8)) - 1);
+ break;
+ case TXBF_ACT_POWER:
+ break;
+ case TXBF_ACT_TX_ANT:
+ nla_put_u8(msg, MT76_TM_ATTR_TX_ANTENNA, val[0]);
+ break;
+ case TXBF_ACT_RX_START:
+ atenl_set_attr_state(an, msg, an->cur_band, MT76_TM_STATE_RX_FRAMES);
+ break;
+ case TXBF_ACT_RX_ANT:
+ nla_put_u8(msg, MT76_TM_ATTR_TX_ANTENNA, val[0]);
+ break;
+ case TXBF_ACT_TX_PKT:
+ nla_put_u8(msg, MT76_TM_ATTR_AID, val[1]);
+ nla_put_u32(msg, MT76_TM_ATTR_TX_COUNT, 10000000);
+ nla_put_u8(msg, MT76_TM_ATTR_TXBF_ACT, MT76_TM_TXBF_ACT_TX_PREP);
+ a = nla_nest_start(msg, MT76_TM_ATTR_TXBF_PARAM);
+ if (!a)
+ return -ENOMEM;
+
+ for (i = 0; i < 5; i++)
+ nla_put_u16(msg, i, val[i]);
+ nla_nest_end(msg, a);
+
+ atenl_set_attr_state(an, msg, an->cur_band, MT76_TM_STATE_TX_FRAMES);
+ break;
+ case TXBF_ACT_IBF_PHASE_COMP:
+ case TXBF_ACT_IBF_PROF_UPDATE:
+ case TXBF_ACT_EBF_PROF_UPDATE:
+ case TXBF_ACT_IBF_PHASE_CAL:
+ nla_put_u8(msg, MT76_TM_ATTR_TXBF_ACT, bf_act_map[action]);
+ a = nla_nest_start(msg, MT76_TM_ATTR_TXBF_PARAM);
+ if (!a)
+ return -ENOMEM;
+
+ for (i = 0; i < 5; i++)
+ nla_put_u16(msg, i, val[i]);
+ nla_nest_end(msg, a);
+ break;
+ case TXBF_ACT_IBF_PHASE_E2P_UPDATE:
+ atenl_eeprom_read_from_driver(an, 0x651, 0x28 * 9);
+ atenl_eeprom_write_mtd(an);
+
+ nla_put_u8(msg, MT76_TM_ATTR_AID, 0);
+ nla_put_u8(msg, MT76_TM_ATTR_TXBF_ACT, MT76_TM_TXBF_ACT_INIT);
+
+ a = nla_nest_start(msg, MT76_TM_ATTR_TXBF_PARAM);
+ if (!a)
+ return -ENOMEM;
+ nla_put_u16(msg, 0, 0);
+ nla_nest_end(msg, a);
+ break;
+ default:
+ break;
+ }
+
+ nla_nest_end(msg, ptr);
+
+ memcpy(hdr->data + 2, &data->ext_id, 4);
+
+ return unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+}
+
+static int
+atenl_nl_ibf_get_status(struct atenl *an, struct atenl_data *data,
+ struct atenl_nl_priv *nl_priv)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ u32 status = htonl(1);
+
+ memcpy(hdr->data + 2, &data->ext_id, 4);
+ memcpy(hdr->data + 6, &status, 4);
+
+ return 0;
+}
+
+static int
+atenl_nl_ibf_profile_update_all(struct atenl *an, struct atenl_data *data,
+ struct atenl_nl_priv *nl_priv)
+{
+ struct atenl_cmd_hdr *hdr = atenl_hdr(data);
+ struct nl_msg *msg;
+ void *ptr, *a;
+ u32 *v = (u32 *)(hdr->data + 4);
+ u16 pfmu_idx = ntohl(v[0]);
+ int i;
+
+ for (i = 0, v = &v[5]; i < 64; i++, v += 5) {
+ int j;
+
+ if (unl_genl_init(&nl_priv->unl, "nl80211") < 0) {
+ fprintf(stderr, "Failed to connect to nl80211\n");
+ return 2;
+ }
+
+ msg = unl_genl_msg(&nl_priv->unl, NL80211_CMD_TESTMODE, false);
+ nla_put_u32(msg, NL80211_ATTR_WIPHY,
+ get_band_val(an, an->cur_band, phy_idx));
+
+ ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+ if (!ptr)
+ return -ENOMEM;
+
+ nla_put_u8(msg, MT76_TM_ATTR_TXBF_ACT, MT76_TM_TXBF_ACT_PROF_UPDATE_ALL);
+ a = nla_nest_start(msg, MT76_TM_ATTR_TXBF_PARAM);
+ if (!a)
+ return -ENOMEM;
+ nla_put_u16(msg, 0, pfmu_idx);
+
+ for (j = 0; j < 5; j++)
+ nla_put_u16(msg, j + 1, ntohl(v[j]));
+ nla_nest_end(msg, a);
+
+ nla_nest_end(msg, ptr);
+
+ unl_genl_request(&nl_priv->unl, msg, NULL, NULL);
+
+ unl_free(&nl_priv->unl);
+ }
+
+ memcpy(hdr->data + 2, &data->ext_id, 4);
+
+ return 0;
+}
+
+#define NL_OPS_GROUP(cmd, ...) [HQA_CMD_##cmd] = { __VA_ARGS__ }
+static const struct atenl_nl_ops nl_ops[] = {
+ NL_OPS_GROUP(SET_TX_PATH, .set=MT76_TM_ATTR_TX_ANTENNA),
+ NL_OPS_GROUP(SET_TX_POWER, .set=MT76_TM_ATTR_TX_POWER),
+ NL_OPS_GROUP(SET_RX_PATH, .set=MT76_TM_ATTR_TX_ANTENNA),
+ NL_OPS_GROUP(SET_FREQ_OFFSET, .set=MT76_TM_ATTR_FREQ_OFFSET),
+ NL_OPS_GROUP(SET_CFG, .ops=atenl_nl_set_cfg),
+ NL_OPS_GROUP(SET_TSSI, .ops=atenl_nl_set_cfg),
+ NL_OPS_GROUP(CONTINUOUS_TX, .ops=atenl_nl_continuous_tx),
+ NL_OPS_GROUP(GET_TX_INFO, .dump=MT76_TM_STATS_ATTR_TX_DONE),
+ NL_OPS_GROUP(GET_RX_INFO, .ops=atenl_nl_get_rx_info, .dump=true),
+ NL_OPS_GROUP(SET_RU, .ops=atenl_nl_set_ru),
+};
+#undef NL_OPS_GROUP
+
+#define NL_OPS_EXT(cmd, ...) [HQA_EXT_CMD_##cmd] = { __VA_ARGS__ }
+static const struct atenl_nl_ops nl_ops_ext[] = {
+ NL_OPS_EXT(SET_TX, .ops=atenl_nl_set_tx),
+ NL_OPS_EXT(START_TX, .ops=atenl_nl_tx),
+ NL_OPS_EXT(STOP_TX, .ops=atenl_nl_tx),
+ NL_OPS_EXT(START_RX, .ops=atenl_nl_rx),
+ NL_OPS_EXT(STOP_RX, .ops=atenl_nl_rx),
+ NL_OPS_EXT(OFF_CH_SCAN, .ops=atenl_off_ch_scan),
+ NL_OPS_EXT(IBF_SET_VAL, .ops=atenl_nl_ibf_set_val),
+ NL_OPS_EXT(IBF_GET_STATUS, .ops=atenl_nl_ibf_get_status),
+ NL_OPS_EXT(IBF_PROF_UPDATE_ALL, .ops=atenl_nl_ibf_profile_update_all),
+};
+#undef NL_OPS_EXT
+
+int atenl_nl_process(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_nl_priv nl_priv = {};
+ const struct atenl_nl_ops *ops;
+ struct nl_msg *msg;
+ int ret = 0;
+
+ if (data->ext_cmd != 0)
+ ops = &nl_ops_ext[data->ext_cmd];
+ else
+ ops = &nl_ops[data->cmd];
+
+ if (unl_genl_init(&nl_priv.unl, "nl80211") < 0) {
+ fprintf(stderr, "Failed to connect to nl80211\n");
+ return -1;
+ }
+
+ msg = unl_genl_msg(&nl_priv.unl, NL80211_CMD_TESTMODE, !!ops->dump);
+ nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, an->cur_band, phy_idx));
+ nl_priv.msg = msg;
+
+ if (ops->ops) {
+ ret = ops->ops(an, data, &nl_priv);
+ } else if (ops->dump) {
+ nl_priv.attr = ops->dump;
+ ret = atenl_nl_dump_attr(an, data, &nl_priv);
+ } else {
+ nl_priv.attr = ops->set;
+ ret = atenl_nl_set_attr(an, data, &nl_priv);
+ }
+
+ if (ret)
+ atenl_err("command process error: %d (%d)\n", data->cmd, data->ext_cmd);
+
+ unl_free(&nl_priv.unl);
+
+ return ret;
+}
+
+int atenl_nl_process_many(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_nl_priv nl_priv = {};
+ const struct atenl_nl_ops *ops;
+ int ret = 0;
+
+ if (data->ext_cmd != 0)
+ ops = &nl_ops_ext[data->ext_cmd];
+ else
+ ops = &nl_ops[data->cmd];
+
+ if (ops->ops)
+ ret = ops->ops(an, data, &nl_priv);
+
+ return ret;
+}
+
+int atenl_nl_set_state(struct atenl *an, u8 band,
+ enum mt76_testmode_state state)
+{
+ struct atenl_nl_priv nl_priv = {};
+ struct nl_msg *msg;
+ void *ptr;
+
+ if (unl_genl_init(&nl_priv.unl, "nl80211") < 0) {
+ fprintf(stderr, "Failed to connect to nl80211\n");
+ return 2;
+ }
+
+ msg = unl_genl_msg(&nl_priv.unl, NL80211_CMD_TESTMODE, false);
+ nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, band, phy_idx));
+
+ ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+ if (!ptr)
+ return -ENOMEM;
+
+ atenl_set_attr_state(an, msg, band, state);
+
+ nla_nest_end(msg, ptr);
+
+ unl_genl_request(&nl_priv.unl, msg, NULL, NULL);
+
+ unl_free(&nl_priv.unl);
+
+ return 0;
+}
+
+static int atenl_nl_check_mtd_cb(struct nl_msg *msg, void *arg)
+{
+ struct atenl_nl_priv *nl_priv = (struct atenl_nl_priv *)arg;
+ struct atenl *an = nl_priv->an;
+ struct nlattr *tb[NUM_MT76_TM_ATTRS];
+ struct nlattr *attr;
+
+ attr = unl_find_attr(&nl_priv->unl, msg, NL80211_ATTR_TESTDATA);
+ if (!attr)
+ return NL_SKIP;
+
+ nla_parse_nested(tb, MT76_TM_ATTR_MAX, attr, testdata_policy);
+ if (!tb[MT76_TM_ATTR_MTD_PART] || !tb[MT76_TM_ATTR_MTD_OFFSET])
+ return NL_SKIP;
+
+ an->mtd_part = strdup(nla_get_string(tb[MT76_TM_ATTR_MTD_PART]));
+ an->mtd_offset = nla_get_u32(tb[MT76_TM_ATTR_MTD_OFFSET]);
+
+ return NL_SKIP;
+}
+
+int atenl_nl_check_mtd(struct atenl *an)
+{
+ struct atenl_nl_priv nl_priv = { .an = an };
+ struct nl_msg *msg;
+
+ if (unl_genl_init(&nl_priv.unl, "nl80211") < 0) {
+ fprintf(stderr, "Failed to connect to nl80211\n");
+ return 2;
+ }
+
+ msg = unl_genl_msg(&nl_priv.unl, NL80211_CMD_TESTMODE, true);
+ nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, 0, phy_idx));
+ unl_genl_request(&nl_priv.unl, msg, atenl_nl_check_mtd_cb, (void *)&nl_priv);
+
+ unl_free(&nl_priv.unl);
+
+ return 0;
+}
+
+int atenl_nl_write_eeprom(struct atenl *an, u32 offset, u8 *val, int len)
+{
+ struct atenl_nl_priv nl_priv = {};
+ struct nl_msg *msg;
+ void *ptr, *a;
+ int i;
+
+ if (unl_genl_init(&nl_priv.unl, "nl80211") < 0) {
+ fprintf(stderr, "Failed to connect to nl80211\n");
+ return 2;
+ }
+
+ if (len > 16)
+ return -EINVAL;
+
+ msg = unl_genl_msg(&nl_priv.unl, NL80211_CMD_TESTMODE, false);
+ nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, 0, phy_idx));
+
+ ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+ if (!ptr)
+ return -ENOMEM;
+
+ nla_put_u8(msg, MT76_TM_ATTR_EEPROM_ACTION,
+ MT76_TM_EEPROM_ACTION_UPDATE_DATA);
+ nla_put_u32(msg, MT76_TM_ATTR_EEPROM_OFFSET, offset);
+
+ a = nla_nest_start(msg, MT76_TM_ATTR_EEPROM_VAL);
+ if (!a)
+ return -ENOMEM;
+
+ for (i = 0; i < len; i++)
+ if (nla_put_u8(msg, i, val[i]))
+ goto out;
+
+ nla_nest_end(msg, a);
+
+ nla_nest_end(msg, ptr);
+
+ unl_genl_request(&nl_priv.unl, msg, NULL, NULL);
+
+ unl_free(&nl_priv.unl);
+
+out:
+ return 0;
+}
+
+int atenl_nl_write_efuse_all(struct atenl *an, struct atenl_data *data)
+{
+ struct atenl_nl_priv nl_priv = {};
+ struct nl_msg *msg;
+ void *ptr;
+
+ if (unl_genl_init(&nl_priv.unl, "nl80211") < 0) {
+ fprintf(stderr, "Failed to connect to nl80211\n");
+ return 2;
+ }
+
+ msg = unl_genl_msg(&nl_priv.unl, NL80211_CMD_TESTMODE, false);
+ nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, 0, phy_idx));
+
+ ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+ if (!ptr)
+ return -ENOMEM;
+
+ nla_put_u8(msg, MT76_TM_ATTR_EEPROM_ACTION,
+ MT76_TM_EEPROM_ACTION_WRITE_TO_EFUSE);
+
+ nla_nest_end(msg, ptr);
+
+ unl_genl_request(&nl_priv.unl, msg, NULL, NULL);
+
+ unl_free(&nl_priv.unl);
+
+ return 0;
+}
+
+int atenl_nl_update_buffer_mode(struct atenl *an)
+{
+ struct atenl_nl_priv nl_priv = {};
+ struct nl_msg *msg;
+ void *ptr;
+
+ if (unl_genl_init(&nl_priv.unl, "nl80211") < 0) {
+ fprintf(stderr, "Failed to connect to nl80211\n");
+ return 2;
+ }
+
+ msg = unl_genl_msg(&nl_priv.unl, NL80211_CMD_TESTMODE, false);
+ nla_put_u32(msg, NL80211_ATTR_WIPHY, get_band_val(an, 0, phy_idx));
+
+ ptr = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
+ if (!ptr)
+ return -ENOMEM;
+
+ nla_put_u8(msg, MT76_TM_ATTR_EEPROM_ACTION,
+ MT76_TM_EEPROM_ACTION_UPDATE_BUFFER_MODE);
+
+ nla_nest_end(msg, ptr);
+
+ unl_genl_request(&nl_priv.unl, msg, NULL, NULL);
+
+ unl_free(&nl_priv.unl);
+
+ return 0;
+}
+
diff --git a/feed/atenl/src/nl.h b/feed/atenl/src/nl.h
new file mode 100644
index 0000000..2a0685d
--- /dev/null
+++ b/feed/atenl/src/nl.h
@@ -0,0 +1,242 @@
+/* Copyright (C) 2021-2022 Mediatek Inc. */
+#ifndef __ATENL_NL_H
+#define __ATENL_NL_H
+
+/* This is copied from mt76/testmode.h */
+
+/**
+ * enum mt76_testmode_attr - testmode attributes inside NL80211_ATTR_TESTDATA
+ *
+ * @MT76_TM_ATTR_UNSPEC: (invalid attribute)
+ *
+ * @MT76_TM_ATTR_RESET: reset parameters to default (flag)
+ * @MT76_TM_ATTR_STATE: test state (u32), see &enum mt76_testmode_state
+ *
+ * @MT76_TM_ATTR_MTD_PART: mtd partition used for eeprom data (string)
+ * @MT76_TM_ATTR_MTD_OFFSET: offset of eeprom data within the partition (u32)
+ *
+ * @MT76_TM_ATTR_TX_COUNT: configured number of frames to send when setting
+ * state to MT76_TM_STATE_TX_FRAMES (u32)
+ * @MT76_TM_ATTR_TX_PENDING: pending frames during MT76_TM_STATE_TX_FRAMES (u32)
+ * @MT76_TM_ATTR_TX_LENGTH: packet tx msdu length (u32)
+ * @MT76_TM_ATTR_TX_RATE_MODE: packet tx mode (u8, see &enum mt76_testmode_tx_mode)
+ * @MT76_TM_ATTR_TX_RATE_NSS: packet tx number of spatial streams (u8)
+ * @MT76_TM_ATTR_TX_RATE_IDX: packet tx rate/MCS index (u8)
+ * @MT76_TM_ATTR_TX_RATE_SGI: packet tx use short guard interval (u8)
+ * @MT76_TM_ATTR_TX_RATE_LDPC: packet tx enable LDPC (u8)
+ * @MT76_TM_ATTR_TX_RATE_STBC: packet tx enable STBC (u8)
+ * @MT76_TM_ATTR_TX_LTF: packet tx LTF, set 0 to 2 for 1x, 2x, and 4x LTF (u8)
+ *
+ * @MT76_TM_ATTR_TX_ANTENNA: tx antenna mask (u8)
+ * @MT76_TM_ATTR_TX_POWER_CONTROL: enable tx power control (u8)
+ * @MT76_TM_ATTR_TX_POWER: per-antenna tx power array (nested, u8 attrs)
+ *
+ * @MT76_TM_ATTR_FREQ_OFFSET: RF frequency offset (u32)
+ *
+ * @MT76_TM_ATTR_STATS: statistics (nested, see &enum mt76_testmode_stats_attr)
+ *
+ * @MT76_TM_ATTR_TX_SPE_IDX: tx spatial extension index (u8)
+ *
+ * @MT76_TM_ATTR_TX_DUTY_CYCLE: packet tx duty cycle (u8)
+ * @MT76_TM_ATTR_TX_IPG: tx inter-packet gap, in unit of us (u32)
+ * @MT76_TM_ATTR_TX_TIME: packet transmission time, in unit of us (u32)
+ *
+ */
+enum mt76_testmode_attr {
+ MT76_TM_ATTR_UNSPEC,
+
+ MT76_TM_ATTR_RESET,
+ MT76_TM_ATTR_STATE,
+
+ MT76_TM_ATTR_MTD_PART,
+ MT76_TM_ATTR_MTD_OFFSET,
+
+ MT76_TM_ATTR_TX_COUNT,
+ MT76_TM_ATTR_TX_LENGTH,
+ MT76_TM_ATTR_TX_RATE_MODE,
+ MT76_TM_ATTR_TX_RATE_NSS,
+ MT76_TM_ATTR_TX_RATE_IDX,
+ MT76_TM_ATTR_TX_RATE_SGI,
+ MT76_TM_ATTR_TX_RATE_LDPC,
+ MT76_TM_ATTR_TX_RATE_STBC,
+ MT76_TM_ATTR_TX_LTF,
+
+ MT76_TM_ATTR_TX_ANTENNA,
+ MT76_TM_ATTR_TX_POWER_CONTROL,
+ MT76_TM_ATTR_TX_POWER,
+
+ MT76_TM_ATTR_FREQ_OFFSET,
+
+ MT76_TM_ATTR_STATS,
+
+ MT76_TM_ATTR_TX_SPE_IDX,
+
+ MT76_TM_ATTR_TX_DUTY_CYCLE,
+ MT76_TM_ATTR_TX_IPG,
+ MT76_TM_ATTR_TX_TIME,
+
+ MT76_TM_ATTR_DRV_DATA,
+
+ MT76_TM_ATTR_MAC_ADDRS,
+
+ MT76_TM_ATTR_EEPROM_ACTION,
+ MT76_TM_ATTR_EEPROM_OFFSET,
+ MT76_TM_ATTR_EEPROM_VAL,
+
+ MT76_TM_ATTR_CFG,
+
+ MT76_TM_ATTR_OFF_CH_SCAN_CH,
+ MT76_TM_ATTR_OFF_CH_SCAN_CENTER_CH,
+ MT76_TM_ATTR_OFF_CH_SCAN_BW,
+ MT76_TM_ATTR_OFF_CH_SCAN_PATH,
+
+ MT76_TM_ATTR_AID,
+ MT76_TM_ATTR_RU_ALLOC,
+ MT76_TM_ATTR_RU_IDX,
+
+ MT76_TM_ATTR_TXBF_ACT,
+ MT76_TM_ATTR_TXBF_PARAM,
+
+ /* keep last */
+ NUM_MT76_TM_ATTRS,
+ MT76_TM_ATTR_MAX = NUM_MT76_TM_ATTRS - 1,
+};
+
+/**
+ * enum mt76_testmode_state - statistics attributes
+ *
+ * @MT76_TM_STATS_ATTR_TX_PENDING: pending tx frames (u32)
+ * @MT76_TM_STATS_ATTR_TX_QUEUED: queued tx frames (u32)
+ * @MT76_TM_STATS_ATTR_TX_QUEUED: completed tx frames (u32)
+ *
+ * @MT76_TM_STATS_ATTR_RX_PACKETS: number of rx packets (u64)
+ * @MT76_TM_STATS_ATTR_RX_FCS_ERROR: number of rx packets with FCS error (u64)
+ * @MT76_TM_STATS_ATTR_LAST_RX: information about the last received packet
+ * see &enum mt76_testmode_rx_attr
+ */
+enum mt76_testmode_stats_attr {
+ MT76_TM_STATS_ATTR_UNSPEC,
+ MT76_TM_STATS_ATTR_PAD,
+
+ MT76_TM_STATS_ATTR_TX_PENDING,
+ MT76_TM_STATS_ATTR_TX_QUEUED,
+ MT76_TM_STATS_ATTR_TX_DONE,
+
+ MT76_TM_STATS_ATTR_RX_PACKETS,
+ MT76_TM_STATS_ATTR_RX_FCS_ERROR,
+ MT76_TM_STATS_ATTR_LAST_RX,
+ MT76_TM_STATS_ATTR_RX_LEN_MISMATCH,
+
+ /* keep last */
+ NUM_MT76_TM_STATS_ATTRS,
+ MT76_TM_STATS_ATTR_MAX = NUM_MT76_TM_STATS_ATTRS - 1,
+};
+
+
+/**
+ * enum mt76_testmode_rx_attr - packet rx information
+ *
+ * @MT76_TM_RX_ATTR_FREQ_OFFSET: frequency offset (s32)
+ * @MT76_TM_RX_ATTR_RCPI: received channel power indicator (array, u8)
+ * @MT76_TM_RX_ATTR_IB_RSSI: internal inband RSSI (array, s8)
+ * @MT76_TM_RX_ATTR_WB_RSSI: internal wideband RSSI (array, s8)
+ * @MT76_TM_RX_ATTR_SNR: signal-to-noise ratio (u8)
+ */
+enum mt76_testmode_rx_attr {
+ MT76_TM_RX_ATTR_UNSPEC,
+
+ MT76_TM_RX_ATTR_FREQ_OFFSET,
+ MT76_TM_RX_ATTR_RCPI,
+ MT76_TM_RX_ATTR_IB_RSSI,
+ MT76_TM_RX_ATTR_WB_RSSI,
+ MT76_TM_RX_ATTR_SNR,
+
+ /* keep last */
+ NUM_MT76_TM_RX_ATTRS,
+ MT76_TM_RX_ATTR_MAX = NUM_MT76_TM_RX_ATTRS - 1,
+};
+
+/**
+ * enum mt76_testmode_state - phy test state
+ *
+ * @MT76_TM_STATE_OFF: test mode disabled (normal operation)
+ * @MT76_TM_STATE_IDLE: test mode enabled, but idle
+ * @MT76_TM_STATE_TX_FRAMES: send a fixed number of test frames
+ * @MT76_TM_STATE_RX_FRAMES: receive packets and keep statistics
+ * @MT76_TM_STATE_TX_CONT: waveform tx without time gap
+ * @MT76_TM_STATE_ON: test mode enabled used in offload firmware
+ */
+enum mt76_testmode_state {
+ MT76_TM_STATE_OFF,
+ MT76_TM_STATE_IDLE,
+ MT76_TM_STATE_TX_FRAMES,
+ MT76_TM_STATE_RX_FRAMES,
+ MT76_TM_STATE_TX_CONT,
+ MT76_TM_STATE_ON,
+
+ /* keep last */
+ NUM_MT76_TM_STATES,
+ MT76_TM_STATE_MAX = NUM_MT76_TM_STATES - 1,
+};
+
+/**
+ * enum mt76_testmode_tx_mode - packet tx phy mode
+ *
+ * @MT76_TM_TX_MODE_CCK: legacy CCK mode
+ * @MT76_TM_TX_MODE_OFDM: legacy OFDM mode
+ * @MT76_TM_TX_MODE_HT: 802.11n MCS
+ * @MT76_TM_TX_MODE_VHT: 802.11ac MCS
+ * @MT76_TM_TX_MODE_HE_SU: 802.11ax single-user MIMO
+ * @MT76_TM_TX_MODE_HE_EXT_SU: 802.11ax extended-range SU
+ * @MT76_TM_TX_MODE_HE_TB: 802.11ax trigger-based
+ * @MT76_TM_TX_MODE_HE_MU: 802.11ax multi-user MIMO
+ */
+enum mt76_testmode_tx_mode {
+ MT76_TM_TX_MODE_CCK,
+ MT76_TM_TX_MODE_OFDM,
+ MT76_TM_TX_MODE_HT,
+ MT76_TM_TX_MODE_VHT,
+ MT76_TM_TX_MODE_HE_SU,
+ MT76_TM_TX_MODE_HE_EXT_SU,
+ MT76_TM_TX_MODE_HE_TB,
+ MT76_TM_TX_MODE_HE_MU,
+
+ /* keep last */
+ NUM_MT76_TM_TX_MODES,
+ MT76_TM_TX_MODE_MAX = NUM_MT76_TM_TX_MODES - 1,
+};
+
+/**
+ * enum mt76_testmode_eeprom_action - eeprom setting actions
+ *
+ * @MT76_TM_EEPROM_ACTION_UPDATE_DATA: update rf values to specific
+ * eeprom data block
+ * @MT76_TM_EEPROM_ACTION_UPDATE_BUFFER_MODE: send updated eeprom data to fw
+ * @MT76_TM_EEPROM_ACTION_WRITE_TO_EFUSE: write eeprom data back to efuse
+ */
+enum mt76_testmode_eeprom_action {
+ MT76_TM_EEPROM_ACTION_UPDATE_DATA,
+ MT76_TM_EEPROM_ACTION_UPDATE_BUFFER_MODE,
+ MT76_TM_EEPROM_ACTION_WRITE_TO_EFUSE,
+
+ /* keep last */
+ NUM_MT76_TM_EEPROM_ACTION,
+ MT76_TM_EEPROM_ACTION_MAX = NUM_MT76_TM_EEPROM_ACTION - 1,
+};
+
+enum mt76_testmode_txbf_act {
+ MT76_TM_TXBF_ACT_INIT,
+ MT76_TM_TXBF_ACT_UPDATE_CH,
+ MT76_TM_TXBF_ACT_PHASE_COMP,
+ MT76_TM_TXBF_ACT_TX_PREP,
+ MT76_TM_TXBF_ACT_IBF_PROF_UPDATE,
+ MT76_TM_TXBF_ACT_EBF_PROF_UPDATE,
+ MT76_TM_TXBF_ACT_PHASE_CAL,
+ MT76_TM_TXBF_ACT_PROF_UPDATE_ALL,
+
+ /* keep last */
+ NUM_MT76_TM_TXBF_ACT,
+ MT76_TM_TXBF_ACT_MAX = NUM_MT76_TM_TXBF_ACT - 1,
+};
+
+#endif
diff --git a/feed/atenl/src/util.c b/feed/atenl/src/util.c
new file mode 100644
index 0000000..1b10663
--- /dev/null
+++ b/feed/atenl/src/util.c
@@ -0,0 +1,105 @@
+/* Copyright (C) 2021-2022 Mediatek Inc. */
+
+#include "atenl.h"
+
+int atenl_reg_read(struct atenl *an, u32 offset, u32 *res)
+{
+ char dir[64], buf[16];
+ unsigned long val;
+ int fd, ret;
+
+ /* write offset into regidx */
+ ret = snprintf(dir, sizeof(dir),
+ "/sys/kernel/debug/ieee80211/phy%d/mt76/regidx",
+ get_band_val(an, 0, phy_idx));
+ if (snprintf_error(sizeof(dir), ret))
+ return ret;
+
+ fd = open(dir, O_WRONLY);
+ if (fd < 0)
+ return fd;
+
+ ret = snprintf(buf, sizeof(buf), "0x%x", offset);
+ if (snprintf_error(sizeof(buf), ret))
+ goto out;
+
+ lseek(fd, 0, SEEK_SET);
+ write(fd, buf, sizeof(buf));
+ close(fd);
+
+ /* read value from regval */
+ ret = snprintf(dir, sizeof(dir),
+ "/sys/kernel/debug/ieee80211/phy%d/mt76/regval",
+ get_band_val(an, 0, phy_idx));
+ if (snprintf_error(sizeof(dir), ret))
+ return ret;
+
+ fd = open(dir, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ ret = read(fd, buf, sizeof(buf) - 1);
+ if (ret < 0)
+ goto out;
+ buf[ret] = 0;
+
+ val = strtoul(buf, NULL, 16);
+ if (val > (u32) -1)
+ return -EINVAL;
+
+ *res = val;
+ ret = 0;
+out:
+ close(fd);
+
+ return ret;
+}
+
+int atenl_reg_write(struct atenl *an, u32 offset, u32 val)
+{
+ char dir[64], buf[16];
+ int fd, ret;
+
+ /* write offset into regidx */
+ ret = snprintf(dir, sizeof(dir),
+ "/sys/kernel/debug/ieee80211/phy%d/mt76/regidx",
+ get_band_val(an, 0, phy_idx));
+ if (snprintf_error(sizeof(dir), ret))
+ return ret;
+
+ fd = open(dir, O_WRONLY);
+ if (fd < 0)
+ return fd;
+
+ ret = snprintf(buf, sizeof(buf), "0x%x", offset);
+ if (snprintf_error(sizeof(buf), ret))
+ goto out;
+
+ lseek(fd, 0, SEEK_SET);
+ write(fd, buf, sizeof(buf));
+ close(fd);
+
+ /* write value into regval */
+ ret = snprintf(dir, sizeof(dir),
+ "/sys/kernel/debug/ieee80211/phy%d/mt76/regval",
+ get_band_val(an, 0, phy_idx));
+ if (snprintf_error(sizeof(dir), ret))
+ return ret;
+
+ fd = open(dir, O_WRONLY);
+ if (fd < 0)
+ return fd;
+
+ ret = snprintf(buf, sizeof(buf), "0x%x", val);
+ if (snprintf_error(sizeof(buf), ret))
+ goto out;
+ buf[ret] = 0;
+
+ lseek(fd, 0, SEEK_SET);
+ write(fd, buf, sizeof(buf));
+ ret = 0;
+out:
+ close(fd);
+
+ return ret;
+}
diff --git a/feed/atenl/src/util.h b/feed/atenl/src/util.h
new file mode 100644
index 0000000..a02b956
--- /dev/null
+++ b/feed/atenl/src/util.h
@@ -0,0 +1,101 @@
+/* Copyright (C) 2021-2022 Mediatek Inc. */
+#ifndef __ATENL_UTIL_H
+#define __ATENL_UTIL_H
+
+#include <linux/const.h>
+#include <linux/if_arp.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <stdint.h>
+#include <string.h>
+
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef uint64_t u64;
+typedef int8_t s8;
+typedef int16_t s16;
+typedef int32_t s32;
+typedef int64_t s64, ktime_t;
+
+#ifndef __WORDSIZE
+#define __WORDSIZE (__SIZEOF_LONG__ * 8)
+#endif
+
+#ifndef BITS_PER_LONG
+#define BITS_PER_LONG __WORDSIZE
+#endif
+
+#define UL(x) (_UL(x))
+#define ULL(x) (_ULL(x))
+
+#define BIT(nr) (1UL << (nr))
+
+#define GENMASK_INPUT_CHECK(h, l) 0
+#define __GENMASK(h, l) \
+ (((~UL(0)) - (UL(1) << (l)) + 1) & \
+ (~UL(0) >> (BITS_PER_LONG - 1 - (h))))
+#define GENMASK(h, l) \
+ (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l))
+
+#define __bf_shf(x) (__builtin_ffsll(x) - 1)
+#define FIELD_GET(_mask, _reg) \
+ ({ \
+ (typeof(_mask))(((_reg) & (_mask)) >> __bf_shf(_mask)); \
+ })
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+#endif
+
+#ifndef DIV_ROUND_UP
+#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
+#endif
+
+#define PIPE_READ 0
+#define PIPE_WRITE 1
+
+#define MAC2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5]
+#define MACSTR "%02x:%02x:%02x:%02x:%02x:%02x"
+
+static inline bool ether_addr_equal(const u8 *addr1, const u8 *addr2)
+{
+ const u16 *a = (const u16 *)addr1;
+ const u16 *b = (const u16 *)addr2;
+
+ return ((a[0] ^ b[0]) | (a[1] ^ b[1]) | (a[2] ^ b[2])) == 0;
+}
+
+static inline bool is_broadcast_ether_addr(const u8 *addr)
+{
+ return (*(const u16 *)(addr + 0) &
+ *(const u16 *)(addr + 2) &
+ *(const u16 *)(addr + 4)) == 0xffff;
+}
+
+static inline bool is_multicast_ether_addr(const u8 *addr)
+{
+ return 0x01 & addr[0];
+}
+
+static inline void eth_broadcast_addr(u8 *addr)
+{
+ memset(addr, 0xff, ETH_ALEN);
+}
+
+static inline void ether_addr_copy(u8 *dst, const u8 *src)
+{
+ u16 *a = (u16 *)dst;
+ const u16 *b = (const u16 *)src;
+
+ a[0] = b[0];
+ a[1] = b[1];
+ a[2] = b[2];
+}
+
+static inline int snprintf_error(size_t size, int res)
+{
+ return res < 0 || (unsigned int) res >= size;
+}
+
+#endif