diff options
author | Victor Liu <victorliu@google.com> | 2024-01-23 11:40:42 -0800 |
---|---|---|
committer | Victor Liu <victorliu@google.com> | 2024-01-30 00:31:42 +0000 |
commit | ab669fdc037be92576600c4014e711dab6a53862 (patch) | |
tree | 323dcdcf21e3a7eb8984da5beabedb01a4bd63af | |
parent | cfd86ed41b8dd014dd7527c871a9844edac94000 (diff) | |
download | dw3000-ab669fdc037be92576600c4014e711dab6a53862.tar.gz |
merge partner-android/kernel/private/google-modules/uwb:android13-gs-pixel-5.10-24Q2 @ a2a6c47584ff7f57ffc9683ece1d803b74ecf4a4android-u-qpr3-beta-2_r0.7android-u-qpr3-beta-2_r0.6android-u-qpr3-beta-2_r0.5android-u-qpr3-beta-2_r0.4android-u-qpr3-beta-2_r0.3android-u-qpr3-beta-2_r0.2android-15-dp-2_r0.6android-15-dp-2_r0.3android-15-dp-2_r0.2android-15-dp-2_r0.1
Bug: 316022384
Signed-off-by: Victor Liu <victorliu@google.com>
Change-Id: Id69437d5844a0be1a8a53549c8e3e7403bae4bd3
(cherry picked from commit 7bcafea394ef769256485c53695aea343a32a7ad)
257 files changed, 54402 insertions, 0 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..e2d7a93 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,14 @@ +image: alpine + +pre-integration: + stage: deploy + variables: + CHILD_PROJECT_PATH: $CI_PROJECT_PATH + MR_BRANCH: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME + CHILDJOB: "1" + trigger: + project: qorvo/uwb-mobile/uwb-stack/release/ci + branch: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME + strategy: depend + only: + - merge_requests @@ -0,0 +1,19 @@ +Copyright (c) 2020 Qorvo US, Inc. + +This software is provided under the GNU General Public License, version 2 +(GPLv2), as well as under a Qorvo commercial license. + +You may choose to use this software under the terms of the GPLv2 License, +version 2 (“GPLv2”), as published by the Free Software Foundation. +You should have received a copy of the GPLv2 along with this program. If +not, see <http://www.gnu.org/licenses/>. + +This program is distributed under the GPLv2 in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more +details. + +If you cannot meet the requirements of the GPLv2, you may not use this +software for any purpose without first obtaining a commercial license from +Qorvo. +Please contact Qorvo to inquire about licensing terms. diff --git a/kernel/.gitignore b/kernel/.gitignore new file mode 100644 index 0000000..3017489 --- /dev/null +++ b/kernel/.gitignore @@ -0,0 +1,11 @@ +*.ko +*.mod.c +*.mod +*.o +*.a +.*.cmd +.*.d +.tmp_versions/ +Module.symvers +modules.order +tags diff --git a/kernel/BUILD.bazel b/kernel/BUILD.bazel new file mode 100644 index 0000000..327e497 --- /dev/null +++ b/kernel/BUILD.bazel @@ -0,0 +1,23 @@ +# NOTE: THIS FILE IS EXPERIMENTAL FOR THE BAZEL MIGRATION AND NOT USED FOR +# YOUR BUILDS CURRENTLY. +# +# It is not yet the source of truth for your build. If you're looking to modify +# the build file, modify the Android.bp file instead. Do *not* modify this file +# unless you have coordinated with the team managing the Soong to Bazel +# migration. + +load("//build/kleaf:kernel.bzl", "kernel_module") + +kernel_module( + name = "kernel.cloudripper", + outs = [ + "drivers/net/ieee802154/dw3000.ko", + "net/mcps802154/mcps802154.ko", + "net/mcps802154/mcps802154_region_fira.ko", + "net/mcps802154/mcps802154_region_nfcc_coex.ko", + ], + kernel_build = "//private/gs-google:cloudripper", + visibility = [ + "//private/gs-google:__pkg__", + ], +) diff --git a/kernel/Kbuild b/kernel/Kbuild new file mode 100644 index 0000000..653885a --- /dev/null +++ b/kernel/Kbuild @@ -0,0 +1,14 @@ +obj-m := net/mcps802154/ drivers/net/ieee802154/ + +subdir-ccflags-y := -I$(src) -I$(src)/include \ + -I$(srctree)/$(src) -I$(srctree)/$(src)/include -Werror -Wall $(DW3000_DEFS) + +ifeq ($(CONFIG_BACKPORT_LINUX),y) +NOSTDINC_FLAGS := \ + -I$(srctree)/backports/backport-include/ \ + -I$(srctree)/backports/backport-include/uapi \ + -I$(srctree)/backports/include/ \ + -I$(srctree)/backports/include/uapi \ + -include $(srctree)/backports/backport-include/backport/backport.h \ + $(CFLAGS) +endif diff --git a/kernel/Makefile b/kernel/Makefile new file mode 100644 index 0000000..a1cba71 --- /dev/null +++ b/kernel/Makefile @@ -0,0 +1,66 @@ +KDIR ?= /lib/modules/$(shell uname -r)/build +PROJECTS_BUILD := $(abspath $(CURDIR)/../projects/linux-build) + +ifeq ($(PROJECTS_BUILD),$(abspath $(KDIR)/../../../../..)) + +# Build in projects/linux-build, use a separated build tree. + +O := $(abspath $(KDIR)) +M := ../../../kernel +B := $(abspath $O/$M) + +$(info *** Building in $B) + +default all: modules + +modules modules_install: + mkdir -p $B + ln -srTf include $B/include + ln -srTf Kbuild $B/Kbuild + $(MAKE) -C $(KDIR) O=$O M=$M $@ + +clean help kernelrelease: + $(MAKE) -C $(KDIR) O=$O M=$M $@ + +bindeb-pkg: + ./builddeb -C $(KDIR) O=$O M=$M + +else # ifeq ($(PROJECTS_BUILD),$(abspath $(KDIR)/../../../../..)) + +# Regular build, build here. +ifneq ($(KERNEL_SRC),) +# Compile Android external module in OUT directory +# KERNEL_SRC = Kernel path +$(info *** Building Android in $O/$M ) + +KDIR := $(KERNEL_SRC) + +all: + $(MAKE) -C $(KDIR) M=$(M) modules $(KBUILD_OPTIONS) + +else # ifneq ($(KERNEL_SRC),) +M := $(CURDIR) +endif # ifneq ($(KERNEL_SRC),) + +B := $(abspath $O/$M) + +default: + $(MAKE) -C $(KDIR) M=$(M) +modules modules_install clean help kernelrelease: + $(MAKE) -C $(KDIR) M=$(M) $@ + +endif # ifeq ($(PROJECTS_BUILD),$(abspath $(KDIR)/../../../../..)) + +# Install generated dw3000.dtbo +dtbo_install: modules_install + d=`echo $(INSTALL_MOD_PATH)/lib/modules/* | sed 's#lib/modules#boot/dtbs#'` && \ + mkdir -p $$d/overlays && \ + find $B -name '*.dtbo' -print -exec cp -v {} $$d/overlays \; + +# Run kunit tests +tests: + ../tools/kunit/run $(KDIR) + +# Run coverage +cov: + ../tools/kunit/run $(KDIR) diff --git a/kernel/drivers/net/ieee802154/.gitignore b/kernel/drivers/net/ieee802154/.gitignore new file mode 100644 index 0000000..8dec1e9 --- /dev/null +++ b/kernel/drivers/net/ieee802154/.gitignore @@ -0,0 +1,31 @@ +# Kernel module compilation files +*.cmd +*.mod +*.mod.c +*.o +*.ko +modules.order +Module.symvers + +# Notes +*.md + +# Ctags's file +tags + +# Debugging scripts +*.sh + +# IDE files +.editorconfig +.clang-format + +# Symlinks +linux +decadriver +decawave +uwb-hal +dw1000 + +# Temporary +rootfs diff --git a/kernel/drivers/net/ieee802154/Kbuild b/kernel/drivers/net/ieee802154/Kbuild new file mode 100644 index 0000000..b4c0511 --- /dev/null +++ b/kernel/drivers/net/ieee802154/Kbuild @@ -0,0 +1,40 @@ +obj-$(CONFIG_MCPS_FAKE) += mcps802154_fake.o + +ccflags-$(CONFIG_DW3000_DEBUG) := -DDEBUG -g +ccflags-$(CONFIG_MCPS802154_TESTMODE) += -DCONFIG_MCPS802154_TESTMODE + +dw3000-y := \ + dw3000_nfcc_coex_core.o \ + dw3000_nfcc_coex_buffer.o \ + dw3000_nfcc_coex_msg.o \ + dw3000_nfcc_coex_mcps.o \ + dw3000_pctt_mcps.o \ + dw3000_calib.o \ + dw3000_chip_c0.o \ + dw3000_chip_d0.o \ + dw3000_chip_e0.o \ + dw3000_core.o \ + dw3000_mcps.o \ + dw3000_spi.o \ + dw3000_stm.o \ + dw3000_debugfs.o \ + dw3000_trc.o \ + dw3000_txpower_adjustment.o + +dw3000-$(CONFIG_MCPS802154_TESTMODE) += dw3000_testmode.o + +dw3000-core-tests-$(CONFIG_KUNIT) += dw3000_core_tests.o + +CFLAGS_dw3000_trc.o := -I$(src) -I$(srctree)/$(src) +ifneq ($(GITVERSION),) +CFLAGS_dw3000_spi.o := -DGITVERSION='"$(GITVERSION)"' +endif + +ifdef CONFIG_SPI_MASTER +# Build dw3000.ko only if SPI supported on target kernel. +obj-m += dw3000.o +endif +ifdef CONFIG_KUNIT +# Build test suites if kunit enabled in target kernel config. +obj-m += dw3000-core-tests.o +endif diff --git a/kernel/drivers/net/ieee802154/dw3000-overlay.dts b/kernel/drivers/net/ieee802154/dw3000-overlay.dts new file mode 100644 index 0000000..775913a --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000-overlay.dts @@ -0,0 +1,78 @@ +// Device Tree overlay for dw3000 for Raspberry Pi 4 +// Using a DW3700 shield connected using an Waveshare ARPI600 HAT. + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,brcm2711"; + + /* Ensure spi0 is enabled */ + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + /* Disable spi-dev for spi0.0 & spi0.1 */ + + fragment@1 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@2 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + /* Configure additional GPIOs */ + /* Check if wherre we wire it in driver */ + + fragment@3 { + target = <&gpio>; + __overlay__ { + dw3000_pins: dw3000_pins { + /* Wakeup, IRQ, RST on GPIO4, GPIO25 and GPIO24 */ + brcm,pins = <4 25 24>; + brcm,function = <0x0 0x0 0x0>; + /* Pull up/down config: 0: disabled, 1: down, 2: up */ + brcm,pull = <0 1 2>; + }; + }; + }; + + /* Add DW3000 device on spi0.0 */ + + fragment@4 { + target = <&spi0>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + dw3000: dw3000@0 { + compatible = "decawave,dw3000"; + reg = <0>; /* CE0 */ + pinctrl-names = "default"; + pinctrl-0 = <&dw3000_pins>; + interrupt-parent = <&gpio>; + interrupts = <25 4>; /*IRQ_TYPE_LEVEL_HIGH*/ + spi-max-frequency = <20000000>; + reset-gpio = <&gpio 24 4>; /* OPEN_DRAIN */ + decawave,eui64 = /bits/ 64 <0>; + decawave,panid = /bits/ 16 <0>; + status = "okay"; + }; + }; + }; + + __overrides__ { + eui64 = <&dw3000>,"decawave,eui64#0"; + panid = <&dw3000>,"decawave,panid;0"; + }; +}; diff --git a/kernel/drivers/net/ieee802154/dw3000.h b/kernel/drivers/net/ieee802154/dw3000.h new file mode 100644 index 0000000..6fb5d5e --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000.h @@ -0,0 +1,769 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_H +#define __DW3000_H + +#include <linux/device.h> +#include <linux/spi/spi.h> +#include <linux/skbuff.h> +#include <net/mcps802154.h> +#include <linux/pm_qos.h> +#include <linux/regulator/consumer.h> +#include "dw3000_chip.h" +#include "dw3000_stm.h" +#include "dw3000_calib.h" +#include "dw3000_testmode_nl.h" +#include "dw3000_nfcc_coex.h" +#include "dw3000_pctt.h" +#include "dw3000_debugfs.h" + +#undef BIT_MASK +#ifndef DEBUG +#define DEBUG 0 +#endif + +#define LOG(format, arg...) \ + do { \ + pr_info("dw3000: %s(): " format "\n", __func__, ##arg); \ + } while (0) + +extern unsigned dw3000_regulator_delay_us; + +/* Defined constants when SPI CRC mode is used */ +enum dw3000_spi_crc_mode { + DW3000_SPI_CRC_MODE_NO = 0, /* No CRC */ + DW3000_SPI_CRC_MODE_WR, /* This is used to enable SPI CRC check (the SPI + CRC check will be enabled on DW3000 and CRC-8 + added for SPI write transactions) */ + DW3000_SPI_CRC_MODE_WRRD /* This is used to optionally enable additional + CRC check on the SPI read operations, while + the CRC check on the SPI write operations is + also enabled */ +}; + +/* ISR data */ +struct dw3000_isr_data { + u64 status; /* initial value of register as ISR is entered */ + u16 datalength; /* length of frame */ + u64 ts_rctu; /* frame timestamp in RCTU unit */ + u8 dss_stat; /* value of the dual-SPI semaphore events */ + u8 rx_flags; /* RX frame flags, see dw3000_rx_flags */ +}; + +/* Time units and conversion factor */ +#define DW3000_DTU_PER_SYS_POWER 4 + +#define DW3000_CHIP_FREQ 499200000 +#define DW3000_CHIP_PER_SYS 2 +#define DW3000_CHIP_PER_DTU \ + (DW3000_CHIP_PER_SYS * (1 << DW3000_DTU_PER_SYS_POWER)) +#define DW3000_CHIP_PER_DLY 512 +#define DW3000_RCTU_PER_CHIP 128 +#define DW3000_RCTU_PER_DTU (DW3000_RCTU_PER_CHIP * DW3000_CHIP_PER_DTU) +#define DW3000_RCTU_PER_SYS (DW3000_RCTU_PER_CHIP * DW3000_CHIP_PER_SYS) +#define DW3000_RCTU_PER_DLY (DW3000_CHIP_PER_DLY / DW3000_RCTU_PER_CHIP) + +#define DW3000_DTU_FREQ (DW3000_CHIP_FREQ / DW3000_CHIP_PER_DTU) + +/* 6.9.1.5 in 4z, for HRP UWB PHY: + 416 chips = 416 / (499.2 * 10^6) ~= 833.33 ns */ +#define DW3000_DTU_PER_RSTU (416 / DW3000_CHIP_PER_DTU) +#define DW3000_DTU_PER_DLY (DW3000_CHIP_PER_DLY / DW3000_CHIP_PER_DTU) +#define DW3000_SYS_PER_DLY (DW3000_CHIP_PER_DLY / DW3000_CHIP_PER_SYS) + +#define DW3000_ANTICIP_DTU (16 * (DW3000_DTU_FREQ / 1000)) + +#define DTU_TO_US(x) (int)((s64)(x)*1000000 / DW3000_DTU_FREQ) +#define US_TO_DTU(x) (u32)((s64)(x)*DW3000_DTU_FREQ / 1000000) +#define NS_TO_DTU(x) (u32)((s64)(x) * (DW3000_DTU_FREQ / 100000) / 10000) + +#define DW3000_RX_ENABLE_STARTUP_DLY 16 +#define DW3000_RX_ENABLE_STARTUP_DTU \ + (DW3000_RX_ENABLE_STARTUP_DLY * DW3000_CHIP_PER_DLY / \ + DW3000_CHIP_PER_DTU) + +/* UWB High band 802.15.4a-2007. Only channels 5 & 9 for DW3000. */ +#define DW3000_SUPPORTED_CHANNELS ((1 << 5) | (1 << 9)) + +/* Number of symbols of accumulation to wait before checking + for an SFD pattern, when Ipatov len > 64 */ +#define DW3000_RX_SFD_HLDOFF 0x20 +/* Default number of symbols of accumulation to wait before checking + for an SFD pattern */ +#define DW3000_RX_SFD_HLDOFF_DEF 0x14 + +/** + * typedef dw3000_wakeup_done_cb - Wake up done handler. + * @dw: Driver context. + * + * Return: 0 on success, else an error. + */ +typedef int (*dw3000_wakeup_done_cb)(struct dw3000 *dw); + +/** + * typedef dw3000_idle_timeout_cb - Idle timeout handler. + * @dw: Driver context. + * + * Return: 0 on success, else an error. + */ +typedef int (*dw3000_idle_timeout_cb)(struct dw3000 *dw); + +/** + * struct dw3000_otp_data - data read from OTP memory of DW3000 device + * @partID: device part ID + * @lotID: device lot ID + * @ldo_tune_lo: tuned value used by chip specific prog_ldo_and_bias_tune + * @ldo_tune_hi: tuned value used by chip specific prog_ldo_and_bias_tune + * @bias_tune: tuned value used by chip specific prog_ldo_and_bias_tune + * @dgc_addr: dgc_addr value used by chip specific prog_ldo_and_bias_tune + * @xtal_trim: tuned value used by dw3000_prog_xtrim + * @vBatP: vBatP + * @tempP: tempP + * @rev: OTP revision + * @mode: Saved last OTP read mode to avoid multiple read + * @pll_coarse_code: PLL coarse code use by chip->prog_pll_coarse_code + */ +struct dw3000_otp_data { + u32 partID; + u32 lotID; + u32 ldo_tune_lo; + u32 ldo_tune_hi; + u32 bias_tune; + u32 dgc_addr; + u8 xtal_trim; + u8 vBatP; + u8 tempP; + u8 rev; + int mode; + u32 pll_coarse_code; +}; + +/** + * enum dw3000_ciagdiag_reg_select - CIA diagnostic register selector config. + * @DW3000_CIA_DIAG_REG_SELECT_WITHOUT_STS: without STS + * @DW3000_CIA_DIAG_REG_SELECT_WITH_STS: with STS + * @DW3000_CIA_DIAG_REG_SELECT_WITH_PDAO_M3: PDOA mode 3 + * + * According to DW3000's configuration, we must read some values + * (e.g: channel impulse response power, preamble accumulation count) + * in different registers in the CIA interface. + */ +enum dw3000_ciagdiag_reg_select { + DW3000_CIA_DIAG_REG_SELECT_WITHOUT_STS = 0, + DW3000_CIA_DIAG_REG_SELECT_WITH_STS = 1, + DW3000_CIA_DIAG_REG_SELECT_WITH_PDAO_M3 = 2, +}; + +/** + * struct dw3000_local_data - Local data and register cache + * @spicrc: current SPI crc mode + * @ciadiag_reg_select: CIA diagnostic register selector according to DW3000's + * config + * @ciadiag_enabled: CIA diagnostic on/off + * @dgc_otp_set: true if DGC info is programmed into OTP + * @check_cfo: true when CFO checking is required for next RX frame + * @ack_time: Auto ack turnaroud time + * @dblbuffon: double buffering mode + * @xtal_bias: XTAL trimming adjustment value + * @max_frames_len: current configured max frame length + * @sleep_mode: bitfield for work to be done on wakeup + * @ststhreshold: current computed STS threshold + * @tx_fctrl: Transmit frame control + * @rx_timeout_pac: Preamble detection timeout period in units of PAC size + * symbols + * @rx_frame_timeout_dly: reception frame timeout period in units of dly. + * @w4r_time: Wait-for-response time (RX after TX delay) + * @sts_key: STS Key + * @sts_iv: STS IV + */ +struct dw3000_local_data { + enum dw3000_spi_crc_mode spicrc; + enum dw3000_ciagdiag_reg_select ciadiag_reg_select; + bool ciadiag_enabled : 1; + bool dgc_otp_set : 1; + bool check_cfo : 1; + u8 dblbuffon; + u8 ack_time; + s8 xtal_bias; + u16 sleep_mode; + u16 max_frames_len; + s16 ststhreshold; + u16 rx_timeout_pac; + u32 rx_frame_timeout_dly; + u32 tx_fctrl; + u32 w4r_time; + u8 sts_key[AES_KEYSIZE_128]; + u8 sts_iv[AES_BLOCK_SIZE]; +}; + +/* Statistics items */ +enum dw3000_stats_items { + DW3000_STATS_RX_GOOD, + DW3000_STATS_RX_ERROR, + DW3000_STATS_RX_TO, + __DW3000_STATS_COUNT +}; + +/** + * struct dw3000_stats - DW3000 statistics + * @count: Count per-items + * @rssi: Last RSSI data per type + * @enabled: Stats enabled flag + * @indexes: Free-running index per-items + */ +struct dw3000_stats { + /* Total stats */ + u16 count[__DW3000_STATS_COUNT]; + /* Required data array for calculation of the RSSI average */ + struct dw3000_rssi rssi[DW3000_RSSI_REPORTS_MAX]; + /* Stats on/off */ + bool enabled; + u16 indexes[__DW3000_STATS_COUNT]; +}; + +/* Maximum skb length + * + * Maximum supported frame size minus the checksum. + */ +#define DW3000_MAX_SKB_LEN (IEEE802154_MAX_SIFS_FRAME_SIZE - IEEE802154_FCS_LEN) + +/* Additional information on rx. */ +enum dw3000_rx_flags { + /* Set if an automatic ack is send. */ + DW3000_RX_FLAG_AACK = BIT(0), + /* Set if no data. */ + DW3000_RX_FLAG_ND = BIT(1), + /* Set if timestamp known. */ + DW3000_RX_FLAG_TS = BIT(2), + /* Ranging bit */ + DW3000_RX_FLAG_RNG = BIT(3), + /* CIA done */ + DW3000_RX_FLAG_CIA = BIT(4), + /* CIA error */ + DW3000_RX_FLAG_CER = BIT(5), + /* STS error */ + DW3000_RX_FLAG_CPER = BIT(6) +}; + +/* Receive descriptor */ +struct dw3000_rx { + /* Receive lock */ + spinlock_t lock; + /* Socket buffer */ + struct sk_buff *skb; + /* Frame timestamp */ + u64 ts_rctu; + /* Additional information on rx. See dw3000_rx_flags. */ + u8 flags; + /* Busy flag */ + u8 busy; +}; + +/* DW3000 STS length field of the CP_CFG register (unit of 8 symbols bloc) */ +enum dw3000_sts_lengths { + DW3000_STS_LEN_8 = 0, + DW3000_STS_LEN_16 = 1, + DW3000_STS_LEN_32 = 2, + DW3000_STS_LEN_64 = 3, + DW3000_STS_LEN_128 = 4, + DW3000_STS_LEN_256 = 5, + DW3000_STS_LEN_512 = 6, + DW3000_STS_LEN_1024 = 7, + DW3000_STS_LEN_2048 = 8 +}; + +/* DW3000 power supply structure */ +struct dw3000_power_control { + /* Power supply generic */ + struct regulator *regulator_vdd; + /* Power supply 1.8V */ + struct regulator *regulator_1p8; + /* Power supply 2.5V */ + struct regulator *regulator_2p5; +}; + +/** + * struct dw3000_config - Structure holding current device configuration + * @chan: Channel number (5 or 9) + * @txPreambLength: DW3000_PLEN_64..DW3000_PLEN_4096 + * @txCode: TX preamble code (the code configures the PRF, e.g. 9 -> PRF of 64 MHz) + * @rxCode: RX preamble code (the code configures the PRF, e.g. 9 -> PRF of 64 MHz) + * @sfdType: SFD type (0 for short IEEE 8b standard, 1 for DW 8b, 2 for DW 16b, 2 for 4z BPRF) + * @dataRate: Data rate {DW3000_BR_850K or DW3000_BR_6M8} + * @phrMode: PHR mode {0x0 - standard DW3000_PHRMODE_STD, 0x3 - extended frames DW3000_PHRMODE_EXT} + * @phrRate: PHR rate {0x0 - standard DW3000_PHRRATE_STD, 0x1 - at datarate DW3000_PHRRATE_DTA} + * @sfdTO: SFD timeout value (in symbols) + * @stsMode: STS mode (no STS, STS before PHR or STS after data) + * @stsLength: STS length (see enum dw3000_sts_lengths) + * @pdoaMode: PDOA mode + * @ant: Antennas currently connected to RF1 & RF2 ports respectively + * @pdoaOffset: Calibrated PDOA offset + * @pdoaLut: Pointer to calibrated PDOA to AoA look-up table + * @rmarkerOffset: Calibrated rmarker offset + * @promisc: Promiscuous mode enabled? + * @alternate_pulse_shape: set alternate pulse shape + * @hw_addr_filt: HW filter configuration + */ +struct dw3000_config { + u8 chan; + u8 txPreambLength; + u8 txCode; + u8 rxCode; + u8 sfdType; + u8 dataRate; + u8 phrMode; + u8 phrRate; + u16 sfdTO; + u8 stsMode; + enum dw3000_sts_lengths stsLength; + u8 pdoaMode; + s8 ant[2]; + s16 pdoaOffset; + const dw3000_pdoa_lut_t *pdoaLut; + u32 rmarkerOffset; + bool promisc; + bool alternate_pulse_shape; + struct ieee802154_hw_addr_filt hw_addr_filt; +}; + +/** + * struct dw3000_txconfig - Structure holding current TX configuration + * @PGdly: PG delay + * @PGcount: PG count + * @power: TX power for 1ms frame + * 31:24 TX_CP_PWR + * 23:16 TX_SHR_PWR + * 15:8 TX_PHR_PWR + * 7:0 TX_DATA_PWR + * @smart: TX smart power enabled flag + * @testmode_enabled: Normal or test mode + */ +struct dw3000_txconfig { + u8 PGdly; + u8 PGcount; + u32 power; + bool smart; + bool testmode_enabled; +}; + +enum operational_state { + DW3000_OP_STATE_OFF = 0, + DW3000_OP_STATE_DEEP_SLEEP, + DW3000_OP_STATE_SLEEP, + DW3000_OP_STATE_WAKE_UP, + DW3000_OP_STATE_INIT_RC, + DW3000_OP_STATE_IDLE_RC, + DW3000_OP_STATE_IDLE_PLL, + DW3000_OP_STATE_TX_WAIT, + DW3000_OP_STATE_TX, + DW3000_OP_STATE_RX_WAIT, + DW3000_OP_STATE_RX, + DW3000_OP_STATE_MAX, +}; + +/** + * struct sysfs_power_stats - DW3000 device power related data + * @dur: accumulated duration in selected state in ns except for RX/TX where + * duration is in DTU + * @count: number of time this state was active + */ +struct sysfs_power_stats { + u64 dur; + u64 count; +}; + +/** + * enum power_state - DW3000 device current power state + * @DW3000_PWR_OFF: DW3000 is OFF (unpowered or in reset) + * @DW3000_PWR_DEEPSLEEP: DW3000 is ACTIVE (started) but in DEEP SLEEP + * @DW3000_PWR_RUN: DW3000 is ACTIVE (include RX/TX state below) + * @DW3000_PWR_IDLE: DW3000 is ACTIVE but IDLE (only count is used for it) + * @DW3000_PWR_RX: DW3000 is currently RECEIVING + * @DW3000_PWR_TX: DW3000 is currently TRANSMITTING + * @DW3000_PWR_MAX: Number of power states stored in struct dw3000_power + */ +enum power_state { + DW3000_PWR_OFF = 0, + DW3000_PWR_DEEPSLEEP, + DW3000_PWR_RUN, + DW3000_PWR_IDLE, + DW3000_PWR_RX, + DW3000_PWR_TX, + DW3000_PWR_MAX, +}; + +/** + * enum config_changed_flags - values for configuration changed bitfield + * @DW3000_AFILT_SADDR_CHANGED: same as IEEE802154_AFILT_SADDR_CHANGED + * @DW3000_AFILT_IEEEADDR_CHANGED: same as IEEE802154_AFILT_IEEEADDR_CHANGED + * @DW3000_AFILT_PANID_CHANGED: same as IEEE802154_AFILT_PANID_CHANGED + * @DW3000_AFILT_PANC_CHANGED: same as IEEE802154_AFILT_PANC_CHANGED + * @DW3000_CHANNEL_CHANGED: channel configuration changed + * @DW3000_PCODE_CHANGED: preamble code configuration changed + * @DW3000_PREAMBLE_LENGTH_CHANGED: length of preamble changed + * @DW3000_SFD_CHANGED: Start Frame Delimiter changed + * @DW3000_PHR_RATE_CHANGED: Physical HeadeR rate changed + * @DW3000_DATA_RATE_CHANGED: Data rate changed + */ +enum config_changed_flags { + DW3000_AFILT_SADDR_CHANGED = IEEE802154_AFILT_SADDR_CHANGED, + DW3000_AFILT_IEEEADDR_CHANGED = IEEE802154_AFILT_IEEEADDR_CHANGED, + DW3000_AFILT_PANID_CHANGED = IEEE802154_AFILT_PANID_CHANGED, + DW3000_AFILT_PANC_CHANGED = IEEE802154_AFILT_PANC_CHANGED, + DW3000_CHANNEL_CHANGED = BIT(4), + DW3000_PCODE_CHANGED = BIT(5), + DW3000_PREAMBLE_LENGTH_CHANGED = BIT(6), + DW3000_SFD_CHANGED = BIT(7), + DW3000_PHR_RATE_CHANGED = BIT(8), + DW3000_DATA_RATE_CHANGED = BIT(9), +}; + +/** + * struct dw3000_power - DW3000 device power related data + * @stats: accumulated statistics defined by struct sysfs_power_stats + * @start_time: timestamp of current state start + * @cur_state: current state defined by enum power_state + * @tx_adjust: TX time adjustment based on frame length + * @rx_start: RX start date in DTU for RX time adjustment + * @interrupts: Hardware interrupts count on the device. + */ +struct dw3000_power { + struct sysfs_power_stats stats[DW3000_PWR_MAX]; + u64 start_time; + int cur_state; + int tx_adjust; + u32 rx_start; + atomic64_t interrupts; +}; + +/** + * struct dw3000_deep_sleep_state - Useful data to restore on wake up + * @next_operational_state: operational state to enter after DEEP SLEEP mode + * @config_changed: bitfield of configuration changed during DEEP-SLEEP + * @frame_idx: saved frame index to use for deferred TX/RX + * @tx_skb: saved frame to transmit for deferred TX + * @tx_config: saved config to use for deferred TX + * @rx_config: saved parameter for deferred RX + * @regbackup: registers backup to detect diff + * @compare_work: deferred registers backup compare work + */ +struct dw3000_deep_sleep_state { + enum operational_state next_operational_state; + unsigned long config_changed; + int frame_idx; + struct sk_buff *tx_skb; + union { + struct mcps802154_tx_frame_config tx_config; + struct mcps802154_rx_frame_config rx_config; + }; +#ifdef CONFIG_DW3000_DEBUG + void *regbackup; + struct work_struct compare_work; +#endif +}; + +/** enum dw3000_rctu_conv_state - DTU to RCTU conversion state + * @UNALIGNED: need to redo all + * @ALIGNED: aligned to DTU but not synced yet with RCTU + * @ALIGNED_SYNCED: all done + * + */ +enum dw3000_rctu_conv_state { UNALIGNED = 0, ALIGNED, ALIGNED_SYNCED }; + +/** + * struct dw3000_rctu_conv - DTU to RCTU conversion data + * @state: current state of converter + * @alignment_rmarker_dtu: alignment DTU value + * @synced_rmarker_rctu: rmarker RCTU value + */ +struct dw3000_rctu_conv { + enum dw3000_rctu_conv_state state; + u32 alignment_rmarker_dtu; + u64 synced_rmarker_rctu; +}; + +struct dw3000_cir_data; + +#define DW3000_MAX_QUEUED_SPI_XFER 32 +#define DW3000_QUEUED_SPI_BUFFER_SZ 2048 + +/* Number of samples to average. */ +#define DW3000_NB_AVERAGE 1 + +/** + * struct dw3000_rx_ctx - Custom rx context use with rx_init/rx_get_measurement. + * @pdoa_rad_q11: Array of PDoA measurements. + * @aoa_rad_q11: Array of AoA measurements. + * @index: Index for pdoa_rad_q11 and aoa_rad_q11 arrays. + * @sample_valid_nb: Number of valid measurements in array, used for average. + */ +struct dw3000_rx_ctx { + int pdoa_rad_q11[DW3000_NB_AVERAGE]; + int aoa_rad_q11[DW3000_NB_AVERAGE]; + int index; + int sample_valid_nb; +}; + +/** + * struct dw3000 - main DW3000 device structure + * @spi: pointer to corresponding spi device + * @dev: pointer to generic device holding sysfs attributes + * @pm_qos_req: CPU latency request object + * @sysfs_power_dir: kobject holding sysfs power directory + * @chip_ops: version specific chip operations + * @llhw: pointer to associated struct mcps802154_llhw + * @config: current running chip configuration + * @txconfig: current running TX configuration + * @data: local data and register cache + * @otp_data: OTP data cache + * @calib_data: calibration data + * @stats: statistics + * @power: power related statistics and states + * @rctu_conv: RCTU converter + * @time_zero_ns: initial time in ns to convert ktime to/from DTU + * @dtu_sync: synchro DTU immediately after wakeup + * @sys_time_sync: device SYS_TIME immediately after wakeup + * @sleep_enter_dtu: DTU when entered sleep + * @deep_sleep_state: state related to the deep sleep + * @idle_timeout: true when idle_timeout_dtu is a valid date. + * @idle_timeout_dtu: timestamp requested to leave idle mode. + * @idle_timer: timer to exiting after an idle call. + * @timer_expired_work: call mcps802154_timer_expired outside driver kthread. + * @wakeup_done_cb: callback called on wakeup done. + * @idle_timeout_cb: callback when idle timer expired + * @auto_sleep_margin_us: configurable automatic deep sleep margin + * @need_ranging_clock: true if next operation need ranging clock + * and deep sleep cannot be used + * @nfcc_coex: NFCC coexistence specific context + * @pctt: PCTT specific context + * @chip_dev_id: identified chip device ID + * @chip_idx: index of current chip in supported devices array + * @of_max_speed_hz: saved SPI max speed from device tree + * @iface_is_started: interface status + * @has_lock_pm: power management locked status + * @reset_gpio: GPIO to use for hard reset + * @regulators: structure that holds regulators to toggle + * @is_powered: save current regulators state + * @chips_per_pac: chips per PAC unit + * @pre_timeout_pac: preamble timeout in PAC unit + * @coex_delay_us: WiFi coexistence GPIO delay in us + * @coex_margin_us: WiFi coexistence GPIO margin in us + * @coex_interval_us: Minimum interval between two operations in us + * under which WiFi coexistence GPIO is kept active + * @coex_gpio: WiFi coexistence GPIO, >= 0 if activated + * @coex_enabled: WiFi coexistence activation + * @coex_status: WiFi coexistence GPIO status, 1 if activated + * @lna_pa_mode: LNA/PA configuration to use + * @autoack: auto-ack status, true if activated + * @pgf_cal_running: true if pgf calibration is running + * @stm: High-priority thread state machine + * @rx: received skbuff and associated spinlock + * @current_operational_state: internal operational state of the chip + * @operational_state_wq: Wait queue for operational state + * @debugfs: debugfs informations + * @spi_pid: PID of the SPI controller pump messages + * @dw3000_pid: PID the dw3000 state machine thread + * @restricted_channels: bit field of restricted channels + * @tx_rf2: parameter to enable the tx on rf2 port + * @rx_rf2: parameter to enable the rx on rf2 port + * @cir_data_changed: true if buffer data have been reallocated + * @full_cia_read: CIA registers fully loaded into cir_data struct + * @cir_data: allocated CIR exploitation data + * @msg_queue: SPI message holding transfer queue + * @msg_queue_xfer: next transfer available + * @msg_queue_xfer_count: number of queued transfers + * @msg_queue_buf: buffer for queued transfers + * @msg_queue_buf_pos: current position in buffer + * @msg_mutex: mutex protecting @msg_readwrite_fdx + * @msg_readwrite_fdx: pre-computed generic register read/write SPI message + * @msg_fast_command: pre-computed fast command SPI message + * @msg_read_cir_pwr: pre-computed SPI message + * @msg_read_pacc_cnt: pre-computed SPI message + * @msg_read_rdb_status: pre-computed SPI message + * @msg_read_rx_timestamp: pre-computed SPI message + * @msg_read_rx_timestamp_a: pre-computed SPI message + * @msg_read_rx_timestamp_b: pre-computed SPI message + * @msg_read_sys_status: pre-computed SPI message + * @msg_read_all_sys_status: pre-computed SPI message + * @msg_read_sys_time: pre-computed SPI message + * @msg_write_sys_status: pre-computed SPI message + * @msg_write_all_sys_status: pre-computed SPI message + * @msg_read_dss_status: pre-computed SPI message + * @msg_write_dss_status: pre-computed SPI message + * @msg_write_spi_collision_status: pre-computed SPI message + */ +struct dw3000 { + /* SPI device */ + struct spi_device *spi; + /* Generic device */ + struct device *dev; + /* PM QoS object */ + struct pm_qos_request pm_qos_req; + /* Kernel object holding sysfs power sub-directory */ + struct kobject sysfs_power_dir; + /* Chip version specific operations */ + const struct dw3000_chip_ops *chip_ops; + /* MCPS 802.15.4 device */ + struct mcps802154_llhw *llhw; + /* Configuration */ + struct dw3000_config config; + struct dw3000_txconfig txconfig; + /* Data */ + struct dw3000_local_data data; + struct dw3000_otp_data otp_data; + struct dw3000_calibration_data calib_data; + /* Statistics */ + struct dw3000_stats stats; + struct dw3000_power power; + /* Time conversion */ + struct dw3000_rctu_conv rctu_conv; + s64 time_zero_ns; + u32 dtu_sync; + u32 sys_time_sync; + u32 sleep_enter_dtu; + /* Deep Sleep & MCPS Idle management */ + struct dw3000_deep_sleep_state deep_sleep_state; + bool idle_timeout; + u32 idle_timeout_dtu; + struct hrtimer idle_timer; + struct work_struct timer_expired_work; + dw3000_wakeup_done_cb wakeup_done_cb; + dw3000_idle_timeout_cb idle_timeout_cb; + bool need_ranging_clock; + int auto_sleep_margin_us; + /* NFCC coexistence specific context. */ + struct dw3000_nfcc_coex nfcc_coex; + /* PCTT specific context. */ + struct dw3000_pctt pctt; + /* Chip type management */ + u32 chip_dev_id; + u32 chip_idx; + /* Saved SPI max speed from device tree */ + u32 of_max_speed_hz; + /* True when MCPS start() operation had been called */ + atomic_t iface_is_started; + /* SPI controller power-management */ + bool has_lock_pm; + /* Control GPIOs */ + int reset_gpio; + /* Regulators handler */ + struct dw3000_power_control regulators; + bool is_powered; + /* Chips per PAC unit. */ + int chips_per_pac; + /* Preamble timeout in PAC unit. */ + int pre_timeout_pac; + /* WiFi coexistence GPIO and delay */ + unsigned coex_delay_us; + unsigned coex_margin_us; + unsigned coex_interval_us; + s8 coex_gpio; + bool coex_enabled; + int coex_status; + /* LNA/PA mode */ + s8 lna_pa_mode; + /* Is auto-ack activated? */ + bool autoack; + /* pgf calibration running */ + bool pgf_cal_running; + /* State machine */ + struct dw3000_state stm; + /* Receive descriptor */ + struct dw3000_rx rx; + /* Internal operational state of the chip */ + enum operational_state current_operational_state; + /* Wait queue for operational state */ + wait_queue_head_t operational_state_wq; + /* Debugfs settings */ + struct dw3000_debugfs debugfs; + /* PID of the SPI controller pump messages */ + pid_t spi_pid; + /* PID the dw3000 state machine thread */ + pid_t dw3000_pid; + /* Restricted channels */ + u16 restricted_channels; + /* enable tx on RF2 port */ + u8 tx_rf2; + /* enable rx on RF2 port */ + u8 rx_rf2; + /* Channel impulse response data */ + bool cir_data_changed; + bool full_cia_read; + struct dw3000_cir_data *cir_data; + /* SPI message holding transfers queue */ + struct spi_message *msg_queue; + struct spi_transfer *msg_queue_xfer; + unsigned msg_queue_xfer_count; + /* Buffer for queued transfers */ + char *msg_queue_buf; + char *msg_queue_buf_pos; + /* dw3000 thread clamp value */ + int min_clamp_value; + /* Insert new fields before this line */ + + /* Shared message protected by a mutex */ + struct mutex msg_mutex; + /* Precomputed spi_messages */ + struct spi_message *msg_readwrite_fdx; + struct spi_message *msg_fast_command; + struct spi_message *msg_read_rdb_status; + struct spi_message *msg_read_rx_timestamp; + struct spi_message *msg_read_rx_timestamp_a; + struct spi_message *msg_read_rx_timestamp_b; + struct spi_message *msg_read_sys_status; + struct spi_message *msg_read_all_sys_status; + struct spi_message *msg_read_sys_time; + struct spi_message *msg_write_sys_status; + struct spi_message *msg_write_all_sys_status; + struct spi_message *msg_read_dss_status; + struct spi_message *msg_write_dss_status; + struct spi_message *msg_write_spi_collision_status; +}; + +/** + * dw3000_is_active() - Return if DW is in active state (UP and running) + * @dw: The DW device. + * + * Allow to know if DW is in active state (dw3000_enable() called successfully) + * Used to avoid modification of registers while DW is in use. + * The chip can be in DEEP_SLEEP state and interface still up & running + * + * Return: true is DW is in active state + */ +static inline bool dw3000_is_active(struct dw3000 *dw) +{ + return atomic_read(&dw->iface_is_started); +} + +/** + * nfcc_coex_to_dw() - Help function to retrieve dw3000 context from nfcc_coex. + * @nfcc_coex: NFCC coexistence context. + * + * Returns: DW3000 device context. + */ +static inline struct dw3000 *nfcc_coex_to_dw(struct dw3000_nfcc_coex *nfcc_coex) +{ + return container_of(nfcc_coex, struct dw3000, nfcc_coex); +} + +#endif /* __DW3000_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_calib.c b/kernel/drivers/net/ieee802154/dw3000_calib.c new file mode 100644 index 0000000..2527486 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_calib.c @@ -0,0 +1,373 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include "dw3000.h" +#include "dw3000_txpower_adjustment.h" + +/* clang-format off */ +#define CHAN_PRF_PARAMS (4 * DW3000_CALIBRATION_PRF_MAX) +#define ANT_CHAN_PARAMS (CHAN_PRF_PARAMS * DW3000_CALIBRATION_CHANNEL_MAX) +#define ANT_OTHER_PARAMS (4) /* port, selector_gpio... */ +#define ANTPAIR_CHAN_PARAMS (2 * DW3000_CALIBRATION_CHANNEL_MAX + 1) + +#define OTHER_PARAMS (13) /* xtal_trim, temperature_reference, + smart_tx_power, spi_pid, dw3000_pid, + auto_sleep_margin, restricted_channels + phrMode, alternate_pulse_shape, + wificoex_gpio, wificoex_delay_us, + wificoex_interval_us, wificoex_margin_us */ + +#define MAX_CALIB_KEYS ((ANTMAX * (ANT_CHAN_PARAMS + ANT_OTHER_PARAMS)) + \ + (ANTPAIR_MAX * ANTPAIR_CHAN_PARAMS) + \ + (DW3000_CALIBRATION_CHANNEL_MAX * 2) + \ + OTHER_PARAMS) + +#define DW_OFFSET(m) offsetof(struct dw3000, m) +#define DW_SIZE(m) sizeof_field(struct dw3000, m) +#define DW_INFO(m) { .offset = DW_OFFSET(m), .length = DW_SIZE(m) } + +#define CAL_INFO(m) DW_INFO(calib_data.m) +#define OTP_INFO(m) DW_INFO(otp_data.m) + +#define PRF_CAL_INFO(b,x) \ + CAL_INFO(b.prf[x].ant_delay), \ + CAL_INFO(b.prf[x].tx_power), \ + CAL_INFO(b.prf[x].pg_count), \ + CAL_INFO(b.prf[x].pg_delay) + +#define ANTENNA_CAL_INFO(x) \ + PRF_CAL_INFO(ant[x].ch[0], 0), \ + PRF_CAL_INFO(ant[x].ch[0], 1), \ + PRF_CAL_INFO(ant[x].ch[1], 0), \ + PRF_CAL_INFO(ant[x].ch[1], 1), \ + CAL_INFO(ant[x].port), \ + CAL_INFO(ant[x].selector_gpio), \ + CAL_INFO(ant[x].selector_gpio_value), \ + CAL_INFO(ant[x].caps) + +#define ANTPAIR_CAL_INFO(x,y) \ + CAL_INFO(antpair[ANTPAIR_IDX(x, y)].ch[0].pdoa_offset), \ + CAL_INFO(antpair[ANTPAIR_IDX(x, y)].ch[0].pdoa_lut), \ + CAL_INFO(antpair[ANTPAIR_IDX(x, y)].ch[1].pdoa_offset), \ + CAL_INFO(antpair[ANTPAIR_IDX(x, y)].ch[1].pdoa_lut) + +static const struct { + unsigned int offset; + unsigned int length; +} dw3000_calib_keys_info[MAX_CALIB_KEYS] = { + /* ant0.* */ + ANTENNA_CAL_INFO(0), + /* ant1.* */ + ANTENNA_CAL_INFO(1), + /* ant0.* */ + ANTENNA_CAL_INFO(2), + /* ant1.* */ + ANTENNA_CAL_INFO(3), + /* antX.antW.* */ + ANTPAIR_CAL_INFO(0,1), + ANTPAIR_CAL_INFO(0,2), + ANTPAIR_CAL_INFO(0,3), + ANTPAIR_CAL_INFO(1,2), + ANTPAIR_CAL_INFO(1,3), + ANTPAIR_CAL_INFO(2,3), + /* chY.* */ + CAL_INFO(ch[0].pll_locking_code), + CAL_INFO(ch[0].wifi_coex_enabled), + CAL_INFO(ch[1].pll_locking_code), + CAL_INFO(ch[1].wifi_coex_enabled), + /* other with direct access in struct dw3000 */ + DW_INFO(txconfig.smart), + DW_INFO(auto_sleep_margin_us), + DW_INFO(spi_pid), + DW_INFO(dw3000_pid), + DW_INFO(restricted_channels), + DW_INFO(config.phrMode), + DW_INFO(coex_gpio), + DW_INFO(coex_delay_us), + DW_INFO(coex_margin_us), + DW_INFO(coex_interval_us), + /* country */ + DW_INFO(config.alternate_pulse_shape), + /* other with defaults from OTP */ + OTP_INFO(xtal_trim), + OTP_INFO(tempP), +}; + +#define PRF_CAL_LABEL(a,c,p) \ + "ant" #a ".ch" #c ".prf" #p ".ant_delay", \ + "ant" #a ".ch" #c ".prf" #p ".tx_power", \ + "ant" #a ".ch" #c ".prf" #p ".pg_count", \ + "ant" #a ".ch" #c ".prf" #p ".pg_delay" + +#define ANTENNA_CAL_LABEL(x) \ + PRF_CAL_LABEL(x, 5, 16), \ + PRF_CAL_LABEL(x, 5, 64), \ + PRF_CAL_LABEL(x, 9, 16), \ + PRF_CAL_LABEL(x, 9, 64), \ + "ant" #x ".port", \ + "ant" #x ".selector_gpio", \ + "ant" #x ".selector_gpio_value", \ + "ant" #x ".caps" + +#define PDOA_CAL_LABEL(a, b, c) \ + "ant" #a ".ant" #b ".ch" #c ".pdoa_offset", \ + "ant" #a ".ant" #b ".ch" #c ".pdoa_lut" + +#define ANTPAIR_CAL_LABEL(x,y) \ + PDOA_CAL_LABEL(x, y, 5), \ + PDOA_CAL_LABEL(x, y, 9) + +/* + * calibration parameters keys table + */ +static const char *const dw3000_calib_keys[MAX_CALIB_KEYS + 1] = { + /* antX */ + ANTENNA_CAL_LABEL(0), + ANTENNA_CAL_LABEL(1), + ANTENNA_CAL_LABEL(2), + ANTENNA_CAL_LABEL(3), + /* antX.antY.* */ + ANTPAIR_CAL_LABEL(0,1), + ANTPAIR_CAL_LABEL(0,2), + ANTPAIR_CAL_LABEL(0,3), + ANTPAIR_CAL_LABEL(1,2), + ANTPAIR_CAL_LABEL(1,3), + ANTPAIR_CAL_LABEL(2,3), + /* chY.* */ + "ch5.pll_locking_code", + "ch5.wifi_coex-enabled", + "ch9.pll_locking_code", + "ch9.wifi_coex-enabled", + /* other */ + "smart_tx_power", + "auto_sleep_margin", + "spi_pid", + "dw3000_pid", + "restricted_channels", + "phr_mode", + "coex_gpio", + "coex_delay_us", + "coex_margin_us", + "coex_interval_us", + /* country */ + "alternate_pulse_shape", + /* other (OTP) */ + "xtal_trim", + "temperature_reference", + /* NULL terminated array for caller of dw3000_calib_list_keys(). */ + NULL +}; +/* clang-format on */ + +const dw3000_pdoa_lut_t dw3000_default_lut_ch5 = { + /* clang-format off */ + { 0xe6de, 0xf36f }, + { 0xe88b, 0xf36f }, + { 0xea38, 0xf5b0 }, + { 0xebe5, 0xf747 }, + { 0xed92, 0xf869 }, + { 0xef3f, 0xf959 }, + { 0xf0ec, 0xfa2e }, + { 0xf299, 0xfaf1 }, + { 0xf445, 0xfba7 }, + { 0xf5f2, 0xfc53 }, + { 0xf79f, 0xfcf9 }, + { 0xf94c, 0xfd9a }, + { 0xfaf9, 0xfe36 }, + { 0xfca6, 0xfed0 }, + { 0xfe53, 0xff69 }, + { 0x0000, 0x0000 }, + { 0x01ad, 0x0097 }, + { 0x035a, 0x0130 }, + { 0x0507, 0x01ca }, + { 0x06b4, 0x0266 }, + { 0x0861, 0x0307 }, + { 0x0a0e, 0x03ad }, + { 0x0bbb, 0x0459 }, + { 0x0d67, 0x050f }, + { 0x0f14, 0x05d2 }, + { 0x10c1, 0x06a7 }, + { 0x126e, 0x0797 }, + { 0x141b, 0x08b9 }, + { 0x15c8, 0x0a50 }, + { 0x1775, 0x0c91 }, + { 0x1922, 0x0c91 } + /* clang-format on */ +}; + +const dw3000_pdoa_lut_t dw3000_default_lut_ch9 = { + /* clang-format off */ + { 0xe6de, 0xf701 }, + { 0xe88b, 0xf7ff }, + { 0xea38, 0xf8d2 }, + { 0xebe5, 0xf98d }, + { 0xed92, 0xfa38 }, + { 0xef3f, 0xfad7 }, + { 0xf0ec, 0xfb6d }, + { 0xf299, 0xfbfc }, + { 0xf445, 0xfc86 }, + { 0xf5f2, 0xfd0c }, + { 0xf79f, 0xfd8f }, + { 0xf94c, 0xfe0f }, + { 0xfaf9, 0xfe8d }, + { 0xfca6, 0xff09 }, + { 0xfe53, 0xff85 }, + { 0x0000, 0x0000 }, + { 0x01ad, 0x007b }, + { 0x035a, 0x00f7 }, + { 0x0507, 0x0173 }, + { 0x06b4, 0x01f1 }, + { 0x0861, 0x0271 }, + { 0x0a0e, 0x02f4 }, + { 0x0bbb, 0x037a }, + { 0x0d67, 0x0404 }, + { 0x0f14, 0x0493 }, + { 0x10c1, 0x0529 }, + { 0x126e, 0x05c8 }, + { 0x141b, 0x0673 }, + { 0x15c8, 0x072e }, + { 0x1775, 0x0801 }, + { 0x1922, 0x08ff } + /* clang-format on */ +}; + +int dw3000_calib_parse_key(struct dw3000 *dw, const char *key, void **param) +{ + int i; + + for (i = 0; dw3000_calib_keys[i]; i++) { + const char *k = dw3000_calib_keys[i]; + + if (strcmp(k, key) == 0) { + /* Key found, calculate parameter address */ + *param = (void *)dw + dw3000_calib_keys_info[i].offset; + return dw3000_calib_keys_info[i].length; + } + } + return -ENOENT; +} + +/** + * dw3000_calib_list_keys - return the @dw3000_calib_keys known key table + * @dw: the DW device + * + * Return: pointer to known keys table. + */ +const char *const *dw3000_calib_list_keys(struct dw3000 *dw) +{ + return dw3000_calib_keys; +} + +/** + * dw3000_calib_update_config - update running configuration + * @dw: the DW device + * + * This function update the required fields in struct dw3000_txconfig according + * the channel and PRF and the corresponding calibration values. + * + * Also update RX/TX RMARKER offset according calibrated antenna delay. + * + * Other calibration parameters aren't used yet. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_calib_update_config(struct dw3000 *dw) +{ + struct dw3000_config *config = &dw->config; + struct dw3000_txconfig *txconfig = &dw->txconfig; + const struct dw3000_antenna_calib *ant_calib; + const struct dw3000_antenna_calib_prf *ant_calib_prf; + const struct dw3000_antenna_pair_calib *antpair_calib; + int ant_rf1, ant_rf2, antpair; + int chanidx, prfidx; + + dw->llhw->hw->phy->supported.channels[4] = DW3000_SUPPORTED_CHANNELS & + ~dw->restricted_channels; + /* Change channel if the current one is restricted. */ + if ((1 << dw->llhw->hw->phy->current_channel) & + dw->restricted_channels) { + config->chan = + ffs(dw->llhw->hw->phy->supported.channels[4]) - 1; + dw->llhw->hw->phy->current_channel = config->chan; + } + + ant_rf1 = config->ant[0]; + ant_rf2 = config->ant[1]; + if (ant_rf1 < 0 && ant_rf2 < 0) { + /* Not configured yet, does nothing. */ + return 0; + } + /* Since both calibs can be used at this time, both needs to be safe. */ + if (ant_rf1 >= ANTMAX || ant_rf2 >= ANTMAX) + return -1; + if (ant_rf1 >= 0) + ant_calib = &dw->calib_data.ant[ant_rf1]; + else + ant_calib = &dw->calib_data.ant[ant_rf2]; + + /* Convert config into index of array. */ + chanidx = config->chan == 9 ? DW3000_CALIBRATION_CHANNEL_9 : + DW3000_CALIBRATION_CHANNEL_5; + prfidx = config->txCode >= 9 ? DW3000_CALIBRATION_PRF_64MHZ : + DW3000_CALIBRATION_PRF_16MHZ; + + /* Shortcut pointers to reduce line length. */ + ant_calib_prf = &ant_calib->ch[chanidx].prf[prfidx]; + + /* WiFi coexistence according to current channel */ + dw->coex_enabled = dw->calib_data.ch[chanidx].wifi_coex_enabled; + + /* Update TX configuration */ + txconfig->power = ant_calib_prf->tx_power ? ant_calib_prf->tx_power : + 0xfefefefe; + txconfig->PGdly = ant_calib_prf->pg_delay ? ant_calib_prf->pg_delay : + 0x34; + txconfig->PGcount = ant_calib_prf->pg_count ? ant_calib_prf->pg_count : + 0; + /* Update RMARKER offsets */ + config->rmarkerOffset = ant_calib_prf->ant_delay; + + /* Early exit if RF2 isn't configured yet. */ + if (ant_rf2 < 0) + return 0; + if (ant_rf1 >= 0 && ant_rf2 >= 0) { + /* RF2 and RF1 port has a valid antenna, so antpair can be used */ + antpair = ant_rf2 > ant_rf1 ? ANTPAIR_IDX(ant_rf1, ant_rf2) : + ANTPAIR_IDX(ant_rf2, ant_rf1); + + antpair_calib = &dw->calib_data.antpair[antpair]; + /* Update PDOA offset */ + config->pdoaOffset = antpair_calib->ch[chanidx].pdoa_offset; + config->pdoaLut = &antpair_calib->ch[chanidx].pdoa_lut; + } + /* Smart TX power */ + /* When deactivated, reset register to default value (if change occurs + while already started) */ + if (!txconfig->smart && dw3000_is_active(dw)) + dw3000_set_tx_power_register(dw, txconfig->power); + + /* Update idle_dtu in case auto_sleep_margin_us changed */ + dw->llhw->idle_dtu = dw->auto_sleep_margin_us > 0 ? + US_TO_DTU(dw->auto_sleep_margin_us) : + DW3000_DTU_FREQ; + return 0; +} diff --git a/kernel/drivers/net/ieee802154/dw3000_calib.h b/kernel/drivers/net/ieee802154/dw3000_calib.h new file mode 100644 index 0000000..b136b4c --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_calib.h @@ -0,0 +1,225 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_CALIB_H +#define __DW3000_CALIB_H + +/** + * DW3000_CALIBRATION_ANTENNA_MAX - number of antenna + */ +#define DW3000_CALIBRATION_ANTENNA_MAX 4 + +/** + * enum dw3000_calibration_channels - calibration channel number. + * @DW3000_CALIBRATION_CHANNEL_5: index in array for channel 5 + * @DW3000_CALIBRATION_CHANNEL_9: index in array for channel 9 + * @DW3000_CALIBRATION_CHANNEL_MAX: channel array size + */ +enum dw3000_calibration_channels { + DW3000_CALIBRATION_CHANNEL_5, + DW3000_CALIBRATION_CHANNEL_9, + + DW3000_CALIBRATION_CHANNEL_MAX +}; + +/** + * enum dw3000_calibration_prfs - calibration Pulse Repetition Frequency. + * @DW3000_CALIBRATION_PRF_16MHZ: index in array for prf 16 + * @DW3000_CALIBRATION_PRF_64MHZ: index in array for prf 64 + * @DW3000_CALIBRATION_PRF_MAX: prf array size + */ +enum dw3000_calibration_prfs { + DW3000_CALIBRATION_PRF_16MHZ, + DW3000_CALIBRATION_PRF_64MHZ, + + DW3000_CALIBRATION_PRF_MAX +}; + +/** + * DW3000_CALIBRATION_PDOA_LUT_MAX - number of value in PDOA LUT table + */ +#define DW3000_CALIBRATION_PDOA_LUT_MAX 31 + +/* Intermediate types to fix following error: + * [kernel-doc ERROR] : can't parse typedef! + */ +typedef s16 pdoa_lut_entry_t[2]; +typedef pdoa_lut_entry_t pdoa_lut_table_t[DW3000_CALIBRATION_PDOA_LUT_MAX]; + +/** + * typedef dw3000_pdoa_lut_t - PDoA LUT array type + */ +typedef pdoa_lut_table_t dw3000_pdoa_lut_t; + +/* Default LUTs, theorical values for Monalisa antenna (20.8mm) */ +extern const dw3000_pdoa_lut_t dw3000_default_lut_ch5; +extern const dw3000_pdoa_lut_t dw3000_default_lut_ch9; + +/** + * DW3000_DEFAULT_ANT_DELAY - antenna delay default value + */ +#define DW3000_DEFAULT_ANT_DELAY 16450 + +/** + * struct dw3000_channel_calib - per-channel dependent calibration parameters + * @pll_locking_code: PLL locking code + * @wifi_coex_enabled: WiFi coexistence activation + */ +struct dw3000_channel_calib { + /* chY.pll_locking_code */ + u32 pll_locking_code; + bool wifi_coex_enabled; +}; + +/** + * struct dw3000_antenna_calib_prf - antenna calibration parameters + * @ant_delay: antenna delay + * @tx_power: tx power + * @pg_count: PG count + * @pg_delay: PG delay + */ +struct dw3000_antenna_calib_prf { + u32 ant_delay; + u32 tx_power; + u8 pg_count; + u8 pg_delay; +}; + +/** + * struct dw3000_antenna_calib - per-antenna dependent calibration parameters + * @ch: table of channels dependent calibration values + * @ch.prf: table of PRF dependent calibration values + * @port: port value this antenna belong to (0 for RF1, 1 for RF2) + * @selector_gpio: GPIO number to select this antenna + * @selector_gpio_value: GPIO value to select this antenna + * @caps: antenna capabilities + */ +struct dw3000_antenna_calib { + /* antX.chY.prfZ.* */ + struct { + struct dw3000_antenna_calib_prf prf[DW3000_CALIBRATION_PRF_MAX]; + } ch[DW3000_CALIBRATION_CHANNEL_MAX]; + /* antX.* */ + u8 port, selector_gpio, selector_gpio_value, caps; +}; + +/** + * struct dw3000_antenna_pair_calib_chan - per-channel antennas pair calibration + * parameters + * @pdoa_offset: PDOA offset + * @pdoa_lut: PDOA LUT + */ +struct dw3000_antenna_pair_calib_chan { + s16 pdoa_offset; + dw3000_pdoa_lut_t pdoa_lut; +}; + +/** + * struct dw3000_antenna_pair_calib - antenna pair dependent calibration values + * @ch: table of channels dependent calibration values + */ +struct dw3000_antenna_pair_calib { + /* antX.antW.chY.* */ + struct dw3000_antenna_pair_calib_chan ch[DW3000_CALIBRATION_CHANNEL_MAX]; +}; + +/* Just to ease reading of the following formulas. */ +#define ANTMAX DW3000_CALIBRATION_ANTENNA_MAX + +/** + * ANTPAIR_MAX - calculated antpair table size + */ +#define ANTPAIR_MAX ((ANTMAX * (ANTMAX - 1)) / 2) + +/** + * ANTPAIR_OFFSET - calculate antpair table indexes row offset + * @x: first antenna index + * + * Return: An index for the first element in antpair table for the given value. + */ +#define ANTPAIR_OFFSET(x) ((((2 * (ANTMAX - 1)) + 1 - (x)) * (x)) / 2) + +/** + * ANTPAIR_IDX - calculate antpair table indexes + * @x: first antenna index + * @w: second antenna index, must be > @x + * + * Return: An index for the antpair table in [0;ANTPAIR_MAX-1] interval. + */ +#define ANTPAIR_IDX(x, w) (ANTPAIR_OFFSET(x) + ((w) - (x)-1)) + +/** + * ANTSET_ID_MAX - calculated antenna set id table size + */ +#define ANTSET_ID_MAX (ANTPAIR_MAX + ANTMAX) + +/** + * dw3000_calib_ant_set_id_to_ant - convert antenna set id pair to corresponding antennas + * @ant_set_id: antenna set id + * @ant_idx1: first antenna + * @ant_idx2: second antenna + */ +static inline void dw3000_calib_ant_set_id_to_ant(int ant_set_id, s8 *ant_idx1, + s8 *ant_idx2) +{ + static const s8 set_id_to_ant[ANTSET_ID_MAX][2] = { + { 0, 1 }, { 0, 2 }, { 0, 3 }, { 1, 2 }, { 1, 3 }, + { 2, 3 }, { 0, -1 }, { 1, -1 }, { 2, -1 }, { 3, -1 } + }; + + *ant_idx1 = set_id_to_ant[ant_set_id][0]; + *ant_idx2 = set_id_to_ant[ant_set_id][1]; +} + +/** + * struct dw3000_calibration_data - all per-antenna and per-channel calibration + * parameters + * @ant: table of antenna dependent calibration values + * @antpair: table of antenna pair dependent calibration values + * @ch: table of channel dependent calibration values + */ +struct dw3000_calibration_data { + struct dw3000_antenna_calib ant[ANTMAX]; + struct dw3000_antenna_pair_calib antpair[ANTPAIR_MAX]; + struct dw3000_channel_calib ch[DW3000_CALIBRATION_CHANNEL_MAX]; +}; + +struct dw3000; + +/** + * dw3000_calib_parse_key - parse key and find corresponding param + * @dw: the DW device + * @key: pointer to NUL terminated string to retrieve param address and len + * @param: pointer where to store the corresponding parameter address + * + * This function lookup the NULL terminated table @dw3000_calib_keys and + * if specified key is found, store the corresponding address in @param and + * + * Return: length of corresponding parameter if found, else a -ENOENT error. + */ +int dw3000_calib_parse_key(struct dw3000 *dw, const char *key, void **param); + +const char *const *dw3000_calib_list_keys(struct dw3000 *dw); + +int dw3000_calib_update_config(struct dw3000 *dw); + +#endif /* __DW3000_CALIB_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_chip.h b/kernel/drivers/net/ieee802154/dw3000_chip.h new file mode 100644 index 0000000..5871ea0 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_chip.h @@ -0,0 +1,175 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_CHIP_H +#define __DW3000_CHIP_H + +/* Forward declaration */ +struct dw3000; +struct dw3000_rssi; + +/** + * enum dw3000_chip_register_flags - flags for the register declaration + * @DW3000_CHIPREG_NONE: no special flag defined + * @DW3000_CHIPREG_DUMP: register is dump only, address is fileid number only + * @DW3000_CHIPREG_RO: register is always read-only + * @DW3000_CHIPREG_WP: register is write-protected. write refused if device is + * already active. + * @DW3000_CHIPREG_PERM: register have a permanent access: chip off or on. + * This will be usefull for some virtual registers wired on callbacks + * @DW3000_CHIPREG_OPENONCE: allow only one file instance at a time + */ +enum dw3000_chip_register_flags { + DW3000_CHIPREG_NONE = 0, + DW3000_CHIPREG_DUMP = 1, + DW3000_CHIPREG_RO = 2, + DW3000_CHIPREG_WP = 4, + DW3000_CHIPREG_PERM = 8, + DW3000_CHIPREG_OPENONCE = 16, +}; + +/** + * typedef dw3000_chip_register_cb - virtual register callback function + * @filp: the debugfs file structure pointer + * @write: true when called for a write operation + * @buffer: input or output buffer, depending on write parameter + * @size: size of input buffer or available space of output buffer + * @ppos: the callback handle the ppos to be blocking or not + * + * All functions of this type will parse input buffer and do the relevant + * action according given parameters when write is true. + * When write is false, relevant information is written to buffer. + * + * The filp parameter is used by the function to retrieve required private + * data given when the file was created. See struct dw3000_chip_register_priv. + * + * Returns: length read from buffer or written to buffer or negative error + */ +typedef int (*dw3000_chip_register_cb)(struct file *filp, bool write, + void *buffer, size_t size, loff_t *ppos); + +/** + * struct dw3000_chip_register - version dependent register declaration + * @name: register name + * @address: register address + * @size: register size (in bits if mask defined) + * @mask: register mask (unused if 0) + * @flags: or'ed value of enum dw3000_chip_register_flags + * @callback: processing callback for a virtual register + */ +struct dw3000_chip_register { + const char *const name; + unsigned address; + size_t size; + unsigned mask; + unsigned flags; + dw3000_chip_register_cb callback; +}; + +/** + * struct dw3000_chip_register_priv - private data for debugfs file + * @dw: backpointer to DW3000 device instance + * @reg: pointer to first struct dw3000_chip_register this file belong to + * @count: number of struct dw3000_chip_register this file handle + * + * If a specific debugfs file need more data, it can derive this structure. + */ +struct dw3000_chip_register_priv { + struct dw3000 *dw; + const struct dw3000_chip_register *reg; + size_t count; +}; + +/** + * struct dw3000_chip_ops - version dependent chip operations + * @softreset: soft-reset + * @init: initialisation + * @coex_init: initialise WiFi coexistence GPIO + * @coex_gpio: change state of WiFi coexistence GPIO + * @check_tx_ok: Check device has correctly entered tx state + * @prog_ldo_and_bias_tune: programs the device's LDO and BIAS tuning + * @get_config_mrxlut_chan: Lookup table default values for channel provided or NULL + * @get_dgc_dec: Read DGC_DBG register + * @pre_read_sys_time: Workaround before the SYS_TIME register reads + * @adc_offset_calibration: Workaround to calibrate ADC offset + * @pll_calibration_from_scratch: Workaround to calibrate the PLL from scratch + * @pll_coarse_code: Workaround to set PLL coarse code + * @prog_pll_coarse_code: Program PLL coarse code from OTP + * @set_mrxlut: configure mrxlut + * @kick_ops_table_on_wakeup: kick the desired operating parameter set table + * @kick_dgc_on_wakeup: kick the DGC upon wakeup from sleep + * @get_registers: Return known registers table and it's size + * @compute_rssi: Uses the parameters to compute RSSI of current frame + */ +struct dw3000_chip_ops { + int (*softreset)(struct dw3000 *dw); + int (*init)(struct dw3000 *dw); + int (*coex_init)(struct dw3000 *dw); + int (*coex_gpio)(struct dw3000 *dw, bool state, int delay_us); + int (*check_tx_ok)(struct dw3000 *dw); + int (*prog_ldo_and_bias_tune)(struct dw3000 *dw); + const u32 *(*get_config_mrxlut_chan)(struct dw3000 *dw, u8 channel); + int (*get_dgc_dec)(struct dw3000 *dw, u8 *value); + int (*pre_read_sys_time)(struct dw3000 *dw); + int (*adc_offset_calibration)(struct dw3000 *dw); + int (*pll_calibration_from_scratch)(struct dw3000 *dw); + int (*pll_coarse_code)(struct dw3000 *dw); + int (*prog_pll_coarse_code)(struct dw3000 *dw); + int (*set_mrxlut)(struct dw3000 *dw, const u32 *lut); + int (*kick_ops_table_on_wakeup)(struct dw3000 *dw); + int (*kick_dgc_on_wakeup)(struct dw3000 *dw); + const struct dw3000_chip_register *(*get_registers)(struct dw3000 *dw, + size_t *count); + u32 (*compute_rssi)(struct dw3000 *dw, struct dw3000_rssi *rssi, + bool rx_tune, u8 sts); +}; + +/** + * struct dw3000_chip_version - supported chip version definition + * @id: device model ID + * @ver: device registers version, saved to __dw3000_chip_version + * @ops: associated version specific operations + * @name: short version name of current device + */ +struct dw3000_chip_version { + unsigned id; + int ver; + const struct dw3000_chip_ops *ops; + const char *name; +}; + +/* DW3000 device model IDs (with or non PDOA) */ +#define DW3000_C0_DEV_ID 0xdeca0302 +#define DW3000_C0_PDOA_DEV_ID 0xdeca0312 +#define DW3000_C0_VERSION 0 +#define DW3000_D0_DEV_ID 0xdeca0303 +#define DW3000_D0_PDOA_DEV_ID 0xdeca0313 +#define DW3000_D0_VERSION 1 +#define DW3000_E0_PDOA_DEV_ID 0xdeca0314 +#define DW3000_E0_VERSION 2 + +/* Declaration of version specific chip operations */ +extern const struct dw3000_chip_ops dw3000_chip_c0_ops; +extern const struct dw3000_chip_ops dw3000_chip_d0_ops; +extern const struct dw3000_chip_ops dw3000_chip_e0_ops; + +#endif /* __DW3000_CHIP_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_chip_c0.c b/kernel/drivers/net/ieee802154/dw3000_chip_c0.c new file mode 100644 index 0000000..2401504 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_chip_c0.c @@ -0,0 +1,419 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include "dw3000.h" +#include "dw3000_core.h" +#include "dw3000_core_reg.h" +#include "dw3000_chip_c0.h" + +#define DW3000_C0_DGC_DBG_ID 0x30060 + +static const struct dw3000_chip_register c0_registers[] = { + /* virtual registers for fileID dump */ + { "GEN_CFG0", 0x00, 0x79, 0, DW3000_CHIPREG_DUMP, NULL }, + { "GEN_CFG1", 0x01, 0x64, 0, DW3000_CHIPREG_DUMP, NULL }, + { "STS_CFG", 0x02, 0x2c, 0, DW3000_CHIPREG_DUMP, NULL }, + /* No fileID 0x03 documented in DW3000 user manual v0.7. */ + { "EXT_SYNC", 0x04, 0x04, 0, DW3000_CHIPREG_DUMP, NULL }, + { "GPIO_CTRL", 0x05, 0x2e, 0, DW3000_CHIPREG_DUMP, NULL }, + { "DRX_CONF", 0x06, 0x10, 0, DW3000_CHIPREG_DUMP, NULL }, + { "RF_CONF", 0x07, 0x4c, 0, DW3000_CHIPREG_DUMP, NULL }, + { "TX_CAL", 0x08, 0x1e, 0, DW3000_CHIPREG_DUMP, NULL }, + { "FS_CTRL", 0x09, 0x15, 0, DW3000_CHIPREG_DUMP, NULL }, + { "AON", 0x0a, 0x15, 0, DW3000_CHIPREG_DUMP, NULL }, + { "OTP_IF", 0x0b, 0x18, 0, DW3000_CHIPREG_DUMP, NULL }, + { "CIA_1", 0x0c, 0x6c, 0, DW3000_CHIPREG_DUMP, NULL }, + { "CIA_2", 0x0d, 0x6c, 0, DW3000_CHIPREG_DUMP, NULL }, + { "CIA_3", 0x0e, 0x20, 0, DW3000_CHIPREG_DUMP, NULL }, + { "DIG_DIAG", 0x0f, 0x51, 0, DW3000_CHIPREG_DUMP, NULL }, + { "PMSC", 0x11, 0x4a, 0, DW3000_CHIPREG_DUMP, NULL }, + /* TODO: RX/TX buffers limited to first 128bytes only. + Shall we dump the whole 1024 bytes? */ + { "RX_BUFFER", 0x12, 0x80, 0, DW3000_CHIPREG_DUMP, NULL }, + { "RX_BUFFER1", 0x13, 0x80, 0, DW3000_CHIPREG_DUMP, NULL }, + /* No TX_BUFFER as read isn't supported */ + /* CIR memory require 2 bits configured elsewhere, so removed. */ + { "SCRATCH_RAM", 0x16, 0x7f, 0, DW3000_CHIPREG_DUMP, NULL }, + { "AES RAM", 0x17, 0x80, 0, DW3000_CHIPREG_DUMP, NULL }, + { "SET_X", 0x18, 0x1d0, 0, DW3000_CHIPREG_DUMP, NULL }, + { "IN_PTR_CFG", 0x1f, 0x12, 0, DW3000_CHIPREG_DUMP, NULL }, + { "euid", 0x000004, 0x08, 0x00, DW3000_CHIPREG_WP, NULL }, + { "frame_filter", 0x000010, 0x01, 0x01, DW3000_CHIPREG_WP, NULL }, + { "rx_phrmode", 0x000010, 0x01, 0x10, DW3000_CHIPREG_WP, NULL }, + { "tx_phrrate", 0x000010, 0x01, 0x20, DW3000_CHIPREG_WP, NULL }, + { "rx_sts_mode", 0x000011, 0x01, 0x8, DW3000_CHIPREG_WP, NULL }, + { "pdoa_mode", 0x000012, 0x01, 0x3, DW3000_CHIPREG_WP, NULL }, + { "role", 0x000015, 0x01, 0x1, DW3000_CHIPREG_WP, NULL }, + { "frame_filter_cfg", 0x000014, 0x02, 0xfeff, DW3000_CHIPREG_WP, NULL }, + { "datarate", 0x000021, 0x01, 0x4, DW3000_CHIPREG_WP, NULL }, + { "tx_pream_len", 0x000021, 0x01, 0xf0, DW3000_CHIPREG_WP, NULL }, + { "tx_antdly", 0x00007c, 0x02, 0x00, DW3000_CHIPREG_WP, NULL }, + { "txrf_pwrfin", 0x010004, 0x01, 0xfc, DW3000_CHIPREG_WP, NULL }, + { "tx_pwr", 0x010004, 0x04, 0x00, DW3000_CHIPREG_WP, NULL }, + { "rx_sfdtype", 0x010008, 0x01, 0x6, DW3000_CHIPREG_WP, NULL }, + { "channel", 0x010008, 0x01, 0x01, DW3000_CHIPREG_WP, NULL }, + { "tx_pream_ch", 0x010008, 0x01, 0xf8, DW3000_CHIPREG_WP, NULL }, + { "prf", 0x010008, 0x01, 0x1f, DW3000_CHIPREG_WP, NULL }, + { "rx_phr_rate", 0x010010, 0x01, 0x20, DW3000_CHIPREG_WP, NULL }, + { "rx_sts_len", 0x020000, 0x01, 0xff, DW3000_CHIPREG_WP, NULL }, + { "ext_clkdly", 0x040000, 0x02, 0x7f8, DW3000_CHIPREG_WP, NULL }, + { "ext_clkdly_en", 0x040001, 0x01, 0x8, DW3000_CHIPREG_WP, NULL }, + { "gpiodir", 0x050008, 0x02, 0x1ff, DW3000_CHIPREG_WP, NULL }, + { "gpioout", 0x05000c, 0x02, 0x1ff, DW3000_CHIPREG_WP, NULL }, + { "rx_paclen", 0x060000, 0x01, 0x03, DW3000_CHIPREG_WP, NULL }, + { "rx_sfd_to", 0x060002, 0x02, 0x00, DW3000_CHIPREG_WP, NULL }, + { "chan_pg_delay", 0x07001c, 0x01, 0x3f, DW3000_CHIPREG_WP, NULL }, + { "chan_pll_cfg", 0x090001, 0x01, 0x10, DW3000_CHIPREG_WP, NULL }, + { "xtal_trim", 0x090014, 0x01, 0x3f, DW3000_CHIPREG_WP, NULL }, + { "rx_antdly", 0x0e0000, 0x01, 0xf8, DW3000_CHIPREG_WP, NULL }, + { "rx_diag", 0x0e0002, 0x01, 0x10, DW3000_CHIPREG_WP, NULL }, + { "digi_diag_en", 0x0f0000, 0x01, 0x1, DW3000_CHIPREG_WP, NULL }, + { "digi_diag_clr", 0x0f0000, 0x01, 0x2, DW3000_CHIPREG_WP, NULL }, +}; + +const struct dw3000_chip_register *dw3000_c0_get_registers(struct dw3000 *dw, + size_t *count) +{ + *count = ARRAY_SIZE(c0_registers); + return c0_registers; +} + +const u32 *dw3000_c0_get_config_mrxlut_chan(struct dw3000 *dw, u8 channel) +{ + /* Lookup table default values for channel 5 */ + static const u32 dw3000_c0_configmrxlut_ch5[DW3000_CONFIGMRXLUT_MAX] = { + 0x1c0fd, 0x1c43e, 0x1c6be, 0x1c77e, 0x1cf36, 0x1cfb5, 0x1cff5 + }; + + /* Lookup table default values for channel 9 */ + static const u32 dw3000_c0_configmrxlut_ch9[DW3000_CONFIGMRXLUT_MAX] = { + 0x2a8fe, 0x2ac36, 0x2a5fe, 0x2af3e, 0x2af7d, 0x2afb5, 0x2afb5 + }; + + switch (channel) { + case 5: + return dw3000_c0_configmrxlut_ch5; + case 9: + return dw3000_c0_configmrxlut_ch9; + default: + return NULL; + } +} + +static int dw3000_c0_softreset(struct dw3000 *dw) +{ + /* Reset HIF, TX, RX and PMSC */ + return dw3000_reg_write8(dw, DW3000_SOFT_RST_ID, 0, DW3000_RESET_ALL); +} + +static int dw3000_c0_init(struct dw3000 *dw) +{ + /* TODO */ + return 0; +} + +static int dw3000_c0_coex_init(struct dw3000 *dw) +{ + /* TODO */ + return 0; +} + +static int dw3000_c0_coex_gpio(struct dw3000 *dw, bool state, int delay_us) +{ + /* TODO */ + return 0; +} + +static int dw3000_c0_check_tx_ok(struct dw3000 *dw) +{ + u32 state; + int rc = dw3000_reg_read32(dw, DW3000_SYS_STATE_LO_ID, 0, &state); + if (rc || state == DW3000_SYS_STATE_TXERR) { + dw3000_forcetrxoff(dw); + return -ETIME; + } + return 0; +} + +/** + * dw3000_c0_prog_ldo_and_bias_tune() - Programs the device's LDO and BIAS tuning + * @dw: The DW device. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_c0_prog_ldo_and_bias_tune(struct dw3000 *dw) +{ + const u16 bias_mask = DW3000_BIAS_CTRL_DIG_BIAS_DAC_ULV_BIT_MASK; + struct dw3000_local_data *local = &dw->data; + struct dw3000_otp_data *otp = &dw->otp_data; + int rc; + u16 bias_tune = (otp->bias_tune >> 16) & bias_mask; + if (otp->ldo_tune_lo && otp->ldo_tune_hi && bias_tune) { + rc = dw3000_reg_or16(dw, DW3000_NVM_CFG_ID, 0, + DW3000_LDO_BIAS_KICK); + if (rc) + return rc; + rc = dw3000_reg_modify16(dw, DW3000_BIAS_CTRL_ID, 0, ~bias_mask, + bias_tune); + if (rc) + return rc; + } + local->dgc_otp_set = false; + return 0; +} + +/** + * dw3000_c0_pre_read_sys_time() - Ensure SYS_TIME register is cleared + * @dw: The DW device. + * + * On C0 chips, the SYS_TIME register value is latched and any subsequent read + * will return the same value. To clear the current value in the register an SPI + * write transaction is necessary, the following read of the SYS_TIME register will + * return a new value. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_c0_pre_read_sys_time(struct dw3000 *dw) +{ + /* The SPI_COLLISION register is choose to make this SPI write + * transaction because it is unused and it is a small 8 bits register. + */ + return dw3000_clear_spi_collision_status( + dw, DW3000_SPI_COLLISION_STATUS_BIT_MASK); +} + +/** + * dw3000_c0_get_dgc_dec() - Read DGC_DBG register content + * @dw: The DW device. + * @value: Pointer to store DGC DECISION value. + * + * Return: zero on succes, else a negative error code. + */ +int dw3000_c0_get_dgc_dec(struct dw3000 *dw, u8 *value) +{ + int rc; + u32 dgc_dbg; + + rc = dw3000_reg_read32(dw, DW3000_C0_DGC_DBG_ID, 0, &dgc_dbg); + if (unlikely(rc)) + return rc; + + /* DGC_DECISION is on bits 28 to 30 of DGC_CFG, cf 8.2.4.2 of DW3700 + * User Manual, store it to right the rssi stats entry */ + *value = (u8)((dgc_dbg & 0x70000000) >> 0x1c); + return 0; +} + +/** + * dw3000_c0_pll_calibration_from_scratch() - Calibrate the PLL from scratch + * @dw: the DW device + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_c0_pll_calibration_from_scratch(struct dw3000 *dw) +{ + int rc = 0; + + /* Run the PLL calibration from scratch. + * The USE_OLD_BIT_MASK tells the chip to use the an old PLL_CAL_ID to start + * its calculation. This is just in order to fasten the process. + */ + rc = dw3000_reg_or32(dw, DW3000_PLL_CAL_ID, 0, + DW3000_PLL_CAL_PLL_CAL_EN_BIT_MASK | + DW3000_PLL_CAL_PLL_USE_OLD_BIT_MASK); + if (rc) + return rc; + /* Wait for the PLL calibration (needed before read the calibration status register) */ + usleep_range(DW3000_C0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US, + DW3000_C0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US + 10); + return rc; +} + +/** + * dw3000_c0_prog_pll_coarse_code() - Programs the device's coarse code + * @dw: The DW device. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_c0_prog_pll_coarse_code(struct dw3000 *dw) +{ + struct dw3000_otp_data *otp = &dw->otp_data; + int rc = 0; + + if (otp->pll_coarse_code) { + /* set the coarse code value as read from OTP */ + rc = dw3000_reg_write8(dw, DW3000_PLL_COARSE_CODE_ID, 0, + otp->pll_coarse_code); + } + return rc; +} + +/** + * dw3000_c0_set_mrxlut() - Configure mrxlut + * @dw: The DW device. + * @lut: Pointer to LUT to write to DGC_LUT_X_CFG registers + * + * Return: zero on success, else a negative error code. + */ +int dw3000_c0_set_mrxlut(struct dw3000 *dw, const u32 *lut) +{ + int rc; + + /* Update LUT registers */ + rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_0_CFG_ID, 0x0, lut[0]); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_1_CFG_ID, 0x0, lut[1]); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_2_CFG_ID, 0x0, lut[2]); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_3_CFG_ID, 0x0, lut[3]); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_4_CFG_ID, 0x0, lut[4]); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_5_CFG_ID, 0x0, lut[5]); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_6_CFG_ID, 0x0, lut[6]); + if (rc) + return rc; + + /* Update DGC CFG. Leave this at end for C0/D0 chip. */ + rc = dw3000_reg_write32(dw, DW3000_DGC_CFG0_ID, 0x0, DW3000_DGC_CFG0); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_DGC_CFG1_ID, 0x0, DW3000_DGC_CFG1); + return rc; +} + +/** + * dw3000_c0_kick_ops_table_on_wakeup() - kick the OPS table + * @dw: The DW device. + * + * kick the desired operating parameter set (OPS) table upon wakeup from sleep + * It will load the required OPS table configuration based upon what OPS table + * was set to be used + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_c0_kick_ops_table_on_wakeup(struct dw3000 *dw) +{ + return 0; +} + +/** + * dw3000_c0_kick_dgc_on_wakeup() - kick the DGC + * @dw: The DW device. + * + * kick the DGC upon wakeup from sleep + * It will load the required DGC configuration from OTP based upon what channel was set to be used + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_c0_kick_dgc_on_wakeup(struct dw3000 *dw) +{ + u8 channel = dw->config.chan; + u16 dgc_sel = (channel == 5 ? 0 : DW3000_NVM_CFG_DGC_SEL_BIT_MASK); + + /* The DGC_SEL bit must be set to '0' for channel 5 and '1' for channel 9 */ + return dw3000_reg_modify16(dw, DW3000_NVM_CFG_ID, 0, + (u16) ~(DW3000_NVM_CFG_DGC_SEL_BIT_MASK), + dgc_sel | DW3000_NVM_CFG_DGC_KICK_BIT_MASK); +} + +/** + * dw3000_c0_compute_rssi() - Compute RSSI from its composites + * @dw: the DW device + * @rssi: RSSI composites + * @rx_tune: state of RX_TUNE_EN bit leads to use dgc_dec value or not + * @sts: current sts mode + * + * Because RSSI cannot be positive in our case (would mean that signals have + * been amplified) we return an unsigned integer considered as always negative. + * + * Return: 0 on error, else the RSSI in absolute value and as an integer. + * expressed in dBm, Q32.0. + */ +static u32 dw3000_c0_compute_rssi(struct dw3000 *dw, struct dw3000_rssi *rssi, + bool rx_tune, u8 sts) +{ + /* Details are logged into UWB-3455 + * DW3700 User Manual v0.3 and 4 section 4.7.2 gives that RSSI + * can be computed using this formula: + * rssi = 10 * log10 ((cir_pwr * 2^21) / pacc_cnt ^ 2) + 6D - A(prf) + * But log10 isn't implemented ; let's use ilog2 instead, because it is + * easy to do on binary numbers. Thus, formula becomes: + * rssi = 3*log2(cir_pwr) - 6*log2(pacc_cnt) + 3*log2(2^21) + 6*D - A(prf) + * Notice that 3*log2(2^21) +6*D - A(prf) can be pre-computed. + * Factor 3 comes from ln(2) / ln(10) almost equal to 3 / 10 ; this is an + * approximation done in equation turning log10 into log2 to avoid floating point + */ + + /* u32 is used because ilog2 macro cannot work on bitfield */ + u32 pwr = rssi->cir_pwr; + u32 cnt = rssi->pacc_cnt; + s32 r, rssi_constant; + + /* Do not consider bad packets */ + if (unlikely(!pwr || !cnt)) + return 0; + + rssi_constant = DW3000_RSSI_CONSTANT + 6 * rx_tune * rssi->dgc_dec; + + if (!rssi->prf_64mhz) { + rssi_constant -= DW3000_RSSI_OFFSET_PRF16; + } else + rssi_constant -= ((sts == DW3000_STS_MODE_OFF) ? + DW3000_RSSI_OFFSET_PRF64_IPATOV : + DW3000_RSSI_OFFSET_PRF64_STS); + + r = 3 * ilog2(pwr) - 6 * ilog2(cnt) + rssi_constant; + if (unlikely(r > 0)) { + dev_err(dw->dev, "bad rssi value. Forced to 0\n"); + r = 0; + } + + return (u32)-r; +} + +const struct dw3000_chip_ops dw3000_chip_c0_ops = { + .softreset = dw3000_c0_softreset, + .init = dw3000_c0_init, + .coex_init = dw3000_c0_coex_init, + .coex_gpio = dw3000_c0_coex_gpio, + .check_tx_ok = dw3000_c0_check_tx_ok, + .prog_ldo_and_bias_tune = dw3000_c0_prog_ldo_and_bias_tune, + .get_config_mrxlut_chan = dw3000_c0_get_config_mrxlut_chan, + .get_dgc_dec = dw3000_c0_get_dgc_dec, + .pre_read_sys_time = dw3000_c0_pre_read_sys_time, + .pll_calibration_from_scratch = dw3000_c0_pll_calibration_from_scratch, + .prog_pll_coarse_code = dw3000_c0_prog_pll_coarse_code, + .set_mrxlut = dw3000_c0_set_mrxlut, + .kick_ops_table_on_wakeup = dw3000_c0_kick_ops_table_on_wakeup, + .kick_dgc_on_wakeup = dw3000_c0_kick_dgc_on_wakeup, + .get_registers = dw3000_c0_get_registers, + .compute_rssi = dw3000_c0_compute_rssi, +}; diff --git a/kernel/drivers/net/ieee802154/dw3000_chip_c0.h b/kernel/drivers/net/ieee802154/dw3000_chip_c0.h new file mode 100644 index 0000000..25734f9 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_chip_c0.h @@ -0,0 +1,48 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_CHIP_C0_H +#define __DW3000_CHIP_C0_H + +/* Register PLL_COARSE_CODE */ +#define DW3000_PLL_COARSE_CODE_ID 0x90004 +#define DW3000_PLL_COARSE_CODE_LEN (4U) +#define DW3000_PLL_COARSE_CODE_MASK 0xFFFFFFFFUL +#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_OFFSET (8U) +#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_LEN (14U) +#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_MASK 0x3fff00UL +#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_OFFSET (0U) +#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_LEN (5U) +#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_MASK 0x1fU + +/* Time to wait before reading the calibration status register + * when a calibration from scratch is executed */ +#define DW3000_C0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US (400) + +/* RSSI constants */ +#define DW3000_RSSI_OFFSET_PRF64_STS 121 +#define DW3000_RSSI_OFFSET_PRF64_IPATOV 122 +#define DW3000_RSSI_OFFSET_PRF16 114 +#define DW3000_RSSI_CONSTANT \ + 63 /* 3 * log2(2^21) because log2 used instead of log10 */ + +#endif /* __DW3000_CHIP_C0_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_chip_d0.c b/kernel/drivers/net/ieee802154/dw3000_chip_d0.c new file mode 100644 index 0000000..8bf3fb8 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_chip_d0.c @@ -0,0 +1,331 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include "dw3000.h" +#include "dw3000_core.h" +#include "dw3000_core_reg.h" +#include "dw3000_chip_d0.h" +#include "dw3000_nfcc_coex_core.h" +#include "dw3000_trc.h" + +int dw3000_c0_get_dgc_dec(struct dw3000 *dw, u8 *value); +int dw3000_c0_prog_pll_coarse_code(struct dw3000 *dw); +int dw3000_c0_set_mrxlut(struct dw3000 *dw, const u32 *lut); + +static const struct dw3000_chip_register d0_registers[] = { + /* registres virtuels pour dump des fileID */ + { "GEN_CFG0", 0x00, 0x7e, 0, DW3000_CHIPREG_DUMP, NULL }, + { "GEN_CFG1", 0x01, 0x64, 0, DW3000_CHIPREG_DUMP, NULL }, + { "STS_CFG", 0x02, 0x2c, 0, DW3000_CHIPREG_DUMP, NULL }, + { "RX_TUNE", 0x03, 0x54, 0, DW3000_CHIPREG_DUMP, NULL }, + { "EXT_SYNC", 0x04, 0x21, 0, DW3000_CHIPREG_DUMP, NULL }, + { "GPIO_CTRL", 0x05, 0x2e, 0, DW3000_CHIPREG_DUMP, NULL }, + { "DRX", 0x06, 0x2c, 0, DW3000_CHIPREG_DUMP, NULL }, + { "RF_CONF", 0x07, 0x51, 0, DW3000_CHIPREG_DUMP, NULL }, + { "RF_CAL", 0x08, 0x1e, 0, DW3000_CHIPREG_DUMP, NULL }, + { "FS_CTRL", 0x09, 0x15, 0, DW3000_CHIPREG_DUMP, NULL }, + { "AON", 0x0a, 0x15, 0, DW3000_CHIPREG_DUMP, NULL }, + { "OTP_IF", 0x0b, 0x18, 0, DW3000_CHIPREG_DUMP, NULL }, + { "CIA_1", 0x0c, 0x6c, 0, DW3000_CHIPREG_DUMP, NULL }, + { "CIA_2", 0x0d, 0x6c, 0, DW3000_CHIPREG_DUMP, NULL }, + { "CIA_3", 0x0e, 0x20, 0, DW3000_CHIPREG_DUMP, NULL }, + { "DIG_DIAG", 0x0f, 0x51, 0, DW3000_CHIPREG_DUMP, NULL }, + { "PMSC", 0x11, 0x4a, 0, DW3000_CHIPREG_DUMP, NULL }, + /* TODO: RX/TX buffers limited to first 128bytes only. + Shall we dump the whole 1024 bytes? */ + { "RX_BUFFER", 0x12, 0x80, 0, DW3000_CHIPREG_DUMP, NULL }, + { "RX_BUFFER1", 0x13, 0x80, 0, DW3000_CHIPREG_DUMP, NULL }, + /* No TX_BUFFER as read isn't supported */ + /* CIR memory require 2 bits configured elsewhere, so removed. */ + { "SCRATCH_RAM", 0x16, 0x7f, 0, DW3000_CHIPREG_DUMP, NULL }, + { "AES RAM", 0x17, 0x80, 0, DW3000_CHIPREG_DUMP, NULL }, + { "SET_X", 0x18, 0x1d0, 0, DW3000_CHIPREG_DUMP, NULL }, + { "IN_PTR_CFG", 0x1f, 0x12, 0, DW3000_CHIPREG_DUMP, NULL }, + { "euid", 0x000004, 0x08, 0x00, DW3000_CHIPREG_WP, NULL }, + { "frame_filter", 0x000010, 0x01, 0x01, DW3000_CHIPREG_WP, NULL }, + { "rx_phrmode", 0x000010, 0x01, 0x10, DW3000_CHIPREG_WP, NULL }, + { "tx_phrrate", 0x000010, 0x01, 0x20, DW3000_CHIPREG_WP, NULL }, + { "rx_sts_mode", 0x000011, 0x01, 0x8, DW3000_CHIPREG_WP, NULL }, + { "pdoa_mode", 0x000012, 0x01, 0x3, DW3000_CHIPREG_WP, NULL }, + { "role", 0x000015, 0x01, 0x1, DW3000_CHIPREG_WP, NULL }, + { "frame_filter_cfg", 0x000014, 0x02, 0xfeff, DW3000_CHIPREG_WP, NULL }, + { "datarate", 0x000021, 0x01, 0x4, DW3000_CHIPREG_WP, NULL }, + { "tx_pream_len", 0x000021, 0x01, 0xf0, DW3000_CHIPREG_WP, NULL }, + { "tx_antdly", 0x00007c, 0x02, 0x00, DW3000_CHIPREG_WP, NULL }, + { "txrf_pwrfin", 0x010004, 0x01, 0xfc, DW3000_CHIPREG_WP, NULL }, + { "tx_pwr", 0x010004, 0x04, 0x00, DW3000_CHIPREG_WP, NULL }, + { "rx_sfdtype", 0x010008, 0x01, 0x6, DW3000_CHIPREG_WP, NULL }, + { "channel", 0x010008, 0x01, 0x01, DW3000_CHIPREG_WP, NULL }, + { "tx_pream_ch", 0x010008, 0x01, 0xf8, DW3000_CHIPREG_WP, NULL }, + { "prf", 0x010008, 0x01, 0x1f, DW3000_CHIPREG_WP, NULL }, + { "rx_phr_rate", 0x010010, 0x01, 0x20, DW3000_CHIPREG_WP, NULL }, + { "rx_sts_len", 0x020000, 0x01, 0xff, DW3000_CHIPREG_WP, NULL }, + { "ext_clkdly", 0x040000, 0x02, 0x7f8, DW3000_CHIPREG_WP, NULL }, + { "ext_clkdly_en", 0x040001, 0x01, 0x8, DW3000_CHIPREG_WP, NULL }, + { "gpiodir", 0x050008, 0x02, 0x1ff, DW3000_CHIPREG_WP, NULL }, + { "gpioout", 0x05000c, 0x02, 0x1ff, DW3000_CHIPREG_WP, NULL }, + { "rx_paclen", 0x060000, 0x01, 0x03, DW3000_CHIPREG_WP, NULL }, + { "rx_sfd_to", 0x060002, 0x02, 0x00, DW3000_CHIPREG_WP, NULL }, + { "chan_pg_delay", 0x07001c, 0x01, 0x3f, DW3000_CHIPREG_WP, NULL }, + { "chan_pll_cfg", 0x090001, 0x01, 0x10, DW3000_CHIPREG_WP, NULL }, + { "xtal_trim", 0x090014, 0x01, 0x3f, DW3000_CHIPREG_WP, NULL }, + { "rx_antdly", 0x0e0000, 0x01, 0xf8, DW3000_CHIPREG_WP, NULL }, + { "rx_diag", 0x0e0002, 0x01, 0x10, DW3000_CHIPREG_WP, NULL }, + { "digi_diag_en", 0x0f0000, 0x01, 0x1, DW3000_CHIPREG_WP, NULL }, + { "digi_diag_clr", 0x0f0000, 0x01, 0x2, DW3000_CHIPREG_WP, NULL }, +}; + +const struct dw3000_chip_register *dw3000_d0_get_registers(struct dw3000 *dw, + size_t *count) +{ + *count = ARRAY_SIZE(d0_registers); + return d0_registers; +} + +const u32 *dw3000_d0_get_config_mrxlut_chan(struct dw3000 *dw, u8 channel) +{ + /* Lookup table default values for channel 5 */ + static const u32 dw3000_d0_configmrxlut_ch5[DW3000_CONFIGMRXLUT_MAX] = { + 0x1c0fd, 0x1c43e, 0x1c6be, 0x1c77e, 0x1cf36, 0x1cfb5, 0x1cff5 + }; + + /* Lookup table default values for channel 9 */ + static const u32 dw3000_d0_configmrxlut_ch9[DW3000_CONFIGMRXLUT_MAX] = { + 0x2a8fe, 0x2ac36, 0x2a5fe, 0x2af3e, 0x2af7d, 0x2afb5, 0x2afb5 + }; + + switch (channel) { + case 5: + return dw3000_d0_configmrxlut_ch5; + case 9: + return dw3000_d0_configmrxlut_ch9; + default: + return NULL; + } +} + +/** + * dw3000_d0_softreset() - D0 chip specific software reset + * @dw: The DW device. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_d0_softreset(struct dw3000 *dw) +{ + /* D0 require a FAST command to start soft-reset */ + return dw3000_write_fastcmd(dw, DW3000_CMD_SEMA_RESET); +} + +/** + * dw3000_d0_init() - D0 chip specific initialisation + * @dw: The DW device. + * + * Note: Still used by dw3000_e0_init(). + * + * Return: zero on success, else a negative error code. + */ +int dw3000_d0_init(struct dw3000 *dw) +{ + int rc = 0; + + if (dw->current_operational_state != DW3000_OP_STATE_DEEP_SLEEP) { + /* Disable nfcc_coex mail box only if the chip is not in deep sleep. + * Else, on wake up, the nfcc_coex state will be not detectable. */ + rc = dw3000_nfcc_coex_disable(dw); + } + return rc; +} + +/** + * dw3000_d0_coex_init() - Configure the device's WiFi coexistence GPIO + * @dw: The DW device. + * + * Note: Still used by dw3000_e0_coex_init() as GPIO pin need to be configured. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_d0_coex_init(struct dw3000 *dw) +{ + u32 modemask; + u16 dirmask; + int rc; + + if (dw->coex_gpio < 0) + return 0; + /* Ensure selected GPIO is well configured */ + modemask = DW3000_GPIO_MODE_MSGP0_MODE_BIT_MASK + << (DW3000_GPIO_MODE_MSGP0_MODE_BIT_LEN * dw->coex_gpio); + rc = dw3000_set_gpio_mode(dw, modemask, 0); + if (rc) + return rc; + dirmask = DW3000_GPIO_DIR_GDP0_BIT_MASK + << (DW3000_GPIO_DIR_GDP0_BIT_LEN * dw->coex_gpio); + rc = dw3000_set_gpio_dir(dw, dirmask, 0); + return rc; +} + +/** + * dw3000_d0_coex_gpio() - Update the device's WiFi coexistence GPIO + * @dw: The DW device. + * @state: The WiFi coexistence GPIO state to apply. + * @delay_us: The delay in us before changing GPIO state. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_d0_coex_gpio(struct dw3000 *dw, bool state, int delay_us) +{ + int offset; + /* /!\ could be called first with (true, 1000), then before end of 1000 + microseconds could be called with (false, 0), should handle this case + with stopping the timer if any */ + if (delay_us) { + /* Wait to ensure GPIO is toggle on time */ + if (delay_us > 10) + usleep_range(delay_us - 10, delay_us); + else + udelay(delay_us); + } + offset = DW3000_GPIO_OUT_GOP0_BIT_LEN * dw->coex_gpio; + trace_dw3000_coex_gpio(dw, state, delay_us, 0, dw->coex_status); + dw3000_set_gpio_out(dw, !state << offset, state << offset); + return 0; +} + +static int dw3000_d0_check_tx_ok(struct dw3000 *dw) +{ + return 0; +} + +/** + * dw3000_d0_prog_ldo_and_bias_tune() - Programs the device's LDO and BIAS tuning + * @dw: The DW device. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_d0_prog_ldo_and_bias_tune(struct dw3000 *dw) +{ + struct dw3000_local_data *local = &dw->data; + struct dw3000_otp_data *otp = &dw->otp_data; + if (otp->ldo_tune_lo && otp->ldo_tune_hi) { + dw3000_reg_or16(dw, DW3000_NVM_CFG_ID, 0, DW3000_LDO_BIAS_KICK); + /* Save the kicks for the on-wake configuration */ + local->sleep_mode |= DW3000_LOADLDO; + } + /* Use DGC_CFG from OTP */ + local->dgc_otp_set = otp->dgc_addr == DW3000_DGC_CFG0 ? true : false; + return 0; +} + +/** + * dw3000_d0_pll_calibration_from_scratch() - Calibrate the PLL from scratch + * @dw: the DW device + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_d0_pll_calibration_from_scratch(struct dw3000 *dw) +{ + int rc = 0; + + /* Run the PLL calibration from scratch. + * The USE_OLD_BIT_MASK tells the chip to use the an old PLL_CAL_ID to start + * its calculation. This is just in order to fasten the process. + */ + rc = dw3000_reg_or32(dw, DW3000_PLL_CAL_ID, 0, + DW3000_PLL_CAL_PLL_USE_OLD_BIT_MASK); + if (rc) + return rc; + /* Wait for the PLL calibration (needed before read the calibration status register) */ + usleep_range(DW3000_D0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US, + DW3000_D0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US + 10); + return rc; +} + +/** + * dw3000_d0_compute_rssi() - Compute RSSI from its composites + * @dw: the DW device + * @rssi: RSSI composites + * @rx_tune: state of RX_TUNE_EN bit leads to use dgc_dec value or not + * @sts: current sts mode + * + * Because RSSI cannot be positive in our case (would mean that signals have + * been amplified) we return an unsigned integer considered as always negative. + * + * Return: 0 on error, else the RSSI in absolute value and as an integer. + * expressed in dBm, Q32.0. + */ +u32 dw3000_d0_compute_rssi(struct dw3000 *dw, struct dw3000_rssi *rssi, + bool rx_tune, u8 sts) +{ + /* Details are logged into UWB-3455 + * DW3700 User Manual v0.4 section 4.7.2 gives that RSSI + * can be computed using this formula: + * rssi = 10 * log10 ((cir_pwr * 2^17) / pacc_cnt ^ 2) + 6D - A(prf) + * But log10 isn't implemented ; let's use ilog2 instead, because it is + * easy to do on binary numbers. Thus, formula becomes: + * rssi = 3*log2(cir_pwr) - 6*log2(pacc_cnt) + 3*log2(2^17) + 6*D - A(prf) + * Notice that 3*log2(2^17) +6*D - A(prf) can be pre-computed. + * Factor 3 comes from ln(2) / ln(10) almost equal to 3 / 10 ; this is an + * approximation done in equation turning log10 into log2 to avoid floating point + */ + + /* u32 is used because ilog2 macro cannot work on bitfield */ + u32 pwr = rssi->cir_pwr; + u32 cnt = rssi->pacc_cnt; + s32 r, rssi_constant; + + /* Do not consider bad packets */ + if (unlikely(!pwr || !cnt)) + return 0; + + rssi_constant = DW3000_RSSI_CONSTANT + 6 * rx_tune * rssi->dgc_dec; + + if (!rssi->prf_64mhz) { + rssi_constant -= DW3000_RSSI_OFFSET_PRF16; + } else + rssi_constant -= ((sts == DW3000_STS_MODE_OFF) ? + DW3000_RSSI_OFFSET_PRF64_IPATOV : + DW3000_RSSI_OFFSET_PRF64_STS); + + r = 3 * ilog2(pwr) - 6 * ilog2(cnt) + rssi_constant; + if (unlikely(r > 0)) { + dev_err(dw->dev, "bad rssi value. Forced to 0\n"); + r = 0; + } + + return (u32)-r; +} + +const struct dw3000_chip_ops dw3000_chip_d0_ops = { + .softreset = dw3000_d0_softreset, + .init = dw3000_d0_init, + .coex_init = dw3000_d0_coex_init, + .coex_gpio = dw3000_d0_coex_gpio, + .check_tx_ok = dw3000_d0_check_tx_ok, + .prog_ldo_and_bias_tune = dw3000_d0_prog_ldo_and_bias_tune, + .get_config_mrxlut_chan = dw3000_d0_get_config_mrxlut_chan, + .get_dgc_dec = dw3000_c0_get_dgc_dec, + .pll_calibration_from_scratch = dw3000_d0_pll_calibration_from_scratch, + .prog_pll_coarse_code = dw3000_c0_prog_pll_coarse_code, + .set_mrxlut = dw3000_c0_set_mrxlut, + .get_registers = dw3000_d0_get_registers, + .compute_rssi = dw3000_d0_compute_rssi, +}; diff --git a/kernel/drivers/net/ieee802154/dw3000_chip_d0.h b/kernel/drivers/net/ieee802154/dw3000_chip_d0.h new file mode 100644 index 0000000..4804623 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_chip_d0.h @@ -0,0 +1,48 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_CHIP_D0_H +#define __DW3000_CHIP_D0_H + +/* Register PLL_COARSE_CODE */ +#define DW3000_PLL_COARSE_CODE_ID 0x90004 +#define DW3000_PLL_COARSE_CODE_LEN (4U) +#define DW3000_PLL_COARSE_CODE_MASK 0xFFFFFFFFUL +#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_OFFSET (8U) +#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_LEN (14U) +#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_MASK 0x3fff00UL +#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_OFFSET (0U) +#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_LEN (5U) +#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_MASK 0x1fU + +/* Time to wait before reading the calibration status register + * when a calibration from scratch is executed */ +#define DW3000_D0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US (400) + +/* RSSI constants */ +#define DW3000_RSSI_OFFSET_PRF64_STS 121 +#define DW3000_RSSI_OFFSET_PRF64_IPATOV 122 +#define DW3000_RSSI_OFFSET_PRF16 114 +#define DW3000_RSSI_CONSTANT \ + 51 /* 3 * log2(2^17) because log2 used instead of log10 */ + +#endif /* __DW3000_CHIP_D0_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_chip_e0.c b/kernel/drivers/net/ieee802154/dw3000_chip_e0.c new file mode 100644 index 0000000..f4158be --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_chip_e0.c @@ -0,0 +1,711 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include "dw3000.h" +#include "dw3000_core.h" +#include "dw3000_core_reg.h" +#include "dw3000_chip_e0.h" +#include "dw3000_trc.h" + +int dw3000_c0_prog_pll_coarse_code(struct dw3000 *dw); +int dw3000_d0_softreset(struct dw3000 *dw); +int dw3000_d0_init(struct dw3000 *dw); +int dw3000_d0_coex_init(struct dw3000 *dw); +const struct dw3000_chip_register *dw3000_d0_get_registers(struct dw3000 *dw, + size_t *count); +u32 dw3000_d0_compute_rssi(struct dw3000 *dw, struct dw3000_rssi *rssi, + bool rx_tune, u8 sts); + +const u32 *dw3000_e0_get_config_mrxlut_chan(struct dw3000 *dw, u8 channel) +{ + /* Lookup table default values for channel 5 */ + static const u32 dw3000_e0_configmrxlut_ch5[DW3000_CONFIGMRXLUT_MAX] = { + 0x3803e, 0x3876e, 0x397fe, 0x38efe, 0x39c7e, 0x39dfe, 0x39ff6 + }; + + /* Lookup table default values for channel 9 */ + static const u32 dw3000_e0_configmrxlut_ch9[DW3000_CONFIGMRXLUT_MAX] = { + 0x5407e, 0x547be, 0x54d36, 0x55e36, 0x55f36, 0x55df6, 0x55ffe + }; + + switch (channel) { + case 5: + return dw3000_e0_configmrxlut_ch5; + case 9: + return dw3000_e0_configmrxlut_ch9; + default: + return NULL; + } +} + +/** + * DW3000_COEX_TIMER_XTAL - Timer frequency to use for WiFi coexistence + * + * Max delay is 1s during ranging (1Hz), so require to use per 64 divisor + * to ensure that calculated expire value is lower than 21bits, the max + * register value. + */ +#define DW3000_COEX_TIMER_XTAL DW3000_TIMER_XTAL_DIV64 + +/** + * dw3000_e0_init() - E0 chip specific initialisation + * @dw: The DW device. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_e0_init(struct dw3000 *dw) +{ + int rc = dw3000_d0_init(dw); + if (rc) + return rc; + /* Ensure GPIO block clock is enabled */ + return dw3000_reg_or8(dw, DW3000_CLK_CTRL_ID, 2, + DW3000_CLK_CTRL_GPIO_CLK_EN_BIT_MASK >> 16); +} + +/** + * dw3000_e0_coex_init() - Configure the device's WiFi coexistence GPIO + * @dw: The DW device. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_e0_coex_init(struct dw3000 *dw) +{ + struct dw3000_timer_cfg cfg = { .divider = DW3000_COEX_TIMER_XTAL, + .mode = DW3000_SINGLE_MODE, + .gpio_stop = 0, + .coex_out = 1 }; + int rc; + u8 tmp; + + if (dw->coex_gpio < 0) + return 0; + /* Validate configured WiFi coex GPIO */ + if (dw->coex_gpio < 4 || dw->coex_gpio > 5) { + /* Disable if badly configured and return an error */ + dw->coex_gpio = -1; + return -EINVAL; + } + /* Ensure selected GPIO clock is enabled */ + rc = dw3000_reg_or8(dw, DW3000_CLK_CTRL_ID, 2, + DW3000_CLK_CTRL_GPIO_CLK_EN_BIT_MASK >> 16); + if (rc) + return rc; + /* Ensure selected GPIO is well configured, same as D0 chip */ + rc = dw3000_d0_coex_init(dw); + if (rc) + return rc; + /* Swap COEX GPIO if need to use GPIO 4 as COEX_OUT */ + if (dw->coex_gpio == 4) { + tmp = DW3000_GPIO_MODE_COEX_IO_SWAP_BIT_MASK >> 24; + rc = dw3000_reg_or8(dw, DW3000_GPIO_MODE_ID, 3, tmp); + } else { + tmp = (u8)(~DW3000_GPIO_MODE_COEX_IO_SWAP_BIT_MASK >> 24); + rc = dw3000_reg_and8(dw, DW3000_GPIO_MODE_ID, 3, tmp); + } + if (rc) + return rc; + + /* Configure E0 timer0 for use */ + rc = dw3000_timers_enable(dw); + if (rc) + return rc; + rc = dw3000_timers_reset(dw); + if (rc) + return rc; + rc = dw3000_timer_configure(dw, DW3000_TIMER0, &cfg); + if (rc) + return rc; + return 0; +} + +/** + * dw3000_e0_coex_gpio() - Update the device's WiFi coexistence GPIO + * @dw: The DW device. + * @state: The WiFi coexistence GPIO state to apply. + * @delay_us: The delay in us before changing GPIO state. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_e0_coex_gpio(struct dw3000 *dw, bool state, int delay_us) +{ + int offset = DW3000_GPIO_MODE_MSGP0_MODE_BIT_LEN * dw->coex_gpio; + u32 modemask = DW3000_GPIO_MODE_MSGP0_MODE_BIT_MASK << offset; + int rc; + /* /!\ could be called first with (true, 1000), then before end of 1000 + microseconds could be called with (false, 0), should handle this case + with stopping the timer if any */ + if (delay_us) { + const int factor = + (DW3000_TIMER_FREQ >> DW3000_COEX_TIMER_XTAL) / 1000; + const int divisor = 1000000 / 1000; + u32 expire; + /* Reconfigure selected GPIO for COEX mode */ + rc = dw3000_set_gpio_mode(dw, modemask, 1 << offset); + if (rc) + return rc; + /* Re-configure COEX_OUT_MODE in SYS_CFG for Timer use */ + rc = dw3000_reg_or32(dw, DW3000_SYS_CFG_ID, 0, + DW3000_SYS_CFG_COEX_OUT_MODE_BIT_MASK); + if (rc) + return rc; + /* Launch timer0 */ + expire = delay_us * factor / divisor; + rc = dw3000_timer_set_expiration(dw, DW3000_TIMER0, expire); + if (rc) + return rc; + trace_dw3000_coex_gpio(dw, state, delay_us, expire, + dw->coex_status); + return dw3000_timer_start(dw, DW3000_TIMER0); + } + if (!state) { + /* Stop/reset timer0 & 1 */ + rc = dw3000_timers_reset(dw); + if (rc) + return rc; + /* Reset COEX_OUT_MODE in SYS_CFG to 0 */ + rc = dw3000_reg_and32( + dw, DW3000_SYS_CFG_ID, 0, + ~(u32)DW3000_SYS_CFG_COEX_OUT_MODE_BIT_MASK); + if (rc) + return rc; + } + /* Reconfigure COEX GPIO for GPIO mode */ + rc = dw3000_set_gpio_mode(dw, modemask, 0); + if (rc) + return rc; + /* Update GPIO output state */ + offset = DW3000_GPIO_OUT_GOP0_BIT_LEN * dw->coex_gpio; + trace_dw3000_coex_gpio(dw, state, delay_us, 0, dw->coex_status); + return dw3000_set_gpio_out(dw, !state << offset, state << offset); +} + +static int dw3000_e0_check_tx_ok(struct dw3000 *dw) +{ + return 0; +} + +/** + * dw3000_e0_prog_ldo_and_bias_tune() - Programs the device's LDO and BIAS tuning + * @dw: The DW device. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_e0_prog_ldo_and_bias_tune(struct dw3000 *dw) +{ + struct dw3000_local_data *local = &dw->data; + struct dw3000_otp_data *otp = &dw->otp_data; + + if (otp->ldo_tune_lo && otp->ldo_tune_hi && otp->bias_tune) { + dw3000_reg_or16(dw, DW3000_NVM_CFG_ID, 0, DW3000_LDO_BIAS_KICK); + /* Save the kicks for the on-wake configuration */ + local->sleep_mode |= DW3000_LOADLDO | DW3000_LOADBIAS; + } + /* Ignore use of DGC from OTP */ + local->dgc_otp_set = false; + return 0; +} + +/** + * dw3000_timers_enable() - Enables the device's timers block + * @dw: the DW device + * + * Return: zero on success, else a negative error code. + */ +int dw3000_timers_enable(struct dw3000 *dw) +{ + /* Enable LDO to run the timer - needed if not in IDLE state */ + return dw3000_reg_or8(dw, DW3000_LDO_CTRL_ID, 0, + DW3000_LDO_CTRL_LDO_VDDPLL_EN_BIT_MASK); +} + +/** + * dw3000_timers_reset() - Reset the device's timers block + * @dw: the DW device + * + * It will reset both timers. It can be used to stop a timer running in repeat + * mode. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_timers_reset(struct dw3000 *dw) +{ + return dw3000_reg_and16(dw, DW3000_SOFT_RST_ID, 0, + (u16)(~DW3000_SOFT_RST_TIM_RST_N_BIT_MASK)); +} + +/** + * dw3000_timers_read_and_clear_events() - Read the timers' event counts + * @dw: the DW device + * @evt0: pointer where to store timer0's event count + * @evt1: pointer where to store timer1's event count + * + * When reading from this register the values will be reset/cleared, thus the + * host needs to read both timers' event counts. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_timers_read_and_clear_events(struct dw3000 *dw, u8 *evt0, u8 *evt1) +{ + int rc; + u16 status; + rc = dw3000_reg_read16(dw, DW3000_TIMER_STATUS_ID, 0, &status); + if (unlikely(rc)) + return rc; + if (evt0) + *evt0 = (u8)status; + if (evt1) + *evt1 = (u8)(status >> 8); + return 0; +} + +/** + * dw3000_timer_configure() - Configures the selected timer + * @dw: the DW device + * @timer: timer to configure + * @cfg: pointer to structure holding timer configuration + * + * Return: zero on success, else a negative error code. + */ +int dw3000_timer_configure(struct dw3000 *dw, enum dw3000_timer timer, + struct dw3000_timer_cfg *cfg) +{ + u32 config = + ((u16)cfg->divider + << DW3000_TIMER_CTRL_TIMER_0_DIV_BIT_OFFSET) | + ((u16)cfg->mode << DW3000_TIMER_CTRL_TIMER_0_MODE_BIT_OFFSET) | + ((u16)cfg->gpio_stop + << DW3000_TIMER_CTRL_TIMER_0_GPIO_BIT_OFFSET) | + ((u16)cfg->coex_out + << DW3000_TIMER_CTRL_TIMER_0_COEXOUT_BIT_OFFSET); + /* For TIMER 1 we write the configuration at offset 2 */ + config <<= (u8)timer * 8; + /* Ensure reading CNT register return current counter value! */ + config |= DW3000_TIMER_CTRL_TIMER_0_RD_COUNT_BIT_MASK << (u8)timer; + return dw3000_reg_write32(dw, DW3000_TIMER_CTRL_ID, 0, config); +} + +/** + * dw3000_timer_set_expiration() - sets timer expiration delay + * @dw: the DW device + * @timer: timer to set expiration period (see enum dw3000_timer) + * @exp: expiry count in timer frequency unit + * + * This function set the 22 lower bits of expiration period. + * It take expiry count in timer frequency unit, e.g. if units are XTAL/64 (1.66 us) + * then setting 1024 ~= 1.7 ms delay. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_timer_set_expiration(struct dw3000 *dw, enum dw3000_timer timer, + u32 exp) +{ + u32 reg = DW3000_TIMER0_CNT_SET_ID + (4 * timer); + return dw3000_reg_write32( + dw, reg, 0, exp & DW3000_TIMER0_CNT_SET_TIMER_0_SET_BIT_MASK); +} + +/** + * dw3000_timer_get_counter() - retrieve timer counter or expiration delay + * @dw: the DW device + * @timer: timer to set expiration period (see enum dw3000_timer) + * @counter: pointer where current counter value or expiry count is saved + * + * Return: zero on success, else a negative error code. + */ +int dw3000_timer_get_counter(struct dw3000 *dw, enum dw3000_timer timer, + u32 *counter) +{ + u32 reg = DW3000_TIMER0_CNT_SET_ID + (4 * timer); + return dw3000_reg_read32(dw, reg, 0, counter); +} + +/** + * dw3000_timer_start() - Enables the specified timer + * @dw: the DW device + * @timer: timer to enable + * + * In order to enable, the timer enable bit [0] for TIMER0 or [1] for TIMER1 + * needs to transition from 0 -> 1. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_timer_start(struct dw3000 *dw, enum dw3000_timer timer) +{ + /* TODO: check if dw3000_reg_modify8() is working or not */ + u8 val = 1 << timer; + int rc; + /* Set to '0' */ + rc = dw3000_reg_and8(dw, DW3000_TIMER_CTRL_ID, 0, ~val); + if (rc) + return rc; + /* Set to '1' */ + return dw3000_reg_or8(dw, DW3000_TIMER_CTRL_ID, 0, val); +} + +/** + * dw3000_e0_adc_calibration_monitor_thresholds() - Monitors the thresholds + * @dw: the DW device + * @thresholds: the monitored thresholds + * @rx_event: true if any RX event triggered, else false + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_e0_adc_calibration_monitor_thresholds(struct dw3000 *dw, + u32 *thresholds, + bool *rx_event) +{ + int rc, i; + u8 status; + u16 threshold_arr[4] = { 0, 0, 0, 0 }; + + /* Monitor thresholds */ + for (i = 0; i < DW3000_E0_ADC_THRESHOLD_AVERAGE_LOOPS; i++) { + /* Unfreeze */ + rc = dw3000_reg_modify8(dw, DW3000_MRX_CFG_ID, 0, 0xFE, 0); + if (rc) + return rc; + /* Freeze */ + rc = dw3000_reg_modify8(dw, DW3000_MRX_CFG_ID, 0, 0xFE, 1); + if (rc) + return rc; + rc = dw3000_reg_read32(dw, DW3000_ADC_THRESH_DBG_ID, 0, + thresholds); + if (rc) + return rc; + threshold_arr[0] += (*thresholds & 0xFF); + threshold_arr[1] += ((*thresholds >> 8) & 0xFF); + threshold_arr[2] += ((*thresholds >> 16) & 0xFF); + threshold_arr[3] += ((*thresholds >> 24) & 0xFF); + } + threshold_arr[0] = + (u8)(threshold_arr[0] / DW3000_E0_ADC_THRESHOLD_AVERAGE_LOOPS); + threshold_arr[1] = + (u8)(threshold_arr[1] / DW3000_E0_ADC_THRESHOLD_AVERAGE_LOOPS); + threshold_arr[2] = + (u8)(threshold_arr[2] / DW3000_E0_ADC_THRESHOLD_AVERAGE_LOOPS); + threshold_arr[3] = + (u8)(threshold_arr[3] / DW3000_E0_ADC_THRESHOLD_AVERAGE_LOOPS); + *thresholds = (threshold_arr[3] << 24) + (threshold_arr[2] << 16) + + (threshold_arr[1] << 8) + threshold_arr[0]; + /* Once thresholds are monitored read the sys status */ + rc = dw3000_reg_read8(dw, DW3000_SYS_STATUS_ID, 0, &status); + if (rc) + return rc; + /* Check and return if any RX event triggered. */ + *rx_event = !!(status & (DW3000_SYS_STATUS_ALL_RX_GOOD | + DW3000_SYS_STATUS_ALL_RX_ERR | + DW3000_SYS_STATUS_ALL_RX_TO)); + return 0; +} + +/** + * dw3000_e0_adc_offset_calibration() - Calibrate ADC + * @dw: the DW device + * + * TODO: This function comes from directly from Qorvo/Decawave. + * Hard coded values come with no documentation and should be converted into + * define when the documentation will be available. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_e0_adc_offset_calibration(struct dw3000 *dw) +{ + int rc, k; + u32 switch_control_reg_backup; + u32 sys_enable_lo; + u32 sys_enable_hi; + u32 agc_reg_backup; + u32 dgc_reg_backup; + u32 dgc_lut0_reg_backup; + u32 dgc_lut6_reg_backup; + u8 pgf_idx; + u32 thresholds; + bool rx_event; + + /* Step 1: Get the current registers value used or modified by the calibration */ + rc = dw3000_reg_read32(dw, DW3000_RF_SWITCH_CTRL_ID, 0, + &switch_control_reg_backup); + if (rc) + return rc; + rc = dw3000_reg_read32(dw, DW3000_AGC_CFG_ID, 0, &agc_reg_backup); + if (rc) + return rc; + rc = dw3000_reg_read32(dw, DW3000_DGC_CFG_ID, 0, &dgc_reg_backup); + if (rc) + return rc; + rc = dw3000_reg_read32(dw, DW3000_DGC_LUT_0_CFG_ID, 0, + &dgc_lut0_reg_backup); + if (rc) + return rc; + rc = dw3000_reg_read32(dw, DW3000_DGC_LUT_6_CFG_ID, 0, + &dgc_lut6_reg_backup); + if (rc) + return rc; + + /* Step 2a: De-sensitise RX path by shunting the TXRX switch */ + rc = dw3000_reg_modify32( + dw, DW3000_RF_SWITCH_CTRL_ID, 0, + ~DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_CTRL_BIT_MASK, + (0x38 << DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_CTRL_BIT_OFFSET) | + DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_EN_BIT_MASK); + if (rc) + return rc; + /* Further de-sensitise the RX path by selecting a higher DGC setting */ + rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_0_CFG_ID, 0, + dgc_lut6_reg_backup); + if (rc) + return rc; + /* Step 2b: Disable AGC and set PGF gain manually */ + pgf_idx = dgc_lut0_reg_backup & 0x7; + rc = dw3000_reg_modify8(dw, DW3000_AGC_CFG_ID, 0, ~(0x40 | 0x38 | 0x1), + (0x40 | (pgf_idx << 3))); + if (rc) + return rc; + /* Step 2c: ADC independent thresholds */ + rc = dw3000_reg_write8(dw, DW3000_AGC_CFG_ID, 3, 0); + if (rc) + return rc; + /* Step 2d: Disable DGC */ + dw3000_reg_and8(dw, DW3000_DGC_CFG_ID, 0x0, + (u8)~DW3000_DGC_CFG_RX_TUNE_EN_BIT_MASK); + if (rc) + return rc; + + /* 2e: Disable interrupt events */ + rc = dw3000_reg_read32(dw, DW3000_SYS_ENABLE_HI_ID, 0, &sys_enable_hi); + if (rc) + return rc; + rc = dw3000_reg_read32(dw, DW3000_SYS_ENABLE_LO_ID, 0, &sys_enable_lo); + if (rc) + return rc; + /* disable interrupts */ + rc = dw3000_reg_write32(dw, DW3000_SYS_ENABLE_LO_ID, 0, 0); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_SYS_ENABLE_HI_ID, 0, 0); + if (rc) + return rc; + + /* Step 3a: Enable RX (may need further work) */ + for (k = 0; k < 2; k++) { + { + /* Ensure coex is disable to have immediate RX */ + s8 gpio = dw->coex_gpio; + dw->coex_gpio = -1; + barrier(); + rc = dw3000_rx_enable(dw, 0, 0, 0); + dw->coex_gpio = gpio; + if (rc) + return rc; + } + usleep_range(DW3000_E0_ADC_CALIBRATION_DELAY_US, + DW3000_E0_ADC_CALIBRATION_DELAY_US + 10); + + /* Step 3b: monitor thresholds */ + rc = dw3000_e0_adc_calibration_monitor_thresholds( + dw, &thresholds, &rx_event); + if (rc) + return rc; + + /* Step 3c: disable receiver */ + { + /* Ensure coex is disable */ + s8 gpio = dw->coex_gpio; + dw->coex_gpio = -1; + barrier(); + rc = dw3000_forcetrxoff(dw); + dw->coex_gpio = gpio; + if (rc) + return rc; + } + + if (!rx_event) + break; + } + + /* 3d: restore interrupts */ + rc = dw3000_reg_write32(dw, DW3000_SYS_ENABLE_HI_ID, 0, sys_enable_hi); + if (rc) + return rc; + + rc = dw3000_reg_write32(dw, DW3000_SYS_ENABLE_LO_ID, 0, sys_enable_lo); + if (rc) + return rc; + + /* Step 3e: Set initial DAC indices to settled RMS values */ + rc = dw3000_reg_write32(dw, DW3000_ADC_THRESH_CFG_ID, 0, thresholds); + if (rc) + return rc; + /* Step 4: restore initial register values */ + rc = dw3000_reg_write32(dw, DW3000_SYS_ENABLE_HI_ID, 0, sys_enable_hi); + if (rc) + return rc; + + rc = dw3000_reg_write32(dw, DW3000_SYS_ENABLE_LO_ID, 0, sys_enable_lo); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_RF_SWITCH_CTRL_ID, 0, + switch_control_reg_backup); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_AGC_CFG_ID, 0, agc_reg_backup); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_DGC_CFG_ID, 0, dgc_reg_backup); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_0_CFG_ID, 0, + dgc_lut0_reg_backup); + if (rc) + return rc; + if (rx_event) + return -EINVAL; + return 0; +} + +/** + * dw3000_e0_pll_calibration_from_scratch() - Calibrate the PLL from scratch + * @dw: the DW device + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_e0_pll_calibration_from_scratch(struct dw3000 *dw) +{ + int rc = 0; + + /* Run the PLL calibration from scratch. + * The USE_OLD_BIT_MASK tells the chip to use the an old PLL_CAL_ID to start + * its calculation. This is just in order to fasten the process. + */ + rc = dw3000_reg_or32(dw, DW3000_PLL_CAL_ID, 0, + DW3000_PLL_CAL_PLL_CAL_EN_BIT_MASK | + DW3000_PLL_CAL_PLL_USE_OLD_BIT_MASK); + if (rc) + return rc; + /* Wait for the PLL calibration (needed before read the calibration status register) */ + usleep_range(DW3000_E0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US, + DW3000_E0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US + 10); + return rc; +} + +/** + * dw3000_e0_get_dgc_dec() - Read DGC_DBG register content + * @dw: The DW device. + * @value: Pointer to store DGC DECISION value. + * + * Return: zero on succes, else a negative error code. + */ +static int dw3000_e0_get_dgc_dec(struct dw3000 *dw, u8 *value) +{ + int rc; + u32 dgc_dbg; + + rc = dw3000_reg_read32(dw, DW3000_E0_DGC_DBG_ID, 0, &dgc_dbg); + if (unlikely(rc)) + return rc; + + /* DGC_DECISION is on bits 28 to 30 of DGC_CFG, cf 8.2.4.2 of DW3700 + * User Manual, store it to right the rssi stats entry */ + *value = (u8)((dgc_dbg & 0x70000000) >> 0x1c); + return 0; +} + +/** + * dw3000_e0_pll_coarse_code() - Calibrate the PLL from scratch + * @dw: the DW device + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_e0_pll_coarse_code(struct dw3000 *dw) +{ + u8 tmp; + + /* Clear CH9_ICAS/RCAS bits */ + tmp = (u8)(~(DW3000_PLL_COARSE_CODE_CH9_ICAS_BIT_MASK | + DW3000_PLL_COARSE_CODE_CH9_RCAS_BIT_MASK) >> + 24); + return dw3000_reg_and8(dw, DW3000_PLL_COARSE_CODE_ID, 3, tmp); +} + +/** + * dw3000_e0_set_mrxlut() - Configure mrxlut + * @dw: The DW device. + * @lut: Pointer to LUT to write to DGC_LUT_X_CFG registers + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_e0_set_mrxlut(struct dw3000 *dw, const u32 *lut) +{ + int rc; + + /* Update DGC CFG. Leave it at the beginning for E0 chip */ + rc = dw3000_reg_write32(dw, DW3000_DGC_CFG0_ID, 0x0, DW3000_DGC_CFG0); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_DGC_CFG1_ID, 0x0, DW3000_DGC_CFG1); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_DGC_CFG2_ID, 0x0, DW3000_DGC_CFG2); + if (rc) + return rc; + + /* Update LUT registers */ + rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_0_CFG_ID, 0x0, lut[0]); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_1_CFG_ID, 0x0, lut[1]); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_2_CFG_ID, 0x0, lut[2]); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_3_CFG_ID, 0x0, lut[3]); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_4_CFG_ID, 0x0, lut[4]); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_5_CFG_ID, 0x0, lut[5]); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_DGC_LUT_6_CFG_ID, 0x0, lut[6]); + return rc; +} + +const struct dw3000_chip_ops dw3000_chip_e0_ops = { + .softreset = dw3000_d0_softreset, + .init = dw3000_e0_init, + .coex_init = dw3000_e0_coex_init, + .coex_gpio = dw3000_e0_coex_gpio, + .check_tx_ok = dw3000_e0_check_tx_ok, + .prog_ldo_and_bias_tune = dw3000_e0_prog_ldo_and_bias_tune, + .get_config_mrxlut_chan = dw3000_e0_get_config_mrxlut_chan, + .get_dgc_dec = dw3000_e0_get_dgc_dec, + .adc_offset_calibration = dw3000_e0_adc_offset_calibration, + .pll_calibration_from_scratch = dw3000_e0_pll_calibration_from_scratch, + .prog_pll_coarse_code = dw3000_c0_prog_pll_coarse_code, + .pll_coarse_code = dw3000_e0_pll_coarse_code, + .set_mrxlut = dw3000_e0_set_mrxlut, + .get_registers = dw3000_d0_get_registers, + .compute_rssi = dw3000_d0_compute_rssi, +}; diff --git a/kernel/drivers/net/ieee802154/dw3000_chip_e0.h b/kernel/drivers/net/ieee802154/dw3000_chip_e0.h new file mode 100644 index 0000000..c4a97ee --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_chip_e0.h @@ -0,0 +1,112 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_CHIP_E0_H +#define __DW3000_CHIP_E0_H + +/* Forward declaration */ +struct dw3000; + +enum dw3000_timer { DW3000_TIMER0 = 0, DW3000_TIMER1 }; + +enum dw3000_timer_mode { DW3000_SINGLE_MODE = 0, DW3000_REPEAT_MODE }; + +/* Register PLL_COARSE_CODE */ +#define DW3000_PLL_COARSE_CODE_ID 0x90004 +#define DW3000_PLL_COARSE_CODE_LEN (4U) +#define DW3000_PLL_COARSE_CODE_MASK 0xFFFFFFFFUL +#define DW3000_PLL_COARSE_CODE_CH9_CAL_WITH_PREBUF_BIT_OFFSET (27U) +#define DW3000_PLL_COARSE_CODE_CH9_CAL_WITH_PREBUF_BIT_LEN (1U) +#define DW3000_PLL_COARSE_CODE_CH9_CAL_WITH_PREBUF_BIT_MASK 0x8000000UL +#define DW3000_PLL_COARSE_CODE_CH5_CAL_WITH_PREBUF_BIT_OFFSET (26U) +#define DW3000_PLL_COARSE_CODE_CH5_CAL_WITH_PREBUF_BIT_LEN (1U) +#define DW3000_PLL_COARSE_CODE_CH5_CAL_WITH_PREBUF_BIT_MASK 0x4000000UL +#define DW3000_PLL_COARSE_CODE_CH9_ICAS_BIT_OFFSET (25U) +#define DW3000_PLL_COARSE_CODE_CH9_ICAS_BIT_LEN (1U) +#define DW3000_PLL_COARSE_CODE_CH9_ICAS_BIT_MASK 0x2000000UL +#define DW3000_PLL_COARSE_CODE_CH9_RCAS_BIT_OFFSET (24U) +#define DW3000_PLL_COARSE_CODE_CH9_RCAS_BIT_LEN (1U) +#define DW3000_PLL_COARSE_CODE_CH9_RCAS_BIT_MASK 0x1000000UL +#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_OFFSET (8U) +#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_LEN (14U) +#define DW3000_PLL_COARSE_CODE_CH5_VCO_COARSE_TUNE_BIT_MASK 0x3fff00UL +#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_OFFSET (0U) +#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_LEN (7U) +#define DW3000_PLL_COARSE_CODE_CH9_VCO_COARSE_TUNE_BIT_MASK 0x7fU + +/* Delay to wait after RX enable to calibrate the ADC on E0 chip */ +#define DW3000_E0_ADC_CALIBRATION_DELAY_US (200) + +/* Loops to compute the ADC threshold average on E0 chip */ +#define DW3000_E0_ADC_THRESHOLD_AVERAGE_LOOPS (4) +/* Time to wait before reading the calibration status register + * when a calibration from scratch is executed */ +#define DW3000_E0_PLL_CALIBRATION_FROM_SCRATCH_DELAY_US (400) + +#define DW3000_TIMER_FREQ 38400000 + +#define DW3000_E0_DGC_DBG_ID 0x30054 + +enum dw3000_timer_period { + /* 38.4 MHz */ + DW3000_TIMER_XTAL_NODIV = 0, + /* 19.2 MHz */ + DW3000_TIMER_XTAL_DIV2, + /* 9.6 MHz */ + DW3000_TIMER_XTAL_DIV4, + /* 4.8 MHz */ + DW3000_TIMER_XTAL_DIV8, + /* 2.4 MHz */ + DW3000_TIMER_XTAL_DIV16, + /* 1.2 MHz */ + DW3000_TIMER_XTAL_DIV32, + /* 0.6 MHz */ + DW3000_TIMER_XTAL_DIV64, + /* 0.3 MHz */ + DW3000_TIMER_XTAL_DIV128 +}; + +struct dw3000_timer_cfg { + /* Select the timer frequency (divider) */ + enum dw3000_timer_period divider; + /* Select the timer mode */ + enum dw3000_timer_mode mode; + /* Set to '1' to halt this timer on GPIO interrupt */ + u8 gpio_stop; + /* Configure GPIO for WiFi co-ex */ + u8 coex_out; +}; + +/* Hardware timer functions */ +int dw3000_timers_enable(struct dw3000 *dw); +int dw3000_timers_reset(struct dw3000 *dw); +int dw3000_timers_read_and_clear_events(struct dw3000 *dw, u8 *evt0, u8 *evt1); + +int dw3000_timer_configure(struct dw3000 *dw, enum dw3000_timer timer, + struct dw3000_timer_cfg *cfg); +int dw3000_timer_set_expiration(struct dw3000 *dw, enum dw3000_timer timer, + u32 exp); +int dw3000_timer_get_counter(struct dw3000 *dw, enum dw3000_timer timer, + u32 *counter); +int dw3000_timer_start(struct dw3000 *dw, enum dw3000_timer timer); + +#endif /* __DW3000_CHIP_E0_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_cir.h b/kernel/drivers/net/ieee802154/dw3000_cir.h new file mode 100644 index 0000000..d66429c --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_cir.h @@ -0,0 +1,83 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_CIR_H +#define __DW3000_CIR_H + +#include <linux/completion.h> + +#include "dw3000_core_reg.h" + +/* Default count of CIR records stored in cir_data */ +#define DW3000_DEFAULT_CIR_RECORD_COUNT 20 + +/** + * struct dw3000_cir_record - Hold a cir data record + * @real: real part of the cir record. Format 6.18 corresponding to sign:figure + * @imag: imaginary part of the cir record. Format 6.18 corresponding to sign:figure + */ +struct dw3000_cir_record { + u8 real[3]; + u8 imag[3]; +} __attribute__((__packed__)); + +/** + * struct dw3000_cir_data - Allow CIR memory records dumped through debugfs + * @complete: synchronize producer and consumer + * @mutex: ensure security of accesses to the instance of this structure + * @ciaregs: array storing all CIA registers + * @count: number of records in data member + * @filter: kind of frame selected and accumulated in CIR + * @ts: timestamp extracted from CIA registers + * @utime: rx timestamp for the cir measurement + * @fp_power1: first path power component f1 + * @fp_power2: first path power component f2 + * @fp_power3: first path power component f3 + * @offset: user defined offset + * @fp_index: index of first path record in CIR register + * @pdoa: pdoa raw value + * @acc: number of symbols accumulated in CIR + * @type: CIR type field + * @dummy: store the dummy byte firstly received at each CIR memory reading + * @data: table storing 'count' records read from CIR data memory + */ +struct dw3000_cir_data { + struct completion complete; + struct mutex mutex; + __le32 ciaregs[DW3000_DB_DIAG_SET_LEN >> 2]; + unsigned int count; + u32 filter; + u64 ts; + u64 utime; + u32 fp_power1; + u32 fp_power2; + u32 fp_power3; + s32 offset; + u16 fp_index; + u16 pdoa; + u16 acc; + u8 type; + u8 dummy; + struct dw3000_cir_record data[1]; +}; + +#endif diff --git a/kernel/drivers/net/ieee802154/dw3000_coex.h b/kernel/drivers/net/ieee802154/dw3000_coex.h new file mode 100644 index 0000000..c2dc6b7 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_coex.h @@ -0,0 +1,159 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_COEX_H +#define __DW3000_COEX_H + +#include "dw3000.h" + +static inline int dw3000_coex_stop(struct dw3000 *dw); + +/** + * dw3000_coex_gpio - change the state of the GPIO used for WiFi coexistence + * @dw: the DW device + * @state: the new GPIO state to set + * @delay_us: delay before toggling the GPIO. + * + * This function only call the version dependent coex_gpio function. + * + * It cannot be called if dw3000_coex_init() has fail or if no coex gpio + * is defined. So no need to test chip_ops. + * + * Return: 0 on success, else a negative error code. + */ +static inline int dw3000_coex_gpio(struct dw3000 *dw, int state, int delay_us) +{ + int rc; + + /* Use SPI in queuing mode */ + dw3000_spi_queue_start(dw); + rc = dw->chip_ops->coex_gpio(dw, state, delay_us); + if (rc) + return dw3000_spi_queue_reset(dw, rc); + rc = dw3000_spi_queue_flush(dw); + if (!rc) + dw->coex_status = state; + return rc; +} + +/** + * dw3000_coex_start - Handle WiFi coex gpio at start of uwb exchange. + * @dw: the DW device + * @trx_delayed: pointer to tx/rx_delayed parameter to update + * @trx_date_dtu: pointer to tx/rx_date_dtu parameter to update + * @cur_time_dtu: current device time in DTU + * + * Calculate a timer delay in us and programme it to ensure the WiFi coex + * GPIO is asserted at least `coex_delay_us` before the next operation. + * + * This function also reset the GPIO immediatly if the calculated timer delay + * is more than `coex_interval_us`. + * + * Return: 0 on success, else a negative error code. + */ +static inline int dw3000_coex_start(struct dw3000 *dw, bool *trx_delayed, + u32 *trx_date_dtu, u32 cur_time_dtu) +{ + int delay_us; + int timer_us = 0; + + if (dw->coex_gpio < 0 || !dw->coex_enabled) + return 0; + /* Add a margin for required SPI transactions to the coex delay time + * to ensure GPIO change at right time. */ + delay_us = dw->coex_delay_us + dw->coex_margin_us; + if (*trx_delayed == false) { + /* Change to delayed TX/RX with the minimum required delay */ + *trx_date_dtu = cur_time_dtu + US_TO_DTU(delay_us); + *trx_delayed = true; + /* Leave timer_us to 0 to set gpio now. */ + } else { + /* Calculate timer duration to program. */ + /* V TX + * | time_difference_us | + * | margin | timer | delay | + * G + * + * timer = time_difference_us - (delay + margin) + */ + int time_difference_dtu = *trx_date_dtu - cur_time_dtu; + int time_difference_us = DTU_TO_US(time_difference_dtu); + if (time_difference_us > delay_us) + timer_us = time_difference_us - delay_us; + /* else, too late for timer, set gpio now */ + } + trace_dw3000_coex_gpio_start(dw, timer_us, dw->coex_status, + dw->coex_interval_us); + if (dw->coex_status) { + if (timer_us < dw->coex_interval_us) + return 0; /* Nothing more to do */ + dw3000_coex_stop(dw); + } + /* Set coexistence gpio on chip */ + return dw3000_coex_gpio(dw, true, timer_us); +} + +/** + * dw3000_coex_stop - Handle WiFi coex gpio at end of uwb exchange. + * @dw: the DW device + * + * Return: 0 on success, else a negative error code. + */ +static inline int dw3000_coex_stop(struct dw3000 *dw) +{ + if (dw->coex_gpio < 0 || !dw->coex_enabled) + return 0; + + trace_dw3000_coex_gpio_stop(dw, dw->coex_status); + if (!dw->coex_status) + return 0; + /* Reset coex GPIO on chip */ + return dw3000_coex_gpio(dw, false, 0); +} + +/** + * dw3000_coex_init - Initialise WiFi coex gpio + * @dw: the DW device + * + * Return: 0 on success, else a negative error code. + */ +static inline int dw3000_coex_init(struct dw3000 *dw) +{ + int rc; + + if (dw->coex_gpio < 0) + return 0; + /* Sanity check chip dependent functions */ + if (!dw->chip_ops || !dw->chip_ops->coex_gpio || + !dw->chip_ops->coex_init) + return -ENOTSUPP; + /* Call chip dependent initialisation */ + rc = dw->chip_ops->coex_init(dw); + if (unlikely(rc)) { + dev_err(dw->dev, + "WiFi coexistence configuration has failed (%d)\n", rc); + } + dw->coex_status = false; + return rc; +} + +#endif /* __DW3000_COEX_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_compat_reg.h b/kernel/drivers/net/ieee802154/dw3000_compat_reg.h new file mode 100644 index 0000000..ae50102 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_compat_reg.h @@ -0,0 +1,205 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_COMPAT_REG_H +#define __DW3000_COMPAT_REG_H + +#define MAX_CHIP_VERSIONS 3 + +#ifdef DEFINE_COMPAT_REGISTERS +#define REG_TYPE unsigned int +#define REG_ADDR(n, ...) REG_TYPE n[MAX_CHIP_VERSIONS] = { __VA_ARGS__ } +#else +#define REG_TYPE extern unsigned int +#define REG_ADDR(n, ...) REG_TYPE n[MAX_CHIP_VERSIONS] +#endif +#define REG(n) n[__dw3000_chip_version] + +REG_TYPE __dw3000_chip_version; + +REG_ADDR(__tx_fctrl_id, 0x24, 0x20, 0x20); +REG_ADDR(__tx_fctrl_hi_id, 0x28, 0x24, 0x24); +REG_ADDR(__dx_time_id, 0x2c, 0x28, 0x28); +REG_ADDR(__rx_ttcko_lo_id, 0x5c, 0x58, 0x58); +REG_ADDR(__rx_ttcko_hi_id, 0x60, 0x5c, 0x5c); +REG_ADDR(__rx_time_0_id, 0x64, 0x60, 0x60); +REG_ADDR(__rx_time_1_id, 0x68, 0x64, 0x64); +REG_ADDR(__rx_time_2_id, 0x6c, 0x68, 0x68); +REG_ADDR(__rx_time_3_id, 0x70, 0x6c, 0x6c); +REG_ADDR(__tx_time_lo_id, 0x74, 0x70, 0x70); +REG_ADDR(__tx_time_hi_id, 0x78, 0x74, 0x74); +REG_ADDR(__tx_time_2_id, 0x10000, 0x78, 0x78); +REG_ADDR(__tx_antd_id, 0x10004, 0x7c, 0x7c); +REG_ADDR(__ack_resp_id, 0x10008, 0x10000, 0x10000); +REG_ADDR(__tx_power_id, 0x1000c, 0x10004, 0x10004); +REG_ADDR(__chan_ctrl_id, 0x10014, 0x10008, 0x10008); +REG_ADDR(__le_pend_01_id, 0x10018, 0x1000C, 0x1000C); +REG_ADDR(__le_pend_23_id, 0x1001c, 0x10010, 0x10010); +REG_ADDR(__spi_collision_status_id, 0x10020, 0x10014, 0x10014); +REG_ADDR(__rdb_status_id, 0x10024, 0x10018, 0x10018); +REG_ADDR(__rdb_diag_mode_id, 0x10028, 0x10020, 0x10020); +REG_ADDR(__regmap_ver_id, 0x1002c, 0x10024, 0x10024); +REG_ADDR(__sar_ctrl_sar_force_sel_bit_len, 3U, 4U, 4U); +REG_ADDR(__sar_ctrl_sar_force_sel_bit_mask, 0x7000U, 0xf000U, 0xf000U); +REG_ADDR(__nvm_cfg_gear_id_bit_offset, 11U, 12U, 12U); +REG_ADDR(__nvm_cfg_gear_id_bit_mask, 0x1800U, 0x3000U, 0x3000U); +REG_ADDR(__nvm_cfg_gear_kick_bit_offset, 10U, 0x11U, 0x11U); +REG_ADDR(__nvm_cfg_gear_kick_bit_mask, 0x400U, 0x800U, 0x800U); +REG_ADDR(__nvm_cfg_bias_kick_bit_offset, 8U, 10U, 10U); +REG_ADDR(__nvm_cfg_bias_kick_bit_mask, 0x100U, 0x400U, 0x400U); +REG_ADDR(__nvm_cfg_ldo_kick_bit_offset, 7U, 9U, 9U); +REG_ADDR(__nvm_cfg_ldo_kick_bit_mask, 0x80U, 0x200U, 0x200U); +REG_ADDR(__nvm_cfg_dgc_kick_bit_offset, 6U, 8U, 8U); +REG_ADDR(__nvm_cfg_dgc_kick_bit_mask, 0x40U, 0x100U, 0x100U); +REG_ADDR(__nvm_cfg_dgc_sel_bit_offset, 13U, 7U, 7U); +REG_ADDR(__nvm_cfg_dgc_sel_bit_mask, 0x2000U, 0x80U, 0x80U); +REG_ADDR(__nvm_cfg_nvm_pd_bit_offset, 9U, 6U, 6U); +REG_ADDR(__nvm_cfg_nvm_pd_bit_mask, 0x200U, 0x40U, 0x40U); +REG_ADDR(__ip_diag_1_ipchannelarea_bit_len, 17U, 19U, 19U); +REG_ADDR(__ip_diag_1_ipchannelarea_bit_mask, 0x1ffffUL, 0x7ffffUL, 0x7ffffUL); +REG_ADDR(__cy0_diag_1_cy0channelarea_bit_len, 16U, 19U, 19U); +REG_ADDR(__cy0_diag_1_cy0channelarea_bit_mask, 0xffffU, 0x7ffffU, 0x7ffffU); +REG_ADDR(__cy1_diag_1_cy1channelarea_bit_len, 16U, 19U, 19U); +REG_ADDR(__cy1_diag_1_cy1channelarea_bit_mask, 0xffffU, 0x7ffffU, 0x7ffffU); +REG_ADDR(__ip_config_hi_id, 0xe000e, 0xe0010, 0xe0010); +REG_ADDR(__cy_config_lo_id, 0xe0012, 0xe0014, 0xe0014); +REG_ADDR(__cy_config_hi_id, 0xe0016, 0xe0018, 0xe0018); +REG_ADDR(__cia_coefficient_adjust_id, 0xe001a, 0xe001c, 0xe001c); +REG_ADDR(__pgf_delay_comp_lo_id, 0xe001e, 0xe0020, 0xe0020); +REG_ADDR(__pgf_delay_comp_hi_id, 0xe0022, 0xe0024, 0xe0024); +REG_ADDR(__adc_mem_ptr_id, 0xf0020, 0xf0024, 0xf0024); +REG_ADDR(__test_ctrl0_id, 0xf0024, 0xf0028, 0xf0028); +REG_ADDR(__fcmd_status_id, 0xf003c, 0xf0040, 0xf0040); +REG_ADDR(__test_logging_id, 0xf0040, 0xf0044, 0xf0044); +REG_ADDR(__status_logging_id, 0xf0044, 0xf0048, 0xf0048); +REG_ADDR(__ctr_dbg_id, 0xf0048, 0xf004C, 0xf004C); +REG_ADDR(__led_ctrl_id, 0x110016, 0x110018, 0x110018); +REG_ADDR(__rx_ppm_id, 0x11001a, 0x11001c, 0x11001c); +REG_ADDR(__fosc_ctrl_id, 0x11001e, 0x110020, 0x110020); +REG_ADDR(__bias_ctrl_id, 0x11001f, 0x110030, 0x110030); +REG_ADDR(__bias_ctrl_dig_bias_ctrl_tc_r3_ulv_bit_offset, 12U, 9U, 9U); +REG_ADDR(__bias_ctrl_dig_bias_ctrl_tc_r3_ulv_bit_mask, 0x3000U, 0x600U, 0x600U); +/* LDO and BIAS tune kick */ +/* C0 : Writing to bit 7 and 8 */ +/* D0 OTP errata : only kick LDO not BIAS */ +/* E0 : only kick LDO bit 9 (Writing to bit 10 for BIAS and 9 for LDO) */ +REG_ADDR(__ldo_bias_kick, 0x180, 0x200, 0x600); + +/* E0 specific */ +REG_ADDR(__dgc_lut_0_cfg_id, 0x30038, 0x30038, 0x3002c); +REG_ADDR(__dgc_lut_1_cfg_id, 0x3003c, 0x3003c, 0x30030); +REG_ADDR(__dgc_lut_2_cfg_id, 0x30040, 0x30040, 0x30034); +REG_ADDR(__dgc_lut_3_cfg_id, 0x30044, 0x30044, 0x30038); +REG_ADDR(__dgc_lut_4_cfg_id, 0x30048, 0x30048, 0x3003c); +REG_ADDR(__dgc_lut_5_cfg_id, 0x3004c, 0x3004c, 0x30040); +REG_ADDR(__dgc_lut_6_cfg_id, 0x30050, 0x30050, 0x30044); +REG_ADDR(__dgc_dbg_id, 0x30060, 0x30060, 0x30054); +REG_ADDR(__cia_tdoa_0_tdoa_bit_len, 32U, 32U, 16U); +REG_ADDR(__cia_tdoa_0_tdoa_bit_mask, 0xffffffffUL, 0xffffffffUL, 0xffffU); + +#define DW3000_TX_FCTRL_ID REG(__tx_fctrl_id) +#define DW3000_TX_FCTRL_HI_ID REG(__tx_fctrl_hi_id) +#define DW3000_DX_TIME_ID REG(__dx_time_id) +#define DW3000_RX_TTCKO_LO_ID REG(__rx_ttcko_lo_id) +#define DW3000_RX_TTCKO_HI_ID REG(__rx_ttcko_hi_id) +#define DW3000_RX_TIME_0_ID REG(__rx_time_0_id) +#define DW3000_RX_TIME_1_ID REG(__rx_time_1_id) +#define DW3000_RX_TIME_2_ID REG(__rx_time_2_id) +#define DW3000_RX_TIME_3_ID REG(__rx_time_3_id) +#define DW3000_TX_TIME_LO_ID REG(__tx_time_lo_id) +#define DW3000_TX_TIME_HI_ID REG(__tx_time_hi_id) +#define DW3000_TX_TIME_2_ID REG(__tx_time_2_id) +#define DW3000_TX_ANTD_ID REG(__tx_antd_id) +#define DW3000_ACK_RESP_ID REG(__ack_resp_id) +#define DW3000_TX_POWER_ID REG(__tx_power_id) +#define DW3000_CHAN_CTRL_ID REG(__chan_ctrl_id) +#define DW3000_LE_PEND_01_ID REG(__le_pend_01_id) +#define DW3000_LE_PEND_23_ID REG(__le_pend_23_id) +#define DW3000_SPI_COLLISION_STATUS_ID REG(__spi_collision_status_id) +#define DW3000_RDB_STATUS_ID REG(__rdb_status_id) +#define DW3000_RDB_DIAG_MODE_ID REG(__rdb_diag_mode_id) +#define DW3000_REGMAP_VER_ID REG(__regmap_ver_id) +#define DW3000_SAR_CTRL_SAR_FORCE_SEL_BIT_LEN \ + REG(__sar_ctrl_sar_force_sel_bit_len) +#define DW3000_SAR_CTRL_SAR_FORCE_SEL_BIT_MASK \ + REG(__sar_ctrl_sar_force_sel_bit_mask) +#define DW3000_NVM_CFG_GEAR_ID_BIT_OFFSET REG(__nvm_cfg_gear_id_bit_offset) +#define DW3000_NVM_CFG_GEAR_ID_BIT_MASK REG(__nvm_cfg_gear_id_bit_mask) +#define DW3000_NVM_CFG_GEAR_KICK_BIT_OFFSET REG(__nvm_cfg_gear_kick_bit_offset) +#define DW3000_NVM_CFG_GEAR_KICK_BIT_MASK REG(__nvm_cfg_gear_kick_bit_mask) +#define DW3000_NVM_CFG_BIAS_KICK_BIT_OFFSET REG(__nvm_cfg_bias_kick_bit_offset) +#define DW3000_NVM_CFG_BIAS_KICK_BIT_MASK REG(__nvm_cfg_bias_kick_bit_mask) +#define DW3000_NVM_CFG_LDO_KICK_BIT_OFFSET REG(__nvm_cfg_ldo_kick_bit_offset) +#define DW3000_NVM_CFG_LDO_KICK_BIT_MASK REG(__nvm_cfg_ldo_kick_bit_mask) +#define DW3000_NVM_CFG_DGC_KICK_BIT_OFFSET REG(__nvm_cfg_dgc_kick_bit_offset) +#define DW3000_NVM_CFG_DGC_KICK_BIT_MASK REG(__nvm_cfg_dgc_kick_bit_mask) +#define DW3000_NVM_CFG_DGC_SEL_BIT_OFFSET REG(__nvm_cfg_dgc_sel_bit_offset) +#define DW3000_NVM_CFG_DGC_SEL_BIT_MASK REG(__nvm_cfg_dgc_sel_bit_mask) +#define DW3000_NVM_CFG_NVM_PD_BIT_OFFSET REG(__nvm_cfg_nvm_pd_bit_offset) +#define DW3000_NVM_CFG_NVM_PD_BIT_MASK REG(__nvm_cfg_nvm_pd_bit_mask) +#define DW3000_IP_DIAG_1_IPCHANNELAREA_BIT_LEN \ + REG(__ip_diag_1_ipchannelarea_bit_len) +#define DW3000_IP_DIAG_1_IPCHANNELAREA_BIT_MASK \ + REG(__ip_diag_1_ipchannelarea_bit_len) +#define DW3000_CY0_DIAG_1_CY0CHANNELAREA_BIT_LEN \ + REG(__cy0_diag_1_cy0channelarea_bit_len) +#define DW3000_CY0_DIAG_1_CY0CHANNELAREA_BIT_MASK \ + REG(__cy0_diag_1_cy0channelarea_bit_mask) +#define DW3000_CY1_DIAG_1_CY1CHANNELAREA_BIT_LEN \ + REG(__cy1_diag_1_cy1channelarea_bit_len) +#define DW3000_CY1_DIAG_1_CY1CHANNELAREA_BIT_MASK \ + REG(__cy1_diag_1_cy1channelarea_bit_mask) +#define DW3000_IP_CONFIG_HI_ID REG(__ip_config_hi_id) +#define DW3000_CY_CONFIG_LO_ID REG(__cy_config_lo_id) +#define DW3000_CY_CONFIG_HI_ID REG(__cy_config_hi_id) +#define DW3000_CIA_COEFFICIENT_ADJUST_ID REG(__cia_coefficient_adjust_id) +#define DW3000_PGF_DELAY_COMP_LO_ID REG(__pgf_delay_comp_lo_id) +#define DW3000_PGF_DELAY_COMP_HI_ID REG(__pgf_delay_comp_hi_id) +#define DW3000_ADC_MEM_PTR_ID REG(__adc_mem_ptr_id) +#define DW3000_TEST_CTRL0_ID REG(__test_ctrl0_id) +#define DW3000_FCMD_STATUS_ID REG(__fcmd_status_id) +#define DW3000_TEST_LOGGING_ID REG(__test_logging_id) +#define DW3000_STATUS_LOGGING_ID REG(__status_logging_id) +#define DW3000_CTR_DBG_ID REG(__ctr_dbg_id) +#define DW3000_LED_CTRL_ID REG(__led_ctrl_id) +#define DW3000_RX_PPM_ID REG(__rx_ppm_id) +#define DW3000_FOSC_CTRL_ID REG(__fosc_ctrl_id) +#define DW3000_BIAS_CTRL_ID REG(__bias_ctrl_id) +#define DW3000_BIAS_CTRL_DIG_BIAS_CTRL_TC_R3_ULV_BIT_OFFSET \ + REG(__bias_ctrl_dig_bias_ctrl_tc_r3_ulv_bit_offset) +#define DW3000_BIAS_CTRL_DIG_BIAS_CTRL_TC_R3_ULV_BIT_MASK \ + REG(__bias_ctrl_dig_bias_ctrl_tc_r3_ulv_bit_mask) +#define DW3000_LDO_BIAS_KICK REG(__ldo_bias_kick) + +/* E0 specific */ +#define DW3000_DGC_LUT_0_CFG_ID REG(__dgc_lut_0_cfg_id) +#define DW3000_DGC_LUT_1_CFG_ID REG(__dgc_lut_1_cfg_id) +#define DW3000_DGC_LUT_2_CFG_ID REG(__dgc_lut_2_cfg_id) +#define DW3000_DGC_LUT_3_CFG_ID REG(__dgc_lut_3_cfg_id) +#define DW3000_DGC_LUT_4_CFG_ID REG(__dgc_lut_4_cfg_id) +#define DW3000_DGC_LUT_5_CFG_ID REG(__dgc_lut_5_cfg_id) +#define DW3000_DGC_LUT_6_CFG_ID REG(__dgc_lut_6_cfg_id) +#define DW3000_DGC_DBG_ID REG(__dgc_dbg_id) +#define DW3000_CIA_TDOA_0_TDOA_BIT_LEN REG(__cia_tdoa_0_tdoa_bit_len) +#define DW3000_CIA_TDOA_0_TDOA_BIT_MASK REG(__cia_tdoa_0_tdoa_bit_mask) + +#endif /* __DW3000_COMPAT_REG_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_core.c b/kernel/drivers/net/ieee802154/dw3000_core.c new file mode 100644 index 0000000..7e8e2cb --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_core.c @@ -0,0 +1,8404 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/of_gpio.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/bitfield.h> +#include <linux/log2.h> + +#include "dw3000.h" +#include "dw3000_core.h" +#define DEFINE_COMPAT_REGISTERS +#include "dw3000_core_reg.h" +#include "dw3000_cir.h" +#include "dw3000_stm.h" +#include "dw3000_trc.h" +#include "dw3000_perf.h" +#include "dw3000_pm.h" +#include "dw3000_coex.h" +#include "dw3000_nfcc_coex_core.h" +#include "dw3000_txpower_adjustment.h" +#include "dw3000_power_stats.h" + +/* Table of supported chip version and associated chip operations */ +const struct dw3000_chip_version dw3000_chip_versions[] = { + { .id = DW3000_C0_DEV_ID, + .ver = DW3000_C0_VERSION, + .ops = &dw3000_chip_c0_ops, + .name = "C0" }, + { .id = DW3000_C0_PDOA_DEV_ID, + .ver = DW3000_C0_VERSION, + .ops = &dw3000_chip_c0_ops, + .name = "C0" }, + { .id = DW3000_D0_DEV_ID, + .ver = DW3000_D0_VERSION, + .ops = &dw3000_chip_d0_ops, + .name = "D0" }, + { .id = DW3000_D0_PDOA_DEV_ID, + .ver = DW3000_D0_VERSION, + .ops = &dw3000_chip_d0_ops, + .name = "D0" }, + { .id = DW3000_E0_PDOA_DEV_ID, + .ver = DW3000_E0_VERSION, + .ops = &dw3000_chip_e0_ops, + .name = "E0" }, +}; + +/* DW3000 hard reset delay (us) */ +#define DW3000_HARD_RESET_DELAY_US 10000 + +/* DW3000 soft reset delay (us) */ +#define DW3000_SOFT_RESET_DELAY_US 1000 + +/* DW3000 pgf calibration delay (us) */ +#define DW3000_PGFCAL_DELAY_US 1000 + +/* The pin operates as the EXTTXE output (C0, output TX state) */ +#define DW3000_GPIO_PIN0_EXTTXE \ + (((u32)0x2) << DW3000_GPIO_MODE_MSGP0_MODE_BIT_OFFSET) +/* The pin operates as the EXTRXE output (C0, output RX state) */ +#define DW3000_GPIO_PIN1_EXTRXE \ + (((u32)0x2) << DW3000_GPIO_MODE_MSGP1_MODE_BIT_OFFSET) + +/* The pin operates as the RXLED output (C0 & D0) */ +#define DW3000_GPIO_PIN2_RXLED \ + (((u32)0x1) << DW3000_GPIO_MODE_MSGP2_MODE_BIT_OFFSET) +/* The pin operates as the TXLED output (C0 & D0) */ +#define DW3000_GPIO_PIN3_TXLED \ + (((u32)0x1) << DW3000_GPIO_MODE_MSGP3_MODE_BIT_OFFSET) + +/* The pin operates as the EXTTXE output (D0, output TX state) */ +#define DW3000_GPIO_PIN4_EXTTXE \ + (((u32)0x2) << DW3000_GPIO_MODE_MSGP4_MODE_BIT_OFFSET) +/* The pin operates as the EXTRXE output (D0, output RX state) */ +#define DW3000_GPIO_PIN5_EXTRXE \ + (((u32)0x2) << DW3000_GPIO_MODE_MSGP5_MODE_BIT_OFFSET) + +/* The pin operates to support external DA/PA (C0) */ +#define DW3000_GPIO_PIN4_EXTDA \ + (((u32)0x1) << DW3000_GPIO_MODE_MSGP4_MODE_BIT_OFFSET) +/* The pin operates to support external PA (C0) */ +#define DW3000_GPIO_PIN5_EXTTX \ + (((u32)0x1) << DW3000_GPIO_MODE_MSGP5_MODE_BIT_OFFSET) +/* The pin operates to support external LNA (C0) */ +#define DW3000_GPIO_PIN6_EXTRX \ + (((u32)0x1) << DW3000_GPIO_MODE_MSGP6_MODE_BIT_OFFSET) + +/* Defined constants for "mode" bit field parameter passed to dw3000_set_leds() + function. */ +#define DW3000_LEDS_DISABLE 0x00 +#define DW3000_LEDS_ENABLE 0x01 +#define DW3000_LEDS_INIT_BLINK 0x02 +/* Default blink time. Blink time is expressed in multiples of 14 ms. + The value defined here is ~225 ms. */ +#define DW3000_LEDS_BLINK_TIME_DEF 0x10 + +/* Defined constants for "lna_pa" bit field parameter passed to + dw3000_set_lna_pa_mode() function */ +#define DW3000_LNA_PA_DISABLE 0x00 +#define DW3000_LNA_ENABLE 0x01 +#define DW3000_PA_ENABLE 0x02 +#define DW3000_TXRX_ENABLE 0x04 + +/* Duration of the negative pulse of the SPI CS signal + to wake up the chip in microseconds */ +#define DW3000_SPI_CS_WAKEUP_DELAY_US 500 + +/* DW3000 double buffered receiver mode */ +#define DW3000_DBL_BUFF_OFF 0x0 +#define DW3000_DBL_BUFF_SWAP 0x2 +#define DW3000_DBL_BUFF_ACCESS_BUFFER_A 0x1 +#define DW3000_DBL_BUFF_ACCESS_BUFFER_B 0x3 + +#define DW3000_SFDTOC_DEF 129 /* default SFD timeout value */ + +/* DW3000 OTP operating parameter set selection */ +#define DW3000_OPSET_LONG (0x0 << 11) +#define DW3000_OPSET_SCP (0x1 << 11) +#define DW3000_OPSET_SHORT (0x2 << 11) + +#define DW3000_RX_FINFO_STD_RXFLEN_MASK \ + 0x0000007FUL /* Receive Frame Length (0 to 127) */ + +/* Receive frame information register */ +#define DW3000_RX_FINFO_RXFLEN(val) (((val) >> 0) & 0x7ff) +#define DW3000_RX_FINFO_RXNSPL(val) (((val) >> 11) & 0x3) +#define DW3000_RX_FINFO_RXPSR(val) (((val) >> 18) & 0x3) +#define DW3000_RX_FINFO_RXPACC(val) (((val) >> 20) & 0xfff) + +/* RX events mask relating to reception into RX buffer A & B, when double + buffer is used */ +#define DW3000_RDB_STATUS_CLEAR_BUFF0_EVENTS \ + ((u8)0xf << DW3000_RDB_STATUS_RXFCG0_BIT_OFFSET) +#define DW3000_RDB_STATUS_CLEAR_BUFF1_EVENTS \ + ((u8)0xf << DW3000_RDB_STATUS_RXFCG1_BIT_OFFSET) + +#define DW3000_RDB_STATUS_RXOK \ + (DW3000_RDB_STATUS_RXFR0_BIT_MASK | \ + DW3000_RDB_STATUS_RXFCG0_BIT_MASK | \ + DW3000_RDB_STATUS_RXFR1_BIT_MASK | DW3000_RDB_STATUS_RXFCG1_BIT_MASK) + +#define DW3000_SYS_STATUS_TX (DW3000_SYS_ENABLE_LO_TXFRS_ENABLE_BIT_MASK) +#define DW3000_SYS_STATUS_RX \ + (DW3000_SYS_ENABLE_LO_RXPHE_ENABLE_BIT_MASK | \ + DW3000_SYS_ENABLE_LO_RXFCG_ENABLE_BIT_MASK | \ + DW3000_SYS_ENABLE_LO_RXFCE_ENABLE_BIT_MASK | \ + DW3000_SYS_ENABLE_LO_RXFSL_ENABLE_BIT_MASK | \ + DW3000_SYS_ENABLE_LO_RXFTO_ENABLE_BIT_MASK | \ + DW3000_SYS_ENABLE_LO_RXPTO_ENABLE_BIT_MASK | \ + DW3000_SYS_ENABLE_LO_RXSTO_ENABLE_BIT_MASK | \ + DW3000_SYS_ENABLE_LO_ARFE_ENABLE_BIT_MASK) +#define DW3000_SYS_STATUS_TRX (DW3000_SYS_STATUS_TX | DW3000_SYS_STATUS_RX) + +#define DW3000_RX_BUFFER_MAX_LEN (1023) +#define DW3000_TX_BUFFER_MAX_LEN (1024) +#define DW3000_REG_DIRECT_OFFSET_MAX_LEN (127) + +#define DW3000_CONFIG \ + 0x0001 /* download the AON array into the HIF + (configuration download) */ + +/* Enable/disable TX fine grain power sequencing */ +#define DW3000_PMSC_TXFINESEQ_ENABLE 0x4d28874 +#define DW3000_PMSC_TXFINESEQ_DISABLE 0x0d20874 +/* TXRX switch control register's configurations */ +#define DW3000_TXRXSWITCH_TX 0x01011100 +#define DW3000_TXRXSWITCH_AUTO 0x1C000000 + +#define DW3000_RF_TXCTRL_CH5 0x1C081134UL +#define DW3000_RF_TXCTRL_CH9 0x1C020034UL +#define DW3000_RF_TXCTRL_LO_B2 0x0E +#define DW3000_RF_PLL_CFG_CH5 0x1F3C +#define DW3000_RF_PLL_CFG_CH9 0x0F3C +#define DW3000_RF_PLL_CFG_LD 0x81 +#define DW3000_LDO_RLOAD_VAL_B1 0x14 + +/* PD threshold for no data STS mode */ +#define DW3000_PD_THRESH_NO_DATA 0xAF5F35CC +#define DW3000_PD_THRESH_DEFAULT 0xAF5F584C + +#define DW3000_NUM_DW_DEV (1) + +#define DW3000_SPI_FAC (0 << 6 | 1 << 0) +#define DW3000_SPI_FARW (0 << 6 | 0 << 0) +#define DW3000_SPI_EAMRW (1 << 6) + +/* Power management control's SYSCLK field */ +#define DW3000_FORCE_SYSCLK_FOSCDIV4 (1) +#define DW3000_FORCE_SYSCLK_PLL (2) +#define DW3000_FORCE_SYSCLK_FOSC (3) +/* RX and TX CLK field */ +#define DW3000_FORCE_CLK_PLL (2) + +/* Defines for dw3000_force_clocks() function */ +#define DW3000_FORCE_CLK_SYS_TX (1) +#define DW3000_FORCE_CLK_AUTO (5) + +#define DW3000_STD_FRAME_LEN (127) +#define DW3000_EXT_FRAME_LEN (1023) + +/* CIA lower bound threshold values for 64 MHz PRF */ +#define DW3000_CIA_MANUALLOWERBOUND_TH_64 (0x10) + +/** + * DW3000_STSQUAL_THRESH_64() - get STS quality thereshold value for 64 MHz PRF + * @x: STS length in unit of 8-symbols block + * + * When using 64 MHz PRF the stsCpQual should be > 60 % of STS length + */ +#define DW3000_STSQUAL_THRESH_64(x) (((x)*8) * 6 / 10) +/* Factor of sqrt(2) for calculation */ +#define DW3000_SQRT2_FACTOR 181 +#define DW3000_SQRT2_SHIFT_VAL 7 +#define DW3000_STS_MNTH_SHIFT 11 +#define DW3000_STS_MNTH_ROUND_SHIFT 1024 +/* The supported STS length options */ +#define DW3000_STS_LEN_SUPPORTED 9 + +static const u16 dw3000_sts_length_factors[DW3000_STS_LEN_SUPPORTED] = { + 0, 0, 1024, 1448, 2048, 2896, 4096, 5793, 8192 +}; + +#define DW3000_STS_ACC_CP_QUAL_SIGNTST 0x0800 /* sign test */ +#define DW3000_STS_ACC_CP_QUAL_SIGNEXT \ + 0xF000 /* 12 bit to 16 bit sign extension */ + +/** + * DW3000_GET_STS_LEN_REG_VALUE() - Convert STS length enum into register value + * @x: value from enum dw3000_sts_lengths + * + * Return: the value to set in CP_CFG0_ID for STS length. + */ +#define DW3000_GET_STS_LEN_REG_VALUE(x) ((u16)((1 << (x)) - 1)) + +/* Delay in symbol used for auto-ack. + * The IEEE 802.15.4 standard specifies a 12 symbol +/- 0.5 symbols + * turnaroud time for ACK transmission. */ +#define DW3000_NUMBER_OF_SYMBOL_DELAY_AUTO_ACK (12) + +/* The PLL calibration should take less than 400us. + * Typically it is < 100us + * (however on some corners with ch9 it can take ~900us) + */ +#define DW3000_MAX_RETRIES_FOR_PLL (50) + +/* The empirical delay to lock the PLL after its activation */ +#define DW3000_PLL_LOCK_DELAY_US (10) + +/* SYS_STATE_LO register errors */ +/* TSE is in TX but TX is in IDLE in SYS_STATE_LO register */ +#define DW3000_SYS_STATE_TXERR 0xD0000 +/* TSE is in IDLE (IDLE_PLL) */ +#define DW3000_SYS_STATE_IDLE 0x3 + +/** + * enum ciadiag_dbl_options - Enable CIA diagnostic's data log level options. + * @DW3000_CIA_DIAG_LOG_DBL_OFF: + * No CIA diagnostic option + * @DW3000_CIA_DIAG_LOG_DBL_MIN: + * CIA to copy to swinging set a minimal set of diagnostic registers + * in Double Buffer mode. + * @DW3000_CIA_DIAG_LOG_DBL_MID: + * CIA to copy to swinging set a medium set of diagnostic registers + * in Double Buffer mode. + * @DW3000_CIA_DIAG_LOG_DBL_MAX: + * CIA to copy to swinging set a maximum set of diagnostic registers + * in Double Buffer mode. + */ +enum ciadiag_dbl_options { + DW3000_CIA_DIAG_LOG_DBL_OFF = 0, + DW3000_CIA_DIAG_LOG_DBL_MIN = BIT(0), + DW3000_CIA_DIAG_LOG_DBL_MID = BIT(1), + DW3000_CIA_DIAG_LOG_DBL_MAX = BIT(2) +}; + +/* Disable CIA diagnostic. CIACONFIG's bit-4 in RX_ANTENNA_DELAY + 1 */ +#define DW3000_CIA_CONFIG_DIAG_OFF (0x1 << 4) + +/* LDO VOUT value */ +#define DW3000_RF_LDO_VOUT 0x0D7FFFFFUL +/* RF SWITCH RX RF2 */ +#define DW3000_RF_SWITCH_RX_RF2 0x2131 + +/* PLL common value */ +#define DW3000_RF_PLL_COMMON 0xE104 + +struct dw3000_ciadiag_reg_info { + u32 diag1; + u32 diag12; +}; + +/* CIA diagnostic register selection according DW3000's configuration */ +static const struct dw3000_ciadiag_reg_info _ciadiag_reg_info[] = { + /* Without STS */ + { + .diag1 = DW3000_IP_DIAG1_ID, + .diag12 = DW3000_IP_DIAG12_ID, + }, + /* With STS */ + { + .diag1 = DW3000_CP0_DIAG1_ID, + .diag12 = DW3000_CP0_DIAG12_ID, + }, + /* PDOA Mode 3 */ + { + .diag1 = DW3000_CP1_DIAG1_ID, + .diag12 = DW3000_CP1_DIAG12_ID, + }, +}; + +static int dw3000_wakeup_done_to_tx(struct dw3000 *dw); +static int dw3000_wakeup_done_to_rx(struct dw3000 *dw); +/* sysfs variables handling */ +static ssize_t dw3000_sysfs_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); +static ssize_t dw3000_sysfs_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, + size_t length); +const struct kobj_attribute dw3000_attribute = + __ATTR(power_stats, 0664, dw3000_sysfs_show, dw3000_sysfs_store); + +/* Interrupt working options */ +enum int_options { + DW3000_DISABLE_INT = 0, + DW3000_ENABLE_INT, + DW3000_ENABLE_INT_ONLY +}; + +/* Indexes are DWT_PLEN_NNNN values - 1 */ +const struct dw3000_plen_info _plen_info[] = { + { + .symb = 64, + .pac_symb = 8, + .dw_reg = DW3000_PLEN_64, + .dw_pac_reg = DW3000_PAC8, + }, + { + .symb = 1024, + .pac_symb = 32, + .dw_reg = DW3000_PLEN_1024, + .dw_pac_reg = DW3000_PAC32, + }, + { + .symb = 4096, + .pac_symb = 64, + .dw_reg = DW3000_PLEN_4096, + .dw_pac_reg = DW3000_PAC32, + }, + { + .symb = 32, + .pac_symb = 8, + .dw_reg = DW3000_PLEN_32, + .dw_pac_reg = DW3000_PAC8, + }, + { + .symb = 128, + .pac_symb = 8, + .dw_reg = DW3000_PLEN_128, + .dw_pac_reg = DW3000_PAC8, + }, + { + .symb = 1536, + .pac_symb = 64, + .dw_reg = DW3000_PLEN_1536, + .dw_pac_reg = DW3000_PAC32, + }, + { + .symb = 72, + .pac_symb = 8, + .dw_reg = DW3000_PLEN_72, + .dw_pac_reg = DW3000_PAC8, + }, + { + /* Invalid */ + .symb = 0, + .pac_symb = 0, + .dw_reg = 0, + .dw_pac_reg = 0, + }, + { + .symb = 256, + .pac_symb = 16, + .dw_reg = DW3000_PLEN_256, + .dw_pac_reg = DW3000_PAC16, + }, + { + .symb = 2048, + .pac_symb = 64, + .dw_reg = DW3000_PLEN_2048, + .dw_pac_reg = DW3000_PAC32, + }, + { + /* Invalid */ + .symb = 0, + .pac_symb = 0, + .dw_reg = 0, + .dw_pac_reg = 0, + }, + { + /* Invalid */ + .symb = 0, + .pac_symb = 0, + .dw_reg = 0, + .dw_pac_reg = 0, + }, + { + .symb = 512, + .pac_symb = 16, + .dw_reg = DW3000_PLEN_512, + .dw_pac_reg = DW3000_PAC16, + }, +}; + +/* Chip per symbol for 850kbps (512) and 6.8Mbps (64) */ +const int _chip_per_symbol_info[2] = { 512, 64 }; + +const struct dw3000_prf_info _prf_info[] = { + { + /* Invalid PRF */ + 0, + }, + { + /* 16 MHz */ + .chip_per_symb = 496, + }, + { + /* 64 MHz */ + .chip_per_symb = 508, + }, +}; + +static int dw3000_transfers_reset(struct dw3000 *dw); +static int dw3000_change_speed(struct dw3000 *dw, u32 new_speed); +static int dw3000_wakeup(struct dw3000 *dw); +static inline bool _dw3000_sts_is_enabled(struct dw3000 *dw); + +/** + * dw3000_get_dtu_time() - compute current DTU time + * @dw: the DW device + * + * This function compute the current DTU time, based on ktime, + * at 15.6MHz rate. + * + * Return: the current DTU time + */ +u32 dw3000_get_dtu_time(struct dw3000 *dw) +{ + s64 bt_ns = ktime_get_boottime_ns(); + return dw3000_ktime_to_dtu(dw, bt_ns); +} + +/** + * dw3000_resync_dtu_sys_time() - resync DTU time and SYS_TIME + * @dw: the DW device + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_resync_dtu_sys_time(struct dw3000 *dw) +{ + int rc; + /* Read SYS_TIME */ + rc = dw3000_read_sys_time(dw, &dw->sys_time_sync); + if (rc) + dw->sys_time_sync = 1000; /* TODO: check value */ + /* Save synchronisation time DTU */ + dw->dtu_sync = dw3000_get_dtu_time(dw); + trace_dw3000_resync_dtu_sys_time(dw, dw->sys_time_sync, dw->dtu_sync); + /* Invalidate RCTU synchronisation */ + dw3000_resync_rctu_conv_state(dw); + return rc; +} + +/** + * dw3000_may_resync() - check if a resync is needed, if yes, do it + * @dw: the DW device + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_may_resync(struct dw3000 *dw) +{ + int rc = 0; + u32 now_dtu; + + now_dtu = dw3000_get_dtu_time(dw); + if (now_dtu - dw->dtu_sync > DW3000_DTU_FREQ) + rc = dw3000_resync_dtu_sys_time(dw); + return rc; +} + +/** + * dw3000_alloc_xfer() - Allocate a new spi_message including spi_tranfer. + * @trcount: the number of transfer to allocate with the new spi_message + * @len: the length of TX buffer to allocate for the first transfer + * + * Return: the spi_message struct or NULL if error. + */ +static inline struct spi_message *dw3000_alloc_xfer(unsigned int trcount, + unsigned int len) +{ + struct spi_message *msg = spi_message_alloc(trcount, GFP_KERNEL); + +#if ((KERNEL_VERSION(4, 12, 0) > LINUX_VERSION_CODE) && \ + (KERNEL_VERSION(4, 6, 0) <= LINUX_VERSION_CODE)) + /* Initialize spi_message resources list to avoid null pointer + * dereference on some SPI controllers to fix a bug in spi_message_alloc() + * appeared in 4.6.0 and fixed in 4.12.0. */ + if (msg) + INIT_LIST_HEAD(&msg->resources); +#endif + + if (msg && len) { + struct spi_transfer *transfer = list_first_entry( + &msg->transfers, struct spi_transfer, transfer_list); + transfer->tx_buf = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!transfer->tx_buf) { + /* failure to allocate requested buffer for header */ + spi_message_free(msg); + msg = NULL; + } else { + transfer->len = len; + } + } + return msg; +} + +/** + * dw3000_free_xfer() - Free an spi_message allocated by @dw3000_alloc_xfer + * @msg: the SPI message to free + * @len: the same length of TX buffer to automatically free it + */ +static inline void dw3000_free_xfer(struct spi_message *msg, unsigned int len) +{ + if (!msg) + return; + if (len) { + struct spi_transfer *tr = list_first_entry( + &msg->transfers, struct spi_transfer, transfer_list); + if (tr->rx_buf && tr->rx_buf != tr->tx_buf) + kfree(tr->rx_buf); + kfree(tr->tx_buf); + } + spi_message_free(msg); +} + +/** + * dw3000_prepare_xfer() - Initialise an spi_message allocated by @dw3000_alloc_xfer + * @msg: the SPI message to initialise + * @reg_fileid: the register fileID to read/write + * @index: the index where to read/write + * @length: the length of provided buffer and SPI data transfer + * @buffer: the address where to read/write data + * @mode: operation mode, ie. RW, WR, AND_OR8, etc. + * + * Return: zero on success, else a negative error code. + */ +static inline int dw3000_prepare_xfer(struct spi_message *msg, u32 reg_fileid, + u16 index, u16 length, void *buffer, + enum spi_modes mode) +{ + struct spi_transfer *tr = list_first_entry( + &msg->transfers, struct spi_transfer, transfer_list); + u8 *header_buf = (u8 *)tr->tx_buf; + u16 header_len; + + /* Extract register file and sub-address (+ offset) */ + u16 reg_file = 0x1F & ((reg_fileid + index) >> 16); + u16 reg_offset = 0x7F & (reg_fileid + index); + + /* Fast command not supported by this function */ + if (unlikely(length == 0 && mode == DW3000_SPI_WR_BIT)) + return -EINVAL; + + /* Set header buffer */ + if (reg_offset || (mode & DW3000_SPI_AND_OR_MSK)) { + /* 2-byte header */ + u16 param = (reg_file << 9) | (reg_offset << 2) | mode; + + header_buf[0] = (u8)(param >> 8) | DW3000_SPI_EAMRW; + header_buf[1] = (u8)param; + header_len = 2; + } else { + /* 1-byte header */ + u8 param = reg_file << 1 | mode >> 8; + + header_buf[0] = (u8)param | DW3000_SPI_FARW; + header_len = 1; + } + /* Adjust header len in the SPI message */ + if (unlikely(header_len > tr->len)) + return -EINVAL; + tr->len = header_len; + + /* Set the data buffer in second transfer */ + if (likely(!buffer)) { + /* Single spi_tranfer messages are used for full-fuplex register + read/write. Just update the transfer length. + The rx_buf is already set dw3000_alloc_prepare_xfer(). */ + tr->len += length; + } else { + struct spi_transfer *tr2; + + tr2 = list_next_entry(tr, transfer_list); + if (mode == DW3000_SPI_RD_BIT) { + /* RX transaction */ + tr2->rx_buf = buffer; + } else { + /* TX transaction */ + tr2->tx_buf = buffer; + } + /* Add data to the SPI message */ + tr2->len = length; + } + return 0; +} + +/** + * dw3000_alloc_prepare_xfer() - Allocate and prepare an spi_message + * @dw: the DW device on which the SPI transfer will occurs + * @reg_fileid: the register fileID to read/write + * @index: the index where to read/write + * @length: expected length of data to read/write + * @mode: operation mode, ie. RW, WR, AND_OR8, etc. + * + * The prepared spi_message will have only one spi_transfer with enough space + * for read/write 16 bytes at least in full-duplex mode (including header). + * + * Return: the spi_message struct or NULL if error. + */ +static struct spi_message *dw3000_alloc_prepare_xfer(struct dw3000 *dw, + u32 reg_fileid, u16 index, + u16 length, + enum spi_modes mode) +{ + struct spi_message *msg; + int len = length < 16 ? 16 : length; + int rc; + + /* Allocate a new SPI message with one transfer. */ + msg = dw3000_alloc_xfer(1, len); + if (!msg) + goto err_alloc; + /* Prepare this message for given register access */ + rc = dw3000_prepare_xfer(msg, reg_fileid, index, length, NULL, mode); + if (rc) + goto err_prepare; + /* Need to have a separated TX/RX buffer because initialised TX + buffer will be clobbered during first exchange if RX buffer + is the same. rx_buf = tx_buf is only right if message is used + once, or is re-initialised before each transfer like in + dw3000_reg_read_fast(). */ + if (mode == DW3000_SPI_RD_BIT) { + struct spi_transfer *tr = list_first_entry( + &msg->transfers, struct spi_transfer, transfer_list); + tr->rx_buf = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!tr->rx_buf) + goto err_rxalloc; + } + return msg; + +err_rxalloc: +err_prepare: + dw3000_free_xfer(msg, len); +err_alloc: + dev_err(dw->dev, + "Failure to allocate message for reg 0x%x (index %u, len %d, mode %d)\n", + reg_fileid, index, length, mode); + return NULL; +} + +/** + * dw3000_alloc_prepare_fastcmd() - Allocate and prepare a fastcmd spi_message + * + * Return: the spi_message struct or NULL if error. + */ +static struct spi_message *dw3000_alloc_prepare_fastcmd(void) +{ + /* Allocate a new SPI message with only one transfer. */ + return dw3000_alloc_xfer(1, 1); +} + +/** + * dw3000_free_fastcmd() - Free a fastcmd spi_message + * @msg: the SPI message to free + */ +static void dw3000_free_fastcmd(struct spi_message *msg) +{ + /* Free SPI message and tx_buf of included transfer */ + dw3000_free_xfer(msg, 1); +} + +static inline int dw3000_spi_sync(struct dw3000 *dw, struct spi_message *msg); + +/** + * dw3000_spi_queue_start() - Prepare SPI messages queue + * @dw: the DW device on which the SPI transfer will occurs + * + * Reset and initialise SPI messages queue to record next transfers until + * dw3000_spi_queue_flush() is called. + */ +void dw3000_spi_queue_start(struct dw3000 *dw) +{ +#ifdef CONFIG_DW3000_SPI_OPTIMIZATION + struct spi_message *msg = dw->msg_queue; + + spi_message_init(msg); + dw->msg_queue_xfer = (struct spi_transfer *)(msg + 1); + dw->msg_queue_xfer_count = 0; + dw->msg_queue_buf_pos = dw->msg_queue_buf; +#endif +} + +/** + * dw3000_spi_queue_msg() - Add one SPI message to messages queue + * @dw: the DW device on which the SPI transfer will occurs + * @msg: the SPI message to add to SPI message queue + * + * All transfers of the provided SPI message are added to the local SPI messages + * queue. TX only transfer can be queued, so message queue must be flushed + * before any RX transfer is required. + * + * Return: 0 on success, else a negative error code. + */ +static inline int dw3000_spi_queue_msg(struct dw3000 *dw, + struct spi_message *msg) +{ + struct spi_transfer *dxfer = dw->msg_queue_xfer; + struct spi_transfer *xfer; + + if (dxfer == NULL) + return -ENOBUFS; + list_for_each_entry (xfer, &msg->transfers, transfer_list) { + /* Don't support queuing RX transfer */ + if (xfer->rx_buf) + return -EINVAL; + if ((dw->msg_queue_buf_pos + xfer->len) >= + (dw->msg_queue_buf + DW3000_QUEUED_SPI_BUFFER_SZ)) + return -EMSGSIZE; + /* Copy this message transfer to next empty transfer in queue */ + memcpy(dw->msg_queue_buf_pos, xfer->tx_buf, xfer->len); + memset(dxfer, 0, sizeof *dxfer); + dxfer->len = xfer->len; + dxfer->tx_buf = dw->msg_queue_buf_pos; + if (list_is_last(&xfer->transfer_list, &msg->transfers)) { + dxfer->cs_change = true; +#if (KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE) + dxfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS; + dxfer->cs_change_delay.value = 0; +#endif + } + /* add transfer to queue */ + spi_message_add_tail(dxfer, dw->msg_queue); + dw->msg_queue_xfer_count++; + /* Prepare next */ + dw->msg_queue_buf_pos += dxfer->len; + if (dw->msg_queue_xfer_count >= DW3000_MAX_QUEUED_SPI_XFER) { + dxfer = NULL; + break; + } + dxfer++; + } + /* Save new dest xfer for next call */ + dw->msg_queue_xfer = dxfer; + if (!dxfer && !list_is_last(&xfer->transfer_list, &msg->transfers)) + return -ENOBUFS; + return 0; +} + +/** + * dw3000_spi_queue_reset() - Reset message queue + * @dw: the DW device on which the SPI transfer will occurs + * @rc: the return value given by caller to return after reset + * + * Reset the SPI messages queue and return the provided error code. + * This must be called when an error happen while SPI message are + * queued to ensure queuing mode is disabled. + * + * This revert dw3000_spi_sync() to immediate transaction mode. + * + * Return: the rc given value + */ +int dw3000_spi_queue_reset(struct dw3000 *dw, int rc) +{ +#ifdef CONFIG_DW3000_SPI_OPTIMIZATION + dw->msg_queue_xfer = NULL; + dw->msg_queue_xfer_count = 0; +#endif + return rc; +} + +/** + * dw3000_spi_queue_flush() - Flush messages queue to dw3000_spi_sync() + * @dw: the DW device on which the SPI transfer will occurs + * + * Flush all queued transfers in SPI message queue by calling the + * dw3000_spi_sync() again with the SPI messages queue. + * + * This revert dw3000_spi_sync() to immediate transaction mode. + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_spi_queue_flush(struct dw3000 *dw) +{ +#ifdef CONFIG_DW3000_SPI_OPTIMIZATION + struct spi_message *msg = dw->msg_queue; + struct spi_transfer *xfer; + int rc; + + /* If nothing queued, nothing to do. Just reset to no-queuing mode. */ + if (!dw->msg_queue_xfer_count) + return dw3000_spi_queue_reset(dw, 0); + /* Ensure last xfer don't have cs_change flag */ + xfer = list_last_entry(&msg->transfers, struct spi_transfer, + transfer_list); + xfer->cs_change = false; + /* Ensure dw3000_spi_sync() stop queue messages */ + dw->msg_queue_xfer = NULL; + /* Do saved SPI transfers */ + rc = dw3000_spi_sync(dw, msg); + /* Cleanup */ + return dw3000_spi_queue_reset(dw, rc); +#else + return 0; +#endif +} + +/** + * dw3000_spi_sync() - Execute or queue an SPI synchronous transaction + * @dw: the DW device on which the SPI transfer will occurs + * @msg: the SPI message to transmit + * + * Call directly spi_sync() with the provided SPI message and display an error + * message if any error occurs. + * + * If SPI messages queue mode was activated previously by a call to + * dw3000_spi_queue_start(), don't call spi_sync() directly but store the + * given transfers into the message queue. + * + * Return: 0 on success, else a negative error code. + */ +static inline int dw3000_spi_sync(struct dw3000 *dw, struct spi_message *msg) +{ + int rc; + if (dw->msg_queue_xfer) + return dw3000_spi_queue_msg(dw, msg); + rc = spi_sync(dw->spi, msg); + if (rc) + dev_err(dw->dev, "could not transfer : %d\n", rc); + return rc; +} + +/** + * dw3000_spi_queue_active() - Check SPI queuing mode status + * @dw: the DW device on which the SPI transfer will occurs + * + * Return: true if SPI message queuing mode is active + */ +static inline bool dw3000_spi_queue_active(struct dw3000 *dw) +{ + return (dw->msg_queue_xfer || dw->msg_queue_xfer_count); +} + +/** + * dw3000_xfer() - Generic low-level slow transfer + * @dw: the DW device on which the SPI transfer will occurs + * @reg_fileid: the fileID to read/write + * @reg_offset: the offset where to read/write + * @length: the length of provided buffer and SPI data transfer + * @buffer: the address where to read/write data + * @mode: operation mode, ie. RW, WR, AND_OR8, etc. + * + * This function initialise a new SPI message and two SPI transfer on the stack. + * First transfer is use for TX header to device. Second transfer is used for RX + * or TX data. Then spi_sync() is called. + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_xfer(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, u16 length, + void *buffer, enum spi_modes mode) +{ + struct { + struct spi_message msg; + struct spi_transfer header; + struct spi_transfer data; + u8 header_buf[2]; + } xfer = {}; + + /* Init transfers first because spi_message_init_with_transfer don't! */ + xfer.header.tx_buf = xfer.header_buf; + xfer.header.len = sizeof(xfer.header_buf); + + /* Then construct SPI message */ + spi_message_init_with_transfers(&xfer.msg, &xfer.header, 2); + + /* Prepare header & data transfers */ + dw3000_prepare_xfer(&xfer.msg, reg_fileid, reg_offset, length, buffer, + mode); + /* Now execute this spi message synchronously */ + return dw3000_spi_sync(dw, &xfer.msg); +} + +/** + * dw3000_write_fastcmd() - Send a fast command to the device + * @dw: the DW device on which the SPI transfer will occurs + * @cmd: the fastcommand to send to the device + * + * This function use a prebuilt SPI message with a single transfer which is + * modified to send the required command. + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_write_fastcmd(struct dw3000 *dw, u8 cmd) +{ + /* Use a prebuilt SPI message to be as fast as possible. */ + struct spi_message *msg = dw->msg_fast_command; + struct spi_transfer *tr = list_first_entry( + &msg->transfers, struct spi_transfer, transfer_list); + u8 *header_buf = (u8 *)tr->tx_buf; + + /* Set the command to send directly in first transfer TX buffer */ + *header_buf = + (u8)((DW3000_SPI_WR_BIT >> 8) | (cmd << 1) | DW3000_SPI_FAC); + /* Now execute this spi message synchronously */ + return dw3000_spi_sync(dw, msg); +} + +/** + * dw3000_reg_read_fast() - Generic full-duplex register read + * @dw: the DW device on which the SPI transfer will occurs + * @reg_fileid: the register fileID to read + * @reg_offset: the register offset to read + * @length: the length of provided buffer and SPI data transfer + * @buffer: the address where to store the read data + * + * This function read the specified register using a dedicated full-duplex + * message. It configure it first, call spi_sync() and then copy read data + * from the spi_tranfer RX buffer to the provided buffer. + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_reg_read_fast(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u16 length, void *buffer) +{ + struct spi_message *msg = dw->msg_readwrite_fdx; + struct spi_transfer *tr = list_first_entry( + &msg->transfers, struct spi_transfer, transfer_list); + int hlen; + int rc; + + mutex_lock(&dw->msg_mutex); + /* Update header and length */ + dw3000_prepare_xfer(msg, reg_fileid, reg_offset, length, NULL, + DW3000_SPI_RD_BIT); + /* Retrieve header length */ + hlen = tr->len - length; + /* Ensure all data bits are 0 */ + memset((void *)tr->tx_buf + hlen, 0, length); + /* Execute SPI transfer */ + rc = dw3000_spi_sync(dw, msg); + if (!rc) + /* Get back the data that are after the header in RX buffer */ + memcpy(buffer, tr->rx_buf + hlen, length); + mutex_unlock(&dw->msg_mutex); + return rc; +} + +/** + * dw3000_reg_read32() - 32 bits register read + * @dw: the DW device on which the SPI transfer will occurs + * @reg_fileid: the register fileID to read + * @reg_offset: the register offset to read + * @val: the u32 value's pointer + * + * Just a little wrapper to the dw3000_reg_read_fast() function. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_reg_read32(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u32 *val) +{ + __le32 buffer; + int rc; + /* Read 4 bytes (32-bits) register into buffer */ + rc = dw3000_reg_read_fast(dw, reg_fileid, reg_offset, sizeof(buffer), + &buffer); + if (likely(!rc)) + *val = le32_to_cpu(buffer); + return rc; +} + +/** + * dw3000_reg_read16() - 16 bits register read + * @dw: the DW device on which the SPI transfer will occurs + * @reg_fileid: the register fileID to read + * @reg_offset: the register offset to read + * @val: the u16 value's pointer + * + * Just a little wrapper to the dw3000_reg_read_fast() function. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_reg_read16(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u16 *val) +{ + __le16 buffer; + int rc; + /* Read 2 bytes (16-bits) register into buffer */ + rc = dw3000_reg_read_fast(dw, reg_fileid, reg_offset, sizeof(buffer), + &buffer); + if (likely(!rc)) + *val = le16_to_cpu(buffer); + return rc; +} + +/** + * dw3000_reg_read8() - 8 bits register read + * @dw: the DW device on which the SPI transfer will occurs + * @reg_fileid: the register fileID to read + * @reg_offset: the register offset to read + * @val: the u8 value's pointer + * + * Just a little wrapper to the dw3000_reg_read_fast() function. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_reg_read8(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, u8 *val) +{ + /* Read 1 byte (8-bits) register into data */ + return dw3000_reg_read_fast(dw, reg_fileid, reg_offset, sizeof(*val), + val); +} + +/** + * dw3000_reg_write_fast() - Generic single-transfer register write + * @dw: the DW device on which the SPI transfer will occurs + * @reg_fileid: the register fileID to read + * @reg_offset: the register offset to read + * @length: the length of provided buffer and SPI data transfer + * @buffer: the address where to store the read data + * @mode: the write operation mode to use (direct, bitmask and/or) + * + * This function write the specified register using a dedicated single-transfer + * message. It configure it first, copy data from provided buffer in it, then + * call spi_sync(). + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_reg_write_fast(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u16 length, const void *buffer, enum spi_modes mode) +{ + struct spi_message *msg = dw->msg_readwrite_fdx; + struct spi_transfer *tr = list_first_entry( + &msg->transfers, struct spi_transfer, transfer_list); + void *rx_buf; + int hlen; + int rc; + + mutex_lock(&dw->msg_mutex); + /* Update header and length */ + dw3000_prepare_xfer(msg, reg_fileid, reg_offset, length, NULL, mode); + /* Data are after the header in TX buffer */ + hlen = tr->len - length; + memcpy((void *)tr->tx_buf + hlen, buffer, length); + /* We don't want to receive data, so remove unused RX buffer to avoid + unrequired fifo read in controller. */ + rx_buf = tr->rx_buf; /* save RX buffer */ + tr->rx_buf = NULL; + /* Execute SPI transfer */ + rc = dw3000_spi_sync(dw, msg); + /* Restore RX buffer */ + tr->rx_buf = rx_buf; + mutex_unlock(&dw->msg_mutex); + return rc; +} + +/** + * _dw3000_reg_write - Generic register write + * @dw: the DW device on which the SPI transfer will occurs + * @reg_fileid: the register fileID to write + * @reg_offset: the register offset to write + * @length: the length of provided buffer and SPI data transfer + * @buffer: the address of the data to write + * + * Just a little wrapper to the dw3000_reg_write_fast() function. + * + * Return: zero on success, else a negative error code. + */ +static inline int _dw3000_reg_write(struct dw3000 *dw, u32 reg_fileid, + u16 reg_offset, u16 length, + const void *buffer) +{ + return dw3000_reg_write_fast(dw, reg_fileid, reg_offset, length, buffer, + DW3000_SPI_WR_BIT); +} + +/** + * dw3000_reg_write32() - 32 bits register write + * @dw: the DW device on which the SPI transfer will occurs + * @reg_fileid: the register fileID to write + * @reg_offset: the register offset to write + * @data: value to write to the register + * + * Just a little wrapper to the dw3000_reg_write() function. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_reg_write32(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u32 data) +{ + __le32 buffer = cpu_to_le32(data); + + return _dw3000_reg_write(dw, reg_fileid, reg_offset, sizeof(buffer), + &buffer); +} + +/** + * dw3000_reg_write16() - 16 bits register write + * @dw: the DW device on which the SPI transfer will occurs + * @reg_fileid: the register fileID to write + * @reg_offset: the register offset to write + * @data: value to write to the register + * + * Just a little wrapper to the dw3000_reg_write() function. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_reg_write16(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u16 data) +{ + __le16 buffer = cpu_to_le16(data); + + return _dw3000_reg_write(dw, reg_fileid, reg_offset, sizeof(buffer), + &buffer); +} + +/** + * dw3000_reg_write8() - 8 bits register write + * @dw: the DW device on which the SPI transfer will occurs + * @reg_fileid: the register fileID to write + * @reg_offset: the register offset to write + * @data: value to write to the register + * + * Just a little wrapper to the dw3000_reg_write() function. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_reg_write8(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u8 data) +{ + return _dw3000_reg_write(dw, reg_fileid, reg_offset, sizeof(data), + &data); +} + +/** + * dw3000_reg_modify32() - 32 bits register modify + * @dw: the DW device on which the SPI transfer will occurs + * @reg_fileid: the register fileID to modify + * @reg_offset: the register offset to modify + * @_and: bitmask to clear + * @_or: bitmask to set + * + * Just a little wrapper to the dw3000_reg_write_fast() function. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_reg_modify32(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u32 _and, u32 _or) +{ + __le32 buffer[2]; + + buffer[0] = cpu_to_le32(_and); + buffer[1] = cpu_to_le32(_or); + return dw3000_reg_write_fast(dw, reg_fileid, reg_offset, sizeof(buffer), + buffer, DW3000_SPI_AND_OR_32); +} + +/** + * dw3000_reg_modify16() - 16 bits register modify + * @dw: the DW device on which the SPI transfer will occurs + * @reg_fileid: the register fileID to modify + * @reg_offset: the register offset to modify + * @_and: bitmask to clear + * @_or: bitmask to set + * + * Just a little wrapper to the dw3000_reg_write_fast() function. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_reg_modify16(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u16 _and, u16 _or) +{ + __le16 buffer[2]; + + buffer[0] = cpu_to_le16(_and); + buffer[1] = cpu_to_le16(_or); + return dw3000_reg_write_fast(dw, reg_fileid, reg_offset, sizeof(buffer), + buffer, DW3000_SPI_AND_OR_16); +} + +/** + * dw3000_reg_modify8() - 8 bits register modify + * @dw: the DW device on which the SPI transfer will occurs + * @reg_fileid: the register fileID to modify + * @reg_offset: the register offset to modify + * @_and: bitmask to clear + * @_or: bitmask to set + * + * Just a little wrapper to the dw3000_reg_write_fast() function. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_reg_modify8(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u8 _and, u8 _or) +{ + u8 buffer[2]; + + buffer[0] = _and; + buffer[1] = _or; + return dw3000_reg_write_fast(dw, reg_fileid, reg_offset, sizeof(buffer), + buffer, DW3000_SPI_AND_OR_8); +} + +/** + * dw3000_clear_sys_status() - Fast clearing of SYS_STATUS register + * @dw: the DW device on which the SPI transfer will occurs + * @clear_bits: the bitmask of bits to clear + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_clear_sys_status(struct dw3000 *dw, u32 clear_bits) +{ + /* Use a prebuilt SPI message to be as fast as possible. */ + struct spi_message *msg = dw->msg_write_sys_status; + struct spi_transfer *tr = list_first_entry( + &msg->transfers, struct spi_transfer, transfer_list); + const int hlen = tr->len - sizeof(clear_bits); + /* Prepared message have only header & length set, need to set data part */ + put_unaligned_le32(clear_bits, (void *)tr->tx_buf + hlen); + return dw3000_spi_sync(dw, msg); +} + +/** + * dw3000_read_sys_status() - Fast read of SYS_STATUS register (4 low bytes upon 6) + * @dw: the DW device on which the SPI transfer will occurs + * @status: address where to put read status + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_read_sys_status(struct dw3000 *dw, u32 *status) +{ + /* Use a prebuilt SPI message to be as fast as possible. */ + struct spi_message *msg = dw->msg_read_sys_status; + struct spi_transfer *tr = list_first_entry( + &msg->transfers, struct spi_transfer, transfer_list); + const int hlen = tr->len - sizeof(*status); + int rc = dw3000_spi_sync(dw, msg); + if (!rc) + *status = get_unaligned_le32(tr->rx_buf + hlen); + return rc; +} + +/** + * dw3000_clear_all_sys_status() - Fast clearing of SYS_STATUS register + * @dw: the DW device on which the SPI transfer will occurs + * @clear_bits: the bitmask of bits to clear + * + * Return: 0 on success, else a negative error code. + */ +static int dw3000_clear_all_sys_status(struct dw3000 *dw, u64 clear_bits) +{ + /* Use a prebuilt SPI message to be as fast as possible. */ + struct spi_message *msg = dw->msg_write_all_sys_status; + struct spi_transfer *tr = list_first_entry( + &msg->transfers, struct spi_transfer, transfer_list); + const int hlen = tr->len - sizeof(clear_bits); + /* Prepared message have only header & length set, need to set data part */ + put_unaligned_le64(clear_bits, (void *)tr->tx_buf + hlen); + return dw3000_spi_sync(dw, msg); +} + +/** + * dw3000_read_all_sys_status() - Fast read of SYS_STATUS register + * @dw: the DW device on which the SPI transfer will occurs + * @status: address where to put read status + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_read_all_sys_status(struct dw3000 *dw, u64 *status) +{ + /* Use a prebuilt SPI message to be as fast as possible. */ + struct spi_message *msg = dw->msg_read_all_sys_status; + struct spi_transfer *tr = list_first_entry( + &msg->transfers, struct spi_transfer, transfer_list); + const int hlen = tr->len - sizeof(*status); + int rc = dw3000_spi_sync(dw, msg); + if (!rc) + *status = get_unaligned_le64(tr->rx_buf + hlen); + return rc; +} + +/** + * dw3000_clear_dss_status() - Fast clearing of DSS_STAT register + * @dw: the DW device on which the SPI transfer will occurs + * @clear_bits: the bitmask of bits to clear + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_clear_dss_status(struct dw3000 *dw, u8 clear_bits) +{ + /* Use a prebuilt SPI message to be as fast as possible. */ + struct spi_message *msg = dw->msg_write_dss_status; + struct spi_transfer *tr = list_first_entry( + &msg->transfers, struct spi_transfer, transfer_list); + const int hlen = tr->len - sizeof(clear_bits); + int rc; + /* Prepared message have only header & length set, need to set data part */ + *((u8 *)tr->tx_buf + hlen) = clear_bits; + rc = spi_sync(dw->spi, msg); + if (rc) + dev_err(dw->dev, "could not transfer : %d\n", rc); + return rc; +} + +/** + * dw3000_read_dss_status() - Fast read of DSS_STAT register + * @dw: the DW device on which the SPI transfer will occurs + * @status: address where to put read status + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_read_dss_status(struct dw3000 *dw, u8 *status) +{ + /* Use a prebuilt SPI message to be as fast as possible. */ + struct spi_message *msg = dw->msg_read_dss_status; + struct spi_transfer *tr = list_last_entry( + &msg->transfers, struct spi_transfer, transfer_list); + const int hlen = tr->len - sizeof(*status); + int rc = spi_sync(dw->spi, msg); + + if (rc) + dev_err(dw->dev, "could not transfer : %d\n", rc); + else + *status = *((u8 *)tr->rx_buf + hlen); + return rc; +} + +/** + * dw3000_clear_spi_collision_status() - Fast clearing of SPI_COLLISION_STATUS register + * @dw: the DW device on which the SPI transfer will occurs + * @clear_bits: the bitmask of bits to clear + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_clear_spi_collision_status(struct dw3000 *dw, u8 clear_bits) +{ + /* Use a prebuilt SPI message to be as fast as possible. */ + struct spi_message *msg = dw->msg_write_spi_collision_status; + struct spi_transfer *tr = list_first_entry( + &msg->transfers, struct spi_transfer, transfer_list); + const int hlen = tr->len - sizeof(clear_bits); + int rc; + /* Prepared message have only header & length set, need to set data part */ + *((u8 *)tr->tx_buf + hlen) = clear_bits; + rc = spi_sync(dw->spi, msg); + if (rc) + dev_err(dw->dev, "could not transfer : %d\n", rc); + return rc; +} + +/** + * dw3000_change_tx_rf_port() - Enable TX on RF2 port. + * @dw: the DW device on which the SPI transfer will occurs + * @use_rf2: variable to trigger the SWITCH to RF2 + * + * Return: 0 on success, else a negative error code. + */ +static int dw3000_change_tx_rf_port(struct dw3000 *dw, bool use_rf2) +{ + int rc; + u32 val = DW3000_TXRXSWITCH_AUTO | + ((u32)use_rf2 + << DW3000_RF_SWITCH_CTRL_ANT_TXRX_TXPORT_BIT_OFFSET) | + ((u32)use_rf2 + << DW3000_RF_SWITCH_CTRL_ANT_TXRX_MODE_OVR_BIT_OFFSET); + rc = dw3000_reg_write32(dw, DW3000_RF_SWITCH_CTRL_ID, 0, val); + if (!rc) + dw->tx_rf2 = use_rf2; + return rc; +} + +/** + * dw3000_change_rx_rf_port() - Enable RX on RF2 port. + * @dw: the DW device on which the SPI transfer will occurs + * @use_rf2: variable to trigger the SWITCH to RF2 + * + * Return: 0 on success, else a negative error code. + */ +static int dw3000_change_rx_rf_port(struct dw3000 *dw, bool use_rf2) +{ + int rc = 0; + u32 val = DW3000_TXRXSWITCH_AUTO | + ((u32)use_rf2 + << DW3000_RF_SWITCH_CTRL_ANT_TXRX_MODE_OVR_BIT_OFFSET); + rc = dw3000_reg_write32(dw, DW3000_RF_SWITCH_CTRL_ID, 0, val); + if (!rc) + dw->rx_rf2 = use_rf2; + return rc; +} + +/** + * dw3000_read_rdb_status() - Fast read of RDB_STATUS register + * @dw: the DW device on which the SPI transfer will occurs + * @status: address where to put read status + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_read_rdb_status(struct dw3000 *dw, u8 *status) +{ + /* Use a prebuilt SPI message to be as fast as possible. */ + struct spi_message *msg = dw->msg_read_rdb_status; + struct spi_transfer *tr = list_last_entry( + &msg->transfers, struct spi_transfer, transfer_list); + const int hlen = tr->len - sizeof(*status); + int rc = dw3000_spi_sync(dw, msg); + if (!rc) + *status = *((u8 *)tr->rx_buf + hlen); + return rc; +} + +/** + * dw3000_check_devid() - Read and check the DEVID register + * @dw: the DW device on which the SPI transfer will occurs + * + * Return: 0 on success, else -ENODEV error code. + */ +int dw3000_check_devid(struct dw3000 *dw) +{ + u32 devid; + int i; + + int rc = dw3000_reg_read32(dw, DW3000_DEV_ID_ID, 0, &devid); + if (unlikely(rc)) + return rc; + for (i = 0; i < ARRAY_SIZE(dw3000_chip_versions); i++) { + if (devid == dw3000_chip_versions[i].id) { + if (!dw->chip_dev_id) { + dev_info(dw->dev, "chip version found : %x\n", + devid); + dw->chip_dev_id = devid; + dw->chip_idx = i; + } + __dw3000_chip_version = dw3000_chip_versions[i].ver; + dw->chip_ops = dw3000_chip_versions[i].ops; + return 0; + } + } + dev_warn(dw->dev, "unknown DEV_ID : %x\n", devid); + return -ENODEV; +} + +/** + * dw3000_check_idlerc() - Read and check the RCINIT bit in SYS_STATUS register + * @dw: the DW device on which the SPI transfer will occurs + * + * Return: True if bit is set else false if unset or SPI error. + */ +static inline bool dw3000_check_idlerc(struct dw3000 *dw) +{ + u32 low_sys_status; + + if (dw3000_read_sys_status(dw, &low_sys_status)) + return false; + + trace_dw3000_check_idlerc(dw, low_sys_status); + return low_sys_status & (DW3000_SYS_STATUS_RCINIT_BIT_MASK); +} + +/** + * dw3000_read_sys_time() - Read current system time + * @dw: the DW device on which the SPI transfer will occurs + * @sys_time: the address where to store current time + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_read_sys_time(struct dw3000 *dw, u32 *sys_time) +{ + /* Use a prebuilt SPI message to be as fast as possible. */ + struct spi_message *msg = dw->msg_read_sys_time; + struct spi_transfer *tr = list_first_entry( + &msg->transfers, struct spi_transfer, transfer_list); + const int hlen = tr->len - sizeof(*sys_time); + int rc; + + if (dw->chip_ops->pre_read_sys_time) { + rc = dw->chip_ops->pre_read_sys_time(dw); + if (rc) + return rc; + } + rc = dw3000_spi_sync(dw, msg); + if (!rc) + *sys_time = get_unaligned_le32(tr->rx_buf + hlen); + return rc; +} + +/** + * dw3000_read_rx_timestamp() - Read precise RX timestamp + * @dw: the DW device on which the SPI transfer will occurs + * @rx_ts: the address where to store RX timestamp value + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_read_rx_timestamp(struct dw3000 *dw, u64 *rx_ts) +{ + /* Use a prebuilt SPI message to be as fast as possible. */ + struct spi_message *msg; + struct spi_transfer *tr; + int hlen; /* depend on selected message */ + int rc; + + trace_dw3000_read_rx_timestamp(dw); + switch (dw->data.dblbuffon) { + case DW3000_DBL_BUFF_ACCESS_BUFFER_A: + msg = dw->msg_read_rx_timestamp_a; + break; + case DW3000_DBL_BUFF_ACCESS_BUFFER_B: + msg = dw->msg_read_rx_timestamp_b; + break; + default: + msg = dw->msg_read_rx_timestamp; + } + tr = list_first_entry(&msg->transfers, struct spi_transfer, + transfer_list); + /* Calc header len using known data size (smaller than sizeof(*rx_ts)) */ + hlen = tr->len - DW3000_RX_TIME_RX_STAMP_LEN; + /* Execute this spi message synchronously */ + rc = dw3000_spi_sync(dw, msg); + if (!rc) + *rx_ts = get_unaligned_le64(tr->rx_buf + hlen) & + ((1ull << (DW3000_RX_TIME_RX_STAMP_LEN * 8)) - 1); + trace_dw3000_return_int_u64(dw, rc, *rx_ts); + return rc; +} + +/** + * dw3000_configure_ciadiag() - Enable CIA diagnostic data + * @dw: the DW device + * @on: Enable/disable CIA to log all diagnostic registers + * @opt: option specified in ciadiag_dbl_options enum. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_configure_ciadiag(struct dw3000 *dw, bool on, + enum ciadiag_dbl_options opt) +{ + struct dw3000_local_data *data = &dw->data; + int rc; + + if (on) { + rc = dw3000_reg_and8(dw, DW3000_CIA_CONF_ID, 2, + ~(DW3000_CIA_CONFIG_DIAG_OFF)); + + } else { + rc = dw3000_reg_or8(dw, DW3000_CIA_CONF_ID, 2, + DW3000_CIA_CONFIG_DIAG_OFF); + } + if (unlikely(rc)) + return rc; + + if (!data->dblbuffon) { + if (opt != 0) { + dev_warn( + dw->dev, + "cannot set CIA diagnostic option while the double buffering is disabled\n"); + } + goto skip_opt; + } + /* Apply double buffering option */ + rc = dw3000_reg_write8(dw, DW3000_RDB_DIAG_MODE_ID, 0, opt); + if (unlikely(rc)) + return rc; +skip_opt: + data->ciadiag_enabled = on; + return rc; +} + +/** + * dw3000_rx_stats_enable() - Enable or disable all statistics + * @dw: the DW device + * @on: true to enable statistics, otherwise disable it. + * + * Return: 0 on success, else a negative error code. + */ +inline int dw3000_rx_stats_enable(struct dw3000 *dw, const bool on) +{ + dw->stats.enabled = on; + /** + * Enable the CIA diagnostic to get diagnostic values as CIR power + * and PACC count needed to calculate the RSSI in userspace. + */ + return dw3000_configure_ciadiag(dw, on, DW3000_CIA_DIAG_LOG_DBL_OFF); +} + +/** + * dw3000_rx_stats_clear() - Clear all statistics + * @dw: the DW device + */ +inline void dw3000_rx_stats_clear(struct dw3000 *dw) +{ + struct dw3000_stats *stats = &dw->stats; + + memset(stats->count, 0, sizeof(stats->count)); +} + +static bool dw3000_stats_enabled = false; +module_param_named(stats_enabled, dw3000_stats_enabled, bool, 0644); +MODULE_PARM_DESC(stats_enabled, + "Enable statistics gathering, to be used with traces, only" + " provides RSSI at this time"); + +/** + * dw3000_rx_store_rssi() - Get RSSI data from last good RX frame + * @dw: the DW device + * @rssi: points to current rssi record in stats structure + * @pkt_sts: sts mode of current packet + * + * The RSSI data must be read before incrementing counter. + * It requires at least one received frame to get any data from + * register. Both 'cir_pwr' and 'pacc_cnt' values cannot be null + * regarding the RSSI formula in the DW3700 User Manual v0.3 section 4.7.2. + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_rx_store_rssi(struct dw3000 *dw, struct dw3000_rssi *rssi, + u8 pkt_sts) +{ + struct dw3000_config *config = &dw->config; + /* Only read RSSI after good RX frame */ + int rc; + u32 cir_pwr; + u16 pacc_cnt; + u8 dgc_dec; + u32 diag1_addr; + + /* No data available */ + if (!rssi) + return -EAGAIN; + /* Get required data to calculate RSSI */ + if (dw->full_cia_read) { + /* Get values from already retrieved CIA registers */ + diag1_addr = (pkt_sts == DW3000_STS_MODE_OFF) ? + (DW3000_DB_DIAG_IP_DIAG1 >> 2) : + (DW3000_DB_DIAG_STS_DIAG1 >> 2); + cir_pwr = dw->cir_data->ciaregs[diag1_addr]; + pacc_cnt = dw->cir_data->acc; + } else { + /* Read CIR power value */ + rc = dw3000_reg_read32( + dw, + _ciadiag_reg_info[dw->data.ciadiag_reg_select].diag1, 0, + &cir_pwr); + if (unlikely(rc)) + return rc; + /* Read preamble accumulation count value */ + rc = dw3000_reg_read16( + dw, + _ciadiag_reg_info[dw->data.ciadiag_reg_select].diag12, + 0, &pacc_cnt); + if (unlikely(rc)) + return rc; + /* Reset minidiag to allow a new measure */ + rc = dw3000_reg_modify32(dw, DW3000_CIA_CONF_ID, 0, + ~DW3000_CIA_CONF_MINDIAG_BIT_MASK, + 0x0); + if (unlikely(rc)) + return rc; + } + + /* Store in provided buffer */ + rssi->cir_pwr = cir_pwr; + rssi->pacc_cnt = pacc_cnt; + rssi->prf_64mhz = ((config->rxCode >= 9) && (config->rxCode <= 24)); + + /* Read chip specific DGC_DBG */ + rc = dw->chip_ops->get_dgc_dec(dw, &dgc_dec); + if (unlikely(rc)) + return rc; + rssi->dgc_dec = dgc_dec; + + return 0; +} + +/** + * dw3000_rx_stats_inc() - Increment statistics. + * @dw: The DW device. + * @item: Statistics item. + * @rssi: The RSSI information to store. + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_rx_stats_inc(struct dw3000 *dw, const enum dw3000_stats_items item, + struct dw3000_rssi *rssi) +{ + struct dw3000_stats *stats = &dw->stats; + const char *chip_name = dw3000_get_chip_name(dw); + bool sts_enabled = _dw3000_sts_is_enabled(dw); + u16 idx; + + /* Increment per-item counter */ + stats->count[item]++; + + /* Save provided RSSI data */ + if (!rssi) + return 0; + + /* Retrieve current idx */ + idx = stats->indexes[item]; + /* RSSI array is divided in two parts, RX_GOOD at first */ + idx += (DW3000_RSSI_REPORTS_MAX >> 1) * (item == DW3000_STATS_RX_ERROR); + stats->rssi[idx] = *rssi; + + /* Update where to store next RSSI */ + stats->indexes[item]++; + if (stats->indexes[item] >= (DW3000_RSSI_REPORTS_MAX >> 1)) + stats->indexes[item] = 0; + /* TODO(UWB-3455): Shall we reset count[item] too to maintain current behaviour? + * See also do_tm_cmd_get_rx_diag() function. + */ + + /* Trace RSSI data*/ + trace_dw3000_rx_rssi(dw, chip_name, sts_enabled, rssi->cir_pwr, + rssi->pacc_cnt, rssi->prf_64mhz, rssi->dgc_dec); + return 0; +} + +/** + * dw3000_rx_calc_rssi() - RSSI computation. + * @dw: The DW device. + * @rssi: The RSSI related informations. + * @info: the MCPS information structure to fill with RSSI. + * @sts: Current STS mode. + * + * This function checks if RSSI is required: by explicit MAC request or by + * stats, then retrieve, store and output it by tracepoint + * or in a structure field. + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_rx_calc_rssi(struct dw3000 *dw, struct dw3000_rssi *rssi, + struct mcps802154_rx_frame_info *info, u8 sts) +{ + bool rssi_required = info->flags & MCPS802154_RX_FRAME_INFO_RSSI; + int rc = 0; + bool rx_tune; + u8 reg; + + if (!rssi_required) + return 0; + + /* Get RX_TUNE_EN bit required by the RSSI formula */ + /* TODO: move this in dw3000_configure_dgc() to cache this value in + struct dw3000_local_data and avoid this read. */ + rc = dw3000_reg_read8(dw, DW3000_DGC_CFG_ID, 0, ®); + if (rc) + return rc; + rx_tune = reg & DW3000_DGC_CFG_RX_TUNE_EN_BIT_MASK; + /* Compute RSSI and give the result to the upper layer in Q7.1 */ + info->rssi = dw->chip_ops->compute_rssi(dw, rssi, rx_tune, sts) << 1; + if (!info->rssi) + info->flags &= ~MCPS802154_RX_FRAME_INFO_RSSI; + return 0; +} + +static int dw3000_power_supply_one(struct regulator *regulator, bool onoff) +{ + int rc; + + if (!regulator) + return 0; + if (onoff) + rc = regulator_enable(regulator); + else + rc = regulator_disable(regulator); + return rc; +} + +/** + * dw3000_power_supply() - Set attached regulators state + * @dw: the DW device + * @onoff: the new expected regulators state + * + * Return: 0 on success, else a negative error code. + */ +static int dw3000_power_supply(struct dw3000 *dw, int onoff) +{ + struct dw3000_power_control *power = &dw->regulators; + int rc = 0; + + /* Early return if no regulator defined */ + if (!power->regulator_1p8 && !power->regulator_2p5 && + !power->regulator_vdd) + return 0; + /* Change defined regulators state */ + rc = dw3000_power_supply_one(power->regulator_1p8, onoff); + if (rc < 0) { + dev_err(dw->dev, "regulator %s failed for 1p8 (%d)\n", + onoff ? "enable" : "disable", rc); + return rc; + } + + rc = dw3000_power_supply_one(power->regulator_2p5, onoff); + if (rc < 0) { + dev_err(dw->dev, "regulator %s failed for 2p5 (%d)\n", + onoff ? "enable" : "disable", rc); + return rc; + } + /* Set 2p5 reg to 2.7V */ + rc = regulator_set_voltage(power->regulator_2p5, 2700000, 2700000); + if (rc < 0) { + dev_err(dw->dev, "regulator failed to set voltage :%d\n", rc); + return rc; + } + + rc = dw3000_power_supply_one(power->regulator_vdd, onoff); + if (rc < 0) { + dev_err(dw->dev, "regulator %s failed for vdd (%d)\n", + onoff ? "enable" : "disable", rc); + return rc; + } + + /* Add some delay to wait regulator stable */ + usleep_range(dw3000_regulator_delay_us, + dw3000_regulator_delay_us + 100); + return rc; +} + +/** + * dw3000_reset_assert() - Reset gpio control + * @dw: the DW device + * @reset: the DW device + * + * The configured reset gpio is switched to input to ensure it is not + * driven low. + * + * Return: 0 on success, else a negative error code. + */ +static int dw3000_reset_assert(struct dw3000 *dw, bool reset) +{ + int rc; + + if (!gpio_is_valid(dw->reset_gpio)) { + dev_err(dw->dev, "invalid reset gpio\n"); + return -EINVAL; + } + + if (reset) { + /* Assert RESET GPIO */ + rc = gpio_direction_output(dw->reset_gpio, 0); + if (rc) + dev_err(dw->dev, + "Could not set reset gpio as output\n"); + } else { + /* Release RESET GPIO. + * Reset should be open drain, or switched to input whenever not driven + * low. It should not be driven high. */ + rc = gpio_direction_input(dw->reset_gpio); + if (rc) + dev_err(dw->dev, "Could not set reset gpio as input\n"); + } + return rc; +} + +/** + * dw3000_set_operational_state() - Set new operational state for the device + * @dw: the DW device + * @st: the new operational state to set + * + * This ensure waiting thread are wake-up when operational state is changed. + */ +static void dw3000_set_operational_state(struct dw3000 *dw, + enum operational_state st) +{ + trace_dw3000_set_operational_state(dw, st); + dw->current_operational_state = st; + wake_up(&dw->operational_state_wq); +} + +/** + * dw3000_wait_idle_state() - Wait device powered-on and ready + * @dw: the DW device to power-on + * + * Device signal it is ready by issuing SPIRDY IRQ. This function waits + * for this IRQ to be handled and current_operational_state changed. + * + * Context: Must be call outside the thread handling device IRQ. + * Return: 0 on success, else a negative error code. + */ +int dw3000_wait_idle_state(struct dw3000 *dw) +{ + int timeout = msecs_to_jiffies(500); + int rc = 0; + + /* Force slow SPI clock speed (at device level) */ + dw3000_change_speed(dw, DW3000_SPI_SLOW_HZ); + /* Enable interrupt so we can catch the SPI ready IRQ */ + enable_irq(dw->spi->irq); + + /* Now, wait for SPI ready interrupt */ + if (!wait_event_timeout(dw->operational_state_wq, + dw->current_operational_state >= + DW3000_OP_STATE_IDLE_RC, + timeout)) { + dev_err(dw->dev, "Timeout waiting poweron event.\n"); + rc = -EIO; + } + + /* No IRQs after this point until device is enabled */ + disable_irq(dw->spi->irq); + /* Restore max SPI clock speed */ + dw3000_change_speed(dw, dw->of_max_speed_hz); + + if (!rc) + dw3000_power_stats(dw, DW3000_PWR_RUN, 0); + return rc; +} + +/** + * dw3000_poweron() - Power-on device + * @dw: the DW device to power-on + * + * Power-on device using configured regulators and de-assert reset gpio. + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_poweron(struct dw3000 *dw) +{ + int rc; + + if (dw->is_powered) { + dev_info(dw->dev, "Device already powered on\n"); + return 0; + } + /* Power up regulators */ + rc = dw3000_power_supply(dw, true); + if (rc) { + dev_err(dw->dev, "Could not enable regulator\n"); + return rc; + } + dw->is_powered = true; + /* De-assert RESET GPIO */ + return dw3000_reset_assert(dw, false); +} + +/** + * dw3000_poweroff() - Power-off device using configured reset gpio + * @dw: the DW device to power-on + * + * The configured reset gpio is switched to output, at low state. + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_poweroff(struct dw3000 *dw) +{ + struct dw3000_local_data *local = &dw->data; + int rc; + + if (!dw->is_powered) { + dev_info(dw->dev, "Device already powered off\n"); + return 0; + } + /* Power down regulators */ + rc = dw3000_power_supply(dw, false); + if (rc) { + dev_err(dw->dev, "Could not disable regulator\n"); + return rc; + } + dw->is_powered = false; + + /* Assert RESET GPIO */ + rc = dw3000_reset_assert(dw, true); + if (rc) + return rc; + + /* Clear security registers related cache */ + memset(local->sts_key, 0, AES_KEYSIZE_128); + memset(local->sts_iv, 0, AES_BLOCK_SIZE); + /* Reset STS mode */ + dw->config.stsMode = DW3000_STS_MODE_OFF | DW3000_STS_MODE_SDC; + /* Update operational state and power stats */ + dw3000_set_operational_state(dw, DW3000_OP_STATE_OFF); + dw3000_power_stats(dw, DW3000_PWR_OFF, 0); + + /* Ensure RESET is asserted at least the required time */ + usleep_range(DW3000_HARD_RESET_DELAY_US, + DW3000_HARD_RESET_DELAY_US + 100); + return 0; +} + +/** + * dw3000_forcetrxoff() - Force device in idle mode, TX/RX off + * @dw: the DW device + * + * According to the DW3000 manual, this command must be send with IRQ disable + * to avoid a race condition. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_forcetrxoff(struct dw3000 *dw) +{ + int rc; + u8 idle = 0; + + /* Check if in TX or RX state before forcing device into IDLE state */ + rc = dw3000_reg_read8(dw, DW3000_SYS_STATE_LO_ID, 2, &idle); + if (idle < DW3000_SYS_STATE_IDLE) { + /* Device is already in IDLE_RC ... must not force to IDLE if in IDLE_RC */ + return rc; + } + disable_irq(dw->spi->irq); + rc = dw3000_write_fastcmd(dw, DW3000_CMD_TXRXOFF); + enable_irq(dw->spi->irq); + if (!rc) + dw3000_power_stats(dw, DW3000_PWR_IDLE, 0); + return rc; +} + +/** + * dw3000_setpreambledetecttimeout() - Set the preamble detection timeout + * @dw: the DW device + * @timeout: preamble detection timeout in units of PAC size symbols. + * + * The counter automatically adds 1 PAC size to the value set. Min value that + * can be set is 1 (i.e. a timeout of 2 PAC size). + * + * The default/reset value is zero which disables the preamble detection + * timeout. + * + * Return: zero on success, else a negative error code. + */ +static inline int dw3000_setpreambledetecttimeout(struct dw3000 *dw, + u16 timeout) +{ + struct dw3000_local_data *local = &dw->data; + int rc; + /* + * Compare with the previous value stored if register access + * is necessary. + */ + if (local->rx_timeout_pac == timeout) + return 0; + rc = dw3000_reg_write16(dw, DW3000_DRX_PRETOC_ID, 0, timeout); + if (unlikely(rc)) + return rc; + local->rx_timeout_pac = timeout; + return 0; +} + +/** + * dw3000_setrxtimeout() - Set the reception timeout + * @dw: the DW device + * @timeout_dly: reception total time timeout in dly unit. + * + * timeout value 0 will disable the timeout. + * + * Return: zero on success, else a negative error code. + */ +static inline int dw3000_setrxtimeout(struct dw3000 *dw, u32 timeout_dly) +{ + struct dw3000_local_data *local = &dw->data; + int rc; + if (local->rx_frame_timeout_dly == timeout_dly) + return 0; + if (timeout_dly) { + rc = dw3000_reg_write32(dw, DW3000_RX_FWTO_ID, 0, timeout_dly); + if (unlikely(rc)) + return rc; + rc = dw3000_reg_or16(dw, DW3000_SYS_CFG_ID, 0, + DW3000_SYS_CFG_RXWTOE_BIT_MASK); + } else { + rc = dw3000_reg_and16(dw, DW3000_SYS_CFG_ID, 0, + (u16)~DW3000_SYS_CFG_RXWTOE_BIT_MASK); + } + if (unlikely(rc)) + return rc; + local->rx_frame_timeout_dly = timeout_dly; + return 0; +} + +static inline int dw3000_setdelayedtrxtime(struct dw3000 *dw, u32 starttime) +{ + u32 sys_starttime = dw3000_dtu_to_sys_time(dw, starttime); + return dw3000_reg_write32(dw, DW3000_DX_TIME_ID, 0, sys_starttime); +} + +/** + * dw3000_can_deep_sleep() - check delay before next operation + * @dw: the DW device + * @delay_us: the delay before which RX/TX must be executed + * + * Return: zero if not enough time to enter deep sleep, else a positive delay + * in us. + */ +int dw3000_can_deep_sleep(struct dw3000 *dw, int delay_us) +{ + /* We can't enter DEEP_SLEEP if previous operation has + * required ranging clock or if deep-sleep is disabled. */ + if (dw->need_ranging_clock || (dw->auto_sleep_margin_us < 0)) + return 0; + if (delay_us < max(DW3000_WAKEUP_LATENCY_US, dw->auto_sleep_margin_us)) + return 0; + /* Take care of wakeup latency in returned result */ + return delay_us - DW3000_WAKEUP_LATENCY_US; +} + +/** + * dw3000_wakeup() - wake-up device by forcing CS line down long enough + * @dw: the DW device + * + * Return: 0 on success, else a negative error code. + */ +static int dw3000_wakeup(struct dw3000 *dw) +{ + /* Wake UP require a minimum time of 500us CS low. Use one of prebuilt + SPI messages to be as fast as possible and modify it to include CS + required delay in microseconds. */ + struct spi_message *msg = dw->msg_read_sys_status; + struct spi_transfer *tr = list_first_entry( + &msg->transfers, struct spi_transfer, transfer_list); + int rc; + + /* Avoid race condition with dw3000_poweroff while chip is stopped just + when dw3000_idle_timeout() HR timer callback is executed. Do nothing + if not in DEEP-SLEEP state. */ + if (dw->current_operational_state != DW3000_OP_STATE_DEEP_SLEEP) + return 0; + + trace_dw3000_wakeup(dw); + + /* Add a delay after transfer. See spi_transfer_delay_exec() called by + spi_transfer_one_message(). */ +#if (KERNEL_VERSION(5, 13, 0) > LINUX_VERSION_CODE) + tr->delay_usecs = DW3000_SPI_CS_WAKEUP_DELAY_US; +#else + tr->delay.unit = SPI_DELAY_UNIT_USECS; + tr->delay.value = DW3000_SPI_CS_WAKEUP_DELAY_US; +#endif + /* Now, execute SPI modified message/transfer */ + rc = dw3000_spi_sync(dw, msg); + if (!rc) { + /* We are waking up the chip, update state according */ + dw3000_set_operational_state(dw, DW3000_OP_STATE_WAKE_UP); + /* Re-enable spi's irqs. deepsleep disable them */ + enable_irq(dw->spi->irq); + } + /* Reset delay in transfer */ +#if (KERNEL_VERSION(5, 13, 0) > LINUX_VERSION_CODE) + tr->delay_usecs = 0; +#else + tr->delay.value = 0; +#endif + return rc; + /* The next part of the wake process is located in + dw3000_isr_handle_spi_ready(), executed when SPIRDY interrupt is + triggered */ +} + +static int do_wakeup(struct dw3000 *dw, const void *in, void *out) +{ + int err; + err = dw3000_wakeup(dw); + if (err) + mcps802154_broken(dw->llhw); + + return err; +} + +int dw3000_deepsleep_wakeup_now(struct dw3000 *dw, + dw3000_idle_timeout_cb idle_timeout_cb, + u32 timestamp_dtu, + enum operational_state next_operational_state) +{ + struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state; + int r; + + r = dw3000_wakeup(dw); + if (r) + return r; + + dss->next_operational_state = next_operational_state; + dw->idle_timeout_cb = idle_timeout_cb; + dw->idle_timeout_dtu = timestamp_dtu; + return 0; +} + +/** + * dw3000_handle_idle_timeout() - Idle expired handler + * @dw: the DW device. + * @in: ignored input. + * @out: ignored output. + * + * Return: 0 on success, -errno otherwise. + */ +static int dw3000_handle_idle_timeout(struct dw3000 *dw, const void *in, + void *out) +{ + dw3000_idle_timeout_cb idle_timeout_cb = dw->idle_timeout_cb; + + /* Consume/remove registered handler before call it.*/ + dw->idle_timeout_cb = NULL; + /* Call handler registered. */ + if (idle_timeout_cb) + return idle_timeout_cb(dw); + return 0; +} + +/** + * dw3000_deepsleep_wakeup() - Handle wake-up. + * @dw: the DW device. + * + * Return: True when the wakeup is started, false otherwise. + */ +bool dw3000_deepsleep_wakeup(struct dw3000 *dw) +{ + trace_dw3000_deepsleep_wakeup(dw); + if (dw->current_operational_state == DW3000_OP_STATE_DEEP_SLEEP && + dw->deep_sleep_state.next_operational_state > + DW3000_OP_STATE_IDLE_PLL) { + struct dw3000_stm_command cmd = { do_wakeup, NULL, NULL }; + /* The chip is about to wake up, let's request the best QoS + latency early */ + dw3000_pm_qos_update_request(dw, dw3000_qos_latency); + /* Must wakeup to execute stored operation, so run the + wake up function in state machine thread. */ + dw3000_enqueue_timer(dw, &cmd); + return true; + } + return false; +} + +/** + * dw3000_idle_timeout() - idle timeout handler + * @timer: the idle_timer field in struct dw3000 + * + * Return: always timer not restarted. + */ +enum hrtimer_restart dw3000_idle_timeout(struct hrtimer *timer) +{ + struct dw3000 *dw = container_of(timer, struct dw3000, idle_timer); + bool wakeup_started; + + trace_dw3000_idle_timeout(dw); + wakeup_started = dw3000_deepsleep_wakeup(dw); + if (!wakeup_started && dw->idle_timeout_cb) { + struct dw3000_stm_command cmd = { dw3000_handle_idle_timeout, + NULL, NULL }; + dw3000_enqueue_timer(dw, &cmd); + } + return HRTIMER_NORESTART; +} + +/** + * dw3000_idle_cancel_timer() - Cancel idle timer. + * @dw: the DW device + * + * Return: 0 on success, -errno otherwise. + */ +int dw3000_idle_cancel_timer(struct dw3000 *dw) +{ + int r; + + trace_dw3000_idle_cancel_timer(dw); + /* Remember: return value of hrtimer_try_to_cancel. + * 0 when the timer was not active. + * 1 when the timer was active. + * -1 when the timer is currently executing the callback function and + * cannot be stopped. */ + r = hrtimer_try_to_cancel(&dw->idle_timer); + if (r < 0) + return -EBUSY; + else if (!r) + return -ENOENT; + /* Ensure wakeup ISR don't call the mcps802154_timer_expired() */ + dw->idle_timeout_cb = NULL; + dw->idle_timeout = false; + return 0; +} + +/** + * dw3000_wakeup_and_wait() - Check current device operational state + * @dw: the DW device to execute an RX/TX/configuration operation + * + * Can't program parameters in device if not in IDLE_PLL state, so + * we need to wakeup it at different places. + */ +void dw3000_wakeup_and_wait(struct dw3000 *dw) +{ + trace_dw3000_wakeup_and_wait(dw, dw->current_operational_state); + if (dw->current_operational_state >= DW3000_OP_STATE_IDLE_PLL) + return; + /* Ensure wakeup ISR don't call the mcps802154_timer_expired() since + this will result in dead lock! */ + dw3000_idle_cancel_timer(dw); + /* Ensure force call to dw3000_wakeup() is made. */ + dw->deep_sleep_state.next_operational_state = DW3000_OP_STATE_MAX; + /* Now wakeup device, and stop timer if running */ + dw3000_deepsleep_wakeup(dw); + dw->deep_sleep_state.next_operational_state = DW3000_OP_STATE_IDLE_PLL; + /* And wait for good state */ + if (current != dw->stm.mthread) + wait_event(dw->operational_state_wq, + dw->current_operational_state == + DW3000_OP_STATE_IDLE_PLL); + else + /* TODO: find a better solution to allow wakeup isr processing + recursively.*/ + mdelay(2); +} + +/** + * dw3000_check_operational_state() - Check current device operational state + * @dw: the DW device to execute an RX/TX/configuration operation + * @delay_dtu: delay before expected operation + * @can_sync: true when it's possible to sync the clock + * + * This function will decide if device should enter/leave DEEP SLEEP + * state according current state and delay before next operation. + * + * Context: called from dw3000-spi thread only. + * Return: 0 if ready, 1 if in deep-sleep or waking-up, or a negative error + * code. + */ +int dw3000_check_operational_state(struct dw3000 *dw, int delay_dtu, + bool can_sync) +{ + int delay_us = DTU_TO_US(delay_dtu); + int rc; + + trace_dw3000_check_operational_state( + dw, delay_dtu, dw->current_operational_state, + dw->deep_sleep_state.next_operational_state); + + if (dw->current_operational_state == DW3000_OP_STATE_OFF) + return -ENODEV; + /* In deep sleep or wake up in progress, we can store parameters only + if no other operation queued. */ + if ((dw->current_operational_state < DW3000_OP_STATE_IDLE_PLL) && + (dw->deep_sleep_state.next_operational_state != + DW3000_OP_STATE_IDLE_PLL)) + return -EIO; + + switch (dw->current_operational_state) { + case DW3000_OP_STATE_DEEP_SLEEP: + /* Update delay_us with wakeup margin */ + delay_us = dw3000_can_deep_sleep(dw, delay_us); + if (delay_us) { + /* No need to wakeup now! Reprogram timer only. */ + dw3000_wakeup_timer_start(dw, delay_us); + /* Stay in deep sleep, inform caller to save params. */ + return 1; + } + /* The chip is about to wake up, request the best QoS latency */ + dw3000_pm_qos_update_request(dw, dw3000_qos_latency); + /* Cancel wakeup timer launch by idle() */ + dw3000_idle_cancel_timer(dw); + /* Wakeup now since delay isn't enough */ + rc = dw3000_wakeup(dw); + if (unlikely(rc)) + return rc; + /* Use kernel defined statement which exist since kernel 5.4. */ + fallthrough; + case DW3000_OP_STATE_WAKE_UP: + /* Inform caller to save parameters. Stored operation will redo + deep sleep if needed. */ + return 1; + default: + /* May need to resync when deep sleep is not enabled */ + if (can_sync) + dw3000_may_resync(dw); + /* Update delay_us with wakeup margin */ + delay_us = dw3000_can_deep_sleep(dw, delay_us); + if (!delay_us) + break; + /* Enter DEEP SLEEP and setup wakeup timer */ + rc = dw3000_deep_sleep_and_wakeup(dw, delay_us); + if (!rc) + return 1; /* And save params. */ + /* Failure to enter deep sleep, continue without it */ + } + /* Cancel wakeup timer launch by idle() */ + dw3000_idle_cancel_timer(dw); + return 0; +} + +/** + * dw3000_check_hpdwarn() - check HPDWARN event status bit + * @dw: the DW device + * + * The HPDWARN event status bit relates to the use of delayed transmit and + * delayed receive functionality. It indicates the delay is more than half + * a period of the system clock. The HPDWARN status flag can be polled after a + * delayed transmission or reception is commanded, to check whether the delayed + * send/receive invocation was given in time (0) or not (1). + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_check_hpdwarn(struct dw3000 *dw) +{ + u8 status; + int rc; + + rc = dw3000_reg_read8(dw, DW3000_SYS_STATUS_ID, 3, &status); + if (rc) + return rc; + if (status & (DW3000_SYS_STATUS_HPDWARN_BIT_MASK >> 24)) { + dw3000_forcetrxoff(dw); + return -ETIME; + } + return 0; +} + +/** + * dw3000_rx_disable() - Disable RX + * @dw: the DW device to put back in IDLE state + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_rx_disable(struct dw3000 *dw) +{ + return dw3000_forcetrxoff(dw); +} + +/** + * dw3000_rx_busy() - Change RX busy state + * @dw: the DW device to change RX busy state + * @busy: the new value to set in busy field + * + * Return: true if busy flag already set to specified value. + */ +bool dw3000_rx_busy(struct dw3000 *dw, bool busy) +{ + unsigned long flags; + /* Check and set busy flag */ + spin_lock_irqsave(&dw->rx.lock, flags); + if (dw->rx.busy == busy) { + spin_unlock_irqrestore(&dw->rx.lock, flags); + return true; + } + dw->rx.busy = busy; + spin_unlock_irqrestore(&dw->rx.lock, flags); + return false; +} + +/** + * dw3000_rx_enable() - Enable RX + * @dw: the DW device to put in RX mode + * @rx_delayed: true if RX delayed must be use + * @date_dtu: the date at which RX must be enabled if @rx_delayed is true + * @timeout_pac: the preamble detect timeout + * + * This function enable RX on DW3000, delayed or not, with a configurable + * preamble detection timeout. + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_rx_enable(struct dw3000 *dw, bool rx_delayed, u32 date_dtu, + u32 timeout_pac) +{ + u32 cur_time_dtu = 0; + int rc; + + /* Read current DTU time to compare with next transfer time */ + if (dw->coex_gpio >= 0) + cur_time_dtu = dw3000_get_dtu_time(dw); + + /* Configure timeout */ + rc = dw3000_setpreambledetecttimeout(dw, timeout_pac); + if (unlikely(rc)) + return rc; + + /* Flush SPI queue before dw3000_coex_start() because coex use it + already. + NOTE: Since dw3000_rx_enable() also called during ADC calibration, + we may have nothing to flush. Call it only if SPI queuing is active */ + if (dw3000_spi_queue_active(dw)) { + rc = dw3000_spi_queue_flush(dw); + if (unlikely(rc)) + return rc; + } + + /* Update RX parameters according to WiFi coexistence */ + rc = dw3000_coex_start(dw, &rx_delayed, &date_dtu, cur_time_dtu); + if (unlikely(rc)) + return rc; + + if (!rx_delayed) { + /* Enter immediate reception mode */ + rc = dw3000_write_fastcmd(dw, DW3000_CMD_RX); + if (unlikely(rc)) + goto stop_coex; + dw3000_power_stats(dw, DW3000_PWR_RX, 0); + return 0; + } + + /* Restart SPI queuing mode */ + dw3000_spi_queue_start(dw); + /* Set reception date */ + rc = dw3000_setdelayedtrxtime(dw, date_dtu); + if (unlikely(rc)) + goto stop_coex; + /* Enter delayed reception mode */ + rc = dw3000_write_fastcmd(dw, DW3000_CMD_DRX); + if (unlikely(rc)) + goto stop_coex; + /* Execute SPI queued transfers */ + rc = dw3000_spi_queue_flush(dw); + if (unlikely(rc)) + goto stop_coex; + + /* Apply power stats now, will goes back to IDLE in dw3000_forcetrxoff() */ + dw3000_power_stats(dw, DW3000_PWR_RX, date_dtu); + /* Check if late */ + rc = dw3000_check_hpdwarn(dw); + if (unlikely(rc)) { + if (rc == -ETIME) { + cur_time_dtu = dw3000_get_dtu_time(dw); + dev_err(dw->dev, + "cannot program delayed rx date_dtu=%x current_dtu=%x\n", + date_dtu, cur_time_dtu); + } + goto stop_coex; + } + return 0; +stop_coex: + dw3000_coex_stop(dw); + return rc; +} + +/** + * dw3000_do_rx_enable() - handle RX enable MCPS operation + * @dw: the DW device to put in RX mode + * @config: RX enable parameters from MCPS + * @frame_idx: Frame index in a continuous block + * + * This function is called to execute all required operation to enable RX on + * the device using provided parameters. + * + * Since RX enable may be deferred until device is wokenup, it may be also + * called a second time with same parameters from the wakeup ISR. + * + * This function calls dw3000_check_operational_state() and save and defer + * the operation if DEEP-SLEEP is possible according delay. + * + * If RX must be enable immediately, this function configure STS, PDOA and + * Antenna pair before enable RX by calling dw3000_rx_enable(). + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_do_rx_enable(struct dw3000 *dw, + const struct mcps802154_rx_frame_config *config, + int frame_idx) +{ + struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state; + struct mcps802154_llhw *llhw = dw->llhw; + u32 cur_time_dtu = 0; + u32 rx_date_dtu = 0; + u32 timeout_pac = 0; + u32 frame_timeout_dly = 0; + bool rx_delayed = true; + int delay_dtu = 0; + bool can_sync = false; + int rc; + bool pdoa_enabled; + u8 sts_mode; + + trace_dw3000_mcps_rx_enable(dw, config->flags, config->timeout_dtu); + + /* Ensure CFO is checked if responder wait first frame of round. */ + dw->data.check_cfo = + !!(config->flags & MCPS802154_RX_FRAME_CONFIG_RANGING_ROUND); + /* Calculate the transfer date. */ + if (config->flags & MCPS802154_RX_FRAME_CONFIG_TIMESTAMP_DTU) { + rx_date_dtu = + config->timestamp_dtu - DW3000_RX_ENABLE_STARTUP_DTU; + } else { + /* Receive immediately. */ + rx_delayed = false; + } + + if (rx_delayed) { + cur_time_dtu = dw3000_get_dtu_time(dw); + delay_dtu = (int)(rx_date_dtu - cur_time_dtu); + if (delay_dtu < 0) { + trace_dw3000_mcps_rx_enable_too_late(dw, rx_date_dtu, + cur_time_dtu); + return -ETIME; + } + } + + /* We are always allowed to sleep & sync at the beginning of a block. */ + if (frame_idx == 0) { + dw->need_ranging_clock = false; + can_sync = true; + } + + /* For delayed RX, where delay_dtu != 0, enter/leave deep sleep */ + rc = dw3000_check_operational_state(dw, delay_dtu, can_sync); + if (rc) { + /* + * Handle error cases first : + * - Do not fail if we are in deep sleep/wakeup state + */ + if (rc < 0 && rc != -EIO) + return rc; + /* Save parameters to activate RX delayed when + wakeup later */ + + dw->wakeup_done_cb = dw3000_wakeup_done_to_rx; + dss->next_operational_state = DW3000_OP_STATE_RX; + dss->rx_config = *config; + dss->frame_idx = frame_idx; + return 0; + } + /* All operation below require the DW chip is in IDLE_PLL state */ + + /* Calculate the preamble timeout. */ + if (config->timeout_dtu == 0) { + timeout_pac = dw->pre_timeout_pac; + } else if (config->timeout_dtu != -1) { + timeout_pac = dw->pre_timeout_pac + + dtu_to_pac(llhw, config->timeout_dtu); + } else { + /* No timeout. */ + } + + /* Add the frame timeout if needed. */ + if (timeout_pac && config->frame_timeout_dtu) { + frame_timeout_dly = + dtu_to_dly(llhw, config->frame_timeout_dtu) + + pac_to_dly(llhw, timeout_pac); + } + + /* Start SPI queuing mode to minimise number of SPI messages + Queue will be flushed by dw3000_tx_frame() itself. */ + dw3000_spi_queue_start(dw); + + /* Enable STS */ + pdoa_enabled = + !!(config->flags & MCPS802154_RX_FRAME_CONFIG_RANGING_PDOA); + sts_mode = FIELD_GET(MCPS802154_RX_FRAME_CONFIG_STS_MODE_MASK, + config->flags); + rc = dw3000_set_sts_pdoa(dw, sts_mode, + sts_to_pdoa(sts_mode, pdoa_enabled)); + if (unlikely(rc)) + goto fail; + /* Ensure correct RX antennas are selected. */ + rc = dw3000_set_rx_antennas(dw, config->ant_set_id, pdoa_enabled, frame_idx); + if (unlikely(rc)) + goto fail; + if (config->flags & MCPS802154_RX_FRAME_CONFIG_AACK) { + dw3000_enable_autoack(dw, false); + } else { + dw3000_disable_autoack(dw, false); + } + rc = dw3000_setrxtimeout(dw, frame_timeout_dly); + if (unlikely(rc)) + goto fail; + rc = dw3000_rx_enable(dw, rx_delayed, rx_date_dtu, timeout_pac); + if (unlikely(rc)) + goto fail; + + /* Store ranging clock requirement for next operation */ + dw->need_ranging_clock = + (config->flags & + MCPS802154_RX_FRAME_CONFIG_KEEP_RANGING_CLOCK) != 0; + +fail: + if (rc) + /* Ensure SPI queuing mode disabled on error */ + dw3000_spi_queue_reset(dw, rc); + trace_dw3000_return_int(dw, rc); + return rc; +} + +static irqreturn_t dw3000_irq_handler(int irq, void *context) +{ + struct dw3000 *dw = context; + + atomic64_inc(&dw->power.interrupts); + dw3000_enqueue_irq(dw); + + return IRQ_HANDLED; +} + +static void dw3000_setup_one_regulator(const char *name, struct device *dev, + struct regulator **out) +{ + struct regulator *regulator; + + regulator = devm_regulator_get_optional(dev, name); + if (IS_ERR(regulator)) { + int err = PTR_ERR(regulator); + dev_notice(dev, "Fail to get %s regulator (%d)\n", name, err); + regulator = NULL; + } + *out = regulator; +} + +/** + * dw3000_setup_regulators() - request regulator + * @dw: the DW device to get regulator config from DT + * + * This will search for configured regulators to use from the device tree. + * All of them are optional, so missing one will just be noticed to dmesg. + */ +void dw3000_setup_regulators(struct dw3000 *dw) +{ + struct dw3000_power_control *power = &dw->regulators; + + dw3000_setup_one_regulator("power_reg_1p8", dw->dev, + &power->regulator_1p8); + dw3000_setup_one_regulator("power_reg_2p5", dw->dev, + &power->regulator_2p5); + dw3000_setup_one_regulator("power_reg", dw->dev, &power->regulator_vdd); + + if (!power->regulator_1p8 && !power->regulator_2p5 && + !power->regulator_vdd) { + dev_warn(dw->dev, "No regulators, assuming always on\n"); + } +} + +/** + * dw3000_setup_reset_gpio() - request reset GPIO + * @dw: the DW device to get reset GPIO config from DT + * + * Get the reset GPIO to use from the DT and configure it in OUTPUT + * open-drain mode and activate it. This ensure the DW device is + * in reset state, IRQ pin low from the end of this function. + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_setup_reset_gpio(struct dw3000 *dw) +{ + /* Initialise reset GPIO pin as output */ + dw->reset_gpio = of_get_named_gpio(dw->dev->of_node, "reset-gpio", 0); + if (!gpio_is_valid(dw->reset_gpio)) { + /* Try with old name */ + dw->reset_gpio = of_get_named_gpio(dw->dev->of_node, + "uwbhal,reset-gpio", 0); + } + if (!gpio_is_valid(dw->reset_gpio)) { + dev_warn(dw->dev, "device does not support GPIO RESET control"); + return 0; + } + return devm_gpio_request_one(dw->dev, dw->reset_gpio, + GPIOF_DIR_OUT | GPIOF_OPEN_DRAIN | + GPIOF_INIT_LOW, + "dw3000-reset"); +} + +/** + * dw3000_setup_irq() - request IRQ for the device + * @dw: the DW device + * + * Ensure the IRQ is correctly configured and install the hard IRQ handler. + * The IRQ is immediately disabled, waiting the device to be started by MCPS. + * + * Note: If the reset GPIO is deasserted before this function, a spurious + * IRQ may be handled and dw3000_irq_handler() called. This IRQ is ignored + * if occurs before dw3000_init() call. + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_setup_irq(struct dw3000 *dw) +{ + int rc; + int irq_flags, irq_gpio; + + /* Check the presence of irq-gpio in DT. If so, + * use it as irq, if not, "interrupt-parent" or + * "interrupt-extended" should be provided and then used. + */ + irq_gpio = of_get_named_gpio(dw->dev->of_node, "irq-gpio", 0); + if (irq_gpio > 0) { + if (!gpio_is_valid(irq_gpio)) + return -EINVAL; + + devm_gpio_request_one(dw->dev, irq_gpio, GPIOF_IN, + dev_name(dw->dev)); + dw->spi->irq = gpio_to_irq(irq_gpio); + } + + irq_flags = irq_get_trigger_type(dw->spi->irq); + if (!irq_flags) { + irq_flags = IRQF_TRIGGER_HIGH; + } + + /* Hook interruption */ + rc = devm_request_irq(dw->dev, dw->spi->irq, dw3000_irq_handler, + irq_flags, dev_name(dw->dev), dw); + if (rc) { + dev_err(dw->dev, "could not request the IRQ %d: %d\n", + dw->spi->irq, rc); + return rc; + } + + /* Disable interrupt before enabling the device */ + disable_irq_nosync(dw->spi->irq); + + return 0; +} + +/** + * dw3000_setup_wifi_coex() - request wifi coex parameters for the device + * @dw: the DW device to get wifi coex parameters from DT + * + * Get the wifi coex parameters from the DT : + * + * wificoex_gpio : wifi coexistence GPIO number + * If not set, it is disabled. + * + * wificoex_delay_us : delay between wifi coexistence GPIO activation and TX/RX in us. + * Default is 1000us. + * + * wificoex_interval_us : minimum interval between two operations in us under which + * wifi coexistence GPIO is kept active. + * Default is 2000us. + * + * wificoex_margin_us: margin to add to the wifi coexistece delay + * for SPI transactions + * Default is 300us. + * + * All these parameters are optional + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_setup_wifi_coex(struct dw3000 *dw) +{ + int rc; + struct device_node *node = dw->dev->of_node; + + /* Initialize wifi coex parameters to default values */ + dw->coex_gpio = -1; + dw->coex_delay_us = 1000; + dw->coex_margin_us = 300; + dw->coex_interval_us = 2000; + + /* + * Get wificoex_gpio : + * If not set stop here to get parameters from DT + * because the feature is disabled + */ + if (of_find_property(node, "wificoex_gpio", NULL)) { + rc = of_property_read_u8(node, "wificoex_gpio", &dw->coex_gpio); + if (rc) { + dev_err(dw->dev, "fail to get 'wificoex_gpio' %s\n", + node->name); + return rc; + } + } else { + dev_info(dw->dev, "gpio coex disabled\n"); + return 0; + } + /* Get wificoex_delay_us */ + if (of_find_property(node, "wificoex_delay_us", NULL)) { + rc = of_property_read_u32(node, "wificoex_delay_us", + &dw->coex_delay_us); + if (rc) { + dev_err(dw->dev, "fail to get 'wificoex_delay_us' %s\n", + node->name); + return rc; + } + } + /* Get wificoex_interval_us */ + if (of_find_property(node, "wificoex_interval_us", NULL)) { + rc = of_property_read_u32(node, "wificoex_interval_us", + &dw->coex_interval_us); + if (rc) { + dev_err(dw->dev, + "fail to get 'wificoex_interval_us' %s\n", + node->name); + return rc; + } + } + /* Get wificoex_margin_us */ + if (of_find_property(node, "wificoex_margin_us", NULL)) { + rc = of_property_read_u32(node, "wificoex_margin_us", + &dw->coex_margin_us); + if (rc) { + dev_err(dw->dev, + "fail to get 'wificoex_margin_us' %s\n", + node->name); + return rc; + } + } + dev_info(dw->dev, + "gpio coex enabled : " + "gpio %u, delay %u, interval %u, margin %u\n", + dw->coex_gpio, dw->coex_delay_us, dw->coex_interval_us, + dw->coex_margin_us); + + return 0; +} + +/** + * dw3000_setup_thread_cpu() - request thread cpu for the device + * @dw: the DW device to get thread cpu from DT + * @dw3000_thread_cpu: pointer to the thread cpu value read in DT or -1 + * + * Get the thread cpu# from the DT : + * + * CPU# on which the DW state machine's thread will run. + * If not set, processing thread is not bound to a specific CPU and + * the scheduler may move it, adding migration latencies. + * + * This property is optional + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_setup_thread_cpu(struct dw3000 *dw, int *dw3000_thread_cpu) +{ + int rc; + struct device_node *node = dw->dev->of_node; + + *dw3000_thread_cpu = -1; + + if (of_find_property(node, "cpu", NULL)) { + rc = of_property_read_u32(node, "cpu", dw3000_thread_cpu); + if (rc) { + dev_err(dw->dev, "fail to get 'cpu' %s\n", node->name); + return rc; + } + } + + dev_info(dw->dev, "thread cpu %d\n", *dw3000_thread_cpu); + + return 0; +} + +/** + * dw3000_setup_qos_latency() - request qos latency for the device + * @dw: the DW device to get qos latency from DT + * + * Get the qos latency from the DT : + * + * Latency request to PM QoS on active ranging in microseconds. + * Default is 0, the minimum PM QoS latency. + * + * This property is optional + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_setup_qos_latency(struct dw3000 *dw) +{ + int rc; + struct device_node *node = dw->dev->of_node; + + if (of_find_property(node, "qos_latency", NULL)) { + rc = of_property_read_u32(node, "qos_latency", + &dw3000_qos_latency); + if (rc) { + dev_err(dw->dev, "fail to get 'qos_latency' %s\n", + node->name); + return rc; + } + } + + dev_info(dw->dev, "qos latency %d\n", dw3000_qos_latency); + + return 0; +} + +/** + * dw3000_setup_regulator_delay() - request regulator delay for the device + * @dw: the DW device to get regulator delay from DT + * + * Get the regulator delay in us from the DT : + * + * Delay in us to wait after regulator(s) state has changed. + * Default is 1000 us. + * + * This property is optional + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_setup_regulator_delay(struct dw3000 *dw) +{ + int rc; + struct device_node *node = dw->dev->of_node; + + if (of_find_property(node, "regulator_delay_us", NULL)) { + rc = of_property_read_u32(node, "regulator_delay_us", + &dw3000_regulator_delay_us); + if (rc) { + dev_err(dw->dev, + "fail to get 'regulator_delay_us' %s\n", + node->name); + return rc; + } + } + + dev_info(dw->dev, "regulator delay %d us\n", dw3000_regulator_delay_us); + + return 0; +} + +int dw3000_hardreset(struct dw3000 *dw) +{ + int rc; + + rc = dw3000_reset_assert(dw, true); + if (rc) + return rc; + + usleep_range(DW3000_HARD_RESET_DELAY_US, + DW3000_HARD_RESET_DELAY_US + 100); + + return dw3000_reset_assert(dw, false); +} + +static inline int dw3000_clear_aonconfig(struct dw3000 *dw) +{ + int rc; + /* Clear any AON auto download bits (as reset will trigger AON + * download). */ + rc = dw3000_reg_write16(dw, DW3000_AON_DIG_CFG_ID, 0, 0x00); + if (rc) + return rc; + /* Clear the wake-up configuration */ + rc = dw3000_reg_write8(dw, DW3000_AON_CFG_ID, 0, 0x00); + if (rc) + return rc; + /* Upload the new configuration */ + rc = dw3000_reg_write8(dw, DW3000_AON_CTRL_ID, 0, 0); + if (rc) + return rc; + return dw3000_reg_write8(dw, DW3000_AON_CTRL_ID, 0, + DW3000_AON_CTRL_ARRAY_UPLOAD_BIT_MASK); +} + +static void _dw3000_softreset(struct dw3000 *dw) +{ + /* Clear any AON configurations (this will leave the device at FOSC/4, + * thus we need low SPI rate) + */ + dw3000_clear_aonconfig(dw); + /* Make sure the new AON array config has been set */ + usleep_range(DW3000_SOFT_RESET_DELAY_US, + DW3000_SOFT_RESET_DELAY_US + 100); + /* Need to make sure clock is not PLL as the PLL will be switched off + * as part of reset. + */ + dw3000_reg_or8(dw, DW3000_CLK_CTRL_ID, 0, DW3000_FORCE_SYSCLK_FOSC); + + /* Call version specific softreset */ + dw->chip_ops->softreset(dw); + + /* DW3000 needs a 10us sleep to let clk PLL lock after reset + * - the PLL will automatically lock after the reset + * Could also have polled the PLL lock flag, + * but then the SPI needs to be <= 7MHz !! So a simple delay is easier. + */ + usleep_range(DW3000_SOFT_RESET_DELAY_US, + DW3000_SOFT_RESET_DELAY_US + 100); + /* DW3000 not in sleep_mode anymore */ + dw->data.sleep_mode = 0; +} + +static int dw3000_tx_write_data(struct dw3000 *dw, u8 *buffer, u16 len) +{ + if (len >= DW3000_TX_BUFFER_MAX_LEN) + return -1; + /* Directly write the data to the IC TX buffer */ + dw3000_xfer(dw, DW3000_TX_BUFFER_ID, 0, len, buffer, DW3000_SPI_WR_BIT); + return 0; +} + +static int do_change_speed(struct dw3000 *dw, const void *in, void *out) +{ + /* Need to reset pre-allocated message which hold speed */ + return dw3000_transfers_reset(dw); +} + +/** + * dw3000_change_speed() - change SPI speed and update transfers + * @dw: the DW device to change the speed + * @new_speed: the new speed to use for all transfers + * + * The speed is first changed into the SPI device structure than + * all pre-computed SPI transfers are updated if their speed isn't + * the same. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_change_speed(struct dw3000 *dw, u32 new_speed) +{ + struct dw3000_stm_command cmd_change_speed = { do_change_speed, NULL, + NULL }; + + /* Setup new SPI speed only if changed */ + if (new_speed == dw->spi->max_speed_hz) + return 0; + /* Change SPI max speed */ + dw->spi->max_speed_hz = new_speed; + + /* + * Must call dw3000_transfers_reset() from our thread + * to guarantee no SPI transfer occurs during + * transfers structure are re-allocated + */ + return dw3000_enqueue_generic(dw, &cmd_change_speed); +} + +/** + * dw3000_softreset() - perform soft-reset of the device + * @dw: the device to soft-reset + * + * The SPI speed is changed to low-speed before doing the soft-reset and is + * restored to full-speed (the speed configured in device tree) after. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_softreset(struct dw3000 *dw) +{ + int rc; + /* Force slow SPI clock speed (at device level) */ + dw3000_change_speed(dw, DW3000_SPI_SLOW_HZ); + + /* Soft reset (require to known chip version) */ + _dw3000_softreset(dw); + + /* Re-read device ID to ensure bus is operational at low-speed */ + rc = dw3000_check_devid(dw); + if (rc) + return rc; + + /* Switch back to full SPI clock speed */ + dw3000_change_speed(dw, dw->of_max_speed_hz); + + /* Check device ID to ensure bus is operational at high-speed */ + return dw3000_check_devid(dw); +} + +static inline int dw3000_ctrl_rftx_blocks(struct dw3000 *dw, u32 chan, + int reg_fileid) +{ + u32 val; + + if (reg_fileid != DW3000_RF_ENABLE_ID && + reg_fileid != DW3000_RF_CTRL_MASK_ID) + return -EINVAL; + val = DW3000_RF_ENABLE_TX_SW_EN_BIT_MASK | + DW3000_RF_ENABLE_TX_EN_BIT_MASK | + DW3000_RF_ENABLE_TX_EN_BUF_BIT_MASK | + DW3000_RF_ENABLE_TX_BIAS_EN_BIT_MASK | + DW3000_RF_ENABLE_TX_CH_ALL_EN_BIT_MASK; + return dw3000_reg_or32(dw, reg_fileid, 0, val); +} + +/** + * dw3000_rftx_blocks_autoseq_disable() - Disables automatic sequencing + * of the tx-blocks + * @dw: the DW device + * @chan: specifies the operating channel (e.g. 5 or 9) + * + * Return: zero on success, else a negative error code. + */ +static inline int dw3000_rftx_blocks_autoseq_disable(struct dw3000 *dw, + u32 chan) +{ + return dw3000_ctrl_rftx_blocks(dw, chan, DW3000_RF_CTRL_MASK_ID); +} + +/** + * dw3000_rftx_blocks_enable() - Enable RF blocks for TX + * @dw: the DW device + * @chan: specifies the operating channel (e.g. 5 or 9) + * + * Return: zero on success, else a negative error code. + */ +static inline int dw3000_rftx_blocks_enable(struct dw3000 *dw, u32 chan) +{ + return dw3000_ctrl_rftx_blocks(dw, chan, DW3000_RF_ENABLE_ID); +} + +/** + * dw3000_enable_rf_tx() - Turn on TX LDOs and enable RF blocks for TX + * @dw: the DW device + * @chan: specifies the operating channel (e.g. 5 or 9) + * @switch_ctrl: specifies whether the switch needs to be configured for TX + * + * Enable TX LDOs and allow TX blocks to be manually turned on by + * dw3000_rftx for a given channel. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_enable_rf_tx(struct dw3000 *dw, u32 chan, u8 switch_ctrl) +{ + int rc; + /* Turn on TX LDOs */ + rc = dw3000_reg_or32(dw, DW3000_LDO_CTRL_ID, 0, + (DW3000_LDO_CTRL_LDO_VDDHVTX_VREF_BIT_MASK | + DW3000_LDO_CTRL_LDO_VDDHVTX_EN_BIT_MASK)); + rc = dw3000_reg_or32(dw, DW3000_LDO_CTRL_ID, 0, + (DW3000_LDO_CTRL_LDO_VDDTX2_VREF_BIT_MASK | + DW3000_LDO_CTRL_LDO_VDDTX1_VREF_BIT_MASK | + DW3000_LDO_CTRL_LDO_VDDTX2_EN_BIT_MASK | + DW3000_LDO_CTRL_LDO_VDDTX1_EN_BIT_MASK)); + /* Enable RF blocks for TX (configure RF_ENABLE_ID register) */ + rc = dw3000_rftx_blocks_enable(dw, chan); + if (rc) + return rc; + if (switch_ctrl) { + /* Configure the TXRX switch for TX mode */ + return dw3000_reg_write32(dw, DW3000_RF_SWITCH_CTRL_ID, 0x0, + DW3000_TXRXSWITCH_TX); + } + return 0; +} + +/** + * dw3000_force_clocks() - Enable/disable clocks to particular digital blocks/system + * @dw: the DW device + * @clocks: set of clocks to enable/disable + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_force_clocks(struct dw3000 *dw, int clocks) +{ + if (clocks == DW3000_FORCE_CLK_SYS_TX) { + /* TX_BUF_CLK = ON & RX_BUF_CLK = ON */ + u16 regvalue0 = DW3000_CLK_CTRL_TX_BUF_CLK_ON_BIT_MASK | + DW3000_CLK_CTRL_RX_BUF_CLK_ON_BIT_MASK; + /* SYS_CLK_SEL = PLL */ + regvalue0 |= (u16)DW3000_FORCE_SYSCLK_PLL + << DW3000_CLK_CTRL_SYS_CLK_SEL_BIT_OFFSET; + /* TX_CLK_SEL = ON */ + regvalue0 |= (u16)DW3000_FORCE_CLK_PLL + << DW3000_CLK_CTRL_TX_CLK_SEL_BIT_OFFSET; + return dw3000_reg_write16(dw, DW3000_CLK_CTRL_ID, 0x0, + regvalue0); + } + if (clocks == DW3000_FORCE_CLK_AUTO) { + /* Restore auto clock mode */ + return dw3000_reg_write16( + dw, DW3000_CLK_CTRL_ID, 0x0, + (u16)(DW3000_CLK_CTRL_FORCE_NVM_CLK_EN_BIT_MASK | + DW3000_CLK_CTRL_RX_BUFF_AUTO_CLK_BIT_MASK | + DW3000_CLK_CTRL_CODE_MEM_AUTO_CLK_BIT_MASK)); + } + return -EINVAL; +} + +/** + * dw3000_setfinegraintxseq() - Enable/disable the fine grain TX sequencing + * @dw: the DW device + * @on: true to enable fine grain TX sequencing, false to disable it. + * + * This is enabled by default in the DW3000. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_setfinegraintxseq(struct dw3000 *dw, bool on) +{ + if (on) { + return dw3000_reg_write32(dw, DW3000_PWR_UP_TIMES_LO_ID, 2, + DW3000_PMSC_TXFINESEQ_ENABLE); + } + return dw3000_reg_write32(dw, DW3000_PWR_UP_TIMES_LO_ID, 2, + DW3000_PMSC_TXFINESEQ_DISABLE); +} + +/** + * dw3000_repeated_cw() - Enable a repeated continuous waveform on the device + * @dw: the DW device + * @cw_enable: CW mode enable + * @cw_mode_config: CW configuration mode. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_repeated_cw(struct dw3000 *dw, int cw_enable, + int cw_mode_config) +{ + int rc; + /* Turn off TX Seq */ + dw3000_setfinegraintxseq(dw, 0); + /* Correct if passing a threshold */ + if (cw_mode_config > 0xF) + cw_mode_config = 0xF; + if (cw_enable > 3 || cw_enable < 1) + cw_enable = 4; + + rc = dw3000_reg_write32(dw, DW3000_TX_TEST_ID, 0x0, 0x10 >> cw_enable); + if (unlikely(rc)) + return rc; + return dw3000_reg_write32(dw, DW3000_PG_TEST_ID, 0x0, + cw_mode_config << ((cw_enable - 1) * 4)); +} + +/** + * dw3000_tx_setcwtone() - Start TX CW signal at specific channel frequency + * @dw: the DW device + * @on: true, enable the test mode and start transmitting a CW signal on the + * device, otherwise disable it and back to normal mode. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_tx_setcwtone(struct dw3000 *dw, bool on) +{ + struct dw3000_txconfig *txconfig = &dw->txconfig; + int rc; + /* Enable test mode */ + if (on) { + u8 chan = dw->config.chan; + + rc = dw3000_enable_rf_tx(dw, chan, 1); + if (unlikely(rc)) + return rc; + rc = dw3000_rftx_blocks_autoseq_disable(dw, chan); + if (unlikely(rc)) + return rc; + rc = dw3000_force_clocks(dw, DW3000_FORCE_CLK_SYS_TX); + if (unlikely(rc)) + return rc; + /* Go to test mode. PulseGen Channel 1, full power. */ + rc = dw3000_repeated_cw(dw, 1, 0xF); + if (unlikely(rc)) + return rc; + txconfig->testmode_enabled = true; + return 0; + } + /* Back to normal mode */ + rc = dw3000_repeated_cw(dw, false, 0); + if (unlikely(rc)) + return rc; + txconfig->testmode_enabled = false; + return 0; +} + +static int dw3000_writetxfctrl(struct dw3000 *dw, u16 txFrameLength, + u16 txBufferOffset, bool ranging) +{ + struct dw3000_local_data *local = &dw->data; + u32 fctrl = + txFrameLength | + ((u32)txBufferOffset << DW3000_TX_FCTRL_TXB_OFFSET_BIT_OFFSET) | + ((u32)ranging << DW3000_TX_FCTRL_TR_BIT_OFFSET); + int rc; + /* + * Compare with the previous value stored if register access + * is necessary. + */ + if (local->tx_fctrl == fctrl) + return 0; + /* Write the frame length to the TX frame control register */ + rc = dw3000_reg_modify32(dw, DW3000_TX_FCTRL_ID, 0, + ~(u32)(DW3000_TX_FCTRL_TXB_OFFSET_BIT_MASK | + DW3000_TX_FCTRL_TR_BIT_MASK | + DW3000_TX_FCTRL_TXFLEN_BIT_MASK), + fctrl); + if (unlikely(rc)) + return rc; + local->tx_fctrl = fctrl; + return 0; +} + +/** dw3000_write_txctrl - Runtime configuration of TX parameters + * @dw: the DW device + * + * This function is called before packet transmission in order to set TX + * parameters (pgdelay, channel, pulse shape) according to the current antenna. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_write_txctrl(struct dw3000 *dw) +{ + struct dw3000_txconfig *txconfig = &dw->txconfig; + struct dw3000_config *config = &dw->config; + u32 txctrl; + + /* Get default values and insert wanted pgdelay */ + txctrl = config->chan == 9 ? DW3000_RF_TXCTRL_CH9 : + DW3000_RF_TXCTRL_CH5; + txctrl = (txctrl & ~DW3000_TX_CTRL_HI_TX_PG_DELAY_BIT_MASK) | + (txconfig->PGdly & DW3000_TX_CTRL_HI_TX_PG_DELAY_BIT_MASK); + + /* Configure pulse shape */ + if (config->alternate_pulse_shape) { + txctrl |= DW3000_TX_CTRL_HI_TX_PULSE_SHAPE_BIT_MASK; + } else { + txctrl &= ~DW3000_TX_CTRL_HI_TX_PULSE_SHAPE_BIT_MASK; + } + + return dw3000_reg_write32(dw, DW3000_TX_CTRL_HI_ID, 0, txctrl); +} + +/** + * dw3000_setrxaftertxdelay() - Set time Wait-for-Response Time + * @dw: the DW device + * @rx_delay_time: Wait-for-Response time in units of approx 1us + * or 128 system clock cycles + * + * It is used to configure the turn-around time between TX complete and RX + * enable when the wait for response function is being used. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_setrxaftertxdelay(struct dw3000 *dw, u32 rx_delay_time) +{ + struct dw3000_local_data *local = &dw->data; + int rc; + + if (unlikely(rx_delay_time > DW3000_ACK_RESP_WAIT4RESP_TIM_BIT_MASK)) + return -EINVAL; + if (local->w4r_time == rx_delay_time) + return 0; + rc = dw3000_reg_write32( + dw, DW3000_ACK_RESP_ID, 0, + local->ack_time << DW3000_ACK_RESP_ACK_TIM_BIT_OFFSET | + rx_delay_time); + if (unlikely(rc)) + return rc; + local->w4r_time = rx_delay_time; + return 0; +} + +/** + * dw3000_tx_frame() - prepare, execute or program TX + * @dw: the DW device + * @skb: TX socket buffer + * @tx_delayed: true if TX delayed must be use + * @tx_date_dtu: the date at which TX must be executed + * @rx_delay_dly: positive if needed to active RX after TX otherwise null + * @rx_timeout_pac: the preamble detect timeout + * @ranging: true if transmitting ranging frame + * + * This function prepares, executes or programs TX according to a given socket + * buffer pointer provided by the MCPS. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_tx_frame(struct dw3000 *dw, struct sk_buff *skb, bool tx_delayed, + u32 tx_date_dtu, int rx_delay_dly, u32 rx_timeout_pac, + bool ranging) +{ + u32 cur_time_dtu = 0; + int rc, len; + u8 cmd; + + /* Print the transmitted frame in hexadecimal characters */ + if (unlikely(DEBUG)) { + if (skb) + print_hex_dump_bytes( + "dw3000: ieee802154: transmitted frame:", + DUMP_PREFIX_NONE, skb->data, skb->len); + else + dev_dbg(dw->dev, + "dw3000: ieee802154: transmitted frame without data"); + } + + /* Read current DTU time to compare with next transfer time */ + if (dw->coex_gpio >= 0) + cur_time_dtu = dw3000_get_dtu_time(dw); + + /* Activate RX after TX ? */ + if (rx_delay_dly >= 0) { + rc = dw3000_setrxaftertxdelay(dw, rx_delay_dly); + if (unlikely(rc)) + return rc; + rc = dw3000_setpreambledetecttimeout(dw, rx_timeout_pac); + if (unlikely(rc)) + return rc; + } + + if (skb) { + /* FCS is already included while pctt is enabled */ + len = skb->len + (dw->pctt.enabled ? 0 : IEEE802154_FCS_LEN); + /* Write frame properties to the transmit frame control register */ + if (WARN_ON(len > dw->data.max_frames_len)) + return -EINVAL; + /* Write frame data to the DW IC buffer */ + if (dw3000_tx_write_data(dw, skb->data, skb->len) != 0) { + dev_err(dw->dev, "cannot write frame data to DW IC\n"); + return -EINVAL; + } + } else + len = 0; + + if (dw->txconfig.smart) + dw3000_adjust_tx_power(dw, len); + + rc = dw3000_writetxfctrl(dw, len, 0, ranging); + if (unlikely(rc)) + return rc; + + rc = dw3000_write_txctrl(dw); + if (unlikely(rc)) + return rc; + + /* Flush SPI queue before dw3000_coex_start() because coex use it + already. */ + rc = dw3000_spi_queue_flush(dw); + if (unlikely(rc)) + return rc; + + /* Update TX parameters according to Wifi coexistence */ + rc = dw3000_coex_start(dw, &tx_delayed, &tx_date_dtu, cur_time_dtu); + if (unlikely(rc)) + return rc; + + if (!tx_delayed) { + /* Program immediate transmission. */ + cmd = rx_delay_dly >= 0 ? DW3000_CMD_TX_W4R : DW3000_CMD_TX; + rc = dw3000_write_fastcmd(dw, cmd); + if (unlikely(rc)) + goto stop_coex; + /* W4R mode are handled by TX event IRQ handler */ + dw3000_power_stats(dw, DW3000_PWR_TX, len); + return 0; + } + + /* Restart SPI queuing mode */ + dw3000_spi_queue_start(dw); + /* Set transmission date. */ + rc = dw3000_setdelayedtrxtime(dw, tx_date_dtu); + if (unlikely(rc)) + goto stop_coex; + /* Program delayed transmission. */ + cmd = rx_delay_dly >= 0 ? DW3000_CMD_DTX_W4R : DW3000_CMD_DTX; + rc = dw3000_write_fastcmd(dw, cmd); + if (unlikely(rc)) + goto stop_coex; + /* Execute SPI queued transfers */ + rc = dw3000_spi_queue_flush(dw); + if (unlikely(rc)) + goto stop_coex; + + /* W4R mode are handled by TX event IRQ handler */ + dw3000_power_stats(dw, DW3000_PWR_TX, len); + /* Check if late */ + rc = dw3000_check_hpdwarn(dw); + if (unlikely(rc)) { + if (rc == -ETIME) { + cur_time_dtu = dw3000_get_dtu_time(dw); + trace_dw3000_mcps_tx_frame_too_late(dw, tx_date_dtu, + cur_time_dtu); + } + goto stop_coex; + } + return dw->chip_ops->check_tx_ok(dw); +stop_coex: + dw3000_coex_stop(dw); + return rc; +} + +/** + * dw3000_do_tx_frame() - handle TX frame MCPS operation + * @dw: the device on which transmit frame + * @config: TX parameters from MCPS + * @skb: the frame to transmit + * @frame_idx: Frame index in a continuous block + * + * This function is called to execute all required operation to transmit the + * given frame using provided parameters. + * + * Since TX may be deferred until device is wokenup, it may be also called a + * second time with same parameters from the wakeup ISR. + * + * This function calls dw3000_check_operational_state() and save and defer + * the operation if DEEP-SLEEP is possible according delay. + * + * If TX frame must be done immediately, this function configure STS, PDOA, + * Antenna pair, and delay for auto-RX before sending SKB by calling + * dw3000_tx_frame(). + * + * Return: 0 on success, else a negative error code. + */ +int dw3000_do_tx_frame(struct dw3000 *dw, + const struct mcps802154_tx_frame_config *config, + struct sk_buff *skb, int frame_idx) +{ + struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state; + struct mcps802154_llhw *llhw = dw->llhw; + u32 cur_time_dtu = 0; + u32 tx_date_dtu = 0; + int rx_delay_dly = -1; + u32 rx_timeout_pac = 0; + bool tx_delayed = true; + bool ranging = false; + int delay_dtu = 0; + bool can_sync = false; + int rc; + u8 sts_mode; + + trace_dw3000_mcps_tx_frame(dw, config->flags, skb ? skb->len : 0); + + /* Calculate the transfer date.*/ + if (config->flags & MCPS802154_TX_FRAME_CONFIG_TIMESTAMP_DTU) { + tx_date_dtu = config->timestamp_dtu + llhw->shr_dtu; + } else { + /* Send immediately. */ + tx_delayed = false; + } + if (config->flags & MCPS802154_TX_FRAME_CONFIG_RANGING) + ranging = true; + + if (tx_delayed) { + cur_time_dtu = dw3000_get_dtu_time(dw); + delay_dtu = (int)(tx_date_dtu - cur_time_dtu); + if (delay_dtu < 0) { + dev_err(dw->dev, + "too late to program delayed tx date_dtu=%x current_dtu=%x\n", + tx_date_dtu, cur_time_dtu); + return -ETIME; + } + } + + /* We are always allowed to sleep & sync at the beginning of a block. */ + if (!frame_idx) { + dw->need_ranging_clock = false; + can_sync = true; + } + + /* For delayed TX, where delay_dtu != 0, enter/leave deep sleep */ + rc = dw3000_check_operational_state(dw, delay_dtu, can_sync); + if (rc) { + /* Handle error cases first */ + if (rc < 0) + return rc; + /* Save parameters to activate TX delayed when + wakeup later */ + dw->wakeup_done_cb = dw3000_wakeup_done_to_tx; + dss->next_operational_state = DW3000_OP_STATE_TX; + dss->tx_config = *config; + dss->tx_skb = skb; + dss->frame_idx = frame_idx; + return 0; + } + /* All operation below require the DW chip is in IDLE_PLL state */ + + /* Start SPI queuing mode to minimise number of SPI messages + Queue will be flushed by dw3000_tx_frame() itself. */ + dw3000_spi_queue_start(dw); + + /* Oscillate XTAL around calibrated value to maximise successful PDoA probability */ + if (DW3000_XTAL_BIAS && + (config->flags & MCPS802154_TX_FRAME_CONFIG_RANGING_ROUND)) { + dw->data.xtal_bias = (dw->data.xtal_bias > 0 ? + -DW3000_XTAL_BIAS : + DW3000_XTAL_BIAS); + rc = dw3000_prog_xtrim(dw); + if (unlikely(rc)) + goto fail; + } + /* Enable STS */ + sts_mode = FIELD_GET(MCPS802154_TX_FRAME_CONFIG_STS_MODE_MASK, + config->flags); + rc = dw3000_set_sts_pdoa( + dw, sts_mode, + sts_to_pdoa(sts_mode, + config->flags & + MCPS802154_TX_FRAME_CONFIG_RANGING_PDOA)); + if (unlikely(rc)) + goto fail; + /* Ensure correct TX antenna is selected. */ + rc = dw3000_set_tx_antenna(dw, config->ant_set_id); + if (unlikely(rc)) + goto fail; + + if (config->rx_enable_after_tx_dtu > 0) { + /* Disable auto-ack if it was previously enabled. */ + dw3000_disable_autoack(dw, false); + /* Calculate the after tx rx delay. */ + rx_delay_dly = + dtu_to_dly(llhw, config->rx_enable_after_tx_dtu) - + DW3000_RX_ENABLE_STARTUP_DLY; + rx_delay_dly = rx_delay_dly >= 0 ? rx_delay_dly : 0; + /* Calculate the after tx rx timeout. */ + if (config->rx_enable_after_tx_timeout_dtu == 0) { + rx_timeout_pac = dw->pre_timeout_pac; + } else if (config->rx_enable_after_tx_timeout_dtu != -1) { + rx_timeout_pac = + dw->pre_timeout_pac + + dtu_to_pac( + llhw, + config->rx_enable_after_tx_timeout_dtu); + } else { + /* No timeout. */ + } + } + rc = dw3000_tx_frame(dw, skb, tx_delayed, tx_date_dtu, rx_delay_dly, + rx_timeout_pac, ranging); + if (unlikely(rc)) + goto fail; + + /* Store ranging clock requirement for next operation */ + dw->need_ranging_clock = + (config->flags & + MCPS802154_TX_FRAME_CONFIG_KEEP_RANGING_CLOCK) != 0; +fail: + if (rc) + /* Ensure SPI queuing mode disabled on error */ + dw3000_spi_queue_reset(dw, rc); + trace_dw3000_return_int(dw, rc); + return rc; +} + +static int dw3000_rx_read_data(struct dw3000 *dw, u8 *buffer, u16 len, + u16 offset) +{ + u32 rx_buff_addr; + + /* If the flag is 0x4 we are reading from RX_BUFFER_B */ + if (dw->data.dblbuffon == DW3000_DBL_BUFF_ACCESS_BUFFER_B) { + rx_buff_addr = DW3000_RX_BUFFER_B_ID; + /* Reading from RX_BUFFER_A - also when non-double buffer mode */ + } else { + rx_buff_addr = DW3000_RX_BUFFER_A_ID; + } + if ((offset + len) > DW3000_RX_BUFFER_MAX_LEN) + return -EINVAL; + if (offset <= DW3000_REG_DIRECT_OFFSET_MAX_LEN) { + /* Directly read data from the IC to the buffer */ + return dw3000_xfer(dw, rx_buff_addr, offset, len, buffer, + DW3000_SPI_RD_BIT); + } else { + /* Program the indirect offset registers B + * for specified offset to RX buffer + */ + dw3000_reg_write32(dw, DW3000_INDIRECT_ADDR_A_ID, 0, + (rx_buff_addr >> 16)); + dw3000_reg_write32(dw, DW3000_ADDR_OFFSET_A_ID, 0, offset); + + /* Indirectly read data from the IC to the buffer */ + return dw3000_xfer(dw, DW3000_INDIRECT_POINTER_A_ID, 0, len, + buffer, DW3000_SPI_RD_BIT); + } +} + +static int dw3000_rx_frame(struct dw3000 *dw, + const struct dw3000_isr_data *data) +{ + struct dw3000_rx *rx = &dw->rx; + size_t len = data->datalength; + struct sk_buff *skb; + unsigned long flags; + u8 *buffer; + int rc; + + /* If rx_disable() callback was called, we can't call mcps802154_rx_frame() */ + if (dw3000_rx_busy(dw, true)) + return 0; + /* Read frame data into skb */ + if (len) { + /* Allocate new skb (including space for FCS added by ieee802154_rx) */ + skb = dev_alloc_skb(len + IEEE802154_FCS_LEN); + if (!skb) { + dev_err(dw->dev, "RX buffer allocation failed\n"); + rc = -ENOMEM; + goto err_spi; + } + if (dw->pctt.enabled) + len += IEEE802154_FCS_LEN; + buffer = skb_put(skb, len); + /* Directly read data from the IC to the buffer */ + rc = dw3000_rx_read_data(dw, buffer, len, 0); + if (rc) + goto err_spi; + } else { + /* SP3 case: Frame with STS and without data */ + skb = NULL; + } + /* Store received frame */ + spin_lock_irqsave(&rx->lock, flags); + WARN_ON(rx->skb); + rx->skb = skb; + rx->flags = data->rx_flags | (skb ? 0 : DW3000_RX_FLAG_ND); + rx->ts_rctu = data->ts_rctu; + spin_unlock_irqrestore(&rx->lock, flags); + /* Print the received frame in hexadecimal characters */ + if (unlikely(DEBUG)) { + dev_dbg(dw->dev, "frame config: len=%lu, rxflags=0x%.2x", len, + data->rx_flags); + if (skb) + print_hex_dump_bytes("dw3000: frame data: ", + DUMP_PREFIX_NONE, skb->data, len); + } + /* Inform MCPS 802.15.4 that we received a frame */ + mcps802154_rx_frame(dw->llhw); + WARN_ON_ONCE(dw3000_rx_busy(dw, false)); + return 0; + +err_spi: + /* Release the socker buffer */ + if (skb) + dev_kfree_skb_any(skb); + WARN_ON_ONCE(dw3000_rx_busy(dw, false)); + return rc; +} + +/** + * dw3000_set_interrupt() - Select the interruption's events to mask or unmask + * @dw: the DW device + * @bitmask: bitmap of selected events + * @opt: + * - DW3000_DISABLE_INT: unmask the selected events + * - DW3000_ENABLE_INT: mask the selected events + * - DW3000_ENABLE_INT_ONLY: mask the selected events and unmask the rest + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_set_interrupt(struct dw3000 *dw, u32 bitmask, + const enum int_options opt) +{ + if (opt == DW3000_ENABLE_INT_ONLY) { + /* Overriding */ + return dw3000_reg_write32(dw, DW3000_SYS_ENABLE_LO_ID, 0, + bitmask); + } else { + if (opt == DW3000_ENABLE_INT) { + /* Set the bits */ + return dw3000_reg_or32(dw, DW3000_SYS_ENABLE_LO_ID, 0, + bitmask); + } else { + /* Clear the bits */ + return dw3000_reg_and32(dw, DW3000_SYS_ENABLE_LO_ID, 0, + (u32)(~bitmask)); + } + } +} + +/** + * dw3000_setplenfine() - Configure frame preamble length + * @dw: the DW device + * @preamble_len: length of the preamble + * + * A preamble_len value of 0 disables this setting and the length of the frame + * will be dependent on the TXPSR_PE setting as configured + * by dw3000_configure() function. + * The frame premable length can be configured in steps of 8, from 16 + * to 2048 symbols. If a non-zero value is configured, then the TXPSR_PE + * setting is ignored. + * + * Return: zero on success, else a negative error code. + */ +static inline int dw3000_setplenfine(struct dw3000 *dw, u8 preamble_len) +{ + return dw3000_reg_write8(dw, DW3000_TX_FCTRL_HI_ID, 1, preamble_len); +} + +/** + * _dw3000_get_sts_mnth() - Calculate the adjusted STS minimum threshold + * @cipher: the STS length's factor + * @threshold: the STS default threshold + * @shift_val: shift value for adjustment + * + * Return: the value of the adjusted STS minimum threshold. + */ +static u16 _dw3000_get_sts_mnth(u16 cipher, u8 threshold, u8 shift_val) +{ + u32 value = cipher * (u32)threshold; + + if (shift_val == 3) { + /* Factor to sqrt(2) */ + value *= DW3000_SQRT2_FACTOR; + value >>= DW3000_SQRT2_SHIFT_VAL; + } + /* Round the result of the shift by 11 (or division by 2048) */ + return (u16)((value + DW3000_STS_MNTH_ROUND_SHIFT) >> + DW3000_STS_MNTH_SHIFT); +} + +/** + * _dw3000_sts_is_enabled() - Return true if STS is enabled + * @dw: the DW device + * + * Return: true is STS is enable. + */ +static inline bool _dw3000_sts_is_enabled(struct dw3000 *dw) +{ + return (dw->config.stsMode & DW3000_STS_BASIC_MODES_MASK) != + DW3000_STS_MODE_OFF; +} + +/** + * dw3000_configure_otp() - set device's OTP configuration + * @dw: the DW device + * @config: the DW device's configuration + * + * This function depend on stsLength if STS is enabled, so require + * stsLength is set before stsMode is updated. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_configure_otp(struct dw3000 *dw, struct dw3000_config *config) +{ + u16 preamble_len = _plen_info[config->txPreambLength - 1].symb; + /* Update the preamble length regarding STS mode */ + if (_dw3000_sts_is_enabled(dw)) + preamble_len += + DW3000_GET_STS_LEN_UNIT_VALUE(config->stsLength) * 8; + /* Configure gearing tables for non-SCP mode */ + if (preamble_len >= 256) { + dw->data.sleep_mode |= DW3000_ALT_GEAR | DW3000_SEL_GEAR0; + return dw3000_reg_modify32( + dw, DW3000_NVM_CFG_ID, 0, + ~(DW3000_NVM_CFG_GEAR_ID_BIT_MASK), + DW3000_OPSET_LONG | DW3000_NVM_CFG_GEAR_KICK_BIT_MASK); + } else { + return dw3000_reg_modify32( + dw, DW3000_NVM_CFG_ID, 0, + ~(DW3000_NVM_CFG_GEAR_ID_BIT_MASK), + DW3000_OPSET_SHORT | DW3000_NVM_CFG_GEAR_KICK_BIT_MASK); + } +} + +/** + * dw3000_configure_sts() - set device's STS configuration + * @dw: the DW device + * @config: the DW device's configuration + * + * STS Minimum Threshold `sts_mnth` needs to be adjusted with changing + * STS length. To adjust the `sts_mnth` following formula can be used: + * + * sts_mnth = sqrt(x/y)*default_sts_mnth + * + * where + * - `default_sts_mnth` is 0x10 + * - `x` is the length of the STS in units of 8 (i.e. 8 for 64 length, + * 16 for 128 length etc..) + * - `y` is either 8 or 16, 8 when no PDOA or PDOA mode 1 and 16 + * for PDOA mode 3. + * + * The API does not use the formula and the `sts_mnth` value is derived + * from approximation formula as given by _dw3000_get_sts_mnth() function. + * The API here supports STS lengths as listed in `dw3000_sts_lengths enum`, + * which are 32, 64, 128, 256, 512, 1024 and 2048. + * The enum value is used as the index into`dw3000_sts_length_factors` array. + * The array has values which are generated by: + * + * val = sqrt(stsLength/16)*2048 + * + * where + * - `stsLength` value of the given `dw3000_sts_lengths enum` + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_configure_sts(struct dw3000 *dw, struct dw3000_config *config) +{ + u16 sts_mnth; + + if (!_dw3000_sts_is_enabled(dw)) + return 0; + /* Update default minimum STS threshold according STS length. */ + dw->data.ststhreshold = (s16)DW3000_STSQUAL_THRESH_64( + (u32)(DW3000_GET_STS_LEN_UNIT_VALUE(config->stsLength))); + /* Configure CIA STS lower bound */ + if ((config->pdoaMode == DW3000_PDOA_M1) || + (config->pdoaMode == DW3000_PDOA_M0)) { + /** + * In PDOA mode 1, number of accumulated symbols + * is the whole length of the STS + */ + sts_mnth = _dw3000_get_sts_mnth( + dw3000_sts_length_factors[(u8)(config->stsLength)], + DW3000_CIA_MANUALLOWERBOUND_TH_64, 3); + } else { + /** + * In PDOA mode 3 number of accumulated symbols + * is half of the length of STS symbols + */ + sts_mnth = _dw3000_get_sts_mnth( + dw3000_sts_length_factors[(u8)(config->stsLength)], + DW3000_CIA_MANUALLOWERBOUND_TH_64, 4); + } + /* TODO: put register value in cache */ + return dw3000_reg_modify16( + dw, DW3000_CY_CONFIG_LO_ID, 2, + (u16) ~(DW3000_CY_CONFIG_LO_MANUALLOWERBOUND_BIT_MASK >> 16), + sts_mnth & 0x7F); +} + +/** + * _swap128() - swap bytes of a 128 bits unaligned buffer + * @dst: the in-stack 64 bits aligned 128 bits destination buffer + * @src: the 128 bits buffer with MSB first to swap + */ +static inline void _swap128(__le64 *dst, const void *src) +{ + u64 val = get_unaligned_be64(src); + dst[1] = cpu_to_le64(val); + val = get_unaligned_be64(src + sizeof(u64)); + dst[0] = cpu_to_le64(val); +} + +/** + * dw3000_configure_sts_key() - set device's STS KEY + * @dw: the DW device + * @key: the 128 bits STS KEY to configure (MSB first) + * + * Return: zero on success, else a negative error code. + */ +int dw3000_configure_sts_key(struct dw3000 *dw, const u8 *key) +{ + static const u8 nul_key[AES_KEYSIZE_128] = { + 0, + }; + /* TODO: Transfer the STS key securely to DW3000 using the AES engine. + * See 5.8.4.3 Decrypt STS KEY into STS KEY registers. */ + struct dw3000_local_data *data = &dw->data; + struct dw3000_config *config = &dw->config; + bool changed; + bool is_nul_key; + int rc; + /* Check NUL key */ + is_nul_key = memcmp(key, nul_key, AES_KEYSIZE_128) == 0; + if (is_nul_key) { + /* DW3000 specific. Use Super Deterministic Code if NUL key is + set. Check if change required. */ + changed = (config->stsMode & DW3000_STS_MODE_SDC) == 0; + /* No need to send key. */ + } else { + bool kc = true; + /* Need to remove SDC flag. Check if change is required. */ + changed = (config->stsMode & DW3000_STS_MODE_SDC) != 0; + /* Update Key. */ + if (!changed) + kc = memcmp(key, data->sts_key, AES_KEYSIZE_128) != 0; + if (kc) { + __le64 swapped_key[AES_KEYSIZE_128 / sizeof(__le64)]; + _swap128(swapped_key, key); + /* Key has changed, update it. */ + rc = _dw3000_reg_write(dw, DW3000_STS_KEY_ID, 0, + AES_KEYSIZE_128, swapped_key); + if (rc) + return rc; + /* Update cached key. */ + memcpy(data->sts_key, key, AES_KEYSIZE_128); + } + } + if (changed) { + /* SDC bit had changed. Sync register. */ + rc = dw3000_reg_modify8( + dw, DW3000_SYS_CFG_ID, 1, + (u8)(~DW3000_SYS_CFG_CP_SDC_BIT_MASK >> 8), + is_nul_key ? (DW3000_SYS_CFG_CP_SDC_BIT_MASK >> 8) : 0); + if (rc) + return rc; + /* Finally, save configured STS mode. */ + if (is_nul_key) + config->stsMode |= DW3000_STS_MODE_SDC; + else + config->stsMode &= ~DW3000_STS_MODE_SDC; + } + return 0; +} + +/** + * dw3000_configure_sts_iv() - set device's STS IV + * @dw: the DW device + * @iv: the 128 bits STS IV to configure (MSB first) + * + * The four least significant bytes are always sent. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_configure_sts_iv(struct dw3000 *dw, const u8 *iv) +{ + struct dw3000_local_data *data = &dw->data; + __le64 swapped_iv[AES_BLOCK_SIZE / sizeof(__le64)]; + bool changed; + int rc; + /* Check if IV MSB had changed. */ + changed = memcmp(iv, data->sts_iv, AES_BLOCK_SIZE - sizeof(u32)) != 0; + /* Convert to Little-endian. */ + _swap128(swapped_iv, iv); + /* Update it (Only reset counter in LSB if unchanged). */ + rc = _dw3000_reg_write(dw, DW3000_STS_IV_ID, 0, + changed ? AES_BLOCK_SIZE : sizeof(u32), + swapped_iv); + if (rc) + return rc; + /* Update cached IV. */ + memcpy(data->sts_iv, iv, AES_KEYSIZE_128); + return 0; +} + +/** + * dw3000_load_sts_iv() - force device's STS IV to be loaded + * @dw: the DW device + * + * Return: zero on success, else a negative error code. + */ +int dw3000_load_sts_iv(struct dw3000 *dw) +{ + return dw3000_reg_or8(dw, DW3000_STS_CTRL_ID, 0, + DW3000_STS_CTRL_LOAD_IV_BIT_MASK); +} + +/** + * _dw3000_ciadiag_update_reg_select() - Update CIA diagnostic register selector + * @dw: the DW device + * + * According to DW3000's configuration, we must read some values + * (e.g: channel impulse response power, preamble accumulation count) + * in different registers in the CIA interface. It on depending the STS + * and PDOA configurations. + */ +static void _dw3000_ciadiag_update_reg_select(struct dw3000 *dw) +{ + struct dw3000_config *config = &dw->config; + struct dw3000_local_data *data = &dw->data; + + if (config->pdoaMode == DW3000_PDOA_M3) { + data->ciadiag_reg_select = + DW3000_CIA_DIAG_REG_SELECT_WITH_PDAO_M3; + } else { + if (_dw3000_sts_is_enabled(dw)) + data->ciadiag_reg_select = + DW3000_CIA_DIAG_REG_SELECT_WITH_STS; + else + data->ciadiag_reg_select = + DW3000_CIA_DIAG_REG_SELECT_WITHOUT_STS; + } +} + +int dw3000_configure_sys_cfg(struct dw3000 *dw, struct dw3000_config *config) +{ + u8 mode = (config->phrMode == DW3000_PHRMODE_EXT) ? + DW3000_SYS_CFG_PHR_MODE_BIT_MASK : + 0; + u8 dw_pac_reg = _plen_info[config->txPreambLength - 1].dw_pac_reg; + int rc; + /* + * SYS_CFG : + * - Clear the PHR Mode, PHR Rate, STS Protocol, SDC, PDOA Mode, + * - Set the relevant bits according to configuration of the PHR Mode, + * PHR Rate, STS Protocol, SDC, PDOA Mode + */ + rc = dw3000_reg_modify32( + dw, DW3000_SYS_CFG_ID, 0, + ~(u32)(DW3000_SYS_CFG_PHR_MODE_BIT_MASK | + DW3000_SYS_CFG_PHR_6M8_BIT_MASK | + DW3000_SYS_CFG_CP_PROTOCOL_BIT_MASK | + DW3000_SYS_CFG_PDOA_MODE_BIT_MASK | + DW3000_SYS_CFG_CP_SDC_BIT_MASK), + (((u32)config->pdoaMode) + << DW3000_SYS_CFG_PDOA_MODE_BIT_OFFSET) | + (((u16)config->stsMode & DW3000_STS_CONFIG_MASK) + << DW3000_SYS_CFG_CP_PROTOCOL_BIT_OFFSET) | + (DW3000_SYS_CFG_PHR_6M8_BIT_MASK & + ((u32)config->phrRate + << DW3000_SYS_CFG_PHR_6M8_BIT_OFFSET)) | + mode); + if (rc) + return rc; + /* Update CIA diagnostic register selection */ + _dw3000_ciadiag_update_reg_select(dw); + /* Configure OTP */ + rc = dw3000_configure_otp(dw, config); + if (rc) + return rc; + /* Configure PAC */ + if (config->pdoaMode == DW3000_PDOA_M1) { + /* Disable STS CMF, and configure PAC size */ + rc = dw3000_reg_modify8( + dw, DW3000_DRX_TUNE0_ID, 0, + (u8) ~(DW3000_DRX_TUNE0_PRE_PAC_SYM_BIT_MASK | + DW3000_DRX_TUNE0_DT0B4_BIT_MASK), + dw_pac_reg); + } else { + /* Enable STS CMF, and configure PAC size */ + rc = dw3000_reg_modify8( + dw, DW3000_DRX_TUNE0_ID, 0, + (u8)~DW3000_DRX_TUNE0_PRE_PAC_SYM_BIT_MASK, + dw_pac_reg | DW3000_DRX_TUNE0_DT0B4_BIT_MASK); + } + if (rc) + return rc; + if (config->txPreambLength == DW3000_PLEN_72) { + /** + * Value 9 sets fine premable length to 72 symbols + * This is needed to set 72 length. + */ + rc = dw3000_setplenfine(dw, 9); + if (rc) + return rc; + } else { + /* Clear the setting in the FINE_PLEN register. */ + rc = dw3000_setplenfine(dw, 0); + if (rc) + return rc; + } + if ((config->stsMode & DW3000_STS_MODE_ND) == DW3000_STS_MODE_ND) { + /** + * Configure lower preamble detection threshold for no data + * STS mode. + */ + return dw3000_reg_write32(dw, DW3000_DRX_TUNE3_ID, 0, + DW3000_PD_THRESH_NO_DATA); + } else { + /** + * Configure default preamble detection threshold for other + * modes. + */ + return dw3000_reg_write32(dw, DW3000_DRX_TUNE3_ID, 0, + DW3000_PD_THRESH_DEFAULT); + } +} + +/** + * dw3000_configure_chan_ctrl() - configure the channel control register + * @dw: the DW device + * @config: the DW device's configuration + * + * Set the DW3000_CHAN_CTRL_ID register. + * + * Return: zero on success, else a negative error code. + */ +static inline int dw3000_configure_chan_ctrl(struct dw3000 *dw, + struct dw3000_config *config) +{ + u8 chan = config->chan; + u32 temp; + int rc; + + /* Adjust configuration according channel/txCode and calib_data */ + dw3000_calib_update_config(dw); + + /* Read current CHAN_CTRL register value*/ + rc = dw3000_reg_read32(dw, DW3000_CHAN_CTRL_ID, 0, &temp); + if (rc) + return rc; + /* Change value */ + temp &= (~(DW3000_CHAN_CTRL_RX_PCODE_BIT_MASK | + DW3000_CHAN_CTRL_TX_PCODE_BIT_MASK | + DW3000_CHAN_CTRL_SFD_TYPE_BIT_MASK | + DW3000_CHAN_CTRL_RF_CHAN_BIT_MASK)); + if (chan == 9) + temp |= DW3000_CHAN_CTRL_RF_CHAN_BIT_MASK; + temp |= (DW3000_CHAN_CTRL_RX_PCODE_BIT_MASK & + ((u32)config->rxCode << DW3000_CHAN_CTRL_RX_PCODE_BIT_OFFSET)); + temp |= (DW3000_CHAN_CTRL_TX_PCODE_BIT_MASK & + ((u32)config->txCode << DW3000_CHAN_CTRL_TX_PCODE_BIT_OFFSET)); + temp |= (DW3000_CHAN_CTRL_SFD_TYPE_BIT_MASK & + ((u32)config->sfdType + << DW3000_CHAN_CTRL_SFD_TYPE_BIT_OFFSET)); + /* Reprogram new value */ + return dw3000_reg_write32(dw, DW3000_CHAN_CTRL_ID, 0, temp); +} + +/** + * dw3000_configure_rf() - configure the device's radio frequency + * @dw: the DW device + * + * According to the channel (ex: 5 or 9), it sets the device's configuration + * of the radio frequency. + * + * Return: zero on success, else a negative error code. + */ +static inline int dw3000_configure_rf(struct dw3000 *dw) +{ + struct dw3000_config *config = &dw->config; + struct dw3000_txconfig *txconfig = &dw->txconfig; + u8 chan = config->chan; + u32 rf_pll_cfg; + int rc; + /* Get default values */ + if (chan == 9) { + rf_pll_cfg = DW3000_RF_PLL_CFG_CH9; + } else { + rf_pll_cfg = DW3000_RF_PLL_CFG_CH5; + } + rc = dw3000_write_txctrl(dw); + if (rc) + return rc; + + rc = dw3000_reg_write16(dw, DW3000_PLL_CFG_ID, 0, rf_pll_cfg); + if (rc) + return rc; + + rc = dw3000_reg_write16(dw, DW3000_PLL_COMMON_ID, 0, DW3000_RF_PLL_COMMON); + if (rc) + return rc; + + rc = dw3000_reg_write8(dw, DW3000_LDO_RLOAD_ID, 1, + DW3000_LDO_RLOAD_VAL_B1); + if (rc) + return rc; + rc = dw3000_reg_write8(dw, DW3000_TX_CTRL_LO_ID, 2, + DW3000_RF_TXCTRL_LO_B2); + if (rc) + return rc; + /* Setup TX power */ + rc = dw3000_reg_write32(dw, DW3000_TX_POWER_ID, 0, txconfig->power); + if (unlikely(rc)) + return rc; + /* Extend the lock delay */ + rc = dw3000_reg_write8(dw, DW3000_PLL_CAL_ID, 0, DW3000_RF_PLL_CFG_LD); + if (unlikely(rc)) + return rc; + + /* Configure PLL coarse code, if needed. */ + if (dw->chip_ops->pll_coarse_code) { + rc = dw->chip_ops->pll_coarse_code(dw); + if (rc) + return rc; + } + return 0; +} + +static int dw3000_configmrxlut(struct dw3000 *dw) +{ + struct dw3000_config *config = &dw->config; + u8 chan = config->chan; + const u32 *lut; + int rc; + + if (!dw->chip_ops->set_mrxlut || !dw->chip_ops->get_config_mrxlut_chan) + return 0; + + lut = dw->chip_ops->get_config_mrxlut_chan(dw, chan); + if (!lut) + return -EINVAL; + + rc = dw->chip_ops->set_mrxlut(dw, lut); + if (unlikely(rc)) { + dev_err(dw->dev, "mrxlut configuration has failed (%d)\n", rc); + } + + return rc; +} + +int dw3000_configure_dgc(struct dw3000 *dw) +{ + struct dw3000_config *config = &dw->config; + /* Only enable DGC for PRF 64. */ + if ((config->rxCode >= 9) && (config->rxCode <= 24)) { + struct dw3000_local_data *local = &dw->data; + int rc; + /* Load RX LUTs */ + if (local->dgc_otp_set) { + /* If the OTP has DGC info programmed into it, do a + * manual kick from OTP. */ + u16 dgc_sel = (config->chan == 5 ? + 0 : + DW3000_NVM_CFG_DGC_SEL_BIT_MASK); + rc = dw3000_reg_modify16( + dw, DW3000_NVM_CFG_ID, 0, + (u16) ~(DW3000_NVM_CFG_DGC_SEL_BIT_MASK), + dgc_sel | DW3000_NVM_CFG_DGC_KICK_BIT_MASK); + if (rc) + return rc; + /* Configure kick bits for when waking up. */ + local->sleep_mode |= DW3000_LOADDGC; + } else { + /* Else we manually program hard-coded values into the + * DGC registers. */ + rc = dw3000_configmrxlut(dw); + if (rc) + return rc; + local->sleep_mode &= ~DW3000_LOADDGC; + } + return dw3000_reg_modify16( + dw, DW3000_DGC_CFG_ID, 0x0, + (u16)~DW3000_DGC_CFG_THR_64_BIT_MASK, + DW3000_DGC_CFG << DW3000_DGC_CFG_THR_64_BIT_OFFSET); + } else { + return dw3000_reg_and8(dw, DW3000_DGC_CFG_ID, 0x0, + (u8)~DW3000_DGC_CFG_RX_TUNE_EN_BIT_MASK); + } +} + +static int dw3000_restore_dgc(struct dw3000 *dw) +{ + struct dw3000_config *config = &dw->config; + int rc = 0; + + /* Restore OPS table configuration */ + if (dw->chip_ops->kick_ops_table_on_wakeup) { + rc = dw->chip_ops->kick_ops_table_on_wakeup(dw); + if (rc) + return rc; + } + + /* Only enable DGC for PRF 64. */ + if ((config->rxCode >= 9) && (config->rxCode <= 24)) { + struct dw3000_local_data *local = &dw->data; + /* Load RX LUTs only if not already loaded from OTP */ + if (!local->dgc_otp_set) + rc = dw3000_configmrxlut(dw); + else if (dw->chip_ops->kick_dgc_on_wakeup) { + /* + * If the OTP has DGC info programmed into it, + * do a manual kick from OTP + */ + rc = dw->chip_ops->kick_dgc_on_wakeup(dw); + if (rc) + return rc; + } + } + return rc; +} + +/** + * dw3000_configure_chan() - configure the device's RF channel + * @dw: the DW device + * + * Return: zero on success, else a negative error code. + */ +int dw3000_configure_chan(struct dw3000 *dw) +{ + struct dw3000_config *config = &dw->config; + int rc; + /* Configure the CHAN_CTRL register */ + rc = dw3000_configure_chan_ctrl(dw, config); + if (rc) + return rc; + /* Set TX/RX analogs for given channel */ + rc = dw3000_configure_rf(dw); + if (rc) + return rc; + /* Disable AGC */ + dw3000_reg_modify32(dw, DW3000_AGC_CFG_ID, 0, DW3000_AGC_DIS_MASK, 0); + /* Configure DGC. */ + return dw3000_configure_dgc(dw); +} + +/** + * dw3000_configure_pcode() - change the device's RF preamble code + * @dw: the DW device + * + * Return: zero on success, else a negative error code. + */ +int dw3000_configure_pcode(struct dw3000 *dw) +{ + /* Just reconfigure the CHAN_CTRL register */ + return dw3000_configure_chan_ctrl(dw, &dw->config); +} + +/** + * dw3000_configure_sfd_type() - change the device's RF SFD type + * @dw: the DW device + * + * Return: zero on success, else a negative error code. + */ +int dw3000_configure_sfd_type(struct dw3000 *dw) +{ + /* Just reconfigure the CHAN_CTRL register */ + return dw3000_configure_chan_ctrl(dw, &dw->config); +} + +/** + * dw3000_configure_phr_rate() - change the device's phr rate + * @dw: the DW device + * + * Return: zero on success, else a negative error code. + */ +int dw3000_configure_phr_rate(struct dw3000 *dw) +{ + struct dw3000_config *config = &dw->config; + u8 mode = (config->phrMode == DW3000_PHRMODE_EXT) ? + DW3000_SYS_CFG_PHR_MODE_BIT_MASK : + 0; + + return dw3000_reg_modify32( + dw, DW3000_SYS_CFG_ID, 0, + ~(u32)(DW3000_SYS_CFG_PHR_MODE_BIT_MASK | + DW3000_SYS_CFG_PHR_6M8_BIT_MASK | + DW3000_SYS_CFG_CP_PROTOCOL_BIT_MASK | + DW3000_SYS_CFG_PDOA_MODE_BIT_MASK | + DW3000_SYS_CFG_CP_SDC_BIT_MASK), + (((u32)config->pdoaMode) + << DW3000_SYS_CFG_PDOA_MODE_BIT_OFFSET) | + (((u16)config->stsMode & DW3000_STS_CONFIG_MASK) + << DW3000_SYS_CFG_CP_PROTOCOL_BIT_OFFSET) | + (DW3000_SYS_CFG_PHR_6M8_BIT_MASK & + ((u32)config->phrRate + << DW3000_SYS_CFG_PHR_6M8_BIT_OFFSET)) | + mode); +} + +/** + * dw3000_configure_preamble_length_and_datarate() - change the device's RF preamble length (PSR) and data rate + * @dw: the DW device + * @update_datarate_only: if true, update only the datarate + * + * Return: zero on success, else a negative error code. + */ +int dw3000_configure_preamble_length_and_datarate(struct dw3000 *dw, + bool update_datarate_only) +{ + struct dw3000_config *config = &dw->config; + int rc; + + /* Setup TX preamble size, and data rate */ + rc = dw3000_reg_modify32( + dw, DW3000_TX_FCTRL_ID, 0, + ~(DW3000_TX_FCTRL_TXBR_BIT_MASK | + DW3000_TX_FCTRL_TXPSR_PE_BIT_MASK), + ((u32)config->dataRate << DW3000_TX_FCTRL_TXBR_BIT_OFFSET) | + ((u32)config->txPreambLength) + << DW3000_TX_FCTRL_TXPSR_PE_BIT_OFFSET); + if (rc) + return rc; + + if (!update_datarate_only) { + int symb = _plen_info[config->txPreambLength - 1].symb; + int pac_symb = _plen_info[config->txPreambLength - 1].pac_symb; + u8 dw_pac_reg = + _plen_info[config->txPreambLength - 1].dw_pac_reg; + int sfd_symb = dw3000_get_sfd_symb(dw); + + /** + * DTUNE (SFD timeout): + * SFD timeout = PLEN + 1 + sfd len - pac size + */ + config->sfdTO = symb + 1 + sfd_symb - pac_symb; + rc = dw3000_reg_write16(dw, DW3000_DRX_SFDTOC_ID, 0, + config->sfdTO); + if (rc) + return rc; + /* Reconfigure PAC */ + rc = dw3000_reg_modify8( + dw, DW3000_DRX_TUNE0_ID, 0, + (u8)~DW3000_DRX_TUNE0_PRE_PAC_SYM_BIT_MASK, dw_pac_reg); + if (rc) + return rc; + + if (config->txPreambLength == DW3000_PLEN_72) { + /** + * Value 9 sets fine premable length to 72 symbols + * This is needed to set 72 length. + */ + rc = dw3000_setplenfine(dw, 9); + if (rc) + return rc; + } else { + /* Clear the setting in the FINE_PLEN register. */ + rc = dw3000_setplenfine(dw, 0); + if (rc) + return rc; + } + if (symb > 64) + rc = dw3000_reg_write8(dw, DW3000_DTUNE4_ID, 0x3, + DW3000_RX_SFD_HLDOFF); + else /* set default value for <= 64 */ + rc = dw3000_reg_write8(dw, DW3000_DTUNE4_ID, 0x3, + DW3000_RX_SFD_HLDOFF_DEF); + if (rc) + return rc; + } + return rc; +} + +static int dw3000_setdwstate(struct dw3000 *dw, enum operational_state state) +{ + int rc; + if (state == dw->current_operational_state) + return 0; + + switch (state) { + case DW3000_OP_STATE_DEEP_SLEEP: + /* Disable IRQ to ensure no IRQ storm in case pull-down is too weak */ + disable_irq(dw->spi->irq); + + /* Clear SPIRDY and RCINIT interrupts to avoid spurious SPIRDY and + * RCINIT interrupts (at startup, for example) that will trigger the + * wake up process of the chip too early. + */ + rc = dw3000_clear_sys_status( + dw, DW3000_SYS_STATUS_SPIRDY_BIT_MASK | + DW3000_SYS_STATUS_RCINIT_BIT_MASK); + if (rc) + return rc; + /* Enable the RCINIT interrupt for wake up. */ + rc = dw3000_reg_or8(dw, DW3000_SYS_ENABLE_LO_ID, 3, + DW3000_SYS_STATUS_RCINIT_BIT_MASK >> 24); + if (rc) + return rc; + /* Store the current DTU */ + dw->sleep_enter_dtu = dw3000_get_dtu_time(dw); + trace_dw3000_deep_sleep_enter(dw, dw->sleep_enter_dtu); + /* + * Set up the deep sleep configuration + */ + /* Step 1: + * - enable configuration copy from AON memory to host registers. + * - set ONW_GO2IDLE to get DW3000_OP_STATE_IDLE_PLL on wakeup. + */ + /* Step 1.1 + * - clear DW3000_RUNSAR bit from DW3000_AON_DIG_CFG_ID + * - turn off auto PGF cal on wake up + */ + dw->data.sleep_mode &= ~DW3000_RUNSAR; + dw->data.sleep_mode |= DW3000_PGFCAL; + + rc = dw3000_reg_write16( + dw, DW3000_AON_DIG_CFG_ID, 0, + dw->data.sleep_mode | + DW3000_AON_DIG_CFG_ONW_AONDLD_MASK | + DW3000_AON_DIG_CFG_ONW_GO2IDLE_MASK); + if (rc) + return rc; + /* + * Step 2: + * - enable the sleep configuration bit. + * - disable sleep counter to get deep sleep. + * - enable wake up using SPI access. + */ + rc = dw3000_reg_write8(dw, DW3000_AON_CFG_ID, 0, + DW3000_AON_SLEEP_EN_MASK | + DW3000_AON_WAKE_CSN_MASK); + if (rc) + return rc; + /* + * Step 3 + * - Set INIT2IDLE bit, to get DW3000_OP_STATE_IDLE_PLL on wakeup. + */ + rc = dw3000_reg_or8(dw, DW3000_SEQ_CTRL_ID, 0x01, + DW3000_SEQ_CTRL_AUTO_INIT2IDLE_BIT_MASK >> + 8); + if (rc) + return rc; + /* Step 4 + * - backup ALL registers to AON memory (include AON config block) + * - and enter DEEP-SLEEP + */ + rc = dw3000_reg_write8(dw, DW3000_AON_CTRL_ID, 0, 0); + if (rc) + return rc; + rc = dw3000_reg_write8(dw, DW3000_AON_CTRL_ID, 0, + DW3000_AON_CTRL_ARRAY_UPLOAD_BIT_MASK); + if (rc) + return rc; + /* Step 5 + * Here the chip is in DW3000_OP_STATE_DEEP_SLEEP so now + * update power statistics and operational state. + */ + dw3000_power_stats(dw, DW3000_PWR_DEEPSLEEP, 0); + dw3000_set_operational_state(dw, DW3000_OP_STATE_DEEP_SLEEP); + break; + + case DW3000_OP_STATE_IDLE_PLL: + /** + * Set the auto INIT2IDLE bit so that DW3000 enters DW3000_OP_STATE_IDLE_PLL mode before + * switching clocks to system_PLL. + * + * NOTE: PLL should be configured prior to this, and the device + * should be in DW3000_OP_STATE_IDLE_RC (if the PLL does not lock device will + * remain in DW3000_OP_STATE_IDLE_RC) + * + * Switch clock to auto, if coming here from DW3000_OP_STATE_IDLE_RC the clock + * will be FOSC/4, need to switch to auto prior to setting auto + * INIT2IDLE bit + */ + rc = dw3000_force_clocks(dw, DW3000_FORCE_CLK_AUTO); + if (rc) + return rc; + /* Calibrate the PLL from scratch, if needed. */ + if (dw->chip_ops->pll_calibration_from_scratch) { + rc = dw->chip_ops->pll_calibration_from_scratch(dw); + if (rc) + return rc; + } + rc = dw3000_reg_or8(dw, DW3000_SEQ_CTRL_ID, 0x01, + DW3000_SEQ_CTRL_AUTO_INIT2IDLE_BIT_MASK >> + 8); + if (rc) + return rc; + dw3000_set_operational_state(dw, DW3000_OP_STATE_IDLE_PLL); + break; + + case DW3000_OP_STATE_IDLE_RC: + /** + * Change state to IDLE_RC and clear auto INIT2IDLE bit + * switch clock to FOSC + */ + rc = dw3000_reg_or8(dw, DW3000_CLK_CTRL_ID, 0, + DW3000_FORCE_SYSCLK_FOSC); + if (rc) + return rc; + /* Clear the auto INIT2IDLE bit and set FORCE2INIT */ + rc = dw3000_reg_modify32( + dw, DW3000_SEQ_CTRL_ID, 0x0, + (u32)~DW3000_SEQ_CTRL_AUTO_INIT2IDLE_BIT_MASK, + DW3000_SEQ_CTRL_FORCE2INIT_BIT_MASK); + if (rc) + return rc; + /* Clear force bits (device will stay in IDLE_RC) */ + rc = dw3000_reg_and8( + dw, DW3000_SEQ_CTRL_ID, 0x2, + (u8) ~(DW3000_SEQ_CTRL_FORCE2INIT_BIT_MASK >> 16)); + if (rc) + return rc; + /* Switch clock to auto */ + rc = dw3000_force_clocks(dw, DW3000_FORCE_CLK_AUTO); + if (rc) + return rc; + dw3000_set_operational_state(dw, DW3000_OP_STATE_IDLE_RC); + break; + + case DW3000_OP_STATE_INIT_RC: + /** + * The SPI rate needs to be <= 7MHz as device is switching + * to INIT_RC state + */ + rc = dw3000_reg_or8(dw, DW3000_CLK_CTRL_ID, 0, + DW3000_FORCE_SYSCLK_FOSCDIV4); + if (rc) + return rc; + /* Clear the auto INIT2IDLE bit and set FORCE2INIT */ + rc = dw3000_reg_modify32( + dw, DW3000_SEQ_CTRL_ID, 0x0, + (u32)~DW3000_SEQ_CTRL_AUTO_INIT2IDLE_BIT_MASK, + DW3000_SEQ_CTRL_FORCE2INIT_BIT_MASK); + if (rc) + return rc; + /* Clear force bits (device will stay in IDLE_RC) */ + rc = dw3000_reg_and8( + dw, DW3000_SEQ_CTRL_ID, 0x2, + (u8) ~(DW3000_SEQ_CTRL_FORCE2INIT_BIT_MASK >> 16)); + if (rc) + return rc; + dw3000_set_operational_state(dw, DW3000_OP_STATE_INIT_RC); + break; + + default: + /* Invalid or unmanaged state */ + dev_err(dw->dev, + "Invalid or unmanaged operational state %d, current state: %d\n", + state, dw->current_operational_state); + rc = -EINVAL; + break; + } + return rc; +} + +#ifdef CONFIG_DW3000_DEBUG +static unsigned dw3000_check_fileid = 0; +module_param_named(checkfid, dw3000_check_fileid, uint, 0644); +MODULE_PARM_DESC(checkfid, + "Selected register fileid to backup and check on wakeup"); + +static void dw3000_wakeup_compare(struct work_struct *work) +{ + struct dw3000 *dw = container_of(work, struct dw3000, + deep_sleep_state.compare_work); + u32 *before = (u32 *)dw->deep_sleep_state.regbackup; + u32 *after = (u32 *)(dw->deep_sleep_state.regbackup + 512); + unsigned offset = dw3000_check_fileid << 16; + const struct dw3000_chip_register *reg; + size_t count; + int length; + /* retrieve registers length */ + reg = dw->chip_ops->get_registers(dw, &count); + while (reg->address != dw3000_check_fileid && count) { + reg++; + count--; + } + length = (reg->size + 3) & ~4; + dev_notice(dw->dev, "compare registers in fileid %u on wakeup\n", + dw3000_check_fileid); + while (length > 0) { + if (*after != *before) + dev_notice(dw->dev, "0x%05x : 0x%x -> 0x%x\n", offset, + *before, *after); + after++; + before++; + offset += 4; + length -= 4; + } + return; +} + +static int dw3000_backup_registers(struct dw3000 *dw, bool after) +{ + const struct dw3000_chip_register *reg; + size_t count; + int offset = after ? 512 : 0; + int rc; + + reg = dw->chip_ops->get_registers(dw, &count); + while (reg->address != dw3000_check_fileid && count) { + reg++; + count--; + } + + rc = dw3000_xfer(dw, dw3000_check_fileid << 16, 0, reg->size, + dw->deep_sleep_state.regbackup + offset, + DW3000_SPI_RD_BIT); + if (rc) + return rc; + + /* Now compare and dump both parts */ + if (after) + schedule_work(&dw->deep_sleep_state.compare_work); + + return 0; +} +#endif /* CONFIG_DW3000_DEBUG */ + +/** + * dw3000_wakeup_timer_start() - Program wakeup timer + * @dw: the DW device on which the SPI transfer will occurs + * @delay_us: the delay after the DW device is woken up + * + * The timer is configured to wake-up the device after the specified delay. + * Caller should take care of wake-up latency. + * + * Return: zero on success, else a negative error code. + */ +void dw3000_wakeup_timer_start(struct dw3000 *dw, int delay_us) +{ + hrtimer_start(&dw->idle_timer, ns_to_ktime(delay_us * 1000ull), + HRTIMER_MODE_REL); + trace_dw3000_wakeup_timer_start(dw, delay_us); +} + +/** + * dw3000_deep_sleep_and_wakeup() - Put device in DEEP SLEEP state + * @dw: the DW device on which the SPI transfer will occurs + * @delay_us: the delay after the DW device is woken up + * + * The caller must save the next operational state and required data before + * the DW device is woken-up by the timer. + * + * The timer is configured to wake-up the device after the specified delay. + * Caller should take care of wake-up latency. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_deep_sleep_and_wakeup(struct dw3000 *dw, int delay_us) +{ + int rc; +#ifdef CONFIG_DW3000_DEBUG + /* Backup config before changing state */ + rc = dw3000_backup_registers(dw, false); + if (rc) + return rc; +#endif + /* Always launch timer, even if entering deep-sleep fail. */ + if (delay_us) + dw3000_wakeup_timer_start(dw, delay_us); + dw3000_pm_qos_update_request(dw, PM_QOS_RESUME_LATENCY_NO_CONSTRAINT); + /* Put the chip in deep sleep immediately */ + rc = dw3000_setdwstate(dw, DW3000_OP_STATE_DEEP_SLEEP); + trace_dw3000_deep_sleep(dw, rc); + return rc; +} + +/** + * dw3000_wakeup_done_to_tx() - Wakeup done, continue to program TX. + * @dw: Driver context. + * + * Return: 0 on success, else an error. + */ +static int dw3000_wakeup_done_to_tx(struct dw3000 *dw) +{ + struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state; + + trace_dw3000_wakeup_done_to_tx(dw); + dss->next_operational_state = dw->current_operational_state; + return dw3000_do_tx_frame(dw, &dss->tx_config, dss->tx_skb, + dss->frame_idx); +} + +/** + * dw3000_wakeup_done_to_rx() - Wakeup done, continue to program RX. + * @dw: Driver context. + * + * Return: 0 on success, else an error. + */ +static int dw3000_wakeup_done_to_rx(struct dw3000 *dw) +{ + struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state; + + trace_dw3000_wakeup_done_to_rx(dw); + dss->next_operational_state = dw->current_operational_state; + return dw3000_do_rx_enable(dw, &dss->rx_config, dss->frame_idx); +} + +/** + * dw3000_wakeup_done_to_idle() - Wakeup done, continue on idle timeout. + * @dw: Driver context. + * + * Return: 0 on success, else an error. + */ +static int dw3000_wakeup_done_to_idle(struct dw3000 *dw) +{ + struct dw3000_stm_command cmd = { dw3000_handle_idle_timeout, NULL, + NULL }; + + trace_dw3000_wakeup_done_to_idle(dw); + if (dw->idle_timeout) { + u32 now_dtu = dw3000_get_dtu_time(dw); + int idle_duration_dtu = dw->idle_timeout_dtu - now_dtu; + + /* TODO: + * 2 timers implemented (idle, deep_sleep), + * or a better FSM (enter, leave, events, state) + * should help to follow/respect timestamp expected. */ + /* WORKAROUND: On a delayed wake-up (by CPU high load), + * the idle_timeout_dtu date can be in the past. And to avoid + * to program a timer in the past, process the idle_timeout_cb + * immediately when duration is negative or equal to 0. + * For NFCC_COEX, the NFC software will enter in "LATE" + * processing, and return immediately too. Thus the CPU high + * load will not broken dw3000 driver and mac layer. */ + if (idle_duration_dtu > 0) { + int idle_duration_us = DTU_TO_US(idle_duration_dtu); + + dw3000_wakeup_timer_start(dw, idle_duration_us); + return 0; + } + trace_dw3000_wakeup_done_to_idle_late(dw); + } + return dw3000_enqueue_generic(dw, &cmd); +} + +/** + * dw3000_idle() - Go into idle. + * @dw: Driver context. + * @timestamp: Idle duration have a end date (timestamp_dtu). + * @timestamp_dtu: End date for this idle duration. + * @idle_timeout_cb: idle timeout callback. + * @next_operational_state: next operation state wanted. + * + * Return: 0 on success, else an error. + */ +int dw3000_idle(struct dw3000 *dw, bool timestamp, u32 timestamp_dtu, + dw3000_idle_timeout_cb idle_timeout_cb, + enum operational_state next_operational_state) +{ + struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state; + u32 cur_time_dtu = 0; + int delay_us = 0, rc; + + trace_dw3000_idle(dw, timestamp, timestamp_dtu, next_operational_state); + + if (WARN_ON(next_operational_state < DW3000_OP_STATE_IDLE_PLL)) + return -EINVAL; + + dw->wakeup_done_cb = dw3000_wakeup_done_to_idle; + /* Reset ranging clock requirement */ + dw->need_ranging_clock = false; + dw3000_reset_rctu_conv_state(dw); + /* Ensure dw3000_idle_timeout() handler does the right thing. */ + dss->next_operational_state = next_operational_state; + + /* Release Wifi coexistence. */ + dw3000_coex_stop(dw); + /* Check if enough idle time to enter DEEP SLEEP */ + dw->idle_timeout = timestamp; + dw->idle_timeout_dtu = timestamp_dtu; + if (timestamp) { + int deepsleep_delay_us; + int idle_delay_dtu; + int idle_delay_us; + bool is_sleeping = dw->current_operational_state == + DW3000_OP_STATE_DEEP_SLEEP; + + cur_time_dtu = dw3000_get_dtu_time(dw); + idle_delay_dtu = + timestamp_dtu - cur_time_dtu - dw->llhw->anticip_dtu; + if (idle_delay_dtu < 0) { + rc = -ETIME; + goto eof; + } + idle_delay_us = DTU_TO_US(idle_delay_dtu); + /* Warning: timestamp_dtu have already taken in consideration + * WakeUp latency! */ + deepsleep_delay_us = idle_delay_us; + if (is_sleeping) + deepsleep_delay_us -= DW3000_WAKEUP_LATENCY_US; + + /* TODO/FIXME: + * Timer is used for idle timeout and deepsleep timeout, + * which haven't the same timeout_dtu! */ + if (deepsleep_delay_us < 0) { + dw3000_deepsleep_wakeup_now(dw, idle_timeout_cb, + dw->idle_timeout_dtu, + DW3000_OP_STATE_MAX); + rc = 0; + goto eof; + } + + delay_us = dw3000_can_deep_sleep(dw, deepsleep_delay_us); + if (!delay_us) { + u32 timer_duration_dtu = is_sleeping ? + deepsleep_delay_us : + idle_delay_us; + /* Provided date isn't far enough to enter deep-sleep, + just launch the timer to have it call the MCPS timer + expired event function. */ + dw->idle_timeout_cb = idle_timeout_cb; + dw3000_wakeup_timer_start(dw, timer_duration_dtu); + rc = 0; + goto eof; + } + } else if (dw->auto_sleep_margin_us < 0) { + /* Deep-sleep is completely disable, so do nothing here! */ + rc = 0; + goto eof; + } + /* Enter DEEP SLEEP */ + rc = dw3000_deep_sleep_and_wakeup(dw, delay_us); + if (!rc) + dw->idle_timeout_cb = idle_timeout_cb; +eof: + return rc; +} + +/** + * dw3000_lock_pll() - Auto calibrate the PLL and change to IDLE_PLL state + * @dw: the DW device on which the SPI transfer will occurs + * @sys_status: the last known sys_status register LSB value + * + * It also checks on the CPLOCK bit field to be set in the SYS_STATUS register + * which means the PLL is locked (for D0/E0). + * + * Return: zero on success, else a negative error code. + */ +static inline int dw3000_lock_pll(struct dw3000 *dw, u8 sys_status) +{ + u8 flag; + int cnt; + int rc; + + if (sys_status & DW3000_SYS_STATUS_CLK_PLL_LOCK_BIT_MASK) { + /* PLL already locked! Just change current_operational_state */ + dw3000_set_operational_state(dw, DW3000_OP_STATE_IDLE_PLL); + goto resync_dtu; + } + + if (__dw3000_chip_version == DW3000_E0_VERSION) { + /* Verify PLL lock bit is cleared */ + int rc = dw3000_reg_write8( + dw, DW3000_SYS_STATUS_ID, 0, + DW3000_SYS_STATUS_CLK_PLL_LOCK_BIT_MASK); + if (rc) + return rc; + } + rc = dw3000_setdwstate(dw, DW3000_OP_STATE_IDLE_PLL); + if (rc) + return rc; + + /* For C0, wait for PLL lock, else SYS_TIME is 0 */ + if (__dw3000_chip_version == DW3000_C0_VERSION) { + udelay(DW3000_PLL_LOCK_DELAY_US); + goto resync_dtu; + } + /* For D0/E0, wait PLL locked bit in SYS_STATUS */ + for (flag = 1, cnt = 0; cnt < DW3000_MAX_RETRIES_FOR_PLL; cnt++) { + u8 status; + + usleep_range(10, 40); + /* Verify PLL lock bit is locked */ + dw3000_reg_read8(dw, DW3000_SYS_STATUS_ID, 0, &status); + if ((status & DW3000_SYS_STATUS_CLK_PLL_LOCK_BIT_MASK)) { + /* PLL is locked. */ + flag = 0; + break; + } + } + if (flag) + return -EAGAIN; +resync_dtu: + /* Re-sync SYS_TIME and DTU when chip is in IDLE_PLL */ + rc = dw3000_resync_dtu_sys_time(dw); + return rc; +} + +/** + * dw3000_run_pgfcal() - Run PGF calibration + * @dw: the DW device + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_run_pgfcal(struct dw3000 *dw) +{ + u32 val = 0; + u8 cal; + int rc; + + /* Put into calibration mode */ + rc = dw3000_reg_modify8(dw, DW3000_PGF_CAL_CFG_ID, 0x0, + (u8)~DW3000_PGF_CAL_CFG_PGF_MODE_BIT_MASK, 0x1); + if (rc) + return rc; + /* Trigger PGF calibration */ + rc = dw3000_reg_or8(dw, DW3000_PGF_CAL_CFG_ID, 0x0, + DW3000_PGF_CAL_CFG_CAL_EN_BIT_MASK); + if (rc) + return rc; + /* Calibration will be done within ~30 us (add some margin) */ + /* TODO: On D0 active wait with lower delays. */ + usleep_range(DW3000_PGFCAL_DELAY_US, DW3000_PGFCAL_DELAY_US + 100); + /* Check if calibration is done.*/ + rc = dw3000_reg_read8(dw, DW3000_PGF_CAL_STS_ID, 0x0, &cal); + if (rc) + return rc; + if (cal != 1) { + goto calib_err; + } + /* Put into normal mode */ + rc = dw3000_reg_write8(dw, DW3000_PGF_CAL_CFG_ID, 0x0, 0); + if (rc) + return rc; + /* Clear the status */ + rc = dw3000_reg_write8(dw, DW3000_PGF_CAL_STS_ID, 0x0, 1); + if (rc) + return rc; + /* Enable reading */ + rc = dw3000_reg_or8(dw, DW3000_PGF_CAL_CFG_ID, 0x2, 0x1); + if (rc) + return rc; + /* PFG I calibration */ + rc = dw3000_reg_read32(dw, DW3000_PGF_I_CTRL1_ID, 0x0, &val); + if (rc) + return rc; + if (val == 0x1fffffff) { + goto calib_err; + } + /* PFG Q calibration */ + rc = dw3000_reg_read32(dw, DW3000_PGF_Q_CTRL1_ID, 0x0, &val); + if (rc) + return rc; + if (val == 0x1fffffff) { + goto calib_err; + } + /* Disable reading */ + rc = dw3000_reg_and8(dw, DW3000_PGF_CAL_CFG_ID, 0x2, ~0x1); + if (rc) + return rc; + return 0; +calib_err: + dev_err(dw->dev, "PGF calibration failed\n"); + return -EREMOTEIO; +} + +/** + * dw3000_pgf_cal() - Run PGF calibration + * @dw: the DW device + * @ldoen: if set to true the function will enable LDOs prior to calibration + * and disable afterwards. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_pgf_cal(struct dw3000 *dw, bool ldoen) +{ + u16 val; + int rc; + + /* Turn on delay mode (ensure good initial value all the time) */ + rc = dw3000_reg_write16(dw, DW3000_PGF_CAL_CFG_ID, 0x2, 0x02); + if (rc) + return rc; + + if (__dw3000_chip_version >= DW3000_D0_VERSION) { + u32 resi; + /* Enable reading of CAL result */ + rc = dw3000_reg_or8(dw, DW3000_RX_CAL_CFG_ID, 0x2, 0x1); + if (rc) + return rc; + /* Read calibration */ + rc = dw3000_reg_read32(dw, DW3000_RX_CAL_RESI_ID, 0x0, &resi); + if (rc) + return rc; + /* Disable reading */ + rc = dw3000_reg_and8(dw, DW3000_RX_CAL_CFG_ID, 0x2, ~0x1); + if (rc) + return rc; + /* If not D0 and not soft reset, no need to continue */ + if ((__dw3000_chip_version != DW3000_D0_VERSION) && (resi != 0)) + return 0; + } + + /* PGF needs LDOs turned on - ensure PGF LDOs are enabled */ + if (ldoen) { + rc = dw3000_reg_read16(dw, DW3000_LDO_CTRL_ID, 0, &val); + if (rc) + return rc; + rc = dw3000_reg_or16(dw, DW3000_LDO_CTRL_ID, 0, + (DW3000_LDO_CTRL_LDO_VDDIF2_EN_BIT_MASK | + DW3000_LDO_CTRL_LDO_VDDMS3_EN_BIT_MASK | + DW3000_LDO_CTRL_LDO_VDDMS1_EN_BIT_MASK)); + if (rc) + return rc; + } + dw->pgf_cal_running = true; + /* Run PGF Cal */ + rc = dw3000_run_pgfcal(dw); + dw->pgf_cal_running = false; + /* Turn off RX LDOs if previously off */ + if (ldoen) { + /* restore LDO values */ + return dw3000_reg_and16(dw, DW3000_LDO_CTRL_ID, 0, val); + } + return rc; +} + +/** + * dw3000_configure() - configure the whole device + * @dw: the DW device + * + * Configure with no STS and SCP mode and without API errors. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_configure(struct dw3000 *dw) +{ + struct dw3000_config *config = &dw->config; + int rc; + + /* Clear the sleep mode ALT_GEAR bit */ + dw->data.sleep_mode &= (~(DW3000_ALT_GEAR | DW3000_SEL_GEAR3)); + dw->data.max_frames_len = config->phrMode ? DW3000_EXT_FRAME_LEN : + DW3000_STD_FRAME_LEN; + /* Configure the SYS_CFG register */ + rc = dw3000_configure_sys_cfg(dw, config); + if (rc) + return rc; + /* Configure the RF channel */ + rc = dw3000_configure_chan(dw); + if (rc) + return rc; + /* Setup TX preamble size, data rate and SDF timeout count */ + rc = dw3000_configure_preamble_length_and_datarate(dw, false); + if (rc) + return rc; + + /* Auto calibrate the PLL and change to IDLE_PLL state */ + rc = dw3000_lock_pll(dw, 0); + if (rc) + return rc; + /** + * PGF: If the RX calibration routine fails the device receiver + * performance will be severely affected, the application should reset + * and try again. + * PGF NOT needed for E0 as routine automatically runs on startup/hw reset, on wakeup + * however if reconfiguring after soft reset then the calibration needs to be run + */ + rc = dw3000_pgf_cal(dw, 1); + if (rc) + return rc; + /* Calibrate ADC offset, if needed, after DGC configuration and after PLL lock. + * If this calibration is executed before the PLL lock, the PLL lock failed. + */ + if (dw->chip_ops->adc_offset_calibration) + rc = dw->chip_ops->adc_offset_calibration(dw); + + /* Update configuration dependent timings */ + dw3000_update_timings(dw); + /* update VOUT */ + rc = dw3000_reg_write32(dw, DW3000_LDO_VOUT_ID, 0, DW3000_RF_LDO_VOUT); + return rc; +} + +static int dw3000_setrxantennadelay(struct dw3000 *dw, u16 rxDelay) +{ + /* Set the RX antenna delay for auto TX timestamp adjustment */ + return dw3000_reg_write16(dw, DW3000_RX_ANTENNA_DELAY_ID, 0, rxDelay); +} + +static int dw3000_settxantennadelay(struct dw3000 *dw, u16 txDelay) +{ + /* Set the TX antenna delay for auto TX timestamp adjustment */ + return dw3000_reg_write16(dw, DW3000_TX_ANTD_ID, 0, txDelay); +} + +static int dw3000_set_antenna_delay(struct dw3000 *dw, u16 delay) +{ + int rc = dw3000_setrxantennadelay(dw, delay); + + if (rc) + return rc; + return dw3000_settxantennadelay(dw, delay); +} + +/** + * dw3000_set_eui64() - Set device's Extended Unique Identifier + * @dw: the DW device + * @val: the EUI + * + * Return: zero on success, else a negative error code. + */ +int dw3000_set_eui64(struct dw3000 *dw, __le64 val) +{ + return dw3000_reg_write_fast(dw, DW3000_EUI_64_ID, 0, 8, &val, + DW3000_SPI_WR_BIT); +} + +/** + * dw3000_set_pancoord() - Enable/disable the device as PAN coordinator + * @dw: the DW device + * @active: enables the device as PAN coordinator if true, otherwise disables + * + * Return: zero on success, else a negative error code. + */ +int dw3000_set_pancoord(struct dw3000 *dw, bool active) +{ + if (active) { + return dw3000_reg_or8(dw, DW3000_ADR_FILT_CFG_ID, 1, + DW3000_AS_PANCOORD); + } else { + return dw3000_reg_and8(dw, DW3000_ADR_FILT_CFG_ID, 1, + ~DW3000_AS_PANCOORD); + } +} + +/** + * dw3000_set_panid() - Set device's PAN Identifier + * @dw: the DW device + * @val: the PAN ID + * + * Return: zero on success, else a negative error code. + */ +int dw3000_set_panid(struct dw3000 *dw, __le16 val) +{ + return dw3000_reg_write_fast(dw, DW3000_PANADR_ID, + (DW3000_PANADR_PAN_ID_BIT_OFFSET / 8), + DW3000_PANADR_PAN_ID_BIT_LEN, &val, + DW3000_SPI_WR_BIT); +} + +/** + * dw3000_set_shortaddr() - Set device's short address + * @dw: the DW device + * @val: the short address + * + * Return: zero on success, else a negative error code. + */ +int dw3000_set_shortaddr(struct dw3000 *dw, __le16 val) +{ + return dw3000_reg_write_fast(dw, DW3000_PANADR_ID, + (DW3000_PANADR_SHORT_ADDR_BIT_OFFSET / 8), + DW3000_PANADR_SHORT_ADDR_BIT_LEN, &val, + DW3000_SPI_WR_BIT); +} + +/** + * dw3000_configure_hw_addr_filt() - Set device's hardware address filtering + * parameters + * @dw: the DW device + * @changed: bitfields of flags indicating parameters which have changed + * + * Return: zero on success, else a negative error code. + */ +int dw3000_configure_hw_addr_filt(struct dw3000 *dw, unsigned long changed) +{ + struct ieee802154_hw_addr_filt *filt = &dw->config.hw_addr_filt; + int rc; + + if (!changed) + return 0; + + if (changed & IEEE802154_AFILT_SADDR_CHANGED) { + rc = dw3000_set_shortaddr(dw, filt->short_addr); + if (rc) + return rc; + } + if (changed & IEEE802154_AFILT_PANID_CHANGED) { + rc = dw3000_set_panid(dw, filt->pan_id); + if (rc) + return rc; + } + if (changed & IEEE802154_AFILT_PANC_CHANGED) { + rc = dw3000_set_pancoord(dw, filt->pan_coord); + if (rc) + return rc; + } + if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED) { + rc = dw3000_set_eui64(dw, filt->ieee_addr); + if (rc) + return rc; + } + + return 0; +} + +static int dw3000_reconfigure_hw_addr_filt(struct dw3000 *dw) +{ + struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state; + struct ieee802154_hw_addr_filt *filt = &dw->config.hw_addr_filt; + unsigned long changed = dss->config_changed; + + /* Always force setup of ieee_addr if non 0 on wakeup + since not saved in AON. */ + if (filt->ieee_addr) + changed |= DW3000_AFILT_IEEEADDR_CHANGED; + dss->config_changed &= + ~(DW3000_AFILT_SADDR_CHANGED | DW3000_AFILT_IEEEADDR_CHANGED | + DW3000_AFILT_PANID_CHANGED | DW3000_AFILT_PANC_CHANGED); + return dw3000_configure_hw_addr_filt(dw, changed); +} + +/** + * dw3000_framefilter_enable() - Enable and set the device's frame filter + * @dw: the DW device + * @filtermode: filter mode + * + * Return: zero on success, else a negative error code. + */ +static inline int dw3000_framefilter_enable(struct dw3000 *dw, u16 filtermode) +{ + /* Use 802.15.4 filtering rules */ + int rc = dw3000_reg_or8(dw, DW3000_SYS_CFG_ID, 0, + (u8)(DW3000_SYS_CFG_FFEN_BIT_MASK)); + if (rc) + return rc; + /* Set the frame filter configuration */ + return dw3000_reg_write16(dw, DW3000_ADR_FILT_CFG_ID, 0, filtermode); +} + +/** + * dw3000_framefilter_disable() - Disable the device's frame filter + * @dw: the DW device + * + * Return: zero on success, else a negative error code. + */ +static inline int dw3000_framefilter_disable(struct dw3000 *dw) +{ + /* Disable frame filter */ + int rc = dw3000_reg_and8(dw, DW3000_SYS_CFG_ID, 0, + (u8)(~(DW3000_SYS_CFG_FFEN_BIT_MASK))); + if (rc) + return rc; + /* Clear the configuration */ + return dw3000_reg_write16(dw, DW3000_ADR_FILT_CFG_ID, 0, 0x0); +} + +/** + * dw3000_read_clockoffset() - Read the clock offset for last frame received + * @dw: the DW device on which the SPI transfer will occurs + * @cfo: the address where to store read CFO + * + * This is used to read the crystal offset (relating to the frequency offset of + * the far DW3720 device compared to this one). + * + * Note: the returned signed number must be divided by 2^26 to get ppm offset. + * Return: 0 on success, else a negative error code. + */ +int dw3000_read_clockoffset(struct dw3000 *dw, s16 *cfo) +{ + int rc; + switch (dw->data.dblbuffon) { + case DW3000_DBL_BUFF_ACCESS_BUFFER_B: + /* !!! Assumes that Indirect pointer register B was already set. */ + rc = dw3000_reg_read16(dw, DW3000_INDIRECT_POINTER_B_ID, + DW3000_DB_DIAG_CIA_DIAG0, (u16 *)cfo); + break; + case DW3000_DBL_BUFF_ACCESS_BUFFER_A: + rc = dw3000_reg_read16(dw, DW3000_DB_DIAG_SET_1, + DW3000_DB_DIAG_CIA_DIAG0, (u16 *)cfo); + break; + default: + rc = dw3000_reg_read16(dw, DW3000_CIA_DIAG0_ID, 0, (u16 *)cfo); + } + if (rc) + return rc; + /* Bit 12 is sign, make the number to be sign extended if this bit is '1' */ + *cfo <<= sizeof(*cfo) * 8 - DW3000_CIA_DIAG0_COE_PPM_BIT_LEN; + *cfo >>= sizeof(*cfo) * 8 - DW3000_CIA_DIAG0_COE_PPM_BIT_LEN; + trace_dw3000_read_clockoffset(dw, *cfo); + return 0; +} + +/** + * dw3000_read_pdoa() - Read the PDOA result + * @dw: The DW device. + * + * This is used to read the PDOA result, it is the phase difference between + * either the Ipatov and STS POA, or the two STS POAs, depending on the PDOA + * mode of operation. (POA - Phase Of Arrival). + * + * NOTE: To convert to degrees: float pdoa_deg = + * ((float)pdoa / (1 << 11)) * 180 / M_PI. + * + * Return: the PDOA result (signed in q11 radian units). + */ +s16 dw3000_read_pdoa(struct dw3000 *dw) +{ + const u16 b12_sign_extend_test = 0x2000; + const u16 b12_sign_extend_mask = 0xc000; + const u16 pi_floored_q11 = 3.141592653589 * (1 << 11); + const u16 pix2_rounded_q11 = 3.141592653589 * 2 * (1 << 11) + 0.5; + u16 val; + s16 pdoa; + + switch (dw->data.dblbuffon) { + case DW3000_DBL_BUFF_ACCESS_BUFFER_B: + dw3000_reg_read16(dw, DW3000_INDIRECT_POINTER_B_ID, + DW3000_DB_DIAG_PDOA + 2, &val); + pdoa = val & (DW3000_CIA_TDOA_1_PDOA_RX_PDOA_BIT_MASK >> 16); + break; + case DW3000_DBL_BUFF_ACCESS_BUFFER_A: + dw3000_reg_read16(dw, DW3000_DB_DIAG_SET_1, + DW3000_DB_DIAG_PDOA + 2, &val); + pdoa = val & (DW3000_CIA_TDOA_1_PDOA_RX_PDOA_BIT_MASK >> 16); + break; + default: + dw3000_reg_read16(dw, DW3000_CIA_TDOA_1_PDOA_ID, 2, &val); + /* Phase difference of the 2 POAs. */ + pdoa = val & (DW3000_CIA_TDOA_1_PDOA_RX_PDOA_BIT_MASK >> 16); + } + trace_dw3000_read_pdoa(dw, pdoa); + if (pdoa & b12_sign_extend_test) + pdoa |= b12_sign_extend_mask; + pdoa -= dw->config.pdoaOffset; + if (pdoa < -pi_floored_q11) + pdoa += pix2_rounded_q11; + if (pdoa > pi_floored_q11) + pdoa -= pix2_rounded_q11; + return pdoa; +} + +/** + * dw3000_pdoa_to_aoa_lut() - Convert PDoA to AoA. + * @dw: the DW device + * @pdoa_rad_q11: the PDoA value as returned by dw3000_read_pdoa() + * + * Convert PDoA (in radian, encoded as a Q11 fixed + * point number) to AoA value using calibration look-up table. + * + * Return: AoA value interpolated from LUT values. + */ +s16 dw3000_pdoa_to_aoa_lut(struct dw3000 *dw, s16 pdoa_rad_q11) +{ + const dw3000_pdoa_lut_t *lut = dw->config.pdoaLut; + int a = 0, b = DW3000_CALIBRATION_PDOA_LUT_MAX - 1; + s16 delta_pdoa, delta_aoa; + + if (pdoa_rad_q11 < (*lut)[0][0]) + return (*lut)[0][1]; + if (pdoa_rad_q11 >= (*lut)[DW3000_CALIBRATION_PDOA_LUT_MAX - 1][0]) + return (*lut)[DW3000_CALIBRATION_PDOA_LUT_MAX - 1][1]; + + while (a != b) { + int m = (a + b) / 2; + if (pdoa_rad_q11 < (*lut)[m][0]) + b = m; + else + a = m + 1; + } + + delta_pdoa = (*lut)[a][0] - (*lut)[a - 1][0]; + delta_aoa = (*lut)[a][1] - (*lut)[a - 1][1]; + + return (*lut)[a][1] + + (delta_aoa * (pdoa_rad_q11 - (*lut)[a][0])) / delta_pdoa; +} + +/** + * dw3000_set_sts_pdoa() - set device's STS & PDOA mode + * @dw: the DW device + * @sts_mode: the STS mode + * @pdoa_mode: the PDOA mode + * + * Return: zero on success, else a negative error code. + */ +int dw3000_set_sts_pdoa(struct dw3000 *dw, u8 sts_mode, u8 pdoa_mode) +{ + struct dw3000_config *config = &dw->config; + bool changed = false; + u8 prev_sts = config->stsMode; + u8 prev_pdoa = config->pdoaMode; + int rc = 0; + + /* This configuration is reserved or not supported + * (c.f DW3000 User Manual) */ + if (pdoa_mode == DW3000_PDOA_M2) + return -EOPNOTSUPP; + + if ((config->stsMode ^ sts_mode) & DW3000_STS_BASIC_MODES_MASK) { + /** + * Enable the Super Deterministic Code according current STS + * key value set by the MCPS. Setting NUL key will set the + * SDC flag in stsMode. + */ + u8 sts_sdc = config->stsMode & DW3000_STS_MODE_SDC; + config->stsMode = sts_mode | sts_sdc; + changed = true; + } + if (config->pdoaMode != pdoa_mode) { + config->pdoaMode = pdoa_mode; + changed = true; + trace_dw3000_set_pdoa(dw, pdoa_mode); + } + /* Re-configure the device with new STS & PDOA mode */ + if (changed) { + rc = dw3000_configure_sys_cfg(dw, config); + if (rc) { + /* Restore initial value */ + config->stsMode = prev_sts; + config->pdoaMode = prev_pdoa; + } + } + return rc; +} + +/** + * dw3000_set_sts_length() - set device's STS length + * @dw: the DW device + * @len: length of the STS in blocks of 8 symbols + * + * Return: zero on success, else a negative error code. + */ +int dw3000_set_sts_length(struct dw3000 *dw, enum dw3000_sts_lengths len) +{ + struct dw3000_config *config = &dw->config; + bool changed = false; + int rc; + + if (config->stsLength != len) { + rc = dw3000_reg_write8(dw, DW3000_CP_CFG0_ID, 0, + DW3000_GET_STS_LEN_REG_VALUE(len)); + if (unlikely(rc)) + return rc; + config->stsLength = len; + changed = true; + } + /* Re-configure the device with new STS length */ + if (changed) + return dw3000_configure_sts(dw, config); + return 0; +} + +/** + * dw3000_read_sts_timestamp() - read frame STS timestamp + * @dw: the DW device + * @sts_ts: the address where to store STS timestamp value + * + * Return: zero on success, else a negative error code. + */ +int dw3000_read_sts_timestamp(struct dw3000 *dw, u64 *sts_ts) +{ + int rc; + int fileid; + int offset; + __le64 buffer; + + switch (dw->data.dblbuffon) { + case DW3000_DBL_BUFF_ACCESS_BUFFER_A: + fileid = DW3000_DB_DIAG_SET_1; + offset = DW3000_DB_DIAG_STS_TS; + break; + case DW3000_DBL_BUFF_ACCESS_BUFFER_B: + fileid = DW3000_INDIRECT_POINTER_B_ID; + offset = DW3000_DB_DIAG_STS_TS; + break; + default: + fileid = DW3000_STS_TOA_LO_ID; + offset = 0; + } + /* Read 8 bytes (64-bits) register into buffer. */ + rc = dw3000_reg_read_fast(dw, fileid, offset, sizeof(buffer), &buffer); + if (rc) + return rc; + *sts_ts = le64_to_cpu(buffer) & + ((u64)DW3000_STS_TOA_LO_STS_TOA_BIT_MASK | + ((u64)DW3000_STS_TOA_HI_STS_TOA_BIT_MASK + << DW3000_STS_TOA_LO_STS_TOA_BIT_LEN)); + return 0; +} + +/** + * dw3000_read_sts_quality() - read received frame STS quality + * @dw: the DW device + * @acc_qual: the STS accumulation quality of the frame + * + * Return: zero on success, else a negative error code. + */ +int dw3000_read_sts_quality(struct dw3000 *dw, s16 *acc_qual) +{ + int rc; + u16 qual; + rc = dw3000_reg_read16(dw, DW3000_STS_STS_ID, 0, &qual); + if (rc) + return rc; + if (qual & DW3000_STS_ACC_CP_QUAL_SIGNTST) + qual |= DW3000_STS_ACC_CP_QUAL_SIGNEXT; + *acc_qual = (s16)qual; + return 0; +} + +/** + * dw3000_set_promiscuous() - Enable or disable the promiscuous mode + * @dw: the DW device + * @on: enable the promiscuous mode if true, otherwise disable it + * + * Return: zero on success, else a negative error code. + */ +int dw3000_set_promiscuous(struct dw3000 *dw, bool on) +{ + if (on) + return dw3000_framefilter_disable(dw); + return dw3000_framefilter_enable( + dw, DW3000_FF_BEACON_EN | DW3000_FF_DATA_EN | DW3000_FF_ACK_EN | + DW3000_FF_MAC_EN); +} + +/** + * dw3000_set_autoack_reply_delay() - Select the delay used for auto-ack. + * @dw: The DW device. + * @response_delay_time_symbols: Delay in symbols. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_set_autoack_reply_delay(struct dw3000 *dw, + u8 response_delay_time_symbols) +{ + struct dw3000_local_data *local = &dw->data; + int rc; + + if (local->ack_time == response_delay_time_symbols) + return 0; + rc = dw3000_reg_write8(dw, DW3000_ACK_RESP_ID, + DW3000_ACK_RESP_ACK_TIM_BIT_OFFSET / 8, + response_delay_time_symbols); + if (unlikely(rc)) + return rc; + local->ack_time = response_delay_time_symbols; + return 0; +} + +/** + * dw3000_enable_autoack() - Enable the autoack for futures rx. + * @dw: The DW device. + * @force: Enable even if it was already in enable state. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_enable_autoack(struct dw3000 *dw, bool force) +{ + int r = 0; + + if (!dw->autoack || force) { + /* Set the AUTO_ACK bit. */ + r = dw3000_reg_or32( + dw, DW3000_SYS_CFG_ID, 0, + DW3000_SYS_CFG_AUTO_ACK_BIT_MASK | + DW3000_SYS_CFG_FAST_AAT_EN_BIT_MASK); + if (!r) + dw->autoack = true; + } + return r; +} + +/** + * dw3000_disable_autoack() - Disable the autoack for futures rx. + * @dw: The DW device. + * @force: Disable even if it was already in disable state. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_disable_autoack(struct dw3000 *dw, bool force) +{ + int r = 0; + + if (dw->autoack || force) { + /* Clear the AUTO_ACK bit.*/ + r = dw3000_reg_and32(dw, DW3000_SYS_CFG_ID, 0, + (~DW3000_SYS_CFG_AUTO_ACK_BIT_MASK | + DW3000_SYS_CFG_FAST_AAT_EN_BIT_MASK)); + if (!r) + dw->autoack = false; + } + return r; +} + +/** + * dw3000_enable_auto_fcs() - Enable or disable the automatic FCS adding + * @dw: the DW device + * @on: enable the auto FCS adding if true, otherwise disable it + * + * Return: zero on success, else a negative error code. + */ +int dw3000_enable_auto_fcs(struct dw3000 *dw, bool on) +{ + if (!on) { + return dw3000_reg_or32(dw, DW3000_SYS_CFG_ID, 0, + DW3000_SYS_CFG_DIS_FCS_TX_BIT_MASK); + } + return dw3000_reg_and32(dw, DW3000_SYS_CFG_ID, 0, + (~DW3000_SYS_CFG_DIS_FCS_TX_BIT_MASK)); +} + +/** + * dw3000_otp_read32() - 32 bits OTP memory read. + * @dw: the DW device + * @addr: address in the OTP memory + * @val: value read from the OTP memory + * + * Return: zero on success, else a negative error code. + */ +int dw3000_otp_read32(struct dw3000 *dw, u16 addr, u32 *val) +{ + int rc; + u32 tmp = 0; + + /* Set manual access mode */ + /* TODO: avoid hard number, and replace it. */ + rc = dw3000_reg_write16(dw, DW3000_NVM_CFG_ID, 0, 0x0001); + if (unlikely(rc)) + return rc; + /* Set the address */ + rc = dw3000_reg_write16(dw, DW3000_NVM_ADDR_ID, 0, addr); + if (unlikely(rc)) + return rc; + /* Assert the read strobe */ + /* TODO: avoid hard number, and replace it. */ + rc = dw3000_reg_write16(dw, DW3000_NVM_CFG_ID, 0, 0x0002); + if (unlikely(rc)) + return rc; + /* Attempt a read from OTP address */ + rc = dw3000_reg_read32(dw, DW3000_NVM_RDATA_ID, 0, &tmp); + if (unlikely(rc)) + return rc; + /* Return the 32 bits of read data */ + *val = tmp; + return 0; +} + +/** + * dw3000_read_otp() - Read all required data from OTP. + * @dw: The DW device. + * @mode: bitfield to select information to retrieve from OTP memory + * See @DW3000_READ_OTP_PID and other values. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_read_otp(struct dw3000 *dw, int mode) +{ + struct dw3000_otp_data *otp = &dw->otp_data; + u32 val; + int rc; + + /* Check if recalled with same parameters */ + if ((mode | 1) == otp->mode) + return 0; /* No need to read OTP again. */ + + /* Reset OTP local data */ + memset(otp, 0, sizeof(*otp)); + otp->mode = mode | 1; + /* Values used by dw3000_prog_ldo_and_bias_tune() */ + rc = dw3000_otp_read32(dw, DW3000_LDOTUNELO_ADDRESS, &otp->ldo_tune_lo); + if (rc) + return rc; + rc = dw3000_otp_read32(dw, DW3000_LDOTUNEHI_ADDRESS, &otp->ldo_tune_hi); + if (rc) + return rc; + rc = dw3000_otp_read32(dw, DW3000_BIAS_TUNE_ADDRESS, &otp->bias_tune); + if (rc) + return rc; + /* Values used by dw3000_prog_xtrim() */ + rc = dw3000_otp_read32(dw, DW3000_XTRIM_ADDRESS, &val); + if (unlikely(rc)) + return rc; + otp->xtal_trim = val & DW3000_XTAL_TRIM_BIT_MASK; + /* Load optional values according to mode parameter */ + if (mode & DW3000_READ_OTP_PID) { + rc = dw3000_otp_read32(dw, DW3000_PARTID_ADDRESS, &otp->partID); + if (unlikely(rc)) + return rc; + } + if (mode & DW3000_READ_OTP_LID) { + rc = dw3000_otp_read32(dw, DW3000_LOTID_ADDRESS, &otp->lotID); + if (unlikely(rc)) + return rc; + } + if (mode & DW3000_READ_OTP_BAT) { + rc = dw3000_otp_read32(dw, DW3000_VBAT_ADDRESS, &val); + if (unlikely(rc)) + return rc; + /* TODO: avoid hard number, and replace it. */ + /* We save three voltage levels in OTP during production testing: + * [7:0] = Vbat @ 1.62V + * [15:8] = Vbat @ 3.6V + * [23:16] = Vbat @ 3.0V + */ + otp->vBatP = (val >> 16) & 0xff; + } + if (mode & DW3000_READ_OTP_TMP) { + rc = dw3000_otp_read32(dw, DW3000_VTEMP_ADDRESS, &val); + if (unlikely(rc)) + return rc; + /* TODO: avoid hard number, and replace it. */ + otp->tempP = val & 0xff; + } + /* If the reference temperature has not been programmed in OTP (early + eng samples) set to default value */ + /* TODO: avoid hard number, and replace it. */ + if (otp->tempP == 0) + otp->tempP = 0x85; /* @temp of 22 deg */ + /* If the reference voltage has not been programmed in OTP (early eng + samples) set to default value */ + /* TODO: avoid hard number, and replace it. */ + if (otp->vBatP == 0) + otp->vBatP = 0x74; /* @Vref of 3.0V */ + /* OTP revision */ + rc = dw3000_otp_read32(dw, DW3000_OTPREV_ADDRESS, &val); + if (unlikely(rc)) + return rc; + otp->rev = val & 0xff; + /* Some chip depending adjustment */ + if (__dw3000_chip_version >= DW3000_D0_VERSION) { + if (otp->xtal_trim == 0) + /* Set the default value for D0 if none set in OTP. */ + otp->xtal_trim = DW3000_DEFAULT_XTAL_TRIM; + /* Read DGC tune addr */ + rc = dw3000_otp_read32(dw, DW3000_DGC_TUNE_ADDRESS, + &otp->dgc_addr); + if (rc) + return rc; + } + /* PLL coarse code : starting code for calibration procedure */ + return dw3000_otp_read32(dw, DW3000_PLL_CC_ADDRESS, + &otp->pll_coarse_code); +} + +/** + * _dw3000_otp_wdata_write() - Configure and write value to the OTP + * memory block. + * @dw: The DW device. + * @val: 16-bit value to write to the OTP block. + * + * This function is used to configure the OTM memory block for memory + * writing operations and also to store the data value to be programmed + * into an OTP location. It is involved in the OTP memory writing procedure + * that is implemented into the '_dw3000_otp_write32()' function. + * + * Return: zero on success, else a negative error code. + */ +static inline int _dw3000_otp_wdata_write(struct dw3000 *dw, u16 val) +{ + /** + * Pull the CS high to enable user interface for programming. + * 'val' is ignored in this instance by the OTP block. + */ + /* TODO: avoid hard number, and replace it. */ + int rc = dw3000_reg_write16(dw, DW3000_NVM_WDATA_ID, 0, 0x0200 | val); + + if (unlikely(rc)) + return rc; + /* Send the relevant command to the OTP block */ + /* TODO: avoid hard number, and replace it. */ + return dw3000_reg_write16(dw, DW3000_NVM_WDATA_ID, 0, 0x0000 | val); +} + +/** + * _dw3000_otp_write32() - Program 32-bit value in OTP memory. + * @dw: The DW device. + * @data: data to write to given address. + * @addr: address to write to. + * + * Return: zero on success, else a negative error code. + */ +static int _dw3000_otp_write32(struct dw3000 *dw, u16 addr, u32 data) +{ + u32 ldo_tune; + u32 rd_buf; + u16 wr_buf[4]; + u16 i; + int rc; + + /* Read current register value */ + rc = dw3000_reg_read32(dw, DW3000_LDO_TUNE_HI_ID, 0, &ldo_tune); + if (rc) + return rc; + + /* Set VDDHV_TX LDO to max */ + rc = dw3000_reg_or32(dw, DW3000_LDO_TUNE_HI_ID, 0, + DW3000_LDO_TUNE_HI_LDO_HVAUX_TUNE_BIT_MASK); + if (rc) + return rc; + + /* Configure mode for programming */ + /* TODO: avoid hard number, and replace it. */ + rc = dw3000_reg_write16(dw, DW3000_NVM_CFG_ID, 0, 0x018); + if (rc) + return rc; + + /* Select fast programming */ + /* TODO: avoid hard number, and replace it. */ + rc = _dw3000_otp_wdata_write(dw, 0x0025); + if (rc) + return rc; + + /* Apply instruction to write the address */ + /* TODO: avoid hard number, and replace it. */ + rc = _dw3000_otp_wdata_write(dw, 0x0002); + if (rc) + return rc; + /* TODO: avoid hard number, and replace it. */ + rc = _dw3000_otp_wdata_write(dw, 0x01fc); + if (rc) + return rc; + + /* Sending the OTP address data (2 bytes) */ + /* TODO: avoid hard number, and replace it. */ + wr_buf[0] = 0x0100 | (addr & 0xff); + rc = _dw3000_otp_wdata_write(dw, wr_buf[0]); + if (rc) + return rc; + + /* Write data (upper byte of address) */ + /* TODO: avoid hard number, and replace it. */ + rc = _dw3000_otp_wdata_write(dw, 0x0100); + if (rc) + return rc; + + /* Clean up */ + /* TODO: avoid hard number, and replace it. */ + rc = _dw3000_otp_wdata_write(dw, 0x0000); + if (rc) + return rc; + + /* Apply instruction to write data */ + /* TODO: avoid hard number, and replace it. */ + rc = _dw3000_otp_wdata_write(dw, 0x0002); + if (rc) + return rc; + /* TODO: avoid hard number, and replace it. */ + rc = _dw3000_otp_wdata_write(dw, 0x01c0); + if (rc) + return rc; + + /* Write the data */ + /* TODO: use standard function to get expected endianness. */ + wr_buf[0] = 0x100 | ((data >> 24) & 0xff); + wr_buf[1] = 0x100 | ((data >> 16) & 0xff); + wr_buf[2] = 0x100 | ((data >> 8) & 0xff); + wr_buf[3] = 0x100 | (data & 0xff); + rc = _dw3000_otp_wdata_write(dw, wr_buf[3]); + if (rc) + return rc; + rc = _dw3000_otp_wdata_write(dw, wr_buf[2]); + if (rc) + return rc; + rc = _dw3000_otp_wdata_write(dw, wr_buf[1]); + if (rc) + return rc; + rc = _dw3000_otp_wdata_write(dw, wr_buf[0]); + if (rc) + return rc; + + /* Clean up */ + /* TODO: avoid hard number, and replace it. */ + rc = _dw3000_otp_wdata_write(dw, 0x0000); + if (rc) + return rc; + + /* Enter prog mode */ + /* TODO: avoid hard number, and replace it. */ + rc = _dw3000_otp_wdata_write(dw, 0x003a); + if (rc) + return rc; + /* TODO: avoid hard number, and replace it. */ + rc = _dw3000_otp_wdata_write(dw, 0x01ff); + if (rc) + return rc; + /* TODO: avoid hard number, and replace it. */ + rc = _dw3000_otp_wdata_write(dw, 0x010a); + if (rc) + return rc; + /* Clean up */ + /* TODO: avoid hard number, and replace it. */ + rc = _dw3000_otp_wdata_write(dw, 0x0000); + if (rc) + return rc; + + /* Enable state/status output */ + /* TODO: avoid hard number, and replace it. */ + _dw3000_otp_wdata_write(dw, 0x003a); + _dw3000_otp_wdata_write(dw, 0x01bf); + _dw3000_otp_wdata_write(dw, 0x0100); + + /* Start prog mode */ + /* TODO: avoid hard number, and replace it. */ + rc = _dw3000_otp_wdata_write(dw, 0x003a); + if (rc) + return rc; + /* TODO: avoid hard number, and replace it. */ + rc = _dw3000_otp_wdata_write(dw, 0x0101); + if (rc) + return rc; + + /* Different to previous one */ + /* TODO: avoid hard number, and replace it. */ + rc = dw3000_reg_write16(dw, DW3000_NVM_WDATA_ID, 0, 0x0002); + if (rc) + return rc; + /* TODO: avoid hard number, and replace it. */ + rc = dw3000_reg_write16(dw, DW3000_NVM_WDATA_ID, 0, 0x0000); + if (rc) + return rc; + + /** + * Read status after program command. + * 1000 is more than sufficient for max OTP programming delay and max + * supported DW3000 SPI rate. + * Instead a delay of 2ms (as commented out below) can be used. + * Burn time is about 1.76ms + */ + /* TODO: avoid hard number, and replace it. */ + for (i = 0; i < 1000; i++) { + dw3000_reg_read32(dw, DW3000_NVM_STATUS_ID, 0, &rd_buf); + if (!(rd_buf & DW3000_NVM_STATUS_NVM_PROG_DONE_BIT_MASK)) + break; + } + + /* Stop prog mode */ + /* TODO: avoid hard number, and replace it. */ + rc = _dw3000_otp_wdata_write(dw, 0x003a); + if (rc) + return rc; + /* TODO: avoid hard number, and replace it. */ + rc = _dw3000_otp_wdata_write(dw, 0x0102); + if (rc) + return rc; + /* Different to previous one */ + /* TODO: avoid hard number, and replace it. */ + rc = dw3000_reg_write16(dw, DW3000_NVM_WDATA_ID, 0, 0x0002); + if (rc) + return rc; + /* TODO: avoid hard number, and replace it. */ + rc = dw3000_reg_write16(dw, DW3000_NVM_WDATA_ID, 0, 0x0000); + if (rc) + return rc; + + /* Configure mode for reading */ + /* TODO: avoid hard number, and replace it. */ + rc = dw3000_reg_write16(dw, DW3000_NVM_CFG_ID, 0, 0x0000); + if (rc) + return rc; + + /* Restore LDO tune register */ + return dw3000_reg_write32(dw, DW3000_LDO_TUNE_HI_ID, 0, ldo_tune); +} + +/** + * dw3000_otp_write32() - 32 bits OTP memory write. + * @dw: The DW device. + * @addr: 16-bit OTP address into which the 32-bit data is programmed. + * @data: 32-bit data to be programmed into OTP. + * + * This function write a 32-bit data at a given address in the OTP memory + * and checks that that data is correctly written. + * + * Return: zero on success, one if the given data does not match the current + * register value, else a negative error code. + */ +int dw3000_otp_write32(struct dw3000 *dw, u16 addr, u32 data) +{ + u32 tmp; + int rc; + + /* Program the word */ + rc = _dw3000_otp_write32(dw, addr, data); + if (rc) + return rc; + + /* Check it is programmed correctly */ + rc = dw3000_otp_read32(dw, addr, &tmp); + if (rc) + return rc; + + return data != tmp; +} + +/** + * dw3000_prog_xtrim() - Programs the device's crystal frequency + * @dw: The DW device. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_prog_xtrim(struct dw3000 *dw) +{ + struct dw3000_otp_data *otp = &dw->otp_data; + struct dw3000_local_data *local = &dw->data; + int rc; + + if (otp->xtal_trim) { + int value = (int)otp->xtal_trim + local->xtal_bias; + if (value < 0) + value = 0; + else if (value > DW3000_XTAL_TRIM_BIT_MASK) + value = DW3000_XTAL_TRIM_BIT_MASK; + /* Set the XTAL trim value */ + rc = dw3000_reg_write8(dw, DW3000_XTAL_ID, 0, (u8)value); + trace_dw3000_prog_xtrim(dw, rc, value, local->xtal_bias); + if (unlikely(rc)) + return rc; + } + return 0; +} + +/** + * dw3000_set_gpio_mode() - Configure GPIO for selected device + * @dw: The DW device. + * @mask: the bits to reset + * @mode: the bits to set + * + * Clear GPIOs which have been specified by the mask and set GPIOs mode as + * specified. + * + * Return: 0 on success or errno. + */ +int dw3000_set_gpio_mode(struct dw3000 *dw, u32 mask, u32 mode) +{ + return dw3000_reg_modify32(dw, DW3000_GPIO_MODE_ID, 0, ~mask, mode); +} + +/** + * dw3000_set_gpio_dir() - Configure GPIO direction for selected device + * @dw: The DW device. + * @mask: the direction bits to reset + * @dir: the direction bits to set, 1 for input, 0 for output + * + * Return: 0 on success or errno. + */ +int dw3000_set_gpio_dir(struct dw3000 *dw, u16 mask, u16 dir) +{ + return dw3000_reg_modify16(dw, DW3000_GPIO_DIR_ID, 0, ~mask, dir); +} + +/** + * dw3000_set_gpio_out() - Set GPIO output state for selected device + * @dw: The DW device. + * @reset: the output bits to reset + * @set: the output bits to set + * + * Return: 0 on success or errno. + */ +int dw3000_set_gpio_out(struct dw3000 *dw, u16 reset, u16 set) +{ + return dw3000_reg_modify16(dw, DW3000_GPIO_OUT_ID, 0, ~reset, set); +} + +/** + * dw3000_set_lna_pa_mode() - Enable GPIO for external LNA or PA functionality + * @dw: The DW device. + * @lna_pa: LNA/PA configuration to set. + * Can be DW3000_LNA_PA_DISABLE, or an or-ed combination of + * DW3000_LNA_ENABLE, DW3000_PA_ENABLE and DW3000_TXRX_ENABLE. + * + * This is HW dependent, consult the DW3000/DW3700 User Manual. + * + * This can also be used for debug as enabling TX and RX GPIOs is quite handy + * to monitor DW3000/DW3700's activity. + * + * NOTE: Enabling PA functionality requires that fine grain TX sequencing is + * deactivated. This can be done using d3000_setfinegraintxseq(). + * + * Return: 0 on success or errno. + */ +static int dw3000_set_lna_pa_mode(struct dw3000 *dw, int lna_pa) +{ + /* Clear GPIO 4, 5 configuration */ + u32 gpio_mask = (DW3000_GPIO_MODE_MSGP4_MODE_BIT_MASK | + DW3000_GPIO_MODE_MSGP5_MODE_BIT_MASK); + u32 gpio_mode = 0; + + if (__dw3000_chip_version >= DW3000_D0_VERSION) { + if (lna_pa & (DW3000_LNA_ENABLE | DW3000_TXRX_ENABLE)) + gpio_mode |= DW3000_GPIO_PIN5_EXTRXE; + if (lna_pa & (DW3000_PA_ENABLE | DW3000_TXRX_ENABLE)) + gpio_mode |= DW3000_GPIO_PIN4_EXTTXE; + } else { + /* Also clear GPIO 0, 1, 6 configuration */ + gpio_mask |= (DW3000_GPIO_MODE_MSGP0_MODE_BIT_MASK | + DW3000_GPIO_MODE_MSGP1_MODE_BIT_MASK | + DW3000_GPIO_MODE_MSGP6_MODE_BIT_MASK); + if (lna_pa & DW3000_LNA_ENABLE) + gpio_mode |= DW3000_GPIO_PIN6_EXTRX; + if (lna_pa & DW3000_PA_ENABLE) + gpio_mode |= (DW3000_GPIO_PIN4_EXTDA | + DW3000_GPIO_PIN5_EXTTX); + if (lna_pa & DW3000_TXRX_ENABLE) + gpio_mode |= (DW3000_GPIO_PIN0_EXTTXE | + DW3000_GPIO_PIN1_EXTRXE); + } + return dw3000_set_gpio_mode(dw, gpio_mask, gpio_mode); +} + +/** + * dw3000_set_leds() - Configure TX/RX GPIOs to control LEDs + * @dw: The DW device. + * @mode: LEDs configuration to set + * + * This is used to set up Tx/Rx GPIOs which could be used to control LEDs + * + * Note: not completely IC dependent, also needs board with LEDS fitted on + * right I/O lines this function enables GPIOs 2 and 3 which are connected + * to LED3 and LED4 on EVB1000 + * + * Return: 0 on success or errno. + */ +static int dw3000_set_leds(struct dw3000 *dw, u8 mode) +{ + u32 gpio_mask = (DW3000_GPIO_MODE_MSGP3_MODE_BIT_MASK | + DW3000_GPIO_MODE_MSGP2_MODE_BIT_MASK); + int rc; + + if (mode & DW3000_LEDS_ENABLE) { + u32 reg; + /* Set up GPIO for LED output. */ + rc = dw3000_set_gpio_mode(dw, gpio_mask, + DW3000_GPIO_PIN2_RXLED | + DW3000_GPIO_PIN3_TXLED); + if (unlikely(rc)) + return rc; + /* Enable LP Oscillator to run from counter and turn on + de-bounce clock. */ + rc = dw3000_reg_or32( + dw, DW3000_CLK_CTRL_ID, 0, + DW3000_CLK_CTRL_LP_CLK_EN_BIT_MASK | + DW3000_CLK_CTRL_GPIO_DBNC_CLK_EN_BIT_MASK); + if (unlikely(rc)) + return rc; + /* Enable LEDs to blink and set default blink time. */ + reg = DW3000_LED_CTRL_BLINK_EN_BIT_MASK | + DW3000_LEDS_BLINK_TIME_DEF; + /* Make LEDs blink once if requested. */ + if (mode & DW3000_LEDS_INIT_BLINK) { + reg |= DW3000_LED_CTRL_FORCE_TRIGGER_BIT_MASK; + } + rc = dw3000_reg_write32(dw, DW3000_LED_CTRL_ID, 0, reg); + /* Clear force blink bits if needed. */ + if (!rc && (mode & DW3000_LEDS_INIT_BLINK)) { + reg &= ~DW3000_LED_CTRL_FORCE_TRIGGER_BIT_MASK; + rc = dw3000_reg_write32(dw, DW3000_LED_CTRL_ID, 0, reg); + } + } else { + /* Clear the GPIO bits that are used for LED control. */ + rc = dw3000_set_gpio_mode(dw, gpio_mask, 0); + if (unlikely(rc)) + return rc; + rc = dw3000_reg_and16(dw, DW3000_LED_CTRL_ID, 0, + (u16)~DW3000_LED_CTRL_BLINK_EN_BIT_MASK); + } + return rc; +} + +/** + * dw3000_config_antenna_gpio() - Set configuration for the given GPIO + * @dw: The DW device. + * @gpio: GPIO number to configure for antenna switching. + * + * The given GPIO is configured for GPIO mode in output direction. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_config_antenna_gpio(struct dw3000 *dw, int gpio) +{ + int rc = 0; + u32 modemask; + u32 modegpio; + u16 dirmask; + + if (gpio < 4) + modegpio = 2 << (DW3000_GPIO_MODE_MSGP0_MODE_BIT_LEN * gpio); + else if (gpio > 6) + modegpio = 1 << (DW3000_GPIO_MODE_MSGP0_MODE_BIT_LEN * gpio); + else + modegpio = 0; + /* Configure selected GPIO for GPIO mode */ + modemask = DW3000_GPIO_MODE_MSGP0_MODE_BIT_MASK + << (DW3000_GPIO_MODE_MSGP0_MODE_BIT_LEN * gpio); + rc = dw3000_set_gpio_mode(dw, modemask, modegpio); + if (rc) + return rc; + /* Configure selected GPIO for output direction */ + dirmask = DW3000_GPIO_DIR_GDP0_BIT_MASK + << (DW3000_GPIO_DIR_GDP0_BIT_LEN * gpio); + rc = dw3000_set_gpio_dir(dw, dirmask, 0); + return rc; +} + +/** + * dw3000_config_antenna_gpios() - Set configuration for all used GPIO + * @dw: The DW device. + * + * This function configure all GPIO found in antenna table. + * It is called before enabling the DW device in start() MCPS API. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_config_antenna_gpios(struct dw3000 *dw) +{ + char used_gpios[DW3000_GPIO_COUNT] = { 0 }; + int rc, i; + /* Read all used GPIO used as antenna selectors */ + for (i = 0; i < DW3000_CALIBRATION_ANTENNA_MAX; i++) { + u8 selector = dw->calib_data.ant[i].selector_gpio; + if ((selector < DW3000_GPIO_COUNT) && !used_gpios[selector]) { + /* Ensure selected GPIO is well configured */ + rc = dw3000_config_antenna_gpio(dw, selector); + if (rc) + return rc; + /* Ensure it is configured only once in this loop */ + used_gpios[selector] = 1; + } + } + return 0; +} + +/** + * dw3000_set_antenna_gpio() - Set GPIO for the given antenna + * @dw: The DW device. + * @ant_calib: Calibration data for the selected antenna. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_set_antenna_gpio(struct dw3000 *dw, + struct dw3000_antenna_calib *ant_calib) +{ + int gpio = ant_calib->selector_gpio; + int value = ant_calib->selector_gpio_value; + int rc = 0; + if (gpio < DW3000_GPIO_COUNT) { + int offset = DW3000_GPIO_OUT_GOP0_BIT_LEN * gpio; + /* Set GPIO state according config to select this antenna */ + rc = dw3000_set_gpio_out(dw, !value << offset, value << offset); + } + trace_dw3000_set_antenna_gpio(dw, rc, gpio, value); + return rc; +} + +/** + * dw3000_set_tx_antenna() - Configure device to use selected antenna for TX + * @dw: The DW device. + * @ant_set_id: The antennas set id to use + * + * Prepare the DW device to transmit frame using the specified antenna. + * The required HW information (port, gpio and gpio value) must be set + * correctly inside calibration data structure. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_set_tx_antenna(struct dw3000 *dw, int ant_set_id) +{ + struct dw3000_config *config = &dw->config; + struct dw3000_antenna_calib *ant_calib; + int rc; + s8 ant_idx1, ant_idx2; + /* Sanity checks first */ + if (ant_set_id < 0 || ant_set_id >= ANTSET_ID_MAX) + return -EINVAL; + /* Retrieve TX antenna from antenna set */ + dw3000_calib_ant_set_id_to_ant(ant_set_id, &ant_idx1, &ant_idx2); + if (ant_idx1 < 0 && ant_idx2 >= 0) + ant_idx1 = ant_idx2; /* use ant_idx2 if ant_idx1 undefined */ + if (ant_idx1 < 0) { + /* Specified TX antenna must be valid */ + dev_warn(dw->dev, "Bad antennas set id selected (%d)\n", + ant_set_id); + return -EINVAL; + } + /* Retrieve antenna GPIO configuration from calibration data */ + ant_calib = &dw->calib_data.ant[ant_idx1]; + + /* switching to RF2 port for TX if necessary */ + if (ant_calib->port == 1) + dw3000_change_tx_rf_port(dw, ant_calib->port == 1); + + /* Set GPIO state according config to select this antenna */ + rc = dw3000_set_antenna_gpio(dw, ant_calib); + + if (rc) + return rc; + config->ant[ant_calib->port] = ant_idx1; + /* Switching antenna require changing some calibration parameters */ + return dw3000_calib_update_config(dw); +} + +/** + * dw3000_set_rx_antennas() - Set GPIOs to use selected antennas for RX + * @dw: The DW device. + * @ant_set_id: The antennas set id to use + * @pdoa_enabled: True if PDoA is enabled + * @frame_idx: the id of the frame to be rcvd + * + * Return: zero on success, else a negative error code. + */ +int dw3000_set_rx_antennas(struct dw3000 *dw, int ant_set_id, bool pdoa_enabled, int frame_idx) +{ + struct dw3000_config *config = &dw->config; + struct dw3000_antenna_calib *ant_calib; + int rc = 0, port = -1, changed = 0; + s8 ant_idx1, ant_idx2; + /* Sanity checks first */ + if (ant_set_id < 0 || ant_set_id >= ANTSET_ID_MAX) + return -EINVAL; + /* Retrieve RX antennas configuration from antenna set id */ + dw3000_calib_ant_set_id_to_ant(ant_set_id, &ant_idx1, &ant_idx2); + if (pdoa_enabled && (ant_idx1 < 0 || ant_idx2 < 0)) { + dev_warn(dw->dev, + "Bad antennas set id selected (%d) for pdoa\n", + ant_set_id); + return -EINVAL; + } + /* Apply config for first antenna */ + if (ant_idx1 >= 0) { + ant_calib = &dw->calib_data.ant[ant_idx1]; + port = ant_calib->port; /* Save port for later check */ + if (ant_calib->port == 1) { + dw3000_change_rx_rf_port(dw, true); + } else { + dw3000_change_rx_rf_port(dw, false); + } + if (ant_idx1 != config->ant[port]) { + /* Set GPIO state according config for this first antenna */ + rc = dw3000_set_antenna_gpio(dw, ant_calib); + if (rc) + return rc; + config->ant[port] = ant_idx1; + changed++; + } + } + /* Apply config for second antenna */ + if (ant_idx2 >= 0) { + ant_calib = &dw->calib_data.ant[ant_idx2]; + + if (port == ant_calib->port) { + /* Specified RX antenna must be on different port */ + dev_warn( + dw->dev, + "Bad antennas selected or bad configuration ant1=%d (port=%d), ant2=%d (port=%d)\n", + ant_idx1, port, ant_idx2, ant_calib->port); + return -EINVAL; + } + port = ant_calib->port; + if (ant_idx2 != config->ant[port]) { + /* Set GPIO state according config for this second antenna */ + rc = dw3000_set_antenna_gpio(dw, ant_calib); + if (rc) + return rc; + config->ant[port] = ant_idx2; + changed++; + } + } + /* Switching antenna require changing some calibration parameters */ + if (changed) + rc = dw3000_calib_update_config(dw); + return rc; +} + +static void dw3000_mcps_timer_expired(struct work_struct *work) +{ + struct dw3000 *dw = + container_of(work, struct dw3000, timer_expired_work); + mcps802154_timer_expired(dw->llhw); +} + +/** + * dw3000_complete_cir_data - Release consummer when data is ready to be dumped + * @dw: DW device + * Context: cir_data mutex already taken + */ +static void dw3000_complete_cir_data(struct dw3000 *dw) +{ + complete(&dw->cir_data->complete); +} + +/** + * dw3000_read_ciaregs - Collect once all CIA regs required by cir_data + * @dw: DW device + * Return: 0 or negative error code + * Context: cir_data mutex already taken + */ +static int dw3000_read_ciaregs(struct dw3000 *dw) +{ + int rc; + unsigned int length; + unsigned int cia_bank; + + /* Find witch bank is storing CIA data */ + switch (dw->data.dblbuffon) { + case DW3000_DBL_BUFF_ACCESS_BUFFER_B: + dev_warn_once(dw->dev, "double banking not supported\n"); + return -ENOSYS; + case DW3000_DBL_BUFF_ACCESS_BUFFER_A: + cia_bank = DW3000_DB_DIAG_SET_1; + length = DW3000_DB_DIAG_SET_LEN; + break; + default: + cia_bank = 0; + } + + /* Read whole active CIA data bank */ + if (cia_bank) + return dw3000_xfer(dw, cia_bank, 0, length, dw->cir_data->ciaregs, + DW3000_SPI_RD_BIT); + + /* From IP_TS -> STS1_TS_ */ + length = DW3000_DB_DIAG_Reserved4 - DW3000_DB_DIAG_IP_TS; + rc = dw3000_xfer(dw, DW3000_IP_TS_LO_ID, 0, length, + dw->cir_data->ciaregs + + (DW3000_DB_DIAG_IP_TS >> 2), + DW3000_SPI_RD_BIT); + if (rc) + return rc; + /* From IP_DIAG0 -> IP_DIAG8 */ + length = DW3000_DB_DIAG_IP_DIAG8 - DW3000_DB_DIAG_IP_DIAG0 + + DW3000_IP_DIAG8_LEN; + rc = dw3000_xfer(dw, DW3000_IP_DIAG0_ID, 0, length, + dw->cir_data->ciaregs + + (DW3000_DB_DIAG_IP_DIAG0 >> 2), + DW3000_SPI_RD_BIT); + if (rc) + return rc; + /* From STS_DIAG0 -> STS_DIAG3 */ + length = DW3000_DB_DIAG_STS_DIAG3 - DW3000_DB_DIAG_STS_DIAG0 + + DW3000_STS_DIAG_3_LEN; + rc = dw3000_xfer(dw, DW3000_STS_DIAG_0_ID, 0, length, + dw->cir_data->ciaregs + + (DW3000_DB_DIAG_STS_DIAG0 >> 2), + DW3000_SPI_RD_BIT); + if (rc) + return rc; + /* From STS_DIAG4 -> STS1_DIAG12 */ + length = DW3000_DB_DIAG_STS1_DIAG12 - DW3000_DB_DIAG_STS_DIAG4 + + DW3000_STS_DIAG_4_LEN; + rc = dw3000_xfer(dw, DW3000_STS_DIAG_4_ID, 0, length, + dw->cir_data->ciaregs + + (DW3000_DB_DIAG_STS_DIAG4 >> 2), + DW3000_SPI_RD_BIT); + if (rc) + return rc; + /* IP_DIAG_12 */ + length = DW3000_IP_DIAG12_LEN; + rc = dw3000_xfer(dw, DW3000_IP_DIAG12_ID, 0, length, + dw->cir_data->ciaregs + + (DW3000_DB_DIAG_IP_DIAG_12 >> 2), + DW3000_SPI_RD_BIT); + return rc; +} + +/** + * dw3000_read_cir_acc - Get how many records have been stored in the CIR accumulator + * @dw: DW device + * @stsMode: sts mode + * Context: cir_data mutex already taken + */ +static void dw3000_read_cir_acc(struct dw3000 *dw, u8 stsMode) +{ + u32 reg_acc; + u32 mask; + + if (stsMode != DW3000_STS_MODE_OFF) { + reg_acc = DW3000_DB_DIAG_STS_DIAG12 >> 2; + mask = DW3000_STS_DIAG_12_MASK; + } else { + reg_acc = DW3000_DB_DIAG_IP_DIAG_12 >> 2; + mask = DW3000_IP_DIAG12_MASK; + } + + dw->cir_data->acc = __le32_to_cpu(dw->cir_data->ciaregs[reg_acc]) & + mask; +} + +/** + * dw3000_read_fp_power - Get current package power registers + * @dw: DW device + * @stsMode: sts mode + * Context: cir_data mutex already taken + */ +static void dw3000_read_fp_power(struct dw3000 *dw, u8 stsMode) +{ + struct dw3000_cir_data *cir = dw->cir_data; + u32 reg_idx; + u32 reg_f1; + + if (stsMode != DW3000_STS_MODE_OFF) { + reg_idx = DW3000_DB_DIAG_STS_DIAG8 >> 2; + reg_f1 = DW3000_DB_DIAG_STS_DIAG2 >> 2; + } else { + reg_idx = DW3000_DB_DIAG_IP_DIAG8 >> 2; + reg_f1 = DW3000_DB_DIAG_IP_DIAG2 >> 2; + } + + /* Fixed point variable, the First Path index is the integer part only */ + cir->fp_index = __le32_to_cpu(cir->ciaregs[reg_idx]); + cir->fp_index = (cir->fp_index & (1 << 5)) + (cir->fp_index >> 6); + + cir->fp_power1 = __le32_to_cpu(cir->ciaregs[reg_f1]); + cir->fp_power2 = __le32_to_cpu(cir->ciaregs[reg_f1 + 1]); + cir->fp_power3 = __le32_to_cpu(cir->ciaregs[reg_f1 + 2]); +} + +/** + * dw3000_read_cir_data - Read the user requested number of complex numbers from CIR memory + * @dw: DW device + * @stsMode: sts mode + * @prf: prf of current package + * Return: 0 or negative error code + * Context: cir_data mutex already taken + */ +static int dw3000_read_cir_data(struct dw3000 *dw, u8 stsMode, u8 prf) +{ + int rc; + u16 offset; + u16 banksize; + s32 firstpath_idx = dw->cir_data->fp_index; + u8 nrecord = dw->cir_data->count; + + if (stsMode != DW3000_STS_MODE_OFF) { + offset = DW3000_ACC_MEM_STS_OFFSET; + banksize = DW3000_ACC_MEM_STS_SIZE; + } else { + offset = DW3000_ACC_MEM_IPATOV_OFFSET; + if (prf == DW3000_PRF_64M) + banksize = DW3000_ACC_MEM_PRF64_SIZE; + else if (prf == DW3000_PRF_16M) + banksize = DW3000_ACC_MEM_PRF16_SIZE; + else { + dev_info( + dw->dev, + "custom prf used so set default cir bank size\n"); + banksize = DW3000_ACC_MEM_PRF16_SIZE; + } + } + + /* Apply user offset, goes at bank start if out of bank */ + if (dw->cir_data->offset + firstpath_idx < 0) { + firstpath_idx = 0; + } else { + firstpath_idx += dw->cir_data->offset; + } + + /* Avoid going out of bank */ + if (firstpath_idx + nrecord > banksize) + nrecord = banksize - firstpath_idx; + offset += firstpath_idx; + + /* Use indirect addressing to reach data in accumulator */ + rc = dw3000_reg_write32(dw, DW3000_INDIRECT_ADDR_A_ID, 0, + (DW3000_CIR_RAM_ID >> 16)); + if (rc) + return rc; + rc = dw3000_reg_write32(dw, DW3000_ADDR_OFFSET_A_ID, 0, offset); + if (rc) + return rc; + + /* Indirectly read data from the IC to the buffer */ + rc = dw3000_xfer(dw, DW3000_INDIRECT_POINTER_A_ID, 0, + sizeof(struct dw3000_cir_record) * nrecord + 1, + (u8 *)dw->cir_data->data - 1, DW3000_SPI_RD_BIT); + + return rc; +} + +/** + * dw3000_acc_clken - Control clock state to allow cir accumulator dumping + * @dw: DW device + * @enable: true to allow CIR accumulator access + * Return: 0 or negative error code + */ +static int dw3000_acc_clken(struct dw3000 *dw, bool enable) +{ + u16 mask = DW3000_CLK_CTRL_FORCE_ACC_CLK_BIT_MASK | + DW3000_CLK_CTRL_ACC_MEM_CLK_ON_BIT_MASK; + + return dw3000_reg_modify16(dw, DW3000_CLK_CTRL_ID, 0, ~mask, + (!!enable) * mask); +} + +/** + * dw3000_read_frame_cir_data - Producer of debugfs CIR data retrieving mecanism + * @dw: DW device + * @info: mac information about the current packet + * @utime: packet timestamp + * + * This function is called when a packet is received and the associated + * consummer is waiting for data because its debugfs interface file is + * in a blocked read call. If the wanted number of records is correctly + * read from CIR bank this function wakes up the consummer + * + * Return: 0 or negative error code + */ +int dw3000_read_frame_cir_data(struct dw3000 *dw, + struct mcps802154_rx_frame_info *info, u64 utime) +{ + struct dw3000_config *config = &dw->config; + struct dw3000_cir_data *cir = dw->cir_data; + u8 prf = config->txCode >= 9 ? DW3000_PRF_64M : DW3000_PRF_16M; + u8 stsMode = config->stsMode & DW3000_STS_BASIC_MODES_MASK; + int rc = 0; + + /* Check if CIR data gathering is enabled */ + if (!dw->cir_data) + return 0; + + trace_dw3000_read_frame_cir_data(dw, prf, stsMode, utime); + + rc = mutex_lock_interruptible(&cir->mutex); + if (rc) + return -EINTR; + + /* Get packet type */ + cir->type = stsMode; + + /* CIA algorithm have to finish before results reading */ + if (!(dw->rx.flags & DW3000_RX_FLAG_CIA)) { + rc = -ENODATA; + goto read_frame_cir_error; + } + + dw->full_cia_read = false; + rc = dw3000_read_ciaregs(dw); + if (rc) + goto read_frame_cir_error; + dw->full_cia_read = true; + dw3000_read_fp_power(dw, stsMode); + dw3000_read_cir_acc(dw, stsMode); + rc = dw3000_acc_clken(dw, true); + if (rc) + goto read_frame_cir_error; + rc = dw3000_read_cir_data(dw, stsMode, prf); + if (rc) + goto read_frame_cir_error; + rc = dw3000_acc_clken(dw, false); + if (rc) + goto read_frame_cir_error; + cir->utime = utime; + cir->ts = ktime_get_boottime_ns(); + + dw3000_complete_cir_data(dw); + + /* Reset minidiag to allow a new measure */ + rc = dw3000_reg_modify32(dw, DW3000_CIA_CONF_ID, 0, + ~DW3000_CIA_CONF_MINDIAG_BIT_MASK, 0x0); + +read_frame_cir_error: + mutex_unlock(&dw->cir_data->mutex); + trace_dw3000_read_frame_cir_data_return(dw, cir->ts, cir->fp_power1, + cir->fp_power2, cir->fp_power3, + cir->acc, cir->fp_index, + cir->offset, cir->filter); + return rc; +} + +/** + * dw3000_cir_data_alloc_count - Allocation or release ressources for CIR exploitation + * @dw: DW device + * @nrecord: number of imaginary number retrieved per call ; 0 means release + * Return: 0 if ok, a negative error code else + */ +int dw3000_cir_data_alloc_count(struct dw3000 *dw, u16 nrecord) +{ + if (dw->cir_data) { + /* Avoid using dw->cir_data elsewhere and force consumer release + * because pointer target is changed */ + struct dw3000_cir_data *cir = dw->cir_data; + dw->cir_data = NULL; + dw->cir_data_changed = true; + smp_wmb(); + mutex_lock(&cir->mutex); + cir->count = 0; + complete(&cir->complete); + mutex_destroy(&cir->mutex); + kfree(cir); + } + + if (nrecord > 0) { + size_t cirsz = sizeof(struct dw3000_cir_data) + + sizeof(struct dw3000_cir_record) * (nrecord - 1); + dw->cir_data = + (struct dw3000_cir_data *)kzalloc(cirsz, GFP_KERNEL); + if (likely(dw->cir_data)) { + dw->cir_data_changed = true; + init_completion(&dw->cir_data->complete); + mutex_init(&dw->cir_data->mutex); + dw->cir_data->count = nrecord; + } else { + dev_err(dw->dev, + "memory allocation failed for cir data buffer\n"); + return -ENOMEM; + } + } + + return 0; +} + +/** + * dw3000_initialise() - Initialise the DW local data. + * @dw: The DW device. + * + * Make sure the local data is completely reset before starting initialisation. + */ +static void dw3000_initialise(struct dw3000 *dw) +{ + struct dw3000_local_data *local = &dw->data; + struct dw3000_stats *stats = &dw->stats; + struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state; + + /* Double buffer mode off by default / clear the flag */ + local->dblbuffon = DW3000_DBL_BUFF_OFF; + local->sleep_mode = DW3000_RUNSAR; + local->spicrc = DW3000_SPI_CRC_MODE_NO; + /* Clear all register cache variables */ + local->rx_timeout_pac = 0; + local->w4r_time = 0; + local->ack_time = 0; + local->tx_fctrl = 0; + /* Initialise statistics, disabled by default, can be enabled by module + * parameter on load, or via testmode */ + stats->enabled = dw3000_stats_enabled; + memset(stats->count, 0, sizeof(stats->count)); + INIT_WORK(&dw->timer_expired_work, dw3000_mcps_timer_expired); + +#ifdef CONFIG_DW3000_DEBUG + if (dss->regbackup) + kfree(dss->regbackup); + memset(dss, 0, sizeof(*dss)); + dss->regbackup = kzalloc(1024, GFP_KERNEL); + INIT_WORK(&dss->compare_work, dw3000_wakeup_compare); +#else + memset(dss, 0, sizeof(*dss)); +#endif + /* CIR data initialization */ + if (!dw->cir_data) + dw3000_cir_data_alloc_count(dw, + DW3000_DEFAULT_CIR_RECORD_COUNT); + /* Reset time origin for DTU calculation */ + dw->time_zero_ns = ktime_get_boottime_ns(); +} + +/** + * dw3000_transfers_free() - Free allocated SPI messages + * @dw: the DW device to free the SPI messages + */ +void dw3000_transfers_free(struct dw3000 *dw) +{ + /* fast command message, only one transfer */ + dw3000_free_fastcmd(dw->msg_fast_command); + dw->msg_fast_command = NULL; + /* specific pre-computed read/write full-duplex messages */ + dw3000_free_xfer(dw->msg_read_rdb_status, 1); + dw->msg_read_rdb_status = NULL; + dw3000_free_xfer(dw->msg_read_rx_timestamp, 1); + dw->msg_read_rx_timestamp = NULL; + dw3000_free_xfer(dw->msg_read_rx_timestamp_a, 1); + dw->msg_read_rx_timestamp_a = NULL; + dw3000_free_xfer(dw->msg_read_rx_timestamp_b, 1); + dw->msg_read_rx_timestamp_b = NULL; + dw3000_free_xfer(dw->msg_read_sys_status, 1); + dw->msg_read_sys_status = NULL; + dw3000_free_xfer(dw->msg_read_all_sys_status, 1); + dw->msg_read_all_sys_status = NULL; + dw3000_free_xfer(dw->msg_read_sys_time, 1); + dw->msg_read_sys_time = NULL; + dw3000_free_xfer(dw->msg_write_sys_status, 1); + dw->msg_write_sys_status = NULL; + dw3000_free_xfer(dw->msg_write_all_sys_status, 1); + dw->msg_write_all_sys_status = NULL; + dw3000_free_xfer(dw->msg_read_dss_status, 1); + dw->msg_read_dss_status = NULL; + dw3000_free_xfer(dw->msg_write_dss_status, 1); + dw->msg_write_dss_status = NULL; + dw3000_free_xfer(dw->msg_write_spi_collision_status, 1); + dw->msg_write_spi_collision_status = NULL; + /* generic read/write full-duplex message */ + dw3000_free_xfer(dw->msg_readwrite_fdx, 1); + dw->msg_readwrite_fdx = NULL; + /* message queue */ + kfree(dw->msg_queue_buf); + dw->msg_queue_buf = NULL; + kfree(dw->msg_queue); + dw->msg_queue = NULL; +} + +/** + * dw3000_transfers_init() - Allocate SPI messages + * @dw: the DW device to allocate SPI message for + * + * Allocate and pre-compute SPI messages to allow minimum overhead + * each time a specific read/write operation occurs. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_transfers_init(struct dw3000 *dw) +{ + /* fast command message, only one transfer */ + dw->msg_fast_command = dw3000_alloc_prepare_fastcmd(); + if (!dw->msg_fast_command) + goto alloc_err; + /* specific pre-computed read/write full-duplex messages */ + dw->msg_read_rdb_status = dw3000_alloc_prepare_xfer( + dw, DW3000_RDB_STATUS_ID, 0, 1, DW3000_SPI_RD_BIT); + if (!dw->msg_read_rdb_status) + goto alloc_err; + dw->msg_read_rx_timestamp = dw3000_alloc_prepare_xfer( + dw, DW3000_RX_TIME_0_ID, 0, DW3000_RX_TIME_RX_STAMP_LEN, + DW3000_SPI_RD_BIT); + if (!dw->msg_read_rx_timestamp) + goto alloc_err; + dw->msg_read_rx_timestamp_a = dw3000_alloc_prepare_xfer( + dw, DW3000_DB_DIAG_SET_1, DW3000_DB_DIAG_RX_TIME, + DW3000_RX_TIME_RX_STAMP_LEN, DW3000_SPI_RD_BIT); + if (!dw->msg_read_rx_timestamp_a) + goto alloc_err; + dw->msg_read_rx_timestamp_b = dw3000_alloc_prepare_xfer( + dw, DW3000_INDIRECT_POINTER_B_ID, DW3000_DB_DIAG_RX_TIME, + DW3000_RX_TIME_RX_STAMP_LEN, DW3000_SPI_RD_BIT); + if (!dw->msg_read_rx_timestamp_b) + goto alloc_err; + dw->msg_read_sys_status = dw3000_alloc_prepare_xfer( + dw, DW3000_SYS_STATUS_ID, 0, 4, DW3000_SPI_RD_BIT); + if (!dw->msg_read_sys_status) + goto alloc_err; + dw->msg_read_all_sys_status = dw3000_alloc_prepare_xfer( + dw, DW3000_SYS_STATUS_ID, 0, 8, DW3000_SPI_RD_BIT); + if (!dw->msg_read_sys_status) + goto alloc_err; + dw->msg_read_sys_time = dw3000_alloc_prepare_xfer( + dw, DW3000_SYS_TIME_ID, 0, 4, DW3000_SPI_RD_BIT); + if (!dw->msg_read_sys_time) + goto alloc_err; + dw->msg_write_sys_status = dw3000_alloc_prepare_xfer( + dw, DW3000_SYS_STATUS_ID, 0, 4, DW3000_SPI_WR_BIT); + if (!dw->msg_write_sys_status) + goto alloc_err; + dw->msg_write_all_sys_status = dw3000_alloc_prepare_xfer( + dw, DW3000_SYS_STATUS_ID, 0, 8, DW3000_SPI_WR_BIT); + if (!dw->msg_write_all_sys_status) + goto alloc_err; + dw->msg_read_dss_status = dw3000_alloc_prepare_xfer( + dw, DW3000_DSS_STAT_ID, 0, 1, DW3000_SPI_RD_BIT); + if (!dw->msg_read_dss_status) + goto alloc_err; + dw->msg_write_dss_status = dw3000_alloc_prepare_xfer( + dw, DW3000_DSS_STAT_ID, 0, 1, DW3000_SPI_WR_BIT); + if (!dw->msg_write_dss_status) + goto alloc_err; + dw->msg_write_spi_collision_status = dw3000_alloc_prepare_xfer( + dw, DW3000_SPI_COLLISION_STATUS_ID, 0, 1, DW3000_SPI_WR_BIT); + if (!dw->msg_write_spi_collision_status) + goto alloc_err; + /* generic read/write full-duplex message */ + dw->msg_readwrite_fdx = + dw3000_alloc_prepare_xfer(dw, 0, 0, 16, DW3000_SPI_RD_BIT); + if (!dw->msg_readwrite_fdx) + goto alloc_err; + /* mutex protecting msg_readwrite_fdx */ + mutex_init(&dw->msg_mutex); + /* message queue */ + dw->msg_queue_buf = kzalloc(DW3000_QUEUED_SPI_BUFFER_SZ, GFP_KERNEL); + if (!dw->msg_queue_buf) + goto alloc_err; + dw->msg_queue = kzalloc(sizeof(struct spi_message) + + DW3000_MAX_QUEUED_SPI_XFER * + sizeof(struct spi_transfer), + GFP_KERNEL); + return 0; + +alloc_err: + dw3000_transfers_free(dw); + return -ENOMEM; +} + +/** + * dw3000_transfers_reset() - Reset allocated SPI messages + * @dw: the DW device for which SPI messages speed must be reset + * + * This ensure ALL transfers are clean, without an existing SPI speed. + * This function must be called each time after SPI speed is changed. + * + * Return: zero on success, else a negative error code. + */ +static int dw3000_transfers_reset(struct dw3000 *dw) +{ + dw3000_transfers_free(dw); + return dw3000_transfers_init(dw); +} + +/** + * dw3000_init() - Initialise device + * @dw: DW3000 device + * @check_idlerc: Check if the device is in IDLE_RC state + * + * Return: zero on success, else a negative error code. + */ +int dw3000_init(struct dw3000 *dw, bool check_idlerc) +{ + int rc; + + /* Initialise device structure first */ + dw3000_initialise(dw); + + if (check_idlerc) { + /* The DW IC should be in IDLE_RC state and ready */ + if (!dw3000_check_idlerc(dw)) { + dev_err(dw->dev, "device not in IDLE_RC state\n"); + return -EINVAL; + } + } + + /* Read LDO_TUNE and BIAS_TUNE from OTP */ + /* This is device specific */ + rc = dw->chip_ops->prog_ldo_and_bias_tune(dw); + if (unlikely(rc)) { + dev_err(dw->dev, + "device LDO & bias tune setup has failed (%d)\n", rc); + return rc; + } + /* Read and init XTRIM */ + rc = dw3000_prog_xtrim(dw); + if (unlikely(rc)) { + dev_err(dw->dev, "device XTRIM setup has failed (%d)\n", rc); + return rc; + } + /* Read and init coarse code from OTP*/ + /* Configure PLL coarse code, if needed. */ + if (dw->chip_ops->prog_pll_coarse_code) { + rc = dw->chip_ops->prog_pll_coarse_code(dw); + if (unlikely(rc)) { + dev_err(dw->dev, + "device coarse code setup has failed (%d)\n", + rc); + return rc; + } + } + /* Ensure STS fields are double-buffered if enabled, also enable stats + * if configured in module parameters */ + rc = dw3000_configure_ciadiag(dw, dw->stats.enabled, + dw->data.dblbuffon ? + DW3000_CIA_DIAG_LOG_DBL_MID : + DW3000_CIA_DIAG_LOG_DBL_OFF); + if (unlikely(rc)) { + dev_err(dw->dev, "device CIA DIAG setup has failed (%d)\n", rc); + return rc; + } + /* Do some device specific initialisation if any */ + rc = dw->chip_ops->init(dw); + if (unlikely(rc)) { + dev_err(dw->dev, "device chip specific init has failed (%d)\n", + rc); + return rc; + } + /* Configure radio frequency */ + rc = dw3000_configure(dw); + if (unlikely(rc)) { + dev_err(dw->dev, "device configuration has failed (%d)\n", rc); + return rc; + } + /* Initialise LEDs */ + rc = dw3000_set_leds(dw, DW3000_LEDS_DISABLE); + if (unlikely(rc)) + return rc; + /* Initialise LNA/PA modes */ + rc = dw3000_set_lna_pa_mode(dw, dw->lna_pa_mode); + if (unlikely(rc)) + return rc; + /* Configure delays */ + rc = dw3000_set_antenna_delay(dw, 0); + if (unlikely(rc)) + return rc; + /* Set auto-ack delay. */ + rc = dw3000_set_autoack_reply_delay( + dw, DW3000_NUMBER_OF_SYMBOL_DELAY_AUTO_ACK); + if (unlikely(rc)) + return rc; + rc = dw3000_disable_autoack(dw, true); + if (unlikely(rc)) + return rc; + /* WiFi coexistence initialisation */ + rc = dw3000_coex_init(dw); + if (unlikely(rc)) + return rc; + /* Configure antenna selection GPIO if any */ + return dw3000_config_antenna_gpios(dw); +} + +void dw3000_remove(struct dw3000 *dw) +{ + struct dw3000_rx *rx = &dw->rx; + unsigned long flags; + + /* Free RX's socket buffer if not claimed */ + spin_lock_irqsave(&rx->lock, flags); + if (rx->skb) + dev_kfree_skb_any(rx->skb); + rx->skb = NULL; + spin_unlock_irqrestore(&rx->lock, flags); + + /* Stop device */ + dw3000_disable(dw); +} + +/** + * dw3000_enable() - Enable the device + * @dw: the DW device + * + * This function masks the device's internal interruptions (fixed + * configuration) then enables the IRQ linked to the device interruption line. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_enable(struct dw3000 *dw) +{ + /* Select the events that will generate an interruption */ + int rc = dw3000_set_interrupt(dw, DW3000_SYS_STATUS_TRX, + DW3000_ENABLE_INT_ONLY); + if (rc) + return rc; + /* Enable interrupt that will call the worker */ + enable_irq(dw->spi->irq); + + /* Update interface status */ + atomic_set(&dw->iface_is_started, true); + return 0; +} + +/** + * dw3000_disable() - Disable the device + * @dw: the DW device + * + * This function disables the IRQ linked to the device interruption line, + * unmasks the device's internal interruptions then clears its pending + * interruptions. + * + * Return: zero on success, else a negative error code. + */ +int dw3000_disable(struct dw3000 *dw) +{ + int rc; + /* Update internal device's status */ + atomic_set(&dw->iface_is_started, false); + /* Handle DEEP-SLEEP active case early */ + if (dw->current_operational_state < DW3000_OP_STATE_INIT_RC) { + /* Seems chip is sleeping or already power-off. Just ensure + wake-up timer will not fire since not required anymore. */ + dw3000_idle_cancel_timer(dw); + /* No need to call disable_irq() since already called and this + will require calling two times enable_irq() later because + enable/disable_irq are nested! */ + return 0; + } + /* No IRQs after this point */ + disable_irq(dw->spi->irq); + /* Disable further interrupt generation */ + rc = dw3000_set_interrupt(dw, 0, DW3000_ENABLE_INT_ONLY); + if (rc) + goto err_spi; + /* Release Wifi coexistence. */ + dw3000_coex_stop(dw); + /* Disable receiver and transmitter */ + rc = dw3000_forcetrxoff(dw); + if (rc) + goto err_spi; + /* Clear pending ALL interrupts */ + rc = dw3000_clear_sys_status(dw, (u32)-1); + if (rc) + goto err_spi; + return 0; + +err_spi: + return rc; +} + +void dw3000_init_config(struct dw3000 *dw) +{ + int i, j, k; + /* Default configuration */ + const struct dw3000_txconfig txconfig = { + .PGdly = 0x34, + .PGcount = 0, + .power = 0xfefefefe, + .smart = false, + .testmode_enabled = false, + }; + const struct dw3000_config config = { + .chan = 5, + .txPreambLength = DW3000_PLEN_64, + .txCode = 9, + .rxCode = 9, + .sfdType = DW3000_SFD_TYPE_4Z, + .dataRate = DW3000_BR_6M8, + .phrMode = DW3000_PHRMODE_STD, + .phrRate = DW3000_PHRRATE_STD, + .sfdTO = DW3000_SFDTOC_DEF, + .stsMode = DW3000_STS_MODE_OFF | DW3000_STS_MODE_SDC, + .stsLength = DW3000_STS_LEN_64, + .pdoaMode = DW3000_PDOA_M0, + .pdoaOffset = 0, + .ant = { -1, -1 }, + }; + /* Set default configuration */ + dw->config = config; + dw->txconfig = txconfig; + for (i = 0; i < DW3000_CALIBRATION_ANTENNA_MAX; i++) { + /* Ensure no GPIO pin are configured by default */ + dw->calib_data.ant[i].selector_gpio = 0xff; + /* Ensure all antennas have the default antenna delay */ + for (j = 0; j < DW3000_CALIBRATION_CHANNEL_MAX; j++) { + for (k = 0; k < DW3000_CALIBRATION_PRF_MAX; k++) + dw->calib_data.ant[i].ch[j].prf[k].ant_delay = + DW3000_DEFAULT_ANT_DELAY; + } + } + for (i = 0; i < ANTPAIR_MAX; i++) { + memcpy(dw->calib_data.antpair[i] + .ch[DW3000_CALIBRATION_CHANNEL_5] + .pdoa_lut, + dw3000_default_lut_ch5, sizeof(dw3000_pdoa_lut_t)); + memcpy(dw->calib_data.antpair[i] + .ch[DW3000_CALIBRATION_CHANNEL_9] + .pdoa_lut, + dw3000_default_lut_ch9, sizeof(dw3000_pdoa_lut_t)); + } + /* Set default antenna ports configuration */ + dw->calib_data.ant[0].port = 0; + dw->calib_data.ant[1].port = 1; + /* By default WiFi Coex is enabled */ + dw->coex_enabled = true; + for (i = 0; i < DW3000_CALIBRATION_CHANNEL_MAX; i++) + dw->calib_data.ch[i].wifi_coex_enabled = true; + /* Ensure power stats timing start at load time */ + dw->power.cur_state = DW3000_PWR_OFF; + dw->power.stats[DW3000_PWR_OFF].count = 1; + dw->power.start_time = ktime_get_boottime_ns(); + dw3000_nfcc_coex_init(dw); + /* Set interface status */ + atomic_set(&dw->iface_is_started, false); + + /* Initialize dw3000_rx spinlock */ + spin_lock_init(&dw->rx.lock); +} + +static inline int dw3000_isr_handle_spi_ready(struct dw3000 *dw, + struct dw3000_isr_data *isr) +{ + struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state; + const dw3000_wakeup_done_cb wakeup_done_cb = dw->wakeup_done_cb; + int rc; + + if (dw->current_operational_state != DW3000_OP_STATE_WAKE_UP) { + /* Not waking-up from DEEP SLEEP state, just change state and + return */ + if (dw->current_operational_state < DW3000_OP_STATE_IDLE_RC && + isr->status & DW3000_SYS_STATUS_SPIRDY_BIT_MASK) { + /* This is a normal SPIRDY IRQ after power-on. Wake-up + * thread waiting for it. Chip is at least in IDLE_RC */ + dw3000_set_operational_state(dw, + DW3000_OP_STATE_IDLE_RC); + } + return 0; + } + + /* + * Second part of the Wake-up from DEEP SLEEP below. + * + * TODO: The RX with auto ACK is broken after DEEP SLEEP and WAKEUP. + * Please see UWB-1037. + */ + + /* Update power statistics */ + dw3000_power_stats(dw, DW3000_PWR_RUN, 0); + + /* TODO: A full initialization with dw3000_init() CANNOT be used + * because it reset all cached value and won't allow put back DW + * in exactly same state than before entering DEEP SLEEP. + * + * dw3000_configure_chan() can be enough, but it hangs the remote + * host on TX. Please see UWB-1032. + * + * The AON memory was copied to register, so only STS & AES keys + * and some others non saved register need to be re-initialised. + */ + + if (dss->config_changed & + (DW3000_CHANNEL_CHANGED | DW3000_PCODE_CHANGED)) { + /* Channel or preamble code was changed during DEEP-SLEEP. + * Need to apply required configuration BEFORE PLL LOCK */ + if (dss->config_changed & DW3000_CHANNEL_CHANGED) + /* Reconfigure all channel dependent */ + rc = dw3000_configure_chan(dw); + else if (dss->config_changed & DW3000_PCODE_CHANGED) + /* Only change CHAN_CTRL with new code */ + rc = dw3000_configure_pcode(dw); + else + rc = 0; /* remove uninit variable error */ + if (rc) + return rc; + dss->config_changed &= + ~(DW3000_CHANNEL_CHANGED | DW3000_PCODE_CHANGED); + /* TODO: If channel is changed, the ongoing automatic PLL + * locking (see SEQ_CTRL & AON_DIG_CFG) might be stopped! + * If required, do it here and let the call below redo it + * properly. */ + } + + /* Preamble length or data rate were changed during DEEP-SLEEP. */ + if (dss->config_changed & + (DW3000_PREAMBLE_LENGTH_CHANGED | DW3000_DATA_RATE_CHANGED)) { + rc = dw3000_configure_preamble_length_and_datarate( + dw, !(dss->config_changed & + DW3000_PREAMBLE_LENGTH_CHANGED)); + if (rc) + return rc; + } + + /* SFD was changed during DEEP-SLEEP. */ + if (dss->config_changed & DW3000_SFD_CHANGED) { + rc = dw3000_configure_sfd_type(dw); + if (rc) + return rc; + } + + /* PHR rate was changed during DEEP-SLEEP. */ + if (dss->config_changed & DW3000_PHR_RATE_CHANGED) { + rc = dw3000_configure_phr_rate(dw); + if (rc) + return rc; + } + + /* Auto calibrate the PLL and change to IDLE_PLL state */ + rc = dw3000_lock_pll(dw, isr->status); + if (rc) + return rc; + + /* Trace Wakeup after DTU/SYS_TIME resync */ + { + u32 next_date_dtu; + s64 sleep_ns; + sleep_ns = dw3000_dtu_to_ktime(dw, dw->dtu_sync) - + dw3000_dtu_to_ktime(dw, dw->sleep_enter_dtu); + next_date_dtu = dss->next_operational_state == + DW3000_OP_STATE_RX ? + dss->rx_config.timestamp_dtu : + dss->tx_config.timestamp_dtu; + trace_dw3000_wakeup_done(dw, sleep_ns / 1000, + dw->sleep_enter_dtu, next_date_dtu, + dss->next_operational_state); + } + + /* PGF calibration */ + rc = dw3000_pgf_cal(dw, 1); + if (rc) + return rc; + /* DGC LUT */ + rc = dw3000_restore_dgc(dw); + if (rc) + return rc; + /* Calibrate ADC offset, if needed, after DGC configuration and after PLL lock. + * If this calibration is executed before the PLL lock, the PLL lock failed. + */ + if (dw->chip_ops->adc_offset_calibration) { + rc = dw->chip_ops->adc_offset_calibration(dw); + if (rc) + goto setuperror; + } + /* WiFi coexistence initialisation */ + rc = dw3000_coex_init(dw); + if (rc) + goto setuperror; + /* Configure antenna selection GPIO if any */ + rc = dw3000_config_antenna_gpios(dw); + if (rc) + goto setuperror; + /* Reset cached antenna config to ensure GPIO are well reconfigured */ + dw->config.ant[0] = -1; + dw->config.ant[1] = -1; + + /* Select the events that will generate an interruption */ + rc = dw3000_set_interrupt(dw, DW3000_SYS_STATUS_TRX, + DW3000_ENABLE_INT_ONLY); + if (rc) + goto setuperror; + + rc = dw3000_reconfigure_hw_addr_filt(dw); + if (rc) + goto setuperror; + + if ((dw->config.stsMode & DW3000_STS_BASIC_MODES_MASK) && + !(dw->config.stsMode & DW3000_STS_MODE_SDC)) { + /* Resend key non-NUL STS KEY */ + __le64 swapped_key[AES_KEYSIZE_128 / sizeof(__le64)]; + _swap128(swapped_key, dw->data.sts_key); + rc = _dw3000_reg_write(dw, DW3000_STS_KEY_ID, 0, + AES_KEYSIZE_128, swapped_key); + if (rc) + goto setuperror; + } else { + /* STS wasn't activate before entering DEEP-SLEEP, invalidate + STS key to ensure it is resent to the chip the next time it + is changed by MCPS. */ + memset(dw->data.sts_key, 0, AES_KEYSIZE_128); + dw->config.stsMode |= DW3000_STS_MODE_SDC; + } + + /* TODO: So, just add below this line more required unsaved registers + * setup. */ + rc = dw3000_reg_write32(dw, DW3000_LDO_VOUT_ID, 0, DW3000_RF_LDO_VOUT); + if (rc) + return rc; + +setuperror: +#ifdef CONFIG_DW3000_DEBUG + /* Read and check configuration */ + rc = dw3000_backup_registers(dw, true); + if (rc) + return rc; +#endif + if (wakeup_done_cb) { + /* Consume the callback handler before execution. */ + dw->wakeup_done_cb = NULL; + rc = wakeup_done_cb(dw); + if (rc == -ETIME) { + if (wakeup_done_cb == dw3000_wakeup_done_to_tx) + mcps802154_tx_too_late(dw->llhw); + else + mcps802154_rx_too_late(dw->llhw); + + rc = 0; + } + } + return rc; +} + +static inline int dw3000_isr_handle_timer_events(struct dw3000 *dw) +{ + /* TODO: call chip specific method if special work is required */ + return 0; +} + +static inline int dw3000_isr_handle_spi_error(struct dw3000 *dw) +{ + dev_warn(dw->dev, "no support for callback %s", __func__); + return 0; +} + +/** + * dw3000_signal_rx_buff_free() - signal the current RX buffer is free + * @dw: the DW device + * @dblbuffon: double buffer mode + * + * Check if in double buffer mode and if so which buffer host is currently + * accessing. Then Free up the current buffer and let the device know that + * it can receive into this buffer again + * + * Return: zero on success, else a negative error code. + */ +static inline int dw3000_signal_rx_buff_free(struct dw3000 *dw, u8 *dblbuffon) +{ + if (*dblbuffon != DW3000_DBL_BUFF_OFF) { + /* Toggle buffer on chip */ + int rc = dw3000_write_fastcmd(dw, DW3000_CMD_DB_TOGGLE); + + if (likely(!rc)) { + /* Update the current buffer status */ + *dblbuffon ^= DW3000_DBL_BUFF_SWAP; + } + } + return 0; +} + +/** + * dw3000_read_frame_info16() - read the 16-bit frame information + * @dw: the DW device + * @dblbuffon: double buffer mode + * @finfo: the 16-bit value of the RX frame information + * + * Check if in double buffer mode and if so which buffer host is + * currently accessing to clear corresponding DB status register + * and return lower 16 bits of RX frame info register. + * + * Return: zero on success, else a negative error code. + */ +static inline int dw3000_read_frame_info16(struct dw3000 *dw, u8 dblbuffon, + u16 *finfo) +{ + int regfile_id; + int rc; + + switch (dblbuffon) { + case DW3000_DBL_BUFF_ACCESS_BUFFER_B: + /* clear DB status register bits corresponding to RX_BUFFER_B */ + rc = dw3000_reg_write8(dw, DW3000_RDB_STATUS_ID, 0, 0x70); + if (unlikely(rc)) + return rc; + /* accessing frame info relating to the second buffer (RX_BUFFER_B) */ + regfile_id = DW3000_INDIRECT_POINTER_B_ID; + break; + case DW3000_DBL_BUFF_ACCESS_BUFFER_A: + /* clear DB status register bits corresponding to RX_BUFFER_A */ + rc = dw3000_reg_write8(dw, DW3000_RDB_STATUS_ID, 0, 0x7); + if (unlikely(rc)) + return rc; + /* accessing frame info relating to the first buffer (RX_BUFFER_A) */ + regfile_id = DW3000_DB_DIAG_SET_1; + break; + default: + /* accessing frame info in single buffer mode */ + regfile_id = DW3000_RX_FINFO_ID; + break; + } + return dw3000_reg_read16(dw, regfile_id, 0, finfo); +} + +static inline int dw3000_isr_handle_rx_call_handler(struct dw3000 *dw, + struct dw3000_isr_data *isr) +{ + u32 eof_dtu; + int rc; + + /* Store LDE/STS RX errors in rx_flags */ + if (isr->status & DW3000_SYS_STATUS_CIAERR_BIT_MASK) + isr->rx_flags |= DW3000_RX_FLAG_CER; + else if (isr->status & DW3000_SYS_STATUS_CIA_DONE_BIT_MASK) + isr->rx_flags |= DW3000_RX_FLAG_CIA; + if (isr->status & DW3000_SYS_STATUS_CPERR_BIT_MASK) + isr->rx_flags |= DW3000_RX_FLAG_CPER; + /* In case of automatic ack reply. */ + if (isr->status & DW3000_SYS_STATUS_AAT_BIT_MASK) + isr->rx_flags |= DW3000_RX_FLAG_AACK; + /* Read frame timestamp */ + rc = dw3000_read_rx_timestamp(dw, &isr->ts_rctu); + if (unlikely(rc)) + return rc; + isr->rx_flags |= DW3000_RX_FLAG_TS; /* don't read it again later */ + eof_dtu = dw3000_sys_time_rctu_to_dtu(dw, isr->ts_rctu) + + dw3000_frame_duration_dtu(dw, isr->datalength, false); + /* Update power statistics */ + dw3000_power_stats(dw, DW3000_PWR_IDLE, eof_dtu); + /* Report received frame */ + rc = dw3000_rx_frame(dw, isr); + if (unlikely(rc)) + return rc; + /* Handle double buffering */ + return dw3000_signal_rx_buff_free(dw, &dw->data.dblbuffon); +} + +static inline int dw3000_isr_handle_rxfcg_event(struct dw3000 *dw, + struct dw3000_isr_data *isr) +{ + const u32 clear = DW3000_SYS_STATUS_ALL_RX_GOOD | + DW3000_SYS_STATUS_CIAERR_BIT_MASK | + DW3000_SYS_STATUS_CPERR_BIT_MASK; + u16 finfo16; + int rc; + + /* Read frame info, only the first two bytes of the register are used + here. */ + rc = dw3000_read_frame_info16(dw, dw->data.dblbuffon, &finfo16); + if (unlikely(rc)) { + dev_err(dw->dev, "could not read the frame info : %d\n", rc); + return rc; + } + /* Report frame length, standard frame length up to 127, extended frame + length up to 1023 bytes */ + isr->datalength = + (finfo16 & dw->data.max_frames_len) - IEEE802154_FCS_LEN; + /* Report ranging bit */ + if (finfo16 & DW3000_RX_FINFO_RNG_BIT_MASK) + isr->rx_flags = DW3000_RX_FLAG_RNG; + else + isr->rx_flags = 0; + rc = dw3000_isr_handle_rx_call_handler(dw, isr); + /* Clear errors (as we do not want to go back into cbRxErr) */ + isr->status &= ~clear; + return rc; +} + +static inline int dw3000_isr_handle_rxfr_sts_event(struct dw3000 *dw, + struct dw3000_isr_data *isr) +{ + const u32 clear = DW3000_SYS_STATUS_ALL_RX_GOOD | + DW3000_SYS_STATUS_RXFCE_BIT_MASK | + DW3000_SYS_STATUS_CIAERR_BIT_MASK | + DW3000_SYS_STATUS_CPERR_BIT_MASK; + int rc; + isr->datalength = 0; + rc = dw3000_isr_handle_rx_call_handler(dw, isr); + /* Clear errors (as we do not want to go back into cbRxErr) */ + isr->status &= ~clear; + return rc; +} + +static inline int dw3000_isr_handle_rxto_event(struct dw3000 *dw, u32 status) +{ + u32 end_dtu; + int rc; + /* If rx_disable() callback was called, we can't call mcps802154_rx_timeout() */ + if (dw3000_rx_busy(dw, true)) + return 0; + /* Update power statistics */ + end_dtu = dw->power.rx_start + (dw->data.rx_timeout_pac + 1) * + dw->chips_per_pac / + DW3000_CHIP_PER_DTU; + dw3000_power_stats(dw, DW3000_PWR_IDLE, end_dtu); + /* Report statistics */ + rc = dw3000_rx_stats_inc(dw, DW3000_STATS_RX_TO, NULL); + if (unlikely(rc)) + goto err; + /* Inform upper layer */ + if (status & DW3000_SYS_STATUS_RXFTO_BIT_MASK) + dev_dbg(dw->dev, "rx frame timeout"); + else + dev_dbg(dw->dev, "rx preamble timeout"); + mcps802154_rx_timeout(dw->llhw); +err: + WARN_ON_ONCE(dw3000_rx_busy(dw, false)); + return rc; +} + +static inline int dw3000_isr_handle_rxerr_event(struct dw3000 *dw, u32 status) +{ + struct mcps802154_llhw *llhw = dw->llhw; + enum mcps802154_rx_error_type error; + + /* If rx_disable() callback was called, we can't call mcps802154_rx_error() */ + if (dw3000_rx_busy(dw, true)) + return 0; + /* Update power statistics */ + dw3000_power_stats(dw, DW3000_PWR_IDLE, 0); + /* Map error to mcps802154_rx_error_type enum */ + if (status & (DW3000_SYS_STATUS_RXPTO_BIT_MASK | + DW3000_SYS_STATUS_RXFTO_BIT_MASK)) { + dev_dbg(dw->dev, "rx preamble timeout\n"); + error = MCPS802154_RX_ERROR_TIMEOUT; + } else if (status & DW3000_SYS_STATUS_RXSTO_BIT_MASK) { + dev_dbg(dw->dev, "rx sfd timeout\n"); + error = MCPS802154_RX_ERROR_SFD_TIMEOUT; + } else if (status & DW3000_SYS_STATUS_ARFE_BIT_MASK) { + u32 time; + + dw3000_reg_read32(dw, DW3000_RX_TIME_0_ID, 0, &time); + dev_dbg(dw->dev, "rx rejected %08x\n", time); + error = MCPS802154_RX_ERROR_FILTERED; + } else if (status & DW3000_SYS_STATUS_RXFCE_BIT_MASK) { + dev_dbg(dw->dev, "bad checksum\n"); + error = MCPS802154_RX_ERROR_BAD_CKSUM; + } else if (status & DW3000_SYS_STATUS_RXPHE_BIT_MASK) { + dev_dbg(dw->dev, "rx phr error\n"); + error = MCPS802154_RX_ERROR_PHR_DECODE; + } else if (status & DW3000_SYS_STATUS_RXFSL_BIT_MASK) { + dev_dbg(dw->dev, "rx sync loss\n"); + error = MCPS802154_RX_ERROR_OTHER; + } else { + dev_dbg(dw->dev, "rx error 0x%x\n", status); + error = MCPS802154_RX_ERROR_OTHER; + } + /* Report RX error event */ + mcps802154_rx_error(llhw, error); + + WARN_ON_ONCE(dw3000_rx_busy(dw, false)); + return 0; +} + +static inline int dw3000_isr_handle_tx_event(struct dw3000 *dw, + struct dw3000_isr_data *isr) +{ + /* Update power statistics */ + dw3000_power_stats(dw, DW3000_PWR_IDLE, 0); + if (dw->data.w4r_time) { + /* W4R configured, switch automatically to RX state. + RX start time is unknown here but we need it, so get current + DTU time. This is subject to IRQ delay but don't known better + solution here. */ + u32 cur_dtu_time = dw3000_get_dtu_time(dw); + dw3000_power_stats(dw, DW3000_PWR_RX, + cur_dtu_time + dw->data.w4r_time * + DW3000_DTU_PER_DLY); + } + /* Report completion to MCPS 802.15.4 stack */ + mcps802154_tx_done(dw->llhw); + /* Clear TXFRS status to not handle it a second time. */ + isr->status &= ~DW3000_SYS_STATUS_TXFRS_BIT_MASK; + return 0; +} + +static inline int dw3000_clear_db_events(struct dw3000 *dw) +{ + struct dw3000_local_data *local = &dw->data; + + if (local->dblbuffon == DW3000_DBL_BUFF_ACCESS_BUFFER_A) { + /* clear RX events relating to buffer A */ + return dw3000_reg_write8(dw, DW3000_RDB_STATUS_ID, 0, + DW3000_RDB_STATUS_CLEAR_BUFF0_EVENTS); + } else if (local->dblbuffon == DW3000_DBL_BUFF_ACCESS_BUFFER_B) { + /* clear RX events relating to buffer B */ + return dw3000_reg_write8(dw, DW3000_RDB_STATUS_ID, 0, + DW3000_RDB_STATUS_CLEAR_BUFF1_EVENTS); + } + return 0; +} + +void dw3000_isr(struct dw3000 *dw) +{ + struct dw3000_local_data *local = &dw->data; + struct dw3000_isr_data isr; /* in-stack */ + int rc = 0; + bool stsnd = ((dw->config.stsMode & DW3000_STS_BASIC_MODES_MASK) == + DW3000_STS_MODE_ND); + + /* Don't read if spurious DEEP-SLEEP IRQ */ + if (dw->current_operational_state == DW3000_OP_STATE_DEEP_SLEEP) { + trace_dw3000_isr(dw, 0); + return; + } + + /* Read status register(64bits). */ + rc = dw3000_read_all_sys_status(dw, &isr.status); + if (rc) + goto spi_err; + trace_dw3000_isr(dw, isr.status); + if (dw->nfcc_coex.enabled) { + rc = dw3000_read_dss_status(dw, &isr.dss_stat); + if (rc) + goto spi_err; + trace_dw3000_isr_dss_stat(dw, isr.dss_stat); + } + /* Early clear all status bits since saved locally */ + rc = dw3000_clear_all_sys_status(dw, isr.status); + if (rc) + goto spi_err; + /* RX double-buffering enabled */ + if (local->dblbuffon) { + u8 status_db; + /* RDB status register */ + rc = dw3000_read_rdb_status(dw, &status_db); + if (rc) + goto spi_err; + /* + * If accessing the second buffer (RX_BUFFER_B then read second + * nibble of the DB status reg) + */ + if (local->dblbuffon == DW3000_DBL_BUFF_ACCESS_BUFFER_B) + status_db >>= 4; + /* + * Setting the relevant bits in the main status register + * according to RDB status register. + */ + if (status_db & DW3000_RDB_STATUS_RXFCG0_BIT_MASK) + isr.status |= DW3000_SYS_STATUS_RXFCG_BIT_MASK; + if (status_db & DW3000_RDB_STATUS_RXFR0_BIT_MASK) + isr.status |= DW3000_SYS_STATUS_RXFR_BIT_MASK; + if (status_db & DW3000_RDB_STATUS_CIADONE0_BIT_MASK) + isr.status |= DW3000_SYS_STATUS_CIA_DONE_BIT_MASK; + if (status_db & DW3000_RDB_STATUS_CP_ERR0_BIT_MASK) + isr.status |= DW3000_SYS_STATUS_CPERR_BIT_MASK; + /* We can clear event early since converted to status */ + rc = dw3000_clear_db_events(dw); + if (unlikely(rc)) + goto spi_err; + } + + /* If automatic acknowledgement is not enabled, then the AAT status bit + must be ignored */ + if (!dw->autoack) + isr.status &= ~DW3000_SYS_STATUS_AAT_BIT_MASK; + + /* Handle TX confirmation event before RX in case of not an ACK. */ + if ((isr.status & (DW3000_SYS_STATUS_AAT_BIT_MASK | + DW3000_SYS_STATUS_TXFRS_BIT_MASK)) == + DW3000_SYS_STATUS_TXFRS_BIT_MASK) { + /* Report TX completion */ + rc = dw3000_isr_handle_tx_event(dw, &isr); + if (unlikely(rc)) + goto spi_err; + } + + /* Handle RX good frame event */ + /* When using No Data STS mode, we do not get RXFCG but RXFR */ + if (stsnd && (isr.status & DW3000_SYS_STATUS_RXFR_BIT_MASK)) { + rc = dw3000_isr_handle_rxfr_sts_event(dw, &isr); + if (unlikely(rc)) + goto spi_err; + } else if (isr.status & DW3000_SYS_STATUS_RXFCG_BIT_MASK) { + /* Handle RX frame */ + rc = dw3000_isr_handle_rxfcg_event(dw, &isr); + if (unlikely(rc)) + goto spi_err; + } + + /* Handle TX confirmation event after RX in case of an ACK. */ + if (isr.status & DW3000_SYS_STATUS_TXFRS_BIT_MASK) { + /* Report TX completion */ + rc = dw3000_isr_handle_tx_event(dw, &isr); + if (unlikely(rc)) + goto spi_err; + } + + /* Handle frame reception/preamble detect timeout events */ + if (isr.status & DW3000_SYS_STATUS_ALL_RX_TO) { + /* Report RX timeout */ + rc = dw3000_isr_handle_rxto_event(dw, isr.status); + if (unlikely(rc)) + goto spi_err; + } + + /* Handle RX errors events */ + if (isr.status & DW3000_SYS_STATUS_ALL_RX_ERR) { + if (isr.status & DW3000_SYS_STATUS_LCSSERR_BIT_MASK) { + /* LCSS error will not stop the receiver, however + because STS timestamp will be wrong the reception + is aborted */ + rc = dw3000_forcetrxoff(dw); + if (unlikely(rc)) + goto spi_err; + } + /* Report RX error */ + rc = dw3000_isr_handle_rxerr_event(dw, isr.status); + if (unlikely(rc)) + goto spi_err; + } + + /* Handle SPI CRC errors events */ + if (local->spicrc && + (isr.status & DW3000_SYS_STATUS_SPICRCERR_BIT_MASK)) { + /* Handle SPI error */ + rc = dw3000_isr_handle_spi_error(dw); + if (unlikely(rc)) + goto spi_err; + } + + /* SPI ready and IDLE_RC bit gets set when device powers on, or on wake + up */ + if (isr.status & (DW3000_SYS_STATUS_SPIRDY_BIT_MASK | + DW3000_SYS_STATUS_RCINIT_BIT_MASK)) { + /* Handle SPI ready */ + rc = dw3000_isr_handle_spi_ready(dw, &isr); + if (unlikely(rc)) + goto spi_err; + } + + /* Handle the SPI1 Available events in nfcc_coex mode only */ + if (dw->nfcc_coex.enabled && + (isr.dss_stat & DW3000_DSS_STAT_SPI1_AVAIL_BIT_MASK)) { + rc = dw3000_clear_dss_status( + dw, DW3000_DSS_STAT_SPI1_AVAIL_BIT_MASK); + if (rc) + goto spi_err; + /* Handle SPI available. */ + rc = dw3000_nfcc_coex_spi1_avail(dw); + if (rc) + goto spi_err; + } + + /* TIMER0/1 event will also set the SYS_EVENT bit */ + if (isr.status & (DW3000_SYS_STATUS_TIMER0_BIT_MASK | + DW3000_SYS_STATUS_TIMER1_BIT_MASK)) { + /* Handle SPI ready */ + rc = dw3000_isr_handle_timer_events(dw); + if (unlikely(rc)) + goto spi_err; + } + + trace_dw3000_return_int(dw, 0); + return; + +spi_err: + mcps802154_broken(dw->llhw); + /* TODO: handle SPI error */ + trace_dw3000_return_int(dw, rc); + return; +} + +int dw3000_testmode_continuous_tx_start(struct dw3000 *dw, u32 frame_length, + u32 rate) +{ + int rc; + int i; + static u8 tx_buf[DW3000_EXT_FRAME_LEN] = { 0 }; + + tx_buf[0] = 0xC5; /* 802.15.4 "blink" frame */ + + if (dw->txconfig.smart) { + rc = dw3000_adjust_tx_power(dw, frame_length); + if (rc) + return rc; + } + + if (frame_length > dw->data.max_frames_len) + return -EINVAL; + + for (i = 2; i < frame_length - IEEE802154_FCS_LEN; i++) + tx_buf[i] = i & 0xFF; + + rc = dw3000_enable_rf_tx(dw, dw->config.chan, 1); + if (rc) + return rc; + rc = dw3000_ctrl_rftx_blocks(dw, dw->config.chan, + DW3000_RF_CTRL_MASK_ID); + if (rc) + return rc; + rc = dw3000_force_clocks(dw, DW3000_FORCE_CLK_SYS_TX); + if (rc) + return rc; + + /* enable repeated frames */ + rc = dw3000_reg_or8(dw, DW3000_TEST_CTRL0_ID, 0x0, + DW3000_TEST_CTRL0_TX_PSTM_BIT_MASK); + if (rc) + return rc; + + if (rate < 2) + rate = 2; + + rc = dw3000_reg_write32(dw, DW3000_DX_TIME_ID, 0x0, rate); + if (rc) + return rc; + rc = dw3000_tx_write_data(dw, tx_buf, frame_length); + if (rc) + return rc; + rc = dw3000_writetxfctrl(dw, frame_length, 0, false); + if (rc) + return rc; + + trace_dw3000_testmode_continuous_tx_start(dw, dw->config.chan, + frame_length, rate); + /* start TX immediately */ + return dw3000_write_fastcmd(dw, DW3000_CMD_TX); +} + +int dw3000_testmode_continuous_tx_stop(struct dw3000 *dw) +{ + int rc; + + trace_dw3000_testmode_continuous_tx_stop(dw); + /* disable repeated frames */ + rc = dw3000_reg_and8(dw, DW3000_TEST_CTRL0_ID, 0x0, + (uint8_t)(~DW3000_TEST_CTRL0_TX_PSTM_BIT_MASK)); + rc |= dw3000_force_clocks(dw, DW3000_FORCE_CLK_AUTO); + rc |= dw3000_reg_write32(dw, DW3000_LDO_CTRL_ID, 0, 0x00000000); + /* Disable RF blocks for TX (configure RF_ENABLE_ID reg) */ + rc |= dw3000_reg_write32(dw, DW3000_RF_ENABLE_ID, 0, 0x00000000); + /* Restore the TXRX switch to auto */ + rc |= dw3000_reg_write32(dw, DW3000_RF_SWITCH_CTRL_ID, 0x0, + DW3000_TXRXSWITCH_AUTO); + rc |= dw3000_reg_write32(dw, DW3000_RF_CTRL_MASK_ID, 0x0, 0x00000000); + return rc; +} + +static int dw3000_spi_tests; +module_param_named(spitests, dw3000_spi_tests, int, 0644); +MODULE_PARM_DESC(spitests, "Activate SPI & GPIO test mode loop in RT thread"); + +bool dw3000_spitests_enabled(struct dw3000 *dw) +{ + ((void)dw); + return dw3000_spi_tests != 0; +} + +void dw3000_spitests(struct dw3000 *dw) +{ + const int count = 16384; + u32 mode_mask, dir_mask; + int test = 0; + + if (!dw3000_spi_tests) + return; + + perf_event_create_all(dw); + + /* Setup DW3000 GPIO 4-6 in GPIO mode in output direction */ + mode_mask = (DW3000_GPIO_MODE_MSGP4_MODE_BIT_MASK | + DW3000_GPIO_MODE_MSGP5_MODE_BIT_MASK | + DW3000_GPIO_MODE_MSGP6_MODE_BIT_MASK); + dir_mask = + (DW3000_GPIO_DIR_GDP6_BIT_MASK | DW3000_GPIO_DIR_GDP5_BIT_MASK | + DW3000_GPIO_DIR_GDP4_BIT_MASK); + dw3000_set_gpio_mode(dw, mode_mask, 0); + dw3000_set_gpio_dir(dw, dir_mask, 0); + + /* Loop until SPI test mode is disabled */ + while (dw3000_spi_tests) { + u64 perfval[PERF_EVT_COUNT]; + u64 start, duration; + u32 status = 0; + int i; + /* Bypass current test if not selected */ + if (!(dw3000_spi_tests & (1 << test))) { + test = (test + 1) % 3; + continue; + } + dev_warn(dw->dev, "test mode: start test %d\n", test); + start = get_jiffies_64(); + perf_event_start_all(); + dw3000_set_gpio_out( + dw, 0, 1 << (test + DW3000_GPIO_DIR_GDP4_BIT_OFFSET)); + switch (test) { + case 0: + /* 32bit register read loop */ + for (i = 0; i < count; i++) + dw3000_reg_read_fast(dw, DW3000_SYS_STATUS_ID, + 0, sizeof(status), + &status); + break; + case 1: + /* 32bit optimised read loop */ + for (i = 0; i < count; i++) + dw3000_read_sys_status(dw, &status); + break; + case 2: + /* 32bit generic read loop */ + for (i = 0; i < count; i++) + dw3000_xfer(dw, DW3000_SYS_STATUS_ID, 0, + sizeof(status), &status, + DW3000_SPI_RD_BIT); + break; + } + dw3000_set_gpio_out( + dw, 1 << (test + DW3000_GPIO_DIR_GDP4_BIT_OFFSET), 0); + perf_event_stop_all(perfval); + duration = jiffies_to_usecs(get_jiffies_64() - start); + dev_warn( + dw->dev, + "test mode: test %d done in %llu ms, %llu us per read (status %x)\n", + test, duration / 1000, duration / count, status); + for (i = 0; i < PERF_EVT_COUNT; i++) + dev_warn(dw->dev, "\t%s: %llu\n", perf_hw_evt_name[i], + perfval[i]); + /* Set next test */ + test = (test + 1) % 3; + } + perf_event_release_all(); +} + +static ssize_t dw3000_sysfs_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct dw3000 *dw = container_of(kobj, struct dw3000, sysfs_power_dir); + u64 idle_dur, rx_ns, tx_ns; + int ret; + if (dw->power.cur_state <= DW3000_PWR_IDLE) + dw3000_power_stats(dw, dw->power.cur_state, 0); + /* TX/RX are kept in DTU unit. Convert it here to limit conversion error */ + rx_ns = dw->power.stats[DW3000_PWR_RX].dur * 10000 / + (DW3000_DTU_FREQ / 100000); + tx_ns = dw->power.stats[DW3000_PWR_TX].dur * 10000 / + (DW3000_DTU_FREQ / 100000); + idle_dur = dw->power.stats[DW3000_PWR_RUN].dur - tx_ns - rx_ns; + ret = scnprintf(buf, PAGE_SIZE, + "Off state:\n\tcount:\t%llu\n\tdur ns:\t%llu\n" + "Deep sleep state:\n\tcount:\t%llu\n\tdur ns:\t%llu\n" + "Run state:\n\tcount:\t%llu\n\tdur ns:\t%llu\n" + "Idle state:\n\tcount:\t%llu\n\tdur ns:\t%llu\n" + "Tx state:\n\tcount:\t%llu\n\tdur ns:\t%llu\n" + "Rx state:\n\tcount:\t%llu\n\tdur ns:\t%llu\n" + "Interrupts:\n\tcount:\t%lld\n", + dw->power.stats[DW3000_PWR_OFF].count, + dw->power.stats[DW3000_PWR_OFF].dur, + dw->power.stats[DW3000_PWR_DEEPSLEEP].count, + dw->power.stats[DW3000_PWR_DEEPSLEEP].dur, + dw->power.stats[DW3000_PWR_RUN].count, + dw->power.stats[DW3000_PWR_RUN].dur, + dw->power.stats[DW3000_PWR_IDLE].count, idle_dur, + dw->power.stats[DW3000_PWR_TX].count, tx_ns, + dw->power.stats[DW3000_PWR_RX].count, rx_ns, + (s64)atomic64_read(&dw->power.interrupts)); + return ret; +} + +static ssize_t dw3000_sysfs_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, + size_t length) +{ + struct dw3000 *dw = container_of(kobj, struct dw3000, sysfs_power_dir); + if (attr == &dw3000_attribute) { + /* Reset statistics on buffer write */ + int cstate = min(dw->power.cur_state, DW3000_PWR_RUN); + memset(dw->power.stats, 0, sizeof(dw->power.stats)); + dw->power.start_time = ktime_get_boottime_ns(); + dw->power.stats[cstate].count = 1; + if (dw->power.cur_state > DW3000_PWR_RUN) + dw->power.stats[dw->power.cur_state].count = 1; + atomic64_set(&dw->power.interrupts, 0); + } + return length; +} + +static struct kobj_type dw3000_kobj_type = { + .sysfs_ops = &kobj_sysfs_ops, +}; + +void dw3000_sysfs_init(struct dw3000 *dw) +{ + int rc; + dev_dbg(dw->dev, "creating sysfs\n"); + rc = kobject_init_and_add(&dw->sysfs_power_dir, &dw3000_kobj_type, + &dw->dev->kobj, "uwb"); + if (rc) + return; + rc = sysfs_create_file(&dw->sysfs_power_dir, &dw3000_attribute.attr); + if (rc) + kobject_del(&dw->sysfs_power_dir); +} + +void dw3000_sysfs_remove(struct dw3000 *dw) +{ + if (dw->sysfs_power_dir.state_in_sysfs) { + sysfs_remove_file(&dw->sysfs_power_dir, &dw3000_attribute.attr); + kobject_del(&dw->sysfs_power_dir); + } +} + +/** + * dw3000_nfcc_coex_prepare_config() - Prepare the configuration before nfcc + * coex. + * @dw: The DW device. + * + * Return: Zero on success, else a negative error code. + */ +int dw3000_nfcc_coex_prepare_config(struct dw3000 *dw) +{ + int rc; + + trace_dw3000_nfcc_coex_prepare_config(dw); + + /* Disable any rx or tx command in progress. */ + rc = dw3000_rx_disable(dw); + if (rc) + return rc; + + /* May need to resync to avoid drift. */ + rc = dw3000_may_resync(dw); + if (rc) + return rc; + + /* Reset Wait-for-Response Time. */ + rc = dw3000_setrxaftertxdelay(dw, 0); + if (rc) + return rc; + + /* Reset the reception timeout. */ + rc = dw3000_setrxtimeout(dw, 0); + if (rc) + return rc; + + /* + * Disable RXPTO behavior for two reasons: + * - CCC firmware doesn't manage it. As the RXPTO_EN is false, + * then ccc is blocked. + * - Update cached value in dw3000 context for next use. + */ + return dw3000_setpreambledetecttimeout(dw, 0); +} + +/** + * dw3000_nfcc_coex_restore_config() - Restore the configuration after nfcc + * coex. + * @dw: The DW device. + * + * Some cache is reset to force the reconfiguration. + * Some RF parameters are reconfigured. + * + * Return: Zero on success, else a negative error code. + */ +int dw3000_nfcc_coex_restore_config(struct dw3000 *dw) +{ + struct dw3000_local_data *local = &dw->data; + struct dw3000_config *config = &dw->config; + int rc; + + trace_dw3000_nfcc_coex_restore_config(dw); + + /* + * We want to restore the configuration after nfcc slot. + */ + + /* + * Clear security registers related cache. + * The STS parameters will be reset during "set_sts_params" call. + */ + memset(local->sts_key, 0, AES_KEYSIZE_128); + memset(local->sts_iv, 0, AES_BLOCK_SIZE); + dw->config.stsLength = 0; + + /* Clear all cache variables to force the reconfiguration. */ + local->rx_timeout_pac = 0; + local->w4r_time = 0; + local->ack_time = 0; + local->tx_fctrl = 0; + local->rx_frame_timeout_dly = 0; + local->ack_time = 0; + + /* Configure the SYS_CFG register. */ + rc = dw3000_configure_sys_cfg(dw, config); + if (rc) + return rc; + + /* WiFi coexistence initialisation. */ + rc = dw3000_coex_init(dw); + if (rc) + return rc; + + /* Configure antenna selection GPIO if any. */ + rc = dw3000_config_antenna_gpios(dw); + if (rc) + return rc; + + /* + * Reset cached antenna config to ensure GPIO are reconfigured + * correctly. + */ + dw->config.ant[0] = -1; + dw->config.ant[1] = -1; + + /* Select the events that will trigger an interrupt. */ + rc = dw3000_set_interrupt(dw, DW3000_SYS_STATUS_TRX, + DW3000_ENABLE_INT_ONLY); + if (rc) + return rc; + + /* + * PLL already is locked but some RF parameters could be changed. + * So we reprogram the Xtal, the DGC, the ADC, ... + */ + + /* Xtal trim could be changed. */ + rc = dw3000_prog_xtrim(dw); + if (rc) + return rc; + + /* Configure PLL coarse code, if needed. */ + if (dw->chip_ops->prog_pll_coarse_code) { + rc = dw->chip_ops->prog_pll_coarse_code(dw); + if (rc) { + dev_err(dw->dev, "device coarse code setup has failed (%d)\n", rc); + return rc; + } + } + + /* Configure delays. */ + rc = dw3000_set_antenna_delay(dw, 0); + if (rc) + return rc; + + rc = dw3000_reconfigure_hw_addr_filt(dw); + if (rc) + return rc; + + /* Do some device specific initialisation if any. */ + rc = dw->chip_ops->init(dw); + if (rc) { + dev_err(dw->dev, "device chip specific init has failed (%d)\n", + rc); + return rc; + } + + /* Reconfigure all dependent channels. */ + rc = dw3000_configure_chan(dw); + if (rc) + return rc; + + rc = dw3000_pgf_cal(dw, 1); + if (rc) + return rc; + + /* Calibrate ADC offset, if needed, after DGC configuration and after PLL lock. */ + if (dw->chip_ops->adc_offset_calibration) { + rc = dw->chip_ops->adc_offset_calibration(dw); + if (rc) + return rc; + } + + /* Setup TX preamble size, data rate and SDF timeout count. */ + rc = dw3000_configure_preamble_length_and_datarate(dw, false); + if (rc) + return rc; + + /* PHR rate. */ + rc = dw3000_configure_phr_rate(dw); + if (rc) + return rc; + + /* + * Ensure STS fields are double-buffered if enabled, also enable stats + * if configured in module parameters. + */ + rc = dw3000_configure_ciadiag(dw, dw->stats.enabled, + dw->data.dblbuffon ? + DW3000_CIA_DIAG_LOG_DBL_MID : + DW3000_CIA_DIAG_LOG_DBL_OFF); + if (rc) { + dev_err(dw->dev, "device CIA DIAG setup has failed (%d)\n", rc); + return rc; + } + + return 0; +} diff --git a/kernel/drivers/net/ieee802154/dw3000_core.h b/kernel/drivers/net/ieee802154/dw3000_core.h new file mode 100644 index 0000000..aa7c4ed --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_core.h @@ -0,0 +1,743 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_CORE_H +#define __DW3000_CORE_H + +#include "dw3000.h" + +/* Maximum SPI bus speed when PLL is not yet locked */ +#define DW3000_SPI_SLOW_HZ 3000000 + +#define DW3000_GPIO_COUNT 9 /* GPIO0 to GPIO8 */ + +/* DW3000 wake-up latency. At least 2ms is required. */ +#define DW3000_WAKEUP_LATENCY_US 15000 + +/* Define DW3000 PDOA modes */ +#define DW3000_PDOA_M0 0x0 /* PDOA mode is off */ +#define DW3000_PDOA_M1 0x1 /* PDOA mode 1 */ +#define DW3000_PDOA_M2 0x2 /* PDOA mode 2 (reserved or not supported) */ +#define DW3000_PDOA_M3 0x3 /* PDOA mode 3 */ +#define DW3000_PDOA_CONFIG_MASK 0x3 + +/* Define DW3000 STS modes */ +#define DW3000_STS_MODE_OFF 0x0 /* STS is off */ +#define DW3000_STS_MODE_1 0x1 /* STS mode 1 */ +#define DW3000_STS_MODE_2 0x2 /* STS mode 2 */ +#define DW3000_STS_MODE_ND 0x3 /* STS with no data */ +#define DW3000_STS_BASIC_MODES_MASK 0x3 /* STS basic modes */ +#define DW3000_STS_MODE_SDC 0x8 /* Enable Super Deterministic Codes */ +#define DW3000_STS_CONFIG_MASK 0xB + +/* Offset of each area in CIR accumulator memory */ +#define DW3000_ACC_MEM_IPATOV_OFFSET 0x0 /* Base address */ +#define DW3000_ACC_MEM_PRF64_SIZE 1016 /* Bank size when PRF=64 MHz */ +#define DW3000_ACC_MEM_PRF16_SIZE 992 /* Bank size when PRF=16 MHz */ +#define DW3000_ACC_MEM_STS_OFFSET \ + 1024 /* Base address when STS enabled and pdoa != 3 */ +#define DW3000_ACC_MEM_STS_SIZE \ + 512 /* Bank size when STS enabled and pdoa != 3 */ +#define DW3000_ACC_MEM_STS_PDOA3_OFFSET \ + (DW3000_ACC_MEM_STS_OFFSET + \ + DW3000_ACC_MEM_STS_SIZE) /* Base address when sts enabled and pdoa mode 3 */ +#define DW3000_ACC_MEM_STS_PDOA3_SZ \ + 512 /* Bank size when sts enabled and pdoa mode 3 */ + +/** + * DW3000_GET_STS_LEN_UNIT_VALUE() - Convert STS length enum into unit value + * @x: value from enum dw3000_sts_lengths + * + * Return: the STS length in unit of 8-symbols block. + */ +#define DW3000_GET_STS_LEN_UNIT_VALUE(x) ((u16)(1 << (x))) + +/* Constants for specifying TX Preamble length in symbols */ +#define DW3000_PLEN_4096 0x03 /* Standard preamble length 4096 symbols */ +#define DW3000_PLEN_2048 0x0A /* Non-standard preamble length 2048 symbols */ +#define DW3000_PLEN_1536 0x06 /* Non-standard preamble length 1536 symbols */ +#define DW3000_PLEN_1024 0x02 /* Standard preamble length 1024 symbols */ +#define DW3000_PLEN_512 0x0d /* Non-standard preamble length 512 symbols */ +#define DW3000_PLEN_256 0x09 /* Non-standard preamble length 256 symbols */ +#define DW3000_PLEN_128 0x05 /* Non-standard preamble length 128 symbols */ +#define DW3000_PLEN_72 0x07 /* Non-standard length 72 */ +#define DW3000_PLEN_32 0x04 /* Non-standard length 32 */ +#define DW3000_PLEN_64 0x01 /* Standard preamble length 64 symbols */ + +/* Constants for specifying the (Nominal) mean Pulse Repetition Frequency + These are defined for direct write (with a shift if necessary) to CHAN_CTRL + and TX_FCTRL regs */ +#define DW3000_PRF_16M 1 /* UWB PRF 16 MHz */ +#define DW3000_PRF_64M 2 /* UWB PRF 64 MHz */ +#define DW3000_PRF_SCP 3 /* SCP UWB PRF ~100 MHz */ + +/* Constants for specifying Start of Frame Delimiter (SFD) type */ +#define DW3000_SFD_TYPE_STD 0 /* Standard short IEEE802154 SFD (8 symbols) */ +#define DW3000_SFD_TYPE_DW_8 1 /* Decawave-defined 8 symbols SFD */ +#define DW3000_SFD_TYPE_DW_16 2 /* Decawave-defined 16 symbols SFD */ +#define DW3000_SFD_TYPE_4Z 3 /* IEEE802154z SFD (8 symbols) */ + +/* Constants for selecting the bit rate for data TX (and RX) + These are defined for write (with just a shift) the TX_FCTRL register */ +#define DW3000_BR_850K 0 /* UWB bit rate 850 kbits/s */ +#define DW3000_BR_6M8 1 /* UWB bit rate 6.8 Mbits/s */ + +/* Constants for selecting the PHR mode */ +#define DW3000_PHRMODE_STD 0x0 /* standard PHR mode */ +#define DW3000_PHRMODE_EXT 0x1 /* DW proprietary extended frames PHR mode */ + +/* Constants for selecting the bit rate for the PHR */ +#define DW3000_PHRRATE_STD 0x0 /* standard PHR rate, 850 kbits/s */ +#define DW3000_PHRRATE_DTA 0x1 /* PHR at data rate 6.8 Mbits/s */ + +/* Constants for specifying Preamble Acquisition Chunk (PAC) Size in symbols */ +#define DW3000_PAC8 0 /* recommended for RX of preamble length 128 and below */ +#define DW3000_PAC16 1 /* recommended for RX of preamble length 256 */ +#define DW3000_PAC32 2 /* recommended for RX of preamble length 512 */ +#define DW3000_PAC4 3 /* recommended for RX of preamble length < 127 */ + +enum spi_modes { + DW3000_SPI_RD_BIT = 0x0000U, + DW3000_SPI_WR_BIT = 0x8000U, + DW3000_SPI_AND_OR_8 = 0x8001U, + DW3000_SPI_AND_OR_16 = 0x8002U, + DW3000_SPI_AND_OR_32 = 0x8003U, + DW3000_SPI_AND_OR_MSK = 0x0003U, +}; + +/* Frame filtering configuration options */ +#define DW3000_FF_ENABLE_802_15_4 0x2 /* use 802.15.4 filtering rules */ +#define DW3000_FF_DISABLE 0x0 /* disable FF */ +#define DW3000_FF_BEACON_EN 0x001 /* beacon frames allowed */ +#define DW3000_FF_DATA_EN 0x002 /* data frames allowed */ +#define DW3000_FF_ACK_EN 0x004 /* ack frames allowed */ +#define DW3000_FF_MAC_EN 0x008 /* mac control frames allowed */ +#define DW3000_FF_RSVD_EN 0x010 /* reserved frame types allowed */ +#define DW3000_FF_MULTI_EN 0x020 /* multipurpose frames allowed */ +#define DW3000_FF_FRAG_EN 0x040 /* fragmented frame types allowed */ +#define DW3000_FF_EXTEND_EN 0x080 /* extended frame types allowed */ +#define DW3000_FF_COORD_EN \ + 0x100 /* behave as coordinator (can receive frames + with no dest address (need PAN ID match)) */ +#define DW3000_FF_IMPBRCAST_EN 0x200 /* allow MAC implicit broadcast */ + +/* DW3000 soft reset options */ +#define DW3000_RESET_ALL 0x00 +#define DW3000_RESET_CTRX 0x0f +#define DW3000_RESET_RX 0xef +#define DW3000_RESET_CLEAR 0xff + +/* SYS_STATE_LO register information */ +/* TSE is in IDLE (IDLE_PLL) */ +#define DW3000_SYS_STATE_IDLE 0x3 +/* TSE is in TX but TX is in IDLE */ +#define DW3000_SYS_STATE_TXERR 0xD0000 + +/* Fast commands */ +/* Turn off TX or RX, clear any TX/RX events and put DW3000 into IDLE */ +#define DW3000_CMD_TXRXOFF 0x0 +/* Start TX */ +#define DW3000_CMD_TX 0x1 +/* Enable RX */ +#define DW3000_CMD_RX 0x2 +/* Start delayed TX (RMARKER will be @ time set in DX_TIME register) */ +#define DW3000_CMD_DTX 0x3 +/* Enable RX @ time specified in DX_TIME register */ +#define DW3000_CMD_DRX 0x4 +/* Start delayed TX (RMARKER will be @ time = TX_TIME + DX_TIME) */ +#define DW3000_CMD_DTX_TS 0x5 +/* Enable RX @ time = TX_TIME + DX_TIME */ +#define DW3000_CMD_DRX_TS 0x6 +/* Start delayed TX (RMARKER will be @ time = RX_TIME + DX_TIME) */ +#define DW3000_CMD_DTX_RS 0x7 +/* Enable RX @ time = RX_TIME + DX_TIME */ +#define DW3000_CMD_DRX_RS 0x8 +/* Start delayed TX (RMARKER will be @ time = DREF_TIME + DX_TIME) */ +#define DW3000_CMD_DTX_REF 0x9 +/* Enable RX @ time = DREF_TIME + DX_TIME */ +#define DW3000_CMD_DRX_REF 0xa +/* Start delayed TX (as DTX below), enable RX when TX done */ +#define DW3000_CMD_DTX_W4R 0xd +/* Start TX (as below), enable RX when TX done */ +#define DW3000_CMD_TX_W4R 0xc +/* Toggle double buffer pointer */ +#define DW3000_CMD_DB_TOGGLE 0x13 +/* Write to the Semaphore and try to reserve access (if it hasn't already been + reserved by the other master) */ +#define DW3000_CMD_SEMA_REQ 0x14 +/* Release the semaphore if it is currently reserved by this master. */ +#define DW3000_CMD_SEMA_REL 0x15 +/* Only SPI 2 can issue this command. Force access regardless of current + semaphore value. */ +#define DW3000_CMD_SEMA_FORCE 0x16 +/* Global digital reset including of the semaphore */ +#define DW3000_CMD_SEMA_RESET 0x18 +/* Global digital reset without reset of the semaphore */ +#define DW3000_CMD_SEMA_RESET_NO_SEM 0x19 +/* Enters sleep/deep sleep according to ANA_CFG - DEEPSLEEP_EN */ +#define DW3000_CMD_ENTER_SLEEP 0x1A + +/* Size of RX LUT configuration tables */ +#define DW3000_CONFIGMRXLUT_MAX 7 +#define DW3000_DGC_CFG 0x38 +#define DW3000_DGC_CFG0 0x00000240 +#define DW3000_DGC_CFG1 0x1a491248 +#define DW3000_DGC_CFG2 0x2db248db + +/* DW3000 SLEEP and WAKEUP configuration parameters */ +#define DW3000_PGFCAL 0x0800 +#define DW3000_GOTORX 0x0200 +#define DW3000_GOTOIDLE 0x0100 +#define DW3000_SEL_GEAR3 0x00C0 +#define DW3000_SEL_GEAR2 0x0080 /* Short gear table */ +#define DW3000_SEL_GEAR1 0x0040 /* SCP */ +#define DW3000_SEL_GEAR0 0x0000 /* Long gear table */ +#define DW3000_ALT_GEAR 0x0020 +#define DW3000_LOADLDO 0x0010 +#define DW3000_LOADDGC 0x0008 +#define DW3000_LOADBIAS 0x0004 +#define DW3000_RUNSAR 0x0002 + +/* OTP addresses definitions */ +#define DW3000_LDOTUNELO_ADDRESS (0x04) +#define DW3000_LDOTUNEHI_ADDRESS (0x05) +#define DW3000_PARTID_ADDRESS (0x06) +#define DW3000_LOTID_ADDRESS (0x07) +#define DW3000_VBAT_ADDRESS (0x08) +#define DW3000_VTEMP_ADDRESS (0x09) +#define DW3000_XTRIM_ADDRESS (0x1E) +#define DW3000_OTPREV_ADDRESS (0x1F) +#define DW3000_BIAS_TUNE_ADDRESS (0xA) +#define DW3000_DGC_TUNE_ADDRESS (0x20) +#define DW3000_PLL_CC_ADDRESS (0x35) + +/* Bit fields to select information to retrieve from OTP memory */ +#define DW3000_READ_OTP_PID 0x10 /* read part ID from OTP */ +#define DW3000_READ_OTP_LID 0x20 /* read lot ID from OTP */ +#define DW3000_READ_OTP_BAT 0x40 /* read ref voltage from OTP */ +#define DW3000_READ_OTP_TMP 0x80 /* read ref temperature from OTP */ + +/* The mean XTAL TRIM value measured on multiple E0 samples. + * During the initialization the XTAL TRIM value can be read from the OTP and + * in case it is not present, the default would be used instead. */ +#define DW3000_DEFAULT_XTAL_TRIM 0x1f + +/* The XTAL TRIM BIAS value for +/- 5PPM offset */ +#define DW3000_XTAL_BIAS 0 + +/* Clock offset value under which the PDoA value is assumed bad. */ +#define DW3000_CFO_THRESHOLD ((s16)(4 * (1 << 26) / 1000000)) + +/* All RX errors mask */ +#define DW3000_SYS_STATUS_ALL_RX_ERR \ + (DW3000_SYS_STATUS_RXPHE_BIT_MASK | DW3000_SYS_STATUS_RXFCE_BIT_MASK | \ + DW3000_SYS_STATUS_RXFSL_BIT_MASK | DW3000_SYS_STATUS_RXSTO_BIT_MASK | \ + DW3000_SYS_STATUS_ARFE_BIT_MASK | DW3000_SYS_STATUS_CIAERR_BIT_MASK | \ + DW3000_SYS_STATUS_CPERR_BIT_MASK | \ + DW3000_SYS_STATUS_LCSSERR_BIT_MASK) + +/* User defined RX timeouts (frame wait timeout and preamble detect timeout) + mask. */ +#define DW3000_SYS_STATUS_ALL_RX_TO \ + (DW3000_SYS_STATUS_RXFTO_BIT_MASK | DW3000_SYS_STATUS_RXPTO_BIT_MASK) + +/* All RX events after a correct packet reception mask */ +#define DW3000_SYS_STATUS_ALL_RX_GOOD \ + (DW3000_SYS_STATUS_RXFR_BIT_MASK | DW3000_SYS_STATUS_RXFCG_BIT_MASK | \ + DW3000_SYS_STATUS_RXPRD_BIT_MASK | \ + DW3000_SYS_STATUS_RXSFDD_BIT_MASK | \ + DW3000_SYS_STATUS_RXPHD_BIT_MASK | \ + DW3000_SYS_STATUS_CIA_DONE_BIT_MASK) + +/* All TX events mask */ +#define DW3000_SYS_STATUS_ALL_TX \ + (DW3000_SYS_STATUS_AAT_BIT_MASK | DW3000_SYS_STATUS_TXFRB_BIT_MASK | \ + DW3000_SYS_STATUS_TXPRS_BIT_MASK | DW3000_SYS_STATUS_TXPHS_BIT_MASK | \ + DW3000_SYS_STATUS_TXFRS_BIT_MASK) + +void dw3000_init_config(struct dw3000 *dw); + +int dw3000_init(struct dw3000 *dw, bool check_idlerc); +void dw3000_remove(struct dw3000 *dw); + +int dw3000_transfers_init(struct dw3000 *dw); +void dw3000_transfers_free(struct dw3000 *dw); + +void dw3000_spitests(struct dw3000 *dw); +bool dw3000_spitests_enabled(struct dw3000 *dw); + +int dw3000_wait_idle_state(struct dw3000 *dw); +int dw3000_poweron(struct dw3000 *dw); +int dw3000_poweroff(struct dw3000 *dw); +int dw3000_hardreset(struct dw3000 *dw); + +int dw3000_softreset(struct dw3000 *dw); +int dw3000_check_devid(struct dw3000 *dw); + +void dw3000_setup_regulators(struct dw3000 *dw); +int dw3000_setup_reset_gpio(struct dw3000 *dw); +int dw3000_setup_irq(struct dw3000 *dw); +int dw3000_setup_wifi_coex(struct dw3000 *dw); +int dw3000_setup_thread_cpu(struct dw3000 *dw, int *dw3000_thread_cpu); +int dw3000_setup_qos_latency(struct dw3000 *dw); +int dw3000_setup_regulator_delay(struct dw3000 *dw); + +void dw3000_spi_queue_start(struct dw3000 *dw); +int dw3000_spi_queue_flush(struct dw3000 *dw); +int dw3000_spi_queue_reset(struct dw3000 *dw, int rc); + +int dw3000_reg_read_fast(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u16 length, void *buffer); +int dw3000_reg_read32(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u32 *val); +int dw3000_reg_read16(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u16 *val); +int dw3000_reg_read8(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u8 *val); +int dw3000_reg_write_fast(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u16 length, const void *buffer, enum spi_modes mode); +int dw3000_reg_write32(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u32 val); +int dw3000_reg_write16(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u16 val); +int dw3000_reg_write8(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u8 val); +int dw3000_reg_modify32(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u32 _and, u32 _or); +int dw3000_reg_modify16(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u16 _and, u16 _or); +int dw3000_reg_modify8(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, + u8 _and, u8 _or); + +int dw3000_write_fastcmd(struct dw3000 *dw, u8 cmd); + +int dw3000_xfer(struct dw3000 *dw, u32 reg_fileid, u16 reg_offset, u16 length, + void *buffer, enum spi_modes mode); +#define dw3000_reg_or8(dw, addr, offset, or_val) \ + dw3000_reg_modify8(dw, addr, offset, -1, or_val) +#define dw3000_reg_and8(dw, addr, offset, and_val) \ + dw3000_reg_modify8(dw, addr, offset, and_val, 0) + +#define dw3000_reg_or16(dw, addr, offset, or_val) \ + dw3000_reg_modify16(dw, addr, offset, -1, or_val) +#define dw3000_reg_and16(dw, addr, offset, and_val) \ + dw3000_reg_modify16(dw, addr, offset, and_val, 0) + +#define dw3000_reg_or32(dw, addr, offset, or_val) \ + dw3000_reg_modify32(dw, addr, offset, -1, or_val) +#define dw3000_reg_and32(dw, addr, offset, and_val) \ + dw3000_reg_modify32(dw, addr, offset, and_val, 0) + +#define sts_to_pdoa(s, pdoa_enabled) \ + ((s) && (pdoa_enabled) ? DW3000_PDOA_M1 : DW3000_PDOA_M0) + +#define dw3000_get_chip_name(dw) (dw3000_chip_versions[dw->chip_idx].name) + +int dw3000_enable(struct dw3000 *dw); +int dw3000_disable(struct dw3000 *dw); + +int dw3000_configure_chan(struct dw3000 *dw); +int dw3000_configure_pcode(struct dw3000 *dw); +int dw3000_configure_sfd_type(struct dw3000 *dw); +int dw3000_configure_phr_rate(struct dw3000 *dw); +int dw3000_configure_preamble_length_and_datarate(struct dw3000 *dw, + bool update_sfd_toc_pac); + +int dw3000_set_eui64(struct dw3000 *dw, __le64 val); +int dw3000_set_panid(struct dw3000 *dw, __le16 val); +int dw3000_set_shortaddr(struct dw3000 *dw, __le16 val); +int dw3000_set_pancoord(struct dw3000 *dw, bool active); +int dw3000_set_promiscuous(struct dw3000 *dw, bool on); +int dw3000_set_sts_pdoa(struct dw3000 *dw, u8 sts_mode, u8 pdoa_mode); +int dw3000_set_sts_length(struct dw3000 *dw, enum dw3000_sts_lengths len); +int dw3000_configure_sts_key(struct dw3000 *dw, const u8 *key); +int dw3000_configure_sts_iv(struct dw3000 *dw, const u8 *iv); +int dw3000_load_sts_iv(struct dw3000 *dw); +int dw3000_configure_sys_cfg(struct dw3000 *dw, struct dw3000_config *config); +int dw3000_configure_hw_addr_filt(struct dw3000 *dw, unsigned long changed); +int dw3000_enable_auto_fcs(struct dw3000 *dw, bool on); + +int dw3000_clear_sys_status(struct dw3000 *dw, u32 clear_bits); +int dw3000_clear_dss_status(struct dw3000 *dw, u8 clear_bits); +int dw3000_clear_spi_collision_status(struct dw3000 *dw, u8 clear_bits); +int dw3000_read_rx_timestamp(struct dw3000 *dw, u64 *rx_ts); +int dw3000_read_rdb_status(struct dw3000 *dw, u8 *status); +int dw3000_read_sys_status(struct dw3000 *dw, u32 *status); +int dw3000_read_sys_time(struct dw3000 *dw, u32 *sys_time); + +int dw3000_rx_store_rssi(struct dw3000 *dw, struct dw3000_rssi *rssi, + u8 pkt_sts); +int dw3000_rx_calc_rssi(struct dw3000 *dw, struct dw3000_rssi *rssi, + struct mcps802154_rx_frame_info *info, u8 pkt_sts); +int dw3000_rx_stats_inc(struct dw3000 *dw, const enum dw3000_stats_items item, + struct dw3000_rssi *rssi); + +u32 dw3000_get_dtu_time(struct dw3000 *dw); + +int dw3000_forcetrxoff(struct dw3000 *dw); + +int dw3000_do_rx_enable(struct dw3000 *dw, + const struct mcps802154_rx_frame_config *config, + int frame_idx); +int dw3000_rx_enable(struct dw3000 *dw, bool rx_delayed, u32 date_dtu, + u32 timeout_pac); +int dw3000_rx_disable(struct dw3000 *dw); +bool dw3000_rx_busy(struct dw3000 *dw, bool busy); + +int dw3000_rx_stats_enable(struct dw3000 *dw, bool on); +void dw3000_rx_stats_clear(struct dw3000 *dw); + +int dw3000_enable_autoack(struct dw3000 *dw, bool force); +int dw3000_disable_autoack(struct dw3000 *dw, bool force); + +struct mcps802154_tx_frame_config; +int dw3000_do_tx_frame(struct dw3000 *dw, + const struct mcps802154_tx_frame_config *config, + struct sk_buff *skb, int frame_idx); + +int dw3000_tx_setcwtone(struct dw3000 *dw, bool on); + +int dw3000_config_antenna_gpios(struct dw3000 *dw); +int dw3000_set_tx_antenna(struct dw3000 *dw, int ant_set_id); +int dw3000_set_rx_antennas(struct dw3000 *dw, int ant_set_id, + bool pdoa_enabled, int frame_idx); + +s16 dw3000_read_pdoa(struct dw3000 *dw); +s16 dw3000_pdoa_to_aoa_lut(struct dw3000 *dw, s16 pdoa_rad_q11); +int dw3000_read_sts_timestamp(struct dw3000 *dw, u64 *sts_ts); +int dw3000_read_sts_quality(struct dw3000 *dw, s16 *acc_qual); +int dw3000_read_clockoffset(struct dw3000 *dw, s16 *cfo); +int dw3000_prog_xtrim(struct dw3000 *dw); + +int dw3000_set_gpio_mode(struct dw3000 *dw, u32 mask, u32 mode); +int dw3000_set_gpio_dir(struct dw3000 *dw, u16 mask, u16 dir); +int dw3000_set_gpio_out(struct dw3000 *dw, u16 reset, u16 set); + +int dw3000_otp_read32(struct dw3000 *dw, u16 addr, u32 *val); +int dw3000_otp_write32(struct dw3000 *dw, u16 addr, u32 data); +int dw3000_read_otp(struct dw3000 *dw, int mode); + +int dw3000_read_frame_cir_data(struct dw3000 *dw, + struct mcps802154_rx_frame_info *info, + u64 utime); +int dw3000_cir_data_alloc_count(struct dw3000 *dw, u16 nrecord); + +void dw3000_sysfs_init(struct dw3000 *dw); +void dw3000_sysfs_remove(struct dw3000 *dw); + +void dw3000_isr(struct dw3000 *dw); +enum hrtimer_restart dw3000_idle_timeout(struct hrtimer *timer); +int dw3000_idle_cancel_timer(struct dw3000 *dw); +void dw3000_wakeup_timer_start(struct dw3000 *dw, int delay_us); +void dw3000_wakeup_and_wait(struct dw3000 *dw); +int dw3000_deep_sleep_and_wakeup(struct dw3000 *dw, int delay_us); +int dw3000_idle(struct dw3000 *dw, bool timestamp, u32 timestamp_dtu, + dw3000_idle_timeout_cb idle_timeout_cb, + enum operational_state next_operational_state); +int dw3000_deepsleep_wakeup_now(struct dw3000 *dw, + dw3000_idle_timeout_cb idle_timeout_cb, + u32 timestamp_dtu, + enum operational_state next_operational_state); +int dw3000_can_deep_sleep(struct dw3000 *dw, int delay_us); +int dw3000_trace_rssi_info(struct dw3000 *dw, u32 regid, char *chipver); + +int dw3000_testmode_continuous_tx_start(struct dw3000 *dw, u32 frame_length, + u32 rate); +int dw3000_testmode_continuous_tx_stop(struct dw3000 *dw); +int dw3000_nfcc_coex_prepare_config(struct dw3000 *dw); +int dw3000_nfcc_coex_restore_config(struct dw3000 *dw); + +/* Preamble length related information. */ +struct dw3000_plen_info { + /* Preamble length in symbols. */ + int symb; + /* PAC size in symbols. */ + int pac_symb; + /* Register value for this preamble length. */ + uint8_t dw_reg; + /* Register value for PAC size. */ + uint8_t dw_pac_reg; +}; +extern const struct dw3000_plen_info _plen_info[]; + +/* Chip per symbol information. */ +extern const int _chip_per_symbol_info[]; + +/* PRF related information. */ +struct dw3000_prf_info { + /* Number of chips per symbol. */ + int chip_per_symb; +}; +extern const struct dw3000_prf_info _prf_info[]; + +static inline int dw3000_get_sfd_symb(struct dw3000 *dw) +{ + /* + * SFD_TYPE_STD, SFD_TYPE_DW_8, SFD_TYPE_4Z : 8 symbols + * SFD_TYPE_DW_16 : 16 symbols + */ + return dw->config.sfdType == DW3000_SFD_TYPE_DW_16 ? 16 : 8; +} + +static inline int dw3000_compute_shr_dtu(struct dw3000 *dw) +{ + const struct dw3000_plen_info *plen_info = + &_plen_info[dw->config.txPreambLength - 1]; + const int chip_per_symb = + _prf_info[dw->config.txCode >= 9 ? DW3000_PRF_64M : + DW3000_PRF_16M] + .chip_per_symb; + const int shr_symb = plen_info->symb + dw3000_get_sfd_symb(dw); + return shr_symb * chip_per_symb / DW3000_CHIP_PER_DTU; +} + +static inline int compute_shr_dtu_from_conf( + const struct mcps802154_hrp_uwb_params *hrp_uwb_params) +{ + const int preamble_symb = hrp_uwb_params->psr; + const int chip_per_symb = + _prf_info[hrp_uwb_params->prf == MCPS802154_PRF_64 ? + DW3000_PRF_64M : + DW3000_PRF_16M] + .chip_per_symb; + /* The only possible sfd number of symbols is 8. */ + const int sfd_symb = 8; + const int shr_symb = preamble_symb + sfd_symb; + return shr_symb * chip_per_symb / DW3000_CHIP_PER_DTU; +} + +static inline int dw3000_compute_symbol_dtu(struct dw3000 *dw) +{ + const int chip_per_symb = + _prf_info[dw->config.txCode >= 9 ? DW3000_PRF_64M : + DW3000_PRF_16M] + .chip_per_symb; + return chip_per_symb / DW3000_CHIP_PER_DTU; +} + +static inline int dw3000_compute_chips_per_pac(struct dw3000 *dw) +{ + const int pac_symb = _plen_info[dw->config.txPreambLength - 1].pac_symb; + const int chip_per_symb = + _prf_info[dw->config.txCode >= 9 ? DW3000_PRF_64M : + DW3000_PRF_16M] + .chip_per_symb; + return chip_per_symb * pac_symb; +} + +static inline int dw3000_compute_pre_timeout_pac(struct dw3000 *dw) +{ + /* Must be called AFTER dw->chips_per_pac initialisation */ + const int symb = _plen_info[dw->config.txPreambLength - 1].symb; + const int pac_symb = _plen_info[dw->config.txPreambLength - 1].pac_symb; + + return (DW3000_RX_ENABLE_STARTUP_DLY * DW3000_CHIP_PER_DLY + + dw->chips_per_pac - 1) / + dw->chips_per_pac + + symb / pac_symb + 2; +} + +static inline int dw3000_frame_duration_dtu(struct dw3000 *dw, + int payload_bytes, bool with_shr) +{ + const struct dw3000_prf_info *prf_info = + &_prf_info[dw->config.txCode >= 9 ? DW3000_PRF_64M : + DW3000_PRF_16M]; + int chip_per_symbol_phr = _chip_per_symbol_info[dw->config.phrRate]; + int chip_per_symbol_data = _chip_per_symbol_info[dw->config.dataRate]; + /* STS part */ + const u8 sts_mode = dw->config.stsMode & DW3000_STS_BASIC_MODES_MASK; + const int sts_symb = + sts_mode == DW3000_STS_MODE_OFF ? 0 : 8 << dw->config.stsLength; + const int sts_chips = sts_symb * prf_info->chip_per_symb; + /* PHR part. */ + const int phr_tail_bits = sts_mode == DW3000_STS_MODE_ND ? 0 : 19 + 2; + const int phr_chips = phr_tail_bits /* 1 bit/symbol */ + * chip_per_symbol_phr; + /* Data part, 48 Reed-Solomon bits per 330 bits. */ + const int data_bits = sts_mode == DW3000_STS_MODE_ND ? + 0 : + (payload_bytes + IEEE802154_FCS_LEN) * 8; + + const int data_rs_bits = data_bits + (data_bits + 329) / 330 * 48; + const int data_chips = data_rs_bits /* 1 bit/symbol */ + * chip_per_symbol_data; + /* Done, convert to dtu. */ + return ((sts_chips + phr_chips + data_chips) / DW3000_CHIP_PER_DTU) + + (with_shr ? dw->llhw->shr_dtu : 0); +} + +static inline void dw3000_update_timings(struct dw3000 *dw) +{ + struct mcps802154_llhw *llhw = dw->llhw; + /* Update configuration dependent timings */ + llhw->shr_dtu = dw3000_compute_shr_dtu(dw); + llhw->symbol_dtu = dw3000_compute_symbol_dtu(dw); + /* The CCA detection time shall be equivalent to 40 data symbol periods, + Tdsym, for a nominal 850 kb/s, or equivalently, at least 8 (multiplexed) + preamble symbols should be captured in the CCA detection time. */ + llhw->cca_dtu = 8 * llhw->symbol_dtu; + dw->chips_per_pac = dw3000_compute_chips_per_pac(dw); + dw->pre_timeout_pac = dw3000_compute_pre_timeout_pac(dw); +} + +/** + * dw3000_dtu_to_ktime() - compute absolute ktime for the specified DTU time + * @dw: the DW device + * @ts_dtu: timestamp in DTU unit to convert + * + * Formula: + * ktime = (ts_dtu - dtu0) * D / N + ktime0 + * Where: + * N = DW3000_DTU_FREQ = 15600000 + * D = 1000000000 + * + * D/N = 1000000000/15600000 = 10000/156 + * dtu0 always 0. + * + * Return: the computed kernel time in ns + */ +static inline s64 dw3000_dtu_to_ktime(struct dw3000 *dw, u32 ts_dtu) +{ + return dw->time_zero_ns + + (10000ll * ts_dtu / (DW3000_DTU_FREQ / 100000)); +} + +/** + * dw3000_ktime_to_dtu() - compute current DTU time + * @dw: the DW device + * @timestamp_ns: kernel time in ns + * + * Formula: + * dtu = (ktime - ktime0) * N / D + dtu0 + * Where: + * N = DW3000_DTU_FREQ = 15600000 + * D = 1000000000 + * + * N/D = 15600000/1000000000 = 156/10000 + * dtu0 always 0. + * + * Return: driver DTU time + */ +static inline u32 dw3000_ktime_to_dtu(struct dw3000 *dw, s64 timestamp_ns) +{ + timestamp_ns -= dw->time_zero_ns; + return (u32)(timestamp_ns * (DW3000_DTU_FREQ / 100000) / 10000); +} + +/** + * dw3000_dtu_to_sys_time() - compute DW SYS_TIME from DTU time + * @dw: the DW device + * @dtu: the DTU timestamp to convert to SYS_TIME + * + * Return: the value to write to SYS_TIME register + */ +static inline u32 dw3000_dtu_to_sys_time(struct dw3000 *dw, u32 dtu) +{ + const int N = DW3000_DTU_PER_SYS_POWER; + u32 dtu_sync = dw->dtu_sync; + u32 sys_time_sync = dw->sys_time_sync; + return ((dtu - dtu_sync) << N) + sys_time_sync; +} + +/** + * dw3000_sys_time_to_dtu() - compute current DTU time from SYS_TIME + * @dw: the DW device + * @sys_time: the DW device SYS_TIME register value to convert to DTU + * @dtu_near: a DTU time which must be in the past relative to sys_time, at less + * than half the SYS_TIME rollover period + * + * Return: the corresponding DTU time + */ +static inline u32 dw3000_sys_time_to_dtu(struct dw3000 *dw, u32 sys_time, + u32 dtu_near) +{ + const int N = DW3000_DTU_PER_SYS_POWER; + u32 dtu_sync = dw->dtu_sync; + u32 sys_time_sync = dw->sys_time_sync; + u32 dtu_lsb = (sys_time - (sys_time_sync - (dtu_sync << N))) >> N; + u32 dtu_add = ((~dtu_lsb & dtu_near) & (1 << (31 - N))) << 1; + u32 mask = (1 << (32 - N)) - 1; + return ((dtu_near & ~mask) | dtu_lsb) + dtu_add; +} + +/** + * dw3000_sys_time_rctu_to_dtu() - compute current DTU time from RCTU. + * @dw: the DW device. + * @timestamp_rctu: The DW device RX_STAMP register value in RCTU to convert to DTU. + * The RCTU, Ranging Counter Time Unit, is approximately 15.65 picoseconds long. + * + * Return: The corresponding DTU time. + */ +static inline u32 dw3000_sys_time_rctu_to_dtu(struct dw3000 *dw, + u64 timestamp_rctu) +{ + u32 sys_time = (u32)(timestamp_rctu / DW3000_RCTU_PER_SYS); + u32 dtu_near = dw3000_get_dtu_time(dw) - DW3000_DTU_FREQ; + + return dw3000_sys_time_to_dtu(dw, sys_time, dtu_near); +} + +/** + * dw3000_reset_rctu_conv_state() - reset RCTU converter + * @dw: the DW device + * + * Called during a stop, rx_disable, reset and idle. + */ +static inline void dw3000_reset_rctu_conv_state(struct dw3000 *dw) +{ + dw->rctu_conv.state = UNALIGNED; +} + +/** + * dw3000_resync_rctu_conv_state() - resync RCTU converter + * @dw: the DW device + * + * Called during a wake-up from deep sleep. + */ +static inline void dw3000_resync_rctu_conv_state(struct dw3000 *dw) +{ + if (dw->rctu_conv.state == ALIGNED_SYNCED) + dw->rctu_conv.state = ALIGNED; +} + +static inline int pac_to_dly(struct mcps802154_llhw *llhw, int pac) +{ + struct dw3000 *dw = llhw->priv; + + return (pac * dw->chips_per_pac / DW3000_CHIP_PER_DLY); +} + +static inline int dtu_to_pac(struct mcps802154_llhw *llhw, int timeout_dtu) +{ + struct dw3000 *dw = llhw->priv; + + return (timeout_dtu * DW3000_CHIP_PER_DTU + dw->chips_per_pac - 1) / + dw->chips_per_pac; +} + +static inline int dtu_to_dly(struct mcps802154_llhw *llhw, int dtu) +{ + return (dtu * DW3000_CHIP_PER_DTU / DW3000_CHIP_PER_DLY); +} + +#endif /* __DW3000_CORE_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_core_reg.h b/kernel/drivers/net/ieee802154/dw3000_core_reg.h new file mode 100644 index 0000000..592f582 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_core_reg.h @@ -0,0 +1,2219 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_CORE_REG_H +#define __DW3000_CORE_REG_H + +#include "dw3000_compat_reg.h" + +/* + * Please, keep registers defines ordered by address. + * This will ease finding duplicate or renaming. + */ + +/* register DEV_ID */ +#define DW3000_DEV_ID_ID 0x0 + +/* register EUI_64 */ +#define DW3000_EUI_64_ID 0x4 +#define DW3000_EUI_64_LEN (8U) + +/* register SYS_CFG */ +#define DW3000_SYS_CFG_ID 0x10 +#define DW3000_SYS_CFG_LEN (4U) +#define DW3000_SYS_CFG_MASK 0xFFFFFFFFUL +#define DW3000_SYS_CFG_COEX_OUT_MODE_BIT_OFFSET (23U) +#define DW3000_SYS_CFG_COEX_OUT_MODE_BIT_LEN (2U) +#define DW3000_SYS_CFG_COEX_OUT_MODE_BIT_MASK 0x1800000UL +#define DW3000_SYS_CFG_DS_IE2_BIT_OFFSET (20U) +#define DW3000_SYS_CFG_DS_IE2_BIT_LEN (1U) +#define DW3000_SYS_CFG_DS_IE2_BIT_MASK 0x100000UL +#define DW3000_SYS_CFG_FAST_AAT_EN_BIT_OFFSET (18U) +#define DW3000_SYS_CFG_FAST_AAT_EN_BIT_LEN (1U) +#define DW3000_SYS_CFG_FAST_AAT_EN_BIT_MASK 0x40000UL +#define DW3000_SYS_CFG_PDOA_MODE_BIT_OFFSET (16U) +#define DW3000_SYS_CFG_PDOA_MODE_BIT_LEN (2U) +#define DW3000_SYS_CFG_PDOA_MODE_BIT_MASK 0x30000UL +#define DW3000_SYS_CFG_CP_SDC_BIT_OFFSET (15U) +#define DW3000_SYS_CFG_CP_SDC_BIT_LEN (1U) +#define DW3000_SYS_CFG_CP_SDC_BIT_MASK 0x8000U +#define DW3000_SYS_CFG_CP_TYPE_BIT_OFFSET (14U) +#define DW3000_SYS_CFG_CP_TYPE_BIT_LEN (1U) +#define DW3000_SYS_CFG_CP_TYPE_BIT_MASK 0x4000U +#define DW3000_SYS_CFG_CP_PROTOCOL_BIT_OFFSET (12U) +#define DW3000_SYS_CFG_CP_PROTOCOL_BIT_LEN (2U) +#define DW3000_SYS_CFG_CP_PROTOCOL_BIT_MASK 0x3000U +#define DW3000_SYS_CFG_AUTO_ACK_BIT_OFFSET (11U) +#define DW3000_SYS_CFG_AUTO_ACK_BIT_LEN (1U) +#define DW3000_SYS_CFG_AUTO_ACK_BIT_MASK 0x800U +#define DW3000_SYS_CFG_RXAUTR_BIT_OFFSET (10U) +#define DW3000_SYS_CFG_RXAUTR_BIT_LEN (1U) +#define DW3000_SYS_CFG_RXAUTR_BIT_MASK 0x400U +#define DW3000_SYS_CFG_RXWTOE_BIT_OFFSET (9U) +#define DW3000_SYS_CFG_RXWTOE_BIT_LEN (1U) +#define DW3000_SYS_CFG_RXWTOE_BIT_MASK 0x200U +#define DW3000_SYS_CFG_CIA_STS_BIT_OFFSET (8U) +#define DW3000_SYS_CFG_CIA_STS_BIT_LEN (1U) +#define DW3000_SYS_CFG_CIA_STS_BIT_MASK 0x100U +#define DW3000_SYS_CFG_CIA_IPATOV_BIT_OFFSET (7U) +#define DW3000_SYS_CFG_CIA_IPATOV_BIT_LEN (1U) +#define DW3000_SYS_CFG_CIA_IPATOV_BIT_MASK 0x80U +#define DW3000_SYS_CFG_SPI_CRC_BIT_OFFSET (6U) +#define DW3000_SYS_CFG_SPI_CRC_BIT_LEN (1U) +#define DW3000_SYS_CFG_SPI_CRC_BIT_MASK 0x40U +#define DW3000_SYS_CFG_PHR_6M8_BIT_OFFSET (5U) +#define DW3000_SYS_CFG_PHR_6M8_BIT_LEN (1U) +#define DW3000_SYS_CFG_PHR_6M8_BIT_MASK 0x20U +#define DW3000_SYS_CFG_PHR_MODE_BIT_OFFSET (4U) +#define DW3000_SYS_CFG_PHR_MODE_BIT_LEN (1U) +#define DW3000_SYS_CFG_PHR_MODE_BIT_MASK 0x10U +#define DW3000_SYS_CFG_EN_DRXB_BIT_OFFSET (3U) +#define DW3000_SYS_CFG_EN_DRXB_BIT_LEN (1U) +#define DW3000_SYS_CFG_EN_DRXB_BIT_MASK 0x8U +#define DW3000_SYS_CFG_DIS_FCE_BIT_OFFSET (2U) +#define DW3000_SYS_CFG_DIS_FCE_BIT_LEN (1U) +#define DW3000_SYS_CFG_DIS_FCE_BIT_MASK 0x4U +#define DW3000_SYS_CFG_DIS_FCS_TX_BIT_OFFSET (1U) +#define DW3000_SYS_CFG_DIS_FCS_TX_BIT_LEN (1U) +#define DW3000_SYS_CFG_DIS_FCS_TX_BIT_MASK 0x2U +#define DW3000_SYS_CFG_FFEN_BIT_OFFSET (0U) +#define DW3000_SYS_CFG_FFEN_BIT_LEN (1U) +#define DW3000_SYS_CFG_FFEN_BIT_MASK 0x1U + +/* register PANADR */ +#define DW3000_PANADR_ID 0xc +#define DW3000_PANADR_SHORT_ADDR_BIT_OFFSET (0U) +#define DW3000_PANADR_SHORT_ADDR_BIT_LEN (2U) +#define DW3000_PANADR_PAN_ID_BIT_OFFSET (16U) +#define DW3000_PANADR_PAN_ID_BIT_LEN (2U) + +/* register ADR_FILT_CFG */ +#define DW3000_ADR_FILT_CFG_ID 0x14 +#define DW3000_ADR_FILT_CFG_FFBC_BIT_LEN (1U) +/* DW3000 as pan coordinator */ +#define DW3000_AS_PANCOORD 0x03 + +/* register SPICRC_CFG */ +#define DW3000_SPICRC_CFG_ID 0x18 + +/* register SYS_TIME */ +#define DW3000_SYS_TIME_ID 0x1c + +/* register TX_FCTRL_HI 0x24, 0x20 */ +#define DW3000_TX_FCTRL_LEN (4U) +#define DW3000_TX_FCTRL_MASK 0xFFFFFFFFUL +#define DW3000_TX_FCTRL_TXB_OFFSET_BIT_OFFSET (16U) +#define DW3000_TX_FCTRL_TXB_OFFSET_BIT_LEN (10U) +#define DW3000_TX_FCTRL_TXB_OFFSET_BIT_MASK 0x3ff0000UL +#define DW3000_TX_FCTRL_TXPSR_PE_BIT_OFFSET (12U) +#define DW3000_TX_FCTRL_TXPSR_PE_BIT_LEN (4U) +#define DW3000_TX_FCTRL_TXPSR_PE_BIT_MASK 0xf000U +#define DW3000_TX_FCTRL_TR_BIT_OFFSET (11U) +#define DW3000_TX_FCTRL_TR_BIT_LEN (1U) +#define DW3000_TX_FCTRL_TR_BIT_MASK 0x800U +#define DW3000_TX_FCTRL_TXBR_BIT_OFFSET (10U) +#define DW3000_TX_FCTRL_TXBR_BIT_LEN (1U) +#define DW3000_TX_FCTRL_TXBR_BIT_MASK 0x400U +#define DW3000_TX_FCTRL_TXFLEN_BIT_OFFSET (0U) +#define DW3000_TX_FCTRL_TXFLEN_BIT_LEN (10U) +#define DW3000_TX_FCTRL_TXFLEN_BIT_MASK 0x3ffU + +/* register RX_FWTO */ +#define DW3000_RX_FWTO_ID 0x34 + +/* register SYS_ENABLE_LO */ +#define DW3000_SYS_ENABLE_LO_ID 0x3c +#define DW3000_SYS_ENABLE_LO_LEN (4U) +#define DW3000_SYS_ENABLE_LO_MASK 0xFFFFFFFFUL +#define DW3000_SYS_ENABLE_LO_ARFE_ENABLE_BIT_OFFSET (29U) +#define DW3000_SYS_ENABLE_LO_ARFE_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_ARFE_ENABLE_BIT_MASK 0x20000000UL +#define DW3000_SYS_ENABLE_LO_CPERR_ENABLE_BIT_OFFSET (28U) +#define DW3000_SYS_ENABLE_LO_CPERR_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_CPERR_ENABLE_BIT_MASK 0x10000000UL +#define DW3000_SYS_ENABLE_LO_HPDWARN_ENABLE_BIT_OFFSET (27U) +#define DW3000_SYS_ENABLE_LO_HPDWARN_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_HPDWARN_ENABLE_BIT_MASK 0x8000000UL +#define DW3000_SYS_ENABLE_LO_RXSTO_ENABLE_BIT_OFFSET (26U) +#define DW3000_SYS_ENABLE_LO_RXSTO_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_RXSTO_ENABLE_BIT_MASK 0x4000000UL +#define DW3000_SYS_ENABLE_LO_PLL_HILO_ENABLE_BIT_OFFSET (25U) +#define DW3000_SYS_ENABLE_LO_PLL_HILO_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_PLL_HILO_ENABLE_BIT_MASK 0x2000000UL +#define DW3000_SYS_ENABLE_LO_RCINIT_ENABLE_BIT_OFFSET (24U) +#define DW3000_SYS_ENABLE_LO_RCINIT_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_RCINIT_ENABLE_BIT_MASK 0x1000000UL +#define DW3000_SYS_ENABLE_LO_SPIRDY_ENABLE_BIT_OFFSET (23U) +#define DW3000_SYS_ENABLE_LO_SPIRDY_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_SPIRDY_ENABLE_BIT_MASK 0x800000UL +#define DW3000_SYS_ENABLE_LO_LCSSERR_ENABLE_BIT_OFFSET (22U) +#define DW3000_SYS_ENABLE_LO_LCSSERR_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_LCSSERR_ENABLE_BIT_MASK 0x400000UL +#define DW3000_SYS_ENABLE_LO_RXPTO_ENABLE_BIT_OFFSET (21U) +#define DW3000_SYS_ENABLE_LO_RXPTO_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_RXPTO_ENABLE_BIT_MASK 0x200000UL +#define DW3000_SYS_ENABLE_LO_RXOVRR_ENABLE_BIT_OFFSET (20U) +#define DW3000_SYS_ENABLE_LO_RXOVRR_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_RXOVRR_ENABLE_BIT_MASK 0x100000UL +#define DW3000_SYS_ENABLE_LO_VWARN_ENABLE_BIT_OFFSET (19U) +#define DW3000_SYS_ENABLE_LO_VWARN_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_VWARN_ENABLE_BIT_MASK 0x80000UL +#define DW3000_SYS_ENABLE_LO_CIAERR_ENABLE_BIT_OFFSET (18U) +#define DW3000_SYS_ENABLE_LO_CIAERR_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_CIAERR_ENABLE_BIT_MASK 0x40000UL +#define DW3000_SYS_ENABLE_LO_RXFTO_ENABLE_BIT_OFFSET (17U) +#define DW3000_SYS_ENABLE_LO_RXFTO_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_RXFTO_ENABLE_BIT_MASK 0x20000UL +#define DW3000_SYS_ENABLE_LO_RXFSL_ENABLE_BIT_OFFSET (16U) +#define DW3000_SYS_ENABLE_LO_RXFSL_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_RXFSL_ENABLE_BIT_MASK 0x10000UL +#define DW3000_SYS_ENABLE_LO_RXFCE_ENABLE_BIT_OFFSET (15U) +#define DW3000_SYS_ENABLE_LO_RXFCE_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_RXFCE_ENABLE_BIT_MASK 0x8000U +#define DW3000_SYS_ENABLE_LO_RXFCG_ENABLE_BIT_OFFSET (14U) +#define DW3000_SYS_ENABLE_LO_RXFCG_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_RXFCG_ENABLE_BIT_MASK 0x4000U +#define DW3000_SYS_ENABLE_LO_RXFR_ENABLE_BIT_OFFSET (13U) +#define DW3000_SYS_ENABLE_LO_RXFR_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_RXFR_ENABLE_BIT_MASK 0x2000U +#define DW3000_SYS_ENABLE_LO_RXPHE_ENABLE_BIT_OFFSET (12U) +#define DW3000_SYS_ENABLE_LO_RXPHE_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_RXPHE_ENABLE_BIT_MASK 0x1000U +#define DW3000_SYS_ENABLE_LO_RXPHD_ENABLE_BIT_OFFSET (11U) +#define DW3000_SYS_ENABLE_LO_RXPHD_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_RXPHD_ENABLE_BIT_MASK 0x800U +#define DW3000_SYS_ENABLE_LO_CIA_DONE_ENABLE_BIT_OFFSET (10U) +#define DW3000_SYS_ENABLE_LO_CIA_DONE_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_CIA_DONE_ENABLE_BIT_MASK 0x400U +#define DW3000_SYS_ENABLE_LO_RXSFDD_ENABLE_BIT_OFFSET (9U) +#define DW3000_SYS_ENABLE_LO_RXSFDD_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_RXSFDD_ENABLE_BIT_MASK 0x200U +#define DW3000_SYS_ENABLE_LO_RXPRD_ENABLE_BIT_OFFSET (8U) +#define DW3000_SYS_ENABLE_LO_RXPRD_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_RXPRD_ENABLE_BIT_MASK 0x100U +#define DW3000_SYS_ENABLE_LO_TXFRS_ENABLE_BIT_OFFSET (7U) +#define DW3000_SYS_ENABLE_LO_TXFRS_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_TXFRS_ENABLE_BIT_MASK 0x80U +#define DW3000_SYS_ENABLE_LO_TXPHS_ENABLE_BIT_OFFSET (6U) +#define DW3000_SYS_ENABLE_LO_TXPHS_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_TXPHS_ENABLE_BIT_MASK 0x40U +#define DW3000_SYS_ENABLE_LO_TXPRS_ENABLE_BIT_OFFSET (5U) +#define DW3000_SYS_ENABLE_LO_TXPRS_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_TXPRS_ENABLE_BIT_MASK 0x20U +#define DW3000_SYS_ENABLE_LO_TXFRB_ENABLE_BIT_OFFSET (4U) +#define DW3000_SYS_ENABLE_LO_TXFRB_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_TXFRB_ENABLE_BIT_MASK 0x10U +#define DW3000_SYS_ENABLE_LO_AAT_ENABLE_BIT_OFFSET (3U) +#define DW3000_SYS_ENABLE_LO_AAT_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_AAT_ENABLE_BIT_MASK 0x8U +#define DW3000_SYS_ENABLE_LO_SPICRCERR_ENABLE_BIT_OFFSET (2U) +#define DW3000_SYS_ENABLE_LO_SPICRCERR_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_SPICRCERR_ENABLE_BIT_MASK 0x4U +#define DW3000_SYS_ENABLE_LO_CLK_PLL_LOCK_ENABLE_BIT_OFFSET (1U) +#define DW3000_SYS_ENABLE_LO_CLK_PLL_LOCK_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_LO_CLK_PLL_LOCK_ENABLE_BIT_MASK 0x2U + +/* register SYS_ENABLE_HI */ +#define DW3000_SYS_ENABLE_HI_ID 0x40 +#define DW3000_SYS_ENABLE_HI_LEN (4U) +#define DW3000_SYS_ENABLE_HI_MASK 0xFFFFFFFFUL +#define DW3000_SYS_ENABLE_HI_RXSTS_ENABLE_BIT_OFFSET (17U) +#define DW3000_SYS_ENABLE_HI_RXSTS_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_HI_RXSTS_ENABLE_BIT_MASK 0x20000UL +#define DW3000_SYS_ENABLE_HI_TXSTS_ENABLE_BIT_OFFSET (16U) +#define DW3000_SYS_ENABLE_HI_TXSTS_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_HI_TXSTS_ENABLE_BIT_MASK 0x10000UL +#define DW3000_SYS_ENABLE_HI_SEMA_ERR_ENABLE_BIT_OFFSET (15U) +#define DW3000_SYS_ENABLE_HI_SEMA_ERR_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_HI_SEMA_ERR_ENABLE_BIT_MASK 0x8000U +#define DW3000_SYS_ENABLE_HI_COEX_CLR_ENABLE_BIT_OFFSET (14U) +#define DW3000_SYS_ENABLE_HI_COEX_CLR_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_HI_COEX_CLR_ENABLE_BIT_MASK 0x4000U +#define DW3000_SYS_ENABLE_HI_COEX_ERR_ENABLE_BIT_OFFSET (13U) +#define DW3000_SYS_ENABLE_HI_COEX_ERR_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_HI_COEX_ERR_ENABLE_BIT_MASK 0x2000U +#define DW3000_SYS_ENABLE_HI_CCA_FAIL_ENABLE_BIT_OFFSET (12U) +#define DW3000_SYS_ENABLE_HI_CCA_FAIL_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_HI_CCA_FAIL_ENABLE_BIT_MASK 0x1000U +#define DW3000_SYS_ENABLE_HI_SPIERR_ENABLE_BIT_OFFSET (11U) +#define DW3000_SYS_ENABLE_HI_SPIERR_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_HI_SPIERR_ENABLE_BIT_MASK 0x800U +#define DW3000_SYS_ENABLE_HI_SPI_UNF_ENABLE_BIT_OFFSET (10U) +#define DW3000_SYS_ENABLE_HI_SPI_UNF_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_HI_SPI_UNF_ENABLE_BIT_MASK 0x400U +#define DW3000_SYS_ENABLE_HI_SPI_OVF_ENABLE_BIT_OFFSET (9U) +#define DW3000_SYS_ENABLE_HI_SPI_OVF_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_HI_SPI_OVF_ENABLE_BIT_MASK 0x200U +#define DW3000_SYS_ENABLE_HI_CMD_ERR_ENABLE_BIT_OFFSET (8U) +#define DW3000_SYS_ENABLE_HI_CMD_ERR_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_HI_CMD_ERR_ENABLE_BIT_MASK 0x100U +#define DW3000_SYS_ENABLE_HI_AES_ERR_ENABLE_BIT_OFFSET (7U) +#define DW3000_SYS_ENABLE_HI_AES_ERR_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_HI_AES_ERR_ENABLE_BIT_MASK 0x80U +#define DW3000_SYS_ENABLE_HI_AES_DONE_ENABLE_BIT_OFFSET (6U) +#define DW3000_SYS_ENABLE_HI_AES_DONE_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_HI_AES_DONE_ENABLE_BIT_MASK 0x40U +#define DW3000_SYS_ENABLE_HI_GPIOIRQ_ENABLE_BIT_OFFSET (5U) +#define DW3000_SYS_ENABLE_HI_GPIOIRQ_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_HI_GPIOIRQ_ENABLE_BIT_MASK 0x20U +#define DW3000_SYS_ENABLE_HI_VT_DET_ENABLE_BIT_OFFSET (4U) +#define DW3000_SYS_ENABLE_HI_VT_DET_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_HI_VT_DET_ENABLE_BIT_MASK 0x10U +#define DW3000_SYS_ENABLE_HI_PGFCAL_ERR_ENABLE_BIT_OFFSET (2U) +#define DW3000_SYS_ENABLE_HI_PGFCAL_ERR_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_HI_PGFCAL_ERR_ENABLE_BIT_MASK 0x4U +#define DW3000_SYS_ENABLE_HI_RXPREJ_ENABLE_BIT_OFFSET (1U) +#define DW3000_SYS_ENABLE_HI_RXPREJ_ENABLE_BIT_LEN (1U) +#define DW3000_SYS_ENABLE_HI_RXPREJ_ENABLE_BIT_MASK 0x2U + +/* register SYS_STATUS */ +#define DW3000_SYS_STATUS_ID 0x44 +#define DW3000_SYS_STATUS_LEN (4U) +#define DW3000_SYS_STATUS_MASK 0xFFFFFFFFUL +#define DW3000_SYS_STATUS_TIMER1_BIT_OFFSET (31U) +#define DW3000_SYS_STATUS_TIMER1_BIT_LEN (1U) +#define DW3000_SYS_STATUS_TIMER1_BIT_MASK 0x80000000UL +#define DW3000_SYS_STATUS_TIMER0_BIT_OFFSET (30U) +#define DW3000_SYS_STATUS_TIMER0_BIT_LEN (1U) +#define DW3000_SYS_STATUS_TIMER0_BIT_MASK 0x40000000UL +#define DW3000_SYS_STATUS_ARFE_BIT_OFFSET (29U) +#define DW3000_SYS_STATUS_ARFE_BIT_LEN (1U) +#define DW3000_SYS_STATUS_ARFE_BIT_MASK 0x20000000UL +#define DW3000_SYS_STATUS_CPERR_BIT_OFFSET (28U) +#define DW3000_SYS_STATUS_CPERR_BIT_LEN (1U) +#define DW3000_SYS_STATUS_CPERR_BIT_MASK 0x10000000UL +#define DW3000_SYS_STATUS_HPDWARN_BIT_OFFSET (27U) +#define DW3000_SYS_STATUS_HPDWARN_BIT_LEN (1U) +#define DW3000_SYS_STATUS_HPDWARN_BIT_MASK 0x8000000UL +#define DW3000_SYS_STATUS_RXSTO_BIT_OFFSET (26U) +#define DW3000_SYS_STATUS_RXSTO_BIT_LEN (1U) +#define DW3000_SYS_STATUS_RXSTO_BIT_MASK 0x4000000UL +#define DW3000_SYS_STATUS_PLL_HILO_BIT_OFFSET (25U) +#define DW3000_SYS_STATUS_PLL_HILO_BIT_LEN (1U) +#define DW3000_SYS_STATUS_PLL_HILO_BIT_MASK 0x2000000UL +#define DW3000_SYS_STATUS_RCINIT_BIT_OFFSET (24U) +#define DW3000_SYS_STATUS_RCINIT_BIT_LEN (1U) +#define DW3000_SYS_STATUS_RCINIT_BIT_MASK 0x1000000UL +#define DW3000_SYS_STATUS_SPIRDY_BIT_OFFSET (23U) +#define DW3000_SYS_STATUS_SPIRDY_BIT_LEN (1U) +#define DW3000_SYS_STATUS_SPIRDY_BIT_MASK 0x800000UL +#define DW3000_SYS_STATUS_LCSSERR_BIT_OFFSET (22U) +#define DW3000_SYS_STATUS_LCSSERR_BIT_LEN (1U) +#define DW3000_SYS_STATUS_LCSSERR_BIT_MASK 0x400000UL +#define DW3000_SYS_STATUS_RXPTO_BIT_OFFSET (21U) +#define DW3000_SYS_STATUS_RXPTO_BIT_LEN (1U) +#define DW3000_SYS_STATUS_RXPTO_BIT_MASK 0x200000UL +#define DW3000_SYS_STATUS_RXOVRR_BIT_OFFSET (20U) +#define DW3000_SYS_STATUS_RXOVRR_BIT_LEN (1U) +#define DW3000_SYS_STATUS_RXOVRR_BIT_MASK 0x100000UL +#define DW3000_SYS_STATUS_VWARN_BIT_OFFSET (19U) +#define DW3000_SYS_STATUS_VWARN_BIT_LEN (1U) +#define DW3000_SYS_STATUS_VWARN_BIT_MASK 0x80000UL +#define DW3000_SYS_STATUS_CIAERR_BIT_OFFSET (18U) +#define DW3000_SYS_STATUS_CIAERR_BIT_LEN (1U) +#define DW3000_SYS_STATUS_CIAERR_BIT_MASK 0x40000UL +#define DW3000_SYS_STATUS_RXFTO_BIT_OFFSET (17U) +#define DW3000_SYS_STATUS_RXFTO_BIT_LEN (1U) +#define DW3000_SYS_STATUS_RXFTO_BIT_MASK 0x20000UL +#define DW3000_SYS_STATUS_RXFSL_BIT_OFFSET (16U) +#define DW3000_SYS_STATUS_RXFSL_BIT_LEN (1U) +#define DW3000_SYS_STATUS_RXFSL_BIT_MASK 0x10000UL +#define DW3000_SYS_STATUS_RXFCE_BIT_OFFSET (15U) +#define DW3000_SYS_STATUS_RXFCE_BIT_LEN (1U) +#define DW3000_SYS_STATUS_RXFCE_BIT_MASK 0x8000U +#define DW3000_SYS_STATUS_RXFCG_BIT_OFFSET (14U) +#define DW3000_SYS_STATUS_RXFCG_BIT_LEN (1U) +#define DW3000_SYS_STATUS_RXFCG_BIT_MASK 0x4000U +#define DW3000_SYS_STATUS_RXFR_BIT_OFFSET (13U) +#define DW3000_SYS_STATUS_RXFR_BIT_LEN (1U) +#define DW3000_SYS_STATUS_RXFR_BIT_MASK 0x2000U +#define DW3000_SYS_STATUS_RXPHE_BIT_OFFSET (12U) +#define DW3000_SYS_STATUS_RXPHE_BIT_LEN (1U) +#define DW3000_SYS_STATUS_RXPHE_BIT_MASK 0x1000U +#define DW3000_SYS_STATUS_RXPHD_BIT_OFFSET (11U) +#define DW3000_SYS_STATUS_RXPHD_BIT_LEN (1U) +#define DW3000_SYS_STATUS_RXPHD_BIT_MASK 0x800U +#define DW3000_SYS_STATUS_CIA_DONE_BIT_OFFSET (10U) +#define DW3000_SYS_STATUS_CIA_DONE_BIT_LEN (1U) +#define DW3000_SYS_STATUS_CIA_DONE_BIT_MASK 0x400U +#define DW3000_SYS_STATUS_RXSFDD_BIT_OFFSET (9U) +#define DW3000_SYS_STATUS_RXSFDD_BIT_LEN (1U) +#define DW3000_SYS_STATUS_RXSFDD_BIT_MASK 0x200U +#define DW3000_SYS_STATUS_RXPRD_BIT_OFFSET (8U) +#define DW3000_SYS_STATUS_RXPRD_BIT_LEN (1U) +#define DW3000_SYS_STATUS_RXPRD_BIT_MASK 0x100U +#define DW3000_SYS_STATUS_TXFRS_BIT_OFFSET (7U) +#define DW3000_SYS_STATUS_TXFRS_BIT_LEN (1U) +#define DW3000_SYS_STATUS_TXFRS_BIT_MASK 0x80U +#define DW3000_SYS_STATUS_TXPHS_BIT_OFFSET (6U) +#define DW3000_SYS_STATUS_TXPHS_BIT_LEN (1U) +#define DW3000_SYS_STATUS_TXPHS_BIT_MASK 0x40U +#define DW3000_SYS_STATUS_TXPRS_BIT_OFFSET (5U) +#define DW3000_SYS_STATUS_TXPRS_BIT_LEN (1U) +#define DW3000_SYS_STATUS_TXPRS_BIT_MASK 0x20U +#define DW3000_SYS_STATUS_TXFRB_BIT_OFFSET (4U) +#define DW3000_SYS_STATUS_TXFRB_BIT_LEN (1U) +#define DW3000_SYS_STATUS_TXFRB_BIT_MASK 0x10U +#define DW3000_SYS_STATUS_AAT_BIT_OFFSET (3U) +#define DW3000_SYS_STATUS_AAT_BIT_LEN (1U) +#define DW3000_SYS_STATUS_AAT_BIT_MASK 0x8U +#define DW3000_SYS_STATUS_SPICRCERR_BIT_OFFSET (2U) +#define DW3000_SYS_STATUS_SPICRCERR_BIT_LEN (1U) +#define DW3000_SYS_STATUS_SPICRCERR_BIT_MASK 0x4U +#define DW3000_SYS_STATUS_CLK_PLL_LOCK_BIT_OFFSET (1U) +#define DW3000_SYS_STATUS_CLK_PLL_LOCK_BIT_LEN (1U) +#define DW3000_SYS_STATUS_CLK_PLL_LOCK_BIT_MASK 0x2U +#define DW3000_SYS_STATUS_IRQS_BIT_OFFSET (0U) +#define DW3000_SYS_STATUS_IRQS_BIT_LEN (1U) +#define DW3000_SYS_STATUS_IRQS_BIT_MASK 0x1U + +/* register SYS_STATUS_HI */ +#define DW3000_SYS_STATUS_HI_ID 0x48 +#define DW3000_SYS_STATUS_HI_LEN (4U) +#define DW3000_SYS_STATUS_HI_MASK 0xFFFFFFFFUL +#define DW3000_SYS_STATUS_HI_RXSTS_BIT_OFFSET (17U) +#define DW3000_SYS_STATUS_HI_RXSTS_BIT_LEN (1U) +#define DW3000_SYS_STATUS_HI_RXSTS_BIT_MASK 0x20000UL +#define DW3000_SYS_STATUS_HI_TXSTS_BIT_OFFSET (16U) +#define DW3000_SYS_STATUS_HI_TXSTS_BIT_LEN (1U) +#define DW3000_SYS_STATUS_HI_TXSTS_BIT_MASK 0x10000UL +#define DW3000_SYS_STATUS_HI_SEMA_ERR_BIT_OFFSET (15U) +#define DW3000_SYS_STATUS_HI_SEMA_ERR_BIT_LEN (1U) +#define DW3000_SYS_STATUS_HI_SEMA_ERR_BIT_MASK 0x8000U +#define DW3000_SYS_STATUS_HI_COEX_CLR_BIT_OFFSET (14U) +#define DW3000_SYS_STATUS_HI_COEX_CLR_BIT_LEN (1U) +#define DW3000_SYS_STATUS_HI_COEX_CLR_BIT_MASK 0x4000U +#define DW3000_SYS_STATUS_HI_COEX_ERR_BIT_OFFSET (13U) +#define DW3000_SYS_STATUS_HI_COEX_ERR_BIT_LEN (1U) +#define DW3000_SYS_STATUS_HI_COEX_ERR_BIT_MASK 0x2000U +#define DW3000_SYS_STATUS_HI_CCA_FAIL_BIT_OFFSET (12U) +#define DW3000_SYS_STATUS_HI_CCA_FAIL_BIT_LEN (1U) +#define DW3000_SYS_STATUS_HI_CCA_FAIL_BIT_MASK 0x1000U +#define DW3000_SYS_STATUS_HI_SPIERR_BIT_OFFSET (11U) +#define DW3000_SYS_STATUS_HI_SPIERR_BIT_LEN (1U) +#define DW3000_SYS_STATUS_HI_SPIERR_BIT_MASK 0x800U +#define DW3000_SYS_STATUS_HI_SPI_UNF_BIT_OFFSET (10U) +#define DW3000_SYS_STATUS_HI_SPI_UNF_BIT_LEN (1U) +#define DW3000_SYS_STATUS_HI_SPI_UNF_BIT_MASK 0x400U +#define DW3000_SYS_STATUS_HI_SPI_OVF_BIT_OFFSET (9U) +#define DW3000_SYS_STATUS_HI_SPI_OVF_BIT_LEN (1U) +#define DW3000_SYS_STATUS_HI_SPI_OVF_BIT_MASK 0x200U +#define DW3000_SYS_STATUS_HI_CMD_ERR_BIT_OFFSET (8U) +#define DW3000_SYS_STATUS_HI_CMD_ERR_BIT_LEN (1U) +#define DW3000_SYS_STATUS_HI_CMD_ERR_BIT_MASK 0x100U +#define DW3000_SYS_STATUS_HI_AES_ERR_BIT_OFFSET (7U) +#define DW3000_SYS_STATUS_HI_AES_ERR_BIT_LEN (1U) +#define DW3000_SYS_STATUS_HI_AES_ERR_BIT_MASK 0x80U +#define DW3000_SYS_STATUS_HI_AES_DONE_BIT_OFFSET (6U) +#define DW3000_SYS_STATUS_HI_AES_DONE_BIT_LEN (1U) +#define DW3000_SYS_STATUS_HI_AES_DONE_BIT_MASK 0x40U +#define DW3000_SYS_STATUS_HI_GPIO_IRQ_BIT_OFFSET (5U) +#define DW3000_SYS_STATUS_HI_GPIO_IRQ_BIT_LEN (1U) +#define DW3000_SYS_STATUS_HI_GPIO_IRQ_BIT_MASK 0x20U +#define DW3000_SYS_STATUS_HI_VT_DET_BIT_OFFSET (4U) +#define DW3000_SYS_STATUS_HI_VT_DET_BIT_LEN (1U) +#define DW3000_SYS_STATUS_HI_VT_DET_BIT_MASK 0x10U +#define DW3000_SYS_STATUS_HI_PGFCAL_ERR_BIT_OFFSET (2U) +#define DW3000_SYS_STATUS_HI_PGFCAL_ERR_BIT_LEN (1U) +#define DW3000_SYS_STATUS_HI_PGFCAL_ERR_BIT_MASK 0x4U +#define DW3000_SYS_STATUS_HI_RXPREJ_BIT_OFFSET (1U) +#define DW3000_SYS_STATUS_HI_RXPREJ_BIT_LEN (1U) +#define DW3000_SYS_STATUS_HI_RXPREJ_BIT_MASK 0x2U + +/* register RX_FINFO */ +#define DW3000_RX_FINFO_ID 0x4c +#define DW3000_RX_FINFO_LEN (4U) +#define DW3000_RX_FINFO_MASK 0xFFFFFFFFUL +#define DW3000_RX_FINFO_RXP2SS_BIT_OFFSET (20U) +#define DW3000_RX_FINFO_RXP2SS_BIT_LEN (12U) +#define DW3000_RX_FINFO_RXP2SS_BIT_MASK 0xfff00000UL +#define DW3000_RX_FINFO_RXPSR_BIT_OFFSET (18U) +#define DW3000_RX_FINFO_RXPSR_BIT_LEN (2U) +#define DW3000_RX_FINFO_RXPSR_BIT_MASK 0xc0000UL +#define DW3000_RX_FINFO_RXPRF_BIT_OFFSET (16U) +#define DW3000_RX_FINFO_RXPRF_BIT_LEN (2U) +#define DW3000_RX_FINFO_RXPRF_BIT_MASK 0x30000UL +#define DW3000_RX_FINFO_RNG_BIT_OFFSET (15U) +#define DW3000_RX_FINFO_RNG_BIT_LEN (1U) +#define DW3000_RX_FINFO_RNG_BIT_MASK 0x8000U +#define DW3000_RX_FINFO_RXBR_BIT_OFFSET (13U) +#define DW3000_RX_FINFO_RXBR_BIT_LEN (1U) +#define DW3000_RX_FINFO_RXBR_BIT_MASK 0x2000U +#define DW3000_RX_FINFO_RXNSPL_BIT_OFFSET (11U) +#define DW3000_RX_FINFO_RXNSPL_BIT_LEN (2U) +#define DW3000_RX_FINFO_RXNSPL_BIT_MASK 0x1800U +#define DW3000_RX_FINFO_RXFLEN_BIT_OFFSET (0U) +#define DW3000_RX_FINFO_RXFLEN_BIT_LEN (10U) +#define DW3000_RX_FINFO_RXFLEN_BIT_MASK 0x3ffU + +/* register RX_TIME_0 0x64/060 */ +#define DW3000_RX_TIME_RX_STAMP_LEN 5 + +/* Register ACK_RESP. 0x10008/0x10000 */ +#define DW3000_ACK_RESP_LEN (4U) +#define DW3000_ACK_RESP_MASK 0xFFFFFFFFUL +#define DW3000_ACK_RESP_ACK_TIM_BIT_OFFSET (24U) +#define DW3000_ACK_RESP_ACK_TIM_BIT_LEN (8U) +#define DW3000_ACK_RESP_ACK_TIM_BIT_MASK 0xff000000UL +#define DW3000_ACK_RESP_WAIT4RESP_TIM_BIT_OFFSET (0U) +#define DW3000_ACK_RESP_WAIT4RESP_TIM_BIT_LEN (20U) +#define DW3000_ACK_RESP_WAIT4RESP_TIM_BIT_MASK 0xfffffUL + +/* register CHAN_CTRL 0x10014/0x10008 */ +#define DW3000_CHAN_CTRL_LEN (4U) +#define DW3000_CHAN_CTRL_MASK 0xFFFFFFFFUL +#define DW3000_CHAN_CTRL_RX_PCODE_BIT_OFFSET (8U) +#define DW3000_CHAN_CTRL_RX_PCODE_BIT_LEN (5U) +#define DW3000_CHAN_CTRL_RX_PCODE_BIT_MASK 0x1f00U +#define DW3000_CHAN_CTRL_TX_PCODE_BIT_OFFSET (3U) +#define DW3000_CHAN_CTRL_TX_PCODE_BIT_LEN (5U) +#define DW3000_CHAN_CTRL_TX_PCODE_BIT_MASK 0xf8U +#define DW3000_CHAN_CTRL_SFD_TYPE_BIT_OFFSET (1U) +#define DW3000_CHAN_CTRL_SFD_TYPE_BIT_LEN (2U) +#define DW3000_CHAN_CTRL_SFD_TYPE_BIT_MASK 0x6U +#define DW3000_CHAN_CTRL_RF_CHAN_BIT_OFFSET (0U) +#define DW3000_CHAN_CTRL_RF_CHAN_BIT_LEN (1U) +#define DW3000_CHAN_CTRL_RF_CHAN_BIT_MASK 0x1U + +/* register SPI_COLLISION 0x10020/0x10014 */ +#define DW3000_SPI_COLLISION_STATUS_BIT_MASK 0x1fU + +/* register RDB_STATUS 0x10024/0x10018 */ +#define DW3000_RDB_STATUS_LEN (4U) +#define DW3000_RDB_STATUS_MASK 0xFFFFFFFFUL +#define DW3000_RDB_STATUS_CP_ERR1_BIT_OFFSET (7U) +#define DW3000_RDB_STATUS_CP_ERR1_BIT_LEN (1U) +#define DW3000_RDB_STATUS_CP_ERR1_BIT_MASK 0x80U +#define DW3000_RDB_STATUS_CIADONE1_BIT_OFFSET (6U) +#define DW3000_RDB_STATUS_CIADONE1_BIT_LEN (1U) +#define DW3000_RDB_STATUS_CIADONE1_BIT_MASK 0x40U +#define DW3000_RDB_STATUS_RXFR1_BIT_OFFSET (5U) +#define DW3000_RDB_STATUS_RXFR1_BIT_LEN (1U) +#define DW3000_RDB_STATUS_RXFR1_BIT_MASK 0x20U +#define DW3000_RDB_STATUS_RXFCG1_BIT_OFFSET (4U) +#define DW3000_RDB_STATUS_RXFCG1_BIT_LEN (1U) +#define DW3000_RDB_STATUS_RXFCG1_BIT_MASK 0x10U +#define DW3000_RDB_STATUS_CP_ERR0_BIT_OFFSET (3U) +#define DW3000_RDB_STATUS_CP_ERR0_BIT_LEN (1U) +#define DW3000_RDB_STATUS_CP_ERR0_BIT_MASK 0x8U +#define DW3000_RDB_STATUS_CIADONE0_BIT_OFFSET (2U) +#define DW3000_RDB_STATUS_CIADONE0_BIT_LEN (1U) +#define DW3000_RDB_STATUS_CIADONE0_BIT_MASK 0x4U +#define DW3000_RDB_STATUS_RXFR0_BIT_OFFSET (1U) +#define DW3000_RDB_STATUS_RXFR0_BIT_LEN (1U) +#define DW3000_RDB_STATUS_RXFR0_BIT_MASK 0x2U +#define DW3000_RDB_STATUS_RXFCG0_BIT_OFFSET (0U) +#define DW3000_RDB_STATUS_RXFCG0_BIT_LEN (1U) +#define DW3000_RDB_STATUS_RXFCG0_BIT_MASK 0x1U + +/* register RDB_DIAG_MODE 0x10028/0x10020 */ +#define DW3000_RDB_DIAG_MODE_LEN (4U) + +/* register AES_START */ +#define DW3000_AES_START_ID 0x1004c +#define DW3000_AES_START_LEN (4U) +#define DW3000_AES_START_MASK 0xFFFFFFFFUL +#define DW3000_AES_START_AES_START_BIT_OFFSET (0U) +#define DW3000_AES_START_AES_START_BIT_LEN (1U) +#define DW3000_AES_START_AES_START_BIT_MASK 0x1U + +/* register AES_STS */ +#define DW3000_AES_STS_ID 0x10050 +#define DW3000_AES_STS_LEN (4U) +#define DW3000_AES_STS_MASK 0xFFFFFFFFUL +#define DW3000_AES_STS_RAM_FULL_BIT_OFFSET (5U) +#define DW3000_AES_STS_RAM_FULL_BIT_LEN (1U) +#define DW3000_AES_STS_RAM_FULL_BIT_MASK 0x20U +#define DW3000_AES_STS_RAM_EMPTY_BIT_OFFSET (4U) +#define DW3000_AES_STS_RAM_EMPTY_BIT_LEN (1U) +#define DW3000_AES_STS_RAM_EMPTY_BIT_MASK 0x10U +#define DW3000_AES_STS_MEM_CONF_BIT_OFFSET (3U) +#define DW3000_AES_STS_MEM_CONF_BIT_LEN (1U) +#define DW3000_AES_STS_MEM_CONF_BIT_MASK 0x8U +#define DW3000_AES_STS_TRANS_ERR_BIT_OFFSET (2U) +#define DW3000_AES_STS_TRANS_ERR_BIT_LEN (1U) +#define DW3000_AES_STS_TRANS_ERR_BIT_MASK 0x4U +#define DW3000_AES_STS_AUTH_ERR_BIT_OFFSET (1U) +#define DW3000_AES_STS_AUTH_ERR_BIT_LEN (1U) +#define DW3000_AES_STS_AUTH_ERR_BIT_MASK 0x2U +#define DW3000_AES_STS_AES_DONE_BIT_OFFSET (0U) +#define DW3000_AES_STS_AES_DONE_BIT_LEN (1U) +#define DW3000_AES_STS_AES_DONE_BIT_MASK 0x1U + +/* register STS_CFG0/CP_CFG0 */ +#define DW3000_CP_CFG0_ID 0x20000 +#define DW3000_STS_CFG0_ID DW3000_CP_CFG0_ID +#define DW3000_STS_CFG0_LEN (4U) +#define DW3000_STS_CFG0_MASK 0xFFFFFFFFUL +#define DW3000_STS_CFG0_CPS_LEN_BIT_OFFSET (0U) +#define DW3000_STS_CFG0_CPS_LEN_BIT_LEN (8U) +#define DW3000_STS_CFG0_CPS_LEN_BIT_MASK 0xffU + +/* register STS_CTRL */ +#define DW3000_STS_CTRL_ID 0x20004 +#define DW3000_STS_CTRL_LEN (4U) +#define DW3000_STS_CTRL_MASK 0xFFFFFFFFUL +#define DW3000_STS_CTRL_RST_LAST_BIT_OFFSET (1U) +#define DW3000_STS_CTRL_RST_LAST_BIT_LEN (1U) +#define DW3000_STS_CTRL_RST_LAST_BIT_MASK 0x2U +#define DW3000_STS_CTRL_LOAD_IV_BIT_OFFSET (0U) +#define DW3000_STS_CTRL_LOAD_IV_BIT_LEN (1U) +#define DW3000_STS_CTRL_LOAD_IV_BIT_MASK 0x1U + +/* register STS_STS */ +#define DW3000_STS_STS_ID 0x20008 +#define DW3000_STS_STS_LEN (4U) +#define DW3000_STS_STS_MASK 0xFFFFFFFFUL +#define DW3000_STS_STS_ACC_QUAL_BIT_OFFSET (0U) +#define DW3000_STS_STS_ACC_QUAL_BIT_LEN (12U) +#define DW3000_STS_STS_ACC_QUAL_BIT_MASK 0xfffU + +/* register STS_KEY0 */ +#define DW3000_STS_KEY0_ID 0x2000c +#define DW3000_STS_KEY0_LEN (4U) +#define DW3000_STS_KEY0_MASK 0xFFFFFFFFUL + +/* register STS_KEY1 */ +#define DW3000_STS_KEY1_ID 0x20010 +#define DW3000_STS_KEY1_LEN (4U) +#define DW3000_STS_KEY1_MASK 0xFFFFFFFFUL + +/* register STS_KEY2 */ +#define DW3000_STS_KEY2_ID 0x20014 +#define DW3000_STS_KEY2_LEN (4U) +#define DW3000_STS_KEY2_MASK 0xFFFFFFFFUL + +/* register STS_KEY3 */ +#define DW3000_STS_KEY3_ID 0x20018 +#define DW3000_STS_KEY3_LEN (4U) +#define DW3000_STS_KEY3_MASK 0xFFFFFFFFUL + +/* register STS_KEY */ +#define DW3000_STS_KEY_ID DW3000_STS_KEY0_ID +#define DW3000_STS_KEY_LEN (16U) + +/* register STS_IV0 */ +#define DW3000_STS_IV0_ID 0x2001c +#define DW3000_STS_IV0_LEN (4U) +#define DW3000_STS_IV0_MASK 0xFFFFFFFFUL + +/* register STS_IV1 */ +#define DW3000_STS_IV1_ID 0x20020 +#define DW3000_STS_IV1_LEN (4U) +#define DW3000_STS_IV1_MASK 0xFFFFFFFFUL + +/* register STS_IV2 */ +#define DW3000_STS_IV2_ID 0x20024 +#define DW3000_STS_IV2_LEN (4U) +#define DW3000_STS_IV2_MASK 0xFFFFFFFFUL + +/* register STS_IV3 */ +#define DW3000_STS_IV3_ID 0x20028 +#define DW3000_STS_IV3_LEN (4U) +#define DW3000_STS_IV3_MASK 0xFFFFFFFFUL + +/* register STS_IV0-STS_IV3 */ +#define DW3000_STS_IV_ID DW3000_STS_IV0_ID +#define DW3000_STS_IV_LEN (16U) + +/* register MRX_CFG */ +#define DW3000_MRX_CFG_ID 0x30000 +#define DW3000_MRX_CFG_LEN (2U) +#define DW3000_MRX_CFG_MASK 0x1FFFUL + +/* register ADC_THRESH_CFG */ +#define DW3000_ADC_THRESH_CFG_ID 0x30010 +#define DW3000_ADC_THRESH_CFG_LEN (4U) +#define DW3000_ADC_THRESH_CFG_MASK 0xFFFFFFFFUL + +/* register AGC_CFG */ +#define DW3000_AGC_CFG_ID 0x30014 +#define DW3000_AGC_CFG_LEN (4U) +#define DW3000_AGC_CFG_MASK 0xFFFFFFFFUL +#define DW3000_AGC_DIS_MASK 0xFFFFFFFEUL + +/* Register DGC_CFG. */ +#define DW3000_DGC_CFG_ID 0x30018 +#define DW3000_DGC_CFG_LEN (4U) +#define DW3000_DGC_CFG_MASK 0xffffffffUL +#define DW3000_DGC_CFG_THR_64_BIT_OFFSET (9U) +#define DW3000_DGC_CFG_THR_64_BIT_LEN (6U) +#define DW3000_DGC_CFG_THR_64_BIT_MASK 0x7e00U +#define DW3000_DGC_CFG_RX_TUNE_EN_BIT_OFFSET (0U) +#define DW3000_DGC_CFG_RX_TUNE_EN_BIT_LEN (1U) +#define DW3000_DGC_CFG_RX_TUNE_EN_BIT_MASK 0x1U + +/* register DGC_CFG0 */ +#define DW3000_DGC_CFG0_ID 0x3001c + +/* register DGC_CFG1 */ +#define DW3000_DGC_CFG1_ID 0x30020 + +/* register DGC_CFG2 */ +#define DW3000_DGC_CFG2_ID 0x30024 + +/* register ADC_THRESH_DBG */ +#define DW3000_ADC_THRESH_DBG_ID 0x3004C +#define DW3000_ADC_THRESH_DBG_LEN (4U) +#define DW3000_ADC_THRESH_DBG_MASK 0xFFFFFFFFUL + +/* register PGF_CAL_CFG */ +#define DW3000_PGF_CAL_CFG_ID 0x4000c +#define DW3000_PGF_CAL_CFG_LEN (4U) +#define DW3000_PGF_CAL_CFG_MASK 0xFFFFFFFFUL +#define DW3000_PGF_CAL_CFG_COMP_DLY_BIT_OFFSET (16U) +#define DW3000_PGF_CAL_CFG_COMP_DLY_BIT_LEN (4U) +#define DW3000_PGF_CAL_CFG_COMP_DLY_BIT_MASK 0xf0000UL +#define DW3000_PGF_CAL_CFG_PGF_GAIN_BIT_OFFSET (8U) +#define DW3000_PGF_CAL_CFG_PGF_GAIN_BIT_LEN (5U) +#define DW3000_PGF_CAL_CFG_PGF_GAIN_BIT_MASK 0x1f00U +#define DW3000_PGF_CAL_CFG_CAL_EN_BIT_OFFSET (4U) +#define DW3000_PGF_CAL_CFG_CAL_EN_BIT_LEN (1U) +#define DW3000_PGF_CAL_CFG_CAL_EN_BIT_MASK 0x10U +#define DW3000_PGF_CAL_CFG_PGF_MODE_BIT_OFFSET (0U) +#define DW3000_PGF_CAL_CFG_PGF_MODE_BIT_LEN (2U) +#define DW3000_PGF_CAL_CFG_PGF_MODE_BIT_MASK 0x3U + +/* register RX_CAL_CFG (PGF_CAL_CFG renamed in E0) */ +#define DW3000_RX_CAL_CFG_ID DW3000_PGF_CAL_CFG_ID +#define DW3000_RX_CAL_CFG_LEN (4U) +#define DW3000_RX_CAL_CFG_MASK 0xFFFFFFFFUL +#define DW3000_RX_CAL_CFG_COMP_DLY_BIT_OFFSET (16U) +#define DW3000_RX_CAL_CFG_COMP_DLY_BIT_LEN (4U) +#define DW3000_RX_CAL_CFG_COMP_DLY_BIT_MASK 0xf0000UL +#define DW3000_RX_CAL_CFG_CAL_EN_BIT_OFFSET (4U) +#define DW3000_RX_CAL_CFG_CAL_EN_BIT_LEN (1U) +#define DW3000_RX_CAL_CFG_CAL_EN_BIT_MASK 0x10U +#define DW3000_RX_CAL_CFG_CAL_MODE_BIT_OFFSET (0U) +#define DW3000_RX_CAL_CFG_CAL_MODE_BIT_LEN (2U) +#define DW3000_RX_CAL_CFG_CAL_MODE_BIT_MASK 0x3U + +/* register PGF_I_CTRL0 */ +#define DW3000_PGF_I_CTRL0_ID 0x40010 + +/* register PGF_I_CTRL1 */ +#define DW3000_PGF_I_CTRL1_ID 0x40014 + +/* register RX_CAL_RESI (PGF_I_CTRL1 renamed in E0) */ +#define DW3000_RX_CAL_RESI_ID 0x40014 +#define DW3000_RX_CAL_RESI_LEN (4U) +#define DW3000_RX_CAL_RESI_MASK 0xFFFFFFFFUL + +/* register PGF_Q_CTRL1 */ +#define DW3000_PGF_Q_CTRL1_ID 0x4001c + +/* register RX_CAL_RESQ (PGF_Q_CTRL1_ID renamed in E0) */ +#define DW3000_RX_CAL_RESQ_ID DW3000_PGF_Q_CTRL1_ID +#define DW3000_RX_CAL_RESQ_LEN (4U) +#define DW3000_RX_CAL_RESQ_MASK 0xFFFFFFFFUL + +/* register PGF_CAL_STS */ +#define DW3000_PGF_CAL_STS_ID 0x40020 +#define DW3000_PGF_CAL_STS_LEN (4U) +#define DW3000_PGF_CAL_STS_MASK 0xFFFFFFFFUL +#define DW3000_PGF_CAL_STS_CAL_DONE_BIT_OFFSET (0U) +#define DW3000_PGF_CAL_STS_CAL_DONE_BIT_LEN (1U) +#define DW3000_PGF_CAL_STS_CAL_DONE_BIT_MASK 0x1U + +/* register RX_CAL_STS (PGF_CAL_STS renamed in E0) */ +#define RX_CAL_STS_ID DW3000_PGF_CAL_STS_ID +#define RX_CAL_STS_LEN (4U) +#define RX_CAL_STS_MASK 0xFFFFFFFFUL + +/* register GPIO_MODE */ +#define DW3000_GPIO_MODE_ID 0x50000 +#define DW3000_GPIO_MODE_LEN (4U) +#define DW3000_GPIO_MODE_MASK 0xFFFFFFFFUL +#define DW3000_GPIO_MODE_COEX_IO_SWAP_BIT_OFFSET (27U) +#define DW3000_GPIO_MODE_COEX_IO_SWAP_BIT_LEN (1U) +#define DW3000_GPIO_MODE_COEX_IO_SWAP_BIT_MASK 0x8000000UL +#define DW3000_GPIO_MODE_MSGP8_MODE_BIT_OFFSET (24U) +#define DW3000_GPIO_MODE_MSGP8_MODE_BIT_LEN (3U) +#define DW3000_GPIO_MODE_MSGP8_MODE_BIT_MASK 0x7000000UL +#define DW3000_GPIO_MODE_MSGP7_MODE_BIT_OFFSET (21U) +#define DW3000_GPIO_MODE_MSGP7_MODE_BIT_LEN (3U) +#define DW3000_GPIO_MODE_MSGP7_MODE_BIT_MASK 0xe00000UL +#define DW3000_GPIO_MODE_MSGP6_MODE_BIT_OFFSET (18U) +#define DW3000_GPIO_MODE_MSGP6_MODE_BIT_LEN (3U) +#define DW3000_GPIO_MODE_MSGP6_MODE_BIT_MASK 0x1c0000UL +#define DW3000_GPIO_MODE_MSGP5_MODE_BIT_OFFSET (15U) +#define DW3000_GPIO_MODE_MSGP5_MODE_BIT_LEN (3U) +#define DW3000_GPIO_MODE_MSGP5_MODE_BIT_MASK 0x38000UL +#define DW3000_GPIO_MODE_MSGP4_MODE_BIT_OFFSET (12U) +#define DW3000_GPIO_MODE_MSGP4_MODE_BIT_LEN (3U) +#define DW3000_GPIO_MODE_MSGP4_MODE_BIT_MASK 0x7000U +#define DW3000_GPIO_MODE_MSGP3_MODE_BIT_OFFSET (9U) +#define DW3000_GPIO_MODE_MSGP3_MODE_BIT_LEN (3U) +#define DW3000_GPIO_MODE_MSGP3_MODE_BIT_MASK 0xe00U +#define DW3000_GPIO_MODE_MSGP2_MODE_BIT_OFFSET (6U) +#define DW3000_GPIO_MODE_MSGP2_MODE_BIT_LEN (3U) +#define DW3000_GPIO_MODE_MSGP2_MODE_BIT_MASK 0x1c0U +#define DW3000_GPIO_MODE_MSGP1_MODE_BIT_OFFSET (3U) +#define DW3000_GPIO_MODE_MSGP1_MODE_BIT_LEN (3U) +#define DW3000_GPIO_MODE_MSGP1_MODE_BIT_MASK 0x38U +#define DW3000_GPIO_MODE_MSGP0_MODE_BIT_OFFSET (0U) +#define DW3000_GPIO_MODE_MSGP0_MODE_BIT_LEN (3U) +#define DW3000_GPIO_MODE_MSGP0_MODE_BIT_MASK 0x7U + +/* register GPIO_DIR */ +#define DW3000_GPIO_DIR_ID 0x50008 +#define DW3000_GPIO_DIR_LEN (4U) +#define DW3000_GPIO_DIR_MASK 0xFFFFFFFFUL +#define DW3000_GPIO_DIR_GDP8_BIT_OFFSET (8U) +#define DW3000_GPIO_DIR_GDP8_BIT_LEN (1U) +#define DW3000_GPIO_DIR_GDP8_BIT_MASK 0x100U +#define DW3000_GPIO_DIR_GDP7_BIT_OFFSET (7U) +#define DW3000_GPIO_DIR_GDP7_BIT_LEN (1U) +#define DW3000_GPIO_DIR_GDP7_BIT_MASK 0x80U +#define DW3000_GPIO_DIR_GDP6_BIT_OFFSET (6U) +#define DW3000_GPIO_DIR_GDP6_BIT_LEN (1U) +#define DW3000_GPIO_DIR_GDP6_BIT_MASK 0x40U +#define DW3000_GPIO_DIR_GDP5_BIT_OFFSET (5U) +#define DW3000_GPIO_DIR_GDP5_BIT_LEN (1U) +#define DW3000_GPIO_DIR_GDP5_BIT_MASK 0x20U +#define DW3000_GPIO_DIR_GDP4_BIT_OFFSET (4U) +#define DW3000_GPIO_DIR_GDP4_BIT_LEN (1U) +#define DW3000_GPIO_DIR_GDP4_BIT_MASK 0x10U +#define DW3000_GPIO_DIR_GDP3_BIT_OFFSET (3U) +#define DW3000_GPIO_DIR_GDP3_BIT_LEN (1U) +#define DW3000_GPIO_DIR_GDP3_BIT_MASK 0x8U +#define DW3000_GPIO_DIR_GDP2_BIT_OFFSET (2U) +#define DW3000_GPIO_DIR_GDP2_BIT_LEN (1U) +#define DW3000_GPIO_DIR_GDP2_BIT_MASK 0x4U +#define DW3000_GPIO_DIR_GDP1_BIT_OFFSET (1U) +#define DW3000_GPIO_DIR_GDP1_BIT_LEN (1U) +#define DW3000_GPIO_DIR_GDP1_BIT_MASK 0x2U +#define DW3000_GPIO_DIR_GDP0_BIT_OFFSET (0U) +#define DW3000_GPIO_DIR_GDP0_BIT_LEN (1U) +#define DW3000_GPIO_DIR_GDP0_BIT_MASK 0x1U + +/* register GPIO_OUT */ +#define DW3000_GPIO_OUT_ID 0x5000c +#define DW3000_GPIO_OUT_LEN (4U) +#define DW3000_GPIO_OUT_MASK 0xFFFFFFFFUL +#define DW3000_GPIO_OUT_GOP8_BIT_OFFSET (8U) +#define DW3000_GPIO_OUT_GOP8_BIT_LEN (1U) +#define DW3000_GPIO_OUT_GOP8_BIT_MASK 0x100U +#define DW3000_GPIO_OUT_GOP7_BIT_OFFSET (7U) +#define DW3000_GPIO_OUT_GOP7_BIT_LEN (1U) +#define DW3000_GPIO_OUT_GOP7_BIT_MASK 0x80U +#define DW3000_GPIO_OUT_GOP6_BIT_OFFSET (6U) +#define DW3000_GPIO_OUT_GOP6_BIT_LEN (1U) +#define DW3000_GPIO_OUT_GOP6_BIT_MASK 0x40U +#define DW3000_GPIO_OUT_GOP5_BIT_OFFSET (5U) +#define DW3000_GPIO_OUT_GOP5_BIT_LEN (1U) +#define DW3000_GPIO_OUT_GOP5_BIT_MASK 0x20U +#define DW3000_GPIO_OUT_GOP4_BIT_OFFSET (4U) +#define DW3000_GPIO_OUT_GOP4_BIT_LEN (1U) +#define DW3000_GPIO_OUT_GOP4_BIT_MASK 0x10U +#define DW3000_GPIO_OUT_GOP3_BIT_OFFSET (3U) +#define DW3000_GPIO_OUT_GOP3_BIT_LEN (1U) +#define DW3000_GPIO_OUT_GOP3_BIT_MASK 0x8U +#define DW3000_GPIO_OUT_GOP2_BIT_OFFSET (2U) +#define DW3000_GPIO_OUT_GOP2_BIT_LEN (1U) +#define DW3000_GPIO_OUT_GOP2_BIT_MASK 0x4U +#define DW3000_GPIO_OUT_GOP1_BIT_OFFSET (1U) +#define DW3000_GPIO_OUT_GOP1_BIT_LEN (1U) +#define DW3000_GPIO_OUT_GOP1_BIT_MASK 0x2U +#define DW3000_GPIO_OUT_GOP0_BIT_OFFSET (0U) +#define DW3000_GPIO_OUT_GOP0_BIT_LEN (1U) +#define DW3000_GPIO_OUT_GOP0_BIT_MASK 0x1U + +/* register GPIO_IRQE - GPIO IRQ enable */ +#define DW3000_GPIO_IRQE_ID 0x50010 +#define DW3000_GPIO_IRQE_LEN (4U) +#define DW3000_GPIO_IRQE_MASK 0xFFFFFFFFUL +#define DW3000_GPIO_IRQE_GIRQE8_BIT_OFFSET (8U) +#define DW3000_GPIO_IRQE_GIRQE8_BIT_LEN (1U) +#define DW3000_GPIO_IRQE_GIRQE8_BIT_MASK 0x100U +#define DW3000_GPIO_IRQE_GIRQE7_BIT_OFFSET (7U) +#define DW3000_GPIO_IRQE_GIRQE7_BIT_LEN (1U) +#define DW3000_GPIO_IRQE_GIRQE7_BIT_MASK 0x80U +#define DW3000_GPIO_IRQE_GIRQE6_BIT_OFFSET (6U) +#define DW3000_GPIO_IRQE_GIRQE6_BIT_LEN (1U) +#define DW3000_GPIO_IRQE_GIRQE6_BIT_MASK 0x40U +#define DW3000_GPIO_IRQE_GIRQE5_BIT_OFFSET (5U) +#define DW3000_GPIO_IRQE_GIRQE5_BIT_LEN (1U) +#define DW3000_GPIO_IRQE_GIRQE5_BIT_MASK 0x20U +#define DW3000_GPIO_IRQE_GIRQE4_BIT_OFFSET (4U) +#define DW3000_GPIO_IRQE_GIRQE4_BIT_LEN (1U) +#define DW3000_GPIO_IRQE_GIRQE4_BIT_MASK 0x10U +#define DW3000_GPIO_IRQE_GIRQE3_BIT_OFFSET (3U) +#define DW3000_GPIO_IRQE_GIRQE3_BIT_LEN (1U) +#define DW3000_GPIO_IRQE_GIRQE3_BIT_MASK 0x8U +#define DW3000_GPIO_IRQE_GIRQE2_BIT_OFFSET (2U) +#define DW3000_GPIO_IRQE_GIRQE2_BIT_LEN (1U) +#define DW3000_GPIO_IRQE_GIRQE2_BIT_MASK 0x4U +#define DW3000_GPIO_IRQE_GIRQE1_BIT_OFFSET (1U) +#define DW3000_GPIO_IRQE_GIRQE1_BIT_LEN (1U) +#define DW3000_GPIO_IRQE_GIRQE1_BIT_MASK 0x2U +#define DW3000_GPIO_IRQE_GIRQE0_BIT_OFFSET (0U) +#define DW3000_GPIO_IRQE_GIRQE0_BIT_LEN (1U) +#define DW3000_GPIO_IRQE_GIRQE0_BIT_MASK 0x1U + +/* register GPIO_ISTS - GPIO IRQ status */ +#define DW3000_GPIO_ISTS_ID 0x50014 +#define DW3000_GPIO_ISTS_LEN (4U) +#define DW3000_GPIO_ISTS_MASK 0xFFFFFFFFUL +#define DW3000_GPIO_ISTS_GISTS8_BIT_OFFSET (8U) +#define DW3000_GPIO_ISTS_GISTS8_BIT_LEN (1U) +#define DW3000_GPIO_ISTS_GISTS8_BIT_MASK 0x100U +#define DW3000_GPIO_ISTS_GISTS7_BIT_OFFSET (7U) +#define DW3000_GPIO_ISTS_GISTS7_BIT_LEN (1U) +#define DW3000_GPIO_ISTS_GISTS7_BIT_MASK 0x80U +#define DW3000_GPIO_ISTS_GISTS6_BIT_OFFSET (6U) +#define DW3000_GPIO_ISTS_GISTS6_BIT_LEN (1U) +#define DW3000_GPIO_ISTS_GISTS6_BIT_MASK 0x40U +#define DW3000_GPIO_ISTS_GISTS5_BIT_OFFSET (5U) +#define DW3000_GPIO_ISTS_GISTS5_BIT_LEN (1U) +#define DW3000_GPIO_ISTS_GISTS5_BIT_MASK 0x20U +#define DW3000_GPIO_ISTS_GISTS4_BIT_OFFSET (4U) +#define DW3000_GPIO_ISTS_GISTS4_BIT_LEN (1U) +#define DW3000_GPIO_ISTS_GISTS4_BIT_MASK 0x10U +#define DW3000_GPIO_ISTS_GISTS3_BIT_OFFSET (3U) +#define DW3000_GPIO_ISTS_GISTS3_BIT_LEN (1U) +#define DW3000_GPIO_ISTS_GISTS3_BIT_MASK 0x8U +#define DW3000_GPIO_ISTS_GISTS2_BIT_OFFSET (2U) +#define DW3000_GPIO_ISTS_GISTS2_BIT_LEN (1U) +#define DW3000_GPIO_ISTS_GISTS2_BIT_MASK 0x4U +#define DW3000_GPIO_ISTS_GISTS1_BIT_OFFSET (1U) +#define DW3000_GPIO_ISTS_GISTS1_BIT_LEN (1U) +#define DW3000_GPIO_ISTS_GISTS1_BIT_MASK 0x2U +#define DW3000_GPIO_ISTS_GISTS0_BIT_OFFSET (0U) +#define DW3000_GPIO_ISTS_GISTS0_BIT_LEN (1U) +#define DW3000_GPIO_ISTS_GISTS0_BIT_MASK 0x1U + +/* register GPIO_ISEN */ +#define DW3000_GPIO_ISEN_ID 0x50018 +#define DW3000_GPIO_ISEN_LEN (4U) +#define DW3000_GPIO_ISEN_MASK 0xFFFFFFFFUL +#define DW3000_GPIO_ISEN_GISEN8_BIT_OFFSET (8U) +#define DW3000_GPIO_ISEN_GISEN8_BIT_LEN (1U) +#define DW3000_GPIO_ISEN_GISEN8_BIT_MASK 0x100U +#define DW3000_GPIO_ISEN_GISEN7_BIT_OFFSET (7U) +#define DW3000_GPIO_ISEN_GISEN7_BIT_LEN (1U) +#define DW3000_GPIO_ISEN_GISEN7_BIT_MASK 0x80U +#define DW3000_GPIO_ISEN_GISEN6_BIT_OFFSET (6U) +#define DW3000_GPIO_ISEN_GISEN6_BIT_LEN (1U) +#define DW3000_GPIO_ISEN_GISEN6_BIT_MASK 0x40U +#define DW3000_GPIO_ISEN_GISEN5_BIT_OFFSET (5U) +#define DW3000_GPIO_ISEN_GISEN5_BIT_LEN (1U) +#define DW3000_GPIO_ISEN_GISEN5_BIT_MASK 0x20U +#define DW3000_GPIO_ISEN_GISEN4_BIT_OFFSET (4U) +#define DW3000_GPIO_ISEN_GISEN4_BIT_LEN (1U) +#define DW3000_GPIO_ISEN_GISEN4_BIT_MASK 0x10U +#define DW3000_GPIO_ISEN_GISEN3_BIT_OFFSET (3U) +#define DW3000_GPIO_ISEN_GISEN3_BIT_LEN (1U) +#define DW3000_GPIO_ISEN_GISEN3_BIT_MASK 0x8U +#define DW3000_GPIO_ISEN_GISEN2_BIT_OFFSET (2U) +#define DW3000_GPIO_ISEN_GISEN2_BIT_LEN (1U) +#define DW3000_GPIO_ISEN_GISEN2_BIT_MASK 0x4U +#define DW3000_GPIO_ISEN_GISEN1_BIT_OFFSET (1U) +#define DW3000_GPIO_ISEN_GISEN1_BIT_LEN (1U) +#define DW3000_GPIO_ISEN_GISEN1_BIT_MASK 0x2U +#define DW3000_GPIO_ISEN_GISEN0_BIT_OFFSET (0U) +#define DW3000_GPIO_ISEN_GISEN0_BIT_LEN (1U) +#define DW3000_GPIO_ISEN_GISEN0_BIT_MASK 0x1U + +/* register GPIO_IMODE */ +#define DW3000_GPIO_IMODE_ID 0x5001c +#define DW3000_GPIO_IMODE_LEN (4U) +#define DW3000_GPIO_IMODE_MASK 0xFFFFFFFFUL +#define DW3000_GPIO_IMODE_GIMOD8_BIT_OFFSET (8U) +#define DW3000_GPIO_IMODE_GIMOD8_BIT_LEN (1U) +#define DW3000_GPIO_IMODE_GIMOD8_BIT_MASK 0x100U +#define DW3000_GPIO_IMODE_GIMOD7_BIT_OFFSET (7U) +#define DW3000_GPIO_IMODE_GIMOD7_BIT_LEN (1U) +#define DW3000_GPIO_IMODE_GIMOD7_BIT_MASK 0x80U +#define DW3000_GPIO_IMODE_GIMOD6_BIT_OFFSET (6U) +#define DW3000_GPIO_IMODE_GIMOD6_BIT_LEN (1U) +#define DW3000_GPIO_IMODE_GIMOD6_BIT_MASK 0x40U +#define DW3000_GPIO_IMODE_GIMOD5_BIT_OFFSET (5U) +#define DW3000_GPIO_IMODE_GIMOD5_BIT_LEN (1U) +#define DW3000_GPIO_IMODE_GIMOD5_BIT_MASK 0x20U +#define DW3000_GPIO_IMODE_GIMOD4_BIT_OFFSET (4U) +#define DW3000_GPIO_IMODE_GIMOD4_BIT_LEN (1U) +#define DW3000_GPIO_IMODE_GIMOD4_BIT_MASK 0x10U +#define DW3000_GPIO_IMODE_GIMOD3_BIT_OFFSET (3U) +#define DW3000_GPIO_IMODE_GIMOD3_BIT_LEN (1U) +#define DW3000_GPIO_IMODE_GIMOD3_BIT_MASK 0x8U +#define DW3000_GPIO_IMODE_GIMOD2_BIT_OFFSET (2U) +#define DW3000_GPIO_IMODE_GIMOD2_BIT_LEN (1U) +#define DW3000_GPIO_IMODE_GIMOD2_BIT_MASK 0x4U +#define DW3000_GPIO_IMODE_GIMOD1_BIT_OFFSET (1U) +#define DW3000_GPIO_IMODE_GIMOD1_BIT_LEN (1U) +#define DW3000_GPIO_IMODE_GIMOD1_BIT_MASK 0x2U +#define DW3000_GPIO_IMODE_GIMOD0_BIT_OFFSET (0U) +#define DW3000_GPIO_IMODE_GIMOD0_BIT_LEN (1U) +#define DW3000_GPIO_IMODE_GIMOD0_BIT_MASK 0x1U + +/* register GPIO_IBES */ +#define DW3000_GPIO_IBES_ID 0x50020 +#define DW3000_GPIO_IBES_LEN (4U) +#define DW3000_GPIO_IBES_MASK 0xFFFFFFFFUL +#define DW3000_GPIO_IBES_GIBES8_BIT_OFFSET (8U) +#define DW3000_GPIO_IBES_GIBES8_BIT_LEN (1U) +#define DW3000_GPIO_IBES_GIBES8_BIT_MASK 0x100U +#define DW3000_GPIO_IBES_GIBES7_BIT_OFFSET (7U) +#define DW3000_GPIO_IBES_GIBES7_BIT_LEN (1U) +#define DW3000_GPIO_IBES_GIBES7_BIT_MASK 0x80U +#define DW3000_GPIO_IBES_GIBES6_BIT_OFFSET (6U) +#define DW3000_GPIO_IBES_GIBES6_BIT_LEN (1U) +#define DW3000_GPIO_IBES_GIBES6_BIT_MASK 0x40U +#define DW3000_GPIO_IBES_GIBES5_BIT_OFFSET (5U) +#define DW3000_GPIO_IBES_GIBES5_BIT_LEN (1U) +#define DW3000_GPIO_IBES_GIBES5_BIT_MASK 0x20U +#define DW3000_GPIO_IBES_GIBES4_BIT_OFFSET (4U) +#define DW3000_GPIO_IBES_GIBES4_BIT_LEN (1U) +#define DW3000_GPIO_IBES_GIBES4_BIT_MASK 0x10U +#define DW3000_GPIO_IBES_GIBES3_BIT_OFFSET (3U) +#define DW3000_GPIO_IBES_GIBES3_BIT_LEN (1U) +#define DW3000_GPIO_IBES_GIBES3_BIT_MASK 0x8U +#define DW3000_GPIO_IBES_GIBES2_BIT_OFFSET (2U) +#define DW3000_GPIO_IBES_GIBES2_BIT_LEN (1U) +#define DW3000_GPIO_IBES_GIBES2_BIT_MASK 0x4U +#define DW3000_GPIO_IBES_GIBES1_BIT_OFFSET (1U) +#define DW3000_GPIO_IBES_GIBES1_BIT_LEN (1U) +#define DW3000_GPIO_IBES_GIBES1_BIT_MASK 0x2U +#define DW3000_GPIO_IBES_GIBES0_BIT_OFFSET (0U) +#define DW3000_GPIO_IBES_GIBES0_BIT_LEN (1U) +#define DW3000_GPIO_IBES_GIBES0_BIT_MASK 0x1U + +/* register GPIO_ICLR - GPIO IRQ clear */ +#define DW3000_GPIO_ICLR_ID 0x50024 +#define DW3000_GPIO_ICLR_LEN (4U) +#define DW3000_GPIO_ICLR_MASK 0xFFFFFFFFUL +#define DW3000_GPIO_ICLR_GICLR8_BIT_OFFSET (8U) +#define DW3000_GPIO_ICLR_GICLR8_BIT_LEN (1U) +#define DW3000_GPIO_ICLR_GICLR8_BIT_MASK 0x100U +#define DW3000_GPIO_ICLR_GICLR7_BIT_OFFSET (7U) +#define DW3000_GPIO_ICLR_GICLR7_BIT_LEN (1U) +#define DW3000_GPIO_ICLR_GICLR7_BIT_MASK 0x80U +#define DW3000_GPIO_ICLR_GICLR6_BIT_OFFSET (6U) +#define DW3000_GPIO_ICLR_GICLR6_BIT_LEN (1U) +#define DW3000_GPIO_ICLR_GICLR6_BIT_MASK 0x40U +#define DW3000_GPIO_ICLR_GICLR5_BIT_OFFSET (5U) +#define DW3000_GPIO_ICLR_GICLR5_BIT_LEN (1U) +#define DW3000_GPIO_ICLR_GICLR5_BIT_MASK 0x20U +#define DW3000_GPIO_ICLR_GICLR4_BIT_OFFSET (4U) +#define DW3000_GPIO_ICLR_GICLR4_BIT_LEN (1U) +#define DW3000_GPIO_ICLR_GICLR4_BIT_MASK 0x10U +#define DW3000_GPIO_ICLR_GICLR3_BIT_OFFSET (3U) +#define DW3000_GPIO_ICLR_GICLR3_BIT_LEN (1U) +#define DW3000_GPIO_ICLR_GICLR3_BIT_MASK 0x8U +#define DW3000_GPIO_ICLR_GICLR2_BIT_OFFSET (2U) +#define DW3000_GPIO_ICLR_GICLR2_BIT_LEN (1U) +#define DW3000_GPIO_ICLR_GICLR2_BIT_MASK 0x4U +#define DW3000_GPIO_ICLR_GICLR1_BIT_OFFSET (1U) +#define DW3000_GPIO_ICLR_GICLR1_BIT_LEN (1U) +#define DW3000_GPIO_ICLR_GICLR1_BIT_MASK 0x2U +#define DW3000_GPIO_ICLR_GICLR0_BIT_OFFSET (0U) +#define DW3000_GPIO_ICLR_GICLR0_BIT_LEN (1U) +#define DW3000_GPIO_ICLR_GICLR0_BIT_MASK 0x1U + +/* register GPIO_IDBE */ +#define DW3000_GPIO_IDBE_ID 0x50028 +#define DW3000_GPIO_IDBE_LEN (4U) +#define DW3000_GPIO_IDBE_MASK 0xFFFFFFFFUL +#define DW3000_GPIO_IDBE_GIDBE8_BIT_OFFSET (8U) +#define DW3000_GPIO_IDBE_GIDBE8_BIT_LEN (1U) +#define DW3000_GPIO_IDBE_GIDBE8_BIT_MASK 0x100U +#define DW3000_GPIO_IDBE_GIDBE7_BIT_OFFSET (7U) +#define DW3000_GPIO_IDBE_GIDBE7_BIT_LEN (1U) +#define DW3000_GPIO_IDBE_GIDBE7_BIT_MASK 0x80U +#define DW3000_GPIO_IDBE_GIDBE6_BIT_OFFSET (6U) +#define DW3000_GPIO_IDBE_GIDBE6_BIT_LEN (1U) +#define DW3000_GPIO_IDBE_GIDBE6_BIT_MASK 0x40U +#define DW3000_GPIO_IDBE_GIDBE5_BIT_OFFSET (5U) +#define DW3000_GPIO_IDBE_GIDBE5_BIT_LEN (1U) +#define DW3000_GPIO_IDBE_GIDBE5_BIT_MASK 0x20U +#define DW3000_GPIO_IDBE_GIDBE4_BIT_OFFSET (4U) +#define DW3000_GPIO_IDBE_GIDBE4_BIT_LEN (1U) +#define DW3000_GPIO_IDBE_GIDBE4_BIT_MASK 0x10U +#define DW3000_GPIO_IDBE_GIDBE3_BIT_OFFSET (3U) +#define DW3000_GPIO_IDBE_GIDBE3_BIT_LEN (1U) +#define DW3000_GPIO_IDBE_GIDBE3_BIT_MASK 0x8U +#define DW3000_GPIO_IDBE_GIDBE2_BIT_OFFSET (2U) +#define DW3000_GPIO_IDBE_GIDBE2_BIT_LEN (1U) +#define DW3000_GPIO_IDBE_GIDBE2_BIT_MASK 0x4U +#define DW3000_GPIO_IDBE_GIDBE1_BIT_OFFSET (1U) +#define DW3000_GPIO_IDBE_GIDBE1_BIT_LEN (1U) +#define DW3000_GPIO_IDBE_GIDBE1_BIT_MASK 0x2U +#define DW3000_GPIO_IDBE_GIDBE0_BIT_OFFSET (0U) +#define DW3000_GPIO_IDBE_GIDBE0_BIT_LEN (1U) +#define DW3000_GPIO_IDBE_GIDBE0_BIT_MASK 0x1U + +/* register GPIO_RAW */ +#define DW3000_GPIO_RAW_ID 0x5002c +#define DW3000_GPIO_RAW_LEN (4U) +#define DW3000_GPIO_RAW_MASK 0xFFFFFFFFUL +#define DW3000_GPIO_RAW_GRAWP8_BIT_OFFSET (8U) +#define DW3000_GPIO_RAW_GRAWP8_BIT_LEN (1U) +#define DW3000_GPIO_RAW_GRAWP8_BIT_MASK 0x100U +#define DW3000_GPIO_RAW_GRAWP7_BIT_OFFSET (7U) +#define DW3000_GPIO_RAW_GRAWP7_BIT_LEN (1U) +#define DW3000_GPIO_RAW_GRAWP7_BIT_MASK 0x80U +#define DW3000_GPIO_RAW_GRAWP6_BIT_OFFSET (6U) +#define DW3000_GPIO_RAW_GRAWP6_BIT_LEN (1U) +#define DW3000_GPIO_RAW_GRAWP6_BIT_MASK 0x40U +#define DW3000_GPIO_RAW_GRAWP5_BIT_OFFSET (5U) +#define DW3000_GPIO_RAW_GRAWP5_BIT_LEN (1U) +#define DW3000_GPIO_RAW_GRAWP5_BIT_MASK 0x20U +#define DW3000_GPIO_RAW_GRAWP4_BIT_OFFSET (4U) +#define DW3000_GPIO_RAW_GRAWP4_BIT_LEN (1U) +#define DW3000_GPIO_RAW_GRAWP4_BIT_MASK 0x10U +#define DW3000_GPIO_RAW_GRAWP3_BIT_OFFSET (3U) +#define DW3000_GPIO_RAW_GRAWP3_BIT_LEN (1U) +#define DW3000_GPIO_RAW_GRAWP3_BIT_MASK 0x8U +#define DW3000_GPIO_RAW_GRAWP2_BIT_OFFSET (2U) +#define DW3000_GPIO_RAW_GRAWP2_BIT_LEN (1U) +#define DW3000_GPIO_RAW_GRAWP2_BIT_MASK 0x4U +#define DW3000_GPIO_RAW_GRAWP1_BIT_OFFSET (1U) +#define DW3000_GPIO_RAW_GRAWP1_BIT_LEN (1U) +#define DW3000_GPIO_RAW_GRAWP1_BIT_MASK 0x2U +#define DW3000_GPIO_RAW_GRAWP0_BIT_OFFSET (0U) +#define DW3000_GPIO_RAW_GRAWP0_BIT_LEN (1U) +#define DW3000_GPIO_RAW_GRAWP0_BIT_MASK 0x1U + +/* register DRX_TUNE0 */ +#define DW3000_DRX_TUNE0_ID 0x60000 +#define DW3000_DRX_TUNE0_LEN (2U) +#define DW3000_DRX_TUNE0_MASK 0xFFFFUL +#define DW3000_DRX_TUNE0_DT0B4_BIT_OFFSET (4U) +#define DW3000_DRX_TUNE0_DT0B4_BIT_LEN (1U) +#define DW3000_DRX_TUNE0_DT0B4_BIT_MASK 0x10U +#define DW3000_DRX_TUNE0_PRE_PAC_SYM_BIT_OFFSET (0U) +#define DW3000_DRX_TUNE0_PRE_PAC_SYM_BIT_LEN (2U) +#define DW3000_DRX_TUNE0_PRE_PAC_SYM_BIT_MASK 0x3U + +/* register DRX_SFDTOC */ +#define DW3000_DRX_SFDTOC_ID 0x60002 +#define DW3000_DRX_SFDTOC_LEN (2U) +#define DW3000_DRX_SFDTOC_BIT_MASK 0xFFFFUL + +/* register DRX_PRETOC */ +#define DW3000_DRX_PRETOC_ID 0x60004 +#define DW3000_DRX_PRETOC_LEN (2U) +#define DW3000_DRX_PRETOC_BIT_MASK 0xFFFFUL + +/* register DRX_TUNE3 */ +#define DW3000_DRX_TUNE3_ID 0x6000c + +/* register DTUNE4 */ +#define DW3000_DTUNE4_ID 0x60010 +#define DW3000_DTUNE4_LEN (4U) +#define DW3000_DTUNE4_MASK 0xFFFFFFFFUL +#define DW3000_DTUNE4_RX_SFD_HLDOFF_BIT_OFFSET (24U) +#define DW3000_DTUNE4_RX_SFD_HLDOFF_BIT_LEN (8U) +#define DW3000_DTUNE4_RX_SFD_HLDOFF_BIT_MASK 0xff000000UL + +/* register RF_ENABLE */ +#define DW3000_RF_ENABLE_ID 0x70000 +#define DW3000_RF_ENABLE_LEN (4U) +#define DW3000_RF_ENABLE_MASK 0xFFFFFFFFUL +#define DW3000_RF_ENABLE_TX_SW_EN_BIT_OFFSET (25U) +#define DW3000_RF_ENABLE_TX_SW_EN_BIT_LEN (1U) +#define DW3000_RF_ENABLE_TX_SW_EN_BIT_MASK 0x2000000UL +#define DW3000_RF_ENABLE_TX_CH_ALL_EN_BIT_OFFSET (13U) +#define DW3000_RF_ENABLE_TX_CH_ALL_EN_BIT_LEN (1U) +#define DW3000_RF_ENABLE_TX_CH_ALL_EN_BIT_MASK 0x2000U +#define DW3000_RF_ENABLE_TX_EN_BIT_OFFSET (12U) +#define DW3000_RF_ENABLE_TX_EN_BIT_LEN (1U) +#define DW3000_RF_ENABLE_TX_EN_BIT_MASK 0x1000U +#define DW3000_RF_ENABLE_TX_EN_BUF_BIT_OFFSET (11U) +#define DW3000_RF_ENABLE_TX_EN_BUF_BIT_LEN (1U) +#define DW3000_RF_ENABLE_TX_EN_BUF_BIT_MASK 0x800U +#define DW3000_RF_ENABLE_TX_BIAS_EN_BIT_OFFSET (10U) +#define DW3000_RF_ENABLE_TX_BIAS_EN_BIT_LEN (1U) +#define DW3000_RF_ENABLE_TX_BIAS_EN_BIT_MASK 0x400U + +/* register RF_CTRL_MASK */ +#define DW3000_RF_CTRL_MASK_ID 0x70004 +#define DW3000_RF_CTRL_MASK_LEN (4U) +#define DW3000_RF_CTRL_MASK_MASK 0xFFFFFFFFUL + +/* register RX_CTRL_LO */ +#define DW3000_RX_CTRL_LO_ID 0x70008 + +/* register RX_CTRL_HI */ +#define DW3000_RX_CTRL_HI_ID 0x70010 + +/* register RX_SWITCH_CTRL */ +#define DW3000_RF_SWITCH_CTRL_ID 0x70014 +#define DW3000_RF_SWITCH_CTRL_LEN (4U) +#define DW3000_RF_SWITCH_CTRL_MASK 0xFFFFFFFFUL +#define DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_CTRL_BIT_OFFSET (24U) +#define DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_CTRL_BIT_LEN (6U) +#define DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_CTRL_BIT_MASK 0x3F000000 +#define DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_EN_BIT_OFFSET (16U) +#define DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_EN_BIT_LEN (1U) +#define DW3000_RF_SWITCH_CTRL_TXRX_SW_OVR_EN_BIT_MASK 0x10000 +#define DW3000_RF_SWITCH_CTRL_ANTSWCTRL_BIT_OFFSET (12U) +#define DW3000_RF_SWITCH_CTRL_ANTSWCTRL_BIT_LEN (3U) +#define DW3000_RF_SWITCH_CTRL_ANTSWCTRL_BIT_MASK 0x7000U +#define DW3000_RF_SWITCH_CTRL_ANTSWEN_BIT_OFFSET (8U) +#define DW3000_RF_SWITCH_CTRL_ANTSWEN_BIT_LEN (1U) +#define DW3000_RF_SWITCH_CTRL_ANTSWEN_BIT_MASK 0x100U +#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_TXPORT_BIT_OFFSET (6U) +#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_TXPORT_BIT_LEN (1U) +#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_TXPORT_BIT_MASK 0x40U +#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_RXPORT_BIT_OFFSET (5U) +#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_RXPORT_BIT_LEN (1U) +#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_RXPORT_BIT_MASK 0x20U +#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_MODE_OVR_BIT_OFFSET (4U) +#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_MODE_OVR_BIT_LEN (1U) +#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_MODE_OVR_BIT_MASK 0x10U +#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_NOTOGGLE_BIT_MASK 0x1U +#define DW3000_RF_SWITCH_CTRL_ANT_TXRX_NOTOGGLE_LEN (0U) + +/* register TX_CTRL_LO */ +#define DW3000_TX_CTRL_LO_ID 0x70018 +#define DW3000_TX_CTRL_LO_LEN (4U) +#define DW3000_TX_CTRL_LO_MASK 0xFFFFFFFFUL +#define DW3000_TX_CTRL_LO_TX_BLEED_CTRL_BIT_OFFSET (25U) +#define DW3000_TX_CTRL_LO_TX_BLEED_CTRL_BIT_LEN (3U) +#define DW3000_TX_CTRL_LO_TX_BLEED_CTRL_BIT_MASK 0xe000000UL +#define DW3000_TX_CTRL_LO_TX_LOBUF_CTRL_BIT_OFFSET (20U) +#define DW3000_TX_CTRL_LO_TX_LOBUF_CTRL_BIT_LEN (5U) +#define DW3000_TX_CTRL_LO_TX_LOBUF_CTRL_BIT_MASK 0x1f00000UL +#define DW3000_TX_CTRL_LO_TX_VBULK_CTRL_BIT_OFFSET (18U) +#define DW3000_TX_CTRL_LO_TX_VBULK_CTRL_BIT_LEN (2U) +#define DW3000_TX_CTRL_LO_TX_VBULK_CTRL_BIT_MASK 0xc0000UL +#define DW3000_TX_CTRL_LO_TX_VCASC_CTRL_BIT_OFFSET (16U) +#define DW3000_TX_CTRL_LO_TX_VCASC_CTRL_BIT_LEN (2U) +#define DW3000_TX_CTRL_LO_TX_VCASC_CTRL_BIT_MASK 0x30000UL +#define DW3000_TX_CTRL_LO_TX_VCM_CTRL_BIT_OFFSET (8U) +#define DW3000_TX_CTRL_LO_TX_VCM_CTRL_BIT_LEN (8U) +#define DW3000_TX_CTRL_LO_TX_VCM_CTRL_BIT_MASK 0xff00U +#define DW3000_TX_CTRL_LO_TX_DELAY_SEL_BIT_OFFSET (6U) +#define DW3000_TX_CTRL_LO_TX_DELAY_SEL_BIT_LEN (2U) +#define DW3000_TX_CTRL_LO_TX_DELAY_SEL_BIT_MASK 0xc0U +#define DW3000_TX_CTRL_LO_TX_CF_CTRL_BIT_OFFSET (1U) +#define DW3000_TX_CTRL_LO_TX_CF_CTRL_BIT_LEN (5U) +#define DW3000_TX_CTRL_LO_TX_CF_CTRL_BIT_MASK 0x3eU +#define DW3000_TX_CTRL_LO_TX_CF_FORCE_BIT_OFFSET (0U) +#define DW3000_TX_CTRL_LO_TX_CF_FORCE_BIT_LEN (1U) +#define DW3000_TX_CTRL_LO_TX_CF_FORCE_BIT_MASK 0x1U + +/* register TX_CTRL_HI */ +#define DW3000_TX_CTRL_HI_ID 0x7001c +#define DW3000_TX_CTRL_HI_LEN (4U) +#define DW3000_TX_CTRL_HI_MASK 0xFFFFFFFFUL +#define DW3000_TX_CTRL_HI_TX_PULSE_SHAPE_BIT_OFFSET (31U) +#define DW3000_TX_CTRL_HI_TX_PULSE_SHAPE_BIT_LEN (1U) +#define DW3000_TX_CTRL_HI_TX_PULSE_SHAPE_BIT_MASK 0x80000000UL +#define DW3000_TX_CTRL_HI_TX_OFF_SW_STATE_BIT_OFFSET (23U) +#define DW3000_TX_CTRL_HI_TX_OFF_SW_STATE_BIT_LEN (6U) +#define DW3000_TX_CTRL_HI_TX_OFF_SW_STATE_BIT_MASK 0x1f800000UL +#define DW3000_TX_CTRL_HI_TX_OFF_SW_DLY_BIT_OFFSET (21U) +#define DW3000_TX_CTRL_HI_TX_OFF_SW_DLY_BIT_LEN (2U) +#define DW3000_TX_CTRL_HI_TX_OFF_SW_DLY_BIT_MASK 0x600000UL +#define DW3000_TX_CTRL_HI_TX_CTUNE_LO_BIT_OFFSET (16U) +#define DW3000_TX_CTRL_HI_TX_CTUNE_LO_BIT_LEN (4U) +#define DW3000_TX_CTRL_HI_TX_CTUNE_LO_BIT_MASK 0xf0000UL +#define DW3000_TX_CTRL_HI_TX_CTUNE_LOAD_P_BIT_OFFSET (12U) +#define DW3000_TX_CTRL_HI_TX_CTUNE_LOAD_P_BIT_LEN (4U) +#define DW3000_TX_CTRL_HI_TX_CTUNE_LOAD_P_BIT_MASK 0xf000U +#define DW3000_TX_CTRL_HI_TX_CTUNE_LOAD_M_BIT_OFFSET (8U) +#define DW3000_TX_CTRL_HI_TX_CTUNE_LOAD_M_BIT_LEN (4U) +#define DW3000_TX_CTRL_HI_TX_CTUNE_LOAD_M_BIT_MASK 0xf00U +#define DW3000_TX_CTRL_HI_TX_PG_START_NUM_BIT_OFFSET (6U) +#define DW3000_TX_CTRL_HI_TX_PG_START_NUM_BIT_LEN (2U) +#define DW3000_TX_CTRL_HI_TX_PG_START_NUM_BIT_MASK 0xc0U +#define DW3000_TX_CTRL_HI_TX_PG_DELAY_BIT_OFFSET (0U) +#define DW3000_TX_CTRL_HI_TX_PG_DELAY_BIT_LEN (6U) +#define DW3000_TX_CTRL_HI_TX_PG_DELAY_BIT_MASK 0x3fU + +/* register TX_TEST */ +#define DW3000_TX_TEST_ID 0x70028 +#define DW3000_TX_TEST_LEN (4U) +#define DW3000_TX_TEST_MASK 0xFFFFFFFFUL +#define DW3000_TX_TEST_PGTEST_EN_CH4_BIT_OFFSET (27U) +#define DW3000_TX_TEST_PGTEST_EN_CH4_BIT_LEN (1U) +#define DW3000_TX_TEST_PGTEST_EN_CH4_BIT_MASK 0x8000000UL +#define DW3000_TX_TEST_PGTEST_EN_CH3_BIT_OFFSET (26U) +#define DW3000_TX_TEST_PGTEST_EN_CH3_BIT_LEN (1U) +#define DW3000_TX_TEST_PGTEST_EN_CH3_BIT_MASK 0x4000000UL +#define DW3000_TX_TEST_PGTEST_EN_CH2_BIT_OFFSET (25U) +#define DW3000_TX_TEST_PGTEST_EN_CH2_BIT_LEN (1U) +#define DW3000_TX_TEST_PGTEST_EN_CH2_BIT_MASK 0x2000000UL +#define DW3000_TX_TEST_PGTEST_EN_CH1_BIT_OFFSET (24U) +#define DW3000_TX_TEST_PGTEST_EN_CH1_BIT_LEN (1U) +#define DW3000_TX_TEST_PGTEST_EN_CH1_BIT_MASK 0x1000000UL +#define DW3000_TX_TEST_XTAL_ANATEST_EN_BIT_OFFSET (18U) +#define DW3000_TX_TEST_XTAL_ANATEST_EN_BIT_LEN (1U) +#define DW3000_TX_TEST_XTAL_ANATEST_EN_BIT_MASK 0x40000UL +#define DW3000_TX_TEST_XTAL_ANATEST_SEL_BIT_OFFSET (15U) +#define DW3000_TX_TEST_XTAL_ANATEST_SEL_BIT_LEN (3U) +#define DW3000_TX_TEST_XTAL_ANATEST_SEL_BIT_MASK 0x38000UL +#define DW3000_TX_TEST_TX_VCM_CTRL_HI_BIT_OFFSET (13U) +#define DW3000_TX_TEST_TX_VCM_CTRL_HI_BIT_LEN (2U) +#define DW3000_TX_TEST_TX_VCM_CTRL_HI_BIT_MASK 0x6000U +#define DW3000_TX_TEST_TX_VCM_CTRL_LO_BIT_OFFSET (9U) +#define DW3000_TX_TEST_TX_VCM_CTRL_LO_BIT_LEN (4U) +#define DW3000_TX_TEST_TX_VCM_CTRL_LO_BIT_MASK 0x1e00U +#define DW3000_TX_TEST_TX_DC_TEST_BIT_OFFSET (5U) +#define DW3000_TX_TEST_TX_DC_TEST_BIT_LEN (4U) +#define DW3000_TX_TEST_TX_DC_TEST_BIT_MASK 0x1e0U +#define DW3000_TX_TEST_TX_DC_TEST_EN_BIT_OFFSET (4U) +#define DW3000_TX_TEST_TX_DC_TEST_EN_BIT_LEN (1U) +#define DW3000_TX_TEST_TX_DC_TEST_EN_BIT_MASK 0x10U +#define DW3000_TX_TEST_TX_ENTEST_CH1_BIT_OFFSET (3U) +#define DW3000_TX_TEST_TX_ENTEST_CH1_BIT_LEN (1U) +#define DW3000_TX_TEST_TX_ENTEST_CH1_BIT_MASK 0x8U +#define DW3000_TX_TEST_TX_ENTEST_CH2_BIT_OFFSET (2U) +#define DW3000_TX_TEST_TX_ENTEST_CH2_BIT_LEN (1U) +#define DW3000_TX_TEST_TX_ENTEST_CH2_BIT_MASK 0x4U +#define DW3000_TX_TEST_TX_ENTEST_CH3_BIT_OFFSET (1U) +#define DW3000_TX_TEST_TX_ENTEST_CH3_BIT_LEN (1U) +#define DW3000_TX_TEST_TX_ENTEST_CH3_BIT_MASK 0x2U +#define DW3000_TX_TEST_TX_ENTEST_CH4_BIT_OFFSET (0U) +#define DW3000_TX_TEST_TX_ENTEST_CH4_BIT_LEN (1U) +#define DW3000_TX_TEST_TX_ENTEST_CH4_BIT_MASK 0x1U + +/* register LDO_TUNE_HI */ +#define DW3000_LDO_TUNE_HI_ID 0x70044 +#define DW3000_LDO_TUNE_HI_LDO_HVAUX_TUNE_BIT_MASK 0xf000U + +/* register LDO_CTRL */ +#define DW3000_LDO_CTRL_ID 0x70048 +#define DW3000_LDO_CTRL_LEN (4U) +#define DW3000_LDO_CTRL_MASK 0xFFFFFFFFUL +#define DW3000_LDO_CTRL_LDO_VDDHVTX_VREF_BIT_OFFSET (27U) +#define DW3000_LDO_CTRL_LDO_VDDHVTX_VREF_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDHVTX_VREF_BIT_MASK 0x8000000UL +#define DW3000_LDO_CTRL_LDO_VDDRFCH9_VREF_BIT_OFFSET (26U) +#define DW3000_LDO_CTRL_LDO_VDDRFCH9_VREF_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDRFCH9_VREF_BIT_MASK 0x4000000UL +#define DW3000_LDO_CTRL_LDO_VDDRFCH5_VREF_BIT_OFFSET (25U) +#define DW3000_LDO_CTRL_LDO_VDDRFCH5_VREF_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDRFCH5_VREF_BIT_MASK 0x2000000UL +#define DW3000_LDO_CTRL_LDO_VDDIF2_VREF_BIT_OFFSET (24U) +#define DW3000_LDO_CTRL_LDO_VDDIF2_VREF_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDIF2_VREF_BIT_MASK 0x1000000UL +#define DW3000_LDO_CTRL_LDO_VDDIF1_VREF_BIT_OFFSET (23U) +#define DW3000_LDO_CTRL_LDO_VDDIF1_VREF_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDIF1_VREF_BIT_MASK 0x800000UL +#define DW3000_LDO_CTRL_LDO_VDDTX2_VREF_BIT_OFFSET (22U) +#define DW3000_LDO_CTRL_LDO_VDDTX2_VREF_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDTX2_VREF_BIT_MASK 0x400000UL +#define DW3000_LDO_CTRL_LDO_VDDTX1_VREF_BIT_OFFSET (21U) +#define DW3000_LDO_CTRL_LDO_VDDTX1_VREF_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDTX1_VREF_BIT_MASK 0x200000UL +#define DW3000_LDO_CTRL_LDO_VDDPLL_VREF_BIT_OFFSET (20U) +#define DW3000_LDO_CTRL_LDO_VDDPLL_VREF_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDPLL_VREF_BIT_MASK 0x100000UL +#define DW3000_LDO_CTRL_LDO_VDDVCO_VREF_BIT_OFFSET (19U) +#define DW3000_LDO_CTRL_LDO_VDDVCO_VREF_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDVCO_VREF_BIT_MASK 0x80000UL +#define DW3000_LDO_CTRL_LDO_VDDMS3_VREF_BIT_OFFSET (18U) +#define DW3000_LDO_CTRL_LDO_VDDMS3_VREF_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDMS3_VREF_BIT_MASK 0x40000UL +#define DW3000_LDO_CTRL_LDO_VDDMS2_VREF_BIT_OFFSET (17U) +#define DW3000_LDO_CTRL_LDO_VDDMS2_VREF_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDMS2_VREF_BIT_MASK 0x20000UL +#define DW3000_LDO_CTRL_LDO_VDDMS1_VREF_BIT_OFFSET (16U) +#define DW3000_LDO_CTRL_LDO_VDDMS1_VREF_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDMS1_VREF_BIT_MASK 0x10000UL +#define DW3000_LDO_CTRL_LDO_VDDHVTX_EN_BIT_OFFSET (11U) +#define DW3000_LDO_CTRL_LDO_VDDHVTX_EN_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDHVTX_EN_BIT_MASK 0x800U +#define DW3000_LDO_CTRL_LDO_VDDRFCH9_EN_BIT_OFFSET (10U) +#define DW3000_LDO_CTRL_LDO_VDDRFCH9_EN_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDRFCH9_EN_BIT_MASK 0x400U +#define DW3000_LDO_CTRL_LDO_VDDRFCH5_EN_BIT_OFFSET (9U) +#define DW3000_LDO_CTRL_LDO_VDDRFCH5_EN_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDRFCH5_EN_BIT_MASK 0x200U +#define DW3000_LDO_CTRL_LDO_VDDIF2_EN_BIT_OFFSET (8U) +#define DW3000_LDO_CTRL_LDO_VDDIF2_EN_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDIF2_EN_BIT_MASK 0x100U +#define DW3000_LDO_CTRL_LDO_VDDIF1_EN_BIT_OFFSET (7U) +#define DW3000_LDO_CTRL_LDO_VDDIF1_EN_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDIF1_EN_BIT_MASK 0x80U +#define DW3000_LDO_CTRL_LDO_VDDTX2_EN_BIT_OFFSET (6U) +#define DW3000_LDO_CTRL_LDO_VDDTX2_EN_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDTX2_EN_BIT_MASK 0x40U +#define DW3000_LDO_CTRL_LDO_VDDTX1_EN_BIT_OFFSET (5U) +#define DW3000_LDO_CTRL_LDO_VDDTX1_EN_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDTX1_EN_BIT_MASK 0x20U +#define DW3000_LDO_CTRL_LDO_VDDPLL_EN_BIT_OFFSET (4U) +#define DW3000_LDO_CTRL_LDO_VDDPLL_EN_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDPLL_EN_BIT_MASK 0x10U +#define DW3000_LDO_CTRL_LDO_VDDVCO_EN_BIT_OFFSET (3U) +#define DW3000_LDO_CTRL_LDO_VDDVCO_EN_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDVCO_EN_BIT_MASK 0x8U +#define DW3000_LDO_CTRL_LDO_VDDMS3_EN_BIT_OFFSET (2U) +#define DW3000_LDO_CTRL_LDO_VDDMS3_EN_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDMS3_EN_BIT_MASK 0x4U +#define DW3000_LDO_CTRL_LDO_VDDMS2_EN_BIT_OFFSET (1U) +#define DW3000_LDO_CTRL_LDO_VDDMS2_EN_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDMS2_EN_BIT_MASK 0x2U +#define DW3000_LDO_CTRL_LDO_VDDMS1_EN_BIT_OFFSET (0U) +#define DW3000_LDO_CTRL_LDO_VDDMS1_EN_BIT_LEN (1U) +#define DW3000_LDO_CTRL_LDO_VDDMS1_EN_BIT_MASK 0x1U + +/* register LDO_VOUT*/ +#define DW3000_LDO_VOUT_ID 0x7004C + +/* register LDO_RLOAD */ +#define DW3000_LDO_RLOAD_ID 0x70050 + +/* register PG_TEST */ +#define DW3000_PG_TEST_ID 0x80018 +#define DW3000_PG_TEST_LEN (4U) +#define DW3000_PG_TEST_MASK 0xFFFFFFFFUL +#define DW3000_PG_TEST_TX_TEST_CH4_BIT_OFFSET (12U) +#define DW3000_PG_TEST_TX_TEST_CH4_BIT_LEN (4U) +#define DW3000_PG_TEST_TX_TEST_CH4_BIT_MASK 0xf000U +#define DW3000_PG_TEST_TX_TEST_CH3_BIT_OFFSET (8U) +#define DW3000_PG_TEST_TX_TEST_CH3_BIT_LEN (4U) +#define DW3000_PG_TEST_TX_TEST_CH3_BIT_MASK 0xf00U +#define DW3000_PG_TEST_TX_TEST_CH2_BIT_OFFSET (4U) +#define DW3000_PG_TEST_TX_TEST_CH2_BIT_LEN (4U) +#define DW3000_PG_TEST_TX_TEST_CH2_BIT_MASK 0xf0U +#define DW3000_PG_TEST_TX_TEST_CH1_BIT_OFFSET (0U) +#define DW3000_PG_TEST_TX_TEST_CH1_BIT_LEN (4U) +#define DW3000_PG_TEST_TX_TEST_CH1_BIT_MASK 0xfU + +/* register PLL_CAL */ +#define DW3000_PLL_CAL_ID 0x90008 +#define DW3000_PLL_CAL_LEN (4U) +#define DW3000_PLL_CAL_MASK 0xFFFFFFFFUL +#define DW3000_PLL_CAL_PLL_CAL_EN_BIT_OFFSET (8U) +#define DW3000_PLL_CAL_PLL_CAL_EN_BIT_LEN (1U) +#define DW3000_PLL_CAL_PLL_CAL_EN_BIT_MASK 0x100U +#define DW3000_PLL_CAL_PLL_USE_OLD_BIT_OFFSET (1U) +#define DW3000_PLL_CAL_PLL_USE_OLD_BIT_LEN (1U) +#define DW3000_PLL_CAL_PLL_USE_OLD_BIT_MASK 0x2U + +/* register PLL_CFG */ +#define DW3000_PLL_CFG_ID 0x90000 + +/* register PLL_COMMON */ +#define DW3000_PLL_COMMON_ID 0x90010 +#define DW3000_PLL_COMMON_LEN (2U) +#define DW3000_PLL_COMMON_MASK 0x0000FFFFUL + +/* register XTAL */ +#define DW3000_XTAL_ID 0x90014 +#define DW3000_XTAL_TRIM_BIT_OFFSET (0U) +#define DW3000_XTAL_TRIM_BIT_LEN (6U) +#define DW3000_XTAL_TRIM_BIT_MASK 0x3FU + +/* register AON_DIG_CFG */ +#define DW3000_AON_DIG_CFG_ID 0xa0000 +#define DW3000_AON_DIG_CFG_LEN (3U) +#define DW3000_AON_DIG_CFG_MASK 0xFFFFFFUL +#define DW3000_AON_DIG_CFG_ONW_AONDLD_OFFSET (0U) +#define DW3000_AON_DIG_CFG_ONW_AONDLD_LEN (1U) +#define DW3000_AON_DIG_CFG_ONW_AONDLD_MASK 0x1U +#define DW3000_AON_DIG_CFG_ONW_GO2IDLE_OFFSET (8U) +#define DW3000_AON_DIG_CFG_ONW_GO2IDLE_LEN (1U) +#define DW3000_AON_DIG_CFG_ONW_GO2IDLE_MASK 0x100U + +/* register AON_CTRL */ +#define DW3000_AON_CTRL_ID 0xa0004 +#define DW3000_AON_CTRL_LEN (1U) +#define DW3000_AON_CTRL_MASK 0xFFUL +#define DW3000_AON_CTRL_OVERRIDE_EN_BIT_OFFSET (7U) +#define DW3000_AON_CTRL_OVERRIDE_EN_BIT_LEN (1U) +#define DW3000_AON_CTRL_OVERRIDE_EN_BIT_MASK 0x80U +#define DW3000_AON_CTRL_AON_CLK_EDGE_SEL_BIT_OFFSET (6U) +#define DW3000_AON_CTRL_AON_CLK_EDGE_SEL_BIT_LEN (1U) +#define DW3000_AON_CTRL_AON_CLK_EDGE_SEL_BIT_MASK 0x40U +#define DW3000_AON_CTRL_OVR_WR_CFG_EN_BIT_OFFSET (5U) +#define DW3000_AON_CTRL_OVR_WR_CFG_EN_BIT_LEN (1U) +#define DW3000_AON_CTRL_OVR_WR_CFG_EN_BIT_MASK 0x20U +#define DW3000_AON_CTRL_OVR_WRITE_EN_BIT_OFFSET (4U) +#define DW3000_AON_CTRL_OVR_WRITE_EN_BIT_LEN (1U) +#define DW3000_AON_CTRL_OVR_WRITE_EN_BIT_MASK 0x10U +#define DW3000_AON_CTRL_OVR_READ_EN_BIT_OFFSET (3U) +#define DW3000_AON_CTRL_OVR_READ_EN_BIT_LEN (1U) +#define DW3000_AON_CTRL_OVR_READ_EN_BIT_MASK 0x8U +#define DW3000_AON_CTRL_CONFIG_UPLOAD_BIT_OFFSET (2U) +#define DW3000_AON_CTRL_CONFIG_UPLOAD_BIT_LEN (1U) +#define DW3000_AON_CTRL_CONFIG_UPLOAD_BIT_MASK 0x4U +#define DW3000_AON_CTRL_ARRAY_UPLOAD_BIT_OFFSET (1U) +#define DW3000_AON_CTRL_ARRAY_UPLOAD_BIT_LEN (1U) +#define DW3000_AON_CTRL_ARRAY_UPLOAD_BIT_MASK 0x2U +#define DW3000_AON_CTRL_ARRAY_DOWNLOAD_BIT_OFFSET (0U) +#define DW3000_AON_CTRL_ARRAY_DOWNLOAD_BIT_LEN (1U) +#define DW3000_AON_CTRL_ARRAY_DOWNLOAD_BIT_MASK 0x1U + +/* register AON_CFG: AON configuration register */ +#define DW3000_AON_CFG_ID 0xa0014 +#define DW3000_AON_CFG_LEN (1U) +#define DW3000_AON_CFG_MASK 0xFFUL +#define DW3000_AON_WAKE_CSN_OFFSET (3U) +#define DW3000_AON_WAKE_CSN_LEN (1U) +#define DW3000_AON_WAKE_CSN_MASK 0x8U +#define DW3000_AON_SLEEP_EN_OFFSET (0U) +#define DW3000_AON_SLEEP_EN_LEN (1U) +#define DW3000_AON_SLEEP_EN_MASK 0x1U + +/* register NVM_WDATA */ +#define DW3000_NVM_WDATA_ID 0xb0000 + +/* register NVM_ADDR */ +#define DW3000_NVM_ADDR_ID 0xb0004 + +/* register NVM_CFG */ +#define DW3000_NVM_CFG_ID 0xb0008 +#define DW3000_NVM_CFG_LEN (4U) +#define DW3000_NVM_CFG_MASK 0xFFFFFFFFUL +#define DW3000_NVM_CFG_DGC_SEL_BIT_LEN (1U) +#define DW3000_NVM_CFG_GEAR_ID_BIT_LEN (2U) +#define DW3000_NVM_CFG_GEAR_KICK_BIT_LEN (1U) +#define DW3000_NVM_CFG_NVM_PD_BIT_LEN (1U) +#define DW3000_NVM_CFG_LDO_KICK_BIT_LEN (1U) +#define DW3000_NVM_CFG_DGC_KICK_BIT_LEN (1U) +#define DW3000_NVM_CFG_ADDR_INC_BIT_OFFSET (5U) +#define DW3000_NVM_CFG_ADDR_INC_BIT_LEN (1U) +#define DW3000_NVM_CFG_ADDR_INC_BIT_MASK 0x20U +#define DW3000_NVM_CFG_NVM_MODE_SEL_BIT_OFFSET (4U) +#define DW3000_NVM_CFG_NVM_MODE_SEL_BIT_LEN (1U) +#define DW3000_NVM_CFG_NVM_MODE_SEL_BIT_MASK 0x10U +#define DW3000_NVM_CFG_NVM_WRITE_MR_BIT_OFFSET (3U) +#define DW3000_NVM_CFG_NVM_WRITE_MR_BIT_LEN (1U) +#define DW3000_NVM_CFG_NVM_WRITE_MR_BIT_MASK 0x8U +#define DW3000_NVM_CFG_NVM_WRITE_BIT_OFFSET (2U) +#define DW3000_NVM_CFG_NVM_WRITE_BIT_LEN (1U) +#define DW3000_NVM_CFG_NVM_WRITE_BIT_MASK 0x4U +#define DW3000_NVM_CFG_NVM_READ_BIT_OFFSET (1U) +#define DW3000_NVM_CFG_NVM_READ_BIT_LEN (1U) +#define DW3000_NVM_CFG_NVM_READ_BIT_MASK 0x2U +#define DW3000_NVM_CFG_NVM_MAN_CTR_EN_BIT_OFFSET (0U) +#define DW3000_NVM_CFG_NVM_MAN_CTR_EN_BIT_LEN (1U) +#define DW3000_NVM_CFG_NVM_MAN_CTR_EN_BIT_MASK 0x1U + +/* register NVM_STATUS */ +#define DW3000_NVM_STATUS_ID 0xb000c +#define DW3000_NVM_STATUS_NVM_PROG_DONE_BIT_MASK 0x1U + +/* register NVM_RDATA */ +#define DW3000_NVM_RDATA_ID 0xb0010 + +/* register STS_TOA_LO */ +#define DW3000_STS_TOA_LO_ID 0xc0008 +#define DW3000_STS_TOA_LO_LEN (4U) +#define DW3000_STS_TOA_LO_MASK 0xFFFFFFFFUL +#define DW3000_STS_TOA_LO_STS_TOA_BIT_OFFSET (0U) +#define DW3000_STS_TOA_LO_STS_TOA_BIT_LEN (32U) +#define DW3000_STS_TOA_LO_STS_TOA_BIT_MASK 0xffffffffUL + +/* register STS_TOA_HI */ +#define DW3000_STS_TOA_HI_ID 0xc000c +#define DW3000_STS_TOA_HI_LEN (4U) +#define DW3000_STS_TOA_HI_MASK 0xFFFFFFFFUL +#define DW3000_STS_TOA_HI_STS_TOAST_BIT_OFFSET (23U) +#define DW3000_STS_TOA_HI_STS_TOAST_BIT_LEN (9U) +#define DW3000_STS_TOA_HI_STS_TOAST_BIT_MASK 0xff800000UL +#define DW3000_STS_TOA_HI_STS_POA_BIT_OFFSET (8U) +#define DW3000_STS_TOA_HI_STS_POA_BIT_LEN (14U) +#define DW3000_STS_TOA_HI_STS_POA_BIT_MASK 0x3fff00UL +#define DW3000_STS_TOA_HI_STS_TOA_BIT_OFFSET (0U) +#define DW3000_STS_TOA_HI_STS_TOA_BIT_LEN (8U) +#define DW3000_STS_TOA_HI_STS_TOA_BIT_MASK 0xffU + +/* register IP_TS_LO */ +#define DW3000_IP_TS_LO_ID 0xc0000 +#define DW3000_IP_TS_LO_LEN (4U) +/* register IP_TS_HI */ +#define DW3000_IP_TS_HI_ID 0xc0004 +#define DW3000_IP_TS_HI_LEN (4U) +/* register STS_TS_LO */ +#define DW3000_STS_TS_LO_ID 0xc0008 +#define DW3000_STS_TS_LO_LEN (4U) +/* register STS_TS_HI */ +#define DW3000_STS_TS_HI_ID 0xc000C +#define DW3000_STS_TS_HI_LEN (4U) +/* register STS1_TS_LO */ +#define DW3000_STS1_TS_LO_ID 0xc0010 +#define DW3000_STS1_TS_LO_LEN (4U) +/* register STS1_TS_HI */ +#define DW3000_STS1_TS_HI_ID 0xc0014 +#define DW3000_STS1_TS_HI_LEN (4U) + +/* register STS1_TOA_LO */ +#define DW3000_STS1_TOA_LO_ID 0xc0010 +#define DW3000_STS1_TOA_LO_LEN (4U) +#define DW3000_STS1_TOA_LO_MASK 0xFFFFFFFFUL +#define DW3000_STS1_TOA_LO_STS1_TOA_BIT_OFFSET (0U) +#define DW3000_STS1_TOA_LO_STS1_TOA_BIT_LEN (32U) +#define DW3000_STS1_TOA_LO_STS1_TOA_BIT_MASK 0xffffffffUL + +/* register STS1_TOA_HI */ +#define DW3000_STS1_TOA_HI_ID 0xc0014 +#define DW3000_STS1_TOA_HI_LEN (4U) +#define DW3000_STS1_TOA_HI_MASK 0xFFFFFFFFUL +#define DW3000_STS1_TOA_HI_STS1_TOAST_BIT_OFFSET (23U) +#define DW3000_STS1_TOA_HI_STS1_TOAST_BIT_LEN (9U) +#define DW3000_STS1_TOA_HI_STS1_TOAST_BIT_MASK 0xff800000UL +#define DW3000_STS1_TOA_HI_STS1_POA_BIT_OFFSET (8U) +#define DW3000_STS1_TOA_HI_STS1_POA_BIT_LEN (14U) +#define DW3000_STS1_TOA_HI_STS1_POA_BIT_MASK 0x3fff00UL +#define DW3000_STS1_TOA_HI_STS1_TOA_BIT_OFFSET (0U) +#define DW3000_STS1_TOA_HI_STS1_TOA_BIT_LEN (8U) +#define DW3000_STS1_TOA_HI_STS1_TOA_BIT_MASK 0xffU + +/* Register CIA_TDOA_1_PDOA */ +#define DW3000_CIA_TDOA_1_PDOA_ID 0xc001c +#define DW3000_CIA_TDOA_1_PDOA_LEN (4U) +#define DW3000_CIA_TDOA_1_PDOA_MASK 0xffffffffUL +#define DW3000_CIA_TDOA_1_PDOA_FP_AGREED_BIT_OFFSET (30U) +#define DW3000_CIA_TDOA_1_PDOA_FP_AGREED_BIT_LEN (1U) +#define DW3000_CIA_TDOA_1_PDOA_FP_AGREED_BIT_MASK 0x40000000UL +#define DW3000_CIA_TDOA_1_PDOA_RX_PDOA_BIT_OFFSET (16U) +#define DW3000_CIA_TDOA_1_PDOA_RX_PDOA_BIT_LEN (14U) +#define DW3000_CIA_TDOA_1_PDOA_RX_PDOA_BIT_MASK 0x3fff0000UL +#define DW3000_CIA_TDOA_1_PDOA_RX_TDOA_BIT_OFFSET (0U) +#define DW3000_CIA_TDOA_1_PDOA_RX_TDOA_BIT_LEN (9U) +#define DW3000_CIA_TDOA_1_PDOA_RX_TDOA_BIT_MASK 0x1ffU + +/* register CIA_DIAG0 */ +#define DW3000_CIA_DIAG0_ID 0xc0020 +#define DW3000_CIA_DIAG0_LEN (4U) +#define DW3000_CIA_DIAG0_COE_PPM_BIT_OFFSET (0U) +#define DW3000_CIA_DIAG0_COE_PPM_BIT_LEN (12U) +#define DW3000_CIA_DIAG0_COE_PPM_BIT_MASK 0xfffU + +/* register IP_DIAG0 */ +#define DW3000_IP_DIAG0_ID 0xc0028 +#define DW3000_IP_DIAG0_LEN (4U) + +/* register IP_DIAG1 */ +#define DW3000_IP_DIAG1_ID 0xc002c +#define DW3000_IP_DIAG1_LEN (4U) + +/* register IP_DIAG2 */ +#define DW3000_IP_DIAG2_ID 0xc0030 +#define DW3000_IP_DIAG2_LEN (4U) + +/* register IP_DIAG8 */ +#define DW3000_IP_DIAG8_ID 0xc0048 +#define DW3000_IP_DIAG8_LEN (4U) + +/* register IP_DIAG12 */ +#define DW3000_IP_DIAG12_ID 0xc0058 +#define DW3000_IP_DIAG12_LEN (4U) +#define DW3000_IP_DIAG12_MASK (0xfff) + +/* register CP0_DIAG12 */ +#define DW3000_CP0_DIAG1_ID 0xc0060 +#define DW3000_CP0_DIAG1_LEN (4U) + +/* register CP0_DIAG12 */ +#define DW3000_CP0_DIAG12_ID 0xd0020 +#define DW3000_CP0_DIAG12_LEN (4U) + +/* register CP1_DIAG1 */ +#define DW3000_CP1_DIAG1_ID 0xd003c +#define DW3000_CP1_DIAG1_LEN (4U) + +/* register CP1_DIAG1 */ +#define DW3000_CP1_DIAG12_ID 0xd0068 +#define DW3000_CP1_DIAG12_LEN (4U) + +/* register STS_DIAG_0 */ +#define DW3000_STS_DIAG_0_ID 0xc005c +#define DW3000_STS_DIAG_0_LEN (4U) +#define DW3000_STS_DIAG_0_MASK 0xFFFFFFFFUL + +/* register STS_DIAG_1 */ +#define DW3000_STS_DIAG_1_ID 0xc0060 +#define DW3000_STS_DIAG_1_LEN (4U) +#define DW3000_STS_DIAG_1_MASK 0xFFFFFFFFUL + +/* register STS_DIAG_2 */ +#define DW3000_STS_DIAG_2_ID 0xc0064 +#define DW3000_STS_DIAG_2_LEN (4U) +#define DW3000_STS_DIAG_2_MASK 0xFFFFFFFFUL + +/* register STS_DIAG_3 */ +#define DW3000_STS_DIAG_3_ID 0xc0068 +#define DW3000_STS_DIAG_3_LEN (4U) +#define DW3000_STS_DIAG_3_MASK 0xFFFFFFFFUL + +/* register STS_DIAG_4 */ +#define DW3000_STS_DIAG_4_ID 0xd0000 +#define DW3000_STS_DIAG_4_LEN (4U) +#define DW3000_STS_DIAG_4_MASK 0xFFFFFFFFUL + +/* register STS_DIAG_5 */ +#define DW3000_STS_DIAG_5_ID 0xd0004 +#define DW3000_STS_DIAG_5_LEN (4U) +#define DW3000_STS_DIAG_5_MASK 0xFFFFFFFFUL + +/* register STS_DIAG_6 */ +#define DW3000_STS_DIAG_6_ID 0xd0008 +#define DW3000_STS_DIAG_6_LEN (4U) +#define DW3000_STS_DIAG_6_MASK 0xFFFFFFFFUL + +/* register STS_DIAG_7 */ +#define DW3000_STS_DIAG_7_ID 0xd000c +#define DW3000_STS_DIAG_7_LEN (4U) +#define DW3000_STS_DIAG_7_MASK 0xFFFFFFFFUL + +/* register STS_DIAG_8 */ +#define DW3000_STS_DIAG_8_ID 0xd0010 +#define DW3000_STS_DIAG_8_LEN (4U) +#define DW3000_STS_DIAG_8_MASK 0xFFFFFFFFUL + +/* register STS_DIAG_9 */ +#define DW3000_STS_DIAG_9_ID 0xd0014 +#define DW3000_STS_DIAG_9_LEN (4U) +#define DW3000_STS_DIAG_9_MASK 0xFFFFFFFFUL + +/* register STS_DIAG_10 */ +#define DW3000_STS_DIAG_10_ID 0xd0018 +#define DW3000_STS_DIAG_10_LEN (4U) +#define DW3000_STS_DIAG_10_MASK 0xFFFFFFFFUL + +/* register STS_DIAG_11 */ +#define DW3000_STS_DIAG_11_ID 0xd001c +#define DW3000_STS_DIAG_11_LEN (4U) +#define DW3000_STS_DIAG_11_MASK 0xFFFFFFFFUL + +/* register STS_DIAG_12 */ +#define DW3000_STS_DIAG_12_ID 0xd0020 +#define DW3000_STS_DIAG_12_LEN (4U) +#define DW3000_STS_DIAG_12_MASK 0x7FFUL + +/* register STS_DIAG_13 */ +#define DW3000_STS_DIAG_13_ID 0xd0024 +#define DW3000_STS_DIAG_13_LEN (4U) +#define DW3000_STS_DIAG_13_MASK 0xFFFFFFFFUL + +/* register STS_DIAG_14 */ +#define DW3000_STS_DIAG_14_ID 0xd0028 +#define DW3000_STS_DIAG_14_LEN (4U) +#define DW3000_STS_DIAG_14_MASK 0xFFFFFFFFUL + +/* register STS_DIAG_15 */ +#define DW3000_STS_DIAG_15_ID 0xd002c +#define DW3000_STS_DIAG_15_LEN (4U) +#define DW3000_STS_DIAG_15_MASK 0xFFFFFFFFUL + +/* register STS_DIAG_16 */ +#define DW3000_STS_DIAG_16_ID 0xd0030 +#define DW3000_STS_DIAG_16_LEN (4U) +#define DW3000_STS_DIAG_16_MASK 0xFFFFFFFFUL + +/* register STS_DIAG_17 */ +#define DW3000_STS_DIAG_17_ID 0xd0034 +#define DW3000_STS_DIAG_17_LEN (4U) +#define DW3000_STS_DIAG_17_MASK 0xFFFFFFFFUL + +/* register STS1_DIAG_0 */ +#define DW3000_STS1_DIAG_0_ID 0xd0038 +#define DW3000_STS1_DIAG_0_LEN (4U) +#define DW3000_STS1_DIAG_0_MASK 0xFFFFFFFFUL + +/* register STS1_DIAG_1 */ +#define DW3000_STS1_DIAG_1_ID 0xd003c +#define DW3000_STS1_DIAG_1_LEN (4U) +#define DW3000_STS1_DIAG_1_MASK 0xFFFFFFFFUL + +/* register STS1_DIAG_2 */ +#define DW3000_STS1_DIAG_2_ID 0xd0040 +#define DW3000_STS1_DIAG_2_LEN (4U) +#define DW3000_STS1_DIAG_2_MASK 0xFFFFFFFFUL + +/* register STS1_DIAG_3 */ +#define DW3000_STS1_DIAG_3_ID 0xd0044 +#define DW3000_STS1_DIAG_3_LEN (4U) +#define DW3000_STS1_DIAG_3_MASK 0xFFFFFFFFUL + +/* register STS1_DIAG_4 */ +#define DW3000_STS1_DIAG_4_ID 0xd0048 +#define DW3000_STS1_DIAG_4_LEN (4U) +#define DW3000_STS1_DIAG_4_MASK 0xFFFFFFFFUL + +/* register STS1_DIAG_5 */ +#define DW3000_STS1_DIAG_5_ID 0xd004c +#define DW3000_STS1_DIAG_5_LEN (4U) +#define DW3000_STS1_DIAG_5_MASK 0xFFFFFFFFUL + +/* register STS1_DIAG_6 */ +#define DW3000_STS1_DIAG_6_ID 0xd0050 +#define DW3000_STS1_DIAG_6_LEN (4U) +#define DW3000_STS1_DIAG_6_MASK 0xFFFFFFFFUL + +/* register STS1_DIAG_7 */ +#define DW3000_STS1_DIAG_7_ID 0xd0054 +#define DW3000_STS1_DIAG_7_LEN (4U) +#define DW3000_STS1_DIAG_7_MASK 0xFFFFFFFFUL + +/* register STS1_DIAG_8 */ +#define DW3000_STS1_DIAG_8_ID 0xd0058 +#define DW3000_STS1_DIAG_8_LEN (4U) +#define DW3000_STS1_DIAG_8_MASK 0xFFFFFFFFUL + +/* register STS1_DIAG_9 */ +#define DW3000_STS1_DIAG_9_ID 0xd005c +#define DW3000_STS1_DIAG_9_LEN (4U) +#define DW3000_STS1_DIAG_9_MASK 0xFFFFFFFFUL + +/* register STS1_DIAG_10 */ +#define DW3000_STS1_DIAG_10_ID 0xd0060 +#define DW3000_STS1_DIAG_10_LEN (4U) +#define DW3000_STS1_DIAG_10_MASK 0xFFFFFFFFUL + +/* register STS1_DIAG_11 */ +#define DW3000_STS1_DIAG_11_ID 0xd0064 +#define DW3000_STS1_DIAG_11_LEN (4U) +#define DW3000_STS1_DIAG_11_MASK 0xFFFFFFFFUL + +/* register STS1_DIAG_12 */ +#define DW3000_STS1_DIAG_12_ID 0xd0068 +#define DW3000_STS1_DIAG_12_LEN (4U) +#define DW3000_STS1_DIAG_12_MASK 0x7FFUL + +/* register CIA_CONF */ +#define DW3000_CIA_CONF_ID 0xe0000 +#define DW3000_CIA_CONF_LEN (4U) +#define DW3000_CIA_CONF_MINDIAG_BIT_MASK (0x100000) + +/* register RX_ANTENNA_DELAY */ +#define DW3000_RX_ANTENNA_DELAY_ID 0xe0000 + +/* register CY_CONFIG_LO_ID 0xe0012/0xe0014 */ +#define DW3000_CY_CONFIG_LO_MANUALLOWERBOUND_BIT_MASK 0x7f0000UL + +/* register STS_CONFIG_LO (CY_CONFIG_LO_ID renamed in E0) */ +#define DW3000_STS_CONFIG_LO_ID DW3000_CY_CONFIG_LO_ID +#define DW3000_STS_CONFIG_LO_LEN (4U) +#define DW3000_STS_CONFIG_LO_MASK 0xFFFFFFFFUL +#define DW3000_STS_CONFIG_LO_STS_MAN_TH_BIT_OFFSET (16U) +#define DW3000_STS_CONFIG_LO_STS_MAN_TH_BIT_LEN (7U) +#define DW3000_STS_CONFIG_LO_STS_MAN_TH_BIT_MASK 0x7f0000UL +#define DW3000_STS_CONFIG_LO_STS_PMULT_BIT_OFFSET (5U) +#define DW3000_STS_CONFIG_LO_STS_PMULT_BIT_LEN (2U) +#define DW3000_STS_CONFIG_LO_STS_PMULT_BIT_MASK 0x60U +#define DW3000_STS_CONFIG_LO_STS_NTM_BIT_OFFSET (0U) +#define DW3000_STS_CONFIG_LO_STS_NTM_BIT_LEN (5U) +#define DW3000_STS_CONFIG_LO_STS_NTM_BIT_MASK 0x1fU + +/* register STS_CONFIG_HI (CY_CONFIG_HI_ID renamed in E0) */ +#define DW3000_STS_CONFIG_HI_ID DW3000_CY_CONFIG_HI_ID +#define DW3000_STS_CONFIG_HI_LEN (4U) +#define DW3000_STS_CONFIG_HI_MASK 0xFFFFFFFFUL +#define DW3000_STS_CONFIG_HI_STS_PGR_EN_BIT_OFFSET (31U) +#define DW3000_STS_CONFIG_HI_STS_PGR_EN_BIT_LEN (1U) +#define DW3000_STS_CONFIG_HI_STS_PGR_EN_BIT_MASK 0x80000000UL +#define DW3000_STS_CONFIG_HI_STS_SS_EN_BIT_OFFSET (30U) +#define DW3000_STS_CONFIG_HI_STS_SS_EN_BIT_LEN (1U) +#define DW3000_STS_CONFIG_HI_STS_SS_EN_BIT_MASK 0x40000000UL +#define DW3000_STS_CONFIG_HI_STS_CQ_EN_BIT_OFFSET (29U) +#define DW3000_STS_CONFIG_HI_STS_CQ_EN_BIT_LEN (1U) +#define DW3000_STS_CONFIG_HI_STS_CQ_EN_BIT_MASK 0x20000000UL +#define DW3000_STS_CONFIG_HI_FP_AGREED_EN_BIT_OFFSET (28U) +#define DW3000_STS_CONFIG_HI_FP_AGREED_EN_BIT_LEN (1U) +#define DW3000_STS_CONFIG_HI_FP_AGREED_EN_BIT_MASK 0x10000000UL +#define DW3000_STS_CONFIG_HI_RES_B0_BIT_OFFSET (4U) +#define DW3000_STS_CONFIG_HI_RES_B0_BIT_LEN (4U) +#define DW3000_STS_CONFIG_HI_RES_B0_BIT_MASK 0xf0U + +/* register TEST_CTRL0 0xf0024/0xf0028 */ +#define DW3000_TEST_CTRL0_LEN (4U) +#define DW3000_TEST_CTRL0_MASK 0xFFFFFFFFUL +#define DW3000_TEST_CTRL0_FIXED_STS_BIT_OFFSET (29U) +#define DW3000_TEST_CTRL0_FIXED_STS_BIT_LEN (1U) +#define DW3000_TEST_CTRL0_FIXED_STS_BIT_MASK 0x20000000UL +#define DW3000_TEST_CTRL0_CIA_RUN_BIT_OFFSET (26U) +#define DW3000_TEST_CTRL0_CIA_RUN_BIT_LEN (1U) +#define DW3000_TEST_CTRL0_CIA_RUN_BIT_MASK 0x4000000UL +#define DW3000_TEST_CTRL0_CIA_WDEN_BIT_OFFSET (24U) +#define DW3000_TEST_CTRL0_CIA_WDEN_BIT_LEN (1U) +#define DW3000_TEST_CTRL0_CIA_WDEN_BIT_MASK 0x1000000UL +#define DW3000_TEST_CTRL0_HIRQ_POL_BIT_OFFSET (21U) +#define DW3000_TEST_CTRL0_HIRQ_POL_BIT_LEN (1U) +#define DW3000_TEST_CTRL0_HIRQ_POL_BIT_MASK 0x200000UL +#define DW3000_TEST_CTRL0_TX_PSTM_BIT_OFFSET (4U) +#define DW3000_TEST_CTRL0_TX_PSTM_BIT_LEN (1U) +#define DW3000_TEST_CTRL0_TX_PSTM_BIT_MASK 0x10U + +/* register SYS_STATE_LO */ +#define DW3000_SYS_STATE_LO_ID 0xf0030 +#define DW3000_SYS_STATE_LO_LEN (4U) +#define DW3000_SYS_STATE_LO_MASK 0xFFFFFFFFUL + +/* register SOFT_RST */ +#define DW3000_SOFT_RST_ID 0x110000 +#define DW3000_SOFT_RST_LEN (4U) +#define DW3000_SOFT_RST_MASK 0xFFFFFFFFUL +#define DW3000_SOFT_RST_DIGAON_RST_N_BIT_OFFSET (11U) +#define DW3000_SOFT_RST_DIGAON_RST_N_BIT_LEN (1U) +#define DW3000_SOFT_RST_DIGAON_RST_N_BIT_MASK 0x800U +#define DW3000_SOFT_RST_TIM_RST_N_BIT_OFFSET (10U) +#define DW3000_SOFT_RST_TIM_RST_N_BIT_LEN (1U) +#define DW3000_SOFT_RST_TIM_RST_N_BIT_MASK 0x400U + +/* register CLK_CTRL */ +#define DW3000_CLK_CTRL_ID 0x110004 +#define DW3000_CLK_CTRL_LEN (4U) +#define DW3000_CLK_CTRL_MASK 0xFFFFFFFFUL +#define DW3000_CLK_CTRL_BIST_CLK_EN_BIT_OFFSET (26U) +#define DW3000_CLK_CTRL_BIST_CLK_EN_BIT_LEN (1U) +#define DW3000_CLK_CTRL_BIST_CLK_EN_BIT_MASK 0x4000000UL +#define DW3000_CLK_CTRL_PLL_LOCK_TIMER_EN_BIT_OFFSET (25U) +#define DW3000_CLK_CTRL_PLL_LOCK_TIMER_EN_BIT_LEN (1U) +#define DW3000_CLK_CTRL_PLL_LOCK_TIMER_EN_BIT_MASK 0x2000000UL +#define DW3000_CLK_CTRL_SLEEP_MODE_BIT_OFFSET (24U) +#define DW3000_CLK_CTRL_SLEEP_MODE_BIT_LEN (1U) +#define DW3000_CLK_CTRL_SLEEP_MODE_BIT_MASK 0x1000000UL +#define DW3000_CLK_CTRL_LP_CLK_EN_BIT_OFFSET (23U) +#define DW3000_CLK_CTRL_LP_CLK_EN_BIT_LEN (1U) +#define DW3000_CLK_CTRL_LP_CLK_EN_BIT_MASK 0x800000UL +#define DW3000_CLK_CTRL_RX_BUFF_AUTO_CLK_BIT_OFFSET (21U) +#define DW3000_CLK_CTRL_RX_BUFF_AUTO_CLK_BIT_LEN (1U) +#define DW3000_CLK_CTRL_RX_BUFF_AUTO_CLK_BIT_MASK 0x200000UL +#define DW3000_CLK_CTRL_CODE_MEM_AUTO_CLK_BIT_OFFSET (20U) +#define DW3000_CLK_CTRL_CODE_MEM_AUTO_CLK_BIT_LEN (1U) +#define DW3000_CLK_CTRL_CODE_MEM_AUTO_CLK_BIT_MASK 0x100000UL +#define DW3000_CLK_CTRL_GPIO_DBNC_RST_N_BIT_OFFSET (19U) +#define DW3000_CLK_CTRL_GPIO_DBNC_RST_N_BIT_LEN (1U) +#define DW3000_CLK_CTRL_GPIO_DBNC_RST_N_BIT_MASK 0x80000UL +#define DW3000_CLK_CTRL_GPIO_DBNC_CLK_EN_BIT_OFFSET (18U) +#define DW3000_CLK_CTRL_GPIO_DBNC_CLK_EN_BIT_LEN (1U) +#define DW3000_CLK_CTRL_GPIO_DBNC_CLK_EN_BIT_MASK 0x40000UL +#define DW3000_CLK_CTRL_GPIO_CLK_EN_BIT_OFFSET (16U) +#define DW3000_CLK_CTRL_GPIO_CLK_EN_BIT_LEN (1U) +#define DW3000_CLK_CTRL_GPIO_CLK_EN_BIT_MASK 0x10000UL +#define DW3000_CLK_CTRL_ACC_MEM_CLK_ON_BIT_OFFSET (15U) +#define DW3000_CLK_CTRL_ACC_MEM_CLK_ON_BIT_LEN (1U) +#define DW3000_CLK_CTRL_ACC_MEM_CLK_ON_BIT_MASK 0x8000U +#define DW3000_CLK_CTRL_RSD_CLK_ON_BIT_OFFSET (14U) +#define DW3000_CLK_CTRL_RSD_CLK_ON_BIT_LEN (1U) +#define DW3000_CLK_CTRL_RSD_CLK_ON_BIT_MASK 0x4000U +#define DW3000_CLK_CTRL_LOOPBACK_CLK_EN_BIT_OFFSET (13U) +#define DW3000_CLK_CTRL_LOOPBACK_CLK_EN_BIT_LEN (1U) +#define DW3000_CLK_CTRL_LOOPBACK_CLK_EN_BIT_MASK 0x2000U +#define DW3000_CLK_CTRL_TX_BUF_CLK_ON_BIT_OFFSET (12U) +#define DW3000_CLK_CTRL_TX_BUF_CLK_ON_BIT_LEN (1U) +#define DW3000_CLK_CTRL_TX_BUF_CLK_ON_BIT_MASK 0x1000U +#define DW3000_CLK_CTRL_RX_BUF_CLK_ON_BIT_OFFSET (11U) +#define DW3000_CLK_CTRL_RX_BUF_CLK_ON_BIT_LEN (1U) +#define DW3000_CLK_CTRL_RX_BUF_CLK_ON_BIT_MASK 0x800U +#define DW3000_CLK_CTRL_FORCE_NVM_CLK_EN_BIT_OFFSET (9U) +#define DW3000_CLK_CTRL_FORCE_NVM_CLK_EN_BIT_LEN (1U) +#define DW3000_CLK_CTRL_FORCE_NVM_CLK_EN_BIT_MASK 0x200U +#define DW3000_CLK_CTRL_FORCE_CIA_CLKS_ON_BIT_OFFSET (8U) +#define DW3000_CLK_CTRL_FORCE_CIA_CLKS_ON_BIT_LEN (1U) +#define DW3000_CLK_CTRL_FORCE_CIA_CLKS_ON_BIT_MASK 0x100U +#define DW3000_CLK_CTRL_RX_CLK_GATE_DISABLE_BIT_OFFSET (7U) +#define DW3000_CLK_CTRL_RX_CLK_GATE_DISABLE_BIT_LEN (1U) +#define DW3000_CLK_CTRL_RX_CLK_GATE_DISABLE_BIT_MASK 0x80U +#define DW3000_CLK_CTRL_FORCE_ACC_CLK_BIT_OFFSET (6U) +#define DW3000_CLK_CTRL_FORCE_ACC_CLK_BIT_LEN (1U) +#define DW3000_CLK_CTRL_FORCE_ACC_CLK_BIT_MASK 0x40U +#define DW3000_CLK_CTRL_TX_CLK_SEL_BIT_OFFSET (4U) +#define DW3000_CLK_CTRL_TX_CLK_SEL_BIT_LEN (2U) +#define DW3000_CLK_CTRL_TX_CLK_SEL_BIT_MASK 0x30U +#define DW3000_CLK_CTRL_RX_CLK_SEL_BIT_OFFSET (2U) +#define DW3000_CLK_CTRL_RX_CLK_SEL_BIT_LEN (2U) +#define DW3000_CLK_CTRL_RX_CLK_SEL_BIT_MASK 0xcU +#define DW3000_CLK_CTRL_SYS_CLK_SEL_BIT_OFFSET (0U) +#define DW3000_CLK_CTRL_SYS_CLK_SEL_BIT_LEN (2U) +#define DW3000_CLK_CTRL_SYS_CLK_SEL_BIT_MASK 0x3U + +/* register SEQ_CTRL */ +#define DW3000_SEQ_CTRL_ID 0x110008 +#define DW3000_SEQ_CTRL_LEN (4U) +#define DW3000_SEQ_CTRL_MASK 0xFFFFFFFFUL +#define DW3000_SEQ_CTRL_LP_CLK_DIV_BIT_OFFSET (26U) +#define DW3000_SEQ_CTRL_LP_CLK_DIV_BIT_LEN (6U) +#define DW3000_SEQ_CTRL_LP_CLK_DIV_BIT_MASK 0xfc000000UL +#define DW3000_SEQ_CTRL_FORCE_SYNC_BIT_OFFSET (25U) +#define DW3000_SEQ_CTRL_FORCE_SYNC_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_FORCE_SYNC_BIT_MASK 0x2000000UL +#define DW3000_SEQ_CTRL_FORCE2RC_BIT_OFFSET (24U) +#define DW3000_SEQ_CTRL_FORCE2RC_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_FORCE2RC_BIT_MASK 0x1000000UL +#define DW3000_SEQ_CTRL_FORCE2INIT_BIT_OFFSET (23U) +#define DW3000_SEQ_CTRL_FORCE2INIT_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_FORCE2INIT_BIT_MASK 0x800000UL +#define DW3000_SEQ_CTRL_FORCE2IDLE_BIT_OFFSET (22U) +#define DW3000_SEQ_CTRL_FORCE2IDLE_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_FORCE2IDLE_BIT_MASK 0x400000UL +#define DW3000_SEQ_CTRL_RX_RST_MODE_BIT_OFFSET (21U) +#define DW3000_SEQ_CTRL_RX_RST_MODE_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_RX_RST_MODE_BIT_MASK 0x200000UL +#define DW3000_SEQ_CTRL_FORCE_RX_STATE_BIT_OFFSET (20U) +#define DW3000_SEQ_CTRL_FORCE_RX_STATE_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_FORCE_RX_STATE_BIT_MASK 0x100000UL +#define DW3000_SEQ_CTRL_FORCE_TX_STATE_BIT_OFFSET (19U) +#define DW3000_SEQ_CTRL_FORCE_TX_STATE_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_FORCE_TX_STATE_BIT_MASK 0x80000UL +#define DW3000_SEQ_CTRL_FORCE_CAL_MODE_BIT_OFFSET (18U) +#define DW3000_SEQ_CTRL_FORCE_CAL_MODE_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_FORCE_CAL_MODE_BIT_MASK 0x40000UL +#define DW3000_SEQ_CTRL_CIA_SEQ_EN_BIT_OFFSET (17U) +#define DW3000_SEQ_CTRL_CIA_SEQ_EN_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_CIA_SEQ_EN_BIT_MASK 0x20000UL +#define DW3000_SEQ_CTRL_RX_OFF_EARLY_EN_BIT_OFFSET (16U) +#define DW3000_SEQ_CTRL_RX_OFF_EARLY_EN_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_RX_OFF_EARLY_EN_BIT_MASK 0x10000UL +#define DW3000_SEQ_CTRL_PLL_SYNC_MODE_BIT_OFFSET (15U) +#define DW3000_SEQ_CTRL_PLL_SYNC_MODE_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_PLL_SYNC_MODE_BIT_MASK 0x8000U +#define DW3000_SEQ_CTRL_SNOOZE_REPEAT_BIT_OFFSET (14U) +#define DW3000_SEQ_CTRL_SNOOZE_REPEAT_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_SNOOZE_REPEAT_BIT_MASK 0x4000U +#define DW3000_SEQ_CTRL_SNOOZE_EN_BIT_OFFSET (13U) +#define DW3000_SEQ_CTRL_SNOOZE_EN_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_SNOOZE_EN_BIT_MASK 0x2000U +#define DW3000_SEQ_CTRL_AUTO_RX2SLP_BIT_OFFSET (12U) +#define DW3000_SEQ_CTRL_AUTO_RX2SLP_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_AUTO_RX2SLP_BIT_MASK 0x1000U +#define DW3000_SEQ_CTRL_AUTO_TX2SLP_BIT_OFFSET (11U) +#define DW3000_SEQ_CTRL_AUTO_TX2SLP_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_AUTO_TX2SLP_BIT_MASK 0x800U +#define DW3000_SEQ_CTRL_AUTO_RX_SEQ_BIT_OFFSET (10U) +#define DW3000_SEQ_CTRL_AUTO_RX_SEQ_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_AUTO_RX_SEQ_BIT_MASK 0x400U +#define DW3000_SEQ_CTRL_AUTO_TX_SEQ_BIT_OFFSET (9U) +#define DW3000_SEQ_CTRL_AUTO_TX_SEQ_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_AUTO_TX_SEQ_BIT_MASK 0x200U +#define DW3000_SEQ_CTRL_AUTO_INIT2IDLE_BIT_OFFSET (8U) +#define DW3000_SEQ_CTRL_AUTO_INIT2IDLE_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_AUTO_INIT2IDLE_BIT_MASK 0x100U +#define DW3000_SEQ_CTRL_AUTOINIT_MODE_BIT_OFFSET (7U) +#define DW3000_SEQ_CTRL_AUTOINIT_MODE_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_AUTOINIT_MODE_BIT_MASK 0x80U +#define DW3000_SEQ_CTRL_AON_CLK_CTRL_BIT_OFFSET (5U) +#define DW3000_SEQ_CTRL_AON_CLK_CTRL_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_AON_CLK_CTRL_BIT_MASK 0x20U +#define DW3000_SEQ_CTRL_RF_CTRL_BIT_OFFSET (3U) +#define DW3000_SEQ_CTRL_RF_CTRL_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_RF_CTRL_BIT_MASK 0x8U +#define DW3000_SEQ_CTRL_AUTOTX2INIT_BIT_OFFSET (2U) +#define DW3000_SEQ_CTRL_AUTOTX2INIT_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_AUTOTX2INIT_BIT_MASK 0x4U +#define DW3000_SEQ_CTRL_AUTORX2INIT_BIT_OFFSET (1U) +#define DW3000_SEQ_CTRL_AUTORX2INIT_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_AUTORX2INIT_BIT_MASK 0x2U +#define DW3000_SEQ_CTRL_SYS_TIME_ON_BIT_OFFSET (0U) +#define DW3000_SEQ_CTRL_SYS_TIME_ON_BIT_LEN (1U) +#define DW3000_SEQ_CTRL_SYS_TIME_ON_BIT_MASK 0x1U + +/* register PWR_UP_TIMES_LO */ +#define DW3000_PWR_UP_TIMES_LO_ID 0x110010 +#define DW3000_PWR_UP_TIMES_LO_LEN (4U) +#define DW3000_PWR_UP_TIMES_LO_MASK 0xFFFFFFFFUL +#define DW3000_PWR_UP_TIMES_TXFINESEQ_BIT_OFFSET (0U) +#define DW3000_PWR_UP_TIMES_TXFINESEQ_BIT_LEN (32U) +#define DW3000_PWR_UP_TIMES_TXFINESEQ_BIT_MASK 0xFFFFFFFFUL + +/* register LED_CTRL 0x110016/0x110018 */ +#define DW3000_LED_CTRL_MASK 0xFFFFFFFFUL +#define DW3000_LED_CTRL_FORCE_TRIGGER_BIT_OFFSET (16U) +#define DW3000_LED_CTRL_FORCE_TRIGGER_BIT_LEN (4U) +#define DW3000_LED_CTRL_FORCE_TRIGGER_BIT_MASK 0xf0000UL +#define DW3000_LED_CTRL_BLINK_EN_BIT_OFFSET (8U) +#define DW3000_LED_CTRL_BLINK_EN_BIT_LEN (1U) +#define DW3000_LED_CTRL_BLINK_EN_BIT_MASK 0x100U +#define DW3000_LED_CTRL_BLINK_CNT_BIT_OFFSET (0U) +#define DW3000_LED_CTRL_BLINK_CNT_BIT_LEN (8U) +#define DW3000_LED_CTRL_BLINK_CNT_BIT_MASK 0xffU + +/* register BIAS_CTRL 0x11001f/0x110030 */ +#define DW3000_BIAS_CTRL_DIG_BIAS_DAC_ULV_BIT_MASK 0x1fU + +/* Dual SPI Semaphore events register DSS_STAT */ +#define DW3000_DSS_STAT_ID 0x110038 +#define DW3000_DSS_STAT_SPI1_AVAIL_BIT_OFFSET (0U) +#define DW3000_DSS_STAT_SPI1_AVAIL_BIT_LEN (1U) +#define DW3000_DSS_STAT_SPI1_AVAIL_BIT_MASK 0x1U +#define DW3000_DSS_STAT_SPI2_AVAIL_BIT_OFFSET (1U) +#define DW3000_DSS_STAT_SPI2_AVAIL_BIT_LEN (1U) +#define DW3000_DSS_STAT_SPI2_AVAIL_BIT_MASK 0x2U + +/* register TIMER_CTRL */ +#define DW3000_TIMER_CTRL_ID 0x11003c +#define DW3000_TIMER_CTRL_LEN (4U) +#define DW3000_TIMER_CTRL_MASK 0xFFFFFFFFUL +#define DW3000_TIMER_CTRL_TIMER_1_COEXOUT_BIT_OFFSET (22U) +#define DW3000_TIMER_CTRL_TIMER_1_COEXOUT_BIT_LEN (1U) +#define DW3000_TIMER_CTRL_TIMER_1_COEXOUT_BIT_MASK 0x400000UL +#define DW3000_TIMER_CTRL_TIMER_1_GPIO_BIT_OFFSET (21U) +#define DW3000_TIMER_CTRL_TIMER_1_GPIO_BIT_LEN (1U) +#define DW3000_TIMER_CTRL_TIMER_1_GPIO_BIT_MASK 0x200000UL +#define DW3000_TIMER_CTRL_TIMER_1_TRIG_BIT_OFFSET (20U) +#define DW3000_TIMER_CTRL_TIMER_1_TRIG_BIT_LEN (1U) +#define DW3000_TIMER_CTRL_TIMER_1_TRIG_BIT_MASK 0x100000UL +#define DW3000_TIMER_CTRL_TIMER_1_MODE_BIT_OFFSET (19U) +#define DW3000_TIMER_CTRL_TIMER_1_MODE_BIT_LEN (1U) +#define DW3000_TIMER_CTRL_TIMER_1_MODE_BIT_MASK 0x80000UL +#define DW3000_TIMER_CTRL_TIMER_1_DIV_BIT_OFFSET (16U) +#define DW3000_TIMER_CTRL_TIMER_1_DIV_BIT_LEN (3U) +#define DW3000_TIMER_CTRL_TIMER_1_DIV_BIT_MASK 0x70000UL +#define DW3000_TIMER_CTRL_TIMER_0_COEXOUT_BIT_OFFSET (14U) +#define DW3000_TIMER_CTRL_TIMER_0_COEXOUT_BIT_LEN (1U) +#define DW3000_TIMER_CTRL_TIMER_0_COEXOUT_BIT_MASK 0x4000U +#define DW3000_TIMER_CTRL_TIMER_0_GPIO_BIT_OFFSET (13U) +#define DW3000_TIMER_CTRL_TIMER_0_GPIO_BIT_LEN (1U) +#define DW3000_TIMER_CTRL_TIMER_0_GPIO_BIT_MASK 0x2000U +#define DW3000_TIMER_CTRL_TIMER_0_TRIG_BIT_OFFSET (12U) +#define DW3000_TIMER_CTRL_TIMER_0_TRIG_BIT_LEN (1U) +#define DW3000_TIMER_CTRL_TIMER_0_TRIG_BIT_MASK 0x1000U +#define DW3000_TIMER_CTRL_TIMER_0_MODE_BIT_OFFSET (11U) +#define DW3000_TIMER_CTRL_TIMER_0_MODE_BIT_LEN (1U) +#define DW3000_TIMER_CTRL_TIMER_0_MODE_BIT_MASK 0x800U +#define DW3000_TIMER_CTRL_TIMER_0_DIV_BIT_OFFSET (8U) +#define DW3000_TIMER_CTRL_TIMER_0_DIV_BIT_LEN (3U) +#define DW3000_TIMER_CTRL_TIMER_0_DIV_BIT_MASK 0x700U +#define DW3000_TIMER_CTRL_TIMER_1_RD_COUNT_BIT_OFFSET (3U) +#define DW3000_TIMER_CTRL_TIMER_1_RD_COUNT_BIT_LEN (1U) +#define DW3000_TIMER_CTRL_TIMER_1_RD_COUNT_BIT_MASK 0x8U +#define DW3000_TIMER_CTRL_TIMER_0_RD_COUNT_BIT_OFFSET (2U) +#define DW3000_TIMER_CTRL_TIMER_0_RD_COUNT_BIT_LEN (1U) +#define DW3000_TIMER_CTRL_TIMER_0_RD_COUNT_BIT_MASK 0x4U +#define DW3000_TIMER_CTRL_TIMER_1_EN_BIT_OFFSET (1U) +#define DW3000_TIMER_CTRL_TIMER_1_EN_BIT_LEN (1U) +#define DW3000_TIMER_CTRL_TIMER_1_EN_BIT_MASK 0x2U +#define DW3000_TIMER_CTRL_TIMER_0_EN_BIT_OFFSET (0U) +#define DW3000_TIMER_CTRL_TIMER_0_EN_BIT_LEN (1U) +#define DW3000_TIMER_CTRL_TIMER_0_EN_BIT_MASK 0x1U + +/* register TIMER0_CNT_SET */ +#define DW3000_TIMER0_CNT_SET_ID 0x110040 +#define DW3000_TIMER0_CNT_SET_LEN (4U) +#define DW3000_TIMER0_CNT_SET_MASK 0xFFFFFFFFUL +#define DW3000_TIMER0_CNT_SET_TIMER_0_SET_BIT_OFFSET (0U) +#define DW3000_TIMER0_CNT_SET_TIMER_0_SET_BIT_LEN (22U) +#define DW3000_TIMER0_CNT_SET_TIMER_0_SET_BIT_MASK 0x3fffffUL + +/* register TIMER1_CNT_SET */ +#define DW3000_TIMER1_CNT_SET_ID 0x110044 +#define DW3000_TIMER1_CNT_SET_LEN (4U) +#define DW3000_TIMER1_CNT_SET_MASK 0xFFFFFFFFUL +#define DW3000_TIMER1_CNT_SET_TIMER_1_SET_BIT_OFFSET (0U) +#define DW3000_TIMER1_CNT_SET_TIMER_1_SET_BIT_LEN (22U) +#define DW3000_TIMER1_CNT_SET_TIMER_1_SET_BIT_MASK 0x3fffffUL + +/* register TIMER_STATUS */ +#define DW3000_TIMER_STATUS_ID 0x110048 +#define DW3000_TIMER_STATUS_LEN (4U) +#define DW3000_TIMER_STATUS_MASK 0xFFFFFFFFUL +#define DW3000_TIMER_STATUS_TIMER1_STATUS_BIT_OFFSET (8U) +#define DW3000_TIMER_STATUS_TIMER1_STATUS_BIT_LEN (8U) +#define DW3000_TIMER_STATUS_TIMER1_STATUS_BIT_MASK 0xff00U +#define DW3000_TIMER_STATUS_TIMER0_STATUS_BIT_OFFSET (0U) +#define DW3000_TIMER_STATUS_TIMER0_STATUS_BIT_LEN (8U) +#define DW3000_TIMER_STATUS_TIMER0_STATUS_BIT_MASK 0xffU + +/* DW3000 CIR memory */ +#define DW3000_CIR_RAM_ID 0x150000 +/* 2048 records of 6 byte each */ +#define DW3000_CIR_RAM_LEN (2048 * 6) + +/* DW3000 scratch ram */ +#define DW3000_SCRATCH_RAM_ID 0x160000 +#define DW3000_SCRATCH_RAM_LEN 127 + +/* Double buffer diagnostic register set */ +#define DW3000_DB_DIAG_SET_1 0x180000 +#define DW3000_DB_DIAG_SET_2 0x1800e8 +#define DW3000_DB_DIAG_SET_LEN 0xe8 +/* Double buffer diagnostic register offsets */ +/* RDB_DMODE = 1 || 2 || 4 */ +#define DW3000_DB_DIAG_RX_FINFO 0x0 +#define DW3000_DB_DIAG_RX_TIME 0x4 +#define DW3000_DB_DIAG_CIA_DIAG0 0xc +#define DW3000_DB_DIAG_TDOA 0x10 +#define DW3000_DB_DIAG_PDOA 0x14 +#define DW3000_DB_DIAG_Reserved1 0x18 +#define DW3000_DB_DIAG_IP_DIAG_12 0x1c +/* RDB_DMODE = 2 || 4 */ +#define DW3000_DB_DIAG_IP_TS 0x20 +#define DW3000_DB_DIAG_Reserved2 0x24 +#define DW3000_DB_DIAG_STS_TS 0x28 +#define DW3000_DB_DIAG_Reserved3 0x2c +#define DW3000_DB_DIAG_STS1_TS 0x30 +#define DW3000_DB_DIAG_Reserved4 0x34 +/* RDB_DMODE = 4 */ +#define DW3000_DB_DIAG_CIA_DIAG1 0x38 +#define DW3000_DB_DIAG_IP_DIAG0 0x3c +#define DW3000_DB_DIAG_IP_DIAG1 0x40 +#define DW3000_DB_DIAG_IP_DIAG2 0x44 +#define DW3000_DB_DIAG_IP_DIAG3 0x48 +#define DW3000_DB_DIAG_IP_DIAG4 0x4c +#define DW3000_DB_DIAG_IP_DIAG8 0x5c +#define DW3000_DB_DIAG_STS_DIAG0 0x6c +#define DW3000_DB_DIAG_STS_DIAG1 0x70 +#define DW3000_DB_DIAG_STS_DIAG2 0x74 +#define DW3000_DB_DIAG_STS_DIAG3 0x78 +#define DW3000_DB_DIAG_STS_DIAG4 0x7c +#define DW3000_DB_DIAG_STS_DIAG8 0x8c +#define DW3000_DB_DIAG_STS_DIAG12 0x9c +#define DW3000_DB_DIAG_STS1_DIAG0 0xb4 +#define DW3000_DB_DIAG_STS1_DIAG1 0xb8 +#define DW3000_DB_DIAG_STS1_DIAG2 0xbc +#define DW3000_DB_DIAG_STS1_DIAG3 0xc0 +#define DW3000_DB_DIAG_STS1_DIAG4 0xc4 +#define DW3000_DB_DIAG_STS1_DIAG8 0xd4 +#define DW3000_DB_DIAG_STS1_DIAG12 0xe4 + +/* Dual SPI Semaphore register SPI_SEM */ +#define DW3000_SPI_SEM_ID 0x1a0000 +#define DW3000_SPI_SEM_SPI1_RG_BIT_OFFSET (0U) +#define DW3000_SPI_SEM_SPI1_RG_BIT_LEN (1U) +#define DW3000_SPI_SEM_SPI1_RG_BIT_MASK 0x1U +#define DW3000_SPI_SEM_SPI2_RG_BIT_OFFSET (1U) +#define DW3000_SPI_SEM_SPI2_RG_BIT_LEN (1U) +#define DW3000_SPI_SEM_SPI2_RG_BIT_MASK 0x2U +#define DW3000_SPI_SEM_SPI1MAVAIL_BIT_OFFSET (9U) +#define DW3000_SPI_SEM_SPI1MAVAIL_BIT_LEN (1U) +#define DW3000_SPI_SEM_SPI1MAVAIL_BIT_MASK 0x200U +#define DW3000_SPI_SEM_SPI2MAVAIL_BIT_OFFSET (10U) +#define DW3000_SPI_SEM_SPI2MAVAIL_BIT_LEN (1U) +#define DW3000_SPI_SEM_SPI2MAVAIL_BIT_MASK 0x400U + +/* Receive Data Buffer (in double buffer set) */ +#define DW3000_RX_BUFFER_A_ID 0x120000 + +/* Receive Data Buffer (in double buffer set) */ +#define DW3000_RX_BUFFER_B_ID 0x130000 + +/* Transmit Data Buffer */ +#define DW3000_TX_BUFFER_ID 0x140000 + +/* pointer to access indirect access buffer A */ +#define DW3000_INDIRECT_POINTER_A_ID 0x1D0000 + +/* pointer to access indirect access buffer B */ +#define DW3000_INDIRECT_POINTER_B_ID 0x1E0000 + +/* register INDIRECT_ADDR_A */ +#define DW3000_INDIRECT_ADDR_A_ID 0x1f0004 + +/* register ADDR_OFFSET_A */ +#define DW3000_ADDR_OFFSET_A_ID 0x1f0008 + +/* register INDIRECT_ADDR_B */ +#define DW3000_INDIRECT_ADDR_B_ID 0x1f000c + +/* register ADDR_OFFSET_B */ +#define DW3000_ADDR_OFFSET_B_ID 0x1f0010 + +#endif /* __DW3000_CORE_REG_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_core_tests.c b/kernel/drivers/net/ieee802154/dw3000_core_tests.c new file mode 100644 index 0000000..ed69b63 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_core_tests.c @@ -0,0 +1,516 @@ +#include <kunit/test.h> + +/* Forward declaration of replacement functions. */ +static u64 kunit_get_boottime_ns(void); + +/* Replace ktime_get_boottime_ns calls to kunit_get_boottime_ns calls. */ +#define ktime_get_boottime_ns kunit_get_boottime_ns +#define dw3000_get_dtu_time kunit_dw3000_get_dtu_time +#define trace_dw3000_power_stats(dw, state, boot_time_ns, len_or_date) \ + do { \ + } while (0) + +/* Include tested functions. */ +#include "dw3000_core.h" +#include "dw3000_power_stats.h" + +/* Keep sync with the same tables in dw3000_core.c */ +const struct dw3000_plen_info _plen_info[] = { + { + .symb = 64, + .pac_symb = 8, + .dw_reg = DW3000_PLEN_64, + .dw_pac_reg = DW3000_PAC8, + }, + { + .symb = 1024, + .pac_symb = 32, + .dw_reg = DW3000_PLEN_1024, + .dw_pac_reg = DW3000_PAC32, + }, + { + .symb = 4096, + .pac_symb = 64, + .dw_reg = DW3000_PLEN_4096, + .dw_pac_reg = DW3000_PAC32, + }, + { + .symb = 32, + .pac_symb = 8, + .dw_reg = DW3000_PLEN_32, + .dw_pac_reg = DW3000_PAC8, + }, + { + .symb = 128, + .pac_symb = 8, + .dw_reg = DW3000_PLEN_128, + .dw_pac_reg = DW3000_PAC8, + }, + { + .symb = 1536, + .pac_symb = 64, + .dw_reg = DW3000_PLEN_1536, + .dw_pac_reg = DW3000_PAC32, + }, + { + .symb = 72, + .pac_symb = 8, + .dw_reg = DW3000_PLEN_72, + .dw_pac_reg = DW3000_PAC8, + }, + { + /* Invalid */ + .symb = 0, + .pac_symb = 0, + .dw_reg = 0, + .dw_pac_reg = 0, + }, + { + .symb = 256, + .pac_symb = 16, + .dw_reg = DW3000_PLEN_256, + .dw_pac_reg = DW3000_PAC16, + }, + { + .symb = 2048, + .pac_symb = 64, + .dw_reg = DW3000_PLEN_2048, + .dw_pac_reg = DW3000_PAC32, + }, + { + /* Invalid */ + .symb = 0, + .pac_symb = 0, + .dw_reg = 0, + .dw_pac_reg = 0, + }, + { + /* Invalid */ + .symb = 0, + .pac_symb = 0, + .dw_reg = 0, + .dw_pac_reg = 0, + }, + { + .symb = 512, + .pac_symb = 16, + .dw_reg = DW3000_PLEN_512, + .dw_pac_reg = DW3000_PAC16, + }, +}; + +/* Chip per symbol for 850kbps (512) and 6.8Mbps (64) */ +const int _chip_per_symbol_info[2] = { 512, 64 }; + +const struct dw3000_prf_info _prf_info[] = { + { + /* Invalid PRF */ + 0, + }, + { + /* 16 MHz */ + .chip_per_symb = 496, + }, + { + /* 64 MHz */ + .chip_per_symb = 508, + }, +}; + +/* Static variable declarations */ +static u64 kunit_boot_time_ns; +static u32 kunit_dtu_time; + +/** + * kunit_get_boottime_ns() - ktime_get_boottime_ns replacement for tests + * + * Returns: boot time value in ns which increment by 1e6 for each call. + */ +static u64 kunit_get_boottime_ns(void) +{ + kunit_boot_time_ns += 1000000; + return kunit_boot_time_ns; +} + +/** + * kunit_dw3000_get_dtu_time() - dw3000_get_dtu_time kunit wrapper + * @dw: the DW device + * + * Return: The current simulated DTU time. + */ +u32 kunit_dw3000_get_dtu_time(struct dw3000 *dw) +{ + return kunit_dtu_time; +} + +/* Define the test cases. */ + +static void dw3000_ktime_to_dtu_test_basic(struct kunit *test) +{ + struct dw3000 *dw = kunit_kzalloc(test, sizeof(*dw), GFP_KERNEL); + /* Ensure allocation succeeded. */ + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dw); + /* Tests with time_zero_ns == 0 */ + KUNIT_EXPECT_EQ(test, 0u, dw3000_ktime_to_dtu(dw, 0)); + KUNIT_EXPECT_EQ(test, 15u, dw3000_ktime_to_dtu(dw, 1000)); + KUNIT_EXPECT_EQ(test, 156u, dw3000_ktime_to_dtu(dw, 10000)); + KUNIT_EXPECT_EQ(test, 312u, dw3000_ktime_to_dtu(dw, 20000)); + /* Tests with time_zero_ns == 1000000 */ + dw->time_zero_ns = 1000000; + KUNIT_EXPECT_EQ(test, 0u, dw3000_ktime_to_dtu(dw, 1000000)); + KUNIT_EXPECT_EQ(test, 15u, dw3000_ktime_to_dtu(dw, 1001000)); + KUNIT_EXPECT_EQ(test, (u32)-15600, dw3000_ktime_to_dtu(dw, 0)); + KUNIT_EXPECT_EQ(test, (u32)-15584, dw3000_ktime_to_dtu(dw, 1000)); +} + +static void dw3000_dtu_to_ktime_test_basic(struct kunit *test) +{ + struct dw3000 *dw = kunit_kzalloc(test, sizeof(*dw), GFP_KERNEL); + /* Ensure allocation succeeded. */ + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dw); + /* Tests with time_zero_ns == 0 */ + KUNIT_EXPECT_EQ(test, 0ll, dw3000_dtu_to_ktime(dw, 0)); + KUNIT_EXPECT_EQ(test, 64102ll, dw3000_dtu_to_ktime(dw, 1000)); + /* Tests with time_zero_ns == 1000000 */ + dw->time_zero_ns = 1000000; + KUNIT_EXPECT_EQ(test, 1000000ll, dw3000_dtu_to_ktime(dw, 0)); + KUNIT_EXPECT_EQ(test, 1064102ll, dw3000_dtu_to_ktime(dw, 1000)); +} + +static void dw3000_dtu_to_sys_time_test_basic(struct kunit *test) +{ + struct dw3000 *dw = kunit_kzalloc(test, sizeof(*dw), GFP_KERNEL); + /* Ensure allocation succeeded. */ + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dw); + /* Tests with dtu_sync == 0 & sys_time_sync == 0 */ + KUNIT_EXPECT_EQ(test, 0u, dw3000_dtu_to_sys_time(dw, 0)); + KUNIT_EXPECT_EQ(test, 1u << 4, dw3000_dtu_to_sys_time(dw, 1)); + /* Tests with dtu_sync == 10000 & sys_time_sync == 0 */ + dw->dtu_sync = 10000; + KUNIT_EXPECT_EQ(test, 0u, dw3000_dtu_to_sys_time(dw, 10000)); + KUNIT_EXPECT_EQ(test, 1u << 4, dw3000_dtu_to_sys_time(dw, 10001)); + /* Tests with dtu_sync == 0 & sys_time_sync == 0 */ + dw->sys_time_sync = 1000000; + KUNIT_EXPECT_EQ(test, 1000000u, dw3000_dtu_to_sys_time(dw, 10000)); + KUNIT_EXPECT_EQ(test, 1000016u, dw3000_dtu_to_sys_time(dw, 10001)); +} + +static void dw3000_sys_time_to_dtu_test_basic(struct kunit *test) +{ + u32 dtu_near = 0; + struct dw3000 *dw = kunit_kzalloc(test, sizeof(*dw), GFP_KERNEL); + /* Ensure allocation succeeded. */ + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dw); + /* Tests with dtu_sync == 0 & sys_time_sync == 0 */ + KUNIT_EXPECT_EQ(test, 0u, dw3000_sys_time_to_dtu(dw, 0, dtu_near)); + KUNIT_EXPECT_EQ(test, 1u, dw3000_sys_time_to_dtu(dw, 1 << 4, dtu_near)); + /* Tests with dtu_near == 10000 */ + dtu_near = 10000; + KUNIT_EXPECT_EQ(test, 1005u, + dw3000_sys_time_to_dtu(dw, 1005 << 4, dtu_near)); + /* Tests with dtu_sync == 1000000/16 & sys_time_sync == 1000000 */ + dw->sys_time_sync = 1000000; + dw->dtu_sync = 1000000 >> 4; + KUNIT_EXPECT_EQ(test, 10000u, + dw3000_sys_time_to_dtu(dw, 10000 << 4, dtu_near)); + /* Tests with real values from traces + wakeup: dtu_near: 0x773a2f1, dtu_sync: 0x852a9ff, sys_time_sync: 0x293a6 + rx_enable: timestamp_dtu=0x085776e8 + read_rx_timestamp: value 1336512593 -> 0x4fa99051 -> sys_time 0x13ea64 + get_rx_frame: timestamp_dtu=0x185776e6 timestamp_rctu=0xffffffffc3fee418 + */ + dtu_near = 0x773a2f1u; + dw->dtu_sync = 0x852a9ffu; + dw->sys_time_sync = 0x293a6u; + KUNIT_EXPECT_EQ(test, 0x853bf6au, + dw3000_sys_time_to_dtu(dw, 0x13ea64u, dtu_near)); +} + +static void dw3000_sys_time_rctu_to_dtu_test_basic(struct kunit *test) +{ + struct dw3000 *dw = kunit_kzalloc(test, sizeof(*dw), GFP_KERNEL); + + /* Ensure allocation succeeded. */ + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dw); + + /* Tests with dtu_sync == 0, sys_time_sync == 0 and dtu_near == 0. + * Set kunit_dtu_time to DW3000_DTU_FREQ to get dtu_near == 0 in + * dw3000_sys_time_rctu_to_dtu(). */ + kunit_dtu_time = DW3000_DTU_FREQ; + KUNIT_EXPECT_EQ(test, 0u, dw3000_sys_time_rctu_to_dtu(dw, 0)); + KUNIT_EXPECT_EQ(test, 1u, + dw3000_sys_time_rctu_to_dtu(dw, DW3000_RCTU_PER_DTU)); + + /* Tests with dtu_near == 10000. + * Set kunit_dtu_time to DW3000_DTU_FREQ + 10000 to get dtu_near == 10000 + * in dw3000_sys_time_rctu_to_dtu(). */ + kunit_dtu_time = DW3000_DTU_FREQ + 10000; + KUNIT_EXPECT_EQ( + test, 1005u, + dw3000_sys_time_rctu_to_dtu(dw, 1005 * DW3000_RCTU_PER_DTU)); + + /* Tests with dtu_sync == 1000000/16 & sys_time_sync == 1000000 */ + dw->sys_time_sync = 1000000; + dw->dtu_sync = 1000000 >> 4; + KUNIT_EXPECT_EQ( + test, 10000u, + dw3000_sys_time_rctu_to_dtu(dw, 10000 * DW3000_RCTU_PER_DTU)); + + /* Tests with real values from traces: + * timestamp_rctu: 63852263355 + * dtu_sync: 13025 + * sys_time_sync: 5414 + * dtu_near: 4349 + * dw3000_sys_time_rctu_to_dtu() return: 15601618 + * + * Set kunit_dtu_time to DW3000_DTU_FREQ + 4349 to get dtu_near == 4349 + * in dw3000_sys_time_rctu_to_dtu(). */ + kunit_dtu_time = DW3000_DTU_FREQ + 4349; + dw->dtu_sync = 13025; + dw->sys_time_sync = 5414; + KUNIT_EXPECT_EQ(test, 15601618u, + dw3000_sys_time_rctu_to_dtu(dw, 63852263355)); +} + +static void power_stats_test_setup(struct dw3000 *dw) +{ + struct dw3000_power *pwr = &dw->power; + + kunit_boot_time_ns = 0; + pwr->stats[DW3000_PWR_OFF].count = 1; + pwr->start_time = ktime_get_boottime_ns(); + dw->config.txPreambLength = DW3000_PLEN_64; + dw->config.txCode = 9; + dw->config.sfdType = DW3000_SFD_TYPE_4Z; + dw3000_update_timings(dw); +} + +static void dw3000_power_stats_test_basic(struct kunit *test) +{ + struct mcps802154_llhw *llhw = + kunit_kzalloc(test, sizeof(*llhw), GFP_KERNEL); + struct dw3000 *dw = kunit_kzalloc(test, sizeof(*dw), GFP_KERNEL); + struct dw3000_power *pwr = &dw->power; + u64 incr = 1000000; + /* Ensure allocation succeeded and good state. */ + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, llhw); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dw); + if (!dw || !llhw) + return; + dw->llhw = llhw; + power_stats_test_setup(dw); + KUNIT_EXPECT_EQ(test, DW3000_PWR_OFF, pwr->cur_state); + KUNIT_EXPECT_EQ(test, incr, pwr->start_time); + + /* Simple changes + * - RUN - DEEPSLEEP - RUN - DEEPSLEEP - RUN - OFF - RUN - OFF + * - cur_state change to new state immediately + * - stats[X].count change immediately + * - stats[X].dur change when another new state Y apply + * - each call update time by 1e6 increment thank to ktime_get_boottime_ns() + * replacement function. + */ + dw3000_power_stats(dw, DW3000_PWR_RUN, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_RUN, pwr->cur_state); + KUNIT_EXPECT_EQ(test, incr * 1, pwr->stats[DW3000_PWR_OFF].dur); + dw3000_power_stats(dw, DW3000_PWR_DEEPSLEEP, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_DEEPSLEEP, pwr->cur_state); + dw3000_power_stats(dw, DW3000_PWR_RUN, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_RUN, pwr->cur_state); + dw3000_power_stats(dw, DW3000_PWR_DEEPSLEEP, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_DEEPSLEEP, pwr->cur_state); + dw3000_power_stats(dw, DW3000_PWR_RUN, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_RUN, pwr->cur_state); + dw3000_power_stats(dw, DW3000_PWR_OFF, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_OFF, pwr->cur_state); + dw3000_power_stats(dw, DW3000_PWR_RUN, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_RUN, pwr->cur_state); + KUNIT_EXPECT_EQ(test, incr * 2, pwr->stats[DW3000_PWR_OFF].dur); + dw3000_power_stats(dw, DW3000_PWR_OFF, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_OFF, pwr->cur_state); + dw3000_power_stats(dw, DW3000_PWR_OFF, 0); + KUNIT_EXPECT_EQ(test, incr * 3, pwr->stats[DW3000_PWR_OFF].dur); + dw3000_power_stats(dw, DW3000_PWR_OFF, 0); + /* Final state checks */ + KUNIT_EXPECT_EQ(test, 3ull, pwr->stats[DW3000_PWR_OFF].count); + KUNIT_EXPECT_EQ(test, 2ull, pwr->stats[DW3000_PWR_DEEPSLEEP].count); + KUNIT_EXPECT_EQ(test, 4ull, pwr->stats[DW3000_PWR_RUN].count); + KUNIT_EXPECT_EQ(test, incr * 4, pwr->stats[DW3000_PWR_OFF].dur); + KUNIT_EXPECT_EQ(test, incr * 2, pwr->stats[DW3000_PWR_DEEPSLEEP].dur); + KUNIT_EXPECT_EQ(test, incr * 4, pwr->stats[DW3000_PWR_RUN].dur); +} + +static void dw3000_power_stats_test_tx(struct kunit *test) +{ + struct mcps802154_llhw *llhw = + kunit_kzalloc(test, sizeof(*llhw), GFP_KERNEL); + struct dw3000 *dw = kunit_kzalloc(test, sizeof(*dw), GFP_KERNEL); + struct dw3000_power *pwr = &dw->power; + u64 incr = 1000000; + u64 txdur = 0; + + /* Ensure allocation succeeded and good state. */ + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, llhw); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dw); + if (!dw || !llhw) + return; + dw->llhw = llhw; + power_stats_test_setup(dw); + KUNIT_EXPECT_EQ(test, DW3000_PWR_OFF, pwr->cur_state); + KUNIT_EXPECT_EQ(test, incr, pwr->start_time); + + /* TX case + * - OFF - RUN - TX - IDLE - TX - IDLE - TX - IDLE - TX - IDLE - OFF + * - stats[TX].dur is in DTU, which is a 15.6MHz clock + * - since purpose is to test d3000_power_stats() and not + * dw3000_frame_duration_dtu(), tx_adjust is summed to test + * resulting stats[TX].dur. + */ + KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_TX].count); + KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_TX].dur); + dw3000_power_stats(dw, DW3000_PWR_RUN, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_RUN, pwr->cur_state); + KUNIT_EXPECT_EQ(test, 1ull, pwr->stats[DW3000_PWR_RUN].count); + + dw3000_power_stats(dw, DW3000_PWR_TX, 0); + txdur += pwr->tx_adjust; /* sum calculated frame duration */ + KUNIT_EXPECT_EQ(test, DW3000_PWR_TX, pwr->cur_state); + KUNIT_EXPECT_EQ(test, 1ull, pwr->stats[DW3000_PWR_TX].count); + dw3000_power_stats(dw, DW3000_PWR_IDLE, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_IDLE, pwr->cur_state); + KUNIT_EXPECT_EQ(test, txdur, pwr->stats[DW3000_PWR_TX].dur); + + dw3000_power_stats(dw, DW3000_PWR_TX, 16); + txdur += pwr->tx_adjust; /* sum calculated frame duration */ + KUNIT_EXPECT_EQ(test, DW3000_PWR_TX, pwr->cur_state); + KUNIT_EXPECT_EQ(test, 2ull, pwr->stats[DW3000_PWR_TX].count); + dw3000_power_stats(dw, DW3000_PWR_IDLE, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_IDLE, pwr->cur_state); + KUNIT_EXPECT_EQ(test, txdur, pwr->stats[DW3000_PWR_TX].dur); + + dw3000_power_stats(dw, DW3000_PWR_TX, 32); + txdur += pwr->tx_adjust; /* sum calculated frame duration */ + KUNIT_EXPECT_EQ(test, DW3000_PWR_TX, pwr->cur_state); + KUNIT_EXPECT_EQ(test, 3ull, pwr->stats[DW3000_PWR_TX].count); + dw3000_power_stats(dw, DW3000_PWR_IDLE, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_IDLE, pwr->cur_state); + KUNIT_EXPECT_EQ(test, txdur, pwr->stats[DW3000_PWR_TX].dur); + + dw3000_power_stats(dw, DW3000_PWR_TX, 127); + txdur += pwr->tx_adjust; /* sum calculated frame duration */ + KUNIT_EXPECT_EQ(test, DW3000_PWR_TX, pwr->cur_state); + KUNIT_EXPECT_EQ(test, 4ull, pwr->stats[DW3000_PWR_TX].count); + dw3000_power_stats(dw, DW3000_PWR_IDLE, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_IDLE, pwr->cur_state); + KUNIT_EXPECT_EQ(test, txdur, pwr->stats[DW3000_PWR_TX].dur); + + dw3000_power_stats(dw, DW3000_PWR_OFF, 0); + dw3000_power_stats(dw, DW3000_PWR_OFF, 0); + /* Final state checks */ + KUNIT_EXPECT_EQ(test, DW3000_PWR_OFF, pwr->cur_state); + KUNIT_EXPECT_EQ(test, 1ull, pwr->stats[DW3000_PWR_RUN].count); + KUNIT_EXPECT_EQ(test, 5ull, pwr->stats[DW3000_PWR_IDLE].count); + KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_RX].count); + KUNIT_EXPECT_EQ(test, 2ull, pwr->stats[DW3000_PWR_OFF].count); + KUNIT_EXPECT_EQ(test, 2 * incr, pwr->stats[DW3000_PWR_OFF].dur); + KUNIT_EXPECT_EQ(test, 9 * incr, pwr->stats[DW3000_PWR_RUN].dur); + KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_IDLE].dur); + KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_RX].dur); +} + +static void dw3000_power_stats_test_rx(struct kunit *test) +{ + struct mcps802154_llhw *llhw = + kunit_kzalloc(test, sizeof(*llhw), GFP_KERNEL); + struct dw3000 *dw = kunit_kzalloc(test, sizeof(*dw), GFP_KERNEL); + struct dw3000_power *pwr = &dw->power; + u64 incr = 1000000; /* in ns first */ + u64 rxdur = 0; + + /* Ensure allocation succeeded and good state. */ + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, llhw); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dw); + if (!dw || !llhw) + return; + dw->llhw = llhw; + power_stats_test_setup(dw); + KUNIT_EXPECT_EQ(test, DW3000_PWR_OFF, pwr->cur_state); + KUNIT_EXPECT_EQ(test, incr, pwr->start_time); + + /* RX case + * - OFF - RUN - RX - IDLE - RX - IDLE - RX - IDLE - RX - IDLE - OFF + * - stats[RX].dur is in DTU, which is a 15.6MHz clock + * - we check here stats[RX].dur is well updated according give DTU dates + */ + incr = 15600; /* those checks are in DTU */ + KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_RX].count); + KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_RX].dur); + dw3000_power_stats(dw, DW3000_PWR_RUN, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_RUN, pwr->cur_state); + KUNIT_EXPECT_EQ(test, 1ull, pwr->stats[DW3000_PWR_RUN].count); + + dw3000_power_stats(dw, DW3000_PWR_RX, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_RX, pwr->cur_state); + KUNIT_EXPECT_EQ(test, 1ull, pwr->stats[DW3000_PWR_RX].count); + dw3000_power_stats(dw, DW3000_PWR_IDLE, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_IDLE, pwr->cur_state); + KUNIT_EXPECT_EQ(test, incr, pwr->stats[DW3000_PWR_RX].dur); + + dw3000_power_stats(dw, DW3000_PWR_RX, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_RX, pwr->cur_state); + KUNIT_EXPECT_EQ(test, 2ull, pwr->stats[DW3000_PWR_RX].count); + dw3000_power_stats(dw, DW3000_PWR_IDLE, 0); + KUNIT_EXPECT_EQ(test, DW3000_PWR_IDLE, pwr->cur_state); + KUNIT_EXPECT_EQ(test, 2 * incr, pwr->stats[DW3000_PWR_RX].dur); + + rxdur = pwr->stats[DW3000_PWR_RX].dur; + dw3000_power_stats(dw, DW3000_PWR_RX, (int)0x10000); + KUNIT_EXPECT_EQ(test, DW3000_PWR_RX, pwr->cur_state); + KUNIT_EXPECT_EQ(test, 3ull, pwr->stats[DW3000_PWR_RX].count); + dw3000_power_stats(dw, DW3000_PWR_IDLE, (int)0x20000u); + KUNIT_EXPECT_EQ(test, DW3000_PWR_IDLE, pwr->cur_state); + KUNIT_EXPECT_EQ(test, rxdur + 0x10000ull, + pwr->stats[DW3000_PWR_RX].dur); + + rxdur = pwr->stats[DW3000_PWR_RX].dur; + dw3000_power_stats(dw, DW3000_PWR_RX, (int)-128); + KUNIT_EXPECT_EQ(test, DW3000_PWR_RX, pwr->cur_state); + KUNIT_EXPECT_EQ(test, 4ull, pwr->stats[DW3000_PWR_RX].count); + dw3000_power_stats(dw, DW3000_PWR_IDLE, (int)128); + KUNIT_EXPECT_EQ(test, DW3000_PWR_IDLE, pwr->cur_state); + KUNIT_EXPECT_EQ(test, rxdur + 256ull, pwr->stats[DW3000_PWR_RX].dur); + + dw3000_power_stats(dw, DW3000_PWR_OFF, 0); + dw3000_power_stats(dw, DW3000_PWR_OFF, 0); + /* Final state checks */ + incr = 1000000; /* those checks are in ns */ + KUNIT_EXPECT_EQ(test, DW3000_PWR_OFF, pwr->cur_state); + KUNIT_EXPECT_EQ(test, 1ull, pwr->stats[DW3000_PWR_RUN].count); + KUNIT_EXPECT_EQ(test, 5ull, pwr->stats[DW3000_PWR_IDLE].count); + KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_TX].count); + KUNIT_EXPECT_EQ(test, 2ull, pwr->stats[DW3000_PWR_OFF].count); + KUNIT_EXPECT_EQ(test, 2 * incr, pwr->stats[DW3000_PWR_OFF].dur); + KUNIT_EXPECT_EQ(test, 9 * incr, pwr->stats[DW3000_PWR_RUN].dur); + KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_IDLE].dur); + KUNIT_EXPECT_EQ(test, 0ull, pwr->stats[DW3000_PWR_TX].dur); +} + +static struct kunit_case dw3000_core_test_cases[] = { + KUNIT_CASE(dw3000_ktime_to_dtu_test_basic), + KUNIT_CASE(dw3000_dtu_to_ktime_test_basic), + KUNIT_CASE(dw3000_dtu_to_sys_time_test_basic), + KUNIT_CASE(dw3000_sys_time_to_dtu_test_basic), + KUNIT_CASE(dw3000_sys_time_rctu_to_dtu_test_basic), + KUNIT_CASE(dw3000_power_stats_test_basic), + KUNIT_CASE(dw3000_power_stats_test_tx), + KUNIT_CASE(dw3000_power_stats_test_rx), + {} +}; + +static struct kunit_suite dw3000_core_test_suite = { + .name = "dw3000-core", + .test_cases = dw3000_core_test_cases, +}; +kunit_test_suite(dw3000_core_test_suite); + +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/net/ieee802154/dw3000_debugfs.c b/kernel/drivers/net/ieee802154/dw3000_debugfs.c new file mode 100644 index 0000000..33b88c1 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_debugfs.c @@ -0,0 +1,775 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <linux/module.h> +#include <linux/debugfs.h> +#include <linux/string.h> + +#include "dw3000.h" +#include "dw3000_core.h" +#include "dw3000_debugfs.h" +#include "dw3000_chip.h" +#include "dw3000_cir.h" + +#define MAX_CHARS_DISPLAY_DEC_UINT32 10 +#define MAX_CHARS_DISPLAY_DEC_INT32 11 +#define MAX_CHARS_DISPLAY_HEX_UINT32 8 + +#define dw3000_dbgfs_regop_write(sz, _dw, _addr, _mask, data) \ + (_mask) ? dw3000_reg_modify##sz(_dw, _addr, 0, ~_mask, data) : \ + dw3000_reg_write##sz(_dw, _addr, 0, data) + +/** + * dw3000_dbgfs_power() - Chip agnostic function to handle poweron/off + * @filp: debugfs file pointer associated to the virtual register + * @write: false means dump current power status, true means: modify it + * @buffer: in case of powerstatus modification, 0 means poweroff, 1 means + * poweron. Other values are rejected + * @size: buffer size + * @ppos: offset in opened file + * + * Return: a negative error code or the size written or readed from buffer + */ +static int dw3000_dbgfs_power(struct file *filp, bool write, void *buffer, + size_t size, loff_t *ppos) +{ + struct dw3000_debugfs_file *dbgfs_file = + (struct dw3000_debugfs_file *)filp->private_data; + struct dw3000_chip_register_priv *crp = &dbgfs_file->chip_reg_priv; + struct dw3000 *dw = crp->dw; + char cbuf[3]; + int rc; + u8 on; + + if (*ppos > 0) + return 0; + + if (write) { + if (kstrtou8_from_user(buffer, size, 0, &on)) { + dev_err(dw->dev, "no valid value provided\n"); + return -EINVAL; + } + + if (on == 1) { + rc = dw3000_poweron(dw); + if (rc) + dev_warn(dw->dev, "poweron returns %d\n", rc); + } else if (on == 0) { + rc = dw3000_poweroff(dw); + if (rc) + dev_warn(dw->dev, "poweroff returns %d\n", rc); + } else { + dev_err(dw->dev, "%u value not handled\n", on); + return -EINVAL; + } + if (!rc) + rc = size; + } else { + on = dw->current_operational_state != DW3000_OP_STATE_OFF; + rc = scnprintf(cbuf, 3, "%u\n", on); + if (copy_to_user(buffer, cbuf, rc)) { + dev_err(dw->dev, "impossible to copy data to userland"); + return -EFAULT; + } + } + *ppos += rc; + return rc; +} + +/** + * dw3000_dbgfs_cir_data() - Consummer function to dump CIR data to userspace + * @filp: debugfs file pointer associated to the virtual register + * @write: false means dump current power status, true means: modify it + * @buffer: in case of powerstatus modification, 0 means poweroff, 1 means + * poweron. Other values are rejected + * @size: buffer size + * @ppos: offset in opened file + * + * Return: a negative error code or the size written or readed from buffer + */ + +static int dw3000_dbgfs_cir_data(struct file *filp, bool write, void *buffer, + size_t size, loff_t *ppos) +{ + struct dw3000_debugfs_file *dbgfs_file = filp->private_data; + struct dw3000_chip_register_priv *crp = &dbgfs_file->chip_reg_priv; + struct dw3000 *dw = crp->dw; + struct dw3000_cir_data *cir = dw->cir_data; + size_t bufsz; + + /* Try to read cir data but no memory have been allocated for */ + if (unlikely(!cir)) + return 0; + + /* size is memory footprint of dw->cir_data without header members */ + bufsz = sizeof(*cir) + + sizeof(struct dw3000_cir_record) * (cir->count - 1) - + offsetof(struct dw3000_cir_data, count); + + /* Reset before waiting. Allowing memory change detection */ + dw->cir_data_changed = false; + + /* Wait for producer */ + if (wait_for_completion_interruptible(&cir->complete)) { + return -ERESTARTSYS; + } + + /* During wait, cir_data can be reallocated. If there is any change + * current caller have to be closed gently */ + smp_rmb(); + if (dw->cir_data_changed) + return 0; + + /* Wait for release of shared memory */ + if (mutex_lock_interruptible(&cir->mutex)) { + return -EINTR; + } + + if (copy_to_user(buffer, &cir->count, bufsz)) { + mutex_unlock(&cir->mutex); + dev_err(dw->dev, "impossible to copy data to userland"); + return -EFAULT; + } + + mutex_unlock(&cir->mutex); + /* Output is 16 bytes aligned */ + bufsz = ALIGN(bufsz, 16); + *ppos += bufsz; + + return bufsz; +} + +/** + * dw3000_dbgfs_cir_config() - Runtime modification of record count and filter + * @filp: debugfs file pointer associated to the virtual register + * @write: false means dump current power status, true means: modify it + * @buffer: in case of powerstatus modification, 0 means poweroff, 1 means + * poweron. Other values are rejected + * @size: buffer size + * @ppos: offset in opened file + * + * Return: a negative error code or the size written or readed from buffer + */ +static int dw3000_dbgfs_cir_config(struct file *filp, bool write, void *buffer, + size_t size, loff_t *ppos) +{ + struct dw3000_debugfs_file *dbgfs_file = + (struct dw3000_debugfs_file *)filp->private_data; + struct dw3000_chip_register_priv *crp = &dbgfs_file->chip_reg_priv; + struct dw3000 *dw = crp->dw; + struct dw3000_cir_data *cir = dw->cir_data; + static const char fmt[] = "count %u filter 0x%x offset %d\n"; + /* fit format string with max values for each (U)32 and sign */ + char cbuf[sizeof(fmt) + + MAX_CHARS_DISPLAY_DEC_UINT32 + + MAX_CHARS_DISPLAY_DEC_UINT32 + + MAX_CHARS_DISPLAY_HEX_UINT32]; + unsigned int newcount; + int newoff; + u32 newfilter; + int r = 0; + + if (unlikely(!cir)) + return 0; + + if (*ppos > 0) + return 0; + + if (!write) { + r = scnprintf(cbuf, sizeof(cbuf), fmt, cir->count, cir->filter, + cir->offset); + if (copy_to_user(buffer, cbuf, r)) { + dev_err(dw->dev, "impossible to copy data to userland"); + *ppos += size; + return -EFAULT; + } + *ppos += r; + return r; + } + + if (size >= sizeof(cbuf)) + return -EINVAL; + if (copy_from_user(cbuf, buffer, size)) { + dev_err(dw->dev, "copy failed\n"); + return -EFAULT; + } + + r = sscanf(cbuf, fmt, &newcount, &newfilter, &newoff); + if (r != 3) { + dev_err(dw->dev, "input format error (%d)\n", r); + return -EINVAL; + } + + if (cir->count != newcount && newcount >= 1) { + r = dw3000_cir_data_alloc_count(dw, newcount); + if (r < 0) + return -ENOMEM; + } else if (newcount < 1) { + return -EINVAL; + } + + dw->cir_data->filter = newfilter; + dw->cir_data->offset = newoff; + + *ppos += size; + return size; +} + +static const struct dw3000_chip_register virtual_registers[] = { + { "power", 0x0, 0x0, 0x0, DW3000_CHIPREG_PERM, dw3000_dbgfs_power }, + { "cir_data", 0x0, 0x0, 0x0, + DW3000_CHIPREG_RO | DW3000_CHIPREG_OPENONCE, dw3000_dbgfs_cir_data }, + { "cir_config", 0x0, 0x0, 0x0, DW3000_CHIPREG_PERM, + dw3000_dbgfs_cir_config }, +}; + +/** struct do_reg_xfer_params - parameters for spi register access + * @reg: pointer to the register to reach + * @operation: set operation to do on reg according to enum dw3000_reg_operation + * @value: pointer on the data for write and modify operations + */ +struct do_reg_xfer_params { + const struct dw3000_chip_register *reg; + int operation; + void *value; +}; + +static int do_dump_xfer(struct dw3000 *dw, const void *in, void *out) +{ + const struct dw3000_debugfs_dump_private *priv = in; + int rc; + + rc = dw3000_xfer(dw, priv->current_reg->address << 16, 0, + priv->current_reg->size, out, DW3000_SPI_RD_BIT); + + return rc; +} + +static int do_reg_write(struct dw3000 *dw, + const struct dw3000_chip_register *reg, u64 *value) +{ + u8 shift = (reg->mask) ? ffs(reg->mask) - 1 : 0; + int rc; + + *value <<= shift; + + switch (reg->size) { + case 1: + rc = dw3000_dbgfs_regop_write(8, dw, reg->address, reg->mask, + *value); + break; + case 2: + rc = dw3000_dbgfs_regop_write(16, dw, reg->address, reg->mask, + *value); + break; + case 4: + rc = dw3000_dbgfs_regop_write(32, dw, reg->address, reg->mask, + *value); + break; + default: + if (unlikely(reg->mask)) { + dev_warn( + dw->dev, + "mask defined but modify not supported for this size\n"); + rc = -ENOSYS; + break; + } + rc = dw3000_reg_write_fast(dw, reg->address, 0, reg->size, + value, DW3000_SPI_WR_BIT); + } + return rc; +} + +static int do_reg_read(struct dw3000 *dw, + const struct dw3000_chip_register *reg, void *value) +{ + u8 shift = (reg->mask) ? ffs(reg->mask) - 1 : 0; + int rc; + + rc = dw3000_reg_read_fast(dw, reg->address, 0, reg->size, value); + + if (reg->mask && !rc) { + *(u32 *)value &= reg->mask; + *(u32 *)value >>= shift; + } + + return rc; +} + +static int do_reg_xfer(struct dw3000 *dw, const void *in, void *out) +{ + const struct do_reg_xfer_params *xfer = in; + int rc = 0; + + if (xfer->operation == DW_REG_READ) + rc = do_reg_read(dw, xfer->reg, out); + else + rc = do_reg_write(dw, xfer->reg, (u64 *)xfer->value); + + return rc; +} + +static int dw3000_file_release(struct inode *inode, struct file *file) +{ + if (file->private_data) { + kfree(file->private_data); + file->private_data = NULL; + } + return 0; +} + +static int dw3000_dump_open(struct inode *inode, struct file *file) +{ + struct dw3000_debugfs_dump_private *dev_priv; + struct dw3000 *dw; + size_t sz = sizeof(struct dw3000_debugfs_dump_private); + + if (unlikely(!inode->i_private)) + return -ENXIO; + + file->private_data = kzalloc(sz, GFP_KERNEL); + if (!file->private_data) { + dev_priv = + (struct dw3000_debugfs_dump_private *)inode->i_private; + dw = dev_priv->dbgfile.chip_reg_priv.dw; + dev_err(dw->dev, "Allocation failed. Cannot open file.\n"); + return -ENOMEM; + } + memcpy(file->private_data, inode->i_private, sz); + return 0; +} + +static ssize_t format_reg_output(struct dw3000 *dw, void *binbuf, + ssize_t reg_sz, char __user *userbuf) +{ + char cbuf[36]; /* 0x + 16 bytes + \n \0 */ + ssize_t r = 0; + + if (reg_sz > 8) { + if (copy_to_user(userbuf, binbuf, reg_sz)) { + dev_err(dw->dev, "copy failed\n"); + return -EFAULT; + } + return reg_sz; + } else { + r = scnprintf(cbuf, 36, "0x%llx\n", *(u64 *)binbuf); + if (copy_to_user(userbuf, cbuf, r)) { + dev_err(dw->dev, "impossible to copy data to userland"); + return -EFAULT; + } + } + return r; +} + +static ssize_t format_dump_output(struct dw3000 *dw, void *buffer, + char *reg_name, ssize_t buffer_sz, + char __user *userbuf) +{ + char *cbuf; + char *cbufpos; + size_t cbuflen; + char *buffer_idx; + ssize_t line_len, remain; + int r = 0; + size_t bufpos = 0; + + /* Buffer for all registers of the fileid + * Size is computed for a byte display, this is more than + * required for word (4bytes) thus we keep this formula to be + * able to securely print data whatever the format selected */ + cbuflen = buffer_sz * 3 + 14 + 1; /* N regs max ; reg name */ + cbuf = (char *)kzalloc(cbuflen * sizeof(char), GFP_KERNEL); + if (!cbuf) { + dev_err(dw->dev, "dbgfs: impossible to alloc buffers mem\n"); + return -ENOMEM; + } + + /* Output generation */ + buffer_idx = buffer; + bufpos += scnprintf(cbuf, cbuflen, "%.12s:\n", reg_name); + + remain = cbuflen - bufpos; + cbufpos = cbuf + bufpos; + + while (buffer_sz) { + line_len = hex_dump_to_buffer(buffer_idx, buffer_sz, 16, 4, + cbufpos, remain, false); + if (line_len >= remain) { + dev_err(dw->dev, "buffer too small"); + break; + } + cbufpos[line_len++] = '\n'; + cbufpos += line_len; + remain -= line_len; + buffer_idx += 16; + buffer_sz -= min(16, (int)buffer_sz); + buffer_sz = (buffer_sz + 3) & ~3; + }; + + r = cbufpos - cbuf; + if (copy_to_user(userbuf, cbuf, r)) { + dev_err(dw->dev, "impossible to copy data to userland"); + r = -EFAULT; + goto format_dump_output_err; + } + +format_dump_output_err: + if (cbuf) + kfree(cbuf); + return r; +} + +static ssize_t dw3000_dump_read(struct file *filp, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct dw3000_debugfs_dump_private *priv = filp->private_data; + struct dw3000_stm_command cmd = { do_dump_xfer, priv, NULL }; + struct dw3000_chip_register_priv *crp = &priv->dbgfile.chip_reg_priv; + struct dw3000 *dw; + void *buffer; + int r = 0; + + dw = priv->dbgfile.chip_reg_priv.dw; + + /* A runlevel under DW3000_OP_STATE_IDLE_RC doesn't allow SPI xfer */ + if (dw->current_operational_state < DW3000_OP_STATE_IDLE_RC) { + dev_err(dw->dev, + "unable to dump registers: device not ready\n"); + return -EIO; + } + + /* Reset feature */ + if (*ppos == 0) + priv->current_reg = crp->reg; + + /* If table's end reached reset current reg */ + if ((priv->current_reg == (crp->reg + crp->count)) || + !(priv->current_reg->flags & DW3000_CHIPREG_DUMP)) + return 0; + + /* Buffer for all registers of the fileid */ + buffer = kzalloc(priv->current_reg->size, GFP_KERNEL); + if (!buffer) { + dev_err(dw->dev, "dbgfs: impossible to alloc buffers mem\n"); + r = -ENOMEM; + goto dw3000_dump_read_err; + } + + /* Retrieve all DW's registers content */ + cmd.out = (void *)buffer; + r = dw3000_enqueue_generic(dw, &cmd); + if (r) { + dev_err(dw->dev, "Fail to read registers in fileId %d (%d)\n", + priv->current_reg->address, r); + goto dw3000_dump_read_err; + } + + /* Output generation */ + r = format_dump_output(dw, buffer, (char *)priv->current_reg->name, + priv->current_reg->size, userbuf); + + /* Update current_reg: jump to the next file_id */ + priv->current_reg++; + + /* Update position for sequential read */ + *ppos = *ppos + r; + +dw3000_dump_read_err: + if (buffer) + kfree(buffer); + return r; +} + +static ssize_t dw3000_dbgfs_reg_op(struct file *filp, char __user *userbuf, + size_t count, loff_t *ppos, bool write) +{ + struct dw3000_debugfs_file *dbgfs_file = + (struct dw3000_debugfs_file *)filp->private_data; + struct dw3000_chip_register_priv *crp = &dbgfs_file->chip_reg_priv; + struct do_reg_xfer_params xfer; + struct dw3000 *dw = crp->dw; + unsigned long long binbuf[DW3000_REG_MAX_SZ / sizeof(long long)]; + struct dw3000_stm_command cmd = { do_reg_xfer, &xfer, binbuf }; + const struct dw3000_chip_register *reg = crp->reg; + ssize_t outsize, buflen; + ssize_t r = 0; + xfer.operation = DW_REG_READ; + memset(binbuf, 0, DW3000_REG_MAX_SZ); + + /* A runlevel under DW3000_OP_STATE_IDLE_RC doesn't allow SPI xfer + * except if permanent access is specially granted to this register */ + if (dw->current_operational_state < DW3000_OP_STATE_IDLE_RC && + !(reg->flags & DW3000_CHIPREG_PERM)) { + dev_err(dw->dev, + "unable to reach registers: device not ready\n"); + return -EIO; + } + + if (reg->callback) + return reg->callback(filp, write, userbuf, count, ppos); + + if (*ppos > 0) + return 0; + + if (write) { + /* Check write-protection of the register */ + if ((reg->flags & DW3000_CHIPREG_WP) && dw3000_is_active(dw)) { + dev_err(dw->dev, + "register %s is protected. Write refused " + "because device is active\n", + reg->name); + r = -EACCES; + goto dw3000_reg_op_err; + } + + /* Get value from userspace */ + if (reg->size <= 8) { + if (kstrtoull_from_user(userbuf, count, 0, binbuf)) { + r = -EINVAL; + dev_err(dw->dev, "%s is not valid value\n", + userbuf); + goto dw3000_reg_op_err; + } + } else { + /* input is binary stream. truncated to reg.size or + * filled with 0 if provided is shorter */ + buflen = min(count, sizeof(binbuf)); + if (copy_from_user(binbuf, userbuf, buflen)) { + r = -EFAULT; + dev_err(dw->dev, "copy failed\n"); + goto dw3000_reg_op_err; + } + } + xfer.operation = DW_REG_WRITE; + xfer.value = binbuf; + } + + /* do spi operation */ + xfer.reg = reg; + r = dw3000_enqueue_generic(dw, &cmd); + if (r) { + dev_err(dw->dev, "fail to access register %s (%ld)\n", + reg->name, r); + goto dw3000_reg_op_err; + } + + if (!write) { + if (reg->mask) { + /* size of mask, 0 inside it included */ + outsize = fls(reg->mask >> (ffs(reg->mask) - 1)) - 1; + outsize = (outsize >> 3) + 1; + } else + outsize = reg->size; + + r = format_reg_output(dw, binbuf, outsize, userbuf); + + if (r < 0) + goto dw3000_reg_op_err; + else + count = r; + } + *ppos += count; + r = count; + +dw3000_reg_op_err: + return r; +} + +static int dw3000_dbgfs_reg_release(struct inode *inode, struct file *file) +{ + struct dw3000_debugfs_file *dbgfs_file = file->private_data; + + atomic_dec(&dbgfs_file->fileopened); + + return 0; +} + +static int dw3000_dbgfs_reg_open(struct inode *inode, struct file *file) +{ + struct dw3000_debugfs_file *dbgfs_file = + (struct dw3000_debugfs_file *)inode->i_private; + struct dw3000_chip_register_priv *crp; + + if (unlikely(!dbgfs_file)) + return -ENXIO; + + file->private_data = dbgfs_file; + crp = &dbgfs_file->chip_reg_priv; + + if (crp->reg->flags & DW3000_CHIPREG_OPENONCE) { + if (!atomic_add_unless(&dbgfs_file->fileopened, 1, 1)) + return -EBUSY; + } + + return 0; +} + +static ssize_t dw3000_dbgfs_reg_read(struct file *filp, char __user *userbuf, + size_t count, loff_t *ppos) +{ + return dw3000_dbgfs_reg_op(filp, userbuf, count, ppos, false); +} + +static ssize_t dw3000_dbgfs_reg_write(struct file *filp, + const char __user *userbuf, size_t count, + loff_t *ppos) +{ + return dw3000_dbgfs_reg_op(filp, (char *)userbuf, count, ppos, true); +} + +static struct file_operations dw3000_dump_fops = { + .read = dw3000_dump_read, + .open = dw3000_dump_open, + .release = dw3000_file_release, +}; + +static struct file_operations dw3000_reg_fops = { + .read = dw3000_dbgfs_reg_read, + .write = dw3000_dbgfs_reg_write, + .open = dw3000_dbgfs_reg_open, + .release = dw3000_dbgfs_reg_release, +}; + +static int dw3000_debugsfs_regs_init(struct dw3000 *dw, + const struct dw3000_chip_register *regs, + size_t reg_count) +{ + struct dw3000_debugfs_file *cur; + int i; + + for (i = 0; i < reg_count; i++) { + if (regs[i].flags & DW3000_CHIPREG_DUMP) + continue; + + cur = kzalloc(sizeof(struct dw3000_debugfs_file), GFP_KERNEL); + if (!cur) { + dw3000_debugfs_remove(dw); + return -ENOMEM; + } + + cur->chip_reg_priv.reg = ®s[i]; + cur->chip_reg_priv.dw = dw; + INIT_LIST_HEAD(&cur->ll); + list_add_tail(&cur->ll, &dw->debugfs.dbgfile_list); + + cur->file = debugfs_create_file( + regs[i].name, + S_IRUGO | + ((regs[i].flags & DW3000_CHIPREG_RO) ? 0 : + S_IWUGO), + dw->debugfs.parent_dir, &cur->chip_reg_priv, + &dw3000_reg_fops); + } + + return 0; +} + +/** + * dw3000_debugsfs_init() - Debugfs interface for DW's registers + * @dw: The DW device. + * + * Debugfs exposition of DW's registers + * + * Return: 0 if success, -EINVAL if dw have no registers configured and + * ENODEV if files are impossible to create (resulting of a debugfs + * missing support) + */ +int dw3000_debugsfs_init(struct dw3000 *dw) +{ + struct dw3000_debugfs_dump_private *dump; + size_t reg_count; + const struct dw3000_chip_register *regs = + dw->chip_ops->get_registers(dw, ®_count); + int rc; + + if (!regs) + return -EINVAL; + if (!reg_count) { + dev_dbg(dw->dev, "empty registers list returned. init abort\n"); + return -EINVAL; + } + + INIT_LIST_HEAD(&dw->debugfs.dbgfile_list); + dw->debugfs.parent_dir = debugfs_create_dir("dw3000", NULL); + if (IS_ERR(dw->debugfs.parent_dir)) { + dw->debugfs.parent_dir = NULL; + dev_err(dw->dev, "unable to create parent dir %ld\n", + PTR_ERR(dw->debugfs.parent_dir)); + return -EINVAL; + } + if (!dw->debugfs.parent_dir) { /* err even if debugfs activated */ + dev_err(dw->dev, "debugfs directory creation failed\n"); + return -EINVAL; + } + + /* Creation of "registers" file to dump whole file ids */ + dump = kzalloc(sizeof(struct dw3000_debugfs_dump_private), GFP_KERNEL); + if (!dump) { + dw3000_debugfs_remove(dw); + return -ENOMEM; + } + + dump->dbgfile.file = debugfs_create_file("registers", S_IRUGO, + dw->debugfs.parent_dir, dump, + &dw3000_dump_fops); + + dump->dbgfile.chip_reg_priv.count = reg_count; + dump->dbgfile.chip_reg_priv.reg = regs; + dump->dbgfile.chip_reg_priv.dw = dw; + INIT_LIST_HEAD(&dump->dbgfile.ll); + list_add_tail(&dump->dbgfile.ll, &dw->debugfs.dbgfile_list); + + /* Creation of a file for each non-dumpable register */ + rc = dw3000_debugsfs_regs_init(dw, regs, reg_count); + if (rc) { + dev_err(dw->dev, "error when init of chip's registers\n"); + return rc; + } + rc = dw3000_debugsfs_regs_init(dw, virtual_registers, + ARRAY_SIZE(virtual_registers)); + if (rc) { + dev_err(dw->dev, "error when init of virtual registers\n"); + return rc; + } + + return 0; +} + +/** + * dw3000_debugfs_remove() - Remove all files and directory created by _init + * @dw: The DW Device + * + * Return: nothing + */ +void dw3000_debugfs_remove(struct dw3000 *dw) +{ + while (!list_empty(&dw->debugfs.dbgfile_list)) { + struct dw3000_debugfs_file *cur = + list_first_entry(&dw->debugfs.dbgfile_list, + struct dw3000_debugfs_file, ll); + debugfs_remove(cur->file); + list_del(&cur->ll); + kfree(cur); + } + + debugfs_remove_recursive(dw->debugfs.parent_dir); +} diff --git a/kernel/drivers/net/ieee802154/dw3000_debugfs.h b/kernel/drivers/net/ieee802154/dw3000_debugfs.h new file mode 100644 index 0000000..4f440fb --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_debugfs.h @@ -0,0 +1,74 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_DEBUGFS_H +#define __DW3000_DEBUGFS_H + +#define DW3000_REGCONTENT_MAX_SZ (12) /* 4 bytes + 0x + \n + \0 */ +#define DW3000_REG_MAX_SZ (16) + +/** + * enum dw3000_reg_operation - operation done on a register + * @DW_REG_READ: read whole register. If mask is set the output is shrinked + * @DW_REG_WRITE: write whole register. include modify operation if mask!=0 + */ +enum dw3000_reg_operation { + DW_REG_READ = 0, + DW_REG_WRITE, +}; + +/** struct dw3000_debugfs - debugfs informations in device struct dw + * @parent_dir: dw parent directory in debugfs + * @dbgfile_list: linked list of each files in debugfs + */ +struct dw3000_debugfs { + struct dentry *parent_dir; + struct list_head dbgfile_list; +}; + +/** struct dw3000_debugfs_file - debugfs file related structure + * @chip_reg_priv: register + * @file: filesystem representation + * @fileopened: different from 0 if file already opened + * @ll: linked list for ressources release + */ +struct dw3000_debugfs_file { + struct dw3000_chip_register_priv chip_reg_priv; + struct dentry *file; + atomic_t fileopened; + struct list_head ll; +}; + +/** + * struct dw3000_debugfs_dump_private - private data associated to dbgfs file + * @dbgfile: structure associated to the dbgfs file + * @current_reg: current fileid reg to dump through 'registers' file + */ +struct dw3000_debugfs_dump_private { + struct dw3000_debugfs_file dbgfile; + const struct dw3000_chip_register *current_reg; +}; + +int dw3000_debugsfs_init(struct dw3000 *dw); +void dw3000_debugfs_remove(struct dw3000 *dw); + +#endif diff --git a/kernel/drivers/net/ieee802154/dw3000_mcps.c b/kernel/drivers/net/ieee802154/dw3000_mcps.c new file mode 100644 index 0000000..c9365b2 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_mcps.c @@ -0,0 +1,1517 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/pm_runtime.h> +#include <linux/bitfield.h> +#include <linux/completion.h> +#include <net/mcps802154.h> + +#include "dw3000.h" +#include "dw3000_pm.h" +#include "dw3000_core.h" +#include "dw3000_mcps.h" +#include "dw3000_testmode.h" +#include "dw3000_trc.h" +#include "dw3000_nfcc_coex_mcps.h" +#include "dw3000_pctt_mcps.h" +#include "dw3000_coex.h" +#include "dw3000_cir.h" +#include "dw3000_power_stats.h" + +static int completion_active(struct completion *completion) +{ +#if (KERNEL_VERSION(5, 7, 0) > LINUX_VERSION_CODE) + return waitqueue_active(&completion->wait); +#else + return swait_active(&completion->wait); +#endif +} + +static inline u64 timestamp_rctu_to_rmarker_rctu(struct dw3000 *dw, + u64 timestamp_rctu, + u32 rmarker_dtu); + +static inline u32 +tx_rmarker_offset(struct dw3000 *dw, + const struct mcps802154_channel *channel_params, + int ant_set_id) +{ + struct dw3000_config *config = &dw->config; + const struct dw3000_antenna_calib *ant_calib; + const struct dw3000_antenna_calib_prf *ant_calib_prf; + int chanidx; + int prfidx; + s8 ant_idx1, ant_idx2; + int chan = channel_params ? channel_params->channel : config->chan; + int pcode = channel_params ? channel_params->preamble_code : + config->txCode; + + if (ant_set_id < 0 || ant_set_id >= ANTSET_ID_MAX) { + dev_err(dw->dev, + "antennas set id %d is out of range, max is %d\n", + ant_set_id, ANTSET_ID_MAX); + return 0; + } + + /* Retrieve TX antenna from antenna set */ + dw3000_calib_ant_set_id_to_ant(ant_set_id, &ant_idx1, &ant_idx2); + if (ant_idx1 < 0 && ant_idx2 >= 0) + ant_idx1 = ant_idx2; /* use ant_idx2 if ant_idx1 undefined */ + + if (ant_idx1 < 0) { + /* Specified TX antenna must be valid */ + dev_err(dw->dev, "Bad antennas set id selected (%d)\n", + ant_set_id); + return 0; + } + + ant_calib = &dw->calib_data.ant[ant_idx1]; + /* Current configured ant_id. */ + if (ant_idx1 == config->ant[ant_calib->port]) + return config->rmarkerOffset; + + + chanidx = chan == 9 ? DW3000_CALIBRATION_CHANNEL_9 : + DW3000_CALIBRATION_CHANNEL_5; + prfidx = pcode >= 9 ? DW3000_CALIBRATION_PRF_64MHZ : + DW3000_CALIBRATION_PRF_16MHZ; + + ant_calib_prf = &ant_calib->ch[chanidx].prf[prfidx]; + + return ant_calib_prf->ant_delay; +} + +static int do_set_hw_addr_filt(struct dw3000 *dw, const void *in, void *out); +static int do_set_promiscuous_mode(struct dw3000 *dw, const void *in, + void *out); + +static int do_start(struct dw3000 *dw, const void *in, void *out) +{ +#if (KERNEL_VERSION(4, 13, 0) <= LINUX_VERSION_CODE) + struct spi_controller *ctlr = dw->spi->controller; +#else + struct spi_master *ctlr = dw->spi->master; +#endif + const unsigned long changed = (unsigned long)-1; + int rc; + + trace_dw3000_mcps_start(dw); + + /* Enforce required CPU latency */ + dw3000_pm_qos_update_request(dw, dw3000_qos_latency); + /* Lock power management of SPI controller */ + rc = pm_runtime_get_sync(ctlr->dev.parent); + if (rc < 0) { + pm_runtime_put_noidle(ctlr->dev.parent); + dev_err(&ctlr->dev, "Failed to power device: %d\n", rc); + } + dw->has_lock_pm = !rc; + /* Soft reset */ + rc = dw3000_softreset(dw); + if (rc) { + dev_err(dw->dev, "device reset failed: %d\n", rc); + goto fail; + } + /* Initialize & configure the device */ + rc = dw3000_init(dw, true); + if (rc) { + dev_err(dw->dev, "device init failed: %d\n", rc); + goto fail; + } + /* Apply other configuration not done by dw3000_init() */ + rc = do_set_hw_addr_filt(dw, &changed, NULL); + if (rc) + goto fail; + rc = do_set_promiscuous_mode(dw, NULL, NULL); + if (rc) + goto fail; + /* Reset ranging clock requirement */ + dw->need_ranging_clock = false; + /* Enable the device */ + rc = dw3000_enable(dw); +fail: + trace_dw3000_return_int(dw, rc); + return rc; +} + +/** + * start() - Start the device and configure it + * @llhw: Low-level hardware without MCPS. + * + * This callback starts the device and put it in right operational state. + * First, device is power-on from the caller context, then it is configured + * using the the hi-priority device thread. + * + * Return: 0 on success else a negative error + */ +static int start(struct mcps802154_llhw *llhw) +{ + struct dw3000 *dw = llhw->priv; + struct dw3000_stm_command cmd = { do_start, NULL, NULL }; + int rc; + + /* Turn on power */ + rc = dw3000_poweron(dw); + if (rc) { + dev_err(dw->dev, "device power on failed: %d\n", rc); + return rc; + } + /* Ensure RESET GPIO for enough time */ + rc = dw3000_hardreset(dw); + if (rc) { + dev_err(dw->dev, "hard reset failed: %d\n", rc); + return rc; + } + /* and wait SPI ready IRQ */ + rc = dw3000_wait_idle_state(dw); + if (rc) { + dev_err(dw->dev, "wait device power on failed: %d\n", rc); + return rc; + } + + /* Do soft reset and all initialization from the high-prio thread */ + return dw3000_enqueue_generic(dw, &cmd); +} + +static int do_stop(struct dw3000 *dw, const void *in, void *out) +{ + int rc; + + trace_dw3000_mcps_stop(dw); + + /* Disable the device */ + rc = dw3000_disable(dw); + if (rc) + dev_warn(dw->dev, "device disable failed: %d\n", rc); + /* Power-off */ + rc = dw3000_poweroff(dw); + if (rc) + dev_err(dw->dev, "device power-off failed: %d\n", rc); + /* Unlock power management of SPI controller */ + if (dw->has_lock_pm) { +#if (KERNEL_VERSION(4, 13, 0) <= LINUX_VERSION_CODE) + struct spi_controller *ctlr = dw->spi->controller; +#else + struct spi_master *ctlr = dw->spi->master; +#endif + pm_runtime_put(ctlr->dev.parent); + dw->has_lock_pm = false; + } + /* Reset ranging clock requirement */ + dw->need_ranging_clock = false; + dw3000_reset_rctu_conv_state(dw); + /* Reset cached antenna config to ensure GPIO are well reconfigured */ + dw->config.ant[0] = -1; + dw->config.ant[1] = -1; + /* Relax CPU latency requirement */ + dw3000_pm_qos_update_request(dw, PM_QOS_RESUME_LATENCY_NO_CONSTRAINT); + trace_dw3000_return_void(dw); + return 0; +} + +/** + * stop() - Stop the device and power-down it + * @llhw: Low-level hardware without MCPS. + * + * This callback stops the device and power-down it. + */ +static void stop(struct mcps802154_llhw *llhw) +{ + struct dw3000 *dw = llhw->priv; + struct dw3000_stm_command cmd = { do_stop, NULL, NULL }; + + dw3000_enqueue_generic(dw, &cmd); +} + +struct do_tx_frame_params { + struct sk_buff *skb; + const struct mcps802154_tx_frame_config *config; + int frame_idx; +}; + +static int do_tx_frame(struct dw3000 *dw, const void *in, void *out) +{ + const struct do_tx_frame_params *params = + (const struct do_tx_frame_params *)in; + + return dw3000_do_tx_frame(dw, params->config, params->skb, + params->frame_idx); +} + +static int tx_frame(struct mcps802154_llhw *llhw, struct sk_buff *skb, + const struct mcps802154_tx_frame_config *config, + int frame_idx, int next_delay_dtu) +{ + struct dw3000 *dw = llhw->priv; + struct do_tx_frame_params params = { .skb = skb, + .config = config, + .frame_idx = frame_idx }; + struct dw3000_stm_command cmd = { do_tx_frame, ¶ms, NULL }; + + /* Check data : no data if SP3, must have data otherwise */ + if (((config->flags & MCPS802154_TX_FRAME_CONFIG_STS_MODE_MASK) == + MCPS802154_TX_FRAME_CONFIG_SP3) != !skb) + return -EINVAL; + + return dw3000_enqueue_generic(dw, &cmd); +} + +struct do_rx_frame_params { + const struct mcps802154_rx_frame_config *config; + int frame_idx; +}; + +static int do_rx_enable(struct dw3000 *dw, const void *in, void *out) +{ + const struct do_rx_frame_params *params = + (const struct do_rx_frame_params *)in; + + return dw3000_do_rx_enable(dw, params->config, params->frame_idx); +} + +static int rx_enable(struct mcps802154_llhw *llhw, + const struct mcps802154_rx_frame_config *config, + int frame_idx, int next_delay_dtu) +{ + struct dw3000 *dw = llhw->priv; + struct do_rx_frame_params params = { .config = config, + .frame_idx = frame_idx }; + struct dw3000_stm_command cmd = { do_rx_enable, ¶ms, NULL }; + + return dw3000_enqueue_generic(dw, &cmd); +} + +static int do_rx_disable(struct dw3000 *dw, const void *in, void *out) +{ + int ret; + + trace_dw3000_mcps_rx_disable(dw); + ret = dw3000_rx_disable(dw); + /* Reset ranging clock requirement */ + dw->need_ranging_clock = false; + dw3000_reset_rctu_conv_state(dw); + trace_dw3000_return_int(dw, ret); + return ret; +} + +static int rx_disable(struct mcps802154_llhw *llhw) +{ + struct dw3000 *dw = llhw->priv; + struct dw3000_stm_command cmd = { do_rx_disable, NULL, NULL }; + int ret; + + if (dw3000_rx_busy(dw, true)) + return -EBUSY; + ret = dw3000_enqueue_generic(dw, &cmd); + WARN_ON_ONCE(dw3000_rx_busy(dw, false)); + return ret; +} + +/** + * get_ranging_pdoa_fom() - compute the figure of merit of the PDoA. + * @sts_fom: STS FoM on a received frame. + * @cfo: Clock-offset of a received frame. + * + * If CFO is inside the [-CFO_THRESHOLD;CFO_THRESHOLD] range or if the STS FoM + * is less than sts_fom_threshold, PDoA FoM is 1, the worst. + * + * If the STS FoM is greater or equal than sts_fom_threshold, + * sts_fom_threshold to 255 values are mapped to 2 to 255. + * + * Return: the PDoA FoM value. + */ +static u8 get_ranging_pdoa_fom(u8 sts_fom, s16 cfo) +{ + /* For a normalized STS FoM in 0 to 255, the STS is not reliable if + * the STS FoM is less than 60 percents of its maximum value. + */ + static const int sts_fom_threshold = 153; + + /* sts_fom_threshold .. sts_fom_max values are mapped to pdoa_fom_min .. pdoa_fom_max. + * The relation is pdoa_fom = a * sts_fom + b, with + * pdoa_fom_min = sts_fom_threshold * a + b + * pdoa_fom_max = sts_fom_max * a + b + * So: + * a = (pdoa_fom_max - pdoa_fom_min) / (sts_fom_max - sts_fom_threshold) + * b = pdoa_fom_min - sts_fom_threshold * a + */ + static const int sts_fom_max = 255; + static const int pdoa_fom_min = 2; + static const int pdoa_fom_max = 255; + static const int a_numerator = pdoa_fom_max - pdoa_fom_min; + static const int a_denominator = sts_fom_max - sts_fom_threshold; + static const int b = pdoa_fom_min - ((sts_fom_threshold * a_numerator) / + a_denominator); + + if (DW3000_XTAL_BIAS && (cfo > -DW3000_CFO_THRESHOLD) && + (cfo < DW3000_CFO_THRESHOLD)) + return 1; + if (sts_fom < sts_fom_threshold) + return 1; + return ((a_numerator * sts_fom) / a_denominator) + b; +} + +static int get_ranging_sts_fom(struct mcps802154_llhw *llhw, + struct mcps802154_rx_frame_info *info) +{ + int ret; + struct dw3000 *dw = llhw->priv; + struct dw3000_config *config = &dw->config; + /* Max sts_acc_qual value depend on STS length */ + int sts_acc_max = DW3000_GET_STS_LEN_UNIT_VALUE(config->stsLength) * 8; + s16 sts_acc_qual; + + /* TODO: Reading TOAST disabled. According to hardware team, + * this needs more tuning. They suggest to use quality only for + * now. See UWB-940 and commit "disable TOAST quality checking + * for STS". */ + + ret = dw3000_read_sts_quality(dw, &sts_acc_qual); + if (ret) + return ret; + /* DW3000 only support one STS segment. */ + info->ranging_sts_fom[0] = + clamp(1 + sts_acc_qual * 254 / sts_acc_max, 1, 255); + /* Set FoM of all other segments to maximum value so that they do not + * cause quality check failure. */ + memset(&info->ranging_sts_fom[1], 0xFF, MCPS802154_STS_N_SEGS_MAX - 1); + return ret; +} + +static int rx_get_rssi(struct dw3000 *dw, struct mcps802154_rx_frame_info *info, + const enum dw3000_stats_items item) +{ + struct dw3000_config *config = &dw->config; + int ret = 0; + + if (dw->stats.enabled || info->flags & MCPS802154_RX_FRAME_INFO_RSSI) { + struct dw3000_rssi rssi; + u8 sts = config->stsMode & DW3000_STS_BASIC_MODES_MASK; + ret = dw3000_rx_store_rssi(dw, &rssi, sts); + if (ret) { + info->flags &= ~MCPS802154_RX_FRAME_INFO_RSSI; + return ret; + } + if (dw->stats.enabled) + dw3000_rx_stats_inc(dw, item, &rssi); + ret = dw3000_rx_calc_rssi(dw, &rssi, info, sts); + } + return ret; +} + +static int rx_get_frame(struct mcps802154_llhw *llhw, struct sk_buff **skb, + struct mcps802154_rx_frame_info *info) +{ + struct dw3000 *dw = llhw->priv; + struct dw3000_config *config = &dw->config; + struct dw3000_rx *rx = &dw->rx; + unsigned long flags; + u64 timestamp_rctu; + u64 pkt_ts = 0; + int ret = 0; + s16 cfo = S16_MAX; + u8 rx_flags; + + trace_dw3000_mcps_rx_get_frame(dw, info->flags); + + /* Sanity check parameters */ + if (unlikely(!info || !skb)) { + ret = -EINVAL; + goto error; + } + + /* Acquire RX lock */ + spin_lock_irqsave(&rx->lock, flags); + /* Check buffer available */ + if (unlikely(!rx->skb && !(rx->flags & DW3000_RX_FLAG_ND))) { + spin_unlock_irqrestore(&rx->lock, flags); + ret = -EAGAIN; + goto error; + } + /* Give the last received frame we stored */ + *skb = rx->skb; + rx->skb = NULL; + rx_flags = rx->flags; + timestamp_rctu = rx->ts_rctu; + pkt_ts = timestamp_rctu; + /* Release RX lock */ + spin_unlock_irqrestore(&rx->lock, flags); + + if (info->flags & (MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU | + MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU)) { + if (!(rx_flags & DW3000_RX_FLAG_TS)) + info->flags &= ~( + MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU | + MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU | + MCPS802154_RX_FRAME_INFO_RANGING_STS_TIMESTAMP_RCTU); + else { + u32 rmarker_dtu = + dw3000_sys_time_rctu_to_dtu(dw, timestamp_rctu); + u64 rmarker_rctu = timestamp_rctu_to_rmarker_rctu( + dw, timestamp_rctu, rmarker_dtu); + info->timestamp_rctu = + rmarker_rctu - config->rmarkerOffset; + info->timestamp_dtu = rmarker_dtu - llhw->shr_dtu; + } + } + /* In case of auto-ack send. */ + if (rx_flags & DW3000_RX_FLAG_AACK) + info->flags |= MCPS802154_RX_FRAME_INFO_AACK; + /* Read CFO and adjust XTAL trim if need */ + if (info->flags & MCPS802154_RX_FRAME_INFO_RANGING_PDOA || + dw->data.check_cfo) { + ret = dw3000_read_clockoffset(dw, &cfo); + if (ret) + goto error; + } + if (DW3000_XTAL_BIAS && dw->data.check_cfo && + (cfo > -DW3000_CFO_THRESHOLD) && (cfo < DW3000_CFO_THRESHOLD)) { + /* CFO inside bad interval, adjust it for next round. */ + if (cfo < 0) { + /* Decrease xtal_bias */ + dw->data.xtal_bias -= DW3000_XTAL_BIAS; + } else { + /* Increase xtal_bias */ + dw->data.xtal_bias += DW3000_XTAL_BIAS; + } + /* Reprogram XTAL_TRIM if needed. */ + ret = dw3000_prog_xtrim(dw); + if (unlikely(ret)) + goto error; + } + /* In case of STS */ + if (info->flags & MCPS802154_RX_FRAME_INFO_RANGING_STS_TIMESTAMP_RCTU) { + u64 sts_ts_rctu; + + ret = dw3000_read_sts_timestamp(dw, &sts_ts_rctu); + if (ret) + goto error; + pkt_ts = sts_ts_rctu; + /* DW3000 only support one STS segment. */ + info->ranging_sts_timestamp_diffs_rctu[0] = 0; + info->ranging_sts_timestamp_diffs_rctu[1] = + sts_ts_rctu - timestamp_rctu; + if ((config->stsMode & DW3000_STS_BASIC_MODES_MASK) == + DW3000_STS_MODE_2) { + /* TODO: calc SRMARKER0 */ + } + } + info->ranging_sts_fom[0] = 0; + if (info->flags & (MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM | + MCPS802154_RX_FRAME_INFO_RANGING_PDOA_FOM)) { + ret = get_ranging_sts_fom(llhw, info); + if (ret) + goto error; + } + if (info->flags & MCPS802154_RX_FRAME_INFO_RANGING_PDOA_FOM) { + if (info->ranging_sts_fom[0]) + info->ranging_pdoa_fom = get_ranging_pdoa_fom( + info->ranging_sts_fom[0], cfo); + else + info->ranging_pdoa_fom = 0; + } + if (info->flags & MCPS802154_RX_FRAME_INFO_RANGING_OFFSET) { + if (cfo == S16_MAX) { + ret = dw3000_read_clockoffset(dw, &cfo); + if (ret) + goto error; + } + info->ranging_offset_rctu = cfo; + /* DW3000 provide directly the ratio (as Q26), + * so set arbitrarily the ranging interval (denominator) to 1 */ + info->ranging_tracking_interval_rctu = 1 << 26; + } + + /* If dbgfs file is opened & waiting for data, fill structure and wake-up reading process */ + if (dw->cir_data && completion_active(&dw->cir_data->complete)) { + ret = dw3000_read_frame_cir_data(dw, info, pkt_ts); + if (ret) + goto error; + } + /* Report statistics and if required process RSSI */ + ret = rx_get_rssi(dw, info, DW3000_STATS_RX_GOOD); + if (ret) + goto error; + + /* Keep only implemented. */ + info->flags &= (MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU | + MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU | + MCPS802154_RX_FRAME_INFO_AACK | + MCPS802154_RX_FRAME_INFO_RANGING_PDOA | + MCPS802154_RX_FRAME_INFO_RANGING_PDOA_FOM | + MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM | + MCPS802154_RX_FRAME_INFO_RANGING_STS_TIMESTAMP_RCTU | + MCPS802154_RX_FRAME_INFO_RANGING_OFFSET | + MCPS802154_RX_FRAME_INFO_RSSI); + trace_dw3000_return_int_u32(dw, info->flags, *skb ? (*skb)->len : 0); + return 0; + +error: + trace_dw3000_return_int_u32(dw, ret, 0); + return ret; +} + +static int rx_get_error_frame(struct mcps802154_llhw *llhw, + struct mcps802154_rx_frame_info *info) +{ + struct dw3000 *dw = llhw->priv; + int ret = 0; + + trace_dw3000_mcps_rx_get_error_frame(dw, info->flags); + /* Sanity check */ + if (unlikely(!info)) { + ret = -EINVAL; + goto error; + } + if (info->flags & MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU) { + if (dw3000_read_rx_timestamp(dw, &info->timestamp_rctu)) + info->flags &= ~MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU; + } + /* Report statistics and if required process RSSI */ + ret = rx_get_rssi(dw, info, DW3000_STATS_RX_ERROR); + if (ret) + goto error; + /* Keep only implemented. */ + info->flags &= (MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU | + MCPS802154_RX_FRAME_INFO_RSSI); + +error: + trace_dw3000_return_int_u32(dw, ret, info->flags); + return ret; +} + +/** + * rx_get_measurement - Update measurement. + * @llhw: Low-level device pointer. + * @rx_ctx: Custom rx context (can be NULL). + * @info: Measurement to update. + * + * Return: 0 or error. + */ +static int rx_get_measurement(struct mcps802154_llhw *llhw, void *rx_ctx, + struct mcps802154_rx_measurement_info *info) +{ + struct dw3000 *dw = llhw->priv; + + if (info->flags & MCPS802154_RX_MEASUREMENTS_AOAS) { + info->aoas[0].pdoa_rad_q11 = dw3000_read_pdoa(dw); + info->aoas[0].aoa_rad_q11 = + dw3000_pdoa_to_aoa_lut(dw, info->aoas[0].pdoa_rad_q11); + info->n_aoas = 1; + } + + /* TODO: UWB-4961 Usage of a mcps802154_rx_frame_info is a + * workaround used until rx_get_rssi() can be fully removed + * from rx_get_frame(). */ + if (info->flags & MCPS802154_RX_MEASUREMENTS_RSSIS) { + struct mcps802154_rx_frame_info frame_info; + int ret; + + frame_info.flags = MCPS802154_RX_FRAME_INFO_RSSI; + ret = rx_get_rssi(dw, &frame_info, DW3000_STATS_RX_GOOD); + if (ret) { + info->n_rssis = 0; + } else { + info->rssis_q1[0] = frame_info.rssi; + /* Only one RSSI computed per frame */ + info->n_rssis = 1; + } + } + + /* Keep only implemented. */ + info->flags &= MCPS802154_RX_MEASUREMENTS_AOAS | + MCPS802154_RX_MEASUREMENTS_RSSIS; + + return 0; +} + +static int dw3000_handle_idle_timeout(struct dw3000 *dw) +{ + /* MCPS feeback must be done outside driver kthread. */ + schedule_work(&dw->timer_expired_work); + return 0; +} + +static int do_idle(struct dw3000 *dw, const void *in, void *out) +{ + bool timestamp = !!in; + u32 timestamp_dtu = timestamp ? *(const u32 *)in : 0; + + int r = dw3000_idle(dw, timestamp, timestamp_dtu, + dw3000_handle_idle_timeout, + DW3000_OP_STATE_IDLE_PLL); + trace_dw3000_return_int(dw, r); + return r; +} + +static int idle(struct mcps802154_llhw *llhw, bool timestamp, u32 timestamp_dtu) +{ + struct dw3000 *dw = llhw->priv; + struct dw3000_stm_command cmd = { do_idle, NULL, NULL }; + + if (timestamp) + cmd.in = ×tamp_dtu; + return dw3000_enqueue_generic(dw, &cmd); +} + +static int do_reset(struct dw3000 *dw, const void *in, void *out) +{ + int rc; + + trace_dw3000_mcps_reset(dw); + /* Disable the device before resetting it */ + rc = dw3000_disable(dw); + if (rc) { + dev_err(dw->dev, "device disable failed: %d\n", rc); + goto fail; + } + /* Soft reset */ + rc = dw3000_softreset(dw); + if (rc != 0) { + dev_err(dw->dev, "device reset failed: %d\n", rc); + goto fail; + } + /* Initialize & configure the device */ + rc = dw3000_init(dw, true); + if (rc != 0) { + dev_err(dw->dev, "device reset failed: %d\n", rc); + goto fail; + } + /* Enable the device */ + rc = dw3000_enable(dw); + if (rc) { + dev_err(dw->dev, "device enable failed: %d\n", rc); + goto fail; + } +fail: + dw3000_reset_rctu_conv_state(dw); + trace_dw3000_return_int(dw, rc); + return rc; +} + +static int reset(struct mcps802154_llhw *llhw) +{ + struct dw3000 *dw = llhw->priv; + struct dw3000_stm_command cmd = { do_reset, NULL, NULL }; + + return dw3000_enqueue_generic(dw, &cmd); +} + +static int get_current_timestamp_dtu(struct mcps802154_llhw *llhw, + u32 *timestamp_dtu) +{ + struct dw3000 *dw = llhw->priv; + int ret = 0; + + trace_dw3000_mcps_get_timestamp(dw); + /* Must be called after start() */ + if (dw3000_is_active(dw)) { + u32 margin = 0; + *timestamp_dtu = dw3000_get_dtu_time(dw); + if (dw->current_operational_state < DW3000_OP_STATE_IDLE_PLL) + margin = US_TO_DTU(DW3000_WAKEUP_LATENCY_US); + *timestamp_dtu += margin; + } else + ret = -EBUSY; + trace_dw3000_return_int_u32(dw, ret, *timestamp_dtu); + return ret; +} + +static inline u64 timestamp_rctu_to_rmarker_rctu(struct dw3000 *dw, + u64 timestamp_rctu, + u32 rmarker_dtu) +{ + struct dw3000_rctu_conv *rctu = &dw->rctu_conv; + static const u64 rctu_mask = (1ll << 40) - 1; + u64 rmarker_rctu; + + if (rctu->state == UNALIGNED) { + rctu->alignment_rmarker_dtu = rmarker_dtu; + rctu->state = ALIGNED; + trace_dw3000_rctu_convert_align(dw, rmarker_dtu); + } + if (rctu->state == ALIGNED) { + u32 alignment_rmarker_sys_time = + dw3000_dtu_to_sys_time(dw, rctu->alignment_rmarker_dtu); + u64 alignment_rmarker_rctu = + (u64)alignment_rmarker_sys_time * DW3000_RCTU_PER_SYS; + rctu->synced_rmarker_rctu = alignment_rmarker_rctu; + rctu->state = ALIGNED_SYNCED; + trace_dw3000_rctu_convert_synced(dw, alignment_rmarker_rctu); + } + rmarker_rctu = (timestamp_rctu - rctu->synced_rmarker_rctu) & rctu_mask; + trace_dw3000_rctu_convert_rx(dw, rmarker_dtu, timestamp_rctu, + rmarker_rctu); + return rmarker_rctu; +} + +static u64 tx_timestamp_dtu_to_rmarker_rctu( + struct mcps802154_llhw *llhw, u32 tx_timestamp_dtu, + const struct mcps802154_hrp_uwb_params *hrp_uwb_params, + const struct mcps802154_channel *channel_params, int ant_set_id) +{ + struct dw3000 *dw = llhw->priv; + struct dw3000_rctu_conv *rctu = &dw->rctu_conv; + + const u32 shr_dtu = hrp_uwb_params ? + compute_shr_dtu_from_conf(hrp_uwb_params) : + llhw->shr_dtu; + const u32 rmarker_dtu = tx_timestamp_dtu + shr_dtu; + const u32 ant_offset = + tx_rmarker_offset(dw, channel_params, ant_set_id); + u64 rmarker_rctu; + s64 rmarker_diff_dtu; + + if (rctu->state == UNALIGNED) { + rctu->alignment_rmarker_dtu = rmarker_dtu; + rctu->state = ALIGNED; + trace_dw3000_rctu_convert_align(dw, rmarker_dtu); + } + rmarker_diff_dtu = rmarker_dtu - rctu->alignment_rmarker_dtu; + rmarker_rctu = rmarker_diff_dtu * DW3000_RCTU_PER_DTU + ant_offset; + trace_dw3000_rctu_convert_tx(dw, tx_timestamp_dtu, ant_offset, + rmarker_rctu); + return rmarker_rctu; +} + +static s64 difference_timestamp_rctu(struct mcps802154_llhw *llhw, + u64 timestamp_a_rctu, u64 timestamp_b_rctu) +{ + /* RCTU time is an unsigned encoded over 40 bytes. This function + calculate the signed difference between two unsigned 40 bytes */ + static const u64 rctu_rollover = 1ll << 40; + static const u64 rctu_mask = rctu_rollover - 1; + s64 diff_rctu = (timestamp_a_rctu - timestamp_b_rctu) & rctu_mask; + + if (diff_rctu & (rctu_rollover >> 1)) + diff_rctu = diff_rctu | ~rctu_mask; + return diff_rctu; +} + +static int compute_frame_duration_dtu(struct mcps802154_llhw *llhw, + int payload_bytes) +{ + struct dw3000 *dw = llhw->priv; + return dw3000_frame_duration_dtu(dw, payload_bytes, true); +} + +static int do_set_channel(struct dw3000 *dw, const void *in, void *out) +{ + struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state; + unsigned long changed = *(const unsigned long *)in; + + if (dw->current_operational_state < DW3000_OP_STATE_IDLE_RC) { + /* Cannot configure device, save info and ensure it will be + configured on wakeup */ + dss->config_changed |= changed; + return 0; + } + if (changed & DW3000_CHANNEL_CHANGED) + /* Reconfigure all channel dependent */ + return dw3000_configure_chan(dw); + else if (changed & DW3000_PCODE_CHANGED) + /* Only change CHAN_CTRL with new code */ + return dw3000_configure_pcode(dw); + return 0; +} + +int set_channel(struct mcps802154_llhw *llhw, u8 page, u8 channel, + u8 preamble_code) +{ + unsigned long changed = 0; + struct dw3000 *dw = llhw->priv; + struct dw3000_config *config = &dw->config; + struct dw3000_stm_command cmd = { do_set_channel, &changed, NULL }; + int ret = 0; + + trace_dw3000_mcps_set_channel(dw, page, channel, preamble_code); + /* Check parameters early */ + if (page != 4 || (channel != 5 && channel != 9) || + (dw->restricted_channels & (1 << (channel % 16)))) { + ret = -EINVAL; + goto trace; + } + switch (preamble_code) { + /* DW3000_PRF_16M */ + case 3: + case 4: + /* DW3000_PRF_64M */ + case 9: + case 10: + case 11: + case 12: + break; + default: + ret = -EINVAL; + goto trace; + } + /* Detect configuration change(s) */ + if (config->chan != channel) + changed |= DW3000_CHANNEL_CHANGED; + if ((config->txCode != preamble_code) || + (config->rxCode != preamble_code)) + changed |= DW3000_PCODE_CHANGED; + if (!changed) + goto trace; + /* Update configuration structure */ + config->chan = channel; + config->txCode = preamble_code; + config->rxCode = preamble_code; + /* Reconfigure the chip with it if needed */ + ret = dw3000_is_active(dw) ? dw3000_enqueue_generic(dw, &cmd) : 0; +trace: + trace_dw3000_return_int(dw, ret); + return ret; +} + +static int do_set_hrp_uwb_params(struct dw3000 *dw, const void *in, void *out) +{ + struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state; + unsigned long changed = *(const unsigned long *)in; + int rc; + + if (dw->current_operational_state < DW3000_OP_STATE_IDLE_RC) { + /* Cannot configure device, save info and ensure it will be + configured on wakeup */ + dss->config_changed |= changed; + return 0; + } + if (changed & + (DW3000_PREAMBLE_LENGTH_CHANGED | DW3000_DATA_RATE_CHANGED)) { + /* reconfigure data rate and preamble size if needed. */ + rc = dw3000_configure_preamble_length_and_datarate( + dw, !(changed & DW3000_PREAMBLE_LENGTH_CHANGED)); + if (rc) + return rc; + } + if (changed & DW3000_SFD_CHANGED) { + /* Only change CHAN_CTRL with new code */ + rc = dw3000_configure_sfd_type(dw); + if (rc) + return rc; + } + if (changed & DW3000_PHR_RATE_CHANGED) { + rc = dw3000_configure_phr_rate(dw); + if (rc) + return rc; + } + + if (changed & (DW3000_SFD_CHANGED | DW3000_PREAMBLE_LENGTH_CHANGED)) + dw3000_update_timings(dw); + + return rc; +} + +static int check_hrp_uwb_params(struct mcps802154_llhw *llhw, + const struct mcps802154_hrp_uwb_params *params) +{ + switch (params->prf) { + case MCPS802154_PRF_16: + case MCPS802154_PRF_64: + break; + case MCPS802154_PRF_125: + case MCPS802154_PRF_250: + return -ENOTSUPP; + default: + return -EINVAL; + } + switch (params->psr) { + case MCPS802154_PSR_32: + case MCPS802154_PSR_64: + case MCPS802154_PSR_128: + case MCPS802154_PSR_256: + case MCPS802154_PSR_1024: + case MCPS802154_PSR_4096: + break; + case MCPS802154_PSR_16: + case MCPS802154_PSR_24: + case MCPS802154_PSR_48: + case MCPS802154_PSR_96: + return -ENOTSUPP; + default: + return -EINVAL; + } + switch (params->sfd_selector) { + case MCPS802154_SFD_4A: + case MCPS802154_SFD_4Z_8: + break; + case MCPS802154_SFD_4Z_4: + case MCPS802154_SFD_4Z_16: + case MCPS802154_SFD_4Z_32: + return -ENOTSUPP; + default: + return -EINVAL; + } + switch (params->data_rate) { + case MCPS802154_DATA_RATE_6M81: + case MCPS802154_DATA_RATE_850K: + break; + case MCPS802154_DATA_RATE_7M80: + case MCPS802154_DATA_RATE_27M2: + case MCPS802154_DATA_RATE_31M2: + return -ENOTSUPP; + default: + return -EINVAL; + } + return 0; +} + +int set_hrp_uwb_params(struct mcps802154_llhw *llhw, + const struct mcps802154_hrp_uwb_params *params) +{ + unsigned long changed = 0; + struct dw3000 *dw = llhw->priv; + struct dw3000_config *config = &dw->config; + struct dw3000_stm_command cmd = { do_set_hrp_uwb_params, &changed, + NULL }; + int ret; + int psr, sfd_selector, phr_hi_rate, data_rate; + + /* The prf parameter is not used. This is due to the specificity of + * the DW3000 chip where the prf is not programmed explicitly, + * but implicitly when the preamble code is configured. + * Please see the DW3000 User Manual V0.3 P116. + */ + + /* Check parameters early */ + ret = check_hrp_uwb_params(llhw, params); + if (ret) + return ret; + + switch (params->psr) { + case MCPS802154_PSR_32: + psr = DW3000_PLEN_32; + break; + case MCPS802154_PSR_128: + psr = DW3000_PLEN_128; + break; + case MCPS802154_PSR_256: + psr = DW3000_PLEN_256; + break; + case MCPS802154_PSR_1024: + psr = DW3000_PLEN_1024; + break; + case MCPS802154_PSR_4096: + psr = DW3000_PLEN_4096; + break; + default: + psr = DW3000_PLEN_64; + break; + } + + switch (params->sfd_selector) { + case MCPS802154_SFD_4A: + sfd_selector = DW3000_SFD_TYPE_STD; + break; + default: + sfd_selector = DW3000_SFD_TYPE_4Z; + break; + } + + switch (params->data_rate) { + case MCPS802154_DATA_RATE_850K: + data_rate = DW3000_BR_850K; + break; + default: + data_rate = DW3000_BR_6M8; + break; + } + + phr_hi_rate = params->phr_hi_rate ? DW3000_PHRRATE_DTA : + DW3000_PHRRATE_STD; + + /* Detect configuration change(s) */ + if (config->txPreambLength != psr) + changed |= DW3000_PREAMBLE_LENGTH_CHANGED; + if (config->sfdType != sfd_selector) + changed |= DW3000_SFD_CHANGED; + if (config->phrRate != phr_hi_rate) + changed |= DW3000_PHR_RATE_CHANGED; + if (config->dataRate != data_rate) + changed |= DW3000_DATA_RATE_CHANGED; + if (!changed) + return 0; + + /* Update configuration structure */ + config->txPreambLength = psr; + config->sfdType = sfd_selector; + config->phrRate = phr_hi_rate; + config->dataRate = data_rate; + + /* Reconfigure the chip with it if needed */ + ret = dw3000_is_active(dw) ? dw3000_enqueue_generic(dw, &cmd) : 0; + return ret; +} + +static int do_set_sts_params(struct dw3000 *dw, const void *in, void *out) +{ + const struct mcps802154_sts_params *params = + (const struct mcps802154_sts_params *)in; + enum dw3000_sts_lengths len; + int rc; + trace_dw3000_mcps_set_sts_params(dw, params); + /* Set STS segment(s) length */ + /* ffs(x) return 1 for bit0, 2 for bit1... */ + len = (enum dw3000_sts_lengths)(ffs(params->seg_len) - 4); + rc = dw3000_set_sts_length(dw, len); + if (rc) + goto fail; + /* Send KEY & IV */ + rc = dw3000_configure_sts_key(dw, params->key); + if (rc) + goto fail; + rc = dw3000_configure_sts_iv(dw, params->v); + if (rc) + goto fail; + rc = dw3000_load_sts_iv(dw); +fail: + trace_dw3000_return_int(dw, rc); + return rc; +} + +static int set_sts_params(struct mcps802154_llhw *llhw, + const struct mcps802154_sts_params *params) +{ + struct dw3000 *dw = llhw->priv; + struct dw3000_stm_command cmd = { do_set_sts_params, params, NULL }; + + /* Must be called after start() */ + if (!dw3000_is_active(dw)) + return -EBUSY; + /* Check parameters */ + if (params->n_segs > 1) + return -EINVAL; + /* TODO: HACK TO REMOVE: store parameters and setup change on wakeup */ + dw3000_wakeup_and_wait(dw); + return dw3000_enqueue_generic(dw, &cmd); +} + +struct do_set_hw_addr_filt_params { + struct ieee802154_hw_addr_filt *filt; + unsigned long changed; +}; + +static int do_set_hw_addr_filt(struct dw3000 *dw, const void *in, void *out) +{ + struct dw3000_deep_sleep_state *dss = &dw->deep_sleep_state; + unsigned long changed = *(const unsigned long *)in; + + if (dw->current_operational_state < DW3000_OP_STATE_IDLE_RC) { + /* Cannot configure device, save info and ensure it will be + configured on wakeup */ + dss->config_changed |= changed; + return 0; + } + + return dw3000_configure_hw_addr_filt(dw, changed); +} + +static int set_hw_addr_filt(struct mcps802154_llhw *llhw, + struct ieee802154_hw_addr_filt *filt, + unsigned long changed) +{ + struct dw3000 *dw = llhw->priv; + struct dw3000_config *config = &dw->config; + struct ieee802154_hw_addr_filt *cfilt = &config->hw_addr_filt; + struct dw3000_stm_command cmd = { do_set_hw_addr_filt, &changed, NULL }; + int ret; + + if (changed & IEEE802154_AFILT_SADDR_CHANGED) + cfilt->short_addr = filt->short_addr; + if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED) + cfilt->ieee_addr = filt->ieee_addr; + if (changed & IEEE802154_AFILT_PANID_CHANGED) + cfilt->pan_id = filt->pan_id; + if (changed & IEEE802154_AFILT_PANC_CHANGED) + cfilt->pan_coord = filt->pan_coord; + + trace_dw3000_mcps_set_hw_addr_filt(dw, (u8)changed); + ret = dw3000_is_active(dw) ? dw3000_enqueue_generic(dw, &cmd) : 0; + trace_dw3000_return_int(dw, ret); + return ret; +} + +static int set_txpower(struct mcps802154_llhw *llhw, s32 mbm) +{ + struct dw3000 *dw = llhw->priv; + + dev_dbg(dw->dev, "%s called\n", __func__); + return 0; +} + +static int set_cca_mode(struct mcps802154_llhw *llhw, + const struct wpan_phy_cca *cca) +{ + struct dw3000 *dw = llhw->priv; + + dev_dbg(dw->dev, "%s called\n", __func__); + return 0; +} + +static int set_cca_ed_level(struct mcps802154_llhw *llhw, s32 mbm) +{ + struct dw3000 *dw = llhw->priv; + + dev_dbg(dw->dev, "%s called\n", __func__); + return 0; +} + +static int do_set_promiscuous_mode(struct dw3000 *dw, const void *in, void *out) +{ + return dw3000_set_promiscuous(dw, dw->config.promisc); +} + +static int set_promiscuous_mode(struct mcps802154_llhw *llhw, bool on) +{ + struct dw3000 *dw = llhw->priv; + struct dw3000_stm_command cmd = { do_set_promiscuous_mode, NULL, NULL }; + + dev_dbg(dw->dev, "%s called, (mode: %sabled)\n", __func__, + (on) ? "en" : "dis"); + dw->config.promisc = on; + return dw3000_is_active(dw) ? dw3000_enqueue_generic(dw, &cmd) : 0; +} + +static int check_calibration_value(struct mcps802154_llhw *llhw, + const char *key, void *value) +{ + struct dw3000 *dw = llhw->priv; + if (!strcmp(key, "restricted_channels")) { + /* Prevent every channels from being restricted. */ + if (!(DW3000_SUPPORTED_CHANNELS & ~*(u16 *)value)) + return -EINVAL; + /* Prevent current channel from being restricted if dw3000 is active. */ + if (((1 << llhw->hw->phy->current_channel) & *(u16 *)value) && + dw3000_is_active(dw)) + return -EBUSY; + } + if ((strlen(key) > 22) && !strcmp(key + 13, ".pdoa_lut")) { + int i; + dw3000_pdoa_lut_t *lut = value; + for (i = 1; i < DW3000_CALIBRATION_PDOA_LUT_MAX; i++) + if ((*lut)[i][0] <= (*lut)[i - 1][0]) + return -EINVAL; + } + return 0; +} + +static int set_calibration(struct mcps802154_llhw *llhw, const char *key, + void *value, size_t length) +{ + struct dw3000 *dw = llhw->priv; + void *param; + int len; + int r; + /* Sanity checks */ + if (!key || !value || !length) + return -EINVAL; + /* Search parameter */ + len = dw3000_calib_parse_key(dw, key, ¶m); + if (len < 0) + return len; + if (len > length) + return -EINVAL; + r = check_calibration_value(llhw, key, value); + if (r) + return r; + /* FIXME: This copy isn't big-endian compatible. */ + memcpy(param, value, len); + + /* One parameter has changed. */ + dw3000_calib_update_config(dw); + /* TODO: need reconfiguration? */ + return 0; +} + +static int get_calibration(struct mcps802154_llhw *llhw, const char *key, + void *value, size_t length) +{ + struct dw3000 *dw = llhw->priv; + void *param; + int len; + /* Sanity checks */ + if (!key) + return -EINVAL; + /* Calibration parameters */ + len = dw3000_calib_parse_key(dw, key, ¶m); + if (len < 0) + return len; + if (len <= length) + memcpy(value, param, len); + else if (value && length) + /* Provided buffer size isn't enough, return an error */ + return -ENOSPC; + /* Return selected parameter length or error */ + return len; +} + +static const char *const *list_calibration(struct mcps802154_llhw *llhw) +{ + return dw3000_calib_list_keys(llhw->priv); +} + +struct do_vendor_params { + u32 vendor_id; + u32 subcmd; + void *data; + size_t data_len; +}; + +static int do_vendor_cmd(struct dw3000 *dw, const void *in, void *out) +{ + const struct do_vendor_params *params = in; + + switch (params->subcmd) { + case LLHW_VENDOR_CMD_PCTT_SETUP_HW: + return dw3000_pctt_vendor_cmd(dw, params->vendor_id, + params->subcmd, params->data, + params->data_len); + case LLHW_VENDOR_CMD_NFCC_COEX_HANDLE_ACCESS: + case LLHW_VENDOR_CMD_NFCC_COEX_GET_ACCESS_INFORMATION: + case LLHW_VENDOR_CMD_NFCC_COEX_STOP: + return dw3000_nfcc_coex_vendor_cmd(dw, params->vendor_id, + params->subcmd, params->data, + params->data_len); + } + return -EINVAL; +} + +/** + * vendor_cmd() - Forward vendor commands processing to dw3000 function. + * @llhw: Low-level hardware without MCPS. + * @vendor_id: Vendor Identifier on 3 bytes. + * @subcmd: Sub-command identifier. + * @data: Null or data related with the sub-command. + * @data_len: Length of the data + * + * Return: 0 on success, 1 to request a stop, error on other value. + */ +static int vendor_cmd(struct mcps802154_llhw *llhw, u32 vendor_id, u32 subcmd, + void *data, size_t data_len) +{ + struct dw3000 *dw = llhw->priv; + struct do_vendor_params params = { + .vendor_id = vendor_id, + .subcmd = subcmd, + .data = data, + .data_len = data_len, + }; + struct dw3000_stm_command cmd = { do_vendor_cmd, ¶ms, NULL }; + return dw3000_enqueue_generic(dw, &cmd); +} + +static int get_antenna_caps(struct mcps802154_llhw *llhw, int ant_idx, + uint32_t *caps) +{ + struct dw3000 *dw = llhw->priv; + const struct dw3000_antenna_calib *ant_calib; + + if (ant_idx < 0 || ant_idx >= ANTMAX) { + dev_err(dw->dev, "Bad antenna number (%d)\n", ant_idx); + return -EINVAL; + } + + ant_calib = &dw->calib_data.ant[ant_idx]; + *caps = ant_calib->caps; + return 0; +} + +/** + * get_power_stats() - Forward vendor commands processing to dw3000 function. + * @llhw: Low-level hardware without MCPS. + * @pwr_stats: mcps802154_power_stats structure to be filled. + * + * Return: 0 on success or negative error code. + */ +static int get_power_stats(struct mcps802154_llhw *llhw, + struct mcps802154_power_stats *pwr_stats) +{ + struct dw3000 *dw = llhw->priv; + u64 idle_dur, rx_ns, tx_ns; + + /* Update the power statistics if needed. */ + if (dw->power.cur_state <= DW3000_PWR_IDLE) + dw3000_power_stats(dw, dw->power.cur_state, 0); + /* TX/RX are kept in DTU unit. Convert it here to limit conversion error */ + rx_ns = dw->power.stats[DW3000_PWR_RX].dur * 10000 / + (DW3000_DTU_FREQ / 100000); + tx_ns = dw->power.stats[DW3000_PWR_TX].dur * 10000 / + (DW3000_DTU_FREQ / 100000); + idle_dur = dw->power.stats[DW3000_PWR_RUN].dur - tx_ns - rx_ns; + + pwr_stats->power_state_stats[MCPS802154_PWR_STATE_OFF].dur = + dw->power.stats[DW3000_PWR_OFF].dur; + pwr_stats->power_state_stats[MCPS802154_PWR_STATE_OFF].count = + dw->power.stats[DW3000_PWR_OFF].count; + pwr_stats->power_state_stats[MCPS802154_PWR_STATE_SLEEP].dur = + dw->power.stats[DW3000_PWR_DEEPSLEEP].dur; + pwr_stats->power_state_stats[MCPS802154_PWR_STATE_SLEEP].count = + dw->power.stats[DW3000_PWR_DEEPSLEEP].count; + pwr_stats->power_state_stats[MCPS802154_PWR_STATE_IDLE].dur = idle_dur; + pwr_stats->power_state_stats[MCPS802154_PWR_STATE_IDLE].count = + dw->power.stats[DW3000_PWR_IDLE].count; + pwr_stats->power_state_stats[MCPS802154_PWR_STATE_RX].dur = rx_ns; + pwr_stats->power_state_stats[MCPS802154_PWR_STATE_RX].count = + dw->power.stats[DW3000_PWR_RX].count; + pwr_stats->power_state_stats[MCPS802154_PWR_STATE_TX].dur = tx_ns; + pwr_stats->power_state_stats[MCPS802154_PWR_STATE_TX].count = + dw->power.stats[DW3000_PWR_TX].count; + pwr_stats->interrupts = atomic64_read(&dw->power.interrupts); + return 0; +} + +static const struct mcps802154_ops dw3000_mcps_ops = { + .start = start, + .stop = stop, + .tx_frame = tx_frame, + .rx_enable = rx_enable, + .rx_disable = rx_disable, + .rx_get_frame = rx_get_frame, + .rx_get_error_frame = rx_get_error_frame, + .rx_get_measurement = rx_get_measurement, + .idle = idle, + .reset = reset, + .get_current_timestamp_dtu = get_current_timestamp_dtu, + .tx_timestamp_dtu_to_rmarker_rctu = tx_timestamp_dtu_to_rmarker_rctu, + .difference_timestamp_rctu = difference_timestamp_rctu, + .compute_frame_duration_dtu = compute_frame_duration_dtu, + .set_channel = set_channel, + .set_hrp_uwb_params = set_hrp_uwb_params, + .check_hrp_uwb_params = check_hrp_uwb_params, + .set_sts_params = set_sts_params, + .set_hw_addr_filt = set_hw_addr_filt, + .set_txpower = set_txpower, + .set_cca_mode = set_cca_mode, + .set_cca_ed_level = set_cca_ed_level, + .set_promiscuous_mode = set_promiscuous_mode, + .set_calibration = set_calibration, + .get_calibration = get_calibration, + .list_calibration = list_calibration, + .vendor_cmd = vendor_cmd, + .get_antenna_caps = get_antenna_caps, + .get_power_stats = get_power_stats, + MCPS802154_TESTMODE_CMD(dw3000_tm_cmd) +}; + +/** + * dw3000_mcps_alloc() - Allocate low-level MCPS driver + * @dev: SPI device to associate with + * + * Return: Allocated struct dw3000 or NULL on error + */ +struct dw3000 *dw3000_mcps_alloc(struct device *dev) +{ + struct mcps802154_llhw *llhw; + struct dw3000 *dw; + + dev_dbg(dev, "%s called\n", __func__); + llhw = mcps802154_alloc_llhw(sizeof(*dw), &dw3000_mcps_ops); + if (llhw == NULL) + return NULL; + dw = llhw->priv; + dw->llhw = llhw; + dw->dev = dev; + dw3000_init_config(dw); + + /* Configure IEEE802154 HW capabilities */ + llhw->hw->flags = + (IEEE802154_HW_TX_OMIT_CKSUM | IEEE802154_HW_AFILT | + IEEE802154_HW_PROMISCUOUS | IEEE802154_HW_RX_OMIT_CKSUM); + llhw->flags = + (MCPS802154_LLHW_BPRF | MCPS802154_LLHW_DATA_RATE_850K | + MCPS802154_LLHW_DATA_RATE_6M81 | + MCPS802154_LLHW_PHR_DATA_RATE_850K | + MCPS802154_LLHW_PHR_DATA_RATE_6M81 | MCPS802154_LLHW_PRF_16 | + MCPS802154_LLHW_PRF_64 | MCPS802154_LLHW_PSR_32 | + MCPS802154_LLHW_PSR_64 | MCPS802154_LLHW_PSR_128 | + MCPS802154_LLHW_PSR_256 | MCPS802154_LLHW_PSR_1024 | + MCPS802154_LLHW_PSR_4096 | MCPS802154_LLHW_SFD_4A | + MCPS802154_LLHW_SFD_4Z_8 | MCPS802154_LLHW_STS_SEGMENT_1 | + MCPS802154_LLHW_AOA_AZIMUTH | MCPS802154_LLHW_AOA_ELEVATION | + MCPS802154_LLHW_AOA_FOM); + + llhw->hw->phy->supported.channels[4] = DW3000_SUPPORTED_CHANNELS; + + /* Set time related fields */ + llhw->dtu_freq_hz = DW3000_DTU_FREQ; + llhw->dtu_rctu = DW3000_RCTU_PER_DTU; + llhw->rstu_dtu = DW3000_DTU_PER_RSTU; + llhw->anticip_dtu = DW3000_ANTICIP_DTU; + llhw->idle_dtu = DW3000_DTU_FREQ; + /* Set antennas related fields */ + llhw->rx_antenna_pairs = ANTPAIR_MAX; + llhw->tx_antennas = DW3000_CALIBRATION_ANTENNA_MAX; + /* Set time related field that are configuration dependent */ + dw3000_update_timings(dw); + /* Symbol is ~0.994us @ PRF16 or ~1.018us @ PRF64. Use 1. */ + llhw->hw->phy->symbol_duration = 1; + + /* Set extended address. */ + llhw->hw->phy->perm_extended_addr = 0xd6552cd6e41ceb57; + + /* Driver phy page 4 as default, channel is copied from init config. */ + llhw->hw->phy->current_channel = dw->config.chan; + llhw->hw->phy->current_page = 4; + llhw->current_preamble_code = dw->config.txCode; + /* AoA/PDoA filtering. */ + llhw->rx_ctx_size = sizeof(struct dw3000_rx_ctx); + + return dw; +} + +/** + * dw3000_mcps_free() - Free low-level MCPS driver + * @dw: the DW device to free + */ +void dw3000_mcps_free(struct dw3000 *dw) +{ + dev_dbg(dw->dev, "%s called\n", __func__); + if (dw->llhw) { + struct mcps802154_llhw *llhw = dw->llhw; + dw->llhw = NULL; + mcps802154_free_llhw(llhw); + } +} + +/** + * dw3000_mcps_register() - Register low-level MCPS driver + * @dw: the allocated DW device to register in MCPS + * + * Return: zero on success, else a negative error code. + */ +int dw3000_mcps_register(struct dw3000 *dw) +{ + dev_dbg(dw->dev, "%s called\n", __func__); + return mcps802154_register_llhw(dw->llhw); +} + +/** + * dw3000_mcps_unregister() - Unregister low-level MCPS driver + * @dw: the DW device to unregister from MCPS + */ +void dw3000_mcps_unregister(struct dw3000 *dw) +{ + dev_dbg(dw->dev, "%s called\n", __func__); + mcps802154_unregister_llhw(dw->llhw); +} diff --git a/kernel/drivers/net/ieee802154/dw3000_mcps.h b/kernel/drivers/net/ieee802154/dw3000_mcps.h new file mode 100644 index 0000000..4eb6aa7 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_mcps.h @@ -0,0 +1,31 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_MCPS_H +#define __DW3000_MCPS_H + +struct dw3000 *dw3000_mcps_alloc(struct device *dev); +int dw3000_mcps_register(struct dw3000 *dw); +void dw3000_mcps_unregister(struct dw3000 *dw); +void dw3000_mcps_free(struct dw3000 *dw); + +#endif /* __DW3000_MCPS_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex.h b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex.h new file mode 100644 index 0000000..0bffafc --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex.h @@ -0,0 +1,188 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_NFCC_COEX_H +#define __DW3000_NFCC_COEX_H + +#include <linux/module.h> +#include <net/vendor_cmd.h> + +/* Main defines */ +#define DW3000_NFCC_COEX_SIGNATURE_STR "QORVO" +#define DW3000_NFCC_COEX_SIGNATURE_LEN 5 +#define DW3000_NFCC_COEX_MAX_NB_TLV 12 + +/* For TLV messages written by AP / read by NFCC, + * the scratch memory region is SCRATCH_RAM[0-63]. */ +#define DW3000_NFCC_COEX_MSG_OUT_OFFSET 0 +#define DW3000_NFCC_COEX_MSG_OUT_SIZE 64 +/* For TLV messages read by AP / written by NFCC, + * the scratch memory region is SCRATCH_RAM[64-127]. */ +#define DW3000_NFCC_COEX_MSG_IN_OFFSET 64 +#define DW3000_NFCC_COEX_MSG_IN_SIZE 63 + +#define DW3000_NFCC_COEX_MSG_MAX_SIZE DW3000_NFCC_COEX_MSG_OUT_SIZE + +/* MSG_HEADER_LEN is also the sizeof of dw3000_nfcc_coex_msg structure. */ +#define MSG_HEADER_LEN (DW3000_NFCC_COEX_SIGNATURE_LEN + 3) +#define MSG_LEN(x) (MSG_HEADER_LEN + (x).tlvs_len) + +#define DW3000_NFCC_COEX_UUS_PER_SYS_POWER 8 /* To use with right shift. */ +#define DW3000_NFCC_COEX_DTU_PER_UUS_POWER 4 /* To use with left shift. */ + +/** + * enum dw3000_nfcc_coex_send - Type of message to send. + * + * @DW3000_NFCC_COEX_SEND_CLK_SYNC: Clock sync message. + * @DW3000_NFCC_COEX_SEND_CLK_OFFSET: Clock offset message. + * @DW3000_NFCC_COEX_SEND_STOP: Stop message. + */ +enum dw3000_nfcc_coex_send { + DW3000_NFCC_COEX_SEND_CLK_SYNC, + DW3000_NFCC_COEX_SEND_CLK_OFFSET, + DW3000_NFCC_COEX_SEND_STOP, +}; + +/** + * struct dw3000_nfcc_coex_msg - Message read/write from/to scratch memory. + */ +struct dw3000_nfcc_coex_msg { + /** + * @signature: String signature, example "QORVO". + */ + u8 signature[DW3000_NFCC_COEX_SIGNATURE_LEN]; + /** + * @ver_id: Version identifier. + */ + u8 ver_id; + /** + * @seqnum: Sequence number. + */ + u8 seqnum; + /** + * @nb_tlv: Number of TLV object in the body message. + */ + u8 nb_tlv; + /** + * @tlvs: Body message. Its addr points to TLVs start. + */ + u8 tlvs[]; +} __attribute__((packed)); + +/** + * struct dw3000_nfcc_coex_buffer - Memory buffer to read/write a NFCC message. + */ +struct dw3000_nfcc_coex_buffer { + /* Unamed union for structured access or raw access. */ + union { + /** + * @raw: Byte memory. + */ + u8 raw[DW3000_NFCC_COEX_MSG_MAX_SIZE]; + /** + * @msg: nfcc_coex message. + */ + struct dw3000_nfcc_coex_msg msg; + }; + /** + * @tlvs_len: Len of TLVs in bytes. + */ + u8 tlvs_len; +} __attribute__((packed)); + +/** + * struct dw3000_nfcc_coex_rx_msg_info - Result of message parsed. + */ +struct dw3000_nfcc_coex_rx_msg_info { + /** + * @next_timestamp_dtu: Next NFCC access requested. + */ + u32 next_timestamp_dtu; + /** + * @next_duration_dtu: Next NFCC duration access. + */ + int next_duration_dtu; + /** + * @next_slot_found: True when first next slot is found. + */ + bool next_slot_found; +}; + +/** + * struct dw3000_nfcc_coex - NFCC coexistence context. + */ +struct dw3000_nfcc_coex { + /** + * @access_info: Access information to provide to upper layer. + */ + struct llhw_vendor_cmd_nfcc_coex_get_access_info access_info; + /** + * @session_time0_dtu: Timestamp used as reference between NFCC and AP. + */ + u32 session_time0_dtu; + /** + * @access_start_dtu: start date of nfcc session in DTU. + */ + u32 access_start_dtu; + /** + * @prev_offset_sys_time: Previous offset between local and decawave time. + */ + u32 prev_offset_sys_time; + /** + * @original_channel: channel number to be restored after NFCC session. + */ + u8 original_channel; + /** + * @rx_seq_num: Sequence number of last valid message check. + */ + u8 rx_seq_num; + /** + * @tx_seq_num: Sequence message counter increased on outgoing message. + */ + u8 tx_seq_num; + /** + * @enabled: True when nfcc coex is handling an access. + */ + bool enabled; + /** + * @configured: True when nfcc coex is configured. + */ + bool configured; + /** + * @send: Type of message to send. + */ + enum dw3000_nfcc_coex_send send; + /** + * @first_rx_message: False after the first valid msg received. + */ + bool first_rx_message; + /** + * @watchdog_timer: Watchdog timer to detect spi not bring back. + */ + struct timer_list watchdog_timer; + /** + * @version: Protocol version to use. + */ + u8 version; +}; + +#endif /* __DW3000_NFCC_COEX_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_buffer.c b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_buffer.c new file mode 100644 index 0000000..594df06 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_buffer.c @@ -0,0 +1,201 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include "dw3000_nfcc_coex_buffer.h" +#include "dw3000_core.h" +#include "dw3000_core_reg.h" +#include "dw3000_trc.h" + +/** + * dw3000_nfcc_coex_read_scratch_ram() - Copy from scratch ram memory to buffer. + * @dw: Driver context. + * @buffer: Buffer to write. + * @len: Number of byte to copy. + * + * Return: 0 on success, else an error. + */ +static int dw3000_nfcc_coex_read_scratch_ram( + struct dw3000 *dw, struct dw3000_nfcc_coex_buffer *buffer, u16 len) +{ + static const int offset = DW3000_NFCC_COEX_MSG_IN_OFFSET; + int end_offset = len + offset; + + if (end_offset > DW3000_SCRATCH_RAM_LEN) { + trace_dw3000_nfcc_coex_err(dw, "read scratch ram bad address"); + return -EINVAL; + } + return dw3000_xfer(dw, DW3000_SCRATCH_RAM_ID, offset, len, buffer->raw, + DW3000_SPI_RD_BIT); +} + +/** + * dw3000_nfcc_coex_write_scratch_ram() - Copy from buffer to scratch ram memory. + * @dw: Driver context. + * @buffer: Buffer to read. + * @len: Number of byte to copy. + * + * Return: 0 on success, else an error. + */ +static int +dw3000_nfcc_coex_write_scratch_ram(struct dw3000 *dw, + const struct dw3000_nfcc_coex_buffer *buffer, + u16 len) +{ + static const int offset = DW3000_NFCC_COEX_MSG_OUT_OFFSET; + int end_offset = len + offset; + /* TODO: Avoid the cast, which remove the const with better code. + * Idea to dig: define dw3000_xfer with 2 pointers(read/write). + * And develop the idea of full duplex API. */ + void *raw = (void *)buffer->raw; + + if (end_offset > DW3000_SCRATCH_RAM_LEN) { + trace_dw3000_nfcc_coex_err(dw, "write scratch ram bad address"); + return -EINVAL; + } + return dw3000_xfer(dw, DW3000_SCRATCH_RAM_ID, offset, len, raw, + DW3000_SPI_WR_BIT); +} + +/** + * dw3000_nfcc_coex_is_spi1_reserved() - Get the status of SPI1 reserved status. + * @dw: Driver context. + * @val: Boolean updated on success. + * + * Return: 0 on success, else an error. + */ +static int dw3000_nfcc_coex_is_spi1_reserved(struct dw3000 *dw, bool *val) +{ + u8 reg; + int rc; + + /* Check if SPI1 is reserved by reading SPI_SEM register. */ + rc = dw3000_reg_read8(dw, DW3000_SPI_SEM_ID, 0, ®); + if (rc) + return rc; + + *val = reg & DW3000_SPI_SEM_SPI1_RG_BIT_MASK; + return 0; +} + +/** + * dw3000_nfcc_coex_release_spi1() - Release the SPI1. + * @dw: Driver context. + * + * Return: 0 on success, else an error. + */ +static int dw3000_nfcc_coex_release_spi1(struct dw3000 *dw) +{ + int rc; + bool is_spi1_enabled; + + rc = dw3000_write_fastcmd(dw, DW3000_CMD_SEMA_REL); + if (rc) + return rc; + rc = dw3000_nfcc_coex_is_spi1_reserved(dw, &is_spi1_enabled); + if (rc) + return rc; + + return is_spi1_enabled ? -EBUSY : 0; +} + +/** + * dw3000_nfcc_coex_reserve_spi1() - Reserve the SPI1. + * @dw: Driver context. + * + * Return: 0 on success, else an error. + */ +static int dw3000_nfcc_coex_reserve_spi1(struct dw3000 *dw) +{ + int rc; + bool is_spi1_reserved; + + rc = dw3000_write_fastcmd(dw, DW3000_CMD_SEMA_REQ); + if (rc) + return rc; + /* Check if the SPI1 is really reserved. + * Indeed, if SPI2 is already reserved, SPI1 could not be reserved. */ + rc = dw3000_nfcc_coex_is_spi1_reserved(dw, &is_spi1_reserved); + if (rc) + return rc; + + return is_spi1_reserved ? 0 : -EBUSY; +} + +/** + * dw3000_nfcc_coex_write_buffer() - Write buffer into scratch memory. + * @dw: Driver context. + * @buffer: buffer to write in scratch memory. + * @len: byte length of the buffer. + * + * Return: 0 on success, else an error. + */ +int dw3000_nfcc_coex_write_buffer(struct dw3000 *dw, + const struct dw3000_nfcc_coex_buffer *buffer, + u16 len) +{ + int rc; + + if (!dw->nfcc_coex.enabled) + return -EOPNOTSUPP; + if (len > DW3000_NFCC_COEX_MSG_OUT_SIZE) { + trace_dw3000_nfcc_coex_err( + dw, "writing to nfcc exceed SCRATCH_AP_SIZE"); + return -EINVAL; + } + rc = dw3000_nfcc_coex_reserve_spi1(dw); + if (rc) + return rc; + rc = dw3000_nfcc_coex_write_scratch_ram(dw, buffer, len); + if (rc) + return rc; + dw->nfcc_coex.tx_seq_num++; + /* Trigger IRQ2 to inform NFCC. */ + return dw3000_nfcc_coex_release_spi1(dw); +} + +/** + * dw3000_nfcc_coex_read_buffer() - Read buffer from scratch memory. + * @dw: Driver context. + * @buffer: buffer fill with content of the scratch memory. + * @len: byte length of the buffer. + * + * Return: 0 on success, else an error. + */ +int dw3000_nfcc_coex_read_buffer(struct dw3000 *dw, + struct dw3000_nfcc_coex_buffer *buffer, + u16 len) +{ + int rc; + + if (!dw->nfcc_coex.enabled) + return -EOPNOTSUPP; + if (len > DW3000_NFCC_COEX_MSG_IN_SIZE) { + trace_dw3000_nfcc_coex_err( + dw, "Reading from NFCC exceed SCRATCH_AP_SIZE"); + return -EINVAL; + } + rc = dw3000_nfcc_coex_read_scratch_ram(dw, buffer, len); + if (rc) + trace_dw3000_nfcc_coex_err( + dw, "Error while reading NFCC scratch RAM"); + return rc; +} diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_buffer.h b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_buffer.h new file mode 100644 index 0000000..61a3d66 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_buffer.h @@ -0,0 +1,38 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef __DW3000_NFCC_COEX_BUFFER_H +#define __DW3000_NFCC_COEX_BUFFER_H + +#include <linux/module.h> +#include "dw3000_nfcc_coex.h" +#include "dw3000.h" + +int dw3000_nfcc_coex_write_buffer(struct dw3000 *dw, + const struct dw3000_nfcc_coex_buffer *buffer, + u16 len); +int dw3000_nfcc_coex_read_buffer(struct dw3000 *dw, + struct dw3000_nfcc_coex_buffer *buffer, + u16 len); + +#endif /* __DW3000_NFCC_COEX_BUFFER_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_core.c b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_core.c new file mode 100644 index 0000000..9d8aef9 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_core.c @@ -0,0 +1,398 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include "dw3000_nfcc_coex_core.h" +#include "dw3000_nfcc_coex_buffer.h" +#include "dw3000_nfcc_coex_msg.h" +#include "dw3000_nfcc_coex.h" +#include "dw3000_core.h" +#include "dw3000_trc.h" +#include "dw3000_mcps.h" + +#include <linux/module.h> + +/* dw3000_nfcc_coex_margin_dtu: + * - Can't be bigger than ANTICIP_DTU (trouble with CLOCK_SYNC). + * - Lower than 4ms is really dangerous. */ +unsigned dw3000_nfcc_coex_margin_dtu = US_TO_DTU(16000); +module_param_named(nfcc_coex_margin_dtu, dw3000_nfcc_coex_margin_dtu, uint, + 0444); +MODULE_PARM_DESC( + nfcc_coex_margin_dtu, + "Margin in dtu needed to give access to the NFCC controller and for the NFCC controller" + " to wake up (default is anticip_dtu)"); + +/** + * dw3000_nfcc_coex_enable_SPIxMAVAIL_interrupts() - Enable all SPI available interrupts. + * @dw: Driver context. + * + * Return: 0 on success, else an error. + */ +static int dw3000_nfcc_coex_enable_SPIxMAVAIL_interrupts(struct dw3000 *dw) +{ + int rc; + u8 reg; + + /* Clear SPI1MAVAIL and SPI2MAVAIL interrupt status. */ + rc = dw3000_clear_dss_status( + dw, DW3000_DSS_STAT_SPI1_AVAIL_BIT_MASK | + DW3000_DSS_STAT_SPI2_AVAIL_BIT_MASK); + if (rc) + return rc; + /* Disable SPIRDY in SYS_MASK. If it is enabled, the IRQ2 will not work. + * It is an undocumented feature. + */ + rc = dw3000_reg_modify32( + dw, DW3000_SYS_ENABLE_LO_ID, 0, + (u32)~DW3000_SYS_ENABLE_LO_SPIRDY_ENABLE_BIT_MASK, 0); + if (rc) + return rc; + /* Enable the dual SPI interrupt for SPI */ + rc = dw3000_reg_modify32(dw, DW3000_SYS_CFG_ID, 0, U32_MAX, + (u32)DW3000_SYS_CFG_DS_IE2_BIT_MASK); + if (rc) + return rc; + /* The masked write transactions do not work on the SPI_SEM register. + * So, a read, modify, write sequence is mandatory on this register. + * + * The 16 bits SPI_SEM register can be accessed as two 8 bits registers. + * So, only read the upper 8 bits for performance. + */ + rc = dw3000_reg_read8(dw, DW3000_SPI_SEM_ID, 1, ®); + if (rc) + return rc; + /* Set SPI1MAVAIL and SPI2MAVAIL masks */ + reg |= (DW3000_SPI_SEM_SPI1MAVAIL_BIT_MASK >> 8) | + (DW3000_SPI_SEM_SPI2MAVAIL_BIT_MASK >> 8); + return dw3000_reg_write8(dw, DW3000_SPI_SEM_ID, 1, reg); +} + +/** + * dw3000_nfcc_coex_disable_SPIxMAVAIL_interrupts() - Disable all SPI available interrupts. + * @dw: Driver context. + * + * Return: 0 on success, else an error. + */ +static int dw3000_nfcc_coex_disable_SPIxMAVAIL_interrupts(struct dw3000 *dw) +{ + int rc; + u8 reg; + + /* Please read the comment in enable_SPIxMAVAIL_interrupts() for SPI_SEM access. */ + rc = dw3000_reg_read8(dw, DW3000_SPI_SEM_ID, 1, ®); + if (rc) + return rc; + /* Reset SPI1MAVAIL and SPI2MAVAIL masks. */ + reg &= ~(DW3000_SPI_SEM_SPI1MAVAIL_BIT_MASK >> 8) & + ~(DW3000_SPI_SEM_SPI2MAVAIL_BIT_MASK >> 8); + rc = dw3000_reg_write8(dw, DW3000_SPI_SEM_ID, 1, reg); + if (rc) + return rc; + /* Disable the dual SPI interrupt for SPI. */ + return dw3000_reg_modify32(dw, DW3000_SYS_CFG_ID, 0, + (u32)~DW3000_SYS_CFG_DS_IE2_BIT_MASK, 0); +} + +/** + * dw3000_nfcc_coex_update_access_info() - Update access info cache. + * @dw: Driver context. + * @buffer: buffer to read. + */ +static void dw3000_nfcc_coex_update_access_info( + struct dw3000 *dw, const struct dw3000_nfcc_coex_buffer *buffer) +{ + struct llhw_vendor_cmd_nfcc_coex_get_access_info *access_info = + &dw->nfcc_coex.access_info; + struct dw3000_nfcc_coex_rx_msg_info rx_msg_info = {}; + int r; + + r = dw3000_nfcc_coex_message_check(dw, buffer, &rx_msg_info); + if (r) { + trace_dw3000_nfcc_coex_warn(dw, "message check failure"); + goto failure; + } + + /* Update access_info. */ + access_info->stop = !rx_msg_info.next_slot_found; + access_info->watchdog_timeout = false; + if (rx_msg_info.next_slot_found) { + /* Request the handle earlier to the mac layer. */ + access_info->next_timestamp_dtu = + rx_msg_info.next_timestamp_dtu - + dw3000_nfcc_coex_margin_dtu; + access_info->next_duration_dtu = rx_msg_info.next_duration_dtu + + dw3000_nfcc_coex_margin_dtu; + } + return; + +failure: + /* When buffer content is unexpected then request a stop. */ + memset(access_info, 0, sizeof(*access_info)); + access_info->stop = true; +} + +/** + * dw3000_nfcc_coex_configure() - Configure the nfcc_coex. + * @dw: Driver context. + * + * Return: 0 on success, else an error. + */ +int dw3000_nfcc_coex_configure(struct dw3000 *dw) +{ + struct dw3000_nfcc_coex *nfcc_coex = &dw->nfcc_coex; + bool channel_changed = nfcc_coex->original_channel != dw->config.chan; + int r; + + if (nfcc_coex->configured) + return 0; + + trace_dw3000_nfcc_coex_configure(dw); + if (channel_changed) { + r = dw3000_configure_chan(dw); + if (r) { + trace_dw3000_nfcc_coex_err(dw, + "configure channel fails"); + return r; + } + } + r = dw3000_nfcc_coex_prepare_config(dw); + if (r) { + trace_dw3000_nfcc_coex_warn(dw, + "prepare the configuration fails"); + return r; + } + + r = dw3000_nfcc_coex_enable_SPIxMAVAIL_interrupts(dw); + if (r) { + trace_dw3000_nfcc_coex_err( + dw, "SPIxMAVAIL interrupts enable failed"); + return r; + } + + nfcc_coex->configured = true; + return 0; +} + +/** + * dw3000_nfcc_coex_do_watchdog_timeout() - Do watchdog timeout event in workqueue. + * @dw: Driver context. + * @in: Data to read. + * @out: Data to write. + * + * Return: 0 on success, else an error. + */ +static int dw3000_nfcc_coex_do_watchdog_timeout(struct dw3000 *dw, + const void *in, void *out) +{ + /* Update status for GET_ACCESS_INFORMATION vendor. */ + dw->nfcc_coex.access_info.watchdog_timeout = true; + /* Do same as dw3000_nfcc_coex_disable function without decawave + * register access which it probably locked by NFCC. */ + dw->nfcc_coex.enabled = false; + dw->config.chan = dw->nfcc_coex.original_channel; + mcps802154_broken(dw->llhw); + + return 0; +} + +/** + * dw3000_nfcc_coex_do_spi1_avail() - Do SPI1 available irq event in workqueue. + * @dw: Driver context. + * @in: Data to read. + * @out: Data to write. + * + * Return: 0 on success, else an error. + */ +static int dw3000_nfcc_coex_do_spi1_avail(struct dw3000 *dw, const void *in, + void *out) +{ + struct dw3000_nfcc_coex_buffer buffer; + int r; + + /* Is watchdog timer running? */ + r = dw3000_nfcc_coex_cancel_watchdog(dw); + if (r) { + trace_dw3000_nfcc_coex_warn( + dw, "spi available without timer pending"); + goto spi1_avail_failure; + } + + r = dw3000_nfcc_coex_read_buffer(dw, &buffer, + DW3000_NFCC_COEX_MSG_IN_SIZE); + if (r) + goto spi1_avail_failure; + + dw3000_nfcc_coex_update_access_info(dw, &buffer); + dw3000_nfcc_coex_disable(dw); + mcps802154_tx_done(dw->llhw); + return 0; + +spi1_avail_failure: + dw3000_nfcc_coex_disable(dw); + mcps802154_broken(dw->llhw); + return r; +} + +/** + * dw3000_nfcc_coex_watchdog_timeout() - Watchdog timeout event. + * @timer: Timer context. + */ +static void dw3000_nfcc_coex_watchdog_timeout(struct timer_list *timer) +{ + struct dw3000_nfcc_coex *nfcc_coex = + from_timer(nfcc_coex, timer, watchdog_timer); + struct dw3000 *dw = nfcc_coex_to_dw(nfcc_coex); + struct dw3000_stm_command cmd = { dw3000_nfcc_coex_do_watchdog_timeout, + NULL, NULL }; + + trace_dw3000_nfcc_coex_watchdog_timeout(dw); + dw3000_enqueue_timer(dw, &cmd); +} + +/** + * dw3000_nfcc_coex_cancel_watchdog() - Cancel the watchdog timer. + * @dw: Driver context. + * + * Return: 0 on success, otherwise -errno on error. + */ +int dw3000_nfcc_coex_cancel_watchdog(struct dw3000 *dw) +{ + struct dw3000_nfcc_coex *nfcc_coex = &dw->nfcc_coex; + + trace_dw3000_nfcc_coex_cancel_watchdog(dw); + /* Is watchdog timer running? */ + if (!del_timer(&nfcc_coex->watchdog_timer)) + return -ENOENT; + return 0; +} + +/** + * dw3000_nfcc_coex_spi1_avail() - Handle SPI1 available interrupt. + * @dw: Driver context. + * + * Return: 0 on success, else an error. + */ +int dw3000_nfcc_coex_spi1_avail(struct dw3000 *dw) +{ + struct dw3000_stm_command cmd = { dw3000_nfcc_coex_do_spi1_avail, NULL, + NULL }; + + trace_dw3000_nfcc_coex_spi1_avail(dw); + return dw3000_enqueue_generic(dw, &cmd); +} + +/** + * dw3000_nfcc_coex_idle_timeout() - Idle expired. + * @dw: Driver context. + * + * Return: 0 on success, else an error. + */ +int dw3000_nfcc_coex_idle_timeout(struct dw3000 *dw) +{ + int r; + + trace_dw3000_nfcc_coex_idle_timeout(dw); + r = dw3000_nfcc_coex_configure(dw); + if (r) + goto idle_timeout_failure; + r = dw3000_nfcc_coex_message_send(dw); + if (r) + goto idle_timeout_failure; + return 0; + +idle_timeout_failure: + dw3000_nfcc_coex_disable(dw); + mcps802154_broken(dw->llhw); + return r; +} + +/** + * dw3000_nfcc_coex_init() - Initialize NFCC coexistence context. + * @dw: Driver context. + */ +void dw3000_nfcc_coex_init(struct dw3000 *dw) +{ + struct dw3000_nfcc_coex *nfcc_coex = &dw->nfcc_coex; + + memset(nfcc_coex, 0, sizeof(*nfcc_coex)); + timer_setup(&nfcc_coex->watchdog_timer, + dw3000_nfcc_coex_watchdog_timeout, 0); +} + +/** + * dw3000_nfcc_coex_enable() - Enable NFCC coexistence. + * @dw: Driver context. + * @channel: Channel number (5 or 9). + * + * Return: 0 on success, else an error. + */ +int dw3000_nfcc_coex_enable(struct dw3000 *dw, u8 channel) +{ + struct dw3000_nfcc_coex *nfcc_coex = &dw->nfcc_coex; + + trace_dw3000_nfcc_coex_enable(dw, channel); + if (nfcc_coex->enabled) + return -EBUSY; + + /* Save current channel. */ + nfcc_coex->original_channel = dw->config.chan; + nfcc_coex->configured = false; + nfcc_coex->enabled = true; + /* Set the new channel. */ + dw->config.chan = channel; + return 0; +} + +/** + * dw3000_nfcc_coex_disable() - Disable NFCC coexistence. + * @dw: Driver context. + * + * Return: 0 on success, else an error. + */ +int dw3000_nfcc_coex_disable(struct dw3000 *dw) +{ + struct dw3000_nfcc_coex *nfcc_coex = &dw->nfcc_coex; + bool channel_changed = nfcc_coex->original_channel != dw->config.chan; + int r; + + trace_dw3000_nfcc_coex_disable(dw); + if (!dw->nfcc_coex.enabled) + return 0; + + dw->config.chan = dw->nfcc_coex.original_channel; + dw->nfcc_coex.enabled = false; + + if (dw->nfcc_coex.configured) { + r = dw3000_nfcc_coex_disable_SPIxMAVAIL_interrupts(dw); + if (r) + return r; + if (channel_changed) { + r = dw3000_configure_chan(dw); + if (r) + return r; + } + r = dw3000_nfcc_coex_restore_config(dw); + if (r) + return r; + } + return 0; +} diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_core.h b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_core.h new file mode 100644 index 0000000..26bfa64 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_core.h @@ -0,0 +1,41 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef __DW3000_NFCC_COEX_CORE_H +#define __DW3000_NFCC_COEX_CORE_H + +#include <linux/module.h> +#include "dw3000_nfcc_coex.h" +#include "dw3000.h" + +extern unsigned dw3000_nfcc_coex_margin_dtu; + +int dw3000_nfcc_coex_cancel_watchdog(struct dw3000 *dw); +int dw3000_nfcc_coex_spi1_avail(struct dw3000 *dw); +int dw3000_nfcc_coex_idle_timeout(struct dw3000 *dw); +void dw3000_nfcc_coex_init(struct dw3000 *dw); +int dw3000_nfcc_coex_enable(struct dw3000 *dw, u8 channel); +int dw3000_nfcc_coex_disable(struct dw3000 *dw); +int dw3000_nfcc_coex_configure(struct dw3000 *dw); + +#endif /* __DW3000_NFCC_COEX_CORE_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_mcps.c b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_mcps.c new file mode 100644 index 0000000..2214859 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_mcps.c @@ -0,0 +1,243 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include "dw3000_nfcc_coex_mcps.h" +#include "dw3000_nfcc_coex_msg.h" +#include "dw3000_nfcc_coex_buffer.h" +#include "dw3000_nfcc_coex_core.h" +#include "dw3000.h" +#include "dw3000_trc.h" +#include "dw3000_core.h" + +#define DW3000_NFCC_COEX_WATCHDOG_DEFAULT_DURATION_MS 24000 + +static int dw3000_nfcc_coex_wakeup_and_send(struct dw3000 *dw, + s32 idle_duration_dtu, + u32 send_timestamp_dtu) +{ + struct dw3000_nfcc_coex *nfcc_coex = &dw->nfcc_coex; + int r; + + trace_dw3000_nfcc_coex_wakeup_and_send( + dw, nfcc_coex->send, idle_duration_dtu, send_timestamp_dtu); + + if (idle_duration_dtu > dw->llhw->anticip_dtu) { + r = dw3000_idle(dw, true, send_timestamp_dtu, + dw3000_nfcc_coex_idle_timeout, + DW3000_OP_STATE_MAX); + goto wakeup_and_send_end; + } else if (dw->current_operational_state == + DW3000_OP_STATE_DEEP_SLEEP) { + r = dw3000_deepsleep_wakeup_now(dw, + dw3000_nfcc_coex_idle_timeout, + send_timestamp_dtu, + DW3000_OP_STATE_MAX); + goto wakeup_and_send_end; + } + + r = dw3000_nfcc_coex_configure(dw); + if (r) + goto wakeup_and_send_end; + r = dw3000_nfcc_coex_message_send(dw); + if (r) + goto wakeup_and_send_end; + return 0; + +wakeup_and_send_end: + if (r) + dw3000_nfcc_coex_disable(dw); + return r; +} + +/** + * dw3000_nfcc_coex_handle_access() - handle access to provide to NFCC. + * @dw: Driver context. + * @data: Address of handle access information. + * @data_len: Number of byte of the data object. + * + * Return: 0 on success, else an error. + */ +static int dw3000_nfcc_coex_handle_access(struct dw3000 *dw, void *data, + int data_len) +{ + const struct llhw_vendor_cmd_nfcc_coex_handle_access *info = data; + struct dw3000_nfcc_coex *nfcc_coex = &dw->nfcc_coex; + const u32 dtu_per_ms = dw->llhw->dtu_freq_hz / 1000; + u32 now_dtu; + s32 idle_duration_dtu; + int r; + + if (!info || data_len != sizeof(*info)) + return -EINVAL; + + if (timer_pending(&nfcc_coex->watchdog_timer)) { + trace_dw3000_nfcc_coex_err(dw, "watchdog timer is pending"); + return -EBUSY; + } + + r = dw3000_nfcc_coex_enable(dw, info->chan); + if (r) + return r; + + now_dtu = dw3000_get_dtu_time(dw); + idle_duration_dtu = info->timestamp_dtu - now_dtu; + + trace_dw3000_nfcc_coex_handle_access(dw, info, idle_duration_dtu); + nfcc_coex->send = info->start ? DW3000_NFCC_COEX_SEND_CLK_SYNC : + DW3000_NFCC_COEX_SEND_CLK_OFFSET; + if (info->start) { + nfcc_coex->version = info->version; + /* + * Save first start session date, to retrieve MSB bits lost + * for next received timestamp through session_time0_dtu. + * It's saved because between session_time0_dtu and + * access_start_dtu, a deep sleep can occur, and so + * `dtu_to_sys_time` must be call after the wake up. + * Between access_start_dtu and now, the duration can be less + * than 2 ms (fira slot) in multi-region feature. + * So the margin is like a second anticip dtu to add to provide + * time for NFC handling. + */ + nfcc_coex->access_start_dtu = + info->timestamp_dtu + dw3000_nfcc_coex_margin_dtu; + } + nfcc_coex->watchdog_timer.expires = + jiffies + + msecs_to_jiffies((info->timestamp_dtu - now_dtu) / dtu_per_ms + + DW3000_NFCC_COEX_WATCHDOG_DEFAULT_DURATION_MS); + add_timer(&nfcc_coex->watchdog_timer); + + /* Send message and so release the SPI close to the nfc_coex_margin. */ + return dw3000_nfcc_coex_wakeup_and_send(dw, idle_duration_dtu, + info->timestamp_dtu); +} + +/** + * dw3000_nfcc_coex_get_access_information() - Forward access info cached. + * @dw: Driver context. + * @data: Address where to write access information. + * @data_len: Number of byte of the data object. + * + * Return: 0 on success, else an error. + */ +static int dw3000_nfcc_coex_get_access_information(struct dw3000 *dw, + void *data, int data_len) +{ + const struct llhw_vendor_cmd_nfcc_coex_get_access_info *access_info = + &dw->nfcc_coex.access_info; + + if (!data || data_len != sizeof(*access_info)) + return -EINVAL; + + memcpy(data, access_info, data_len); + return 0; +} + +/** + * dw3000_nfcc_coex_stop() - Stop NFCC. + * @dw: Driver context. + * @data: Address of stop information. + * @data_len: Number of byte of the data object. + * + * Return: 0 on success, else an error. + */ +static int dw3000_nfcc_coex_stop(struct dw3000 *dw, void *data, int data_len) +{ + const struct llhw_vendor_cmd_nfcc_coex_stop *info = data; + struct dw3000_nfcc_coex *nfcc_coex = &dw->nfcc_coex; + const u32 dtu_per_ms = dw->llhw->dtu_freq_hz / 1000; + u32 now_dtu, send_timestamp_dtu; + s32 idle_duration_dtu; + int r; + + if (data_len && data_len != sizeof(*info)) + return -EINVAL; + + if (timer_pending(&nfcc_coex->watchdog_timer)) { + trace_dw3000_nfcc_coex_err(dw, "watchdog timer is pending"); + return -EBUSY; + } + + r = dw3000_nfcc_coex_enable(dw, dw->config.chan); + if (r) + return r; + + nfcc_coex->send = DW3000_NFCC_COEX_SEND_STOP; + + if (info) { + now_dtu = dw3000_get_dtu_time(dw); + send_timestamp_dtu = info->timestamp_dtu; + idle_duration_dtu = send_timestamp_dtu - now_dtu; + nfcc_coex->watchdog_timer.expires = + jiffies + + msecs_to_jiffies( + (info->timestamp_dtu - now_dtu) / dtu_per_ms + + DW3000_NFCC_COEX_WATCHDOG_DEFAULT_DURATION_MS); + nfcc_coex->version = info->version; + } else { + send_timestamp_dtu = 0; + idle_duration_dtu = 0; + nfcc_coex->watchdog_timer.expires = + jiffies + + msecs_to_jiffies( + DW3000_NFCC_COEX_WATCHDOG_DEFAULT_DURATION_MS); + /* Cancel wakeup timer launch by idle() */ + dw3000_idle_cancel_timer(dw); + } + + add_timer(&nfcc_coex->watchdog_timer); + + /* Send message and so release the SPI close to the nfc_coex_margin. */ + return dw3000_nfcc_coex_wakeup_and_send(dw, idle_duration_dtu, + send_timestamp_dtu); +} + +/** + * dw3000_nfcc_coex_vendor_cmd() - Vendor NFCC coexistence command processing. + * + * @dw: Driver context. + * @vendor_id: Vendor Identifier on 3 bytes. + * @subcmd: Sub-command identifier. + * @data: Null or data related with the sub-command. + * @data_len: Length of the data + * + * Return: 0 on success, 1 to request a stop, error on other value. + */ +int dw3000_nfcc_coex_vendor_cmd(struct dw3000 *dw, u32 vendor_id, u32 subcmd, + void *data, size_t data_len) +{ + /* NFCC needs a D0 chip or above. C0 does not have 2 SPI interfaces. */ + if (__dw3000_chip_version == DW3000_C0_VERSION) + return -EOPNOTSUPP; + + switch (subcmd) { + case LLHW_VENDOR_CMD_NFCC_COEX_HANDLE_ACCESS: + return dw3000_nfcc_coex_handle_access(dw, data, data_len); + case LLHW_VENDOR_CMD_NFCC_COEX_GET_ACCESS_INFORMATION: + return dw3000_nfcc_coex_get_access_information(dw, data, + data_len); + case LLHW_VENDOR_CMD_NFCC_COEX_STOP: + return dw3000_nfcc_coex_stop(dw, data, data_len); + default: + return -EINVAL; + } +} diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_mcps.h b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_mcps.h new file mode 100644 index 0000000..a2ba707 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_mcps.h @@ -0,0 +1,32 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_NFCC_COEX_MCPS_H +#define __DW3000_NFCC_COEX_MCPS_H + +#include <linux/module.h> +#include "dw3000.h" + +int dw3000_nfcc_coex_vendor_cmd(struct dw3000 *dw, u32 vendor_id, u32 subcmd, + void *data, size_t data_len); + +#endif /* __DW3000_NFCC_COEX_MCPS_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_msg.c b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_msg.c new file mode 100644 index 0000000..18b74be --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_msg.c @@ -0,0 +1,330 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include "dw3000_nfcc_coex_msg.h" +#include "dw3000_nfcc_coex_buffer.h" +#include "dw3000_nfcc_coex_core.h" +#include "dw3000.h" +#include "dw3000_trc.h" +#include "dw3000_core.h" + +/* TLVs len helpers. */ +#define TLV_TYPELEN_LEN 2 /* type 1 byte, len 1 byte. */ +#define TLV_U32_LEN (4 + 1) /* u32 + ack/nack. */ +#define TLV_SLOTS_LEN(nbslots) \ + (1 + (8 * (nbslots)) + 1) /* nslots + slots + ack/nack. */ +#define TLV_SLOTS_LIST_SIZE_MAX (1 + (8 * (TLV_MAX_NB_SLOTS))) +#define MSG_NEXT_TLV(buffer, offset) \ + (struct dw3000_nfcc_coex_tlv *)((buffer)->msg.tlvs + (offset)) + +/** + * struct dw3000_nfcc_coex_tlv - Type Length Value. + */ +struct dw3000_nfcc_coex_tlv { + /** + * @type: Identifier of TLV. + */ + u8 type; + /** + * @len: Number of byte of TLV array. + */ + u8 len; + /** + * @tlv: Value of the TLV. + */ + u8 tlv[]; +} __attribute__((packed)); + +/** + * dw3000_nfcc_coex_header_put() - Fill NFCC frame header. + * @dw: Driver context. + * @buffer: Message buffer to fill. + */ +void dw3000_nfcc_coex_header_put(struct dw3000 *dw, + struct dw3000_nfcc_coex_buffer *buffer) +{ + struct dw3000_nfcc_coex_msg *msg = &buffer->msg; + + trace_dw3000_nfcc_coex_header_put(dw, dw->nfcc_coex.version, + dw->nfcc_coex.tx_seq_num); + memcpy(msg->signature, DW3000_NFCC_COEX_SIGNATURE_STR, + DW3000_NFCC_COEX_SIGNATURE_LEN); + msg->ver_id = dw->nfcc_coex.version; + msg->seqnum = dw->nfcc_coex.tx_seq_num; + msg->nb_tlv = 0; + buffer->tlvs_len = 0; +} + +/** + * dw3000_nfcc_coex_tlv_u32_put() - Fill buffer payload for a TLV. + * @buffer: Message buffer to fill. + * @type: Type id of the TLV. + * @value: Value of TLV. + */ +static void dw3000_nfcc_coex_tlv_u32_put(struct dw3000_nfcc_coex_buffer *buffer, + enum dw3000_nfcc_coex_tlv_type type, + u32 value) +{ + struct dw3000_nfcc_coex_msg *msg = &buffer->msg; + struct dw3000_nfcc_coex_tlv *tlv; + u32 *v; + + tlv = MSG_NEXT_TLV(buffer, buffer->tlvs_len); + msg->nb_tlv++; + tlv->type = type; + tlv->len = 4; + v = (u32 *)&tlv->tlv; + *v = value; + buffer->tlvs_len += TLV_TYPELEN_LEN + TLV_U32_LEN; +} + +/** + * dw3000_nfcc_coex_clock_sync_payload_put() - Fill clock sync frame payload. + * @dw: Driver context. + * @buffer: Buffer to set with help of handle_access. + */ +static void +dw3000_nfcc_coex_clock_sync_payload_put(struct dw3000 *dw, + struct dw3000_nfcc_coex_buffer *buffer) +{ + u32 session_time0_sys_time = + dw3000_dtu_to_sys_time(dw, dw->nfcc_coex.access_start_dtu); + + /* Update clock reference. */ + dw->nfcc_coex.session_time0_dtu = dw->nfcc_coex.access_start_dtu; + /* Prepare message. */ + trace_dw3000_nfcc_coex_clock_sync_payload_put(dw, + session_time0_sys_time); + dw3000_nfcc_coex_header_put(dw, buffer); + dw3000_nfcc_coex_tlv_u32_put(buffer, + DW3000_NFCC_COEX_TLV_TYPE_SESSION_TIME0, + session_time0_sys_time); +} + +/** + * dw3000_nfcc_coex_clock_offset_payload_put() - Fill clock offset payload. + * @dw: Driver context. + * @buffer: Buffer to set with help of handle_access. + * @clock_offset_sys_time: Offset to add to next nfcc_coex schedule. + */ +static void dw3000_nfcc_coex_clock_offset_payload_put( + struct dw3000 *dw, struct dw3000_nfcc_coex_buffer *buffer, + u32 clock_offset_sys_time) +{ + trace_dw3000_nfcc_coex_clock_offset_payload_put(dw, + clock_offset_sys_time); + dw3000_nfcc_coex_header_put(dw, buffer); + dw3000_nfcc_coex_tlv_u32_put(buffer, + DW3000_NFCC_COEX_TLV_TYPE_TLV_UWBCNT_OFFS, + clock_offset_sys_time); +} + +/** + * dw3000_nfcc_coex_stop_session_payload_put() - Fill stop session payload. + * @dw: Driver context. + * @buffer: Buffer to set with help of handle_access. + * @session_id: Session id to stop. + */ +static void dw3000_nfcc_coex_stop_session_payload_put( + struct dw3000 *dw, struct dw3000_nfcc_coex_buffer *buffer, + u32 session_id) +{ + trace_dw3000_nfcc_coex_stop_session_payload_put(dw, session_id); + + dw3000_nfcc_coex_header_put(dw, buffer); + + dw3000_nfcc_coex_tlv_u32_put( + buffer, DW3000_NFCC_COEX_TLV_TYPE_STOP_SESSION, session_id); +} + +/** + * dw3000_nfcc_coex_message_send() - Write message for NFCC and release SPI1. + * @dw: Driver context. + * + * Return: 0 on success, else an error. + */ +int dw3000_nfcc_coex_message_send(struct dw3000 *dw) +{ + struct dw3000_nfcc_coex_buffer buffer = {}; + /* Build the absolute sys time offset. */ + u32 offset_sys_time = + (dw->dtu_sync << DW3000_DTU_PER_SYS_POWER) - dw->sys_time_sync; + u32 clock_offset_sys_time; + + switch (dw->nfcc_coex.send) { + case DW3000_NFCC_COEX_SEND_CLK_SYNC: + dw3000_nfcc_coex_clock_sync_payload_put(dw, &buffer); + break; + default: + case DW3000_NFCC_COEX_SEND_CLK_OFFSET: + /* Compute the clock correction to forward to NFCC. */ + clock_offset_sys_time = + offset_sys_time - dw->nfcc_coex.prev_offset_sys_time; + /* Build the message with the clock update to forward. */ + dw3000_nfcc_coex_clock_offset_payload_put( + dw, &buffer, -clock_offset_sys_time); + break; + case DW3000_NFCC_COEX_SEND_STOP: + dw3000_nfcc_coex_stop_session_payload_put( + dw, &buffer, DW3000_NFCC_COEX_SESSION_ID_DEFAULT); + break; + } + + dw->nfcc_coex.prev_offset_sys_time = offset_sys_time; + /* Write message to NFCC and release SP1. */ + return dw3000_nfcc_coex_write_buffer(dw, &buffer, MSG_LEN(buffer)); +} + +/** + * dw3000_nfcc_coex_header_check() - Check header message. + * @dw: Driver context. + * @buffer: Buffer to check. + * + * Return: 0 when buffer contain a valid message, else an error. + */ +static int +dw3000_nfcc_coex_header_check(struct dw3000 *dw, + const struct dw3000_nfcc_coex_buffer *buffer) +{ + const struct dw3000_nfcc_coex_msg *msg = &buffer->msg; + + trace_dw3000_nfcc_coex_header_check(dw, msg->signature, msg->ver_id, + msg->seqnum, msg->nb_tlv); + /* Check signature. */ + if (memcmp(msg->signature, DW3000_NFCC_COEX_SIGNATURE_STR, + DW3000_NFCC_COEX_SIGNATURE_LEN)) { + return -EINVAL; + } + /* Check AP_NFCC Interface Version ID. */ + if (msg->ver_id != dw->nfcc_coex.version) { + return -EINVAL; + } + /* Read number of TLVs. */ + if (msg->nb_tlv > DW3000_NFCC_COEX_MAX_NB_TLV) { + return -EINVAL; + } + /* Check if message is a new one with the sequence number. */ + if (!dw->nfcc_coex.first_rx_message && + (msg->seqnum - dw->nfcc_coex.rx_seq_num) > 0) { + /* TODO: Reject message with bad seqnum. */ + } + + dw->nfcc_coex.rx_seq_num = msg->seqnum; + dw->nfcc_coex.first_rx_message = false; + return 0; +} + +/** + * dw3000_nfcc_coex_tlvs_check() - Set information on a received message. + * @dw: Driver context. + * @buffer: Buffer to read. + * @rx_msg_info: information updated on valid message. + * + * Return: 0 on success, else an error. + */ +static int +dw3000_nfcc_coex_tlvs_check(struct dw3000 *dw, + const struct dw3000_nfcc_coex_buffer *buffer, + struct dw3000_nfcc_coex_rx_msg_info *rx_msg_info) +{ + static const int tlvs_len_max = + DW3000_NFCC_COEX_MSG_MAX_SIZE - MSG_HEADER_LEN; + const struct dw3000_nfcc_coex_msg *msg = &buffer->msg; + const struct dw3000_nfcc_coex_tlv_slot_list *slot_list = NULL; + int tlvs_len = 0; /* Start parsing at first TLV. */ + int i; + + /* Process tlvs. */ + for (i = 0; i < msg->nb_tlv; i++) { + struct dw3000_nfcc_coex_tlv *tlv; + + if ((tlvs_len + sizeof(*tlv)) > tlvs_len_max) + return -EINVAL; + + tlv = MSG_NEXT_TLV(buffer, tlvs_len); + tlvs_len += tlv->len; + + if (tlvs_len > tlvs_len_max) + return -EINVAL; + + trace_dw3000_nfcc_coex_tlv_check(dw, tlv->type, tlv->len, + tlv->tlv); + if (tlv->type == DW3000_NFCC_COEX_TLV_TYPE_SLOT_LIST_UUS) { + /* Reject a new TLV with same type. Behavior not defined. */ + if (slot_list) + return -EINVAL; + /* Check if the tlv size isn't exceeding the list max size */ + if (tlv->len > TLV_SLOTS_LIST_SIZE_MAX) + return -EINVAL; + slot_list = (const struct dw3000_nfcc_coex_tlv_slot_list + *)&tlv->tlv; + /* Update rx_msg_info. */ + if (slot_list->nb_slots > 0) { + const struct dw3000_nfcc_coex_tlv_slot *slot = + &slot_list->slots[0]; + u32 next_in_session_dtu = + slot->t_start_uus + << DW3000_NFCC_COEX_DTU_PER_UUS_POWER; + + rx_msg_info->next_slot_found = true; + rx_msg_info->next_timestamp_dtu = + dw->nfcc_coex.session_time0_dtu + + next_in_session_dtu; + rx_msg_info->next_duration_dtu = + (slot->t_end_uus - slot->t_start_uus) + << DW3000_NFCC_COEX_DTU_PER_UUS_POWER; + } + } + } + + return 0; +} + +/** + * dw3000_nfcc_coex_message_check() - Check and read message. + * @dw: Driver context. + * @buffer: Buffer to read. + * @rx_msg_info: Result of message parsed updated on success. + * + * Return: 0 on success, else an error. + */ +int dw3000_nfcc_coex_message_check( + struct dw3000 *dw, const struct dw3000_nfcc_coex_buffer *buffer, + struct dw3000_nfcc_coex_rx_msg_info *rx_msg_info) +{ + int r; + + r = dw3000_nfcc_coex_header_check(dw, buffer); + if (r) + return r; + + r = dw3000_nfcc_coex_tlvs_check(dw, buffer, rx_msg_info); + if (r) + return r; + + if (rx_msg_info->next_slot_found) + trace_dw3000_nfcc_coex_rx_msg_info( + dw, rx_msg_info->next_timestamp_dtu, + rx_msg_info->next_duration_dtu); + return 0; +} diff --git a/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_msg.h b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_msg.h new file mode 100644 index 0000000..4214088 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_nfcc_coex_msg.h @@ -0,0 +1,96 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_NFCC_COEX_MSG_H +#define __DW3000_NFCC_COEX_MSG_H + +#include <linux/module.h> +#include "dw3000_nfcc_coex.h" +#include "dw3000.h" + +#define TLV_MAX_NB_SLOTS 4 +#define DW3000_NFCC_COEX_SESSION_ID_DEFAULT 0 + +/** + * enum dw3000_nfcc_coex_tlv_type - TLVs types. + * + * @DW3000_NFCC_COEX_TLV_TYPE_UNSPEC: Invalid command. + * @DW3000_NFCC_COEX_TLV_TYPE_SESSION_TIME0: + * Indicate start of UWB session in RCTU time unit. + * @DW3000_NFCC_COEX_TLV_TYPE_SLOT_LIST: + * Indicate the requested next time slots. + * @DW3000_NFCC_COEX_TLV_TYPE_TLV_UWBCNT_OFFS: + * Indicate the UWB clock offset in V1 protocol. + * @DW3000_NFCC_COEX_TLV_TYPE_ERROR: + * Indicate error condition. + * @DW3000_NFCC_COEX_TLV_TYPE_SLOT_LIST_UUS: + * Indicate the UWB clock offset in V2 protocol. + * @DW3000_NFCC_COEX_TLV_TYPE_STOP_SESSION: + * Indicate the stop session in V3 protocol. + */ +enum dw3000_nfcc_coex_tlv_type { + DW3000_NFCC_COEX_TLV_TYPE_UNSPEC, + + DW3000_NFCC_COEX_TLV_TYPE_SESSION_TIME0, + DW3000_NFCC_COEX_TLV_TYPE_SLOT_LIST, + DW3000_NFCC_COEX_TLV_TYPE_TLV_UWBCNT_OFFS, + DW3000_NFCC_COEX_TLV_TYPE_ERROR, + DW3000_NFCC_COEX_TLV_TYPE_SLOT_LIST_UUS, + DW3000_NFCC_COEX_TLV_TYPE_STOP_SESSION +}; + +/** + * struct dw3000_nfcc_coex_tlv_slot - TLV slot definition. + */ +struct dw3000_nfcc_coex_tlv_slot { + /** + * @t_start_uus: Start date in 65536*RCTU (close to usec unit). + */ + u32 t_start_uus; + /** + * @t_end_uus: End date in 65536*RCTU (close to usec unit). + */ + u32 t_end_uus; +}; + +/** + * struct dw3000_nfcc_coex_tlv_slot_list - TLV slots. + */ +struct dw3000_nfcc_coex_tlv_slot_list { + /** + * @nb_slots: Number of slots used. + */ + u8 nb_slots; + /** + * @slots: array of start/end session time. + */ + struct dw3000_nfcc_coex_tlv_slot slots[TLV_MAX_NB_SLOTS]; +} __attribute__((packed)); + +void dw3000_nfcc_coex_header_put(struct dw3000 *dw, + struct dw3000_nfcc_coex_buffer *buffer); +int dw3000_nfcc_coex_message_send(struct dw3000 *dw); +int dw3000_nfcc_coex_message_check( + struct dw3000 *dw, const struct dw3000_nfcc_coex_buffer *buffer, + struct dw3000_nfcc_coex_rx_msg_info *rx_msg_info); + +#endif /* __DW3000_NFCC_COEX_MSG_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_pctt.h b/kernel/drivers/net/ieee802154/dw3000_pctt.h new file mode 100644 index 0000000..74a2542 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_pctt.h @@ -0,0 +1,36 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_PCTT_H +#define __DW3000_PCTT_H + +/** + * struct dw3000_pctt - PCTT coexistence context. + */ +struct dw3000_pctt { + /** + * @enabled: True when pctt is enabled. + */ + bool enabled; +}; + +#endif /* __DW3000_PCTT_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_pctt_mcps.c b/kernel/drivers/net/ieee802154/dw3000_pctt_mcps.c new file mode 100644 index 0000000..400080c --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_pctt_mcps.c @@ -0,0 +1,66 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include "dw3000_pctt_mcps.h" +#include "dw3000.h" +#include "dw3000_core.h" + +#include <net/vendor_cmd.h> + +int dw3000_pctt_vendor_cmd(struct dw3000 *dw, u32 vendor_id, u32 subcmd, + void *data, size_t data_len) +{ + struct llhw_vendor_cmd_pctt_setup_hw *info = data; + struct dw3000_config *config = &dw->config; + int rc; + + if (info) { + if (sizeof(*info) != data_len) + return -EINVAL; + + if (dw->pctt.enabled) + return -EBUSY; + + config->chan = info->chan; + config->txPreambLength = !info->preamble_duration ? + DW3000_PLEN_32 : + DW3000_PLEN_64; + config->txCode = config->rxCode = info->preamble_code_index; + config->sfdType = !info->sfd_id ? DW3000_SFD_TYPE_STD : + DW3000_SFD_TYPE_4Z; + config->dataRate = !info->psdu_data_rate ? DW3000_BR_6M8 : + DW3000_BR_850K; + config->stsMode = info->rframe_config; + + rc = dw3000_set_promiscuous(dw, true); + if (rc) + return rc; + rc = dw3000_configure_sys_cfg(dw, config); + if (rc) + return rc; + rc = dw3000_configure_chan(dw); + if (rc) + return rc; + } + dw->pctt.enabled = !!info; + return dw3000_enable_auto_fcs(dw, !dw->pctt.enabled); +} diff --git a/kernel/drivers/net/ieee802154/dw3000_pctt_mcps.h b/kernel/drivers/net/ieee802154/dw3000_pctt_mcps.h new file mode 100644 index 0000000..6eda5eb --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_pctt_mcps.h @@ -0,0 +1,34 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_PCTT_MCPS_H +#define __DW3000_PCTT_MCPS_H + +#include <linux/module.h> + +/* Forward declaration. */ +struct dw3000; + +int dw3000_pctt_vendor_cmd(struct dw3000 *dw, u32 vendor_id, u32 subcmd, + void *data, size_t data_len); + +#endif /* __DW3000_PCTT_MCPS_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_perf.h b/kernel/drivers/net/ieee802154/dw3000_perf.h new file mode 100644 index 0000000..98d2f92 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_perf.h @@ -0,0 +1,144 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_PERF_H +#define __DW3000_PERF_H + +#include <linux/version.h> +#include <linux/perf_event.h> + +static struct perf_event_attr perf_hw_attr[] = { + { + .type = PERF_TYPE_HARDWARE, + .config = PERF_COUNT_HW_CPU_CYCLES, + .size = sizeof(struct perf_event_attr), + .pinned = 1, + .disabled = 1, + }, + { + .type = PERF_TYPE_HARDWARE, + .config = PERF_COUNT_HW_INSTRUCTIONS, + .size = sizeof(struct perf_event_attr), + .pinned = 1, + .disabled = 1, + }, + { + .type = PERF_TYPE_HARDWARE, + .config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS, + .size = sizeof(struct perf_event_attr), + .pinned = 1, + .disabled = 1, + }, + { + .type = PERF_TYPE_HARDWARE, + .config = PERF_COUNT_HW_CACHE_MISSES, + .size = sizeof(struct perf_event_attr), + .pinned = 1, + .disabled = 1, + } +}; + +#define PERF_EVT_COUNT (ARRAY_SIZE(perf_hw_attr)) + +static const char *const perf_hw_evt_name[PERF_EVT_COUNT] = { + "cpu cycles ", "instructions", "branch insts", "cache misses" +}; + +static struct perf_event *perf_hw_evt[PERF_EVT_COUNT]; + +/* Callback function for perf event subsystem */ +static void overflow_callback(struct perf_event *event, + struct perf_sample_data *data, + struct pt_regs *regs) +{ + struct dw3000 *dw = event->overflow_handler_context; + + event->hw.interrupts = 0; + dev_warn(dw->dev, "test mode: counter overflow for event %x:%llx\n", + event->attr.type, event->attr.config); +} + +static inline void perf_event_create_all(struct dw3000 *dw) +{ + int i; + + for (i = 0; i < PERF_EVT_COUNT; i++) { + struct perf_event *evt = perf_event_create_kernel_counter( + &perf_hw_attr[i], smp_processor_id(), NULL, + overflow_callback, dw); + if (IS_ERR(evt)) { + dev_warn( + dw->dev, + "test mode: cannot create perf_hw_evt %d (err %ld)\n", + i, PTR_ERR(evt)); + evt = NULL; + } + perf_hw_evt[i] = evt; + } +} + +static inline void perf_event_release_all(void) +{ + int i; + + for (i = 0; i < PERF_EVT_COUNT; i++) { + struct perf_event *evt = perf_hw_evt[i]; + + if (evt) + perf_event_release_kernel(evt); + } +} + +static inline void perf_event_start_all(void) +{ + int i; + + for (i = 0; i < PERF_EVT_COUNT; i++) { + struct perf_event *evt = perf_hw_evt[i]; + + if (evt) + perf_event_enable(evt); + } +} + +static inline void perf_event_stop_all(u64 *vals) +{ + u64 dummy[2]; + int i; + + for (i = 0; i < PERF_EVT_COUNT; i++) { + struct perf_event *evt = perf_hw_evt[i]; + + if (evt) { + vals[i] = perf_event_read_value(evt, &dummy[0], + &dummy[1]); +#if (KERNEL_VERSION(5, 5, 0) > LINUX_VERSION_CODE) + perf_event_disable(evt); + local64_set(&evt->count, 0); +#else + perf_event_pause(evt, true); +#endif + } + } +} + +#endif /* __DW3000_PERF_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_pm.h b/kernel/drivers/net/ieee802154/dw3000_pm.h new file mode 100644 index 0000000..9baf0c7 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_pm.h @@ -0,0 +1,58 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_PM_H +#define __DW3000_PM_H + +#include <linux/pm_qos.h> +#include <linux/version.h> + +#include "dw3000.h" + +#if (KERNEL_VERSION(4, 15, 0) > LINUX_VERSION_CODE) +#define PM_QOS_RESUME_LATENCY_NO_CONSTRAINT S32_MAX +#endif + +extern int dw3000_qos_latency; + +static inline void dw3000_pm_qos_add_request(struct dw3000 *dw, int latency) +{ +#if (KERNEL_VERSION(5, 7, 0) <= LINUX_VERSION_CODE) + cpu_latency_qos_add_request(&dw->pm_qos_req, latency); +#endif +} + +static inline void dw3000_pm_qos_update_request(struct dw3000 *dw, int latency) +{ +#if (KERNEL_VERSION(5, 7, 0) <= LINUX_VERSION_CODE) + cpu_latency_qos_update_request(&dw->pm_qos_req, latency); +#endif +} + +static inline void dw3000_pm_qos_remove_request(struct dw3000 *dw) +{ +#if (KERNEL_VERSION(5, 7, 0) <= LINUX_VERSION_CODE) + cpu_latency_qos_remove_request(&dw->pm_qos_req); +#endif +} + +#endif /* __DW3000_PM_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_power_stats.h b/kernel/drivers/net/ieee802154/dw3000_power_stats.h new file mode 100644 index 0000000..baa857c --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_power_stats.h @@ -0,0 +1,105 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +/** + * dw3000_power_stats() - compute time elapsed in dw3000 states + * @dw: the DW device on which state is changed + * @state: the new state + * @len_or_date: frame length to be transmitted or RX dates in DTU + * + * Update power statistics according current state leaved and new state. + * States > RUN are handled specifically. + */ +static inline void dw3000_power_stats(struct dw3000 *dw, int state, + int len_or_date) +{ + struct dw3000_power *pw = &dw->power; + int cstate = min(pw->cur_state, DW3000_PWR_RUN); + int nstate = min(state, DW3000_PWR_RUN); + u64 boot_time_ns = ktime_get_boottime_ns(); + s64 duration; + u32 adjust; + u32 cur_dtu_time; + + /* Trace call */ + trace_dw3000_power_stats(dw, state, boot_time_ns, len_or_date); + /* Sanity checks first */ + if (state < 0 || state >= DW3000_PWR_MAX) + return; + /* Calculate duration of current state. */ + duration = boot_time_ns - pw->start_time; + /* Update basic state statistics */ + pw->stats[cstate].dur += duration; + if (nstate != cstate) + pw->stats[nstate].count++; + /* Handle specific states */ + cstate = pw->cur_state; + switch (state) { + case DW3000_PWR_IDLE: + adjust = 0; + if (cstate == DW3000_PWR_TX) { + /* TX duration is just saved pw->tx_adjust */ + adjust = (u32)pw->tx_adjust; + } else if (cstate == DW3000_PWR_RX) { + /* RX duration is just cur_dtu_time-pw->rx_start */ + cur_dtu_time = (u32)len_or_date; + if (!cur_dtu_time) { + /* RX frame end time is not given, so get current DTU. */ + cur_dtu_time = + dw3000_ktime_to_dtu(dw, boot_time_ns); + } + adjust = cur_dtu_time - pw->rx_start; + } + pw->stats[cstate].dur += adjust; + if (state != cstate) + pw->stats[DW3000_PWR_IDLE].count++; + break; + case DW3000_PWR_TX: + /* TX time is calculated according frame len only */ + pw->tx_adjust = + dw3000_frame_duration_dtu(dw, len_or_date, true); + pw->stats[DW3000_PWR_TX].count++; + break; + case DW3000_PWR_RX: + /* RX time is calculated using start time and reception time */ + cur_dtu_time = (u32)len_or_date; + if (!cur_dtu_time) { + /* Start time is unknown for immediate RX but we need + it, so get current DTU time. */ + cur_dtu_time = dw3000_ktime_to_dtu(dw, boot_time_ns); + } + pw->rx_start = cur_dtu_time; + pw->stats[DW3000_PWR_RX].count++; + break; + case DW3000_PWR_RUN: + /* Entering RUN state also enter IDLE state */ + if (state != cstate) + pw->stats[DW3000_PWR_IDLE].count++; + break; + default: + break; + } + /* Update current information */ + pw->start_time = boot_time_ns; + pw->cur_state = state; +} diff --git a/kernel/drivers/net/ieee802154/dw3000_spi.c b/kernel/drivers/net/ieee802154/dw3000_spi.c new file mode 100644 index 0000000..cadc9b1 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_spi.c @@ -0,0 +1,296 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <linux/version.h> +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/of.h> + +#include "dw3000.h" +#include "dw3000_pm.h" +#include "dw3000_core.h" +#include "dw3000_stm.h" +#include "dw3000_mcps.h" +#include "dw3000_debugfs.h" + +/* Default value for auto_deep_sleep_margin. + * Set to -1 (disabled) until we want to have deep-sleep enabled by default. */ +#define DW3000_AUTO_DEEP_SLEEP_MARGIN_US -1 + +int dw3000_qos_latency = 0; + +unsigned dw3000_regulator_delay_us = 1000; + +static int dw3000_lna_pa_mode = 0; +module_param_named(lna_pa_mode, dw3000_lna_pa_mode, int, 0444); +MODULE_PARM_DESC( + lna_pa_mode, + "Configure LNA/PA mode. May conflict with WiFi coexistence GPIO number, 0 for disabled (default)"); + +/** + * dw3000_spi_probe() - Probe and initialize DW3000 SPI device + * @spi: the SPI device to probe and initialize + * + * Called when module is loaded and an SPI controller driver exist. + * This function allocates private device structure and then initialize + * + * - SPI, HRTimer, waitqueue, + * - sysfs/debugfs, + * - regulators, GPIO & IRQ from DT, + * - IRQ & events processing thread, + * + * Then, if no errors, the device is registered to MCPS upper-layer and + * the processing thread is started to do device probing. + * + * Return: 0 if device probed correctly else a negative error. + */ +static int dw3000_spi_probe(struct spi_device *spi) +{ + struct dw3000 *dw; + int rc; + int dw3000_thread_cpu; + + /* Allocate MCPS 802.15.4 device */ + dw = dw3000_mcps_alloc(&spi->dev); + if (!dw) { + rc = -ENOMEM; + goto err_alloc_hw; + } + dw->llhw->hw->parent = &spi->dev; + spi_set_drvdata(spi, dw); + dw->spi = spi; + + /* Initialization of the wifi coex parameters */ + rc = dw3000_setup_wifi_coex(dw); + if (rc != 0) + goto err_wifi_coex; + + /* Initialization of thread cpu */ + rc = dw3000_setup_thread_cpu(dw, &dw3000_thread_cpu); + if (rc != 0) + goto err_thread_cpu; + + /* Initialization of qos latency */ + rc = dw3000_setup_qos_latency(dw); + if (rc != 0) + goto err_qos_latency; + + /* Initialization of regulator delay */ + rc = dw3000_setup_regulator_delay(dw); + if (rc != 0) + goto err_regulator_delay; + + dw->lna_pa_mode = (s8)dw3000_lna_pa_mode; +#if (KERNEL_VERSION(4, 13, 0) <= LINUX_VERSION_CODE) +#if (KERNEL_VERSION(5, 9, 0) <= LINUX_VERSION_CODE) + dw->spi_pid = spi->controller->kworker->task->pid; +#else + dw->spi_pid = spi->controller->kworker.task->pid; +#endif +#else + dw->spi_pid = spi->master->kworker.task->pid; +#endif + dw->auto_sleep_margin_us = DW3000_AUTO_DEEP_SLEEP_MARGIN_US; + dw->current_operational_state = DW3000_OP_STATE_OFF; + init_waitqueue_head(&dw->operational_state_wq); + /* Initialization of the idle timer for wakeup. */ + hrtimer_init(&dw->idle_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + dw->idle_timer.function = dw3000_idle_timeout; + + dev_info(dw->dev, "Loading driver...06202023"); + dw3000_sysfs_init(dw); + + /* Setup SPI parameters */ + dev_info(dw->dev, "setup mode: %d, %u bits/w, %u Hz max\n", + (int)(spi->mode & (SPI_CPOL | SPI_CPHA)), spi->bits_per_word, + spi->max_speed_hz); + dev_info(dw->dev, "can_dma: %d\n", spi->master->can_dma != NULL); + spi->bits_per_word = 8; +#if (KERNEL_VERSION(5, 3, 0) <= LINUX_VERSION_CODE) + spi->rt = 1; +#endif +#if (KERNEL_VERSION(5, 9, 0) <= LINUX_VERSION_CODE) + /* Quirk to force spi_set_cs() in spi_setup() to do something! + !!! Not doing this results in CS line stay LOW and SPIRDY IRQ + isn't fired later when powering-on the device. Previous kernel + don't have this bug as it always apply new CS state. */ + spi->master->last_cs_enable = true; +#endif + /* Save configured device max speed */ + dw->of_max_speed_hz = spi->max_speed_hz; + /* Setup SPI and put CS line in HIGH state! */ + rc = spi_setup(spi); + if (rc != 0) + goto err_spi_setup; + + /* Request and setup regulators if availables*/ + dw3000_setup_regulators(dw); + + /* Request and setup the reset GPIO pin */ + /* This leave the DW3000 in reset state until dw3000_hardreset() put + the GPIO back in input mode. + This also ensure no spurious IRQ fire during the dw3000_setup_irq() + call below. (If the GPIO state is well maintained low). */ + rc = dw3000_setup_reset_gpio(dw); + if (rc != 0) + goto err_setup_gpios; + + /* Allocate pre-computed SPI messages for fast access some registers */ + rc = dw3000_transfers_init(dw); + if (rc != 0) + goto err_transfers_init; + + /* Initialise state descriptor */ + /* This ensure wait queue exist before IRQ handler is setup in case + of spurious IRQ (mainly because hw problem with reset GPIO). */ + rc = dw3000_state_init(dw, dw3000_thread_cpu); + if (rc != 0) { + dev_err(dw->dev, "state machine initialisation failed: %d\n", + rc); + goto err_state_init; + } + + dev_info(dw->dev, "SPI pid: %u, dw3000 pid: %u\n", dw->spi_pid, + dw->dw3000_pid); + /* Request and setup the irq GPIO pin */ + rc = dw3000_setup_irq(dw); + if (rc != 0) + goto err_setup_irq; + + /* + * Initialize PM QoS. Using the default latency won't change anything + * to the QoS list + */ + dw3000_pm_qos_add_request(dw, PM_QOS_DEFAULT_VALUE); + + /* Start state machine & initialise device using high-prio thread */ + rc = dw3000_state_start(dw); + if (rc != 0) + goto err_state_start; + + /* Debugfs interface */ + rc = dw3000_debugsfs_init(dw); + if (rc != 0) + goto err_debugfs; + + /* Register MCPS 802.15.4 device */ + rc = dw3000_mcps_register(dw); + if (rc != 0) { + dev_err(&spi->dev, "could not register: %d\n", rc); + goto err_register_hw; + } + + /* All is ok */ + return 0; + +err_register_hw: + dw3000_debugfs_remove(dw); +err_debugfs: +err_state_start: + dw3000_pm_qos_remove_request(dw); +err_setup_irq: + dw3000_state_stop(dw); +err_state_init: + dw3000_transfers_free(dw); +err_transfers_init: +err_setup_gpios: +err_spi_setup: + dw3000_sysfs_remove(dw); +err_regulator_delay: +err_qos_latency: +err_thread_cpu: +err_wifi_coex: + dw3000_mcps_free(dw); + spi_set_drvdata(spi, NULL); +err_alloc_hw: + return rc; +} + +/** + * dw3000_spi_remove() - Remove DW3000 SPI device + * @spi: the SPI device to remove + * + * Called when module is unloaded, this function removes all + * sysfs/debugfs files, unregister device from the MCPS + * module and them free all remaining resources. + * + * Return: always 0 + */ +static int dw3000_spi_remove(struct spi_device *spi) +{ + struct dw3000 *dw = spi_get_drvdata(spi); + + if (dw == NULL) + /* Error during probe, all already freed */ + return 0; + + dev_dbg(dw->dev, "unloading..."); + + /* Remove sysfs files */ + dw3000_debugfs_remove(dw); + dw3000_sysfs_remove(dw); + /* Unregister subsystems */ + dw3000_mcps_unregister(dw); + /* Stop state machine */ + dw3000_state_stop(dw); + dw3000_pm_qos_remove_request(dw); + /* Free pre-computed SPI messages */ + dw3000_transfers_free(dw); + /* Release the mcps 802.15.4 device */ + dw3000_cir_data_alloc_count(dw, 0); + dw3000_mcps_free(dw); + + return 0; +} + +enum { DW3000, +}; + +static const struct of_device_id dw3000_of_ids[] = { + { .compatible = "decawave,dw3000", .data = (void *)DW3000 }, + {}, +}; +MODULE_DEVICE_TABLE(of, dw3000_of_ids); + +static const struct spi_device_id dw3000_spi_ids[] = { + { "dw3000", DW3000 }, + {}, +}; +MODULE_DEVICE_TABLE(spi, dw3000_spi_ids); + +static struct spi_driver dw3000_driver = { + .driver = { + .name = "dw3000", + .of_match_table = of_match_ptr(dw3000_of_ids), + }, + .id_table = dw3000_spi_ids, + .probe = dw3000_spi_probe, + .remove = dw3000_spi_remove, +}; +module_spi_driver(dw3000_driver); + +#ifdef GITVERSION +MODULE_VERSION(GITVERSION); +#endif +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("David Girault <david.girault@qorvo.com>"); +MODULE_DESCRIPTION("DecaWave DW3000 IEEE 802.15.4 driver"); diff --git a/kernel/drivers/net/ieee802154/dw3000_stm.c b/kernel/drivers/net/ieee802154/dw3000_stm.c new file mode 100644 index 0000000..5b85643 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_stm.c @@ -0,0 +1,406 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <linux/version.h> +#include <linux/module.h> +#include <linux/workqueue.h> +#include <linux/sched.h> +#include <linux/mutex.h> +#include <linux/of.h> + +#include "dw3000.h" +#include "dw3000_core.h" + +#define DW3000_MIN_CLAMP_VALUE 460 + +/* First version with sched_setattr_nocheck: v4.16-rc1~164^2~5 */ +#if (KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE) +#include <uapi/linux/sched/types.h> +#endif + +static int dw3000_min_clamp_value = 0; + +module_param_named(min_clamp_value, dw3000_min_clamp_value, int, 0644); +MODULE_PARM_DESC(min_clamp_value, "Sets the minimum cpu frequency the dw3000 thread must run at"); + + +static void dw3000_get_clamp_from_dt(struct dw3000 *dw) { + int dt_clamp = DW3000_MIN_CLAMP_VALUE; + int ret; + + if (!dw->dev->of_node) + return; + /* debug value is priority */ + if (dw3000_min_clamp_value) { + dw->min_clamp_value = dw3000_min_clamp_value; + dev_info(dw->dev, "using debug min clamp=%d\n", dw->min_clamp_value); + return; + } + + ret = of_property_read_u32(dw->dev->of_node, "min_clamp", &dt_clamp); + if (ret) { + dev_err(dw->dev, "error reading dt_clamp ret=%d\n", ret); + } + dw->min_clamp_value = dt_clamp; + dev_info(dw->dev, "dt_clamp=%d\n", dw->min_clamp_value); +} + +static inline int dw3000_set_sched_attr(struct dw3000 *dw, struct task_struct *p) +{ +#if (KERNEL_VERSION(5, 9, 0) > LINUX_VERSION_CODE) + struct sched_param sched_par = { .sched_priority = MAX_RT_PRIO - 2 }; + /* Increase thread priority */ + return sched_setscheduler(p, SCHED_FIFO, &sched_par); +#else + struct sched_attr attr = { .sched_policy = SCHED_FIFO, + .sched_priority = MAX_RT_PRIO - 2, + .sched_flags = SCHED_FLAG_UTIL_CLAMP_MIN, + .sched_util_min = dw->min_clamp_value }; + return sched_setattr_nocheck(p, &attr); +#endif +} + +/* Enqueue work item(s) */ +void dw3000_enqueue(struct dw3000 *dw, unsigned long work) +{ + struct dw3000_state *stm = &dw->stm; + unsigned long flags; + + spin_lock_irqsave(&stm->work_wq.lock, flags); + stm->pending_work |= work; + wake_up_locked(&stm->work_wq); + spin_unlock_irqrestore(&stm->work_wq.lock, flags); +} + +/* Enqueue a generic work and wait for execution */ +int dw3000_enqueue_generic(struct dw3000 *dw, struct dw3000_stm_command *cmd) +{ + struct dw3000_state *stm = &dw->stm; + unsigned long flags; + int work = DW3000_COMMAND_WORK; + + if (current == stm->mthread) { + /* We can't enqueue a new work from the same context and wait, + but it can be executed directly instead. */ + return cmd->cmd(dw, cmd->in, cmd->out); + } + + /* Mutex is used in dw3000_enqueue_generic() + * This protection will work with the spinlock in order to allow + * the CPU to sleep and avoid ressources wasting during spinning + */ + if (mutex_lock_interruptible(&stm->mtx) == -EINTR) { + dev_err(dw->dev, "work enqueuing interrupted by signal"); + return -EINTR; + } + /* Slow path if not in STM thread context */ + spin_lock_irqsave(&stm->work_wq.lock, flags); + stm->pending_work |= work; + stm->generic_work = cmd; + wake_up_locked(&stm->work_wq); + wait_event_interruptible_locked_irq(stm->work_wq, + !(stm->pending_work & work)); + spin_unlock_irqrestore(&stm->work_wq.lock, flags); + mutex_unlock(&stm->mtx); + return cmd->ret; +} + +/* Enqueue a timer work and don't wait for execution because sleeping in not + * possible from a timer callback function. + */ +void dw3000_enqueue_timer(struct dw3000 *dw, struct dw3000_stm_command *cmd) +{ + struct dw3000_state *stm = &dw->stm; + unsigned long flags; + + spin_lock_irqsave(&stm->work_wq.lock, flags); + if (stm->pending_work & DW3000_TIMER_WORK) { + /* A timer cmd is already queued. */ + spin_unlock_irqrestore(&stm->work_wq.lock, flags); + dev_err(dw->dev, + "A timer cmd is already queued, this cmd will be ignored\n"); + return; + } + stm->pending_work |= DW3000_TIMER_WORK; + /* The cmd should not be stored on the stack of the calling function. */ + stm->timer_work = *cmd; + wake_up_locked(&stm->work_wq); + /* Can't unlock in the event thread, when the cmd is finished, because + * the current function is executed in the timer function in atomic context. + * If the unlock is made in the event thread, a preempt leak warning + * occurs in call_timer_fn(). + * So, it's less bad to unlock here. + */ + spin_unlock_irqrestore(&stm->work_wq.lock, flags); + /* Can't return cmd->ret because it's not yet executed. */ +} + +/* Dequeue work item(s) */ +void dw3000_dequeue(struct dw3000 *dw, unsigned long work) +{ + struct dw3000_state *stm = &dw->stm; + unsigned long flags; + + spin_lock_irqsave(&stm->work_wq.lock, flags); + stm->pending_work &= ~work; + wake_up_locked(&stm->work_wq); + spin_unlock_irqrestore(&stm->work_wq.lock, flags); +} + +/* Enqueue IRQ work */ +void dw3000_enqueue_irq(struct dw3000 *dw) +{ + struct dw3000_state *stm = &dw->stm; + unsigned long flags; + + spin_lock_irqsave(&stm->work_wq.lock, flags); + if (!(stm->pending_work & DW3000_IRQ_WORK)) { + stm->pending_work |= DW3000_IRQ_WORK; + disable_irq_nosync(dw->spi->irq); + } + wake_up_locked(&stm->work_wq); + spin_unlock_irqrestore(&stm->work_wq.lock, flags); +} + +void dw3000_clear_irq(struct dw3000 *dw) +{ + struct dw3000_state *stm = &dw->stm; + unsigned long flags; + + spin_lock_irqsave(&stm->work_wq.lock, flags); + stm->pending_work &= ~DW3000_IRQ_WORK; + enable_irq(dw->spi->irq); + spin_unlock_irqrestore(&stm->work_wq.lock, flags); +} + +/* Wait for new work in the queue */ +void dw3000_wait_pending_work(struct dw3000 *dw) +{ + struct dw3000_state *stm = &dw->stm; + unsigned long flags; + + spin_lock_irqsave(&stm->work_wq.lock, flags); + wait_event_interruptible_locked_irq( + stm->work_wq, stm->pending_work || kthread_should_stop()); + spin_unlock_irqrestore(&stm->work_wq.lock, flags); +} + +/* Read work queue state */ +unsigned long dw3000_get_pending_work(struct dw3000 *dw) +{ + struct dw3000_state *stm = &dw->stm; + unsigned long work; + unsigned long flags; + + spin_lock_irqsave(&stm->work_wq.lock, flags); + work = stm->pending_work; + spin_unlock_irqrestore(&stm->work_wq.lock, flags); + + return work; +} + +/* Chip detect work that run inside the high-priority thread below */ +static int dw3000_detect_work(struct dw3000 *dw, const void *in, void *out) +{ + int rc; + + /* Now, read DEV_ID and initialise chip version */ + dev_notice(dw->dev, "checking device presence\n"); + rc = dw3000_check_devid(dw); + if (rc) { + dev_err(dw->dev, "device checking failed: %d\n", rc); + dw3000_poweroff(dw); // Force power-off if error. + return rc; + } + dev_notice(dw->dev, "device present\n"); + + /* Read OTP data early */ + rc = dw3000_read_otp(dw, DW3000_READ_OTP_PID | DW3000_READ_OTP_LID); + if (unlikely(rc)) { + dev_err(dw->dev, "device OTP read has failed (%d)\n", rc); + return rc; + } + + /* Now, we just power-off the device waiting for it to be used by the + * MAC to avoid power consumption. Except if SPI tests are enabled. */ + rc = dw3000_spitests_enabled(dw) ? 0 : dw3000_poweroff(dw); + return rc; +} + +/* Event handling thread function */ +int dw3000_event_thread(void *data) +{ + struct dw3000 *dw = data; + struct dw3000_state *stm = &dw->stm; + unsigned long pending_work = 0; + + /* Run until stopped */ + while (!kthread_should_stop()) { + /* TODO: State independent errors (ex: PLL_HILO) */ + + /* Pending work items */ + pending_work = dw3000_get_pending_work(dw); + + /* TODO: SPI/HW errors. + * Every function that uses SPI transmission must enqueue + * DW3000_ERROR_WORK item if any error occurs. + */ + + /* Check IRQ activity */ + if (pending_work & DW3000_IRQ_WORK) { + /* Handle the event in the ISR */ + dw3000_isr(dw); + dw3000_clear_irq(dw); + continue; + } + + /* In nearly all states, we can execute generic works. */ + if (pending_work & DW3000_COMMAND_WORK) { + struct dw3000_stm_command *cmd = stm->generic_work; + bool is_detect_work = cmd->cmd == dw3000_detect_work; + + cmd->ret = cmd->cmd(dw, cmd->in, cmd->out); + dw3000_dequeue(dw, DW3000_COMMAND_WORK); + if (unlikely(is_detect_work && + dw3000_spitests_enabled(dw))) { + /* Run SPI tests if enabled after dw3000_detect_work. */ + dw3000_spitests(dw); + /* Power down the device after SPI tests */ + dw3000_poweroff(dw); + } + } + + /* Execute the cmd from a timer handler that can't sleep. */ + if (pending_work & DW3000_TIMER_WORK) { + struct dw3000_stm_command *cmd = &stm->timer_work; + + cmd->ret = cmd->cmd(dw, cmd->in, cmd->out); + dw3000_dequeue(dw, DW3000_TIMER_WORK); + } + + if (!pending_work) { + /* Wait for more work */ + dw3000_wait_pending_work(dw); + } + } + + /* Make sure device is off */ + dw3000_remove(dw); + /* Power down the device */ + dw3000_poweroff(dw); + + dev_dbg(dw->dev, "thread finished\n"); + return 0; +} + +/* Prepare state machine */ +int dw3000_state_init(struct dw3000 *dw, int cpu) +{ + struct dw3000_state *stm = &dw->stm; + int rc; + /* Clear memory */ + memset(stm, 0, sizeof(*stm)); + + /* Wait queues */ + init_waitqueue_head(&stm->work_wq); + + mutex_init(&stm->mtx); + + /* SKIP: Setup timers (state timeout and ADC timers) */ + + /* Init event handler thread */ + stm->mthread = kthread_create(dw3000_event_thread, dw, "dw3000-%s", + dev_name(dw->dev)); + if (IS_ERR(stm->mthread)) { + int err = PTR_ERR(stm->mthread); + stm->mthread = NULL; + return err; + } + get_task_struct(stm->mthread); + if (cpu >= 0) + kthread_bind(stm->mthread, (unsigned)cpu); + dw->dw3000_pid = stm->mthread->pid; + + /* Increase thread priority */ + dw3000_get_clamp_from_dt(dw); + rc = dw3000_set_sched_attr(dw, stm->mthread); + if (rc) + dev_err(dw->dev, "dw3000_set_sched_attr failed: %d\n", rc); + return 0; +} + +/* Start state machine */ +int dw3000_state_start(struct dw3000 *dw) +{ + struct dw3000_state *stm = &dw->stm; + struct dw3000_stm_command cmd = { dw3000_detect_work, NULL, NULL }; + unsigned long flags; + int rc; + + /* Ensure spurious IRQ that may come during dw3000_setup_irq() (because + IRQ pin is already HIGH) isn't handle by the STM thread. */ + spin_lock_irqsave(&stm->work_wq.lock, flags); + stm->pending_work &= ~DW3000_IRQ_WORK; + spin_unlock_irqrestore(&stm->work_wq.lock, flags); + + /* Start state machine thread */ + wake_up_process(stm->mthread); + dev_dbg(dw->dev, "state machine started\n"); + + /* Turn on power and de-assert reset GPIO */ + rc = dw3000_poweron(dw); + if (rc) { + dev_err(dw->dev, "device power on failed: %d\n", rc); + return rc; + } + /* Ensure RESET GPIO for enough time */ + rc = dw3000_hardreset(dw); + if (rc) { + dev_err(dw->dev, "hard reset failed: %d\n", rc); + return rc; + } + /* and wait SPI ready IRQ */ + rc = dw3000_wait_idle_state(dw); + if (rc) { + dev_err(dw->dev, "wait device power on failed: %d\n", rc); + return rc; + } + /* Do chip detection and return result to caller */ + return dw3000_enqueue_generic(dw, &cmd); +} + +/* Stop state machine */ +int dw3000_state_stop(struct dw3000 *dw) +{ + struct dw3000_state *stm = &dw->stm; + + if (stm->mthread == NULL) + return 0; /* already stopped or not created yet */ + + /* Stop state machine thread */ + kthread_stop(stm->mthread); + put_task_struct(stm->mthread); + stm->mthread = NULL; + + dev_dbg(dw->dev, "state machine stopped\n"); + return 0; +} diff --git a/kernel/drivers/net/ieee802154/dw3000_stm.h b/kernel/drivers/net/ieee802154/dw3000_stm.h new file mode 100644 index 0000000..81a67dd --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_stm.h @@ -0,0 +1,75 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_STM_H +#define __DW3000_STM_H + +struct dw3000; + +/* Pending work bits */ +enum { DW3000_IRQ_WORK = BIT(0), + DW3000_COMMAND_WORK = BIT(1), + DW3000_TIMER_WORK = BIT(2), +}; + +/* Custom function for command */ +typedef int (*cmd_func)(struct dw3000 *dw, const void *in, void *out); + +/* Generic command descriptor */ +struct dw3000_stm_command { + cmd_func cmd; + const void *in; + void *out; + int ret; +}; + +/* DW3000 state machine */ +struct dw3000_state { + /* Pending work bitmap */ + unsigned long pending_work; + /* Error recovery count */ + unsigned int recovery_count; + /* Generic work argument */ + struct dw3000_stm_command *generic_work; + /* Timer work argument */ + struct dw3000_stm_command timer_work; + /* Event handler thread */ + struct task_struct *mthread; + /* Wait queue */ + wait_queue_head_t work_wq; + /* Enqueue generic mutex */ + struct mutex mtx; +}; + +/* Event handler of the state machine */ +int dw3000_event_thread(void *data); + +void dw3000_enqueue(struct dw3000 *dw, unsigned long work); +void dw3000_enqueue_irq(struct dw3000 *dw); +int dw3000_enqueue_generic(struct dw3000 *dw, struct dw3000_stm_command *cmd); +void dw3000_enqueue_timer(struct dw3000 *dw, struct dw3000_stm_command *cmd); + +int dw3000_state_init(struct dw3000 *dw, int cpu); +int dw3000_state_start(struct dw3000 *dw); +int dw3000_state_stop(struct dw3000 *dw); + +#endif /* __DW3000_STM_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_testmode.c b/kernel/drivers/net/ieee802154/dw3000_testmode.c new file mode 100644 index 0000000..1097d2d --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_testmode.c @@ -0,0 +1,436 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/slab.h> +#include <net/netlink.h> + +#include "dw3000.h" +#include "dw3000_core.h" +#include "dw3000_trc.h" +#include "dw3000_testmode.h" +#include "dw3000_testmode_nl.h" + +int set_hrp_uwb_params(struct mcps802154_llhw *llhw, int prf, int psr, + int sfd_selector, int phr_rate, int data_rate); +int set_channel(struct mcps802154_llhw *llhw, u8 page, u8 channel, + u8 preamble_code); + +static const struct nla_policy dw3000_tm_policy[DW3000_TM_ATTR_MAX + 1] = { + [DW3000_TM_ATTR_CMD] = { .type = NLA_U32 }, + [DW3000_TM_ATTR_RX_GOOD_CNT] = { .type = NLA_U32 }, + [DW3000_TM_ATTR_RX_BAD_CNT] = { .type = NLA_U32 }, + [DW3000_TM_ATTR_RSSI_DATA] = { .type = NLA_BINARY, + .len = DW3000_TM_RSSI_DATA_MAX_LEN }, + [DW3000_TM_ATTR_OTP_ADDR] = { .type = NLA_U16 }, + [DW3000_TM_ATTR_OTP_VAL] = { .type = NLA_U32 }, + [DW3000_TM_ATTR_OTP_DONE] = { .type = NLA_U8 }, + [DW3000_TM_ATTR_DEEP_SLEEP_DELAY_MS] = { .type = NLA_U32 }, + [DW3000_TM_ATTR_CONTTX_FRAME_LENGHT] = { .type = NLA_U32 }, + [DW3000_TM_ATTR_CONTTX_RATE] = { .type = NLA_U32 }, + [DW3000_TM_ATTR_CONTTX_DURATION] = { .type = NLA_S32 }, + [DW3000_TM_ATTR_PSR] = { .type = NLA_U32 }, + [DW3000_TM_ATTR_SFD] = { .type = NLA_U32 }, + [DW3000_TM_ATTR_PHR_RATE] = { .type = NLA_U32 }, + [DW3000_TM_ATTR_DATA_RATE] = { .type = NLA_U32 }, + [DW3000_TM_ATTR_PAGE] = { .type = NLA_U32 }, + [DW3000_TM_ATTR_CHANNEL] = { .type = NLA_U32 }, + [DW3000_TM_ATTR_PREAMBLE_CODE] = { .type = NLA_U32 }, +}; + +struct do_tm_cmd_params { + struct mcps802154_llhw *llhw; + struct nlattr **nl_attr; +}; + +static int do_tm_cmd_start_rx_diag(struct dw3000 *dw, const void *in, void *out) +{ + int rc; + /** + * Since the MCPS enables RX by default on the device at startup before + * running any test, enabling it once again manually via testmode + * command will cause the device to raise repeated IRQ and flood + * this driver's event thread. + * As a workaround, we disable RX before performing the testmode + * command. + */ + rc = dw3000_rx_disable(dw); + if (rc) + return rc; + /* Enable receiver and promiscuous mode */ + rc = dw3000_rx_enable(dw, 0, 0, 0); + if (rc) + return rc; + rc = dw3000_set_promiscuous(dw, true); + if (rc) + return rc; + /* Enable statistics */ + return dw3000_rx_stats_enable(dw, true); +} + +static int do_tm_cmd_stop_rx_diag(struct dw3000 *dw, const void *in, void *out) +{ + int rc; + /* Disable receiver and promiscuous mode */ + rc = dw3000_rx_disable(dw); + if (rc) + return rc; + rc = dw3000_set_promiscuous(dw, false); + if (rc) + return rc; + /* Disable statistics */ + return dw3000_rx_stats_enable(dw, false); +} + +static int do_tm_cmd_get_rx_diag(struct dw3000 *dw, const void *in, void *out) +{ + const struct do_tm_cmd_params *params = in; + struct dw3000_stats *stats = &dw->stats; + struct sk_buff *nl_skb; + size_t rssi_len; + int count = stats->count[DW3000_STATS_RX_GOOD]; + int rc; + + /* TODO: we don't send RSSI data for error frames. We should change this. */ + rssi_len = count < (DW3000_RSSI_REPORTS_MAX << 1) ? + count : + DW3000_RSSI_REPORTS_MAX << 1; + rssi_len *= sizeof(struct dw3000_rssi); + + /** + * Allocate netlink message. The approximated size includes + * the testmode's command id and data. + */ + nl_skb = mcps802154_testmode_alloc_reply_skb( + params->llhw, 3 * sizeof(u32) + rssi_len); + if (!nl_skb) { + dev_err(dw->dev, "failed to alloc skb reply\n"); + return -ENOMEM; + } + /* Append good and bad RX counters to the netlink message */ + rc = nla_put_u32(nl_skb, DW3000_TM_ATTR_RX_GOOD_CNT, + stats->count[DW3000_STATS_RX_GOOD]); + if (rc) { + dev_err(dw->dev, "failed to put testmode rx counter: %d\n", rc); + goto nla_put_failure; + } + rc = nla_put_u32(nl_skb, DW3000_TM_ATTR_RX_BAD_CNT, + stats->count[DW3000_STATS_RX_ERROR] + + stats->count[DW3000_STATS_RX_TO]); + if (rc) { + dev_err(dw->dev, "failed to put testmode rx counter: %d\n", rc); + goto nla_put_failure; + } + /* Append RSSI data to the netlink message */ + rc = nla_put(nl_skb, DW3000_TM_ATTR_RSSI_DATA, rssi_len, stats->rssi); + if (rc) { + dev_err(dw->dev, "failed to copy testmode data: %d\n", rc); + goto nla_put_failure; + } + return mcps802154_testmode_reply(params->llhw, nl_skb); + +nla_put_failure: + nlmsg_free(nl_skb); + return rc; +} + +static int do_tm_cmd_clear_rx_diag(struct dw3000 *dw, const void *in, void *out) +{ + /* Clear statistics */ + dw3000_rx_stats_clear(dw); + return 0; +} + +static int do_tm_cmd_start_tx_cwtone(struct dw3000 *dw, const void *in, + void *out) +{ + int rc; + /* Disable receiver */ + rc = dw3000_rx_disable(dw); + if (rc) + return rc; + /* Play repeated CW tone */ + return dw3000_tx_setcwtone(dw, true); +} + +static int do_tm_cmd_stop_tx_cwtone(struct dw3000 *dw, const void *in, + void *out) +{ + /* Stop repeated CW tone */ + return dw3000_tx_setcwtone(dw, false); +} + +static int do_tm_cmd_otp_read(struct dw3000 *dw, const void *in, void *out) +{ + const struct do_tm_cmd_params *params = in; + struct sk_buff *msg; + u32 otp_val; + u16 otp_addr; + int rc; + + /* Verify the OTP address attribute exists */ + if (!params->nl_attr[DW3000_TM_ATTR_OTP_ADDR]) + return -EINVAL; + + otp_addr = nla_get_u16(params->nl_attr[DW3000_TM_ATTR_OTP_ADDR]); + /* Verify if the given OTP address exceeds the limit */ + if (otp_addr > DW3000_OTP_ADDRESS_LIMIT) + return -EINVAL; + /* Read at OTP address */ + rc = dw3000_otp_read32(dw, otp_addr, &otp_val); + if (rc) + return rc; + /** + * Allocate netlink message. The approximated size includes + * the testmode's command id and data. + */ + msg = mcps802154_testmode_alloc_reply_skb(params->llhw, + 2 * sizeof(u32)); + if (!msg) { + dev_err(dw->dev, "failed to alloc skb reply\n"); + return -ENOMEM; + } + /* Append OTP memory's value to the netlink message */ + rc = nla_put_u32(msg, DW3000_TM_ATTR_OTP_VAL, otp_val); + if (rc) { + dev_err(dw->dev, "failed to put testmode otp val: %d\n", rc); + goto nla_put_failure; + } + return mcps802154_testmode_reply(params->llhw, msg); + +nla_put_failure: + nlmsg_free(msg); + return rc; +} + +static int do_tm_cmd_otp_write(struct dw3000 *dw, const void *in, void *out) +{ + const struct do_tm_cmd_params *params = in; + struct sk_buff *msg; + u32 otp_val; + u16 otp_addr; + u8 otp_done; + int rc; + + /* Verify the OTP address attribute exists */ + if (!params->nl_attr[DW3000_TM_ATTR_OTP_ADDR] || + !params->nl_attr[DW3000_TM_ATTR_OTP_VAL]) + return -EINVAL; + + otp_addr = nla_get_u16(params->nl_attr[DW3000_TM_ATTR_OTP_ADDR]); + /* Verify if the given OTP address exceeds the limit */ + if (otp_addr > DW3000_OTP_ADDRESS_LIMIT) + return -EINVAL; + otp_val = nla_get_u32(params->nl_attr[DW3000_TM_ATTR_OTP_VAL]); + /* Write at OTP address */ + rc = dw3000_otp_write32(dw, otp_addr, otp_val); + otp_done = (rc) ? 0 : 1; + /** + * Allocate netlink message. The approximated size includes + * the testmode's command id and data. + */ + msg = mcps802154_testmode_alloc_reply_skb(params->llhw, + 2 * sizeof(u32)); + if (!msg) { + dev_err(dw->dev, "failed to alloc skb reply\n"); + return -ENOMEM; + } + /* Append OTP memory's value to the netlink message */ + rc = nla_put_u8(msg, DW3000_TM_ATTR_OTP_DONE, otp_done); + if (rc) { + dev_err(dw->dev, "failed to put testmode otp done: %d\n", rc); + goto nla_put_failure; + } + return mcps802154_testmode_reply(params->llhw, msg); + +nla_put_failure: + nlmsg_free(msg); + return rc; +} + +static int do_tm_cmd_deep_sleep(struct dw3000 *dw, const void *in, void *out) +{ + const struct do_tm_cmd_params *params = in; + u32 delay; + + /* Verify the delay attribute exists */ + if (!params->nl_attr[DW3000_TM_ATTR_DEEP_SLEEP_DELAY_MS]) + return -EINVAL; + delay = nla_get_u32( + params->nl_attr[DW3000_TM_ATTR_DEEP_SLEEP_DELAY_MS]); + dw->deep_sleep_state.next_operational_state = DW3000_OP_STATE_IDLE_PLL; + return dw3000_deep_sleep_and_wakeup(dw, delay * 1000); +} + +static int do_tm_cmd_start_cont_tx(struct dw3000 *dw, const void *in, void *out) +{ + const struct do_tm_cmd_params *params = in; + u32 frame_length; + u32 rate; + s32 duration; + int rc; + + /* Verify mandatory attributes */ + if (!params->nl_attr[DW3000_TM_ATTR_CONTTX_FRAME_LENGHT] || + !params->nl_attr[DW3000_TM_ATTR_CONTTX_RATE]) + return -EINVAL; + + frame_length = nla_get_u32( + params->nl_attr[DW3000_TM_ATTR_CONTTX_FRAME_LENGHT]); + if (frame_length < 4) + return -EINVAL; + rate = nla_get_u32(params->nl_attr[DW3000_TM_ATTR_CONTTX_RATE]); + + /* Disable receiver */ + rc = dw3000_rx_disable(dw); + if (rc) + return rc; + + dw->config.stsMode = DW3000_STS_MODE_OFF; + + rc = dw3000_testmode_continuous_tx_start(dw, frame_length, rate); + if (rc) + return rc; + + if (params->nl_attr[DW3000_TM_ATTR_CONTTX_DURATION]) { + duration = nla_get_s32( + params->nl_attr[DW3000_TM_ATTR_CONTTX_DURATION]); + msleep(duration * 1000); + rc = dw3000_testmode_continuous_tx_stop(dw); + } + + return rc; +} + +static int do_tm_cmd_stop_cont_tx(struct dw3000 *dw, const void *in, void *out) +{ + return dw3000_testmode_continuous_tx_stop(dw); +} + +static int do_tm_cmd_set_hrp_uwb_params(struct dw3000 *dw, const void *in, + void *out) +{ + const struct do_tm_cmd_params *params = in; + u32 psr; + u32 sfd; + u32 phr_rate; + u32 data_rate; + + /* Verify the psr attribute exists */ + if (!params->nl_attr[DW3000_TM_ATTR_PSR]) + return -EINVAL; + psr = nla_get_u32(params->nl_attr[DW3000_TM_ATTR_PSR]); + + /* Verify the sfd attribute exists */ + if (!params->nl_attr[DW3000_TM_ATTR_SFD]) + return -EINVAL; + sfd = nla_get_u32(params->nl_attr[DW3000_TM_ATTR_SFD]); + + /* Verify the phr_rate attribute exists */ + if (!params->nl_attr[DW3000_TM_ATTR_PHR_RATE]) + return -EINVAL; + phr_rate = nla_get_u32(params->nl_attr[DW3000_TM_ATTR_PHR_RATE]); + + /* Verify the data_rate attribute exists */ + if (!params->nl_attr[DW3000_TM_ATTR_DATA_RATE]) + return -EINVAL; + data_rate = nla_get_u32(params->nl_attr[DW3000_TM_ATTR_DATA_RATE]); + + return set_hrp_uwb_params(params->llhw, 0, psr, sfd, phr_rate, + data_rate); +} + +static int do_tm_cmd_set_channel(struct dw3000 *dw, const void *in, void *out) +{ + const struct do_tm_cmd_params *params = in; + u32 page; + u32 channel; + u32 preamble_code; + + /* Verify the page attribute exists */ + if (!params->nl_attr[DW3000_TM_ATTR_PAGE]) + return -EINVAL; + page = nla_get_u32(params->nl_attr[DW3000_TM_ATTR_PAGE]); + + /* Verify the channel attribute exists */ + if (!params->nl_attr[DW3000_TM_ATTR_CHANNEL]) + return -EINVAL; + channel = nla_get_u32(params->nl_attr[DW3000_TM_ATTR_CHANNEL]); + + /* Verify the preamble_code attribute exists */ + if (!params->nl_attr[DW3000_TM_ATTR_PREAMBLE_CODE]) + return -EINVAL; + preamble_code = + nla_get_u32(params->nl_attr[DW3000_TM_ATTR_PREAMBLE_CODE]); + + return set_channel(params->llhw, page, channel, preamble_code); +} + +int dw3000_tm_cmd(struct mcps802154_llhw *llhw, void *data, int len) +{ + struct dw3000 *dw = llhw->priv; + struct do_tm_cmd_params params; + struct dw3000_stm_command cmd = { NULL, ¶ms, NULL }; + struct nlattr *attr[DW3000_TM_ATTR_MAX + 1]; + + static const cmd_func cmds[__DW3000_TM_CMD_AFTER_LAST] = { + [DW3000_TM_CMD_START_RX_DIAG] = do_tm_cmd_start_rx_diag, + [DW3000_TM_CMD_STOP_RX_DIAG] = do_tm_cmd_stop_rx_diag, + [DW3000_TM_CMD_GET_RX_DIAG] = do_tm_cmd_get_rx_diag, + [DW3000_TM_CMD_CLEAR_RX_DIAG] = do_tm_cmd_clear_rx_diag, + [DW3000_TM_CMD_OTP_READ] = do_tm_cmd_otp_read, + [DW3000_TM_CMD_OTP_WRITE] = do_tm_cmd_otp_write, + [DW3000_TM_CMD_START_TX_CWTONE] = do_tm_cmd_start_tx_cwtone, + [DW3000_TM_CMD_STOP_TX_CWTONE] = do_tm_cmd_stop_tx_cwtone, + [DW3000_TM_CMD_START_CONTINUOUS_TX] = do_tm_cmd_start_cont_tx, + [DW3000_TM_CMD_STOP_CONTINUOUS_TX] = do_tm_cmd_stop_cont_tx, + [DW3000_TM_CMD_DEEP_SLEEP] = do_tm_cmd_deep_sleep, + [DW3000_TM_CMD_SET_HRP_PARAMS] = do_tm_cmd_set_hrp_uwb_params, + [DW3000_TM_CMD_SET_CHANNEL] = do_tm_cmd_set_channel, + }; + u32 tm_cmd; + int ret; + + ret = nla_parse(attr, DW3000_TM_ATTR_MAX, data, len, dw3000_tm_policy, + NULL); + + if (ret) + return ret; + + if (!attr[DW3000_TM_ATTR_CMD]) + return -EINVAL; + + params = (struct do_tm_cmd_params){ llhw, attr }; + + tm_cmd = nla_get_u32(attr[DW3000_TM_ATTR_CMD]); + /* Share the testmode's command with each thread-safe function */ + trace_dw3000_tm_cmd(dw, tm_cmd); + + if (tm_cmd < __DW3000_TM_CMD_AFTER_LAST && cmds[tm_cmd]) { + cmd.cmd = cmds[tm_cmd]; + ret = dw3000_enqueue_generic(dw, &cmd); + } else + ret = -EOPNOTSUPP; + + trace_dw3000_return_int(dw, ret); + return ret; +} diff --git a/kernel/drivers/net/ieee802154/dw3000_testmode.h b/kernel/drivers/net/ieee802154/dw3000_testmode.h new file mode 100644 index 0000000..09b7fcd --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_testmode.h @@ -0,0 +1,20 @@ +#ifndef DW3000_TESTMODE_H +#define DW3000_TESTMODE_H + +#include <net/mcps802154.h> + +#ifdef CONFIG_MCPS802154_TESTMODE + +int dw3000_tm_cmd(struct mcps802154_llhw *llhw, void *data, int len); + +#else + +static inline int dw3000_tm_cmd(struct mcps802154_llhw *llhw, void *data, + int len) +{ + return 0; +} + +#endif /* CONFIG_MCPS802154_TESTMODE */ + +#endif /* DW3000_TESTMODE_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_testmode_nl.h b/kernel/drivers/net/ieee802154/dw3000_testmode_nl.h new file mode 100644 index 0000000..5628aa7 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_testmode_nl.h @@ -0,0 +1,128 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef DW3000_TESTMODE_NL_H +#define DW3000_TESTMODE_NL_H + +#include <linux/types.h> + +/** + * struct dw3000_rssi - Required data for RSSI calculation in userland. + * @cir_pwr: value of the channel impulse response power (CIR power) + * @pacc_cnt: value of the the preamble accumulation count (PACC count) + * @prf_64mhz: value of preamble repetition frequency (0 = 16MHz, 1 = 64MHz) + * @dgc_dec: value of dgc decision from DGC_CFG register + */ +struct dw3000_rssi { + uint32_t cir_pwr : 17; + uint16_t pacc_cnt : 11; + uint8_t prf_64mhz : 1; + uint8_t dgc_dec : 3; +} __attribute__((__packed__)); + +/* Since both DW3720 & DW3120 user manuals specify only 11-bits at most for + * diagnostic counters, we do the same for RSSI report number. */ +#define DW3000_RSSI_REPORTS_MAX (1 << 11) +#define DW3000_TM_RSSI_DATA_MAX_LEN \ + (DW3000_RSSI_REPORTS_MAX * sizeof(struct dw3000_rssi)) + +/* OTP address limit */ +#define DW3000_OTP_ADDRESS_LIMIT 0x7f + +/* All dw3000 testmode interface attributes */ +enum dw3000_tm_attr { + __DW3000_TM_ATTR_INVALID = 0, + DW3000_TM_ATTR_CMD, + DW3000_TM_ATTR_RX_GOOD_CNT, + DW3000_TM_ATTR_RX_BAD_CNT, + DW3000_TM_ATTR_RSSI_DATA, + DW3000_TM_ATTR_OTP_ADDR, + DW3000_TM_ATTR_OTP_VAL, + DW3000_TM_ATTR_OTP_DONE, + DW3000_TM_ATTR_DEEP_SLEEP_DELAY_MS, + DW3000_TM_ATTR_CONTTX_FRAME_LENGHT, + DW3000_TM_ATTR_CONTTX_RATE, + DW3000_TM_ATTR_CONTTX_DURATION, + + /* HRP parameters */ + DW3000_TM_ATTR_PSR, + DW3000_TM_ATTR_SFD, + DW3000_TM_ATTR_PHR_RATE, + DW3000_TM_ATTR_DATA_RATE, + + /* Complex channel parameters */ + DW3000_TM_ATTR_PAGE, + DW3000_TM_ATTR_CHANNEL, + DW3000_TM_ATTR_PREAMBLE_CODE, + + /* keep last */ + __DW3000_TM_ATTR_AFTER_LAST, + DW3000_TM_ATTR_MAX = __DW3000_TM_ATTR_AFTER_LAST - 1, +}; + +/* All dw3000 testmode interface commands specified in DW3000_TM_ATTR_CMD */ +enum dw3000_tm_cmd { + __DW3000_TM_CMD_INVALID = 0, + DW3000_TM_CMD_START_RX_DIAG, + DW3000_TM_CMD_STOP_RX_DIAG, + DW3000_TM_CMD_GET_RX_DIAG, + DW3000_TM_CMD_CLEAR_RX_DIAG, + DW3000_TM_CMD_OTP_READ, + DW3000_TM_CMD_OTP_WRITE, + + /* Start/Stop Continous Wave Tone */ + DW3000_TM_CMD_START_TX_CWTONE, + DW3000_TM_CMD_STOP_TX_CWTONE, + + /* Continuous TX : start/stop sending frame at regular interval */ + DW3000_TM_CMD_START_CONTINUOUS_TX, + DW3000_TM_CMD_STOP_CONTINUOUS_TX, + + /* TODO: CCC enum could be remove, as their are no more used. */ + DW3000_TM_CMD_CCC_START, + DW3000_TM_CMD_CCC_TEST_SCRATCH, + DW3000_TM_CMD_CCC_TEST_SPI1, + DW3000_TM_CMD_CCC_TEST_SPI2, + DW3000_TM_CMD_CCC_READ_TLVS, + DW3000_TM_CMD_CCC_WRITE_TLVS, + DW3000_TM_CMD_CCC_TEST_DIRECT, + DW3000_TM_CMD_CCC_TEST_WAIT, + DW3000_TM_CMD_CCC_TEST_LATE, + DW3000_TM_CMD_CCC_TEST_CONFLICT, + DW3000_TM_CMD_CCC_TEST_OFFSET, + + /* Deep sleep test */ + DW3000_TM_CMD_DEEP_SLEEP, + + /* Set HRP parameters */ + DW3000_TM_CMD_SET_HRP_PARAMS, + + /* Set complex channel */ + DW3000_TM_CMD_SET_CHANNEL, + + /* keep last */ + __DW3000_TM_CMD_AFTER_LAST, + DW3000_TM_CMD_MAX = __DW3000_TM_CMD_AFTER_LAST - 1, +}; + +#endif /* DW3000_TESTMODE_NL_H */ diff --git a/kernel/drivers/net/ieee802154/dw3000_trc.c b/kernel/drivers/net/ieee802154/dw3000_trc.c new file mode 100644 index 0000000..7f8080a --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_trc.c @@ -0,0 +1,29 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <linux/module.h> + +#ifndef __CHECKER__ +#define CREATE_TRACE_POINTS +#include "dw3000_trc.h" + +#endif diff --git a/kernel/drivers/net/ieee802154/dw3000_trc.h b/kernel/drivers/net/ieee802154/dw3000_trc.h new file mode 100644 index 0000000..29943e8 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_trc.h @@ -0,0 +1,1535 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM dw3000 + +#if !defined(__DW3000_TRACE) || defined(TRACE_HEADER_MULTI_READ) +#define __DW3000_TRACE + +#include <linux/tracepoint.h> +#include <linux/version.h> + +#include "dw3000.h" +#include "dw3000_nfcc_coex.h" +#include "dw3000_nfcc_coex_msg.h" +#include "dw3000_testmode_nl.h" +#include "dw3000_core_reg.h" +#include "dw3000_core.h" + +#define MAXNAME 32 +#define DW_ENTRY __array(char, dw_name, MAXNAME) +#define DW_ASSIGN strlcpy(__entry->dw_name, dw->dev->kobj.name, MAXNAME) +#define DW_PR_FMT "%s" +#define DW_PR_ARG __entry->dw_name + +#ifdef CONFIG_MCPS802154_TESTMODE +#define DW_TM_CMD_ENTRY __field(u32, cmd) +#define DW_TM_CMD_ASSIGN __entry->cmd = cmd +#define DW_TM_CMD_PR_FMT "cmd: %s" +#define dw3000_tm_cmd_name(name) \ + { \ + DW3000_TM_CMD_##name, #name \ + } +/* clang-format off */ +#define DW_TM_CMD_SYMBOLS \ + dw3000_tm_cmd_name(START_RX_DIAG), \ + dw3000_tm_cmd_name(STOP_RX_DIAG), \ + dw3000_tm_cmd_name(GET_RX_DIAG), \ + dw3000_tm_cmd_name(CLEAR_RX_DIAG), \ + dw3000_tm_cmd_name(START_TX_CWTONE), \ + dw3000_tm_cmd_name(STOP_TX_CWTONE), \ + dw3000_tm_cmd_name(OTP_READ), \ + dw3000_tm_cmd_name(OTP_WRITE) +/* clang-format on */ +TRACE_DEFINE_ENUM(DW3000_TM_CMD_START_RX_DIAG); +TRACE_DEFINE_ENUM(DW3000_TM_CMD_STOP_RX_DIAG); +TRACE_DEFINE_ENUM(DW3000_TM_CMD_GET_RX_DIAG); +TRACE_DEFINE_ENUM(DW3000_TM_CMD_CLEAR_RX_DIAG); +TRACE_DEFINE_ENUM(DW3000_TM_CMD_START_TX_CWTONE); +TRACE_DEFINE_ENUM(DW3000_TM_CMD_STOP_TX_CWTONE); +TRACE_DEFINE_ENUM(DW3000_TM_CMD_OTP_READ); +TRACE_DEFINE_ENUM(DW3000_TM_CMD_OTP_WRITE); +#define DW_TM_CMD_PR_ARG __print_symbolic(__entry->cmd, DW_TM_CMD_SYMBOLS) +#endif + +#define DW_PWR_ENTRY __field(int, state) +#define DW_PWR_ASSIGN __entry->state = state +#define DW_PWR_PR_FMT "state: %s" + +TRACE_DEFINE_ENUM(DW3000_OP_STATE_OFF); +TRACE_DEFINE_ENUM(DW3000_OP_STATE_DEEP_SLEEP); +TRACE_DEFINE_ENUM(DW3000_OP_STATE_SLEEP); +TRACE_DEFINE_ENUM(DW3000_OP_STATE_WAKE_UP); +TRACE_DEFINE_ENUM(DW3000_OP_STATE_INIT_RC); +TRACE_DEFINE_ENUM(DW3000_OP_STATE_IDLE_RC); +TRACE_DEFINE_ENUM(DW3000_OP_STATE_IDLE_PLL); +TRACE_DEFINE_ENUM(DW3000_OP_STATE_TX_WAIT); +TRACE_DEFINE_ENUM(DW3000_OP_STATE_TX); +TRACE_DEFINE_ENUM(DW3000_OP_STATE_RX_WAIT); +TRACE_DEFINE_ENUM(DW3000_OP_STATE_RX); + +#define dw3000_op_state_name(name) \ + { \ + DW3000_OP_STATE_##name, #name \ + } + +/* clang-format off */ +#define DW3000_OP_STATE_SYMBOLS \ + dw3000_op_state_name(OFF), \ + dw3000_op_state_name(DEEP_SLEEP), \ + dw3000_op_state_name(SLEEP), \ + dw3000_op_state_name(WAKE_UP), \ + dw3000_op_state_name(INIT_RC), \ + dw3000_op_state_name(IDLE_RC), \ + dw3000_op_state_name(IDLE_PLL), \ + dw3000_op_state_name(TX_WAIT), \ + dw3000_op_state_name(TX), \ + dw3000_op_state_name(RX_WAIT), \ + dw3000_op_state_name(RX) +/* clang-format on */ + +#define dw3000_pwr_name(name) \ + { \ + DW3000_PWR_##name, #name \ + } +/* clang-format off */ +#define DW_PWR_SYMBOLS \ + dw3000_pwr_name(OFF), \ + dw3000_pwr_name(DEEPSLEEP), \ + dw3000_pwr_name(RUN), \ + dw3000_pwr_name(IDLE), \ + dw3000_pwr_name(RX), \ + dw3000_pwr_name(TX) +/* clang-format on */ +TRACE_DEFINE_ENUM(DW3000_PWR_OFF); +TRACE_DEFINE_ENUM(DW3000_PWR_DEEPSLEEP); +TRACE_DEFINE_ENUM(DW3000_PWR_RUN); +TRACE_DEFINE_ENUM(DW3000_PWR_IDLE); +TRACE_DEFINE_ENUM(DW3000_PWR_RX); +TRACE_DEFINE_ENUM(DW3000_PWR_TX); +#define DW_PWR_PR_ARG __print_symbolic(__entry->state, DW_PWR_SYMBOLS) + +#define dw3000_sys_status_hi_name(name) \ + { \ + (u64)(DW3000_SYS_STATUS_HI_##name##_BIT_MASK) << 32, #name \ + } +#define dw3000_sys_status_name(name) \ + { \ + DW3000_SYS_STATUS_##name##_BIT_MASK, #name \ + } + +#define DW_SYS_STATUS_FLAGS_ENTRY __field(u64, status) +#define DW_SYS_STATUS_FLAGS_ASSIGN __entry->status = status +#if (KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE) +#define DW_SYS_STATUS_FLAGS_PR_FMT "status: %s" +/* clang-format off */ +#define DW_SYS_STATUS_FLAGS \ + dw3000_sys_status_hi_name(RXSTS), \ + dw3000_sys_status_hi_name(TXSTS), \ + dw3000_sys_status_hi_name(SEMA_ERR), \ + dw3000_sys_status_hi_name(COEX_CLR), \ + dw3000_sys_status_hi_name(COEX_ERR), \ + dw3000_sys_status_hi_name(CCA_FAIL), \ + dw3000_sys_status_hi_name(SPIERR), \ + dw3000_sys_status_hi_name(SPI_UNF), \ + dw3000_sys_status_hi_name(SPI_OVF), \ + dw3000_sys_status_hi_name(CMD_ERR), \ + dw3000_sys_status_hi_name(AES_ERR), \ + dw3000_sys_status_hi_name(AES_DONE), \ + dw3000_sys_status_hi_name(GPIO_IRQ), \ + dw3000_sys_status_hi_name(VT_DET), \ + dw3000_sys_status_hi_name(PGFCAL_ERR), \ + dw3000_sys_status_hi_name(RXPREJ), \ + dw3000_sys_status_name(TIMER1), \ + dw3000_sys_status_name(TIMER0), \ + dw3000_sys_status_name(ARFE), \ + dw3000_sys_status_name(CPERR), \ + dw3000_sys_status_name(HPDWARN), \ + dw3000_sys_status_name(RXSTO), \ + dw3000_sys_status_name(PLL_HILO), \ + dw3000_sys_status_name(RCINIT), \ + dw3000_sys_status_name(SPIRDY), \ + dw3000_sys_status_name(LCSSERR), \ + dw3000_sys_status_name(RXPTO), \ + dw3000_sys_status_name(RXOVRR), \ + dw3000_sys_status_name(VWARN), \ + dw3000_sys_status_name(CIAERR), \ + dw3000_sys_status_name(RXFTO), \ + dw3000_sys_status_name(RXFSL), \ + dw3000_sys_status_name(RXFCE), \ + dw3000_sys_status_name(RXFCG), \ + dw3000_sys_status_name(RXFR), \ + dw3000_sys_status_name(RXPHE), \ + dw3000_sys_status_name(RXPHD), \ + dw3000_sys_status_name(CIA_DONE), \ + dw3000_sys_status_name(RXSFDD), \ + dw3000_sys_status_name(RXPRD), \ + dw3000_sys_status_name(TXFRS), \ + dw3000_sys_status_name(TXPHS), \ + dw3000_sys_status_name(TXPRS), \ + dw3000_sys_status_name(TXFRB), \ + dw3000_sys_status_name(AAT), \ + dw3000_sys_status_name(SPICRCERR), \ + dw3000_sys_status_name(CLK_PLL_LOCK), \ + dw3000_sys_status_name(IRQS) +/* clang-format on */ +#define DW_SYS_STATUS_FLAGS_PR_ARG \ + __print_flags_u64(__entry->status, "|", DW_SYS_STATUS_FLAGS) +#else +/* __print_flags_u64 is not supported. */ +#define DW_SYS_STATUS_FLAGS_PR_FMT "status: 0x%0llx" +#define DW_SYS_STATUS_FLAGS_PR_ARG __entry->status +#endif + +#define RX_FRAME_CONFIG_FLAGS_ENTRY __field(u8, flags) +#define RX_FRAME_CONFIG_FLAGS_ASSIGN entry->flags = flags +#define RX_FRAME_CONFIG_FLAGS_PR_FMT "flags: %s" + +#define mcps802154_rx_frame_config_name(name) \ + { \ + MCPS802154_RX_FRAME_CONFIG_##name, #name \ + } + +/* clang-format off */ +#define RX_FRAME_CONFIG_FLAGS \ + mcps802154_rx_frame_config_name(TIMESTAMP_DTU), \ + mcps802154_rx_frame_config_name(AACK), \ + mcps802154_rx_frame_config_name(RANGING), \ + mcps802154_rx_frame_config_name(KEEP_RANGING_CLOCK), \ + mcps802154_rx_frame_config_name(RANGING_PDOA), \ + mcps802154_rx_frame_config_name(SP3), \ + mcps802154_rx_frame_config_name(SP2), \ + mcps802154_rx_frame_config_name(SP1), \ + mcps802154_rx_frame_config_name(STS_MODE_MASK) +/* clang-format on */ + +#define RX_FRAME_CONFIG_FLAGS_PR_ARG \ + __print_flags(__entry->flags, "|", RX_FRAME_CONFIG_FLAGS) + +#define RX_FRAME_INFO_FLAGS_ENTRY __field(u16, flags) +#define RX_FRAME_INFO_FLAGS_ASSIGN entry->flags = flags +#define RX_FRAME_INFO_FLAGS_PR_FMT "flags: %s" + +#define mcps802154_rx_frame_info_name(name) \ + { \ + MCPS802154_RX_FRAME_INFO_##name, #name \ + } + +/* clang-format off */ +#define RX_FRAME_INFO_FLAGS \ + mcps802154_rx_frame_info_name(TIMESTAMP_DTU), \ + mcps802154_rx_frame_info_name(TIMESTAMP_RCTU), \ + mcps802154_rx_frame_info_name(LQI), \ + mcps802154_rx_frame_info_name(RSSI), \ + mcps802154_rx_frame_info_name(RANGING_FOM), \ + mcps802154_rx_frame_info_name(RANGING_OFFSET), \ + mcps802154_rx_frame_info_name(RANGING_PDOA), \ + mcps802154_rx_frame_info_name(RANGING_PDOA_FOM), \ + mcps802154_rx_frame_info_name(RANGING_STS_TIMESTAMP_RCTU), \ + mcps802154_rx_frame_info_name(RANGING_STS_FOM), \ + mcps802154_rx_frame_info_name(AACK) +/* clang-format on */ + +#define RX_FRAME_INFO_FLAGS_PR_ARG \ + __print_flags(__entry->flags, "|", RX_FRAME_INFO_FLAGS) + +#define TX_FRAME_CONFIG_FLAGS_ENTRY __field(u8, flags) +#define TX_FRAME_CONFIG_FLAGS_ASSIGN entry->flags = flags +#define TX_FRAME_CONFIG_FLAGS_PR_FMT "flags: %s" + +#define mcps802154_tx_frame_config_name(name) \ + { \ + MCPS802154_TX_FRAME_CONFIG_##name, #name \ + } + +/* clang-format off */ +#define TX_FRAME_CONFIG_FLAGS \ + mcps802154_tx_frame_config_name(TIMESTAMP_DTU), \ + mcps802154_tx_frame_config_name(CCA), \ + mcps802154_tx_frame_config_name(RANGING), \ + mcps802154_tx_frame_config_name(KEEP_RANGING_CLOCK), \ + mcps802154_tx_frame_config_name(SP3), \ + mcps802154_tx_frame_config_name(SP2), \ + mcps802154_tx_frame_config_name(SP1), \ + mcps802154_tx_frame_config_name(STS_MODE_MASK) +/* clang-format on */ + +#define TX_FRAME_CONFIG_FLAGS_PR_ARG \ + __print_flags(__entry->flags, "|", TX_FRAME_CONFIG_FLAGS) + +#define dw3000_nfcc_coex_tlv_type_name(name) \ + { \ + DW3000_NFCC_COEX_TLV_TYPE_##name, #name \ + } + +#define DW3000_NFCC_COEX_TLV_TYPE_ENTRY \ + __field(enum dw3000_nfcc_coex_tlv_type, type) +#define DW3000_NFCC_COEX_TLV_TYPE_ASSIGN __entry->type = type +#define DW3000_NFCC_COEX_TLV_TYPE_PR_FMT "type: %s" +/* clang-format off */ +#define DW3000_NFCC_COEX_TLV_TYPE_SYMBOLS \ + dw3000_nfcc_coex_tlv_type_name(UNSPEC), \ + dw3000_nfcc_coex_tlv_type_name(SESSION_TIME0), \ + dw3000_nfcc_coex_tlv_type_name(SLOT_LIST), \ + dw3000_nfcc_coex_tlv_type_name(TLV_UWBCNT_OFFS), \ + dw3000_nfcc_coex_tlv_type_name(ERROR), \ + dw3000_nfcc_coex_tlv_type_name(SLOT_LIST_UUS) +/* clang-format on */ +TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_TLV_TYPE_UNSPEC); +TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_TLV_TYPE_SESSION_TIME0); +TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_TLV_TYPE_SLOT_LIST); +TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_TLV_TYPE_TLV_UWBCNT_OFFS); +TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_TLV_TYPE_ERROR); +TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_TLV_TYPE_SLOT_LIST_UUS); +#define DW3000_NFCC_COEX_TLV_TYPE_ARG \ + __print_symbolic(__entry->type, DW3000_NFCC_COEX_TLV_TYPE_SYMBOLS) + +#define dw3000_dss_stats_name(name) \ + { \ + DW3000_DSS_STAT_##name##_BIT_MASK, #name \ + } +/* clang-format off */ +#define DW3000_DSS_STATS_SYMBOLS \ + dw3000_dss_stats_name(SPI1_AVAIL), \ + dw3000_dss_stats_name(SPI2_AVAIL) +/* clang-format on */ +TRACE_DEFINE_ENUM(DW3000_DSS_STAT_SPI1_AVAIL_BIT_MASK); +TRACE_DEFINE_ENUM(DW3000_DSS_STAT_SPI2_AVAIL_BIT_MASK); + +#define dw3000_nfcc_coex_send_name(name) \ + { \ + DW3000_NFCC_COEX_SEND_##name, #name \ + } + +#define DW3000_NFCC_COEX_SEND_ENTRY __field(enum dw3000_nfcc_coex_send, send) +#define DW3000_NFCC_COEX_SEND_ASSIGN __entry->send = send +#define DW3000_NFCC_COEX_SEND_PR_FMT "send: %s" +/* clang-format off */ +#define DW3000_NFCC_COEX_SEND \ + dw3000_nfcc_coex_send_name(CLK_SYNC), \ + dw3000_nfcc_coex_send_name(CLK_OFFSET), \ + dw3000_nfcc_coex_send_name(STOP) +/* clang-format on */ +TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_SEND_CLK_SYNC); +TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_SEND_CLK_OFFSET); +TRACE_DEFINE_ENUM(DW3000_NFCC_COEX_SEND_STOP); +#define DW3000_NFCC_COEX_SEND_ARG \ + __print_symbolic(__entry->send, DW3000_NFCC_COEX_SEND) + +/* We don't want clang-format to modify the following events definition! + Look at net/wireless/trace.h for the required format. */ +/* clang-format off */ + +/************************************************************* + * dw3000 functions return traces * + *************************************************************/ + +DECLARE_EVENT_CLASS(dw_only_evt, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw), + TP_STRUCT__entry( + DW_ENTRY + ), + TP_fast_assign( + DW_ASSIGN; + ), + TP_printk(DW_PR_FMT, DW_PR_ARG) +); + +DEFINE_EVENT(dw_only_evt, dw3000_return_void, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +TRACE_EVENT(dw3000_return_int, + TP_PROTO(struct dw3000 *dw, int ret), + TP_ARGS(dw, ret), + TP_STRUCT__entry( + DW_ENTRY + __field(int, ret) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->ret = ret; + ), + TP_printk(DW_PR_FMT ", return: %d", DW_PR_ARG, __entry->ret) +); + +TRACE_EVENT(dw3000_return_int_u32, + TP_PROTO(struct dw3000 *dw, int ret, u32 val), + TP_ARGS(dw, ret, val), + TP_STRUCT__entry( + DW_ENTRY + __field(int, ret) + __field(u32, val) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->ret = ret; + __entry->val = val; + ), + TP_printk(DW_PR_FMT ", return: %d, value: 0x%x", DW_PR_ARG, + __entry->ret, __entry->val) +); + +TRACE_EVENT(dw3000_return_int_u64, + TP_PROTO(struct dw3000 *dw, int ret, u64 val), + TP_ARGS(dw, ret, val), + TP_STRUCT__entry( + DW_ENTRY + __field(int, ret) + __field(u64, val) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->ret = ret; + __entry->val = val; + ), + TP_printk(DW_PR_FMT ", return: %d, value: 0x%llx", DW_PR_ARG, + __entry->ret, __entry->val) +); + +/************************************************************* + * dw3000 mcps functions traces * + *************************************************************/ + +DEFINE_EVENT(dw_only_evt, dw3000_mcps_start, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +DEFINE_EVENT(dw_only_evt, dw3000_mcps_stop, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +TRACE_EVENT(dw3000_mcps_tx_frame, + TP_PROTO(struct dw3000 *dw, u8 flags, u16 len), + TP_ARGS(dw, flags, len), + TP_STRUCT__entry( + DW_ENTRY + TX_FRAME_CONFIG_FLAGS_ENTRY + __field(u16, len) + ), + TP_fast_assign( + DW_ASSIGN; + TX_FRAME_CONFIG_FLAGS_ASSIGN; + __entry->len = len; + ), + TP_printk(DW_PR_FMT ", " TX_FRAME_CONFIG_FLAGS_PR_FMT ", skb->len: %d", + DW_PR_ARG, TX_FRAME_CONFIG_FLAGS_PR_ARG,__entry->len) +); + +TRACE_EVENT(dw3000_mcps_tx_frame_too_late, + TP_PROTO(struct dw3000 *dw, u32 tx_date_dtu, u32 cur_time_dtu), + TP_ARGS(dw, tx_date_dtu, cur_time_dtu), + TP_STRUCT__entry( + DW_ENTRY + __field(u32, tx_date_dtu) + __field(u32, cur_time_dtu) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->tx_date_dtu = tx_date_dtu; + __entry->cur_time_dtu = cur_time_dtu; + ), + TP_printk(DW_PR_FMT ", cannot program delayed, tx_date_dtu: 0x%x" + ", cur_time_dtu: 0x%x", DW_PR_ARG, __entry->tx_date_dtu, + __entry->cur_time_dtu) +); + +TRACE_EVENT(dw3000_mcps_rx_enable, + TP_PROTO(struct dw3000 *dw, u8 flags, int timeout), + TP_ARGS(dw, flags, timeout), + TP_STRUCT__entry( + DW_ENTRY + RX_FRAME_CONFIG_FLAGS_ENTRY + __field(int, timeout) + ), + TP_fast_assign( + DW_ASSIGN; + RX_FRAME_CONFIG_FLAGS_ASSIGN; + __entry->timeout = timeout; + ), + TP_printk(DW_PR_FMT ", " RX_FRAME_CONFIG_FLAGS_PR_FMT ", timeout: %d", + DW_PR_ARG, RX_FRAME_CONFIG_FLAGS_PR_ARG, __entry->timeout) +); + +TRACE_EVENT(dw3000_mcps_rx_enable_too_late, + TP_PROTO(struct dw3000 *dw, u32 rx_date_dtu, u32 cur_time_dtu), + TP_ARGS(dw, rx_date_dtu, cur_time_dtu), + TP_STRUCT__entry( + DW_ENTRY + __field(u32, rx_date_dtu) + __field(u32, cur_time_dtu) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->rx_date_dtu = rx_date_dtu; + __entry->cur_time_dtu = cur_time_dtu; + ), + TP_printk(DW_PR_FMT ", too late to program delayed, rx_date_dtu: 0x%x, current_dtu: 0x%x", + DW_PR_ARG, __entry->rx_date_dtu, __entry->cur_time_dtu) +); + +DEFINE_EVENT(dw_only_evt, dw3000_mcps_rx_disable, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +TRACE_EVENT(dw3000_mcps_rx_get_frame, + TP_PROTO(struct dw3000 *dw, u16 flags), + TP_ARGS(dw, flags), + TP_STRUCT__entry( + DW_ENTRY + RX_FRAME_INFO_FLAGS_ENTRY + ), + TP_fast_assign( + DW_ASSIGN; + RX_FRAME_INFO_FLAGS_ASSIGN; + ), + TP_printk(DW_PR_FMT ", " RX_FRAME_INFO_FLAGS_PR_FMT, + DW_PR_ARG, RX_FRAME_INFO_FLAGS_PR_ARG) +); + +TRACE_EVENT(dw3000_mcps_rx_get_error_frame, + TP_PROTO(struct dw3000 *dw, u16 flags), + TP_ARGS(dw, flags), + TP_STRUCT__entry( + DW_ENTRY + RX_FRAME_INFO_FLAGS_ENTRY + ), + TP_fast_assign( + DW_ASSIGN; + RX_FRAME_INFO_FLAGS_ASSIGN; + ), + TP_printk(DW_PR_FMT ", " RX_FRAME_INFO_FLAGS_PR_FMT, + DW_PR_ARG, RX_FRAME_INFO_FLAGS_PR_ARG) + ); + +DEFINE_EVENT(dw_only_evt, dw3000_wakeup_done_to_tx, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +DEFINE_EVENT(dw_only_evt, dw3000_wakeup_done_to_rx, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +DEFINE_EVENT(dw_only_evt, dw3000_wakeup_done_to_idle, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +DEFINE_EVENT(dw_only_evt, dw3000_wakeup_done_to_idle_late, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +TRACE_EVENT(dw3000_idle, + TP_PROTO(struct dw3000 *dw, bool timeout, u32 timeout_dtu, + enum operational_state next_operational_state), + TP_ARGS(dw, timeout, timeout_dtu, next_operational_state), + TP_STRUCT__entry( + DW_ENTRY + __field(bool, timeout) + __field(u32, timeout_dtu) + __field(enum operational_state, next_operational_state) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->timeout = timeout; + __entry->timeout_dtu = timeout_dtu; + __entry->next_operational_state = next_operational_state; + ), + TP_printk(DW_PR_FMT ", timeout: %s, timeout_dtu: 0x%0x, " + "next_operational_state: %s" , DW_PR_ARG, + __entry->timeout ? "true" : "false", __entry->timeout_dtu, + __print_symbolic(__entry->next_operational_state, + DW3000_OP_STATE_SYMBOLS)) +); + +DEFINE_EVENT(dw_only_evt, dw3000_mcps_reset, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +DEFINE_EVENT(dw_only_evt, dw3000_mcps_get_timestamp, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +TRACE_EVENT(dw3000_mcps_set_channel, + TP_PROTO(struct dw3000 *dw, u8 page, u8 channel, u8 pcode), + TP_ARGS(dw, page, channel, pcode), + TP_STRUCT__entry( + DW_ENTRY + __field(u8, page) + __field(u8, channel) + __field(u8, pcode) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->page = page; + __entry->channel = channel; + __entry->pcode = pcode; + ), + TP_printk(DW_PR_FMT ", page: %u, channel: %u, preamble_code: %u", + DW_PR_ARG, __entry->page, __entry->channel, + __entry->pcode) +); + +TRACE_EVENT(dw3000_mcps_set_hw_addr_filt, + TP_PROTO(struct dw3000 *dw, u8 changed), + TP_ARGS(dw, changed), + TP_STRUCT__entry( + DW_ENTRY + __field(u8, changed) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->changed = changed; + ), + TP_printk(DW_PR_FMT ", changed: 0x%x", DW_PR_ARG, + __entry->changed) +); + +TRACE_EVENT(dw3000_mcps_set_sts_params, + TP_PROTO(struct dw3000 *dw, const struct mcps802154_sts_params *p), + TP_ARGS(dw, p), + TP_STRUCT__entry( + DW_ENTRY + __field(u8, n_segs) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->n_segs = p->n_segs; + ), + TP_printk(DW_PR_FMT ", n_segs: %u", DW_PR_ARG, + __entry->n_segs) +); + +/************************************************************* + * dw3000 RCTU converter functions traces * + *************************************************************/ +TRACE_EVENT(dw3000_rctu_convert_align, + TP_PROTO(struct dw3000 *dw, u32 rmarker_dtu), + TP_ARGS(dw, rmarker_dtu), + TP_STRUCT__entry( + DW_ENTRY + __field(u32, rmarker_dtu) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->rmarker_dtu = rmarker_dtu; + ), + TP_printk(DW_PR_FMT ", rmarker_dtu: 0x%x", DW_PR_ARG, + __entry->rmarker_dtu) +); + +TRACE_EVENT(dw3000_rctu_convert_synced, + TP_PROTO(struct dw3000 *dw, u64 rmarker_rctu), + TP_ARGS(dw, rmarker_rctu), + TP_STRUCT__entry( + DW_ENTRY + __field(u64, rmarker_rctu) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->rmarker_rctu = rmarker_rctu; + ), + TP_printk(DW_PR_FMT ", rmarker_rctu: 0x%llx", DW_PR_ARG, + __entry->rmarker_rctu) +); + +TRACE_EVENT(dw3000_rctu_convert_rx, + TP_PROTO(struct dw3000 *dw, u32 rmarker_dtu, u64 ts_rctu, u64 rmarker_rctu), + TP_ARGS(dw, rmarker_dtu, ts_rctu, rmarker_rctu), + TP_STRUCT__entry( + DW_ENTRY + __field(u32, rmarker_dtu) + __field(u64, ts_rctu) + __field(u64, rmarker_rctu) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->rmarker_dtu = rmarker_dtu; + __entry->ts_rctu = ts_rctu; + __entry->rmarker_rctu = rmarker_rctu; + ), + TP_printk(DW_PR_FMT ", rmarker_dtu: 0x%x, ts_rctu: 0x%llx, " + "rmarker_rctu: 0x%llx", + DW_PR_ARG, __entry->rmarker_dtu, __entry->ts_rctu, + __entry->rmarker_rctu) +); + +TRACE_EVENT(dw3000_rctu_convert_tx, + TP_PROTO(struct dw3000 *dw, u32 ts_dtu, u32 ant_offset, u64 rmarker_rctu), + TP_ARGS(dw, ts_dtu, ant_offset, rmarker_rctu), + TP_STRUCT__entry( + DW_ENTRY + __field(u32, ts_dtu) + __field(u32, ant_offset) + __field(u64, rmarker_rctu) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->ts_dtu = ts_dtu; + __entry->ant_offset = ant_offset; + __entry->rmarker_rctu = rmarker_rctu; + ), + TP_printk(DW_PR_FMT ", ts_dtu: 0x%x, ant_offset: 0x%x, rmarker_rctu: 0x%llx", + DW_PR_ARG, __entry->ts_dtu, __entry->ant_offset, + __entry->rmarker_rctu) +); + +/************************************************************* + * dw3000 core functions traces * + *************************************************************/ +TRACE_EVENT(dw3000_isr, + TP_PROTO(struct dw3000 *dw, u64 status), + TP_ARGS(dw, status), + TP_STRUCT__entry( + DW_ENTRY + DW_SYS_STATUS_FLAGS_ENTRY + ), + TP_fast_assign( + DW_ASSIGN; + DW_SYS_STATUS_FLAGS_ASSIGN; + ), + TP_printk(DW_PR_FMT ", " DW_SYS_STATUS_FLAGS_PR_FMT, + DW_PR_ARG, DW_SYS_STATUS_FLAGS_PR_ARG) +); + +TRACE_EVENT(dw3000_check_idlerc, + TP_PROTO(struct dw3000 *dw, u32 low_sys_status), + TP_ARGS(dw, low_sys_status), + TP_STRUCT__entry( + DW_ENTRY + __field(u32, low_sys_status) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->low_sys_status = low_sys_status; + ), + TP_printk(DW_PR_FMT ", low_sys_status: 0x%08x", + DW_PR_ARG, __entry->low_sys_status) +); + +TRACE_EVENT(dw3000_wakeup_and_wait, + TP_PROTO(struct dw3000 *dw, enum operational_state operational_state), + TP_ARGS(dw, operational_state), + TP_STRUCT__entry( + DW_ENTRY + __field(enum operational_state, operational_state) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->operational_state = operational_state; + ), + TP_printk(DW_PR_FMT ", operational_state: %s", DW_PR_ARG, + __print_symbolic(__entry->operational_state, + DW3000_OP_STATE_SYMBOLS)) +); + +TRACE_EVENT(dw3000_set_operational_state, + TP_PROTO(struct dw3000 *dw, enum operational_state operational_state), + TP_ARGS(dw, operational_state), + TP_STRUCT__entry( + DW_ENTRY + __field(enum operational_state, operational_state) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->operational_state = operational_state; + ), + TP_printk(DW_PR_FMT ", operational_state: %s", DW_PR_ARG, + __print_symbolic(__entry->operational_state, + DW3000_OP_STATE_SYMBOLS)) +); + +DEFINE_EVENT(dw_only_evt, dw3000_deepsleep_wakeup, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +DEFINE_EVENT(dw_only_evt, dw3000_idle_timeout, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +DEFINE_EVENT(dw_only_evt, dw3000_idle_cancel_timer, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +TRACE_EVENT(dw3000_check_operational_state, + TP_PROTO(struct dw3000 *dw, int delay_dtu, + enum operational_state current_operational_state, + enum operational_state next_operational_state), + TP_ARGS(dw, delay_dtu, current_operational_state, + next_operational_state), + TP_STRUCT__entry( + DW_ENTRY + __field(int, delay_dtu) + __field(enum operational_state, current_operational_state) + __field(enum operational_state, next_operational_state) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->delay_dtu = delay_dtu; + __entry->current_operational_state = current_operational_state; + __entry->next_operational_state = next_operational_state; + ), + TP_printk(DW_PR_FMT ", delay_dtu: %d, current_operational_state: %s, " + "next_operational_state: %s", DW_PR_ARG, __entry->delay_dtu, + __print_symbolic(__entry->current_operational_state, + DW3000_OP_STATE_SYMBOLS), + __print_symbolic(__entry->next_operational_state, + DW3000_OP_STATE_SYMBOLS)) +); + +DEFINE_EVENT(dw_only_evt, dw3000_read_rx_timestamp, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +TRACE_EVENT(dw3000_resync_dtu_sys_time, + TP_PROTO(struct dw3000 *dw, u32 sys_time_sync, u32 dtu_sync), + TP_ARGS(dw, sys_time_sync, dtu_sync), + TP_STRUCT__entry( + DW_ENTRY + __field(u32, sys_time_sync) + __field(u32, dtu_sync) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->sys_time_sync = sys_time_sync; + __entry->dtu_sync = dtu_sync; + ), + TP_printk(DW_PR_FMT ", sys_time_sync: 0x%08x, dtu_sync: 0x%08x", + DW_PR_ARG, __entry->sys_time_sync, __entry->dtu_sync) +); + +TRACE_EVENT(dw3000_deep_sleep, + TP_PROTO(struct dw3000 *dw, int rc), + TP_ARGS(dw, rc), + TP_STRUCT__entry( + DW_ENTRY + __field(int, result) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->result = rc; + ), + TP_printk(DW_PR_FMT ", result: %d", DW_PR_ARG, + __entry->result) +); + +TRACE_EVENT(dw3000_deep_sleep_enter, + TP_PROTO(struct dw3000 *dw, s64 time_before), + TP_ARGS(dw, time_before), + TP_STRUCT__entry( + DW_ENTRY + __field(s64, time_before) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->time_before = time_before; + ), + TP_printk(DW_PR_FMT ", time_ns: %lld", DW_PR_ARG, __entry->time_before) +); + +TRACE_EVENT(dw3000_wakeup_timer_start, + TP_PROTO(struct dw3000 *dw, int delay_us), + TP_ARGS(dw, delay_us), + TP_STRUCT__entry( + DW_ENTRY + __field(int, delay_us) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->delay_us = delay_us; + ), + TP_printk(DW_PR_FMT ", delay: %dus", DW_PR_ARG, __entry->delay_us) +); + +DEFINE_EVENT(dw_only_evt, dw3000_wakeup, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +TRACE_EVENT(dw3000_wakeup_done, + TP_PROTO(struct dw3000 *dw, s64 sleep_time_us, u32 sleep_enter_dtu, + u32 dtu_next_op, enum operational_state next_op), + TP_ARGS(dw, sleep_time_us, sleep_enter_dtu, dtu_next_op, next_op), + TP_STRUCT__entry( + DW_ENTRY + __field(s64, sleep_time_us) + __field(u32, sleep_enter_dtu) + __field(u32, dtu_next_op) + __field(enum operational_state, next_op) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->sleep_time_us = sleep_time_us; + __entry->sleep_enter_dtu = sleep_enter_dtu; + __entry->dtu_next_op = dtu_next_op; + __entry->next_op = next_op; + ), + TP_printk(DW_PR_FMT ", sleep_us: %lld, sleep_enter_dtu: 0x%x, " + "next_op_date: 0x%x, next_op: %s", DW_PR_ARG, + __entry->sleep_time_us, __entry->sleep_enter_dtu, + __entry->dtu_next_op, + __print_symbolic(__entry->next_op, DW3000_OP_STATE_SYMBOLS)) +); + +TRACE_EVENT(dw3000_power_stats, + TP_PROTO(struct dw3000 *dw, int state, u64 boot_time_ns, int len_or_date), + TP_ARGS(dw, state, boot_time_ns, len_or_date), + TP_STRUCT__entry( + DW_ENTRY + DW_PWR_ENTRY + __field(u64, boot_time_ns) + __field(int, len_or_date) + ), + TP_fast_assign( + DW_ASSIGN; + DW_PWR_ASSIGN; + __entry->boot_time_ns = boot_time_ns; + __entry->len_or_date = len_or_date; + ), + TP_printk(DW_PR_FMT ", " DW_PWR_PR_FMT ", boot_time_ns: %llu, len_or_date: %u", + DW_PR_ARG, DW_PWR_PR_ARG, __entry->boot_time_ns, + (unsigned)__entry->len_or_date) +); + +TRACE_EVENT(dw3000_prog_xtrim, + TP_PROTO(struct dw3000 *dw, int res, int value, int bias), + TP_ARGS(dw, res, value, bias), + TP_STRUCT__entry( + DW_ENTRY + __field(int, res) + __field(int, value) + __field(int, bias) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->res = res; + __entry->value = value; + __entry->bias = bias; + ), + TP_printk(DW_PR_FMT ", res: %d, xtrim: %d, bias: %d", + DW_PR_ARG, __entry->res, __entry->value, __entry->bias) +); + +TRACE_EVENT(dw3000_set_antenna_gpio, + TP_PROTO(struct dw3000 *dw, int res, u8 gpio, u8 value), + TP_ARGS(dw, res, gpio, value), + TP_STRUCT__entry( + DW_ENTRY + __field(int, res) + __field(u8, gpio) + __field(u8, value) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->res = res; + __entry->gpio = gpio; + __entry->value = value; + ), + TP_printk(DW_PR_FMT ", res: %d, gpio: %u, value: %u", + DW_PR_ARG, __entry->res, __entry->gpio, __entry->value) +); + +TRACE_EVENT(dw3000_read_frame_cir_data, + TP_PROTO(struct dw3000 *dw, u8 prf, u8 stsMode, u64 utime), + TP_ARGS(dw, prf, stsMode, utime), + TP_STRUCT__entry( + DW_ENTRY + __field(u8, prf) + __field(u8, stsMode) + __field(u64, utime) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->prf = prf; + __entry->stsMode = stsMode; + __entry->utime = utime; + ), + TP_printk(DW_PR_FMT ", utime: %llu, prf: %u, sts: %u", + DW_PR_ARG, __entry->utime, __entry->prf, __entry->stsMode) +); + +TRACE_EVENT(dw3000_read_frame_cir_data_return, + TP_PROTO(struct dw3000 *dw, u64 ts, u32 f1, u32 f2, u32 f3, + u16 acc, u16 fpidx, s32 off, u32 filter), + TP_ARGS(dw, ts, f1, f2, f3, acc, fpidx, off, filter), + TP_STRUCT__entry( + DW_ENTRY + __field(u64, ts) + __field(u32, f1) + __field(u32, f2) + __field(u32, f3) + __field(u16, acc) + __field(u16, fpidx) + __field(s32, off) + __field(u32, filter) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->ts = ts; + __entry->f1 = f1; + __entry->f2 = f2; + __entry->f3 = f3; + __entry->acc = acc; + __entry->fpidx = fpidx; + __entry->off = off; + __entry->filter = filter; + ), + TP_printk(DW_PR_FMT ", ts: %llu, f1: %u, f2: %u, f3: %u, " + "acc: %hu, fpidx: %hu, offset: %d, filter: 0x%x", + DW_PR_ARG, __entry->ts, __entry->f1, __entry->f2, + __entry->f3, __entry->acc, __entry->fpidx, __entry->off, + __entry->filter) +); + +TRACE_EVENT(dw3000_coex_gpio, + TP_PROTO(struct dw3000 *dw, bool state, int delay_us, u32 expire, bool status), + TP_ARGS(dw, state, delay_us, expire, status), + TP_STRUCT__entry( + DW_ENTRY + __field(bool, state) + __field(int, delay_us) + __field(u32, expire) + __field(bool, status) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->state = state; + __entry->delay_us = delay_us; + __entry->expire = expire; + __entry->status = status; + ), + TP_printk(DW_PR_FMT ", current state: %s, new state: %s, delay_us: %d, expire: %u", + DW_PR_ARG, __entry->status ? "ON" : "OFF", __entry->state ? "ON" : "OFF", + __entry->delay_us, (unsigned)__entry->expire) +); + +TRACE_EVENT(dw3000_coex_gpio_start, + TP_PROTO(struct dw3000 *dw, int delay_us, bool status, int coex_interval_us), + TP_ARGS(dw, delay_us, status, coex_interval_us), + TP_STRUCT__entry( + DW_ENTRY + __field(int, delay_us) + __field(bool, status) + __field(int, coex_interval_us) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->delay_us = delay_us; + __entry->status = status; + __entry->coex_interval_us = coex_interval_us; + ), + TP_printk(DW_PR_FMT ", delay_us: %d, status: %s, coex_interval_us: %d", + DW_PR_ARG, __entry->delay_us, + __entry->status ? "ON" : "OFF", __entry->coex_interval_us) +); + +TRACE_EVENT(dw3000_coex_gpio_stop, + TP_PROTO(struct dw3000 *dw, bool status), + TP_ARGS(dw, status), + TP_STRUCT__entry( + DW_ENTRY + __field(bool, status) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->status = status; + ), + TP_printk(DW_PR_FMT ", status: %s", + DW_PR_ARG, + __entry->status ? "ON" : "OFF") +); + +TRACE_EVENT(dw3000_adjust_tx_power, + TP_PROTO(struct dw3000 *dw, u32 base_power, u32 adjusted_power, + u32 frm_dur, u16 pl_len, u8 chan, u16 th_boost, + u16 app_boost), + TP_ARGS(dw, base_power, adjusted_power, frm_dur, pl_len, chan, + th_boost, app_boost), + TP_STRUCT__entry( + DW_ENTRY + __field(u32, base_power) + __field(u32, adjusted_power) + __field(u32, frm_dur) + __field(u16, pl_len) + __field(u8, chan) + __field(u16, th_boost) + __field(u16, app_boost) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->base_power = base_power; + __entry->adjusted_power = adjusted_power; + __entry->frm_dur = frm_dur; + __entry->pl_len = pl_len; + __entry->chan = chan; + __entry->th_boost = th_boost; + __entry->app_boost = app_boost; + ), + TP_printk(DW_PR_FMT " base pwr: 0x%08x, adjusted: 0x%08x (chan: %u," + "frm_dur: %u, PL_len: %u, th. boost: %u, applied boost: %u)", + DW_PR_ARG, __entry->base_power, __entry->adjusted_power, + __entry->chan, __entry->frm_dur, __entry->pl_len, + __entry->th_boost, __entry->app_boost) +); + +TRACE_EVENT(dw3000_rx_rssi, + TP_PROTO(struct dw3000 *dw, const char *chip_name, bool sts, u32 cir_pwr, + u16 pacc_cnt, u8 prf_64mhz, u8 dgc_dec), + TP_ARGS(dw, chip_name, sts, cir_pwr, pacc_cnt, prf_64mhz, dgc_dec), + TP_STRUCT__entry( + DW_ENTRY + __field(const char *, chip_name) + __field(bool, sts) + __field(u32, cir_pwr) + __field(u16, pacc_cnt) + __field(u8, prf_64mhz) + __field(u8, dgc_dec) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->chip_name = chip_name; + __entry->sts = sts; + __entry->cir_pwr = cir_pwr; + __entry->pacc_cnt = pacc_cnt; + __entry->prf_64mhz = prf_64mhz; + __entry->dgc_dec = dgc_dec; + ), + TP_printk(DW_PR_FMT ", chip: %s, sts: %u, cir_pwr: %u, pacc_cnt: %u" + ", prf_64mhz: %u, dgc_dec: %u", + DW_PR_ARG, __entry->chip_name, __entry->sts, + __entry->cir_pwr, __entry->pacc_cnt, __entry->prf_64mhz, + __entry->dgc_dec) + ); + +TRACE_EVENT(dw3000_isr_dss_stat, + TP_PROTO(struct dw3000 *dw, u8 dss_stat), + TP_ARGS(dw, dss_stat), + TP_STRUCT__entry( + DW_ENTRY + __field(u8, dss_stat) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->dss_stat = dss_stat; + ), + TP_printk(DW_PR_FMT ", dss_stat: %s", DW_PR_ARG, + __print_flags(__entry->dss_stat, "|", + DW3000_DSS_STATS_SYMBOLS)) +); + +TRACE_EVENT(dw3000_nfcc_coex_header_put, + TP_PROTO(struct dw3000 *dw, u8 ver_id, u8 seq_num), + TP_ARGS(dw, ver_id, seq_num), + TP_STRUCT__entry( + DW_ENTRY + __field(u8, ver_id) + __field(u8, seq_num) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->ver_id = ver_id; + __entry->seq_num = seq_num; + ), + TP_printk(DW_PR_FMT ", ver_id: %u, seq_num: %u", DW_PR_ARG, + __entry->ver_id, __entry->seq_num) +); + +TRACE_EVENT(dw3000_nfcc_coex_clock_sync_payload_put, + TP_PROTO(struct dw3000 *dw, u32 session_time0_sys_time), + TP_ARGS(dw, session_time0_sys_time), + TP_STRUCT__entry( + DW_ENTRY + __field(u32, session_time0_sys_time) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->session_time0_sys_time = session_time0_sys_time; + ), + TP_printk(DW_PR_FMT ", session_time0_sys_time: 0x%08x", DW_PR_ARG, + __entry->session_time0_sys_time) +); + +TRACE_EVENT(dw3000_nfcc_coex_clock_offset_payload_put, + TP_PROTO(struct dw3000 *dw, u32 clock_offset_sys_time), + TP_ARGS(dw, clock_offset_sys_time), + TP_STRUCT__entry( + DW_ENTRY + __field(u32, clock_offset_sys_time) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->clock_offset_sys_time = clock_offset_sys_time; + ), + TP_printk(DW_PR_FMT ", clock_offset_sys_time: 0x%08x", DW_PR_ARG, + __entry->clock_offset_sys_time) +); + +TRACE_EVENT(dw3000_nfcc_coex_stop_session_payload_put, + TP_PROTO(struct dw3000 *dw, u32 session_id), + TP_ARGS(dw, session_id), + TP_STRUCT__entry( + DW_ENTRY + __field(u32, session_id) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->session_id = session_id; + ), + TP_printk(DW_PR_FMT ", session_id %d", DW_PR_ARG, + __entry->session_id) +); + +TRACE_EVENT(dw3000_nfcc_coex_rx_msg_info, + TP_PROTO(struct dw3000 *dw, u32 next_timestamp_dtu, + int next_duration_dtu), + TP_ARGS(dw, next_timestamp_dtu, next_duration_dtu), + TP_STRUCT__entry( + DW_ENTRY + __field(u32, next_timestamp_dtu) + __field(int, next_duration_dtu) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->next_timestamp_dtu = next_timestamp_dtu; + __entry->next_duration_dtu = next_duration_dtu; + ), + TP_printk(DW_PR_FMT ", next_timestamp_dtu: 0x%08x" + ", next_duration_dtu: 0x%08x", DW_PR_ARG, + __entry->next_timestamp_dtu, __entry->next_duration_dtu) +); + +TRACE_EVENT(dw3000_nfcc_coex_header_check, + TP_PROTO(struct dw3000 *dw, const char *signature, + u8 ver_id, u8 seq_num, u8 nb_tlv), + TP_ARGS(dw, signature, ver_id, seq_num, nb_tlv), + TP_STRUCT__entry( + DW_ENTRY + __array(u8, signature, DW3000_NFCC_COEX_SIGNATURE_LEN) + __field(u8, ver_id) + __field(u8, seq_num) + __field(u8, nb_tlv) + ), + TP_fast_assign( + DW_ASSIGN; + memcpy(__entry->signature, signature, + DW3000_NFCC_COEX_SIGNATURE_LEN); + __entry->ver_id = ver_id; + __entry->seq_num = seq_num; + __entry->nb_tlv = nb_tlv; + ), + TP_printk(DW_PR_FMT ", signature: %s, ver_id: %u, seq_num: %u" + ", nb_tlv: %u", DW_PR_ARG, + __print_hex(__entry->signature, + DW3000_NFCC_COEX_SIGNATURE_LEN), + __entry->ver_id, __entry->seq_num, __entry->nb_tlv) +); + +TRACE_EVENT(dw3000_nfcc_coex_tlv_check, + TP_PROTO(struct dw3000 *dw, enum dw3000_nfcc_coex_tlv_type type, + u8 len, const u8 *data), + TP_ARGS(dw, type, len, data), + TP_STRUCT__entry( + DW_ENTRY + DW3000_NFCC_COEX_TLV_TYPE_ENTRY + __dynamic_array(u8, data, len) + ), + TP_fast_assign( + DW_ASSIGN; + DW3000_NFCC_COEX_TLV_TYPE_ASSIGN; + memcpy(__get_dynamic_array(data), data, + __get_dynamic_array_len(data)); + ), + TP_printk(DW_PR_FMT ", " DW3000_NFCC_COEX_TLV_TYPE_PR_FMT ", data: %s", + DW_PR_ARG, DW3000_NFCC_COEX_TLV_TYPE_ARG, + __print_hex(__get_dynamic_array(data), __get_dynamic_array_len(data))) +); + +TRACE_EVENT(dw3000_nfcc_coex_handle_access, + TP_PROTO(struct dw3000 *dw, const struct llhw_vendor_cmd_nfcc_coex_handle_access *info, + s32 idle_duration_dtu), + TP_ARGS(dw, info, idle_duration_dtu), + TP_STRUCT__entry( + DW_ENTRY + __field(bool, start) + __field(s32, idle_duration_dtu) + __field(u32, timestamp_dtu) + __field(int, duration_dtu) + __field(int, chan) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->start = info->start; + __entry->idle_duration_dtu = idle_duration_dtu; + __entry->timestamp_dtu = info->timestamp_dtu; + __entry->duration_dtu = info->duration_dtu; + __entry->chan = info->chan; + ), + TP_printk(DW_PR_FMT ", start: %s, idle_duration_dtu: 0x%08x" + ", timestamp_dtu: 0x%08x, duration_dtu: 0x%08x," + " chan: %d", DW_PR_ARG, + __entry->start ? "true" : "false", + __entry->idle_duration_dtu, + __entry->timestamp_dtu, + __entry->duration_dtu, __entry->chan) +); + +TRACE_EVENT(dw3000_nfcc_coex_wakeup_and_send, + TP_PROTO(struct dw3000 *dw, enum dw3000_nfcc_coex_send send, + s32 idle_duration_dtu, u32 send_timestamp_dtu), + TP_ARGS(dw, send, idle_duration_dtu, send_timestamp_dtu), + TP_STRUCT__entry( + DW_ENTRY + DW3000_NFCC_COEX_SEND_ENTRY + __field(s32, idle_duration_dtu) + __field(u32, send_timestamp_dtu) + ), + TP_fast_assign( + DW_ASSIGN; + DW3000_NFCC_COEX_SEND_ASSIGN, + __entry->idle_duration_dtu = idle_duration_dtu; + __entry->send_timestamp_dtu = send_timestamp_dtu; + ), + TP_printk(DW_PR_FMT ", " DW3000_NFCC_COEX_SEND_PR_FMT + ", idle_duration_dtu: %d, send_timestamp_dtu: 0x%08x", + DW_PR_ARG, + DW3000_NFCC_COEX_SEND_ARG, + __entry->idle_duration_dtu, + __entry->send_timestamp_dtu) +); + +TRACE_EVENT(dw3000_nfcc_coex_err, + TP_PROTO(struct dw3000 *dw, const char *err), + TP_ARGS(dw, err), + TP_STRUCT__entry( + DW_ENTRY + __string(err, err) + ), + TP_fast_assign( + DW_ASSIGN; + __assign_str(err, err); + ), + TP_printk(DW_PR_FMT ", err: \"%s\"", DW_PR_ARG, __get_str(err)) +); + +TRACE_EVENT(dw3000_nfcc_coex_warn, + TP_PROTO(struct dw3000 *dw, const char *warn), + TP_ARGS(dw, warn), + TP_STRUCT__entry( + DW_ENTRY + __string(warn, warn) + ), + TP_fast_assign( + DW_ASSIGN; + __assign_str(warn, warn); + ), + TP_printk(DW_PR_FMT ", warn: \"%s\"", DW_PR_ARG, __get_str(warn)) +); + +DEFINE_EVENT(dw_only_evt, dw3000_nfcc_coex_configure, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +DEFINE_EVENT(dw_only_evt, dw3000_nfcc_coex_watchdog_timeout, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +DEFINE_EVENT(dw_only_evt, dw3000_nfcc_coex_cancel_watchdog, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +DEFINE_EVENT(dw_only_evt, dw3000_nfcc_coex_spi1_avail, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +DEFINE_EVENT(dw_only_evt, dw3000_nfcc_coex_idle_timeout, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +TRACE_EVENT(dw3000_nfcc_coex_enable, + TP_PROTO(struct dw3000 *dw, int channel), + TP_ARGS(dw, channel), + TP_STRUCT__entry( + DW_ENTRY + __field(int, channel) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->channel = channel; + ), + TP_printk(DW_PR_FMT ", channel: %d", DW_PR_ARG, __entry->channel) +); + +DEFINE_EVENT(dw_only_evt, dw3000_nfcc_coex_disable, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw) +); + +/************************************************************* + * dw3000 optional functions traces * + *************************************************************/ + +#ifdef CONFIG_MCPS802154_TESTMODE +TRACE_EVENT(dw3000_tm_cmd, + TP_PROTO(struct dw3000 *dw, u32 cmd), + TP_ARGS(dw, cmd), + TP_STRUCT__entry( + DW_ENTRY + DW_TM_CMD_ENTRY + ), + TP_fast_assign( + DW_ASSIGN; + DW_TM_CMD_ASSIGN; + ), + TP_printk(DW_PR_FMT ", " DW_TM_CMD_PR_FMT, DW_PR_ARG, DW_TM_CMD_PR_ARG) +); +#endif + +TRACE_EVENT(dw3000_set_pdoa, + TP_PROTO(struct dw3000 *dw, u32 mode), + TP_ARGS(dw, mode), + TP_STRUCT__entry( + DW_ENTRY + __field(u32, mode) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->mode = mode; + ), + TP_printk(DW_PR_FMT ", mode: %d", DW_PR_ARG, + __entry->mode) + ); + +TRACE_EVENT(dw3000_read_pdoa, + TP_PROTO(struct dw3000 *dw, u32 pdoa), + TP_ARGS(dw, pdoa), + TP_STRUCT__entry( + DW_ENTRY + __field(u32, pdoa) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->pdoa = pdoa; + ), + TP_printk(DW_PR_FMT ", pdoa: 0x%08x", DW_PR_ARG, + __entry->pdoa) + ); + +TRACE_EVENT(dw3000_testmode_continuous_tx_start, + TP_PROTO(struct dw3000 *dw, u8 chan, u32 len, u32 rate), + TP_ARGS(dw, chan, len, rate), + TP_STRUCT__entry( + DW_ENTRY + __field(u8, chan) + __field(u32, len) + __field(u32, rate) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->chan = chan; + __entry->len = len; + __entry->rate = rate; + ), + TP_printk(DW_PR_FMT ", chan: %d, len: %d, rate: %d", DW_PR_ARG, + __entry->chan, + __entry->len, + __entry->rate) + ); + +TRACE_EVENT(dw3000_testmode_continuous_tx_stop, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw), + TP_STRUCT__entry( + DW_ENTRY + ), + TP_fast_assign( + DW_ASSIGN; + ), + TP_printk(DW_PR_FMT, DW_PR_ARG) + ); + +TRACE_EVENT(dw3000_read_clockoffset, + TP_PROTO(struct dw3000 *dw, s16 cfo), + TP_ARGS(dw, cfo), + TP_STRUCT__entry( + DW_ENTRY + __field(s16, cfo) + ), + TP_fast_assign( + DW_ASSIGN; + __entry->cfo = cfo; + ), + TP_printk(DW_PR_FMT ", clockoffset: %d", DW_PR_ARG, + __entry->cfo) + ); + +TRACE_EVENT(dw3000_nfcc_coex_prepare_config, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw), + TP_STRUCT__entry( + DW_ENTRY + ), + TP_fast_assign( + DW_ASSIGN; + ), + TP_printk(DW_PR_FMT, DW_PR_ARG) + ); + +TRACE_EVENT(dw3000_nfcc_coex_restore_config, + TP_PROTO(struct dw3000 *dw), + TP_ARGS(dw), + TP_STRUCT__entry( + DW_ENTRY + ), + TP_fast_assign( + DW_ASSIGN; + ), + TP_printk(DW_PR_FMT, DW_PR_ARG) + ); + +/* clang-format on */ +#endif /* !__DW3000_TRACE || TRACE_HEADER_MULTI_READ */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE dw3000_trc +#include <trace/define_trace.h> diff --git a/kernel/drivers/net/ieee802154/dw3000_txpower_adjustment.c b/kernel/drivers/net/ieee802154/dw3000_txpower_adjustment.c new file mode 100644 index 0000000..1b2ae14 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_txpower_adjustment.c @@ -0,0 +1,565 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/types.h> +#include "dw3000_trc.h" +#include "dw3000_txpower_adjustment.h" + +/* Size of Tx power compensation look-up table */ +#define LUT_COMP_SIZE 64 + +/* Margin for tx power adjustment in 0.1dB steps. */ +#define TXPOWER_ADJUSTMENT_MARGIN 5 + +/* TxPower difference between coarse gain n and coarse gain n+1 in 0.1dB Step. + * Same for CH5 and CH9 */ +#define COARSE_0_TO_1 32 +#define COARSE_1_TO_2 13 +#define COARSE_2_TO_3 5 +#define NUM_COARSE_GAIN 3 + +/* LookUpTable : 1000us - 200us */ +#define LUT_1000_200_US_NUM 33 +#define LUT_1000_200_US_STEP 25 +#define LUT_1000_200_US_MIN 200 +#define LUT_1000_200_US_MIN_BST 0 + +/* LookUpTable : 200us - 70us */ +#define LUT_200_70_US_NUM 14 +#define LUT_200_70_US_STEP 10 +#define LUT_200_70_US_MIN 70 +#define LUT_200_70_US_MAX_BST 113 + +/* The reference duration for a frame is 1000us. Longer frame will + * have 0dB boost. */ +#define FRAME_DURATION_REF 1000 + +#define MAX_BOOST_CH5 354 +#define MAX_BOOST_CH9 305 +#define TX_POWER_COARSE_GAIN_MASK 0x03 +#define TX_POWER_FINE_GAIN_MASK 0x3F + +/* Reference look-up table to calculate Txpower Dial Back depending on + * frame duration. Each new entry in the table is relative to the previous one. + * Using two different tables a per logarithmic calculation: + * - 1000us to 200us range (index unit of 25us) + * - 200us to 70us (index unit of 10us) + * The table values are in steps 0f 0.1dB + * This allows having a maximum granularity of 0.5dB between two frame + * duration, which is in the magnitude of the DW3XXX TxOutput setting. + */ +static const u8 + txpower_boost_per_frame_duration_1000_200_us[LUT_1000_200_US_NUM] = { + 0, /* 1000us */ + 1, /* 975us -> 1*0.1dB boost between 975us and 1000us frames */ + 2, /* 950us -> 2*0.1dB boost between 950us and 1000us frames */ + 3, /* 925us */ + 4, /* 900us */ + 5, /* 875us */ + 6, /* 850us */ + 7, /* 825us */ + 8, /* 800us */ + 9, /* 775us */ + 10, /* 750us */ + 11, /* 725us */ + 13, /* 700us */ + 15, /* 675us */ + 17, /* 650us */ + 19, /* 625us */ + 21, /* 600us */ + 23, /* 575us */ + 25, /* 550us */ + 27, /* 525us */ + 29, /* 500us */ + 31, /* 475us */ + 33, /* 450us */ + 35, /* 425us */ + 38, /* 400us */ + 41, /* 375us */ + 44, /* 350us */ + 47, /* 325us */ + 50, /* 300us */ + 54, /* 275us */ + 58, /* 250us */ + 63, /* 225us */ + 68 /* 200us */ + }; + +static const u8 txpower_boost_per_frame_duration_200_70_us[LUT_200_70_US_NUM] = { + 68, /* 200us -> 68*0.1dB boost between 200us frame and 1000us frame. */ + 70, /* 190us -> 70*0.1dB boost between 190us and 1000us frame */ + 72, /* 180us */ + 74, /* 170us */ + 77, /* 160us */ + 80, /* 150us */ + 83, /* 140us */ + 86, /* 130us */ + 89, /* 120us */ + 93, /* 110us */ + 97, /* 100us */ + 102, /* 90us */ + 107, /* 80us */ + 113 /* 70us */ +}; + +/* TxPower difference between the coarse gain setting (i+1) and (i) + * The step is in unit of 0.1dB + */ +static const u8 lut_coarse_gain[NUM_COARSE_GAIN] = { + COARSE_0_TO_1, + COARSE_1_TO_2, + COARSE_2_TO_3, +}; + +/* TxPower difference between the fine gain setting (i+1) and (i) + * The step is in unit of 0.1dB + */ +static const u8 fine_gain_lut_chan5[LUT_COMP_SIZE] = { + 0, /* Fine gain setting ( 0 ) Minimum Value */ + 32, /* Fine gain setting ( 1 - 0 ) */ + 29, /* Fine gain setting ( 2 - 1 ) */ + 28, /* Fine gain setting ( 3 - 2 ) */ + 20, /* Fine gain setting ( 4 - 3 ) */ + 18, /* Fine gain setting ( 5 - 4 ) */ + 12, /* Fine gain setting ( 6 - 5 ) */ + 13, /* Fine gain setting ( 7 - 6 ) */ + 10, /* Fine gain setting ( 8 - 7 ) */ + 10, /* Fine gain setting ( 9 - 8 ) */ + 7, /* Fine gain setting ( 10 - 9 ) */ + 8, /* Fine gain setting ( 11 - 10 ) */ + 6, /* Fine gain setting ( 12 - 11 ) */ + 7, /* Fine gain setting ( 13 - 12 ) */ + 5, /* Fine gain setting ( 14 - 13 ) */ + 6, /* Fine gain setting ( 15 - 14 ) */ + 5, /* Fine gain setting ( 16 - 15 ) */ + 5, /* Fine gain setting ( 17 - 16 ) */ + 4, /* Fine gain setting ( 18 - 17 ) */ + 4, /* Fine gain setting ( 19 - 18 ) */ + 4, /* Fine gain setting ( 20 - 19 ) */ + 4, /* Fine gain setting ( 21 - 20 ) */ + 3, /* Fine gain setting ( 22 - 21 ) */ + 3, /* Fine gain setting ( 23 - 22 ) */ + 3, /* Fine gain setting ( 24 - 23 ) */ + 3, /* Fine gain setting ( 25 - 24 ) */ + 2, /* Fine gain setting ( 26 - 25 ) */ + 3, /* Fine gain setting ( 27 - 26 ) */ + 2, /* Fine gain setting ( 28 - 27 ) */ + 3, /* Fine gain setting ( 29 - 28 ) */ + 2, /* Fine gain setting ( 30 - 29 ) */ + 3, /* Fine gain setting ( 31 - 30 ) */ + 3, /* Fine gain setting ( 32 - 31 ) */ + 2, /* Fine gain setting ( 33 - 32 ) */ + 2, /* Fine gain setting ( 34 - 33 ) */ + 2, /* Fine gain setting ( 35 - 34 ) */ + 1, /* Fine gain setting ( 36 - 35 ) */ + 2, /* Fine gain setting ( 37 - 36 ) */ + 1, /* Fine gain setting ( 38 - 37 ) */ + 2, /* Fine gain setting ( 39 - 38 ) */ + 1, /* Fine gain setting ( 40 - 39 ) */ + 2, /* Fine gain setting ( 41 - 40 ) */ + 1, /* Fine gain setting ( 42 - 41 ) */ + 1, /* Fine gain setting ( 43 - 42 ) */ + 1, /* Fine gain setting ( 44 - 43 ) */ + 1, /* Fine gain setting ( 45 - 44 ) */ + 1, /* Fine gain setting ( 46 - 45 ) */ + 1, /* Fine gain setting ( 47 - 46 ) */ + 1, /* Fine gain setting ( 48 - 47 ) */ + 1, /* Fine gain setting ( 49 - 48 ) */ + 1, /* Fine gain setting ( 50 - 49 ) */ + 1, /* Fine gain setting ( 51 - 50 ) */ + 1, /* Fine gain setting ( 52 - 51 ) */ + 1, /* Fine gain setting ( 53 - 52 ) */ + 1, /* Fine gain setting ( 54 - 53 ) */ + 1, /* Fine gain setting ( 55 - 54 ) */ + 1, /* Fine gain setting ( 56 - 55 ) */ + 1, /* Fine gain setting ( 57 - 56 ) */ + 1, /* Fine gain setting ( 58 - 57 ) */ + 1, /* Fine gain setting ( 59 - 58 ) */ + 1, /* Fine gain setting ( 60 - 59 ) */ + 1, /* Fine gain setting ( 61 - 60 ) */ + 1, /* Fine gain setting ( 62 - 61 ) */ + 1 /* Fine gain setting ( 63 - 62 ) */ +}; + +static const u8 fine_gain_lut_chan9[LUT_COMP_SIZE] = { + 0, /* Fine gain setting ( 0 ) Minimum Value */ + 11, /* Fine gain setting ( 1 - 0 ) */ + 14, /* Fine gain setting ( 2 - 1 ) */ + 18, /* Fine gain setting ( 3 - 2 ) */ + 15, /* Fine gain setting ( 4 - 3 ) */ + 15, /* Fine gain setting ( 5 - 4 ) */ + 10, /* Fine gain setting ( 6 - 5 ) */ + 12, /* Fine gain setting ( 7 - 6 ) */ + 9, /* Fine gain setting ( 8 - 7 ) */ + 9, /* Fine gain setting ( 9 - 8 ) */ + 7, /* Fine gain setting ( 10 - 9 ) */ + 8, /* Fine gain setting ( 11 - 10 ) */ + 6, /* Fine gain setting ( 12 - 11 ) */ + 7, /* Fine gain setting ( 13 - 12 ) */ + 5, /* Fine gain setting ( 14 - 13 ) */ + 6, /* Fine gain setting ( 15 - 14 ) */ + 5, /* Fine gain setting ( 16 - 15 ) */ + 5, /* Fine gain setting ( 17 - 16 ) */ + 4, /* Fine gain setting ( 18 - 17 ) */ + 5, /* Fine gain setting ( 19 - 18 ) */ + 4, /* Fine gain setting ( 20 - 19 ) */ + 4, /* Fine gain setting ( 21 - 20 ) */ + 3, /* Fine gain setting ( 22 - 21 ) */ + 4, /* Fine gain setting ( 23 - 22 ) */ + 3, /* Fine gain setting ( 24 - 23 ) */ + 3, /* Fine gain setting ( 25 - 24 ) */ + 3, /* Fine gain setting ( 26 - 25 ) */ + 3, /* Fine gain setting ( 27 - 26 ) */ + 3, /* Fine gain setting ( 28 - 27 ) */ + 3, /* Fine gain setting ( 29 - 28 ) */ + 2, /* Fine gain setting ( 30 - 29 ) */ + 3, /* Fine gain setting ( 31 - 30 ) */ + 3, /* Fine gain setting ( 32 - 31 ) */ + 2, /* Fine gain setting ( 33 - 32 ) */ + 2, /* Fine gain setting ( 34 - 33 ) */ + 2, /* Fine gain setting ( 35 - 34 ) */ + 2, /* Fine gain setting ( 36 - 35 ) */ + 2, /* Fine gain setting ( 37 - 36 ) */ + 1, /* Fine gain setting ( 38 - 37 ) */ + 2, /* Fine gain setting ( 39 - 38 ) */ + 2, /* Fine gain setting ( 40 - 39 ) */ + 2, /* Fine gain setting ( 41 - 40 ) */ + 2, /* Fine gain setting ( 42 - 41 ) */ + 2, /* Fine gain setting ( 43 - 42 ) */ + 1, /* Fine gain setting ( 44 - 43 ) */ + 2, /* Fine gain setting ( 45 - 44 ) */ + 1, /* Fine gain setting ( 46 - 45 ) */ + 2, /* Fine gain setting ( 47 - 46 ) */ + 1, /* Fine gain setting ( 48 - 47 ) */ + 1, /* Fine gain setting ( 49 - 48 ) */ + 1, /* Fine gain setting ( 50 - 49 ) */ + 2, /* Fine gain setting ( 51 - 50 ) */ + 1, /* Fine gain setting ( 52 - 51 ) */ + 1, /* Fine gain setting ( 53 - 52 ) */ + 1, /* Fine gain setting ( 54 - 53 ) */ + 1, /* Fine gain setting ( 55 - 54 ) */ + 1, /* Fine gain setting ( 56 - 55 ) */ + 1, /* Fine gain setting ( 57 - 56 ) */ + 1, /* Fine gain setting ( 58 - 57 ) */ + 1, /* Fine gain setting ( 59 - 58 ) */ + 0, /* Fine gain setting ( 60 - 59 ) */ + 1, /* Fine gain setting ( 61 - 60 ) */ + 1, /* Fine gain setting ( 62 - 61 ) */ + 1 /* Fine gain setting ( 63 - 62 ) */ +}; + +/* Calculate the power_boost for a frame_duration_us relative to 1ms */ +static u8 calculate_power_boost(u16 frame_duration_us) +{ + const u8 *lut = NULL; + u16 lut_i; + u16 lut_num; + u16 lut_min; + u16 lut_step; + u16 limit; + + /* Calculating the LUT index corresponding to the frameduration */ + if (frame_duration_us >= FRAME_DURATION_REF) { + return LUT_1000_200_US_MIN_BST; + } else if (frame_duration_us < LUT_200_70_US_MIN) { + return LUT_200_70_US_MAX_BST; + } else if (frame_duration_us > LUT_1000_200_US_MIN) { + lut_num = LUT_1000_200_US_NUM; + lut_min = LUT_1000_200_US_MIN; + lut_step = LUT_1000_200_US_STEP; + lut = txpower_boost_per_frame_duration_1000_200_us; + } else { + lut_num = LUT_200_70_US_NUM; + lut_min = LUT_200_70_US_MIN; + lut_step = LUT_200_70_US_STEP; + lut = txpower_boost_per_frame_duration_200_70_us; + } + + lut_i = (lut_num - (frame_duration_us - lut_min) / lut_step); + limit = (lut_num - lut_i) * lut_step + lut_min; + + /* Selecting the index that gives the closest LUT */ + if (abs(frame_duration_us - limit) > lut_step / 2) { + lut_i--; + } + + return lut[lut_i - 1]; +} + +/** + * adjust_tx_power() - actual TxPower setting calculation + * @frame_duration_us: the frame (headers, payload, FCS) duration + * @ref_tx_power: the power setting corresponding to a frame of 1ms (0dB) + * @channel: the current RF channel used for transmission of UWB frames + * @th_boost: pointer to store the theorical boost to be applied + * @applied_boost: pointer to store the calculated, actually applied boost + * + * The reference TxPower setting should correspond to a 1ms frame (or 0dB) + * boost. The boost to be applied should be provided in unit of 0.1dB boost. + * + * Return: the adjusted_tx_power setting that can be used to configure the + * chip transmission level + */ +static u32 adjust_tx_power(u16 frame_duration_us, u32 ref_tx_power, u8 channel, + u16 *th_boost, u16 *applied_boost) +{ + u32 adjusted_tx_power; + u16 target_boost = 0; + u16 base_target_boost = 0; + u16 current_boost = 0; + u16 best_boost_abs = 0; + u16 best_boost = 0; + u16 upper_limit = 0; + u16 lower_limit = 0; + + const u8 *lut = NULL; + uint8_t ref_tx_power_byte[4]; /* txpwr of each segment (UM 8.2.2.20) */ + uint8_t adj_tx_power_byte[4]; + uint8_t adj_tx_power_boost[4]; + u8 best_index; + u8 best_coarse_gain; + u8 ref_coarse_gain; + u8 ref_fine_gain; + bool within_margin_flag; + bool reached_max_fine_gain_flag; + bool shortcut_optim_flag; + u8 unlock; + u8 i, j; + int k; + int ret = 0; + + base_target_boost = calculate_power_boost(frame_duration_us); + if (th_boost) { + *th_boost = base_target_boost; + } + switch (channel) { + case 5: + lut = fine_gain_lut_chan5; + if (base_target_boost > MAX_BOOST_CH5) + base_target_boost = MAX_BOOST_CH5; + break; + default: + lut = fine_gain_lut_chan9; + if (base_target_boost > MAX_BOOST_CH9) + base_target_boost = MAX_BOOST_CH9; + break; + } + + for (k = 0; k < 4; k++) { + target_boost = base_target_boost; + current_boost = 0; + best_boost_abs = 0; + best_boost = 0; + best_index = 0; + best_coarse_gain = 0; + within_margin_flag = false; + reached_max_fine_gain_flag = false; + shortcut_optim_flag = false; + unlock = 0; + i = 0; + ref_tx_power_byte[k] = (u8)(ref_tx_power >> (k << 3)); + ref_coarse_gain = ((u8)ref_tx_power_byte[k]) & + TX_POWER_COARSE_GAIN_MASK; + ref_fine_gain = ((u8)ref_tx_power_byte[k] >> 2) & + TX_POWER_FINE_GAIN_MASK; + adj_tx_power_boost[k] = 0; + i = ref_fine_gain; + + /* Avoid re-doing the same math four times */ + for (j = 0; !shortcut_optim_flag && (j < k); j++) { + if (ref_tx_power_byte[k] == ref_tx_power_byte[j]) { + adj_tx_power_byte[k] = adj_tx_power_byte[j]; + shortcut_optim_flag = true; + } + } + if (shortcut_optim_flag) + continue; + + /* PHR power must be 6dB lower than PSDU */ + if (k == 1) { + uint8_t psdu_boost_err = + (target_boost > adj_tx_power_boost[0] ? + target_boost - adj_tx_power_boost[0] : + 0); + if (psdu_boost_err < 0) + psdu_boost_err = 0; + target_boost -= psdu_boost_err; + } + + upper_limit = target_boost + TXPOWER_ADJUSTMENT_MARGIN; + lower_limit = + (target_boost > TXPOWER_ADJUSTMENT_MARGIN ? + target_boost - TXPOWER_ADJUSTMENT_MARGIN : + 0); + best_boost_abs = TXPOWER_ADJUSTMENT_MARGIN; + + if (target_boost < TXPOWER_ADJUSTMENT_MARGIN && + target_boost < lut[i + 1] - TXPOWER_ADJUSTMENT_MARGIN) { + if (k == 0 && applied_boost) + *applied_boost = 0; + adj_tx_power_byte[k] = ref_tx_power_byte[k]; + continue; + } + + /* Increase coarse setting if the required boost is greater than the + * TxPower gain using the increased coarse setting. + * NB : Coarse gain = 0x3 should not be used in CHAN9 */ + while (ref_coarse_gain < 0x2) { + if (lut_coarse_gain[ref_coarse_gain] < + target_boost - current_boost) { + current_boost += + lut_coarse_gain[ref_coarse_gain]; + ref_coarse_gain++; + } else { + break; + } + } + + /* Increase current_boost until reaching value closest to target_boost */ + while (current_boost != target_boost) { + unlock++; + /* Ensure loop does not got "locked" */ + if (unlock > 2 * LUT_COMP_SIZE) { + ret = -EFAULT; + break; + } + + if (current_boost > lower_limit && + current_boost < upper_limit) { + if (abs((s16)(target_boost - current_boost)) <= + best_boost_abs) { + best_boost_abs = abs((s16)target_boost - current_boost); + best_boost = current_boost; + best_index = i; + best_coarse_gain = ref_coarse_gain; + within_margin_flag = true; + } else if (within_margin_flag) { + i = best_index; + ref_coarse_gain = best_coarse_gain; + break; + } + } else if (within_margin_flag) { + current_boost -= lut[i]; + i = best_index; + break; + } + + /* Corner case: when fine gain setting is very low, it can happened that + * current boost is already larger than target_boost but not within margin. + * Then, just return current solution. */ + if (current_boost >= upper_limit && + !reached_max_fine_gain_flag) { + break; + } + + /* Search for max fine gain value */ + if (i == LUT_COMP_SIZE - 1) { + reached_max_fine_gain_flag = true; + + /* return previously found solution */ + if (within_margin_flag) { + i = best_index; + ref_coarse_gain = best_coarse_gain; + current_boost = best_boost; + break; + } + + if ((ref_coarse_gain == 0x3) || + (ref_coarse_gain == 0x2 && channel == 9)) { + break; + } + + if (current_boost + + lut_coarse_gain[ref_coarse_gain] <= + target_boost) { + current_boost += + lut_coarse_gain[ref_coarse_gain]; + ref_coarse_gain++; + break; + } else { + current_boost += + lut_coarse_gain[ref_coarse_gain]; + ref_coarse_gain++; + } + } + + /* Adjust fine gain */ + if (!reached_max_fine_gain_flag) { + i++; + i &= TX_POWER_FINE_GAIN_MASK; + current_boost += lut[i]; + } else { + current_boost -= lut[i]; + i--; + i &= TX_POWER_FINE_GAIN_MASK; + if (i == 0) + reached_max_fine_gain_flag = false; + } + } + + if (ret) { + adj_tx_power_byte[k] = ref_tx_power_byte[k]; + current_boost = 0; + } else { + adj_tx_power_byte[k] = (i << 2) | ref_coarse_gain; + } + + adj_tx_power_boost[k] = current_boost; + + if (applied_boost && (k == 0)) + *applied_boost = current_boost; + } + adjusted_tx_power = (u32)adj_tx_power_byte[3] << 24 | + (u32)adj_tx_power_byte[2] << 16 | + (u32)adj_tx_power_byte[1] << 8 | + (u32)adj_tx_power_byte[0]; + + return adjusted_tx_power; +} + +/** + * dw3000_adjust_tx_power() - calculates the adjusted TxPower + * @dw: the DW device + * @payload_bytes: payload skbuf's length in bytes + * + * Wraps actual TX power calculation. Converts frame length to duration (in µs) + * with respect to RF settings (channel, PRF, ...) then calls adjust_tx_power + * + * Return: the adjusted_tx_power setting than can be used to configure the chip + * transmission level + */ +int dw3000_adjust_tx_power(struct dw3000 *dw, int payload_bytes) +{ + u16 th_boost, app_boost; + u8 chan = dw->config.chan; + u16 frm_dur = + DTU_TO_US(dw3000_frame_duration_dtu(dw, payload_bytes, true)); + u32 adjusted_tx_power = adjust_tx_power(frm_dur, dw->txconfig.power, + chan, &th_boost, &app_boost); + + trace_dw3000_adjust_tx_power(dw, dw->txconfig.power, adjusted_tx_power, + frm_dur, payload_bytes, chan, th_boost, + app_boost); + + return dw3000_set_tx_power_register(dw, adjusted_tx_power); +} diff --git a/kernel/drivers/net/ieee802154/dw3000_txpower_adjustment.h b/kernel/drivers/net/ieee802154/dw3000_txpower_adjustment.h new file mode 100644 index 0000000..f25fa32 --- /dev/null +++ b/kernel/drivers/net/ieee802154/dw3000_txpower_adjustment.h @@ -0,0 +1,36 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef __DW3000_TXPOWER_ADJUSTMENT_H +#define __DW3000_TXPOWER_ADJUSTMENT_H + +#include "dw3000_core.h" +#include "dw3000_core_reg.h" + +static inline int dw3000_set_tx_power_register(struct dw3000 *dw, u32 value) +{ + return dw3000_reg_write32(dw, DW3000_TX_POWER_ID, 0, value); +} + +int dw3000_adjust_tx_power(struct dw3000 *dw, int payload_bytes); + +#endif /* __DW3000_TXPOWER_ADJUSTMENT_H */ diff --git a/kernel/drivers/net/ieee802154/mcps802154_fake.c b/kernel/drivers/net/ieee802154/mcps802154_fake.c new file mode 100644 index 0000000..082b20b --- /dev/null +++ b/kernel/drivers/net/ieee802154/mcps802154_fake.c @@ -0,0 +1,488 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/timer.h> +#include <net/mcps802154.h> + +MODULE_AUTHOR("Saad Zouiten <saad.zouiten@qorvo.com>"); +MODULE_DESCRIPTION("stubbed 802.15.4 mcps driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); + +static const int g_time_interval = 1000; +static struct timer_list g_timer; +static struct mcps802154_llhw *driver_llhw; +static bool rx_enabled; +static bool tx_queued; +static bool started; +static bool stop_timer; +static bool scanning; +static u8 seq; +static u8 curchan; + +const char *const calib_strings[] = { "calib.param1", NULL }; +static u8 calib; + +static void periodic_task(struct timer_list *unused) +{ + if (stop_timer) + return; + pr_info("fake_mcps: %s called\n", __func__); + /*Restarting the timer...*/ + mod_timer(&g_timer, jiffies + msecs_to_jiffies(g_time_interval)); + if (rx_enabled) { + rx_enabled = false; + mcps802154_rx_frame(driver_llhw); + } + if (tx_queued) { + tx_queued = false; + mcps802154_tx_done(driver_llhw); + } +} + +static int start(struct mcps802154_llhw *llhw) +{ + pr_info("fake_mcps: %s called\n", __func__); + started = true; + return 0; +} + +static void stop(struct mcps802154_llhw *llhw) +{ + pr_info("fake_mcps: %s called\n", __func__); + started = false; +} + +static int tx_frame(struct mcps802154_llhw *llhw, struct sk_buff *skb, + const struct mcps802154_tx_frame_config *config, + int frame_idx, int next_delay_dtu) +{ + if (!started) { + pr_err("fake_mcps: %s called and not started\n", __func__); + return -EIO; + } + pr_info("fake_mcps: tx_frame called skb len=%d\n", skb->len); + print_hex_dump(KERN_CONT, " ", DUMP_PREFIX_OFFSET, 16, 1, skb->data, + skb->len, false); + tx_queued = true; + return 0; +} + +static int rx_enable(struct mcps802154_llhw *llhw, + const struct mcps802154_rx_frame_config *info, + int frame_idx, int next_delay_dtu) +{ + if (!started) { + pr_err("fake_mcps: %s called and not started\n", __func__); + return -EIO; + } + pr_info("fake_mcps: %s called\n", __func__); + rx_enabled = true; + return 0; +} + +static int rx_disable(struct mcps802154_llhw *llhw) +{ + if (!started) { + pr_err("fake_mcps: %s called and not started\n", __func__); + return -EIO; + } + pr_info("fake_mcps: %s called\n", __func__); + rx_enabled = false; + return 0; +} + +static struct sk_buff *create_fake_data_frame(void) +{ + unsigned char *data; + struct sk_buff *skb = dev_alloc_skb(20 + 2); + + if (!skb) + return NULL; + data = skb_put(skb, 20); + data[0] = 0x21; /* Frame Control Field */ + data[1] = 0xc8; /* Frame Control Field */ + data[2] = 0x8b; /* Sequence number */ + data[3] = 0xff; /* Destination PAN ID 0xffff */ + data[4] = 0xff; /* Destination PAN ID */ + data[5] = 0x02; /* Destination short address 0x0002 */ + data[6] = 0x00; /* Destination short address */ + data[7] = 0x23; /* Source PAN ID 0x0023 */ + data[8] = 0x00; /* */ + data[9] = 0x60; /* Source extended address ae:c2:4a:1c:21:16:e2:60 */ + data[10] = 0xe2; /* */ + data[11] = 0x16; /* */ + data[12] = 0x21; /* */ + data[13] = 0x1c; /* */ + data[14] = 0x4a; /* */ + data[15] = 0xc2; /* */ + data[16] = 0xae; /* */ + data[17] = 0xaa; /* Payload */ + data[18] = 0xbb; /* */ + data[19] = 0xcc; /* */ + return skb; +} + +static struct sk_buff *create_fake_beacon_frame(void) +{ + unsigned char *data; + struct sk_buff *skb = dev_alloc_skb(17 + 2); + + if (!skb) + return NULL; + data = skb_put(skb, 17); + data[0] = 0x00; /* Frame Control Field: 0b00000000 */ + data[1] = 0xd0; /* Frame Control Field: 0b11010000 */ + data[2] = seq; /* Sequence number */ + data[3] = curchan; /* Source PAN ID 0x0100 + channel */ + data[4] = 0x01; /* */ + data[5] = curchan; /* Source extended address ae:c2:4a:1c:21:16:e2:xx */ + data[6] = 0xe2; /* */ + data[7] = 0x16; /* */ + data[8] = 0x21; /* */ + data[9] = 0x1c; /* */ + data[10] = 0x4a; /* */ + data[11] = 0xc2; /* */ + data[12] = 0xae; /* */ + data[13] = 0x77; /* Superframe spec */ + data[14] = 0xcf; /* */ + data[15] = 0x00; /* GTS spec */ + data[16] = 0x00; /* Pending address spec */ + /* TODO: CFP Alloc */ + /* TODO: CFP Usage */ + /* Update seq for next beacon */ + seq = (seq + 1) & 0x3f; + return skb; +} + +static int rx_get_frame(struct mcps802154_llhw *llhw, struct sk_buff **skb, + struct mcps802154_rx_frame_info *info) +{ + if (!started) { + pr_err("fake_mcps: %s called and not started\n", __func__); + return -EIO; + } + pr_info("fake_mcps: %s called\n", __func__); + if (scanning) + *skb = create_fake_beacon_frame(); + else + *skb = create_fake_data_frame(); + if (!*skb) { + pr_err("RX buffer allocation failed\n"); + return -ENOMEM; + } + return 0; +} + +static int rx_get_error_frame(struct mcps802154_llhw *llhw, + struct mcps802154_rx_frame_info *info) +{ + if (!started) { + pr_err("fake_mcps: %s called and not started\n", __func__); + return -EIO; + } + pr_info("fake_mcps: %s called\n", __func__); + return 0; +} + +static int idle(struct mcps802154_llhw *llhw, bool timestamp, u32 timestamp_dtu) +{ + if (!started) { + pr_err("fake_mcps: %s called and not started\n", __func__); + return -EIO; + } + pr_info("fake_mcps: %s called\n", __func__); + return 0; +} + +static int reset(struct mcps802154_llhw *llhw) +{ + if (!started) { + pr_err("fake_mcps: %s called and not started\n", __func__); + return -EIO; + } + pr_info("fake_mcps: %s called\n", __func__); + return 0; +} + +static int get_current_timestamp_dtu(struct mcps802154_llhw *llhw, + u32 *timestamp_dtu) +{ + if (!started) { + pr_err("fake_mcps: %s called and not started\n", __func__); + return -EIO; + } + pr_info("fake_mcps: %s called\n", __func__); + *timestamp_dtu = 0; + return 0; +} + +static u64 tx_timestamp_dtu_to_rmarker_rctu( + struct mcps802154_llhw *llhw, u32 tx_timestamp_dtu, + const struct mcps802154_hrp_uwb_params *hrp_uwb_params, + const struct mcps802154_channel *channel_params, int ant_set_id) +{ + if (!started) { + pr_err("fake_mcps: %s called and not started\n", __func__); + return -EIO; + } + pr_info("fake_mcps: %s called\n", __func__); + return 0; +} + +static s64 difference_timestamp_rctu(struct mcps802154_llhw *llhw, + u64 timestamp_a_rctu, u64 timestamp_b_rctu) +{ + if (!started) { + pr_err("fake_mcps: %s called and not started\n", __func__); + return -EIO; + } + pr_info("fake_mcps: %s called\n", __func__); + return 0; +} + +static int compute_frame_duration_dtu(struct mcps802154_llhw *llhw, + int payload_bytes) +{ + if (!started) { + pr_err("fake_mcps: %s called and not started\n", __func__); + return -EIO; + } + pr_info("fake_mcps: %s called\n", __func__); + return 0; +} + +static int set_channel(struct mcps802154_llhw *llhw, u8 page, u8 channel, + u8 preamble_code) +{ + pr_info("fake_mcps: %s called\n", __func__); + seq = 0; /* reset beacon sequence number */ + curchan = channel; /* save current channel */ + return 0; +} + +static int set_hrp_uwb_params(struct mcps802154_llhw *llhw, + const struct mcps802154_hrp_uwb_params *params) +{ + if (!started) { + pr_err("fake_mcps: %s called and not started\n", __func__); + return -EIO; + } + pr_info("fake_mcps: %s called\n", __func__); + return 0; +} + +static int set_hw_addr_filt(struct mcps802154_llhw *llhw, + struct ieee802154_hw_addr_filt *filt, + unsigned long changed) +{ + if (changed & IEEE802154_AFILT_SADDR_CHANGED) { + pr_info("fake_mcps: new short addr=%x", filt->short_addr); + } + if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED) { + pr_info("fake_mcps: new extended addr=%llx", filt->ieee_addr); + } + if (changed & IEEE802154_AFILT_PANID_CHANGED) { + pr_info("fake_mcps: new pan id=%x", filt->pan_id); + } + if (changed & IEEE802154_AFILT_PANC_CHANGED) { + pr_info("fake_mcps: new pan coordinator=%x", filt->pan_coord); + } + return 0; +} + +static int set_txpower(struct mcps802154_llhw *llhw, s32 mbm) +{ + if (!started) { + pr_err("fake_mcps: %s called and not started\n", __func__); + return -EIO; + } + pr_info("fake_mcps: %s called\n", __func__); + return 0; +} + +static int set_cca_mode(struct mcps802154_llhw *llhw, + const struct wpan_phy_cca *cca) +{ + if (!started) { + pr_err("fake_mcps: %s called and not started\n", __func__); + return -EIO; + } + pr_info("fake_mcps: %s called\n", __func__); + return 0; +} + +static int set_cca_ed_level(struct mcps802154_llhw *llhw, s32 mbm) +{ + if (!started) { + pr_err("fake_mcps: %s called and not started\n", __func__); + return -EIO; + } + pr_info("fake_mcps: %s called\n", __func__); + return 0; +} + +static int set_promiscuous_mode(struct mcps802154_llhw *llhw, bool on) +{ + pr_info("fake_mcps: %s called on=%d\n", __func__, on); + return 0; +} + +static int set_scanning_mode(struct mcps802154_llhw *llhw, bool mode) +{ + pr_info("fake_mcps: %s called\n", __func__); + scanning = mode; + return 0; +} + +static int set_calibration(struct mcps802154_llhw *llhw, const char *key, + void *value, size_t length) +{ + pr_info("fake_mcps: %s called\n", __func__); + if (!key || !value || length != 1) + return -EINVAL; + if (strcmp(key, calib_strings[0]) == 0) + calib = *(u8 *)value; + else + return -ENOENT; + return 0; +} + +static int get_calibration(struct mcps802154_llhw *llhw, const char *key, + void *value, size_t length) +{ + pr_info("fake_mcps: %s called\n", __func__); + if (!key || !value || length < 1) + return -EINVAL; + if (strcmp(key, calib_strings[0]) == 0) + *(u8 *)value = calib; + else + return -ENOENT; + return 1; +} + +static const char *const *list_calibration(struct mcps802154_llhw *llhw) +{ + pr_info("fake_mcps: %s called\n", __func__); + return calib_strings; +} + +static int vendor_cmd(struct mcps802154_llhw *llhw, u32 vendor_id, u32 subcmd, + void *data, size_t data_len) +{ + pr_info("fake_mcps: %s called\n", __func__); + return 0; +} + +static const struct mcps802154_ops fake_ops = { + .start = start, + .stop = stop, + .tx_frame = tx_frame, + .rx_enable = rx_enable, + .rx_disable = rx_disable, + .rx_get_frame = rx_get_frame, + .rx_get_error_frame = rx_get_error_frame, + .idle = idle, + .reset = reset, + .get_current_timestamp_dtu = get_current_timestamp_dtu, + .tx_timestamp_dtu_to_rmarker_rctu = tx_timestamp_dtu_to_rmarker_rctu, + .difference_timestamp_rctu = difference_timestamp_rctu, + .compute_frame_duration_dtu = compute_frame_duration_dtu, + .set_channel = set_channel, + .set_hrp_uwb_params = set_hrp_uwb_params, + .set_hw_addr_filt = set_hw_addr_filt, + .set_txpower = set_txpower, + .set_cca_mode = set_cca_mode, + .set_cca_ed_level = set_cca_ed_level, + .set_promiscuous_mode = set_promiscuous_mode, + .set_scanning_mode = set_scanning_mode, + .set_calibration = set_calibration, + .get_calibration = get_calibration, + .list_calibration = list_calibration, + .vendor_cmd = vendor_cmd, +}; + +static int __init fake_init(void) +{ + int r; + + pr_info("fake_mcps: init\n"); + driver_llhw = mcps802154_alloc_llhw(0, &fake_ops); + if (driver_llhw == NULL) { + return -ENOMEM; + } + driver_llhw->hw->flags = + (IEEE802154_HW_TX_OMIT_CKSUM | IEEE802154_HW_AFILT | + IEEE802154_HW_PROMISCUOUS | IEEE802154_HW_RX_OMIT_CKSUM); + driver_llhw->flags = + (MCPS802154_LLHW_BPRF | MCPS802154_LLHW_DATA_RATE_6M81 | + MCPS802154_LLHW_PHR_DATA_RATE_850K | + MCPS802154_LLHW_PHR_DATA_RATE_6M81 | MCPS802154_LLHW_PRF_16 | + MCPS802154_LLHW_PRF_64 | MCPS802154_LLHW_PSR_32 | + MCPS802154_LLHW_PSR_64 | MCPS802154_LLHW_PSR_128 | + MCPS802154_LLHW_PSR_256 | MCPS802154_LLHW_PSR_1024 | + MCPS802154_LLHW_PSR_4096 | MCPS802154_LLHW_SFD_4A | + MCPS802154_LLHW_SFD_4Z_8 | MCPS802154_LLHW_STS_SEGMENT_1 | + MCPS802154_LLHW_AOA_AZIMUTH | MCPS802154_LLHW_AOA_ELEVATION | + MCPS802154_LLHW_AOA_FOM); + /* UWB High band 802.15.4a-2007. */ + driver_llhw->hw->phy->supported.channels[4] |= 0xffe0; + + /* UWB symbol duration at PRF16 & PRF64 is ~1us */ + driver_llhw->hw->phy->symbol_duration = 1; + + /* Set extended address. */ + driver_llhw->hw->phy->perm_extended_addr = 0xd6552cd6e41ceb57; + + /* fake driver phy channel 5 as default */ + driver_llhw->hw->phy->current_page = 4; + driver_llhw->hw->phy->current_channel = 5; + driver_llhw->current_preamble_code = 9; + + r = mcps802154_register_llhw(driver_llhw); + if (r) { + mcps802154_free_llhw(driver_llhw); + driver_llhw = NULL; + return r; + } + + /*Starting the periodic task.*/ + timer_setup(&g_timer, periodic_task, 0); + mod_timer(&g_timer, jiffies + msecs_to_jiffies(g_time_interval)); + return 0; +} + +static void __exit fake_exit(void) +{ + pr_info("fake_mcps: Exit\n"); + mcps802154_unregister_llhw(driver_llhw); + mcps802154_free_llhw(driver_llhw); + driver_llhw = NULL; + /*Deleting the timer aka the periodic task.*/ + stop_timer = true; + del_timer(&g_timer); +} + +module_init(fake_init); +module_exit(fake_exit); diff --git a/kernel/include/net/fira_region_nl.h b/kernel/include/net/fira_region_nl.h new file mode 120000 index 0000000..48e3fd1 --- /dev/null +++ b/kernel/include/net/fira_region_nl.h @@ -0,0 +1 @@ +../../../mac/include/net/fira_region_nl.h
\ No newline at end of file diff --git a/kernel/include/net/fira_region_params.h b/kernel/include/net/fira_region_params.h new file mode 120000 index 0000000..e0d3525 --- /dev/null +++ b/kernel/include/net/fira_region_params.h @@ -0,0 +1 @@ +../../../mac/include/net/fira_region_params.h
\ No newline at end of file diff --git a/kernel/include/net/idle_region_nl.h b/kernel/include/net/idle_region_nl.h new file mode 120000 index 0000000..d1e5a9c --- /dev/null +++ b/kernel/include/net/idle_region_nl.h @@ -0,0 +1 @@ +../../../mac/include/net/idle_region_nl.h
\ No newline at end of file diff --git a/kernel/include/net/mcps802154.h b/kernel/include/net/mcps802154.h new file mode 120000 index 0000000..0f40957 --- /dev/null +++ b/kernel/include/net/mcps802154.h @@ -0,0 +1 @@ +../../../mac/include/net/mcps802154.h
\ No newline at end of file diff --git a/kernel/include/net/mcps802154_frame.h b/kernel/include/net/mcps802154_frame.h new file mode 120000 index 0000000..c209d7a --- /dev/null +++ b/kernel/include/net/mcps802154_frame.h @@ -0,0 +1 @@ +../../../mac/include/net/mcps802154_frame.h
\ No newline at end of file diff --git a/kernel/include/net/mcps802154_nl.h b/kernel/include/net/mcps802154_nl.h new file mode 120000 index 0000000..edeefa2 --- /dev/null +++ b/kernel/include/net/mcps802154_nl.h @@ -0,0 +1 @@ +../../../mac/include/net/mcps802154_nl.h
\ No newline at end of file diff --git a/kernel/include/net/mcps802154_schedule.h b/kernel/include/net/mcps802154_schedule.h new file mode 120000 index 0000000..31ab0c8 --- /dev/null +++ b/kernel/include/net/mcps802154_schedule.h @@ -0,0 +1 @@ +../../../mac/include/net/mcps802154_schedule.h
\ No newline at end of file diff --git a/kernel/include/net/mcps_skb_frag.h b/kernel/include/net/mcps_skb_frag.h new file mode 120000 index 0000000..55fa1c3 --- /dev/null +++ b/kernel/include/net/mcps_skb_frag.h @@ -0,0 +1 @@ +../../../mac/include/net/mcps_skb_frag.h
\ No newline at end of file diff --git a/kernel/include/net/nfcc_coex_region_nl.h b/kernel/include/net/nfcc_coex_region_nl.h new file mode 120000 index 0000000..deaad68 --- /dev/null +++ b/kernel/include/net/nfcc_coex_region_nl.h @@ -0,0 +1 @@ +../../../mac/include/net/nfcc_coex_region_nl.h
\ No newline at end of file diff --git a/kernel/include/net/pctt_region_nl.h b/kernel/include/net/pctt_region_nl.h new file mode 120000 index 0000000..55871be --- /dev/null +++ b/kernel/include/net/pctt_region_nl.h @@ -0,0 +1 @@ +../../../mac/include/net/pctt_region_nl.h
\ No newline at end of file diff --git a/kernel/include/net/pctt_region_params.h b/kernel/include/net/pctt_region_params.h new file mode 120000 index 0000000..2b147ff --- /dev/null +++ b/kernel/include/net/pctt_region_params.h @@ -0,0 +1 @@ +../../../mac/include/net/pctt_region_params.h
\ No newline at end of file diff --git a/kernel/include/net/vendor_cmd.h b/kernel/include/net/vendor_cmd.h new file mode 120000 index 0000000..ffc7086 --- /dev/null +++ b/kernel/include/net/vendor_cmd.h @@ -0,0 +1 @@ +../../../mac/include/net/vendor_cmd.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/Kbuild b/kernel/net/mcps802154/Kbuild new file mode 100644 index 0000000..1fd78c3 --- /dev/null +++ b/kernel/net/mcps802154/Kbuild @@ -0,0 +1,71 @@ +ifndef CONFIG_MCPS802154 +CONFIG_MCPS802154:=m +endif + +obj-$(CONFIG_MCPS802154) := mcps802154.o mcps802154_region_fira.o \ + mcps802154_region_nfcc_coex.o \ + mcps802154_region_pctt.o \ + +ccflags-$(CONFIG_MCPS802154_TESTMODE) += -DCONFIG_MCPS802154_TESTMODE + +mcps802154-y := \ + ca.o \ + default_region.o \ + endless_scheduler.o \ + on_demand_scheduler.o \ + idle_region.o \ + fproc.o \ + fproc_broken.o \ + fproc_multi.o \ + fproc_vendor.o \ + fproc_nothing.o \ + fproc_idle.o \ + fproc_rx.o \ + fproc_stopped.o \ + fproc_tx.o \ + frame.o \ + ie.o \ + mcps_main.o \ + mcps_skb_frag.o \ + nl.o \ + ops.o \ + regions.o \ + schedule.o \ + schedulers.o \ + trace.o + +mcps802154_region_fira-y := \ + fira_access.o \ + fira_crypto.o \ + fira_round_hopping_sequence.o \ + fira_round_hopping_crypto.o \ + fira_frame.o \ + fira_region.o \ + fira_region_call.o \ + fira_session.o \ + fira_session_fsm.o \ + fira_session_fsm_init.o \ + fira_session_fsm_idle.o \ + fira_session_fsm_active.o \ + fira_sts.o \ + fira_trace.o \ + mcps_crypto.o + +mcps802154_region_nfcc_coex-y := \ + nfcc_coex_access.o \ + nfcc_coex_region.o \ + nfcc_coex_region_call.o \ + nfcc_coex_session.o \ + nfcc_coex_trace.o + +mcps802154_region_pctt-y := \ + pctt_access.o \ + pctt_region.o \ + pctt_region_call.o \ + pctt_session.o \ + pctt_trace.o + +CFLAGS_trace.o := -I$(src) -I$(srctree)/$(src) +CFLAGS_fira_trace.o := -I$(src) -I$(srctree)/$(src) +CFLAGS_pctt_trace.o := -I$(src) -I$(srctree)/$(src) +CFLAGS_nfcc_coex_trace.o := -I$(src) -I$(srctree)/$(src) diff --git a/kernel/net/mcps802154/backport_nl.h b/kernel/net/mcps802154/backport_nl.h new file mode 120000 index 0000000..d4e6c20 --- /dev/null +++ b/kernel/net/mcps802154/backport_nl.h @@ -0,0 +1 @@ +../../../mac/backport_nl.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/ca.c b/kernel/net/mcps802154/ca.c new file mode 120000 index 0000000..7ba9df4 --- /dev/null +++ b/kernel/net/mcps802154/ca.c @@ -0,0 +1 @@ +../../../mac/ca.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/ca.h b/kernel/net/mcps802154/ca.h new file mode 120000 index 0000000..16119fb --- /dev/null +++ b/kernel/net/mcps802154/ca.h @@ -0,0 +1 @@ +../../../mac/ca.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/default_region.c b/kernel/net/mcps802154/default_region.c new file mode 120000 index 0000000..1ece23b --- /dev/null +++ b/kernel/net/mcps802154/default_region.c @@ -0,0 +1 @@ +../../../mac/default_region.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/default_region.h b/kernel/net/mcps802154/default_region.h new file mode 120000 index 0000000..899bd4e --- /dev/null +++ b/kernel/net/mcps802154/default_region.h @@ -0,0 +1 @@ +../../../mac/default_region.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/endless_scheduler.c b/kernel/net/mcps802154/endless_scheduler.c new file mode 120000 index 0000000..ffe271c --- /dev/null +++ b/kernel/net/mcps802154/endless_scheduler.c @@ -0,0 +1 @@ +../../../mac/endless_scheduler.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/endless_scheduler.h b/kernel/net/mcps802154/endless_scheduler.h new file mode 120000 index 0000000..222d943 --- /dev/null +++ b/kernel/net/mcps802154/endless_scheduler.h @@ -0,0 +1 @@ +../../../mac/endless_scheduler.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_access.c b/kernel/net/mcps802154/fira_access.c new file mode 120000 index 0000000..f10c44b --- /dev/null +++ b/kernel/net/mcps802154/fira_access.c @@ -0,0 +1 @@ +../../../mac/fira_access.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_access.h b/kernel/net/mcps802154/fira_access.h new file mode 120000 index 0000000..9516beb --- /dev/null +++ b/kernel/net/mcps802154/fira_access.h @@ -0,0 +1 @@ +../../../mac/fira_access.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_crypto.c b/kernel/net/mcps802154/fira_crypto.c new file mode 120000 index 0000000..ae20d55 --- /dev/null +++ b/kernel/net/mcps802154/fira_crypto.c @@ -0,0 +1 @@ +../../../mac/fira_crypto.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_crypto.h b/kernel/net/mcps802154/fira_crypto.h new file mode 120000 index 0000000..1a5ac8c --- /dev/null +++ b/kernel/net/mcps802154/fira_crypto.h @@ -0,0 +1 @@ +../../../mac/fira_crypto.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_frame.c b/kernel/net/mcps802154/fira_frame.c new file mode 120000 index 0000000..1faceea --- /dev/null +++ b/kernel/net/mcps802154/fira_frame.c @@ -0,0 +1 @@ +../../../mac/fira_frame.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_frame.h b/kernel/net/mcps802154/fira_frame.h new file mode 120000 index 0000000..50be740 --- /dev/null +++ b/kernel/net/mcps802154/fira_frame.h @@ -0,0 +1 @@ +../../../mac/fira_frame.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_region.c b/kernel/net/mcps802154/fira_region.c new file mode 120000 index 0000000..25b2744 --- /dev/null +++ b/kernel/net/mcps802154/fira_region.c @@ -0,0 +1 @@ +../../../mac/fira_region.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_region.h b/kernel/net/mcps802154/fira_region.h new file mode 120000 index 0000000..d1ae2a0 --- /dev/null +++ b/kernel/net/mcps802154/fira_region.h @@ -0,0 +1 @@ +../../../mac/fira_region.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_region_call.c b/kernel/net/mcps802154/fira_region_call.c new file mode 120000 index 0000000..016a88a --- /dev/null +++ b/kernel/net/mcps802154/fira_region_call.c @@ -0,0 +1 @@ +../../../mac/fira_region_call.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_region_call.h b/kernel/net/mcps802154/fira_region_call.h new file mode 120000 index 0000000..42dcdf6 --- /dev/null +++ b/kernel/net/mcps802154/fira_region_call.h @@ -0,0 +1 @@ +../../../mac/fira_region_call.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_round_hopping_crypto.c b/kernel/net/mcps802154/fira_round_hopping_crypto.c new file mode 100644 index 0000000..efb8259 --- /dev/null +++ b/kernel/net/mcps802154/fira_round_hopping_crypto.c @@ -0,0 +1,94 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include "fira_round_hopping_crypto_impl.h" + +#include <crypto/aes.h> +#include <linux/scatterlist.h> + +int fira_round_hopping_crypto_encrypt( + const struct fira_round_hopping_sequence *round_hopping_sequence, + const u8 *data, u8 *out) +{ + struct scatterlist sg; + SYNC_SKCIPHER_REQUEST_ON_STACK(req, round_hopping_sequence->tfm); + int r; + + sg_init_one(&sg, round_hopping_sequence->data, AES_BLOCK_SIZE); + memcpy(round_hopping_sequence->data, data, AES_BLOCK_SIZE); + + skcipher_request_set_sync_tfm(req, round_hopping_sequence->tfm); + skcipher_request_set_callback(req, 0, NULL, NULL); + skcipher_request_set_crypt(req, &sg, &sg, AES_BLOCK_SIZE, NULL); + + r = crypto_skcipher_encrypt(req); + skcipher_request_zero(req); + + memcpy(out, round_hopping_sequence->data, AES_BLOCK_SIZE); + + return r; +} + +int fira_round_hopping_crypto_init( + struct fira_round_hopping_sequence *round_hopping_sequence) +{ + struct crypto_sync_skcipher *tfm; + u8 *data; + int r; + + data = kmalloc(AES_BLOCK_SIZE, GFP_KERNEL); + if (!data) + return -ENOMEM; + + tfm = crypto_alloc_sync_skcipher("ecb(aes)", 0, 0); + if (IS_ERR(tfm)) { + r = PTR_ERR(tfm); + if (r == -ENOENT) { + pr_err("The crypto transform ecb(aes) seems to be missing." + " Please check your kernel configuration.\n"); + } + goto err_free_data; + } + + r = crypto_sync_skcipher_setkey(tfm, round_hopping_sequence->key, + AES_KEYSIZE_128); + if (r) + goto err_free_tfm; + + round_hopping_sequence->data = data; + round_hopping_sequence->tfm = tfm; + + return r; +err_free_tfm: + crypto_free_sync_skcipher(tfm); +err_free_data: + kfree(data); + return r; +} + +void fira_round_hopping_crypto_destroy( + struct fira_round_hopping_sequence *round_hopping_sequence) +{ + kfree(round_hopping_sequence->data); + crypto_free_sync_skcipher(round_hopping_sequence->tfm); +} diff --git a/kernel/net/mcps802154/fira_round_hopping_crypto.h b/kernel/net/mcps802154/fira_round_hopping_crypto.h new file mode 120000 index 0000000..6f89208 --- /dev/null +++ b/kernel/net/mcps802154/fira_round_hopping_crypto.h @@ -0,0 +1 @@ +../../../mac/fira_round_hopping_crypto.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_round_hopping_crypto_impl.h b/kernel/net/mcps802154/fira_round_hopping_crypto_impl.h new file mode 100644 index 0000000..d5d216a --- /dev/null +++ b/kernel/net/mcps802154/fira_round_hopping_crypto_impl.h @@ -0,0 +1,36 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef FIRA_ROUND_HOPPING_CRYPTO_IMPL_H +#define FIRA_ROUND_HOPPING_CRYPTO_IMPL_H + +#include "fira_round_hopping_crypto.h" +#include <crypto/skcipher.h> + +struct fira_round_hopping_sequence { + u8 key[AES_KEYSIZE_128]; + u8 *data; + struct crypto_sync_skcipher *tfm; +}; + +#endif /* FIRA_ROUND_HOPPING_CRYPTO_IMPL_H */ diff --git a/kernel/net/mcps802154/fira_round_hopping_sequence.c b/kernel/net/mcps802154/fira_round_hopping_sequence.c new file mode 120000 index 0000000..52cd607 --- /dev/null +++ b/kernel/net/mcps802154/fira_round_hopping_sequence.c @@ -0,0 +1 @@ +../../../mac/fira_round_hopping_sequence.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_round_hopping_sequence.h b/kernel/net/mcps802154/fira_round_hopping_sequence.h new file mode 120000 index 0000000..30b3441 --- /dev/null +++ b/kernel/net/mcps802154/fira_round_hopping_sequence.h @@ -0,0 +1 @@ +../../../mac/fira_round_hopping_sequence.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_session.c b/kernel/net/mcps802154/fira_session.c new file mode 120000 index 0000000..236e906 --- /dev/null +++ b/kernel/net/mcps802154/fira_session.c @@ -0,0 +1 @@ +../../../mac/fira_session.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_session.h b/kernel/net/mcps802154/fira_session.h new file mode 120000 index 0000000..6fc7a5c --- /dev/null +++ b/kernel/net/mcps802154/fira_session.h @@ -0,0 +1 @@ +../../../mac/fira_session.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_session_fsm.c b/kernel/net/mcps802154/fira_session_fsm.c new file mode 120000 index 0000000..b815ae3 --- /dev/null +++ b/kernel/net/mcps802154/fira_session_fsm.c @@ -0,0 +1 @@ +../../../mac/fira_session_fsm.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_session_fsm.h b/kernel/net/mcps802154/fira_session_fsm.h new file mode 120000 index 0000000..5506507 --- /dev/null +++ b/kernel/net/mcps802154/fira_session_fsm.h @@ -0,0 +1 @@ +../../../mac/fira_session_fsm.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_session_fsm_active.c b/kernel/net/mcps802154/fira_session_fsm_active.c new file mode 120000 index 0000000..63f915b --- /dev/null +++ b/kernel/net/mcps802154/fira_session_fsm_active.c @@ -0,0 +1 @@ +../../../mac/fira_session_fsm_active.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_session_fsm_active.h b/kernel/net/mcps802154/fira_session_fsm_active.h new file mode 120000 index 0000000..7654f0a --- /dev/null +++ b/kernel/net/mcps802154/fira_session_fsm_active.h @@ -0,0 +1 @@ +../../../mac/fira_session_fsm_active.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_session_fsm_idle.c b/kernel/net/mcps802154/fira_session_fsm_idle.c new file mode 120000 index 0000000..4725342 --- /dev/null +++ b/kernel/net/mcps802154/fira_session_fsm_idle.c @@ -0,0 +1 @@ +../../../mac/fira_session_fsm_idle.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_session_fsm_idle.h b/kernel/net/mcps802154/fira_session_fsm_idle.h new file mode 120000 index 0000000..5182a72 --- /dev/null +++ b/kernel/net/mcps802154/fira_session_fsm_idle.h @@ -0,0 +1 @@ +../../../mac/fira_session_fsm_idle.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_session_fsm_init.c b/kernel/net/mcps802154/fira_session_fsm_init.c new file mode 120000 index 0000000..41149be --- /dev/null +++ b/kernel/net/mcps802154/fira_session_fsm_init.c @@ -0,0 +1 @@ +../../../mac/fira_session_fsm_init.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_session_fsm_init.h b/kernel/net/mcps802154/fira_session_fsm_init.h new file mode 120000 index 0000000..f520adb --- /dev/null +++ b/kernel/net/mcps802154/fira_session_fsm_init.h @@ -0,0 +1 @@ +../../../mac/fira_session_fsm_init.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_sts.c b/kernel/net/mcps802154/fira_sts.c new file mode 120000 index 0000000..84ac15f --- /dev/null +++ b/kernel/net/mcps802154/fira_sts.c @@ -0,0 +1 @@ +../../../mac/fira_sts.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_sts.h b/kernel/net/mcps802154/fira_sts.h new file mode 120000 index 0000000..e93e356 --- /dev/null +++ b/kernel/net/mcps802154/fira_sts.h @@ -0,0 +1 @@ +../../../mac/fira_sts.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/fira_trace.c b/kernel/net/mcps802154/fira_trace.c new file mode 100644 index 0000000..148df18 --- /dev/null +++ b/kernel/net/mcps802154/fira_trace.c @@ -0,0 +1,25 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#define CREATE_TRACE_POINTS +#include "fira_trace.h" diff --git a/kernel/net/mcps802154/fira_trace.h b/kernel/net/mcps802154/fira_trace.h new file mode 120000 index 0000000..7dd5a65 --- /dev/null +++ b/kernel/net/mcps802154/fira_trace.h @@ -0,0 +1 @@ +../../../mac/fira_trace.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/fproc.c b/kernel/net/mcps802154/fproc.c new file mode 120000 index 0000000..808f290 --- /dev/null +++ b/kernel/net/mcps802154/fproc.c @@ -0,0 +1 @@ +../../../mac/fproc.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fproc.h b/kernel/net/mcps802154/fproc.h new file mode 120000 index 0000000..e10c2da --- /dev/null +++ b/kernel/net/mcps802154/fproc.h @@ -0,0 +1 @@ +../../../mac/fproc.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/fproc_broken.c b/kernel/net/mcps802154/fproc_broken.c new file mode 120000 index 0000000..1938d26 --- /dev/null +++ b/kernel/net/mcps802154/fproc_broken.c @@ -0,0 +1 @@ +../../../mac/fproc_broken.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fproc_idle.c b/kernel/net/mcps802154/fproc_idle.c new file mode 120000 index 0000000..430ae47 --- /dev/null +++ b/kernel/net/mcps802154/fproc_idle.c @@ -0,0 +1 @@ +../../../mac/fproc_idle.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fproc_multi.c b/kernel/net/mcps802154/fproc_multi.c new file mode 120000 index 0000000..67ef311 --- /dev/null +++ b/kernel/net/mcps802154/fproc_multi.c @@ -0,0 +1 @@ +../../../mac/fproc_multi.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fproc_nothing.c b/kernel/net/mcps802154/fproc_nothing.c new file mode 120000 index 0000000..b090714 --- /dev/null +++ b/kernel/net/mcps802154/fproc_nothing.c @@ -0,0 +1 @@ +../../../mac/fproc_nothing.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fproc_rx.c b/kernel/net/mcps802154/fproc_rx.c new file mode 120000 index 0000000..21d4610 --- /dev/null +++ b/kernel/net/mcps802154/fproc_rx.c @@ -0,0 +1 @@ +../../../mac/fproc_rx.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fproc_stopped.c b/kernel/net/mcps802154/fproc_stopped.c new file mode 120000 index 0000000..a2f151f --- /dev/null +++ b/kernel/net/mcps802154/fproc_stopped.c @@ -0,0 +1 @@ +../../../mac/fproc_stopped.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fproc_tx.c b/kernel/net/mcps802154/fproc_tx.c new file mode 120000 index 0000000..a40701d --- /dev/null +++ b/kernel/net/mcps802154/fproc_tx.c @@ -0,0 +1 @@ +../../../mac/fproc_tx.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/fproc_vendor.c b/kernel/net/mcps802154/fproc_vendor.c new file mode 120000 index 0000000..ff613ed --- /dev/null +++ b/kernel/net/mcps802154/fproc_vendor.c @@ -0,0 +1 @@ +../../../mac/fproc_vendor.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/frame.c b/kernel/net/mcps802154/frame.c new file mode 120000 index 0000000..c37a9e9 --- /dev/null +++ b/kernel/net/mcps802154/frame.c @@ -0,0 +1 @@ +../../../mac/frame.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/idle_region.c b/kernel/net/mcps802154/idle_region.c new file mode 120000 index 0000000..6e6ca32 --- /dev/null +++ b/kernel/net/mcps802154/idle_region.c @@ -0,0 +1 @@ +../../../mac/idle_region.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/idle_region.h b/kernel/net/mcps802154/idle_region.h new file mode 120000 index 0000000..fe7992b --- /dev/null +++ b/kernel/net/mcps802154/idle_region.h @@ -0,0 +1 @@ +../../../mac/idle_region.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/ie.c b/kernel/net/mcps802154/ie.c new file mode 120000 index 0000000..53183af --- /dev/null +++ b/kernel/net/mcps802154/ie.c @@ -0,0 +1 @@ +../../../mac/ie.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/llhw-ops.h b/kernel/net/mcps802154/llhw-ops.h new file mode 120000 index 0000000..1b6195d --- /dev/null +++ b/kernel/net/mcps802154/llhw-ops.h @@ -0,0 +1 @@ +../../../mac/llhw-ops.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/mcps802154_fproc.h b/kernel/net/mcps802154/mcps802154_fproc.h new file mode 120000 index 0000000..add11c0 --- /dev/null +++ b/kernel/net/mcps802154/mcps802154_fproc.h @@ -0,0 +1 @@ +../../../mac/mcps802154_fproc.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/mcps802154_i.h b/kernel/net/mcps802154/mcps802154_i.h new file mode 120000 index 0000000..3cd3c1e --- /dev/null +++ b/kernel/net/mcps802154/mcps802154_i.h @@ -0,0 +1 @@ +../../../mac/mcps802154_i.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/mcps802154_qorvo.h b/kernel/net/mcps802154/mcps802154_qorvo.h new file mode 120000 index 0000000..48ea31d --- /dev/null +++ b/kernel/net/mcps802154/mcps802154_qorvo.h @@ -0,0 +1 @@ +../../../mac/mcps802154_qorvo.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/mcps_crypto.c b/kernel/net/mcps802154/mcps_crypto.c new file mode 100644 index 0000000..faef7a4 --- /dev/null +++ b/kernel/net/mcps802154/mcps_crypto.c @@ -0,0 +1,321 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/crypto.h> +#include <linux/scatterlist.h> +#include <crypto/hash.h> +#include <crypto/skcipher.h> +#include <crypto/aead.h> +#include <crypto/aes.h> + +#include "mcps_crypto.h" + +#if !(defined(CONFIG_CRYPTO_HASH2) && defined(CONFIG_CRYPTO_AEAD2)) +#error "required CONFIG_CRYPTO_HASH2 && CONFIG_CRYPTO_AEAD2" +#endif + +#define FIRA_CRYPTO_AEAD_AUTHSIZE 8 + + +struct mcps_aes_ccm_star_128_ctx { + struct crypto_aead *tfm; +}; + +struct mcps_aes_ecb_128_ctx { + struct crypto_skcipher *tfm; + bool decrypt; +}; + + +int mcps_crypto_cmac_aes_128_digest(const uint8_t *key, const uint8_t *data, + unsigned int data_len, uint8_t *out) +{ + struct crypto_shash *tfm; + int r; + + tfm = crypto_alloc_shash("cmac(aes)", 0, 0); + if (IS_ERR(tfm)) { + if (PTR_ERR(tfm) == -ENOENT) + pr_err("The crypto transform cmac(aes) seems to be missing." + " Please check your kernel configuration.\n"); + return PTR_ERR(tfm); + } + + r = crypto_shash_setkey(tfm, key, AES_KEYSIZE_128); + if (r != 0) + goto out; + + do { + /* tfm need to be allocated for kernel < 4.20, so don't remove + * this do..while block + */ + SHASH_DESC_ON_STACK(desc, tfm); + + desc->tfm = tfm; + + r = crypto_shash_init(desc); + if (r != 0) + goto out; + + r = crypto_shash_finup(desc, data, data_len, out); + } while (0); + +out: + crypto_free_shash(tfm); + + return r; +} + +struct mcps_aes_ccm_star_128_ctx *mcps_crypto_aead_aes_ccm_star_128_create(void) +{ + struct mcps_aes_ccm_star_128_ctx *ctx; + int r; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + goto error; + + ctx->tfm = crypto_alloc_aead("ccm(aes)", 0, 0); + if (IS_ERR(ctx->tfm)) { + if (PTR_ERR(ctx->tfm) == -ENOENT) + pr_err("The crypto transform ccm(aes) seems to be missing." + " Please check your kernel configuration.\n"); + goto error; + } + + r = crypto_aead_setauthsize(ctx->tfm, FIRA_CRYPTO_AEAD_AUTHSIZE); + if (r != 0) + goto error; + + return ctx; + +error: + mcps_crypto_aead_aes_ccm_star_128_destroy(ctx); + + return NULL; +} + +int mcps_crypto_aead_aes_ccm_star_128_set(struct mcps_aes_ccm_star_128_ctx *ctx, + const uint8_t *key) +{ + if (!ctx || !key) + return -EINVAL; + + return crypto_aead_setkey(ctx->tfm, key, AES_KEYSIZE_128); +} + +void mcps_crypto_aead_aes_ccm_star_128_destroy(struct mcps_aes_ccm_star_128_ctx *ctx) +{ + if (!ctx) + return; + + crypto_free_aead(ctx->tfm); + kfree(ctx); +} + +int mcps_crypto_aead_aes_ccm_star_128_encrypt( + struct mcps_aes_ccm_star_128_ctx *ctx, const uint8_t *nonce, + const uint8_t *header, unsigned int header_len, + uint8_t *data, unsigned int data_len, + uint8_t *mac, unsigned int mac_len) +{ + struct aead_request *req = NULL; + struct scatterlist sg[3]; + u8 iv[AES_BLOCK_SIZE]; + DECLARE_CRYPTO_WAIT(wait); + int r = -1; + + if (!ctx || !nonce || !header || header_len <= 0 || !data || + data_len <= 0 || !mac || + mac_len != FIRA_CRYPTO_AEAD_AUTHSIZE) { + return -EINVAL; + } + + req = aead_request_alloc(ctx->tfm, GFP_KERNEL); + if (!req) { + r = -ENOMEM; + goto end; + } + + sg_init_table(sg, ARRAY_SIZE(sg)); + sg_set_buf(&sg[0], header, header_len); + sg_set_buf(&sg[1], data, data_len); + sg_set_buf(&sg[2], mac, mac_len); + + iv[0] = sizeof(u16) - 1; + memcpy(iv + 1, nonce, MCPS_CRYPTO_AES_CCM_STAR_NONCE_LEN); + + aead_request_set_callback(req, + CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP, + crypto_req_done, &wait); + aead_request_set_ad(req, header_len); + aead_request_set_crypt(req, sg, sg, data_len, iv); + + r = crypto_wait_req(crypto_aead_encrypt(req), &wait); + +end: + aead_request_free(req); + + return r; +} + +int mcps_crypto_aead_aes_ccm_star_128_decrypt( + struct mcps_aes_ccm_star_128_ctx *ctx, const uint8_t *nonce, + const uint8_t *header, unsigned int header_len, + uint8_t *data, unsigned int data_len, + uint8_t *mac, unsigned int mac_len) +{ + struct aead_request *req = NULL; + struct scatterlist sg[3]; + u8 iv[AES_BLOCK_SIZE]; + DECLARE_CRYPTO_WAIT(wait); + int r = -1; + + if (!ctx || !nonce || !header || header_len <= 0 || !data || + data_len <= 0 || !mac || + mac_len != FIRA_CRYPTO_AEAD_AUTHSIZE) { + return -EINVAL; + } + + req = aead_request_alloc(ctx->tfm, GFP_KERNEL); + if (!req) { + r = -ENOMEM; + goto end; + } + + iv[0] = sizeof(u16) - 1; + memcpy(iv + 1, nonce, MCPS_CRYPTO_AES_CCM_STAR_NONCE_LEN); + + sg_init_table(sg, ARRAY_SIZE(sg)); + sg_set_buf(&sg[0], header, header_len); + sg_set_buf(&sg[1], data, data_len); + sg_set_buf(&sg[2], mac, mac_len); + + aead_request_set_callback(req, + CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP, + crypto_req_done, &wait); + aead_request_set_ad(req, header_len); + aead_request_set_crypt(req, sg, sg, data_len + mac_len, iv); + + r = crypto_wait_req(crypto_aead_decrypt(req), &wait); + +end: + aead_request_free(req); + + return r; +} + +struct mcps_aes_ecb_128_ctx *mcps_crypto_aes_ecb_128_create(void) +{ + struct mcps_aes_ecb_128_ctx *ctx; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + goto error; + + ctx->tfm = crypto_alloc_skcipher("ecb(aes)", 0, 0); + if (IS_ERR(ctx->tfm)) { + if (PTR_ERR(ctx->tfm) == -ENOENT) + pr_err("The crypto transform ecb(aes) seems to be missing." + " Please check your kernel configuration.\n"); + goto error; + } + + return ctx; + +error: + mcps_crypto_aes_ecb_128_destroy(ctx); + + return NULL; +} + +int mcps_crypto_aes_ecb_128_set_encrypt(struct mcps_aes_ecb_128_ctx *ctx, + const uint8_t *key) +{ + if (!ctx || !key) + return -EINVAL; + + ctx->decrypt = false; + + return crypto_skcipher_setkey(ctx->tfm, key, AES_KEYSIZE_128); +} + +int mcps_crypto_aes_ecb_128_set_decrypt(struct mcps_aes_ecb_128_ctx *ctx, + const uint8_t *key) +{ + if (!ctx || !key) + return -EINVAL; + + ctx->decrypt = true; + + return crypto_skcipher_setkey(ctx->tfm, key, AES_KEYSIZE_128); +} + +void mcps_crypto_aes_ecb_128_destroy(struct mcps_aes_ecb_128_ctx *ctx) +{ + if (!ctx) + return; + + crypto_free_skcipher(ctx->tfm); + kfree(ctx); +} + +int mcps_crypto_aes_ecb_128_encrypt(struct mcps_aes_ecb_128_ctx *ctx, + const uint8_t *data, unsigned int data_len, uint8_t *out) +{ + struct skcipher_request *req = NULL; + struct scatterlist sgin, sgout; + DECLARE_CRYPTO_WAIT(wait); + int r = -1; + + if (!ctx || !data || data_len <= 0 || !out) + return -EINVAL; + + /* round to full cipher block */ + data_len = ((data_len - 1) & -AES_KEYSIZE_128) + AES_KEYSIZE_128; + + req = skcipher_request_alloc(ctx->tfm, GFP_KERNEL); + if (!req) { + r = -ENOMEM; + goto end; + } + + sg_init_one(&sgin, data, data_len); + sg_init_one(&sgout, out, data_len); + skcipher_request_set_callback(req, + CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP, + crypto_req_done, &wait); + skcipher_request_set_crypt(req, &sgin, &sgout, data_len, NULL); + + if (ctx->decrypt) + r = crypto_skcipher_decrypt(req); + else + r = crypto_skcipher_encrypt(req); + r = crypto_wait_req(r, &wait); + +end: + skcipher_request_free(req); + + return r; +} + diff --git a/kernel/net/mcps802154/mcps_crypto.h b/kernel/net/mcps802154/mcps_crypto.h new file mode 100644 index 0000000..5a1a5c5 --- /dev/null +++ b/kernel/net/mcps802154/mcps_crypto.h @@ -0,0 +1,197 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef MCPS_CRYPTO_H +#define MCPS_CRYPTO_H + +#ifdef __KERNEL__ +#include <linux/types.h> +#else +#include <stdint.h> +#endif + +#define MCPS_CRYPTO_AES_CCM_STAR_NONCE_LEN 13 + +/** + * struct mcps_aes_ccm_star_128_ctx - Context containing AES-128-CCM* related + * information. + * + * This is an opaque structure left to the implementation. + */ +struct mcps_aes_ccm_star_128_ctx; + +/** + * struct mcps_aes_ecb_128_ctx - Context containing AES-128-ECB related + * information. + * + * This is an opaque structure left to the implementation. + */ +struct mcps_aes_ecb_128_ctx; + + +/** + * mcps_crypto_cmac_aes_128_digest() - Compute a cmac AES 128. + * @key: AES key. + * @data: Input data. + * @data_len: Input data length in bytes. + * @out: Output hash, with length AES_BLOCK_SIZE. + * + * NOTE: This API should be implemented by platform. + * + * Return: 0 or error. + */ +int mcps_crypto_cmac_aes_128_digest(const uint8_t *key, const uint8_t *data, + unsigned int data_len, uint8_t *out); + +/** + * mcps_crypto_aead_aes_ccm_star_128_create() - Create a context using + * Authenticated Encryption Associated Data with AES CCM* 128. + * + * NOTE: This API should be implemented by platform. + * + * Return: The pointer to the context that will be used to encrypt & decrypt. + */ +struct mcps_aes_ccm_star_128_ctx *mcps_crypto_aead_aes_ccm_star_128_create(void); + +/** + * mcps_crypto_aead_aes_ccm_star_128_set() - Set a context using + * Authenticated Encryption Associated Data with AES CCM* 128. + * @ctx: Context. + * @key: AES key. + * + * NOTE: This API should be implemented by platform. + * + * Return: 0 or error. + */ +int mcps_crypto_aead_aes_ccm_star_128_set(struct mcps_aes_ccm_star_128_ctx *ctx, const uint8_t *key); + +/** + * mcps_crypto_aead_aes_ccm_star_128_destroy() - Destroy the Authenticated + * Encryption Associated Data with AES CCM* 128 context. + * @ctx: Context. + * + * NOTE: This API should be implemented by platform. + */ +void mcps_crypto_aead_aes_ccm_star_128_destroy(struct mcps_aes_ccm_star_128_ctx *ctx); + +/** + * mcps_crypto_aead_aes_ccm_star_128_encrypt() - Encrypt using Authenticated + * Encryption Associated Data with AES CCM* 128. + * @ctx: Context. + * @nonce: Nonce, with length MCPS_CRYPTO_AES_CCM_STAR_NONCE_LEN. + * @header: Header data. + * @header_len: Header length in bytes. + * @data: Data to encrypt, will be replaced with encrypted data. + * @data_len: Data length in bytes. + * @mac: AES CCM* MAC. + * @mac_len: AES CCM* MAC size in bytes. + * + * NOTE: This API should be implemented by platform. + * + * Return: 0 or error. + */ +int mcps_crypto_aead_aes_ccm_star_128_encrypt( + struct mcps_aes_ccm_star_128_ctx *ctx, const uint8_t *nonce, + const uint8_t *header, unsigned int header_len, + uint8_t *data, unsigned int data_len, + uint8_t *mac, unsigned int mac_len); + +/** + * mcps_crypto_aead_aes_ccm_star_128_decrypt() - Decrypt using Authenticated + * Encryption Associated Data with AES CCM* 128. + * @ctx: Context. + * @nonce: Nonce, with length MCPS_CRYPTO_AES_CCM_STAR_NONCE_LEN. + * @header: Header data. + * @header_len: Header length in bytes. + * @data: Data to decrypt, will be replaced with decrypted data. + * @data_len: Data length in bytes. + * @mac: AES CCM* MAC. + * @mac_len: AES CCM* MAC size in bytes. + * + * NOTE: This API should be implemented by platform. In case of mismatch + * between the MAC and calculated MAC, this function should return -EBADMSG. + * + * Return: 0 or error. + */ +int mcps_crypto_aead_aes_ccm_star_128_decrypt( + struct mcps_aes_ccm_star_128_ctx *ctx, const uint8_t *nonce, + const uint8_t *header, unsigned int header_len, + uint8_t *data, unsigned int data_len, + uint8_t *mac, unsigned int mac_len); + +/** + * mcps_crypto_aes_ecb_128_create() - Create a context using AES ECB 128. + * + * NOTE: This API should be implemented by platform. + * + * Return: The pointer to the context that will be used to encrypt & decrypt. + */ +struct mcps_aes_ecb_128_ctx *mcps_crypto_aes_ecb_128_create(void); + +/** + * mcps_crypto_aes_ecb_128_set_encrypt() - Set a context using + * Authenticated Encryption Associated Data with AES ECB* 128. + * @ctx: Context. + * @key: AES key. + * + * NOTE: This API should be implemented by platform. + * + * Return: 0 or error. + */ +int mcps_crypto_aes_ecb_128_set_encrypt(struct mcps_aes_ecb_128_ctx *ctx, const uint8_t *key); + +/** + * mcps_crypto_aes_ecb_128_set_decrypt() - Set a context using + * Authenticated Encryption Associated Data with AES ECB* 128. + * @ctx: Context. + * @key: AES key. + * + * NOTE: This API should be implemented by platform. + * + * Return: 0 or error. + */ +int mcps_crypto_aes_ecb_128_set_decrypt(struct mcps_aes_ecb_128_ctx *ctx, const uint8_t *key); + +/** + * mcps_crypto_aes_ecb_128_destroy() - Destroy the AES ECB 128 context. + * @ctx: Context. + * + * NOTE: This API should be implemented by platform. + */ +void mcps_crypto_aes_ecb_128_destroy(struct mcps_aes_ecb_128_ctx *ctx); + +/** + * mcps_crypto_aes_ecb_128_encrypt() - Encrypt using AES ECB 128. + * @ctx: Context. + * @data: Data to encrypt. + * @data_len: Data length in bytes, should be a multiple of AES_BLOCK_SIZE. + * @out: Ciphered data with same length as data. + * + * NOTE: This API should be implemented by platform. + * + * Return: 0 or error. + */ +int mcps_crypto_aes_ecb_128_encrypt(struct mcps_aes_ecb_128_ctx *ctx, + const uint8_t *data, unsigned int data_len, uint8_t *out); + +#endif /* MCPS_CRYPTO_H */ diff --git a/kernel/net/mcps802154/mcps_main.c b/kernel/net/mcps802154/mcps_main.c new file mode 120000 index 0000000..25880ca --- /dev/null +++ b/kernel/net/mcps802154/mcps_main.c @@ -0,0 +1 @@ +../../../mac/mcps_main.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/mcps_skb_frag.c b/kernel/net/mcps802154/mcps_skb_frag.c new file mode 120000 index 0000000..cfdbfe1 --- /dev/null +++ b/kernel/net/mcps802154/mcps_skb_frag.c @@ -0,0 +1 @@ +../../../mac/mcps_skb_frag.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/nfcc_coex_access.c b/kernel/net/mcps802154/nfcc_coex_access.c new file mode 120000 index 0000000..bb60a76 --- /dev/null +++ b/kernel/net/mcps802154/nfcc_coex_access.c @@ -0,0 +1 @@ +../../../mac/nfcc_coex_access.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/nfcc_coex_access.h b/kernel/net/mcps802154/nfcc_coex_access.h new file mode 120000 index 0000000..94e058d --- /dev/null +++ b/kernel/net/mcps802154/nfcc_coex_access.h @@ -0,0 +1 @@ +../../../mac/nfcc_coex_access.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/nfcc_coex_region.c b/kernel/net/mcps802154/nfcc_coex_region.c new file mode 120000 index 0000000..6746827 --- /dev/null +++ b/kernel/net/mcps802154/nfcc_coex_region.c @@ -0,0 +1 @@ +../../../mac/nfcc_coex_region.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/nfcc_coex_region.h b/kernel/net/mcps802154/nfcc_coex_region.h new file mode 120000 index 0000000..f6fec7b --- /dev/null +++ b/kernel/net/mcps802154/nfcc_coex_region.h @@ -0,0 +1 @@ +../../../mac/nfcc_coex_region.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/nfcc_coex_region_call.c b/kernel/net/mcps802154/nfcc_coex_region_call.c new file mode 120000 index 0000000..57ba9fe --- /dev/null +++ b/kernel/net/mcps802154/nfcc_coex_region_call.c @@ -0,0 +1 @@ +../../../mac/nfcc_coex_region_call.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/nfcc_coex_region_call.h b/kernel/net/mcps802154/nfcc_coex_region_call.h new file mode 120000 index 0000000..721c4e9 --- /dev/null +++ b/kernel/net/mcps802154/nfcc_coex_region_call.h @@ -0,0 +1 @@ +../../../mac/nfcc_coex_region_call.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/nfcc_coex_session.c b/kernel/net/mcps802154/nfcc_coex_session.c new file mode 120000 index 0000000..0326872 --- /dev/null +++ b/kernel/net/mcps802154/nfcc_coex_session.c @@ -0,0 +1 @@ +../../../mac/nfcc_coex_session.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/nfcc_coex_session.h b/kernel/net/mcps802154/nfcc_coex_session.h new file mode 120000 index 0000000..c326154 --- /dev/null +++ b/kernel/net/mcps802154/nfcc_coex_session.h @@ -0,0 +1 @@ +../../../mac/nfcc_coex_session.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/nfcc_coex_trace.c b/kernel/net/mcps802154/nfcc_coex_trace.c new file mode 100644 index 0000000..fad16db --- /dev/null +++ b/kernel/net/mcps802154/nfcc_coex_trace.c @@ -0,0 +1,25 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#define CREATE_TRACE_POINTS +#include "nfcc_coex_trace.h" diff --git a/kernel/net/mcps802154/nfcc_coex_trace.h b/kernel/net/mcps802154/nfcc_coex_trace.h new file mode 120000 index 0000000..9561220 --- /dev/null +++ b/kernel/net/mcps802154/nfcc_coex_trace.h @@ -0,0 +1 @@ +../../../mac/nfcc_coex_trace.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/nl.c b/kernel/net/mcps802154/nl.c new file mode 100644 index 0000000..80e520b --- /dev/null +++ b/kernel/net/mcps802154/nl.c @@ -0,0 +1,1244 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/rtnetlink.h> +#include <net/genetlink.h> +#include <linux/version.h> +#include <net/mcps802154_nl.h> + +#include "mcps802154_i.h" +#include "llhw-ops.h" +#include "nl.h" + +#define ATTR_STRING_SIZE 20 +#define ATTR_STRING_POLICY \ + { \ + .type = NLA_NUL_STRING, .len = ATTR_STRING_SIZE - 1 \ + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 11, 0) +#define nla_strscpy nla_strlcpy +#endif + +static struct genl_family mcps802154_nl_family; + +static const struct nla_policy + mcps802154_nl_calibration_policy[MCPS802154_CALIBRATIONS_ATTR_MAX + 1] = { + [MCPS802154_CALIBRATIONS_ATTR_KEY] = { .type = NLA_NUL_STRING, + .len = 64 }, + [MCPS802154_CALIBRATIONS_ATTR_VALUE] = { .type = NLA_BINARY }, + [MCPS802154_CALIBRATIONS_ATTR_STATUS] = { .type = NLA_S32 }, + }; + +static const struct nla_policy + mcps802154_nl_region_policy[MCPS802154_REGION_MAX + 1] = { + [MCPS802154_REGION_ATTR_ID] = { .type = NLA_U32 }, + [MCPS802154_REGION_ATTR_NAME] = ATTR_STRING_POLICY, + [MCPS802154_REGION_ATTR_PARAMS] = { .type = NLA_NESTED }, + [MCPS802154_REGION_ATTR_CALL] = { .type = NLA_U32 }, + [MCPS802154_REGION_ATTR_CALL_PARAMS] = { .type = NLA_NESTED }, + }; + +static const struct nla_policy mcps802154_nl_policy[MCPS802154_ATTR_MAX + 1] = { + [MCPS802154_ATTR_HW] = { .type = NLA_U32 }, + [MCPS802154_ATTR_WPAN_PHY_NAME] = ATTR_STRING_POLICY, + [MCPS802154_ATTR_SCHEDULER_NAME] = ATTR_STRING_POLICY, + [MCPS802154_ATTR_SCHEDULER_PARAMS] = { .type = NLA_NESTED }, + [MCPS802154_ATTR_SCHEDULER_REGIONS] = + NLA_POLICY_NESTED_ARRAY(mcps802154_nl_region_policy), + [MCPS802154_ATTR_SCHEDULER_CALL] = { .type = NLA_U32 }, + [MCPS802154_ATTR_SCHEDULER_CALL_PARAMS] = { .type = NLA_NESTED }, + [MCPS802154_ATTR_SCHEDULER_REGION_CALL] = { .type = NLA_NESTED }, + [MCPS802154_ATTR_CALIBRATIONS] = { .type = NLA_NESTED }, + [MCPS802154_ATTR_PWR_STATS] = { .type = NLA_NESTED }, + +#ifdef CONFIG_MCPS802154_TESTMODE + [MCPS802154_ATTR_TESTDATA] = { .type = NLA_NESTED }, +#endif +}; + +/** + * mcps802154_nl_send_hw() - Append device information to a netlink message. + * @local: MCPS private data. + * @msg: Message to write to. + * @portid: Destination port address. + * @seq: Message sequence number. + * @flags: Message flags (0 or NLM_F_MULTI). + * + * Return: 0 or error. + */ +static int mcps802154_nl_send_hw(struct mcps802154_local *local, + struct sk_buff *msg, u32 portid, u32 seq, + int flags) +{ + void *hdr; + + hdr = genlmsg_put(msg, portid, seq, &mcps802154_nl_family, flags, + MCPS802154_CMD_NEW_HW); + if (!hdr) + return -ENOBUFS; + + if (nla_put_u32(msg, MCPS802154_ATTR_HW, local->hw_idx) || + nla_put_string(msg, MCPS802154_ATTR_WPAN_PHY_NAME, + wpan_phy_name(local->hw->phy))) + goto error; + + if (local->ca.scheduler && + nla_put_string(msg, MCPS802154_ATTR_SCHEDULER_NAME, + local->ca.scheduler->ops->name)) + goto error; + + genlmsg_end(msg, hdr); + return 0; +error: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + +/** + * mcps802154_nl_get_hw() - Request information about a device. + * @skb: Request message. + * @info: Request information. + * + * Return: 0 or error. + */ +static int mcps802154_nl_get_hw(struct sk_buff *skb, struct genl_info *info) +{ + struct sk_buff *msg; + struct mcps802154_local *local = info->user_ptr[0]; + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + if (mcps802154_nl_send_hw(local, msg, info->snd_portid, info->snd_seq, + 0)) { + nlmsg_free(msg); + return -ENOBUFS; + } + + return genlmsg_reply(msg, info); +} + +/** + * mcps802154_nl_dump_hw() - Dump information on all devices. + * @skb: Allocated message for response. + * @cb: Netlink callbacks. + * + * Return: Size of response message, or error. + */ +static int mcps802154_nl_dump_hw(struct sk_buff *skb, + struct netlink_callback *cb) +{ + int start_idx = cb->args[0]; + int r = 0; + struct mcps802154_local *local; + + rtnl_lock(); + local = mcps802154_get_first_by_idx(start_idx); + if (local) { + cb->args[0] = local->hw_idx + 1; + r = mcps802154_nl_send_hw(local, skb, + NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, NLM_F_MULTI); + } + rtnl_unlock(); + + return r ? r : skb->len; +} + +/** + * mcps802154_nl_set_regions() - Set the regions which populate the schedule. + * @local: MCPS private data. + * @scheduler_name: Name of the scheduler. + * @regions_attr: Nested attribute containing regions parameters. + * @extack: Extended ACK report structure. + * @update: True if we need only to update region parameters. + * + * Return: 0 or error. + */ +static int mcps802154_nl_set_regions(struct mcps802154_local *local, + const char *scheduler_name, + const struct nlattr *regions_attr, + struct netlink_ext_ack *extack, + bool update) +{ + struct nlattr *request; + struct nlattr *attrs[MCPS802154_REGION_MAX + 1]; + int r, rem; + u32 region_id = 0; + char region_name[ATTR_STRING_SIZE]; + + if (!regions_attr) + return -EINVAL; + + nla_for_each_nested (request, regions_attr, rem) { + r = nla_parse_nested(attrs, MCPS802154_REGION_MAX, request, + mcps802154_nl_region_policy, extack); + if (r) + return r; + + if (!attrs[MCPS802154_REGION_ATTR_NAME]) + return -EINVAL; + + if (attrs[MCPS802154_REGION_ATTR_ID]) + region_id = + nla_get_s32(attrs[MCPS802154_REGION_ATTR_ID]); + nla_strscpy(region_name, attrs[MCPS802154_REGION_ATTR_NAME], + sizeof(region_name)); + mutex_lock(&local->fsm_lock); + if (update) + r = mcps802154_ca_set_region_parameters( + local, scheduler_name, region_id, region_name, + attrs[MCPS802154_REGION_ATTR_PARAMS], extack); + else + r = mcps802154_ca_set_region( + local, scheduler_name, region_id, region_name, + attrs[MCPS802154_REGION_ATTR_PARAMS], extack); + mutex_unlock(&local->fsm_lock); + if (r) + return r; + } + + return 0; +} + +/** + * mcps802154_nl_generic_set_params() - Set scheduler parameters and/or regions. + * @skb: Request message. + * @info: Request information. + * + * Return: 0 or error. + */ +static int mcps802154_nl_generic_set_params(struct sk_buff *skb, + struct genl_info *info) +{ + struct mcps802154_local *local = info->user_ptr[0]; + int r; + struct nlattr *params_attr = + info->attrs[MCPS802154_ATTR_SCHEDULER_PARAMS]; + struct nlattr *regions_attr = + info->attrs[MCPS802154_ATTR_SCHEDULER_REGIONS]; + struct nlattr *name_attr = info->attrs[MCPS802154_ATTR_SCHEDULER_NAME]; + char name[ATTR_STRING_SIZE]; + enum mcps802154_commands cmd = info->genlhdr->cmd; + + if (!name_attr || + (cmd == MCPS802154_CMD_SET_SCHEDULER_PARAMS && !params_attr) || + ((cmd == MCPS802154_CMD_SET_SCHEDULER_REGIONS || + cmd == MCPS802154_CMD_SET_REGIONS_PARAMS) && + (!regions_attr || params_attr))) + return -EINVAL; + + nla_strscpy(name, name_attr, sizeof(name)); + + if (params_attr) { + mutex_lock(&local->fsm_lock); + r = mcps802154_ca_scheduler_set_parameters( + local, name, params_attr, info->extack); + mutex_unlock(&local->fsm_lock); + + if (r) + return r; + } + + if (regions_attr) + r = mcps802154_nl_set_regions( + local, name, regions_attr, info->extack, + cmd == MCPS802154_CMD_SET_REGIONS_PARAMS); + + return r; +} + +/** + * mcps802154_nl_set_scheduler() - Set the scheduler which manage the schedule. + * @skb: Request message. + * @info: Request information. + * + * Return: 0 or error. + */ +static int mcps802154_nl_set_scheduler(struct sk_buff *skb, + struct genl_info *info) +{ + struct mcps802154_local *local = info->user_ptr[0]; + struct nlattr *params_attr = + info->attrs[MCPS802154_ATTR_SCHEDULER_PARAMS]; + struct nlattr *regions_attr = + info->attrs[MCPS802154_ATTR_SCHEDULER_REGIONS]; + char name[ATTR_STRING_SIZE]; + int r; + + if (!info->attrs[MCPS802154_ATTR_SCHEDULER_NAME]) + return -EINVAL; + nla_strscpy(name, info->attrs[MCPS802154_ATTR_SCHEDULER_NAME], + sizeof(name)); + + mutex_lock(&local->fsm_lock); + r = mcps802154_ca_set_scheduler(local, name, params_attr, info->extack); + mutex_unlock(&local->fsm_lock); + if (r) + return r; + + if (regions_attr) + r = mcps802154_nl_set_regions(local, name, regions_attr, + info->extack, false); + + return r; +} + +/** + * mcps802154_nl_call_scheduler() - Call scheduler specific procedure. + * @skb: Request message. + * @info: Request information. + * + * Return: 0 or error. + */ +static int mcps802154_nl_call_scheduler(struct sk_buff *skb, + struct genl_info *info) +{ + struct mcps802154_local *local = info->user_ptr[0]; + int r; + struct nlattr *params_attr = + info->attrs[MCPS802154_ATTR_SCHEDULER_CALL_PARAMS]; + struct nlattr *call_attr = info->attrs[MCPS802154_ATTR_SCHEDULER_CALL]; + struct nlattr *name_attr = info->attrs[MCPS802154_ATTR_SCHEDULER_NAME]; + char name[ATTR_STRING_SIZE]; + u32 call_id; + + if (!name_attr || !call_attr) + return -EINVAL; + + nla_strscpy(name, name_attr, sizeof(name)); + call_id = nla_get_u32(call_attr); + + mutex_lock(&local->fsm_lock); + r = mcps802154_ca_scheduler_call(local, name, call_id, params_attr, + info); + mutex_unlock(&local->fsm_lock); + + return r; +} + +/** + * mcps802154_nl_call_region() - Call region specific procedure. + * @skb: Request message. + * @info: Request information. + * + * Return: 0 or error. + */ +static int mcps802154_nl_call_region(struct sk_buff *skb, + struct genl_info *info) +{ + struct mcps802154_local *local = info->user_ptr[0]; + struct nlattr *region_call_attr = + info->attrs[MCPS802154_ATTR_SCHEDULER_REGION_CALL]; + struct nlattr *name_attr = info->attrs[MCPS802154_ATTR_SCHEDULER_NAME]; + struct nlattr *attrs[MCPS802154_REGION_MAX + 1]; + int r, call_id; + char scheduler_name[ATTR_STRING_SIZE]; + u32 region_id = 0; + char region_name[ATTR_STRING_SIZE]; + + if (!name_attr || !region_call_attr) + return -EINVAL; + + nla_strscpy(scheduler_name, name_attr, sizeof(scheduler_name)); + + r = nla_parse_nested(attrs, MCPS802154_REGION_MAX, region_call_attr, + mcps802154_nl_region_policy, info->extack); + if (r) + return r; + + if (!attrs[MCPS802154_REGION_ATTR_NAME] || + !attrs[MCPS802154_REGION_ATTR_CALL]) + return -EINVAL; + + if (attrs[MCPS802154_REGION_ATTR_ID]) + region_id = nla_get_s32(attrs[MCPS802154_REGION_ATTR_ID]); + nla_strscpy(region_name, attrs[MCPS802154_REGION_ATTR_NAME], + sizeof(region_name)); + call_id = nla_get_u32(attrs[MCPS802154_REGION_ATTR_CALL]); + + mutex_lock(&local->fsm_lock); + local->cur_cmd_info = info; + r = mcps802154_ca_call_region(local, scheduler_name, region_id, + region_name, call_id, + attrs[MCPS802154_REGION_ATTR_CALL_PARAMS], + info); + local->cur_cmd_info = NULL; + mutex_unlock(&local->fsm_lock); + + return r; +} + +/** + * mcps802154_nl_close_scheduler() - Close current scheduler and its regions. + * @skb: Request message. + * @info: Request information. + * + * Return: 0 or error. + */ +static int mcps802154_nl_close_scheduler(struct sk_buff *skb, + struct genl_info *info) +{ + struct mcps802154_local *local = info->user_ptr[0]; + mutex_lock(&local->fsm_lock); + local->cur_cmd_info = info; + mcps802154_ca_close(local); + local->cur_cmd_info = NULL; + mutex_unlock(&local->fsm_lock); + + return 0; +} + +struct sk_buff * +mcps802154_region_call_alloc_reply_skb(struct mcps802154_llhw *llhw, + struct mcps802154_region *region, + u32 call_id, int approx_len) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + struct sk_buff *msg; + void *hdr; + struct nlattr *call, *params; + + if (WARN_ON(!local->cur_cmd_info)) + return NULL; + + msg = nlmsg_new(approx_len + NLMSG_HDRLEN, GFP_KERNEL); + if (!msg) + return NULL; + + hdr = genlmsg_put(msg, local->cur_cmd_info->snd_portid, + local->cur_cmd_info->snd_seq, &mcps802154_nl_family, + 0, MCPS802154_CMD_CALL_REGION); + if (!hdr) + goto nla_put_failure; + + if (nla_put_u32(msg, MCPS802154_ATTR_HW, local->hw_idx)) + goto nla_put_failure; + + call = nla_nest_start(msg, MCPS802154_ATTR_SCHEDULER_REGION_CALL); + if (!call) + goto nla_put_failure; + + if (nla_put_string(msg, MCPS802154_REGION_ATTR_NAME, + region->ops->name) || + nla_put_u32(msg, MCPS802154_REGION_ATTR_CALL, call_id)) + goto nla_put_failure; + + params = nla_nest_start(msg, MCPS802154_REGION_ATTR_CALL_PARAMS); + if (!params) + goto nla_put_failure; + + ((void **)msg->cb)[0] = hdr; + ((void **)msg->cb)[1] = call; + ((void **)msg->cb)[2] = params; + + return msg; +nla_put_failure: + kfree_skb(msg); + return NULL; +} +EXPORT_SYMBOL(mcps802154_region_call_alloc_reply_skb); + +int mcps802154_region_call_reply(struct mcps802154_llhw *llhw, + struct sk_buff *skb) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + void *hdr = ((void **)skb->cb)[0]; + struct nlattr *call = ((void **)skb->cb)[1]; + struct nlattr *params = ((void **)skb->cb)[2]; + + /* Clear CB data for netlink core to own from now on. */ + memset(skb->cb, 0, sizeof(skb->cb)); + + if (WARN_ON(!local->cur_cmd_info)) { + kfree_skb(skb); + return -EINVAL; + } + + nla_nest_end(skb, params); + nla_nest_end(skb, call); + genlmsg_end(skb, hdr); + + return genlmsg_reply(skb, local->cur_cmd_info); +} +EXPORT_SYMBOL(mcps802154_region_call_reply); + +struct sk_buff * +mcps802154_region_event_alloc_skb(struct mcps802154_llhw *llhw, + struct mcps802154_region *region, u32 call_id, + u32 portid, int approx_len, gfp_t gfp) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + struct sk_buff *msg; + void *hdr; + struct nlattr *call, *params; + + msg = nlmsg_new(approx_len + NLMSG_HDRLEN, gfp); + if (!msg) + return NULL; + + hdr = genlmsg_put(msg, portid, 0, &mcps802154_nl_family, 0, + MCPS802154_CMD_CALL_REGION); + if (!hdr) + goto nla_put_failure; + + if (nla_put_u32(msg, MCPS802154_ATTR_HW, local->hw_idx)) + goto nla_put_failure; + + call = nla_nest_start(msg, MCPS802154_ATTR_SCHEDULER_REGION_CALL); + if (!call) + goto nla_put_failure; + + if (nla_put_string(msg, MCPS802154_REGION_ATTR_NAME, + region->ops->name) || + nla_put_u32(msg, MCPS802154_REGION_ATTR_CALL, call_id)) + goto nla_put_failure; + + params = nla_nest_start(msg, MCPS802154_REGION_ATTR_CALL_PARAMS); + if (!params) + goto nla_put_failure; + + ((void **)msg->cb)[0] = hdr; + ((void **)msg->cb)[1] = call; + ((void **)msg->cb)[2] = params; + + return msg; +nla_put_failure: + kfree_skb(msg); + return NULL; +} +EXPORT_SYMBOL(mcps802154_region_event_alloc_skb); + +int mcps802154_region_event(struct mcps802154_llhw *llhw, struct sk_buff *skb) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + void *hdr = ((void **)skb->cb)[0]; + struct nlmsghdr *nlhdr = nlmsg_hdr(skb); + struct nlattr *call = ((void **)skb->cb)[1]; + struct nlattr *params = ((void **)skb->cb)[2]; + + /* Clear CB data for netlink core to own from now on. */ + memset(skb->cb, 0, sizeof(skb->cb)); + + nla_nest_end(skb, params); + nla_nest_end(skb, call); + genlmsg_end(skb, hdr); + + return genlmsg_unicast(wpan_phy_net(local->hw->phy), skb, + nlhdr->nlmsg_pid); +} +EXPORT_SYMBOL(mcps802154_region_event); + +#ifdef CONFIG_MCPS802154_TESTMODE +/** + * mcps802154_nl_testmode_do() - Run a testmode command. + * @skb: Request message. + * @info: Request information. + * + * This function is a passthrough to send testmode command to + * the driver. + * + * Return: 0 or error. + */ +static int mcps802154_nl_testmode_do(struct sk_buff *skb, + struct genl_info *info) +{ + struct mcps802154_local *local = info->user_ptr[0]; + int r; + + if (!local->ops->testmode_cmd) + return -EOPNOTSUPP; + + if (!info->attrs[MCPS802154_ATTR_TESTDATA]) + return -EINVAL; + + mutex_lock(&local->fsm_lock); + local->cur_cmd_info = info; + r = llhw_testmode_cmd(local, + nla_data(info->attrs[MCPS802154_ATTR_TESTDATA]), + nla_len(info->attrs[MCPS802154_ATTR_TESTDATA])); + local->cur_cmd_info = NULL; + mutex_unlock(&local->fsm_lock); + return r; +} + +struct sk_buff * +mcps802154_testmode_alloc_reply_skb(struct mcps802154_llhw *llhw, int approxlen) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + struct sk_buff *skb; + void *hdr; + struct nlattr *data; + + if (WARN_ON(!local->cur_cmd_info)) + return NULL; + + skb = nlmsg_new(approxlen + 100, GFP_KERNEL); + if (!skb) + return NULL; + + /* Append testmode header to the netlink message */ + hdr = genlmsg_put(skb, local->cur_cmd_info->snd_portid, + local->cur_cmd_info->snd_seq, &mcps802154_nl_family, + 0, MCPS802154_CMD_TESTMODE); + if (!hdr) + goto nla_put_failure; + + /* Start putting nested testmode data into the netlink message */ + data = nla_nest_start(skb, MCPS802154_ATTR_TESTDATA); + if (!data) + goto nla_put_failure; + + /* We put our private variables there to keep them across layers */ + ((void **)skb->cb)[0] = hdr; + ((void **)skb->cb)[1] = data; + + return skb; +nla_put_failure: + kfree_skb(skb); + return NULL; +} +EXPORT_SYMBOL(mcps802154_testmode_alloc_reply_skb); + +int mcps802154_testmode_reply(struct mcps802154_llhw *llhw, struct sk_buff *skb) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + void *hdr = ((void **)skb->cb)[0]; + struct nlattr *data = ((void **)skb->cb)[1]; + + /* Clear CB data for netlink core to own from now on. */ + memset(skb->cb, 0, sizeof(skb->cb)); + + if (WARN_ON(!local->cur_cmd_info)) { + kfree_skb(skb); + return -EINVAL; + } + + /* Stop putting nested testmode data into the netlink message */ + nla_nest_end(skb, data); + genlmsg_end(skb, hdr); + return genlmsg_reply(skb, local->cur_cmd_info); +} +EXPORT_SYMBOL(mcps802154_testmode_reply); +#endif + +/** + * mcps802154_nl_put_calibration() - put on calibration in msg. + * @msg: Request message. + * @key: calibration name + * @status: status of reading operation, and length of calibration value when positive. + * @data: calibration value, if available. + * @onlykey: true to put only calibration key. + * + * Return: 0 or error. + */ +static int mcps802154_nl_put_calibration(struct sk_buff *msg, const char *key, + int status, void *data, bool onlykey) +{ + struct nlattr *calibration; + int r; + + calibration = nla_nest_start(msg, 1); + if (!calibration) + return -EMSGSIZE; + + r = nla_put_string(msg, MCPS802154_CALIBRATIONS_ATTR_KEY, key); + if (r) + return -EMSGSIZE; + if (onlykey) + goto finish; + + if (status < 0) + r = nla_put_s32(msg, MCPS802154_CALIBRATIONS_ATTR_STATUS, + -status); + else if (data != NULL) + /* when positive, the status represent the data length. */ + r = nla_put(msg, MCPS802154_CALIBRATIONS_ATTR_VALUE, status, + data); + if (r) + return -EMSGSIZE; + +finish: + nla_nest_end(msg, calibration); + return 0; +} + +/** + * mcps802154_nl_set_calibration() - Set calibrations parameters. + * @skb: Request message. + * @info: Request information. + * + * Return: 0 or error. + */ +static int mcps802154_nl_set_calibration(struct sk_buff *skb, + struct genl_info *info) +{ + struct mcps802154_local *local = info->user_ptr[0]; + struct sk_buff *msg; + void *hdr; + int err; + + if (!local->ops->set_calibration) + return -EOPNOTSUPP; + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, + &mcps802154_nl_family, 0, + MCPS802154_CMD_SET_CALIBRATIONS); + if (!hdr) { + err = -ENOBUFS; + goto failure; + } + + if (nla_put_u32(msg, MCPS802154_ATTR_HW, local->hw_idx)) { + err = -EMSGSIZE; + goto nla_put_failure; + } + + if (info->attrs[MCPS802154_ATTR_CALIBRATIONS]) { + struct nlattr *attrs[MCPS802154_CALIBRATIONS_ATTR_MAX + 1]; + struct nlattr *calibrations, *input; + int rem; + + nla_for_each_nested ( + input, info->attrs[MCPS802154_ATTR_CALIBRATIONS], rem) { + char *key; + int r; + + r = nla_parse_nested( + attrs, MCPS802154_CALIBRATIONS_ATTR_MAX, input, + mcps802154_nl_calibration_policy, info->extack); + if (r) + continue; + + if (!attrs[MCPS802154_CALIBRATIONS_ATTR_KEY]) + continue; + key = nla_data(attrs[MCPS802154_CALIBRATIONS_ATTR_KEY]); + + if (!attrs[MCPS802154_CALIBRATIONS_ATTR_VALUE]) + r = -EINVAL; + else { + struct nlattr *value; + + value = attrs[MCPS802154_CALIBRATIONS_ATTR_VALUE]; + r = llhw_set_calibration(local, key, + nla_data(value), + nla_len(value)); + } + if (r < 0) { + calibrations = nla_nest_start( + msg, MCPS802154_ATTR_CALIBRATIONS); + /* Put the result in the response message. */ + err = mcps802154_nl_put_calibration( + msg, key, r, NULL, false); + if (err) + goto nla_put_failure; + nla_nest_end(msg, calibrations); + break; + } + } + } + + genlmsg_end(msg, hdr); + return genlmsg_reply(msg, info); + +nla_put_failure: + genlmsg_cancel(msg, hdr); +failure: + nlmsg_free(msg); + return err; +} + +/** + * mcps802154_nl_get_calibration() - Set calibrations parameters. + * @skb: Request message. + * @info: Request information. + * + * Return: 0 or error. + */ +static int mcps802154_nl_get_calibration(struct sk_buff *skb, + struct genl_info *info) +{ + struct nlattr *attrs[MCPS802154_CALIBRATIONS_ATTR_MAX + 1]; + struct mcps802154_local *local = info->user_ptr[0]; + struct nlattr *calibrations; + struct nlattr *input; + struct sk_buff *msg; + void *hdr; + char *key; + u32 tmp[32]; + int err; + int r; + + if (!local->ops->get_calibration) + return -EOPNOTSUPP; + + /* NLMSG_DEFAULT_SIZE isn't enough for 4 antennas configuration. + So add a full page to maintain page alignment of the message size. */ + msg = nlmsg_new(NLMSG_DEFAULT_SIZE + PAGE_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, + &mcps802154_nl_family, 0, + MCPS802154_CMD_GET_CALIBRATIONS); + if (!hdr) { + err = -ENOBUFS; + goto failure; + } + + /* Build the confirm message in same time as request message. */ + if (nla_put_u32(msg, MCPS802154_ATTR_HW, local->hw_idx)) { + err = -EMSGSIZE; + goto nla_put_failure; + } + + calibrations = nla_nest_start(msg, MCPS802154_ATTR_CALIBRATIONS); + if (info->attrs[MCPS802154_ATTR_CALIBRATIONS]) { + int rem; + + nla_for_each_nested ( + input, info->attrs[MCPS802154_ATTR_CALIBRATIONS], rem) { + r = nla_parse_nested( + attrs, MCPS802154_CALIBRATIONS_ATTR_MAX, input, + mcps802154_nl_calibration_policy, info->extack); + if (r) + continue; + if (!attrs[MCPS802154_CALIBRATIONS_ATTR_KEY]) + continue; + + key = nla_data(attrs[MCPS802154_CALIBRATIONS_ATTR_KEY]); + r = llhw_get_calibration(local, key, &tmp, sizeof(tmp)); + + /* Put the result in the response message. */ + err = mcps802154_nl_put_calibration(msg, key, r, &tmp, + false); + if (err) + goto nla_put_failure; + } + } else if (local->ops->list_calibration) { + const char *const *calibration; + const char *const *entry; + + calibration = llhw_list_calibration(local); + if (!calibration) { + err = -ENOENT; + goto nla_put_failure; + } + for (entry = calibration; *entry; entry++) { + r = llhw_get_calibration(local, *entry, &tmp, + sizeof(tmp)); + + /* Put the result in the response message. */ + err = mcps802154_nl_put_calibration(msg, *entry, r, + &tmp, false); + if (err) + goto nla_put_failure; + } + } + nla_nest_end(msg, calibrations); + + genlmsg_end(msg, hdr); + return genlmsg_reply(msg, info); + +nla_put_failure: + genlmsg_cancel(msg, hdr); +failure: + nlmsg_free(msg); + return err; +} + +/** + * mcps802154_nl_list_calibration() - Set calibrations parameters. + * @skb: Request message. + * @info: Request information. + * + * Return: 0 or error. + */ +static int mcps802154_nl_list_calibration(struct sk_buff *skb, + struct genl_info *info) +{ + struct mcps802154_local *local = info->user_ptr[0]; + struct nlattr *calibrations; + const char *const *list; + const char *const *entry; + struct sk_buff *msg; + void *hdr; + int err; + + if (!local->ops->list_calibration) + return -EOPNOTSUPP; + + list = llhw_list_calibration(local); + if (!list) + return -ENOENT; + /* NLMSG_DEFAULT_SIZE isn't enough for 4 antennas configuration. + So add a full page to maintain page alignment of the message size. */ + msg = nlmsg_new(NLMSG_DEFAULT_SIZE + PAGE_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, + &mcps802154_nl_family, 0, + MCPS802154_CMD_LIST_CALIBRATIONS); + if (!hdr) { + err = -ENOBUFS; + goto failure; + } + + if (nla_put_u32(msg, MCPS802154_ATTR_HW, local->hw_idx)) { + err = -EMSGSIZE; + goto nla_put_failure; + } + + calibrations = nla_nest_start(msg, MCPS802154_ATTR_CALIBRATIONS); + for (entry = list; *entry; entry++) { + /* Put the result in the response message. */ + err = mcps802154_nl_put_calibration(msg, *entry, 0, NULL, true); + if (err) + goto nla_put_failure; + } + nla_nest_end(msg, calibrations); + + genlmsg_end(msg, hdr); + return genlmsg_reply(msg, info); + +nla_put_failure: + genlmsg_cancel(msg, hdr); +failure: + nlmsg_free(msg); + return err; +} + +/** + * mcps802154_nl_put_pwr_stats_state() - Put a power statistic state on a netlink message. + * @msg: Netlink message. + * @state: Related power statistic state. + * @time: Duration of this state. + * @count: Transitions count to this state. + * + * Return: 0 or error. + */ +static int +mcps802154_nl_put_pwr_stats_state(struct sk_buff *msg, + enum mcps802154_pwr_stats_attrs state, + u32 time, u32 count) +{ + struct nlattr *nl_pwr_stats_state; + + nl_pwr_stats_state = nla_nest_start(msg, state); + if (!nl_pwr_stats_state) + return -EMSGSIZE; + if (nla_put_u32(msg, MCPS802154_PWR_STATS_STATE_ATTR_TIME, time)) + return -EMSGSIZE; + if (nla_put_u32(msg, MCPS802154_PWR_STATS_STATE_ATTR_COUNT, count)) + return -EMSGSIZE; + nla_nest_end(msg, nl_pwr_stats_state); + return 0; +} + +/** + * mcps802154_nl_get_pwr_stats() - Get power statistics. + * @skb: Request message. + * @info: Request information. + * + * Return: 0 or error. + */ +static int mcps802154_nl_get_pwr_stats(struct sk_buff *skb, + struct genl_info *info) +{ + struct mcps802154_local *local = info->user_ptr[0]; + struct mcps802154_llhw *llhw = &local->llhw; + struct sk_buff *msg; + void *hdr; + struct mcps802154_power_stats pwr_stats; + struct nlattr *nl_pwr_stats; + int rc; + + if (!local->ops->get_power_stats) + return -EOPNOTSUPP; + + /* Get the power statistics from the low level hardware driver. */ + rc = local->ops->get_power_stats(llhw, &pwr_stats); + if (rc) + return rc; + + /* Build the response netlink message. */ + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, + &mcps802154_nl_family, 0, + MCPS802154_CMD_GET_PWR_STATS); + if (!hdr) { + rc = -ENOBUFS; + goto failure; + } + + nl_pwr_stats = nla_nest_start(msg, MCPS802154_ATTR_PWR_STATS); + if (!nl_pwr_stats) + goto nla_put_failure; + + /* Process the SLEEP state. */ + rc = mcps802154_nl_put_pwr_stats_state( + msg, MCPS802154_PWR_STATS_ATTR_SLEEP, + pwr_stats.power_state_stats[MCPS802154_PWR_STATE_SLEEP].dur / + 1000000, + pwr_stats.power_state_stats[MCPS802154_PWR_STATE_SLEEP].count); + if (rc) + goto nla_put_failure; + + /* Process the IDLE state. */ + rc = mcps802154_nl_put_pwr_stats_state( + msg, MCPS802154_PWR_STATS_ATTR_IDLE, + pwr_stats.power_state_stats[MCPS802154_PWR_STATE_IDLE].dur / + 1000000, + pwr_stats.power_state_stats[MCPS802154_PWR_STATE_IDLE].count); + if (rc) + goto nla_put_failure; + + /* Process the RX state. */ + rc = mcps802154_nl_put_pwr_stats_state( + msg, MCPS802154_PWR_STATS_ATTR_RX, + pwr_stats.power_state_stats[MCPS802154_PWR_STATE_RX].dur / + 1000000, + pwr_stats.power_state_stats[MCPS802154_PWR_STATE_RX].count); + if (rc) + goto nla_put_failure; + + /* Process the TX state. */ + rc = mcps802154_nl_put_pwr_stats_state( + msg, MCPS802154_PWR_STATS_ATTR_TX, + pwr_stats.power_state_stats[MCPS802154_PWR_STATE_TX].dur / + 1000000, + pwr_stats.power_state_stats[MCPS802154_PWR_STATE_TX].count); + if (rc) + goto nla_put_failure; + + /* Process the interrupts count. */ + if (nla_put_u32(msg, MCPS802154_PWR_STATS_ATTR_INTERRUPTS, + pwr_stats.interrupts)) { + rc = -EMSGSIZE; + goto nla_put_failure; + } + + nla_nest_end(msg, nl_pwr_stats); + genlmsg_end(msg, hdr); + return genlmsg_reply(msg, info); + +nla_put_failure: + genlmsg_cancel(msg, hdr); +failure: + nlmsg_free(msg); + return rc; +} + +enum mcps802154_nl_internal_flags { + MCPS802154_NL_NEED_HW = 1, +}; + +/** + * mcps802154_get_from_info() - Retrieve private data from netlink request. + * information. + * @info: Request information. + * + * Return: Found MCPS data, or error pointer. + */ +static struct mcps802154_local *mcps802154_get_from_info(struct genl_info *info) +{ + struct nlattr **attrs = info->attrs; + int hw_idx; + struct mcps802154_local *local; + + ASSERT_RTNL(); + + if (!attrs[MCPS802154_ATTR_HW]) + return ERR_PTR(-EINVAL); + + hw_idx = nla_get_u32(attrs[MCPS802154_ATTR_HW]); + + local = mcps802154_get_first_by_idx(hw_idx); + if (!local || local->hw_idx != hw_idx) + return ERR_PTR(-ENODEV); + + if (!net_eq(wpan_phy_net(local->hw->phy), genl_info_net(info))) + return ERR_PTR(-ENODEV); + + return local; +} + +/** + * mcps802154_nl_pre_doit() - Called before single requests (but not dump). + * @ops: Command to be executed ops structure. + * @skb: Request message. + * @info: Request information. + * + * Set MCPS private data in user_ptr[0] if needed, and lock RTNL to make it + * stick. + * + * Return: 0 or error. + */ +static int mcps802154_nl_pre_doit(const struct genl_ops *ops, + struct sk_buff *skb, struct genl_info *info) +{ + struct mcps802154_local *local; + + if (ops->internal_flags & MCPS802154_NL_NEED_HW) { + rtnl_lock(); + local = mcps802154_get_from_info(info); + if (IS_ERR(local)) { + rtnl_unlock(); + return PTR_ERR(local); + } + info->user_ptr[0] = local; + } + + return 0; +} + +/** + * mcps802154_nl_post_doit() - Called after single requests (but not dump). + * @ops: Command to be executed ops structure. + * @skb: Request message. + * @info: Request information. + * + * Release RTNL if needed. + */ +static void mcps802154_nl_post_doit(const struct genl_ops *ops, + struct sk_buff *skb, struct genl_info *info) +{ + if (ops->internal_flags & MCPS802154_NL_NEED_HW) + rtnl_unlock(); +} + +static const struct genl_ops mcps802154_nl_ops[] = { + { + .cmd = MCPS802154_CMD_GET_HW, + .doit = mcps802154_nl_get_hw, + .dumpit = mcps802154_nl_dump_hw, + .internal_flags = MCPS802154_NL_NEED_HW, + }, + { + .cmd = MCPS802154_CMD_SET_SCHEDULER, + .doit = mcps802154_nl_set_scheduler, + .flags = GENL_ADMIN_PERM, + .internal_flags = MCPS802154_NL_NEED_HW, + }, + { + .cmd = MCPS802154_CMD_SET_SCHEDULER_PARAMS, + .doit = mcps802154_nl_generic_set_params, + .flags = GENL_ADMIN_PERM, + .internal_flags = MCPS802154_NL_NEED_HW, + }, + { + .cmd = MCPS802154_CMD_CALL_SCHEDULER, + .doit = mcps802154_nl_call_scheduler, + .flags = GENL_ADMIN_PERM, + .internal_flags = MCPS802154_NL_NEED_HW, + }, + { + .cmd = MCPS802154_CMD_CLOSE_SCHEDULER, + .doit = mcps802154_nl_close_scheduler, + .flags = GENL_ADMIN_PERM, + .internal_flags = MCPS802154_NL_NEED_HW, + }, + { + .cmd = MCPS802154_CMD_SET_SCHEDULER_REGIONS, + .doit = mcps802154_nl_generic_set_params, + .flags = GENL_ADMIN_PERM, + .internal_flags = MCPS802154_NL_NEED_HW, + }, + { + .cmd = MCPS802154_CMD_SET_REGIONS_PARAMS, + .doit = mcps802154_nl_generic_set_params, + .flags = GENL_ADMIN_PERM, + .internal_flags = MCPS802154_NL_NEED_HW, + }, + { + .cmd = MCPS802154_CMD_CALL_REGION, + .doit = mcps802154_nl_call_region, + .flags = GENL_ADMIN_PERM, + .internal_flags = MCPS802154_NL_NEED_HW, + }, +#ifdef CONFIG_MCPS802154_TESTMODE + { + .cmd = MCPS802154_CMD_TESTMODE, + .doit = mcps802154_nl_testmode_do, + .flags = GENL_ADMIN_PERM, + .internal_flags = MCPS802154_NL_NEED_HW, + }, +#endif + { + .cmd = MCPS802154_CMD_SET_CALIBRATIONS, + .doit = mcps802154_nl_set_calibration, + .flags = GENL_ADMIN_PERM, + .internal_flags = MCPS802154_NL_NEED_HW, + }, + { + .cmd = MCPS802154_CMD_GET_CALIBRATIONS, + .doit = mcps802154_nl_get_calibration, + .flags = GENL_ADMIN_PERM, + .internal_flags = MCPS802154_NL_NEED_HW, + }, + { + .cmd = MCPS802154_CMD_LIST_CALIBRATIONS, + .doit = mcps802154_nl_list_calibration, + .flags = GENL_ADMIN_PERM, + .internal_flags = MCPS802154_NL_NEED_HW, + }, + { + .cmd = MCPS802154_CMD_GET_PWR_STATS, + .doit = mcps802154_nl_get_pwr_stats, + .flags = GENL_ADMIN_PERM, + .internal_flags = MCPS802154_NL_NEED_HW, + }, +}; + +static struct genl_family mcps802154_nl_family __ro_after_init = { + .name = MCPS802154_GENL_NAME, + .version = 1, + .maxattr = MCPS802154_ATTR_MAX, + .policy = mcps802154_nl_policy, + .netnsok = true, + .pre_doit = mcps802154_nl_pre_doit, + .post_doit = mcps802154_nl_post_doit, + .ops = mcps802154_nl_ops, + .n_ops = ARRAY_SIZE(mcps802154_nl_ops), + .module = THIS_MODULE, +}; + +int __init mcps802154_nl_init(void) +{ + return genl_register_family(&mcps802154_nl_family); +} + +void __exit mcps802154_nl_exit(void) +{ + genl_unregister_family(&mcps802154_nl_family); +} diff --git a/kernel/net/mcps802154/nl.h b/kernel/net/mcps802154/nl.h new file mode 100644 index 0000000..8bfe3bb --- /dev/null +++ b/kernel/net/mcps802154/nl.h @@ -0,0 +1,30 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef MCPS802154_NL_H +#define MCPS802154_NL_H + +int mcps802154_nl_init(void); +void mcps802154_nl_exit(void); + +#endif /* MCPS802154_NL_H */ diff --git a/kernel/net/mcps802154/on_demand_scheduler.c b/kernel/net/mcps802154/on_demand_scheduler.c new file mode 120000 index 0000000..5899a77 --- /dev/null +++ b/kernel/net/mcps802154/on_demand_scheduler.c @@ -0,0 +1 @@ +../../../mac/on_demand_scheduler.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/on_demand_scheduler.h b/kernel/net/mcps802154/on_demand_scheduler.h new file mode 120000 index 0000000..be9bdbe --- /dev/null +++ b/kernel/net/mcps802154/on_demand_scheduler.h @@ -0,0 +1 @@ +../../../mac/on_demand_scheduler.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/ops.c b/kernel/net/mcps802154/ops.c new file mode 120000 index 0000000..4e1e3ac --- /dev/null +++ b/kernel/net/mcps802154/ops.c @@ -0,0 +1 @@ +../../../mac/ops.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/pctt_access.c b/kernel/net/mcps802154/pctt_access.c new file mode 120000 index 0000000..2c1912c --- /dev/null +++ b/kernel/net/mcps802154/pctt_access.c @@ -0,0 +1 @@ +../../../mac/pctt_access.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/pctt_access.h b/kernel/net/mcps802154/pctt_access.h new file mode 120000 index 0000000..065c8b6 --- /dev/null +++ b/kernel/net/mcps802154/pctt_access.h @@ -0,0 +1 @@ +../../../mac/pctt_access.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/pctt_region.c b/kernel/net/mcps802154/pctt_region.c new file mode 120000 index 0000000..3ba8a7e --- /dev/null +++ b/kernel/net/mcps802154/pctt_region.c @@ -0,0 +1 @@ +../../../mac/pctt_region.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/pctt_region.h b/kernel/net/mcps802154/pctt_region.h new file mode 120000 index 0000000..912feee --- /dev/null +++ b/kernel/net/mcps802154/pctt_region.h @@ -0,0 +1 @@ +../../../mac/pctt_region.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/pctt_region_call.c b/kernel/net/mcps802154/pctt_region_call.c new file mode 120000 index 0000000..2154d8e --- /dev/null +++ b/kernel/net/mcps802154/pctt_region_call.c @@ -0,0 +1 @@ +../../../mac/pctt_region_call.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/pctt_region_call.h b/kernel/net/mcps802154/pctt_region_call.h new file mode 120000 index 0000000..52ebe0b --- /dev/null +++ b/kernel/net/mcps802154/pctt_region_call.h @@ -0,0 +1 @@ +../../../mac/pctt_region_call.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/pctt_session.c b/kernel/net/mcps802154/pctt_session.c new file mode 120000 index 0000000..9e657a2 --- /dev/null +++ b/kernel/net/mcps802154/pctt_session.c @@ -0,0 +1 @@ +../../../mac/pctt_session.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/pctt_session.h b/kernel/net/mcps802154/pctt_session.h new file mode 120000 index 0000000..4171fe7 --- /dev/null +++ b/kernel/net/mcps802154/pctt_session.h @@ -0,0 +1 @@ +../../../mac/pctt_session.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/pctt_trace.c b/kernel/net/mcps802154/pctt_trace.c new file mode 100644 index 0000000..3d8754d --- /dev/null +++ b/kernel/net/mcps802154/pctt_trace.c @@ -0,0 +1,25 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#define CREATE_TRACE_POINTS +#include "pctt_trace.h" diff --git a/kernel/net/mcps802154/pctt_trace.h b/kernel/net/mcps802154/pctt_trace.h new file mode 120000 index 0000000..ebac311 --- /dev/null +++ b/kernel/net/mcps802154/pctt_trace.h @@ -0,0 +1 @@ +../../../mac/pctt_trace.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/regions.c b/kernel/net/mcps802154/regions.c new file mode 120000 index 0000000..3c959bb --- /dev/null +++ b/kernel/net/mcps802154/regions.c @@ -0,0 +1 @@ +../../../mac/regions.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/schedule.c b/kernel/net/mcps802154/schedule.c new file mode 120000 index 0000000..22c60ab --- /dev/null +++ b/kernel/net/mcps802154/schedule.c @@ -0,0 +1 @@ +../../../mac/schedule.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/schedule.h b/kernel/net/mcps802154/schedule.h new file mode 120000 index 0000000..3c22005 --- /dev/null +++ b/kernel/net/mcps802154/schedule.h @@ -0,0 +1 @@ +../../../mac/schedule.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/schedulers.c b/kernel/net/mcps802154/schedulers.c new file mode 120000 index 0000000..8204990 --- /dev/null +++ b/kernel/net/mcps802154/schedulers.c @@ -0,0 +1 @@ +../../../mac/schedulers.c
\ No newline at end of file diff --git a/kernel/net/mcps802154/schedulers.h b/kernel/net/mcps802154/schedulers.h new file mode 120000 index 0000000..82a61dd --- /dev/null +++ b/kernel/net/mcps802154/schedulers.h @@ -0,0 +1 @@ +../../../mac/schedulers.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/trace.c b/kernel/net/mcps802154/trace.c new file mode 100644 index 0000000..77af338 --- /dev/null +++ b/kernel/net/mcps802154/trace.c @@ -0,0 +1,25 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#define CREATE_TRACE_POINTS +#include "trace.h" diff --git a/kernel/net/mcps802154/trace.h b/kernel/net/mcps802154/trace.h new file mode 120000 index 0000000..1df4e64 --- /dev/null +++ b/kernel/net/mcps802154/trace.h @@ -0,0 +1 @@ +../../../mac/trace.h
\ No newline at end of file diff --git a/kernel/net/mcps802154/warn_return.h b/kernel/net/mcps802154/warn_return.h new file mode 120000 index 0000000..f7057b9 --- /dev/null +++ b/kernel/net/mcps802154/warn_return.h @@ -0,0 +1 @@ +../../../mac/warn_return.h
\ No newline at end of file diff --git a/mac/.gitignore b/mac/.gitignore new file mode 100644 index 0000000..6e92f57 --- /dev/null +++ b/mac/.gitignore @@ -0,0 +1 @@ +tags diff --git a/mac/Recipes b/mac/Recipes new file mode 100644 index 0000000..fec5036 --- /dev/null +++ b/mac/Recipes @@ -0,0 +1,39 @@ +# Run with `make -f Recipes`. +# +# You can get help with `make -f Recipes help`. + +all: + ninja -C utest/build + +test: all + ./utest/build/test_mac $(TESTFLAGS) + +cov: + ninja -C utest/build-cov test_mac_coverage + +cmake: + cmake -S utest -B utest/build -G Ninja -DCMAKE_BUILD_TYPE=Debug + cmake -S utest -B utest/build-cov -G Ninja -DCMAKE_BUILD_TYPE=Debug -DENABLE_TEST_COVERAGE=on + +clean: + test -d utest/build && ninja -C utest/build clean + test -d utest/build-cov && ninja -C utest/build-cov clean + +check: check_format check_doc + +check_format: + ../tools/check-format . ../kernel/net ../kernel/include + +check_doc: + ../deps/linux/linux-uwb/scripts/kernel-doc -Werror -none *.h *.c include/net/*.h ../kernel/include/net/*.h + +help: + @echo "cmake - run cmake to create the build tree, must be done once" + @echo "all - build program" + @echo "test - build and run tests" + @echo "cov - build and run tests with coverage analysis" + @echo "clean - remove generated files" + @echo "" + @echo "check_format - run clang-format" + @echo "check_doc - check kernel-doc comments" + @echo "check - run all checks" diff --git a/mac/backport_nl.h b/mac/backport_nl.h new file mode 100644 index 0000000..d22ad53 --- /dev/null +++ b/mac/backport_nl.h @@ -0,0 +1,46 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef BACKPORT_NL_H +#define BACKPORT_NL_H + +#include <linux/types.h> +#include <linux/version.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0) +#define NLA_POLICY_FULL_RANGE(tp, _range) \ + { \ + .type = (tp) \ + } + +#else + +/* NLA_POLICY_FULL_RANGE expect to have a range defined. + * This define exist for backport linux compilations, as: + * - struct netlink_range_validation don't exist, + * - unused object are considered as error. */ +#define ADD_NETLINK_RANGE_VALIDATION + +#endif + +#endif /* BACKPORT_NL_H */ diff --git a/mac/ca.c b/mac/ca.c new file mode 100644 index 0000000..6c8301f --- /dev/null +++ b/mac/ca.c @@ -0,0 +1,497 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/string.h> + +#include "mcps802154_i.h" +#include "schedulers.h" +#include "trace.h" + +struct mcps802154_access_common_ops ca_access_ops = {}; + +static int mcps802154_ca_trace_int(struct mcps802154_local *local, const int r) +{ + trace_ca_return_int(local, r); + return r; +} + +static void mcps802154_ca_close_scheduler(struct mcps802154_local *local) +{ + struct mcps802154_region *region, *r; + struct mcps802154_ca *ca = &local->ca; + struct list_head *regions = &ca->regions; + + mcps802154_schedule_clear(local); + if (local->ca.scheduler) { + mcps802154_scheduler_close(local->ca.scheduler); + local->ca.scheduler = NULL; + } + list_for_each_entry_safe (region, r, regions, ca_entry) { + list_del(®ion->ca_entry); + mcps802154_region_close(&local->llhw, region); + } + ca->n_regions = 0; +} + +static int check_and_get_region(struct mcps802154_ca *ca, + const char *scheduler_name, u32 region_id, + const char *region_name, + struct mcps802154_region **sel_region) +{ + struct mcps802154_scheduler *scheduler = ca->scheduler; + struct list_head *regions = &ca->regions; + struct mcps802154_region *region; + + /* Check scheduler is the correct one. */ + if (!scheduler || strcmp(scheduler->ops->name, scheduler_name) || + !region_name) + return -EINVAL; + + list_for_each_entry (region, regions, ca_entry) { + if (!strcmp(region_name, region->ops->name) && + region->id == region_id) { + *sel_region = region; + return 0; + } + } + return -ENOENT; +} + +void mcps802154_ca_init(struct mcps802154_local *local) +{ + struct mcps802154_ca *ca = &local->ca; + + local->ca.held = false; + local->ca.reset = false; + INIT_LIST_HEAD(&ca->regions); + ca->n_regions = 0; +} + +void mcps802154_ca_uninit(struct mcps802154_local *local) +{ +} + +int mcps802154_ca_start(struct mcps802154_local *local) +{ + int r; + + if (!local->ca.scheduler) { + r = mcps802154_ca_set_scheduler(local, "default", NULL, NULL); + if (r) + return r; + + r = mcps802154_ca_set_region(local, "default", 0, "default", + NULL, NULL); + if (r) + return r; + } + + local->start_stop_request = true; + mcps802154_fproc_schedule_change(local); + + return local->started ? 0 : -EIO; +} + +void mcps802154_ca_stop(struct mcps802154_local *local) +{ + local->start_stop_request = false; + mcps802154_fproc_schedule_change(local); +} + +void mcps802154_ca_notify_stop(struct mcps802154_local *local) +{ + struct mcps802154_region *region; + struct mcps802154_ca *ca = &local->ca; + struct list_head *regions = &ca->regions; + + mcps802154_scheduler_notify_stop(local->ca.scheduler); + list_for_each_entry (region, regions, ca_entry) { + mcps802154_region_notify_stop(&local->llhw, region); + } +} + +void mcps802154_ca_close(struct mcps802154_local *local) +{ + mcps802154_ca_close_scheduler(local); +} + +int mcps802154_ca_set_scheduler(struct mcps802154_local *local, + const char *name, + const struct nlattr *params_attr, + struct netlink_ext_ack *extack) +{ + struct mcps802154_scheduler *scheduler; + + trace_ca_set_scheduler(local, name); + + if (local->started) + return mcps802154_ca_trace_int(local, -EBUSY); + /* Open new scheduler. */ + scheduler = mcps802154_scheduler_open(local, name, params_attr, extack); + if (!scheduler) + return mcps802154_ca_trace_int(local, -EINVAL); + /* Close previous scheduler and set the new one. */ + mcps802154_ca_close_scheduler(local); + local->ca.scheduler = scheduler; + + return mcps802154_ca_trace_int(local, 0); +} + +int mcps802154_ca_set_region(struct mcps802154_local *local, + const char *scheduler_name, u32 region_id, + const char *region_name, + const struct nlattr *params_attr, + struct netlink_ext_ack *extack) +{ + struct mcps802154_ca *ca = &local->ca; + struct list_head *regions = &ca->regions; + struct mcps802154_scheduler *scheduler; + struct mcps802154_region *region, *old_region; + struct list_head *position = regions; + bool region_id_present = false; + + trace_ca_set_region(local, scheduler_name, region_id, region_name); + + scheduler = local->ca.scheduler; + /* Check scheduler is the correct one. */ + if (!scheduler || strcmp(scheduler->ops->name, scheduler_name) || + !region_name) + return mcps802154_ca_trace_int(local, -EINVAL); + + /* Check if we need to replace an already opened region. */ + list_for_each_entry (old_region, regions, ca_entry) { + if (old_region->id == region_id) { + region_id_present = true; + break; + } else if (old_region->id < region_id) { + position = &old_region->ca_entry; + } else { + break; + } + } + + /* Regions number is limited by the scheduler */ + if (!region_id_present && scheduler->n_regions && + ca->n_regions >= scheduler->n_regions) + return mcps802154_ca_trace_int(local, -ENOSPC); + + region = mcps802154_region_open(&local->llhw, region_name, params_attr, + extack); + + if (!region) + return mcps802154_ca_trace_int(local, -EINVAL); + region->id = region_id; + + if (region_id_present) { + list_replace(&old_region->ca_entry, ®ion->ca_entry); + mcps802154_region_close(&local->llhw, old_region); + } else { + list_add(®ion->ca_entry, position); + ca->n_regions++; + } + + return mcps802154_ca_trace_int(local, 0); +} + +int mcps802154_ca_scheduler_set_parameters(struct mcps802154_local *local, + const char *name, + const struct nlattr *params_attr, + struct netlink_ext_ack *extack) +{ + struct mcps802154_scheduler *scheduler; + int r; + + trace_ca_set_scheduler_parameters(local, name); + + scheduler = local->ca.scheduler; + + /* Check scheduler is the correct one. */ + if (!scheduler || strcmp(scheduler->ops->name, name)) + return mcps802154_ca_trace_int(local, -EINVAL); + + r = mcps802154_scheduler_set_parameters(scheduler, params_attr, extack); + return mcps802154_ca_trace_int(local, r); +} + +int mcps802154_ca_scheduler_call(struct mcps802154_local *local, + const char *scheduler_name, u32 call_id, + const struct nlattr *params_attr, + const struct genl_info *info) +{ + struct mcps802154_scheduler *scheduler; + int r; + + trace_ca_scheduler_call(local, scheduler_name, call_id); + + scheduler = local->ca.scheduler; + /* Check scheduler is the correct one. */ + if (!scheduler || strcmp(scheduler->ops->name, scheduler_name)) + return mcps802154_ca_trace_int(local, -EINVAL); + r = mcps802154_scheduler_call(scheduler, call_id, params_attr, info); + return mcps802154_ca_trace_int(local, r); +} + +int mcps802154_ca_set_region_parameters(struct mcps802154_local *local, + const char *scheduler_name, + u32 region_id, const char *region_name, + const struct nlattr *params_attr, + struct netlink_ext_ack *extack) +{ + struct mcps802154_region *region; + int r; + + trace_ca_set_region_params(local, scheduler_name, region_id, + region_name); + + r = check_and_get_region(&local->ca, scheduler_name, region_id, + region_name, ®ion); + if (r) + goto end; + + r = mcps802154_region_set_parameters(&local->llhw, region, params_attr, + extack); + +end: + return mcps802154_ca_trace_int(local, r); +} + +int mcps802154_ca_call_region(struct mcps802154_local *local, + const char *scheduler_name, u32 region_id, + const char *region_name, u32 call_id, + const struct nlattr *params_attr, + const struct genl_info *info) +{ + struct mcps802154_region *region; + int r; + + trace_ca_call_region(local, scheduler_name, region_id, region_name, + call_id); + + r = check_and_get_region(&local->ca, scheduler_name, region_id, + region_name, ®ion); + if (r) + goto end; + + r = mcps802154_region_call(&local->llhw, region, call_id, params_attr, + info); + +end: + return mcps802154_ca_trace_int(local, r); +} + +int mcps802154_ca_xmit_skb(struct mcps802154_local *local, struct sk_buff *skb) +{ + struct mcps802154_region *region; + int r = -EOPNOTSUPP; + + list_for_each_entry (region, &local->ca.regions, ca_entry) { + if (region->ops->xmit_skb) { + if (region->ops->xmit_skb(region, skb)) { + r = 0; + break; + } + } + } + return r; +} + +/** + * mcps802154_ca_next_region() - Check the current region is still valid, if not + * change region. + * @local: MCPS private data. + * @next_timestamp_dtu: Date of next access opportunity. + * + * Return: 0 if unchanged, 1 if changed, or negative error. + */ +static int mcps802154_ca_next_region(struct mcps802154_local *local, + u32 next_timestamp_dtu) +{ + struct mcps802154_schedule *sched = &local->ca.schedule; + struct mcps802154_schedule_region *sched_region; + int next_dtu = next_timestamp_dtu - sched->start_timestamp_dtu; + bool changed = 0; + bool once; + + sched_region = &sched->regions[sched->current_index]; + once = sched_region->once; + + /* If the region schedule is over, select the next region if + * possible. */ + while (once || (sched_region->duration_dtu != 0 && + next_dtu - sched_region->start_dtu >= + sched_region->duration_dtu)) { + sched->current_index++; + changed = 1; + once = false; + + /* No more region, need a new schedule. */ + if (sched->current_index >= sched->n_regions) { + /* Reduce the schedule duration when not fully used. */ + if (sched_region->once && sched_region->duration_dtu) { + sched->duration_dtu = + next_timestamp_dtu - + sched->start_timestamp_dtu; + } + return mcps802154_schedule_update(local, + next_timestamp_dtu); + } + + sched_region = &sched->regions[sched->current_index]; + } + + return changed; +} + +/** + * mcps802154_ca_nothing() - Fill and return a nothing access. + * @local: MCPS private data. + * @timestamp_dtu: Start of nothing period. + * @duration_dtu: Duration of nothing period, or 0 for endless. + * + * Return: Pointer to access allocated inside the context. + */ +struct mcps802154_access *mcps802154_ca_nothing(struct mcps802154_local *local, + u32 timestamp_dtu, + int duration_dtu) +{ + struct mcps802154_access *access = &local->ca.idle_access; + + access->method = MCPS802154_ACCESS_METHOD_NOTHING; + access->common_ops = &ca_access_ops; + access->timestamp_dtu = timestamp_dtu; + access->duration_dtu = duration_dtu; + return access; +} + +struct mcps802154_access * +mcps802154_ca_get_access(struct mcps802154_local *local, u32 next_timestamp_dtu) +{ + struct mcps802154_schedule *sched = &local->ca.schedule; + struct mcps802154_schedule_region *sched_region; + struct mcps802154_region *region; + struct mcps802154_access *access; + u32 idle_timestamp_dtu, region_start_timestamp_dtu; + int next_in_region_dtu, region_duration_dtu; + int r, changed; + + local->ca.held = false; + + trace_ca_get_access(local, next_timestamp_dtu); + + if (local->ca.reset) { + mcps802154_schedule_clear(local); + local->ca.reset = false; + } + + /* Do not examine accesses later than this date. */ + idle_timestamp_dtu = next_timestamp_dtu + local->llhw.idle_dtu; + while (1) { + /* Need a schedule. */ + if (!sched->n_regions) + r = mcps802154_schedule_update(local, + next_timestamp_dtu); + else + /* Need a region. */ + r = mcps802154_ca_next_region(local, + next_timestamp_dtu); + + /* Stay in IDLE when no schedule. */ + if (r == -ENOENT) + return mcps802154_ca_nothing(local, next_timestamp_dtu, + 0); + else if (r < 0) + return NULL; + + changed = r; + sched_region = &sched->regions[sched->current_index]; + region = sched_region->region; + region_start_timestamp_dtu = + sched->start_timestamp_dtu + sched_region->start_dtu; + region_duration_dtu = sched_region->duration_dtu; + + if (changed) { + if (is_before_dtu(next_timestamp_dtu, + region_start_timestamp_dtu)) { + /* Access date may be postponed. */ + next_timestamp_dtu = region_start_timestamp_dtu; + } + } + + /* Get access. */ + if (region_duration_dtu) + next_in_region_dtu = + next_timestamp_dtu - region_start_timestamp_dtu; + else + next_in_region_dtu = 0; + trace_region_get_access(local, region, next_timestamp_dtu, + next_in_region_dtu, + region_duration_dtu); + access = region->ops->get_access(region, next_timestamp_dtu, + next_in_region_dtu, + region_duration_dtu); + + if (access) + return access; + + /* If no access is found, look for next region, or wait. */ + if (region_duration_dtu) { + u32 region_end_timestamp_dtu = + region_start_timestamp_dtu + + region_duration_dtu; + + if (is_before_dtu(idle_timestamp_dtu, + region_end_timestamp_dtu)) { + return mcps802154_ca_nothing( + local, next_timestamp_dtu, + region_end_timestamp_dtu - + next_timestamp_dtu); + } + + /* Continue after the current region. */ + next_timestamp_dtu = region_end_timestamp_dtu; + } else { + return mcps802154_ca_nothing(local, next_timestamp_dtu, + 0); + } + } +} + +void mcps802154_ca_may_reschedule(struct mcps802154_local *local) +{ + if (!local->ca.held) + mcps802154_fproc_schedule_change(local); +} + +void mcps802154_ca_access_hold(struct mcps802154_local *local) +{ + local->ca.held = true; +} + +void mcps802154_ca_invalidate_schedule(struct mcps802154_local *local) +{ + local->ca.reset = true; + + mcps802154_fproc_schedule_change(local); +} diff --git a/mac/ca.h b/mac/ca.h new file mode 100644 index 0000000..a140eff --- /dev/null +++ b/mac/ca.h @@ -0,0 +1,279 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_CA_H +#define NET_MCPS802154_CA_H + +#include <linux/atomic.h> + +#include "schedule.h" + +struct mcps802154_local; + +/** + * struct mcps802154_ca - CA private data. + */ +struct mcps802154_ca { + /** + * @schedule: Current schedule. + */ + struct mcps802154_schedule schedule; + /** + * @scheduler: Scheduler responsible to maintain the schedule + * or NULL when not chosen yet. + */ + struct mcps802154_scheduler *scheduler; + /** + * @regions: List of regions currently available in the schedule. + */ + struct list_head regions; + /** + * @n_regions: current number of opened regions. + */ + int n_regions; + /** + * @held: Whether access is currently held and cannot change. + */ + bool held; + /** + * @reset: Whether the schedule was invalidated and need to be changed. + */ + bool reset; + /** + * @idle_access: Access used to wait when there is nothing to do. + */ + struct mcps802154_access idle_access; +}; + +/** + * mcps802154_ca_init() - Initialize CA. + * @local: MCPS private data. + */ +void mcps802154_ca_init(struct mcps802154_local *local); + +/** + * mcps802154_ca_uninit() - Uninitialize CA. + * @local: MCPS private data. + */ +void mcps802154_ca_uninit(struct mcps802154_local *local); + +/** + * mcps802154_ca_start() - Start device. + * @local: MCPS private data. + * + * FSM mutex should be locked. + * + * Return: 0 or error. + */ +int mcps802154_ca_start(struct mcps802154_local *local); + +/** + * mcps802154_ca_stop() - Request to stop device. + * @local: MCPS private data. + * + * FSM mutex should be locked. + * + * This is asynchronous, caller needs to wait !local->started. + */ +void mcps802154_ca_stop(struct mcps802154_local *local); + +/** + * mcps802154_ca_notify_stop() - Notify that device has been stopped. + * @local: MCPS private data. + * + * FSM mutex should be locked. + * + */ +void mcps802154_ca_notify_stop(struct mcps802154_local *local); + +/** + * mcps802154_ca_close() - Request to close all the schedules. + * @local: MCPS private data. + * + * FSM mutex should be locked. + */ +void mcps802154_ca_close(struct mcps802154_local *local); + +/** + * mcps802154_ca_set_scheduler() - Set the scheduler responsible for managing + * the schedule, and configure its parameters. + * @local: MCPS private data. + * @name: Scheduler name. + * @params_attr: Nested attribute containing region parameters. May be NULL. + * @extack: Extended ACK report structure. + * + * FSM mutex should be locked. + * + * Device should not be started for the moment. + * + * Return: 0 or error. + */ +int mcps802154_ca_set_scheduler(struct mcps802154_local *local, + const char *name, + const struct nlattr *params_attr, + struct netlink_ext_ack *extack); + +/** + * mcps802154_ca_set_region() - Set scheduler's region. + * @local: MCPS private data. + * @scheduler_name: Scheduler name. + * @region_id: Identifier of the region, scheduler specific. + * @region_name: Name of region to attach to the scheduler. + * @params_attr: Nested attribute containing region parameters. + * @extack: Extended ACK report structure. + * + * FSM mutex should be locked. + * + * Return: 0 or error. + */ +int mcps802154_ca_set_region(struct mcps802154_local *local, + const char *scheduler_name, u32 region_id, + const char *region_name, + const struct nlattr *params_attr, + struct netlink_ext_ack *extack); + +/** + * mcps802154_ca_scheduler_set_parameters() - Set the scheduler parameters. + * @local: MCPS private data. + * @name: Scheduler name. + * @params_attr: Nested attribute containing region parameters. + * @extack: Extended ACK report structure. + * + * FSM mutex should be locked. + * + * Return: 0 or error. + */ +int mcps802154_ca_scheduler_set_parameters(struct mcps802154_local *local, + const char *name, + const struct nlattr *params_attr, + struct netlink_ext_ack *extack); + +/** + * mcps802154_ca_scheduler_call() - Call scheduler specific procedure. + * @local: MCPS private data. + * @scheduler_name: Scheduler name. + * @call_id: Identifier of the procedure, scheduler specific. + * @params_attr: Nested attribute containing procedure parameters. + * @info: Request information. + * + * FSM mutex should be locked. + * + * Return: 0 or error. + */ +int mcps802154_ca_scheduler_call(struct mcps802154_local *local, + const char *scheduler_name, u32 call_id, + const struct nlattr *params_attr, + const struct genl_info *info); + +/** + * mcps802154_ca_set_region_parameters() - Set the region parameters. + * @local: MCPS private data. + * @scheduler_name: Scheduler name. + * @region_id: Identifier of the region, scheduler specific. + * @region_name: Name of the region to call. + * @params_attr: Nested attribute containing region parameters. + * @extack: Extended ACK report structure. + * + * FSM mutex should be locked. + * + * Return: 0 or error. + */ +int mcps802154_ca_set_region_parameters(struct mcps802154_local *local, + const char *scheduler_name, + u32 region_id, const char *region_name, + const struct nlattr *params_attr, + struct netlink_ext_ack *extack); + +/** + * mcps802154_ca_call_region() - Call region specific procedure. + * @local: MCPS private data. + * @scheduler_name: Scheduler name. + * @region_id: Identifier of the region, scheduler specific. + * @region_name: Name of the region to call. + * @call_id: Identifier of the procedure, region specific. + * @params_attr: Nested attribute containing procedure parameters. + * @info: Request information. + * + * FSM mutex should be locked. + * + * Return: 0 or error. + */ +int mcps802154_ca_call_region(struct mcps802154_local *local, + const char *scheduler_name, u32 region_id, + const char *region_name, u32 call_id, + const struct nlattr *params_attr, + const struct genl_info *info); + +/** + * mcps802154_ca_xmit_skb() - Transmit the buffer through the first region + * that accepts it. + * @local: MCPS private data. + * @skb: Buffer to be transmitted. + * + * Return: 0 or error. + */ +int mcps802154_ca_xmit_skb(struct mcps802154_local *local, struct sk_buff *skb); + +/** + * mcps802154_ca_get_access() - Compute and return access. + * @local: MCPS private data. + * @next_timestamp_dtu: Date of next access opportunity. + * + * Return: A pointer to current access. + */ +struct mcps802154_access * +mcps802154_ca_get_access(struct mcps802154_local *local, + u32 next_timestamp_dtu); + +/** + * mcps802154_ca_may_reschedule() - If needed, request FProc to change access. + * @local: MCPS private data. + * + * FSM mutex should be locked. + * + * When something has changed that could impact the current access, this + * function should be called to evaluate the change and notify FProc. This + * should be done for example when a new frame is queued. + */ +void mcps802154_ca_may_reschedule(struct mcps802154_local *local); + +/** + * mcps802154_ca_access_hold() - Prevent any reschedule until access is done. + * @local: MCPS private data. + */ +void mcps802154_ca_access_hold(struct mcps802154_local *local); + +/** + * mcps802154_ca_invalidate_schedule() - Invalidate the schedule and force update. + * @local: MCPS private data. + * + * FSM mutex should be locked. + * + * When something has changed that impact the current schedule, this function + * can be called to invalidate the schedule and force an update. + * The update will be done after the end of current access. + * This API should be called for example when a region parameter changes. + */ +void mcps802154_ca_invalidate_schedule(struct mcps802154_local *local); + +#endif /* NET_MCPS802154_CA_H */ diff --git a/mac/default_region.c b/mac/default_region.c new file mode 100644 index 0000000..197a7ab --- /dev/null +++ b/mac/default_region.c @@ -0,0 +1,175 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/netdevice.h> + +#include <net/mcps802154_schedule.h> + +#include "mcps802154_i.h" + +#include "default_region.h" +#include "warn_return.h" + +/** + * MCPS802154_DEFAULT_REGION_QUEUE_SIZE - number of buffers in the queue. + */ +#define MCPS802154_DEFAULT_REGION_QUEUE_SIZE 2 + +static struct mcps802154_region_ops default_region_ops; + +static void +mcps802154_default_rx_frame(struct mcps802154_access *access, int frame_idx, + struct sk_buff *skb, + const struct mcps802154_rx_frame_info *info, + enum mcps802154_rx_error_type error) +{ + struct default_local *local = access_to_local(access); + + mcps802154_region_rx_skb(local->llhw, &local->region, skb, info->lqi); +} + +static struct sk_buff * +mcps802154_default_tx_frame(struct mcps802154_access *access, int frame_idx) +{ + struct default_local *local = access_to_local(access); + + return skb_dequeue(&local->queue); +} + +static void +mcps802154_default_tx_return(struct mcps802154_access *access, int frame_idx, + struct sk_buff *skb, + enum mcps802154_access_tx_return_reason reason) +{ + struct default_local *local = access_to_local(access); + struct mcps802154_local *mlocal = llhw_to_local(local->llhw); + + if (reason == MCPS802154_ACCESS_TX_RETURN_REASON_FAILURE) { + local->retries++; + if (local->retries <= mlocal->pib.mac_max_frame_retries) { + /* Retry the frame. */ + skb_queue_head(&local->queue, skb); + } else { + local->retries = 0; + mcps802154_region_xmit_done(local->llhw, &local->region, + skb, false); + atomic_dec(&local->n_queued); + } + } else if (reason == MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL) { + skb_queue_head(&local->queue, skb); + } else { + local->retries = 0; + mcps802154_region_xmit_done(local->llhw, &local->region, skb, + true); + atomic_dec(&local->n_queued); + } +} + +struct mcps802154_access_ops default_access_ops = { + .rx_frame = mcps802154_default_rx_frame, + .tx_get_frame = mcps802154_default_tx_frame, + .tx_return = mcps802154_default_tx_return, +}; + +static struct mcps802154_region * +mcps802154_default_open(struct mcps802154_llhw *llhw) +{ + struct default_local *local; + + local = kmalloc(sizeof(*local), GFP_KERNEL); + if (!local) + return NULL; + + local->llhw = llhw; + local->region.ops = &default_region_ops; + skb_queue_head_init(&local->queue); + atomic_set(&local->n_queued, 0); + local->retries = 0; + return &local->region; +} + +static void mcps802154_default_close(struct mcps802154_region *region) +{ + struct default_local *local = region_to_local(region); + + skb_queue_purge(&local->queue); + atomic_set(&local->n_queued, 0); + kfree(local); +} + +static void mcps802154_default_notify_stop(struct mcps802154_region *region) +{ + struct default_local *local = region_to_local(region); + + skb_queue_purge(&local->queue); + atomic_set(&local->n_queued, 0); +} + +static struct mcps802154_access * +mcps802154_default_get_access(struct mcps802154_region *region, + u32 next_timestamp_dtu, int next_in_region_dtu, + int region_duration_dtu) +{ + struct default_local *local = region_to_local(region); + + local->access.method = skb_queue_empty(&local->queue) ? + MCPS802154_ACCESS_METHOD_IMMEDIATE_RX : + MCPS802154_ACCESS_METHOD_IMMEDIATE_TX; + local->access.ops = &default_access_ops; + return &local->access; +} + +static int mcps802154_default_xmit_skb(struct mcps802154_region *region, + struct sk_buff *skb) +{ + struct default_local *local = region_to_local(region); + int n_queued; + + skb_queue_tail(&local->queue, skb); + n_queued = atomic_inc_return(&local->n_queued); + if (n_queued < MCPS802154_DEFAULT_REGION_QUEUE_SIZE) + mcps802154_region_xmit_resume(local->llhw, &local->region, 0); + + return 1; +} + +static struct mcps802154_region_ops default_region_ops = { + .owner = THIS_MODULE, + .name = "default", + .open = mcps802154_default_open, + .close = mcps802154_default_close, + .notify_stop = mcps802154_default_notify_stop, + .get_access = mcps802154_default_get_access, + .xmit_skb = mcps802154_default_xmit_skb +}; + +int __init mcps802154_default_region_init(void) +{ + return mcps802154_region_register(&default_region_ops); +} + +void __exit mcps802154_default_region_exit(void) +{ + mcps802154_region_unregister(&default_region_ops); +} diff --git a/mac/default_region.h b/mac/default_region.h new file mode 100644 index 0000000..d399673 --- /dev/null +++ b/mac/default_region.h @@ -0,0 +1,75 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_DEFAULT_REGION_H +#define NET_MCPS802154_DEFAULT_REGION_H + +#include <net/mcps802154_schedule.h> + +/** + * struct default_local - Local context. + */ +struct default_local { + /** + * @region: Region instance returned to MCPS. + */ + struct mcps802154_region region; + /** + * @llhw: Low-level device pointer. + */ + struct mcps802154_llhw *llhw; + /** + * @access: Access returned to MCPS. + */ + struct mcps802154_access access; + /** + * @queue: Queue of frames to be transmitted. + */ + struct sk_buff_head queue; + /** + * @n_queued: Number of queued frames. This also includes frame being + * transmitted which is no longer in &queue. + */ + atomic_t n_queued; + /** + * @retries: Number of retries done on the current tx frame. + */ + int retries; +}; + +static inline struct default_local * +region_to_local(struct mcps802154_region *region) +{ + return container_of(region, struct default_local, region); +} + +static inline struct default_local * +access_to_local(struct mcps802154_access *access) +{ + return container_of(access, struct default_local, access); +} + +int mcps802154_default_region_init(void); +void mcps802154_default_region_exit(void); + +#endif /* NET_MCPS802154_DEFAULT_REGION_H */ diff --git a/mac/endless_scheduler.c b/mac/endless_scheduler.c new file mode 100644 index 0000000..76ee7f8 --- /dev/null +++ b/mac/endless_scheduler.c @@ -0,0 +1,139 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> + +#include <net/mcps802154_schedule.h> + +#include "endless_scheduler.h" +#include "warn_return.h" + +struct mcps802154_endless_local { + struct mcps802154_scheduler scheduler; + struct mcps802154_llhw *llhw; +}; + +static inline struct mcps802154_endless_local * +scheduler_to_plocal(const struct mcps802154_scheduler *scheduler) +{ + return container_of(scheduler, struct mcps802154_endless_local, + scheduler); +} + +static struct mcps802154_scheduler * +mcps802154_endless_scheduler_open(struct mcps802154_llhw *llhw) +{ + struct mcps802154_endless_local *plocal; + + plocal = kmalloc(sizeof(*plocal), GFP_KERNEL); + if (!plocal) + return NULL; + plocal->llhw = llhw; + plocal->scheduler.n_regions = 1; + return &plocal->scheduler; +} + +static void +mcps802154_endless_scheduler_close(struct mcps802154_scheduler *scheduler) +{ + struct mcps802154_endless_local *plocal = + scheduler_to_plocal(scheduler); + + kfree(plocal); +} +static int mcps802154_endless_scheduler_update_schedule( + struct mcps802154_scheduler *scheduler, + const struct mcps802154_schedule_update *schedule_update, + u32 next_timestamp_dtu) +{ + struct mcps802154_endless_local *plocal = + scheduler_to_plocal(scheduler); + struct list_head *regions = NULL; + struct mcps802154_region *region; + int r, n_regions; + + n_regions = mcps802154_schedule_get_regions(plocal->llhw, ®ions); + if (!n_regions) + return -ENOENT; + WARN_RETURN_ON(n_regions > 1, -EINVAL); + + region = list_first_entry(regions, typeof(*region), ca_entry); + + r = mcps802154_schedule_set_start( + schedule_update, schedule_update->expected_start_timestamp_dtu); + /* Can not fail, only possible error is invalid parameters. */ + WARN_RETURN(r); + + r = mcps802154_schedule_recycle(schedule_update, 0, + MCPS802154_DURATION_NO_CHANGE); + /* Can not fail, only possible error is invalid parameters. */ + WARN_RETURN(r); + + r = mcps802154_schedule_add_region(schedule_update, region, 0, 0, + false); + + return r; +} + +static struct mcps802154_scheduler_ops mcps802154_endless_scheduler_scheduler = { + .owner = THIS_MODULE, + .name = "endless", + .open = mcps802154_endless_scheduler_open, + .close = mcps802154_endless_scheduler_close, + .update_schedule = mcps802154_endless_scheduler_update_schedule, +}; + +static struct mcps802154_scheduler_ops mcps802154_default_scheduler_scheduler = { + .owner = THIS_MODULE, + .name = "default", + .open = mcps802154_endless_scheduler_open, + .close = mcps802154_endless_scheduler_close, + .update_schedule = mcps802154_endless_scheduler_update_schedule, +}; + +int __init mcps802154_endless_scheduler_init(void) +{ + return mcps802154_scheduler_register( + &mcps802154_endless_scheduler_scheduler); +} + +void __exit mcps802154_endless_scheduler_exit(void) +{ + mcps802154_scheduler_unregister( + &mcps802154_endless_scheduler_scheduler); +} + +int __init mcps802154_default_scheduler_init(void) +{ + return mcps802154_scheduler_register( + &mcps802154_default_scheduler_scheduler); +} + +void __exit mcps802154_default_scheduler_exit(void) +{ + mcps802154_scheduler_unregister( + &mcps802154_default_scheduler_scheduler); +} diff --git a/mac/endless_scheduler.h b/mac/endless_scheduler.h new file mode 100644 index 0000000..254612c --- /dev/null +++ b/mac/endless_scheduler.h @@ -0,0 +1,33 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_ENDLESS_SCHEDULER_H +#define NET_MCPS802154_ENDLESS_SCHEDULER_H + +int mcps802154_endless_scheduler_init(void); +void mcps802154_endless_scheduler_exit(void); + +int mcps802154_default_scheduler_init(void); +void mcps802154_default_scheduler_exit(void); + +#endif /* NET_MCPS802154_ENDLESS_SCHEDULER_H */ diff --git a/mac/fira_access.c b/mac/fira_access.c new file mode 100644 index 0000000..083a76f --- /dev/null +++ b/mac/fira_access.c @@ -0,0 +1,1205 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include "fira_round_hopping_sequence.h" +#include "fira_access.h" +#include "fira_session.h" +#include "fira_frame.h" +#include "fira_trace.h" +#include "fira_sts.h" + +#include <asm/unaligned.h> +#include <linux/string.h> +#include <linux/ieee802154.h> +#include <linux/math64.h> +#include <linux/limits.h> +#include <linux/errno.h> +#include <net/mcps802154_frame.h> + +#include "warn_return.h" + +#define FIRA_STS_FOM_THRESHOLD 153 +#define FIRA_RSSI_MAX 0xff + +/** + * sat_fp() - Saturate the range of fixed-point + * @x: fixed-point value on s32. + * + * Return: value saturate to s16 range + */ +static s16 sat_fp(s32 x) +{ + if (x > S16_MAX) + return S16_MAX; + else if (x < S16_MIN) + return S16_MIN; + else + return x; +} + +/** + * map_q11_to_2pi() - Map a Fixed Point angle to an signed 16 bit interger + * @x: angle as Q11 fixed_point value in range [-PI, PI] + * + * Return: the angle mapped to [INT16_MIN, INT16_MAX] + */ +static s16 map_q11_to_2pi(s16 x) +{ + const s16 pi = 6434; /* Same as round(M_PI * (1 << 11)). */ + + s32 temp = (s32)x * S16_MAX; + temp /= pi; + + return sat_fp(temp); +} + +/** + * fira_access_setup_frame() - Fill an access frame from a FiRa slot. + * @local: FiRa context. + * @session: Session. + * @frame: Access frame. + * @sts_params: Where to store STS parameters. + * @slot: Corresponding slot. + * @frame_dtu: frame transmission or reception date. + * @is_tx: true for TX. + */ +static void fira_access_setup_frame(struct fira_local *local, + struct fira_session *session, + struct mcps802154_access_frame *frame, + struct mcps802154_sts_params *sts_params, + const struct fira_slot *slot, u32 frame_dtu, + bool is_tx) +{ + const struct fira_session_params *params = &session->params; + struct mcps802154_access *access = &local->access; + struct mcps802154_sts_params *sts_params_for_access = NULL; + int rframe_config = session->params.rframe_config; + + const struct fira_measurement_sequence_step *current_ms_step = + fira_session_get_meas_seq_step(session); + + bool is_rframe = slot->message_id <= FIRA_MESSAGE_ID_RFRAME_MAX; + bool is_last_rframe = slot->message_id == FIRA_MESSAGE_ID_RANGING_FINAL; + bool is_first_frame = slot->message_id == FIRA_MESSAGE_ID_CONTROL; + bool request_rssi = session->params.report_rssi; + if (is_rframe) { + fira_sts_get_sts_params(session, slot, sts_params->v, + sizeof(sts_params->v), sts_params->key, + sizeof(sts_params->key)); + sts_params->n_segs = params->number_of_sts_segments; + sts_params->seg_len = + params->sts_length == FIRA_STS_LENGTH_128 ? + 128 : + params->sts_length == FIRA_STS_LENGTH_32 ? 32 : + 64; + sts_params->sp2_tx_gap_4chips = 0; + sts_params->sp2_rx_gap_4chips[0] = 0; + sts_params->sp2_rx_gap_4chips[1] = 0; + sts_params->sp2_rx_gap_4chips[2] = 0; + sts_params->sp2_rx_gap_4chips[3] = 0; + sts_params_for_access = sts_params; + } + + if (is_tx) { + u8 flags = MCPS802154_TX_FRAME_CONFIG_TIMESTAMP_DTU; + + /* Add a small margin to the Tx timestamps. */ + if (!is_first_frame) + frame_dtu += FIRA_TX_MARGIN_US * + local->llhw->dtu_freq_hz / 1000000; + + if (is_rframe) { + struct fira_ranging_info *ranging_info; + + ranging_info = + &local->ranging_info[slot->ranging_index]; + ranging_info->timestamps_rctu[slot->message_id] = + mcps802154_tx_timestamp_dtu_to_rmarker_rctu( + local->llhw, frame_dtu, + access->hrp_uwb_params, access->channel, + slot->tx_ant_set); + + flags |= MCPS802154_TX_FRAME_CONFIG_RANGING; + if (rframe_config == FIRA_RFRAME_CONFIG_SP3) + flags |= MCPS802154_TX_FRAME_CONFIG_SP3; + else + flags |= MCPS802154_TX_FRAME_CONFIG_SP1; + if (!is_last_rframe) + flags |= + MCPS802154_TX_FRAME_CONFIG_KEEP_RANGING_CLOCK; + } else if (is_first_frame) { + flags |= MCPS802154_TX_FRAME_CONFIG_RANGING_ROUND; + } + *frame = (struct mcps802154_access_frame){ + .is_tx = true, + .tx_frame_config = { + .timestamp_dtu = frame_dtu, + .flags = flags, + .ant_set_id = slot->tx_ant_set, + }, + .sts_params = sts_params_for_access, + }; + } else { + u8 flags = MCPS802154_RX_FRAME_CONFIG_TIMESTAMP_DTU; + u16 request = 0; + + if (request_rssi) + request |= MCPS802154_RX_FRAME_INFO_RSSI; + if (is_rframe) { + flags |= MCPS802154_RX_FRAME_CONFIG_RANGING; + if (rframe_config == FIRA_RFRAME_CONFIG_SP3) + flags |= MCPS802154_RX_FRAME_CONFIG_SP3; + else + flags |= MCPS802154_RX_FRAME_CONFIG_SP1; + if (!is_last_rframe) + flags |= + MCPS802154_RX_FRAME_CONFIG_KEEP_RANGING_CLOCK; + request |= MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU | + MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM; + if (current_ms_step->type != + FIRA_MEASUREMENT_TYPE_RANGE) { + flags |= + MCPS802154_RX_FRAME_CONFIG_RANGING_PDOA; + request |= + MCPS802154_RX_FRAME_INFO_RANGING_PDOA | + MCPS802154_RX_FRAME_INFO_RANGING_PDOA_FOM; + } + if (params->ranging_round_usage == + FIRA_RANGING_ROUND_USAGE_SSTWR && + session->params.device_type == + FIRA_DEVICE_TYPE_CONTROLEE) + request |= + MCPS802154_RX_FRAME_INFO_RANGING_OFFSET; + } + *frame = (struct mcps802154_access_frame){ + .is_tx = false, + .rx = { + .frame_config = { + .timestamp_dtu = frame_dtu, + .flags = flags, + .ant_set_id = slot->rx_ant_set, + }, + .frame_info_flags_request = request, + }, + .sts_params = sts_params_for_access, + }; + } +} + +static void fira_controlee_resync(struct fira_session *session, + u32 phy_sts_index, u32 timestamp_dtu) +{ + u32 block_idx, round_idx, slot_idx; + int block_start_timestamp_dtu; + const struct fira_session_params *params = &session->params; + + fira_sts_convert_phy_sts_idx_to_time_indexes( + session, phy_sts_index, &block_idx, &round_idx, &slot_idx); + block_start_timestamp_dtu = + timestamp_dtu - + (round_idx * session->params.round_duration_slots + slot_idx) * + params->slot_duration_dtu; + + /* Update the session. */ + session->block_start_dtu = block_start_timestamp_dtu; + session->block_index = block_idx; + session->round_index = round_idx; + session->controlee.synchronised = true; + session->controlee.next_round_index_valid = false; + session->controlee.block_index_sync = block_idx; +} + +static bool fira_rx_sts_good(struct fira_local *local, + const struct mcps802154_rx_frame_info *info) +{ + int i; + if (!(info->flags & MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM)) + return false; + for (i = 0; i < MCPS802154_STS_N_SEGS_MAX; i++) { + if (info->ranging_sts_fom[i] < FIRA_STS_FOM_THRESHOLD) + return false; + } + return true; +} + +static void fira_ranging_info_set_status(const struct fira_session *session, + struct fira_ranging_info *ranging_info, + enum fira_ranging_status status, + u8 slot_index) +{ + /* Report first error. */ + if (ranging_info->status) + return; + ranging_info->status = status; + ranging_info->slot_index = slot_index; + fira_session_set_range_data_ntf_status(session, ranging_info); +} + +static void +fira_diagnostic_rssis(const struct mcps802154_rx_measurement_info *info, + struct fira_diagnostic *diagnostic) +{ + if (info->flags & MCPS802154_RX_MEASUREMENTS_RSSIS) { + int max = max(MCPS802154_RSSIS_N_MAX - 1, info->n_rssis); + int i; + for (i = 0; i < max; i++) + diagnostic->rssis_q1[i] = info->rssis_q1[i]; + diagnostic->n_rssis = i; + } +} + +static void +fira_diagnostic_aoas(const struct mcps802154_rx_measurement_info *info, + struct fira_diagnostic *diagnostic) +{ + int max = max(MCPS802154_RX_AOA_MEASUREMENTS_MAX - 1, info->n_aoas); + int i; + + for (i = 0; i < max; i++) + diagnostic->aoas[i] = info->aoas[i]; + diagnostic->n_aoas = info->n_aoas; +} + +static struct mcps802154_rx_cir * +fira_diagnostic_cirs_alloc(const struct mcps802154_rx_measurement_info *info) +{ + const struct mcps802154_rx_cir_sample_window *si; + struct mcps802154_rx_cir_sample_window *so; + struct mcps802154_rx_cir *cirs; + int i; + int j; + + cirs = kmalloc(info->n_cirs * sizeof(struct mcps802154_rx_cir), + GFP_KERNEL); + if (!cirs) + return NULL; + + for (i = 0; i < info->n_cirs; i++) { + so = &cirs[i].sample_window; + si = &info->cirs[i].sample_window; + so->samples = + kmalloc(si->n_samples * si->sizeof_sample, GFP_KERNEL); + + if (!so->samples) + goto failed; + } + return cirs; + +failed: + for (j = 0; j < i; j++) + kfree(cirs[j].sample_window.samples); + kfree(cirs); + return NULL; +} + +static void +fira_diagnostic_cirs_copy(const struct mcps802154_rx_measurement_info *info, + struct fira_diagnostic *diagnostic) +{ + int i; + + for (i = 0; i < info->n_cirs; i++) { + struct mcps802154_rx_cir *cir_in; + struct mcps802154_rx_cir *cir_out; + struct mcps802154_rx_cir_sample_window *si; + struct mcps802154_rx_cir_sample_window *so; + + cir_out = &diagnostic->cirs[i]; + cir_in = &info->cirs[i]; + so = &cir_out->sample_window; + si = &cir_in->sample_window; + + cir_out->fp_index = cir_in->fp_index; + cir_out->fp_snr = cir_in->fp_snr; + cir_out->fp_ns_q6 = cir_in->fp_ns_q6; + cir_out->pp_index = cir_in->pp_index; + cir_out->pp_snr = cir_in->pp_snr; + cir_out->pp_ns_q6 = cir_in->pp_ns_q6; + cir_out->fp_sample_offset = cir_in->fp_sample_offset; + so->n_samples = si->n_samples; + so->sizeof_sample = si->sizeof_sample; + + memcpy(so->samples, si->samples, + si->n_samples * si->sizeof_sample); + } + diagnostic->n_cirs = i; +} + +static void +fira_diagnostic_cirs(const struct mcps802154_rx_measurement_info *info, + struct fira_diagnostic *diagnostic) +{ + if (info->flags & MCPS802154_RX_MEASUREMENTS_CIRS) { + diagnostic->cirs = fira_diagnostic_cirs_alloc(info); + if (diagnostic->cirs) + fira_diagnostic_cirs_copy(info, diagnostic); + else + diagnostic->n_cirs = 0; + } +} + +static void fira_diagnostic(struct fira_local *local, + struct fira_session *session, void *rx_ctx, + int slot_idx) +{ + const struct fira_session_params *params = &session->params; + struct fira_diagnostic *diagnostic = &local->diagnostics[slot_idx]; + struct mcps802154_rx_measurement_info info = {}; + int r; + + WARN_ON(diagnostic->cirs); + + if (params->diagnostic_report_flags & + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_RSSIS) + info.flags |= MCPS802154_RX_MEASUREMENTS_RSSIS; + if (params->diagnostic_report_flags & + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_CIRS) + info.flags |= MCPS802154_RX_MEASUREMENTS_CIRS; + + if (!info.flags) + return; + + r = mcps802154_rx_get_measurement(local->llhw, rx_ctx, &info); + + if (r) + return; + + fira_diagnostic_rssis(&info, diagnostic); + fira_diagnostic_cirs(&info, diagnostic); +} + +static void fira_diagnostic_free(struct fira_local *local) +{ + int i; + + for (i = 0; i < local->access.n_frames; i++) { + struct fira_diagnostic *diagnostic = &local->diagnostics[i]; + int j; + + for (j = 0; j < diagnostic->n_cirs; j++) + kfree(diagnostic->cirs[j].sample_window.samples); + + kfree(diagnostic->cirs); + diagnostic->cirs = NULL; + diagnostic->n_cirs = 0; + } +} + +static void fira_rx_frame_ranging(struct fira_local *local, + const struct fira_slot *slot, + struct sk_buff *skb, + const struct mcps802154_rx_frame_info *info) +{ + const struct fira_session *session = local->current_session; + const struct fira_session_params *params = &session->params; + struct fira_ranging_info *ranging_info = + &local->ranging_info[slot->ranging_index]; + struct mcps802154_ie_get_context ie_get = {}; + bool pdoa_info_present; + + if (!fira_rx_sts_good(local, info)) { + fira_ranging_info_set_status( + session, ranging_info, + FIRA_STATUS_RANGING_RX_PHY_STS_FAILED, slot->index); + return; + } + + if (!(info->flags & MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU)) { + fira_ranging_info_set_status( + session, ranging_info, + FIRA_STATUS_RANGING_RX_PHY_TOA_FAILED, slot->index); + return; + } + ranging_info->timestamps_rctu[slot->message_id] = info->timestamp_rctu; + + pdoa_info_present = info->flags & MCPS802154_RX_FRAME_INFO_RANGING_PDOA; + + if (pdoa_info_present) { + struct fira_local_aoa_info *local_aoa; + bool pdoa_fom_info_present = + info->flags & MCPS802154_RX_FRAME_INFO_RANGING_PDOA_FOM; + s16 local_pdoa_q11 = 0; + s16 local_aoa_q11 = 0; + const struct fira_measurement_sequence_step *current_step = + fira_session_get_meas_seq_step(session); + struct mcps802154_rx_measurement_info meas_info = {}; + int r; + + meas_info.flags |= MCPS802154_RX_MEASUREMENTS_AOAS; + r = mcps802154_rx_get_measurement( + local->llhw, ranging_info->rx_ctx, &meas_info); + + if (!r && meas_info.flags & MCPS802154_RX_MEASUREMENTS_AOAS && + meas_info.n_aoas) { + struct fira_diagnostic *diagnostic = + &local->diagnostics[slot->index]; + + /* TODO: Find which aoas index to use. */ + local_pdoa_q11 = meas_info.aoas[0].pdoa_rad_q11; + local_aoa_q11 = meas_info.aoas[0].aoa_rad_q11; + if (params->diagnostic_report_flags & + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_AOAS) + fira_diagnostic_aoas(&meas_info, diagnostic); + } + + switch (current_step->type) { + case FIRA_MEASUREMENT_TYPE_AOA: + local_aoa = &ranging_info->local_aoa; + break; + case FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH: + local_aoa = &ranging_info->local_aoa_azimuth; + break; + case FIRA_MEASUREMENT_TYPE_AOA_ELEVATION: + local_aoa = &ranging_info->local_aoa_elevation; + break; + case FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH_ELEVATION: + local_aoa = (slot->message_id == + FIRA_MESSAGE_ID_RANGING_FINAL) ? + &ranging_info->local_aoa_elevation : + &ranging_info->local_aoa_azimuth; + break; + default: /* LCOV_EXCL_START */ + local_aoa = NULL; + /* LCOV_EXCL_STOP */ + } + + /* LCOV_EXCL_START */ + if (local_aoa) { + /* LCOV_EXCL_STOP */ + local_aoa->present = true; + local_aoa->rx_ant_set = slot->rx_ant_set; + local_aoa->pdoa_2pi = map_q11_to_2pi(local_pdoa_q11); + local_aoa->aoa_2pi = map_q11_to_2pi(local_aoa_q11); + /* LCOV_EXCL_START */ + /* FoM is always expected when PDoA present. */ + if (pdoa_fom_info_present) + /* LCOV_EXCL_STOP */ + local_aoa->aoa_fom = info->ranging_pdoa_fom; + } + } + + if (info->flags & MCPS802154_RX_FRAME_INFO_RANGING_OFFSET) { + ranging_info->clock_offset_q26 = + div64_s64((s64)info->ranging_offset_rctu << 26, + info->ranging_tracking_interval_rctu); + ranging_info->clock_offset_present = true; + } + + if (skb) { + if (fira_frame_header_check_decrypt(local, slot, skb, + &ie_get) || + !fira_frame_rframe_payload_check(local, slot, skb, + &ie_get)) { + fira_ranging_info_set_status( + session, ranging_info, + FIRA_STATUS_RANGING_RX_MAC_IE_DEC_FAILED, + slot->index); + } + } +} + +static void fira_rx_frame_control(struct fira_local *local, + const struct fira_slot *slot, + struct sk_buff *skb, + const struct mcps802154_rx_frame_info *info) +{ + struct mcps802154_access *access = &local->access; + struct fira_ranging_info *ri = + &local->ranging_info[slot->ranging_index]; + struct mcps802154_ie_get_context ie_get = {}; + const struct fira_session_params *params = NULL; + struct fira_session *session; + int header_len; + __le16 src_short_addr; + int last_slot_index = 0; + int offset_in_access_duration_dtu; + int left_duration_dtu; + unsigned n_slots; + u32 phy_sts_index; + u8 *header; + int r; + + if (!(info->flags & MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU)) { + fira_ranging_info_set_status( + local->current_session, ri, + FIRA_STATUS_RANGING_RX_PHY_DEC_FAILED, slot->index); + return; + } + + offset_in_access_duration_dtu = + info->timestamp_dtu - access->timestamp_dtu; + + /* Read the header to capture the session context. */ + header = skb->data; + session = fira_rx_frame_control_header_check(local, slot, skb, &ie_get, + &phy_sts_index); + if (!session) + goto failed; + + fira_controlee_resync(session, phy_sts_index, info->timestamp_dtu); + + params = &session->params; + ri->rx_ctx = session->rx_ctx[0]; + + header_len = skb->data - header; + src_short_addr = slot->controller_tx ? local->dst_short_addr : + slot->controlee->short_addr; + + /* Continue to decode the frame. */ + r = fira_sts_decrypt_frame(session, slot, skb, header_len, src_short_addr); + if (r) + goto failed; + r = fira_frame_control_payload_check(local, skb, &ie_get, &n_slots, + &session->stop_inband, + &session->block_stride_len); + if (!r) + goto failed; + + left_duration_dtu = + access->duration_dtu - offset_in_access_duration_dtu; + + /* + * The RCM has been received, remaining slots are: n_slots - 1. + * Stop if no time left to finish the ranging or if asked to. + */ + if (left_duration_dtu < (n_slots - 1) * params->slot_duration_dtu || + session->stop_inband) { + n_slots = 1; + } else { + int i; + + for (i = 1; i < n_slots; i++) { + const struct fira_slot *slot = &local->slots[i]; + struct mcps802154_access_frame *frame = + &local->frames[i]; + struct mcps802154_sts_params *sts_params = + &local->sts_params[i]; + bool is_tx; + u32 frame_dtu; + + is_tx = !slot->controller_tx; + frame_dtu = info->timestamp_dtu + + params->slot_duration_dtu * slot->index; + last_slot_index = slot->index; + + fira_access_setup_frame(local, session, frame, + sts_params, slot, frame_dtu, + is_tx); + } + } + + /* Trace the new (or not) session context and slots received. */ + trace_region_fira_rx_frame_control(local, session, left_duration_dtu, + n_slots); + /* Update the access. */ + access->duration_dtu = + offset_in_access_duration_dtu + + (last_slot_index + 1) * params->slot_duration_dtu; + access->n_frames = n_slots; + return; + +failed: + session = local->current_session; + params = &local->current_session->params; + access->duration_dtu = + offset_in_access_duration_dtu + params->slot_duration_dtu; + fira_ranging_info_set_status(session, ri, + FIRA_STATUS_RANGING_RX_MAC_IE_DEC_FAILED, + slot->index); +} + +static void +fira_rx_frame_control_update(struct fira_local *local, + const struct fira_slot *slot, struct sk_buff *skb, + const struct mcps802154_rx_frame_info *info) +{ + struct fira_ranging_info *ranging_info = + &local->ranging_info[slot->ranging_index]; + struct mcps802154_ie_get_context ie_get = {}; + + if (fira_frame_header_check_decrypt(local, slot, skb, &ie_get)) + goto failed; + + return; +failed: + fira_ranging_info_set_status(local->current_session, ranging_info, + FIRA_STATUS_RANGING_RX_MAC_IE_DEC_FAILED, + slot->index); +} + +static void fira_rx_frame_measurement_report( + struct fira_local *local, const struct fira_slot *slot, + struct sk_buff *skb, const struct mcps802154_rx_frame_info *info) +{ + struct fira_ranging_info *ranging_info = + &local->ranging_info[slot->ranging_index]; + struct mcps802154_ie_get_context ie_get = {}; + + if (fira_frame_header_check_decrypt(local, slot, skb, &ie_get)) + goto failed; + + if (!fira_frame_measurement_report_payload_check(local, slot, skb, + &ie_get)) + goto failed; + + return; +failed: + fira_ranging_info_set_status(local->current_session, ranging_info, + FIRA_STATUS_RANGING_RX_MAC_IE_DEC_FAILED, + slot->index); +} + +static void +fira_rx_frame_result_report(struct fira_local *local, + const struct fira_slot *slot, struct sk_buff *skb, + const struct mcps802154_rx_frame_info *info) +{ + struct fira_ranging_info *ranging_info = + &local->ranging_info[slot->ranging_index]; + struct mcps802154_ie_get_context ie_get = {}; + + if (fira_frame_header_check_decrypt(local, slot, skb, &ie_get)) + goto failed; + + if (!fira_frame_result_report_payload_check(local, slot, skb, &ie_get)) + goto failed; + + return; +failed: + fira_ranging_info_set_status(local->current_session, ranging_info, + FIRA_STATUS_RANGING_RX_MAC_IE_DEC_FAILED, + slot->index); +} + +static bool fira_do_process_rx_frame(const struct fira_session *session, + enum mcps802154_rx_error_type error, + struct fira_ranging_info *ranging_info, + u8 slot_index) +{ + enum fira_ranging_status status = FIRA_STATUS_RANGING_INTERNAL_ERROR; + + switch (error) { + case MCPS802154_RX_ERROR_NONE: + return true; + case MCPS802154_RX_ERROR_SFD_TIMEOUT: + case MCPS802154_RX_ERROR_TIMEOUT: + case MCPS802154_RX_ERROR_HPDWARN: + status = FIRA_STATUS_RANGING_RX_TIMEOUT; + break; + case MCPS802154_RX_ERROR_FILTERED: + case MCPS802154_RX_ERROR_BAD_CKSUM: + status = FIRA_STATUS_RANGING_RX_MAC_DEC_FAILED; + break; + case MCPS802154_RX_ERROR_UNCORRECTABLE: + case MCPS802154_RX_ERROR_OTHER: + case MCPS802154_RX_ERROR_PHR_DECODE: + status = FIRA_STATUS_RANGING_RX_PHY_DEC_FAILED; + break; + } + fira_ranging_info_set_status(session, ranging_info, status, slot_index); + return false; +} + +static void fira_rx_frame(struct mcps802154_access *access, int frame_idx, + struct sk_buff *skb, + const struct mcps802154_rx_frame_info *info, + enum mcps802154_rx_error_type error) +{ + struct fira_local *local = access_to_local(access); + const struct fira_session_params *params; + const struct fira_slot *slot = &local->slots[frame_idx]; + struct fira_ranging_info *ri = + &local->ranging_info[slot->ranging_index]; + /* Don't initialize session before rx_frame_control. */ + struct fira_session *session; + + trace_region_fira_rx_frame(local->current_session, slot->message_id, + error); + + if (info && info->flags & MCPS802154_RX_FRAME_INFO_RSSI) { + if ((ri->n_rx_rssis + 1) > FIRA_MESSAGE_ID_MAX) + return; + + ri->rx_rssis[ri->n_rx_rssis++] = + info->rssi < FIRA_RSSI_MAX ? info->rssi : FIRA_RSSI_MAX; + } + + if (fira_do_process_rx_frame(local->current_session, error, ri, + slot->index)) { + switch (slot->message_id) { + case FIRA_MESSAGE_ID_RANGING_INITIATION: + case FIRA_MESSAGE_ID_RANGING_RESPONSE: + case FIRA_MESSAGE_ID_RANGING_FINAL: + fira_rx_frame_ranging(local, slot, skb, info); + break; + case FIRA_MESSAGE_ID_CONTROL: + fira_rx_frame_control(local, slot, skb, info); + break; + case FIRA_MESSAGE_ID_MEASUREMENT_REPORT: + fira_rx_frame_measurement_report(local, slot, skb, + info); + break; + case FIRA_MESSAGE_ID_RESULT_REPORT: + fira_rx_frame_result_report(local, slot, skb, info); + break; + case FIRA_MESSAGE_ID_CONTROL_UPDATE: + fira_rx_frame_control_update(local, slot, skb, info); + break; + default: + WARN_UNREACHABLE_DEFAULT(); + } + } + /* Current session can change after call of rx_frame_control function. */ + session = local->current_session; + session->last_access_timestamp_dtu = access->timestamp_dtu; + params = &session->params; + + kfree_skb(skb); + fira_diagnostic(local, session, ri->rx_ctx, slot->index); + + /* + * Controlee: Stop round on error. + * Controller: Stop when all ranging fails. + */ + /* + * TODO: + * The usage of ri->status is hidden in function called. + * The reason of the end of access is not limpid. + */ + if (ri->status != FIRA_STATUS_RANGING_SUCCESS) { + if (params->device_type == FIRA_DEVICE_TYPE_CONTROLEE || + (slot->message_id <= FIRA_MESSAGE_ID_RFRAME_MAX && + --local->n_ranging_valid == 0)) + access->n_frames = frame_idx + 1; + } +} + +static struct sk_buff *fira_tx_get_frame(struct mcps802154_access *access, + int frame_idx) +{ + struct fira_local *local = access_to_local(access); + struct fira_session *session = local->current_session; + const struct fira_session_params *params = &session->params; + const struct fira_slot *slot = &local->slots[frame_idx]; + struct sk_buff *skb; + int header_len; + + trace_region_fira_tx_get_frame(session, slot->message_id); + if (params->rframe_config == FIRA_RFRAME_CONFIG_SP3 && + slot->message_id <= FIRA_MESSAGE_ID_RFRAME_MAX) + return NULL; + + skb = mcps802154_frame_alloc(local->llhw, IEEE802154_MTU, GFP_KERNEL); + if (!skb) + return NULL; + + fira_frame_header_put(local, slot, skb); + + switch (slot->message_id) { + case FIRA_MESSAGE_ID_RANGING_INITIATION: + case FIRA_MESSAGE_ID_RANGING_RESPONSE: + fira_frame_rframe_payload_put(local, skb); + break; + case FIRA_MESSAGE_ID_RANGING_FINAL: + break; + case FIRA_MESSAGE_ID_CONTROL: + fira_frame_control_payload_put(local, slot, skb); + break; + case FIRA_MESSAGE_ID_MEASUREMENT_REPORT: + fira_frame_measurement_report_payload_put(local, slot, skb); + break; + case FIRA_MESSAGE_ID_RESULT_REPORT: + fira_frame_result_report_payload_put(local, slot, skb); + break; + case FIRA_MESSAGE_ID_CONTROL_UPDATE: + break; + default: /* LCOV_EXCL_START */ + kfree_skb(skb); + WARN_UNREACHABLE_DEFAULT(); + return NULL; + /* LCOV_EXCL_STOP */ + } + + header_len = mcps802154_ie_put_end(skb, false); + WARN_ON(header_len < 0); + + if (fira_sts_encrypt_frame(local->current_session, slot, skb, header_len, + local->src_short_addr)) { + kfree_skb(skb); + return NULL; + } + + return skb; +} + +static void fira_tx_return(struct mcps802154_access *access, int frame_idx, + struct sk_buff *skb, + enum mcps802154_access_tx_return_reason reason) +{ + struct fira_local *local = access_to_local(access); + struct fira_session *session = local->current_session; + int i; + + kfree_skb(skb); + + /* Error on TX. */ + trace_region_fira_tx_return(session, reason); + if (reason == MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL) { + for (i = 0; i < local->n_ranging_info; i++) { + local->ranging_info[i].status = + FIRA_STATUS_RANGING_TX_FAILED; + } + } +} + +static void fira_access_done(struct mcps802154_access *access, bool error) +{ + struct fira_local *local = access_to_local(access); + struct fira_session *session = local->current_session; + u32 timestamp_dtu = access->timestamp_dtu; + + trace_region_fira_access_done(local, session, access->duration_dtu, + error); + + /* propagate llhw error to fira session */ + session->last_error = access->error; + fira_session_fsm_access_done(local, session, error); + fira_diagnostic_free(local); + if (!error) + /* No access are infinite normally. */ + timestamp_dtu += access->duration_dtu; + /* + * Must be call after FSM access done, because + * shared resource in local are used. + */ + fira_check_all_missed_ranging(local, session, timestamp_dtu); +} + +static __le16 fira_access_set_short_address(struct fira_local *local, + const struct fira_session *session, + struct mcps802154_access *access) +{ + const struct fira_session_params *params = &session->params; + __le16 src_short_addr = mcps802154_get_short_addr(local->llhw); + + if (params->short_addr != IEEE802154_ADDR_SHORT_BROADCAST && + src_short_addr != params->short_addr) { + access->hw_addr_filt = (struct ieee802154_hw_addr_filt){ + .short_addr = params->short_addr, + }; + access->hw_addr_filt_changed = IEEE802154_AFILT_SADDR_CHANGED; + return params->short_addr; + } + access->hw_addr_filt_changed = 0; + return src_short_addr; +} + +static struct mcps802154_access_ops fira_controller_access_ops = { + .common = { + .access_done = fira_access_done, + }, + .rx_frame = fira_rx_frame, + .tx_get_frame = fira_tx_get_frame, + .tx_return = fira_tx_return, +}; + +int fira_session_get_slot_count(const struct fira_session *session) +{ + const struct fira_session_params *params = &session->params; + int nb_controlee = fira_session_controlees_running_count(session); + /* Control frame. */ + int slot_count = 1; + + if (nb_controlee) { + /* Ranging initiation frame. */ + slot_count++; + /* Ranging response frame(s). */ + slot_count += nb_controlee; + /* Ranging final frame. */ + if (params->ranging_round_usage == + FIRA_RANGING_ROUND_USAGE_DSTWR) + slot_count++; + /* Measurement report frame. */ + slot_count++; + /* Result report frame(s). */ + slot_count += nb_controlee; + } + return slot_count; +} + +struct mcps802154_access * +fira_get_access_controller(struct fira_local *local, + const struct fira_session_demand *fsd) +{ + struct fira_session *session = local->current_session; + const struct fira_session_params *params = &session->params; + const struct fira_measurement_sequence_step *step = + fira_session_get_meas_seq_step(session); + struct mcps802154_access *access = &local->access; + struct mcps802154_access_frame *frame; + struct mcps802154_sts_params *sts_params; + struct fira_ranging_info *ri; + struct fira_slot *s; + u32 frame_dtu; + int index = 0; + int i; + struct fira_controlee *controlee; + + trace_region_fira_get_access_controller(local, session, fsd); + + /* Update local context (shared memory used by all sessions). */ + local->src_short_addr = + fira_access_set_short_address(local, session, access); + local->dst_short_addr = + session->n_current_controlees == 1 ? + list_first_entry(&session->current_controlees, + struct fira_controlee, entry) + ->short_addr : + IEEE802154_ADDR_SHORT_BROADCAST; + + /* Update session. */ + session->last_access_timestamp_dtu = fsd->timestamp_dtu; + session->block_start_dtu = fsd->block_start_dtu; + session->block_index += fsd->add_blocks; + session->block_stride_len = params->block_stride_len; + session->round_index = fsd->round_index; + session->controller.next_block_index = + session->block_index + session->block_stride_len + 1; + session->next_round_index = + params->round_hopping ? + fira_round_hopping_sequence_get( + session, session->controller.next_block_index) : + 0; + + /* Build number of controlee which are stopped. */ + local->n_stopped_controlees = 0; + list_for_each_entry (controlee, &session->current_controlees, entry) { + if (controlee->state == FIRA_CONTROLEE_STATE_STOPPING || + controlee->state == FIRA_CONTROLEE_STATE_DELETING) + local->stopped_controlees[local->n_stopped_controlees++] = + controlee->short_addr; + } + /* Build number of controlee which are running. */ + local->n_ranging_valid = + session->n_current_controlees - local->n_stopped_controlees; + + /* Reset 'n' ranging info to store info related to controlees. */ + local->n_ranging_info = local->n_ranging_valid; + ri = local->ranging_info; + memset(ri, 0, + local->n_ranging_valid * sizeof(struct fira_ranging_info)); + + /* Prepare control message slot for fira_rx_frame. */ + s = local->slots; + s->index = index++; + s->controller_tx = true; + s->ranging_index = 0; + s->tx_ant_set = step->tx_ant_set_nonranging; + s->message_id = FIRA_MESSAGE_ID_CONTROL; + s->controlee = NULL; + s++; + /* Prepare other slots. */ + if (local->n_ranging_info) { + s->index = index++; + s->controller_tx = true; + s->ranging_index = 0; + s->tx_ant_set = step->tx_ant_set_ranging; + s->message_id = FIRA_MESSAGE_ID_RANGING_INITIATION; + s->controlee = NULL; + + s++; + i = 0; + list_for_each_entry (controlee, &session->current_controlees, + entry) { + if (!fira_session_controlee_active(controlee)) + continue; + ri->short_addr = controlee->short_addr; + ri->rx_ctx = session->rx_ctx[i]; + /* Requested in fira_report_aoa function. */ + ri++; + s->index = index++; + s->controller_tx = false; + s->ranging_index = i++; + s->rx_ant_set = fira_session_get_rx_ant_set( + session, FIRA_MESSAGE_ID_RANGING_RESPONSE); + s->message_id = FIRA_MESSAGE_ID_RANGING_RESPONSE; + s->controlee = controlee; + s++; + } + + if (params->ranging_round_usage == + FIRA_RANGING_ROUND_USAGE_DSTWR) { + s->index = index++; + s->controller_tx = true; + s->ranging_index = 0; + s->tx_ant_set = step->tx_ant_set_ranging; + s->message_id = FIRA_MESSAGE_ID_RANGING_FINAL; + s->controlee = NULL; + s++; + } + + s->index = index++; + s->controller_tx = true; + s->ranging_index = 0; + s->tx_ant_set = step->tx_ant_set_nonranging; + s->message_id = FIRA_MESSAGE_ID_MEASUREMENT_REPORT; + s->controlee = NULL; + + s++; + if (params->result_report_phase) { + i = 0; + list_for_each_entry (controlee, + &session->current_controlees, + entry) { + if (!fira_session_controlee_active(controlee)) + continue; + s->index = index++; + s->controller_tx = false; + s->ranging_index = i++; + s->rx_ant_set = step->rx_ant_set_nonranging; + s->message_id = FIRA_MESSAGE_ID_RESULT_REPORT; + s->controlee = controlee; + s++; + } + } + } + + /* Configure frames for fproc. */ + frame_dtu = fsd->timestamp_dtu; + for (i = 0; i < index; i++) { + s = &local->slots[i]; + frame = &local->frames[i]; + sts_params = &local->sts_params[i]; + + fira_access_setup_frame(local, session, frame, sts_params, s, + frame_dtu, s->controller_tx); + + frame_dtu += params->slot_duration_dtu; + } + + /* + * Configure the access. + * 'duration_dtu' can be decrease on reception error. + */ + access->ops = &fira_controller_access_ops; + access->timestamp_dtu = fsd->timestamp_dtu; + access->duration_dtu = frame_dtu - fsd->timestamp_dtu; + access->n_frames = index; + return access; +} + +static struct mcps802154_access_ops fira_controlee_access_ops = { + .common = { + .access_done = fira_access_done, + }, + .rx_frame = fira_rx_frame, + .tx_get_frame = fira_tx_get_frame, + .tx_return = fira_tx_return, +}; + +struct mcps802154_access * +fira_get_access_controlee(struct fira_local *local, + const struct fira_session_demand *fsd) +{ + /* + * \ Important: + * '-.__.-' It's almost forbidden to update session + * /oo |--.--,--,--. content for a controlee. Because, the + * \_.-'._i__i__i_.' session can change on control frame received. + * """"""""" + */ + struct fira_session *session = local->current_session; + const struct fira_session_params *params = &session->params; + struct mcps802154_access *access = &local->access; + struct mcps802154_access_frame *frame; + struct fira_ranging_info *ri; + struct fira_slot *s; + u16 request = MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU; + + trace_region_fira_get_access_controlee(local, session, fsd); + + /* Update local context (shared memory used by all sessions). */ + local->src_short_addr = + fira_access_set_short_address(local, session, access); + local->dst_short_addr = params->controller_short_addr; + local->n_stopped_controlees = 0; + + /* + * Update session. + * Updated values are used in case of bad reception (timeout/error). + * Otherwise on good reception, many are override. + * by the controlee resync function. + */ + session->block_start_dtu = fsd->block_start_dtu; + session->block_index += fsd->add_blocks; + + /* Reset one ranging info to store info related to the controller. */ + local->n_ranging_info = 1; + ri = local->ranging_info; + memset(ri, 0, sizeof(struct fira_ranging_info)); + /* + * Warning: + * - short_addr is used when rx control have an error. + * - Be careful to not initialize too much in ri, because + * session can change on rx control. + */ + ri->short_addr = params->controller_short_addr; + + /* Prepare control message slot for fira_rx_frame. */ + s = local->slots; + s->index = 0; + s->controller_tx = true; + s->ranging_index = 0; + s->rx_ant_set = + fira_session_get_meas_seq_step(session)->rx_ant_set_nonranging; + s->message_id = FIRA_MESSAGE_ID_CONTROL; + s->controlee = NULL; + + /* Configure frames for fproc. */ + if (params->report_rssi) + request |= MCPS802154_RX_FRAME_INFO_RSSI; + frame = local->frames; + *frame = (struct mcps802154_access_frame){ + .is_tx = false, + .rx = { + .frame_config = { + .timestamp_dtu = fsd->timestamp_dtu, + .timeout_dtu = fsd->rx_timeout_dtu, + .flags = MCPS802154_RX_FRAME_CONFIG_RANGING_ROUND | + MCPS802154_RX_FRAME_CONFIG_TIMESTAMP_DTU, + .ant_set_id = s->rx_ant_set, + }, + .frame_info_flags_request = request, + }, + }; + + /* + * Configure the access. + * 'duration_dtu' will be overridden on control frame reception. + */ + access->ops = &fira_controlee_access_ops; + access->timestamp_dtu = fsd->timestamp_dtu; + access->duration_dtu = fsd->max_duration_dtu; + access->n_frames = 1; + return access; +} diff --git a/mac/fira_access.h b/mac/fira_access.h new file mode 100644 index 0000000..3b6ca8c --- /dev/null +++ b/mac/fira_access.h @@ -0,0 +1,64 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef FIRA_ACCESS_H +#define FIRA_ACCESS_H + +#include <net/mcps802154_schedule.h> + +/* Forward declaration. */ +struct fira_local; +struct fira_session; +struct fira_session_demand; + +/** + * fira_session_get_slot_count() - Return the number of slot for the session. + * @session: FiRa session context. + * + * Return: Number of slot. + */ +int fira_session_get_slot_count(const struct fira_session *session); + +/** + * fira_get_access_controller() - Build the access for controller. + * @local: FiRa region context. + * @fsd: FiRa Session Demand from the get_demand of the session fsm. + * + * Return: A valid access. + */ +struct mcps802154_access * +fira_get_access_controller(struct fira_local *local, + const struct fira_session_demand *fsd); + +/** + * fira_get_access_controlee() - Build the access for controlee. + * @local: FiRa region context. + * @fsd: FiRa Session Demand from the get_demand of the session fsm. + * + * Return: A valid access. + */ +struct mcps802154_access * +fira_get_access_controlee(struct fira_local *local, + const struct fira_session_demand *fsd); + +#endif /* FIRA_ACCESS_H */ diff --git a/mac/fira_crypto.c b/mac/fira_crypto.c new file mode 100755 index 0000000..3613c9a --- /dev/null +++ b/mac/fira_crypto.c @@ -0,0 +1,1419 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. You should + * have received a copy of the GPLv2 along with this program. If not, see + * <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifdef __KERNEL__ +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ +#endif + +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <crypto/aes.h> + +#include <asm/unaligned.h> + +#include "fira_crypto.h" +#include <net/mcps802154_frame.h> +#include "mcps_crypto.h" + +#ifdef CONFIG_FIRA_CRYPTO_HAVE_SE +#include "key_manager.h" +#endif + +#ifdef __KERNEL__ +static inline void *platform_malloc(size_t s) { return kmalloc(s, GFP_KERNEL); } +static inline void platform_free(void *p) { kfree(p); } +#else +#include "trace/define_trace_specific.h" +#define pr_info(...) print_trace(__VA_ARGS__) +#define pr_err(...) print_trace(__VA_ARGS__) +#include "platform_alloc.h" +#endif + +#define FIRA_CRYPTO_KDF_LABEL_LEN 8 +#define FIRA_CRYPTO_KDF_CONTEXT_LEN 16 +#define FIRA_CRYPTO_KEY_STS_MASK 0x7FFFFFFF + +#define FIRA_IE_VENDOR_OUI_LEN 3 + +#define FIRA_CRYPTO_AEAD_AUTHSIZE 8 + +/** + * struct fira_crypto_aead - Context for payload encryption/decryption. + */ +struct fira_crypto_aead { + /** + * @ctx: The context to be used during aead encryption & decryption. + */ + struct mcps_aes_ccm_star_128_ctx *ctx; +}; + +/** + * struct fira_crypto - Context containing all crypto related + * information. + * + * NOTE: It is regularly used by the FiRa region core to produce the STS + * parameters for a given a session and to encrypt/decrypt frames. + */ +struct fira_crypto_base { + /** + * @key_size: Size of the key used in the AES derivation. + */ + u8 key_size; + + /** + * @config_digest: Digest of the configuration, used as input for keys + * derivation. + */ + u8 config_digest[AES_BLOCK_SIZE]; + + /** + * @data_protection_key: Derived from the session key, the label + * "DataPrtK" and the config_digest. The precise size is given by the + * key_size. + */ + u8 data_protection_key[FIRA_KEY_SIZE_MIN]; + + /** + * @derived_authentication_iv: Derived from data_protection_key, the + * label "DerAuthI", the current value of crypto_sts_index, and the + * config_digest. Used to compute the STS parameters for a slot. + */ + u8 derived_authentication_iv[AES_BLOCK_SIZE]; + + /** + * @derived_authentication_key: Derived from data_protection_key, the + * label "DerAuthK", the current value of crypto_sts_index and the + * config_digest. Used to compute the STS parameters for a slot. + */ + u8 derived_authentication_key[FIRA_KEY_SIZE_MIN]; + + /** + * @derived_payload_key: Derived from data_protection_key, the label + * "DerPaylK", the current value of crypto_sts_index and the + * config_digest. Used to encrypt/decrypt message PIE. + */ + u8 derived_payload_key[FIRA_KEY_SIZE_MIN]; + + /** + * @aead: AEAD Context for payload encryption/decryption. + */ + struct fira_crypto_aead aead; +}; + +struct fira_crypto { + /** + * @phy_sts_index_init: Initial phy_sts_index deduced at context init. + */ + u32 phy_sts_index_init; + /** + * @sts_config: The type of STS requested for this crypto. + */ + enum fira_sts_mode sts_config; + + /** + * @base: Common parameters between all types of crypto contexts. + */ + struct fira_crypto_base base; + + /******* Dynamic STS Only **************/ + + /** + * @ecb_ctx: AES ECB context + */ + struct mcps_aes_ecb_128_ctx *ecb_ctx; + + /** + * @privacy_key: Derived from the session key, the label + * "PrivacyK" and the config_digest. Used to encrypt/decrypt message HIE. + */ + u8 privacy_key[FIRA_KEY_SIZE_MIN]; + + /******* Static STS Only **************/ + /** + * @vupper64: The vupper 64 to use when static STS is used. + */ + u8 vupper64[FIRA_VUPPER64_SIZE]; +}; + +static int fira_crypto_kdf(const u8 *input_key, unsigned int input_key_len, + const char *label, const u8 *context, u8 *output_key, + unsigned int output_key_len) +{ + u8 derivation_data[sizeof(u32) + FIRA_CRYPTO_KDF_LABEL_LEN + + FIRA_CRYPTO_KDF_CONTEXT_LEN + sizeof(u32)]; + u8 *p; + int r; + + if (input_key_len != AES_KEYSIZE_128) { + pr_err("input_key_len != AES_KEYSIZE_128"); + return -EINVAL; + } + + p = derivation_data; + put_unaligned_be32(1, p); + p += sizeof(u32); + memcpy(p, label, FIRA_CRYPTO_KDF_LABEL_LEN); + p += FIRA_CRYPTO_KDF_LABEL_LEN; + memcpy(p, context, FIRA_CRYPTO_KDF_CONTEXT_LEN); + p += FIRA_CRYPTO_KDF_CONTEXT_LEN; + put_unaligned_be32(output_key_len * 8, p); + + r = mcps_crypto_cmac_aes_128_digest(input_key, derivation_data, + sizeof(derivation_data), output_key); + + return r; +} + +static int fira_crypto_aead_set_key(struct fira_crypto_aead *aead, const u8 *key) +{ + aead->ctx = mcps_crypto_aead_aes_ccm_star_128_create(); + + return aead->ctx ? mcps_crypto_aead_aes_ccm_star_128_set(aead->ctx, key) : -ENOMEM; +} + +static void fira_aead_fill_nonce(u8 *nonce, __le16 src_short_addr, + u32 crypto_sts_index) +{ + u8 *p; + + p = nonce; + memset(p, 0, IEEE802154_EXTENDED_ADDR_LEN - IEEE802154_SHORT_ADDR_LEN); + p += IEEE802154_EXTENDED_ADDR_LEN - IEEE802154_SHORT_ADDR_LEN; + put_unaligned_be16(src_short_addr, p); + p += IEEE802154_SHORT_ADDR_LEN; + put_unaligned_be32(crypto_sts_index, p); + p += sizeof(u32); + *p++ = IEEE802154_SCF_SECLEVEL_ENC_MIC64; +} + +static int fira_crypto_aead_encrypt(struct fira_crypto_aead *aead, + struct sk_buff *skb, unsigned int header_len, + __le16 src_short_addr, u32 crypto_sts_index) +{ + u8 nonce[MCPS_CRYPTO_AES_CCM_STAR_NONCE_LEN]; + u8 *header = skb->data; + u8 *payload = skb->data + header_len; + const int payload_len = skb->len - header_len; + u8 *mac = skb->data + skb->len; + int r; + + fira_aead_fill_nonce(nonce, src_short_addr, crypto_sts_index); + + skb->data[IEEE802154_FC_LEN + IEEE802154_SHORT_ADDR_LEN] = + IEEE802154_SCF_SECLEVEL_ENC_MIC64 | + IEEE802154_SCF_NO_FRAME_COUNTER; + + r = mcps_crypto_aead_aes_ccm_star_128_encrypt( + aead->ctx, nonce, header, header_len, payload, payload_len, mac, + FIRA_CRYPTO_AEAD_AUTHSIZE); + + if (!r) + skb_put(skb, FIRA_CRYPTO_AEAD_AUTHSIZE); + + return r; +} + +static int fira_crypto_aead_decrypt(struct fira_crypto_aead *aead, + struct sk_buff *skb, unsigned int header_len, + __le16 src_short_addr, u32 crypto_sts_index) +{ + u8 nonce[MCPS_CRYPTO_AES_CCM_STAR_NONCE_LEN]; + u8 *header; + u8 *payload; + unsigned int payload_len; + u8 *mac; + int r; + + header = skb->data - header_len; + payload = skb->data; + payload_len = skb->len; + mac = skb->data + payload_len; + + fira_aead_fill_nonce(nonce, src_short_addr, crypto_sts_index); + + r = mcps_crypto_aead_aes_ccm_star_128_decrypt( + aead->ctx, nonce, header, header_len, payload, payload_len, mac, + FIRA_CRYPTO_AEAD_AUTHSIZE); + + if (!r) { + header[IEEE802154_FC_LEN + IEEE802154_SHORT_ADDR_LEN] |= + IEEE802154_SCF_NO_FRAME_COUNTER; + } + + memzero_explicit(mac, FIRA_CRYPTO_AEAD_AUTHSIZE); + + return r; +} + +static void fira_crypto_aead_destroy(struct fira_crypto_aead *aead) +{ + mcps_crypto_aead_aes_ccm_star_128_destroy(aead->ctx); + aead->ctx = NULL; +} + +static void remove_session(struct fira_crypto *session) +{ + fira_crypto_aead_destroy(&session->base.aead); + mcps_crypto_aes_ecb_128_destroy(session->ecb_ctx); + /* Wipe all derived keys */ + memzero_explicit(session, sizeof(*session)); + platform_free(session); +} + +/*! ---------------------------------------------------------------------------------------------- + * @brief This function is used to initialise the FIRA crypto backend + * + * input parameters: + * @param key_manager - key manager to use. Not used for the moment. + * + * output parameters + * + * return 0 if no error. + */ +int fira_crypto_init(void *key_manager) +{ +#ifdef CONFIG_FIRA_CRYPTO_HAVE_SE + /* Opens the UBWS - SE secure channel */ + return key_manager_init(NULL); +#else + return 0; +#endif +} + +/************************************** FIRA STS API FCTS ***************************************/ + +int fira_crypto_context_init(const struct fira_crypto_params *crypto_params, + struct fira_crypto **crypto) +{ + int status = 0; + struct fira_crypto *fira_crypto_ctx; + u8 session_key[AES_KEYSIZE_128]; + + fira_crypto_ctx = platform_malloc(sizeof(*fira_crypto_ctx)); + + if (!fira_crypto_ctx) + return -ENOMEM; + + memset(fira_crypto_ctx, 0, sizeof(*fira_crypto_ctx)); + + /* Retrieve session key */ + switch (crypto_params->sts_config) { + case FIRA_STS_MODE_STATIC: + memcpy(session_key, "StaticTSStaticTS", AES_KEYSIZE_128); + fira_crypto_ctx->base.key_size = AES_KEYSIZE_128; + memcpy(fira_crypto_ctx->vupper64, crypto_params->vupper64, FIRA_VUPPER64_SIZE); + break; + + case FIRA_STS_MODE_PROVISIONED: + case FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY: + if (crypto_params->key) { + memcpy(session_key, crypto_params->key, crypto_params->key_len); + fira_crypto_ctx->base.key_size = crypto_params->key_len; + } else { + /* Session key not set */ + pr_err("Session key not provisioned !\n"); + status = -1; + } + break; + + default: + /* Bad value */ + pr_err("STS config unknown !\n"); + status = -1; + } + + fira_crypto_ctx->sts_config = crypto_params->sts_config; + + if (!status) { + /* Compute Config Digest */ + static const u8 zero_key[AES_KEYSIZE_128]; + + status = mcps_crypto_cmac_aes_128_digest(zero_key, + crypto_params->concat_params, + crypto_params->concat_params_size, + fira_crypto_ctx->base.config_digest); + if (status) + goto error_out; + + /* Compute secDataProtectionKey */ + status = fira_crypto_kdf(session_key, + fira_crypto_ctx->base.key_size, + "DataPrtK", + fira_crypto_ctx->base.config_digest, + fira_crypto_ctx->base.data_protection_key, + FIRA_KEY_SIZE_MIN); + if (status) + goto error_out; + + if (crypto_params->sts_config == FIRA_STS_MODE_STATIC) { + /* rotate keys only once for static_sts */ + status = fira_crypto_rotate_elements( + fira_crypto_ctx, + 0); + } else { + + /* Compute secPrivacy Key and setup AES ECB context */ + status = fira_crypto_kdf( + session_key, fira_crypto_ctx->base.key_size, + "PrivacyK", fira_crypto_ctx->base.config_digest, + fira_crypto_ctx->privacy_key, + FIRA_KEY_SIZE_MIN); + } + if (status) + goto error_out; + } + + /* Wipe session key */ + memzero_explicit(session_key, AES_KEYSIZE_128); + + *crypto = fira_crypto_ctx; + + return status; + +error_out: + /* Wipe session key */ + memzero_explicit(session_key, AES_KEYSIZE_128); + *crypto = NULL; + remove_session(fira_crypto_ctx); + return status; +} + +/** + * fira_crypto_context_deinit() - De-initialize a crypto context. + * @session_id: Pointer to the session id. + */ +void fira_crypto_context_deinit(struct fira_crypto *fira_crypto_ctx) +{ + if (fira_crypto_ctx) { + /* Remove the context */ + remove_session(fira_crypto_ctx); + fira_crypto_ctx = NULL; + } else { + /* The context doesn't exist */ + pr_err("Crypto context NULL\n"); + } +} + +/** + * fira_crypto_rotate_elements() - Rotate the crypto elements contained in the + * crypto context. + * + * NOTE: After calling this function, all active crypto elements will be the latest + * rotated ones. + * + * @crypto: The context containing the elements to rotate. + * @crypto_sts_index: The crypto STS index to use to rotate the elements. + * + * Return: 0 or error. + */ +int fira_crypto_rotate_elements(struct fira_crypto *fira_crypto_ctx, + const u32 crypto_sts_index) +{ + int r = 0; + u8 context[AES_BLOCK_SIZE]; + + if (!fira_crypto_ctx) + goto error_out; + + memcpy(context, fira_crypto_ctx->base.config_digest + sizeof(u32), + AES_BLOCK_SIZE - sizeof(u32)); + put_unaligned_be32(crypto_sts_index, context + AES_BLOCK_SIZE - + sizeof(u32)); + + r = fira_crypto_kdf(fira_crypto_ctx->base.data_protection_key, + fira_crypto_ctx->base.key_size, + "DerAuthI", context, + fira_crypto_ctx->base.derived_authentication_iv, + fira_crypto_ctx->base.key_size); + if (r) + goto error_out; + + r = fira_crypto_kdf(fira_crypto_ctx->base.data_protection_key, + fira_crypto_ctx->base.key_size, + "DerAuthK", context, + fira_crypto_ctx->base.derived_authentication_key, + fira_crypto_ctx->base.key_size); + if (r) + goto error_out; + + r = fira_crypto_kdf(fira_crypto_ctx->base.data_protection_key, + fira_crypto_ctx->base.key_size, + "DerPaylK", context, + fira_crypto_ctx->base.derived_payload_key, + fira_crypto_ctx->base.key_size); + if (r) + goto error_out; + + if (fira_crypto_ctx->base.aead.ctx == NULL) + r = fira_crypto_aead_set_key(&fira_crypto_ctx->base.aead, + fira_crypto_ctx->base.derived_payload_key); + +error_out: + memzero_explicit(context, sizeof(context)); + return r; +} + +u32 fira_crypto_get_phy_sts_index_init(const struct fira_crypto *crypto) +{ + if (!crypto) + return -1; + return crypto->phy_sts_index_init; +} + +/** + * fira_crypto_build_phy_sts_index_init() - Build the phy STS index init value + * related to the given crypto context. + * + * @crypto: The context to use to compute the phy STS index init value. + * + * Return: 0 or error. + */ +int fira_crypto_build_phy_sts_index_init(struct fira_crypto *fira_crypto_ctx) +{ + int r = -1; + u8 phy_sts_index_init_tmp[AES_KEYSIZE_128]; + + if (!fira_crypto_ctx) + goto error_out; + + r = fira_crypto_kdf(fira_crypto_ctx->base.data_protection_key, + fira_crypto_ctx->base.key_size, + "StsIndIn", fira_crypto_ctx->base.config_digest, + phy_sts_index_init_tmp, + fira_crypto_ctx->base.key_size); + if (r) + goto error_out; + + fira_crypto_ctx->phy_sts_index_init = + get_unaligned_be32(phy_sts_index_init_tmp + + fira_crypto_ctx->base.key_size - sizeof(u32)) & + FIRA_CRYPTO_KEY_STS_MASK; + return 0; + +error_out: + memzero_explicit(phy_sts_index_init_tmp, + sizeof(phy_sts_index_init_tmp)); + return r; + +} + +/** + * fira_crypto_dynamic_get_sts_params() - Get STS parameters for a given slot + * using a dynamic STS configuration. + * @crypto: The context to use to get the STS parameters. + * @crypto_sts_index: The crypto STS index related to the slot request slot. + * @sts_v: Output buffer to store the STS V. + * @sts_v_size: Size of the STS V buffer. + * @sts_key: Output buffer to store the STS key. + * @sts_key_size: Size of the STS key buffer. + * + * Return: 0 or error. + */ +int fira_crypto_get_sts_params(struct fira_crypto *fira_crypto_ctx, + const u32 crypto_sts_index, u8 *sts_v, u32 sts_v_size, + u8 *sts_key, u32 sts_key_size) +{ + u8 *vupper64 = NULL; + u32 v_counter; + u8 *sts_v_temp; + + if (!fira_crypto_ctx) + return -1; + + if (fira_crypto_ctx->sts_config == FIRA_STS_MODE_STATIC) + vupper64 = fira_crypto_ctx->vupper64; + else + vupper64 = fira_crypto_ctx->base.derived_authentication_iv; + + if (sts_v_size < AES_BLOCK_SIZE || sts_key_size < AES_KEYSIZE_128) + return -EINVAL; + + sts_v_temp = sts_v; + + /* Concatenate the 128 bits of sts_v + * sts_v = vupper64 | crypto_sts_index | v_counter + */ + memcpy(sts_v_temp, vupper64, FIRA_VUPPER64_SIZE); + sts_v_temp += FIRA_VUPPER64_SIZE; + put_unaligned_be32(crypto_sts_index, sts_v_temp); + sts_v_temp += sizeof(crypto_sts_index); + v_counter = get_unaligned_be32( + fira_crypto_ctx->base.derived_authentication_iv + + AES_BLOCK_SIZE - sizeof(v_counter)) & + FIRA_CRYPTO_KEY_STS_MASK; + put_unaligned_be32(v_counter, sts_v_temp); + + memcpy(sts_key, fira_crypto_ctx->base.derived_authentication_key, + fira_crypto_ctx->base.key_size); + + return 0; +} + +/** + * fira_crypto_encrypt_frame() - Encrypt a 802154 frame using a given context. + * + * NOTE: The src address is given as an argument as it is a part of the nonce needed + * to encrypt the frame and it is not present in the 802154 frame. + * The length of the header is given because only the payload is encrypted even if + * the encryption algorithm needs the whole 802154 frame. + * Encryption is done in-place. + * When called this function shall increase the size of the skb of + * FIRA_CRYPTO_AEAD_AUTHSIZE and set the correct bits in the 802154 frame SCF. + * + * @crypto: The context to use to encrypt the frame. + * @skb: The buffer containing the whole frame, skb->data points to the start of + * the 802154 frame header. + * @header_len: The length of the 802154 frame header. Can be used to find the + * position of the 802154 frame payload relative to skb->data. + * @src_short_addr: The short source address attached to the frame. + * @crypto_sts_index: The crypto STS index attached to the frame. + * + * Return: 0 or error. + */ +int fira_crypto_encrypt_frame(struct fira_crypto *fira_crypto_ctx, struct sk_buff *skb, + int header_len, __le16 src_short_addr, u32 crypto_sts_index) +{ + if (!fira_crypto_ctx) + return -1; + + return fira_crypto_aead_encrypt(&fira_crypto_ctx->base.aead, skb, header_len, + src_short_addr, crypto_sts_index); +} + +/** + * fira_crypto_decrypt_frame() - Decrypt a 802154 frame using a given context. + * + * NOTE: The src address is given as an argument as it is a part of the nonce needed + * to decrypt the frame and it is not present in the 802154 frame. + * The length of the header is given because only the payload is encrypted even if + * the encryption algorithm needs the whole 802154 frame. + * Decryption is done in-place. + * When called, this function shall reduce the + * size of the skb of FIRA_CRYPTO_AEAD_AUTHSIZE and verify the correct bits in the + * 802154 frame SCF. + * + * @crypto: The crypto to use to decrypt the frame. + * @skb: The buffer containing the whole frame, skb->data points to the start of + * the 802154 frame payload. + * @header_len: The length of the 802154 frame header. Can be used to find the + * start of the 802154 frame payload relative to skb->data. + * @src_short_addr: The short source address attached to the frame. + * @crypto_sts_index: The crypto STS index attached to the frame. + * + * Return: 0 or error. + */ +int fira_crypto_decrypt_frame(struct fira_crypto *fira_crypto_ctx, struct sk_buff *skb, + int header_len, __le16 src_short_addr, u32 crypto_sts_index) +{ + if (!fira_crypto_ctx) + return -1; + + return fira_crypto_aead_decrypt(&fira_crypto_ctx->base.aead, skb, header_len, + src_short_addr, crypto_sts_index); +} + +/** + * fira_crypto_encrypt_hie() - Encrypt the HIE in a FiRa 802154 frame. + * @crypto: The context to use to get the STS parameters. + * @skb: Buffer containing the frame to encrypt. + * @hie_offset: Offset to the start of the HIE (relating to skb->data) to encrypt. + * @hie_len: Length of the HIE to encrypt. + * + * Return: 0 or error. + */ +int fira_crypto_encrypt_hie(struct fira_crypto *fira_crypto_ctx, struct sk_buff *skb, + int hie_offset, int hie_len) +{ + int rc; + + if (!fira_crypto_ctx) + return -1; + + if (fira_crypto_ctx->sts_config == FIRA_STS_MODE_STATIC) + return 0; + + fira_crypto_ctx->ecb_ctx = mcps_crypto_aes_ecb_128_create(); + if (!fira_crypto_ctx->ecb_ctx || + mcps_crypto_aes_ecb_128_set_encrypt( + fira_crypto_ctx->ecb_ctx, + fira_crypto_ctx->privacy_key)) + return -1; + + rc = mcps_crypto_aes_ecb_128_encrypt(fira_crypto_ctx->ecb_ctx, + (const uint8_t *)(skb->data + hie_offset + + IEEE802154_IE_HEADER_LEN + + FIRA_IE_VENDOR_OUI_LEN), + (unsigned int)hie_len - IEEE802154_IE_HEADER_LEN - + FIRA_IE_VENDOR_OUI_LEN, + (uint8_t *)(skb->data + hie_offset + + IEEE802154_IE_HEADER_LEN + + FIRA_IE_VENDOR_OUI_LEN)); + + mcps_crypto_aes_ecb_128_destroy(fira_crypto_ctx->ecb_ctx); + fira_crypto_ctx->ecb_ctx = NULL; + + return rc; +} + +/** + * fira_crypto_decrypt_hie() - Decrypt the HIE in a FiRa 802154 frame. + * @crypto: The context to use to get the STS parameters. + * @skb: Buffer containing the frame to decrypt. + * @hie_offset: Offset to the start of the HIE (relative to skb->data) to decrypt. + * @hie_len: Length of the HIE to decrypt. + * + * Return: 0 or error. + */ +int fira_crypto_decrypt_hie(struct fira_crypto *fira_crypto_ctx, struct sk_buff *skb, + int hie_offset, int hie_len) +{ + int rc; + + if (!fira_crypto_ctx) + return -1; + + if (fira_crypto_ctx->sts_config == FIRA_STS_MODE_STATIC) + return 0; + + fira_crypto_ctx->ecb_ctx = mcps_crypto_aes_ecb_128_create(); + if (!fira_crypto_ctx->ecb_ctx || + mcps_crypto_aes_ecb_128_set_decrypt( + fira_crypto_ctx->ecb_ctx, + fira_crypto_ctx->privacy_key)) + return -1; + + rc = mcps_crypto_aes_ecb_128_encrypt(fira_crypto_ctx->ecb_ctx, + (const uint8_t *)(skb->data + hie_offset), + (unsigned int)hie_len, + (uint8_t *)(skb->data + hie_offset)); + + mcps_crypto_aes_ecb_128_destroy(fira_crypto_ctx->ecb_ctx); + fira_crypto_ctx->ecb_ctx = NULL; + + return rc; +} + +/** + * fira_crypto_get_capabilities() - Get capabilities of the platform used. + * + * Return: + * bit 0 : FIRA_STS_MODE_STATIC supported + * bit 1 : FIRA_STS_MODE_DYNAMIC supported + * bit 2 : FIRA_STS_MODE_DYNAMIC_INDIVIDUAL_KEY supported + * bit 3 : FIRA_STS_MODE_PROVISIONED supported + * bit 4 : FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY supported + * other : not used + */ +u32 fira_crypto_get_capabilities(void) +{ + u32 status = 0; + + status += STS_CAP(STATIC); + status += STS_CAP(PROVISIONED); + status += STS_CAP(PROVISIONED_INDIVIDUAL_KEY); + +#ifdef CONFIG_FIRA_CRYPTO_HAVE_SE + status += STS_CAP(DYNAMIC); +#endif + + return status; +} + +int fira_crypto_prepare_decrypt(struct fira_crypto *crypto, struct sk_buff *skb) +{ + u8 scf; + u8 *p; + + p = skb->data - (IEEE802154_FC_LEN + IEEE802154_SHORT_ADDR_LEN + + IEEE802154_SCF_LEN); + scf = p[IEEE802154_FC_LEN + IEEE802154_SHORT_ADDR_LEN]; + if (!(scf == (IEEE802154_SCF_SECLEVEL_ENC_MIC64 | + IEEE802154_SCF_NO_FRAME_COUNTER)) || + (skb->len < FIRA_CRYPTO_AEAD_AUTHSIZE)) + return -EBADMSG; + skb_trim(skb, skb->len - FIRA_CRYPTO_AEAD_AUTHSIZE); + + return 0; +} + +static bool compare_bufs(const void *a, size_t alen, const void *b, size_t blen) +{ + if (alen != blen || memcmp(a, b, alen) != 0) { +#ifdef __KERNEL__ + print_hex_dump(KERN_ERR, "a: ", DUMP_PREFIX_OFFSET, 16, 1, a, + alen, false); + print_hex_dump(KERN_ERR, "b: ", DUMP_PREFIX_OFFSET, 16, 1, b, + blen, false); +#endif + return false; + } + + return true; +} + +/** + * fira_crypto_test_static() + * Run the FIRA CONSORTIUM UWB MAC TECHNICAL REQUIREMENTS + * version 1.3.0 test vectors for Static STS + * + * NOTE: This APis used for unit tests only. + * + * Return: 0 if ok + */ +static int fira_crypto_test_static(void) +{ + /* Static STS */ + static const u8 config[] = { + 0x02, 0x00, 0x00, 0x09, 0x07, 0xd0, 0x00, 0x03, + 0x0a, 0x02, 0x00, 0x01, 0x03, 0x01, 0x23, 0x45, + 0x67 + }; + static const u8 vUpp[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; + static const struct fira_crypto_params param = { + .session_id = 0x01234567, + .sts_config = FIRA_STS_MODE_STATIC, + .concat_params = config, + .concat_params_size = sizeof(config), + .vupper64 = vUpp + }; + static const u8 configDigest[] = { + 0xa0, 0x43, 0x90, 0xcf, 0x8a, 0x33, 0xf6, 0xeb, + 0x7e, 0x2f, 0xc3, 0x78, 0x87, 0xb6, 0xb2, 0xa3 + }; + static const u8 secDataProtectionKey[] = { + 0xf3, 0x21, 0x6c, 0x87, 0xd0, 0xc6, 0x93, 0x2e, + 0x39, 0x57, 0xb4, 0x81, 0xfa, 0xb8, 0xb2, 0x09 + }; + static const u8 derived_authentication_iv[] = { + 0x8b, 0x54, 0x37, 0x6e, 0x7c, 0xd7, 0xa5, 0xd6, + 0x6b, 0xd1, 0x20, 0x00, 0x97, 0x27, 0x41, 0x19 + }; + static const u8 derived_authentication_key[] = { + 0xdd, 0x98, 0x97, 0xf2, 0xb8, 0x5c, 0x9d, 0xc8, + 0xa7, 0xde, 0xc0, 0x1c, 0xca, 0x5b, 0x61, 0xdb + }; + static const u8 derived_payload_key[] = { + 0xa5, 0x5f, 0xab, 0x83, 0xb6, 0x20, 0xf9, 0xf6, + 0xa4, 0x7c, 0xdb, 0x72, 0x91, 0x7c, 0x73, 0x8a + }; + static const u8 sts_v_ref[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x17, 0x27, 0x41, 0x19 + }; + /* build the RCM Frame */ + /* build the header */ + static const u8 RCM[] = { + 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff, + 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0x78, 0xbe, + 0x9b, 0x0b, 0x00, 0x3f, 0x1b, 0x90, 0xff, 0x18, + 0x5a, 0x03, 0x05, 0x00, 0x00, 0x03, 0x42, 0x55, + 0x01, 0x04, 0x44, 0x55, 0x03, 0x07, 0x42, 0x55, + 0x05, 0x09, 0x42, 0x55, 0x09, 0x0a, 0x44, 0x55, + 0x0b + }; + static const u8 RCMRef[] = { + 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff, + 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0x78, 0xbe, + 0x9b, 0x0b, 0x00, 0x3f, 0xcb, 0xa4, 0xfd, 0x37, + 0xd1, 0x99, 0x44, 0x88, 0x7c, 0x2b, 0xec, 0x2e, + 0x1a, 0x99, 0x8e, 0x80, 0x61, 0x7c, 0x44, 0xb5, + 0xe8, 0xe3, 0xf3, 0x35, 0x3a, 0xb9, 0xf2, 0x29, + 0x1b, 0x80, 0x4b, 0xba, 0xe1, 0xa9, 0x2a, 0x20, + 0x28 + }; + static const u8 Header[] = { + 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff, + 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0x78, 0xbe, + 0x9b, 0x0b + }; + static const u8 HeaderRef[] = { + 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff, + 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0x78, 0xbe, + 0x9b, 0x0b + }; + /* Decrypt Frame */ + static const u8 RCM_Rcv_Ref[] = { + 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff, + 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0x78, 0xbe, + 0x9b, 0x0b, 0x00, 0x3f, 0x1b, 0x90, 0xff, 0x18, + 0x5a, 0x03, 0x05, 0x00, 0x00, 0x03, 0x42, 0x55, + 0x01, 0x04, 0x44, 0x55, 0x03, 0x07, 0x42, 0x55, + 0x05, 0x09, 0x42, 0x55, 0x09, 0x0a, 0x44, 0x55, + 0x0b + }; + static const u8 Frame_Rcv[] = { + 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff, + 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0x78, 0xbe, + 0x9b, 0x0b, 0x00, 0x3f, 0xcb, 0xa4, 0xfd, 0x37, + 0xd1, 0x99, 0x44, 0x88, 0x7c, 0x2b, 0xec, 0x2e, + 0x1a, 0x99, 0x8e, 0x80, 0x61, 0x7c, 0x44, 0xb5, + 0xe8, 0xe3, 0xf3, 0x35, 0x3a, 0xb9, 0xf2, 0x29, + 0x1b, 0x80, 0x4b, 0xba, 0xe1, 0xa9, 0x2a, 0x20, + 0x28 + }; + static const u8 Header_Rcv[] = { + 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff, + 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0x78, 0xbe, + 0x9b, 0x0b + }; + static const u8 HeaderRef_Rcv[] = { + 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff, + 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0x78, 0xbe, + 0x9b, 0x0b + }; + int err = -1, r; + struct fira_crypto *fira_crypto_ctx; + const u32 crypto_sts_index = 0; + u8 sts_v[16]; + u8 sts_key[16]; + struct sk_buff *skb = NULL; + + r = fira_crypto_context_init(¶m, &fira_crypto_ctx); + if (r != 0 || !fira_crypto_ctx) { + pr_err("fira_crypto_context_init fail: %d\n", r); + goto end; + } + + if (!compare_bufs(configDigest, sizeof(configDigest), + fira_crypto_ctx->base.config_digest, + sizeof(fira_crypto_ctx->base.config_digest))) { + pr_err("compare configDigest fail\n"); + goto end; + } + + if (!compare_bufs(secDataProtectionKey, sizeof(secDataProtectionKey), + fira_crypto_ctx->base.data_protection_key, + sizeof(fira_crypto_ctx->base.data_protection_key))) { + pr_err("compare secDataProtectionKey fail\n"); + goto end; + } + + if (!compare_bufs(derived_authentication_iv, + sizeof(derived_authentication_iv), + fira_crypto_ctx->base.derived_authentication_iv, + sizeof(fira_crypto_ctx->base.derived_authentication_iv))) { + pr_err("compare derived_authentication_iv fail\n"); + goto end; + } + + if (!compare_bufs(derived_authentication_key, + sizeof(derived_authentication_key), + fira_crypto_ctx->base.derived_authentication_key, + sizeof(fira_crypto_ctx->base.derived_authentication_key))) { + pr_err("compare derived_authentication_key fail\n"); + goto end; + } + + if (!compare_bufs(derived_payload_key, sizeof(derived_payload_key), + fira_crypto_ctx->base.derived_payload_key, + sizeof(fira_crypto_ctx->base.derived_payload_key))) { + pr_err("compare derived_payload_key fail\n"); + goto end; + } + + r = fira_crypto_build_phy_sts_index_init(fira_crypto_ctx); + if (r != 0) { + pr_err("fira_crypto_build_phy_sts_index_init fail: %d\n", r); + goto end; + } + if (fira_crypto_ctx->phy_sts_index_init != 0x0b9bbe78) { + pr_err("phy_sts_index_init fail\n"); + goto end; + } + + r = fira_crypto_get_sts_params(fira_crypto_ctx, crypto_sts_index, sts_v, + sizeof(sts_v), sts_key, sizeof(sts_key)); + if (r != 0) { + pr_err("fira_crypto_get_sts_params fail: %d\n", r); + goto end; + } + if (!compare_bufs(derived_authentication_key, + sizeof(derived_authentication_key), + sts_key, sizeof(sts_key))) { + pr_err("compare sts_key fail\n"); + goto end; + } + + if (!compare_bufs(sts_v_ref, sizeof(sts_v_ref), sts_v, sizeof(sts_v))) { + pr_err("compare sts_v fail\n"); + goto end; + } + + skb = alloc_skb(128, GFP_KERNEL); + if (!skb) { + pr_err("cannot allocate skb\n"); + goto end; + } + + skb_put_data(skb, Header, sizeof(Header)); + + /* Encrypt Header first (NOP in Static STS) */ + r = fira_crypto_encrypt_hie(fira_crypto_ctx, skb, 5, 21); + if (r != 0) { + pr_err("fira_crypto_encrypt_hie fail: %d\n", r); + goto end; + } + if (!compare_bufs(HeaderRef, sizeof(HeaderRef), skb->data, skb->len)) { + pr_err("fira_crypto_encrypt_hie compare HeaderRef fail\n"); + goto end; + } + + kfree_skb(skb); + + skb = alloc_skb(128, GFP_KERNEL); + if (!skb) { + pr_err("cannot allocate skb\n"); + goto end; + } + + skb_put_data(skb, RCM, sizeof(RCM)); + + r = fira_crypto_encrypt_frame(fira_crypto_ctx, skb, 28, 0xaaa1, 0); + if (r != 0) { + pr_err("fira_crypto_encrypt_frame fail: %d\n", r); + goto end; + } + + if (!compare_bufs(RCMRef, sizeof(RCMRef), skb->data, skb->len)) { + pr_err("fira_crypto_encrypt_frame compare RCMRef fail\n"); + goto end; + } + + kfree_skb(skb); + + skb = alloc_skb(128, GFP_KERNEL); + if (!skb) { + pr_err("cannot allocate skb\n"); + goto end; + } + + skb_put_data(skb, Frame_Rcv, sizeof(Frame_Rcv)); + skb_pull(skb, 28); /* skip header */ + + skb_trim(skb, skb->len - FIRA_CRYPTO_AEAD_AUTHSIZE); + r = fira_crypto_decrypt_frame(fira_crypto_ctx, skb, 28, 0xaaa1, 0); + if (r != 0) { + pr_err("fira_crypto_decrypt_frame fail: %d\n", r); + goto end; + } + + skb_push(skb, 28); /* restore header */ + + if (!compare_bufs(RCM_Rcv_Ref, sizeof(RCM_Rcv_Ref), skb->data, skb->len)) { + pr_err("fira_crypto_decrypt_frame compare RCM_Rcv_Ref fail\n"); + goto end; + } + + kfree_skb(skb); + + skb = alloc_skb(128, GFP_KERNEL); + if (!skb) { + pr_err("cannot allocate skb\n"); + goto end; + } + + skb_put_data(skb, Header_Rcv, sizeof(Header_Rcv)); + + /* Decrypt header (NOP in Static STS) */ + r = fira_crypto_decrypt_hie(fira_crypto_ctx, skb, 10, 16); + if (r != 0) { + pr_err("fira_crypto_decrypt_hie fail: %d\n", r); + goto end; + } + if (!compare_bufs(HeaderRef_Rcv, sizeof(HeaderRef_Rcv), skb->data, + skb->len)) { + pr_err("fira_crypto_decrypt_hie compare HeaderRef_Rcv fail\n"); + goto end; + } + + err = 0; + + pr_info("Static STS tests success\n"); + +end: + if (skb) + kfree_skb(skb); + if (fira_crypto_ctx) + fira_crypto_context_deinit(fira_crypto_ctx); + + return err; +} + +/** + * fira_crypto_test_provisioned() + * Run the FIRA CONSORTIUM UWB MAC TECHNICAL REQUIREMENTS + * version 1.3.0 test vectors for Dynamic STS (Provisioned STS is ran instead of + * pure dynamic) + * + * NOTE: This APis used for unit tests only. + * + * Return: 0 if ok + */ +static int fira_crypto_test_provisioned(void) +{ + /* Provisioned STS (equivalent to D-STS) */ + static const u8 config_P_STS[] = { + 0x02, 0x01, 0x00, 0x09, 0x07, 0xD0, 0x00, 0x03, + 0x0a, 0x02, 0x00, 0x01, 0x03, 0x01, 0x23, 0x45, + 0x67 + }; + static const u8 sessionKey[] = { + 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x53, + 0x54, 0x53, 0x4e, 0x6f, 0x52, 0x6f, 0x74, 0x30 + }; + static const struct fira_crypto_params param_p_sts = { + .session_id = 0x01234567, + .sts_config = FIRA_STS_MODE_PROVISIONED, + .concat_params = config_P_STS, + .concat_params_size = sizeof(config_P_STS), + .key = sessionKey, + .key_len = sizeof(sessionKey) + }; + static const u8 configDigest_p_sts[] = { + 0x08, 0x93, 0x66, 0xba, 0xfb, 0x3b, 0x24, 0xbf, + 0xd2, 0x93, 0x33, 0x77, 0x61, 0xb8, 0x8f, 0xc3 + }; + static const u8 secDataPrivacyKey_p_sts[] = { + 0x3a, 0x4b, 0xab, 0x18, 0x74, 0x4a, 0xee, 0x93, + 0x86, 0x50, 0xf1, 0xa0, 0x3f, 0x58, 0x5a, 0x49 + }; + static const u8 secDataProtectionKey_p_sts[] = { + 0x67, 0xf7, 0x02, 0x7e, 0xa6, 0x2d, 0x84, 0xa5, + 0xe1, 0xa8, 0xd7, 0xb8, 0xb8, 0xac, 0xae, 0xaf + }; + static const u8 derived_authentication_iv_p_sts[] = { + 0xfa, 0x32, 0x6f, 0xed, 0x87, 0xd2, 0xef, 0x7e, + 0xb6, 0x80, 0xb2, 0xd6, 0xd1, 0x19, 0xa9, 0xb8 + }; + static const u8 derived_authentication_key_p_sts[] = { + 0x91, 0xa2, 0xde, 0x58, 0xff, 0x3b, 0x5e, 0x85, + 0x15, 0x33, 0x58, 0xd6, 0x15, 0x64, 0x64, 0xff + }; + static const u8 derived_payload_key_p_sts[] = { + 0x97, 0xe4, 0xab, 0x69, 0x61, 0x77, 0xbb, 0x39, + 0x92, 0x77, 0xb8, 0x35, 0x9f, 0xa5, 0x5d, 0x19 + }; + static const u8 sts_v_ref_p_sts[] = { + 0xfa, 0x32, 0x6f, 0xed, 0x87, 0xd2, 0xef, 0x7e, + 0x04, 0x1f, 0x3b, 0xa0, 0x51, 0x19, 0xa9, 0xb8 + }; + /* build the RCM Frame */ + static const u8 RCM_p_sts[] = { + 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff, + 0x18, 0x5a, 0x84, 0x6c, 0xa4, 0x3c, 0x52, 0xfb, + 0xb0, 0x2b, 0x56, 0xa9, 0x87, 0x9d, 0xb0, 0x4e, + 0x4e, 0x03, 0x00, 0x3f, 0x1b, 0x90, 0xff, 0x18, + 0x5a, 0x03, 0x05, 0x00, 0x00, 0x03, 0x42, 0x55, + 0x01, 0x04, 0x44, 0x55, 0x03, 0x07, 0x42, 0x55, + 0x05, 0x09, 0x42, 0x55, 0x09, 0x0a, 0x44, 0x55, + 0x0b + }; + static const u8 RCMRef_p_sts[] = { + 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff, + 0x18, 0x5a, 0x84, 0x6c, 0xa4, 0x3c, 0x52, 0xfb, + 0xb0, 0x2b, 0x56, 0xa9, 0x87, 0x9d, 0xb0, 0x4e, + 0x4e, 0x03, 0x00, 0x3f, 0x82, 0x76, 0xe0, 0x44, + 0xf3, 0x78, 0xab, 0xbe, 0xd2, 0x39, 0x86, 0x7e, + 0xd2, 0xfe, 0x5c, 0x9d, 0xcd, 0x13, 0x1d, 0x1f, + 0x63, 0x38, 0xf1, 0xf7, 0x9d, 0xb1, 0x84, 0x71, + 0x72, 0x7a, 0x10, 0xfc, 0x80, 0x04, 0x7e, 0xdb, + 0x0f + }; + static const u8 Header_p_sts[] = { + 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff, + 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0xa0, 0x3b, + 0x1f, 0x04 + }; + static const u8 HeaderRef_p_sts[] = { + 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff, + 0x18, 0x5a, 0x84, 0x6c, 0xa4, 0x3c, 0x52, 0xfb, + 0xb0, 0x2b, 0x56, 0xa9, 0x87, 0x9d, 0xb0, 0x4e, + 0x4e, 0x03 + }; + /* Decrypt Frame */ + static const u8 Header_RCM_p_sts_Rcv[] = { + 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff, + 0x18, 0x5a, 0x84, 0x6c, 0xa4, 0x3c, 0x52, 0xfb, + 0xb0, 0x2b, 0x56, 0xa9, 0x87, 0x9d, 0xb0, 0x4e, + 0x4e, 0x03, 0x00, 0x3f, 0x82, 0x76, 0xe0, 0x44, + 0xf3, 0x78, 0xab, 0xbe, 0xd2, 0x39, 0x86, 0x7e, + 0xd2, 0xfe, 0x5c, 0x9d, 0xcd, 0x13, 0x1d, 0x1f, + 0x63, 0x38, 0xf1, 0xf7, 0x9d, 0xb1, 0x84, 0x71, + 0x72, 0x7a, 0x10, 0xfc, 0x80, 0x04, 0x7e, 0xdb, + 0x0f + }; + static const u8 RCMRef_p_sts_Rcv[] = { + 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff, + 0x18, 0x5a, 0x84, 0x6c, 0xa4, 0x3c, 0x52, 0xfb, + 0xb0, 0x2b, 0x56, 0xa9, 0x87, 0x9d, 0xb0, 0x4e, + 0x4e, 0x03, 0x00, 0x3f, 0x1b, 0x90, 0xff, 0x18, + 0x5a, 0x03, 0x05, 0x00, 0x00, 0x03, 0x42, 0x55, + 0x01, 0x04, 0x44, 0x55, 0x03, 0x07, 0x42, 0x55, + 0x05, 0x09, 0x42, 0x55, 0x09, 0x0a, 0x44, 0x55, + 0x0b + }; + static const u8 Header_p_sts_Rcv[] = { + 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff, + 0x18, 0x5a, 0x84, 0x6c, 0xa4, 0x3c, 0x52, 0xfb, + 0xb0, 0x2b, 0x56, 0xa9, 0x87, 0x9d, 0xb0, 0x4e, + 0x4e, 0x03 + }; + static const u8 HeaderRef_p_sts_Rcv[] = { + 0x49, 0x2b, 0xa2, 0xaa, 0x26, 0x13, 0x00, 0xff, + 0x18, 0x5a, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x67, 0x45, 0x23, 0x01, 0xa0, 0x3b, + 0x1f, 0x04 + }; + int err = -1, r; + struct fira_crypto *fira_crypto_ctx; + u32 crypto_sts_index_p_sts = 0; + u8 sts_v[16]; + u8 sts_key[16]; + struct sk_buff *skb = NULL; + + r = fira_crypto_context_init(¶m_p_sts, &fira_crypto_ctx); + if (r != 0 || !fira_crypto_ctx) { + pr_err("fira_crypto_context_init fail: %d\n", r); + goto end; + } + + if (!compare_bufs(configDigest_p_sts, sizeof(configDigest_p_sts), + fira_crypto_ctx->base.config_digest, + sizeof(fira_crypto_ctx->base.config_digest))) { + pr_err("compare configDigest_p_sts fail\n"); + goto end; + } + + if (!compare_bufs(secDataPrivacyKey_p_sts, + sizeof(secDataPrivacyKey_p_sts), + fira_crypto_ctx->privacy_key, + sizeof(fira_crypto_ctx->privacy_key))) { + pr_err("compare secDataPrivacyKey_p_sts fail\n"); + goto end; + } + + if (!compare_bufs(secDataProtectionKey_p_sts, + sizeof(secDataProtectionKey_p_sts), + fira_crypto_ctx->base.data_protection_key, + sizeof(fira_crypto_ctx->base.data_protection_key))) { + pr_err("compare secDataProtectionKey fail\n"); + goto end; + } + + r = fira_crypto_build_phy_sts_index_init(fira_crypto_ctx); + if (r != 0) { + pr_err("fira_crypto_build_phy_sts_index_init fail: %d\n", r); + goto end; + } + if (fira_crypto_ctx->phy_sts_index_init != 0x041f3ba0) { + pr_err("phy_sts_index_init fail\n"); + goto end; + } + + r = fira_crypto_rotate_elements(fira_crypto_ctx, + fira_crypto_ctx->phy_sts_index_init); + + if (r != 0) { + pr_err("fira_crypto_rotate_elements fail: %d\n", r); + goto end; + } + + if (!compare_bufs(derived_authentication_iv_p_sts, + sizeof(derived_authentication_iv_p_sts), + fira_crypto_ctx->base.derived_authentication_iv, + sizeof(fira_crypto_ctx->base.derived_authentication_iv))) { + pr_err("compare derived_authentication_iv_p_sts fail\n"); + goto end; + } + + if (!compare_bufs(derived_authentication_key_p_sts, + sizeof(derived_authentication_key_p_sts), + fira_crypto_ctx->base.derived_authentication_key, + sizeof(fira_crypto_ctx->base.derived_authentication_key))) { + pr_err("compare derived_authentication_key_p_sts fail\n"); + goto end; + } + + if (!compare_bufs(derived_payload_key_p_sts, + sizeof(derived_payload_key_p_sts), + fira_crypto_ctx->base.derived_payload_key, + sizeof(fira_crypto_ctx->base.derived_payload_key))) { + pr_err("compare derived_payload_key fail\n"); + goto end; + } + + /* Return STS parameters slot 0 */ + crypto_sts_index_p_sts = 0x041f3ba0; + r = fira_crypto_get_sts_params(fira_crypto_ctx, crypto_sts_index_p_sts, sts_v, + sizeof(sts_v), sts_key, sizeof(sts_key)); + if (r != 0) { + pr_err("fira_crypto_get_sts_params fail: %d\n", r); + goto end; + } + if (!compare_bufs(derived_authentication_key_p_sts, + sizeof(derived_authentication_key_p_sts), + sts_key, sizeof(sts_key))) { + pr_err("compare sts_key Fail\n"); + goto end; + } + if (!compare_bufs(sts_v_ref_p_sts, sizeof(sts_v_ref_p_sts), sts_v, + sizeof(sts_v))) { + pr_err("compare sts_v_ref_p_sts Fail\n"); + goto end; + } + + skb = alloc_skb(128, GFP_KERNEL); + if (!skb) { + pr_err("cannot allocate skb\n"); + goto end; + } + + skb_put_data(skb, Header_p_sts, sizeof(Header_p_sts)); + + /* Encrypt Header first */ + r = fira_crypto_encrypt_hie(fira_crypto_ctx, skb, 5, 16); + if (r != 0) { + pr_err("fira_crypto_encrypt_hie fail: %d\n", r); + goto end; + } + if (!compare_bufs(HeaderRef_p_sts, sizeof(HeaderRef_p_sts), skb->data, + skb->len)) { + pr_err("compare HeaderRef_p_sts fail\n"); + goto end; + } + + kfree_skb(skb); + + skb = alloc_skb(128, GFP_KERNEL); + if (!skb) { + pr_err("cannot allocate skb\n"); + goto end; + } + + skb_put_data(skb, RCM_p_sts, sizeof(RCM_p_sts)); + + r = fira_crypto_encrypt_frame(fira_crypto_ctx, skb, 28, 0xaaa1, 0x041f3ba0); + + if (r != 0) { + pr_err("fira_crypto_encrypt_frame fail: %d\n", r); + goto end; + } + if (!compare_bufs(RCMRef_p_sts, sizeof(RCMRef_p_sts), skb->data, + skb->len)) { + pr_err("compare RCMRef_p_sts fail\n"); + goto end; + } + + kfree_skb(skb); + + skb = alloc_skb(128, GFP_KERNEL); + if (!skb) { + pr_err("cannot allocate skb\n"); + goto end; + } + + skb_put_data(skb, Header_RCM_p_sts_Rcv, sizeof(Header_RCM_p_sts_Rcv)); + skb_pull(skb, 28); /* skip header */ + + skb_trim(skb, skb->len - FIRA_CRYPTO_AEAD_AUTHSIZE); + r = fira_crypto_decrypt_frame(fira_crypto_ctx, skb, 28, 0xaaa1, 0x041f3ba0); + if (r != 0) { + pr_err("fira_crypto_decrypt_frame fail: %d\n", r); + goto end; + } + + skb_push(skb, 28); /* restore header */ + + if (!compare_bufs(RCMRef_p_sts_Rcv, sizeof(RCMRef_p_sts_Rcv), skb->data, + skb->len)) { + pr_err("compare RCMRef_p_sts_Rcv fail\n"); + goto end; + } + + kfree_skb(skb); + + skb = alloc_skb(128, GFP_KERNEL); + if (!skb) { + pr_err("cannot allocate skb\n"); + goto end; + } + + skb_put_data(skb, Header_p_sts_Rcv, sizeof(Header_p_sts_Rcv)); + + /* Decrypt header */ + r = fira_crypto_decrypt_hie(fira_crypto_ctx, skb, 10, 16); + if (r != 0) { + pr_err("fira_crypto_decrypt_hie fail: %d\n", r); + goto end; + } + if (!compare_bufs(HeaderRef_p_sts_Rcv, sizeof(HeaderRef_p_sts_Rcv), + skb->data, skb->len)) { + pr_err("compare HeaderRef_p_sts_Rcv fail\n"); + goto end; + } + + err = 0; + + pr_info("Provisioned STS tests success\n"); + +end: + if (skb) + kfree_skb(skb); + if (fira_crypto_ctx) + fira_crypto_context_deinit(fira_crypto_ctx); + + return err; +} + +/** + * fira_crypto_test() - Run the FIRA CONSORTIUM UWB MAC TECHNICAL REQUIREMENTS + * version 1.3.0 test vectors for Static STS and Dynamic STS (Provisioned STS is + * ran instead of pure dynnamic) + * + * + * NOTE: This APis used for unit tests only. + * + * Return: 0 if ok, + */ +int fira_crypto_test(void) +{ + int r = 0; + + r = fira_crypto_test_static() || r; + r = fira_crypto_test_provisioned() || r; + + return r ? -1 : 0; +} diff --git a/mac/fira_crypto.h b/mac/fira_crypto.h new file mode 100644 index 0000000..e89a1e2 --- /dev/null +++ b/mac/fira_crypto.h @@ -0,0 +1,260 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_FIRA_CRYPTO_H +#define NET_MCPS802154_FIRA_CRYPTO_H + +#include <linux/skbuff.h> +#include <linux/kernel.h> + +#include <linux/errno.h> +#include <asm/unaligned.h> +#include <linux/string.h> +#include <linux/ieee802154.h> +#include <linux/printk.h> +#include <net/fira_region_params.h> + +struct fira_crypto; + +/** + * fira_crypto_init() - Callback to initialize crypto module implementation. + * + * @key_manager: Handler to key manager. + * + * Return: 0 or error. + */ +int fira_crypto_init(void *key_manager); + +/** + * struct fira_crypto_params - Arguments grouping structure for the crypto context + * initialization function. + */ +struct fira_crypto_params { + /** + * @session_id: Id of the session using the current fira_crypto. + */ + u32 session_id; + /** + * @sub_session_id: Id of the sub-session using the current fira_crypto. + * It is only used in case of Responder Specific Sub-session Key STS mode 0x04. + */ + u32 sub_session_id; + /** + * @sts_config: The type of STS requested for this crypto. + */ + enum fira_sts_mode sts_config; + /** + * @concat_params: The concatenated parameters of the session/sub-session according + * to the FiRa specs. + */ + const u8 *concat_params; + /** + * @concat_params_size: The size of the concatenated parameters. + */ + int concat_params_size; + /** + * @vupper64: The vupper 64 to use when static STS is used. + */ + const u8 *vupper64; + /** + * @key: The key when provisioned STS is used. + */ + const u8 *key; + /** + * @key_len: The length of the key when provisioned STS is used. + */ + u8 key_len; +}; + +/** + * fira_crypto_get_capabilities() - Query FiRa STS capabilities + * + * Return: FiRa crypto backend capabilities as a bitfield + * (see &enum fira_sts_mode). + */ +u32 fira_crypto_get_capabilities(void); + +/** + * fira_crypto_context_init() - Initialize a crypto context containing the crypto + * elements for a session or sub-session. + * @crypto_params: Parameters to initialize the crypto context. + * @crypto: The initialized crypto context. + * + * Return: 0 or error. + */ +int fira_crypto_context_init(const struct fira_crypto_params *crypto_params, + struct fira_crypto **crypto); + +/** + * fira_crypto_context_deinit() - Deinitialize a crypto context. + * @crypto: The crypto context to deinitialize. + */ +void fira_crypto_context_deinit(struct fira_crypto *crypto); + +/** + * fira_crypto_rotate_elements() - Rotate the crypto elements contained in the + * crypto context. + * + * NOTE: After calling this function, all active crypto elements will be the latest + * rotated ones. + * + * @crypto: The context containing the elements to rotate. + * @crypto_sts_index: The crypto STS index to use to rotate the elements. + * + * Return: 0 or error. + */ +int fira_crypto_rotate_elements(struct fira_crypto *crypto, + const u32 crypto_sts_index); + +/** + * fira_crypto_build_phy_sts_index_init() - Build the phy STS index init value + * related to the given crypto context. + * + * @crypto: The context to use to compute the phy STS index init value. + * + * Return: 0 or error. + */ +int fira_crypto_build_phy_sts_index_init(struct fira_crypto *crypto); + +/** + * fira_crypto_get_phy_sts_index_init() - Get phy_sts_index_init related to the current crypto context. + * @crypto: Context storing the crypto-related parameters. + * + * Return: phy_sts_index_init deduced at crypto context initialization. + */ +u32 fira_crypto_get_phy_sts_index_init(const struct fira_crypto *crypto); + +/** + * fira_crypto_get_sts_params() - Build and get the STS parameters according to + * a specific crypto context. + * + * NOTE: The elements built are the STS value and the STS key. Their construction + * depends on the STS config and is described in the FiRa MAC specification. + * + * @crypto: The context to use to build the STS parameters. + * @crypto_sts_index: The crypto STS index to use to build the STS parameters. + * @sts_v: The output buffer for STS V. + * @sts_v_size: The size of the output buffer for STS V. + * @sts_key: The output buffer for STS key. + * @sts_key_size: The size of the output buffer for STS key. + * + * Return: 0 or error. + */ +int fira_crypto_get_sts_params(struct fira_crypto *crypto, u32 crypto_sts_index, + u8 *sts_v, u32 sts_v_size, u8 *sts_key, + u32 sts_key_size); + +/** + * fira_crypto_prepare_decrypt() - Prepare skb for header decryption and verification. + * @crypto: The crypto context used to decrypt the frame. + * @skb: Buffer containing the frame to decrypt. + * + * Return: 0 or error. + */ +int fira_crypto_prepare_decrypt(struct fira_crypto *crypto, + struct sk_buff *skb); + +/** + * fira_crypto_encrypt_frame() - Encrypt a 802154 frame using a given context. + * + * NOTE: The src address is given as an argument as it is a part of the nonce needed + * to encrypt the frame and it is not present in the 802154 frame. + * The length of the header is given because only the payload is encrypted even if + * the encryption algorithm needs the whole 802154 frame. + * Encryption is done in-place. + * When called this function shall increase the size of the skb of + * FIRA_CRYPTO_AEAD_AUTHSIZE and set the correct bits in the 802154 frame SCF. + * + * @crypto: The context to use to encrypt the frame. + * @skb: The buffer containing the whole frame, skb->data points to the start of + * the 802154 frame header. + * @header_len: The length of the 802154 frame header. Can be used to find the + * position of the 802154 frame payload relative to skb->data. + * @src_short_addr: The short source address attached to the frame. + * @crypto_sts_index: The crypto STS index attached to the frame. + * + * Return: 0 or error. + */ +int fira_crypto_encrypt_frame(struct fira_crypto *crypto, struct sk_buff *skb, + int header_len, __le16 src_short_addr, + u32 crypto_sts_index); + +/** + * fira_crypto_decrypt_frame() - Decrypt a 802.15.4 frame using the given context. + * + * NOTE: The src address is given as an argument as it is a part of the nonce needed to perform the + * decryption and it is not present in the 802.15.4 frame. The length of the header is given as + * only the payload should be decrypted but the algorithm needs the whole 802.15.4 frame. + * Decryption is performed in-place. When calling this function, it shall decrease the size of the + * skb in FIRA_CRYPTO_AEAD_AUTHSIZE bytes and verify the correct bits in the 802.15.4 frame SCF. + * + * @crypto: The context used for frame decryption. + * @skb: The buffer containing the whole frame, skb->data points to the start of the 802.15.4 + * frame payload. + * @header_len: The length of the 802.15.4 frame header. Can be used to find the start of the + * 802.15.4 frame payload relative to skb->data. + * @src_short_addr: The short source address in the current frame. + * @crypto_sts_index: The current crypto STS index. + * + * Return: 0 or error. + */ +int fira_crypto_decrypt_frame(struct fira_crypto *crypto, struct sk_buff *skb, + int header_len, __le16 src_short_addr, + u32 crypto_sts_index); + +/** + * fira_crypto_encrypt_hie() - Encrypt a 802154 header using a given context. + * + * @crypto: The crypto to use to encrypt the frame. + * @skb: The buffer containing the whole frame, skb->data points to the start of + * the 802154 frame header. + * @hie_offset: Offset of the FiRa HIE relative to skb->data. + * @hie_len: The length of the FiRa HIE. + * + * Return: 0 or error. + */ +int fira_crypto_encrypt_hie(struct fira_crypto *crypto, struct sk_buff *skb, + int hie_offset, int hie_len); + +/** + * fira_crypto_decrypt_hie() - Decrypt a 802154 header using a given context. + * + * @crypto: The crypto to use to encrypt the frame. + * @skb: The buffer containing the whole frame, skb->data points to the start of + * the 802154 frame payload. + * @hie_offset: Offset of the FiRa HIE relative to skb->data. + * @hie_len: The length of 802154 header. + * + * Return: 0 or error. + */ +int fira_crypto_decrypt_hie(struct fira_crypto *crypto, struct sk_buff *skb, + int hie_offset, int hie_len); + +/** + * fira_crypto_test() - Autotest for FiRa crypto. + * + * Return: 0 or error. + */ +int fira_crypto_test(void); + +#endif /* NET_MCPS802154_FIRA_CRYPTO_H */ diff --git a/mac/fira_frame.c b/mac/fira_frame.c new file mode 100644 index 0000000..ffb27f8 --- /dev/null +++ b/mac/fira_frame.c @@ -0,0 +1,965 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include "fira_frame.h" +#include "fira_session.h" +#include "fira_crypto.h" +#include "fira_trace.h" + +#include <asm/unaligned.h> +#include <linux/bitfield.h> +#include <linux/errno.h> +#include <linux/ieee802154.h> +#include <linux/math64.h> +#include <net/af_ieee802154.h> +#include <net/mcps802154_frame.h> + +#include "warn_return.h" + +bool fira_frame_check_n_controlees(const struct fira_session *session, + size_t n_controlees, bool active) +{ + /* + * TODO: use more parameters (embedded mode, ranging mode, device + * type...) to calculate the size of frames. + * Currently only SS-TWR vs DS-TWR mode is considered. + * The computation MUST stay "pessimistic" (aka strict). + * E.g.: for control frame, each new controlee consumes 8 bytes so + * we need AT LEAST 8 * n_controlee bytes of "free space". + */ + const struct fira_session_params *params = &session->params; + size_t mrm_size, rcm_size; + size_t n_msg_controller; + size_t n_msg_controlee = 2; + + if (n_controlees > FIRA_CONTROLEES_MAX) + return false; + if (!active) + return true; + + if (params->ranging_round_usage == FIRA_RANGING_ROUND_USAGE_DSTWR) { + mrm_size = FIRA_FRAME_WITHOUT_PAYLOAD_LEN + + FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE1_LEN( + 1, n_controlees); + n_msg_controller = 4; + } else { + mrm_size = FIRA_FRAME_WITHOUT_PAYLOAD_LEN + + FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE2_LEN( + 1, 0, n_controlees); + n_msg_controller = 3; + } + + rcm_size = FIRA_FRAME_WITHOUT_PAYLOAD_LEN + + FIRA_IE_PAYLOAD_CONTROL_LEN(n_msg_controller + + n_msg_controlee * n_controlees); + + return mrm_size <= IEEE802154_MTU && rcm_size <= IEEE802154_MTU; +} + +void fira_frame_header_put(const struct fira_local *local, + const struct fira_slot *slot, struct sk_buff *skb) +{ + const struct fira_session *session = local->current_session; + u16 fc = (IEEE802154_FC_TYPE_DATA | IEEE802154_FC_SECEN | + IEEE802154_FC_INTRA_PAN | IEEE802154_FC_NO_SEQ | + (IEEE802154_ADDR_SHORT << IEEE802154_FC_DAMODE_SHIFT) | + (2 << IEEE802154_FC_VERSION_SHIFT) | + (IEEE802154_ADDR_NONE << IEEE802154_FC_SAMODE_SHIFT)); + u8 *p; + int i; + u8 *p_hie; + + p = skb_put(skb, IEEE802154_FC_LEN + IEEE802154_SHORT_ADDR_LEN + + IEEE802154_SCF_LEN); + put_unaligned_le16(fc, p); + p += IEEE802154_FC_LEN; + put_unaligned_le16(local->dst_short_addr, p); + p += IEEE802154_SHORT_ADDR_LEN; + *p = IEEE802154_SCF_NO_FRAME_COUNTER; + + p_hie = skb->data + skb->len; + mcps802154_ie_put_begin(skb); + p = mcps802154_ie_put_header_ie(skb, IEEE802154_IE_HEADER_VENDOR_ID, + FIRA_IE_HEADER_LEN); + put_unaligned_le24(FIRA_IE_VENDOR_OUI, p); + p += FIRA_IE_VENDOR_OUI_LEN; + for (i = 0; i < FIRA_IE_HEADER_PADDING_LEN; i++) + *p++ = FIRA_IE_HEADER_PADDING; + put_unaligned_le32(session->id, p); + p += FIRA_IE_HEADER_SESSION_ID_LEN; + put_unaligned_le32(fira_sts_get_phy_sts_index(session, slot), p); + fira_sts_encrypt_hie(local->current_session, slot, skb, p_hie - skb->data, + FIRA_IE_HEADER_LEN + IEEE802154_IE_HEADER_LEN); +} + +static u8 *fira_frame_common_payload_put(struct sk_buff *skb, unsigned int len, + enum fira_message_id message_id) +{ + u8 *p; + + p = mcps802154_ie_put_payload_ie(skb, IEEE802154_IE_PAYLOAD_VENDOR_GID, + len); + WARN_RETURN_ON(!p, NULL); + + put_unaligned_le24(FIRA_IE_VENDOR_OUI, p); + p += FIRA_IE_VENDOR_OUI_LEN; + *p++ = message_id; + + return p; +} + +void fira_frame_control_payload_put(const struct fira_local *local, + const struct fira_slot *slot, + struct sk_buff *skb) +{ + const struct fira_session *session = local->current_session; + int n_mngt; + u8 *p; + int i; + + n_mngt = local->access.n_frames - 1 + local->n_stopped_controlees; + + p = fira_frame_common_payload_put(skb, + FIRA_IE_PAYLOAD_CONTROL_LEN(n_mngt), + FIRA_MESSAGE_ID_CONTROL); + + *p++ = n_mngt; + *p++ = 0; + *p++ = session->block_stride_len; + + for (i = 0; i < local->access.n_frames - 1; i++) { + const struct fira_slot *slot = &local->slots[i + 1]; + int initiator = slot->controller_tx; + int slot_index = slot->index; + __le16 short_addr = slot->controller_tx ? + local->src_short_addr : + slot->controlee->short_addr; + int message_id = slot->message_id; + u32 mngt = FIELD_PREP(FIRA_MNGT_RANGING_ROLE, initiator) | + FIELD_PREP(FIRA_MNGT_SLOT_INDEX, slot_index) | + FIELD_PREP(FIRA_MNGT_SHORT_ADDR, short_addr) | + FIELD_PREP(FIRA_MNGT_MESSAGE_ID, message_id); + put_unaligned_le32(mngt, p); + p += sizeof(u32); + } + + for (i = 0; i < local->n_stopped_controlees; i++) { + __le16 short_addr = local->stopped_controlees[i]; + u32 mngt = FIELD_PREP(FIRA_MNGT_SHORT_ADDR, short_addr) | + FIELD_PREP(FIRA_MNGT_STOP, 1); + put_unaligned_le32(mngt, p); + p += sizeof(u32); + } +} + +void fira_frame_measurement_report_payload_put(const struct fira_local *local, + const struct fira_slot *slot, + struct sk_buff *skb) +{ + const struct fira_session *session = local->current_session; + const struct fira_session_params *params = &session->params; + const struct fira_ranging_info *ranging_info = + &local->ranging_info[slot->ranging_index]; + u8 *p; + int hopping_mode = params->round_hopping; + int round_index_present = 1; + int reply_time_present = 0; /* for initiator */ + int n_reply_time = local->n_ranging_valid; + int i; + u32 first_round_trip_time; + u32 reply_time; + u64 initiation_rctu, response_rctu, final_rctu; + bool double_sided = params->ranging_round_usage == + FIRA_RANGING_ROUND_USAGE_DSTWR; + + p = fira_frame_common_payload_put( + skb, + (double_sided ? FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE1_LEN( + round_index_present, n_reply_time) : + FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE2_LEN( + round_index_present, reply_time_present, + n_reply_time)), + FIRA_MESSAGE_ID_MEASUREMENT_REPORT); + + *p++ = FIELD_PREP(FIRA_MEASUREMENT_REPORT_CONTROL_HOPPING_MODE, + hopping_mode) | + FIELD_PREP(FIRA_MEASUREMENT_REPORT_CONTROL_ROUND_INDEX_PRESENT, + round_index_present) | + FIELD_PREP(FIRA_MEASUREMENT_REPORT_CONTROL_N_REPLY_TIME, + n_reply_time); + + if (!double_sided) + *p++ = FIELD_PREP( + FIRA_MEASUREMENT_REPORT_CONTROL_REPLY_TIME_PRESENT, + reply_time_present); + + put_unaligned_le16(session->next_round_index, p); + p += sizeof(u16); + + /* + * No handling for failed measurement, as there is only one, a failed + * measurement will cancel the ranging round. + * With several measurements, make sure a later measurement can still be + * done if an earlier one is failed. + */ + initiation_rctu = + ranging_info + ->timestamps_rctu[FIRA_MESSAGE_ID_RANGING_INITIATION]; + final_rctu = + ranging_info->timestamps_rctu[FIRA_MESSAGE_ID_RANGING_FINAL]; + + /* Retrieve first measurement. */ + for (i = 0; i < local->n_ranging_info; i++) { + ranging_info = &local->ranging_info[i]; + if (!ranging_info->status) + break; + } + response_rctu = + ranging_info->timestamps_rctu[FIRA_MESSAGE_ID_RANGING_RESPONSE]; + if (double_sided) { + /* Add first round trip measurement. */ + first_round_trip_time = mcps802154_difference_timestamp_rctu( + local->llhw, response_rctu, initiation_rctu); + put_unaligned_le32(first_round_trip_time, p); + p += sizeof(u32); + } + /* Retrieve reply measurement. */ + for (; i < local->n_ranging_info; i++) { + ranging_info = &local->ranging_info[i]; + if (ranging_info->status) + continue; + put_unaligned_le16(ranging_info->short_addr, p); + p += sizeof(u16); + response_rctu = ranging_info->timestamps_rctu + [FIRA_MESSAGE_ID_RANGING_RESPONSE]; + if (double_sided) { + reply_time = mcps802154_difference_timestamp_rctu( + local->llhw, final_rctu, response_rctu); + } else { + reply_time = mcps802154_difference_timestamp_rctu( + local->llhw, response_rctu, initiation_rctu); + } + put_unaligned_le32(reply_time, p); + p += sizeof(u32); + } +} + +void fira_frame_result_report_payload_put(const struct fira_local *local, + const struct fira_slot *slot, + struct sk_buff *skb) +{ + const struct fira_session *session = local->current_session; + const struct fira_session_params *params = &session->params; + const struct fira_ranging_info *ranging_info = + &local->ranging_info[slot->ranging_index]; + bool tof_present, aoa_azimuth_present, aoa_elevation_present, + aoa_fom_present, neg_tof_present; + u8 *p; + + tof_present = ranging_info->tof_present && params->report_tof; + aoa_azimuth_present = ranging_info->local_aoa_azimuth.present && + params->report_aoa_azimuth; + aoa_elevation_present = ranging_info->local_aoa_elevation.present && + params->report_aoa_elevation; + aoa_fom_present = (ranging_info->local_aoa_azimuth.aoa_fom || + ranging_info->local_aoa_elevation.aoa_fom) && + params->report_aoa_fom; + neg_tof_present = tof_present && (ranging_info->tof_rctu < 0); + p = fira_frame_common_payload_put( + skb, + FIRA_IE_PAYLOAD_RESULT_REPORT_LEN( + tof_present, aoa_azimuth_present, aoa_elevation_present, + aoa_fom_present, neg_tof_present), + FIRA_MESSAGE_ID_RESULT_REPORT); + + *p++ = FIELD_PREP(FIRA_RESULT_REPORT_CONTROL_TOF_PRESENT, tof_present) | + FIELD_PREP(FIRA_RESULT_REPORT_CONTROL_AOA_AZIMUTH_PRESENT, + aoa_azimuth_present) | + FIELD_PREP(FIRA_RESULT_REPORT_CONTROL_AOA_ELEVATION_PRESENT, + aoa_elevation_present) | + FIELD_PREP(FIRA_RESULT_REPORT_CONTROL_AOA_FOM_PRESENT, + aoa_fom_present) | + FIELD_PREP(FIRA_RESULT_REPORT_CONTROL_NEG_TOF_PRESENT, + neg_tof_present); + + if (tof_present) { + put_unaligned_le32( + ranging_info->tof_rctu > 0 ? ranging_info->tof_rctu : 0, + p); + p += sizeof(u32); + } + if (aoa_azimuth_present) { + put_unaligned_le16(ranging_info->local_aoa_azimuth.aoa_2pi, p); + p += sizeof(u16); + if (aoa_fom_present) { + *p = ranging_info->local_aoa_azimuth.aoa_fom; + p++; + } + } + if (aoa_elevation_present) { + put_unaligned_le16( + ranging_info->local_aoa_elevation.aoa_2pi * 2, p); + p += sizeof(u16); + if (aoa_fom_present) { + *p = ranging_info->local_aoa_elevation.aoa_fom; + p++; + } + } + if (neg_tof_present) { + put_unaligned_le32(-ranging_info->tof_rctu, p); + p += sizeof(u32); + } +} + +void fira_frame_rframe_payload_put(struct fira_local *local, + struct sk_buff *skb) +{ + struct fira_session *session = local->current_session; + const struct fira_session_params *params = &session->params; + u8 *p; + + if (session->data_payload.seq == params->data_payload_seq) + return; + + p = mcps802154_ie_put_payload_ie(skb, IEEE802154_IE_PAYLOAD_VENDOR_GID, + FIRA_IE_VENDOR_OUI_LEN + + params->data_payload_len); + WARN_RETURN_VOID_ON(!p); + put_unaligned_le24(params->data_vendor_oui, p); + p += FIRA_IE_VENDOR_OUI_LEN; + memcpy(p, params->data_payload, params->data_payload_len); + session->data_payload.seq = params->data_payload_seq; + session->data_payload.sent = true; +} + +bool fira_frame_header_check(struct fira_local *local, + const struct fira_slot *slot, struct sk_buff *skb, + struct mcps802154_ie_get_context *ie_get, + u32 *phy_sts_index, u32 *session_id) +{ + struct fira_session *session = local->current_session; + u16 fc = (IEEE802154_FC_TYPE_DATA | IEEE802154_FC_SECEN | + IEEE802154_FC_INTRA_PAN | IEEE802154_FC_NO_SEQ | + IEEE802154_FC_IE_PRESENT | + (IEEE802154_ADDR_SHORT << IEEE802154_FC_DAMODE_SHIFT) | + (2 << IEEE802154_FC_VERSION_SHIFT) | + (IEEE802154_ADDR_NONE << IEEE802154_FC_SAMODE_SHIFT)); + u8 ciphered_hie[FIRA_IE_HEADER_PADDING_LEN + + FIRA_IE_HEADER_SESSION_ID_LEN + + FIRA_IE_HEADER_STS_INDEX_LEN] = { 0 }; + bool fira_header_seen = false; + int r; + u8 *p; + + p = skb->data; + if (!skb_pull(skb, IEEE802154_FC_LEN + IEEE802154_SHORT_ADDR_LEN + + IEEE802154_SCF_LEN) || + get_unaligned_le16(p) != fc) + return false; + + if (fira_sts_prepare_decrypt(session, slot, skb)) + return false; + + for (r = mcps802154_ie_get(skb, ie_get); r == 0 && !ie_get->in_payload; + r = mcps802154_ie_get(skb, ie_get)) { + p = skb->data; + ie_get->mlme_len = 0; + + if (ie_get->id == IEEE802154_IE_HEADER_VENDOR_ID && + ie_get->len >= FIRA_IE_VENDOR_OUI_LEN) { + u32 vendor; + + vendor = get_unaligned_le24(p); + p += FIRA_IE_VENDOR_OUI_LEN; + if (vendor != FIRA_IE_VENDOR_OUI) + goto next; + if (fira_header_seen) + goto hie_error; + if (ie_get->len != FIRA_IE_HEADER_LEN) + goto hie_error; + + memcpy(ciphered_hie, skb->data + FIRA_IE_VENDOR_OUI_LEN, + sizeof(ciphered_hie)); + if (fira_sts_decrypt_hie( + session, slot, skb, FIRA_IE_VENDOR_OUI_LEN, + ie_get->len - FIRA_IE_VENDOR_OUI_LEN)) + goto hie_error; + p += FIRA_IE_HEADER_PADDING_LEN; + *session_id = get_unaligned_le32(p); + p += FIRA_IE_HEADER_SESSION_ID_LEN; + *phy_sts_index = get_unaligned_le32(p); + p += FIRA_IE_HEADER_STS_INDEX_LEN; + fira_header_seen = true; + memcpy(skb->data + FIRA_IE_VENDOR_OUI_LEN, ciphered_hie, + ie_get->len - FIRA_IE_VENDOR_OUI_LEN); + memzero_explicit(ciphered_hie, sizeof(ciphered_hie)); + } + next: + skb_pull(skb, ie_get->len); + } + + return r >= 0 && fira_header_seen; + +hie_error: + skb_pull(skb, ie_get->len); + return false; +} + +static u8 *fira_frame_twr_get_aoa_info(u8 *p, + struct fira_ranging_info *ranging_info, + bool aoa_azimuth_present, + bool aoa_elevation_present, + bool aoa_fom_present) +{ + if (aoa_azimuth_present) { + ranging_info->remote_aoa_azimuth_present = true; + ranging_info->remote_aoa_azimuth_2pi = get_unaligned_le16(p); + p += sizeof(s16); + } + if (aoa_elevation_present) { + ranging_info->remote_aoa_elevation_present = true; + ranging_info->remote_aoa_elevation_pi = get_unaligned_le16(p); + p += sizeof(s16); + } + if (aoa_fom_present) { + u8 aoa_azimuth_fom = 0, aoa_elevation_fom = 0; + + ranging_info->remote_aoa_fom_present = true; + if (aoa_azimuth_present) + aoa_azimuth_fom = *p++; + if (aoa_elevation_present) + aoa_elevation_fom = *p++; + ranging_info->remote_aoa_azimuth_fom = + (aoa_azimuth_fom > 100) ? 0 : aoa_azimuth_fom; + ranging_info->remote_aoa_elevation_fom = + (aoa_elevation_fom > 100) ? 0 : aoa_elevation_fom; + } + return p; +} + +static bool fira_frame_control_read(struct fira_local *local, u8 *p, + unsigned int ie_len, unsigned int *n_slots, + bool *stop, int *block_stride_len) +{ + const struct fira_session *session = local->current_session; + struct fira_slot *slot, last; + int n_mngt, i; + u16 msg_ids = 0; + bool stop_found = false; + const struct fira_measurement_sequence_step *step = + fira_session_get_meas_seq_step(session); + + n_mngt = *p++; + if (ie_len < FIRA_IE_PAYLOAD_CONTROL_LEN(n_mngt)) + return false; + p++; + + *block_stride_len = *p++; + + slot = local->slots; + last = *slot++; + for (i = 0; i < n_mngt; i++) { + u32 mngt; + bool initiator; + int slot_index; + __le16 short_addr; + enum fira_message_id message_id; + bool stop_ranging; + bool is_rframe; + + mngt = get_unaligned_le32(p); + p += sizeof(u32); + + initiator = !!(mngt & FIRA_MNGT_RANGING_ROLE); + slot_index = FIELD_GET(FIRA_MNGT_SLOT_INDEX, mngt); + short_addr = FIELD_GET(FIRA_MNGT_SHORT_ADDR, mngt); + message_id = FIELD_GET(FIRA_MNGT_MESSAGE_ID, mngt); + stop_ranging = !!(mngt & FIRA_MNGT_STOP); + + is_rframe = message_id <= FIRA_MESSAGE_ID_RFRAME_MAX; + if (stop_ranging) { + if (short_addr == local->src_short_addr) { + stop_found = true; + } + continue; + } + + if (slot_index <= last.index || + slot_index >= session->params.round_duration_slots) + return false; + if (initiator && short_addr == local->src_short_addr) + return false; + + last.index = slot_index; + if (message_id <= FIRA_MESSAGE_ID_MAX && + (initiator || short_addr == local->src_short_addr)) { + u16 msg_id = 1 << message_id; + if (message_id == FIRA_MESSAGE_ID_CONTROL_UPDATE && + !initiator) + msg_id <<= 1; + if (msg_id < msg_ids || msg_id & msg_ids) + return false; + msg_ids |= msg_id; + if (slot == local->slots + FIRA_CONTROLEE_FRAMES_MAX) + return false; + last.controller_tx = initiator; + last.ranging_index = 0; + last.message_id = message_id; + if (!initiator) { + last.tx_ant_set = + is_rframe ? step->tx_ant_set_ranging : + step->tx_ant_set_nonranging; + } else { + last.rx_ant_set = fira_session_get_rx_ant_set( + session, message_id); + } + *slot++ = last; + } + } + *stop = stop_found; + *n_slots = slot - local->slots; + + return true; +} + +bool fira_frame_control_payload_check(struct fira_local *local, + struct sk_buff *skb, + struct mcps802154_ie_get_context *ie_get, + unsigned int *n_slots, bool *stop_ranging, + int *block_stride_len) +{ + bool fira_payload_seen = false; + int r; + u8 *p; + + for (r = mcps802154_ie_get(skb, ie_get); r == 0; + r = mcps802154_ie_get(skb, ie_get)) { + p = skb->data; + skb_pull(skb, ie_get->len); + + if (ie_get->id == IEEE802154_IE_PAYLOAD_VENDOR_GID && + ie_get->len >= FIRA_IE_VENDOR_OUI_LEN) { + u32 vendor; + int message_id; + + vendor = get_unaligned_le24(p); + p += FIRA_IE_VENDOR_OUI_LEN; + if (vendor != FIRA_IE_VENDOR_OUI) + continue; + + if (ie_get->len < FIRA_IE_PAYLOAD_CONTROL_LEN(0)) + return false; + message_id = (*p++) & 0xf; + if (message_id != FIRA_MESSAGE_ID_CONTROL) + return false; + + if (fira_payload_seen) + return false; + + if (!fira_frame_control_read(local, p, ie_get->len, + n_slots, stop_ranging, + block_stride_len)) + return false; + + fira_payload_seen = true; + } + } + + return r >= 0 && fira_payload_seen; +} + +static bool +fira_frame_measurement_report_fill_ranging_info(struct fira_local *local, + const struct fira_slot *slot, + u8 *p, unsigned int ie_len) +{ + struct fira_session *session = local->current_session; + struct fira_ranging_info *ranging_info = + &local->ranging_info[slot->ranging_index]; + u8 control; + bool hopping_mode, round_index_present, reply_time_present; + unsigned int n_reply_time; + u32 remote_round_trip_rctu, remote_reply_rctu = 0; + u64 rx_initiation_rctu, tx_response_rctu, rx_final_rctu; + u32 local_round_trip_rctu, local_reply_rctu; + int tof_rctu, i; + bool double_sided = session->params.ranging_round_usage == + FIRA_RANGING_ROUND_USAGE_DSTWR; + control = *p++; + hopping_mode = FIELD_GET(FIRA_MEASUREMENT_REPORT_CONTROL_HOPPING_MODE, + control); + round_index_present = FIELD_GET( + FIRA_MEASUREMENT_REPORT_CONTROL_ROUND_INDEX_PRESENT, control); + n_reply_time = FIELD_GET(FIRA_MEASUREMENT_REPORT_CONTROL_N_REPLY_TIME, + control); + + if (!double_sided) { + control = *p++; + /* Is reply time present? Not supported. */ + reply_time_present = FIELD_GET( + FIRA_MEASUREMENT_REPORT_CONTROL_REPLY_TIME_PRESENT, + control); + if (reply_time_present) { + trace_fira_nondeferred_not_supported(session); + return false; + } + } + + if (ie_len < (double_sided ? + FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE1_LEN( + round_index_present, n_reply_time) : + FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE2_LEN( + round_index_present, reply_time_present, + n_reply_time))) + return false; + + if (round_index_present) { + int next_round_index; + + next_round_index = get_unaligned_le16(p); + p += sizeof(u16); + + session->controlee.next_round_index_valid = true; + session->next_round_index = next_round_index; + } + + if (double_sided) { + /* Remote_round_trip = first_round_trip + first_reply - my_reply. */ + remote_round_trip_rctu = get_unaligned_le32(p); + p += sizeof(u32); + /* Add first_reply. */ + remote_round_trip_rctu += get_unaligned_le32(p + sizeof(u16)); + } + + for (i = 0; i < n_reply_time; i++) { + __le16 short_addr = get_unaligned_le16(p); + p += sizeof(u16); + if (local->src_short_addr == short_addr) { + remote_reply_rctu = get_unaligned_le32(p); + break; + } + p += sizeof(u32); + } + /* Reply time not found. */ + if (i == n_reply_time) + return false; + if (double_sided) + /* Substract my_reply. */ + remote_round_trip_rctu -= remote_reply_rctu; + else + remote_round_trip_rctu = remote_reply_rctu; + + rx_initiation_rctu = + ranging_info + ->timestamps_rctu[FIRA_MESSAGE_ID_RANGING_INITIATION]; + tx_response_rctu = + ranging_info->timestamps_rctu[FIRA_MESSAGE_ID_RANGING_RESPONSE]; + local_reply_rctu = mcps802154_difference_timestamp_rctu( + local->llhw, tx_response_rctu, rx_initiation_rctu); + + if (double_sided) { + rx_final_rctu = + ranging_info + ->timestamps_rctu[FIRA_MESSAGE_ID_RANGING_FINAL]; + local_round_trip_rctu = mcps802154_difference_timestamp_rctu( + local->llhw, rx_final_rctu, tx_response_rctu); + tof_rctu = div64_s64( + (s64)remote_round_trip_rctu * local_round_trip_rctu - + (s64)remote_reply_rctu * local_reply_rctu, + (s64)remote_round_trip_rctu + local_round_trip_rctu + + remote_reply_rctu + local_reply_rctu); + } else { + static const s32 Q26 = 1 << 26; + s32 adjusted_reply_rctu = + (ranging_info->clock_offset_present) ? + (((u64)local_reply_rctu * Q26) / + (Q26 - ranging_info->clock_offset_q26)) : + local_reply_rctu; + tof_rctu = + ((s32)remote_round_trip_rctu - adjusted_reply_rctu) / 2; + } + ranging_info->tof_rctu = (!slot->controller_tx) ? -tof_rctu : tof_rctu; + ranging_info->tof_present = true; + session->controlee.hopping_mode = hopping_mode; + return true; +} + +bool fira_frame_measurement_report_payload_check( + struct fira_local *local, const struct fira_slot *slot, + struct sk_buff *skb, struct mcps802154_ie_get_context *ie_get) +{ + const struct fira_session *session = local->current_session; + const struct fira_session_params *params = &session->params; + bool fira_payload_seen = false; + unsigned int minimum_payload_len; + int r; + u8 *p; + + if (params->ranging_round_usage == FIRA_RANGING_ROUND_USAGE_DSTWR) + minimum_payload_len = + FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE1_LEN(false, 0); + else + minimum_payload_len = + FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE2_LEN(false, + false, 0); + + for (r = mcps802154_ie_get(skb, ie_get); r == 0; + r = mcps802154_ie_get(skb, ie_get)) { + p = skb->data; + skb_pull(skb, ie_get->len); + + if (ie_get->id == IEEE802154_IE_PAYLOAD_VENDOR_GID && + ie_get->len >= FIRA_IE_VENDOR_OUI_LEN) { + u32 vendor; + int message_id; + + vendor = get_unaligned_le24(p); + p += FIRA_IE_VENDOR_OUI_LEN; + if (vendor != FIRA_IE_VENDOR_OUI) + continue; + + if (ie_get->len < minimum_payload_len) + return false; + message_id = (*p++) & 0xf; + if (message_id != FIRA_MESSAGE_ID_MEASUREMENT_REPORT) + return false; + + if (fira_payload_seen) + return false; + + if (!fira_frame_measurement_report_fill_ranging_info( + local, slot, p, ie_get->len)) + return false; + + fira_payload_seen = true; + } + } + + return r >= 0 && fira_payload_seen; +} + +static bool +fira_frame_result_report_fill_ranging_info(struct fira_local *local, + const struct fira_slot *slot, u8 *p, + unsigned int ie_len) +{ + struct fira_ranging_info *ranging_info = + &local->ranging_info[slot->ranging_index]; + u8 control; + bool tof_present, neg_tof_present, aoa_azimuth_present, aoa_elevation_present, + aoa_fom_present; + + control = *p++; + tof_present = !!(control & FIRA_RESULT_REPORT_CONTROL_TOF_PRESENT); + aoa_azimuth_present = + !!(control & FIRA_RESULT_REPORT_CONTROL_AOA_AZIMUTH_PRESENT); + aoa_elevation_present = + !!(control & FIRA_RESULT_REPORT_CONTROL_AOA_ELEVATION_PRESENT); + aoa_fom_present = + !!(control & FIRA_RESULT_REPORT_CONTROL_AOA_FOM_PRESENT); + neg_tof_present = !!(control & FIRA_RESULT_REPORT_CONTROL_NEG_TOF_PRESENT); + if (ie_len < FIRA_IE_PAYLOAD_RESULT_REPORT_LEN( + tof_present, aoa_azimuth_present, + aoa_elevation_present, aoa_fom_present, neg_tof_present)) + return false; + + if (tof_present) { + ranging_info->tof_present = true; + ranging_info->tof_rctu = get_unaligned_le32(p); + p += sizeof(u32); + } + p = fira_frame_twr_get_aoa_info(p, ranging_info, aoa_azimuth_present, + aoa_elevation_present, aoa_fom_present); + if (neg_tof_present) { + /* When negative ToF is present at end of frame, + * ToF read ahead MUST be 0, so, is safe to overwrite */ + ranging_info->tof_rctu = -get_unaligned_le32(p); + p += sizeof(u32); + } + + return true; +} + +bool fira_frame_result_report_payload_check( + struct fira_local *local, const struct fira_slot *slot, + struct sk_buff *skb, struct mcps802154_ie_get_context *ie_get) +{ + bool fira_payload_seen = false; + int r; + u8 *p; + + for (r = mcps802154_ie_get(skb, ie_get); r == 0; + r = mcps802154_ie_get(skb, ie_get)) { + p = skb->data; + skb_pull(skb, ie_get->len); + + if (ie_get->id == IEEE802154_IE_PAYLOAD_VENDOR_GID && + ie_get->len >= FIRA_IE_VENDOR_OUI_LEN) { + u32 vendor; + int message_id; + + vendor = get_unaligned_le24(p); + p += FIRA_IE_VENDOR_OUI_LEN; + if (vendor != FIRA_IE_VENDOR_OUI) + continue; + + if (ie_get->len < FIRA_IE_PAYLOAD_RESULT_REPORT_LEN( + false, false, false, false, false)) + return false; + message_id = (*p++) & 0xf; + if (message_id != FIRA_MESSAGE_ID_RESULT_REPORT) + return false; + + if (fira_payload_seen) + return false; + + if (!fira_frame_result_report_fill_ranging_info( + local, slot, p, ie_get->len)) + return false; + + fira_payload_seen = true; + } + } + + return r >= 0 && fira_payload_seen; +} + +bool fira_frame_rframe_payload_check(struct fira_local *local, + const struct fira_slot *slot, + struct sk_buff *skb, + struct mcps802154_ie_get_context *ie_get) +{ + const struct fira_session *session = local->current_session; + const struct fira_session_params *params = &session->params; + struct fira_ranging_info *ranging_info = + &local->ranging_info[slot->ranging_index]; + bool rframe_payload_seen = false; + int r; + u8 *p; + + for (r = mcps802154_ie_get(skb, ie_get); r == 0; + r = mcps802154_ie_get(skb, ie_get)) { + p = skb->data; + skb_pull(skb, ie_get->len); + + if (ie_get->id == IEEE802154_IE_PAYLOAD_VENDOR_GID && + ie_get->len >= FIRA_IE_VENDOR_OUI_LEN && + ie_get->len <= FIRA_IE_VENDOR_OUI_LEN + FIRA_DATA_PAYLOAD_SIZE_MAX) { + u32 vendor; + unsigned int data_len; + + vendor = get_unaligned_le24(p); + p += FIRA_IE_VENDOR_OUI_LEN; + if (vendor != params->data_vendor_oui) + continue; + + if (ie_get->len < FIRA_IE_VENDOR_OUI_LEN + 1) + continue; + + if (rframe_payload_seen) + return false; + + data_len = ie_get->len - FIRA_IE_VENDOR_OUI_LEN; + memcpy(&ranging_info->data_payload, p, data_len); + ranging_info->data_payload_len = data_len; + + rframe_payload_seen = true; + } + } + + return r >= 0; +} + +struct fira_session *fira_rx_frame_control_header_check( + struct fira_local *local, const struct fira_slot *slot, + struct sk_buff *skb, struct mcps802154_ie_get_context *ie_get, + u32 *phy_sts_index) +{ + const struct fira_session *session = local->current_session; + struct fira_session *session_found = NULL; + u32 session_id; + + if (!fira_frame_header_check(local, slot, skb, ie_get, phy_sts_index, + &session_id)) + return NULL; + if (session->id == session_id) { + session_found = local->current_session; + } else if (session->controlee.synchronised) { + return NULL; + } else { + session_found = + fira_get_session_by_session_id(local, session_id); + if (!session_found || + session_found->params.device_type != + FIRA_DEVICE_TYPE_CONTROLEE || + !fira_session_is_active(session_found)) + return NULL; + /* + * FIXME: The previous session will not sent a ranging round + * report failure. + * + * The most simple is probably to remove a round ranging? + * or keep somewhere, previous value. + * or choice number 3. + * ``` + * int remove_blocks = session->block_stride_len + 1; + * + * session->block_start_dtu -= remove_blocks * + * params->block_duration_dtu; + * session->block_index -= remove_blocks; + * ``` + */ + } + /* Update current and allow content of session to be updated. */ + local->current_session = session_found; + return session_found; +} + +int fira_frame_header_check_decrypt(struct fira_local *local, + const struct fira_slot *slot, + struct sk_buff *skb, + struct mcps802154_ie_get_context *ie_get) +{ + struct fira_session *session = local->current_session; + int header_len; + __le16 src_short_addr; + u32 phy_sts_index; + u32 session_id; + u8 *header; + + header = skb->data; + + if (!fira_frame_header_check(local, slot, skb, ie_get, &phy_sts_index, + &session_id)) + return -EBADMSG; + if (session_id != session->id) + return -EBADMSG; + + if (phy_sts_index != fira_sts_get_phy_sts_index(session, slot)) + return -EBADMSG; + + header_len = skb->data - header; + src_short_addr = slot->controller_tx ? local->dst_short_addr : + slot->controlee->short_addr; + return fira_sts_decrypt_frame(session, slot, skb, header_len, src_short_addr); +} diff --git a/mac/fira_frame.h b/mac/fira_frame.h new file mode 100644 index 0000000..64e7f81 --- /dev/null +++ b/mac/fira_frame.h @@ -0,0 +1,268 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef FIRA_FRAME_H +#define FIRA_FRAME_H + +#include <linux/types.h> + +struct fira_local; +struct fira_session; +struct fira_slot; +struct sk_buff; +struct mcps802154_ie_get_context; +struct fira_session_params; + +#define FIRA_IE_VENDOR_OUI_LEN 3 +#define FIRA_IE_HEADER_PADDING_LEN 8 +#define FIRA_IE_HEADER_SESSION_ID_LEN 4 +#define FIRA_IE_HEADER_STS_INDEX_LEN 4 +#define FIRA_IE_HEADER_LEN \ + (FIRA_IE_VENDOR_OUI_LEN + FIRA_IE_HEADER_PADDING_LEN + \ + FIRA_IE_HEADER_SESSION_ID_LEN + FIRA_IE_HEADER_STS_INDEX_LEN) + +#define FIRA_IE_PAYLOAD_CONTROL_LEN(n_mngt) \ + (FIRA_IE_VENDOR_OUI_LEN + 4 + 4 * (n_mngt)) +#define FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE1_LEN(round_index_present, \ + n_reply_time) \ + (FIRA_IE_VENDOR_OUI_LEN + 2 + 2 * (round_index_present) + 4 + \ + 6 * (n_reply_time)) +#define FIRA_IE_PAYLOAD_MEASUREMENT_REPORT_TYPE2_LEN( \ + round_index_present, reply_time_present, n_reply_time) \ + (FIRA_IE_VENDOR_OUI_LEN + 3 + 2 * (round_index_present) + \ + 4 * (reply_time_present) + 6 * (n_reply_time)) +#define FIRA_IE_PAYLOAD_RESULT_REPORT_LEN(tof_present, aoa_azimuth_present, \ + aoa_elevation_present, \ + aoa_fom_present, neg_tof_present) \ + (FIRA_IE_VENDOR_OUI_LEN + 2 + 4 * (tof_present) + \ + 2 * (aoa_azimuth_present) + 2 * (aoa_elevation_present) + \ + (aoa_fom_present) * \ + (1 * (aoa_azimuth_present) + 1 * (aoa_elevation_present)) + \ + 4 * (neg_tof_present)) + +#define FIRA_MIC_LEVEL 64 +#define FIRA_MIC_LEN (FIRA_MIC_LEVEL / 8) + +/* 3 IE headers in the frame : vendor IE, header terminator and payload. */ +#define FIRA_FRAME_WITHOUT_PAYLOAD_LEN \ + (IEEE802154_FC_LEN + IEEE802154_SCF_LEN + IEEE802154_SHORT_ADDR_LEN + \ + 3 * IEEE802154_IE_HEADER_LEN + FIRA_IE_HEADER_LEN + FIRA_MIC_LEN + \ + IEEE802154_FCS_LEN) + +#define FIRA_IE_VENDOR_OUI 0x5a18ff +#define FIRA_IE_HEADER_PADDING 0x08 + +#define FIRA_MNGT_RANGING_ROLE (1 << 0) +#define FIRA_MNGT_SLOT_INDEX (0xff << 1) +#define FIRA_MNGT_SHORT_ADDR (0xffff << 9) +#define FIRA_MNGT_MESSAGE_ID (0xf << 25) +#define FIRA_MNGT_STOP (1 << 29) +#define FIRA_MNGT_RESERVED (0x3U << 30) + +#define FIRA_MEASUREMENT_REPORT_CONTROL_HOPPING_MODE (1 << 0) +#define FIRA_MEASUREMENT_REPORT_CONTROL_ROUND_INDEX_PRESENT (1 << 1) +#define FIRA_MEASUREMENT_REPORT_CONTROL_N_REPLY_TIME (0x3f << 2) + +#define FIRA_MEASUREMENT_REPORT_CONTROL_REPLY_TIME_PRESENT (1 << 0) + +#define FIRA_RESULT_REPORT_CONTROL_TOF_PRESENT (1 << 0) +#define FIRA_RESULT_REPORT_CONTROL_AOA_AZIMUTH_PRESENT (1 << 1) +#define FIRA_RESULT_REPORT_CONTROL_AOA_ELEVATION_PRESENT (1 << 2) +#define FIRA_RESULT_REPORT_CONTROL_AOA_FOM_PRESENT (1 << 3) +#define FIRA_RESULT_REPORT_CONTROL_NEG_TOF_PRESENT (1 << 4) + +/** + * fira_frame_check_n_controlees() - Check the number of wanted + * controlees. + * @session: Current session. + * @n_controlees: Wanted number of controlees. + * @active: Is the session (supposed to be) active? + * + * Return: true if number of controlees fits. + * + * For an inactive session, the number of controlees is limited by the list + * size, aka FIRA_CONTROLEES_MAX. + * For an active session, it depends on the space left in messages, which is + * determined by the session parameters. + */ +bool fira_frame_check_n_controlees(const struct fira_session *session, + size_t n_controlees, bool active); + +/** + * fira_frame_header_put() - Fill FiRa frame header. + * @local: FiRa context. + * @slot: Slot information. + * @skb: Frame buffer. + */ +void fira_frame_header_put(const struct fira_local *local, + const struct fira_slot *slot, struct sk_buff *skb); + +/** + * fira_frame_control_payload_put() - Fill FiRa frame payload for a control + * message. + * @local: FiRa context. + * @slot: Slot information. + * @skb: Frame buffer. + */ +void fira_frame_control_payload_put(const struct fira_local *local, + const struct fira_slot *slot, + struct sk_buff *skb); + +/** + * fira_frame_measurement_report_payload_put() - Fill FiRa frame payload for + * a measurement report message. + * @local: FiRa context. + * @slot: Slot information. + * @skb: Frame buffer. + */ +void fira_frame_measurement_report_payload_put(const struct fira_local *local, + const struct fira_slot *slot, + struct sk_buff *skb); + +/** + * fira_frame_result_report_payload_put() - Fill FiRa frame payload for a result + * report message. + * @local: FiRa context. + * @slot: Slot information. + * @skb: Frame buffer. + */ +void fira_frame_result_report_payload_put(const struct fira_local *local, + const struct fira_slot *slot, + struct sk_buff *skb); + +/** + * fira_frame_rframe_payload_put() - Check availability of a custom data + * payload, write it to tx frame. + * @local: FiRa context. + * @skb: Frame buffer. + */ +void fira_frame_rframe_payload_put(struct fira_local *local, + struct sk_buff *skb); + +/** + * fira_frame_header_check() - Check and consume FiRa header. + * @local: FiRa context. + * @skb: Frame buffer. + * @ie_get: Context used to read IE, must be zero initialized. + * @phy_sts_index: STS index read from header. + * @session_id: Session id read from header. + * + * Return: true if header is correct. + */ +bool fira_frame_header_check(struct fira_local *local, + const struct fira_slot *slot, struct sk_buff *skb, + struct mcps802154_ie_get_context *ie_get, + u32 *phy_sts_index, u32 *session_id); + +/** + * fira_frame_control_payload_check() - Check FiRa frame payload for a control + * message. + * @local: FiRa context. + * @skb: Frame buffer. + * @ie_get: Context used to read IE, must have been used to read header first. + * @n_slots: Pointer where to store number of used slots. + * @stop_ranging: True if the message indicates that the ranging must be stopped. + * @block_stride_len: Pointer where to store number of blocks to stride. + * + * Return: true if message is correct. Extra payload is accepted. + */ +bool fira_frame_control_payload_check(struct fira_local *local, + struct sk_buff *skb, + struct mcps802154_ie_get_context *ie_get, + unsigned int *n_slots, bool *stop_ranging, + int *block_stride_len); + +/** + * fira_frame_measurement_report_payload_check() - Check FiRa frame payload for + * a measurement report message. + * @local: FiRa context. + * @slot: Slot information. + * @skb: Frame buffer. + * @ie_get: Context used to read IE, must have been used to read header first. + * + * Return: true if message is correct. Extra payload is accepted. + */ +bool fira_frame_measurement_report_payload_check( + struct fira_local *local, const struct fira_slot *slot, + struct sk_buff *skb, struct mcps802154_ie_get_context *ie_get); + +/** + * fira_frame_result_report_payload_check() - Check FiRa frame payload for + * a result report message. + * @local: FiRa context. + * @slot: Slot information. + * @skb: Frame buffer. + * @ie_get: Context used to read IE, must have been used to read header first. + * + * Return: true if message is correct. Extra payload is accepted. + */ +bool fira_frame_result_report_payload_check( + struct fira_local *local, const struct fira_slot *slot, + struct sk_buff *skb, struct mcps802154_ie_get_context *ie_get); + +/** + * fira_frame_rframe_payload_check() - Parse custom data from ranging frame. + * @local: FiRa context. + * @slot: Slot information. + * @skb: Frame buffer. + * @ie_get: Context used to read IE, must have been used to read header first. + * + * Return: true if message is correct. Extra payload is accepted. + */ +bool fira_frame_rframe_payload_check(struct fira_local *local, + const struct fira_slot *slot, + struct sk_buff *skb, + struct mcps802154_ie_get_context *ie_get); + +/** + * fira_rx_frame_control_header_check() - Check control frame and consume + * header. + * @local: FiRa context. + * @slot: Corresponding slot. + * @skb: Frame buffer. + * @ie_get: Context used to read IE, must be zero initialized. + * @phy_sts_index: STS index received. + * + * Return: Session context or NULL. + */ +struct fira_session *fira_rx_frame_control_header_check( + struct fira_local *local, const struct fira_slot *slot, + struct sk_buff *skb, struct mcps802154_ie_get_context *ie_get, + u32 *phy_sts_index); + +/** + * fira_frame_header_check_decrypt() - Check and consume header, and decrypt + * payload. + * @local: FiRa context. + * @slot: Corresponding slot. + * @skb: Frame buffer. + * @ie_get: Context used to read IE, must be zero initialized. + * + * Return: 0 or error. + */ +int fira_frame_header_check_decrypt(struct fira_local *local, + const struct fira_slot *slot, + struct sk_buff *skb, + struct mcps802154_ie_get_context *ie_get); + +#endif /* FIRA_FRAME_H */ diff --git a/mac/fira_region.c b/mac/fira_region.c new file mode 100644 index 0000000..9e4bf53 --- /dev/null +++ b/mac/fira_region.c @@ -0,0 +1,482 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/slab.h> +#include <linux/netdevice.h> +#include <linux/errno.h> + +#include <net/mcps802154_schedule.h> +#include <net/fira_region_nl.h> + +#include "fira_region.h" +#include "fira_region_call.h" +#include "fira_access.h" +#include "fira_session.h" +#include "fira_crypto.h" +#include "warn_return.h" + +static struct mcps802154_region_ops fira_region_ops; +static bool do_crypto_selftest_on_module_init; + +static void fira_report_event(struct work_struct *work) +{ + struct fira_local *local = + container_of(work, struct fira_local, report_work); + struct sk_buff *skb; + int r; + + while (!skb_queue_empty(&local->report_queue)) { + skb = skb_dequeue(&local->report_queue); + r = mcps802154_region_event(local->llhw, skb); + if (r == -ECONNREFUSED) + /* TODO stop. */ + ; + } +} + +static struct mcps802154_region *fira_open(struct mcps802154_llhw *llhw) +{ + struct fira_local *local; + + local = kzalloc(sizeof(*local), GFP_KERNEL); + if (!local) + return NULL; + local->llhw = llhw; + local->region.ops = &fira_region_ops; + INIT_LIST_HEAD(&local->inactive_sessions); + INIT_LIST_HEAD(&local->active_sessions); + skb_queue_head_init(&local->report_queue); + INIT_WORK(&local->report_work, fira_report_event); + /* FIXME: Hack to simplify unit test, which is borderline. */ + local->block_duration_rx_margin_ppm = UWB_BLOCK_DURATION_MARGIN_PPM; + fira_crypto_init(NULL); + return &local->region; +} + +static void fira_close(struct mcps802154_region *region) +{ + struct fira_local *local = region_to_local(region); + struct fira_session *session, *s; + + list_for_each_entry_safe (session, s, &local->inactive_sessions, + entry) { + fira_session_free(local, session); + } + + cancel_work_sync(&local->report_work); + skb_queue_purge(&local->report_queue); + kfree_sensitive(local); +} + +static void fira_notify_stop(struct mcps802154_region *region) +{ + struct fira_local *local = region_to_local(region); + struct fira_session *session, *s; + + list_for_each_entry_safe (session, s, &local->active_sessions, entry) { + fira_session_stop_controlees(session); + fira_session_fsm_stop(local, session); + } +} + +static int fira_call(struct mcps802154_region *region, u32 call_id, + const struct nlattr *attrs, const struct genl_info *info) +{ + struct fira_local *local = region_to_local(region); + + switch (call_id) { + case FIRA_CALL_GET_CAPABILITIES: + return fira_get_capabilities(local, info); + default: + return fira_session_control(local, call_id, attrs, info); + } +} + +/** + * fira_session_init_block_start_dtu() - Build the first block start dtu. + * @local: FiRa context. + * @session: Session context. + * @timestamp_dtu: First access opportunity. + */ +static void fira_session_init_block_start_dtu(struct fira_local *local, + struct fira_session *session, + u32 timestamp_dtu) +{ + if (!session->block_start_valid) { + const struct fira_session_params *params = &session->params; + int dtu_freq_khz = local->llhw->dtu_freq_hz / 1000; + + session->block_start_valid = true; + session->block_start_dtu = + timestamp_dtu + + params->initiation_time_ms * dtu_freq_khz; + session->next_access_timestamp_dtu = session->block_start_dtu; + } +} + +/** + * fira_get_next_session() - Find the next session which should have the + * access. + * @local: FiRa context. + * @next_timestamp_dtu: Next access opportunity. + * @max_duration_dtu: Max duration of the next access opportunity. + * @adopted_demand: Output updated related to next session returned. + * + * Return: Pointer to the next session which should have the access. + */ +static struct fira_session * +fira_get_next_session(struct fira_local *local, u32 next_timestamp_dtu, + int max_duration_dtu, + struct fira_session_demand *adopted_demand) +{ + struct fira_session *adopted_session = NULL; + struct fira_session *session; + bool is_candidate_unsync, is_adopted_unsync; + int max_unsync_duration_dtu = max_duration_dtu; + int r; + + /* + * Little cheat to start all sessions as initiation_time_ms is a + * delay, and not a absolute time. This is the only function + * which change the session content. + */ + list_for_each_entry (session, &local->active_sessions, entry) { + fira_session_init_block_start_dtu(local, session, + next_timestamp_dtu); + } + + /* + * Reminder: active_sessions list is sorted by session->priority + * from highest priority to lowest priority. + */ + list_for_each_entry (session, &local->active_sessions, entry) { + /* + * Welcome in sessions election! + * + * First, the candidate session will provide its wish in + * 'candidate_demand'. + * And then the candidate will be compared with the adopted + * session. The "best" will be become or stay the adopted + * session. + * So the session election will process candidate after + * candidate, to find the most appropriate session. + */ + struct fira_session_demand candidate_demand; + + /* + * Sessions with lower priority are not allowed to overlap + * the adopted session. But a lower priority can start and + * stop before the session adopted. + */ + if (adopted_session && adopted_session->params.priority != + session->params.priority) { + /* Is there some time left? */ + if (is_before_dtu(next_timestamp_dtu, + adopted_demand->timestamp_dtu)) + /* + * Limit max duration for session with lower + * priority to not overlap sessions which have + * an higher priority. + */ + max_duration_dtu = + adopted_demand->timestamp_dtu - + next_timestamp_dtu; + else + /* No more time left. */ + break; + if (!max_unsync_duration_dtu || + max_unsync_duration_dtu > max_duration_dtu) + max_unsync_duration_dtu = max_duration_dtu; + } + + is_candidate_unsync = session->params.device_type == + FIRA_DEVICE_TYPE_CONTROLEE && + !session->controlee.synchronised; + /* Retrieve the wish of the session candidate. */ + r = fira_session_fsm_get_demand( + local, session, next_timestamp_dtu, + is_candidate_unsync ? max_unsync_duration_dtu : + max_duration_dtu, + &candidate_demand); + /* When 'r' is one, the session have a demand. */ + if (r != 1) + /* The session doesn't have a demand. */ + continue; + + /* + * If there is no adopted session, the candidate is the + * adopted session. + */ + if (!adopted_session) + goto candidate_adopted; + /* + * Is session finish before the adopted session ? + * adopted_demand | [-----] + * candidate | [------] + * --+-----------------------> Time + */ + if (is_before_dtu(candidate_demand.timestamp_dtu + + candidate_demand.max_duration_dtu, + adopted_demand->timestamp_dtu)) + /* + * Candidate is adopted and replace the + * previous one. + */ + goto candidate_adopted; + /* + * Is session start after the adopted session ? + * adopted_demand | [------] + * candidate | [--------] + * --+----------------------> Time + */ + if (is_before_dtu(adopted_demand->timestamp_dtu + + adopted_demand->max_duration_dtu, + candidate_demand.timestamp_dtu)) + /* Candidate is not adopted. */ + continue; + /* + * The candidate session have an overlap with the adopted + * session. Try the negotiation first to find an agreement + * about the access usage. + * + * But take care, synchronized session have a better + * eloquence in case of negotiation failure with an + * unsynchronized session. + */ + is_adopted_unsync = adopted_session->params.device_type == + FIRA_DEVICE_TYPE_CONTROLEE && + !adopted_session->controlee.synchronised; + /* + * The candidate session have an overlap with the adopted + * session. + * + * adopted_demand | [------] + * candidate | [--------] + * --+----------------------> Time + * + * Request a duration reduction to the adopted session. + */ + if (is_adopted_unsync && + !is_before_dtu(candidate_demand.timestamp_dtu, + adopted_demand->timestamp_dtu)) { + int limit_duration_dtu = + candidate_demand.timestamp_dtu - + adopted_demand->timestamp_dtu; + struct fira_session_demand tmp; + + if (limit_duration_dtu) + /* Ask to reduce the duration. */ + r = fira_session_fsm_get_demand( + local, adopted_session, + next_timestamp_dtu, limit_duration_dtu, + &tmp); + else + /* Both sessions start at same time. */ + r = 0; + if (r == 1) { + /* + * The adopted session accept to + * reduction its max duration. + */ + *adopted_demand = tmp; + max_unsync_duration_dtu = limit_duration_dtu; + continue; + } + if (!is_candidate_unsync) + /* + * In this corrupted world, synchronized + * session have better relation. + */ + goto candidate_adopted; + } + /* + * The candidate session have an overlap with the adopted + * session. + * + * adopted_demand | [-----] + * candidate | [------] + * --+----------------------> Time + * + * Request a duration reduction to the candidate session. + */ + if (is_candidate_unsync && + !is_before_dtu(adopted_demand->timestamp_dtu, + candidate_demand.timestamp_dtu)) { + int limit_duration_dtu = adopted_demand->timestamp_dtu - + candidate_demand.timestamp_dtu; + struct fira_session_demand tmp; + + if (limit_duration_dtu) + /* Ask to reduce the duration. */ + r = fira_session_fsm_get_demand( + local, session, next_timestamp_dtu, + limit_duration_dtu, &tmp); + else + /* Both sessions start at same time. */ + r = 0; + if (r == 1) { + /* + * The candidate session accept to + * reduction its max duration. + */ + adopted_session = session; + *adopted_demand = tmp; + max_unsync_duration_dtu = limit_duration_dtu; + continue; + } + if (!is_adopted_unsync) + /* + * In this corrupted world, synchronized + * session have better relation. + */ + continue; + } + + /* + * Finally, negotiation between adopted and candidate fails. + * One of the session will probably have ranging not done. + * Choose the session which have the oldest access. + */ + if (is_before_dtu(session->last_access_timestamp_dtu, + adopted_session->last_access_timestamp_dtu)) + goto candidate_adopted; + + /* Candidate is not adopted. */ + continue; + + candidate_adopted: + adopted_session = session; + *adopted_demand = candidate_demand; + } + return adopted_session; +} + +static struct mcps802154_access * +fira_get_access(struct mcps802154_region *region, u32 next_timestamp_dtu, + int next_in_region_dtu, int region_duration_dtu) +{ + struct fira_local *local = region_to_local(region); + /* 'fsd' acronyms is FiRa Session Demand. */ + struct fira_session_demand fsd; + struct fira_session *session; + int max_duration_dtu = + region_duration_dtu ? region_duration_dtu - next_in_region_dtu : + 0; + + session = fira_get_next_session(local, next_timestamp_dtu, + max_duration_dtu, &fsd); + if (session) + return fira_session_fsm_get_access(local, session, &fsd); + return NULL; +} + +static int fira_get_demand(struct mcps802154_region *region, + u32 next_timestamp_dtu, + struct mcps802154_region_demand *next_demand) +{ + struct fira_local *local = region_to_local(region); + /* 'fsd' for FiRa Session Demand. */ + struct fira_session_demand fsd; + struct fira_session *session; + + session = fira_get_next_session(local, next_timestamp_dtu, 0, &fsd); + if (session) { + next_demand->timestamp_dtu = fsd.timestamp_dtu; + next_demand->max_duration_dtu = fsd.max_duration_dtu; + return 1; + } + return 0; +} + +static struct mcps802154_region_ops fira_region_ops = { + .owner = THIS_MODULE, + .name = "fira", + .open = fira_open, + .close = fira_close, + .notify_stop = fira_notify_stop, + .call = fira_call, + .get_access = fira_get_access, + .get_demand = fira_get_demand, +}; + +struct fira_session *fira_get_session_by_session_id(struct fira_local *local, + u32 session_id) +{ + struct fira_session *session; + + list_for_each_entry (session, &local->active_sessions, entry) { + if (session->id == session_id) + return session; + } + list_for_each_entry (session, &local->inactive_sessions, entry) { + if (session->id == session_id) + return session; + } + return NULL; +} + +void fira_check_all_missed_ranging(struct fira_local *local, + const struct fira_session *recent_session, + u32 timestamp_dtu) +{ + struct fira_session *session, *s; + + /* + * Process sessions with safe function, as the session FSM can leave + * the active list for many stop reasons. + */ + list_for_each_entry_safe (session, s, &local->active_sessions, entry) { + if (recent_session == session) + continue; + /* + * Does the session started during the access of + * recent_session? + */ + if (!session->block_start_valid) + continue; + fira_session_fsm_check_missed_ranging(local, session, + timestamp_dtu); + } +} + +int __init fira_region_init(void) +{ + if (do_crypto_selftest_on_module_init) + WARN_RETURN(fira_crypto_test()); + + return mcps802154_region_register(&fira_region_ops); +} + +void __exit fira_region_exit(void) +{ + mcps802154_region_unregister(&fira_region_ops); +} + +module_param_named(crypto_selftest, do_crypto_selftest_on_module_init, bool, 0644); +module_init(fira_region_init); +module_exit(fira_region_exit); + +MODULE_DESCRIPTION("FiRa Region for IEEE 802.15.4 MCPS"); +MODULE_AUTHOR("Nicolas Schodet <nicolas.schodet@qorvo.com>"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/mac/fira_region.h b/mac/fira_region.h new file mode 100644 index 0000000..6038d09 --- /dev/null +++ b/mac/fira_region.h @@ -0,0 +1,473 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_FIRA_REGION_H +#define NET_FIRA_REGION_H + +#include <linux/kernel.h> +#include <linux/workqueue.h> +#include <linux/math64.h> +#include <net/mcps802154_schedule.h> + +#include "net/fira_region_params.h" + +#define FIRA_SLOT_DURATION_RSTU_DEFAULT 2400 +#define FIRA_BLOCK_DURATION_MS_DEFAULT 200 +#define FIRA_ROUND_DURATION_SLOTS_DEFAULT 30 +#define FIRA_MAX_RR_RETRY_DEFAULT 0 +#define FIRA_PRIORITY_MAX 100 +#define FIRA_PRIORITY_DEFAULT 50 +#define FIRA_IN_BAND_TERMINATION_ATTEMPT_COUNT_MAX 10 +#define FIRA_IN_BAND_TERMINATION_ATTEMPT_COUNT_MIN 1 +#define FIRA_BOOLEAN_MAX 1 +#define FIRA_BLOCK_STRIDE_LEN_MAX 255 +#define FIRA_FRAMES_MAX (3 + 3 * FIRA_CONTROLEES_MAX) +#define FIRA_CONTROLEE_FRAMES_MAX (3 + 3 + 1) +/* IEEE 802.15.4z 2020 section 6.9.7.2 */ +#define UWB_BLOCK_DURATION_MARGIN_PPM 100 +/* FiRa Tx should arrive between 0 and 10 us, always add 2 us. */ +#define FIRA_TX_MARGIN_US 2 + +/* + * FIRA_SESSION_DATA_NTF_LOWER_/UPPER_BOUND_AOA min/max : + * Azimuth in rad_2pi_q16 : -32768 / 32767 (equal to -180 / ~180 degrees) + * Elevation in rad_2pi_q16 : -16384 / 16384 (equal to -90 / 90 degrees) + */ +#define FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI_MIN -32768 +#define FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI_MAX 32767 +#define FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI_MIN -32768 +#define FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI_MAX 32767 +#define FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI_MIN -16384 +#define FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI_MAX 16384 +#define FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI_MIN -16384 +#define FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI_MAX 16384 + +/** + * enum fira_message_id - Message identifiers, used in internal state and in + * messages. + * @FIRA_MESSAGE_ID_RANGING_INITIATION: Initial ranging message. + * @FIRA_MESSAGE_ID_RANGING_RESPONSE: Response ranging message. + * @FIRA_MESSAGE_ID_RANGING_FINAL: Final ranging message, only for DS-TWR. + * @FIRA_MESSAGE_ID_CONTROL: Control message, sent by the controller. + * @FIRA_MESSAGE_ID_MEASUREMENT_REPORT: Deferred report of ranging measures. + * @FIRA_MESSAGE_ID_RESULT_REPORT: Report computed ranging result. + * @FIRA_MESSAGE_ID_CONTROL_UPDATE: Message to change hopping. + * @FIRA_MESSAGE_ID_RFRAME_MAX: Maximum identifier of message transmitted using + * an RFRAME. + * @FIRA_MESSAGE_ID_MAX: Maximum message identifier. + */ +enum fira_message_id { + FIRA_MESSAGE_ID_RANGING_INITIATION = 0, + FIRA_MESSAGE_ID_RANGING_RESPONSE = 1, + FIRA_MESSAGE_ID_RANGING_FINAL = 2, + FIRA_MESSAGE_ID_CONTROL = 3, + FIRA_MESSAGE_ID_MEASUREMENT_REPORT = 4, + FIRA_MESSAGE_ID_RESULT_REPORT = 5, + FIRA_MESSAGE_ID_CONTROL_UPDATE = 6, + FIRA_MESSAGE_ID_RFRAME_MAX = FIRA_MESSAGE_ID_RANGING_FINAL, + FIRA_MESSAGE_ID_MAX = FIRA_MESSAGE_ID_CONTROL_UPDATE, +}; + +/** + * struct fira_diagnostic - Diagnostic result. + */ +struct fira_diagnostic { + /** + * @rssis_q1: Received signal strength indication (RSSI), absolute value + * in Q1 fixed point format, unit is dBm. + */ + u8 rssis_q1[MCPS802154_RSSIS_N_MAX]; + /** + * @n_rssis: The number of RSSI in the array below. + */ + size_t n_rssis; + /** + * @aoas: Angle of arrival, ordered by increasing measurement type. + */ + struct mcps802154_rx_aoa_measurements + aoas[MCPS802154_RX_AOA_MEASUREMENTS_MAX]; + /** + * @n_aoas: Number of angle of arrival. + */ + size_t n_aoas; + /** + * @cirs: CIR for different parts of the frame. + * + * Set by low-level driver, must be kept valid until next received + * frame. + */ + struct mcps802154_rx_cir *cirs; + /** + * @n_cirs: Number of parts of CIR. + */ + size_t n_cirs; +}; + +/** + * struct fira_slot - Information on an active slot. + */ +struct fira_slot { + /** + * @index: Index of this slot, add it to the block STS index to get the + * slot STS index. Note: there can be holes for a controlee as only + * relevant slots are recorded. + */ + int index; + /** + * @controller_tx: True if Tx is performed by the controller. + */ + bool controller_tx; + /** + * @ranging_index: Index of the ranging in the ranging information + * table, -1 if none. + */ + int ranging_index; + /** + * @message_id: Identifier of the message exchanged in this slot. + */ + enum fira_message_id message_id; + /** + * @tx_ant_set: Tx antenna set. + */ + int tx_ant_set; + /** + * @rx_ant_set: Rx antenna set. + */ + int rx_ant_set; + /** + * @controlee: Controlee. + */ + struct fira_controlee *controlee; +}; + +/** + * struct fira_local_aoa_info - Ranging AoA information. + */ +struct fira_local_aoa_info { + /** + * @pdoa_2pi: Phase Difference of Arrival. + */ + s16 pdoa_2pi; + /** + * @aoa_2pi: Angle of Arrival. + */ + s16 aoa_2pi; + /** + * @aoa_fom: Figure of merit of the AoA. + */ + u8 aoa_fom; + /** + * @rx_ant_set: Antenna set index. + */ + u8 rx_ant_set; + /** + * @present: true if AoA information is present. + */ + bool present; +}; + +/** + * enum fira_range_data_ntf_status - Device (controller or controlee) + * status, used for range_data_ntf. + * @FIRA_RANGE_DATA_NTF_NONE: Undetermined, no ranging data for this + * device yet, or N/A (not applicable). + * @FIRA_RANGE_DATA_NTF_IN: Last ranging data for this device + * were inside given boudaries. + * @FIRA_RANGE_DATA_NTF_OUT: Last ranging data for this device + * were outside given boudaries. + * @FIRA_RANGE_DATA_NTF_ERROR: Last ranging round(s) for this device + * failed (timeout, error, ...). No info about a previous state or N/A. + * @FIRA_RANGE_DATA_NTF_IN_ERROR: Last ranging round(s) for this device + * failed (timeout, error, ...). Previous data were inside given boudaries. + * @FIRA_RANGE_DATA_NTF_OUT_ERROR: Last ranging round(s) for this device + * failed (timeout, error, ...). Previous data were inside given boudaries. +*/ +enum fira_range_data_ntf_status { + FIRA_RANGE_DATA_NTF_NONE, + FIRA_RANGE_DATA_NTF_IN, + FIRA_RANGE_DATA_NTF_OUT, + FIRA_RANGE_DATA_NTF_ERROR, + FIRA_RANGE_DATA_NTF_IN_ERROR, + FIRA_RANGE_DATA_NTF_OUT_ERROR, +}; + +/** + * struct fira_ranging_info - Ranging information. + */ +struct fira_ranging_info { + /** + * @timestamps_rctu: Timestamps of the ranging messages. + */ + u64 timestamps_rctu[FIRA_MESSAGE_ID_RFRAME_MAX + 1]; + /** + * @tof_rctu: Computed Time of Flight. + */ + int tof_rctu; + /** + * @local_aoa: Local ranging AoA information. + */ + struct fira_local_aoa_info local_aoa; + /** + * @local_aoa_azimuth: Azimuth ranging AoA information. + */ + struct fira_local_aoa_info local_aoa_azimuth; + /** + * @local_aoa_elevation: Elevation ranging AoA information. + */ + struct fira_local_aoa_info local_aoa_elevation; + /** + * @remote_aoa_azimuth_2pi: Remote azimuth AoA. + */ + s16 remote_aoa_azimuth_2pi; + /** + * @remote_aoa_elevation_pi: Remote elevation AoA. + */ + s16 remote_aoa_elevation_pi; + /** + * @remote_aoa_azimuth_fom: Remote azimuth FoM. + */ + u8 remote_aoa_azimuth_fom; + /** + * @remote_aoa_elevation_fom: Remote elevation FoM. + */ + u8 remote_aoa_elevation_fom; + /** + * @rx_rssis: RSSI value measured for individual Rx frames. + */ + u8 rx_rssis[FIRA_MESSAGE_ID_MAX + 1]; + /** + * @n_rx_rssis: Number of Rx RSSI saved. + */ + int n_rx_rssis; + /** + * @short_addr: Peer short address. + */ + __le16 short_addr; + /** + * @status: Success or failure reason. + */ + enum fira_ranging_status status; + /** + * @slot_index: In case of failure, the slot index where it has occured. + */ + u8 slot_index; + /** + * @tof_present: true if time of flight information is present. + */ + bool tof_present; + /** + * @remote_aoa_azimuth_present: true if azimuth AoA information is present. + */ + bool remote_aoa_azimuth_present; + /** + * @remote_aoa_elevation_present: true if elevation AoA information is present. + */ + bool remote_aoa_elevation_present; + /** + * @remote_aoa_fom_present: true if FoM AoA is present. + */ + bool remote_aoa_fom_present; + /** + * @clock_offset_present: true if the driver provided clock_offset info. + */ + bool clock_offset_present; + /** + * @clock_offset_q26: clock offset value, as signed Q26, if present. + */ + s16 clock_offset_q26; + /** + * @data_payload: Custom data payload. + */ + u8 data_payload[FIRA_DATA_PAYLOAD_SIZE_MAX]; + /** + * @data_payload_len: Custom data payload length. + */ + int data_payload_len; + /** + * @rx_ctx: Pointer to the current rx_ctx context controlee. + */ + void *rx_ctx; + /** + * @range_data_ntf_status: range_data_ntf status of the remote device. + */ + enum fira_range_data_ntf_status range_data_ntf_status; + /** + * @notify: if true, add this ranging to the notification report. + */ + bool notify; +}; + +/** + * struct fira_local - Local context. + */ +struct fira_local { + /** + * @region: Region instance returned to MCPS. + */ + struct mcps802154_region region; + /** + * @llhw: Low-level device pointer. + */ + struct mcps802154_llhw *llhw; + /** + * @access: Access returned to MCPS. + */ + struct mcps802154_access access; + /** + * @report_queue: Queue of report frame to be processed. + */ + struct sk_buff_head report_queue; + /** + * @report_work: Process work of report event. + */ + struct work_struct report_work; + /** + * @frames: Access frames referenced from access. + */ + struct mcps802154_access_frame frames[FIRA_FRAMES_MAX]; + /** + * @sts_params: STS parameters for access frames. + */ + struct mcps802154_sts_params sts_params[FIRA_FRAMES_MAX]; + /** + * @channel: Channel parameters for access. + */ + struct mcps802154_channel channel; + /** + * @inactive_sessions: List of inactive sessions. + */ + struct list_head inactive_sessions; + /** + * @active_sessions: List of active sessions. + */ + struct list_head active_sessions; + /** + * @current_session: Pointer to the current session. + */ + struct fira_session *current_session; + /** + * @src_short_addr: Source address for the current session (actually + * never put as a source address in a frame, but used for control + * message). + */ + __le16 src_short_addr; + /** + * @dst_short_addr: Destination address for the current session. When + * controller, this is broadcast or the address of the only controlee. + * When controlee, this is the address of the controller. + */ + __le16 dst_short_addr; + /** + * @block_duration_rx_margin_ppm: Block duration rx margin for + * controlees. + */ + int block_duration_rx_margin_ppm; + /** + * @slots: Descriptions of each active slots for the current session. + * When controller, this is filled when the access is requested. When + * controlee, the first slot is filled when the access is requested and + * the other slots are filled when the control message is received. + */ + struct fira_slot slots[FIRA_FRAMES_MAX]; + /** + * @ranging_info: Information on ranging for the current session. Index + * in the table is determined by the order of the ranging messages. + * First ranging exchange is put at index 0. When a message is shared + * between several exchanges, its information is stored at index 0. + * Reset when access is requested. + */ + struct fira_ranging_info ranging_info[FIRA_CONTROLEES_MAX]; + /** + * @n_ranging_info: Number of element in the ranging information table. + */ + int n_ranging_info; + /** + * @n_ranging_valid: Number of valid ranging in the current ranging + * information table. + */ + int n_ranging_valid; + /** + * @diagnostics: Diagnostic collected for each slot. + */ + struct fira_diagnostic diagnostics[FIRA_FRAMES_MAX]; + /** + * @stopped_controlees: Short addresses of the stopped controlees for + * which an element must be added to the Device Management List of + * the control message. + */ + __le16 stopped_controlees[FIRA_CONTROLEES_MAX]; + /** + * @n_stopped_controlees: Number of elements in the stopped controlees . + */ + int n_stopped_controlees; +}; + +static const s64 speed_of_light_mm_per_s = 299702547000ull; + +static inline s64 fira_rctu_to_mm(s64 rctu_freq_hz, s32 rctu) +{ + s64 temp = speed_of_light_mm_per_s * rctu + rctu_freq_hz / 2; + return div64_s64(temp, rctu_freq_hz); +} + +static inline s64 fira_mm_to_rctu(struct fira_local *local, s32 mm) +{ + s64 temp = (s64)mm * local->llhw->dtu_freq_hz * local->llhw->dtu_rctu + + speed_of_light_mm_per_s / 2; + return div64_s64(temp, speed_of_light_mm_per_s); +} + +static inline struct fira_local * +region_to_local(struct mcps802154_region *region) +{ + return container_of(region, struct fira_local, region); +} + +static inline struct fira_local * +access_to_local(struct mcps802154_access *access) +{ + return container_of(access, struct fira_local, access); +} + +/** + * fira_get_session_by_session_id() - Get a session by its identifier. + * @local: FiRa context. + * @session_id: Session identifier. + * + * Return: The session or NULL if not found. + */ +struct fira_session *fira_get_session_by_session_id(struct fira_local *local, + u32 session_id); + +/** + * fira_check_all_missed_ranging() - Check missed ranging round for all active + * session except the recent. + * @local: FiRa context. + * @recent_session: FiRa session to not check in active list. + * @timestamp_dtu: Timestamp used to trig (or not) a report of ranging failure. + */ +void fira_check_all_missed_ranging(struct fira_local *local, + const struct fira_session *recent_session, + u32 timestamp_dtu); + +#endif /* NET_FIRA_REGION_H */ diff --git a/mac/fira_region_call.c b/mac/fira_region_call.c new file mode 100644 index 0000000..2ed37f2 --- /dev/null +++ b/mac/fira_region_call.c @@ -0,0 +1,1235 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <asm/unaligned.h> + +#include <linux/errno.h> +#include <linux/ieee802154.h> +#include <linux/string.h> + +#include <net/fira_region_nl.h> +#include <net/mcps802154_frame.h> + +#include "fira_session.h" +#include "fira_access.h" +#include "fira_region_call.h" +#include "fira_trace.h" +#include "fira_sts.h" + +static const struct nla_policy fira_call_nla_policy[FIRA_CALL_ATTR_MAX + 1] = { + [FIRA_CALL_ATTR_SESSION_ID] = { .type = NLA_U32 }, + [FIRA_CALL_ATTR_SESSION_PARAMS] = { .type = NLA_NESTED }, + [FIRA_CALL_ATTR_CONTROLEES] = { .type = NLA_NESTED_ARRAY }, +}; + +static const struct nla_policy fira_session_param_nla_policy[FIRA_SESSION_PARAM_ATTR_MAX + + 1] = { + [FIRA_SESSION_PARAM_ATTR_DEVICE_TYPE] = + NLA_POLICY_MAX(NLA_U8, FIRA_DEVICE_TYPE_CONTROLLER), + [FIRA_SESSION_PARAM_ATTR_DEVICE_ROLE] = + NLA_POLICY_MAX(NLA_U8, FIRA_DEVICE_ROLE_INITIATOR), + [FIRA_SESSION_PARAM_ATTR_RANGING_ROUND_USAGE] = + NLA_POLICY_MAX(NLA_U8, FIRA_RANGING_ROUND_USAGE_DSTWR), + [FIRA_SESSION_PARAM_ATTR_MULTI_NODE_MODE] = + NLA_POLICY_MAX(NLA_U8, FIRA_MULTI_NODE_MODE_MANY_TO_MANY), + [FIRA_SESSION_PARAM_ATTR_SHORT_ADDR] = { .type = NLA_U16 }, + [FIRA_SESSION_PARAM_ATTR_DESTINATION_SHORT_ADDR] = { .type = NLA_U16 }, + [FIRA_SESSION_PARAM_ATTR_INITIATION_TIME_MS] = { .type = NLA_U32 }, + [FIRA_SESSION_PARAM_ATTR_SLOT_DURATION_RSTU] = { .type = NLA_U32 }, + [FIRA_SESSION_PARAM_ATTR_BLOCK_DURATION_MS] = { .type = NLA_U32 }, + [FIRA_SESSION_PARAM_ATTR_ROUND_DURATION_SLOTS] = { .type = NLA_U32 }, + [FIRA_SESSION_PARAM_ATTR_BLOCK_STRIDE_LENGTH] = + NLA_POLICY_MAX(NLA_U32, FIRA_BLOCK_STRIDE_LEN_MAX), + [FIRA_SESSION_PARAM_ATTR_MAX_NUMBER_OF_MEASUREMENTS] = { .type = NLA_U32 }, + [FIRA_SESSION_PARAM_ATTR_MAX_RR_RETRY] = { .type = NLA_U32 }, + [FIRA_SESSION_PARAM_ATTR_ROUND_HOPPING] = + NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX), + [FIRA_SESSION_PARAM_ATTR_PRIORITY] = + NLA_POLICY_MAX(NLA_U8, FIRA_PRIORITY_MAX), + [FIRA_SESSION_PARAM_ATTR_RESULT_REPORT_PHASE] = + NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX), + [FIRA_SESSION_PARAM_ATTR_MR_AT_INITIATOR] = + NLA_POLICY_MAX(NLA_U8, FIRA_MEASUREMENT_REPORT_AT_INITIATOR), + [FIRA_SESSION_PARAM_ATTR_EMBEDDED_MODE] = + NLA_POLICY_MAX(NLA_U8, FIRA_EMBEDDED_MODE_NON_DEFERRED), + [FIRA_SESSION_PARAM_ATTR_IN_BAND_TERMINATION_ATTEMPT_COUNT] = + NLA_POLICY_RANGE(NLA_U32, + FIRA_IN_BAND_TERMINATION_ATTEMPT_COUNT_MIN, + FIRA_IN_BAND_TERMINATION_ATTEMPT_COUNT_MAX), + [FIRA_SESSION_PARAM_ATTR_CHANNEL_NUMBER] = { .type = NLA_U8 }, + [FIRA_SESSION_PARAM_ATTR_PREAMBLE_CODE_INDEX] = { .type = NLA_U8 }, + [FIRA_SESSION_PARAM_ATTR_RFRAME_CONFIG] = + NLA_POLICY_MAX(NLA_U8, FIRA_RFRAME_CONFIG_SP3), + [FIRA_SESSION_PARAM_ATTR_PRF_MODE] = + NLA_POLICY_MAX(NLA_U8, FIRA_PRF_MODE_HPRF_HIGH_RATE), + [FIRA_SESSION_PARAM_ATTR_PREAMBLE_DURATION] = + NLA_POLICY_MAX(NLA_U8, FIRA_PREAMBULE_DURATION_64), + [FIRA_SESSION_PARAM_ATTR_SFD_ID] = + NLA_POLICY_MAX(NLA_U8, FIRA_SFD_ID_4), + [FIRA_SESSION_PARAM_ATTR_NUMBER_OF_STS_SEGMENTS] = + NLA_POLICY_MAX(NLA_U8, FIRA_STS_SEGMENTS_4), + [FIRA_SESSION_PARAM_ATTR_PSDU_DATA_RATE] = + NLA_POLICY_MAX(NLA_U8, FIRA_PSDU_DATA_RATE_31M2), + [FIRA_SESSION_PARAM_ATTR_BPRF_PHR_DATA_RATE] = + NLA_POLICY_MAX(NLA_U8, FIRA_PHR_DATA_RATE_6M81), + [FIRA_SESSION_PARAM_ATTR_MAC_FCS_TYPE] = + NLA_POLICY_MAX(NLA_U8, FIRA_MAC_FCS_TYPE_CRC_32), + [FIRA_SESSION_PARAM_ATTR_TX_ADAPTIVE_PAYLOAD_POWER] = + NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX), + [FIRA_SESSION_PARAM_ATTR_MEASUREMENT_SEQUENCE] = { .type = NLA_NESTED_ARRAY }, + [FIRA_SESSION_PARAM_ATTR_STS_CONFIG] = + NLA_POLICY_MAX(NLA_U8, FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY), + [FIRA_SESSION_PARAM_ATTR_SUB_SESSION_ID] = { .type = NLA_U32 }, + [FIRA_SESSION_PARAM_ATTR_VUPPER64] = + NLA_POLICY_EXACT_LEN(FIRA_VUPPER64_SIZE), + [FIRA_SESSION_PARAM_ATTR_SESSION_KEY] = { + .type = NLA_BINARY, .len = FIRA_KEY_SIZE_MAX, }, + [FIRA_SESSION_PARAM_ATTR_SUB_SESSION_KEY] = { + .type = NLA_BINARY, .len = FIRA_KEY_SIZE_MAX, }, + [FIRA_SESSION_PARAM_ATTR_KEY_ROTATION] = + NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX), + [FIRA_SESSION_PARAM_ATTR_KEY_ROTATION_RATE] = { .type = NLA_U8 }, + [FIRA_SESSION_PARAM_ATTR_AOA_RESULT_REQ] = + NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX), + [FIRA_SESSION_PARAM_ATTR_REPORT_TOF] = + NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX), + [FIRA_SESSION_PARAM_ATTR_REPORT_AOA_AZIMUTH] = + NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX), + [FIRA_SESSION_PARAM_ATTR_REPORT_AOA_ELEVATION] = + NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX), + [FIRA_SESSION_PARAM_ATTR_REPORT_AOA_FOM] = + NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX), + [FIRA_SESSION_PARAM_ATTR_REPORT_RSSI] = + NLA_POLICY_MAX(NLA_U8, FIRA_RSSI_REPORT_AVERAGE), + [FIRA_SESSION_PARAM_ATTR_DATA_VENDOR_OUI] = { .type = NLA_U32 }, + [FIRA_SESSION_PARAM_ATTR_DATA_PAYLOAD] = { + .type = NLA_BINARY, .len = FIRA_DATA_PAYLOAD_SIZE_MAX, }, + [FIRA_SESSION_PARAM_ATTR_DIAGNOSTICS] = + NLA_POLICY_MAX(NLA_U8, FIRA_BOOLEAN_MAX), + [FIRA_SESSION_PARAM_ATTR_DIAGNOSTICS_FRAME_REPORTS_FIELDS] = {.type = NLA_U32}, + [FIRA_SESSION_PARAM_ATTR_STS_LENGTH] = + NLA_POLICY_MAX(NLA_U8, FIRA_STS_LENGTH_128), + [FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_CONFIG] = + NLA_POLICY_MAX(NLA_U8, FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA_CROSSING), + [FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_PROXIMITY_NEAR_MM] = + { .type = NLA_U32 }, + [FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_PROXIMITY_FAR_MM] = + { .type = NLA_U32 }, + [FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI] = + NLA_POLICY_RANGE(NLA_S16, + FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI_MIN, + FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI_MAX), + [FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI] = + NLA_POLICY_RANGE(NLA_S16, + FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI_MIN, + FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI_MAX), + [FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI] = + NLA_POLICY_RANGE(NLA_S16, + FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI_MIN, + FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI_MAX), + [FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI] = + NLA_POLICY_RANGE(NLA_S16, + FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI_MIN, + FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI_MAX), +}; + +/** + * fira_get_state_by_session_id() - Get state of the session. + * @local: FiRa context. + * @session_id: FiRa session id. + * + * Return: current session state. + */ +static enum fira_session_state_id +fira_get_state_by_session_id(struct fira_local *local, u32 session_id) +{ + struct fira_session *session = + fira_get_session_by_session_id(local, session_id); + + if (session) + return fira_session_get_state_id(session); + return FIRA_SESSION_STATE_ID_DEINIT; +} + +/** + * fira_session_init() - Initialize FiRa session. + * @local: FiRa context. + * @session_id: FiRa session id. + * + * Return: 0 or error. + */ +static int fira_session_init(struct fira_local *local, u32 session_id) +{ + struct fira_session *session = + fira_get_session_by_session_id(local, session_id); + + if (session) + return -EBUSY; + + session = fira_session_new(local, session_id); + if (!session) + return -ENOMEM; + + return 0; +} + +/** + * fira_session_start() - Start FiRa session. + * @local: FiRa context. + * @session_id: FiRa session id. + * @info: Request information. + * + * Return: 0 or error. + */ +static int fira_session_start(struct fira_local *local, u32 session_id, + const struct genl_info *info) +{ + struct fira_session *session; + + session = fira_get_session_by_session_id(local, session_id); + if (!session) + return -ENOENT; + + return fira_session_fsm_start(local, session, info); +} + +/** + * fira_session_stop() - Stop FiRa session. + * @local: FiRa context. + * @session_id: FiRa session id. + * + * Return: 0 or error. + */ +static int fira_session_stop(struct fira_local *local, u32 session_id) +{ + struct fira_session *session; + + session = fira_get_session_by_session_id(local, session_id); + if (!session) + return -ENOENT; + + return fira_session_fsm_stop(local, session); +} + +/** + * fira_session_deinit() - Deinitialize FiRa session. + * @local: FiRa context. + * @session_id: FiRa session id. + * + * Return: 0 or error. + */ +static int fira_session_deinit(struct fira_local *local, u32 session_id) +{ + struct fira_session *session = + fira_get_session_by_session_id(local, session_id); + + if (!session) + return -ENOENT; + if (fira_session_is_active(session)) + return -EBUSY; + + fira_session_free(local, session); + return 0; +} + +/** + * fira_session_params_set_measurement_sequence_step() - Retrieve a + * measurement sequence step from a NL message and store it in the session + * parameters. + * @params: The parameters contained in the NL message. + * @info: NL context info. + * @step: The step where to store the information. + * + * Return: 0 or error. + */ +static int fira_session_params_set_measurement_sequence_step( + const struct nlattr *params, const struct genl_info *info, + struct fira_measurement_sequence_step *step) +{ +#define STEP_ATTR(x) FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_##x +#define ASR_ATTR(x) \ + FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_##x + static const struct nla_policy meas_seq_step_policy[STEP_ATTR(MAX) + + 1] = { + [STEP_ATTR(MEASUREMENT_TYPE)] = { .type = NLA_U8 }, + [STEP_ATTR(N_MEASUREMENTS)] = { .type = NLA_U8 }, + [STEP_ATTR(RX_ANT_SET_NONRANGING)] = { .type = NLA_U8 }, + [STEP_ATTR(RX_ANT_SETS_RANGING)] = { .type = NLA_NESTED }, + [STEP_ATTR(TX_ANT_SET_NONRANGING)] = { .type = NLA_U8 }, + [STEP_ATTR(TX_ANT_SET_RANGING)] = { .type = NLA_U8 }, + }; + static const struct nla_policy + rx_ant_sets_ranging_policy[ASR_ATTR(MAX) + 1] = { + [ASR_ATTR(0)] = { .type = NLA_U8 }, + [ASR_ATTR(1)] = { .type = NLA_U8 }, + }; + struct nlattr *step_attrs[STEP_ATTR(MAX) + 1]; + struct nlattr *rx_ant_sets_attrs[ASR_ATTR(MAX) + 1]; + int r = 0; + u8 n_measurements = 0; + + enum fira_measurement_type type = __FIRA_MEASUREMENT_TYPE_AFTER_LAST; + + r = nla_parse_nested(step_attrs, STEP_ATTR(MAX), params, + meas_seq_step_policy, info->extack); + /* LCOV_EXCL_START */ + if (r) + return r; + /* LCOV_EXCL_STOP */ + + memset(step, 0, sizeof(struct fira_measurement_sequence_step)); + if (!step_attrs[STEP_ATTR(MEASUREMENT_TYPE)] || + !step_attrs[STEP_ATTR(N_MEASUREMENTS)]) + return -EINVAL; + + type = nla_get_u8(step_attrs[STEP_ATTR(MEASUREMENT_TYPE)]); + if (type >= __FIRA_MEASUREMENT_TYPE_AFTER_LAST) + return -EINVAL; + step->type = type; + + n_measurements = nla_get_u8(step_attrs[STEP_ATTR(N_MEASUREMENTS)]); + if (n_measurements == 0) + return -EINVAL; + step->n_measurements = n_measurements; + +#define GET_ANTENNA(nl_attr, ant_set) \ + (ant_set) = !(nl_attr) ? -1 : nla_get_u8(nl_attr); + + GET_ANTENNA(step_attrs[STEP_ATTR(RX_ANT_SET_NONRANGING)], + step->rx_ant_set_nonranging); + GET_ANTENNA(step_attrs[STEP_ATTR(TX_ANT_SET_NONRANGING)], + step->tx_ant_set_nonranging); + GET_ANTENNA(step_attrs[STEP_ATTR(TX_ANT_SET_RANGING)], + step->tx_ant_set_ranging); + + if (!step_attrs[STEP_ATTR(RX_ANT_SETS_RANGING)]) + return -EINVAL; + + r = nla_parse_nested(rx_ant_sets_attrs, ASR_ATTR(MAX), + step_attrs[STEP_ATTR(RX_ANT_SETS_RANGING)], + rx_ant_sets_ranging_policy, info->extack); + /* LCOV_EXCL_START */ + if (r) + return r; + /* LCOV_EXCL_STOP */ + + GET_ANTENNA(rx_ant_sets_attrs[ASR_ATTR(0)], + step->rx_ant_sets_ranging[0]); + GET_ANTENNA(rx_ant_sets_attrs[ASR_ATTR(1)], + step->rx_ant_sets_ranging[1]); + +#undef GET_ANTENNA +#undef STEP_ATTR +#undef ASR_ATTR + + if (step->rx_ant_sets_ranging[0] == -1) + step->rx_ant_sets_ranging[0] = 0; + if (step->rx_ant_sets_ranging[1] == -1) + step->rx_ant_sets_ranging[1] = 0; + if (step->rx_ant_set_nonranging == -1) + step->rx_ant_set_nonranging = step->rx_ant_sets_ranging[0]; + if (step->tx_ant_set_ranging == -1) + step->tx_ant_set_ranging = 0; + if (step->tx_ant_set_nonranging == -1) + step->tx_ant_set_nonranging = step->tx_ant_set_ranging; + + return 0; +} + +/** + * fira_session_params_set_measurement_sequence() - Retrieve the measurement + * schedule from a NL message and store it in the session parameters. + * @params: The parameters contained in the NL message. + * @info: NL context info. + * @meas_seq: The measurement sequence where to store the information. + * + * Return: 0 or error. + */ +static int fira_session_params_set_measurement_sequence( + const struct nlattr *params, const struct genl_info *info, + struct fira_measurement_sequence *meas_seq) +{ + struct nlattr *request; + int r, rem = 0; + size_t n_steps = 0; + + nla_for_each_nested (request, params, rem) { + if (n_steps >= FIRA_MEASUREMENT_SEQUENCE_STEP_MAX) + return -EINVAL; + r = fira_session_params_set_measurement_sequence_step( + request, info, &meas_seq->steps[n_steps]); + if (r) + return r; + n_steps++; + } + if (!n_steps) + return -EINVAL; + meas_seq->n_steps = n_steps; + return 0; +} + +/** + * fira_session_set_parameters() - Set FiRa session parameters. + * @local: FiRa context. + * @session_id: FiRa session id. + * @params: Nested attribute containing session parameters. + * @info: Request information. + * + * Return: 0 or error. + */ +static int fira_session_set_parameters(struct fira_local *local, u32 session_id, + const struct nlattr *params, + const struct genl_info *info) +{ + struct nlattr *attrs[FIRA_SESSION_PARAM_ATTR_MAX + 1]; + struct fira_session *session; + struct fira_session_params *p; + struct fira_measurement_sequence meas_seq = {}; + int r; + + if (!params) + return -EINVAL; + + session = fira_get_session_by_session_id(local, session_id); + if (!session) + return -ENOENT; + + r = nla_parse_nested(attrs, FIRA_SESSION_PARAM_ATTR_MAX, params, + fira_session_param_nla_policy, info->extack); + if (r) + return r; + /* Check attribute validity. */ + if (attrs[FIRA_SESSION_PARAM_ATTR_MEASUREMENT_SEQUENCE]) { + r = fira_session_params_set_measurement_sequence( + attrs[FIRA_SESSION_PARAM_ATTR_MEASUREMENT_SEQUENCE], + info, &meas_seq); + if (r) + return r; + } + r = fira_session_fsm_check_parameters(session, attrs); + if (r) + return r; + + p = &session->params; +#define P(attr, member, type, conv) \ + do { \ + int x; \ + if (attrs[FIRA_SESSION_PARAM_ATTR_##attr]) { \ + x = nla_get_##type( \ + attrs[FIRA_SESSION_PARAM_ATTR_##attr]); \ + p->member = conv; \ + } \ + } while (0) +#define PMEMCPY(attr, member) \ + do { \ + if (attrs[FIRA_SESSION_PARAM_ATTR_##attr]) { \ + struct nlattr *attr = \ + attrs[FIRA_SESSION_PARAM_ATTR_##attr]; \ + memcpy(p->member, nla_data(attr), nla_len(attr)); \ + } \ + } while (0) +#define PMEMNCPY(attr, member, size) \ + do { \ + if (attrs[FIRA_SESSION_PARAM_ATTR_##attr]) { \ + struct nlattr *attr = \ + attrs[FIRA_SESSION_PARAM_ATTR_##attr]; \ + int len = nla_len(attr); \ + memcpy(p->member, nla_data(attr), len); \ + p->size = len; \ + } \ + } while (0) + /* Main session parameters. */ + P(DEVICE_TYPE, device_type, u8, x); + P(RANGING_ROUND_USAGE, ranging_round_usage, u8, x); + P(MULTI_NODE_MODE, multi_node_mode, u8, x); + P(SHORT_ADDR, short_addr, u16, x); + P(DESTINATION_SHORT_ADDR, controller_short_addr, u16, x); + /* Timings parameters. */ + P(INITIATION_TIME_MS, initiation_time_ms, u32, x); + P(SLOT_DURATION_RSTU, slot_duration_dtu, u32, + x * local->llhw->rstu_dtu); + P(BLOCK_DURATION_MS, block_duration_dtu, u32, + x * (local->llhw->dtu_freq_hz / 1000)); + P(ROUND_DURATION_SLOTS, round_duration_slots, u32, x); + /* Behaviour parameters. */ + P(BLOCK_STRIDE_LENGTH, block_stride_len, u32, x); + P(MAX_NUMBER_OF_MEASUREMENTS, max_number_of_measurements, u32, x); + P(MAX_RR_RETRY, max_rr_retry, u32, x); + P(ROUND_HOPPING, round_hopping, u8, !!x); + P(PRIORITY, priority, u8, x); + P(RESULT_REPORT_PHASE, result_report_phase, u8, !!x); + /* Radio parameters. */ + P(CHANNEL_NUMBER, channel_number, u8, x); + P(PREAMBLE_CODE_INDEX, preamble_code_index, u8, x); + P(RFRAME_CONFIG, rframe_config, u8, x); + P(PREAMBLE_DURATION, preamble_duration, u8, x); + P(SFD_ID, sfd_id, u8, x); + P(NUMBER_OF_STS_SEGMENTS, number_of_sts_segments, u8, x); + P(PSDU_DATA_RATE, psdu_data_rate, u8, x); + P(MAC_FCS_TYPE, mac_fcs_type, u8, x); + P(PRF_MODE, prf_mode, u8, x); + P(BPRF_PHR_DATA_RATE, phr_data_rate, u8, x); + /* Measurement Sequence */ + if (attrs[FIRA_SESSION_PARAM_ATTR_MEASUREMENT_SEQUENCE]) { + p->meas_seq = meas_seq; + session->measurements.reset = true; + } + /* STS and crypto parameters. */ + P(STS_CONFIG, sts_config, u8, x); + PMEMCPY(VUPPER64, vupper64); + if (attrs[FIRA_SESSION_PARAM_ATTR_SESSION_KEY]) { + struct nlattr *attr = + attrs[FIRA_SESSION_PARAM_ATTR_SESSION_KEY]; + memcpy(p->session_key, nla_data(attr), nla_len(attr)); + p->session_key_len = nla_len(attr); + } + P(SUB_SESSION_ID, sub_session_id, u32, x); + if (attrs[FIRA_SESSION_PARAM_ATTR_SUB_SESSION_KEY]) { + struct nlattr *attr = + attrs[FIRA_SESSION_PARAM_ATTR_SUB_SESSION_KEY]; + memcpy(p->sub_session_key, nla_data(attr), nla_len(attr)); + p->sub_session_key_len = nla_len(attr); + } + P(KEY_ROTATION, key_rotation, u8, !!x); + P(KEY_ROTATION_RATE, key_rotation_rate, u8, x); + /* Report parameters. */ + P(AOA_RESULT_REQ, aoa_result_req, u8, !!x); + P(REPORT_TOF, report_tof, u8, !!x); + P(REPORT_AOA_AZIMUTH, report_aoa_azimuth, u8, !!x); + P(REPORT_AOA_ELEVATION, report_aoa_elevation, u8, !!x); + P(REPORT_AOA_FOM, report_aoa_fom, u8, !!x); + P(REPORT_RSSI, report_rssi, u8, x); + /* Custom data */ + P(DATA_VENDOR_OUI, data_vendor_oui, u32, x); + + PMEMNCPY(DATA_PAYLOAD, data_payload, data_payload_len); + /* Increment payload sequence number if a new data is received. */ + if (attrs[FIRA_SESSION_PARAM_ATTR_DATA_PAYLOAD]) + p->data_payload_seq++; + /* Diagnostics */ + P(DIAGNOSTICS, report_diagnostics, u8, x); + P(DIAGNOSTICS_FRAME_REPORTS_FIELDS, diagnostic_report_flags, u32, x); + /* Misc */ + P(STS_LENGTH, sts_length, u8, x); + P(RANGE_DATA_NTF_CONFIG, range_data_ntf_config, u8, x); + P(RANGE_DATA_NTF_PROXIMITY_NEAR_MM, range_data_ntf_proximity_near_rctu, + u32, fira_mm_to_rctu(local, x)); + P(RANGE_DATA_NTF_PROXIMITY_FAR_MM, range_data_ntf_proximity_far_rctu, + u32, fira_mm_to_rctu(local, x)); + P(RANGE_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI, + range_data_ntf_lower_bound_aoa_azimuth_2pi, s16, x); + P(RANGE_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI, + range_data_ntf_upper_bound_aoa_azimuth_2pi, s16, x); + P(RANGE_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI, + range_data_ntf_lower_bound_aoa_elevation_2pi, s16, x); + P(RANGE_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI, + range_data_ntf_upper_bound_aoa_elevation_2pi, s16, x); +#undef PMEMNCPY +#undef PMEMCPY +#undef P + + fira_session_fsm_parameters_updated(local, session); + return 0; +} + +/** + * fira_session_get_state() - Get state of the session. + * @local: FiRa context. + * @session_id: FiRa session id. + * + * Return: 0 or error. + */ +static int fira_session_get_state(struct fira_local *local, u32 session_id) +{ + struct sk_buff *msg; + enum fira_session_state_id state; + + state = fira_get_state_by_session_id(local, session_id); + + msg = mcps802154_region_call_alloc_reply_skb( + local->llhw, &local->region, FIRA_CALL_SESSION_GET_STATE, + NLMSG_DEFAULT_SIZE); + if (!msg) + return -ENOMEM; + + if (nla_put_u32(msg, FIRA_CALL_ATTR_SESSION_ID, session_id)) + goto nla_put_failure; + + if (nla_put_u8(msg, FIRA_CALL_ATTR_SESSION_STATE, state)) + goto nla_put_failure; + + return mcps802154_region_call_reply(local->llhw, msg); + +nla_put_failure: + kfree_skb(msg); + return -ENOBUFS; +} + +/** + * fira_session_params_get_measurement_sequence_step() - Retrieve a + * measurement sequence step in a NL message. + * @step: The measurement sequence step to add to the message. + * @msg_buf: NL message buffer. + * @idx: Index of the step. + * + * Return: 0 or error. + */ +static int fira_session_params_get_measurement_sequence_step( + const struct fira_measurement_sequence_step *step, + struct sk_buff *msg_buf, size_t idx) +{ +#define P(attr, member, type, conv) \ + do { \ + type x = member; \ + if (nla_put_##type(msg_buf, attr, conv)) \ + return -ENOBUFS; \ + } while (0) +#define STEP_ATTR(x) FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_##x +#define ASR_ATTR(x) \ + FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_##x + + struct nlattr *step_params = NULL; + struct nlattr *rx_ant_sets_ranging_params = NULL; + + step_params = nla_nest_start(msg_buf, idx); + + if (!step_params) + return -ENOBUFS; + P(STEP_ATTR(MEASUREMENT_TYPE), step->type, u8, x); + P(STEP_ATTR(N_MEASUREMENTS), step->n_measurements, u8, x); + P(STEP_ATTR(RX_ANT_SET_NONRANGING), step->rx_ant_set_nonranging, u8, x); + + rx_ant_sets_ranging_params = + nla_nest_start(msg_buf, STEP_ATTR(RX_ANT_SETS_RANGING)); + if (!rx_ant_sets_ranging_params) + return -ENOBUFS; + + P(ASR_ATTR(0), step->rx_ant_sets_ranging[0], u8, x); + P(ASR_ATTR(1), step->rx_ant_sets_ranging[1], u8, x); + + nla_nest_end(msg_buf, rx_ant_sets_ranging_params); + + P(STEP_ATTR(TX_ANT_SET_NONRANGING), step->tx_ant_set_nonranging, u8, x); + P(STEP_ATTR(TX_ANT_SET_RANGING), step->tx_ant_set_ranging, u8, x); + + nla_nest_end(msg_buf, step_params); + +#undef STEP_ATTR +#undef ASR_ATTR +#undef P + return 0; +} + +/** + * fira_session_params_get_measurement_sequence() - Retrieve a + * measurement sequence in a NL message. + * @meas_seq: The measurement sequence to add to the message. + * @msg_buf: NL message buffer. + * + * Return: 0 or error. + */ +static int fira_session_params_get_measurement_sequence( + const struct fira_measurement_sequence *meas_seq, + struct sk_buff *msg_buf) +{ + struct nlattr *meas_seq_params = NULL; + size_t i; + + meas_seq_params = nla_nest_start( + msg_buf, FIRA_SESSION_PARAM_ATTR_MEASUREMENT_SEQUENCE); + if (!meas_seq_params) + return -ENOBUFS; + + for (i = 0; i < meas_seq->n_steps; ++i) { + int r = 0; + r = fira_session_params_get_measurement_sequence_step( + meas_seq->steps + i, msg_buf, i); + if (r) + return r; + } + + nla_nest_end(msg_buf, meas_seq_params); + + return 0; +} + +/** + * fira_session_get_parameters() - Get FiRa session parameters. + * @local: FiRa context. + * @session_id: FiRa session id. + * + * Return: 0 or error. + */ +static int fira_session_get_parameters(struct fira_local *local, u32 session_id) +{ + const struct fira_session *session; + const struct fira_session_params *p; + struct sk_buff *msg; + struct nlattr *params; + + session = fira_get_session_by_session_id(local, session_id); + if (!session) + return -ENOENT; + + p = &session->params; + msg = mcps802154_region_call_alloc_reply_skb( + local->llhw, &local->region, FIRA_CALL_SESSION_GET_PARAMS, + NLMSG_DEFAULT_SIZE); + if (!msg) + return -ENOMEM; + + if (nla_put_u32(msg, FIRA_CALL_ATTR_SESSION_ID, session->id)) + goto nla_put_failure; + + params = nla_nest_start(msg, FIRA_CALL_ATTR_SESSION_PARAMS); + if (!params) + goto nla_put_failure; + +#define P(attr, member, type, conv) \ + do { \ + type x = p->member; \ + if (nla_put_##type(msg, FIRA_SESSION_PARAM_ATTR_##attr, conv)) \ + goto nla_put_failure; \ + } while (0) +#define PMEMCPY(attr, member) \ + do { \ + if (nla_put(msg, FIRA_SESSION_PARAM_ATTR_##attr, \ + sizeof(p->member), p->member)) \ + goto nla_put_failure; \ + } while (0) + /* Main session parameters. */ + P(DEVICE_TYPE, device_type, u8, x); + P(RANGING_ROUND_USAGE, ranging_round_usage, u8, x); + P(MULTI_NODE_MODE, multi_node_mode, u8, x); + if (p->short_addr != IEEE802154_ADDR_SHORT_BROADCAST) + P(SHORT_ADDR, short_addr, u16, x); + if (p->controller_short_addr != IEEE802154_ADDR_SHORT_BROADCAST) + P(DESTINATION_SHORT_ADDR, controller_short_addr, u16, x); + /* Timings parameters. */ + P(INITIATION_TIME_MS, initiation_time_ms, u32, x); + P(SLOT_DURATION_RSTU, slot_duration_dtu, u32, + x / local->llhw->rstu_dtu); + P(BLOCK_DURATION_MS, block_duration_dtu, u32, + x / (local->llhw->dtu_freq_hz / 1000)); + P(ROUND_DURATION_SLOTS, round_duration_slots, u32, x); + /* Behaviour parameters. */ + P(BLOCK_STRIDE_LENGTH, block_stride_len, u32, x); + P(MAX_NUMBER_OF_MEASUREMENTS, max_number_of_measurements, u32, x); + P(MAX_RR_RETRY, max_rr_retry, u32, x); + P(ROUND_HOPPING, round_hopping, u8, !!x); + P(PRIORITY, priority, u8, x); + P(RESULT_REPORT_PHASE, result_report_phase, u8, !!x); + /* Radio parameters. */ + if (p->channel_number) + P(CHANNEL_NUMBER, channel_number, u8, x); + if (p->preamble_code_index) + P(PREAMBLE_CODE_INDEX, preamble_code_index, u8, x); + P(RFRAME_CONFIG, rframe_config, u8, x); + P(PREAMBLE_DURATION, preamble_duration, u8, x); + P(SFD_ID, sfd_id, u8, x); + P(NUMBER_OF_STS_SEGMENTS, number_of_sts_segments, u8, x); + P(PSDU_DATA_RATE, psdu_data_rate, u8, x); + P(PRF_MODE, prf_mode, u8, x); + P(BPRF_PHR_DATA_RATE, phr_data_rate, u8, x); + P(MAC_FCS_TYPE, mac_fcs_type, u8, x); + /* Measurement Sequence */ + if (fira_session_params_get_measurement_sequence( + &session->measurements.sequence, msg)) + goto nla_put_failure; + /* STS and crypto parameters. */ + PMEMCPY(VUPPER64, vupper64); + P(SUB_SESSION_ID, sub_session_id, u32, x); + P(STS_CONFIG, sts_config, u8, x); + P(KEY_ROTATION, key_rotation, u8, x); + P(KEY_ROTATION_RATE, key_rotation_rate, u8, x); + /* Report parameters. */ + P(AOA_RESULT_REQ, aoa_result_req, u8, !!x); + P(REPORT_TOF, report_tof, u8, !!x); + P(REPORT_AOA_AZIMUTH, report_aoa_azimuth, u8, !!x); + P(REPORT_AOA_ELEVATION, report_aoa_elevation, u8, !!x); + P(REPORT_AOA_FOM, report_aoa_fom, u8, !!x); + P(REPORT_RSSI, report_rssi, u8, !!x); + /* Custom data */ + if (p->data_vendor_oui) + P(DATA_VENDOR_OUI, data_vendor_oui, u32, x); + /* Diagnostics */ + P(DIAGNOSTICS, report_diagnostics, u8, x); + P(DIAGNOSTICS_FRAME_REPORTS_FIELDS, diagnostic_report_flags, u32, x); + /* Misc */ + P(STS_LENGTH, sts_length, u8, x); + P(RANGE_DATA_NTF_CONFIG, range_data_ntf_config, u8, x); + P(RANGE_DATA_NTF_PROXIMITY_NEAR_MM, range_data_ntf_proximity_near_rctu, + u32, fira_rctu_to_mm((s64)local->llhw->dtu_freq_hz * local->llhw->dtu_rctu, x)); + P(RANGE_DATA_NTF_PROXIMITY_FAR_MM, range_data_ntf_proximity_far_rctu, + u32, fira_rctu_to_mm((s64)local->llhw->dtu_freq_hz * local->llhw->dtu_rctu, x)); + P(RANGE_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI, + range_data_ntf_lower_bound_aoa_azimuth_2pi, s16, x); + P(RANGE_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI, + range_data_ntf_upper_bound_aoa_azimuth_2pi, s16, x); + P(RANGE_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI, + range_data_ntf_lower_bound_aoa_elevation_2pi, s16, x); + P(RANGE_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI, + range_data_ntf_upper_bound_aoa_elevation_2pi, s16, x); +#undef P +#undef PMEMCPY + + nla_nest_end(msg, params); + + return mcps802154_region_call_reply(local->llhw, msg); +nla_put_failure: + kfree_skb(msg); + return -ENOBUFS; +} + +/** + * fira_manage_controlees() - Manage controlees. + * @local: FiRa context. + * @call_id: FiRa call id. + * @session_id: FiRa session id. + * @params: Nested attribute containing controlee parameters. + * @info: Request information. + * Return: 0 or error. + */ +static int fira_manage_controlees(struct fira_local *local, + enum fira_call call_id, u32 session_id, + const struct nlattr *params, + const struct genl_info *info) +{ + static const struct nla_policy new_controlee_nla_policy[FIRA_CALL_CONTROLEE_ATTR_MAX + + 1] = { + [FIRA_CALL_CONTROLEE_ATTR_SHORT_ADDR] = { .type = NLA_U16 }, + [FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_ID] = { .type = NLA_U32 }, + [FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_KEY] = { .type = NLA_BINARY, + .len = FIRA_KEY_SIZE_MAX }, + }; + struct nlattr *request; + struct nlattr *attrs[FIRA_CALL_CONTROLEE_ATTR_MAX + 1]; + int r, rem, i, slot_duration_us, n_controlees = 0; + struct fira_session *session; + struct fira_controlee *controlee = NULL, *tmp_controlee; + bool is_active; + struct list_head controlees; + + if (!params) + return -EINVAL; + + session = fira_get_session_by_session_id(local, session_id); + if (!session) + return -ENOENT; + + INIT_LIST_HEAD(&controlees); + + nla_for_each_nested (request, params, rem) { + if (n_controlees >= FIRA_CONTROLEES_MAX) { + r = -EINVAL; + goto end; + } + + r = nla_parse_nested(attrs, FIRA_CALL_CONTROLEE_ATTR_MAX, + request, new_controlee_nla_policy, + info->extack); + if (r) + goto end; + + controlee = kzalloc(sizeof(struct fira_controlee), GFP_KERNEL); + if (!controlee) { + r = -ENOMEM; + goto end; + } + + if (!attrs[FIRA_CALL_CONTROLEE_ATTR_SHORT_ADDR] || + (!attrs[FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_ID] && + attrs[FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_KEY])) { + kfree(controlee); + r = -EINVAL; + goto end; + } + + controlee->short_addr = nla_get_le16( + attrs[FIRA_CALL_CONTROLEE_ATTR_SHORT_ADDR]); + if (attrs[FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_ID]) { + if (call_id == FIRA_CALL_DEL_CONTROLEE) { + kfree(controlee); + r = -EINVAL; + goto end; + } + controlee->sub_session = true; + controlee->sub_session_id = nla_get_u32( + attrs[FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_ID]); + if (attrs[FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_KEY]) { + memcpy(controlee->sub_session_key, + nla_data( + attrs[FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_KEY]), + nla_len(attrs[FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_KEY])); + controlee->sub_session_key_len = nla_len( + attrs[FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_KEY]); + } + } else { + controlee->sub_session = false; + } + controlee->range_data_ntf_status = FIRA_RANGE_DATA_NTF_NONE; + controlee->state = FIRA_CONTROLEE_STATE_RUNNING; + /* Check and reject a duplication of short_addr. */ + list_for_each_entry (tmp_controlee, &controlees, entry) { + if (controlee->short_addr == + tmp_controlee->short_addr) { + kfree(controlee); + r = -EINVAL; + goto end; + } + } + list_add_tail(&controlee->entry, &controlees); + n_controlees++; + } + if (list_empty(&controlees)) { + r = -EINVAL; + goto end; + } + + /* + * Be careful, active is not equal to + * 'local->current_session == session'. + */ + is_active = fira_session_is_active(session); + + if (is_active) { + /* If unicast refuse to add more than one controlee. */ + switch (call_id) { + case FIRA_CALL_NEW_CONTROLEE: + if (session->params.multi_node_mode == + FIRA_MULTI_NODE_MODE_UNICAST) { + r = -EPERM; + goto end; + } + break; + case FIRA_CALL_SET_CONTROLEE: + r = -EBUSY; + goto end; + default: + break; + } + } + + slot_duration_us = (session->params.slot_duration_dtu * 1000) / + (local->llhw->dtu_freq_hz / 1000); + + switch (call_id) { + case FIRA_CALL_SET_CONTROLEE: + r = fira_session_set_controlees(local, session, &controlees, + slot_duration_us, n_controlees); + break; + case FIRA_CALL_DEL_CONTROLEE: + if (n_controlees > 1) { + r = -EINVAL; + goto end; + } else { + /* 'controlee' points the unique entry in the list. */ + r = fira_session_del_controlee(session, controlee, + is_active); + } + break; + /* FIRA_CALL_NEW_CONTROLEE. */ + default: + if (n_controlees > 1) { + r = -EINVAL; + goto end; + } else { + /* 'controlee' points the unique entry in the list. */ + r = fira_session_new_controlee(local, session, + controlee, + slot_duration_us, + is_active); + } + } + if (r) + goto end; + + if (!is_active && local->llhw->rx_ctx_size) { + for (i = 0; i < session->n_current_controlees; i++) { + memset(session->rx_ctx[i], 0, local->llhw->rx_ctx_size); + } + } + + fira_session_fsm_controlee_list_updated(local, session); +end: + list_for_each_entry_safe (controlee, tmp_controlee, &controlees, + entry) { + kfree(controlee); + } + return r; +} + +/** + * fira_session_get_controlees() - Get list of controlees. + * @local: FiRa context. + * @session_id: FiRa session id. + * @info: Request information. + * + * Return: 0 or error. + */ +static int fira_session_get_controlees(struct fira_local *local, u32 session_id, + const struct genl_info *info) +{ + const struct fira_session *session; + struct sk_buff *msg; + struct nlattr *controlees, *controlee_attr; + struct fira_controlee *controlee; + + session = fira_get_session_by_session_id(local, session_id); + if (!session) + return -ENOENT; + + msg = mcps802154_region_call_alloc_reply_skb(local->llhw, + &local->region, + FIRA_CALL_GET_CONTROLEES, + NLMSG_DEFAULT_SIZE); + if (!msg) + return -ENOMEM; + + if (nla_put_u32(msg, FIRA_CALL_ATTR_SESSION_ID, session->id)) + goto nla_put_failure; + + controlees = nla_nest_start(msg, FIRA_CALL_ATTR_CONTROLEES); + if (!controlees) + goto nla_put_failure; + + list_for_each_entry (controlee, &session->current_controlees, entry) { + controlee_attr = nla_nest_start(msg, 1); + if (!controlee_attr) + goto nla_put_failure; + if (nla_put_le16(msg, FIRA_CALL_CONTROLEE_ATTR_SHORT_ADDR, + controlee->short_addr)) + goto nla_put_failure; + if (controlee->sub_session && + nla_put_u32(msg, FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_ID, + controlee->sub_session_id)) + goto nla_put_failure; + nla_nest_end(msg, controlee_attr); + } + + nla_nest_end(msg, controlees); + + return mcps802154_region_call_reply(local->llhw, msg); +nla_put_failure: + kfree_skb(msg); + return -ENOBUFS; +} + +int fira_get_capabilities(struct fira_local *local, + const struct genl_info *info) +{ + struct sk_buff *msg; + struct nlattr *capabilities; + u64 hw_flags = local->llhw->flags; + u32 sts_caps = fira_crypto_get_capabilities(); + + if (!info) + return 0; + + msg = mcps802154_region_call_alloc_reply_skb(local->llhw, + &local->region, + FIRA_CALL_GET_CAPABILITIES, + NLMSG_DEFAULT_SIZE); + if (!msg) + return -ENOMEM; + + capabilities = nla_nest_start(msg, FIRA_CALL_ATTR_CAPABILITIES); + if (!capabilities) { + goto nla_put_failure; + } + +#define P(name, type, value) \ + do { \ + if (nla_put_##type(msg, FIRA_CAPABILITY_ATTR_##name, value)) { \ + goto nla_put_failure; \ + } \ + } while (0) +#define F(name) \ + do { \ + if (nla_put_flag(msg, FIRA_CAPABILITY_ATTR_##name)) { \ + goto nla_put_failure; \ + } \ + } while (0) +#define C(name, hw_flag) \ + do { \ + if (hw_flags & (hw_flag)) \ + F(name); \ + } while (0) +#define S(mode) \ + do { \ + if (sts_caps & (1 << FIRA_STS_MODE_##mode)) \ + F(STS_##mode); \ + } while (0) + + /* Main session capabilities. */ + P(FIRA_PHY_VERSION_RANGE, u32, 0x01010101); + P(FIRA_MAC_VERSION_RANGE, u32, 0x01010101); + P(DEVICE_CLASS, u8, 1); + F(DEVICE_TYPE_CONTROLEE_RESPONDER); + F(DEVICE_TYPE_CONTROLLER_INITIATOR); + F(MULTI_NODE_MODE_UNICAST); + F(MULTI_NODE_MODE_ONE_TO_MANY); + F(RANGING_ROUND_USAGE_DS_TWR); + P(NUMBER_OF_CONTROLEES_MAX, u32, FIRA_CONTROLEES_MAX); + /* Behaviour. */ + F(ROUND_HOPPING); + F(BLOCK_STRIDING); + /* Radio. */ + P(CHANNEL_NUMBER, u16, + local->llhw->hw->phy->supported + .channels[local->llhw->hw->phy->current_page]); + C(RFRAME_CONFIG_SP1, + MCPS802154_LLHW_STS_SEGMENT_1 | MCPS802154_LLHW_STS_SEGMENT_2); + C(RFRAME_CONFIG_SP3, + MCPS802154_LLHW_STS_SEGMENT_1 | MCPS802154_LLHW_STS_SEGMENT_2); + C(PRF_MODE_BPRF, MCPS802154_LLHW_BPRF); + C(PRF_MODE_HPRF, MCPS802154_LLHW_HPRF); + C(PREAMBLE_DURATION_32, MCPS802154_LLHW_PSR_32); + C(PREAMBLE_DURATION_64, MCPS802154_LLHW_PSR_64); + C(SFD_ID_0, MCPS802154_LLHW_SFD_4A); + C(SFD_ID_1, MCPS802154_LLHW_SFD_4Z_4); + C(SFD_ID_2, MCPS802154_LLHW_SFD_4Z_8); + C(SFD_ID_3, MCPS802154_LLHW_SFD_4Z_16); + C(SFD_ID_4, MCPS802154_LLHW_SFD_4Z_32); + F(NUMBER_OF_STS_SEGMENTS_0); + C(NUMBER_OF_STS_SEGMENTS_1, MCPS802154_LLHW_STS_SEGMENT_1); + C(NUMBER_OF_STS_SEGMENTS_2, MCPS802154_LLHW_STS_SEGMENT_2); + C(NUMBER_OF_STS_SEGMENTS_3, MCPS802154_LLHW_STS_SEGMENT_3); + C(NUMBER_OF_STS_SEGMENTS_4, MCPS802154_LLHW_STS_SEGMENT_4); + C(PSDU_DATA_RATE_6M81, MCPS802154_LLHW_DATA_RATE_6M81); + C(PSDU_DATA_RATE_7M80, MCPS802154_LLHW_DATA_RATE_7M80); + C(PSDU_DATA_RATE_27M2, MCPS802154_LLHW_DATA_RATE_27M2); + C(PSDU_DATA_RATE_31M2, MCPS802154_LLHW_DATA_RATE_31M2); + C(BPRF_PHR_DATA_RATE_850K, MCPS802154_LLHW_PHR_DATA_RATE_850K); + C(BPRF_PHR_DATA_RATE_6M81, MCPS802154_LLHW_PHR_DATA_RATE_6M81); + F(TX_ADAPTIVE_PAYLOAD_POWER); + /* Antenna. */ + P(RX_ANTENNA_PAIRS, u32, local->llhw->rx_antenna_pairs); + P(TX_ANTENNAS, u32, local->llhw->tx_antennas); + /* STS and crypto capabilities. */ + S(STATIC); + S(DYNAMIC); + S(DYNAMIC_INDIVIDUAL_KEY); + S(PROVISIONED); + S(PROVISIONED_INDIVIDUAL_KEY); + /* Report. */ + C(AOA_AZIMUTH, MCPS802154_LLHW_AOA_AZIMUTH); + C(AOA_AZIMUTH_FULL, MCPS802154_LLHW_AOA_AZIMUTH_FULL); + C(AOA_ELEVATION, MCPS802154_LLHW_AOA_ELEVATION); + C(AOA_FOM, MCPS802154_LLHW_AOA_FOM); +#undef C +#undef F +#undef P + + nla_nest_end(msg, capabilities); + return mcps802154_region_call_reply(local->llhw, msg); +nla_put_failure: + kfree_skb(msg); + return -ENOBUFS; +} + +int fira_session_get_count(struct fira_local *local) +{ + struct fira_session *session; + struct sk_buff *msg; + u32 count = 0; + + list_for_each_entry (session, &local->inactive_sessions, entry) { + count++; + } + + list_for_each_entry (session, &local->active_sessions, entry) { + count++; + } + + msg = mcps802154_region_call_alloc_reply_skb( + local->llhw, &local->region, FIRA_CALL_SESSION_GET_COUNT, + NLMSG_DEFAULT_SIZE); + if (!msg) + return -ENOMEM; + + if (nla_put_u32(msg, FIRA_CALL_ATTR_SESSION_COUNT, count)) + goto nla_put_failure; + + return mcps802154_region_call_reply(local->llhw, msg); + +nla_put_failure: + kfree_skb(msg); + return -ENOBUFS; +} + +int fira_session_control(struct fira_local *local, enum fira_call call_id, + const struct nlattr *params, + const struct genl_info *info) +{ + u32 session_id; + struct nlattr *attrs[FIRA_CALL_ATTR_MAX + 1]; + int r; + + /* Special case of get count that doesn't need params. */ + if (call_id == FIRA_CALL_SESSION_GET_COUNT) + return fira_session_get_count(local); + + if (!params) + return -EINVAL; + r = nla_parse_nested(attrs, FIRA_CALL_ATTR_MAX, params, + fira_call_nla_policy, info->extack); + + if (r) + return r; + + if (!attrs[FIRA_CALL_ATTR_SESSION_ID]) + return -EINVAL; + + session_id = nla_get_u32(attrs[FIRA_CALL_ATTR_SESSION_ID]); + trace_region_fira_session_control(local, session_id, call_id); + + switch (call_id) { + case FIRA_CALL_SESSION_INIT: + return fira_session_init(local, session_id); + case FIRA_CALL_SESSION_START: + return fira_session_start(local, session_id, info); + case FIRA_CALL_SESSION_STOP: + return fira_session_stop(local, session_id); + case FIRA_CALL_SESSION_DEINIT: + return fira_session_deinit(local, session_id); + case FIRA_CALL_SESSION_SET_PARAMS: + return fira_session_set_parameters( + local, session_id, attrs[FIRA_CALL_ATTR_SESSION_PARAMS], + info); + case FIRA_CALL_SET_CONTROLEE: + case FIRA_CALL_NEW_CONTROLEE: + case FIRA_CALL_DEL_CONTROLEE: + return fira_manage_controlees(local, call_id, session_id, + attrs[FIRA_CALL_ATTR_CONTROLEES], + info); + case FIRA_CALL_GET_CONTROLEES: + return fira_session_get_controlees(local, session_id, info); + case FIRA_CALL_SESSION_GET_PARAMS: + return fira_session_get_parameters(local, session_id); + case FIRA_CALL_SESSION_GET_STATE: + return fira_session_get_state(local, session_id); + default: + return -EINVAL; + } +} diff --git a/mac/fira_region_call.h b/mac/fira_region_call.h new file mode 100644 index 0000000..670b6b3 --- /dev/null +++ b/mac/fira_region_call.h @@ -0,0 +1,59 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef NET_MCPS802154_FIRA_REGION_CALL_H +#define NET_MCPS802154_FIRA_REGION_CALL_H + +#include "fira_region.h" + +/** + * fira_get_capabilities() - Get FiRa capabilities. + * @local: FiRa context. + * @info: Request information. + * + * Return: 0 or error. + */ +int fira_get_capabilities(struct fira_local *local, + const struct genl_info *info); + +/** + * fira_session_control() - Control FiRa session. + * @local: FiRa context. + * @call_id: Identifier of the FiRa procedure. + * @params: Nested attribute containing procedure parameters. + * @info: Request information. + * + * Return: 0 or error. + */ +int fira_session_control(struct fira_local *local, enum fira_call call_id, + const struct nlattr *params, + const struct genl_info *info); + +/** + * fira_session_get_count() - Get count of active and inactive sessions. + * @local: FiRa context. + * + * Return: 0 or error. + */ +int fira_session_get_count(struct fira_local *local); + +#endif /* NET_MCPS802154_FIRA_REGION_CALL_H */ diff --git a/mac/fira_round_hopping_crypto.h b/mac/fira_round_hopping_crypto.h new file mode 100644 index 0000000..0f0603a --- /dev/null +++ b/mac/fira_round_hopping_crypto.h @@ -0,0 +1,60 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef FIRA_ROUND_HOPPING_CRYPTO_H +#define FIRA_ROUND_HOPPING_CRYPTO_H + +#include <linux/types.h> +#include <crypto/aes.h> + +struct fira_round_hopping_sequence; + +/** + * fira_round_hopping_crypto_encrypt() - Compute a cipher using AES. + * @round_hopping_sequence: Round hopping context. + * @data: Input data, with length AES_BLOCK_SIZE. + * @out: Output hash, with length AES_BLOCK_SIZE. + * + * Return: 0 or error. + */ +int fira_round_hopping_crypto_encrypt( + const struct fira_round_hopping_sequence *round_hopping_sequence, + const u8 *data, u8 *out); + +/** + * fira_round_hopping_crypto_init() - Initialize round hopping context. + * @round_hopping_sequence: Round hopping context. + * + * Return: 0 or error. + */ +int fira_round_hopping_crypto_init( + struct fira_round_hopping_sequence *round_hopping_sequence); + +/** + * fira_round_hopping_crypto_destroy() - Destroy round hopping context. + * @round_hopping_sequence: Round hopping context. + */ +void fira_round_hopping_crypto_destroy( + struct fira_round_hopping_sequence *round_hopping_sequence); + +#endif /* FIRA_ROUND_HOPPING_CRYPTO_H */ diff --git a/mac/fira_round_hopping_sequence.c b/mac/fira_round_hopping_sequence.c new file mode 100644 index 0000000..921f8cc --- /dev/null +++ b/mac/fira_round_hopping_sequence.c @@ -0,0 +1,73 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include "fira_session.h" +#include <linux/string.h> +#include <asm/unaligned.h> + +int fira_round_hopping_sequence_init(struct fira_session *session) +{ + struct fira_round_hopping_sequence *round_hopping_sequence = + &session->round_hopping_sequence; + + memset(round_hopping_sequence->key, 0, AES_KEYSIZE_128 - sizeof(u32)); + put_unaligned_be32(session->id, round_hopping_sequence->key + + AES_KEYSIZE_128 - sizeof(u32)); + return fira_round_hopping_crypto_init(round_hopping_sequence); +} + +void fira_round_hopping_sequence_destroy(struct fira_session *session) +{ + struct fira_round_hopping_sequence *round_hopping_sequence = + &session->round_hopping_sequence; + + fira_round_hopping_crypto_destroy(round_hopping_sequence); +} + +int fira_round_hopping_sequence_get(const struct fira_session *session, + int block_index) +{ + const struct fira_session_params *params = &session->params; + const struct fira_round_hopping_sequence *round_hopping_sequence = + &session->round_hopping_sequence; + int block_duration_slots = + params->block_duration_dtu / params->slot_duration_dtu; + int n_rounds = block_duration_slots / params->round_duration_slots; + u8 block[AES_BLOCK_SIZE]; + u8 out[AES_BLOCK_SIZE]; + int r; + + if (!block_index) + return 0; + memset(block, 0, AES_BLOCK_SIZE - sizeof(u32)); + put_unaligned_be32(block_index, block + AES_BLOCK_SIZE - sizeof(u32)); + r = fira_round_hopping_crypto_encrypt(round_hopping_sequence, block, + out); + if (!r) { + u16 hash = + get_unaligned_be16(out + AES_BLOCK_SIZE - sizeof(u16)); + + return hash * n_rounds >> 16; + } + return 0; +} diff --git a/mac/fira_round_hopping_sequence.h b/mac/fira_round_hopping_sequence.h new file mode 100644 index 0000000..a89d91b --- /dev/null +++ b/mac/fira_round_hopping_sequence.h @@ -0,0 +1,53 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_FIRA_ROUND_HOPPING_SEQUENCE_H +#define NET_MCPS802154_FIRA_ROUND_HOPPING_SEQUENCE_H + +struct fira_session; + +/** + * fira_round_hopping_sequence_init() - Initialize round hopping context. + * @session: Session. + * + * Return: 0 or error. + */ +int fira_round_hopping_sequence_init(struct fira_session *session); + +/** + * fira_round_hopping_sequence_destroy() - Destroy round hopping context. + * @session: Session. + */ +void fira_round_hopping_sequence_destroy(struct fira_session *session); + +/** + * fira_round_hopping_sequence_get() - Get round index for block index. + * @session: Session. + * @block_index: Block index. + * + * Return: Round index. + */ +int fira_round_hopping_sequence_get(const struct fira_session *session, + int block_index); + +#endif /* NET_MCPS802154_FIRA_ROUND_HOPPING_SEQUENCE_H */ diff --git a/mac/fira_session.c b/mac/fira_session.c new file mode 100644 index 0000000..b4c8e75 --- /dev/null +++ b/mac/fira_session.c @@ -0,0 +1,1217 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/bitops.h> +#include <linux/errno.h> +#include <linux/ieee802154.h> +#include <linux/string.h> +#include <linux/limits.h> + +#include <net/mcps802154_frame.h> +#include <net/fira_region_nl.h> + +#include "fira_session.h" +#include "fira_round_hopping_sequence.h" +#include "fira_access.h" +#include "fira_frame.h" +#include "fira_trace.h" + +inline static int +fira_compute_minimum_rssi(const struct fira_ranging_info *ranging_data) +{ + /* + * We want the WORST RSSI level. + * Please Note : RSSI is actually a negative number, but encoded + * as an absolute value. + */ + u8 min_rssi = 0; + int i; + + for (i = 0; i < ranging_data->n_rx_rssis; i++) + min_rssi = max(ranging_data->rx_rssis[i], min_rssi); + return min_rssi; +} + +inline static int +fira_compute_average_rssi(const struct fira_ranging_info *ranging_data) +{ + unsigned sum; + int i; + + if (!ranging_data->n_rx_rssis) + return 0; + + for (i = 0, sum = 0; i < ranging_data->n_rx_rssis; i++) + sum += ranging_data->rx_rssis[i]; + return sum / i; +} + +static int fira_report_local_aoa(struct sk_buff *msg, int nest_attr_id, + const struct fira_local_aoa_info *info) +{ + struct nlattr *aoa; + + aoa = nla_nest_start(msg, nest_attr_id); + if (!aoa) + goto nla_put_failure; +#define A(x) FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_##x + if (nla_put_u8(msg, A(RX_ANTENNA_SET), info->rx_ant_set)) + goto nla_put_failure; + if (nla_put_s16(msg, A(AOA_2PI), info->aoa_2pi)) + goto nla_put_failure; + if (nla_put_s16(msg, A(PDOA_2PI), info->pdoa_2pi)) + goto nla_put_failure; + if (nla_put_u8(msg, A(AOA_FOM), info->aoa_fom)) + goto nla_put_failure; +#undef A + nla_nest_end(msg, aoa); + return 0; +nla_put_failure: + return -EMSGSIZE; +} + +inline static int fira_session_report_measurement( + const struct fira_session *session, struct sk_buff *msg, + const struct fira_ranging_info *ranging_data, s64 rctu_freq_hz) +{ + const struct fira_session_params *params = &session->params; + bool report_rssi_val_present = false; + int report_rssi_val = 0; + + if (params->report_rssi && + ranging_data->status == FIRA_STATUS_RANGING_SUCCESS) { + switch (params->report_rssi) { + case FIRA_RSSI_REPORT_MINIMUM: + report_rssi_val_present = true; + report_rssi_val = + fira_compute_minimum_rssi(ranging_data); + break; + case FIRA_RSSI_REPORT_AVERAGE: + report_rssi_val_present = true; + report_rssi_val = + fira_compute_average_rssi(ranging_data); + break; + default: + break; + } + } + +#define A(x) FIRA_RANGING_DATA_MEASUREMENTS_ATTR_##x + + if (nla_put_u16(msg, A(SHORT_ADDR), ranging_data->short_addr) || + nla_put_u8(msg, A(STATUS), ranging_data->status)) + goto nla_put_failure; + + if (ranging_data->status) { + if (nla_put_u8(msg, A(SLOT_INDEX), ranging_data->slot_index)) + goto nla_put_failure; + return 0; + } + if (ranging_data->tof_present) { + if (nla_put_s32(msg, A(DISTANCE_MM), + fira_rctu_to_mm(rctu_freq_hz, ranging_data->tof_rctu))) + goto nla_put_failure; + } + if (ranging_data->local_aoa.present) { + if (fira_report_local_aoa(msg, A(LOCAL_AOA), + &ranging_data->local_aoa)) + goto nla_put_failure; + } + if (ranging_data->local_aoa_azimuth.present) { + if (fira_report_local_aoa(msg, A(LOCAL_AOA_AZIMUTH), + &ranging_data->local_aoa_azimuth)) + goto nla_put_failure; + } + if (ranging_data->local_aoa_elevation.present) { + if (fira_report_local_aoa(msg, A(LOCAL_AOA_ELEVATION), + &ranging_data->local_aoa_elevation)) + goto nla_put_failure; + } + if (ranging_data->remote_aoa_azimuth_present) { + if (nla_put_s16(msg, A(REMOTE_AOA_AZIMUTH_2PI), + ranging_data->remote_aoa_azimuth_2pi)) + goto nla_put_failure; + if (ranging_data->remote_aoa_fom_present) { + if (nla_put_u8(msg, A(REMOTE_AOA_AZIMUTH_FOM), + ranging_data->remote_aoa_azimuth_fom)) + goto nla_put_failure; + } + } + if (ranging_data->remote_aoa_elevation_present) { + if (nla_put_s16(msg, A(REMOTE_AOA_ELEVATION_PI), + ranging_data->remote_aoa_elevation_pi)) + goto nla_put_failure; + if (ranging_data->remote_aoa_fom_present) { + if (nla_put_u8(msg, A(REMOTE_AOA_ELEVATION_FOM), + ranging_data->remote_aoa_elevation_fom)) + goto nla_put_failure; + } + } + if (report_rssi_val_present) { + if (nla_put_u8(msg, A(RSSI), report_rssi_val)) + goto nla_put_failure; + } + if (ranging_data->data_payload_len > 0) { + if (nla_put(msg, A(DATA_PAYLOAD_RECV), + ranging_data->data_payload_len, + ranging_data->data_payload)) + goto nla_put_failure; + } + if (session->data_payload.sent) { + if (nla_put_u32(msg, A(DATA_PAYLOAD_SEQ_SENT), + session->data_payload.seq)) + goto nla_put_failure; + } + +#undef A + return 0; +nla_put_failure: + return -EMSGSIZE; +} + +inline static int fira_report_measurement_stopped_controlee(struct sk_buff *msg, + __le16 short_addr) +{ +#define A(x) FIRA_RANGING_DATA_MEASUREMENTS_ATTR_##x + + if (nla_put_u16(msg, A(SHORT_ADDR), short_addr) || + nla_put_u8(msg, A(STOPPED), 1)) + goto nla_put_failure; + +#undef A + return 0; +nla_put_failure: + return -EMSGSIZE; +} + +inline static int +fira_session_report_ranging_data(const struct fira_session *session, + const struct fira_report_info *report_info, + int dtu_freq_hz, int dtu_rctu, + struct sk_buff *msg) +{ + const struct fira_session_params *params = &session->params; + struct nlattr *data, *measurements, *measurement; + int ranging_interval_ms = params->block_duration_dtu * + (session->block_stride_len + 1) / + (dtu_freq_hz / 1000); + s64 rctu_freq_hz = (s64)dtu_freq_hz * dtu_rctu; + + int i; + int n_ranging_to_send = 0; + + data = nla_nest_start(msg, FIRA_CALL_ATTR_RANGING_DATA); + if (!data) + goto nla_put_failure; + + if (nla_put_u32(msg, FIRA_RANGING_DATA_ATTR_BLOCK_INDEX, + session->block_index) || + nla_put_u32(msg, FIRA_RANGING_DATA_ATTR_RANGING_INTERVAL_MS, + ranging_interval_ms)) + goto nla_put_failure; + + if (report_info->stopped) { + enum fira_ranging_data_attrs_stopped_values stopped; + + if (session->stop_no_response) + stopped = FIRA_RANGING_DATA_ATTR_STOPPED_NO_RESPONSE; + else if (session->stop_inband) + stopped = FIRA_RANGING_DATA_ATTR_STOPPED_IN_BAND; + else + stopped = FIRA_RANGING_DATA_ATTR_STOPPED_REQUEST; + + if (nla_put_u8(msg, FIRA_RANGING_DATA_ATTR_STOPPED, stopped)) + goto nla_put_failure; + + /* + Case where measurements are not available: + - A controller stop request. + - A controller max measurements reached. + - A controlee stop in band. + */ + if ((session->params.device_type == + FIRA_DEVICE_TYPE_CONTROLLER && + stopped == FIRA_RANGING_DATA_ATTR_STOPPED_REQUEST) || + (session->params.device_type == + FIRA_DEVICE_TYPE_CONTROLEE && + stopped == FIRA_RANGING_DATA_ATTR_STOPPED_IN_BAND)) + goto end_report; + } + + for (i = 0; i < report_info->n_ranging_data; i++) { + if (report_info->ranging_data[i].notify) + n_ranging_to_send++; + } + + if (n_ranging_to_send + report_info->n_stopped_controlees) { + measurements = nla_nest_start( + msg, FIRA_RANGING_DATA_ATTR_MEASUREMENTS); + if (!measurements) + goto nla_put_failure; + + for (i = 0; i < report_info->n_ranging_data; i++) { + if (!report_info->ranging_data[i].notify) + continue; + measurement = nla_nest_start(msg, 1); + if (fira_session_report_measurement( + session, msg, &report_info->ranging_data[i], + rctu_freq_hz)) + goto nla_put_failure; + nla_nest_end(msg, measurement); + } + + for (i = 0; i < report_info->n_stopped_controlees; i++) { + measurement = nla_nest_start(msg, 1); + if (fira_report_measurement_stopped_controlee( + msg, report_info->stopped_controlees[i])) + goto nla_put_failure; + nla_nest_end(msg, measurement); + } + + nla_nest_end(msg, measurements); + } + +end_report: + nla_nest_end(msg, data); + return 0; + +nla_put_failure: + return -EMSGSIZE; +} + +static int +fira_session_report_diagnostic_rssi(const struct fira_diagnostic *diagnostic, + struct sk_buff *msg) +{ + struct nlattr *nest; + int i; + + nest = nla_nest_start( + msg, FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_RSSIS); + if (!nest) + goto nla_put_failure; + for (i = 0; i < diagnostic->n_rssis; i++) { + if (nla_put_u8(msg, i, diagnostic->rssis_q1[i])) + goto nla_put_failure; + } + nla_nest_end(msg, nest); + return 0; + +nla_put_failure: + return -EMSGSIZE; +} + +static int +fira_session_report_diagnostic_aoa(const struct fira_diagnostic *diagnostic, + struct sk_buff *msg) +{ + const struct mcps802154_rx_aoa_measurements *aoa; + struct nlattr *aoas, *aoa_report; + int i; + + aoas = nla_nest_start(msg, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_AOAS); + if (!aoas) + goto nla_put_failure; + for (i = 0; i < diagnostic->n_aoas; i++) { + aoa = &diagnostic->aoas[i]; + + aoa_report = nla_nest_start(msg, i); + if (!aoa_report) + goto nla_put_failure; +#define A(x) FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_##x + if (nla_put_s16(msg, A(TDOA), aoa->tdoa_rctu)) + goto nla_put_failure; + if (nla_put_s16(msg, A(PDOA), aoa->pdoa_rad_q11)) + goto nla_put_failure; + if (nla_put_s16(msg, A(AOA), aoa->aoa_rad_q11)) + goto nla_put_failure; + if (nla_put_u8(msg, A(FOM), aoa->fom)) + goto nla_put_failure; + if (nla_put_u8(msg, A(TYPE), aoa->type)) + goto nla_put_failure; +#undef A + nla_nest_end(msg, aoa_report); + } + nla_nest_end(msg, aoas); + return 0; + +nla_put_failure: + return -EMSGSIZE; +} + +static int fira_session_report_cir_samples(const struct mcps802154_rx_cir *cir, + struct sk_buff *msg) +{ + const struct mcps802154_rx_cir_sample_window *sw = &cir->sample_window; + const u8 *samples = sw->samples; + struct nlattr *sample_nest; + int i; + + sample_nest = nla_nest_start( + msg, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_SAMPLE_WINDOW); + if (!sample_nest) + goto nla_put_failure; + for (i = 0; i < sw->n_samples; i++) { + const u8 *sample = &samples[i * sw->sizeof_sample]; + + if (nla_put(msg, i, sw->sizeof_sample, sample)) + goto nla_put_failure; + } + nla_nest_end(msg, sample_nest); + return 0; + +nla_put_failure: + return -EMSGSIZE; +} + +static int +fira_session_report_diagnostic_cir(const struct fira_diagnostic *diagnostic, + struct sk_buff *msg) +{ + struct nlattr *cirs_nest, *cir_nest; + int i; + + cirs_nest = nla_nest_start( + msg, FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_CIRS); + if (!cirs_nest) + goto nla_put_failure; + for (i = 0; i < diagnostic->n_cirs; i++) { + struct mcps802154_rx_cir *cir = &diagnostic->cirs[i]; + + cir_nest = nla_nest_start(msg, i); + if (!cir_nest) + goto nla_put_failure; +#define A(x) FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_##x + if (nla_put_u16(msg, A(FP_IDX), cir->fp_index)) + goto nla_put_failure; + if (nla_put_s16(msg, A(FP_SNR), cir->fp_snr)) + goto nla_put_failure; + if (nla_put_u16(msg, A(FP_NS), cir->fp_ns_q6)) + goto nla_put_failure; + if (nla_put_u16(msg, A(PP_IDX), cir->pp_index)) + goto nla_put_failure; + if (nla_put_s16(msg, A(PP_SNR), cir->pp_snr)) + goto nla_put_failure; + if (nla_put_u16(msg, A(PP_NS), cir->pp_ns_q6)) + goto nla_put_failure; + if (nla_put_u16(msg, A(FP_SAMPLE_OFFSET), + cir->fp_sample_offset)) + goto nla_put_failure; +#undef A + if (fira_session_report_cir_samples(cir, msg)) + goto nla_put_failure; + nla_nest_end(msg, cir_nest); + } + nla_nest_end(msg, cirs_nest); + return 0; + +nla_put_failure: + return -EMSGSIZE; +} + +static int fira_session_report_frame_diagnostics( + const struct fira_session *session, + const struct fira_report_info *report_info, struct sk_buff *msg) +{ + const struct fira_session_params *params = &session->params; + struct nlattr *frame_nest, *reports_nest; + bool is_controller = params->device_type == FIRA_DEVICE_TYPE_CONTROLLER; + int i; + + frame_nest = nla_nest_start( + msg, FIRA_RANGING_DIAGNOSTICS_ATTR_FRAME_REPORTS); + if (!frame_nest) + goto nla_put_failure; + + for (i = 0; i < report_info->n_slots; i++) { + const struct fira_slot *slot = &report_info->slots[i]; + int is_tx = slot->controller_tx ? is_controller : + !is_controller; + + reports_nest = nla_nest_start(msg, i); + if (!reports_nest) + goto nla_put_failure; +#define A(x) FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_##x + if (nla_put_u8(msg, A(ANT_SET), + is_tx ? slot->tx_ant_set : slot->rx_ant_set)) + goto nla_put_failure; + if (nla_put_u8(msg, A(ACTION), is_tx)) + goto nla_put_failure; + if (nla_put_u8(msg, A(MSG_ID), slot->message_id)) + goto nla_put_failure; +#undef A + /* Specific reports are done for Rx frames only. */ + if (!is_tx) { + const struct fira_diagnostic *diagnostic = + &report_info->diagnostics[i]; + + if (params->diagnostic_report_flags & + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_RSSIS) { + if (fira_session_report_diagnostic_rssi( + diagnostic, msg)) + goto nla_put_failure; + } + if (params->diagnostic_report_flags & + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_AOAS) { + if (fira_session_report_diagnostic_aoa( + diagnostic, msg)) + goto nla_put_failure; + } + if (params->diagnostic_report_flags & + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_CIRS) { + if (fira_session_report_diagnostic_cir( + diagnostic, msg)) + goto nla_put_failure; + } + } + nla_nest_end(msg, reports_nest); + } + nla_nest_end(msg, frame_nest); + return 0; + +nla_put_failure: + return -EMSGSIZE; +} + +static inline int fira_session_report_ranging_diagnostics( + const struct fira_session *session, + const struct fira_report_info *report_info, struct sk_buff *msg) +{ + const struct fira_session_params *params = &session->params; + struct nlattr *diagnostics_nest; + + if (!params->report_diagnostics) + return 0; + + diagnostics_nest = + nla_nest_start(msg, FIRA_CALL_ATTR_RANGING_DIAGNOSTICS); + if (!diagnostics_nest) + goto nla_put_failure; + + if (fira_session_report_frame_diagnostics(session, report_info, msg)) + goto nla_put_failure; + + nla_nest_end(msg, diagnostics_nest); + return 0; + +nla_put_failure: + return -EMSGSIZE; +} + +struct fira_session *fira_session_new(struct fira_local *local, u32 session_id) +{ + struct fira_session *session; + struct fira_session_params *params; + int all_rx_ctx_size = FIRA_CONTROLEES_MAX * local->llhw->rx_ctx_size; + void *rx_ctx_base = NULL; + int i; + + session = kzalloc(sizeof(*session), GFP_KERNEL); + if (!session) + return NULL; + if (all_rx_ctx_size) { + rx_ctx_base = kzalloc(all_rx_ctx_size, GFP_KERNEL); + if (!rx_ctx_base) + goto failed; + } + + params = &session->params; + session->id = session_id; + session->measurements.reset = true; + + /* Explicit default parameters as implicit is zero. */ + params->ranging_round_usage = FIRA_RANGING_ROUND_USAGE_DSTWR; + params->short_addr = IEEE802154_ADDR_SHORT_BROADCAST; + params->controller_short_addr = IEEE802154_ADDR_SHORT_BROADCAST; + params->slot_duration_dtu = + FIRA_SLOT_DURATION_RSTU_DEFAULT * local->llhw->rstu_dtu; + params->block_duration_dtu = FIRA_BLOCK_DURATION_MS_DEFAULT * + (local->llhw->dtu_freq_hz / 1000); + params->round_duration_slots = FIRA_ROUND_DURATION_SLOTS_DEFAULT; + params->max_rr_retry = FIRA_MAX_RR_RETRY_DEFAULT; + params->round_hopping = false; + params->priority = FIRA_PRIORITY_DEFAULT; + params->sts_length = FIRA_STS_LENGTH_64; + params->sts_config = FIRA_STS_MODE_STATIC; + params->rframe_config = FIRA_RFRAME_CONFIG_SP3; + params->preamble_duration = FIRA_PREAMBULE_DURATION_64; + params->sfd_id = FIRA_SFD_ID_2; + params->number_of_sts_segments = FIRA_STS_SEGMENTS_1; + params->meas_seq.n_steps = 1; + params->meas_seq.steps[0].type = FIRA_MEASUREMENT_TYPE_RANGE; + params->meas_seq.steps[0].n_measurements = 1; + params->meas_seq.steps[0].rx_ant_set_nonranging = 0; + params->meas_seq.steps[0].rx_ant_sets_ranging[0] = 0; + params->meas_seq.steps[0].rx_ant_sets_ranging[1] = 0; + params->meas_seq.steps[0].tx_ant_set_nonranging = 0; + params->meas_seq.steps[0].tx_ant_set_ranging = 0; + /* Report parameters. */ + params->aoa_result_req = true; + params->report_tof = true; + params->result_report_phase = true; + params->range_data_ntf_config = FIRA_RANGE_DATA_NTF_ALWAYS; + params->range_data_ntf_proximity_near_rctu = 0; + params->range_data_ntf_proximity_far_rctu = fira_mm_to_rctu( + local, FIRA_RANGE_DATA_NTF_PROXIMITY_FAR_MM_DEFAULT); + params->range_data_ntf_lower_bound_aoa_azimuth_2pi = + FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI_DEFAULT; + params->range_data_ntf_upper_bound_aoa_azimuth_2pi = + FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI_DEFAULT; + params->range_data_ntf_lower_bound_aoa_elevation_2pi = + FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI_DEFAULT; + params->range_data_ntf_upper_bound_aoa_elevation_2pi = + FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI_DEFAULT; + + if (fira_round_hopping_sequence_init(session)) + goto failed; + + if (all_rx_ctx_size) { + for (i = 0; i < FIRA_CONTROLEES_MAX; i++) { + void *rx_ctx = (char *)rx_ctx_base + + i * local->llhw->rx_ctx_size; + session->rx_ctx[i] = rx_ctx; + } + } + + INIT_LIST_HEAD(&session->current_controlees); + + fira_session_fsm_initialise(local, session); + return session; + +failed: + kfree(rx_ctx_base); + kfree(session); + return NULL; +} + +void fira_session_free(struct fira_local *local, struct fira_session *session) +{ + struct fira_controlee *controlee, *tmp_controlee; + + list_for_each_entry_safe (controlee, tmp_controlee, + &session->current_controlees, entry) { + list_del(&controlee->entry); + kfree(controlee); + } + fira_session_fsm_uninit(local, session); + fira_round_hopping_sequence_destroy(session); + kfree(session->rx_ctx[0]); + kfree_sensitive(session); +} + +int fira_session_set_controlees(struct fira_local *local, + struct fira_session *session, + struct list_head *controlees, + int slot_duration_us, int n_controlees) +{ + int r; + struct fira_controlee *controlee, *tmp_controlee; + + if (!fira_frame_check_n_controlees(session, n_controlees, false)) + return -EINVAL; + + list_for_each_entry_safe (controlee, tmp_controlee, + &session->current_controlees, entry) { + fira_sts_controlee_deinit(controlee); + list_del(&controlee->entry); + kfree(controlee); + } + list_for_each_entry_safe (controlee, tmp_controlee, controlees, entry) { + r = fira_sts_controlee_init( + session, controlee, slot_duration_us, + mcps802154_get_current_channel(local->llhw)); + if (r) + return r; + list_move_tail(&controlee->entry, &session->current_controlees); + } + session->n_current_controlees = n_controlees; + return 0; +} + +int fira_session_new_controlee(struct fira_local *local, + struct fira_session *session, + struct fira_controlee *new_controlee, + int slot_duration_us, bool active_session) +{ + int r; + struct fira_controlee *controlee; + + if (session->n_current_controlees == FIRA_CONTROLEES_MAX) + return -ENOBUFS; + + list_for_each_entry (controlee, &session->current_controlees, entry) { + if (new_controlee->short_addr == controlee->short_addr) + return -EEXIST; + } + + if (active_session) { + new_controlee->state = FIRA_CONTROLEE_STATE_PENDING_RUN; + } else { + r = fira_sts_controlee_init(session, new_controlee, + slot_duration_us, + mcps802154_get_current_channel(local->llhw)); + if (r) + return r; + } + list_move_tail(&new_controlee->entry, &session->current_controlees); + session->n_current_controlees++; + return 0; +} + +int fira_session_del_controlee(struct fira_session *session, + struct fira_controlee *del_controlee, + bool active_session) +{ + struct fira_controlee *controlee, *tmp_controlee; + int ret = -ENOENT; + + if (session->n_current_controlees < 1) + return -ENOENT; + + list_for_each_entry_safe (controlee, tmp_controlee, + &session->current_controlees, entry) { + if (del_controlee->short_addr == controlee->short_addr) { + if (active_session) { + if (controlee->state == + FIRA_CONTROLEE_STATE_STOPPING) + controlee->state = + FIRA_CONTROLEE_STATE_DELETING; + else if (controlee->state != + FIRA_CONTROLEE_STATE_DELETING) { + controlee->state = + FIRA_CONTROLEE_STATE_PENDING_DEL; + } else { + fira_sts_controlee_deinit(controlee); + list_del(&controlee->entry); + kfree(controlee); + session->n_current_controlees--; + } + } + list_del(&del_controlee->entry); + kfree(del_controlee); + ret = 0; + break; + } + } + return ret; +} + +void fira_session_stop_controlees(struct fira_session *session) +{ + struct fira_controlee *controlee; + + list_for_each_entry (controlee, &session->current_controlees, entry) { + controlee->state = FIRA_CONTROLEE_STATE_PENDING_STOP; + } +} + +void fira_session_restart_controlees(struct fira_session *session) +{ + struct fira_controlee *controlee; + + list_for_each_entry (controlee, &session->current_controlees, entry) { + if (controlee->state != FIRA_CONTROLEE_STATE_PENDING_DEL && + controlee->state != FIRA_CONTROLEE_STATE_DELETING) + controlee->state = FIRA_CONTROLEE_STATE_RUNNING; + } +} + +int fira_session_controlees_running_count(const struct fira_session *session) +{ + struct fira_controlee *controlee; + int count = 0; + + list_for_each_entry (controlee, &session->current_controlees, entry) { + if (controlee->state == FIRA_CONTROLEE_STATE_RUNNING || + controlee->state == FIRA_CONTROLEE_STATE_PENDING_STOP || + controlee->state == FIRA_CONTROLEE_STATE_PENDING_DEL) + count++; + } + return count; +} + +void fira_session_update_controlees(struct fira_local *local, + struct fira_session *session) +{ + struct fira_controlee *controlee, *tmp_controlee; + bool reset_rx_ctx = false; + int i, slot_duration_us; + + slot_duration_us = (session->params.slot_duration_dtu * 1000) / + (local->llhw->dtu_freq_hz / 1000); + + list_for_each_entry_safe (controlee, tmp_controlee, + &session->current_controlees, entry) { + if (controlee->state == FIRA_CONTROLEE_STATE_PENDING_RUN) { + controlee->state = FIRA_CONTROLEE_STATE_RUNNING; + reset_rx_ctx = true; + if (fira_sts_controlee_init( + session, controlee, slot_duration_us, + mcps802154_get_current_channel(local->llhw))) + controlee->state = + FIRA_CONTROLEE_STATE_DELETING; + } else if (controlee->state == FIRA_CONTROLEE_STATE_RUNNING) { + /* Stop raised by max number of measurements threshold. */ + if (session->stop_request) + controlee->state = + FIRA_CONTROLEE_STATE_STOPPING; + } else if (controlee->state == + FIRA_CONTROLEE_STATE_PENDING_STOP) { + controlee->state = FIRA_CONTROLEE_STATE_STOPPING; + } else if (controlee->state == + FIRA_CONTROLEE_STATE_PENDING_DEL) { + controlee->state = FIRA_CONTROLEE_STATE_DELETING; + } else if (controlee->state == FIRA_CONTROLEE_STATE_DELETING) { + list_del(&controlee->entry); + kfree(controlee); + session->n_current_controlees--; + reset_rx_ctx = true; + } + } + + if (reset_rx_ctx && local->llhw->rx_ctx_size) { + for (i = 0; i < session->n_current_controlees; i++) { + memset(session->rx_ctx[i], 0, local->llhw->rx_ctx_size); + } + } +} + +/** + * check_parameter_proximity_range() - Check proximity range consistency. + * @params: Current session parameters. + * + * Return: 0 or error. + */ +static inline int +check_parameter_proximity_range(const struct fira_session_params *params) +{ + return (params->range_data_ntf_proximity_near_rctu > + params->range_data_ntf_proximity_far_rctu) ? + -ERANGE : + 0; +} + +/** + * check_parameter_bound_aoa_azimuth() - Check aoa azimuth range consistency. + * @params: Current session parameters. + * + * Return: 0 or error. + */ +static inline int +check_parameter_bound_aoa_azimuth(const struct fira_session_params *params) +{ + return (params->range_data_ntf_lower_bound_aoa_azimuth_2pi > + params->range_data_ntf_upper_bound_aoa_azimuth_2pi) ? + -ERANGE : + 0; +} + +/** + * check_parameter_bound_aoa_elevation() - Check aoa elevation range consistency. + * @params: Current session parameters. + * + * Return: 0 or error. + */ +static inline int +check_parameter_bound_aoa_elevation(const struct fira_session_params *params) +{ + return (params->range_data_ntf_lower_bound_aoa_elevation_2pi > + params->range_data_ntf_upper_bound_aoa_elevation_2pi) ? + -ERANGE : + 0; +} + +bool fira_session_is_ready(const struct fira_local *local, + const struct fira_session *session) +{ + const struct fira_session_params *params = &session->params; + int round_duration_dtu; + + if (params->multi_node_mode == FIRA_MULTI_NODE_MODE_UNICAST) { + if (session->n_current_controlees > 1) + return false; + } else { + /* On success, session will become active, so assume it is. */ + if (!fira_frame_check_n_controlees( + session, session->n_current_controlees, true)) + return false; + } + + /* Check uwb parameters. */ + if (params->prf_mode == FIRA_PRF_MODE_BPRF) { + /* FIXME: when preamble code index is not set, we will use + * the default set one, that may be for HPRF... */ + if (params->preamble_code_index != 0 && + (params->preamble_code_index < 9 || + params->preamble_code_index > 24)) + return false; + if (params->sfd_id != FIRA_SFD_ID_0 && + params->sfd_id != FIRA_SFD_ID_2) + return false; + if (params->psdu_data_rate != FIRA_PSDU_DATA_RATE_6M81) + return false; + if (params->preamble_duration != FIRA_PREAMBULE_DURATION_64) + return false; + if (params->number_of_sts_segments > FIRA_STS_SEGMENTS_1) + return false; + } else { + if (params->preamble_code_index != 0 && + (params->preamble_code_index < 25 || + params->preamble_code_index > 32)) + return false; + if (params->sfd_id == FIRA_SFD_ID_0) + return false; + if (params->prf_mode == FIRA_PRF_MODE_HPRF && + params->psdu_data_rate > FIRA_PSDU_DATA_RATE_7M80) + return false; + if (params->prf_mode == FIRA_PRF_MODE_HPRF_HIGH_RATE && + params->psdu_data_rate < FIRA_PSDU_DATA_RATE_27M2) + return false; + } + if ((params->rframe_config == FIRA_RFRAME_CONFIG_SP0) && + (params->number_of_sts_segments != FIRA_STS_SEGMENTS_0)) + return false; + if ((params->rframe_config != FIRA_RFRAME_CONFIG_SP0) && + (params->number_of_sts_segments == FIRA_STS_SEGMENTS_0)) + return false; + + /* Check range data ntf parameters consistency. */ + switch (params->range_data_ntf_config) { + case FIRA_RANGE_DATA_NTF_PROXIMITY: + case FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA: + case FIRA_RANGE_DATA_NTF_PROXIMITY_CROSSING: + case FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA_CROSSING: + if (check_parameter_proximity_range(&session->params)) + return false; + default: + break; + } + switch (params->range_data_ntf_config) { + case FIRA_RANGE_DATA_NTF_AOA: + case FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA: + case FIRA_RANGE_DATA_NTF_AOA_CROSSING: + case FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA_CROSSING: + if (check_parameter_bound_aoa_azimuth(&session->params)) + return false; + if (check_parameter_bound_aoa_elevation(&session->params)) + return false; + default: + break; + } + round_duration_dtu = + params->slot_duration_dtu * params->round_duration_slots; + return params->slot_duration_dtu != 0 && + params->block_duration_dtu != 0 && + params->round_duration_slots != 0 && + round_duration_dtu <= params->block_duration_dtu; +} + +/** + * is_ranging_in_proximity_bounds() - Check if ranging data are in + * range_data_ntf proximity boundaries. + * @session: FiRa session. + * @ranging_data: Ranging data to be evaluated. + * + * Return: true if the ranging data are inside proximity boundaries. + */ +static inline bool +is_ranging_in_proximity_bounds(const struct fira_session *session, + const struct fira_ranging_info *ranging_data) +{ + return (ranging_data->tof_rctu >= + session->params.range_data_ntf_proximity_near_rctu && + ranging_data->tof_rctu <= + session->params.range_data_ntf_proximity_far_rctu); +} + +/** + * is_ranging_in_aoa_bounds() - Check if ranging data are in + * range_data_ntf AoA boundaries. + * @session: FiRa local session. + * @ranging_data: Ranging data to be evaluated. + * + * Return: true if ranging data are inside AoA boundaries. + */ +static inline bool +is_ranging_in_aoa_bounds(const struct fira_session *session, + const struct fira_ranging_info *ranging_data) +{ + if (ranging_data->local_aoa_azimuth.present && + ((ranging_data->local_aoa_azimuth.aoa_2pi < + session->params.range_data_ntf_lower_bound_aoa_azimuth_2pi) || + (ranging_data->local_aoa_azimuth.aoa_2pi > + session->params.range_data_ntf_upper_bound_aoa_azimuth_2pi))) + return false; + + if (ranging_data->local_aoa_elevation.present && + ((ranging_data->local_aoa_elevation.aoa_2pi < + session->params.range_data_ntf_lower_bound_aoa_elevation_2pi) || + (ranging_data->local_aoa_elevation.aoa_2pi > + session->params.range_data_ntf_upper_bound_aoa_elevation_2pi))) + return false; + return true; +} + +void fira_session_set_range_data_ntf_status(const struct fira_session *session, + struct fira_ranging_info *ri) +{ + switch (session->params.range_data_ntf_config) { + default: + case FIRA_RANGE_DATA_NTF_DISABLED: + case FIRA_RANGE_DATA_NTF_ALWAYS: + ri->range_data_ntf_status = FIRA_RANGE_DATA_NTF_NONE; + break; + case FIRA_RANGE_DATA_NTF_PROXIMITY: + case FIRA_RANGE_DATA_NTF_PROXIMITY_CROSSING: + if (ri->status != FIRA_STATUS_RANGING_SUCCESS || + !ri->tof_present) + ri->range_data_ntf_status = FIRA_RANGE_DATA_NTF_ERROR; + else + ri->range_data_ntf_status = + is_ranging_in_proximity_bounds(session, ri) ? + FIRA_RANGE_DATA_NTF_IN : + FIRA_RANGE_DATA_NTF_OUT; + break; + case FIRA_RANGE_DATA_NTF_AOA: + case FIRA_RANGE_DATA_NTF_AOA_CROSSING: + if (ri->status != FIRA_STATUS_RANGING_SUCCESS) + ri->range_data_ntf_status = FIRA_RANGE_DATA_NTF_ERROR; + else + ri->range_data_ntf_status = + is_ranging_in_aoa_bounds(session, ri) ? + FIRA_RANGE_DATA_NTF_IN : + FIRA_RANGE_DATA_NTF_OUT; + break; + case FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA: + case FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA_CROSSING: + if (FIRA_STATUS_RANGING_SUCCESS || !ri->tof_present) + ri->range_data_ntf_status = FIRA_RANGE_DATA_NTF_ERROR; + else + ri->range_data_ntf_status = + is_ranging_in_proximity_bounds(session, ri) && + is_ranging_in_aoa_bounds( + session, ri) ? + FIRA_RANGE_DATA_NTF_IN : + FIRA_RANGE_DATA_NTF_OUT; + break; + } +} + +/** + * send_ranging_data -- Compute if a device has data to report. + * @config: range_data_ntf_config session parameter. + * @ranging_status: Ranging range_data_ntf_status for the device this round. + * @device_status: Previous device range_data_ntf_status. + * + * Return: true if the device has data to report, else false. + */ +static inline bool +send_ranging_data(enum fira_range_data_ntf_config config, + enum fira_range_data_ntf_status ranging_status, + enum fira_range_data_ntf_status device_status) +{ + switch (config) { + default: + case FIRA_RANGE_DATA_NTF_DISABLED: + return false; + case FIRA_RANGE_DATA_NTF_ALWAYS: + return true; + case FIRA_RANGE_DATA_NTF_PROXIMITY: + case FIRA_RANGE_DATA_NTF_AOA: + case FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA: + return (ranging_status == FIRA_RANGE_DATA_NTF_IN) || + (ranging_status == FIRA_RANGE_DATA_NTF_ERROR) || + (ranging_status == FIRA_RANGE_DATA_NTF_IN_ERROR) || + (ranging_status == FIRA_RANGE_DATA_NTF_OUT_ERROR); + case FIRA_RANGE_DATA_NTF_PROXIMITY_CROSSING: + case FIRA_RANGE_DATA_NTF_AOA_CROSSING: + case FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA_CROSSING: + return (((ranging_status == FIRA_RANGE_DATA_NTF_IN) && + ((device_status == FIRA_RANGE_DATA_NTF_OUT) || + (device_status == FIRA_RANGE_DATA_NTF_OUT_ERROR) || + (device_status == FIRA_RANGE_DATA_NTF_IN_ERROR) || + (device_status == FIRA_RANGE_DATA_NTF_ERROR) || + (device_status == FIRA_RANGE_DATA_NTF_NONE))) || + ((ranging_status == FIRA_RANGE_DATA_NTF_OUT) && + ((device_status == FIRA_RANGE_DATA_NTF_IN) || + (device_status == FIRA_RANGE_DATA_NTF_IN_ERROR))) || + ((ranging_status == FIRA_RANGE_DATA_NTF_ERROR) && + (device_status == FIRA_RANGE_DATA_NTF_IN))); + } +} + +/** + * get_range_data_status_update -- Compute new device's range_data_ntf_status, + * given the ranging status and previous device status. + * @ranging_status: Ranging range_data_ntf_status for the device this round. + * @device_status: Previous device range_data_ntf_status. + * + * Return: new device range_data_ntf_status. + */ +static inline enum fira_range_data_ntf_status +get_range_data_status_update(enum fira_range_data_ntf_status ranging_status, + enum fira_range_data_ntf_status device_status) +{ + enum fira_range_data_ntf_status ret = FIRA_RANGE_DATA_NTF_NONE; + switch (ranging_status) { + case FIRA_RANGE_DATA_NTF_NONE: + case FIRA_RANGE_DATA_NTF_IN: + case FIRA_RANGE_DATA_NTF_OUT: + ret = ranging_status; + break; + case FIRA_RANGE_DATA_NTF_ERROR: + switch (device_status) { + case FIRA_RANGE_DATA_NTF_NONE: + case FIRA_RANGE_DATA_NTF_ERROR: + ret = FIRA_RANGE_DATA_NTF_ERROR; + break; + case FIRA_RANGE_DATA_NTF_IN: + case FIRA_RANGE_DATA_NTF_IN_ERROR: + ret = FIRA_RANGE_DATA_NTF_IN_ERROR; + break; + case FIRA_RANGE_DATA_NTF_OUT: + case FIRA_RANGE_DATA_NTF_OUT_ERROR: + ret = FIRA_RANGE_DATA_NTF_OUT_ERROR; + break; + } + break; + /* defensive check, should not happened */ + /* LCOV_EXCL_START */ + case FIRA_RANGE_DATA_NTF_IN_ERROR: + case FIRA_RANGE_DATA_NTF_OUT_ERROR: + default: + ret = FIRA_RANGE_DATA_NTF_NONE; + break; + } + /* LCOV_EXCL_STOP */ + return ret; +} + +/** + * range_data_notif_update -- Update remote device(s) range_data_ntf_status + * and determine if report is to be sent. + * @local: FiRa context. + * @session: FiRa session. + * @report_info: Report info to be analysed. + * + * Return: true if there is actually data to report, else false. + */ +static bool range_data_notif_update(struct fira_local *local, + struct fira_session *session, + struct fira_report_info *report_info) +{ + bool send_report = false; + enum fira_range_data_ntf_config config = + session->params.range_data_ntf_config; + + if (report_info->stopped || report_info->n_stopped_controlees) + send_report = true; + + if (session->params.device_type == FIRA_DEVICE_TYPE_CONTROLLER) { + int i; + + for (i = 0; i < report_info->n_ranging_data; i++) { + enum fira_range_data_ntf_status ctlee_status; + struct fira_ranging_info *ri = + &report_info->ranging_data[i]; + struct fira_controlee *controlee = + fira_session_get_controlee(session, + ri->short_addr); + + fira_session_set_range_data_ntf_status(session, ri); + if (!controlee) { + /* + * This case can happen in Contention Based mode. + * In this mode, controlees are unknown. Let's notify. + */ + ri->notify = true; + send_report = true; + continue; + } + ctlee_status = controlee->range_data_ntf_status; + ri->notify = send_ranging_data( + config, ri->range_data_ntf_status, + ctlee_status); + controlee->range_data_ntf_status = + get_range_data_status_update( + ri->range_data_ntf_status, + ctlee_status); + send_report |= ri->notify; + } + } else { + /* Controlee, so we can assume n_ranging_data == 0 or 1 */ + if (report_info->n_ranging_data) { + enum fira_range_data_ntf_status ctlr_status = + session->controlee.ctlr_range_data_ntf_status; + struct fira_ranging_info *ri = + &report_info->ranging_data[0]; + fira_session_set_range_data_ntf_status(session, ri); + ri->notify = send_report = send_ranging_data( + config, ri->range_data_ntf_status, ctlr_status); + session->controlee.ctlr_range_data_ntf_status = + get_range_data_status_update( + ri->range_data_ntf_status, ctlr_status); + } + } + return send_report; +} + +void fira_session_report(struct fira_local *local, struct fira_session *session, + struct fira_report_info *report_info) +{ + struct sk_buff *msg; + + if (!range_data_notif_update(local, session, report_info)) + return; + + trace_region_fira_session_report(session, report_info); + msg = mcps802154_region_event_alloc_skb(local->llhw, &local->region, + FIRA_CALL_SESSION_NOTIFICATION, + session->event_portid, + NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return; + + if (nla_put_u32(msg, FIRA_CALL_ATTR_SESSION_ID, session->id)) + goto nla_put_failure; + if (nla_put_u32(msg, FIRA_CALL_ATTR_SEQUENCE_NUMBER, + session->sequence_number)) + goto nla_put_failure; + if (fira_session_report_ranging_data(session, report_info, + local->llhw->dtu_freq_hz, + local->llhw->dtu_rctu, msg)) + goto nla_put_failure; + if (fira_session_report_ranging_diagnostics(session, report_info, msg)) + goto nla_put_failure; + session->sequence_number++; + session->data_payload.sent = false; + + skb_queue_tail(&local->report_queue, msg); + schedule_work(&local->report_work); + return; + +nla_put_failure: + kfree_skb(msg); +} diff --git a/mac/fira_session.h b/mac/fira_session.h new file mode 100644 index 0000000..d4e9252 --- /dev/null +++ b/mac/fira_session.h @@ -0,0 +1,683 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_FIRA_SESSION_H +#define NET_MCPS802154_FIRA_SESSION_H + +#include "fira_session_fsm.h" +#include "fira_region.h" +#include "fira_sts.h" +#include "fira_crypto.h" +#include "fira_round_hopping_crypto_impl.h" + +/** + * enum fira_controlee_state - State of controlee. + * @FIRA_CONTROLEE_STATE_PENDING_RUN: The controlee will be set to running state + * at the end of round. + * @FIRA_CONTROLEE_STATE_RUNNING: The controlee is running. + * @FIRA_CONTROLEE_STATE_PENDING_STOP: The controlee will be set to stopping + * state at the end of round. + * @FIRA_CONTROLEE_STATE_STOPPING: The controlee is stopping. + * @FIRA_CONTROLEE_STATE_PENDING_DEL: The controlee will be set to deleting + * state at the end of round. + * @FIRA_CONTROLEE_STATE_DELETING: The controlee is being deleted. + */ +enum fira_controlee_state { + FIRA_CONTROLEE_STATE_PENDING_RUN, + FIRA_CONTROLEE_STATE_RUNNING, + FIRA_CONTROLEE_STATE_PENDING_STOP, + FIRA_CONTROLEE_STATE_STOPPING, + FIRA_CONTROLEE_STATE_PENDING_DEL, + FIRA_CONTROLEE_STATE_DELETING, +}; + +/** + * struct fira_controlee - Represent a controlee. + */ +struct fira_controlee { + /** + * @sub_session_id: Sub-session ID for the controlee device. + */ + __u32 sub_session_id; + /** + * @short_addr: Short address of the controlee. + */ + __le16 short_addr; + /** + * @sub_session_key_len: Length of the sub-session key used by + * the controlee. + */ + __u8 sub_session_key_len; + /** + * @sub_session_key: Sub-session key used by the controlee. + */ + u8 sub_session_key[FIRA_KEY_SIZE_MAX]; + /** + * @sub_session: Is the controlee using a sub-session. + */ + bool sub_session; + /** + * @state: Current state of the controlee. + */ + enum fira_controlee_state state; + /** + * @range_data_ntf_status: range_data_ntf status of the controlee. + */ + enum fira_range_data_ntf_status range_data_ntf_status; + /* + * @crypto: Crypto related variables in the sub-session. Only valid if the current device + * is a controller/initiator and the sts_config is FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY. + */ + struct fira_crypto *crypto; + /** + * @entry: Entry in list of controlees. + */ + struct list_head entry; +}; + +struct fira_measurement_sequence_step { + enum fira_measurement_type type; + u8 n_measurements; + s8 rx_ant_set_nonranging; + s8 rx_ant_sets_ranging[2]; + s8 tx_ant_set_nonranging; + s8 tx_ant_set_ranging; +}; + +struct fira_measurement_sequence { + struct fira_measurement_sequence_step + steps[FIRA_MEASUREMENT_SEQUENCE_STEP_MAX]; + size_t n_steps; +}; + +struct fira_session_params { + /* Main parameters. */ + enum fira_device_type device_type; + enum fira_ranging_round_usage ranging_round_usage; + enum fira_multi_node_mode multi_node_mode; + __le16 short_addr; + __le16 controller_short_addr; + /* Timings parameters. */ + int initiation_time_ms; + int slot_duration_dtu; + int block_duration_dtu; + int round_duration_slots; + /* Behaviour parameters. */ + u32 block_stride_len; + u32 max_number_of_measurements; + u32 max_rr_retry; + bool round_hopping; + u8 priority; + bool result_report_phase; + /* Radio. */ + int channel_number; + int preamble_code_index; + enum fira_rframe_config rframe_config; + enum fira_preambule_duration preamble_duration; + enum fira_sfd_id sfd_id; + enum fira_sts_segments number_of_sts_segments; + enum fira_psdu_data_rate psdu_data_rate; + enum fira_mac_fcs_type mac_fcs_type; + enum fira_prf_mode prf_mode; + enum fira_phr_data_rate phr_data_rate; + /* STS and crypto. */ + enum fira_sts_mode sts_config; + u8 vupper64[FIRA_VUPPER64_SIZE]; + u8 session_key_len; + u8 session_key[FIRA_KEY_SIZE_MAX]; + u32 sub_session_id; + u8 sub_session_key_len; + u8 sub_session_key[FIRA_KEY_SIZE_MAX]; + bool key_rotation; + u8 key_rotation_rate; + bool aoa_result_req; + bool report_tof; + bool report_aoa_azimuth; + bool report_aoa_elevation; + bool report_aoa_fom; + enum fira_rssi_report_type report_rssi; + struct fira_measurement_sequence meas_seq; + u32 data_vendor_oui; + u8 data_payload[FIRA_DATA_PAYLOAD_SIZE_MAX]; + u32 data_payload_seq; + int data_payload_len; + bool report_diagnostics; + enum fira_ranging_diagnostics_frame_report_flags diagnostic_report_flags; + /* Misc */ + enum fira_sts_length sts_length; + enum fira_range_data_ntf_config range_data_ntf_config; + u32 range_data_ntf_proximity_near_rctu; + u32 range_data_ntf_proximity_far_rctu; + s16 range_data_ntf_lower_bound_aoa_azimuth_2pi; + s16 range_data_ntf_upper_bound_aoa_azimuth_2pi; + s16 range_data_ntf_lower_bound_aoa_elevation_2pi; + s16 range_data_ntf_upper_bound_aoa_elevation_2pi; +}; + +/** + * struct fira_session - Session information. + */ +struct fira_session { + /** + * @id: Session identifier. + */ + u32 id; + /** + * @sequence_number: Session notification counter. + */ + u32 sequence_number; + /** + * @entry: Entry in list of sessions. + */ + struct list_head entry; + /** + * @state: State of the session. + */ + const struct fira_session_fsm_state *state; + /** + * @params: Session parameters, mostly read only while the session is + * active. + */ + struct fira_session_params params; + /** + * @hrp_uwb_params: HRP UWB parameters, read only while the session is + * active. + */ + struct mcps802154_hrp_uwb_params hrp_uwb_params; + /** + * @event_portid: Port identifier to use for notifications. + */ + u32 event_portid; + /** + * @block_start_valid: True when block_start_dtu is valid. + * It's false on the first access wo initiation delay. + */ + bool block_start_valid; + /** + * @block_start_dtu: Block start timestamp in dtu of the last + * get_access. + */ + u32 block_start_dtu; + /** + * @next_access_timestamp_dtu: Next access timestamp in dtu. + * It's equal to block_start_dtu when the hopping is disabled. + * Otherwise it's beyond the block_start_dtu. + * It's updated after each good or missed ranging round. + */ + u32 next_access_timestamp_dtu; + /** + * @last_access_timestamp_dtu: Last timestamp where the session got + * the access. + * It's used only on session's election, when a negotiation between + * two session fails. + */ + u32 last_access_timestamp_dtu; + /** + * @block_index: Block index used on the last access. + */ + u32 block_index; + /** + * @block_stride_len: Stride length indicates how many ranging blocks + * will be skipped. + * The value is updated at the beginning of an access. + */ + int block_stride_len; + /** + * @round_index: Round index used on the last access. + */ + int round_index; + /** + * @next_round_index: Next round index a announced in measurement + * report message. + */ + int next_round_index; + /** + * @stop_request: Session has been requested to stop. + */ + bool stop_request; + /** + * @stop_inband: Session has been requested to stop by controller. This + * field is only used for controlee sessions. + */ + bool stop_inband; + /** + * @stop_no_response: Session has been requested to stop because ranging + * failed for max_rr_retry consecutive rounds. + */ + bool stop_no_response; + /** + * @n_ranging_round_retry: Number of ranging round failed. + * Counter reset on ranging round success. + */ + int n_ranging_round_retry; + + /** + * @round_hopping_sequence: Round hopping sequence generation context. + */ + struct fira_round_hopping_sequence round_hopping_sequence; + /** + * @controlee: Group of persistent variable(s) used when session + * is a controlee. + */ + struct { + /** + * @synchronised: Whether a controlee session was synchronised. + */ + bool synchronised; + /** + * @block_index_sync: Last block index received. + */ + int block_index_sync; + /** + * @hopping_mode: True when hopping is enabled on last + * measurement frame. + */ + bool hopping_mode; + /** + * @next_round_index_valid: True when the next round index + * is present in measurement report frame. + */ + bool next_round_index_valid; + /** + * @ctlr_range_data_ntf_status: range_data_ntf status of the + * controller. + */ + enum fira_range_data_ntf_status ctlr_range_data_ntf_status; + /* + * @responder_specific_crypto: crypto related variables in the sub-session. + * Only valid if the sts_config is FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY. + */ + struct fira_crypto *responder_specific_crypto; + } controlee; + /** + * @controller: Group of persistent variable(s) used when session + * is a controller. + */ + struct { + /** + * @next_block_index: Next block index built on get access with + * next round index. + * It's only to avoid to rebuild the next round index on next + * access, when this last occur in time as block index will + * match. + */ + u32 next_block_index; + } controller; + /** + * @data_payload: Local context for data_payload feature. + */ + struct { + /** + * @seq: Sequence number of last sent data. + */ + u32 seq; + /** + * @sent: True when data have been send during ranging round. + */ + bool sent; + } data_payload; + /** + * @current_controlees: Current list of controlees. + */ + struct list_head current_controlees; + /** + * @n_current_controlees: Number of elements in the list of current + * controlees. + */ + size_t n_current_controlees; + /** + * @measurements: Measurement configurations which influence diagnostics. + */ + struct { + /** + * @sequence: Copy of the meas_seq parameter on get_access + * event. + */ + struct fira_measurement_sequence sequence; + /** + * @index: Index of the step in sequence array. + */ + int index; + /** + * @n_achieved: Number of measurements done inside a step. + */ + int n_achieved; + /** + * @n_total_achieved: Total number of measurements done. + */ + int n_total_achieved; + /** + * @reset: True when new parameters have to be retrieved. + */ + bool reset; + } measurements; + /** + * @rx_ctx: Custom rx context for all controlees. + */ + void *rx_ctx[FIRA_CONTROLEES_MAX]; + /** + * @crypto: crypto related variables in a session. + */ + struct fira_crypto *crypto; + /** + * @sts: sts related variables. + */ + struct { + /** + * @last_rotation_block_index: index to the last block where the + * rotation occurred. + */ + u32 last_rotation_block_index; + } sts; + /* + * @last_error: last error that occurred during the active session. + */ + int last_error; +}; + +/** + * struct fira_session_demand - Next access information for one FiRa session. + */ +struct fira_session_demand { + /** + * @block_start_dtu: Block start in dtu. + */ + u32 block_start_dtu; + /** + * @timestamp_dtu: Access timestamp in dtu. + */ + u32 timestamp_dtu; + /** + * @max_duration_dtu: Maximum duration for the access. + */ + int max_duration_dtu; + /** + * @add_blocks: Number of block to add. + */ + int add_blocks; + /** + * @round_index: Round index to apply for the access. + */ + int round_index; + /** + * @rx_timeout_dtu: timeout to apply when first frame of the controlee. + */ + int rx_timeout_dtu; +}; + +/** + * struct fira_report_info - Report information for all peer. + */ +struct fira_report_info { + /** + * @ranging_data: Base address of ranging data per peer, or null + * pointer. + */ + struct fira_ranging_info *ranging_data; + /** + * @n_ranging_data: Number of entry in ranging_data above. + */ + size_t n_ranging_data; + /** + * @stopped_controlees: NULL, or short address of all stopped controlees. + */ + const __le16 *stopped_controlees; + /** + * @n_stopped_controlees: Number of controlees stopped in array above. + */ + size_t n_stopped_controlees; + /** + * @diagnostics: Array of diagnostic collected per slots. + */ + const struct fira_diagnostic *diagnostics; + /** + * @slots: Array of information slots. + */ + const struct fira_slot *slots; + /** + * @n_slots: Number of slots above. + */ + size_t n_slots; + /** + * @stopped: True when the session is stopped. + */ + bool stopped; +}; + +/** + * fira_session_new() - Create a new session. + * @local: FiRa context. + * @session_id: Session identifier, must be unique. + * + * Return: The new session or NULL on error. + */ +struct fira_session *fira_session_new(struct fira_local *local, u32 session_id); + +/** + * fira_session_free() - Remove a session. + * @local: FiRa context. + * @session: Session to remove, must be inactive. + */ +void fira_session_free(struct fira_local *local, struct fira_session *session); + +/** + * fira_session_set_controlees() - Set controlees. + * @local: FiRa context. + * @session: Session. + * @controlees: List of controlees. + * @slot_duration_us: Duration of a FiRa slot in us (according to session config). + * @n_controlees: Number of controlees. + * + * Return: 0 or error. + */ +int fira_session_set_controlees(struct fira_local *local, + struct fira_session *session, + struct list_head *controlees, + int slot_duration_us, int n_controlees); + +/** + * fira_session_new_controlee() - Add new controlee. + * @session: Session. + * @controlee: Controlee to add. + * @slot_duration_us: Duration of a FiRa slot in us (according to session + * config). + * @active_session: True if controlee addition should be postponed as ranging + * session is active. + * + * If succeed, function as taken ownership of the structure and removed it + * from the original list. + * + * Return: 0 or error. + */ +int fira_session_new_controlee(struct fira_local *local, + struct fira_session *session, + struct fira_controlee *controlee, + int slot_duration_us, + bool active_session); + +/** + * fira_session_restart_controlees() - Restart controlee and erase pending del. + * @session: FiRa session context. + * + * Return: Number of controlee removed. + */ +void fira_session_restart_controlees(struct fira_session *session); + +/** + * fira_session_del_controlee() - Delete controlee. + * @session: Session. + * @controlee: Controlee to delete. + * @active_session: True if controlee deletion should be postponed as ranging + * session is active. + * + * If succeed, function has removed given controlee from its current list and + * freed it. + * + * Return: 0 or error. + */ +int fira_session_del_controlee(struct fira_session *session, + struct fira_controlee *controlee, + bool active_session); + +/** + * fira_session_get_controlee() - Get controlee info from short address. + * @session: Session. + * @short_addr: Short address of the controlee. + * + * Return: The corresponding controlee object or NULL. + */ +static inline struct fira_controlee * +fira_session_get_controlee(struct fira_session *session, u16 short_addr) +{ + struct fira_controlee *controlee; + list_for_each_entry (controlee, &session->current_controlees, entry) { + if (controlee->short_addr == short_addr) + return controlee; + } + return NULL; +} + +/** + * fira_session_stop_controlees() - Stop controlees. + * @session: Session. + */ +void fira_session_stop_controlees(struct fira_session *session); + +/** + * fira_session_controlees_running_count() - Get the number of running controlees. + * @session: Session. + * + * Return: Number of running controlees. + */ +int fira_session_controlees_running_count(const struct fira_session *session); + +/** + * fira_session_update_controlees() - Update controlee's states. + * @local: FiRa context. + * @session: Session to test. + */ +void fira_session_update_controlees(struct fira_local *local, + struct fira_session *session); + +/** + * fira_session_is_ready() - Test whether a session is ready to be started. + * @local: FiRa context. + * @session: Session to test. + * + * Return: true if the session can be started. + */ +bool fira_session_is_ready(const struct fira_local *local, + const struct fira_session *session); + +/** + * fira_session_get_meas_seq_step() - Get current measurement step. + * @session: Session. + * + * Return: Current Measurement Sequence step for given session. + */ +static inline const struct fira_measurement_sequence_step * +fira_session_get_meas_seq_step(const struct fira_session *session) +{ + const struct fira_measurement_sequence *seq = + &session->measurements.sequence; + + return &seq->steps[session->measurements.index]; +} + +/** + * fira_session_get_rx_ant_set() - Get Rx antenna set for a given message ID. + * @session: Session. + * @message_id: Message ID of FiRa frame. + * + * Return: Adequate antenna set id for given frame and session parameters. + */ +static inline int +fira_session_get_rx_ant_set(const struct fira_session *session, + enum fira_message_id message_id) +{ + const struct fira_session_params *params = &session->params; + const struct fira_measurement_sequence_step *step = + fira_session_get_meas_seq_step(session); + + if (message_id > FIRA_MESSAGE_ID_RFRAME_MAX) + return step->rx_ant_set_nonranging; + + /* TODO: replace this test by device_role == FIRA_DEVICE_ROLE_INITIATOR + * as soon as this feature is supported */ + if (params->device_type == FIRA_DEVICE_TYPE_CONTROLLER) + return step->rx_ant_sets_ranging[0]; + else + switch (step->type) { + case FIRA_MEASUREMENT_TYPE_RANGE: + case FIRA_MEASUREMENT_TYPE_AOA: + case FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH: + case FIRA_MEASUREMENT_TYPE_AOA_ELEVATION: + return step->rx_ant_sets_ranging[0]; + case FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH_ELEVATION: + return step->rx_ant_sets_ranging + [message_id == FIRA_MESSAGE_ID_RANGING_FINAL]; + default: + return -1; + } + return -1; +} + +/** + * fira_session_set_range_data_ntf_status() - Update range_data_ntf_status + * for a given ranging. + * @session: FiRa session. + * @ranging_info: ranging data to be evaluated. + */ +void fira_session_set_range_data_ntf_status( + const struct fira_session *session, + struct fira_ranging_info *ranging_info); + +/** + * fira_session_report() - Report state change and ranging result for a session. + * @local: FiRa context. + * @session: Session to report. + * @report_info: report information to exploit for the reporting. + */ +void fira_session_report(struct fira_local *local, struct fira_session *session, + struct fira_report_info *report_info); + +/** + * fira_session_controlee_active() - Return whether the controlee is currently active. + * @controlee: Controlee. + * + * Return: True if the controlee is currently active. + */ +static inline bool +fira_session_controlee_active(struct fira_controlee *controlee) +{ + switch (controlee->state) { + case FIRA_CONTROLEE_STATE_RUNNING: + case FIRA_CONTROLEE_STATE_PENDING_STOP: + case FIRA_CONTROLEE_STATE_PENDING_DEL: + return true; + default: + return false; + } +} + +#endif /* NET_MCPS802154_FIRA_SESSION_H */ diff --git a/mac/fira_session_fsm.c b/mac/fira_session_fsm.c new file mode 100644 index 0000000..3d1d478 --- /dev/null +++ b/mac/fira_session_fsm.c @@ -0,0 +1,156 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/errno.h> + +#include "fira_session_fsm_init.h" +#include "fira_session_fsm_idle.h" +#include "fira_session_fsm_active.h" +#include "fira_session.h" +#include "fira_access.h" +#include "fira_trace.h" + +void fira_session_fsm_initialise(struct fira_local *local, + struct fira_session *session) +{ + list_add(&session->entry, &local->inactive_sessions); + session->state = &fira_session_fsm_init; + WARN_ON(!session->state->enter); + session->state->enter(local, session); +} + +void fira_session_fsm_uninit(struct fira_local *local, + struct fira_session *session) +{ + if (session->state->leave) + session->state->leave(local, session); + + trace_region_fira_session_fsm_change_state( + session, FIRA_SESSION_STATE_ID_DEINIT); + list_del(&session->entry); +} + +void fira_session_fsm_change_state( + struct fira_local *local, struct fira_session *session, + const struct fira_session_fsm_state *new_state) +{ + if (session->state->leave) + session->state->leave(local, session); + trace_region_fira_session_fsm_change_state(session, new_state->id); + session->state = new_state; + if (session->state->enter) + session->state->enter(local, session); +} + +bool fira_session_is_active(const struct fira_session *session) +{ + return session->state == &fira_session_fsm_active; +} + +enum fira_session_state_id +fira_session_get_state_id(const struct fira_session *session) +{ + return session->state->id; +} + +int fira_session_fsm_check_parameters(const struct fira_session *session, + struct nlattr **attrs) +{ + WARN_ON(!session->state->check_parameters); + return session->state->check_parameters(session, attrs); +} + +void fira_session_fsm_parameters_updated(struct fira_local *local, + struct fira_session *session) +{ + /* The handler is defined for all states. */ + WARN_ON(!session->state->parameters_updated); + session->state->parameters_updated(local, session); +} + +void fira_session_fsm_controlee_list_updated(struct fira_local *local, + struct fira_session *session) +{ + if (session->state->controlee_list_updated) + session->state->controlee_list_updated(local, session); +} + +int fira_session_fsm_start(struct fira_local *local, + struct fira_session *session, + const struct genl_info *info) +{ + if (session->state->start) + return session->state->start(local, session, info); + return -EINVAL; +} + +int fira_session_fsm_stop(struct fira_local *local, + struct fira_session *session) +{ + if (session->state->stop) + return session->state->stop(local, session); + return 0; +} + +int fira_session_fsm_get_demand(const struct fira_local *local, + const struct fira_session *session, + u32 next_timestamp_dtu, int max_duration_dtu, + struct fira_session_demand *session_demand) +{ + /* + * fira_get_demand will not call this function without an + * active session. + */ + WARN_ON(!session->state->get_demand); + return session->state->get_demand(local, session, next_timestamp_dtu, + max_duration_dtu, session_demand); +} + +struct mcps802154_access * +fira_session_fsm_get_access(struct fira_local *local, + struct fira_session *session, + const struct fira_session_demand *session_demand) +{ + /* + * fira_get_access will not call this function without an + * active session. + */ + WARN_ON(!session->state->get_access); + return session->state->get_access(local, session, session_demand); +} + +void fira_session_fsm_access_done(struct fira_local *local, + struct fira_session *session, bool error) +{ + WARN_ON(!session->state->access_done); + return session->state->access_done(local, session, error); +} + +void fira_session_fsm_check_missed_ranging(struct fira_local *local, + struct fira_session *session, + u32 timestamp_dtu) +{ + WARN_ON(!session->state->check_missed_ranging); + return session->state->check_missed_ranging(local, session, + timestamp_dtu); +} diff --git a/mac/fira_session_fsm.h b/mac/fira_session_fsm.h new file mode 100644 index 0000000..9d1b622 --- /dev/null +++ b/mac/fira_session_fsm.h @@ -0,0 +1,241 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_FIRA_SESSION_FSM_H +#define NET_MCPS802154_FIRA_SESSION_FSM_H + +#include <linux/ieee802154.h> + +#include "fira_access.h" + +/* Forward declaration. */ +struct fira_local; +struct fira_session; +struct fira_session_demand; + +/** + * enum fira_session_state_id - State of the FiRa session. + * @FIRA_SESSION_STATE_ID_INIT: + * Initial state, session is not ready yet. + * @FIRA_SESSION_STATE_ID_DEINIT: + * Session does not exist. + * @FIRA_SESSION_STATE_ID_ACTIVE: + * Session is currently active. + * @FIRA_SESSION_STATE_ID_IDLE: + * Session is ready to start, but not currently active. + */ +enum fira_session_state_id { + FIRA_SESSION_STATE_ID_INIT, + FIRA_SESSION_STATE_ID_DEINIT, + FIRA_SESSION_STATE_ID_ACTIVE, + FIRA_SESSION_STATE_ID_IDLE, +}; + +/** + * struct fira_session_fsm_state - FiRa session FSM state. + * + * This structure contains the callbacks which are called on an event to handle + * the transition from the current state. + */ +struct fira_session_fsm_state { + /** @id: Name of state. */ + enum fira_session_state_id id; + /** @enter: Run when the state is entered. */ + void (*enter)(struct fira_local *local, struct fira_session *session); + /** @leave: Run when the state is left. */ + void (*leave)(struct fira_local *local, struct fira_session *session); + /** @check_parameters: Handle a check parameters. */ + int (*check_parameters)(const struct fira_session *session, + struct nlattr **attrs); + /** @parameters_updated: Handle parameters updated event. */ + void (*parameters_updated)(struct fira_local *local, + struct fira_session *session); + /** @controlee_list_updated: Handle controlee list updated event. */ + void (*controlee_list_updated)(struct fira_local *local, + struct fira_session *session); + /** @start: Handle start. */ + int (*start)(struct fira_local *local, struct fira_session *session, + const struct genl_info *info); + /** @stop: Handle stop. */ + int (*stop)(struct fira_local *local, struct fira_session *session); + /** @get_demand: Handle the get demand. */ + int (*get_demand)(const struct fira_local *local, + const struct fira_session *session, + u32 next_timestamp_dtu, int max_duration_dtu, + struct fira_session_demand *session_demand); + /** @get_access: Handle the get access. */ + struct mcps802154_access *(*get_access)( + struct fira_local *local, struct fira_session *session, + const struct fira_session_demand *session_demand); + /** @access_done: Handle end of access. */ + void (*access_done)(struct fira_local *local, + struct fira_session *session, bool error); + /** @check_missed_ranging: Handle the check of missed ranging. */ + void (*check_missed_ranging)(struct fira_local *local, + struct fira_session *session, + u32 timestamp_dtu); +}; + +/** + * fira_session_fsm_change_state() - Change the state of the FSM. + * @local: FiRa context. + * @session: Session context. + * @new_state: New to state to use in the FSM. + * + * This function shall be called only by fira_session_fsm files. + */ +void fira_session_fsm_change_state( + struct fira_local *local, struct fira_session *session, + const struct fira_session_fsm_state *new_state); + +/** + * fira_session_is_active() - Return the active status of the session. + * @session: Session context. + * + * Return: True is the session is active, false otherwise. + */ +bool fira_session_is_active(const struct fira_session *session); + +/** + * fira_session_fsm_initialise() - Initialize the FSM. + * @local: FiRa context. + * @session: Session context. + */ +void fira_session_fsm_initialise(struct fira_local *local, + struct fira_session *session); + +/** + * fira_session_fsm_uninit() - Uninitialise the FSM. + * @local: FiRa context. + * @session: Session context. + */ +void fira_session_fsm_uninit(struct fira_local *local, + struct fira_session *session); + +/** + * fira_session_get_state_id() - Get current state id (for reporting). + * @session: Session context. + * + * Return: State id value. + */ +enum fira_session_state_id +fira_session_get_state_id(const struct fira_session *session); + +/** + * fira_session_fsm_check_parameters() - Check parameters change ask by upper + * layer. + * @session: Session context. + * @attrs: Netlink attributs. + * + * Return: 0 on success, errno when change are refused. + */ +int fira_session_fsm_check_parameters(const struct fira_session *session, + struct nlattr **attrs); + +/** + * fira_session_fsm_parameters_updated() - Parameters updated by upper layer. + * @local: FiRa context. + * @session: Session context. + */ +void fira_session_fsm_parameters_updated(struct fira_local *local, + struct fira_session *session); + +/** + * fira_session_fsm_controlee_list_updated() - Controlee list updated by upper + * layer. + * @local: FiRa context. + * @session: Session context. + */ +void fira_session_fsm_controlee_list_updated(struct fira_local *local, + struct fira_session *session); + +/** + * fira_session_fsm_start() - Start request from upper layer. + * @local: FiRa context. + * @session: Session context. + * @info: Netlink info used only for the portid. + * + * Return: 0 on success, errno otherwise. + */ +int fira_session_fsm_start(struct fira_local *local, + struct fira_session *session, + const struct genl_info *info); + +/** + * fira_session_fsm_stop() - Stop request from upper layer. + * @local: FiRa context. + * @session: Session context. + * + * Return: 0 on success, errno otherwise. + */ +int fira_session_fsm_stop(struct fira_local *local, + struct fira_session *session); + +/** + * fira_session_fsm_get_demand() - Request the next ranging round of the session. + * @local: FiRa context. + * @session: Session context. + * @next_timestamp_dtu: Timestamp to start a demand. + * @max_duration_dtu: Max duration obligation to be consider by the session. + * @session_demand: Wish of the session when the return value is 1. + * + * Return: 1 for a session demand otherwise 0 for no demand. + */ +int fira_session_fsm_get_demand(const struct fira_local *local, + const struct fira_session *session, + u32 next_timestamp_dtu, int max_duration_dtu, + struct fira_session_demand *session_demand); + +/** + * fira_session_fsm_get_access() - Get access to process. + * @local: FiRa context. + * @session: Session context. + * @session_demand: Next access built by the get_demand. + * + * Return: The access for fproc, or NULL pointer. + */ +struct mcps802154_access * +fira_session_fsm_get_access(struct fira_local *local, + struct fira_session *session, + const struct fira_session_demand *session_demand); + +/** + * fira_session_fsm_access_done() - End of the access to report. + * @local: FiRa context. + * @session: Session context. + * @error: True when an error happen. + */ +void fira_session_fsm_access_done(struct fira_local *local, + struct fira_session *session, bool error); + +/** + * fira_session_fsm_check_missed_ranging() - Report a missed ranging if exist. + * @local: FiRa context. + * @session: Session context. + * @timestamp_dtu: Timestamp dtu where no fallback is possible. + */ +void fira_session_fsm_check_missed_ranging(struct fira_local *local, + struct fira_session *session, + u32 timestamp_dtu); + +#endif /* NET_MCPS802154_FIRA_SESSION_FSM_H */ diff --git a/mac/fira_session_fsm_active.c b/mac/fira_session_fsm_active.c new file mode 100644 index 0000000..75ebc7c --- /dev/null +++ b/mac/fira_session_fsm_active.c @@ -0,0 +1,988 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <net/mcps802154_frame.h> +#include <net/fira_region_nl.h> +#include <linux/errno.h> +#include <linux/math64.h> + +#include "fira_round_hopping_sequence.h" +#include "fira_session_fsm_init.h" +#include "fira_session_fsm_idle.h" +#include "fira_session_fsm_active.h" +#include "fira_session.h" +#include "fira_trace.h" +#include "warn_return.h" + +/** + * list_move_to_active() - Move from inactive list to active list. + * @local: FiRa context. + * @session: Session context. + */ +static void list_move_to_active(struct fira_local *local, + struct fira_session *session) +{ + struct list_head *position = &local->active_sessions; + struct fira_session *tmp; + + /* + * Search the position to maintain a list sorted from highest to + * lowest priority. And for the same priority keep the call + * order (moved to the tail). + * Highest value of priority is the highest priority. + * Range of priority is between: 0 to FIRA_PRIORITY_MAX. + */ + list_for_each_entry (tmp, &local->active_sessions, entry) { + if (session->params.priority <= tmp->params.priority) + position = &tmp->entry; + else + break; + } + list_move(&session->entry, position); +} + +/** + * get_channel() - Retrieve the channel to applied on the access. + * @local: FiRa context. + * @session: Session context. + * + * Return: The channel. + */ +static const struct mcps802154_channel * +get_channel(struct fira_local *local, const struct fira_session *session) +{ + const struct fira_session_params *params = &session->params; + + if (params->channel_number || params->preamble_code_index) { + const struct mcps802154_channel *channel = + mcps802154_get_current_channel(local->llhw); + + local->channel = *channel; + if (params->channel_number) + local->channel.channel = params->channel_number; + if (params->preamble_code_index) + local->channel.preamble_code = + params->preamble_code_index; + return &local->channel; + } + return NULL; +} + +/** + * get_round_index() - Return the round index for a specific block index. + * @session: Session context. + * @block_index: Block index. + * + * Return: Round index freshly computed or the round index saved. + */ +static int get_round_index(const struct fira_session *session, int block_index) +{ + const struct fira_session_params *params = &session->params; + int expected_block_index; + + switch (params->device_type) { + default: + case FIRA_DEVICE_TYPE_CONTROLLER: + if (!params->round_hopping) + return 0; + /* + * Avoid to rebuild the round_index. + * The condition is true on first get_access too. + */ + if (session->controller.next_block_index == block_index) + return session->next_round_index; + break; + case FIRA_DEVICE_TYPE_CONTROLEE: + if (!session->controlee.hopping_mode) + return 0; + /* + * Return the round index received, only when the block index + * match with expected block index. + */ + expected_block_index = session->controlee.block_index_sync + + session->block_stride_len + 1; + if (expected_block_index == block_index && + session->controlee.next_round_index_valid) + return session->next_round_index; + break; + } + return fira_round_hopping_sequence_get(session, block_index); +} + +/** + * get_rx_margin_duration_dtu() - Build the maximum margin tolerance for Rx. + * @local: FiRa context. + * @session: Session context. + * + * Return: Duration to apply on first and Rx frame of controlee's access. + */ +static int get_rx_margin_duration_dtu(const struct fira_local *local, + const struct fira_session *session) +{ + const struct fira_session_params *params = &session->params; + s64 duration_dtu = (s64)(session->block_stride_len + 1) * + params->block_duration_dtu; + /* + * TODO: Unit test should be able to predic timestamp. + * - Replace 'local->block_duration_rx_margin_ppm by' + * UWB_BLOCK_DURATION_MARGIN_PPM + * - Remove 'local' from args. + */ + return div64_s64(duration_dtu * local->block_duration_rx_margin_ppm, + 1000000); +} + +/** + * get_next_access_timestamp_dtu() - Build the next access timestamp. + * @local: FiRa context. + * @session: Session context. + * + * Return: Timestamp in dtu. + */ +static u32 get_next_access_timestamp_dtu(const struct fira_local *local, + const struct fira_session *session) +{ + const struct fira_session_params *params = &session->params; + int add_blocks = session->block_stride_len + 1; + int next_block_index = session->block_index + add_blocks; + int next_round_index = get_round_index(session, next_block_index); + int round_duration_dtu = + params->round_duration_slots * params->slot_duration_dtu; + u32 next_block_start_dtu = session->block_start_dtu + + add_blocks * params->block_duration_dtu + + next_round_index * round_duration_dtu; + + switch (params->device_type) { + default: + case FIRA_DEVICE_TYPE_CONTROLLER: + return next_block_start_dtu; + case FIRA_DEVICE_TYPE_CONTROLEE: + return next_block_start_dtu - + get_rx_margin_duration_dtu(local, session); + } +} + +/** + * is_controlee_synchronised() - Answer to the question of the synchronisation + * status. + * @local: FiRa context. + * @session: Session context. + * + * Return: True when the controlee session is still synchronized. + */ +static bool is_controlee_synchronised(const struct fira_local *local, + const struct fira_session *session) +{ +#define FIRA_DRIFT_TOLERANCE_PPM 30 + const struct fira_session_params *params = &session->params; + int n_unsync_blocks; + s64 unsync_duration_dtu; + int drift_ppm, rx_margin_ppm; + + if (session->controlee.synchronised) { + n_unsync_blocks = session->block_index - + session->controlee.block_index_sync; + unsync_duration_dtu = + n_unsync_blocks * params->block_duration_dtu; + drift_ppm = div64_s64(unsync_duration_dtu * + FIRA_DRIFT_TOLERANCE_PPM, + 1000000); + rx_margin_ppm = get_rx_margin_duration_dtu(local, session); + + trace_region_fira_is_controlee_synchronised(session, drift_ppm, + rx_margin_ppm); + if (drift_ppm <= rx_margin_ppm) + return true; + } + return false; +} + +/** + * is_stopped() - Is the session stopped? + * @session: Session context. + * + * Return: True when the session is stopped, false otherwise. + */ +static bool is_stopped(struct fira_session *session) +{ + const struct fira_session_params *params = &session->params; + int nb_controlee = fira_session_controlees_running_count(session); + + if (params->max_rr_retry && + session->n_ranging_round_retry >= params->max_rr_retry) + session->stop_no_response = true; + + return (session->stop_request && !nb_controlee) || + session->stop_inband || session->stop_no_response; +} + +/** + * forward_to_next_ranging() - Update the session to forward to next ranging. + * @session: Session context. + * @n_ranging: Number of ranging (forward). + */ +static void forward_to_next_ranging(struct fira_session *session, int n_ranging) +{ + const struct fira_session_params *params = &session->params; + int blocks_per_ranging = session->block_stride_len + 1; + int add_blocks = n_ranging * blocks_per_ranging; + int duration_dtu = add_blocks * params->block_duration_dtu; + + session->block_index += add_blocks; + session->block_start_dtu += duration_dtu; + session->n_ranging_round_retry += n_ranging; +} + +/** + * ranging_round_done() - Update controlee and notify the upper layer and rotate crypto keys. + * @local: FiRa context. + * @session: Session context. + * @report_info: Report information to forward fira_session_report. + */ +static void ranging_round_done(struct fira_local *local, + struct fira_session *session, + struct fira_report_info *report_info) +{ + const struct fira_session_params *params = &session->params; + + session->next_access_timestamp_dtu = + get_next_access_timestamp_dtu(local, session); + report_info->stopped = is_stopped(session); + + switch (params->device_type) { + default: + case FIRA_DEVICE_TYPE_CONTROLLER: + /* Update controlee's states between two ranging round. */ + fira_session_update_controlees(local, session); + fira_sts_rotate_keys(session); + break; + case FIRA_DEVICE_TYPE_CONTROLEE: + /* Did the controlee's access lose the synchronisation? */ + session->controlee.synchronised = + is_controlee_synchronised(local, session); + if (session->controlee.synchronised) + fira_sts_rotate_keys(session); + break; + } + + fira_session_report(local, session, report_info); + + if (report_info->stopped) { + fira_session_fsm_change_state(local, session, + &fira_session_fsm_idle); + } else { + forward_to_next_ranging(session, 1); + } +} + +static void fira_session_fsm_active_enter(struct fira_local *local, + struct fira_session *session) +{ + const struct fira_session_params *params = &session->params; + + session->stop_request = false; + session->stop_inband = false; + session->stop_no_response = false; + session->measurements.n_total_achieved = 0; + session->block_stride_len = params->block_stride_len; + session->controlee.synchronised = false; + session->controlee.hopping_mode = false; + session->controlee.next_round_index_valid = false; + session->controlee.block_index_sync = 0; + session->round_index = 0; + /* + * Initialize to 1 when initiation_time_ms is 0, + * because first add_blocks built will be 0. + */ + session->n_ranging_round_retry = params->initiation_time_ms ? 0 : 1; + + list_move_to_active(local, session); +} + +static void fira_session_fsm_active_leave(struct fira_local *local, + struct fira_session *session) +{ + fira_sts_deinit(session); + list_move(&session->entry, &local->inactive_sessions); + fira_session_restart_controlees(session); +} + +static int +fira_session_fsm_active_check_parameters(const struct fira_session *session, + struct nlattr **attrs) +{ + const struct fira_session_params *params = &session->params; + enum fira_session_param_attrs i; + + for (i = FIRA_SESSION_PARAM_ATTR_UNSPEC + 1; + i <= FIRA_SESSION_PARAM_ATTR_MAX; i++) { + const struct nlattr *attr = attrs[i]; + + if (!attr) + /* Attribute not provided. */ + continue; + + switch (i) { + case FIRA_SESSION_PARAM_ATTR_MEASUREMENT_SEQUENCE: + case FIRA_SESSION_PARAM_ATTR_DATA_PAYLOAD: + case FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_CONFIG: + case FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_PROXIMITY_NEAR_MM: + case FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_PROXIMITY_FAR_MM: + case FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI: + case FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI: + case FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI: + case FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI: /* Allowed for all device type. */ + break; + case FIRA_SESSION_PARAM_ATTR_BLOCK_STRIDE_LENGTH: + /* Allowed only for controller. */ + if (params->device_type == FIRA_DEVICE_TYPE_CONTROLLER) + continue; + return -EBUSY; + default: + return -EBUSY; + } + } + return 0; +} + +static void +fira_session_fsm_active_parameters_updated(struct fira_local *local, + struct fira_session *session) +{ + const struct fira_session_params *params = &session->params; + int i; + + if (session->measurements.reset) { + for (i = 0; i < params->meas_seq.n_steps; i++) { + const struct fira_measurement_sequence_step *step; + + step = ¶ms->meas_seq.steps[i]; + trace_region_fira_session_meas_seq_params(session, step, + i); + } + } +} + +static int fira_session_fsm_active_start(struct fira_local *local, + struct fira_session *session, + const struct genl_info *info) +{ + /* Already started. */ + return 0; +} + +static int fira_session_fsm_active_stop(struct fira_local *local, + struct fira_session *session) +{ + const struct fira_session_params *params = &session->params; + struct fira_report_info report_info = { + .stopped = true, + }; + + switch (params->device_type) { + default: + case FIRA_DEVICE_TYPE_CONTROLLER: + if (local->current_session == NULL) { + session->stop_request = true; + /* + * FIXME/BUG: + * In unit test, the stop_tx_frame_error (or rx), + * stop the current access and trig a broken event. + * Then the TearDown request a session_stop, but + * there is still more than one controlee running. + * + * Normally the controller must do an access to + * announce a stop of all controlees. + * But here, there is a missing mechanism, as: + * - notify_stop is not called, + * - error is only a boolean in access_done. + * And the error boolean is True on -ETIME. + * + * And as the current_session equal to NULL is a + * normal behavior in multi-region. We have a bug. + */ + fira_session_report(local, session, &report_info); + fira_session_fsm_change_state(local, session, + &fira_session_fsm_idle); + } else if (session->n_current_controlees) { + /* + * A ranging round to announced all controlee + * stopped is required. + */ + fira_session_stop_controlees(session); + session->stop_request = true; + } else if (local->current_session != session) { + session->stop_request = true; + fira_session_report(local, session, &report_info); + fira_session_fsm_change_state(local, session, + &fira_session_fsm_idle); + } + break; + case FIRA_DEVICE_TYPE_CONTROLEE: + session->stop_request = true; + if (local->current_session != session) { + fira_session_report(local, session, &report_info); + fira_session_fsm_change_state(local, session, + &fira_session_fsm_idle); + } + break; + } + mcps802154_reschedule(local->llhw); + return 0; +} + +static int +fira_session_fsm_active_get_demand(const struct fira_local *local, + const struct fira_session *session, + u32 next_timestamp_dtu, int max_duration_dtu, + struct fira_session_demand *session_demand) +{ + const struct fira_session_params *params = &session->params; + int round_duration_dtu = + params->round_duration_slots * params->slot_duration_dtu; + u32 block_start_dtu; + u32 timestamp_dtu; + u32 duration_dtu; + int round_index = 0; + u32 block_index; + int add_blocks = 0; + int rx_timeout_dtu = 0; + int slot_count; + + /* First, determine two dates: block_start_dtu and timestamp_dtu. */ + if (!is_before_dtu(session->next_access_timestamp_dtu, + next_timestamp_dtu)) { + /* + * block_start_dtu is set in the future or present. + * It's happen mainly when initiation_time_ms is not zero. + */ + timestamp_dtu = block_start_dtu = session->block_start_dtu; + } else { + /* + * block start is in the past, we have to evaluate the + * new block start dtu. + * It's could be the same with a controlee not synchronized. + * + * Example of time graph of what's could happen: + * + * -------x----------------x----------------x------- + * #x - 1 | Block Index #x | #x + 1 | #x + 2 + * -------x----------------x------x---------x-------> Time + * |<--------------------->| + * Block | | + * start | next_timestamp_dtu + * | + * duration_from_block_start_dtu + * + * In the graph example, one block is missed, but it's could be + * more or less(controlee). + */ + int duration_from_block_start_dtu = + next_timestamp_dtu - session->block_start_dtu; + + if (params->device_type == FIRA_DEVICE_TYPE_CONTROLEE && + !session->controlee.synchronised) { + /* + * With a controlee not synchronized, consider the + * block as missed when there is no more left duration + * in the current block. + * + * block + * start next_timestamp_dtu + * | | + * -------x-----------------x---x------ + * #x - 1 | #x : | #x + 1 + * -------x-----------------x---x-----> Time + * | | + * |<--------------->| + * | + * | + * add_blocks = Time / block_duration + */ + add_blocks = duration_from_block_start_dtu / + params->block_duration_dtu; + } else { + int blocks_per_ranging = session->block_stride_len + 1; + + /* + * With a controller or a controlee synchronized, + * consider a block started as a missed block. + */ + add_blocks = (duration_from_block_start_dtu + + params->block_duration_dtu - 1) / + params->block_duration_dtu; + /* + * Block stride feature announced/received in last + * access. + */ + if (session->block_stride_len) { + int n = add_blocks % blocks_per_ranging; + + /* + * Add more block(s) to reach block stride + * modulo. + */ + if (n) + add_blocks += blocks_per_ranging - n; + } + } + + /* Compute block start dtu. 'add_blocks' can be zero. */ + block_start_dtu = session->block_start_dtu + + add_blocks * params->block_duration_dtu; + /* Determine the access timestamp. */ + if (is_before_dtu(block_start_dtu, next_timestamp_dtu)) + /* + * Only the controlee not synchronized can have its + * next access timestamp_dtu in the future of the + * block start. + * + * block_start_dtu + * | + * -------x-----------------x---------- + * #x - 1 | Block index #x | #x + 1 + * -------x------x----------x----------> Time + * | + * next_timestamp_dtu + */ + timestamp_dtu = next_timestamp_dtu; + else + timestamp_dtu = block_start_dtu; + } + + /* + * As block_start_dtu is updated with new timestamp in the future, + * or still in the past (controlee), then other variables will be + * build to fill the session_demand output. + * + * In other words, locale variables can have a new values which + * represent the next(future) block/access/index/... + * Or keep +/- the same values. + */ + block_index = session->block_index + add_blocks; + switch (params->device_type) { + default: + case FIRA_DEVICE_TYPE_CONTROLLER: + slot_count = fira_session_get_slot_count(session); + round_index = get_round_index(session, block_index); + timestamp_dtu = + block_start_dtu + round_index * round_duration_dtu; + duration_dtu = slot_count * params->slot_duration_dtu; + if (max_duration_dtu && + is_before_dtu(next_timestamp_dtu + max_duration_dtu, + timestamp_dtu + duration_dtu)) + /* No way to start an access. */ + return 0; + break; + case FIRA_DEVICE_TYPE_CONTROLEE: + if (session->controlee.synchronised) { + int margin_less, margin_more; + + /* + * Time graph to illustrate the controlee access + * and its synchronization. + * + * Next Timestamp + * timestamp without margin + * | | + * ----x--------x----x----x-----> Time + * |<---|--->| + * Rx enabled Rx timeout + * @timestamp_dtu + * + * rx_margin is the maximum margin accepted. + */ + round_index = get_round_index(session, block_index); + timestamp_dtu += round_index * round_duration_dtu; + margin_less = margin_more = + get_rx_margin_duration_dtu(local, session); + if (timestamp_dtu - next_timestamp_dtu < margin_less) + /* + * Avoid to build a timestamp_dtu which is in + * the past of next_timestamp_dtu. + */ + margin_less = + timestamp_dtu - next_timestamp_dtu; + timestamp_dtu -= margin_less; + rx_timeout_dtu = margin_less + margin_more; + duration_dtu = round_duration_dtu + margin_less; + if (max_duration_dtu && + is_before_dtu(next_timestamp_dtu + max_duration_dtu, + timestamp_dtu + duration_dtu)) + /* No way to start an access. */ + return 0; + } else { + int unsync_max_duration_dtu = + params->block_duration_dtu + + params->slot_duration_dtu; + + /* + * A controlee not synchronized is allowed to start/end + * anywhere in the block to find the controller. + * But the session continue to work with block duration + * to provide: + * - Regular reporting. + * - Time-sharing in multi-session/multi-region. + * + * Time graph: + * + * unsync_max_duration_dtu + * |<----------------------------->| + * | | + * --+---x-----------------------|-------x------> + * | Block #x | Block #x + 1 + * --+---x-----------------------|---x---x------> Time + * |<------------------------->|<->| + * block duration slot duration + * + * The unsync duration is bigger than the block, to + * listen the medium for one block min. But to avoid + * to be in late on the next access, we must add one + * slot. + */ + if (max_duration_dtu && + is_before_dtu(next_timestamp_dtu + max_duration_dtu, + timestamp_dtu + + params->slot_duration_dtu)) + /* No way to start an access. */ + return 0; + else if (!max_duration_dtu || + is_before_dtu(timestamp_dtu + + unsync_max_duration_dtu, + next_timestamp_dtu + + max_duration_dtu)) + /* Maximum access granted. */ + duration_dtu = unsync_max_duration_dtu; + else + /* Adjusted access duration. */ + duration_dtu = next_timestamp_dtu + + max_duration_dtu - timestamp_dtu; + + /* + * 'rx_timeout_dtu' is set to allow the reception + * of the control frame close to the end of the + * access, and so be synchronized for next block. + * + * But if the control message is received + * at the end of access, the other frames + * will be dropped to respect the duration_dtu. + * See: rx control frame. + */ + rx_timeout_dtu = + duration_dtu - params->slot_duration_dtu; + } + break; + } + + /* + * Update the session demand (output): + * - rx_timeout_dtu: Used only by the controlee. + * + * On the get_access, the session_demand will be applied + * to the session. Otherwise the session_demand is dropped. + * + * In a way, session_demand represent the next access. + */ + *session_demand = (struct fira_session_demand){ + .block_start_dtu = block_start_dtu, + .timestamp_dtu = timestamp_dtu, + .max_duration_dtu = duration_dtu, + .add_blocks = add_blocks, + .rx_timeout_dtu = rx_timeout_dtu, + .round_index = round_index, + }; + trace_region_fira_session_fsm_active_get_demand_return(local, session, + session_demand); + return 1; +} + +static struct mcps802154_access * +fira_session_fsm_active_get_access(struct fira_local *local, + struct fira_session *session, + const struct fira_session_demand *fsd) +{ + const struct fira_session_params *params = &session->params; + const struct mcps802154_hrp_uwb_params *hrp = &session->hrp_uwb_params; + struct mcps802154_access *access = &local->access; + int blocks_per_ranging; + + /* + * , , + * (\____/) Important: + * (_oo_) + * (O) It's almost forbidden to update session + * __||__ \) content for a controlee. + * []/______\[] / + * / \______/ \/ Because, the session can change on control + * / /__\ frame reception (static STS only). + * (\ /____\ + */ + local->current_session = session; + + /* + * Update common access fields for controlee and controller. + * hrp must stay const, see 'Important' above. + */ + access->method = MCPS802154_ACCESS_METHOD_MULTI; + access->frames = local->frames; + access->n_frames = 0; + access->channel = get_channel(local, session); + access->hrp_uwb_params = hrp; + + /* + * For the ranging round failure counter, consider these rounds as + * failed. And reset the counter in the access_done if success. + */ + blocks_per_ranging = session->block_stride_len + 1; + session->n_ranging_round_retry += fsd->add_blocks / blocks_per_ranging; + + /* Continue to 'device type' access. */ + if (params->device_type == FIRA_DEVICE_TYPE_CONTROLLER) + return fira_get_access_controller(local, fsd); + return fira_get_access_controlee(local, fsd); +} + +static void fira_session_fsm_active_access_done(struct fira_local *local, + struct fira_session *session, + bool error) +{ + const struct fira_session_params *params = &session->params; + const struct fira_measurement_sequence_step *step; + struct fira_report_info report_info = { + .ranging_data = local->ranging_info, + .n_ranging_data = local->n_ranging_info, + .stopped_controlees = local->stopped_controlees, + .n_stopped_controlees = local->n_stopped_controlees, + .diagnostics = local->diagnostics, + .slots = local->slots, + .n_slots = local->access.n_frames, + }; + struct fira_ranging_info *ri; + int i; + + /* Update local. */ + local->current_session = NULL; + + if (error) { + /* + * FIXME: + * Why corrupt all status, the last slot_index is not + * enough? + * TODO: Proposal: + * - Set INTERNAL_ERROR on status during the get_access. + * - Update status on tx_return/rx_frame. + * - Update testu which expect the wrong status. + */ + for (i = 0; i < local->n_ranging_info; i++) { + ri = &local->ranging_info[i]; + ri->status = FIRA_STATUS_RANGING_INTERNAL_ERROR; + } + } else { + for (i = 0; i < local->n_ranging_info; i++) { + ri = &local->ranging_info[i]; + if (ri->status != FIRA_STATUS_RANGING_SUCCESS) + break; + } + /* Reset ranging round failure counter. */ + if (i == local->n_ranging_info) + session->n_ranging_round_retry = 0; + } + + session->measurements.n_achieved++; + session->measurements.n_total_achieved++; + step = fira_session_get_meas_seq_step(session); + if (session->measurements.reset) { + /* Copy new measurement sequence. */ + session->measurements.sequence = params->meas_seq; + session->measurements.index = 0; + session->measurements.n_achieved = 0; + session->measurements.reset = false; + } else if (session->measurements.n_achieved >= step->n_measurements) { + struct fira_measurement_sequence *seq = + &session->measurements.sequence; + + session->measurements.n_achieved = 0; + session->measurements.index++; + session->measurements.index %= seq->n_steps; + } + /* Check max number of measurements. */ + if (params->max_number_of_measurements && + session->measurements.n_total_achieved >= + params->max_number_of_measurements) { + session->stop_request = true; + } + + ranging_round_done(local, session, &report_info); +} + +static void +fira_session_fsm_active_check_missed_ranging(struct fira_local *local, + struct fira_session *session, + u32 timestamp_dtu) +{ + const struct fira_session_params *params = &session->params; + int next_block_start_dtu = + session->block_start_dtu + params->block_duration_dtu; + bool is_missed_ranging_round = false; + + /* + * Example of possible timings (without hopping): + * + * check(timestamp_dtu) + * Ok Miss Miss | + * Session: [--] [--] [--] | [--] + * ------x---------x---------------x--------> Time + * | | + * block_start_dtu next_access_timestamp_dtu + * + * Tips: + * - 'session->block_start_dtu' is the block start of the last access. + * - 'session->next_access_timestamp_dtu' value can be: + * - Next block start when hopping is disabled. + * - Beyond the next block start when hopping is enabled. + * - When the session haven't been check since a long time, + * many blocks could be missed. + */ + + /* First, determine if there is missed ranging round. */ + if (params->device_type == FIRA_DEVICE_TYPE_CONTROLEE && + !session->controlee.synchronised) { + /* Consider the block as missed when next block is reached. */ + if (!is_before_dtu(timestamp_dtu, next_block_start_dtu)) + is_missed_ranging_round = true; + } else if (is_before_dtu(session->next_access_timestamp_dtu, + timestamp_dtu)) { + /* A late is not accepted here. */ + is_missed_ranging_round = true; + } + + /* Compute the number of missed ranging. */ + if (is_missed_ranging_round) { + int blocks_per_ranging = session->block_stride_len + 1; + int add_blocks = 0; + + /* Drift probably due to multi-session or multi-region. */ + if (is_before_dtu(next_block_start_dtu, timestamp_dtu)) + add_blocks = (timestamp_dtu - next_block_start_dtu) / + params->block_duration_dtu; + if (add_blocks >= blocks_per_ranging) { + int n_ranging_failed = add_blocks / blocks_per_ranging; + + if (params->max_rr_retry && + session->n_ranging_round_retry + n_ranging_failed > + params->max_rr_retry) { + /* + * Avoid to set a block index bigger than the + * max ranging round retry in the report. + */ + n_ranging_failed = + params->max_rr_retry - + session->n_ranging_round_retry; + } + forward_to_next_ranging(session, n_ranging_failed); + } + } + + /* Finally, do the missed ranging round report. */ + if (is_missed_ranging_round) { + struct fira_report_info report_info = {}; + __le16 *pend_del; + struct fira_ranging_info *ri; + int j, k; + struct fira_controlee *controlee; + + /* + * \\\||||||//// + * \\ ~ ~ // + * ( @ @ ) + * _________ oOOo-(_)-oOOo________________________________ + * WARN_RETURN_VOID_ON: Because the 'local' information will + * be used until the end of this bloc. + * So this function must not be called during an access, + * to avoid to use a shared memory already used by current + * session. + * ________________Oooo.__________________________________ + * .oooO ( ) + * ( ) ) / + * \ ( (_/ + * \_) + */ + WARN_RETURN_VOID_ON(local->current_session); + /* Build a missed ranging round report. */ + report_info.ranging_data = local->ranging_info; + switch (params->device_type) { + default: + case FIRA_DEVICE_TYPE_CONTROLLER: + pend_del = local->stopped_controlees; + j = k = 0; + list_for_each_entry (controlee, + &session->current_controlees, + entry) { + switch (controlee->state) { + case FIRA_CONTROLEE_STATE_RUNNING: + case FIRA_CONTROLEE_STATE_PENDING_STOP: + case FIRA_CONTROLEE_STATE_PENDING_DEL: + ri = &local->ranging_info[j]; + *ri = (struct fira_ranging_info){ + .short_addr = + controlee->short_addr, + .status = + FIRA_STATUS_RANGING_TX_FAILED, + }; + j++; + break; + default: + pend_del[k++] = controlee->short_addr; + break; + } + } + report_info.stopped_controlees = pend_del; + report_info.n_stopped_controlees = k, + report_info.n_ranging_data = j; + break; + case FIRA_DEVICE_TYPE_CONTROLEE: + ri = &local->ranging_info[0]; + *ri = (struct fira_ranging_info){ + .short_addr = params->controller_short_addr, + .status = FIRA_STATUS_RANGING_RX_TIMEOUT, + }; + report_info.n_ranging_data = 1; + break; + } + ranging_round_done(local, session, &report_info); + } +} + +const struct fira_session_fsm_state fira_session_fsm_active = { + .id = FIRA_SESSION_STATE_ID_ACTIVE, + .enter = fira_session_fsm_active_enter, + .leave = fira_session_fsm_active_leave, + .check_parameters = fira_session_fsm_active_check_parameters, + .parameters_updated = fira_session_fsm_active_parameters_updated, + .start = fira_session_fsm_active_start, + .stop = fira_session_fsm_active_stop, + .get_demand = fira_session_fsm_active_get_demand, + .get_access = fira_session_fsm_active_get_access, + .access_done = fira_session_fsm_active_access_done, + .check_missed_ranging = fira_session_fsm_active_check_missed_ranging, +}; diff --git a/mac/fira_session_fsm_active.h b/mac/fira_session_fsm_active.h new file mode 100644 index 0000000..f6ad54a --- /dev/null +++ b/mac/fira_session_fsm_active.h @@ -0,0 +1,31 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_FIRA_SESSION_FSM_ACTIVE_H +#define NET_MCPS802154_FIRA_SESSION_FSM_ACTIVE_H + +#include "fira_session_fsm.h" + +extern const struct fira_session_fsm_state fira_session_fsm_active; + +#endif /* NET_MCPS802154_FIRA_SESSION_FSM_ACTIVE_H */ diff --git a/mac/fira_session_fsm_idle.c b/mac/fira_session_fsm_idle.c new file mode 100644 index 0000000..fcbcf58 --- /dev/null +++ b/mac/fira_session_fsm_idle.c @@ -0,0 +1,169 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <net/mcps802154_frame.h> + +#include "fira_session_fsm_init.h" +#include "fira_session_fsm_idle.h" +#include "fira_session_fsm_active.h" +#include "fira_session.h" +#include "fira_trace.h" + +static void +fira_session_fsm_idle_parameters_updated(struct fira_local *local, + struct fira_session *session) +{ + const struct fira_session_params *params = &session->params; + + if (session->measurements.reset) { + session->measurements.reset = false; + session->measurements.sequence = params->meas_seq; + } + if (!fira_session_is_ready(local, session)) { + fira_session_fsm_change_state(local, session, + &fira_session_fsm_init); + } +} + +static void +fira_session_fsm_idle_controlee_list_updated(struct fira_local *local, + struct fira_session *session) +{ + if (!fira_session_is_ready(local, session)) { + fira_session_fsm_change_state(local, session, + &fira_session_fsm_init); + } +} + +static int fira_session_fsm_idle_start(struct fira_local *local, + struct fira_session *session, + const struct genl_info *info) +{ + const struct fira_session_params *params = &session->params; + struct mcps802154_hrp_uwb_params *hrp = &session->hrp_uwb_params; + u32 now_dtu; + int r; + int i; + int slot_duration_us; + + trace_region_fira_session_params(session, params); + for (i = 0; i < params->meas_seq.n_steps; i++) { + const struct fira_measurement_sequence_step *step; + + step = ¶ms->meas_seq.steps[i]; + trace_region_fira_session_meas_seq_params(session, step, i); + } + slot_duration_us = (session->params.slot_duration_dtu * 1000) / + (local->llhw->dtu_freq_hz / 1000); + + r = mcps802154_get_current_timestamp_dtu(local->llhw, &now_dtu); + if (r) + return r; + + /* Update session. */ + session->event_portid = info->snd_portid; + session->block_start_valid = false; + session->block_index = 0; + session->round_index = 0; + session->controlee.synchronised = false; + session->last_access_timestamp_dtu = now_dtu; + + r = fira_sts_init(session, slot_duration_us, + mcps802154_get_current_channel(local->llhw)); + if (r) + return r; + + /* Set radio parameters. */ + switch (params->prf_mode) { + case FIRA_PRF_MODE_BPRF: + hrp->prf = MCPS802154_PRF_64; + break; + case FIRA_PRF_MODE_HPRF: + hrp->prf = MCPS802154_PRF_125; + break; + default: + hrp->prf = MCPS802154_PRF_250; + break; + } + hrp->psr = params->preamble_duration == FIRA_PREAMBULE_DURATION_64 ? + MCPS802154_PSR_64 : + MCPS802154_PSR_32; + hrp->sfd_selector = (enum mcps802154_sfd)params->sfd_id; + hrp->phr_hi_rate = params->phr_data_rate == FIRA_PHR_DATA_RATE_6M81; + switch (params->psdu_data_rate) { + default: + case FIRA_PSDU_DATA_RATE_6M81: + hrp->data_rate = MCPS802154_DATA_RATE_6M81; + break; + case FIRA_PSDU_DATA_RATE_7M80: + hrp->data_rate = MCPS802154_DATA_RATE_7M80; + break; + case FIRA_PSDU_DATA_RATE_27M2: + hrp->data_rate = MCPS802154_DATA_RATE_27M2; + break; + case FIRA_PSDU_DATA_RATE_31M2: + hrp->data_rate = MCPS802154_DATA_RATE_31M2; + break; + } + fira_session_fsm_change_state(local, session, &fira_session_fsm_active); + + mcps802154_reschedule(local->llhw); + return 0; +} + +/* not static: shared with init state */ +int fira_session_fsm_idle_check_parameters(const struct fira_session *session, + struct nlattr **attrs) +{ + enum fira_session_param_attrs i; + + for (i = FIRA_SESSION_PARAM_ATTR_UNSPEC + 1; + i <= FIRA_SESSION_PARAM_ATTR_MAX; i++) { + const struct nlattr *attr = attrs[i]; + + if (!attr) + /* Attribute not provided. */ + continue; + + switch (i) { + case FIRA_SESSION_PARAM_ATTR_STS_CONFIG: + if (fira_crypto_get_capabilities() & + (1 << nla_get_u8(attr))) + continue; + else + return -EINVAL; + break; + /* no check on other parameters */ + default: + break; + } + } + return 0; +} + +const struct fira_session_fsm_state fira_session_fsm_idle = { + .id = FIRA_SESSION_STATE_ID_IDLE, + .parameters_updated = fira_session_fsm_idle_parameters_updated, + .controlee_list_updated = fira_session_fsm_idle_controlee_list_updated, + .start = fira_session_fsm_idle_start, + .check_parameters = fira_session_fsm_idle_check_parameters, +}; diff --git a/mac/fira_session_fsm_idle.h b/mac/fira_session_fsm_idle.h new file mode 100644 index 0000000..a8dd0aa --- /dev/null +++ b/mac/fira_session_fsm_idle.h @@ -0,0 +1,31 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_FIRA_SESSION_FSM_IDLE_H +#define NET_MCPS802154_FIRA_SESSION_FSM_IDLE_H + +#include "fira_session_fsm.h" + +extern const struct fira_session_fsm_state fira_session_fsm_idle; + +#endif /* NET_MCPS802154_FIRA_SESSION_FSM_IDLE_H */ diff --git a/mac/fira_session_fsm_init.c b/mac/fira_session_fsm_init.c new file mode 100644 index 0000000..5c12e62 --- /dev/null +++ b/mac/fira_session_fsm_init.c @@ -0,0 +1,77 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <net/mcps802154_frame.h> + +#include "fira_session_fsm_init.h" +#include "fira_session_fsm_idle.h" +#include "fira_session_fsm_active.h" +#include "fira_session.h" + +static void fira_session_fsm_init_enter(struct fira_local *local, + struct fira_session *session) +{ + const struct fira_session_params *params = &session->params; + + session->measurements.sequence = params->meas_seq; + + if (fira_session_is_ready(local, session)) { + fira_session_fsm_change_state(local, session, + &fira_session_fsm_idle); + } +} + +void fira_session_fsm_init_parameters_updated(struct fira_local *local, + struct fira_session *session) +{ + const struct fira_session_params *params = &session->params; + + if (session->measurements.reset) { + session->measurements.reset = false; + session->measurements.sequence = params->meas_seq; + } + if (fira_session_is_ready(local, session)) { + fira_session_fsm_change_state(local, session, + &fira_session_fsm_idle); + } +} + +static void +fira_session_fsm_init_controlee_list_updated(struct fira_local *local, + struct fira_session *session) +{ + if (fira_session_is_ready(local, session)) { + fira_session_fsm_change_state(local, session, + &fira_session_fsm_idle); + } +} + +int fira_session_fsm_idle_check_parameters(const struct fira_session *session, + struct nlattr **attrs); + +const struct fira_session_fsm_state fira_session_fsm_init = { + .id = FIRA_SESSION_STATE_ID_INIT, + .enter = fira_session_fsm_init_enter, + .parameters_updated = fira_session_fsm_init_parameters_updated, + .controlee_list_updated = fira_session_fsm_init_controlee_list_updated, + .check_parameters = fira_session_fsm_idle_check_parameters, +}; diff --git a/mac/fira_session_fsm_init.h b/mac/fira_session_fsm_init.h new file mode 100644 index 0000000..5191a2e --- /dev/null +++ b/mac/fira_session_fsm_init.h @@ -0,0 +1,31 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_FIRA_SESSION_FSM_INIT_H +#define NET_MCPS802154_FIRA_SESSION_FSM_INIT_H + +#include "fira_session_fsm.h" + +extern const struct fira_session_fsm_state fira_session_fsm_init; + +#endif /* NET_MCPS802154_FIRA_SESSION_FSM_INIT_H */ diff --git a/mac/fira_sts.c b/mac/fira_sts.c new file mode 100644 index 0000000..53f106b --- /dev/null +++ b/mac/fira_sts.c @@ -0,0 +1,505 @@ +/* +* This file is part of the UWB stack for linux. +* +* Copyright (c) 2022 Qorvo US, Inc. +* +* This software is provided under the GNU General Public License, version 2 +* (GPLv2), as well as under a Qorvo commercial license. +* +* You may choose to use this software under the terms of the GPLv2 License, +* version 2 ("GPLv2"), as published by the Free Software Foundation. +* You should have received a copy of the GPLv2 along with this program. If +* not, see <http://www.gnu.org/licenses/>. +* not, see <http://www.gnu.org/licenses/>. +* +* This program is distributed under the GPLv2 in the hope that it will be +* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more +* details. +* +* If you cannot meet the requirements of the GPLv2, you may not use this +* software for any purpose without first obtaining a commercial license from +* Qorvo. Please contact Qorvo to inquire about licensing terms. +*/ + +#include "fira_region.h" +#include "fira_session.h" +#include "fira_sts.h" +#include "fira_crypto.h" + +#include <asm/unaligned.h> +#include <linux/errno.h> +#include <linux/string.h> + +#define FIRA_CONCATENATED_PARAMS_SIZE 17 + +/** +* fira_sts_concatenate_params() - Concatenate the session parameters to compute +* the digest. +* @session: FiRa session for which we need to concatenate the parameter. +* @channel: Channel parameter coming from the LLHW. +* @slot_duration_us: duration of a FiRa slot in us (according to session config). +* @concat_params: output buffer. +* @concat_params_size: size of the output buffer. +*/ +static void +fira_sts_concatenate_params(const struct fira_session *session, + const struct mcps802154_channel *channel, + int slot_duration_us, u8 *concat_params, + u8 concat_params_size) +{ + u8 *p; + + p = concat_params; + *p++ = session->params.ranging_round_usage; + *p++ = session->params.sts_config; + *p++ = session->params.multi_node_mode; + *p++ = session->params.channel_number != 0 ? + session->params.channel_number : + channel->channel; + put_unaligned_be16(slot_duration_us, p); + p += sizeof(u16); + *p++ = session->params.mac_fcs_type; + *p++ = session->params.rframe_config; + *p++ = session->params.preamble_code_index != 0 ? + session->params.preamble_code_index : + channel->preamble_code; + *p++ = session->params.sfd_id; + *p++ = session->params.psdu_data_rate; + *p++ = session->params.preamble_duration; + *p++ = 0x03; + put_unaligned_be32(session->id, p); +} + +/** + * fira_sts_get_absolute_slot_index() - Compute the absolute index of the current slot. + * @session: The session requesting absolute slot index. + * @slot_index: index to the current slot in the round. + * + * Return: crypto_sts_index depending on the sts mode. + */ +static u32 fira_sts_get_absolute_slot_index(const struct fira_session *session, + const u32 slot_index) +{ + return (session->block_index * (session->params.block_duration_dtu / + session->params.slot_duration_dtu)) + + (session->round_index * session->params.round_duration_slots) + + slot_index; +} + +/** + * fira_sts_get_crypto_sts_index() - Compute the current crypto STS index. + * @session: The session requesting crypto sts index. + * @crypto: The crypto context containing initial value of phy sts index. + * @slot_index: index to the current slot in the round. + * + * Return: crypto_sts_index depending on the sts mode. + */ +static u32 fira_sts_get_crypto_sts_index(const struct fira_session *session, + const struct fira_crypto *crypto, + const u32 slot_index) +{ + u32 phy_sts_index_init, absolute_slot_index; + + /* + * In static sts, crypto_sts_index is the current slot index in the ranging round. + * For other sts configurations, crypto_sts_index shall be the current phy_sts_index. + */ + if (session->params.sts_config == FIRA_STS_MODE_STATIC) + return slot_index; + + phy_sts_index_init = fira_crypto_get_phy_sts_index_init(crypto); + absolute_slot_index = + fira_sts_get_absolute_slot_index(session, slot_index); + return phy_sts_index_init + absolute_slot_index; +} + +/** + * fira_sts_crypto_init() - Initialize crypto related variables for the current + * session or sub-session. + * @session: The FiRa session the current crypto context belongs to. + * @crypto_params: Parameters to initialize the crypto context. + * @crypto: Crypto related variables. + * + * Return: 0 or error. +*/ +static int fira_sts_crypto_init(struct fira_session *session, + struct fira_crypto_params *crypto_params, + struct fira_crypto **crypto) +{ + int r; + u32 crypto_sts_index = 0; + u32 phy_sts_index_init, block_duration_in_slots; + + r = fira_crypto_context_init(crypto_params, crypto); + if (r) + return r; + + r = fira_crypto_build_phy_sts_index_init(*crypto); + if (r) + goto error_out; + + /* + * crypto_sts_index shall be calculated at the block where the last key rotation occurred, + * not at the exact slot where crypto_context is being initialized. For Static STS, as key + * rotation is not supported, crypto_sts_index shall be 0 (first slot, RR and block). + */ + if (session->params.sts_config != FIRA_STS_MODE_STATIC) { + phy_sts_index_init = + fira_crypto_get_phy_sts_index_init(*crypto); + block_duration_in_slots = session->params.block_duration_dtu / + session->params.slot_duration_dtu; + crypto_sts_index = phy_sts_index_init + + (session->sts.last_rotation_block_index * + block_duration_in_slots); + } + + r = fira_crypto_rotate_elements(*crypto, crypto_sts_index); + if (r) + goto error_out; + + return 0; +error_out: + fira_crypto_context_deinit(*crypto); + return r; +} +/** + * fira_sts_responder_specific_crypto_init() - Initialize sub-session's crypto context + * in case of a controlee device with a sts_config being FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY. + * @session: The FiRa session the current crypto context belongs to. + * @crypto_params: Parameters to initialize the crypto context. + * @concat_params: Subsession's parameters concatenation used to calculate configDigest. + * @concat_params_size: Size of the parameter concatenation buffer. + * + * Return: 0 or error. + */ +static int fira_sts_responder_specific_crypto_init( + struct fira_session *session, struct fira_crypto_params *crypto_params, + u8 *concat_params, u8 concat_params_size) +{ + bool sts_mode_responder_specific_key, device_controlee_responder; + + sts_mode_responder_specific_key = + session->params.sts_config == + FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY; + device_controlee_responder = session->params.device_type == + FIRA_DEVICE_TYPE_CONTROLEE; + + /* Replace by params.device_role == FIRA_DEVICE_ROLE_RESPONDER when roles + * are implemented in FiRa region. Currently, FIRA_DEVICE_TYPE_CONTROLEE == FIRA_DEVICE_ROLE_RESPONDER + * and FIRA_DEVICE_TYPE_CONTROLLER == FIRA_DEVICE_ROLE_INITIATOR. + */ + if (!sts_mode_responder_specific_key || !device_controlee_responder) + return 0; + + /* Reused most part of crypto_params except for sub-session specific parameters. */ + crypto_params->sub_session_id = session->params.sub_session_id; + crypto_params->key = session->params.sub_session_key; + crypto_params->key_len = session->params.sub_session_key_len; + return fira_sts_crypto_init( + session, crypto_params, + &session->controlee.responder_specific_crypto); +} + +int fira_sts_init(struct fira_session *session, int slot_duration_us, + const struct mcps802154_channel *current_channel) +{ + int r = 0; + u8 concat_params[FIRA_CONCATENATED_PARAMS_SIZE]; + struct fira_crypto_params crypto_params; + + if ((session->params.sts_config == + FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY) && + session->params.multi_node_mode == FIRA_MULTI_NODE_MODE_UNICAST) + return -EINVAL; + + fira_sts_concatenate_params(session, current_channel, + slot_duration_us, concat_params, + sizeof(concat_params)); + + crypto_params.session_id = session->id; + crypto_params.sts_config = session->params.sts_config; + crypto_params.concat_params = concat_params; + crypto_params.concat_params_size = sizeof(concat_params); + crypto_params.vupper64 = session->params.vupper64; + crypto_params.key = session->params.session_key; + crypto_params.key_len = session->params.session_key_len; + + r = fira_sts_crypto_init(session, &crypto_params, &session->crypto); + if (r) + return r; + + r = fira_sts_responder_specific_crypto_init( + session, &crypto_params, concat_params, sizeof(concat_params)); + if (r) + goto error_out; + + session->sts.last_rotation_block_index = 0; + if (r) + goto error_out; + + return 0; + +error_out: + fira_crypto_context_deinit(session->crypto); + session->crypto = NULL; + return r; +} + +void fira_sts_deinit(struct fira_session *session) +{ + if (session->crypto) + fira_crypto_context_deinit(session->crypto); + if (session->controlee.responder_specific_crypto) + fira_crypto_context_deinit( + session->controlee.responder_specific_crypto); + + session->crypto = session->controlee.responder_specific_crypto = NULL; +} + +int fira_sts_controlee_init(struct fira_session *session, + struct fira_controlee *controlee, + int slot_duration_us, + const struct mcps802154_channel *current_channel) +{ + int r; + u8 concat_params[FIRA_CONCATENATED_PARAMS_SIZE]; + struct fira_crypto_params crypto_params = { 0 }; + + if (session->params.sts_config != + FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY) { + controlee->crypto = NULL; + return 0; + } + + /* + * Session should not be unicast if STS is configured in + * FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY + */ + if (session->params.multi_node_mode == FIRA_MULTI_NODE_MODE_UNICAST) + return -EINVAL; + + fira_sts_concatenate_params(session, current_channel, + slot_duration_us, concat_params, + sizeof(concat_params)); + + crypto_params.session_id = session->id; + crypto_params.sub_session_id = controlee->sub_session_id; + crypto_params.sts_config = session->params.sts_config; + crypto_params.concat_params = concat_params; + crypto_params.concat_params_size = sizeof(concat_params); + crypto_params.vupper64 = NULL; + crypto_params.key = controlee->sub_session_key; + crypto_params.key_len = controlee->sub_session_key_len; + + r = fira_sts_crypto_init(session, &crypto_params, &controlee->crypto); + if (r) + return r; + + session->sts.last_rotation_block_index = 0; + return 0; +} + +void fira_sts_controlee_deinit(struct fira_controlee *controlee) +{ + if (!controlee->crypto) + fira_crypto_context_deinit(controlee->crypto); +} + +/** + * fira_sts_rotate_elements() - Rotate intermediate keys. + * @session: The FiRa session requesting key rotation. + * @crypto: crypto related variables. + * @n_slots_per_block: Amount of slots contained in a FiRa block. + * @rotation_block_index: Block index where key rotation shall be placed. + */ +static void fira_sts_rotate_elements(const struct fira_session *session, + struct fira_crypto *crypto, + const u32 n_slots_per_block, + const int rotation_block_index) +{ + u32 crypto_sts_index, phy_sts_index_init; + + phy_sts_index_init = fira_crypto_get_phy_sts_index_init(crypto); + /* crypto_sts_index shall be calculated at the block triggering key rotation. */ + crypto_sts_index = + phy_sts_index_init + (rotation_block_index * n_slots_per_block); + fira_crypto_rotate_elements(crypto, crypto_sts_index); +} + +void fira_sts_rotate_keys(struct fira_session *session) +{ + const struct fira_session_params *params = &session->params; + struct fira_controlee *controlee, *tmp_controlee; + u32 next_block = session->block_index + 1; + u32 rotation_period, n_slots_per_block; + bool time_to_rotate, rotation_after_resync; + int rotation_block_index; + + if (params->sts_config == FIRA_STS_MODE_STATIC || !params->key_rotation) + return; + + /* Rotation is triggered after period expires or by resynchronization in the controlee. */ + rotation_period = (1 << params->key_rotation_rate); + time_to_rotate = + (next_block - session->sts.last_rotation_block_index) >= + rotation_period; + rotation_after_resync = next_block < + session->sts.last_rotation_block_index; + if (!time_to_rotate && !rotation_after_resync) + return; + + /* Remove extra blocks following resynchronization. */ + rotation_block_index = next_block - (next_block % rotation_period); + n_slots_per_block = + (params->block_duration_dtu / params->slot_duration_dtu); + fira_sts_rotate_elements(session, session->crypto, n_slots_per_block, + rotation_block_index); + + if (params->device_type == FIRA_DEVICE_TYPE_CONTROLEE && + session->controlee.responder_specific_crypto) + fira_sts_rotate_elements( + session, session->controlee.responder_specific_crypto, + n_slots_per_block, rotation_block_index); + else if (params->device_type == FIRA_DEVICE_TYPE_CONTROLLER && + session->n_current_controlees > 0) + list_for_each_entry_safe (controlee, tmp_controlee, + &session->current_controlees, entry) { + if (!controlee->crypto) + continue; + fira_sts_rotate_elements(session, controlee->crypto, + n_slots_per_block, + rotation_block_index); + } + session->sts.last_rotation_block_index = rotation_block_index; +} + +/** + * fira_sts_get_fira_crypto_context() - Get the corresponding fira crypto context to be used in the current + * slot depending on sts configuration and device type. + * @session: The FiRa session requesting crypto context retrieval. + * @slot: The slot on which desired fira crypto context will be used. + * + * Return: FiRa crypto context to use. + */ +static struct fira_crypto * +fira_sts_get_fira_crypto_context(const struct fira_session *session, + const struct fira_slot *slot) +{ + bool sts_mode_responder_specific_key = + session->params.sts_config == + FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY; + + if (!sts_mode_responder_specific_key || slot->controller_tx) + return session->crypto; + + return session->params.device_type == FIRA_DEVICE_TYPE_CONTROLEE ? + session->controlee.responder_specific_crypto : + slot->controlee->crypto; +} + +int fira_sts_get_sts_params(const struct fira_session *session, + const struct fira_slot *slot, u8 *sts_v, + u32 sts_v_size, u8 *sts_key, u32 sts_key_size) +{ + struct fira_crypto *crypto = + fira_sts_get_fira_crypto_context(session, slot); + + u32 crypto_sts_index = + fira_sts_get_crypto_sts_index(session, crypto, slot->index); + + return fira_crypto_get_sts_params(crypto, crypto_sts_index, sts_v, + sts_v_size, sts_key, sts_key_size); +} + +u32 fira_sts_get_phy_sts_index(const struct fira_session *session, + const struct fira_slot *slot) +{ + struct fira_crypto *crypto = + fira_sts_get_fira_crypto_context(session, slot); + u32 phy_sts_index_init = fira_crypto_get_phy_sts_index_init(crypto); + u32 absolute_slot_index = + fira_sts_get_absolute_slot_index(session, slot->index); + + return phy_sts_index_init + absolute_slot_index; +} + +int fira_sts_convert_phy_sts_idx_to_time_indexes( + const struct fira_session *session, const u32 current_phy_sts_index, + u32 *block_idx, u32 *round_idx, u32 *slot_idx) +{ + u32 remaining_slots, absolute_phy_sts_index, n_slots_per_block, + phy_sts_index_init; + const struct fira_session_params *params = &session->params; + + phy_sts_index_init = + fira_crypto_get_phy_sts_index_init(session->crypto); + + if (current_phy_sts_index < phy_sts_index_init) + return -EINVAL; + n_slots_per_block = + params->block_duration_dtu / params->slot_duration_dtu; + absolute_phy_sts_index = current_phy_sts_index - phy_sts_index_init; + *block_idx = (u32)(absolute_phy_sts_index / n_slots_per_block); + remaining_slots = absolute_phy_sts_index % n_slots_per_block; + *round_idx = (u32)(remaining_slots / params->round_duration_slots); + *slot_idx = remaining_slots % params->round_duration_slots; + + return 0; +} + +int fira_sts_prepare_decrypt(struct fira_session *session, + const struct fira_slot *slot, struct sk_buff *skb) +{ + struct fira_crypto *crypto = + fira_sts_get_fira_crypto_context(session, slot); + + return fira_crypto_prepare_decrypt(crypto, skb); +} + +int fira_sts_encrypt_frame(const struct fira_session *session, + const struct fira_slot *slot, struct sk_buff *skb, + int header_len, __le16 src_short_addr) +{ + struct fira_crypto *crypto = + fira_sts_get_fira_crypto_context(session, slot); + u32 crypto_sts_index = + fira_sts_get_crypto_sts_index(session, crypto, slot->index); + + return fira_crypto_encrypt_frame(crypto, skb, header_len, + src_short_addr, crypto_sts_index); +} + +int fira_sts_decrypt_frame(const struct fira_session *session, + const struct fira_slot *slot, struct sk_buff *skb, + int header_len, __le16 src_short_addr) +{ + struct fira_crypto *crypto = + fira_sts_get_fira_crypto_context(session, slot); + u32 crypto_sts_index = + fira_sts_get_crypto_sts_index(session, crypto, slot->index); + + return fira_crypto_decrypt_frame(crypto, skb, header_len, + src_short_addr, crypto_sts_index); +} + +int fira_sts_decrypt_hie(const struct fira_session *session, + const struct fira_slot *slot, struct sk_buff *skb, + int hie_offset, int hie_len) +{ + struct fira_crypto *crypto = + fira_sts_get_fira_crypto_context(session, slot); + + return fira_crypto_decrypt_hie(crypto, skb, hie_offset, hie_len); + +} + +int fira_sts_encrypt_hie(const struct fira_session *session, + const struct fira_slot *slot, struct sk_buff *skb, + int hie_offset, int hie_len) +{ + struct fira_crypto *crypto = + fira_sts_get_fira_crypto_context(session, slot); + + return fira_crypto_encrypt_hie(crypto, skb, hie_offset, hie_len); +} diff --git a/mac/fira_sts.h b/mac/fira_sts.h new file mode 100644 index 0000000..dbe13e4 --- /dev/null +++ b/mac/fira_sts.h @@ -0,0 +1,193 @@ +/* +* This file is part of the UWB stack for linux. +* +* Copyright (c) 2022 Qorvo US, Inc. +* +* This software is provided under the GNU General Public License, version 2 +* (GPLv2), as well as under a Qorvo commercial license. +* +* You may choose to use this software under the terms of the GPLv2 License, +* version 2 ("GPLv2"), as published by the Free Software Foundation. +* You should have received a copy of the GPLv2 along with this program. If +* not, see <http://www.gnu.org/licenses/>. +* not, see <http://www.gnu.org/licenses/>. +* +* This program is distributed under the GPLv2 in the hope that it will be +* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more +* details. +* +* If you cannot meet the requirements of the GPLv2, you may not use this +* software for any purpose without first obtaining a commercial license from +* Qorvo. Please contact Qorvo to inquire about licensing terms. +*/ + +#ifndef NET_MCPS802154_FIRA_STS_H +#define NET_MCPS802154_FIRA_STS_H + +#include <linux/types.h> +#include <linux/list.h> +#include <linux/ieee802154.h> + +#include <net/fira_region_params.h> +#include <net/mcps802154_frame.h> + +struct fira_session; +struct fira_local; + +/** + * fira_sts_init() - Initialization of STS context for a given + * session. + * @session: The session for which the context shall be initialized. + * @slot_duration_us: Duration of a slot in us. + * @current_channel: Current channel used by the LLHW. + * + * Return: 0 or error. + */ +int fira_sts_init(struct fira_session *session, int slot_duration_us, + const struct mcps802154_channel *current_channel); + +/** + * fira_sts_deinit() - Deinitialize STS context and release its resources. + * @session: The session for which the STS shall be de-initialized. + */ +void fira_sts_deinit(struct fira_session *session); + +/** + * * fira_sts_controlee_init() - Initialize sub-session's STS context for a given controlee. + * @session: The session requesting sub-session context init. + * @controlee: The controlee containing the sub-session context init. + * @slot_duration_us: Duration of a slot in us. + * @current_channel: Current channel used by the llhw. + * + * Return: 0 or error. + */ +int fira_sts_controlee_init(struct fira_session *session, + struct fira_controlee *controlee, + int slot_duration_us, + const struct mcps802154_channel *current_channel); + +/** + * fira_sts_controlee_deinit() - Deinitialize sub-session's STS context for a given controlee. + * @controlee: The controlee containing the sub-session context to deinit. + */ +void fira_sts_controlee_deinit(struct fira_controlee *controlee); + +/** + * fira_sts_rotate_keys() - Rotate intermediate keys if needed. + * @session: The session requesting key rotation. + */ +void fira_sts_rotate_keys(struct fira_session *session); + +/** + * fira_sts_get_sts_params() - To fetch sts_params in order to configure the + * current frame. + * @session: The session for which the sts params are requested. + * @slot: The index of the slot for which the STS shall be computed. + * @sts_v: STS Vector to be filled using the context. + * @sts_v_size: Size of the requested STS vector. + * @sts_key: STS Key to be set using the context. + * @sts_key_size: Size of the requested STS key. + * + * Return: 0 or error. + */ +int fira_sts_get_sts_params(const struct fira_session *session, + const struct fira_slot *slot, u8 *sts_v, + u32 sts_v_size, u8 *sts_key, u32 sts_key_size); + +/** + * fira_sts_get_phy_sts_index() - Compute phy sts index on a specific slot for a given session. + * @session: The session requesting phy sts index. + * @slot: The slot associated with the desired phy sts index. + * + * Return: phy sts index on the current slot. + */ +u32 fira_sts_get_phy_sts_index(const struct fira_session *session, + const struct fira_slot *slot); + +/** + * fira_sts_convert_phy_sts_idx_to_time_indexes() - Convert a given phy + * sts index to the corresponding time related indexes (block, round and slot + * indexes). + * @session: The session to for which the indexes are needed. + * @current_phy_sts_index: phy_sts_index used for time synchronization. + * @block_idx: The block index pointed by the given phy sts index. + * @round_idx: The block index pointed by the given phy sts index. + * @slot_idx: The block index pointed by the given phy sts index. + * + * Return: 0 or error. + */ +int fira_sts_convert_phy_sts_idx_to_time_indexes( + const struct fira_session *session, const u32 current_phy_sts_index, + u32 *block_idx, u32 *round_idx, u32 *slot_idx); + +/** +* fira_sts_prepare_decrypt() - Prepare skb for header decryption and verification. +* @session: The session to for which the indexes are needed. +* @slot: The slot for which the frame should be decrypted. +* @skb: Buffer containing the frame to decrypt. +* +* Return: 0 or error. +*/ +int fira_sts_prepare_decrypt(struct fira_session *session, + const struct fira_slot *slot, struct sk_buff *skb); + +/** +* fira_sts_encrypt_frame() - Encrypt the given FiRa 802154 frame. +* @session: The session to use to encrypt the frame. +* @slot: The slot with the frame to encrypt. +* @skb: Buffer containing the frame to encrypt. +* @header_len: Length of the 802154 header. Can be used to find the start of the +* payload (relative to skb->data). +* @src_short_addr: Source short address. +* @slot_index: The slot index of the frame to encrypt. +* +* Return: 0 or error. +*/ +int fira_sts_encrypt_frame(const struct fira_session *session, + const struct fira_slot *slot, struct sk_buff *skb, + int header_len, __le16 src_short_addr); +/** +* fira_sts_decrypt_frame() - Decrypt the given FiRa 802154 frame. +* @session: The session to use to decrypt the frame. +* @skb: Buffer containing the frame to decrypt. +* @header_len: Length of the 802154 header. Used to find the start of the +* frame payload (relative to skb->data). +* @src_short_addr: Source short address. +* +* Return: 0 or error. +*/ +int fira_sts_decrypt_frame(const struct fira_session *session, + const struct fira_slot *slot, struct sk_buff *skb, + int header_len, __le16 src_short_addr); + +/** +* fira_sts_encrypt_hie() - Encrypt the HIE stored in a FiRa 802154 +* frame. +* @session: The session to attached to the HIE. +* @slot: The slot with the HIE to encrypt. +* @skb: Buffer containing the frame to encrypt. +* @hie_offset: Offset to the start of the HIE (relative to skb->data) to encrypt. +* @hie_len: Length of the HIE to encrypt. +* +* Return: 0 or error. +*/ +int fira_sts_encrypt_hie(const struct fira_session *session, + const struct fira_slot *slot, struct sk_buff *skb, + int hie_offset, int hie_len); + +/** +* fira_sts_decrypt_hie() - Decrypt the HIE stored in a FiRa 802154 frame. +* @session: The session attached to the HIE +* @slot: The slot with the HIE to decrypt. +* @skb: Buffer containing the frame to decrypt. +* @hie_offset: Offset to the start of the HIE (relative to skb->data) to decrypt. +* @hie_len: Length of the HIE to decrypt. +* +* Return: 0 or error. +*/ + +int fira_sts_decrypt_hie(const struct fira_session *session, + const struct fira_slot *slot, struct sk_buff *skb, + int hie_offset, int hie_len); +#endif /* NET_MCPS802154_FIRA_STS_H */ diff --git a/mac/fira_trace.h b/mac/fira_trace.h new file mode 100644 index 0000000..8928fe3 --- /dev/null +++ b/mac/fira_trace.h @@ -0,0 +1,748 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM mcps802154_region_fira + +#if !defined(FIRA_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define FIRA_TRACE_H + +#include <linux/tracepoint.h> +#include "fira_session.h" +#include "net/fira_region_params.h" +#include <net/fira_region_nl.h> + +/* clang-format off */ + +#define FIRA_SESSION_ENTRY __field(u32, session_id) +#define FIRA_SESSION_ASSIGN __entry->session_id = session->id +#define FIRA_SESSION_PR_FMT "session_id=%d" +#define FIRA_SESSION_PR_ARG __entry->session_id + +#define FIRA_DEVICE_TYPE_SYMBOLS \ + { FIRA_DEVICE_TYPE_CONTROLEE, "controlee" }, \ + { FIRA_DEVICE_TYPE_CONTROLLER, "controller" } +TRACE_DEFINE_ENUM(FIRA_DEVICE_TYPE_CONTROLEE); +TRACE_DEFINE_ENUM(FIRA_DEVICE_TYPE_CONTROLLER); + +#define FIRA_DEVICE_ROLE_SYMBOLS \ + { FIRA_DEVICE_ROLE_RESPONDER, "responder" }, \ + { FIRA_DEVICE_ROLE_INITIATOR, "initiator" } +TRACE_DEFINE_ENUM(FIRA_DEVICE_ROLE_RESPONDER); +TRACE_DEFINE_ENUM(FIRA_DEVICE_ROLE_INITIATOR); + +#define FIRA_RANGING_ROUND_SYMBOLS \ + { FIRA_RANGING_ROUND_USAGE_OWR, "OWR" }, \ + { FIRA_RANGING_ROUND_USAGE_SSTWR, "SSTWR" }, \ + { FIRA_RANGING_ROUND_USAGE_DSTWR, "DSTWR" } +TRACE_DEFINE_ENUM(FIRA_RANGING_ROUND_USAGE_OWR); +TRACE_DEFINE_ENUM(FIRA_RANGING_ROUND_USAGE_SSTWR); +TRACE_DEFINE_ENUM(FIRA_RANGING_ROUND_USAGE_DSTWR); + +#define FIRA_MULTI_NODE_MODE_SYMBOLS \ + { FIRA_MULTI_NODE_MODE_UNICAST, "UNICAST" }, \ + { FIRA_MULTI_NODE_MODE_ONE_TO_MANY, "ONE_TO_MANY" }, \ + { FIRA_MULTI_NODE_MODE_MANY_TO_MANY, "MANY_TO_MANY" } +TRACE_DEFINE_ENUM(FIRA_MULTI_NODE_MODE_UNICAST); +TRACE_DEFINE_ENUM(FIRA_MULTI_NODE_MODE_ONE_TO_MANY); +TRACE_DEFINE_ENUM(FIRA_MULTI_NODE_MODE_MANY_TO_MANY); + +#define FIRA_RFRAME_CONFIG_SYMBOLS \ + { FIRA_RFRAME_CONFIG_SP0, "SP0" }, \ + { FIRA_RFRAME_CONFIG_SP1, "SP1" }, \ + { FIRA_RFRAME_CONFIG_SP2, "SP2" }, \ + { FIRA_RFRAME_CONFIG_SP3, "SP3" } +TRACE_DEFINE_ENUM(FIRA_RFRAME_CONFIG_SP0); +TRACE_DEFINE_ENUM(FIRA_RFRAME_CONFIG_SP1); +TRACE_DEFINE_ENUM(FIRA_RFRAME_CONFIG_SP2); +TRACE_DEFINE_ENUM(FIRA_RFRAME_CONFIG_SP3); + +#define FIRA_PREAMBULE_DURATION_SYMBOLS \ + { FIRA_PREAMBULE_DURATION_32, "32" }, \ + { FIRA_PREAMBULE_DURATION_64, "64" } +TRACE_DEFINE_ENUM(FIRA_PREAMBULE_DURATION_32); +TRACE_DEFINE_ENUM(FIRA_PREAMBULE_DURATION_64); + +#define FIRA_STS_SEGMENTS_SYMBOLS \ + { FIRA_STS_SEGMENTS_0, "0" }, \ + { FIRA_STS_SEGMENTS_1, "1" }, \ + { FIRA_STS_SEGMENTS_2, "2" }, \ + { FIRA_STS_SEGMENTS_3, "3" }, \ + { FIRA_STS_SEGMENTS_4, "4" } +TRACE_DEFINE_ENUM(FIRA_STS_SEGMENTS_0); +TRACE_DEFINE_ENUM(FIRA_STS_SEGMENTS_1); +TRACE_DEFINE_ENUM(FIRA_STS_SEGMENTS_2); +TRACE_DEFINE_ENUM(FIRA_STS_SEGMENTS_3); +TRACE_DEFINE_ENUM(FIRA_STS_SEGMENTS_4); + +#define FIRA_PSDU_DATA_RATE_SYMBOLS \ + { FIRA_PSDU_DATA_RATE_6M81, "6M81" }, \ + { FIRA_PSDU_DATA_RATE_7M80, "7M80" }, \ + { FIRA_PSDU_DATA_RATE_27M2, "27M2" }, \ + { FIRA_PSDU_DATA_RATE_31M2, "31M2" } +TRACE_DEFINE_ENUM(FIRA_PSDU_DATA_RATE_6M81); +TRACE_DEFINE_ENUM(FIRA_PSDU_DATA_RATE_7M80); +TRACE_DEFINE_ENUM(FIRA_PSDU_DATA_RATE_27M2); +TRACE_DEFINE_ENUM(FIRA_PSDU_DATA_RATE_31M2); + +#define FIRA_PHR_DATA_RATE_SYMBOLS \ + { FIRA_PHR_DATA_RATE_850K, "850k" }, \ + { FIRA_PHR_DATA_RATE_6M81, "6M81" } +TRACE_DEFINE_ENUM(FIRA_PHR_DATA_RATE_850K); +TRACE_DEFINE_ENUM(FIRA_PHR_DATA_RATE_6M81); + +#define FIRA_MAC_FCS_TYPE_CRC_SYMBOLS \ + { FIRA_MAC_FCS_TYPE_CRC_16, "16" }, \ + { FIRA_MAC_FCS_TYPE_CRC_32, "32" } +TRACE_DEFINE_ENUM(FIRA_MAC_FCS_TYPE_CRC_16); +TRACE_DEFINE_ENUM(FIRA_MAC_FCS_TYPE_CRC_32); + +#define FIRA_STS_MODE_SYMBOLS \ + { FIRA_STS_MODE_STATIC, "static" }, \ + { FIRA_STS_MODE_DYNAMIC, "dynamic" }, \ + { FIRA_STS_MODE_DYNAMIC_INDIVIDUAL_KEY, "dynamic_individual_key" }, \ + { FIRA_STS_MODE_PROVISIONED, "provisioned" }, \ + { FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY, "provisioned_individual_key" } +TRACE_DEFINE_ENUM(FIRA_STS_MODE_STATIC); +TRACE_DEFINE_ENUM(FIRA_STS_MODE_DYNAMIC); +TRACE_DEFINE_ENUM(FIRA_STS_MODE_DYNAMIC_INDIVIDUAL_KEY); +TRACE_DEFINE_ENUM(FIRA_STS_MODE_PROVISIONED); +TRACE_DEFINE_ENUM(FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY); + +#define FIRA_MESSAGE_TYPE \ + { FIRA_MESSAGE_ID_RANGING_INITIATION, "RIM" }, \ + { FIRA_MESSAGE_ID_RANGING_RESPONSE, "RRM" }, \ + { FIRA_MESSAGE_ID_RANGING_FINAL, "RFM" }, \ + { FIRA_MESSAGE_ID_CONTROL, "RCM" }, \ + { FIRA_MESSAGE_ID_MEASUREMENT_REPORT, "MRM" }, \ + { FIRA_MESSAGE_ID_RESULT_REPORT, "RRRM" }, \ + { FIRA_MESSAGE_ID_CONTROL_UPDATE, "CMU" } +TRACE_DEFINE_ENUM(FIRA_MESSAGE_ID_RANGING_INITIATION); +TRACE_DEFINE_ENUM(FIRA_MESSAGE_ID_RANGING_RESPONSE); +TRACE_DEFINE_ENUM(FIRA_MESSAGE_ID_RANGING_FINAL); +TRACE_DEFINE_ENUM(FIRA_MESSAGE_ID_CONTROL); +TRACE_DEFINE_ENUM(FIRA_MESSAGE_ID_MEASUREMENT_REPORT); +TRACE_DEFINE_ENUM(FIRA_MESSAGE_ID_RESULT_REPORT); +TRACE_DEFINE_ENUM(FIRA_MESSAGE_ID_CONTROL_UPDATE); + +#define mcps802154_rx_error_name(name) \ + { \ + MCPS802154_RX_ERROR_##name, #name \ + } +#define MCPS802154_RX_ERROR_SYMBOLS \ + mcps802154_rx_error_name(NONE), \ + mcps802154_rx_error_name(TIMEOUT), \ + mcps802154_rx_error_name(BAD_CKSUM), \ + mcps802154_rx_error_name(UNCORRECTABLE), \ + mcps802154_rx_error_name(FILTERED), \ + mcps802154_rx_error_name(SFD_TIMEOUT), \ + mcps802154_rx_error_name(PHR_DECODE), \ + mcps802154_rx_error_name(OTHER) +TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_NONE); +TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_TIMEOUT); +TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_BAD_CKSUM); +TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_UNCORRECTABLE); +TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_FILTERED); +TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_SFD_TIMEOUT); +TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_PHR_DECODE); +TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_OTHER); + +#define mcps802154_tx_reason_name(name) \ + { \ + MCPS802154_ACCESS_TX_RETURN_REASON_##name, #name \ + } +#define MCPS802154_TX_REASON_SYMBOLS \ + mcps802154_tx_reason_name(CONSUMED), \ + mcps802154_tx_reason_name(FAILURE), \ + mcps802154_tx_reason_name(CANCEL) +TRACE_DEFINE_ENUM(MCPS802154_ACCESS_TX_RETURN_REASON_CONSUMED); +TRACE_DEFINE_ENUM(MCPS802154_ACCESS_TX_RETURN_REASON_FAILURE); +TRACE_DEFINE_ENUM(MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL); + +#define FIRA_MEAS_SEQ_STEP_TYPE \ + { FIRA_MEASUREMENT_TYPE_RANGE, "range" }, \ + { FIRA_MEASUREMENT_TYPE_AOA, "AoA" }, \ + { FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH, "AoA Azimuth" }, \ + { FIRA_MEASUREMENT_TYPE_AOA_ELEVATION, "AoA Elevation" }, \ + { FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH_ELEVATION, \ + "AoA Azimuth/Elevation" } +TRACE_DEFINE_ENUM(FIRA_MEASUREMENT_TYPE_RANGE); +TRACE_DEFINE_ENUM(FIRA_MEASUREMENT_TYPE_AOA); +TRACE_DEFINE_ENUM(FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH); +TRACE_DEFINE_ENUM(FIRA_MEASUREMENT_TYPE_AOA_ELEVATION); +TRACE_DEFINE_ENUM(FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH_ELEVATION); + +#define fira_session_state_id_name(name) \ + { \ + FIRA_SESSION_STATE_ID_##name, #name \ + } +#define FIRA_SESSION_STATE_ID_SYMBOLS \ + fira_session_state_id_name(DEINIT), \ + fira_session_state_id_name(INIT), \ + fira_session_state_id_name(ACTIVE), \ + fira_session_state_id_name(IDLE) +TRACE_DEFINE_ENUM(FIRA_SESSION_STATE_ID_DEINIT); +TRACE_DEFINE_ENUM(FIRA_SESSION_STATE_ID_INIT); +TRACE_DEFINE_ENUM(FIRA_SESSION_STATE_ID_ACTIVE); +TRACE_DEFINE_ENUM(FIRA_SESSION_STATE_ID_IDLE); + + +#define fira_call_name(name) \ + { \ + FIRA_CALL_##name, #name \ + } +#define FIRA_CALL_SYMBOLS \ + fira_call_name(GET_CAPABILITIES), \ + fira_call_name(SESSION_INIT), \ + fira_call_name(SESSION_START), \ + fira_call_name(SESSION_STOP), \ + fira_call_name(SESSION_DEINIT), \ + fira_call_name(SESSION_SET_PARAMS), \ + fira_call_name(NEW_CONTROLEE), \ + fira_call_name(DEL_CONTROLEE), \ + fira_call_name(SESSION_NOTIFICATION), \ + fira_call_name(SESSION_GET_PARAMS), \ + fira_call_name(SESSION_GET_STATE), \ + fira_call_name(SESSION_GET_COUNT), \ + fira_call_name(SET_CONTROLEE), \ + fira_call_name(GET_CONTROLEES) +TRACE_DEFINE_ENUM(FIRA_CALL_GET_CAPABILITIES); +TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_INIT); +TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_START); +TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_STOP); +TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_DEINIT); +TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_SET_PARAMS); +TRACE_DEFINE_ENUM(FIRA_CALL_NEW_CONTROLEE); +TRACE_DEFINE_ENUM(FIRA_CALL_DEL_CONTROLEE); +TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_NOTIFICATION); +TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_GET_PARAMS); +TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_GET_STATE); +TRACE_DEFINE_ENUM(FIRA_CALL_SESSION_GET_COUNT); +TRACE_DEFINE_ENUM(FIRA_CALL_SET_CONTROLEE); +TRACE_DEFINE_ENUM(FIRA_CALL_GET_CONTROLEES); + + +TRACE_EVENT(region_fira_session_params, + TP_PROTO(const struct fira_session *session, + const struct fira_session_params *params), + TP_ARGS(session, params), + TP_STRUCT__entry( + FIRA_SESSION_ENTRY + __field(enum fira_device_type, device_type) + __field(enum fira_ranging_round_usage, ranging_round_usage) + __field(enum fira_multi_node_mode, multi_node_mode) + __field(__le16, controller_short_addr) + __field(int, initiation_time_ms) + __field(int, slot_duration_dtu) + __field(int, block_duration_dtu) + __field(u32, block_stride_len) + __field(u32, max_number_of_measurements) + __field(u32, max_rr_retry) + __field(int, round_duration_slots) + __field(bool, round_hopping) + __field(u8, priority) + __field(int, channel_number) + __field(int, preamble_code_index) + __field(enum fira_rframe_config, rframe_config) + __field(enum fira_preambule_duration, preamble_duration) + __field(enum fira_sfd_id, sfd_id) + __field(enum fira_sts_segments, number_of_sts_segments) + __field(enum fira_psdu_data_rate, psdu_data_rate) + __field(enum fira_mac_fcs_type, mac_fcs_type) + __field(enum fira_sts_mode, sts_config) + __array(u8, vupper64, FIRA_VUPPER64_SIZE) + __field(u32, sub_session_id) + __field(u32, session_key_len) + __field(bool, key_rotation) + __field(int, key_rotation_rate) + __field(bool, aoa_result_req) + __field(bool, report_tof) + __field(bool, report_aoa_azimuth) + __field(bool, report_aoa_elevation) + __field(bool, report_aoa_fom) + __field(bool, report_diagnostics) + ), + TP_fast_assign( + FIRA_SESSION_ASSIGN; + __entry->device_type = params->device_type; + __entry->ranging_round_usage = params->ranging_round_usage; + __entry->multi_node_mode = params->multi_node_mode; + __entry->controller_short_addr = params->controller_short_addr; + __entry->initiation_time_ms = params->initiation_time_ms; + __entry->slot_duration_dtu = params->slot_duration_dtu; + __entry->block_duration_dtu = params->block_duration_dtu; + __entry->block_stride_len = params->block_stride_len; + __entry->max_number_of_measurements = params->max_number_of_measurements; + __entry->max_rr_retry = params->max_rr_retry; + __entry->round_duration_slots = params->round_duration_slots; + __entry->round_hopping = params->round_hopping; + __entry->priority = params->priority; + __entry->channel_number = params->channel_number; + __entry->preamble_code_index = params->preamble_code_index; + __entry->rframe_config = params->rframe_config; + __entry->preamble_duration = params->preamble_duration; + __entry->sfd_id = params->sfd_id; + __entry->number_of_sts_segments = params->number_of_sts_segments; + __entry->psdu_data_rate = params->psdu_data_rate; + __entry->mac_fcs_type = params->mac_fcs_type; + __entry->sts_config = params->sts_config; + memcpy(__entry->vupper64, params->vupper64, sizeof(params->vupper64)); + __entry->sub_session_id = params->sub_session_id; + __entry->session_key_len = params->session_key_len; + __entry->key_rotation = params->key_rotation; + __entry->key_rotation_rate = params->key_rotation_rate; + __entry->aoa_result_req = params->aoa_result_req; + __entry->report_tof = params->report_tof; + __entry->report_aoa_azimuth = params->report_aoa_azimuth; + __entry->report_aoa_elevation = params->report_aoa_elevation; + __entry->report_aoa_fom = params->report_aoa_fom; + __entry->report_diagnostics = params->report_diagnostics; + ), + TP_printk(FIRA_SESSION_PR_FMT " device_type=%s ranging_round_usage=%s multi_node_mode=%s " + "controller_short_addr=0x%x initiation_time_ms=%d slot_duration_dtu=%d " + "block_duration_dtu=%d block_stride_len=%d max_nb_of_measurements=%d " + "max_rr_retry=%d round_duration_slots=%d round_hopping=%d " + "priority=%d channel_number=%d preamble_code_index=%d rframe_config=%s " + "preamble_duration=%s sfd_id=%d number_of_sts_segments=%s psdu_data_rate=%s mac_fcs_type=%s " + "sts_config=%s vupper64=%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x sub_session_id=%d session_key_len=%d " + "key_rotation=%d key_rotation_rate=%d aoa_result_req=%d report_tof=%d report_aoa_azimuth=%d " + "report_aoa_elevation=%d report_aoa_fom=%d diagnostics=%d", + FIRA_SESSION_PR_ARG, + __print_symbolic(__entry->device_type, FIRA_DEVICE_TYPE_SYMBOLS), + __print_symbolic(__entry->ranging_round_usage, FIRA_RANGING_ROUND_SYMBOLS), + __print_symbolic(__entry->multi_node_mode, FIRA_MULTI_NODE_MODE_SYMBOLS), + __entry->controller_short_addr, + __entry->initiation_time_ms, + __entry->slot_duration_dtu, + __entry->block_duration_dtu, + __entry->block_stride_len, + __entry->max_number_of_measurements, + __entry->max_rr_retry, + __entry->round_duration_slots, + __entry->round_hopping, + __entry->priority, + __entry->channel_number, + __entry->preamble_code_index, + __print_symbolic(__entry->rframe_config, FIRA_RFRAME_CONFIG_SYMBOLS), + __print_symbolic(__entry->preamble_duration, FIRA_PREAMBULE_DURATION_SYMBOLS), + __entry->sfd_id, + __print_symbolic(__entry->number_of_sts_segments, FIRA_STS_SEGMENTS_SYMBOLS), + __print_symbolic(__entry->psdu_data_rate, FIRA_PSDU_DATA_RATE_SYMBOLS), + __print_symbolic(__entry->mac_fcs_type, FIRA_MAC_FCS_TYPE_CRC_SYMBOLS), + __print_symbolic(__entry->sts_config, FIRA_STS_MODE_SYMBOLS), + __entry->vupper64[0], __entry->vupper64[1], __entry->vupper64[2], __entry->vupper64[3], + __entry->vupper64[4], __entry->vupper64[5], __entry->vupper64[6], __entry->vupper64[7], + __entry->sub_session_id, + __entry->session_key_len, + __entry->key_rotation, + __entry->key_rotation_rate, + __entry->aoa_result_req, + __entry->report_tof, + __entry->report_aoa_azimuth, + __entry->report_aoa_elevation, + __entry->report_aoa_fom, + __entry->report_diagnostics + ) + ); + +TRACE_EVENT(region_fira_session_meas_seq_params, + TP_PROTO(const struct fira_session *session, + const struct fira_measurement_sequence_step *step, + int index), + TP_ARGS(session, step, index), + TP_STRUCT__entry( + FIRA_SESSION_ENTRY + __field(int, index) + __field(enum fira_measurement_type, type) + __field(u8, n_measurements) + __field(s8, rx_ant_set_nonranging) + __field(s8, rx_ant_sets_ranging_0) + __field(s8, rx_ant_sets_ranging_1) + __field(s8, tx_ant_set_nonranging) + __field(s8, tx_ant_set_ranging) + ), + TP_fast_assign( + FIRA_SESSION_ASSIGN; + __entry->index = index; + __entry->type = step->type; + __entry->n_measurements = step->n_measurements; + __entry->rx_ant_set_nonranging = step->rx_ant_set_nonranging; + __entry->rx_ant_sets_ranging_0 = step->rx_ant_sets_ranging[0]; + __entry->rx_ant_sets_ranging_1 = step->rx_ant_sets_ranging[1]; + __entry->tx_ant_set_nonranging = step->tx_ant_set_nonranging; + __entry->tx_ant_set_ranging = step->tx_ant_set_ranging; + ), + TP_printk(FIRA_SESSION_PR_FMT " index=%d type=%s n_measurements=%d " + "rx_ant_set_nonranging=%d rx_ant_sets_ranging_0=%d " + "rx_ant_sets_ranging_1=%d tx_ant_set_nonranging=%d " + "tx_ant_set_ranging=%d", + FIRA_SESSION_PR_ARG, + __entry->index, + __print_symbolic(__entry->type, FIRA_MEAS_SEQ_STEP_TYPE), + __entry->n_measurements, + __entry->rx_ant_set_nonranging, + __entry->rx_ant_sets_ranging_0, + __entry->rx_ant_sets_ranging_1, + __entry->tx_ant_set_nonranging, + __entry->tx_ant_set_ranging) + ); + +TRACE_EVENT(region_fira_session_control, + TP_PROTO(const struct fira_local *local, + int session_id, enum fira_call call_id), + TP_ARGS(local, session_id, call_id), + TP_STRUCT__entry( + FIRA_SESSION_ENTRY + __field(enum fira_call, call_id) + ), + TP_fast_assign( + __entry->session_id = session_id; + __entry->call_id = call_id; + ), + TP_printk(FIRA_SESSION_PR_FMT " call_id=%s", + FIRA_SESSION_PR_ARG, + __print_symbolic(__entry->call_id, FIRA_CALL_SYMBOLS) + ) + ); + +TRACE_EVENT( + region_fira_session_fsm_active_get_demand_return, + TP_PROTO(const struct fira_local *local, + const struct fira_session *session, + const struct fira_session_demand *fsd), + TP_ARGS(local, session, fsd), + TP_STRUCT__entry( + FIRA_SESSION_ENTRY + __field(u32, block_start_dtu) + __field(u32, timestamp_dtu) + __field(int, max_duration_dtu) + ), + TP_fast_assign( + FIRA_SESSION_ASSIGN; + __entry->block_start_dtu = fsd->block_start_dtu; + __entry->timestamp_dtu = fsd->timestamp_dtu; + __entry->max_duration_dtu = fsd->max_duration_dtu; + ), + TP_printk(FIRA_SESSION_PR_FMT " block_start_dtu=%#x " + "timestamp_dtu=%#x max_duration_dtu=%d", + FIRA_SESSION_PR_ARG, + __entry->block_start_dtu, + __entry->timestamp_dtu, + __entry->max_duration_dtu + ) + ); + +TRACE_EVENT( + region_fira_get_access_controller, + TP_PROTO(const struct fira_local *local, + const struct fira_session *session, + const struct fira_session_demand *fsd), + TP_ARGS(local, session, fsd), + TP_STRUCT__entry( + FIRA_SESSION_ENTRY + __field(int, block_index) + __field(int, add_blocks) + __field(int, round_index) + __field(int, block_stride_len) + __field(u32, block_start_dtu) + __field(u32, timestamp_dtu) + __field(int, max_duration_dtu) + ), + TP_fast_assign( + FIRA_SESSION_ASSIGN; + __entry->block_index = session->block_index; + __entry->add_blocks = fsd->add_blocks; + __entry->round_index = fsd->round_index; + __entry->block_stride_len = session->block_stride_len; + __entry->block_start_dtu = fsd->block_start_dtu; + __entry->timestamp_dtu = fsd->timestamp_dtu; + __entry->max_duration_dtu = fsd->max_duration_dtu; + ), + TP_printk(FIRA_SESSION_PR_FMT " block_index=%d add_blocks=%d " + "round_index=%d block_stride_len=%d block_start_dtu=%#x " + "timestamp_dtu=%#x max_duration_dtu=%d", + FIRA_SESSION_PR_ARG, + __entry->block_index, + __entry->add_blocks, + __entry->round_index, + __entry->block_stride_len, + __entry->block_start_dtu, + __entry->timestamp_dtu, + __entry->max_duration_dtu + ) + ); + +TRACE_EVENT( + region_fira_get_access_controlee, + TP_PROTO(const struct fira_local *local, + const struct fira_session *session, + const struct fira_session_demand *fsd), + TP_ARGS(local, session, fsd), + TP_STRUCT__entry( + FIRA_SESSION_ENTRY + __field(int, block_index) + __field(int, add_blocks) + __field(int, round_index) + __field(u32, block_start_dtu) + __field(u32, timestamp_dtu) + __field(int, max_duration_dtu) + ), + TP_fast_assign( + FIRA_SESSION_ASSIGN; + __entry->block_index = session->block_index; + __entry->add_blocks = fsd->add_blocks; + __entry->round_index = fsd->round_index; + __entry->block_start_dtu = fsd->block_start_dtu; + __entry->timestamp_dtu = fsd->timestamp_dtu; + __entry->max_duration_dtu = fsd->max_duration_dtu; + ), + TP_printk(FIRA_SESSION_PR_FMT " block_index=%d add_blocks=%d " + "round_index=%d block_start_dtu=%#x timestamp_dtu=%#x " + "max_duration_dtu=%d", + FIRA_SESSION_PR_ARG, + __entry->block_index, + __entry->add_blocks, + __entry->round_index, + __entry->block_start_dtu, + __entry->timestamp_dtu, + __entry->max_duration_dtu + ) + ); + +TRACE_EVENT(region_fira_rx_frame, + TP_PROTO(const struct fira_session *session, + enum fira_message_id message_id, + enum mcps802154_rx_error_type error), + TP_ARGS(session, message_id, error), + TP_STRUCT__entry( + FIRA_SESSION_ENTRY + __field(enum fira_message_id, message_id) + __field(enum mcps802154_rx_error_type, error) + ), + TP_fast_assign( + FIRA_SESSION_ASSIGN; + __entry->message_id = message_id; + __entry->error = error; + ), + TP_printk(FIRA_SESSION_PR_FMT " message_id=%s error=%s", + FIRA_SESSION_PR_ARG, + __print_symbolic(__entry->message_id, FIRA_MESSAGE_TYPE), + __print_symbolic(__entry->error, MCPS802154_RX_ERROR_SYMBOLS) + ) + ); + +TRACE_EVENT(region_fira_rx_frame_control, + TP_PROTO(const struct fira_local *local, + const struct fira_session *session, + int left_duration_dtu, int n_slots), + TP_ARGS(local, session, left_duration_dtu, n_slots), + TP_STRUCT__entry( + FIRA_SESSION_ENTRY + __field(u32, block_start_dtu) + __field(int, block_index) + __field(int, round_index) + __field(bool, stop_inband) + __field(int, left_duration_dtu) + __field(int, n_slots) + ), + TP_fast_assign( + FIRA_SESSION_ASSIGN; + __entry->block_start_dtu = session->block_start_dtu; + __entry->block_index = session->block_index; + __entry->round_index = session->round_index; + __entry->stop_inband = session->stop_inband; + __entry->left_duration_dtu = left_duration_dtu; + __entry->n_slots = n_slots; + ), + TP_printk(FIRA_SESSION_PR_FMT " block_start_dtu=%#x block_index=%d " + "round_index=%d stop_inband=%s left_duration_dtu=%d n_slots=%d", + FIRA_SESSION_PR_ARG, + __entry->block_start_dtu, + __entry->block_index, + __entry->round_index, + __entry->stop_inband ? "true": "false", + __entry->left_duration_dtu, + __entry->n_slots + ) + ); + +TRACE_EVENT(region_fira_tx_get_frame, + TP_PROTO(const struct fira_session *session, + enum fira_message_id message_id), + TP_ARGS(session, message_id), + TP_STRUCT__entry( + FIRA_SESSION_ENTRY + __field(enum fira_message_id, message_id) + ), + TP_fast_assign( + FIRA_SESSION_ASSIGN; + __entry->message_id = message_id; + ), + TP_printk(FIRA_SESSION_PR_FMT " message_id=%s", + FIRA_SESSION_PR_ARG, + __print_symbolic(__entry->message_id, FIRA_MESSAGE_TYPE) + ) + ); + +TRACE_EVENT(region_fira_tx_return, + TP_PROTO(const struct fira_session *session, + enum mcps802154_access_tx_return_reason reason), + TP_ARGS(session, reason), + TP_STRUCT__entry( + FIRA_SESSION_ENTRY + __field(enum mcps802154_access_tx_return_reason, reason) + ), + TP_fast_assign( + FIRA_SESSION_ASSIGN; + __entry->reason = reason; + ), + TP_printk(FIRA_SESSION_PR_FMT " reason=%s", + FIRA_SESSION_PR_ARG, + __print_symbolic(__entry->reason, + MCPS802154_TX_REASON_SYMBOLS) + ) + ); + +TRACE_EVENT( + region_fira_session_fsm_change_state, + TP_PROTO(const struct fira_session *session, enum fira_session_state_id new_state_id), + TP_ARGS(session, new_state_id), + TP_STRUCT__entry( + FIRA_SESSION_ENTRY + __field(enum fira_session_state_id, new_state_id) + ), + TP_fast_assign( + FIRA_SESSION_ASSIGN; + __entry->new_state_id = new_state_id; + ), + TP_printk(FIRA_SESSION_PR_FMT " new_state_id=%s", + FIRA_SESSION_PR_ARG, + __print_symbolic(__entry->new_state_id, + FIRA_SESSION_STATE_ID_SYMBOLS) + ) + ); + +TRACE_EVENT( + region_fira_access_done, + TP_PROTO(const struct fira_local *local, + const struct fira_session *session, + int access_duration_dtu, bool error), + TP_ARGS(local, session, access_duration_dtu, error), + TP_STRUCT__entry( + FIRA_SESSION_ENTRY + __field(int, access_duration_dtu) + __field(bool, error) + ), + TP_fast_assign( + FIRA_SESSION_ASSIGN; + __entry->access_duration_dtu = access_duration_dtu; + __entry->error = error; + ), + TP_printk(FIRA_SESSION_PR_FMT " access_duration_dtu=%d error=%s", + FIRA_SESSION_PR_ARG, + __entry->access_duration_dtu, + __entry->error ? "true": "false" + ) + ); + +TRACE_EVENT( + region_fira_is_controlee_synchronised, + TP_PROTO(const struct fira_session *session, + int drift_ppm, int rx_margin_ppm), + TP_ARGS(session, drift_ppm, rx_margin_ppm), + TP_STRUCT__entry( + FIRA_SESSION_ENTRY + __field(int, block_index_sync) + __field(int, drift_ppm) + __field(int, rx_margin_ppm) + ), + TP_fast_assign( + FIRA_SESSION_ASSIGN; + __entry->block_index_sync = session->controlee.block_index_sync; + __entry->drift_ppm = drift_ppm; + __entry->rx_margin_ppm = rx_margin_ppm; + ), + TP_printk(FIRA_SESSION_PR_FMT " block_index_sync=%d drift_ppm=%d rx_margin_ppm=%d", + FIRA_SESSION_PR_ARG, + __entry->block_index_sync, + __entry->drift_ppm, + __entry->rx_margin_ppm + ) + ); + +TRACE_EVENT( + region_fira_session_report, + TP_PROTO(const struct fira_session *session, + const struct fira_report_info *report_info), + TP_ARGS(session, report_info), + TP_STRUCT__entry( + FIRA_SESSION_ENTRY + __field(int, sequence_number) + __field(int, block_index) + __field(int, n_ranging_data) + __field(int, n_stopped_controlees) + __field(int, n_slots) + __field(bool, stopped) + ), + TP_fast_assign( + FIRA_SESSION_ASSIGN; + __entry->sequence_number = session->sequence_number; + __entry->block_index = session->block_index; + __entry->n_ranging_data = report_info->n_ranging_data; + __entry->n_stopped_controlees = report_info->n_stopped_controlees; + __entry->n_slots = report_info->n_slots; + __entry->stopped = report_info->stopped; + ), + TP_printk(FIRA_SESSION_PR_FMT " sequence_number=%d block_index=%d " + "n_ranging_data=%d n_stopped_controlees=%d n_slots=%d stopped=%s", + FIRA_SESSION_PR_ARG, + __entry->sequence_number, + __entry->block_index, + __entry->n_ranging_data, + __entry->n_stopped_controlees, + __entry->n_slots, + __entry->stopped ? "true": "false" + ) + ); + +TRACE_EVENT(fira_nondeferred_not_supported, + TP_PROTO(const struct fira_session *session), + TP_ARGS(session), + TP_STRUCT__entry( + FIRA_SESSION_ENTRY + ), + TP_fast_assign( + FIRA_SESSION_ASSIGN; + ), + TP_printk(FIRA_SESSION_PR_FMT, + FIRA_SESSION_PR_ARG + ) + ); + +#endif /* !FIRA_TRACE_H || TRACE_HEADER_MULTI_READ */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE fira_trace +#include <trace/define_trace.h> diff --git a/mac/fproc.c b/mac/fproc.c new file mode 100644 index 0000000..4e1274c --- /dev/null +++ b/mac/fproc.c @@ -0,0 +1,311 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <linux/module.h> + +#include "mcps802154_i.h" +#include "llhw-ops.h" + +bool mcps802154_fproc_is_non_recoverable_error(struct mcps802154_access *access) +{ + bool rc = false; + if (access->error < 0) { + switch (access->error) { + case -ETIME: + case -EIO: + case -EAGAIN: + rc = false; + break; + default: + pr_err("mcps: error %d is not recoverable", access->error); + rc = true; + break; + } + } + return rc; +} + +void mcps802154_fproc_init(struct mcps802154_local *local) +{ + local->fproc.state = &mcps802154_fproc_stopped; + WARN_ON(!local->fproc.state->enter); + local->fproc.state->enter(local); +} + +void mcps802154_fproc_uninit(struct mcps802154_local *local) +{ + WARN_ON(local->fproc.access); + WARN_ON(local->fproc.tx_skb); + WARN_ON(local->started); + WARN_ON(local->fproc.deferred); +} + +void mcps802154_fproc_change_state( + struct mcps802154_local *local, + const struct mcps802154_fproc_state *new_state) +{ + if (local->fproc.state->leave) + local->fproc.state->leave(local); + local->fproc.state = new_state; + if (local->fproc.state->enter) + local->fproc.state->enter(local); +} + +void mcps802154_fproc_access(struct mcps802154_local *local, + u32 next_timestamp_dtu) +{ + struct mcps802154_access *access; + + if (!local->start_stop_request) { + mcps802154_fproc_stopped_handle(local); + return; + } + + access = mcps802154_ca_get_access(local, next_timestamp_dtu); + if (unlikely(!access)) { + mcps802154_fproc_broken_handle(local); + return; + } + + local->fproc.access = access; + local->fproc.frame_idx = 0; + + switch (access->method) { + case MCPS802154_ACCESS_METHOD_NOTHING: + access->error = mcps802154_fproc_nothing_handle(local, access); + break; + case MCPS802154_ACCESS_METHOD_IDLE: + access->error = mcps802154_fproc_idle_handle(local, access); + break; + case MCPS802154_ACCESS_METHOD_IMMEDIATE_RX: + access->error = mcps802154_fproc_rx_handle(local, access); + break; + case MCPS802154_ACCESS_METHOD_IMMEDIATE_TX: + access->error = mcps802154_fproc_tx_handle(local, access); + break; + case MCPS802154_ACCESS_METHOD_MULTI: + access->error = mcps802154_fproc_multi_handle(local, access); + break; + case MCPS802154_ACCESS_METHOD_VENDOR: + access->error = mcps802154_fproc_vendor_handle(local, access); + break; + default: + access->error = -1; + } + + if (access->error) { + if (mcps802154_fproc_is_non_recoverable_error(access)) { + mcps802154_fproc_access_done(local, true); + mcps802154_fproc_broken_handle(local); + } else { + mcps802154_fproc_access_done(local, false); + mcps802154_fproc_access_now(local); + } + } +} + +void mcps802154_fproc_access_now(struct mcps802154_local *local) +{ + int r = 0; + u32 next_timestamp_dtu = 0; + + if (local->start_stop_request) + r = llhw_get_current_timestamp_dtu(local, &next_timestamp_dtu); + + if (r) + mcps802154_fproc_broken_handle(local); + else + mcps802154_fproc_access(local, next_timestamp_dtu + + local->llhw.anticip_dtu); +} + +void mcps802154_fproc_access_done(struct mcps802154_local *local, bool error) +{ + struct mcps802154_access *access = local->fproc.access; + + if (access->common_ops->access_done) + access->common_ops->access_done(access, error); + local->fproc.access = NULL; +} + +void mcps802154_fproc_access_reset(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + + if (access) { + if (local->fproc.tx_skb) { + WARN_ON(access->method == + MCPS802154_ACCESS_METHOD_VENDOR); + access->ops->tx_return( + access, local->fproc.frame_idx, + local->fproc.tx_skb, + MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL); + local->fproc.tx_skb = NULL; + } + mcps802154_fproc_access_done(local, false); + local->fproc.access = NULL; + } +} + +static void mcps802154_broken_safe(struct mcps802154_local *local) +{ + if (local->fproc.state->broken) + local->fproc.state->broken(local); + else + mcps802154_fproc_broken_handle(local); +} + +static void mcps802154_fproc_call_deferred(struct mcps802154_local *local) +{ + struct mcps802154_region *region = local->fproc.deferred; + + if (region) { + local->fproc.deferred = NULL; + region->ops->deferred(region); + } +} + +void mcps802154_fproc_schedule_change(struct mcps802154_local *local) +{ + local->fproc.state->schedule_change(local); + mcps802154_fproc_call_deferred(local); +} + +void mcps802154_rx_frame(struct mcps802154_llhw *llhw) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + mutex_lock(&local->fsm_lock); + trace_llhw_event_rx_frame(local); + if (local->fproc.state->rx_frame) + local->fproc.state->rx_frame(local); + else + mcps802154_broken_safe(local); + mcps802154_fproc_call_deferred(local); + trace_llhw_event_done(local); + mutex_unlock(&local->fsm_lock); +} +EXPORT_SYMBOL(mcps802154_rx_frame); + +void mcps802154_rx_timeout(struct mcps802154_llhw *llhw) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + mutex_lock(&local->fsm_lock); + trace_llhw_event_rx_timeout(local); + if (local->fproc.state->rx_timeout) + local->fproc.state->rx_timeout(local); + else + mcps802154_broken_safe(local); + mcps802154_fproc_call_deferred(local); + trace_llhw_event_done(local); + mutex_unlock(&local->fsm_lock); +} +EXPORT_SYMBOL(mcps802154_rx_timeout); + +void mcps802154_rx_error(struct mcps802154_llhw *llhw, + enum mcps802154_rx_error_type error) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + mutex_lock(&local->fsm_lock); + trace_llhw_event_rx_error(local, error); + if (local->fproc.state->rx_error) + local->fproc.state->rx_error(local, error); + else + mcps802154_broken_safe(local); + mcps802154_fproc_call_deferred(local); + trace_llhw_event_done(local); + mutex_unlock(&local->fsm_lock); +} +EXPORT_SYMBOL(mcps802154_rx_error); + +void mcps802154_tx_done(struct mcps802154_llhw *llhw) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + mutex_lock(&local->fsm_lock); + trace_llhw_event_tx_done(local); + if (local->fproc.state->tx_done) + local->fproc.state->tx_done(local); + else + mcps802154_broken_safe(local); + mcps802154_fproc_call_deferred(local); + trace_llhw_event_done(local); + mutex_unlock(&local->fsm_lock); +} +EXPORT_SYMBOL(mcps802154_tx_done); + +void mcps802154_tx_too_late(struct mcps802154_llhw *llhw) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + mutex_lock(&local->fsm_lock); + if (local->fproc.state->tx_too_late) + local->fproc.state->tx_too_late(local); + else + mcps802154_broken_safe(local); + trace_llhw_event_done(local); + mutex_unlock(&local->fsm_lock); +} +EXPORT_SYMBOL(mcps802154_tx_too_late); + +void mcps802154_rx_too_late(struct mcps802154_llhw *llhw) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + mutex_lock(&local->fsm_lock); + if (local->fproc.state->rx_too_late) + local->fproc.state->rx_too_late(local); + else + mcps802154_broken_safe(local); + trace_llhw_event_done(local); + mutex_unlock(&local->fsm_lock); +} +EXPORT_SYMBOL(mcps802154_rx_too_late); + +void mcps802154_broken(struct mcps802154_llhw *llhw) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + mutex_lock(&local->fsm_lock); + trace_llhw_event_broken(local); + mcps802154_broken_safe(local); + mcps802154_fproc_call_deferred(local); + trace_llhw_event_done(local); + mutex_unlock(&local->fsm_lock); +} +EXPORT_SYMBOL(mcps802154_broken); + +void mcps802154_timer_expired(struct mcps802154_llhw *llhw) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + mutex_lock(&local->fsm_lock); + trace_llhw_event_timer_expired(local); + if (local->fproc.state->timer_expired) + local->fproc.state->timer_expired(local); + mcps802154_fproc_call_deferred(local); + trace_llhw_event_done(local); + mutex_unlock(&local->fsm_lock); +} +EXPORT_SYMBOL(mcps802154_timer_expired); diff --git a/mac/fproc.h b/mac/fproc.h new file mode 100644 index 0000000..f26f55d --- /dev/null +++ b/mac/fproc.h @@ -0,0 +1,213 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_FPROC_H +#define NET_MCPS802154_FPROC_H + +struct mcps802154_local; + +/** + * struct mcps802154_fproc_state - FProc FSM state. + * + * This structure contains the callbacks which are called on an event to handle + * the transition from the active state. + */ +struct mcps802154_fproc_state { + /** @name: State name. */ + const char *name; + /** @enter: Run when the state is entered. */ + void (*enter)(struct mcps802154_local *local); + /** @leave: Run when the state is left. */ + void (*leave)(struct mcps802154_local *local); + /** @rx_frame: Handle frame reception. */ + void (*rx_frame)(struct mcps802154_local *local); + /** @rx_timeout: Handle reception timeout. */ + void (*rx_timeout)(struct mcps802154_local *local); + void (*rx_too_late)(struct mcps802154_local *local); + /** @rx_error: Handle reception error. */ + void (*rx_error)(struct mcps802154_local *local, + enum mcps802154_rx_error_type error); + /** @tx_done: Handle end of transmission. */ + void (*tx_done)(struct mcps802154_local *local); + void (*tx_too_late)(struct mcps802154_local *local); + /** @broken: Handle unrecoverable error. */ + void (*broken)(struct mcps802154_local *local); + /** @timer_expired: Handle timer expiration, ignored if NULL. */ + void (*timer_expired)(struct mcps802154_local *local); + /** @schedule_change: Handle schedule change. */ + void (*schedule_change)(struct mcps802154_local *local); +}; + +/** struct mcps802154_fproc - FProc private data. */ +struct mcps802154_fproc { + /** @state: Pointer to current state. */ + const struct mcps802154_fproc_state *state; + /** @access: Access being handled. */ + struct mcps802154_access *access; + /** @tx_skb: Buffer for frame being sent. */ + struct sk_buff *tx_skb; + /** @frame_idx: Frame index for multiple frames method. */ + size_t frame_idx; + /** @deferred: Pointer to region context requesting deferred call. */ + struct mcps802154_region *deferred; +}; + +extern const struct mcps802154_fproc_state mcps802154_fproc_stopped; + +/** + * mcps802154_fproc_init() - Initialize FProc. + * @local: MCPS private data. + */ +void mcps802154_fproc_init(struct mcps802154_local *local); + +/** + * mcps802154_fproc_uninit() - Uninitialize FProc. + * @local: MCPS private data. + */ +void mcps802154_fproc_uninit(struct mcps802154_local *local); + +/** + * mcps802154_fproc_change_state() - Change the active state. + * @local: MCPS private data. + * @new_state: State to switch to. + */ +void mcps802154_fproc_change_state( + struct mcps802154_local *local, + const struct mcps802154_fproc_state *new_state); + +/** + * mcps802154_fproc_access() - Get access and handle it. + * @local: MCPS private data. + * @next_timestamp_dtu: Date of next access opportunity. + */ +void mcps802154_fproc_access(struct mcps802154_local *local, + u32 next_timestamp_dtu); + +/** + * mcps802154_fproc_access_now() - Get access for current date, and handle it. + * @local: MCPS private data. + */ +void mcps802154_fproc_access_now(struct mcps802154_local *local); + +/** + * mcps802154_fproc_access_done() - Done with the access, release it. + * @local: MCPS private data. + * @error: True when an error happens during the access. + */ +void mcps802154_fproc_access_done(struct mcps802154_local *local, bool error); + +/** + * mcps802154_fproc_access_reset() - Reset an access when things go wrong. + * @local: MCPS private data. + * + * When an unexpected event is received, current transmitted frame and current + * access are kept as the frame buffer is possibly used by the low level driver. + * Later when the driver is reset or stopped, the buffer and the access can be + * released. + */ +void mcps802154_fproc_access_reset(struct mcps802154_local *local); + +/** + * mcps802154_fproc_schedule_change() - Try a schedule change. + * @local: MCPS private data. + * + * Inform the current state that the schedule has changed. To be called + * exclusively from CA. + */ +void mcps802154_fproc_schedule_change(struct mcps802154_local *local); + +/** + * mcps802154_fproc_stopped_handle() - Go to stopped. + * @local: MCPS private data. + */ +void mcps802154_fproc_stopped_handle(struct mcps802154_local *local); + +/** + * mcps802154_fproc_broken_handle() - Go to broken, or directly to stopped. + * @local: MCPS private data. + */ +void mcps802154_fproc_broken_handle(struct mcps802154_local *local); + +/** + * mcps802154_fproc_nothing_handle() - Handle inactivity. + * @local: MCPS private data. + * @access: Current access to handle. + * + * Return: 0 or error. + */ +int mcps802154_fproc_nothing_handle(struct mcps802154_local *local, + struct mcps802154_access *access); + +/** + * mcps802154_fproc_idle_handle() - Handle inactivity with trust in + * access->duration. + * @local: MCPS private data. + * @access: Current access to handle. + * + * Return: 0 or error. + */ +int mcps802154_fproc_idle_handle(struct mcps802154_local *local, + struct mcps802154_access *access); + +/** + * mcps802154_fproc_rx_handle() - Handle an RX access and change state. + * @local: MCPS private data. + * @access: Current access to handle. + * + * Return: 0 or error. + */ +int mcps802154_fproc_rx_handle(struct mcps802154_local *local, + struct mcps802154_access *access); + +/** + * mcps802154_fproc_tx_handle() - Handle an TX access and change state. + * @local: MCPS private data. + * @access: Current access to handle. + * + * Return: 0 or error. + */ +int mcps802154_fproc_tx_handle(struct mcps802154_local *local, + struct mcps802154_access *access); + +/** + * mcps802154_fproc_multi_handle() - Handle a multiple frames access and change + * state. + * @local: MCPS private data. + * @access: Current access to handle. + * + * Return: 0 or error. + */ +int mcps802154_fproc_multi_handle(struct mcps802154_local *local, + struct mcps802154_access *access); + +/** + * mcps802154_fproc_vendor_handle() - Handle a multiple frames access manage by vendor. + * @local: MCPS private data. + * @access: Current access to handle. + * + * Return: 0 or error. + */ +int mcps802154_fproc_vendor_handle(struct mcps802154_local *local, + struct mcps802154_access *access); + +#endif /* NET_MCPS802154_FPROC_H */ diff --git a/mac/fproc_broken.c b/mac/fproc_broken.c new file mode 100644 index 0000000..86bed74 --- /dev/null +++ b/mac/fproc_broken.c @@ -0,0 +1,77 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <linux/printk.h> + +#include "mcps802154_i.h" +#include "trace.h" + +static void mcps802154_fproc_broken_enter(struct mcps802154_local *local) +{ + trace_fproc_broken_enter(local); + pr_err_ratelimited("mcps802154: entering broken state for %s\n", + wpan_phy_name(local->hw->phy)); + local->broken = true; +} + +static void mcps802154_fproc_broken_leave(struct mcps802154_local *local) +{ + local->broken = false; +} + +static void mcps802154_fproc_broken_ignore(struct mcps802154_local *local) +{ +} + +static void +mcps802154_fproc_broken_ignore_rx_error(struct mcps802154_local *local, + enum mcps802154_rx_error_type error) +{ +} + +static void +mcps802154_fproc_broken_schedule_change(struct mcps802154_local *local) +{ + if (!local->start_stop_request) + mcps802154_fproc_stopped_handle(local); +} + +static const struct mcps802154_fproc_state mcps802154_fproc_broken = { + .name = "broken", + .enter = mcps802154_fproc_broken_enter, + .leave = mcps802154_fproc_broken_leave, + .rx_frame = mcps802154_fproc_broken_ignore, + .rx_timeout = mcps802154_fproc_broken_ignore, + .rx_error = mcps802154_fproc_broken_ignore_rx_error, + .tx_done = mcps802154_fproc_broken_ignore, + .broken = mcps802154_fproc_broken_ignore, + .schedule_change = mcps802154_fproc_broken_schedule_change, +}; + +void mcps802154_fproc_broken_handle(struct mcps802154_local *local) +{ + if (!local->start_stop_request) + /* Try to stop anyway. */ + mcps802154_fproc_stopped_handle(local); + else + mcps802154_fproc_change_state(local, &mcps802154_fproc_broken); +} diff --git a/mac/fproc_idle.c b/mac/fproc_idle.c new file mode 100644 index 0000000..300ddb0 --- /dev/null +++ b/mac/fproc_idle.c @@ -0,0 +1,68 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include "mcps802154_i.h" +#include "llhw-ops.h" + +static void mcps802154_fproc_idle_timer_expired(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + + mcps802154_fproc_access_done(local, false); + if (access->duration_dtu) { + u32 next_access_dtu = + access->timestamp_dtu + access->duration_dtu; + + mcps802154_fproc_access(local, next_access_dtu); + } else { + mcps802154_fproc_access_now(local); + } +} + +static void +mcps802154_fproc_idle_schedule_change(struct mcps802154_local *local) +{ + mcps802154_fproc_access_done(local, false); + mcps802154_fproc_access_now(local); +} + +static const struct mcps802154_fproc_state mcps802154_fproc_idle = { + .name = "idle", + .timer_expired = mcps802154_fproc_idle_timer_expired, + .schedule_change = mcps802154_fproc_idle_schedule_change, +}; + +int mcps802154_fproc_idle_handle(struct mcps802154_local *local, + struct mcps802154_access *access) +{ + int r; + + r = llhw_idle(local, access->duration_dtu != 0, + access->timestamp_dtu + access->duration_dtu); + if (r) + return r; + + mcps802154_fproc_change_state(local, &mcps802154_fproc_idle); + + return 0; +} diff --git a/mac/fproc_multi.c b/mac/fproc_multi.c new file mode 100644 index 0000000..2c7b2fa --- /dev/null +++ b/mac/fproc_multi.c @@ -0,0 +1,415 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/errno.h> + +#include "mcps802154_fproc.h" +#include "mcps802154_i.h" +#include "llhw-ops.h" + +static int mcps802154_fproc_multi_handle_frame(struct mcps802154_local *local, + struct mcps802154_access *access, + size_t frame_idx); + +static int +mcps802154_fproc_multi_restore_filter(struct mcps802154_local *local, + struct mcps802154_access *access) +{ + int r = 0; + + if (access->promiscuous) { + r = llhw_set_promiscuous_mode(local, + local->pib.mac_promiscuous); + } else if (access->hw_addr_filt_changed) { + struct ieee802154_hw_addr_filt hw_addr_filt; + + hw_addr_filt.pan_id = local->pib.mac_pan_id; + hw_addr_filt.short_addr = local->pib.mac_short_addr; + hw_addr_filt.ieee_addr = local->pib.mac_extended_addr; + hw_addr_filt.pan_coord = local->mac_pan_coord; + r = llhw_set_hw_addr_filt(local, &hw_addr_filt, + access->hw_addr_filt_changed); + } + + return r; +} + +static int mcps802154_fproc_multi_restore(struct mcps802154_local *local, + struct mcps802154_access *access) +{ + if (access->channel) { + int r; + const struct mcps802154_channel *channel = + &local->pib.phy_current_channel; + + r = llhw_set_channel(local, channel->page, channel->channel, + channel->preamble_code); + if (r) + return r; + } + + return mcps802154_fproc_multi_restore_filter(local, access); +} + +/** + * mcps802154_fproc_multi_check_frames() - Check absence of Rx without timeout. + * @local: MCPS private data. + * @access: Current access to handle. + * @frame_idx: Start frame index in the frames array. + * + * Returns: 0 on success, -errno otherwise. + */ +static int +mcps802154_fproc_multi_check_frames(struct mcps802154_local *local, + const struct mcps802154_access *access, + int frame_idx) +{ + if (access->n_frames && !access->frames) + return -EINVAL; + for (; frame_idx < access->n_frames; frame_idx++) { + const struct mcps802154_access_frame *frame = + &access->frames[frame_idx]; + /* Only first Rx can be without timeout. */ + if (!frame->is_tx && frame->rx.frame_config.timeout_dtu == -1) + return -EINVAL; + } + return 0; +} + +/** + * mcps802154_fproc_multi_next() - Continue with the next frame, or next + * access. + * @local: MCPS private data. + * @access: Current access to handle. + * @frame_idx: Frame index in current access, must be valid, will be + * incremented. + */ +static void mcps802154_fproc_multi_next(struct mcps802154_local *local, + struct mcps802154_access *access, + size_t frame_idx) +{ + frame_idx++; + if (access->ops->access_extend && frame_idx == access->n_frames) { + frame_idx = 0; + access->n_frames = 0; + access->ops->access_extend(access); + access->error = mcps802154_fproc_multi_check_frames(local, access, 0); + if (access->error) { + if (mcps802154_fproc_is_non_recoverable_error(access)) { + mcps802154_fproc_access_done(local, true); + mcps802154_fproc_broken_handle(local); + return; + } + } + } + if (frame_idx < access->n_frames) { + /* Next frame. */ + access->error = mcps802154_fproc_multi_handle_frame(local, access, + frame_idx); + if (access->error) { + if (mcps802154_fproc_is_non_recoverable_error(access)) { + mcps802154_fproc_access_done(local, true); + mcps802154_fproc_broken_handle(local); + } else { + mcps802154_fproc_access_done(local, false); + mcps802154_fproc_access_now(local); + } + } + } else { + access->error = mcps802154_fproc_multi_restore(local, access); + mcps802154_fproc_access_done(local, !!access->error); + if (access->error) { + if (mcps802154_fproc_is_non_recoverable_error(access)) { + mcps802154_fproc_broken_handle(local); + return; + } + } + /* Next access. */ + if (access->duration_dtu) { + u32 next_access_dtu = + access->timestamp_dtu + access->duration_dtu; + mcps802154_fproc_access(local, next_access_dtu); + } else { + mcps802154_fproc_access_now(local); + } + } +} + +static void mcps802154_fproc_multi_rx_rx_frame(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + size_t frame_idx = local->fproc.frame_idx; + struct mcps802154_access_frame *frame = &access->frames[frame_idx]; + + /* Read frame. */ + struct sk_buff *skb = NULL; + struct mcps802154_rx_frame_info info = { + .flags = frame->rx.frame_info_flags_request, + }; + access->error = llhw_rx_get_frame(local, &skb, &info); + if (!access->error) + access->ops->rx_frame(access, frame_idx, skb, &info, + MCPS802154_RX_ERROR_NONE); + + if (access->error) { + if (mcps802154_fproc_is_non_recoverable_error(access)) { + mcps802154_fproc_access_done(local, true); + mcps802154_fproc_broken_handle(local); + return; + } + } + mcps802154_fproc_multi_next(local, access, frame_idx); +} + +static void mcps802154_fproc_multi_rx_rx_timeout(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + size_t frame_idx = local->fproc.frame_idx; + + access->ops->rx_frame(access, frame_idx, NULL, NULL, + MCPS802154_RX_ERROR_TIMEOUT); + + /* Next. */ + mcps802154_fproc_multi_next(local, access, frame_idx); +} + +static void +mcps802154_fproc_multi_rx_rx_error(struct mcps802154_local *local, + enum mcps802154_rx_error_type error) +{ + struct mcps802154_access *access = local->fproc.access; + size_t frame_idx = local->fproc.frame_idx; + struct mcps802154_rx_frame_info info = { + .flags = MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU, + }; + + llhw_rx_get_error_frame(local, &info); + access->ops->rx_frame(access, frame_idx, NULL, &info, error); + + /* Next. */ + mcps802154_fproc_multi_next(local, access, frame_idx); +} + +static void +mcps802154_fproc_multi_rx_schedule_change(struct mcps802154_local *local) +{ + /* If the Rx is done without a timeout, disable RX and change the access. */ + struct mcps802154_access *access = local->fproc.access; + int frame_idx = local->fproc.frame_idx; + struct mcps802154_access_frame *frame = &access->frames[frame_idx]; + + if (frame->rx.frame_config.timeout_dtu == -1) { + /* Disable RX. */ + access->error = llhw_rx_disable(local); + if (access->error == -EBUSY) + /* Wait for RX result. */ + return; + + access->ops->rx_frame(access, frame_idx, NULL, NULL, + MCPS802154_RX_ERROR_TIMEOUT); + if (access->error) { + if (mcps802154_fproc_is_non_recoverable_error(access)) { + mcps802154_fproc_access_done(local, true); + mcps802154_fproc_broken_handle(local); + return; + } + } + /* Next. */ + mcps802154_fproc_multi_next(local, access, frame_idx); + } +} + +static void mcps802154_fproc_multi_rx_too_late(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + size_t frame_idx = local->fproc.frame_idx; + + access->ops->rx_frame(access, frame_idx, NULL, NULL, + MCPS802154_RX_ERROR_HPDWARN); + + /* Next. */ + mcps802154_fproc_multi_next(local, access, frame_idx); +} + +static const struct mcps802154_fproc_state mcps802154_fproc_multi_rx = { + .name = "multi_rx", + .rx_frame = mcps802154_fproc_multi_rx_rx_frame, + .rx_timeout = mcps802154_fproc_multi_rx_rx_timeout, + .rx_error = mcps802154_fproc_multi_rx_rx_error, + .rx_too_late = mcps802154_fproc_multi_rx_too_late, + .schedule_change = mcps802154_fproc_multi_rx_schedule_change, +}; + +static void mcps802154_fproc_multi_tx_tx_done(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + size_t frame_idx = local->fproc.frame_idx; + + access->ops->tx_return(access, frame_idx, local->fproc.tx_skb, + MCPS802154_ACCESS_TX_RETURN_REASON_CONSUMED); + local->fproc.tx_skb = NULL; + + /* Next. */ + mcps802154_fproc_multi_next(local, access, frame_idx); +} + +static void mcps802154_fproc_multi_tx_too_late(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + size_t frame_idx = local->fproc.frame_idx; + + access->ops->tx_return(access, frame_idx, local->fproc.tx_skb, + MCPS802154_TX_ERROR_HPDWARN); + local->fproc.tx_skb = NULL; + + /* Next. */ + mcps802154_fproc_multi_next(local, access, frame_idx); +} + +static void +mcps802154_fproc_multi_tx_schedule_change(struct mcps802154_local *local) +{ + /* Wait for end of current frame. */ +} + +static const struct mcps802154_fproc_state mcps802154_fproc_multi_tx = { + .name = "multi_tx", + .tx_done = mcps802154_fproc_multi_tx_tx_done, + .tx_too_late = mcps802154_fproc_multi_tx_too_late, + .schedule_change = mcps802154_fproc_multi_tx_schedule_change, +}; + +/** + * mcps802154_fproc_multi_handle_frame() - Handle a single frame and change + * state. + * @local: MCPS private data. + * @access: Current access to handle. + * @frame_idx: Frame index in current access, must be valid. + * + * Return: 0 or error. + */ +static int mcps802154_fproc_multi_handle_frame(struct mcps802154_local *local, + struct mcps802154_access *access, + size_t frame_idx) +{ + struct mcps802154_access_frame *frame; + struct sk_buff *skb; + int r; + + local->fproc.frame_idx = frame_idx; + + frame = &access->frames[frame_idx]; + if (!frame->is_tx) { + if (frame->rx.frame_config.flags & + MCPS802154_RX_FRAME_CONFIG_AACK) + return -EINVAL; + + if (frame->sts_params) { + r = llhw_set_sts_params(local, frame->sts_params); + if (r) + return r; + } + + r = llhw_rx_enable(local, &frame->rx.frame_config, frame_idx, + 0); + if (r) + return r; + + mcps802154_fproc_change_state(local, + &mcps802154_fproc_multi_rx); + } else { + if (frame->tx_frame_config.rx_enable_after_tx_dtu) + return -EINVAL; + + skb = access->ops->tx_get_frame(access, frame_idx); + + /* TODO: prepare next RX directly. */ + + if (frame->sts_params) { + r = llhw_set_sts_params(local, frame->sts_params); + if (r) { + access->ops->tx_return( + access, frame_idx, skb, + MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL); + return r; + } + } + + r = llhw_tx_frame(local, skb, &frame->tx_frame_config, + frame_idx, 0); + if (r) { + access->ops->tx_return( + access, frame_idx, skb, + MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL); + return r; + } + + local->fproc.tx_skb = skb; + mcps802154_ca_access_hold(local); + mcps802154_fproc_change_state(local, + &mcps802154_fproc_multi_tx); + } + + return 0; +} + +int mcps802154_fproc_multi_handle(struct mcps802154_local *local, + struct mcps802154_access *access) +{ + int r; + + if (access->n_frames == 0) + return -EINVAL; + r = mcps802154_fproc_multi_check_frames(local, access, 1); + if (r) + return r; + + if (access->promiscuous) { + r = llhw_set_promiscuous_mode(local, true); + if (r) + return r; + } else if (access->hw_addr_filt_changed) { + r = llhw_set_hw_addr_filt(local, &access->hw_addr_filt, + access->hw_addr_filt_changed); + if (r) + return r; + } + + if (access->channel) { + r = llhw_set_channel(local, access->channel->page, + access->channel->channel, + access->channel->preamble_code); + if (r) { + mcps802154_fproc_multi_restore_filter(local, access); + return r; + } + } + + if (access->hrp_uwb_params) { + r = llhw_set_hrp_uwb_params(local, access->hrp_uwb_params); + if (r) + return r; + } + + return mcps802154_fproc_multi_handle_frame(local, access, 0); +} diff --git a/mac/fproc_nothing.c b/mac/fproc_nothing.c new file mode 100644 index 0000000..07d3b45 --- /dev/null +++ b/mac/fproc_nothing.c @@ -0,0 +1,52 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include "mcps802154_i.h" +#include "llhw-ops.h" + +static void mcps802154_fproc_nothing_access(struct mcps802154_local *local) +{ + mcps802154_fproc_access_done(local, false); + mcps802154_fproc_access_now(local); +} + +static const struct mcps802154_fproc_state mcps802154_fproc_nothing = { + .name = "nothing", + .timer_expired = mcps802154_fproc_nothing_access, + .schedule_change = mcps802154_fproc_nothing_access, +}; + +int mcps802154_fproc_nothing_handle(struct mcps802154_local *local, + struct mcps802154_access *access) +{ + int r; + + r = llhw_idle(local, access->duration_dtu != 0, + access->timestamp_dtu + access->duration_dtu); + if (r) + return r; + + mcps802154_fproc_change_state(local, &mcps802154_fproc_nothing); + + return 0; +} diff --git a/mac/fproc_rx.c b/mac/fproc_rx.c new file mode 100644 index 0000000..b7cc171 --- /dev/null +++ b/mac/fproc_rx.c @@ -0,0 +1,131 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <linux/errno.h> + +#include "mcps802154_fproc.h" +#include "mcps802154_i.h" +#include "llhw-ops.h" + +static void +mcps802154_fproc_rx_wait_tx_done_tx_done(struct mcps802154_local *local) +{ + /* End current access and ask for next one. */ + mcps802154_fproc_access_done(local, false); + mcps802154_fproc_access_now(local); +} + +static void +mcps802154_fproc_rx_wait_tx_done_schedule_change(struct mcps802154_local *local) +{ + /* Nothing, wait for tx_done. */ +} + +static const struct mcps802154_fproc_state mcps802154_fproc_rx_wait_tx_done = { + .name = "rx_wait_tx_done", + .tx_done = mcps802154_fproc_rx_wait_tx_done_tx_done, + .schedule_change = mcps802154_fproc_rx_wait_tx_done_schedule_change, +}; + +static void mcps802154_fproc_rx_rx_frame(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + + /* Read frame. */ + struct sk_buff *skb = NULL; + struct mcps802154_rx_frame_info info = { + .flags = MCPS802154_RX_FRAME_INFO_LQI, + }; + access->error = llhw_rx_get_frame(local, &skb, &info); + if (!access->error) { + access->ops->rx_frame(access, 0, skb, &info, + MCPS802154_RX_ERROR_NONE); + /* If auto-ack was sent, wait for tx_done. */ + if (info.flags & MCPS802154_RX_FRAME_INFO_AACK) { + mcps802154_fproc_change_state( + local, &mcps802154_fproc_rx_wait_tx_done); + return; + } + } + if (access->error) { + if (mcps802154_fproc_is_non_recoverable_error(access)) { + mcps802154_fproc_access_done(local, true); + mcps802154_fproc_broken_handle(local); + return; + } + } + /* Next access. */ + mcps802154_fproc_access_done(local, false); + mcps802154_fproc_access_now(local); +} + +static void mcps802154_fproc_rx_rx_error(struct mcps802154_local *local, + enum mcps802154_rx_error_type error) +{ + mcps802154_fproc_access_done(local, true); + /* Next access. */ + mcps802154_fproc_access_now(local); +} + +static void mcps802154_fproc_rx_schedule_change(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + /* Disable RX. */ + access->error = llhw_rx_disable(local); + if (access->error == -EBUSY) + /* Wait for RX result. */ + return; + + if (access->error) { + if (mcps802154_fproc_is_non_recoverable_error(access)) { + mcps802154_fproc_access_done(local, true); + mcps802154_fproc_broken_handle(local); + return; + } + } + /* Next access. */ + mcps802154_fproc_access_done(local, false); + mcps802154_fproc_access_now(local); +} + +static const struct mcps802154_fproc_state mcps802154_fproc_rx = { + .name = "rx", + .rx_frame = mcps802154_fproc_rx_rx_frame, + .rx_error = mcps802154_fproc_rx_rx_error, + .schedule_change = mcps802154_fproc_rx_schedule_change, +}; + +int mcps802154_fproc_rx_handle(struct mcps802154_local *local, + struct mcps802154_access *access) +{ + struct mcps802154_rx_frame_config rx_config = { + .flags = MCPS802154_RX_FRAME_CONFIG_AACK, + .timeout_dtu = -1, + }; + access->error = llhw_rx_enable(local, &rx_config, 0, 0); + if (access->error) + return access->error; + + mcps802154_fproc_change_state(local, &mcps802154_fproc_rx); + + return 0; +} diff --git a/mac/fproc_stopped.c b/mac/fproc_stopped.c new file mode 100644 index 0000000..4ad3ff8 --- /dev/null +++ b/mac/fproc_stopped.c @@ -0,0 +1,94 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include "mcps802154_i.h" +#include "llhw-ops.h" + +static void mcps802154_fproc_stopped_enter(struct mcps802154_local *local) +{ + mcps802154_ca_notify_stop(local); + local->started = false; + wake_up(&local->wq); +} + +static void mcps802154_fproc_stopped_leave(struct mcps802154_local *local) +{ + local->started = true; +} + +static void mcps802154_fproc_stopped_ignore(struct mcps802154_local *local) +{ + WARN_ON_ONCE(1); +} + +static void +mcps802154_fproc_stopped_ignore_rx_error(struct mcps802154_local *local, + enum mcps802154_rx_error_type error) +{ + mcps802154_fproc_stopped_ignore(local); +} + +static void +mcps802154_fproc_stopped_schedule_change(struct mcps802154_local *local) +{ + int r; + + if (local->start_stop_request) { + r = llhw_start(local); + if (r) + goto err; + + mcps802154_fproc_access_now(local); + if (local->broken) + goto err_stop; + } + + return; + +err_stop: + local->start_stop_request = false; + local->fproc.state->schedule_change(local); +err: + /* Device is broken, but stopped. */ + WARN_ON(local->started); +} + +const struct mcps802154_fproc_state mcps802154_fproc_stopped = { + .name = "stopped", + .enter = mcps802154_fproc_stopped_enter, + .leave = mcps802154_fproc_stopped_leave, + .rx_frame = mcps802154_fproc_stopped_ignore, + .rx_timeout = mcps802154_fproc_stopped_ignore, + .rx_error = mcps802154_fproc_stopped_ignore_rx_error, + .tx_done = mcps802154_fproc_stopped_ignore, + .broken = mcps802154_fproc_stopped_ignore, + .schedule_change = mcps802154_fproc_stopped_schedule_change, +}; + +void mcps802154_fproc_stopped_handle(struct mcps802154_local *local) +{ + llhw_stop(local); + mcps802154_fproc_access_reset(local); + mcps802154_schedule_clear(local); + mcps802154_fproc_change_state(local, &mcps802154_fproc_stopped); +} diff --git a/mac/fproc_tx.c b/mac/fproc_tx.c new file mode 100644 index 0000000..f96a431 --- /dev/null +++ b/mac/fproc_tx.c @@ -0,0 +1,171 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <linux/errno.h> +#include <linux/ieee802154.h> +#include <linux/netdevice.h> + +#include "mcps802154_fproc.h" +#include "mcps802154_i.h" +#include "llhw-ops.h" + +static void mcps802154_fproc_tx_tx_done(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + + access->ops->tx_return(access, 0, local->fproc.tx_skb, + MCPS802154_ACCESS_TX_RETURN_REASON_CONSUMED); + local->fproc.tx_skb = NULL; + + mcps802154_fproc_access_done(local, false); + + /* Next access. */ + mcps802154_fproc_access_now(local); +} + +static void mcps802154_fproc_tx_schedule_change(struct mcps802154_local *local) +{ + /* Nothing, wait TX done. */ +} + +static const struct mcps802154_fproc_state mcps802154_fproc_tx = { + .name = "tx", + .tx_done = mcps802154_fproc_tx_tx_done, + .schedule_change = mcps802154_fproc_tx_schedule_change, +}; + +static void mcps802154_fproc_tx_wack_rx_frame(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + + /* Read frame. */ + struct sk_buff *skb = NULL; + struct mcps802154_rx_frame_info info = { + .flags = MCPS802154_RX_FRAME_INFO_LQI, + }; + access->error = llhw_rx_get_frame(local, &skb, &info); + if (!access->error) { + /* Is it an ack frame? With same seq number? */ + if (IEEE802154_FC_TYPE(skb->data[0]) == + IEEE802154_FC_TYPE_ACK && + skb->data[IEEE802154_FC_LEN] == + local->fproc.tx_skb->data[IEEE802154_FC_LEN]) { + /* Ack frame. */ + access->ops->tx_return( + access, 0, local->fproc.tx_skb, + MCPS802154_ACCESS_TX_RETURN_REASON_CONSUMED); + } else { + /* Not an ack or read failure or a bad sequence number. */ + access->ops->tx_return( + access, 0, local->fproc.tx_skb, + MCPS802154_ACCESS_TX_RETURN_REASON_FAILURE); + } + dev_kfree_skb_any(skb); + local->fproc.tx_skb = NULL; + mcps802154_fproc_access_done(local, false); + mcps802154_fproc_access_now(local); + } else if (access->error && mcps802154_fproc_is_non_recoverable_error(access)) { + mcps802154_fproc_broken_handle(local); + } else { + mcps802154_fproc_access_done(local, false); + mcps802154_fproc_access_now(local); + } +} + +/* Used for error and time-out. */ +static void mcps802154_fproc_tx_wack_rx_timeout(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + + access->ops->tx_return(access, 0, local->fproc.tx_skb, + MCPS802154_ACCESS_TX_RETURN_REASON_FAILURE); + local->fproc.tx_skb = NULL; + + mcps802154_fproc_access_done(local, false); + + /* Next access. */ + mcps802154_fproc_access_now(local); +} + +static void +mcps802154_fproc_tx_wack_rx_error(struct mcps802154_local *local, + enum mcps802154_rx_error_type error) +{ + mcps802154_fproc_tx_wack_rx_timeout(local); +} + +static void mcps802154_fproc_tx_wack_tx_done(struct mcps802154_local *local) +{ + /* Nothing, wait for ack. */ +} + +static void +mcps802154_fproc_tx_wack_schedule_change(struct mcps802154_local *local) +{ + /* Nothing, wait for ack. */ +} + +static const struct mcps802154_fproc_state mcps802154_fproc_tx_wack = { + .name = "tx_wack", + .rx_frame = mcps802154_fproc_tx_wack_rx_frame, + .rx_timeout = mcps802154_fproc_tx_wack_rx_timeout, + .rx_error = mcps802154_fproc_tx_wack_rx_error, + .tx_done = mcps802154_fproc_tx_wack_tx_done, + .schedule_change = mcps802154_fproc_tx_wack_schedule_change, +}; + +#define IEEE802154_AIFS_DURATION_SYMBOLS 12 + +int mcps802154_fproc_tx_handle(struct mcps802154_local *local, + struct mcps802154_access *access) +{ + int r; + u8 ack_req; + struct mcps802154_tx_frame_config tx_config = {}; + struct sk_buff *skb = access->ops->tx_get_frame(access, 0); + + if (!skb) + return -ENOMEM; + + ack_req = skb->data[0] & IEEE802154_FC_ACK_REQ; + if (ack_req) { + tx_config.rx_enable_after_tx_dtu = + IEEE802154_AIFS_DURATION_SYMBOLS * + local->llhw.symbol_dtu; + } + + r = llhw_tx_frame(local, skb, &tx_config, 0, 0); + if (r) { + access->ops->tx_return( + access, 0, skb, + MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL); + return r; + } + + local->fproc.tx_skb = skb; + mcps802154_ca_access_hold(local); + if (ack_req) + mcps802154_fproc_change_state(local, &mcps802154_fproc_tx_wack); + else + mcps802154_fproc_change_state(local, &mcps802154_fproc_tx); + return 0; +} diff --git a/mac/fproc_vendor.c b/mac/fproc_vendor.c new file mode 100644 index 0000000..f067e16 --- /dev/null +++ b/mac/fproc_vendor.c @@ -0,0 +1,162 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/errno.h> + +#include "mcps802154_i.h" + +static void +mcps802154_fproc_vendor_handle_callback_return(struct mcps802154_local *local, + int r) +{ + struct mcps802154_access *access = local->fproc.access; + /* Fetch/copy needed data before access_done. + * Avoid usage of access pointer after access done call. */ + int duration_dtu = access->duration_dtu; + u32 next_access_dtu = access->timestamp_dtu + duration_dtu; + /* Filter-out the 'stop' request as error. */ + bool error = r != 1; + + if (!r) + return; + + access->error = r; + mcps802154_fproc_access_done(local, error); + if (error) { + mcps802154_fproc_broken_handle(local); + } else if (duration_dtu) { + mcps802154_fproc_access(local, next_access_dtu); + } else { + mcps802154_fproc_access_now(local); + } +} + +static void mcps802154_fproc_vendor_rx_frame(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + int r; + + if (access->vendor_ops->rx_frame) + r = access->vendor_ops->rx_frame(access); + else + r = -EOPNOTSUPP; + mcps802154_fproc_vendor_handle_callback_return(local, r); +} + +static void mcps802154_fproc_vendor_rx_timeout(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + int r; + + if (access->vendor_ops->rx_timeout) + r = access->vendor_ops->rx_timeout(access); + else + r = -EOPNOTSUPP; + mcps802154_fproc_vendor_handle_callback_return(local, r); +} + +static void +mcps802154_fproc_vendor_rx_error(struct mcps802154_local *local, + enum mcps802154_rx_error_type error) +{ + struct mcps802154_access *access = local->fproc.access; + int r; + + if (access->vendor_ops->rx_error) + r = access->vendor_ops->rx_error(access, error); + else + r = -EOPNOTSUPP; + mcps802154_fproc_vendor_handle_callback_return(local, r); +} + +static void mcps802154_fproc_vendor_tx_done(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + int r; + + if (access->vendor_ops->tx_done) + r = access->vendor_ops->tx_done(access); + else + r = -EOPNOTSUPP; + mcps802154_fproc_vendor_handle_callback_return(local, r); +} + +static void mcps802154_fproc_vendor_broken(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + int r; + + if (access->vendor_ops->broken) + r = access->vendor_ops->broken(access); + else + r = -EOPNOTSUPP; + mcps802154_fproc_vendor_handle_callback_return(local, r); +} + +static void +mcps802154_fproc_vendor_timer_expired(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + + if (access->vendor_ops->timer_expired) { + int r; + + r = access->vendor_ops->timer_expired(access); + mcps802154_fproc_vendor_handle_callback_return(local, r); + } +} + +static void +mcps802154_fproc_vendor_schedule_change(struct mcps802154_local *local) +{ + struct mcps802154_access *access = local->fproc.access; + + if (access->vendor_ops->schedule_change) { + int r; + + r = access->vendor_ops->schedule_change(access); + mcps802154_fproc_vendor_handle_callback_return(local, r); + } +} + +static const struct mcps802154_fproc_state mcps802154_fproc_vendor = { + .name = "vendor", + .rx_frame = mcps802154_fproc_vendor_rx_frame, + .rx_timeout = mcps802154_fproc_vendor_rx_timeout, + .rx_error = mcps802154_fproc_vendor_rx_error, + .tx_done = mcps802154_fproc_vendor_tx_done, + .broken = mcps802154_fproc_vendor_broken, + .timer_expired = mcps802154_fproc_vendor_timer_expired, + .schedule_change = mcps802154_fproc_vendor_schedule_change, +}; + +int mcps802154_fproc_vendor_handle(struct mcps802154_local *local, + struct mcps802154_access *access) +{ + mcps802154_fproc_change_state(local, &mcps802154_fproc_vendor); + + if (access->vendor_ops->handle) { + return access->vendor_ops->handle(access); + } + return 0; +} diff --git a/mac/frame.c b/mac/frame.c new file mode 100644 index 0000000..8a61512 --- /dev/null +++ b/mac/frame.c @@ -0,0 +1,50 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/ieee802154.h> +#include <linux/module.h> +#include <net/mcps802154_frame.h> +#include <net/mcps802154.h> + +#include "mcps802154_i.h" + +struct sk_buff *mcps802154_frame_alloc(struct mcps802154_llhw *llhw, + unsigned int size, gfp_t flags) +{ + struct sk_buff *skb; + size_t hlen; + size_t tlen; + + hlen = llhw->hw->extra_tx_headroom; + tlen = IEEE802154_FCS_LEN; + + skb = alloc_skb(hlen + size + tlen, flags); + if (!skb) + return NULL; + + skb_reserve(skb, hlen); + skb_tailroom_reserve(skb, size, tlen); + + return skb; +} +EXPORT_SYMBOL(mcps802154_frame_alloc); diff --git a/mac/idle_region.c b/mac/idle_region.c new file mode 100644 index 0000000..e4f62c3 --- /dev/null +++ b/mac/idle_region.c @@ -0,0 +1,166 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include "idle_region.h" +#include "trace.h" +#include <net/idle_region_nl.h> +#include <linux/errno.h> +#include <linux/limits.h> + +static struct mcps802154_region_ops idle_region_ops; + +struct idle_local { + /** + * @region: Region instance returned to MCPS. + */ + struct mcps802154_region region; + /** + * @llhw: Low-level device pointer. + */ + struct mcps802154_llhw *llhw; + /** + * @params: Parameters. + */ + struct idle_params params; + /** + * @access: Access returned to ca. + */ + struct mcps802154_access access; +}; + +static inline struct idle_local * +region_to_local(struct mcps802154_region *region) +{ + return container_of(region, struct idle_local, region); +} + +static struct mcps802154_region *idle_open(struct mcps802154_llhw *llhw) +{ + struct idle_local *local; + + local = kzalloc(sizeof(*local), GFP_KERNEL); + if (!local) + return NULL; + local->llhw = llhw; + local->region.ops = &idle_region_ops; + + /* Default value of parameters. */ + local->params.min_duration_dtu = llhw->anticip_dtu * 2; + local->params.max_duration_dtu = 0; + + return &local->region; +} + +static void idle_close(struct mcps802154_region *region) +{ + kfree(region_to_local(region)); +} + +static const struct nla_policy idle_param_nla_policy[IDLE_PARAM_ATTR_MAX + 1] = { + [IDLE_PARAM_ATTR_MIN_DURATION_DTU] = NLA_POLICY_MIN(NLA_S32, 0), + [IDLE_PARAM_ATTR_MAX_DURATION_DTU] = NLA_POLICY_MIN(NLA_S32, 0), +}; + +static int idle_set_parameters(struct mcps802154_region *region, + const struct nlattr *params, + struct netlink_ext_ack *extack) +{ + struct idle_local *local = region_to_local(region); + struct nlattr *attrs[IDLE_PARAM_ATTR_MAX + 1]; + struct idle_params *p = &local->params; + int min_duration_dtu, max_duration_dtu; + int r; + + r = nla_parse_nested(attrs, IDLE_PARAM_ATTR_MAX, params, + idle_param_nla_policy, extack); + if (r) + return r; + + min_duration_dtu = + attrs[IDLE_PARAM_ATTR_MIN_DURATION_DTU] ? + nla_get_s32(attrs[IDLE_PARAM_ATTR_MIN_DURATION_DTU]) : + p->min_duration_dtu; + max_duration_dtu = + attrs[IDLE_PARAM_ATTR_MAX_DURATION_DTU] ? + nla_get_s32(attrs[IDLE_PARAM_ATTR_MAX_DURATION_DTU]) : + p->max_duration_dtu; + if (max_duration_dtu && min_duration_dtu && + min_duration_dtu > max_duration_dtu) + return -EINVAL; + + p->min_duration_dtu = min_duration_dtu; + p->max_duration_dtu = max_duration_dtu; + trace_region_idle_params(p); + return 0; +} + +static struct mcps802154_access_ops idle_access_ops = {}; + +static struct mcps802154_access * +idle_get_access(struct mcps802154_region *region, u32 next_timestamp_dtu, + int next_in_region_dtu, int region_duration_dtu) +{ + struct idle_local *local = region_to_local(region); + struct mcps802154_access *access = &local->access; + const struct idle_params *p = &local->params; + int left_region_duration_dtu = region_duration_dtu - next_in_region_dtu; + int duration_dtu; + + if (!left_region_duration_dtu) { + /* Region used with endless scheduler. */ + duration_dtu = p->max_duration_dtu; + } else { + /* Region used directly in on_demand scheduler. */ + if (left_region_duration_dtu < p->min_duration_dtu) + return NULL; + duration_dtu = left_region_duration_dtu; + } + + trace_region_idle_get_access(next_timestamp_dtu, duration_dtu); + access->method = MCPS802154_ACCESS_METHOD_IDLE; + access->ops = &idle_access_ops; + access->timestamp_dtu = next_timestamp_dtu; + access->duration_dtu = duration_dtu; + + return access; +} + +static struct mcps802154_region_ops idle_region_ops = { + .owner = THIS_MODULE, + .name = "idle", + .open = idle_open, + .close = idle_close, + .set_parameters = idle_set_parameters, + .get_demand = NULL, /* Not wanted. */ + .get_access = idle_get_access, +}; + +int mcps802154_idle_region_init(void) +{ + return mcps802154_region_register(&idle_region_ops); +} + +void mcps802154_idle_region_exit(void) +{ + mcps802154_region_unregister(&idle_region_ops); +} diff --git a/mac/idle_region.h b/mac/idle_region.h new file mode 100644 index 0000000..f3082d1 --- /dev/null +++ b/mac/idle_region.h @@ -0,0 +1,42 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef IDLE_REGION_H +#define IDLE_REGION_H + +struct idle_params { + /** + * @min_duration_dtu: Minimum duration of an access. + * If min is 0, no minimum is required on get_access. + */ + int min_duration_dtu; + /** + * @max_duration_dtu: Maximum duration of an access. + */ + int max_duration_dtu; +}; + +int mcps802154_idle_region_init(void); +void mcps802154_idle_region_exit(void); + +#endif /* IDLE_REGION_H */ diff --git a/mac/ie.c b/mac/ie.c new file mode 100644 index 0000000..2c7d2a9 --- /dev/null +++ b/mac/ie.c @@ -0,0 +1,312 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <asm/unaligned.h> +#include <linux/bitfield.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/ieee802154.h> +#include <linux/module.h> +#include <net/mcps802154_frame.h> + +/** + * enum mcps802154_ie_state - State of information element writing. + * @MCPS802154_IE_STATE_INIT: Initial state, no IE written. + * @MCPS802154_IE_STATE_HEADER_IE: At least one header element written. + * @MCPS802154_IE_STATE_PAYLOAD_IE: At least one payload element written. + * @MCPS802154_IE_STATE_NESTED_MLME_IE: At least one payload element written, + * the last one is a MLME IE containing at least one nested IE. + */ +enum mcps802154_ie_state { + MCPS802154_IE_STATE_INIT, + MCPS802154_IE_STATE_HEADER_IE, + MCPS802154_IE_STATE_PAYLOAD_IE, + MCPS802154_IE_STATE_NESTED_MLME_IE, +}; + +/** + * struct mcps802154_ie_cb - Control buffer used in sk_buff to store information + * while building IE. + */ +struct mcps802154_ie_cb { + /** + * @ie_state: State of IEs writing, used to know whether a terminator is + * needed or not. + */ + enum mcps802154_ie_state ie_state; + /** + * @mlme_ie_index: Index in buffer of the MLME IE header. Valid in the + * corresponding state only, used to increment IE payload length. + */ + u16 mlme_ie_index; + /** + * @header_len: Length of frame header. Can be used for frame + * encryption, the header is not encrypted but only used for + * authentication. + */ + u16 header_len; +}; + +void mcps802154_ie_put_begin(struct sk_buff *skb) +{ + struct mcps802154_ie_cb *cb = (struct mcps802154_ie_cb *)&skb->cb; + + cb->ie_state = MCPS802154_IE_STATE_INIT; + cb->header_len = skb->len; +} +EXPORT_SYMBOL(mcps802154_ie_put_begin); + +int mcps802154_ie_put_end(struct sk_buff *skb, bool data_payload) +{ + struct mcps802154_ie_cb *cb = (struct mcps802154_ie_cb *)&skb->cb; + + if (data_payload) { + bool err = false; + + if (cb->ie_state == MCPS802154_IE_STATE_HEADER_IE) + err = !mcps802154_ie_put_header_ie( + skb, IEEE802154_IE_HEADER_TERMINATION_2_ID, 0); + else if (cb->ie_state >= MCPS802154_IE_STATE_PAYLOAD_IE) + err = !mcps802154_ie_put_payload_ie( + skb, IEEE802154_IE_PAYLOAD_TERMINATION_GID, 0); + if (unlikely(err)) + return -ENOBUFS; + } + return cb->header_len; +} +EXPORT_SYMBOL(mcps802154_ie_put_end); + +void *mcps802154_ie_put_header_ie(struct sk_buff *skb, int element_id, + unsigned int len) +{ + u16 ie_header; + u8 *ie; + struct mcps802154_ie_cb *cb = (struct mcps802154_ie_cb *)&skb->cb; + + if (unlikely(len > FIELD_MAX(IEEE802154_HEADER_IE_HEADER_LENGTH))) + return NULL; + if (unlikely(skb_availroom(skb) < IEEE802154_IE_HEADER_LEN + len)) + return NULL; + + if (cb->ie_state == MCPS802154_IE_STATE_INIT) + skb->data[1] |= IEEE802154_FC_IE_PRESENT >> 8; + cb->ie_state = MCPS802154_IE_STATE_HEADER_IE; + + ie_header = + FIELD_PREP(IEEE802154_HEADER_IE_HEADER_LENGTH, len) | + FIELD_PREP(IEEE802154_HEADER_IE_HEADER_ELEMENT_ID, element_id) | + IEEE802154_HEADER_IE_HEADER_TYPE; + + ie = skb_put(skb, IEEE802154_IE_HEADER_LEN + len); + put_unaligned_le16(ie_header, ie); + + cb->header_len = skb->len; + + return ie + IEEE802154_IE_HEADER_LEN; +} +EXPORT_SYMBOL(mcps802154_ie_put_header_ie); + +void *mcps802154_ie_put_payload_ie(struct sk_buff *skb, int group_id, + unsigned int len) +{ + u16 ie_header; + u8 *ie; + struct mcps802154_ie_cb *cb = (struct mcps802154_ie_cb *)&skb->cb; + bool need_terminator = false; + + if (cb->ie_state < MCPS802154_IE_STATE_PAYLOAD_IE) + need_terminator = true; + + if (unlikely(len > FIELD_MAX(IEEE802154_PAYLOAD_IE_HEADER_LENGTH))) + return NULL; + if (unlikely(skb_availroom(skb) < + (need_terminator ? IEEE802154_IE_HEADER_LEN : 0) + + IEEE802154_IE_HEADER_LEN + len)) + return NULL; + + if (cb->ie_state == MCPS802154_IE_STATE_INIT) + skb->data[1] |= IEEE802154_FC_IE_PRESENT >> 8; + if (need_terminator) + mcps802154_ie_put_header_ie( + skb, IEEE802154_IE_HEADER_TERMINATION_1_ID, 0); + cb->ie_state = MCPS802154_IE_STATE_PAYLOAD_IE; + + ie_header = + FIELD_PREP(IEEE802154_PAYLOAD_IE_HEADER_LENGTH, len) | + FIELD_PREP(IEEE802154_PAYLOAD_IE_HEADER_GROUP_ID, group_id) | + IEEE802154_PAYLOAD_IE_HEADER_TYPE; + + ie = skb_put(skb, IEEE802154_IE_HEADER_LEN + len); + put_unaligned_le16(ie_header, ie); + + return ie + IEEE802154_IE_HEADER_LEN; +} +EXPORT_SYMBOL(mcps802154_ie_put_payload_ie); + +void *mcps802154_ie_put_nested_mlme_ie(struct sk_buff *skb, int sub_id, + unsigned int len) +{ + u16 ie_header; + u8 *ie; + struct mcps802154_ie_cb *cb = (struct mcps802154_ie_cb *)&skb->cb; + + if (sub_id < IEEE802154_IE_NESTED_SHORT_MIN_SID) { + if (unlikely( + len > + FIELD_MAX(IEEE802154_LONG_NESTED_IE_HEADER_LENGTH))) + return NULL; + ie_header = FIELD_PREP(IEEE802154_LONG_NESTED_IE_HEADER_LENGTH, + len) | + FIELD_PREP(IEEE802154_LONG_NESTED_IE_HEADER_SUB_ID, + sub_id) | + IEEE802154_LONG_NESTED_IE_HEADER_TYPE; + } else { + if (unlikely(len > + FIELD_MAX( + IEEE802154_SHORT_NESTED_IE_HEADER_LENGTH))) + return NULL; + ie_header = FIELD_PREP(IEEE802154_SHORT_NESTED_IE_HEADER_LENGTH, + len) | + FIELD_PREP(IEEE802154_SHORT_NESTED_IE_HEADER_SUB_ID, + sub_id) | + IEEE802154_SHORT_NESTED_IE_HEADER_TYPE; + } + + if (cb->ie_state != MCPS802154_IE_STATE_NESTED_MLME_IE) { + ie = mcps802154_ie_put_payload_ie( + skb, IEEE802154_IE_PAYLOAD_MLME_GID, + IEEE802154_IE_HEADER_LEN + len); + if (unlikely(!ie)) + return NULL; + cb->ie_state = MCPS802154_IE_STATE_NESTED_MLME_IE; + cb->mlme_ie_index = ie - IEEE802154_IE_HEADER_LEN - skb->data; + } else { + u8 *mlme_ie = skb->data + cb->mlme_ie_index; + u16 mlme_ie_header = get_unaligned_le16(mlme_ie); + int mlme_len = FIELD_GET(IEEE802154_PAYLOAD_IE_HEADER_LENGTH, + mlme_ie_header); + + mlme_len += IEEE802154_IE_HEADER_LEN + len; + if (unlikely(mlme_len > + FIELD_MAX(IEEE802154_PAYLOAD_IE_HEADER_LENGTH))) + return NULL; + mlme_ie_header = (mlme_ie_header & + ~IEEE802154_PAYLOAD_IE_HEADER_LENGTH) | + FIELD_PREP(IEEE802154_PAYLOAD_IE_HEADER_LENGTH, + mlme_len); + put_unaligned_le16(mlme_ie_header, mlme_ie); + + ie = skb_put(skb, IEEE802154_IE_HEADER_LEN + len); + } + + put_unaligned_le16(ie_header, ie); + + return ie + IEEE802154_IE_HEADER_LEN; +} +EXPORT_SYMBOL(mcps802154_ie_put_nested_mlme_ie); + +int mcps802154_ie_get(struct sk_buff *skb, + struct mcps802154_ie_get_context *context) +{ + u16 ie_header; + bool header; + bool last = false; + + if (skb->len < IEEE802154_IE_HEADER_LEN) { + if (context->mlme_len) + /* This could only happen if caller made a mistake. */ + return -EINVAL; + if (skb->len > 0) + /* Not enough for a header, but too much for nothing. */ + return -EBADMSG; + context->in_payload = true; + context->kind = MCPS802154_IE_GET_KIND_NONE; + context->id = 0; + context->len = 0; + return 1; + } + + ie_header = get_unaligned_le16(skb->data); + skb_pull(skb, sizeof(ie_header)); + + if (context->mlme_len) { + context->kind = MCPS802154_IE_GET_KIND_MLME_NESTED; + if ((ie_header & IEEE802154_IE_HEADER_TYPE) == + IEEE802154_LONG_NESTED_IE_HEADER_TYPE) { + context->id = FIELD_GET( + IEEE802154_LONG_NESTED_IE_HEADER_SUB_ID, + ie_header); + context->len = FIELD_GET( + IEEE802154_LONG_NESTED_IE_HEADER_LENGTH, + ie_header); + } else { + context->id = FIELD_GET( + IEEE802154_SHORT_NESTED_IE_HEADER_SUB_ID, + ie_header); + context->len = FIELD_GET( + IEEE802154_SHORT_NESTED_IE_HEADER_LENGTH, + ie_header); + } + if (context->mlme_len < IEEE802154_IE_HEADER_LEN + context->len) + return -EBADMSG; + context->mlme_len -= IEEE802154_IE_HEADER_LEN + context->len; + } else { + header = (ie_header & IEEE802154_IE_HEADER_TYPE) == + IEEE802154_HEADER_IE_HEADER_TYPE; + if (header != !context->in_payload) + return -EBADMSG; + if (header) { + context->kind = MCPS802154_IE_GET_KIND_HEADER; + context->id = FIELD_GET( + IEEE802154_HEADER_IE_HEADER_ELEMENT_ID, + ie_header); + context->len = FIELD_GET( + IEEE802154_HEADER_IE_HEADER_LENGTH, ie_header); + if (context->id == + IEEE802154_IE_HEADER_TERMINATION_1_ID) + context->in_payload = true; + if (context->id == + IEEE802154_IE_HEADER_TERMINATION_2_ID) { + context->in_payload = true; + last = true; + } + } else { + context->kind = MCPS802154_IE_GET_KIND_PAYLOAD; + context->id = + FIELD_GET(IEEE802154_PAYLOAD_IE_HEADER_GROUP_ID, + ie_header); + context->len = FIELD_GET( + IEEE802154_PAYLOAD_IE_HEADER_LENGTH, ie_header); + if (context->id == + IEEE802154_IE_PAYLOAD_TERMINATION_GID) + last = true; + else if (context->id == IEEE802154_IE_PAYLOAD_MLME_GID) + context->mlme_len = context->len; + } + if (skb->len < context->len) + return -EBADMSG; + } + + return last ? 1 : 0; +} +EXPORT_SYMBOL(mcps802154_ie_get); diff --git a/mac/include/Android.bp b/mac/include/Android.bp new file mode 100644 index 0000000..a7d34c7 --- /dev/null +++ b/mac/include/Android.bp @@ -0,0 +1,20 @@ +// Copyright 2021 Qorvo US, Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_library_headers { + name: "uwb.mac.net.headers", + host_supported: true, + vendor_available: true, + system_ext_specific: true, + export_include_dirs : ["."], +} diff --git a/mac/include/net/fira_region_nl.h b/mac/include/net/fira_region_nl.h new file mode 100644 index 0000000..07c3fb5 --- /dev/null +++ b/mac/include/net/fira_region_nl.h @@ -0,0 +1,902 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef FIRA_REGION_NL_H +#define FIRA_REGION_NL_H + +/** + * enum fira_call - FiRa calls identifiers. + * + * @FIRA_CALL_GET_CAPABILITIES: + * Request FiRa capabilities. + * @FIRA_CALL_SESSION_INIT: + * Initialize FiRa session. + * @FIRA_CALL_SESSION_START: + * Start FiRa session. + * @FIRA_CALL_SESSION_STOP: + * Stop FiRa session. + * @FIRA_CALL_SESSION_DEINIT: + * Deinit FiRa session. + * @FIRA_CALL_SESSION_SET_PARAMS: + * Set session parameters. + * @FIRA_CALL_NEW_CONTROLEE: + * Add a new controlee to a session. + * @FIRA_CALL_DEL_CONTROLEE: + * Delete controlee from the session. + * @FIRA_CALL_SESSION_NOTIFICATION: + * Notify session reports. + * @FIRA_CALL_SESSION_GET_PARAMS: + * Get session parameters. + * @FIRA_CALL_SESSION_GET_STATE: + * Get session state. + * @FIRA_CALL_SESSION_GET_COUNT: + * Get count of active and inactive sessions. + * @FIRA_CALL_SET_CONTROLEE: + * Set controlees to a session. + * @FIRA_CALL_GET_CONTROLEES: + * Get the list of controlees. + * @FIRA_CALL_MAX: Internal use. + */ +enum fira_call { + FIRA_CALL_GET_CAPABILITIES, + FIRA_CALL_SESSION_INIT, + FIRA_CALL_SESSION_START, + FIRA_CALL_SESSION_STOP, + FIRA_CALL_SESSION_DEINIT, + FIRA_CALL_SESSION_SET_PARAMS, + FIRA_CALL_NEW_CONTROLEE, + FIRA_CALL_DEL_CONTROLEE, + FIRA_CALL_SESSION_NOTIFICATION, + FIRA_CALL_SESSION_GET_PARAMS, + FIRA_CALL_SESSION_GET_STATE, + FIRA_CALL_SESSION_GET_COUNT, + FIRA_CALL_SET_CONTROLEE, + FIRA_CALL_GET_CONTROLEES, + FIRA_CALL_MAX, +}; + +/** + * enum fira_capability_attrs - FiRa capabilities. + * + * @FIRA_CAPABILITY_ATTR_FIRA_PHY_VERSION_RANGE: + * FiRa PHY version range supported, ex: 0x01010202 -> support from 1.1 to 2.2. + * @FIRA_CAPABILITY_ATTR_FIRA_MAC_VERSION_RANGE: + * FiRa MAC version range supported, ex: @0x01010202 -> support from 1.1 to 2.2. + * @FIRA_CAPABILITY_ATTR_DEVICE_CLASS: + * Class of FiRa device. + * @FIRA_CAPABILITY_ATTR_DEVICE_TYPE_CONTROLEE_RESPONDER: + * Acting as a controlee/responder supported. + * @FIRA_CAPABILITY_ATTR_DEVICE_TYPE_CONTROLEE_INITIATOR: + * Acting as a controlee/initiator supported. + * @FIRA_CAPABILITY_ATTR_DEVICE_TYPE_CONTROLLER_RESPONDER: + * Acting as a controller/responder supported. + * @FIRA_CAPABILITY_ATTR_DEVICE_TYPE_CONTROLLER_INITIATOR: + * Acting as a controller/initiator supported. + * @FIRA_CAPABILITY_ATTR_MULTI_NODE_MODE_UNICAST: + * Unicast supported. + * @FIRA_CAPABILITY_ATTR_MULTI_NODE_MODE_ONE_TO_MANY: + * One to many supported. + * @FIRA_CAPABILITY_ATTR_RANGING_ROUND_USAGE_SS_TWR: + * SS-TWR supported. + * @FIRA_CAPABILITY_ATTR_RANGING_ROUND_USAGE_DS_TWR: + * DS-TWR supported. + * @FIRA_CAPABILITY_ATTR_NUMBER_OF_CONTROLEES_MAX: + * Maximum number of controlee, no limit if not present (but the size of + * the control message is a limit). + * @FIRA_CAPABILITY_ATTR_ROUND_HOPPING: + * Round hopping supported. + * @FIRA_CAPABILITY_ATTR_BLOCK_STRIDING: + * Block striding supported. + * @FIRA_CAPABILITY_ATTR_EMBEDDED_MODE_NON_DEFERRED: + * Non-deferred mode supported. + * @FIRA_CAPABILITY_ATTR_CHANNEL_NUMBER: + * Bitmask, with bits 5, 6, 8, 9, 10, 12, 13 and 14 representing + * the corresponding channel support. + * @FIRA_CAPABILITY_ATTR_RFRAME_CONFIG_SP0: + * SP0 supported. + * @FIRA_CAPABILITY_ATTR_RFRAME_CONFIG_SP1: + * SP1 supported. + * @FIRA_CAPABILITY_ATTR_RFRAME_CONFIG_SP3: + * SP3 supported. + * @FIRA_CAPABILITY_ATTR_PRF_MODE_BPRF: + * BPRF supported. + * @FIRA_CAPABILITY_ATTR_PRF_MODE_HPRF: + * HPRF supported. + * @FIRA_CAPABILITY_ATTR_PREAMBLE_DURATION_64: + * Preamble duration 64 symbols supported. + * @FIRA_CAPABILITY_ATTR_PREAMBLE_DURATION_32: + * Preamble duration 32 symbols supported. + * @FIRA_CAPABILITY_ATTR_SFD_ID_0: + * SFD 0 supported. + * @FIRA_CAPABILITY_ATTR_SFD_ID_1: + * SFD 1 supported. + * @FIRA_CAPABILITY_ATTR_SFD_ID_2: + * SFD 2 supported. + * @FIRA_CAPABILITY_ATTR_SFD_ID_3: + * SFD 3 supported. + * @FIRA_CAPABILITY_ATTR_SFD_ID_4: + * SFD 4 supported. + * @FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_0: + * 0 segment for STS supported (SP0). + * @FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_1: + * 1 segment for STS supported. + * @FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_2: + * 2 segments for STS supported. + * @FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_3: + * 3 segments for STS supported. + * @FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_4: + * 4 segments for STS supported. + * @FIRA_CAPABILITY_ATTR_PSDU_DATA_RATE_6M81: + * 6.81 Mbps support. + * @FIRA_CAPABILITY_ATTR_PSDU_DATA_RATE_7M80: + * 7.80 Mbps support. + * @FIRA_CAPABILITY_ATTR_PSDU_DATA_RATE_27M2: + * 27.2 Mbps support. + * @FIRA_CAPABILITY_ATTR_PSDU_DATA_RATE_31M2: + * 31.2 Mbps support. + * @FIRA_CAPABILITY_ATTR_BPRF_PHR_DATA_RATE_850K: + * 850 kbps for PHR in BPRF supported. + * @FIRA_CAPABILITY_ATTR_BPRF_PHR_DATA_RATE_6M81: + * 6.81 Mbps for PHR in BPRF supported. + * @FIRA_CAPABILITY_ATTR_MAC_FCS_TYPE_CRC32: + * CRC32 supported. + * @FIRA_CAPABILITY_ATTR_TX_ADAPTIVE_PAYLOAD_POWER: + * Adaptive payload power for TX supported. + * @FIRA_CAPABILITY_ATTR_RX_ANTENNA_PAIRS: + * Number of antenna pairs for RX. + * @FIRA_CAPABILITY_ATTR_TX_ANTENNAS: + * Number of antennas for TX. + * @FIRA_CAPABILITY_ATTR_STS_STATIC: + * Static STS supported. + * @FIRA_CAPABILITY_ATTR_STS_DYNAMIC: + * Dynamic STS supported. + * @FIRA_CAPABILITY_ATTR_STS_DYNAMIC_INDIVIDUAL_KEY: + * Dynamic STS for controlee individual keys supported. + * @FIRA_CAPABILITY_ATTR_STS_PROVISIONED: + * Provisioned STS supported. + * @FIRA_CAPABILITY_ATTR_STS_PROVISIONED_INDIVIDUAL_KEY: + * Provisioned STS for controlee individual keys supported. + * @FIRA_CAPABILITY_ATTR_AOA_AZIMUTH: + * AoA in azimuth supported. + * @FIRA_CAPABILITY_ATTR_AOA_AZIMUTH_FULL: + * AoA in azimuth supported on full circle (front/back difference). + * @FIRA_CAPABILITY_ATTR_AOA_ELEVATION: + * AoA in elevation supported. + * @FIRA_CAPABILITY_ATTR_AOA_FOM: + * AoA FOM supported. + * + * @FIRA_CAPABILITY_ATTR_UNSPEC: Invalid command. + * @__FIRA_CAPABILITY_ATTR_AFTER_LAST: Internal use. + * @FIRA_CAPABILITY_ATTR_MAX: Internal use. + */ +enum fira_capability_attrs { + FIRA_CAPABILITY_ATTR_UNSPEC, + /* Main session capabilities. */ + FIRA_CAPABILITY_ATTR_FIRA_PHY_VERSION_RANGE, + FIRA_CAPABILITY_ATTR_FIRA_MAC_VERSION_RANGE, + FIRA_CAPABILITY_ATTR_DEVICE_CLASS, + FIRA_CAPABILITY_ATTR_DEVICE_TYPE_CONTROLEE_RESPONDER, + FIRA_CAPABILITY_ATTR_DEVICE_TYPE_CONTROLEE_INITIATOR, + FIRA_CAPABILITY_ATTR_DEVICE_TYPE_CONTROLLER_RESPONDER, + FIRA_CAPABILITY_ATTR_DEVICE_TYPE_CONTROLLER_INITIATOR, + FIRA_CAPABILITY_ATTR_MULTI_NODE_MODE_UNICAST, + FIRA_CAPABILITY_ATTR_MULTI_NODE_MODE_ONE_TO_MANY, + FIRA_CAPABILITY_ATTR_RANGING_ROUND_USAGE_SS_TWR, + FIRA_CAPABILITY_ATTR_RANGING_ROUND_USAGE_DS_TWR, + FIRA_CAPABILITY_ATTR_NUMBER_OF_CONTROLEES_MAX, + /* Behaviour. */ + FIRA_CAPABILITY_ATTR_ROUND_HOPPING, + FIRA_CAPABILITY_ATTR_BLOCK_STRIDING, + FIRA_CAPABILITY_ATTR_EMBEDDED_MODE_NON_DEFERRED, + /* Radio. */ + FIRA_CAPABILITY_ATTR_CHANNEL_NUMBER, + FIRA_CAPABILITY_ATTR_RFRAME_CONFIG_SP0, + FIRA_CAPABILITY_ATTR_RFRAME_CONFIG_SP1, + FIRA_CAPABILITY_ATTR_RFRAME_CONFIG_SP3, + FIRA_CAPABILITY_ATTR_PRF_MODE_BPRF, + FIRA_CAPABILITY_ATTR_PRF_MODE_HPRF, + FIRA_CAPABILITY_ATTR_PREAMBLE_DURATION_64, + FIRA_CAPABILITY_ATTR_PREAMBLE_DURATION_32, + FIRA_CAPABILITY_ATTR_SFD_ID_0, + FIRA_CAPABILITY_ATTR_SFD_ID_1, + FIRA_CAPABILITY_ATTR_SFD_ID_2, + FIRA_CAPABILITY_ATTR_SFD_ID_3, + FIRA_CAPABILITY_ATTR_SFD_ID_4, + FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_0, + FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_1, + FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_2, + FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_3, + FIRA_CAPABILITY_ATTR_NUMBER_OF_STS_SEGMENTS_4, + FIRA_CAPABILITY_ATTR_PSDU_DATA_RATE_6M81, + FIRA_CAPABILITY_ATTR_PSDU_DATA_RATE_7M80, + FIRA_CAPABILITY_ATTR_PSDU_DATA_RATE_27M2, + FIRA_CAPABILITY_ATTR_PSDU_DATA_RATE_31M2, + FIRA_CAPABILITY_ATTR_BPRF_PHR_DATA_RATE_850K, + FIRA_CAPABILITY_ATTR_BPRF_PHR_DATA_RATE_6M81, + FIRA_CAPABILITY_ATTR_MAC_FCS_TYPE_CRC32, + FIRA_CAPABILITY_ATTR_TX_ADAPTIVE_PAYLOAD_POWER, + /* Antenna. */ + FIRA_CAPABILITY_ATTR_RX_ANTENNA_PAIRS, + FIRA_CAPABILITY_ATTR_TX_ANTENNAS, + /* STS and crypto capabilities. */ + FIRA_CAPABILITY_ATTR_STS_STATIC, + FIRA_CAPABILITY_ATTR_STS_DYNAMIC, + FIRA_CAPABILITY_ATTR_STS_DYNAMIC_INDIVIDUAL_KEY, + FIRA_CAPABILITY_ATTR_STS_PROVISIONED, + FIRA_CAPABILITY_ATTR_STS_PROVISIONED_INDIVIDUAL_KEY, + /* Report. */ + FIRA_CAPABILITY_ATTR_AOA_AZIMUTH, + FIRA_CAPABILITY_ATTR_AOA_AZIMUTH_FULL, + FIRA_CAPABILITY_ATTR_AOA_ELEVATION, + FIRA_CAPABILITY_ATTR_AOA_FOM, + + __FIRA_CAPABILITY_ATTR_AFTER_LAST, + FIRA_CAPABILITY_ATTR_MAX = __FIRA_CAPABILITY_ATTR_AFTER_LAST - 1 +}; + +/** + * enum fira_call_attrs - FiRa call attributes. + * + * @FIRA_CALL_ATTR_SESSION_ID: + * Session identifier. + * @FIRA_CALL_ATTR_SESSION_PARAMS: + * Session parameters. + * @FIRA_CALL_ATTR_CONTROLEES: + * Controlees information. + * @FIRA_CALL_ATTR_RANGING_DATA: + * Ranging data. + * @FIRA_CALL_ATTR_CAPABILITIES: + * Capabilities. + * @FIRA_CALL_ATTR_SESSION_STATE: + * Session state. + * @FIRA_CALL_ATTR_SESSION_COUNT: + * Sessions count. + * @FIRA_CALL_ATTR_SEQUENCE_NUMBER: + * Session notification counter. + * @FIRA_CALL_ATTR_RANGING_DIAGNOSTICS: + * Diagnostic information. + * + * @FIRA_CALL_ATTR_UNSPEC: Invalid command. + * @__FIRA_CALL_ATTR_AFTER_LAST: Internal use. + * @FIRA_CALL_ATTR_MAX: Internal use. + */ +enum fira_call_attrs { + FIRA_CALL_ATTR_UNSPEC, + FIRA_CALL_ATTR_SESSION_ID, + FIRA_CALL_ATTR_SESSION_PARAMS, + FIRA_CALL_ATTR_CONTROLEES, + FIRA_CALL_ATTR_RANGING_DATA, + FIRA_CALL_ATTR_CAPABILITIES, + FIRA_CALL_ATTR_SESSION_STATE, + FIRA_CALL_ATTR_SESSION_COUNT, + FIRA_CALL_ATTR_SEQUENCE_NUMBER, + FIRA_CALL_ATTR_RANGING_DIAGNOSTICS, + + __FIRA_CALL_ATTR_AFTER_LAST, + FIRA_CALL_ATTR_MAX = __FIRA_CALL_ATTR_AFTER_LAST - 1 +}; + +/** + * enum fira_session_param_attrs - FiRa session parameters attributes. + * + * @FIRA_SESSION_PARAM_ATTR_DEVICE_TYPE: + * Controlee (0) or controller (1) + * @FIRA_SESSION_PARAM_ATTR_DEVICE_ROLE: + * Responder (0) or initiator (1) + * @FIRA_SESSION_PARAM_ATTR_RANGING_ROUND_USAGE: + * SS-TWR (1) or DS-TWR (2, default) + * @FIRA_SESSION_PARAM_ATTR_MULTI_NODE_MODE: + * Unicast (0), one to many (1) or many to many (2, unused) + * @FIRA_SESSION_PARAM_ATTR_SHORT_ADDR: + * Override device address for this session (UCI: DEVICE_MAC_ADDRESS) + * @FIRA_SESSION_PARAM_ATTR_DESTINATION_SHORT_ADDR: + * Controller short addresses (UCI: DST_MAC_ADDRESS) [controlee only] + * @FIRA_SESSION_PARAM_ATTR_INITIATION_TIME_MS: + * Initiation time in unit of 1200 RSTU (same as ms), default 0. + * @FIRA_SESSION_PARAM_ATTR_SLOT_DURATION_RSTU: + * Duration of a slot in RSTU, default 2400. (2 ms) + * @FIRA_SESSION_PARAM_ATTR_BLOCK_DURATION_MS: + * Block size in unit of 1200 RSTU (same as ms), default 200. + * @FIRA_SESSION_PARAM_ATTR_ROUND_DURATION_SLOTS: + * Number of slots per ranging round, default 30 (UCI: SLOTS_PER_RR) + * @FIRA_SESSION_PARAM_ATTR_BLOCK_STRIDE_LENGTH: + * Value of block striding, default 0, can be changed when the + * session is active [controller only] + * @FIRA_SESSION_PARAM_ATTR_MAX_NUMBER_OF_MEASUREMENTS: + * Unlimited (0, default) or limit of measurements (1-65535) + * @FIRA_SESSION_PARAM_ATTR_MAX_RR_RETRY: + * Number of failed ranging round attempts before stopping the session, + * or disabled (0, default) [controller only] + * @FIRA_SESSION_PARAM_ATTR_ROUND_HOPPING: + * Disabled (0, default) or enabled (1) + * @FIRA_SESSION_PARAM_ATTR_PRIORITY: + * Priority value, higher has more priority (1-100, default 50) + * @FIRA_SESSION_PARAM_ATTR_RESULT_REPORT_PHASE: + * Disabled (0) or enabled (1, default) report phase [controller only] + * @FIRA_SESSION_PARAM_ATTR_MR_AT_INITIATOR: + * Measurement report available at responder (0, default) + * or at initiator (1) [controller only] + * @FIRA_SESSION_PARAM_ATTR_EMBEDDED_MODE: + * Deferred (0, default) or non-deferred (1) (not in UCI) + * @FIRA_SESSION_PARAM_ATTR_IN_BAND_TERMINATION_ATTEMPT_COUNT: + * 1-10, default 1 [controller only] + * @FIRA_SESSION_PARAM_ATTR_CHANNEL_NUMBER: + * Override channel for this session: 5, 6, 8, 9, 10, 12, 13 or 14 + * @FIRA_SESSION_PARAM_ATTR_PREAMBLE_CODE_INDEX: + * Override preamble code for this session, BPRF (9-24), HPRF (25-32) + * @FIRA_SESSION_PARAM_ATTR_RFRAME_CONFIG: + * SP0 (0), SP1 (1), SP2 (2, unused, not in FiRa 1.1) or SP3 (3, default) + * @FIRA_SESSION_PARAM_ATTR_PRF_MODE: + * BPRF (0, default), HPRF (1) or HPRF with high data rate (2) + * @FIRA_SESSION_PARAM_ATTR_PREAMBLE_DURATION: + * 64 (1, default) or 32 (0, only for HPRF) + * @FIRA_SESSION_PARAM_ATTR_SFD_ID: + * BPRF (0 or 2), HPRF (1-4), default 2 + * @FIRA_SESSION_PARAM_ATTR_NUMBER_OF_STS_SEGMENTS: + * 0-2, default to 0 for SP0, default to 1 for SP1 & SP3, 2 not supported + * @FIRA_SESSION_PARAM_ATTR_PSDU_DATA_RATE: + * 6.81 Mbps (0, default), 7.80 Mbps (1), 27.2 Mbps (2), 31.2 Mbps (3) + * @FIRA_SESSION_PARAM_ATTR_BPRF_PHR_DATA_RATE: + * 850 kbps (0, default) or 6.81 Mbps (1) + * @FIRA_SESSION_PARAM_ATTR_MAC_FCS_TYPE: + * CRC16 (0, default) or CRC32 (1, not supported) + * @FIRA_SESSION_PARAM_ATTR_TX_ADAPTIVE_PAYLOAD_POWER: + * Disable adaptive payload power for TX (0, default) or enable (1) + * @FIRA_SESSION_PARAM_ATTR_MEASUREMENT_SEQUENCE: + * Sequence of measurement steps. Configures antenna flexibility. + * @FIRA_SESSION_PARAM_ATTR_STS_CONFIG: + * Static STS (0, default), Dynamic STS (1), Dynamic STS for controlee + * individual keys (2), Provisioned STS (3), Provisioned STS for controlee + * individual keys (4). See &enum fira_sts_mode. + * @FIRA_SESSION_PARAM_ATTR_SUB_SESSION_ID: + * For dynamic STS for controlee individual key, sub session ID [controlee only] + * @FIRA_SESSION_PARAM_ATTR_VUPPER64: + * vUpper64 for static STS (UCI: STATIC_STS_IV | VENDOR_ID) + * @FIRA_SESSION_PARAM_ATTR_SESSION_KEY: + * For provisioned sts only, session key. + * @FIRA_SESSION_PARAM_ATTR_SUB_SESSION_KEY: + * For dynamic or provisioned STS, sub session key [controlee only] + * @FIRA_SESSION_PARAM_ATTR_KEY_ROTATION: + * Disable (0, default) or enabled (1) + * @FIRA_SESSION_PARAM_ATTR_KEY_ROTATION_RATE: + * Key rotation rate parameter n, key rotated after 2^n rounds + * @FIRA_SESSION_PARAM_ATTR_AOA_RESULT_REQ: + * No local AoA report (0) or -90 to +90 (1, default) + * @FIRA_SESSION_PARAM_ATTR_REPORT_TOF: + * Report ToF in result message, disabled (0) or enabled (1, default) + * @FIRA_SESSION_PARAM_ATTR_REPORT_AOA_AZIMUTH: + * Report AoA azimuth in result message, disabled (0, default) or enabled (1) + * @FIRA_SESSION_PARAM_ATTR_REPORT_AOA_ELEVATION: + * Report AoA elevation in result message, disabled (0, default) or enabled (1) + * @FIRA_SESSION_PARAM_ATTR_REPORT_AOA_FOM: + * Report AoA FOM in result message, disabled (0, default) or enabled (1) + * @FIRA_SESSION_PARAM_ATTR_REPORT_RSSI: + * Report average RSSI of the round in result message, disabled (0, default) or enabled (1) + * @FIRA_SESSION_PARAM_ATTR_DATA_VENDOR_OUI: +* Set the vendor OUI for custom data exchanges +* @FIRA_SESSION_PARAM_ATTR_DATA_PAYLOAD: +* Set the data payload to send in next ranging packet + * @FIRA_SESSION_PARAM_ATTR_DIAGNOSTICS: + * Report diagnostic information on each round, disabled (0, default) or enabled (1) + * @FIRA_SESSION_PARAM_ATTR_DIAGNOSTICS_FRAME_REPORTS_FIELDS: + * Bitfield activating various frame diagnostics in the report (0: no frame diagnostic report, default). + * see &enum fira_ranging_diagnostics_frame_report_flags + * @FIRA_SESSION_PARAM_ATTR_STS_LENGTH: + * Number of symbols in a STS segment. 32 (0x00), 64 (0x01, default) or 128 + * symbols (0x02) + * @FIRA_SESSION_PARAM_ATTR_CAP_SIZE_MAX: + * Maximum for contention access period size + * @FIRA_SESSION_PARAM_ATTR_CAP_SIZE_MIN: + * Minimum for contention access period size + * @FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_CONFIG: + * Configure range data notification + * @FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_PROXIMITY_NEAR_MM: + * Lower bound in mm above which the ranging notifications + * should be enabled when RANGE_DATA_NTF_CONFIG is set to "proximity" or "aoa_proximity" + * @FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_PROXIMITY_FAR_MM: + * Upper bound in mm above which the ranging notifications + * should be disabled when RANGE_DATA_NTF_CONFIG is set to "proximity" or "aoa_proximity" + * @FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI: + * Lower bound in rad_2pi_q16 for AOA azimuth above which the ranging notifications + * should automatically be enabled if RANGE_DATA_NTF_CONFIG is set to "aoa" or "aoa_proximity". + * It is a signed value on 16 bits (rad_2pi_q16). Allowed values range from -180° to ~180°. + * should be less than or equal to RANGE_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH value. + * (default = -180) + * @FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI: + * Upper bound in rad_2pi_q16 for AOA azimuth above which the ranging notifications + * should automatically be disabled if RANGE_DATA_NTF_CONFIG is set to "aoa" or "aoa_proximity". + * It is a signed value on 16 bits (rad_2pi_q16). Allowed values range from -180° to ~180°. + * Should be greater than or equal to RANGE_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH value. + * (default = ~180) + * @FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI: + * Lower bound in rad_2pi_q16 for AOA elevation above which the ranging notifications + * should automatically be enabled if RANGE_DATA_NTF_CONFIG is set to "aoa" or "aoa_proximity". + * It is a signed value on 16 bits (rad_2pi_q16). Allowed values range from -90° to +90°. + * Should be less than or equal to RANGE_DATA_NTF_PROXIMITY_UPPER_BOUND_A_ELEVATION value. + * (default = -90) + * @FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI: + * Upper bound in rad_2pi_q16 for AOA elevation above which the ranging notifications + * should automatically be disabled if RANGE_DATA_NTF_CONFIG has bit is set to "aoa" or "aoa_proximity". + * It is a signed value on 16 bits (rad_2pi_q16). Allowed values range from -90° to +90°. + * Should be greater than or equal to RANGE_DATA_NTF_LOWER_BOUND_AOA_ELEVATION value. + * (default = +90) + * @FIRA_SESSION_PARAM_ATTR_UNSPEC: Invalid command. + * @__FIRA_SESSION_PARAM_ATTR_AFTER_LAST: Internal use. + * @FIRA_SESSION_PARAM_ATTR_MAX: Internal use. + */ +enum fira_session_param_attrs { + FIRA_SESSION_PARAM_ATTR_UNSPEC, + /* Main session parameters */ + FIRA_SESSION_PARAM_ATTR_DEVICE_TYPE, + FIRA_SESSION_PARAM_ATTR_DEVICE_ROLE, + FIRA_SESSION_PARAM_ATTR_RANGING_ROUND_USAGE, + FIRA_SESSION_PARAM_ATTR_MULTI_NODE_MODE, + FIRA_SESSION_PARAM_ATTR_SHORT_ADDR, + FIRA_SESSION_PARAM_ATTR_DESTINATION_SHORT_ADDR, + /* Timings */ + FIRA_SESSION_PARAM_ATTR_INITIATION_TIME_MS, + FIRA_SESSION_PARAM_ATTR_SLOT_DURATION_RSTU, + FIRA_SESSION_PARAM_ATTR_BLOCK_DURATION_MS, + FIRA_SESSION_PARAM_ATTR_ROUND_DURATION_SLOTS, + FIRA_SESSION_PARAM_ATTR_BLOCK_STRIDE_LENGTH, + /* Behaviour */ + FIRA_SESSION_PARAM_ATTR_MAX_NUMBER_OF_MEASUREMENTS, + FIRA_SESSION_PARAM_ATTR_MAX_RR_RETRY, + FIRA_SESSION_PARAM_ATTR_ROUND_HOPPING, + FIRA_SESSION_PARAM_ATTR_PRIORITY, + FIRA_SESSION_PARAM_ATTR_RESULT_REPORT_PHASE, + FIRA_SESSION_PARAM_ATTR_MR_AT_INITIATOR, + FIRA_SESSION_PARAM_ATTR_EMBEDDED_MODE, + FIRA_SESSION_PARAM_ATTR_IN_BAND_TERMINATION_ATTEMPT_COUNT, + /* Radio */ + FIRA_SESSION_PARAM_ATTR_CHANNEL_NUMBER, + FIRA_SESSION_PARAM_ATTR_PREAMBLE_CODE_INDEX, + FIRA_SESSION_PARAM_ATTR_RFRAME_CONFIG, + FIRA_SESSION_PARAM_ATTR_PRF_MODE, + FIRA_SESSION_PARAM_ATTR_PREAMBLE_DURATION, + FIRA_SESSION_PARAM_ATTR_SFD_ID, + FIRA_SESSION_PARAM_ATTR_NUMBER_OF_STS_SEGMENTS, + FIRA_SESSION_PARAM_ATTR_PSDU_DATA_RATE, + FIRA_SESSION_PARAM_ATTR_BPRF_PHR_DATA_RATE, + FIRA_SESSION_PARAM_ATTR_MAC_FCS_TYPE, + FIRA_SESSION_PARAM_ATTR_TX_ADAPTIVE_PAYLOAD_POWER, + /* Measurement Sequence */ + FIRA_SESSION_PARAM_ATTR_MEASUREMENT_SEQUENCE, + /* STS and crypto */ + FIRA_SESSION_PARAM_ATTR_STS_CONFIG, + FIRA_SESSION_PARAM_ATTR_SUB_SESSION_ID, + FIRA_SESSION_PARAM_ATTR_VUPPER64, + FIRA_SESSION_PARAM_ATTR_SESSION_KEY, + FIRA_SESSION_PARAM_ATTR_SUB_SESSION_KEY, + FIRA_SESSION_PARAM_ATTR_KEY_ROTATION, + FIRA_SESSION_PARAM_ATTR_KEY_ROTATION_RATE, + /* Report */ + FIRA_SESSION_PARAM_ATTR_AOA_RESULT_REQ, + FIRA_SESSION_PARAM_ATTR_REPORT_TOF, + FIRA_SESSION_PARAM_ATTR_REPORT_AOA_AZIMUTH, + FIRA_SESSION_PARAM_ATTR_REPORT_AOA_ELEVATION, + FIRA_SESSION_PARAM_ATTR_REPORT_AOA_FOM, + FIRA_SESSION_PARAM_ATTR_REPORT_RSSI, + /* Custom Data */ + FIRA_SESSION_PARAM_ATTR_DATA_VENDOR_OUI, + FIRA_SESSION_PARAM_ATTR_DATA_PAYLOAD, + /* Diagnostics */ + FIRA_SESSION_PARAM_ATTR_DIAGNOSTICS, + FIRA_SESSION_PARAM_ATTR_DIAGNOSTICS_FRAME_REPORTS_FIELDS, + /* Misc */ + FIRA_SESSION_PARAM_ATTR_STS_LENGTH, + /* Contention-based ranging */ + FIRA_SESSION_PARAM_ATTR_CAP_SIZE_MAX, + FIRA_SESSION_PARAM_ATTR_CAP_SIZE_MIN, + /* Range data notification enable */ + FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_CONFIG, + FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_PROXIMITY_NEAR_MM, + FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_PROXIMITY_FAR_MM, + FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI, + FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI, + FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI, + FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI, + __FIRA_SESSION_PARAM_ATTR_AFTER_LAST, + FIRA_SESSION_PARAM_ATTR_MAX = __FIRA_SESSION_PARAM_ATTR_AFTER_LAST - 1 +}; + +/** + * enum fira_call_controlee_attrs - FiRa controlee parameters attributes. + * + * @FIRA_CALL_CONTROLEE_ATTR_SHORT_ADDR: + * Controlee short address. + * @FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_ID: + * Controlee sub session identifier + * @FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_KEY: + * Controlee sub session key + * + * @FIRA_CALL_CONTROLEE_ATTR_UNSPEC: Invalid command. + * @__FIRA_CALL_CONTROLEE_ATTR_AFTER_LAST: Internal use. + * @FIRA_CALL_CONTROLEE_ATTR_MAX: Internal use. + */ +enum fira_call_controlee_attrs { + FIRA_CALL_CONTROLEE_ATTR_UNSPEC, + FIRA_CALL_CONTROLEE_ATTR_SHORT_ADDR, + FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_ID, + FIRA_CALL_CONTROLEE_ATTR_SUB_SESSION_KEY, + + __FIRA_CALL_CONTROLEE_ATTR_AFTER_LAST, + FIRA_CALL_CONTROLEE_ATTR_MAX = __FIRA_CALL_CONTROLEE_ATTR_AFTER_LAST - 1 +}; + +/** + * enum fira_ranging_data_attrs_stopped_values - Values for + * FIRA_RANGING_DATA_ATTR_STOPPED attribute. + * + * @FIRA_RANGING_DATA_ATTR_STOPPED_REQUEST: + * Stopped due to stop request. + * @FIRA_RANGING_DATA_ATTR_STOPPED_IN_BAND: + * Stopped using in band signaling from the controller [controlee only]. + * @FIRA_RANGING_DATA_ATTR_STOPPED_NO_RESPONSE: + * Stopped due to maximum attempts reached with no response [controller + * only]. + */ +enum fira_ranging_data_attrs_stopped_values { + FIRA_RANGING_DATA_ATTR_STOPPED_REQUEST, + FIRA_RANGING_DATA_ATTR_STOPPED_IN_BAND, + FIRA_RANGING_DATA_ATTR_STOPPED_NO_RESPONSE, +}; + +/** + * enum fira_ranging_data_attrs - FiRa ranging data attributes. + * + * @FIRA_RANGING_DATA_ATTR_STOPPED: + * If present, session was stopped, see + * &enum fira_ranging_data_attrs_stopped_values. + * @FIRA_RANGING_DATA_ATTR_BLOCK_INDEX: + * Current block index. + * @FIRA_RANGING_DATA_ATTR_TIMESTAMP_NS: + * Timestamp in nanoseconds in the CLOCK_MONOTONIC time reference. + * @FIRA_RANGING_DATA_ATTR_RANGING_INTERVAL_MS: + * Current ranging interval (block size * (stride + 1)) in unit of + * 1200 RSTU (same as ms). + * @FIRA_RANGING_DATA_ATTR_MEASUREMENTS: + * Measurements, see fira_ranging_data_measurements. + * + * @FIRA_RANGING_DATA_ATTR_UNSPEC: Invalid command. + * @__FIRA_RANGING_DATA_ATTR_AFTER_LAST: Internal use. + * @FIRA_RANGING_DATA_ATTR_MAX: Internal use. + */ +enum fira_ranging_data_attrs { + FIRA_RANGING_DATA_ATTR_UNSPEC, + FIRA_RANGING_DATA_ATTR_STOPPED, + FIRA_RANGING_DATA_ATTR_BLOCK_INDEX, + FIRA_RANGING_DATA_ATTR_TIMESTAMP_NS, + FIRA_RANGING_DATA_ATTR_RANGING_INTERVAL_MS, + FIRA_RANGING_DATA_ATTR_MEASUREMENTS, + + __FIRA_RANGING_DATA_ATTR_AFTER_LAST, + FIRA_RANGING_DATA_ATTR_MAX = __FIRA_RANGING_DATA_ATTR_AFTER_LAST - 1 +}; + +/** + * enum fira_ranging_data_measurements_attrs - FiRa ranging data measurements + * attributes. + * + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_SHORT_ADDR: + * Address of the participing device. + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_STOPPED: + * If present, ranging was stopped as requested [controller only]. + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_STATUS: + * Status of the measurement (0: success, 1: tx failed, 2: rx timeout, + * 3: rx phy dec error, 4: rx phy toa error, 5: rx phy sts error, + * 6: rx mac dec error, 7: rx mac ie dec error, 8: rx mac ie missing) + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_SLOT_INDEX: + * In case of failure slot index where the error was detected. + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_NLOS: + * Set if in non line of sight. + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_LOS: + * Set if in line of sight. + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_DISTANCE_MM: + * Distance in mm. + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_LOCAL_AOA: + * Local AoA measurement, + * cf. fira_ranging_data_measurements_aoa_attrs. + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_LOCAL_AOA_AZIMUTH: + * Local AoA measurement for azimuth, + * cf. fira_ranging_data_measurements_aoa_attrs. + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_LOCAL_AOA_ELEVATION: + * Local AoA measurement for elevation, + * cf. fira_ranging_data_measurements_aoa_attrs. + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_REMOTE_AOA_AZIMUTH_2PI: + * Estimation of reception angle in the azimuth of the participing device. + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_REMOTE_AOA_ELEVATION_PI: + * Estimation of reception angle in elevation of the participing device. + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_REMOTE_AOA_AZIMUTH_FOM: + * Estimation of azimuth reliability of the participing device. + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_REMOTE_AOA_ELEVATION_FOM: + * Estimation of elevation reliability of the participing device. + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_RSSI: + * RSSI "summary" for received frames during the ranging round, + * reported as Q7.1. Summary method depends on session params + * (average, minimum, etc). + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_DATA_PAYLOAD_SEQ_SENT: + * Sequence number of last data sent + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_DATA_PAYLOAD_RECV: + * Received Data payload in the SP1 RFRAME + * + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_UNSPEC: Invalid command. + * @__FIRA_RANGING_DATA_MEASUREMENTS_ATTR_AFTER_LAST: Internal use. + * @FIRA_RANGING_DATA_MEASUREMENTS_ATTR_MAX: Internal use. + */ +enum fira_ranging_data_measurements_attrs { + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_UNSPEC, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_SHORT_ADDR, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_STOPPED, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_STATUS, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_SLOT_INDEX, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_NLOS, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_LOS, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_DISTANCE_MM, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_LOCAL_AOA, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_LOCAL_AOA_AZIMUTH, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_LOCAL_AOA_ELEVATION, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_REMOTE_AOA_AZIMUTH_2PI, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_REMOTE_AOA_ELEVATION_PI, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_REMOTE_AOA_AZIMUTH_FOM, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_REMOTE_AOA_ELEVATION_FOM, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_RSSI, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_DATA_PAYLOAD_SEQ_SENT, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_DATA_PAYLOAD_RECV, + + __FIRA_RANGING_DATA_MEASUREMENTS_ATTR_AFTER_LAST, + FIRA_RANGING_DATA_MEASUREMENTS_ATTR_MAX = + __FIRA_RANGING_DATA_MEASUREMENTS_ATTR_AFTER_LAST - 1 +}; + +/** + * enum fira_ranging_data_measurements_aoa_attrs - FiRa ranging AoA measurements + * attributes. + * + * @FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_RX_ANTENNA_SET: + * Antenna set index. + * @FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_AOA_2PI: + * Estimation of reception angle. + * @FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_AOA_FOM: + * Estimation of local AoA reliability. + * @FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_PDOA_2PI: + * Estimation of reception phase difference. + * + * @FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_UNSPEC: Invalid command. + * @__FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_AFTER_LAST: Internal use. + * @FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_MAX: Internal use. + */ +enum fira_ranging_data_measurements_aoa_attrs { + FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_UNSPEC, + FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_RX_ANTENNA_SET, + FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_AOA_2PI, + FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_AOA_FOM, + FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_PDOA_2PI, + + __FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_AFTER_LAST, + FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_MAX = + __FIRA_RANGING_DATA_MEASUREMENTS_AOA_ATTR_AFTER_LAST - 1 +}; + +/** + * enum fira_session_param_meas_seq_step_attrs - FiRa measurement sequence + * step attributes. + * + * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_MEASUREMENT_TYPE: + * The type of measurement to perform during the step. + * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_N_MEASUREMENTS: + * The number of times this type of measurement shall be performed + * during the step. + * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_RX_ANT_SET_NONRANGING: + * The antenna set to use to receive the non-rfames during the step. + * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_RX_ANT_SETS_RANGING: + * The antenna set to use to receive the rframes frame during the step. + * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_TX_ANT_SET_NONRANGING: + * The antenna set to use to transmit the non-rframes during the step. + * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_TX_ANT_SET_RANGING: + * The antenna set to use to transmit the rframes during the step. + * + * + * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_UNSPEC: Invalid command. + * @__FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_AFTER_LAST: Internal use. + * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_MAX: Internal use. + */ +enum fira_session_param_meas_seq_step_attrs { + FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_UNSPEC, + FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_MEASUREMENT_TYPE, + FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_N_MEASUREMENTS, + FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_RX_ANT_SET_NONRANGING, + FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_RX_ANT_SETS_RANGING, + FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_TX_ANT_SET_NONRANGING, + FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_TX_ANT_SET_RANGING, + + __FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_AFTER_LAST, + FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_MAX = + __FIRA_SESSION_PARAM_MEAS_SEQ_STEP_ATTR_AFTER_LAST - 1 +}; + +/** + * enum fira_session_params_meas_seq_step_sets_attrs - Attributes of the + * FiRa RX antenna sets to use during a step. + * + * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_0: + * Antenna set used to receive all rframes for range, azimuth and elevation + * steps or initial rframe for azimuth_elevation step. + * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_1: + * Antenna set used to receive final rframes for azimuth_elevation step. + * + * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_UNSPEC: + * Invalid command. + * @FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_MAX: + * Internal use. + * @__FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_AFTER_LAST: + * Internal use. + */ +enum fira_session_params_meas_seq_step_sets_attrs { + FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_UNSPEC, + FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_0, + FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_1, + + __FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_AFTER_LAST, + FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_MAX = + __FIRA_SESSION_PARAM_MEAS_SEQ_STEP_RX_ANT_SETS_RANGING_ATTR_AFTER_LAST - + 1 +}; + +/** + * enum fira_ranging_diagnostics_attrs - FiRa ranging diagnostic attributes. + * + * @FIRA_RANGING_DIAGNOSTICS_ATTR_FRAME_REPORTS: + * Diagnostics for individual frames of the round. + * + * @FIRA_RANGING_DIAGNOSTICS_ATTR_UNSPEC: Invalid command. + * @__FIRA_RANGING_DIAGNOSTICS_ATTR_AFTER_LAST : Internal use. + * @FIRA_RANGING_DIAGNOSTICS_ATTR_MAX : Internal use. + */ +enum fira_ranging_diagnostics_attrs { + FIRA_RANGING_DIAGNOSTICS_ATTR_UNSPEC, + FIRA_RANGING_DIAGNOSTICS_ATTR_FRAME_REPORTS, + + __FIRA_RANGING_DIAGNOSTICS_ATTR_AFTER_LAST, + FIRA_RANGING_DIAGNOSTICS_ATTR_MAX = + __FIRA_RANGING_DIAGNOSTICS_ATTR_AFTER_LAST - 1 +}; + +/** + * enum fira_ranging_diagnostics_frame_reports_attrs - FiRa ranging + * diagnostic info for individual frames. + * + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_ANT_SET: + * Antenna set ID, used for the frame transmission. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_ACTION: + * Action type of the frame (0: TX or 1: RX). + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_MSG_ID: + * FiRa message ID (0: RIM, 1: RRM, 2: RFM, 3: CM, + * 4: MRM, 5: RRRM, 6: CU). + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_RSSIS: + * RSSI for the current (Rx) frame, reported as a Q7.1. + * As many values as receivers. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_AOAS: + * Nested attribute reporting different AoA related information. + * As many as AoA types. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_CIRS: + * Nested attribute reporting CIR sample window information. + * As many array elements as receivers. + * + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_UNSPEC: Invalid command. + * @__FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_AFTER_LAST: Internal use. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_MAX: Internal use. + */ +enum fira_ranging_diagnostics_frame_reports_attrs { + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_UNSPEC, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_ANT_SET, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_ACTION, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_MSG_ID, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_RSSIS, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_AOAS, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_CIRS, + + __FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_AFTER_LAST, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_MAX = + __FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_ATTR_AFTER_LAST - 1 +}; + +/** + * enum fira_ranging_diagnostics_frame_reports_aoa_attrs - AoA diagnostic + * information per frame + * + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_TDOA: + * TDoA in rctu, reported as s16. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_PDOA: + * PDoA in radians, reported as Q5.11. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_AOA: + * AoA in radians, reported as Q5.11. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_FOM: + * AoA FoM between 0 and 255 (0 being an invalid measure and 255 being + * a 100% confidence measure), reported as u8. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_TYPE: + * AoA Measurement type (azimuth, elevation...), reported as u8. + * + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_UNSPEC: Invalid command. + * @__FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_AFTER_LAST: Internal use. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_MAX: Internal use. + */ +enum fira_ranging_diagnostics_frame_reports_aoa_attrs { + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_UNSPEC, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_TDOA, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_PDOA, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_AOA, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_FOM, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_TYPE, + + __FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_AFTER_LAST, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_MAX = + __FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_AOAS_ATTR_AFTER_LAST - + 1 +}; + +/** + * enum fira_ranging_diagnostics_frame_reports_cir_attrs - CIR diagnostic + * information per frame + * + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_IDX: + * Absolute index of the sample considered as first path, reported as u16. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_SNR: + * SNR of the sample considered as first path, reported as s16. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_NS: + * Timestamp of the sample considered as first path, reported as u16. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_PP_IDX: + * Absolute index of the sample considered as peak path, reported as u16. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_PP_SNR: + * SNR of the sample considered as peak path, reported as s16. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_PP_NS: + * Timestamp of the sample considered as peak path, reported as u16. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_SAMPLE_OFFSET: + * Offset of the first path in the sample window, reported as u16. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_SAMPLE_WINDOW: + * Sliding window containing CIR samples, each sample is considered as + * a byte sequence depending on sample size. + * As many samples as the window size. + * + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_UNSPEC: Invalid command. + * @__FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_AFTER_LAST: Internal use. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_MAX: Internal use. + */ +enum fira_ranging_diagnostics_frame_reports_cir_attrs { + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_UNSPEC, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_IDX, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_SNR, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_NS, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_PP_IDX, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_PP_SNR, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_PP_NS, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_SAMPLE_OFFSET, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_FP_SAMPLE_WINDOW, + + __FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_AFTER_LAST, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_MAX = + __FIRA_RANGING_DIAGNOSTICS_FRAME_REPORTS_CIRS_ATTR_AFTER_LAST - + 1 +}; + +#endif /* FIRA_REGION_NL_H */ diff --git a/mac/include/net/fira_region_params.h b/mac/include/net/fira_region_params.h new file mode 100644 index 0000000..cf98038 --- /dev/null +++ b/mac/include/net/fira_region_params.h @@ -0,0 +1,417 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_FIRA_REGION_PARAMS_H +#define NET_FIRA_REGION_PARAMS_H + +#include <linux/types.h> + +#define FIRA_VUPPER64_SIZE 8 +#define FIRA_STS_VUPPER64_OFFSET 8 +#define FIRA_KEY_SIZE_MAX 16 +#define FIRA_KEY_SIZE_MIN 16 +#define FIRA_CONTROLEES_MAX 8 +#define FIRA_RX_ANTENNA_PAIR_INVALID 0xff +/* + * In BPRF, frame is at most 127 + * 127 - (MHR + HIE + HT + PIE_Header + V_OUI + MIC + CRC) + */ +#define FIRA_DATA_PAYLOAD_SIZE_MAX 84 + +/* From UCI spec v1.1.0 (converted to mm). + * Need a 2mm margin to avoid errors when converting to and from RCTU */ +#define FIRA_RANGE_DATA_NTF_PROXIMITY_FAR_MM_DEFAULT 200002 + +/* + * FIRA_SESSION_DATA_NTF_LOWER_/UPPER_BOUND_AOA default values : + * Azimuth in rad_2pi_q16 : -32768 / 32767 (equal to -180 / ~180 degrees) + * Elevation in rad_2pi_q16 : -16384 / 16384 (equal to -90 / 90 degrees) + */ +#define FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_AZIMUTH_2PI_DEFAULT -32768 +#define FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_AZIMUTH_2PI_DEFAULT 32767 +#define FIRA_SESSION_DATA_NTF_LOWER_BOUND_AOA_ELEVATION_2PI_DEFAULT -16384 +#define FIRA_SESSION_DATA_NTF_UPPER_BOUND_AOA_ELEVATION_2PI_DEFAULT 16384 + +/** + * enum fira_device_type - Type of a device. + * @FIRA_DEVICE_TYPE_CONTROLEE: The device is a controlee. + * @FIRA_DEVICE_TYPE_CONTROLLER: The device is a controller. + */ +enum fira_device_type { + FIRA_DEVICE_TYPE_CONTROLEE, + FIRA_DEVICE_TYPE_CONTROLLER, +}; + +/** + * enum fira_device_role - **[NOT IMPLEMENTED]** Role played by a device. + * @FIRA_DEVICE_ROLE_RESPONDER: The device acts as a responder. + * @FIRA_DEVICE_ROLE_INITIATOR: The device acts as an initiator. + * + * Current implementation does not support decorrelation between the + * device's role and the device's type. The controller is always + * the initiator and the controlee is always the responder. + * + * This enum is not used in the current implementation. + */ +enum fira_device_role { + FIRA_DEVICE_ROLE_RESPONDER, + FIRA_DEVICE_ROLE_INITIATOR, +}; + +/** + * enum fira_ranging_round_usage - Ranging mode. + * @FIRA_RANGING_ROUND_USAGE_OWR: One Way Ranging mode (unused, not in FiRa 1.1). + * @FIRA_RANGING_ROUND_USAGE_SSTWR: Single-Sided Two Way Ranging mode. + * @FIRA_RANGING_ROUND_USAGE_DSTWR: Dual-Sided Two Way Ranging mode. + */ +enum fira_ranging_round_usage { + FIRA_RANGING_ROUND_USAGE_OWR, + FIRA_RANGING_ROUND_USAGE_SSTWR, + FIRA_RANGING_ROUND_USAGE_DSTWR, +}; + +/** + * enum fira_multi_node_mode - Multi-node mode. + * @FIRA_MULTI_NODE_MODE_UNICAST: Ranging between one initiator and one + * responder. + * @FIRA_MULTI_NODE_MODE_ONE_TO_MANY: Ranging between one initiator and + * multiple responders. + * @FIRA_MULTI_NODE_MODE_MANY_TO_MANY: Ranging between multiple initiators + * and multiple responders. + */ +enum fira_multi_node_mode { + FIRA_MULTI_NODE_MODE_UNICAST, + FIRA_MULTI_NODE_MODE_ONE_TO_MANY, + FIRA_MULTI_NODE_MODE_MANY_TO_MANY, +}; + +/** + * enum fira_measurement_report - **[NOT IMPLEMENTED]** Transmission of a + * Ranging Measurement Report Message (MRM) option. + * @FIRA_MEASUREMENT_REPORT_AT_RESPONDER: The initiator emits a MRM. + * @FIRA_MEASUREMENT_REPORT_AT_INITIATOR: The responder emits a MRM. + * + * In the current implementation measurement report is always available at + * responder. + * + * This enum is not used in the current implementation. + */ +enum fira_measurement_report { + FIRA_MEASUREMENT_REPORT_AT_RESPONDER, + FIRA_MEASUREMENT_REPORT_AT_INITIATOR, +}; + +/** + * enum fira_embedded_mode - **[NOT IMPLEMENTED]** Message embedding + * behaviour. + * @FIRA_EMBEDDED_MODE_DEFERRED: Ranging messages do not embed control messages. + * Additional messages are required. + * @FIRA_EMBEDDED_MODE_NON_DEFERRED: Ranging messages embed control messages. + * + * The current implementation only supports deferred mode. + * + * This enum is not used in the current implementation. + */ +enum fira_embedded_mode { + FIRA_EMBEDDED_MODE_DEFERRED, + FIRA_EMBEDDED_MODE_NON_DEFERRED, +}; + +/** + * enum fira_rframe_config - Rframe configuration used to transmit/receive + * ranging messages. + * @FIRA_RFRAME_CONFIG_SP0: Use SP0 mode. + * @FIRA_RFRAME_CONFIG_SP1: Use SP1 mode. + * @FIRA_RFRAME_CONFIG_SP2: RFU + * @FIRA_RFRAME_CONFIG_SP3: Use SP3 mode. + */ +enum fira_rframe_config { + FIRA_RFRAME_CONFIG_SP0, + FIRA_RFRAME_CONFIG_SP1, + FIRA_RFRAME_CONFIG_SP2, + FIRA_RFRAME_CONFIG_SP3, +}; + +/** + * enum fira_prf_mode - Pulse Repetition Frequency mode + * @FIRA_PRF_MODE_BPRF: Base Pulse Repetition Frequency. + * @FIRA_PRF_MODE_HPRF: Higher Pulse Repetition Frequency. + * @FIRA_PRF_MODE_HPRF_HIGH_RATE: Higher Pulse Repetition Frequency allows + * high data rate (27.2 Mbps and 31.2 Mbps). + * + * This enum is not used in the current implementation. + */ +enum fira_prf_mode { + FIRA_PRF_MODE_BPRF, + FIRA_PRF_MODE_HPRF, + FIRA_PRF_MODE_HPRF_HIGH_RATE, +}; + +/** + * enum fira_preambule_duration - Duration of preamble in symbols. + * @FIRA_PREAMBULE_DURATION_32: 32 symbols duration. + * @FIRA_PREAMBULE_DURATION_64: 64 symbols duration. + */ +enum fira_preambule_duration { + FIRA_PREAMBULE_DURATION_32, + FIRA_PREAMBULE_DURATION_64, +}; + +/** + * enum fira_sfd_id - Start-of-frame delimiter. + * @FIRA_SFD_ID_0: Delimiter is [0 +1 0 –1 +1 0 0 –1] + * @FIRA_SFD_ID_1: Delimiter is [ –1 –1 +1 –1 ] + * @FIRA_SFD_ID_2: Delimiter is [ –1 –1 –1 +1 –1 –1 +1 –1 ] + * @FIRA_SFD_ID_3: Delimiter is + * [ –1 –1 –1 –1 –1 +1 +1 –1 –1 +1 –1 +1 –1 –1 +1 –1 ] + * @FIRA_SFD_ID_4: Delimiter is + * [ –1 –1 –1 –1 –1 –1 –1 +1 –1 –1 +1 –1 –1 +1 –1 +1 –1 +1 + * –1 –1 –1 +1 +1 –1 –1 –1 +1 –1 +1 +1 –1 –1 ] + */ +enum fira_sfd_id { + FIRA_SFD_ID_0, + FIRA_SFD_ID_1, + FIRA_SFD_ID_2, + FIRA_SFD_ID_3, + FIRA_SFD_ID_4, +}; + +/** + * enum fira_sts_segments - Number of STS segments. + * @FIRA_STS_SEGMENTS_0: No STS Segment (Rframe config SP0). + * @FIRA_STS_SEGMENTS_1: 1 STS Segment. + * @FIRA_STS_SEGMENTS_2: 2 STS Segments. + * @FIRA_STS_SEGMENTS_3: 3 STS Segments. + * @FIRA_STS_SEGMENTS_4: 4 STS Segments. + */ +enum fira_sts_segments { + FIRA_STS_SEGMENTS_0, + FIRA_STS_SEGMENTS_1, + FIRA_STS_SEGMENTS_2, + FIRA_STS_SEGMENTS_3, + FIRA_STS_SEGMENTS_4, +}; + +/** + * enum fira_psdu_data_rate - Data rate used to exchange PSDUs. + * @FIRA_PSDU_DATA_RATE_6M81: 6.8Mb/s rate. + * @FIRA_PSDU_DATA_RATE_7M80: 7.8Mb/s rate. + * @FIRA_PSDU_DATA_RATE_27M2: 27.2Mb/s rate. + * @FIRA_PSDU_DATA_RATE_31M2: 31.2Mb/s rate. + */ +enum fira_psdu_data_rate { + FIRA_PSDU_DATA_RATE_6M81, + FIRA_PSDU_DATA_RATE_7M80, + FIRA_PSDU_DATA_RATE_27M2, + FIRA_PSDU_DATA_RATE_31M2, +}; + +/** + * enum fira_phr_data_rate - Data rate used to exchange PHR. + * @FIRA_PHR_DATA_RATE_850K: 850kb/s rate. + * @FIRA_PHR_DATA_RATE_6M81: 6.8Mb/s rate. + * + * This enum is not used in the current implementation. + */ +enum fira_phr_data_rate { + FIRA_PHR_DATA_RATE_850K, + FIRA_PHR_DATA_RATE_6M81, +}; + +/** + * enum fira_mac_fcs_type - Length of Frame Check Sequence. + * @FIRA_MAC_FCS_TYPE_CRC_16: 2 bytes sequence. + * @FIRA_MAC_FCS_TYPE_CRC_32: 4 bytes sequence. + */ +enum fira_mac_fcs_type { + FIRA_MAC_FCS_TYPE_CRC_16, + FIRA_MAC_FCS_TYPE_CRC_32, +}; + +/** + * enum fira_rssi_report_type - Mode used to sum up individual frames RSSI + * in report. + * @FIRA_RSSI_REPORT_NONE: No RSSI value in report. + * @FIRA_RSSI_REPORT_MINIMUM: Report minimum RSSI + * @FIRA_RSSI_REPORT_AVERAGE: Report average RSSI + */ +enum fira_rssi_report_type { + FIRA_RSSI_REPORT_NONE, + FIRA_RSSI_REPORT_MINIMUM, + FIRA_RSSI_REPORT_AVERAGE, +}; + +/** + * enum fira_sts_mode - Scrambled Timestamp Sequence modes. + * + * @FIRA_STS_MODE_STATIC: Static STS mode. + * @FIRA_STS_MODE_DYNAMIC: Use a dynamic STS mode. + * @FIRA_STS_MODE_DYNAMIC_INDIVIDUAL_KEY: Use a dynamic STS mode + * with individual controlee key. + * @FIRA_STS_MODE_PROVISIONED: Use a provisioned STS mode. + * @FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY: Use a provisioned STS + * mode with individual controlee key. + */ +enum fira_sts_mode { + FIRA_STS_MODE_STATIC = 0, + FIRA_STS_MODE_DYNAMIC = 1, + FIRA_STS_MODE_DYNAMIC_INDIVIDUAL_KEY = 2, + FIRA_STS_MODE_PROVISIONED = 3, + FIRA_STS_MODE_PROVISIONED_INDIVIDUAL_KEY = 4, +}; + +/* + * Get the capabilities bitfield value corresponding to given STS mode. + */ +#define STS_CAP(mode) (1 << (FIRA_STS_MODE_##mode)) + +/** + * enum fira_ranging_status - The ranging status. + * @FIRA_STATUS_RANGING_INTERNAL_ERROR: Implementation specific error. + * @FIRA_STATUS_RANGING_SUCCESS: Ranging info are valid. + * @FIRA_STATUS_RANGING_TX_FAILED: Failed to transmit UWB packet. + * @FIRA_STATUS_RANGING_RX_TIMEOUT: No UWB packet detected by the receiver. + * @FIRA_STATUS_RANGING_RX_PHY_DEC_FAILED: UWB packet channel decoding error. + * @FIRA_STATUS_RANGING_RX_PHY_TOA_FAILED: Failed to detect time of arrival of + * the UWB packet from CIR samples. + * @FIRA_STATUS_RANGING_RX_PHY_STS_FAILED: UWB packet STS segment mismatch. + * @FIRA_STATUS_RANGING_RX_MAC_DEC_FAILED: MAC CRC or syntax error. + * @FIRA_STATUS_RANGING_RX_MAC_IE_DEC_FAILED: IE syntax error. + * @FIRA_STATUS_RANGING_RX_MAC_IE_MISSING: Expected IE missing in the packet. + */ +enum fira_ranging_status { + FIRA_STATUS_RANGING_INTERNAL_ERROR = -1, + FIRA_STATUS_RANGING_SUCCESS = 0, + FIRA_STATUS_RANGING_TX_FAILED = 1, + FIRA_STATUS_RANGING_RX_TIMEOUT = 2, + FIRA_STATUS_RANGING_RX_PHY_DEC_FAILED = 3, + FIRA_STATUS_RANGING_RX_PHY_TOA_FAILED = 4, + FIRA_STATUS_RANGING_RX_PHY_STS_FAILED = 5, + FIRA_STATUS_RANGING_RX_MAC_DEC_FAILED = 6, + FIRA_STATUS_RANGING_RX_MAC_IE_DEC_FAILED = 7, + FIRA_STATUS_RANGING_RX_MAC_IE_MISSING = 8, +}; + +/** + * enum fira_session_state - Session state. + * @FIRA_SESSION_STATE_INIT: Initial state, session is not ready yet. + * @FIRA_SESSION_STATE_DEINIT: Session does not exist. + * @FIRA_SESSION_STATE_ACTIVE: Session is currently active. + * @FIRA_SESSION_STATE_IDLE: Session is ready to start, but not currently + * active. + */ +enum fira_session_state { + FIRA_SESSION_STATE_INIT, + FIRA_SESSION_STATE_DEINIT, + FIRA_SESSION_STATE_ACTIVE, + FIRA_SESSION_STATE_IDLE, +}; + +/* + * The maximum number of steps a measurement sequence can contain. + */ +#define FIRA_MEASUREMENT_SEQUENCE_STEP_MAX 10 + +/** + * enum fira_measurement_type - The different type of available measurements. + * @FIRA_MEASUREMENT_TYPE_RANGE: Measure only range. + * @FIRA_MEASUREMENT_TYPE_AOA: Measure range + unspecified AoA. + * @FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH: Measure range + azimuth. + * @FIRA_MEASUREMENT_TYPE_AOA_ELEVATION: Measure range + elevation. + * @FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH_ELEVATION: Measure range+azimuth+elevation. + * @__FIRA_MEASUREMENT_TYPE_AFTER_LAST: Internal use. + */ +enum fira_measurement_type { + FIRA_MEASUREMENT_TYPE_RANGE = 0, + FIRA_MEASUREMENT_TYPE_AOA, + FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH, + FIRA_MEASUREMENT_TYPE_AOA_ELEVATION, + FIRA_MEASUREMENT_TYPE_AOA_AZIMUTH_ELEVATION, + __FIRA_MEASUREMENT_TYPE_AFTER_LAST, +}; + +/** + * enum fira_ranging_diagnostics_frame_report_flags - Activation flags for different frame diagnostics information. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_NONE: No specific frame diagnostic report requested. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_RSSIS: Report RSSI in frame diagnostics. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_AOAS: Report AOA in frame diagnostics. + * @FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_CIRS: Report CIR in frame diagnostics. + * @__FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_AFTER_LAST: Internal use. + */ +enum fira_ranging_diagnostics_frame_report_flags { + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_NONE = 0, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_RSSIS = 1 << 0, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_AOAS = 1 << 1, + FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_CIRS = 1 << 2, + __FIRA_RANGING_DIAGNOSTICS_FRAME_REPORT_AFTER_LAST = 1 << 31, +}; + +/** + * enum fira_sts_length - Number of symbols in a STS segment. + * @FIRA_STS_LENGTH_32: The STS length is 32 symbols. + * @FIRA_STS_LENGTH_64: The STS length is 64 symbols. + * @FIRA_STS_LENGTH_128: The STS length is 128 symbols. + */ +enum fira_sts_length { + FIRA_STS_LENGTH_32 = 0, + FIRA_STS_LENGTH_64 = 1, + FIRA_STS_LENGTH_128 = 2, +}; + +/** + * enum fira_range_data_ntf_config - Configure range data notification. + * @FIRA_RANGE_DATA_NTF_DISABLED: Do not report range data. + * @FIRA_RANGE_DATA_NTF_ALWAYS: Report range data. + * @FIRA_RANGE_DATA_NTF_CONFIG_PROXIMITY: Report range data if it is within + * proximity range defined by proximity parameters + * defined by proximity parameters (RANGE_DATA_NTF_PROXIMITY_NEAR/FAR). + * @FIRA_RANGE_DATA_NTF_CONFIG_AOA: Report range data in AoA upper and lower bound. + * defined by AOA parameters (FIRA_SESSION_PARAM_ATTR_RANGE_DATA_NTF_UPPER/ + * LOWER_BOUND_AOA_AZIMUTH/ELEVATION) + * @FIRA_RANGE_DATA_NTF_CONFIG_PROXIMITY_AND_AOA: Report range data in AoA upper + * and lower bound as well as in proximity range. + * @FIRA_RANGE_DATA_NTF_CONFIG_PROXIMITY_CROSSING: Same as + * FIRA_RANGE_DATA_NTF_CONFIG_PROXIMITY, but issues notification on crossing of + * boundaries. As for now, same notif is sent for "enter" and "exit" events. + * @FIRA_RANGE_DATA_NTF_CONFIG_AOA_CROSSING: Same as + * FIRA_RANGE_DATA_NTF_CONFIG_AOA, but issues notification on crossing of boundaries. + * As for now, same notif is sent for "enter" and "exit" events. + * @FIRA_RANGE_DATA_NTF_CONFIG_PROXIMITY_AND_AOA_CROSSING: Same as + * FIRA_RANGE_DATA_NTF_CONFIG_PROXIMITY_AND_AOA, but issues notification on crossing of + * As for now, same notif is sent for "enter" and "exit" events. + * @FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA_CROSSING: Same as + * FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA, but issues notification on crossing of + * boundaries. As for now, same notif is sent for "enter" and "exit" events. + */ +enum fira_range_data_ntf_config { + FIRA_RANGE_DATA_NTF_DISABLED = 0x00, + FIRA_RANGE_DATA_NTF_ALWAYS = 0x01, + FIRA_RANGE_DATA_NTF_PROXIMITY = 0x02, + FIRA_RANGE_DATA_NTF_AOA = 0x03, + FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA = 0x04, + FIRA_RANGE_DATA_NTF_PROXIMITY_CROSSING = 0x05, + FIRA_RANGE_DATA_NTF_AOA_CROSSING = 0x06, + FIRA_RANGE_DATA_NTF_PROXIMITY_AND_AOA_CROSSING = 0x07, +}; + +#endif /* NET_FIRA_REGION_PARAMS_H */ diff --git a/mac/include/net/idle_region_nl.h b/mac/include/net/idle_region_nl.h new file mode 100644 index 0000000..03a7e05 --- /dev/null +++ b/mac/include/net/idle_region_nl.h @@ -0,0 +1,49 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef IDLE_REGION_NL_H +#define IDLE_REGION_NL_H + +/** + * enum idle_param_attrs - Idle parameters attributes. + * + * @IDLE_PARAM_ATTR_MIN_DURATION_DTU: + * Minimum duration of an access. + * @IDLE_PARAM_ATTR_MAX_DURATION_DTU: + * Maximum duration of an access. + * + * @IDLE_PARAM_ATTR_UNSPEC: Invalid command. + * @__IDLE_PARAM_ATTR_AFTER_LAST: Internal use. + * @IDLE_PARAM_ATTR_MAX: Internal use. + */ +enum idle_param_attrs { + IDLE_PARAM_ATTR_UNSPEC, + + IDLE_PARAM_ATTR_MIN_DURATION_DTU, + IDLE_PARAM_ATTR_MAX_DURATION_DTU, + + __IDLE_PARAM_ATTR_AFTER_LAST, + IDLE_PARAM_ATTR_MAX = __IDLE_PARAM_ATTR_AFTER_LAST - 1 +}; + +#endif /* IDLE_REGION_NL_H */ diff --git a/mac/include/net/mcps802154.h b/mac/include/net/mcps802154.h new file mode 100644 index 0000000..77ef002 --- /dev/null +++ b/mac/include/net/mcps802154.h @@ -0,0 +1,1487 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_H +#define NET_MCPS802154_H + +#include <net/mac802154.h> +#include <crypto/aes.h> + +/* Antennas set id to use for transmission by default. */ +#define TX_ANT_SET_ID_DEFAULT 0 +/* Antennas set id to use for reception by default. */ +#define RX_ANT_SET_ID_DEFAULT 0 + +/** Maximum number of STS segments. */ +#define MCPS802154_STS_N_SEGS_MAX 4 + +/** Maximum number of RSSI values. */ +#define MCPS802154_RSSIS_N_MAX 2 + +/** Maximum number of angle of arrival measurements. */ +#define MCPS802154_RX_AOA_MEASUREMENTS_MAX 3 + +/** + * struct mcps802154_channel - Channel parameters. + */ +struct mcps802154_channel { + /** + * @page: Channel page used in conjunction with channel to uniquely + * identify the channel. + */ + int page; + /** + * @channel: RF channel to use for all transmissions and receptions. + */ + int channel; + /** + * @preamble_code: Preamble code index for HRP UWB. Must be zero for + * other PHYs. + */ + int preamble_code; +}; + +/** + * enum mcps802154_llhw_flags - Low-level hardware without MCPS flags. + * @MCPS802154_LLHW_RDEV: + * Support for ranging (RDEV). TODO: move to &ieee802154_hw. + * @MCPS802154_LLHW_ERDEV: + * Support for enhanced ranging (ERDEV). TODO: move to &ieee802154_hw. + * @MCPS802154_LLHW_BPRF: + * Support for BPRF. + * @MCPS802154_LLHW_HPRF: + * Support for HPRF. + * @MCPS802154_LLHW_DATA_RATE_850K: + * Support for data rate 110 kpbs. + * @MCPS802154_LLHW_DATA_RATE_6M81: + * Support for data rate 6.81 Mpbs. + * @MCPS802154_LLHW_DATA_RATE_7M80: + * Support for data rate 7.8 Mpbs. + * @MCPS802154_LLHW_DATA_RATE_27M2: + * Support for data rate 27.2 Mpbs. + * @MCPS802154_LLHW_DATA_RATE_31M2: + * Support for data rate 31.2 Mpbs. + * @MCPS802154_LLHW_DATA_RATE_CUSTOM: + * Support for custom data rate, When presents extra data rate are + * possible to set. + * @MCPS802154_LLHW_PHR_DATA_RATE_850K: + * Support PHR data rate 850 kpbs. + * @MCPS802154_LLHW_PHR_DATA_RATE_6M81: + * Support PHR data rate 6.81 Mpbs. + * @MCPS802154_LLHW_PRF_4: + * Support Pulse Repetition Frequency 4 MHz. + * @MCPS802154_LLHW_PRF_16: + * Support Pulse Repetition Frequency 16 MHz. + * @MCPS802154_LLHW_PRF_64: + * Support Pulse Repetition Frequency 64 MHz. + * @MCPS802154_LLHW_PRF_125: + * Support Pulse Repetition Frequency 125 MHz. + * @MCPS802154_LLHW_PRF_250: + * Support Pulse Repetition Frequency 250 MHz. + * @MCPS802154_LLHW_PSR_16: + * Support 16 symbols in preamble symbol repetitions. + * @MCPS802154_LLHW_PSR_24: + * Support 24 symbols in preamble symbol repetitions. + * @MCPS802154_LLHW_PSR_32: + * Support 32 symbols in preamble symbol repetitions. + * @MCPS802154_LLHW_PSR_48: + * Support 48 symbols in preamble symbol repetitions. + * @MCPS802154_LLHW_PSR_64: + * Support 64 symbols in preamble symbol repetitions. + * @MCPS802154_LLHW_PSR_96: + * Support 96 symbols in preamble symbol repetitions. + * @MCPS802154_LLHW_PSR_128: + * Support 128 symbols in preamble symbol repetitions. + * @MCPS802154_LLHW_PSR_256: + * Support 256 symbols in preamble symbol repetitions. + * @MCPS802154_LLHW_PSR_1024: + * Support 1024 symbols in preamble symbol repetitions. + * @MCPS802154_LLHW_PSR_4096: + * Support 4096 symbols in preamble symbol repetitions. + * @MCPS802154_LLHW_SFD_4A: + * Support SFD defined in 4a. + * @MCPS802154_LLHW_SFD_4Z_4: + * Support SFD defined in 4z with length of 4 symbols. + * @MCPS802154_LLHW_SFD_4Z_8: + * Support SFD defined in 4z with length of 8 symbols. + * @MCPS802154_LLHW_SFD_4Z_16: + * Support SFD defined in 4z with length of 16 symbols. + * @MCPS802154_LLHW_SFD_4Z_32: + * Support SFD defined in 4z with length of 32 symbols. + * @MCPS802154_LLHW_STS_SEGMENT_1: + * Support one STS segment. + * @MCPS802154_LLHW_STS_SEGMENT_2: + * Support two STS segments. + * @MCPS802154_LLHW_STS_SEGMENT_3: + * Support three STS segments. + * @MCPS802154_LLHW_STS_SEGMENT_4: + * Support four STS segments. + * @MCPS802154_LLHW_AOA_AZIMUTH: + * Support AOA azimuth [-90°,+90°]. + * @MCPS802154_LLHW_AOA_AZIMUTH_FULL: + * Support AOA full azimuth [-180°,+180°]. + * @MCPS802154_LLHW_AOA_ELEVATION: + * Support AOA elevation [-90°,+90°]. + * @MCPS802154_LLHW_AOA_FOM: + * Support AOA figure of merit. + */ +enum mcps802154_llhw_flags { + MCPS802154_LLHW_RDEV = BIT(0), + MCPS802154_LLHW_ERDEV = BIT(1), + MCPS802154_LLHW_BPRF = BIT(2), + MCPS802154_LLHW_HPRF = BIT(3), + MCPS802154_LLHW_DATA_RATE_850K = BIT(4), + MCPS802154_LLHW_DATA_RATE_6M81 = BIT(5), + MCPS802154_LLHW_DATA_RATE_7M80 = BIT(6), + MCPS802154_LLHW_DATA_RATE_27M2 = BIT(7), + MCPS802154_LLHW_DATA_RATE_31M2 = BIT(8), + MCPS802154_LLHW_DATA_RATE_CUSTOM = BIT(9), + MCPS802154_LLHW_PHR_DATA_RATE_850K = BIT(10), + MCPS802154_LLHW_PHR_DATA_RATE_6M81 = BIT(11), + MCPS802154_LLHW_PRF_4 = BIT(12), + MCPS802154_LLHW_PRF_16 = BIT(13), + MCPS802154_LLHW_PRF_64 = BIT(14), + MCPS802154_LLHW_PRF_125 = BIT(15), + MCPS802154_LLHW_PRF_250 = BIT(16), + MCPS802154_LLHW_PSR_16 = BIT(17), + MCPS802154_LLHW_PSR_24 = BIT(18), + MCPS802154_LLHW_PSR_32 = BIT(19), + MCPS802154_LLHW_PSR_48 = BIT(20), + MCPS802154_LLHW_PSR_64 = BIT(21), + MCPS802154_LLHW_PSR_96 = BIT(22), + MCPS802154_LLHW_PSR_128 = BIT(23), + MCPS802154_LLHW_PSR_256 = BIT(24), + MCPS802154_LLHW_PSR_1024 = BIT(25), + MCPS802154_LLHW_PSR_4096 = BIT(26), + MCPS802154_LLHW_SFD_4A = BIT(27), + MCPS802154_LLHW_SFD_4Z_4 = BIT(28), + MCPS802154_LLHW_SFD_4Z_8 = BIT(29), + MCPS802154_LLHW_SFD_4Z_16 = BIT(30), + MCPS802154_LLHW_SFD_4Z_32 = BIT(31), + MCPS802154_LLHW_STS_SEGMENT_1 = BIT_ULL(32), + MCPS802154_LLHW_STS_SEGMENT_2 = BIT_ULL(33), + MCPS802154_LLHW_STS_SEGMENT_3 = BIT_ULL(34), + MCPS802154_LLHW_STS_SEGMENT_4 = BIT_ULL(35), + MCPS802154_LLHW_AOA_AZIMUTH = BIT_ULL(36), + MCPS802154_LLHW_AOA_AZIMUTH_FULL = BIT_ULL(37), + MCPS802154_LLHW_AOA_ELEVATION = BIT_ULL(38), + MCPS802154_LLHW_AOA_FOM = BIT_ULL(39), +}; + +/** + * struct mcps802154_llhw - Low-level hardware without MCPS. + * + * This must be allocated with mcps802154_alloc_llhw(). The low-level driver + * should then initialize it. + */ +struct mcps802154_llhw { + /** + * @dtu_freq_hz: Inverse of device time unit duration, in Hz. + */ + int dtu_freq_hz; + /** + * @symbol_dtu: Symbol duration in device time unit, can change if radio + * parameters are changed. Can be set to 1 if device time unit is the + * symbol. + */ + int symbol_dtu; + /** + * @cca_dtu: CCA duration in device time unit, can change if radio + * parameters or CCA modes are changed. + */ + int cca_dtu; + /** + * @shr_dtu: Synchronisation header duration in device time unit, can + * change if radio parameters are changed. If ranging is supported, this + * is the difference between the RMARKER and the first frame symbol. + */ + int shr_dtu; + /** + * @dtu_rctu: Duration of one device time unit in ranging counter time + * unit (RDEV only). + */ + int dtu_rctu; + /** + * @rstu_dtu: Duration of ranging slot time unit in device time unit + * (ERDEV only). + */ + int rstu_dtu; + /** + * @anticip_dtu: Reasonable delay between reading the current timestamp + * and doing an operation in device time unit. + */ + int anticip_dtu; + /** + * @idle_dtu: Duration long enough to prefer entering the idle mode + * rather than trying to find a valid access. + */ + int idle_dtu; + /** + * @current_preamble_code: Current value of preamble code index for HRP + * UWB. Must be zero for other PHYs. + */ + int current_preamble_code; + /** + * @rx_antenna_pairs: Number of antenna pairs for RX. + */ + u32 rx_antenna_pairs; + /** + * @tx_antennas: Number of antennas for TX. + */ + u32 tx_antennas; + /** + * @flags: Low-level hardware flags, see &enum mcps802154_llhw_flags. + */ + u64 flags; + /** + * @hw: Pointer to IEEE802154 hardware exposed by MCPS. The low-level + * driver needs to update this and hw->phy according to supported + * hardware features and radio parameters. More specifically: + * + * * &ieee802154_hw.extra_tx_headroom + * * in &ieee802154_hw.flags: + * + * * IEEE802154_HW_TX_OMIT_CKSUM + * * IEEE802154_HW_RX_OMIT_CKSUM + * * IEEE802154_HW_RX_DROP_BAD_CKSUM + * + * * &wpan_phy.flags + * * &wpan_phy_supported + * * &wpan_phy.symbol_duration + */ + struct ieee802154_hw *hw; + /** + * @priv: Driver private data. + */ + void *priv; + /** + * @rx_ctx_size: size of the context. + */ + u32 rx_ctx_size; +}; + +/** + * enum mcps802154_tx_frame_config_flags - Flags for transmitting a frame. + * @MCPS802154_TX_FRAME_CONFIG_TIMESTAMP_DTU: + * Start transmission at given timestamp in device time unit. + * @MCPS802154_TX_FRAME_CONFIG_CCA: + * Use CCA before transmission using the programmed mode. + * @MCPS802154_TX_FRAME_CONFIG_RANGING: + * Enable precise timestamping for the transmitted frame and its response + * (RDEV only). + * @MCPS802154_TX_FRAME_CONFIG_KEEP_RANGING_CLOCK: + * Request that the ranging clock be kept valid after the transmission of + * this frame (RDEV only). + * @MCPS802154_TX_FRAME_CONFIG_RANGING_PDOA: + * Enable phase difference of arrival measurement for the response frame + * (RDEV only). + * @MCPS802154_TX_FRAME_CONFIG_SP1: + * Enable STS for the transmitted frame and its response, mode 1 (STS after + * SFD and before PHR, ERDEV only). + * @MCPS802154_TX_FRAME_CONFIG_SP2: + * Enable STS for the transmitted frame and its response, mode 2 (STS after + * the payload, ERDEV only). + * @MCPS802154_TX_FRAME_CONFIG_SP3: + * Enable STS for the transmitted frame and its response, mode 3 (STS after + * SFD, no PHR, no payload, ERDEV only). + * @MCPS802154_TX_FRAME_CONFIG_STS_MODE_MASK: + * Mask covering all the STS mode configuration values. + * @MCPS802154_TX_FRAME_CONFIG_RANGING_ROUND: + * Inform low-level driver the transmitted frame is the start of a ranging + * round (RDEV only). + * + * If no timestamp flag is given, transmit as soon as possible. + */ +enum mcps802154_tx_frame_config_flags { + MCPS802154_TX_FRAME_CONFIG_TIMESTAMP_DTU = BIT(0), + MCPS802154_TX_FRAME_CONFIG_CCA = BIT(1), + MCPS802154_TX_FRAME_CONFIG_RANGING = BIT(2), + MCPS802154_TX_FRAME_CONFIG_KEEP_RANGING_CLOCK = BIT(3), + MCPS802154_TX_FRAME_CONFIG_RANGING_PDOA = BIT(4), + MCPS802154_TX_FRAME_CONFIG_SP1 = BIT(5), + MCPS802154_TX_FRAME_CONFIG_SP2 = BIT(6), + MCPS802154_TX_FRAME_CONFIG_SP3 = BIT(5) | BIT(6), + MCPS802154_TX_FRAME_CONFIG_STS_MODE_MASK = BIT(5) | BIT(6), + MCPS802154_TX_FRAME_CONFIG_RANGING_ROUND = BIT(7), +}; + +/** + * struct mcps802154_tx_frame_config - Information for transmitting a frame. + */ +struct mcps802154_tx_frame_config { + /** + * @timestamp_dtu: If timestamped, date of transmission start. + */ + u32 timestamp_dtu; + /** + * @rx_enable_after_tx_dtu: If positive, enable receiver this number of + * device time unit after the end of the transmitted frame. + */ + int rx_enable_after_tx_dtu; + /** + * @rx_enable_after_tx_timeout_dtu: When receiver is enabled after the + * end of the transmitted frame: if negative, no timeout, if zero, use + * a default timeout value, else this is the timeout value in device + * time unit. + */ + int rx_enable_after_tx_timeout_dtu; + /** + * @flags: See &enum mcps802154_tx_frame_config_flags. + */ + u8 flags; + /** + * @ant_set_id : antenna set index to use for transmit. + */ + int ant_set_id; +}; + +/** + * enum mcps802154_rx_frame_config_flags - Flags for enabling the receiver. + * @MCPS802154_RX_FRAME_CONFIG_TIMESTAMP_DTU: + * Enable receiver at given timestamp in device time unit. + * @MCPS802154_RX_FRAME_CONFIG_AACK: + * Enable automatic acknowledgment. + * @MCPS802154_RX_FRAME_CONFIG_RANGING: + * Enable precise timestamping for the received frame (RDEV only). + * @MCPS802154_RX_FRAME_CONFIG_KEEP_RANGING_CLOCK: + * Request that the ranging clock be kept valid after the reception of the + * frame (RDEV only). + * @MCPS802154_RX_FRAME_CONFIG_RANGING_PDOA: + * Enable phase difference of arrival measurement (RDEV only). + * @MCPS802154_RX_FRAME_CONFIG_SP1: + * Enable STS for the received frame, mode 1 (STS after SFD and before PHR, + * ERDEV only). + * @MCPS802154_RX_FRAME_CONFIG_SP2: + * Enable STS for the received frame, mode 2 (STS after the payload, ERDEV + * only). + * @MCPS802154_RX_FRAME_CONFIG_SP3: + * Enable STS for the received frame, mode 3 (STS after SFD, no PHR, no + * payload, ERDEV only). + * @MCPS802154_RX_FRAME_CONFIG_STS_MODE_MASK: + * Mask covering all the STS mode configuration values. + * @MCPS802154_RX_FRAME_CONFIG_RANGING_ROUND: + * Inform low-level driver the expected received frame is the start of a + * ranging round (RDEV only). + * + * If no timestamp flag is given, enable receiver as soon as possible. + */ +enum mcps802154_rx_frame_config_flags { + MCPS802154_RX_FRAME_CONFIG_TIMESTAMP_DTU = BIT(0), + MCPS802154_RX_FRAME_CONFIG_AACK = BIT(1), + MCPS802154_RX_FRAME_CONFIG_RANGING = BIT(2), + MCPS802154_RX_FRAME_CONFIG_KEEP_RANGING_CLOCK = BIT(3), + MCPS802154_RX_FRAME_CONFIG_RANGING_PDOA = BIT(4), + MCPS802154_RX_FRAME_CONFIG_SP1 = BIT(5), + MCPS802154_RX_FRAME_CONFIG_SP2 = BIT(6), + MCPS802154_RX_FRAME_CONFIG_SP3 = BIT(5) | BIT(6), + MCPS802154_RX_FRAME_CONFIG_STS_MODE_MASK = BIT(5) | BIT(6), + MCPS802154_RX_FRAME_CONFIG_RANGING_ROUND = BIT(7), +}; + +/** + * struct mcps802154_rx_frame_config - Information for enabling the receiver. + */ +struct mcps802154_rx_frame_config { + /** + * @timestamp_dtu: If timestamped, date to enable the receiver. + */ + u32 timestamp_dtu; + /** + * @timeout_dtu: If negative, no timeout, if zero, use a default timeout + * value, else this is the timeout value in device time unit. + */ + int timeout_dtu; + /** + * @frame_timeout_dtu: If no zero, timeout value for the full frame + * reception. This allow limiting the length of accepted frame. The + * timeout starts after &mcps802154_rx_frame_config.timeout_dtu value. + */ + int frame_timeout_dtu; + /** + * @flags: See &enum mcps802154_rx_frame_config_flags. + */ + u8 flags; + /** + * @ant_set_id: Antenna set index to use for reception. + */ + int ant_set_id; +}; + +/** + * enum mcps802154_rx_frame_info_flags - Flags for a received frame. + * @MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU: + * Set by MCPS to request timestamp in device time unit. + * @MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU: + * Set by MCPS to request RMARKER timestamp in ranging counter time unit + * (RDEV only). + * @MCPS802154_RX_FRAME_INFO_LQI: + * Set by MCPS to request link quality indicator (LQI). + * @MCPS802154_RX_FRAME_INFO_RSSI: + * Set by MCPS to request RSSI. + * @MCPS802154_RX_FRAME_INFO_RANGING_FOM: + * Set by MCPS to request ranging figure of merit (FoM, RDEV only). + * @MCPS802154_RX_FRAME_INFO_RANGING_OFFSET: + * Set by MCPS to request clock characterization data (RDEV only). + * @MCPS802154_RX_FRAME_INFO_RANGING_PDOA: + * Set by MCPS to request phase difference of arrival (RDEV only). + * @MCPS802154_RX_FRAME_INFO_RANGING_PDOA_FOM: + * Set by MCPS to request phase difference of arrival figure of merit (FoM, + * RDEV only). + * @MCPS802154_RX_FRAME_INFO_RANGING_STS_TIMESTAMP_RCTU: + * Set by MCPS to request SRMARKERx timestamps for each STS segments in + * ranging counter time unit (ERDEV only). + * @MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM: + * Set by MCPS to request STS segments figure of merit measuring the + * correlation strength between the received STS segment and the expected + * one (FoM, ERDEV only). + * @MCPS802154_RX_FRAME_INFO_AACK: + * Set by low-level driver if an automatic acknowledgment was sent or is + * being sent. + * + * The low-level driver must clear the corresponding flag if the information is + * not available. + */ +enum mcps802154_rx_frame_info_flags { + MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU = BIT(0), + MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU = BIT(1), + MCPS802154_RX_FRAME_INFO_LQI = BIT(2), + MCPS802154_RX_FRAME_INFO_RSSI = BIT(3), + MCPS802154_RX_FRAME_INFO_RANGING_FOM = BIT(4), + MCPS802154_RX_FRAME_INFO_RANGING_OFFSET = BIT(5), + MCPS802154_RX_FRAME_INFO_RANGING_PDOA = BIT(6), + MCPS802154_RX_FRAME_INFO_RANGING_PDOA_FOM = BIT(7), + MCPS802154_RX_FRAME_INFO_RANGING_STS_TIMESTAMP_RCTU = BIT(8), + MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM = BIT(9), + MCPS802154_RX_FRAME_INFO_AACK = BIT(10), +}; + +/** + * struct mcps802154_rx_frame_info - Information on a received frame. + */ +struct mcps802154_rx_frame_info { + /** + * @timestamp_dtu: Timestamp of start of frame in device time unit. + */ + u32 timestamp_dtu; + /** + * @timestamp_rctu: Timestamp of RMARKER in ranging count time unit + * (RDEV only). + */ + u64 timestamp_rctu; + /** + * @frame_duration_dtu: Duration of the whole frame in device time unit + * or 0 if unknown. + */ + int frame_duration_dtu; + /** + * @rssi: Received signal strength indication (RSSI), + * absolute value in Q1 fixed point format. + */ + int rssi; + /** + * @ranging_tracking_interval_rctu: Interval on which tracking offset + * was measured (RDEV only). + */ + int ranging_tracking_interval_rctu; + /** + * @ranging_offset_rctu: Difference between the transmitter and the + * receiver clock measure over the tracking interval, if positive, the + * transmitter operates at a higher frequency (RDEV only). + */ + int ranging_offset_rctu; + /** + * @ranging_sts_timestamp_diffs_rctu: For each SRMARKERx, difference + * between the measured timestamp and the expected timestamp relative to + * RMARKER in ranging count time unit (ERDEV only). When STS mode is + * 1 or 3, SRMARKER0 is the same as RMARKER and difference is always 0. + */ + s16 ranging_sts_timestamp_diffs_rctu[MCPS802154_STS_N_SEGS_MAX + 1]; + /** + * @lqi: Link quality indicator (LQI). + */ + u8 lqi; + /** + * @ranging_fom: Ranging figure of merit (FoM, RDEV only). Should be + * formatted according to 802.15.4. + */ + u8 ranging_fom; + /** + * @ranging_pdoa_fom: Phase difference of arrival figure of merit (FoM, + * RDEV only). Range is 0 to 255, with 0 being an invalid measure and + * 255 being a 100% confidence. + */ + u8 ranging_pdoa_fom; + /** + * @ranging_sts_fom: Table of figures of merit measuring the correlation + * strength between the received STS segment and the expected one (FoM, + * ERDEV only). Range is 0 to 255, with 0 being an invalid measure and + * 255 being a 100% confidence. + */ + u8 ranging_sts_fom[MCPS802154_STS_N_SEGS_MAX]; + /** + * @flags: See &enum mcps802154_rx_frame_info_flags. + */ + u16 flags; +}; + +/** + * enum mcps802154_rx_measurement_info_flags - Flags for measurements on a received + * frame. + * @MCPS802154_RX_MEASUREMENTS_TIMESTAMP: + * Set by MCPS to request time of arrival measurement and associated figure + * of merit (RDEV only). + * @MCPS802154_RX_MEASUREMENTS_CLOCK_OFFSET: + * Set by MCPS to request clock characterization data (RDEV only). + * @MCPS802154_RX_MEASUREMENTS_STS_SEGS_TIMESTAMPS: + * Set by MCPS to request time of arrival measurement on STS segments and + * associated figure of merit (ERDEV only). + * @MCPS802154_RX_MEASUREMENTS_RSSIS: + * Set by MCPS to request RSSI values. + * @MCPS802154_RX_MEASUREMENTS_AOAS: + * Set by MCPS to request angle of arrival measurements, time difference of + * arrival, phase difference of arrival and associated figure of merit + * (RDEV only). + * @MCPS802154_RX_MEASUREMENTS_CIRS: + * Set by MCPS to request CIR samples (RDEV only). + * @MCPS802154_RX_MEASUREMENTS_VENDOR0: + * Set by MCPS to request first set of vendor specific measurements. + * @MCPS802154_RX_MEASUREMENTS_VENDOR1: + * Set by MCPS to request second set of vendor specific measurements. + * @MCPS802154_RX_MEASUREMENTS_VENDOR2: + * Set by MCPS to request third set of vendor specific measurements. + * @MCPS802154_RX_MEASUREMENTS_VENDOR3: + * Set by MCPS to request fourth set of vendor specific measurements. + * + * The low-level driver must clear the corresponding flag if the information is + * not available. + */ +enum mcps802154_rx_measurement_info_flags { + MCPS802154_RX_MEASUREMENTS_TIMESTAMP = BIT(0), + MCPS802154_RX_MEASUREMENTS_CLOCK_OFFSET = BIT(1), + MCPS802154_RX_MEASUREMENTS_STS_SEGS_TIMESTAMPS = BIT(2), + MCPS802154_RX_MEASUREMENTS_RSSIS = BIT(3), + MCPS802154_RX_MEASUREMENTS_AOAS = BIT(4), + MCPS802154_RX_MEASUREMENTS_CIRS = BIT(5), + MCPS802154_RX_MEASUREMENTS_VENDOR0 = BIT(12), + MCPS802154_RX_MEASUREMENTS_VENDOR1 = BIT(13), + MCPS802154_RX_MEASUREMENTS_VENDOR2 = BIT(14), + MCPS802154_RX_MEASUREMENTS_VENDOR3 = BIT(15), +}; + +/** + * struct mcps802154_rx_aoa_measurements - Angle of arrival measurements on a + * received frame (RDEV only). + */ +struct mcps802154_rx_aoa_measurements { + /** + * @tdoa_rctu: Time difference of arrival, in ranging count time unit. + */ + s16 tdoa_rctu; + /** + * @pdoa_rad_q11: Phase difference of arrival, unit is radian multiplied + * by 2048. + */ + s16 pdoa_rad_q11; + /** + * @aoa_rad_q11: Angle of arrival, unit is radian multiplied by 2048. + */ + s16 aoa_rad_q11; + /** + * @fom: Measurements figure of merit (FoM). Range is 0 to 255, with 0 + * being an invalid measure and 255 being a 100% confidence. + */ + u8 fom; + /** + * @type: Measurement type (azimuth, elevation...). Actual value is + * driver dependant. + */ + u8 type; +}; + +/** + * struct mcps802154_rx_cir_sample_window - Window of CIR samples. + */ +struct mcps802154_rx_cir_sample_window { + /** + * @n_samples: The number of samples contained in the window. + */ + u16 n_samples; + /** + * @sizeof_sample: The size of a single sample. + */ + u16 sizeof_sample; + /** + * @samples: CIR samples values. + * + * Each sample is composed of the real and imaginary part which are + * signed numbers. Each sample is encoded using the platform endianness + * with @mcps802154_rx_cir_sample_window.sizeof_sample bytes, first half + * is the real part, second half is the imaginary part. + * + * Must be kept valid until next received frame + */ + void *samples; +}; + +/** + * struct mcps802154_rx_cir - CIR measurements. + */ +struct mcps802154_rx_cir { + /** + * @fp_index: The absolute index of the sample considered as first path. + */ + u16 fp_index; + /** + * @fp_snr: The SNR of the sample considered as first path. + */ + s16 fp_snr; + /** + * @fp_ns_q6: (Q10.6) Time in nanosecond of the first path index + */ + u16 fp_ns_q6; + /** + * @pp_index: The absolute index of the sample considered as peak path. + */ + u16 pp_index; + /** + * @pp_snr: The SNR of the sample considered as peak path. + */ + s16 pp_snr; + /** + * @pp_ns_q6: (Q10.6) Time in nanosecond of the peak path index + */ + u16 pp_ns_q6; + /** + * @fp_sample_offset: The offset of the first path in the sample window. + */ + u16 fp_sample_offset; + /** + * @sample_window: CIR samples. + */ + struct mcps802154_rx_cir_sample_window sample_window; +}; + +/** + * struct mcps802154_rx_measurement_info - Measurements on a received frame. + */ +struct mcps802154_rx_measurement_info { + /** + * @n_rssis: The number of RSSI computed for this frame. Depends on the + * antenna set used to receive. + * + * Set by low-level driver. + */ + int n_rssis; + /** + * @rssis_q1: Received signal strength indication (RSSI), array of + * absolute values in Q7.1 fixed point format, unit is dBm. + */ + u8 rssis_q1[MCPS802154_RSSIS_N_MAX]; + /** + * @n_aoas: Number of angle of arrival measurements. + * + * Set by low-level driver. + */ + int n_aoas; + /** + * @aoas: Angle of arrival measurements, ordered by increasing + * measurement type. + */ + struct mcps802154_rx_aoa_measurements + aoas[MCPS802154_RX_AOA_MEASUREMENTS_MAX]; + /** + * @n_cirs: Number of parts of CIR measurements. + * + * Set by low-level driver. + */ + int n_cirs; + /** + * @cirs: CIR measurements for different parts of the frame. + * + * Set by low-level driver, must be kept valid until next received + * frame. + */ + struct mcps802154_rx_cir *cirs; + /** + * @flags: See &enum mcps802154_rx_measurement_info_flags. + */ + int flags; +}; + +/** + * struct mcps802154_sts_params - STS parameters for HRP UWB. + */ +struct mcps802154_sts_params { + /** + * @v: Value V used in DRBG for generating the STS. The 32 LSB are the + * VCounter which is incremented every 128 generated pulse. + */ + u8 v[AES_BLOCK_SIZE]; + /** + * @key: STS AES key used in DRBG for generating the STS. + */ + u8 key[AES_KEYSIZE_128]; + /** + * @n_segs: Number of STS segments. + */ + int n_segs; + /** + * @seg_len: Length of STS segments. + */ + int seg_len; + /** + * @sp2_tx_gap_4chips: For SP2 frame format, additional gap in unit of + * 4 chips between the end of the payload and the start of the STS, used + * for TX. + */ + int sp2_tx_gap_4chips; + /** + * @sp2_rx_gap_4chips: For SP2 frame format, additional gap in unit of + * 4 chips between the end of the payload and the start of the STS, used + * for RX. A0 and A1 bits in PHR are used to index the array. + */ + int sp2_rx_gap_4chips[MCPS802154_STS_N_SEGS_MAX]; +}; + +/** + * enum mcps802154_prf - Pulse repetition frequency. + * @MCPS802154_PRF_16: + * 16 MHz, only used in 4a. + * @MCPS802154_PRF_64: + * 64 MHz, used for 4a and 4z BPRF. + * @MCPS802154_PRF_125: + * 125 MHz, used for 4z HPRF. + * @MCPS802154_PRF_250: + * 250 MHz, used for 4z HPRF. + */ +enum mcps802154_prf { + MCPS802154_PRF_16 = 16, + MCPS802154_PRF_64 = 64, + MCPS802154_PRF_125 = 125, + MCPS802154_PRF_250 = 250, +}; + +/** + * enum mcps802154_psr - Number of preamble symbol repetitions in the SYNC + * sequence. + * @MCPS802154_PSR_16: + * 16 symbols, used in 4a and 4z HPRF. + * @MCPS802154_PSR_24: + * 24 symbols, used only in 4z HPRF. + * @MCPS802154_PSR_32: + * 32 symbols, used only in 4z HPRF. + * @MCPS802154_PSR_48: + * 48 symbols, used only in 4z HPRF. + * @MCPS802154_PSR_64: + * 64 symbols, used 4a and 4z BPRF and HPRF. + * @MCPS802154_PSR_96: + * 96 symbols, used only in 4z HPRF. + * @MCPS802154_PSR_128: + * 128 symbols, used only in 4z HPRF. + * @MCPS802154_PSR_256: + * 256 symbols, used only in 4z HPRF. + * @MCPS802154_PSR_1024: + * 1024 symbols, used only in 4a. + * @MCPS802154_PSR_4096: + * 4096 symbols, used only in 4a. + */ +enum mcps802154_psr { + MCPS802154_PSR_16 = 16, + MCPS802154_PSR_24 = 24, + MCPS802154_PSR_32 = 32, + MCPS802154_PSR_48 = 48, + MCPS802154_PSR_64 = 64, + MCPS802154_PSR_96 = 96, + MCPS802154_PSR_128 = 128, + MCPS802154_PSR_256 = 256, + MCPS802154_PSR_1024 = 1024, + MCPS802154_PSR_4096 = 4096, +}; + +/** + * enum mcps802154_sfd - sfd type selector. + * @MCPS802154_SFD_4A: + * SFD defined in 4a, length of 8 symbols. + * @MCPS802154_SFD_4Z_4: + * SFD defined in 4z, length of 4 symbols. + * @MCPS802154_SFD_4Z_8: + * SFD defined in 4z, length of 8 symbols. + * @MCPS802154_SFD_4Z_16: + * SFD defined in 4z, length of 16 symbols. + * @MCPS802154_SFD_4Z_32: + * SFD defined in 4z, length of 32 symbols. + */ +enum mcps802154_sfd { + MCPS802154_SFD_4A, + MCPS802154_SFD_4Z_4, + MCPS802154_SFD_4Z_8, + MCPS802154_SFD_4Z_16, + MCPS802154_SFD_4Z_32, +}; + +/** + * enum mcps802154_data_rate - Data rate. + * @MCPS802154_DATA_RATE_850K: + * 850 kbps, used only for 4a. + * @MCPS802154_DATA_RATE_6M81: + * 6.81 Mbps, used for 4a and 4z (PRF must be 125MHz). + * @MCPS802154_DATA_RATE_7M80: + * 7.80 Mbps, only used for 4z (PRF must be 125MHz). + * @MCPS802154_DATA_RATE_27M2: + * 27.2 Mbps, used for 4a and 4z (PRF must be 250MHz). + * @MCPS802154_DATA_RATE_31M2: + * 31.2 Mbps, used for 4z (PRF must be 250MHz). + * NOTE: device specific values can be set to use a custom data rate. + */ +enum mcps802154_data_rate { + MCPS802154_DATA_RATE_850K = 0, + MCPS802154_DATA_RATE_6M81 = 6, + MCPS802154_DATA_RATE_7M80 = 7, + MCPS802154_DATA_RATE_27M2 = 27, + MCPS802154_DATA_RATE_31M2 = 31, +}; + +/** + * enum mcps802154_hrp_uwb_psdu_size - PSDU size in HPRF. + * @MCPS802154_HRP_UWB_PSDU_SIZE_1023: + * 1023-bytes PSDU. + * @MCPS802154_HRP_UWB_PSDU_SIZE_2047: + * 2047-bytes PSDU. + * @MCPS802154_HRP_UWB_PSDU_SIZE_4095: + * 4095-bytes PSDU. + */ +enum mcps802154_hrp_uwb_psdu_size { + MCPS802154_HRP_UWB_PSDU_SIZE_1023 = 0, + MCPS802154_HRP_UWB_PSDU_SIZE_2047 = 1, + MCPS802154_HRP_UWB_PSDU_SIZE_4095 = 2, +}; + +/** + * struct mcps802154_hrp_uwb_params - Parameters for HRP UWB. + * + * Parameters are given directly to driver without checking. The driver needs to + * check the parameters for supported values, but it can accept non-standard + * values. + */ +struct mcps802154_hrp_uwb_params { + /** + * @prf: Nominal mean Pulse Repetition Frequency. + * + * For 4a, one of MCPS802154_PRF_16 or MCPS802154_PRF_64. + * + * For 4z BPRF, must be MCPS802154_PRF_64. + * + * For 4z HPRF, one of MCPS802154_PRF_125 or MCPS802154_PRF_250. + */ + enum mcps802154_prf prf; + /** + * @psr: Number of preamble symbol repetitions in the SYNC sequence, or + * preamble length. + * + * For 4a, one of 16, 64, 1024 or 4096. + * + * For 4z BPRF, must be 64. + * + * For 4z HPRF, one of 16, 24, 32, 48, 64, 96, 128 or 256. + */ + enum mcps802154_psr psr; + /** + * @sfd_selector: SFD type selector. + * + * When MCPS802154_SFD_4A, use short SFD defined in 802.15.4a. + * + * When MCPS802154_SFD_4Z_*, use SFD defined in 802.15.4z, with length + * 4, 8, 16 or 32. + * + * For 4a, must be MCPS802154_SFD_4A. + * + * For 4z BPRF, one of MCPS802154_SFD_4A or MCPS802154_SFD_4Z_8. + * + * For 4z HPRF, one of MCPS802154_SFD_4Z_{4,8,16,32}. + */ + enum mcps802154_sfd sfd_selector; + /** + * @data_rate: Data rate. + * + * For 4a, one of 850 kbps, 6.81 Mbps or 27.2 Mbps. + * + * For 4z BPRF, must be 6.81 Mbps. + * + * For 4z HPRF at 125 MHz, use 6.81 Mbps or 7.8 Mbps. + * + * For 4z HPRF at 250 MHz, use 27.2 Mbps or 31.2 Mbps. + */ + int data_rate; + /** + * @phr_hi_rate: Use high PHR data rate, for 4z BPRF only. + * + * For 4a and 4z HPRF, this parameter is ignored. + * + * For 4z BPRF, when enabled use 6.81 Mbps, otherwise use 850 kbps. + */ + bool phr_hi_rate; + /** + * @psdu_size: PSDU size in HPRF. + */ + enum mcps802154_hrp_uwb_psdu_size psdu_size; +}; + +/** + * enum mcps802154_antenna_caps - Antenna set capabilities + * @MCPS802154_AOA_X_AXIS: + * Antenna can report azimuth + * @MCPS802154_AOA_Y_AXIS: + * Antenna can report elevation + */ +enum mcps802154_antenna_caps { + MCPS802154_AOA_X_AXIS = BIT(0), + MCPS802154_AOA_Y_AXIS = BIT(1), +}; + +/** + * enum mcps802154_power_state - Power states + * @MCPS802154_PWR_STATE_OFF: + * Power off state. + * @MCPS802154_PWR_STATE_SLEEP: + * Deep sleep state. + * @MCPS802154_PWR_STATE_IDLE: + * Idle state, ready to transmit or receive. + * @MCPS802154_PWR_STATE_RX: + * Receive state. + * @MCPS802154_PWR_STATE_TX: + * Transmit state. + * @MCPS802154_PWR_STATE_MAX: + * Total power states count. + */ +enum mcps802154_power_state { + MCPS802154_PWR_STATE_OFF, + MCPS802154_PWR_STATE_SLEEP, + MCPS802154_PWR_STATE_IDLE, + MCPS802154_PWR_STATE_RX, + MCPS802154_PWR_STATE_TX, + MCPS802154_PWR_STATE_MAX +}; + +/** + * struct mcps802154_power_state_stats - Statistics for a power state. + * @dur: Duration in this power state in ns. + * @count: Count of transitions in this power state. + */ +struct mcps802154_power_state_stats { + u64 dur; + u64 count; +}; + +/** + * struct mcps802154_power_stats - Global power statistics. + * @power_state_stats: Array of power statistics for each power state. + * @interrupts: Hardware interrupts count on the device. + */ +struct mcps802154_power_stats { + struct mcps802154_power_state_stats + power_state_stats[MCPS802154_PWR_STATE_MAX]; + u64 interrupts; +}; + +/** + * struct mcps802154_ops - Callback from MCPS to the driver. + */ +struct mcps802154_ops { + /** + * @start: Initialize device. Reception should not be activated. + * + * Return: 0 or error. + */ + int (*start)(struct mcps802154_llhw *llhw); + /** + * @stop: Stop device. Should stop any transmission or reception and put + * the device in a low power mode. + */ + void (*stop)(struct mcps802154_llhw *llhw); + /** + * @tx_frame: Transmit a frame. skb contains the buffer starting from + * the IEEE 802.15.4 header. The low-level driver should send the frame + * as specified in config. Receiver should be disabled automatically + * unless a frame is being received. + * + * The &frame_idx parameter gives the index of the frame in a "block". + * Frames from the same block (aka frame_idx > 0) should maintain the + * same synchronization. + * + * The &next_delay_dtu parameter gives the expected delay between the + * start of the transmitted frame and the next action. + * + * Return: 0, -ETIME if frame can not be sent at specified timestamp, + * -EBUSY if a reception is happening right now, or any other error. + */ + int (*tx_frame)(struct mcps802154_llhw *llhw, struct sk_buff *skb, + const struct mcps802154_tx_frame_config *config, + int frame_idx, int next_delay_dtu); + /** + * @rx_enable: Enable receiver. + * + * The &frame_idx parameter gives the index of the frame in a "block". + * Frames from the same block (aka frame_idx > 0) should maintain the + * same synchronization. + * + * The &next_delay_dtu parameter gives the expected delay between the + * start of the received frame or timeout event and the next action. + * + * Return: 0, -ETIME if receiver can not be enabled at specified + * timestamp, or any other error. + */ + int (*rx_enable)(struct mcps802154_llhw *llhw, + const struct mcps802154_rx_frame_config *config, + int frame_idx, int next_delay_dtu); + /** + * @rx_disable: Disable receiver, or a programmed receiver enabling, + * unless a frame reception is happening right now. + * + * Return: 0, -EBUSY if a reception is happening right now, or any other + * error. + */ + int (*rx_disable)(struct mcps802154_llhw *llhw); + /** + * @rx_get_frame: Get previously received frame. MCPS calls this handler + * after a frame reception has been signaled by the low-level driver. + * + * The received buffer is owned by MCPS after this call. Only the + * requested information need to be filled in the information structure. + * + * Return: 0, -EBUSY if no longer available, or any other error. + */ + int (*rx_get_frame)(struct mcps802154_llhw *llhw, struct sk_buff **skb, + struct mcps802154_rx_frame_info *info); + /** + * @rx_get_error_frame: Get information on rejected frame. MCPS can call + * this handler after a frame rejection has been signaled by the + * low-level driver. + * + * In case of error, info flags must be cleared by this callback. + * + * Return: 0, -EBUSY if no longer available, or any other error. + */ + int (*rx_get_error_frame)(struct mcps802154_llhw *llhw, + struct mcps802154_rx_frame_info *info); + /** + * @rx_get_measurement: Get measurement associated with a received + * frame. + * + * Return: 0, -EBUSY if no longer available, or any other error. + */ + int (*rx_get_measurement)(struct mcps802154_llhw *llhw, void *rx_ctx, + struct mcps802154_rx_measurement_info *info); + /** + * @idle: Put the device into idle mode without time limit or until the + * given timestamp. The driver should call &mcps802154_timer_expired() + * before the given timestamp so that an action can be programmed at the + * given timestamp. + * + * The &mcps802154_timer_expired() function must not be called + * immediately from this callback, but should be scheduled to be called + * later. + * + * If the driver is late, the regular handling of late actions will take + * care of the situation. + * + * Return: 0 or error. + */ + int (*idle)(struct mcps802154_llhw *llhw, bool timestamp, + u32 timestamp_dtu); + /** + * @reset: Reset device after an unrecoverable error. + * + * Return: 0 or error. + */ + int (*reset)(struct mcps802154_llhw *llhw); + /** + * @get_current_timestamp_dtu: Get current timestamp in device time + * unit. + * + * If the device is currently in a low power state, the eventual wake up + * delay should be added to the returned timestamp. + * + * If the current timestamp can not be determined precisely, it should + * return a pessimistic value, i.e. rounded up. + * + * Return: 0 or error. + */ + int (*get_current_timestamp_dtu)(struct mcps802154_llhw *llhw, + u32 *timestamp_dtu); + /** + * @tx_timestamp_dtu_to_rmarker_rctu: Compute the RMARKER timestamp in + * ranging counter time unit for a frame transmitted at given timestamp + * in device time unit (RDEV only). + * + * Return: The RMARKER timestamp. + */ + u64 (*tx_timestamp_dtu_to_rmarker_rctu)( + struct mcps802154_llhw *llhw, u32 tx_timestamp_dtu, + const struct mcps802154_hrp_uwb_params *hrp_uwb_params, + const struct mcps802154_channel *channel_params, + int ant_set_id); + /** + * @difference_timestamp_rctu: Compute the difference between two + * timestamp values. + * + * Return: The difference between A and B. + */ + s64 (*difference_timestamp_rctu)(struct mcps802154_llhw *llhw, + u64 timestamp_a_rctu, + u64 timestamp_b_rctu); + /** + * @compute_frame_duration_dtu: Compute the duration of a frame with + * given payload length (header and checksum included) using the current + * radio parameters. + * + * Return: The duration in device time unit. + */ + int (*compute_frame_duration_dtu)(struct mcps802154_llhw *llhw, + int payload_bytes); + /** + * @set_channel: Set channel parameters. + * + * Return: 0 or error. + */ + int (*set_channel)(struct mcps802154_llhw *llhw, u8 page, u8 channel, + u8 preamble_code); + /** + * @set_hrp_uwb_params: Set radio parameters for HRP UWB. + * + * The parameters in &mcps802154_llhw can change according to radio + * parameters. + * + * Return: 0 or error. + */ + int (*set_hrp_uwb_params)( + struct mcps802154_llhw *llhw, + const struct mcps802154_hrp_uwb_params *params); + /** + * @check_hrp_uwb_params: Check that the HRP parameters are compatible + * with the hardware capabilities. + * + * Return: 0 or error. + */ + int (*check_hrp_uwb_params)( + struct mcps802154_llhw *llhw, + const struct mcps802154_hrp_uwb_params *params); + /** + * @set_sts_params: Set STS parameters (ERDEV only). + * + * Return: 0 or error. + */ + int (*set_sts_params)(struct mcps802154_llhw *llhw, + const struct mcps802154_sts_params *params); + /** + * @set_hw_addr_filt: Set hardware filter parameters. + * + * Return: 0 or error. + */ + int (*set_hw_addr_filt)(struct mcps802154_llhw *llhw, + struct ieee802154_hw_addr_filt *filt, + unsigned long changed); + /** + * @set_txpower: Set transmission power. + * + * Return: 0 or error. + */ + int (*set_txpower)(struct mcps802154_llhw *llhw, s32 mbm); + /** + * @set_cca_mode: Set CCA mode. + * + * The CCA duration in &mcps802154_llhw can change according to CCA + * mode. + * + * Return: 0 or error. + */ + int (*set_cca_mode)(struct mcps802154_llhw *llhw, + const struct wpan_phy_cca *cca); + /** + * @set_cca_ed_level: Set CCA energy detection threshold. + * + * Return: 0 or error. + */ + int (*set_cca_ed_level)(struct mcps802154_llhw *llhw, s32 mbm); + /** + * @set_promiscuous_mode: Set promiscuous mode. + * + * Return: 0 or error. + */ + int (*set_promiscuous_mode)(struct mcps802154_llhw *llhw, bool on); + /** + * @set_scanning_mode: Set SW scanning mode. + * + * Return: 0 or error. + */ + int (*set_scanning_mode)(struct mcps802154_llhw *llhw, bool on); + /** + * @set_calibration: Set calibration value. + * + * Set the calibration parameter specified by the key string with the + * value specified in the provided buffer. The provided length must + * match the length returned by the @get_calibration() callback. + * + * Return: 0 or error. + */ + int (*set_calibration)(struct mcps802154_llhw *llhw, const char *key, + void *value, size_t length); + /** + * @get_calibration: Get calibration value. + * + * Get the calibration parameter specified by the key string into the + * provided buffer. + * + * Return: size of parameter written in buffer or error. + */ + int (*get_calibration)(struct mcps802154_llhw *llhw, const char *key, + void *value, size_t length); + /** + * @list_calibration: Returns list of accepted calibration key strings + * + * Return: NULL terminated strings pointer array. + */ + const char *const *(*list_calibration)(struct mcps802154_llhw *llhw); + /** + * @vendor_cmd: Run a vendor specific command. + * + * Do not (ab)use this feature to implement features that could be + * openly shared across drivers. + * + * Return: 0 or error. + */ + int (*vendor_cmd)(struct mcps802154_llhw *llhw, u32 vendor_id, + u32 subcmd, void *data, size_t data_len); + /** + * @get_antenna_caps: Return antenna set capabilites. + * + * Return: 0 or error. + */ + int (*get_antenna_caps)(struct mcps802154_llhw *llhw, int ant_idx, + u32 *caps); + /** + * @get_power_stats: Get the power statistics. + * + * Return: 0 or error. + */ + int (*get_power_stats)(struct mcps802154_llhw *llhw, + struct mcps802154_power_stats *pwr_stats); +#ifdef CONFIG_MCPS802154_TESTMODE + /** + * @testmode_cmd: Run a testmode command. + * + * Return: 0 or error. + */ + int (*testmode_cmd)(struct mcps802154_llhw *llhw, void *data, int len); +#endif +}; + +#ifdef CONFIG_MCPS802154_TESTMODE +#define MCPS802154_TESTMODE_CMD(cmd) .testmode_cmd = (cmd), +#else +#define MCPS802154_TESTMODE_CMD(cmd) +#endif + +/** + * enum mcps802154_rx_error_type - Type of reception errors. + * @MCPS802154_RX_ERROR_NONE: + * RX successful. + * @MCPS802154_RX_ERROR_TIMEOUT: + * RX timeout. + * @MCPS802154_RX_ERROR_BAD_CKSUM: + * Checksum is not correct. + * @MCPS802154_RX_ERROR_UNCORRECTABLE: + * During reception, the error correction code detected an uncorrectable + * error. + * @MCPS802154_RX_ERROR_FILTERED: + * A received frame was rejected due to frame filter. + * @MCPS802154_RX_ERROR_SFD_TIMEOUT: + * A preamble has been detected but without SFD. + * @MCPS802154_RX_ERROR_OTHER: + * Other error, frame reception is aborted. + * @MCPS802154_RX_ERROR_PHR_DECODE: + * the preamble and SFD have been detected but without PHR. + * @MCPS802154_RX_ERROR_HPDWARN: + * Too late to program RX operation. + */ +enum mcps802154_rx_error_type { + MCPS802154_RX_ERROR_NONE = 0, + MCPS802154_RX_ERROR_TIMEOUT = 1, + MCPS802154_RX_ERROR_BAD_CKSUM = 2, + MCPS802154_RX_ERROR_UNCORRECTABLE = 3, + MCPS802154_RX_ERROR_FILTERED = 4, + MCPS802154_RX_ERROR_SFD_TIMEOUT = 5, + MCPS802154_RX_ERROR_OTHER = 6, + MCPS802154_RX_ERROR_PHR_DECODE = 7, + MCPS802154_RX_ERROR_HPDWARN = 8, +}; + +/** + * mcps802154_alloc_llhw() - Allocate a new low-level hardware device. + * @priv_data_len: Length of private data. + * @ops: Callbacks for this device. + * + * Return: A pointer to the new low-level hardware device, or %NULL on error. + */ +struct mcps802154_llhw *mcps802154_alloc_llhw(size_t priv_data_len, + const struct mcps802154_ops *ops); + +/** + * mcps802154_free_llhw() - Free low-level hardware descriptor. + * @llhw: Low-level device pointer. + * + * You must call mcps802154_unregister_hw() before calling this function. + */ +void mcps802154_free_llhw(struct mcps802154_llhw *llhw); + +/** + * mcps802154_register_llhw() - Register low-level hardware device. + * @llhw: Low-level device pointer. + * + * Return: 0 or error. + */ +int mcps802154_register_llhw(struct mcps802154_llhw *llhw); + +/** + * mcps802154_unregister_llhw() - Unregister low-level hardware device. + * @llhw: Low-level device pointer. + */ +void mcps802154_unregister_llhw(struct mcps802154_llhw *llhw); + +/** + * mcps802154_rx_frame() - Signal a frame reception. + * @llhw: Low-level device this frame came in on. + * + * The MCPS will call the &mcps802154_ops.rx_get_frame() handler to retrieve + * frame. + */ +void mcps802154_rx_frame(struct mcps802154_llhw *llhw); + +/** + * mcps802154_rx_timeout() - Signal a reception timeout. + * @llhw: Low-level device pointer. + */ +void mcps802154_rx_timeout(struct mcps802154_llhw *llhw); + +/** + * mcps802154_rx_too_late() - Signal a problem programing a RX. + * @llhw: Low-level device pointer. + */ +void mcps802154_rx_too_late(struct mcps802154_llhw *llhw); + +/** + * mcps802154_rx_error() - Signal a reception error. + * @llhw: Low-level device pointer. + * @error: Type of detected error. + * + * In case of filtered frame, the MCPS can call the + * &mcps802154_ops.rx_get_error_frame() handler to retrieve frame information. + */ +void mcps802154_rx_error(struct mcps802154_llhw *llhw, + enum mcps802154_rx_error_type error); + +/** + * mcps802154_tx_done() - Signal the end of an MCPS transmission. + * @llhw: Low-level device pointer. + */ +void mcps802154_tx_done(struct mcps802154_llhw *llhw); + +/** + * mcps802154_tx_too_late() - Signal a problem programing a TX. + * @llhw: Low-level device pointer. + */ +void mcps802154_tx_too_late(struct mcps802154_llhw *llhw); + +/** + * mcps802154_broken() - Signal an unrecoverable error, device needs to be + * reset. + * @llhw: Low-level device pointer. + */ +void mcps802154_broken(struct mcps802154_llhw *llhw); + +/** + * mcps802154_timer_expired() - Signal that a programmed timer expired. + * @llhw: Low-level device pointer. + * + * To be called before the timestamp given to &mcps802154_ops.idle() callback. + */ +void mcps802154_timer_expired(struct mcps802154_llhw *llhw); + +/** + * is_before_dtu() - Check if timestamp A is before timestamp B. + * @a_dtu: A timestamp in device time unit. + * @b_dtu: B timestamp in device time unit. + * + * Return: true if A timestamp is before B timestamp. + */ +static inline bool is_before_dtu(u32 a_dtu, u32 b_dtu) +{ + return (s32)(a_dtu - b_dtu) < 0; +} + +#ifdef CONFIG_MCPS802154_TESTMODE +/** + * mcps802154_testmode_alloc_reply_skb() - Allocate testmode reply. + * @llhw: Low-level device pointer. + * @approxlen: an upper bound of the length of the data that will + * be put into the skb. + * + * This function allocates and pre-fills an skb for a reply to + * the testmode command. Since it is intended for a reply, calling + * it outside of the @testmode_cmd operation is invalid. + * + * The returned skb is pre-filled with the netlink message's header + * and attribute's data and set up in a way that any data that is + * put into the skb (with skb_put(), nla_put() or similar) will end up + * being within the %MCPS802154_ATTR_TESTDATA attribute, so all + * that needs to be done with the skb is adding data for + * the corresponding userspace tool which can then read that data + * out of the testdata attribute. You must not modify the skb + * in any other way. + * + * When done, call mcps802154_testmode_reply() with the skb and return + * its error code as the result of the @testmode_cmd operation. + * + * Return: An allocated and pre-filled skb. %NULL if any errors happen. + */ +struct sk_buff * +mcps802154_testmode_alloc_reply_skb(struct mcps802154_llhw *llhw, + int approxlen); + +/** + * mcps802154_testmode_reply() - Send the reply skb. + * @llhw: Low-level device pointer. + * @skb: The skb, must have been allocated with + * mcps802154_testmode_alloc_reply_skb(). + * + * Since calling this function will usually be the last thing + * before returning from the @testmode_cmd you should return + * the error code. Note that this function consumes the skb + * regardless of the return value. + * + * Return: 0 or error. + */ +int mcps802154_testmode_reply(struct mcps802154_llhw *llhw, + struct sk_buff *skb); +#endif + +#endif /* NET_MCPS802154_H */ diff --git a/mac/include/net/mcps802154_frame.h b/mac/include/net/mcps802154_frame.h new file mode 100644 index 0000000..2bc8105 --- /dev/null +++ b/mac/include/net/mcps802154_frame.h @@ -0,0 +1,334 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_FRAME_H +#define NET_MCPS802154_FRAME_H + +#include <linux/skbuff.h> +#include "mcps802154.h" + +#define IEEE802154_FC_NO_SEQ_SHIFT 8 +#define IEEE802154_FC_NO_SEQ (1 << IEEE802154_FC_NO_SEQ_SHIFT) + +#define IEEE802154_FC_IE_PRESENT_SHIFT 9 +#define IEEE802154_FC_IE_PRESENT (1 << IEEE802154_FC_IE_PRESENT_SHIFT) + +#define IEEE802154_SCF_LEN 1 +#define IEEE802154_SCF_NO_FRAME_COUNTER (1 << 5) + +#define IEEE802154_IE_HEADER_LEN 2 +#define IEEE802154_IE_HEADER_TYPE_SHIFT 15 +#define IEEE802154_IE_HEADER_TYPE (1 << IEEE802154_IE_HEADER_TYPE_SHIFT) + +#define IEEE802154_HEADER_IE_HEADER_LENGTH 0x7f +#define IEEE802154_HEADER_IE_HEADER_ELEMENT_ID (0xff << 7) +#define IEEE802154_HEADER_IE_HEADER_TYPE (0 << IEEE802154_IE_HEADER_TYPE_SHIFT) + +#define IEEE802154_PAYLOAD_IE_HEADER_LENGTH 0x7ff +#define IEEE802154_PAYLOAD_IE_HEADER_GROUP_ID (0xf << 11) +#define IEEE802154_PAYLOAD_IE_HEADER_TYPE (1 << IEEE802154_IE_HEADER_TYPE_SHIFT) + +#define IEEE802154_LONG_NESTED_IE_HEADER_LENGTH 0x7ff +#define IEEE802154_LONG_NESTED_IE_HEADER_SUB_ID (0xf << 11) +#define IEEE802154_LONG_NESTED_IE_HEADER_TYPE \ + (1 << IEEE802154_IE_HEADER_TYPE_SHIFT) + +#define IEEE802154_SHORT_NESTED_IE_HEADER_LENGTH 0xff +#define IEEE802154_SHORT_NESTED_IE_HEADER_SUB_ID (0x7f << 8) +#define IEEE802154_SHORT_NESTED_IE_HEADER_TYPE \ + (0 << IEEE802154_IE_HEADER_TYPE_SHIFT) + +#define IEEE802154_IE_NESTED_SHORT_MIN_SID 0x10 + +#define IEEE802154_IE_HEADER_VENDOR_ID 0x00 +#define IEEE802154_IE_HEADER_TERMINATION_1_ID 0x7e +#define IEEE802154_IE_HEADER_TERMINATION_2_ID 0x7f + +#define IEEE802154_IE_PAYLOAD_MLME_GID 0x1 +#define IEEE802154_IE_PAYLOAD_VENDOR_GID 0x2 +#define IEEE802154_IE_PAYLOAD_TERMINATION_GID 0xf + +struct mcps802154_llhw; + +/** + * enum mcps802154_ie_get_kind - Kind of IE, or none. + * @MCPS802154_IE_GET_KIND_NONE: No IE decoded (at end of frame). + * @MCPS802154_IE_GET_KIND_HEADER: Header IE decoded. + * @MCPS802154_IE_GET_KIND_PAYLOAD: Payload IE decoded. + * @MCPS802154_IE_GET_KIND_MLME_NESTED: + * Nested IE inside a MLME payload IE decoded. + */ +enum mcps802154_ie_get_kind { + MCPS802154_IE_GET_KIND_NONE, + MCPS802154_IE_GET_KIND_HEADER, + MCPS802154_IE_GET_KIND_PAYLOAD, + MCPS802154_IE_GET_KIND_MLME_NESTED, +}; + +/** + * struct mcps802154_ie_get_context - Context for IE decoding, to be used with + * mcps802154_ie_get(). Initialize to zero. + */ +struct mcps802154_ie_get_context { + /** + * @in_payload: If true, next decoding is in payload. + */ + bool in_payload; + /** + * @kind: Kind of decoded IE. + */ + enum mcps802154_ie_get_kind kind; + /** + * @id: Element identifier, group identifier or sub identifier of the + * decoded IE. + */ + int id; + /** + * @len: Length of the decoded IE. + */ + unsigned int len; + /** + * @mlme_len: While an MLME IE is decoded, length of data still in the + * frame buffer for this IE. Set this to 0 if you pulled all the MLME + * payload. + */ + unsigned int mlme_len; +}; + +/** + * mcps802154_frame_alloc() - Allocate a buffer for TX. + * @llhw: Low-level device pointer. + * @size: Header and payload size. + * @flags: Allocation mask. + * + * This is to allocate a buffer for sending a frame to the low-level driver + * directly. Additional space is reserved for low-level driver headroom and for + * checksum. + * + * Return: Allocated buffer, or NULL. + */ +struct sk_buff *mcps802154_frame_alloc(struct mcps802154_llhw *llhw, + unsigned int size, gfp_t flags); + +/** + * mcps802154_ie_put_begin() - Begin writing information elements. + * @skb: Frame buffer. + * + * Prepare a frame buffer for writing IEs. The buffer control buffer is used to + * store state information. + */ +void mcps802154_ie_put_begin(struct sk_buff *skb); + +/** + * mcps802154_ie_put_end() - End writing information elements. + * @skb: Frame buffer. + * @data_payload: True if data will be appended to the buffer after the IEs. In + * this case, a terminator IE may be needed. + * + * This function appends a terminator IE if needed. + * + * Return: Length of frame header or -ENOBUFS in case of error. + */ +int mcps802154_ie_put_end(struct sk_buff *skb, bool data_payload); + +/** + * mcps802154_ie_put_header_ie() - Add a header IE. + * @skb: Frame buffer. + * @element_id: Header IE element identifier. + * @len: Header IE payload length. + * + * This adds the IE header and reserves room to write your payload. This works + * like skb_put, you must write at the returned address. + * + * Return: Address of reserved space to write payload, or NULL in case of error. + */ +void *mcps802154_ie_put_header_ie(struct sk_buff *skb, int element_id, + unsigned int len); + +/** + * mcps802154_ie_put_payload_ie() - Add a payload IE. + * @skb: Frame buffer. + * @group_id: Payload IE group identifier. + * @len: Payload IE payload length. + * + * This adds the IE header and reserves room to write your payload. This works + * like skb_put, you must write at the returned address. + * + * Return: Address of reserved space to write payload, or NULL in case of error. + */ +void *mcps802154_ie_put_payload_ie(struct sk_buff *skb, int group_id, + unsigned int len); + +/** + * mcps802154_ie_put_nested_mlme_ie() - Add a nested IE, inside a MLME IE. + * @skb: Frame buffer. + * @sub_id: Nested IE element identifier. + * @len: Nested IE payload length. + * + * This adds the IE header and reserves room to write your payload. This works + * like skb_put, you must write at the returned address. + * + * The MLME payload IE is added automatically if needed and its length is + * incremented if present yet. + * + * Return: Address of reserved space to write payload, or NULL in case of error. + */ +void *mcps802154_ie_put_nested_mlme_ie(struct sk_buff *skb, int sub_id, + unsigned int len); + +/** + * mcps802154_ie_get() - Parse one IE and fill context. + * @skb: Frame buffer. + * @context: Parse context, should be zero initialized at first call. + * + * This should only be called if the buffer contains IEs. This can be determined + * using the IE_PRESENT bit in the frame control. + * + * On successful parsing, the context structure is filled with information about + * the read IE. The IE payload can be read at the head of the frame buffer, + * headers are consumed. + * + * On last return, 1 is returned and a termination IE can be present in the + * context, it usually can be ignored. + * + * When an MLME IE is found, you have two options: + * - ignore it and call again to parse nested IE. + * - pull nested payload from the frame buffer, in this case you should set + * mlme_len to zero to proceed with the next IE. + * + * Return: 1 if last IE, 0 on successfully decoded IE, else negative error code. + */ +int mcps802154_ie_get(struct sk_buff *skb, + struct mcps802154_ie_get_context *context); + +/** + * mcps802154_get_extended_addr() - Get current extended address. + * @llhw: Low-level device pointer. + * + * Return: Extended address. + */ +__le64 mcps802154_get_extended_addr(struct mcps802154_llhw *llhw); + +/** + * mcps802154_get_pan_id() - Get current PAN identifier. + * @llhw: Low-level device pointer. + * + * Return: PAN ID. + */ +__le16 mcps802154_get_pan_id(struct mcps802154_llhw *llhw); + +/** + * mcps802154_get_short_addr() - Get current short address. + * @llhw: Low-level device pointer. + * + * Return: Short address. + */ +__le16 mcps802154_get_short_addr(struct mcps802154_llhw *llhw); + +/** + * mcps802154_get_current_channel() - Get current channel. + * @llhw: Low-level device pointer. + * + * Return: Channel parameters. + */ +const struct mcps802154_channel * +mcps802154_get_current_channel(struct mcps802154_llhw *llhw); + +/** + * mcps802154_get_current_timestamp_dtu() - Get current timestamp in device time + * unit. + * @llhw: Low-level device pointer. + * @timestamp_dtu: Pointer to timestamp to write. + * + * Return: 0 or error. + */ +int mcps802154_get_current_timestamp_dtu(struct mcps802154_llhw *llhw, + u32 *timestamp_dtu); + +/** + * mcps802154_tx_timestamp_dtu_to_rmarker_rctu() - Compute the RMARKER timestamp + * in ranging counter time unit for a frame transmitted at given timestamp in + * device time unit (RDEV only). + * @llhw: Low-level device pointer. + * @tx_timestamp_dtu: TX timestamp in device time unit. + * @hrp_uwb_params: HRP UWB parameters. + * @channel_params: Channel parameters. + * @ant_set_id: Antennas set id used to transmit. + * + * Return: RMARKER timestamp in ranging count time unit. + */ +u64 mcps802154_tx_timestamp_dtu_to_rmarker_rctu( + struct mcps802154_llhw *llhw, u32 tx_timestamp_dtu, + const struct mcps802154_hrp_uwb_params *hrp_uwb_params, + const struct mcps802154_channel *channel_params, int ant_set_id); + +/** + * mcps802154_difference_timestamp_rctu() - Compute the difference between two + * timestamp values. + * @llhw: Low-level device pointer. + * @timestamp_a_rctu: Timestamp A value. + * @timestamp_b_rctu: Timestamp B value. + * + * Return: Difference between A and B. + */ +s64 mcps802154_difference_timestamp_rctu(struct mcps802154_llhw *llhw, + u64 timestamp_a_rctu, + u64 timestamp_b_rctu); + +/** + * mcps802154_compute_frame_duration_dtu() - Compute the duration of a frame. + * @llhw: Low-level device pointer. + * @payload_bytes: Payload length (header and checksum included). + * + * Return: The duration in device time unit. + */ +int mcps802154_compute_frame_duration_dtu(struct mcps802154_llhw *llhw, + int payload_bytes); + +/** + * mcps802154_vendor_cmd() - Run a driver vendor specific command. + * @llhw: Low-level device pointer. + * @vendor_id: Vendor identifier as an OUI. + * @subcmd: Command identifier. + * @data: Data to be passed to driver, can be in/out. + * @data_len: Data length. + * + * The valid moment to call this function is driver and command specific. + * + * Return: 0 or error. + */ +int mcps802154_vendor_cmd(struct mcps802154_llhw *llhw, u32 vendor_id, + u32 subcmd, void *data, size_t data_len); + +/** + * mcps802154_rx_get_measurement() - Get measurement. + * @llhw: Low-level device pointer. + * @rx_ctx: Rx context (can be NULL). + * @info: Measurements updated by the llhw. + * + * Return: 0 or error. + */ +int mcps802154_rx_get_measurement(struct mcps802154_llhw *llhw, void *rx_ctx, + struct mcps802154_rx_measurement_info *info); + +#endif /* NET_MCPS802154_FRAME_H */ diff --git a/mac/include/net/mcps802154_nl.h b/mac/include/net/mcps802154_nl.h new file mode 100644 index 0000000..9aa5a9b --- /dev/null +++ b/mac/include/net/mcps802154_nl.h @@ -0,0 +1,254 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_NL_H +#define NET_MCPS802154_NL_H + +#define MCPS802154_GENL_NAME "mcps802154" + +/** + * enum mcps802154_commands - MCPS net link commands. + * + * @MCPS802154_CMD_GET_HW: + * Request information about a device, or dump request to get a list of all + * devices. + * @MCPS802154_CMD_NEW_HW: + * Result from information request. + * @MCPS802154_CMD_SET_SCHEDULER: + * Set the scheduler used to manage the schedule. + * @MCPS802154_CMD_SET_SCHEDULER_PARAMS: + * Set the parameters of the current scheduler. + * @MCPS802154_CMD_CALL_SCHEDULER: + * Call specific scheduler procedure. + * @MCPS802154_CMD_SET_SCHEDULER_REGIONS: + * Set and configure the scheduler regions. + * @MCPS802154_CMD_SET_REGIONS_PARAMS: + * Set the parameters of a specific region. + * @MCPS802154_CMD_CALL_REGION: + * Call specific region procedure. + * @MCPS802154_CMD_SET_CALIBRATIONS: + * Attempts to write the given value to the indicated calibration item. + * @MCPS802154_CMD_GET_CALIBRATIONS: + * Requests information about a given calibration items. + * @MCPS802154_CMD_LIST_CALIBRATIONS: + * Reports all calibrations existing. + * @MCPS802154_CMD_TESTMODE: + * Run a testmode command with TESTDATA blob attribute to pass through + * to the driver. + * @MCPS802154_CMD_CLOSE_SCHEDULER: + * Close current scheduler and its regions. + * @MCPS802154_CMD_GET_PWR_STATS: + * Get the power statistics. + * + * @MCPS802154_CMD_UNSPEC: Invalid command. + * @__MCPS802154_CMD_AFTER_LAST: Internal use. + * @MCPS802154_CMD_MAX: Internal use. + */ +enum mcps802154_commands { + MCPS802154_CMD_UNSPEC, + + MCPS802154_CMD_GET_HW, /* can dump */ + MCPS802154_CMD_NEW_HW, + MCPS802154_CMD_SET_SCHEDULER, + MCPS802154_CMD_SET_SCHEDULER_PARAMS, + MCPS802154_CMD_CALL_SCHEDULER, + MCPS802154_CMD_SET_SCHEDULER_REGIONS, + MCPS802154_CMD_SET_REGIONS_PARAMS, + MCPS802154_CMD_CALL_REGION, + MCPS802154_CMD_SET_CALIBRATIONS, + MCPS802154_CMD_GET_CALIBRATIONS, + MCPS802154_CMD_LIST_CALIBRATIONS, + MCPS802154_CMD_TESTMODE, + MCPS802154_CMD_CLOSE_SCHEDULER, + MCPS802154_CMD_GET_PWR_STATS, + __MCPS802154_CMD_AFTER_LAST, + MCPS802154_CMD_MAX = __MCPS802154_CMD_AFTER_LAST - 1 +}; + +/** + * enum mcps802154_attrs - Top level MCPS net link attributes. + * + * @MCPS802154_ATTR_HW: + * Hardware device index, internal to MCPS. + * @MCPS802154_ATTR_WPAN_PHY_NAME: + * Name of corresponding wpan_phy device. + * @MCPS802154_ATTR_SCHEDULER_NAME: + * Name of the scheduler. + * @MCPS802154_ATTR_SCHEDULER_PARAMS: + * Parameters of the scheduler. + * @MCPS802154_ATTR_SCHEDULER_REGIONS: + * Parameters of the regions. + * @MCPS802154_ATTR_SCHEDULER_CALL: + * Call id of the scheduler's procedure, scheduler specific. + * @MCPS802154_ATTR_SCHEDULER_CALL_PARAMS: + * Parameters of the scheduler's procedure, scheduler specific. + * @MCPS802154_ATTR_SCHEDULER_REGION_CALL: + * Parameters of the region call, scheduler specific. + * @MCPS802154_ATTR_TESTDATA: + * Testmode's data blob, passed through to the driver. It contains + * driver-specific attributes. + * @MCPS802154_ATTR_CALIBRATIONS: + * Nested array of calibrations. + * @MCPS802154_ATTR_PWR_STATS: + * Nested power statistics data. + * + * @MCPS802154_ATTR_UNSPEC: Invalid command. + * @__MCPS802154_ATTR_AFTER_LAST: Internal use. + * @MCPS802154_ATTR_MAX: Internal use. + */ +enum mcps802154_attrs { + MCPS802154_ATTR_UNSPEC, + + MCPS802154_ATTR_HW, + MCPS802154_ATTR_WPAN_PHY_NAME, + + MCPS802154_ATTR_SCHEDULER_NAME, + MCPS802154_ATTR_SCHEDULER_PARAMS, + MCPS802154_ATTR_SCHEDULER_REGIONS, + + MCPS802154_ATTR_SCHEDULER_CALL, + MCPS802154_ATTR_SCHEDULER_CALL_PARAMS, + + MCPS802154_ATTR_SCHEDULER_REGION_CALL, + + MCPS802154_ATTR_TESTDATA, + + MCPS802154_ATTR_CALIBRATIONS, + + MCPS802154_ATTR_PWR_STATS, + + __MCPS802154_ATTR_AFTER_LAST, + MCPS802154_ATTR_MAX = __MCPS802154_ATTR_AFTER_LAST - 1 +}; + +/** + * enum mcps802154_region_attrs - Regions attributes. + * + * @MCPS802154_REGION_ATTR_ID: + * ID of the region, scheduler specific. + * @MCPS802154_REGION_ATTR_NAME: + * Name of the region, caller specific. + * @MCPS802154_REGION_ATTR_PARAMS: + * Parameters of the region. + * @MCPS802154_REGION_ATTR_CALL: + * Call id of the region's procedure, scheduler specific. + * @MCPS802154_REGION_ATTR_CALL_PARAMS: + * Parameters of the region's procedure, scheduler specific. + * + * @MCPS802154_REGION_UNSPEC: Invalid command. + * @__MCPS802154_REGION_AFTER_LAST: Internal use. + * @MCPS802154_REGION_MAX: Internal use. + */ +enum mcps802154_region_attrs { + MCPS802154_REGION_UNSPEC, + + MCPS802154_REGION_ATTR_ID, + MCPS802154_REGION_ATTR_NAME, + MCPS802154_REGION_ATTR_PARAMS, + MCPS802154_REGION_ATTR_CALL, + MCPS802154_REGION_ATTR_CALL_PARAMS, + + __MCPS802154_REGION_AFTER_LAST, + MCPS802154_REGION_MAX = __MCPS802154_REGION_AFTER_LAST - 1 +}; + +/** + * enum mcps802154_calibrations_attrs - Calibration item. + * + * @MCPS802154_CALIBRATIONS_ATTR_KEY: + * Calibration name. + * @MCPS802154_CALIBRATIONS_ATTR_VALUE: + * Calibration value. + * @MCPS802154_CALIBRATIONS_ATTR_STATUS: + * Status in case of error on write or read. + * + * @MCPS802154_CALIBRATIONS_ATTR_UNSPEC: Invalid command. + * @__MCPS802154_CALIBRATIONS_ATTR_AFTER_LAST: Internal use. + * @MCPS802154_CALIBRATIONS_ATTR_MAX: Internal use. + */ +enum mcps802154_calibrations_attrs { + MCPS802154_CALIBRATIONS_ATTR_UNSPEC, + + MCPS802154_CALIBRATIONS_ATTR_KEY, + MCPS802154_CALIBRATIONS_ATTR_VALUE, + MCPS802154_CALIBRATIONS_ATTR_STATUS, + + __MCPS802154_CALIBRATIONS_ATTR_AFTER_LAST, + MCPS802154_CALIBRATIONS_ATTR_MAX = + __MCPS802154_CALIBRATIONS_ATTR_AFTER_LAST - 1 +}; + +/** + * enum mcps802154_nl_pwr_stats_state_attrs - Power state item. + * + * @MCPS802154_PWR_STATS_STATE_ATTR_TIME: + * Time spent in this state. + * @MCPS802154_PWR_STATS_STATE_ATTR_COUNT: + * Number of transitions to this state. + * @MCPS802154_PWR_STATS_STATE_ATTR_UNSPEC: Invalid command. + * @__MCPS802154_PWR_STATS_STATE_ATTR_AFTER_LAST: Internal use. + * @MCPS802154_PWR_STATS_STATE_ATTR_MAX: Internal use. + */ +enum mcps802154_nl_pwr_stats_state_attrs { + MCPS802154_PWR_STATS_STATE_ATTR_UNSPEC, + + MCPS802154_PWR_STATS_STATE_ATTR_TIME, + MCPS802154_PWR_STATS_STATE_ATTR_COUNT, + + __MCPS802154_PWR_STATS_STATE_ATTR_AFTER_LAST, + MCPS802154_PWR_STATS_STATE_ATTR_MAX = + __MCPS802154_PWR_STATS_STATE_ATTR_AFTER_LAST - 1 +}; + +/** + * enum mcps802154_pwr_stats_attrs - Power statistics item. + * + * @MCPS802154_PWR_STATS_ATTR_SLEEP: + * Sleep state nested attribute. + * @MCPS802154_PWR_STATS_ATTR_IDLE: + * Idle state nested attribute. + * @MCPS802154_PWR_STATS_ATTR_RX: + * Rx state nested attribute. + * @MCPS802154_PWR_STATS_ATTR_TX: + * Tx state nested attribute. + * @MCPS802154_PWR_STATS_ATTR_INTERRUPTS: + * Interrupts count attribute. + * @MCPS802154_PWR_STATS_ATTR_UNSPEC: Invalid command. + * @__MCPS802154_PWR_STATS_ATTR_AFTER_LAST: Internal use. + * @MCPS802154_PWR_STATS_ATTR_MAX: Internal use. + */ +enum mcps802154_pwr_stats_attrs { + MCPS802154_PWR_STATS_ATTR_UNSPEC, + + MCPS802154_PWR_STATS_ATTR_SLEEP, + MCPS802154_PWR_STATS_ATTR_IDLE, + MCPS802154_PWR_STATS_ATTR_RX, + MCPS802154_PWR_STATS_ATTR_TX, + MCPS802154_PWR_STATS_ATTR_INTERRUPTS, + + __MCPS802154_PWR_STATS_ATTR_AFTER_LAST, + MCPS802154_PWR_STATS_ATTR_MAX = + __MCPS802154_PWR_STATS_ATTR_AFTER_LAST - 1 +}; + +#endif /* NET_MCPS802154_NL_H */ diff --git a/mac/include/net/mcps802154_schedule.h b/mac/include/net/mcps802154_schedule.h new file mode 100644 index 0000000..7355742 --- /dev/null +++ b/mac/include/net/mcps802154_schedule.h @@ -0,0 +1,859 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_SCHEDULE_H +#define NET_MCPS802154_SCHEDULE_H + +#include <linux/list.h> +#include <linux/module.h> +#include <linux/skbuff.h> +#include <net/genetlink.h> +#include <net/netlink.h> + +#include <net/mcps802154.h> + +struct mcps802154_nl_ranging_request; + +/** + * MCPS802154_DURATION_NO_CHANGE - Do not change duration. + */ +#define MCPS802154_DURATION_NO_CHANGE (-1) + +/** + * enum mcps802154_access_method - Method to implement an access. + * @MCPS802154_ACCESS_METHOD_NOTHING: + * Nothing to do, wait for end of region, or a schedule change. Internal, + * region handlers must return a NULL access if no access is possible. + * @MCPS802154_ACCESS_METHOD_IDLE: + * Nothing to do, wait for end of region, or a schedule change. + * Trust the access duration to not get the current time. + * @MCPS802154_ACCESS_METHOD_IMMEDIATE_RX: + * RX as soon as possible, without timeout, with auto-ack. + * @MCPS802154_ACCESS_METHOD_IMMEDIATE_TX: + * TX as soon as possible. Could be with or without ack request (AR). + * @MCPS802154_ACCESS_METHOD_MULTI: + * Multiple frames described in frames table. + * @MCPS802154_ACCESS_METHOD_VENDOR: + * Route all signals to access callbacks for vendor specific handling. + */ +enum mcps802154_access_method { + MCPS802154_ACCESS_METHOD_NOTHING, + MCPS802154_ACCESS_METHOD_IDLE, + MCPS802154_ACCESS_METHOD_IMMEDIATE_RX, + MCPS802154_ACCESS_METHOD_IMMEDIATE_TX, + MCPS802154_ACCESS_METHOD_MULTI, + MCPS802154_ACCESS_METHOD_VENDOR, +}; + +/** + * enum mcps802154_access_tx_return_reason - Reason of TX buffer return. + * @MCPS802154_ACCESS_TX_RETURN_REASON_CONSUMED: + * Frame was sent successfully. + * @MCPS802154_ACCESS_TX_RETURN_REASON_FAILURE: + * An attempt was done to deliver the frame, but it failed. + * @MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL: + * No attempt was done to deliver the frame, or there was an unexpected + * error doing it. + */ +enum mcps802154_access_tx_return_reason { + MCPS802154_ACCESS_TX_RETURN_REASON_CONSUMED, + MCPS802154_ACCESS_TX_RETURN_REASON_FAILURE, + MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL, + MCPS802154_TX_ERROR_HPDWARN, +}; + +/** + * struct mcps802154_access_frame - Information for a single frame for multiple + * frames method. + */ +struct mcps802154_access_frame { + /** + * @is_tx: True if frame is TX, else RX. + */ + bool is_tx; + union { + /** + * @tx_frame_config: Information for transmitting a frame. Should + * have rx_enable_after_tx_dtu == 0. + */ + struct mcps802154_tx_frame_config tx_frame_config; + /** + * @rx: Information for receiving a frame. + */ + struct { + /** + * @rx.frame_config: Information for enabling the + * receiver. + */ + struct mcps802154_rx_frame_config frame_config; + /** + * @rx.frame_info_flags_request: Information to request + * when a frame is received, see + * &enum mcps802154_rx_frame_info_flags. + */ + u16 frame_info_flags_request; + } rx; + }; + /** + * @sts_params: Pointer to STS parameters for this frame and all + * following frames. STS is still only used if requested in flags. For + * TX, this is read after mcps802154_access::tx_get_frame() is called, + * so it can be changed by the callback. For RX, this is read earlier, + * so it needs to be valid after mcps802154_access_ops::get_access(), or + * after the previous callback. + */ + const struct mcps802154_sts_params *sts_params; +}; + +/** + * struct mcps802154_region_demand - Access information for on demand + * schedulers. + */ +struct mcps802154_region_demand { + /** + * @timestamp_dtu: Start of the demand. + */ + u32 timestamp_dtu; + /** + * @max_duration_dtu: Maximum duration of the demand, 0 for endless. + */ + int max_duration_dtu; +}; + +/** + * struct mcps802154_access - Single medium access. + * + * This structure gives MCPS all the information needed to perform a single + * access. + */ +struct mcps802154_access { + /** + * @method: Method of access, see &enum mcps802154_access_method. + */ + enum mcps802154_access_method method; + union { + /** + * @common_ops: Callbacks to implement the access, common part + * to all access methods. + */ + struct mcps802154_access_common_ops *common_ops; + /** + * @ops: Callbacks to implement access with frames, unless + * a more specific structure is defined. + */ + struct mcps802154_access_ops *ops; + /** + * @vendor_ops: Callbacks to implement the vendor specific + * access. + */ + struct mcps802154_access_vendor_ops *vendor_ops; + }; + /** + * @timestamp_dtu: Start of the access, only valid when the access has + * a duration. Invalid for immediate accesses. + */ + u32 timestamp_dtu; + /** + * @duration_dtu: Duration of the access, or 0 if unknown (this is the + * case if the first frame is a RX with no timeout and the frame has not + * been received yet). + */ + int duration_dtu; + /** + * @n_frames: Number of frames in an access using multiple frames + * method. This can be changed by the &mcps802154_access_ops.rx_frame() + * callback. + */ + size_t n_frames; + /** + * @frames: Table of information for each frames in an access using + * multiple frames method. This can be changed by the + * &mcps802154_access_ops.rx_frame() callback. + */ + struct mcps802154_access_frame *frames; + /** + * @promiscuous: If true, promiscuous mode is activated for this access. + */ + bool promiscuous; + /** + * @hw_addr_filt: Hardware address filter parameters. This is used at the + * start of multiple frames access to change the filter for this access. + * The filter is restored after the access. + */ + struct ieee802154_hw_addr_filt hw_addr_filt; + /** + * @hw_addr_filt_changed: Hardware address filter parameters flags, + * see &enum ieee802154_hw_addr_filt_flags. + */ + unsigned long hw_addr_filt_changed; + /** + * @channel: If not %NULL, channel parameters for this access. This is + * set at the start of multiple frames access. Parameters are restored + * after the access. If %NULL, use default parameters set through + * ieee802154 interface. + */ + const struct mcps802154_channel *channel; + /** + * @hrp_uwb_params: If not NULL, parameters for a HRP UWB Phy set at the + * start of a multiple frames access. + */ + const struct mcps802154_hrp_uwb_params *hrp_uwb_params; + /** + * @error: contain the error from the llhw in order to propagate it to upper regions. + */ + int error; +}; + +/** + * struct mcps802154_access_common_ops - Callbacks to implement an access, + * common part to all access methods. + */ +struct mcps802154_access_common_ops { + /** + * @access_done: Called when the access is done, successfully or + * not, ignored if NULL. + * @error: Boolean used to signal internal MAC/driver/config error. + */ + void (*access_done)(struct mcps802154_access *access, bool error); +}; + +/** + * struct mcps802154_access_ops - Callbacks to implement an access. + * + * This is used for all accesses with frames unless a more specific structure is + * defined. + */ +struct mcps802154_access_ops { + /** + * @common: Common part of callbacks. + */ + struct mcps802154_access_common_ops common; + /** + * @rx_frame: Once a frame is received, it is given to this function. + * Buffer ownership is transferred to the callee. + * + * For multiple frames access method, the error parameter is used to + * inform of RX timeout or error. In case of RX timeout, info is NULL. + * In case of RX error, info can give some information about the error + * frame. + * + * The skb parameter can be NULL for frame without data. + */ + void (*rx_frame)(struct mcps802154_access *access, int frame_idx, + struct sk_buff *skb, + const struct mcps802154_rx_frame_info *info, + enum mcps802154_rx_error_type error); + /** + * @tx_get_frame: Return a frame to send, the buffer is lend to caller + * and should be returned with &mcps802154_access_ops.tx_return(). + * + * The return value can be NULL for frames without data. In this case, + * &mcps802154_access_ops.tx_return() will be called anyway, with a NULL + * pointer. + */ + struct sk_buff *(*tx_get_frame)(struct mcps802154_access *access, + int frame_idx); + /** + * @tx_return: Give back an unmodified buffer. + */ + void (*tx_return)(struct mcps802154_access *access, int frame_idx, + struct sk_buff *skb, + enum mcps802154_access_tx_return_reason reason); + /** + * @access_extend: Extend the current access with new frames. + * + * Current frame index and n_frames are reset before call. + * The region can override the frames array to extend the current + * access. + */ + void (*access_extend)(struct mcps802154_access *access); +}; + +/** + * struct mcps802154_access_vendor_ops - Callbacks to implement a vendor + * specific access. + * + * Each callback can return 0 to continue the access, 1 to stop it or an error. + * + * If access is stopped, the &mcps802154_access.timestamp_dtu and + * &mcps802154_access.duration_dtu are used to compute the next access, unless + * duration is 0, in this case the current date is requested from the driver. + * + * In case of error, the devices transition to the broken state. + * + * If the callback is missing this is treated like an error, except for + * &mcps802154_access_vendor_ops.handle, + * &mcps802154_access_vendor_ops.timer_expired and + * &mcps802154_access_vendor_ops.schedule_change which are ignored. + */ +struct mcps802154_access_vendor_ops { + /** + * @common: Common part of callbacks. + */ + struct mcps802154_access_common_ops common; + /** + * @handle: Called once to start the access, ignored if NULL. + */ + int (*handle)(struct mcps802154_access *access); + /** + * @rx_frame: Called when a frame reception is signaled, error if NULL. + */ + int (*rx_frame)(struct mcps802154_access *access); + /** + * @rx_timeout: Called when a reception timeout is signaled, error if NULL. + */ + int (*rx_timeout)(struct mcps802154_access *access); + /** + * @rx_error: Called when a reception error is signaled, error if NULL. + */ + int (*rx_error)(struct mcps802154_access *access, + enum mcps802154_rx_error_type error); + /** + * @tx_done: Called when end of transmission is signaled, error if NULL. + */ + int (*tx_done)(struct mcps802154_access *access); + /** + * @broken: Called when a unrecoverable error is signaled, error if NULL. + */ + int (*broken)(struct mcps802154_access *access); + /** + * @timer_expired: Called when a timer expired event is signaled, + * ignored if NULL. + */ + int (*timer_expired)(struct mcps802154_access *access); + /** + * @schedule_change: Called to handle schedule change, ignored if NULL. + */ + int (*schedule_change)(struct mcps802154_access *access); +}; + +/** + * struct mcps802154_region - An open region instance. Region handlers can have + * private data appended after this structure. + */ +struct mcps802154_region { + /** + * @ops: Callbacks for the region. + */ + const struct mcps802154_region_ops *ops; + /** + * @ca_entry: Entry in list of CA regions. + */ + struct list_head ca_entry; + /** + * @id: Assigned region ID. + */ + int id; +}; + +/** + * struct mcps802154_region_ops - Region callbacks, handle access for a specific + * region in schedule. + */ +struct mcps802154_region_ops { + /** + * @owner: Module owning this region, should be THIS_MODULE in most + * cases. + */ + struct module *owner; + /** + * @name: Region name. + */ + const char *name; + /** + * @registered_entry: Entry in list of registered regions. + */ + struct list_head registered_entry; + /** + * @open: Open an instance of this region, return a new region instance, + * or NULL in case of error. + */ + struct mcps802154_region *(*open)(struct mcps802154_llhw *llhw); + /** + * @close: Close a region instance. + */ + void (*close)(struct mcps802154_region *region); + /** + * @notify_stop: Notify a region that device has been stopped. + */ + void (*notify_stop)(struct mcps802154_region *region); + /** + * @set_parameters: Set region parameters, may be NULL. + */ + int (*set_parameters)(struct mcps802154_region *region, + const struct nlattr *attrs, + struct netlink_ext_ack *extack); + /** + * @call: Call region procedure, may be NULL. + */ + int (*call)(struct mcps802154_region *region, u32 call_id, + const struct nlattr *attrs, const struct genl_info *info); + /** + * @get_demand: Get access demand from a region, may be NULL. + */ + int (*get_demand)(struct mcps802154_region *region, + u32 next_timestamp_dtu, + struct mcps802154_region_demand *demand); + /** + * @get_access: Get access for a given region at the given timestamp. + * Access is valid until &mcps802154_access_ops.access_done() callback + * is called. Return NULL if access is not possible. + */ + struct mcps802154_access *(*get_access)( + struct mcps802154_region *region, u32 next_timestamp_dtu, + int next_in_region_dtu, int region_duration_dtu); + /** + * @xmit_skb: Transmit buffer. Warning: Bypass the FSM lock mechanism. + * Return 1 if the region accepted to transmit the buffer, 0 otherwise. + */ + int (*xmit_skb)(struct mcps802154_region *region, struct sk_buff *skb); + /** + * @deferred: Called at the end of event processing on request. See + * mcps802154_region_deferred. + */ + void (*deferred)(struct mcps802154_region *region); +}; + +/** + * struct mcps802154_schedule_update - Context valid during a schedule + * update. + */ +struct mcps802154_schedule_update { + /** + * @expected_start_timestamp_dtu: Expected start timestamp, based on the + * current access date and having the new schedule put right after the + * old one. + */ + u32 expected_start_timestamp_dtu; + /** + * @start_timestamp_dtu: Date of the schedule start, might be too far in + * the past for endless schedule. + */ + u32 start_timestamp_dtu; + /** + * @duration_dtu: Schedule duration or 0 for endless schedule. This is + * also 0 when the schedule is empty. + */ + int duration_dtu; + /** + * @n_regions: Number of regions in the schedule. + */ + size_t n_regions; +}; + +/** + * struct mcps802154_scheduler - An open scheduler instance. Schedulers can have + * private data appended after this structure. + */ +struct mcps802154_scheduler { + /** + * @n_regions: Number of regions possible to add in the scheduler, + * zero means infinite number of regions can be added to the scheduler. + */ + u32 n_regions; + /** + * @ops: Callbacks for the scheduler. + */ + const struct mcps802154_scheduler_ops *ops; +}; + +/** + * struct mcps802154_scheduler_ops - Callbacks for schedulers. A scheduler + * provides a schedule to MCPS and updates it when specific frames are + * received or schedule is no longer valid. + */ +struct mcps802154_scheduler_ops { + /** + * @owner: Module owning this scheduler, should be THIS_MODULE in most + * cases. + */ + struct module *owner; + /** + * @name: Scheduler name. + */ + const char *name; + /** + * @registered_entry: Entry in list of registered schedulers. + */ + struct list_head registered_entry; + /** + * @open: Attach a scheduler to a device. + */ + struct mcps802154_scheduler *(*open)(struct mcps802154_llhw *llhw); + /** + * @close: Detach and close a scheduler. + */ + void (*close)(struct mcps802154_scheduler *scheduler); + /** + * @notify_stop: Notify a scheduler that device has been stopped. + */ + void (*notify_stop)(struct mcps802154_scheduler *scheduler); + /** + * @set_parameters: Configure the scheduler. + */ + int (*set_parameters)(struct mcps802154_scheduler *scheduler, + const struct nlattr *attrs, + struct netlink_ext_ack *extack); + /** + * @call: Call scheduler specific procedure. + */ + int (*call)(struct mcps802154_scheduler *scheduler, u32 call_id, + const struct nlattr *attrs, const struct genl_info *info); + /** + * @update_schedule: Called to initialize and update the schedule. + */ + int (*update_schedule)( + struct mcps802154_scheduler *scheduler, + const struct mcps802154_schedule_update *schedule_update, + u32 next_timestamp_dtu); + /** + * @ranging_setup: Called to configure ranging. This is a temporary + * interface. + */ + int (*ranging_setup)( + struct mcps802154_scheduler *scheduler, + const struct mcps802154_nl_ranging_request *requests, + unsigned int n_requests); + /** + * @get_next_demands: Called to get an aggregated demand for the specified + * region. + */ + int (*get_next_demands)(struct mcps802154_scheduler *scheduler, + const struct mcps802154_region *region, + u32 timestamp_dtu, int duration_dtu, + int delta_dtu, + struct mcps802154_region_demand *demands); +}; + +/** + * mcps802154_region_register() - Register a region, to be called when your + * module is loaded. + * @region_ops: Region to register. + * + * Return: 0 or error. + */ +int mcps802154_region_register(struct mcps802154_region_ops *region_ops); + +/** + * mcps802154_region_unregister() - Unregister a region, to be called at module + * unloading. + * @region_ops: Region to unregister. + */ +void mcps802154_region_unregister(struct mcps802154_region_ops *region_ops); + +/** + * mcps802154_region_open() - Open a region, and set parameters. + * @llhw: Low-level device pointer. + * @name: Name of region to open. + * @params_attr: Nested attribute containing region parameters, may be NULL. + * @extack: Extended ACK report structure. + * + * Return: The open region or NULL on error. + */ +struct mcps802154_region * +mcps802154_region_open(struct mcps802154_llhw *llhw, const char *name, + const struct nlattr *params_attr, + struct netlink_ext_ack *extack); + +/** + * mcps802154_region_close() - Close a region. + * @llhw: Low-level device pointer. + * @region: Pointer to the open region. + */ +void mcps802154_region_close(struct mcps802154_llhw *llhw, + struct mcps802154_region *region); + +/** + * mcps802154_region_notify_stop() - Notify a region that device has been + * stopped. + * @llhw: Low-level device pointer. + * @region: Pointer to the open region. + */ +void mcps802154_region_notify_stop(struct mcps802154_llhw *llhw, + struct mcps802154_region *region); + +/** + * mcps802154_region_set_parameters() - Set parameters of an open region. + * @llhw: Low-level device pointer. + * @region: Pointer to the open region. + * @params_attr: Nested attribute containing region parameters, may be NULL. + * @extack: Extended ACK report structure. + * + * Return: 0 or error. + */ +int mcps802154_region_set_parameters(struct mcps802154_llhw *llhw, + struct mcps802154_region *region, + const struct nlattr *params_attr, + struct netlink_ext_ack *extack); + +/** + * mcps802154_region_call() - Call specific procedure in this region. + * @llhw: Low-level device pointer. + * @region: Pointer to the open region. + * @call_id: Identifier of the procedure, region specific. + * @params_attr: Nested attribute containing region parameters, may be NULL. + * @info: Request information. + * + * Return: 0 or error. + */ +int mcps802154_region_call(struct mcps802154_llhw *llhw, + struct mcps802154_region *region, u32 call_id, + const struct nlattr *params_attr, + const struct genl_info *info); + +/** + * mcps802154_region_get_demand() - Get access demand from a region in order to + * help building a schedule. + * @llhw: Low-level device pointer. + * @region: Pointer to the open region. + * @next_timestamp_dtu: Date of next access opportunity. + * @demand: Where to write the demand. + * + * If the region does not implement access demand, this function will request an + * endless demand starting at the next access opportunity. + * + * Return: 1 if data is available, 0 if no demand (do not want access) or error. + */ +int mcps802154_region_get_demand(struct mcps802154_llhw *llhw, + struct mcps802154_region *region, + u32 next_timestamp_dtu, + struct mcps802154_region_demand *demand); + +/** + * mcps802154_region_call_alloc_reply_skb() - Allocate buffer to send + * a response for a region call. + * @llhw: Low-level device pointer. + * @region: Pointer to the open region. + * @call_id: Identifier of the procedure, region specific. + * @approx_len: Upper bound of the data to be put into the buffer. + * + * Return: An allocated and pre-filled buffer, or NULL on error. + */ +struct sk_buff * +mcps802154_region_call_alloc_reply_skb(struct mcps802154_llhw *llhw, + struct mcps802154_region *region, + u32 call_id, int approx_len); + +/** + * mcps802154_region_call_reply() - Send a previously allocated and filled + * reply buffer. + * @llhw: Low-level device pointer. + * @skb: Buffer to send. + * + * Return: 0 or error. + */ +int mcps802154_region_call_reply(struct mcps802154_llhw *llhw, + struct sk_buff *skb); + +/** + * mcps802154_region_event_alloc_skb() - Allocate buffer to send a notification + * for a region. + * @llhw: Low-level device pointer. + * @region: Pointer to the open region. + * @call_id: Identifier of the procedure, region specific. + * @portid: Port identifier of the receiver. + * @approx_len: Upper bound of the data to be put into the buffer. + * @gfp: Allocation flags. + * + * Return: An allocated and pre-filled buffer, or NULL on error. + */ +struct sk_buff * +mcps802154_region_event_alloc_skb(struct mcps802154_llhw *llhw, + struct mcps802154_region *region, u32 call_id, + u32 portid, int approx_len, gfp_t gfp); + +/** + * mcps802154_region_event() - Send a previously allocated and filled + * buffer. + * @llhw: Low-level device pointer. + * @skb: Buffer to send. + * + * Return: 0 or error. + */ +int mcps802154_region_event(struct mcps802154_llhw *llhw, struct sk_buff *skb); + +/** + * mcps802154_region_xmit_resume() - Signal buffer transmit can resume. + * @llhw: Low-level device pointer. + * @region: Pointer to the open region. + * @queue_index: Queue index. + */ +void mcps802154_region_xmit_resume(struct mcps802154_llhw *llhw, + struct mcps802154_region *region, + int queue_index); + +/** + * mcps802154_region_xmit_done() - Signal buffer transmit completion. + * @llhw: Low-level device pointer. + * @region: Pointer to the open region. + * @skb: Buffer. + * @ok: True if the buffer was successfully transmitted. + */ +void mcps802154_region_xmit_done(struct mcps802154_llhw *llhw, + struct mcps802154_region *region, + struct sk_buff *skb, bool ok); + +/** + * mcps802154_region_rx_skb() - Signal the reception of a buffer. + * @llhw: Low-level device pointer. + * @region: Pointer to the open region. + * @skb: Received buffer. + * @lqi: Link Quality Indicator (LQI). + */ +void mcps802154_region_rx_skb(struct mcps802154_llhw *llhw, + struct mcps802154_region *region, + struct sk_buff *skb, u8 lqi); + +/** + * mcps802154_region_deferred() - Request to call the deferred callback at the + * end of event processing. + * @llhw: Low-level device pointer. + * @region: Pointer to the open region. + * + * Event is coming from the low-level device. The region must be the one which + * triggered the event (region must not call this after a get_access). If this + * is not respected, this call can return -EINVAL in case two regions request + * the deferred callback at the same time. + * + * Return: 0 or -EINVAL. + */ +int mcps802154_region_deferred(struct mcps802154_llhw *llhw, + struct mcps802154_region *region); + +/** + * mcps802154_scheduler_register() - Register a scheduler, to be called when + * your module is loaded. + * @scheduler_ops: Scheduler to register. + * + * Return: 0 or error. + */ +int mcps802154_scheduler_register( + struct mcps802154_scheduler_ops *scheduler_ops); + +/** + * mcps802154_scheduler_unregister() - Unregister a scheduler, to be called at + * module unloading. + * @scheduler_ops: Scheduler to unregister. + */ +void mcps802154_scheduler_unregister( + struct mcps802154_scheduler_ops *scheduler_ops); + +/** + * mcps802154_schedule_set_start() - Change the currently updated schedule start + * timestamp. + * @schedule_update: Schedule update context. + * @start_timestamp_dtu: New start timestamp. + * + * Return: 0 or -EINVAL if arguments are garbage. + */ +int mcps802154_schedule_set_start( + const struct mcps802154_schedule_update *schedule_update, + u32 start_timestamp_dtu); + +/** + * mcps802154_schedule_recycle() - Purge or recycle the current schedule. + * @schedule_update: Schedule update context. + * @n_keeps: Number of regions to keep from the previous schedule. + * @last_region_duration_dtu: + * Duration of the last region, or MCPS802154_DURATION_NO_CHANGE to keep it + * unchanged. + * + * Return: 0 or -EINVAL if arguments are garbage. + */ +int mcps802154_schedule_recycle( + const struct mcps802154_schedule_update *schedule_update, + size_t n_keeps, int last_region_duration_dtu); + +/** + * mcps802154_schedule_add_region() - Add a new region to the currently updated + * schedule. + * @schedule_update: Schedule update context. + * @region: Region to add. + * @start_dtu: Region start from the start of the schedule. + * @duration_dtu: Region duration, or 0 for endless region. + * @once: Schedule the region once, ignoring the remaining region duration. + * + * Return: 0 or error. + */ +int mcps802154_schedule_add_region( + const struct mcps802154_schedule_update *schedule_update, + struct mcps802154_region *region, int start_dtu, int duration_dtu, + bool once); + +/** + * mcps802154_reschedule() - Request to change access as possible. + * @llhw: Low-level device pointer. + * + * Use this to reevaluate the current access as new data is available. For + * example, the device may be sleeping, or waiting to receive a frame, and you + * have a fresh frame to send. + * + * Request may be ignored if the device is busy, in which case the current + * access will be done before the new access is examined. + */ +void mcps802154_reschedule(struct mcps802154_llhw *llhw); + +/** + * mcps802154_schedule_invalidate() - Request to invalidate the schedule. + * @llhw: Low-level device pointer. + * + * FSM mutex should be locked. + * + * Invalidate the current schedule, which will result on a schedule change. + * This API should be called from external modules to force schedule change, + * when for example some parameters changed. + */ +void mcps802154_schedule_invalidate(struct mcps802154_llhw *llhw); + +/** + * mcps802154_schedule_get_regions() - Get opened regions. + * @llhw: Low-level device pointer. + * @regions: pointer to list of regions. + * + * FSM mutex should be locked. + * + * Get the list of current regions opened. + * + * Return: Number of regions. + */ +int mcps802154_schedule_get_regions(struct mcps802154_llhw *llhw, + struct list_head **regions); + +/** + * mcps802154_schedule_get_next_demands() - Get an aggregated demand for the + * specified region. + * @llhw: Low-level device pointer. + * @region: Region. + * @timestamp_dtu: Timestamp from which demands must be computed. + * @duration_dtu: Duration for which demands are considered. + * @delta_dtu: Maximum gap between two demands. + * @demands: Aggregated demand. + * + * Return: 1 if demand is returned, 0 if no demand or error. + */ +int mcps802154_schedule_get_next_demands( + struct mcps802154_llhw *llhw, const struct mcps802154_region *region, + u32 timestamp_dtu, int duration_dtu, int delta_dtu, + struct mcps802154_region_demand *demands); + +#endif /* NET_MCPS802154_SCHEDULE_H */ diff --git a/mac/include/net/mcps_skb_frag.h b/mac/include/net/mcps_skb_frag.h new file mode 100644 index 0000000..78418f0 --- /dev/null +++ b/mac/include/net/mcps_skb_frag.h @@ -0,0 +1,34 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/skbuff.h> + +/** + * mcps_skb_frags_len() - Return the total length of the fragments attached to this buffer. + * @skb: Pointer to buffer. + * + * Return: Attached fragments length. + * + * NOTE: The parent length is NOT included in the computed value + */ +int mcps_skb_frags_len(struct sk_buff *skb); diff --git a/mac/include/net/nfcc_coex_region_nl.h b/mac/include/net/nfcc_coex_region_nl.h new file mode 100644 index 0000000..169691a --- /dev/null +++ b/mac/include/net/nfcc_coex_region_nl.h @@ -0,0 +1,96 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NFCC_COEX_REGION_NL_H +#define NFCC_COEX_REGION_NL_H + +/** + * enum nfcc_coex_call - NFCC coexistence calls identifiers. + * + * @NFCC_COEX_CALL_CCC_SESSION_START: + * Start CCC session. + * @NFCC_COEX_CALL_CCC_SESSION_STOP: + * Stop CCC session. + * @NFCC_COEX_CALL_CCC_SESSION_NOTIFICATION: + * Notify session reports. + * @NFCC_COEX_CALL_MAX: Internal use. + */ +enum nfcc_coex_call { + NFCC_COEX_CALL_CCC_SESSION_START, + NFCC_COEX_CALL_CCC_SESSION_STOP, + NFCC_COEX_CALL_CCC_SESSION_NOTIFICATION, + NFCC_COEX_CALL_MAX, +}; + +/** + * enum nfcc_coex_call_attrs - NFCC coexistence call attributes. + * + * @NFCC_COEX_CALL_ATTR_CCC_SESSION_PARAMS: + * Session parameters. + * @NFCC_COEX_CALL_ATTR_CCC_WATCHDOG_TIMEOUT: + * Watchdog trigged. + * @NFCC_COEX_CALL_ATTR_CCC_STOPPED: + * Session stopped. + * + * @NFCC_COEX_CALL_ATTR_UNSPEC: Invalid command. + * @__NFCC_COEX_CALL_ATTR_AFTER_LAST: Internal use. + * @NFCC_COEX_CALL_ATTR_MAX: Internal use. + */ +enum nfcc_coex_call_attrs { + NFCC_COEX_CALL_ATTR_UNSPEC, + + NFCC_COEX_CALL_ATTR_CCC_SESSION_PARAMS, + NFCC_COEX_CALL_ATTR_CCC_WATCHDOG_TIMEOUT, + NFCC_COEX_CALL_ATTR_CCC_STOPPED, + + __NFCC_COEX_CALL_ATTR_AFTER_LAST, + NFCC_COEX_CALL_ATTR_MAX = __NFCC_COEX_CALL_ATTR_AFTER_LAST - 1 +}; + +/** + * enum nfcc_coex_ccc_session_param_attrs - NFCC coexistence session parameters attributes. + * + * @NFCC_COEX_CCC_SESSION_PARAM_ATTR_TIME0_NS: + * Initiation time in unit of ns, default 0. + * @NFCC_COEX_CCC_SESSION_PARAM_ATTR_CHANNEL_NUMBER: + * Override channel for this session: 5, 6, 8, 9, 10, 12, 13 or 14. + * @NFCC_COEX_CCC_SESSION_PARAM_ATTR_VERSION: + * Protocol version to be used. + * + * @NFCC_COEX_CCC_SESSION_PARAM_ATTR_UNSPEC: Invalid command. + * @__NFCC_COEX_CCC_SESSION_PARAM_ATTR_AFTER_LAST: Internal use. + * @NFCC_COEX_CCC_SESSION_PARAM_ATTR_MAX: Internal use. + */ +enum nfcc_coex_ccc_session_param_attrs { + NFCC_COEX_CCC_SESSION_PARAM_ATTR_UNSPEC, + + NFCC_COEX_CCC_SESSION_PARAM_ATTR_TIME0_NS, + NFCC_COEX_CCC_SESSION_PARAM_ATTR_CHANNEL_NUMBER, + NFCC_COEX_CCC_SESSION_PARAM_ATTR_VERSION, + + __NFCC_COEX_CCC_SESSION_PARAM_ATTR_AFTER_LAST, + NFCC_COEX_CCC_SESSION_PARAM_ATTR_MAX = + __NFCC_COEX_CCC_SESSION_PARAM_ATTR_AFTER_LAST - 1 +}; + +#endif /* NFCC_COEX_REGION_NL_H */ diff --git a/mac/include/net/pctt_region_nl.h b/mac/include/net/pctt_region_nl.h new file mode 100644 index 0000000..f9a674a --- /dev/null +++ b/mac/include/net/pctt_region_nl.h @@ -0,0 +1,241 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_PCTT_REGION_NL_H +#define NET_PCTT_REGION_NL_H + +/** + * enum pctt_call - PCTT calls identifiers. + * + * @PCTT_CALL_SESSION_INIT: + * Initialize PCTT session. + * @PCTT_CALL_SESSION_CMD: + * Identifier of command to do: start test, stop. + * TODO: Could be move in session parameters. + * @PCTT_CALL_SESSION_DEINIT: + * Deinit PCTT session. + * @PCTT_CALL_SESSION_GET_STATE: + * Get session state. + * @PCTT_CALL_SESSION_GET_PARAMS: + * Request parameters. + * @PCTT_CALL_SESSION_SET_PARAMS: + * Set session parameters. + * @PCTT_CALL_SESSION_NOTIFICATION: + * Notify session reports. + * @PCTT_CALL_MAX: Internal use. + */ +enum pctt_call { + PCTT_CALL_SESSION_INIT, + PCTT_CALL_SESSION_CMD, + PCTT_CALL_SESSION_DEINIT, + PCTT_CALL_SESSION_GET_STATE, + PCTT_CALL_SESSION_GET_PARAMS, + PCTT_CALL_SESSION_SET_PARAMS, + PCTT_CALL_SESSION_NOTIFICATION, + PCTT_CALL_MAX, +}; + +enum pctt_call_attrs { + PCTT_CALL_ATTR_UNSPEC, + PCTT_CALL_ATTR_CMD_ID, + PCTT_CALL_ATTR_RESULT_DATA, + PCTT_CALL_ATTR_SESSION_ID, + PCTT_CALL_ATTR_SESSION_STATE, + PCTT_CALL_ATTR_SESSION_PARAMS, + + __PCTT_CALL_ATTR_AFTER_LAST, + PCTT_CALL_ATTR_MAX = __PCTT_CALL_ATTR_AFTER_LAST - 1, +}; + +/** + * enum pctt_session_param_attrs - Pctt session parameters attributes. + * + * @PCTT_SESSION_PARAM_ATTR_DEVICE_ROLE: + * Responder (0) or initiator (1) + * @PCTT_SESSION_PARAM_ATTR_SHORT_ADDR: + * Override device address for this session (UCI: DEVICE_MAC_ADDRESS) + * @PCTT_SESSION_PARAM_ATTR_DESTINATION_SHORT_ADDR: + * Controller short addresses (UCI: DST_MAC_ADDRESS) [controlee only] + * @PCTT_SESSION_PARAM_ATTR_RX_ANTENNA_SELECTION: + * Antenna set to use during RX phases + * @PCTT_SESSION_PARAM_ATTR_TX_ANTENNA_SELECTION: + * Antenna set to use during Tx phases + * @PCTT_SESSION_PARAM_ATTR_SLOT_DURATION_RSTU: + * Duration of a slot in RSTU, default 2400. (2 ms) + * @PCTT_SESSION_PARAM_ATTR_CHANNEL_NUMBER: + * Override channel for this session: 5, 6, 8, 9, 10, 12, 13 or 14 + * @PCTT_SESSION_PARAM_ATTR_PREAMBLE_CODE_INDEX: + * Override preamble code for this session, BPRF (9-24), + * HPRF (25-32, not supported) + * @PCTT_SESSION_PARAM_ATTR_RFRAME_CONFIG: + * SP0 (0), SP1 (1), SP2 (2, unused, not in PcTt 1.1) or SP3 (3, default) + * @PCTT_SESSION_PARAM_ATTR_PRF_MODE: + * BPRF (0, default) or HPRF (1, not supported) + * @PCTT_SESSION_PARAM_ATTR_PREAMBLE_DURATION: + * 64 (1, default) or 32 (0, only for HPRF) + * @PCTT_SESSION_PARAM_ATTR_SFD_ID: + * BPRF (0 or 2), HPRF (1-4, not supported), default 2 + * @PCTT_SESSION_PARAM_ATTR_NUMBER_OF_STS_SEGMENTS: + * 0-2, default to 0 for SP0, default to 1 for SP1 & SP3, 2 not supported + * @PCTT_SESSION_PARAM_ATTR_PSDU_DATA_RATE: + * 6.81 Mbps (0, default), 7.80 Mbps (1, not supported), + * 27.2 Mbps (2, not supported), 31.2 Mbps (3, not supported) + * @PCTT_SESSION_PARAM_ATTR_BPRF_PHR_DATA_RATE: + * 850 kbps (0, default) or 6.81 Mbps (1) + * @PCTT_SESSION_PARAM_ATTR_MAC_FCS_TYPE: + * CRC16 (0, default) or CRC32 (1, not supported) + * @PCTT_SESSION_PARAM_ATTR_TX_ADAPTIVE_PAYLOAD_POWER: + * Disable adaptive payload power for TX (0, default) or enable (1) + * @PCTT_SESSION_PARAM_ATTR_STS_INDEX: + * STS index initialization value + * @PCTT_SESSION_PARAM_ATTR_STS_LENGTH: + * Number of symbols in a STS segment. 32 (0x00), 64 (0x01, default) or 128 + * symbols (0x02) + * @PCTT_SESSION_PARAM_ATTR_NUM_PACKETS: + * Number of packets (default 1000). + * @PCTT_SESSION_PARAM_ATTR_T_GAP: + * Gap between start of one packet to the next in µs (default 2000). + * @PCTT_SESSION_PARAM_ATTR_T_START: + * Max. time from the start of T_GAP to SFD found state in µs (default + * 450us). + * @PCTT_SESSION_PARAM_ATTR_T_WIN: + * Max. time for which RX is looking for a packet from the start of T_GAP + * in µs (default 750). + * @PCTT_SESSION_PARAM_ATTR_RANDOMIZE_PSDU: + * Disable (0, default) or enable (1) PSDU randomization. + * @PCTT_SESSION_PARAM_ATTR_PHR_RANGING_BIT: + * Disable (0, default) or enable (1) ranging bit field of PHR in both BPRF + * and HPRF. + * @PCTT_SESSION_PARAM_ATTR_RMARKER_TX_START: + * Start time of TX in 1/(128*499.2MHz) units. + * @PCTT_SESSION_PARAM_ATTR_RMARKER_RX_START: + * Start time of RX in 1/(128*499.2MHz) units. + * @PCTT_SESSION_PARAM_ATTR_STS_INDEX_AUTO_INCR: + * Disable (0, default) or enable (1) incrementation of STS_INDEX config + * value for every frame in PER Rx/Periodic TX test. + * @PCTT_SESSION_PARAM_ATTR_DATA_PAYLOAD: + * PSDU Data. + * @PCTT_SESSION_PARAM_ATTR_UNSPEC: Invalid command. + * @__PCTT_SESSION_PARAM_ATTR_AFTER_LAST: Internal use. + * @PCTT_SESSION_PARAM_ATTR_MAX: Internal use. + */ +enum pctt_session_param_attrs { + PCTT_SESSION_PARAM_ATTR_UNSPEC, + /* Main session parameters */ + PCTT_SESSION_PARAM_ATTR_DEVICE_ROLE, + PCTT_SESSION_PARAM_ATTR_SHORT_ADDR, + PCTT_SESSION_PARAM_ATTR_DESTINATION_SHORT_ADDR, + PCTT_SESSION_PARAM_ATTR_RX_ANTENNA_SELECTION, + PCTT_SESSION_PARAM_ATTR_TX_ANTENNA_SELECTION, + /* Timings */ + PCTT_SESSION_PARAM_ATTR_SLOT_DURATION_RSTU, + /* Radio */ + PCTT_SESSION_PARAM_ATTR_CHANNEL_NUMBER, + PCTT_SESSION_PARAM_ATTR_PREAMBLE_CODE_INDEX, + PCTT_SESSION_PARAM_ATTR_RFRAME_CONFIG, + PCTT_SESSION_PARAM_ATTR_PRF_MODE, + PCTT_SESSION_PARAM_ATTR_PREAMBLE_DURATION, + PCTT_SESSION_PARAM_ATTR_SFD_ID, + PCTT_SESSION_PARAM_ATTR_NUMBER_OF_STS_SEGMENTS, + PCTT_SESSION_PARAM_ATTR_PSDU_DATA_RATE, + PCTT_SESSION_PARAM_ATTR_BPRF_PHR_DATA_RATE, + PCTT_SESSION_PARAM_ATTR_MAC_FCS_TYPE, + PCTT_SESSION_PARAM_ATTR_TX_ADAPTIVE_PAYLOAD_POWER, + /* STS and crypto */ + PCTT_SESSION_PARAM_ATTR_STS_INDEX, + PCTT_SESSION_PARAM_ATTR_STS_LENGTH, + /* Test configuration parameters */ + PCTT_SESSION_PARAM_ATTR_NUM_PACKETS, + PCTT_SESSION_PARAM_ATTR_T_GAP, + PCTT_SESSION_PARAM_ATTR_T_START, + PCTT_SESSION_PARAM_ATTR_T_WIN, + PCTT_SESSION_PARAM_ATTR_RANDOMIZE_PSDU, + PCTT_SESSION_PARAM_ATTR_PHR_RANGING_BIT, + PCTT_SESSION_PARAM_ATTR_RMARKER_TX_START, + PCTT_SESSION_PARAM_ATTR_RMARKER_RX_START, + PCTT_SESSION_PARAM_ATTR_STS_INDEX_AUTO_INCR, + /* Payload */ + PCTT_SESSION_PARAM_ATTR_DATA_PAYLOAD, + + __PCTT_SESSION_PARAM_ATTR_AFTER_LAST, + PCTT_SESSION_PARAM_ATTR_MAX = __PCTT_SESSION_PARAM_ATTR_AFTER_LAST - 1 +}; + +enum pctt_id_attrs { + PCTT_ID_ATTR_UNSPEC, + + PCTT_ID_ATTR_PERIODIC_TX, + PCTT_ID_ATTR_PER_RX, + PCTT_ID_ATTR_RX, + PCTT_ID_ATTR_LOOPBACK, + PCTT_ID_ATTR_SS_TWR, + PCTT_ID_ATTR_STOP_TEST, + + __PCTT_ID_ATTR_AFTER_LAST, + PCTT_ID_ATTR_MAX = __PCTT_ID_ATTR_AFTER_LAST - 1 +}; + +enum pctt_result_data_attrs { + PCTT_RESULT_DATA_ATTR_UNSPEC, + + PCTT_RESULT_DATA_ATTR_STATUS, + PCTT_RESULT_DATA_ATTR_ATTEMPTS, + PCTT_RESULT_DATA_ATTR_ACQ_DETECT, + PCTT_RESULT_DATA_ATTR_ACQ_REJECT, + PCTT_RESULT_DATA_ATTR_RX_FAIL, + PCTT_RESULT_DATA_ATTR_SYNC_CIR_READY, + PCTT_RESULT_DATA_ATTR_SFD_FAIL, + PCTT_RESULT_DATA_ATTR_SFD_FOUND, + PCTT_RESULT_DATA_ATTR_PHR_DEC_ERROR, + PCTT_RESULT_DATA_ATTR_PHR_BIT_ERROR, + PCTT_RESULT_DATA_ATTR_PSDU_DEC_ERROR, + PCTT_RESULT_DATA_ATTR_PSDU_BIT_ERROR, + PCTT_RESULT_DATA_ATTR_STS_FOUND, + PCTT_RESULT_DATA_ATTR_EOF, + + PCTT_RESULT_DATA_ATTR_RX_DONE_TS_INT, + PCTT_RESULT_DATA_ATTR_RX_DONE_TS_FRAC, + PCTT_RESULT_DATA_ATTR_AOA_AZIMUTH, + PCTT_RESULT_DATA_ATTR_AOA_ELEVATION, + PCTT_RESULT_DATA_ATTR_TOA_GAP, + PCTT_RESULT_DATA_ATTR_PHR, + PCTT_RESULT_DATA_ATTR_PSDU_DATA_LEN, + PCTT_RESULT_DATA_ATTR_PSDU_DATA, + PCTT_RESULT_DATA_ATTR_TX_TS_INT, + PCTT_RESULT_DATA_ATTR_TX_TS_FRAC, + PCTT_RESULT_DATA_ATTR_RX_TS_INT, + PCTT_RESULT_DATA_ATTR_RX_TS_FRAC, + + PCTT_RESULT_DATA_ATTR_MEASUREMENT, + + PCTT_RESULT_DATA_ATTR_PDOA_AZIMUTH_DEG_Q7, + PCTT_RESULT_DATA_ATTR_PDOA_ELEVATION_DEG_Q7, + PCTT_RESULT_DATA_ATTR_RSSI, + PCTT_RESULT_DATA_ATTR_AOA_AZIMUTH_DEG_Q7, + PCTT_RESULT_DATA_ATTR_AOA_ELEVATION_DEG_Q7, + + __PCTT_RESULT_DATA_ATTR_AFTER_LAST, + PCTT_RESULT_DATA_ATTR_MAX = __PCTT_RESULT_DATA_ATTR_AFTER_LAST - 1, +}; + +#endif /* NET_PCTT_REGION_NL_H */ diff --git a/mac/include/net/pctt_region_params.h b/mac/include/net/pctt_region_params.h new file mode 100644 index 0000000..0a0f745 --- /dev/null +++ b/mac/include/net/pctt_region_params.h @@ -0,0 +1,220 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. + * Please contact Qorvo to inquire about licensing terms. + * + * 802.15.4 mac common part sublayer, pctt ranging region. + * + */ + +#ifndef NET_PCTT_REGION_PARAMS_H +#define NET_PCTT_REGION_PARAMS_H + +#include <linux/types.h> + +#define PCTT_VUPPER64_SIZE 8 +#define PCTT_KEY_SIZE_MAX 32 +#define PCTT_KEY_SIZE_MIN 16 +#define PCTT_CONTROLEES_MAX 16 +#define PCTT_RX_ANTENNA_PAIR_INVALID 0xff +/* + * In BPRF, frame is at most 127 + * 127 - (MHR + HIE + HT + PIE_Header + V_OUI + MIC + CRC) + */ +#define PCTT_DATA_PAYLOAD_SIZE_MAX 84 + +/** + * enum pctt_device_role - **[NOT IMPLEMENTED]** Role played by a device. + * @PCTT_DEVICE_ROLE_RESPONDER: The device acts as a responder. + * @PCTT_DEVICE_ROLE_INITIATOR: The device acts as an initiator. + * + * Current implementation does not support decorrelation between the + * device's role and the device's type. The controller is always + * the initiator and the controlee is always the responder. + * + * This enum is not used in the current implementation. + */ +enum pctt_device_role { + PCTT_DEVICE_ROLE_RESPONDER, + PCTT_DEVICE_ROLE_INITIATOR, +}; + +/** + * enum pctt_rframe_config - Rframe configuration used to transmit/receive + * ranging messages. + * @PCTT_RFRAME_CONFIG_SP0: Use SP0 mode. + * @PCTT_RFRAME_CONFIG_SP1: Use SP1 mode. + * @PCTT_RFRAME_CONFIG_SP2: RFU + * @PCTT_RFRAME_CONFIG_SP3: Use SP3 mode. + */ +enum pctt_rframe_config { + PCTT_RFRAME_CONFIG_SP0, + PCTT_RFRAME_CONFIG_SP1, + PCTT_RFRAME_CONFIG_SP2, + PCTT_RFRAME_CONFIG_SP3, +}; + +/** + * enum pctt_prf_mode - Pulse Repetition Frequency mode. + * @PCTT_PRF_MODE_BPRF: Base Pulse Repetition Frequency. + * @PCTT_PRF_MODE_HPRF: Higher Pulse Repetition Frequency. + * @PCTT_PRF_MODE_HPRF_HIGH_RATE: Higher Pulse Repetition Frequency allowing + * higher data rates (27M2 and 31M2). + * + * This enum is not used in the current implementation. + */ +enum pctt_prf_mode { + PCTT_PRF_MODE_BPRF, + PCTT_PRF_MODE_HPRF, + PCTT_PRF_MODE_HPRF_HIGH_RATE, +}; + +/** + * enum pctt_preamble_duration - Duration of preamble in symbols. + * @PCTT_PREAMBLE_DURATION_32: 32 symbols duration. + * @PCTT_PREAMBLE_DURATION_64: 64 symbols duration. + */ +enum pctt_preamble_duration { + PCTT_PREAMBLE_DURATION_32, + PCTT_PREAMBLE_DURATION_64, +}; + +/** + * enum pctt_sfd_id - Start-of-frame delimiter. + * @PCTT_SFD_ID_0: Delimiter is [0 +1 0 –1 +1 0 0 –1] + * @PCTT_SFD_ID_1: Delimiter is [ –1 –1 +1 –1 ] + * @PCTT_SFD_ID_2: Delimiter is [ –1 –1 –1 +1 –1 –1 +1 –1 ] + * @PCTT_SFD_ID_3: Delimiter is + * [ –1 –1 –1 –1 –1 +1 +1 –1 –1 +1 –1 +1 –1 –1 +1 –1 ] + * @PCTT_SFD_ID_4: Delimiter is + * [ –1 –1 –1 –1 –1 –1 –1 +1 –1 –1 +1 –1 –1 +1 –1 +1 –1 +1 + * –1 –1 –1 +1 +1 –1 –1 –1 +1 –1 +1 +1 –1 –1 ] + */ +enum pctt_sfd_id { + PCTT_SFD_ID_0, + PCTT_SFD_ID_1, + PCTT_SFD_ID_2, + PCTT_SFD_ID_3, + PCTT_SFD_ID_4, +}; + +/** + * enum pctt_number_of_sts_segments - Number of STS segments. + * @PCTT_NUMBER_OF_STS_SEGMENTS_NONE: No STS Segment (Rframe config SP0). + * @PCTT_NUMBER_OF_STS_SEGMENTS_1_SEGMENT: 1 STS Segment. + * @PCTT_NUMBER_OF_STS_SEGMENTS_2_SEGMENTS: 2 STS Segments. + * @PCTT_NUMBER_OF_STS_SEGMENTS_3_SEGMENTS: 3 STS Segments. + * @PCTT_NUMBER_OF_STS_SEGMENTS_4_SEGMENTS: 4 STS Segments. + */ +enum pctt_number_of_sts_segments { + PCTT_NUMBER_OF_STS_SEGMENTS_NONE, + PCTT_NUMBER_OF_STS_SEGMENTS_1_SEGMENT, + PCTT_NUMBER_OF_STS_SEGMENTS_2_SEGMENTS, + PCTT_NUMBER_OF_STS_SEGMENTS_3_SEGMENTS, + PCTT_NUMBER_OF_STS_SEGMENTS_4_SEGMENTS, +}; + +/** + * enum pctt_psdu_data_rate - Data rate used to exchange PSDUs. + * @PCTT_PSDU_DATA_RATE_6M81: 6.8Mb/s rate. + * @PCTT_PSDU_DATA_RATE_7M80: 7.8Mb/s rate. + * @PCTT_PSDU_DATA_RATE_27M2: 27.2Mb/s rate. + * @PCTT_PSDU_DATA_RATE_31M2: 31.2Mb/s rate. + */ +enum pctt_psdu_data_rate { + PCTT_PSDU_DATA_RATE_6M81, + PCTT_PSDU_DATA_RATE_7M80, + PCTT_PSDU_DATA_RATE_27M2, + PCTT_PSDU_DATA_RATE_31M2, +}; + +/** + * enum pctt_phr_data_rate - Data rate used to exchange PHR. + * @PCTT_PHR_DATA_RATE_850K: 850kb/s rate. + * @PCTT_PHR_DATA_RATE_6M81: 6.8Mb/s rate. + * + * This enum is not used in the current implementation. + */ +enum pctt_phr_data_rate { + PCTT_PHR_DATA_RATE_850K, + PCTT_PHR_DATA_RATE_6M81, +}; + +enum pctt_mac_fcs_type { + PCTT_MAC_FCS_TYPE_CRC_16, + PCTT_MAC_FCS_TYPE_CRC_32, +}; + +/** + * enum pctt_status_ranging - Ranging status: success or failure reason. + * @PCTT_STATUS_RANGING_INTERNAL_ERROR: Implementation specific error. + * @PCTT_STATUS_RANGING_SUCCESS: Ranging info are valid. + * @PCTT_STATUS_RANGING_TX_FAILED: Failed to transmit UWB packet. + * @PCTT_STATUS_RANGING_RX_TIMEOUT: No UWB packet detected by the receiver. + * @PCTT_STATUS_RANGING_RX_PHY_DEC_FAILED: UWB packet channel decoding error. + * @PCTT_STATUS_RANGING_RX_PHY_TOA_FAILED: Failed to detect time of arrival of + * the UWB packet from CIR samples. + * @PCTT_STATUS_RANGING_RX_PHY_STS_FAILED: UWB packet STS segment mismatch. + * @PCTT_STATUS_RANGING_RX_MAC_DEC_FAILED: MAC CRC or syntax error. + * @PCTT_STATUS_RANGING_RX_MAC_IE_DEC_FAILED: IE syntax error. + * @PCTT_STATUS_RANGING_RX_MAC_IE_MISSING: Expected IE missing in the packet. + */ +enum pctt_status_ranging { + PCTT_STATUS_RANGING_INTERNAL_ERROR = -1, + PCTT_STATUS_RANGING_SUCCESS = 0, + PCTT_STATUS_RANGING_TX_FAILED = 1, + PCTT_STATUS_RANGING_RX_TIMEOUT = 2, + PCTT_STATUS_RANGING_RX_PHY_DEC_FAILED = 3, + PCTT_STATUS_RANGING_RX_PHY_TOA_FAILED = 4, + PCTT_STATUS_RANGING_RX_PHY_STS_FAILED = 5, + PCTT_STATUS_RANGING_RX_MAC_DEC_FAILED = 6, + PCTT_STATUS_RANGING_RX_MAC_IE_DEC_FAILED = 7, + PCTT_STATUS_RANGING_RX_MAC_IE_MISSING = 8, +}; + +/** + * enum pctt_session_state - Session state. + * @PCTT_SESSION_STATE_INIT: Initial state, session is not ready yet. + * @PCTT_SESSION_STATE_DEINIT: Session does not exist. + * @PCTT_SESSION_STATE_ACTIVE: Session is currently active. + * @PCTT_SESSION_STATE_IDLE: Session is ready to start, but not currently + * active. + */ +enum pctt_session_state { + PCTT_SESSION_STATE_INIT, + PCTT_SESSION_STATE_DEINIT, + PCTT_SESSION_STATE_ACTIVE, + PCTT_SESSION_STATE_IDLE, +}; + +/** + * enum pctt_sts_length - Number of symbols in a STS segment. + * @PCTT_STS_LENGTH_32: The STS length is 32 symbols. + * @PCTT_STS_LENGTH_64: The STS length is 64 symbols. + * @PCTT_STS_LENGTH_128: The STS length is 128 symbols. + */ +enum pctt_sts_length { + PCTT_STS_LENGTH_32 = 0, + PCTT_STS_LENGTH_64 = 1, + PCTT_STS_LENGTH_128 = 2, +}; + +#endif /* NET_PCTT_REGION_PARAMS_H */ diff --git a/mac/include/net/vendor_cmd.h b/mac/include/net/vendor_cmd.h new file mode 100644 index 0000000..38a6224 --- /dev/null +++ b/mac/include/net/vendor_cmd.h @@ -0,0 +1,243 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_VENDOR_CMD_H +#define NET_VENDOR_CMD_H + +#include <linux/types.h> +#include <net/mcps802154.h> + +/** + * enum llhw_vendor_cmd - Vendor command identifiers. + * @LLHW_VENDOR_CMD_NFCC_COEX_HANDLE_ACCESS: + * NFCC Coex: Handle access. + * @LLHW_VENDOR_CMD_NFCC_COEX_GET_ACCESS_INFORMATION: + * NFCC Coex: Get access information. + * @LLHW_VENDOR_CMD_NFCC_COEX_STOP: + * NFCC Coex: Stop. + * @LLHW_VENDOR_CMD_PCTT_SETUP_HW: + * PCTT: Setup hardware access. + * @LLHW_VENDOR_CMD_PCTT_HANDLE_LOOPBACK: + * PCTT: Handle loop-back test. + * @LLHW_VENDOR_CMD_PCTT_GET_LOOPBACK_INFO: + * PCTT: Get loop-back information. + * @LLHW_VENDOR_CMD_PCTT_GET_FRAME_INFO: + * PCTT: Get the last received frame information. + */ +enum llhw_vendor_cmd { + LLHW_VENDOR_CMD_NFCC_COEX_HANDLE_ACCESS, + LLHW_VENDOR_CMD_NFCC_COEX_GET_ACCESS_INFORMATION, + LLHW_VENDOR_CMD_NFCC_COEX_STOP, + LLHW_VENDOR_CMD_PCTT_SETUP_HW, + LLHW_VENDOR_CMD_PCTT_HANDLE_LOOPBACK, + LLHW_VENDOR_CMD_PCTT_GET_LOOPBACK_INFO, + LLHW_VENDOR_CMD_PCTT_GET_FRAME_INFO, +}; + +/** + * struct llhw_vendor_cmd_nfcc_coex_handle_access - NFCC Coex: handle access + * vendor command. + */ +struct llhw_vendor_cmd_nfcc_coex_handle_access { + /** + * @start: True to start a new session. + */ + bool start; + /** + * @timestamp_dtu: + * Access start date. If this is a new session, this also defines + * TIME0. + */ + u32 timestamp_dtu; + /** + * @duration_dtu: + * Duration of the access, or 0 if unknown (this is the case when + * starting a new session). + */ + int duration_dtu; + /** + * @chan: Channel number, 5 or 9. + */ + int chan; + /** + * @version: Protocol version. + */ + int version; +}; + +/** + * struct llhw_vendor_cmd_nfcc_coex_get_access_info - NFCC Coex: get access + * info vendor command. + */ +struct llhw_vendor_cmd_nfcc_coex_get_access_info { + /** + * @stop: If true, the NFCC did not give a next access. + */ + bool stop; + /** + * @watchdog_timeout: + * If true, Watchdog triggered before NFCC released the SPI. + */ + bool watchdog_timeout; + /** + * @duration_dtu: + * Effective duration of the access. If 0, the current date will be + * read to continue. + */ + int duration_dtu; + /** + * @next_timestamp_dtu: Next access date. + */ + u32 next_timestamp_dtu; + /** + * @next_duration_dtu: Next access duration, or 0 if unknown. + */ + int next_duration_dtu; +}; + +/** + * struct llhw_vendor_cmd_nfcc_coex_stop - NFCC Coex: stop + * vendor command. + */ +struct llhw_vendor_cmd_nfcc_coex_stop { + /** + * @timestamp_dtu: + * Access date when the stop must be sent. + */ + u32 timestamp_dtu; + /** + * @duration_dtu: + * Duration of the access. + */ + int duration_dtu; + /** + * @version: Protocol version. + */ + int version; +}; + +/** + * struct llhw_vendor_cmd_pctt_setup_hw - PCTT: direct HW access + * vendor command. + */ +struct llhw_vendor_cmd_pctt_setup_hw { + /** + * @chan: Channel number, 5 or 9. + */ + int chan; + /** + * @rframe_config: STS packet configuration for ranging frame. + */ + u8 rframe_config; + /** + * @preamble_code_index: Specifies code index according to Table 16-7 in + * IEEE Std 802.15.4-2020 and Table 42 in IEEE Std 802.15.4z-2020 + */ + u8 preamble_code_index; + /** + * @sfd_id: Start of frame delimiter. + */ + u8 sfd_id; + /** + * @psdu_data_rate: PSDU data rate. + */ + u8 psdu_data_rate; + /** + * @preamble_duration: Preamble duration. + */ + u8 preamble_duration; +}; + +/** + * struct llhw_vendor_cmd_pctt_handle_loopback - PCTT: handle loopback access. + */ +struct llhw_vendor_cmd_pctt_handle_loopback { + /** + * @ant_set_id : antenna set index to use for transmit/receive. + */ + int ant_set_id; + /** + * @rx_timeout_dtu: If negative, no timeout, if zero, use a default timeout + * value, else this is the timeout value in device time unit. + */ + int rx_timeout_dtu; + /** + * @rx_frame_timeout_dtu: If no zero, timeout value for the full frame + * reception. This allow limiting the length of accepted frame. The + * timeout starts after rx_timeout_dtu value. + */ + int rx_frame_timeout_dtu; + /** + * @data_payload: Array of payload to send during loopback test. + */ + const u8 *data_payload; + /** + * @data_payload_len: Length of the payload array in byte. + */ + size_t data_payload_len; +}; + +/** + * struct llhw_vendor_cmd_pctt_get_loopback_info - PCTT: get access + * info vendor command. + */ +struct llhw_vendor_cmd_pctt_get_loopback_info { + /** + * @skb: sk buffer containing received data. + */ + struct sk_buff *skb; + /** + * @success: True when data sent match with received. + */ + bool success; + /** + * @rssi: Received signal strength indication (RSSI), + * absolute value in Q1 fixed point format. + */ + int rssi; + /** + * @rx_timestamp_rctu: RX timestamp in RCTU units. + */ + u64 rx_timestamp_rctu; + /** + * @tx_timestamp_rctu: TX timestamp in RCTU units. + */ + u64 tx_timestamp_rctu; +}; + +/** + * struct llhw_vendor_cmd_pctt_get_frame_info - PCTT: last received frame + * information. + */ +struct llhw_vendor_cmd_pctt_get_frame_info { + /** + * @skb: sk buffer containing received data. + */ + struct sk_buff *skb; + /** + * @info: frame information. + */ + struct mcps802154_rx_frame_info info; +}; + +#endif /* NET_VENDOR_CMD_H */ diff --git a/mac/llhw-ops.h b/mac/llhw-ops.h new file mode 100644 index 0000000..1dfcf54 --- /dev/null +++ b/mac/llhw-ops.h @@ -0,0 +1,359 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef LLHW_OPS_H +#define LLHW_OPS_H + +#include <linux/errno.h> + +#include "mcps802154_i.h" +#include "trace.h" + +static inline int llhw_start(struct mcps802154_local *local) +{ + int r; + + trace_llhw_start(local); + r = local->ops->start(&local->llhw); + trace_llhw_return_int(local, r); + return r; +} + +static inline void llhw_stop(struct mcps802154_local *local) +{ + trace_llhw_stop(local); + local->ops->stop(&local->llhw); + trace_llhw_return_void(local); +} + +static inline int llhw_tx_frame(struct mcps802154_local *local, + struct sk_buff *skb, + const struct mcps802154_tx_frame_config *config, + int frame_idx, int next_delay_dtu) +{ + int r; + + trace_llhw_tx_frame(local, config, frame_idx, next_delay_dtu); + r = local->ops->tx_frame(&local->llhw, skb, config, frame_idx, + next_delay_dtu); + trace_llhw_return_int(local, r); + return r; +} + +static inline int llhw_rx_enable(struct mcps802154_local *local, + const struct mcps802154_rx_frame_config *info, + int frame_idx, int next_delay_dtu) +{ + int r; + + trace_llhw_rx_enable(local, info, frame_idx, next_delay_dtu); + r = local->ops->rx_enable(&local->llhw, info, frame_idx, + next_delay_dtu); + trace_llhw_return_int(local, r); + return r; +} + +static inline int llhw_rx_disable(struct mcps802154_local *local) +{ + int r; + + trace_llhw_rx_disable(local); + r = local->ops->rx_disable(&local->llhw); + trace_llhw_return_int(local, r); + return r; +} + +static inline int llhw_rx_get_frame(struct mcps802154_local *local, + struct sk_buff **skb, + struct mcps802154_rx_frame_info *info) +{ + int r; + + trace_llhw_rx_get_frame(local, info); + r = local->ops->rx_get_frame(&local->llhw, skb, info); + trace_llhw_return_rx_frame(local, r, info); + return r; +} + +static inline int llhw_rx_get_error_frame(struct mcps802154_local *local, + struct mcps802154_rx_frame_info *info) +{ + int r; + + trace_llhw_rx_get_error_frame(local, info); + r = local->ops->rx_get_error_frame(&local->llhw, info); + trace_llhw_return_rx_frame(local, r, info); + return r; +} + +static inline int llhw_idle(struct mcps802154_local *local, bool timestamp, + u32 timestamp_dtu) +{ + int r; + + if (timestamp) + trace_llhw_idle_timestamp(local, timestamp_dtu); + else + trace_llhw_idle(local); + r = local->ops->idle(&local->llhw, timestamp, timestamp_dtu); + trace_llhw_return_int(local, r); + return r; +} + +static inline int llhw_reset(struct mcps802154_local *local) +{ + int r; + + trace_llhw_reset(local); + r = local->ops->reset(&local->llhw); + trace_llhw_return_int(local, r); + return r; +} + +static inline int llhw_get_current_timestamp_dtu(struct mcps802154_local *local, + u32 *timestamp_dtu) +{ + int r; + + trace_llhw_get_current_timestamp_dtu(local); + r = local->ops->get_current_timestamp_dtu(&local->llhw, timestamp_dtu); + trace_llhw_return_timestamp_dtu(local, r, *timestamp_dtu); + return r; +} + +static inline u64 llhw_tx_timestamp_dtu_to_rmarker_rctu( + struct mcps802154_local *local, u32 tx_timestamp_dtu, + const struct mcps802154_hrp_uwb_params *hrp_uwb_params, + const struct mcps802154_channel *channel_params, int ant_set_id) +{ + return local->ops->tx_timestamp_dtu_to_rmarker_rctu( + &local->llhw, tx_timestamp_dtu, hrp_uwb_params, channel_params, + ant_set_id); +} + +static inline s64 llhw_difference_timestamp_rctu(struct mcps802154_local *local, + u64 timestamp_a_rctu, + u64 timestamp_b_rctu) +{ + return local->ops->difference_timestamp_rctu( + &local->llhw, timestamp_a_rctu, timestamp_b_rctu); +} + +static inline int +llhw_compute_frame_duration_dtu(struct mcps802154_local *local, + int payload_bytes) +{ + return local->ops->compute_frame_duration_dtu(&local->llhw, + payload_bytes); +} + +static inline int llhw_set_channel(struct mcps802154_local *local, u8 page, + u8 channel, u8 preamble_code) +{ + int r; + + trace_llhw_set_channel(local, page, channel, preamble_code); + r = local->ops->set_channel(&local->llhw, page, channel, preamble_code); + trace_llhw_return_int(local, r); + return r; +} + +static inline int __nocfi +llhw_set_hrp_uwb_params(struct mcps802154_local *local, + const struct mcps802154_hrp_uwb_params *params) +{ + int r; + + trace_llhw_set_hrp_uwb_params(local, params); + r = local->ops->set_hrp_uwb_params(&local->llhw, params); + trace_llhw_return_int(local, r); + return r; +} + +static inline int +llhw_set_sts_params(struct mcps802154_local *local, + const struct mcps802154_sts_params *params) +{ + int r; + + trace_llhw_set_sts_params(local, params); + if (local->ops->set_sts_params) + r = local->ops->set_sts_params(&local->llhw, params); + else + r = -EOPNOTSUPP; + trace_llhw_return_int(local, r); + return r; +} + +static inline int llhw_set_hw_addr_filt(struct mcps802154_local *local, + struct ieee802154_hw_addr_filt *filt, + unsigned long changed) +{ + int r; + + trace_llhw_set_hw_addr_filt(local, filt, changed); + r = local->ops->set_hw_addr_filt(&local->llhw, filt, changed); + trace_llhw_return_int(local, r); + return r; +} + +static inline int llhw_set_txpower(struct mcps802154_local *local, s32 mbm) +{ + int r; + + trace_llhw_set_txpower(local, mbm); + r = local->ops->set_txpower(&local->llhw, mbm); + trace_llhw_return_int(local, r); + return r; +} + +static inline int llhw_set_cca_ed_level(struct mcps802154_local *local, s32 mbm) +{ + int r; + + trace_llhw_set_cca_ed_level(local, mbm); + r = local->ops->set_cca_ed_level(&local->llhw, mbm); + trace_llhw_return_int(local, r); + return r; +} + +static inline int llhw_set_promiscuous_mode(struct mcps802154_local *local, + bool on) +{ + int r; + + trace_llhw_set_promiscuous_mode(local, on); + r = local->ops->set_promiscuous_mode(&local->llhw, on); + trace_llhw_return_int(local, r); + return r; +} + +static inline int llhw_set_scanning_mode(struct mcps802154_local *local, + bool on) +{ + int r; + + trace_llhw_set_scanning_mode(local, on); + r = local->ops->set_scanning_mode(&local->llhw, on); + trace_llhw_return_int(local, r); + return r; +} + +static inline int llhw_set_calibration(struct mcps802154_local *local, + const char *key, void *value, + size_t length) +{ + int r; + + trace_llhw_set_calibration(local, key); + r = local->ops->set_calibration(&local->llhw, key, value, length); + trace_llhw_return_int(local, r); + return r; +} + +static inline int llhw_get_calibration(struct mcps802154_local *local, + const char *key, void *value, + size_t length) +{ + int r; + + trace_llhw_get_calibration(local, key); + r = local->ops->get_calibration(&local->llhw, key, value, length); + trace_llhw_return_int(local, r); + return r; +} + +static inline const char *const * +llhw_list_calibration(struct mcps802154_local *local) +{ + const char *const *r; + + trace_llhw_list_calibration(local); + if (local->ops->list_calibration) { + r = local->ops->list_calibration(&local->llhw); + } else { + r = NULL; + } + trace_llhw_return_void(local); + return r; +} + +static inline int llhw_vendor_cmd(struct mcps802154_local *local, u32 vendor_id, + u32 subcmd, void *data, size_t data_len) +{ + int r; + + trace_llhw_vendor_cmd(local, vendor_id, subcmd, data_len); + if (local->ops->vendor_cmd) + r = local->ops->vendor_cmd(&local->llhw, vendor_id, subcmd, + data, data_len); + else + r = -EOPNOTSUPP; + trace_llhw_return_int(local, r); + return r; +} + +static inline int llhw_check_hrp_uwb_params( + struct mcps802154_local *local, + const struct mcps802154_hrp_uwb_params *hrp_uwb_params) +{ + int r; + + trace_llhw_check_hrp_uwb_params(local, hrp_uwb_params); + if (local->ops->check_hrp_uwb_params) + r = local->ops->check_hrp_uwb_params(&local->llhw, + hrp_uwb_params); + else + r = -EOPNOTSUPP; + trace_llhw_return_int(local, r); + return r; +} + +static inline int +llhw_rx_get_measurement(struct mcps802154_local *local, void *rx_ctx, + struct mcps802154_rx_measurement_info *info) +{ + int r; + trace_llhw_rx_get_measurement(local, rx_ctx); + if (local->ops->rx_get_measurement) + r = local->ops->rx_get_measurement(&local->llhw, rx_ctx, info); + else + r = -EOPNOTSUPP; + trace_llhw_return_measurement(local, r, info); + return r; +} + +#ifdef CONFIG_MCPS802154_TESTMODE +static inline int llhw_testmode_cmd(struct mcps802154_local *local, void *data, + int len) +{ + int r; + + trace_llhw_testmode_cmd(local); + r = local->ops->testmode_cmd(&local->llhw, data, len); + trace_llhw_return_int(local, r); + return r; +} +#endif /* CONFIG_MCPS802154_TESTMODE */ + +#endif /* LLHW_OPS_H */ diff --git a/mac/mcps802154_fproc.h b/mac/mcps802154_fproc.h new file mode 100644 index 0000000..fc6e871 --- /dev/null +++ b/mac/mcps802154_fproc.h @@ -0,0 +1,32 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef __MCPS802154_FPROC_H__ +#define __MCPS802154_FPROC_H__ + +#include <net/mcps802154_schedule.h> + +bool mcps802154_fproc_is_non_recoverable_error(struct mcps802154_access *access); + +#endif /* MCPS802154_FPROC_H */ + diff --git a/mac/mcps802154_i.h b/mac/mcps802154_i.h new file mode 100644 index 0000000..311ee3a --- /dev/null +++ b/mac/mcps802154_i.h @@ -0,0 +1,161 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_MCPS802154_I_H +#define NET_MCPS802154_MCPS802154_I_H + +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/wait.h> +#include <linux/workqueue.h> +#include <net/mac802154.h> +#include <net/mcps802154.h> + +#include "ca.h" +#include "fproc.h" + +/** + * struct mcps802154_pib - PIB (PAN Information Base): this is a database of + * 802.15.4 settings. + */ +struct mcps802154_pib { + /** + * @mac_extended_addr: Current extended address. + */ + __le64 mac_extended_addr; + /** + * @mac_pan_id: The identifier of the PAN on which the device is + * operating. 0xffff if the device is not associated. + */ + __le16 mac_pan_id; + /** + * @mac_short_addr: The address the device uses to communicate inside + * its PAN. 0xffff if the device is not associated, 0xfffe if the device + * is associated but has no short address. + */ + __le16 mac_short_addr; + /** + * @mac_promiscuous: Indicate whether the promiscuous mode is enabled. + */ + bool mac_promiscuous; + /** + * @mac_max_frame_retries: Number of retries on TX. + */ + s8 mac_max_frame_retries; + /** + * @phy_current_channel: Current channel parameters. + */ + struct mcps802154_channel phy_current_channel; +}; + +/** + * struct mcps802154_local - MCPS private data. + */ +struct mcps802154_local { + /** + * @llhw: Low-level hardware. + */ + struct mcps802154_llhw llhw; + /** + * @hw: Pointer to MCPS hw instance. + */ + struct ieee802154_hw *hw; + /** + * @ops: Low-level driver operations. + */ + const struct mcps802154_ops *ops; + /** + * @hw_idx: Index of hardware. + */ + int hw_idx; + /** + * @cur_cmd_info: Current netlink command. + */ + struct genl_info *cur_cmd_info; + /** + * @registered_entry: Entry in list of registered low-level driver. + */ + struct list_head registered_entry; + /** + * @wq: Wake queue for synchronous operation with an asynchronous + * implementation. + */ + wait_queue_head_t wq; + /** + * @ca: Channel access context. + */ + struct mcps802154_ca ca; + /** + * @fproc: Frame processing context. + */ + struct mcps802154_fproc fproc; + /** + * @fsm_lock: FSM lock to avoid multiple access. + */ + struct mutex fsm_lock; + /** + * @tx_work: Transmit work to schedule async actions. + */ + struct work_struct tx_work; + /** + * @start_stop_request: Request to start (true) or stop (false) from + * mac802154 layer. + */ + bool start_stop_request; + /** + * @started: Current started state. + */ + bool started; + /** + * @broken: Currently broken. + */ + bool broken; + /** + * @pib: PAN Information Base. + */ + struct mcps802154_pib pib; + /** + * @mac_pan_coord: Indicate whether the hardware filtering should operate as + * coordinator. + */ + bool mac_pan_coord; +}; + +static inline struct mcps802154_local * +llhw_to_local(struct mcps802154_llhw *llhw) +{ + return container_of(llhw, struct mcps802154_local, llhw); +} + +static inline struct mcps802154_local * +txwork_to_local(struct work_struct *tx_work) +{ + return container_of(tx_work, struct mcps802154_local, tx_work); +} + +struct mcps802154_local *mcps802154_get_first_by_idx(int hw_idx); + +extern const struct ieee802154_ops mcps802154_ops; + +#endif /* NET_MCPS802154_MCPS802154_I_H */ diff --git a/mac/mcps802154_qorvo.h b/mac/mcps802154_qorvo.h new file mode 100644 index 0000000..170ea06 --- /dev/null +++ b/mac/mcps802154_qorvo.h @@ -0,0 +1,32 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef MCPS802154_QORVO_H +#define MCPS802154_QORVO_H + +/* Qorvo OUI in big endian. + * The define can't be declared in include/net/vendor_cmd.h because + * it's not generic to other mac provider. */ +#define VENDOR_QORVO_OUI 0xc8b1ee00 + +#endif /* MCPS802154_QORVO_H */ diff --git a/mac/mcps_main.c b/mac/mcps_main.c new file mode 100644 index 0000000..f3c2590 --- /dev/null +++ b/mac/mcps_main.c @@ -0,0 +1,324 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <linux/atomic.h> +#include <linux/errno.h> +#include <linux/ieee802154.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/netdevice.h> +#include <net/rtnetlink.h> + +#include "mcps802154_i.h" +#include "llhw-ops.h" +#include "default_region.h" +#include "idle_region.h" +#include "endless_scheduler.h" +#include "on_demand_scheduler.h" +#include "nl.h" +#include "warn_return.h" + +static LIST_HEAD(registered_llhw); +static DEFINE_MUTEX(registered_llhw_lock); + +static void mcps802154_tx_event(struct work_struct *work) +{ + struct mcps802154_local *local = txwork_to_local(work); + + mutex_lock(&local->fsm_lock); + if (likely(local->started)) + mcps802154_ca_may_reschedule(local); + mutex_unlock(&local->fsm_lock); +} + +struct mcps802154_llhw *mcps802154_alloc_llhw(size_t priv_data_len, + const struct mcps802154_ops *ops) +{ + static atomic_t llhw_counter = ATOMIC_INIT(0); + int idx; + struct ieee802154_hw *hw; + struct mcps802154_local *local; + size_t priv_size; + + if (WARN_ON(!ops || !ops->start || !ops->stop || !ops->tx_frame || + !ops->rx_enable || !ops->rx_disable || !ops->rx_get_frame || + !ops->rx_get_error_frame)) + return NULL; + + priv_size = ALIGN(sizeof(*local), NETDEV_ALIGN) + priv_data_len; + hw = ieee802154_alloc_hw(priv_size, &mcps802154_ops); + if (!hw) + return NULL; + + idx = atomic_inc_return(&llhw_counter); + if (idx < 0) { + /* Wrapped! */ + atomic_dec(&llhw_counter); + ieee802154_free_hw(hw); + return NULL; + } + + local = hw->priv; + local->hw = hw; + local->llhw.hw = hw; + local->llhw.priv = (char *)local + ALIGN(sizeof(*local), NETDEV_ALIGN); + local->ops = ops; + local->hw_idx = idx - 1; + init_waitqueue_head(&local->wq); + mutex_init(&local->fsm_lock); + INIT_WORK(&local->tx_work, mcps802154_tx_event); + mutex_lock(&local->fsm_lock); + mcps802154_ca_init(local); + mcps802154_fproc_init(local); + mutex_unlock(&local->fsm_lock); + + return &local->llhw; +} +EXPORT_SYMBOL(mcps802154_alloc_llhw); + +void mcps802154_free_llhw(struct mcps802154_llhw *llhw) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + mutex_lock(&local->fsm_lock); + mcps802154_fproc_uninit(local); + mcps802154_ca_uninit(local); + mutex_unlock(&local->fsm_lock); + mutex_destroy(&local->fsm_lock); + + WARN_ON(waitqueue_active(&local->wq)); +#ifndef __KERNEL__ + destroy_waitqueue_head(&local->wq); +#endif + + ieee802154_free_hw(local->hw); +} +EXPORT_SYMBOL(mcps802154_free_llhw); + +int mcps802154_register_llhw(struct mcps802154_llhw *llhw) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + int r; + + llhw->hw->flags |= IEEE802154_HW_FRAME_RETRIES; + + r = ieee802154_register_hw(local->hw); + if (r) + return r; + + local->pib.mac_extended_addr = local->hw->phy->perm_extended_addr; + local->pib.mac_pan_id = IEEE802154_PAN_ID_BROADCAST; + local->pib.mac_promiscuous = false; + local->pib.mac_short_addr = IEEE802154_ADDR_SHORT_BROADCAST; + local->pib.phy_current_channel.page = local->hw->phy->current_page; + local->pib.phy_current_channel.channel = + local->hw->phy->current_channel; + local->pib.phy_current_channel.preamble_code = + llhw->current_preamble_code; + local->mac_pan_coord = false; + + mutex_lock(®istered_llhw_lock); + list_add(&local->registered_entry, ®istered_llhw); + mutex_unlock(®istered_llhw_lock); + + return 0; +} +EXPORT_SYMBOL(mcps802154_register_llhw); + +void mcps802154_unregister_llhw(struct mcps802154_llhw *llhw) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + mutex_lock(®istered_llhw_lock); + list_del(&local->registered_entry); + mutex_unlock(®istered_llhw_lock); + ieee802154_unregister_hw(local->hw); + mutex_lock(&local->fsm_lock); + mcps802154_ca_close(local); + mutex_unlock(&local->fsm_lock); +} +EXPORT_SYMBOL(mcps802154_unregister_llhw); + +__le64 mcps802154_get_extended_addr(struct mcps802154_llhw *llhw) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + return local->pib.mac_extended_addr; +} +EXPORT_SYMBOL(mcps802154_get_extended_addr); + +__le16 mcps802154_get_pan_id(struct mcps802154_llhw *llhw) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + return local->pib.mac_pan_id; +} +EXPORT_SYMBOL(mcps802154_get_pan_id); + +__le16 mcps802154_get_short_addr(struct mcps802154_llhw *llhw) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + return local->pib.mac_short_addr; +} +EXPORT_SYMBOL(mcps802154_get_short_addr); + +const struct mcps802154_channel * +mcps802154_get_current_channel(struct mcps802154_llhw *llhw) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + return &local->pib.phy_current_channel; +} +EXPORT_SYMBOL(mcps802154_get_current_channel); + +int mcps802154_get_current_timestamp_dtu(struct mcps802154_llhw *llhw, + u32 *timestamp_dtu) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + if (!local->started) + return -ENETDOWN; + + return llhw_get_current_timestamp_dtu(local, timestamp_dtu); +} +EXPORT_SYMBOL(mcps802154_get_current_timestamp_dtu); + +u64 mcps802154_tx_timestamp_dtu_to_rmarker_rctu( + struct mcps802154_llhw *llhw, u32 tx_timestamp_dtu, + const struct mcps802154_hrp_uwb_params *hrp_uwb_params, + const struct mcps802154_channel *channel_params, int ant_set_id) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + return llhw_tx_timestamp_dtu_to_rmarker_rctu(local, tx_timestamp_dtu, + hrp_uwb_params, + channel_params, + ant_set_id); +} +EXPORT_SYMBOL(mcps802154_tx_timestamp_dtu_to_rmarker_rctu); + +s64 mcps802154_difference_timestamp_rctu(struct mcps802154_llhw *llhw, + u64 timestamp_a_rctu, + u64 timestamp_b_rctu) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + return llhw_difference_timestamp_rctu(local, timestamp_a_rctu, + timestamp_b_rctu); +} +EXPORT_SYMBOL(mcps802154_difference_timestamp_rctu); + +int mcps802154_rx_get_measurement(struct mcps802154_llhw *llhw, void *rx_ctx, + struct mcps802154_rx_measurement_info *info) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + return llhw_rx_get_measurement(local, rx_ctx, info); +} +EXPORT_SYMBOL(mcps802154_rx_get_measurement); + +int mcps802154_compute_frame_duration_dtu(struct mcps802154_llhw *llhw, + int payload_bytes) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + return llhw_compute_frame_duration_dtu(local, payload_bytes); +} +EXPORT_SYMBOL(mcps802154_compute_frame_duration_dtu); + +int mcps802154_vendor_cmd(struct mcps802154_llhw *llhw, u32 vendor_id, + u32 subcmd, void *data, size_t data_len) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + return llhw_vendor_cmd(local, vendor_id, subcmd, data, data_len); +} +EXPORT_SYMBOL(mcps802154_vendor_cmd); + +int mcps802154_check_hrp_uwb_params( + struct mcps802154_llhw *llhw, + const struct mcps802154_hrp_uwb_params *hrp_uwb_params) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + return llhw_check_hrp_uwb_params(local, hrp_uwb_params); +} +EXPORT_SYMBOL(mcps802154_check_hrp_uwb_params); + +struct mcps802154_local *mcps802154_get_first_by_idx(int hw_idx) +{ + struct mcps802154_local *result = NULL, *local; + + ASSERT_RTNL(); + + mutex_lock(®istered_llhw_lock); + list_for_each_entry (local, ®istered_llhw, registered_entry) { + if (local->hw_idx >= hw_idx) { + result = local; + break; + } + } + mutex_unlock(®istered_llhw_lock); + + return result; +} + +int __init mcps802154_init(void) +{ + int r; + + r = mcps802154_nl_init(); + if (r) + return r; + r = mcps802154_default_region_init(); + WARN_RETURN(r); + r = mcps802154_idle_region_init(); + WARN_RETURN(r); + r = mcps802154_endless_scheduler_init(); + WARN_ON(r); + r = mcps802154_default_scheduler_init(); + WARN_ON(r); + r = mcps802154_on_demand_scheduler_init(); + WARN_ON(r); + + return r; +} + +void __exit mcps802154_exit(void) +{ + mcps802154_on_demand_scheduler_exit(); + mcps802154_default_scheduler_exit(); + mcps802154_endless_scheduler_exit(); + mcps802154_idle_region_exit(); + mcps802154_default_region_exit(); + mcps802154_nl_exit(); +} + +module_init(mcps802154_init); +module_exit(mcps802154_exit); + +MODULE_DESCRIPTION("IEEE 802.15.4 MAC common part sublayer"); +MODULE_AUTHOR("Nicolas Schodet <nicolas.schodet@qorvo.com>"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/mac/mcps_skb_frag.c b/mac/mcps_skb_frag.c new file mode 100644 index 0000000..6657612 --- /dev/null +++ b/mac/mcps_skb_frag.c @@ -0,0 +1,33 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/skbuff.h> +#include <linux/module.h> +#include <linux/errno.h> + +int mcps_skb_frags_len(struct sk_buff *skb) +{ + /* No fragmentation on Linux. */ + return 0; +} +EXPORT_SYMBOL(mcps_skb_frags_len); diff --git a/mac/nfcc_coex_access.c b/mac/nfcc_coex_access.c new file mode 100644 index 0000000..60d6bae --- /dev/null +++ b/mac/nfcc_coex_access.c @@ -0,0 +1,179 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include "nfcc_coex_access.h" +#include "nfcc_coex_session.h" +#include "nfcc_coex_region.h" +#include "nfcc_coex_trace.h" +#include "llhw-ops.h" +#include "mcps802154_qorvo.h" + +#include <linux/string.h> +#include <linux/ieee802154.h> +#include <net/mcps802154_frame.h> +#include <net/vendor_cmd.h> + +#include "warn_return.h" + +static void nfcc_coex_access_done(struct mcps802154_access *access, bool error) +{ + struct nfcc_coex_local *local = access_to_local(access); + struct nfcc_coex_session *session = &local->session; + + /* Stop on error because the next timestamps is unknown. + * Stop in V2, because the vendor stop is not supported by NFC. */ + if ((error || (session->state == NFCC_COEX_STATE_STOPPING && + session->params.version == 2)) && + !session->get_access_info.watchdog_timeout) { + const struct llhw_vendor_cmd_nfcc_coex_get_access_info stop = { + .stop = true, + }; + + local->session.get_access_info = stop; + } + + if (session->get_access_info.stop || + session->get_access_info.watchdog_timeout) + nfcc_coex_set_state(local, NFCC_COEX_STATE_IDLE); + + nfcc_coex_report(local); +} + +static int nfcc_coex_handle(struct mcps802154_access *access) +{ + struct nfcc_coex_local *local = access_to_local(access); + struct nfcc_coex_session *session = &local->session; + struct llhw_vendor_cmd_nfcc_coex_handle_access handle_access = {}; + + handle_access.start = session->first_access; + handle_access.timestamp_dtu = access->timestamp_dtu; + handle_access.duration_dtu = access->duration_dtu; + handle_access.chan = session->params.channel_number; + handle_access.version = session->params.version; + + if (session->state == NFCC_COEX_STATE_STOPPING && + session->params.version == 3) { + /* Stop processing : stop the nfcc coex */ + if (local->session.first_access) { + struct mcps802154_region_demand *rd = + &session->region_demand; + struct llhw_vendor_cmd_nfcc_coex_stop stop = { + .timestamp_dtu = rd->timestamp_dtu, + .duration_dtu = rd->max_duration_dtu, + .version = session->params.version, + }; + return mcps802154_vendor_cmd( + local->llhw, VENDOR_QORVO_OUI, + LLHW_VENDOR_CMD_NFCC_COEX_STOP, &stop, + sizeof(stop)); + } else + return mcps802154_vendor_cmd( + local->llhw, VENDOR_QORVO_OUI, + LLHW_VENDOR_CMD_NFCC_COEX_STOP, NULL, 0); + } + + return mcps802154_vendor_cmd(local->llhw, VENDOR_QORVO_OUI, + LLHW_VENDOR_CMD_NFCC_COEX_HANDLE_ACCESS, + &handle_access, sizeof(handle_access)); +} + +static int nfcc_coex_tx_done(struct mcps802154_access *access) +{ + struct nfcc_coex_local *local = access_to_local(access); + struct nfcc_coex_session *session = &local->session; + struct llhw_vendor_cmd_nfcc_coex_get_access_info *get_access_info = + &session->get_access_info; + struct mcps802154_region_demand *rd = &session->region_demand; + int r; + + session->first_access = false; + + r = mcps802154_vendor_cmd( + local->llhw, VENDOR_QORVO_OUI, + LLHW_VENDOR_CMD_NFCC_COEX_GET_ACCESS_INFORMATION, + get_access_info, sizeof(*get_access_info)); + if (r) + return r; + + /* Update region demand for next access. */ + rd->timestamp_dtu = get_access_info->next_timestamp_dtu; + rd->max_duration_dtu = get_access_info->next_duration_dtu; + /* Request end of current access. */ + return 1; +} + +static int nfcc_coex_broken(struct mcps802154_access *access) +{ + struct nfcc_coex_local *local = access_to_local(access); + const struct llhw_vendor_cmd_nfcc_coex_get_access_info + watchdog_timeout = { + .watchdog_timeout = true, + }; + + local->session.get_access_info = watchdog_timeout; + /* Request end of current access. */ + return -ETIME; +} + +struct mcps802154_access_vendor_ops nfcc_coex_ops = { + .common = { + .access_done = nfcc_coex_access_done, + }, + .handle = nfcc_coex_handle, + .tx_done = nfcc_coex_tx_done, + .broken = nfcc_coex_broken, +}; + +static struct mcps802154_access * +nfcc_coex_access_controller(struct nfcc_coex_local *local, + struct nfcc_coex_session *session) +{ + struct mcps802154_access *access = &local->access; + const struct mcps802154_region_demand *rd = &session->region_demand; + + access->method = MCPS802154_ACCESS_METHOD_VENDOR; + access->vendor_ops = &nfcc_coex_ops; + access->duration_dtu = rd->max_duration_dtu; + access->timestamp_dtu = rd->timestamp_dtu; + access->n_frames = 0; + access->frames = NULL; + + return access; +} + +struct mcps802154_access *nfcc_coex_get_access(struct mcps802154_region *region, + u32 next_timestamp_dtu, + int next_in_region_dtu, + int region_duration_dtu) +{ + struct nfcc_coex_local *local = region_to_local(region); + struct nfcc_coex_session *session = &local->session; + + if (session->state == NFCC_COEX_STATE_STARTED || + session->state == NFCC_COEX_STATE_STOPPING) { + nfcc_coex_session_update(local, session, next_timestamp_dtu, + region_duration_dtu); + return nfcc_coex_access_controller(local, session); + } + return NULL; +} diff --git a/mac/nfcc_coex_access.h b/mac/nfcc_coex_access.h new file mode 100644 index 0000000..de4d103 --- /dev/null +++ b/mac/nfcc_coex_access.h @@ -0,0 +1,43 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NFCC_COEX_ACCESS_H +#define NFCC_COEX_ACCESS_H + +#include <net/mcps802154_schedule.h> + +/** + * nfcc_coex_get_access() - NFCC coexitence compute and return access. + * @region: Region context. + * @next_timestamp_dtu: Date of next access opportunity. + * @next_in_region_dtu: Region start from the start of the access opportunity. + * @region_duration_dtu: Region duration, or 0 for endless region. + * + * Return: A pointer to current access or NULL if none. + */ +struct mcps802154_access *nfcc_coex_get_access(struct mcps802154_region *region, + u32 next_timestamp_dtu, + int next_in_region_dtu, + int region_duration_dtu); + +#endif /* NFCC_COEX_ACCESS_H */ diff --git a/mac/nfcc_coex_region.c b/mac/nfcc_coex_region.c new file mode 100644 index 0000000..1e26b64 --- /dev/null +++ b/mac/nfcc_coex_region.c @@ -0,0 +1,197 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/math64.h> +#include <linux/printk.h> + +#include <linux/netdevice.h> + +#include <net/mcps802154_schedule.h> +#include "net/nfcc_coex_region_nl.h" + +#include "nfcc_coex_region.h" +#include "nfcc_coex_region_call.h" +#include "nfcc_coex_access.h" +#include "nfcc_coex_session.h" +#include "nfcc_coex_trace.h" + +static struct mcps802154_region_ops nfcc_coex_region_ops; + +static struct mcps802154_region *nfcc_coex_open(struct mcps802154_llhw *llhw) +{ + struct nfcc_coex_local *local; + + local = kzalloc(sizeof(*local), GFP_KERNEL); + if (!local) + return NULL; + + local->llhw = llhw; + local->region.ops = &nfcc_coex_region_ops; + local->session.state = NFCC_COEX_STATE_IDLE; + + return &local->region; +} + +static void nfcc_coex_close(struct mcps802154_region *region) +{ + struct nfcc_coex_local *local = region_to_local(region); + + kfree(local); +} + +static void nfcc_coex_notify_stop(struct mcps802154_region *region) +{ + struct nfcc_coex_local *local = region_to_local(region); + + trace_region_nfcc_coex_notify_stop(local); +} + +static int nfcc_coex_call(struct mcps802154_region *region, u32 call_id, + const struct nlattr *attrs, + const struct genl_info *info) +{ + struct nfcc_coex_local *local = region_to_local(region); + + trace_region_nfcc_coex_call(local, call_id); + switch (call_id) { + case NFCC_COEX_CALL_CCC_SESSION_START: + case NFCC_COEX_CALL_CCC_SESSION_STOP: + return nfcc_coex_session_control(local, call_id, attrs, info); + default: + return -EINVAL; + } +} + +static int nfcc_coex_get_demand(struct mcps802154_region *region, + u32 next_timestamp_dtu, + struct mcps802154_region_demand *demand) +{ + struct nfcc_coex_local *local = region_to_local(region); + const struct nfcc_coex_session *session = &local->session; + const struct mcps802154_region_demand *rd = &session->region_demand; + + demand->max_duration_dtu = 0; + + switch (session->state) { + case NFCC_COEX_STATE_STARTED: + if (is_before_dtu(rd->timestamp_dtu, next_timestamp_dtu)) + demand->timestamp_dtu = next_timestamp_dtu; + else + demand->timestamp_dtu = rd->timestamp_dtu; + return 1; + + case NFCC_COEX_STATE_STOPPING: + if (session->first_access) { + if (is_before_dtu(rd->timestamp_dtu, + next_timestamp_dtu)) + demand->timestamp_dtu = next_timestamp_dtu; + else + demand->timestamp_dtu = rd->timestamp_dtu; + } else + demand->timestamp_dtu = next_timestamp_dtu; + return 1; + + default: + return 0; + } +} + +void nfcc_coex_set_state(struct nfcc_coex_local *local, + enum nfcc_coex_state new_state) +{ + struct nfcc_coex_session *session = &local->session; + + trace_region_nfcc_coex_set_state(local, new_state); + session->state = new_state; +} + +void nfcc_coex_report(struct nfcc_coex_local *local) +{ + struct nfcc_coex_session *session = &local->session; + const struct llhw_vendor_cmd_nfcc_coex_get_access_info *get_access_info = + &session->get_access_info; + struct sk_buff *msg; + int r; + + trace_region_nfcc_coex_report(local, get_access_info); + msg = mcps802154_region_event_alloc_skb( + local->llhw, &local->region, + NFCC_COEX_CALL_CCC_SESSION_NOTIFICATION, session->event_portid, + NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return; + +#define P(attr, type, value) \ + do { \ + if (nla_put_##type(msg, NFCC_COEX_CALL_ATTR_CCC_##attr, \ + value)) { \ + goto nla_put_failure; \ + } \ + } while (0) + + P(WATCHDOG_TIMEOUT, u8, get_access_info->watchdog_timeout); + P(STOPPED, u8, get_access_info->stop); +#undef P + + r = mcps802154_region_event(local->llhw, msg); + if (r == -ECONNREFUSED) + /* TODO stop. */ + ; + return; + +nla_put_failure: + trace_region_nfcc_coex_report_nla_put_failure(local); + kfree_skb(msg); +} + +static struct mcps802154_region_ops nfcc_coex_region_ops = { + /* clang-format off */ + .owner = THIS_MODULE, + .name = "nfcc_coex", + .open = nfcc_coex_open, + .close = nfcc_coex_close, + .notify_stop = nfcc_coex_notify_stop, + .call = nfcc_coex_call, + .get_access = nfcc_coex_get_access, + .get_demand = nfcc_coex_get_demand, + /* clang-format on */ +}; + +int __init nfcc_coex_region_init(void) +{ + return mcps802154_region_register(&nfcc_coex_region_ops); +} + +void __exit nfcc_coex_region_exit(void) +{ + mcps802154_region_unregister(&nfcc_coex_region_ops); +} + +module_init(nfcc_coex_region_init); +module_exit(nfcc_coex_region_exit); + +MODULE_DESCRIPTION("Vendor Region for IEEE 802.15.4 MCPS"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/mac/nfcc_coex_region.h b/mac/nfcc_coex_region.h new file mode 100644 index 0000000..b9828df --- /dev/null +++ b/mac/nfcc_coex_region.h @@ -0,0 +1,79 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_NFCC_COEX_REGION_H +#define NET_NFCC_COEX_REGION_H + +#include <net/mcps802154_schedule.h> +#include <net/vendor_cmd.h> +#include "nfcc_coex_session.h" + +/** + * struct nfcc_coex_local - Local context. + */ +struct nfcc_coex_local { + /** + * @region: Region instance returned to MCPS. + */ + struct mcps802154_region region; + /** + * @llhw: Low-level device pointer. + */ + struct mcps802154_llhw *llhw; + /** + * @access: Access returned to MCPS. + */ + struct mcps802154_access access; + /** + * @session: Unique session on the NFCC controller. + */ + struct nfcc_coex_session session; +}; + +static inline struct nfcc_coex_local * +region_to_local(struct mcps802154_region *region) +{ + return container_of(region, struct nfcc_coex_local, region); +} + +static inline struct nfcc_coex_local * +access_to_local(struct mcps802154_access *access) +{ + return container_of(access, struct nfcc_coex_local, access); +} + +/** + * nfcc_coex_set_state() - Set the new state. + * @local: NFCC coex context. + * @new_state: New nfcc_coex state. + */ +void nfcc_coex_set_state(struct nfcc_coex_local *local, + enum nfcc_coex_state new_state); + +/** + * nfcc_coex_report() - Send notification to upper layer. + * @local: Local nfcc coex context. + */ +void nfcc_coex_report(struct nfcc_coex_local *local); + +#endif /* NET_NFCC_COEX_REGION_H */ diff --git a/mac/nfcc_coex_region_call.c b/mac/nfcc_coex_region_call.c new file mode 100644 index 0000000..bf8fb9b --- /dev/null +++ b/mac/nfcc_coex_region_call.c @@ -0,0 +1,221 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/limits.h> + +#include <net/nfcc_coex_region_nl.h> +#include <net/mcps802154_frame.h> + +#include "mcps802154_qorvo.h" +#include "nfcc_coex_session.h" +#include "nfcc_coex_region_call.h" +#include "nfcc_coex_trace.h" + +static const struct nla_policy nfcc_coex_call_nla_policy[NFCC_COEX_CALL_ATTR_MAX + + 1] = { + [NFCC_COEX_CALL_ATTR_CCC_SESSION_PARAMS] = { .type = NLA_NESTED }, +}; + +static const struct nla_policy nfcc_coex_session_param_nla_policy + [NFCC_COEX_CCC_SESSION_PARAM_ATTR_MAX + 1] = { + [NFCC_COEX_CCC_SESSION_PARAM_ATTR_TIME0_NS] = { .type = NLA_U64 }, + [NFCC_COEX_CCC_SESSION_PARAM_ATTR_CHANNEL_NUMBER] = { .type = NLA_U8 }, + [NFCC_COEX_CCC_SESSION_PARAM_ATTR_VERSION] = + NLA_POLICY_RANGE(NLA_U8, 2, 3), + }; + +/** + * nfcc_coex_session_set_parameters() - Set NFCC coexistence session parameters. + * @local: NFCC coexistence context. + * @params: Nested attribute containing session parameters. + * @info: Request information. + * @now_ns: current kernel time. + * + * Return: 0 or error. + */ +static int nfcc_coex_session_set_parameters(struct nfcc_coex_local *local, + const struct nlattr *params, + const struct genl_info *info, + u64 now_ns) +{ + struct nlattr *attrs[NFCC_COEX_CCC_SESSION_PARAM_ATTR_MAX + 1]; + struct nfcc_coex_session *session = &local->session; + struct nfcc_coex_session_params *p = &session->params; + /* Maximum dtu duration is INT32_MAX. */ + const s64 max_time0_ns = + (S32_MAX * NS_PER_SECOND) / local->llhw->dtu_freq_hz; + int r; + + if (!params) + return -EINVAL; + + r = nla_parse_nested(attrs, NFCC_COEX_CCC_SESSION_PARAM_ATTR_MAX, + params, nfcc_coex_session_param_nla_policy, + info->extack); + if (r) + return r; + +#define P(attr, member, type, conv) \ + do { \ + type x; \ + if (attrs[NFCC_COEX_CCC_SESSION_PARAM_ATTR_##attr]) { \ + x = nla_get_##type( \ + attrs[NFCC_COEX_CCC_SESSION_PARAM_ATTR_##attr]); \ + p->member = conv; \ + } \ + } while (0) + + P(TIME0_NS, time0_ns, u64, x); + P(CHANNEL_NUMBER, channel_number, u8, x); + P(VERSION, version, u8, x); + +#undef P + + if (!attrs[NFCC_COEX_CCC_SESSION_PARAM_ATTR_TIME0_NS]) { + p->time0_ns = (NS_PER_SECOND * local->llhw->anticip_dtu) / + local->llhw->dtu_freq_hz + now_ns; + } + + if ((s64)(p->time0_ns - now_ns) > max_time0_ns) + return -ERANGE; + return 0; +} + +/** + * nfcc_coex_session_start() - Start NFCC coex session. + * @local: NFCC coexistence context. + * @info: Request information. + * @now_ns: current kernel time. + * + * Return: 0 or error. + */ +static int nfcc_coex_session_start(struct nfcc_coex_local *local, + const struct genl_info *info, u64 now_ns) +{ + struct nfcc_coex_session *session = &local->session; + const struct nfcc_coex_session_params *p = &session->params; + u32 now_dtu; + s64 diff_ns; + s64 diff_dtu; + int r; + + WARN_ON(session->state == NFCC_COEX_STATE_STARTED); + + trace_region_nfcc_coex_session_start(local, p); + r = mcps802154_get_current_timestamp_dtu(local->llhw, &now_dtu); + if (r) + return r; + + diff_ns = p->time0_ns - now_ns; + diff_dtu = div64_s64(diff_ns * local->llhw->dtu_freq_hz, NS_PER_SECOND); + /* If the requested start date is in the past, start immediately */ + if (diff_dtu < local->llhw->anticip_dtu) { + pr_warn("dw3000: Computed start date is in the past, scheduling" + " to anticip_dtu instead"); + diff_dtu = local->llhw->anticip_dtu; + } + + session->region_demand.timestamp_dtu = now_dtu + diff_dtu; + session->region_demand.max_duration_dtu = 0; + session->event_portid = info->snd_portid; + session->first_access = true; + nfcc_coex_set_state(local, NFCC_COEX_STATE_STARTED); + + mcps802154_reschedule(local->llhw); + return 0; +} + +/** + * nfcc_coex_session_start_all() - Start all for a NFCC coex session. + * @local: NFCC coexistence context. + * @params: Call parameters. + * @info: Request information. + * + * Return: 0 or error. + */ +static int nfcc_coex_session_start_all(struct nfcc_coex_local *local, + const struct nlattr *params, + const struct genl_info *info) +{ + struct nlattr *attrs[NFCC_COEX_CALL_ATTR_MAX + 1]; + int r; + u64 now_ns; + + if (!params) + return -EINVAL; + + r = nla_parse_nested(attrs, NFCC_COEX_CALL_ATTR_MAX, params, + nfcc_coex_call_nla_policy, info->extack); + if (r) + return r; + + if (local->session.state == NFCC_COEX_STATE_STARTED) + return -EBUSY; + + nfcc_coex_session_init(local); + now_ns = ktime_to_ns(ktime_get_boottime()); + r = nfcc_coex_session_set_parameters( + local, attrs[NFCC_COEX_CALL_ATTR_CCC_SESSION_PARAMS], info, + now_ns); + if (r) + return r; + + r = nfcc_coex_session_start(local, info, now_ns); + if (r) + return r; + + return 0; +} + +/** + * nfcc_coex_session_stop() - Stop NFCC coex session. + * @local: NFCC coexistence context. + * + * Return: 0 or error. + */ +static int nfcc_coex_session_stop(struct nfcc_coex_local *local) +{ + struct nfcc_coex_session *session = &local->session; + + trace_region_nfcc_coex_session_stop(local); + if (session->state == NFCC_COEX_STATE_STARTED) { + nfcc_coex_set_state(local, NFCC_COEX_STATE_STOPPING); + mcps802154_schedule_invalidate(local->llhw); + } + return 0; +} + +int nfcc_coex_session_control(struct nfcc_coex_local *local, u32 call_id, + const struct nlattr *params, + const struct genl_info *info) +{ + switch (call_id) { + case NFCC_COEX_CALL_CCC_SESSION_START: + return nfcc_coex_session_start_all(local, params, info); + default: + case NFCC_COEX_CALL_CCC_SESSION_STOP: + return nfcc_coex_session_stop(local); + } +} diff --git a/mac/nfcc_coex_region_call.h b/mac/nfcc_coex_region_call.h new file mode 100644 index 0000000..7720d75 --- /dev/null +++ b/mac/nfcc_coex_region_call.h @@ -0,0 +1,41 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef NET_MCPS802154_NFCC_COEX_REGION_CALL_H +#define NET_MCPS802154_NFCC_COEX_REGION_CALL_H + +#include "nfcc_coex_region.h" + +/** + * nfcc_coex_session_control() - Control nfcc_coex session. + * @local: Vendor context. + * @call_id: Identifier of the nfcc_coex procedure. + * @params: Call parameters. + * @info: Request information. + * + * Return: 0 or error. + */ +int nfcc_coex_session_control(struct nfcc_coex_local *local, u32 call_id, + const struct nlattr *params, + const struct genl_info *info); + +#endif /* NET_MCPS802154_NFCC_COEX_REGION_CALL_H */ diff --git a/mac/nfcc_coex_session.c b/mac/nfcc_coex_session.c new file mode 100644 index 0000000..6247365 --- /dev/null +++ b/mac/nfcc_coex_session.c @@ -0,0 +1,52 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include "nfcc_coex_session.h" +#include "nfcc_coex_region.h" +#include "nfcc_coex_trace.h" + +void nfcc_coex_session_init(struct nfcc_coex_local *local) +{ + struct nfcc_coex_session_params *p = &local->session.params; + + memset(p, 0, sizeof(*p)); + + /* Default protocol version is V2 */ + p->version = 3; +} + +void nfcc_coex_session_update(struct nfcc_coex_local *local, + struct nfcc_coex_session *session, + u32 next_timestamp_dtu, int region_duration_dtu) +{ + struct mcps802154_region_demand *rd = &session->region_demand; + + if (is_before_dtu(rd->timestamp_dtu, next_timestamp_dtu)) { + int shift_dtu = next_timestamp_dtu - rd->timestamp_dtu; + + /* Date is late. */ + trace_region_nfcc_coex_session_update_late(local, shift_dtu, 0); + rd->timestamp_dtu = next_timestamp_dtu; + rd->max_duration_dtu = 0; + } +} diff --git a/mac/nfcc_coex_session.h b/mac/nfcc_coex_session.h new file mode 100644 index 0000000..49de0b3 --- /dev/null +++ b/mac/nfcc_coex_session.h @@ -0,0 +1,117 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_NFCC_COEX_SESSION_H +#define NET_MCPS802154_NFCC_COEX_SESSION_H + +#include <linux/kernel.h> +#include <net/mcps802154_schedule.h> +#include <net/vendor_cmd.h> + +#define NS_PER_SECOND 1000000000ull + +/** + * struct nfcc_coex_session_params - Session parameters. + */ +struct nfcc_coex_session_params { + /** + * @time0_ns: Timestamp in nanoseconds in the ``CLOCK_MONOTONIC`` time. + */ + u64 time0_ns; + /** + * @channel_number: Channel to use for the session, 5 or 9. + */ + u8 channel_number; + /** + * @version: Protocol version to use. + */ + u8 version; +}; + +/** + * enum nfcc_coex_state - State of the unique session. + * @NFCC_COEX_STATE_IDLE: + * Session is not used by access right now. + * @NFCC_COEX_STATE_STARTED: + * Session is started. + * @NFCC_COEX_STATE_STOPPING: + * Session is currently used for the last access. + */ +enum nfcc_coex_state { + NFCC_COEX_STATE_IDLE, + NFCC_COEX_STATE_STARTED, + NFCC_COEX_STATE_STOPPING, +}; + +/** + * struct nfcc_coex_session - Session information. + */ +struct nfcc_coex_session { + /** + * @params: Session parameters, mostly read only while the session is + * active. + */ + struct nfcc_coex_session_params params; + /** + * @event_portid: Port identifier to use for notifications. + */ + u32 event_portid; + /** + * @get_access_info: Next access feedback get through a vendor command. + */ + struct llhw_vendor_cmd_nfcc_coex_get_access_info get_access_info; + /** + * @region_demand: Region access demand which contains start and duration. + */ + struct mcps802154_region_demand region_demand; + /** + * @first_access: True on the first access. + */ + bool first_access; + /** + * @state: State of the unique session. + */ + enum nfcc_coex_state state; +}; + +/* Forward declaration. */ +struct nfcc_coex_local; + +/** + * nfcc_coex_session_init() - Initialize session parameters to default value. + * @local: NFCC coex context. + */ +void nfcc_coex_session_init(struct nfcc_coex_local *local); + +/** + * nfcc_coex_session_update() - Update session timestamps. + * @local: NFCC coex context. + * @session: Session context. + * @next_timestamp_dtu: Next start access opportunity. + * @region_duration_dtu: Region duration, or 0 for endless region. + */ +void nfcc_coex_session_update(struct nfcc_coex_local *local, + struct nfcc_coex_session *session, + u32 next_timestamp_dtu, int region_duration_dtu); + +#endif /* NET_MCPS802154_NFCC_COEX_SESSION_H */ diff --git a/mac/nfcc_coex_trace.h b/mac/nfcc_coex_trace.h new file mode 100644 index 0000000..3d24f81 --- /dev/null +++ b/mac/nfcc_coex_trace.h @@ -0,0 +1,208 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM mcps802154_region_nfcc_coex + +#if !defined(NFCC_COEX_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define NFCC_COEX_TRACE_H + +#include <linux/tracepoint.h> +#include <net/nfcc_coex_region_nl.h> +#include "mcps802154_i.h" +#include "nfcc_coex_region.h" +#include "nfcc_coex_session.h" + +/* clang-format off */ +#define nfcc_coex_call_name(name) \ + { \ + NFCC_COEX_CALL_##name, #name \ + } +#define NFCC_COEX_CALL_SYMBOLS \ + nfcc_coex_call_name(CCC_SESSION_START), \ + nfcc_coex_call_name(CCC_SESSION_STOP), \ + nfcc_coex_call_name(CCC_SESSION_NOTIFICATION) +TRACE_DEFINE_ENUM(NFCC_COEX_CALL_CCC_SESSION_START); +TRACE_DEFINE_ENUM(NFCC_COEX_CALL_CCC_SESSION_STOP); +TRACE_DEFINE_ENUM(NFCC_COEX_CALL_CCC_SESSION_NOTIFICATION); + +#define nfcc_coex_state_name(name) \ + { \ + NFCC_COEX_STATE_##name, #name \ + } +#define NFCC_COEX_STATE_SYMBOLS \ + nfcc_coex_state_name(IDLE), \ + nfcc_coex_state_name(STARTED), \ + nfcc_coex_state_name(STOPPING) +TRACE_DEFINE_ENUM(NFCC_COEX_STATE_IDLE); +TRACE_DEFINE_ENUM(NFCC_COEX_STATE_STARTED); +TRACE_DEFINE_ENUM(NFCC_COEX_STATE_STOPPING); + +#define NFCC_COEX_LOCAL_ENTRY __field(enum nfcc_coex_state, state) +#define NFCC_COEX_LOCAL_ASSIGN __entry->state = local->session.state +#define NFCC_COEX_LOCAL_PR_FMT "state=%s" +#define NFCC_COEX_LOCAL_PR_ARG \ + __print_symbolic(__entry->state, NFCC_COEX_STATE_SYMBOLS) + +DECLARE_EVENT_CLASS( + local_only_evt_nfcc, + TP_PROTO(const struct nfcc_coex_local *local), + TP_ARGS(local), + TP_STRUCT__entry( + NFCC_COEX_LOCAL_ENTRY + ), + TP_fast_assign( + NFCC_COEX_LOCAL_ASSIGN; + ), + TP_printk(NFCC_COEX_LOCAL_PR_FMT, NFCC_COEX_LOCAL_PR_ARG) +); + +TRACE_EVENT( + region_nfcc_coex_session_start, + TP_PROTO(const struct nfcc_coex_local *local, + const struct nfcc_coex_session_params *p), + TP_ARGS(local, p), + TP_STRUCT__entry( + NFCC_COEX_LOCAL_ENTRY + __field(u64, time0_ns) + __field(u8, channel_number) + __field(u8, version) + ), + TP_fast_assign( + NFCC_COEX_LOCAL_ASSIGN; + __entry->time0_ns = p->time0_ns; + __entry->channel_number = p->channel_number; + __entry->version = p->version; + ), + TP_printk(NFCC_COEX_LOCAL_PR_FMT " time0_ns=%llu channel_number=%d version=%d", + NFCC_COEX_LOCAL_PR_ARG, __entry->time0_ns, + __entry->channel_number, __entry->version) +); + +DEFINE_EVENT( + local_only_evt_nfcc, region_nfcc_coex_session_stop, + TP_PROTO(const struct nfcc_coex_local *local), + TP_ARGS(local) +); + +DEFINE_EVENT( + local_only_evt_nfcc, region_nfcc_coex_notify_stop, + TP_PROTO(const struct nfcc_coex_local *local), + TP_ARGS(local) +); + +TRACE_EVENT( + region_nfcc_coex_call, + TP_PROTO(const struct nfcc_coex_local *local, + enum nfcc_coex_call call_id), + TP_ARGS(local, call_id), + TP_STRUCT__entry( + NFCC_COEX_LOCAL_ENTRY + __field(enum nfcc_coex_call, call_id) + ), + TP_fast_assign( + NFCC_COEX_LOCAL_ASSIGN; + __entry->call_id = call_id; + ), + TP_printk(NFCC_COEX_LOCAL_PR_FMT " call_id=%s", + NFCC_COEX_LOCAL_PR_ARG, + __print_symbolic(__entry->call_id, NFCC_COEX_CALL_SYMBOLS)) +); + +TRACE_EVENT( + region_nfcc_coex_session_update_late, + TP_PROTO(const struct nfcc_coex_local *local, + int shift_dtu, int new_duration_dtu), + TP_ARGS(local, shift_dtu, new_duration_dtu), + TP_STRUCT__entry( + NFCC_COEX_LOCAL_ENTRY + __field(int, shift_dtu) + __field(int, new_duration_dtu) + ), + TP_fast_assign( + NFCC_COEX_LOCAL_ASSIGN; + __entry->shift_dtu = shift_dtu; + __entry->new_duration_dtu = new_duration_dtu; + ), + TP_printk(NFCC_COEX_LOCAL_PR_FMT " shift_dtu=0x%08x " + "new_duration_dtu=0x%08x", + NFCC_COEX_LOCAL_PR_ARG, + __entry->shift_dtu, + __entry->new_duration_dtu) +); + +TRACE_EVENT( + region_nfcc_coex_set_state, + TP_PROTO(const struct nfcc_coex_local *local, + enum nfcc_coex_state new_state), + TP_ARGS(local, new_state), + TP_STRUCT__entry( + NFCC_COEX_LOCAL_ENTRY + __field(enum nfcc_coex_state, new_state) + ), + TP_fast_assign( + NFCC_COEX_LOCAL_ASSIGN; + __entry->new_state = new_state; + ), + TP_printk(NFCC_COEX_LOCAL_PR_FMT " new_state=%s", + NFCC_COEX_LOCAL_PR_ARG, + __print_symbolic(__entry->new_state, + NFCC_COEX_STATE_SYMBOLS)) +); + +TRACE_EVENT( + region_nfcc_coex_report, + TP_PROTO(const struct nfcc_coex_local *local, + const struct llhw_vendor_cmd_nfcc_coex_get_access_info *info), + TP_ARGS(local, info), + TP_STRUCT__entry( + NFCC_COEX_LOCAL_ENTRY + __field(bool, watchdog_timeout) + __field(bool, stop) + ), + TP_fast_assign( + NFCC_COEX_LOCAL_ASSIGN; + __entry->watchdog_timeout = info->watchdog_timeout; + __entry->stop = info->stop; + ), + TP_printk(NFCC_COEX_LOCAL_PR_FMT " watchdog_timeout=%s stop=%s", + NFCC_COEX_LOCAL_PR_ARG, + __entry->watchdog_timeout ? "true": "false", + __entry->stop ? "true": "false") +); + +DEFINE_EVENT( + local_only_evt_nfcc, region_nfcc_coex_report_nla_put_failure, + TP_PROTO(const struct nfcc_coex_local *local), + TP_ARGS(local) +); + +/* clang-format on */ + +#endif /* !NFCC_COEX_TRACE_H || TRACE_HEADER_MULTI_READ */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE nfcc_coex_trace +#include <trace/define_trace.h> diff --git a/mac/on_demand_scheduler.c b/mac/on_demand_scheduler.c new file mode 100644 index 0000000..852901e --- /dev/null +++ b/mac/on_demand_scheduler.c @@ -0,0 +1,287 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. + * Please contact Qorvo to inquire about licensing terms. + * + * 802.15.4 mac common part sublayer, on_demand scheduler. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> + +#include <net/mcps802154_schedule.h> + +#include "mcps802154_i.h" +#include "on_demand_scheduler.h" +#include "warn_return.h" + +/** + * struct mcps802154_on_demand_local - local context for on demand scheduler. + */ +struct mcps802154_on_demand_local { + /** + * @scheduler: Common scheduler context. + */ + struct mcps802154_scheduler scheduler; + /** + * @llhw: Low layer hardware attached. + */ + struct mcps802154_llhw *llhw; + /** + * @idle_region: Idle region to delay start of region selected. + */ + struct mcps802154_region *idle_region; +}; + +static inline struct mcps802154_on_demand_local * +scheduler_to_plocal(const struct mcps802154_scheduler *scheduler) +{ + return container_of(scheduler, struct mcps802154_on_demand_local, + scheduler); +} + +static struct mcps802154_scheduler * +mcps802154_on_demand_scheduler_open(struct mcps802154_llhw *llhw) +{ + struct mcps802154_on_demand_local *plocal; + + plocal = kmalloc(sizeof(*plocal), GFP_KERNEL); + if (!plocal) + goto open_failure; + + plocal->idle_region = mcps802154_region_open(llhw, "idle", NULL, NULL); + if (!plocal->idle_region) { + goto open_failure; + } + + plocal->llhw = llhw; + plocal->scheduler.n_regions = 0; + return &plocal->scheduler; + +open_failure: + kfree(plocal); + return NULL; +} + +static void +mcps802154_on_demand_scheduler_close(struct mcps802154_scheduler *scheduler) +{ + struct mcps802154_on_demand_local *plocal = + scheduler_to_plocal(scheduler); + + kfree(plocal->idle_region); + kfree(plocal); +} + +static int mcps802154_on_demand_scheduler_get_next_region( + struct mcps802154_on_demand_local *plocal, struct list_head *regions, + const struct mcps802154_region *first_region, u32 next_timestamp_dtu, + struct mcps802154_region_demand *next_demand, + struct mcps802154_region **next_region) +{ + struct mcps802154_region *region; + int max_duration_dtu = 0; + int r; + + *next_region = NULL; + list_for_each_entry (region, regions, ca_entry) { + struct mcps802154_region_demand candidate = {}; + + if (first_region && region == first_region) + continue; + + r = mcps802154_region_get_demand( + plocal->llhw, region, next_timestamp_dtu, &candidate); + switch (r) { + case 0: + /* The region doesn't have a demand. */ + continue; + case 1: + /* The region have a demand. */ + break; + default: + return r; + } + + /* Reduce duration of candidate region with less priority. */ + if (max_duration_dtu && + (!candidate.max_duration_dtu || + is_before_dtu(next_timestamp_dtu + max_duration_dtu, + candidate.timestamp_dtu + + candidate.max_duration_dtu))) + candidate.max_duration_dtu = max_duration_dtu - + candidate.timestamp_dtu + + next_timestamp_dtu; + + /* Arbitrate between regions. */ + if (!*next_region || + is_before_dtu(candidate.timestamp_dtu, + next_demand->timestamp_dtu)) { + *next_region = region; + *next_demand = candidate; + /* Is there some time remaining for a region with + * less priority? */ + if (!is_before_dtu(next_timestamp_dtu, + next_demand->timestamp_dtu)) + break; + else + max_duration_dtu = next_demand->timestamp_dtu - + next_timestamp_dtu; + } + } + + return *next_region ? 1 : 0; +} + +static int mcps802154_on_demand_scheduler_update_schedule( + struct mcps802154_scheduler *scheduler, + const struct mcps802154_schedule_update *schedule_update, + u32 next_timestamp_dtu) +{ + struct mcps802154_on_demand_local *plocal = + scheduler_to_plocal(scheduler); + struct list_head *regions; + struct mcps802154_region_demand next_demand; + struct mcps802154_region *next_region = NULL; + u32 start_in_schedule_dtu; + int r; + + mcps802154_schedule_get_regions(plocal->llhw, ®ions); + r = mcps802154_on_demand_scheduler_get_next_region( + plocal, regions, NULL, next_timestamp_dtu, &next_demand, + &next_region); + if (r < 0) + return r; + + if (!next_region) + return -ENOENT; + + start_in_schedule_dtu = next_demand.timestamp_dtu - next_timestamp_dtu; + + r = mcps802154_schedule_set_start(schedule_update, next_timestamp_dtu); + WARN_RETURN(r); + + r = mcps802154_schedule_recycle(schedule_update, 0, + MCPS802154_DURATION_NO_CHANGE); + /* Can not fail, only possible error is invalid parameters. */ + WARN_RETURN(r); + + if (next_demand.max_duration_dtu) + next_demand.max_duration_dtu += start_in_schedule_dtu; + start_in_schedule_dtu = 0; + + if (start_in_schedule_dtu) + /* Don't give the access to the region too early. + * And provide advantages: + * - to have a region inserted with a CA invalidate schedule. + * - Reduce latency with TX frame prepared close to region + * start date. */ + r = mcps802154_schedule_add_region(schedule_update, + plocal->idle_region, 0, + start_in_schedule_dtu, + false); + r = mcps802154_schedule_add_region(schedule_update, next_region, + start_in_schedule_dtu, + next_demand.max_duration_dtu, true); + + return r; +} + +static int mcps802154_on_demand_scheduler_get_next_demands( + struct mcps802154_scheduler *scheduler, + const struct mcps802154_region *region, u32 timestamp_dtu, + int duration_dtu, int delta_dtu, + struct mcps802154_region_demand *demands) +{ + struct mcps802154_on_demand_local *plocal = + scheduler_to_plocal(scheduler); + struct list_head *regions; + bool is_demands_set = false; + u32 next_timestamp_dtu = timestamp_dtu; + int r; + + mcps802154_schedule_get_regions(plocal->llhw, ®ions); + + while (true) { + struct mcps802154_region_demand next_demand; + struct mcps802154_region *next_region = NULL; + + r = mcps802154_on_demand_scheduler_get_next_region( + plocal, regions, region, next_timestamp_dtu, + &next_demand, &next_region); + if (r < 0) + return r; + if (!r || !next_demand.max_duration_dtu || + !is_before_dtu(next_demand.timestamp_dtu, + timestamp_dtu + duration_dtu)) + break; + if (!is_demands_set) { + *demands = next_demand; + is_demands_set = true; + } else if (!is_before_dtu(demands->timestamp_dtu + + demands->max_duration_dtu + + delta_dtu, + next_demand.timestamp_dtu)) { + demands->max_duration_dtu = + next_demand.timestamp_dtu + + next_demand.max_duration_dtu - + demands->timestamp_dtu; + } else { + break; + } + + if (!is_before_dtu(demands->timestamp_dtu + + demands->max_duration_dtu, + timestamp_dtu + duration_dtu)) + break; + + next_timestamp_dtu = + demands->timestamp_dtu + demands->max_duration_dtu; + } + return is_demands_set ? 1 : 0; +} + +static struct mcps802154_scheduler_ops + mcps802154_on_demand_scheduler_scheduler = { + .owner = THIS_MODULE, + .name = "on_demand", + .open = mcps802154_on_demand_scheduler_open, + .close = mcps802154_on_demand_scheduler_close, + .set_parameters = NULL, /* No scheduler parameters for now. */ + .update_schedule = + mcps802154_on_demand_scheduler_update_schedule, + .get_next_demands = + mcps802154_on_demand_scheduler_get_next_demands, + }; + +int __init mcps802154_on_demand_scheduler_init(void) +{ + return mcps802154_scheduler_register( + &mcps802154_on_demand_scheduler_scheduler); +} + +void __exit mcps802154_on_demand_scheduler_exit(void) +{ + mcps802154_scheduler_unregister( + &mcps802154_on_demand_scheduler_scheduler); +} diff --git a/mac/on_demand_scheduler.h b/mac/on_demand_scheduler.h new file mode 100644 index 0000000..1568568 --- /dev/null +++ b/mac/on_demand_scheduler.h @@ -0,0 +1,32 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. + * Please contact Qorvo to inquire about licensing terms. + * + * 802.15.4 mac common part sublayer, default data path regions definitions. + */ +#ifndef NET_MCPS802154_ON_DEMAND_SCHEDULER_H +#define NET_MCPS802154_ON_DEMAND_SCHEDULER_H + +int mcps802154_on_demand_scheduler_init(void); +void mcps802154_on_demand_scheduler_exit(void); + +#endif /* NET_MCPS802154_ON_DEMAND_SCHEDULER_H */ diff --git a/mac/ops.c b/mac/ops.c new file mode 100644 index 0000000..bfeb3a2 --- /dev/null +++ b/mac/ops.c @@ -0,0 +1,215 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <net/rtnetlink.h> +#include <linux/jiffies.h> + +#include "mcps802154_i.h" +#include "llhw-ops.h" + +#define DW3000_MAX_STOP_WAIT 10000 + +static int mcps802154_start(struct ieee802154_hw *hw) +{ + struct mcps802154_local *local = hw->priv; + int r; + + ASSERT_RTNL(); + WARN_ON(local->started); + + mutex_lock(&local->fsm_lock); + local->pib.phy_current_channel.page = local->hw->phy->current_page; + local->pib.phy_current_channel.channel = + local->hw->phy->current_channel; + local->pib.phy_current_channel.preamble_code = + local->llhw.current_preamble_code; + r = llhw_set_channel(local, local->pib.phy_current_channel.page, + local->pib.phy_current_channel.channel, + local->pib.phy_current_channel.preamble_code); + if (!r) + r = mcps802154_ca_start(local); + mutex_unlock(&local->fsm_lock); + + return r; +} + +static void mcps802154_stop(struct ieee802154_hw *hw) +{ + struct mcps802154_local *local = hw->priv; + int rc; + + ASSERT_RTNL(); + WARN_ON(!local->started); + + mutex_lock(&local->fsm_lock); + mcps802154_ca_stop(local); + mutex_unlock(&local->fsm_lock); + + rc = wait_event_timeout(local->wq, !local->started, msecs_to_jiffies(DW3000_MAX_STOP_WAIT)); + if (!rc) + pr_err("%s timeout elapsed, event !local->started = false\n", __func__); +} + +static int mcps802154_xmit_async(struct ieee802154_hw *hw, struct sk_buff *skb) +{ + struct mcps802154_local *local = hw->priv; + int r; + + if (unlikely(!local->started)) { + r = -EPIPE; + } else { + r = mcps802154_ca_xmit_skb(local, skb); + } + + schedule_work(&local->tx_work); + return r; +} + +static int mcps802154_ed(struct ieee802154_hw *hw, u8 *level) +{ + /* Not supported for the moment (and not used in Linux SoftMAC). */ + return -EOPNOTSUPP; +} + +static int mcps802154_set_channel(struct ieee802154_hw *hw, u8 page, u8 channel) +{ + struct mcps802154_local *local = hw->priv; + int r; + + if (!local->ops->set_channel) + return -EOPNOTSUPP; + + mutex_lock(&local->fsm_lock); + r = llhw_set_channel(local, page, channel, + local->pib.phy_current_channel.preamble_code); + if (!r) { + local->pib.phy_current_channel.page = page; + local->pib.phy_current_channel.channel = channel; + } + mutex_unlock(&local->fsm_lock); + + return r; +} + +static int mcps802154_set_hw_addr_filt(struct ieee802154_hw *hw, + struct ieee802154_hw_addr_filt *filt, + unsigned long changed) +{ + struct mcps802154_local *local = hw->priv; + int r; + + if (!local->ops->set_hw_addr_filt) + return -EOPNOTSUPP; + + mutex_lock(&local->fsm_lock); + if (local->started) { + r = -EBUSY; + } else { + r = llhw_set_hw_addr_filt(local, filt, changed); + if (!r) { + if (changed & IEEE802154_AFILT_PANID_CHANGED) + local->pib.mac_pan_id = filt->pan_id; + if (changed & IEEE802154_AFILT_SADDR_CHANGED) + local->pib.mac_short_addr = filt->short_addr; + if (changed & IEEE802154_AFILT_IEEEADDR_CHANGED) + local->pib.mac_extended_addr = filt->ieee_addr; + if (changed & IEEE802154_AFILT_PANC_CHANGED) + local->mac_pan_coord = filt->pan_coord; + } + } + mutex_unlock(&local->fsm_lock); + + return r; +} + +static int mcps802154_set_frame_retries(struct ieee802154_hw *hw, s8 retries) +{ + struct mcps802154_local *local = hw->priv; + + if (retries < 0 || retries > 7) + return -EINVAL; + local->pib.mac_max_frame_retries = retries; + + return 0; +} + +static int mcps802154_set_promiscuous_mode(struct ieee802154_hw *hw, bool on) +{ + struct mcps802154_local *local = hw->priv; + int r; + + if (!local->ops->set_promiscuous_mode) + return -EOPNOTSUPP; + + mutex_lock(&local->fsm_lock); + r = llhw_set_promiscuous_mode(local, on); + if (!r) + local->pib.mac_promiscuous = on; + mutex_unlock(&local->fsm_lock); + + return r; +} + +#ifdef CONFIG_HAVE_IEEE802154_SCANNING +static void mcps802154_sw_scan_start(struct ieee802154_hw *hw, __le64 addr) +{ + struct mcps802154_local *local = hw->priv; + + if (!local->ops->set_scanning_mode) + return; + + mutex_lock(&local->fsm_lock); + llhw_set_scanning_mode(local, true); + mutex_unlock(&local->fsm_lock); +} + +static void mcps802154_sw_scan_complete(struct ieee802154_hw *hw) +{ + struct mcps802154_local *local = hw->priv; + + if (!local->ops->set_scanning_mode) + return; + + mutex_lock(&local->fsm_lock); + llhw_set_scanning_mode(local, false); + mutex_unlock(&local->fsm_lock); +} +#endif + +const struct ieee802154_ops mcps802154_ops = { + .owner = THIS_MODULE, + .start = mcps802154_start, + .stop = mcps802154_stop, + .xmit_async = mcps802154_xmit_async, + .ed = mcps802154_ed, + .set_channel = mcps802154_set_channel, + .set_hw_addr_filt = mcps802154_set_hw_addr_filt, + .set_frame_retries = mcps802154_set_frame_retries, + .set_promiscuous_mode = mcps802154_set_promiscuous_mode, +#ifdef CONFIG_HAVE_IEEE802154_SCANNING + .sw_scan_start = mcps802154_sw_scan_start, + .sw_scan_complete = mcps802154_sw_scan_complete, +#endif +}; diff --git a/mac/pctt_access.c b/mac/pctt_access.c new file mode 100644 index 0000000..ae7b994 --- /dev/null +++ b/mac/pctt_access.c @@ -0,0 +1,786 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/math64.h> +#include "pctt_access.h" +#include "pctt_region.h" +#include "pctt_region_call.h" +#include "llhw-ops.h" +#include "mcps802154_qorvo.h" + +#include <net/mcps802154_frame.h> +#include <net/pctt_region_nl.h> +#include <net/pctt_region_params.h> +#include <asm/unaligned.h> + +#include "warn_return.h" + +#define PCTT_STS_FOM_THRESHOLD 153 +/* The FC-PHY shall have a block timing tolerance of +/-100 ppm as + specified in IEEE Std 802.15.4z-2020, subclause 6.9.7.2. */ +#define PCTT_MARGIN_PPM 200 + +static inline int pctt_rx_margin(int duration) +{ + return duration / (1000000 / PCTT_MARGIN_PPM); +} + +static void +pctt_set_sts_params(struct mcps802154_sts_params *sts_params, + const struct pctt_session_params *session_params) +{ + const u8 key[AES_KEYSIZE_128] = { 0x14, 0x14, 0x86, 0x74, 0xd1, 0xd3, + 0x36, 0xaa, 0xf8, 0x60, 0x50, 0xa8, + 0x14, 0xeb, 0x22, 0xf }; + u8 *iv = sts_params->v; + u8 seg_len = session_params->sts_length == PCTT_STS_LENGTH_128 ? + 128 : + session_params->sts_length == PCTT_STS_LENGTH_32 ? + 32 : + 64; + + sts_params->n_segs = session_params->number_of_sts_segments; + sts_params->seg_len = seg_len; + sts_params->sp2_tx_gap_4chips = 0; + sts_params->sp2_rx_gap_4chips[0] = 0; + sts_params->sp2_rx_gap_4chips[1] = 0; + sts_params->sp2_rx_gap_4chips[2] = 0; + sts_params->sp2_rx_gap_4chips[3] = 0; + + /* Overflow is not propagated to the next IV */ + put_unaligned_be32(0x362eeb34u, &iv[0]); + put_unaligned_be32(0xc44fa8fbu + session_params->sts_index, + &iv[sizeof(u32)]); + put_unaligned_be64(0xd37ec3ca1f9a3de4ull, &iv[sizeof(u64)]); + memcpy(sts_params->key, key, AES_KEYSIZE_128); +} + +static void pctt_randomize_psdu(struct pctt_local *local) +{ + struct pctt_session *session = &local->session; + struct pctt_session_params *p = &session->params; + + if (p->randomize_psdu && session->first_access) { + const int A = 1664525, B = 1013904223; + /* First byte of data is used as seed. */ + u32 state = p->data_payload[0]; + u8 *buf = p->data_payload; + int size = p->data_payload_len; + int i; + for (i = 0; i < size; i++) { + state = A * state + B; + buf[i] = state >> 8; + } + } +} + +/** + * pctt_access_setup_frame() - Fill an access frame from a PCTT slot. + * @local: PCTT context. + * @slot: Corresponding slot. + * @frame_dtu: Frame transmission or reception date. + * @frame: Access frame. + * @sts_params: Where to store STS parameters. + */ +static void pctt_access_setup_frame(struct pctt_local *local, + const struct pctt_slot *slot, + const u32 frame_dtu, + struct mcps802154_access_frame *frame, + struct mcps802154_sts_params *sts_params) +{ + struct mcps802154_sts_params *sts_params_for_access = NULL; + struct pctt_session *session = &local->session; + const struct pctt_session_params *p = &session->params; + bool is_rframe = p->rframe_config != PCTT_RFRAME_CONFIG_SP0; + + if (is_rframe) { + pctt_set_sts_params(sts_params, p); + sts_params_for_access = sts_params; + } + + if (slot->is_tx) { + u8 flags = slot->is_immediate ? + 0 : + MCPS802154_TX_FRAME_CONFIG_TIMESTAMP_DTU; + + if (is_rframe) { + if (p->rframe_config == PCTT_RFRAME_CONFIG_SP3) + flags |= MCPS802154_TX_FRAME_CONFIG_SP3; + else if (p->rframe_config == PCTT_RFRAME_CONFIG_SP2) + flags |= MCPS802154_TX_FRAME_CONFIG_SP2; + else + flags |= MCPS802154_TX_FRAME_CONFIG_SP1; + } + *frame = (struct mcps802154_access_frame){ + .is_tx = true, + .tx_frame_config = { + .timestamp_dtu = frame_dtu, + .flags = flags, + .ant_set_id = p->tx_antenna_selection, + }, + .sts_params = sts_params_for_access, + }; + } else { + u8 flags = slot->is_immediate ? + 0 : + MCPS802154_RX_FRAME_CONFIG_TIMESTAMP_DTU; + u16 request = MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU | + MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU | + MCPS802154_RX_FRAME_INFO_RSSI; + + if (is_rframe) { + request |= MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM; + flags |= MCPS802154_RX_FRAME_CONFIG_RANGING; + if (session->cmd_id == PCTT_ID_ATTR_SS_TWR) { + flags |= + MCPS802154_RX_FRAME_CONFIG_RANGING_PDOA; + request |= + MCPS802154_RX_FRAME_INFO_RANGING_PDOA; + } + if (p->rframe_config == PCTT_RFRAME_CONFIG_SP3) + flags |= MCPS802154_RX_FRAME_CONFIG_SP3; + else if (p->rframe_config == PCTT_RFRAME_CONFIG_SP2) + flags |= MCPS802154_RX_FRAME_CONFIG_SP2; + else + flags |= MCPS802154_RX_FRAME_CONFIG_SP1; + } + *frame = (struct mcps802154_access_frame){ + .is_tx = false, + .rx = { + .frame_config = { + .timestamp_dtu = frame_dtu, + .flags = flags, + .timeout_dtu = slot->timeout_dtu, + .ant_set_id = p->rx_antenna_selection, + }, + .frame_info_flags_request = request, + }, + .sts_params = sts_params_for_access, + }; + } +} + +static struct sk_buff *pctt_tx_get_frame(struct mcps802154_access *access, + int frame_idx) +{ + struct pctt_local *local = access_to_local(access); + struct pctt_session *session = &local->session; + const struct pctt_session_params *p = &session->params; + struct sk_buff *skb = NULL; + + if (p->data_payload_len) { + skb = mcps802154_frame_alloc(local->llhw, p->data_payload_len, + GFP_KERNEL); + if (skb) + skb_put_data(skb, p->data_payload, p->data_payload_len); + } + + return skb; +} + +static void pctt_tx_return(struct mcps802154_access *access, int frame_idx, + struct sk_buff *skb, + enum mcps802154_access_tx_return_reason reason) +{ + struct pctt_local *local = access_to_local(access); + + kfree_skb(skb); + + /* Error on TX. */ + if (reason == MCPS802154_ACCESS_TX_RETURN_REASON_CANCEL) + local->results.status = PCTT_STATUS_RANGING_TX_FAILED; + else + local->frames_remaining_nb--; +} + +static bool pctt_rx_sts_good(const struct mcps802154_rx_frame_info *i) +{ + int idx; + if (!(i->flags & MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM)) + return false; + for (idx = 0; idx < MCPS802154_STS_N_SEGS_MAX; idx++) { + if (i->ranging_sts_fom[idx] < PCTT_STS_FOM_THRESHOLD) + return false; + } + return true; +} + +static void pctt_rx_frame_ss_twr(struct pctt_local *local, + const struct mcps802154_rx_frame_info *info) +{ + struct pctt_session *session = &local->session; + const struct pctt_session_params *p = &session->params; + struct pctt_test_ss_twr_results *ss_twr = &local->results.tests.ss_twr; + bool is_responser = p->device_role == PCTT_DEVICE_ROLE_RESPONDER; + + if (info) { + if (!(info->flags & MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU)) { + local->results.status = + PCTT_STATUS_RANGING_RX_PHY_TOA_FAILED; + return; + } + if (!pctt_rx_sts_good(info)) { + local->results.status = + PCTT_STATUS_RANGING_RX_PHY_STS_FAILED; + return; + } + if (info->flags & MCPS802154_RX_FRAME_INFO_RANGING_PDOA) { + struct mcps802154_rx_measurement_info info = {}; + int r; + + info.flags |= MCPS802154_RX_MEASUREMENTS_AOAS; + r = mcps802154_rx_get_measurement(local->llhw, NULL, + &info); + if (!r && + info.flags & MCPS802154_RX_MEASUREMENTS_AOAS && + info.n_aoas) { + /* TODO: Find which aoas index to use */ + ss_twr->pdoa_azimuth_deg_q7 = + map_rad_q11_to_deg_q7( + info.aoas[0].pdoa_rad_q11); + ss_twr->aoa_azimuth_deg_q7 = + map_rad_q11_to_deg_q7( + info.aoas[0].aoa_rad_q11); + } + } + + if (info->flags & MCPS802154_RX_FRAME_INFO_RSSI) { + ss_twr->rssi = info->rssi; + } + + ss_twr->rx_timestamps_rctu = info->timestamp_rctu; + + /* Resync timestamps. */ + if (is_responser) { + struct mcps802154_access *access = &local->access; + struct mcps802154_access_frame *frame = + &local->frames[1]; + struct mcps802154_sts_params *sts_params = + &local->sts_params[1]; + struct pctt_slot *s = &local->slots[1]; + u32 frame_dtu; + + if (!(info->flags & + MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU)) { + local->results.status = + PCTT_STATUS_RANGING_RX_PHY_DEC_FAILED; + return; + } + + frame_dtu = info->timestamp_dtu + p->slot_duration_dtu; + + ss_twr->tx_timestamps_rctu = + mcps802154_tx_timestamp_dtu_to_rmarker_rctu( + local->llhw, frame_dtu, + access->hrp_uwb_params, access->channel, + p->tx_antenna_selection); + + pctt_access_setup_frame(local, s, frame_dtu, frame, + sts_params); + + frame_dtu += p->slot_duration_dtu; + + access->timestamp_dtu = info->timestamp_dtu; + access->duration_dtu = frame_dtu - info->timestamp_dtu; + access->n_frames = 2; + } + + /* Tround time of Initiator or Treply time of Responder. */ + ss_twr->measurement_rctu = mcps802154_difference_timestamp_rctu( + local->llhw, + is_responser ? ss_twr->tx_timestamps_rctu : + ss_twr->rx_timestamps_rctu, + is_responser ? ss_twr->rx_timestamps_rctu : + ss_twr->tx_timestamps_rctu); + } +} + +static void pctt_rx_frame_per_rx(struct pctt_local *local, struct sk_buff *skb, + const struct mcps802154_rx_frame_info *info, + enum mcps802154_rx_error_type error) +{ + struct pctt_test_per_rx_results *per_rx = &local->results.tests.per_rx; + + struct pctt_session *session = &local->session; + const struct pctt_session_params *p = &session->params; + bool has_sts = p->rframe_config != PCTT_RFRAME_CONFIG_SP0; + + if (info) { + if (info->flags & MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU) { + session->next_timestamp_dtu = info->timestamp_dtu; + session->first_rx_synchronized = true; + } + if (info->flags & MCPS802154_RX_FRAME_INFO_RSSI) { + if (!per_rx->rssi || per_rx->rssi > info->rssi) + per_rx->rssi = info->rssi; + } + } + session->next_timestamp_dtu += + p->gap_duration_dtu - pctt_rx_margin(p->gap_duration_dtu); + + switch (error) { + case MCPS802154_RX_ERROR_NONE: + case MCPS802154_RX_ERROR_BAD_CKSUM: + per_rx->acq_detect++; + per_rx->sync_cir_ready++; + per_rx->sfd_found++; + per_rx->eof++; + if (has_sts && pctt_rx_sts_good(info)) + per_rx->sts_found++; + if (skb && (skb->len != p->data_payload_len || + (!p->randomize_psdu && + memcmp(skb->data, p->data_payload, skb->len)))) + per_rx->psdu_bit_error++; + if (error == MCPS802154_RX_ERROR_BAD_CKSUM) + per_rx->psdu_dec_error++; + break; + case MCPS802154_RX_ERROR_SFD_TIMEOUT: + per_rx->acq_detect++; + per_rx->sfd_fail++; + break; + case MCPS802154_RX_ERROR_UNCORRECTABLE: + case MCPS802154_RX_ERROR_FILTERED: + case MCPS802154_RX_ERROR_HPDWARN: + case MCPS802154_RX_ERROR_OTHER: + case MCPS802154_RX_ERROR_PHR_DECODE: + per_rx->acq_detect++; + per_rx->sync_cir_ready++; + per_rx->sfd_found++; + if (error == MCPS802154_RX_ERROR_OTHER) { + per_rx->psdu_dec_error++; + } else if (error == MCPS802154_RX_ERROR_PHR_DECODE) { + per_rx->phr_dec_error++; + } + break; + case MCPS802154_RX_ERROR_TIMEOUT: + per_rx->rx_fail++; + break; + } +} + +static void pctt_rx_frame_rx(struct pctt_local *local, struct sk_buff *skb, + const struct mcps802154_rx_frame_info *info) +{ + struct pctt_test_rx_results *rx = &local->results.tests.rx; + + if (skb) { + int len = min(skb->len, (unsigned int)PCTT_PAYLOAD_MAX_LEN); + + rx->psdu_data_len = len; + memcpy(rx->psdu_data, skb->data, len); + } + + if (info) { + if (info->flags & MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU) { + /* 8ns unit for both stats. */ + rx->rx_done_ts_int = (info->timestamp_rctu >> 32) & + 0xfffffffe; + rx->rx_done_ts_frac = info->timestamp_rctu & 0xffff; + } else { + local->results.status = + PCTT_STATUS_RANGING_RX_PHY_TOA_FAILED; + } + if (info->flags & MCPS802154_RX_FRAME_INFO_RSSI) + rx->rssi = info->rssi; + } else + local->results.status = PCTT_STATUS_RANGING_RX_TIMEOUT; +} + +static void pctt_rx_frame(struct mcps802154_access *access, int frame_idx, + struct sk_buff *skb, + const struct mcps802154_rx_frame_info *info, + enum mcps802154_rx_error_type error) +{ + struct pctt_local *local = access_to_local(access); + struct pctt_session *session = &local->session; + struct llhw_vendor_cmd_pctt_get_frame_info frame_info = {}; + + local->frames_remaining_nb--; + + if (error == MCPS802154_RX_ERROR_BAD_CKSUM) { + struct mcps802154_access_frame *frame = + &access->frames[frame_idx]; + int r; + + frame_info.info.flags = frame->rx.frame_info_flags_request; + r = mcps802154_vendor_cmd(local->llhw, VENDOR_QORVO_OUI, + LLHW_VENDOR_CMD_PCTT_GET_FRAME_INFO, + &frame_info, sizeof(frame_info)); + if (!r) { + skb = frame_info.skb; + info = &frame_info.info; + } + } + + if (session->cmd_id == PCTT_ID_ATTR_SS_TWR) + pctt_rx_frame_ss_twr(local, info); + else if (session->cmd_id == PCTT_ID_ATTR_PER_RX) + pctt_rx_frame_per_rx(local, skb, info, error); + else + pctt_rx_frame_rx(local, skb, info); + + switch (error) { + case MCPS802154_RX_ERROR_NONE: + break; + case MCPS802154_RX_ERROR_SFD_TIMEOUT: + case MCPS802154_RX_ERROR_TIMEOUT: + local->results.status = PCTT_STATUS_RANGING_RX_TIMEOUT; + break; + case MCPS802154_RX_ERROR_FILTERED: + case MCPS802154_RX_ERROR_BAD_CKSUM: + local->results.status = PCTT_STATUS_RANGING_RX_MAC_DEC_FAILED; + break; + case MCPS802154_RX_ERROR_UNCORRECTABLE: + case MCPS802154_RX_ERROR_HPDWARN: + case MCPS802154_RX_ERROR_OTHER: + case MCPS802154_RX_ERROR_PHR_DECODE: + local->results.status = PCTT_STATUS_RANGING_RX_PHY_DEC_FAILED; + break; + } + if (skb) + kfree_skb(skb); +} + +static void pctt_access_done(struct mcps802154_access *access, bool error) +{ + struct pctt_local *local = access_to_local(access); + struct pctt_session *session = &local->session; + bool end_of_test = false; + + if (session->cmd_id == PCTT_ID_ATTR_PER_RX) + local->results.tests.per_rx.attempts++; + + if (error && !local->results.status) + local->results.status = PCTT_STATUS_RANGING_INTERNAL_ERROR; + + switch (session->cmd_id) { + case PCTT_ID_ATTR_LOOPBACK: + case PCTT_ID_ATTR_SS_TWR: + case PCTT_ID_ATTR_RX: + end_of_test = true; + break; + default: + /* Only stop rx tests when all packets are received. */ + if (local->results.status != PCTT_STATUS_RANGING_SUCCESS && + session->cmd_id != PCTT_ID_ATTR_PER_RX) + end_of_test = true; + if (session->stop_request || !local->frames_remaining_nb) + end_of_test = true; + break; + } + + if (end_of_test) { + int r; + + r = mcps802154_vendor_cmd(local->llhw, VENDOR_QORVO_OUI, + LLHW_VENDOR_CMD_PCTT_SETUP_HW, NULL, + 0); + + if (r) + local->results.status = + PCTT_STATUS_RANGING_INTERNAL_ERROR; + pctt_report(local); + pctt_session_set_state(local, PCTT_SESSION_STATE_IDLE); + session->test_on_going = false; + session->stop_request = false; + } +} + +struct mcps802154_access_ops pctt_access_ops = { + .common = { + .access_done = pctt_access_done, + }, + .tx_get_frame = pctt_tx_get_frame, + .tx_return = pctt_tx_return, + .rx_frame = pctt_rx_frame, +}; + +static struct mcps802154_access * +pctt_get_access_periodic_tx(struct pctt_local *local, u32 next_timestamp_dtu) +{ + struct pctt_session *session = &local->session; + const struct pctt_session_params *p = &session->params; + struct mcps802154_access *access = &local->access; + struct pctt_slot *s = local->slots; + u32 frame_dtu; + access->hrp_uwb_params = &session->hrp_uwb_params; + + /* Unique frame in this access. */ + *s = (struct pctt_slot){ + .is_tx = true, + }; + frame_dtu = session->first_access ? next_timestamp_dtu : + session->next_timestamp_dtu; + + pctt_access_setup_frame(local, s, frame_dtu, &local->frames[0], + &local->sts_params[0]); + + access->method = MCPS802154_ACCESS_METHOD_MULTI; + access->ops = &pctt_access_ops; + access->duration_dtu = 0; + access->n_frames = 1; + access->frames = local->frames; + access->timestamp_dtu = frame_dtu; + /* Compute next transmit date. */ + session->next_timestamp_dtu = frame_dtu + p->gap_duration_dtu; + + pctt_randomize_psdu(local); + + return access; +} + +static struct mcps802154_access * +pctt_get_access_per_rx(struct pctt_local *local, u32 next_timestamp_dtu) +{ + struct pctt_session *session = &local->session; + const struct pctt_session_params *p = &session->params; + struct mcps802154_access *access = &local->access; + struct pctt_slot *s = local->slots; + u32 frame_timestamp_dtu; + access->hrp_uwb_params = &session->hrp_uwb_params; + + /* Unique frame in this access. */ + *s = (struct pctt_slot){ + .is_immediate = !session->first_rx_synchronized, + .timeout_dtu = session->first_rx_synchronized ? + 2 * pctt_rx_margin(p->gap_duration_dtu) : + -1, + }; + + frame_timestamp_dtu = session->first_rx_synchronized ? + session->next_timestamp_dtu : + next_timestamp_dtu; + + pctt_access_setup_frame(local, s, frame_timestamp_dtu, + &local->frames[0], &local->sts_params[0]); + + access->ops = &pctt_access_ops; + access->method = MCPS802154_ACCESS_METHOD_MULTI; + access->timestamp_dtu = frame_timestamp_dtu; + access->duration_dtu = + session->first_rx_synchronized ? p->gap_duration_dtu : 0; + access->n_frames = 1; + access->frames = local->frames; + + return access; +} + +static int pctt_handle_loopback(struct mcps802154_access *access) +{ + struct pctt_local *local = access_to_local(access); + struct pctt_session *session = &local->session; + const struct pctt_session_params *p = &session->params; + struct llhw_vendor_cmd_pctt_handle_loopback handle_loopback = {}; + + handle_loopback.ant_set_id = p->tx_antenna_selection; + handle_loopback.data_payload = p->data_payload; + handle_loopback.data_payload_len = p->data_payload_len; + + return mcps802154_vendor_cmd(local->llhw, VENDOR_QORVO_OUI, + LLHW_VENDOR_CMD_PCTT_HANDLE_LOOPBACK, + &handle_loopback, sizeof(handle_loopback)); +} + +static int pctt_tx_done_loopback(struct mcps802154_access *access) +{ + struct pctt_local *local = access_to_local(access); + struct pctt_session *session = &local->session; + const struct pctt_session_params *p = &session->params; + struct llhw_vendor_cmd_pctt_get_loopback_info loopback_info = {}; + int r; + + r = mcps802154_vendor_cmd(local->llhw, VENDOR_QORVO_OUI, + LLHW_VENDOR_CMD_PCTT_GET_LOOPBACK_INFO, + &loopback_info, sizeof(loopback_info)); + if (r) + return r; + + local->results.status = loopback_info.success ? + PCTT_STATUS_RANGING_SUCCESS : + PCTT_STATUS_RANGING_TX_FAILED; + + local->results.tests.loopback.rssi = loopback_info.rssi; + + if (loopback_info.success) { + /* Compare data received with the one sent. */ + struct sk_buff *rx_skb = loopback_info.skb; + WARN_RETURN_ON(!rx_skb, -EFAULT); + + if ((rx_skb->len != p->data_payload_len) || + memcmp(rx_skb->data, p->data_payload, rx_skb->len)) { + local->results.status = PCTT_STATUS_RANGING_TX_FAILED; + } + + /* Free rx_frame skb. */ + kfree_skb(rx_skb); + } + + local->results.tests.loopback.rx_ts_int = + (u32)(loopback_info.rx_timestamp_rctu >> PCTT_TIMESTAMP_SHIFT); + local->results.tests.loopback.rx_ts_frac = + (u16)(loopback_info.rx_timestamp_rctu & + (((unsigned long)1 << PCTT_TIMESTAMP_SHIFT) - 1)); + local->results.tests.loopback.tx_ts_int = + (u32)(loopback_info.tx_timestamp_rctu >> PCTT_TIMESTAMP_SHIFT); + local->results.tests.loopback.tx_ts_frac = + (u16)(loopback_info.tx_timestamp_rctu & + (((unsigned long)1 << PCTT_TIMESTAMP_SHIFT) - 1)); + + /* Request end of current access. */ + return 1; +} + +struct mcps802154_access_vendor_ops pctt_access_ops_loopback = { + .common = { + .access_done = pctt_access_done, + }, + .handle = pctt_handle_loopback, + .tx_done = pctt_tx_done_loopback, +}; + +static struct mcps802154_access * +pctt_get_access_loopback(struct pctt_local *local, u32 next_timestamp_dtu) +{ + struct mcps802154_access *access = &local->access; + + access->method = MCPS802154_ACCESS_METHOD_VENDOR; + access->vendor_ops = &pctt_access_ops_loopback; + access->duration_dtu = 0; + access->timestamp_dtu = next_timestamp_dtu; + access->n_frames = 0; + access->frames = NULL; + return access; +} + +static struct mcps802154_access * +pctt_get_access_ss_twr(struct pctt_local *local, u32 next_timestamp_dtu) +{ + struct mcps802154_access *access = &local->access; + struct pctt_session *session = &local->session; + struct pctt_slot *s = local->slots; + const struct pctt_session_params *p = &session->params; + const bool is_initiator = p->device_role == PCTT_DEVICE_ROLE_INITIATOR; + int nb_frames; + u32 frame_dtu; + int i; + access->hrp_uwb_params = &session->hrp_uwb_params; + + /* First frames. */ + *s = (struct pctt_slot){ + .is_tx = is_initiator, + .timeout_dtu = -1, + }; + s++; + /* Second frames. */ + *s = (struct pctt_slot){ + .is_tx = !is_initiator, + }; + s++; + + if (is_initiator) { + struct pctt_test_ss_twr_results *ss_twr = + &local->results.tests.ss_twr; + + ss_twr->tx_timestamps_rctu = + mcps802154_tx_timestamp_dtu_to_rmarker_rctu( + local->llhw, next_timestamp_dtu, + access->hrp_uwb_params, access->channel, + p->tx_antenna_selection); + } + + frame_dtu = next_timestamp_dtu; + nb_frames = is_initiator ? 2 : 1; + + for (i = 0; i < nb_frames; i++) { + struct mcps802154_access_frame *frame = &local->frames[i]; + struct mcps802154_sts_params *sts_params = + &local->sts_params[i]; + + s = &local->slots[i]; + pctt_access_setup_frame(local, s, frame_dtu, frame, sts_params); + frame_dtu += p->slot_duration_dtu; + } + + access->method = MCPS802154_ACCESS_METHOD_MULTI; + access->ops = &pctt_access_ops; + access->timestamp_dtu = next_timestamp_dtu; + access->frames = local->frames; + access->n_frames = nb_frames; + access->duration_dtu = frame_dtu - next_timestamp_dtu; + + return access; +} + +struct mcps802154_access *pctt_get_access(struct mcps802154_region *region, + u32 next_timestamp_dtu, + int next_in_region_dtu, + int region_duration_dtu) +{ + struct pctt_local *local = region_to_local(region); + struct pctt_session *session = &local->session; + struct mcps802154_access *access = NULL; + + if (!session->test_on_going) + return NULL; + + switch (session->cmd_id) { + case PCTT_ID_ATTR_PERIODIC_TX: + access = pctt_get_access_periodic_tx(local, next_timestamp_dtu); + break; + case PCTT_ID_ATTR_PER_RX: + case PCTT_ID_ATTR_RX: + access = pctt_get_access_per_rx(local, next_timestamp_dtu); + break; + case PCTT_ID_ATTR_LOOPBACK: + access = pctt_get_access_loopback(local, next_timestamp_dtu); + break; + case PCTT_ID_ATTR_SS_TWR: + access = pctt_get_access_ss_twr(local, next_timestamp_dtu); + break; + default: /* LCOV_EXCL_START */ + /* Impossible to cover with unit test. + * The only way is a memory corruption on the cmd_id. */ + break; + /* LCOV_EXCL_STOP */ + } + + WARN_ON(!access); + if (session->first_access) { + int r; + + r = mcps802154_vendor_cmd(local->llhw, VENDOR_QORVO_OUI, + LLHW_VENDOR_CMD_PCTT_SETUP_HW, + &session->setup_hw, + sizeof(session->setup_hw)); + if (r) { + local->results.status = + PCTT_STATUS_RANGING_INTERNAL_ERROR; + pctt_report(local); + pctt_session_set_state(local, PCTT_SESSION_STATE_IDLE); + return NULL; + } + } + + session->first_access = false; + return access; +} diff --git a/mac/pctt_access.h b/mac/pctt_access.h new file mode 100644 index 0000000..d61f8bf --- /dev/null +++ b/mac/pctt_access.h @@ -0,0 +1,43 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef PCTT_ACCESS_H +#define PCTT_ACCESS_H + +#include <net/mcps802154_schedule.h> + +/** + * pctt_get_access() - Get access for a given region at the given timestamp. + * @region: Region. + * @next_timestamp_dtu: Date of next access opportunity. + * @next_in_region_dtu: Region start from the start of the access opportunity. + * @region_duration_dtu: Region duration, or 0 for endless region. + * + * Return: The access. + */ +struct mcps802154_access *pctt_get_access(struct mcps802154_region *region, + u32 next_timestamp_dtu, + int next_in_region_dtu, + int region_duration_dtu); + +#endif /* PCTT_ACCESS_H */ diff --git a/mac/pctt_region.c b/mac/pctt_region.c new file mode 100644 index 0000000..4c11f5b --- /dev/null +++ b/mac/pctt_region.c @@ -0,0 +1,310 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/errno.h> + +#include <net/pctt_region_nl.h> +#include <net/mcps802154_frame.h> + +#include "pctt_trace.h" +#include "pctt_region.h" +#include "pctt_region_call.h" +#include "pctt_access.h" +#include "llhw-ops.h" + +static struct mcps802154_region_ops pctt_region_ops; + +static struct mcps802154_region *pctt_open(struct mcps802154_llhw *llhw) +{ + struct pctt_local *local; + + local = kzalloc(sizeof(*local), GFP_KERNEL); + if (!local) + return NULL; + local->llhw = llhw; + local->region.ops = &pctt_region_ops; + local->session.state = PCTT_SESSION_STATE_DEINIT; + + return &local->region; +} + +static void pctt_close(struct mcps802154_region *region) +{ + struct pctt_local *local = region_to_local(region); + + kfree_sensitive(local); +} + +static int pctt_call(struct mcps802154_region *region, u32 call_id, + const struct nlattr *attrs, const struct genl_info *info) +{ + struct pctt_local *local = region_to_local(region); + + switch (call_id) { + case PCTT_CALL_SESSION_INIT: + return pctt_session_init(local); + case PCTT_CALL_SESSION_DEINIT: + return pctt_session_deinit(local); + case PCTT_CALL_SESSION_GET_STATE: + return pctt_call_session_get_state(local); + case PCTT_CALL_SESSION_GET_PARAMS: + return pctt_call_session_get_params(local); + case PCTT_CALL_SESSION_SET_PARAMS: + case PCTT_CALL_SESSION_CMD: + return pctt_call_session_control(local, call_id, attrs, info); + default: + return -EINVAL; + } +} + +static int pctt_report_periodic_tx(struct pctt_local *local, + struct sk_buff *msg) +{ + trace_region_pctt_report_periodic_tx(local->results.status); + + if (nla_put_u8(msg, PCTT_RESULT_DATA_ATTR_STATUS, + local->results.status)) + return -EMSGSIZE; + return 0; +} + +static int pctt_report_per_rx(struct pctt_local *local, struct sk_buff *msg) +{ + const struct pctt_test_per_rx_results *per_rx = + &local->results.tests.per_rx; + + trace_region_pctt_report_per_rx(local->results.status, per_rx); + +#define P(attr, type, value) \ + do { \ + if (nla_put_##type(msg, PCTT_RESULT_DATA_ATTR_##attr, \ + value)) { \ + goto nla_put_failure; \ + } \ + } while (0) + P(STATUS, u8, PCTT_STATUS_RANGING_SUCCESS); + P(ATTEMPTS, u32, per_rx->attempts); + P(ACQ_DETECT, u32, per_rx->acq_detect); + P(ACQ_REJECT, u32, per_rx->acq_reject); + P(RX_FAIL, u32, per_rx->rx_fail); + P(SYNC_CIR_READY, u32, per_rx->sync_cir_ready); + P(SFD_FAIL, u32, per_rx->sfd_fail); + P(SFD_FOUND, u32, per_rx->sfd_found); + P(PHR_DEC_ERROR, u32, per_rx->phr_dec_error); + P(PHR_BIT_ERROR, u32, per_rx->phr_bit_error); + P(PSDU_DEC_ERROR, u32, per_rx->psdu_dec_error); + P(PSDU_BIT_ERROR, u32, per_rx->psdu_bit_error); + P(STS_FOUND, u32, per_rx->sts_found); + P(EOF, u32, per_rx->eof); + P(RSSI, u8, per_rx->rssi); +#undef P + return 0; + +nla_put_failure: + return -EMSGSIZE; +} + +static int pctt_report_rx(struct pctt_local *local, struct sk_buff *msg) +{ + const struct pctt_test_rx_results *rx = &local->results.tests.rx; + + trace_region_pctt_report_rx(local->results.status, rx); + +#define P(attr, type, value) \ + do { \ + if (nla_put_##type(msg, PCTT_RESULT_DATA_ATTR_##attr, \ + value)) { \ + goto nla_put_failure; \ + } \ + } while (0) + P(STATUS, u8, local->results.status); + P(RX_DONE_TS_INT, u32, rx->rx_done_ts_int); + P(RX_DONE_TS_FRAC, u16, rx->rx_done_ts_frac); + P(AOA_AZIMUTH, s16, rx->aoa_azimuth); + P(AOA_ELEVATION, s16, rx->aoa_elevation); + P(TOA_GAP, u8, rx->toa_gap); + P(PHR, u16, rx->phr); + P(RSSI, u8, rx->rssi); + P(PSDU_DATA_LEN, u16, rx->psdu_data_len); + if (rx->psdu_data_len > 0 && + nla_put(msg, PCTT_RESULT_DATA_ATTR_PSDU_DATA, rx->psdu_data_len, + rx->psdu_data)) + goto nla_put_failure; +#undef P + return 0; + +nla_put_failure: + return -EMSGSIZE; +} + +static int pctt_report_loopback(struct pctt_local *local, struct sk_buff *msg) +{ + struct pctt_session *session = &local->session; + const struct pctt_session_params *p = &session->params; + trace_region_pctt_report_loopback(local->results.status); + +#define P(attr, type, value) \ + do { \ + if (nla_put_##type(msg, PCTT_RESULT_DATA_ATTR_##attr, \ + value)) { \ + goto nla_put_failure; \ + } \ + } while (0) + + P(STATUS, u8, local->results.status); + P(RSSI, u8, local->results.tests.loopback.rssi); + P(RX_TS_INT, u32, local->results.tests.loopback.rx_ts_int); + P(RX_TS_FRAC, u16, local->results.tests.loopback.rx_ts_frac); + P(TX_TS_INT, u32, local->results.tests.loopback.tx_ts_int); + P(TX_TS_FRAC, u16, local->results.tests.loopback.tx_ts_frac); + + /* If test succeeded, return data that was sent (and received) as + * PSDU payload. */ + if (!local->results.status) { + P(PSDU_DATA_LEN, u16, p->data_payload_len); + if (nla_put(msg, PCTT_RESULT_DATA_ATTR_PSDU_DATA, + p->data_payload_len, p->data_payload)) { + goto nla_put_failure; + } + } else { + P(PSDU_DATA_LEN, u16, 0); + } +#undef P + return 0; + +nla_put_failure: + return -EMSGSIZE; +} + +static int pctt_report_ss_twr(struct pctt_local *local, struct sk_buff *msg) +{ + const struct pctt_test_ss_twr_results *ss_twr = + &local->results.tests.ss_twr; + + trace_region_pctt_report_ss_twr(local->results.status, ss_twr); +#define P(attr, type, value) \ + do { \ + if (nla_put_##type(msg, PCTT_RESULT_DATA_ATTR_##attr, \ + value)) { \ + goto nla_put_failure; \ + } \ + } while (0) + P(STATUS, u8, local->results.status); + P(MEASUREMENT, u32, ss_twr->measurement_rctu); + P(PDOA_AZIMUTH_DEG_Q7, s16, ss_twr->pdoa_azimuth_deg_q7); + P(PDOA_ELEVATION_DEG_Q7, s16, ss_twr->pdoa_elevation_deg_q7); + P(AOA_AZIMUTH_DEG_Q7, s16, ss_twr->aoa_azimuth_deg_q7); + P(AOA_ELEVATION_DEG_Q7, s16, ss_twr->aoa_elevation_deg_q7); + P(RSSI, u8, ss_twr->rssi); +#undef P + return 0; + +nla_put_failure: + return -EMSGSIZE; +} + +void pctt_report(struct pctt_local *local) +{ + struct pctt_session *session = &local->session; + struct sk_buff *msg; + struct nlattr *data; + + msg = mcps802154_region_event_alloc_skb(local->llhw, &local->region, + PCTT_CALL_SESSION_NOTIFICATION, + session->event_portid, + NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return; + + if (nla_put_u8(msg, PCTT_CALL_ATTR_CMD_ID, session->cmd_id)) + goto nla_put_failure; + + data = nla_nest_start(msg, PCTT_CALL_ATTR_RESULT_DATA); + if (!data) + goto nla_put_failure; + + switch (session->cmd_id) { + case PCTT_ID_ATTR_PERIODIC_TX: + if (pctt_report_periodic_tx(local, msg)) + goto nla_put_failure; + break; + case PCTT_ID_ATTR_PER_RX: + if (pctt_report_per_rx(local, msg)) + goto nla_put_failure; + break; + case PCTT_ID_ATTR_RX: + if (pctt_report_rx(local, msg)) + goto nla_put_failure; + break; + case PCTT_ID_ATTR_LOOPBACK: + if (pctt_report_loopback(local, msg)) + goto nla_put_failure; + break; + case PCTT_ID_ATTR_SS_TWR: + if (pctt_report_ss_twr(local, msg)) + goto nla_put_failure; + break; + default: /* LCOV_EXCL_START */ + /* Impossible to cover with unit test. + * The only way is a memory corruption on the cmd_id. */ + goto nla_put_failure; + /* LCOV_EXCL_STOP */ + } + + nla_nest_end(msg, data); + mcps802154_region_event(local->llhw, msg); + return; + +nla_put_failure: + trace_region_pctt_report_nla_put_failure(session->cmd_id); + kfree_skb(msg); +} + +static struct mcps802154_region_ops pctt_region_ops = { + /* clang-format off */ + .owner = THIS_MODULE, + .name = "pctt", + .open = pctt_open, + .close = pctt_close, + .call = pctt_call, + .get_access = pctt_get_access, + /* clang-format on */ +}; + +int __init pctt_region_init(void) +{ + return mcps802154_region_register(&pctt_region_ops); +} + +void __exit pctt_region_exit(void) +{ + mcps802154_region_unregister(&pctt_region_ops); +} + +module_init(pctt_region_init); +module_exit(pctt_region_exit); + +MODULE_DESCRIPTION("PCTT Region for IEEE 802.15.4 MCPS"); +MODULE_AUTHOR("Clement Calmels <clement.calmels@qorvo.com>"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/mac/pctt_region.h b/mac/pctt_region.h new file mode 100644 index 0000000..8e7830c --- /dev/null +++ b/mac/pctt_region.h @@ -0,0 +1,343 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef PCTT_REGION_H +#define PCTT_REGION_H + +#include <linux/kernel.h> +#include <net/mcps802154_schedule.h> +#include <net/vendor_cmd.h> +#include <linux/types.h> +#include <net/pctt_region_params.h> +#include <net/pctt_region_nl.h> + +#include "pctt_session.h" + +#define PCTT_SESSION_ID 0 +#define PCTT_BOOLEAN_MAX 1 +#define PCTT_FRAMES_MAX 2 + +#define PCTT_TIMESTAMP_SHIFT 9 +/** + * map_rad_q11_to_deg_q7() - Map a Fixed Point angle to a signed 16-bit integer + * @ang_rad_q11: angle as Q11 fixed_point value in range [-PI, PI] + * + * Return: the angle mapped to deg q7 + */ +static inline s16 map_rad_q11_to_deg_q7(int ang_rad_q11) +{ + /* 180 / (pi * (1 << 4)) => ~3,581. */ + return ang_rad_q11 * 3581 / 1000; +} + +/** + * struct pctt_test_per_rx_results - PER_RX result for report. + */ +struct pctt_test_per_rx_results { + /** + * @attempts: No. of RX attempts. + */ + u32 attempts; + /** + * @acq_detect: No. of times signal was detected. + */ + u32 acq_detect; + /** + * @acq_reject: No. of times signal was rejected. + */ + u32 acq_reject; + /** + * @rx_fail: No. of times RX did not go beyond ACQ stage. + */ + u32 rx_fail; + /** + * @sync_cir_ready: No. of times sync CIR ready event was received. + */ + u32 sync_cir_ready; + /** + * @sfd_fail: No. of time RX was stuck at either ACQ detect or sync CIR ready. + */ + u32 sfd_fail; + /** + * @sfd_found: No. of times SFD was found. + */ + u32 sfd_found; + /** + * @phr_dec_error: No. of times PHR decode failed. + */ + u32 phr_dec_error; + /** + * @phr_bit_error: No. of times PHR bits in error. + */ + u32 phr_bit_error; + /** + * @psdu_dec_error: No. of times payload decode failed. + */ + u32 psdu_dec_error; + /** + * @psdu_bit_error: No. of times payload bits in error. + */ + u32 psdu_bit_error; + /** + * @sts_found: No. of times STS detection was successful. + */ + u32 sts_found; + /** + * @eof: No. of times end of frame event was triggered. + */ + u32 eof; + /** + * @rssi: Received signal strength indication (RSSI). + */ + u8 rssi; +}; + +/** + * struct pctt_test_rx_results - RX result for report. + */ +struct pctt_test_rx_results { + /** + * @rx_done_ts_int: Integer part of timestamp 1/124.8Mhz ticks. + */ + u32 rx_done_ts_int; + /** + * @rx_done_ts_frac: Fractional part of timestamp in 1/(128 * 499.2Mhz) ticks. + */ + u16 rx_done_ts_frac; + /** + * @aoa_azimuth: AoA azimuth in degrees and it is a signed value in Q9.7 format. + */ + s16 aoa_azimuth; + /** + * @aoa_elevation: AoA elevation in degrees and it is a signed value in Q9.7 format. + */ + s16 aoa_elevation; + /** + * @phr: Received PHR (bits 0-12 as per IEEE spec). + */ + u16 phr; + /** + * @psdu_data_len: Length of PSDU Data(N). + */ + u16 psdu_data_len; + /** + * @toa_gap: ToA of main path minus ToA of first path in nanosecond. + */ + u8 toa_gap; + /** + * @rssi: Received signal strength indication (RSSI). + */ + u8 rssi; + /** + * @psdu_data: Received PSDU Data[0:N] bytes. + */ + u8 psdu_data[PCTT_PAYLOAD_MAX_LEN]; +}; + +/** + * struct pctt_test_ss_twr_results - SS_TWR result for report. + */ +struct pctt_test_ss_twr_results { + /** + * @tx_timestamps_rctu: Timestamps of transmitted frame. + */ + u64 tx_timestamps_rctu; + /** + * @rx_timestamps_rctu: Timestamps of received frame. + */ + u64 rx_timestamps_rctu; + /** + * @measurement_rctu: Contains Tround time of Initiator or + * Treply time of Responder depending on DEVICE_ROLE option. + */ + u32 measurement_rctu; + /** + * @pdoa_azimuth_deg_q7: Phase Difference of Arrival Azimuth in deg Q7 + */ + s16 pdoa_azimuth_deg_q7; + /** + * @aoa_azimuth_deg_q7: AoA Azimuth in deg Q7 + */ + s16 aoa_azimuth_deg_q7; + /** + * @pdoa_elevation_deg_q7: Phase Difference of Arrival Elevation in deg Q7 + */ + s16 pdoa_elevation_deg_q7; + /** + * @aoa_elevation_deg_q7: AoA Elevation in deg Q7 + */ + s16 aoa_elevation_deg_q7; + /** + * @rssi: Received signal strength indication (RSSI). + */ + u8 rssi; +}; + +/** + * struct pctt_test_loopback_results - LOOPBACK result for report. + */ +struct pctt_test_loopback_results { + /** + * @rssi: Received signal strength indication (RSSI). + */ + u8 rssi; + /** + * @tx_ts_int: Integer part of TX timestamp in 1/124.8 us. resolution. + */ + u32 tx_ts_int; + /** + * @tx_ts_frac: Fractional part of TX timestamp in 1/124.8/512 us. resolution. + */ + u16 tx_ts_frac; + /** + * @rx_ts_int: Integer part of Rx timestamp in 1/124.8 us. resolution. + */ + u32 rx_ts_int; + /** + * @rx_ts_frac: Fractional part of RX timestamp in 1/124.8/512 us. resolution. + */ + u16 rx_ts_frac; +}; + +/** + * union pctt_tests_results - All commands notifications. + */ +union pctt_tests_results { + /** + * @per_rx: Result of the PER_RX command. + */ + struct pctt_test_per_rx_results per_rx; + /** + * @rx: Result of the RX command. + */ + struct pctt_test_rx_results rx; + /** + * @ss_twr: Result of the SS_TWR command. + */ + struct pctt_test_ss_twr_results ss_twr; + /** + * @loopback: Result of the LOOPBACK command. + */ + struct pctt_test_loopback_results loopback; +}; + +/** + * union pctt_results - Main notification for all commands. + */ +struct pctt_results { + /** + * @status: Result of the command done. + */ + enum pctt_status_ranging status; + /** + * @tests: Result detail. + */ + union pctt_tests_results tests; +}; + +/** + * struct pctt_slot - Information on an active slot. + */ +struct pctt_slot { + /** + * @is_tx: Is transmit frame? + */ + bool is_tx; + /** + * @is_rframe: Is ranging frame? + */ + bool is_rframe; + /** + * @is_immediate: True when the frame is immediate. + */ + bool is_immediate; + /** + * @timeout_dtu: see (mcps802154_rx_frame_config).timeout_dtu. + */ + int timeout_dtu; +}; + +struct pctt_local { + /** + * @region: Region instance returned to MCPS. + */ + struct mcps802154_region region; + /** + * @llhw: Low-level device pointer. + */ + struct mcps802154_llhw *llhw; + /** + * @access: Access returned to MCPS. + */ + struct mcps802154_access access; + /** + * @session: Unique session on the PCTT. + */ + struct pctt_session session; + /** + * @frames: Access frames referenced from access. + */ + struct mcps802154_access_frame frames[PCTT_FRAMES_MAX]; + /** + * @sts_params: STS parameters for access frames. + */ + struct mcps802154_sts_params sts_params[PCTT_FRAMES_MAX]; + /** + * @slots: Descriptions of each active slots for the current session. + */ + struct pctt_slot slots[PCTT_FRAMES_MAX]; + /** + * @results: Test result used for notification at the end of test. + */ + struct pctt_results results; + /** + * @frames_remaining_nb: Number of frame remaining to do for the current test. + */ + int frames_remaining_nb; +}; + +static inline struct pctt_local * +region_to_local(struct mcps802154_region *region) +{ + return container_of(region, struct pctt_local, region); +} + +static inline struct pctt_local * +access_to_local(struct mcps802154_access *access) +{ + return container_of(access, struct pctt_local, access); +} + +/** + * pctt_report() - PCTT Report. + * @local: pctt context. + */ +void pctt_report(struct pctt_local *local); + +/** + * pctt_session_notify_state_change() - Notify session state change to upper layers. + * @local: context. + */ +void pctt_session_notify_state_change(struct pctt_local *local); + +#endif /* PCTT_REGION_H */ diff --git a/mac/pctt_region_call.c b/mac/pctt_region_call.c new file mode 100644 index 0000000..65b60f3 --- /dev/null +++ b/mac/pctt_region_call.c @@ -0,0 +1,339 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/errno.h> +#include <linux/ieee802154.h> + +#include <net/pctt_region_nl.h> +#include <net/mcps802154_frame.h> + +#include "pctt_access.h" +#include "pctt_region.h" +#include "pctt_region_call.h" +#include "pctt_session.h" +#include "pctt_trace.h" + +static const struct nla_policy pctt_call_nla_policy[PCTT_CALL_ATTR_MAX + 1] = { + [PCTT_CALL_ATTR_CMD_ID] = { .type = NLA_U8 }, + [PCTT_CALL_ATTR_RESULT_DATA] = { .type = NLA_NESTED }, + [PCTT_CALL_ATTR_SESSION_ID] = { .type = NLA_U32 }, + [PCTT_CALL_ATTR_SESSION_STATE] = { .type = NLA_U8 }, + [PCTT_CALL_ATTR_SESSION_PARAMS] = { .type = NLA_NESTED }, +}; + +static const struct nla_policy pctt_session_param_nla_policy[PCTT_SESSION_PARAM_ATTR_MAX + + 1] = { + [PCTT_SESSION_PARAM_ATTR_DEVICE_ROLE] = { + .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX, + .max = PCTT_DEVICE_ROLE_INITIATOR, + }, + [PCTT_SESSION_PARAM_ATTR_SHORT_ADDR] = { .type = NLA_U16 }, + [PCTT_SESSION_PARAM_ATTR_DESTINATION_SHORT_ADDR] = { .type = NLA_U16 }, + [PCTT_SESSION_PARAM_ATTR_SLOT_DURATION_RSTU] = { .type = NLA_U32 }, + [PCTT_SESSION_PARAM_ATTR_RX_ANTENNA_SELECTION] = { .type = NLA_U8 }, + [PCTT_SESSION_PARAM_ATTR_TX_ANTENNA_SELECTION] = { .type = NLA_U8 }, + [PCTT_SESSION_PARAM_ATTR_CHANNEL_NUMBER] = { .type = NLA_U8 }, + [PCTT_SESSION_PARAM_ATTR_PREAMBLE_CODE_INDEX] = { .type = NLA_U8 }, + [PCTT_SESSION_PARAM_ATTR_RFRAME_CONFIG] = { + .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX, + .max = PCTT_RFRAME_CONFIG_SP3, + }, + [PCTT_SESSION_PARAM_ATTR_PRF_MODE] = { + .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX, + .max = PCTT_PRF_MODE_HPRF_HIGH_RATE, + }, + [PCTT_SESSION_PARAM_ATTR_PREAMBLE_DURATION] = { + .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX, + .max = PCTT_PREAMBLE_DURATION_64, + }, + [PCTT_SESSION_PARAM_ATTR_SFD_ID] = { + .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX, + .max = PCTT_SFD_ID_4, + }, + [PCTT_SESSION_PARAM_ATTR_NUMBER_OF_STS_SEGMENTS] = { + .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX, + .max = PCTT_NUMBER_OF_STS_SEGMENTS_4_SEGMENTS, + }, + [PCTT_SESSION_PARAM_ATTR_PSDU_DATA_RATE] = { + .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX, + .max = PCTT_PSDU_DATA_RATE_31M2, + }, + [PCTT_SESSION_PARAM_ATTR_BPRF_PHR_DATA_RATE] = { + .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX, + .max = PCTT_PHR_DATA_RATE_6M81, + }, + [PCTT_SESSION_PARAM_ATTR_MAC_FCS_TYPE] = { + .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX, + .max = PCTT_MAC_FCS_TYPE_CRC_32, + }, + [PCTT_SESSION_PARAM_ATTR_TX_ADAPTIVE_PAYLOAD_POWER] = { + .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX, + .max = PCTT_BOOLEAN_MAX, + }, + [PCTT_SESSION_PARAM_ATTR_STS_INDEX] = { .type = NLA_U32 }, + [PCTT_SESSION_PARAM_ATTR_STS_LENGTH] = { + .type = NLA_U8, .validation_type = NLA_VALIDATE_MAX, + .max = PCTT_STS_LENGTH_128, + }, + [PCTT_SESSION_PARAM_ATTR_NUM_PACKETS] = { .type = NLA_U32 }, + [PCTT_SESSION_PARAM_ATTR_T_GAP] = { .type = NLA_U32 }, + [PCTT_SESSION_PARAM_ATTR_T_START] = { .type = NLA_U32 }, + [PCTT_SESSION_PARAM_ATTR_T_WIN] = { .type = NLA_U32 }, + [PCTT_SESSION_PARAM_ATTR_RANDOMIZE_PSDU] = { .type = NLA_U8 }, + [PCTT_SESSION_PARAM_ATTR_PHR_RANGING_BIT] = { .type = NLA_U8 }, + [PCTT_SESSION_PARAM_ATTR_RMARKER_TX_START] = { .type = NLA_U32 }, + [PCTT_SESSION_PARAM_ATTR_RMARKER_RX_START] = { .type = NLA_U32 }, + [PCTT_SESSION_PARAM_ATTR_STS_INDEX_AUTO_INCR] = { .type = NLA_U8 }, + [PCTT_SESSION_PARAM_ATTR_DATA_PAYLOAD] = { + .type = NLA_BINARY, + .len = PCTT_PAYLOAD_MAX_LEN + }, +}; + +int pctt_call_session_get_state(struct pctt_local *local) +{ + struct pctt_session *session = &local->session; + struct sk_buff *msg; + + msg = mcps802154_region_call_alloc_reply_skb( + local->llhw, &local->region, PCTT_CALL_SESSION_GET_STATE, + NLMSG_DEFAULT_SIZE); + if (!msg) + return -ENOMEM; + + if (nla_put_u32(msg, PCTT_CALL_ATTR_SESSION_ID, PCTT_SESSION_ID)) + goto nla_put_failure; + + if (nla_put_u8(msg, PCTT_CALL_ATTR_SESSION_STATE, session->state)) + goto nla_put_failure; + + return mcps802154_region_call_reply(local->llhw, msg); + +nla_put_failure: + kfree_skb(msg); + return -ENOBUFS; +} + +int pctt_call_session_get_params(struct pctt_local *local) +{ + struct pctt_session *session = &local->session; + const struct pctt_session_params *p = &session->params; + struct nlattr *params; + struct sk_buff *msg; + + msg = mcps802154_region_call_alloc_reply_skb( + local->llhw, &local->region, PCTT_CALL_SESSION_GET_PARAMS, + NLMSG_DEFAULT_SIZE); + if (!msg) + return -ENOMEM; + + if (nla_put_u32(msg, PCTT_CALL_ATTR_SESSION_ID, PCTT_SESSION_ID)) + goto nla_put_failure; + + params = nla_nest_start(msg, PCTT_CALL_ATTR_SESSION_PARAMS); + if (!params) + goto nla_put_failure; + +#define P(attr, member, type, conv) \ + do { \ + type x = p->member; \ + if (nla_put_##type(msg, PCTT_SESSION_PARAM_ATTR_##attr, conv)) \ + goto nla_put_failure; \ + } while (0) + P(DEVICE_ROLE, device_role, u8, x); + P(SHORT_ADDR, short_addr, u16, x); + P(DESTINATION_SHORT_ADDR, dst_short_addr, u16, x); + P(RX_ANTENNA_SELECTION, rx_antenna_selection, u8, x); + P(TX_ANTENNA_SELECTION, tx_antenna_selection, u8, x); + P(SLOT_DURATION_RSTU, slot_duration_dtu, u32, + x / local->llhw->rstu_dtu); + P(CHANNEL_NUMBER, channel_number, u8, x); + P(PREAMBLE_CODE_INDEX, preamble_code_index, u8, x); + P(RFRAME_CONFIG, rframe_config, u8, x); + P(PREAMBLE_DURATION, preamble_duration, u8, x); + P(SFD_ID, sfd_id, u8, x); + P(NUMBER_OF_STS_SEGMENTS, number_of_sts_segments, u8, x); + P(PSDU_DATA_RATE, psdu_data_rate, u8, x); + P(MAC_FCS_TYPE, mac_fcs_type, u8, x); + P(PRF_MODE, prf_mode, u8, x); + P(BPRF_PHR_DATA_RATE, phr_data_rate, u8, x); + P(TX_ADAPTIVE_PAYLOAD_POWER, tx_adaptive_payload_power, u8, x); + P(STS_INDEX, sts_index, u32, x); + P(STS_LENGTH, sts_length, u8, x); + P(NUM_PACKETS, num_packets, u32, x); + P(T_GAP, gap_duration_dtu, u32, + (((u64)x * 1000) / (local->llhw->dtu_freq_hz / 1000))); + P(T_START, t_start, u32, x); + P(T_WIN, t_win, u32, x); + P(RANDOMIZE_PSDU, randomize_psdu, u8, x); + P(PHR_RANGING_BIT, phr_ranging_bit, u8, x); + P(RMARKER_TX_START, rmarker_tx_start, u32, x); + P(RMARKER_RX_START, rmarker_rx_start, u32, x); + P(STS_INDEX_AUTO_INCR, sts_index_auto_incr, u8, x); +#undef P + nla_nest_end(msg, params); + + return mcps802154_region_call_reply(local->llhw, msg); +nla_put_failure: + kfree_skb(msg); + return -ENOBUFS; +} + +static int pctt_call_session_set_params(struct pctt_local *local, + const struct nlattr *params, + const struct genl_info *info) +{ + struct nlattr *attrs[PCTT_SESSION_PARAM_ATTR_MAX + 1]; + struct pctt_session *session = &local->session; + struct pctt_session_params *p = &session->params; + int r; + + if (!params) + return -EINVAL; + if (session->test_on_going) + return -EBUSY; + + r = nla_parse_nested(attrs, PCTT_SESSION_PARAM_ATTR_MAX, params, + pctt_session_param_nla_policy, info->extack); + if (r) + return r; + +#define P(attr, member, type, conv) \ + do { \ + int x; \ + if (attrs[PCTT_SESSION_PARAM_ATTR_##attr]) { \ + x = nla_get_##type( \ + attrs[PCTT_SESSION_PARAM_ATTR_##attr]); \ + p->member = conv; \ + } \ + } while (0) +#define PMEMNCPY(attr, member, size) \ + do { \ + if (attrs[PCTT_SESSION_PARAM_ATTR_##attr]) { \ + struct nlattr *attr = \ + attrs[PCTT_SESSION_PARAM_ATTR_##attr]; \ + int len = nla_len(attr); \ + memcpy(p->member, nla_data(attr), len); \ + p->size = len; \ + } \ + } while (0) + + P(DEVICE_ROLE, device_role, u8, x); + P(SHORT_ADDR, short_addr, u16, x); + P(DESTINATION_SHORT_ADDR, dst_short_addr, u16, x); + P(RX_ANTENNA_SELECTION, rx_antenna_selection, u8, x); + P(TX_ANTENNA_SELECTION, tx_antenna_selection, u8, x); + P(SLOT_DURATION_RSTU, slot_duration_dtu, u32, + x * local->llhw->rstu_dtu); + P(CHANNEL_NUMBER, channel_number, u8, x); + P(PREAMBLE_CODE_INDEX, preamble_code_index, u8, x); + P(RFRAME_CONFIG, rframe_config, u8, x); + P(PREAMBLE_DURATION, preamble_duration, u8, x); + P(SFD_ID, sfd_id, u8, x); + P(NUMBER_OF_STS_SEGMENTS, number_of_sts_segments, u8, x); + P(PSDU_DATA_RATE, psdu_data_rate, u8, x); + P(MAC_FCS_TYPE, mac_fcs_type, u8, x); + P(PRF_MODE, prf_mode, u8, x); + P(BPRF_PHR_DATA_RATE, phr_data_rate, u8, x); + P(TX_ADAPTIVE_PAYLOAD_POWER, tx_adaptive_payload_power, u8, x); + P(STS_INDEX, sts_index, u32, x); + P(STS_LENGTH, sts_length, u8, x); + P(NUM_PACKETS, num_packets, u32, x); + P(T_GAP, gap_duration_dtu, u32, + ((u64)x * (local->llhw->dtu_freq_hz / 1000)) / 1000); + P(T_START, t_start, u32, x); + P(T_WIN, t_win, u32, x); + P(RANDOMIZE_PSDU, randomize_psdu, u8, x); + P(PHR_RANGING_BIT, phr_ranging_bit, u8, x); + P(RMARKER_TX_START, rmarker_tx_start, u32, x); + P(RMARKER_RX_START, rmarker_rx_start, u32, x); + P(STS_INDEX_AUTO_INCR, sts_index_auto_incr, u8, x); + PMEMNCPY(DATA_PAYLOAD, data_payload, data_payload_len); +#undef PMEMNCPY +#undef P + + return 0; +} + +static int pctt_call_cmd(struct pctt_local *local, + const struct nlattr *cmd_id_attr, + const struct genl_info *info) +{ + struct pctt_session *session = &local->session; + enum pctt_id_attrs cmd_id; + + if (!cmd_id_attr) + return -EINVAL; + cmd_id = nla_get_u8(cmd_id_attr); + + if (session->test_on_going) { + if (cmd_id == PCTT_ID_ATTR_STOP_TEST && + !session->stop_request) { + session->stop_request = true; + mcps802154_reschedule(local->llhw); + return 0; + } + return -EBUSY; + } else { + u32 now_dtu; + int r; + + if (cmd_id == PCTT_ID_ATTR_STOP_TEST) + return 0; + + /* FIXME: Used only to detect dw3000_is_active. */ + r = mcps802154_get_current_timestamp_dtu(local->llhw, &now_dtu); + if (r) + return r; + r = pctt_session_start_test(local, cmd_id, info); + if (r) + return r; + } + + mcps802154_reschedule(local->llhw); + return 0; +} + +int pctt_call_session_control(struct pctt_local *local, enum pctt_call call_id, + const struct nlattr *params, + const struct genl_info *info) +{ + struct pctt_session *session = &local->session; + struct nlattr *attrs[PCTT_CALL_ATTR_MAX + 1]; + int r; + + if (session->state == PCTT_SESSION_STATE_DEINIT) + return -EPERM; + if (!params || !info) + return -EINVAL; + r = nla_parse_nested(attrs, PCTT_CALL_ATTR_MAX, params, + pctt_call_nla_policy, info->extack); + if (r) + return r; + + if (call_id == PCTT_CALL_SESSION_SET_PARAMS) + return pctt_call_session_set_params( + local, attrs[PCTT_CALL_ATTR_SESSION_PARAMS], info); + else + return pctt_call_cmd(local, attrs[PCTT_CALL_ATTR_CMD_ID], info); +} diff --git a/mac/pctt_region_call.h b/mac/pctt_region_call.h new file mode 100644 index 0000000..5aa0a7d --- /dev/null +++ b/mac/pctt_region_call.h @@ -0,0 +1,57 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#ifndef PCTT_REGION_CALL_H +#define PCTT_REGION_CALL_H + +#include "pctt_region.h" + +/** + * pctt_call_session_control() - PCTT specific region call + * @local: PCTT region context. + * @call_id: Identifier of the call procedure. + * @params: Nested attribute containing procedure parameters. + * @info: Request information. + * + * Return: 0 on success -errno otherwise. + */ +int pctt_call_session_control(struct pctt_local *local, enum pctt_call call_id, + const struct nlattr *params, + const struct genl_info *info); + +/** + * pctt_call_session_get_state() - Return current state on netlink reply. + * @local: PCTT region context. + * + * Return: 0 on success -errno otherwise. + */ +int pctt_call_session_get_state(struct pctt_local *local); + +/** + * pctt_call_session_get_params() - Return session params on netlink reply + * @local: PCTT region context. + * + * Return: 0 on success -errno otherwise. + */ +int pctt_call_session_get_params(struct pctt_local *local); + +#endif /* PCTT_REGION_CALL_H */ diff --git a/mac/pctt_session.c b/mac/pctt_session.c new file mode 100644 index 0000000..70beaf1 --- /dev/null +++ b/mac/pctt_session.c @@ -0,0 +1,194 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include "pctt_session.h" +#include "pctt_trace.h" +#include "pctt_region.h" +#include "pctt_region_call.h" + +#include <linux/errno.h> +#include <linux/ieee802154.h> +#include <linux/string.h> + +int pctt_session_init(struct pctt_local *local) +{ + struct pctt_session *session = &local->session; + struct pctt_session_params *p = &session->params; + + /* Do the same behavior as get_session_state in FiRa. + * INIT state means kzalloc in FiRa once by session_id. + * But as pctt have only one static region. Simulate + * kzalloc to do or already done with the local state. */ + if (session->state != PCTT_SESSION_STATE_DEINIT) + return -EBUSY; + + memset(p, 0, sizeof(*p)); + p->rx_antenna_selection = RX_ANT_SET_ID_DEFAULT; + p->tx_antenna_selection = TX_ANT_SET_ID_DEFAULT; + p->preamble_duration = PCTT_PREAMBLE_DURATION_64; + p->preamble_code_index = 9; + p->sts_length = PCTT_STS_LENGTH_64; + pctt_session_set_state(local, PCTT_SESSION_STATE_INIT); + return 0; +} + +int pctt_session_deinit(struct pctt_local *local) +{ + struct pctt_session *session = &local->session; + + /* Do the same behavior as get_session_state in FiRa. + * DEINIT state means kfree in FiRa. + * But as pctt have only one static region. Simulate + * kfree to do or already done with the local state. */ + if (session->state == PCTT_SESSION_STATE_DEINIT) + return -ENOENT; + if (session->test_on_going) + return -EBUSY; + + pctt_session_set_state(local, PCTT_SESSION_STATE_DEINIT); + return 0; +} + +void pctt_session_set_state(struct pctt_local *local, + enum pctt_session_state new_state) +{ + struct pctt_session *session = &local->session; + const enum pctt_session_state old_state = session->state; + + trace_region_pctt_session_set_state(old_state, new_state); + + session->state = new_state; +} + +int pctt_session_start_test(struct pctt_local *local, enum pctt_id_attrs cmd_id, + const struct genl_info *info) +{ + struct pctt_session *session = &local->session; + const struct pctt_session_params *p = &session->params; + static const enum mcps802154_data_rate pctt_rate_to_mcps_rate[] = { + MCPS802154_DATA_RATE_6M81, + MCPS802154_DATA_RATE_7M80, + MCPS802154_DATA_RATE_27M2, + MCPS802154_DATA_RATE_31M2, + }; + + trace_region_pctt_session_start_test(cmd_id, p); + + switch (cmd_id) { + case PCTT_ID_ATTR_PERIODIC_TX: + break; + case PCTT_ID_ATTR_LOOPBACK: + break; + case PCTT_ID_ATTR_SS_TWR: + if (p->rframe_config != PCTT_RFRAME_CONFIG_SP3) + return -EINVAL; + if (!p->slot_duration_dtu) + return -EINVAL; + break; + case PCTT_ID_ATTR_RX: + break; + case PCTT_ID_ATTR_PER_RX: + break; + default: + return -EINVAL; + } + + /* check uwb parameters. */ + if (p->prf_mode == PCTT_PRF_MODE_BPRF) { + if (p->preamble_code_index < 9 || p->preamble_code_index > 24) + return -EINVAL; + if (p->sfd_id != PCTT_SFD_ID_0 && p->sfd_id != PCTT_SFD_ID_2) + return -EINVAL; + if (p->psdu_data_rate != PCTT_PSDU_DATA_RATE_6M81) + return -EINVAL; + if (p->preamble_duration != PCTT_PREAMBLE_DURATION_64) + return -EINVAL; + if (p->number_of_sts_segments > + PCTT_NUMBER_OF_STS_SEGMENTS_1_SEGMENT) + return -EINVAL; + } else { + if (p->preamble_code_index < 25 || p->preamble_code_index > 32) + return -EINVAL; + if (p->sfd_id == PCTT_SFD_ID_0) + return -EINVAL; + if (p->prf_mode == PCTT_PRF_MODE_HPRF && + p->psdu_data_rate > PCTT_PSDU_DATA_RATE_7M80) + return -EINVAL; + if (p->prf_mode == PCTT_PRF_MODE_HPRF_HIGH_RATE && + p->psdu_data_rate < PCTT_PSDU_DATA_RATE_27M2) + return -EINVAL; + } + if ((p->rframe_config == PCTT_RFRAME_CONFIG_SP0) && + (p->number_of_sts_segments != PCTT_NUMBER_OF_STS_SEGMENTS_NONE)) + return -EINVAL; + if ((p->rframe_config != PCTT_RFRAME_CONFIG_SP0) && + (p->number_of_sts_segments == PCTT_NUMBER_OF_STS_SEGMENTS_NONE)) + return -EINVAL; + if ((p->rframe_config == PCTT_RFRAME_CONFIG_SP3) && + (p->data_payload_len)) + return -EINVAL; + + /* Set radio parameters. */ + switch (p->prf_mode) { + case PCTT_PRF_MODE_BPRF: + session->hrp_uwb_params.prf = MCPS802154_PRF_64; + break; + case PCTT_PRF_MODE_HPRF: + session->hrp_uwb_params.prf = MCPS802154_PRF_125; + break; + default: + session->hrp_uwb_params.prf = MCPS802154_PRF_250; + } + session->hrp_uwb_params.psr = + p->preamble_duration == PCTT_PREAMBLE_DURATION_64 ? + MCPS802154_PSR_64 : + MCPS802154_PSR_32; + session->hrp_uwb_params.sfd_selector = (enum mcps802154_sfd)(p->sfd_id); + session->hrp_uwb_params.phr_hi_rate = !!p->phr_data_rate; + session->hrp_uwb_params.data_rate = + pctt_rate_to_mcps_rate[p->psdu_data_rate]; + + /* Update unique session context. */ + session->first_access = true; + session->first_rx_synchronized = false; + /* FIXME: Delete portid_set_once. + * See: UWB-2057. */ + session->portid_set_once = true; + session->event_portid = info->snd_portid; + /* Set parameters used by the PCTT vendor command. */ + session->setup_hw = (struct llhw_vendor_cmd_pctt_setup_hw){ + .chan = p->channel_number, + .rframe_config = p->rframe_config, + .preamble_code_index = p->preamble_code_index, + .sfd_id = p->sfd_id, + .psdu_data_rate = p->psdu_data_rate, + .preamble_duration = p->preamble_duration, + }; + /* Update region context. */ + memset(&local->results, 0, sizeof(local->results)); + local->frames_remaining_nb = session->params.num_packets; + session->cmd_id = cmd_id; + session->test_on_going = true; + /* At the end, update the state. */ + pctt_session_set_state(local, PCTT_SESSION_STATE_ACTIVE); + return 0; +} diff --git a/mac/pctt_session.h b/mac/pctt_session.h new file mode 100644 index 0000000..40058e4 --- /dev/null +++ b/mac/pctt_session.h @@ -0,0 +1,166 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021-2022 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_PCTT_SESSION_H +#define NET_MCPS802154_PCTT_SESSION_H + +#include <linux/kernel.h> +#include <net/mcps802154_schedule.h> +#include <net/vendor_cmd.h> +#include <net/pctt_region_params.h> +#include <net/pctt_region_nl.h> + +#define PCTT_PAYLOAD_MAX_LEN 4096 + +struct pctt_session_params { + enum pctt_device_role device_role; + __le16 short_addr; + __le16 dst_short_addr; + u8 rx_antenna_selection; + u8 tx_antenna_selection; + int slot_duration_dtu; + int channel_number; + int preamble_code_index; + enum pctt_rframe_config rframe_config; + enum pctt_preamble_duration preamble_duration; + enum pctt_sfd_id sfd_id; + enum pctt_number_of_sts_segments number_of_sts_segments; + enum pctt_psdu_data_rate psdu_data_rate; + enum pctt_mac_fcs_type mac_fcs_type; + enum pctt_prf_mode prf_mode; + enum pctt_phr_data_rate phr_data_rate; + u8 tx_adaptive_payload_power; + u32 sts_index; + enum pctt_sts_length sts_length; + /* Test specific parameters */ + u32 num_packets; + int gap_duration_dtu; + u32 t_start; + u32 t_win; + u8 randomize_psdu; + u8 phr_ranging_bit; + u32 rmarker_tx_start; + u32 rmarker_rx_start; + u8 sts_index_auto_incr; + /* Data payload to put in TX test frame */ + u8 data_payload[PCTT_PAYLOAD_MAX_LEN]; + int data_payload_len; +}; + +/** + * struct pctt_session - Session information. + */ +struct pctt_session { + /** + * @params: Session parameters, mostly read only while the session is + * active. + */ + struct pctt_session_params params; + /** + * @hrp_uwb_params: HRP UWB parameters, read only while the session is + * active. + */ + struct mcps802154_hrp_uwb_params hrp_uwb_params; + /** + * @event_portid: Port identifier to use for notifications. + */ + u32 event_portid; + /** + * @portid_set_once: True when portid have been set once. + * + * FIXME: To be delete with status notification! + */ + bool portid_set_once; + /** + * @first_access: True on the first access. + */ + bool first_access; + /** + * @first_rx_synchronized: True after the first successful reception. + */ + bool first_rx_synchronized; + /** + * @stop_request: True to not start an another access. + */ + bool stop_request; + /** + * @next_timestamp_dtu: next date for next frame. + */ + u32 next_timestamp_dtu; + /** + * @setup_hw: setup hardware through a vendor command. + */ + struct llhw_vendor_cmd_pctt_setup_hw setup_hw; + /** + * @state: UWB session state. + */ + enum pctt_session_state state; + /** + * @cmd_id: test identifier in progress. + */ + enum pctt_id_attrs cmd_id; + /** + * @test_on_going: True when test is in progress. + */ + bool test_on_going; +}; + +/* Forward declaration. */ +struct pctt_local; + +/** + * pctt_session_init() - Initialize session parameters to default value. + * @local: PCTT context. + * + * Return: 0 on success -errno otherwise. + */ +int pctt_session_init(struct pctt_local *local); + +/** + * pctt_session_deinit() - Declare session as not ready. + * @local: PCTT context. + * + * Return: 0 on success -errno otherwise. + */ +int pctt_session_deinit(struct pctt_local *local); + +/** + * pctt_session_set_state() - Update PCTT state and send notification on change. + * @local: PCTT context. + * @new_state: New state to set. + */ +void pctt_session_set_state(struct pctt_local *local, + enum pctt_session_state new_state); + +/** + * pctt_session_start_test() - Start PCTT test. + * @local: PCTT context. + * @id: Test identifier requested. + * @info: Request information. + * + * Return: 0 on success -errno otherwise. + */ +int pctt_session_start_test(struct pctt_local *local, enum pctt_id_attrs id, + const struct genl_info *info); + +#endif /* NET_MCPS802154_PCTT_SESSION_H */ diff --git a/mac/pctt_trace.h b/mac/pctt_trace.h new file mode 100644 index 0000000..3cba8e7 --- /dev/null +++ b/mac/pctt_trace.h @@ -0,0 +1,519 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM mcps802154_region_pctt + +#if !defined(PCTT_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define PCTT_TRACE_H + +#include <linux/tracepoint.h> +#include <net/pctt_region_nl.h> +#include <net/pctt_region_params.h> +#include "pctt_session.h" +#include "pctt_region.h" + +/* clang-format off */ +#define pctt_device_role_name(name) \ + { PCTT_DEVICE_ROLE_##name, #name } +#define PCTT_DEVICE_ROLE_SYMBOLS \ + pctt_device_role_name(RESPONDER), \ + pctt_device_role_name(INITIATOR) +TRACE_DEFINE_ENUM(PCTT_DEVICE_ROLE_RESPONDER); +TRACE_DEFINE_ENUM(PCTT_DEVICE_ROLE_INITIATOR); +#define PCTT_DEVICE_ROLE_ENTRY __field(enum pctt_device_role, device_role) +#define PCTT_DEVICE_ROLE_ASSIGN __entry->device_role = params->device_role +#define PCTT_DEVICE_ROLE_PR_FMT "device_role=%s" +#define PCTT_DEVICE_ROLE_PR_ARG \ + __print_symbolic(__entry->device_role, PCTT_DEVICE_ROLE_SYMBOLS) + +#define pctt_rframe_config_name(name) \ + { PCTT_RFRAME_CONFIG_##name, #name } +#define PCTT_RFRAME_CONFIG_SYMBOLS \ + pctt_rframe_config_name(SP0), \ + pctt_rframe_config_name(SP1), \ + pctt_rframe_config_name(SP2), \ + pctt_rframe_config_name(SP3) +TRACE_DEFINE_ENUM(PCTT_RFRAME_CONFIG_SP0); +TRACE_DEFINE_ENUM(PCTT_RFRAME_CONFIG_SP1); +TRACE_DEFINE_ENUM(PCTT_RFRAME_CONFIG_SP2); +TRACE_DEFINE_ENUM(PCTT_RFRAME_CONFIG_SP3); +#define PCTT_RFRAME_CONFIG_ENTRY __field(enum pctt_rframe_config, rframe_config) +#define PCTT_RFRAME_CONFIG_ASSIGN __entry->rframe_config = params->rframe_config +#define PCTT_RFRAME_CONFIG_PR_FMT "rframe_config=%s" +#define PCTT_RFRAME_CONFIG_PR_ARG \ + __print_symbolic(__entry->rframe_config, PCTT_RFRAME_CONFIG_SYMBOLS) + +#define pctt_prf_mode_name(name) \ + { PCTT_PRF_MODE_##name, #name } +#define PCTT_PRF_MODE_SYMBOLS \ + pctt_prf_mode_name(BPRF), \ + pctt_prf_mode_name(HPRF), \ + pctt_prf_mode_name(HPRF_HIGH_RATE) +TRACE_DEFINE_ENUM(PCTT_PRF_MODE_BPRF); +TRACE_DEFINE_ENUM(PCTT_PRF_MODE_HPRF); +TRACE_DEFINE_ENUM(PCTT_PRF_MODE_HPRF_HIGH_RATE); +#define PCTT_PRF_MODE_ENTRY __field(enum pctt_prf_mode, prf_mode) +#define PCTT_PRF_MODE_ASSIGN __entry->prf_mode = params->prf_mode +#define PCTT_PRF_MODE_PR_FMT "prf_mode=%s" +#define PCTT_PRF_MODE_PR_ARG \ + __print_symbolic(__entry->prf_mode, PCTT_PRF_MODE_SYMBOLS) + +#define pctt_preamble_duration_name(name) \ + { PCTT_PREAMBLE_DURATION_##name, #name } +#define PCTT_PREAMBLE_DURATION_SYMBOLS \ + pctt_preamble_duration_name(32), \ + pctt_preamble_duration_name(64) +TRACE_DEFINE_ENUM(PCTT_PREAMBLE_DURATION_32); +TRACE_DEFINE_ENUM(PCTT_PREAMBLE_DURATION_64); +#define PCTT_PREAMBLE_DURATION_ENTRY \ + __field(enum pctt_preamble_duration, preamble_duration) +#define PCTT_PREAMBLE_DURATION_ASSIGN \ + __entry->preamble_duration = params->preamble_duration +#define PCTT_PREAMBLE_DURATION_PR_FMT "preamble_duration=%s" +#define PCTT_PREAMBLE_DURATION_PR_ARG \ + __print_symbolic(__entry->preamble_duration, \ + PCTT_PREAMBLE_DURATION_SYMBOLS) + +#define pctt_sfd_id_name(name) \ + { PCTT_SFD_ID_##name, #name } +#define PCTT_SFD_ID_SYMBOLS \ + pctt_sfd_id_name(0), \ + pctt_sfd_id_name(1), \ + pctt_sfd_id_name(2), \ + pctt_sfd_id_name(3), \ + pctt_sfd_id_name(4) +TRACE_DEFINE_ENUM(PCTT_SFD_ID_0); +TRACE_DEFINE_ENUM(PCTT_SFD_ID_1); +TRACE_DEFINE_ENUM(PCTT_SFD_ID_2); +TRACE_DEFINE_ENUM(PCTT_SFD_ID_3); +TRACE_DEFINE_ENUM(PCTT_SFD_ID_4); +#define PCTT_SFD_ID_ENTRY __field(enum pctt_sfd_id, sfd_id) +#define PCTT_SFD_ID_ASSIGN __entry->sfd_id = params->sfd_id +#define PCTT_SFD_ID_PR_FMT "sfd_id=%s" +#define PCTT_SFD_ID_PR_ARG \ + __print_symbolic(__entry->sfd_id, PCTT_SFD_ID_SYMBOLS) + +#define pctt_number_of_sts_segments_name(name) \ + { PCTT_NUMBER_OF_STS_SEGMENTS_##name, #name } +#define PCTT_NUMBER_OF_STS_SEGMENTS_SYMBOLS \ + pctt_number_of_sts_segments_name(NONE), \ + pctt_number_of_sts_segments_name(1_SEGMENT), \ + pctt_number_of_sts_segments_name(2_SEGMENTS), \ + pctt_number_of_sts_segments_name(3_SEGMENTS), \ + pctt_number_of_sts_segments_name(4_SEGMENTS) +TRACE_DEFINE_ENUM(PCTT_NUMBER_OF_STS_SEGMENTS_NONE); +TRACE_DEFINE_ENUM(PCTT_NUMBER_OF_STS_SEGMENTS_1_SEGMENT); +TRACE_DEFINE_ENUM(PCTT_NUMBER_OF_STS_SEGMENTS_2_SEGMENTS); +TRACE_DEFINE_ENUM(PCTT_NUMBER_OF_STS_SEGMENTS_3_SEGMENTS); +TRACE_DEFINE_ENUM(PCTT_NUMBER_OF_STS_SEGMENTS_4_SEGMENTS); +#define PCTT_NUMBER_OF_STS_SEGMENTS_ENTRY \ + __field(enum pctt_number_of_sts_segments, number_of_sts_segments) +#define PCTT_NUMBER_OF_STS_SEGMENTS_ASSIGN \ + __entry->number_of_sts_segments = params->number_of_sts_segments +#define PCTT_NUMBER_OF_STS_SEGMENTS_PR_FMT "number_of_sts_segments=%s" +#define PCTT_NUMBER_OF_STS_SEGMENTS_PR_ARG \ + __print_symbolic(__entry->number_of_sts_segments, \ + PCTT_NUMBER_OF_STS_SEGMENTS_SYMBOLS) + +#define pctt_psdu_data_rate_name(name) \ + { PCTT_PSDU_DATA_RATE_##name, #name } +#define PCTT_PSDU_DATA_RATE_SYMBOLS \ + pctt_psdu_data_rate_name(6M81), \ + pctt_psdu_data_rate_name(7M80), \ + pctt_psdu_data_rate_name(27M2), \ + pctt_psdu_data_rate_name(31M2) +TRACE_DEFINE_ENUM(PCTT_PSDU_DATA_RATE_6M81); +TRACE_DEFINE_ENUM(PCTT_PSDU_DATA_RATE_7M80); +TRACE_DEFINE_ENUM(PCTT_PSDU_DATA_RATE_27M2); +TRACE_DEFINE_ENUM(PCTT_PSDU_DATA_RATE_31M2); +#define PCTT_PSDU_DATA_RATE_ENTRY \ + __field(enum pctt_psdu_data_rate, psdu_data_rate) +#define PCTT_PSDU_DATA_RATE_ASSIGN \ + __entry->psdu_data_rate = params->psdu_data_rate +#define PCTT_PSDU_DATA_RATE_PR_FMT "psdu_data_rate=%s" +#define PCTT_PSDU_DATA_RATE_PR_ARG \ + __print_symbolic(__entry->psdu_data_rate, PCTT_PSDU_DATA_RATE_SYMBOLS) + +#define pctt_phr_data_rate_name(name) \ + { PCTT_PHR_DATA_RATE##name, #name } +#define PCTT_PHR_DATA_RATE_SYMBOLS \ + pctt_phr_data_rate_name(850k), \ + pctt_phr_data_rate_name(6M81), \ +TRACE_DEFINE_ENUM(PCTT_PHR_DATA_RATE_850k); +TRACE_DEFINE_ENUM(PCTT_PHR_DATA_RATE_6M81); +#define PCTT_PHR_DATA_RATE_ENTRY \ + __field(enum pctt_phr_data_rate, phr_data_rate) +#define PCTT_PHR_DATA_RATE_ASSIGN \ + __entry->phr_data_rate = params->phr_data_rate +#define PCTT_PHR_DATA_RATE_PR_FMT "phr_data_rate=%s" +#define PCTT_PHR_DATA_RATE_PR_ARG \ + __print_symbolic(__entry->phr_data_rate, PCTT_PHR_DATA_RATE_SYMBOLS) + +#define pctt_mac_fcs_type_name(name) \ + { PCTT_MAC_FCS_TYPE_##name, #name } +#define PCTT_MAC_FCS_TYPE_SYMBOLS \ + pctt_mac_fcs_type_name(CRC_16), \ + pctt_mac_fcs_type_name(CRC_32) +TRACE_DEFINE_ENUM(PCTT_MAC_FCS_TYPE_CRC_16); +TRACE_DEFINE_ENUM(PCTT_MAC_FCS_TYPE_CRC_32); +#define PCTT_MAC_FCS_TYPE_ENTRY __field(enum pctt_mac_fcs_type, mac_fcs_type) +#define PCTT_MAC_FCS_TYPE_ASSIGN __entry->mac_fcs_type = params->mac_fcs_type +#define PCTT_MAC_FCS_TYPE_PR_FMT "mac_fcs_type=%s" +#define PCTT_MAC_FCS_TYPE_PR_ARG \ + __print_symbolic(__entry->mac_fcs_type, PCTT_MAC_FCS_TYPE_SYMBOLS) + +/* clang-format off */ +#define pctt_status_ranging_name(name) \ + { PCTT_STATUS_RANGING_##name, #name } +#define PCTT_STATUS_RANGING_SYMBOLS \ + pctt_status_ranging_name(INTERNAL_ERROR), \ + pctt_status_ranging_name(SUCCESS), \ + pctt_status_ranging_name(TX_FAILED), \ + pctt_status_ranging_name(RX_TIMEOUT), \ + pctt_status_ranging_name(RX_PHY_DEC_FAILED), \ + pctt_status_ranging_name(RX_PHY_TOA_FAILED), \ + pctt_status_ranging_name(RX_PHY_STS_FAILED), \ + pctt_status_ranging_name(RX_MAC_DEC_FAILED), \ + pctt_status_ranging_name(RX_MAC_IE_DEC_FAILED), \ + pctt_status_ranging_name(RX_MAC_IE_MISSING) +TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_INTERNAL_ERROR); +TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_SUCCESS); +TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_TX_FAILED); +TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_RX_TIMEOUT); +TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_RX_PHY_DEC_FAILED); +TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_RX_PHY_TOA_FAILED); +TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_RX_PHY_STS_FAILED); +TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_RX_MAC_DEC_FAILED); +TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_RX_MAC_IE_DEC_FAILED); +TRACE_DEFINE_ENUM(PCTT_STATUS_RANGING_RX_MAC_IE_MISSING); +#define PCTT_STATUS_RANGING_ENTRY \ + __field(enum pctt_status_ranging, status_ranging) +#define PCTT_STATUS_RANGING_ASSIGN __entry->status_ranging = status_ranging +#define PCTT_STATUS_RANGING_PR_FMT "status_ranging=%s" +#define PCTT_STATUS_RANGING_PR_ARG \ + __print_symbolic(__entry->status_ranging, PCTT_STATUS_RANGING_SYMBOLS) + +#define pctt_session_state_name(name) \ + { PCTT_SESSION_STATE_##name, #name } +#define PCTT_SESSION_STATE_SYMBOLS \ + pctt_session_state_name(INIT), \ + pctt_session_state_name(DEINIT), \ + pctt_session_state_name(ACTIVE), \ + pctt_session_state_name(IDLE) +TRACE_DEFINE_ENUM(PCTT_SESSION_STATE_INIT); +TRACE_DEFINE_ENUM(PCTT_SESSION_STATE_DEINIT); +TRACE_DEFINE_ENUM(PCTT_SESSION_STATE_ACTIVE); +TRACE_DEFINE_ENUM(PCTT_SESSION_STATE_IDLE); +#define PCTT_SESSION_STATE_ENTRY(name) __field(enum pctt_session_state, name) +#define PCTT_SESSION_STATE_ASSIGN(name) __entry->name = name +#define PCTT_SESSION_STATE_PR_FMT(name) "name=%s" +#define PCTT_SESSION_STATE_PR_ARG(name) \ + __print_symbolic(__entry->name, PCTT_SESSION_STATE_SYMBOLS) + +#define pctt_id_name(name) \ + { PCTT_ID_ATTR_##name, #name } +#define PCTT_ID_SYMBOLS \ + pctt_id_name(UNSPEC), \ + pctt_id_name(PERIODIC_TX), \ + pctt_id_name(PER_RX), \ + pctt_id_name(RX), \ + pctt_id_name(LOOPBACK), \ + pctt_id_name(SS_TWR), \ + pctt_id_name(RX) +TRACE_DEFINE_ENUM(PCTT_ID_ATTR_UNSPEC); +TRACE_DEFINE_ENUM(PCTT_ID_ATTR_PERIODIC_TX); +TRACE_DEFINE_ENUM(PCTT_ID_ATTR_PER_RX); +TRACE_DEFINE_ENUM(PCTT_ID_ATTR_RX); +TRACE_DEFINE_ENUM(PCTT_ID_ATTR_LOOPBACK); +TRACE_DEFINE_ENUM(PCTT_ID_ATTR_SS_TWR); +TRACE_DEFINE_ENUM(PCTT_ID_ATTR_STOP_TEST); +#define PCTT_ID_ENTRY __field(enum pctt_id_attrs, cmd_id) +#define PCTT_ID_ASSIGN __entry->cmd_id = cmd_id +#define PCTT_ID_PR_FMT "cmd_id=%s" +#define PCTT_ID_PR_ARG \ + __print_symbolic(__entry->cmd_id, PCTT_ID_SYMBOLS) + +TRACE_EVENT( + region_pctt_session_start_test, + TP_PROTO(enum pctt_id_attrs cmd_id, + const struct pctt_session_params *params), + TP_ARGS(cmd_id, params), + TP_STRUCT__entry( + PCTT_ID_ENTRY + PCTT_DEVICE_ROLE_ENTRY + __field(__le16, short_addr) + __field(__le16, dst_short_addr) + __field(int, slot_duration_dtu) + __field(int, channel_number) + __field(int, preamble_code_index) + PCTT_RFRAME_CONFIG_ENTRY + PCTT_PRF_MODE_ENTRY + PCTT_PREAMBLE_DURATION_ENTRY + PCTT_SFD_ID_ENTRY + PCTT_NUMBER_OF_STS_SEGMENTS_ENTRY + PCTT_PSDU_DATA_RATE_ENTRY + PCTT_MAC_FCS_TYPE_ENTRY + __field(u8, tx_adaptive_payload_power) + __field(uint32_t, sts_index) + ), + TP_fast_assign( + PCTT_ID_ASSIGN; + PCTT_DEVICE_ROLE_ASSIGN; + __entry->short_addr = params->short_addr; + __entry->dst_short_addr = params->dst_short_addr; + __entry->slot_duration_dtu = params->slot_duration_dtu; + __entry->channel_number = params->channel_number; + __entry->preamble_code_index = params->preamble_code_index; + PCTT_RFRAME_CONFIG_ASSIGN; + PCTT_PRF_MODE_ASSIGN; + PCTT_PREAMBLE_DURATION_ASSIGN; + PCTT_SFD_ID_ASSIGN; + PCTT_NUMBER_OF_STS_SEGMENTS_ASSIGN; + PCTT_PSDU_DATA_RATE_ASSIGN; + PCTT_MAC_FCS_TYPE_ASSIGN; + __entry->tx_adaptive_payload_power = + params->tx_adaptive_payload_power; + __entry->sts_index = params->sts_index; + ), + TP_printk( + PCTT_ID_PR_FMT " " PCTT_DEVICE_ROLE_PR_FMT " " + "short_addr=0x%x " + "dst_short_addr=0x%x slot_duration_dtu=%d " + "channel_number=%d preamble_code_index=%d " + PCTT_RFRAME_CONFIG_PR_FMT " " + PCTT_PRF_MODE_PR_FMT " " + PCTT_PREAMBLE_DURATION_PR_FMT " " + PCTT_SFD_ID_PR_FMT " " + PCTT_NUMBER_OF_STS_SEGMENTS_PR_FMT " " + PCTT_PSDU_DATA_RATE_PR_FMT " " + PCTT_MAC_FCS_TYPE_PR_FMT " " + "tx_adaptive_payload_power=%u sts_index=%u", + PCTT_ID_PR_ARG, PCTT_DEVICE_ROLE_PR_ARG, + __entry->short_addr, + __entry->dst_short_addr, + __entry->slot_duration_dtu, + __entry->channel_number, + __entry->preamble_code_index, + PCTT_RFRAME_CONFIG_PR_ARG, + PCTT_PRF_MODE_PR_ARG, + PCTT_PREAMBLE_DURATION_PR_ARG, + PCTT_SFD_ID_PR_ARG, + PCTT_NUMBER_OF_STS_SEGMENTS_PR_ARG, + PCTT_PSDU_DATA_RATE_PR_ARG, + PCTT_MAC_FCS_TYPE_PR_ARG, + __entry->tx_adaptive_payload_power, + __entry->sts_index + ) +); + +TRACE_EVENT(region_pctt_session_set_state, + TP_PROTO(enum pctt_session_state old_state, + enum pctt_session_state new_state), + TP_ARGS(old_state, new_state), + TP_STRUCT__entry( + PCTT_SESSION_STATE_ENTRY(old_state) + PCTT_SESSION_STATE_ENTRY(new_state) + ), + TP_fast_assign( + PCTT_SESSION_STATE_ASSIGN(old_state); + PCTT_SESSION_STATE_ASSIGN(new_state); + ), + TP_printk(PCTT_SESSION_STATE_PR_FMT(old_state) " " + PCTT_SESSION_STATE_PR_FMT(new_state), + PCTT_SESSION_STATE_PR_ARG(old_state), + PCTT_SESSION_STATE_PR_ARG(new_state)) +); + +TRACE_EVENT(region_pctt_report_periodic_tx, + TP_PROTO(enum pctt_status_ranging status_ranging), + TP_ARGS(status_ranging), + TP_STRUCT__entry( + PCTT_STATUS_RANGING_ENTRY + ), + TP_fast_assign( + PCTT_STATUS_RANGING_ASSIGN; + ), + TP_printk(PCTT_STATUS_RANGING_PR_FMT, + PCTT_STATUS_RANGING_PR_ARG) +); + +TRACE_EVENT(region_pctt_report_per_rx, + TP_PROTO(enum pctt_status_ranging status_ranging, + const struct pctt_test_per_rx_results *per_rx), + TP_ARGS(status_ranging, per_rx), + TP_STRUCT__entry( + PCTT_STATUS_RANGING_ENTRY + __field(u32, attempts) + __field(u32, acq_detect) + __field(u32, acq_reject) + __field(u32, rx_fail) + __field(u32, sync_cir_ready) + __field(u32, sfd_fail) + __field(u32, sfd_found) + __field(u32, phr_dec_error) + __field(u32, phr_bit_error) + __field(u32, psdu_dec_error) + __field(u32, psdu_bit_error) + __field(u32, sts_found) + __field(u32, eof) + __field(u8, rssi) + ), + TP_fast_assign( + PCTT_STATUS_RANGING_ASSIGN; + __entry->attempts = per_rx->attempts; + __entry->acq_detect = per_rx->acq_detect; + __entry->acq_reject = per_rx->acq_reject; + __entry->rx_fail = per_rx->rx_fail; + __entry->sync_cir_ready = per_rx->sync_cir_ready; + __entry->sfd_fail = per_rx->sfd_fail; + __entry->sfd_found = per_rx->sfd_found; + __entry->phr_dec_error = per_rx->phr_dec_error; + __entry->phr_bit_error = per_rx->phr_bit_error; + __entry->psdu_dec_error = per_rx->psdu_dec_error; + __entry->psdu_bit_error = per_rx->psdu_bit_error; + __entry->sts_found = per_rx->sts_found; + __entry->eof = per_rx->eof; + __entry->rssi = per_rx->rssi; + ), + TP_printk(PCTT_STATUS_RANGING_PR_FMT " " + "attempts=%u acq_detect=%u acq_reject=%u " + "rx_fail=%u sync_cir_ready=%u sfd_fail=%u " + "sfd_found=%u phr_dec_error=%u phr_bit_error=%u " + "psdu_dec_error=%u psdu_bit_error=%u sts_found=%u " + "eof=%u rssi=%u ", + PCTT_STATUS_RANGING_PR_ARG, __entry->attempts, + __entry->acq_detect, __entry->acq_reject, + __entry->rx_fail, __entry->sync_cir_ready, + __entry->sfd_fail, __entry->sfd_found, + __entry->phr_dec_error, __entry->phr_bit_error, + __entry->psdu_dec_error, __entry->psdu_bit_error, + __entry->sts_found, __entry->eof, __entry->rssi) +); + +TRACE_EVENT(region_pctt_report_rx, + TP_PROTO(enum pctt_status_ranging status_ranging, + const struct pctt_test_rx_results *rx), + TP_ARGS(status_ranging, rx), + TP_STRUCT__entry( + PCTT_STATUS_RANGING_ENTRY + __field(u32, rx_done_ts_int) + __field(u16, rx_done_ts_frac) + __field(s16, aoa_azimuth) + __field(s16, aoa_elevation) + __field(u8, toa_gap) + __field(u16, phr) + __field(u8, rssi) + __field(u16, psdu_data_len) + __dynamic_array(u8, psdu_data, rx->psdu_data_len) + ), + TP_fast_assign( + PCTT_STATUS_RANGING_ASSIGN; + __entry->rx_done_ts_int = rx->rx_done_ts_int; + __entry->rx_done_ts_frac = rx->rx_done_ts_frac; + __entry->aoa_azimuth = rx->aoa_azimuth; + __entry->aoa_elevation = rx->aoa_elevation; + __entry->toa_gap = rx->toa_gap; + __entry->phr = rx->phr; + __entry->rssi = rx->rssi; + __entry->psdu_data_len = rx->psdu_data_len; + memcpy(__get_dynamic_array(psdu_data), rx->psdu_data, + __get_dynamic_array_len(psdu_data)); + + ), + TP_printk(PCTT_STATUS_RANGING_PR_FMT " " + "rx_done_ts_int=%u rx_done_ts_frac=%u " + "aoa_azimuth=%d aoa_elevation=%d toa_gap=%u " + "phr=%u rssi=%u psdu_data_len=%u psdu_data=%s", + PCTT_STATUS_RANGING_PR_ARG, __entry->rx_done_ts_int, + __entry->rx_done_ts_frac, __entry->aoa_azimuth, + __entry->aoa_elevation, __entry->toa_gap, __entry->phr, + __entry->rssi, __entry->psdu_data_len, + __print_hex(__get_dynamic_array(psdu_data), + __get_dynamic_array_len(psdu_data))) +); + +TRACE_EVENT(region_pctt_report_loopback, + TP_PROTO(enum pctt_status_ranging status_ranging), + TP_ARGS(status_ranging), + TP_STRUCT__entry( + PCTT_STATUS_RANGING_ENTRY + ), + TP_fast_assign( + PCTT_STATUS_RANGING_ASSIGN; + ), + TP_printk(PCTT_STATUS_RANGING_PR_FMT, + PCTT_STATUS_RANGING_PR_ARG) +); + +TRACE_EVENT(region_pctt_report_ss_twr, + TP_PROTO(enum pctt_status_ranging status_ranging, + const struct pctt_test_ss_twr_results *ss_twr), + TP_ARGS(status_ranging, ss_twr), + TP_STRUCT__entry( + PCTT_STATUS_RANGING_ENTRY + __field(u32, measurement_rctu) + __field(s16, pdoa_azimuth_deg_q7) + __field(s16, pdoa_elevation_deg_q7) + __field(u8, rssi) + __field(s16, aoa_azimuth_deg_q7) + __field(s16, aoa_elevation_deg_q7) + ), + TP_fast_assign( + PCTT_STATUS_RANGING_ASSIGN; + __entry->measurement_rctu = ss_twr->measurement_rctu; + __entry->pdoa_azimuth_deg_q7 = ss_twr->pdoa_azimuth_deg_q7; + __entry->pdoa_elevation_deg_q7 = ss_twr->pdoa_elevation_deg_q7; + __entry->rssi = ss_twr->rssi; + __entry->aoa_azimuth_deg_q7 = ss_twr->aoa_azimuth_deg_q7; + __entry->aoa_elevation_deg_q7 = ss_twr->aoa_elevation_deg_q7; + ), + TP_printk(PCTT_STATUS_RANGING_PR_FMT " measurement_rctu=%u " + "pdoa_azimuth_deg_q7=%u pdoa_elevation_deg_q7=%u " + "rssi=%u aoa_azimuth_deg_q7=%u aoa_elevation_deg_q7=%u", + PCTT_STATUS_RANGING_PR_ARG, __entry->measurement_rctu, + __entry->pdoa_azimuth_deg_q7, __entry->pdoa_elevation_deg_q7, + __entry->rssi, __entry->aoa_azimuth_deg_q7, __entry->aoa_elevation_deg_q7) +); + +TRACE_EVENT(region_pctt_report_nla_put_failure, + TP_PROTO(enum pctt_id_attrs cmd_id), + TP_ARGS(cmd_id), + TP_STRUCT__entry( + PCTT_ID_ENTRY + ), + TP_fast_assign( + PCTT_ID_ASSIGN; + ), + TP_printk(PCTT_ID_PR_FMT, + PCTT_ID_PR_ARG) +); +/* clang-format on */ + +#endif /* !PCTT_TRACE_H || TRACE_HEADER_MULTI_READ */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE pctt_trace +#include <trace/define_trace.h> diff --git a/mac/regions.c b/mac/regions.c new file mode 100644 index 0000000..26ef8d8 --- /dev/null +++ b/mac/regions.c @@ -0,0 +1,229 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/string.h> +#include <linux/netdevice.h> + +#include "mcps802154_i.h" +#include "trace.h" + +static LIST_HEAD(registered_regions); +static DEFINE_MUTEX(registered_regions_lock); + +int mcps802154_region_register(struct mcps802154_region_ops *region_ops) +{ + struct mcps802154_region_ops *ops; + int r = 0; + + if (WARN_ON(!region_ops || !region_ops->owner || !region_ops->name || + !region_ops->open || !region_ops->close || + !region_ops->get_access)) + return -EINVAL; + + mutex_lock(®istered_regions_lock); + + list_for_each_entry (ops, ®istered_regions, registered_entry) { + if (WARN_ON(strcmp(ops->name, region_ops->name) == 0)) { + r = -EBUSY; + goto unlock; + } + } + + list_add(®ion_ops->registered_entry, ®istered_regions); + +unlock: + mutex_unlock(®istered_regions_lock); + + return r; +} +EXPORT_SYMBOL_GPL(mcps802154_region_register); + +void mcps802154_region_unregister(struct mcps802154_region_ops *region_ops) +{ + mutex_lock(®istered_regions_lock); + list_del(®ion_ops->registered_entry); + mutex_unlock(®istered_regions_lock); +} +EXPORT_SYMBOL_GPL(mcps802154_region_unregister); + +struct mcps802154_region * +mcps802154_region_open(struct mcps802154_llhw *llhw, const char *name, + const struct nlattr *params_attr, + struct netlink_ext_ack *extack) +{ + struct mcps802154_region_ops *ops; + struct mcps802154_region *region; + bool found = false; + + mutex_lock(®istered_regions_lock); + + list_for_each_entry (ops, ®istered_regions, registered_entry) { + if (strcmp(ops->name, name) == 0) { + if (try_module_get(ops->owner)) + found = true; + break; + } + } + + mutex_unlock(®istered_regions_lock); + + if (!found) + return NULL; + + region = ops->open(llhw); + if (!region) { + module_put(ops->owner); + return NULL; + } + + region->ops = ops; + if (mcps802154_region_set_parameters(llhw, region, params_attr, + extack)) { + ops->close(region); + return NULL; + } + + return region; +} +EXPORT_SYMBOL_GPL(mcps802154_region_open); + +void mcps802154_region_close(struct mcps802154_llhw *llhw, + struct mcps802154_region *region) +{ + const struct mcps802154_region_ops *ops; + + ops = region->ops; + ops->close(region); + module_put(ops->owner); +} +EXPORT_SYMBOL_GPL(mcps802154_region_close); + +void mcps802154_region_notify_stop(struct mcps802154_llhw *llhw, + struct mcps802154_region *region) +{ + if (!region->ops->notify_stop) + return; + + trace_region_notify_stop(region); + region->ops->notify_stop(region); +} +EXPORT_SYMBOL_GPL(mcps802154_region_notify_stop); + +int mcps802154_region_set_parameters(struct mcps802154_llhw *llhw, + struct mcps802154_region *region, + const struct nlattr *params_attr, + struct netlink_ext_ack *extack) +{ + if (!params_attr) + return 0; + + if (!region->ops->set_parameters) + return -EOPNOTSUPP; + + return region->ops->set_parameters(region, params_attr, extack); +} +EXPORT_SYMBOL_GPL(mcps802154_region_set_parameters); + +int mcps802154_region_call(struct mcps802154_llhw *llhw, + struct mcps802154_region *region, u32 call_id, + const struct nlattr *params_attr, + const struct genl_info *info) +{ + if (!region->ops->call) + return -EOPNOTSUPP; + + return region->ops->call(region, call_id, params_attr, info); +} +EXPORT_SYMBOL_GPL(mcps802154_region_call); + +int mcps802154_region_get_demand(struct mcps802154_llhw *llhw, + struct mcps802154_region *region, + u32 next_timestamp_dtu, + struct mcps802154_region_demand *demand) +{ + int r; + + if (!region->ops->get_demand) + return -EOPNOTSUPP; + + trace_region_get_demand(region, next_timestamp_dtu); + r = region->ops->get_demand(region, next_timestamp_dtu, demand); + trace_region_get_demand_return(region, demand, r); + + return r; +} +EXPORT_SYMBOL_GPL(mcps802154_region_get_demand); + +void mcps802154_region_xmit_resume(struct mcps802154_llhw *llhw, + struct mcps802154_region *region, + int queue_index) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + ieee802154_wake_queue(local->hw); +} +EXPORT_SYMBOL_GPL(mcps802154_region_xmit_resume); + +void mcps802154_region_xmit_done(struct mcps802154_llhw *llhw, + struct mcps802154_region *region, + struct sk_buff *skb, bool ok) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + if (ok) { + ieee802154_xmit_complete(local->hw, skb, false); + } else { + ieee802154_wake_queue(local->hw); + dev_kfree_skb_any(skb); + } +} +EXPORT_SYMBOL_GPL(mcps802154_region_xmit_done); + +void mcps802154_region_rx_skb(struct mcps802154_llhw *llhw, + struct mcps802154_region *region, + struct sk_buff *skb, u8 lqi) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + ieee802154_rx_irqsafe(local->hw, skb, lqi); +} +EXPORT_SYMBOL_GPL(mcps802154_region_rx_skb); + +int mcps802154_region_deferred(struct mcps802154_llhw *llhw, + struct mcps802154_region *region) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + if (local->fproc.deferred && local->fproc.deferred != region) + return -EINVAL; + + local->fproc.deferred = region; + + return 0; +} +EXPORT_SYMBOL_GPL(mcps802154_region_deferred); diff --git a/mac/schedule.c b/mac/schedule.c new file mode 100644 index 0000000..1ca0bfa --- /dev/null +++ b/mac/schedule.c @@ -0,0 +1,252 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/errno.h> + +#include "mcps802154_i.h" +#include "trace.h" + +void mcps802154_schedule_clear(struct mcps802154_local *local) +{ + if (local->ca.schedule.regions) { + local->ca.schedule.n_regions = 0; + kfree(local->ca.schedule.regions); + local->ca.schedule.regions = NULL; + } +} + +int mcps802154_schedule_update(struct mcps802154_local *local, + u32 next_timestamp_dtu) +{ + struct mcps802154_schedule_update_local sulocal; + struct mcps802154_schedule_update *su = &sulocal.schedule_update; + struct mcps802154_schedule *sched = &local->ca.schedule; + struct mcps802154_scheduler *scheduler; + u32 expected_start_timestamp_dtu; + int r; + + trace_schedule_update(local, next_timestamp_dtu); + + /* If no schedule at all, set sane values. */ + if (sched->n_regions == 0) { + sched->start_timestamp_dtu = next_timestamp_dtu; + sched->duration_dtu = 0; + expected_start_timestamp_dtu = next_timestamp_dtu; + } else if (sched->duration_dtu == 0) { + expected_start_timestamp_dtu = next_timestamp_dtu; + } else { + expected_start_timestamp_dtu = + sched->start_timestamp_dtu + sched->duration_dtu; + } + sched->current_index = 0; + + /* Call scheduler. */ + su->expected_start_timestamp_dtu = expected_start_timestamp_dtu; + su->start_timestamp_dtu = sched->start_timestamp_dtu; + su->duration_dtu = sched->duration_dtu; + su->n_regions = sched->n_regions; + sulocal.local = local; + scheduler = local->ca.scheduler; + r = scheduler->ops->update_schedule(scheduler, su, next_timestamp_dtu); + if (r) { + if (r != -ENOENT) + trace_update_schedule_error(local, su, + next_timestamp_dtu); + mcps802154_schedule_clear(local); + return r; + } + + /* Check we have a valid schedule. */ + if (sched->n_regions == 0 || + is_before_dtu(sched->start_timestamp_dtu, + expected_start_timestamp_dtu)) { + mcps802154_schedule_clear(local); + return -EOPNOTSUPP; + } + + trace_schedule_update_done(local, sched); + + return 1; +} + +int mcps802154_schedule_set_start( + const struct mcps802154_schedule_update *schedule_update, + u32 start_timestamp_dtu) +{ + struct mcps802154_schedule_update_local *sulocal = + schedule_update_to_local(schedule_update); + struct mcps802154_schedule_update *su = &sulocal->schedule_update; + struct mcps802154_schedule *sched = &sulocal->local->ca.schedule; + + if (is_before_dtu(start_timestamp_dtu, + schedule_update->expected_start_timestamp_dtu)) + return -EINVAL; + + su->start_timestamp_dtu = sched->start_timestamp_dtu = + start_timestamp_dtu; + + return 0; +} +EXPORT_SYMBOL(mcps802154_schedule_set_start); + +int mcps802154_schedule_recycle( + const struct mcps802154_schedule_update *schedule_update, + size_t n_keeps, int last_region_duration_dtu) +{ + struct mcps802154_schedule_update_local *sulocal = + schedule_update_to_local(schedule_update); + struct mcps802154_schedule_update *su = &sulocal->schedule_update; + struct mcps802154_schedule *sched = &sulocal->local->ca.schedule; + struct mcps802154_schedule_region *last_sched_region; + + if (n_keeps > sched->n_regions) + return -EINVAL; + + if (n_keeps == 0 && + last_region_duration_dtu != MCPS802154_DURATION_NO_CHANGE) + return -EINVAL; + + /* Change the number of currently used regions. */ + su->n_regions = sched->n_regions = n_keeps; + + /* Update last region. */ + last_sched_region = + sched->n_regions ? &sched->regions[sched->n_regions - 1] : NULL; + if (last_region_duration_dtu != MCPS802154_DURATION_NO_CHANGE) + last_sched_region->duration_dtu = last_region_duration_dtu; + + /* Update schedule duration. */ + if (!last_sched_region || last_sched_region->duration_dtu == 0) { + su->duration_dtu = sched->duration_dtu = 0; + } else { + su->duration_dtu = sched->duration_dtu = + last_sched_region->start_dtu + + last_sched_region->duration_dtu; + } + + return 0; +} +EXPORT_SYMBOL(mcps802154_schedule_recycle); + +int mcps802154_schedule_add_region( + const struct mcps802154_schedule_update *schedule_update, + struct mcps802154_region *region, int start_dtu, int duration_dtu, + bool once) +{ + struct mcps802154_schedule_update_local *sulocal = + schedule_update_to_local(schedule_update); + struct mcps802154_schedule_update *su = &sulocal->schedule_update; + struct mcps802154_schedule *sched = &sulocal->local->ca.schedule; + struct mcps802154_schedule_region *last_sched_region = + sched->n_regions ? &sched->regions[sched->n_regions - 1] : NULL; + struct mcps802154_schedule_region *sched_region, *new_sched_regions; + + if (start_dtu < 0 || duration_dtu < 0) + return -EINVAL; + + /* Can not add a region after an endless region. */ + if (last_sched_region && last_sched_region->duration_dtu == 0) + return -EINVAL; + + /* Regions can not overlap. */ + if (last_sched_region && + start_dtu < last_sched_region->start_dtu + + last_sched_region->duration_dtu) + return -EINVAL; + + if (!region || !region->ops || !region->ops->get_access) + return -EINVAL; + + /* Add to schedule. */ + new_sched_regions = krealloc( + sched->regions, + sizeof(sched->regions[0]) * (sched->n_regions + 1), GFP_KERNEL); + if (!new_sched_regions) + return -ENOMEM; + + /* Fill new added schedule region. */ + sched_region = &new_sched_regions[sched->n_regions]; + sched_region->start_dtu = start_dtu; + sched_region->duration_dtu = duration_dtu; + sched_region->region = region; + sched_region->once = once; + + sched->regions = new_sched_regions; + su->n_regions = sched->n_regions = sched->n_regions + 1; + + /* Update schedule duration. */ + if (duration_dtu == 0) { + su->duration_dtu = sched->duration_dtu = 0; + } else { + su->duration_dtu = sched->duration_dtu = + start_dtu + duration_dtu; + } + + return 0; +} +EXPORT_SYMBOL(mcps802154_schedule_add_region); + +void mcps802154_reschedule(struct mcps802154_llhw *llhw) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + mcps802154_ca_may_reschedule(local); +} +EXPORT_SYMBOL(mcps802154_reschedule); + +void mcps802154_schedule_invalidate(struct mcps802154_llhw *llhw) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + if (likely(local->started)) + mcps802154_ca_invalidate_schedule(local); +} +EXPORT_SYMBOL(mcps802154_schedule_invalidate); + +int mcps802154_schedule_get_regions(struct mcps802154_llhw *llhw, + struct list_head **regions) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + + *regions = &local->ca.regions; + + return local->ca.n_regions; +} +EXPORT_SYMBOL(mcps802154_schedule_get_regions); + +int mcps802154_schedule_get_next_demands( + struct mcps802154_llhw *llhw, const struct mcps802154_region *region, + u32 timestamp_dtu, int duration_dtu, int delta_dtu, + struct mcps802154_region_demand *demands) +{ + struct mcps802154_local *local = llhw_to_local(llhw); + struct mcps802154_scheduler *scheduler = local->ca.scheduler; + + if (!scheduler->ops->get_next_demands) + return -EOPNOTSUPP; + return scheduler->ops->get_next_demands(scheduler, region, + timestamp_dtu, duration_dtu, + delta_dtu, demands); +} +EXPORT_SYMBOL(mcps802154_schedule_get_next_demands); diff --git a/mac/schedule.h b/mac/schedule.h new file mode 100644 index 0000000..f17feae --- /dev/null +++ b/mac/schedule.h @@ -0,0 +1,122 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef MCPS802154_SCHEDULE_H +#define MCPS802154_SCHEDULE_H + +#include <linux/kernel.h> +#include <net/mcps802154_schedule.h> + +struct mcps802154_local; + +/** + * struct mcps802154_schedule_region - Region as defined in the schedule. + */ +struct mcps802154_schedule_region { + /** + * @region: Pointer to the open region. + */ + struct mcps802154_region *region; + /** + * @start_dtu: Region start from the start of the schedule. + */ + int start_dtu; + /** + * @duration_dtu: Region duration or 0 for endless region. + */ + int duration_dtu; + /** + * @once: Schedule the region once, ignoring the remaining region duration. + */ + bool once; +}; + +/** + * struct mcps802154_schedule - Schedule. + */ +struct mcps802154_schedule { + /** + * @start_timestamp_dtu: Date of the schedule start, might be too far in + * the past for endless schedule. + */ + u32 start_timestamp_dtu; + /** + * @duration_dtu: Schedule duration or 0 for endless schedule. + */ + int duration_dtu; + /** + * @regions: Table of regions. + */ + struct mcps802154_schedule_region *regions; + /** + * @n_regions: Number of regions in the schedule. + */ + size_t n_regions; + /** + * @current_index: Index of the current region. + */ + int current_index; +}; + +/** + * struct mcps802154_schedule_update_local - Private part of a schedule + * update context. + */ +struct mcps802154_schedule_update_local { + /** + * @schedule_update: Public part. + */ + struct mcps802154_schedule_update schedule_update; + /** + * @local: MCPS private data. + */ + struct mcps802154_local *local; +}; + +static inline struct mcps802154_schedule_update_local *schedule_update_to_local( + const struct mcps802154_schedule_update *schedule_update) +{ + return container_of(schedule_update, + struct mcps802154_schedule_update_local, + schedule_update); +} + +/** + * mcps802154_schedule_clear() - Clear schedule and release regions. + * @local: MCPS private data. + */ +void mcps802154_schedule_clear(struct mcps802154_local *local); + +/** + * mcps802154_schedule_update() - Initialize or update the schedule. + * @local: MCPS private data. + * @next_timestamp_dtu: Date of next access opportunity. + * + * Request the scheduler to update the schedule. + * + * Return: 1 or error. + */ +int mcps802154_schedule_update(struct mcps802154_local *local, + u32 next_timestamp_dtu); + +#endif /* NET_MCPS802154_SCHEDULE_H */ diff --git a/mac/schedulers.c b/mac/schedulers.c new file mode 100644 index 0000000..f938c58 --- /dev/null +++ b/mac/schedulers.c @@ -0,0 +1,155 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/string.h> + +#include "mcps802154_i.h" +#include "schedulers.h" + +static LIST_HEAD(registered_schedulers); +static DEFINE_MUTEX(registered_schedulers_lock); + +int mcps802154_scheduler_register(struct mcps802154_scheduler_ops *scheduler_ops) +{ + struct mcps802154_scheduler_ops *ops; + int r = 0; + + if (WARN_ON(!scheduler_ops || !scheduler_ops->owner || + !scheduler_ops->name || !scheduler_ops->open || + !scheduler_ops->close || !scheduler_ops->update_schedule)) + return -EINVAL; + + mutex_lock(®istered_schedulers_lock); + + list_for_each_entry (ops, ®istered_schedulers, registered_entry) { + if (WARN_ON(strcmp(ops->name, scheduler_ops->name) == 0)) { + r = -EBUSY; + goto unlock; + } + } + + list_add(&scheduler_ops->registered_entry, ®istered_schedulers); + +unlock: + mutex_unlock(®istered_schedulers_lock); + + return r; +} +EXPORT_SYMBOL(mcps802154_scheduler_register); + +void mcps802154_scheduler_unregister( + struct mcps802154_scheduler_ops *scheduler_ops) +{ + mutex_lock(®istered_schedulers_lock); + list_del(&scheduler_ops->registered_entry); + mutex_unlock(®istered_schedulers_lock); +} +EXPORT_SYMBOL(mcps802154_scheduler_unregister); + +struct mcps802154_scheduler * +mcps802154_scheduler_open(struct mcps802154_local *local, const char *name, + const struct nlattr *params_attr, + struct netlink_ext_ack *extack) +{ + struct mcps802154_scheduler_ops *ops; + struct mcps802154_scheduler *scheduler; + bool found = false; + + mutex_lock(®istered_schedulers_lock); + + list_for_each_entry (ops, ®istered_schedulers, registered_entry) { + if (strcmp(ops->name, name) == 0) { + if (try_module_get(ops->owner)) + found = true; + break; + } + } + + mutex_unlock(®istered_schedulers_lock); + + if (!found) + return NULL; + + scheduler = ops->open(&local->llhw); + if (!scheduler) { + module_put(ops->owner); + return NULL; + } + + scheduler->ops = ops; + if (mcps802154_scheduler_set_parameters(scheduler, params_attr, + extack)) { + ops->close(scheduler); + return NULL; + } + + return scheduler; +} + +void mcps802154_scheduler_close(struct mcps802154_scheduler *scheduler) +{ + const struct mcps802154_scheduler_ops *ops; + + ops = scheduler->ops; + ops->close(scheduler); + module_put(ops->owner); +} + +void mcps802154_scheduler_notify_stop(struct mcps802154_scheduler *scheduler) +{ + const struct mcps802154_scheduler_ops *ops; + + if (!scheduler) + return; + + ops = scheduler->ops; + if (ops->notify_stop) + ops->notify_stop(scheduler); +} + +int mcps802154_scheduler_set_parameters(struct mcps802154_scheduler *scheduler, + const struct nlattr *params_attr, + struct netlink_ext_ack *extack) +{ + if (!params_attr) + return 0; + + if (!scheduler->ops->set_parameters) + return -EOPNOTSUPP; + + return scheduler->ops->set_parameters(scheduler, params_attr, extack); +} + +int mcps802154_scheduler_call(struct mcps802154_scheduler *scheduler, + u32 call_id, const struct nlattr *params_attr, + const struct genl_info *info) +{ + if (!scheduler->ops->call) + return -EOPNOTSUPP; + + return scheduler->ops->call(scheduler, call_id, params_attr, info); +} diff --git a/mac/schedulers.h b/mac/schedulers.h new file mode 100644 index 0000000..af34d07 --- /dev/null +++ b/mac/schedulers.h @@ -0,0 +1,84 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef NET_MCPS802154_SCHEDULERS_H +#define NET_MCPS802154_SCHEDULERS_H + +#include <net/netlink.h> + +struct mcps802154_local; +struct mcps802154_scheduler; + +/** + * mcps802154_scheduler_open() - Open a scheduler, and set parameters. + * @local: MCPS private data. + * @name: Name of scheduler to open. + * @params_attr: Nested attribute containing scheduler parameters, may be NULL. + * @extack: Extended ACK report structure. + * + * Return: The open scheduler or NULL on error. + */ +struct mcps802154_scheduler * +mcps802154_scheduler_open(struct mcps802154_local *local, const char *name, + const struct nlattr *params_attr, + struct netlink_ext_ack *extack); + +/** + * mcps802154_scheduler_close() - Close a scheduler. + * @scheduler: Pointer to the scheduler. + */ +void mcps802154_scheduler_close(struct mcps802154_scheduler *scheduler); + +/** + * mcps802154_scheduler_notify_stop() - Notify a scheduler that device has been + * stopped. + * @scheduler: Pointer to the scheduler. + */ +void mcps802154_scheduler_notify_stop(struct mcps802154_scheduler *scheduler); + +/** + * mcps802154_scheduler_set_parameters() - Set parameters of an open scheduler. + * @scheduler: Pointer to the scheduler. + * @params_attr: Nested attribute containing scheduler parameters, may be NULL. + * @extack: Extended ACK report structure. + * + * Return: 0 or error. + */ +int mcps802154_scheduler_set_parameters(struct mcps802154_scheduler *scheduler, + const struct nlattr *params_attr, + struct netlink_ext_ack *extack); + +/** + * mcps802154_scheduler_call() - Call scheduler specific procedure. + * @scheduler: Pointer to the scheduler. + * @call_id: Identifier of the procedure, scheduler specific. + * @params_attr: Nested attribute containing procedure parameters. + * @info: Request information. + * + * Return: 0 or error. + */ +int mcps802154_scheduler_call(struct mcps802154_scheduler *scheduler, + u32 call_id, const struct nlattr *params_attr, + const struct genl_info *info); + +#endif /* NET_MCPS802154_SCHEDULERS_H */ diff --git a/mac/trace.h b/mac/trace.h new file mode 100644 index 0000000..3808554 --- /dev/null +++ b/mac/trace.h @@ -0,0 +1,1108 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM mcps802154 + +#if !defined(TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define TRACE_H + +#include <linux/tracepoint.h> +#include "mcps802154_i.h" +#include "idle_region.h" + +/* clang-format off */ + +#define LOCAL_ENTRY __field(int, hw_idx) +#define LOCAL_ASSIGN __entry->hw_idx = local->hw_idx +#define LOCAL_PR_FMT "hw%d" +#define LOCAL_PR_ARG __entry->hw_idx + +#define mcps802154_tx_frame_config_name(name) \ + { \ + MCPS802154_TX_FRAME_CONFIG_##name, #name \ + } +#define MCPS802154_TX_FRAME_CONFIG_NAMES \ + mcps802154_tx_frame_config_name(TIMESTAMP_DTU), \ + mcps802154_tx_frame_config_name(CCA), \ + mcps802154_tx_frame_config_name(RANGING), \ + mcps802154_tx_frame_config_name(KEEP_RANGING_CLOCK), \ + mcps802154_tx_frame_config_name(RANGING_PDOA), \ + mcps802154_tx_frame_config_name(SP1), \ + mcps802154_tx_frame_config_name(SP2), \ + mcps802154_tx_frame_config_name(SP3), \ + mcps802154_tx_frame_config_name(STS_MODE_MASK), \ + mcps802154_tx_frame_config_name(RANGING_ROUND) +TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_TIMESTAMP_DTU); +TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_CCA); +TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_RANGING); +TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_KEEP_RANGING_CLOCK); +TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_RANGING_PDOA); +TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_SP1); +TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_SP2); +TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_SP3); +TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_STS_MODE_MASK); +TRACE_DEFINE_ENUM(MCPS802154_TX_FRAME_CONFIG_RANGING_ROUND); + +#define mcps802154_rx_frame_config_name(name) \ + { \ + MCPS802154_RX_FRAME_CONFIG_##name, #name \ + } +#define MCPS802154_RX_FRAME_CONFIG_NAMES \ + mcps802154_rx_frame_config_name(TIMESTAMP_DTU), \ + mcps802154_rx_frame_config_name(AACK), \ + mcps802154_rx_frame_config_name(RANGING), \ + mcps802154_rx_frame_config_name(KEEP_RANGING_CLOCK), \ + mcps802154_rx_frame_config_name(RANGING_PDOA), \ + mcps802154_rx_frame_config_name(SP1), \ + mcps802154_rx_frame_config_name(SP2), \ + mcps802154_rx_frame_config_name(SP3), \ + mcps802154_rx_frame_config_name(STS_MODE_MASK), \ + mcps802154_rx_frame_config_name(RANGING_ROUND) +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_TIMESTAMP_DTU); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_AACK); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_RANGING); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_KEEP_RANGING_CLOCK); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_RANGING_PDOA); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_SP1); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_SP2); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_SP3); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_STS_MODE_MASK); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_CONFIG_RANGING_ROUND); + +#define mcps802154_rx_error_name(name) \ + { \ + MCPS802154_RX_ERROR_##name, #name \ + } +#define MCPS802154_RX_ERROR_NAMES \ + mcps802154_rx_error_name(NONE), \ + mcps802154_rx_error_name(TIMEOUT), \ + mcps802154_rx_error_name(BAD_CKSUM), \ + mcps802154_rx_error_name(UNCORRECTABLE), \ + mcps802154_rx_error_name(FILTERED), \ + mcps802154_rx_error_name(SFD_TIMEOUT), \ + mcps802154_rx_error_name(PHR_DECODE), \ + mcps802154_rx_error_name(OTHER) +TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_NONE); +TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_TIMEOUT); +TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_BAD_CKSUM); +TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_UNCORRECTABLE); +TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_FILTERED); +TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_SFD_TIMEOUT); +TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_PHR_DECODE); +TRACE_DEFINE_ENUM(MCPS802154_RX_ERROR_OTHER); + +#define ieee802154_afilt_name(name) \ + { \ + IEEE802154_AFILT_##name, #name \ + } +#define IEEE802154_AFILT_NAMES \ + ieee802154_afilt_name(SADDR_CHANGED), \ + ieee802154_afilt_name(IEEEADDR_CHANGED), \ + ieee802154_afilt_name(PANID_CHANGED), \ + ieee802154_afilt_name(PANC_CHANGED) +TRACE_DEFINE_ENUM(IEEE802154_AFILT_SADDR_CHANGED); +TRACE_DEFINE_ENUM(IEEE802154_AFILT_IEEEADDR_CHANGED); +TRACE_DEFINE_ENUM(IEEE802154_AFILT_PANID_CHANGED); +TRACE_DEFINE_ENUM(IEEE802154_AFILT_PANC_CHANGED); + +#define RX_FRAME_INFO_FLAGS_ENTRY __field(u16, flags) +#define RX_FRAME_INFO_FLAGS_ASSIGN __entry->flags = info->flags +#define RX_FRAME_INFO_FLAGS_PR_FMT "flags=%s" +#define rx_frame_info_name(name) \ + { \ + MCPS802154_RX_FRAME_INFO_##name, #name \ + } +#define RX_FRAME_INFO_FLAGS_NAMES \ + rx_frame_info_name(TIMESTAMP_DTU), \ + rx_frame_info_name(TIMESTAMP_RCTU), \ + rx_frame_info_name(LQI), \ + rx_frame_info_name(RSSI), \ + rx_frame_info_name(RANGING_FOM), \ + rx_frame_info_name(RANGING_OFFSET), \ + rx_frame_info_name(RANGING_PDOA), \ + rx_frame_info_name(RANGING_PDOA_FOM), \ + rx_frame_info_name(RANGING_STS_TIMESTAMP_RCTU), \ + rx_frame_info_name(RANGING_STS_FOM), \ + rx_frame_info_name(AACK) +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_TIMESTAMP_DTU); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_TIMESTAMP_RCTU); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_LQI); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_RSSI); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_RANGING_FOM); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_RANGING_OFFSET); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_RANGING_PDOA); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_RANGING_PDOA_FOM); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_RANGING_STS_TIMESTAMP_RCTU); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_RANGING_STS_FOM); +TRACE_DEFINE_ENUM(MCPS802154_RX_FRAME_INFO_AACK); +#define RX_FRAME_INFO_FLAGS_PR_ARG \ + __print_flags(__entry->flags, "|", RX_FRAME_INFO_FLAGS_NAMES) + +#define RX_FRAME_INFO_ENTRY \ + __field(u32, timestamp_dtu) \ + __field(u64, timestamp_rctu) \ + __field(int, frame_duration_dtu) \ + __field(u8, ranging_sts_fom0) \ + RX_FRAME_INFO_FLAGS_ENTRY +#define RX_FRAME_INFO_ASSIGN \ + __entry->timestamp_dtu = info->timestamp_dtu; \ + __entry->timestamp_rctu = info->timestamp_rctu; \ + __entry->frame_duration_dtu = info->frame_duration_dtu; \ + __entry->ranging_sts_fom0 = info->ranging_sts_fom[0]; \ + RX_FRAME_INFO_FLAGS_ASSIGN +#define RX_FRAME_INFO_PR_FMT \ + "timestamp_dtu=0x%08x timestamp_rctu=0x%llx frame_duration_dtu=%d " \ + "ranging_sts_fom[0]=0x%02x " \ + RX_FRAME_INFO_FLAGS_PR_FMT +#define RX_FRAME_INFO_PR_ARG \ + __entry->timestamp_dtu, \ + __entry->timestamp_rctu, \ + __entry->frame_duration_dtu, \ + __entry->ranging_sts_fom0, \ + RX_FRAME_INFO_FLAGS_PR_ARG + +#define KEY_ENTRY __string(key, key) +#define KEY_ASSIGN __assign_str(key, key) +#define KEY_PR_FMT "key=%s" +#define KEY_PR_ARG __get_str(key) + +DECLARE_EVENT_CLASS(local_only_evt, + TP_PROTO(const struct mcps802154_local *local), + TP_ARGS(local), + TP_STRUCT__entry( + LOCAL_ENTRY + ), + TP_fast_assign( + LOCAL_ASSIGN; + ), + TP_printk(LOCAL_PR_FMT, LOCAL_PR_ARG) + ); + +DEFINE_EVENT(local_only_evt, llhw_return_void, + TP_PROTO(const struct mcps802154_local *local), + TP_ARGS(local) + ); + +TRACE_EVENT(llhw_return_int, + TP_PROTO(const struct mcps802154_local *local, int ret), + TP_ARGS(local, ret), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(int, ret) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->ret = ret; + ), + TP_printk(LOCAL_PR_FMT " returned=%d", LOCAL_PR_ARG, __entry->ret) + ); + +TRACE_EVENT(llhw_return_rx_frame, + TP_PROTO(const struct mcps802154_local *local, int ret, + const struct mcps802154_rx_frame_info *info), + TP_ARGS(local, ret, info), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(int, ret) + RX_FRAME_INFO_ENTRY + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->ret = ret; + RX_FRAME_INFO_ASSIGN; + ), + TP_printk(LOCAL_PR_FMT " returned=%d " RX_FRAME_INFO_PR_FMT, + LOCAL_PR_ARG, __entry->ret, RX_FRAME_INFO_PR_ARG) + ); + +TRACE_EVENT(llhw_return_timestamp_dtu, + TP_PROTO(const struct mcps802154_local *local, int ret, + u32 timestamp_dtu), + TP_ARGS(local, ret, timestamp_dtu), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(int, ret) + __field(u32, timestamp_dtu) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->ret = ret; + __entry->timestamp_dtu = timestamp_dtu; + ), + TP_printk(LOCAL_PR_FMT " returned=%d timestamp_dtu=0x%08x", + LOCAL_PR_ARG, __entry->ret, __entry->timestamp_dtu) + ); + +TRACE_EVENT(llhw_return_timestamp_rctu, + TP_PROTO(const struct mcps802154_local *local, int ret, + u64 timestamp_rctu), + TP_ARGS(local, ret, timestamp_rctu), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(int, ret) + __field(u64, timestamp_rctu) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->ret = ret; + __entry->timestamp_rctu = timestamp_rctu; + ), + TP_printk(LOCAL_PR_FMT " returned=%d timestamp_rctu=0x%llx", + LOCAL_PR_ARG, __entry->ret, __entry->timestamp_rctu) + ); + +DEFINE_EVENT(local_only_evt, llhw_start, + TP_PROTO(const struct mcps802154_local *local), + TP_ARGS(local) + ); + +DEFINE_EVENT(local_only_evt, llhw_stop, + TP_PROTO(const struct mcps802154_local *local), + TP_ARGS(local) + ); + +TRACE_EVENT(llhw_tx_frame, + TP_PROTO(const struct mcps802154_local *local, + const struct mcps802154_tx_frame_config *config, + int frame_idx, int next_delay_dtu), + TP_ARGS(local, config, frame_idx, next_delay_dtu), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(u32, timestamp_dtu) + __field(int, rx_enable_after_tx_dtu) + __field(int, rx_enable_after_tx_timeout_dtu) + __field(int, ant_set_id) + __field(u8, flags) + __field(int, frame_idx) + __field(int, next_delay_dtu) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->timestamp_dtu = config->timestamp_dtu; + __entry->rx_enable_after_tx_dtu = config->rx_enable_after_tx_dtu; + __entry->rx_enable_after_tx_timeout_dtu = config->rx_enable_after_tx_timeout_dtu; + __entry->ant_set_id = config->ant_set_id; + __entry->flags = config->flags; + __entry->frame_idx = frame_idx; + __entry->next_delay_dtu = next_delay_dtu; + ), + TP_printk(LOCAL_PR_FMT " timestamp_dtu=0x%08x" + " rx_enable_after_tx_dtu=%d rx_enable_after_tx_timeout_dtu=%d" + " ant_set_id=%d flags=%s frame_idx=%d next_delay_dtu=%d", + LOCAL_PR_ARG, + __entry->timestamp_dtu, __entry->rx_enable_after_tx_dtu, + __entry->rx_enable_after_tx_timeout_dtu, __entry->ant_set_id, + __print_flags(__entry->flags, "|", MCPS802154_TX_FRAME_CONFIG_NAMES), + __entry->frame_idx, + __entry->next_delay_dtu + ) + ); + +TRACE_EVENT(llhw_rx_enable, + TP_PROTO(const struct mcps802154_local *local, + const struct mcps802154_rx_frame_config *config, + int frame_idx, int next_delay_dtu), + TP_ARGS(local, config, frame_idx, next_delay_dtu), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(u32, timestamp_dtu) + __field(int, timeout_dtu) + __field(int, frame_timeout_dtu) + __field(u8, flags) + __field(int, ant_set_id) + __field(int, frame_idx) + __field(int, next_delay_dtu) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->timestamp_dtu = config->timestamp_dtu; + __entry->timeout_dtu = config->timeout_dtu; + __entry->frame_timeout_dtu = config->frame_timeout_dtu; + __entry->flags = config->flags; + __entry->ant_set_id = config->ant_set_id; + __entry->frame_idx = frame_idx; + __entry->next_delay_dtu = next_delay_dtu; + ), + TP_printk(LOCAL_PR_FMT " timestamp_dtu=0x%08x timeout_dtu=%d" + " frame_timeout_dtu=%d ant_set_id=%d flags=%s frame_idx=%d" + " next_delay_dtu=%d", + LOCAL_PR_ARG, + __entry->timestamp_dtu, __entry->timeout_dtu, + __entry->frame_timeout_dtu, __entry->ant_set_id, + __print_flags(__entry->flags, "|", MCPS802154_RX_FRAME_CONFIG_NAMES), + __entry->frame_idx, + __entry->next_delay_dtu + ) + ); + +DEFINE_EVENT(local_only_evt, llhw_rx_disable, + TP_PROTO(const struct mcps802154_local *local), + TP_ARGS(local) + ); + +DECLARE_EVENT_CLASS(rx_frame_info_evt, + TP_PROTO(const struct mcps802154_local *local, + const struct mcps802154_rx_frame_info *info), + TP_ARGS(local, info), + TP_STRUCT__entry( + LOCAL_ENTRY + RX_FRAME_INFO_FLAGS_ENTRY + ), + TP_fast_assign( + LOCAL_ASSIGN; + RX_FRAME_INFO_FLAGS_ASSIGN; + ), + TP_printk(LOCAL_PR_FMT " " RX_FRAME_INFO_FLAGS_PR_FMT, LOCAL_PR_ARG, + RX_FRAME_INFO_FLAGS_PR_ARG) + ); + +DEFINE_EVENT(rx_frame_info_evt, llhw_rx_get_frame, + TP_PROTO(const struct mcps802154_local *local, + const struct mcps802154_rx_frame_info *info), + TP_ARGS(local, info) + ); + +DEFINE_EVENT(rx_frame_info_evt, llhw_rx_get_error_frame, + TP_PROTO(const struct mcps802154_local *local, + const struct mcps802154_rx_frame_info *info), + TP_ARGS(local, info) + ); + +DEFINE_EVENT(local_only_evt, llhw_idle, + TP_PROTO(const struct mcps802154_local *local), + TP_ARGS(local) + ); + +TRACE_EVENT(llhw_idle_timestamp, + TP_PROTO(const struct mcps802154_local *local, + u32 timestamp_dtu), + TP_ARGS(local, timestamp_dtu), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(u32, timestamp_dtu) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->timestamp_dtu = timestamp_dtu; + ), + TP_printk(LOCAL_PR_FMT " timestamp_dtu=0x%08x", + LOCAL_PR_ARG, + __entry->timestamp_dtu + ) + ); + +DEFINE_EVENT(local_only_evt, llhw_reset, + TP_PROTO(const struct mcps802154_local *local), + TP_ARGS(local) + ); + +DEFINE_EVENT(local_only_evt, llhw_get_current_timestamp_dtu, + TP_PROTO(const struct mcps802154_local *local), + TP_ARGS(local) + ); + +TRACE_EVENT(llhw_set_channel, + TP_PROTO(const struct mcps802154_local *local, u8 page, u8 channel, + u8 preamble_code), + TP_ARGS(local, page, channel, preamble_code), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(u8, page) + __field(u8, channel) + __field(u8, preamble_code) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->page = page; + __entry->channel = channel; + __entry->preamble_code = preamble_code; + ), + TP_printk(LOCAL_PR_FMT " page=%d channel=%d preamble_code=%d", + LOCAL_PR_ARG, __entry->page, __entry->channel, + __entry->preamble_code) + ); + +TRACE_EVENT(llhw_set_hrp_uwb_params, + TP_PROTO(const struct mcps802154_local *local, + const struct mcps802154_hrp_uwb_params *params), + TP_ARGS(local, params), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(int, prf) + __field(int, psr) + __field(int, sfd_selector) + __field(int, phr_hi_rate) + __field(int, data_rate) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->prf = params->prf; + __entry->psr = params->psr; + __entry->sfd_selector = params->sfd_selector; + __entry->phr_hi_rate = params->phr_hi_rate; + __entry->data_rate = params->data_rate; + ), + TP_printk(LOCAL_PR_FMT " prf=%d psr=%d sfd_selector=%d phr_hi_rate=%d data_rate=%d", + LOCAL_PR_ARG, __entry->prf, __entry->psr, + __entry->sfd_selector, __entry->phr_hi_rate, __entry->data_rate) + ); + +TRACE_EVENT(llhw_check_hrp_uwb_params, + TP_PROTO(const struct mcps802154_local *local, + const struct mcps802154_hrp_uwb_params *params), + TP_ARGS(local, params), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(int, prf) + __field(int, psr) + __field(int, sfd_selector) + __field(int, phr_hi_rate) + __field(int, data_rate) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->prf = params->prf; + __entry->psr = params->psr; + __entry->sfd_selector = params->sfd_selector; + __entry->phr_hi_rate = params->phr_hi_rate; + __entry->data_rate = params->data_rate; + ), + TP_printk(LOCAL_PR_FMT " prf=%d psr=%d sfd_selector=%d phr_hi_rate=%d data_rate=%d", + LOCAL_PR_ARG, __entry->prf, __entry->psr, + __entry->sfd_selector, __entry->phr_hi_rate, __entry->data_rate) + ); + +TRACE_EVENT(llhw_set_sts_params, + TP_PROTO(const struct mcps802154_local *local, const struct + mcps802154_sts_params *params), + TP_ARGS(local, params), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(int, n_segs) + __field(int, seg_len) + __field(int, sp2_tx_gap_4chips) + __array(int, sp2_rx_gap_4chips, MCPS802154_STS_N_SEGS_MAX) + __array(u8, key, AES_BLOCK_SIZE) + __array(u8, v, AES_BLOCK_SIZE) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->n_segs = params->n_segs; + __entry->seg_len = params->seg_len; + __entry->sp2_tx_gap_4chips = params->sp2_tx_gap_4chips; + memcpy(__entry->sp2_rx_gap_4chips, params->sp2_rx_gap_4chips, + sizeof(params->sp2_rx_gap_4chips)); + memcpy(__entry->key, params->key, sizeof(params->key)); + memcpy(__entry->v, params->v, sizeof(params->v)); + ), + TP_printk(LOCAL_PR_FMT " n_segs=%d seg_len=%d sp2_tx_gap_4chips=%d" + " sp2_rx_gap_4chips=%d,%d,%d,%d" + " sts_key=%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x" + " sts_v=%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", + LOCAL_PR_ARG, __entry->n_segs, __entry->seg_len, + __entry->sp2_tx_gap_4chips, __entry->sp2_rx_gap_4chips[0], + __entry->sp2_rx_gap_4chips[1], __entry->sp2_rx_gap_4chips[2], + __entry->sp2_rx_gap_4chips[3], __entry->key[0], __entry->key[1], + __entry->key[2], __entry->key[3], __entry->key[4], __entry->key[5], + __entry->key[6], __entry->key[7], __entry->key[8], __entry->key[9], + __entry->key[10], __entry->key[11], __entry->key[12], __entry->key[13], + __entry->key[14], __entry->key[15], __entry->v[0], __entry->v[1], + __entry->v[2], __entry->v[3], __entry->v[4], __entry->v[5], + __entry->v[6], __entry->v[7], __entry->v[8], __entry->v[9], + __entry->v[10], __entry->v[11], __entry->v[12], __entry->v[13], + __entry->v[14], __entry->v[15]) + ); + +TRACE_EVENT(llhw_rx_get_measurement, + TP_PROTO(const struct mcps802154_local *local, const void *rx_ctx), + TP_ARGS(local, rx_ctx), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(const void *, rx_ctx) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->rx_ctx = rx_ctx; + ), + TP_printk(LOCAL_PR_FMT " rx_ctx=%p", + LOCAL_PR_ARG, + __entry->rx_ctx) + ); + +TRACE_EVENT(llhw_return_measurement, + TP_PROTO(const struct mcps802154_local *local, int r, + const struct mcps802154_rx_measurement_info *info), + TP_ARGS(local, r, info), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(int, r) + __field(int, n_rssis) + __field(int, n_aoas) + __field(int, n_cirs) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->r = r; + __entry->n_rssis = info->n_rssis; + __entry->n_aoas = info->n_aoas; + __entry->n_cirs = info->n_cirs; + ), + TP_printk(LOCAL_PR_FMT " r=%d n_rssis=%d n_aoas=%d n_cirs=%d", + LOCAL_PR_ARG, + __entry->r, + __entry->n_rssis, + __entry->n_aoas, + __entry->n_cirs) + ); + +TRACE_EVENT(llhw_set_hw_addr_filt, + TP_PROTO(const struct mcps802154_local *local, + const struct ieee802154_hw_addr_filt *filt, + unsigned long changed), + TP_ARGS(local, filt, changed), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(__le16, pan_id) + __field(__le16, short_addr) + __field(__le64, extended_addr) + __field(bool, pan_coord) + __field(unsigned long, changed) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->pan_id = filt->pan_id; + __entry->short_addr = filt->short_addr; + __entry->extended_addr = filt->ieee_addr; + __entry->pan_coord = filt->pan_coord; + __entry->changed = changed; + ), + TP_printk(LOCAL_PR_FMT " pan_id=0x%04x short_addr=0x%04x" + " extended_addr=0x%016llx pan_coord=%s changed=%s", + LOCAL_PR_ARG, __entry->pan_id, __entry->short_addr, + __entry->extended_addr, + __entry->pan_coord ? "true" : "false", + __print_flags(__entry->changed, "|", IEEE802154_AFILT_NAMES)) + ); + +TRACE_EVENT(llhw_set_txpower, + TP_PROTO(const struct mcps802154_local *local, s32 mbm), + TP_ARGS(local, mbm), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(s32, mbm) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->mbm = mbm; + ), + TP_printk(LOCAL_PR_FMT " mbm=%d", LOCAL_PR_ARG, __entry->mbm) + ); + +TRACE_EVENT(llhw_set_cca_ed_level, + TP_PROTO(const struct mcps802154_local *local, s32 mbm), + TP_ARGS(local, mbm), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(s32, mbm) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->mbm = mbm; + ), + TP_printk(LOCAL_PR_FMT " mbm=%d", LOCAL_PR_ARG, __entry->mbm) + ); + +DECLARE_EVENT_CLASS(bool_on_evt, + TP_PROTO(const struct mcps802154_local *local, bool on), + TP_ARGS(local, on), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(bool, on) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->on = on; + ), + TP_printk(LOCAL_PR_FMT " %s", LOCAL_PR_ARG, __entry->on ? "on" : "off") + ); + +DEFINE_EVENT(bool_on_evt, llhw_set_promiscuous_mode, + TP_PROTO(const struct mcps802154_local *local, bool on), + TP_ARGS(local, on) + ); + +DEFINE_EVENT(bool_on_evt, llhw_set_scanning_mode, + TP_PROTO(const struct mcps802154_local *local, bool on), + TP_ARGS(local, on) + ); + +DEFINE_EVENT(local_only_evt, llhw_testmode_cmd, + TP_PROTO(const struct mcps802154_local *local), + TP_ARGS(local) + ); + +DEFINE_EVENT(local_only_evt, llhw_event_rx_frame, + TP_PROTO(const struct mcps802154_local *local), + TP_ARGS(local) + ); + +DEFINE_EVENT(local_only_evt, llhw_event_rx_timeout, + TP_PROTO(const struct mcps802154_local *local), + TP_ARGS(local) + ); + +DECLARE_EVENT_CLASS(str_key_evt, + TP_PROTO(const struct mcps802154_local *local, const char *const key), + TP_ARGS(local, key), + TP_STRUCT__entry( + LOCAL_ENTRY + KEY_ENTRY + ), + TP_fast_assign( + LOCAL_ASSIGN; + KEY_ASSIGN; + ), + TP_printk(LOCAL_PR_FMT " " KEY_PR_FMT, LOCAL_PR_ARG, KEY_PR_ARG) + ); + +DEFINE_EVENT(str_key_evt, llhw_set_calibration, + TP_PROTO(const struct mcps802154_local *local, const char *const key), + TP_ARGS(local, key) + ); + +DEFINE_EVENT(str_key_evt, llhw_get_calibration, + TP_PROTO(const struct mcps802154_local *local, const char *const key), + TP_ARGS(local, key) + ); + +DEFINE_EVENT(local_only_evt, llhw_list_calibration, + TP_PROTO(const struct mcps802154_local *local), + TP_ARGS(local) + ); + +TRACE_EVENT(llhw_vendor_cmd, + TP_PROTO(const struct mcps802154_local *local, u32 vendor_id, + u32 subcmd, size_t data_len), + TP_ARGS(local, vendor_id, subcmd, data_len), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(u32, vendor_id) + __field(u32, subcmd) + __field(u32, data_len) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->vendor_id = vendor_id; + __entry->subcmd = subcmd; + __entry->data_len = data_len; + ), + TP_printk(LOCAL_PR_FMT " vendor_id=0x%06x subcmd=0x%x data_len=%d", + LOCAL_PR_ARG, __entry->vendor_id, __entry->subcmd, + __entry->data_len) + ); + +TRACE_EVENT(llhw_event_rx_error, + TP_PROTO(const struct mcps802154_local *local, + enum mcps802154_rx_error_type error), + TP_ARGS(local, error), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(enum mcps802154_rx_error_type, error) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->error = error; + ), + TP_printk(LOCAL_PR_FMT " error=%s", LOCAL_PR_ARG, + __print_symbolic(__entry->error, MCPS802154_RX_ERROR_NAMES)) + ); + +DEFINE_EVENT(local_only_evt, llhw_event_tx_done, + TP_PROTO(const struct mcps802154_local *local), + TP_ARGS(local) + ); + +DEFINE_EVENT(local_only_evt, llhw_event_broken, + TP_PROTO(const struct mcps802154_local *local), + TP_ARGS(local) + ); + +DEFINE_EVENT(local_only_evt, llhw_event_timer_expired, + TP_PROTO(const struct mcps802154_local *local), + TP_ARGS(local) + ); + +DEFINE_EVENT(local_only_evt, llhw_event_done, + TP_PROTO(const struct mcps802154_local *local), + TP_ARGS(local) + ); + +TRACE_EVENT(ca_set_scheduler, + TP_PROTO(const struct mcps802154_local *local, const char *name), + TP_ARGS(local, name), + TP_STRUCT__entry( + LOCAL_ENTRY + __string(name, name) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __assign_str(name, name); + ), + TP_printk(LOCAL_PR_FMT " name=%s", LOCAL_PR_ARG, __get_str(name)) + ); + +TRACE_EVENT(ca_set_scheduler_parameters, + TP_PROTO(const struct mcps802154_local *local, const char *name), + TP_ARGS(local, name), + TP_STRUCT__entry( + LOCAL_ENTRY + __string(name, name) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __assign_str(name, name); + ), + TP_printk(LOCAL_PR_FMT " name=%s", LOCAL_PR_ARG, __get_str(name)) + ); + +TRACE_EVENT(ca_set_region, + TP_PROTO(const struct mcps802154_local *local, + const char *scheduler_name, u32 region_id, + const char *region_name), + TP_ARGS(local, scheduler_name, region_id, region_name), + TP_STRUCT__entry( + LOCAL_ENTRY + __string(scheduler_name, scheduler_name) + __field(u32, region_id) + __string(region_name, region_name) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __assign_str(scheduler_name, scheduler_name); + __entry->region_id = region_id; + __assign_str(region_name, region_name); + ), + TP_printk(LOCAL_PR_FMT " scheduler=%s region_id=%u region_name=%s", + LOCAL_PR_ARG, __get_str(scheduler_name), __entry->region_id, + __get_str(region_name)) + ); + +TRACE_EVENT(ca_scheduler_call, + TP_PROTO(const struct mcps802154_local *local, + const char *scheduler_name, u32 call_id), + TP_ARGS(local, scheduler_name, call_id), + TP_STRUCT__entry( + LOCAL_ENTRY + __string(scheduler_name, scheduler_name) + __field(u32, call_id) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __assign_str(scheduler_name, scheduler_name); + __entry->call_id = call_id; + ), + TP_printk(LOCAL_PR_FMT" scheduler=%s call_id=0x%x", + LOCAL_PR_ARG, __get_str(scheduler_name), __entry->call_id) + ); + +TRACE_EVENT(ca_set_region_params, + TP_PROTO(const struct mcps802154_local *local, + const char *scheduler_name, u32 region_id, + const char *region_name), + TP_ARGS(local, scheduler_name, region_id, region_name), + TP_STRUCT__entry( + LOCAL_ENTRY + __string(scheduler_name, scheduler_name) + __field(u32, region_id) + __string(region_name, region_name) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __assign_str(scheduler_name, scheduler_name); + __entry->region_id = region_id; + __assign_str(region_name, region_name); + ), + TP_printk(LOCAL_PR_FMT " scheduler=%s region_id=%u region_name=%s", + LOCAL_PR_ARG, __get_str(scheduler_name), __entry->region_id, + __get_str(region_name)) + ); + +TRACE_EVENT(ca_call_region, + TP_PROTO(const struct mcps802154_local *local, + const char *scheduler_name, u32 region_id, + const char *region_name, u32 call_id), + TP_ARGS(local, scheduler_name, region_id, region_name, call_id), + TP_STRUCT__entry( + LOCAL_ENTRY + __string(scheduler_name, scheduler_name) + __field(u32, region_id) + __string(region_name, region_name) + __field(u32, call_id) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __assign_str(scheduler_name, scheduler_name); + __entry->region_id = region_id; + __assign_str(region_name, region_name); + __entry->call_id = call_id; + ), + TP_printk(LOCAL_PR_FMT " scheduler=%s region_id=%u region_name=%s call_id=0x%x", + LOCAL_PR_ARG, __get_str(scheduler_name), __entry->region_id, + __get_str(region_name), __entry->call_id) + ); + +TRACE_EVENT(ca_get_access, + TP_PROTO(const struct mcps802154_local *local, u32 next_timestamp_dtu), + TP_ARGS(local, next_timestamp_dtu), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(u32, next_timestamp_dtu) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->next_timestamp_dtu = next_timestamp_dtu; + ), + TP_printk(LOCAL_PR_FMT " next_timestamp_dtu=0x%08x", LOCAL_PR_ARG, + __entry->next_timestamp_dtu) + ); + +TRACE_EVENT(ca_return_int, + TP_PROTO(const struct mcps802154_local *local, int r), + TP_ARGS(local, r), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(int, r) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->r = r; + ), + TP_printk(LOCAL_PR_FMT " r=%d", LOCAL_PR_ARG, __entry->r) + ); + +TRACE_EVENT(fproc_broken_enter, + TP_PROTO(const struct mcps802154_local *local), + TP_ARGS(local), + TP_STRUCT__entry( + LOCAL_ENTRY + ), + TP_fast_assign( + LOCAL_ASSIGN; + ), + TP_printk(LOCAL_PR_FMT, LOCAL_PR_ARG) + ); + +TRACE_EVENT(schedule_update, + TP_PROTO(const struct mcps802154_local *local, u32 next_timestamp_dtu), + TP_ARGS(local, next_timestamp_dtu), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(u32, next_timestamp_dtu) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->next_timestamp_dtu = next_timestamp_dtu; + ), + TP_printk(LOCAL_PR_FMT " next_timestamp_dtu=0x%08x", LOCAL_PR_ARG, + __entry->next_timestamp_dtu) + ); + +TRACE_EVENT(update_schedule_error, + TP_PROTO(const struct mcps802154_local *local, + const struct mcps802154_schedule_update *su, + u32 next_timestamp_dtu), + TP_ARGS(local, su, next_timestamp_dtu), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(u32, expected_start_timestamp_dtu) + __field(u32, start_timestamp_dtu) + __field(int, duration_dtu) + __field(size_t, n_regions) + __field(u32, next_timestamp_dtu) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->expected_start_timestamp_dtu = su->expected_start_timestamp_dtu; + __entry->start_timestamp_dtu = su->start_timestamp_dtu; + __entry->duration_dtu = su->duration_dtu; + __entry->n_regions = su->n_regions; + __entry->next_timestamp_dtu = next_timestamp_dtu; + ), + TP_printk(LOCAL_PR_FMT " expected_start_timestamp_dtu=0x%08x " + "start_timestamp_dtu=0x%08x duration_dtu=%d " + "n_regions=%lu next_timestamp_dtu=0x%08x", + LOCAL_PR_ARG, + __entry->expected_start_timestamp_dtu, + __entry->start_timestamp_dtu, + __entry->duration_dtu, + __entry->n_regions, + __entry->next_timestamp_dtu) + ); + +TRACE_EVENT(schedule_update_done, + TP_PROTO(const struct mcps802154_local *local, + const struct mcps802154_schedule *sched), + TP_ARGS(local, sched), + TP_STRUCT__entry( + LOCAL_ENTRY + __field(u32, start_timestamp_dtu) + __field(int, duration_dtu) + __field(size_t, n_regions) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __entry->start_timestamp_dtu = sched->start_timestamp_dtu; + __entry->duration_dtu = sched->duration_dtu; + __entry->n_regions = sched->n_regions; + ), + TP_printk(LOCAL_PR_FMT " start_timestamp_dtu=0x%08x duration_dtu=%d n_regions=%lu", + LOCAL_PR_ARG, __entry->start_timestamp_dtu, + __entry->duration_dtu, __entry->n_regions) + ); + +TRACE_EVENT( + region_notify_stop, + TP_PROTO(const struct mcps802154_region *region), + TP_ARGS(region), + TP_STRUCT__entry( + __string(region_name, region->ops->name) + ), + TP_fast_assign( + __assign_str(region_name, region->ops->name); + ), + TP_printk("region=%s", + __get_str(region_name) + ) + ); + +TRACE_EVENT( + region_get_demand, + TP_PROTO(const struct mcps802154_region *region, + u32 next_timestamp_dtu), + TP_ARGS(region, next_timestamp_dtu), + TP_STRUCT__entry( + __string(region_name, region->ops->name) + __field(u32, next_timestamp_dtu) + ), + TP_fast_assign( + __assign_str(region_name, region->ops->name); + __entry->next_timestamp_dtu = next_timestamp_dtu; + ), + TP_printk("region=%s next_timestamp_dtu=%#x", + __get_str(region_name), + __entry->next_timestamp_dtu + ) + ); + +TRACE_EVENT( + region_get_demand_return, + TP_PROTO(const struct mcps802154_region *region, + const struct mcps802154_region_demand *demand, + int r), + TP_ARGS(region, demand, r), + TP_STRUCT__entry( + __field(int, r) + __field(u32, timestamp_dtu) + __field(u32, max_duration_dtu) + ), + TP_fast_assign( + __entry->r = r; + __entry->timestamp_dtu = demand->timestamp_dtu; + __entry->max_duration_dtu = demand->max_duration_dtu; + ), + TP_printk("r=%d timestamp_dtu=%#x max_duration_dtu=%d", + __entry->r, + __entry->timestamp_dtu, + __entry->max_duration_dtu + ) + ); + +TRACE_EVENT(region_get_access, + TP_PROTO(const struct mcps802154_local *local, + const struct mcps802154_region *region, + u32 next_timestamp_dtu, int next_in_region_dtu, + int region_duration_dtu), + TP_ARGS(local, region, next_timestamp_dtu, next_in_region_dtu, + region_duration_dtu), + TP_STRUCT__entry( + LOCAL_ENTRY + __string(region_name, region->ops->name) + __field(u32, next_timestamp_dtu) + __field(int, next_in_region_dtu) + __field(int, region_duration_dtu) + ), + TP_fast_assign( + LOCAL_ASSIGN; + __assign_str(region_name, region->ops->name); + __entry->next_timestamp_dtu = next_timestamp_dtu; + __entry->next_in_region_dtu = next_in_region_dtu; + __entry->region_duration_dtu = region_duration_dtu; + ), + TP_printk(LOCAL_PR_FMT " region=%s next_timestamp_dtu=0x%08x next_in_region_dtu=%d region_duration_dtu=%d", + LOCAL_PR_ARG, + __get_str(region_name), __entry->next_timestamp_dtu, + __entry->next_in_region_dtu, __entry->region_duration_dtu) + ); + +TRACE_EVENT( + region_idle_params, + TP_PROTO(const struct idle_params *params), + TP_ARGS(params), + TP_STRUCT__entry( + __field(s32, min_duration_dtu) + __field(s32, max_duration_dtu) + ), + TP_fast_assign( + __entry->min_duration_dtu = params->min_duration_dtu; + __entry->max_duration_dtu = params->max_duration_dtu; + ), + TP_printk("min_duration_dtu=%d max_duration_dtu=%d", + __entry->min_duration_dtu, + __entry->max_duration_dtu) +); + +TRACE_EVENT( + region_idle_get_access, + TP_PROTO(u32 timestamp_dtu, int duration_dtu), + TP_ARGS(timestamp_dtu, duration_dtu), + TP_STRUCT__entry( + __field(u32, timestamp_dtu) + __field(int, duration_dtu) + ), + TP_fast_assign( + __entry->timestamp_dtu = timestamp_dtu; + __entry->duration_dtu = duration_dtu; + ), + TP_printk("timestamp_dtu=%#x duration_dtu=%d", + __entry->timestamp_dtu, + __entry->duration_dtu) +); + +#endif /* !TRACE_H || TRACE_HEADER_MULTI_READ */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace +#include <trace/define_trace.h> diff --git a/mac/warn_return.h b/mac/warn_return.h new file mode 100644 index 0000000..66d95dd --- /dev/null +++ b/mac/warn_return.h @@ -0,0 +1,47 @@ +/* + * This file is part of the UWB stack for linux. + * + * Copyright (c) 2020-2021 Qorvo US, Inc. + * + * This software is provided under the GNU General Public License, version 2 + * (GPLv2), as well as under a Qorvo commercial license. + * + * You may choose to use this software under the terms of the GPLv2 License, + * version 2 ("GPLv2"), as published by the Free Software Foundation. + * You should have received a copy of the GPLv2 along with this program. If + * not, see <http://www.gnu.org/licenses/>. + * + * This program is distributed under the GPLv2 in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more + * details. + * + * If you cannot meet the requirements of the GPLv2, you may not use this + * software for any purpose without first obtaining a commercial license from + * Qorvo. Please contact Qorvo to inquire about licensing terms. + */ + +#ifndef WARN_RETURN_H +#define WARN_RETURN_H + +#define WARN_RETURN(r) \ + do { \ + if (WARN_ON(r)) \ + return (r); \ + } while (0) + +#define WARN_RETURN_ON(r, ret) \ + do { \ + if (WARN_ON(r)) \ + return (ret); \ + } while (0) + +#define WARN_RETURN_VOID_ON(r) \ + do { \ + if (WARN_ON(r)) \ + return; \ + } while (0) + +#define WARN_UNREACHABLE_DEFAULT() WARN(1, "unreachable case") + +#endif /* WARN_RETURN_H */ diff --git a/tools/calibrations/pdoa_lut_generation b/tools/calibrations/pdoa_lut_generation new file mode 100755 index 0000000..dc850ee --- /dev/null +++ b/tools/calibrations/pdoa_lut_generation @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 + +import os +import sys +import math +import json +from optparse import OptionParser + +# This defaulr value is sync with: +# dw3000_calib.c:DW3000_CALIBRATION_PDOA_LUT_MAX +DW3000_CALIBRATION_PDOA_LUT_MAX = 31 + +usage="""%s [options] + +This script aims to dump a binary blob suitable for uwb-stack calibration file. +It works in two modes : + 1. Input JSON LUT + 2. Antenna spacing and formula + +== Input JSON LUT == +It takes a JSON input on stdin and dumps the corresponding blob on stdout. +The JSON input must be a list of either: + * lists containing 2 items of type float, 1st is assumed to be PDoA, 2nd AoA, + * maps containing 'aoa' and 'pdoa' keys, and corresponding values of type float. + float values are assumed to be in radian unit. + +== Antenna spacing and formula == +This mode uses the theorical math formula to compute AoA value from PDoA measurement. +It also uses the antenna spacing parameter, please ensure to provide the right value. +2 sub modes are possible: + * PDoA value are provided in list of float that matches the LUT size + * --no-json option is specified or empty input is provided, then the tool will + split the [-Pi, Pi] range in %d equal parts and use it as PDoA value list +"""%( + os.path.basename(sys.argv[0]), + DW3000_CALIBRATION_PDOA_LUT_MAX, + ) + + +# ==== Fixed point / math utils ==== + +Q = 11 +K = 2 ** Q +S16_MAX = ((2 ** 15) - 1) +S16_MIN = -(2 ** 15) + +speed_of_light_m_per_s = 299702547 + +def freq_hz(chan): + if chan == 5: + return 6.5e9 + elif chan == 9: + return 7.9872e9 + else: + raise ValueError("Channel must be either 5 or 9") + +def sat_uf(x): + return min(S16_MAX, max(S16_MIN, x)) + +def float_to_q11(x): + return sat_uf(round(x * K)) + +pi_q = float_to_q11(math.pi) + +def pdoa_to_aoa_rad(x, chan, antenna_dist_m): + L_M = speed_of_light_m_per_s / freq_hz(chan) + phase_m = x * L_M / (2.0 * math.pi) + rad = phase_m / antenna_dist_m + rad = min(1.0, max(-1.0, rad)) + return math.asin(rad) + +# ================================== + +def blob_item(pdoa, aoa, fmt, byteorder): + pdoa_q11 = float_to_q11(pdoa) + aoa_q11 = float_to_q11(aoa) + pdoa_bytes = pdoa_q11.to_bytes(2, byteorder=byteorder, signed=True) + aoa_bytes = aoa_q11.to_bytes(2, byteorder=byteorder, signed=True) + return fmt%(pdoa_bytes[0], pdoa_bytes[1], aoa_bytes[0], aoa_bytes[1]) + +def input_json_lut(pdoa_lut, options): + print("Generating LUT using 'input JSON LUT' mode...") + print("LUT = %s"%str(pdoa_lut)) + if not isinstance(pdoa_lut, list) \ + or (options.size_check and (len(pdoa_lut) != options.lut_size)): + raise ValueError("input JSON must be list of size %d" + %(str(options.lut_size))) + + blob = "" + first_val = True + internal_pdoa_lut = [] + for item in pdoa_lut: + if isinstance(item, list) and len(item) == 2: + pdoa = item[0] + aoa = item[1] + elif isinstance(item, dict): + # NB: exception raised if key not provided in input data + pdoa = item['pdoa'] + aoa = item['aoa'] + else: + raise ValueError("input JSON list item must of type list \ +[float, float] (PDoA then AoA) or map {'aoa':float, 'pdoa':float}") + internal_pdoa_lut.append((pdoa, aoa)) + + internal_pdoa_lut.sort(key=lambda x: x[0]) + + for item in internal_pdoa_lut: + pdoa = item[0] + aoa = item[1] + if first_val: + first_val = False + else: + blob += options.sep + blob += blob_item(pdoa, aoa, options.fmt, options.byteorder) + + return blob + +def antenna_spacing_and_formula(pdoa_values, options): + print("Generating LUT using 'antenna spacing and formula' mode...") + print("PDoA values = %s"%(str(pdoa_values))) + print("Antenna spacing (mm) = %f"%(options.antenna_spacing / 1000.0)) + if not isinstance(pdoa_values, list) \ + or (options.size_check and (len(pdoa_values) != options.lut_size)): + raise ValueError("input JSON must be list of size %d" + %(options.lut_size)) + blob = "" + first_val = True + + for value in pdoa_values: + pdoa = float(value) # would raise TypeError + aoa = pdoa_to_aoa_rad(pdoa, options.channel, + options.antenna_spacing / 1000000.0) + print("\t%f\t=>\t%f"%(pdoa, aoa)) + if first_val: + first_val = False + else: + blob += options.sep + blob += blob_item(pdoa, aoa, options.fmt, options.byteorder) + return blob + +def float_range(start, stop, step): + while start < stop: + yield float(start) + start += step + +if __name__ == "__main__": + parser = OptionParser(usage=usage) + parser.add_option("--channel", dest="channel", type="int", default=5, + help="UWB Channel (5 or 9, default is 5)") + parser.add_option("--formula-mode", dest="formula_mode", + action="store_true", default=False, + help="selects formula mode") + parser.add_option("--antenna-spacing", dest="antenna_spacing", + type="int", default=20800, + help="sets the antenna pair spacing used in formula \ + mode, unit is micrometers (default value 20800 \ + matches Mona Lisa design).") + parser.add_option("--byteorder", dest="byteorder", + type="string", default=sys.byteorder, + help="Override byte order. Default is the system's \ + byteorder (%s)"%(sys.byteorder)) + parser.add_option("--lut-size", dest="lut_size", + type="int", default=DW3000_CALIBRATION_PDOA_LUT_MAX, + help="Override default lut size (%d)"%(DW3000_CALIBRATION_PDOA_LUT_MAX)) + parser.add_option("--no-size-check", dest="size_check", + action="store_false", default=True, + help="skips length of the input map.") + parser.add_option("--dump-c-array", dest="dump_c_array", + action="store_true", default=False, + help="Dumps a C array instead of calib blob") + parser.add_option("--no-input", dest="read_stdin", + action="store_false", default=True, + help="Skips reading input on stdin") + (options, args) = parser.parse_args() + + if options.dump_c_array: + options.sep = ",\n" + options.fmt = "\t{ 0x%02x%02x, 0x%02x%02x }" + options.byteorder = 'big' + else: + options.sep = ":" + options.fmt = "%02x:%02x:%02x:%02x" + + txt_input = "" + if options.read_stdin: + for line in sys.stdin: + txt_input += line + + if options.formula_mode: + if txt_input: + pdoa_values = json.loads(txt_input) + else: + step = 2 * math.pi / (options.lut_size-1) + pdoa_values = list(float_range(-math.pi, math.pi, step)) + blob = antenna_spacing_and_formula(pdoa_values, options) + else: + pdoa_lut = json.loads(txt_input) + blob = input_json_lut(pdoa_lut, options) + + if options.dump_c_array: + print("const dw3000_pdoa_lut_t pdoa_lut = {\n" + blob + "\n};") + else: + print("antX.antY.chN.pdoa_lut " + blob) |